package lnd
import (
"fmt"
"maps"
"sync"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/channeldb"
)
// accessMan is responsible for managing the server's access permissions.
type accessMan struct {
cfg *accessManConfig
// banScoreMtx is used for the server's ban tracking. If the server
// mutex is also going to be locked, ensure that this is locked after
// the server mutex.
banScoreMtx sync.RWMutex
// peerCounts is a mapping from remote public key to {bool, uint64}
// where the bool indicates that we have an open/closed channel with
// the peer and where the uint64 indicates the number of pending-open
// channels we currently have with them. This mapping will be used to
// determine access permissions for the peer. The map key is the
// string-version of the serialized public key.
//
// NOTE: This MUST be accessed with the banScoreMtx held.
peerCounts map[string]channeldb.ChanCount
// peerScores stores each connected peer's access status. The map key
// is the string-version of the serialized public key.
//
// NOTE: This MUST be accessed with the banScoreMtx held.
peerScores map[string]peerSlotStatus
// numRestricted tracks the number of peers with restricted access in
// peerScores. This MUST be accessed with the banScoreMtx held.
numRestricted int64
}
type accessManConfig struct {
// initAccessPerms checks the channeldb for initial access permissions
// and then populates the peerCounts and peerScores maps.
initAccessPerms func() (map[string]channeldb.ChanCount, error)
// shouldDisconnect determines whether we should disconnect a peer or
// not.
shouldDisconnect func(*btcec.PublicKey) (bool, error)
// maxRestrictedSlots is the number of restricted slots we'll allocate.
maxRestrictedSlots int64
}
func newAccessMan(cfg *accessManConfig) (*accessMan, error) {
a := &accessMan{
cfg: cfg,
peerCounts: make(map[string]channeldb.ChanCount),
peerScores: make(map[string]peerSlotStatus),
}
counts, err := a.cfg.initAccessPerms()
if err != nil {
return nil, err
}
// We'll populate the server's peerCounts map with the counts fetched
// via initAccessPerms. Also note that we haven't yet connected to the
// peers.
maps.Copy(a.peerCounts, counts)
return a, nil
}
// assignPeerPerms assigns a new peer its permissions. This does not track the
// access in the maps. This is intentional.
func (a *accessMan) assignPeerPerms(remotePub *btcec.PublicKey) (
peerAccessStatus, error) {
// Default is restricted unless the below filters say otherwise.
access := peerStatusRestricted
shouldDisconnect, err := a.cfg.shouldDisconnect(remotePub)
if err != nil {
// Access is restricted here.
return access, err
}
if shouldDisconnect {
// Access is restricted here.
return access, ErrGossiperBan
}
peerMapKey := string(remotePub.SerializeCompressed())
// Lock banScoreMtx for reading so that we can update the banning maps
// below.
a.banScoreMtx.RLock()
defer a.banScoreMtx.RUnlock()
if count, found := a.peerCounts[peerMapKey]; found {
if count.HasOpenOrClosedChan {
access = peerStatusProtected
} else if count.PendingOpenCount != 0 {
access = peerStatusTemporary
}
}
// If we've reached this point and access hasn't changed from
// restricted, then we need to check if we even have a slot for this
// peer.
if a.numRestricted >= a.cfg.maxRestrictedSlots &&
access == peerStatusRestricted {
return access, ErrNoMoreRestrictedAccessSlots
}
return access, nil
}
// newPendingOpenChan is called after the pending-open channel has been
// committed to the database. This may transition a restricted-access peer to a
// temporary-access peer.
func (a *accessMan) newPendingOpenChan(remotePub *btcec.PublicKey) error {
a.banScoreMtx.Lock()
defer a.banScoreMtx.Unlock()
peerMapKey := string(remotePub.SerializeCompressed())
// Fetch the peer's access status from peerScores.
status, found := a.peerScores[peerMapKey]
if !found {
// If we didn't find the peer, we'll return an error.
return ErrNoPeerScore
}
switch status.state {
case peerStatusProtected:
// If this peer's access status is protected, we don't need to
// do anything.
return nil
case peerStatusTemporary:
// If this peer's access status is temporary, we'll need to
// update the peerCounts map. The peer's access status will
// stay temporary.
peerCount, found := a.peerCounts[peerMapKey]
if !found {
// Error if we did not find any info in peerCounts.
return ErrNoPendingPeerInfo
}
// Increment the pending channel amount.
peerCount.PendingOpenCount += 1
a.peerCounts[peerMapKey] = peerCount
case peerStatusRestricted:
// If the peer's access status is restricted, then we can
// transition it to a temporary-access peer. We'll need to
// update numRestricted and also peerScores. We'll also need to
// update peerCounts.
peerCount := channeldb.ChanCount{
HasOpenOrClosedChan: false,
PendingOpenCount: 1,
}
a.peerCounts[peerMapKey] = peerCount
// A restricted-access slot has opened up.
a.numRestricted -= 1
a.peerScores[peerMapKey] = peerSlotStatus{
state: peerStatusTemporary,
}
default:
// This should not be possible.
return fmt.Errorf("invalid peer access status")
}
return nil
}
// newPendingCloseChan is called when a pending-open channel prematurely closes
// before the funding transaction has confirmed. This potentially demotes a
// temporary-access peer to a restricted-access peer. If no restricted-access
// slots are available, the peer will be disconnected.
func (a *accessMan) newPendingCloseChan(remotePub *btcec.PublicKey) error {
a.banScoreMtx.Lock()
defer a.banScoreMtx.Unlock()
peerMapKey := string(remotePub.SerializeCompressed())
// Fetch the peer's access status from peerScores.
status, found := a.peerScores[peerMapKey]
if !found {
return ErrNoPeerScore
}
switch status.state {
case peerStatusProtected:
// If this peer is protected, we don't do anything.
return nil
case peerStatusTemporary:
// If this peer is temporary, we need to check if it will
// revert to a restricted-access peer.
peerCount, found := a.peerCounts[peerMapKey]
if !found {
// Error if we did not find any info in peerCounts.
return ErrNoPendingPeerInfo
}
currentNumPending := peerCount.PendingOpenCount - 1
if currentNumPending == 0 {
// Remove the entry from peerCounts.
delete(a.peerCounts, peerMapKey)
// If this is the only pending-open channel for this
// peer and it's getting removed, attempt to demote
// this peer to a restricted peer.
if a.numRestricted == a.cfg.maxRestrictedSlots {
// There are no available restricted slots, so
// we need to disconnect this peer. We leave
// this up to the caller.
return ErrNoMoreRestrictedAccessSlots
}
// Otherwise, there is an available restricted-access
// slot, so we can demote this peer.
a.peerScores[peerMapKey] = peerSlotStatus{
state: peerStatusRestricted,
}
// Update numRestricted.
a.numRestricted++
return nil
}
// Else, we don't need to demote this peer since it has other
// pending-open channels with us.
peerCount.PendingOpenCount = currentNumPending
a.peerCounts[peerMapKey] = peerCount
return nil
case peerStatusRestricted:
// This should not be possible. This indicates an error.
return fmt.Errorf("invalid peer access state transition")
default:
// This should not be possible.
return fmt.Errorf("invalid peer access status")
}
}
// newOpenChan is called when a pending-open channel becomes an open channel
// (i.e. the funding transaction has confirmed). If the remote peer is a
// temporary-access peer, it will be promoted to a protected-access peer.
func (a *accessMan) newOpenChan(remotePub *btcec.PublicKey) error {
a.banScoreMtx.Lock()
defer a.banScoreMtx.Unlock()
peerMapKey := string(remotePub.SerializeCompressed())
// Fetch the peer's access status from peerScores.
status, found := a.peerScores[peerMapKey]
if !found {
// If we didn't find the peer, we'll return an error.
return ErrNoPeerScore
}
switch status.state {
case peerStatusProtected:
// If the peer's state is already protected, we don't need to
// do anything more.
return nil
case peerStatusTemporary:
// If the peer's state is temporary, we'll upgrade the peer to
// a protected peer.
peerCount, found := a.peerCounts[peerMapKey]
if !found {
// Error if we did not find any info in peerCounts.
return ErrNoPendingPeerInfo
}
peerCount.HasOpenOrClosedChan = true
a.peerCounts[peerMapKey] = peerCount
newStatus := peerSlotStatus{
state: peerStatusProtected,
}
a.peerScores[peerMapKey] = newStatus
return nil
case peerStatusRestricted:
// This should not be possible. For the server to receive a
// state-transition event via NewOpenChan, the server must have
// previously granted this peer "temporary" access. This
// temporary access would not have been revoked or downgraded
// without `CloseChannel` being called with the pending
// argument set to true. This means that an open-channel state
// transition would be impossible. Therefore, we can return an
// error.
return fmt.Errorf("invalid peer access status")
default:
// This should not be possible.
return fmt.Errorf("invalid peer access status")
}
}
// checkIncomingConnBanScore checks whether, given the remote's public hex-
// encoded key, we should not accept this incoming connection or immediately
// disconnect. This does not assign to the server's peerScores maps. This is
// just an inbound filter that the brontide listeners use.
func (a *accessMan) checkIncomingConnBanScore(remotePub *btcec.PublicKey) (
bool, error) {
a.banScoreMtx.RLock()
defer a.banScoreMtx.RUnlock()
peerMapKey := string(remotePub.SerializeCompressed())
if _, found := a.peerCounts[peerMapKey]; !found {
// Check numRestricted to see if there is an available slot. In
// the future, it's possible to add better heuristics.
if a.numRestricted < a.cfg.maxRestrictedSlots {
// There is an available slot.
return true, nil
}
// If there are no slots left, then we reject this connection.
return false, ErrNoMoreRestrictedAccessSlots
}
// Else, the peer is either protected or temporary.
return true, nil
}
// addPeerAccess tracks a peer's access in the maps. This should be called when
// the peer has fully connected.
func (a *accessMan) addPeerAccess(remotePub *btcec.PublicKey,
access peerAccessStatus) {
// Add the remote public key to peerScores.
a.banScoreMtx.Lock()
defer a.banScoreMtx.Unlock()
peerMapKey := string(remotePub.SerializeCompressed())
a.peerScores[peerMapKey] = peerSlotStatus{state: access}
// Increment numRestricted.
if access == peerStatusRestricted {
a.numRestricted++
}
}
// removePeerAccess removes the peer's access from the maps. This should be
// called when the peer has been disconnected.
func (a *accessMan) removePeerAccess(remotePub *btcec.PublicKey) {
a.banScoreMtx.Lock()
defer a.banScoreMtx.Unlock()
peerMapKey := string(remotePub.SerializeCompressed())
status, found := a.peerScores[peerMapKey]
if !found {
return
}
if status.state == peerStatusRestricted {
// If the status is restricted, then we decrement from
// numRestrictedSlots.
a.numRestricted--
}
delete(a.peerScores, peerMapKey)
}
package aezeed
import (
"bytes"
"crypto/rand"
"encoding/binary"
"hash/crc32"
"io"
"time"
"github.com/Yawning/aez"
"github.com/kkdai/bstream"
"golang.org/x/crypto/scrypt"
)
const (
// CipherSeedVersion is the current version of the aezeed scheme as
// defined in this package. This version indicates the following
// parameters for the deciphered cipher seed: a 1 byte version, 2 bytes
// for the Bitcoin Days Genesis timestamp, and 16 bytes for entropy. It
// also governs how the cipher seed should be enciphered. In this
// version we take the deciphered seed, create a 5 byte salt, use that
// with an optional passphrase to generate a 32-byte key (via scrypt),
// then encipher with aez (using the salt and version as AD). The final
// enciphered seed is: version || ciphertext || salt.
CipherSeedVersion uint8 = 0
// DecipheredCipherSeedSize is the size of the plaintext seed resulting
// from deciphering the cipher seed. The size consists of the
// following:
//
// * 1 byte version || 2 bytes timestamp || 16 bytes of entropy.
//
// The version is used by wallets to know how to re-derive relevant
// addresses, the 2 byte timestamp a BDG (Bitcoin Days Genesis) offset,
// and finally, the 16 bytes to be used to generate the HD wallet seed.
DecipheredCipherSeedSize = 19
// EncipheredCipherSeedSize is the size of the fully encoded+enciphered
// cipher seed. We first obtain the enciphered plaintext seed by
// carrying out the enciphering as governed in the current version. We
// then take that enciphered seed (now 19+4=23 bytes due to ciphertext
// expansion, essentially a checksum) and prepend a version, then
// append the salt, and then take a checksum of everything. The
// checksum allows us to verify that the user input the correct set of
// words, then we can verify the passphrase due to the internal MAC
// equiv. The final breakdown is:
//
// * 1 byte version || 23 byte enciphered seed || 5 byte salt || 4 byte checksum
//
// With CipherSeedVersion we encipher as follows: we use
// scrypt(n=32768, r=8, p=1) to derive a 32-byte key from an optional
// user passphrase. We then encipher the plaintext seed using a value
// of tau (with aez) of 8-bytes (so essentially a 32-bit MAC). When
// enciphering, we include the version and scrypt salt as the AD. This
// gives us a total of 33 bytes. These 33 bytes fit cleanly into 24
// mnemonic words.
EncipheredCipherSeedSize = 33
// CipherTextExpansion is the number of bytes that will be added as
// redundancy for the enciphering scheme implemented by aez. This can
// be seen as the size of the equivalent MAC.
CipherTextExpansion = 4
// EntropySize is the number of bytes of entropy we'll use to generate
// the seed.
EntropySize = 16
// NumMnemonicWords is the number of words that an encoded cipher seed
// will result in.
NumMnemonicWords = 24
// SaltSize is the size of the salt we'll generate to use with scrypt
// to generate a key for use within aez from the user's passphrase. The
// role of the salt is to make the creation of rainbow tables
// infeasible.
SaltSize = 5
// adSize is the size of the encoded associated data that will be
// passed into aez when enciphering and deciphering the seed. The AD
// itself (associated data) is just the cipher seed version and salt.
adSize = 6
// checkSumSize is the size of the checksum applied to the final
// encoded ciphertext.
checkSumSize = 4
// keyLen is the size of the key that we'll use for encryption with
// aez.
keyLen = 32
// BitsPerWord is the number of bits each word in the wordlist encodes.
// We encode our mnemonic using 24 words, so 264 bits (33 bytes).
BitsPerWord = 11
// saltOffset is the index within an enciphered cipher seed that marks
// the start of the salt.
saltOffset = EncipheredCipherSeedSize - checkSumSize - SaltSize
// checkSumSize is the index within an enciphered cipher seed that
// marks the start of the checksum.
checkSumOffset = EncipheredCipherSeedSize - checkSumSize
)
var (
// Below at the default scrypt parameters that are tied to cipher seed
// version zero.
scryptN = 32768
scryptR = 8
scryptP = 1
// crcTable is a table that presents the polynomial we'll use for
// computing our checksum.
crcTable = crc32.MakeTable(crc32.Castagnoli)
// defaultPassphrase is the default passphrase that will be used for
// encryption in the case that the user chooses not to specify their
// own passphrase.
defaultPassphrase = []byte("aezeed")
)
var (
// BitcoinGenesisDate is the timestamp of Bitcoin's genesis block.
// We'll use this value in order to create a compact birthday for the
// seed. The birthday will be interested as the number of days since
// the genesis date. We refer to this time period as ABE (after Bitcoin
// era).
BitcoinGenesisDate = time.Unix(1231006505, 0)
)
// SeedOptions is a type that holds options that configure the generation of a
// new cipher seed.
type SeedOptions struct {
// randomnessSource is the source of randomness that is used to generate
// the salt that is used for encrypting the seed.
randomnessSource io.Reader
}
// DefaultOptions returns the default seed options.
func DefaultOptions() *SeedOptions {
return &SeedOptions{
randomnessSource: rand.Reader,
}
}
// SeedOptionModifier is a function signature for modifying the default
// SeedOptions.
type SeedOptionModifier func(*SeedOptions)
// WithRandomnessSource returns an option modifier that replaces the default
// randomness source with the given reader.
func WithRandomnessSource(src io.Reader) SeedOptionModifier {
return func(opts *SeedOptions) {
opts.randomnessSource = src
}
}
// CipherSeed is a fully decoded instance of the aezeed scheme. At a high
// level, the encoded cipher seed is the enciphering of: a version byte, a set
// of bytes for a timestamp, the entropy which will be used to directly
// construct the HD seed, and finally a checksum over the rest. This scheme was
// created as the widely used schemes in the space lack two critical traits: a
// version byte, and a birthday timestamp. The version allows us to modify the
// details of the scheme in the future, and the birthday gives wallets a limit
// of how far back in the chain they'll need to start scanning. We also add an
// external version to the enciphering plaintext seed. With this addition,
// seeds are able to be "upgraded" (to diff params, or entirely diff crypt),
// while maintaining the semantics of the plaintext seed.
//
// The core of the scheme is the usage of aez to carefully control the size of
// the final encrypted seed. With the current parameters, this scheme can be
// encoded using a 24 word mnemonic. We use 4 bytes of ciphertext expansion
// when enciphering the raw seed, giving us the equivalent of 40-bit MAC (as we
// check for a particular seed version). Using the external 4 byte checksum,
// we're able to ensure that the user input the correct set of words. Finally,
// the password in the scheme is optional. If not specified, "aezeed" will be
// used as the password. Otherwise, the addition of the password means that
// users can encrypt the raw "plaintext" seed under distinct passwords to
// produce unique mnemonic phrases.
type CipherSeed struct {
// InternalVersion is the version of the plaintext cipher seed. This is
// to be used by wallets to determine if the seed version is compatible
// with the derivation schemes they know.
InternalVersion uint8
// Birthday is the time that the seed was created. This is expressed as
// the number of days since the timestamp in the Bitcoin genesis block.
// We use days as seconds gives us wasted granularity. The oldest seed
// that we can encode using this format is through the date 2188.
Birthday uint16
// Entropy is a set of bytes generated via a CSPRNG. This is the value
// that should be used to directly generate the HD root, as defined
// within BIP0032.
Entropy [EntropySize]byte
// salt is the salt that was used to generate the key from the user's
// specified passphrase.
salt [SaltSize]byte
}
// New generates a new CipherSeed instance from an optional source of entropy.
// If the entropy isn't provided, then a set of random bytes will be used in
// place. The final fixed argument should be the time at which the seed was
// created, followed by optional seed option modifiers.
func New(internalVersion uint8, entropy *[EntropySize]byte,
now time.Time, modifiers ...SeedOptionModifier) (*CipherSeed, error) {
opts := DefaultOptions()
for _, modifier := range modifiers {
modifier(opts)
}
// If a set of entropy wasn't provided, then we'll read a set of bytes
// from the randomness source provided (which by default is the system's
// CSPRNG).
var seed [EntropySize]byte
if entropy == nil {
if _, err := opts.randomnessSource.Read(seed[:]); err != nil {
return nil, err
}
} else {
// Otherwise, we'll copy the set of bytes.
copy(seed[:], entropy[:])
}
// To compute our "birthday", we'll first use the current time, then
// subtract that from the Bitcoin Genesis Date. We'll then convert that
// value to days.
birthday := uint16(now.Sub(BitcoinGenesisDate) / (time.Hour * 24))
c := &CipherSeed{
InternalVersion: internalVersion,
Birthday: birthday,
Entropy: seed,
}
// Next, we'll read a random salt that will be used with scrypt to
// eventually derive our key.
if _, err := opts.randomnessSource.Read(c.salt[:]); err != nil {
return nil, err
}
return c, nil
}
// encode attempts to encode the target cipherSeed into the passed io.Writer
// instance.
func (c *CipherSeed) encode(w io.Writer) error {
err := binary.Write(w, binary.BigEndian, c.InternalVersion)
if err != nil {
return err
}
if err := binary.Write(w, binary.BigEndian, c.Birthday); err != nil {
return err
}
if _, err := w.Write(c.Entropy[:]); err != nil {
return err
}
return nil
}
// decode attempts to decode an encoded cipher seed instance into the target
// CipherSeed struct.
func (c *CipherSeed) decode(r io.Reader) error {
err := binary.Read(r, binary.BigEndian, &c.InternalVersion)
if err != nil {
return err
}
if err := binary.Read(r, binary.BigEndian, &c.Birthday); err != nil {
return err
}
if _, err := io.ReadFull(r, c.Entropy[:]); err != nil {
return err
}
return nil
}
// encodeAD returns the fully encoded associated data for use when performing
// our current enciphering operation. The AD is: version || salt.
func encodeAD(version uint8, salt [SaltSize]byte) [adSize]byte {
var ad [adSize]byte
ad[0] = version
copy(ad[1:], salt[:])
return ad
}
// extractAD extracts an associated data from a fully encoded and enciphered
// cipher seed. This is to be used when attempting to decrypt an enciphered
// cipher seed.
func extractAD(encipheredSeed [EncipheredCipherSeedSize]byte) [adSize]byte {
var ad [adSize]byte
ad[0] = encipheredSeed[0]
copy(ad[1:], encipheredSeed[saltOffset:checkSumOffset])
return ad
}
// encipher takes a fully populated cipher seed instance, and enciphers the
// encoded seed, then appends a randomly generated seed used to stretch the
// passphrase out into an appropriate key, then computes a checksum over the
// preceding.
func (c *CipherSeed) encipher(pass []byte) ([EncipheredCipherSeedSize]byte,
error) {
var cipherSeedBytes [EncipheredCipherSeedSize]byte
// If the passphrase wasn't provided, then we'll use the string
// "aezeed" in place.
passphrase := pass
if len(passphrase) == 0 {
passphrase = defaultPassphrase
}
// With our salt pre-generated, we'll now run the password through a
// KDF to obtain the key we'll use for encryption.
key, err := scrypt.Key(
passphrase, c.salt[:], scryptN, scryptR, scryptP, keyLen,
)
if err != nil {
return cipherSeedBytes, err
}
// Next, we'll encode the serialized plaintext cipher seed into a buffer
// that we'll use for encryption.
var seedBytes bytes.Buffer
if err := c.encode(&seedBytes); err != nil {
return cipherSeedBytes, err
}
// With our plaintext seed encoded, we'll now construct the AD that
// will be passed to the encryption operation. This ensures to
// authenticate both the salt and the external version.
ad := encodeAD(CipherSeedVersion, c.salt)
// With all items assembled, we'll now encipher the plaintext seed
// with our AD, key, and MAC size.
cipherSeed := seedBytes.Bytes()
cipherText := aez.Encrypt(
key, nil, [][]byte{ad[:]}, CipherTextExpansion, cipherSeed, nil,
)
// Finally, we'll pack the {version || ciphertext || salt || checksum}
// seed into a byte slice for encoding as a mnemonic.
cipherSeedBytes[0] = CipherSeedVersion
copy(cipherSeedBytes[1:saltOffset], cipherText)
copy(cipherSeedBytes[saltOffset:], c.salt[:])
// With the seed mostly assembled, we'll now compute a checksum all the
// contents.
checkSum := crc32.Checksum(cipherSeedBytes[:checkSumOffset], crcTable)
// With our checksum computed, we can finish encoding the full cipher
// seed.
var checkSumBytes [4]byte
binary.BigEndian.PutUint32(checkSumBytes[:], checkSum)
copy(cipherSeedBytes[checkSumOffset:], checkSumBytes[:])
return cipherSeedBytes, nil
}
// cipherTextToMnemonic converts the aez ciphertext appended with the salt to a
// 24-word mnemonic pass phrase.
func cipherTextToMnemonic(cipherText [EncipheredCipherSeedSize]byte) (Mnemonic,
error) {
var words [NumMnemonicWords]string
// First, we'll convert the ciphertext itself into a bitstream for easy
// manipulation.
cipherBits := bstream.NewBStreamReader(cipherText[:])
// With our bitstream obtained, we'll read 11 bits at a time, then use
// that to index into our word list to obtain the next word.
for i := 0; i < NumMnemonicWords; i++ {
index, err := cipherBits.ReadBits(BitsPerWord)
if err != nil {
return Mnemonic{}, err
}
words[i] = DefaultWordList[index]
}
return words, nil
}
// ToMnemonic maps the final enciphered cipher seed to a human-readable 24-word
// mnemonic phrase. The password is optional, as if it isn't specified aezeed
// will be used in its place.
func (c *CipherSeed) ToMnemonic(pass []byte) (Mnemonic, error) {
// First, we'll convert the valid seed triple into an aez cipher text
// with our KDF salt appended to it.
cipherText, err := c.encipher(pass)
if err != nil {
return Mnemonic{}, err
}
// Now that we have our cipher text, we'll convert it into a mnemonic
// phrase.
return cipherTextToMnemonic(cipherText)
}
// Encipher maps the cipher seed to an aez ciphertext using an optional
// passphrase.
func (c *CipherSeed) Encipher(pass []byte) ([EncipheredCipherSeedSize]byte,
error) {
return c.encipher(pass)
}
// BirthdayTime returns the cipher seed's internal birthday format as a native
// golang Time struct.
func (c *CipherSeed) BirthdayTime() time.Time {
offset := time.Duration(c.Birthday) * 24 * time.Hour
return BitcoinGenesisDate.Add(offset)
}
// Mnemonic is a 24-word passphrase as of cipher seed version zero. This
// passphrase encodes an encrypted seed triple (version, birthday, entropy).
// Additionally, we also encode the salt used with scrypt to derive the key
// that the cipher text is encrypted with, and the version which tells us how
// to decipher the seed.
type Mnemonic [NumMnemonicWords]string
// mnemonicToCipherText converts a 24-word mnemonic phrase into a 33 byte
// cipher text.
//
// NOTE: This assumes that all words have already been checked to be amongst
// our word list.
func mnemonicToCipherText(mnemonic *Mnemonic) [EncipheredCipherSeedSize]byte {
var cipherText [EncipheredCipherSeedSize]byte
// We'll now perform the reverse mapping to that of
// cipherTextToMnemonic: we'll get the index of the word, then write
// out that index to the bit stream.
cipherBits := bstream.NewBStreamWriter(EncipheredCipherSeedSize)
for _, word := range mnemonic {
// Using the reverse word map, we'll locate the index of this
// word within the word list.
index := uint64(ReverseWordMap[word])
// With the index located, we'll now write this out to the
// bitstream, appending to what's already there.
cipherBits.WriteBits(index, BitsPerWord)
}
copy(cipherText[:], cipherBits.Bytes())
return cipherText
}
// ToCipherSeed attempts to map the mnemonic to the original cipher text byte
// slice. Then we'll attempt to decrypt the ciphertext using aez with the
// passed passphrase, using the last 5 bytes of the ciphertext as a salt for
// the KDF.
func (m *Mnemonic) ToCipherSeed(pass []byte) (*CipherSeed, error) {
// First, we'll attempt to decipher the mnemonic by mapping back into
// our byte slice and applying our deciphering scheme.
plainSeed, salt, err := m.Decipher(pass)
if err != nil {
return nil, err
}
// If decryption was successful, then we'll decode into a fresh
// CipherSeed struct.
c := CipherSeed{
salt: salt,
}
if err := c.decode(bytes.NewReader(plainSeed[:])); err != nil {
return nil, err
}
return &c, nil
}
// decipherCipherSeed attempts to decipher the passed cipher seed ciphertext
// using the passed passphrase. This function is the opposite of
// the encipher method.
func decipherCipherSeed(cipherSeedBytes [EncipheredCipherSeedSize]byte,
pass []byte) ([DecipheredCipherSeedSize]byte, [SaltSize]byte, error) {
var (
plainSeed [DecipheredCipherSeedSize]byte
salt [SaltSize]byte
)
// Before we do anything, we'll ensure that the version is one that we
// understand. Otherwise, we won't be able to decrypt, or even parse
// the cipher seed.
if cipherSeedBytes[0] != CipherSeedVersion {
return plainSeed, salt, ErrIncorrectVersion
}
// Next, we'll slice off the salt from the pass cipher seed, then
// snip off the end of the cipher seed, ignoring the version, and
// finally the checksum.
copy(salt[:], cipherSeedBytes[saltOffset:saltOffset+SaltSize])
cipherSeed := cipherSeedBytes[1:saltOffset]
checksum := cipherSeedBytes[checkSumOffset:]
// Before we perform any crypto operations, we'll re-create and verify
// the checksum to ensure that the user input the proper set of words.
freshChecksum := crc32.Checksum(
cipherSeedBytes[:checkSumOffset], crcTable,
)
if freshChecksum != binary.BigEndian.Uint32(checksum) {
return plainSeed, salt, ErrIncorrectMnemonic
}
// With the salt separated from the cipher text, we'll now obtain the
// key used for encryption.
key, err := scrypt.Key(pass, salt[:], scryptN, scryptR, scryptP, keyLen)
if err != nil {
return plainSeed, salt, err
}
// We'll also extract the AD that will be required to properly pass the
// MAC check.
ad := extractAD(cipherSeedBytes)
// With the key, we'll attempt to decrypt the plaintext. If the
// ciphertext was altered, or the passphrase is incorrect, then we'll
// error out.
plainSeedBytes, ok := aez.Decrypt(
key, nil, [][]byte{ad[:]}, CipherTextExpansion, cipherSeed, nil,
)
if !ok {
return plainSeed, salt, ErrInvalidPass
}
copy(plainSeed[:], plainSeedBytes)
return plainSeed, salt, nil
}
// Decipher attempts to decipher the encoded mnemonic by first mapping to the
// original ciphertext, then applying our deciphering scheme. ErrInvalidPass
// will be returned if the passphrase is incorrect.
func (m *Mnemonic) Decipher(pass []byte) ([DecipheredCipherSeedSize]byte,
[SaltSize]byte, error) {
// Before we attempt to map the mnemonic back to the original
// ciphertext, we'll ensure that all the word are actually a part of
// the current default word list.
wordDict := make(map[string]struct{}, len(DefaultWordList))
for _, word := range DefaultWordList {
wordDict[word] = struct{}{}
}
for i, word := range m {
if _, ok := wordDict[word]; !ok {
emptySeed := [DecipheredCipherSeedSize]byte{}
return emptySeed, [SaltSize]byte{},
ErrUnknownMnemonicWord{
Word: word,
Index: uint8(i),
}
}
}
// If the passphrase wasn't provided, then we'll use the string
// "aezeed" in place.
passphrase := pass
if len(passphrase) == 0 {
passphrase = defaultPassphrase
}
// Next, we'll map the mnemonic phrase back into the original cipher
// text.
cipherText := mnemonicToCipherText(m)
// Finally, we'll attempt to decipher the enciphered seed. The result
// will be the raw seed minus the ciphertext expansion, external
// version, and salt.
return decipherCipherSeed(cipherText, passphrase)
}
// ChangePass takes an existing mnemonic, and passphrase for said mnemonic and
// re-enciphers the plaintext cipher seed into a brand-new mnemonic. This can
// be used to allow users to re-encrypt the same seed with multiple pass
// phrases, or just change the passphrase on an existing seed.
func (m *Mnemonic) ChangePass(oldPass, newPass []byte) (Mnemonic, error) {
var newMnemonic Mnemonic
// First, we'll try to decrypt the current mnemonic using the existing
// passphrase. If this fails, then we can't proceed any further.
cipherSeed, err := m.ToCipherSeed(oldPass)
if err != nil {
return newMnemonic, err
}
// If the deciphering was successful, then we'll now re-encipher using
// the new user provided passphrase.
return cipherSeed.ToMnemonic(newPass)
}
package aezeed
import "fmt"
var (
// ErrIncorrectVersion is returned if a seed bares a mismatched
// external version to that of the package executing the aezeed scheme.
ErrIncorrectVersion = fmt.Errorf("wrong seed version")
// ErrInvalidPass is returned if the user enters an invalid passphrase
// for a particular enciphered mnemonic.
ErrInvalidPass = fmt.Errorf("invalid passphrase")
// ErrIncorrectMnemonic is returned if we detect that the checksum of
// the specified mnemonic doesn't match. This indicates the user input
// the wrong mnemonic.
ErrIncorrectMnemonic = fmt.Errorf("mnemonic phrase checksum doesn't " +
"match")
)
// ErrUnknownMnemonicWord is returned when attempting to decipher and
// enciphered mnemonic, but a word encountered isn't a member of our word list.
type ErrUnknownMnemonicWord struct {
// Word is the unknown word in the mnemonic phrase.
Word string
// Index is the index (starting from zero) within the slice of strings
// that makes up the mnemonic that points to the incorrect word.
Index uint8
}
// Error returns a human-readable string describing the error.
func (e ErrUnknownMnemonicWord) Error() string {
return fmt.Sprintf("word %v isn't a part of default word list "+
"(index=%v)", e.Word, e.Index)
}
package aezeed
import (
"strings"
)
var (
// ReverseWordMap maps a word to its position within the default word list.
ReverseWordMap map[string]int
)
func init() {
ReverseWordMap = make(map[string]int)
for i, v := range DefaultWordList {
ReverseWordMap[v] = i
}
}
// DefaultWordList is a slice of the current default word list that's used to
// encode the enciphered seed into a human readable set of words.
var DefaultWordList = strings.Split(englishWordList, "\n")
// englishWordList is an English wordlist that's used as part of version 0 of
// the cipherseed scheme. This is the *same* word list that's recommend for use
// with BIP0039.
var englishWordList = `abandon
ability
able
about
above
absent
absorb
abstract
absurd
abuse
access
accident
account
accuse
achieve
acid
acoustic
acquire
across
act
action
actor
actress
actual
adapt
add
addict
address
adjust
admit
adult
advance
advice
aerobic
affair
afford
afraid
again
age
agent
agree
ahead
aim
air
airport
aisle
alarm
album
alcohol
alert
alien
all
alley
allow
almost
alone
alpha
already
also
alter
always
amateur
amazing
among
amount
amused
analyst
anchor
ancient
anger
angle
angry
animal
ankle
announce
annual
another
answer
antenna
antique
anxiety
any
apart
apology
appear
apple
approve
april
arch
arctic
area
arena
argue
arm
armed
armor
army
around
arrange
arrest
arrive
arrow
art
artefact
artist
artwork
ask
aspect
assault
asset
assist
assume
asthma
athlete
atom
attack
attend
attitude
attract
auction
audit
august
aunt
author
auto
autumn
average
avocado
avoid
awake
aware
away
awesome
awful
awkward
axis
baby
bachelor
bacon
badge
bag
balance
balcony
ball
bamboo
banana
banner
bar
barely
bargain
barrel
base
basic
basket
battle
beach
bean
beauty
because
become
beef
before
begin
behave
behind
believe
below
belt
bench
benefit
best
betray
better
between
beyond
bicycle
bid
bike
bind
biology
bird
birth
bitter
black
blade
blame
blanket
blast
bleak
bless
blind
blood
blossom
blouse
blue
blur
blush
board
boat
body
boil
bomb
bone
bonus
book
boost
border
boring
borrow
boss
bottom
bounce
box
boy
bracket
brain
brand
brass
brave
bread
breeze
brick
bridge
brief
bright
bring
brisk
broccoli
broken
bronze
broom
brother
brown
brush
bubble
buddy
budget
buffalo
build
bulb
bulk
bullet
bundle
bunker
burden
burger
burst
bus
business
busy
butter
buyer
buzz
cabbage
cabin
cable
cactus
cage
cake
call
calm
camera
camp
can
canal
cancel
candy
cannon
canoe
canvas
canyon
capable
capital
captain
car
carbon
card
cargo
carpet
carry
cart
case
cash
casino
castle
casual
cat
catalog
catch
category
cattle
caught
cause
caution
cave
ceiling
celery
cement
census
century
cereal
certain
chair
chalk
champion
change
chaos
chapter
charge
chase
chat
cheap
check
cheese
chef
cherry
chest
chicken
chief
child
chimney
choice
choose
chronic
chuckle
chunk
churn
cigar
cinnamon
circle
citizen
city
civil
claim
clap
clarify
claw
clay
clean
clerk
clever
click
client
cliff
climb
clinic
clip
clock
clog
close
cloth
cloud
clown
club
clump
cluster
clutch
coach
coast
coconut
code
coffee
coil
coin
collect
color
column
combine
come
comfort
comic
common
company
concert
conduct
confirm
congress
connect
consider
control
convince
cook
cool
copper
copy
coral
core
corn
correct
cost
cotton
couch
country
couple
course
cousin
cover
coyote
crack
cradle
craft
cram
crane
crash
crater
crawl
crazy
cream
credit
creek
crew
cricket
crime
crisp
critic
crop
cross
crouch
crowd
crucial
cruel
cruise
crumble
crunch
crush
cry
crystal
cube
culture
cup
cupboard
curious
current
curtain
curve
cushion
custom
cute
cycle
dad
damage
damp
dance
danger
daring
dash
daughter
dawn
day
deal
debate
debris
decade
december
decide
decline
decorate
decrease
deer
defense
define
defy
degree
delay
deliver
demand
demise
denial
dentist
deny
depart
depend
deposit
depth
deputy
derive
describe
desert
design
desk
despair
destroy
detail
detect
develop
device
devote
diagram
dial
diamond
diary
dice
diesel
diet
differ
digital
dignity
dilemma
dinner
dinosaur
direct
dirt
disagree
discover
disease
dish
dismiss
disorder
display
distance
divert
divide
divorce
dizzy
doctor
document
dog
doll
dolphin
domain
donate
donkey
donor
door
dose
double
dove
draft
dragon
drama
drastic
draw
dream
dress
drift
drill
drink
drip
drive
drop
drum
dry
duck
dumb
dune
during
dust
dutch
duty
dwarf
dynamic
eager
eagle
early
earn
earth
easily
east
easy
echo
ecology
economy
edge
edit
educate
effort
egg
eight
either
elbow
elder
electric
elegant
element
elephant
elevator
elite
else
embark
embody
embrace
emerge
emotion
employ
empower
empty
enable
enact
end
endless
endorse
enemy
energy
enforce
engage
engine
enhance
enjoy
enlist
enough
enrich
enroll
ensure
enter
entire
entry
envelope
episode
equal
equip
era
erase
erode
erosion
error
erupt
escape
essay
essence
estate
eternal
ethics
evidence
evil
evoke
evolve
exact
example
excess
exchange
excite
exclude
excuse
execute
exercise
exhaust
exhibit
exile
exist
exit
exotic
expand
expect
expire
explain
expose
express
extend
extra
eye
eyebrow
fabric
face
faculty
fade
faint
faith
fall
false
fame
family
famous
fan
fancy
fantasy
farm
fashion
fat
fatal
father
fatigue
fault
favorite
feature
february
federal
fee
feed
feel
female
fence
festival
fetch
fever
few
fiber
fiction
field
figure
file
film
filter
final
find
fine
finger
finish
fire
firm
first
fiscal
fish
fit
fitness
fix
flag
flame
flash
flat
flavor
flee
flight
flip
float
flock
floor
flower
fluid
flush
fly
foam
focus
fog
foil
fold
follow
food
foot
force
forest
forget
fork
fortune
forum
forward
fossil
foster
found
fox
fragile
frame
frequent
fresh
friend
fringe
frog
front
frost
frown
frozen
fruit
fuel
fun
funny
furnace
fury
future
gadget
gain
galaxy
gallery
game
gap
garage
garbage
garden
garlic
garment
gas
gasp
gate
gather
gauge
gaze
general
genius
genre
gentle
genuine
gesture
ghost
giant
gift
giggle
ginger
giraffe
girl
give
glad
glance
glare
glass
glide
glimpse
globe
gloom
glory
glove
glow
glue
goat
goddess
gold
good
goose
gorilla
gospel
gossip
govern
gown
grab
grace
grain
grant
grape
grass
gravity
great
green
grid
grief
grit
grocery
group
grow
grunt
guard
guess
guide
guilt
guitar
gun
gym
habit
hair
half
hammer
hamster
hand
happy
harbor
hard
harsh
harvest
hat
have
hawk
hazard
head
health
heart
heavy
hedgehog
height
hello
helmet
help
hen
hero
hidden
high
hill
hint
hip
hire
history
hobby
hockey
hold
hole
holiday
hollow
home
honey
hood
hope
horn
horror
horse
hospital
host
hotel
hour
hover
hub
huge
human
humble
humor
hundred
hungry
hunt
hurdle
hurry
hurt
husband
hybrid
ice
icon
idea
identify
idle
ignore
ill
illegal
illness
image
imitate
immense
immune
impact
impose
improve
impulse
inch
include
income
increase
index
indicate
indoor
industry
infant
inflict
inform
inhale
inherit
initial
inject
injury
inmate
inner
innocent
input
inquiry
insane
insect
inside
inspire
install
intact
interest
into
invest
invite
involve
iron
island
isolate
issue
item
ivory
jacket
jaguar
jar
jazz
jealous
jeans
jelly
jewel
job
join
joke
journey
joy
judge
juice
jump
jungle
junior
junk
just
kangaroo
keen
keep
ketchup
key
kick
kid
kidney
kind
kingdom
kiss
kit
kitchen
kite
kitten
kiwi
knee
knife
knock
know
lab
label
labor
ladder
lady
lake
lamp
language
laptop
large
later
latin
laugh
laundry
lava
law
lawn
lawsuit
layer
lazy
leader
leaf
learn
leave
lecture
left
leg
legal
legend
leisure
lemon
lend
length
lens
leopard
lesson
letter
level
liar
liberty
library
license
life
lift
light
like
limb
limit
link
lion
liquid
list
little
live
lizard
load
loan
lobster
local
lock
logic
lonely
long
loop
lottery
loud
lounge
love
loyal
lucky
luggage
lumber
lunar
lunch
luxury
lyrics
machine
mad
magic
magnet
maid
mail
main
major
make
mammal
man
manage
mandate
mango
mansion
manual
maple
marble
march
margin
marine
market
marriage
mask
mass
master
match
material
math
matrix
matter
maximum
maze
meadow
mean
measure
meat
mechanic
medal
media
melody
melt
member
memory
mention
menu
mercy
merge
merit
merry
mesh
message
metal
method
middle
midnight
milk
million
mimic
mind
minimum
minor
minute
miracle
mirror
misery
miss
mistake
mix
mixed
mixture
mobile
model
modify
mom
moment
monitor
monkey
monster
month
moon
moral
more
morning
mosquito
mother
motion
motor
mountain
mouse
move
movie
much
muffin
mule
multiply
muscle
museum
mushroom
music
must
mutual
myself
mystery
myth
naive
name
napkin
narrow
nasty
nation
nature
near
neck
need
negative
neglect
neither
nephew
nerve
nest
net
network
neutral
never
news
next
nice
night
noble
noise
nominee
noodle
normal
north
nose
notable
note
nothing
notice
novel
now
nuclear
number
nurse
nut
oak
obey
object
oblige
obscure
observe
obtain
obvious
occur
ocean
october
odor
off
offer
office
often
oil
okay
old
olive
olympic
omit
once
one
onion
online
only
open
opera
opinion
oppose
option
orange
orbit
orchard
order
ordinary
organ
orient
original
orphan
ostrich
other
outdoor
outer
output
outside
oval
oven
over
own
owner
oxygen
oyster
ozone
pact
paddle
page
pair
palace
palm
panda
panel
panic
panther
paper
parade
parent
park
parrot
party
pass
patch
path
patient
patrol
pattern
pause
pave
payment
peace
peanut
pear
peasant
pelican
pen
penalty
pencil
people
pepper
perfect
permit
person
pet
phone
photo
phrase
physical
piano
picnic
picture
piece
pig
pigeon
pill
pilot
pink
pioneer
pipe
pistol
pitch
pizza
place
planet
plastic
plate
play
please
pledge
pluck
plug
plunge
poem
poet
point
polar
pole
police
pond
pony
pool
popular
portion
position
possible
post
potato
pottery
poverty
powder
power
practice
praise
predict
prefer
prepare
present
pretty
prevent
price
pride
primary
print
priority
prison
private
prize
problem
process
produce
profit
program
project
promote
proof
property
prosper
protect
proud
provide
public
pudding
pull
pulp
pulse
pumpkin
punch
pupil
puppy
purchase
purity
purpose
purse
push
put
puzzle
pyramid
quality
quantum
quarter
question
quick
quit
quiz
quote
rabbit
raccoon
race
rack
radar
radio
rail
rain
raise
rally
ramp
ranch
random
range
rapid
rare
rate
rather
raven
raw
razor
ready
real
reason
rebel
rebuild
recall
receive
recipe
record
recycle
reduce
reflect
reform
refuse
region
regret
regular
reject
relax
release
relief
rely
remain
remember
remind
remove
render
renew
rent
reopen
repair
repeat
replace
report
require
rescue
resemble
resist
resource
response
result
retire
retreat
return
reunion
reveal
review
reward
rhythm
rib
ribbon
rice
rich
ride
ridge
rifle
right
rigid
ring
riot
ripple
risk
ritual
rival
river
road
roast
robot
robust
rocket
romance
roof
rookie
room
rose
rotate
rough
round
route
royal
rubber
rude
rug
rule
run
runway
rural
sad
saddle
sadness
safe
sail
salad
salmon
salon
salt
salute
same
sample
sand
satisfy
satoshi
sauce
sausage
save
say
scale
scan
scare
scatter
scene
scheme
school
science
scissors
scorpion
scout
scrap
screen
script
scrub
sea
search
season
seat
second
secret
section
security
seed
seek
segment
select
sell
seminar
senior
sense
sentence
series
service
session
settle
setup
seven
shadow
shaft
shallow
share
shed
shell
sheriff
shield
shift
shine
ship
shiver
shock
shoe
shoot
shop
short
shoulder
shove
shrimp
shrug
shuffle
shy
sibling
sick
side
siege
sight
sign
silent
silk
silly
silver
similar
simple
since
sing
siren
sister
situate
six
size
skate
sketch
ski
skill
skin
skirt
skull
slab
slam
sleep
slender
slice
slide
slight
slim
slogan
slot
slow
slush
small
smart
smile
smoke
smooth
snack
snake
snap
sniff
snow
soap
soccer
social
sock
soda
soft
solar
soldier
solid
solution
solve
someone
song
soon
sorry
sort
soul
sound
soup
source
south
space
spare
spatial
spawn
speak
special
speed
spell
spend
sphere
spice
spider
spike
spin
spirit
split
spoil
sponsor
spoon
sport
spot
spray
spread
spring
spy
square
squeeze
squirrel
stable
stadium
staff
stage
stairs
stamp
stand
start
state
stay
steak
steel
stem
step
stereo
stick
still
sting
stock
stomach
stone
stool
story
stove
strategy
street
strike
strong
struggle
student
stuff
stumble
style
subject
submit
subway
success
such
sudden
suffer
sugar
suggest
suit
summer
sun
sunny
sunset
super
supply
supreme
sure
surface
surge
surprise
surround
survey
suspect
sustain
swallow
swamp
swap
swarm
swear
sweet
swift
swim
swing
switch
sword
symbol
symptom
syrup
system
table
tackle
tag
tail
talent
talk
tank
tape
target
task
taste
tattoo
taxi
teach
team
tell
ten
tenant
tennis
tent
term
test
text
thank
that
theme
then
theory
there
they
thing
this
thought
three
thrive
throw
thumb
thunder
ticket
tide
tiger
tilt
timber
time
tiny
tip
tired
tissue
title
toast
tobacco
today
toddler
toe
together
toilet
token
tomato
tomorrow
tone
tongue
tonight
tool
tooth
top
topic
topple
torch
tornado
tortoise
toss
total
tourist
toward
tower
town
toy
track
trade
traffic
tragic
train
transfer
trap
trash
travel
tray
treat
tree
trend
trial
tribe
trick
trigger
trim
trip
trophy
trouble
truck
true
truly
trumpet
trust
truth
try
tube
tuition
tumble
tuna
tunnel
turkey
turn
turtle
twelve
twenty
twice
twin
twist
two
type
typical
ugly
umbrella
unable
unaware
uncle
uncover
under
undo
unfair
unfold
unhappy
uniform
unique
unit
universe
unknown
unlock
until
unusual
unveil
update
upgrade
uphold
upon
upper
upset
urban
urge
usage
use
used
useful
useless
usual
utility
vacant
vacuum
vague
valid
valley
valve
van
vanish
vapor
various
vast
vault
vehicle
velvet
vendor
venture
venue
verb
verify
version
very
vessel
veteran
viable
vibrant
vicious
victory
video
view
village
vintage
violin
virtual
virus
visa
visit
visual
vital
vivid
vocal
voice
void
volcano
volume
vote
voyage
wage
wagon
wait
walk
wall
walnut
want
warfare
warm
warrior
wash
wasp
waste
water
wave
way
wealth
weapon
wear
weasel
weather
web
wedding
weekend
weird
welcome
west
wet
whale
what
wheat
wheel
when
where
whip
whisper
wide
width
wife
wild
will
win
window
wine
wing
wink
winner
winter
wire
wisdom
wise
wish
witness
wolf
woman
wonder
wood
wool
word
work
world
worry
worth
wrap
wreck
wrestle
wrist
write
wrong
yard
year
yellow
you
young
youth
zebra
zero
zone
zoo`
package aliasmgr
import (
"encoding/binary"
"fmt"
"maps"
"slices"
"sync"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
)
// UpdateLinkAliases is a function type for a function that locates the active
// link that matches the given shortID and triggers an update based on the
// latest values of the alias manager.
type UpdateLinkAliases func(shortID lnwire.ShortChannelID) error
// ScidAliasMap is a map from a base short channel ID to a set of alias short
// channel IDs.
type ScidAliasMap map[lnwire.ShortChannelID][]lnwire.ShortChannelID
var (
// aliasBucket stores aliases as keys and their base SCIDs as values.
// This is used to populate the maps that the Manager uses. The keys
// are alias SCIDs and the values are their respective base SCIDs. This
// is used instead of the other way around (base -> alias...) because
// updating an alias would require fetching all the existing aliases,
// adding another one, and then flushing the write to disk. This is
// inefficient compared to N 1:1 mappings at the cost of marginally
// more disk space.
aliasBucket = []byte("alias-bucket")
// confirmedBucket stores whether or not a given base SCID should no
// longer have entries in the ToBase maps. The key is the SCID that is
// confirmed with 6 confirmations and is public, and the value is
// empty.
confirmedBucket = []byte("base-bucket")
// aliasAllocBucket is a root-level bucket that stores the last alias
// that was allocated. It is used to allocate a new alias when
// requested.
aliasAllocBucket = []byte("alias-alloc-bucket")
// lastAliasKey is a key in the aliasAllocBucket whose value is the
// last allocated alias ShortChannelID. This will be updated upon calls
// to RequestAlias.
lastAliasKey = []byte("last-alias-key")
// invoiceAliasBucket is a root-level bucket that stores the alias
// SCIDs that our peers send us in the channel_ready TLV. The keys are
// the ChannelID generated from the FundingOutpoint and the values are
// the remote peer's alias SCID.
invoiceAliasBucket = []byte("invoice-alias-bucket")
// byteOrder denotes the byte order of database (de)-serialization
// operations.
byteOrder = binary.BigEndian
// AliasStartBlockHeight is the starting block height of the alias
// range.
AliasStartBlockHeight uint32 = 16_000_000
// AliasEndBlockHeight is the ending block height of the alias range.
AliasEndBlockHeight uint32 = 16_250_000
// StartingAlias is the first alias ShortChannelID that will get
// assigned by RequestAlias. The starting BlockHeight is chosen so that
// legitimate SCIDs in integration tests aren't mistaken for an alias.
StartingAlias = lnwire.ShortChannelID{
BlockHeight: AliasStartBlockHeight,
TxIndex: 0,
TxPosition: 0,
}
// errNoBase is returned when a base SCID isn't found.
errNoBase = fmt.Errorf("no base found")
// errNoPeerAlias is returned when the peer's alias for a given
// channel is not found.
errNoPeerAlias = fmt.Errorf("no peer alias found")
// ErrAliasNotFound is returned when the alias is not found and can't
// be mapped to a base SCID.
ErrAliasNotFound = fmt.Errorf("alias not found")
)
// Manager is a struct that handles aliases for LND. It has an underlying
// database that can allocate aliases for channels, stores the peer's last
// alias for use in our hop hints, and contains mappings that both the Switch
// and Gossiper use.
type Manager struct {
backend kvdb.Backend
// linkAliasUpdater is a function used by the alias manager to
// facilitate live update of aliases in other subsystems.
linkAliasUpdater UpdateLinkAliases
// baseToSet is a mapping from the "base" SCID to the set of aliases
// for this channel. This mapping includes all channels that
// negotiated the option-scid-alias feature bit.
baseToSet ScidAliasMap
// aliasToBase is a mapping that maps all aliases for a given channel
// to its base SCID. This is only used for channels that have
// negotiated option-scid-alias feature bit.
aliasToBase map[lnwire.ShortChannelID]lnwire.ShortChannelID
// peerAlias is a cache for the alias SCIDs that our peers send us in
// the channel_ready TLV. The keys are the ChannelID generated from
// the FundingOutpoint and the values are the remote peer's alias SCID.
// The values should match the ones stored in the "invoice-alias-bucket"
// bucket.
peerAlias map[lnwire.ChannelID]lnwire.ShortChannelID
sync.RWMutex
}
// NewManager initializes an alias Manager from the passed database backend.
func NewManager(db kvdb.Backend, linkAliasUpdater UpdateLinkAliases) (*Manager,
error) {
m := &Manager{
backend: db,
baseToSet: make(ScidAliasMap),
linkAliasUpdater: linkAliasUpdater,
}
m.aliasToBase = make(map[lnwire.ShortChannelID]lnwire.ShortChannelID)
m.peerAlias = make(map[lnwire.ChannelID]lnwire.ShortChannelID)
err := m.populateMaps()
return m, err
}
// populateMaps reads the database state and populates the maps.
func (m *Manager) populateMaps() error {
// This map tracks the base SCIDs that are confirmed and don't need to
// have entries in the *ToBase mappings as they won't be used in the
// gossiper.
baseConfMap := make(map[lnwire.ShortChannelID]struct{})
// This map caches what is found in the database and is used to
// populate the Manager's actual maps.
aliasMap := make(map[lnwire.ShortChannelID]lnwire.ShortChannelID)
// This map caches the ChannelID/alias SCIDs stored in the database and
// is used to populate the Manager's cache.
peerAliasMap := make(map[lnwire.ChannelID]lnwire.ShortChannelID)
err := kvdb.Update(m.backend, func(tx kvdb.RwTx) error {
baseConfBucket, err := tx.CreateTopLevelBucket(confirmedBucket)
if err != nil {
return err
}
err = baseConfBucket.ForEach(func(k, v []byte) error {
// The key will the base SCID and the value will be
// empty. Existence in the bucket means the SCID is
// confirmed.
baseScid := lnwire.NewShortChanIDFromInt(
byteOrder.Uint64(k),
)
baseConfMap[baseScid] = struct{}{}
return nil
})
if err != nil {
return err
}
aliasToBaseBucket, err := tx.CreateTopLevelBucket(aliasBucket)
if err != nil {
return err
}
err = aliasToBaseBucket.ForEach(func(k, v []byte) error {
// The key will be the alias SCID and the value will be
// the base SCID.
aliasScid := lnwire.NewShortChanIDFromInt(
byteOrder.Uint64(k),
)
baseScid := lnwire.NewShortChanIDFromInt(
byteOrder.Uint64(v),
)
aliasMap[aliasScid] = baseScid
return nil
})
if err != nil {
return err
}
invAliasBucket, err := tx.CreateTopLevelBucket(
invoiceAliasBucket,
)
if err != nil {
return err
}
err = invAliasBucket.ForEach(func(k, v []byte) error {
var chanID lnwire.ChannelID
copy(chanID[:], k)
alias := lnwire.NewShortChanIDFromInt(
byteOrder.Uint64(v),
)
peerAliasMap[chanID] = alias
return nil
})
return err
}, func() {
baseConfMap = make(map[lnwire.ShortChannelID]struct{})
aliasMap = make(map[lnwire.ShortChannelID]lnwire.ShortChannelID)
peerAliasMap = make(map[lnwire.ChannelID]lnwire.ShortChannelID)
})
if err != nil {
return err
}
// Populate the baseToSet map regardless if the baseSCID is marked as
// public with 6 confirmations.
for aliasSCID, baseSCID := range aliasMap {
m.baseToSet[baseSCID] = append(m.baseToSet[baseSCID], aliasSCID)
// Skip if baseSCID is in the baseConfMap.
if _, ok := baseConfMap[baseSCID]; ok {
continue
}
m.aliasToBase[aliasSCID] = baseSCID
}
// Populate the peer alias cache.
m.peerAlias = peerAliasMap
return nil
}
// AddLocalAlias adds a database mapping from the passed alias to the passed
// base SCID. The gossip boolean marks whether or not to create a mapping
// that the gossiper will use. It is set to false for the upgrade path where
// the feature-bit is toggled on and there are existing channels. The linkUpdate
// flag is used to signal whether this function should also trigger an update
// on the htlcswitch scid alias maps.
func (m *Manager) AddLocalAlias(alias, baseScid lnwire.ShortChannelID,
gossip, linkUpdate bool) error {
// We need to lock the manager for the whole duration of this method,
// except for the very last part where we call the link updater. In
// order for us to safely use a defer _and_ still be able to manually
// unlock, we use a sync.Once.
m.Lock()
unlockOnce := sync.Once{}
unlock := func() {
unlockOnce.Do(m.Unlock)
}
defer unlock()
err := kvdb.Update(m.backend, func(tx kvdb.RwTx) error {
// If the caller does not want to allow the alias to be used
// for a channel update, we'll mark it in the baseConfBucket.
if !gossip {
var baseGossipBytes [8]byte
byteOrder.PutUint64(
baseGossipBytes[:], baseScid.ToUint64(),
)
confBucket, err := tx.CreateTopLevelBucket(
confirmedBucket,
)
if err != nil {
return err
}
err = confBucket.Put(baseGossipBytes[:], []byte{})
if err != nil {
return err
}
}
aliasToBaseBucket, err := tx.CreateTopLevelBucket(aliasBucket)
if err != nil {
return err
}
var (
aliasBytes [8]byte
baseBytes [8]byte
)
byteOrder.PutUint64(aliasBytes[:], alias.ToUint64())
byteOrder.PutUint64(baseBytes[:], baseScid.ToUint64())
return aliasToBaseBucket.Put(aliasBytes[:], baseBytes[:])
}, func() {})
if err != nil {
return err
}
// Update the aliasToBase and baseToSet maps.
m.baseToSet[baseScid] = append(m.baseToSet[baseScid], alias)
// Only store the gossiper map if gossip is true.
if gossip {
m.aliasToBase[alias] = baseScid
}
// We definitely need to unlock the Manager before calling the link
// updater. If we don't, we'll deadlock. We use a sync.Once to ensure
// that we only unlock once.
unlock()
// Finally, we trigger a htlcswitch update if the flag is set, in order
// for any future htlc that references the added alias to be properly
// routed.
if linkUpdate {
return m.linkAliasUpdater(baseScid)
}
return nil
}
// GetAliases fetches the set of aliases stored under a given base SCID from
// write-through caches.
func (m *Manager) GetAliases(
base lnwire.ShortChannelID) []lnwire.ShortChannelID {
m.RLock()
defer m.RUnlock()
aliasSet, ok := m.baseToSet[base]
if ok {
// Copy the found alias slice.
setCopy := make([]lnwire.ShortChannelID, len(aliasSet))
copy(setCopy, aliasSet)
return setCopy
}
return nil
}
// FindBaseSCID finds the base SCID for a given alias. This is used in the
// gossiper to find the correct SCID to lookup in the graph database.
func (m *Manager) FindBaseSCID(
alias lnwire.ShortChannelID) (lnwire.ShortChannelID, error) {
m.RLock()
defer m.RUnlock()
base, ok := m.aliasToBase[alias]
if ok {
return base, nil
}
return lnwire.ShortChannelID{}, errNoBase
}
// DeleteSixConfs removes a mapping for the gossiper once six confirmations
// have been reached and the channel is public. At this point, only the
// confirmed SCID should be used.
func (m *Manager) DeleteSixConfs(baseScid lnwire.ShortChannelID) error {
m.Lock()
defer m.Unlock()
err := kvdb.Update(m.backend, func(tx kvdb.RwTx) error {
baseConfBucket, err := tx.CreateTopLevelBucket(confirmedBucket)
if err != nil {
return err
}
var baseBytes [8]byte
byteOrder.PutUint64(baseBytes[:], baseScid.ToUint64())
return baseConfBucket.Put(baseBytes[:], []byte{})
}, func() {})
if err != nil {
return err
}
// Now that the database state has been updated, we'll delete all of
// the aliasToBase mappings for this SCID.
for alias, base := range m.aliasToBase {
if base.ToUint64() == baseScid.ToUint64() {
delete(m.aliasToBase, alias)
}
}
return nil
}
// DeleteLocalAlias removes a mapping from the database and the Manager's maps.
func (m *Manager) DeleteLocalAlias(alias,
baseScid lnwire.ShortChannelID) error {
// We need to lock the manager for the whole duration of this method,
// except for the very last part where we call the link updater. In
// order for us to safely use a defer _and_ still be able to manually
// unlock, we use a sync.Once.
m.Lock()
unlockOnce := sync.Once{}
unlock := func() {
unlockOnce.Do(m.Unlock)
}
defer unlock()
err := kvdb.Update(m.backend, func(tx kvdb.RwTx) error {
aliasToBaseBucket, err := tx.CreateTopLevelBucket(aliasBucket)
if err != nil {
return err
}
var aliasBytes [8]byte
byteOrder.PutUint64(aliasBytes[:], alias.ToUint64())
// If the user attempts to delete an alias that doesn't exist,
// we'll want to inform them about it and not just do nothing.
if aliasToBaseBucket.Get(aliasBytes[:]) == nil {
return ErrAliasNotFound
}
return aliasToBaseBucket.Delete(aliasBytes[:])
}, func() {})
if err != nil {
return err
}
// Now that the database state has been updated, we'll delete the
// mapping from the Manager's maps.
aliasSet, ok := m.baseToSet[baseScid]
if !ok {
return ErrAliasNotFound
}
// We'll filter the alias set and remove the alias from it.
aliasSet = fn.Filter(aliasSet, func(a lnwire.ShortChannelID) bool {
return a.ToUint64() != alias.ToUint64()
})
// If the alias set is empty, we'll delete the base SCID from the
// baseToSet map.
if len(aliasSet) == 0 {
delete(m.baseToSet, baseScid)
} else {
m.baseToSet[baseScid] = aliasSet
}
// Finally, we'll delete the aliasToBase mapping from the Manager's
// cache (but this is only set if we gossip the alias).
delete(m.aliasToBase, alias)
// We definitely need to unlock the Manager before calling the link
// updater. If we don't, we'll deadlock. We use a sync.Once to ensure
// that we only unlock once.
unlock()
return m.linkAliasUpdater(baseScid)
}
// PutPeerAlias stores the peer's alias SCID once we learn of it in the
// channel_ready message.
func (m *Manager) PutPeerAlias(chanID lnwire.ChannelID,
alias lnwire.ShortChannelID) error {
m.Lock()
defer m.Unlock()
err := kvdb.Update(m.backend, func(tx kvdb.RwTx) error {
bucket, err := tx.CreateTopLevelBucket(invoiceAliasBucket)
if err != nil {
return err
}
var scratch [8]byte
byteOrder.PutUint64(scratch[:], alias.ToUint64())
return bucket.Put(chanID[:], scratch[:])
}, func() {})
if err != nil {
return err
}
// Now that the database state has been updated, we can update it in
// our cache.
m.peerAlias[chanID] = alias
return nil
}
// GetPeerAlias retrieves a peer's alias SCID by the channel's ChanID.
func (m *Manager) GetPeerAlias(chanID lnwire.ChannelID) (lnwire.ShortChannelID,
error) {
m.RLock()
defer m.RUnlock()
alias, ok := m.peerAlias[chanID]
if !ok || alias == hop.Source {
return lnwire.ShortChannelID{}, errNoPeerAlias
}
return alias, nil
}
// RequestAlias returns a new ALIAS ShortChannelID to the caller by allocating
// the next un-allocated ShortChannelID. The starting ShortChannelID is
// 16000000:0:0 and the ending ShortChannelID is 16250000:16777215:65535. This
// gives roughly 2^58 possible ALIAS ShortChannelIDs which ensures this space
// won't get exhausted.
func (m *Manager) RequestAlias() (lnwire.ShortChannelID, error) {
var nextAlias lnwire.ShortChannelID
m.RLock()
defer m.RUnlock()
// haveAlias returns true if the passed alias is already assigned to a
// channel in the baseToSet map.
haveAlias := func(maybeNextAlias lnwire.ShortChannelID) bool {
return fn.Any(
slices.Collect(maps.Values(m.baseToSet)),
func(aliasList []lnwire.ShortChannelID) bool {
return fn.Any(
aliasList,
func(alias lnwire.ShortChannelID) bool {
return alias == maybeNextAlias
},
)
},
)
}
err := kvdb.Update(m.backend, func(tx kvdb.RwTx) error {
bucket, err := tx.CreateTopLevelBucket(aliasAllocBucket)
if err != nil {
return err
}
lastBytes := bucket.Get(lastAliasKey)
if lastBytes == nil {
// If the key does not exist, then we can write the
// StartingAlias to it.
nextAlias = StartingAlias
// If the very first alias is already assigned, we'll
// keep incrementing until we find an unassigned alias.
// This is to avoid collision with custom added SCID
// aliases that fall into the same range as the ones we
// generate here monotonically. Those custom SCIDs are
// stored in a different bucket, but we can just check
// the in-memory map for simplicity.
for {
if !haveAlias(nextAlias) {
break
}
nextAlias = getNextScid(nextAlias)
// Abort if we've reached the end of the range.
if nextAlias.BlockHeight >=
AliasEndBlockHeight {
return fmt.Errorf("range for custom " +
"aliases exhausted")
}
}
var scratch [8]byte
byteOrder.PutUint64(scratch[:], nextAlias.ToUint64())
return bucket.Put(lastAliasKey, scratch[:])
}
// Otherwise the key does exist so we can convert the retrieved
// lastAlias to a ShortChannelID and use it to assign the next
// ShortChannelID. This next ShortChannelID will then be
// persisted in the database.
lastScid := lnwire.NewShortChanIDFromInt(
byteOrder.Uint64(lastBytes),
)
nextAlias = getNextScid(lastScid)
// If the next alias is already assigned, we'll keep
// incrementing until we find an unassigned alias. This is to
// avoid collision with custom added SCID aliases that fall into
// the same range as the ones we generate here monotonically.
// Those custom SCIDs are stored in a different bucket, but we
// can just check the in-memory map for simplicity.
for {
if !haveAlias(nextAlias) {
break
}
nextAlias = getNextScid(nextAlias)
// Abort if we've reached the end of the range.
if nextAlias.BlockHeight >= AliasEndBlockHeight {
return fmt.Errorf("range for custom " +
"aliases exhausted")
}
}
var scratch [8]byte
byteOrder.PutUint64(scratch[:], nextAlias.ToUint64())
return bucket.Put(lastAliasKey, scratch[:])
}, func() {
nextAlias = lnwire.ShortChannelID{}
})
if err != nil {
return nextAlias, err
}
return nextAlias, nil
}
// ListAliases returns a carbon copy of baseToSet. This is used by the rpc
// layer.
func (m *Manager) ListAliases() ScidAliasMap {
m.RLock()
defer m.RUnlock()
baseCopy := make(ScidAliasMap)
for k, v := range m.baseToSet {
setCopy := make([]lnwire.ShortChannelID, len(v))
copy(setCopy, v)
baseCopy[k] = setCopy
}
return baseCopy
}
// getNextScid is a utility function that returns the next SCID for a given
// alias SCID. The BlockHeight ranges from [16000000, 16250000], the TxIndex
// ranges from [1, 16777215], and the TxPosition ranges from [1, 65535].
func getNextScid(last lnwire.ShortChannelID) lnwire.ShortChannelID {
var (
next lnwire.ShortChannelID
incrementIdx bool
incrementHeight bool
)
// If the TxPosition is 65535, then it goes to 0 and we need to
// increment the TxIndex.
if last.TxPosition == 65535 {
incrementIdx = true
}
// If the TxIndex is 16777215 and we need to increment it, then it goes
// to 0 and we need to increment the BlockHeight.
if last.TxIndex == 16777215 && incrementIdx {
incrementIdx = false
incrementHeight = true
}
switch {
// If we increment the TxIndex, then TxPosition goes to 0.
case incrementIdx:
next.BlockHeight = last.BlockHeight
next.TxIndex = last.TxIndex + 1
next.TxPosition = 0
// If we increment the BlockHeight, then the Tx fields go to 0.
case incrementHeight:
next.BlockHeight = last.BlockHeight + 1
next.TxIndex = 0
next.TxPosition = 0
// Otherwise, we only need to increment the TxPosition.
default:
next.BlockHeight = last.BlockHeight
next.TxIndex = last.TxIndex
next.TxPosition = last.TxPosition + 1
}
return next
}
// IsAlias returns true if the passed SCID is an alias. The function determines
// this by looking at the BlockHeight. If the BlockHeight is greater than
// AliasStartBlockHeight and less than AliasEndBlockHeight, then it is an alias
// assigned by RequestAlias. These bounds only apply to aliases we generate.
// Our peers are free to use any range they choose.
func IsAlias(scid lnwire.ShortChannelID) bool {
return scid.BlockHeight >= AliasStartBlockHeight &&
scid.BlockHeight < AliasEndBlockHeight
}
package amp
import (
"crypto/sha256"
"encoding/binary"
"fmt"
"github.com/lightningnetwork/lnd/lntypes"
)
// Share represents an n-of-n sharing of a secret 32-byte value. The secret can
// be recovered by XORing all n shares together.
type Share [32]byte
// Xor stores the byte-wise xor of shares x and y in z.
func (z *Share) Xor(x, y *Share) {
for i := range z {
z[i] = x[i] ^ y[i]
}
}
// ChildDesc contains the information necessary to derive a child hash/preimage
// pair that is attached to a particular HTLC. This information will be known by
// both the sender and receiver in the process of fulfilling an AMP payment.
type ChildDesc struct {
// Share is one of n shares of the root seed. Once all n shares are
// known to the receiver, the Share will also provide entropy to the
// derivation of child hash and preimage.
Share Share
// Index is 32-bit value that can be used to derive up to 2^32 child
// hashes and preimages from a single Share. This allows the payment
// hashes sent over the network to be refreshed without needing to
// modify the Share.
Index uint32
}
// Child is a payment hash and preimage pair derived from the root seed. In
// addition to the derived values, a Child carries all information required in
// the derivation apart from the root seed (unless n=1).
type Child struct {
// ChildDesc contains the data required to derive the child hash and
// preimage below.
ChildDesc
// Preimage is the child payment preimage that can be used to settle the
// HTLC carrying Hash.
Preimage lntypes.Preimage
// Hash is the child payment hash that to be carried by the HTLC.
Hash lntypes.Hash
}
// String returns a human-readable description of a Child.
func (c *Child) String() string {
return fmt.Sprintf("share=%x, index=%d -> preimage=%v, hash=%v",
c.Share, c.Index, c.Preimage, c.Hash)
}
// DeriveChild computes the child preimage and child hash for a given (root,
// share, index) tuple. The derivation is defined as:
//
// child_preimage = SHA256(root || share || be32(index)),
// child_hash = SHA256(child_preimage).
func DeriveChild(root Share, desc ChildDesc) *Child {
var (
indexBytes [4]byte
preimage lntypes.Preimage
hash lntypes.Hash
)
// Serialize the child index in big-endian order.
binary.BigEndian.PutUint32(indexBytes[:], desc.Index)
// Compute child_preimage as SHA256(root || share || child_index).
h := sha256.New()
_, _ = h.Write(root[:])
_, _ = h.Write(desc.Share[:])
_, _ = h.Write(indexBytes[:])
copy(preimage[:], h.Sum(nil))
// Compute child_hash as SHA256(child_preimage).
h = sha256.New()
_, _ = h.Write(preimage[:])
copy(hash[:], h.Sum(nil))
return &Child{
ChildDesc: desc,
Preimage: preimage,
Hash: hash,
}
}
package amp
import (
"crypto/rand"
"encoding/binary"
"fmt"
"sync"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/routing/shards"
)
// Shard is an implementation of the shards.PaymentShards interface specific
// to AMP payments.
type Shard struct {
child *Child
mpp *record.MPP
amp *record.AMP
}
// A compile time check to ensure Shard implements the shards.PaymentShard
// interface.
var _ shards.PaymentShard = (*Shard)(nil)
// Hash returns the hash used for the HTLC representing this AMP shard.
func (s *Shard) Hash() lntypes.Hash {
return s.child.Hash
}
// MPP returns any extra MPP records that should be set for the final hop on
// the route used by this shard.
func (s *Shard) MPP() *record.MPP {
return s.mpp
}
// AMP returns any extra AMP records that should be set for the final hop on
// the route used by this shard.
func (s *Shard) AMP() *record.AMP {
return s.amp
}
// ShardTracker is an implementation of the shards.ShardTracker interface
// that is able to generate payment shards according to the AMP splitting
// algorithm. It can be used to generate new hashes to use for HTLCs, and also
// cancel shares used for failed payment shards.
type ShardTracker struct {
setID [32]byte
paymentAddr [32]byte
totalAmt lnwire.MilliSatoshi
sharer Sharer
shards map[uint64]*Child
sync.Mutex
}
// A compile time check to ensure ShardTracker implements the
// shards.ShardTracker interface.
var _ shards.ShardTracker = (*ShardTracker)(nil)
// NewShardTracker creates a new shard tracker to use for AMP payments. The
// root shard, setID, payment address and total amount must be correctly set in
// order for the TLV options to include with each shard to be created
// correctly.
func NewShardTracker(root, setID, payAddr [32]byte,
totalAmt lnwire.MilliSatoshi) *ShardTracker {
// Create a new seed sharer from this root.
rootShare := Share(root)
rootSharer := SeedSharerFromRoot(&rootShare)
return &ShardTracker{
setID: setID,
paymentAddr: payAddr,
totalAmt: totalAmt,
sharer: rootSharer,
shards: make(map[uint64]*Child),
}
}
// NewShard registers a new attempt with the ShardTracker and returns a
// new shard representing this attempt. This attempt's shard should be canceled
// if it ends up not being used by the overall payment, i.e. if the attempt
// fails.
func (s *ShardTracker) NewShard(pid uint64, last bool) (shards.PaymentShard,
error) {
s.Lock()
defer s.Unlock()
// Use a random child index.
var childIndex [4]byte
if _, err := rand.Read(childIndex[:]); err != nil {
return nil, err
}
idx := binary.BigEndian.Uint32(childIndex[:])
// Depending on whether we are requesting the last shard or not, either
// split the current share into two, or get a Child directly from the
// current sharer.
var child *Child
if last {
child = s.sharer.Child(idx)
// If this was the last shard, set the current share to the
// zero share to indicate we cannot split it further.
s.sharer = s.sharer.Zero()
} else {
left, sharer, err := s.sharer.Split()
if err != nil {
return nil, err
}
s.sharer = sharer
child = left.Child(idx)
}
// Track the new child and return the shard.
s.shards[pid] = child
mpp := record.NewMPP(s.totalAmt, s.paymentAddr)
amp := record.NewAMP(
child.ChildDesc.Share, s.setID, child.ChildDesc.Index,
)
return &Shard{
child: child,
mpp: mpp,
amp: amp,
}, nil
}
// CancelShard cancel's the shard corresponding to the given attempt ID.
func (s *ShardTracker) CancelShard(pid uint64) error {
s.Lock()
defer s.Unlock()
c, ok := s.shards[pid]
if !ok {
return fmt.Errorf("pid not found")
}
delete(s.shards, pid)
// Now that we are canceling this shard, we XOR the share back into our
// current share.
s.sharer = s.sharer.Merge(c)
return nil
}
// GetHash retrieves the hash used by the shard of the given attempt ID. This
// will return an error if the attempt ID is unknown.
func (s *ShardTracker) GetHash(pid uint64) (lntypes.Hash, error) {
s.Lock()
defer s.Unlock()
c, ok := s.shards[pid]
if !ok {
return lntypes.Hash{}, fmt.Errorf("AMP shard for attempt %v "+
"not found", pid)
}
return c.Hash, nil
}
package amp
import (
"crypto/rand"
"fmt"
)
// zeroShare is the all-zero 32-byte share.
var zeroShare = Share{}
// Sharer facilitates dynamic splitting of a root share value and derivation of
// child preimage and hashes for individual HTLCs in an AMP payment. A sharer
// represents a specific node in an abstract binary tree that can generate up to
// 2^32-1 unique child preimage-hash pairs for the same share value. A node can
// also be split into it's left and right child in the tree. The Sharer
// guarantees that the share value of the left and right child XOR to the share
// value of the parent. This allows larger HTLCs to split into smaller
// subpayments, while ensuring that the reconstructed secret will exactly match
// the root seed.
type Sharer interface {
// Root returns the root share of the derivation tree. This is the value
// that will be reconstructed when combining the set of all child
// shares.
Root() Share
// Child derives a child preimage and child hash given a 32-bit index.
// Passing a different index will generate a unique preimage-hash pair
// with high probability, allowing the payment hash carried on HTLCs to
// be refreshed without needing to modify the share value. This would
// typically be used when an partial payment needs to be retried if it
// encounters routine network failures.
Child(index uint32) *Child
// Split returns a Sharer for the left and right child of the parent
// Sharer. XORing the share values of both sharers always yields the
// share value of the parent. The sender should use this to recursively
// divide payments that are too large into smaller subpayments, knowing
// that the shares of all nodes descending from the parent will XOR to
// the parent's share.
Split() (Sharer, Sharer, error)
// Merge takes the given Child and "merges" it into the Sharer by
// XOR-ing its share with the Sharer's current share.
Merge(*Child) Sharer
// Zero returns a a new "zero Sharer" that has its current share set to
// zero, while keeping the root share. Merging a Child from the
// original Sharer into this zero-Sharer gives back the original
// Sharer.
Zero() Sharer
}
// SeedSharer orchestrates the sharing of the root AMP seed along multiple
// paths. It also supports derivation of the child payment hashes that get
// attached to HTLCs, and the child preimages used by the receiver to settle
// individual HTLCs in the set.
type SeedSharer struct {
root Share
curr Share
}
// NewSeedSharer generates a new SeedSharer instance with a seed drawn at
// random.
func NewSeedSharer() (*SeedSharer, error) {
var root Share
if _, err := rand.Read(root[:]); err != nil {
return nil, err
}
return SeedSharerFromRoot(&root), nil
}
// SeedSharerFromRoot instantiates a SeedSharer with an externally provided
// seed.
func SeedSharerFromRoot(root *Share) *SeedSharer {
return initSeedSharer(root, root)
}
func initSeedSharer(root, curr *Share) *SeedSharer {
return &SeedSharer{
root: *root,
curr: *curr,
}
}
// Seed returns the sharer's seed, the primary source of entropy for deriving
// shares of the root.
func (s *SeedSharer) Root() Share {
return s.root
}
// Split constructs two child Sharers whose shares sum to the parent Sharer.
// This allows an HTLC whose payment amount could not be routed to be
// recursively split into smaller subpayments. After splitting a sharer the
// parent share should no longer be used, and the caller should use the Child
// method on each to derive preimage/hash pairs for the HTLCs.
func (s *SeedSharer) Split() (Sharer, Sharer, error) {
// We cannot split the zero-Sharer.
if s.curr == zeroShare {
return nil, nil, fmt.Errorf("cannot split zero-Sharer")
}
shareLeft, shareRight, err := split(&s.curr)
if err != nil {
return nil, nil, err
}
left := initSeedSharer(&s.root, &shareLeft)
right := initSeedSharer(&s.root, &shareRight)
return left, right, nil
}
// Merge takes the given Child and "merges" it into the Sharer by XOR-ing its
// share with the Sharer's current share.
func (s *SeedSharer) Merge(child *Child) Sharer {
var curr Share
curr.Xor(&s.curr, &child.Share)
sharer := initSeedSharer(&s.root, &curr)
return sharer
}
// Zero returns a a new "zero Sharer" that has its current share set to zero,
// while keeping the root share. Merging a Child from the original Sharer into
// this zero-Sharer gives back the original Sharer.
func (s *SeedSharer) Zero() Sharer {
return initSeedSharer(&s.root, &zeroShare)
}
// Child derives a preimage/hash pair to be used for an AMP HTLC.
// All children of s will use the same underlying share, but have unique
// preimage and hash. This can be used to rerandomize the preimage/hash pair for
// a given HTLC if a new route is needed.
func (s *SeedSharer) Child(index uint32) *Child {
desc := ChildDesc{
Share: s.curr,
Index: index,
}
return DeriveChild(s.root, desc)
}
// ReconstructChildren derives the set of children hashes and preimages from the
// provided descriptors. The shares from each child descriptor are first used to
// compute the root, afterwards the child hashes and preimages are
// deterministically computed. For child descriptor at index i in the input,
// it's derived child will occupy index i of the returned children.
func ReconstructChildren(descs ...ChildDesc) []*Child {
// Recompute the root by XORing the provided shares.
var root Share
for _, desc := range descs {
root.Xor(&root, &desc.Share)
}
// With the root computed, derive the child hashes and preimages from
// the child descriptors.
children := make([]*Child, len(descs))
for i, desc := range descs {
children[i] = DeriveChild(root, desc)
}
return children
}
// split splits a share into two random values, that when XOR'd reproduce the
// original share. Given a share s, the two shares are derived as:
//
// left <-$- random
// right = parent ^ left.
//
// When reconstructed, we have that:
//
// left ^ right = left ^ parent ^ left
// = parent.
func split(parent *Share) (Share, Share, error) {
// Generate a random share for the left child.
var left Share
if _, err := rand.Read(left[:]); err != nil {
return Share{}, Share{}, err
}
// Compute right = parent ^ left.
var right Share
right.Xor(parent, &left)
return left, right, nil
}
var _ Sharer = (*SeedSharer)(nil)
package autopilot
import (
"bytes"
"fmt"
"math/rand"
"net"
"sync"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/lnwire"
)
// Config couples all the items that an autopilot agent needs to function.
// All items within the struct MUST be populated for the Agent to be able to
// carry out its duties.
type Config struct {
// Self is the identity public key of the Lightning Network node that
// is being driven by the agent. This is used to ensure that we don't
// accidentally attempt to open a channel with ourselves.
Self *btcec.PublicKey
// Heuristic is an attachment heuristic which will govern to whom we
// open channels to, and also what those channels look like in terms of
// desired capacity. The Heuristic will take into account the current
// state of the graph, our set of open channels, and the amount of
// available funds when determining how channels are to be opened.
// Additionally, a heuristic make also factor in extra-graph
// information in order to make more pertinent recommendations.
Heuristic AttachmentHeuristic
// ChanController is an interface that is able to directly manage the
// creation, closing and update of channels within the network.
ChanController ChannelController
// ConnectToPeer attempts to connect to the peer using one of its
// advertised addresses. The boolean returned signals whether the peer
// was already connected.
ConnectToPeer func(*btcec.PublicKey, []net.Addr) (bool, error)
// DisconnectPeer attempts to disconnect the peer with the given public
// key.
DisconnectPeer func(*btcec.PublicKey) error
// WalletBalance is a function closure that should return the current
// available balance of the backing wallet.
WalletBalance func() (btcutil.Amount, error)
// Graph is an abstract channel graph that the Heuristic and the Agent
// will use to make decisions w.r.t channel allocation and placement
// within the graph.
Graph ChannelGraph
// Constraints is the set of constraints the autopilot must adhere to
// when opening channels.
Constraints AgentConstraints
// TODO(roasbeef): add additional signals from fee rates and revenue of
// currently opened channels
}
// channelState is a type that represents the set of active channels of the
// backing LN node that the Agent should be aware of. This type contains a few
// helper utility methods.
type channelState map[lnwire.ShortChannelID]LocalChannel
// Channels returns a slice of all the active channels.
func (c channelState) Channels() []LocalChannel {
chans := make([]LocalChannel, 0, len(c))
for _, channel := range c {
chans = append(chans, channel)
}
return chans
}
// ConnectedNodes returns the set of nodes we currently have a channel with.
// This information is needed as we want to avoid making repeated channels with
// any node.
func (c channelState) ConnectedNodes() map[NodeID]struct{} {
nodes := make(map[NodeID]struct{})
for _, channels := range c {
nodes[channels.Node] = struct{}{}
}
// TODO(roasbeef): add outgoing, nodes, allow incoming and outgoing to
// per node
// * only add node is chan as funding amt set
return nodes
}
// Agent implements a closed-loop control system which seeks to autonomously
// optimize the allocation of satoshis within channels throughput the network's
// channel graph. An agent is configurable by swapping out different
// AttachmentHeuristic strategies. The agent uses external signals such as the
// wallet balance changing, or new channels being opened/closed for the local
// node as an indicator to re-examine its internal state, and the amount of
// available funds in order to make updated decisions w.r.t the channel graph.
// The Agent will automatically open, close, and splice in/out channel as
// necessary for it to step closer to its optimal state.
//
// TODO(roasbeef): prob re-word
type Agent struct {
started sync.Once
stopped sync.Once
// cfg houses the configuration state of the Ant.
cfg Config
// chanState tracks the current set of open channels.
chanState channelState
chanStateMtx sync.Mutex
// stateUpdates is a channel that any external state updates that may
// affect the heuristics of the agent will be sent over.
stateUpdates chan interface{}
// balanceUpdates is a channel where notifications about updates to the
// wallet's balance will be sent. This channel will be buffered to
// ensure we have at most one pending update of this type to handle at
// a given time.
balanceUpdates chan *balanceUpdate
// nodeUpdates is a channel that changes to the graph node landscape
// will be sent over. This channel will be buffered to ensure we have
// at most one pending update of this type to handle at a given time.
nodeUpdates chan *nodeUpdates
// pendingOpenUpdates is a channel where updates about channel pending
// opening will be sent. This channel will be buffered to ensure we
// have at most one pending update of this type to handle at a given
// time.
pendingOpenUpdates chan *chanPendingOpenUpdate
// chanOpenFailures is a channel where updates about channel open
// failures will be sent. This channel will be buffered to ensure we
// have at most one pending update of this type to handle at a given
// time.
chanOpenFailures chan *chanOpenFailureUpdate
// heuristicUpdates is a channel where updates from active heuristics
// will be sent.
heuristicUpdates chan *heuristicUpdate
// totalBalance is the total number of satoshis the backing wallet is
// known to control at any given instance. This value will be updated
// when the agent receives external balance update signals.
totalBalance btcutil.Amount
// failedNodes lists nodes that we've previously attempted to initiate
// channels with, but didn't succeed.
failedNodes map[NodeID]struct{}
// pendingConns tracks the nodes that we are attempting to make
// connections to. This prevents us from making duplicate connection
// requests to the same node.
pendingConns map[NodeID]struct{}
// pendingOpens tracks the channels that we've requested to be
// initiated, but haven't yet been confirmed as being fully opened.
// This state is required as otherwise, we may go over our allotted
// channel limit, or open multiple channels to the same node.
pendingOpens map[NodeID]LocalChannel
pendingMtx sync.Mutex
quit chan struct{}
wg sync.WaitGroup
}
// New creates a new instance of the Agent instantiated using the passed
// configuration and initial channel state. The initial channel state slice
// should be populated with the set of Channels that are currently opened by
// the backing Lightning Node.
func New(cfg Config, initialState []LocalChannel) (*Agent, error) {
a := &Agent{
cfg: cfg,
chanState: make(map[lnwire.ShortChannelID]LocalChannel),
quit: make(chan struct{}),
stateUpdates: make(chan interface{}),
balanceUpdates: make(chan *balanceUpdate, 1),
nodeUpdates: make(chan *nodeUpdates, 1),
chanOpenFailures: make(chan *chanOpenFailureUpdate, 1),
heuristicUpdates: make(chan *heuristicUpdate, 1),
pendingOpenUpdates: make(chan *chanPendingOpenUpdate, 1),
failedNodes: make(map[NodeID]struct{}),
pendingConns: make(map[NodeID]struct{}),
pendingOpens: make(map[NodeID]LocalChannel),
}
for _, c := range initialState {
a.chanState[c.ChanID] = c
}
return a, nil
}
// Start starts the agent along with any goroutines it needs to perform its
// normal duties.
func (a *Agent) Start() error {
var err error
a.started.Do(func() {
err = a.start()
})
return err
}
func (a *Agent) start() error {
rand.Seed(time.Now().Unix())
log.Infof("Autopilot Agent starting")
a.wg.Add(1)
go a.controller()
return nil
}
// Stop signals the Agent to gracefully shutdown. This function will block
// until all goroutines have exited.
func (a *Agent) Stop() error {
var err error
a.stopped.Do(func() {
err = a.stop()
})
return err
}
func (a *Agent) stop() error {
log.Infof("Autopilot Agent stopping")
close(a.quit)
a.wg.Wait()
return nil
}
// balanceUpdate is a type of external state update that reflects an
// increase/decrease in the funds currently available to the wallet.
type balanceUpdate struct {
}
// nodeUpdates is a type of external state update that reflects an addition or
// modification in channel graph node membership.
type nodeUpdates struct{}
// chanOpenUpdate is a type of external state update that indicates a new
// channel has been opened, either by the Agent itself (within the main
// controller loop), or by an external user to the system.
type chanOpenUpdate struct {
newChan LocalChannel
}
// chanPendingOpenUpdate is a type of external state update that indicates a new
// channel has been opened, either by the agent itself or an external subsystem,
// but is still pending.
type chanPendingOpenUpdate struct{}
// chanOpenFailureUpdate is a type of external state update that indicates
// a previous channel open failed, and that it might be possible to try again.
type chanOpenFailureUpdate struct{}
// heuristicUpdate is an update sent when one of the autopilot heuristics has
// changed, and prompts the agent to make a new attempt at opening more
// channels.
type heuristicUpdate struct {
heuristic AttachmentHeuristic
}
// chanCloseUpdate is a type of external state update that indicates that the
// backing Lightning Node has closed a previously open channel.
type chanCloseUpdate struct {
closedChans []lnwire.ShortChannelID
}
// OnBalanceChange is a callback that should be executed each time the balance
// of the backing wallet changes.
func (a *Agent) OnBalanceChange() {
select {
case a.balanceUpdates <- &balanceUpdate{}:
default:
}
}
// OnNodeUpdates is a callback that should be executed each time our channel
// graph has new nodes or their node announcements are updated.
func (a *Agent) OnNodeUpdates() {
select {
case a.nodeUpdates <- &nodeUpdates{}:
default:
}
}
// OnChannelOpen is a callback that should be executed each time a new channel
// is manually opened by the user or any system outside the autopilot agent.
func (a *Agent) OnChannelOpen(c LocalChannel) {
a.wg.Add(1)
go func() {
defer a.wg.Done()
select {
case a.stateUpdates <- &chanOpenUpdate{newChan: c}:
case <-a.quit:
}
}()
}
// OnChannelPendingOpen is a callback that should be executed each time a new
// channel is opened, either by the agent or an external subsystems, but is
// still pending.
func (a *Agent) OnChannelPendingOpen() {
select {
case a.pendingOpenUpdates <- &chanPendingOpenUpdate{}:
default:
}
}
// OnChannelOpenFailure is a callback that should be executed when the
// autopilot has attempted to open a channel, but failed. In this case we can
// retry channel creation with a different node.
func (a *Agent) OnChannelOpenFailure() {
select {
case a.chanOpenFailures <- &chanOpenFailureUpdate{}:
default:
}
}
// OnChannelClose is a callback that should be executed each time a prior
// channel has been closed for any reason. This includes regular
// closes, force closes, and channel breaches.
func (a *Agent) OnChannelClose(closedChans ...lnwire.ShortChannelID) {
a.wg.Add(1)
go func() {
defer a.wg.Done()
select {
case a.stateUpdates <- &chanCloseUpdate{closedChans: closedChans}:
case <-a.quit:
}
}()
}
// OnHeuristicUpdate is a method called when a heuristic has been updated, to
// trigger the agent to do a new state assessment.
func (a *Agent) OnHeuristicUpdate(h AttachmentHeuristic) {
select {
case a.heuristicUpdates <- &heuristicUpdate{
heuristic: h,
}:
default:
}
}
// mergeNodeMaps merges the Agent's set of nodes that it already has active
// channels open to, with the other sets of nodes that should be removed from
// consideration during heuristic selection. This ensures that the Agent doesn't
// attempt to open any "duplicate" channels to the same node.
func mergeNodeMaps(c map[NodeID]LocalChannel,
skips ...map[NodeID]struct{}) map[NodeID]struct{} {
numNodes := len(c)
for _, skip := range skips {
numNodes += len(skip)
}
res := make(map[NodeID]struct{}, numNodes)
for nodeID := range c {
res[nodeID] = struct{}{}
}
for _, skip := range skips {
for nodeID := range skip {
res[nodeID] = struct{}{}
}
}
return res
}
// mergeChanState merges the Agent's set of active channels, with the set of
// channels awaiting confirmation. This ensures that the agent doesn't go over
// the prescribed channel limit or fund allocation limit.
func mergeChanState(pendingChans map[NodeID]LocalChannel,
activeChans channelState) []LocalChannel {
numChans := len(pendingChans) + len(activeChans)
totalChans := make([]LocalChannel, 0, numChans)
totalChans = append(totalChans, activeChans.Channels()...)
for _, pendingChan := range pendingChans {
totalChans = append(totalChans, pendingChan)
}
return totalChans
}
// controller implements the closed-loop control system of the Agent. The
// controller will make a decision w.r.t channel placement within the graph
// based on: its current internal state of the set of active channels open,
// and external state changes as a result of decisions it makes w.r.t channel
// allocation, or attributes affecting its control loop being updated by the
// backing Lightning Node.
func (a *Agent) controller() {
defer a.wg.Done()
// We'll start off by assigning our starting balance, and injecting
// that amount as an initial wake up to the main controller goroutine.
a.OnBalanceChange()
// TODO(roasbeef): do we in fact need to maintain order?
// * use sync.Cond if so
updateBalance := func() {
newBalance, err := a.cfg.WalletBalance()
if err != nil {
log.Warnf("unable to update wallet balance: %v", err)
return
}
a.totalBalance = newBalance
}
// TODO(roasbeef): add 10-minute wake up timer
for {
select {
// A new external signal has arrived. We'll use this to update
// our internal state, then determine if we should trigger a
// channel state modification (open/close, splice in/out).
case signal := <-a.stateUpdates:
log.Infof("Processing new external signal")
switch update := signal.(type) {
// A new channel has been opened successfully. This was
// either opened by the Agent, or an external system
// that is able to drive the Lightning Node.
case *chanOpenUpdate:
log.Debugf("New channel successfully opened, "+
"updating state with: %v",
spew.Sdump(update.newChan))
newChan := update.newChan
a.chanStateMtx.Lock()
a.chanState[newChan.ChanID] = newChan
a.chanStateMtx.Unlock()
a.pendingMtx.Lock()
delete(a.pendingOpens, newChan.Node)
a.pendingMtx.Unlock()
updateBalance()
// A channel has been closed, this may free up an
// available slot, triggering a new channel update.
case *chanCloseUpdate:
log.Debugf("Applying closed channel "+
"updates: %v",
spew.Sdump(update.closedChans))
a.chanStateMtx.Lock()
for _, closedChan := range update.closedChans {
delete(a.chanState, closedChan)
}
a.chanStateMtx.Unlock()
updateBalance()
}
// A new channel has been opened by the agent or an external
// subsystem, but is still pending confirmation.
case <-a.pendingOpenUpdates:
updateBalance()
// The balance of the backing wallet has changed, if more funds
// are now available, we may attempt to open up an additional
// channel, or splice in funds to an existing one.
case <-a.balanceUpdates:
log.Debug("Applying external balance state update")
updateBalance()
// The channel we tried to open previously failed for whatever
// reason.
case <-a.chanOpenFailures:
log.Debug("Retrying after previous channel open " +
"failure.")
updateBalance()
// New nodes have been added to the graph or their node
// announcements have been updated. We will consider opening
// channels to these nodes if we haven't stabilized.
case <-a.nodeUpdates:
log.Debugf("Node updates received, assessing " +
"need for more channels")
// Any of the deployed heuristics has been updated, check
// whether we have new channel candidates available.
case upd := <-a.heuristicUpdates:
log.Debugf("Heuristic %v updated, assessing need for "+
"more channels", upd.heuristic.Name())
// The agent has been signalled to exit, so we'll bail out
// immediately.
case <-a.quit:
return
}
a.pendingMtx.Lock()
log.Debugf("Pending channels: %v", spew.Sdump(a.pendingOpens))
a.pendingMtx.Unlock()
// With all the updates applied, we'll obtain a set of the
// current active channels (confirmed channels), and also
// factor in our set of unconfirmed channels.
a.chanStateMtx.Lock()
a.pendingMtx.Lock()
totalChans := mergeChanState(a.pendingOpens, a.chanState)
a.pendingMtx.Unlock()
a.chanStateMtx.Unlock()
// Now that we've updated our internal state, we'll consult our
// channel attachment heuristic to determine if we can open
// up any additional channels while staying within our
// constraints.
availableFunds, numChans := a.cfg.Constraints.ChannelBudget(
totalChans, a.totalBalance,
)
switch {
case numChans == 0:
continue
// If the amount is too small, we don't want to attempt opening
// another channel.
case availableFunds == 0:
continue
case availableFunds < a.cfg.Constraints.MinChanSize():
continue
}
log.Infof("Triggering attachment directive dispatch, "+
"total_funds=%v", a.totalBalance)
err := a.openChans(availableFunds, numChans, totalChans)
if err != nil {
log.Errorf("Unable to open channels: %v", err)
}
}
}
// openChans queries the agent's heuristic for a set of channel candidates, and
// attempts to open channels to them.
func (a *Agent) openChans(availableFunds btcutil.Amount, numChans uint32,
totalChans []LocalChannel) error {
// As channel size we'll use the maximum channel size available.
chanSize := a.cfg.Constraints.MaxChanSize()
if availableFunds < chanSize {
chanSize = availableFunds
}
if chanSize < a.cfg.Constraints.MinChanSize() {
return fmt.Errorf("not enough funds available to open a " +
"single channel")
}
// We're to attempt an attachment so we'll obtain the set of
// nodes that we currently have channels with so we avoid
// duplicate edges.
a.chanStateMtx.Lock()
connectedNodes := a.chanState.ConnectedNodes()
a.chanStateMtx.Unlock()
for nID := range connectedNodes {
log.Tracef("Skipping node %x with open channel", nID[:])
}
a.pendingMtx.Lock()
for nID := range a.pendingOpens {
log.Tracef("Skipping node %x with pending channel open", nID[:])
}
for nID := range a.pendingConns {
log.Tracef("Skipping node %x with pending connection", nID[:])
}
for nID := range a.failedNodes {
log.Tracef("Skipping failed node %v", nID[:])
}
nodesToSkip := mergeNodeMaps(a.pendingOpens,
a.pendingConns, connectedNodes, a.failedNodes,
)
a.pendingMtx.Unlock()
// Gather the set of all nodes in the graph, except those we
// want to skip.
selfPubBytes := a.cfg.Self.SerializeCompressed()
nodes := make(map[NodeID]struct{})
addresses := make(map[NodeID][]net.Addr)
if err := a.cfg.Graph.ForEachNode(func(node Node) error {
nID := NodeID(node.PubKey())
// If we come across ourselves, them we'll continue in
// order to avoid attempting to make a channel with
// ourselves.
if bytes.Equal(nID[:], selfPubBytes) {
log.Tracef("Skipping self node %x", nID[:])
return nil
}
// If the node has no known addresses, we cannot connect to it,
// so we'll skip it.
addrs := node.Addrs()
if len(addrs) == 0 {
log.Tracef("Skipping node %x since no addresses known",
nID[:])
return nil
}
addresses[nID] = addrs
// Additionally, if this node is in the blacklist, then
// we'll skip it.
if _, ok := nodesToSkip[nID]; ok {
log.Tracef("Skipping blacklisted node %x", nID[:])
return nil
}
nodes[nID] = struct{}{}
return nil
}); err != nil {
return fmt.Errorf("unable to get graph nodes: %w", err)
}
// Use the heuristic to calculate a score for each node in the
// graph.
log.Debugf("Scoring %d nodes for chan_size=%v", len(nodes), chanSize)
scores, err := a.cfg.Heuristic.NodeScores(
a.cfg.Graph, totalChans, chanSize, nodes,
)
if err != nil {
return fmt.Errorf("unable to calculate node scores : %w", err)
}
log.Debugf("Got scores for %d nodes", len(scores))
// Now use the score to make a weighted choice which nodes to attempt
// to open channels to.
scores, err = chooseN(numChans, scores)
if err != nil {
return fmt.Errorf("unable to make weighted choice: %w",
err)
}
chanCandidates := make(map[NodeID]*AttachmentDirective)
for nID := range scores {
log.Tracef("Creating attachment directive for chosen node %x",
nID[:])
// Track the available funds we have left.
if availableFunds < chanSize {
chanSize = availableFunds
}
availableFunds -= chanSize
// If we run out of funds, we can break early.
if chanSize < a.cfg.Constraints.MinChanSize() {
log.Tracef("Chan size %v too small to satisfy min "+
"channel size %v, breaking", chanSize,
a.cfg.Constraints.MinChanSize())
break
}
chanCandidates[nID] = &AttachmentDirective{
NodeID: nID,
ChanAmt: chanSize,
Addrs: addresses[nID],
}
}
if len(chanCandidates) == 0 {
log.Infof("No eligible candidates to connect to")
return nil
}
log.Infof("Attempting to execute channel attachment "+
"directives: %v", spew.Sdump(chanCandidates))
// Before proceeding, check to see if we have any slots
// available to open channels. If there are any, we will attempt
// to dispatch the retrieved directives since we can't be
// certain which ones may actually succeed. If too many
// connections succeed, they will be ignored and made
// available to future heuristic selections.
a.pendingMtx.Lock()
defer a.pendingMtx.Unlock()
if uint16(len(a.pendingOpens)) >= a.cfg.Constraints.MaxPendingOpens() {
log.Debugf("Reached cap of %v pending "+
"channel opens, will retry "+
"after success/failure",
a.cfg.Constraints.MaxPendingOpens())
return nil
}
// For each recommended attachment directive, we'll launch a
// new goroutine to attempt to carry out the directive. If any
// of these succeed, then we'll receive a new state update,
// taking us back to the top of our controller loop.
for _, chanCandidate := range chanCandidates {
// Skip candidates which we are already trying
// to establish a connection with.
nodeID := chanCandidate.NodeID
if _, ok := a.pendingConns[nodeID]; ok {
continue
}
a.pendingConns[nodeID] = struct{}{}
a.wg.Add(1)
go a.executeDirective(*chanCandidate)
}
return nil
}
// executeDirective attempts to connect to the channel candidate specified by
// the given attachment directive, and open a channel of the given size.
//
// NOTE: MUST be run as a goroutine.
func (a *Agent) executeDirective(directive AttachmentDirective) {
defer a.wg.Done()
// We'll start out by attempting to connect to the peer in order to
// begin the funding workflow.
nodeID := directive.NodeID
pub, err := btcec.ParsePubKey(nodeID[:])
if err != nil {
log.Errorf("Unable to parse pubkey %x: %v", nodeID, err)
return
}
connected := make(chan bool)
errChan := make(chan error)
// To ensure a call to ConnectToPeer doesn't block the agent from
// shutting down, we'll launch it in a non-waitgrouped goroutine, that
// will signal when a result is returned.
// TODO(halseth): use DialContext to cancel on transport level.
go func() {
alreadyConnected, err := a.cfg.ConnectToPeer(
pub, directive.Addrs,
)
if err != nil {
select {
case errChan <- err:
case <-a.quit:
}
return
}
select {
case connected <- alreadyConnected:
case <-a.quit:
return
}
}()
var alreadyConnected bool
select {
case alreadyConnected = <-connected:
case err = <-errChan:
case <-a.quit:
return
}
if err != nil {
log.Warnf("Unable to connect to %x: %v",
pub.SerializeCompressed(), err)
// Since we failed to connect to them, we'll mark them as
// failed so that we don't attempt to connect to them again.
a.pendingMtx.Lock()
delete(a.pendingConns, nodeID)
a.failedNodes[nodeID] = struct{}{}
a.pendingMtx.Unlock()
// Finally, we'll trigger the agent to select new peers to
// connect to.
a.OnChannelOpenFailure()
return
}
// The connection was successful, though before progressing we must
// check that we have not already met our quota for max pending open
// channels. This can happen if multiple directives were spawned but
// fewer slots were available, and other successful attempts finished
// first.
a.pendingMtx.Lock()
if uint16(len(a.pendingOpens)) >= a.cfg.Constraints.MaxPendingOpens() {
// Since we've reached our max number of pending opens, we'll
// disconnect this peer and exit. However, if we were
// previously connected to them, then we'll make sure to
// maintain the connection alive.
if alreadyConnected {
// Since we succeeded in connecting, we won't add this
// peer to the failed nodes map, but we will remove it
// from a.pendingConns so that it can be retried in the
// future.
delete(a.pendingConns, nodeID)
a.pendingMtx.Unlock()
return
}
err = a.cfg.DisconnectPeer(pub)
if err != nil {
log.Warnf("Unable to disconnect peer %x: %v",
pub.SerializeCompressed(), err)
}
// Now that we have disconnected, we can remove this node from
// our pending conns map, permitting subsequent connection
// attempts.
delete(a.pendingConns, nodeID)
a.pendingMtx.Unlock()
return
}
// If we were successful, we'll track this peer in our set of pending
// opens. We do this here to ensure we don't stall on selecting new
// peers if the connection attempt happens to take too long.
delete(a.pendingConns, nodeID)
a.pendingOpens[nodeID] = LocalChannel{
Balance: directive.ChanAmt,
Node: nodeID,
}
a.pendingMtx.Unlock()
// We can then begin the funding workflow with this peer.
err = a.cfg.ChanController.OpenChannel(pub, directive.ChanAmt)
if err != nil {
log.Warnf("Unable to open channel to %x of %v: %v",
pub.SerializeCompressed(), directive.ChanAmt, err)
// As the attempt failed, we'll clear the peer from the set of
// pending opens and mark them as failed so we don't attempt to
// open a channel to them again.
a.pendingMtx.Lock()
delete(a.pendingOpens, nodeID)
a.failedNodes[nodeID] = struct{}{}
a.pendingMtx.Unlock()
// Trigger the agent to re-evaluate everything and possibly
// retry with a different node.
a.OnChannelOpenFailure()
// Finally, we should also disconnect the peer if we weren't
// already connected to them beforehand by an external
// subsystem.
if alreadyConnected {
return
}
err = a.cfg.DisconnectPeer(pub)
if err != nil {
log.Warnf("Unable to disconnect peer %x: %v",
pub.SerializeCompressed(), err)
}
}
// Since the channel open was successful and is currently pending,
// we'll trigger the autopilot agent to query for more peers.
// TODO(halseth): this triggers a new loop before all the new channels
// are added to the pending channels map. Should add before executing
// directive in goroutine?
a.OnChannelPendingOpen()
}
package autopilot
import (
"github.com/btcsuite/btcd/btcutil"
)
// AgentConstraints is an interface the agent will query to determine what
// limits it will need to stay inside when opening channels.
type AgentConstraints interface {
// ChannelBudget should, given the passed parameters, return whether
// more channels can be opened while still staying within the set
// constraints. If the constraints allow us to open more channels, then
// the first return value will represent the amount of additional funds
// available towards creating channels. The second return value is the
// exact *number* of additional channels available.
ChannelBudget(chans []LocalChannel, balance btcutil.Amount) (
btcutil.Amount, uint32)
// MaxPendingOpens returns the maximum number of pending channel
// establishment goroutines that can be lingering. We cap this value in
// order to control the level of parallelism caused by the autopilot
// agent.
MaxPendingOpens() uint16
// MinChanSize returns the smallest channel that the autopilot agent
// should create.
MinChanSize() btcutil.Amount
// MaxChanSize returns largest channel that the autopilot agent should
// create.
MaxChanSize() btcutil.Amount
}
// agentConstraints is an implementation of the AgentConstraints interface that
// indicate the constraints the autopilot agent must adhere to when opening
// channels.
type agentConstraints struct {
// minChanSize is the smallest channel that the autopilot agent should
// create.
minChanSize btcutil.Amount
// maxChanSize is the largest channel that the autopilot agent should
// create.
maxChanSize btcutil.Amount
// chanLimit is the maximum number of channels that should be created.
chanLimit uint16
// allocation is the percentage of total funds that should be committed
// to automatic channel establishment.
allocation float64
// maxPendingOpens is the maximum number of pending channel
// establishment goroutines that can be lingering. We cap this value in
// order to control the level of parallelism caused by the autopilot
// agent.
maxPendingOpens uint16
}
// A compile time assertion to ensure agentConstraints satisfies the
// AgentConstraints interface.
var _ AgentConstraints = (*agentConstraints)(nil)
// NewConstraints returns a new AgentConstraints with the given limits.
func NewConstraints(minChanSize, maxChanSize btcutil.Amount, chanLimit,
maxPendingOpens uint16, allocation float64) AgentConstraints {
return &agentConstraints{
minChanSize: minChanSize,
maxChanSize: maxChanSize,
chanLimit: chanLimit,
allocation: allocation,
maxPendingOpens: maxPendingOpens,
}
}
// ChannelBudget should, given the passed parameters, return whether more
// channels can be be opened while still staying within the set constraints.
// If the constraints allow us to open more channels, then the first return
// value will represent the amount of additional funds available towards
// creating channels. The second return value is the exact *number* of
// additional channels available.
//
// Note: part of the AgentConstraints interface.
func (h *agentConstraints) ChannelBudget(channels []LocalChannel,
funds btcutil.Amount) (btcutil.Amount, uint32) {
// If we're already over our maximum allowed number of channels, then
// we'll instruct the controller not to create any more channels.
if len(channels) >= int(h.chanLimit) {
return 0, 0
}
// The number of additional channels that should be opened is the
// difference between the channel limit, and the number of channels we
// already have open.
numAdditionalChans := uint32(h.chanLimit) - uint32(len(channels))
// First, we'll tally up the total amount of funds that are currently
// present within the set of active channels.
var totalChanAllocation btcutil.Amount
for _, channel := range channels {
totalChanAllocation += channel.Balance
}
// With this value known, we'll now compute the total amount of fund
// allocated across regular utxo's and channel utxo's.
totalFunds := funds + totalChanAllocation
// Once the total amount has been computed, we then calculate the
// fraction of funds currently allocated to channels.
fundsFraction := float64(totalChanAllocation) / float64(totalFunds)
// If this fraction is below our threshold, then we'll return true, to
// indicate the controller should call Select to obtain a candidate set
// of channels to attempt to open.
needMore := fundsFraction < h.allocation
if !needMore {
return 0, 0
}
// Now that we know we need more funds, we'll compute the amount of
// additional funds we should allocate towards channels.
targetAllocation := btcutil.Amount(float64(totalFunds) * h.allocation)
fundsAvailable := targetAllocation - totalChanAllocation
return fundsAvailable, numAdditionalChans
}
// MaxPendingOpens returns the maximum number of pending channel establishment
// goroutines that can be lingering. We cap this value in order to control the
// level of parallelism caused by the autopilot agent.
//
// Note: part of the AgentConstraints interface.
func (h *agentConstraints) MaxPendingOpens() uint16 {
return h.maxPendingOpens
}
// MinChanSize returns the smallest channel that the autopilot agent should
// create.
//
// Note: part of the AgentConstraints interface.
func (h *agentConstraints) MinChanSize() btcutil.Amount {
return h.minChanSize
}
// MaxChanSize returns largest channel that the autopilot agent should create.
//
// Note: part of the AgentConstraints interface.
func (h *agentConstraints) MaxChanSize() btcutil.Amount {
return h.maxChanSize
}
package autopilot
import (
"fmt"
"sync"
)
// stack is a simple int stack to help with readability of Brandes'
// betweenness centrality implementation below.
type stack struct {
stack []int
}
func (s *stack) push(v int) {
s.stack = append(s.stack, v)
}
func (s *stack) top() int {
return s.stack[len(s.stack)-1]
}
func (s *stack) pop() {
s.stack = s.stack[:len(s.stack)-1]
}
func (s *stack) empty() bool {
return len(s.stack) == 0
}
// queue is a simple int queue to help with readability of Brandes'
// betweenness centrality implementation below.
type queue struct {
queue []int
}
func (q *queue) push(v int) {
q.queue = append(q.queue, v)
}
func (q *queue) front() int {
return q.queue[0]
}
func (q *queue) pop() {
q.queue = q.queue[1:]
}
func (q *queue) empty() bool {
return len(q.queue) == 0
}
// BetweennessCentrality is a NodeMetric that calculates node betweenness
// centrality using Brandes' algorithm. Betweenness centrality for each node
// is the number of shortest paths passing through that node, not counting
// shortest paths starting or ending at that node. This is a useful metric
// to measure control of individual nodes over the whole network.
type BetweennessCentrality struct {
// workers number of goroutines are used to parallelize
// centrality calculation.
workers int
// centrality stores original (not normalized) centrality values for
// each node in the graph.
centrality map[NodeID]float64
// min is the minimum centrality in the graph.
min float64
// max is the maximum centrality in the graph.
max float64
}
// NewBetweennessCentralityMetric creates a new BetweennessCentrality instance.
// Users can specify the number of workers to use for calculating centrality.
func NewBetweennessCentralityMetric(workers int) (*BetweennessCentrality, error) {
// There should be at least one worker.
if workers < 1 {
return nil, fmt.Errorf("workers must be positive")
}
return &BetweennessCentrality{
workers: workers,
}, nil
}
// Name returns the name of the metric.
func (bc *BetweennessCentrality) Name() string {
return "betweenness_centrality"
}
// betweennessCentrality is the core of Brandes' algorithm.
// We first calculate the shortest paths from the start node s to all other
// nodes with BFS, then update the betweenness centrality values by using
// Brandes' dependency trick.
// For detailed explanation please read:
// https://www.cl.cam.ac.uk/teaching/1617/MLRD/handbook/brandes.html
func betweennessCentrality(g *SimpleGraph, s int, centrality []float64) {
// pred[w] is the list of nodes that immediately precede w on a
// shortest path from s to t for each node t.
pred := make([][]int, len(g.Nodes))
// sigma[t] is the number of shortest paths between nodes s and t
// for each node t.
sigma := make([]int, len(g.Nodes))
sigma[s] = 1
// dist[t] holds the distance between s and t for each node t.
// We initialize this to -1 (meaning infinity) for each t != s.
dist := make([]int, len(g.Nodes))
for i := range dist {
dist[i] = -1
}
dist[s] = 0
var (
st stack
q queue
)
q.push(s)
// BFS to calculate the shortest paths (sigma and pred)
// from s to t for each node t.
for !q.empty() {
v := q.front()
q.pop()
st.push(v)
for _, w := range g.Adj[v] {
// If distance from s to w is infinity (-1)
// then set it and enqueue w.
if dist[w] < 0 {
dist[w] = dist[v] + 1
q.push(w)
}
// If w is on a shortest path the update
// sigma and add v to w's predecessor list.
if dist[w] == dist[v]+1 {
sigma[w] += sigma[v]
pred[w] = append(pred[w], v)
}
}
}
// delta[v] is the ratio of the shortest paths between s and t that go
// through v and the total number of shortest paths between s and t.
// If we have delta then the betweenness centrality is simply the sum
// of delta[w] for each w != s.
delta := make([]float64, len(g.Nodes))
for !st.empty() {
w := st.top()
st.pop()
// pred[w] is the list of nodes that immediately precede w on a
// shortest path from s.
for _, v := range pred[w] {
// Update delta using Brandes' equation.
delta[v] += (float64(sigma[v]) / float64(sigma[w])) * (1.0 + delta[w])
}
if w != s {
// As noted above centrality is simply the sum
// of delta[w] for each w != s.
centrality[w] += delta[w]
}
}
}
// Refresh recalculates and stores centrality values.
func (bc *BetweennessCentrality) Refresh(graph ChannelGraph) error {
cache, err := NewSimpleGraph(graph)
if err != nil {
return err
}
var wg sync.WaitGroup
work := make(chan int)
partials := make(chan []float64, bc.workers)
// Each worker will compute a partial result.
// This partial result is a sum of centrality updates
// on roughly N / workers nodes.
worker := func() {
defer wg.Done()
partial := make([]float64, len(cache.Nodes))
// Consume the next node, update centrality
// parital to avoid unnecessary synchronization.
for node := range work {
betweennessCentrality(cache, node, partial)
}
partials <- partial
}
// Now start the N workers.
wg.Add(bc.workers)
for i := 0; i < bc.workers; i++ {
go worker()
}
// Distribute work amongst workers.
// Should be fair when the graph is sufficiently large.
for node := range cache.Nodes {
work <- node
}
close(work)
wg.Wait()
close(partials)
// Collect and sum partials for final result.
centrality := make([]float64, len(cache.Nodes))
for partial := range partials {
for i := 0; i < len(partial); i++ {
centrality[i] += partial[i]
}
}
// Get min/max to be able to normalize
// centrality values between 0 and 1.
bc.min = 0
bc.max = 0
if len(centrality) > 0 {
for _, v := range centrality {
if v < bc.min {
bc.min = v
} else if v > bc.max {
bc.max = v
}
}
}
// Divide by two as this is an undirected graph.
bc.min /= 2.0
bc.max /= 2.0
bc.centrality = make(map[NodeID]float64)
for u, value := range centrality {
// Divide by two as this is an undirected graph.
bc.centrality[cache.Nodes[u]] = value / 2.0
}
return nil
}
// GetMetric returns the current centrality values for each node indexed
// by node id.
func (bc *BetweennessCentrality) GetMetric(normalize bool) map[NodeID]float64 {
// Normalization factor.
var z float64
if (bc.max - bc.min) > 0 {
z = 1.0 / (bc.max - bc.min)
}
centrality := make(map[NodeID]float64)
for k, v := range bc.centrality {
if normalize {
v = (v - bc.min) * z
}
centrality[k] = v
}
return centrality
}
package autopilot
import (
"errors"
"fmt"
"math/rand"
)
// ErrNoPositive is returned from weightedChoice when there are no positive
// weights left to choose from.
var ErrNoPositive = errors.New("no positive weights left")
// weightedChoice draws a random index from the slice of weights, with a
// probability proportional to the weight at the given index.
func weightedChoice(w []float64) (int, error) {
// Calculate the sum of weights.
var sum float64
for _, v := range w {
sum += v
}
if sum <= 0 {
return 0, ErrNoPositive
}
// Pick a random number in the range [0.0, 1.0) and multiply it with
// the sum of weights. Then we'll iterate the weights until the number
// goes below 0. This means that each index is picked with a probability
// equal to their normalized score.
//
// Example:
// Items with scores [1, 5, 2, 2]
// Normalized scores [0.1, 0.5, 0.2, 0.2]
// Imagine they each occupy a "range" equal to their normalized score
// in [0, 1.0]:
// [|-0.1-||-----0.5-----||--0.2--||--0.2--|]
// The following loop is now equivalent to "hitting" the intervals.
r := rand.Float64() * sum
for i := range w {
r -= w[i]
if r <= 0 {
return i, nil
}
}
return 0, fmt.Errorf("unable to make choice")
}
// chooseN picks at random min[n, len(s)] nodes if from the NodeScore map, with
// a probability weighted by their score.
func chooseN(n uint32, s map[NodeID]*NodeScore) (
map[NodeID]*NodeScore, error) {
// Keep track of the number of nodes not yet chosen, in addition to
// their scores and NodeIDs.
rem := len(s)
scores := make([]float64, len(s))
nodeIDs := make([]NodeID, len(s))
i := 0
for k, v := range s {
scores[i] = v.Score
nodeIDs[i] = k
i++
}
// Pick a weighted choice from the remaining nodes as long as there are
// nodes left, and we haven't already picked n.
chosen := make(map[NodeID]*NodeScore)
for len(chosen) < int(n) && rem > 0 {
choice, err := weightedChoice(scores)
if err == ErrNoPositive {
return chosen, nil
} else if err != nil {
return nil, err
}
nID := nodeIDs[choice]
chosen[nID] = s[nID]
// We set the score of the chosen node to 0, so it won't be
// picked the next iteration.
scores[choice] = 0
}
return chosen, nil
}
package autopilot
import (
"fmt"
"github.com/btcsuite/btcd/btcutil"
)
// WeightedHeuristic is a tuple that associates a weight to an
// AttachmentHeuristic. This is used to determining a node's final score when
// querying several heuristics for scores.
type WeightedHeuristic struct {
// Weight is this AttachmentHeuristic's relative weight factor. It
// should be between 0.0 and 1.0.
Weight float64
AttachmentHeuristic
}
// WeightedCombAttachment is an implementation of the AttachmentHeuristic
// interface that combines the scores given by several sub-heuristics into one.
type WeightedCombAttachment struct {
heuristics []*WeightedHeuristic
}
// NewWeightedCombAttachment creates a new instance of a WeightedCombAttachment.
func NewWeightedCombAttachment(h ...*WeightedHeuristic) (
*WeightedCombAttachment, error) {
// The sum of weights given to the sub-heuristics must sum to exactly
// 1.0.
var sum float64
for _, w := range h {
sum += w.Weight
}
if sum != 1.0 {
return nil, fmt.Errorf("weights MUST sum to 1.0 (was %v)", sum)
}
return &WeightedCombAttachment{
heuristics: h,
}, nil
}
// A compile time assertion to ensure WeightedCombAttachment meets the
// AttachmentHeuristic and ScoreSettable interfaces.
var _ AttachmentHeuristic = (*WeightedCombAttachment)(nil)
var _ ScoreSettable = (*WeightedCombAttachment)(nil)
// Name returns the name of this heuristic.
//
// NOTE: This is a part of the AttachmentHeuristic interface.
func (c *WeightedCombAttachment) Name() string {
return "weightedcomb"
}
// NodeScores is a method that given the current channel graph, current set of
// local channels and funds available, scores the given nodes according to the
// preference of opening a channel with them. The returned channel candidates
// maps the NodeID to an attachment directive containing a score and a channel
// size.
//
// The scores is determined by querying the set of sub-heuristics, then
// combining these scores into a final score according to the active
// configuration.
//
// The returned scores will be in the range [0, 1.0], where 0 indicates no
// improvement in connectivity if a channel is opened to this node, while 1.0
// is the maximum possible improvement in connectivity.
//
// NOTE: This is a part of the AttachmentHeuristic interface.
func (c *WeightedCombAttachment) NodeScores(g ChannelGraph, chans []LocalChannel,
chanSize btcutil.Amount, nodes map[NodeID]struct{}) (
map[NodeID]*NodeScore, error) {
// We now query each heuristic to determine the score they give to the
// nodes for the given channel size.
var subScores []map[NodeID]*NodeScore
for _, h := range c.heuristics {
log.Tracef("Getting scores from sub heuristic %v", h.Name())
s, err := h.NodeScores(
g, chans, chanSize, nodes,
)
if err != nil {
return nil, fmt.Errorf("unable to get sub score: %w",
err)
}
subScores = append(subScores, s)
}
// We combine the scores given by the sub-heuristics by using the
// heuristics' given weight factor.
scores := make(map[NodeID]*NodeScore)
for nID := range nodes {
score := &NodeScore{
NodeID: nID,
}
// Each sub-heuristic should have scored the node, if not it is
// implicitly given a zero score by that heuristic.
for i, h := range c.heuristics {
sub, ok := subScores[i][nID]
if !ok {
log.Tracef("No score given to node %x by sub "+
"heuristic %v", nID[:], h.Name())
continue
}
// Use the heuristic's weight factor to determine of
// how much weight we should give to this particular
// score.
subScore := h.Weight * sub.Score
log.Tracef("Giving node %x a sub score of %v "+
"(%v * %v) from sub heuristic %v", nID[:],
subScore, h.Weight, sub.Score, h.Name())
score.Score += subScore
}
log.Tracef("Node %x got final combined score %v", nID[:],
score.Score)
switch {
// Instead of adding a node with score 0 to the returned set,
// we just skip it.
case score.Score == 0:
continue
// Sanity check the new score.
case score.Score < 0 || score.Score > 1.0:
return nil, fmt.Errorf("invalid node score from "+
"combination: %v", score.Score)
}
scores[nID] = score
}
return scores, nil
}
// SetNodeScores is used to set the internal map from NodeIDs to scores. The
// passed scores must be in the range [0, 1.0]. The fist parameter is the name
// of the targeted heuristic, to allow recursively target specific
// sub-heuristics. The returned boolean indicates whether the targeted
// heuristic was found.
//
// Since this heuristic doesn't keep any internal scores, it will recursively
// apply the scores to its sub-heuristics.
//
// NOTE: This is a part of the ScoreSettable interface.
func (c *WeightedCombAttachment) SetNodeScores(targetHeuristic string,
newScores map[NodeID]float64) (bool, error) {
found := false
for _, h := range c.heuristics {
// It must be ScoreSettable to be available for external
// scores.
s, ok := h.AttachmentHeuristic.(ScoreSettable)
if !ok {
continue
}
// Heuristic supports scoring, attempt to set them.
applied, err := s.SetNodeScores(targetHeuristic, newScores)
if err != nil {
return false, err
}
found = found || applied
}
return found, nil
}
package autopilot
import (
"fmt"
"sync"
"github.com/btcsuite/btcd/btcutil"
)
// ExternalScoreAttachment is an implementation of the AttachmentHeuristic
// interface that allows an external source to provide it with node scores.
type ExternalScoreAttachment struct {
// TODO(halseth): persist across restarts.
nodeScores map[NodeID]float64
sync.Mutex
}
// NewExternalScoreAttachment creates a new instance of an
// ExternalScoreAttachment.
func NewExternalScoreAttachment() *ExternalScoreAttachment {
return &ExternalScoreAttachment{}
}
// A compile time assertion to ensure ExternalScoreAttachment meets the
// AttachmentHeuristic and ScoreSettable interfaces.
var _ AttachmentHeuristic = (*ExternalScoreAttachment)(nil)
var _ ScoreSettable = (*ExternalScoreAttachment)(nil)
// Name returns the name of this heuristic.
//
// NOTE: This is a part of the AttachmentHeuristic interface.
func (s *ExternalScoreAttachment) Name() string {
return "externalscore"
}
// SetNodeScores is used to set the internal map from NodeIDs to scores. The
// passed scores must be in the range [0, 1.0]. The fist parameter is the name
// of the targeted heuristic, to allow recursively target specific
// sub-heuristics. The returned boolean indicates whether the targeted
// heuristic was found.
//
// NOTE: This is a part of the ScoreSettable interface.
func (s *ExternalScoreAttachment) SetNodeScores(targetHeuristic string,
newScores map[NodeID]float64) (bool, error) {
// Return if this heuristic wasn't targeted.
if targetHeuristic != s.Name() {
return false, nil
}
// Since there's a requirement that all score are in the range [0,
// 1.0], we validate them before setting the internal list.
for nID, s := range newScores {
if s < 0 || s > 1.0 {
return false, fmt.Errorf("invalid score %v for "+
"nodeID %v", s, nID)
}
}
s.Lock()
defer s.Unlock()
s.nodeScores = newScores
log.Tracef("Setting %v external scores", len(s.nodeScores))
return true, nil
}
// NodeScores is a method that given the current channel graph and current set
// of local channels, scores the given nodes according to the preference of
// opening a channel of the given size with them. The returned channel
// candidates maps the NodeID to a NodeScore for the node.
//
// The returned scores will be in the range [0, 1.0], where 0 indicates no
// improvement in connectivity if a channel is opened to this node, while 1.0
// is the maximum possible improvement in connectivity.
//
// The scores are determined by checking the internal node scores list. Nodes
// not known will get a score of 0.
//
// NOTE: This is a part of the AttachmentHeuristic interface.
func (s *ExternalScoreAttachment) NodeScores(g ChannelGraph, chans []LocalChannel,
chanSize btcutil.Amount, nodes map[NodeID]struct{}) (
map[NodeID]*NodeScore, error) {
existingPeers := make(map[NodeID]struct{})
for _, c := range chans {
existingPeers[c.Node] = struct{}{}
}
s.Lock()
defer s.Unlock()
log.Tracef("External scoring %v nodes, from %v set scores",
len(nodes), len(s.nodeScores))
// Fill the map of candidates to return.
candidates := make(map[NodeID]*NodeScore)
for nID := range nodes {
var score float64
if nodeScore, ok := s.nodeScores[nID]; ok {
score = nodeScore
}
// If the node is among or existing channel peers, we don't
// need another channel.
if _, ok := existingPeers[nID]; ok {
log.Tracef("Skipping existing peer %x from external "+
"score results", nID[:])
continue
}
log.Tracef("External score %v given to node %x", score, nID[:])
// Instead of adding a node with score 0 to the returned set,
// we just skip it.
if score == 0 {
continue
}
candidates[nID] = &NodeScore{
NodeID: nID,
Score: score,
}
}
return candidates, nil
}
package autopilot
import (
"encoding/hex"
"net"
"sort"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcutil"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
var (
testRBytes, _ = hex.DecodeString("8ce2bc69281ce27da07e6683571319d18e949ddfa2965fb6caa1bf0314f882d7")
testSBytes, _ = hex.DecodeString("299105481d63e0f4bc2a88121167221b6700d72a0ead154c03be696a292d24ae")
testRScalar = new(btcec.ModNScalar)
testSScalar = new(btcec.ModNScalar)
_ = testRScalar.SetByteSlice(testRBytes)
_ = testSScalar.SetByteSlice(testSBytes)
testSig = ecdsa.NewSignature(testRScalar, testSScalar)
chanIDCounter uint64 // To be used atomically.
)
// databaseChannelGraph wraps a channeldb.ChannelGraph instance with the
// necessary API to properly implement the autopilot.ChannelGraph interface.
//
// TODO(roasbeef): move inmpl to main package?
type databaseChannelGraph struct {
db GraphSource
}
// A compile time assertion to ensure databaseChannelGraph meets the
// autopilot.ChannelGraph interface.
var _ ChannelGraph = (*databaseChannelGraph)(nil)
// ChannelGraphFromDatabase returns an instance of the autopilot.ChannelGraph
// backed by a GraphSource.
func ChannelGraphFromDatabase(db GraphSource) ChannelGraph {
return &databaseChannelGraph{
db: db,
}
}
// type dbNode is a wrapper struct around a database transaction an
// channeldb.LightningNode. The wrapper method implement the autopilot.Node
// interface.
type dbNode struct {
tx graphdb.NodeRTx
}
// A compile time assertion to ensure dbNode meets the autopilot.Node
// interface.
var _ Node = (*dbNode)(nil)
// PubKey is the identity public key of the node. This will be used to attempt
// to target a node for channel opening by the main autopilot agent. The key
// will be returned in serialized compressed format.
//
// NOTE: Part of the autopilot.Node interface.
func (d *dbNode) PubKey() [33]byte {
return d.tx.Node().PubKeyBytes
}
// Addrs returns a slice of publicly reachable public TCP addresses that the
// peer is known to be listening on.
//
// NOTE: Part of the autopilot.Node interface.
func (d *dbNode) Addrs() []net.Addr {
return d.tx.Node().Addresses
}
// ForEachChannel is a higher-order function that will be used to iterate
// through all edges emanating from/to the target node. For each active
// channel, this function should be called with the populated ChannelEdge that
// describes the active channel.
//
// NOTE: Part of the autopilot.Node interface.
func (d *dbNode) ForEachChannel(cb func(ChannelEdge) error) error {
return d.tx.ForEachChannel(func(ei *models.ChannelEdgeInfo, ep,
_ *models.ChannelEdgePolicy) error {
// Skip channels for which no outgoing edge policy is available.
//
// TODO(joostjager): Ideally the case where channels have a nil
// policy should be supported, as autopilot is not looking at
// the policies. For now, it is not easily possible to get a
// reference to the other end LightningNode object without
// retrieving the policy.
if ep == nil {
return nil
}
node, err := d.tx.FetchNode(ep.ToNode)
if err != nil {
return err
}
edge := ChannelEdge{
ChanID: lnwire.NewShortChanIDFromInt(ep.ChannelID),
Capacity: ei.Capacity,
Peer: &dbNode{
tx: node,
},
}
return cb(edge)
})
}
// ForEachNode is a higher-order function that should be called once for each
// connected node within the channel graph. If the passed callback returns an
// error, then execution should be terminated.
//
// NOTE: Part of the autopilot.ChannelGraph interface.
func (d *databaseChannelGraph) ForEachNode(cb func(Node) error) error {
return d.db.ForEachNode(func(nodeTx graphdb.NodeRTx) error {
// We'll skip over any node that doesn't have any advertised
// addresses. As we won't be able to reach them to actually
// open any channels.
if len(nodeTx.Node().Addresses) == 0 {
return nil
}
node := &dbNode{
tx: nodeTx,
}
return cb(node)
})
}
// databaseChannelGraphCached wraps a channeldb.ChannelGraph instance with the
// necessary API to properly implement the autopilot.ChannelGraph interface.
type databaseChannelGraphCached struct {
db GraphSource
}
// A compile time assertion to ensure databaseChannelGraphCached meets the
// autopilot.ChannelGraph interface.
var _ ChannelGraph = (*databaseChannelGraphCached)(nil)
// ChannelGraphFromCachedDatabase returns an instance of the
// autopilot.ChannelGraph backed by a live, open channeldb instance.
func ChannelGraphFromCachedDatabase(db GraphSource) ChannelGraph {
return &databaseChannelGraphCached{
db: db,
}
}
// dbNodeCached is a wrapper struct around a database transaction for a
// channeldb.LightningNode. The wrapper methods implement the autopilot.Node
// interface.
type dbNodeCached struct {
node route.Vertex
channels map[uint64]*graphdb.DirectedChannel
}
// A compile time assertion to ensure dbNodeCached meets the autopilot.Node
// interface.
var _ Node = (*dbNodeCached)(nil)
// PubKey is the identity public key of the node.
//
// NOTE: Part of the autopilot.Node interface.
func (nc dbNodeCached) PubKey() [33]byte {
return nc.node
}
// Addrs returns a slice of publicly reachable public TCP addresses that the
// peer is known to be listening on.
//
// NOTE: Part of the autopilot.Node interface.
func (nc dbNodeCached) Addrs() []net.Addr {
// TODO: Add addresses to be usable by autopilot.
return []net.Addr{}
}
// ForEachChannel is a higher-order function that will be used to iterate
// through all edges emanating from/to the target node. For each active
// channel, this function should be called with the populated ChannelEdge that
// describes the active channel.
//
// NOTE: Part of the autopilot.Node interface.
func (nc dbNodeCached) ForEachChannel(cb func(ChannelEdge) error) error {
for cid, channel := range nc.channels {
edge := ChannelEdge{
ChanID: lnwire.NewShortChanIDFromInt(cid),
Capacity: channel.Capacity,
Peer: dbNodeCached{
node: channel.OtherNode,
},
}
if err := cb(edge); err != nil {
return err
}
}
return nil
}
// ForEachNode is a higher-order function that should be called once for each
// connected node within the channel graph. If the passed callback returns an
// error, then execution should be terminated.
//
// NOTE: Part of the autopilot.ChannelGraph interface.
func (dc *databaseChannelGraphCached) ForEachNode(cb func(Node) error) error {
return dc.db.ForEachNodeCached(func(n route.Vertex,
channels map[uint64]*graphdb.DirectedChannel) error {
if len(channels) > 0 {
node := dbNodeCached{
node: n,
channels: channels,
}
return cb(node)
}
return nil
})
}
// memNode is a purely in-memory implementation of the autopilot.Node
// interface.
type memNode struct {
pub *btcec.PublicKey
chans []ChannelEdge
addrs []net.Addr
}
// A compile time assertion to ensure memNode meets the autopilot.Node
// interface.
var _ Node = (*memNode)(nil)
// PubKey is the identity public key of the node. This will be used to attempt
// to target a node for channel opening by the main autopilot agent.
//
// NOTE: Part of the autopilot.Node interface.
func (m memNode) PubKey() [33]byte {
var n [33]byte
copy(n[:], m.pub.SerializeCompressed())
return n
}
// Addrs returns a slice of publicly reachable public TCP addresses that the
// peer is known to be listening on.
//
// NOTE: Part of the autopilot.Node interface.
func (m memNode) Addrs() []net.Addr {
return m.addrs
}
// ForEachChannel is a higher-order function that will be used to iterate
// through all edges emanating from/to the target node. For each active
// channel, this function should be called with the populated ChannelEdge that
// describes the active channel.
//
// NOTE: Part of the autopilot.Node interface.
func (m memNode) ForEachChannel(cb func(ChannelEdge) error) error {
for _, channel := range m.chans {
if err := cb(channel); err != nil {
return err
}
}
return nil
}
// Median returns the median value in the slice of Amounts.
func Median(vals []btcutil.Amount) btcutil.Amount {
sort.Slice(vals, func(i, j int) bool {
return vals[i] < vals[j]
})
num := len(vals)
switch {
case num == 0:
return 0
case num%2 == 0:
return (vals[num/2-1] + vals[num/2]) / 2
default:
return vals[num/2]
}
}
package autopilot
import (
"net"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
// DefaultConfTarget is the default confirmation target for autopilot channels.
// TODO(halseth): possibly make dynamic, going aggressive->lax as more channels
// are opened.
const DefaultConfTarget = 3
// Node is an interface which represents n abstract vertex within the
// channel graph. All nodes should have at least a single edge to/from them
// within the graph.
//
// TODO(roasbeef): combine with routing.ChannelGraphSource
type Node interface {
// PubKey is the identity public key of the node. This will be used to
// attempt to target a node for channel opening by the main autopilot
// agent. The key will be returned in serialized compressed format.
PubKey() [33]byte
// Addrs returns a slice of publicly reachable public TCP addresses
// that the peer is known to be listening on.
Addrs() []net.Addr
// ForEachChannel is a higher-order function that will be used to
// iterate through all edges emanating from/to the target node. For
// each active channel, this function should be called with the
// populated ChannelEdge that describes the active channel.
ForEachChannel(func(ChannelEdge) error) error
}
// LocalChannel is a simple struct which contains relevant details of a
// particular channel the local node has. The fields in this struct may be used
// as signals for various AttachmentHeuristic implementations.
type LocalChannel struct {
// ChanID is the short channel ID for this channel as defined within
// BOLT-0007.
ChanID lnwire.ShortChannelID
// Balance is the local balance of the channel expressed in satoshis.
Balance btcutil.Amount
// Node is the peer that this channel has been established with.
Node NodeID
// TODO(roasbeef): also add other traits?
// * fee, timelock, etc
}
// ChannelEdge is a struct that holds details concerning a channel, but also
// contains a reference to the Node that this channel connects to as a directed
// edge within the graph. The existence of this reference to the connected node
// will allow callers to traverse the graph in an object-oriented manner.
type ChannelEdge struct {
// ChanID is the short channel ID for this channel as defined within
// BOLT-0007.
ChanID lnwire.ShortChannelID
// Capacity is the capacity of the channel expressed in satoshis.
Capacity btcutil.Amount
// Peer is the peer that this channel creates an edge to in the channel
// graph.
Peer Node
}
// ChannelGraph in an interface that represents a traversable channel graph.
// The autopilot agent will use this interface as its source of graph traits in
// order to make decisions concerning which channels should be opened, and to
// whom.
//
// TODO(roasbeef): abstract??
type ChannelGraph interface {
// ForEachNode is a higher-order function that should be called once
// for each connected node within the channel graph. If the passed
// callback returns an error, then execution should be terminated.
ForEachNode(func(Node) error) error
}
// NodeScore is a tuple mapping a NodeID to a score indicating the preference
// of opening a channel with it.
type NodeScore struct {
// NodeID is the serialized compressed pubkey of the node that is being
// scored.
NodeID NodeID
// Score is the score given by the heuristic for opening a channel of
// the given size to this node.
Score float64
}
// AttachmentDirective describes a channel attachment proscribed by an
// AttachmentHeuristic. It details to which node a channel should be created
// to, and also the parameters which should be used in the channel creation.
type AttachmentDirective struct {
// NodeID is the serialized compressed pubkey of the target node for
// this attachment directive. It can be identified by its public key,
// and therefore can be used along with a ChannelOpener implementation
// to execute the directive.
NodeID NodeID
// ChanAmt is the size of the channel that should be opened, expressed
// in satoshis.
ChanAmt btcutil.Amount
// Addrs is a list of addresses that the target peer may be reachable
// at.
Addrs []net.Addr
}
// AttachmentHeuristic is one of the primary interfaces within this package.
// Implementations of this interface will be used to implement a control system
// which automatically regulates channels of a particular agent, attempting to
// optimize channels opened/closed based on various heuristics. The purpose of
// the interface is to allow an auto-pilot agent to decide if it needs more
// channels, and if so, which exact channels should be opened.
type AttachmentHeuristic interface {
// Name returns the name of this heuristic.
Name() string
// NodeScores is a method that given the current channel graph and
// current set of local channels, scores the given nodes according to
// the preference of opening a channel of the given size with them. The
// returned channel candidates maps the NodeID to a NodeScore for the
// node.
//
// The returned scores will be in the range [0, 1.0], where 0 indicates
// no improvement in connectivity if a channel is opened to this node,
// while 1.0 is the maximum possible improvement in connectivity. The
// implementation of this interface must return scores in this range to
// properly allow the autopilot agent to make a reasonable choice based
// on the score from multiple heuristics.
//
// NOTE: A NodeID not found in the returned map is implicitly given a
// score of 0.
NodeScores(g ChannelGraph, chans []LocalChannel,
chanSize btcutil.Amount, nodes map[NodeID]struct{}) (
map[NodeID]*NodeScore, error)
}
// NodeMetric is a common interface for all graph metrics that are not
// directly used as autopilot node scores but may be used in compositional
// heuristics or statistical information exposed to users.
type NodeMetric interface {
// Name returns the unique name of this metric.
Name() string
// Refresh refreshes the metric values based on the current graph.
Refresh(graph ChannelGraph) error
// GetMetric returns the latest value of this metric. Values in the
// map are per node and can be in arbitrary domain. If normalize is
// set to true, then the returned values are normalized to either
// [0, 1] or [-1, 1] depending on the metric.
GetMetric(normalize bool) map[NodeID]float64
}
// ScoreSettable is an interface that indicates that the scores returned by the
// heuristic can be mutated by an external caller. The ExternalScoreAttachment
// currently implements this interface, and so should any heuristic that is
// using the ExternalScoreAttachment as a sub-heuristic, or keeps their own
// internal list of mutable scores, to allow access to setting the internal
// scores.
type ScoreSettable interface {
// SetNodeScores is used to set the internal map from NodeIDs to
// scores. The passed scores must be in the range [0, 1.0]. The first
// parameter is the name of the targeted heuristic, to allow
// recursively target specific sub-heuristics. The returned boolean
// indicates whether the targeted heuristic was found.
SetNodeScores(string, map[NodeID]float64) (bool, error)
}
var (
// availableHeuristics holds all heuristics possible to combine for use
// with the autopilot agent.
availableHeuristics = []AttachmentHeuristic{
NewPrefAttachment(),
NewExternalScoreAttachment(),
NewTopCentrality(),
}
// AvailableHeuristics is a map that holds the name of available
// heuristics to the actual heuristic for easy lookup. It will be
// filled during init().
AvailableHeuristics = make(map[string]AttachmentHeuristic)
)
func init() {
// Fill the map from heuristic names to available heuristics for easy
// lookup.
for _, h := range availableHeuristics {
AvailableHeuristics[h.Name()] = h
}
}
// ChannelController is a simple interface that allows an auto-pilot agent to
// open a channel within the graph to a target peer, close targeted channels,
// or add/remove funds from existing channels via a splice in/out mechanisms.
type ChannelController interface {
// OpenChannel opens a channel to a target peer, using at most amt
// funds. This means that the resulting channel capacity might be
// slightly less to account for fees. This function should un-block
// immediately after the funding transaction that marks the channel
// open has been broadcast.
OpenChannel(target *btcec.PublicKey, amt btcutil.Amount) error
// CloseChannel attempts to close out the target channel.
//
// TODO(roasbeef): add force option?
CloseChannel(chanPoint *wire.OutPoint) error
}
// GraphSource represents read access to the channel graph.
type GraphSource interface {
// ForEachNode iterates through all the stored vertices/nodes in the
// graph, executing the passed callback with each node encountered. If
// the callback returns an error, then the transaction is aborted and
// the iteration stops early. Any operations performed on the NodeTx
// passed to the call-back are executed under the same read transaction.
ForEachNode(func(graphdb.NodeRTx) error) error
// ForEachNodeCached is similar to ForEachNode, but it utilizes the
// channel graph cache if one is available. It is less consistent than
// ForEachNode since any further calls are made across multiple
// transactions.
ForEachNodeCached(cb func(node route.Vertex,
chans map[uint64]*graphdb.DirectedChannel) error) error
}
package autopilot
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("ATPL", nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package autopilot
import (
"fmt"
"sync"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/wire"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
)
// ManagerCfg houses a set of values and methods that is passed to the Manager
// for it to properly manage its autopilot agent.
type ManagerCfg struct {
// Self is the public key of the lnd instance. It is used to making
// sure the autopilot is not opening channels to itself.
Self *btcec.PublicKey
// PilotCfg is the config of the autopilot agent managed by the
// Manager.
PilotCfg *Config
// ChannelState is a function closure that returns the current set of
// channels managed by this node.
ChannelState func() ([]LocalChannel, error)
// ChannelInfo is a function closure that returns the channel managed
// by the node given by the passed channel point.
ChannelInfo func(wire.OutPoint) (*LocalChannel, error)
// SubscribeTransactions is used to get a subscription for transactions
// relevant to this node's wallet.
SubscribeTransactions func() (lnwallet.TransactionSubscription, error)
// SubscribeTopology is used to get a subscription for topology changes
// on the network.
SubscribeTopology func() (*graphdb.TopologyClient, error)
}
// Manager is struct that manages an autopilot agent, making it possible to
// enable and disable it at will, and hand it relevant external information.
// It implements the autopilot grpc service, which is used to get data about
// the running autopilot, and gives it relevant information.
type Manager struct {
started sync.Once
stopped sync.Once
cfg *ManagerCfg
// pilot is the current autopilot agent. It will be nil if the agent is
// disabled.
pilot *Agent
quit chan struct{}
wg sync.WaitGroup
sync.Mutex
}
// NewManager creates a new instance of the Manager from the passed config.
func NewManager(cfg *ManagerCfg) (*Manager, error) {
return &Manager{
cfg: cfg,
quit: make(chan struct{}),
}, nil
}
// Start starts the Manager.
func (m *Manager) Start() error {
m.started.Do(func() {})
return nil
}
// Stop stops the Manager. If an autopilot agent is active, it will also be
// stopped.
func (m *Manager) Stop() error {
m.stopped.Do(func() {
if err := m.StopAgent(); err != nil {
log.Errorf("Unable to stop pilot: %v", err)
}
close(m.quit)
m.wg.Wait()
})
return nil
}
// IsActive returns whether the autopilot agent is currently active.
func (m *Manager) IsActive() bool {
m.Lock()
defer m.Unlock()
return m.pilot != nil
}
// StartAgent creates and starts an autopilot agent from the Manager's
// config.
func (m *Manager) StartAgent() error {
m.Lock()
defer m.Unlock()
// Already active.
if m.pilot != nil {
return nil
}
// Next, we'll fetch the current state of open channels from the
// database to use as initial state for the auto-pilot agent.
initialChanState, err := m.cfg.ChannelState()
if err != nil {
return err
}
// Now that we have all the initial dependencies, we can create the
// auto-pilot instance itself.
pilot, err := New(*m.cfg.PilotCfg, initialChanState)
if err != nil {
return err
}
if err := pilot.Start(); err != nil {
return err
}
// Finally, we'll need to subscribe to two things: incoming
// transactions that modify the wallet's balance, and also any graph
// topology updates.
txnSubscription, err := m.cfg.SubscribeTransactions()
if err != nil {
pilot.Stop()
return err
}
graphSubscription, err := m.cfg.SubscribeTopology()
if err != nil {
txnSubscription.Cancel()
pilot.Stop()
return err
}
m.pilot = pilot
// We'll launch a goroutine to provide the agent with notifications
// whenever the balance of the wallet changes.
// TODO(halseth): can lead to panic if in process of shutting down.
m.wg.Add(1)
go func() {
defer txnSubscription.Cancel()
defer m.wg.Done()
for {
select {
case <-txnSubscription.ConfirmedTransactions():
pilot.OnBalanceChange()
// We won't act upon new unconfirmed transaction, as
// we'll only use confirmed outputs when funding.
// However, we will still drain this request in order
// to avoid goroutine leaks, and ensure we promptly
// read from the channel if available.
case <-txnSubscription.UnconfirmedTransactions():
case <-pilot.quit:
return
case <-m.quit:
return
}
}
}()
// We'll also launch a goroutine to provide the agent with
// notifications for when the graph topology controlled by the node
// changes.
m.wg.Add(1)
go func() {
defer graphSubscription.Cancel()
defer m.wg.Done()
for {
select {
case topChange, ok := <-graphSubscription.TopologyChanges:
// If the router is shutting down, then we will
// as well.
if !ok {
return
}
for _, edgeUpdate := range topChange.ChannelEdgeUpdates {
// If this isn't an advertisement by
// the backing lnd node, then we'll
// continue as we only want to add
// channels that we've created
// ourselves.
if !edgeUpdate.AdvertisingNode.IsEqual(m.cfg.Self) {
continue
}
// If this is indeed a channel we
// opened, then we'll convert it to the
// autopilot.Channel format, and notify
// the pilot of the new channel.
cp := edgeUpdate.ChanPoint
edge, err := m.cfg.ChannelInfo(cp)
if err != nil {
log.Errorf("Unable to fetch "+
"channel info for %v: "+
"%v", cp, err)
continue
}
pilot.OnChannelOpen(*edge)
}
// For each closed channel, we'll obtain
// the chanID of the closed channel and send it
// to the pilot.
for _, chanClose := range topChange.ClosedChannels {
chanID := lnwire.NewShortChanIDFromInt(
chanClose.ChanID,
)
pilot.OnChannelClose(chanID)
}
// If new nodes were added to the graph, or
// node information has changed, we'll poke
// autopilot to see if it can make use of them.
if len(topChange.NodeUpdates) > 0 {
pilot.OnNodeUpdates()
}
case <-pilot.quit:
return
case <-m.quit:
return
}
}
}()
log.Debugf("Manager started autopilot agent")
return nil
}
// StopAgent stops any active autopilot agent.
func (m *Manager) StopAgent() error {
m.Lock()
defer m.Unlock()
// Not active, so we can return early.
if m.pilot == nil {
return nil
}
if err := m.pilot.Stop(); err != nil {
return err
}
// Make sure to nil the current agent, indicating it is no longer
// active.
m.pilot = nil
log.Debugf("Manager stopped autopilot agent")
return nil
}
// QueryHeuristics queries the available autopilot heuristics for node scores.
func (m *Manager) QueryHeuristics(nodes []NodeID, localState bool) (
HeuristicScores, error) {
m.Lock()
defer m.Unlock()
n := make(map[NodeID]struct{})
for _, node := range nodes {
n[node] = struct{}{}
}
log.Debugf("Querying heuristics for %d nodes", len(n))
return m.queryHeuristics(n, localState)
}
// HeuristicScores is an alias for a map that maps heuristic names to a map of
// scores for pubkeys.
type HeuristicScores map[string]map[NodeID]float64
// queryHeuristics gets node scores from all available simple heuristics, and
// the agent's current active heuristic.
//
// NOTE: Must be called with the manager's lock.
func (m *Manager) queryHeuristics(nodes map[NodeID]struct{}, localState bool) (
HeuristicScores, error) {
// If we want to take the local state into action when querying the
// heuristics, we fetch it. If not we'll just pass an empty slice to
// the heuristic.
var totalChans []LocalChannel
var err error
if localState {
// Fetch the current set of channels.
totalChans, err = m.cfg.ChannelState()
if err != nil {
return nil, err
}
// If the agent is active, we can merge the channel state with
// the channels pending open.
if m.pilot != nil {
m.pilot.chanStateMtx.Lock()
m.pilot.pendingMtx.Lock()
totalChans = mergeChanState(
m.pilot.pendingOpens, m.pilot.chanState,
)
m.pilot.pendingMtx.Unlock()
m.pilot.chanStateMtx.Unlock()
}
}
// As channel size we'll use the maximum size.
chanSize := m.cfg.PilotCfg.Constraints.MaxChanSize()
// We'll start by getting the scores from each available sub-heuristic,
// in addition the current agent heuristic.
var heuristics []AttachmentHeuristic
heuristics = append(heuristics, availableHeuristics...)
heuristics = append(heuristics, m.cfg.PilotCfg.Heuristic)
report := make(HeuristicScores)
for _, h := range heuristics {
name := h.Name()
// If the agent heuristic is among the simple heuristics it
// might get queried more than once. As an optimization we'll
// just skip it the second time.
if _, ok := report[name]; ok {
continue
}
s, err := h.NodeScores(
m.cfg.PilotCfg.Graph, totalChans, chanSize, nodes,
)
if err != nil {
return nil, fmt.Errorf("unable to get sub score: %w",
err)
}
log.Debugf("Heuristic \"%v\" scored %d nodes", name, len(s))
scores := make(map[NodeID]float64)
for nID, score := range s {
scores[nID] = score.Score
}
report[name] = scores
}
return report, nil
}
// SetNodeScores is used to set the scores of the given heuristic, if it is
// active, and ScoreSettable.
func (m *Manager) SetNodeScores(name string, scores map[NodeID]float64) error {
m.Lock()
defer m.Unlock()
// It must be ScoreSettable to be available for external
// scores.
s, ok := m.cfg.PilotCfg.Heuristic.(ScoreSettable)
if !ok {
return fmt.Errorf("current heuristic doesn't support " +
"external scoring")
}
// Heuristic was found, set its node scores.
applied, err := s.SetNodeScores(name, scores)
if err != nil {
return err
}
if !applied {
return fmt.Errorf("heuristic with name %v not found", name)
}
// If the autopilot agent is active, notify about the updated
// heuristic.
if m.pilot != nil {
m.pilot.OnHeuristicUpdate(m.cfg.PilotCfg.Heuristic)
}
return nil
}
package autopilot
import (
prand "math/rand"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
)
// minMedianChanSizeFraction determines the minimum size a channel must have to
// count positively when calculating the scores using preferential attachment.
// The minimum channel size is calculated as median/minMedianChanSizeFraction,
// where median is the median channel size of the entire graph.
const minMedianChanSizeFraction = 4
// PrefAttachment is an implementation of the AttachmentHeuristic interface
// that implement a non-linear preferential attachment heuristic. This means
// that given a threshold to allocate to automatic channel establishment, the
// heuristic will attempt to favor connecting to nodes which already have a set
// amount of links, selected by sampling from a power law distribution. The
// attachment is non-linear in that it favors nodes with a higher in-degree but
// less so than regular linear preferential attachment. As a result, this
// creates smaller and less clusters than regular linear preferential
// attachment.
//
// TODO(roasbeef): BA, with k=-3
type PrefAttachment struct {
}
// NewPrefAttachment creates a new instance of a PrefAttachment heuristic.
func NewPrefAttachment() *PrefAttachment {
prand.Seed(time.Now().Unix())
return &PrefAttachment{}
}
// A compile time assertion to ensure PrefAttachment meets the
// AttachmentHeuristic interface.
var _ AttachmentHeuristic = (*PrefAttachment)(nil)
// NodeID is a simple type that holds an EC public key serialized in compressed
// format.
type NodeID [33]byte
// NewNodeID creates a new nodeID from a passed public key.
func NewNodeID(pub *btcec.PublicKey) NodeID {
var n NodeID
copy(n[:], pub.SerializeCompressed())
return n
}
// Name returns the name of this heuristic.
//
// NOTE: This is a part of the AttachmentHeuristic interface.
func (p *PrefAttachment) Name() string {
return "preferential"
}
// NodeScores is a method that given the current channel graph and current set
// of local channels, scores the given nodes according to the preference of
// opening a channel of the given size with them. The returned channel
// candidates maps the NodeID to a NodeScore for the node.
//
// The heuristic employed by this method is one that attempts to promote a
// scale-free network globally, via local attachment preferences for new nodes
// joining the network with an amount of available funds to be allocated to
// channels. Specifically, we consider the degree of each node (and the flow
// in/out of the node available via its open channels) and utilize the
// Barabási–Albert model to drive our recommended attachment heuristics. If
// implemented globally for each new participant, this results in a channel
// graph that is scale-free and follows a power law distribution with k=-3.
//
// To avoid assigning a high score to nodes with a large number of small
// channels, we only count channels at least as large as a given fraction of
// the graph's median channel size.
//
// The returned scores will be in the range [0.0, 1.0], where higher scores are
// given to nodes already having high connectivity in the graph.
//
// NOTE: This is a part of the AttachmentHeuristic interface.
func (p *PrefAttachment) NodeScores(g ChannelGraph, chans []LocalChannel,
chanSize btcutil.Amount, nodes map[NodeID]struct{}) (
map[NodeID]*NodeScore, error) {
// We first run though the graph once in order to find the median
// channel size.
var (
allChans []btcutil.Amount
seenChans = make(map[uint64]struct{})
)
if err := g.ForEachNode(func(n Node) error {
err := n.ForEachChannel(func(e ChannelEdge) error {
if _, ok := seenChans[e.ChanID.ToUint64()]; ok {
return nil
}
seenChans[e.ChanID.ToUint64()] = struct{}{}
allChans = append(allChans, e.Capacity)
return nil
})
if err != nil {
return err
}
return nil
}); err != nil {
return nil, err
}
medianChanSize := Median(allChans)
log.Tracef("Found channel median %v for preferential score heuristic",
medianChanSize)
// Count the number of large-ish channels for each particular node in
// the graph.
var maxChans int
nodeChanNum := make(map[NodeID]int)
if err := g.ForEachNode(func(n Node) error {
var nodeChans int
err := n.ForEachChannel(func(e ChannelEdge) error {
// Since connecting to nodes with a lot of small
// channels actually worsens our connectivity in the
// graph (we will potentially waste time trying to use
// these useless channels in path finding), we decrease
// the counter for such channels.
if e.Capacity < medianChanSize/minMedianChanSizeFraction {
nodeChans--
return nil
}
// Larger channels we count.
nodeChans++
return nil
})
if err != nil {
return err
}
// We keep track of the highest-degree node we've seen, as this
// will be given the max score.
if nodeChans > maxChans {
maxChans = nodeChans
}
// If this node is not among our nodes to score, we can return
// early.
nID := NodeID(n.PubKey())
if _, ok := nodes[nID]; !ok {
log.Tracef("Node %x not among nodes to score, "+
"ignoring", nID[:])
return nil
}
// Otherwise we'll record the number of channels.
nodeChanNum[nID] = nodeChans
log.Tracef("Counted %v channels for node %x", nodeChans, nID[:])
return nil
}); err != nil {
return nil, err
}
// If there are no channels in the graph we cannot determine any
// preferences, so we return, indicating all candidates get a score of
// zero.
if maxChans == 0 {
log.Tracef("No channels in the graph")
return nil, nil
}
existingPeers := make(map[NodeID]struct{})
for _, c := range chans {
existingPeers[c.Node] = struct{}{}
}
// For each node in the set of nodes, count their fraction of channels
// in the graph, and use that as the score.
candidates := make(map[NodeID]*NodeScore)
for nID, nodeChans := range nodeChanNum {
// If the node is among or existing channel peers, we don't
// need another channel.
if _, ok := existingPeers[nID]; ok {
log.Tracef("Node %x among existing peers for pref "+
"attach heuristic, giving zero score", nID[:])
continue
}
// If the node had no large channels, we skip it, since it
// would have gotten a zero score anyway.
if nodeChans <= 0 {
log.Tracef("Skipping node %x with channel count %v",
nID[:], nodeChans)
continue
}
// Otherwise we score the node according to its fraction of
// channels in the graph, scaled such that the highest-degree
// node will be given a score of 1.0.
score := float64(nodeChans) / float64(maxChans)
log.Tracef("Giving node %x a pref attach score of %v",
nID[:], score)
candidates[nID] = &NodeScore{
NodeID: nID,
Score: score,
}
}
return candidates, nil
}
package autopilot
// diameterCutoff is used to discard nodes in the diameter calculation.
// It is the multiplier for the eccentricity of the highest-degree node,
// serving as a cutoff to discard all nodes with a smaller hop distance. This
// number should not be set close to 1 and is a tradeoff for computation cost,
// where 0 is maximally costly.
const diameterCutoff = 0.75
// SimpleGraph stores a simplified adj graph of a channel graph to speed
// up graph processing by eliminating all unnecessary hashing and map access.
type SimpleGraph struct {
// Nodes is a map from node index to NodeID.
Nodes []NodeID
// Adj stores nodes and neighbors in an adjacency list.
Adj [][]int
}
// NewSimpleGraph creates a simplified graph from the current channel graph.
// Returns an error if the channel graph iteration fails due to underlying
// failure.
func NewSimpleGraph(g ChannelGraph) (*SimpleGraph, error) {
nodes := make(map[NodeID]int)
adj := make(map[int][]int)
nextIndex := 0
// getNodeIndex returns the integer index of the passed node.
// The returned index is then used to create a simplified adjacency list
// where each node is identified by its index instead of its pubkey, and
// also to create a mapping from node index to node pubkey.
getNodeIndex := func(node Node) int {
key := NodeID(node.PubKey())
nodeIndex, ok := nodes[key]
if !ok {
nodes[key] = nextIndex
nodeIndex = nextIndex
nextIndex++
}
return nodeIndex
}
// Iterate over each node and each channel and update the adj and the node
// index.
err := g.ForEachNode(func(node Node) error {
u := getNodeIndex(node)
return node.ForEachChannel(func(edge ChannelEdge) error {
v := getNodeIndex(edge.Peer)
adj[u] = append(adj[u], v)
return nil
})
})
if err != nil {
return nil, err
}
graph := &SimpleGraph{
Nodes: make([]NodeID, len(nodes)),
Adj: make([][]int, len(nodes)),
}
// Fill the adj and the node index to node pubkey mapping.
for nodeID, nodeIndex := range nodes {
graph.Adj[nodeIndex] = adj[nodeIndex]
graph.Nodes[nodeIndex] = nodeID
}
// We prepare to give some debug output about the size of the graph.
totalChannels := 0
for _, channels := range graph.Adj {
totalChannels += len(channels)
}
// The number of channels is double counted, so divide by two.
log.Debugf("Initialized simple graph with %d nodes and %d "+
"channels", len(graph.Adj), totalChannels/2)
return graph, nil
}
// maxVal is a helper function to get the maximal value of all values of a map.
func maxVal(mapping map[int]uint32) uint32 {
maxValue := uint32(0)
for _, value := range mapping {
maxValue = max(maxValue, value)
}
return maxValue
}
// degree determines the number of edges for a node in the graph.
func (graph *SimpleGraph) degree(node int) int {
return len(graph.Adj[node])
}
// nodeMaxDegree determines the node with the max degree and its degree.
func (graph *SimpleGraph) nodeMaxDegree() (int, int) {
var maxNode, maxDegree int
for node := range graph.Adj {
degree := graph.degree(node)
if degree > maxDegree {
maxNode = node
maxDegree = degree
}
}
return maxNode, maxDegree
}
// shortestPathLengths performs a breadth-first-search from a node to all other
// nodes, returning the lengths of the paths.
func (graph *SimpleGraph) shortestPathLengths(node int) map[int]uint32 {
// level indicates the shell of the search around the root node.
var level uint32
graphOrder := len(graph.Adj)
// nextLevel tracks which nodes should be visited in the next round.
nextLevel := make([]int, 0, graphOrder)
// The root node is put as a starting point for the exploration.
nextLevel = append(nextLevel, node)
// Seen tracks already visited nodes and tracks how far away they are.
seen := make(map[int]uint32, graphOrder)
// Mark the root node as seen.
seen[node] = level
// thisLevel contains the nodes that are explored in the round.
thisLevel := make([]int, 0, graphOrder)
// Abort if we have an empty graph.
if len(graph.Adj) == 0 {
return seen
}
// We discover other nodes in a ring-like structure as long as we don't
// have more nodes to explore.
for len(nextLevel) > 0 {
level++
// We swap the queues for efficient memory management.
thisLevel, nextLevel = nextLevel, thisLevel
// Visit all neighboring nodes of the level and mark them as
// seen if they were not discovered before.
for _, thisNode := range thisLevel {
for _, neighbor := range graph.Adj[thisNode] {
_, ok := seen[neighbor]
if !ok {
nextLevel = append(nextLevel, neighbor)
seen[neighbor] = level
}
// If we have seen all nodes, we return early.
if len(seen) == graphOrder {
return seen
}
}
}
// Empty the queue to be used in the next level.
thisLevel = thisLevel[:0:cap(thisLevel)]
}
return seen
}
// nodeEccentricity calculates the eccentricity (longest shortest path to all
// other nodes) of a node.
func (graph *SimpleGraph) nodeEccentricity(node int) uint32 {
pathLengths := graph.shortestPathLengths(node)
return maxVal(pathLengths)
}
// nodeEccentricities calculates the eccentricities for the given nodes.
func (graph *SimpleGraph) nodeEccentricities(nodes []int) map[int]uint32 {
eccentricities := make(map[int]uint32, len(graph.Adj))
for _, node := range nodes {
eccentricities[node] = graph.nodeEccentricity(node)
}
return eccentricities
}
// Diameter returns the maximal eccentricity (longest shortest path
// between any node pair) in the graph.
//
// Note: This method is exact but expensive, use DiameterRadialCutoff instead.
func (graph *SimpleGraph) Diameter() uint32 {
nodes := make([]int, len(graph.Adj))
for a := range nodes {
nodes[a] = a
}
eccentricities := graph.nodeEccentricities(nodes)
return maxVal(eccentricities)
}
// DiameterRadialCutoff is a method to efficiently evaluate the diameter of a
// graph. The highest-degree node is usually central in the graph. We can
// determine its eccentricity (shortest-longest path length to any other node)
// and use it as an approximation for the radius of the network. We then
// use this radius to compute a cutoff. All the nodes within a distance of the
// cutoff are discarded, as they represent the inside of the graph. We then
// loop over all outer nodes and determine their eccentricities, from which we
// get the diameter.
func (graph *SimpleGraph) DiameterRadialCutoff() uint32 {
// Determine the reference node as the node with the highest degree.
nodeMaxDegree, _ := graph.nodeMaxDegree()
distances := graph.shortestPathLengths(nodeMaxDegree)
eccentricityMaxDegreeNode := maxVal(distances)
// We use the eccentricity to define a cutoff for the interior of the
// graph from the reference node.
cutoff := uint32(float32(eccentricityMaxDegreeNode) * diameterCutoff)
log.Debugf("Cutoff radius is %d hops (max-degree node's "+
"eccentricity is %d)", cutoff, eccentricityMaxDegreeNode)
// Remove the nodes that are close to the reference node.
var nodes []int
for node, distance := range distances {
if distance > cutoff {
nodes = append(nodes, node)
}
}
log.Debugf("Evaluated nodes: %d, discarded nodes %d",
len(nodes), len(graph.Adj)-len(nodes))
// Compute the diameter of the remaining nodes.
eccentricities := graph.nodeEccentricities(nodes)
return maxVal(eccentricities)
}
package autopilot
import (
"runtime"
"github.com/btcsuite/btcd/btcutil"
)
// TopCentrality is a simple greedy technique to create connections to nodes
// with the top betweenness centrality value. This algorithm is usually
// referred to as TopK in the literature. The idea is that by opening channels
// to nodes with top betweenness centrality we also increase our own betweenness
// centrality (given we already have at least one channel, or create at least
// two new channels).
// A different and much better approach is instead of selecting nodes with top
// centrality value, we extend the graph in a loop by inserting a new non
// existing edge and recalculate the betweenness centrality of each node. This
// technique is usually referred to as "greedy" algorithm and gives better
// results than TopK but is considerably slower too.
type TopCentrality struct {
centralityMetric *BetweennessCentrality
}
// A compile time assertion to ensure TopCentrality meets the
// AttachmentHeuristic interface.
var _ AttachmentHeuristic = (*TopCentrality)(nil)
// NewTopCentrality constructs and returns a new TopCentrality heuristic.
func NewTopCentrality() *TopCentrality {
metric, err := NewBetweennessCentralityMetric(
runtime.NumCPU(),
)
if err != nil {
panic(err)
}
return &TopCentrality{
centralityMetric: metric,
}
}
// Name returns the name of the heuristic.
func (g *TopCentrality) Name() string {
return "top_centrality"
}
// NodeScores will return a [0,1] normalized map of scores for the given nodes
// except for the ones we already have channels with. The scores will simply
// be the betweenness centrality values of the nodes.
// As our current implementation of betweenness centrality is non-incremental,
// NodeScores will recalculate the centrality values on every call, which is
// slow for large graphs.
func (g *TopCentrality) NodeScores(graph ChannelGraph, chans []LocalChannel,
chanSize btcutil.Amount, nodes map[NodeID]struct{}) (
map[NodeID]*NodeScore, error) {
// Calculate betweenness centrality for the whole graph.
if err := g.centralityMetric.Refresh(graph); err != nil {
return nil, err
}
normalize := true
centrality := g.centralityMetric.GetMetric(normalize)
// Create a map of the existing peers for faster filtering.
existingPeers := make(map[NodeID]struct{})
for _, c := range chans {
existingPeers[c.Node] = struct{}{}
}
result := make(map[NodeID]*NodeScore, len(nodes))
for nodeID := range nodes {
// Skip nodes we already have channel with.
if _, ok := existingPeers[nodeID]; ok {
continue
}
// Skip passed nodes not in the graph. This could happen if
// the graph changed before computing the centrality values as
// the nodes we iterate are prefiltered by the autopilot agent.
score, ok := centrality[nodeID]
if !ok {
continue
}
result[nodeID] = &NodeScore{
NodeID: nodeID,
Score: score,
}
}
return result, nil
}
package batch
import (
"errors"
"sync"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/sqldb"
)
// errSolo is a sentinel error indicating that the requester should re-run the
// operation in isolation.
var errSolo = errors.New(
"batch function returned an error and should be re-run solo",
)
type request struct {
*Request
errChan chan error
}
type batch struct {
db kvdb.Backend
start sync.Once
reqs []*request
clear func(b *batch)
locker sync.Locker
}
// trigger is the entry point for the batch and ensures that run is started at
// most once.
func (b *batch) trigger() {
b.start.Do(b.run)
}
// run executes the current batch of requests. If any individual requests fail
// alongside others they will be retried by the caller.
func (b *batch) run() {
// Clear the batch from its scheduler, ensuring that no new requests are
// added to this batch.
b.clear(b)
// If a cache lock was provided, hold it until the this method returns.
// This is critical for ensuring external consistency of the operation,
// so that caches don't get out of sync with the on disk state.
if b.locker != nil {
b.locker.Lock()
defer b.locker.Unlock()
}
// Apply the batch until a subset succeeds or all of them fail. Requests
// that fail will be retried individually.
for len(b.reqs) > 0 {
var failIdx = -1
err := kvdb.Update(b.db, func(tx kvdb.RwTx) error {
for i, req := range b.reqs {
err := req.Update(tx)
if err != nil {
// If we get a serialization error, we
// want the underlying SQL retry
// mechanism to retry the entire batch.
// Otherwise, we can succeed in an
// sqldb retry and still re-execute the
// failing request individually.
dbErr := sqldb.MapSQLError(err)
if !sqldb.IsSerializationError(dbErr) {
failIdx = i
return err
}
return dbErr
}
}
return nil
}, func() {
for _, req := range b.reqs {
if req.Reset != nil {
req.Reset()
}
}
})
// If a request's Update failed, extract it and re-run the
// batch. The removed request will be retried individually by
// the caller.
if failIdx >= 0 {
req := b.reqs[failIdx]
// It's safe to shorten b.reqs here because the
// scheduler's batch no longer points to us.
b.reqs[failIdx] = b.reqs[len(b.reqs)-1]
b.reqs = b.reqs[:len(b.reqs)-1]
// Tell the submitter re-run it solo, continue with the
// rest of the batch.
req.errChan <- errSolo
continue
}
// None of the remaining requests failed, process the errors
// using each request's OnCommit closure and return the error
// to the requester. If no OnCommit closure is provided, simply
// return the error directly.
for _, req := range b.reqs {
if req.OnCommit != nil {
req.errChan <- req.OnCommit(err)
} else {
req.errChan <- err
}
}
return
}
}
package batch
import "github.com/lightningnetwork/lnd/kvdb"
// Request defines an operation that can be batched into a single bbolt
// transaction.
type Request struct {
// Reset is called before each invocation of Update and is used to clear
// any possible modifications to local state as a result of previous
// calls to Update that were not committed due to a concurrent batch
// failure.
//
// NOTE: This field is optional.
Reset func()
// Update is applied alongside other operations in the batch.
//
// NOTE: This method MUST NOT acquire any mutexes.
Update func(tx kvdb.RwTx) error
// OnCommit is called if the batch or a subset of the batch including
// this request all succeeded without failure. The passed error should
// contain the result of the transaction commit, as that can still fail
// even if none of the closures returned an error.
//
// NOTE: This field is optional.
OnCommit func(commitErr error) error
// lazy should be true if we don't have to immediately execute this
// request when it comes in. This means that it can be scheduled later,
// allowing larger batches.
lazy bool
}
// SchedulerOption is a type that can be used to supply options to a scheduled
// request.
type SchedulerOption func(r *Request)
// LazyAdd will make the request be executed lazily, added to the next batch to
// reduce db contention.
func LazyAdd() SchedulerOption {
return func(r *Request) {
r.lazy = true
}
}
// Scheduler abstracts a generic batching engine that accumulates an incoming
// set of Requests, executes them, and returns the error from the operation.
type Scheduler interface {
// Execute schedules a Request for execution with the next available
// batch. This method blocks until the underlying closure has been
// run against the database. The resulting error is returned to the
// caller.
Execute(req *Request) error
}
package batch
import (
"sync"
"time"
"github.com/lightningnetwork/lnd/kvdb"
)
// TimeScheduler is a batching engine that executes requests within a fixed
// horizon. When the first request is received, a TimeScheduler waits a
// configurable duration for other concurrent requests to join the batch. Once
// this time has elapsed, the batch is closed and executed. Subsequent requests
// are then added to a new batch which undergoes the same process.
type TimeScheduler struct {
db kvdb.Backend
locker sync.Locker
duration time.Duration
mu sync.Mutex
b *batch
}
// NewTimeScheduler initializes a new TimeScheduler with a fixed duration at
// which to schedule batches. If the operation needs to modify a higher-level
// cache, the cache's lock should be provided to so that external consistency
// can be maintained, as successful db operations will cause a request's
// OnCommit method to be executed while holding this lock.
func NewTimeScheduler(db kvdb.Backend, locker sync.Locker,
duration time.Duration) *TimeScheduler {
return &TimeScheduler{
db: db,
locker: locker,
duration: duration,
}
}
// Execute schedules the provided request for batch execution along with other
// concurrent requests. The request will be executed within a fixed horizon,
// parameterizeed by the duration of the scheduler. The error from the
// underlying operation is returned to the caller.
//
// NOTE: Part of the Scheduler interface.
func (s *TimeScheduler) Execute(r *Request) error {
req := request{
Request: r,
errChan: make(chan error, 1),
}
// Add the request to the current batch. If the batch has been cleared
// or no batch exists, create a new one.
s.mu.Lock()
if s.b == nil {
s.b = &batch{
db: s.db,
clear: s.clear,
locker: s.locker,
}
time.AfterFunc(s.duration, s.b.trigger)
}
s.b.reqs = append(s.b.reqs, &req)
// If this is a non-lazy request, we'll execute the batch immediately.
if !r.lazy {
go s.b.trigger()
}
s.mu.Unlock()
// Wait for the batch to process the request. If the batch didn't
// ask us to execute the request individually, simply return the error.
err := <-req.errChan
if err != errSolo {
return err
}
// Obtain exclusive access to the cache if this scheduler needs to
// modify the cache in OnCommit.
if s.locker != nil {
s.locker.Lock()
defer s.locker.Unlock()
}
// Otherwise, run the request on its own.
commitErr := kvdb.Update(s.db, req.Update, func() {
if req.Reset != nil {
req.Reset()
}
})
// Finally, return the commit error directly or execute the OnCommit
// closure with the commit error if present.
if req.OnCommit != nil {
return req.OnCommit(commitErr)
}
return commitErr
}
// clear resets the scheduler's batch to nil so that no more requests can be
// added.
func (s *TimeScheduler) clear(b *batch) {
s.mu.Lock()
if s.b == b {
s.b = nil
}
s.mu.Unlock()
}
package blockcache
import (
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/neutrino"
"github.com/lightninglabs/neutrino/cache"
"github.com/lightninglabs/neutrino/cache/lru"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/multimutex"
)
// BlockCache is an lru cache for blocks.
type BlockCache struct {
Cache *lru.Cache[wire.InvVect, *neutrino.CacheableBlock]
HashMutex *multimutex.Mutex[lntypes.Hash]
}
// NewBlockCache creates a new BlockCache with the given maximum capacity.
func NewBlockCache(capacity uint64) *BlockCache {
return &BlockCache{
Cache: lru.NewCache[wire.InvVect, *neutrino.CacheableBlock](
capacity,
),
HashMutex: multimutex.NewMutex[lntypes.Hash](),
}
}
// GetBlock first checks to see if the BlockCache already contains the block
// with the given hash. If it does then the block is fetched from the cache and
// returned. Otherwise the getBlockImpl function is used in order to fetch the
// new block and then it is stored in the block cache and returned.
func (bc *BlockCache) GetBlock(hash *chainhash.Hash,
getBlockImpl func(hash *chainhash.Hash) (*wire.MsgBlock,
error)) (*wire.MsgBlock, error) {
bc.HashMutex.Lock(lntypes.Hash(*hash))
defer bc.HashMutex.Unlock(lntypes.Hash(*hash))
// Create an inv vector for getting the block.
inv := wire.NewInvVect(wire.InvTypeWitnessBlock, hash)
// Check if the block corresponding to the given hash is already
// stored in the blockCache and return it if it is.
cacheBlock, err := bc.Cache.Get(*inv)
if err != nil && err != cache.ErrElementNotFound {
return nil, err
}
if cacheBlock != nil {
return cacheBlock.MsgBlock(), nil
}
// Fetch the block from the chain backends.
msgBlock, err := getBlockImpl(hash)
if err != nil {
return nil, err
}
// Make a copy of the block so it won't escape to the heap.
msgBlockCopy := msgBlock.Copy()
block := btcutil.NewBlock(msgBlockCopy)
// Add the new block to blockCache. If the Cache is at its maximum
// capacity then the LFU item will be evicted in favour of this new
// block.
_, err = bc.Cache.Put(
*inv, &neutrino.CacheableBlock{
Block: block,
},
)
if err != nil {
return nil, err
}
return msgBlockCopy, nil
}
package brontide
import (
"bytes"
"io"
"math"
"net"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tor"
)
// Conn is an implementation of net.Conn which enforces an authenticated key
// exchange and message encryption protocol dubbed "Brontide" after initial TCP
// connection establishment. In the case of a successful handshake, all
// messages sent via the .Write() method are encrypted with an AEAD cipher
// along with an encrypted length-prefix. See the Machine struct for
// additional details w.r.t to the handshake and encryption scheme.
type Conn struct {
conn net.Conn
noise *Machine
readBuf bytes.Buffer
}
// A compile-time assertion to ensure that Conn meets the net.Conn interface.
var _ net.Conn = (*Conn)(nil)
// Dial attempts to establish an encrypted+authenticated connection with the
// remote peer located at address which has remotePub as its long-term static
// public key. In the case of a handshake failure, the connection is closed and
// a non-nil error is returned.
func Dial(local keychain.SingleKeyECDH, netAddr *lnwire.NetAddress,
timeout time.Duration, dialer tor.DialFunc) (*Conn, error) {
ipAddr := netAddr.Address.String()
var conn net.Conn
var err error
conn, err = dialer("tcp", ipAddr, timeout)
if err != nil {
return nil, err
}
b := &Conn{
conn: conn,
noise: NewBrontideMachine(true, local, netAddr.IdentityKey),
}
// Initiate the handshake by sending the first act to the receiver.
actOne, err := b.noise.GenActOne()
if err != nil {
b.conn.Close()
return nil, err
}
if _, err := conn.Write(actOne[:]); err != nil {
b.conn.Close()
return nil, err
}
// We'll ensure that we get ActTwo from the remote peer in a timely
// manner. If they don't respond within handshakeReadTimeout, then
// we'll kill the connection.
err = conn.SetReadDeadline(time.Now().Add(handshakeReadTimeout))
if err != nil {
b.conn.Close()
return nil, err
}
// If the first act was successful (we know that address is actually
// remotePub), then read the second act after which we'll be able to
// send our static public key to the remote peer with strong forward
// secrecy.
var actTwo [ActTwoSize]byte
if _, err := io.ReadFull(conn, actTwo[:]); err != nil {
b.conn.Close()
return nil, err
}
if err := b.noise.RecvActTwo(actTwo); err != nil {
b.conn.Close()
return nil, err
}
// Finally, complete the handshake by sending over our encrypted static
// key and execute the final ECDH operation.
actThree, err := b.noise.GenActThree()
if err != nil {
b.conn.Close()
return nil, err
}
if _, err := conn.Write(actThree[:]); err != nil {
b.conn.Close()
return nil, err
}
// We'll reset the deadline as it's no longer critical beyond the
// initial handshake.
err = conn.SetReadDeadline(time.Time{})
if err != nil {
b.conn.Close()
return nil, err
}
return b, nil
}
// ReadNextMessage uses the connection in a message-oriented manner, instructing
// it to read the next _full_ message with the brontide stream. This function
// will block until the read of the header and body succeeds.
//
// NOTE: This method SHOULD NOT be used in the case that the connection may be
// adversarial and induce long delays. If the caller needs to set read deadlines
// appropriately, it is preferred that they use the split ReadNextHeader and
// ReadNextBody methods so that the deadlines can be set appropriately on each.
func (c *Conn) ReadNextMessage() ([]byte, error) {
return c.noise.ReadMessage(c.conn)
}
// ReadNextHeader uses the connection to read the next header from the brontide
// stream. This function will block until the read of the header succeeds and
// return the packet length (including MAC overhead) that is expected from the
// subsequent call to ReadNextBody.
func (c *Conn) ReadNextHeader() (uint32, error) {
return c.noise.ReadHeader(c.conn)
}
// ReadNextBody uses the connection to read the next message body from the
// brontide stream. This function will block until the read of the body succeeds
// and return the decrypted payload. The provided buffer MUST be the packet
// length returned by the preceding call to ReadNextHeader.
func (c *Conn) ReadNextBody(buf []byte) ([]byte, error) {
return c.noise.ReadBody(c.conn, buf)
}
// Read reads data from the connection. Read can be made to time out and
// return an Error with Timeout() == true after a fixed time limit; see
// SetDeadline and SetReadDeadline.
//
// Part of the net.Conn interface.
func (c *Conn) Read(b []byte) (n int, err error) {
// In order to reconcile the differences between the record abstraction
// of our AEAD connection, and the stream abstraction of TCP, we
// maintain an intermediate read buffer. If this buffer becomes
// depleted, then we read the next record, and feed it into the
// buffer. Otherwise, we read directly from the buffer.
if c.readBuf.Len() == 0 {
plaintext, err := c.noise.ReadMessage(c.conn)
if err != nil {
return 0, err
}
if _, err := c.readBuf.Write(plaintext); err != nil {
return 0, err
}
}
return c.readBuf.Read(b)
}
// Write writes data to the connection. Write can be made to time out and
// return an Error with Timeout() == true after a fixed time limit; see
// SetDeadline and SetWriteDeadline.
//
// Part of the net.Conn interface.
func (c *Conn) Write(b []byte) (n int, err error) {
// If the message doesn't require any chunking, then we can go ahead
// with a single write.
if len(b) <= math.MaxUint16 {
err = c.noise.WriteMessage(b)
if err != nil {
return 0, err
}
return c.noise.Flush(c.conn)
}
// If we need to split the message into fragments, then we'll write
// chunks which maximize usage of the available payload.
chunkSize := math.MaxUint16
bytesToWrite := len(b)
bytesWritten := 0
for bytesWritten < bytesToWrite {
// If we're on the last chunk, then truncate the chunk size as
// necessary to avoid an out-of-bounds array memory access.
if bytesWritten+chunkSize > len(b) {
chunkSize = len(b) - bytesWritten
}
// Slice off the next chunk to be written based on our running
// counter and next chunk size.
chunk := b[bytesWritten : bytesWritten+chunkSize]
if err := c.noise.WriteMessage(chunk); err != nil {
return bytesWritten, err
}
n, err := c.noise.Flush(c.conn)
bytesWritten += n
if err != nil {
return bytesWritten, err
}
}
return bytesWritten, nil
}
// WriteMessage encrypts and buffers the next message p for the connection. The
// ciphertext of the message is prepended with an encrypt+auth'd length which
// must be used as the AD to the AEAD construction when being decrypted by the
// other side.
//
// NOTE: This DOES NOT write the message to the wire, it should be followed by a
// call to Flush to ensure the message is written.
func (c *Conn) WriteMessage(b []byte) error {
return c.noise.WriteMessage(b)
}
// Flush attempts to write a message buffered using WriteMessage to the
// underlying connection. If no buffered message exists, this will result in a
// NOP. Otherwise, it will continue to write the remaining bytes, picking up
// where the byte stream left off in the event of a partial write. The number of
// bytes returned reflects the number of plaintext bytes in the payload, and
// does not account for the overhead of the header or MACs.
//
// NOTE: It is safe to call this method again iff a timeout error is returned.
func (c *Conn) Flush() (int, error) {
return c.noise.Flush(c.conn)
}
// Close closes the connection. Any blocked Read or Write operations will be
// unblocked and return errors.
//
// Part of the net.Conn interface.
func (c *Conn) Close() error {
// TODO(roasbeef): reset brontide state?
return c.conn.Close()
}
// LocalAddr returns the local network address.
//
// Part of the net.Conn interface.
func (c *Conn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
// RemoteAddr returns the remote network address.
//
// Part of the net.Conn interface.
func (c *Conn) RemoteAddr() net.Addr {
return c.conn.RemoteAddr()
}
// SetDeadline sets the read and write deadlines associated with the
// connection. It is equivalent to calling both SetReadDeadline and
// SetWriteDeadline.
//
// Part of the net.Conn interface.
func (c *Conn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
// SetReadDeadline sets the deadline for future Read calls. A zero value for t
// means Read will not time out.
//
// Part of the net.Conn interface.
func (c *Conn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
// SetWriteDeadline sets the deadline for future Write calls. Even if write
// times out, it may return n > 0, indicating that some of the data was
// successfully written. A zero value for t means Write will not time out.
//
// Part of the net.Conn interface.
func (c *Conn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}
// RemotePub returns the remote peer's static public key.
func (c *Conn) RemotePub() *btcec.PublicKey {
return c.noise.remoteStatic
}
// LocalPub returns the local peer's static public key.
func (c *Conn) LocalPub() *btcec.PublicKey {
return c.noise.localStatic.PubKey()
}
package brontide
import (
"errors"
"fmt"
"io"
"net"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/keychain"
)
// defaultHandshakes is the maximum number of handshakes that can be done in
// parallel.
const defaultHandshakes = 50
// Listener is an implementation of a net.Conn which executes an authenticated
// key exchange and message encryption protocol dubbed "Machine" after
// initial connection acceptance. See the Machine struct for additional
// details w.r.t the handshake and encryption scheme used within the
// connection.
type Listener struct {
localStatic keychain.SingleKeyECDH
tcp *net.TCPListener
// shouldAccept is a closure that determines if we should accept the
// incoming connection or not based on its public key.
shouldAccept func(*btcec.PublicKey) (bool, error)
handshakeSema chan struct{}
conns chan maybeConn
quit chan struct{}
}
// A compile-time assertion to ensure that Conn meets the net.Listener interface.
var _ net.Listener = (*Listener)(nil)
// NewListener returns a new net.Listener which enforces the Brontide scheme
// during both initial connection establishment and data transfer.
func NewListener(localStatic keychain.SingleKeyECDH, listenAddr string,
shouldAccept func(*btcec.PublicKey) (bool, error)) (*Listener, error) {
addr, err := net.ResolveTCPAddr("tcp", listenAddr)
if err != nil {
return nil, err
}
l, err := net.ListenTCP("tcp", addr)
if err != nil {
return nil, err
}
brontideListener := &Listener{
localStatic: localStatic,
tcp: l,
shouldAccept: shouldAccept,
handshakeSema: make(chan struct{}, defaultHandshakes),
conns: make(chan maybeConn),
quit: make(chan struct{}),
}
for i := 0; i < defaultHandshakes; i++ {
brontideListener.handshakeSema <- struct{}{}
}
go brontideListener.listen()
return brontideListener, nil
}
// listen accepts connection from the underlying tcp conn, then performs
// the brontinde handshake procedure asynchronously. A maximum of
// defaultHandshakes will be active at any given time.
//
// NOTE: This method must be run as a goroutine.
func (l *Listener) listen() {
for {
select {
case <-l.handshakeSema:
case <-l.quit:
return
}
conn, err := l.tcp.Accept()
if err != nil {
l.rejectConn(err)
l.handshakeSema <- struct{}{}
continue
}
go l.doHandshake(conn)
}
}
// rejectedConnErr is a helper function that prepends the remote address of the
// failed connection attempt to the original error message.
func rejectedConnErr(err error, remoteAddr string) error {
return fmt.Errorf("unable to accept connection from %v: %w", remoteAddr,
err)
}
// doHandshake asynchronously performs the brontide handshake, so that it does
// not block the main accept loop. This prevents peers that delay writing to the
// connection from block other connection attempts.
func (l *Listener) doHandshake(conn net.Conn) {
defer func() { l.handshakeSema <- struct{}{} }()
select {
case <-l.quit:
return
default:
}
remoteAddr := conn.RemoteAddr().String()
brontideConn := &Conn{
conn: conn,
noise: NewBrontideMachine(false, l.localStatic, nil),
}
// We'll ensure that we get ActOne from the remote peer in a timely
// manner. If they don't respond within handshakeReadTimeout, then
// we'll kill the connection.
err := conn.SetReadDeadline(time.Now().Add(handshakeReadTimeout))
if err != nil {
brontideConn.conn.Close()
l.rejectConn(rejectedConnErr(err, remoteAddr))
return
}
// Attempt to carry out the first act of the handshake protocol. If the
// connecting node doesn't know our long-term static public key, then
// this portion will fail with a non-nil error.
var actOne [ActOneSize]byte
if _, err := io.ReadFull(conn, actOne[:]); err != nil {
brontideConn.conn.Close()
l.rejectConn(rejectedConnErr(err, remoteAddr))
return
}
if err := brontideConn.noise.RecvActOne(actOne); err != nil {
brontideConn.conn.Close()
l.rejectConn(rejectedConnErr(err, remoteAddr))
return
}
// Next, progress the handshake processes by sending over our ephemeral
// key for the session along with an authenticating tag.
actTwo, err := brontideConn.noise.GenActTwo()
if err != nil {
brontideConn.conn.Close()
l.rejectConn(rejectedConnErr(err, remoteAddr))
return
}
if _, err := conn.Write(actTwo[:]); err != nil {
brontideConn.conn.Close()
l.rejectConn(rejectedConnErr(err, remoteAddr))
return
}
select {
case <-l.quit:
return
default:
}
// We'll ensure that we get ActTwo from the remote peer in a timely
// manner. If they don't respond within handshakeReadTimeout, then
// we'll kill the connection.
err = conn.SetReadDeadline(time.Now().Add(handshakeReadTimeout))
if err != nil {
brontideConn.conn.Close()
l.rejectConn(rejectedConnErr(err, remoteAddr))
return
}
// Finally, finish the handshake processes by reading and decrypting
// the connection peer's static public key. If this succeeds then both
// sides have mutually authenticated each other.
var actThree [ActThreeSize]byte
if _, err := io.ReadFull(conn, actThree[:]); err != nil {
brontideConn.conn.Close()
l.rejectConn(rejectedConnErr(err, remoteAddr))
return
}
if err := brontideConn.noise.RecvActThree(actThree); err != nil {
brontideConn.conn.Close()
l.rejectConn(rejectedConnErr(err, remoteAddr))
return
}
// We'll reset the deadline as it's no longer critical beyond the
// initial handshake.
err = conn.SetReadDeadline(time.Time{})
if err != nil {
brontideConn.conn.Close()
l.rejectConn(rejectedConnErr(err, remoteAddr))
return
}
// Call the shouldAccept closure to see if the remote node's public key
// is allowed according to our banning heuristic. This is here because
// we do not learn the remote node's public static key until we've
// received and validated Act 3.
remoteKey := brontideConn.RemotePub()
if remoteKey == nil {
connErr := fmt.Errorf("no remote pubkey")
brontideConn.conn.Close()
l.rejectConn(rejectedConnErr(connErr, remoteAddr))
return
}
accepted, acceptErr := l.shouldAccept(remoteKey)
if !accepted {
// Reject the connection.
brontideConn.conn.Close()
l.rejectConn(rejectedConnErr(acceptErr, remoteAddr))
return
}
l.acceptConn(brontideConn)
}
// maybeConn holds either a brontide connection or an error returned from the
// handshake.
type maybeConn struct {
conn *Conn
err error
}
// acceptConn returns a connection that successfully performed a handshake.
func (l *Listener) acceptConn(conn *Conn) {
select {
case l.conns <- maybeConn{conn: conn}:
case <-l.quit:
}
}
// rejectConn returns any errors encountered during connection or handshake.
func (l *Listener) rejectConn(err error) {
select {
case l.conns <- maybeConn{err: err}:
case <-l.quit:
}
}
// Accept waits for and returns the next connection to the listener. All
// incoming connections are authenticated via the three act Brontide
// key-exchange scheme. This function will fail with a non-nil error in the
// case that either the handshake breaks down, or the remote peer doesn't know
// our static public key.
//
// Part of the net.Listener interface.
func (l *Listener) Accept() (net.Conn, error) {
select {
case result := <-l.conns:
return result.conn, result.err
case <-l.quit:
return nil, errors.New("brontide connection closed")
}
}
// Close closes the listener. Any blocked Accept operations will be unblocked
// and return errors.
//
// Part of the net.Listener interface.
func (l *Listener) Close() error {
select {
case <-l.quit:
default:
close(l.quit)
}
return l.tcp.Close()
}
// Addr returns the listener's network address.
//
// Part of the net.Listener interface.
func (l *Listener) Addr() net.Addr {
return l.tcp.Addr()
}
// DisabledBanClosure is used in places that NewListener is invoked to bypass
// the ban-scoring.
func DisabledBanClosure(p *btcec.PublicKey) (bool, error) {
return true, nil
}
package brontide
import (
"crypto/cipher"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/keychain"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/hkdf"
)
const (
// protocolName is the precise instantiation of the Noise protocol
// handshake at the center of Brontide. This value will be used as part
// of the prologue. If the initiator and responder aren't using the
// exact same string for this value, along with prologue of the Bitcoin
// network, then the initial handshake will fail.
protocolName = "Noise_XK_secp256k1_ChaChaPoly_SHA256"
// macSize is the length in bytes of the tags generated by poly1305.
macSize = 16
// lengthHeaderSize is the number of bytes used to prefix encode the
// length of a message payload.
lengthHeaderSize = 2
// encHeaderSize is the number of bytes required to hold an encrypted
// header and it's MAC.
encHeaderSize = lengthHeaderSize + macSize
// keyRotationInterval is the number of messages sent on a single
// cipher stream before the keys are rotated forwards.
keyRotationInterval = 1000
// handshakeReadTimeout is a read timeout that will be enforced when
// waiting for data payloads during the various acts of Brontide. If
// the remote party fails to deliver the proper payload within this
// time frame, then we'll fail the connection.
handshakeReadTimeout = time.Second * 5
)
var (
// ErrMaxMessageLengthExceeded is returned when a message to be written to
// the cipher session exceeds the maximum allowed message payload.
ErrMaxMessageLengthExceeded = errors.New("the generated payload exceeds " +
"the max allowed message length of (2^16)-1")
// ErrMessageNotFlushed signals that the connection cannot accept a new
// message because the prior message has not been fully flushed.
ErrMessageNotFlushed = errors.New("prior message not flushed")
// lightningPrologue is the noise prologue that is used to initialize
// the brontide noise handshake.
lightningPrologue = []byte("lightning")
// ephemeralGen is the default ephemeral key generator, used to derive a
// unique ephemeral key for each brontide handshake.
ephemeralGen = func() (*btcec.PrivateKey, error) {
return btcec.NewPrivateKey()
}
)
// TODO(roasbeef): free buffer pool?
// ecdh performs an ECDH operation between pub and priv. The returned value is
// the sha256 of the compressed shared point.
func ecdh(pub *btcec.PublicKey, priv keychain.SingleKeyECDH) ([]byte, error) {
hash, err := priv.ECDH(pub)
return hash[:], err
}
// cipherState encapsulates the state for the AEAD which will be used to
// encrypt+authenticate any payloads sent during the handshake, and messages
// sent once the handshake has completed.
type cipherState struct {
// nonce is the nonce passed into the chacha20-poly1305 instance for
// encryption+decryption. The nonce is incremented after each successful
// encryption/decryption.
//
// TODO(roasbeef): this should actually be 96 bit
nonce uint64
// secretKey is the shared symmetric key which will be used to
// instantiate the cipher.
//
// TODO(roasbeef): m-lock??
secretKey [32]byte
// salt is an additional secret which is used during key rotation to
// generate new keys.
salt [32]byte
// cipher is an instance of the ChaCha20-Poly1305 AEAD construction
// created using the secretKey above.
cipher cipher.AEAD
}
// Encrypt returns a ciphertext which is the encryption of the plainText
// observing the passed associatedData within the AEAD construction.
func (c *cipherState) Encrypt(associatedData, cipherText, plainText []byte) []byte {
defer func() {
c.nonce++
if c.nonce == keyRotationInterval {
c.rotateKey()
}
}()
var nonce [12]byte
binary.LittleEndian.PutUint64(nonce[4:], c.nonce)
return c.cipher.Seal(cipherText, nonce[:], plainText, associatedData)
}
// Decrypt attempts to decrypt the passed ciphertext observing the specified
// associatedData within the AEAD construction. In the case that the final MAC
// check fails, then a non-nil error will be returned.
func (c *cipherState) Decrypt(associatedData, plainText, cipherText []byte) ([]byte, error) {
defer func() {
c.nonce++
if c.nonce == keyRotationInterval {
c.rotateKey()
}
}()
var nonce [12]byte
binary.LittleEndian.PutUint64(nonce[4:], c.nonce)
return c.cipher.Open(plainText, nonce[:], cipherText, associatedData)
}
// InitializeKey initializes the secret key and AEAD cipher scheme based off of
// the passed key.
func (c *cipherState) InitializeKey(key [32]byte) {
c.secretKey = key
c.nonce = 0
// Safe to ignore the error here as our key is properly sized
// (32-bytes).
c.cipher, _ = chacha20poly1305.New(c.secretKey[:])
}
// InitializeKeyWithSalt is identical to InitializeKey however it also sets the
// cipherState's salt field which is used for key rotation.
func (c *cipherState) InitializeKeyWithSalt(salt, key [32]byte) {
c.salt = salt
c.InitializeKey(key)
}
// rotateKey rotates the current encryption/decryption key for this cipherState
// instance. Key rotation is performed by ratcheting the current key forward
// using an HKDF invocation with the cipherState's salt as the salt, and the
// current key as the input.
func (c *cipherState) rotateKey() {
var (
info []byte
nextKey [32]byte
)
oldKey := c.secretKey
h := hkdf.New(sha256.New, oldKey[:], c.salt[:], info)
// hkdf(ck, k, zero)
// |
// | \
// | \
// ck k'
h.Read(c.salt[:])
h.Read(nextKey[:])
c.InitializeKey(nextKey)
}
// symmetricState encapsulates a cipherState object and houses the ephemeral
// handshake digest state. This struct is used during the handshake to derive
// new shared secrets based off of the result of ECDH operations. Ultimately,
// the final key yielded by this struct is the result of an incremental
// Triple-DH operation.
type symmetricState struct {
cipherState
// chainingKey is used as the salt to the HKDF function to derive a new
// chaining key as well as a new tempKey which is used for
// encryption/decryption.
chainingKey [32]byte
// tempKey is the latter 32 bytes resulted from the latest HKDF
// iteration. This key is used to encrypt/decrypt any handshake
// messages or payloads sent until the next DH operation is executed.
tempKey [32]byte
// handshakeDigest is the cumulative hash digest of all handshake
// messages sent from start to finish. This value is never transmitted
// to the other side, but will be used as the AD when
// encrypting/decrypting messages using our AEAD construction.
handshakeDigest [32]byte
}
// mixKey implements a basic HKDF-based key ratchet. This method is called
// with the result of each DH output generated during the handshake process.
// The first 32 bytes extract from the HKDF reader is the next chaining key,
// then latter 32 bytes become the temp secret key using within any future AEAD
// operations until another DH operation is performed.
func (s *symmetricState) mixKey(input []byte) {
var info []byte
secret := input
salt := s.chainingKey
h := hkdf.New(sha256.New, secret, salt[:], info)
// hkdf(ck, input, zero)
// |
// | \
// | \
// ck k
h.Read(s.chainingKey[:])
h.Read(s.tempKey[:])
// cipher.k = temp_key
s.InitializeKey(s.tempKey)
}
// mixHash hashes the passed input data into the cumulative handshake digest.
// The running result of this value (h) is used as the associated data in all
// decryption/encryption operations.
func (s *symmetricState) mixHash(data []byte) {
h := sha256.New()
h.Write(s.handshakeDigest[:])
h.Write(data)
copy(s.handshakeDigest[:], h.Sum(nil))
}
// EncryptAndHash returns the authenticated encryption of the passed plaintext.
// When encrypting the handshake digest (h) is used as the associated data to
// the AEAD cipher.
func (s *symmetricState) EncryptAndHash(plaintext []byte) []byte {
ciphertext := s.Encrypt(s.handshakeDigest[:], nil, plaintext)
s.mixHash(ciphertext)
return ciphertext
}
// DecryptAndHash returns the authenticated decryption of the passed
// ciphertext. When encrypting the handshake digest (h) is used as the
// associated data to the AEAD cipher.
func (s *symmetricState) DecryptAndHash(ciphertext []byte) ([]byte, error) {
plaintext, err := s.Decrypt(s.handshakeDigest[:], nil, ciphertext)
if err != nil {
return nil, err
}
s.mixHash(ciphertext)
return plaintext, nil
}
// InitializeSymmetric initializes the symmetric state by setting the handshake
// digest (h) and the chaining key (ck) to protocol name.
func (s *symmetricState) InitializeSymmetric(protocolName []byte) {
var empty [32]byte
s.handshakeDigest = sha256.Sum256(protocolName)
s.chainingKey = s.handshakeDigest
s.InitializeKey(empty)
}
// handshakeState encapsulates the symmetricState and keeps track of all the
// public keys (static and ephemeral) for both sides during the handshake
// transcript. If the handshake completes successfully, then two instances of a
// cipherState are emitted: one to encrypt messages from initiator to
// responder, and the other for the opposite direction.
type handshakeState struct {
symmetricState
initiator bool
localStatic keychain.SingleKeyECDH
localEphemeral keychain.SingleKeyECDH // nolint (false positive)
remoteStatic *btcec.PublicKey
remoteEphemeral *btcec.PublicKey
}
// newHandshakeState returns a new instance of the handshake state initialized
// with the prologue and protocol name. If this is the responder's handshake
// state, then the remotePub can be nil.
func newHandshakeState(initiator bool, prologue []byte,
localKey keychain.SingleKeyECDH,
remotePub *btcec.PublicKey) handshakeState {
h := handshakeState{
initiator: initiator,
localStatic: localKey,
remoteStatic: remotePub,
}
// Set the current chaining key and handshake digest to the hash of the
// protocol name, and additionally mix in the prologue. If either sides
// disagree about the prologue or protocol name, then the handshake
// will fail.
h.InitializeSymmetric([]byte(protocolName))
h.mixHash(prologue)
// In Noise_XK, the initiator should know the responder's static
// public key, therefore we include the responder's static key in the
// handshake digest. If the initiator gets this value wrong, then the
// handshake will fail.
if initiator {
h.mixHash(remotePub.SerializeCompressed())
} else {
h.mixHash(localKey.PubKey().SerializeCompressed())
}
return h
}
// EphemeralGenerator is a functional option that allows callers to substitute
// a custom function for use when generating ephemeral keys for ActOne or
// ActTwo. The function closure returned by this function can be passed into
// NewBrontideMachine as a function option parameter.
func EphemeralGenerator(gen func() (*btcec.PrivateKey, error)) func(*Machine) {
return func(m *Machine) {
m.ephemeralGen = gen
}
}
// Machine is a state-machine which implements Brontide: an
// Authenticated-key Exchange in Three Acts. Brontide is derived from the Noise
// framework, specifically implementing the Noise_XK handshake. Once the
// initial 3-act handshake has completed all messages are encrypted with a
// chacha20 AEAD cipher. On the wire, all messages are prefixed with an
// authenticated+encrypted length field. Additionally, the encrypted+auth'd
// length prefix is used as the AD when encrypting+decryption messages. This
// construction provides confidentiality of packet length, avoids introducing
// a padding-oracle, and binds the encrypted packet length to the packet
// itself.
//
// The acts proceeds the following order (initiator on the left):
//
// GenActOne() ->
// RecvActOne()
// <- GenActTwo()
// RecvActTwo()
// GenActThree() ->
// RecvActThree()
//
// This exchange corresponds to the following Noise handshake:
//
// <- s
// ...
// -> e, es
// <- e, ee
// -> s, se
type Machine struct {
sendCipher cipherState
recvCipher cipherState
ephemeralGen func() (*btcec.PrivateKey, error)
handshakeState
// nextCipherHeader is a static buffer that we'll use to read in the
// next ciphertext header from the wire. The header is a 2 byte length
// (of the next ciphertext), followed by a 16 byte MAC.
nextCipherHeader [encHeaderSize]byte
// nextHeaderSend holds a reference to the remaining header bytes to
// write out for a pending message. This allows us to tolerate timeout
// errors that cause partial writes.
nextHeaderSend []byte
// nextBodySend holds a reference to the remaining body bytes to write
// out for a pending message. This allows us to tolerate timeout errors
// that cause partial writes.
nextBodySend []byte
}
// NewBrontideMachine creates a new instance of the brontide state-machine. If
// the responder (listener) is creating the object, then the remotePub should
// be nil. The handshake state within brontide is initialized using the ascii
// string "lightning" as the prologue. The last parameter is a set of variadic
// arguments for adding additional options to the brontide Machine
// initialization.
func NewBrontideMachine(initiator bool, localKey keychain.SingleKeyECDH,
remotePub *btcec.PublicKey, options ...func(*Machine)) *Machine {
handshake := newHandshakeState(
initiator, lightningPrologue, localKey, remotePub,
)
m := &Machine{
handshakeState: handshake,
ephemeralGen: ephemeralGen,
}
// With the default options established, we'll now process all the
// options passed in as parameters.
for _, option := range options {
option(m)
}
return m
}
const (
// HandshakeVersion is the expected version of the brontide handshake.
// Any messages that carry a different version will cause the handshake
// to abort immediately.
HandshakeVersion = byte(0)
// ActOneSize is the size of the packet sent from initiator to
// responder in ActOne. The packet consists of a handshake version, an
// ephemeral key in compressed format, and a 16-byte poly1305 tag.
//
// 1 + 33 + 16
ActOneSize = 50
// ActTwoSize is the size the packet sent from responder to initiator
// in ActTwo. The packet consists of a handshake version, an ephemeral
// key in compressed format and a 16-byte poly1305 tag.
//
// 1 + 33 + 16
ActTwoSize = 50
// ActThreeSize is the size of the packet sent from initiator to
// responder in ActThree. The packet consists of a handshake version,
// the initiators static key encrypted with strong forward secrecy and
// a 16-byte poly1035 tag.
//
// 1 + 33 + 16 + 16
ActThreeSize = 66
)
// GenActOne generates the initial packet (act one) to be sent from initiator
// to responder. During act one the initiator generates a fresh ephemeral key,
// hashes it into the handshake digest, and performs an ECDH between this key
// and the responder's static key. Future payloads are encrypted with a key
// derived from this result.
//
// -> e, es
func (b *Machine) GenActOne() ([ActOneSize]byte, error) {
var actOne [ActOneSize]byte
// e
localEphemeral, err := b.ephemeralGen()
if err != nil {
return actOne, err
}
b.localEphemeral = &keychain.PrivKeyECDH{
PrivKey: localEphemeral,
}
ephemeral := localEphemeral.PubKey().SerializeCompressed()
b.mixHash(ephemeral)
// es
s, err := ecdh(b.remoteStatic, b.localEphemeral)
if err != nil {
return actOne, err
}
b.mixKey(s[:])
authPayload := b.EncryptAndHash([]byte{})
actOne[0] = HandshakeVersion
copy(actOne[1:34], ephemeral)
copy(actOne[34:], authPayload)
return actOne, nil
}
// RecvActOne processes the act one packet sent by the initiator. The responder
// executes the mirrored actions to that of the initiator extending the
// handshake digest and deriving a new shared secret based on an ECDH with the
// initiator's ephemeral key and responder's static key.
func (b *Machine) RecvActOne(actOne [ActOneSize]byte) error {
var (
err error
e [33]byte
p [16]byte
)
// If the handshake version is unknown, then the handshake fails
// immediately.
if actOne[0] != HandshakeVersion {
return fmt.Errorf("act one: invalid handshake version: %v, "+
"only %v is valid, msg=%x", actOne[0], HandshakeVersion,
actOne[:])
}
copy(e[:], actOne[1:34])
copy(p[:], actOne[34:])
// e
b.remoteEphemeral, err = btcec.ParsePubKey(e[:])
if err != nil {
return err
}
b.mixHash(b.remoteEphemeral.SerializeCompressed())
// es
s, err := ecdh(b.remoteEphemeral, b.localStatic)
if err != nil {
return err
}
b.mixKey(s)
// If the initiator doesn't know our static key, then this operation
// will fail.
_, err = b.DecryptAndHash(p[:])
return err
}
// GenActTwo generates the second packet (act two) to be sent from the
// responder to the initiator. The packet for act two is identical to that of
// act one, but then results in a different ECDH operation between the
// initiator's and responder's ephemeral keys.
//
// <- e, ee
func (b *Machine) GenActTwo() ([ActTwoSize]byte, error) {
var actTwo [ActTwoSize]byte
// e
localEphemeral, err := b.ephemeralGen()
if err != nil {
return actTwo, err
}
b.localEphemeral = &keychain.PrivKeyECDH{
PrivKey: localEphemeral,
}
ephemeral := localEphemeral.PubKey().SerializeCompressed()
b.mixHash(localEphemeral.PubKey().SerializeCompressed())
// ee
s, err := ecdh(b.remoteEphemeral, b.localEphemeral)
if err != nil {
return actTwo, err
}
b.mixKey(s)
authPayload := b.EncryptAndHash([]byte{})
actTwo[0] = HandshakeVersion
copy(actTwo[1:34], ephemeral)
copy(actTwo[34:], authPayload)
return actTwo, nil
}
// RecvActTwo processes the second packet (act two) sent from the responder to
// the initiator. A successful processing of this packet authenticates the
// initiator to the responder.
func (b *Machine) RecvActTwo(actTwo [ActTwoSize]byte) error {
var (
err error
e [33]byte
p [16]byte
)
// If the handshake version is unknown, then the handshake fails
// immediately.
if actTwo[0] != HandshakeVersion {
return fmt.Errorf("act two: invalid handshake version: %v, "+
"only %v is valid, msg=%x", actTwo[0], HandshakeVersion,
actTwo[:])
}
copy(e[:], actTwo[1:34])
copy(p[:], actTwo[34:])
// e
b.remoteEphemeral, err = btcec.ParsePubKey(e[:])
if err != nil {
return err
}
b.mixHash(b.remoteEphemeral.SerializeCompressed())
// ee
s, err := ecdh(b.remoteEphemeral, b.localEphemeral)
if err != nil {
return err
}
b.mixKey(s)
_, err = b.DecryptAndHash(p[:])
return err
}
// GenActThree creates the final (act three) packet of the handshake. Act three
// is to be sent from the initiator to the responder. The purpose of act three
// is to transmit the initiator's public key under strong forward secrecy to
// the responder. This act also includes the final ECDH operation which yields
// the final session.
//
// -> s, se
func (b *Machine) GenActThree() ([ActThreeSize]byte, error) {
var actThree [ActThreeSize]byte
ourPubkey := b.localStatic.PubKey().SerializeCompressed()
ciphertext := b.EncryptAndHash(ourPubkey)
s, err := ecdh(b.remoteEphemeral, b.localStatic)
if err != nil {
return actThree, err
}
b.mixKey(s)
authPayload := b.EncryptAndHash([]byte{})
actThree[0] = HandshakeVersion
copy(actThree[1:50], ciphertext)
copy(actThree[50:], authPayload)
// With the final ECDH operation complete, derive the session sending
// and receiving keys.
b.split()
return actThree, nil
}
// RecvActThree processes the final act (act three) sent from the initiator to
// the responder. After processing this act, the responder learns of the
// initiator's static public key. Decryption of the static key serves to
// authenticate the initiator to the responder.
func (b *Machine) RecvActThree(actThree [ActThreeSize]byte) error {
var (
err error
s [33 + 16]byte
p [16]byte
)
// If the handshake version is unknown, then the handshake fails
// immediately.
if actThree[0] != HandshakeVersion {
return fmt.Errorf("act three: invalid handshake version: %v, "+
"only %v is valid, msg=%x", actThree[0], HandshakeVersion,
actThree[:])
}
copy(s[:], actThree[1:33+16+1])
copy(p[:], actThree[33+16+1:])
// s
remotePub, err := b.DecryptAndHash(s[:])
if err != nil {
return err
}
b.remoteStatic, err = btcec.ParsePubKey(remotePub)
if err != nil {
return err
}
// se
se, err := ecdh(b.remoteStatic, b.localEphemeral)
if err != nil {
return err
}
b.mixKey(se)
if _, err := b.DecryptAndHash(p[:]); err != nil {
return err
}
// With the final ECDH operation complete, derive the session sending
// and receiving keys.
b.split()
return nil
}
// split is the final wrap-up act to be executed at the end of a successful
// three act handshake. This function creates two internal cipherState
// instances: one which is used to encrypt messages from the initiator to the
// responder, and another which is used to encrypt message for the opposite
// direction.
func (b *Machine) split() {
var (
empty []byte
sendKey [32]byte
recvKey [32]byte
)
h := hkdf.New(sha256.New, empty, b.chainingKey[:], empty)
// If we're the initiator the first 32 bytes are used to encrypt our
// messages and the second 32-bytes to decrypt their messages. For the
// responder the opposite is true.
if b.initiator {
h.Read(sendKey[:])
b.sendCipher = cipherState{}
b.sendCipher.InitializeKeyWithSalt(b.chainingKey, sendKey)
h.Read(recvKey[:])
b.recvCipher = cipherState{}
b.recvCipher.InitializeKeyWithSalt(b.chainingKey, recvKey)
} else {
h.Read(recvKey[:])
b.recvCipher = cipherState{}
b.recvCipher.InitializeKeyWithSalt(b.chainingKey, recvKey)
h.Read(sendKey[:])
b.sendCipher = cipherState{}
b.sendCipher.InitializeKeyWithSalt(b.chainingKey, sendKey)
}
}
// WriteMessage encrypts and buffers the next message p. The ciphertext of the
// message is prepended with an encrypt+auth'd length which must be used as the
// AD to the AEAD construction when being decrypted by the other side.
//
// NOTE: This DOES NOT write the message to the wire, it should be followed by a
// call to Flush to ensure the message is written.
func (b *Machine) WriteMessage(p []byte) error {
// The total length of each message payload including the MAC size
// payload exceed the largest number encodable within a 16-bit unsigned
// integer.
if len(p) > math.MaxUint16 {
return ErrMaxMessageLengthExceeded
}
// If a prior message was written but it hasn't been fully flushed,
// return an error as we only support buffering of one message at a
// time.
if len(b.nextHeaderSend) > 0 || len(b.nextBodySend) > 0 {
return ErrMessageNotFlushed
}
// The full length of the packet is only the packet length, and does
// NOT include the MAC.
fullLength := uint16(len(p))
var pktLen [2]byte
binary.BigEndian.PutUint16(pktLen[:], fullLength)
// First, generate the encrypted+MAC'd length prefix for the packet.
b.nextHeaderSend = b.sendCipher.Encrypt(nil, nil, pktLen[:])
// Finally, generate the encrypted packet itself.
b.nextBodySend = b.sendCipher.Encrypt(nil, nil, p)
return nil
}
// Flush attempts to write a message buffered using WriteMessage to the provided
// io.Writer. If no buffered message exists, this will result in a NOP.
// Otherwise, it will continue to write the remaining bytes, picking up where
// the byte stream left off in the event of a partial write. The number of bytes
// returned reflects the number of plaintext bytes in the payload, and does not
// account for the overhead of the header or MACs.
//
// NOTE: It is safe to call this method again iff a timeout error is returned.
func (b *Machine) Flush(w io.Writer) (int, error) {
// First, write out the pending header bytes, if any exist. Any header
// bytes written will not count towards the total amount flushed.
if len(b.nextHeaderSend) > 0 {
// Write any remaining header bytes and shift the slice to point
// to the next segment of unwritten bytes. If an error is
// encountered, we can continue to write the header from where
// we left off on a subsequent call to Flush.
n, err := w.Write(b.nextHeaderSend)
b.nextHeaderSend = b.nextHeaderSend[n:]
if err != nil {
return 0, err
}
}
// Next, write the pending body bytes, if any exist. Only the number of
// bytes written that correspond to the ciphertext will be included in
// the total bytes written, bytes written as part of the MAC will not be
// counted.
var nn int
if len(b.nextBodySend) > 0 {
// Write out all bytes excluding the mac and shift the body
// slice depending on the number of actual bytes written.
n, err := w.Write(b.nextBodySend)
b.nextBodySend = b.nextBodySend[n:]
// If we partially or fully wrote any of the body's MAC, we'll
// subtract that contribution from the total amount flushed to
// preserve the abstraction of returning the number of plaintext
// bytes written by the connection.
//
// There are three possible scenarios we must handle to ensure
// the returned value is correct. In the first case, the write
// straddles both payload and MAC bytes, and we must subtract
// the number of MAC bytes written from n. In the second, only
// payload bytes are written, thus we can return n unmodified.
// The final scenario pertains to the case where only MAC bytes
// are written, none of which count towards the total.
//
// |-----------Payload------------|----MAC----|
// Straddle: S---------------------------------E--------0
// Payload-only: S------------------------E-----------------0
// MAC-only: S-------E-0
start, end := n+len(b.nextBodySend), len(b.nextBodySend)
switch {
// Straddles payload and MAC bytes, subtract number of MAC bytes
// written from the actual number written.
case start > macSize && end <= macSize:
nn = n - (macSize - end)
// Only payload bytes are written, return n directly.
case start > macSize && end > macSize:
nn = n
// Only MAC bytes are written, return 0 bytes written.
default:
}
if err != nil {
return nn, err
}
}
return nn, nil
}
// ReadMessage attempts to read the next message from the passed io.Reader. In
// the case of an authentication error, a non-nil error is returned.
func (b *Machine) ReadMessage(r io.Reader) ([]byte, error) {
pktLen, err := b.ReadHeader(r)
if err != nil {
return nil, err
}
buf := make([]byte, pktLen)
return b.ReadBody(r, buf)
}
// ReadHeader attempts to read the next message header from the passed
// io.Reader. The header contains the length of the next body including
// additional overhead of the MAC. In the case of an authentication error, a
// non-nil error is returned.
//
// NOTE: This method SHOULD NOT be used in the case that the io.Reader may be
// adversarial and induce long delays. If the caller needs to set read deadlines
// appropriately, it is preferred that they use the split ReadHeader and
// ReadBody methods so that the deadlines can be set appropriately on each.
func (b *Machine) ReadHeader(r io.Reader) (uint32, error) {
_, err := io.ReadFull(r, b.nextCipherHeader[:])
if err != nil {
return 0, err
}
// Attempt to decrypt+auth the packet length present in the stream.
//
// By passing in `nextCipherHeader` as the destination, we avoid making
// the library allocate a new buffer to decode the plaintext.
pktLenBytes, err := b.recvCipher.Decrypt(
nil, b.nextCipherHeader[:0], b.nextCipherHeader[:],
)
if err != nil {
return 0, err
}
// Compute the packet length that we will need to read off the wire.
pktLen := uint32(binary.BigEndian.Uint16(pktLenBytes)) + macSize
return pktLen, nil
}
// ReadBody attempts to ready the next message body from the passed io.Reader.
// The provided buffer MUST be the length indicated by the packet length
// returned by the preceding call to ReadHeader. In the case of an
// authentication error, a non-nil error is returned.
func (b *Machine) ReadBody(r io.Reader, buf []byte) ([]byte, error) {
// Next, using the length read from the packet header, read the
// encrypted packet itself into the buffer allocated by the read
// pool.
_, err := io.ReadFull(r, buf)
if err != nil {
return nil, err
}
// Finally, decrypt the message held in the buffer, and return a new
// byte slice containing the plaintext.
//
// By passing in the buf (the ciphertext) as the first argument, we end
// up re-using it as we don't force the library to allocate a new
// buffer to decode the plaintext.
return b.recvCipher.Decrypt(nil, buf[:0], buf)
}
package buffer
import (
"github.com/lightningnetwork/lnd/lnwire"
)
// ReadSize represents the size of the maximum message that can be read off the
// wire by brontide. The buffer is used to hold the ciphertext while the
// brontide state machine decrypts the message.
const ReadSize = lnwire.MaxSliceLength + 16
// Read is a static byte array sized to the maximum-allowed Lightning message
// size, plus 16 bytes for the MAC.
type Read [ReadSize]byte
// Recycle zeroes the Read, making it fresh for another use.
func (b *Read) Recycle() {
RecycleSlice(b[:])
}
package buffer
// RecycleSlice zeroes byte slice, making it fresh for another use.
// Zeroing the buffer using a logarithmic number of calls to the optimized copy
// method. Benchmarking shows this to be ~30 times faster than a for loop that
// sets each index to 0 for ~65KB buffers use for wire messages. Inspired by:
// https://stackoverflow.com/questions/30614165/is-there-analog-of-memset-in-go
func RecycleSlice(b []byte) {
if len(b) == 0 {
return
}
b[0] = 0
for i := 1; i < len(b); i *= 2 {
copy(b[i:], b[:i])
}
}
package buffer
import (
"github.com/lightningnetwork/lnd/lnwire"
)
// WriteSize represents the size of the maximum plaintext message than can be
// sent using brontide. The buffer does not include extra space for the MAC, as
// that is applied by the Noise protocol after encrypting the plaintext.
const WriteSize = lnwire.MaxSliceLength
// Write is static byte array occupying to maximum-allowed plaintext-message
// size.
type Write [WriteSize]byte
// Recycle zeroes the Write, making it fresh for another use.
func (b *Write) Recycle() {
RecycleSlice(b[:])
}
package build
import (
"fmt"
"github.com/btcsuite/btclog/v2"
)
const (
callSiteOff = "off"
callSiteShort = "short"
callSiteLong = "long"
defaultLogCompressor = Gzip
// DefaultMaxLogFiles is the default maximum number of log files to
// keep.
DefaultMaxLogFiles = 10
// DefaultMaxLogFileSize is the default maximum log file size in MB.
DefaultMaxLogFileSize = 20
)
// LogConfig holds logging configuration options.
//
//nolint:ll
type LogConfig struct {
Console *consoleLoggerCfg `group:"console" namespace:"console" description:"The logger writing to stdout and stderr."`
File *FileLoggerConfig `group:"file" namespace:"file" description:"The logger writing to LND's standard log file."`
NoCommitHash bool `long:"no-commit-hash" description:"If set, the commit-hash of the current build will not be included in log lines by default."`
}
// Validate validates the LogConfig struct values.
func (c *LogConfig) Validate() error {
if !SupportedLogCompressor(c.File.Compressor) {
return fmt.Errorf("invalid log compressor: %v",
c.File.Compressor)
}
return nil
}
// LoggerConfig holds options for a particular logger.
//
//nolint:ll
type LoggerConfig struct {
Disable bool `long:"disable" description:"Disable this logger."`
NoTimestamps bool `long:"no-timestamps" description:"Omit timestamps from log lines."`
CallSite string `long:"call-site" description:"Include the call-site of each log line." choice:"off" choice:"short" choice:"long"`
}
// DefaultLogConfig returns the default logging config options.
func DefaultLogConfig() *LogConfig {
return &LogConfig{
Console: defaultConsoleLoggerCfg(),
File: &FileLoggerConfig{
Compressor: defaultLogCompressor,
MaxLogFiles: DefaultMaxLogFiles,
MaxLogFileSize: DefaultMaxLogFileSize,
LoggerConfig: &LoggerConfig{
CallSite: callSiteOff,
},
},
}
}
// HandlerOptions returns the set of btclog.HandlerOptions that the state of the
// config struct translates to.
func (cfg *LoggerConfig) HandlerOptions() []btclog.HandlerOption {
opts := []btclog.HandlerOption{
// We wrap the logger provided by the logging library with
// another layer of abstraction with the handlerSet, and so we
// need to increase the default skip depth by 1.
btclog.WithCallSiteSkipDepth(btclog.DefaultSkipDepth + 1),
}
if cfg.NoTimestamps {
opts = append(opts, btclog.WithNoTimestamp())
}
switch cfg.CallSite {
case callSiteShort:
opts = append(opts, btclog.WithCallerFlags(btclog.Lshortfile))
case callSiteLong:
opts = append(opts, btclog.WithCallerFlags(btclog.Llongfile))
}
return opts
}
// FileLoggerConfig extends LoggerConfig with specific log file options.
//
//nolint:ll
type FileLoggerConfig struct {
*LoggerConfig `yaml:",inline"`
Compressor string `long:"compressor" description:"Compression algorithm to use when rotating logs." choice:"gzip" choice:"zstd"`
MaxLogFiles int `long:"max-files" description:"Maximum logfiles to keep (0 for no rotation)"`
MaxLogFileSize int `long:"max-file-size" description:"Maximum logfile size in MB"`
}
//go:build !dev
// +build !dev
package build
// consoleLoggerCfg embeds the LoggerConfig struct along with any extensions
// specific to a production deployment.
//
//nolint:ll
type consoleLoggerCfg struct {
*LoggerConfig `yaml:",inline"`
}
// defaultConsoleLoggerCfg returns the default consoleLoggerCfg for the prod
// console logger.
func defaultConsoleLoggerCfg() *consoleLoggerCfg {
return &consoleLoggerCfg{
LoggerConfig: &LoggerConfig{
CallSite: callSiteOff,
},
}
}
package build
// DeploymentType is an enum specifying the deployment to compile.
type DeploymentType byte
const (
// Development is a deployment that includes extra testing hooks and
// logging configurations.
Development DeploymentType = iota
// Production is a deployment that strips out testing logic and uses
// Default logging.
Production
)
// String returns a human readable name for a build type.
func (b DeploymentType) String() string {
switch b {
case Development:
return "development"
case Production:
return "production"
default:
return "unknown"
}
}
// IsProdBuild returns true if this is a production build.
func IsProdBuild() bool {
return Deployment == Production
}
// IsDevBuild returns true if this is a development build.
func IsDevBuild() bool {
return Deployment == Development
}
package build
import (
"context"
"log/slog"
btclogv1 "github.com/btcsuite/btclog"
"github.com/btcsuite/btclog/v2"
)
// handlerSet is an implementation of Handler that abstracts away multiple
// Handlers.
type handlerSet struct {
level btclogv1.Level
set []btclog.Handler
}
// newHandlerSet constructs a new HandlerSet.
func newHandlerSet(level btclogv1.Level, set ...btclog.Handler) *handlerSet {
h := &handlerSet{
set: set,
level: level,
}
h.SetLevel(level)
return h
}
// Enabled reports whether the handler handles records at the given level.
//
// NOTE: this is part of the slog.Handler interface.
func (h *handlerSet) Enabled(ctx context.Context, level slog.Level) bool {
for _, handler := range h.set {
if !handler.Enabled(ctx, level) {
return false
}
}
return true
}
// Handle handles the Record.
//
// NOTE: this is part of the slog.Handler interface.
func (h *handlerSet) Handle(ctx context.Context, record slog.Record) error {
for _, handler := range h.set {
if err := handler.Handle(ctx, record); err != nil {
return err
}
}
return nil
}
// WithAttrs returns a new Handler whose attributes consist of both the
// receiver's attributes and the arguments.
//
// NOTE: this is part of the slog.Handler interface.
func (h *handlerSet) WithAttrs(attrs []slog.Attr) slog.Handler {
newSet := &reducedSet{set: make([]slog.Handler, len(h.set))}
for i, handler := range h.set {
newSet.set[i] = handler.WithAttrs(attrs)
}
return newSet
}
// WithGroup returns a new Handler with the given group appended to the
// receiver's existing groups.
//
// NOTE: this is part of the slog.Handler interface.
func (h *handlerSet) WithGroup(name string) slog.Handler {
newSet := &reducedSet{set: make([]slog.Handler, len(h.set))}
for i, handler := range h.set {
newSet.set[i] = handler.WithGroup(name)
}
return newSet
}
// SubSystem creates a new Handler with the given sub-system tag.
//
// NOTE: this is part of the Handler interface.
func (h *handlerSet) SubSystem(tag string) btclog.Handler {
newSet := &handlerSet{set: make([]btclog.Handler, len(h.set))}
for i, handler := range h.set {
newSet.set[i] = handler.SubSystem(tag)
}
return newSet
}
// SetLevel changes the logging level of the Handler to the passed
// level.
//
// NOTE: this is part of the btclog.Handler interface.
func (h *handlerSet) SetLevel(level btclogv1.Level) {
for _, handler := range h.set {
handler.SetLevel(level)
}
h.level = level
}
// Level returns the current logging level of the Handler.
//
// NOTE: this is part of the btclog.Handler interface.
func (h *handlerSet) Level() btclogv1.Level {
return h.level
}
// WithPrefix returns a copy of the Handler but with the given string prefixed
// to each log message.
//
// NOTE: this is part of the btclog.Handler interface.
func (h *handlerSet) WithPrefix(prefix string) btclog.Handler {
newSet := &handlerSet{set: make([]btclog.Handler, len(h.set))}
for i, handler := range h.set {
newSet.set[i] = handler.WithPrefix(prefix)
}
return newSet
}
// A compile-time check to ensure that handlerSet implements btclog.Handler.
var _ btclog.Handler = (*handlerSet)(nil)
// reducedSet is an implementation of the slog.Handler interface which is
// itself backed by multiple slog.Handlers. This is used by the handlerSet
// WithGroup and WithAttrs methods so that we can apply the WithGroup and
// WithAttrs to the underlying handlers in the set. These calls, however,
// produce slog.Handlers and not btclog.Handlers. So the reducedSet represents
// the resulting set produced.
type reducedSet struct {
set []slog.Handler
}
// Enabled reports whether the handler handles records at the given level.
//
// NOTE: this is part of the slog.Handler interface.
func (r *reducedSet) Enabled(ctx context.Context, level slog.Level) bool {
for _, handler := range r.set {
if !handler.Enabled(ctx, level) {
return false
}
}
return true
}
// Handle handles the Record.
//
// NOTE: this is part of the slog.Handler interface.
func (r *reducedSet) Handle(ctx context.Context, record slog.Record) error {
for _, handler := range r.set {
if err := handler.Handle(ctx, record); err != nil {
return err
}
}
return nil
}
// WithAttrs returns a new Handler whose attributes consist of both the
// receiver's attributes and the arguments.
//
// NOTE: this is part of the slog.Handler interface.
func (r *reducedSet) WithAttrs(attrs []slog.Attr) slog.Handler {
newSet := &reducedSet{set: make([]slog.Handler, len(r.set))}
for i, handler := range r.set {
newSet.set[i] = handler.WithAttrs(attrs)
}
return newSet
}
// WithGroup returns a new Handler with the given group appended to the
// receiver's existing groups.
//
// NOTE: this is part of the slog.Handler interface.
func (r *reducedSet) WithGroup(name string) slog.Handler {
newSet := &reducedSet{set: make([]slog.Handler, len(r.set))}
for i, handler := range r.set {
newSet.set[i] = handler.WithGroup(name)
}
return newSet
}
// A compile-time check to ensure that handlerSet implements slog.Handler.
var _ slog.Handler = (*reducedSet)(nil)
// subLogGenerator implements the SubLogCreator backed by a Handler.
type subLogGenerator struct {
handler btclog.Handler
}
// newSubLogGenerator constructs a new subLogGenerator from a Handler.
func newSubLogGenerator(handler btclog.Handler) *subLogGenerator {
return &subLogGenerator{
handler: handler,
}
}
// Logger returns a new logger for a particular sub-system.
//
// NOTE: this is part of the SubLogCreator interface.
func (b *subLogGenerator) Logger(subsystemTag string) btclog.Logger {
handler := b.handler.SubSystem(subsystemTag)
return btclog.NewSLogger(handler)
}
// A compile-time check to ensure that handlerSet implements slog.Handler.
var _ SubLogCreator = (*subLogGenerator)(nil)
package build
import (
"os"
"github.com/btcsuite/btclog/v2"
)
// NewDefaultLogHandlers returns the standard console logger and rotating log
// writer handlers that we generally want to use. It also applies the various
// config options to the loggers.
func NewDefaultLogHandlers(cfg *LogConfig,
rotator *RotatingLogWriter) []btclog.Handler {
var handlers []btclog.Handler
consoleLogHandler := btclog.NewDefaultHandler(
os.Stdout, cfg.Console.HandlerOptions()...,
)
logFileHandler := btclog.NewDefaultHandler(
rotator, cfg.File.HandlerOptions()...,
)
maybeAddLogger := func(cmdOptionDisable bool, handler btclog.Handler) {
if !cmdOptionDisable {
handlers = append(handlers, handler)
}
}
switch LoggingType {
case LogTypeStdOut:
maybeAddLogger(cfg.Console.Disable, consoleLogHandler)
case LogTypeDefault:
maybeAddLogger(cfg.Console.Disable, consoleLogHandler)
maybeAddLogger(cfg.File.Disable, logFileHandler)
}
return handlers
}
package build
import (
"os"
"github.com/btcsuite/btclog/v2"
)
// LogType is an indicating the type of logging specified by the build flag.
type LogType byte
const (
// LogTypeNone indicates no logging.
LogTypeNone LogType = iota
// LogTypeStdOut all logging is written directly to stdout.
LogTypeStdOut
// LogTypeDefault logs to both stdout and a given io.PipeWriter.
LogTypeDefault
)
// String returns a human readable identifier for the logging type.
func (t LogType) String() string {
switch t {
case LogTypeNone:
return "none"
case LogTypeStdOut:
return "stdout"
case LogTypeDefault:
return "default"
default:
return "unknown"
}
}
// Declare the supported log file compressors as exported consts for easier use
// from other projects.
const (
// Gzip is the default compressor.
Gzip = "gzip"
// Zstd is a modern compressor that compresses better than Gzip, in less
// time.
Zstd = "zstd"
)
// logCompressors maps the identifier for each supported compression algorithm
// to the extension used for the compressed log files.
var logCompressors = map[string]string{
Gzip: "gz",
Zstd: "zst",
}
// SupportedLogCompressor returns whether or not logCompressor is a supported
// compression algorithm for log files.
func SupportedLogCompressor(logCompressor string) bool {
_, ok := logCompressors[logCompressor]
return ok
}
// NewSubLogger constructs a new subsystem log from the current LogWriter
// implementation. This is primarily intended for use with stdlog, as the actual
// writer is shared amongst all instantiations.
func NewSubLogger(subsystem string,
genSubLogger func(string) btclog.Logger) btclog.Logger {
switch Deployment {
// For production builds, generate a new subsystem logger from the
// primary log backend. If no function is provided, logging will be
// disabled.
case Production:
if genSubLogger != nil {
return genSubLogger(subsystem)
}
// For development builds, we must handle two distinct types of logging:
// unit tests and running the live daemon, e.g. for integration testing.
case Development:
switch LoggingType {
// Default logging is used when running the standalone daemon.
// We'll use the optional sublogger constructor to mimic the
// production behavior.
case LogTypeDefault:
if genSubLogger != nil {
return genSubLogger(subsystem)
}
// Logging to stdout is used in unit tests. It is not important
// that they share the same backend, since all output is written
// to std out.
case LogTypeStdOut:
backend := btclog.NewDefaultHandler(os.Stdout)
logger := btclog.NewSLogger(
backend.SubSystem(subsystem),
)
// Set the logging level of the stdout logger to use the
// configured logging level specified by build flags.
level, _ := btclog.LevelFromString(LogLevel)
logger.SetLevel(level)
return logger
}
}
// For any other configurations, we'll disable logging.
return btclog.Disabled
}
package build
import (
"context"
"github.com/btcsuite/btclog/v2"
)
// ShutdownLogger wraps an existing logger with a shutdown function which will
// be called on Critical/Criticalf to prompt shutdown.
type ShutdownLogger struct {
btclog.Logger
shutdown func()
}
// NewShutdownLogger creates a shutdown logger for the log provided which will
// use the signal package to request shutdown on critical errors.
func NewShutdownLogger(logger btclog.Logger, shutdown func()) *ShutdownLogger {
return &ShutdownLogger{
Logger: logger,
shutdown: shutdown,
}
}
// Criticalf formats message according to format specifier and writes to
// log with LevelCritical. It will then call the shutdown logger's shutdown
// function to prompt safe shutdown.
//
// Note: it is part of the btclog.Logger interface.
func (s *ShutdownLogger) Criticalf(format string, params ...interface{}) {
s.Logger.Criticalf(format, params...)
s.Logger.Info("Sending request for shutdown")
s.shutdown()
}
// Critical formats message using the default formats for its operands
// and writes to log with LevelCritical. It will then call the shutdown
// logger's shutdown function to prompt safe shutdown.
//
// Note: it is part of the btclog.Logger interface.
func (s *ShutdownLogger) Critical(v ...interface{}) {
s.Logger.Critical(v)
s.Logger.Info("Sending request for shutdown")
s.shutdown()
}
// CriticalS writes a structured log with the given message and key-value pair
// attributes with LevelCritical to the log. It will then call the shutdown
// logger's shutdown function to prompt safe shutdown.
//
// Note: it is part of the btclog.Logger interface.
func (s *ShutdownLogger) CriticalS(ctx context.Context, msg string, err error,
attr ...interface{}) {
s.Logger.CriticalS(ctx, msg, err, attr...)
s.Logger.Info("Sending request for shutdown")
s.shutdown()
}
package build
import (
"compress/gzip"
"fmt"
"io"
"os"
"path/filepath"
"github.com/jrick/logrotate/rotator"
"github.com/klauspost/compress/zstd"
)
// RotatingLogWriter is a wrapper around the LogWriter that supports log file
// rotation.
type RotatingLogWriter struct {
// pipe is the write-end pipe for writing to the log rotator.
pipe *io.PipeWriter
rotator *rotator.Rotator
}
// NewRotatingLogWriter creates a new file rotating log writer.
//
// NOTE: `InitLogRotator` must be called to set up log rotation after creating
// the writer.
func NewRotatingLogWriter() *RotatingLogWriter {
return &RotatingLogWriter{}
}
// InitLogRotator initializes the log file rotator to write logs to logFile and
// create roll files in the same directory. It should be called as early on
// startup and possible and must be closed on shutdown by calling `Close`.
func (r *RotatingLogWriter) InitLogRotator(cfg *FileLoggerConfig,
logFile string) error {
logDir, _ := filepath.Split(logFile)
err := os.MkdirAll(logDir, 0700)
if err != nil {
return fmt.Errorf("failed to create log directory: %w", err)
}
r.rotator, err = rotator.New(
logFile, int64(cfg.MaxLogFileSize*1024), false, cfg.MaxLogFiles,
)
if err != nil {
return fmt.Errorf("failed to create file rotator: %w", err)
}
// Reject unknown compressors.
if !SupportedLogCompressor(cfg.Compressor) {
return fmt.Errorf("unknown log compressor: %v", cfg.Compressor)
}
var c rotator.Compressor
switch cfg.Compressor {
case Gzip:
c = gzip.NewWriter(nil)
case Zstd:
c, err = zstd.NewWriter(nil)
if err != nil {
return fmt.Errorf("failed to create zstd compressor: "+
"%w", err)
}
}
// Apply the compressor and its file suffix to the log rotator.
r.rotator.SetCompressor(c, logCompressors[cfg.Compressor])
// Run rotator as a goroutine now but make sure we catch any errors
// that happen in case something with the rotation goes wrong during
// runtime (like running out of disk space or not being allowed to
// create a new logfile for whatever reason).
pr, pw := io.Pipe()
go func() {
err := r.rotator.Run(pr)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr,
"failed to run file rotator: %v\n", err)
}
}()
r.pipe = pw
return nil
}
// Write writes the byte slice to the log rotator, if present.
func (r *RotatingLogWriter) Write(b []byte) (int, error) {
if r.rotator != nil {
return r.rotator.Write(b)
}
return len(b), nil
}
// Close closes the underlying log rotator if it has already been created.
func (r *RotatingLogWriter) Close() error {
if r.rotator != nil {
return r.rotator.Close()
}
return nil
}
package build
import (
"fmt"
"sort"
"strings"
"sync"
"github.com/btcsuite/btclog/v2"
)
// SubLogCreator can be used to create a new logger for a particular subsystem.
type SubLogCreator interface {
// Logger returns a new logger for a particular subsystem.
Logger(subsystemTag string) btclog.Logger
}
// SubLoggerManager manages a set of subsystem loggers. Level updates will be
// applied to all the loggers managed by the manager.
type SubLoggerManager struct {
genLogger SubLogCreator
loggers SubLoggers
mu sync.Mutex
}
// A compile time check to ensure SubLoggerManager implements the
// LeveledSubLogger interface.
var _ LeveledSubLogger = (*SubLoggerManager)(nil)
// NewSubLoggerManager constructs a new SubLoggerManager.
func NewSubLoggerManager(handlers ...btclog.Handler) *SubLoggerManager {
return &SubLoggerManager{
loggers: make(SubLoggers),
genLogger: newSubLogGenerator(
newHandlerSet(btclog.LevelInfo, handlers...),
),
}
}
// GenSubLogger creates a new sub-logger and adds it to the set managed by the
// SubLoggerManager. A shutdown callback function is provided to be able to shut
// down in case of a critical error.
func (r *SubLoggerManager) GenSubLogger(subsystem string,
shutdown func()) btclog.Logger {
// Create a new logger with the given subsystem tag.
logger := r.genLogger.Logger(subsystem)
// Wrap the new logger in a Shutdown logger so that the shutdown
// call back is called if a critical log is ever written via this new
// logger.
l := NewShutdownLogger(logger, shutdown)
r.RegisterSubLogger(subsystem, l)
return l
}
// RegisterSubLogger registers the given logger under the given subsystem name.
func (r *SubLoggerManager) RegisterSubLogger(subsystem string,
logger btclog.Logger) {
// Add the new logger to the set of loggers managed by the manager.
r.mu.Lock()
r.loggers[subsystem] = logger
r.mu.Unlock()
}
// SubLoggers returns all currently registered subsystem loggers for this log
// writer.
//
// NOTE: This is part of the LeveledSubLogger interface.
func (r *SubLoggerManager) SubLoggers() SubLoggers {
r.mu.Lock()
defer r.mu.Unlock()
return r.loggers
}
// SupportedSubsystems returns a sorted string slice of all keys in the
// subsystems map, corresponding to the names of the subsystems.
//
// NOTE: This is part of the LeveledSubLogger interface.
func (r *SubLoggerManager) SupportedSubsystems() []string {
r.mu.Lock()
defer r.mu.Unlock()
// Convert the subsystemLoggers map keys to a string slice.
subsystems := make([]string, 0, len(r.loggers))
for subsysID := range r.loggers {
subsystems = append(subsystems, subsysID)
}
// Sort the subsystems for stable display.
sort.Strings(subsystems)
return subsystems
}
// SetLogLevel sets the logging level for provided subsystem. Invalid
// subsystems are ignored. Uninitialized subsystems are dynamically created as
// needed.
//
// NOTE: This is part of the LeveledSubLogger interface.
func (r *SubLoggerManager) SetLogLevel(subsystemID string, logLevel string) {
r.mu.Lock()
defer r.mu.Unlock()
r.setLogLevelUnsafe(subsystemID, logLevel)
}
// setLogLevelUnsafe sets the logging level for provided subsystem. Invalid
// subsystems are ignored. Uninitialized subsystems are dynamically created as
// needed.
//
// NOTE: the SubLoggerManager mutex must be held before calling this method.
func (r *SubLoggerManager) setLogLevelUnsafe(subsystemID string,
logLevel string) {
// Ignore invalid subsystems.
logger, ok := r.loggers[subsystemID]
if !ok {
return
}
// Defaults to info if the log level is invalid.
level, _ := btclog.LevelFromString(logLevel)
logger.SetLevel(level)
}
// SetLogLevels sets the log level for all subsystem loggers to the passed
// level. It also dynamically creates the subsystem loggers as needed, so it
// can be used to initialize the logging system.
//
// NOTE: This is part of the LeveledSubLogger interface.
func (r *SubLoggerManager) SetLogLevels(logLevel string) {
r.mu.Lock()
defer r.mu.Unlock()
// Configure all sub-systems with the new logging level. Dynamically
// create loggers as needed.
for subsystemID := range r.loggers {
r.setLogLevelUnsafe(subsystemID, logLevel)
}
}
// SubLoggers is a type that holds a map of subsystem loggers keyed by their
// subsystem name.
type SubLoggers map[string]btclog.Logger
// LeveledSubLogger provides the ability to retrieve the subsystem loggers of
// a logger and set their log levels individually or all at once.
type LeveledSubLogger interface {
// SubLoggers returns the map of all registered subsystem loggers.
SubLoggers() SubLoggers
// SupportedSubsystems returns a slice of strings containing the names
// of the supported subsystems. Should ideally correspond to the keys
// of the subsystem logger map and be sorted.
SupportedSubsystems() []string
// SetLogLevel assigns an individual subsystem logger a new log level.
SetLogLevel(subsystemID string, logLevel string)
// SetLogLevels assigns all subsystem loggers the same new log level.
SetLogLevels(logLevel string)
}
// ParseAndSetDebugLevels attempts to parse the specified debug level and set
// the levels accordingly on the given logger. An appropriate error is returned
// if anything is invalid.
func ParseAndSetDebugLevels(level string, logger LeveledSubLogger) error {
// Split at the delimiter.
levels := strings.Split(level, ",")
if len(levels) == 0 {
return fmt.Errorf("invalid log level: %v", level)
}
// If the first entry has no =, treat is as the log level for all
// subsystems.
globalLevel := levels[0]
if !strings.Contains(globalLevel, "=") {
// Validate debug log level.
if !validLogLevel(globalLevel) {
str := "the specified debug level [%v] is invalid"
return fmt.Errorf(str, globalLevel)
}
// Change the logging level for all subsystems.
logger.SetLogLevels(globalLevel)
// The rest will target specific subsystems.
levels = levels[1:]
}
// Go through the subsystem/level pairs while detecting issues and
// update the log levels accordingly.
for _, logLevelPair := range levels {
if !strings.Contains(logLevelPair, "=") {
str := "the specified debug level contains an " +
"invalid subsystem/level pair [%v]"
return fmt.Errorf(str, logLevelPair)
}
// Extract the specified subsystem and log level.
fields := strings.Split(logLevelPair, "=")
if len(fields) != 2 {
str := "the specified debug level has an invalid " +
"format [%v] -- use format subsystem1=level1," +
"subsystem2=level2"
return fmt.Errorf(str, logLevelPair)
}
subsysID, logLevel := fields[0], fields[1]
subLoggers := logger.SubLoggers()
// Validate subsystem.
if _, exists := subLoggers[subsysID]; !exists {
str := "the specified subsystem [%v] is invalid -- " +
"supported subsystems are %v"
return fmt.Errorf(
str, subsysID, logger.SupportedSubsystems(),
)
}
// Validate log level.
if !validLogLevel(logLevel) {
str := "the specified debug level [%v] is invalid"
return fmt.Errorf(str, logLevel)
}
logger.SetLogLevel(subsysID, logLevel)
}
return nil
}
// validLogLevel returns whether or not logLevel is a valid debug log level.
func validLogLevel(logLevel string) bool {
switch logLevel {
case "trace":
fallthrough
case "debug":
fallthrough
case "info":
fallthrough
case "warn":
fallthrough
case "error":
fallthrough
case "critical":
fallthrough
case "off":
return true
}
return false
}
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Heavily inspired by https://github.com/btcsuite/btcd/blob/master/version.go
// Copyright (C) 2015-2022 The Lightning Network Developers
package build
import (
"context"
"encoding/hex"
"fmt"
"runtime/debug"
"strings"
"github.com/btcsuite/btclog/v2"
)
var (
// Commit stores the current commit of this build, which includes the
// most recent tag, the number of commits since that tag (if non-zero),
// the commit hash, and a dirty marker. This should be set using the
// -ldflags during compilation.
Commit string
// CommitHash stores the current commit hash of this build.
CommitHash string
// RawTags contains the raw set of build tags, separated by commas.
RawTags string
// GoVersion stores the go version that the executable was compiled
// with.
GoVersion string
)
// semanticAlphabet is the set of characters that are permitted for use in an
// AppPreRelease.
const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-."
// These constants define the application version and follow the semantic
// versioning 2.0.0 spec (http://semver.org/).
const (
// AppMajor defines the major version of this binary.
AppMajor uint = 0
// AppMinor defines the minor version of this binary.
AppMinor uint = 19
// AppPatch defines the application patch for this binary.
AppPatch uint = 00
// AppPreRelease MUST only contain characters from semanticAlphabet per
// the semantic versioning spec.
AppPreRelease = "beta.rc2"
)
func init() {
// Assert that AppPreRelease is valid according to the semantic
// versioning guidelines for pre-release version and build metadata
// strings. In particular it MUST only contain characters in
// semanticAlphabet.
for _, r := range AppPreRelease {
if !strings.ContainsRune(semanticAlphabet, r) {
panic(fmt.Errorf("rune: %v is not in the semantic "+
"alphabet", r))
}
}
// Get build information from the runtime.
if info, ok := debug.ReadBuildInfo(); ok {
GoVersion = info.GoVersion
for _, setting := range info.Settings {
switch setting.Key {
case "vcs.revision":
CommitHash = setting.Value
case "-tags":
RawTags = setting.Value
}
}
}
}
// Version returns the application version as a properly formed string per the
// semantic versioning 2.0.0 spec (http://semver.org/).
func Version() string {
// Start with the major, minor, and patch versions.
version := fmt.Sprintf("%d.%d.%d", AppMajor, AppMinor, AppPatch)
// Append pre-release version if there is one. The hyphen called for by
// the semantic versioning spec is automatically appended and should not
// be contained in the pre-release string.
if AppPreRelease != "" {
version = fmt.Sprintf("%s-%s", version, AppPreRelease)
}
return version
}
// Tags returns the list of build tags that were compiled into the executable.
func Tags() []string {
if len(RawTags) == 0 {
return nil
}
return strings.Split(RawTags, ",")
}
// WithBuildInfo derives a child context with the build information attached as
// attributes. At the moment, this only includes the current build's commit
// hash.
func WithBuildInfo(ctx context.Context, cfg *LogConfig) (context.Context,
error) {
if cfg.NoCommitHash {
return ctx, nil
}
// Convert the commit hash to a byte slice.
commitHash, err := hex.DecodeString(CommitHash)
if err != nil {
return nil, fmt.Errorf("unable to decode commit hash: %w", err)
}
return btclog.WithCtx(ctx, btclog.Hex3("rev", commitHash)), nil
}
package chainio
import (
"fmt"
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/chainntnfs"
)
// Beat implements the Blockbeat interface. It contains the block epoch and a
// customized logger.
//
// TODO(yy): extend this to check for confirmation status - which serves as the
// single source of truth, to avoid the potential race between receiving blocks
// and `GetTransactionDetails/RegisterSpendNtfn/RegisterConfirmationsNtfn`.
type Beat struct {
// epoch is the current block epoch the blockbeat is aware of.
epoch chainntnfs.BlockEpoch
// log is the customized logger for the blockbeat which prints the
// block height.
log btclog.Logger
}
// Compile-time check to ensure Beat satisfies the Blockbeat interface.
var _ Blockbeat = (*Beat)(nil)
// NewBeat creates a new beat with the specified block epoch and a customized
// logger.
func NewBeat(epoch chainntnfs.BlockEpoch) *Beat {
b := &Beat{
epoch: epoch,
}
// Create a customized logger for the blockbeat.
logPrefix := fmt.Sprintf("Height[%6d]:", b.Height())
b.log = clog.WithPrefix(logPrefix)
return b
}
// Height returns the height of the block epoch.
//
// NOTE: Part of the Blockbeat interface.
func (b *Beat) Height() int32 {
return b.epoch.Height
}
// logger returns the logger for the blockbeat.
//
// NOTE: Part of the private blockbeat interface.
func (b *Beat) logger() btclog.Logger {
return b.log
}
package chainio
// BeatConsumer defines a supplementary component that should be used by
// subsystems which implement the `Consumer` interface. It partially implements
// the `Consumer` interface by providing the method `ProcessBlock` such that
// subsystems don't need to re-implement it.
//
// While inheritance is not commonly used in Go, subsystems embedding this
// struct cannot pass the interface check for `Consumer` because the `Name`
// method is not implemented, which gives us a "mortise and tenon" structure.
// In addition to reducing code duplication, this design allows `ProcessBlock`
// to work on the concrete type `Beat` to access its internal states.
type BeatConsumer struct {
// BlockbeatChan is a channel to receive blocks from Blockbeat. The
// received block contains the best known height and the txns confirmed
// in this block.
BlockbeatChan chan Blockbeat
// name is the name of the consumer which embeds the BlockConsumer.
name string
// quit is a channel that closes when the BlockConsumer is shutting
// down.
//
// NOTE: this quit channel should be mounted to the same quit channel
// used by the subsystem.
quit chan struct{}
// errChan is a buffered chan that receives an error returned from
// processing this block.
errChan chan error
}
// NewBeatConsumer creates a new BlockConsumer.
func NewBeatConsumer(quit chan struct{}, name string) BeatConsumer {
// Refuse to start `lnd` if the quit channel is not initialized. We
// treat this case as if we are facing a nil pointer dereference, as
// there's no point to return an error here, which will cause the node
// to fail to be started anyway.
if quit == nil {
panic("quit channel is nil")
}
b := BeatConsumer{
BlockbeatChan: make(chan Blockbeat),
name: name,
errChan: make(chan error, 1),
quit: quit,
}
return b
}
// ProcessBlock takes a blockbeat and sends it to the consumer's blockbeat
// channel. It will send it to the subsystem's BlockbeatChan, and block until
// the processed result is received from the subsystem. The subsystem must call
// `NotifyBlockProcessed` after it has finished processing the block.
//
// NOTE: part of the `chainio.Consumer` interface.
func (b *BeatConsumer) ProcessBlock(beat Blockbeat) error {
// Update the current height.
beat.logger().Tracef("set current height for [%s]", b.name)
select {
// Send the beat to the blockbeat channel. It's expected that the
// consumer will read from this channel and process the block. Once
// processed, it should return the error or nil to the beat.Err chan.
case b.BlockbeatChan <- beat:
beat.logger().Tracef("Sent blockbeat to [%s]", b.name)
case <-b.quit:
beat.logger().Debugf("[%s] received shutdown before sending "+
"beat", b.name)
return nil
}
// Check the consumer's err chan. We expect the consumer to call
// `beat.NotifyBlockProcessed` to send the error back here.
select {
case err := <-b.errChan:
beat.logger().Tracef("[%s] processed beat: err=%v", b.name, err)
return err
case <-b.quit:
beat.logger().Debugf("[%s] received shutdown", b.name)
}
return nil
}
// NotifyBlockProcessed signals that the block has been processed. It takes the
// blockbeat being processed and an error resulted from processing it. This
// error is then sent back to the consumer's err chan to unblock
// `ProcessBlock`.
//
// NOTE: This method must be called by the subsystem after it has finished
// processing the block.
func (b *BeatConsumer) NotifyBlockProcessed(beat Blockbeat, err error) {
// Update the current height.
beat.logger().Tracef("[%s]: notifying beat processed", b.name)
select {
case b.errChan <- err:
beat.logger().Tracef("[%s]: notified beat processed, err=%v",
b.name, err)
case <-b.quit:
beat.logger().Debugf("[%s] received shutdown before notifying "+
"beat processed", b.name)
}
}
package chainio
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/lnutils"
"golang.org/x/sync/errgroup"
)
// DefaultProcessBlockTimeout is the timeout value used when waiting for one
// consumer to finish processing the new block epoch.
var DefaultProcessBlockTimeout = 60 * time.Second
// ErrProcessBlockTimeout is the error returned when a consumer takes too long
// to process the block.
var ErrProcessBlockTimeout = errors.New("process block timeout")
// BlockbeatDispatcher is a service that handles dispatching new blocks to
// `lnd`'s subsystems. During startup, subsystems that are block-driven should
// implement the `Consumer` interface and register themselves via
// `RegisterQueue`. When two subsystems are independent of each other, they
// should be registered in different queues so blocks are notified concurrently.
// Otherwise, when living in the same queue, the subsystems are notified of the
// new blocks sequentially, which means it's critical to understand the
// relationship of these systems to properly handle the order.
type BlockbeatDispatcher struct {
wg sync.WaitGroup
// notifier is used to receive new block epochs.
notifier chainntnfs.ChainNotifier
// beat is the latest blockbeat received.
beat Blockbeat
// consumerQueues is a map of consumers that will receive blocks. Its
// key is a unique counter and its value is a queue of consumers. Each
// queue is notified concurrently, and consumers in the same queue is
// notified sequentially.
consumerQueues map[uint32][]Consumer
// counter is used to assign a unique id to each queue.
counter atomic.Uint32
// quit is used to signal the BlockbeatDispatcher to stop.
quit chan struct{}
// queryHeightChan is used to receive queries on the current height of
// the dispatcher.
queryHeightChan chan *query
}
// query is used to fetch the internal state of the dispatcher.
type query struct {
// respChan is used to send back the current height back to the caller.
//
// NOTE: This channel must be buffered.
respChan chan int32
}
// newQuery creates a query to be used to fetch the internal state of the
// dispatcher.
func newQuery() *query {
return &query{
respChan: make(chan int32, 1),
}
}
// NewBlockbeatDispatcher returns a new blockbeat dispatcher instance.
func NewBlockbeatDispatcher(n chainntnfs.ChainNotifier) *BlockbeatDispatcher {
return &BlockbeatDispatcher{
notifier: n,
quit: make(chan struct{}),
consumerQueues: make(map[uint32][]Consumer),
queryHeightChan: make(chan *query, 1),
}
}
// RegisterQueue takes a list of consumers and registers them in the same
// queue.
//
// NOTE: these consumers are notified sequentially.
func (b *BlockbeatDispatcher) RegisterQueue(consumers []Consumer) {
qid := b.counter.Add(1)
b.consumerQueues[qid] = append(b.consumerQueues[qid], consumers...)
clog.Infof("Registered queue=%d with %d blockbeat consumers", qid,
len(consumers))
for _, c := range consumers {
clog.Debugf("Consumer [%s] registered in queue %d", c.Name(),
qid)
}
}
// Start starts the blockbeat dispatcher - it registers a block notification
// and monitors and dispatches new blocks in a goroutine. It will refuse to
// start if there are no registered consumers.
func (b *BlockbeatDispatcher) Start() error {
// Make sure consumers are registered.
if len(b.consumerQueues) == 0 {
return fmt.Errorf("no consumers registered")
}
// Start listening to new block epochs. We should get a notification
// with the current best block immediately.
blockEpochs, err := b.notifier.RegisterBlockEpochNtfn(nil)
if err != nil {
return fmt.Errorf("register block epoch ntfn: %w", err)
}
clog.Infof("BlockbeatDispatcher is starting with %d consumer queues",
len(b.consumerQueues))
defer clog.Debug("BlockbeatDispatcher started")
b.wg.Add(1)
go b.dispatchBlocks(blockEpochs)
return nil
}
// Stop shuts down the blockbeat dispatcher.
func (b *BlockbeatDispatcher) Stop() {
clog.Info("BlockbeatDispatcher is stopping")
defer clog.Debug("BlockbeatDispatcher stopped")
// Signal the dispatchBlocks goroutine to stop.
close(b.quit)
b.wg.Wait()
}
func (b *BlockbeatDispatcher) log() btclog.Logger {
return b.beat.logger()
}
// dispatchBlocks listens to new block epoch and dispatches it to all the
// consumers. Each queue is notified concurrently, and the consumers in the
// same queue are notified sequentially.
//
// NOTE: Must be run as a goroutine.
func (b *BlockbeatDispatcher) dispatchBlocks(
blockEpochs *chainntnfs.BlockEpochEvent) {
defer b.wg.Done()
defer blockEpochs.Cancel()
for {
select {
case blockEpoch, ok := <-blockEpochs.Epochs:
if !ok {
clog.Debugf("Block epoch channel closed")
return
}
// Log a separator so it's easier to identify when a
// new block arrives for subsystems.
clog.Debugf("%v", lnutils.NewSeparatorClosure())
clog.Debugf("Received new block %v at height %d, "+
"notifying consumers...", blockEpoch.Hash,
blockEpoch.Height)
// Record the time it takes the consumer to process
// this block.
start := time.Now()
// Update the current block epoch.
b.beat = NewBeat(*blockEpoch)
// Notify all consumers.
err := b.notifyQueues()
if err != nil {
b.log().Errorf("Notify block failed: %v", err)
}
b.log().Debugf("Notified all consumers on new block "+
"in %v", time.Since(start))
// A query has been made to fetch the current height, we now
// send the height from its current beat.
case query := <-b.queryHeightChan:
// The beat may not be set yet, e.g., during the startup
// the query is made before the block epoch being sent.
height := int32(0)
if b.beat != nil {
height = b.beat.Height()
}
query.respChan <- height
case <-b.quit:
b.log().Debugf("BlockbeatDispatcher quit signal " +
"received")
return
}
}
}
// CurrentHeight returns the current best height known to the dispatcher. 0 is
// returned if the dispatcher is shutting down.
func (b *BlockbeatDispatcher) CurrentHeight() int32 {
query := newQuery()
select {
case b.queryHeightChan <- query:
case <-b.quit:
clog.Debugf("BlockbeatDispatcher quit before query")
return 0
}
select {
case height := <-query.respChan:
clog.Debugf("Responded current height: %v", height)
return height
case <-b.quit:
clog.Debugf("BlockbeatDispatcher quit before response")
return 0
}
}
// notifyQueues notifies each queue concurrently about the latest block epoch.
func (b *BlockbeatDispatcher) notifyQueues() error {
// errChans is a map of channels that will be used to receive errors
// returned from notifying the consumers.
errChans := make(map[uint32]chan error, len(b.consumerQueues))
// Notify each queue in goroutines.
for qid, consumers := range b.consumerQueues {
b.log().Debugf("Notifying queue=%d with %d consumers", qid,
len(consumers))
// Create a signal chan.
errChan := make(chan error, 1)
errChans[qid] = errChan
// Notify each queue concurrently.
go func(qid uint32, c []Consumer, beat Blockbeat) {
// Notify each consumer in this queue sequentially.
errChan <- DispatchSequential(beat, c)
}(qid, consumers, b.beat)
}
// Wait for all consumers in each queue to finish.
for qid, errChan := range errChans {
select {
case err := <-errChan:
if err != nil {
return fmt.Errorf("queue=%d got err: %w", qid,
err)
}
b.log().Debugf("Notified queue=%d", qid)
case <-b.quit:
b.log().Debugf("BlockbeatDispatcher quit signal " +
"received, exit notifyQueues")
return nil
}
}
return nil
}
// DispatchSequential takes a list of consumers and notify them about the new
// epoch sequentially. It requires the consumer to finish processing the block
// within the specified time, otherwise a timeout error is returned.
func DispatchSequential(b Blockbeat, consumers []Consumer) error {
for _, c := range consumers {
// Send the beat to the consumer.
err := notifyAndWait(b, c, DefaultProcessBlockTimeout)
if err != nil {
b.logger().Errorf("Failed to process block: %v", err)
return err
}
}
return nil
}
// DispatchConcurrent notifies each consumer concurrently about the blockbeat.
// It requires the consumer to finish processing the block within the specified
// time, otherwise a timeout error is returned.
func DispatchConcurrent(b Blockbeat, consumers []Consumer) error {
eg := &errgroup.Group{}
// Notify each queue in goroutines.
for _, c := range consumers {
// Notify each consumer concurrently.
eg.Go(func() error {
// Send the beat to the consumer.
err := notifyAndWait(b, c, DefaultProcessBlockTimeout)
// Exit early if there's no error.
if err == nil {
return nil
}
b.logger().Errorf("Consumer=%v failed to process "+
"block: %v", c.Name(), err)
return err
})
}
// Wait for all consumers in each queue to finish.
if err := eg.Wait(); err != nil {
return err
}
return nil
}
// notifyAndWait sends the blockbeat to the specified consumer. It requires the
// consumer to finish processing the block within the specified time, otherwise
// a timeout error is returned.
func notifyAndWait(b Blockbeat, c Consumer, timeout time.Duration) error {
b.logger().Debugf("Waiting for consumer[%s] to process it", c.Name())
// Record the time it takes the consumer to process this block.
start := time.Now()
errChan := make(chan error, 1)
go func() {
errChan <- c.ProcessBlock(b)
}()
// We expect the consumer to finish processing this block under 30s,
// otherwise a timeout error is returned.
select {
case err := <-errChan:
if err == nil {
break
}
return fmt.Errorf("%s got err in ProcessBlock: %w", c.Name(),
err)
case <-time.After(timeout):
return fmt.Errorf("consumer %s: %w", c.Name(),
ErrProcessBlockTimeout)
}
b.logger().Debugf("Consumer[%s] processed block in %v", c.Name(),
time.Since(start))
return nil
}
package chainio
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// Subsystem defines the logging code for this subsystem.
const Subsystem = "CHIO"
// clog is a logger that is initialized with no output filters. This means the
// package will not perform any logging by default until the caller requests
// it.
var clog btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled by
// default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info. This
// should be used in preference to SetLogWriter if the caller is also using
// btclog.
func UseLogger(logger btclog.Logger) {
clog = logger
}
package chainio
import (
"github.com/btcsuite/btclog/v2"
"github.com/stretchr/testify/mock"
)
// MockConsumer is a mock implementation of the Consumer interface.
type MockConsumer struct {
mock.Mock
}
// Compile-time constraint to ensure MockConsumer implements Consumer.
var _ Consumer = (*MockConsumer)(nil)
// Name returns a human-readable string for this subsystem.
func (m *MockConsumer) Name() string {
args := m.Called()
return args.String(0)
}
// ProcessBlock takes a blockbeat and processes it. A receive-only error chan
// must be returned.
func (m *MockConsumer) ProcessBlock(b Blockbeat) error {
args := m.Called(b)
return args.Error(0)
}
// MockBlockbeat is a mock implementation of the Blockbeat interface.
type MockBlockbeat struct {
mock.Mock
}
// Compile-time constraint to ensure MockBlockbeat implements Blockbeat.
var _ Blockbeat = (*MockBlockbeat)(nil)
// Height returns the current block height.
func (m *MockBlockbeat) Height() int32 {
args := m.Called()
return args.Get(0).(int32)
}
// logger returns the logger for the blockbeat.
func (m *MockBlockbeat) logger() btclog.Logger {
args := m.Called()
return args.Get(0).(btclog.Logger)
}
package chainntnfs
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"github.com/btcsuite/btcd/wire"
)
// BestBlockView is an interface that allows the querying of the most
// up-to-date blockchain state with low overhead. Valid implementations of this
// interface must track the latest chain state.
type BestBlockView interface {
// BestHeight gets the most recent block height known to the view.
BestHeight() (uint32, error)
// BestBlockHeader gets the most recent block header known to the view.
BestBlockHeader() (*wire.BlockHeader, error)
}
// BestBlockTracker is a tiny subsystem that tracks the blockchain tip
// and saves the most recent tip information in memory for querying. It is a
// valid implementation of BestBlockView and additionally includes
// methods for starting and stopping the system.
type BestBlockTracker struct {
notifier ChainNotifier
blockNtfnStream *BlockEpochEvent
current atomic.Pointer[BlockEpoch]
mu sync.Mutex
quit chan struct{}
wg sync.WaitGroup
}
// This is a compile time check to ensure that BestBlockTracker implements
// BestBlockView.
var _ BestBlockView = (*BestBlockTracker)(nil)
// NewBestBlockTracker creates a new BestBlockTracker that isn't running yet.
// It will not provide up to date information unless it has been started. The
// ChainNotifier parameter must also be started prior to starting the
// BestBlockTracker.
func NewBestBlockTracker(chainNotifier ChainNotifier) *BestBlockTracker {
return &BestBlockTracker{
notifier: chainNotifier,
blockNtfnStream: nil,
quit: make(chan struct{}),
}
}
// BestHeight gets the most recent block height known to the
// BestBlockTracker.
func (t *BestBlockTracker) BestHeight() (uint32, error) {
epoch := t.current.Load()
if epoch == nil {
return 0, errors.New("best block height not yet known")
}
return uint32(epoch.Height), nil
}
// BestBlockHeader gets the most recent block header known to the
// BestBlockTracker.
func (t *BestBlockTracker) BestBlockHeader() (*wire.BlockHeader, error) {
epoch := t.current.Load()
if epoch == nil {
return nil, errors.New("best block header not yet known")
}
return epoch.BlockHeader, nil
}
// updateLoop is a helper that subscribes to the underlying BlockEpochEvent
// stream and updates the internal values to match the new BlockEpochs that
// are discovered.
//
// MUST be run as a goroutine.
func (t *BestBlockTracker) updateLoop() {
defer t.wg.Done()
for {
select {
case epoch, ok := <-t.blockNtfnStream.Epochs:
if !ok {
Log.Error("dead epoch stream in " +
"BestBlockTracker")
return
}
t.current.Store(epoch)
case <-t.quit:
t.current.Store(nil)
return
}
}
}
// Start starts the BestBlockTracker. It is an error to start it if it
// is already started.
func (t *BestBlockTracker) Start() error {
t.mu.Lock()
defer t.mu.Unlock()
if t.blockNtfnStream != nil {
return fmt.Errorf("BestBlockTracker is already started")
}
var err error
t.blockNtfnStream, err = t.notifier.RegisterBlockEpochNtfn(nil)
if err != nil {
return err
}
t.wg.Add(1)
go t.updateLoop()
return nil
}
// Stop stops the BestBlockTracker. It is an error to stop it if it has
// not been started or if it has already been stopped.
func (t *BestBlockTracker) Stop() error {
t.mu.Lock()
defer t.mu.Unlock()
if t.blockNtfnStream == nil {
return fmt.Errorf("BestBlockTracker is not running")
}
close(t.quit)
t.wg.Wait()
t.blockNtfnStream.Cancel()
t.blockNtfnStream = nil
return nil
}
package bitcoindnotify
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/chain"
"github.com/lightningnetwork/lnd/blockcache"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/queue"
)
const (
// notifierType uniquely identifies a concrete implementation of the
// ChainNotifier interface that makes use of the bitcoind ZMQ interface.
notifierTypeZMQ = "bitcoind"
// notifierTypeRPCPolling uniquely identifies a concrete implementation
// of the ChainNotifier interface that makes use of the bitcoind RPC
// interface.
notifierTypeRPCPolling = "bitcoind-rpc-polling"
)
// TODO(roasbeef): generalize struct below:
// * move chans to config
// * extract common code
// * allow outside callers to handle send conditions
// BitcoindNotifier implements the ChainNotifier interface using a bitcoind
// chain client. Multiple concurrent clients are supported. All notifications
// are achieved via non-blocking sends on client channels.
type BitcoindNotifier struct {
epochClientCounter uint64 // To be used atomically.
start sync.Once
active int32 // To be used atomically.
stopped int32 // To be used atomically.
chainConn *chain.BitcoindClient
chainParams *chaincfg.Params
notificationCancels chan interface{}
notificationRegistry chan interface{}
txNotifier *chainntnfs.TxNotifier
blockEpochClients map[uint64]*blockEpochRegistration
bestBlock chainntnfs.BlockEpoch
// blockCache is a LRU block cache.
blockCache *blockcache.BlockCache
// spendHintCache is a cache used to query and update the latest height
// hints for an outpoint. Each height hint represents the earliest
// height at which the outpoint could have been spent within the chain.
spendHintCache chainntnfs.SpendHintCache
// confirmHintCache is a cache used to query the latest height hints for
// a transaction. Each height hint represents the earliest height at
// which the transaction could have confirmed within the chain.
confirmHintCache chainntnfs.ConfirmHintCache
// memNotifier notifies clients of events related to the mempool.
memNotifier *chainntnfs.MempoolNotifier
wg sync.WaitGroup
quit chan struct{}
}
// Ensure BitcoindNotifier implements the ChainNotifier interface at compile
// time.
var _ chainntnfs.ChainNotifier = (*BitcoindNotifier)(nil)
// Ensure BitcoindNotifier implements the MempoolWatcher interface at compile
// time.
var _ chainntnfs.MempoolWatcher = (*BitcoindNotifier)(nil)
// New returns a new BitcoindNotifier instance. This function assumes the
// bitcoind node detailed in the passed configuration is already running, and
// willing to accept RPC requests and new zmq clients.
func New(chainConn *chain.BitcoindConn, chainParams *chaincfg.Params,
spendHintCache chainntnfs.SpendHintCache,
confirmHintCache chainntnfs.ConfirmHintCache,
blockCache *blockcache.BlockCache) *BitcoindNotifier {
notifier := &BitcoindNotifier{
chainParams: chainParams,
notificationCancels: make(chan interface{}),
notificationRegistry: make(chan interface{}),
blockEpochClients: make(map[uint64]*blockEpochRegistration),
spendHintCache: spendHintCache,
confirmHintCache: confirmHintCache,
blockCache: blockCache,
memNotifier: chainntnfs.NewMempoolNotifier(),
quit: make(chan struct{}),
}
notifier.chainConn = chainConn.NewBitcoindClient()
return notifier
}
// Start connects to the running bitcoind node over websockets, registers for
// block notifications, and finally launches all related helper goroutines.
func (b *BitcoindNotifier) Start() error {
var startErr error
b.start.Do(func() {
startErr = b.startNotifier()
})
return startErr
}
// Stop shutsdown the BitcoindNotifier.
func (b *BitcoindNotifier) Stop() error {
// Already shutting down?
if atomic.AddInt32(&b.stopped, 1) != 1 {
return nil
}
chainntnfs.Log.Info("bitcoind notifier shutting down...")
defer chainntnfs.Log.Debug("bitcoind notifier shutdown complete")
// Shutdown the rpc client, this gracefully disconnects from bitcoind,
// and cleans up all related resources.
b.chainConn.Stop()
b.chainConn.WaitForShutdown()
close(b.quit)
b.wg.Wait()
// Notify all pending clients of our shutdown by closing the related
// notification channels.
for _, epochClient := range b.blockEpochClients {
close(epochClient.cancelChan)
epochClient.wg.Wait()
close(epochClient.epochChan)
}
// The txNotifier is only initialized in the start method therefore we
// need to make sure we don't access a nil pointer here.
if b.txNotifier != nil {
b.txNotifier.TearDown()
}
// Stop the mempool notifier.
b.memNotifier.TearDown()
return nil
}
// Started returns true if this instance has been started, and false otherwise.
func (b *BitcoindNotifier) Started() bool {
return atomic.LoadInt32(&b.active) != 0
}
func (b *BitcoindNotifier) startNotifier() error {
chainntnfs.Log.Infof("bitcoind notifier starting...")
// Connect to bitcoind, and register for notifications on connected,
// and disconnected blocks.
if err := b.chainConn.Start(); err != nil {
return err
}
if err := b.chainConn.NotifyBlocks(); err != nil {
return err
}
currentHash, currentHeight, err := b.chainConn.GetBestBlock()
if err != nil {
return err
}
blockHeader, err := b.chainConn.GetBlockHeader(currentHash)
if err != nil {
return err
}
b.txNotifier = chainntnfs.NewTxNotifier(
uint32(currentHeight), chainntnfs.ReorgSafetyLimit,
b.confirmHintCache, b.spendHintCache,
)
b.bestBlock = chainntnfs.BlockEpoch{
Height: currentHeight,
Hash: currentHash,
BlockHeader: blockHeader,
}
b.wg.Add(1)
go b.notificationDispatcher()
// Set the active flag now that we've completed the full
// startup.
atomic.StoreInt32(&b.active, 1)
chainntnfs.Log.Debugf("bitcoind notifier started")
return nil
}
// notificationDispatcher is the primary goroutine which handles client
// notification registrations, as well as notification dispatches.
func (b *BitcoindNotifier) notificationDispatcher() {
defer b.wg.Done()
out:
for {
select {
case cancelMsg := <-b.notificationCancels:
switch msg := cancelMsg.(type) {
case *epochCancel:
chainntnfs.Log.Infof("Cancelling epoch "+
"notification, epoch_id=%v", msg.epochID)
// First, we'll lookup the original
// registration in order to stop the active
// queue goroutine.
reg := b.blockEpochClients[msg.epochID]
reg.epochQueue.Stop()
// Next, close the cancel channel for this
// specific client, and wait for the client to
// exit.
close(b.blockEpochClients[msg.epochID].cancelChan)
b.blockEpochClients[msg.epochID].wg.Wait()
// Once the client has exited, we can then
// safely close the channel used to send epoch
// notifications, in order to notify any
// listeners that the intent has been
// canceled.
close(b.blockEpochClients[msg.epochID].epochChan)
delete(b.blockEpochClients, msg.epochID)
}
case registerMsg := <-b.notificationRegistry:
switch msg := registerMsg.(type) {
case *chainntnfs.HistoricalConfDispatch:
// Look up whether the transaction is already
// included in the active chain. We'll do this
// in a goroutine to prevent blocking
// potentially long rescans.
//
// TODO(wilmer): add retry logic if rescan fails?
b.wg.Add(1)
//nolint:ll
go func(msg *chainntnfs.HistoricalConfDispatch) {
defer b.wg.Done()
confDetails, _, err := b.historicalConfDetails(
msg.ConfRequest,
msg.StartHeight, msg.EndHeight,
)
if err != nil {
chainntnfs.Log.Errorf("Rescan to "+
"determine the conf "+
"details of %v within "+
"range %d-%d failed: %v",
msg.ConfRequest,
msg.StartHeight,
msg.EndHeight, err)
return
}
// If the historical dispatch finished
// without error, we will invoke
// UpdateConfDetails even if none were
// found. This allows the notifier to
// begin safely updating the height hint
// cache at tip, since any pending
// rescans have now completed.
err = b.txNotifier.UpdateConfDetails(
msg.ConfRequest, confDetails,
)
if err != nil {
chainntnfs.Log.Errorf("Unable "+
"to update conf "+
"details of %v: %v",
msg.ConfRequest, err)
}
}(msg)
case *chainntnfs.HistoricalSpendDispatch:
// In order to ensure we don't block the caller
// on what may be a long rescan, we'll launch a
// goroutine to do so in the background.
//
// TODO(wilmer): add retry logic if rescan fails?
b.wg.Add(1)
//nolint:ll
go func(msg *chainntnfs.HistoricalSpendDispatch) {
defer b.wg.Done()
spendDetails, err := b.historicalSpendDetails(
msg.SpendRequest,
msg.StartHeight, msg.EndHeight,
)
if err != nil {
chainntnfs.Log.Errorf("Rescan to "+
"determine the spend "+
"details of %v within "+
"range %d-%d failed: %v",
msg.SpendRequest,
msg.StartHeight,
msg.EndHeight, err)
return
}
chainntnfs.Log.Infof("Historical "+
"spend dispatch finished "+
"for request %v (start=%v "+
"end=%v) with details: %v",
msg.SpendRequest,
msg.StartHeight, msg.EndHeight,
spendDetails)
// If the historical dispatch finished
// without error, we will invoke
// UpdateSpendDetails even if none were
// found. This allows the notifier to
// begin safely updating the height hint
// cache at tip, since any pending
// rescans have now completed.
err = b.txNotifier.UpdateSpendDetails(
msg.SpendRequest, spendDetails,
)
if err != nil {
chainntnfs.Log.Errorf("Unable "+
"to update spend "+
"details of %v: %v",
msg.SpendRequest, err)
}
}(msg)
case *blockEpochRegistration:
chainntnfs.Log.Infof("New block epoch subscription")
b.blockEpochClients[msg.epochID] = msg
// If the client did not provide their best
// known block, then we'll immediately dispatch
// a notification for the current tip.
if msg.bestBlock == nil {
b.notifyBlockEpochClient(
msg, b.bestBlock.Height,
b.bestBlock.Hash,
b.bestBlock.BlockHeader,
)
msg.errorChan <- nil
continue
}
// Otherwise, we'll attempt to deliver the
// backlog of notifications from their best
// known block.
missedBlocks, err := chainntnfs.GetClientMissedBlocks(
b.chainConn, msg.bestBlock,
b.bestBlock.Height, true,
)
if err != nil {
msg.errorChan <- err
continue
}
for _, block := range missedBlocks {
b.notifyBlockEpochClient(
msg, block.Height, block.Hash,
block.BlockHeader,
)
}
msg.errorChan <- nil
}
case ntfn := <-b.chainConn.Notifications():
switch item := ntfn.(type) {
case chain.BlockConnected:
blockHeader, err :=
b.chainConn.GetBlockHeader(&item.Hash)
if err != nil {
chainntnfs.Log.Errorf("Unable to fetch "+
"block header: %v", err)
continue
}
if blockHeader.PrevBlock != *b.bestBlock.Hash {
// Handle the case where the notifier
// missed some blocks from its chain
// backend.
chainntnfs.Log.Infof("Missed blocks, " +
"attempting to catch up")
newBestBlock, missedBlocks, err :=
chainntnfs.HandleMissedBlocks(
b.chainConn,
b.txNotifier,
b.bestBlock, item.Height,
true,
)
if err != nil {
// Set the bestBlock here in case
// a catch up partially completed.
b.bestBlock = newBestBlock
chainntnfs.Log.Error(err)
continue
}
for _, block := range missedBlocks {
err := b.handleBlockConnected(block)
if err != nil {
chainntnfs.Log.Error(err)
continue out
}
}
}
newBlock := chainntnfs.BlockEpoch{
Height: item.Height,
Hash: &item.Hash,
BlockHeader: blockHeader,
}
if err := b.handleBlockConnected(newBlock); err != nil {
chainntnfs.Log.Error(err)
}
continue
case chain.BlockDisconnected:
if item.Height != b.bestBlock.Height {
chainntnfs.Log.Infof("Missed disconnected" +
"blocks, attempting to catch up")
}
newBestBlock, err := chainntnfs.RewindChain(
b.chainConn, b.txNotifier,
b.bestBlock, item.Height-1,
)
if err != nil {
chainntnfs.Log.Errorf("Unable to rewind chain "+
"from height %d to height %d: %v",
b.bestBlock.Height, item.Height-1, err)
}
// Set the bestBlock here in case a chain
// rewind partially completed.
b.bestBlock = newBestBlock
case chain.RelevantTx:
tx := btcutil.NewTx(&item.TxRecord.MsgTx)
// Init values.
isMempool := false
height := uint32(0)
// Unwrap values.
if item.Block == nil {
isMempool = true
} else {
height = uint32(item.Block.Height)
}
// Handle the transaction.
b.handleRelevantTx(tx, isMempool, height)
}
case <-b.quit:
break out
}
}
}
// handleRelevantTx handles a new transaction that has been seen either in a
// block or in the mempool. If in mempool, it will ask the mempool notifier to
// handle it. If in a block, it will ask the txNotifier to handle it, and
// cancel any relevant subscriptions made in the mempool.
func (b *BitcoindNotifier) handleRelevantTx(tx *btcutil.Tx,
mempool bool, height uint32) {
// If this is a mempool spend, we'll ask the mempool notifier to handle
// it.
if mempool {
err := b.memNotifier.ProcessRelevantSpendTx(tx)
if err != nil {
chainntnfs.Log.Errorf("Unable to process transaction "+
"%v: %v", tx.Hash(), err)
}
return
}
// Otherwise this is a confirmed spend, and we'll ask the tx notifier
// to handle it.
err := b.txNotifier.ProcessRelevantSpendTx(tx, height)
if err != nil {
chainntnfs.Log.Errorf("Unable to process transaction %v: %v",
tx.Hash(), err)
return
}
// Once the tx is processed, we will ask the memNotifier to unsubscribe
// the input.
//
// NOTE(yy): we could build it into txNotifier.ProcessRelevantSpendTx,
// but choose to implement it here so we can easily decouple the two
// notifiers in the future.
b.memNotifier.UnsubsribeConfirmedSpentTx(tx)
}
// historicalConfDetails looks up whether a confirmation request (txid/output
// script) has already been included in a block in the active chain and, if so,
// returns details about said block.
func (b *BitcoindNotifier) historicalConfDetails(confRequest chainntnfs.ConfRequest,
startHeight, endHeight uint32) (*chainntnfs.TxConfirmation,
chainntnfs.TxConfStatus, error) {
// If a txid was not provided, then we should dispatch upon seeing the
// script on-chain, so we'll short-circuit straight to scanning manually
// as there doesn't exist a script index to query.
if confRequest.TxID == chainntnfs.ZeroHash {
return b.confDetailsManually(
confRequest, startHeight, endHeight,
)
}
// Otherwise, we'll dispatch upon seeing a transaction on-chain with the
// given hash.
//
// We'll first attempt to retrieve the transaction using the node's
// txindex.
txNotFoundErr := "No such mempool or blockchain transaction"
txConf, txStatus, err := chainntnfs.ConfDetailsFromTxIndex(
b.chainConn, confRequest, txNotFoundErr,
)
// We'll then check the status of the transaction lookup returned to
// determine whether we should proceed with any fallback methods.
switch {
// We failed querying the index for the transaction, fall back to
// scanning manually.
case err != nil:
chainntnfs.Log.Debugf("Failed getting conf details from "+
"index (%v), scanning manually", err)
return b.confDetailsManually(confRequest, startHeight, endHeight)
// The transaction was found within the node's mempool.
case txStatus == chainntnfs.TxFoundMempool:
// The transaction was found within the node's txindex.
case txStatus == chainntnfs.TxFoundIndex:
// The transaction was not found within the node's mempool or txindex.
case txStatus == chainntnfs.TxNotFoundIndex:
// Unexpected txStatus returned.
default:
return nil, txStatus,
fmt.Errorf("Got unexpected txConfStatus: %v", txStatus)
}
return txConf, txStatus, nil
}
// confDetailsManually looks up whether a transaction/output script has already
// been included in a block in the active chain by scanning the chain's blocks
// within the given range. If the transaction/output script is found, its
// confirmation details are returned. Otherwise, nil is returned.
func (b *BitcoindNotifier) confDetailsManually(confRequest chainntnfs.ConfRequest,
heightHint, currentHeight uint32) (*chainntnfs.TxConfirmation,
chainntnfs.TxConfStatus, error) {
// Begin scanning blocks at every height to determine where the
// transaction was included in.
for height := currentHeight; height >= heightHint && height > 0; height-- {
// Ensure we haven't been requested to shut down before
// processing the next height.
select {
case <-b.quit:
return nil, chainntnfs.TxNotFoundManually,
chainntnfs.ErrChainNotifierShuttingDown
default:
}
blockHash, err := b.chainConn.GetBlockHash(int64(height))
if err != nil {
return nil, chainntnfs.TxNotFoundManually,
fmt.Errorf("unable to get hash from block "+
"with height %d", height)
}
block, err := b.GetBlock(blockHash)
if err != nil {
return nil, chainntnfs.TxNotFoundManually,
fmt.Errorf("unable to get block with hash "+
"%v: %v", blockHash, err)
}
// For every transaction in the block, check which one matches
// our request. If we find one that does, we can dispatch its
// confirmation details.
for txIndex, tx := range block.Transactions {
if !confRequest.MatchesTx(tx) {
continue
}
return &chainntnfs.TxConfirmation{
Tx: tx.Copy(),
BlockHash: blockHash,
BlockHeight: height,
TxIndex: uint32(txIndex),
Block: block,
}, chainntnfs.TxFoundManually, nil
}
}
// If we reach here, then we were not able to find the transaction
// within a block, so we avoid returning an error.
return nil, chainntnfs.TxNotFoundManually, nil
}
// handleBlockConnected applies a chain update for a new block. Any watched
// transactions included this block will processed to either send notifications
// now or after numConfirmations confs.
func (b *BitcoindNotifier) handleBlockConnected(block chainntnfs.BlockEpoch) error {
// First, we'll fetch the raw block as we'll need to gather all the
// transactions to determine whether any are relevant to our registered
// clients.
rawBlock, err := b.GetBlock(block.Hash)
if err != nil {
return fmt.Errorf("unable to get block: %w", err)
}
utilBlock := btcutil.NewBlock(rawBlock)
// We'll then extend the txNotifier's height with the information of
// this new block, which will handle all of the notification logic for
// us.
err = b.txNotifier.ConnectTip(utilBlock, uint32(block.Height))
if err != nil {
return fmt.Errorf("unable to connect tip: %w", err)
}
chainntnfs.Log.Infof("New block: height=%v, sha=%v", block.Height,
block.Hash)
// Now that we've guaranteed the new block extends the txNotifier's
// current tip, we'll proceed to dispatch notifications to all of our
// registered clients whom have had notifications fulfilled. Before
// doing so, we'll make sure update our in memory state in order to
// satisfy any client requests based upon the new block.
b.bestBlock = block
err = b.txNotifier.NotifyHeight(uint32(block.Height))
if err != nil {
return fmt.Errorf("unable to notify height: %w", err)
}
b.notifyBlockEpochs(block.Height, block.Hash, block.BlockHeader)
return nil
}
// notifyBlockEpochs notifies all registered block epoch clients of the newly
// connected block to the main chain.
func (b *BitcoindNotifier) notifyBlockEpochs(newHeight int32, newSha *chainhash.Hash,
blockHeader *wire.BlockHeader) {
for _, client := range b.blockEpochClients {
b.notifyBlockEpochClient(client, newHeight, newSha, blockHeader)
}
}
// notifyBlockEpochClient sends a registered block epoch client a notification
// about a specific block.
func (b *BitcoindNotifier) notifyBlockEpochClient(epochClient *blockEpochRegistration,
height int32, sha *chainhash.Hash, header *wire.BlockHeader) {
epoch := &chainntnfs.BlockEpoch{
Height: height,
Hash: sha,
BlockHeader: header,
}
select {
case epochClient.epochQueue.ChanIn() <- epoch:
case <-epochClient.cancelChan:
case <-b.quit:
}
}
// RegisterSpendNtfn registers an intent to be notified once the target
// outpoint/output script has been spent by a transaction on-chain. When
// intending to be notified of the spend of an output script, a nil outpoint
// must be used. The heightHint should represent the earliest height in the
// chain of the transaction that spent the outpoint/output script.
//
// Once a spend of has been detected, the details of the spending event will be
// sent across the 'Spend' channel.
func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) {
// Register the conf notification with the TxNotifier. A non-nil value
// for `dispatch` will be returned if we are required to perform a
// manual scan for the confirmation. Otherwise the notifier will begin
// watching at tip for the transaction to confirm.
ntfn, err := b.txNotifier.RegisterSpend(outpoint, pkScript, heightHint)
if err != nil {
return nil, err
}
// We'll then request the backend to notify us when it has detected the
// outpoint/output script as spent.
//
// TODO(wilmer): use LoadFilter API instead.
if outpoint == nil || *outpoint == chainntnfs.ZeroOutPoint {
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
pkScript, b.chainParams,
)
if err != nil {
return nil, fmt.Errorf("unable to parse script: %w",
err)
}
if err := b.chainConn.NotifyReceived(addrs); err != nil {
return nil, err
}
} else {
ops := []*wire.OutPoint{outpoint}
if err := b.chainConn.NotifySpent(ops); err != nil {
return nil, err
}
}
// If the txNotifier didn't return any details to perform a historical
// scan of the chain, then we can return early as there's nothing left
// for us to do.
if ntfn.HistoricalDispatch == nil {
return ntfn.Event, nil
}
// Otherwise, we'll need to dispatch a historical rescan to determine if
// the outpoint was already spent at a previous height.
//
// We'll short-circuit the path when dispatching the spend of a script,
// rather than an outpoint, as there aren't any additional checks we can
// make for scripts.
if ntfn.HistoricalDispatch.OutPoint == chainntnfs.ZeroOutPoint {
select {
case b.notificationRegistry <- ntfn.HistoricalDispatch:
case <-b.quit:
return nil, chainntnfs.ErrChainNotifierShuttingDown
}
return ntfn.Event, nil
}
// When dispatching spends of outpoints, there are a number of checks we
// can make to start our rescan from a better height or completely avoid
// it.
//
// We'll start by checking the backend's UTXO set to determine whether
// the outpoint has been spent. If it hasn't, we can return to the
// caller as well.
txOut, err := b.chainConn.GetTxOut(&outpoint.Hash, outpoint.Index, true)
if err != nil {
return nil, err
}
if txOut != nil {
// We'll let the txNotifier know the outpoint is still unspent
// in order to begin updating its spend hint.
err := b.txNotifier.UpdateSpendDetails(
ntfn.HistoricalDispatch.SpendRequest, nil,
)
if err != nil {
return nil, err
}
return ntfn.Event, nil
}
// Since the outpoint was spent, as it no longer exists within the UTXO
// set, we'll determine when it happened by scanning the chain.
//
// As a minimal optimization, we'll query the backend's transaction
// index (if enabled) to determine if we have a better rescan starting
// height. We can do this as the GetRawTransaction call will return the
// hash of the block it was included in within the chain.
tx, err := b.chainConn.GetRawTransactionVerbose(&outpoint.Hash)
if err != nil {
// Avoid returning an error if the transaction was not found to
// proceed with fallback methods.
jsonErr, ok := err.(*btcjson.RPCError)
if !ok || jsonErr.Code != btcjson.ErrRPCNoTxInfo {
return nil, fmt.Errorf("unable to query for txid "+
"%v: %w", outpoint.Hash, err)
}
}
// If the transaction index was enabled, we'll use the block's hash to
// retrieve its height and check whether it provides a better starting
// point for our rescan.
if tx != nil {
// If the transaction containing the outpoint hasn't confirmed
// on-chain, then there's no need to perform a rescan.
if tx.BlockHash == "" {
return ntfn.Event, nil
}
blockHash, err := chainhash.NewHashFromStr(tx.BlockHash)
if err != nil {
return nil, err
}
blockHeight, err := b.chainConn.GetBlockHeight(blockHash)
if err != nil {
return nil, err
}
if uint32(blockHeight) > ntfn.HistoricalDispatch.StartHeight {
ntfn.HistoricalDispatch.StartHeight = uint32(blockHeight)
}
}
// Now that we've determined the starting point of our rescan, we can
// dispatch it and return.
select {
case b.notificationRegistry <- ntfn.HistoricalDispatch:
case <-b.quit:
return nil, chainntnfs.ErrChainNotifierShuttingDown
}
return ntfn.Event, nil
}
// historicalSpendDetails attempts to manually scan the chain within the given
// height range for a transaction that spends the given outpoint/output script.
// If one is found, the spend details are assembled and returned to the caller.
// If the spend is not found, a nil spend detail will be returned.
func (b *BitcoindNotifier) historicalSpendDetails(
spendRequest chainntnfs.SpendRequest, startHeight, endHeight uint32) (
*chainntnfs.SpendDetail, error) {
// Begin scanning blocks at every height to determine if the outpoint
// was spent.
for height := endHeight; height >= startHeight && height > 0; height-- {
// Ensure we haven't been requested to shut down before
// processing the next height.
select {
case <-b.quit:
return nil, chainntnfs.ErrChainNotifierShuttingDown
default:
}
// First, we'll fetch the block for the current height.
blockHash, err := b.chainConn.GetBlockHash(int64(height))
if err != nil {
return nil, fmt.Errorf("unable to retrieve hash for "+
"block with height %d: %v", height, err)
}
block, err := b.GetBlock(blockHash)
if err != nil {
return nil, fmt.Errorf("unable to retrieve block "+
"with hash %v: %v", blockHash, err)
}
// Then, we'll manually go over every input in every transaction
// in it and determine whether it spends the request in
// question. If we find one, we'll dispatch the spend details.
for _, tx := range block.Transactions {
matches, inputIdx, err := spendRequest.MatchesTx(tx)
if err != nil {
return nil, err
}
if !matches {
continue
}
txCopy := tx.Copy()
txHash := txCopy.TxHash()
spendOutPoint := &txCopy.TxIn[inputIdx].PreviousOutPoint
return &chainntnfs.SpendDetail{
SpentOutPoint: spendOutPoint,
SpenderTxHash: &txHash,
SpendingTx: txCopy,
SpenderInputIndex: inputIdx,
SpendingHeight: int32(height),
}, nil
}
}
return nil, nil
}
// RegisterConfirmationsNtfn registers an intent to be notified once the target
// txid/output script has reached numConfs confirmations on-chain. When
// intending to be notified of the confirmation of an output script, a nil txid
// must be used. The heightHint should represent the earliest height at which
// the txid/output script could have been included in the chain.
//
// Progress on the number of confirmations left can be read from the 'Updates'
// channel. Once it has reached all of its confirmations, a notification will be
// sent across the 'Confirmed' channel.
func (b *BitcoindNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
pkScript []byte, numConfs, heightHint uint32,
opts ...chainntnfs.NotifierOption) (*chainntnfs.ConfirmationEvent, error) {
// Register the conf notification with the TxNotifier. A non-nil value
// for `dispatch` will be returned if we are required to perform a
// manual scan for the confirmation. Otherwise the notifier will begin
// watching at tip for the transaction to confirm.
ntfn, err := b.txNotifier.RegisterConf(
txid, pkScript, numConfs, heightHint, opts...,
)
if err != nil {
return nil, err
}
if ntfn.HistoricalDispatch == nil {
return ntfn.Event, nil
}
select {
case b.notificationRegistry <- ntfn.HistoricalDispatch:
return ntfn.Event, nil
case <-b.quit:
return nil, chainntnfs.ErrChainNotifierShuttingDown
}
}
// blockEpochRegistration represents a client's intent to receive a
// notification with each newly connected block.
type blockEpochRegistration struct {
epochID uint64
epochChan chan *chainntnfs.BlockEpoch
epochQueue *queue.ConcurrentQueue
bestBlock *chainntnfs.BlockEpoch
errorChan chan error
cancelChan chan struct{}
wg sync.WaitGroup
}
// epochCancel is a message sent to the BitcoindNotifier when a client wishes
// to cancel an outstanding epoch notification that has yet to be dispatched.
type epochCancel struct {
epochID uint64
}
// RegisterBlockEpochNtfn returns a BlockEpochEvent which subscribes the
// caller to receive notifications, of each new block connected to the main
// chain. Clients have the option of passing in their best known block, which
// the notifier uses to check if they are behind on blocks and catch them up. If
// they do not provide one, then a notification will be dispatched immediately
// for the current tip of the chain upon a successful registration.
func (b *BitcoindNotifier) RegisterBlockEpochNtfn(
bestBlock *chainntnfs.BlockEpoch) (*chainntnfs.BlockEpochEvent, error) {
reg := &blockEpochRegistration{
epochQueue: queue.NewConcurrentQueue(20),
epochChan: make(chan *chainntnfs.BlockEpoch, 20),
cancelChan: make(chan struct{}),
epochID: atomic.AddUint64(&b.epochClientCounter, 1),
bestBlock: bestBlock,
errorChan: make(chan error, 1),
}
reg.epochQueue.Start()
// Before we send the request to the main goroutine, we'll launch a new
// goroutine to proxy items added to our queue to the client itself.
// This ensures that all notifications are received *in order*.
reg.wg.Add(1)
go func() {
defer reg.wg.Done()
for {
select {
case ntfn := <-reg.epochQueue.ChanOut():
blockNtfn := ntfn.(*chainntnfs.BlockEpoch)
select {
case reg.epochChan <- blockNtfn:
case <-reg.cancelChan:
return
case <-b.quit:
return
}
case <-reg.cancelChan:
return
case <-b.quit:
return
}
}
}()
select {
case <-b.quit:
// As we're exiting before the registration could be sent,
// we'll stop the queue now ourselves.
reg.epochQueue.Stop()
return nil, errors.New("chainntnfs: system interrupt while " +
"attempting to register for block epoch notification.")
case b.notificationRegistry <- reg:
return &chainntnfs.BlockEpochEvent{
Epochs: reg.epochChan,
Cancel: func() {
cancel := &epochCancel{
epochID: reg.epochID,
}
// Submit epoch cancellation to notification dispatcher.
select {
case b.notificationCancels <- cancel:
// Cancellation is being handled, drain the epoch channel until it is
// closed before yielding to caller.
for {
select {
case _, ok := <-reg.epochChan:
if !ok {
return
}
case <-b.quit:
return
}
}
case <-b.quit:
}
},
}, nil
}
}
// GetBlock is used to retrieve the block with the given hash. This function
// wraps the blockCache's GetBlock function.
func (b *BitcoindNotifier) GetBlock(hash *chainhash.Hash) (*wire.MsgBlock,
error) {
return b.blockCache.GetBlock(hash, b.chainConn.GetBlock)
}
// SubscribeMempoolSpent allows the caller to register a subscription to watch
// for a spend of an outpoint in the mempool.The event will be dispatched once
// the outpoint is spent in the mempool.
//
// NOTE: part of the MempoolWatcher interface.
func (b *BitcoindNotifier) SubscribeMempoolSpent(
outpoint wire.OutPoint) (*chainntnfs.MempoolSpendEvent, error) {
event := b.memNotifier.SubscribeInput(outpoint)
ops := []*wire.OutPoint{&outpoint}
return event, b.chainConn.NotifySpent(ops)
}
// CancelMempoolSpendEvent allows the caller to cancel a subscription to watch
// for a spend of an outpoint in the mempool.
//
// NOTE: part of the MempoolWatcher interface.
func (b *BitcoindNotifier) CancelMempoolSpendEvent(
sub *chainntnfs.MempoolSpendEvent) {
b.memNotifier.UnsubscribeEvent(sub)
}
// LookupInputMempoolSpend takes an outpoint and queries the mempool to find
// its spending tx. Returns the tx if found, otherwise fn.None.
//
// NOTE: part of the MempoolWatcher interface.
func (b *BitcoindNotifier) LookupInputMempoolSpend(
op wire.OutPoint) fn.Option[wire.MsgTx] {
// Find the spending txid.
txid, found := b.chainConn.LookupInputMempoolSpend(op)
if !found {
return fn.None[wire.MsgTx]()
}
// Query the spending tx using the id.
tx, err := b.chainConn.GetRawTransaction(&txid)
if err != nil {
// TODO(yy): enable logging errors in this package.
return fn.None[wire.MsgTx]()
}
return fn.Some(*tx.MsgTx().Copy())
}
package bitcoindnotify
import (
"errors"
"fmt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcwallet/chain"
"github.com/lightningnetwork/lnd/blockcache"
"github.com/lightningnetwork/lnd/chainntnfs"
)
// createNewNotifier creates a new instance of the ChainNotifier interface
// implemented by BitcoindNotifier.
func createNewNotifier(args ...interface{}) (chainntnfs.ChainNotifier, error) {
if len(args) != 5 {
return nil, fmt.Errorf("incorrect number of arguments to "+
".New(...), expected 5, instead passed %v", len(args))
}
chainConn, ok := args[0].(*chain.BitcoindConn)
if !ok {
return nil, errors.New("first argument to bitcoindnotify.New " +
"is incorrect, expected a *chain.BitcoindConn")
}
chainParams, ok := args[1].(*chaincfg.Params)
if !ok {
return nil, errors.New("second argument to bitcoindnotify.New " +
"is incorrect, expected a *chaincfg.Params")
}
spendHintCache, ok := args[2].(chainntnfs.SpendHintCache)
if !ok {
return nil, errors.New("third argument to bitcoindnotify.New " +
"is incorrect, expected a chainntnfs.SpendHintCache")
}
confirmHintCache, ok := args[3].(chainntnfs.ConfirmHintCache)
if !ok {
return nil, errors.New("fourth argument to bitcoindnotify.New " +
"is incorrect, expected a chainntnfs.ConfirmHintCache")
}
blockCache, ok := args[4].(*blockcache.BlockCache)
if !ok {
return nil, errors.New("fifth argument to bitcoindnotify.New " +
"is incorrect, expected a *blockcache.BlockCache")
}
return New(chainConn, chainParams, spendHintCache,
confirmHintCache, blockCache), nil
}
// init registers a driver for the BtcdNotifier concrete implementation of the
// chainntnfs.ChainNotifier interface.
func init() {
// Register the driver.
notifierZMQ := &chainntnfs.NotifierDriver{
NotifierType: notifierTypeZMQ,
New: createNewNotifier,
}
if err := chainntnfs.RegisterNotifier(notifierZMQ); err != nil {
panic(fmt.Sprintf("failed to register notifier driver '%s': %v",
notifierTypeZMQ, err))
}
notifierRPC := &chainntnfs.NotifierDriver{
NotifierType: notifierTypeRPCPolling,
New: createNewNotifier,
}
if err := chainntnfs.RegisterNotifier(notifierRPC); err != nil {
panic(fmt.Sprintf("failed to register notifier driver '%s': %v",
notifierTypeRPCPolling, err))
}
}
package btcdnotify
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/chain"
"github.com/lightningnetwork/lnd/blockcache"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/queue"
)
const (
// notifierType uniquely identifies this concrete implementation of the
// ChainNotifier interface.
notifierType = "btcd"
)
// chainUpdate encapsulates an update to the current main chain. This struct is
// used as an element within an unbounded queue in order to avoid blocking the
// main rpc dispatch rule.
type chainUpdate struct {
blockHash *chainhash.Hash
blockHeight int32
// connected is true if this update is a new block and false if it is a
// disconnected block.
connect bool
}
// txUpdate encapsulates a transaction related notification sent from btcd to
// the registered RPC client. This struct is used as an element within an
// unbounded queue in order to avoid blocking the main rpc dispatch rule.
type txUpdate struct {
tx *btcutil.Tx
details *btcjson.BlockDetails
}
// TODO(roasbeef): generalize struct below:
// * move chans to config, allow outside callers to handle send conditions
// BtcdNotifier implements the ChainNotifier interface using btcd's websockets
// notifications. Multiple concurrent clients are supported. All notifications
// are achieved via non-blocking sends on client channels.
type BtcdNotifier struct {
epochClientCounter uint64 // To be used atomically.
start sync.Once
active int32 // To be used atomically.
stopped int32 // To be used atomically.
chainConn *chain.RPCClient
chainParams *chaincfg.Params
notificationCancels chan interface{}
notificationRegistry chan interface{}
txNotifier *chainntnfs.TxNotifier
blockEpochClients map[uint64]*blockEpochRegistration
bestBlock chainntnfs.BlockEpoch
// blockCache is a LRU block cache.
blockCache *blockcache.BlockCache
chainUpdates *queue.ConcurrentQueue
txUpdates *queue.ConcurrentQueue
// spendHintCache is a cache used to query and update the latest height
// hints for an outpoint. Each height hint represents the earliest
// height at which the outpoint could have been spent within the chain.
spendHintCache chainntnfs.SpendHintCache
// confirmHintCache is a cache used to query the latest height hints for
// a transaction. Each height hint represents the earliest height at
// which the transaction could have confirmed within the chain.
confirmHintCache chainntnfs.ConfirmHintCache
// memNotifier notifies clients of events related to the mempool.
memNotifier *chainntnfs.MempoolNotifier
wg sync.WaitGroup
quit chan struct{}
}
// Ensure BtcdNotifier implements the ChainNotifier interface at compile time.
var _ chainntnfs.ChainNotifier = (*BtcdNotifier)(nil)
// Ensure BtcdNotifier implements the MempoolWatcher interface at compile time.
var _ chainntnfs.MempoolWatcher = (*BtcdNotifier)(nil)
// New returns a new BtcdNotifier instance. This function assumes the btcd node
// detailed in the passed configuration is already running, and willing to
// accept new websockets clients.
func New(config *rpcclient.ConnConfig, chainParams *chaincfg.Params,
spendHintCache chainntnfs.SpendHintCache,
confirmHintCache chainntnfs.ConfirmHintCache,
blockCache *blockcache.BlockCache) (*BtcdNotifier, error) {
notifier := &BtcdNotifier{
chainParams: chainParams,
notificationCancels: make(chan interface{}),
notificationRegistry: make(chan interface{}),
blockEpochClients: make(map[uint64]*blockEpochRegistration),
chainUpdates: queue.NewConcurrentQueue(10),
txUpdates: queue.NewConcurrentQueue(10),
spendHintCache: spendHintCache,
confirmHintCache: confirmHintCache,
blockCache: blockCache,
memNotifier: chainntnfs.NewMempoolNotifier(),
quit: make(chan struct{}),
}
ntfnCallbacks := &rpcclient.NotificationHandlers{
OnBlockConnected: notifier.onBlockConnected,
OnBlockDisconnected: notifier.onBlockDisconnected,
OnRedeemingTx: notifier.onRedeemingTx,
}
rpcCfg := &chain.RPCClientConfig{
ReconnectAttempts: 20,
Conn: config,
Chain: chainParams,
NotificationHandlers: ntfnCallbacks,
}
chainRPC, err := chain.NewRPCClientWithConfig(rpcCfg)
if err != nil {
return nil, err
}
notifier.chainConn = chainRPC
return notifier, nil
}
// Start connects to the running btcd node over websockets, registers for block
// notifications, and finally launches all related helper goroutines.
func (b *BtcdNotifier) Start() error {
var startErr error
b.start.Do(func() {
startErr = b.startNotifier()
})
return startErr
}
// Started returns true if this instance has been started, and false otherwise.
func (b *BtcdNotifier) Started() bool {
return atomic.LoadInt32(&b.active) != 0
}
// Stop shutsdown the BtcdNotifier.
func (b *BtcdNotifier) Stop() error {
// Already shutting down?
if atomic.AddInt32(&b.stopped, 1) != 1 {
return nil
}
chainntnfs.Log.Info("btcd notifier shutting down...")
defer chainntnfs.Log.Debug("btcd notifier shutdown complete")
// Shutdown the rpc client, this gracefully disconnects from btcd, and
// cleans up all related resources.
b.chainConn.Stop()
close(b.quit)
b.wg.Wait()
b.chainUpdates.Stop()
b.txUpdates.Stop()
// Notify all pending clients of our shutdown by closing the related
// notification channels.
for _, epochClient := range b.blockEpochClients {
close(epochClient.cancelChan)
epochClient.wg.Wait()
close(epochClient.epochChan)
}
b.txNotifier.TearDown()
// Stop the mempool notifier.
b.memNotifier.TearDown()
return nil
}
// startNotifier is the main starting point for the BtcdNotifier. It connects
// to btcd and start the main dispatcher goroutine.
func (b *BtcdNotifier) startNotifier() error {
chainntnfs.Log.Infof("btcd notifier starting...")
// Start our concurrent queues before starting the chain connection, to
// ensure onBlockConnected and onRedeemingTx callbacks won't be
// blocked.
b.chainUpdates.Start()
b.txUpdates.Start()
// Connect to btcd, and register for notifications on connected, and
// disconnected blocks.
if err := b.chainConn.Connect(20); err != nil {
b.txUpdates.Stop()
b.chainUpdates.Stop()
return err
}
// Before we fetch the best block/block height we need to register the
// notifications for connected blocks, otherwise we might think we are
// at an earlier block height because during block notification
// registration we might have already mined some new blocks. Hence we
// will not get notified accordingly.
if err := b.chainConn.NotifyBlocks(); err != nil {
b.txUpdates.Stop()
b.chainUpdates.Stop()
return err
}
currentHash, currentHeight, err := b.chainConn.GetBestBlock()
if err != nil {
b.txUpdates.Stop()
b.chainUpdates.Stop()
return err
}
bestBlock, err := b.chainConn.GetBlock(currentHash)
if err != nil {
b.txUpdates.Stop()
b.chainUpdates.Stop()
return err
}
b.txNotifier = chainntnfs.NewTxNotifier(
uint32(currentHeight), chainntnfs.ReorgSafetyLimit,
b.confirmHintCache, b.spendHintCache,
)
b.bestBlock = chainntnfs.BlockEpoch{
Height: currentHeight,
Hash: currentHash,
BlockHeader: &bestBlock.Header,
}
b.wg.Add(1)
go b.notificationDispatcher()
// Set the active flag now that we've completed the full
// startup.
atomic.StoreInt32(&b.active, 1)
chainntnfs.Log.Debugf("btcd notifier started")
return nil
}
// onBlockConnected implements on OnBlockConnected callback for rpcclient.
// Ingesting a block updates the wallet's internal utxo state based on the
// outputs created and destroyed within each block.
func (b *BtcdNotifier) onBlockConnected(hash *chainhash.Hash, height int32, t time.Time) {
// Append this new chain update to the end of the queue of new chain
// updates.
select {
case b.chainUpdates.ChanIn() <- &chainUpdate{
blockHash: hash,
blockHeight: height,
connect: true,
}:
case <-b.quit:
return
}
}
// filteredBlock represents a new block which has been connected to the main
// chain. The slice of transactions will only be populated if the block
// includes a transaction that confirmed one of our watched txids, or spends
// one of the outputs currently being watched.
//
// TODO(halseth): this is currently used for complete blocks. Change to use
// onFilteredBlockConnected and onFilteredBlockDisconnected, making it easier
// to unify with the Neutrino implementation.
type filteredBlock struct {
hash chainhash.Hash
height uint32
block *btcutil.Block
// connected is true if this update is a new block and false if it is a
// disconnected block.
connect bool
}
// onBlockDisconnected implements on OnBlockDisconnected callback for rpcclient.
func (b *BtcdNotifier) onBlockDisconnected(hash *chainhash.Hash, height int32, t time.Time) {
// Append this new chain update to the end of the queue of new chain
// updates.
select {
case b.chainUpdates.ChanIn() <- &chainUpdate{
blockHash: hash,
blockHeight: height,
connect: false,
}:
case <-b.quit:
return
}
}
// onRedeemingTx implements on OnRedeemingTx callback for rpcclient.
func (b *BtcdNotifier) onRedeemingTx(tx *btcutil.Tx, details *btcjson.BlockDetails) {
// Append this new transaction update to the end of the queue of new
// chain updates.
select {
case b.txUpdates.ChanIn() <- &txUpdate{tx, details}:
case <-b.quit:
return
}
}
// notificationDispatcher is the primary goroutine which handles client
// notification registrations, as well as notification dispatches.
func (b *BtcdNotifier) notificationDispatcher() {
defer b.wg.Done()
out:
for {
select {
case cancelMsg := <-b.notificationCancels:
switch msg := cancelMsg.(type) {
case *epochCancel:
chainntnfs.Log.Infof("Cancelling epoch "+
"notification, epoch_id=%v", msg.epochID)
// First, we'll lookup the original
// registration in order to stop the active
// queue goroutine.
reg := b.blockEpochClients[msg.epochID]
reg.epochQueue.Stop()
// Next, close the cancel channel for this
// specific client, and wait for the client to
// exit.
close(b.blockEpochClients[msg.epochID].cancelChan)
b.blockEpochClients[msg.epochID].wg.Wait()
// Once the client has exited, we can then
// safely close the channel used to send epoch
// notifications, in order to notify any
// listeners that the intent has been
// canceled.
close(b.blockEpochClients[msg.epochID].epochChan)
delete(b.blockEpochClients, msg.epochID)
}
case registerMsg := <-b.notificationRegistry:
switch msg := registerMsg.(type) {
case *chainntnfs.HistoricalConfDispatch:
// Look up whether the transaction/output script
// has already confirmed in the active chain.
// We'll do this in a goroutine to prevent
// blocking potentially long rescans.
//
// TODO(wilmer): add retry logic if rescan fails?
b.wg.Add(1)
//nolint:ll
go func(msg *chainntnfs.HistoricalConfDispatch) {
defer b.wg.Done()
confDetails, _, err := b.historicalConfDetails(
msg.ConfRequest,
msg.StartHeight, msg.EndHeight,
)
if err != nil {
chainntnfs.Log.Error(err)
return
}
// If the historical dispatch finished
// without error, we will invoke
// UpdateConfDetails even if none were
// found. This allows the notifier to
// begin safely updating the height hint
// cache at tip, since any pending
// rescans have now completed.
err = b.txNotifier.UpdateConfDetails(
msg.ConfRequest, confDetails,
)
if err != nil {
chainntnfs.Log.Error(err)
}
}(msg)
case *blockEpochRegistration:
chainntnfs.Log.Infof("New block epoch subscription")
b.blockEpochClients[msg.epochID] = msg
// If the client did not provide their best
// known block, then we'll immediately dispatch
// a notification for the current tip.
if msg.bestBlock == nil {
b.notifyBlockEpochClient(
msg, b.bestBlock.Height,
b.bestBlock.Hash,
b.bestBlock.BlockHeader,
)
msg.errorChan <- nil
continue
}
// Otherwise, we'll attempt to deliver the
// backlog of notifications from their best
// known block.
missedBlocks, err := chainntnfs.GetClientMissedBlocks(
b.chainConn, msg.bestBlock,
b.bestBlock.Height, true,
)
if err != nil {
msg.errorChan <- err
continue
}
for _, block := range missedBlocks {
b.notifyBlockEpochClient(
msg, block.Height, block.Hash,
block.BlockHeader,
)
}
msg.errorChan <- nil
}
case item := <-b.chainUpdates.ChanOut():
update := item.(*chainUpdate)
if update.connect {
blockHeader, err := b.chainConn.GetBlockHeader(
update.blockHash,
)
if err != nil {
chainntnfs.Log.Errorf("Unable to fetch "+
"block header: %v", err)
continue
}
if blockHeader.PrevBlock != *b.bestBlock.Hash {
// Handle the case where the notifier
// missed some blocks from its chain
// backend
chainntnfs.Log.Infof("Missed blocks, " +
"attempting to catch up")
newBestBlock, missedBlocks, err :=
chainntnfs.HandleMissedBlocks(
b.chainConn,
b.txNotifier,
b.bestBlock,
update.blockHeight,
true,
)
if err != nil {
// Set the bestBlock here in case
// a catch up partially completed.
b.bestBlock = newBestBlock
chainntnfs.Log.Error(err)
continue
}
for _, block := range missedBlocks {
err := b.handleBlockConnected(block)
if err != nil {
chainntnfs.Log.Error(err)
continue out
}
}
}
newBlock := chainntnfs.BlockEpoch{
Height: update.blockHeight,
Hash: update.blockHash,
BlockHeader: blockHeader,
}
if err := b.handleBlockConnected(newBlock); err != nil {
chainntnfs.Log.Error(err)
}
continue
}
if update.blockHeight != b.bestBlock.Height {
chainntnfs.Log.Infof("Missed disconnected" +
"blocks, attempting to catch up")
}
newBestBlock, err := chainntnfs.RewindChain(
b.chainConn, b.txNotifier, b.bestBlock,
update.blockHeight-1,
)
if err != nil {
chainntnfs.Log.Errorf("Unable to rewind chain "+
"from height %d to height %d: %v",
b.bestBlock.Height, update.blockHeight-1, err)
}
// Set the bestBlock here in case a chain rewind
// partially completed.
b.bestBlock = newBestBlock
case item := <-b.txUpdates.ChanOut():
newSpend := item.(*txUpdate)
tx := newSpend.tx
// Init values.
isMempool := false
height := uint32(0)
// Unwrap values.
if newSpend.details == nil {
isMempool = true
} else {
height = uint32(newSpend.details.Height)
}
// Handle the transaction.
b.handleRelevantTx(tx, isMempool, height)
case <-b.quit:
break out
}
}
}
// handleRelevantTx handles a new transaction that has been seen either in a
// block or in the mempool. If in mempool, it will ask the mempool notifier to
// handle it. If in a block, it will ask the txNotifier to handle it, and
// cancel any relevant subscriptions made in the mempool.
func (b *BtcdNotifier) handleRelevantTx(tx *btcutil.Tx,
mempool bool, height uint32) {
// If this is a mempool spend, we'll ask the mempool notifier to handle
// it.
if mempool {
err := b.memNotifier.ProcessRelevantSpendTx(tx)
if err != nil {
chainntnfs.Log.Errorf("Unable to process transaction "+
"%v: %v", tx.Hash(), err)
}
return
}
// Otherwise this is a confirmed spend, and we'll ask the tx notifier
// to handle it.
err := b.txNotifier.ProcessRelevantSpendTx(tx, height)
if err != nil {
chainntnfs.Log.Errorf("Unable to process transaction %v: %v",
tx.Hash(), err)
return
}
// Once the tx is processed, we will ask the memNotifier to unsubscribe
// the input.
//
// NOTE(yy): we could build it into txNotifier.ProcessRelevantSpendTx,
// but choose to implement it here so we can easily decouple the two
// notifiers in the future.
b.memNotifier.UnsubsribeConfirmedSpentTx(tx)
}
// historicalConfDetails looks up whether a confirmation request (txid/output
// script) has already been included in a block in the active chain and, if so,
// returns details about said block.
func (b *BtcdNotifier) historicalConfDetails(confRequest chainntnfs.ConfRequest,
startHeight, endHeight uint32) (*chainntnfs.TxConfirmation,
chainntnfs.TxConfStatus, error) {
// If a txid was not provided, then we should dispatch upon seeing the
// script on-chain, so we'll short-circuit straight to scanning manually
// as there doesn't exist a script index to query.
if confRequest.TxID == chainntnfs.ZeroHash {
return b.confDetailsManually(
confRequest, startHeight, endHeight,
)
}
// Otherwise, we'll dispatch upon seeing a transaction on-chain with the
// given hash.
//
// We'll first attempt to retrieve the transaction using the node's
// txindex.
txNotFoundErr := "No information available about transaction"
txConf, txStatus, err := chainntnfs.ConfDetailsFromTxIndex(
b.chainConn, confRequest, txNotFoundErr,
)
// We'll then check the status of the transaction lookup returned to
// determine whether we should proceed with any fallback methods.
switch {
// We failed querying the index for the transaction, fall back to
// scanning manually.
case err != nil:
chainntnfs.Log.Debugf("Unable to determine confirmation of %v "+
"through the backend's txindex (%v), scanning manually",
confRequest.TxID, err)
return b.confDetailsManually(
confRequest, startHeight, endHeight,
)
// The transaction was found within the node's mempool.
case txStatus == chainntnfs.TxFoundMempool:
// The transaction was found within the node's txindex.
case txStatus == chainntnfs.TxFoundIndex:
// The transaction was not found within the node's mempool or txindex.
case txStatus == chainntnfs.TxNotFoundIndex:
// Unexpected txStatus returned.
default:
return nil, txStatus,
fmt.Errorf("Got unexpected txConfStatus: %v", txStatus)
}
return txConf, txStatus, nil
}
// confDetailsManually looks up whether a transaction/output script has already
// been included in a block in the active chain by scanning the chain's blocks
// within the given range. If the transaction/output script is found, its
// confirmation details are returned. Otherwise, nil is returned.
func (b *BtcdNotifier) confDetailsManually(confRequest chainntnfs.ConfRequest,
startHeight, endHeight uint32) (*chainntnfs.TxConfirmation,
chainntnfs.TxConfStatus, error) {
// Begin scanning blocks at every height to determine where the
// transaction was included in.
for height := endHeight; height >= startHeight && height > 0; height-- {
// Ensure we haven't been requested to shut down before
// processing the next height.
select {
case <-b.quit:
return nil, chainntnfs.TxNotFoundManually,
chainntnfs.ErrChainNotifierShuttingDown
default:
}
blockHash, err := b.chainConn.GetBlockHash(int64(height))
if err != nil {
return nil, chainntnfs.TxNotFoundManually,
fmt.Errorf("unable to get hash from block "+
"with height %d", height)
}
// TODO: fetch the neutrino filters instead.
block, err := b.GetBlock(blockHash)
if err != nil {
return nil, chainntnfs.TxNotFoundManually,
fmt.Errorf("unable to get block with hash "+
"%v: %v", blockHash, err)
}
// For every transaction in the block, check which one matches
// our request. If we find one that does, we can dispatch its
// confirmation details.
for txIndex, tx := range block.Transactions {
if !confRequest.MatchesTx(tx) {
continue
}
return &chainntnfs.TxConfirmation{
Tx: tx.Copy(),
BlockHash: blockHash,
BlockHeight: height,
TxIndex: uint32(txIndex),
Block: block,
}, chainntnfs.TxFoundManually, nil
}
}
// If we reach here, then we were not able to find the transaction
// within a block, so we avoid returning an error.
return nil, chainntnfs.TxNotFoundManually, nil
}
// handleBlockConnected applies a chain update for a new block. Any watched
// transactions included this block will processed to either send notifications
// now or after numConfirmations confs.
// TODO(halseth): this is reusing the neutrino notifier implementation, unify
// them.
func (b *BtcdNotifier) handleBlockConnected(epoch chainntnfs.BlockEpoch) error {
// First, we'll fetch the raw block as we'll need to gather all the
// transactions to determine whether any are relevant to our registered
// clients.
rawBlock, err := b.GetBlock(epoch.Hash)
if err != nil {
return fmt.Errorf("unable to get block: %w", err)
}
newBlock := &filteredBlock{
hash: *epoch.Hash,
height: uint32(epoch.Height),
block: btcutil.NewBlock(rawBlock),
connect: true,
}
// We'll then extend the txNotifier's height with the information of
// this new block, which will handle all of the notification logic for
// us.
err = b.txNotifier.ConnectTip(newBlock.block, newBlock.height)
if err != nil {
return fmt.Errorf("unable to connect tip: %w", err)
}
chainntnfs.Log.Infof("New block: height=%v, sha=%v", epoch.Height,
epoch.Hash)
// Now that we've guaranteed the new block extends the txNotifier's
// current tip, we'll proceed to dispatch notifications to all of our
// registered clients whom have had notifications fulfilled. Before
// doing so, we'll make sure update our in memory state in order to
// satisfy any client requests based upon the new block.
b.bestBlock = epoch
err = b.txNotifier.NotifyHeight(uint32(epoch.Height))
if err != nil {
return fmt.Errorf("unable to notify height: %w", err)
}
b.notifyBlockEpochs(
epoch.Height, epoch.Hash, epoch.BlockHeader,
)
return nil
}
// notifyBlockEpochs notifies all registered block epoch clients of the newly
// connected block to the main chain.
func (b *BtcdNotifier) notifyBlockEpochs(newHeight int32,
newSha *chainhash.Hash, blockHeader *wire.BlockHeader) {
for _, client := range b.blockEpochClients {
b.notifyBlockEpochClient(
client, newHeight, newSha, blockHeader,
)
}
}
// notifyBlockEpochClient sends a registered block epoch client a notification
// about a specific block.
func (b *BtcdNotifier) notifyBlockEpochClient(epochClient *blockEpochRegistration,
height int32, sha *chainhash.Hash, blockHeader *wire.BlockHeader) {
epoch := &chainntnfs.BlockEpoch{
Height: height,
Hash: sha,
BlockHeader: blockHeader,
}
select {
case epochClient.epochQueue.ChanIn() <- epoch:
case <-epochClient.cancelChan:
case <-b.quit:
}
}
// RegisterSpendNtfn registers an intent to be notified once the target
// outpoint/output script has been spent by a transaction on-chain. When
// intending to be notified of the spend of an output script, a nil outpoint
// must be used. The heightHint should represent the earliest height in the
// chain of the transaction that spent the outpoint/output script.
//
// Once a spend of has been detected, the details of the spending event will be
// sent across the 'Spend' channel.
func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) {
// Register the conf notification with the TxNotifier. A non-nil value
// for `dispatch` will be returned if we are required to perform a
// manual scan for the confirmation. Otherwise the notifier will begin
// watching at tip for the transaction to confirm.
ntfn, err := b.txNotifier.RegisterSpend(outpoint, pkScript, heightHint)
if err != nil {
return nil, err
}
// We'll then request the backend to notify us when it has detected the
// outpoint/output script as spent.
//
// TODO(wilmer): use LoadFilter API instead.
if outpoint == nil || *outpoint == chainntnfs.ZeroOutPoint {
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
pkScript, b.chainParams,
)
if err != nil {
return nil, fmt.Errorf("unable to parse script: %w",
err)
}
if err := b.chainConn.NotifyReceived(addrs); err != nil {
return nil, err
}
} else {
ops := []*wire.OutPoint{outpoint}
if err := b.chainConn.NotifySpent(ops); err != nil {
return nil, err
}
}
// If the txNotifier didn't return any details to perform a historical
// scan of the chain, then we can return early as there's nothing left
// for us to do.
if ntfn.HistoricalDispatch == nil {
return ntfn.Event, nil
}
// Otherwise, we'll need to dispatch a historical rescan to determine if
// the outpoint was already spent at a previous height.
//
// We'll short-circuit the path when dispatching the spend of a script,
// rather than an outpoint, as there aren't any additional checks we can
// make for scripts.
if outpoint == nil || *outpoint == chainntnfs.ZeroOutPoint {
startHash, err := b.chainConn.GetBlockHash(
int64(ntfn.HistoricalDispatch.StartHeight),
)
if err != nil {
return nil, err
}
// TODO(wilmer): add retry logic if rescan fails?
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
pkScript, b.chainParams,
)
if err != nil {
return nil, fmt.Errorf("unable to parse address: %w",
err)
}
asyncResult := b.chainConn.RescanAsync(startHash, addrs, nil)
go func() {
if rescanErr := asyncResult.Receive(); rescanErr != nil {
chainntnfs.Log.Errorf("Rescan to determine "+
"the spend details of %v failed: %v",
ntfn.HistoricalDispatch.SpendRequest,
rescanErr)
}
}()
return ntfn.Event, nil
}
// When dispatching spends of outpoints, there are a number of checks we
// can make to start our rescan from a better height or completely avoid
// it.
//
// We'll start by checking the backend's UTXO set to determine whether
// the outpoint has been spent. If it hasn't, we can return to the
// caller as well.
txOut, err := b.chainConn.GetTxOut(&outpoint.Hash, outpoint.Index, true)
if err != nil {
return nil, err
}
if txOut != nil {
// We'll let the txNotifier know the outpoint is still unspent
// in order to begin updating its spend hint.
err := b.txNotifier.UpdateSpendDetails(
ntfn.HistoricalDispatch.SpendRequest, nil,
)
if err != nil {
return nil, err
}
return ntfn.Event, nil
}
// Since the outpoint was spent, as it no longer exists within the UTXO
// set, we'll determine when it happened by scanning the chain. We'll
// begin by fetching the block hash of our starting height.
startHash, err := b.chainConn.GetBlockHash(
int64(ntfn.HistoricalDispatch.StartHeight),
)
if err != nil {
return nil, fmt.Errorf("unable to get block hash for height "+
"%d: %v", ntfn.HistoricalDispatch.StartHeight, err)
}
// As a minimal optimization, we'll query the backend's transaction
// index (if enabled) to determine if we have a better rescan starting
// height. We can do this as the GetRawTransaction call will return the
// hash of the block it was included in within the chain.
tx, err := b.chainConn.GetRawTransactionVerbose(&outpoint.Hash)
if err != nil {
// Avoid returning an error if the transaction was not found to
// proceed with fallback methods.
jsonErr, ok := err.(*btcjson.RPCError)
if !ok || jsonErr.Code != btcjson.ErrRPCNoTxInfo {
return nil, fmt.Errorf("unable to query for txid %v: "+
"%w", outpoint.Hash, err)
}
}
// If the transaction index was enabled, we'll use the block's hash to
// retrieve its height and check whether it provides a better starting
// point for our rescan.
if tx != nil {
// If the transaction containing the outpoint hasn't confirmed
// on-chain, then there's no need to perform a rescan.
if tx.BlockHash == "" {
return ntfn.Event, nil
}
blockHash, err := chainhash.NewHashFromStr(tx.BlockHash)
if err != nil {
return nil, err
}
blockHeader, err := b.chainConn.GetBlockHeaderVerbose(blockHash)
if err != nil {
return nil, fmt.Errorf("unable to get header for "+
"block %v: %v", blockHash, err)
}
if uint32(blockHeader.Height) > ntfn.HistoricalDispatch.StartHeight {
startHash, err = b.chainConn.GetBlockHash(
int64(blockHeader.Height),
)
if err != nil {
return nil, fmt.Errorf("unable to get block "+
"hash for height %d: %v",
blockHeader.Height, err)
}
}
}
// Now that we've determined the best starting point for our rescan,
// we can go ahead and dispatch it.
//
// In order to ensure that we don't block the caller on what may be a
// long rescan, we'll launch a new goroutine to handle the async result
// of the rescan. We purposefully prevent from adding this goroutine to
// the WaitGroup as we cannot wait for a quit signal due to the
// asyncResult channel not being exposed.
//
// TODO(wilmer): add retry logic if rescan fails?
asyncResult := b.chainConn.RescanAsync(
startHash, nil, []*wire.OutPoint{outpoint},
)
go func() {
if rescanErr := asyncResult.Receive(); rescanErr != nil {
chainntnfs.Log.Errorf("Rescan to determine the spend "+
"details of %v failed: %v", outpoint, rescanErr)
}
}()
return ntfn.Event, nil
}
// RegisterConfirmationsNtfn registers an intent to be notified once the target
// txid/output script has reached numConfs confirmations on-chain. When
// intending to be notified of the confirmation of an output script, a nil txid
// must be used. The heightHint should represent the earliest height at which
// the txid/output script could have been included in the chain.
//
// Progress on the number of confirmations left can be read from the 'Updates'
// channel. Once it has reached all of its confirmations, a notification will be
// sent across the 'Confirmed' channel.
func (b *BtcdNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
pkScript []byte, numConfs, heightHint uint32,
opts ...chainntnfs.NotifierOption) (*chainntnfs.ConfirmationEvent, error) {
// Register the conf notification with the TxNotifier. A non-nil value
// for `dispatch` will be returned if we are required to perform a
// manual scan for the confirmation. Otherwise the notifier will begin
// watching at tip for the transaction to confirm.
ntfn, err := b.txNotifier.RegisterConf(
txid, pkScript, numConfs, heightHint, opts...,
)
if err != nil {
return nil, err
}
if ntfn.HistoricalDispatch == nil {
return ntfn.Event, nil
}
select {
case b.notificationRegistry <- ntfn.HistoricalDispatch:
return ntfn.Event, nil
case <-b.quit:
return nil, chainntnfs.ErrChainNotifierShuttingDown
}
}
// blockEpochRegistration represents a client's intent to receive a
// notification with each newly connected block.
type blockEpochRegistration struct {
epochID uint64
epochChan chan *chainntnfs.BlockEpoch
epochQueue *queue.ConcurrentQueue
bestBlock *chainntnfs.BlockEpoch
errorChan chan error
cancelChan chan struct{}
wg sync.WaitGroup
}
// epochCancel is a message sent to the BtcdNotifier when a client wishes to
// cancel an outstanding epoch notification that has yet to be dispatched.
type epochCancel struct {
epochID uint64
}
// RegisterBlockEpochNtfn returns a BlockEpochEvent which subscribes the
// caller to receive notifications, of each new block connected to the main
// chain. Clients have the option of passing in their best known block, which
// the notifier uses to check if they are behind on blocks and catch them up. If
// they do not provide one, then a notification will be dispatched immediately
// for the current tip of the chain upon a successful registration.
func (b *BtcdNotifier) RegisterBlockEpochNtfn(
bestBlock *chainntnfs.BlockEpoch) (*chainntnfs.BlockEpochEvent, error) {
reg := &blockEpochRegistration{
epochQueue: queue.NewConcurrentQueue(20),
epochChan: make(chan *chainntnfs.BlockEpoch, 20),
cancelChan: make(chan struct{}),
epochID: atomic.AddUint64(&b.epochClientCounter, 1),
bestBlock: bestBlock,
errorChan: make(chan error, 1),
}
reg.epochQueue.Start()
// Before we send the request to the main goroutine, we'll launch a new
// goroutine to proxy items added to our queue to the client itself.
// This ensures that all notifications are received *in order*.
reg.wg.Add(1)
go func() {
defer reg.wg.Done()
for {
select {
case ntfn := <-reg.epochQueue.ChanOut():
blockNtfn := ntfn.(*chainntnfs.BlockEpoch)
select {
case reg.epochChan <- blockNtfn:
case <-reg.cancelChan:
return
case <-b.quit:
return
}
case <-reg.cancelChan:
return
case <-b.quit:
return
}
}
}()
select {
case <-b.quit:
// As we're exiting before the registration could be sent,
// we'll stop the queue now ourselves.
reg.epochQueue.Stop()
return nil, errors.New("chainntnfs: system interrupt while " +
"attempting to register for block epoch notification.")
case b.notificationRegistry <- reg:
return &chainntnfs.BlockEpochEvent{
Epochs: reg.epochChan,
Cancel: func() {
cancel := &epochCancel{
epochID: reg.epochID,
}
// Submit epoch cancellation to notification dispatcher.
select {
case b.notificationCancels <- cancel:
// Cancellation is being handled, drain
// the epoch channel until it is closed
// before yielding to caller.
for {
select {
case _, ok := <-reg.epochChan:
if !ok {
return
}
case <-b.quit:
return
}
}
case <-b.quit:
}
},
}, nil
}
}
// GetBlock is used to retrieve the block with the given hash. This function
// wraps the blockCache's GetBlock function.
func (b *BtcdNotifier) GetBlock(hash *chainhash.Hash) (*wire.MsgBlock,
error) {
return b.blockCache.GetBlock(hash, b.chainConn.GetBlock)
}
// SubscribeMempoolSpent allows the caller to register a subscription to watch
// for a spend of an outpoint in the mempool.The event will be dispatched once
// the outpoint is spent in the mempool.
//
// NOTE: part of the MempoolWatcher interface.
func (b *BtcdNotifier) SubscribeMempoolSpent(
outpoint wire.OutPoint) (*chainntnfs.MempoolSpendEvent, error) {
event := b.memNotifier.SubscribeInput(outpoint)
ops := []*wire.OutPoint{&outpoint}
return event, b.chainConn.NotifySpent(ops)
}
// CancelMempoolSpendEvent allows the caller to cancel a subscription to watch
// for a spend of an outpoint in the mempool.
//
// NOTE: part of the MempoolWatcher interface.
func (b *BtcdNotifier) CancelMempoolSpendEvent(
sub *chainntnfs.MempoolSpendEvent) {
b.memNotifier.UnsubscribeEvent(sub)
}
// LookupInputMempoolSpend takes an outpoint and queries the mempool to find
// its spending tx. Returns the tx if found, otherwise fn.None.
//
// NOTE: part of the MempoolWatcher interface.
func (b *BtcdNotifier) LookupInputMempoolSpend(
op wire.OutPoint) fn.Option[wire.MsgTx] {
// Find the spending txid.
txid, found := b.chainConn.LookupInputMempoolSpend(op)
if !found {
return fn.None[wire.MsgTx]()
}
// Query the spending tx using the id.
tx, err := b.chainConn.GetRawTransaction(&txid)
if err != nil {
// TODO(yy): enable logging errors in this package.
return fn.None[wire.MsgTx]()
}
return fn.Some(*tx.MsgTx().Copy())
}
package btcdnotify
import (
"errors"
"fmt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/rpcclient"
"github.com/lightningnetwork/lnd/blockcache"
"github.com/lightningnetwork/lnd/chainntnfs"
)
// createNewNotifier creates a new instance of the ChainNotifier interface
// implemented by BtcdNotifier.
func createNewNotifier(args ...interface{}) (chainntnfs.ChainNotifier, error) {
if len(args) != 5 {
return nil, fmt.Errorf("incorrect number of arguments to "+
".New(...), expected 5, instead passed %v", len(args))
}
config, ok := args[0].(*rpcclient.ConnConfig)
if !ok {
return nil, errors.New("first argument to btcdnotify.New " +
"is incorrect, expected a *rpcclient.ConnConfig")
}
chainParams, ok := args[1].(*chaincfg.Params)
if !ok {
return nil, errors.New("second argument to btcdnotify.New " +
"is incorrect, expected a *chaincfg.Params")
}
spendHintCache, ok := args[2].(chainntnfs.SpendHintCache)
if !ok {
return nil, errors.New("third argument to btcdnotify.New " +
"is incorrect, expected a chainntnfs.SpendHintCache")
}
confirmHintCache, ok := args[3].(chainntnfs.ConfirmHintCache)
if !ok {
return nil, errors.New("fourth argument to btcdnotify.New " +
"is incorrect, expected a chainntnfs.ConfirmHintCache")
}
blockCache, ok := args[4].(*blockcache.BlockCache)
if !ok {
return nil, errors.New("fifth argument to btcdnotify.New " +
"is incorrect, expected a *blockcache.BlockCache")
}
return New(
config, chainParams, spendHintCache, confirmHintCache, blockCache,
)
}
// init registers a driver for the BtcdNotifier concrete implementation of the
// chainntnfs.ChainNotifier interface.
func init() {
// Register the driver.
notifier := &chainntnfs.NotifierDriver{
NotifierType: notifierType,
New: createNewNotifier,
}
if err := chainntnfs.RegisterNotifier(notifier); err != nil {
panic(fmt.Sprintf("failed to register notifier driver '%s': %v",
notifierType, err))
}
}
package chainntnfs
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"strings"
"sync"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/fn/v2"
)
var (
// ErrChainNotifierShuttingDown is used when we are trying to
// measure a spend notification when notifier is already stopped.
ErrChainNotifierShuttingDown = errors.New("chain notifier shutting down")
)
// TxConfStatus denotes the status of a transaction's lookup.
type TxConfStatus uint8
const (
// TxFoundMempool denotes that the transaction was found within the
// backend node's mempool.
TxFoundMempool TxConfStatus = iota
// TxFoundIndex denotes that the transaction was found within the
// backend node's txindex.
TxFoundIndex
// TxNotFoundIndex denotes that the transaction was not found within the
// backend node's txindex.
TxNotFoundIndex
// TxFoundManually denotes that the transaction was found within the
// chain by scanning for it manually.
TxFoundManually
// TxNotFoundManually denotes that the transaction was not found within
// the chain by scanning for it manually.
TxNotFoundManually
)
// String returns the string representation of the TxConfStatus.
func (t TxConfStatus) String() string {
switch t {
case TxFoundMempool:
return "TxFoundMempool"
case TxFoundIndex:
return "TxFoundIndex"
case TxNotFoundIndex:
return "TxNotFoundIndex"
case TxFoundManually:
return "TxFoundManually"
case TxNotFoundManually:
return "TxNotFoundManually"
default:
return "unknown"
}
}
// notifierOptions is a set of functional options that allow callers to further
// modify the type of chain event notifications they receive.
type notifierOptions struct {
// includeBlock if true, then the dispatched confirmation notification
// will include the block that mined the transaction.
includeBlock bool
}
// defaultNotifierOptions returns the set of default options for the notifier.
func defaultNotifierOptions() *notifierOptions {
return ¬ifierOptions{}
}
// NotifierOption is a functional option that allows a caller to modify the
// events received from the notifier.
type NotifierOption func(*notifierOptions)
// WithIncludeBlock is an optional argument that allows the calelr to specify
// that the block that mined a transaction should be included in the response.
func WithIncludeBlock() NotifierOption {
return func(o *notifierOptions) {
o.includeBlock = true
}
}
// ChainNotifier represents a trusted source to receive notifications concerning
// targeted events on the Bitcoin blockchain. The interface specification is
// intentionally general in order to support a wide array of chain notification
// implementations such as: btcd's websockets notifications, Bitcoin Core's
// ZeroMQ notifications, various Bitcoin API services, Electrum servers, etc.
//
// Concrete implementations of ChainNotifier should be able to support multiple
// concurrent client requests, as well as multiple concurrent notification events.
type ChainNotifier interface {
// RegisterConfirmationsNtfn registers an intent to be notified once
// txid reaches numConfs confirmations. We also pass in the pkScript as
// the default light client instead needs to match on scripts created in
// the block. If a nil txid is passed in, then not only should we match
// on the script, but we should also dispatch once the transaction
// containing the script reaches numConfs confirmations. This can be
// useful in instances where we only know the script in advance, but not
// the transaction containing it.
//
// The returned ConfirmationEvent should properly notify the client once
// the specified number of confirmations has been reached for the txid,
// as well as if the original tx gets re-org'd out of the mainchain. The
// heightHint parameter is provided as a convenience to light clients.
// It heightHint denotes the earliest height in the blockchain in which
// the target txid _could_ have been included in the chain. This can be
// used to bound the search space when checking to see if a notification
// can immediately be dispatched due to historical data.
//
// NOTE: Dispatching notifications to multiple clients subscribed to
// the same (txid, numConfs) tuple MUST be supported.
RegisterConfirmationsNtfn(txid *chainhash.Hash, pkScript []byte,
numConfs, heightHint uint32,
opts ...NotifierOption) (*ConfirmationEvent, error)
// RegisterSpendNtfn registers an intent to be notified once the target
// outpoint is successfully spent within a transaction. The script that
// the outpoint creates must also be specified. This allows this
// interface to be implemented by BIP 158-like filtering. If a nil
// outpoint is passed in, then not only should we match on the script,
// but we should also dispatch once a transaction spends the output
// containing said script. This can be useful in instances where we only
// know the script in advance, but not the outpoint itself.
//
// The returned SpendEvent will receive a send on the 'Spend'
// transaction once a transaction spending the input is detected on the
// blockchain. The heightHint parameter is provided as a convenience to
// light clients. It denotes the earliest height in the blockchain in
// which the target output could have been spent.
//
// NOTE: The notification should only be triggered when the spending
// transaction receives a single confirmation.
//
// NOTE: Dispatching notifications to multiple clients subscribed to a
// spend of the same outpoint MUST be supported.
RegisterSpendNtfn(outpoint *wire.OutPoint, pkScript []byte,
heightHint uint32) (*SpendEvent, error)
// RegisterBlockEpochNtfn registers an intent to be notified of each
// new block connected to the tip of the main chain. The returned
// BlockEpochEvent struct contains a channel which will be sent upon
// for each new block discovered.
//
// Clients have the option of passing in their best known block.
// If they specify a block, the ChainNotifier checks whether the client
// is behind on blocks. If they are, the ChainNotifier sends a backlog
// of block notifications for the missed blocks. If they do not provide
// one, then a notification will be dispatched immediately for the
// current tip of the chain upon a successful registration.
RegisterBlockEpochNtfn(*BlockEpoch) (*BlockEpochEvent, error)
// Start the ChainNotifier. Once started, the implementation should be
// ready, and able to receive notification registrations from clients.
Start() error
// Started returns true if this instance has been started, and false otherwise.
Started() bool
// Stops the concrete ChainNotifier. Once stopped, the ChainNotifier
// should disallow any future requests from potential clients.
// Additionally, all pending client notifications will be canceled
// by closing the related channels on the *Event's.
Stop() error
}
// TxConfirmation carries some additional block-level details of the exact
// block that specified transactions was confirmed within.
type TxConfirmation struct {
// BlockHash is the hash of the block that confirmed the original
// transition.
BlockHash *chainhash.Hash
// BlockHeight is the height of the block in which the transaction was
// confirmed within.
BlockHeight uint32
// TxIndex is the index within the block of the ultimate confirmed
// transaction.
TxIndex uint32
// Tx is the transaction for which the notification was requested for.
Tx *wire.MsgTx
// Block is the block that contains the transaction referenced above.
//
// NOTE: This is only specified if the confirmation request opts to
// have the response include the block itself.
Block *wire.MsgBlock
}
// ConfirmationEvent encapsulates a confirmation notification. With this struct,
// callers can be notified of: the instance the target txid reaches the targeted
// number of confirmations, how many confirmations are left for the target txid
// to be fully confirmed at every new block height, and also in the event that
// the original txid becomes disconnected from the blockchain as a result of a
// re-org.
//
// Once the txid reaches the specified number of confirmations, the 'Confirmed'
// channel will be sent upon fulfilling the notification.
//
// If the event that the original transaction becomes re-org'd out of the main
// chain, the 'NegativeConf' will be sent upon with a value representing the
// depth of the re-org.
//
// NOTE: If the caller wishes to cancel their registered spend notification,
// the Cancel closure MUST be called.
type ConfirmationEvent struct {
// Confirmed is a channel that will be sent upon once the transaction
// has been fully confirmed. The struct sent will contain all the
// details of the channel's confirmation.
//
// NOTE: This channel must be buffered.
Confirmed chan *TxConfirmation
// Updates is a channel that will sent upon, at every incremental
// confirmation, how many confirmations are left to declare the
// transaction as fully confirmed.
//
// NOTE: This channel must be buffered with the number of required
// confirmations.
Updates chan uint32
// NegativeConf is a channel that will be sent upon if the transaction
// confirms, but is later reorged out of the chain. The integer sent
// through the channel represents the reorg depth.
//
// NOTE: This channel must be buffered.
NegativeConf chan int32
// Done is a channel that gets sent upon once the confirmation request
// is no longer under the risk of being reorged out of the chain.
//
// NOTE: This channel must be buffered.
Done chan struct{}
// Cancel is a closure that should be executed by the caller in the case
// that they wish to prematurely abandon their registered confirmation
// notification.
Cancel func()
}
// NewConfirmationEvent constructs a new ConfirmationEvent with newly opened
// channels.
func NewConfirmationEvent(numConfs uint32, cancel func()) *ConfirmationEvent {
return &ConfirmationEvent{
// We cannot rely on the subscriber to immediately read from
// the channel so we need to create a larger buffer to avoid
// blocking the notifier.
Confirmed: make(chan *TxConfirmation, 1),
Updates: make(chan uint32, numConfs),
NegativeConf: make(chan int32, 1),
Done: make(chan struct{}, 1),
Cancel: cancel,
}
}
// SpendDetail contains details pertaining to a spent output. This struct itself
// is the spentness notification. It includes the original outpoint which triggered
// the notification, the hash of the transaction spending the output, the
// spending transaction itself, and finally the input index which spent the
// target output.
type SpendDetail struct {
SpentOutPoint *wire.OutPoint
SpenderTxHash *chainhash.Hash
SpendingTx *wire.MsgTx
SpenderInputIndex uint32
SpendingHeight int32
}
// HasSpenderWitness returns true if the spending transaction has non-empty
// witness.
func (s *SpendDetail) HasSpenderWitness() bool {
tx := s.SpendingTx
// If there are no inputs, then there is no witness.
if len(tx.TxIn) == 0 {
return false
}
// If the spender input index is larger than the number of inputs, then
// we don't have a witness and this is an error case so we log it.
if uint32(len(tx.TxIn)) <= s.SpenderInputIndex {
Log.Errorf("SpenderInputIndex %d is out of range for tx %v",
s.SpenderInputIndex, tx.TxHash())
return false
}
// If the witness is empty, then there is no witness.
if len(tx.TxIn[s.SpenderInputIndex].Witness) == 0 {
return false
}
// If the witness is non-empty, then we have a witness.
return true
}
// String returns a string representation of SpendDetail.
func (s *SpendDetail) String() string {
return fmt.Sprintf("%v[%d] spending %v at height=%v", s.SpenderTxHash,
s.SpenderInputIndex, s.SpentOutPoint, s.SpendingHeight)
}
// SpendEvent encapsulates a spentness notification. Its only field 'Spend' will
// be sent upon once the target output passed into RegisterSpendNtfn has been
// spent on the blockchain.
//
// NOTE: If the caller wishes to cancel their registered spend notification,
// the Cancel closure MUST be called.
type SpendEvent struct {
// Spend is a receive only channel which will be sent upon once the
// target outpoint has been spent.
//
// NOTE: This channel must be buffered.
Spend chan *SpendDetail
// Reorg is a channel that will be sent upon once we detect the spending
// transaction of the outpoint in question has been reorged out of the
// chain.
//
// NOTE: This channel must be buffered.
Reorg chan struct{}
// Done is a channel that gets sent upon once the confirmation request
// is no longer under the risk of being reorged out of the chain.
//
// NOTE: This channel must be buffered.
Done chan struct{}
// Cancel is a closure that should be executed by the caller in the case
// that they wish to prematurely abandon their registered spend
// notification.
Cancel func()
}
// NewSpendEvent constructs a new SpendEvent with newly opened channels.
func NewSpendEvent(cancel func()) *SpendEvent {
return &SpendEvent{
Spend: make(chan *SpendDetail, 1),
Reorg: make(chan struct{}, 1),
Done: make(chan struct{}, 1),
Cancel: cancel,
}
}
// BlockEpoch represents metadata concerning each new block connected to the
// main chain.
type BlockEpoch struct {
// Hash is the block hash of the latest block to be added to the tip of
// the main chain.
Hash *chainhash.Hash
// Height is the height of the latest block to be added to the tip of
// the main chain.
Height int32
// BlockHeader is the block header of this new height.
BlockHeader *wire.BlockHeader
}
// BlockEpochEvent encapsulates an on-going stream of block epoch
// notifications. Its only field 'Epochs' will be sent upon for each new block
// connected to the main-chain.
//
// NOTE: If the caller wishes to cancel their registered block epoch
// notification, the Cancel closure MUST be called.
type BlockEpochEvent struct {
// Epochs is a receive only channel that will be sent upon each time a
// new block is connected to the end of the main chain.
//
// NOTE: This channel must be buffered.
Epochs <-chan *BlockEpoch
// Cancel is a closure that should be executed by the caller in the case
// that they wish to abandon their registered block epochs notification.
Cancel func()
}
// NotifierDriver represents a "driver" for a particular interface. A driver is
// identified by a globally unique string identifier along with a 'New()'
// method which is responsible for initializing a particular ChainNotifier
// concrete implementation.
type NotifierDriver struct {
// NotifierType is a string which uniquely identifies the ChainNotifier
// that this driver, drives.
NotifierType string
// New creates a new instance of a concrete ChainNotifier
// implementation given a variadic set up arguments. The function takes
// a variadic number of interface parameters in order to provide
// initialization flexibility, thereby accommodating several potential
// ChainNotifier implementations.
New func(args ...interface{}) (ChainNotifier, error)
}
var (
notifiers = make(map[string]*NotifierDriver)
registerMtx sync.Mutex
)
// RegisteredNotifiers returns a slice of all currently registered notifiers.
//
// NOTE: This function is safe for concurrent access.
func RegisteredNotifiers() []*NotifierDriver {
registerMtx.Lock()
defer registerMtx.Unlock()
drivers := make([]*NotifierDriver, 0, len(notifiers))
for _, driver := range notifiers {
drivers = append(drivers, driver)
}
return drivers
}
// RegisterNotifier registers a NotifierDriver which is capable of driving a
// concrete ChainNotifier interface. In the case that this driver has already
// been registered, an error is returned.
//
// NOTE: This function is safe for concurrent access.
func RegisterNotifier(driver *NotifierDriver) error {
registerMtx.Lock()
defer registerMtx.Unlock()
if _, ok := notifiers[driver.NotifierType]; ok {
return fmt.Errorf("notifier already registered")
}
notifiers[driver.NotifierType] = driver
return nil
}
// SupportedNotifiers returns a slice of strings that represent the database
// drivers that have been registered and are therefore supported.
//
// NOTE: This function is safe for concurrent access.
func SupportedNotifiers() []string {
registerMtx.Lock()
defer registerMtx.Unlock()
supportedNotifiers := make([]string, 0, len(notifiers))
for driverName := range notifiers {
supportedNotifiers = append(supportedNotifiers, driverName)
}
return supportedNotifiers
}
// ChainConn enables notifiers to pass in their chain backend to interface
// functions that require it.
type ChainConn interface {
// GetBlockHeader returns the block header for a hash.
GetBlockHeader(blockHash *chainhash.Hash) (*wire.BlockHeader, error)
// GetBlockHeaderVerbose returns the verbose block header for a hash.
GetBlockHeaderVerbose(blockHash *chainhash.Hash) (
*btcjson.GetBlockHeaderVerboseResult, error)
// GetBlockHash returns the hash from a block height.
GetBlockHash(blockHeight int64) (*chainhash.Hash, error)
}
// GetCommonBlockAncestorHeight takes in:
// (1) the hash of a block that has been reorged out of the main chain
// (2) the hash of the block of the same height from the main chain
// It returns the height of the nearest common ancestor between the two hashes,
// or an error
func GetCommonBlockAncestorHeight(chainConn ChainConn, reorgHash,
chainHash chainhash.Hash) (int32, error) {
for reorgHash != chainHash {
reorgHeader, err := chainConn.GetBlockHeader(&reorgHash)
if err != nil {
return 0, fmt.Errorf("unable to get header for "+
"hash=%v: %w", reorgHash, err)
}
chainHeader, err := chainConn.GetBlockHeader(&chainHash)
if err != nil {
return 0, fmt.Errorf("unable to get header for "+
"hash=%v: %w", chainHash, err)
}
reorgHash = reorgHeader.PrevBlock
chainHash = chainHeader.PrevBlock
}
verboseHeader, err := chainConn.GetBlockHeaderVerbose(&chainHash)
if err != nil {
return 0, fmt.Errorf("unable to get verbose header for "+
"hash=%v: %w", chainHash, err)
}
return verboseHeader.Height, nil
}
// GetClientMissedBlocks uses a client's best block to determine what blocks
// it missed being notified about, and returns them in a slice. Its
// backendStoresReorgs parameter tells it whether or not the notifier's
// chainConn stores information about blocks that have been reorged out of the
// chain, which allows GetClientMissedBlocks to find out whether the client's
// best block has been reorged out of the chain, rewind to the common ancestor
// and return blocks starting right after the common ancestor.
func GetClientMissedBlocks(chainConn ChainConn, clientBestBlock *BlockEpoch,
notifierBestHeight int32, backendStoresReorgs bool) ([]BlockEpoch, error) {
startingHeight := clientBestBlock.Height
if backendStoresReorgs {
// If a reorg causes the client's best hash to be incorrect,
// retrieve the closest common ancestor and dispatch
// notifications from there.
hashAtBestHeight, err := chainConn.GetBlockHash(
int64(clientBestBlock.Height))
if err != nil {
return nil, fmt.Errorf("unable to find blockhash for "+
"height=%d: %v", clientBestBlock.Height, err)
}
startingHeight, err = GetCommonBlockAncestorHeight(
chainConn, *clientBestBlock.Hash, *hashAtBestHeight,
)
if err != nil {
return nil, fmt.Errorf("unable to find common ancestor: "+
"%v", err)
}
}
// We want to start dispatching historical notifications from the block
// right after the client's best block, to avoid a redundant notification.
missedBlocks, err := getMissedBlocks(
chainConn, startingHeight+1, notifierBestHeight+1,
)
if err != nil {
return nil, fmt.Errorf("unable to get missed blocks: %w", err)
}
return missedBlocks, nil
}
// RewindChain handles internal state updates for the notifier's TxNotifier. It
// has no effect if given a height greater than or equal to our current best
// known height. It returns the new best block for the notifier.
func RewindChain(chainConn ChainConn, txNotifier *TxNotifier,
currBestBlock BlockEpoch, targetHeight int32) (BlockEpoch, error) {
newBestBlock := BlockEpoch{
Height: currBestBlock.Height,
Hash: currBestBlock.Hash,
BlockHeader: currBestBlock.BlockHeader,
}
for height := currBestBlock.Height; height > targetHeight; height-- {
hash, err := chainConn.GetBlockHash(int64(height - 1))
if err != nil {
return newBestBlock, fmt.Errorf("unable to "+
"find blockhash for disconnected height=%d: %v",
height, err)
}
header, err := chainConn.GetBlockHeader(hash)
if err != nil {
return newBestBlock, fmt.Errorf("unable to get block "+
"header for height=%v", height-1)
}
Log.Infof("Block disconnected from main chain: "+
"height=%v, sha=%v", height, newBestBlock.Hash)
err = txNotifier.DisconnectTip(uint32(height))
if err != nil {
return newBestBlock, fmt.Errorf("unable to "+
" disconnect tip for height=%d: %v",
height, err)
}
newBestBlock.Height = height - 1
newBestBlock.Hash = hash
newBestBlock.BlockHeader = header
}
return newBestBlock, nil
}
// HandleMissedBlocks is called when the chain backend for a notifier misses a
// series of blocks, handling a reorg if necessary. Its backendStoresReorgs
// parameter tells it whether or not the notifier's chainConn stores
// information about blocks that have been reorged out of the chain, which allows
// HandleMissedBlocks to check whether the notifier's best block has been
// reorged out, and rewind the chain accordingly. It returns the best block for
// the notifier and a slice of the missed blocks. The new best block needs to be
// returned in case a chain rewind occurs and partially completes before
// erroring. In the case where there is no rewind, the notifier's
// current best block is returned.
func HandleMissedBlocks(chainConn ChainConn, txNotifier *TxNotifier,
currBestBlock BlockEpoch, newHeight int32,
backendStoresReorgs bool) (BlockEpoch, []BlockEpoch, error) {
startingHeight := currBestBlock.Height
if backendStoresReorgs {
// If a reorg causes our best hash to be incorrect, rewind the
// chain so our best block is set to the closest common
// ancestor, then dispatch notifications from there.
hashAtBestHeight, err := chainConn.GetBlockHash(
int64(currBestBlock.Height),
)
if err != nil {
return currBestBlock, nil, fmt.Errorf("unable to find "+
"blockhash for height=%d: %v",
currBestBlock.Height, err)
}
startingHeight, err = GetCommonBlockAncestorHeight(
chainConn, *currBestBlock.Hash, *hashAtBestHeight,
)
if err != nil {
return currBestBlock, nil, fmt.Errorf("unable to find "+
"common ancestor: %v", err)
}
currBestBlock, err = RewindChain(
chainConn, txNotifier, currBestBlock, startingHeight,
)
if err != nil {
return currBestBlock, nil, fmt.Errorf("unable to "+
"rewind chain: %v", err)
}
}
// We want to start dispatching historical notifications from the block
// right after our best block, to avoid a redundant notification.
missedBlocks, err := getMissedBlocks(chainConn, startingHeight+1, newHeight)
if err != nil {
return currBestBlock, nil, fmt.Errorf("unable to get missed "+
"blocks: %v", err)
}
return currBestBlock, missedBlocks, nil
}
// getMissedBlocks returns a slice of blocks: [startingHeight, endingHeight)
// fetched from the chain.
func getMissedBlocks(chainConn ChainConn, startingHeight,
endingHeight int32) ([]BlockEpoch, error) {
numMissedBlocks := endingHeight - startingHeight
if numMissedBlocks < 0 {
return nil, fmt.Errorf("starting height %d is greater than "+
"ending height %d", startingHeight, endingHeight)
}
missedBlocks := make([]BlockEpoch, 0, numMissedBlocks)
for height := startingHeight; height < endingHeight; height++ {
hash, err := chainConn.GetBlockHash(int64(height))
if err != nil {
return nil, fmt.Errorf("unable to find blockhash for "+
"height=%d: %v", height, err)
}
header, err := chainConn.GetBlockHeader(hash)
if err != nil {
return nil, fmt.Errorf("unable to find block header "+
"for height=%d: %v", height, err)
}
missedBlocks = append(
missedBlocks,
BlockEpoch{
Hash: hash,
Height: height,
BlockHeader: header,
},
)
}
return missedBlocks, nil
}
// TxIndexConn abstracts an RPC backend with txindex enabled.
type TxIndexConn interface {
// GetRawTransactionVerbose returns the transaction identified by the
// passed chain hash, and returns additional information such as the
// block that the transaction confirmed.
GetRawTransactionVerbose(*chainhash.Hash) (*btcjson.TxRawResult, error)
// GetBlock returns the block identified by the chain hash.
GetBlock(*chainhash.Hash) (*wire.MsgBlock, error)
}
// ConfDetailsFromTxIndex looks up whether a transaction is already included in
// a block in the active chain by using the backend node's transaction index.
// If the transaction is found its TxConfStatus is returned. If it was found in
// the mempool this will be TxFoundMempool, if it is found in a block this will
// be TxFoundIndex. Otherwise TxNotFoundIndex is returned. If the tx is found
// in a block its confirmation details are also returned.
func ConfDetailsFromTxIndex(chainConn TxIndexConn, r ConfRequest,
txNotFoundErr string) (*TxConfirmation, TxConfStatus, error) {
// If the transaction has some or all of its confirmations required,
// then we may be able to dispatch it immediately.
rawTxRes, err := chainConn.GetRawTransactionVerbose(&r.TxID)
if err != nil {
// If the transaction lookup was successful, but it wasn't found
// within the index itself, then we can exit early. We'll also
// need to look at the error message returned as the error code
// is used for multiple errors.
jsonErr, ok := err.(*btcjson.RPCError)
if ok && jsonErr.Code == btcjson.ErrRPCNoTxInfo &&
strings.Contains(jsonErr.Message, txNotFoundErr) {
return nil, TxNotFoundIndex, nil
}
return nil, TxNotFoundIndex,
fmt.Errorf("unable to query for txid %v: %w",
r.TxID, err)
}
// Deserialize the hex-encoded transaction to include it in the
// confirmation details.
rawTx, err := hex.DecodeString(rawTxRes.Hex)
if err != nil {
return nil, TxNotFoundIndex,
fmt.Errorf("unable to deserialize tx %v: %w",
r.TxID, err)
}
var tx wire.MsgTx
if err := tx.Deserialize(bytes.NewReader(rawTx)); err != nil {
return nil, TxNotFoundIndex,
fmt.Errorf("unable to deserialize tx %v: %w",
r.TxID, err)
}
// Ensure the transaction matches our confirmation request in terms of
// txid and pkscript.
if !r.MatchesTx(&tx) {
return nil, TxNotFoundIndex,
fmt.Errorf("unable to locate tx %v", r.TxID)
}
// Make sure we actually retrieved a transaction that is included in a
// block. If not, the transaction must be unconfirmed (in the mempool),
// and we'll return TxFoundMempool together with a nil TxConfirmation.
if rawTxRes.BlockHash == "" {
return nil, TxFoundMempool, nil
}
// As we need to fully populate the returned TxConfirmation struct,
// grab the block in which the transaction was confirmed so we can
// locate its exact index within the block.
blockHash, err := chainhash.NewHashFromStr(rawTxRes.BlockHash)
if err != nil {
return nil, TxNotFoundIndex,
fmt.Errorf("unable to get block hash %v for "+
"historical dispatch: %w", rawTxRes.BlockHash,
err)
}
block, err := chainConn.GetBlock(blockHash)
if err != nil {
return nil, TxNotFoundIndex,
fmt.Errorf("unable to get block with hash %v for "+
"historical dispatch: %w", blockHash, err)
}
// In the modern chain (the only one we really care about for LN), the
// coinbase transaction of all blocks will include the block height.
// Therefore we can save another query, and just use that height
// directly.
blockHeight, err := blockchain.ExtractCoinbaseHeight(
btcutil.NewTx(block.Transactions[0]),
)
if err != nil {
return nil, TxNotFoundIndex, fmt.Errorf("unable to extract "+
"coinbase height: %w", err)
}
// If the block was obtained, locate the transaction's index within the
// block so we can give the subscriber full confirmation details.
for txIndex, blockTx := range block.Transactions {
if blockTx.TxHash() != r.TxID {
continue
}
return &TxConfirmation{
Tx: tx.Copy(),
BlockHash: blockHash,
BlockHeight: uint32(blockHeight),
TxIndex: uint32(txIndex),
Block: block,
}, TxFoundIndex, nil
}
// We return an error because we should have found the transaction
// within the block, but didn't.
return nil, TxNotFoundIndex, fmt.Errorf("unable to locate "+
"tx %v in block %v", r.TxID, blockHash)
}
// SpendHintCache is an interface whose duty is to cache spend hints for
// outpoints. A spend hint is defined as the earliest height in the chain at
// which an outpoint could have been spent within.
type SpendHintCache interface {
// CommitSpendHint commits a spend hint for the outpoints to the cache.
CommitSpendHint(height uint32, spendRequests ...SpendRequest) error
// QuerySpendHint returns the latest spend hint for an outpoint.
// ErrSpendHintNotFound is returned if a spend hint does not exist
// within the cache for the outpoint.
QuerySpendHint(spendRequest SpendRequest) (uint32, error)
// PurgeSpendHint removes the spend hint for the outpoints from the
// cache.
PurgeSpendHint(spendRequests ...SpendRequest) error
}
// ConfirmHintCache is an interface whose duty is to cache confirm hints for
// transactions. A confirm hint is defined as the earliest height in the chain
// at which a transaction could have been included in a block.
type ConfirmHintCache interface {
// CommitConfirmHint commits a confirm hint for the transactions to the
// cache.
CommitConfirmHint(height uint32, confRequests ...ConfRequest) error
// QueryConfirmHint returns the latest confirm hint for a transaction
// hash. ErrConfirmHintNotFound is returned if a confirm hint does not
// exist within the cache for the transaction hash.
QueryConfirmHint(confRequest ConfRequest) (uint32, error)
// PurgeConfirmHint removes the confirm hint for the transactions from
// the cache.
PurgeConfirmHint(confRequests ...ConfRequest) error
}
// MempoolWatcher defines an interface that allows the caller to query
// information in the mempool.
type MempoolWatcher interface {
// SubscribeMempoolSpent allows the caller to register a subscription
// to watch for a spend of an outpoint in the mempool.The event will be
// dispatched once the outpoint is spent in the mempool.
SubscribeMempoolSpent(op wire.OutPoint) (*MempoolSpendEvent, error)
// CancelMempoolSpendEvent allows the caller to cancel a subscription to
// watch for a spend of an outpoint in the mempool.
CancelMempoolSpendEvent(sub *MempoolSpendEvent)
// LookupInputMempoolSpend looks up the mempool to find a spending tx
// which spends the given outpoint. A fn.None is returned if it's not
// found.
LookupInputMempoolSpend(op wire.OutPoint) fn.Option[wire.MsgTx]
}
package chainntnfs
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// Log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var Log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("NTFN", nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
Log = logger
}
package chainntnfs
import (
"sync"
"sync/atomic"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/lnutils"
)
// inputsWithTx is a map of outpoints to the tx that spends them.
type inputsWithTx map[wire.OutPoint]*SpendDetail
// MempoolNotifier defines an internal mempool notifier that's used to notify
// the spending of given inputs. This is mounted to either `BitcoindNotifier`
// or `BtcdNotifier` depending on the chain backend.
type MempoolNotifier struct {
wg sync.WaitGroup
// subscribedInputs stores the inputs that we want to watch their
// spending event for.
subscribedInputs *lnutils.SyncMap[wire.OutPoint,
*lnutils.SyncMap[uint64, *MempoolSpendEvent]]
// sCounter is used to generate unique subscription IDs.
sCounter atomic.Uint64
// quit is closed when the notifier is torn down.
quit chan struct{}
}
// MempoolSpendEvent is returned to the subscriber to watch for the spending
// event for the requested input.
type MempoolSpendEvent struct {
// Spend is a receive only channel which will be sent upon once the
// target outpoint has been spent.
//
// NOTE: This channel must be buffered.
Spend <-chan *SpendDetail
// id is the unique identifier of this subscription.
id uint64
// outpoint is the subscribed outpoint.
outpoint wire.OutPoint
// event is the channel that will be sent upon once the target outpoint
// is spent.
event chan *SpendDetail
// cancel cancels the subscription.
cancel chan struct{}
}
// newMempoolSpendEvent returns a new instance of MempoolSpendEvent.
func newMempoolSpendEvent(id uint64, op wire.OutPoint) *MempoolSpendEvent {
sub := &MempoolSpendEvent{
id: id,
outpoint: op,
event: make(chan *SpendDetail, 1),
cancel: make(chan struct{}),
}
// Mount the receive only channel to the event channel.
sub.Spend = (<-chan *SpendDetail)(sub.event)
return sub
}
// NewMempoolNotifier takes a chain connection and returns a new mempool
// notifier.
func NewMempoolNotifier() *MempoolNotifier {
return &MempoolNotifier{
subscribedInputs: &lnutils.SyncMap[
wire.OutPoint, *lnutils.SyncMap[
uint64, *MempoolSpendEvent,
]]{},
quit: make(chan struct{}),
}
}
// SubscribeInput takes an outpoint of the input and returns a channel that the
// subscriber can listen to for the spending event.
func (m *MempoolNotifier) SubscribeInput(
outpoint wire.OutPoint) *MempoolSpendEvent {
// Get the current subscribers for this input or create a new one.
clients := &lnutils.SyncMap[uint64, *MempoolSpendEvent]{}
clients, _ = m.subscribedInputs.LoadOrStore(outpoint, clients)
// Increment the subscription counter and return the new value.
subscriptionID := m.sCounter.Add(1)
// Create a new subscription.
sub := newMempoolSpendEvent(subscriptionID, outpoint)
// Add the subscriber with a unique id.
clients.Store(subscriptionID, sub)
// Update the subscribed inputs.
m.subscribedInputs.Store(outpoint, clients)
Log.Debugf("Subscribed(id=%v) mempool event for input=%s",
subscriptionID, outpoint)
return sub
}
// UnsubscribeInput removes all the subscriptions for the given outpoint.
func (m *MempoolNotifier) UnsubscribeInput(outpoint wire.OutPoint) {
Log.Debugf("Unsubscribing MempoolSpendEvent for input %s", outpoint)
m.subscribedInputs.Delete(outpoint)
}
// UnsubscribeEvent removes a given subscriber for the given MempoolSpendEvent.
func (m *MempoolNotifier) UnsubscribeEvent(sub *MempoolSpendEvent) {
Log.Debugf("Unsubscribing(id=%v) MempoolSpendEvent for input=%s",
sub.id, sub.outpoint)
// Load all the subscribers for this input.
clients, loaded := m.subscribedInputs.Load(sub.outpoint)
if !loaded {
Log.Debugf("No subscribers for input %s", sub.outpoint)
return
}
// Load the subscriber.
subscriber, loaded := clients.Load(sub.id)
if !loaded {
Log.Debugf("No subscribers for input %s with id %v",
sub.outpoint, sub.id)
return
}
// Close the cancel channel in case it's been used in a goroutine.
close(subscriber.cancel)
// Remove the subscriber.
clients.Delete(sub.id)
}
// UnsubsribeConfirmedSpentTx takes a transaction and removes the subscriptions
// identified using its inputs.
func (m *MempoolNotifier) UnsubsribeConfirmedSpentTx(tx *btcutil.Tx) {
Log.Tracef("Unsubscribe confirmed tx %s", tx.Hash())
// Get the spent inputs of interest.
spentInputs, err := m.findRelevantInputs(tx)
if err != nil {
Log.Errorf("Unable to find relevant inputs for tx %s: %v",
tx.Hash(), err)
return
}
// Unsubscribe the subscribers.
for outpoint := range spentInputs {
m.UnsubscribeInput(outpoint)
}
Log.Tracef("Finished unsubscribing confirmed tx %s, found %d inputs",
tx.Hash(), len(spentInputs))
}
// ProcessRelevantSpendTx takes a transaction and checks whether it spends any
// of the subscribed inputs. If so, spend notifications are sent to the
// relevant subscribers.
func (m *MempoolNotifier) ProcessRelevantSpendTx(tx *btcutil.Tx) error {
Log.Tracef("Processing mempool tx %s", tx.Hash())
defer Log.Tracef("Finished processing mempool tx %s", tx.Hash())
// Get the spent inputs of interest.
spentInputs, err := m.findRelevantInputs(tx)
if err != nil {
return err
}
// Notify the subscribers.
m.notifySpent(spentInputs)
return nil
}
// TearDown stops the notifier and cleans up resources.
func (m *MempoolNotifier) TearDown() {
Log.Infof("Stopping mempool notifier")
defer Log.Debug("mempool notifier stopped")
close(m.quit)
m.wg.Wait()
}
// findRelevantInputs takes a transaction to find the subscribed inputs and
// returns them.
func (m *MempoolNotifier) findRelevantInputs(tx *btcutil.Tx) (inputsWithTx,
error) {
txid := tx.Hash()
watchedInputs := make(inputsWithTx)
// NOTE: we may have found multiple targeted inputs in the same tx.
for i, input := range tx.MsgTx().TxIn {
op := &input.PreviousOutPoint
// Check whether this input is subscribed.
_, loaded := m.subscribedInputs.Load(*op)
if !loaded {
continue
}
// If found, save it to watchedInputs to notify the
// subscriber later.
Log.Debugf("Found input %s, spent in %s", op, txid)
// Construct the spend details.
details := &SpendDetail{
SpentOutPoint: op,
SpenderTxHash: txid,
SpendingTx: tx.MsgTx().Copy(),
SpenderInputIndex: uint32(i),
SpendingHeight: 0,
}
watchedInputs[*op] = details
// Sanity check the witness stack. If it's not empty, continue
// to next iteration.
if details.HasSpenderWitness() {
continue
}
// Return an error if the witness data is not present in the
// spending transaction.
Log.Criticalf("Found spending tx for outpoint=%v in mempool, "+
"but the transaction %v does not have witness",
op, details.SpendingTx.TxHash())
return nil, ErrEmptyWitnessStack
}
return watchedInputs, nil
}
// notifySpent iterates all the spentInputs and notifies the subscribers about
// the spent details.
func (m *MempoolNotifier) notifySpent(spentInputs inputsWithTx) {
// notifySingle sends a notification to a single subscriber about the
// spending event.
//
// NOTE: must be used inside a goroutine.
notifySingle := func(id uint64, sub *MempoolSpendEvent,
op wire.OutPoint, detail *SpendDetail) {
defer m.wg.Done()
// Send the spend details to the subscriber.
select {
case sub.event <- detail:
Log.Debugf("Notified(id=%v) mempool spent for input %s",
sub.id, op)
case <-sub.cancel:
Log.Debugf("Subscription(id=%v) canceled, skipped "+
"notifying spent for input %s", sub.id, op)
case <-m.quit:
Log.Debugf("Mempool notifier quit, skipped notifying "+
"mempool spent for input %s", op)
}
}
// notifyAll is a helper closure that constructs a spend detail and
// sends it to all the subscribers of that particular input.
//
// NOTE: must be used inside a goroutine.
notifyAll := func(detail *SpendDetail, op wire.OutPoint) {
defer m.wg.Done()
txid := detail.SpendingTx.TxHash()
Log.Debugf("Notifying all clients for the spend of %s in tx %s",
op, txid)
// Load the subscriber.
subs, loaded := m.subscribedInputs.Load(op)
if !loaded {
Log.Errorf("Sub not found for %s", op)
return
}
// Iterate all the subscribers for this input and notify them.
subs.ForEach(func(id uint64, sub *MempoolSpendEvent) error {
m.wg.Add(1)
go notifySingle(id, sub, op, detail)
return nil
})
}
// Iterate the spent inputs to notify the subscribers concurrently.
for op, tx := range spentInputs {
op, tx := op, tx
m.wg.Add(1)
go notifyAll(tx, op)
}
}
package chainntnfs
import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/stretchr/testify/mock"
)
// MockMempoolWatcher is a mock implementation of the MempoolWatcher interface.
// This is used by other subsystems to mock the behavior of the mempool
// watcher.
type MockMempoolWatcher struct {
mock.Mock
}
// NewMockMempoolWatcher returns a new instance of a mock mempool watcher.
func NewMockMempoolWatcher() *MockMempoolWatcher {
return &MockMempoolWatcher{}
}
// Compile-time check to ensure MockMempoolWatcher implements MempoolWatcher.
var _ MempoolWatcher = (*MockMempoolWatcher)(nil)
// SubscribeMempoolSpent implements the MempoolWatcher interface.
func (m *MockMempoolWatcher) SubscribeMempoolSpent(
op wire.OutPoint) (*MempoolSpendEvent, error) {
args := m.Called(op)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*MempoolSpendEvent), args.Error(1)
}
// CancelMempoolSpendEvent implements the MempoolWatcher interface.
func (m *MockMempoolWatcher) CancelMempoolSpendEvent(
sub *MempoolSpendEvent) {
m.Called(sub)
}
// LookupInputMempoolSpend looks up the mempool to find a spending tx which
// spends the given outpoint.
func (m *MockMempoolWatcher) LookupInputMempoolSpend(
op wire.OutPoint) fn.Option[wire.MsgTx] {
args := m.Called(op)
return args.Get(0).(fn.Option[wire.MsgTx])
}
// MockNotifier is a mock implementation of the ChainNotifier interface.
type MockChainNotifier struct {
mock.Mock
}
// Compile-time check to ensure MockChainNotifier implements ChainNotifier.
var _ ChainNotifier = (*MockChainNotifier)(nil)
// RegisterConfirmationsNtfn registers an intent to be notified once txid
// reaches numConfs confirmations.
func (m *MockChainNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
pkScript []byte, numConfs, heightHint uint32,
opts ...NotifierOption) (*ConfirmationEvent, error) {
args := m.Called(txid, pkScript, numConfs, heightHint)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*ConfirmationEvent), args.Error(1)
}
// RegisterSpendNtfn registers an intent to be notified once the target
// outpoint is successfully spent within a transaction.
func (m *MockChainNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
pkScript []byte, heightHint uint32) (*SpendEvent, error) {
args := m.Called(outpoint, pkScript, heightHint)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*SpendEvent), args.Error(1)
}
// RegisterBlockEpochNtfn registers an intent to be notified of each new block
// connected to the tip of the main chain.
func (m *MockChainNotifier) RegisterBlockEpochNtfn(epoch *BlockEpoch) (
*BlockEpochEvent, error) {
args := m.Called(epoch)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*BlockEpochEvent), args.Error(1)
}
// Start the ChainNotifier. Once started, the implementation should be ready,
// and able to receive notification registrations from clients.
func (m *MockChainNotifier) Start() error {
args := m.Called()
return args.Error(0)
}
// Started returns true if this instance has been started, and false otherwise.
func (m *MockChainNotifier) Started() bool {
args := m.Called()
return args.Bool(0)
}
// Stops the concrete ChainNotifier.
func (m *MockChainNotifier) Stop() error {
args := m.Called()
return args.Error(0)
}
package neutrinonotify
import (
"errors"
"fmt"
"github.com/lightninglabs/neutrino"
"github.com/lightningnetwork/lnd/blockcache"
"github.com/lightningnetwork/lnd/chainntnfs"
)
// createNewNotifier creates a new instance of the ChainNotifier interface
// implemented by NeutrinoNotifier.
func createNewNotifier(args ...interface{}) (chainntnfs.ChainNotifier, error) {
if len(args) != 4 {
return nil, fmt.Errorf("incorrect number of arguments to "+
".New(...), expected 4, instead passed %v", len(args))
}
config, ok := args[0].(*neutrino.ChainService)
if !ok {
return nil, errors.New("first argument to neutrinonotify.New " +
"is incorrect, expected a *neutrino.ChainService")
}
spendHintCache, ok := args[1].(chainntnfs.SpendHintCache)
if !ok {
return nil, errors.New("second argument to neutrinonotify.New " +
"is incorrect, expected a chainntfs.SpendHintCache")
}
confirmHintCache, ok := args[2].(chainntnfs.ConfirmHintCache)
if !ok {
return nil, errors.New("third argument to neutrinonotify.New " +
"is incorrect, expected a chainntfs.ConfirmHintCache")
}
blockCache, ok := args[3].(*blockcache.BlockCache)
if !ok {
return nil, errors.New("fourth argument to neutrinonotify.New " +
"is incorrect, expected a *blockcache.BlockCache")
}
return New(config, spendHintCache, confirmHintCache, blockCache), nil
}
// init registers a driver for the NeutrinoNotify concrete implementation of
// the chainntnfs.ChainNotifier interface.
func init() {
// Register the driver.
notifier := &chainntnfs.NotifierDriver{
NotifierType: notifierType,
New: createNewNotifier,
}
if err := chainntnfs.RegisterNotifier(notifier); err != nil {
panic(fmt.Sprintf("failed to register notifier driver '%s': %v",
notifierType, err))
}
}
package neutrinonotify
import (
"errors"
"fmt"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/gcs/builder"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/neutrino"
"github.com/lightninglabs/neutrino/headerfs"
"github.com/lightningnetwork/lnd/blockcache"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/queue"
)
const (
// notifierType uniquely identifies this concrete implementation of the
// ChainNotifier interface.
notifierType = "neutrino"
)
// NeutrinoNotifier is a version of ChainNotifier that's backed by the neutrino
// Bitcoin light client. Unlike other implementations, this implementation
// speaks directly to the p2p network. As a result, this implementation of the
// ChainNotifier interface is much more light weight that other implementation
// which rely of receiving notification over an RPC interface backed by a
// running full node.
//
// TODO(roasbeef): heavily consolidate with NeutrinoNotifier code
// - maybe combine into single package?
type NeutrinoNotifier struct {
epochClientCounter uint64 // To be used atomically.
start sync.Once
active int32 // To be used atomically.
stopped int32 // To be used atomically.
bestBlockMtx sync.RWMutex
bestBlock chainntnfs.BlockEpoch
p2pNode *neutrino.ChainService
chainView *neutrino.Rescan
chainConn *NeutrinoChainConn
notificationCancels chan interface{}
notificationRegistry chan interface{}
txNotifier *chainntnfs.TxNotifier
blockEpochClients map[uint64]*blockEpochRegistration
rescanErr <-chan error
chainUpdates chan *filteredBlock
txUpdates *queue.ConcurrentQueue
// spendHintCache is a cache used to query and update the latest height
// hints for an outpoint. Each height hint represents the earliest
// height at which the outpoint could have been spent within the chain.
spendHintCache chainntnfs.SpendHintCache
// confirmHintCache is a cache used to query the latest height hints for
// a transaction. Each height hint represents the earliest height at
// which the transaction could have confirmed within the chain.
confirmHintCache chainntnfs.ConfirmHintCache
// blockCache is an LRU block cache.
blockCache *blockcache.BlockCache
wg sync.WaitGroup
quit chan struct{}
}
// Ensure NeutrinoNotifier implements the ChainNotifier interface at compile time.
var _ chainntnfs.ChainNotifier = (*NeutrinoNotifier)(nil)
// New creates a new instance of the NeutrinoNotifier concrete implementation
// of the ChainNotifier interface.
//
// NOTE: The passed neutrino node should already be running and active before
// being passed into this function.
func New(node *neutrino.ChainService, spendHintCache chainntnfs.SpendHintCache,
confirmHintCache chainntnfs.ConfirmHintCache,
blockCache *blockcache.BlockCache) *NeutrinoNotifier {
return &NeutrinoNotifier{
notificationCancels: make(chan interface{}),
notificationRegistry: make(chan interface{}),
blockEpochClients: make(map[uint64]*blockEpochRegistration),
p2pNode: node,
chainConn: &NeutrinoChainConn{node},
rescanErr: make(chan error),
chainUpdates: make(chan *filteredBlock, 100),
txUpdates: queue.NewConcurrentQueue(10),
spendHintCache: spendHintCache,
confirmHintCache: confirmHintCache,
blockCache: blockCache,
quit: make(chan struct{}),
}
}
// Start contacts the running neutrino light client and kicks off an initial
// empty rescan.
func (n *NeutrinoNotifier) Start() error {
var startErr error
n.start.Do(func() {
startErr = n.startNotifier()
})
return startErr
}
// Stop shuts down the NeutrinoNotifier.
func (n *NeutrinoNotifier) Stop() error {
// Already shutting down?
if atomic.AddInt32(&n.stopped, 1) != 1 {
return nil
}
chainntnfs.Log.Info("neutrino notifier shutting down...")
defer chainntnfs.Log.Debug("neutrino notifier shutdown complete")
close(n.quit)
n.wg.Wait()
n.txUpdates.Stop()
// Notify all pending clients of our shutdown by closing the related
// notification channels.
for _, epochClient := range n.blockEpochClients {
close(epochClient.cancelChan)
epochClient.wg.Wait()
close(epochClient.epochChan)
}
// The txNotifier is only initialized in the start method therefore we
// need to make sure we don't access a nil pointer here.
if n.txNotifier != nil {
n.txNotifier.TearDown()
}
return nil
}
// Started returns true if this instance has been started, and false otherwise.
func (n *NeutrinoNotifier) Started() bool {
return atomic.LoadInt32(&n.active) != 0
}
func (n *NeutrinoNotifier) startNotifier() error {
chainntnfs.Log.Infof("neutrino notifier starting...")
// Start our concurrent queues before starting the rescan, to ensure
// onFilteredBlockConnected and onRelavantTx callbacks won't be
// blocked.
n.txUpdates.Start()
// First, we'll obtain the latest block height of the p2p node. We'll
// start the auto-rescan from this point. Once a caller actually wishes
// to register a chain view, the rescan state will be rewound
// accordingly.
startingPoint, err := n.p2pNode.BestBlock()
if err != nil {
n.txUpdates.Stop()
return err
}
startingHeader, err := n.p2pNode.GetBlockHeader(
&startingPoint.Hash,
)
if err != nil {
n.txUpdates.Stop()
return err
}
n.bestBlock.Hash = &startingPoint.Hash
n.bestBlock.Height = startingPoint.Height
n.bestBlock.BlockHeader = startingHeader
n.txNotifier = chainntnfs.NewTxNotifier(
uint32(n.bestBlock.Height), chainntnfs.ReorgSafetyLimit,
n.confirmHintCache, n.spendHintCache,
)
// Next, we'll create our set of rescan options. Currently it's
// required that a user MUST set an addr/outpoint/txid when creating a
// rescan. To get around this, we'll add a "zero" outpoint, that won't
// actually be matched.
var zeroInput neutrino.InputWithScript
rescanOptions := []neutrino.RescanOption{
neutrino.StartBlock(startingPoint),
neutrino.QuitChan(n.quit),
neutrino.NotificationHandlers(
rpcclient.NotificationHandlers{
OnFilteredBlockConnected: n.onFilteredBlockConnected,
OnFilteredBlockDisconnected: n.onFilteredBlockDisconnected,
OnRedeemingTx: n.onRelevantTx,
},
),
neutrino.WatchInputs(zeroInput),
}
// Finally, we'll create our rescan struct, start it, and launch all
// the goroutines we need to operate this ChainNotifier instance.
n.chainView = neutrino.NewRescan(
&neutrino.RescanChainSource{
ChainService: n.p2pNode,
},
rescanOptions...,
)
n.rescanErr = n.chainView.Start()
n.wg.Add(1)
go n.notificationDispatcher()
// Set the active flag now that we've completed the full
// startup.
atomic.StoreInt32(&n.active, 1)
chainntnfs.Log.Debugf("neutrino notifier started")
return nil
}
// filteredBlock represents a new block which has been connected to the main
// chain. The slice of transactions will only be populated if the block
// includes a transaction that confirmed one of our watched txids, or spends
// one of the outputs currently being watched.
type filteredBlock struct {
header *wire.BlockHeader
hash chainhash.Hash
height uint32
txns []*btcutil.Tx
// connected is true if this update is a new block and false if it is a
// disconnected block.
connect bool
}
// rescanFilterUpdate represents a request that will be sent to the
// notificaionRegistry in order to prevent race conditions between the filter
// update and new block notifications.
type rescanFilterUpdate struct {
updateOptions []neutrino.UpdateOption
errChan chan error
}
// onFilteredBlockConnected is a callback which is executed each a new block is
// connected to the end of the main chain.
func (n *NeutrinoNotifier) onFilteredBlockConnected(height int32,
header *wire.BlockHeader, txns []*btcutil.Tx) {
// Append this new chain update to the end of the queue of new chain
// updates.
select {
case n.chainUpdates <- &filteredBlock{
hash: header.BlockHash(),
height: uint32(height),
txns: txns,
header: header,
connect: true,
}:
case <-n.quit:
}
}
// onFilteredBlockDisconnected is a callback which is executed each time a new
// block has been disconnected from the end of the mainchain due to a re-org.
func (n *NeutrinoNotifier) onFilteredBlockDisconnected(height int32,
header *wire.BlockHeader) {
// Append this new chain update to the end of the queue of new chain
// disconnects.
select {
case n.chainUpdates <- &filteredBlock{
hash: header.BlockHash(),
height: uint32(height),
connect: false,
}:
case <-n.quit:
}
}
// relevantTx represents a relevant transaction to the notifier that fulfills
// any outstanding spend requests.
type relevantTx struct {
tx *btcutil.Tx
details *btcjson.BlockDetails
}
// onRelevantTx is a callback that proxies relevant transaction notifications
// from the backend to the notifier's main event handler.
func (n *NeutrinoNotifier) onRelevantTx(tx *btcutil.Tx, details *btcjson.BlockDetails) {
select {
case n.txUpdates.ChanIn() <- &relevantTx{tx, details}:
case <-n.quit:
}
}
// connectFilteredBlock is called when we receive a filteredBlock from the
// backend. If the block is ahead of what we're expecting, we'll attempt to
// catch up and then process the block.
func (n *NeutrinoNotifier) connectFilteredBlock(update *filteredBlock) {
n.bestBlockMtx.Lock()
defer n.bestBlockMtx.Unlock()
if update.height != uint32(n.bestBlock.Height+1) {
chainntnfs.Log.Infof("Missed blocks, attempting to catch up")
_, missedBlocks, err := chainntnfs.HandleMissedBlocks(
n.chainConn, n.txNotifier, n.bestBlock,
int32(update.height), false,
)
if err != nil {
chainntnfs.Log.Error(err)
return
}
for _, block := range missedBlocks {
filteredBlock, err := n.getFilteredBlock(block)
if err != nil {
chainntnfs.Log.Error(err)
return
}
err = n.handleBlockConnected(filteredBlock)
if err != nil {
chainntnfs.Log.Error(err)
return
}
}
}
err := n.handleBlockConnected(update)
if err != nil {
chainntnfs.Log.Error(err)
}
}
// disconnectFilteredBlock is called when our disconnected filtered block
// callback is fired. It attempts to rewind the chain to before the
// disconnection and updates our best block.
func (n *NeutrinoNotifier) disconnectFilteredBlock(update *filteredBlock) {
n.bestBlockMtx.Lock()
defer n.bestBlockMtx.Unlock()
if update.height != uint32(n.bestBlock.Height) {
chainntnfs.Log.Infof("Missed disconnected blocks, attempting" +
" to catch up")
}
newBestBlock, err := chainntnfs.RewindChain(n.chainConn, n.txNotifier,
n.bestBlock, int32(update.height-1),
)
if err != nil {
chainntnfs.Log.Errorf("Unable to rewind chain from height %d"+
"to height %d: %v", n.bestBlock.Height,
update.height-1, err,
)
}
n.bestBlock = newBestBlock
}
// drainChainUpdates is called after updating the filter. It reads every
// buffered item off the chan and returns when no more are available. It is
// used to ensure that callers performing a historical scan properly update
// their EndHeight to scan blocks that did not have the filter applied at
// processing time. Without this, a race condition exists that could allow a
// spend or confirmation notification to be missed. It is unlikely this would
// occur in a real-world scenario, and instead would manifest itself in tests.
func (n *NeutrinoNotifier) drainChainUpdates() {
for {
select {
case update := <-n.chainUpdates:
if update.connect {
n.connectFilteredBlock(update)
break
}
n.disconnectFilteredBlock(update)
default:
return
}
}
}
// notificationDispatcher is the primary goroutine which handles client
// notification registrations, as well as notification dispatches.
func (n *NeutrinoNotifier) notificationDispatcher() {
defer n.wg.Done()
for {
select {
case cancelMsg := <-n.notificationCancels:
switch msg := cancelMsg.(type) {
case *epochCancel:
chainntnfs.Log.Infof("Cancelling epoch "+
"notification, epoch_id=%v", msg.epochID)
// First, we'll lookup the original
// registration in order to stop the active
// queue goroutine.
reg := n.blockEpochClients[msg.epochID]
reg.epochQueue.Stop()
// Next, close the cancel channel for this
// specific client, and wait for the client to
// exit.
close(n.blockEpochClients[msg.epochID].cancelChan)
n.blockEpochClients[msg.epochID].wg.Wait()
// Once the client has exited, we can then
// safely close the channel used to send epoch
// notifications, in order to notify any
// listeners that the intent has been
// canceled.
close(n.blockEpochClients[msg.epochID].epochChan)
delete(n.blockEpochClients, msg.epochID)
}
case registerMsg := <-n.notificationRegistry:
switch msg := registerMsg.(type) {
case *chainntnfs.HistoricalConfDispatch:
// We'll start a historical rescan chain of the
// chain asynchronously to prevent blocking
// potentially long rescans.
n.wg.Add(1)
//nolint:ll
go func(msg *chainntnfs.HistoricalConfDispatch) {
defer n.wg.Done()
confDetails, err := n.historicalConfDetails(
msg.ConfRequest,
msg.StartHeight, msg.EndHeight,
)
if err != nil {
chainntnfs.Log.Error(err)
return
}
// If the historical dispatch finished
// without error, we will invoke
// UpdateConfDetails even if none were
// found. This allows the notifier to
// begin safely updating the height hint
// cache at tip, since any pending
// rescans have now completed.
err = n.txNotifier.UpdateConfDetails(
msg.ConfRequest, confDetails,
)
if err != nil {
chainntnfs.Log.Error(err)
}
}(msg)
case *blockEpochRegistration:
chainntnfs.Log.Infof("New block epoch subscription")
n.blockEpochClients[msg.epochID] = msg
// If the client did not provide their best
// known block, then we'll immediately dispatch
// a notification for the current tip.
if msg.bestBlock == nil {
n.notifyBlockEpochClient(
msg, n.bestBlock.Height,
n.bestBlock.Hash,
n.bestBlock.BlockHeader,
)
msg.errorChan <- nil
continue
}
// Otherwise, we'll attempt to deliver the
// backlog of notifications from their best
// known block.
n.bestBlockMtx.Lock()
bestHeight := n.bestBlock.Height
n.bestBlockMtx.Unlock()
missedBlocks, err := chainntnfs.GetClientMissedBlocks(
n.chainConn, msg.bestBlock, bestHeight,
false,
)
if err != nil {
msg.errorChan <- err
continue
}
for _, block := range missedBlocks {
n.notifyBlockEpochClient(
msg, block.Height, block.Hash,
block.BlockHeader,
)
}
msg.errorChan <- nil
case *rescanFilterUpdate:
err := n.chainView.Update(msg.updateOptions...)
if err != nil {
chainntnfs.Log.Errorf("Unable to "+
"update rescan filter: %v", err)
}
// Drain the chainUpdates chan so the caller
// listening on errChan can be sure that
// updates after receiving the error will have
// the filter applied. This allows the caller
// to update their EndHeight if they're
// performing a historical scan.
n.drainChainUpdates()
// After draining, send the error to the
// caller.
msg.errChan <- err
}
case item := <-n.chainUpdates:
update := item
if update.connect {
n.connectFilteredBlock(update)
continue
}
n.disconnectFilteredBlock(update)
case txUpdate := <-n.txUpdates.ChanOut():
// A new relevant transaction notification has been
// received from the backend. We'll attempt to process
// it to determine if it fulfills any outstanding
// confirmation and/or spend requests and dispatch
// notifications for them.
update := txUpdate.(*relevantTx)
err := n.txNotifier.ProcessRelevantSpendTx(
update.tx, uint32(update.details.Height),
)
if err != nil {
chainntnfs.Log.Errorf("Unable to process "+
"transaction %v: %v", update.tx.Hash(),
err)
}
case err := <-n.rescanErr:
chainntnfs.Log.Errorf("Error during rescan: %v", err)
case <-n.quit:
return
}
}
}
// historicalConfDetails looks up whether a confirmation request (txid/output
// script) has already been included in a block in the active chain and, if so,
// returns details about said block.
func (n *NeutrinoNotifier) historicalConfDetails(confRequest chainntnfs.ConfRequest,
startHeight, endHeight uint32) (*chainntnfs.TxConfirmation, error) {
// Starting from the height hint, we'll walk forwards in the chain to
// see if this transaction/output script has already been confirmed.
for scanHeight := endHeight; scanHeight >= startHeight && scanHeight > 0; scanHeight-- {
// Ensure we haven't been requested to shut down before
// processing the next height.
select {
case <-n.quit:
return nil, chainntnfs.ErrChainNotifierShuttingDown
default:
}
// First, we'll fetch the block header for this height so we
// can compute the current block hash.
blockHash, err := n.p2pNode.GetBlockHash(int64(scanHeight))
if err != nil {
return nil, fmt.Errorf("unable to get header for "+
"height=%v: %w", scanHeight, err)
}
// With the hash computed, we can now fetch the basic filter for this
// height. Since the range of required items is known we avoid
// roundtrips by requesting a batched response and save bandwidth by
// limiting the max number of items per batch. Since neutrino populates
// its underline filters cache with the batch response, the next call
// will execute a network query only once per batch and not on every
// iteration.
regFilter, err := n.p2pNode.GetCFilter(
*blockHash, wire.GCSFilterRegular,
neutrino.NumRetries(5),
neutrino.OptimisticReverseBatch(),
neutrino.MaxBatchSize(int64(scanHeight-startHeight+1)),
)
if err != nil {
return nil, fmt.Errorf("unable to retrieve regular "+
"filter for height=%v: %w", scanHeight, err)
}
// In the case that the filter exists, we'll attempt to see if
// any element in it matches our target public key script.
key := builder.DeriveKey(blockHash)
match, err := regFilter.Match(key, confRequest.PkScript.Script())
if err != nil {
return nil, fmt.Errorf("unable to query filter: %w",
err)
}
// If there's no match, then we can continue forward to the
// next block.
if !match {
continue
}
// In the case that we do have a match, we'll fetch the block
// from the network so we can find the positional data required
// to send the proper response.
block, err := n.GetBlock(*blockHash)
if err != nil {
return nil, fmt.Errorf("unable to get block from "+
"network: %w", err)
}
// For every transaction in the block, check which one matches
// our request. If we find one that does, we can dispatch its
// confirmation details.
for i, tx := range block.Transactions() {
if !confRequest.MatchesTx(tx.MsgTx()) {
continue
}
return &chainntnfs.TxConfirmation{
Tx: tx.MsgTx().Copy(),
BlockHash: blockHash,
BlockHeight: scanHeight,
TxIndex: uint32(i),
Block: block.MsgBlock(),
}, nil
}
}
return nil, nil
}
// handleBlockConnected applies a chain update for a new block. Any watched
// transactions included this block will processed to either send notifications
// now or after numConfirmations confs.
//
// NOTE: This method must be called with the bestBlockMtx lock held.
func (n *NeutrinoNotifier) handleBlockConnected(newBlock *filteredBlock) error {
// We'll extend the txNotifier's height with the information of this
// new block, which will handle all of the notification logic for us.
//
// We actually need the _full_ block here as well in order to be able
// to send the full block back up to the client. The neutrino client
// itself will only dispatch a block if one of the items we're looking
// for matches, so ultimately passing it the full block will still only
// result in the items we care about being dispatched.
rawBlock, err := n.GetBlock(newBlock.hash)
if err != nil {
return fmt.Errorf("unable to get full block: %w", err)
}
err = n.txNotifier.ConnectTip(rawBlock, newBlock.height)
if err != nil {
return fmt.Errorf("unable to connect tip: %w", err)
}
chainntnfs.Log.Infof("New block: height=%v, sha=%v", newBlock.height,
newBlock.hash)
// Now that we've guaranteed the new block extends the txNotifier's
// current tip, we'll proceed to dispatch notifications to all of our
// registered clients whom have had notifications fulfilled. Before
// doing so, we'll make sure update our in memory state in order to
// satisfy any client requests based upon the new block.
n.bestBlock.Hash = &newBlock.hash
n.bestBlock.Height = int32(newBlock.height)
n.bestBlock.BlockHeader = newBlock.header
err = n.txNotifier.NotifyHeight(newBlock.height)
if err != nil {
return fmt.Errorf("unable to notify height: %w", err)
}
n.notifyBlockEpochs(
int32(newBlock.height), &newBlock.hash, newBlock.header,
)
return nil
}
// getFilteredBlock is a utility to retrieve the full filtered block from a block epoch.
func (n *NeutrinoNotifier) getFilteredBlock(epoch chainntnfs.BlockEpoch) (*filteredBlock, error) {
rawBlock, err := n.GetBlock(*epoch.Hash)
if err != nil {
return nil, fmt.Errorf("unable to get block: %w", err)
}
txns := rawBlock.Transactions()
block := &filteredBlock{
hash: *epoch.Hash,
height: uint32(epoch.Height),
header: &rawBlock.MsgBlock().Header,
txns: txns,
connect: true,
}
return block, nil
}
// notifyBlockEpochs notifies all registered block epoch clients of the newly
// connected block to the main chain.
func (n *NeutrinoNotifier) notifyBlockEpochs(newHeight int32, newSha *chainhash.Hash,
blockHeader *wire.BlockHeader) {
for _, client := range n.blockEpochClients {
n.notifyBlockEpochClient(client, newHeight, newSha, blockHeader)
}
}
// notifyBlockEpochClient sends a registered block epoch client a notification
// about a specific block.
func (n *NeutrinoNotifier) notifyBlockEpochClient(epochClient *blockEpochRegistration,
height int32, sha *chainhash.Hash, blockHeader *wire.BlockHeader) {
epoch := &chainntnfs.BlockEpoch{
Height: height,
Hash: sha,
BlockHeader: blockHeader,
}
select {
case epochClient.epochQueue.ChanIn() <- epoch:
case <-epochClient.cancelChan:
case <-n.quit:
}
}
// RegisterSpendNtfn registers an intent to be notified once the target
// outpoint/output script has been spent by a transaction on-chain. When
// intending to be notified of the spend of an output script, a nil outpoint
// must be used. The heightHint should represent the earliest height in the
// chain of the transaction that spent the outpoint/output script.
//
// Once a spend of has been detected, the details of the spending event will be
// sent across the 'Spend' channel.
func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) {
// Register the conf notification with the TxNotifier. A non-nil value
// for `dispatch` will be returned if we are required to perform a
// manual scan for the confirmation. Otherwise the notifier will begin
// watching at tip for the transaction to confirm.
ntfn, err := n.txNotifier.RegisterSpend(outpoint, pkScript, heightHint)
if err != nil {
return nil, err
}
// To determine whether this outpoint has been spent on-chain, we'll
// update our filter to watch for the transaction at tip and we'll also
// dispatch a historical rescan to determine if it has been spent in the
// past.
//
// We'll update our filter first to ensure we can immediately detect the
// spend at tip.
if outpoint == nil {
outpoint = &chainntnfs.ZeroOutPoint
}
inputToWatch := neutrino.InputWithScript{
OutPoint: *outpoint,
PkScript: pkScript,
}
updateOptions := []neutrino.UpdateOption{
neutrino.AddInputs(inputToWatch),
neutrino.DisableDisconnectedNtfns(true),
}
// We'll use the txNotifier's tip as the starting point of our filter
// update. In the case of an output script spend request, we'll check if
// we should perform a historical rescan and start from there, as we
// cannot do so with GetUtxo since it matches outpoints.
rewindHeight := ntfn.Height
if ntfn.HistoricalDispatch != nil && *outpoint == chainntnfs.ZeroOutPoint {
rewindHeight = ntfn.HistoricalDispatch.StartHeight
}
updateOptions = append(updateOptions, neutrino.Rewind(rewindHeight))
errChan := make(chan error, 1)
select {
case n.notificationRegistry <- &rescanFilterUpdate{
updateOptions: updateOptions,
errChan: errChan,
}:
case <-n.quit:
return nil, chainntnfs.ErrChainNotifierShuttingDown
}
select {
case err = <-errChan:
case <-n.quit:
return nil, chainntnfs.ErrChainNotifierShuttingDown
}
if err != nil {
return nil, fmt.Errorf("unable to update filter: %w", err)
}
// If the txNotifier didn't return any details to perform a historical
// scan of the chain, or if we already performed one like in the case of
// output script spend requests, then we can return early as there's
// nothing left for us to do.
if ntfn.HistoricalDispatch == nil || *outpoint == chainntnfs.ZeroOutPoint {
return ntfn.Event, nil
}
// Grab the current best height as the height may have been updated
// while we were draining the chainUpdates queue.
n.bestBlockMtx.RLock()
currentHeight := uint32(n.bestBlock.Height)
n.bestBlockMtx.RUnlock()
ntfn.HistoricalDispatch.EndHeight = currentHeight
// With the filter updated, we'll dispatch our historical rescan to
// ensure we detect the spend if it happened in the past.
n.wg.Add(1)
go func() {
defer n.wg.Done()
// We'll ensure that neutrino is caught up to the starting
// height before we attempt to fetch the UTXO from the chain.
// If we're behind, then we may miss a notification dispatch.
for {
n.bestBlockMtx.RLock()
currentHeight := uint32(n.bestBlock.Height)
n.bestBlockMtx.RUnlock()
if currentHeight >= ntfn.HistoricalDispatch.StartHeight {
break
}
select {
case <-time.After(time.Millisecond * 200):
case <-n.quit:
return
}
}
spendReport, err := n.p2pNode.GetUtxo(
neutrino.WatchInputs(inputToWatch),
neutrino.StartBlock(&headerfs.BlockStamp{
Height: int32(ntfn.HistoricalDispatch.StartHeight),
}),
neutrino.EndBlock(&headerfs.BlockStamp{
Height: int32(ntfn.HistoricalDispatch.EndHeight),
}),
neutrino.ProgressHandler(func(processedHeight uint32) {
// We persist the rescan progress to achieve incremental
// behavior across restarts, otherwise long rescans may
// start from the beginning with every restart.
err := n.spendHintCache.CommitSpendHint(
processedHeight,
ntfn.HistoricalDispatch.SpendRequest)
if err != nil {
chainntnfs.Log.Errorf("Failed to update rescan "+
"progress: %v", err)
}
}),
neutrino.QuitChan(n.quit),
)
if err != nil && !strings.Contains(err.Error(), "not found") {
chainntnfs.Log.Errorf("Failed getting UTXO: %v", err)
return
}
// If a spend report was returned, and the transaction is present, then
// this means that the output is already spent.
var spendDetails *chainntnfs.SpendDetail
if spendReport != nil && spendReport.SpendingTx != nil {
spendingTxHash := spendReport.SpendingTx.TxHash()
spendDetails = &chainntnfs.SpendDetail{
SpentOutPoint: outpoint,
SpenderTxHash: &spendingTxHash,
SpendingTx: spendReport.SpendingTx,
SpenderInputIndex: spendReport.SpendingInputIndex,
SpendingHeight: int32(spendReport.SpendingTxHeight),
}
}
// Finally, no matter whether the rescan found a spend in the past or
// not, we'll mark our historical rescan as complete to ensure the
// outpoint's spend hint gets updated upon connected/disconnected
// blocks.
err = n.txNotifier.UpdateSpendDetails(
ntfn.HistoricalDispatch.SpendRequest, spendDetails,
)
if err != nil {
chainntnfs.Log.Errorf("Failed to update spend details: %v", err)
return
}
}()
return ntfn.Event, nil
}
// RegisterConfirmationsNtfn registers an intent to be notified once the target
// txid/output script has reached numConfs confirmations on-chain. When
// intending to be notified of the confirmation of an output script, a nil txid
// must be used. The heightHint should represent the earliest height at which
// the txid/output script could have been included in the chain.
//
// Progress on the number of confirmations left can be read from the 'Updates'
// channel. Once it has reached all of its confirmations, a notification will be
// sent across the 'Confirmed' channel.
func (n *NeutrinoNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
pkScript []byte, numConfs, heightHint uint32,
opts ...chainntnfs.NotifierOption) (*chainntnfs.ConfirmationEvent, error) {
// Register the conf notification with the TxNotifier. A non-nil value
// for `dispatch` will be returned if we are required to perform a
// manual scan for the confirmation. Otherwise the notifier will begin
// watching at tip for the transaction to confirm.
ntfn, err := n.txNotifier.RegisterConf(
txid, pkScript, numConfs, heightHint, opts...,
)
if err != nil {
return nil, err
}
// To determine whether this transaction has confirmed on-chain, we'll
// update our filter to watch for the transaction at tip and we'll also
// dispatch a historical rescan to determine if it has confirmed in the
// past.
//
// We'll update our filter first to ensure we can immediately detect the
// confirmation at tip. To do so, we'll map the script into an address
// type so we can instruct neutrino to match if the transaction
// containing the script is found in a block.
params := n.p2pNode.ChainParams()
_, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript, ¶ms)
if err != nil {
return nil, fmt.Errorf("unable to extract script: %w", err)
}
// We'll send the filter update request to the notifier's main event
// handler and wait for its response.
errChan := make(chan error, 1)
select {
case n.notificationRegistry <- &rescanFilterUpdate{
updateOptions: []neutrino.UpdateOption{
neutrino.AddAddrs(addrs...),
neutrino.Rewind(ntfn.Height),
neutrino.DisableDisconnectedNtfns(true),
},
errChan: errChan,
}:
case <-n.quit:
return nil, chainntnfs.ErrChainNotifierShuttingDown
}
select {
case err = <-errChan:
case <-n.quit:
return nil, chainntnfs.ErrChainNotifierShuttingDown
}
if err != nil {
return nil, fmt.Errorf("unable to update filter: %w", err)
}
// If a historical rescan was not requested by the txNotifier, then we
// can return to the caller.
if ntfn.HistoricalDispatch == nil {
return ntfn.Event, nil
}
// Grab the current best height as the height may have been updated
// while we were draining the chainUpdates queue.
n.bestBlockMtx.RLock()
currentHeight := uint32(n.bestBlock.Height)
n.bestBlockMtx.RUnlock()
ntfn.HistoricalDispatch.EndHeight = currentHeight
// Finally, with the filter updated, we can dispatch the historical
// rescan to ensure we can detect if the event happened in the past.
select {
case n.notificationRegistry <- ntfn.HistoricalDispatch:
case <-n.quit:
return nil, chainntnfs.ErrChainNotifierShuttingDown
}
return ntfn.Event, nil
}
// GetBlock is used to retrieve the block with the given hash. Since the block
// cache used by neutrino will be the same as that used by LND (since it is
// passed to neutrino on initialisation), the neutrino GetBlock method can be
// called directly since it already uses the block cache. However, neutrino
// does not lock the block cache mutex for the given block hash and so that is
// done here.
func (n *NeutrinoNotifier) GetBlock(hash chainhash.Hash) (
*btcutil.Block, error) {
n.blockCache.HashMutex.Lock(lntypes.Hash(hash))
defer n.blockCache.HashMutex.Unlock(lntypes.Hash(hash))
return n.p2pNode.GetBlock(hash)
}
// blockEpochRegistration represents a client's intent to receive a
// notification with each newly connected block.
type blockEpochRegistration struct {
epochID uint64
epochChan chan *chainntnfs.BlockEpoch
epochQueue *queue.ConcurrentQueue
cancelChan chan struct{}
bestBlock *chainntnfs.BlockEpoch
errorChan chan error
wg sync.WaitGroup
}
// epochCancel is a message sent to the NeutrinoNotifier when a client wishes
// to cancel an outstanding epoch notification that has yet to be dispatched.
type epochCancel struct {
epochID uint64
}
// RegisterBlockEpochNtfn returns a BlockEpochEvent which subscribes the
// caller to receive notifications, of each new block connected to the main
// chain. Clients have the option of passing in their best known block, which
// the notifier uses to check if they are behind on blocks and catch them up. If
// they do not provide one, then a notification will be dispatched immediately
// for the current tip of the chain upon a successful registration.
func (n *NeutrinoNotifier) RegisterBlockEpochNtfn(
bestBlock *chainntnfs.BlockEpoch) (*chainntnfs.BlockEpochEvent, error) {
reg := &blockEpochRegistration{
epochQueue: queue.NewConcurrentQueue(20),
epochChan: make(chan *chainntnfs.BlockEpoch, 20),
cancelChan: make(chan struct{}),
epochID: atomic.AddUint64(&n.epochClientCounter, 1),
bestBlock: bestBlock,
errorChan: make(chan error, 1),
}
reg.epochQueue.Start()
// Before we send the request to the main goroutine, we'll launch a new
// goroutine to proxy items added to our queue to the client itself.
// This ensures that all notifications are received *in order*.
reg.wg.Add(1)
go func() {
defer reg.wg.Done()
for {
select {
case ntfn := <-reg.epochQueue.ChanOut():
blockNtfn := ntfn.(*chainntnfs.BlockEpoch)
select {
case reg.epochChan <- blockNtfn:
case <-reg.cancelChan:
return
case <-n.quit:
return
}
case <-reg.cancelChan:
return
case <-n.quit:
return
}
}
}()
select {
case <-n.quit:
// As we're exiting before the registration could be sent,
// we'll stop the queue now ourselves.
reg.epochQueue.Stop()
return nil, errors.New("chainntnfs: system interrupt while " +
"attempting to register for block epoch notification.")
case n.notificationRegistry <- reg:
return &chainntnfs.BlockEpochEvent{
Epochs: reg.epochChan,
Cancel: func() {
cancel := &epochCancel{
epochID: reg.epochID,
}
// Submit epoch cancellation to notification dispatcher.
select {
case n.notificationCancels <- cancel:
// Cancellation is being handled, drain the epoch channel until it is
// closed before yielding to caller.
for {
select {
case _, ok := <-reg.epochChan:
if !ok {
return
}
case <-n.quit:
return
}
}
case <-n.quit:
}
},
}, nil
}
}
// NeutrinoChainConn is a wrapper around neutrino's chain backend in order
// to satisfy the chainntnfs.ChainConn interface.
type NeutrinoChainConn struct {
p2pNode *neutrino.ChainService
}
// GetBlockHeader returns the block header for a hash.
func (n *NeutrinoChainConn) GetBlockHeader(blockHash *chainhash.Hash) (*wire.BlockHeader, error) {
return n.p2pNode.GetBlockHeader(blockHash)
}
// GetBlockHeaderVerbose returns a verbose block header result for a hash. This
// result only contains the height with a nil hash.
func (n *NeutrinoChainConn) GetBlockHeaderVerbose(blockHash *chainhash.Hash) (
*btcjson.GetBlockHeaderVerboseResult, error) {
height, err := n.p2pNode.GetBlockHeight(blockHash)
if err != nil {
return nil, err
}
// Since only the height is used from the result, leave the hash nil.
return &btcjson.GetBlockHeaderVerboseResult{Height: int32(height)}, nil
}
// GetBlockHash returns the hash from a block height.
func (n *NeutrinoChainConn) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) {
return n.p2pNode.GetBlockHash(blockHeight)
}
package chainntnfs
import (
"bytes"
"errors"
"fmt"
"sync"
"sync/atomic"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
)
const (
// ReorgSafetyLimit is the chain depth beyond which it is assumed a
// block will not be reorganized out of the chain. This is used to
// determine when to prune old confirmation requests so that reorgs are
// handled correctly. The average number of blocks in a day is a
// reasonable value to use.
ReorgSafetyLimit = 144
// MaxNumConfs is the maximum number of confirmations that can be
// requested on a transaction.
MaxNumConfs = ReorgSafetyLimit
)
var (
// ZeroHash is the value that should be used as the txid when
// registering for the confirmation of a script on-chain. This allows
// the notifier to match _and_ dispatch upon the inclusion of the script
// on-chain, rather than the txid.
ZeroHash chainhash.Hash
// ZeroOutPoint is the value that should be used as the outpoint when
// registering for the spend of a script on-chain. This allows the
// notifier to match _and_ dispatch upon detecting the spend of the
// script on-chain, rather than the outpoint.
ZeroOutPoint wire.OutPoint
// zeroV1KeyPush is a pkScript that pushes an all-zero 32-byte Taproot
// SegWit v1 key to the stack.
zeroV1KeyPush = [34]byte{
txscript.OP_1, txscript.OP_DATA_32, // 32 byte of zeroes here
}
// ZeroTaprootPkScript is the parsed txscript.PkScript of an empty
// Taproot SegWit v1 key being pushed to the stack. This allows the
// notifier to match _and_ dispatch upon detecting the spend of the
// outpoint on-chain, rather than the pkScript (which cannot be derived
// from the witness alone in the SegWit v1 case).
ZeroTaprootPkScript, _ = txscript.ParsePkScript(zeroV1KeyPush[:])
)
var (
// ErrTxNotifierExiting is an error returned when attempting to interact
// with the TxNotifier but it been shut down.
ErrTxNotifierExiting = errors.New("TxNotifier is exiting")
// ErrNoScript is an error returned when a confirmation/spend
// registration is attempted without providing an accompanying output
// script.
ErrNoScript = errors.New("an output script must be provided")
// ErrNoHeightHint is an error returned when a confirmation/spend
// registration is attempted without providing an accompanying height
// hint.
ErrNoHeightHint = errors.New("a height hint greater than 0 must be " +
"provided")
// ErrNumConfsOutOfRange is an error returned when a confirmation/spend
// registration is attempted and the number of confirmations provided is
// out of range.
ErrNumConfsOutOfRange = fmt.Errorf("number of confirmations must be "+
"between %d and %d", 1, MaxNumConfs)
// ErrEmptyWitnessStack is returned when a spending transaction has an
// empty witness stack. More details in,
// - https://github.com/bitcoin/bitcoin/issues/28730
ErrEmptyWitnessStack = errors.New("witness stack is empty")
)
// rescanState indicates the progression of a registration before the notifier
// can begin dispatching confirmations at tip.
type rescanState byte
const (
// rescanNotStarted is the initial state, denoting that a historical
// dispatch may be required.
rescanNotStarted rescanState = iota
// rescanPending indicates that a dispatch has already been made, and we
// are waiting for its completion. No other rescans should be dispatched
// while in this state.
rescanPending
// rescanComplete signals either that a rescan was dispatched and has
// completed, or that we began watching at tip immediately. In either
// case, the notifier can only dispatch notifications from tip when in
// this state.
rescanComplete
)
// confNtfnSet holds all known, registered confirmation notifications for a
// txid/output script. If duplicates notifications are requested, only one
// historical dispatch will be spawned to ensure redundant scans are not
// permitted. A single conf detail will be constructed and dispatched to all
// interested
// clients.
type confNtfnSet struct {
// ntfns keeps tracks of all the active client notification requests for
// a transaction/output script
ntfns map[uint64]*ConfNtfn
// rescanStatus represents the current rescan state for the
// transaction/output script.
rescanStatus rescanState
// details serves as a cache of the confirmation details of a
// transaction that we'll use to determine if a transaction/output
// script has already confirmed at the time of registration.
// details is also used to make sure that in case of an address reuse
// (funds sent to a previously confirmed script) no additional
// notification is registered which would lead to an inconsistent state.
details *TxConfirmation
}
// newConfNtfnSet constructs a fresh confNtfnSet for a group of clients
// interested in a notification for a particular txid.
func newConfNtfnSet() *confNtfnSet {
return &confNtfnSet{
ntfns: make(map[uint64]*ConfNtfn),
rescanStatus: rescanNotStarted,
}
}
// spendNtfnSet holds all known, registered spend notifications for a spend
// request (outpoint/output script). If duplicate notifications are requested,
// only one historical dispatch will be spawned to ensure redundant scans are
// not permitted.
type spendNtfnSet struct {
// ntfns keeps tracks of all the active client notification requests for
// an outpoint/output script.
ntfns map[uint64]*SpendNtfn
// rescanStatus represents the current rescan state for the spend
// request (outpoint/output script).
rescanStatus rescanState
// details serves as a cache of the spend details for an outpoint/output
// script that we'll use to determine if it has already been spent at
// the time of registration.
details *SpendDetail
}
// newSpendNtfnSet constructs a new spend notification set.
func newSpendNtfnSet() *spendNtfnSet {
return &spendNtfnSet{
ntfns: make(map[uint64]*SpendNtfn),
rescanStatus: rescanNotStarted,
}
}
// ConfRequest encapsulates a request for a confirmation notification of either
// a txid or output script.
type ConfRequest struct {
// TxID is the hash of the transaction for which confirmation
// notifications are requested. If set to a zero hash, then a
// confirmation notification will be dispatched upon inclusion of the
// _script_, rather than the txid.
TxID chainhash.Hash
// PkScript is the public key script of an outpoint created in this
// transaction.
PkScript txscript.PkScript
}
// NewConfRequest creates a request for a confirmation notification of either a
// txid or output script. A nil txid or an allocated ZeroHash can be used to
// dispatch the confirmation notification on the script.
func NewConfRequest(txid *chainhash.Hash, pkScript []byte) (ConfRequest, error) {
var r ConfRequest
outputScript, err := txscript.ParsePkScript(pkScript)
if err != nil {
return r, err
}
// We'll only set a txid for which we'll dispatch a confirmation
// notification on this request if one was provided. Otherwise, we'll
// default to dispatching on the confirmation of the script instead.
if txid != nil {
r.TxID = *txid
}
r.PkScript = outputScript
return r, nil
}
// String returns the string representation of the ConfRequest.
func (r ConfRequest) String() string {
if r.TxID != ZeroHash {
return fmt.Sprintf("txid=%v", r.TxID)
}
return fmt.Sprintf("script=%v", r.PkScript)
}
// MatchesTx determines whether the given transaction satisfies the confirmation
// request. If the confirmation request is for a script, then we'll check all of
// the outputs of the transaction to determine if it matches. Otherwise, we'll
// match on the txid.
func (r ConfRequest) MatchesTx(tx *wire.MsgTx) bool {
scriptMatches := func() bool {
pkScript := r.PkScript.Script()
for _, txOut := range tx.TxOut {
if bytes.Equal(txOut.PkScript, pkScript) {
return true
}
}
return false
}
if r.TxID != ZeroHash {
return r.TxID == tx.TxHash() && scriptMatches()
}
return scriptMatches()
}
// ConfNtfn represents a notifier client's request to receive a notification
// once the target transaction/output script gets sufficient confirmations. The
// client is asynchronously notified via the ConfirmationEvent channels.
type ConfNtfn struct {
// ConfID uniquely identifies the confirmation notification request for
// the specified transaction/output script.
ConfID uint64
// ConfRequest represents either the txid or script we should detect
// inclusion of within the chain.
ConfRequest
// NumConfirmations is the number of confirmations after which the
// notification is to be sent.
NumConfirmations uint32
// Event contains references to the channels that the notifications are
// to be sent over.
Event *ConfirmationEvent
// HeightHint is the minimum height in the chain that we expect to find
// this txid.
HeightHint uint32
// dispatched is false if the confirmed notification has not been sent
// yet.
dispatched bool
// includeBlock is true if the dispatched notification should also have
// the block included with it.
includeBlock bool
// numConfsLeft is the number of confirmations left to be sent to the
// subscriber.
numConfsLeft uint32
}
// HistoricalConfDispatch parametrizes a manual rescan for a particular
// transaction/output script. The parameters include the start and end block
// heights specifying the range of blocks to scan.
type HistoricalConfDispatch struct {
// ConfRequest represents either the txid or script we should detect
// inclusion of within the chain.
ConfRequest
// StartHeight specifies the block height at which to begin the
// historical rescan.
StartHeight uint32
// EndHeight specifies the last block height (inclusive) that the
// historical scan should consider.
EndHeight uint32
}
// ConfRegistration encompasses all of the information required for callers to
// retrieve details about a confirmation event.
type ConfRegistration struct {
// Event contains references to the channels that the notifications are
// to be sent over.
Event *ConfirmationEvent
// HistoricalDispatch, if non-nil, signals to the client who registered
// the notification that they are responsible for attempting to manually
// rescan blocks for the txid/output script between the start and end
// heights.
HistoricalDispatch *HistoricalConfDispatch
// Height is the height of the TxNotifier at the time the confirmation
// notification was registered. This can be used so that backends can
// request to be notified of confirmations from this point forwards.
Height uint32
}
// SpendRequest encapsulates a request for a spend notification of either an
// outpoint or output script.
type SpendRequest struct {
// OutPoint is the outpoint for which a client has requested a spend
// notification for. If set to a zero outpoint, then a spend
// notification will be dispatched upon detecting the spend of the
// _script_, rather than the outpoint.
OutPoint wire.OutPoint
// PkScript is the script of the outpoint. If a zero outpoint is set,
// then this can be an arbitrary script.
PkScript txscript.PkScript
}
// NewSpendRequest creates a request for a spend notification of either an
// outpoint or output script. A nil outpoint or an allocated ZeroOutPoint can be
// used to dispatch the confirmation notification on the script.
func NewSpendRequest(op *wire.OutPoint, pkScript []byte) (SpendRequest, error) {
var r SpendRequest
outputScript, err := txscript.ParsePkScript(pkScript)
if err != nil {
return r, err
}
// We'll only set an outpoint for which we'll dispatch a spend
// notification on this request if one was provided. Otherwise, we'll
// default to dispatching on the spend of the script instead.
if op != nil {
r.OutPoint = *op
}
r.PkScript = outputScript
// For Taproot spends we have the main problem that for the key spend
// path we cannot derive the pkScript from only looking at the input's
// witness. So we need to rely on the outpoint information alone.
//
// TODO(guggero): For script path spends we can derive the pkScript from
// the witness, since we have the full control block and the spent
// script available.
if outputScript.Class() == txscript.WitnessV1TaprootTy {
if op == nil {
return r, fmt.Errorf("cannot register witness v1 " +
"spend request without outpoint")
}
// We have an outpoint, so we can set the pkScript to an all
// zero Taproot key that we'll compare this spend request to.
r.PkScript = ZeroTaprootPkScript
}
return r, nil
}
// String returns the string representation of the SpendRequest.
func (r SpendRequest) String() string {
if r.OutPoint != ZeroOutPoint {
return fmt.Sprintf("outpoint=%v, script=%v", r.OutPoint,
r.PkScript)
}
return fmt.Sprintf("outpoint=<zero>, script=%v", r.PkScript)
}
// MatchesTx determines whether the given transaction satisfies the spend
// request. If the spend request is for an outpoint, then we'll check all of
// the outputs being spent by the inputs of the transaction to determine if it
// matches. Otherwise, we'll need to match on the output script being spent, so
// we'll recompute it for each input of the transaction to determine if it
// matches.
func (r SpendRequest) MatchesTx(tx *wire.MsgTx) (bool, uint32, error) {
if r.OutPoint != ZeroOutPoint {
for i, txIn := range tx.TxIn {
if txIn.PreviousOutPoint == r.OutPoint {
return true, uint32(i), nil
}
}
return false, 0, nil
}
for i, txIn := range tx.TxIn {
pkScript, err := txscript.ComputePkScript(
txIn.SignatureScript, txIn.Witness,
)
if err == txscript.ErrUnsupportedScriptType {
continue
}
if err != nil {
return false, 0, err
}
if bytes.Equal(pkScript.Script(), r.PkScript.Script()) {
return true, uint32(i), nil
}
}
return false, 0, nil
}
// SpendNtfn represents a client's request to receive a notification once an
// outpoint/output script has been spent on-chain. The client is asynchronously
// notified via the SpendEvent channels.
type SpendNtfn struct {
// SpendID uniquely identies the spend notification request for the
// specified outpoint/output script.
SpendID uint64
// SpendRequest represents either the outpoint or script we should
// detect the spend of.
SpendRequest
// Event contains references to the channels that the notifications are
// to be sent over.
Event *SpendEvent
// HeightHint is the earliest height in the chain that we expect to find
// the spending transaction of the specified outpoint/output script.
// This value will be overridden by the spend hint cache if it contains
// an entry for it.
HeightHint uint32
// dispatched signals whether a spend notification has been dispatched
// to the client.
dispatched bool
}
// HistoricalSpendDispatch parametrizes a manual rescan to determine the
// spending details (if any) of an outpoint/output script. The parameters
// include the start and end block heights specifying the range of blocks to
// scan.
type HistoricalSpendDispatch struct {
// SpendRequest represents either the outpoint or script we should
// detect the spend of.
SpendRequest
// StartHeight specified the block height at which to begin the
// historical rescan.
StartHeight uint32
// EndHeight specifies the last block height (inclusive) that the
// historical rescan should consider.
EndHeight uint32
}
// SpendRegistration encompasses all of the information required for callers to
// retrieve details about a spend event.
type SpendRegistration struct {
// Event contains references to the channels that the notifications are
// to be sent over.
Event *SpendEvent
// HistoricalDispatch, if non-nil, signals to the client who registered
// the notification that they are responsible for attempting to manually
// rescan blocks for the txid/output script between the start and end
// heights.
HistoricalDispatch *HistoricalSpendDispatch
// Height is the height of the TxNotifier at the time the spend
// notification was registered. This can be used so that backends can
// request to be notified of spends from this point forwards.
Height uint32
}
// TxNotifier is a struct responsible for delivering transaction notifications
// to subscribers. These notifications can be of two different types:
// transaction/output script confirmations and/or outpoint/output script spends.
// The TxNotifier will watch the blockchain as new blocks come in, in order to
// satisfy its client requests.
type TxNotifier struct {
confClientCounter uint64 // To be used atomically.
spendClientCounter uint64 // To be used atomically.
// currentHeight is the height of the tracked blockchain. It is used to
// determine the number of confirmations a tx has and ensure blocks are
// connected and disconnected in order.
currentHeight uint32
// reorgSafetyLimit is the chain depth beyond which it is assumed a
// block will not be reorganized out of the chain. This is used to
// determine when to prune old notification requests so that reorgs are
// handled correctly. The coinbase maturity period is a reasonable value
// to use.
reorgSafetyLimit uint32
// reorgDepth is the depth of a chain organization that this system is
// being informed of. This is incremented as long as a sequence of
// blocks are disconnected without being interrupted by a new block.
reorgDepth uint32
// confNotifications is an index of confirmation notification requests
// by transaction hash/output script.
confNotifications map[ConfRequest]*confNtfnSet
// confsByInitialHeight is an index of watched transactions/output
// scripts by the height that they are included at in the chain. This
// is tracked so that incorrect notifications are not sent if a
// transaction/output script is reorged out of the chain and so that
// negative confirmations can be recognized.
confsByInitialHeight map[uint32]map[ConfRequest]struct{}
// ntfnsByConfirmHeight is an index of notification requests by the
// height at which the transaction/output script will have sufficient
// confirmations.
ntfnsByConfirmHeight map[uint32]map[*ConfNtfn]struct{}
// spendNotifications is an index of all active notification requests
// per outpoint/output script.
spendNotifications map[SpendRequest]*spendNtfnSet
// spendsByHeight is an index that keeps tracks of the spending height
// of outpoints/output scripts we are currently tracking notifications
// for. This is used in order to recover from spending transactions
// being reorged out of the chain.
spendsByHeight map[uint32]map[SpendRequest]struct{}
// confirmHintCache is a cache used to maintain the latest height hints
// for transactions/output scripts. Each height hint represents the
// earliest height at which they scripts could have been confirmed
// within the chain.
confirmHintCache ConfirmHintCache
// spendHintCache is a cache used to maintain the latest height hints
// for outpoints/output scripts. Each height hint represents the
// earliest height at which they could have been spent within the chain.
spendHintCache SpendHintCache
// quit is closed in order to signal that the notifier is gracefully
// exiting.
quit chan struct{}
sync.Mutex
}
// NewTxNotifier creates a TxNotifier. The current height of the blockchain is
// accepted as a parameter. The different hint caches (confirm and spend) are
// used as an optimization in order to retrieve a better starting point when
// dispatching a rescan for a historical event in the chain.
func NewTxNotifier(startHeight uint32, reorgSafetyLimit uint32,
confirmHintCache ConfirmHintCache,
spendHintCache SpendHintCache) *TxNotifier {
return &TxNotifier{
currentHeight: startHeight,
reorgSafetyLimit: reorgSafetyLimit,
confNotifications: make(map[ConfRequest]*confNtfnSet),
confsByInitialHeight: make(map[uint32]map[ConfRequest]struct{}),
ntfnsByConfirmHeight: make(map[uint32]map[*ConfNtfn]struct{}),
spendNotifications: make(map[SpendRequest]*spendNtfnSet),
spendsByHeight: make(map[uint32]map[SpendRequest]struct{}),
confirmHintCache: confirmHintCache,
spendHintCache: spendHintCache,
quit: make(chan struct{}),
}
}
// newConfNtfn validates all of the parameters required to successfully create
// and register a confirmation notification.
func (n *TxNotifier) newConfNtfn(txid *chainhash.Hash,
pkScript []byte, numConfs, heightHint uint32,
opts *notifierOptions) (*ConfNtfn, error) {
// An accompanying output script must always be provided.
if len(pkScript) == 0 {
return nil, ErrNoScript
}
// Enforce that we will not dispatch confirmations beyond the reorg
// safety limit.
if numConfs == 0 || numConfs > n.reorgSafetyLimit {
return nil, ErrNumConfsOutOfRange
}
// A height hint must be provided to prevent scanning from the genesis
// block.
if heightHint == 0 {
return nil, ErrNoHeightHint
}
// Ensure the output script is of a supported type.
confRequest, err := NewConfRequest(txid, pkScript)
if err != nil {
return nil, err
}
confID := atomic.AddUint64(&n.confClientCounter, 1)
return &ConfNtfn{
ConfID: confID,
ConfRequest: confRequest,
NumConfirmations: numConfs,
Event: NewConfirmationEvent(numConfs, func() {
n.CancelConf(confRequest, confID)
}),
HeightHint: heightHint,
includeBlock: opts.includeBlock,
numConfsLeft: numConfs,
}, nil
}
// RegisterConf handles a new confirmation notification request. The client will
// be notified when the transaction/output script gets a sufficient number of
// confirmations in the blockchain.
//
// NOTE: If the transaction/output script has already been included in a block
// on the chain, the confirmation details must be provided with the
// UpdateConfDetails method, otherwise we will wait for the transaction/output
// script to confirm even though it already has.
func (n *TxNotifier) RegisterConf(txid *chainhash.Hash, pkScript []byte,
numConfs, heightHint uint32,
optFuncs ...NotifierOption) (*ConfRegistration, error) {
select {
case <-n.quit:
return nil, ErrTxNotifierExiting
default:
}
opts := defaultNotifierOptions()
for _, optFunc := range optFuncs {
optFunc(opts)
}
// We'll start by performing a series of validation checks.
ntfn, err := n.newConfNtfn(txid, pkScript, numConfs, heightHint, opts)
if err != nil {
return nil, err
}
// Before proceeding to register the notification, we'll query our
// height hint cache to determine whether a better one exists.
//
// TODO(conner): verify that all submitted height hints are identical.
startHeight := ntfn.HeightHint
hint, err := n.confirmHintCache.QueryConfirmHint(ntfn.ConfRequest)
if err == nil {
if hint > startHeight {
Log.Debugf("Using height hint %d retrieved from cache "+
"for %v instead of %d for conf subscription",
hint, ntfn.ConfRequest, startHeight)
startHeight = hint
}
} else if err != ErrConfirmHintNotFound {
Log.Errorf("Unable to query confirm hint for %v: %v",
ntfn.ConfRequest, err)
}
Log.Infof("New confirmation subscription: conf_id=%d, %v, "+
"num_confs=%v height_hint=%d", ntfn.ConfID, ntfn.ConfRequest,
numConfs, startHeight)
n.Lock()
defer n.Unlock()
confSet, ok := n.confNotifications[ntfn.ConfRequest]
if !ok {
// If this is the first registration for this request, construct
// a confSet to coalesce all notifications for the same request.
confSet = newConfNtfnSet()
n.confNotifications[ntfn.ConfRequest] = confSet
}
confSet.ntfns[ntfn.ConfID] = ntfn
switch confSet.rescanStatus {
// A prior rescan has already completed and we are actively watching at
// tip for this request.
case rescanComplete:
// If the confirmation details for this set of notifications has
// already been found, we'll attempt to deliver them immediately
// to this client.
Log.Debugf("Attempting to dispatch confirmation for %v on "+
"registration since rescan has finished, conf_id=%v",
ntfn.ConfRequest, ntfn.ConfID)
// The default notification we assigned above includes the
// block along with the rest of the details. However not all
// clients want the block, so we make a copy here w/o the block
// if needed so we can give clients only what they ask for.
confDetails := confSet.details
if !ntfn.includeBlock && confDetails != nil {
confDetailsCopy := *confDetails
confDetailsCopy.Block = nil
confDetails = &confDetailsCopy
}
// Deliver the details to the whole conf set where this ntfn
// lives in.
for _, subscriber := range confSet.ntfns {
err := n.dispatchConfDetails(subscriber, confDetails)
if err != nil {
return nil, err
}
}
return &ConfRegistration{
Event: ntfn.Event,
HistoricalDispatch: nil,
Height: n.currentHeight,
}, nil
// A rescan is already in progress, return here to prevent dispatching
// another. When the rescan returns, this notification's details will be
// updated as well.
case rescanPending:
Log.Debugf("Waiting for pending rescan to finish before "+
"notifying %v at tip", ntfn.ConfRequest)
return &ConfRegistration{
Event: ntfn.Event,
HistoricalDispatch: nil,
Height: n.currentHeight,
}, nil
// If no rescan has been dispatched, attempt to do so now.
case rescanNotStarted:
}
// If the provided or cached height hint indicates that the
// transaction with the given txid/output script is to be confirmed at a
// height greater than the notifier's current height, we'll refrain from
// spawning a historical dispatch.
if startHeight > n.currentHeight {
Log.Debugf("Height hint is above current height, not "+
"dispatching historical confirmation rescan for %v",
ntfn.ConfRequest)
// Set the rescan status to complete, which will allow the
// notifier to start delivering messages for this set
// immediately.
confSet.rescanStatus = rescanComplete
return &ConfRegistration{
Event: ntfn.Event,
HistoricalDispatch: nil,
Height: n.currentHeight,
}, nil
}
Log.Debugf("Dispatching historical confirmation rescan for %v",
ntfn.ConfRequest)
// Construct the parameters for historical dispatch, scanning the range
// of blocks between our best known height hint and the notifier's
// current height. The notifier will begin also watching for
// confirmations at tip starting with the next block.
dispatch := &HistoricalConfDispatch{
ConfRequest: ntfn.ConfRequest,
StartHeight: startHeight,
EndHeight: n.currentHeight,
}
// Set this confSet's status to pending, ensuring subsequent
// registrations don't also attempt a dispatch.
confSet.rescanStatus = rescanPending
return &ConfRegistration{
Event: ntfn.Event,
HistoricalDispatch: dispatch,
Height: n.currentHeight,
}, nil
}
// CancelConf cancels an existing request for a spend notification of an
// outpoint/output script. The request is identified by its spend ID.
func (n *TxNotifier) CancelConf(confRequest ConfRequest, confID uint64) {
select {
case <-n.quit:
return
default:
}
n.Lock()
defer n.Unlock()
confSet, ok := n.confNotifications[confRequest]
if !ok {
return
}
ntfn, ok := confSet.ntfns[confID]
if !ok {
return
}
Log.Debugf("Canceling confirmation notification: conf_id=%d, %v",
confID, confRequest)
// We'll close all the notification channels to let the client know
// their cancel request has been fulfilled.
close(ntfn.Event.Confirmed)
close(ntfn.Event.Updates)
close(ntfn.Event.NegativeConf)
// Finally, we'll clean up any lingering references to this
// notification.
delete(confSet.ntfns, confID)
// Remove the queued confirmation notification if the transaction has
// already confirmed, but hasn't met its required number of
// confirmations.
if confSet.details != nil {
confHeight := confSet.details.BlockHeight +
ntfn.NumConfirmations - 1
delete(n.ntfnsByConfirmHeight[confHeight], ntfn)
}
}
// UpdateConfDetails attempts to update the confirmation details for an active
// notification within the notifier. This should only be used in the case of a
// transaction/output script that has confirmed before the notifier's current
// height.
//
// NOTE: The notification should be registered first to ensure notifications are
// dispatched correctly.
func (n *TxNotifier) UpdateConfDetails(confRequest ConfRequest,
details *TxConfirmation) error {
select {
case <-n.quit:
return ErrTxNotifierExiting
default:
}
// Ensure we hold the lock throughout handling the notification to
// prevent the notifier from advancing its height underneath us.
n.Lock()
defer n.Unlock()
// First, we'll determine whether we have an active confirmation
// notification for the given txid/script.
confSet, ok := n.confNotifications[confRequest]
if !ok {
return fmt.Errorf("confirmation notification for %v not found",
confRequest)
}
// If the confirmation details were already found at tip, all existing
// notifications will have been dispatched or queued for dispatch. We
// can exit early to avoid sending too many notifications on the
// buffered channels.
if confSet.details != nil {
return nil
}
// The historical dispatch has been completed for this confSet. We'll
// update the rescan status and cache any details that were found. If
// the details are nil, that implies we did not find them and will
// continue to watch for them at tip.
confSet.rescanStatus = rescanComplete
// The notifier has yet to reach the height at which the
// transaction/output script was included in a block, so we should defer
// until handling it then within ConnectTip.
if details == nil {
Log.Debugf("Confirmation details for %v not found during "+
"historical dispatch, waiting to dispatch at tip",
confRequest)
// We'll commit the current height as the confirm hint to
// prevent another potentially long rescan if we restart before
// a new block comes in.
err := n.confirmHintCache.CommitConfirmHint(
n.currentHeight, confRequest,
)
if err != nil {
// The error is not fatal as this is an optimistic
// optimization, so we'll avoid returning an error.
Log.Debugf("Unable to update confirm hint to %d for "+
"%v: %v", n.currentHeight, confRequest, err)
}
return nil
}
if details.BlockHeight > n.currentHeight {
Log.Debugf("Confirmation details for %v found above current "+
"height, waiting to dispatch at tip", confRequest)
return nil
}
Log.Debugf("Updating confirmation details for %v", confRequest)
err := n.confirmHintCache.CommitConfirmHint(
details.BlockHeight, confRequest,
)
if err != nil {
// The error is not fatal, so we should not return an error to
// the caller.
Log.Errorf("Unable to update confirm hint to %d for %v: %v",
details.BlockHeight, confRequest, err)
}
// Cache the details found in the rescan and attempt to dispatch any
// notifications that have not yet been delivered.
confSet.details = details
for _, ntfn := range confSet.ntfns {
// The default notification we assigned above includes the
// block along with the rest of the details. However not all
// clients want the block, so we make a copy here w/o the block
// if needed so we can give clients only what they ask for.
confDetails := *details
if !ntfn.includeBlock {
confDetails.Block = nil
}
err = n.dispatchConfDetails(ntfn, &confDetails)
if err != nil {
return err
}
}
return nil
}
// dispatchConfDetails attempts to cache and dispatch details to a particular
// client if the transaction/output script has sufficiently confirmed. If the
// provided details are nil, this method will be a no-op.
func (n *TxNotifier) dispatchConfDetails(
ntfn *ConfNtfn, details *TxConfirmation) error {
// If there are no conf details to dispatch or if the notification has
// already been dispatched, then we can skip dispatching to this
// client.
if details == nil {
Log.Debugf("Skipped dispatching nil conf details for request "+
"%v, conf_id=%v", ntfn.ConfRequest, ntfn.ConfID)
return nil
}
if ntfn.dispatched {
Log.Debugf("Skipped dispatched conf details for request %v "+
"conf_id=%v", ntfn.ConfRequest, ntfn.ConfID)
return nil
}
// Now, we'll examine whether the transaction/output script of this
// request has reached its required number of confirmations. If it has,
// we'll dispatch a confirmation notification to the caller.
confHeight := details.BlockHeight + ntfn.NumConfirmations - 1
if confHeight <= n.currentHeight {
Log.Debugf("Dispatching %v confirmation notification for "+
"conf_id=%v, %v", ntfn.NumConfirmations, ntfn.ConfID,
ntfn.ConfRequest)
// We'll send a 0 value to the Updates channel,
// indicating that the transaction/output script has already
// been confirmed.
err := n.notifyNumConfsLeft(ntfn, 0)
if err != nil {
return err
}
select {
case ntfn.Event.Confirmed <- details:
ntfn.dispatched = true
case <-n.quit:
return ErrTxNotifierExiting
}
} else {
Log.Debugf("Queueing %v confirmation notification for %v at "+
"tip", ntfn.NumConfirmations, ntfn.ConfRequest)
// Otherwise, we'll keep track of the notification
// request by the height at which we should dispatch the
// confirmation notification.
ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight]
if !exists {
ntfnSet = make(map[*ConfNtfn]struct{})
n.ntfnsByConfirmHeight[confHeight] = ntfnSet
}
ntfnSet[ntfn] = struct{}{}
// We'll also send an update to the client of how many
// confirmations are left for the transaction/output script to
// be confirmed.
numConfsLeft := confHeight - n.currentHeight
err := n.notifyNumConfsLeft(ntfn, numConfsLeft)
if err != nil {
return err
}
}
// As a final check, we'll also watch the transaction/output script if
// it's still possible for it to get reorged out of the chain.
reorgSafeHeight := details.BlockHeight + n.reorgSafetyLimit
if reorgSafeHeight > n.currentHeight {
txSet, exists := n.confsByInitialHeight[details.BlockHeight]
if !exists {
txSet = make(map[ConfRequest]struct{})
n.confsByInitialHeight[details.BlockHeight] = txSet
}
txSet[ntfn.ConfRequest] = struct{}{}
}
return nil
}
// newSpendNtfn validates all of the parameters required to successfully create
// and register a spend notification.
func (n *TxNotifier) newSpendNtfn(outpoint *wire.OutPoint,
pkScript []byte, heightHint uint32) (*SpendNtfn, error) {
// An accompanying output script must always be provided.
if len(pkScript) == 0 {
return nil, ErrNoScript
}
// A height hint must be provided to prevent scanning from the genesis
// block.
if heightHint == 0 {
return nil, ErrNoHeightHint
}
// Ensure the output script is of a supported type.
spendRequest, err := NewSpendRequest(outpoint, pkScript)
if err != nil {
return nil, err
}
spendID := atomic.AddUint64(&n.spendClientCounter, 1)
return &SpendNtfn{
SpendID: spendID,
SpendRequest: spendRequest,
Event: NewSpendEvent(func() {
n.CancelSpend(spendRequest, spendID)
}),
HeightHint: heightHint,
}, nil
}
// RegisterSpend handles a new spend notification request. The client will be
// notified once the outpoint/output script is detected as spent within the
// chain.
//
// NOTE: If the outpoint/output script has already been spent within the chain
// before the notifier's current tip, the spend details must be provided with
// the UpdateSpendDetails method, otherwise we will wait for the outpoint/output
// script to be spent at tip, even though it already has.
func (n *TxNotifier) RegisterSpend(outpoint *wire.OutPoint, pkScript []byte,
heightHint uint32) (*SpendRegistration, error) {
select {
case <-n.quit:
return nil, ErrTxNotifierExiting
default:
}
// We'll start by performing a series of validation checks.
ntfn, err := n.newSpendNtfn(outpoint, pkScript, heightHint)
if err != nil {
return nil, err
}
// Before proceeding to register the notification, we'll query our spend
// hint cache to determine whether a better one exists.
startHeight := ntfn.HeightHint
hint, err := n.spendHintCache.QuerySpendHint(ntfn.SpendRequest)
if err == nil {
if hint > startHeight {
Log.Debugf("Using height hint %d retrieved from cache "+
"for %v instead of %d for spend subscription",
hint, ntfn.SpendRequest, startHeight)
startHeight = hint
}
} else if err != ErrSpendHintNotFound {
Log.Errorf("Unable to query spend hint for %v: %v",
ntfn.SpendRequest, err)
}
n.Lock()
defer n.Unlock()
Log.Debugf("New spend subscription: spend_id=%d, %v, height_hint=%d",
ntfn.SpendID, ntfn.SpendRequest, startHeight)
// Keep track of the notification request so that we can properly
// dispatch a spend notification later on.
spendSet, ok := n.spendNotifications[ntfn.SpendRequest]
if !ok {
// If this is the first registration for the request, we'll
// construct a spendNtfnSet to coalesce all notifications.
spendSet = newSpendNtfnSet()
n.spendNotifications[ntfn.SpendRequest] = spendSet
}
spendSet.ntfns[ntfn.SpendID] = ntfn
// We'll now let the caller know whether a historical rescan is needed
// depending on the current rescan status.
switch spendSet.rescanStatus {
// If the spending details for this request have already been determined
// and cached, then we can use them to immediately dispatch the spend
// notification to the client.
case rescanComplete:
Log.Debugf("Attempting to dispatch spend for %v on "+
"registration since rescan has finished",
ntfn.SpendRequest)
err := n.dispatchSpendDetails(ntfn, spendSet.details)
if err != nil {
return nil, err
}
return &SpendRegistration{
Event: ntfn.Event,
HistoricalDispatch: nil,
Height: n.currentHeight,
}, nil
// If there is an active rescan to determine whether the request has
// been spent, then we won't trigger another one.
case rescanPending:
Log.Debugf("Waiting for pending rescan to finish before "+
"notifying %v at tip", ntfn.SpendRequest)
return &SpendRegistration{
Event: ntfn.Event,
HistoricalDispatch: nil,
Height: n.currentHeight,
}, nil
// Otherwise, we'll fall through and let the caller know that a rescan
// should be dispatched to determine whether the request has already
// been spent.
case rescanNotStarted:
}
// However, if the spend hint, either provided by the caller or
// retrieved from the cache, is found to be at a later height than the
// TxNotifier is aware of, then we'll refrain from dispatching a
// historical rescan and wait for the spend to come in at tip.
if startHeight > n.currentHeight {
Log.Debugf("Spend hint of %d for %v is above current height %d",
startHeight, ntfn.SpendRequest, n.currentHeight)
// We'll also set the rescan status as complete to ensure that
// spend hints for this request get updated upon
// connected/disconnected blocks.
spendSet.rescanStatus = rescanComplete
return &SpendRegistration{
Event: ntfn.Event,
HistoricalDispatch: nil,
Height: n.currentHeight,
}, nil
}
// We'll set the rescan status to pending to ensure subsequent
// notifications don't also attempt a historical dispatch.
spendSet.rescanStatus = rescanPending
Log.Debugf("Dispatching historical spend rescan for %v, start=%d, "+
"end=%d", ntfn.SpendRequest, startHeight, n.currentHeight)
return &SpendRegistration{
Event: ntfn.Event,
HistoricalDispatch: &HistoricalSpendDispatch{
SpendRequest: ntfn.SpendRequest,
StartHeight: startHeight,
EndHeight: n.currentHeight,
},
Height: n.currentHeight,
}, nil
}
// CancelSpend cancels an existing request for a spend notification of an
// outpoint/output script. The request is identified by its spend ID.
func (n *TxNotifier) CancelSpend(spendRequest SpendRequest, spendID uint64) {
select {
case <-n.quit:
return
default:
}
n.Lock()
defer n.Unlock()
spendSet, ok := n.spendNotifications[spendRequest]
if !ok {
return
}
ntfn, ok := spendSet.ntfns[spendID]
if !ok {
return
}
Log.Debugf("Canceling spend notification: spend_id=%d, %v", spendID,
spendRequest)
// We'll close all the notification channels to let the client know
// their cancel request has been fulfilled.
close(ntfn.Event.Spend)
close(ntfn.Event.Reorg)
close(ntfn.Event.Done)
delete(spendSet.ntfns, spendID)
}
// ProcessRelevantSpendTx processes a transaction provided externally. This will
// check whether the transaction is relevant to the notifier if it spends any
// outpoints/output scripts for which we currently have registered notifications
// for. If it is relevant, spend notifications will be dispatched to the caller.
func (n *TxNotifier) ProcessRelevantSpendTx(tx *btcutil.Tx,
blockHeight uint32) error {
select {
case <-n.quit:
return ErrTxNotifierExiting
default:
}
// Ensure we hold the lock throughout handling the notification to
// prevent the notifier from advancing its height underneath us.
n.Lock()
defer n.Unlock()
// We'll use a channel to coalesce all the spend requests that this
// transaction fulfills.
type spend struct {
request *SpendRequest
details *SpendDetail
}
// We'll set up the onSpend filter callback to gather all the fulfilled
// spends requests within this transaction.
var spends []spend
onSpend := func(request SpendRequest, details *SpendDetail) {
spends = append(spends, spend{&request, details})
}
n.filterTx(nil, tx, blockHeight, nil, onSpend)
// After the transaction has been filtered, we can finally dispatch
// notifications for each request.
for _, spend := range spends {
err := n.updateSpendDetails(*spend.request, spend.details)
if err != nil {
return err
}
}
return nil
}
// UpdateSpendDetails attempts to update the spend details for all active spend
// notification requests for an outpoint/output script. This method should be
// used once a historical scan of the chain has finished. If the historical scan
// did not find a spending transaction for it, the spend details may be nil.
//
// NOTE: A notification request for the outpoint/output script must be
// registered first to ensure notifications are delivered.
func (n *TxNotifier) UpdateSpendDetails(spendRequest SpendRequest,
details *SpendDetail) error {
select {
case <-n.quit:
return ErrTxNotifierExiting
default:
}
// Ensure we hold the lock throughout handling the notification to
// prevent the notifier from advancing its height underneath us.
n.Lock()
defer n.Unlock()
return n.updateSpendDetails(spendRequest, details)
}
// updateSpendDetails attempts to update the spend details for all active spend
// notification requests for an outpoint/output script. This method should be
// used once a historical scan of the chain has finished. If the historical scan
// did not find a spending transaction for it, the spend details may be nil.
//
// NOTE: This method must be called with the TxNotifier's lock held.
func (n *TxNotifier) updateSpendDetails(spendRequest SpendRequest,
details *SpendDetail) error {
// Mark the ongoing historical rescan for this request as finished. This
// will allow us to update the spend hints for it at tip.
spendSet, ok := n.spendNotifications[spendRequest]
if !ok {
return fmt.Errorf("spend notification for %v not found",
spendRequest)
}
// If the spend details have already been found either at tip, then the
// notifications should have already been dispatched, so we can exit
// early to prevent sending duplicate notifications.
if spendSet.details != nil {
return nil
}
// Since the historical rescan has completed for this request, we'll
// mark its rescan status as complete in order to ensure that the
// TxNotifier can properly update its spend hints upon
// connected/disconnected blocks.
spendSet.rescanStatus = rescanComplete
// If the historical rescan was not able to find a spending transaction
// for this request, then we can track the spend at tip.
if details == nil {
// We'll commit the current height as the spend hint to prevent
// another potentially long rescan if we restart before a new
// block comes in.
err := n.spendHintCache.CommitSpendHint(
n.currentHeight, spendRequest,
)
if err != nil {
// The error is not fatal as this is an optimistic
// optimization, so we'll avoid returning an error.
Log.Debugf("Unable to update spend hint to %d for %v: %v",
n.currentHeight, spendRequest, err)
}
Log.Debugf("Updated spend hint to height=%v for unconfirmed "+
"spend request %v", n.currentHeight, spendRequest)
return nil
}
// Return an error if the witness data is not present in the spending
// transaction.
//
// NOTE: if the witness stack is empty, we will do a critical log which
// shuts down the node.
if !details.HasSpenderWitness() {
Log.Criticalf("Found spending tx for outpoint=%v, but the "+
"transaction %v does not have witness",
spendRequest.OutPoint, details.SpendingTx.TxHash())
return ErrEmptyWitnessStack
}
// If the historical rescan found the spending transaction for this
// request, but it's at a later height than the notifier (this can
// happen due to latency with the backend during a reorg), then we'll
// defer handling the notification until the notifier has caught up to
// such height.
if uint32(details.SpendingHeight) > n.currentHeight {
return nil
}
// Now that we've determined the request has been spent, we'll commit
// its spending height as its hint in the cache and dispatch
// notifications to all of its respective clients.
err := n.spendHintCache.CommitSpendHint(
uint32(details.SpendingHeight), spendRequest,
)
if err != nil {
// The error is not fatal as this is an optimistic optimization,
// so we'll avoid returning an error.
Log.Debugf("Unable to update spend hint to %d for %v: %v",
details.SpendingHeight, spendRequest, err)
}
Log.Debugf("Updated spend hint to height=%v for confirmed spend "+
"request %v", details.SpendingHeight, spendRequest)
spendSet.details = details
for _, ntfn := range spendSet.ntfns {
err := n.dispatchSpendDetails(ntfn, spendSet.details)
if err != nil {
return err
}
}
return nil
}
// dispatchSpendDetails dispatches a spend notification to the client.
//
// NOTE: This must be called with the TxNotifier's lock held.
func (n *TxNotifier) dispatchSpendDetails(ntfn *SpendNtfn, details *SpendDetail) error {
// If there are no spend details to dispatch or if the notification has
// already been dispatched, then we can skip dispatching to this client.
if details == nil || ntfn.dispatched {
Log.Debugf("Skipping dispatch of spend details(%v) for "+
"request %v, dispatched=%v", details, ntfn.SpendRequest,
ntfn.dispatched)
return nil
}
Log.Debugf("Dispatching confirmed spend notification for %v at "+
"current height=%d: %v", ntfn.SpendRequest, n.currentHeight,
details)
select {
case ntfn.Event.Spend <- details:
ntfn.dispatched = true
case <-n.quit:
return ErrTxNotifierExiting
}
spendHeight := uint32(details.SpendingHeight)
// We also add to spendsByHeight to notify on chain reorgs.
reorgSafeHeight := spendHeight + n.reorgSafetyLimit
if reorgSafeHeight > n.currentHeight {
txSet, exists := n.spendsByHeight[spendHeight]
if !exists {
txSet = make(map[SpendRequest]struct{})
n.spendsByHeight[spendHeight] = txSet
}
txSet[ntfn.SpendRequest] = struct{}{}
}
return nil
}
// ConnectTip handles a new block extending the current chain. It will go
// through every transaction and determine if it is relevant to any of its
// clients. A transaction can be relevant in either of the following two ways:
//
// 1. One of the inputs in the transaction spends an outpoint/output script
// for which we currently have an active spend registration for.
//
// 2. The transaction has a txid or output script for which we currently have
// an active confirmation registration for.
//
// In the event that the transaction is relevant, a confirmation/spend
// notification will be queued for dispatch to the relevant clients.
// Confirmation notifications will only be dispatched for transactions/output
// scripts that have met the required number of confirmations required by the
// client.
//
// NOTE: In order to actually dispatch the relevant transaction notifications to
// clients, NotifyHeight must be called with the same block height in order to
// maintain correctness.
func (n *TxNotifier) ConnectTip(block *btcutil.Block,
blockHeight uint32) error {
select {
case <-n.quit:
return ErrTxNotifierExiting
default:
}
n.Lock()
defer n.Unlock()
if blockHeight != n.currentHeight+1 {
return fmt.Errorf("received blocks out of order: "+
"current height=%d, new height=%d",
n.currentHeight, blockHeight)
}
n.currentHeight++
n.reorgDepth = 0
// First, we'll iterate over all the transactions found in this block to
// determine if it includes any relevant transactions to the TxNotifier.
if block != nil {
Log.Debugf("Filtering %d txns for %d spend requests at "+
"height %d", len(block.Transactions()),
len(n.spendNotifications), blockHeight)
for _, tx := range block.Transactions() {
n.filterTx(
block, tx, blockHeight,
n.handleConfDetailsAtTip,
n.handleSpendDetailsAtTip,
)
}
}
// Now that we've determined which requests were confirmed and spent
// within the new block, we can update their entries in their respective
// caches, along with all of our unconfirmed and unspent requests.
n.updateHints(blockHeight)
// Finally, we'll clear the entries from our set of notifications for
// requests that are no longer under the risk of being reorged out of
// the chain.
if blockHeight >= n.reorgSafetyLimit {
matureBlockHeight := blockHeight - n.reorgSafetyLimit
for confRequest := range n.confsByInitialHeight[matureBlockHeight] {
confSet := n.confNotifications[confRequest]
for _, ntfn := range confSet.ntfns {
select {
case ntfn.Event.Done <- struct{}{}:
case <-n.quit:
return ErrTxNotifierExiting
}
}
delete(n.confNotifications, confRequest)
}
delete(n.confsByInitialHeight, matureBlockHeight)
for spendRequest := range n.spendsByHeight[matureBlockHeight] {
spendSet := n.spendNotifications[spendRequest]
for _, ntfn := range spendSet.ntfns {
select {
case ntfn.Event.Done <- struct{}{}:
case <-n.quit:
return ErrTxNotifierExiting
}
}
Log.Debugf("Deleting mature spend request %v at "+
"height=%d", spendRequest, blockHeight)
delete(n.spendNotifications, spendRequest)
}
delete(n.spendsByHeight, matureBlockHeight)
}
return nil
}
// filterTx determines whether the transaction spends or confirms any
// outstanding pending requests. The onConf and onSpend callbacks can be used to
// retrieve all the requests fulfilled by this transaction as they occur.
func (n *TxNotifier) filterTx(block *btcutil.Block, tx *btcutil.Tx,
blockHeight uint32, onConf func(ConfRequest, *TxConfirmation),
onSpend func(SpendRequest, *SpendDetail)) {
// In order to determine if this transaction is relevant to the
// notifier, we'll check its inputs for any outstanding spend
// requests.
txHash := tx.Hash()
if onSpend != nil {
// notifyDetails is a helper closure that will construct the
// spend details of a request and hand them off to the onSpend
// callback.
notifyDetails := func(spendRequest SpendRequest,
prevOut wire.OutPoint, inputIdx uint32) {
Log.Debugf("Found spend of %v: spend_tx=%v, "+
"block_height=%d", spendRequest, txHash,
blockHeight)
onSpend(spendRequest, &SpendDetail{
SpentOutPoint: &prevOut,
SpenderTxHash: txHash,
SpendingTx: tx.MsgTx(),
SpenderInputIndex: inputIdx,
SpendingHeight: int32(blockHeight),
})
}
for i, txIn := range tx.MsgTx().TxIn {
// We'll re-derive the script of the output being spent
// to determine if the inputs spends any registered
// requests.
prevOut := txIn.PreviousOutPoint
pkScript, err := txscript.ComputePkScript(
txIn.SignatureScript, txIn.Witness,
)
if err != nil {
continue
}
spendRequest := SpendRequest{
OutPoint: prevOut,
PkScript: pkScript,
}
// If we have any, we'll record their spend height so
// that notifications get dispatched to the respective
// clients.
if _, ok := n.spendNotifications[spendRequest]; ok {
notifyDetails(spendRequest, prevOut, uint32(i))
}
// Now try with an empty taproot key pkScript, since we
// cannot derive the spent pkScript directly from the
// witness. But we have the outpoint, which should be
// enough.
spendRequest.PkScript = ZeroTaprootPkScript
if _, ok := n.spendNotifications[spendRequest]; ok {
notifyDetails(spendRequest, prevOut, uint32(i))
}
// Restore the pkScript but try with a zero outpoint
// instead (won't be possible for Taproot).
spendRequest.PkScript = pkScript
spendRequest.OutPoint = ZeroOutPoint
if _, ok := n.spendNotifications[spendRequest]; ok {
notifyDetails(spendRequest, prevOut, uint32(i))
}
}
}
// We'll also check its outputs to determine if there are any
// outstanding confirmation requests.
if onConf != nil {
// notifyDetails is a helper closure that will construct the
// confirmation details of a request and hand them off to the
// onConf callback.
notifyDetails := func(confRequest ConfRequest) {
Log.Debugf("Found initial confirmation of %v: "+
"height=%d, hash=%v", confRequest,
blockHeight, block.Hash())
details := &TxConfirmation{
Tx: tx.MsgTx(),
BlockHash: block.Hash(),
BlockHeight: blockHeight,
TxIndex: uint32(tx.Index()),
Block: block.MsgBlock(),
}
onConf(confRequest, details)
}
for _, txOut := range tx.MsgTx().TxOut {
// We'll parse the script of the output to determine if
// we have any registered requests for it or the
// transaction itself.
pkScript, err := txscript.ParsePkScript(txOut.PkScript)
if err != nil {
continue
}
confRequest := ConfRequest{
TxID: *txHash,
PkScript: pkScript,
}
// If we have any, we'll record their confirmed height
// so that notifications get dispatched when they
// reaches the clients' desired number of confirmations.
if _, ok := n.confNotifications[confRequest]; ok {
notifyDetails(confRequest)
}
confRequest.TxID = ZeroHash
if _, ok := n.confNotifications[confRequest]; ok {
notifyDetails(confRequest)
}
}
}
}
// handleConfDetailsAtTip tracks the confirmation height of the txid/output
// script in order to properly dispatch a confirmation notification after
// meeting each request's desired number of confirmations for all current and
// future registered clients.
func (n *TxNotifier) handleConfDetailsAtTip(confRequest ConfRequest,
details *TxConfirmation) {
// TODO(wilmer): cancel pending historical rescans if any?
confSet := n.confNotifications[confRequest]
// If we already have details for this request, we don't want to add it
// again since we have already dispatched notifications for it.
if confSet.details != nil {
Log.Warnf("Ignoring address reuse for %s at height %d.",
confRequest, details.BlockHeight)
return
}
confSet.rescanStatus = rescanComplete
confSet.details = details
for _, ntfn := range confSet.ntfns {
// In the event that this notification was aware that the
// transaction/output script was reorged out of the chain, we'll
// consume the reorg notification if it hasn't been done yet
// already.
select {
case <-ntfn.Event.NegativeConf:
default:
}
// We'll note this client's required number of confirmations so
// that we can notify them when expected.
confHeight := details.BlockHeight + ntfn.NumConfirmations - 1
ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight]
if !exists {
ntfnSet = make(map[*ConfNtfn]struct{})
n.ntfnsByConfirmHeight[confHeight] = ntfnSet
}
ntfnSet[ntfn] = struct{}{}
}
// We'll also note the initial confirmation height in order to correctly
// handle dispatching notifications when the transaction/output script
// gets reorged out of the chain.
txSet, exists := n.confsByInitialHeight[details.BlockHeight]
if !exists {
txSet = make(map[ConfRequest]struct{})
n.confsByInitialHeight[details.BlockHeight] = txSet
}
txSet[confRequest] = struct{}{}
}
// handleSpendDetailsAtTip tracks the spend height of the outpoint/output script
// in order to properly dispatch a spend notification for all current and future
// registered clients.
func (n *TxNotifier) handleSpendDetailsAtTip(spendRequest SpendRequest,
details *SpendDetail) {
// TODO(wilmer): cancel pending historical rescans if any?
spendSet := n.spendNotifications[spendRequest]
spendSet.rescanStatus = rescanComplete
spendSet.details = details
for _, ntfn := range spendSet.ntfns {
// In the event that this notification was aware that the
// spending transaction of its outpoint/output script was
// reorged out of the chain, we'll consume the reorg
// notification if it hasn't been done yet already.
select {
case <-ntfn.Event.Reorg:
default:
}
}
// We'll note the spending height of the request in order to correctly
// handle dispatching notifications when the spending transactions gets
// reorged out of the chain.
spendHeight := uint32(details.SpendingHeight)
opSet, exists := n.spendsByHeight[spendHeight]
if !exists {
opSet = make(map[SpendRequest]struct{})
n.spendsByHeight[spendHeight] = opSet
}
opSet[spendRequest] = struct{}{}
Log.Debugf("Spend request %v spent at tip=%d", spendRequest,
spendHeight)
}
// NotifyHeight dispatches confirmation and spend notifications to the clients
// who registered for a notification which has been fulfilled at the passed
// height.
func (n *TxNotifier) NotifyHeight(height uint32) error {
n.Lock()
defer n.Unlock()
// First, we'll dispatch an update to all of the notification clients
// for our watched requests with the number of confirmations left at
// this new height.
for _, confRequests := range n.confsByInitialHeight {
for confRequest := range confRequests {
confSet := n.confNotifications[confRequest]
for _, ntfn := range confSet.ntfns {
txConfHeight := confSet.details.BlockHeight +
ntfn.NumConfirmations - 1
numConfsLeft := txConfHeight - height
// Since we don't clear notifications until
// transactions/output scripts are no longer
// under the risk of being reorganized out of
// the chain, we'll skip sending updates for
// those that have already been confirmed.
if int32(numConfsLeft) < 0 {
continue
}
err := n.notifyNumConfsLeft(ntfn, numConfsLeft)
if err != nil {
return err
}
}
}
}
// Then, we'll dispatch notifications for all the requests that have
// become confirmed at this new block height.
for ntfn := range n.ntfnsByConfirmHeight[height] {
confSet := n.confNotifications[ntfn.ConfRequest]
// The default notification we assigned above includes the
// block along with the rest of the details. However not all
// clients want the block, so we make a copy here w/o the block
// if needed so we can give clients only what they ask for.
confDetails := *confSet.details
if !ntfn.includeBlock {
confDetails.Block = nil
}
// If the `confDetails` has already been sent before, we'll
// skip it and continue processing the next one.
if ntfn.dispatched {
Log.Debugf("Skipped dispatched conf details for "+
"request %v conf_id=%v", ntfn.ConfRequest,
ntfn.ConfID)
continue
}
Log.Debugf("Dispatching %v confirmation notification for "+
"conf_id=%v, %v", ntfn.NumConfirmations, ntfn.ConfID,
ntfn.ConfRequest)
select {
case ntfn.Event.Confirmed <- &confDetails:
ntfn.dispatched = true
case <-n.quit:
return ErrTxNotifierExiting
}
}
delete(n.ntfnsByConfirmHeight, height)
// Finally, we'll dispatch spend notifications for all the requests that
// were spent at this new block height.
for spendRequest := range n.spendsByHeight[height] {
spendSet := n.spendNotifications[spendRequest]
for _, ntfn := range spendSet.ntfns {
err := n.dispatchSpendDetails(ntfn, spendSet.details)
if err != nil {
return err
}
}
}
return nil
}
// DisconnectTip handles the tip of the current chain being disconnected during
// a chain reorganization. If any watched requests were included in this block,
// internal structures are updated to ensure confirmation/spend notifications
// are consumed (if not already), and reorg notifications are dispatched
// instead. Confirmation/spend notifications will be dispatched again upon block
// inclusion.
func (n *TxNotifier) DisconnectTip(blockHeight uint32) error {
select {
case <-n.quit:
return ErrTxNotifierExiting
default:
}
n.Lock()
defer n.Unlock()
if blockHeight != n.currentHeight {
return fmt.Errorf("received blocks out of order: "+
"current height=%d, disconnected height=%d",
n.currentHeight, blockHeight)
}
n.currentHeight--
n.reorgDepth++
// With the block disconnected, we'll update the confirm and spend hints
// for our notification requests to reflect the new height, except for
// those that have confirmed/spent at previous heights.
n.updateHints(blockHeight)
// We'll go through all of our watched confirmation requests and attempt
// to drain their notification channels to ensure sending notifications
// to the clients is always non-blocking.
for initialHeight, txHashes := range n.confsByInitialHeight {
for txHash := range txHashes {
// If the transaction/output script has been reorged out
// of the chain, we'll make sure to remove the cached
// confirmation details to prevent notifying clients
// with old information.
confSet := n.confNotifications[txHash]
if initialHeight == blockHeight {
confSet.details = nil
}
for _, ntfn := range confSet.ntfns {
// First, we'll attempt to drain an update
// from each notification to ensure sends to the
// Updates channel are always non-blocking.
select {
case <-ntfn.Event.Updates:
case <-n.quit:
return ErrTxNotifierExiting
default:
}
// We also reset the num of confs update.
ntfn.numConfsLeft = ntfn.NumConfirmations
// Then, we'll check if the current
// transaction/output script was included in the
// block currently being disconnected. If it
// was, we'll need to dispatch a reorg
// notification to the client.
if initialHeight == blockHeight {
err := n.dispatchConfReorg(
ntfn, blockHeight,
)
if err != nil {
return err
}
}
}
}
}
// We'll also go through our watched spend requests and attempt to drain
// their dispatched notifications to ensure dispatching notifications to
// clients later on is always non-blocking. We're only interested in
// requests whose spending transaction was included at the height being
// disconnected.
for op := range n.spendsByHeight[blockHeight] {
// Since the spending transaction is being reorged out of the
// chain, we'll need to clear out the spending details of the
// request.
spendSet := n.spendNotifications[op]
spendSet.details = nil
// For all requests which have had a spend notification
// dispatched, we'll attempt to drain it and send a reorg
// notification instead.
for _, ntfn := range spendSet.ntfns {
if err := n.dispatchSpendReorg(ntfn); err != nil {
return err
}
}
}
// Finally, we can remove the requests that were confirmed and/or spent
// at the height being disconnected. We'll still continue to track them
// until they have been confirmed/spent and are no longer under the risk
// of being reorged out of the chain again.
delete(n.confsByInitialHeight, blockHeight)
delete(n.spendsByHeight, blockHeight)
return nil
}
// updateHints attempts to update the confirm and spend hints for all relevant
// requests respectively. The height parameter is used to determine which
// requests we should update based on whether a new block is being
// connected/disconnected.
//
// NOTE: This must be called with the TxNotifier's lock held and after its
// height has already been reflected by a block being connected/disconnected.
func (n *TxNotifier) updateHints(height uint32) {
// TODO(wilmer): update under one database transaction.
//
// To update the height hint for all the required confirmation requests
// under one database transaction, we'll gather the set of unconfirmed
// requests along with the ones that confirmed at the height being
// connected/disconnected.
confRequests := n.unconfirmedRequests()
for confRequest := range n.confsByInitialHeight[height] {
confRequests = append(confRequests, confRequest)
}
err := n.confirmHintCache.CommitConfirmHint(
n.currentHeight, confRequests...,
)
if err != nil {
// The error is not fatal as this is an optimistic optimization,
// so we'll avoid returning an error.
Log.Debugf("Unable to update confirm hints to %d for "+
"%v: %v", n.currentHeight, confRequests, err)
}
// Similarly, to update the height hint for all the required spend
// requests under one database transaction, we'll gather the set of
// unspent requests along with the ones that were spent at the height
// being connected/disconnected.
spendRequests := n.unspentRequests()
for spendRequest := range n.spendsByHeight[height] {
spendRequests = append(spendRequests, spendRequest)
}
err = n.spendHintCache.CommitSpendHint(n.currentHeight, spendRequests...)
if err != nil {
// The error is not fatal as this is an optimistic optimization,
// so we'll avoid returning an error.
Log.Debugf("Unable to update spend hints to %d for "+
"%v: %v", n.currentHeight, spendRequests, err)
}
}
// unconfirmedRequests returns the set of confirmation requests that are
// still seen as unconfirmed by the TxNotifier.
//
// NOTE: This method must be called with the TxNotifier's lock held.
func (n *TxNotifier) unconfirmedRequests() []ConfRequest {
var unconfirmed []ConfRequest
for confRequest, confNtfnSet := range n.confNotifications {
// If the notification is already aware of its confirmation
// details, or it's in the process of learning them, we'll skip
// it as we can't yet determine if it's confirmed or not.
if confNtfnSet.rescanStatus != rescanComplete ||
confNtfnSet.details != nil {
continue
}
unconfirmed = append(unconfirmed, confRequest)
}
return unconfirmed
}
// unspentRequests returns the set of spend requests that are still seen as
// unspent by the TxNotifier.
//
// NOTE: This method must be called with the TxNotifier's lock held.
func (n *TxNotifier) unspentRequests() []SpendRequest {
var unspent []SpendRequest
for spendRequest, spendNtfnSet := range n.spendNotifications {
// If the notification is already aware of its spend details, or
// it's in the process of learning them, we'll skip it as we
// can't yet determine if it's unspent or not.
if spendNtfnSet.rescanStatus != rescanComplete ||
spendNtfnSet.details != nil {
continue
}
unspent = append(unspent, spendRequest)
}
return unspent
}
// dispatchConfReorg dispatches a reorg notification to the client if the
// confirmation notification was already delivered.
//
// NOTE: This must be called with the TxNotifier's lock held.
func (n *TxNotifier) dispatchConfReorg(ntfn *ConfNtfn,
heightDisconnected uint32) error {
// If the request's confirmation notification has yet to be dispatched,
// we'll need to clear its entry within the ntfnsByConfirmHeight index
// to prevent from notifying the client once the notifier reaches the
// confirmation height.
if !ntfn.dispatched {
confHeight := heightDisconnected + ntfn.NumConfirmations - 1
ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight]
if exists {
delete(ntfnSet, ntfn)
}
return nil
}
// Otherwise, the entry within the ntfnsByConfirmHeight has already been
// deleted, so we'll attempt to drain the confirmation notification to
// ensure sends to the Confirmed channel are always non-blocking.
select {
case <-ntfn.Event.Confirmed:
case <-n.quit:
return ErrTxNotifierExiting
default:
}
ntfn.dispatched = false
// Send a negative confirmation notification to the client indicating
// how many blocks have been disconnected successively.
select {
case ntfn.Event.NegativeConf <- int32(n.reorgDepth):
case <-n.quit:
return ErrTxNotifierExiting
}
return nil
}
// dispatchSpendReorg dispatches a reorg notification to the client if a spend
// notiification was already delivered.
//
// NOTE: This must be called with the TxNotifier's lock held.
func (n *TxNotifier) dispatchSpendReorg(ntfn *SpendNtfn) error {
if !ntfn.dispatched {
return nil
}
// Attempt to drain the spend notification to ensure sends to the Spend
// channel are always non-blocking.
select {
case <-ntfn.Event.Spend:
default:
}
// Send a reorg notification to the client in order for them to
// correctly handle reorgs.
select {
case ntfn.Event.Reorg <- struct{}{}:
case <-n.quit:
return ErrTxNotifierExiting
}
ntfn.dispatched = false
return nil
}
// TearDown is to be called when the owner of the TxNotifier is exiting. This
// closes the event channels of all registered notifications that have not been
// dispatched yet.
func (n *TxNotifier) TearDown() {
close(n.quit)
n.Lock()
defer n.Unlock()
for _, confSet := range n.confNotifications {
for confID, ntfn := range confSet.ntfns {
close(ntfn.Event.Confirmed)
close(ntfn.Event.Updates)
close(ntfn.Event.NegativeConf)
close(ntfn.Event.Done)
delete(confSet.ntfns, confID)
}
}
for _, spendSet := range n.spendNotifications {
for spendID, ntfn := range spendSet.ntfns {
close(ntfn.Event.Spend)
close(ntfn.Event.Reorg)
close(ntfn.Event.Done)
delete(spendSet.ntfns, spendID)
}
}
}
// notifyNumConfsLeft sends the number of confirmations left to the
// notification subscriber through the Event.Updates channel.
//
// NOTE: must be used with the TxNotifier's lock held.
func (n *TxNotifier) notifyNumConfsLeft(ntfn *ConfNtfn, num uint32) error {
// If the number left is no less than the recorded value, we can skip
// sending it as it means this same value has already been sent before.
if num >= ntfn.numConfsLeft {
Log.Debugf("Skipped dispatched update (numConfsLeft=%v) for "+
"request %v conf_id=%v", num, ntfn.ConfRequest,
ntfn.ConfID)
return nil
}
// Update the number of confirmations left to the notification.
ntfn.numConfsLeft = num
select {
case ntfn.Event.Updates <- num:
case <-n.quit:
return ErrTxNotifierExiting
}
return nil
}
package chainreg
import (
bitcoinCfg "github.com/btcsuite/btcd/chaincfg"
bitcoinWire "github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/keychain"
)
// BitcoinNetParams couples the p2p parameters of a network with the
// corresponding RPC port of a daemon running on the particular network.
type BitcoinNetParams struct {
*bitcoinCfg.Params
RPCPort string
CoinType uint32
}
// BitcoinTestNetParams contains parameters specific to the 3rd version of the
// test network.
var BitcoinTestNetParams = BitcoinNetParams{
Params: &bitcoinCfg.TestNet3Params,
RPCPort: "18334",
CoinType: keychain.CoinTypeTestnet,
}
// BitcoinTestNet4Params contains parameters specific to the 4th version of the
// test network.
var BitcoinTestNet4Params = BitcoinNetParams{
Params: &bitcoinCfg.TestNet4Params,
RPCPort: "48334",
CoinType: keychain.CoinTypeTestnet,
}
// BitcoinMainNetParams contains parameters specific to the current Bitcoin
// mainnet.
var BitcoinMainNetParams = BitcoinNetParams{
Params: &bitcoinCfg.MainNetParams,
RPCPort: "8334",
CoinType: keychain.CoinTypeBitcoin,
}
// BitcoinSimNetParams contains parameters specific to the simulation test
// network.
var BitcoinSimNetParams = BitcoinNetParams{
Params: &bitcoinCfg.SimNetParams,
RPCPort: "18556",
CoinType: keychain.CoinTypeTestnet,
}
// BitcoinSigNetParams contains parameters specific to the signet test network.
var BitcoinSigNetParams = BitcoinNetParams{
Params: &bitcoinCfg.SigNetParams,
RPCPort: "38332",
CoinType: keychain.CoinTypeTestnet,
}
// BitcoinRegTestNetParams contains parameters specific to a local bitcoin
// regtest network.
var BitcoinRegTestNetParams = BitcoinNetParams{
Params: &bitcoinCfg.RegressionNetParams,
RPCPort: "18334",
CoinType: keychain.CoinTypeTestnet,
}
// IsTestnet tests if the given params correspond to a testnet parameter
// configuration.
func IsTestnet(params *BitcoinNetParams) bool {
return params.Params.Net == bitcoinWire.TestNet3 ||
params.Params.Net == bitcoinWire.TestNet4
}
package chainreg
import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/url"
"os"
"strconv"
"strings"
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcwallet/chain"
"github.com/lightninglabs/neutrino"
"github.com/lightningnetwork/lnd/blockcache"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/chainntnfs/bitcoindnotify"
"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
"github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/chainview"
"github.com/lightningnetwork/lnd/walletunlocker"
)
// Config houses necessary fields that a chainControl instance needs to
// function.
type Config struct {
// Bitcoin defines settings for the Bitcoin chain.
Bitcoin *lncfg.Chain
// HeightHintCacheQueryDisable is a boolean that disables height hint
// queries if true.
HeightHintCacheQueryDisable bool
// NeutrinoMode defines settings for connecting to a neutrino
// light-client.
NeutrinoMode *lncfg.Neutrino
// BitcoindMode defines settings for connecting to a bitcoind node.
BitcoindMode *lncfg.Bitcoind
// BtcdMode defines settings for connecting to a btcd node.
BtcdMode *lncfg.Btcd
// HeightHintDB is a pointer to the database that stores the height
// hints.
HeightHintDB kvdb.Backend
// ChanStateDB is a pointer to the database that stores the channel
// state.
ChanStateDB *channeldb.ChannelStateDB
// AuxLeafStore is an optional store that can be used to store auxiliary
// leaves for certain custom channel types.
AuxLeafStore fn.Option[lnwallet.AuxLeafStore]
// AuxSigner is an optional signer that can be used to sign auxiliary
// leaves for certain custom channel types.
AuxSigner fn.Option[lnwallet.AuxSigner]
// BlockCache is the main cache for storing block information.
BlockCache *blockcache.BlockCache
// WalletUnlockParams are the parameters that were used for unlocking
// the main wallet.
WalletUnlockParams *walletunlocker.WalletUnlockParams
// NeutrinoCS is a pointer to a neutrino ChainService. Must be non-nil
// if using neutrino.
NeutrinoCS *neutrino.ChainService
// ActiveNetParams details the current chain we are on.
ActiveNetParams BitcoinNetParams
// Deprecated: Use Fee.URL. FeeURL defines the URL for fee estimation
// we will use. This field is optional.
FeeURL string
// Fee defines settings for the web fee estimator. This field is
// optional.
Fee *lncfg.Fee
// Dialer is a function closure that will be used to establish outbound
// TCP connections to Bitcoin peers in the event of a pruned block being
// requested.
Dialer chain.Dialer
}
const (
// DefaultBitcoinMinHTLCInMSat is the default smallest value htlc this
// node will accept. This value is proposed in the channel open sequence
// and cannot be changed during the life of the channel. It is 1 msat by
// default to allow maximum flexibility in deciding what size payments
// to forward.
//
// All forwarded payments are subjected to the min htlc constraint of
// the routing policy of the outgoing channel. This implicitly controls
// the minimum htlc value on the incoming channel too.
DefaultBitcoinMinHTLCInMSat = lnwire.MilliSatoshi(1)
// DefaultBitcoinMinHTLCOutMSat is the default minimum htlc value that
// we require for sending out htlcs. Our channel peer may have a lower
// min htlc channel parameter, but we - by default - don't forward
// anything under the value defined here.
DefaultBitcoinMinHTLCOutMSat = lnwire.MilliSatoshi(1000)
// DefaultBitcoinBaseFeeMSat is the default forwarding base fee.
DefaultBitcoinBaseFeeMSat = lnwire.MilliSatoshi(1000)
// DefaultBitcoinFeeRate is the default forwarding fee rate.
DefaultBitcoinFeeRate = lnwire.MilliSatoshi(1)
// DefaultBitcoinTimeLockDelta is the default forwarding time lock
// delta.
DefaultBitcoinTimeLockDelta = 80
// DefaultBitcoinStaticFeePerKW is the fee rate of 50 sat/vbyte
// expressed in sat/kw.
DefaultBitcoinStaticFeePerKW = chainfee.SatPerKWeight(12500)
// DefaultBitcoinStaticMinRelayFeeRate is the min relay fee used for
// static estimators.
DefaultBitcoinStaticMinRelayFeeRate = chainfee.FeePerKwFloor
// DefaultMinOutboundPeers is the min number of connected
// outbound peers the chain backend should have to maintain a
// healthy connection to the network.
DefaultMinOutboundPeers = 6
)
// PartialChainControl contains all the primary interfaces of the chain control
// that can be purely constructed from the global configuration. No wallet
// instance is required for constructing this partial state.
type PartialChainControl struct {
// Cfg is the configuration that was used to create the partial chain
// control.
Cfg *Config
// HealthCheck is a function which can be used to send a low-cost, fast
// query to the chain backend to ensure we still have access to our
// node.
HealthCheck func() error
// FeeEstimator is used to estimate an optimal fee for transactions
// important to us.
FeeEstimator chainfee.Estimator
// ChainNotifier is used to receive blockchain events that we are
// interested in.
ChainNotifier chainntnfs.ChainNotifier
// BestBlockTracker is used to maintain a view of the global
// chain state that changes over time
BestBlockTracker *chainntnfs.BestBlockTracker
// MempoolNotifier is used to watch for spending events happened in
// mempool.
MempoolNotifier chainntnfs.MempoolWatcher
// ChainView is used in the router for maintaining an up-to-date graph.
ChainView chainview.FilteredChainView
// ChainSource is the primary chain interface. This is used to operate
// the wallet and do things such as rescanning, sending transactions,
// notifications for received funds, etc.
ChainSource chain.Interface
// RoutingPolicy is the routing policy we have decided to use.
RoutingPolicy models.ForwardingPolicy
// MinHtlcIn is the minimum HTLC we will accept.
MinHtlcIn lnwire.MilliSatoshi
}
// ChainControl couples the three primary interfaces lnd utilizes for a
// particular chain together. A single ChainControl instance will exist for all
// the chains lnd is currently active on.
type ChainControl struct {
// PartialChainControl is the part of the chain control that was
// initialized purely from the configuration and doesn't contain any
// wallet related elements.
*PartialChainControl
// ChainIO represents an abstraction over a source that can query the
// blockchain.
ChainIO lnwallet.BlockChainIO
// Signer is used to provide signatures over things like transactions.
Signer input.Signer
// KeyRing represents a set of keys that we have the private keys to.
KeyRing keychain.SecretKeyRing
// Wc is an abstraction over some basic wallet commands. This base set
// of commands will be provided to the Wallet *LightningWallet raw
// pointer below.
Wc lnwallet.WalletController
// MsgSigner is used to sign arbitrary messages.
MsgSigner lnwallet.MessageSigner
// Wallet is our LightningWallet that also contains the abstract Wc
// above. This wallet handles all of the lightning operations.
Wallet *lnwallet.LightningWallet
}
// NewPartialChainControl creates a new partial chain control that contains all
// the parts that can be purely constructed from the passed in global
// configuration and doesn't need any wallet instance yet.
//
//nolint:ll
func NewPartialChainControl(cfg *Config) (*PartialChainControl, func(), error) {
cc := &PartialChainControl{
Cfg: cfg,
RoutingPolicy: models.ForwardingPolicy{
MinHTLCOut: cfg.Bitcoin.MinHTLCOut,
BaseFee: cfg.Bitcoin.BaseFee,
FeeRate: cfg.Bitcoin.FeeRate,
TimeLockDelta: cfg.Bitcoin.TimeLockDelta,
},
MinHtlcIn: cfg.Bitcoin.MinHTLCIn,
FeeEstimator: chainfee.NewStaticEstimator(
DefaultBitcoinStaticFeePerKW,
DefaultBitcoinStaticMinRelayFeeRate,
),
}
var err error
heightHintCacheConfig := channeldb.CacheConfig{
QueryDisable: cfg.HeightHintCacheQueryDisable,
}
if cfg.HeightHintCacheQueryDisable {
log.Infof("Height Hint Cache Queries disabled")
}
// Initialize the height hint cache within the chain directory.
hintCache, err := channeldb.NewHeightHintCache(
heightHintCacheConfig, cfg.HeightHintDB,
)
if err != nil {
return nil, nil, fmt.Errorf("unable to initialize height hint "+
"cache: %v", err)
}
// Map the deprecated feeurl flag to fee.url.
if cfg.FeeURL != "" {
if cfg.Fee.URL != "" {
return nil, nil, errors.New("fee.url and " +
"feeurl are mutually exclusive")
}
cfg.Fee.URL = cfg.FeeURL
}
// If spv mode is active, then we'll be using a distinct set of
// chainControl interfaces that interface directly with the p2p network
// of the selected chain.
switch cfg.Bitcoin.Node {
case "neutrino":
// We'll create ChainNotifier and FilteredChainView instances,
// along with the wallet's ChainSource, which are all backed by
// the neutrino light client.
cc.ChainNotifier = neutrinonotify.New(
cfg.NeutrinoCS, hintCache, hintCache, cfg.BlockCache,
)
cc.ChainView, err = chainview.NewCfFilteredChainView(
cfg.NeutrinoCS, cfg.BlockCache,
)
if err != nil {
return nil, nil, err
}
cc.ChainSource = chain.NewNeutrinoClient(
cfg.ActiveNetParams.Params, cfg.NeutrinoCS,
)
// Get our best block as a health check.
cc.HealthCheck = func() error {
_, _, err := cc.ChainSource.GetBestBlock()
return err
}
case "bitcoind":
bitcoindMode := cfg.BitcoindMode
// Otherwise, we'll be speaking directly via RPC and ZMQ to a
// bitcoind node. If the specified host for the btcd RPC
// server already has a port specified, then we use that
// directly. Otherwise, we assume the default port according to
// the selected chain parameters.
var bitcoindHost string
if strings.Contains(bitcoindMode.RPCHost, ":") {
bitcoindHost = bitcoindMode.RPCHost
} else {
// The RPC ports specified in chainparams.go assume
// btcd, which picks a different port so that btcwallet
// can use the same RPC port as bitcoind. We convert
// this back to the btcwallet/bitcoind port.
rpcPort, err := strconv.Atoi(cfg.ActiveNetParams.RPCPort)
if err != nil {
return nil, nil, err
}
rpcPort -= 2
bitcoindHost = fmt.Sprintf("%v:%d",
bitcoindMode.RPCHost, rpcPort)
if cfg.Bitcoin.RegTest || cfg.Bitcoin.SigNet {
conn, err := net.Dial("tcp", bitcoindHost)
if err != nil || conn == nil {
switch {
case cfg.Bitcoin.RegTest:
rpcPort = 18443
case cfg.Bitcoin.SigNet:
rpcPort = 38332
}
bitcoindHost = fmt.Sprintf("%v:%d",
bitcoindMode.RPCHost,
rpcPort)
} else {
conn.Close()
}
}
}
bitcoindCfg := &chain.BitcoindConfig{
ChainParams: cfg.ActiveNetParams.Params,
Host: bitcoindHost,
User: bitcoindMode.RPCUser,
Pass: bitcoindMode.RPCPass,
Dialer: cfg.Dialer,
PrunedModeMaxPeers: bitcoindMode.PrunedNodeMaxPeers,
}
if bitcoindMode.RPCPolling {
bitcoindCfg.PollingConfig = &chain.PollingConfig{
BlockPollingInterval: bitcoindMode.BlockPollingInterval,
TxPollingInterval: bitcoindMode.TxPollingInterval,
TxPollingIntervalJitter: lncfg.DefaultTxPollingJitter,
}
} else {
bitcoindCfg.ZMQConfig = &chain.ZMQConfig{
ZMQBlockHost: bitcoindMode.ZMQPubRawBlock,
ZMQTxHost: bitcoindMode.ZMQPubRawTx,
ZMQReadDeadline: bitcoindMode.ZMQReadDeadline,
MempoolPollingInterval: bitcoindMode.TxPollingInterval,
PollingIntervalJitter: lncfg.DefaultTxPollingJitter,
}
}
// Establish the connection to bitcoind and create the clients
// required for our relevant subsystems.
bitcoindConn, err := chain.NewBitcoindConn(bitcoindCfg)
if err != nil {
return nil, nil, err
}
if err := bitcoindConn.Start(); err != nil {
return nil, nil, fmt.Errorf("unable to connect to "+
"bitcoind: %v", err)
}
chainNotifier := bitcoindnotify.New(
bitcoindConn, cfg.ActiveNetParams.Params, hintCache,
hintCache, cfg.BlockCache,
)
cc.ChainNotifier = chainNotifier
cc.MempoolNotifier = chainNotifier
cc.ChainView = chainview.NewBitcoindFilteredChainView(
bitcoindConn, cfg.BlockCache,
)
cc.ChainSource = bitcoindConn.NewBitcoindClient()
// Initialize config to connect to bitcoind RPC.
rpcConfig := &rpcclient.ConnConfig{
Host: bitcoindHost,
User: bitcoindMode.RPCUser,
Pass: bitcoindMode.RPCPass,
DisableConnectOnNew: true,
DisableAutoReconnect: false,
DisableTLS: true,
HTTPPostMode: true,
}
// If feeurl is not provided, use bitcoind's fee estimator.
if cfg.Fee.URL == "" {
log.Infof("Initializing bitcoind backed fee estimator "+
"in %s mode", bitcoindMode.EstimateMode)
// Finally, we'll re-initialize the fee estimator, as
// if we're using bitcoind as a backend, then we can
// use live fee estimates, rather than a statically
// coded value.
fallBackFeeRate := chainfee.SatPerKVByte(25 * 1000)
cc.FeeEstimator, err = chainfee.NewBitcoindEstimator(
*rpcConfig, bitcoindMode.EstimateMode,
fallBackFeeRate.FeePerKWeight(),
)
if err != nil {
return nil, nil, err
}
}
// We need to use some apis that are not exposed by btcwallet,
// for a health check function so we create an ad-hoc bitcoind
// connection.
chainConn, err := rpcclient.New(rpcConfig, nil)
if err != nil {
return nil, nil, err
}
// Before we continue any further, we'll ensure that the
// backend understands Taproot. If not, then all the default
// features can't be used.
if !backendSupportsTaproot(chainConn) {
return nil, nil, fmt.Errorf("node backend does not " +
"support taproot")
}
// The api we will use for our health check depends on the
// bitcoind version.
cmd, ver, err := getBitcoindHealthCheckCmd(chainConn)
if err != nil {
return nil, nil, err
}
// If the getzmqnotifications api is available (was added in
// version 0.17.0) we make sure lnd subscribes to the correct
// zmq events. We do this to avoid a situation in which we are
// not notified of new transactions or blocks.
if ver >= 170000 && !bitcoindMode.RPCPolling {
zmqPubRawBlockURL, err := url.Parse(bitcoindMode.ZMQPubRawBlock)
if err != nil {
return nil, nil, err
}
zmqPubRawTxURL, err := url.Parse(bitcoindMode.ZMQPubRawTx)
if err != nil {
return nil, nil, err
}
// Fetch all active zmq notifications from the bitcoind client.
resp, err := chainConn.RawRequest("getzmqnotifications", nil)
if err != nil {
return nil, nil, err
}
zmq := []struct {
Type string `json:"type"`
Address string `json:"address"`
}{}
if err = json.Unmarshal([]byte(resp), &zmq); err != nil {
return nil, nil, err
}
pubRawBlockActive := false
pubRawTxActive := false
for i := range zmq {
if zmq[i].Type == "pubrawblock" {
url, err := url.Parse(zmq[i].Address)
if err != nil {
return nil, nil, err
}
if url.Port() != zmqPubRawBlockURL.Port() {
log.Warnf(
"unable to subscribe to zmq block events on "+
"%s (bitcoind is running on %s)",
zmqPubRawBlockURL.Host,
url.Host,
)
}
pubRawBlockActive = true
}
if zmq[i].Type == "pubrawtx" {
url, err := url.Parse(zmq[i].Address)
if err != nil {
return nil, nil, err
}
if url.Port() != zmqPubRawTxURL.Port() {
log.Warnf(
"unable to subscribe to zmq tx events on "+
"%s (bitcoind is running on %s)",
zmqPubRawTxURL.Host,
url.Host,
)
}
pubRawTxActive = true
}
}
// Return an error if raw tx or raw block notification over
// zmq is inactive.
if !pubRawBlockActive {
return nil, nil, errors.New(
"block notification over zmq is inactive on " +
"bitcoind",
)
}
if !pubRawTxActive {
return nil, nil, errors.New(
"tx notification over zmq is inactive on " +
"bitcoind",
)
}
}
cc.HealthCheck = func() error {
_, err := chainConn.RawRequest(cmd, nil)
if err != nil {
return err
}
// On local test networks we usually don't have multiple
// chain backend peers, so we can skip
// the checkOutboundPeers test.
if cfg.Bitcoin.SimNet || cfg.Bitcoin.RegTest {
return nil
}
// Make sure the bitcoind chain backend maintains a
// healthy connection to the network by checking the
// number of outbound peers.
return checkOutboundPeers(chainConn)
}
case "btcd":
// Otherwise, we'll be speaking directly via RPC to a node.
//
// So first we'll load btcd's TLS cert for the RPC
// connection. If a raw cert was specified in the config, then
// we'll set that directly. Otherwise, we attempt to read the
// cert from the path specified in the config.
var (
rpcCert []byte
btcdMode = cfg.BtcdMode
)
if btcdMode.RawRPCCert != "" {
rpcCert, err = hex.DecodeString(btcdMode.RawRPCCert)
if err != nil {
return nil, nil, err
}
} else {
certFile, err := os.Open(btcdMode.RPCCert)
if err != nil {
return nil, nil, err
}
rpcCert, err = io.ReadAll(certFile)
if err != nil {
return nil, nil, err
}
if err := certFile.Close(); err != nil {
return nil, nil, err
}
}
// If the specified host for the btcd RPC server already
// has a port specified, then we use that directly. Otherwise,
// we assume the default port according to the selected chain
// parameters.
var btcdHost string
if strings.Contains(btcdMode.RPCHost, ":") {
btcdHost = btcdMode.RPCHost
} else {
btcdHost = fmt.Sprintf("%v:%v", btcdMode.RPCHost,
cfg.ActiveNetParams.RPCPort)
}
btcdUser := btcdMode.RPCUser
btcdPass := btcdMode.RPCPass
rpcConfig := &rpcclient.ConnConfig{
Host: btcdHost,
Endpoint: "ws",
User: btcdUser,
Pass: btcdPass,
Certificates: rpcCert,
DisableTLS: false,
DisableConnectOnNew: true,
DisableAutoReconnect: false,
}
chainNotifier, err := btcdnotify.New(
rpcConfig, cfg.ActiveNetParams.Params, hintCache,
hintCache, cfg.BlockCache,
)
if err != nil {
return nil, nil, err
}
cc.ChainNotifier = chainNotifier
cc.MempoolNotifier = chainNotifier
// Finally, we'll create an instance of the default chain view
// to be used within the routing layer.
cc.ChainView, err = chainview.NewBtcdFilteredChainView(
*rpcConfig, cfg.BlockCache,
)
if err != nil {
log.Errorf("unable to create chain view: %v", err)
return nil, nil, err
}
// Create a special websockets rpc client for btcd which will be
// used by the wallet for notifications, calls, etc.
chainRPC, err := chain.NewRPCClient(
cfg.ActiveNetParams.Params, btcdHost, btcdUser,
btcdPass, rpcCert, false, 20,
)
if err != nil {
return nil, nil, err
}
// Before we continue any further, we'll ensure that the
// backend understands Taproot. If not, then all the default
// features can't be used.
restConfCopy := *rpcConfig
restConfCopy.Endpoint = ""
restConfCopy.HTTPPostMode = true
chainConn, err := rpcclient.New(&restConfCopy, nil)
if err != nil {
return nil, nil, err
}
if !backendSupportsTaproot(chainConn) {
return nil, nil, fmt.Errorf("node backend does not " +
"support taproot")
}
cc.ChainSource = chainRPC
// Use a query for our best block as a health check.
cc.HealthCheck = func() error {
_, _, err := cc.ChainSource.GetBestBlock()
if err != nil {
return err
}
// On local test networks we usually don't have multiple
// chain backend peers, so we can skip
// the checkOutboundPeers test.
if cfg.Bitcoin.SimNet || cfg.Bitcoin.RegTest {
return nil
}
// Make sure the btcd chain backend maintains a
// healthy connection to the network by checking the
// number of outbound peers.
return checkOutboundPeers(chainRPC.Client)
}
// If feeurl is not provided, use btcd's fee estimator.
if cfg.Fee.URL == "" {
log.Info("Initializing btcd backed fee estimator")
// Finally, we'll re-initialize the fee estimator, as
// if we're using btcd as a backend, then we can use
// live fee estimates, rather than a statically coded
// value.
fallBackFeeRate := chainfee.SatPerKVByte(25 * 1000)
cc.FeeEstimator, err = chainfee.NewBtcdEstimator(
*rpcConfig, fallBackFeeRate.FeePerKWeight(),
)
if err != nil {
return nil, nil, err
}
}
case "nochainbackend":
backend := &NoChainBackend{}
source := &NoChainSource{
BestBlockTime: time.Now(),
}
cc.ChainNotifier = backend
cc.ChainView = backend
cc.FeeEstimator = backend
cc.ChainSource = source
cc.HealthCheck = func() error {
return nil
}
default:
return nil, nil, fmt.Errorf("unknown node type: %s",
cfg.Bitcoin.Node)
}
cc.BestBlockTracker =
chainntnfs.NewBestBlockTracker(cc.ChainNotifier)
switch {
// If the fee URL isn't set, and the user is running mainnet, then
// we'll return an error to instruct them to set a proper fee
// estimator.
case cfg.Fee.URL == "" && cfg.Bitcoin.MainNet &&
cfg.Bitcoin.Node == "neutrino":
return nil, nil, fmt.Errorf("--fee.url parameter required " +
"when running neutrino on mainnet")
// Override default fee estimator if an external service is specified.
case cfg.Fee.URL != "":
// Do not cache fees on regtest to make it easier to execute
// manual or automated test cases.
cacheFees := !cfg.Bitcoin.RegTest
log.Infof("Using external fee estimator %v: cached=%v: "+
"min update timeout=%v, max update timeout=%v",
cfg.Fee.URL, cacheFees, cfg.Fee.MinUpdateTimeout,
cfg.Fee.MaxUpdateTimeout)
cc.FeeEstimator, err = chainfee.NewWebAPIEstimator(
chainfee.SparseConfFeeSource{
URL: cfg.Fee.URL,
},
!cacheFees,
cfg.Fee.MinUpdateTimeout,
cfg.Fee.MaxUpdateTimeout,
)
if err != nil {
return nil, nil, err
}
}
ccCleanup := func() {
if cc.FeeEstimator != nil {
if err := cc.FeeEstimator.Stop(); err != nil {
log.Errorf("Failed to stop feeEstimator: %v",
err)
}
}
}
// Start fee estimator.
if err := cc.FeeEstimator.Start(); err != nil {
return nil, nil, err
}
return cc, ccCleanup, nil
}
// NewChainControl attempts to create a ChainControl instance according
// to the parameters in the passed configuration. Currently three
// branches of ChainControl instances exist: one backed by a running btcd
// full-node, another backed by a running bitcoind full-node, and the other
// backed by a running neutrino light client instance. When running with a
// neutrino light client instance, `neutrinoCS` must be non-nil.
func NewChainControl(walletConfig lnwallet.Config,
msgSigner lnwallet.MessageSigner,
pcc *PartialChainControl) (*ChainControl, func(), error) {
cc := &ChainControl{
PartialChainControl: pcc,
MsgSigner: msgSigner,
Signer: walletConfig.Signer,
ChainIO: walletConfig.ChainIO,
Wc: walletConfig.WalletController,
KeyRing: walletConfig.SecretKeyRing,
}
ccCleanup := func() {
if cc.Wallet != nil {
if err := cc.Wallet.Shutdown(); err != nil {
log.Errorf("Failed to shutdown wallet: %v", err)
}
}
}
lnWallet, err := lnwallet.NewLightningWallet(walletConfig)
if err != nil {
return nil, ccCleanup, fmt.Errorf("unable to create wallet: %w",
err)
}
if err := lnWallet.Startup(); err != nil {
return nil, ccCleanup, fmt.Errorf("unable to create wallet: %w",
err)
}
log.Info("LightningWallet opened")
cc.Wallet = lnWallet
return cc, ccCleanup, nil
}
// getBitcoindHealthCheckCmd queries bitcoind for its version to decide which
// api we should use for our health check. We prefer to use the uptime
// command, because it has no locking and is an inexpensive call, which was
// added in version 0.15. If we are on an earlier version, we fallback to using
// getblockchaininfo.
func getBitcoindHealthCheckCmd(client *rpcclient.Client) (string, int64, error) {
// Query bitcoind to get our current version.
resp, err := client.RawRequest("getnetworkinfo", nil)
if err != nil {
return "", 0, err
}
// Parse the response to retrieve bitcoind's version.
info := struct {
Version int64 `json:"version"`
}{}
if err := json.Unmarshal(resp, &info); err != nil {
return "", 0, err
}
// Bitcoind returns a single value representing the semantic version:
// 1000000 * CLIENT_VERSION_MAJOR + 10000 * CLIENT_VERSION_MINOR
// + 100 * CLIENT_VERSION_REVISION + 1 * CLIENT_VERSION_BUILD
//
// The uptime call was added in version 0.15.0, so we return it for
// any version value >= 150000, as per the above calculation.
if info.Version >= 150000 {
return "uptime", info.Version, nil
}
return "getblockchaininfo", info.Version, nil
}
var (
// BitcoinTestnetGenesis is the genesis hash of Bitcoin's testnet
// chain.
BitcoinTestnetGenesis = chainhash.Hash([chainhash.HashSize]byte{
0x43, 0x49, 0x7f, 0xd7, 0xf8, 0x26, 0x95, 0x71,
0x08, 0xf4, 0xa3, 0x0f, 0xd9, 0xce, 0xc3, 0xae,
0xba, 0x79, 0x97, 0x20, 0x84, 0xe9, 0x0e, 0xad,
0x01, 0xea, 0x33, 0x09, 0x00, 0x00, 0x00, 0x00,
})
// BitcoinTestnet4Genesis is the genesis hash of Bitcoin's testnet4
// chain.
BitcoinTestnet4Genesis = chainhash.Hash([chainhash.HashSize]byte{
0x43, 0xf0, 0x8b, 0xda, 0xb0, 0x50, 0xe3, 0x5b,
0x56, 0x7c, 0x86, 0x4b, 0x91, 0xf4, 0x7f, 0x50,
0xae, 0x72, 0x5a, 0xe2, 0xde, 0x53, 0xbc, 0xfb,
0xba, 0xf2, 0x84, 0xda, 0x00, 0x00, 0x00, 0x00,
})
// BitcoinSignetGenesis is the genesis hash of Bitcoin's signet chain.
BitcoinSignetGenesis = chainhash.Hash([chainhash.HashSize]byte{
0xf6, 0x1e, 0xee, 0x3b, 0x63, 0xa3, 0x80, 0xa4,
0x77, 0xa0, 0x63, 0xaf, 0x32, 0xb2, 0xbb, 0xc9,
0x7c, 0x9f, 0xf9, 0xf0, 0x1f, 0x2c, 0x42, 0x25,
0xe9, 0x73, 0x98, 0x81, 0x08, 0x00, 0x00, 0x00,
})
// BitcoinMainnetGenesis is the genesis hash of Bitcoin's main chain.
BitcoinMainnetGenesis = chainhash.Hash([chainhash.HashSize]byte{
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
})
// ChainDNSSeeds is a map of a chain's hash to the set of DNS seeds
// that will be use to bootstrap peers upon first startup.
//
// The first item in the array is the primary host we'll use to attempt
// the SRV lookup we require. If we're unable to receive a response
// over UDP, then we'll fall back to manual TCP resolution. The second
// item in the array is a special A record that we'll query in order to
// receive the IP address of the current authoritative DNS server for
// the network seed.
//
// TODO(roasbeef): extend and collapse these and chainparams.go into
// struct like chaincfg.Params.
ChainDNSSeeds = map[chainhash.Hash][][2]string{
BitcoinMainnetGenesis: {
{
"nodes.lightning.directory",
"soa.nodes.lightning.directory",
},
{
"lseed.bitcoinstats.com",
},
},
BitcoinTestnetGenesis: {
{
"test.nodes.lightning.directory",
"soa.nodes.lightning.directory",
},
},
BitcoinSignetGenesis: {
{
"ln.signet.secp.tech",
},
},
}
)
// checkOutboundPeers checks the number of outbound peers connected to the
// provided RPC client. If the number of outbound peers is below 6, a warning
// is logged. This function is intended to ensure that the chain backend
// maintains a healthy connection to the network.
func checkOutboundPeers(client *rpcclient.Client) error {
peers, err := client.GetPeerInfo()
if err != nil {
return err
}
var outboundPeers int
for _, peer := range peers {
if !peer.Inbound {
outboundPeers++
}
}
if outboundPeers < DefaultMinOutboundPeers {
log.Warnf("The chain backend has an insufficient number "+
"of connected outbound peers (%d connected, expected "+
"minimum is %d) which can be a security issue. "+
"Connect to more trusted nodes manually if necessary.",
outboundPeers, DefaultMinOutboundPeers)
}
return nil
}
package chainreg
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// Subsystem defines the logging code for this subsystem.
const Subsystem = "CHRE"
// log is a logger that is initialized with the btclog.Disabled logger.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all logging output.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package chainreg
import (
"errors"
"time"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/lightningnetwork/lnd/chainntnfs"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/routing/chainview"
)
var (
// defaultFee is the fee that is returned by NoChainBackend.
defaultFee = chainfee.FeePerKwFloor
// noChainBackendName is the backend name returned by NoChainBackend.
noChainBackendName = "nochainbackend"
// errNotImplemented is the error that is returned by NoChainBackend for
// any operation that is not supported by it. Such paths should in
// practice never been hit, so seeing this error either means a remote
// signing instance was used for an unsupported purpose or a previously
// forgotten edge case path was hit.
errNotImplemented = errors.New("not implemented in nochainbackend " +
"mode")
// noChainBackendBestHash is the chain hash of the chain tip that is
// returned by NoChainBackend.
noChainBackendBestHash = &chainhash.Hash{0x01}
// noChainBackendBestHeight is the best height that is returned by
// NoChainBackend.
noChainBackendBestHeight int32 = 1
)
// NoChainBackend is a mock implementation of the following interfaces:
// - chainview.FilteredChainView
// - chainntnfs.ChainNotifier
// - chainfee.Estimator
type NoChainBackend struct {
}
func (n *NoChainBackend) EstimateFeePerKW(uint32) (chainfee.SatPerKWeight,
error) {
return defaultFee, nil
}
func (n *NoChainBackend) RelayFeePerKW() chainfee.SatPerKWeight {
return defaultFee
}
func (n *NoChainBackend) RegisterConfirmationsNtfn(*chainhash.Hash, []byte,
uint32, uint32,
...chainntnfs.NotifierOption) (*chainntnfs.ConfirmationEvent, error) {
return nil, errNotImplemented
}
func (n *NoChainBackend) RegisterSpendNtfn(*wire.OutPoint, []byte,
uint32) (*chainntnfs.SpendEvent, error) {
return nil, errNotImplemented
}
func (n *NoChainBackend) RegisterBlockEpochNtfn(
*chainntnfs.BlockEpoch) (*chainntnfs.BlockEpochEvent, error) {
epochChan := make(chan *chainntnfs.BlockEpoch)
return &chainntnfs.BlockEpochEvent{
Epochs: epochChan,
Cancel: func() {
close(epochChan)
},
}, nil
}
func (n *NoChainBackend) Started() bool {
return true
}
func (n *NoChainBackend) FilteredBlocks() <-chan *chainview.FilteredBlock {
return make(chan *chainview.FilteredBlock)
}
func (n *NoChainBackend) DisconnectedBlocks() <-chan *chainview.FilteredBlock {
return make(chan *chainview.FilteredBlock)
}
func (n *NoChainBackend) UpdateFilter([]graphdb.EdgePoint, uint32) error {
return nil
}
func (n *NoChainBackend) FilterBlock(*chainhash.Hash) (*chainview.FilteredBlock,
error) {
return nil, errNotImplemented
}
func (n *NoChainBackend) Start() error {
return nil
}
func (n *NoChainBackend) Stop() error {
return nil
}
var _ chainview.FilteredChainView = (*NoChainBackend)(nil)
var _ chainntnfs.ChainNotifier = (*NoChainBackend)(nil)
var _ chainfee.Estimator = (*NoChainBackend)(nil)
// NoChainSource is a mock implementation of chain.Interface.
// The mock is designed to return static values where necessary to make any
// caller believe the chain is fully synced to virtual block height 1 (hash
// 0x0000..0001). That should avoid calls to other methods completely since they
// are only used for advancing the chain forward.
type NoChainSource struct {
notifChan chan interface{}
BestBlockTime time.Time
}
func (n *NoChainSource) Start() error {
n.notifChan = make(chan interface{})
go func() {
n.notifChan <- &chain.RescanFinished{
Hash: noChainBackendBestHash,
Height: noChainBackendBestHeight,
Time: n.BestBlockTime,
}
}()
return nil
}
func (n *NoChainSource) Stop() {
}
func (n *NoChainSource) WaitForShutdown() {
}
func (n *NoChainSource) GetBestBlock() (*chainhash.Hash, int32, error) {
return noChainBackendBestHash, noChainBackendBestHeight, nil
}
func (n *NoChainSource) GetBlock(*chainhash.Hash) (*wire.MsgBlock, error) {
return &wire.MsgBlock{
Header: wire.BlockHeader{
Timestamp: n.BestBlockTime,
},
Transactions: []*wire.MsgTx{},
}, nil
}
func (n *NoChainSource) GetBlockHash(int64) (*chainhash.Hash, error) {
return noChainBackendBestHash, nil
}
func (n *NoChainSource) GetBlockHeader(*chainhash.Hash) (*wire.BlockHeader,
error) {
return &wire.BlockHeader{
Timestamp: n.BestBlockTime,
}, nil
}
func (n *NoChainSource) IsCurrent() bool {
return true
}
func (n *NoChainSource) FilterBlocks(
*chain.FilterBlocksRequest) (*chain.FilterBlocksResponse, error) {
return nil, errNotImplemented
}
func (n *NoChainSource) BlockStamp() (*waddrmgr.BlockStamp, error) {
return nil, errNotImplemented
}
func (n *NoChainSource) SendRawTransaction(*wire.MsgTx, bool) (*chainhash.Hash,
error) {
return nil, errNotImplemented
}
func (n *NoChainSource) Rescan(*chainhash.Hash, []btcutil.Address,
map[wire.OutPoint]btcutil.Address) error {
return nil
}
func (n *NoChainSource) NotifyReceived([]btcutil.Address) error {
return nil
}
func (n *NoChainSource) NotifyBlocks() error {
return nil
}
func (n *NoChainSource) Notifications() <-chan interface{} {
return n.notifChan
}
func (n *NoChainSource) BackEnd() string {
return noChainBackendName
}
func (n *NoChainSource) TestMempoolAccept([]*wire.MsgTx,
float64) ([]*btcjson.TestMempoolAcceptResult, error) {
return nil, nil
}
func (n *NoChainSource) MapRPCErr(err error) error {
return err
}
var _ chain.Interface = (*NoChainSource)(nil)
package chainreg
import (
"encoding/json"
"github.com/btcsuite/btcd/rpcclient"
)
// backendSupportsTaproot returns true if the backend understands the taproot
// soft fork.
func backendSupportsTaproot(rpc *rpcclient.Client) bool {
// First, we'll try to access the normal getblockchaininfo call.
chainInfo, err := rpc.GetBlockChainInfo()
if err == nil {
// If this call worked, then we'll check that the taproot
// deployment is defined.
switch {
// Bitcoind versions before 0.19 and also btcd use the
// SoftForks fields.
case chainInfo.SoftForks != nil:
_, ok := chainInfo.SoftForks.Bip9SoftForks["taproot"]
if ok {
return ok
}
// Bitcoind versions after 0.19 will use the UnifiedSoftForks
// field that factors in the set of "buried" soft forks.
case chainInfo.UnifiedSoftForks != nil:
_, ok := chainInfo.UnifiedSoftForks.SoftForks["taproot"]
if ok {
return ok
}
}
}
// The user might be running a newer version of bitcoind that doesn't
// implement the getblockchaininfo call any longer, so we'll fall back
// here.
//
// Alternatively, the fork wasn't specified, but the user might be
// running a newer version of bitcoind that still has the
// getblockchaininfo call, but doesn't populate the data, so we'll hit
// the new getdeploymentinfo call.
resp, err := rpc.RawRequest("getdeploymentinfo", nil)
if err != nil {
log.Warnf("unable to make getdeploymentinfo request: %v", err)
return false
}
info := struct {
Deployments map[string]struct {
Type string `json:"type"`
Active bool `json:"active"`
Height int32 `json:"height"`
} `json:"deployments"`
}{}
if err := json.Unmarshal(resp, &info); err != nil {
log.Warnf("unable to decode getdeploymentinfo resp: %v", err)
return false
}
_, ok := info.Deployments["taproot"]
return ok
}
package chanacceptor
import (
"sync"
"sync/atomic"
)
// ChainedAcceptor represents a conjunction of ChannelAcceptor results.
type ChainedAcceptor struct {
acceptorID uint64 // To be used atomically.
// acceptors is a map of ChannelAcceptors that will be evaluated when
// the ChainedAcceptor's Accept method is called.
acceptors map[uint64]ChannelAcceptor
acceptorsMtx sync.RWMutex
}
// NewChainedAcceptor initializes a ChainedAcceptor.
func NewChainedAcceptor() *ChainedAcceptor {
return &ChainedAcceptor{
acceptors: make(map[uint64]ChannelAcceptor),
}
}
// AddAcceptor adds a ChannelAcceptor to this ChainedAcceptor.
//
// NOTE: Part of the MultiplexAcceptor interface.
func (c *ChainedAcceptor) AddAcceptor(acceptor ChannelAcceptor) uint64 {
id := atomic.AddUint64(&c.acceptorID, 1)
c.acceptorsMtx.Lock()
c.acceptors[id] = acceptor
c.acceptorsMtx.Unlock()
// Return the id so that a caller can call RemoveAcceptor.
return id
}
// RemoveAcceptor removes a ChannelAcceptor from this ChainedAcceptor given
// an ID.
//
// NOTE: Part of the MultiplexAcceptor interface.
func (c *ChainedAcceptor) RemoveAcceptor(id uint64) {
c.acceptorsMtx.Lock()
delete(c.acceptors, id)
c.acceptorsMtx.Unlock()
}
// numAcceptors returns the number of acceptors contained in the
// ChainedAcceptor.
func (c *ChainedAcceptor) numAcceptors() int {
c.acceptorsMtx.RLock()
defer c.acceptorsMtx.RUnlock()
return len(c.acceptors)
}
// Accept evaluates the results of all ChannelAcceptors in the acceptors map
// and returns the conjunction of all these predicates.
//
// NOTE: Part of the ChannelAcceptor interface.
func (c *ChainedAcceptor) Accept(req *ChannelAcceptRequest) *ChannelAcceptResponse {
c.acceptorsMtx.RLock()
defer c.acceptorsMtx.RUnlock()
var finalResp ChannelAcceptResponse
for _, acceptor := range c.acceptors {
// Call our acceptor to determine whether we want to accept this
// channel.
acceptorResponse := acceptor.Accept(req)
// If we should reject the channel, we can just exit early. This
// has the effect of returning the error belonging to our first
// failed acceptor.
if acceptorResponse.RejectChannel() {
return acceptorResponse
}
// If we have accepted the channel, we need to set the other
// fields that were set in the response. However, since we are
// dealing with multiple responses, we need to make sure that we
// have not received inconsistent values (eg a csv delay of 1
// from one acceptor, and a delay of 120 from another). We
// set each value on our final response if it has not been set
// yet, and allow duplicate sets if the value is the same. If
// we cannot set a field, we return an error response.
var err error
finalResp, err = mergeResponse(finalResp, *acceptorResponse)
if err != nil {
log.Errorf("response for: %x has inconsistent values: %v",
req.OpenChanMsg.PendingChannelID, err)
return NewChannelAcceptResponse(
false, errChannelRejected, nil, 0, 0,
0, 0, 0, 0, false,
)
}
}
// If we have gone through all of our acceptors with no objections, we
// can return an acceptor with a nil error.
return &finalResp
}
// A compile-time constraint to ensure ChainedAcceptor implements the
// MultiplexAcceptor interface.
var _ MultiplexAcceptor = (*ChainedAcceptor)(nil)
package chanacceptor
import (
"errors"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// errChannelRejected is returned when the rpc channel acceptor rejects
// a channel due to acceptor timeout, shutdown, or because no custom
// error value is available when the channel was rejected.
errChannelRejected = errors.New("channel rejected")
)
// ChannelAcceptRequest is a struct containing the requesting node's public key
// along with the lnwire.OpenChannel message that they sent when requesting an
// inbound channel. This information is provided to each acceptor so that they
// can each leverage their own decision-making with this information.
type ChannelAcceptRequest struct {
// Node is the public key of the node requesting to open a channel.
Node *btcec.PublicKey
// OpenChanMsg is the actual OpenChannel protocol message that the peer
// sent to us.
OpenChanMsg *lnwire.OpenChannel
}
// ChannelAcceptResponse is a struct containing the response to a request to
// open an inbound channel. Note that fields added to this struct must be added
// to the mergeResponse function to allow combining of responses from different
// acceptors.
type ChannelAcceptResponse struct {
// ChanAcceptError the error returned by the channel acceptor. If the
// channel was accepted, this value will be nil.
ChanAcceptError
// UpfrontShutdown is the address that we will set as our upfront
// shutdown address.
UpfrontShutdown lnwire.DeliveryAddress
// CSVDelay is the csv delay we require for the remote peer.
CSVDelay uint16
// Reserve is the amount that require the remote peer hold in reserve
// on the channel.
Reserve btcutil.Amount
// InFlightTotal is the maximum amount that we allow the remote peer to
// hold in outstanding htlcs.
InFlightTotal lnwire.MilliSatoshi
// HtlcLimit is the maximum number of htlcs that we allow the remote
// peer to offer us.
HtlcLimit uint16
// MinHtlcIn is the minimum incoming htlc value allowed on the channel.
MinHtlcIn lnwire.MilliSatoshi
// MinAcceptDepth is the minimum depth that the initiator of the
// channel should wait before considering the channel open.
MinAcceptDepth uint16
// ZeroConf indicates that the fundee wishes to send min_depth = 0 and
// request a zero-conf channel with the counter-party.
ZeroConf bool
}
// NewChannelAcceptResponse is a constructor for a channel accept response,
// which creates a response with an appropriately wrapped error (in the case of
// a rejection) so that the error will be whitelisted and delivered to the
// initiating peer. Accepted channels simply return a response containing a nil
// error.
func NewChannelAcceptResponse(accept bool, acceptErr error,
upfrontShutdown lnwire.DeliveryAddress, csvDelay, htlcLimit,
minDepth uint16, reserve btcutil.Amount, inFlight,
minHtlcIn lnwire.MilliSatoshi, zeroConf bool) *ChannelAcceptResponse {
resp := &ChannelAcceptResponse{
UpfrontShutdown: upfrontShutdown,
CSVDelay: csvDelay,
Reserve: reserve,
InFlightTotal: inFlight,
HtlcLimit: htlcLimit,
MinHtlcIn: minHtlcIn,
MinAcceptDepth: minDepth,
ZeroConf: zeroConf,
}
// If we want to accept the channel, we return a response with a nil
// error.
if accept {
return resp
}
// Use a generic error when no custom error is provided.
if acceptErr == nil {
acceptErr = errChannelRejected
}
resp.ChanAcceptError = ChanAcceptError{
error: acceptErr,
}
return resp
}
// RejectChannel returns a boolean that indicates whether we should reject the
// channel.
func (c *ChannelAcceptResponse) RejectChannel() bool {
return c.error != nil
}
// ChannelAcceptor is an interface that represents a predicate on the data
// contained in ChannelAcceptRequest.
type ChannelAcceptor interface {
Accept(req *ChannelAcceptRequest) *ChannelAcceptResponse
}
// MultiplexAcceptor is an interface that abstracts the ability of a
// ChannelAcceptor to contain sub-ChannelAcceptors.
type MultiplexAcceptor interface {
// Embed the ChannelAcceptor.
ChannelAcceptor
// AddAcceptor nests a ChannelAcceptor inside the MultiplexAcceptor.
AddAcceptor(acceptor ChannelAcceptor) uint64
// Remove a sub-ChannelAcceptor.
RemoveAcceptor(id uint64)
}
package chanacceptor
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// Subsystem defines the logging code for this subsystem.
const Subsystem = "CHAC"
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package chanacceptor
import (
"bytes"
"fmt"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/lnwire"
)
const (
// We use field names in our errors for more readable errors. Create
// consts for them here so that we can exactly match in our unit tests.
fieldCSV = "csv delay"
fieldHtlcLimit = "htlc limit"
fieldMinDep = "min depth"
fieldReserve = "reserve"
fieldMinIn = "min htlc in"
fieldInFlightTotal = "in flight total"
fieldUpfrontShutdown = "upfront shutdown"
)
var (
errZeroConf = fmt.Errorf("zero-conf set with non-zero min-depth")
)
// fieldMismatchError returns a merge error for a named field when we get two
// channel acceptor responses which have different values set.
func fieldMismatchError(name string, current, newValue interface{}) error {
return fmt.Errorf("multiple values set for: %v, %v and %v",
name, current, newValue)
}
// mergeBool merges two boolean values.
func mergeBool(current, newValue bool) bool {
// If either is true, return true. It is not possible to have different
// "non-zero" values like the other cases.
return current || newValue
}
// mergeInt64 merges two int64 values, failing if they have different non-zero
// values.
func mergeInt64(name string, current, newValue int64) (int64, error) {
switch {
case current == 0:
return newValue, nil
case newValue == 0:
return current, nil
case current != newValue:
return 0, fieldMismatchError(name, current, newValue)
default:
return newValue, nil
}
}
// mergeMillisatoshi merges two msat values, failing if they have different
// non-zero values.
func mergeMillisatoshi(name string, current,
newValue lnwire.MilliSatoshi) (lnwire.MilliSatoshi, error) {
switch {
case current == 0:
return newValue, nil
case newValue == 0:
return current, nil
case current != newValue:
return 0, fieldMismatchError(name, current, newValue)
default:
return newValue, nil
}
}
// mergeDeliveryAddress merges two delivery address values, failing if they have
// different non-zero values.
func mergeDeliveryAddress(name string, current,
newValue lnwire.DeliveryAddress) (lnwire.DeliveryAddress, error) {
switch {
case current == nil:
return newValue, nil
case newValue == nil:
return current, nil
case !bytes.Equal(current, newValue):
return nil, fieldMismatchError(name, current, newValue)
default:
return newValue, nil
}
}
// mergeResponse takes two channel accept responses, and attempts to merge their
// fields, failing if any fields conflict (are non-zero and not equal). It
// returns a new response that has all the merged fields in it.
func mergeResponse(current,
newValue ChannelAcceptResponse) (ChannelAcceptResponse, error) {
csv, err := mergeInt64(
fieldCSV, int64(current.CSVDelay), int64(newValue.CSVDelay),
)
if err != nil {
return current, err
}
current.CSVDelay = uint16(csv)
htlcLimit, err := mergeInt64(
fieldHtlcLimit, int64(current.HtlcLimit),
int64(newValue.HtlcLimit),
)
if err != nil {
return current, err
}
current.HtlcLimit = uint16(htlcLimit)
minDepth, err := mergeInt64(
fieldMinDep, int64(current.MinAcceptDepth),
int64(newValue.MinAcceptDepth),
)
if err != nil {
return current, err
}
current.MinAcceptDepth = uint16(minDepth)
current.ZeroConf = mergeBool(current.ZeroConf, newValue.ZeroConf)
// Assert that if zero-conf is set, min-depth is zero.
if current.ZeroConf && current.MinAcceptDepth != 0 {
return current, errZeroConf
}
reserve, err := mergeInt64(
fieldReserve, int64(current.Reserve), int64(newValue.Reserve),
)
if err != nil {
return current, err
}
current.Reserve = btcutil.Amount(reserve)
current.MinHtlcIn, err = mergeMillisatoshi(
fieldMinIn, current.MinHtlcIn, newValue.MinHtlcIn,
)
if err != nil {
return current, err
}
current.InFlightTotal, err = mergeMillisatoshi(
fieldInFlightTotal, current.InFlightTotal,
newValue.InFlightTotal,
)
if err != nil {
return current, err
}
current.UpfrontShutdown, err = mergeDeliveryAddress(
fieldUpfrontShutdown, current.UpfrontShutdown,
newValue.UpfrontShutdown,
)
if err != nil {
return current, err
}
return current, nil
}
package chanacceptor
import (
"encoding/hex"
"errors"
"fmt"
"sync"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
errShuttingDown = errors.New("server shutting down")
// errCustomLength is returned when our custom error's length exceeds
// our maximum.
errCustomLength = fmt.Errorf("custom error message exceeds length "+
"limit: %v", maxErrorLength)
// errInvalidUpfrontShutdown is returned when we cannot parse the
// upfront shutdown address returned.
errInvalidUpfrontShutdown = fmt.Errorf("could not parse upfront " +
"shutdown address")
// errInsufficientReserve is returned when the reserve proposed by for
// a channel is less than the dust limit originally supplied.
errInsufficientReserve = fmt.Errorf("reserve lower than proposed dust " +
"limit")
// errAcceptWithError is returned when we get a response which accepts
// a channel but ambiguously also sets a custom error message.
errAcceptWithError = errors.New("channel acceptor response accepts " +
"channel, but also includes custom error")
// errMaxHtlcTooHigh is returned if our htlc count exceeds the number
// hard-set by BOLT 2.
errMaxHtlcTooHigh = fmt.Errorf("htlc limit exceeds spec limit of: %v",
input.MaxHTLCNumber/2)
// maxErrorLength is the maximum error length we allow the error we
// send to our peer to be.
maxErrorLength = 500
)
// chanAcceptInfo contains a request for a channel acceptor decision, and a
// channel that the response should be sent on.
type chanAcceptInfo struct {
request *ChannelAcceptRequest
response chan *ChannelAcceptResponse
}
// RPCAcceptor represents the RPC-controlled variant of the ChannelAcceptor.
// One RPCAcceptor allows one RPC client.
type RPCAcceptor struct {
// receive is a function from which we receive channel acceptance
// decisions. Note that this function is expected to block.
receive func() (*lnrpc.ChannelAcceptResponse, error)
// send is a function which sends requests for channel acceptance
// decisions into our rpc stream.
send func(request *lnrpc.ChannelAcceptRequest) error
// requests is a channel that we send requests for a acceptor response
// into.
requests chan *chanAcceptInfo
// timeout is the amount of time we allow the channel acceptance
// decision to take. This time includes the time to send a query to the
// acceptor, and the time it takes to receive a response.
timeout time.Duration
// params are our current chain params.
params *chaincfg.Params
// done is closed when the rpc client terminates.
done chan struct{}
// quit is closed when lnd is shutting down.
quit chan struct{}
wg sync.WaitGroup
}
// Accept is a predicate on the ChannelAcceptRequest which is sent to the RPC
// client who will respond with the ultimate decision. This function passes the
// request into the acceptor's requests channel, and returns the response it
// receives, failing the request if the timeout elapses.
//
// NOTE: Part of the ChannelAcceptor interface.
func (r *RPCAcceptor) Accept(req *ChannelAcceptRequest) *ChannelAcceptResponse {
respChan := make(chan *ChannelAcceptResponse, 1)
newRequest := &chanAcceptInfo{
request: req,
response: respChan,
}
// timeout is the time after which ChannelAcceptRequests expire.
timeout := time.After(r.timeout)
// Create a rejection response which we can use for the cases where we
// reject the channel.
rejectChannel := NewChannelAcceptResponse(
false, errChannelRejected, nil, 0, 0, 0, 0, 0, 0, false,
)
// Send the request to the newRequests channel.
select {
case r.requests <- newRequest:
case <-timeout:
log.Errorf("RPCAcceptor returned false - reached timeout of %v",
r.timeout)
return rejectChannel
case <-r.done:
return rejectChannel
case <-r.quit:
return rejectChannel
}
// Receive the response and return it. If no response has been received
// in AcceptorTimeout, then return false.
select {
case resp := <-respChan:
return resp
case <-timeout:
log.Errorf("RPCAcceptor returned false - reached timeout of %v",
r.timeout)
return rejectChannel
case <-r.done:
return rejectChannel
case <-r.quit:
return rejectChannel
}
}
// NewRPCAcceptor creates and returns an instance of the RPCAcceptor.
func NewRPCAcceptor(receive func() (*lnrpc.ChannelAcceptResponse, error),
send func(*lnrpc.ChannelAcceptRequest) error, timeout time.Duration,
params *chaincfg.Params, quit chan struct{}) *RPCAcceptor {
return &RPCAcceptor{
receive: receive,
send: send,
requests: make(chan *chanAcceptInfo),
timeout: timeout,
params: params,
done: make(chan struct{}),
quit: quit,
}
}
// Run is the main loop for the RPC Acceptor. This function will block until
// it receives the signal that lnd is shutting down, or the rpc stream is
// cancelled by the client.
func (r *RPCAcceptor) Run() error {
// Wait for our goroutines to exit before we return.
defer r.wg.Wait()
// Create a channel that responses from acceptors are sent into.
responses := make(chan *lnrpc.ChannelAcceptResponse)
// errChan is used by the receive loop to signal any errors that occur
// during reading from the stream. This is primarily used to shutdown
// the send loop in the case of an RPC client disconnecting.
errChan := make(chan error, 1)
// Start a goroutine to receive responses from the channel acceptor.
// We expect the receive function to block, so it must be run in a
// goroutine (otherwise we could not send more than one channel accept
// request to the client).
r.wg.Add(1)
go func() {
r.receiveResponses(errChan, responses)
r.wg.Done()
}()
return r.sendAcceptRequests(errChan, responses)
}
// receiveResponses receives responses for our channel accept requests and
// dispatches them into the responses channel provided, sending any errors that
// occur into the error channel provided.
func (r *RPCAcceptor) receiveResponses(errChan chan error,
responses chan *lnrpc.ChannelAcceptResponse) {
for {
resp, err := r.receive()
if err != nil {
errChan <- err
return
}
var pendingID [32]byte
copy(pendingID[:], resp.PendingChanId)
openChanResp := &lnrpc.ChannelAcceptResponse{
Accept: resp.Accept,
PendingChanId: pendingID[:],
Error: resp.Error,
UpfrontShutdown: resp.UpfrontShutdown,
CsvDelay: resp.CsvDelay,
ReserveSat: resp.ReserveSat,
InFlightMaxMsat: resp.InFlightMaxMsat,
MaxHtlcCount: resp.MaxHtlcCount,
MinHtlcIn: resp.MinHtlcIn,
MinAcceptDepth: resp.MinAcceptDepth,
ZeroConf: resp.ZeroConf,
}
// We have received a decision for one of our channel
// acceptor requests.
select {
case responses <- openChanResp:
case <-r.done:
return
case <-r.quit:
return
}
}
}
// sendAcceptRequests handles channel acceptor requests sent to us by our
// Accept() function, dispatching them to our acceptor stream and coordinating
// return of responses to their callers.
func (r *RPCAcceptor) sendAcceptRequests(errChan chan error,
responses chan *lnrpc.ChannelAcceptResponse) error {
// Close the done channel to indicate that the acceptor is no longer
// listening and any in-progress requests should be terminated.
defer close(r.done)
// Create a map of pending channel IDs to our original open channel
// request and a response channel. We keep the original channel open
// message so that we can validate our response against it.
acceptRequests := make(map[[32]byte]*chanAcceptInfo)
for {
//nolint:ll
select {
// Consume requests passed to us from our Accept() function and
// send them into our stream.
case newRequest := <-r.requests:
req := newRequest.request
pendingChanID := req.OpenChanMsg.PendingChannelID
// Map the channel commitment type to its RPC
// counterpart. Also determine whether the zero-conf or
// scid-alias channel types are set.
var (
commitmentType lnrpc.CommitmentType
wantsZeroConf bool
wantsScidAlias bool
)
if req.OpenChanMsg.ChannelType != nil {
channelFeatures := lnwire.RawFeatureVector(
*req.OpenChanMsg.ChannelType,
)
switch {
case channelFeatures.OnlyContains(
lnwire.ZeroConfRequired,
lnwire.ScidAliasRequired,
lnwire.ScriptEnforcedLeaseRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
commitmentType = lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE
case channelFeatures.OnlyContains(
lnwire.ZeroConfRequired,
lnwire.ScriptEnforcedLeaseRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
commitmentType = lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE
case channelFeatures.OnlyContains(
lnwire.ScidAliasRequired,
lnwire.ScriptEnforcedLeaseRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
commitmentType = lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE
case channelFeatures.OnlyContains(
lnwire.ScriptEnforcedLeaseRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
commitmentType = lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE
case channelFeatures.OnlyContains(
lnwire.ZeroConfRequired,
lnwire.ScidAliasRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
commitmentType = lnrpc.CommitmentType_ANCHORS
case channelFeatures.OnlyContains(
lnwire.ZeroConfRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
commitmentType = lnrpc.CommitmentType_ANCHORS
case channelFeatures.OnlyContains(
lnwire.ScidAliasRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
commitmentType = lnrpc.CommitmentType_ANCHORS
case channelFeatures.OnlyContains(
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
commitmentType = lnrpc.CommitmentType_ANCHORS
case channelFeatures.OnlyContains(
lnwire.SimpleTaprootChannelsRequiredStaging,
lnwire.ZeroConfRequired,
lnwire.ScidAliasRequired,
):
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
case channelFeatures.OnlyContains(
lnwire.SimpleTaprootChannelsRequiredStaging,
lnwire.ZeroConfRequired,
):
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
case channelFeatures.OnlyContains(
lnwire.SimpleTaprootChannelsRequiredStaging,
lnwire.ScidAliasRequired,
):
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
case channelFeatures.OnlyContains(
lnwire.SimpleTaprootChannelsRequiredStaging,
):
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
case channelFeatures.OnlyContains(
lnwire.SimpleTaprootOverlayChansRequired,
lnwire.ZeroConfRequired,
lnwire.ScidAliasRequired,
):
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY
case channelFeatures.OnlyContains(
lnwire.SimpleTaprootOverlayChansRequired,
lnwire.ZeroConfRequired,
):
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY
case channelFeatures.OnlyContains(
lnwire.SimpleTaprootOverlayChansRequired,
lnwire.ScidAliasRequired,
):
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY
case channelFeatures.OnlyContains(
lnwire.SimpleTaprootOverlayChansRequired,
):
commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY
case channelFeatures.OnlyContains(
lnwire.StaticRemoteKeyRequired,
):
commitmentType = lnrpc.CommitmentType_STATIC_REMOTE_KEY
case channelFeatures.OnlyContains():
commitmentType = lnrpc.CommitmentType_LEGACY
default:
log.Warnf("Unhandled commitment type "+
"in channel acceptor request: %v",
req.OpenChanMsg.ChannelType)
}
if channelFeatures.IsSet(
lnwire.ZeroConfRequired,
) {
wantsZeroConf = true
}
if channelFeatures.IsSet(
lnwire.ScidAliasRequired,
) {
wantsScidAlias = true
}
}
acceptRequests[pendingChanID] = newRequest
// A ChannelAcceptRequest has been received, send it to the client.
chanAcceptReq := &lnrpc.ChannelAcceptRequest{
NodePubkey: req.Node.SerializeCompressed(),
ChainHash: req.OpenChanMsg.ChainHash[:],
PendingChanId: req.OpenChanMsg.PendingChannelID[:],
FundingAmt: uint64(req.OpenChanMsg.FundingAmount),
PushAmt: uint64(req.OpenChanMsg.PushAmount),
DustLimit: uint64(req.OpenChanMsg.DustLimit),
MaxValueInFlight: uint64(req.OpenChanMsg.MaxValueInFlight),
ChannelReserve: uint64(req.OpenChanMsg.ChannelReserve),
MinHtlc: uint64(req.OpenChanMsg.HtlcMinimum),
FeePerKw: uint64(req.OpenChanMsg.FeePerKiloWeight),
CsvDelay: uint32(req.OpenChanMsg.CsvDelay),
MaxAcceptedHtlcs: uint32(req.OpenChanMsg.MaxAcceptedHTLCs),
ChannelFlags: uint32(req.OpenChanMsg.ChannelFlags),
CommitmentType: commitmentType,
WantsZeroConf: wantsZeroConf,
WantsScidAlias: wantsScidAlias,
}
if err := r.send(chanAcceptReq); err != nil {
return err
}
// Process newly received responses from our channel acceptor,
// looking the original request up in our map of requests and
// dispatching the response.
case resp := <-responses:
// Look up the appropriate channel to send on given the
// pending ID. If a channel is found, send the response
// over it.
var pendingID [32]byte
copy(pendingID[:], resp.PendingChanId)
requestInfo, ok := acceptRequests[pendingID]
if !ok {
continue
}
// Validate the response we have received. If it is not
// valid, we log our error and proceed to deliver the
// rejection.
accept, acceptErr, shutdown, err := r.validateAcceptorResponse(
requestInfo.request.OpenChanMsg.DustLimit, resp,
)
if err != nil {
log.Errorf("Invalid acceptor response: %v", err)
}
requestInfo.response <- NewChannelAcceptResponse(
accept, acceptErr, shutdown,
uint16(resp.CsvDelay),
uint16(resp.MaxHtlcCount),
uint16(resp.MinAcceptDepth),
btcutil.Amount(resp.ReserveSat),
lnwire.MilliSatoshi(resp.InFlightMaxMsat),
lnwire.MilliSatoshi(resp.MinHtlcIn),
resp.ZeroConf,
)
// Delete the channel from the acceptRequests map.
delete(acceptRequests, pendingID)
// If we failed to receive from our acceptor, we exit.
case err := <-errChan:
log.Errorf("Received an error: %v, shutting down", err)
return err
// Exit if we are shutting down.
case <-r.quit:
return errShuttingDown
}
}
}
// validateAcceptorResponse validates the response we get from the channel
// acceptor, returning a boolean indicating whether to accept the channel, an
// error to send to the peer, and any validation errors that occurred.
func (r *RPCAcceptor) validateAcceptorResponse(dustLimit btcutil.Amount,
req *lnrpc.ChannelAcceptResponse) (bool, error, lnwire.DeliveryAddress,
error) {
channelStr := hex.EncodeToString(req.PendingChanId)
// Check that the max htlc count is within the BOLT 2 hard-limit of 483.
// The initiating side should fail values above this anyway, but we
// catch the invalid user input here.
if req.MaxHtlcCount > input.MaxHTLCNumber/2 {
log.Errorf("Max htlc count: %v for channel: %v is greater "+
"than limit of: %v", req.MaxHtlcCount, channelStr,
input.MaxHTLCNumber/2)
return false, errChannelRejected, nil, errMaxHtlcTooHigh
}
// Ensure that the reserve that has been proposed, if it is set, is at
// least the dust limit that was proposed by the remote peer. This is
// required by BOLT 2.
reserveSat := btcutil.Amount(req.ReserveSat)
if reserveSat != 0 && reserveSat < dustLimit {
log.Errorf("Remote reserve: %v sat for channel: %v must be "+
"at least equal to proposed dust limit: %v",
req.ReserveSat, channelStr, dustLimit)
return false, errChannelRejected, nil, errInsufficientReserve
}
// Attempt to parse the upfront shutdown address provided.
upfront, err := chancloser.ParseUpfrontShutdownAddress(
req.UpfrontShutdown, r.params,
)
if err != nil {
log.Errorf("Could not parse upfront shutdown for "+
"%v: %v", channelStr, err)
return false, errChannelRejected, nil, errInvalidUpfrontShutdown
}
// Check that the custom error provided is valid.
if len(req.Error) > maxErrorLength {
return false, errChannelRejected, nil, errCustomLength
}
var haveCustomError = len(req.Error) != 0
switch {
// If accept is true, but we also have an error specified, we fail
// because this result is ambiguous.
case req.Accept && haveCustomError:
return false, errChannelRejected, nil, errAcceptWithError
// If we accept without an error message, we can just return a nil
// error.
case req.Accept:
return true, nil, upfront, nil
// If we reject the channel, and have a custom error, then we use it.
case haveCustomError:
return false, fmt.Errorf(req.Error), nil, nil
// Otherwise, we have rejected the channel with no custom error, so we
// just use a generic error to fail the channel.
default:
return false, errChannelRejected, nil, nil
}
}
// A compile-time constraint to ensure RPCAcceptor implements the ChannelAcceptor
// interface.
var _ ChannelAcceptor = (*RPCAcceptor)(nil)
package chanacceptor
import "github.com/lightningnetwork/lnd/lnwire"
// ZeroConfAcceptor wraps a regular ChainedAcceptor. If no acceptors are in the
// ChainedAcceptor, then Accept will reject all channel open requests. This
// should only be enabled when the zero-conf feature bit is set and is used to
// protect users from a malicious counter-party double-spending the zero-conf
// funding tx.
type ZeroConfAcceptor struct {
chainedAcceptor *ChainedAcceptor
}
// NewZeroConfAcceptor initializes a ZeroConfAcceptor.
func NewZeroConfAcceptor() *ZeroConfAcceptor {
return &ZeroConfAcceptor{
chainedAcceptor: NewChainedAcceptor(),
}
}
// AddAcceptor adds a sub-ChannelAcceptor to the internal ChainedAcceptor.
func (z *ZeroConfAcceptor) AddAcceptor(acceptor ChannelAcceptor) uint64 {
return z.chainedAcceptor.AddAcceptor(acceptor)
}
// RemoveAcceptor removes a sub-ChannelAcceptor from the internal
// ChainedAcceptor.
func (z *ZeroConfAcceptor) RemoveAcceptor(id uint64) {
z.chainedAcceptor.RemoveAcceptor(id)
}
// Accept will deny the channel open request if the internal ChainedAcceptor is
// empty. If the internal ChainedAcceptor has any acceptors, then Accept will
// instead be called on it.
//
// NOTE: Part of the ChannelAcceptor interface.
func (z *ZeroConfAcceptor) Accept(
req *ChannelAcceptRequest) *ChannelAcceptResponse {
// Alias for less verbosity.
channelType := req.OpenChanMsg.ChannelType
// Check if the channel type sets the zero-conf bit.
var zeroConfSet bool
if channelType != nil {
channelFeatures := lnwire.RawFeatureVector(*channelType)
zeroConfSet = channelFeatures.IsSet(lnwire.ZeroConfRequired)
}
// If there are no acceptors and the counter-party is requesting a zero
// conf channel, reject the attempt.
if z.chainedAcceptor.numAcceptors() == 0 && zeroConfSet {
// Deny the channel open request.
rejectChannel := NewChannelAcceptResponse(
false, nil, nil, 0, 0, 0, 0, 0, 0, false,
)
return rejectChannel
}
// Otherwise, the ChainedAcceptor has sub-acceptors, so call Accept on
// it.
return z.chainedAcceptor.Accept(req)
}
// A compile-time constraint to ensure ZeroConfAcceptor implements the
// MultiplexAcceptor interface.
var _ MultiplexAcceptor = (*ZeroConfAcceptor)(nil)
package chanbackup
import (
"fmt"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
)
// LiveChannelSource is an interface that allows us to query for the set of
// live channels. A live channel is one that is open, and has not had a
// commitment transaction broadcast.
type LiveChannelSource interface {
// FetchAllChannels returns all known live channels.
FetchAllChannels() ([]*channeldb.OpenChannel, error)
// FetchChannel attempts to locate a live channel identified by the
// passed chanPoint. Optionally an existing db tx can be supplied.
FetchChannel(chanPoint wire.OutPoint) (*channeldb.OpenChannel, error)
}
// assembleChanBackup attempts to assemble a static channel backup for the
// passed open channel. The backup includes all information required to restore
// the channel, as well as addressing information so we can find the peer and
// reconnect to them to initiate the protocol.
func assembleChanBackup(addrSource channeldb.AddrSource,
openChan *channeldb.OpenChannel) (*Single, error) {
log.Debugf("Crafting backup for ChannelPoint(%v)",
openChan.FundingOutpoint)
// First, we'll query the channel source to obtain all the addresses
// that are associated with the peer for this channel.
known, nodeAddrs, err := addrSource.AddrsForNode(openChan.IdentityPub)
if err != nil {
return nil, err
}
if !known {
return nil, fmt.Errorf("node unknown by address source")
}
single := NewSingle(openChan, nodeAddrs)
return &single, nil
}
// buildCloseTxInputs generates inputs needed to force close a channel from
// an open channel. Anyone having these inputs and the signer, can sign the
// force closure transaction. Warning! If the channel state updates, an attempt
// to close the channel using this method with outdated CloseTxInputs can result
// in loss of funds! This may happen if an outdated channel backup is attempted
// to be used to force close the channel.
func buildCloseTxInputs(
targetChan *channeldb.OpenChannel) fn.Option[CloseTxInputs] {
log.Debugf("Crafting CloseTxInputs for ChannelPoint(%v)",
targetChan.FundingOutpoint)
localCommit := targetChan.LocalCommitment
if localCommit.CommitTx == nil {
log.Infof("CommitTx is nil for ChannelPoint(%v), "+
"skipping CloseTxInputs. This is possible when "+
"DLP is active.", targetChan.FundingOutpoint)
return fn.None[CloseTxInputs]()
}
// We need unsigned force close tx and the counterparty's signature.
inputs := CloseTxInputs{
CommitTx: localCommit.CommitTx,
CommitSig: localCommit.CommitSig,
}
// In case of a taproot channel, commit height is needed as well to
// produce verification nonce for the taproot channel using shachain.
if targetChan.ChanType.IsTaproot() {
inputs.CommitHeight = localCommit.CommitHeight
}
// In case of a custom taproot channel, TapscriptRoot is needed as well.
if targetChan.ChanType.HasTapscriptRoot() {
inputs.TapscriptRoot = targetChan.TapscriptRoot
}
return fn.Some(inputs)
}
// FetchBackupForChan attempts to create a plaintext static channel backup for
// the target channel identified by its channel point. If we're unable to find
// the target channel, then an error will be returned.
func FetchBackupForChan(chanPoint wire.OutPoint, chanSource LiveChannelSource,
addrSource channeldb.AddrSource) (*Single, error) {
// First, we'll query the channel source to see if the channel is known
// and open within the database.
targetChan, err := chanSource.FetchChannel(chanPoint)
if err != nil {
// If we can't find the channel, then we return with an error,
// as we have nothing to backup.
return nil, fmt.Errorf("unable to find target channel")
}
// Once we have the target channel, we can assemble the backup using
// the source to obtain any extra information that we may need.
staticChanBackup, err := assembleChanBackup(addrSource, targetChan)
if err != nil {
return nil, fmt.Errorf("unable to create chan backup: %w", err)
}
return staticChanBackup, nil
}
// FetchStaticChanBackups will return a plaintext static channel back up for
// all known active/open channels within the passed channel source.
func FetchStaticChanBackups(chanSource LiveChannelSource,
addrSource channeldb.AddrSource) ([]Single, error) {
// First, we'll query the backup source for information concerning all
// currently open and available channels.
openChans, err := chanSource.FetchAllChannels()
if err != nil {
return nil, err
}
// Now that we have all the channels, we'll use the chanSource to
// obtain any auxiliary information we need to craft a backup for each
// channel.
staticChanBackups := make([]Single, 0, len(openChans))
for _, openChan := range openChans {
chanBackup, err := assembleChanBackup(addrSource, openChan)
if err != nil {
return nil, err
}
staticChanBackups = append(staticChanBackups, *chanBackup)
}
return staticChanBackups, nil
}
package chanbackup
import (
"fmt"
"io"
"os"
"path/filepath"
"time"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnrpc"
)
const (
// DefaultBackupFileName is the default name of the auto updated static
// channel backup fie.
DefaultBackupFileName = "channel.backup"
// DefaultTempBackupFileName is the default name of the temporary SCB
// file that we'll use to atomically update the primary back up file
// when new channel are detected.
DefaultTempBackupFileName = "temp-dont-use.backup"
// DefaultChanBackupArchiveDirName is the default name of the directory
// that we'll use to store old channel backups.
DefaultChanBackupArchiveDirName = "chan-backup-archives"
)
var (
// ErrNoBackupFileExists is returned if caller attempts to call
// UpdateAndSwap with the file name not set.
ErrNoBackupFileExists = fmt.Errorf("back up file name not set")
// ErrNoTempBackupFile is returned if caller attempts to call
// UpdateAndSwap with the temp back up file name not set.
ErrNoTempBackupFile = fmt.Errorf("temp backup file not set")
)
// MultiFile represents a file on disk that a caller can use to read the packed
// multi backup into an unpacked one, and also atomically update the contents
// on disk once new channels have been opened, and old ones closed. This struct
// relies on an atomic file rename property which most widely use file systems
// have.
type MultiFile struct {
// fileName is the file name of the main back up file.
fileName string
// tempFileName is the name of the file that we'll use to stage a new
// packed multi-chan backup, and the rename to the main back up file.
tempFileName string
// tempFile is an open handle to the temp back up file.
tempFile *os.File
// archiveDir is the directory where we'll store old channel backups.
archiveDir string
// noBackupArchive indicates whether old backups should be deleted
// rather than archived.
noBackupArchive bool
}
// NewMultiFile create a new multi-file instance at the target location on the
// file system.
func NewMultiFile(fileName string, noBackupArchive bool) *MultiFile {
// We'll our temporary backup file in the very same directory as the
// main backup file.
backupFileDir := filepath.Dir(fileName)
tempFileName := filepath.Join(
backupFileDir, DefaultTempBackupFileName,
)
archiveDir := filepath.Join(
backupFileDir, DefaultChanBackupArchiveDirName,
)
return &MultiFile{
fileName: fileName,
tempFileName: tempFileName,
archiveDir: archiveDir,
noBackupArchive: noBackupArchive,
}
}
// UpdateAndSwap will attempt write a new temporary backup file to disk with
// the newBackup encoded, then atomically swap (via rename) the old file for
// the new file by updating the name of the new file to the old. It also checks
// if the old file should be archived first before swapping it.
func (b *MultiFile) UpdateAndSwap(newBackup PackedMulti) error {
// If the main backup file isn't set, then we can't proceed.
if b.fileName == "" {
return ErrNoBackupFileExists
}
log.Infof("Updating backup file at %v", b.fileName)
// If the old back up file still exists, then we'll delete it before
// proceeding.
if _, err := os.Stat(b.tempFileName); err == nil {
log.Infof("Found old temp backup @ %v, removing before swap",
b.tempFileName)
err = os.Remove(b.tempFileName)
if err != nil {
return fmt.Errorf("unable to remove temp "+
"backup file: %v", err)
}
}
// Now that we know the staging area is clear, we'll create the new
// temporary back up file.
var err error
b.tempFile, err = os.Create(b.tempFileName)
if err != nil {
return fmt.Errorf("unable to create temp file: %w", err)
}
// With the file created, we'll write the new packed multi backup and
// remove the temporary file all together once this method exits.
_, err = b.tempFile.Write([]byte(newBackup))
if err != nil {
return fmt.Errorf("unable to write backup to temp file: %w",
err)
}
if err := b.tempFile.Sync(); err != nil {
return fmt.Errorf("unable to sync temp file: %w", err)
}
defer os.Remove(b.tempFileName)
log.Infof("Swapping old multi backup file from %v to %v",
b.tempFileName, b.fileName)
// Before we rename the swap (atomic name swap), we'll make
// sure to close the current file as some OSes don't support
// renaming a file that's already open (Windows).
if err := b.tempFile.Close(); err != nil {
return fmt.Errorf("unable to close file: %w", err)
}
// Archive the old channel backup file before replacing.
if err := b.createArchiveFile(); err != nil {
return fmt.Errorf("unable to archive old channel "+
"backup file: %w", err)
}
// Finally, we'll attempt to atomically rename the temporary file to
// the main back up file. If this succeeds, then we'll only have a
// single file on disk once this method exits.
return os.Rename(b.tempFileName, b.fileName)
}
// ExtractMulti attempts to extract the packed multi backup we currently point
// to into an unpacked version. This method will fail if no backup file
// currently exists as the specified location.
func (b *MultiFile) ExtractMulti(keyChain keychain.KeyRing) (*Multi, error) {
var err error
// We'll return an error if the main file isn't currently set.
if b.fileName == "" {
return nil, ErrNoBackupFileExists
}
// Now that we've confirmed the target file is populated, we'll read
// all the contents of the file. This function ensures that file is
// always closed, even if we can't read the contents.
multiBytes, err := os.ReadFile(b.fileName)
if err != nil {
return nil, err
}
// Finally, we'll attempt to unpack the file and return the unpack
// version to the caller.
packedMulti := PackedMulti(multiBytes)
return packedMulti.Unpack(keyChain)
}
// createArchiveFile creates an archive file with a timestamped name in the
// specified archive directory, and copies the contents of the main backup file
// to the new archive file.
func (b *MultiFile) createArchiveFile() error {
// User can skip archiving of old backup files to save disk space.
if b.noBackupArchive {
log.Debug("Skipping archive of old backup file as configured")
return nil
}
// Check for old channel backup file.
oldFileExists := lnrpc.FileExists(b.fileName)
if !oldFileExists {
log.Debug("No old channel backup file to archive")
return nil
}
log.Infof("Archiving old channel backup to %v", b.archiveDir)
// Generate archive file path with timestamped name.
baseFileName := filepath.Base(b.fileName)
timestamp := time.Now().Format("2006-01-02-15-04-05")
archiveFileName := fmt.Sprintf("%s-%s", baseFileName, timestamp)
archiveFilePath := filepath.Join(b.archiveDir, archiveFileName)
oldBackupFile, err := os.Open(b.fileName)
if err != nil {
return fmt.Errorf("unable to open old channel backup file: "+
"%w", err)
}
defer func() {
err := oldBackupFile.Close()
if err != nil {
log.Errorf("unable to close old channel backup file: "+
"%v", err)
}
}()
// Ensure the archive directory exists. If it doesn't we create it.
const archiveDirPermissions = 0o700
err = os.MkdirAll(b.archiveDir, archiveDirPermissions)
if err != nil {
return fmt.Errorf("unable to create archive directory: %w", err)
}
// Create new archive file.
archiveFile, err := os.Create(archiveFilePath)
if err != nil {
return fmt.Errorf("unable to create archive file: %w", err)
}
defer func() {
err := archiveFile.Close()
if err != nil {
log.Errorf("unable to close archive file: %v", err)
}
}()
// Copy contents of old backup to the newly created archive files.
_, err = io.Copy(archiveFile, oldBackupFile)
if err != nil {
return fmt.Errorf("unable to copy to archive file: %w", err)
}
err = archiveFile.Sync()
if err != nil {
return fmt.Errorf("unable to sync archive file: %w", err)
}
return nil
}
package chanbackup
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("CHBU", nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package chanbackup
import (
"bytes"
"fmt"
"io"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnencrypt"
"github.com/lightningnetwork/lnd/lnwire"
)
// MultiBackupVersion denotes the version of the multi channel static channel
// backup. Based on this version, we know how to encode/decode packed/unpacked
// versions of multi backups.
type MultiBackupVersion byte
const (
// DefaultMultiVersion is the default version of the multi channel
// backup. The serialized format for this version is simply: version ||
// numBackups || SCBs...
DefaultMultiVersion = 0
// NilMultiSizePacked is the size of a "nil" packed Multi (45 bytes).
// This consists of the 24 byte chacha nonce, the 16 byte MAC, one byte
// for the version, and 4 bytes to signal zero entries.
NilMultiSizePacked = 24 + 16 + 1 + 4
)
// Multi is a form of static channel backup that is amenable to being
// serialized in a single file. Rather than a series of ciphertexts, a
// multi-chan backup is a single ciphertext of all static channel backups
// concatenated. This form factor gives users a single blob that they can use
// to safely copy/obtain at anytime to backup their channels.
type Multi struct {
// Version is the version that should be observed when attempting to
// pack the multi backup.
Version MultiBackupVersion
// StaticBackups is the set of single channel backups that this multi
// backup is comprised of.
StaticBackups []Single
}
// PackToWriter packs (encrypts+serializes) the target set of static channel
// backups into a single AEAD ciphertext into the passed io.Writer. This is the
// opposite of UnpackFromReader. The plaintext form of a multi-chan backup is
// the following: a 4 byte integer denoting the number of serialized static
// channel backups serialized, a series of serialized static channel backups
// concatenated. To pack this payload, we then apply our chacha20 AEAD to the
// entire payload, using the 24-byte nonce as associated data.
func (m Multi) PackToWriter(w io.Writer, keyRing keychain.KeyRing) error {
// The only version that we know how to pack atm is version 0. Attempts
// to pack any other version will result in an error.
switch m.Version {
case DefaultMultiVersion:
break
default:
return fmt.Errorf("unable to pack unknown multi-version "+
"of %v", m.Version)
}
var multiBackupBuffer bytes.Buffer
// First, we'll write out the version of this multi channel baackup.
err := lnwire.WriteElements(&multiBackupBuffer, byte(m.Version))
if err != nil {
return err
}
// Now that we've written out the version of this multi-pack format,
// we'll now write the total number of backups to expect after this
// point.
numBackups := uint32(len(m.StaticBackups))
err = lnwire.WriteElements(&multiBackupBuffer, numBackups)
if err != nil {
return err
}
// Next, we'll serialize the raw plaintext version of each of the
// backup into the intermediate buffer.
for _, chanBackup := range m.StaticBackups {
err := chanBackup.Serialize(&multiBackupBuffer)
if err != nil {
return fmt.Errorf("unable to serialize backup "+
"for %v: %v", chanBackup.FundingOutpoint, err)
}
}
// With the plaintext multi backup assembled, we'll now encrypt it
// directly to the passed writer.
e, err := lnencrypt.KeyRingEncrypter(keyRing)
if err != nil {
return fmt.Errorf("unable to generate encrypt key %w", err)
}
return e.EncryptPayloadToWriter(multiBackupBuffer.Bytes(), w)
}
// UnpackFromReader attempts to unpack (decrypt+deserialize) a packed
// multi-chan backup form the passed io.Reader. If we're unable to decrypt the
// any portion of the multi-chan backup, an error will be returned.
func (m *Multi) UnpackFromReader(r io.Reader, keyRing keychain.KeyRing) error {
// We'll attempt to read the entire packed backup, and also decrypt it
// using the passed key ring which is expected to be able to derive the
// encryption keys.
e, err := lnencrypt.KeyRingEncrypter(keyRing)
if err != nil {
return fmt.Errorf("unable to generate encrypt key %w", err)
}
plaintextBackup, err := e.DecryptPayloadFromReader(r)
if err != nil {
return err
}
backupReader := bytes.NewReader(plaintextBackup)
// Now that we've decrypted the payload successfully, we can parse out
// each of the individual static channel backups.
// First, we'll need to read the version of this multi-back up so we
// can know how to unpack each of the individual SCB's.
var multiVersion byte
err = lnwire.ReadElements(backupReader, &multiVersion)
if err != nil {
return err
}
m.Version = MultiBackupVersion(multiVersion)
switch m.Version {
// The default version is simply a set of serialized SCB's with the
// number of total SCB's prepended to the front of the byte slice.
case DefaultMultiVersion:
// First, we'll need to read out the total number of backups
// that've been serialized into this multi-chan backup. Each
// backup is the same size, so we can continue until we've
// parsed out everything.
var numBackups uint32
err = lnwire.ReadElements(backupReader, &numBackups)
if err != nil {
return err
}
// We'll continue to parse out each backup until we've read all
// that was indicated from the length prefix.
for ; numBackups != 0; numBackups-- {
// Attempt to parse out the net static channel backup,
// if it's been malformed, then we'll return with an
// error
var chanBackup Single
err := chanBackup.Deserialize(backupReader)
if err != nil {
return err
}
// Collect the next valid chan backup into the main
// multi backup slice.
m.StaticBackups = append(m.StaticBackups, chanBackup)
}
default:
return fmt.Errorf("unable to unpack unknown multi-version "+
"of %v", multiVersion)
}
return nil
}
// TODO(roasbeef): new key ring interface?
// * just returns key given params?
// PackedMulti represents a raw fully packed (serialized+encrypted)
// multi-channel static channel backup.
type PackedMulti []byte
// Unpack attempts to unpack (decrypt+desrialize) the target packed
// multi-channel back up. If we're unable to fully unpack this back, then an
// error will be returned.
func (p *PackedMulti) Unpack(keyRing keychain.KeyRing) (*Multi, error) {
var m Multi
packedReader := bytes.NewReader(*p)
if err := m.UnpackFromReader(packedReader, keyRing); err != nil {
return nil, err
}
return &m, nil
}
// TODO(roasbsef): fuzz parsing
package chanbackup
import (
"bytes"
"fmt"
"net"
"os"
"sync"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnutils"
)
// Swapper is an interface that allows the chanbackup.SubSwapper to update the
// main multi backup location once it learns of new channels or that prior
// channels have been closed.
type Swapper interface {
// UpdateAndSwap attempts to atomically update the main multi back up
// file location with the new fully packed multi-channel backup.
UpdateAndSwap(newBackup PackedMulti) error
// ExtractMulti attempts to obtain and decode the current SCB instance
// stored by the Swapper instance.
ExtractMulti(keychain keychain.KeyRing) (*Multi, error)
}
// ChannelWithAddrs bundles an open channel along with all the addresses for
// the channel peer.
type ChannelWithAddrs struct {
*channeldb.OpenChannel
// Addrs is the set of addresses that we can use to reach the target
// peer.
Addrs []net.Addr
}
// ChannelEvent packages a new update of new channels since subscription, and
// channels that have been opened since prior channel event.
type ChannelEvent struct {
// ClosedChans are the set of channels that have been closed since the
// last event.
ClosedChans []wire.OutPoint
// NewChans is the set of channels that have been opened since the last
// event.
NewChans []ChannelWithAddrs
}
// manualUpdate holds a group of channel state updates and an error channel
// to send back an error happened upon update processing or file updating.
type manualUpdate struct {
// singles hold channels backups. They can be either new or known
// channels in the Swapper.
singles []Single
// errChan is the channel to send an error back. If the update handling
// and the subsequent file updating succeeds, nil is sent.
// The channel must have capacity of 1 to prevent Swapper blocking.
errChan chan error
}
// ChannelSubscription represents an intent to be notified of any updates to
// the primary channel state.
type ChannelSubscription struct {
// ChanUpdates is a channel that will be sent upon once the primary
// channel state is updated.
ChanUpdates chan ChannelEvent
// Cancel is a closure that allows the caller to cancel their
// subscription and free up any resources allocated.
Cancel func()
}
// ChannelNotifier represents a system that allows the chanbackup.SubSwapper to
// be notified of any changes to the primary channel state.
type ChannelNotifier interface {
// SubscribeChans requests a new channel subscription relative to the
// initial set of known channels. We use the knownChans as a
// synchronization point to ensure that the chanbackup.SubSwapper does
// not miss any channel open or close events in the period between when
// it's created, and when it requests the channel subscription.
SubscribeChans(map[wire.OutPoint]struct{}) (*ChannelSubscription, error)
}
// SubSwapper subscribes to new updates to the open channel state, and then
// swaps out the on-disk channel backup state in response. This sub-system
// that will ensure that the multi chan backup file on disk will always be
// updated with the latest channel back up state. We'll receive new
// opened/closed channels from the ChannelNotifier, then use the Swapper to
// update the file state on disk with the new set of open channels. This can
// be used to implement a system that always keeps the multi-chan backup file
// on disk in a consistent state for safety purposes.
type SubSwapper struct {
started sync.Once
stopped sync.Once
// backupState are the set of SCBs for all open channels we know of.
backupState map[wire.OutPoint]Single
// chanEvents is an active subscription to receive new channel state
// over.
chanEvents *ChannelSubscription
manualUpdates chan manualUpdate
// keyRing is the main key ring that will allow us to pack the new
// multi backup.
keyRing keychain.KeyRing
Swapper
quit chan struct{}
wg sync.WaitGroup
}
// NewSubSwapper creates a new instance of the SubSwapper given the starting
// set of channels, and the required interfaces to be notified of new channel
// updates, pack a multi backup, and swap the current best backup from its
// storage location.
func NewSubSwapper(startingChans []Single, chanNotifier ChannelNotifier,
keyRing keychain.KeyRing, backupSwapper Swapper) (*SubSwapper, error) {
// First, we'll subscribe to the latest set of channel updates given
// the set of channels we already know of.
knownChans := make(map[wire.OutPoint]struct{})
for _, chanBackup := range startingChans {
knownChans[chanBackup.FundingOutpoint] = struct{}{}
}
chanEvents, err := chanNotifier.SubscribeChans(knownChans)
if err != nil {
return nil, err
}
// Next, we'll construct our own backup state so we can add/remove
// channels that have been opened and closed.
backupState := make(map[wire.OutPoint]Single)
for _, chanBackup := range startingChans {
backupState[chanBackup.FundingOutpoint] = chanBackup
}
return &SubSwapper{
backupState: backupState,
chanEvents: chanEvents,
keyRing: keyRing,
Swapper: backupSwapper,
quit: make(chan struct{}),
manualUpdates: make(chan manualUpdate),
}, nil
}
// Start starts the chanbackup.SubSwapper.
func (s *SubSwapper) Start() error {
var startErr error
s.started.Do(func() {
log.Infof("chanbackup.SubSwapper starting")
// Before we enter our main loop, we'll update the on-disk
// state with the latest Single state, as nodes may have new
// advertised addresses.
if err := s.updateBackupFile(); err != nil {
startErr = fmt.Errorf("unable to refresh backup "+
"file: %v", err)
return
}
s.wg.Add(1)
go s.backupUpdater()
})
return startErr
}
// Stop signals the SubSwapper to being a graceful shutdown.
func (s *SubSwapper) Stop() error {
s.stopped.Do(func() {
log.Infof("chanbackup.SubSwapper shutting down...")
defer log.Debug("chanbackup.SubSwapper shutdown complete")
close(s.quit)
s.wg.Wait()
})
return nil
}
// ManualUpdate inserts/updates channel states into the swapper. The updates
// are processed in another goroutine. The method waits for the updates to be
// fully processed and the file to be updated on-disk before returning.
func (s *SubSwapper) ManualUpdate(singles []Single) error {
// Create the channel to send an error back. If the update handling
// and the subsequent file updating succeeds, nil is sent.
// The channel must have capacity of 1 to prevent Swapper blocking.
errChan := make(chan error, 1)
// Create the update object to insert into the processing loop.
update := manualUpdate{
singles: singles,
errChan: errChan,
}
select {
case s.manualUpdates <- update:
case <-s.quit:
return fmt.Errorf("swapper stopped when sending manual update")
}
// Wait for processing, block on errChan.
select {
case err := <-errChan:
if err != nil {
return fmt.Errorf("processing of manual update "+
"failed: %w", err)
}
case <-s.quit:
return fmt.Errorf("swapper stopped when waiting for outcome")
}
// Success.
return nil
}
// updateBackupFile updates the backup file in place given the current state of
// the SubSwapper. We accept the set of channels that were closed between this
// update and the last to make sure we leave them out of our backup set union.
func (s *SubSwapper) updateBackupFile(closedChans ...wire.OutPoint) error {
// Before we pack the new set of SCBs, we'll first decode what we
// already have on-disk, to make sure we can decode it (proper seed)
// and that we're able to combine it with our new data.
diskMulti, err := s.Swapper.ExtractMulti(s.keyRing)
// If the file doesn't exist on disk, then that's OK as it was never
// created. In this case we'll continue onwards as it isn't a critical
// error.
if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("unable to extract on disk encrypted "+
"SCB: %v", err)
}
// Now that we have channels stored on-disk, we'll create a new set of
// the combined old and new channels to make sure we retain what's
// already on-disk.
//
// NOTE: The ordering of this operations means that our in-memory
// structure will replace what we read from disk.
combinedBackup := make(map[wire.OutPoint]Single)
if diskMulti != nil {
for _, diskChannel := range diskMulti.StaticBackups {
chanPoint := diskChannel.FundingOutpoint
combinedBackup[chanPoint] = diskChannel
}
}
for _, memChannel := range s.backupState {
chanPoint := memChannel.FundingOutpoint
if _, ok := combinedBackup[chanPoint]; ok {
log.Warnf("Replacing disk backup for ChannelPoint(%v) "+
"w/ newer version", chanPoint)
}
combinedBackup[chanPoint] = memChannel
}
// Remove the set of closed channels from the final set of backups.
for _, closedChan := range closedChans {
delete(combinedBackup, closedChan)
}
// With our updated channel state obtained, we'll create a new multi
// from our series of singles.
var newMulti Multi
for _, backup := range combinedBackup {
newMulti.StaticBackups = append(
newMulti.StaticBackups, backup,
)
}
// Now that our multi has been assembled, we'll attempt to pack
// (encrypt+encode) the new channel state to our target reader.
var b bytes.Buffer
err = newMulti.PackToWriter(&b, s.keyRing)
if err != nil {
return fmt.Errorf("unable to pack multi backup: %w", err)
}
// Finally, we'll swap out the old backup for this new one in a single
// atomic step, combining the file already on-disk with this set of new
// channels.
err = s.Swapper.UpdateAndSwap(PackedMulti(b.Bytes()))
if err != nil {
return fmt.Errorf("unable to update multi backup: %w", err)
}
return nil
}
// backupFileUpdater is the primary goroutine of the SubSwapper which is
// responsible for listening for changes to the channel, and updating the
// persistent multi backup state with a new packed multi of the latest channel
// state.
func (s *SubSwapper) backupUpdater() {
// Ensure that once we exit, we'll cancel our active channel
// subscription.
defer s.chanEvents.Cancel()
defer s.wg.Done()
log.Debugf("SubSwapper's backupUpdater is active!")
for {
select {
// The channel state has been modified! We'll evaluate all
// changes, and swap out the old packed multi with a new one
// with the latest channel state.
case chanUpdate := <-s.chanEvents.ChanUpdates:
oldStateSize := len(s.backupState)
// For all new open channels, we'll create a new SCB
// given the required information.
for _, newChan := range chanUpdate.NewChans {
log.Debugf("Adding channel %v to backup state",
newChan.FundingOutpoint)
single := NewSingle(
newChan.OpenChannel, newChan.Addrs,
)
s.backupState[newChan.FundingOutpoint] = single
}
// For all closed channels, we'll remove the prior
// backup state.
closedChans := make(
[]wire.OutPoint, 0, len(chanUpdate.ClosedChans),
)
for i, closedChan := range chanUpdate.ClosedChans {
log.Debugf("Removing channel %v from backup "+
"state", lnutils.NewLogClosure(
chanUpdate.ClosedChans[i].String))
delete(s.backupState, closedChan)
closedChans = append(closedChans, closedChan)
}
newStateSize := len(s.backupState)
log.Infof("Updating on-disk multi SCB backup: "+
"num_old_chans=%v, num_new_chans=%v",
oldStateSize, newStateSize)
// Without new state constructed, we'll, atomically
// update the on-disk backup state.
if err := s.updateBackupFile(closedChans...); err != nil {
log.Errorf("unable to update backup file: %v",
err)
}
// We received a manual update. Handle it and update the file.
case manualUpdate := <-s.manualUpdates:
oldStateSize := len(s.backupState)
// For all open channels, we'll create a new SCB given
// the required information.
for _, single := range manualUpdate.singles {
log.Debugf("Manual update of channel %v",
single.FundingOutpoint)
s.backupState[single.FundingOutpoint] = single
}
newStateSize := len(s.backupState)
log.Infof("Updating on-disk multi SCB backup: "+
"num_old_chans=%v, num_new_chans=%v",
oldStateSize, newStateSize)
// Without new state constructed, we'll, atomically
// update the on-disk backup state.
err := s.updateBackupFile()
if err != nil {
log.Errorf("unable to update backup file: %v",
err)
}
// Send the error (or nil) to the caller of
// ManualUpdate. The error channel must have capacity of
// 1 not to block here.
manualUpdate.errChan <- err
// TODO(roasbeef): refresh periodically on a time basis due to
// possible addr changes from node
// Exit at once if a quit signal is detected.
case <-s.quit:
return
}
}
}
package chanbackup
import (
"net"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnutils"
)
// ChannelRestorer is an interface that allows the Recover method to map the
// set of single channel backups into a set of "channel shells" and store these
// persistently on disk. The channel shell should contain all the information
// needed to execute the data loss recovery protocol once the channel peer is
// connected to.
type ChannelRestorer interface {
// RestoreChansFromSingles attempts to map the set of single channel
// backups to channel shells that will be stored persistently. Once
// these shells have been stored on disk, we'll be able to connect to
// the channel peer an execute the data loss recovery protocol.
RestoreChansFromSingles(...Single) error
}
// PeerConnector is an interface that allows the Recover method to connect to
// the target node given the set of possible addresses.
type PeerConnector interface {
// ConnectPeer attempts to connect to the target node at the set of
// available addresses. Once this method returns with a non-nil error,
// the connector should attempt to persistently connect to the target
// peer in the background as a persistent attempt.
ConnectPeer(node *btcec.PublicKey, addrs []net.Addr) error
}
// Recover attempts to recover the static channel state from a set of static
// channel backups. If successfully, the database will be populated with a
// series of "shell" channels. These "shell" channels cannot be used to operate
// the channel as normal, but instead are meant to be used to enter the data
// loss recovery phase, and recover the settled funds within
// the channel. In addition a LinkNode will be created for each new peer as
// well, in order to expose the addressing information required to locate to
// and connect to each peer in order to initiate the recovery protocol.
// The number of channels that were successfully restored is returned.
func Recover(backups []Single, restorer ChannelRestorer,
peerConnector PeerConnector) (int, error) {
var numRestored int
for i, backup := range backups {
log.Infof("Restoring ChannelPoint(%v) to disk: ",
backup.FundingOutpoint)
err := restorer.RestoreChansFromSingles(backup)
// If a channel is already present in the channel DB, we can
// just continue. No reason to fail a whole set of multi backups
// for example. This allows resume of a restore in case another
// error happens.
if err == channeldb.ErrChanAlreadyExists {
continue
}
if err != nil {
return numRestored, err
}
numRestored++
log.Infof("Attempting to connect to node=%x (addrs=%v) to "+
"restore ChannelPoint(%v)",
backup.RemoteNodePub.SerializeCompressed(),
lnutils.SpewLogClosure(backups[i].Addresses),
backup.FundingOutpoint)
err = peerConnector.ConnectPeer(
backup.RemoteNodePub, backup.Addresses,
)
if err != nil {
return numRestored, err
}
// TODO(roasbeef): to handle case where node has changed addrs,
// need to subscribe to new updates for target node pub to
// attempt to connect to other addrs
//
// * just to to fresh w/ call to node addrs and de-dup?
}
return numRestored, nil
}
// TODO(roasbeef): more specific keychain interface?
// UnpackAndRecoverSingles is a one-shot method, that given a set of packed
// single channel backups, will restore the channel state to a channel shell,
// and also reach out to connect to any of the known node addresses for that
// channel. It is assumes that after this method exists, if a connection was
// established, then the PeerConnector will continue to attempt to re-establish
// a persistent connection in the background. The number of channels that were
// successfully restored is returned.
func UnpackAndRecoverSingles(singles PackedSingles,
keyChain keychain.KeyRing, restorer ChannelRestorer,
peerConnector PeerConnector) (int, error) {
chanBackups, err := singles.Unpack(keyChain)
if err != nil {
return 0, err
}
return Recover(chanBackups, restorer, peerConnector)
}
// UnpackAndRecoverMulti is a one-shot method, that given a set of packed
// multi-channel backups, will restore the channel states to channel shells,
// and also reach out to connect to any of the known node addresses for that
// channel. It is assumes that after this method exists, if a connection was
// established, then the PeerConnector will continue to attempt to re-establish
// a persistent connection in the background. The number of channels that were
// successfully restored is returned.
func UnpackAndRecoverMulti(packedMulti PackedMulti,
keyChain keychain.KeyRing, restorer ChannelRestorer,
peerConnector PeerConnector) (int, error) {
chanBackups, err := packedMulti.Unpack(keyChain)
if err != nil {
return 0, err
}
return Recover(chanBackups.StaticBackups, restorer, peerConnector)
}
package chanbackup
import (
"bytes"
"errors"
"fmt"
"io"
"net"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnencrypt"
"github.com/lightningnetwork/lnd/lnwire"
)
// SingleBackupVersion denotes the version of the single static channel backup.
// Based on this version, we know how to pack/unpack serialized versions of the
// backup.
type SingleBackupVersion byte
const (
// DefaultSingleVersion is the default version of the single channel
// backup. The serialized version of this static channel backup is
// simply: version || SCB. Where SCB is the known format of the
// version.
DefaultSingleVersion = 0
// TweaklessCommitVersion is the second SCB version. This version
// implicitly denotes that this channel uses the new tweakless commit
// format.
TweaklessCommitVersion = 1
// AnchorsCommitVersion is the third SCB version. This version
// implicitly denotes that this channel uses the new anchor commitment
// format.
AnchorsCommitVersion = 2
// AnchorsZeroFeeHtlcTxCommitVersion is a version that denotes this
// channel is using the zero-fee second-level anchor commitment format.
AnchorsZeroFeeHtlcTxCommitVersion = 3
// ScriptEnforcedLeaseVersion is a version that denotes this channel is
// using the zero-fee second-level anchor commitment format along with
// an additional CLTV requirement of the channel lease maturity on any
// commitment and HTLC outputs that pay directly to the channel
// initiator.
ScriptEnforcedLeaseVersion = 4
// SimpleTaprootVersion is a version that denotes this channel is using
// the musig2 based taproot commitment format.
SimpleTaprootVersion = 5
// TapscriptRootVersion is a version that denotes this is a MuSig2
// channel with a top level tapscript commitment.
TapscriptRootVersion = 6
// closeTxVersionMask is the byte mask used that is ORed to version byte
// on wire indicating that the backup has CloseTxInputs.
closeTxVersionMask = 1 << 7
)
// Encode returns encoding of the version to put into channel backup.
// Argument "closeTx" specifies if the backup includes force close transaction.
func (v SingleBackupVersion) Encode(closeTx bool) byte {
encoded := byte(v)
// If the backup includes closing transaction, set this bit in the
// encoded version.
if closeTx {
encoded |= closeTxVersionMask
}
return encoded
}
// DecodeVersion decodes the encoding of the version from a channel backup.
// It returns the version and if the backup includes the force close tx.
func DecodeVersion(encoded byte) (SingleBackupVersion, bool) {
// Find if it has a closing transaction by inspecting the bit.
closeTx := (encoded & closeTxVersionMask) != 0
// The version byte also encodes the closeTxVersion feature, so we
// extract it here and return it separately to the backup version.
version := SingleBackupVersion(encoded &^ closeTxVersionMask)
return version, closeTx
}
// IsTaproot returns if this is a backup of a taproot channel. This will also be
// true for simple taproot overlay channels when a version is added.
func (v SingleBackupVersion) IsTaproot() bool {
return v == SimpleTaprootVersion || v == TapscriptRootVersion
}
// HasTapscriptRoot returns true if the channel is using a top level tapscript
// root commitment.
func (v SingleBackupVersion) HasTapscriptRoot() bool {
return v == TapscriptRootVersion
}
// Single is a static description of an existing channel that can be used for
// the purposes of backing up. The fields in this struct allow a node to
// recover the settled funds within a channel in the case of partial or
// complete data loss. We provide the network address that we last used to
// connect to the peer as well, in case the node stops advertising the IP on
// the network for whatever reason.
//
// TODO(roasbeef): suffix version into struct?
type Single struct {
// Version is the version that should be observed when attempting to
// pack the single backup.
Version SingleBackupVersion
// IsInitiator is true if we were the initiator of the channel, and
// false otherwise. We'll need to know this information in order to
// properly re-derive the state hint information.
IsInitiator bool
// ChainHash is a hash which represents the blockchain that this
// channel will be opened within. This value is typically the genesis
// hash. In the case that the original chain went through a contentious
// hard-fork, then this value will be tweaked using the unique fork
// point on each branch.
ChainHash chainhash.Hash
// FundingOutpoint is the outpoint of the final funding transaction.
// This value uniquely and globally identities the channel within the
// target blockchain as specified by the chain hash parameter.
FundingOutpoint wire.OutPoint
// ShortChannelID encodes the exact location in the chain in which the
// channel was initially confirmed. This includes: the block height,
// transaction index, and the output within the target transaction.
// Channels that were not confirmed at the time of backup creation will
// have the funding TX broadcast height set as their block height in
// the ShortChannelID.
ShortChannelID lnwire.ShortChannelID
// RemoteNodePub is the identity public key of the remote node this
// channel has been established with.
RemoteNodePub *btcec.PublicKey
// Addresses is a list of IP address in which either we were able to
// reach the node over in the past, OR we received an incoming
// authenticated connection for the stored identity public key.
Addresses []net.Addr
// Capacity is the size of the original channel.
Capacity btcutil.Amount
// LocalChanCfg is our local channel configuration. It contains all the
// information we need to re-derive the keys we used within the
// channel. Most importantly, it allows to derive the base public
// that's used to deriving the key used within the non-delayed
// pay-to-self output on the commitment transaction for a node. With
// this information, we can re-derive the private key needed to sweep
// the funds on-chain.
//
// NOTE: Of the items in the ChannelConstraints, we only write the CSV
// delay.
LocalChanCfg channeldb.ChannelConfig
// RemoteChanCfg is the remote channel confirmation. We store this as
// well since we'll need some of their keys to re-derive things like
// the state hint obfuscator which will allow us to recognize the state
// their broadcast on chain.
//
// NOTE: Of the items in the ChannelConstraints, we only write the CSV
// delay.
RemoteChanCfg channeldb.ChannelConfig
// ShaChainRootDesc describes how to derive the private key that was
// used as the shachain root for this channel.
ShaChainRootDesc keychain.KeyDescriptor
// LeaseExpiry represents the absolute expiration as a height of the
// chain of a channel lease that is applied to every output that pays
// directly to the channel initiator in addition to the usual CSV
// requirement.
//
// NOTE: This field will only be present for the following versions:
//
// - ScriptEnforcedLeaseVersion
LeaseExpiry uint32
// CloseTxInputs contains data needed to produce a force close tx
// using for example the "chantools scbforceclose" command.
//
// The field is optional.
CloseTxInputs fn.Option[CloseTxInputs]
}
// CloseTxInputs contains data needed to produce a force close transaction
// using for example the "chantools scbforceclose" command.
type CloseTxInputs struct {
// CommitTx is the latest version of the commitment state, broadcast
// able by us, but not signed. It can be signed by for example the
// "chantools scbforceclose" command.
CommitTx *wire.MsgTx
// CommitSig is one half of the signature required to fully complete
// the script for the commitment transaction above. This is the
// signature signed by the remote party for our version of the
// commitment transactions.
CommitSig []byte
// CommitHeight is the update number that this ChannelDelta represents
// the total number of commitment updates to this point. This can be
// viewed as sort of a "commitment height" as this number is
// monotonically increasing.
//
// This field is filled only for taproot channels.
CommitHeight uint64
// TapscriptRoot is the root of the tapscript tree that will be used to
// create the funding output. This is an optional field that should
// only be set for overlay taproot channels (HasTapscriptRoot).
TapscriptRoot fn.Option[chainhash.Hash]
}
// NewSingle creates a new static channel backup based on an existing open
// channel. We also pass in the set of addresses that we used in the past to
// connect to the channel peer. If possible, we include the data needed to
// produce a force close transaction from the most recent state using externally
// provided private key.
func NewSingle(channel *channeldb.OpenChannel,
nodeAddrs []net.Addr) Single {
var shaChainRootDesc keychain.KeyDescriptor
// If the channel has a populated RevocationKeyLocator, then we can
// just store that instead of the public key.
if channel.RevocationKeyLocator.Family == keychain.KeyFamilyRevocationRoot {
shaChainRootDesc = keychain.KeyDescriptor{
KeyLocator: channel.RevocationKeyLocator,
}
} else {
// If the RevocationKeyLocator is not populated, then we'll need
// to obtain a public point for the shachain root and store that.
// This is the legacy scheme.
var b bytes.Buffer
_ = channel.RevocationProducer.Encode(&b) // Can't return an error.
// Once we have the root, we'll make a public key from it, such that
// the backups plaintext don't carry any private information. When
// we go to recover, we'll present this in order to derive the
// private key.
_, shaChainPoint := btcec.PrivKeyFromBytes(b.Bytes())
shaChainRootDesc = keychain.KeyDescriptor{
PubKey: shaChainPoint,
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamilyRevocationRoot,
},
}
}
// If a channel is unconfirmed, the block height of the ShortChannelID
// is zero. This will lead to problems when trying to restore that
// channel as the spend notifier would get a height hint of zero.
// To work around that problem, we add the channel broadcast height
// to the channel ID so we can use that as height hint on restore.
chanID := channel.ShortChanID()
if chanID.BlockHeight == 0 {
chanID.BlockHeight = channel.BroadcastHeight()
}
// If this is a zero-conf channel, we'll need to have separate logic
// depending on whether it's confirmed or not. This is because the
// ShortChanID is an alias.
if channel.IsZeroConf() {
// If the channel is confirmed, we'll use the confirmed SCID.
if channel.ZeroConfConfirmed() {
chanID = channel.ZeroConfRealScid()
} else {
// Else if the zero-conf channel is unconfirmed, we'll
// need to use the broadcast height and zero out the
// TxIndex and TxPosition fields. This is so
// openChannelShell works properly.
chanID.BlockHeight = channel.BroadcastHeight()
chanID.TxIndex = 0
chanID.TxPosition = 0
}
}
single := Single{
IsInitiator: channel.IsInitiator,
ChainHash: channel.ChainHash,
FundingOutpoint: channel.FundingOutpoint,
ShortChannelID: chanID,
RemoteNodePub: channel.IdentityPub,
Addresses: nodeAddrs,
Capacity: channel.Capacity,
LocalChanCfg: channel.LocalChanCfg,
RemoteChanCfg: channel.RemoteChanCfg,
ShaChainRootDesc: shaChainRootDesc,
}
switch {
case channel.ChanType.IsTaproot():
if channel.ChanType.HasTapscriptRoot() {
single.Version = TapscriptRootVersion
} else {
single.Version = SimpleTaprootVersion
}
case channel.ChanType.HasLeaseExpiration():
single.Version = ScriptEnforcedLeaseVersion
single.LeaseExpiry = channel.ThawHeight
case channel.ChanType.ZeroHtlcTxFee():
single.Version = AnchorsZeroFeeHtlcTxCommitVersion
case channel.ChanType.HasAnchors():
single.Version = AnchorsCommitVersion
case channel.ChanType.IsTweakless():
single.Version = TweaklessCommitVersion
default:
single.Version = DefaultSingleVersion
}
// Include unsigned force-close transaction for the most recent channel
// state as well as the data needed to produce the signature, given the
// private key is provided separately.
single.CloseTxInputs = buildCloseTxInputs(channel)
return single
}
// errEmptyTapscriptRoot is returned by Serialize if field TapscriptRoot is
// empty, when it should be filled according to the channel version.
var errEmptyTapscriptRoot = errors.New("field TapscriptRoot is not filled")
// Serialize attempts to write out the serialized version of the target
// StaticChannelBackup into the passed io.Writer.
func (s *Single) Serialize(w io.Writer) error {
// Check to ensure that we'll only attempt to serialize a version that
// we're aware of.
switch s.Version {
case DefaultSingleVersion:
case TweaklessCommitVersion:
case AnchorsCommitVersion:
case AnchorsZeroFeeHtlcTxCommitVersion:
case ScriptEnforcedLeaseVersion:
case SimpleTaprootVersion:
case TapscriptRootVersion:
default:
return fmt.Errorf("unable to serialize w/ unknown "+
"version: %v", s.Version)
}
// If the sha chain root has specified a public key (which is
// optional), then we'll encode it now.
var shaChainPub [33]byte
if s.ShaChainRootDesc.PubKey != nil {
copy(
shaChainPub[:],
s.ShaChainRootDesc.PubKey.SerializeCompressed(),
)
}
// First we gather the SCB as is into a temporary buffer so we can
// determine the total length. Before we write out the serialized SCB,
// we write the length which allows us to skip any Singles that we
// don't know of when decoding a multi.
var singleBytes bytes.Buffer
if err := lnwire.WriteElements(
&singleBytes,
s.IsInitiator,
s.ChainHash[:],
s.FundingOutpoint,
s.ShortChannelID,
s.RemoteNodePub,
s.Addresses,
s.Capacity,
s.LocalChanCfg.CsvDelay,
// We only need to write out the KeyLocator portion of the
// local channel config.
uint32(s.LocalChanCfg.MultiSigKey.Family),
s.LocalChanCfg.MultiSigKey.Index,
uint32(s.LocalChanCfg.RevocationBasePoint.Family),
s.LocalChanCfg.RevocationBasePoint.Index,
uint32(s.LocalChanCfg.PaymentBasePoint.Family),
s.LocalChanCfg.PaymentBasePoint.Index,
uint32(s.LocalChanCfg.DelayBasePoint.Family),
s.LocalChanCfg.DelayBasePoint.Index,
uint32(s.LocalChanCfg.HtlcBasePoint.Family),
s.LocalChanCfg.HtlcBasePoint.Index,
s.RemoteChanCfg.CsvDelay,
// We only need to write out the raw pubkey for the remote
// channel config.
s.RemoteChanCfg.MultiSigKey.PubKey,
s.RemoteChanCfg.RevocationBasePoint.PubKey,
s.RemoteChanCfg.PaymentBasePoint.PubKey,
s.RemoteChanCfg.DelayBasePoint.PubKey,
s.RemoteChanCfg.HtlcBasePoint.PubKey,
shaChainPub[:],
uint32(s.ShaChainRootDesc.KeyLocator.Family),
s.ShaChainRootDesc.KeyLocator.Index,
); err != nil {
return err
}
if s.Version == ScriptEnforcedLeaseVersion {
err := lnwire.WriteElements(&singleBytes, s.LeaseExpiry)
if err != nil {
return err
}
}
// Encode version enum and hasCloseTx flag to version byte.
version := s.Version.Encode(s.CloseTxInputs.IsSome())
// Serialize CloseTxInputs if it is provided. Fill err if it fails.
err := fn.MapOptionZ(s.CloseTxInputs, func(inputs CloseTxInputs) error {
err := inputs.CommitTx.Serialize(&singleBytes)
if err != nil {
return err
}
err = lnwire.WriteElements(
&singleBytes,
uint16(len(inputs.CommitSig)), inputs.CommitSig,
)
if err != nil {
return err
}
if !s.Version.IsTaproot() {
return nil
}
// Write fields needed for taproot channels.
err = lnwire.WriteElements(
&singleBytes, inputs.CommitHeight,
)
if err != nil {
return err
}
if s.Version.HasTapscriptRoot() {
opt := inputs.TapscriptRoot
var tapscriptRoot chainhash.Hash
tapscriptRoot, err = opt.UnwrapOrErr(
errEmptyTapscriptRoot,
)
if err != nil {
return err
}
err = lnwire.WriteElements(
&singleBytes, tapscriptRoot[:],
)
if err != nil {
return err
}
}
return nil
})
if err != nil {
return fmt.Errorf("failed to encode CloseTxInputs: %w", err)
}
// TODO(yy): remove the type assertion when we finished refactoring db
// into using write buffer.
buf, ok := w.(*bytes.Buffer)
if !ok {
return fmt.Errorf("expect io.Writer to be *bytes.Buffer")
}
return lnwire.WriteElements(
buf,
version,
uint16(len(singleBytes.Bytes())),
singleBytes.Bytes(),
)
}
// PackToWriter is similar to the Serialize method, but takes the operation a
// step further by encryption the raw bytes of the static channel back up. For
// encryption we use the chacah20poly1305 AEAD cipher with a 24 byte nonce and
// 32-byte key size. We use a 24-byte nonce, as we can't ensure that we have a
// global counter to use as a sequence number for nonces, and want to ensure
// that we're able to decrypt these blobs without any additional context. We
// derive the key that we use for encryption via a SHA2 operation of the with
// the golden keychain.KeyFamilyBaseEncryption base encryption key. We then
// take the serialized resulting shared secret point, and hash it using sha256
// to obtain the key that we'll use for encryption. When using the AEAD, we
// pass the nonce as associated data such that we'll be able to package the two
// together for storage. Before writing out the encrypted payload, we prepend
// the nonce to the final blob.
func (s *Single) PackToWriter(w io.Writer, keyRing keychain.KeyRing) error {
// First, we'll serialize the SCB (StaticChannelBackup) into a
// temporary buffer so we can store it in a temporary place before we
// go to encrypt the entire thing.
var rawBytes bytes.Buffer
if err := s.Serialize(&rawBytes); err != nil {
return err
}
// Finally, we'll encrypt the raw serialized SCB (using the nonce as
// associated data), and write out the ciphertext prepend with the
// nonce that we used to the passed io.Reader.
e, err := lnencrypt.KeyRingEncrypter(keyRing)
if err != nil {
return fmt.Errorf("unable to generate encrypt key %w", err)
}
return e.EncryptPayloadToWriter(rawBytes.Bytes(), w)
}
// readLocalKeyDesc reads a KeyDescriptor encoded within an unpacked Single.
// For local KeyDescs, we only write out the KeyLocator information as we can
// re-derive the pubkey from it.
func readLocalKeyDesc(r io.Reader) (keychain.KeyDescriptor, error) {
var keyDesc keychain.KeyDescriptor
var keyFam uint32
if err := lnwire.ReadElements(r, &keyFam); err != nil {
return keyDesc, err
}
keyDesc.Family = keychain.KeyFamily(keyFam)
if err := lnwire.ReadElements(r, &keyDesc.Index); err != nil {
return keyDesc, err
}
return keyDesc, nil
}
// readRemoteKeyDesc reads a remote KeyDescriptor encoded within an unpacked
// Single. For remote KeyDescs, we write out only the PubKey since we don't
// actually have the KeyLocator data.
func readRemoteKeyDesc(r io.Reader) (keychain.KeyDescriptor, error) {
var (
keyDesc keychain.KeyDescriptor
pub [33]byte
)
_, err := io.ReadFull(r, pub[:])
if err != nil {
return keychain.KeyDescriptor{}, err
}
keyDesc.PubKey, err = btcec.ParsePubKey(pub[:])
if err != nil {
return keychain.KeyDescriptor{}, err
}
return keyDesc, nil
}
// Deserialize attempts to read the raw plaintext serialized SCB from the
// passed io.Reader. If the method is successful, then the target
// StaticChannelBackup will be fully populated.
func (s *Single) Deserialize(r io.Reader) error {
// First, we'll need to read the version of this single-back up so we
// can know how to unpack each of the SCB.
var version byte
err := lnwire.ReadElements(r, &version)
if err != nil {
return err
}
// Decode version byte to version enum and hasCloseTx flag.
var hasCloseTx bool
s.Version, hasCloseTx = DecodeVersion(version)
switch s.Version {
case DefaultSingleVersion:
case TweaklessCommitVersion:
case AnchorsCommitVersion:
case AnchorsZeroFeeHtlcTxCommitVersion:
case ScriptEnforcedLeaseVersion:
case SimpleTaprootVersion:
case TapscriptRootVersion:
default:
return fmt.Errorf("unable to de-serialize w/ unknown "+
"version: %v", s.Version)
}
var length uint16
if err := lnwire.ReadElements(r, &length); err != nil {
return err
}
err = lnwire.ReadElements(
r, &s.IsInitiator, s.ChainHash[:], &s.FundingOutpoint,
&s.ShortChannelID, &s.RemoteNodePub, &s.Addresses, &s.Capacity,
)
if err != nil {
return err
}
err = lnwire.ReadElements(r, &s.LocalChanCfg.CsvDelay)
if err != nil {
return err
}
s.LocalChanCfg.MultiSigKey, err = readLocalKeyDesc(r)
if err != nil {
return err
}
s.LocalChanCfg.RevocationBasePoint, err = readLocalKeyDesc(r)
if err != nil {
return err
}
s.LocalChanCfg.PaymentBasePoint, err = readLocalKeyDesc(r)
if err != nil {
return err
}
s.LocalChanCfg.DelayBasePoint, err = readLocalKeyDesc(r)
if err != nil {
return err
}
s.LocalChanCfg.HtlcBasePoint, err = readLocalKeyDesc(r)
if err != nil {
return err
}
err = lnwire.ReadElements(r, &s.RemoteChanCfg.CsvDelay)
if err != nil {
return err
}
s.RemoteChanCfg.MultiSigKey, err = readRemoteKeyDesc(r)
if err != nil {
return err
}
s.RemoteChanCfg.RevocationBasePoint, err = readRemoteKeyDesc(r)
if err != nil {
return err
}
s.RemoteChanCfg.PaymentBasePoint, err = readRemoteKeyDesc(r)
if err != nil {
return err
}
s.RemoteChanCfg.DelayBasePoint, err = readRemoteKeyDesc(r)
if err != nil {
return err
}
s.RemoteChanCfg.HtlcBasePoint, err = readRemoteKeyDesc(r)
if err != nil {
return err
}
// Finally, we'll parse out the ShaChainRootDesc.
var (
shaChainPub [33]byte
zeroPub [33]byte
)
if err := lnwire.ReadElements(r, shaChainPub[:]); err != nil {
return err
}
// Since this field is optional, we'll check to see if the pubkey has
// been specified or not.
if !bytes.Equal(shaChainPub[:], zeroPub[:]) {
s.ShaChainRootDesc.PubKey, err = btcec.ParsePubKey(
shaChainPub[:],
)
if err != nil {
return err
}
}
var shaKeyFam uint32
if err := lnwire.ReadElements(r, &shaKeyFam); err != nil {
return err
}
s.ShaChainRootDesc.KeyLocator.Family = keychain.KeyFamily(shaKeyFam)
err = lnwire.ReadElements(r, &s.ShaChainRootDesc.KeyLocator.Index)
if err != nil {
return err
}
if s.Version == ScriptEnforcedLeaseVersion {
if err := lnwire.ReadElement(r, &s.LeaseExpiry); err != nil {
return err
}
}
if !hasCloseTx {
return nil
}
// Deserialize CloseTxInputs if it is present in serialized data.
commitTx := &wire.MsgTx{}
if err := commitTx.Deserialize(r); err != nil {
return err
}
var commitSigLen uint16
if err := lnwire.ReadElement(r, &commitSigLen); err != nil {
return err
}
commitSig := make([]byte, commitSigLen)
if err := lnwire.ReadElement(r, commitSig); err != nil {
return err
}
var commitHeight uint64
if s.Version.IsTaproot() {
err := lnwire.ReadElement(r, &commitHeight)
if err != nil {
return err
}
}
tapscriptRootOpt := fn.None[chainhash.Hash]()
if s.Version.HasTapscriptRoot() {
var tapscriptRoot chainhash.Hash
err := lnwire.ReadElement(r, tapscriptRoot[:])
if err != nil {
return err
}
tapscriptRootOpt = fn.Some(tapscriptRoot)
}
s.CloseTxInputs = fn.Some(CloseTxInputs{
CommitTx: commitTx,
CommitSig: commitSig,
CommitHeight: commitHeight,
TapscriptRoot: tapscriptRootOpt,
})
return nil
}
// UnpackFromReader is similar to Deserialize method, but it expects the passed
// io.Reader to contain an encrypt SCB. Refer to the SerializeAndEncrypt method
// for details w.r.t the encryption scheme used. If we're unable to decrypt the
// payload for whatever reason (wrong key, wrong nonce, etc), then this method
// will return an error.
func (s *Single) UnpackFromReader(r io.Reader, keyRing keychain.KeyRing) error {
e, err := lnencrypt.KeyRingEncrypter(keyRing)
if err != nil {
return fmt.Errorf("unable to generate key decrypter %w", err)
}
plaintext, err := e.DecryptPayloadFromReader(r)
if err != nil {
return err
}
// Finally, we'll pack the bytes into a reader to we can deserialize
// the plaintext bytes of the SCB.
backupReader := bytes.NewReader(plaintext)
return s.Deserialize(backupReader)
}
// PackStaticChanBackups accepts a set of existing open channels, and a
// keychain.KeyRing, and returns a map of outpoints to the serialized+encrypted
// static channel backups. The passed keyRing should be backed by the users
// root HD seed in order to ensure full determinism.
func PackStaticChanBackups(backups []Single,
keyRing keychain.KeyRing) (map[wire.OutPoint][]byte, error) {
packedBackups := make(map[wire.OutPoint][]byte)
for _, chanBackup := range backups {
chanPoint := chanBackup.FundingOutpoint
var b bytes.Buffer
err := chanBackup.PackToWriter(&b, keyRing)
if err != nil {
return nil, fmt.Errorf("unable to pack chan backup "+
"for %v: %v", chanPoint, err)
}
packedBackups[chanPoint] = b.Bytes()
}
return packedBackups, nil
}
// PackedSingles represents a series of fully packed SCBs. This may be the
// combination of a series of individual SCBs in order to batch their
// unpacking.
type PackedSingles [][]byte
// Unpack attempts to decrypt the passed set of encrypted SCBs and deserialize
// each one into a new SCB struct. The passed keyRing should be backed by the
// same HD seed as was used to encrypt the set of backups in the first place.
// If we're unable to decrypt any of the back ups, then we'll return an error.
func (p PackedSingles) Unpack(keyRing keychain.KeyRing) ([]Single, error) {
backups := make([]Single, len(p))
for i, encryptedBackup := range p {
var backup Single
backupReader := bytes.NewReader(encryptedBackup)
err := backup.UnpackFromReader(backupReader, keyRing)
if err != nil {
return nil, err
}
backups[i] = backup
}
return backups, nil
}
// TODO(roasbeef): make codec package?
package chanfitness
import (
"fmt"
"time"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/clock"
)
type eventType int
const (
peerOnlineEvent eventType = iota
peerOfflineEvent
)
// String provides string representations of channel events.
func (e eventType) String() string {
switch e {
case peerOnlineEvent:
return "peer_online"
case peerOfflineEvent:
return "peer_offline"
}
return "unknown"
}
type event struct {
timestamp time.Time
eventType eventType
}
// peerLog tracks events for a peer and its channels. If we currently have no
// channels with the peer, it will simply track its current online state. If we
// do have channels open with the peer, it will track the peer's online and
// offline events so that we can calculate uptime for our channels. A single
// event log is used for these online and offline events, and uptime for a
// channel is calculated by examining a subsection of this log.
type peerLog struct {
// online stores whether the peer is currently online.
online bool
// onlineEvents is a log of timestamped events observed for the peer
// that we have committed to allocating memory to.
onlineEvents []*event
// stagedEvent represents an event that is pending addition to the
// events list. It has not yet been added because we rate limit the
// frequency that we store events at. We need to store this value
// in the log (rather than just ignore events) so that we can flush the
// aggregate outcome to our event log once the rate limiting period has
// ended.
//
// Take the following example:
// - Peer online event recorded
// - Peer offline event, not recorded due to rate limit
// - No more events, we incorrectly believe our peer to be online
// Instead of skipping events, we stage the most recent event during the
// rate limited period so that we know what happened (on aggregate)
// while we were rate limiting events.
//
// Note that we currently only store offline/online events so we can
// use this field to track our online state. With the addition of other
// event types, we need to only stage online/offline events, or split
// them out.
stagedEvent *event
// flapCount is the number of times this peer has been observed as
// going offline.
flapCount int
// lastFlap is the timestamp of the last flap we recorded for the peer.
// This value will be nil if we have never recorded a flap for the peer.
lastFlap *time.Time
// clock allows creation of deterministic unit tests.
clock clock.Clock
// channels contains a set of currently open channels. Channels will be
// added and removed from this map as they are opened and closed.
channels map[wire.OutPoint]*channelInfo
}
// newPeerLog creates a log for a peer, taking its historical flap count and
// last flap time as parameters. These values may be zero/nil if we have no
// record of historical flap count for the peer.
func newPeerLog(clock clock.Clock, flapCount int,
lastFlap *time.Time) *peerLog {
return &peerLog{
clock: clock,
flapCount: flapCount,
lastFlap: lastFlap,
channels: make(map[wire.OutPoint]*channelInfo),
}
}
// channelInfo contains information about a channel.
type channelInfo struct {
// openedAt tracks the first time this channel was seen. This is not
// necessarily the time that it confirmed on chain because channel
// events are not persisted at present.
openedAt time.Time
}
func newChannelInfo(openedAt time.Time) *channelInfo {
return &channelInfo{
openedAt: openedAt,
}
}
// onlineEvent records a peer online or offline event in the log and increments
// the peer's flap count.
func (p *peerLog) onlineEvent(online bool) {
eventTime := p.clock.Now()
// If we have a non-nil last flap time, potentially apply a cooldown
// factor to the peer's flap count before we rate limit it. This allows
// us to decrease the penalty for historical flaps over time, provided
// the peer has not flapped for a while.
if p.lastFlap != nil {
p.flapCount = cooldownFlapCount(
p.clock.Now(), p.flapCount, *p.lastFlap,
)
}
// Record flap count information and online state regardless of whether
// we have any channels open with this peer.
p.flapCount++
p.lastFlap = &eventTime
p.online = online
// If we have no channels currently open with the peer, we do not want
// to commit resources to tracking their online state beyond a simple
// online boolean, so we exit early.
if p.channelCount() == 0 {
return
}
p.addEvent(online, eventTime)
}
// addEvent records an online or offline event in our event log. and increments
// the peer's flap count.
func (p *peerLog) addEvent(online bool, time time.Time) {
eventType := peerOnlineEvent
if !online {
eventType = peerOfflineEvent
}
event := &event{
timestamp: time,
eventType: eventType,
}
// If we have no staged events, we can just stage this event and return.
if p.stagedEvent == nil {
p.stagedEvent = event
return
}
// We get the amount of time we require between events according to
// peer flap count.
aggregation := getRateLimit(p.flapCount)
nextRecordTime := p.stagedEvent.timestamp.Add(aggregation)
flushEvent := nextRecordTime.Before(event.timestamp)
// If enough time has passed since our last staged event, we add our
// event to our in-memory list.
if flushEvent {
p.onlineEvents = append(p.onlineEvents, p.stagedEvent)
}
// Finally, we replace our staged event with the new event we received.
p.stagedEvent = event
}
// addChannel adds a channel to our log. If we have not tracked any online
// events for our peer yet, we create one with our peer's current online state
// so that we know the state that the peer had at channel start, which is
// required to calculate uptime over the channel's lifetime.
func (p *peerLog) addChannel(channelPoint wire.OutPoint) error {
_, ok := p.channels[channelPoint]
if ok {
return fmt.Errorf("channel: %v already present", channelPoint)
}
openTime := p.clock.Now()
p.channels[channelPoint] = newChannelInfo(openTime)
// If we do not have any online events tracked for our peer (which is
// the case when we have no other channels open with the peer), we add
// an event with the peer's current online state so that we know that
// starting state for this peer when a channel was connected (which
// allows us to calculate uptime over the lifetime of the channel).
if len(p.onlineEvents) == 0 {
p.addEvent(p.online, openTime)
}
return nil
}
// removeChannel removes a channel from our log. If we have no more channels
// with the peer after removing this one, we clear our list of events.
func (p *peerLog) removeChannel(channelPoint wire.OutPoint) error {
_, ok := p.channels[channelPoint]
if !ok {
return fmt.Errorf("channel: %v not present", channelPoint)
}
delete(p.channels, channelPoint)
// If we have no more channels in our event log, we can discard all of
// our online events in memory, since we don't need them anymore.
// TODO(carla): this could be done on a per channel basis.
if p.channelCount() == 0 {
p.onlineEvents = nil
p.stagedEvent = nil
}
return nil
}
// channelCount returns the number of channels that we currently have
// with the peer.
func (p *peerLog) channelCount() int {
return len(p.channels)
}
// channelUptime looks up a channel and returns the amount of time that the
// channel has been monitored for and its uptime over this period.
func (p *peerLog) channelUptime(channelPoint wire.OutPoint) (time.Duration,
time.Duration, error) {
channel, ok := p.channels[channelPoint]
if !ok {
return 0, 0, ErrChannelNotFound
}
now := p.clock.Now()
uptime, err := p.uptime(channel.openedAt, now)
if err != nil {
return 0, 0, err
}
return now.Sub(channel.openedAt), uptime, nil
}
// getFlapCount returns the peer's flap count and the timestamp that we last
// recorded a flap.
func (p *peerLog) getFlapCount() (int, *time.Time) {
return p.flapCount, p.lastFlap
}
// listEvents returns all of the events that our event log has tracked,
// including events that are staged for addition to our set of events but have
// not yet been committed to (because we rate limit and store only the aggregate
// outcome over a period).
func (p *peerLog) listEvents() []*event {
if p.stagedEvent == nil {
return p.onlineEvents
}
return append(p.onlineEvents, p.stagedEvent)
}
// onlinePeriod represents a period of time over which a peer was online.
type onlinePeriod struct {
start, end time.Time
}
// getOnlinePeriods returns a list of all the periods that the event log has
// recorded the remote peer as being online. In the unexpected case where there
// are no events, the function returns early. Online periods are defined as a
// peer online event which is terminated by a peer offline event. If the event
// log ends on a peer online event, it appends a final period which is
// calculated until the present. This function expects the event log provided
// to be ordered by ascending timestamp, and can tolerate multiple consecutive
// online or offline events.
func (p *peerLog) getOnlinePeriods() []*onlinePeriod {
events := p.listEvents()
// Return early if there are no events, there are no online periods.
if len(events) == 0 {
return nil
}
var (
// lastEvent tracks the last event that we had that was of
// a different type to our own. It is used to determine the
// start time of our online periods when we experience an
// offline event, and to track our last recorded state.
lastEvent *event
onlinePeriods []*onlinePeriod
)
// Loop through all events to build a list of periods that the peer was
// online. Online periods are added when they are terminated with a peer
// offline event. If the log ends on an online event, the period between
// the online event and the present is not tracked. The type of the most
// recent event is tracked using the offline bool so that we can add a
// final online period if necessary.
for _, event := range events {
switch event.eventType {
case peerOnlineEvent:
// If our previous event is nil, we just set it and
// break out of the switch.
if lastEvent == nil {
lastEvent = event
break
}
// If our previous event was an offline event, we update
// it to this event. We do not do this if it was an
// online event because duplicate online events would
// progress our online timestamp forward (rather than
// keep it at our earliest online event timestamp).
if lastEvent.eventType == peerOfflineEvent {
lastEvent = event
}
case peerOfflineEvent:
// If our previous event is nil, we just set it and
// break out of the switch since we cannot record an
// online period from this single event.
if lastEvent == nil {
lastEvent = event
break
}
// If the last event we saw was an online event, we
// add an online period to our set and progress our
// previous event to this offline event. We do not
// do this if we have had duplicate offline events
// because we would be tracking the most recent offline
// event (rather than keep it at our earliest offline
// event timestamp).
if lastEvent.eventType == peerOnlineEvent {
onlinePeriods = append(
onlinePeriods, &onlinePeriod{
start: lastEvent.timestamp,
end: event.timestamp,
},
)
lastEvent = event
}
}
}
// If the last event was an peer offline event, we do not need to
// calculate a final online period and can return online periods as is.
if lastEvent.eventType == peerOfflineEvent {
return onlinePeriods
}
// The log ended on an online event, so we need to add a final online
// period which terminates at the present.
finalEvent := &onlinePeriod{
start: lastEvent.timestamp,
end: p.clock.Now(),
}
// Add the final online period to the set and return.
return append(onlinePeriods, finalEvent)
}
// uptime calculates the total uptime we have recorded for a peer over the
// inclusive range specified. An error is returned if the end of the range is
// before the start or a zero end time is returned.
func (p *peerLog) uptime(start, end time.Time) (time.Duration, error) {
// Error if we are provided with an invalid range to calculate uptime
// for.
if end.Before(start) {
return 0, fmt.Errorf("end time: %v before start time: %v",
end, start)
}
if end.IsZero() {
return 0, fmt.Errorf("zero end time")
}
var uptime time.Duration
for _, p := range p.getOnlinePeriods() {
// The online period ends before the range we're looking at, so
// we can skip over it.
if p.end.Before(start) {
continue
}
// The online period starts after the range we're looking at, so
// can stop calculating uptime.
if p.start.After(end) {
break
}
// If the online period starts before our range, shift the start
// time up so that we only calculate uptime from the start of
// our range.
if p.start.Before(start) {
p.start = start
}
// If the online period ends after our range, shift the end
// time forward so that we only calculate uptime until the end
// of the range.
if p.end.After(end) {
p.end = end
}
uptime += p.end.Sub(p.start)
}
return uptime, nil
}
// Package chanfitness monitors the behaviour of channels to provide insight
// into the health and performance of a channel. This is achieved by maintaining
// an event store which tracks events for each channel.
//
// Lifespan: the period that the channel has been known to the scoring system.
// Note that lifespan may not equal the channel's full lifetime because data is
// not currently persisted.
//
// Uptime: the total time within a given period that the channel's remote peer
// has been online.
package chanfitness
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channelnotifier"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/peernotifier"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/subscribe"
"github.com/lightningnetwork/lnd/ticker"
)
const (
// FlapCountFlushRate determines how often we write peer total flap
// count to disk.
FlapCountFlushRate = time.Hour
)
var (
// errShuttingDown is returned when the store cannot respond to a query
// because it has received the shutdown signal.
errShuttingDown = errors.New("channel event store shutting down")
// ErrChannelNotFound is returned when a query is made for a channel
// that the event store does not have knowledge of.
ErrChannelNotFound = errors.New("channel not found in event store")
// ErrPeerNotFound is returned when a query is made for a channel
// that has a peer that the event store is not currently tracking.
ErrPeerNotFound = errors.New("peer not found in event store")
)
// ChannelEventStore maintains a set of event logs for the node's channels to
// provide insight into the performance and health of channels.
type ChannelEventStore struct {
started atomic.Bool
stopped atomic.Bool
cfg *Config
// peers tracks all of our currently monitored peers and their channels.
peers map[route.Vertex]peerMonitor
// chanInfoRequests serves requests for information about our channel.
chanInfoRequests chan channelInfoRequest
// peerRequests serves requests for information about a peer.
peerRequests chan peerRequest
quit chan struct{}
wg sync.WaitGroup
}
// Config provides the event store with functions required to monitor channel
// activity. All elements of the config must be non-nil for the event store to
// operate.
type Config struct {
// SubscribeChannelEvents provides a subscription client which provides
// a stream of channel events.
SubscribeChannelEvents func() (subscribe.Subscription, error)
// SubscribePeerEvents provides a subscription client which provides a
// stream of peer online/offline events.
SubscribePeerEvents func() (subscribe.Subscription, error)
// GetOpenChannels provides a list of existing open channels which is
// used to populate the ChannelEventStore with a set of channels on
// startup.
GetOpenChannels func() ([]*channeldb.OpenChannel, error)
// Clock is the time source that the subsystem uses, provided here
// for ease of testing.
Clock clock.Clock
// WriteFlapCount records the flap count for a set of peers on disk.
WriteFlapCount func(map[route.Vertex]*channeldb.FlapCount) error
// ReadFlapCount gets the flap count for a peer on disk.
ReadFlapCount func(route.Vertex) (*channeldb.FlapCount, error)
// FlapCountTicker is a ticker which controls how often we flush our
// peer's flap count to disk.
FlapCountTicker ticker.Ticker
}
// peerFlapCountMap is the map used to map peers to flap counts, declared here
// to allow shorter function signatures.
type peerFlapCountMap map[route.Vertex]*channeldb.FlapCount
type channelInfoRequest struct {
peer route.Vertex
channelPoint wire.OutPoint
responseChan chan channelInfoResponse
}
type channelInfoResponse struct {
info *ChannelInfo
err error
}
type peerRequest struct {
peer route.Vertex
responseChan chan peerResponse
}
type peerResponse struct {
flapCount int
ts *time.Time
err error
}
// NewChannelEventStore initializes an event store with the config provided.
// Note that this function does not start the main event loop, Start() must be
// called.
func NewChannelEventStore(config *Config) *ChannelEventStore {
store := &ChannelEventStore{
cfg: config,
peers: make(map[route.Vertex]peerMonitor),
chanInfoRequests: make(chan channelInfoRequest),
peerRequests: make(chan peerRequest),
quit: make(chan struct{}),
}
return store
}
// Start adds all existing open channels to the event store and starts the main
// loop which records channel and peer events, and serves requests for
// information from the store. If this function fails, it cancels its existing
// subscriptions and returns an error.
func (c *ChannelEventStore) Start() error {
log.Info("ChannelEventStore starting...")
if c.started.Swap(true) {
return fmt.Errorf("ChannelEventStore started more than once")
}
// Create a subscription to channel events.
channelClient, err := c.cfg.SubscribeChannelEvents()
if err != nil {
return err
}
// Create a subscription to peer events. If an error occurs, cancel the
// existing subscription to channel events and return.
peerClient, err := c.cfg.SubscribePeerEvents()
if err != nil {
channelClient.Cancel()
return err
}
// cancel should be called to cancel all subscriptions if an error
// occurs.
cancel := func() {
channelClient.Cancel()
peerClient.Cancel()
}
// Add the existing set of channels to the event store. This is required
// because channel events will not be triggered for channels that exist
// at startup time.
channels, err := c.cfg.GetOpenChannels()
if err != nil {
cancel()
return err
}
log.Infof("Adding %v channels to event store", len(channels))
for _, ch := range channels {
peerKey, err := route.NewVertexFromBytes(
ch.IdentityPub.SerializeCompressed(),
)
if err != nil {
cancel()
return err
}
// Add existing channels to the channel store with an initial
// peer online or offline event.
c.addChannel(ch.FundingOutpoint, peerKey)
}
// Start a goroutine that consumes events from all subscriptions.
c.wg.Add(1)
go c.consume(&subscriptions{
channelUpdates: channelClient.Updates(),
peerUpdates: peerClient.Updates(),
cancel: cancel,
})
log.Debug("ChannelEventStore started")
return nil
}
// Stop terminates all goroutines started by the event store.
func (c *ChannelEventStore) Stop() error {
log.Info("ChannelEventStore shutting down...")
if c.stopped.Swap(true) {
return fmt.Errorf("ChannelEventStore stopped more than once")
}
// Stop the consume goroutine.
close(c.quit)
c.wg.Wait()
// Stop the ticker after the goroutine reading from it has exited, to
// avoid a race.
var err error
if c.cfg.FlapCountTicker == nil {
err = fmt.Errorf("ChannelEventStore FlapCountTicker not " +
"initialized")
} else {
c.cfg.FlapCountTicker.Stop()
}
log.Debugf("ChannelEventStore shutdown complete")
return err
}
// addChannel checks whether we are already tracking a channel's peer, creates a
// new peer log to track it if we are not yet monitoring it, and adds the
// channel.
func (c *ChannelEventStore) addChannel(channelPoint wire.OutPoint,
peer route.Vertex) {
peerMonitor, err := c.getPeerMonitor(peer)
if err != nil {
log.Error("could not create monitor: %v", err)
return
}
if err := peerMonitor.addChannel(channelPoint); err != nil {
log.Errorf("could not add channel: %v", err)
}
}
// getPeerMonitor tries to get an existing peer monitor from our in memory list,
// and falls back to creating a new monitor if it is not currently known.
func (c *ChannelEventStore) getPeerMonitor(peer route.Vertex) (peerMonitor,
error) {
peerMonitor, ok := c.peers[peer]
if ok {
return peerMonitor, nil
}
var (
flapCount int
lastFlap *time.Time
)
historicalFlap, err := c.cfg.ReadFlapCount(peer)
switch err {
// If we do not have any records for this peer we set a 0 flap count
// and timestamp.
case channeldb.ErrNoPeerBucket:
case nil:
flapCount = int(historicalFlap.Count)
lastFlap = &historicalFlap.LastFlap
// Return if we get an unexpected error.
default:
return nil, err
}
peerMonitor = newPeerLog(c.cfg.Clock, flapCount, lastFlap)
c.peers[peer] = peerMonitor
return peerMonitor, nil
}
// closeChannel records a closed time for a channel, and returns early is the
// channel is not known to the event store. We log warnings (rather than errors)
// when we cannot find a peer/channel because channels that we restore from a
// static channel backup do not have their open notified, so the event store
// never learns about them, but they are closed using the regular flow so we
// will try to remove them on close. At present, we cannot easily distinguish
// between these closes and others.
func (c *ChannelEventStore) closeChannel(channelPoint wire.OutPoint,
peer route.Vertex) {
peerMonitor, ok := c.peers[peer]
if !ok {
log.Warnf("peer not known to store: %v", peer)
return
}
if err := peerMonitor.removeChannel(channelPoint); err != nil {
log.Warnf("could not remove channel: %v", err)
}
}
// peerEvent creates a peer monitor for a peer if we do not currently have
// one, and adds an online event to it.
func (c *ChannelEventStore) peerEvent(peer route.Vertex, online bool) {
peerMonitor, err := c.getPeerMonitor(peer)
if err != nil {
log.Error("could not create monitor: %v", err)
return
}
peerMonitor.onlineEvent(online)
}
// subscriptions abstracts away from subscription clients to allow for mocking.
type subscriptions struct {
channelUpdates <-chan interface{}
peerUpdates <-chan interface{}
cancel func()
}
// consume is the event store's main loop. It consumes subscriptions to update
// the event store with channel and peer events, and serves requests for channel
// uptime and lifespan.
func (c *ChannelEventStore) consume(subscriptions *subscriptions) {
// Start our flap count ticker.
c.cfg.FlapCountTicker.Resume()
// On exit, we will cancel our subscriptions and write our most recent
// flap counts to disk. This ensures that we have consistent data in
// the case of a graceful shutdown. If we do not shutdown gracefully,
// our worst case is data from our last flap count tick (1H).
defer func() {
subscriptions.cancel()
if err := c.recordFlapCount(); err != nil {
log.Errorf("error recording flap on shutdown: %v", err)
}
c.wg.Done()
}()
// Consume events until the channel is closed.
for {
select {
// Process channel opened and closed events.
case e := <-subscriptions.channelUpdates:
switch event := e.(type) {
// A new channel has been opened, we must add the
// channel to the store and record a channel open event.
case channelnotifier.OpenChannelEvent:
compressed := event.Channel.IdentityPub.SerializeCompressed()
peerKey, err := route.NewVertexFromBytes(
compressed,
)
if err != nil {
log.Errorf("Could not get vertex "+
"from: %v", compressed)
}
c.addChannel(
event.Channel.FundingOutpoint, peerKey,
)
// A channel has been closed, we must remove the channel
// from the store and record a channel closed event.
case channelnotifier.ClosedChannelEvent:
compressed := event.CloseSummary.RemotePub.SerializeCompressed()
peerKey, err := route.NewVertexFromBytes(
compressed,
)
if err != nil {
log.Errorf("Could not get vertex "+
"from: %v", compressed)
continue
}
c.closeChannel(
event.CloseSummary.ChanPoint, peerKey,
)
}
// Process peer online and offline events.
case e := <-subscriptions.peerUpdates:
switch event := e.(type) {
// We have reestablished a connection with our peer,
// and should record an online event for any channels
// with that peer.
case peernotifier.PeerOnlineEvent:
c.peerEvent(event.PubKey, true)
// We have lost a connection with our peer, and should
// record an offline event for any channels with that
// peer.
case peernotifier.PeerOfflineEvent:
c.peerEvent(event.PubKey, false)
}
// Serve all requests for channel lifetime.
case req := <-c.chanInfoRequests:
var resp channelInfoResponse
resp.info, resp.err = c.getChanInfo(req)
req.responseChan <- resp
// Serve all requests for information about our peer.
case req := <-c.peerRequests:
var resp peerResponse
resp.flapCount, resp.ts, resp.err = c.flapCount(
req.peer,
)
req.responseChan <- resp
case <-c.cfg.FlapCountTicker.Ticks():
if err := c.recordFlapCount(); err != nil {
log.Errorf("could not record flap "+
"count: %v", err)
}
// Exit if the store receives the signal to shutdown.
case <-c.quit:
return
}
}
}
// ChannelInfo provides the set of information that the event store has recorded
// for a channel.
type ChannelInfo struct {
// Lifetime is the total amount of time we have monitored the channel
// for.
Lifetime time.Duration
// Uptime is the total amount of time that the channel peer has been
// observed as online during the monitored lifespan.
Uptime time.Duration
}
// GetChanInfo gets all the information we have on a channel in the event store.
func (c *ChannelEventStore) GetChanInfo(channelPoint wire.OutPoint,
peer route.Vertex) (*ChannelInfo, error) {
request := channelInfoRequest{
peer: peer,
channelPoint: channelPoint,
responseChan: make(chan channelInfoResponse),
}
// Send a request for the channel's information to the main event loop,
// or return early with an error if the store has already received a
// shutdown signal.
select {
case c.chanInfoRequests <- request:
case <-c.quit:
return nil, errShuttingDown
}
// Return the response we receive on the response channel or exit early
// if the store is instructed to exit.
select {
case resp := <-request.responseChan:
return resp.info, resp.err
case <-c.quit:
return nil, errShuttingDown
}
}
// getChanInfo collects channel information for a channel. It gets uptime over
// the full lifetime of the channel.
func (c *ChannelEventStore) getChanInfo(req channelInfoRequest) (*ChannelInfo,
error) {
peerMonitor, ok := c.peers[req.peer]
if !ok {
return nil, ErrPeerNotFound
}
lifetime, uptime, err := peerMonitor.channelUptime(req.channelPoint)
if err != nil {
return nil, err
}
return &ChannelInfo{
Lifetime: lifetime,
Uptime: uptime,
}, nil
}
// FlapCount returns the flap count we have for a peer and the timestamp of its
// last flap. If we do not have any flaps recorded for the peer, the last flap
// timestamp will be nil.
func (c *ChannelEventStore) FlapCount(peer route.Vertex) (int, *time.Time,
error) {
request := peerRequest{
peer: peer,
responseChan: make(chan peerResponse),
}
// Send a request for the peer's information to the main event loop,
// or return early with an error if the store has already received a
// shutdown signal.
select {
case c.peerRequests <- request:
case <-c.quit:
return 0, nil, errShuttingDown
}
// Return the response we receive on the response channel or exit early
// if the store is instructed to exit.
select {
case resp := <-request.responseChan:
return resp.flapCount, resp.ts, resp.err
case <-c.quit:
return 0, nil, errShuttingDown
}
}
// flapCount gets our peer flap count and last flap timestamp from our in memory
// record of a peer, falling back to on disk if we are not currently tracking
// the peer. If we have no flap count recorded for the peer, a nil last flap
// time will be returned.
func (c *ChannelEventStore) flapCount(peer route.Vertex) (int, *time.Time,
error) {
// First check whether we are tracking this peer in memory, because this
// record will have the most accurate flap count. We do not fail if we
// can't find the peer in memory, because we may have previously
// recorded its flap count on disk.
peerMonitor, ok := c.peers[peer]
if ok {
count, ts := peerMonitor.getFlapCount()
return count, ts, nil
}
// Try to get our flap count from the database. If this value is not
// recorded, we return a nil last flap time to indicate that we have no
// record of the peer's flap count.
flapCount, err := c.cfg.ReadFlapCount(peer)
switch err {
case channeldb.ErrNoPeerBucket:
return 0, nil, nil
case nil:
return int(flapCount.Count), &flapCount.LastFlap, nil
default:
return 0, nil, err
}
}
// recordFlapCount will record our flap count for each peer that we are
// currently tracking, skipping peers that have a 0 flap count.
func (c *ChannelEventStore) recordFlapCount() error {
updates := make(peerFlapCountMap)
for peer, monitor := range c.peers {
flapCount, lastFlap := monitor.getFlapCount()
if lastFlap == nil {
continue
}
updates[peer] = &channeldb.FlapCount{
Count: uint32(flapCount),
LastFlap: *lastFlap,
}
}
log.Debugf("recording flap count for: %v peers", len(updates))
return c.cfg.WriteFlapCount(updates)
}
package chanfitness
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// Subsystem defines the logging code for this subsystem.
const Subsystem = "CHFT"
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package chanfitness
import (
"math"
"time"
)
const (
// rateLimitScale is the number of events we allow per rate limited
// tier. Increasing this value makes our rate limiting more lenient,
// decreasing it makes us less lenient.
rateLimitScale = 200
// flapCountCooldownFactor is the factor by which we decrease a peer's
// flap count if they have not flapped for the cooldown period.
flapCountCooldownFactor = 0.95
// flapCountCooldownPeriod is the amount of time that we require a peer
// has not flapped for before we reduce their all time flap count using
// our cooldown factor.
flapCountCooldownPeriod = time.Hour * 8
)
// rateLimits is the set of rate limit tiers we apply to our peers based on
// their flap count. A peer can be placed in their tier by dividing their flap
// count by the rateLimitScale and returning the value at that index.
var rateLimits = []time.Duration{
time.Second,
time.Second * 5,
time.Second * 30,
time.Minute,
time.Minute * 30,
time.Hour,
}
// getRateLimit returns the value of the rate limited tier that we are on based
// on current flap count. If a peer's flap count exceeds the top tier, we just
// return our highest tier.
func getRateLimit(flapCount int) time.Duration {
// Figure out the tier we fall into based on our current flap count.
tier := flapCount / rateLimitScale
// If we have more events than our number of tiers, we just use the
// last tier
tierLen := len(rateLimits)
if tier >= tierLen {
tier = tierLen - 1
}
return rateLimits[tier]
}
// cooldownFlapCount takes a timestamped flap count, and returns its value
// scaled down by our cooldown factor if at least our cooldown period has
// elapsed since the peer last flapped. We do this because we store all-time
// flap count for peers, and want to allow downgrading of peers that have not
// flapped for a long time.
func cooldownFlapCount(now time.Time, flapCount int,
lastFlap time.Time) int {
// Calculate time since our last flap, and the number of times we need
// to apply our cooldown factor.
timeSinceFlap := now.Sub(lastFlap)
// If our cooldown period has not elapsed yet, we just return our flap
// count. We allow fractional cooldown periods once this period has
// elapsed, so we do not want to apply a fractional cooldown before the
// full cooldown period has elapsed.
if timeSinceFlap < flapCountCooldownPeriod {
return flapCount
}
// Get the factor by which we need to cooldown our flap count. If
// insufficient time has passed to cooldown our flap count. Use use a
// float so that we allow fractional cooldown periods.
cooldownPeriods := float64(timeSinceFlap) /
float64(flapCountCooldownPeriod)
effectiveFactor := math.Pow(flapCountCooldownFactor, cooldownPeriods)
return int(float64(flapCount) * effectiveFactor)
}
package lnd
import (
"fmt"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channelnotifier"
)
// channelNotifier is an implementation of the chanbackup.ChannelNotifier
// interface using the existing channelnotifier.ChannelNotifier struct. This
// implementation allows us to satisfy all the dependencies of the
// chanbackup.SubSwapper struct.
type channelNotifier struct {
// chanNotifier is the based channel notifier that we'll proxy requests
// from.
chanNotifier *channelnotifier.ChannelNotifier
// addrs is an implementation of the addrSource interface that allows
// us to get the latest set of addresses for a given node. We'll need
// this to be able to create an SCB for new channels.
addrs channeldb.AddrSource
}
// SubscribeChans requests a new channel subscription relative to the initial
// set of known channels. We use the knownChans as a synchronization point to
// ensure that the chanbackup.SubSwapper does not miss any channel open or
// close events in the period between when it's created, and when it requests
// the channel subscription.
//
// NOTE: This is part of the chanbackup.ChannelNotifier interface.
func (c *channelNotifier) SubscribeChans(startingChans map[wire.OutPoint]struct{}) (
*chanbackup.ChannelSubscription, error) {
ltndLog.Infof("Channel backup proxy channel notifier starting")
// TODO(roasbeef): read existing set of chans and diff
quit := make(chan struct{})
chanUpdates := make(chan chanbackup.ChannelEvent, 1)
// sendChanOpenUpdate is a closure that sends a ChannelEvent to the
// chanUpdates channel to inform subscribers about new pending or
// confirmed channels.
sendChanOpenUpdate := func(newOrPendingChan *channeldb.OpenChannel) {
_, nodeAddrs, err := c.addrs.AddrsForNode(
newOrPendingChan.IdentityPub,
)
if err != nil {
pub := newOrPendingChan.IdentityPub
ltndLog.Errorf("unable to fetch addrs for %x: %v",
pub.SerializeCompressed(), err)
}
chanEvent := chanbackup.ChannelEvent{
NewChans: []chanbackup.ChannelWithAddrs{
{
OpenChannel: newOrPendingChan,
Addrs: nodeAddrs,
},
},
}
select {
case chanUpdates <- chanEvent:
case <-quit:
return
}
}
// In order to adhere to the interface, we'll proxy the events from the
// channel notifier to the sub-swapper in a format it understands.
go func() {
// First, we'll subscribe to the primary channel notifier so we can
// obtain events for new opened/closed channels.
chanSubscription, err := c.chanNotifier.SubscribeChannelEvents()
if err != nil {
panic(fmt.Sprintf("unable to subscribe to chans: %v",
err))
}
defer chanSubscription.Cancel()
for {
select {
// A new event has been sent by the chanNotifier, we'll
// filter out the events we actually care about and
// send them to the sub-swapper.
case e := <-chanSubscription.Updates():
// TODO(roasbeef): batch dispatch ntnfs
switch event := e.(type) {
// A new channel has been opened and is still
// pending. We can still create a backup, even
// if the final channel ID is not yet available.
case channelnotifier.PendingOpenChannelEvent:
pendingChan := event.PendingChannel
sendChanOpenUpdate(pendingChan)
// A new channel has been confirmed, we'll
// obtain the node address, then send to the
// sub-swapper.
case channelnotifier.OpenChannelEvent:
sendChanOpenUpdate(event.Channel)
// An existing channel has been closed, we'll
// send only the chanPoint of the closed
// channel to the sub-swapper.
case channelnotifier.ClosedChannelEvent:
chanPoint := event.CloseSummary.ChanPoint
closeType := event.CloseSummary.CloseType
// Because we see the contract as closed
// once our local force close TX
// confirms, the channel arbitrator
// already fires on this event. But
// because our funds can be in limbo for
// up to 2 weeks worst case we don't
// want to remove the crucial info we
// need for sweeping that time locked
// output before we've actually done so.
if closeType == channeldb.LocalForceClose {
ltndLog.Debugf("Channel %v "+
"was force closed by "+
"us, not removing "+
"from channel backup "+
"until fully resolved",
chanPoint)
continue
}
chanEvent := chanbackup.ChannelEvent{
ClosedChans: []wire.OutPoint{
chanPoint,
},
}
select {
case chanUpdates <- chanEvent:
case <-quit:
return
}
// A channel was fully resolved on chain. This
// should only really interest us if it was a
// locally force closed channel where we didn't
// remove the channel already when the close
// event was fired.
case channelnotifier.FullyResolvedChannelEvent:
chanEvent := chanbackup.ChannelEvent{
ClosedChans: []wire.OutPoint{
*event.ChannelPoint,
},
}
select {
case chanUpdates <- chanEvent:
case <-quit:
return
}
}
// The cancel method has been called, signalling us to
// exit
case <-quit:
return
}
}
}()
return &chanbackup.ChannelSubscription{
ChanUpdates: chanUpdates,
Cancel: func() {
close(quit)
},
}, nil
}
// A compile-time constraint to ensure channelNotifier implements
// chanbackup.ChannelNotifier.
var _ chanbackup.ChannelNotifier = (*channelNotifier)(nil)
package channeldb
import (
"errors"
"net"
"github.com/btcsuite/btcd/btcec/v2"
)
// AddrSource is an interface that allow us to get the addresses for a target
// node. It may combine the results of multiple address sources.
type AddrSource interface {
// AddrsForNode returns all known addresses for the target node public
// key. The returned boolean must indicate if the given node is unknown
// to the backing source.
AddrsForNode(nodePub *btcec.PublicKey) (bool, []net.Addr, error)
}
// multiAddrSource is an implementation of AddrSource which gathers all the
// known addresses for a given node from multiple backends and de-duplicates the
// results.
type multiAddrSource struct {
sources []AddrSource
}
// NewMultiAddrSource constructs a new AddrSource which will query all the
// provided sources for a node's addresses and will then de-duplicate the
// results.
func NewMultiAddrSource(sources ...AddrSource) AddrSource {
return &multiAddrSource{
sources: sources,
}
}
// AddrsForNode returns all known addresses for the target node public key. It
// queries all the address sources provided and de-duplicates the results. The
// returned boolean is false only if none of the backing sources know of the
// node.
//
// NOTE: this implements the AddrSource interface.
func (c *multiAddrSource) AddrsForNode(nodePub *btcec.PublicKey) (bool,
[]net.Addr, error) {
if len(c.sources) == 0 {
return false, nil, errors.New("no address sources")
}
// The multiple address sources will likely contain duplicate addresses,
// so we use a map here to de-dup them.
dedupedAddrs := make(map[string]net.Addr)
// known will be set to true if any backing source is aware of the node.
var known bool
// Iterate over all the address sources and query each one for the
// addresses it has for the node in question.
for _, src := range c.sources {
isKnown, addrs, err := src.AddrsForNode(nodePub)
if err != nil {
return false, nil, err
}
if isKnown {
known = true
}
for _, addr := range addrs {
dedupedAddrs[addr.String()] = addr
}
}
// Convert the map into a list we can return.
addrs := make([]net.Addr, 0, len(dedupedAddrs))
for _, addr := range dedupedAddrs {
addrs = append(addrs, addr)
}
return known, addrs, nil
}
// A compile-time check to ensure that multiAddrSource implements the AddrSource
// interface.
var _ AddrSource = (*multiAddrSource)(nil)
package channeldb
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"strconv"
"strings"
"sync"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/lightningnetwork/lnd/fn/v2"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/shachain"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// AbsoluteThawHeightThreshold is the threshold at which a thaw height
// begins to be interpreted as an absolute block height, rather than a
// relative one.
AbsoluteThawHeightThreshold uint32 = 500000
// HTLCBlindingPointTLV is the tlv type used for storing blinding
// points with HTLCs.
HTLCBlindingPointTLV tlv.Type = 0
)
var (
// closedChannelBucket stores summarization information concerning
// previously open, but now closed channels.
closedChannelBucket = []byte("closed-chan-bucket")
// openChannelBucket stores all the currently open channels. This bucket
// has a second, nested bucket which is keyed by a node's ID. Within
// that node ID bucket, all attributes required to track, update, and
// close a channel are stored.
//
// openChan -> nodeID -> chanPoint
//
// TODO(roasbeef): flesh out comment
openChannelBucket = []byte("open-chan-bucket")
// outpointBucket stores all of our channel outpoints and a tlv
// stream containing channel data.
//
// outpoint -> tlv stream.
//
outpointBucket = []byte("outpoint-bucket")
// chanIDBucket stores all of the 32-byte channel ID's we know about.
// These could be derived from outpointBucket, but it is more
// convenient to have these in their own bucket.
//
// chanID -> tlv stream.
//
chanIDBucket = []byte("chan-id-bucket")
// historicalChannelBucket stores all channels that have seen their
// commitment tx confirm. All information from their previous open state
// is retained.
historicalChannelBucket = []byte("historical-chan-bucket")
// chanInfoKey can be accessed within the bucket for a channel
// (identified by its chanPoint). This key stores all the static
// information for a channel which is decided at the end of the
// funding flow.
chanInfoKey = []byte("chan-info-key")
// localUpfrontShutdownKey can be accessed within the bucket for a channel
// (identified by its chanPoint). This key stores an optional upfront
// shutdown script for the local peer.
localUpfrontShutdownKey = []byte("local-upfront-shutdown-key")
// remoteUpfrontShutdownKey can be accessed within the bucket for a channel
// (identified by its chanPoint). This key stores an optional upfront
// shutdown script for the remote peer.
remoteUpfrontShutdownKey = []byte("remote-upfront-shutdown-key")
// chanCommitmentKey can be accessed within the sub-bucket for a
// particular channel. This key stores the up to date commitment state
// for a particular channel party. Appending a 0 to the end of this key
// indicates it's the commitment for the local party, and appending a 1
// to the end of this key indicates it's the commitment for the remote
// party.
chanCommitmentKey = []byte("chan-commitment-key")
// unsignedAckedUpdatesKey is an entry in the channel bucket that
// contains the remote updates that we have acked, but not yet signed
// for in one of our remote commits.
unsignedAckedUpdatesKey = []byte("unsigned-acked-updates-key")
// remoteUnsignedLocalUpdatesKey is an entry in the channel bucket that
// contains the local updates that the remote party has acked, but
// has not yet signed for in one of their local commits.
remoteUnsignedLocalUpdatesKey = []byte("remote-unsigned-local-updates-key")
// revocationStateKey stores their current revocation hash, our
// preimage producer and their preimage store.
revocationStateKey = []byte("revocation-state-key")
// dataLossCommitPointKey stores the commitment point received from the
// remote peer during a channel sync in case we have lost channel state.
dataLossCommitPointKey = []byte("data-loss-commit-point-key")
// forceCloseTxKey points to a the unilateral closing tx that we
// broadcasted when moving the channel to state CommitBroadcasted.
forceCloseTxKey = []byte("closing-tx-key")
// coopCloseTxKey points to a the cooperative closing tx that we
// broadcasted when moving the channel to state CoopBroadcasted.
coopCloseTxKey = []byte("coop-closing-tx-key")
// shutdownInfoKey points to the serialised shutdown info that has been
// persisted for a channel. The existence of this info means that we
// have sent the Shutdown message before and so should re-initiate the
// shutdown on re-establish.
shutdownInfoKey = []byte("shutdown-info-key")
// commitDiffKey stores the current pending commitment state we've
// extended to the remote party (if any). Each time we propose a new
// state, we store the information necessary to reconstruct this state
// from the prior commitment. This allows us to resync the remote party
// to their expected state in the case of message loss.
//
// TODO(roasbeef): rename to commit chain?
commitDiffKey = []byte("commit-diff-key")
// frozenChanKey is the key where we store the information for any
// active "frozen" channels. This key is present only in the leaf
// bucket for a given channel.
frozenChanKey = []byte("frozen-chans")
// lastWasRevokeKey is a key that stores true when the last update we
// sent was a revocation and false when it was a commitment signature.
// This is nil in the case of new channels with no updates exchanged.
lastWasRevokeKey = []byte("last-was-revoke")
// finalHtlcsBucket contains the htlcs that have been resolved
// definitively. Within this bucket, there is a sub-bucket for each
// channel. In each channel bucket, the htlc indices are stored along
// with final outcome.
//
// final-htlcs -> chanID -> htlcIndex -> outcome
//
// 'outcome' is a byte value that encodes:
//
// | true false
// ------+------------------
// bit 0 | settled failed
// bit 1 | offchain onchain
//
// This bucket is positioned at the root level, because its contents
// will be kept independent of the channel lifecycle. This is to avoid
// the situation where a channel force-closes autonomously and the user
// not being able to query for htlc outcomes anymore.
finalHtlcsBucket = []byte("final-htlcs")
)
var (
// ErrNoCommitmentsFound is returned when a channel has not set
// commitment states.
ErrNoCommitmentsFound = fmt.Errorf("no commitments found")
// ErrNoChanInfoFound is returned when a particular channel does not
// have any channels state.
ErrNoChanInfoFound = fmt.Errorf("no chan info found")
// ErrNoRevocationsFound is returned when revocation state for a
// particular channel cannot be found.
ErrNoRevocationsFound = fmt.Errorf("no revocations found")
// ErrNoPendingCommit is returned when there is not a pending
// commitment for a remote party. A new commitment is written to disk
// each time we write a new state in order to be properly fault
// tolerant.
ErrNoPendingCommit = fmt.Errorf("no pending commits found")
// ErrNoCommitPoint is returned when no data loss commit point is found
// in the database.
ErrNoCommitPoint = fmt.Errorf("no commit point found")
// ErrNoCloseTx is returned when no closing tx is found for a channel
// in the state CommitBroadcasted.
ErrNoCloseTx = fmt.Errorf("no closing tx found")
// ErrNoShutdownInfo is returned when no shutdown info has been
// persisted for a channel.
ErrNoShutdownInfo = errors.New("no shutdown info")
// ErrNoRestoredChannelMutation is returned when a caller attempts to
// mutate a channel that's been recovered.
ErrNoRestoredChannelMutation = fmt.Errorf("cannot mutate restored " +
"channel state")
// ErrChanBorked is returned when a caller attempts to mutate a borked
// channel.
ErrChanBorked = fmt.Errorf("cannot mutate borked channel")
// ErrMissingIndexEntry is returned when a caller attempts to close a
// channel and the outpoint is missing from the index.
ErrMissingIndexEntry = fmt.Errorf("missing outpoint from index")
// ErrOnionBlobLength is returned is an onion blob with incorrect
// length is read from disk.
ErrOnionBlobLength = errors.New("onion blob < 1366 bytes")
)
const (
// A tlv type definition used to serialize an outpoint's indexStatus
// for use in the outpoint index.
indexStatusType tlv.Type = 0
)
// openChannelTlvData houses the new data fields that are stored for each
// channel in a TLV stream within the root bucket. This is stored as a TLV
// stream appended to the existing hard-coded fields in the channel's root
// bucket. New fields being added to the channel state should be added here.
//
// NOTE: This struct is used for serialization purposes only and its fields
// should be accessed via the OpenChannel struct while in memory.
type openChannelTlvData struct {
// revokeKeyLoc is the key locator for the revocation key.
revokeKeyLoc tlv.RecordT[tlv.TlvType1, keyLocRecord]
// initialLocalBalance is the initial local balance of the channel.
initialLocalBalance tlv.RecordT[tlv.TlvType2, uint64]
// initialRemoteBalance is the initial remote balance of the channel.
initialRemoteBalance tlv.RecordT[tlv.TlvType3, uint64]
// realScid is the real short channel ID of the channel corresponding to
// the on-chain outpoint.
realScid tlv.RecordT[tlv.TlvType4, lnwire.ShortChannelID]
// memo is an optional text field that gives context to the user about
// the channel.
memo tlv.OptionalRecordT[tlv.TlvType5, []byte]
// tapscriptRoot is the optional Tapscript root the channel funding
// output commits to.
tapscriptRoot tlv.OptionalRecordT[tlv.TlvType6, [32]byte]
// customBlob is an optional TLV encoded blob of data representing
// custom channel funding information.
customBlob tlv.OptionalRecordT[tlv.TlvType7, tlv.Blob]
}
// encode serializes the openChannelTlvData to the given io.Writer.
func (c *openChannelTlvData) encode(w io.Writer) error {
tlvRecords := []tlv.Record{
c.revokeKeyLoc.Record(),
c.initialLocalBalance.Record(),
c.initialRemoteBalance.Record(),
c.realScid.Record(),
}
c.memo.WhenSome(func(memo tlv.RecordT[tlv.TlvType5, []byte]) {
tlvRecords = append(tlvRecords, memo.Record())
})
c.tapscriptRoot.WhenSome(
func(root tlv.RecordT[tlv.TlvType6, [32]byte]) {
tlvRecords = append(tlvRecords, root.Record())
},
)
c.customBlob.WhenSome(func(blob tlv.RecordT[tlv.TlvType7, tlv.Blob]) {
tlvRecords = append(tlvRecords, blob.Record())
})
// Create the tlv stream.
tlvStream, err := tlv.NewStream(tlvRecords...)
if err != nil {
return err
}
return tlvStream.Encode(w)
}
// decode deserializes the openChannelTlvData from the given io.Reader.
func (c *openChannelTlvData) decode(r io.Reader) error {
memo := c.memo.Zero()
tapscriptRoot := c.tapscriptRoot.Zero()
blob := c.customBlob.Zero()
// Create the tlv stream.
tlvStream, err := tlv.NewStream(
c.revokeKeyLoc.Record(),
c.initialLocalBalance.Record(),
c.initialRemoteBalance.Record(),
c.realScid.Record(),
memo.Record(),
tapscriptRoot.Record(),
blob.Record(),
)
if err != nil {
return err
}
tlvs, err := tlvStream.DecodeWithParsedTypes(r)
if err != nil {
return err
}
if _, ok := tlvs[memo.TlvType()]; ok {
c.memo = tlv.SomeRecordT(memo)
}
if _, ok := tlvs[tapscriptRoot.TlvType()]; ok {
c.tapscriptRoot = tlv.SomeRecordT(tapscriptRoot)
}
if _, ok := tlvs[c.customBlob.TlvType()]; ok {
c.customBlob = tlv.SomeRecordT(blob)
}
return nil
}
// indexStatus is an enum-like type that describes what state the
// outpoint is in. Currently only two possible values.
type indexStatus uint8
const (
// outpointOpen represents an outpoint that is open in the outpoint index.
outpointOpen indexStatus = 0
// outpointClosed represents an outpoint that is closed in the outpoint
// index.
outpointClosed indexStatus = 1
)
// ChannelType is an enum-like type that describes one of several possible
// channel types. Each open channel is associated with a particular type as the
// channel type may determine how higher level operations are conducted such as
// fee negotiation, channel closing, the format of HTLCs, etc. Structure-wise,
// a ChannelType is a bit field, with each bit denoting a modification from the
// base channel type of single funder.
type ChannelType uint64
const (
// NOTE: iota isn't used here for this enum needs to be stable
// long-term as it will be persisted to the database.
// SingleFunderBit represents a channel wherein one party solely funds
// the entire capacity of the channel.
SingleFunderBit ChannelType = 0
// DualFunderBit represents a channel wherein both parties contribute
// funds towards the total capacity of the channel. The channel may be
// funded symmetrically or asymmetrically.
DualFunderBit ChannelType = 1 << 0
// SingleFunderTweaklessBit is similar to the basic SingleFunder channel
// type, but it omits the tweak for one's key in the commitment
// transaction of the remote party.
SingleFunderTweaklessBit ChannelType = 1 << 1
// NoFundingTxBit denotes if we have the funding transaction locally on
// disk. This bit may be on if the funding transaction was crafted by a
// wallet external to the primary daemon.
NoFundingTxBit ChannelType = 1 << 2
// AnchorOutputsBit indicates that the channel makes use of anchor
// outputs to bump the commitment transaction's effective feerate. This
// channel type also uses a delayed to_remote output script.
AnchorOutputsBit ChannelType = 1 << 3
// FrozenBit indicates that the channel is a frozen channel, meaning
// that only the responder can decide to cooperatively close the
// channel.
FrozenBit ChannelType = 1 << 4
// ZeroHtlcTxFeeBit indicates that the channel should use zero-fee
// second-level HTLC transactions.
ZeroHtlcTxFeeBit ChannelType = 1 << 5
// LeaseExpirationBit indicates that the channel has been leased for a
// period of time, constraining every output that pays to the channel
// initiator with an additional CLTV of the lease maturity.
LeaseExpirationBit ChannelType = 1 << 6
// ZeroConfBit indicates that the channel is a zero-conf channel.
ZeroConfBit ChannelType = 1 << 7
// ScidAliasChanBit indicates that the channel has negotiated the
// scid-alias channel type.
ScidAliasChanBit ChannelType = 1 << 8
// ScidAliasFeatureBit indicates that the scid-alias feature bit was
// negotiated during the lifetime of this channel.
ScidAliasFeatureBit ChannelType = 1 << 9
// SimpleTaprootFeatureBit indicates that the simple-taproot-chans
// feature bit was negotiated during the lifetime of the channel.
SimpleTaprootFeatureBit ChannelType = 1 << 10
// TapscriptRootBit indicates that this is a MuSig2 channel with a top
// level tapscript commitment. This MUST be set along with the
// SimpleTaprootFeatureBit.
TapscriptRootBit ChannelType = 1 << 11
)
// IsSingleFunder returns true if the channel type if one of the known single
// funder variants.
func (c ChannelType) IsSingleFunder() bool {
return c&DualFunderBit == 0
}
// IsDualFunder returns true if the ChannelType has the DualFunderBit set.
func (c ChannelType) IsDualFunder() bool {
return c&DualFunderBit == DualFunderBit
}
// IsTweakless returns true if the target channel uses a commitment that
// doesn't tweak the key for the remote party.
func (c ChannelType) IsTweakless() bool {
return c&SingleFunderTweaklessBit == SingleFunderTweaklessBit
}
// HasFundingTx returns true if this channel type is one that has a funding
// transaction stored locally.
func (c ChannelType) HasFundingTx() bool {
return c&NoFundingTxBit == 0
}
// HasAnchors returns true if this channel type has anchor outputs on its
// commitment.
func (c ChannelType) HasAnchors() bool {
return c&AnchorOutputsBit == AnchorOutputsBit
}
// ZeroHtlcTxFee returns true if this channel type uses second-level HTLC
// transactions signed with zero-fee.
func (c ChannelType) ZeroHtlcTxFee() bool {
return c&ZeroHtlcTxFeeBit == ZeroHtlcTxFeeBit
}
// IsFrozen returns true if the channel is considered to be "frozen". A frozen
// channel means that only the responder can initiate a cooperative channel
// closure.
func (c ChannelType) IsFrozen() bool {
return c&FrozenBit == FrozenBit
}
// HasLeaseExpiration returns true if the channel originated from a lease.
func (c ChannelType) HasLeaseExpiration() bool {
return c&LeaseExpirationBit == LeaseExpirationBit
}
// HasZeroConf returns true if the channel is a zero-conf channel.
func (c ChannelType) HasZeroConf() bool {
return c&ZeroConfBit == ZeroConfBit
}
// HasScidAliasChan returns true if the scid-alias channel type was negotiated.
func (c ChannelType) HasScidAliasChan() bool {
return c&ScidAliasChanBit == ScidAliasChanBit
}
// HasScidAliasFeature returns true if the scid-alias feature bit was
// negotiated during the lifetime of this channel.
func (c ChannelType) HasScidAliasFeature() bool {
return c&ScidAliasFeatureBit == ScidAliasFeatureBit
}
// IsTaproot returns true if the channel is using taproot features.
func (c ChannelType) IsTaproot() bool {
return c&SimpleTaprootFeatureBit == SimpleTaprootFeatureBit
}
// HasTapscriptRoot returns true if the channel is using a top level tapscript
// root commitment.
func (c ChannelType) HasTapscriptRoot() bool {
return c&TapscriptRootBit == TapscriptRootBit
}
// ChannelStateBounds are the parameters from OpenChannel and AcceptChannel
// that are responsible for providing bounds on the state space of the abstract
// channel state. These values must be remembered for normal channel operation
// but they do not impact how we compute the commitment transactions themselves.
type ChannelStateBounds struct {
// ChanReserve is an absolute reservation on the channel for the
// owner of this set of constraints. This means that the current
// settled balance for this node CANNOT dip below the reservation
// amount. This acts as a defense against costless attacks when
// either side no longer has any skin in the game.
ChanReserve btcutil.Amount
// MaxPendingAmount is the maximum pending HTLC value that the
// owner of these constraints can offer the remote node at a
// particular time.
MaxPendingAmount lnwire.MilliSatoshi
// MinHTLC is the minimum HTLC value that the owner of these
// constraints can offer the remote node. If any HTLCs below this
// amount are offered, then the HTLC will be rejected. This, in
// tandem with the dust limit allows a node to regulate the
// smallest HTLC that it deems economically relevant.
MinHTLC lnwire.MilliSatoshi
// MaxAcceptedHtlcs is the maximum number of HTLCs that the owner of
// this set of constraints can offer the remote node. This allows each
// node to limit their over all exposure to HTLCs that may need to be
// acted upon in the case of a unilateral channel closure or a contract
// breach.
MaxAcceptedHtlcs uint16
}
// CommitmentParams are the parameters from OpenChannel and
// AcceptChannel that are required to render an abstract channel state to a
// concrete commitment transaction. These values are necessary to (re)compute
// the commitment transaction. We treat these differently than the state space
// bounds because their history needs to be stored in order to properly handle
// chain resolution.
type CommitmentParams struct {
// DustLimit is the threshold (in satoshis) below which any outputs
// should be trimmed. When an output is trimmed, it isn't materialized
// as an actual output, but is instead burned to miner's fees.
DustLimit btcutil.Amount
// CsvDelay is the relative time lock delay expressed in blocks. Any
// settled outputs that pay to the owner of this channel configuration
// MUST ensure that the delay branch uses this value as the relative
// time lock. Similarly, any HTLC's offered by this node should use
// this value as well.
CsvDelay uint16
}
// ChannelConfig is a struct that houses the various configuration opens for
// channels. Each side maintains an instance of this configuration file as it
// governs: how the funding and commitment transaction to be created, the
// nature of HTLC's allotted, the keys to be used for delivery, and relative
// time lock parameters.
type ChannelConfig struct {
// ChannelStateBounds is the set of constraints that must be
// upheld for the duration of the channel for the owner of this channel
// configuration. Constraints govern a number of flow control related
// parameters, also including the smallest HTLC that will be accepted
// by a participant.
ChannelStateBounds
// CommitmentParams is an embedding of the parameters
// required to render an abstract channel state into a concrete
// commitment transaction.
CommitmentParams
// MultiSigKey is the key to be used within the 2-of-2 output script
// for the owner of this channel config.
MultiSigKey keychain.KeyDescriptor
// RevocationBasePoint is the base public key to be used when deriving
// revocation keys for the remote node's commitment transaction. This
// will be combined along with a per commitment secret to derive a
// unique revocation key for each state.
RevocationBasePoint keychain.KeyDescriptor
// PaymentBasePoint is the base public key to be used when deriving
// the key used within the non-delayed pay-to-self output on the
// commitment transaction for a node. This will be combined with a
// tweak derived from the per-commitment point to ensure unique keys
// for each commitment transaction.
PaymentBasePoint keychain.KeyDescriptor
// DelayBasePoint is the base public key to be used when deriving the
// key used within the delayed pay-to-self output on the commitment
// transaction for a node. This will be combined with a tweak derived
// from the per-commitment point to ensure unique keys for each
// commitment transaction.
DelayBasePoint keychain.KeyDescriptor
// HtlcBasePoint is the base public key to be used when deriving the
// local HTLC key. The derived key (combined with the tweak derived
// from the per-commitment point) is used within the "to self" clause
// within any HTLC output scripts.
HtlcBasePoint keychain.KeyDescriptor
}
// commitTlvData stores all the optional data that may be stored as a TLV stream
// at the _end_ of the normal serialized commit on disk.
type commitTlvData struct {
// customBlob is a custom blob that may store extra data for custom
// channels.
customBlob tlv.OptionalRecordT[tlv.TlvType1, tlv.Blob]
}
// encode encodes the aux data into the passed io.Writer.
func (c *commitTlvData) encode(w io.Writer) error {
var tlvRecords []tlv.Record
c.customBlob.WhenSome(func(blob tlv.RecordT[tlv.TlvType1, tlv.Blob]) {
tlvRecords = append(tlvRecords, blob.Record())
})
// Create the tlv stream.
tlvStream, err := tlv.NewStream(tlvRecords...)
if err != nil {
return err
}
return tlvStream.Encode(w)
}
// decode attempts to decode the aux data from the passed io.Reader.
func (c *commitTlvData) decode(r io.Reader) error {
blob := c.customBlob.Zero()
tlvStream, err := tlv.NewStream(
blob.Record(),
)
if err != nil {
return err
}
tlvs, err := tlvStream.DecodeWithParsedTypes(r)
if err != nil {
return err
}
if _, ok := tlvs[c.customBlob.TlvType()]; ok {
c.customBlob = tlv.SomeRecordT(blob)
}
return nil
}
// ChannelCommitment is a snapshot of the commitment state at a particular
// point in the commitment chain. With each state transition, a snapshot of the
// current state along with all non-settled HTLCs are recorded. These snapshots
// detail the state of the _remote_ party's commitment at a particular state
// number. For ourselves (the local node) we ONLY store our most recent
// (unrevoked) state for safety purposes.
type ChannelCommitment struct {
// CommitHeight is the update number that this ChannelDelta represents
// the total number of commitment updates to this point. This can be
// viewed as sort of a "commitment height" as this number is
// monotonically increasing.
CommitHeight uint64
// LocalLogIndex is the cumulative log index index of the local node at
// this point in the commitment chain. This value will be incremented
// for each _update_ added to the local update log.
LocalLogIndex uint64
// LocalHtlcIndex is the current local running HTLC index. This value
// will be incremented for each outgoing HTLC the local node offers.
LocalHtlcIndex uint64
// RemoteLogIndex is the cumulative log index index of the remote node
// at this point in the commitment chain. This value will be
// incremented for each _update_ added to the remote update log.
RemoteLogIndex uint64
// RemoteHtlcIndex is the current remote running HTLC index. This value
// will be incremented for each outgoing HTLC the remote node offers.
RemoteHtlcIndex uint64
// LocalBalance is the current available settled balance within the
// channel directly spendable by us.
//
// NOTE: This is the balance *after* subtracting any commitment fee,
// AND anchor output values.
LocalBalance lnwire.MilliSatoshi
// RemoteBalance is the current available settled balance within the
// channel directly spendable by the remote node.
//
// NOTE: This is the balance *after* subtracting any commitment fee,
// AND anchor output values.
RemoteBalance lnwire.MilliSatoshi
// CommitFee is the amount calculated to be paid in fees for the
// current set of commitment transactions. The fee amount is persisted
// with the channel in order to allow the fee amount to be removed and
// recalculated with each channel state update, including updates that
// happen after a system restart.
CommitFee btcutil.Amount
// FeePerKw is the min satoshis/kilo-weight that should be paid within
// the commitment transaction for the entire duration of the channel's
// lifetime. This field may be updated during normal operation of the
// channel as on-chain conditions change.
//
// TODO(halseth): make this SatPerKWeight. Cannot be done atm because
// this will cause the import cycle lnwallet<->channeldb. Fee
// estimation stuff should be in its own package.
FeePerKw btcutil.Amount
// CommitTx is the latest version of the commitment state, broadcast
// able by us.
CommitTx *wire.MsgTx
// CustomBlob is an optional blob that can be used to store information
// specific to a custom channel type. This may track some custom
// specific state for this given commitment.
CustomBlob fn.Option[tlv.Blob]
// CommitSig is one half of the signature required to fully complete
// the script for the commitment transaction above. This is the
// signature signed by the remote party for our version of the
// commitment transactions.
CommitSig []byte
// Htlcs is the set of HTLC's that are pending at this particular
// commitment height.
Htlcs []HTLC
}
// amendTlvData updates the channel with the given auxiliary TLV data.
func (c *ChannelCommitment) amendTlvData(auxData commitTlvData) {
auxData.customBlob.WhenSomeV(func(blob tlv.Blob) {
c.CustomBlob = fn.Some(blob)
})
}
// extractTlvData creates a new commitTlvData from the given commitment.
func (c *ChannelCommitment) extractTlvData() commitTlvData {
var auxData commitTlvData
c.CustomBlob.WhenSome(func(blob tlv.Blob) {
auxData.customBlob = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType1](blob),
)
})
return auxData
}
// ChannelStatus is a bit vector used to indicate whether an OpenChannel is in
// the default usable state, or a state where it shouldn't be used.
type ChannelStatus uint64
var (
// ChanStatusDefault is the normal state of an open channel.
ChanStatusDefault ChannelStatus
// ChanStatusBorked indicates that the channel has entered an
// irreconcilable state, triggered by a state desynchronization or
// channel breach. Channels in this state should never be added to the
// htlc switch.
ChanStatusBorked ChannelStatus = 1
// ChanStatusCommitBroadcasted indicates that a commitment for this
// channel has been broadcasted.
ChanStatusCommitBroadcasted ChannelStatus = 1 << 1
// ChanStatusLocalDataLoss indicates that we have lost channel state
// for this channel, and broadcasting our latest commitment might be
// considered a breach.
//
// TODO(halseh): actually enforce that we are not force closing such a
// channel.
ChanStatusLocalDataLoss ChannelStatus = 1 << 2
// ChanStatusRestored is a status flag that signals that the channel
// has been restored, and doesn't have all the fields a typical channel
// will have.
ChanStatusRestored ChannelStatus = 1 << 3
// ChanStatusCoopBroadcasted indicates that a cooperative close for
// this channel has been broadcasted. Older cooperatively closed
// channels will only have this status set. Newer ones will also have
// close initiator information stored using the local/remote initiator
// status. This status is set in conjunction with the initiator status
// so that we do not need to check multiple channel statues for
// cooperative closes.
ChanStatusCoopBroadcasted ChannelStatus = 1 << 4
// ChanStatusLocalCloseInitiator indicates that we initiated closing
// the channel.
ChanStatusLocalCloseInitiator ChannelStatus = 1 << 5
// ChanStatusRemoteCloseInitiator indicates that the remote node
// initiated closing the channel.
ChanStatusRemoteCloseInitiator ChannelStatus = 1 << 6
)
// chanStatusStrings maps a ChannelStatus to a human friendly string that
// describes that status.
var chanStatusStrings = map[ChannelStatus]string{
ChanStatusDefault: "ChanStatusDefault",
ChanStatusBorked: "ChanStatusBorked",
ChanStatusCommitBroadcasted: "ChanStatusCommitBroadcasted",
ChanStatusLocalDataLoss: "ChanStatusLocalDataLoss",
ChanStatusRestored: "ChanStatusRestored",
ChanStatusCoopBroadcasted: "ChanStatusCoopBroadcasted",
ChanStatusLocalCloseInitiator: "ChanStatusLocalCloseInitiator",
ChanStatusRemoteCloseInitiator: "ChanStatusRemoteCloseInitiator",
}
// orderedChanStatusFlags is an in-order list of all that channel status flags.
var orderedChanStatusFlags = []ChannelStatus{
ChanStatusBorked,
ChanStatusCommitBroadcasted,
ChanStatusLocalDataLoss,
ChanStatusRestored,
ChanStatusCoopBroadcasted,
ChanStatusLocalCloseInitiator,
ChanStatusRemoteCloseInitiator,
}
// String returns a human-readable representation of the ChannelStatus.
func (c ChannelStatus) String() string {
// If no flags are set, then this is the default case.
if c == ChanStatusDefault {
return chanStatusStrings[ChanStatusDefault]
}
// Add individual bit flags.
statusStr := ""
for _, flag := range orderedChanStatusFlags {
if c&flag == flag {
statusStr += chanStatusStrings[flag] + "|"
c -= flag
}
}
// Remove anything to the right of the final bar, including it as well.
statusStr = strings.TrimRight(statusStr, "|")
// Add any remaining flags which aren't accounted for as hex.
if c != 0 {
statusStr += "|0x" + strconv.FormatUint(uint64(c), 16)
}
// If this was purely an unknown flag, then remove the extra bar at the
// start of the string.
statusStr = strings.TrimLeft(statusStr, "|")
return statusStr
}
// FinalHtlcByte defines a byte type that encodes information about the final
// htlc resolution.
type FinalHtlcByte byte
const (
// FinalHtlcSettledBit is the bit that encodes whether the htlc was
// settled or failed.
FinalHtlcSettledBit FinalHtlcByte = 1 << 0
// FinalHtlcOffchainBit is the bit that encodes whether the htlc was
// resolved offchain or onchain.
FinalHtlcOffchainBit FinalHtlcByte = 1 << 1
)
// OpenChannel encapsulates the persistent and dynamic state of an open channel
// with a remote node. An open channel supports several options for on-disk
// serialization depending on the exact context. Full (upon channel creation)
// state commitments, and partial (due to a commitment update) writes are
// supported. Each partial write due to a state update appends the new update
// to an on-disk log, which can then subsequently be queried in order to
// "time-travel" to a prior state.
type OpenChannel struct {
// ChanType denotes which type of channel this is.
ChanType ChannelType
// ChainHash is a hash which represents the blockchain that this
// channel will be opened within. This value is typically the genesis
// hash. In the case that the original chain went through a contentious
// hard-fork, then this value will be tweaked using the unique fork
// point on each branch.
ChainHash chainhash.Hash
// FundingOutpoint is the outpoint of the final funding transaction.
// This value uniquely and globally identifies the channel within the
// target blockchain as specified by the chain hash parameter.
FundingOutpoint wire.OutPoint
// ShortChannelID encodes the exact location in the chain in which the
// channel was initially confirmed. This includes: the block height,
// transaction index, and the output within the target transaction.
//
// If IsZeroConf(), then this will the "base" (very first) ALIAS scid
// and the confirmed SCID will be stored in ConfirmedScid.
ShortChannelID lnwire.ShortChannelID
// IsPending indicates whether a channel's funding transaction has been
// confirmed.
IsPending bool
// IsInitiator is a bool which indicates if we were the original
// initiator for the channel. This value may affect how higher levels
// negotiate fees, or close the channel.
IsInitiator bool
// chanStatus is the current status of this channel. If it is not in
// the state Default, it should not be used for forwarding payments.
chanStatus ChannelStatus
// FundingBroadcastHeight is the height in which the funding
// transaction was broadcast. This value can be used by higher level
// sub-systems to determine if a channel is stale and/or should have
// been confirmed before a certain height.
FundingBroadcastHeight uint32
// NumConfsRequired is the number of confirmations a channel's funding
// transaction must have received in order to be considered available
// for normal transactional use.
NumConfsRequired uint16
// ChannelFlags holds the flags that were sent as part of the
// open_channel message.
ChannelFlags lnwire.FundingFlag
// IdentityPub is the identity public key of the remote node this
// channel has been established with.
IdentityPub *btcec.PublicKey
// Capacity is the total capacity of this channel.
Capacity btcutil.Amount
// TotalMSatSent is the total number of milli-satoshis we've sent
// within this channel.
TotalMSatSent lnwire.MilliSatoshi
// TotalMSatReceived is the total number of milli-satoshis we've
// received within this channel.
TotalMSatReceived lnwire.MilliSatoshi
// InitialLocalBalance is the balance we have during the channel
// opening. When we are not the initiator, this value represents the
// push amount.
InitialLocalBalance lnwire.MilliSatoshi
// InitialRemoteBalance is the balance they have during the channel
// opening.
InitialRemoteBalance lnwire.MilliSatoshi
// LocalChanCfg is the channel configuration for the local node.
LocalChanCfg ChannelConfig
// RemoteChanCfg is the channel configuration for the remote node.
RemoteChanCfg ChannelConfig
// LocalCommitment is the current local commitment state for the local
// party. This is stored distinct from the state of the remote party
// as there are certain asymmetric parameters which affect the
// structure of each commitment.
LocalCommitment ChannelCommitment
// RemoteCommitment is the current remote commitment state for the
// remote party. This is stored distinct from the state of the local
// party as there are certain asymmetric parameters which affect the
// structure of each commitment.
RemoteCommitment ChannelCommitment
// RemoteCurrentRevocation is the current revocation for their
// commitment transaction. However, since this the derived public key,
// we don't yet have the private key so we aren't yet able to verify
// that it's actually in the hash chain.
RemoteCurrentRevocation *btcec.PublicKey
// RemoteNextRevocation is the revocation key to be used for the *next*
// commitment transaction we create for the local node. Within the
// specification, this value is referred to as the
// per-commitment-point.
RemoteNextRevocation *btcec.PublicKey
// RevocationProducer is used to generate the revocation in such a way
// that remote side might store it efficiently and have the ability to
// restore the revocation by index if needed. Current implementation of
// secret producer is shachain producer.
RevocationProducer shachain.Producer
// RevocationStore is used to efficiently store the revocations for
// previous channels states sent to us by remote side. Current
// implementation of secret store is shachain store.
RevocationStore shachain.Store
// Packager is used to create and update forwarding packages for this
// channel, which encodes all necessary information to recover from
// failures and reforward HTLCs that were not fully processed.
Packager FwdPackager
// FundingTxn is the transaction containing this channel's funding
// outpoint. Upon restarts, this txn will be rebroadcast if the channel
// is found to be pending.
//
// NOTE: This value will only be populated for single-funder channels
// for which we are the initiator, and that we also have the funding
// transaction for. One can check this by using the HasFundingTx()
// method on the ChanType field.
FundingTxn *wire.MsgTx
// LocalShutdownScript is set to a pre-set script if the channel was opened
// by the local node with option_upfront_shutdown_script set. If the option
// was not set, the field is empty.
LocalShutdownScript lnwire.DeliveryAddress
// RemoteShutdownScript is set to a pre-set script if the channel was opened
// by the remote node with option_upfront_shutdown_script set. If the option
// was not set, the field is empty.
RemoteShutdownScript lnwire.DeliveryAddress
// ThawHeight is the height when a frozen channel once again becomes a
// normal channel. If this is zero, then there're no restrictions on
// this channel. If the value is lower than 500,000, then it's
// interpreted as a relative height, or an absolute height otherwise.
ThawHeight uint32
// LastWasRevoke is a boolean that determines if the last update we sent
// was a revocation (true) or a commitment signature (false).
LastWasRevoke bool
// RevocationKeyLocator stores the KeyLocator information that we will
// need to derive the shachain root for this channel. This allows us to
// have private key isolation from lnd.
RevocationKeyLocator keychain.KeyLocator
// confirmedScid is the confirmed ShortChannelID for a zero-conf
// channel. If the channel is unconfirmed, then this will be the
// default ShortChannelID. This is only set for zero-conf channels.
confirmedScid lnwire.ShortChannelID
// Memo is any arbitrary information we wish to store locally about the
// channel that will be useful to our future selves.
Memo []byte
// TapscriptRoot is an optional tapscript root used to derive the MuSig2
// funding output.
TapscriptRoot fn.Option[chainhash.Hash]
// CustomBlob is an optional blob that can be used to store information
// specific to a custom channel type. This information is only created
// at channel funding time, and after wards is to be considered
// immutable.
CustomBlob fn.Option[tlv.Blob]
// TODO(roasbeef): eww
Db *ChannelStateDB
// TODO(roasbeef): just need to store local and remote HTLC's?
sync.RWMutex
}
// String returns a string representation of the channel.
func (c *OpenChannel) String() string {
indexStr := "height=%v, local_htlc_index=%v, local_log_index=%v, " +
"remote_htlc_index=%v, remote_log_index=%v"
commit := c.LocalCommitment
local := fmt.Sprintf(indexStr, commit.CommitHeight,
commit.LocalHtlcIndex, commit.LocalLogIndex,
commit.RemoteHtlcIndex, commit.RemoteLogIndex,
)
commit = c.RemoteCommitment
remote := fmt.Sprintf(indexStr, commit.CommitHeight,
commit.LocalHtlcIndex, commit.LocalLogIndex,
commit.RemoteHtlcIndex, commit.RemoteLogIndex,
)
return fmt.Sprintf("SCID=%v, status=%v, initiator=%v, pending=%v, "+
"local commitment has %s, remote commitment has %s",
c.ShortChannelID, c.chanStatus, c.IsInitiator, c.IsPending,
local, remote,
)
}
// Initiator returns the ChannelParty that originally opened this channel.
func (c *OpenChannel) Initiator() lntypes.ChannelParty {
c.RLock()
defer c.RUnlock()
if c.IsInitiator {
return lntypes.Local
}
return lntypes.Remote
}
// ShortChanID returns the current ShortChannelID of this channel.
func (c *OpenChannel) ShortChanID() lnwire.ShortChannelID {
c.RLock()
defer c.RUnlock()
return c.ShortChannelID
}
// ZeroConfRealScid returns the zero-conf channel's confirmed scid. This should
// only be called if IsZeroConf returns true.
func (c *OpenChannel) ZeroConfRealScid() lnwire.ShortChannelID {
c.RLock()
defer c.RUnlock()
return c.confirmedScid
}
// ZeroConfConfirmed returns whether the zero-conf channel has confirmed. This
// should only be called if IsZeroConf returns true.
func (c *OpenChannel) ZeroConfConfirmed() bool {
c.RLock()
defer c.RUnlock()
return c.confirmedScid != hop.Source
}
// IsZeroConf returns whether the option_zeroconf channel type was negotiated.
func (c *OpenChannel) IsZeroConf() bool {
c.RLock()
defer c.RUnlock()
return c.ChanType.HasZeroConf()
}
// IsOptionScidAlias returns whether the option_scid_alias channel type was
// negotiated.
func (c *OpenChannel) IsOptionScidAlias() bool {
c.RLock()
defer c.RUnlock()
return c.ChanType.HasScidAliasChan()
}
// NegotiatedAliasFeature returns whether the option-scid-alias feature bit was
// negotiated.
func (c *OpenChannel) NegotiatedAliasFeature() bool {
c.RLock()
defer c.RUnlock()
return c.ChanType.HasScidAliasFeature()
}
// ChanStatus returns the current ChannelStatus of this channel.
func (c *OpenChannel) ChanStatus() ChannelStatus {
c.RLock()
defer c.RUnlock()
return c.chanStatus
}
// ApplyChanStatus allows the caller to modify the internal channel state in a
// thead-safe manner.
func (c *OpenChannel) ApplyChanStatus(status ChannelStatus) error {
c.Lock()
defer c.Unlock()
return c.putChanStatus(status)
}
// ClearChanStatus allows the caller to clear a particular channel status from
// the primary channel status bit field. After this method returns, a call to
// HasChanStatus(status) should return false.
func (c *OpenChannel) ClearChanStatus(status ChannelStatus) error {
c.Lock()
defer c.Unlock()
return c.clearChanStatus(status)
}
// HasChanStatus returns true if the internal bitfield channel status of the
// target channel has the specified status bit set.
func (c *OpenChannel) HasChanStatus(status ChannelStatus) bool {
c.RLock()
defer c.RUnlock()
return c.hasChanStatus(status)
}
func (c *OpenChannel) hasChanStatus(status ChannelStatus) bool {
// Special case ChanStatusDefualt since it isn't actually flag, but a
// particular combination (or lack-there-of) of flags.
if status == ChanStatusDefault {
return c.chanStatus == ChanStatusDefault
}
return c.chanStatus&status == status
}
// BroadcastHeight returns the height at which the funding tx was broadcast.
func (c *OpenChannel) BroadcastHeight() uint32 {
c.RLock()
defer c.RUnlock()
return c.FundingBroadcastHeight
}
// SetBroadcastHeight sets the FundingBroadcastHeight.
func (c *OpenChannel) SetBroadcastHeight(height uint32) {
c.Lock()
defer c.Unlock()
c.FundingBroadcastHeight = height
}
// amendTlvData updates the channel with the given auxiliary TLV data.
func (c *OpenChannel) amendTlvData(auxData openChannelTlvData) {
c.RevocationKeyLocator = auxData.revokeKeyLoc.Val.KeyLocator
c.InitialLocalBalance = lnwire.MilliSatoshi(
auxData.initialLocalBalance.Val,
)
c.InitialRemoteBalance = lnwire.MilliSatoshi(
auxData.initialRemoteBalance.Val,
)
c.confirmedScid = auxData.realScid.Val
auxData.memo.WhenSomeV(func(memo []byte) {
c.Memo = memo
})
auxData.tapscriptRoot.WhenSomeV(func(h [32]byte) {
c.TapscriptRoot = fn.Some[chainhash.Hash](h)
})
auxData.customBlob.WhenSomeV(func(blob tlv.Blob) {
c.CustomBlob = fn.Some(blob)
})
}
// extractTlvData creates a new openChannelTlvData from the given channel.
func (c *OpenChannel) extractTlvData() openChannelTlvData {
auxData := openChannelTlvData{
revokeKeyLoc: tlv.NewRecordT[tlv.TlvType1](
keyLocRecord{c.RevocationKeyLocator},
),
initialLocalBalance: tlv.NewPrimitiveRecord[tlv.TlvType2](
uint64(c.InitialLocalBalance),
),
initialRemoteBalance: tlv.NewPrimitiveRecord[tlv.TlvType3](
uint64(c.InitialRemoteBalance),
),
realScid: tlv.NewRecordT[tlv.TlvType4](
c.confirmedScid,
),
}
if len(c.Memo) != 0 {
auxData.memo = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType5](c.Memo),
)
}
c.TapscriptRoot.WhenSome(func(h chainhash.Hash) {
auxData.tapscriptRoot = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType6, [32]byte](h),
)
})
c.CustomBlob.WhenSome(func(blob tlv.Blob) {
auxData.customBlob = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType7](blob),
)
})
return auxData
}
// Refresh updates the in-memory channel state using the latest state observed
// on disk.
func (c *OpenChannel) Refresh() error {
c.Lock()
defer c.Unlock()
err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error {
chanBucket, err := fetchChanBucket(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
if err != nil {
return err
}
// We'll re-populating the in-memory channel with the info
// fetched from disk.
if err := fetchChanInfo(chanBucket, c); err != nil {
return fmt.Errorf("unable to fetch chan info: %w", err)
}
// Also populate the channel's commitment states for both sides
// of the channel.
if err := fetchChanCommitments(chanBucket, c); err != nil {
return fmt.Errorf("unable to fetch chan commitments: "+
"%v", err)
}
// Also retrieve the current revocation state.
if err := fetchChanRevocationState(chanBucket, c); err != nil {
return fmt.Errorf("unable to fetch chan revocations: "+
"%v", err)
}
return nil
}, func() {})
if err != nil {
return err
}
return nil
}
// fetchChanBucket is a helper function that returns the bucket where a
// channel's data resides in given: the public key for the node, the outpoint,
// and the chainhash that the channel resides on.
func fetchChanBucket(tx kvdb.RTx, nodeKey *btcec.PublicKey,
outPoint *wire.OutPoint, chainHash chainhash.Hash) (kvdb.RBucket, error) {
// First fetch the top level bucket which stores all data related to
// current, active channels.
openChanBucket := tx.ReadBucket(openChannelBucket)
if openChanBucket == nil {
return nil, ErrNoChanDBExists
}
// TODO(roasbeef): CreateTopLevelBucket on the interface isn't like
// CreateIfNotExists, will return error
// Within this top level bucket, fetch the bucket dedicated to storing
// open channel data specific to the remote node.
nodePub := nodeKey.SerializeCompressed()
nodeChanBucket := openChanBucket.NestedReadBucket(nodePub)
if nodeChanBucket == nil {
return nil, ErrNoActiveChannels
}
// We'll then recurse down an additional layer in order to fetch the
// bucket for this particular chain.
chainBucket := nodeChanBucket.NestedReadBucket(chainHash[:])
if chainBucket == nil {
return nil, ErrNoActiveChannels
}
// With the bucket for the node and chain fetched, we can now go down
// another level, for this channel itself.
var chanPointBuf bytes.Buffer
if err := graphdb.WriteOutpoint(&chanPointBuf, outPoint); err != nil {
return nil, err
}
chanBucket := chainBucket.NestedReadBucket(chanPointBuf.Bytes())
if chanBucket == nil {
return nil, ErrChannelNotFound
}
return chanBucket, nil
}
// fetchChanBucketRw is a helper function that returns the bucket where a
// channel's data resides in given: the public key for the node, the outpoint,
// and the chainhash that the channel resides on. This differs from
// fetchChanBucket in that it returns a writeable bucket.
func fetchChanBucketRw(tx kvdb.RwTx, nodeKey *btcec.PublicKey,
outPoint *wire.OutPoint, chainHash chainhash.Hash) (kvdb.RwBucket,
error) {
// First fetch the top level bucket which stores all data related to
// current, active channels.
openChanBucket := tx.ReadWriteBucket(openChannelBucket)
if openChanBucket == nil {
return nil, ErrNoChanDBExists
}
// TODO(roasbeef): CreateTopLevelBucket on the interface isn't like
// CreateIfNotExists, will return error
// Within this top level bucket, fetch the bucket dedicated to storing
// open channel data specific to the remote node.
nodePub := nodeKey.SerializeCompressed()
nodeChanBucket := openChanBucket.NestedReadWriteBucket(nodePub)
if nodeChanBucket == nil {
return nil, ErrNoActiveChannels
}
// We'll then recurse down an additional layer in order to fetch the
// bucket for this particular chain.
chainBucket := nodeChanBucket.NestedReadWriteBucket(chainHash[:])
if chainBucket == nil {
return nil, ErrNoActiveChannels
}
// With the bucket for the node and chain fetched, we can now go down
// another level, for this channel itself.
var chanPointBuf bytes.Buffer
if err := graphdb.WriteOutpoint(&chanPointBuf, outPoint); err != nil {
return nil, err
}
chanBucket := chainBucket.NestedReadWriteBucket(chanPointBuf.Bytes())
if chanBucket == nil {
return nil, ErrChannelNotFound
}
return chanBucket, nil
}
func fetchFinalHtlcsBucketRw(tx kvdb.RwTx,
chanID lnwire.ShortChannelID) (kvdb.RwBucket, error) {
finalHtlcsBucket, err := tx.CreateTopLevelBucket(finalHtlcsBucket)
if err != nil {
return nil, err
}
var chanIDBytes [8]byte
byteOrder.PutUint64(chanIDBytes[:], chanID.ToUint64())
chanBucket, err := finalHtlcsBucket.CreateBucketIfNotExists(
chanIDBytes[:],
)
if err != nil {
return nil, err
}
return chanBucket, nil
}
// fullSync syncs the contents of an OpenChannel while re-using an existing
// database transaction.
func (c *OpenChannel) fullSync(tx kvdb.RwTx) error {
// Fetch the outpoint bucket and check if the outpoint already exists.
opBucket := tx.ReadWriteBucket(outpointBucket)
if opBucket == nil {
return ErrNoChanDBExists
}
cidBucket := tx.ReadWriteBucket(chanIDBucket)
if cidBucket == nil {
return ErrNoChanDBExists
}
var chanPointBuf bytes.Buffer
err := graphdb.WriteOutpoint(&chanPointBuf, &c.FundingOutpoint)
if err != nil {
return err
}
// Now, check if the outpoint exists in our index.
if opBucket.Get(chanPointBuf.Bytes()) != nil {
return ErrChanAlreadyExists
}
cid := lnwire.NewChanIDFromOutPoint(c.FundingOutpoint)
if cidBucket.Get(cid[:]) != nil {
return ErrChanAlreadyExists
}
status := uint8(outpointOpen)
// Write the status of this outpoint as the first entry in a tlv
// stream.
statusRecord := tlv.MakePrimitiveRecord(indexStatusType, &status)
opStream, err := tlv.NewStream(statusRecord)
if err != nil {
return err
}
var b bytes.Buffer
if err := opStream.Encode(&b); err != nil {
return err
}
// Add the outpoint to our outpoint index with the tlv stream.
if err := opBucket.Put(chanPointBuf.Bytes(), b.Bytes()); err != nil {
return err
}
if err := cidBucket.Put(cid[:], []byte{}); err != nil {
return err
}
// First fetch the top level bucket which stores all data related to
// current, active channels.
openChanBucket, err := tx.CreateTopLevelBucket(openChannelBucket)
if err != nil {
return err
}
// Within this top level bucket, fetch the bucket dedicated to storing
// open channel data specific to the remote node.
nodePub := c.IdentityPub.SerializeCompressed()
nodeChanBucket, err := openChanBucket.CreateBucketIfNotExists(nodePub)
if err != nil {
return err
}
// We'll then recurse down an additional layer in order to fetch the
// bucket for this particular chain.
chainBucket, err := nodeChanBucket.CreateBucketIfNotExists(c.ChainHash[:])
if err != nil {
return err
}
// With the bucket for the node fetched, we can now go down another
// level, creating the bucket for this channel itself.
chanBucket, err := chainBucket.CreateBucket(
chanPointBuf.Bytes(),
)
switch {
case err == kvdb.ErrBucketExists:
// If this channel already exists, then in order to avoid
// overriding it, we'll return an error back up to the caller.
return ErrChanAlreadyExists
case err != nil:
return err
}
return putOpenChannel(chanBucket, c)
}
// MarkAsOpen marks a channel as fully open given a locator that uniquely
// describes its location within the chain.
func (c *OpenChannel) MarkAsOpen(openLoc lnwire.ShortChannelID) error {
c.Lock()
defer c.Unlock()
if err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
chanBucket, err := fetchChanBucketRw(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
if err != nil {
return err
}
channel, err := fetchOpenChannel(chanBucket, &c.FundingOutpoint)
if err != nil {
return err
}
channel.IsPending = false
channel.ShortChannelID = openLoc
return putOpenChannel(chanBucket, channel)
}, func() {}); err != nil {
return err
}
c.IsPending = false
c.ShortChannelID = openLoc
c.Packager = NewChannelPackager(openLoc)
return nil
}
// MarkRealScid marks the zero-conf channel's confirmed ShortChannelID. This
// should only be done if IsZeroConf returns true.
func (c *OpenChannel) MarkRealScid(realScid lnwire.ShortChannelID) error {
c.Lock()
defer c.Unlock()
if err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
chanBucket, err := fetchChanBucketRw(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
if err != nil {
return err
}
channel, err := fetchOpenChannel(
chanBucket, &c.FundingOutpoint,
)
if err != nil {
return err
}
channel.confirmedScid = realScid
return putOpenChannel(chanBucket, channel)
}, func() {}); err != nil {
return err
}
c.confirmedScid = realScid
return nil
}
// MarkScidAliasNegotiated adds ScidAliasFeatureBit to ChanType in-memory and
// in the database.
func (c *OpenChannel) MarkScidAliasNegotiated() error {
c.Lock()
defer c.Unlock()
if err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
chanBucket, err := fetchChanBucketRw(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
if err != nil {
return err
}
channel, err := fetchOpenChannel(
chanBucket, &c.FundingOutpoint,
)
if err != nil {
return err
}
channel.ChanType |= ScidAliasFeatureBit
return putOpenChannel(chanBucket, channel)
}, func() {}); err != nil {
return err
}
c.ChanType |= ScidAliasFeatureBit
return nil
}
// MarkDataLoss marks sets the channel status to LocalDataLoss and stores the
// passed commitPoint for use to retrieve funds in case the remote force closes
// the channel.
func (c *OpenChannel) MarkDataLoss(commitPoint *btcec.PublicKey) error {
c.Lock()
defer c.Unlock()
var b bytes.Buffer
if err := WriteElement(&b, commitPoint); err != nil {
return err
}
putCommitPoint := func(chanBucket kvdb.RwBucket) error {
return chanBucket.Put(dataLossCommitPointKey, b.Bytes())
}
return c.putChanStatus(ChanStatusLocalDataLoss, putCommitPoint)
}
// DataLossCommitPoint retrieves the stored commit point set during
// MarkDataLoss. If not found ErrNoCommitPoint is returned.
func (c *OpenChannel) DataLossCommitPoint() (*btcec.PublicKey, error) {
var commitPoint *btcec.PublicKey
err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error {
chanBucket, err := fetchChanBucket(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
switch err {
case nil:
case ErrNoChanDBExists, ErrNoActiveChannels, ErrChannelNotFound:
return ErrNoCommitPoint
default:
return err
}
bs := chanBucket.Get(dataLossCommitPointKey)
if bs == nil {
return ErrNoCommitPoint
}
r := bytes.NewReader(bs)
if err := ReadElements(r, &commitPoint); err != nil {
return err
}
return nil
}, func() {
commitPoint = nil
})
if err != nil {
return nil, err
}
return commitPoint, nil
}
// MarkBorked marks the event when the channel as reached an irreconcilable
// state, such as a channel breach or state desynchronization. Borked channels
// should never be added to the switch.
func (c *OpenChannel) MarkBorked() error {
c.Lock()
defer c.Unlock()
return c.putChanStatus(ChanStatusBorked)
}
// SecondCommitmentPoint returns the second per-commitment-point for use in the
// channel_ready message.
func (c *OpenChannel) SecondCommitmentPoint() (*btcec.PublicKey, error) {
c.RLock()
defer c.RUnlock()
// Since we start at commitment height = 0, the second per commitment
// point is actually at the 1st index.
revocation, err := c.RevocationProducer.AtIndex(1)
if err != nil {
return nil, err
}
return input.ComputeCommitmentPoint(revocation[:]), nil
}
var (
// taprootRevRootKey is the key used to derive the revocation root for
// the taproot nonces. This is done via HMAC of the existing revocation
// root.
taprootRevRootKey = []byte("taproot-rev-root")
)
// DeriveMusig2Shachain derives a shachain producer for the taproot channel
// from normal shachain revocation root.
func DeriveMusig2Shachain(revRoot shachain.Producer) (shachain.Producer, error) { //nolint:ll
// In order to obtain the revocation root hash to create the taproot
// revocation, we'll encode the producer into a buffer, then use that
// to derive the shachain root needed.
var rootHashBuf bytes.Buffer
if err := revRoot.Encode(&rootHashBuf); err != nil {
return nil, fmt.Errorf("unable to encode producer: %w", err)
}
revRootHash := chainhash.HashH(rootHashBuf.Bytes())
// For taproot channel types, we'll also generate a distinct shachain
// root using the same seed information. We'll use this to generate
// verification nonces for the channel. We'll bind with this a simple
// hmac.
taprootRevHmac := hmac.New(sha256.New, taprootRevRootKey)
if _, err := taprootRevHmac.Write(revRootHash[:]); err != nil {
return nil, err
}
taprootRevRoot := taprootRevHmac.Sum(nil)
// Once we have the root, we can then generate our shachain producer
// and from that generate the per-commitment point.
return shachain.NewRevocationProducerFromBytes(
taprootRevRoot,
)
}
// NewMusigVerificationNonce generates the local or verification nonce for
// another musig2 session. In order to permit our implementation to not have to
// write any secret nonce state to disk, we'll use the _next_ shachain
// pre-image as our primary randomness source. When used to generate the nonce
// again to broadcast our commitment hte current height will be used.
func NewMusigVerificationNonce(pubKey *btcec.PublicKey, targetHeight uint64,
shaGen shachain.Producer) (*musig2.Nonces, error) {
// Now that we know what height we need, we'll grab the shachain
// pre-image at the target destination.
nextPreimage, err := shaGen.AtIndex(targetHeight)
if err != nil {
return nil, err
}
shaChainRand := musig2.WithCustomRand(bytes.NewBuffer(nextPreimage[:]))
pubKeyOpt := musig2.WithPublicKey(pubKey)
return musig2.GenNonces(pubKeyOpt, shaChainRand)
}
// ChanSyncMsg returns the ChannelReestablish message that should be sent upon
// reconnection with the remote peer that we're maintaining this channel with.
// The information contained within this message is necessary to re-sync our
// commitment chains in the case of a last or only partially processed message.
// When the remote party receives this message one of three things may happen:
//
// 1. We're fully synced and no messages need to be sent.
// 2. We didn't get the last CommitSig message they sent, so they'll re-send
// it.
// 3. We didn't get the last RevokeAndAck message they sent, so they'll
// re-send it.
//
// If this is a restored channel, having status ChanStatusRestored, then we'll
// modify our typical chan sync message to ensure they force close even if
// we're on the very first state.
func (c *OpenChannel) ChanSyncMsg() (*lnwire.ChannelReestablish, error) {
c.Lock()
defer c.Unlock()
// The remote commitment height that we'll send in the
// ChannelReestablish message is our current commitment height plus
// one. If the receiver thinks that our commitment height is actually
// *equal* to this value, then they'll re-send the last commitment that
// they sent but we never fully processed.
localHeight := c.LocalCommitment.CommitHeight
nextLocalCommitHeight := localHeight + 1
// The second value we'll send is the height of the remote commitment
// from our PoV. If the receiver thinks that their height is actually
// *one plus* this value, then they'll re-send their last revocation.
remoteChainTipHeight := c.RemoteCommitment.CommitHeight
// If this channel has undergone a commitment update, then in order to
// prove to the remote party our knowledge of their prior commitment
// state, we'll also send over the last commitment secret that the
// remote party sent.
var lastCommitSecret [32]byte
if remoteChainTipHeight != 0 {
remoteSecret, err := c.RevocationStore.LookUp(
remoteChainTipHeight - 1,
)
if err != nil {
return nil, err
}
lastCommitSecret = [32]byte(*remoteSecret)
}
// Additionally, we'll send over the current unrevoked commitment on
// our local commitment transaction.
currentCommitSecret, err := c.RevocationProducer.AtIndex(
localHeight,
)
if err != nil {
return nil, err
}
// If we've restored this channel, then we'll purposefully give them an
// invalid LocalUnrevokedCommitPoint so they'll force close the channel
// allowing us to sweep our funds.
if c.hasChanStatus(ChanStatusRestored) {
currentCommitSecret[0] ^= 1
// If this is a tweakless channel, then we'll purposefully send
// a next local height taht's invalid to trigger a force close
// on their end. We do this as tweakless channels don't require
// that the commitment point is valid, only that it's present.
if c.ChanType.IsTweakless() {
nextLocalCommitHeight = 0
}
}
// If this is a taproot channel, then we'll need to generate our next
// verification nonce to send to the remote party. They'll use this to
// sign the next update to our commitment transaction.
var nextTaprootNonce lnwire.OptMusig2NonceTLV
if c.ChanType.IsTaproot() {
taprootRevProducer, err := DeriveMusig2Shachain(
c.RevocationProducer,
)
if err != nil {
return nil, err
}
nextNonce, err := NewMusigVerificationNonce(
c.LocalChanCfg.MultiSigKey.PubKey,
nextLocalCommitHeight, taprootRevProducer,
)
if err != nil {
return nil, fmt.Errorf("unable to gen next "+
"nonce: %w", err)
}
nextTaprootNonce = lnwire.SomeMusig2Nonce(nextNonce.PubNonce)
}
return &lnwire.ChannelReestablish{
ChanID: lnwire.NewChanIDFromOutPoint(
c.FundingOutpoint,
),
NextLocalCommitHeight: nextLocalCommitHeight,
RemoteCommitTailHeight: remoteChainTipHeight,
LastRemoteCommitSecret: lastCommitSecret,
LocalUnrevokedCommitPoint: input.ComputeCommitmentPoint(
currentCommitSecret[:],
),
LocalNonce: nextTaprootNonce,
}, nil
}
// MarkShutdownSent serialises and persist the given ShutdownInfo for this
// channel. Persisting this info represents the fact that we have sent the
// Shutdown message to the remote side and hence that we should re-transmit the
// same Shutdown message on re-establish.
func (c *OpenChannel) MarkShutdownSent(info *ShutdownInfo) error {
c.Lock()
defer c.Unlock()
return c.storeShutdownInfo(info)
}
// storeShutdownInfo serialises the ShutdownInfo and persists it under the
// shutdownInfoKey.
func (c *OpenChannel) storeShutdownInfo(info *ShutdownInfo) error {
var b bytes.Buffer
err := info.encode(&b)
if err != nil {
return err
}
return kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
chanBucket, err := fetchChanBucketRw(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
if err != nil {
return err
}
return chanBucket.Put(shutdownInfoKey, b.Bytes())
}, func() {})
}
// ShutdownInfo decodes the shutdown info stored for this channel and returns
// the result. If no shutdown info has been persisted for this channel then the
// ErrNoShutdownInfo error is returned.
func (c *OpenChannel) ShutdownInfo() (fn.Option[ShutdownInfo], error) {
c.RLock()
defer c.RUnlock()
var shutdownInfo *ShutdownInfo
err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error {
chanBucket, err := fetchChanBucket(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
switch {
case err == nil:
case errors.Is(err, ErrNoChanDBExists),
errors.Is(err, ErrNoActiveChannels),
errors.Is(err, ErrChannelNotFound):
return ErrNoShutdownInfo
default:
return err
}
shutdownInfoBytes := chanBucket.Get(shutdownInfoKey)
if shutdownInfoBytes == nil {
return ErrNoShutdownInfo
}
shutdownInfo, err = decodeShutdownInfo(shutdownInfoBytes)
return err
}, func() {
shutdownInfo = nil
})
if err != nil {
return fn.None[ShutdownInfo](), err
}
return fn.Some[ShutdownInfo](*shutdownInfo), nil
}
// isBorked returns true if the channel has been marked as borked in the
// database. This requires an existing database transaction to already be
// active.
//
// NOTE: The primary mutex should already be held before this method is called.
func (c *OpenChannel) isBorked(chanBucket kvdb.RBucket) (bool, error) {
channel, err := fetchOpenChannel(chanBucket, &c.FundingOutpoint)
if err != nil {
return false, err
}
return channel.chanStatus != ChanStatusDefault, nil
}
// MarkCommitmentBroadcasted marks the channel as a commitment transaction has
// been broadcast, either our own or the remote, and we should watch the chain
// for it to confirm before taking any further action. It takes as argument the
// closing tx _we believe_ will appear in the chain. This is only used to
// republish this tx at startup to ensure propagation, and we should still
// handle the case where a different tx actually hits the chain.
func (c *OpenChannel) MarkCommitmentBroadcasted(closeTx *wire.MsgTx,
closer lntypes.ChannelParty) error {
return c.markBroadcasted(
ChanStatusCommitBroadcasted, forceCloseTxKey, closeTx,
closer,
)
}
// MarkCoopBroadcasted marks the channel to indicate that a cooperative close
// transaction has been broadcast, either our own or the remote, and that we
// should watch the chain for it to confirm before taking further action. It
// takes as argument a cooperative close tx that could appear on chain, and
// should be rebroadcast upon startup. This is only used to republish and
// ensure propagation, and we should still handle the case where a different tx
// actually hits the chain.
func (c *OpenChannel) MarkCoopBroadcasted(closeTx *wire.MsgTx,
closer lntypes.ChannelParty) error {
return c.markBroadcasted(
ChanStatusCoopBroadcasted, coopCloseTxKey, closeTx,
closer,
)
}
// markBroadcasted is a helper function which modifies the channel status of the
// receiving channel and inserts a close transaction under the requested key,
// which should specify either a coop or force close. It adds a status which
// indicates the party that initiated the channel close.
func (c *OpenChannel) markBroadcasted(status ChannelStatus, key []byte,
closeTx *wire.MsgTx, closer lntypes.ChannelParty) error {
c.Lock()
defer c.Unlock()
// If a closing tx is provided, we'll generate a closure to write the
// transaction in the appropriate bucket under the given key.
var putClosingTx func(kvdb.RwBucket) error
if closeTx != nil {
var b bytes.Buffer
if err := WriteElement(&b, closeTx); err != nil {
return err
}
putClosingTx = func(chanBucket kvdb.RwBucket) error {
return chanBucket.Put(key, b.Bytes())
}
}
// Add the initiator status to the status provided. These statuses are
// set in addition to the broadcast status so that we do not need to
// migrate the original logic which does not store initiator.
if closer.IsLocal() {
status |= ChanStatusLocalCloseInitiator
} else {
status |= ChanStatusRemoteCloseInitiator
}
return c.putChanStatus(status, putClosingTx)
}
// BroadcastedCommitment retrieves the stored unilateral closing tx set during
// MarkCommitmentBroadcasted. If not found ErrNoCloseTx is returned.
func (c *OpenChannel) BroadcastedCommitment() (*wire.MsgTx, error) {
return c.getClosingTx(forceCloseTxKey)
}
// BroadcastedCooperative retrieves the stored cooperative closing tx set during
// MarkCoopBroadcasted. If not found ErrNoCloseTx is returned.
func (c *OpenChannel) BroadcastedCooperative() (*wire.MsgTx, error) {
return c.getClosingTx(coopCloseTxKey)
}
// getClosingTx is a helper method which returns the stored closing transaction
// for key. The caller should use either the force or coop closing keys.
func (c *OpenChannel) getClosingTx(key []byte) (*wire.MsgTx, error) {
var closeTx *wire.MsgTx
err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error {
chanBucket, err := fetchChanBucket(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
switch err {
case nil:
case ErrNoChanDBExists, ErrNoActiveChannels, ErrChannelNotFound:
return ErrNoCloseTx
default:
return err
}
bs := chanBucket.Get(key)
if bs == nil {
return ErrNoCloseTx
}
r := bytes.NewReader(bs)
return ReadElement(r, &closeTx)
}, func() {
closeTx = nil
})
if err != nil {
return nil, err
}
return closeTx, nil
}
// putChanStatus appends the given status to the channel. fs is an optional
// list of closures that are given the chanBucket in order to atomically add
// extra information together with the new status.
func (c *OpenChannel) putChanStatus(status ChannelStatus,
fs ...func(kvdb.RwBucket) error) error {
if err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
chanBucket, err := fetchChanBucketRw(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
if err != nil {
return err
}
channel, err := fetchOpenChannel(chanBucket, &c.FundingOutpoint)
if err != nil {
return err
}
// Add this status to the existing bitvector found in the DB.
status = channel.chanStatus | status
channel.chanStatus = status
if err := putOpenChannel(chanBucket, channel); err != nil {
return err
}
for _, f := range fs {
// Skip execution of nil closures.
if f == nil {
continue
}
if err := f(chanBucket); err != nil {
return err
}
}
return nil
}, func() {}); err != nil {
return err
}
// Update the in-memory representation to keep it in sync with the DB.
c.chanStatus = status
return nil
}
func (c *OpenChannel) clearChanStatus(status ChannelStatus) error {
if err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
chanBucket, err := fetchChanBucketRw(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
if err != nil {
return err
}
channel, err := fetchOpenChannel(chanBucket, &c.FundingOutpoint)
if err != nil {
return err
}
// Unset this bit in the bitvector on disk.
status = channel.chanStatus & ^status
channel.chanStatus = status
return putOpenChannel(chanBucket, channel)
}, func() {}); err != nil {
return err
}
// Update the in-memory representation to keep it in sync with the DB.
c.chanStatus = status
return nil
}
// putOpenChannel serializes, and stores the current state of the channel in its
// entirety.
func putOpenChannel(chanBucket kvdb.RwBucket, channel *OpenChannel) error {
// First, we'll write out all the relatively static fields, that are
// decided upon initial channel creation.
if err := putChanInfo(chanBucket, channel); err != nil {
return fmt.Errorf("unable to store chan info: %w", err)
}
// With the static channel info written out, we'll now write out the
// current commitment state for both parties.
if err := putChanCommitments(chanBucket, channel); err != nil {
return fmt.Errorf("unable to store chan commitments: %w", err)
}
// Next, if this is a frozen channel, we'll add in the axillary
// information we need to store.
if channel.ChanType.IsFrozen() || channel.ChanType.HasLeaseExpiration() {
err := storeThawHeight(
chanBucket, channel.ThawHeight,
)
if err != nil {
return fmt.Errorf("unable to store thaw height: %w",
err)
}
}
// Finally, we'll write out the revocation state for both parties
// within a distinct key space.
if err := putChanRevocationState(chanBucket, channel); err != nil {
return fmt.Errorf("unable to store chan revocations: %w", err)
}
return nil
}
// fetchOpenChannel retrieves, and deserializes (including decrypting
// sensitive) the complete channel currently active with the passed nodeID.
func fetchOpenChannel(chanBucket kvdb.RBucket,
chanPoint *wire.OutPoint) (*OpenChannel, error) {
channel := &OpenChannel{
FundingOutpoint: *chanPoint,
}
// First, we'll read all the static information that changes less
// frequently from disk.
if err := fetchChanInfo(chanBucket, channel); err != nil {
return nil, fmt.Errorf("unable to fetch chan info: %w", err)
}
// With the static information read, we'll now read the current
// commitment state for both sides of the channel.
if err := fetchChanCommitments(chanBucket, channel); err != nil {
return nil, fmt.Errorf("unable to fetch chan commitments: %w",
err)
}
// Next, if this is a frozen channel, we'll add in the axillary
// information we need to store.
if channel.ChanType.IsFrozen() || channel.ChanType.HasLeaseExpiration() {
thawHeight, err := fetchThawHeight(chanBucket)
if err != nil {
return nil, fmt.Errorf("unable to store thaw "+
"height: %v", err)
}
channel.ThawHeight = thawHeight
}
// Finally, we'll retrieve the current revocation state so we can
// properly
if err := fetchChanRevocationState(chanBucket, channel); err != nil {
return nil, fmt.Errorf("unable to fetch chan revocations: %w",
err)
}
channel.Packager = NewChannelPackager(channel.ShortChannelID)
return channel, nil
}
// SyncPending writes the contents of the channel to the database while it's in
// the pending (waiting for funding confirmation) state. The IsPending flag
// will be set to true. When the channel's funding transaction is confirmed,
// the channel should be marked as "open" and the IsPending flag set to false.
// Note that this function also creates a LinkNode relationship between this
// newly created channel and a new LinkNode instance. This allows listing all
// channels in the database globally, or according to the LinkNode they were
// created with.
//
// TODO(roasbeef): addr param should eventually be an lnwire.NetAddress type
// that includes service bits.
func (c *OpenChannel) SyncPending(addr net.Addr, pendingHeight uint32) error {
c.Lock()
defer c.Unlock()
c.FundingBroadcastHeight = pendingHeight
return kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
return syncNewChannel(tx, c, []net.Addr{addr})
}, func() {})
}
// syncNewChannel will write the passed channel to disk, and also create a
// LinkNode (if needed) for the channel peer.
func syncNewChannel(tx kvdb.RwTx, c *OpenChannel, addrs []net.Addr) error {
// First, sync all the persistent channel state to disk.
if err := c.fullSync(tx); err != nil {
return err
}
nodeInfoBucket, err := tx.CreateTopLevelBucket(nodeInfoBucket)
if err != nil {
return err
}
// If a LinkNode for this identity public key already exists,
// then we can exit early.
nodePub := c.IdentityPub.SerializeCompressed()
if nodeInfoBucket.Get(nodePub) != nil {
return nil
}
// Next, we need to establish a (possibly) new LinkNode relationship
// for this channel. The LinkNode metadata contains reachability,
// up-time, and service bits related information.
linkNode := NewLinkNode(
&LinkNodeDB{backend: c.Db.backend},
wire.MainNet, c.IdentityPub, addrs...,
)
// TODO(roasbeef): do away with link node all together?
return putLinkNode(nodeInfoBucket, linkNode)
}
// UpdateCommitment updates the local commitment state. It locks in the pending
// local updates that were received by us from the remote party. The commitment
// state completely describes the balance state at this point in the commitment
// chain. In addition to that, it persists all the remote log updates that we
// have acked, but not signed a remote commitment for yet. These need to be
// persisted to be able to produce a valid commit signature if a restart would
// occur. This method its to be called when we revoke our prior commitment
// state.
//
// A map is returned of all the htlc resolutions that were locked in this
// commitment. Keys correspond to htlc indices and values indicate whether the
// htlc was settled or failed.
func (c *OpenChannel) UpdateCommitment(newCommitment *ChannelCommitment,
unsignedAckedUpdates []LogUpdate) (map[uint64]bool, error) {
c.Lock()
defer c.Unlock()
// If this is a restored channel, then we want to avoid mutating the
// state as all, as it's impossible to do so in a protocol compliant
// manner.
if c.hasChanStatus(ChanStatusRestored) {
return nil, ErrNoRestoredChannelMutation
}
var finalHtlcs = make(map[uint64]bool)
err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
chanBucket, err := fetchChanBucketRw(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
if err != nil {
return err
}
// If the channel is marked as borked, then for safety reasons,
// we shouldn't attempt any further updates.
isBorked, err := c.isBorked(chanBucket)
if err != nil {
return err
}
if isBorked {
return ErrChanBorked
}
if err = putChanInfo(chanBucket, c); err != nil {
return fmt.Errorf("unable to store chan info: %w", err)
}
// With the proper bucket fetched, we'll now write the latest
// commitment state to disk for the target party.
err = putChanCommitment(
chanBucket, newCommitment, true,
)
if err != nil {
return fmt.Errorf("unable to store chan "+
"revocations: %v", err)
}
// Persist unsigned but acked remote updates that need to be
// restored after a restart.
var b bytes.Buffer
err = serializeLogUpdates(&b, unsignedAckedUpdates)
if err != nil {
return err
}
err = chanBucket.Put(unsignedAckedUpdatesKey, b.Bytes())
if err != nil {
return fmt.Errorf("unable to store dangline remote "+
"updates: %v", err)
}
// Since we have just sent the counterparty a revocation, store true
// under lastWasRevokeKey.
var b2 bytes.Buffer
if err := WriteElements(&b2, true); err != nil {
return err
}
if err := chanBucket.Put(lastWasRevokeKey, b2.Bytes()); err != nil {
return err
}
// Persist the remote unsigned local updates that are not included
// in our new commitment.
updateBytes := chanBucket.Get(remoteUnsignedLocalUpdatesKey)
if updateBytes == nil {
return nil
}
r := bytes.NewReader(updateBytes)
updates, err := deserializeLogUpdates(r)
if err != nil {
return err
}
// Get the bucket where settled htlcs are recorded if the user
// opted in to storing this information.
var finalHtlcsBucket kvdb.RwBucket
if c.Db.parent.storeFinalHtlcResolutions {
bucket, err := fetchFinalHtlcsBucketRw(
tx, c.ShortChannelID,
)
if err != nil {
return err
}
finalHtlcsBucket = bucket
}
var unsignedUpdates []LogUpdate
for _, upd := range updates {
// Gather updates that are not on our local commitment.
if upd.LogIndex >= newCommitment.LocalLogIndex {
unsignedUpdates = append(unsignedUpdates, upd)
continue
}
// The update was locked in. If the update was a
// resolution, then store it in the database.
err := processFinalHtlc(
finalHtlcsBucket, upd, finalHtlcs,
)
if err != nil {
return err
}
}
var b3 bytes.Buffer
err = serializeLogUpdates(&b3, unsignedUpdates)
if err != nil {
return fmt.Errorf("unable to serialize log updates: %w",
err)
}
err = chanBucket.Put(remoteUnsignedLocalUpdatesKey, b3.Bytes())
if err != nil {
return fmt.Errorf("unable to restore chanbucket: %w",
err)
}
return nil
}, func() {
finalHtlcs = make(map[uint64]bool)
})
if err != nil {
return nil, err
}
c.LocalCommitment = *newCommitment
return finalHtlcs, nil
}
// processFinalHtlc stores a final htlc outcome in the database if signaled via
// the supplied log update. An in-memory htlcs map is updated too.
func processFinalHtlc(finalHtlcsBucket walletdb.ReadWriteBucket, upd LogUpdate,
finalHtlcs map[uint64]bool) error {
var (
settled bool
id uint64
)
switch msg := upd.UpdateMsg.(type) {
case *lnwire.UpdateFulfillHTLC:
settled = true
id = msg.ID
case *lnwire.UpdateFailHTLC:
settled = false
id = msg.ID
case *lnwire.UpdateFailMalformedHTLC:
settled = false
id = msg.ID
default:
return nil
}
// Store the final resolution in the database if a bucket is provided.
if finalHtlcsBucket != nil {
err := putFinalHtlc(
finalHtlcsBucket, id,
FinalHtlcInfo{
Settled: settled,
Offchain: true,
},
)
if err != nil {
return err
}
}
finalHtlcs[id] = settled
return nil
}
// ActiveHtlcs returns a slice of HTLC's which are currently active on *both*
// commitment transactions.
func (c *OpenChannel) ActiveHtlcs() []HTLC {
c.RLock()
defer c.RUnlock()
// We'll only return HTLC's that are locked into *both* commitment
// transactions. So we'll iterate through their set of HTLC's to note
// which ones are present on their commitment.
remoteHtlcs := make(map[[32]byte]struct{})
for _, htlc := range c.RemoteCommitment.Htlcs {
log.Tracef("RemoteCommitment has htlc: id=%v, update=%v "+
"incoming=%v", htlc.HtlcIndex, htlc.LogIndex,
htlc.Incoming)
onionHash := sha256.Sum256(htlc.OnionBlob[:])
remoteHtlcs[onionHash] = struct{}{}
}
// Now that we know which HTLC's they have, we'll only mark the HTLC's
// as active if *we* know them as well.
activeHtlcs := make([]HTLC, 0, len(remoteHtlcs))
for _, htlc := range c.LocalCommitment.Htlcs {
log.Tracef("LocalCommitment has htlc: id=%v, update=%v "+
"incoming=%v", htlc.HtlcIndex, htlc.LogIndex,
htlc.Incoming)
onionHash := sha256.Sum256(htlc.OnionBlob[:])
if _, ok := remoteHtlcs[onionHash]; !ok {
log.Tracef("Skipped htlc due to onion mismatched: "+
"id=%v, update=%v incoming=%v",
htlc.HtlcIndex, htlc.LogIndex, htlc.Incoming)
continue
}
activeHtlcs = append(activeHtlcs, htlc)
}
return activeHtlcs
}
// HTLC is the on-disk representation of a hash time-locked contract. HTLCs are
// contained within ChannelDeltas which encode the current state of the
// commitment between state updates.
//
// TODO(roasbeef): save space by using smaller ints at tail end?
type HTLC struct {
// TODO(yy): can embed an HTLCEntry here.
// Signature is the signature for the second level covenant transaction
// for this HTLC. The second level transaction is a timeout tx in the
// case that this is an outgoing HTLC, and a success tx in the case
// that this is an incoming HTLC.
//
// TODO(roasbeef): make [64]byte instead?
Signature []byte
// RHash is the payment hash of the HTLC.
RHash [32]byte
// Amt is the amount of milli-satoshis this HTLC escrows.
Amt lnwire.MilliSatoshi
// RefundTimeout is the absolute timeout on the HTLC that the sender
// must wait before reclaiming the funds in limbo.
RefundTimeout uint32
// OutputIndex is the output index for this particular HTLC output
// within the commitment transaction.
OutputIndex int32
// Incoming denotes whether we're the receiver or the sender of this
// HTLC.
Incoming bool
// OnionBlob is an opaque blob which is used to complete multi-hop
// routing.
OnionBlob [lnwire.OnionPacketSize]byte
// HtlcIndex is the HTLC counter index of this active, outstanding
// HTLC. This differs from the LogIndex, as the HtlcIndex is only
// incremented for each offered HTLC, while they LogIndex is
// incremented for each update (includes settle+fail).
HtlcIndex uint64
// LogIndex is the cumulative log index of this HTLC. This differs
// from the HtlcIndex as this will be incremented for each new log
// update added.
LogIndex uint64
// ExtraData contains any additional information that was transmitted
// with the HTLC via TLVs. This data *must* already be encoded as a
// TLV stream, and may be empty. The length of this data is naturally
// limited by the space available to TLVs in update_add_htlc:
// = 65535 bytes (bolt 8 maximum message size):
// - 2 bytes (bolt 1 message_type)
// - 32 bytes (channel_id)
// - 8 bytes (id)
// - 8 bytes (amount_msat)
// - 32 bytes (payment_hash)
// - 4 bytes (cltv_expiry)
// - 1366 bytes (onion_routing_packet)
// = 64083 bytes maximum possible TLV stream
//
// Note that this extra data is stored inline with the OnionBlob for
// legacy reasons, see serialization/deserialization functions for
// detail.
ExtraData lnwire.ExtraOpaqueData
// BlindingPoint is an optional blinding point included with the HTLC.
//
// Note: this field is not a part of on-disk representation of the
// HTLC. It is stored in the ExtraData field, which is used to store
// a TLV stream of additional information associated with the HTLC.
BlindingPoint lnwire.BlindingPointRecord
// CustomRecords is a set of custom TLV records that are associated with
// this HTLC. These records are used to store additional information
// about the HTLC that is not part of the standard HTLC fields. This
// field is encoded within the ExtraData field.
CustomRecords lnwire.CustomRecords
}
// serializeExtraData encodes a TLV stream of extra data to be stored with a
// HTLC. It uses the update_add_htlc TLV types, because this is where extra
// data is passed with a HTLC. At present blinding points are the only extra
// data that we will store, and the function is a no-op if a nil blinding
// point is provided.
//
// This function MUST be called to persist all HTLC values when they are
// serialized.
func (h *HTLC) serializeExtraData() error {
var records []tlv.RecordProducer
h.BlindingPoint.WhenSome(func(b tlv.RecordT[lnwire.BlindingPointTlvType,
*btcec.PublicKey]) {
records = append(records, &b)
})
records, err := h.CustomRecords.ExtendRecordProducers(records)
if err != nil {
return err
}
return h.ExtraData.PackRecords(records...)
}
// deserializeExtraData extracts TLVs from the extra data persisted for the
// htlc and populates values in the struct accordingly.
//
// This function MUST be called to populate the struct properly when HTLCs
// are deserialized.
func (h *HTLC) deserializeExtraData() error {
if len(h.ExtraData) == 0 {
return nil
}
blindingPoint := h.BlindingPoint.Zero()
tlvMap, err := h.ExtraData.ExtractRecords(&blindingPoint)
if err != nil {
return err
}
if val, ok := tlvMap[h.BlindingPoint.TlvType()]; ok && val == nil {
h.BlindingPoint = tlv.SomeRecordT(blindingPoint)
// Remove the entry from the TLV map. Anything left in the map
// will be included in the custom records field.
delete(tlvMap, h.BlindingPoint.TlvType())
}
// Set the custom records field to the remaining TLV records.
customRecords, err := lnwire.NewCustomRecords(tlvMap)
if err != nil {
return err
}
h.CustomRecords = customRecords
return nil
}
// SerializeHtlcs writes out the passed set of HTLC's into the passed writer
// using the current default on-disk serialization format.
//
// This inline serialization has been extended to allow storage of extra data
// associated with a HTLC in the following way:
// - The known-length onion blob (1366 bytes) is serialized as var bytes in
// WriteElements (ie, the length 1366 was written, followed by the 1366
// onion bytes).
// - To include extra data, we append any extra data present to this one
// variable length of data. Since we know that the onion is strictly 1366
// bytes, any length after that should be considered to be extra data.
//
// NOTE: This API is NOT stable, the on-disk format will likely change in the
// future.
func SerializeHtlcs(b io.Writer, htlcs ...HTLC) error {
numHtlcs := uint16(len(htlcs))
if err := WriteElement(b, numHtlcs); err != nil {
return err
}
for _, htlc := range htlcs {
// Populate TLV stream for any additional fields contained
// in the TLV.
if err := htlc.serializeExtraData(); err != nil {
return err
}
// The onion blob and hltc data are stored as a single var
// bytes blob.
onionAndExtraData := make(
[]byte, lnwire.OnionPacketSize+len(htlc.ExtraData),
)
copy(onionAndExtraData, htlc.OnionBlob[:])
copy(onionAndExtraData[lnwire.OnionPacketSize:], htlc.ExtraData)
if err := WriteElements(b,
htlc.Signature, htlc.RHash, htlc.Amt, htlc.RefundTimeout,
htlc.OutputIndex, htlc.Incoming, onionAndExtraData,
htlc.HtlcIndex, htlc.LogIndex,
); err != nil {
return err
}
}
return nil
}
// DeserializeHtlcs attempts to read out a slice of HTLC's from the passed
// io.Reader. The bytes within the passed reader MUST have been previously
// written to using the SerializeHtlcs function.
//
// This inline deserialization has been extended to allow storage of extra data
// associated with a HTLC in the following way:
// - The known-length onion blob (1366 bytes) and any additional data present
// are read out as a single blob of variable byte data.
// - They are stored like this to take advantage of the variable space
// available for extension without migration (see SerializeHtlcs).
// - The first 1366 bytes are interpreted as the onion blob, and any remaining
// bytes as extra HTLC data.
// - This extra HTLC data is expected to be serialized as a TLV stream, and
// its parsing is left to higher layers.
//
// NOTE: This API is NOT stable, the on-disk format will likely change in the
// future.
func DeserializeHtlcs(r io.Reader) ([]HTLC, error) {
var numHtlcs uint16
if err := ReadElement(r, &numHtlcs); err != nil {
return nil, err
}
var htlcs []HTLC
if numHtlcs == 0 {
return htlcs, nil
}
htlcs = make([]HTLC, numHtlcs)
for i := uint16(0); i < numHtlcs; i++ {
var onionAndExtraData []byte
if err := ReadElements(r,
&htlcs[i].Signature, &htlcs[i].RHash, &htlcs[i].Amt,
&htlcs[i].RefundTimeout, &htlcs[i].OutputIndex,
&htlcs[i].Incoming, &onionAndExtraData,
&htlcs[i].HtlcIndex, &htlcs[i].LogIndex,
); err != nil {
return htlcs, err
}
// Sanity check that we have at least the onion blob size we
// expect.
if len(onionAndExtraData) < lnwire.OnionPacketSize {
return nil, ErrOnionBlobLength
}
// First OnionPacketSize bytes are our fixed length onion
// packet.
copy(
htlcs[i].OnionBlob[:],
onionAndExtraData[0:lnwire.OnionPacketSize],
)
// Any additional bytes belong to extra data. ExtraDataLen
// will be >= 0, because we know that we always have a fixed
// length onion packet.
extraDataLen := len(onionAndExtraData) - lnwire.OnionPacketSize
if extraDataLen > 0 {
htlcs[i].ExtraData = make([]byte, extraDataLen)
copy(
htlcs[i].ExtraData,
onionAndExtraData[lnwire.OnionPacketSize:],
)
}
// Finally, deserialize any TLVs contained in that extra data
// if they are present.
if err := htlcs[i].deserializeExtraData(); err != nil {
return nil, err
}
}
return htlcs, nil
}
// Copy returns a full copy of the target HTLC.
func (h *HTLC) Copy() HTLC {
clone := HTLC{
Incoming: h.Incoming,
Amt: h.Amt,
RefundTimeout: h.RefundTimeout,
OutputIndex: h.OutputIndex,
}
copy(clone.Signature[:], h.Signature)
copy(clone.RHash[:], h.RHash[:])
copy(clone.ExtraData, h.ExtraData)
clone.BlindingPoint = h.BlindingPoint
clone.CustomRecords = h.CustomRecords.Copy()
return clone
}
// LogUpdate represents a pending update to the remote commitment chain. The
// log update may be an add, fail, or settle entry. We maintain this data in
// order to be able to properly retransmit our proposed state if necessary.
type LogUpdate struct {
// LogIndex is the log index of this proposed commitment update entry.
LogIndex uint64
// UpdateMsg is the update message that was included within our
// local update log. The LogIndex value denotes the log index of this
// update which will be used when restoring our local update log if
// we're left with a dangling update on restart.
UpdateMsg lnwire.Message
}
// serializeLogUpdate writes a log update to the provided io.Writer.
func serializeLogUpdate(w io.Writer, l *LogUpdate) error {
return WriteElements(w, l.LogIndex, l.UpdateMsg)
}
// deserializeLogUpdate reads a log update from the provided io.Reader.
func deserializeLogUpdate(r io.Reader) (*LogUpdate, error) {
l := &LogUpdate{}
if err := ReadElements(r, &l.LogIndex, &l.UpdateMsg); err != nil {
return nil, err
}
return l, nil
}
// CommitDiff represents the delta needed to apply the state transition between
// two subsequent commitment states. Given state N and state N+1, one is able
// to apply the set of messages contained within the CommitDiff to N to arrive
// at state N+1. Each time a new commitment is extended, we'll write a new
// commitment (along with the full commitment state) to disk so we can
// re-transmit the state in the case of a connection loss or message drop.
type CommitDiff struct {
// ChannelCommitment is the full commitment state that one would arrive
// at by applying the set of messages contained in the UpdateDiff to
// the prior accepted commitment.
Commitment ChannelCommitment
// LogUpdates is the set of messages sent prior to the commitment state
// transition in question. Upon reconnection, if we detect that they
// don't have the commitment, then we re-send this along with the
// proper signature.
LogUpdates []LogUpdate
// CommitSig is the exact CommitSig message that should be sent after
// the set of LogUpdates above has been retransmitted. The signatures
// within this message should properly cover the new commitment state
// and also the HTLC's within the new commitment state.
CommitSig *lnwire.CommitSig
// OpenedCircuitKeys is a set of unique identifiers for any downstream
// Add packets included in this commitment txn. After a restart, this
// set of htlcs is acked from the link's incoming mailbox to ensure
// there isn't an attempt to re-add them to this commitment txn.
OpenedCircuitKeys []models.CircuitKey
// ClosedCircuitKeys records the unique identifiers for any settle/fail
// packets that were resolved by this commitment txn. After a restart,
// this is used to ensure those circuits are removed from the circuit
// map, and the downstream packets in the link's mailbox are removed.
ClosedCircuitKeys []models.CircuitKey
// AddAcks specifies the locations (commit height, pkg index) of any
// Adds that were failed/settled in this commit diff. This will ack
// entries in *this* channel's forwarding packages.
//
// NOTE: This value is not serialized, it is used to atomically mark the
// resolution of adds, such that they will not be reprocessed after a
// restart.
AddAcks []AddRef
// SettleFailAcks specifies the locations (chan id, commit height, pkg
// index) of any Settles or Fails that were locked into this commit
// diff, and originate from *another* channel, i.e. the outgoing link.
//
// NOTE: This value is not serialized, it is used to atomically acks
// settles and fails from the forwarding packages of other channels,
// such that they will not be reforwarded internally after a restart.
SettleFailAcks []SettleFailRef
}
// serializeLogUpdates serializes provided list of updates to a stream.
func serializeLogUpdates(w io.Writer, logUpdates []LogUpdate) error {
numUpdates := uint16(len(logUpdates))
if err := binary.Write(w, byteOrder, numUpdates); err != nil {
return err
}
for _, diff := range logUpdates {
err := WriteElements(w, diff.LogIndex, diff.UpdateMsg)
if err != nil {
return err
}
}
return nil
}
// deserializeLogUpdates deserializes a list of updates from a stream.
func deserializeLogUpdates(r io.Reader) ([]LogUpdate, error) {
var numUpdates uint16
if err := binary.Read(r, byteOrder, &numUpdates); err != nil {
return nil, err
}
logUpdates := make([]LogUpdate, numUpdates)
for i := 0; i < int(numUpdates); i++ {
err := ReadElements(r,
&logUpdates[i].LogIndex, &logUpdates[i].UpdateMsg,
)
if err != nil {
return nil, err
}
}
return logUpdates, nil
}
func serializeCommitDiff(w io.Writer, diff *CommitDiff) error { // nolint: dupl
if err := serializeChanCommit(w, &diff.Commitment); err != nil {
return err
}
if err := WriteElements(w, diff.CommitSig); err != nil {
return err
}
if err := serializeLogUpdates(w, diff.LogUpdates); err != nil {
return err
}
numOpenRefs := uint16(len(diff.OpenedCircuitKeys))
if err := binary.Write(w, byteOrder, numOpenRefs); err != nil {
return err
}
for _, openRef := range diff.OpenedCircuitKeys {
err := WriteElements(w, openRef.ChanID, openRef.HtlcID)
if err != nil {
return err
}
}
numClosedRefs := uint16(len(diff.ClosedCircuitKeys))
if err := binary.Write(w, byteOrder, numClosedRefs); err != nil {
return err
}
for _, closedRef := range diff.ClosedCircuitKeys {
err := WriteElements(w, closedRef.ChanID, closedRef.HtlcID)
if err != nil {
return err
}
}
// We'll also encode the commit aux data stream here. We do this here
// rather than above (at the call to serializeChanCommit), to ensure
// backwards compat for reads to existing non-custom channels.
auxData := diff.Commitment.extractTlvData()
if err := auxData.encode(w); err != nil {
return fmt.Errorf("unable to write aux data: %w", err)
}
return nil
}
func deserializeCommitDiff(r io.Reader) (*CommitDiff, error) {
var (
d CommitDiff
err error
)
d.Commitment, err = deserializeChanCommit(r)
if err != nil {
return nil, err
}
var msg lnwire.Message
if err := ReadElements(r, &msg); err != nil {
return nil, err
}
commitSig, ok := msg.(*lnwire.CommitSig)
if !ok {
return nil, fmt.Errorf("expected lnwire.CommitSig, instead "+
"read: %T", msg)
}
d.CommitSig = commitSig
d.LogUpdates, err = deserializeLogUpdates(r)
if err != nil {
return nil, err
}
var numOpenRefs uint16
if err := binary.Read(r, byteOrder, &numOpenRefs); err != nil {
return nil, err
}
d.OpenedCircuitKeys = make([]models.CircuitKey, numOpenRefs)
for i := 0; i < int(numOpenRefs); i++ {
err := ReadElements(r,
&d.OpenedCircuitKeys[i].ChanID,
&d.OpenedCircuitKeys[i].HtlcID)
if err != nil {
return nil, err
}
}
var numClosedRefs uint16
if err := binary.Read(r, byteOrder, &numClosedRefs); err != nil {
return nil, err
}
d.ClosedCircuitKeys = make([]models.CircuitKey, numClosedRefs)
for i := 0; i < int(numClosedRefs); i++ {
err := ReadElements(r,
&d.ClosedCircuitKeys[i].ChanID,
&d.ClosedCircuitKeys[i].HtlcID)
if err != nil {
return nil, err
}
}
// As a final step, we'll read out any aux commit data that we have at
// the end of this byte stream. We do this here to ensure backward
// compatibility, as otherwise we risk erroneously reading into the
// wrong field.
var auxData commitTlvData
if err := auxData.decode(r); err != nil {
return nil, fmt.Errorf("unable to decode aux data: %w", err)
}
d.Commitment.amendTlvData(auxData)
return &d, nil
}
// AppendRemoteCommitChain appends a new CommitDiff to the end of the
// commitment chain for the remote party. This method is to be used once we
// have prepared a new commitment state for the remote party, but before we
// transmit it to the remote party. The contents of the argument should be
// sufficient to retransmit the updates and signature needed to reconstruct the
// state in full, in the case that we need to retransmit.
func (c *OpenChannel) AppendRemoteCommitChain(diff *CommitDiff) error {
c.Lock()
defer c.Unlock()
// If this is a restored channel, then we want to avoid mutating the
// state at all, as it's impossible to do so in a protocol compliant
// manner.
if c.hasChanStatus(ChanStatusRestored) {
return ErrNoRestoredChannelMutation
}
return kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
// First, we'll grab the writable bucket where this channel's
// data resides.
chanBucket, err := fetchChanBucketRw(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
if err != nil {
return err
}
// If the channel is marked as borked, then for safety reasons,
// we shouldn't attempt any further updates.
isBorked, err := c.isBorked(chanBucket)
if err != nil {
return err
}
if isBorked {
return ErrChanBorked
}
// Any outgoing settles and fails necessarily have a
// corresponding adds in this channel's forwarding packages.
// Mark all of these as being fully processed in our forwarding
// package, which prevents us from reprocessing them after
// startup.
err = c.Packager.AckAddHtlcs(tx, diff.AddAcks...)
if err != nil {
return err
}
// Additionally, we ack from any fails or settles that are
// persisted in another channel's forwarding package. This
// prevents the same fails and settles from being retransmitted
// after restarts. The actual fail or settle we need to
// propagate to the remote party is now in the commit diff.
err = c.Packager.AckSettleFails(tx, diff.SettleFailAcks...)
if err != nil {
return err
}
// We are sending a commitment signature so lastWasRevokeKey should
// store false.
var b bytes.Buffer
if err := WriteElements(&b, false); err != nil {
return err
}
if err := chanBucket.Put(lastWasRevokeKey, b.Bytes()); err != nil {
return err
}
// TODO(roasbeef): use seqno to derive key for later LCP
// With the bucket retrieved, we'll now serialize the commit
// diff itself, and write it to disk.
var b2 bytes.Buffer
if err := serializeCommitDiff(&b2, diff); err != nil {
return err
}
return chanBucket.Put(commitDiffKey, b2.Bytes())
}, func() {})
}
// RemoteCommitChainTip returns the "tip" of the current remote commitment
// chain. This value will be non-nil iff, we've created a new commitment for
// the remote party that they haven't yet ACK'd. In this case, their commitment
// chain will have a length of two: their current unrevoked commitment, and
// this new pending commitment. Once they revoked their prior state, we'll swap
// these pointers, causing the tip and the tail to point to the same entry.
func (c *OpenChannel) RemoteCommitChainTip() (*CommitDiff, error) {
var cd *CommitDiff
err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error {
chanBucket, err := fetchChanBucket(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
switch err {
case nil:
case ErrNoChanDBExists, ErrNoActiveChannels, ErrChannelNotFound:
return ErrNoPendingCommit
default:
return err
}
tipBytes := chanBucket.Get(commitDiffKey)
if tipBytes == nil {
return ErrNoPendingCommit
}
tipReader := bytes.NewReader(tipBytes)
dcd, err := deserializeCommitDiff(tipReader)
if err != nil {
return err
}
cd = dcd
return nil
}, func() {
cd = nil
})
if err != nil {
return nil, err
}
return cd, nil
}
// UnsignedAckedUpdates retrieves the persisted unsigned acked remote log
// updates that still need to be signed for.
func (c *OpenChannel) UnsignedAckedUpdates() ([]LogUpdate, error) {
var updates []LogUpdate
err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error {
chanBucket, err := fetchChanBucket(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
switch err {
case nil:
case ErrNoChanDBExists, ErrNoActiveChannels, ErrChannelNotFound:
return nil
default:
return err
}
updateBytes := chanBucket.Get(unsignedAckedUpdatesKey)
if updateBytes == nil {
return nil
}
r := bytes.NewReader(updateBytes)
updates, err = deserializeLogUpdates(r)
return err
}, func() {
updates = nil
})
if err != nil {
return nil, err
}
return updates, nil
}
// RemoteUnsignedLocalUpdates retrieves the persisted, unsigned local log
// updates that the remote still needs to sign for.
func (c *OpenChannel) RemoteUnsignedLocalUpdates() ([]LogUpdate, error) {
var updates []LogUpdate
err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error {
chanBucket, err := fetchChanBucket(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
switch err {
case nil:
break
case ErrNoChanDBExists, ErrNoActiveChannels, ErrChannelNotFound:
return nil
default:
return err
}
updateBytes := chanBucket.Get(remoteUnsignedLocalUpdatesKey)
if updateBytes == nil {
return nil
}
r := bytes.NewReader(updateBytes)
updates, err = deserializeLogUpdates(r)
return err
}, func() {
updates = nil
})
if err != nil {
return nil, err
}
return updates, nil
}
// InsertNextRevocation inserts the _next_ commitment point (revocation) into
// the database, and also modifies the internal RemoteNextRevocation attribute
// to point to the passed key. This method is to be using during final channel
// set up, _after_ the channel has been fully confirmed.
//
// NOTE: If this method isn't called, then the target channel won't be able to
// propose new states for the commitment state of the remote party.
func (c *OpenChannel) InsertNextRevocation(revKey *btcec.PublicKey) error {
c.Lock()
defer c.Unlock()
c.RemoteNextRevocation = revKey
err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
chanBucket, err := fetchChanBucketRw(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
if err != nil {
return err
}
return putChanRevocationState(chanBucket, c)
}, func() {})
if err != nil {
return err
}
return nil
}
// AdvanceCommitChainTail records the new state transition within an on-disk
// append-only log which records all state transitions by the remote peer. In
// the case of an uncooperative broadcast of a prior state by the remote peer,
// this log can be consulted in order to reconstruct the state needed to
// rectify the situation. This method will add the current commitment for the
// remote party to the revocation log, and promote the current pending
// commitment to the current remote commitment. The updates parameter is the
// set of local updates that the peer still needs to send us a signature for.
// We store this set of updates in case we go down.
func (c *OpenChannel) AdvanceCommitChainTail(fwdPkg *FwdPkg,
updates []LogUpdate, ourOutputIndex, theirOutputIndex uint32) error {
c.Lock()
defer c.Unlock()
// If this is a restored channel, then we want to avoid mutating the
// state at all, as it's impossible to do so in a protocol compliant
// manner.
if c.hasChanStatus(ChanStatusRestored) {
return ErrNoRestoredChannelMutation
}
var newRemoteCommit *ChannelCommitment
err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
chanBucket, err := fetchChanBucketRw(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
if err != nil {
return err
}
// If the channel is marked as borked, then for safety reasons,
// we shouldn't attempt any further updates.
isBorked, err := c.isBorked(chanBucket)
if err != nil {
return err
}
if isBorked {
return ErrChanBorked
}
// Persist the latest preimage state to disk as the remote peer
// has just added to our local preimage store, and given us a
// new pending revocation key.
if err := putChanRevocationState(chanBucket, c); err != nil {
return err
}
// With the current preimage producer/store state updated,
// append a new log entry recording this the delta of this
// state transition.
//
// TODO(roasbeef): could make the deltas relative, would save
// space, but then tradeoff for more disk-seeks to recover the
// full state.
logKey := revocationLogBucket
logBucket, err := chanBucket.CreateBucketIfNotExists(logKey)
if err != nil {
return err
}
// Before we append this revoked state to the revocation log,
// we'll swap out what's currently the tail of the commit tip,
// with the current locked-in commitment for the remote party.
tipBytes := chanBucket.Get(commitDiffKey)
tipReader := bytes.NewReader(tipBytes)
newCommit, err := deserializeCommitDiff(tipReader)
if err != nil {
return err
}
err = putChanCommitment(
chanBucket, &newCommit.Commitment, false,
)
if err != nil {
return err
}
if err := chanBucket.Delete(commitDiffKey); err != nil {
return err
}
// With the commitment pointer swapped, we can now add the
// revoked (prior) state to the revocation log.
err = putRevocationLog(
logBucket, &c.RemoteCommitment, ourOutputIndex,
theirOutputIndex, c.Db.parent.noRevLogAmtData,
)
if err != nil {
return err
}
// Lastly, we write the forwarding package to disk so that we
// can properly recover from failures and reforward HTLCs that
// have not received a corresponding settle/fail.
if err := c.Packager.AddFwdPkg(tx, fwdPkg); err != nil {
return err
}
// Persist the unsigned acked updates that are not included
// in their new commitment.
updateBytes := chanBucket.Get(unsignedAckedUpdatesKey)
if updateBytes == nil {
// This shouldn't normally happen as we always store
// the number of updates, but could still be
// encountered by nodes that are upgrading.
newRemoteCommit = &newCommit.Commitment
return nil
}
r := bytes.NewReader(updateBytes)
unsignedUpdates, err := deserializeLogUpdates(r)
if err != nil {
return err
}
var validUpdates []LogUpdate
for _, upd := range unsignedUpdates {
lIdx := upd.LogIndex
// Filter for updates that are not on the remote
// commitment.
if lIdx >= newCommit.Commitment.RemoteLogIndex {
validUpdates = append(validUpdates, upd)
}
}
var b bytes.Buffer
err = serializeLogUpdates(&b, validUpdates)
if err != nil {
return fmt.Errorf("unable to serialize log updates: %w",
err)
}
err = chanBucket.Put(unsignedAckedUpdatesKey, b.Bytes())
if err != nil {
return fmt.Errorf("unable to store under "+
"unsignedAckedUpdatesKey: %w", err)
}
// Persist the local updates the peer hasn't yet signed so they
// can be restored after restart.
var b2 bytes.Buffer
err = serializeLogUpdates(&b2, updates)
if err != nil {
return err
}
err = chanBucket.Put(remoteUnsignedLocalUpdatesKey, b2.Bytes())
if err != nil {
return fmt.Errorf("unable to restore remote unsigned "+
"local updates: %v", err)
}
newRemoteCommit = &newCommit.Commitment
return nil
}, func() {
newRemoteCommit = nil
})
if err != nil {
return err
}
// With the db transaction complete, we'll swap over the in-memory
// pointer of the new remote commitment, which was previously the tip
// of the commit chain.
c.RemoteCommitment = *newRemoteCommit
return nil
}
// FinalHtlcInfo contains information about the final outcome of an htlc.
type FinalHtlcInfo struct {
// Settled is true is the htlc was settled. If false, the htlc was
// failed.
Settled bool
// Offchain indicates whether the htlc was resolved off-chain or
// on-chain.
Offchain bool
}
// putFinalHtlc writes the final htlc outcome to the database. Additionally it
// records whether the htlc was resolved off-chain or on-chain.
func putFinalHtlc(finalHtlcsBucket kvdb.RwBucket, id uint64,
info FinalHtlcInfo) error {
var key [8]byte
byteOrder.PutUint64(key[:], id)
var finalHtlcByte FinalHtlcByte
if info.Settled {
finalHtlcByte |= FinalHtlcSettledBit
}
if info.Offchain {
finalHtlcByte |= FinalHtlcOffchainBit
}
return finalHtlcsBucket.Put(key[:], []byte{byte(finalHtlcByte)})
}
// NextLocalHtlcIndex returns the next unallocated local htlc index. To ensure
// this always returns the next index that has been not been allocated, this
// will first try to examine any pending commitments, before falling back to the
// last locked-in remote commitment.
func (c *OpenChannel) NextLocalHtlcIndex() (uint64, error) {
// First, load the most recent commit diff that we initiated for the
// remote party. If no pending commit is found, this is not treated as
// a critical error, since we can always fall back.
pendingRemoteCommit, err := c.RemoteCommitChainTip()
if err != nil && err != ErrNoPendingCommit {
return 0, err
}
// If a pending commit was found, its local htlc index will be at least
// as large as the one on our local commitment.
if pendingRemoteCommit != nil {
return pendingRemoteCommit.Commitment.LocalHtlcIndex, nil
}
// Otherwise, fallback to using the local htlc index of their commitment.
return c.RemoteCommitment.LocalHtlcIndex, nil
}
// LoadFwdPkgs scans the forwarding log for any packages that haven't been
// processed, and returns their deserialized log updates in map indexed by the
// remote commitment height at which the updates were locked in.
func (c *OpenChannel) LoadFwdPkgs() ([]*FwdPkg, error) {
c.RLock()
defer c.RUnlock()
var fwdPkgs []*FwdPkg
if err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error {
var err error
fwdPkgs, err = c.Packager.LoadFwdPkgs(tx)
return err
}, func() {
fwdPkgs = nil
}); err != nil {
return nil, err
}
return fwdPkgs, nil
}
// AckAddHtlcs updates the AckAddFilter containing any of the provided AddRefs
// indicating that a response to this Add has been committed to the remote party.
// Doing so will prevent these Add HTLCs from being reforwarded internally.
func (c *OpenChannel) AckAddHtlcs(addRefs ...AddRef) error {
c.Lock()
defer c.Unlock()
return kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
return c.Packager.AckAddHtlcs(tx, addRefs...)
}, func() {})
}
// AckSettleFails updates the SettleFailFilter containing any of the provided
// SettleFailRefs, indicating that the response has been delivered to the
// incoming link, corresponding to a particular AddRef. Doing so will prevent
// the responses from being retransmitted internally.
func (c *OpenChannel) AckSettleFails(settleFailRefs ...SettleFailRef) error {
c.Lock()
defer c.Unlock()
return kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
return c.Packager.AckSettleFails(tx, settleFailRefs...)
}, func() {})
}
// SetFwdFilter atomically sets the forwarding filter for the forwarding package
// identified by `height`.
func (c *OpenChannel) SetFwdFilter(height uint64, fwdFilter *PkgFilter) error {
c.Lock()
defer c.Unlock()
return kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
return c.Packager.SetFwdFilter(tx, height, fwdFilter)
}, func() {})
}
// RemoveFwdPkgs atomically removes forwarding packages specified by the remote
// commitment heights. If one of the intermediate RemovePkg calls fails, then the
// later packages won't be removed.
//
// NOTE: This method should only be called on packages marked FwdStateCompleted.
func (c *OpenChannel) RemoveFwdPkgs(heights ...uint64) error {
c.Lock()
defer c.Unlock()
return kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
for _, height := range heights {
err := c.Packager.RemovePkg(tx, height)
if err != nil {
return err
}
}
return nil
}, func() {})
}
// revocationLogTailCommitHeight returns the commit height at the end of the
// revocation log. This entry represents the last previous state for the remote
// node's commitment chain. The ChannelDelta returned by this method will
// always lag one state behind the most current (unrevoked) state of the remote
// node's commitment chain.
// NOTE: used in unit test only.
func (c *OpenChannel) revocationLogTailCommitHeight() (uint64, error) {
c.RLock()
defer c.RUnlock()
var height uint64
// If we haven't created any state updates yet, then we'll exit early as
// there's nothing to be found on disk in the revocation bucket.
if c.RemoteCommitment.CommitHeight == 0 {
return height, nil
}
if err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error {
chanBucket, err := fetchChanBucket(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
if err != nil {
return err
}
logBucket, err := fetchLogBucket(chanBucket)
if err != nil {
return err
}
// Once we have the bucket that stores the revocation log from
// this channel, we'll jump to the _last_ key in bucket. Since
// the key is the commit height, we'll decode the bytes and
// return it.
cursor := logBucket.ReadCursor()
rawHeight, _ := cursor.Last()
height = byteOrder.Uint64(rawHeight)
return nil
}, func() {}); err != nil {
return height, err
}
return height, nil
}
// CommitmentHeight returns the current commitment height. The commitment
// height represents the number of updates to the commitment state to date.
// This value is always monotonically increasing. This method is provided in
// order to allow multiple instances of a particular open channel to obtain a
// consistent view of the number of channel updates to date.
func (c *OpenChannel) CommitmentHeight() (uint64, error) {
c.RLock()
defer c.RUnlock()
var height uint64
err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error {
// Get the bucket dedicated to storing the metadata for open
// channels.
chanBucket, err := fetchChanBucket(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
if err != nil {
return err
}
commit, err := fetchChanCommitment(chanBucket, true)
if err != nil {
return err
}
height = commit.CommitHeight
return nil
}, func() {
height = 0
})
if err != nil {
return 0, err
}
return height, nil
}
// FindPreviousState scans through the append-only log in an attempt to recover
// the previous channel state indicated by the update number. This method is
// intended to be used for obtaining the relevant data needed to claim all
// funds rightfully spendable in the case of an on-chain broadcast of the
// commitment transaction.
func (c *OpenChannel) FindPreviousState(
updateNum uint64) (*RevocationLog, *ChannelCommitment, error) {
c.RLock()
defer c.RUnlock()
commit := &ChannelCommitment{}
rl := &RevocationLog{}
err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error {
chanBucket, err := fetchChanBucket(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
if err != nil {
return err
}
// Find the revocation log from both the new and the old
// bucket.
r, c, err := fetchRevocationLogCompatible(chanBucket, updateNum)
if err != nil {
return err
}
rl = r
commit = c
return nil
}, func() {})
if err != nil {
return nil, nil, err
}
// Either the `rl` or the `commit` is nil here. We return them as-is
// and leave it to the caller to decide its following action.
return rl, commit, nil
}
// ClosureType is an enum like structure that details exactly _how_ a channel
// was closed. Three closure types are currently possible: none, cooperative,
// local force close, remote force close, and (remote) breach.
type ClosureType uint8
const (
// CooperativeClose indicates that a channel has been closed
// cooperatively. This means that both channel peers were online and
// signed a new transaction paying out the settled balance of the
// contract.
CooperativeClose ClosureType = 0
// LocalForceClose indicates that we have unilaterally broadcast our
// current commitment state on-chain.
LocalForceClose ClosureType = 1
// RemoteForceClose indicates that the remote peer has unilaterally
// broadcast their current commitment state on-chain.
RemoteForceClose ClosureType = 4
// BreachClose indicates that the remote peer attempted to broadcast a
// prior _revoked_ channel state.
BreachClose ClosureType = 2
// FundingCanceled indicates that the channel never was fully opened
// before it was marked as closed in the database. This can happen if
// we or the remote fail at some point during the opening workflow, or
// we timeout waiting for the funding transaction to be confirmed.
FundingCanceled ClosureType = 3
// Abandoned indicates that the channel state was removed without
// any further actions. This is intended to clean up unusable
// channels during development.
Abandoned ClosureType = 5
)
// ChannelCloseSummary contains the final state of a channel at the point it
// was closed. Once a channel is closed, all the information pertaining to that
// channel within the openChannelBucket is deleted, and a compact summary is
// put in place instead.
type ChannelCloseSummary struct {
// ChanPoint is the outpoint for this channel's funding transaction,
// and is used as a unique identifier for the channel.
ChanPoint wire.OutPoint
// ShortChanID encodes the exact location in the chain in which the
// channel was initially confirmed. This includes: the block height,
// transaction index, and the output within the target transaction.
ShortChanID lnwire.ShortChannelID
// ChainHash is the hash of the genesis block that this channel resides
// within.
ChainHash chainhash.Hash
// ClosingTXID is the txid of the transaction which ultimately closed
// this channel.
ClosingTXID chainhash.Hash
// RemotePub is the public key of the remote peer that we formerly had
// a channel with.
RemotePub *btcec.PublicKey
// Capacity was the total capacity of the channel.
Capacity btcutil.Amount
// CloseHeight is the height at which the funding transaction was
// spent.
CloseHeight uint32
// SettledBalance is our total balance settled balance at the time of
// channel closure. This _does not_ include the sum of any outputs that
// have been time-locked as a result of the unilateral channel closure.
SettledBalance btcutil.Amount
// TimeLockedBalance is the sum of all the time-locked outputs at the
// time of channel closure. If we triggered the force closure of this
// channel, then this value will be non-zero if our settled output is
// above the dust limit. If we were on the receiving side of a channel
// force closure, then this value will be non-zero if we had any
// outstanding outgoing HTLC's at the time of channel closure.
TimeLockedBalance btcutil.Amount
// CloseType details exactly _how_ the channel was closed. Five closure
// types are possible: cooperative, local force, remote force, breach
// and funding canceled.
CloseType ClosureType
// IsPending indicates whether this channel is in the 'pending close'
// state, which means the channel closing transaction has been
// confirmed, but not yet been fully resolved. In the case of a channel
// that has been cooperatively closed, it will go straight into the
// fully resolved state as soon as the closing transaction has been
// confirmed. However, for channels that have been force closed, they'll
// stay marked as "pending" until _all_ the pending funds have been
// swept.
IsPending bool
// RemoteCurrentRevocation is the current revocation for their
// commitment transaction. However, since this is the derived public key,
// we don't yet have the private key so we aren't yet able to verify
// that it's actually in the hash chain.
RemoteCurrentRevocation *btcec.PublicKey
// RemoteNextRevocation is the revocation key to be used for the *next*
// commitment transaction we create for the local node. Within the
// specification, this value is referred to as the
// per-commitment-point.
RemoteNextRevocation *btcec.PublicKey
// LocalChanConfig is the channel configuration for the local node.
LocalChanConfig ChannelConfig
// LastChanSyncMsg is the ChannelReestablish message for this channel
// for the state at the point where it was closed.
LastChanSyncMsg *lnwire.ChannelReestablish
}
// CloseChannel closes a previously active Lightning channel. Closing a channel
// entails deleting all saved state within the database concerning this
// channel. This method also takes a struct that summarizes the state of the
// channel at closing, this compact representation will be the only component
// of a channel left over after a full closing. It takes an optional set of
// channel statuses which will be written to the historical channel bucket.
// These statuses are used to record close initiators.
func (c *OpenChannel) CloseChannel(summary *ChannelCloseSummary,
statuses ...ChannelStatus) error {
c.Lock()
defer c.Unlock()
return kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
openChanBucket := tx.ReadWriteBucket(openChannelBucket)
if openChanBucket == nil {
return ErrNoChanDBExists
}
nodePub := c.IdentityPub.SerializeCompressed()
nodeChanBucket := openChanBucket.NestedReadWriteBucket(nodePub)
if nodeChanBucket == nil {
return ErrNoActiveChannels
}
chainBucket := nodeChanBucket.NestedReadWriteBucket(c.ChainHash[:])
if chainBucket == nil {
return ErrNoActiveChannels
}
var chanPointBuf bytes.Buffer
err := graphdb.WriteOutpoint(&chanPointBuf, &c.FundingOutpoint)
if err != nil {
return err
}
chanKey := chanPointBuf.Bytes()
chanBucket := chainBucket.NestedReadWriteBucket(
chanKey,
)
if chanBucket == nil {
return ErrNoActiveChannels
}
// Before we delete the channel state, we'll read out the full
// details, as we'll also store portions of this information
// for record keeping.
chanState, err := fetchOpenChannel(
chanBucket, &c.FundingOutpoint,
)
if err != nil {
return err
}
// Delete all the forwarding packages stored for this particular
// channel.
if err = chanState.Packager.Wipe(tx); err != nil {
return err
}
// Now that the index to this channel has been deleted, purge
// the remaining channel metadata from the database.
err = deleteOpenChannel(chanBucket)
if err != nil {
return err
}
// We'll also remove the channel from the frozen channel bucket
// if we need to.
if c.ChanType.IsFrozen() || c.ChanType.HasLeaseExpiration() {
err := deleteThawHeight(chanBucket)
if err != nil {
return err
}
}
// With the base channel data deleted, attempt to delete the
// information stored within the revocation log.
if err := deleteLogBucket(chanBucket); err != nil {
return err
}
err = chainBucket.DeleteNestedBucket(chanPointBuf.Bytes())
if err != nil {
return err
}
// Fetch the outpoint bucket to see if the outpoint exists or
// not.
opBucket := tx.ReadWriteBucket(outpointBucket)
if opBucket == nil {
return ErrNoChanDBExists
}
// Add the closed outpoint to our outpoint index. This should
// replace an open outpoint in the index.
if opBucket.Get(chanPointBuf.Bytes()) == nil {
return ErrMissingIndexEntry
}
status := uint8(outpointClosed)
// Write the IndexStatus of this outpoint as the first entry in a tlv
// stream.
statusRecord := tlv.MakePrimitiveRecord(indexStatusType, &status)
opStream, err := tlv.NewStream(statusRecord)
if err != nil {
return err
}
var b bytes.Buffer
if err := opStream.Encode(&b); err != nil {
return err
}
// Finally add the closed outpoint and tlv stream to the index.
if err := opBucket.Put(chanPointBuf.Bytes(), b.Bytes()); err != nil {
return err
}
// Add channel state to the historical channel bucket.
historicalBucket, err := tx.CreateTopLevelBucket(
historicalChannelBucket,
)
if err != nil {
return err
}
historicalChanBucket, err :=
historicalBucket.CreateBucketIfNotExists(chanKey)
if err != nil {
return err
}
// Apply any additional statuses to the channel state.
for _, status := range statuses {
chanState.chanStatus |= status
}
err = putOpenChannel(historicalChanBucket, chanState)
if err != nil {
return err
}
// Finally, create a summary of this channel in the closed
// channel bucket for this node.
return putChannelCloseSummary(
tx, chanPointBuf.Bytes(), summary, chanState,
)
}, func() {})
}
// ChannelSnapshot is a frozen snapshot of the current channel state. A
// snapshot is detached from the original channel that generated it, providing
// read-only access to the current or prior state of an active channel.
//
// TODO(roasbeef): remove all together? pretty much just commitment
type ChannelSnapshot struct {
// RemoteIdentity is the identity public key of the remote node that we
// are maintaining the open channel with.
RemoteIdentity btcec.PublicKey
// ChanPoint is the outpoint that created the channel. This output is
// found within the funding transaction and uniquely identified the
// channel on the resident chain.
ChannelPoint wire.OutPoint
// ChainHash is the genesis hash of the chain that the channel resides
// within.
ChainHash chainhash.Hash
// Capacity is the total capacity of the channel.
Capacity btcutil.Amount
// TotalMSatSent is the total number of milli-satoshis we've sent
// within this channel.
TotalMSatSent lnwire.MilliSatoshi
// TotalMSatReceived is the total number of milli-satoshis we've
// received within this channel.
TotalMSatReceived lnwire.MilliSatoshi
// ChannelCommitment is the current up-to-date commitment for the
// target channel.
ChannelCommitment
}
// Snapshot returns a read-only snapshot of the current channel state. This
// snapshot includes information concerning the current settled balance within
// the channel, metadata detailing total flows, and any outstanding HTLCs.
func (c *OpenChannel) Snapshot() *ChannelSnapshot {
c.RLock()
defer c.RUnlock()
localCommit := c.LocalCommitment
snapshot := &ChannelSnapshot{
RemoteIdentity: *c.IdentityPub,
ChannelPoint: c.FundingOutpoint,
Capacity: c.Capacity,
TotalMSatSent: c.TotalMSatSent,
TotalMSatReceived: c.TotalMSatReceived,
ChainHash: c.ChainHash,
ChannelCommitment: ChannelCommitment{
LocalBalance: localCommit.LocalBalance,
RemoteBalance: localCommit.RemoteBalance,
CommitHeight: localCommit.CommitHeight,
CommitFee: localCommit.CommitFee,
},
}
localCommit.CustomBlob.WhenSome(func(blob tlv.Blob) {
blobCopy := make([]byte, len(blob))
copy(blobCopy, blob)
snapshot.ChannelCommitment.CustomBlob = fn.Some(blobCopy)
})
// Copy over the current set of HTLCs to ensure the caller can't mutate
// our internal state.
snapshot.Htlcs = make([]HTLC, len(localCommit.Htlcs))
for i, h := range localCommit.Htlcs {
snapshot.Htlcs[i] = h.Copy()
}
return snapshot
}
// LatestCommitments returns the two latest commitments for both the local and
// remote party. These commitments are read from disk to ensure that only the
// latest fully committed state is returned. The first commitment returned is
// the local commitment, and the second returned is the remote commitment.
func (c *OpenChannel) LatestCommitments() (*ChannelCommitment, *ChannelCommitment, error) {
err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error {
chanBucket, err := fetchChanBucket(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
if err != nil {
return err
}
return fetchChanCommitments(chanBucket, c)
}, func() {})
if err != nil {
return nil, nil, err
}
return &c.LocalCommitment, &c.RemoteCommitment, nil
}
// RemoteRevocationStore returns the most up to date commitment version of the
// revocation storage tree for the remote party. This method can be used when
// acting on a possible contract breach to ensure, that the caller has the most
// up to date information required to deliver justice.
func (c *OpenChannel) RemoteRevocationStore() (shachain.Store, error) {
err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error {
chanBucket, err := fetchChanBucket(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
if err != nil {
return err
}
return fetchChanRevocationState(chanBucket, c)
}, func() {})
if err != nil {
return nil, err
}
return c.RevocationStore, nil
}
// AbsoluteThawHeight determines a frozen channel's absolute thaw height. If the
// channel is not frozen, then 0 is returned.
func (c *OpenChannel) AbsoluteThawHeight() (uint32, error) {
// Only frozen channels have a thaw height.
if !c.ChanType.IsFrozen() && !c.ChanType.HasLeaseExpiration() {
return 0, nil
}
// If the channel has the frozen bit set and it's thaw height is below
// the absolute threshold, then it's interpreted as a relative height to
// the chain's current height.
if c.ChanType.IsFrozen() && c.ThawHeight < AbsoluteThawHeightThreshold {
// We'll only known of the channel's short ID once it's
// confirmed.
if c.IsPending {
return 0, errors.New("cannot use relative thaw " +
"height for unconfirmed channel")
}
// For non-zero-conf channels, this is the base height to use.
blockHeightBase := c.ShortChannelID.BlockHeight
// If this is a zero-conf channel, the ShortChannelID will be
// an alias.
if c.IsZeroConf() {
if !c.ZeroConfConfirmed() {
return 0, errors.New("cannot use relative " +
"height for unconfirmed zero-conf " +
"channel")
}
// Use the confirmed SCID's BlockHeight.
blockHeightBase = c.confirmedScid.BlockHeight
}
return blockHeightBase + c.ThawHeight, nil
}
return c.ThawHeight, nil
}
// DeriveHeightHint derives the block height for the channel opening.
func (c *OpenChannel) DeriveHeightHint() uint32 {
// As a height hint, we'll try to use the opening height, but if the
// channel isn't yet open, then we'll use the height it was broadcast
// at. This may be an unconfirmed zero-conf channel.
heightHint := c.ShortChanID().BlockHeight
if heightHint == 0 {
heightHint = c.BroadcastHeight()
}
// Since no zero-conf state is stored in a channel backup, the below
// logic will not be triggered for restored, zero-conf channels. Set
// the height hint for zero-conf channels.
if c.IsZeroConf() {
if c.ZeroConfConfirmed() {
// If the zero-conf channel is confirmed, we'll use the
// confirmed SCID's block height.
heightHint = c.ZeroConfRealScid().BlockHeight
} else {
// The zero-conf channel is unconfirmed. We'll need to
// use the FundingBroadcastHeight.
heightHint = c.BroadcastHeight()
}
}
return heightHint
}
func putChannelCloseSummary(tx kvdb.RwTx, chanID []byte,
summary *ChannelCloseSummary, lastChanState *OpenChannel) error {
closedChanBucket, err := tx.CreateTopLevelBucket(closedChannelBucket)
if err != nil {
return err
}
summary.RemoteCurrentRevocation = lastChanState.RemoteCurrentRevocation
summary.RemoteNextRevocation = lastChanState.RemoteNextRevocation
summary.LocalChanConfig = lastChanState.LocalChanCfg
var b bytes.Buffer
if err := serializeChannelCloseSummary(&b, summary); err != nil {
return err
}
return closedChanBucket.Put(chanID, b.Bytes())
}
func serializeChannelCloseSummary(w io.Writer, cs *ChannelCloseSummary) error {
err := WriteElements(w,
cs.ChanPoint, cs.ShortChanID, cs.ChainHash, cs.ClosingTXID,
cs.CloseHeight, cs.RemotePub, cs.Capacity, cs.SettledBalance,
cs.TimeLockedBalance, cs.CloseType, cs.IsPending,
)
if err != nil {
return err
}
// If this is a close channel summary created before the addition of
// the new fields, then we can exit here.
if cs.RemoteCurrentRevocation == nil {
return WriteElements(w, false)
}
// If fields are present, write boolean to indicate this, and continue.
if err := WriteElements(w, true); err != nil {
return err
}
if err := WriteElements(w, cs.RemoteCurrentRevocation); err != nil {
return err
}
if err := writeChanConfig(w, &cs.LocalChanConfig); err != nil {
return err
}
// The RemoteNextRevocation field is optional, as it's possible for a
// channel to be closed before we learn of the next unrevoked
// revocation point for the remote party. Write a boolean indicating
// whether this field is present or not.
if err := WriteElements(w, cs.RemoteNextRevocation != nil); err != nil {
return err
}
// Write the field, if present.
if cs.RemoteNextRevocation != nil {
if err = WriteElements(w, cs.RemoteNextRevocation); err != nil {
return err
}
}
// Write whether the channel sync message is present.
if err := WriteElements(w, cs.LastChanSyncMsg != nil); err != nil {
return err
}
// Write the channel sync message, if present.
if cs.LastChanSyncMsg != nil {
if err := WriteElements(w, cs.LastChanSyncMsg); err != nil {
return err
}
}
return nil
}
func deserializeCloseChannelSummary(r io.Reader) (*ChannelCloseSummary, error) {
c := &ChannelCloseSummary{}
err := ReadElements(r,
&c.ChanPoint, &c.ShortChanID, &c.ChainHash, &c.ClosingTXID,
&c.CloseHeight, &c.RemotePub, &c.Capacity, &c.SettledBalance,
&c.TimeLockedBalance, &c.CloseType, &c.IsPending,
)
if err != nil {
return nil, err
}
// We'll now check to see if the channel close summary was encoded with
// any of the additional optional fields.
var hasNewFields bool
err = ReadElements(r, &hasNewFields)
if err != nil {
return nil, err
}
// If fields are not present, we can return.
if !hasNewFields {
return c, nil
}
// Otherwise read the new fields.
if err := ReadElements(r, &c.RemoteCurrentRevocation); err != nil {
return nil, err
}
if err := readChanConfig(r, &c.LocalChanConfig); err != nil {
return nil, err
}
// Finally, we'll attempt to read the next unrevoked commitment point
// for the remote party. If we closed the channel before receiving a
// channel_ready message then this might not be present. A boolean
// indicating whether the field is present will come first.
var hasRemoteNextRevocation bool
err = ReadElements(r, &hasRemoteNextRevocation)
if err != nil {
return nil, err
}
// If this field was written, read it.
if hasRemoteNextRevocation {
err = ReadElements(r, &c.RemoteNextRevocation)
if err != nil {
return nil, err
}
}
// Check if we have a channel sync message to read.
var hasChanSyncMsg bool
err = ReadElements(r, &hasChanSyncMsg)
if err == io.EOF {
return c, nil
} else if err != nil {
return nil, err
}
// If a chan sync message is present, read it.
if hasChanSyncMsg {
// We must pass in reference to a lnwire.Message for the codec
// to support it.
var msg lnwire.Message
if err := ReadElements(r, &msg); err != nil {
return nil, err
}
chanSync, ok := msg.(*lnwire.ChannelReestablish)
if !ok {
return nil, errors.New("unable cast db Message to " +
"ChannelReestablish")
}
c.LastChanSyncMsg = chanSync
}
return c, nil
}
func writeChanConfig(b io.Writer, c *ChannelConfig) error {
return WriteElements(b,
c.DustLimit, c.MaxPendingAmount, c.ChanReserve, c.MinHTLC,
c.MaxAcceptedHtlcs, c.CsvDelay, c.MultiSigKey,
c.RevocationBasePoint, c.PaymentBasePoint, c.DelayBasePoint,
c.HtlcBasePoint,
)
}
// fundingTxPresent returns true if expect the funding transcation to be found
// on disk or already populated within the passed open channel struct.
func fundingTxPresent(channel *OpenChannel) bool {
chanType := channel.ChanType
return chanType.IsSingleFunder() && chanType.HasFundingTx() &&
channel.IsInitiator &&
!channel.hasChanStatus(ChanStatusRestored)
}
func putChanInfo(chanBucket kvdb.RwBucket, channel *OpenChannel) error {
var w bytes.Buffer
if err := WriteElements(&w,
channel.ChanType, channel.ChainHash, channel.FundingOutpoint,
channel.ShortChannelID, channel.IsPending, channel.IsInitiator,
channel.chanStatus, channel.FundingBroadcastHeight,
channel.NumConfsRequired, channel.ChannelFlags,
channel.IdentityPub, channel.Capacity, channel.TotalMSatSent,
channel.TotalMSatReceived,
); err != nil {
return err
}
// For single funder channels that we initiated, and we have the
// funding transaction, then write the funding txn.
if fundingTxPresent(channel) {
if err := WriteElement(&w, channel.FundingTxn); err != nil {
return err
}
}
if err := writeChanConfig(&w, &channel.LocalChanCfg); err != nil {
return err
}
if err := writeChanConfig(&w, &channel.RemoteChanCfg); err != nil {
return err
}
auxData := channel.extractTlvData()
if err := auxData.encode(&w); err != nil {
return fmt.Errorf("unable to encode aux data: %w", err)
}
if err := chanBucket.Put(chanInfoKey, w.Bytes()); err != nil {
return err
}
// Finally, add optional shutdown scripts for the local and remote peer if
// they are present.
if err := putOptionalUpfrontShutdownScript(
chanBucket, localUpfrontShutdownKey, channel.LocalShutdownScript,
); err != nil {
return err
}
return putOptionalUpfrontShutdownScript(
chanBucket, remoteUpfrontShutdownKey, channel.RemoteShutdownScript,
)
}
// putOptionalUpfrontShutdownScript adds a shutdown script under the key
// provided if it has a non-zero length.
func putOptionalUpfrontShutdownScript(chanBucket kvdb.RwBucket, key []byte,
script []byte) error {
// If the script is empty, we do not need to add anything.
if len(script) == 0 {
return nil
}
var w bytes.Buffer
if err := WriteElement(&w, script); err != nil {
return err
}
return chanBucket.Put(key, w.Bytes())
}
// getOptionalUpfrontShutdownScript reads the shutdown script stored under the
// key provided if it is present. Upfront shutdown scripts are optional, so the
// function returns with no error if the key is not present.
func getOptionalUpfrontShutdownScript(chanBucket kvdb.RBucket, key []byte,
script *lnwire.DeliveryAddress) error {
// Return early if the bucket does not exit, a shutdown script was not set.
bs := chanBucket.Get(key)
if bs == nil {
return nil
}
var tempScript []byte
r := bytes.NewReader(bs)
if err := ReadElement(r, &tempScript); err != nil {
return err
}
*script = tempScript
return nil
}
func serializeChanCommit(w io.Writer, c *ChannelCommitment) error {
if err := WriteElements(w,
c.CommitHeight, c.LocalLogIndex, c.LocalHtlcIndex,
c.RemoteLogIndex, c.RemoteHtlcIndex, c.LocalBalance,
c.RemoteBalance, c.CommitFee, c.FeePerKw, c.CommitTx,
c.CommitSig,
); err != nil {
return err
}
return SerializeHtlcs(w, c.Htlcs...)
}
func putChanCommitment(chanBucket kvdb.RwBucket, c *ChannelCommitment,
local bool) error {
var commitKey []byte
if local {
commitKey = append(chanCommitmentKey, byte(0x00))
} else {
commitKey = append(chanCommitmentKey, byte(0x01))
}
var b bytes.Buffer
if err := serializeChanCommit(&b, c); err != nil {
return err
}
// Before we write to disk, we'll also write our aux data as well.
auxData := c.extractTlvData()
if err := auxData.encode(&b); err != nil {
return fmt.Errorf("unable to write aux data: %w", err)
}
return chanBucket.Put(commitKey, b.Bytes())
}
func putChanCommitments(chanBucket kvdb.RwBucket, channel *OpenChannel) error {
// If this is a restored channel, then we don't have any commitments to
// write.
if channel.hasChanStatus(ChanStatusRestored) {
return nil
}
err := putChanCommitment(
chanBucket, &channel.LocalCommitment, true,
)
if err != nil {
return err
}
return putChanCommitment(
chanBucket, &channel.RemoteCommitment, false,
)
}
func putChanRevocationState(chanBucket kvdb.RwBucket, channel *OpenChannel) error {
var b bytes.Buffer
err := WriteElements(
&b, channel.RemoteCurrentRevocation, channel.RevocationProducer,
channel.RevocationStore,
)
if err != nil {
return err
}
// If the next revocation is present, which is only the case after the
// ChannelReady message has been sent, then we'll write it to disk.
if channel.RemoteNextRevocation != nil {
err = WriteElements(&b, channel.RemoteNextRevocation)
if err != nil {
return err
}
}
return chanBucket.Put(revocationStateKey, b.Bytes())
}
func readChanConfig(b io.Reader, c *ChannelConfig) error {
return ReadElements(b,
&c.DustLimit, &c.MaxPendingAmount, &c.ChanReserve,
&c.MinHTLC, &c.MaxAcceptedHtlcs, &c.CsvDelay,
&c.MultiSigKey, &c.RevocationBasePoint,
&c.PaymentBasePoint, &c.DelayBasePoint,
&c.HtlcBasePoint,
)
}
func fetchChanInfo(chanBucket kvdb.RBucket, channel *OpenChannel) error {
infoBytes := chanBucket.Get(chanInfoKey)
if infoBytes == nil {
return ErrNoChanInfoFound
}
r := bytes.NewReader(infoBytes)
if err := ReadElements(r,
&channel.ChanType, &channel.ChainHash, &channel.FundingOutpoint,
&channel.ShortChannelID, &channel.IsPending, &channel.IsInitiator,
&channel.chanStatus, &channel.FundingBroadcastHeight,
&channel.NumConfsRequired, &channel.ChannelFlags,
&channel.IdentityPub, &channel.Capacity, &channel.TotalMSatSent,
&channel.TotalMSatReceived,
); err != nil {
return err
}
// For single funder channels that we initiated and have the funding
// transaction to, read the funding txn.
if fundingTxPresent(channel) {
if err := ReadElement(r, &channel.FundingTxn); err != nil {
return err
}
}
if err := readChanConfig(r, &channel.LocalChanCfg); err != nil {
return err
}
if err := readChanConfig(r, &channel.RemoteChanCfg); err != nil {
return err
}
// Retrieve the boolean stored under lastWasRevokeKey.
lastWasRevokeBytes := chanBucket.Get(lastWasRevokeKey)
if lastWasRevokeBytes == nil {
// If nothing has been stored under this key, we store false in the
// OpenChannel struct.
channel.LastWasRevoke = false
} else {
// Otherwise, read the value into the LastWasRevoke field.
revokeReader := bytes.NewReader(lastWasRevokeBytes)
err := ReadElements(revokeReader, &channel.LastWasRevoke)
if err != nil {
return err
}
}
var auxData openChannelTlvData
if err := auxData.decode(r); err != nil {
return fmt.Errorf("unable to decode aux data: %w", err)
}
// Assign all the relevant fields from the aux data into the actual
// open channel.
channel.amendTlvData(auxData)
channel.Packager = NewChannelPackager(channel.ShortChannelID)
// Finally, read the optional shutdown scripts.
if err := getOptionalUpfrontShutdownScript(
chanBucket, localUpfrontShutdownKey, &channel.LocalShutdownScript,
); err != nil {
return err
}
return getOptionalUpfrontShutdownScript(
chanBucket, remoteUpfrontShutdownKey, &channel.RemoteShutdownScript,
)
}
func deserializeChanCommit(r io.Reader) (ChannelCommitment, error) {
var c ChannelCommitment
err := ReadElements(r,
&c.CommitHeight, &c.LocalLogIndex, &c.LocalHtlcIndex, &c.RemoteLogIndex,
&c.RemoteHtlcIndex, &c.LocalBalance, &c.RemoteBalance,
&c.CommitFee, &c.FeePerKw, &c.CommitTx, &c.CommitSig,
)
if err != nil {
return c, err
}
c.Htlcs, err = DeserializeHtlcs(r)
if err != nil {
return c, err
}
return c, nil
}
func fetchChanCommitment(chanBucket kvdb.RBucket,
local bool) (ChannelCommitment, error) {
var commitKey []byte
if local {
commitKey = append(chanCommitmentKey, byte(0x00))
} else {
commitKey = append(chanCommitmentKey, byte(0x01))
}
commitBytes := chanBucket.Get(commitKey)
if commitBytes == nil {
return ChannelCommitment{}, ErrNoCommitmentsFound
}
r := bytes.NewReader(commitBytes)
chanCommit, err := deserializeChanCommit(r)
if err != nil {
return ChannelCommitment{}, fmt.Errorf("unable to decode "+
"chan commit: %w", err)
}
// We'll also check to see if we have any aux data stored as the end of
// the stream.
var auxData commitTlvData
if err := auxData.decode(r); err != nil {
return ChannelCommitment{}, fmt.Errorf("unable to decode "+
"chan aux data: %w", err)
}
chanCommit.amendTlvData(auxData)
return chanCommit, nil
}
func fetchChanCommitments(chanBucket kvdb.RBucket, channel *OpenChannel) error {
var err error
// If this is a restored channel, then we don't have any commitments to
// read.
if channel.hasChanStatus(ChanStatusRestored) {
return nil
}
channel.LocalCommitment, err = fetchChanCommitment(chanBucket, true)
if err != nil {
return err
}
channel.RemoteCommitment, err = fetchChanCommitment(chanBucket, false)
if err != nil {
return err
}
return nil
}
func fetchChanRevocationState(chanBucket kvdb.RBucket, channel *OpenChannel) error {
revBytes := chanBucket.Get(revocationStateKey)
if revBytes == nil {
return ErrNoRevocationsFound
}
r := bytes.NewReader(revBytes)
err := ReadElements(
r, &channel.RemoteCurrentRevocation, &channel.RevocationProducer,
&channel.RevocationStore,
)
if err != nil {
return err
}
// If there aren't any bytes left in the buffer, then we don't yet have
// the next remote revocation, so we can exit early here.
if r.Len() == 0 {
return nil
}
// Otherwise we'll read the next revocation for the remote party which
// is always the last item within the buffer.
return ReadElements(r, &channel.RemoteNextRevocation)
}
func deleteOpenChannel(chanBucket kvdb.RwBucket) error {
if err := chanBucket.Delete(chanInfoKey); err != nil {
return err
}
err := chanBucket.Delete(append(chanCommitmentKey, byte(0x00)))
if err != nil {
return err
}
err = chanBucket.Delete(append(chanCommitmentKey, byte(0x01)))
if err != nil {
return err
}
if err := chanBucket.Delete(revocationStateKey); err != nil {
return err
}
if diff := chanBucket.Get(commitDiffKey); diff != nil {
return chanBucket.Delete(commitDiffKey)
}
return nil
}
// makeLogKey converts a uint64 into an 8 byte array.
func makeLogKey(updateNum uint64) [8]byte {
var key [8]byte
byteOrder.PutUint64(key[:], updateNum)
return key
}
func fetchThawHeight(chanBucket kvdb.RBucket) (uint32, error) {
var height uint32
heightBytes := chanBucket.Get(frozenChanKey)
heightReader := bytes.NewReader(heightBytes)
if err := ReadElements(heightReader, &height); err != nil {
return 0, err
}
return height, nil
}
func storeThawHeight(chanBucket kvdb.RwBucket, height uint32) error {
var heightBuf bytes.Buffer
if err := WriteElements(&heightBuf, height); err != nil {
return err
}
return chanBucket.Put(frozenChanKey, heightBuf.Bytes())
}
func deleteThawHeight(chanBucket kvdb.RwBucket) error {
return chanBucket.Delete(frozenChanKey)
}
// keyLocRecord is a wrapper struct around keychain.KeyLocator to implement the
// tlv.RecordProducer interface.
type keyLocRecord struct {
keychain.KeyLocator
}
// Record creates a Record out of a KeyLocator using the passed Type and the
// EKeyLocator and DKeyLocator functions. The size will always be 8 as
// KeyFamily is uint32 and the Index is uint32.
//
// NOTE: This is part of the tlv.RecordProducer interface.
func (k *keyLocRecord) Record() tlv.Record {
// Note that we set the type here as zero, as when used with a
// tlv.RecordT, the type param will be used as the type.
return tlv.MakeStaticRecord(
0, &k.KeyLocator, 8, EKeyLocator, DKeyLocator,
)
}
// EKeyLocator is an encoder for keychain.KeyLocator.
func EKeyLocator(w io.Writer, val interface{}, buf *[8]byte) error {
if v, ok := val.(*keychain.KeyLocator); ok {
err := tlv.EUint32T(w, uint32(v.Family), buf)
if err != nil {
return err
}
return tlv.EUint32T(w, v.Index, buf)
}
return tlv.NewTypeForEncodingErr(val, "keychain.KeyLocator")
}
// DKeyLocator is a decoder for keychain.KeyLocator.
func DKeyLocator(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
if v, ok := val.(*keychain.KeyLocator); ok {
var family uint32
err := tlv.DUint32(r, &family, buf, 4)
if err != nil {
return err
}
v.Family = keychain.KeyFamily(family)
return tlv.DUint32(r, &v.Index, buf, 4)
}
return tlv.NewTypeForDecodingErr(val, "keychain.KeyLocator", l, 8)
}
// ShutdownInfo contains various info about the shutdown initiation of a
// channel.
type ShutdownInfo struct {
// DeliveryScript is the address that we have included in any previous
// Shutdown message for a particular channel and so should include in
// any future re-sends of the Shutdown message.
DeliveryScript tlv.RecordT[tlv.TlvType0, lnwire.DeliveryAddress]
// LocalInitiator is true if we sent a Shutdown message before ever
// receiving a Shutdown message from the remote peer.
LocalInitiator tlv.RecordT[tlv.TlvType1, bool]
}
// NewShutdownInfo constructs a new ShutdownInfo object.
func NewShutdownInfo(deliveryScript lnwire.DeliveryAddress,
locallyInitiated bool) *ShutdownInfo {
return &ShutdownInfo{
DeliveryScript: tlv.NewRecordT[tlv.TlvType0](deliveryScript),
LocalInitiator: tlv.NewPrimitiveRecord[tlv.TlvType1](
locallyInitiated,
),
}
}
// Closer identifies the ChannelParty that initiated the coop-closure process.
func (s ShutdownInfo) Closer() lntypes.ChannelParty {
if s.LocalInitiator.Val {
return lntypes.Local
}
return lntypes.Remote
}
// encode serialises the ShutdownInfo to the given io.Writer.
func (s *ShutdownInfo) encode(w io.Writer) error {
records := []tlv.Record{
s.DeliveryScript.Record(),
s.LocalInitiator.Record(),
}
stream, err := tlv.NewStream(records...)
if err != nil {
return err
}
return stream.Encode(w)
}
// decodeShutdownInfo constructs a ShutdownInfo struct by decoding the given
// byte slice.
func decodeShutdownInfo(b []byte) (*ShutdownInfo, error) {
tlvStream := lnwire.ExtraOpaqueData(b)
var info ShutdownInfo
records := []tlv.RecordProducer{
&info.DeliveryScript,
&info.LocalInitiator,
}
_, err := tlvStream.ExtractRecords(records...)
return &info, err
}
package channeldb
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"net"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/shachain"
"github.com/lightningnetwork/lnd/tlv"
)
// UnknownElementType is an error returned when the codec is unable to encode or
// decode a particular type.
type UnknownElementType struct {
method string
element interface{}
}
// NewUnknownElementType creates a new UnknownElementType error from the passed
// method name and element.
func NewUnknownElementType(method string, el interface{}) UnknownElementType {
return UnknownElementType{method: method, element: el}
}
// Error returns the name of the method that encountered the error, as well as
// the type that was unsupported.
func (e UnknownElementType) Error() string {
return fmt.Sprintf("Unknown type in %s: %T", e.method, e.element)
}
// WriteElement is a one-stop shop to write the big endian representation of
// any element which is to be serialized for storage on disk. The passed
// io.Writer should be backed by an appropriately sized byte slice, or be able
// to dynamically expand to accommodate additional data.
func WriteElement(w io.Writer, element interface{}) error {
switch e := element.(type) {
case keychain.KeyDescriptor:
if err := binary.Write(w, byteOrder, e.Family); err != nil {
return err
}
if err := binary.Write(w, byteOrder, e.Index); err != nil {
return err
}
if e.PubKey != nil {
if err := binary.Write(w, byteOrder, true); err != nil {
return fmt.Errorf("error writing serialized "+
"element: %w", err)
}
return WriteElement(w, e.PubKey)
}
return binary.Write(w, byteOrder, false)
case ChannelType:
var buf [8]byte
if err := tlv.WriteVarInt(w, uint64(e), &buf); err != nil {
return err
}
case chainhash.Hash:
if _, err := w.Write(e[:]); err != nil {
return err
}
case wire.OutPoint:
return graphdb.WriteOutpoint(w, &e)
case lnwire.ShortChannelID:
if err := binary.Write(w, byteOrder, e.ToUint64()); err != nil {
return err
}
case lnwire.ChannelID:
if _, err := w.Write(e[:]); err != nil {
return err
}
case int64, uint64:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case uint32:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case int32:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case uint16:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case uint8:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case bool:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case btcutil.Amount:
if err := binary.Write(w, byteOrder, uint64(e)); err != nil {
return err
}
case lnwire.MilliSatoshi:
if err := binary.Write(w, byteOrder, uint64(e)); err != nil {
return err
}
case *btcec.PrivateKey:
b := e.Serialize()
if _, err := w.Write(b); err != nil {
return err
}
case *btcec.PublicKey:
b := e.SerializeCompressed()
if _, err := w.Write(b); err != nil {
return err
}
case shachain.Producer:
return e.Encode(w)
case shachain.Store:
return e.Encode(w)
case *wire.MsgTx:
return e.Serialize(w)
case [32]byte:
if _, err := w.Write(e[:]); err != nil {
return err
}
case []byte:
if err := wire.WriteVarBytes(w, 0, e); err != nil {
return err
}
case lnwire.Message:
var msgBuf bytes.Buffer
if _, err := lnwire.WriteMessage(&msgBuf, e, 0); err != nil {
return err
}
msgLen := uint16(len(msgBuf.Bytes()))
if err := WriteElements(w, msgLen); err != nil {
return err
}
if _, err := w.Write(msgBuf.Bytes()); err != nil {
return err
}
case ChannelStatus:
var buf [8]byte
if err := tlv.WriteVarInt(w, uint64(e), &buf); err != nil {
return err
}
case ClosureType:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case paymentIndexType:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case lnwire.FundingFlag:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case net.Addr:
if err := graphdb.SerializeAddr(w, e); err != nil {
return err
}
case []net.Addr:
if err := WriteElement(w, uint32(len(e))); err != nil {
return err
}
for _, addr := range e {
if err := graphdb.SerializeAddr(w, addr); err != nil {
return err
}
}
default:
return UnknownElementType{"WriteElement", e}
}
return nil
}
// WriteElements is writes each element in the elements slice to the passed
// io.Writer using WriteElement.
func WriteElements(w io.Writer, elements ...interface{}) error {
for _, element := range elements {
err := WriteElement(w, element)
if err != nil {
return err
}
}
return nil
}
// ReadElement is a one-stop utility function to deserialize any datastructure
// encoded using the serialization format of the database.
func ReadElement(r io.Reader, element interface{}) error {
switch e := element.(type) {
case *keychain.KeyDescriptor:
if err := binary.Read(r, byteOrder, &e.Family); err != nil {
return err
}
if err := binary.Read(r, byteOrder, &e.Index); err != nil {
return err
}
var hasPubKey bool
if err := binary.Read(r, byteOrder, &hasPubKey); err != nil {
return err
}
if hasPubKey {
return ReadElement(r, &e.PubKey)
}
case *ChannelType:
var buf [8]byte
ctype, err := tlv.ReadVarInt(r, &buf)
if err != nil {
return err
}
*e = ChannelType(ctype)
case *chainhash.Hash:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *wire.OutPoint:
return graphdb.ReadOutpoint(r, e)
case *lnwire.ShortChannelID:
var a uint64
if err := binary.Read(r, byteOrder, &a); err != nil {
return err
}
*e = lnwire.NewShortChanIDFromInt(a)
case *lnwire.ChannelID:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *int64, *uint64:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *uint32:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *int32:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *uint16:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *uint8:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *bool:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *btcutil.Amount:
var a uint64
if err := binary.Read(r, byteOrder, &a); err != nil {
return err
}
*e = btcutil.Amount(a)
case *lnwire.MilliSatoshi:
var a uint64
if err := binary.Read(r, byteOrder, &a); err != nil {
return err
}
*e = lnwire.MilliSatoshi(a)
case **btcec.PrivateKey:
var b [btcec.PrivKeyBytesLen]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
priv, _ := btcec.PrivKeyFromBytes(b[:])
*e = priv
case **btcec.PublicKey:
var b [btcec.PubKeyBytesLenCompressed]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
pubKey, err := btcec.ParsePubKey(b[:])
if err != nil {
return err
}
*e = pubKey
case *shachain.Producer:
var root [32]byte
if _, err := io.ReadFull(r, root[:]); err != nil {
return err
}
// TODO(roasbeef): remove
producer, err := shachain.NewRevocationProducerFromBytes(root[:])
if err != nil {
return err
}
*e = producer
case *shachain.Store:
store, err := shachain.NewRevocationStoreFromBytes(r)
if err != nil {
return err
}
*e = store
case **wire.MsgTx:
tx := wire.NewMsgTx(2)
if err := tx.Deserialize(r); err != nil {
return err
}
*e = tx
case *[32]byte:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *[]byte:
bytes, err := wire.ReadVarBytes(r, 0, 66000, "[]byte")
if err != nil {
return err
}
*e = bytes
case *lnwire.Message:
var msgLen uint16
if err := ReadElement(r, &msgLen); err != nil {
return err
}
msgReader := io.LimitReader(r, int64(msgLen))
msg, err := lnwire.ReadMessage(msgReader, 0)
if err != nil {
return err
}
*e = msg
case *ChannelStatus:
var buf [8]byte
status, err := tlv.ReadVarInt(r, &buf)
if err != nil {
return err
}
*e = ChannelStatus(status)
case *ClosureType:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *paymentIndexType:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *lnwire.FundingFlag:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *net.Addr:
addr, err := graphdb.DeserializeAddr(r)
if err != nil {
return err
}
*e = addr
case *[]net.Addr:
var numAddrs uint32
if err := ReadElement(r, &numAddrs); err != nil {
return err
}
*e = make([]net.Addr, numAddrs)
for i := uint32(0); i < numAddrs; i++ {
addr, err := graphdb.DeserializeAddr(r)
if err != nil {
return err
}
(*e)[i] = addr
}
default:
return UnknownElementType{"ReadElement", e}
}
return nil
}
// ReadElements deserializes a variable number of elements into the passed
// io.Reader, with each element being deserialized according to the ReadElement
// function.
func ReadElements(r io.Reader, elements ...interface{}) error {
for _, element := range elements {
err := ReadElement(r, element)
if err != nil {
return err
}
}
return nil
}
package channeldb
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"os"
"testing"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/go-errors/errors"
mig "github.com/lightningnetwork/lnd/channeldb/migration"
"github.com/lightningnetwork/lnd/channeldb/migration12"
"github.com/lightningnetwork/lnd/channeldb/migration13"
"github.com/lightningnetwork/lnd/channeldb/migration16"
"github.com/lightningnetwork/lnd/channeldb/migration20"
"github.com/lightningnetwork/lnd/channeldb/migration21"
"github.com/lightningnetwork/lnd/channeldb/migration23"
"github.com/lightningnetwork/lnd/channeldb/migration24"
"github.com/lightningnetwork/lnd/channeldb/migration25"
"github.com/lightningnetwork/lnd/channeldb/migration26"
"github.com/lightningnetwork/lnd/channeldb/migration27"
"github.com/lightningnetwork/lnd/channeldb/migration29"
"github.com/lightningnetwork/lnd/channeldb/migration30"
"github.com/lightningnetwork/lnd/channeldb/migration31"
"github.com/lightningnetwork/lnd/channeldb/migration32"
"github.com/lightningnetwork/lnd/channeldb/migration33"
"github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
"github.com/lightningnetwork/lnd/clock"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
const (
dbName = "channel.db"
)
var (
// ErrDryRunMigrationOK signals that a migration executed successful,
// but we intentionally did not commit the result.
ErrDryRunMigrationOK = errors.New("dry run migration successful")
// ErrFinalHtlcsBucketNotFound signals that the top-level final htlcs
// bucket does not exist.
ErrFinalHtlcsBucketNotFound = errors.New("final htlcs bucket not " +
"found")
// ErrFinalChannelBucketNotFound signals that the channel bucket for
// final htlc outcomes does not exist.
ErrFinalChannelBucketNotFound = errors.New("final htlcs channel " +
"bucket not found")
)
// migration is a function which takes a prior outdated version of the database
// instances and mutates the key/bucket structure to arrive at a more
// up-to-date version of the database.
type migration func(tx kvdb.RwTx) error
// mandatoryVersion defines a db version that must be applied before the lnd
// starts.
type mandatoryVersion struct {
number uint32
migration migration
}
// MigrationConfig is an interface combines the config interfaces of all
// optional migrations.
type MigrationConfig interface {
migration30.MigrateRevLogConfig
}
// MigrationConfigImpl is a super set of all the various migration configs and
// an implementation of MigrationConfig.
type MigrationConfigImpl struct {
migration30.MigrateRevLogConfigImpl
}
// optionalMigration defines an optional migration function. When a migration
// is optional, it usually involves a large scale of changes that might touch
// millions of keys. Due to OOM concern, the update cannot be safely done
// within one db transaction. Thus, for optional migrations, they must take the
// db backend and construct transactions as needed.
type optionalMigration func(db kvdb.Backend, cfg MigrationConfig) error
// optionalVersion defines a db version that can be optionally applied. When
// applying migrations, we must apply all the mandatory migrations first before
// attempting optional ones.
type optionalVersion struct {
name string
migration optionalMigration
}
var (
// dbVersions is storing all mandatory versions of database. If current
// version of database don't match with latest version this list will
// be used for retrieving all migration function that are need to apply
// to the current db.
dbVersions = []mandatoryVersion{
{
// The base DB version requires no migration.
number: 0,
migration: nil,
},
{
// The version of the database where two new indexes
// for the update time of node and channel updates were
// added.
number: 1,
migration: migration_01_to_11.MigrateNodeAndEdgeUpdateIndex,
},
{
// The DB version that added the invoice event time
// series.
number: 2,
migration: migration_01_to_11.MigrateInvoiceTimeSeries,
},
{
// The DB version that updated the embedded invoice in
// outgoing payments to match the new format.
number: 3,
migration: migration_01_to_11.MigrateInvoiceTimeSeriesOutgoingPayments,
},
{
// The version of the database where every channel
// always has two entries in the edges bucket. If
// a policy is unknown, this will be represented
// by a special byte sequence.
number: 4,
migration: migration_01_to_11.MigrateEdgePolicies,
},
{
// The DB version where we persist each attempt to send
// an HTLC to a payment hash, and track whether the
// payment is in-flight, succeeded, or failed.
number: 5,
migration: migration_01_to_11.PaymentStatusesMigration,
},
{
// The DB version that properly prunes stale entries
// from the edge update index.
number: 6,
migration: migration_01_to_11.MigratePruneEdgeUpdateIndex,
},
{
// The DB version that migrates the ChannelCloseSummary
// to a format where optional fields are indicated with
// boolean flags.
number: 7,
migration: migration_01_to_11.MigrateOptionalChannelCloseSummaryFields,
},
{
// The DB version that changes the gossiper's message
// store keys to account for the message's type and
// ShortChannelID.
number: 8,
migration: migration_01_to_11.MigrateGossipMessageStoreKeys,
},
{
// The DB version where the payments and payment
// statuses are moved to being stored in a combined
// bucket.
number: 9,
migration: migration_01_to_11.MigrateOutgoingPayments,
},
{
// The DB version where we started to store legacy
// payload information for all routes, as well as the
// optional TLV records.
number: 10,
migration: migration_01_to_11.MigrateRouteSerialization,
},
{
// Add invoice htlc and cltv delta fields.
number: 11,
migration: migration_01_to_11.MigrateInvoices,
},
{
// Migrate to TLV invoice bodies, add payment address
// and features, remove receipt.
number: 12,
migration: migration12.MigrateInvoiceTLV,
},
{
// Migrate to multi-path payments.
number: 13,
migration: migration13.MigrateMPP,
},
{
// Initialize payment address index and begin using it
// as the default index, falling back to payment hash
// index.
number: 14,
migration: mig.CreateTLB(payAddrIndexBucket),
},
{
// Initialize payment index bucket which will be used
// to index payments by sequence number. This index will
// be used to allow more efficient ListPayments queries.
number: 15,
migration: mig.CreateTLB(paymentsIndexBucket),
},
{
// Add our existing payments to the index bucket created
// in migration 15.
number: 16,
migration: migration16.MigrateSequenceIndex,
},
{
// Create a top level bucket which will store extra
// information about channel closes.
number: 17,
migration: mig.CreateTLB(closeSummaryBucket),
},
{
// Create a top level bucket which holds information
// about our peers.
number: 18,
migration: mig.CreateTLB(peersBucket),
},
{
// Create a top level bucket which holds outpoint
// information.
number: 19,
migration: mig.CreateTLB(outpointBucket),
},
{
// Migrate some data to the outpoint index.
number: 20,
migration: migration20.MigrateOutpointIndex,
},
{
// Migrate to length prefixed wire messages everywhere
// in the database.
number: 21,
migration: migration21.MigrateDatabaseWireMessages,
},
{
// Initialize set id index so that invoices can be
// queried by individual htlc sets.
number: 22,
migration: mig.CreateTLB(setIDIndexBucket),
},
{
number: 23,
migration: migration23.MigrateHtlcAttempts,
},
{
// Remove old forwarding packages of closed channels.
number: 24,
migration: migration24.MigrateFwdPkgCleanup,
},
{
// Save the initial local/remote balances in channel
// info.
number: 25,
migration: migration25.MigrateInitialBalances,
},
{
// Migrate the initial local/remote balance fields into
// tlv records.
number: 26,
migration: migration26.MigrateBalancesToTlvRecords,
},
{
// Patch the initial local/remote balance fields with
// empty values for historical channels.
number: 27,
migration: migration27.MigrateHistoricalBalances,
},
{
number: 28,
migration: mig.CreateTLB(chanIDBucket),
},
{
number: 29,
migration: migration29.MigrateChanID,
},
{
// Removes the "sweeper-last-tx" bucket. Although we
// do not have a mandatory version 30 we skip this
// version because its naming is already used for the
// first optional migration.
number: 31,
migration: migration31.DeleteLastPublishedTxTLB,
},
{
number: 32,
migration: migration32.MigrateMCRouteSerialisation,
},
{
number: 33,
migration: migration33.MigrateMCStoreNameSpacedResults,
},
}
// optionalVersions stores all optional migrations that are applied
// after dbVersions.
//
// NOTE: optional migrations must be fault-tolerant and re-run already
// migrated data must be noop, which means the migration must be able
// to determine its state.
optionalVersions = []optionalVersion{
{
name: "prune revocation log",
migration: func(db kvdb.Backend,
cfg MigrationConfig) error {
return migration30.MigrateRevocationLog(db, cfg)
},
},
}
// Big endian is the preferred byte order, due to cursor scans over
// integer keys iterating in order.
byteOrder = binary.BigEndian
// channelOpeningStateBucket is the database bucket used to store the
// channelOpeningState for each channel that is currently in the process
// of being opened.
channelOpeningStateBucket = []byte("channelOpeningState")
)
// DB is the primary datastore for the lnd daemon. The database stores
// information related to nodes, routing data, open/closed channels, fee
// schedules, and reputation data.
type DB struct {
kvdb.Backend
// channelStateDB separates all DB operations on channel state.
channelStateDB *ChannelStateDB
dbPath string
clock clock.Clock
dryRun bool
keepFailedPaymentAttempts bool
storeFinalHtlcResolutions bool
// noRevLogAmtData if true, means that commitment transaction amount
// data should not be stored in the revocation log.
noRevLogAmtData bool
}
// OpenForTesting opens or creates a channeldb to be used for tests. Any
// necessary schemas migrations due to updates will take place as necessary.
func OpenForTesting(t testing.TB, dbPath string,
modifiers ...OptionModifier) *DB {
backend, err := kvdb.GetBoltBackend(&kvdb.BoltBackendConfig{
DBPath: dbPath,
DBFileName: dbName,
NoFreelistSync: true,
AutoCompact: false,
AutoCompactMinAge: kvdb.DefaultBoltAutoCompactMinAge,
DBTimeout: kvdb.DefaultDBTimeout,
})
require.NoError(t, err)
db, err := CreateWithBackend(backend, modifiers...)
require.NoError(t, err)
db.dbPath = dbPath
t.Cleanup(func() {
require.NoError(t, db.Close())
})
return db
}
// CreateWithBackend creates channeldb instance using the passed kvdb.Backend.
// Any necessary schemas migrations due to updates will take place as necessary.
func CreateWithBackend(backend kvdb.Backend, modifiers ...OptionModifier) (*DB,
error) {
opts := DefaultOptions()
for _, modifier := range modifiers {
modifier(&opts)
}
if !opts.NoMigration {
if err := initChannelDB(backend); err != nil {
return nil, err
}
}
chanDB := &DB{
Backend: backend,
channelStateDB: &ChannelStateDB{
linkNodeDB: &LinkNodeDB{
backend: backend,
},
backend: backend,
},
clock: opts.clock,
dryRun: opts.dryRun,
keepFailedPaymentAttempts: opts.keepFailedPaymentAttempts,
storeFinalHtlcResolutions: opts.storeFinalHtlcResolutions,
noRevLogAmtData: opts.NoRevLogAmtData,
}
// Set the parent pointer (only used in tests).
chanDB.channelStateDB.parent = chanDB
// Synchronize the version of database and apply migrations if needed.
if !opts.NoMigration {
if err := chanDB.syncVersions(dbVersions); err != nil {
backend.Close()
return nil, err
}
// Grab the optional migration config.
omc := opts.OptionalMiragtionConfig
if err := chanDB.applyOptionalVersions(omc); err != nil {
backend.Close()
return nil, err
}
}
return chanDB, nil
}
// Path returns the file path to the channel database.
func (d *DB) Path() string {
return d.dbPath
}
var dbTopLevelBuckets = [][]byte{
openChannelBucket,
closedChannelBucket,
forwardingLogBucket,
fwdPackagesKey,
invoiceBucket,
payAddrIndexBucket,
setIDIndexBucket,
paymentsIndexBucket,
peersBucket,
nodeInfoBucket,
metaBucket,
closeSummaryBucket,
outpointBucket,
chanIDBucket,
historicalChannelBucket,
}
// Wipe completely deletes all saved state within all used buckets within the
// database. The deletion is done in a single transaction, therefore this
// operation is fully atomic.
func (d *DB) Wipe() error {
err := kvdb.Update(d, func(tx kvdb.RwTx) error {
for _, tlb := range dbTopLevelBuckets {
err := tx.DeleteTopLevelBucket(tlb)
if err != nil && err != kvdb.ErrBucketNotFound {
return err
}
}
return nil
}, func() {})
if err != nil {
return err
}
return initChannelDB(d.Backend)
}
// initChannelDB creates and initializes a fresh version of channeldb. In the
// case that the target path has not yet been created or doesn't yet exist, then
// the path is created. Additionally, all required top-level buckets used within
// the database are created.
func initChannelDB(db kvdb.Backend) error {
err := kvdb.Update(db, func(tx kvdb.RwTx) error {
// Check if DB was marked as inactive with a tomb stone.
if err := EnsureNoTombstone(tx); err != nil {
return err
}
meta := &Meta{}
// Check if DB is already initialized.
err := FetchMeta(meta, tx)
if err == nil {
return nil
}
for _, tlb := range dbTopLevelBuckets {
if _, err := tx.CreateTopLevelBucket(tlb); err != nil {
return err
}
}
meta.DbVersionNumber = getLatestDBVersion(dbVersions)
return putMeta(meta, tx)
}, func() {})
if err != nil {
return fmt.Errorf("unable to create new channeldb: %w", err)
}
return nil
}
// fileExists returns true if the file exists, and false otherwise.
func fileExists(path string) bool {
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
// ChannelStateDB is a database that keeps track of all channel state.
type ChannelStateDB struct {
// linkNodeDB separates all DB operations on LinkNodes.
linkNodeDB *LinkNodeDB
// parent holds a pointer to the "main" channeldb.DB object. This is
// only used for testing and should never be used in production code.
// For testing use the ChannelStateDB.GetParentDB() function to retrieve
// this pointer.
parent *DB
// backend points to the actual backend holding the channel state
// database. This may be a real backend or a cache middleware.
backend kvdb.Backend
}
// GetParentDB returns the "main" channeldb.DB object that is the owner of this
// ChannelStateDB instance. Use this function only in tests where passing around
// pointers makes testing less readable. Never to be used in production code!
func (c *ChannelStateDB) GetParentDB() *DB {
return c.parent
}
// LinkNodeDB returns the current instance of the link node database.
func (c *ChannelStateDB) LinkNodeDB() *LinkNodeDB {
return c.linkNodeDB
}
// FetchOpenChannels starts a new database transaction and returns all stored
// currently active/open channels associated with the target nodeID. In the case
// that no active channels are known to have been created with this node, then a
// zero-length slice is returned.
func (c *ChannelStateDB) FetchOpenChannels(nodeID *btcec.PublicKey) (
[]*OpenChannel, error) {
var channels []*OpenChannel
err := kvdb.View(c.backend, func(tx kvdb.RTx) error {
var err error
channels, err = c.fetchOpenChannels(tx, nodeID)
return err
}, func() {
channels = nil
})
return channels, err
}
// fetchOpenChannels uses and existing database transaction and returns all
// stored currently active/open channels associated with the target nodeID. In
// the case that no active channels are known to have been created with this
// node, then a zero-length slice is returned.
func (c *ChannelStateDB) fetchOpenChannels(tx kvdb.RTx,
nodeID *btcec.PublicKey) ([]*OpenChannel, error) {
// Get the bucket dedicated to storing the metadata for open channels.
openChanBucket := tx.ReadBucket(openChannelBucket)
if openChanBucket == nil {
return nil, nil
}
// Within this top level bucket, fetch the bucket dedicated to storing
// open channel data specific to the remote node.
pub := nodeID.SerializeCompressed()
nodeChanBucket := openChanBucket.NestedReadBucket(pub)
if nodeChanBucket == nil {
return nil, nil
}
// Next, we'll need to go down an additional layer in order to retrieve
// the channels for each chain the node knows of.
var channels []*OpenChannel
err := nodeChanBucket.ForEach(func(chainHash, v []byte) error {
// If there's a value, it's not a bucket so ignore it.
if v != nil {
return nil
}
// If we've found a valid chainhash bucket, then we'll retrieve
// that so we can extract all the channels.
chainBucket := nodeChanBucket.NestedReadBucket(chainHash)
if chainBucket == nil {
return fmt.Errorf("unable to read bucket for chain=%x",
chainHash[:])
}
// Finally, we both of the necessary buckets retrieved, fetch
// all the active channels related to this node.
nodeChannels, err := c.fetchNodeChannels(chainBucket)
if err != nil {
return fmt.Errorf("unable to read channel for "+
"chain_hash=%x, node_key=%x: %v",
chainHash[:], pub, err)
}
channels = append(channels, nodeChannels...)
return nil
})
return channels, err
}
// fetchNodeChannels retrieves all active channels from the target chainBucket
// which is under a node's dedicated channel bucket. This function is typically
// used to fetch all the active channels related to a particular node.
func (c *ChannelStateDB) fetchNodeChannels(chainBucket kvdb.RBucket) (
[]*OpenChannel, error) {
var channels []*OpenChannel
// A node may have channels on several chains, so for each known chain,
// we'll extract all the channels.
err := chainBucket.ForEach(func(chanPoint, v []byte) error {
// If there's a value, it's not a bucket so ignore it.
if v != nil {
return nil
}
// Once we've found a valid channel bucket, we'll extract it
// from the node's chain bucket.
chanBucket := chainBucket.NestedReadBucket(chanPoint)
var outPoint wire.OutPoint
err := graphdb.ReadOutpoint(
bytes.NewReader(chanPoint), &outPoint,
)
if err != nil {
return err
}
oChannel, err := fetchOpenChannel(chanBucket, &outPoint)
if err != nil {
return fmt.Errorf("unable to read channel data for "+
"chan_point=%v: %w", outPoint, err)
}
oChannel.Db = c
channels = append(channels, oChannel)
return nil
})
if err != nil {
return nil, err
}
return channels, nil
}
// FetchChannel attempts to locate a channel specified by the passed channel
// point. If the channel cannot be found, then an error will be returned.
func (c *ChannelStateDB) FetchChannel(chanPoint wire.OutPoint) (*OpenChannel,
error) {
var targetChanPoint bytes.Buffer
err := graphdb.WriteOutpoint(&targetChanPoint, &chanPoint)
if err != nil {
return nil, err
}
targetChanPointBytes := targetChanPoint.Bytes()
selector := func(chainBkt walletdb.ReadBucket) ([]byte, *wire.OutPoint,
error) {
return targetChanPointBytes, &chanPoint, nil
}
return c.channelScanner(nil, selector)
}
// FetchChannelByID attempts to locate a channel specified by the passed channel
// ID. If the channel cannot be found, then an error will be returned.
// Optionally an existing db tx can be supplied.
func (c *ChannelStateDB) FetchChannelByID(tx kvdb.RTx, id lnwire.ChannelID) (
*OpenChannel, error) {
selector := func(chainBkt walletdb.ReadBucket) ([]byte, *wire.OutPoint,
error) {
var (
targetChanPointBytes []byte
targetChanPoint *wire.OutPoint
// errChanFound is used to signal that the channel has
// been found so that iteration through the DB buckets
// can stop.
errChanFound = errors.New("channel found")
)
err := chainBkt.ForEach(func(k, _ []byte) error {
var outPoint wire.OutPoint
err := graphdb.ReadOutpoint(
bytes.NewReader(k), &outPoint,
)
if err != nil {
return err
}
chanID := lnwire.NewChanIDFromOutPoint(outPoint)
if chanID != id {
return nil
}
targetChanPoint = &outPoint
targetChanPointBytes = k
return errChanFound
})
if err != nil && !errors.Is(err, errChanFound) {
return nil, nil, err
}
if targetChanPoint == nil {
return nil, nil, ErrChannelNotFound
}
return targetChanPointBytes, targetChanPoint, nil
}
return c.channelScanner(tx, selector)
}
// ChanCount is used by the server in determining access control.
type ChanCount struct {
HasOpenOrClosedChan bool
PendingOpenCount uint64
}
// FetchPermAndTempPeers returns a map where the key is the remote node's
// public key and the value is a struct that has a tally of the pending-open
// channels and whether the peer has an open or closed channel with us.
func (c *ChannelStateDB) FetchPermAndTempPeers(
chainHash []byte) (map[string]ChanCount, error) {
peerCounts := make(map[string]ChanCount)
err := kvdb.View(c.backend, func(tx kvdb.RTx) error {
openChanBucket := tx.ReadBucket(openChannelBucket)
if openChanBucket == nil {
return ErrNoChanDBExists
}
openChanErr := openChanBucket.ForEach(func(nodePub,
v []byte) error {
// If there is a value, this is not a bucket.
if v != nil {
return nil
}
nodeChanBucket := openChanBucket.NestedReadBucket(
nodePub,
)
if nodeChanBucket == nil {
return nil
}
chainBucket := nodeChanBucket.NestedReadBucket(
chainHash,
)
if chainBucket == nil {
return fmt.Errorf("no chain bucket exists")
}
var isPermPeer bool
var pendingOpenCount uint64
internalErr := chainBucket.ForEach(func(chanPoint,
val []byte) error {
// If there is a value, this is not a bucket.
if val != nil {
return nil
}
chanBucket := chainBucket.NestedReadBucket(
chanPoint,
)
if chanBucket == nil {
return nil
}
var op wire.OutPoint
readErr := graphdb.ReadOutpoint(
bytes.NewReader(chanPoint), &op,
)
if readErr != nil {
return readErr
}
// We need to go through each channel and look
// at the IsPending status.
openChan, err := fetchOpenChannel(
chanBucket, &op,
)
if err != nil {
return err
}
if openChan.IsPending {
// Add to the pending-open count since
// this is a temp peer.
pendingOpenCount++
return nil
}
// Since IsPending is false, this is a perm
// peer.
isPermPeer = true
return nil
})
if internalErr != nil {
return internalErr
}
peerCount := ChanCount{
HasOpenOrClosedChan: isPermPeer,
PendingOpenCount: pendingOpenCount,
}
peerCounts[string(nodePub)] = peerCount
return nil
})
if openChanErr != nil {
return openChanErr
}
// Now check the closed channel bucket.
historicalChanBucket := tx.ReadBucket(historicalChannelBucket)
if historicalChanBucket == nil {
return ErrNoHistoricalBucket
}
historicalErr := historicalChanBucket.ForEach(func(chanPoint,
v []byte) error {
// Parse each nested bucket and the chanInfoKey to get
// the IsPending bool. This determines whether the
// peer is protected or not.
if v != nil {
// This is not a bucket. This is currently not
// possible.
return nil
}
chanBucket := historicalChanBucket.NestedReadBucket(
chanPoint,
)
if chanBucket == nil {
// This is not possible.
return fmt.Errorf("no historical channel " +
"bucket exists")
}
var op wire.OutPoint
readErr := graphdb.ReadOutpoint(
bytes.NewReader(chanPoint), &op,
)
if readErr != nil {
return readErr
}
// This channel is closed, but the structure of the
// historical bucket is the same. This is by design,
// which means we can call fetchOpenChannel.
channel, fetchErr := fetchOpenChannel(chanBucket, &op)
if fetchErr != nil {
return fetchErr
}
// Only include this peer in the protected class if
// the closing transaction confirmed. Note that
// CloseChannel can be called in the funding manager
// while IsPending is true which is why we need this
// special-casing to not count premature funding
// manager calls to CloseChannel.
if !channel.IsPending {
// Fetch the public key of the remote node. We
// need to use the string-ified serialized,
// compressed bytes as the key.
remotePub := channel.IdentityPub
remoteSer := remotePub.SerializeCompressed()
remoteKey := string(remoteSer)
count, exists := peerCounts[remoteKey]
if exists {
count.HasOpenOrClosedChan = true
peerCounts[remoteKey] = count
} else {
peerCount := ChanCount{
HasOpenOrClosedChan: true,
}
peerCounts[remoteKey] = peerCount
}
}
return nil
})
if historicalErr != nil {
return historicalErr
}
return nil
}, func() {
clear(peerCounts)
})
return peerCounts, err
}
// channelSelector describes a function that takes a chain-hash bucket from
// within the open-channel DB and returns the wanted channel point bytes, and
// channel point. It must return the ErrChannelNotFound error if the wanted
// channel is not in the given bucket.
type channelSelector func(chainBkt walletdb.ReadBucket) ([]byte, *wire.OutPoint,
error)
// channelScanner will traverse the DB to each chain-hash bucket of each node
// pub-key bucket in the open-channel-bucket. The chanSelector will then be used
// to fetch the wanted channel outpoint from the chain bucket.
func (c *ChannelStateDB) channelScanner(tx kvdb.RTx,
chanSelect channelSelector) (*OpenChannel, error) {
var (
targetChan *OpenChannel
// errChanFound is used to signal that the channel has been
// found so that iteration through the DB buckets can stop.
errChanFound = errors.New("channel found")
)
// chanScan will traverse the following bucket structure:
// * nodePub => chainHash => chanPoint
//
// At each level we go one further, ensuring that we're traversing the
// proper key (that's actually a bucket). By only reading the bucket
// structure and skipping fully decoding each channel, we save a good
// bit of CPU as we don't need to do things like decompress public
// keys.
chanScan := func(tx kvdb.RTx) error {
// Get the bucket dedicated to storing the metadata for open
// channels.
openChanBucket := tx.ReadBucket(openChannelBucket)
if openChanBucket == nil {
return ErrNoActiveChannels
}
// Within the node channel bucket, are the set of node pubkeys
// we have channels with, we don't know the entire set, so we'll
// check them all.
return openChanBucket.ForEach(func(nodePub, v []byte) error {
// Ensure that this is a key the same size as a pubkey,
// and also that it leads directly to a bucket.
if len(nodePub) != 33 || v != nil {
return nil
}
nodeChanBucket := openChanBucket.NestedReadBucket(
nodePub,
)
if nodeChanBucket == nil {
return nil
}
// The next layer down is all the chains that this node
// has channels on with us.
return nodeChanBucket.ForEach(func(chainHash,
v []byte) error {
// If there's a value, it's not a bucket so
// ignore it.
if v != nil {
return nil
}
chainBucket := nodeChanBucket.NestedReadBucket(
chainHash,
)
if chainBucket == nil {
return fmt.Errorf("unable to read "+
"bucket for chain=%x",
chainHash)
}
// Finally, we reach the leaf bucket that stores
// all the chanPoints for this node.
targetChanBytes, chanPoint, err := chanSelect(
chainBucket,
)
if errors.Is(err, ErrChannelNotFound) {
return nil
} else if err != nil {
return err
}
chanBucket := chainBucket.NestedReadBucket(
targetChanBytes,
)
if chanBucket == nil {
return nil
}
channel, err := fetchOpenChannel(
chanBucket, chanPoint,
)
if err != nil {
return err
}
targetChan = channel
targetChan.Db = c
return errChanFound
})
})
}
var err error
if tx == nil {
err = kvdb.View(c.backend, chanScan, func() {})
} else {
err = chanScan(tx)
}
if err != nil && !errors.Is(err, errChanFound) {
return nil, err
}
if targetChan != nil {
return targetChan, nil
}
// If we can't find the channel, then we return with an error, as we
// have nothing to back up.
return nil, ErrChannelNotFound
}
// FetchAllChannels attempts to retrieve all open channels currently stored
// within the database, including pending open, fully open and channels waiting
// for a closing transaction to confirm.
func (c *ChannelStateDB) FetchAllChannels() ([]*OpenChannel, error) {
return fetchChannels(c)
}
// FetchAllOpenChannels will return all channels that have the funding
// transaction confirmed, and is not waiting for a closing transaction to be
// confirmed.
func (c *ChannelStateDB) FetchAllOpenChannels() ([]*OpenChannel, error) {
return fetchChannels(
c,
pendingChannelFilter(false),
waitingCloseFilter(false),
)
}
// FetchPendingChannels will return channels that have completed the process of
// generating and broadcasting funding transactions, but whose funding
// transactions have yet to be confirmed on the blockchain.
func (c *ChannelStateDB) FetchPendingChannels() ([]*OpenChannel, error) {
return fetchChannels(c,
pendingChannelFilter(true),
waitingCloseFilter(false),
)
}
// FetchWaitingCloseChannels will return all channels that have been opened,
// but are now waiting for a closing transaction to be confirmed.
//
// NOTE: This includes channels that are also pending to be opened.
func (c *ChannelStateDB) FetchWaitingCloseChannels() ([]*OpenChannel, error) {
return fetchChannels(
c, waitingCloseFilter(true),
)
}
// fetchChannelsFilter applies a filter to channels retrieved in fetchchannels.
// A set of filters can be combined to filter across multiple dimensions.
type fetchChannelsFilter func(channel *OpenChannel) bool
// pendingChannelFilter returns a filter based on whether channels are pending
// (ie, their funding transaction still needs to confirm). If pending is false,
// channels with confirmed funding transactions are returned.
func pendingChannelFilter(pending bool) fetchChannelsFilter {
return func(channel *OpenChannel) bool {
return channel.IsPending == pending
}
}
// waitingCloseFilter returns a filter which filters channels based on whether
// they are awaiting the confirmation of their closing transaction. If waiting
// close is true, channels that have had their closing tx broadcast are
// included. If it is false, channels that are not awaiting confirmation of
// their close transaction are returned.
func waitingCloseFilter(waitingClose bool) fetchChannelsFilter {
return func(channel *OpenChannel) bool {
// If the channel is in any other state than Default,
// then it means it is waiting to be closed.
channelWaitingClose :=
channel.ChanStatus() != ChanStatusDefault
// Include the channel if it matches the value for
// waiting close that we are filtering on.
return channelWaitingClose == waitingClose
}
}
// fetchChannels attempts to retrieve channels currently stored in the
// database. It takes a set of filters which are applied to each channel to
// obtain a set of channels with the desired set of properties. Only channels
// which have a true value returned for *all* of the filters will be returned.
// If no filters are provided, every channel in the open channels bucket will
// be returned.
func fetchChannels(c *ChannelStateDB, filters ...fetchChannelsFilter) (
[]*OpenChannel, error) {
var channels []*OpenChannel
err := kvdb.View(c.backend, func(tx kvdb.RTx) error {
// Get the bucket dedicated to storing the metadata for open
// channels.
openChanBucket := tx.ReadBucket(openChannelBucket)
if openChanBucket == nil {
return ErrNoActiveChannels
}
// Next, fetch the bucket dedicated to storing metadata related
// to all nodes. All keys within this bucket are the serialized
// public keys of all our direct counterparties.
nodeMetaBucket := tx.ReadBucket(nodeInfoBucket)
if nodeMetaBucket == nil {
return fmt.Errorf("node bucket not created")
}
// Finally for each node public key in the bucket, fetch all
// the channels related to this particular node.
return nodeMetaBucket.ForEach(func(k, v []byte) error {
nodeChanBucket := openChanBucket.NestedReadBucket(k)
if nodeChanBucket == nil {
return nil
}
return nodeChanBucket.ForEach(func(chainHash, v []byte) error {
// If there's a value, it's not a bucket so
// ignore it.
if v != nil {
return nil
}
// If we've found a valid chainhash bucket,
// then we'll retrieve that so we can extract
// all the channels.
chainBucket := nodeChanBucket.NestedReadBucket(
chainHash,
)
if chainBucket == nil {
return fmt.Errorf("unable to read "+
"bucket for chain=%x", chainHash[:])
}
nodeChans, err := c.fetchNodeChannels(chainBucket)
if err != nil {
return fmt.Errorf("unable to read "+
"channel for chain_hash=%x, "+
"node_key=%x: %v", chainHash[:], k, err)
}
for _, channel := range nodeChans {
// includeChannel indicates whether the channel
// meets the criteria specified by our filters.
includeChannel := true
// Run through each filter and check whether the
// channel should be included.
for _, f := range filters {
// If the channel fails the filter, set
// includeChannel to false and don't bother
// checking the remaining filters.
if !f(channel) {
includeChannel = false
break
}
}
// If the channel passed every filter, include it in
// our set of channels.
if includeChannel {
channels = append(channels, channel)
}
}
return nil
})
})
}, func() {
channels = nil
})
if err != nil {
return nil, err
}
return channels, nil
}
// FetchClosedChannels attempts to fetch all closed channels from the database.
// The pendingOnly bool toggles if channels that aren't yet fully closed should
// be returned in the response or not. When a channel was cooperatively closed,
// it becomes fully closed after a single confirmation. When a channel was
// forcibly closed, it will become fully closed after _all_ the pending funds
// (if any) have been swept.
func (c *ChannelStateDB) FetchClosedChannels(pendingOnly bool) (
[]*ChannelCloseSummary, error) {
var chanSummaries []*ChannelCloseSummary
if err := kvdb.View(c.backend, func(tx kvdb.RTx) error {
closeBucket := tx.ReadBucket(closedChannelBucket)
if closeBucket == nil {
return ErrNoClosedChannels
}
return closeBucket.ForEach(func(chanID []byte, summaryBytes []byte) error {
summaryReader := bytes.NewReader(summaryBytes)
chanSummary, err := deserializeCloseChannelSummary(summaryReader)
if err != nil {
return err
}
// If the query specified to only include pending
// channels, then we'll skip any channels which aren't
// currently pending.
if !chanSummary.IsPending && pendingOnly {
return nil
}
chanSummaries = append(chanSummaries, chanSummary)
return nil
})
}, func() {
chanSummaries = nil
}); err != nil {
return nil, err
}
return chanSummaries, nil
}
// ErrClosedChannelNotFound signals that a closed channel could not be found in
// the channeldb.
var ErrClosedChannelNotFound = errors.New("unable to find closed channel summary")
// FetchClosedChannel queries for a channel close summary using the channel
// point of the channel in question.
func (c *ChannelStateDB) FetchClosedChannel(chanID *wire.OutPoint) (
*ChannelCloseSummary, error) {
var chanSummary *ChannelCloseSummary
if err := kvdb.View(c.backend, func(tx kvdb.RTx) error {
closeBucket := tx.ReadBucket(closedChannelBucket)
if closeBucket == nil {
return ErrClosedChannelNotFound
}
var b bytes.Buffer
var err error
if err = graphdb.WriteOutpoint(&b, chanID); err != nil {
return err
}
summaryBytes := closeBucket.Get(b.Bytes())
if summaryBytes == nil {
return ErrClosedChannelNotFound
}
summaryReader := bytes.NewReader(summaryBytes)
chanSummary, err = deserializeCloseChannelSummary(summaryReader)
return err
}, func() {
chanSummary = nil
}); err != nil {
return nil, err
}
return chanSummary, nil
}
// FetchClosedChannelForID queries for a channel close summary using the
// channel ID of the channel in question.
func (c *ChannelStateDB) FetchClosedChannelForID(cid lnwire.ChannelID) (
*ChannelCloseSummary, error) {
var chanSummary *ChannelCloseSummary
if err := kvdb.View(c.backend, func(tx kvdb.RTx) error {
closeBucket := tx.ReadBucket(closedChannelBucket)
if closeBucket == nil {
return ErrClosedChannelNotFound
}
// The first 30 bytes of the channel ID and outpoint will be
// equal.
cursor := closeBucket.ReadCursor()
op, c := cursor.Seek(cid[:30])
// We scan over all possible candidates for this channel ID.
for ; op != nil && bytes.Compare(cid[:30], op[:30]) <= 0; op, c = cursor.Next() {
var outPoint wire.OutPoint
err := graphdb.ReadOutpoint(
bytes.NewReader(op), &outPoint,
)
if err != nil {
return err
}
// If the found outpoint does not correspond to this
// channel ID, we continue.
if !cid.IsChanPoint(&outPoint) {
continue
}
// Deserialize the close summary and return.
r := bytes.NewReader(c)
chanSummary, err = deserializeCloseChannelSummary(r)
if err != nil {
return err
}
return nil
}
return ErrClosedChannelNotFound
}, func() {
chanSummary = nil
}); err != nil {
return nil, err
}
return chanSummary, nil
}
// MarkChanFullyClosed marks a channel as fully closed within the database. A
// channel should be marked as fully closed if the channel was initially
// cooperatively closed and it's reached a single confirmation, or after all
// the pending funds in a channel that has been forcibly closed have been
// swept.
func (c *ChannelStateDB) MarkChanFullyClosed(chanPoint *wire.OutPoint) error {
var (
openChannels []*OpenChannel
pruneLinkNode *btcec.PublicKey
)
err := kvdb.Update(c.backend, func(tx kvdb.RwTx) error {
var b bytes.Buffer
if err := graphdb.WriteOutpoint(&b, chanPoint); err != nil {
return err
}
chanID := b.Bytes()
closedChanBucket, err := tx.CreateTopLevelBucket(
closedChannelBucket,
)
if err != nil {
return err
}
chanSummaryBytes := closedChanBucket.Get(chanID)
if chanSummaryBytes == nil {
return fmt.Errorf("no closed channel for "+
"chan_point=%v found", chanPoint)
}
chanSummaryReader := bytes.NewReader(chanSummaryBytes)
chanSummary, err := deserializeCloseChannelSummary(
chanSummaryReader,
)
if err != nil {
return err
}
chanSummary.IsPending = false
var newSummary bytes.Buffer
err = serializeChannelCloseSummary(&newSummary, chanSummary)
if err != nil {
return err
}
err = closedChanBucket.Put(chanID, newSummary.Bytes())
if err != nil {
return err
}
// Now that the channel is closed, we'll check if we have any
// other open channels with this peer. If we don't we'll
// garbage collect it to ensure we don't establish persistent
// connections to peers without open channels.
pruneLinkNode = chanSummary.RemotePub
openChannels, err = c.fetchOpenChannels(
tx, pruneLinkNode,
)
if err != nil {
return fmt.Errorf("unable to fetch open channels for "+
"peer %x: %v",
pruneLinkNode.SerializeCompressed(), err)
}
return nil
}, func() {
openChannels = nil
pruneLinkNode = nil
})
if err != nil {
return err
}
// Decide whether we want to remove the link node, based upon the number
// of still open channels.
return c.pruneLinkNode(openChannels, pruneLinkNode)
}
// pruneLinkNode determines whether we should garbage collect a link node from
// the database due to no longer having any open channels with it. If there are
// any left, then this acts as a no-op.
func (c *ChannelStateDB) pruneLinkNode(openChannels []*OpenChannel,
remotePub *btcec.PublicKey) error {
if len(openChannels) > 0 {
return nil
}
log.Infof("Pruning link node %x with zero open channels from database",
remotePub.SerializeCompressed())
return c.linkNodeDB.DeleteLinkNode(remotePub)
}
// PruneLinkNodes attempts to prune all link nodes found within the database
// with whom we no longer have any open channels with.
func (c *ChannelStateDB) PruneLinkNodes() error {
allLinkNodes, err := c.linkNodeDB.FetchAllLinkNodes()
if err != nil {
return err
}
for _, linkNode := range allLinkNodes {
var (
openChannels []*OpenChannel
linkNode = linkNode
)
err := kvdb.View(c.backend, func(tx kvdb.RTx) error {
var err error
openChannels, err = c.fetchOpenChannels(
tx, linkNode.IdentityPub,
)
return err
}, func() {
openChannels = nil
})
if err != nil {
return err
}
err = c.pruneLinkNode(openChannels, linkNode.IdentityPub)
if err != nil {
return err
}
}
return nil
}
// ChannelShell is a shell of a channel that is meant to be used for channel
// recovery purposes. It contains a minimal OpenChannel instance along with
// addresses for that target node.
type ChannelShell struct {
// NodeAddrs the set of addresses that this node has known to be
// reachable at in the past.
NodeAddrs []net.Addr
// Chan is a shell of an OpenChannel, it contains only the items
// required to restore the channel on disk.
Chan *OpenChannel
}
// RestoreChannelShells is a method that allows the caller to reconstruct the
// state of an OpenChannel from the ChannelShell. We'll attempt to write the
// new channel to disk, create a LinkNode instance with the passed node
// addresses, and finally create an edge within the graph for the channel as
// well. This method is idempotent, so repeated calls with the same set of
// channel shells won't modify the database after the initial call.
func (c *ChannelStateDB) RestoreChannelShells(channelShells ...*ChannelShell) error {
err := kvdb.Update(c.backend, func(tx kvdb.RwTx) error {
for _, channelShell := range channelShells {
channel := channelShell.Chan
// When we make a channel, we mark that the channel has
// been restored, this will signal to other sub-systems
// to not attempt to use the channel as if it was a
// regular one.
channel.chanStatus |= ChanStatusRestored
// First, we'll attempt to create a new open channel
// and link node for this channel. If the channel
// already exists, then in order to ensure this method
// is idempotent, we'll continue to the next step.
channel.Db = c
err := syncNewChannel(
tx, channel, channelShell.NodeAddrs,
)
if err != nil {
return err
}
}
return nil
}, func() {})
if err != nil {
return err
}
return nil
}
// AddrsForNode consults the channel database for all addresses known to the
// passed node public key. The returned boolean indicates if the given node is
// unknown to the channel DB or not.
//
// NOTE: this is part of the AddrSource interface.
func (d *DB) AddrsForNode(nodePub *btcec.PublicKey) (bool, []net.Addr, error) {
linkNode, err := d.channelStateDB.linkNodeDB.FetchLinkNode(nodePub)
// Only if the error is something other than ErrNodeNotFound do we
// return it.
switch {
case err != nil && !errors.Is(err, ErrNodeNotFound):
return false, nil, err
case errors.Is(err, ErrNodeNotFound):
return false, nil, nil
}
return true, linkNode.Addresses, nil
}
// AbandonChannel attempts to remove the target channel from the open channel
// database. If the channel was already removed (has a closed channel entry),
// then we'll return a nil error. Otherwise, we'll insert a new close summary
// into the database.
func (c *ChannelStateDB) AbandonChannel(chanPoint *wire.OutPoint,
bestHeight uint32) error {
// With the chanPoint constructed, we'll attempt to find the target
// channel in the database. If we can't find the channel, then we'll
// return the error back to the caller.
dbChan, err := c.FetchChannel(*chanPoint)
switch {
// If the channel wasn't found, then it's possible that it was already
// abandoned from the database.
case err == ErrChannelNotFound:
_, closedErr := c.FetchClosedChannel(chanPoint)
if closedErr != nil {
return closedErr
}
// If the channel was already closed, then we don't return an
// error as we'd like this step to be repeatable.
return nil
case err != nil:
return err
}
// Now that we've found the channel, we'll populate a close summary for
// the channel, so we can store as much information for this abounded
// channel as possible. We also ensure that we set Pending to false, to
// indicate that this channel has been "fully" closed.
summary := &ChannelCloseSummary{
CloseType: Abandoned,
ChanPoint: *chanPoint,
ChainHash: dbChan.ChainHash,
CloseHeight: bestHeight,
RemotePub: dbChan.IdentityPub,
Capacity: dbChan.Capacity,
SettledBalance: dbChan.LocalCommitment.LocalBalance.ToSatoshis(),
ShortChanID: dbChan.ShortChanID(),
RemoteCurrentRevocation: dbChan.RemoteCurrentRevocation,
RemoteNextRevocation: dbChan.RemoteNextRevocation,
LocalChanConfig: dbChan.LocalChanCfg,
}
// Finally, we'll close the channel in the DB, and return back to the
// caller. We set ourselves as the close initiator because we abandoned
// the channel.
return dbChan.CloseChannel(summary, ChanStatusLocalCloseInitiator)
}
// SaveChannelOpeningState saves the serialized channel state for the provided
// chanPoint to the channelOpeningStateBucket.
func (c *ChannelStateDB) SaveChannelOpeningState(outPoint,
serializedState []byte) error {
return kvdb.Update(c.backend, func(tx kvdb.RwTx) error {
bucket, err := tx.CreateTopLevelBucket(channelOpeningStateBucket)
if err != nil {
return err
}
return bucket.Put(outPoint, serializedState)
}, func() {})
}
// GetChannelOpeningState fetches the serialized channel state for the provided
// outPoint from the database, or returns ErrChannelNotFound if the channel
// is not found.
func (c *ChannelStateDB) GetChannelOpeningState(outPoint []byte) ([]byte,
error) {
var serializedState []byte
err := kvdb.View(c.backend, func(tx kvdb.RTx) error {
bucket := tx.ReadBucket(channelOpeningStateBucket)
if bucket == nil {
// If the bucket does not exist, it means we never added
// a channel to the db, so return ErrChannelNotFound.
return ErrChannelNotFound
}
stateBytes := bucket.Get(outPoint)
if stateBytes == nil {
return ErrChannelNotFound
}
serializedState = append(serializedState, stateBytes...)
return nil
}, func() {
serializedState = nil
})
return serializedState, err
}
// DeleteChannelOpeningState removes any state for outPoint from the database.
func (c *ChannelStateDB) DeleteChannelOpeningState(outPoint []byte) error {
return kvdb.Update(c.backend, func(tx kvdb.RwTx) error {
bucket := tx.ReadWriteBucket(channelOpeningStateBucket)
if bucket == nil {
return ErrChannelNotFound
}
return bucket.Delete(outPoint)
}, func() {})
}
// syncVersions function is used for safe db version synchronization. It
// applies migration functions to the current database and recovers the
// previous state of db if at least one error/panic appeared during migration.
func (d *DB) syncVersions(versions []mandatoryVersion) error {
meta, err := d.FetchMeta()
if err != nil {
if err == ErrMetaNotFound {
meta = &Meta{}
} else {
return err
}
}
latestVersion := getLatestDBVersion(versions)
log.Infof("Checking for schema update: latest_version=%v, "+
"db_version=%v", latestVersion, meta.DbVersionNumber)
switch {
// If the database reports a higher version that we are aware of, the
// user is probably trying to revert to a prior version of lnd. We fail
// here to prevent reversions and unintended corruption.
case meta.DbVersionNumber > latestVersion:
log.Errorf("Refusing to revert from db_version=%d to "+
"lower version=%d", meta.DbVersionNumber,
latestVersion)
return ErrDBReversion
// If the current database version matches the latest version number,
// then we don't need to perform any migrations.
case meta.DbVersionNumber == latestVersion:
return nil
}
log.Infof("Performing database schema migration")
// Otherwise, we fetch the migrations which need to applied, and
// execute them serially within a single database transaction to ensure
// the migration is atomic.
migrations, migrationVersions := getMigrationsToApply(
versions, meta.DbVersionNumber,
)
return kvdb.Update(d, func(tx kvdb.RwTx) error {
for i, migration := range migrations {
if migration == nil {
continue
}
log.Infof("Applying migration #%v",
migrationVersions[i])
if err := migration(tx); err != nil {
log.Infof("Unable to apply migration #%v",
migrationVersions[i])
return err
}
}
meta.DbVersionNumber = latestVersion
err := putMeta(meta, tx)
if err != nil {
return err
}
// In dry-run mode, return an error to prevent the transaction
// from committing.
if d.dryRun {
return ErrDryRunMigrationOK
}
return nil
}, func() {})
}
// applyOptionalVersions takes a config to determine whether the optional
// migrations will be applied.
//
// NOTE: only support the prune_revocation_log optional migration atm.
func (d *DB) applyOptionalVersions(cfg OptionalMiragtionConfig) error {
// TODO(yy): need to design the db to support dry run for optional
// migrations.
if d.dryRun {
log.Info("Skipped optional migrations as dry run mode is not " +
"supported yet")
return nil
}
om, err := d.fetchOptionalMeta()
if err != nil {
if err == ErrMetaNotFound {
om = &OptionalMeta{
Versions: make(map[uint64]string),
}
} else {
return err
}
}
log.Infof("Checking for optional update: prune_revocation_log=%v, "+
"db_version=%s", cfg.PruneRevocationLog, om)
// Exit early if the optional migration is not specified.
if !cfg.PruneRevocationLog {
return nil
}
// Exit early if the optional migration has already been applied.
if _, ok := om.Versions[0]; ok {
return nil
}
// Get the optional version.
version := optionalVersions[0]
log.Infof("Performing database optional migration: %s", version.name)
migrationCfg := &MigrationConfigImpl{
migration30.MigrateRevLogConfigImpl{
NoAmountData: d.noRevLogAmtData,
},
}
// Migrate the data.
if err := version.migration(d, migrationCfg); err != nil {
log.Errorf("Unable to apply optional migration: %s, error: %v",
version.name, err)
return err
}
// Update the optional meta. Notice that unlike the mandatory db
// migrations where we perform the migration and updating meta in a
// single db transaction, we use different transactions here. Even when
// the following update is failed, we should be fine here as we would
// re-run the optional migration again, which is a noop, during next
// startup.
om.Versions[0] = version.name
if err := d.putOptionalMeta(om); err != nil {
log.Errorf("Unable to update optional meta: %v", err)
return err
}
return nil
}
// ChannelStateDB returns the sub database that is concerned with the channel
// state.
func (d *DB) ChannelStateDB() *ChannelStateDB {
return d.channelStateDB
}
// LatestDBVersion returns the number of the latest database version currently
// known to the channel DB.
func LatestDBVersion() uint32 {
return getLatestDBVersion(dbVersions)
}
func getLatestDBVersion(versions []mandatoryVersion) uint32 {
return versions[len(versions)-1].number
}
// getMigrationsToApply retrieves the migration function that should be
// applied to the database.
func getMigrationsToApply(versions []mandatoryVersion,
version uint32) ([]migration, []uint32) {
migrations := make([]migration, 0, len(versions))
migrationVersions := make([]uint32, 0, len(versions))
for _, v := range versions {
if v.number > version {
migrations = append(migrations, v.migration)
migrationVersions = append(migrationVersions, v.number)
}
}
return migrations, migrationVersions
}
// fetchHistoricalChanBucket returns a the channel bucket for a given outpoint
// from the historical channel bucket. If the bucket does not exist,
// ErrNoHistoricalBucket is returned.
func fetchHistoricalChanBucket(tx kvdb.RTx,
outPoint *wire.OutPoint) (kvdb.RBucket, error) {
// First fetch the top level bucket which stores all data related to
// historically stored channels.
historicalChanBucket := tx.ReadBucket(historicalChannelBucket)
if historicalChanBucket == nil {
return nil, ErrNoHistoricalBucket
}
// With the bucket for the node and chain fetched, we can now go down
// another level, for the channel itself.
var chanPointBuf bytes.Buffer
if err := graphdb.WriteOutpoint(&chanPointBuf, outPoint); err != nil {
return nil, err
}
chanBucket := historicalChanBucket.NestedReadBucket(
chanPointBuf.Bytes(),
)
if chanBucket == nil {
return nil, ErrChannelNotFound
}
return chanBucket, nil
}
// FetchHistoricalChannel fetches open channel data from the historical channel
// bucket.
func (c *ChannelStateDB) FetchHistoricalChannel(outPoint *wire.OutPoint) (
*OpenChannel, error) {
var channel *OpenChannel
err := kvdb.View(c.backend, func(tx kvdb.RTx) error {
chanBucket, err := fetchHistoricalChanBucket(tx, outPoint)
if err != nil {
return err
}
channel, err = fetchOpenChannel(chanBucket, outPoint)
if err != nil {
return err
}
channel.Db = c
return nil
}, func() {
channel = nil
})
if err != nil {
return nil, err
}
return channel, nil
}
func fetchFinalHtlcsBucket(tx kvdb.RTx,
chanID lnwire.ShortChannelID) (kvdb.RBucket, error) {
finalHtlcsBucket := tx.ReadBucket(finalHtlcsBucket)
if finalHtlcsBucket == nil {
return nil, ErrFinalHtlcsBucketNotFound
}
var chanIDBytes [8]byte
byteOrder.PutUint64(chanIDBytes[:], chanID.ToUint64())
chanBucket := finalHtlcsBucket.NestedReadBucket(chanIDBytes[:])
if chanBucket == nil {
return nil, ErrFinalChannelBucketNotFound
}
return chanBucket, nil
}
var ErrHtlcUnknown = errors.New("htlc unknown")
// LookupFinalHtlc retrieves a final htlc resolution from the database. If the
// htlc has no final resolution yet, ErrHtlcUnknown is returned.
func (c *ChannelStateDB) LookupFinalHtlc(chanID lnwire.ShortChannelID,
htlcIndex uint64) (*FinalHtlcInfo, error) {
var idBytes [8]byte
byteOrder.PutUint64(idBytes[:], htlcIndex)
var settledByte byte
err := kvdb.View(c.backend, func(tx kvdb.RTx) error {
finalHtlcsBucket, err := fetchFinalHtlcsBucket(
tx, chanID,
)
switch {
case errors.Is(err, ErrFinalHtlcsBucketNotFound):
fallthrough
case errors.Is(err, ErrFinalChannelBucketNotFound):
return ErrHtlcUnknown
case err != nil:
return fmt.Errorf("cannot fetch final htlcs bucket: %w",
err)
}
value := finalHtlcsBucket.Get(idBytes[:])
if value == nil {
return ErrHtlcUnknown
}
if len(value) != 1 {
return errors.New("unexpected final htlc value length")
}
settledByte = value[0]
return nil
}, func() {
settledByte = 0
})
if err != nil {
return nil, err
}
info := FinalHtlcInfo{
Settled: settledByte&byte(FinalHtlcSettledBit) != 0,
Offchain: settledByte&byte(FinalHtlcOffchainBit) != 0,
}
return &info, nil
}
// PutOnchainFinalHtlcOutcome stores the final on-chain outcome of an htlc in
// the database.
func (c *ChannelStateDB) PutOnchainFinalHtlcOutcome(
chanID lnwire.ShortChannelID, htlcID uint64, settled bool) error {
// Skip if the user did not opt in to storing final resolutions.
if !c.parent.storeFinalHtlcResolutions {
return nil
}
return kvdb.Update(c.backend, func(tx kvdb.RwTx) error {
finalHtlcsBucket, err := fetchFinalHtlcsBucketRw(tx, chanID)
if err != nil {
return err
}
return putFinalHtlc(
finalHtlcsBucket, htlcID,
FinalHtlcInfo{
Settled: settled,
Offchain: false,
},
)
}, func() {})
}
// MakeTestInvoiceDB is used to create a test invoice database for testing
// purposes. It simply calls into MakeTestDB so the same modifiers can be used.
func MakeTestInvoiceDB(t *testing.T, modifiers ...OptionModifier) (
invoices.InvoiceDB, error) {
return MakeTestDB(t, modifiers...)
}
// MakeTestDB creates a new instance of the ChannelDB for testing purposes.
// A callback which cleans up the created temporary directories is also
// returned and intended to be executed after the test completes.
func MakeTestDB(t *testing.T, modifiers ...OptionModifier) (*DB, error) {
// First, create a temporary directory to be used for the duration of
// this test.
tempDirName := t.TempDir()
// Next, create channeldb for the first time.
backend, backendCleanup, err := kvdb.GetTestBackend(tempDirName, "cdb")
if err != nil {
backendCleanup()
return nil, err
}
cdb, err := CreateWithBackend(backend, modifiers...)
if err != nil {
backendCleanup()
return nil, err
}
t.Cleanup(func() {
cdb.Close()
backendCleanup()
})
return cdb, nil
}
package channeldb
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
var (
// duplicatePaymentsBucket is the name of a optional sub-bucket within
// the payment hash bucket, that is used to hold duplicate payments to a
// payment hash. This is needed to support information from earlier
// versions of lnd, where it was possible to pay to a payment hash more
// than once.
duplicatePaymentsBucket = []byte("payment-duplicate-bucket")
// duplicatePaymentSettleInfoKey is a key used in the payment's
// sub-bucket to store the settle info of the payment.
duplicatePaymentSettleInfoKey = []byte("payment-settle-info")
// duplicatePaymentAttemptInfoKey is a key used in the payment's
// sub-bucket to store the info about the latest attempt that was done
// for the payment in question.
duplicatePaymentAttemptInfoKey = []byte("payment-attempt-info")
// duplicatePaymentCreationInfoKey is a key used in the payment's
// sub-bucket to store the creation info of the payment.
duplicatePaymentCreationInfoKey = []byte("payment-creation-info")
// duplicatePaymentFailInfoKey is a key used in the payment's sub-bucket
// to store information about the reason a payment failed.
duplicatePaymentFailInfoKey = []byte("payment-fail-info")
// duplicatePaymentSequenceKey is a key used in the payment's sub-bucket
// to store the sequence number of the payment.
duplicatePaymentSequenceKey = []byte("payment-sequence-key")
)
// duplicateHTLCAttemptInfo contains static information about a specific HTLC
// attempt for a payment. This information is used by the router to handle any
// errors coming back after an attempt is made, and to query the switch about
// the status of the attempt.
type duplicateHTLCAttemptInfo struct {
// attemptID is the unique ID used for this attempt.
attemptID uint64
// sessionKey is the ephemeral key used for this attempt.
sessionKey [btcec.PrivKeyBytesLen]byte
// route is the route attempted to send the HTLC.
route route.Route
}
// fetchDuplicatePaymentStatus fetches the payment status of the payment. If
// the payment isn't found, it will return error `ErrPaymentNotInitiated`.
func fetchDuplicatePaymentStatus(bucket kvdb.RBucket) (PaymentStatus, error) {
if bucket.Get(duplicatePaymentSettleInfoKey) != nil {
return StatusSucceeded, nil
}
if bucket.Get(duplicatePaymentFailInfoKey) != nil {
return StatusFailed, nil
}
if bucket.Get(duplicatePaymentCreationInfoKey) != nil {
return StatusInFlight, nil
}
return 0, ErrPaymentNotInitiated
}
func deserializeDuplicateHTLCAttemptInfo(r io.Reader) (
*duplicateHTLCAttemptInfo, error) {
a := &duplicateHTLCAttemptInfo{}
err := ReadElements(r, &a.attemptID, &a.sessionKey)
if err != nil {
return nil, err
}
a.route, err = DeserializeRoute(r)
if err != nil {
return nil, err
}
return a, nil
}
func deserializeDuplicatePaymentCreationInfo(r io.Reader) (
*PaymentCreationInfo, error) {
var scratch [8]byte
c := &PaymentCreationInfo{}
if _, err := io.ReadFull(r, c.PaymentIdentifier[:]); err != nil {
return nil, err
}
if _, err := io.ReadFull(r, scratch[:]); err != nil {
return nil, err
}
c.Value = lnwire.MilliSatoshi(byteOrder.Uint64(scratch[:]))
if _, err := io.ReadFull(r, scratch[:]); err != nil {
return nil, err
}
c.CreationTime = time.Unix(int64(byteOrder.Uint64(scratch[:])), 0)
if _, err := io.ReadFull(r, scratch[:4]); err != nil {
return nil, err
}
reqLen := byteOrder.Uint32(scratch[:4])
payReq := make([]byte, reqLen)
if reqLen > 0 {
if _, err := io.ReadFull(r, payReq); err != nil {
return nil, err
}
}
c.PaymentRequest = payReq
return c, nil
}
func fetchDuplicatePayment(bucket kvdb.RBucket) (*MPPayment, error) {
seqBytes := bucket.Get(duplicatePaymentSequenceKey)
if seqBytes == nil {
return nil, fmt.Errorf("sequence number not found")
}
sequenceNum := binary.BigEndian.Uint64(seqBytes)
// Get the payment status.
paymentStatus, err := fetchDuplicatePaymentStatus(bucket)
if err != nil {
return nil, err
}
// Get the PaymentCreationInfo.
b := bucket.Get(duplicatePaymentCreationInfoKey)
if b == nil {
return nil, fmt.Errorf("creation info not found")
}
r := bytes.NewReader(b)
creationInfo, err := deserializeDuplicatePaymentCreationInfo(r)
if err != nil {
return nil, err
}
// Get failure reason if available.
var failureReason *FailureReason
b = bucket.Get(duplicatePaymentFailInfoKey)
if b != nil {
reason := FailureReason(b[0])
failureReason = &reason
}
payment := &MPPayment{
SequenceNum: sequenceNum,
Info: creationInfo,
FailureReason: failureReason,
Status: paymentStatus,
}
// Get the HTLCAttemptInfo. It can be absent.
b = bucket.Get(duplicatePaymentAttemptInfoKey)
if b != nil {
r = bytes.NewReader(b)
attempt, err := deserializeDuplicateHTLCAttemptInfo(r)
if err != nil {
return nil, err
}
htlc := HTLCAttempt{
HTLCAttemptInfo: HTLCAttemptInfo{
AttemptID: attempt.attemptID,
Route: attempt.route,
sessionKey: attempt.sessionKey,
},
}
// Get the payment preimage. This is only found for
// successful payments.
b = bucket.Get(duplicatePaymentSettleInfoKey)
if b != nil {
var preimg lntypes.Preimage
copy(preimg[:], b)
htlc.Settle = &HTLCSettleInfo{
Preimage: preimg,
SettleTime: time.Time{},
}
} else {
// Otherwise the payment must have failed.
htlc.Failure = &HTLCFailInfo{
FailTime: time.Time{},
}
}
payment.HTLCs = []HTLCAttempt{htlc}
}
return payment, nil
}
func fetchDuplicatePayments(paymentHashBucket kvdb.RBucket) ([]*MPPayment,
error) {
var payments []*MPPayment
// For older versions of lnd, duplicate payments to a payment has was
// possible. These will be found in a sub-bucket indexed by their
// sequence number if available.
dup := paymentHashBucket.NestedReadBucket(duplicatePaymentsBucket)
if dup == nil {
return nil, nil
}
err := dup.ForEach(func(k, v []byte) error {
subBucket := dup.NestedReadBucket(k)
if subBucket == nil {
// We one bucket for each duplicate to be found.
return fmt.Errorf("non bucket element" +
"in duplicate bucket")
}
p, err := fetchDuplicatePayment(subBucket)
if err != nil {
return err
}
payments = append(payments, p)
return nil
})
if err != nil {
return nil, err
}
return payments, nil
}
package channeldb
import (
"bytes"
"io"
"sort"
"time"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// forwardingLogBucket is the bucket that we'll use to store the
// forwarding log. The forwarding log contains a time series database
// of the forwarding history of a lightning daemon. Each key within the
// bucket is a timestamp (in nano seconds since the unix epoch), and
// the value a slice of a forwarding event for that timestamp.
forwardingLogBucket = []byte("circuit-fwd-log")
)
const (
// forwardingEventSize is the size of a forwarding event. The breakdown
// is as follows:
//
// * 8 byte incoming chan ID || 8 byte outgoing chan ID || 8 byte value in
// || 8 byte value out
//
// From the value in and value out, callers can easily compute the
// total fee extract from a forwarding event.
forwardingEventSize = 32
// MaxResponseEvents is the max number of forwarding events that will
// be returned by a single query response. This size was selected to
// safely remain under gRPC's 4MiB message size response limit. As each
// full forwarding event (including the timestamp) is 40 bytes, we can
// safely return 50k entries in a single response.
MaxResponseEvents = 50000
)
// ForwardingLog returns an instance of the ForwardingLog object backed by the
// target database instance.
func (d *DB) ForwardingLog() *ForwardingLog {
return &ForwardingLog{
db: d,
}
}
// ForwardingLog is a time series database that logs the fulfillment of payment
// circuits by a lightning network daemon. The log contains a series of
// forwarding events which map a timestamp to a forwarding event. A forwarding
// event describes which channels were used to create+settle a circuit, and the
// amount involved. Subtracting the outgoing amount from the incoming amount
// reveals the fee charged for the forwarding service.
type ForwardingLog struct {
db *DB
}
// ForwardingEvent is an event in the forwarding log's time series. Each
// forwarding event logs the creation and tear-down of a payment circuit. A
// circuit is created once an incoming HTLC has been fully forwarded, and
// destroyed once the payment has been settled.
type ForwardingEvent struct {
// Timestamp is the settlement time of this payment circuit.
Timestamp time.Time
// IncomingChanID is the incoming channel ID of the payment circuit.
IncomingChanID lnwire.ShortChannelID
// OutgoingChanID is the outgoing channel ID of the payment circuit.
OutgoingChanID lnwire.ShortChannelID
// AmtIn is the amount of the incoming HTLC. Subtracting this from the
// outgoing amount gives the total fees of this payment circuit.
AmtIn lnwire.MilliSatoshi
// AmtOut is the amount of the outgoing HTLC. Subtracting the incoming
// amount from this gives the total fees for this payment circuit.
AmtOut lnwire.MilliSatoshi
}
// encodeForwardingEvent writes out the target forwarding event to the passed
// io.Writer, using the expected DB format. Note that the timestamp isn't
// serialized as this will be the key value within the bucket.
func encodeForwardingEvent(w io.Writer, f *ForwardingEvent) error {
return WriteElements(
w, f.IncomingChanID, f.OutgoingChanID, f.AmtIn, f.AmtOut,
)
}
// decodeForwardingEvent attempts to decode the raw bytes of a serialized
// forwarding event into the target ForwardingEvent. Note that the timestamp
// won't be decoded, as the caller is expected to set this due to the bucket
// structure of the forwarding log.
func decodeForwardingEvent(r io.Reader, f *ForwardingEvent) error {
return ReadElements(
r, &f.IncomingChanID, &f.OutgoingChanID, &f.AmtIn, &f.AmtOut,
)
}
// AddForwardingEvents adds a series of forwarding events to the database.
// Before inserting, the set of events will be sorted according to their
// timestamp. This ensures that all writes to disk are sequential.
func (f *ForwardingLog) AddForwardingEvents(events []ForwardingEvent) error {
// Before we create the database transaction, we'll ensure that the set
// of forwarding events are properly sorted according to their
// timestamp and that no duplicate timestamps exist to avoid collisions
// in the key we are going to store the events under.
makeUniqueTimestamps(events)
var timestamp [8]byte
return kvdb.Batch(f.db.Backend, func(tx kvdb.RwTx) error {
// First, we'll fetch the bucket that stores our time series
// log.
logBucket, err := tx.CreateTopLevelBucket(
forwardingLogBucket,
)
if err != nil {
return err
}
// With the bucket obtained, we can now begin to write out the
// series of events.
for _, event := range events {
err := storeEvent(logBucket, event, timestamp[:])
if err != nil {
return err
}
}
return nil
})
}
// storeEvent tries to store a forwarding event into the given bucket by trying
// to avoid collisions. If a key for the event timestamp already exists in the
// database, the timestamp is incremented in nanosecond intervals until a "free"
// slot is found.
func storeEvent(bucket walletdb.ReadWriteBucket, event ForwardingEvent,
timestampScratchSpace []byte) error {
// First, we'll serialize this timestamp into our
// timestamp buffer.
byteOrder.PutUint64(
timestampScratchSpace, uint64(event.Timestamp.UnixNano()),
)
// Next we'll loop until we find a "free" slot in the bucket to store
// the event under. This should almost never happen unless we're running
// on a system that has a very bad system clock that doesn't properly
// resolve to nanosecond scale. We try up to 100 times (which would come
// to a maximum shift of 0.1 microsecond which is acceptable for most
// use cases). If we don't find a free slot, we just give up and let
// the collision happen. Something must be wrong with the data in that
// case, even on a very fast machine forwarding payments _will_ take a
// few microseconds at least so we should find a nanosecond slot
// somewhere.
const maxTries = 100
tries := 0
for tries < maxTries {
val := bucket.Get(timestampScratchSpace)
if val == nil {
break
}
// Collision, try the next nanosecond timestamp.
nextNano := event.Timestamp.UnixNano() + 1
event.Timestamp = time.Unix(0, nextNano)
byteOrder.PutUint64(timestampScratchSpace, uint64(nextNano))
tries++
}
// With the key encoded, we'll then encode the event
// into our buffer, then write it out to disk.
var eventBytes [forwardingEventSize]byte
eventBuf := bytes.NewBuffer(eventBytes[0:0:forwardingEventSize])
err := encodeForwardingEvent(eventBuf, &event)
if err != nil {
return err
}
return bucket.Put(timestampScratchSpace, eventBuf.Bytes())
}
// ForwardingEventQuery represents a query to the forwarding log payment
// circuit time series database. The query allows a caller to retrieve all
// records for a particular time slice, offset in that time slice, limiting the
// total number of responses returned.
type ForwardingEventQuery struct {
// StartTime is the start time of the time slice.
StartTime time.Time
// EndTime is the end time of the time slice.
EndTime time.Time
// IndexOffset is the offset within the time slice to start at. This
// can be used to start the response at a particular record.
IndexOffset uint32
// NumMaxEvents is the max number of events to return.
NumMaxEvents uint32
}
// ForwardingLogTimeSlice is the response to a forwarding query. It includes
// the original query, the set events that match the query, and an integer
// which represents the offset index of the last item in the set of returned
// events. This integer allows callers to resume their query using this offset
// in the event that the query's response exceeds the max number of returnable
// events.
type ForwardingLogTimeSlice struct {
ForwardingEventQuery
// ForwardingEvents is the set of events in our time series that answer
// the query embedded above.
ForwardingEvents []ForwardingEvent
// LastIndexOffset is the index of the last element in the set of
// returned ForwardingEvents above. Callers can use this to resume
// their query in the event that the time slice has too many events to
// fit into a single response.
LastIndexOffset uint32
}
// Query allows a caller to query the forwarding event time series for a
// particular time slice. The caller can control the precise time as well as
// the number of events to be returned.
//
// TODO(roasbeef): rename?
func (f *ForwardingLog) Query(q ForwardingEventQuery) (ForwardingLogTimeSlice, error) {
var resp ForwardingLogTimeSlice
// If the user provided an index offset, then we'll not know how many
// records we need to skip. We'll also keep track of the record offset
// as that's part of the final return value.
recordsToSkip := q.IndexOffset
recordOffset := q.IndexOffset
err := kvdb.View(f.db, func(tx kvdb.RTx) error {
// If the bucket wasn't found, then there aren't any events to
// be returned.
logBucket := tx.ReadBucket(forwardingLogBucket)
if logBucket == nil {
return ErrNoForwardingEvents
}
// We'll be using a cursor to seek into the database, so we'll
// populate byte slices that represent the start of the key
// space we're interested in, and the end.
var startTime, endTime [8]byte
byteOrder.PutUint64(startTime[:], uint64(q.StartTime.UnixNano()))
byteOrder.PutUint64(endTime[:], uint64(q.EndTime.UnixNano()))
// If we know that a set of log events exists, then we'll begin
// our seek through the log in order to satisfy the query.
// We'll continue until either we reach the end of the range,
// or reach our max number of events.
logCursor := logBucket.ReadCursor()
timestamp, events := logCursor.Seek(startTime[:])
for ; timestamp != nil && bytes.Compare(timestamp, endTime[:]) <= 0; timestamp, events = logCursor.Next() {
// If our current return payload exceeds the max number
// of events, then we'll exit now.
if uint32(len(resp.ForwardingEvents)) >= q.NumMaxEvents {
return nil
}
// If we're not yet past the user defined offset, then
// we'll continue to seek forward.
if recordsToSkip > 0 {
recordsToSkip--
continue
}
currentTime := time.Unix(
0, int64(byteOrder.Uint64(timestamp)),
)
// At this point, we've skipped enough records to start
// to collate our query. For each record, we'll
// increment the final record offset so the querier can
// utilize pagination to seek further.
readBuf := bytes.NewReader(events)
for readBuf.Len() != 0 {
var event ForwardingEvent
err := decodeForwardingEvent(readBuf, &event)
if err != nil {
return err
}
event.Timestamp = currentTime
resp.ForwardingEvents = append(resp.ForwardingEvents, event)
recordOffset++
}
}
return nil
}, func() {
resp = ForwardingLogTimeSlice{
ForwardingEventQuery: q,
}
})
if err != nil && err != ErrNoForwardingEvents {
return ForwardingLogTimeSlice{}, err
}
resp.LastIndexOffset = recordOffset
return resp, nil
}
// makeUniqueTimestamps takes a slice of forwarding events, sorts it by the
// event timestamps and then makes sure there are no duplicates in the
// timestamps. If duplicates are found, some of the timestamps are increased on
// the nanosecond scale until only unique values remain. This is a fix to
// address the problem that in some environments (looking at you, Windows) the
// system clock has such a bad resolution that two serial invocations of
// time.Now() might return the same timestamp, even if some time has elapsed
// between the calls.
func makeUniqueTimestamps(events []ForwardingEvent) {
sort.Slice(events, func(i, j int) bool {
return events[i].Timestamp.Before(events[j].Timestamp)
})
// Now that we know the events are sorted by timestamp, we can go
// through the list and fix all duplicates until only unique values
// remain.
for outer := 0; outer < len(events)-1; outer++ {
current := events[outer].Timestamp.UnixNano()
next := events[outer+1].Timestamp.UnixNano()
// We initially sorted the slice. So if the current is now
// greater or equal to the next one, it's either because it's a
// duplicate or because we increased the current in the last
// iteration.
if current >= next {
next = current + 1
events[outer+1].Timestamp = time.Unix(0, next)
}
}
}
package channeldb
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
)
// ErrCorruptedFwdPkg signals that the on-disk structure of the forwarding
// package has potentially been mangled.
var ErrCorruptedFwdPkg = errors.New("fwding package db has been corrupted")
// FwdState is an enum used to describe the lifecycle of a FwdPkg.
type FwdState byte
const (
// FwdStateLockedIn is the starting state for all forwarding packages.
// Packages in this state have not yet committed to the exact set of
// Adds to forward to the switch.
FwdStateLockedIn FwdState = iota
// FwdStateProcessed marks the state in which all Adds have been
// locally processed and the forwarding decision to the switch has been
// persisted.
FwdStateProcessed
// FwdStateCompleted signals that all Adds have been acked, and that all
// settles and fails have been delivered to their sources. Packages in
// this state can be removed permanently.
FwdStateCompleted
)
var (
// fwdPackagesKey is the root-level bucket that all forwarding packages
// are written. This bucket is further subdivided based on the short
// channel ID of each channel.
//
// Bucket hierarchy:
//
// fwdPackagesKey(root-bucket)
// |
// |-- <shortChannelID>
// | |
// | |-- <height>
// | | |-- ackFilterKey: <encoded bytes of PkgFilter>
// | | |-- settleFailFilterKey: <encoded bytes of PkgFilter>
// | | |-- fwdFilterKey: <encoded bytes of PkgFilter>
// | | |
// | | |-- addBucketKey
// | | | |-- <index of LogUpdate>: <encoded bytes of LogUpdate>
// | | | |-- <index of LogUpdate>: <encoded bytes of LogUpdate>
// | | | ...
// | | |
// | | |-- failSettleBucketKey
// | | |-- <index of LogUpdate>: <encoded bytes of LogUpdate>
// | | |-- <index of LogUpdate>: <encoded bytes of LogUpdate>
// | | ...
// | |
// | |-- <height>
// | | |
// | ... ...
// |
// |
// |-- <shortChannelID>
// | |
// | ...
// ...
//
fwdPackagesKey = []byte("fwd-packages")
// addBucketKey is the bucket to which all Add log updates are written.
addBucketKey = []byte("add-updates")
// failSettleBucketKey is the bucket to which all Settle/Fail log
// updates are written.
failSettleBucketKey = []byte("fail-settle-updates")
// fwdFilterKey is a key used to write the set of Adds that passed
// validation and are to be forwarded to the switch.
// NOTE: The presence of this key within a forwarding package indicates
// that the package has reached FwdStateProcessed.
fwdFilterKey = []byte("fwd-filter-key")
// ackFilterKey is a key used to access the PkgFilter indicating which
// Adds have received a Settle/Fail. This response may come from a
// number of sources, including: exitHop settle/fails, switch failures,
// chain arbiter interjections, as well as settle/fails from the
// next hop in the route.
ackFilterKey = []byte("ack-filter-key")
// settleFailFilterKey is a key used to access the PkgFilter indicating
// which Settles/Fails in have been received and processed by the link
// that originally received the Add.
settleFailFilterKey = []byte("settle-fail-filter-key")
)
// PkgFilter is used to compactly represent a particular subset of the Adds in a
// forwarding package. Each filter is represented as a simple, statically-sized
// bitvector, where the elements are intended to be the indices of the Adds as
// they are written in the FwdPkg.
type PkgFilter struct {
count uint16
filter []byte
}
// NewPkgFilter initializes an empty PkgFilter supporting `count` elements.
func NewPkgFilter(count uint16) *PkgFilter {
// We add 7 to ensure that the integer division yields properly rounded
// values.
filterLen := (count + 7) / 8
return &PkgFilter{
count: count,
filter: make([]byte, filterLen),
}
}
// Count returns the number of elements represented by this PkgFilter.
func (f *PkgFilter) Count() uint16 {
return f.count
}
// Set marks the `i`-th element as included by this filter.
// NOTE: It is assumed that i is always less than count.
func (f *PkgFilter) Set(i uint16) {
byt := i / 8
bit := i % 8
// Set the i-th bit in the filter.
// TODO(conner): ignore if > count to prevent panic?
f.filter[byt] |= byte(1 << (7 - bit))
}
// Contains queries the filter for membership of index `i`.
// NOTE: It is assumed that i is always less than count.
func (f *PkgFilter) Contains(i uint16) bool {
byt := i / 8
bit := i % 8
// Read the i-th bit in the filter.
// TODO(conner): ignore if > count to prevent panic?
return f.filter[byt]&(1<<(7-bit)) != 0
}
// Equal checks two PkgFilters for equality.
func (f *PkgFilter) Equal(f2 *PkgFilter) bool {
if f == f2 {
return true
}
if f.count != f2.count {
return false
}
return bytes.Equal(f.filter, f2.filter)
}
// IsFull returns true if every element in the filter has been Set, and false
// otherwise.
func (f *PkgFilter) IsFull() bool {
// Batch validate bytes that are fully used.
for i := uint16(0); i < f.count/8; i++ {
if f.filter[i] != 0xFF {
return false
}
}
// If the count is not a multiple of 8, check that the filter contains
// all remaining bits.
rem := f.count % 8
for idx := f.count - rem; idx < f.count; idx++ {
if !f.Contains(idx) {
return false
}
}
return true
}
// Size returns number of bytes produced when the PkgFilter is serialized.
func (f *PkgFilter) Size() uint16 {
// 2 bytes for uint16 `count`, then round up number of bytes required to
// represent `count` bits.
return 2 + (f.count+7)/8
}
// Encode writes the filter to the provided io.Writer.
func (f *PkgFilter) Encode(w io.Writer) error {
if err := binary.Write(w, binary.BigEndian, f.count); err != nil {
return err
}
_, err := w.Write(f.filter)
return err
}
// Decode reads the filter from the provided io.Reader.
func (f *PkgFilter) Decode(r io.Reader) error {
if err := binary.Read(r, binary.BigEndian, &f.count); err != nil {
return err
}
f.filter = make([]byte, f.Size()-2)
_, err := io.ReadFull(r, f.filter)
return err
}
// String returns a human-readable string.
func (f *PkgFilter) String() string {
return fmt.Sprintf("count=%v, filter=%v", f.count, f.filter)
}
// FwdPkg records all adds, settles, and fails that were locked in as a result
// of the remote peer sending us a revocation. Each package is identified by
// the short chanid and remote commitment height corresponding to the revocation
// that locked in the HTLCs. For everything except a locally initiated payment,
// settles and fails in a forwarding package must have a corresponding Add in
// another package, and can be removed individually once the source link has
// received the fail/settle.
//
// Adds cannot be removed, as we need to present the same batch of Adds to
// properly handle replay protection. Instead, we use a PkgFilter to mark that
// we have finished processing a particular Add. A FwdPkg should only be deleted
// after the AckFilter is full and all settles and fails have been persistently
// removed.
type FwdPkg struct {
// Source identifies the channel that wrote this forwarding package.
Source lnwire.ShortChannelID
// Height is the height of the remote commitment chain that locked in
// this forwarding package.
Height uint64
// State signals the persistent condition of the package and directs how
// to reprocess the package in the event of failures.
State FwdState
// Adds contains all add messages which need to be processed and
// forwarded to the switch. Adds does not change over the life of a
// forwarding package.
Adds []LogUpdate
// FwdFilter is a filter containing the indices of all Adds that were
// forwarded to the switch.
FwdFilter *PkgFilter
// AckFilter is a filter containing the indices of all Adds for which
// the source has received a settle or fail and is reflected in the next
// commitment txn. A package should not be removed until IsFull()
// returns true.
AckFilter *PkgFilter
// SettleFails contains all settle and fail messages that should be
// forwarded to the switch.
SettleFails []LogUpdate
// SettleFailFilter is a filter containing the indices of all Settle or
// Fails originating in this package that have been received and locked
// into the incoming link's commitment state.
SettleFailFilter *PkgFilter
}
// NewFwdPkg initializes a new forwarding package in FwdStateLockedIn. This
// should be used to create a package at the time we receive a revocation.
func NewFwdPkg(source lnwire.ShortChannelID, height uint64,
addUpdates, settleFailUpdates []LogUpdate) *FwdPkg {
nAddUpdates := uint16(len(addUpdates))
nSettleFailUpdates := uint16(len(settleFailUpdates))
return &FwdPkg{
Source: source,
Height: height,
State: FwdStateLockedIn,
Adds: addUpdates,
FwdFilter: NewPkgFilter(nAddUpdates),
AckFilter: NewPkgFilter(nAddUpdates),
SettleFails: settleFailUpdates,
SettleFailFilter: NewPkgFilter(nSettleFailUpdates),
}
}
// SourceRef is a convenience method that returns an AddRef to this forwarding
// package for the index in the argument. It is the caller's responsibility
// to ensure that the index is in bounds.
func (f *FwdPkg) SourceRef(i uint16) AddRef {
return AddRef{
Height: f.Height,
Index: i,
}
}
// DestRef is a convenience method that returns a SettleFailRef to this
// forwarding package for the index in the argument. It is the caller's
// responsibility to ensure that the index is in bounds.
func (f *FwdPkg) DestRef(i uint16) SettleFailRef {
return SettleFailRef{
Source: f.Source,
Height: f.Height,
Index: i,
}
}
// ID returns an unique identifier for this package, used to ensure that sphinx
// replay processing of this batch is idempotent.
func (f *FwdPkg) ID() []byte {
var id = make([]byte, 16)
byteOrder.PutUint64(id[:8], f.Source.ToUint64())
byteOrder.PutUint64(id[8:], f.Height)
return id
}
// String returns a human-readable description of the forwarding package.
func (f *FwdPkg) String() string {
return fmt.Sprintf("%T(src=%v, height=%v, nadds=%v, nfailsettles=%v)",
f, f.Source, f.Height, len(f.Adds), len(f.SettleFails))
}
// AddRef is used to identify a particular Add in a FwdPkg. The short channel ID
// is assumed to be that of the packager.
type AddRef struct {
// Height is the remote commitment height that locked in the Add.
Height uint64
// Index is the index of the Add within the fwd pkg's Adds.
//
// NOTE: This index is static over the lifetime of a forwarding package.
Index uint16
}
// Encode serializes the AddRef to the given io.Writer.
func (a *AddRef) Encode(w io.Writer) error {
if err := binary.Write(w, binary.BigEndian, a.Height); err != nil {
return err
}
return binary.Write(w, binary.BigEndian, a.Index)
}
// Decode deserializes the AddRef from the given io.Reader.
func (a *AddRef) Decode(r io.Reader) error {
if err := binary.Read(r, binary.BigEndian, &a.Height); err != nil {
return err
}
return binary.Read(r, binary.BigEndian, &a.Index)
}
// SettleFailRef is used to locate a Settle/Fail in another channel's FwdPkg. A
// channel does not remove its own Settle/Fail htlcs, so the source is provided
// to locate a db bucket belonging to another channel.
type SettleFailRef struct {
// Source identifies the outgoing link that locked in the settle or
// fail. This is then used by the *incoming* link to find the settle
// fail in another link's forwarding packages.
Source lnwire.ShortChannelID
// Height is the remote commitment height that locked in this
// Settle/Fail.
Height uint64
// Index is the index of the Add with the fwd pkg's SettleFails.
//
// NOTE: This index is static over the lifetime of a forwarding package.
Index uint16
}
// SettleFailAcker is a generic interface providing the ability to acknowledge
// settle/fail HTLCs stored in forwarding packages.
type SettleFailAcker interface {
// AckSettleFails atomically updates the settle-fail filters in *other*
// channels' forwarding packages.
AckSettleFails(tx kvdb.RwTx, settleFailRefs ...SettleFailRef) error
}
// GlobalFwdPkgReader is an interface used to retrieve the forwarding packages
// of any active channel.
type GlobalFwdPkgReader interface {
// LoadChannelFwdPkgs loads all known forwarding packages for the given
// channel.
LoadChannelFwdPkgs(tx kvdb.RTx,
source lnwire.ShortChannelID) ([]*FwdPkg, error)
}
// FwdOperator defines the interfaces for managing forwarding packages that are
// external to a particular channel. This interface is used by the switch to
// read forwarding packages from arbitrary channels, and acknowledge settles and
// fails for locally-sourced payments.
type FwdOperator interface {
// GlobalFwdPkgReader provides read access to all known forwarding
// packages
GlobalFwdPkgReader
// SettleFailAcker grants the ability to acknowledge settles or fails
// residing in arbitrary forwarding packages.
SettleFailAcker
}
// SwitchPackager is a concrete implementation of the FwdOperator interface.
// A SwitchPackager offers the ability to read any forwarding package, and ack
// arbitrary settle and fail HTLCs.
type SwitchPackager struct{}
// NewSwitchPackager instantiates a new SwitchPackager.
func NewSwitchPackager() *SwitchPackager {
return &SwitchPackager{}
}
// AckSettleFails atomically updates the settle-fail filters in *other*
// channels' forwarding packages, to mark that the switch has received a settle
// or fail residing in the forwarding package of a link.
func (*SwitchPackager) AckSettleFails(tx kvdb.RwTx,
settleFailRefs ...SettleFailRef) error {
return ackSettleFails(tx, settleFailRefs)
}
// LoadChannelFwdPkgs loads all forwarding packages for a particular channel.
func (*SwitchPackager) LoadChannelFwdPkgs(tx kvdb.RTx,
source lnwire.ShortChannelID) ([]*FwdPkg, error) {
return loadChannelFwdPkgs(tx, source)
}
// FwdPackager supports all operations required to modify fwd packages, such as
// creation, updates, reading, and removal. The interfaces are broken down in
// this way to support future delegation of the subinterfaces.
type FwdPackager interface {
// AddFwdPkg serializes and writes a FwdPkg for this channel at the
// remote commitment height included in the forwarding package.
AddFwdPkg(tx kvdb.RwTx, fwdPkg *FwdPkg) error
// SetFwdFilter looks up the forwarding package at the remote `height`
// and sets the `fwdFilter`, marking the Adds for which:
// 1) We are not the exit node
// 2) Passed all validation
// 3) Should be forwarded to the switch immediately after a failure
SetFwdFilter(tx kvdb.RwTx, height uint64, fwdFilter *PkgFilter) error
// AckAddHtlcs atomically updates the add filters in this channel's
// forwarding packages to mark the resolution of an Add that was
// received from the remote party.
AckAddHtlcs(tx kvdb.RwTx, addRefs ...AddRef) error
// SettleFailAcker allows a link to acknowledge settle/fail HTLCs
// belonging to other channels.
SettleFailAcker
// LoadFwdPkgs loads all known forwarding packages owned by this
// channel.
LoadFwdPkgs(tx kvdb.RTx) ([]*FwdPkg, error)
// RemovePkg deletes a forwarding package owned by this channel at
// the provided remote `height`.
RemovePkg(tx kvdb.RwTx, height uint64) error
// Wipe deletes all the forwarding packages owned by this channel.
Wipe(tx kvdb.RwTx) error
}
// ChannelPackager is used by a channel to manage the lifecycle of its forwarding
// packages. The packager is tied to a particular source channel ID, allowing it
// to create and edit its own packages. Each packager also has the ability to
// remove fail/settle htlcs that correspond to an add contained in one of
// source's packages.
type ChannelPackager struct {
source lnwire.ShortChannelID
}
// NewChannelPackager creates a new packager for a single channel.
func NewChannelPackager(source lnwire.ShortChannelID) *ChannelPackager {
return &ChannelPackager{
source: source,
}
}
// AddFwdPkg writes a newly locked in forwarding package to disk.
func (*ChannelPackager) AddFwdPkg(tx kvdb.RwTx, fwdPkg *FwdPkg) error { // nolint: dupl
fwdPkgBkt, err := tx.CreateTopLevelBucket(fwdPackagesKey)
if err != nil {
return err
}
source := makeLogKey(fwdPkg.Source.ToUint64())
sourceBkt, err := fwdPkgBkt.CreateBucketIfNotExists(source[:])
if err != nil {
return err
}
heightKey := makeLogKey(fwdPkg.Height)
heightBkt, err := sourceBkt.CreateBucketIfNotExists(heightKey[:])
if err != nil {
return err
}
// Write ADD updates we received at this commit height.
addBkt, err := heightBkt.CreateBucketIfNotExists(addBucketKey)
if err != nil {
return err
}
// Write SETTLE/FAIL updates we received at this commit height.
failSettleBkt, err := heightBkt.CreateBucketIfNotExists(failSettleBucketKey)
if err != nil {
return err
}
for i := range fwdPkg.Adds {
err = putLogUpdate(addBkt, uint16(i), &fwdPkg.Adds[i])
if err != nil {
return err
}
}
// Persist the initialized pkg filter, which will be used to determine
// when we can remove this forwarding package from disk.
var ackFilterBuf bytes.Buffer
if err := fwdPkg.AckFilter.Encode(&ackFilterBuf); err != nil {
return err
}
if err := heightBkt.Put(ackFilterKey, ackFilterBuf.Bytes()); err != nil {
return err
}
for i := range fwdPkg.SettleFails {
err = putLogUpdate(failSettleBkt, uint16(i), &fwdPkg.SettleFails[i])
if err != nil {
return err
}
}
var settleFailFilterBuf bytes.Buffer
err = fwdPkg.SettleFailFilter.Encode(&settleFailFilterBuf)
if err != nil {
return err
}
return heightBkt.Put(settleFailFilterKey, settleFailFilterBuf.Bytes())
}
// putLogUpdate writes an htlc to the provided `bkt`, using `index` as the key.
func putLogUpdate(bkt kvdb.RwBucket, idx uint16, htlc *LogUpdate) error {
var b bytes.Buffer
if err := serializeLogUpdate(&b, htlc); err != nil {
return err
}
return bkt.Put(uint16Key(idx), b.Bytes())
}
// LoadFwdPkgs scans the forwarding log for any packages that haven't been
// processed, and returns their deserialized log updates in a map indexed by the
// remote commitment height at which the updates were locked in.
func (p *ChannelPackager) LoadFwdPkgs(tx kvdb.RTx) ([]*FwdPkg, error) {
return loadChannelFwdPkgs(tx, p.source)
}
// loadChannelFwdPkgs loads all forwarding packages owned by `source`.
func loadChannelFwdPkgs(tx kvdb.RTx, source lnwire.ShortChannelID) ([]*FwdPkg, error) {
fwdPkgBkt := tx.ReadBucket(fwdPackagesKey)
if fwdPkgBkt == nil {
return nil, nil
}
sourceKey := makeLogKey(source.ToUint64())
sourceBkt := fwdPkgBkt.NestedReadBucket(sourceKey[:])
if sourceBkt == nil {
return nil, nil
}
var heights []uint64
if err := sourceBkt.ForEach(func(k, _ []byte) error {
if len(k) != 8 {
return ErrCorruptedFwdPkg
}
heights = append(heights, byteOrder.Uint64(k))
return nil
}); err != nil {
return nil, err
}
// Load the forwarding package for each retrieved height.
fwdPkgs := make([]*FwdPkg, 0, len(heights))
for _, height := range heights {
fwdPkg, err := loadFwdPkg(fwdPkgBkt, source, height)
if err != nil {
return nil, err
}
fwdPkgs = append(fwdPkgs, fwdPkg)
}
return fwdPkgs, nil
}
// loadFwdPkg reads the packager's fwd pkg at a given height, and determines the
// appropriate FwdState.
func loadFwdPkg(fwdPkgBkt kvdb.RBucket, source lnwire.ShortChannelID,
height uint64) (*FwdPkg, error) {
sourceKey := makeLogKey(source.ToUint64())
sourceBkt := fwdPkgBkt.NestedReadBucket(sourceKey[:])
if sourceBkt == nil {
return nil, ErrCorruptedFwdPkg
}
heightKey := makeLogKey(height)
heightBkt := sourceBkt.NestedReadBucket(heightKey[:])
if heightBkt == nil {
return nil, ErrCorruptedFwdPkg
}
// Load ADDs from disk.
addBkt := heightBkt.NestedReadBucket(addBucketKey)
if addBkt == nil {
return nil, ErrCorruptedFwdPkg
}
adds, err := loadHtlcs(addBkt)
if err != nil {
return nil, err
}
// Load ack filter from disk.
ackFilterBytes := heightBkt.Get(ackFilterKey)
if ackFilterBytes == nil {
return nil, ErrCorruptedFwdPkg
}
ackFilterReader := bytes.NewReader(ackFilterBytes)
ackFilter := &PkgFilter{}
if err := ackFilter.Decode(ackFilterReader); err != nil {
return nil, err
}
// Load SETTLE/FAILs from disk.
failSettleBkt := heightBkt.NestedReadBucket(failSettleBucketKey)
if failSettleBkt == nil {
return nil, ErrCorruptedFwdPkg
}
failSettles, err := loadHtlcs(failSettleBkt)
if err != nil {
return nil, err
}
// Load settle fail filter from disk.
settleFailFilterBytes := heightBkt.Get(settleFailFilterKey)
if settleFailFilterBytes == nil {
return nil, ErrCorruptedFwdPkg
}
settleFailFilterReader := bytes.NewReader(settleFailFilterBytes)
settleFailFilter := &PkgFilter{}
if err := settleFailFilter.Decode(settleFailFilterReader); err != nil {
return nil, err
}
// Initialize the fwding package, which always starts in the
// FwdStateLockedIn. We can determine what state the package was left in
// by examining constraints on the information loaded from disk.
fwdPkg := &FwdPkg{
Source: source,
State: FwdStateLockedIn,
Height: height,
Adds: adds,
AckFilter: ackFilter,
SettleFails: failSettles,
SettleFailFilter: settleFailFilter,
}
// Check to see if we have written the set exported filter adds to
// disk. If we haven't, processing of this package was never started, or
// failed during the last attempt.
fwdFilterBytes := heightBkt.Get(fwdFilterKey)
if fwdFilterBytes == nil {
nAdds := uint16(len(adds))
fwdPkg.FwdFilter = NewPkgFilter(nAdds)
return fwdPkg, nil
}
fwdFilterReader := bytes.NewReader(fwdFilterBytes)
fwdPkg.FwdFilter = &PkgFilter{}
if err := fwdPkg.FwdFilter.Decode(fwdFilterReader); err != nil {
return nil, err
}
// Otherwise, a complete round of processing was completed, and we
// advance the package to FwdStateProcessed.
fwdPkg.State = FwdStateProcessed
// If every add, settle, and fail has been fully acknowledged, we can
// safely set the package's state to FwdStateCompleted, signalling that
// it can be garbage collected.
if fwdPkg.AckFilter.IsFull() && fwdPkg.SettleFailFilter.IsFull() {
fwdPkg.State = FwdStateCompleted
}
return fwdPkg, nil
}
// loadHtlcs retrieves all serialized htlcs in a bucket, returning
// them in order of the indexes they were written under.
func loadHtlcs(bkt kvdb.RBucket) ([]LogUpdate, error) {
var htlcs []LogUpdate
if err := bkt.ForEach(func(_, v []byte) error {
htlc, err := deserializeLogUpdate(bytes.NewReader(v))
if err != nil {
return err
}
htlcs = append(htlcs, *htlc)
return nil
}); err != nil {
return nil, err
}
return htlcs, nil
}
// SetFwdFilter writes the set of indexes corresponding to Adds at the
// `height` that are to be forwarded to the switch. Calling this method causes
// the forwarding package at `height` to be in FwdStateProcessed. We write this
// forwarding decision so that we always arrive at the same behavior for HTLCs
// leaving this channel. After a restart, we skip validation of these Adds,
// since they are assumed to have already been validated, and make the switch or
// outgoing link responsible for handling replays.
func (p *ChannelPackager) SetFwdFilter(tx kvdb.RwTx, height uint64,
fwdFilter *PkgFilter) error {
fwdPkgBkt := tx.ReadWriteBucket(fwdPackagesKey)
if fwdPkgBkt == nil {
return ErrCorruptedFwdPkg
}
source := makeLogKey(p.source.ToUint64())
sourceBkt := fwdPkgBkt.NestedReadWriteBucket(source[:])
if sourceBkt == nil {
return ErrCorruptedFwdPkg
}
heightKey := makeLogKey(height)
heightBkt := sourceBkt.NestedReadWriteBucket(heightKey[:])
if heightBkt == nil {
return ErrCorruptedFwdPkg
}
// If the fwd filter has already been written, we return early to avoid
// modifying the persistent state.
forwardedAddsBytes := heightBkt.Get(fwdFilterKey)
if forwardedAddsBytes != nil {
return nil
}
// Otherwise we serialize and write the provided fwd filter.
var b bytes.Buffer
if err := fwdFilter.Encode(&b); err != nil {
return err
}
return heightBkt.Put(fwdFilterKey, b.Bytes())
}
// AckAddHtlcs accepts a list of references to add htlcs, and updates the
// AckAddFilter of those forwarding packages to indicate that a settle or fail
// has been received in response to the add.
func (p *ChannelPackager) AckAddHtlcs(tx kvdb.RwTx, addRefs ...AddRef) error {
if len(addRefs) == 0 {
return nil
}
fwdPkgBkt := tx.ReadWriteBucket(fwdPackagesKey)
if fwdPkgBkt == nil {
return ErrCorruptedFwdPkg
}
sourceKey := makeLogKey(p.source.ToUint64())
sourceBkt := fwdPkgBkt.NestedReadWriteBucket(sourceKey[:])
if sourceBkt == nil {
return ErrCorruptedFwdPkg
}
// Organize the forward references such that we just get a single slice
// of indexes for each unique height.
heightDiffs := make(map[uint64][]uint16)
for _, addRef := range addRefs {
heightDiffs[addRef.Height] = append(
heightDiffs[addRef.Height],
addRef.Index,
)
}
// Load each height bucket once and remove all acked htlcs at that
// height.
for height, indexes := range heightDiffs {
err := ackAddHtlcsAtHeight(sourceBkt, height, indexes)
if err != nil {
return err
}
}
return nil
}
// ackAddHtlcsAtHeight updates the AddAckFilter of a single forwarding package
// with a list of indexes, writing the resulting filter back in its place.
func ackAddHtlcsAtHeight(sourceBkt kvdb.RwBucket, height uint64,
indexes []uint16) error {
heightKey := makeLogKey(height)
heightBkt := sourceBkt.NestedReadWriteBucket(heightKey[:])
if heightBkt == nil {
// If the height bucket isn't found, this could be because the
// forwarding package was already removed. We'll return nil to
// signal that the operation is successful, as there is nothing
// to ack.
return nil
}
// Load ack filter from disk.
ackFilterBytes := heightBkt.Get(ackFilterKey)
if ackFilterBytes == nil {
return ErrCorruptedFwdPkg
}
ackFilter := &PkgFilter{}
ackFilterReader := bytes.NewReader(ackFilterBytes)
if err := ackFilter.Decode(ackFilterReader); err != nil {
return err
}
// Update the ack filter for this height.
for _, index := range indexes {
ackFilter.Set(index)
}
// Write the resulting filter to disk.
var ackFilterBuf bytes.Buffer
if err := ackFilter.Encode(&ackFilterBuf); err != nil {
return err
}
return heightBkt.Put(ackFilterKey, ackFilterBuf.Bytes())
}
// AckSettleFails persistently acknowledges settles or fails from a remote forwarding
// package. This should only be called after the source of the Add has locked in
// the settle/fail, or it becomes otherwise safe to forgo retransmitting the
// settle/fail after a restart.
func (p *ChannelPackager) AckSettleFails(tx kvdb.RwTx, settleFailRefs ...SettleFailRef) error {
return ackSettleFails(tx, settleFailRefs)
}
// ackSettleFails persistently acknowledges a batch of settle fail references.
func ackSettleFails(tx kvdb.RwTx, settleFailRefs []SettleFailRef) error {
if len(settleFailRefs) == 0 {
return nil
}
fwdPkgBkt := tx.ReadWriteBucket(fwdPackagesKey)
if fwdPkgBkt == nil {
return ErrCorruptedFwdPkg
}
// Organize the forward references such that we just get a single slice
// of indexes for each unique destination-height pair.
destHeightDiffs := make(map[lnwire.ShortChannelID]map[uint64][]uint16)
for _, settleFailRef := range settleFailRefs {
destHeights, ok := destHeightDiffs[settleFailRef.Source]
if !ok {
destHeights = make(map[uint64][]uint16)
destHeightDiffs[settleFailRef.Source] = destHeights
}
destHeights[settleFailRef.Height] = append(
destHeights[settleFailRef.Height],
settleFailRef.Index,
)
}
// With the references organized by destination and height, we now load
// each remote bucket, and update the settle fail filter for any
// settle/fail htlcs.
for dest, destHeights := range destHeightDiffs {
destKey := makeLogKey(dest.ToUint64())
destBkt := fwdPkgBkt.NestedReadWriteBucket(destKey[:])
if destBkt == nil {
// If the destination bucket is not found, this is
// likely the result of the destination channel being
// closed and having it's forwarding packages wiped. We
// won't treat this as an error, because the response
// will no longer be retransmitted internally.
continue
}
for height, indexes := range destHeights {
err := ackSettleFailsAtHeight(destBkt, height, indexes)
if err != nil {
return err
}
}
}
return nil
}
// ackSettleFailsAtHeight given a destination bucket, acks the provided indexes
// at particular a height by updating the settle fail filter.
func ackSettleFailsAtHeight(destBkt kvdb.RwBucket, height uint64,
indexes []uint16) error {
heightKey := makeLogKey(height)
heightBkt := destBkt.NestedReadWriteBucket(heightKey[:])
if heightBkt == nil {
// If the height bucket isn't found, this could be because the
// forwarding package was already removed. We'll return nil to
// signal that the operation is as there is nothing to ack.
return nil
}
// Load ack filter from disk.
settleFailFilterBytes := heightBkt.Get(settleFailFilterKey)
if settleFailFilterBytes == nil {
return ErrCorruptedFwdPkg
}
settleFailFilter := &PkgFilter{}
settleFailFilterReader := bytes.NewReader(settleFailFilterBytes)
if err := settleFailFilter.Decode(settleFailFilterReader); err != nil {
return err
}
// Update the ack filter for this height.
for _, index := range indexes {
settleFailFilter.Set(index)
}
// Write the resulting filter to disk.
var settleFailFilterBuf bytes.Buffer
if err := settleFailFilter.Encode(&settleFailFilterBuf); err != nil {
return err
}
return heightBkt.Put(settleFailFilterKey, settleFailFilterBuf.Bytes())
}
// RemovePkg deletes the forwarding package at the given height from the
// packager's source bucket.
func (p *ChannelPackager) RemovePkg(tx kvdb.RwTx, height uint64) error {
fwdPkgBkt := tx.ReadWriteBucket(fwdPackagesKey)
if fwdPkgBkt == nil {
return nil
}
sourceBytes := makeLogKey(p.source.ToUint64())
sourceBkt := fwdPkgBkt.NestedReadWriteBucket(sourceBytes[:])
if sourceBkt == nil {
return ErrCorruptedFwdPkg
}
heightKey := makeLogKey(height)
return sourceBkt.DeleteNestedBucket(heightKey[:])
}
// Wipe deletes all the channel's forwarding packages, if any.
func (p *ChannelPackager) Wipe(tx kvdb.RwTx) error {
// If the root bucket doesn't exist, there's no need to delete.
fwdPkgBkt := tx.ReadWriteBucket(fwdPackagesKey)
if fwdPkgBkt == nil {
return nil
}
sourceBytes := makeLogKey(p.source.ToUint64())
// If the nested bucket doesn't exist, there's no need to delete.
if fwdPkgBkt.NestedReadWriteBucket(sourceBytes[:]) == nil {
return nil
}
return fwdPkgBkt.DeleteNestedBucket(sourceBytes[:])
}
// uint16Key writes the provided 16-bit unsigned integer to a 2-byte slice.
func uint16Key(i uint16) []byte {
key := make([]byte, 2)
byteOrder.PutUint16(key, i)
return key
}
// Compile-time constraint to ensure that ChannelPackager implements the public
// FwdPackager interface.
var _ FwdPackager = (*ChannelPackager)(nil)
// Compile-time constraint to ensure that SwitchPackager implements the public
// FwdOperator interface.
var _ FwdOperator = (*SwitchPackager)(nil)
package channeldb
import (
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// initialChannelForwardingPolicyBucket is the database bucket used to
// store the forwarding policy for each permanent channel that is
// currently in the process of being opened.
initialChannelForwardingPolicyBucket = []byte(
"initialChannelFwdingPolicy",
)
)
// SaveInitialForwardingPolicy saves the serialized forwarding policy for the
// provided permanent channel id to the initialChannelForwardingPolicyBucket.
func (c *ChannelStateDB) SaveInitialForwardingPolicy(chanID lnwire.ChannelID,
forwardingPolicy *models.ForwardingPolicy) error {
chanIDCopy := make([]byte, 32)
copy(chanIDCopy, chanID[:])
scratch := make([]byte, 36)
byteOrder.PutUint64(scratch[:8], uint64(forwardingPolicy.MinHTLCOut))
byteOrder.PutUint64(scratch[8:16], uint64(forwardingPolicy.MaxHTLC))
byteOrder.PutUint64(scratch[16:24], uint64(forwardingPolicy.BaseFee))
byteOrder.PutUint64(scratch[24:32], uint64(forwardingPolicy.FeeRate))
byteOrder.PutUint32(scratch[32:], forwardingPolicy.TimeLockDelta)
return kvdb.Update(c.backend, func(tx kvdb.RwTx) error {
bucket, err := tx.CreateTopLevelBucket(
initialChannelForwardingPolicyBucket,
)
if err != nil {
return err
}
return bucket.Put(chanIDCopy, scratch)
}, func() {})
}
// GetInitialForwardingPolicy fetches the serialized forwarding policy for the
// provided channel id from the database, or returns ErrChannelNotFound if
// a forwarding policy for this channel id is not found.
func (c *ChannelStateDB) GetInitialForwardingPolicy(
chanID lnwire.ChannelID) (*models.ForwardingPolicy, error) {
chanIDCopy := make([]byte, 32)
copy(chanIDCopy, chanID[:])
var forwardingPolicy *models.ForwardingPolicy
err := kvdb.View(c.backend, func(tx kvdb.RTx) error {
bucket := tx.ReadBucket(initialChannelForwardingPolicyBucket)
if bucket == nil {
// If the bucket does not exist, it means we
// never added a channel fees to the db, so
// return ErrChannelNotFound.
return ErrChannelNotFound
}
stateBytes := bucket.Get(chanIDCopy)
if stateBytes == nil {
return ErrChannelNotFound
}
forwardingPolicy = &models.ForwardingPolicy{
MinHTLCOut: lnwire.MilliSatoshi(
byteOrder.Uint64(stateBytes[:8]),
),
MaxHTLC: lnwire.MilliSatoshi(
byteOrder.Uint64(stateBytes[8:16]),
),
BaseFee: lnwire.MilliSatoshi(
byteOrder.Uint64(stateBytes[16:24]),
),
FeeRate: lnwire.MilliSatoshi(
byteOrder.Uint64(stateBytes[24:32]),
),
TimeLockDelta: byteOrder.Uint32(stateBytes[32:36]),
}
return nil
}, func() {
forwardingPolicy = nil
})
return forwardingPolicy, err
}
// DeleteInitialForwardingPolicy removes the forwarding policy for a given
// channel from the database.
func (c *ChannelStateDB) DeleteInitialForwardingPolicy(
chanID lnwire.ChannelID) error {
chanIDCopy := make([]byte, 32)
copy(chanIDCopy, chanID[:])
return kvdb.Update(c.backend, func(tx kvdb.RwTx) error {
bucket := tx.ReadWriteBucket(
initialChannelForwardingPolicyBucket,
)
if bucket == nil {
return ErrChannelNotFound
}
return bucket.Delete(chanIDCopy)
}, func() {})
}
package channeldb
import (
"bytes"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
// spendHintBucket is the name of the bucket which houses the height
// hint for outpoints. Each height hint represents the earliest height
// at which its corresponding outpoint could have been spent within.
spendHintBucket = []byte("spend-hints")
// confirmHintBucket is the name of the bucket which houses the height
// hints for transactions. Each height hint represents the earliest
// height at which its corresponding transaction could have been
// confirmed within.
confirmHintBucket = []byte("confirm-hints")
)
// CacheConfig contains the HeightHintCache configuration.
type CacheConfig struct {
// QueryDisable prevents reliance on the Height Hint Cache. This is
// necessary to recover from an edge case when the height recorded in
// the cache is higher than the actual height of a spend, causing a
// channel to become "stuck" in a pending close state.
QueryDisable bool
}
// HeightHintCache is an implementation of the SpendHintCache and
// ConfirmHintCache interfaces backed by a channeldb DB instance where the hints
// will be stored.
type HeightHintCache struct {
cfg CacheConfig
db kvdb.Backend
}
// Compile-time checks to ensure HeightHintCache satisfies the SpendHintCache
// and ConfirmHintCache interfaces.
var _ chainntnfs.SpendHintCache = (*HeightHintCache)(nil)
var _ chainntnfs.ConfirmHintCache = (*HeightHintCache)(nil)
// NewHeightHintCache returns a new height hint cache backed by a database.
func NewHeightHintCache(cfg CacheConfig, db kvdb.Backend) (*HeightHintCache,
error) {
cache := &HeightHintCache{cfg, db}
if err := cache.initBuckets(); err != nil {
return nil, err
}
return cache, nil
}
// initBuckets ensures that the primary buckets used by the circuit are
// initialized so that we can assume their existence after startup.
func (c *HeightHintCache) initBuckets() error {
return kvdb.Batch(c.db, func(tx kvdb.RwTx) error {
_, err := tx.CreateTopLevelBucket(spendHintBucket)
if err != nil {
return err
}
_, err = tx.CreateTopLevelBucket(confirmHintBucket)
return err
})
}
// CommitSpendHint commits a spend hint for the outpoints to the cache.
func (c *HeightHintCache) CommitSpendHint(height uint32,
spendRequests ...chainntnfs.SpendRequest) error {
if len(spendRequests) == 0 {
return nil
}
log.Tracef("Updating spend hint to height %d for %v", height,
spendRequests)
return kvdb.Batch(c.db, func(tx kvdb.RwTx) error {
spendHints := tx.ReadWriteBucket(spendHintBucket)
if spendHints == nil {
return chainntnfs.ErrCorruptedHeightHintCache
}
var hint bytes.Buffer
if err := WriteElement(&hint, height); err != nil {
return err
}
for _, spendRequest := range spendRequests {
spendRequest := spendRequest
spendHintKey, err := spendHintKey(&spendRequest)
if err != nil {
return err
}
err = spendHints.Put(spendHintKey, hint.Bytes())
if err != nil {
return err
}
}
return nil
})
}
// QuerySpendHint returns the latest spend hint for an outpoint.
// ErrSpendHintNotFound is returned if a spend hint does not exist within the
// cache for the outpoint.
func (c *HeightHintCache) QuerySpendHint(
spendRequest chainntnfs.SpendRequest) (uint32, error) {
var hint uint32
if c.cfg.QueryDisable {
log.Debugf("Ignoring spend height hint for %v (height hint "+
"cache query disabled)", spendRequest)
return 0, nil
}
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
spendHints := tx.ReadBucket(spendHintBucket)
if spendHints == nil {
return chainntnfs.ErrCorruptedHeightHintCache
}
spendHintKey, err := spendHintKey(&spendRequest)
if err != nil {
return err
}
spendHint := spendHints.Get(spendHintKey)
if spendHint == nil {
return chainntnfs.ErrSpendHintNotFound
}
return ReadElement(bytes.NewReader(spendHint), &hint)
}, func() {
hint = 0
})
if err != nil {
return 0, err
}
return hint, nil
}
// PurgeSpendHint removes the spend hint for the outpoints from the cache.
func (c *HeightHintCache) PurgeSpendHint(
spendRequests ...chainntnfs.SpendRequest) error {
if len(spendRequests) == 0 {
return nil
}
log.Tracef("Removing spend hints for %v", spendRequests)
return kvdb.Batch(c.db, func(tx kvdb.RwTx) error {
spendHints := tx.ReadWriteBucket(spendHintBucket)
if spendHints == nil {
return chainntnfs.ErrCorruptedHeightHintCache
}
for _, spendRequest := range spendRequests {
spendRequest := spendRequest
spendHintKey, err := spendHintKey(&spendRequest)
if err != nil {
return err
}
if err := spendHints.Delete(spendHintKey); err != nil {
return err
}
}
return nil
})
}
// CommitConfirmHint commits a confirm hint for the transactions to the cache.
func (c *HeightHintCache) CommitConfirmHint(height uint32,
confRequests ...chainntnfs.ConfRequest) error {
if len(confRequests) == 0 {
return nil
}
log.Tracef("Updating confirm hints to height %d for %v", height,
confRequests)
return kvdb.Batch(c.db, func(tx kvdb.RwTx) error {
confirmHints := tx.ReadWriteBucket(confirmHintBucket)
if confirmHints == nil {
return chainntnfs.ErrCorruptedHeightHintCache
}
var hint bytes.Buffer
if err := WriteElement(&hint, height); err != nil {
return err
}
for _, confRequest := range confRequests {
confRequest := confRequest
confHintKey, err := confHintKey(&confRequest)
if err != nil {
return err
}
err = confirmHints.Put(confHintKey, hint.Bytes())
if err != nil {
return err
}
}
return nil
})
}
// QueryConfirmHint returns the latest confirm hint for a transaction hash.
// ErrConfirmHintNotFound is returned if a confirm hint does not exist within
// the cache for the transaction hash.
func (c *HeightHintCache) QueryConfirmHint(
confRequest chainntnfs.ConfRequest) (uint32, error) {
var hint uint32
if c.cfg.QueryDisable {
log.Debugf("Ignoring confirmation height hint for %v (height "+
"hint cache query disabled)", confRequest)
return 0, nil
}
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
confirmHints := tx.ReadBucket(confirmHintBucket)
if confirmHints == nil {
return chainntnfs.ErrCorruptedHeightHintCache
}
confHintKey, err := confHintKey(&confRequest)
if err != nil {
return err
}
confirmHint := confirmHints.Get(confHintKey)
if confirmHint == nil {
return chainntnfs.ErrConfirmHintNotFound
}
return ReadElement(bytes.NewReader(confirmHint), &hint)
}, func() {
hint = 0
})
if err != nil {
return 0, err
}
return hint, nil
}
// PurgeConfirmHint removes the confirm hint for the transactions from the
// cache.
func (c *HeightHintCache) PurgeConfirmHint(
confRequests ...chainntnfs.ConfRequest) error {
if len(confRequests) == 0 {
return nil
}
log.Tracef("Removing confirm hints for %v", confRequests)
return kvdb.Batch(c.db, func(tx kvdb.RwTx) error {
confirmHints := tx.ReadWriteBucket(confirmHintBucket)
if confirmHints == nil {
return chainntnfs.ErrCorruptedHeightHintCache
}
for _, confRequest := range confRequests {
confRequest := confRequest
confHintKey, err := confHintKey(&confRequest)
if err != nil {
return err
}
if err := confirmHints.Delete(confHintKey); err != nil {
return err
}
}
return nil
})
}
// confHintKey returns the key that will be used to index the confirmation
// request's hint within the height hint cache.
func confHintKey(r *chainntnfs.ConfRequest) ([]byte, error) {
if r.TxID == chainntnfs.ZeroHash {
return r.PkScript.Script(), nil
}
var txid bytes.Buffer
if err := WriteElement(&txid, r.TxID); err != nil {
return nil, err
}
return txid.Bytes(), nil
}
// spendHintKey returns the key that will be used to index the spend request's
// hint within the height hint cache.
func spendHintKey(r *chainntnfs.SpendRequest) ([]byte, error) {
if r.OutPoint == chainntnfs.ZeroOutPoint {
return r.PkScript.Script(), nil
}
var outpoint bytes.Buffer
err := WriteElement(&outpoint, r.OutPoint)
if err != nil {
return nil, err
}
return outpoint.Bytes(), nil
}
package channeldb
import (
"bytes"
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"maps"
"time"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
invpkg "github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/tlv"
)
var (
// invoiceBucket is the name of the bucket within the database that
// stores all data related to invoices no matter their final state.
// Within the invoice bucket, each invoice is keyed by its invoice ID
// which is a monotonically increasing uint32.
invoiceBucket = []byte("invoices")
// paymentHashIndexBucket is the name of the sub-bucket within the
// invoiceBucket which indexes all invoices by their payment hash. The
// payment hash is the sha256 of the invoice's payment preimage. This
// index is used to detect duplicates, and also to provide a fast path
// for looking up incoming HTLCs to determine if we're able to settle
// them fully.
//
// maps: payHash => invoiceKey
invoiceIndexBucket = []byte("paymenthashes")
// payAddrIndexBucket is the name of the top-level bucket that maps
// payment addresses to their invoice number. This can be used
// to efficiently query or update non-legacy invoices. Note that legacy
// invoices will not be included in this index since they all have the
// same, all-zero payment address, however all newly generated invoices
// will end up in this index.
//
// maps: payAddr => invoiceKey
payAddrIndexBucket = []byte("pay-addr-index")
// setIDIndexBucket is the name of the top-level bucket that maps set
// ids to their invoice number. This can be used to efficiently query or
// update AMP invoice. Note that legacy or MPP invoices will not be
// included in this index, since their HTLCs do not have a set id.
//
// maps: setID => invoiceKey
setIDIndexBucket = []byte("set-id-index")
// numInvoicesKey is the name of key which houses the auto-incrementing
// invoice ID which is essentially used as a primary key. With each
// invoice inserted, the primary key is incremented by one. This key is
// stored within the invoiceIndexBucket. Within the invoiceBucket
// invoices are uniquely identified by the invoice ID.
numInvoicesKey = []byte("nik")
// addIndexBucket is an index bucket that we'll use to create a
// monotonically increasing set of add indexes. Each time we add a new
// invoice, this sequence number will be incremented and then populated
// within the new invoice.
//
// In addition to this sequence number, we map:
//
// addIndexNo => invoiceKey
addIndexBucket = []byte("invoice-add-index")
// settleIndexBucket is an index bucket that we'll use to create a
// monotonically increasing integer for tracking a "settle index". Each
// time an invoice is settled, this sequence number will be incremented
// as populate within the newly settled invoice.
//
// In addition to this sequence number, we map:
//
// settleIndexNo => invoiceKey
settleIndexBucket = []byte("invoice-settle-index")
// invoiceBucketTombstone is a special key that indicates the invoice
// bucket has been permanently closed. Its purpose is to prevent the
// invoice bucket from being reopened in the future. A key use case for
// the tombstone is to ensure users cannot switch back to the KV invoice
// database after migrating to the native SQL database.
invoiceBucketTombstone = []byte("invoice-tombstone")
)
const (
// A set of tlv type definitions used to serialize invoice htlcs to the
// database.
//
// NOTE: A migration should be added whenever this list changes. This
// prevents against the database being rolled back to an older
// format where the surrounding logic might assume a different set of
// fields are known.
chanIDType tlv.Type = 1
htlcIDType tlv.Type = 3
amtType tlv.Type = 5
acceptHeightType tlv.Type = 7
acceptTimeType tlv.Type = 9
resolveTimeType tlv.Type = 11
expiryHeightType tlv.Type = 13
htlcStateType tlv.Type = 15
mppTotalAmtType tlv.Type = 17
htlcAMPType tlv.Type = 19
htlcHashType tlv.Type = 21
htlcPreimageType tlv.Type = 23
// A set of tlv type definitions used to serialize invoice bodiees.
//
// NOTE: A migration should be added whenever this list changes. This
// prevents against the database being rolled back to an older
// format where the surrounding logic might assume a different set of
// fields are known.
memoType tlv.Type = 0
payReqType tlv.Type = 1
createTimeType tlv.Type = 2
settleTimeType tlv.Type = 3
addIndexType tlv.Type = 4
settleIndexType tlv.Type = 5
preimageType tlv.Type = 6
valueType tlv.Type = 7
cltvDeltaType tlv.Type = 8
expiryType tlv.Type = 9
paymentAddrType tlv.Type = 10
featuresType tlv.Type = 11
invStateType tlv.Type = 12
amtPaidType tlv.Type = 13
hodlInvoiceType tlv.Type = 14
invoiceAmpStateType tlv.Type = 15
// A set of tlv type definitions used to serialize the invoice AMP
// state along-side the main invoice body.
ampStateSetIDType tlv.Type = 0
ampStateHtlcStateType tlv.Type = 1
ampStateSettleIndexType tlv.Type = 2
ampStateSettleDateType tlv.Type = 3
ampStateCircuitKeysType tlv.Type = 4
ampStateAmtPaidType tlv.Type = 5
)
// AddInvoice inserts the targeted invoice into the database. If the invoice has
// *any* payment hashes which already exists within the database, then the
// insertion will be aborted and rejected due to the strict policy banning any
// duplicate payment hashes. A side effect of this function is that it sets
// AddIndex on newInvoice.
func (d *DB) AddInvoice(_ context.Context, newInvoice *invpkg.Invoice,
paymentHash lntypes.Hash) (uint64, error) {
if err := invpkg.ValidateInvoice(newInvoice, paymentHash); err != nil {
return 0, err
}
var invoiceAddIndex uint64
err := kvdb.Update(d, func(tx kvdb.RwTx) error {
invoices, err := tx.CreateTopLevelBucket(invoiceBucket)
if err != nil {
return err
}
invoiceIndex, err := invoices.CreateBucketIfNotExists(
invoiceIndexBucket,
)
if err != nil {
return err
}
addIndex, err := invoices.CreateBucketIfNotExists(
addIndexBucket,
)
if err != nil {
return err
}
// Ensure that an invoice an identical payment hash doesn't
// already exist within the index.
if invoiceIndex.Get(paymentHash[:]) != nil {
return invpkg.ErrDuplicateInvoice
}
// Check that we aren't inserting an invoice with a duplicate
// payment address. The all-zeros payment address is
// special-cased to support legacy keysend invoices which don't
// assign one. This is safe since later we also will avoid
// indexing them and avoid collisions.
payAddrIndex := tx.ReadWriteBucket(payAddrIndexBucket)
if newInvoice.Terms.PaymentAddr != invpkg.BlankPayAddr {
paymentAddr := newInvoice.Terms.PaymentAddr[:]
if payAddrIndex.Get(paymentAddr) != nil {
return invpkg.ErrDuplicatePayAddr
}
}
// If the current running payment ID counter hasn't yet been
// created, then create it now.
var invoiceNum uint32
invoiceCounter := invoiceIndex.Get(numInvoicesKey)
if invoiceCounter == nil {
var scratch [4]byte
byteOrder.PutUint32(scratch[:], invoiceNum)
err := invoiceIndex.Put(numInvoicesKey, scratch[:])
if err != nil {
return err
}
} else {
invoiceNum = byteOrder.Uint32(invoiceCounter)
}
newIndex, err := putInvoice(
invoices, invoiceIndex, payAddrIndex, addIndex,
newInvoice, invoiceNum, paymentHash,
)
if err != nil {
return err
}
invoiceAddIndex = newIndex
return nil
}, func() {
invoiceAddIndex = 0
})
if err != nil {
return 0, err
}
return invoiceAddIndex, err
}
// InvoicesAddedSince can be used by callers to seek into the event time series
// of all the invoices added in the database. The specified sinceAddIndex
// should be the highest add index that the caller knows of. This method will
// return all invoices with an add index greater than the specified
// sinceAddIndex.
//
// NOTE: The index starts from 1, as a result. We enforce that specifying a
// value below the starting index value is a noop.
func (d *DB) InvoicesAddedSince(_ context.Context, sinceAddIndex uint64) (
[]invpkg.Invoice, error) {
var newInvoices []invpkg.Invoice
// If an index of zero was specified, then in order to maintain
// backwards compat, we won't send out any new invoices.
if sinceAddIndex == 0 {
return newInvoices, nil
}
var startIndex [8]byte
byteOrder.PutUint64(startIndex[:], sinceAddIndex)
err := kvdb.View(d, func(tx kvdb.RTx) error {
invoices := tx.ReadBucket(invoiceBucket)
if invoices == nil {
return nil
}
addIndex := invoices.NestedReadBucket(addIndexBucket)
if addIndex == nil {
return nil
}
// We'll now run through each entry in the add index starting
// at our starting index. We'll continue until we reach the
// very end of the current key space.
invoiceCursor := addIndex.ReadCursor()
// We'll seek to the starting index, then manually advance the
// cursor in order to skip the entry with the since add index.
invoiceCursor.Seek(startIndex[:])
addSeqNo, invoiceKey := invoiceCursor.Next()
for ; addSeqNo != nil && bytes.Compare(addSeqNo, startIndex[:]) > 0; addSeqNo, invoiceKey = invoiceCursor.Next() {
// For each key found, we'll look up the actual
// invoice, then accumulate it into our return value.
invoice, err := fetchInvoice(
invoiceKey, invoices, nil, false,
)
if err != nil {
return err
}
newInvoices = append(newInvoices, invoice)
}
return nil
}, func() {
newInvoices = nil
})
if err != nil {
return nil, err
}
return newInvoices, nil
}
// LookupInvoice attempts to look up an invoice according to its 32 byte
// payment hash. If an invoice which can settle the HTLC identified by the
// passed payment hash isn't found, then an error is returned. Otherwise, the
// full invoice is returned. Before setting the incoming HTLC, the values
// SHOULD be checked to ensure the payer meets the agreed upon contractual
// terms of the payment.
func (d *DB) LookupInvoice(_ context.Context, ref invpkg.InvoiceRef) (
invpkg.Invoice, error) {
var invoice invpkg.Invoice
err := kvdb.View(d, func(tx kvdb.RTx) error {
invoices := tx.ReadBucket(invoiceBucket)
if invoices == nil {
return invpkg.ErrNoInvoicesCreated
}
invoiceIndex := invoices.NestedReadBucket(invoiceIndexBucket)
if invoiceIndex == nil {
return invpkg.ErrNoInvoicesCreated
}
payAddrIndex := tx.ReadBucket(payAddrIndexBucket)
setIDIndex := tx.ReadBucket(setIDIndexBucket)
// Retrieve the invoice number for this invoice using
// the provided invoice reference.
invoiceNum, err := fetchInvoiceNumByRef(
invoiceIndex, payAddrIndex, setIDIndex, ref,
)
if err != nil {
return err
}
var setID *invpkg.SetID
switch {
// If this is a payment address ref, and the blank modified was
// specified, then we'll use the zero set ID to indicate that
// we won't want any HTLCs returned.
case ref.PayAddr() != nil &&
ref.Modifier() == invpkg.HtlcSetBlankModifier:
var zeroSetID invpkg.SetID
setID = &zeroSetID
// If this is a set ID ref, and the htlc set only modified was
// specified, then we'll pass through the specified setID so
// only that will be returned.
case ref.SetID() != nil &&
ref.Modifier() == invpkg.HtlcSetOnlyModifier:
setID = (*invpkg.SetID)(ref.SetID())
}
// An invoice was found, retrieve the remainder of the invoice
// body.
i, err := fetchInvoice(
invoiceNum, invoices, []*invpkg.SetID{setID}, true,
)
if err != nil {
return err
}
invoice = i
return nil
}, func() {})
if err != nil {
return invoice, err
}
return invoice, nil
}
// fetchInvoiceNumByRef retrieve the invoice number for the provided invoice
// reference. The payment address will be treated as the primary key, falling
// back to the payment hash if nothing is found for the payment address. An
// error is returned if the invoice is not found.
func fetchInvoiceNumByRef(invoiceIndex, payAddrIndex, setIDIndex kvdb.RBucket,
ref invpkg.InvoiceRef) ([]byte, error) {
// If the set id is present, we only consult the set id index for this
// invoice. This type of query is only used to facilitate user-facing
// requests to lookup, settle or cancel an AMP invoice.
setID := ref.SetID()
if setID != nil {
invoiceNumBySetID := setIDIndex.Get(setID[:])
if invoiceNumBySetID == nil {
return nil, invpkg.ErrInvoiceNotFound
}
return invoiceNumBySetID, nil
}
payHash := ref.PayHash()
payAddr := ref.PayAddr()
getInvoiceNumByHash := func() []byte {
if payHash != nil {
return invoiceIndex.Get(payHash[:])
}
return nil
}
getInvoiceNumByAddr := func() []byte {
if payAddr != nil {
// Only allow lookups for payment address if it is not a
// blank payment address, which is a special-cased value
// for legacy keysend invoices.
if *payAddr != invpkg.BlankPayAddr {
return payAddrIndex.Get(payAddr[:])
}
}
return nil
}
invoiceNumByHash := getInvoiceNumByHash()
invoiceNumByAddr := getInvoiceNumByAddr()
switch {
// If payment address and payment hash both reference an existing
// invoice, ensure they reference the _same_ invoice.
case invoiceNumByAddr != nil && invoiceNumByHash != nil:
if !bytes.Equal(invoiceNumByAddr, invoiceNumByHash) {
return nil, invpkg.ErrInvRefEquivocation
}
return invoiceNumByAddr, nil
// Return invoices by payment addr only.
//
// NOTE: We constrain this lookup to only apply if the invoice ref does
// not contain a payment hash. Legacy and MPP payments depend on the
// payment hash index to enforce that the HTLCs payment hash matches the
// payment hash for the invoice, without this check we would
// inadvertently assume the invoice contains the correct preimage for
// the HTLC, which we only enforce via the lookup by the invoice index.
case invoiceNumByAddr != nil && payHash == nil:
return invoiceNumByAddr, nil
// If we were only able to reference the invoice by hash, return the
// corresponding invoice number. This can happen when no payment address
// was provided, or if it didn't match anything in our records.
case invoiceNumByHash != nil:
return invoiceNumByHash, nil
// Otherwise we don't know of the target invoice.
default:
return nil, invpkg.ErrInvoiceNotFound
}
}
// FetchPendingInvoices returns all invoices that have not yet been settled or
// canceled. The returned map is keyed by the payment hash of each respective
// invoice.
func (d *DB) FetchPendingInvoices(_ context.Context) (
map[lntypes.Hash]invpkg.Invoice, error) {
result := make(map[lntypes.Hash]invpkg.Invoice)
err := kvdb.View(d, func(tx kvdb.RTx) error {
invoices := tx.ReadBucket(invoiceBucket)
if invoices == nil {
return nil
}
invoiceIndex := invoices.NestedReadBucket(invoiceIndexBucket)
if invoiceIndex == nil {
// Mask the error if there's no invoice
// index as that simply means there are no
// invoices added yet to the DB. In this case
// we simply return an empty list.
return nil
}
return invoiceIndex.ForEach(func(k, v []byte) error {
// Skip the special numInvoicesKey as that does not
// point to a valid invoice.
if bytes.Equal(k, numInvoicesKey) {
return nil
}
// Skip sub-buckets.
if v == nil {
return nil
}
invoice, err := fetchInvoice(v, invoices, nil, false)
if err != nil {
return err
}
if invoice.IsPending() {
var paymentHash lntypes.Hash
copy(paymentHash[:], k)
result[paymentHash] = invoice
}
return nil
})
}, func() {
result = make(map[lntypes.Hash]invpkg.Invoice)
})
if err != nil {
return nil, err
}
return result, nil
}
// QueryInvoices allows a caller to query the invoice database for invoices
// within the specified add index range.
func (d *DB) QueryInvoices(_ context.Context, q invpkg.InvoiceQuery) (
invpkg.InvoiceSlice, error) {
var resp invpkg.InvoiceSlice
err := kvdb.View(d, func(tx kvdb.RTx) error {
// If the bucket wasn't found, then there aren't any invoices
// within the database yet, so we can simply exit.
invoices := tx.ReadBucket(invoiceBucket)
if invoices == nil {
return invpkg.ErrNoInvoicesCreated
}
// Get the add index bucket which we will use to iterate through
// our indexed invoices.
invoiceAddIndex := invoices.NestedReadBucket(addIndexBucket)
if invoiceAddIndex == nil {
return invpkg.ErrNoInvoicesCreated
}
// Create a paginator which reads from our add index bucket with
// the parameters provided by the invoice query.
paginator := newPaginator(
invoiceAddIndex.ReadCursor(), q.Reversed, q.IndexOffset,
q.NumMaxInvoices,
)
// accumulateInvoices looks up an invoice based on the index we
// are given, adds it to our set of invoices if it has the right
// characteristics for our query and returns the number of items
// we have added to our set of invoices.
accumulateInvoices := func(_, indexValue []byte) (bool, error) {
invoice, err := fetchInvoice(
indexValue, invoices, nil, false,
)
if err != nil {
return false, err
}
// Skip any settled or canceled invoices if the caller
// is only interested in pending ones.
if q.PendingOnly && !invoice.IsPending() {
return false, nil
}
// Get the creation time in Unix seconds, this always
// rounds down the nanoseconds to full seconds.
createTime := invoice.CreationDate.Unix()
// Skip any invoices that were created before the
// specified time.
if createTime < q.CreationDateStart {
return false, nil
}
// Skip any invoices that were created after the
// specified time.
if q.CreationDateEnd != 0 &&
createTime > q.CreationDateEnd {
return false, nil
}
// At this point, we've exhausted the offset, so we'll
// begin collecting invoices found within the range.
resp.Invoices = append(resp.Invoices, invoice)
return true, nil
}
// Query our paginator using accumulateInvoices to build up a
// set of invoices.
if err := paginator.query(accumulateInvoices); err != nil {
return err
}
// If we iterated through the add index in reverse order, then
// we'll need to reverse the slice of invoices to return them in
// forward order.
if q.Reversed {
numInvoices := len(resp.Invoices)
for i := 0; i < numInvoices/2; i++ {
reverse := numInvoices - i - 1
resp.Invoices[i], resp.Invoices[reverse] =
resp.Invoices[reverse], resp.Invoices[i]
}
}
return nil
}, func() {
resp = invpkg.InvoiceSlice{
InvoiceQuery: q,
}
})
if err != nil && !errors.Is(err, invpkg.ErrNoInvoicesCreated) {
return resp, err
}
// Finally, record the indexes of the first and last invoices returned
// so that the caller can resume from this point later on.
if len(resp.Invoices) > 0 {
resp.FirstIndexOffset = resp.Invoices[0].AddIndex
lastIdx := len(resp.Invoices) - 1
resp.LastIndexOffset = resp.Invoices[lastIdx].AddIndex
}
return resp, nil
}
// UpdateInvoice attempts to update an invoice corresponding to the passed
// payment hash. If an invoice matching the passed payment hash doesn't exist
// within the database, then the action will fail with a "not found" error.
//
// The update is performed inside the same database transaction that fetches the
// invoice and is therefore atomic. The fields to update are controlled by the
// supplied callback. When updating an invoice, the update itself happens
// in-memory on a copy of the invoice. Once it is written successfully to the
// database, the in-memory copy is returned to the caller.
func (d *DB) UpdateInvoice(_ context.Context, ref invpkg.InvoiceRef,
setIDHint *invpkg.SetID, callback invpkg.InvoiceUpdateCallback) (
*invpkg.Invoice, error) {
var updatedInvoice *invpkg.Invoice
err := kvdb.Update(d, func(tx kvdb.RwTx) error {
invoices, err := tx.CreateTopLevelBucket(invoiceBucket)
if err != nil {
return err
}
invoiceIndex, err := invoices.CreateBucketIfNotExists(
invoiceIndexBucket,
)
if err != nil {
return err
}
settleIndex, err := invoices.CreateBucketIfNotExists(
settleIndexBucket,
)
if err != nil {
return err
}
payAddrIndex := tx.ReadBucket(payAddrIndexBucket)
setIDIndex := tx.ReadWriteBucket(setIDIndexBucket)
// Retrieve the invoice number for this invoice using the
// provided invoice reference.
invoiceNum, err := fetchInvoiceNumByRef(
invoiceIndex, payAddrIndex, setIDIndex, ref,
)
if err != nil {
return err
}
// setIDHint can also be nil here, which means all the HTLCs
// for AMP invoices are fetched. If the blank setID is passed
// in, then no HTLCs are fetched for the AMP invoice. If a
// specific setID is passed in, then only the HTLCs for that
// setID are fetched for a particular sub-AMP invoice.
invoice, err := fetchInvoice(
invoiceNum, invoices, []*invpkg.SetID{setIDHint}, false,
)
if err != nil {
return err
}
now := d.clock.Now()
updater := &kvInvoiceUpdater{
db: d,
invoicesBucket: invoices,
settleIndexBucket: settleIndex,
setIDIndexBucket: setIDIndex,
updateTime: now,
invoiceNum: invoiceNum,
invoice: &invoice,
updatedAmpHtlcs: make(ampHTLCsMap),
settledSetIDs: make(map[invpkg.SetID]struct{}),
}
payHash := ref.PayHash()
updatedInvoice, err = invpkg.UpdateInvoice(
payHash, updater.invoice, now, callback, updater,
)
if err != nil {
return err
}
// If this is an AMP update, then limit the returned AMP state
// to only the requested set ID.
if setIDHint != nil {
filterInvoiceAMPState(updatedInvoice, setIDHint)
}
return nil
}, func() {
updatedInvoice = nil
})
return updatedInvoice, err
}
// filterInvoiceAMPState filters the AMP state of the invoice to only include
// state for the specified set IDs.
func filterInvoiceAMPState(invoice *invpkg.Invoice, setIDs ...*invpkg.SetID) {
filteredAMPState := make(invpkg.AMPInvoiceState)
for _, setID := range setIDs {
if setID == nil {
return
}
ampState, ok := invoice.AMPState[*setID]
if ok {
filteredAMPState[*setID] = ampState
}
}
invoice.AMPState = filteredAMPState
}
// ampHTLCsMap is a map of AMP HTLCs affected by an invoice update.
type ampHTLCsMap map[invpkg.SetID]map[models.CircuitKey]*invpkg.InvoiceHTLC
// kvInvoiceUpdater is an implementation of the InvoiceUpdater interface that
// is used with the kv implementation of the invoice database. Note that this
// updater is not concurrency safe and synchronizaton is expected to be handled
// on the DB level.
type kvInvoiceUpdater struct {
db *DB
invoicesBucket kvdb.RwBucket
settleIndexBucket kvdb.RwBucket
setIDIndexBucket kvdb.RwBucket
// updateTime is the timestamp for the update.
updateTime time.Time
// invoiceNum is a legacy key similar to the add index that is used
// only in the kv implementation.
invoiceNum []byte
// invoice is the invoice that we're updating. As a side effect of the
// update this invoice will be mutated.
invoice *invpkg.Invoice
// updatedAmpHtlcs holds the set of AMP HTLCs that were added or
// cancelled as part of this update.
updatedAmpHtlcs ampHTLCsMap
// settledSetIDs holds the set IDs that are settled with this update.
settledSetIDs map[invpkg.SetID]struct{}
}
// NOTE: this method does nothing in the k/v implementation of InvoiceUpdater.
func (k *kvInvoiceUpdater) AddHtlc(_ models.CircuitKey,
_ *invpkg.InvoiceHTLC) error {
return nil
}
// NOTE: this method does nothing in the k/v implementation of InvoiceUpdater.
func (k *kvInvoiceUpdater) ResolveHtlc(_ models.CircuitKey, _ invpkg.HtlcState,
_ time.Time) error {
return nil
}
// NOTE: this method does nothing in the k/v implementation of InvoiceUpdater.
func (k *kvInvoiceUpdater) AddAmpHtlcPreimage(_ [32]byte, _ models.CircuitKey,
_ lntypes.Preimage) error {
return nil
}
// NOTE: this method does nothing in the k/v implementation of InvoiceUpdater.
func (k *kvInvoiceUpdater) UpdateInvoiceState(_ invpkg.ContractState,
_ *lntypes.Preimage) error {
return nil
}
// NOTE: this method does nothing in the k/v implementation of InvoiceUpdater.
func (k *kvInvoiceUpdater) UpdateInvoiceAmtPaid(_ lnwire.MilliSatoshi) error {
return nil
}
// UpdateAmpState updates the state of the AMP invoice identified by the setID.
func (k *kvInvoiceUpdater) UpdateAmpState(setID [32]byte,
state invpkg.InvoiceStateAMP, circuitKey models.CircuitKey) error {
if _, ok := k.updatedAmpHtlcs[setID]; !ok {
switch state.State {
case invpkg.HtlcStateAccepted:
// If we're just now creating the HTLCs for this set
// then we'll also pull in the existing HTLCs that are
// part of this set, so we can write them all to disk
// together (same value)
k.updatedAmpHtlcs[setID] = k.invoice.HTLCSet(
&setID, invpkg.HtlcStateAccepted,
)
case invpkg.HtlcStateCanceled:
// Only HTLCs in the accepted state, can be cancelled,
// but we also want to merge that with HTLCs that may be
// canceled as well since it can be cancelled one by
// one.
k.updatedAmpHtlcs[setID] = k.invoice.HTLCSet(
&setID, invpkg.HtlcStateAccepted,
)
cancelledHtlcs := k.invoice.HTLCSet(
&setID, invpkg.HtlcStateCanceled,
)
maps.Copy(k.updatedAmpHtlcs[setID], cancelledHtlcs)
case invpkg.HtlcStateSettled:
k.updatedAmpHtlcs[setID] = make(
map[models.CircuitKey]*invpkg.InvoiceHTLC,
)
}
}
if state.State == invpkg.HtlcStateSettled {
// Add the set ID to the set that was settled in this invoice
// update. We'll use this later to update the settle index.
k.settledSetIDs[setID] = struct{}{}
}
k.updatedAmpHtlcs[setID][circuitKey] = k.invoice.Htlcs[circuitKey]
return nil
}
// Finalize finalizes the update before it is written to the database.
func (k *kvInvoiceUpdater) Finalize(updateType invpkg.UpdateType) error {
switch updateType {
case invpkg.AddHTLCsUpdate:
return k.storeAddHtlcsUpdate()
case invpkg.CancelHTLCsUpdate:
return k.storeCancelHtlcsUpdate()
case invpkg.SettleHodlInvoiceUpdate:
return k.storeSettleHodlInvoiceUpdate()
case invpkg.CancelInvoiceUpdate:
// Persist all changes which where made when cancelling the
// invoice. All HTLCs which were accepted are now canceled, so
// we persist this state.
return k.storeCancelHtlcsUpdate()
}
return fmt.Errorf("unknown update type: %v", updateType)
}
// storeCancelHtlcsUpdate updates the invoice in the database after cancelling a
// set of HTLCs.
func (k *kvInvoiceUpdater) storeCancelHtlcsUpdate() error {
err := k.serializeAndStoreInvoice()
if err != nil {
return err
}
// If this is an AMP invoice, then we'll actually store the rest
// of the HTLCs in-line with the invoice, using the invoice ID
// as a prefix, and the AMP key as a suffix: invoiceNum ||
// setID.
if k.invoice.IsAMP() {
return k.updateAMPInvoices()
}
return nil
}
// storeAddHtlcsUpdate updates the invoice in the database after adding a set of
// HTLCs.
func (k *kvInvoiceUpdater) storeAddHtlcsUpdate() error {
invoiceIsAMP := k.invoice.IsAMP()
for htlcSetID := range k.updatedAmpHtlcs {
// Check if this SetID already exist.
setIDInvNum := k.setIDIndexBucket.Get(htlcSetID[:])
if setIDInvNum == nil {
err := k.setIDIndexBucket.Put(
htlcSetID[:], k.invoiceNum,
)
if err != nil {
return err
}
} else if !bytes.Equal(setIDInvNum, k.invoiceNum) {
return invpkg.ErrDuplicateSetID{
SetID: htlcSetID,
}
}
}
// If this is a non-AMP invoice, then the state can eventually go to
// ContractSettled, so we pass in nil value as part of
// setSettleMetaFields.
if !invoiceIsAMP && k.invoice.State == invpkg.ContractSettled {
err := k.setSettleMetaFields(nil)
if err != nil {
return err
}
}
// As we don't update the settle index above for AMP invoices, we'll do
// it here for each sub-AMP invoice that was settled.
for settledSetID := range k.settledSetIDs {
settledSetID := settledSetID
err := k.setSettleMetaFields(&settledSetID)
if err != nil {
return err
}
}
err := k.serializeAndStoreInvoice()
if err != nil {
return err
}
// If this is an AMP invoice, then we'll actually store the rest of the
// HTLCs in-line with the invoice, using the invoice ID as a prefix,
// and the AMP key as a suffix: invoiceNum || setID.
if invoiceIsAMP {
return k.updateAMPInvoices()
}
return nil
}
// storeSettleHodlInvoiceUpdate updates the invoice in the database after
// settling a hodl invoice.
func (k *kvInvoiceUpdater) storeSettleHodlInvoiceUpdate() error {
err := k.setSettleMetaFields(nil)
if err != nil {
return err
}
return k.serializeAndStoreInvoice()
}
// setSettleMetaFields updates the metadata associated with settlement of an
// invoice. If a non-nil setID is passed in, then the value will be append to
// the invoice number as well, in order to allow us to detect repeated payments
// to the same AMP invoices "across time".
func (k *kvInvoiceUpdater) setSettleMetaFields(setID *invpkg.SetID) error {
// Now that we know the invoice hasn't already been settled, we'll
// update the settle index so we can place this settle event in the
// proper location within our time series.
nextSettleSeqNo, err := k.settleIndexBucket.NextSequence()
if err != nil {
return err
}
// Make a new byte array on the stack that can potentially store the 4
// byte invoice number along w/ the 32 byte set ID. We capture valueLen
// here which is the number of bytes copied so we can only store the 4
// bytes if this is a non-AMP invoice.
var indexKey [invoiceSetIDKeyLen]byte
valueLen := copy(indexKey[:], k.invoiceNum)
if setID != nil {
valueLen += copy(indexKey[valueLen:], setID[:])
}
var seqNoBytes [8]byte
byteOrder.PutUint64(seqNoBytes[:], nextSettleSeqNo)
err = k.settleIndexBucket.Put(seqNoBytes[:], indexKey[:valueLen])
if err != nil {
return err
}
// If the setID is nil, then this means that this is a non-AMP settle,
// so we'll update the invoice settle index directly.
if setID == nil {
k.invoice.SettleDate = k.updateTime
k.invoice.SettleIndex = nextSettleSeqNo
} else {
// If the set ID isn't blank, we'll update the AMP state map
// which tracks when each of the setIDs associated with a given
// AMP invoice are settled.
ampState := k.invoice.AMPState[*setID]
ampState.SettleDate = k.updateTime
ampState.SettleIndex = nextSettleSeqNo
k.invoice.AMPState[*setID] = ampState
}
return nil
}
// updateAMPInvoices updates the set of AMP invoices in-place. For AMP, rather
// then continually write the invoices to the end of the invoice value, we
// instead write the invoices into a new key preifx that follows the main
// invoice number. This ensures that we don't need to continually decode a
// potentially massive HTLC set, and also allows us to quickly find the HLTCs
// associated with a particular HTLC set.
func (k *kvInvoiceUpdater) updateAMPInvoices() error {
for setID, htlcSet := range k.updatedAmpHtlcs {
// First write out the set of HTLCs including all the relevant
// TLV values.
var b bytes.Buffer
if err := serializeHtlcs(&b, htlcSet); err != nil {
return err
}
// Next store each HTLC in-line, using a prefix based off the
// invoice number.
invoiceSetIDKey := makeInvoiceSetIDKey(k.invoiceNum, setID[:])
err := k.invoicesBucket.Put(invoiceSetIDKey[:], b.Bytes())
if err != nil {
return err
}
}
return nil
}
// serializeAndStoreInvoice is a helper function used to store invoices.
func (k *kvInvoiceUpdater) serializeAndStoreInvoice() error {
var buf bytes.Buffer
if err := serializeInvoice(&buf, k.invoice); err != nil {
return err
}
return k.invoicesBucket.Put(k.invoiceNum, buf.Bytes())
}
// InvoicesSettledSince can be used by callers to catch up any settled invoices
// they missed within the settled invoice time series. We'll return all known
// settled invoice that have a settle index higher than the passed
// sinceSettleIndex.
//
// NOTE: The index starts from 1, as a result. We enforce that specifying a
// value below the starting index value is a noop.
func (d *DB) InvoicesSettledSince(_ context.Context, sinceSettleIndex uint64) (
[]invpkg.Invoice, error) {
var settledInvoices []invpkg.Invoice
// If an index of zero was specified, then in order to maintain
// backwards compat, we won't send out any new invoices.
if sinceSettleIndex == 0 {
return settledInvoices, nil
}
var startIndex [8]byte
byteOrder.PutUint64(startIndex[:], sinceSettleIndex)
err := kvdb.View(d, func(tx kvdb.RTx) error {
invoices := tx.ReadBucket(invoiceBucket)
if invoices == nil {
return nil
}
settleIndex := invoices.NestedReadBucket(settleIndexBucket)
if settleIndex == nil {
return nil
}
// We'll now run through each entry in the add index starting
// at our starting index. We'll continue until we reach the
// very end of the current key space.
invoiceCursor := settleIndex.ReadCursor()
// We'll seek to the starting index, then manually advance the
// cursor in order to skip the entry with the since add index.
invoiceCursor.Seek(startIndex[:])
seqNo, indexValue := invoiceCursor.Next()
for ; seqNo != nil && bytes.Compare(seqNo, startIndex[:]) > 0; seqNo, indexValue = invoiceCursor.Next() {
// Depending on the length of the index value, this may
// or may not be an AMP invoice, so we'll extract the
// invoice value into two components: the invoice num,
// and the setID (may not be there).
var (
invoiceKey [4]byte
setID *invpkg.SetID
)
valueLen := copy(invoiceKey[:], indexValue)
if len(indexValue) == invoiceSetIDKeyLen {
setID = new(invpkg.SetID)
copy(setID[:], indexValue[valueLen:])
}
// For each key found, we'll look up the actual
// invoice, then accumulate it into our return value.
invoice, err := fetchInvoice(
invoiceKey[:], invoices, []*invpkg.SetID{setID},
true,
)
if err != nil {
return err
}
settledInvoices = append(settledInvoices, invoice)
}
return nil
}, func() {
settledInvoices = nil
})
if err != nil {
return nil, err
}
return settledInvoices, nil
}
func putInvoice(invoices, invoiceIndex, payAddrIndex, addIndex kvdb.RwBucket,
i *invpkg.Invoice, invoiceNum uint32, paymentHash lntypes.Hash) (
uint64, error) {
// Create the invoice key which is just the big-endian representation
// of the invoice number.
var invoiceKey [4]byte
byteOrder.PutUint32(invoiceKey[:], invoiceNum)
// Increment the num invoice counter index so the next invoice bares
// the proper ID.
var scratch [4]byte
invoiceCounter := invoiceNum + 1
byteOrder.PutUint32(scratch[:], invoiceCounter)
if err := invoiceIndex.Put(numInvoicesKey, scratch[:]); err != nil {
return 0, err
}
// Add the payment hash to the invoice index. This will let us quickly
// identify if we can settle an incoming payment, and also to possibly
// allow a single invoice to have multiple payment installations.
err := invoiceIndex.Put(paymentHash[:], invoiceKey[:])
if err != nil {
return 0, err
}
// Add the invoice to the payment address index, but only if the invoice
// has a non-zero payment address. The all-zero payment address is still
// in use by legacy keysend, so we special-case here to avoid
// collisions.
if i.Terms.PaymentAddr != invpkg.BlankPayAddr {
err = payAddrIndex.Put(i.Terms.PaymentAddr[:], invoiceKey[:])
if err != nil {
return 0, err
}
}
// Next, we'll obtain the next add invoice index (sequence
// number), so we can properly place this invoice within this
// event stream.
nextAddSeqNo, err := addIndex.NextSequence()
if err != nil {
return 0, err
}
// With the next sequence obtained, we'll updating the event series in
// the add index bucket to map this current add counter to the index of
// this new invoice.
var seqNoBytes [8]byte
byteOrder.PutUint64(seqNoBytes[:], nextAddSeqNo)
if err := addIndex.Put(seqNoBytes[:], invoiceKey[:]); err != nil {
return 0, err
}
i.AddIndex = nextAddSeqNo
// Finally, serialize the invoice itself to be written to the disk.
var buf bytes.Buffer
if err := serializeInvoice(&buf, i); err != nil {
return 0, err
}
if err := invoices.Put(invoiceKey[:], buf.Bytes()); err != nil {
return 0, err
}
return nextAddSeqNo, nil
}
// recordSize returns the amount of bytes this TLV record will occupy when
// encoded.
func ampRecordSize(a *invpkg.AMPInvoiceState) func() uint64 {
var (
b bytes.Buffer
buf [8]byte
)
// We know that encoding works since the tests pass in the build this
// file is checked into, so we'll simplify things and simply encode it
// ourselves then report the total amount of bytes used.
if err := ampStateEncoder(&b, a, &buf); err != nil {
// This should never error out, but we log it just in case it
// does.
log.Errorf("encoding the amp invoice state failed: %v", err)
}
return func() uint64 {
return uint64(len(b.Bytes()))
}
}
// serializeInvoice serializes an invoice to a writer.
//
// Note: this function is in use for a migration. Before making changes that
// would modify the on disk format, make a copy of the original code and store
// it with the migration.
func serializeInvoice(w io.Writer, i *invpkg.Invoice) error {
creationDateBytes, err := i.CreationDate.MarshalBinary()
if err != nil {
return err
}
settleDateBytes, err := i.SettleDate.MarshalBinary()
if err != nil {
return err
}
var fb bytes.Buffer
err = i.Terms.Features.EncodeBase256(&fb)
if err != nil {
return err
}
featureBytes := fb.Bytes()
preimage := [32]byte(invpkg.UnknownPreimage)
if i.Terms.PaymentPreimage != nil {
preimage = *i.Terms.PaymentPreimage
if preimage == invpkg.UnknownPreimage {
return errors.New("cannot use all-zeroes preimage")
}
}
value := uint64(i.Terms.Value)
cltvDelta := uint32(i.Terms.FinalCltvDelta)
expiry := uint64(i.Terms.Expiry)
amtPaid := uint64(i.AmtPaid)
state := uint8(i.State)
var hodlInvoice uint8
if i.HodlInvoice {
hodlInvoice = 1
}
tlvStream, err := tlv.NewStream(
// Memo and payreq.
tlv.MakePrimitiveRecord(memoType, &i.Memo),
tlv.MakePrimitiveRecord(payReqType, &i.PaymentRequest),
// Add/settle metadata.
tlv.MakePrimitiveRecord(createTimeType, &creationDateBytes),
tlv.MakePrimitiveRecord(settleTimeType, &settleDateBytes),
tlv.MakePrimitiveRecord(addIndexType, &i.AddIndex),
tlv.MakePrimitiveRecord(settleIndexType, &i.SettleIndex),
// Terms.
tlv.MakePrimitiveRecord(preimageType, &preimage),
tlv.MakePrimitiveRecord(valueType, &value),
tlv.MakePrimitiveRecord(cltvDeltaType, &cltvDelta),
tlv.MakePrimitiveRecord(expiryType, &expiry),
tlv.MakePrimitiveRecord(paymentAddrType, &i.Terms.PaymentAddr),
tlv.MakePrimitiveRecord(featuresType, &featureBytes),
// Invoice state.
tlv.MakePrimitiveRecord(invStateType, &state),
tlv.MakePrimitiveRecord(amtPaidType, &amtPaid),
tlv.MakePrimitiveRecord(hodlInvoiceType, &hodlInvoice),
// Invoice AMP state.
tlv.MakeDynamicRecord(
invoiceAmpStateType, &i.AMPState,
ampRecordSize(&i.AMPState),
ampStateEncoder, ampStateDecoder,
),
)
if err != nil {
return err
}
var b bytes.Buffer
if err = tlvStream.Encode(&b); err != nil {
return err
}
err = binary.Write(w, byteOrder, uint64(b.Len()))
if err != nil {
return err
}
if _, err = w.Write(b.Bytes()); err != nil {
return err
}
// Only if this is a _non_ AMP invoice do we serialize the HTLCs
// in-line with the rest of the invoice.
if i.IsAMP() {
return nil
}
return serializeHtlcs(w, i.Htlcs)
}
// serializeHtlcs serializes a map containing circuit keys and invoice htlcs to
// a writer.
func serializeHtlcs(w io.Writer,
htlcs map[models.CircuitKey]*invpkg.InvoiceHTLC) error {
for key, htlc := range htlcs {
// Encode the htlc in a tlv stream.
chanID := key.ChanID.ToUint64()
amt := uint64(htlc.Amt)
mppTotalAmt := uint64(htlc.MppTotalAmt)
acceptTime := putNanoTime(htlc.AcceptTime)
resolveTime := putNanoTime(htlc.ResolveTime)
state := uint8(htlc.State)
var records []tlv.Record
records = append(records,
tlv.MakePrimitiveRecord(chanIDType, &chanID),
tlv.MakePrimitiveRecord(htlcIDType, &key.HtlcID),
tlv.MakePrimitiveRecord(amtType, &amt),
tlv.MakePrimitiveRecord(
acceptHeightType, &htlc.AcceptHeight,
),
tlv.MakePrimitiveRecord(acceptTimeType, &acceptTime),
tlv.MakePrimitiveRecord(resolveTimeType, &resolveTime),
tlv.MakePrimitiveRecord(expiryHeightType, &htlc.Expiry),
tlv.MakePrimitiveRecord(htlcStateType, &state),
tlv.MakePrimitiveRecord(mppTotalAmtType, &mppTotalAmt),
)
if htlc.AMP != nil {
setIDRecord := tlv.MakeDynamicRecord(
htlcAMPType, &htlc.AMP.Record,
htlc.AMP.Record.PayloadSize,
record.AMPEncoder, record.AMPDecoder,
)
records = append(records, setIDRecord)
hash32 := [32]byte(htlc.AMP.Hash)
hashRecord := tlv.MakePrimitiveRecord(
htlcHashType, &hash32,
)
records = append(records, hashRecord)
if htlc.AMP.Preimage != nil {
preimage32 := [32]byte(*htlc.AMP.Preimage)
preimageRecord := tlv.MakePrimitiveRecord(
htlcPreimageType, &preimage32,
)
records = append(records, preimageRecord)
}
}
// Convert the custom records to tlv.Record types that are ready
// for serialization.
customRecords := tlv.MapToRecords(htlc.CustomRecords)
// Append the custom records. Their ids are in the experimental
// range and sorted, so there is no need to sort again.
records = append(records, customRecords...)
tlvStream, err := tlv.NewStream(records...)
if err != nil {
return err
}
var b bytes.Buffer
if err := tlvStream.Encode(&b); err != nil {
return err
}
// Write the length of the tlv stream followed by the stream
// bytes.
err = binary.Write(w, byteOrder, uint64(b.Len()))
if err != nil {
return err
}
if _, err := w.Write(b.Bytes()); err != nil {
return err
}
}
return nil
}
// putNanoTime returns the unix nano time for the passed timestamp. A zero-value
// timestamp will be mapped to 0, since calling UnixNano in that case is
// undefined.
func putNanoTime(t time.Time) uint64 {
if t.IsZero() {
return 0
}
return uint64(t.UnixNano())
}
// getNanoTime returns a timestamp for the given number of nano seconds. If zero
// is provided, an zero-value time stamp is returned.
func getNanoTime(ns uint64) time.Time {
if ns == 0 {
return time.Time{}
}
return time.Unix(0, int64(ns))
}
// fetchFilteredAmpInvoices retrieves only a select set of AMP invoices
// identified by the setID value.
func fetchFilteredAmpInvoices(invoiceBucket kvdb.RBucket, invoiceNum []byte,
setIDs ...*invpkg.SetID) (map[models.CircuitKey]*invpkg.InvoiceHTLC,
error) {
htlcs := make(map[models.CircuitKey]*invpkg.InvoiceHTLC)
for _, setID := range setIDs {
invoiceSetIDKey := makeInvoiceSetIDKey(invoiceNum, setID[:])
htlcSetBytes := invoiceBucket.Get(invoiceSetIDKey[:])
if htlcSetBytes == nil {
// A set ID was passed in, but we don't have this
// stored yet, meaning that the setID is being added
// for the first time.
return htlcs, invpkg.ErrInvoiceNotFound
}
htlcSetReader := bytes.NewReader(htlcSetBytes)
htlcsBySetID, err := deserializeHtlcs(htlcSetReader)
if err != nil {
return nil, err
}
maps.Copy(htlcs, htlcsBySetID)
}
return htlcs, nil
}
// forEachAMPInvoice is a helper function that attempts to iterate over each of
// the HTLC sets (based on their set ID) for the given AMP invoice identified
// by its invoiceNum. The callback closure is called for each key within the
// prefix range.
func forEachAMPInvoice(invoiceBucket kvdb.RBucket, invoiceNum []byte,
callback func(key, htlcSet []byte) error) error {
invoiceCursor := invoiceBucket.ReadCursor()
// Seek to the first key that includes the invoice data itself.
invoiceCursor.Seek(invoiceNum)
// Advance to the very first key _after_ the invoice data, as this is
// where we'll encounter our first HTLC (if any are present).
cursorKey, htlcSet := invoiceCursor.Next()
// If at this point, the cursor key doesn't match the invoice num
// prefix, then we know that this HTLC doesn't have any set ID HTLCs
// associated with it.
if !bytes.HasPrefix(cursorKey, invoiceNum) {
return nil
}
// Otherwise continue to iterate until we no longer match the prefix,
// executing the call back at each step.
for ; cursorKey != nil && bytes.HasPrefix(cursorKey, invoiceNum); cursorKey, htlcSet = invoiceCursor.Next() {
err := callback(cursorKey, htlcSet)
if err != nil {
return err
}
}
return nil
}
// fetchAmpSubInvoices attempts to use the invoiceNum as a prefix within the
// AMP bucket to find all the individual HTLCs (by setID) associated with a
// given invoice. If a list of set IDs are specified, then only HTLCs
// associated with that setID will be retrieved.
func fetchAmpSubInvoices(invoiceBucket kvdb.RBucket, invoiceNum []byte,
setIDs ...*invpkg.SetID) (map[models.CircuitKey]*invpkg.InvoiceHTLC,
error) {
// If a set of setIDs was specified, then we can skip the cursor and
// just read out exactly what we need.
if len(setIDs) != 0 && setIDs[0] != nil {
return fetchFilteredAmpInvoices(
invoiceBucket, invoiceNum, setIDs...,
)
}
// Otherwise, iterate over all the htlc sets that are prefixed beside
// this invoice in the main invoice bucket.
htlcs := make(map[models.CircuitKey]*invpkg.InvoiceHTLC)
err := forEachAMPInvoice(invoiceBucket, invoiceNum,
func(key, htlcSet []byte) error {
htlcSetReader := bytes.NewReader(htlcSet)
htlcsBySetID, err := deserializeHtlcs(htlcSetReader)
if err != nil {
return err
}
maps.Copy(htlcs, htlcsBySetID)
return nil
},
)
if err != nil {
return nil, err
}
return htlcs, nil
}
// fetchInvoice attempts to read out the relevant state for the invoice as
// specified by the invoice number. If the setID fields are set, then only the
// HTLC information pertaining to those set IDs is returned.
func fetchInvoice(invoiceNum []byte, invoices kvdb.RBucket,
setIDs []*invpkg.SetID, filterAMPState bool) (invpkg.Invoice, error) {
invoiceBytes := invoices.Get(invoiceNum)
if invoiceBytes == nil {
return invpkg.Invoice{}, invpkg.ErrInvoiceNotFound
}
invoiceReader := bytes.NewReader(invoiceBytes)
invoice, err := deserializeInvoice(invoiceReader)
if err != nil {
return invpkg.Invoice{}, err
}
// If this is an AMP invoice we'll also attempt to read out the set of
// HTLCs that were paid to prior set IDs, if needed.
if !invoice.IsAMP() {
return invoice, nil
}
if shouldFetchAMPHTLCs(invoice, setIDs) {
invoice.Htlcs, err = fetchAmpSubInvoices(
invoices, invoiceNum, setIDs...,
)
// TODO(positiveblue): we should fail when we are not able to
// fetch all the HTLCs for an AMP invoice. Multiple tests in
// the invoice and channeldb package break if we return this
// error. We need to update them when we migrate this logic to
// the sql implementation.
if err != nil {
log.Errorf("unable to fetch amp htlcs for inv "+
"%v and setIDs %v: %w", invoiceNum, setIDs, err)
}
if filterAMPState {
filterInvoiceAMPState(&invoice, setIDs...)
}
}
return invoice, nil
}
// shouldFetchAMPHTLCs returns true if we need to fetch the set of HTLCs that
// were paid to the relevant set IDs.
func shouldFetchAMPHTLCs(invoice invpkg.Invoice, setIDs []*invpkg.SetID) bool {
// For AMP invoice that already have HTLCs populated (created before
// recurring invoices), then we don't need to read from the prefix
// keyed section of the bucket.
if len(invoice.Htlcs) != 0 {
return false
}
// If the "zero" setID was specified, then this means that no HTLC data
// should be returned alongside of it.
if len(setIDs) != 0 && setIDs[0] != nil &&
*setIDs[0] == invpkg.BlankPayAddr {
return false
}
return true
}
// fetchInvoiceStateAMP retrieves the state of all the relevant sub-invoice for
// an AMP invoice. This methods only decode the relevant state vs the entire
// invoice.
func fetchInvoiceStateAMP(invoiceNum []byte,
invoices kvdb.RBucket) (invpkg.AMPInvoiceState, error) {
// Fetch the raw invoice bytes.
invoiceBytes := invoices.Get(invoiceNum)
if invoiceBytes == nil {
return nil, invpkg.ErrInvoiceNotFound
}
r := bytes.NewReader(invoiceBytes)
var bodyLen int64
err := binary.Read(r, byteOrder, &bodyLen)
if err != nil {
return nil, err
}
// Next, we'll make a new TLV stream that only attempts to decode the
// bytes we actually need.
ampState := make(invpkg.AMPInvoiceState)
tlvStream, err := tlv.NewStream(
// Invoice AMP state.
tlv.MakeDynamicRecord(
invoiceAmpStateType, &State, nil,
ampStateEncoder, ampStateDecoder,
),
)
if err != nil {
return nil, err
}
invoiceReader := io.LimitReader(r, bodyLen)
if err = tlvStream.Decode(invoiceReader); err != nil {
return nil, err
}
return ampState, nil
}
func deserializeInvoice(r io.Reader) (invpkg.Invoice, error) {
var (
preimageBytes [32]byte
value uint64
cltvDelta uint32
expiry uint64
amtPaid uint64
state uint8
hodlInvoice uint8
creationDateBytes []byte
settleDateBytes []byte
featureBytes []byte
)
var i invpkg.Invoice
i.AMPState = make(invpkg.AMPInvoiceState)
tlvStream, err := tlv.NewStream(
// Memo and payreq.
tlv.MakePrimitiveRecord(memoType, &i.Memo),
tlv.MakePrimitiveRecord(payReqType, &i.PaymentRequest),
// Add/settle metadata.
tlv.MakePrimitiveRecord(createTimeType, &creationDateBytes),
tlv.MakePrimitiveRecord(settleTimeType, &settleDateBytes),
tlv.MakePrimitiveRecord(addIndexType, &i.AddIndex),
tlv.MakePrimitiveRecord(settleIndexType, &i.SettleIndex),
// Terms.
tlv.MakePrimitiveRecord(preimageType, &preimageBytes),
tlv.MakePrimitiveRecord(valueType, &value),
tlv.MakePrimitiveRecord(cltvDeltaType, &cltvDelta),
tlv.MakePrimitiveRecord(expiryType, &expiry),
tlv.MakePrimitiveRecord(paymentAddrType, &i.Terms.PaymentAddr),
tlv.MakePrimitiveRecord(featuresType, &featureBytes),
// Invoice state.
tlv.MakePrimitiveRecord(invStateType, &state),
tlv.MakePrimitiveRecord(amtPaidType, &amtPaid),
tlv.MakePrimitiveRecord(hodlInvoiceType, &hodlInvoice),
// Invoice AMP state.
tlv.MakeDynamicRecord(
invoiceAmpStateType, &i.AMPState, nil,
ampStateEncoder, ampStateDecoder,
),
)
if err != nil {
return i, err
}
var bodyLen int64
err = binary.Read(r, byteOrder, &bodyLen)
if err != nil {
return i, err
}
lr := io.LimitReader(r, bodyLen)
if err = tlvStream.Decode(lr); err != nil {
return i, err
}
preimage := lntypes.Preimage(preimageBytes)
if preimage != invpkg.UnknownPreimage {
i.Terms.PaymentPreimage = &preimage
}
i.Terms.Value = lnwire.MilliSatoshi(value)
i.Terms.FinalCltvDelta = int32(cltvDelta)
i.Terms.Expiry = time.Duration(expiry)
i.AmtPaid = lnwire.MilliSatoshi(amtPaid)
i.State = invpkg.ContractState(state)
if hodlInvoice != 0 {
i.HodlInvoice = true
}
err = i.CreationDate.UnmarshalBinary(creationDateBytes)
if err != nil {
return i, err
}
err = i.SettleDate.UnmarshalBinary(settleDateBytes)
if err != nil {
return i, err
}
rawFeatures := lnwire.NewRawFeatureVector()
err = rawFeatures.DecodeBase256(
bytes.NewReader(featureBytes), len(featureBytes),
)
if err != nil {
return i, err
}
i.Terms.Features = lnwire.NewFeatureVector(
rawFeatures, lnwire.Features,
)
i.Htlcs, err = deserializeHtlcs(r)
return i, err
}
func encodeCircuitKeys(w io.Writer, val interface{}, buf *[8]byte) error {
if v, ok := val.(*map[models.CircuitKey]struct{}); ok {
// We encode the set of circuit keys as a varint length prefix.
// followed by a series of fixed sized uint8 integers.
numKeys := uint64(len(*v))
if err := tlv.WriteVarInt(w, numKeys, buf); err != nil {
return err
}
for key := range *v {
scidInt := key.ChanID.ToUint64()
if err := tlv.EUint64(w, &scidInt, buf); err != nil {
return err
}
if err := tlv.EUint64(w, &key.HtlcID, buf); err != nil {
return err
}
}
return nil
}
return tlv.NewTypeForEncodingErr(val, "*map[CircuitKey]struct{}")
}
func decodeCircuitKeys(r io.Reader, val interface{}, buf *[8]byte,
l uint64) error {
if v, ok := val.(*map[models.CircuitKey]struct{}); ok {
// First, we'll read out the varint that encodes the number of
// circuit keys encoded.
numKeys, err := tlv.ReadVarInt(r, buf)
if err != nil {
return err
}
// Now that we know how many keys to expect, iterate reading
// each one until we're done.
for i := uint64(0); i < numKeys; i++ {
var (
key models.CircuitKey
scid uint64
)
if err := tlv.DUint64(r, &scid, buf, 8); err != nil {
return err
}
key.ChanID = lnwire.NewShortChanIDFromInt(scid)
err := tlv.DUint64(r, &key.HtlcID, buf, 8)
if err != nil {
return err
}
(*v)[key] = struct{}{}
}
return nil
}
return tlv.NewTypeForDecodingErr(val, "*map[CircuitKey]struct{}", l, l)
}
// ampStateEncoder is a custom TLV encoder for the AMPInvoiceState record.
func ampStateEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
if v, ok := val.(*invpkg.AMPInvoiceState); ok {
// We'll encode the AMP state as a series of KV pairs on the
// wire with a length prefix.
numRecords := uint64(len(*v))
// First, we'll write out the number of records as a var int.
if err := tlv.WriteVarInt(w, numRecords, buf); err != nil {
return err
}
// With that written out, we'll now encode the entries
// themselves as a sub-TLV record, which includes its _own_
// inner length prefix.
for setID, ampState := range *v {
setID := [32]byte(setID)
ampState := ampState
htlcState := uint8(ampState.State)
settleDate := ampState.SettleDate
settleDateBytes, err := settleDate.MarshalBinary()
if err != nil {
return err
}
amtPaid := uint64(ampState.AmtPaid)
var ampStateTlvBytes bytes.Buffer
tlvStream, err := tlv.NewStream(
tlv.MakePrimitiveRecord(
ampStateSetIDType, &setID,
),
tlv.MakePrimitiveRecord(
ampStateHtlcStateType, &htlcState,
),
tlv.MakePrimitiveRecord(
ampStateSettleIndexType,
&State.SettleIndex,
),
tlv.MakePrimitiveRecord(
ampStateSettleDateType,
&settleDateBytes,
),
tlv.MakeDynamicRecord(
ampStateCircuitKeysType,
&State.InvoiceKeys,
func() uint64 {
// The record takes 8 bytes to
// encode the set of circuits,
// 8 bytes for the scid for the
// key, and 8 bytes for the HTLC
// index.
keys := ampState.InvoiceKeys
numKeys := uint64(len(keys))
size := tlv.VarIntSize(numKeys)
dataSize := (numKeys * 16)
return size + dataSize
},
encodeCircuitKeys, decodeCircuitKeys,
),
tlv.MakePrimitiveRecord(
ampStateAmtPaidType, &amtPaid,
),
)
if err != nil {
return err
}
err = tlvStream.Encode(&StateTlvBytes)
if err != nil {
return err
}
// We encode the record with a varint length followed by
// the _raw_ TLV bytes.
tlvLen := uint64(len(ampStateTlvBytes.Bytes()))
if err := tlv.WriteVarInt(w, tlvLen, buf); err != nil {
return err
}
_, err = w.Write(ampStateTlvBytes.Bytes())
if err != nil {
return err
}
}
return nil
}
return tlv.NewTypeForEncodingErr(val, "channeldb.AMPInvoiceState")
}
// ampStateDecoder is a custom TLV decoder for the AMPInvoiceState record.
func ampStateDecoder(r io.Reader, val interface{}, buf *[8]byte,
l uint64) error {
if v, ok := val.(*invpkg.AMPInvoiceState); ok {
// First, we'll decode the varint that encodes how many set IDs
// are encoded within the greater map.
numRecords, err := tlv.ReadVarInt(r, buf)
if err != nil {
return err
}
// Now that we know how many records we'll need to read, we can
// iterate and read them all out in series.
for i := uint64(0); i < numRecords; i++ {
// Read out the varint that encodes the size of this
// inner TLV record.
stateRecordSize, err := tlv.ReadVarInt(r, buf)
if err != nil {
return err
}
// Using this information, we'll create a new limited
// reader that'll return an EOF once the end has been
// reached so the stream stops consuming bytes.
innerTlvReader := io.LimitedReader{
R: r,
N: int64(stateRecordSize),
}
var (
setID [32]byte
htlcState uint8
settleIndex uint64
settleDateBytes []byte
invoiceKeys = make(
map[models.CircuitKey]struct{},
)
amtPaid uint64
)
tlvStream, err := tlv.NewStream(
tlv.MakePrimitiveRecord(
ampStateSetIDType, &setID,
),
tlv.MakePrimitiveRecord(
ampStateHtlcStateType, &htlcState,
),
tlv.MakePrimitiveRecord(
ampStateSettleIndexType, &settleIndex,
),
tlv.MakePrimitiveRecord(
ampStateSettleDateType,
&settleDateBytes,
),
tlv.MakeDynamicRecord(
ampStateCircuitKeysType,
&invoiceKeys, nil,
encodeCircuitKeys, decodeCircuitKeys,
),
tlv.MakePrimitiveRecord(
ampStateAmtPaidType, &amtPaid,
),
)
if err != nil {
return err
}
err = tlvStream.Decode(&innerTlvReader)
if err != nil {
return err
}
var settleDate time.Time
err = settleDate.UnmarshalBinary(settleDateBytes)
if err != nil {
return err
}
(*v)[setID] = invpkg.InvoiceStateAMP{
State: invpkg.HtlcState(htlcState),
SettleIndex: settleIndex,
SettleDate: settleDate,
InvoiceKeys: invoiceKeys,
AmtPaid: lnwire.MilliSatoshi(amtPaid),
}
}
return nil
}
return tlv.NewTypeForDecodingErr(
val, "channeldb.AMPInvoiceState", l, l,
)
}
// deserializeHtlcs reads a list of invoice htlcs from a reader and returns it
// as a map.
func deserializeHtlcs(r io.Reader) (map[models.CircuitKey]*invpkg.InvoiceHTLC,
error) {
htlcs := make(map[models.CircuitKey]*invpkg.InvoiceHTLC)
for {
// Read the length of the tlv stream for this htlc.
var streamLen int64
if err := binary.Read(r, byteOrder, &streamLen); err != nil {
if err == io.EOF {
break
}
return nil, err
}
// Limit the reader so that it stops at the end of this htlc's
// stream.
htlcReader := io.LimitReader(r, streamLen)
// Decode the contents into the htlc fields.
var (
htlc invpkg.InvoiceHTLC
key models.CircuitKey
chanID uint64
state uint8
acceptTime, resolveTime uint64
amt, mppTotalAmt uint64
amp = &record.AMP{}
hash32 = &[32]byte{}
preimage32 = &[32]byte{}
)
tlvStream, err := tlv.NewStream(
tlv.MakePrimitiveRecord(chanIDType, &chanID),
tlv.MakePrimitiveRecord(htlcIDType, &key.HtlcID),
tlv.MakePrimitiveRecord(amtType, &amt),
tlv.MakePrimitiveRecord(
acceptHeightType, &htlc.AcceptHeight,
),
tlv.MakePrimitiveRecord(acceptTimeType, &acceptTime),
tlv.MakePrimitiveRecord(resolveTimeType, &resolveTime),
tlv.MakePrimitiveRecord(expiryHeightType, &htlc.Expiry),
tlv.MakePrimitiveRecord(htlcStateType, &state),
tlv.MakePrimitiveRecord(mppTotalAmtType, &mppTotalAmt),
tlv.MakeDynamicRecord(
htlcAMPType, amp, amp.PayloadSize,
record.AMPEncoder, record.AMPDecoder,
),
tlv.MakePrimitiveRecord(htlcHashType, hash32),
tlv.MakePrimitiveRecord(htlcPreimageType, preimage32),
)
if err != nil {
return nil, err
}
parsedTypes, err := tlvStream.DecodeWithParsedTypes(htlcReader)
if err != nil {
return nil, err
}
if _, ok := parsedTypes[htlcAMPType]; !ok {
amp = nil
}
var preimage *lntypes.Preimage
if _, ok := parsedTypes[htlcPreimageType]; ok {
pimg := lntypes.Preimage(*preimage32)
preimage = &pimg
}
var hash *lntypes.Hash
if _, ok := parsedTypes[htlcHashType]; ok {
h := lntypes.Hash(*hash32)
hash = &h
}
key.ChanID = lnwire.NewShortChanIDFromInt(chanID)
htlc.AcceptTime = getNanoTime(acceptTime)
htlc.ResolveTime = getNanoTime(resolveTime)
htlc.State = invpkg.HtlcState(state)
htlc.Amt = lnwire.MilliSatoshi(amt)
htlc.MppTotalAmt = lnwire.MilliSatoshi(mppTotalAmt)
if amp != nil && hash != nil {
htlc.AMP = &invpkg.InvoiceHtlcAMPData{
Record: *amp,
Hash: *hash,
Preimage: preimage,
}
}
// Reconstruct the custom records fields from the parsed types
// map return from the tlv parser.
htlc.CustomRecords = hop.NewCustomRecords(parsedTypes)
htlcs[key] = &htlc
}
return htlcs, nil
}
// invoiceSetIDKeyLen is the length of the key that's used to store the
// individual HTLCs prefixed by their ID along side the main invoice within the
// invoiceBytes. We use 4 bytes for the invoice number, and 32 bytes for the
// set ID.
const invoiceSetIDKeyLen = 4 + 32
// makeInvoiceSetIDKey returns the prefix key, based on the set ID and invoice
// number where the HTLCs for this setID will be stored udner.
func makeInvoiceSetIDKey(invoiceNum, setID []byte) [invoiceSetIDKeyLen]byte {
// Construct the prefix key we need to obtain the invoice information:
// invoiceNum || setID.
var invoiceSetIDKey [invoiceSetIDKeyLen]byte
copy(invoiceSetIDKey[:], invoiceNum)
copy(invoiceSetIDKey[len(invoiceNum):], setID)
return invoiceSetIDKey
}
// delAMPInvoices attempts to delete all the "sub" invoices associated with a
// greater AMP invoices. We do this by deleting the set of keys that share the
// invoice number as a prefix.
func delAMPInvoices(invoiceNum []byte, invoiceBucket kvdb.RwBucket) error {
// Since it isn't safe to delete using an active cursor, we'll use the
// cursor simply to collect the set of keys we need to delete, _then_
// delete them in another pass.
var keysToDel [][]byte
err := forEachAMPInvoice(
invoiceBucket, invoiceNum,
func(cursorKey, v []byte) error {
keysToDel = append(keysToDel, cursorKey)
return nil
},
)
if err != nil {
return err
}
// In this next phase, we'll then delete all the relevant invoices.
for _, keyToDel := range keysToDel {
if err := invoiceBucket.Delete(keyToDel); err != nil {
return err
}
}
return nil
}
// delAMPSettleIndex removes all the entries in the settle index associated
// with a given AMP invoice.
func delAMPSettleIndex(invoiceNum []byte, invoices,
settleIndex kvdb.RwBucket) error {
// First, we need to grab the AMP invoice state to see if there's
// anything that we even need to delete.
ampState, err := fetchInvoiceStateAMP(invoiceNum, invoices)
if err != nil {
return err
}
// If there's no AMP state at all (non-AMP invoice), then we can return
// early.
if len(ampState) == 0 {
return nil
}
// Otherwise, we'll need to iterate and delete each settle index within
// the set of returned entries.
var settleIndexKey [8]byte
for _, subState := range ampState {
byteOrder.PutUint64(
settleIndexKey[:], subState.SettleIndex,
)
if err := settleIndex.Delete(settleIndexKey[:]); err != nil {
return err
}
}
return nil
}
// DeleteCanceledInvoices deletes all canceled invoices from the database.
func (d *DB) DeleteCanceledInvoices(_ context.Context) error {
return kvdb.Update(d, func(tx kvdb.RwTx) error {
invoices := tx.ReadWriteBucket(invoiceBucket)
if invoices == nil {
return nil
}
invoiceIndex := invoices.NestedReadWriteBucket(
invoiceIndexBucket,
)
if invoiceIndex == nil {
return nil
}
invoiceAddIndex := invoices.NestedReadWriteBucket(
addIndexBucket,
)
if invoiceAddIndex == nil {
return nil
}
payAddrIndex := tx.ReadWriteBucket(payAddrIndexBucket)
return invoiceIndex.ForEach(func(k, v []byte) error {
// Skip the special numInvoicesKey as that does not
// point to a valid invoice.
if bytes.Equal(k, numInvoicesKey) {
return nil
}
// Skip sub-buckets.
if v == nil {
return nil
}
invoice, err := fetchInvoice(v, invoices, nil, false)
if err != nil {
return err
}
if invoice.State != invpkg.ContractCanceled {
return nil
}
// Delete the payment hash from the invoice index.
err = invoiceIndex.Delete(k)
if err != nil {
return err
}
// Delete payment address index reference if there's a
// valid payment address.
if invoice.Terms.PaymentAddr != invpkg.BlankPayAddr {
// To ensure consistency check that the already
// fetched invoice key matches the one in the
// payment address index.
key := payAddrIndex.Get(
invoice.Terms.PaymentAddr[:],
)
if bytes.Equal(key, k) {
// Delete from the payment address
// index.
if err := payAddrIndex.Delete(
invoice.Terms.PaymentAddr[:],
); err != nil {
return err
}
}
}
// Remove from the add index.
var addIndexKey [8]byte
byteOrder.PutUint64(addIndexKey[:], invoice.AddIndex)
err = invoiceAddIndex.Delete(addIndexKey[:])
if err != nil {
return err
}
// Note that we don't need to delete the invoice from
// the settle index as it is not added until the
// invoice is settled.
// Now remove all sub invoices.
err = delAMPInvoices(k, invoices)
if err != nil {
return err
}
// Finally remove the serialized invoice from the
// invoice bucket.
return invoices.Delete(k)
})
}, func() {})
}
// DeleteInvoice attempts to delete the passed invoices from the database in
// one transaction. The passed delete references hold all keys required to
// delete the invoices without also needing to deserialize them.
func (d *DB) DeleteInvoice(_ context.Context,
invoicesToDelete []invpkg.InvoiceDeleteRef) error {
err := kvdb.Update(d, func(tx kvdb.RwTx) error {
invoices := tx.ReadWriteBucket(invoiceBucket)
if invoices == nil {
return invpkg.ErrNoInvoicesCreated
}
invoiceIndex := invoices.NestedReadWriteBucket(
invoiceIndexBucket,
)
if invoiceIndex == nil {
return invpkg.ErrNoInvoicesCreated
}
invoiceAddIndex := invoices.NestedReadWriteBucket(
addIndexBucket,
)
if invoiceAddIndex == nil {
return invpkg.ErrNoInvoicesCreated
}
// settleIndex can be nil, as the bucket is created lazily
// when the first invoice is settled.
settleIndex := invoices.NestedReadWriteBucket(settleIndexBucket)
payAddrIndex := tx.ReadWriteBucket(payAddrIndexBucket)
for _, ref := range invoicesToDelete {
// Fetch the invoice key for using it to check for
// consistency and also to delete from the invoice
// index.
invoiceKey := invoiceIndex.Get(ref.PayHash[:])
if invoiceKey == nil {
return invpkg.ErrInvoiceNotFound
}
err := invoiceIndex.Delete(ref.PayHash[:])
if err != nil {
return err
}
// Delete payment address index reference if there's a
// valid payment address passed.
if ref.PayAddr != nil {
// To ensure consistency check that the already
// fetched invoice key matches the one in the
// payment address index.
key := payAddrIndex.Get(ref.PayAddr[:])
if bytes.Equal(key, invoiceKey) {
// Delete from the payment address
// index. Note that since the payment
// address index has been introduced
// with an empty migration it may be
// possible that the index doesn't have
// an entry for this invoice.
// ref: https://github.com/lightningnetwork/lnd/pull/4285/commits/cbf71b5452fa1d3036a43309e490787c5f7f08dc#r426368127
if err := payAddrIndex.Delete(
ref.PayAddr[:],
); err != nil {
return err
}
}
}
var addIndexKey [8]byte
byteOrder.PutUint64(addIndexKey[:], ref.AddIndex)
// To ensure consistency check that the key stored in
// the add index also matches the previously fetched
// invoice key.
key := invoiceAddIndex.Get(addIndexKey[:])
if !bytes.Equal(key, invoiceKey) {
return fmt.Errorf("unknown invoice " +
"in add index")
}
// Remove from the add index.
err = invoiceAddIndex.Delete(addIndexKey[:])
if err != nil {
return err
}
// Remove from the settle index if available and
// if the invoice is settled.
if settleIndex != nil && ref.SettleIndex > 0 {
var settleIndexKey [8]byte
byteOrder.PutUint64(
settleIndexKey[:], ref.SettleIndex,
)
// To ensure consistency check that the already
// fetched invoice key matches the one in the
// settle index
key := settleIndex.Get(settleIndexKey[:])
if !bytes.Equal(key, invoiceKey) {
return fmt.Errorf("unknown invoice " +
"in settle index")
}
err = settleIndex.Delete(settleIndexKey[:])
if err != nil {
return err
}
}
// In addition to deleting the main invoice state, if
// this is an AMP invoice, then we'll also need to
// delete the set HTLC set stored as a key prefix. For
// non-AMP invoices, this'll be a noop.
err = delAMPSettleIndex(
invoiceKey, invoices, settleIndex,
)
if err != nil {
return err
}
err = delAMPInvoices(invoiceKey, invoices)
if err != nil {
return err
}
// Finally remove the serialized invoice from the
// invoice bucket.
err = invoices.Delete(invoiceKey)
if err != nil {
return err
}
}
return nil
}, func() {})
return err
}
// SetInvoiceBucketTombstone sets the tombstone key in the invoice bucket to
// mark the bucket as permanently closed. This prevents it from being reopened
// in the future.
func (d *DB) SetInvoiceBucketTombstone() error {
return kvdb.Update(d, func(tx kvdb.RwTx) error {
// Access the top-level invoice bucket.
invoices := tx.ReadWriteBucket(invoiceBucket)
if invoices == nil {
return fmt.Errorf("invoice bucket does not exist")
}
// Add the tombstone key to the invoice bucket.
err := invoices.Put(invoiceBucketTombstone, []byte("1"))
if err != nil {
return fmt.Errorf("failed to set tombstone: %w", err)
}
return nil
}, func() {})
}
// GetInvoiceBucketTombstone checks if the tombstone key exists in the invoice
// bucket. It returns true if the tombstone is present and false otherwise.
func (d *DB) GetInvoiceBucketTombstone() (bool, error) {
var tombstoneExists bool
err := kvdb.View(d, func(tx kvdb.RTx) error {
// Access the top-level invoice bucket.
invoices := tx.ReadBucket(invoiceBucket)
if invoices == nil {
return fmt.Errorf("invoice bucket does not exist")
}
// Check if the tombstone key exists.
tombstone := invoices.Get(invoiceBucketTombstone)
tombstoneExists = tombstone != nil
return nil
}, func() {})
if err != nil {
return false, err
}
return tombstoneExists, nil
}
package channeldb
import (
"io"
)
// deserializeCloseChannelSummaryV6 reads the v6 database format for
// ChannelCloseSummary.
//
// NOTE: deprecated, only for migration.
func deserializeCloseChannelSummaryV6(r io.Reader) (*ChannelCloseSummary, error) {
c := &ChannelCloseSummary{}
err := ReadElements(r,
&c.ChanPoint, &c.ShortChanID, &c.ChainHash, &c.ClosingTXID,
&c.CloseHeight, &c.RemotePub, &c.Capacity, &c.SettledBalance,
&c.TimeLockedBalance, &c.CloseType, &c.IsPending,
)
if err != nil {
return nil, err
}
// We'll now check to see if the channel close summary was encoded with
// any of the additional optional fields.
err = ReadElements(r, &c.RemoteCurrentRevocation)
switch {
case err == io.EOF:
return c, nil
// If we got a non-eof error, then we know there's an actually issue.
// Otherwise, it may have been the case that this summary didn't have
// the set of optional fields.
case err != nil:
return nil, err
}
if err := readChanConfig(r, &c.LocalChanConfig); err != nil {
return nil, err
}
// Finally, we'll attempt to read the next unrevoked commitment point
// for the remote party. If we closed the channel before receiving a
// channel_ready message, then this can be nil. As a result, we'll use
// the same technique to read the field, only if there's still data
// left in the buffer.
err = ReadElements(r, &c.RemoteNextRevocation)
if err != nil && err != io.EOF {
// If we got a non-eof error, then we know there's an actually
// issue. Otherwise, it may have been the case that this
// summary didn't have the set of optional fields.
return nil, err
}
return c, nil
}
package channeldb
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
mig "github.com/lightningnetwork/lnd/channeldb/migration"
"github.com/lightningnetwork/lnd/channeldb/migration12"
"github.com/lightningnetwork/lnd/channeldb/migration13"
"github.com/lightningnetwork/lnd/channeldb/migration16"
"github.com/lightningnetwork/lnd/channeldb/migration24"
"github.com/lightningnetwork/lnd/channeldb/migration30"
"github.com/lightningnetwork/lnd/channeldb/migration31"
"github.com/lightningnetwork/lnd/channeldb/migration32"
"github.com/lightningnetwork/lnd/channeldb/migration33"
"github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
"github.com/lightningnetwork/lnd/kvdb"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
func init() {
UseLogger(build.NewSubLogger("CHDB", nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
mig.UseLogger(logger)
migration_01_to_11.UseLogger(logger)
migration12.UseLogger(logger)
migration13.UseLogger(logger)
migration16.UseLogger(logger)
migration24.UseLogger(logger)
migration30.UseLogger(logger)
migration31.UseLogger(logger)
migration32.UseLogger(logger)
migration33.UseLogger(logger)
kvdb.UseLogger(logger)
}
package channeldb
import (
"bytes"
"errors"
"fmt"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/tlv"
)
var (
// metaBucket stores all the meta information concerning the state of
// the database.
metaBucket = []byte("metadata")
// dbVersionKey is a boltdb key and it's used for storing/retrieving
// current database version.
dbVersionKey = []byte("dbp")
// dbVersionKey is a boltdb key and it's used for storing/retrieving
// a list of optional migrations that have been applied.
optionalVersionKey = []byte("ovk")
// TombstoneKey is the key under which we add a tag in the source DB
// after we've successfully and completely migrated it to the target/
// destination DB.
TombstoneKey = []byte("data-migration-tombstone")
// ErrMarkerNotPresent is the error that is returned if the queried
// marker is not present in the given database.
ErrMarkerNotPresent = errors.New("marker not present")
)
// Meta structure holds the database meta information.
type Meta struct {
// DbVersionNumber is the current schema version of the database.
DbVersionNumber uint32
}
// FetchMeta fetches the metadata from boltdb and returns filled meta structure.
func (d *DB) FetchMeta() (*Meta, error) {
var meta *Meta
err := kvdb.View(d, func(tx kvdb.RTx) error {
return FetchMeta(meta, tx)
}, func() {
meta = &Meta{}
})
if err != nil {
return nil, err
}
return meta, nil
}
// FetchMeta is a helper function used in order to allow callers to re-use a
// database transaction.
func FetchMeta(meta *Meta, tx kvdb.RTx) error {
metaBucket := tx.ReadBucket(metaBucket)
if metaBucket == nil {
return ErrMetaNotFound
}
data := metaBucket.Get(dbVersionKey)
if data == nil {
meta.DbVersionNumber = getLatestDBVersion(dbVersions)
} else {
meta.DbVersionNumber = byteOrder.Uint32(data)
}
return nil
}
// PutMeta writes the passed instance of the database met-data struct to disk.
func (d *DB) PutMeta(meta *Meta) error {
return kvdb.Update(d, func(tx kvdb.RwTx) error {
return putMeta(meta, tx)
}, func() {})
}
// putMeta is an internal helper function used in order to allow callers to
// re-use a database transaction. See the publicly exported PutMeta method for
// more information.
func putMeta(meta *Meta, tx kvdb.RwTx) error {
metaBucket, err := tx.CreateTopLevelBucket(metaBucket)
if err != nil {
return err
}
return putDbVersion(metaBucket, meta)
}
func putDbVersion(metaBucket kvdb.RwBucket, meta *Meta) error {
scratch := make([]byte, 4)
byteOrder.PutUint32(scratch, meta.DbVersionNumber)
return metaBucket.Put(dbVersionKey, scratch)
}
// OptionalMeta structure holds the database optional migration information.
type OptionalMeta struct {
// Versions is a set that contains the versions that have been applied.
// When saved to disk, only the indexes are stored.
Versions map[uint64]string
}
func (om *OptionalMeta) String() string {
s := ""
for index, name := range om.Versions {
s += fmt.Sprintf("%d: %s", index, name)
}
if s == "" {
s = "empty"
}
return s
}
// fetchOptionalMeta reads the optional meta from the database.
func (d *DB) fetchOptionalMeta() (*OptionalMeta, error) {
om := &OptionalMeta{
Versions: make(map[uint64]string),
}
err := kvdb.View(d, func(tx kvdb.RTx) error {
metaBucket := tx.ReadBucket(metaBucket)
if metaBucket == nil {
return ErrMetaNotFound
}
vBytes := metaBucket.Get(optionalVersionKey)
// Exit early if nothing found.
if vBytes == nil {
return nil
}
// Read the versions' length.
r := bytes.NewReader(vBytes)
vLen, err := tlv.ReadVarInt(r, &[8]byte{})
if err != nil {
return err
}
// Write the version index.
for i := uint64(0); i < vLen; i++ {
version, err := tlv.ReadVarInt(r, &[8]byte{})
if err != nil {
return err
}
om.Versions[version] = optionalVersions[i].name
}
return nil
}, func() {})
if err != nil {
return nil, err
}
return om, nil
}
// putOptionalMeta writes an optional meta to the database.
func (d *DB) putOptionalMeta(om *OptionalMeta) error {
return kvdb.Update(d, func(tx kvdb.RwTx) error {
metaBucket, err := tx.CreateTopLevelBucket(metaBucket)
if err != nil {
return err
}
var b bytes.Buffer
// Write the total length.
err = tlv.WriteVarInt(&b, uint64(len(om.Versions)), &[8]byte{})
if err != nil {
return err
}
// Write the version indexes.
for v := range om.Versions {
err := tlv.WriteVarInt(&b, v, &[8]byte{})
if err != nil {
return err
}
}
return metaBucket.Put(optionalVersionKey, b.Bytes())
}, func() {})
}
// CheckMarkerPresent returns the marker under the requested key or
// ErrMarkerNotFound if either the root bucket or the marker key within that
// bucket does not exist.
func CheckMarkerPresent(tx kvdb.RTx, markerKey []byte) ([]byte, error) {
markerBucket := tx.ReadBucket(markerKey)
if markerBucket == nil {
return nil, ErrMarkerNotPresent
}
val := markerBucket.Get(markerKey)
// If we wrote the marker correctly, we created a bucket _and_ created a
// key with a non-empty value. It doesn't matter to us whether the key
// exists or whether its value is empty, to us, it just means the marker
// isn't there.
if len(val) == 0 {
return nil, ErrMarkerNotPresent
}
return val, nil
}
// EnsureNoTombstone returns an error if there is a tombstone marker in the DB
// of the given transaction.
func EnsureNoTombstone(tx kvdb.RTx) error {
marker, err := CheckMarkerPresent(tx, TombstoneKey)
if err == ErrMarkerNotPresent {
// No marker present, so no tombstone. The DB is still alive.
return nil
}
if err != nil {
return err
}
// There was no error so there is a tombstone marker/tag. We cannot use
// this DB anymore.
return fmt.Errorf("refusing to use db, it was marked with a tombstone "+
"after successful data migration; tombstone reads: %s",
string(marker))
}
// AddMarker adds the marker with the given key into a top level bucket with the
// same name. So the structure will look like:
//
// marker-key (top level bucket)
// |-> marker-key:marker-value (key/value pair)
func AddMarker(tx kvdb.RwTx, markerKey, markerValue []byte) error {
if len(markerValue) == 0 {
return fmt.Errorf("marker value cannot be empty")
}
markerBucket, err := tx.CreateTopLevelBucket(markerKey)
if err != nil {
return err
}
return markerBucket.Put(markerKey, markerValue)
}
package migration
import (
"fmt"
"github.com/lightningnetwork/lnd/kvdb"
)
// CreateTLB creates a new top-level bucket with the passed bucket identifier.
func CreateTLB(bucket []byte) func(kvdb.RwTx) error {
return func(tx kvdb.RwTx) error {
log.Infof("Creating top-level bucket: \"%s\" ...", bucket)
if tx.ReadBucket(bucket) != nil {
return fmt.Errorf("top-level bucket \"%s\" "+
"already exists", bucket)
}
_, err := tx.CreateTopLevelBucket(bucket)
if err != nil {
return err
}
log.Infof("Created top-level bucket: \"%s\"", bucket)
return nil
}
}
package lnwire
import (
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
)
// AcceptChannel is the message Bob sends to Alice after she initiates the
// single funder channel workflow via an AcceptChannel message. Once Alice
// receives Bob's response, then she has all the items necessary to construct
// the funding transaction, and both commitment transactions.
type AcceptChannel struct {
// PendingChannelID serves to uniquely identify the future channel
// created by the initiated single funder workflow.
PendingChannelID [32]byte
// DustLimit is the specific dust limit the sender of this message
// would like enforced on their version of the commitment transaction.
// Any output below this value will be "trimmed" from the commitment
// transaction, with the amount of the HTLC going to dust.
DustLimit btcutil.Amount
// MaxValueInFlight represents the maximum amount of coins that can be
// pending within the channel at any given time. If the amount of funds
// in limbo exceeds this amount, then the channel will be failed.
MaxValueInFlight MilliSatoshi
// ChannelReserve is the amount of BTC that the receiving party MUST
// maintain a balance above at all times. This is a safety mechanism to
// ensure that both sides always have skin in the game during the
// channel's lifetime.
ChannelReserve btcutil.Amount
// HtlcMinimum is the smallest HTLC that the sender of this message
// will accept.
HtlcMinimum MilliSatoshi
// MinAcceptDepth is the minimum depth that the initiator of the
// channel should wait before considering the channel open.
MinAcceptDepth uint32
// CsvDelay is the number of blocks to use for the relative time lock
// in the pay-to-self output of both commitment transactions.
CsvDelay uint16
// MaxAcceptedHTLCs is the total number of incoming HTLC's that the
// sender of this channel will accept.
//
// TODO(roasbeef): acks the initiator's, same with max in flight?
MaxAcceptedHTLCs uint16
// FundingKey is the key that should be used on behalf of the sender
// within the 2-of-2 multi-sig output that it contained within the
// funding transaction.
FundingKey *btcec.PublicKey
// RevocationPoint is the base revocation point for the sending party.
// Any commitment transaction belonging to the receiver of this message
// should use this key and their per-commitment point to derive the
// revocation key for the commitment transaction.
RevocationPoint *btcec.PublicKey
// PaymentPoint is the base payment point for the sending party. This
// key should be combined with the per commitment point for a
// particular commitment state in order to create the key that should
// be used in any output that pays directly to the sending party, and
// also within the HTLC covenant transactions.
PaymentPoint *btcec.PublicKey
// DelayedPaymentPoint is the delay point for the sending party. This
// key should be combined with the per commitment point to derive the
// keys that are used in outputs of the sender's commitment transaction
// where they claim funds.
DelayedPaymentPoint *btcec.PublicKey
// HtlcPoint is the base point used to derive the set of keys for this
// party that will be used within the HTLC public key scripts. This
// value is combined with the receiver's revocation base point in order
// to derive the keys that are used within HTLC scripts.
HtlcPoint *btcec.PublicKey
// FirstCommitmentPoint is the first commitment point for the sending
// party. This value should be combined with the receiver's revocation
// base point in order to derive the revocation keys that are placed
// within the commitment transaction of the sender.
FirstCommitmentPoint *btcec.PublicKey
// UpfrontShutdownScript is the script to which the channel funds should
// be paid when mutually closing the channel. This field is optional, and
// and has a length prefix, so a zero will be written if it is not set
// and its length followed by the script will be written if it is set.
UpfrontShutdownScript DeliveryAddress
}
// A compile time check to ensure AcceptChannel implements the lnwire.Message
// interface.
var _ Message = (*AcceptChannel)(nil)
// Encode serializes the target AcceptChannel into the passed io.Writer
// implementation. Serialization will observe the rules defined by the passed
// protocol version.
//
// This is part of the lnwire.Message interface.
func (a *AcceptChannel) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
a.PendingChannelID[:],
a.DustLimit,
a.MaxValueInFlight,
a.ChannelReserve,
a.HtlcMinimum,
a.MinAcceptDepth,
a.CsvDelay,
a.MaxAcceptedHTLCs,
a.FundingKey,
a.RevocationPoint,
a.PaymentPoint,
a.DelayedPaymentPoint,
a.HtlcPoint,
a.FirstCommitmentPoint,
a.UpfrontShutdownScript,
)
}
// Decode deserializes the serialized AcceptChannel stored in the passed
// io.Reader into the target AcceptChannel using the deserialization rules
// defined by the passed protocol version.
//
// This is part of the lnwire.Message interface.
func (a *AcceptChannel) Decode(r io.Reader, pver uint32) error {
// Read all the mandatory fields in the accept message.
err := ReadElements(r,
a.PendingChannelID[:],
&a.DustLimit,
&a.MaxValueInFlight,
&a.ChannelReserve,
&a.HtlcMinimum,
&a.MinAcceptDepth,
&a.CsvDelay,
&a.MaxAcceptedHTLCs,
&a.FundingKey,
&a.RevocationPoint,
&a.PaymentPoint,
&a.DelayedPaymentPoint,
&a.HtlcPoint,
&a.FirstCommitmentPoint,
)
if err != nil {
return err
}
// Check for the optional upfront shutdown script field. If it is not there,
// silence the EOF error.
err = ReadElement(r, &a.UpfrontShutdownScript)
if err != nil && err != io.EOF {
return err
}
return nil
}
// MsgType returns the MessageType code which uniquely identifies this message
// as an AcceptChannel on the wire.
//
// This is part of the lnwire.Message interface.
func (a *AcceptChannel) MsgType() MessageType {
return MsgAcceptChannel
}
// MaxPayloadLength returns the maximum allowed payload length for a
// AcceptChannel message.
//
// This is part of the lnwire.Message interface.
func (a *AcceptChannel) MaxPayloadLength(uint32) uint32 {
// 32 + (8 * 4) + (4 * 1) + (2 * 2) + (33 * 6)
var length uint32 = 270 // base length
// Upfront shutdown script max length.
length += 2 + deliveryAddressMaxSize
return length
}
package lnwire
import (
"io"
)
// AnnounceSignatures is a direct message between two endpoints of a
// channel and serves as an opt-in mechanism to allow the announcement of
// the channel to the rest of the network. It contains the necessary
// signatures by the sender to construct the channel announcement message.
type AnnounceSignatures struct {
// ChannelID is the unique description of the funding transaction.
// Channel id is better for users and debugging and short channel id is
// used for quick test on existence of the particular utxo inside the
// block chain, because it contains information about block.
ChannelID ChannelID
// ShortChannelID is the unique description of the funding
// transaction. It is constructed with the most significant 3 bytes
// as the block height, the next 3 bytes indicating the transaction
// index within the block, and the least significant two bytes
// indicating the output index which pays to the channel.
ShortChannelID ShortChannelID
// NodeSignature is the signature which contains the signed announce
// channel message, by this signature we proof that we possess of the
// node pub key and creating the reference node_key -> bitcoin_key.
NodeSignature Sig
// BitcoinSignature is the signature which contains the signed node
// public key, by this signature we proof that we possess of the
// bitcoin key and and creating the reverse reference bitcoin_key ->
// node_key.
BitcoinSignature Sig
// ExtraOpaqueData is the set of data that was appended to this
// message, some of which we may not actually know how to iterate or
// parse. By holding onto this data, we ensure that we're able to
// properly validate the set of signatures that cover these new fields,
// and ensure we're able to make upgrades to the network in a forwards
// compatible manner.
ExtraOpaqueData []byte
}
// A compile time check to ensure AnnounceSignatures implements the
// lnwire.Message interface.
var _ Message = (*AnnounceSignatures)(nil)
// Decode deserializes a serialized AnnounceSignatures stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (a *AnnounceSignatures) Decode(r io.Reader, pver uint32) error {
err := ReadElements(r,
&a.ChannelID,
&a.ShortChannelID,
&a.NodeSignature,
&a.BitcoinSignature,
)
if err != nil {
return err
}
// Now that we've read out all the fields that we explicitly know of,
// we'll collect the remainder into the ExtraOpaqueData field. If there
// aren't any bytes, then we'll snip off the slice to avoid carrying
// around excess capacity.
a.ExtraOpaqueData, err = io.ReadAll(r)
if err != nil {
return err
}
if len(a.ExtraOpaqueData) == 0 {
a.ExtraOpaqueData = nil
}
return nil
}
// Encode serializes the target AnnounceSignatures into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (a *AnnounceSignatures) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
a.ChannelID,
a.ShortChannelID,
a.NodeSignature,
a.BitcoinSignature,
a.ExtraOpaqueData,
)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (a *AnnounceSignatures) MsgType() MessageType {
return MsgAnnounceSignatures
}
// MaxPayloadLength returns the maximum allowed payload size for this message
// observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (a *AnnounceSignatures) MaxPayloadLength(pver uint32) uint32 {
return 65533
}
package lnwire
import (
"bytes"
"io"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// ChannelAnnouncement message is used to announce the existence of a channel
// between two peers in the overlay, which is propagated by the discovery
// service over broadcast handler.
type ChannelAnnouncement struct {
// This signatures are used by nodes in order to create cross
// references between node's channel and node. Requiring both nodes
// to sign indicates they are both willing to route other payments via
// this node.
NodeSig1 Sig
NodeSig2 Sig
// This signatures are used by nodes in order to create cross
// references between node's channel and node. Requiring the bitcoin
// signatures proves they control the channel.
BitcoinSig1 Sig
BitcoinSig2 Sig
// Features is the feature vector that encodes the features supported
// by the target node. This field can be used to signal the type of the
// channel, or modifications to the fields that would normally follow
// this vector.
Features *RawFeatureVector
// ChainHash denotes the target chain that this channel was opened
// within. This value should be the genesis hash of the target chain.
ChainHash chainhash.Hash
// ShortChannelID is the unique description of the funding transaction,
// or where exactly it's located within the target blockchain.
ShortChannelID ShortChannelID
// The public keys of the two nodes who are operating the channel, such
// that is NodeID1 the numerically-lesser than NodeID2 (ascending
// numerical order).
NodeID1 [33]byte
NodeID2 [33]byte
// Public keys which corresponds to the keys which was declared in
// multisig funding transaction output.
BitcoinKey1 [33]byte
BitcoinKey2 [33]byte
// ExtraOpaqueData is the set of data that was appended to this
// message, some of which we may not actually know how to iterate or
// parse. By holding onto this data, we ensure that we're able to
// properly validate the set of signatures that cover these new fields,
// and ensure we're able to make upgrades to the network in a forwards
// compatible manner.
ExtraOpaqueData []byte
}
// A compile time check to ensure ChannelAnnouncement implements the
// lnwire.Message interface.
var _ Message = (*ChannelAnnouncement)(nil)
// Decode deserializes a serialized ChannelAnnouncement stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (a *ChannelAnnouncement) Decode(r io.Reader, pver uint32) error {
err := ReadElements(r,
&a.NodeSig1,
&a.NodeSig2,
&a.BitcoinSig1,
&a.BitcoinSig2,
&a.Features,
a.ChainHash[:],
&a.ShortChannelID,
&a.NodeID1,
&a.NodeID2,
&a.BitcoinKey1,
&a.BitcoinKey2,
)
if err != nil {
return err
}
// Now that we've read out all the fields that we explicitly know of,
// we'll collect the remainder into the ExtraOpaqueData field. If there
// aren't any bytes, then we'll snip off the slice to avoid carrying
// around excess capacity.
a.ExtraOpaqueData, err = io.ReadAll(r)
if err != nil {
return err
}
if len(a.ExtraOpaqueData) == 0 {
a.ExtraOpaqueData = nil
}
return nil
}
// Encode serializes the target ChannelAnnouncement into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (a *ChannelAnnouncement) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
a.NodeSig1,
a.NodeSig2,
a.BitcoinSig1,
a.BitcoinSig2,
a.Features,
a.ChainHash[:],
a.ShortChannelID,
a.NodeID1,
a.NodeID2,
a.BitcoinKey1,
a.BitcoinKey2,
a.ExtraOpaqueData,
)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (a *ChannelAnnouncement) MsgType() MessageType {
return MsgChannelAnnouncement
}
// MaxPayloadLength returns the maximum allowed payload size for this message
// observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (a *ChannelAnnouncement) MaxPayloadLength(pver uint32) uint32 {
return 65533
}
// DataToSign is used to retrieve part of the announcement message which should
// be signed.
func (a *ChannelAnnouncement) DataToSign() ([]byte, error) {
// We should not include the signatures itself.
var w bytes.Buffer
err := WriteElements(&w,
a.Features,
a.ChainHash[:],
a.ShortChannelID,
a.NodeID1,
a.NodeID2,
a.BitcoinKey1,
a.BitcoinKey2,
a.ExtraOpaqueData,
)
if err != nil {
return nil, err
}
return w.Bytes(), nil
}
package lnwire
import (
"encoding/binary"
"encoding/hex"
"math"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
)
const (
// MaxFundingTxOutputs is the maximum number of allowed outputs on a
// funding transaction within the protocol. This is due to the fact
// that we use 2-bytes to encode the index within the funding output
// during the funding workflow. Funding transaction with more outputs
// than this are considered invalid within the protocol.
MaxFundingTxOutputs = math.MaxUint16
)
// ChannelID is a series of 32-bytes that uniquely identifies all channels
// within the network. The ChannelID is computed using the outpoint of the
// funding transaction (the txid, and output index). Given a funding output the
// ChannelID can be calculated by XOR'ing the big-endian serialization of the
// txid and the big-endian serialization of the output index, truncated to
// 2 bytes.
type ChannelID [32]byte
// ConnectionWideID is an all-zero ChannelID, which is used to represent a
// message intended for all channels to specific peer.
var ConnectionWideID = ChannelID{}
// String returns the string representation of the ChannelID. This is just the
// hex string encoding of the ChannelID itself.
func (c ChannelID) String() string {
return hex.EncodeToString(c[:])
}
// NewChanIDFromOutPoint converts a target OutPoint into a ChannelID that is
// usable within the network. In order to convert the OutPoint into a ChannelID,
// we XOR the lower 2-bytes of the txid within the OutPoint with the big-endian
// serialization of the Index of the OutPoint, truncated to 2-bytes.
func NewChanIDFromOutPoint(op *wire.OutPoint) ChannelID {
// First we'll copy the txid of the outpoint into our channel ID slice.
var cid ChannelID
copy(cid[:], op.Hash[:])
// With the txid copied over, we'll now XOR the lower 2-bytes of the
// partial channelID with big-endian serialization of output index.
xorTxid(&cid, uint16(op.Index))
return cid
}
// xorTxid performs the transformation needed to transform an OutPoint into a
// ChannelID. To do this, we expect the cid parameter to contain the txid
// unaltered and the outputIndex to be the output index
func xorTxid(cid *ChannelID, outputIndex uint16) {
var buf [2]byte
binary.BigEndian.PutUint16(buf[:], outputIndex)
cid[30] ^= buf[0]
cid[31] ^= buf[1]
}
// GenPossibleOutPoints generates all the possible outputs given a channel ID.
// In order to generate these possible outpoints, we perform a brute-force
// search through the candidate output index space, performing a reverse
// mapping from channelID back to OutPoint.
func (c *ChannelID) GenPossibleOutPoints() [MaxFundingTxOutputs]wire.OutPoint {
var possiblePoints [MaxFundingTxOutputs]wire.OutPoint
for i := uint16(0); i < MaxFundingTxOutputs; i++ {
cidCopy := *c
xorTxid(&cidCopy, i)
possiblePoints[i] = wire.OutPoint{
Hash: chainhash.Hash(cidCopy),
Index: uint32(i),
}
}
return possiblePoints
}
// IsChanPoint returns true if the OutPoint passed corresponds to the target
// ChannelID.
func (c ChannelID) IsChanPoint(op *wire.OutPoint) bool {
candidateCid := NewChanIDFromOutPoint(op)
return candidateCid == c
}
package lnwire
import (
"io"
"github.com/btcsuite/btcd/btcec/v2"
)
// ChannelReestablish is a message sent between peers that have an existing
// open channel upon connection reestablishment. This message allows both sides
// to report their local state, and their current knowledge of the state of the
// remote commitment chain. If a deviation is detected and can be recovered
// from, then the necessary messages will be retransmitted. If the level of
// desynchronization is irreconcilable, then the channel will be force closed.
type ChannelReestablish struct {
// ChanID is the channel ID of the channel state we're attempting to
// synchronize with the remote party.
ChanID ChannelID
// NextLocalCommitHeight is the next local commitment height of the
// sending party. If the height of the sender's commitment chain from
// the receiver's Pov is one less that this number, then the sender
// should re-send the *exact* same proposed commitment.
//
// In other words, the receiver should re-send their last sent
// commitment iff:
//
// * NextLocalCommitHeight == remoteCommitChain.Height
//
// This covers the case of a lost commitment which was sent by the
// sender of this message, but never received by the receiver of this
// message.
NextLocalCommitHeight uint64
// RemoteCommitTailHeight is the height of the receiving party's
// unrevoked commitment from the PoV of the sender of this message. If
// the height of the receiver's commitment is *one more* than this
// value, then their prior RevokeAndAck message should be
// retransmitted.
//
// In other words, the receiver should re-send their last sent
// RevokeAndAck message iff:
//
// * localCommitChain.tail().Height == RemoteCommitTailHeight + 1
//
// This covers the case of a lost revocation, wherein the receiver of
// the message sent a revocation for a prior state, but the sender of
// the message never fully processed it.
RemoteCommitTailHeight uint64
// LastRemoteCommitSecret is the last commitment secret that the
// receiving node has sent to the sending party. This will be the
// secret of the last revoked commitment transaction. Including this
// provides proof that the sending node at least knows of this state,
// as they couldn't have produced it if it wasn't sent, as the value
// can be authenticated by querying the shachain or the receiving
// party.
LastRemoteCommitSecret [32]byte
// LocalUnrevokedCommitPoint is the commitment point used in the
// current un-revoked commitment transaction of the sending party.
LocalUnrevokedCommitPoint *btcec.PublicKey
}
// A compile time check to ensure ChannelReestablish implements the
// lnwire.Message interface.
var _ Message = (*ChannelReestablish)(nil)
// Encode serializes the target ChannelReestablish into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (a *ChannelReestablish) Encode(w io.Writer, pver uint32) error {
err := WriteElements(w,
a.ChanID,
a.NextLocalCommitHeight,
a.RemoteCommitTailHeight,
)
if err != nil {
return err
}
// If the commit point wasn't sent, then we won't write out any of the
// remaining fields as they're optional.
if a.LocalUnrevokedCommitPoint == nil {
return nil
}
// Otherwise, we'll write out the remaining elements.
return WriteElements(w, a.LastRemoteCommitSecret[:],
a.LocalUnrevokedCommitPoint)
}
// Decode deserializes a serialized ChannelReestablish stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (a *ChannelReestablish) Decode(r io.Reader, pver uint32) error {
err := ReadElements(r,
&a.ChanID,
&a.NextLocalCommitHeight,
&a.RemoteCommitTailHeight,
)
if err != nil {
return err
}
// This message has currently defined optional fields. As a result,
// we'll only proceed if there's still bytes remaining within the
// reader.
//
// We'll manually parse out the optional fields in order to be able to
// still utilize the io.Reader interface.
// We'll first attempt to read the optional commit secret, if we're at
// the EOF, then this means the field wasn't included so we can exit
// early.
var buf [32]byte
_, err = io.ReadFull(r, buf[:32])
if err == io.EOF {
return nil
} else if err != nil {
return err
}
// If the field is present, then we'll copy it over and proceed.
copy(a.LastRemoteCommitSecret[:], buf[:])
// We'll conclude by parsing out the commitment point. We don't check
// the error in this case, as it has included the commit secret, then
// they MUST also include the commit point.
return ReadElement(r, &a.LocalUnrevokedCommitPoint)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (a *ChannelReestablish) MsgType() MessageType {
return MsgChannelReestablish
}
// MaxPayloadLength returns the maximum allowed payload size for this message
// observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (a *ChannelReestablish) MaxPayloadLength(pver uint32) uint32 {
var length uint32
// ChanID - 32 bytes
length += 32
// NextLocalCommitHeight - 8 bytes
length += 8
// RemoteCommitTailHeight - 8 bytes
length += 8
// LastRemoteCommitSecret - 32 bytes
length += 32
// LocalUnrevokedCommitPoint - 33 bytes
length += 33
return length
}
package lnwire
import (
"bytes"
"fmt"
"io"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// ChanUpdateMsgFlags is a bitfield that signals whether optional fields are
// present in the ChannelUpdate.
type ChanUpdateMsgFlags uint8
const (
// ChanUpdateRequiredMaxHtlc is a bit that indicates whether the
// optional htlc_maximum_msat field is present in this ChannelUpdate.
ChanUpdateRequiredMaxHtlc ChanUpdateMsgFlags = 1 << iota
)
// String returns the bitfield flags as a string.
func (c ChanUpdateMsgFlags) String() string {
return fmt.Sprintf("%08b", c)
}
// HasMaxHtlc returns true if the htlc_maximum_msat option bit is set in the
// message flags.
func (c ChanUpdateMsgFlags) HasMaxHtlc() bool {
return c&ChanUpdateRequiredMaxHtlc != 0
}
// ChanUpdateChanFlags is a bitfield that signals various options concerning a
// particular channel edge. Each bit is to be examined in order to determine
// how the ChannelUpdate message is to be interpreted.
type ChanUpdateChanFlags uint8
const (
// ChanUpdateDirection indicates the direction of a channel update. If
// this bit is set to 0 if Node1 (the node with the "smaller" Node ID)
// is updating the channel, and to 1 otherwise.
ChanUpdateDirection ChanUpdateChanFlags = 1 << iota
// ChanUpdateDisabled is a bit that indicates if the channel edge
// selected by the ChanUpdateDirection bit is to be treated as being
// disabled.
ChanUpdateDisabled
)
// IsDisabled determines whether the channel flags has the disabled bit set.
func (c ChanUpdateChanFlags) IsDisabled() bool {
return c&ChanUpdateDisabled == ChanUpdateDisabled
}
// String returns the bitfield flags as a string.
func (c ChanUpdateChanFlags) String() string {
return fmt.Sprintf("%08b", c)
}
// ChannelUpdate message is used after channel has been initially announced.
// Each side independently announces its fees and minimum expiry for HTLCs and
// other parameters. Also this message is used to redeclare initially set
// channel parameters.
type ChannelUpdate struct {
// Signature is used to validate the announced data and prove the
// ownership of node id.
Signature Sig
// ChainHash denotes the target chain that this channel was opened
// within. This value should be the genesis hash of the target chain.
// Along with the short channel ID, this uniquely identifies the
// channel globally in a blockchain.
ChainHash chainhash.Hash
// ShortChannelID is the unique description of the funding transaction.
ShortChannelID ShortChannelID
// Timestamp allows ordering in the case of multiple announcements. We
// should ignore the message if timestamp is not greater than
// the last-received.
Timestamp uint32
// MessageFlags is a bitfield that describes whether optional fields
// are present in this update. Currently, the least-significant bit
// must be set to 1 if the optional field MaxHtlc is present.
MessageFlags ChanUpdateMsgFlags
// ChannelFlags is a bitfield that describes additional meta-data
// concerning how the update is to be interpreted. Currently, the
// least-significant bit must be set to 0 if the creating node
// corresponds to the first node in the previously sent channel
// announcement and 1 otherwise. If the second bit is set, then the
// channel is set to be disabled.
ChannelFlags ChanUpdateChanFlags
// TimeLockDelta is the minimum number of blocks this node requires to
// be added to the expiry of HTLCs. This is a security parameter
// determined by the node operator. This value represents the required
// gap between the time locks of the incoming and outgoing HTLC's set
// to this node.
TimeLockDelta uint16
// HtlcMinimumMsat is the minimum HTLC value which will be accepted.
HtlcMinimumMsat MilliSatoshi
// BaseFee is the base fee that must be used for incoming HTLC's to
// this particular channel. This value will be tacked onto the required
// for a payment independent of the size of the payment.
BaseFee uint32
// FeeRate is the fee rate that will be charged per millionth of a
// satoshi.
FeeRate uint32
// HtlcMaximumMsat is the maximum HTLC value which will be accepted.
HtlcMaximumMsat MilliSatoshi
// ExtraOpaqueData is the set of data that was appended to this
// message, some of which we may not actually know how to iterate or
// parse. By holding onto this data, we ensure that we're able to
// properly validate the set of signatures that cover these new fields,
// and ensure we're able to make upgrades to the network in a forwards
// compatible manner.
ExtraOpaqueData []byte
}
// A compile time check to ensure ChannelUpdate implements the lnwire.Message
// interface.
var _ Message = (*ChannelUpdate)(nil)
// Decode deserializes a serialized ChannelUpdate stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (a *ChannelUpdate) Decode(r io.Reader, pver uint32) error {
err := ReadElements(r,
&a.Signature,
a.ChainHash[:],
&a.ShortChannelID,
&a.Timestamp,
&a.MessageFlags,
&a.ChannelFlags,
&a.TimeLockDelta,
&a.HtlcMinimumMsat,
&a.BaseFee,
&a.FeeRate,
)
if err != nil {
return err
}
// Now check whether the max HTLC field is present and read it if so.
if a.MessageFlags.HasMaxHtlc() {
if err := ReadElements(r, &a.HtlcMaximumMsat); err != nil {
return err
}
}
// Now that we've read out all the fields that we explicitly know of,
// we'll collect the remainder into the ExtraOpaqueData field. If there
// aren't any bytes, then we'll snip off the slice to avoid carrying
// around excess capacity.
a.ExtraOpaqueData, err = io.ReadAll(r)
if err != nil {
return err
}
if len(a.ExtraOpaqueData) == 0 {
a.ExtraOpaqueData = nil
}
return nil
}
// Encode serializes the target ChannelUpdate into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (a *ChannelUpdate) Encode(w io.Writer, pver uint32) error {
err := WriteElements(w,
a.Signature,
a.ChainHash[:],
a.ShortChannelID,
a.Timestamp,
a.MessageFlags,
a.ChannelFlags,
a.TimeLockDelta,
a.HtlcMinimumMsat,
a.BaseFee,
a.FeeRate,
)
if err != nil {
return err
}
// Now append optional fields if they are set. Currently, the only
// optional field is max HTLC.
if a.MessageFlags.HasMaxHtlc() {
if err := WriteElements(w, a.HtlcMaximumMsat); err != nil {
return err
}
}
// Finally, append any extra opaque data.
return WriteElements(w, a.ExtraOpaqueData)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (a *ChannelUpdate) MsgType() MessageType {
return MsgChannelUpdate
}
// MaxPayloadLength returns the maximum allowed payload size for this message
// observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (a *ChannelUpdate) MaxPayloadLength(pver uint32) uint32 {
return 65533
}
// DataToSign is used to retrieve part of the announcement message which should
// be signed.
func (a *ChannelUpdate) DataToSign() ([]byte, error) {
// We should not include the signatures itself.
var w bytes.Buffer
err := WriteElements(&w,
a.ChainHash[:],
a.ShortChannelID,
a.Timestamp,
a.MessageFlags,
a.ChannelFlags,
a.TimeLockDelta,
a.HtlcMinimumMsat,
a.BaseFee,
a.FeeRate,
)
if err != nil {
return nil, err
}
// Now append optional fields if they are set. Currently, the only
// optional field is max HTLC.
if a.MessageFlags.HasMaxHtlc() {
if err := WriteElements(&w, a.HtlcMaximumMsat); err != nil {
return nil, err
}
}
// Finally, append any extra opaque data.
if err := WriteElements(&w, a.ExtraOpaqueData); err != nil {
return nil, err
}
return w.Bytes(), nil
}
package lnwire
import (
"io"
"github.com/btcsuite/btcd/btcutil"
)
// ClosingSigned is sent by both parties to a channel once the channel is clear
// of HTLCs, and is primarily concerned with negotiating fees for the close
// transaction. Each party provides a signature for a transaction with a fee
// that they believe is fair. The process terminates when both sides agree on
// the same fee, or when one side force closes the channel.
//
// NOTE: The responder is able to send a signature without any additional
// messages as all transactions are assembled observing BIP 69 which defines a
// canonical ordering for input/outputs. Therefore, both sides are able to
// arrive at an identical closure transaction as they know the order of the
// inputs/outputs.
type ClosingSigned struct {
// ChannelID serves to identify which channel is to be closed.
ChannelID ChannelID
// FeeSatoshis is the total fee in satoshis that the party to the
// channel would like to propose for the close transaction.
FeeSatoshis btcutil.Amount
// Signature is for the proposed channel close transaction.
Signature Sig
}
// NewClosingSigned creates a new empty ClosingSigned message.
func NewClosingSigned(cid ChannelID, fs btcutil.Amount,
sig Sig) *ClosingSigned {
return &ClosingSigned{
ChannelID: cid,
FeeSatoshis: fs,
Signature: sig,
}
}
// A compile time check to ensure ClosingSigned implements the lnwire.Message
// interface.
var _ Message = (*ClosingSigned)(nil)
// Decode deserializes a serialized ClosingSigned message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *ClosingSigned) Decode(r io.Reader, pver uint32) error {
return ReadElements(r, &c.ChannelID, &c.FeeSatoshis, &c.Signature)
}
// Encode serializes the target ClosingSigned into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *ClosingSigned) Encode(w io.Writer, pver uint32) error {
return WriteElements(w, c.ChannelID, c.FeeSatoshis, c.Signature)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *ClosingSigned) MsgType() MessageType {
return MsgClosingSigned
}
// MaxPayloadLength returns the maximum allowed payload size for a
// ClosingSigned complete message observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *ClosingSigned) MaxPayloadLength(uint32) uint32 {
var length uint32
// ChannelID - 32 bytes
length += 32
// FeeSatoshis - 8 bytes
length += 8
// Signature - 64 bytes
length += 64
return length
}
package lnwire
import (
"io"
)
// CommitSig is sent by either side to stage any pending HTLC's in the
// receiver's pending set into a new commitment state. Implicitly, the new
// commitment transaction constructed which has been signed by CommitSig
// includes all HTLC's in the remote node's pending set. A CommitSig message
// may be sent after a series of UpdateAddHTLC/UpdateFulfillHTLC messages in
// order to batch add several HTLC's with a single signature covering all
// implicitly accepted HTLC's.
type CommitSig struct {
// ChanID uniquely identifies to which currently active channel this
// CommitSig applies to.
ChanID ChannelID
// CommitSig is Alice's signature for Bob's new commitment transaction.
// Alice is able to send this signature without requesting any
// additional data due to the piggybacking of Bob's next revocation
// hash in his prior RevokeAndAck message, as well as the canonical
// ordering used for all inputs/outputs within commitment transactions.
// If initiating a new commitment state, this signature should ONLY
// cover all of the sending party's pending log updates, and the log
// updates of the remote party that have been ACK'd.
CommitSig Sig
// HtlcSigs is a signature for each relevant HTLC output within the
// created commitment. The order of the signatures is expected to be
// identical to the placement of the HTLC's within the BIP 69 sorted
// commitment transaction. For each outgoing HTLC (from the PoV of the
// sender of this message), a signature for an HTLC timeout transaction
// should be signed, for each incoming HTLC the HTLC timeout
// transaction should be signed.
HtlcSigs []Sig
}
// NewCommitSig creates a new empty CommitSig message.
func NewCommitSig() *CommitSig {
return &CommitSig{}
}
// A compile time check to ensure CommitSig implements the lnwire.Message
// interface.
var _ Message = (*CommitSig)(nil)
// Decode deserializes a serialized CommitSig message stored in the
// passed io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *CommitSig) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&c.ChanID,
&c.CommitSig,
&c.HtlcSigs,
)
}
// Encode serializes the target CommitSig into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *CommitSig) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
c.ChanID,
c.CommitSig,
c.HtlcSigs,
)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *CommitSig) MsgType() MessageType {
return MsgCommitSig
}
// MaxPayloadLength returns the maximum allowed payload size for a
// CommitSig complete message observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *CommitSig) MaxPayloadLength(uint32) uint32 {
// 32 + 64 + 2 + max_allowed_htlcs
return MaxMessagePayload
}
// TargetChanID returns the channel id of the link for which this message is
// intended.
//
// NOTE: Part of peer.LinkUpdater interface.
func (c *CommitSig) TargetChanID() ChannelID {
return c.ChanID
}
package lnwire
import (
"bytes"
"fmt"
"io"
"sort"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// MinCustomRecordsTlvType is the minimum custom records TLV type as
// defined in BOLT 01.
MinCustomRecordsTlvType = 65536
)
// CustomRecords stores a set of custom key/value pairs. Map keys are TLV types
// which must be greater than or equal to MinCustomRecordsTlvType.
type CustomRecords map[uint64][]byte
// NewCustomRecords creates a new CustomRecords instance from a
// tlv.TypeMap.
func NewCustomRecords(tlvMap tlv.TypeMap) (CustomRecords, error) {
// Make comparisons in unit tests easy by returning nil if the map is
// empty.
if len(tlvMap) == 0 {
return nil, nil
}
customRecords := make(CustomRecords, len(tlvMap))
for k, v := range tlvMap {
customRecords[uint64(k)] = v
}
// Validate the custom records.
err := customRecords.Validate()
if err != nil {
return nil, fmt.Errorf("custom records from tlv map "+
"validation error: %w", err)
}
return customRecords, nil
}
// ParseCustomRecords creates a new CustomRecords instance from a tlv.Blob.
func ParseCustomRecords(b tlv.Blob) (CustomRecords, error) {
return ParseCustomRecordsFrom(bytes.NewReader(b))
}
// ParseCustomRecordsFrom creates a new CustomRecords instance from a reader.
func ParseCustomRecordsFrom(r io.Reader) (CustomRecords, error) {
typeMap, err := DecodeRecords(r)
if err != nil {
return nil, fmt.Errorf("error decoding HTLC record: %w", err)
}
return NewCustomRecords(typeMap)
}
// Validate checks that all custom records are in the custom type range.
func (c CustomRecords) Validate() error {
if c == nil {
return nil
}
for key := range c {
if key < MinCustomRecordsTlvType {
return fmt.Errorf("custom records entry with TLV "+
"type below min: %d", MinCustomRecordsTlvType)
}
}
return nil
}
// Copy returns a copy of the custom records.
func (c CustomRecords) Copy() CustomRecords {
if c == nil {
return nil
}
customRecords := make(CustomRecords, len(c))
for k, v := range c {
customRecords[k] = v
}
return customRecords
}
// ExtendRecordProducers extends the given records slice with the custom
// records. The resultant records slice will be sorted if the given records
// slice contains TLV types greater than or equal to MinCustomRecordsTlvType.
func (c CustomRecords) ExtendRecordProducers(
producers []tlv.RecordProducer) ([]tlv.RecordProducer, error) {
// If the custom records are nil or empty, there is nothing to do.
if len(c) == 0 {
return producers, nil
}
// Validate the custom records.
err := c.Validate()
if err != nil {
return nil, err
}
// Ensure that the existing records slice TLV types are not also present
// in the custom records. If they are, the resultant extended records
// slice would erroneously contain duplicate TLV types.
for _, rp := range producers {
record := rp.Record()
recordTlvType := uint64(record.Type())
_, foundDuplicateTlvType := c[recordTlvType]
if foundDuplicateTlvType {
return nil, fmt.Errorf("custom records contains a TLV "+
"type that is already present in the "+
"existing records: %d", recordTlvType)
}
}
// Convert the custom records map to a TLV record producer slice and
// append them to the exiting records slice.
customRecordProducers := RecordsAsProducers(tlv.MapToRecords(c))
producers = append(producers, customRecordProducers...)
// If the records slice which was given as an argument included TLV
// values greater than or equal to the minimum custom records TLV type
// we will sort the extended records slice to ensure that it is ordered
// correctly.
SortProducers(producers)
return producers, nil
}
// RecordProducers returns a slice of record producers for the custom records.
func (c CustomRecords) RecordProducers() []tlv.RecordProducer {
// If the custom records are nil or empty, return an empty slice.
if len(c) == 0 {
return nil
}
// Convert the custom records map to a TLV record producer slice.
records := tlv.MapToRecords(c)
return RecordsAsProducers(records)
}
// Serialize serializes the custom records into a byte slice.
func (c CustomRecords) Serialize() ([]byte, error) {
records := tlv.MapToRecords(c)
return EncodeRecords(records)
}
// SerializeTo serializes the custom records into the given writer.
func (c CustomRecords) SerializeTo(w io.Writer) error {
records := tlv.MapToRecords(c)
return EncodeRecordsTo(w, records)
}
// ProduceRecordsSorted converts a slice of record producers into a slice of
// records and then sorts it by type.
func ProduceRecordsSorted(recordProducers ...tlv.RecordProducer) []tlv.Record {
records := fn.Map(
recordProducers,
func(producer tlv.RecordProducer) tlv.Record {
return producer.Record()
},
)
// Ensure that the set of records are sorted before we attempt to
// decode from the stream, to ensure they're canonical.
tlv.SortRecords(records)
return records
}
// SortProducers sorts the given record producers by their type.
func SortProducers(producers []tlv.RecordProducer) {
sort.Slice(producers, func(i, j int) bool {
recordI := producers[i].Record()
recordJ := producers[j].Record()
return recordI.Type() < recordJ.Type()
})
}
// TlvMapToRecords converts a TLV map into a slice of records.
func TlvMapToRecords(tlvMap tlv.TypeMap) []tlv.Record {
tlvMapGeneric := make(map[uint64][]byte)
for k, v := range tlvMap {
tlvMapGeneric[uint64(k)] = v
}
return tlv.MapToRecords(tlvMapGeneric)
}
// RecordsAsProducers converts a slice of records into a slice of record
// producers.
func RecordsAsProducers(records []tlv.Record) []tlv.RecordProducer {
return fn.Map(records, func(record tlv.Record) tlv.RecordProducer {
return &record
})
}
// EncodeRecords encodes the given records into a byte slice.
func EncodeRecords(records []tlv.Record) ([]byte, error) {
var buf bytes.Buffer
if err := EncodeRecordsTo(&buf, records); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// EncodeRecordsTo encodes the given records into the given writer.
func EncodeRecordsTo(w io.Writer, records []tlv.Record) error {
tlvStream, err := tlv.NewStream(records...)
if err != nil {
return err
}
return tlvStream.Encode(w)
}
// DecodeRecords decodes the given byte slice into the given records and returns
// the rest as a TLV type map.
func DecodeRecords(r io.Reader,
records ...tlv.Record) (tlv.TypeMap, error) {
tlvStream, err := tlv.NewStream(records...)
if err != nil {
return nil, err
}
return tlvStream.DecodeWithParsedTypes(r)
}
// DecodeRecordsP2P decodes the given byte slice into the given records and
// returns the rest as a TLV type map. This function is identical to
// DecodeRecords except that the record size is capped at 65535.
func DecodeRecordsP2P(r *bytes.Reader,
records ...tlv.Record) (tlv.TypeMap, error) {
tlvStream, err := tlv.NewStream(records...)
if err != nil {
return nil, err
}
return tlvStream.DecodeWithParsedTypesP2P(r)
}
// AssertUniqueTypes asserts that the given records have unique types.
func AssertUniqueTypes(r []tlv.Record) error {
seen := make(fn.Set[tlv.Type], len(r))
for _, record := range r {
t := record.Type()
if seen.Contains(t) {
return fmt.Errorf("duplicate record type: %d", t)
}
seen.Add(t)
}
return nil
}
package lnwire
import (
"fmt"
"io"
)
// FundingError represents a set of errors that can be encountered and sent
// during the funding workflow.
type FundingError uint8
const (
// ErrMaxPendingChannels is returned by remote peer when the number of
// active pending channels exceeds their maximum policy limit.
ErrMaxPendingChannels FundingError = 1
// ErrSynchronizingChain is returned by a remote peer that receives a
// channel update or a funding request while it's still syncing to the
// latest state of the blockchain.
ErrSynchronizingChain FundingError = 2
// ErrChanTooLarge is returned by a remote peer that receives a
// FundingOpen request for a channel that is above their current
// soft-limit.
ErrChanTooLarge FundingError = 3
)
// String returns a human readable version of the target FundingError.
func (e FundingError) String() string {
switch e {
case ErrMaxPendingChannels:
return "Number of pending channels exceed maximum"
case ErrSynchronizingChain:
return "Synchronizing blockchain"
case ErrChanTooLarge:
return "channel too large"
default:
return "unknown error"
}
}
// Error returns the human readable version of the target FundingError.
//
// NOTE: Satisfies the Error interface.
func (e FundingError) Error() string {
return e.String()
}
// ErrorData is a set of bytes associated with a particular sent error. A
// receiving node SHOULD only print out data verbatim if the string is composed
// solely of printable ASCII characters. For reference, the printable character
// set includes byte values 32 through 127 inclusive.
type ErrorData []byte
// Error represents a generic error bound to an exact channel. The message
// format is purposefully general in order to allow expression of a wide array
// of possible errors. Each Error message is directed at a particular open
// channel referenced by ChannelPoint.
type Error struct {
// ChanID references the active channel in which the error occurred
// within. If the ChanID is all zeros, then this error applies to the
// entire established connection.
ChanID ChannelID
// Data is the attached error data that describes the exact failure
// which caused the error message to be sent.
Data ErrorData
}
// NewError creates a new Error message.
func NewError() *Error {
return &Error{}
}
// A compile time check to ensure Error implements the lnwire.Message
// interface.
var _ Message = (*Error)(nil)
// Error returns the string representation to Error.
//
// NOTE: Satisfies the error interface.
func (c *Error) Error() string {
errMsg := "non-ascii data"
if isASCII(c.Data) {
errMsg = string(c.Data)
}
return fmt.Sprintf("chan_id=%v, err=%v", c.ChanID, errMsg)
}
// Decode deserializes a serialized Error message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *Error) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&c.ChanID,
&c.Data,
)
}
// Encode serializes the target Error into the passed io.Writer observing the
// protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *Error) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
c.ChanID,
c.Data,
)
}
// MsgType returns the integer uniquely identifying an Error message on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *Error) MsgType() MessageType {
return MsgError
}
// MaxPayloadLength returns the maximum allowed payload size for an Error
// complete message observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *Error) MaxPayloadLength(uint32) uint32 {
// 32 + 2 + 65501
return MaxMessagePayload
}
// isASCII is a helper method that checks whether all bytes in `data` would be
// printable ASCII characters if interpreted as a string.
func isASCII(data []byte) bool {
for _, c := range data {
if c < 32 || c > 126 {
return false
}
}
return true
}
package lnwire
import (
"encoding/binary"
"errors"
"io"
)
var (
// ErrFeaturePairExists signals an error in feature vector construction
// where the opposing bit in a feature pair has already been set.
ErrFeaturePairExists = errors.New("feature pair exists")
)
// FeatureBit represents a feature that can be enabled in either a local or
// global feature vector at a specific bit position. Feature bits follow the
// "it's OK to be odd" rule, where features at even bit positions must be known
// to a node receiving them from a peer while odd bits do not. In accordance,
// feature bits are usually assigned in pairs, first being assigned an odd bit
// position which may later be changed to the preceding even position once
// knowledge of the feature becomes required on the network.
type FeatureBit uint16
const (
// DataLossProtectRequired is a feature bit that indicates that a peer
// *requires* the other party know about the data-loss-protect optional
// feature. If the remote peer does not know of such a feature, then
// the sending peer SHOLUD disconnect them. The data-loss-protect
// feature allows a peer that's lost partial data to recover their
// settled funds of the latest commitment state.
DataLossProtectRequired FeatureBit = 0
// DataLossProtectOptional is an optional feature bit that indicates
// that the sending peer knows of this new feature and can activate it
// it. The data-loss-protect feature allows a peer that's lost partial
// data to recover their settled funds of the latest commitment state.
DataLossProtectOptional FeatureBit = 1
// InitialRoutingSync is a local feature bit meaning that the receiving
// node should send a complete dump of routing information when a new
// connection is established.
InitialRoutingSync FeatureBit = 3
// UpfrontShutdownScriptRequired is a feature bit which indicates that a
// peer *requires* that the remote peer accept an upfront shutdown script to
// which payout is enforced on cooperative closes.
UpfrontShutdownScriptRequired FeatureBit = 4
// UpfrontShutdownScriptOptional is an optional feature bit which indicates
// that the peer will accept an upfront shutdown script to which payout is
// enforced on cooperative closes.
UpfrontShutdownScriptOptional FeatureBit = 5
// GossipQueriesRequired is a feature bit that indicates that the
// receiving peer MUST know of the set of features that allows nodes to
// more efficiently query the network view of peers on the network for
// reconciliation purposes.
GossipQueriesRequired FeatureBit = 6
// GossipQueriesOptional is an optional feature bit that signals that
// the setting peer knows of the set of features that allows more
// efficient network view reconciliation.
GossipQueriesOptional FeatureBit = 7
// TLVOnionPayloadRequired is a feature bit that indicates a node is
// able to decode the new TLV information included in the onion packet.
TLVOnionPayloadRequired FeatureBit = 8
// TLVOnionPayloadOptional is an optional feature bit that indicates a
// node is able to decode the new TLV information included in the onion
// packet.
TLVOnionPayloadOptional FeatureBit = 9
// StaticRemoteKeyRequired is a required feature bit that signals that
// within one's commitment transaction, the key used for the remote
// party's non-delay output should not be tweaked.
StaticRemoteKeyRequired FeatureBit = 12
// StaticRemoteKeyOptional is an optional feature bit that signals that
// within one's commitment transaction, the key used for the remote
// party's non-delay output should not be tweaked.
StaticRemoteKeyOptional FeatureBit = 13
// PaymentAddrRequired is a required feature bit that signals that a
// node requires payment addresses, which are used to mitigate probing
// attacks on the receiver of a payment.
PaymentAddrRequired FeatureBit = 14
// PaymentAddrOptional is an optional feature bit that signals that a
// node supports payment addresses, which are used to mitigate probing
// attacks on the receiver of a payment.
PaymentAddrOptional FeatureBit = 15
// MPPOptional is a required feature bit that signals that the receiver
// of a payment requires settlement of an invoice with more than one
// HTLC.
MPPRequired FeatureBit = 16
// MPPOptional is an optional feature bit that signals that the receiver
// of a payment supports settlement of an invoice with more than one
// HTLC.
MPPOptional FeatureBit = 17
// WumboChannelsRequired is a required feature bit that signals that a
// node is willing to accept channels larger than 2^24 satoshis.
WumboChannelsRequired FeatureBit = 18
// WumboChannelsOptional is an optional feature bit that signals that a
// node is willing to accept channels larger than 2^24 satoshis.
WumboChannelsOptional FeatureBit = 19
// AnchorsRequired is a required feature bit that signals that the node
// requires channels to be made using commitments having anchor
// outputs.
AnchorsRequired FeatureBit = 20
// AnchorsOptional is an optional feature bit that signals that the
// node supports channels to be made using commitments having anchor
// outputs.
AnchorsOptional FeatureBit = 21
// AnchorsZeroFeeHtlcTxRequired is a required feature bit that signals
// that the node requires channels having zero-fee second-level HTLC
// transactions, which also imply anchor commitments.
AnchorsZeroFeeHtlcTxRequired FeatureBit = 22
// AnchorsZeroFeeHtlcTxRequired is an optional feature bit that signals
// that the node supports channels having zero-fee second-level HTLC
// transactions, which also imply anchor commitments.
AnchorsZeroFeeHtlcTxOptional FeatureBit = 23
// maxAllowedSize is a maximum allowed size of feature vector.
//
// NOTE: Within the protocol, the maximum allowed message size is 65535
// bytes for all messages. Accounting for the overhead within the feature
// message to signal the type of message, that leaves us with 65533 bytes
// for the init message itself. Next, we reserve 4 bytes to encode the
// lengths of both the local and global feature vectors, so 65529 bytes
// for the local and global features. Knocking off one byte for the sake
// of the calculation, that leads us to 32764 bytes for each feature
// vector, or 131056 different features.
maxAllowedSize = 32764
)
// IsRequired returns true if the feature bit is even, and false otherwise.
func (b FeatureBit) IsRequired() bool {
return b&0x01 == 0x00
}
// Features is a mapping of known feature bits to a descriptive name. All known
// feature bits must be assigned a name in this mapping, and feature bit pairs
// must be assigned together for correct behavior.
var Features = map[FeatureBit]string{
DataLossProtectRequired: "data-loss-protect",
DataLossProtectOptional: "data-loss-protect",
InitialRoutingSync: "initial-routing-sync",
UpfrontShutdownScriptRequired: "upfront-shutdown-script",
UpfrontShutdownScriptOptional: "upfront-shutdown-script",
GossipQueriesRequired: "gossip-queries",
GossipQueriesOptional: "gossip-queries",
TLVOnionPayloadRequired: "tlv-onion",
TLVOnionPayloadOptional: "tlv-onion",
StaticRemoteKeyOptional: "static-remote-key",
StaticRemoteKeyRequired: "static-remote-key",
PaymentAddrOptional: "payment-addr",
PaymentAddrRequired: "payment-addr",
MPPOptional: "multi-path-payments",
MPPRequired: "multi-path-payments",
AnchorsRequired: "anchor-commitments",
AnchorsOptional: "anchor-commitments",
AnchorsZeroFeeHtlcTxRequired: "anchors-zero-fee-htlc-tx",
AnchorsZeroFeeHtlcTxOptional: "anchors-zero-fee-htlc-tx",
WumboChannelsRequired: "wumbo-channels",
WumboChannelsOptional: "wumbo-channels",
}
// RawFeatureVector represents a set of feature bits as defined in BOLT-09. A
// RawFeatureVector itself just stores a set of bit flags but can be used to
// construct a FeatureVector which binds meaning to each bit. Feature vectors
// can be serialized and deserialized to/from a byte representation that is
// transmitted in Lightning network messages.
type RawFeatureVector struct {
features map[FeatureBit]bool
}
// NewRawFeatureVector creates a feature vector with all of the feature bits
// given as arguments enabled.
func NewRawFeatureVector(bits ...FeatureBit) *RawFeatureVector {
fv := &RawFeatureVector{features: make(map[FeatureBit]bool)}
for _, bit := range bits {
fv.Set(bit)
}
return fv
}
// Merges sets all feature bits in other on the receiver's feature vector.
func (fv *RawFeatureVector) Merge(other *RawFeatureVector) error {
for bit := range other.features {
err := fv.SafeSet(bit)
if err != nil {
return err
}
}
return nil
}
// Clone makes a copy of a feature vector.
func (fv *RawFeatureVector) Clone() *RawFeatureVector {
newFeatures := NewRawFeatureVector()
for bit := range fv.features {
newFeatures.Set(bit)
}
return newFeatures
}
// IsSet returns whether a particular feature bit is enabled in the vector.
func (fv *RawFeatureVector) IsSet(feature FeatureBit) bool {
return fv.features[feature]
}
// Set marks a feature as enabled in the vector.
func (fv *RawFeatureVector) Set(feature FeatureBit) {
fv.features[feature] = true
}
// SafeSet sets the chosen feature bit in the feature vector, but returns an
// error if the opposing feature bit is already set. This ensures both that we
// are creating properly structured feature vectors, and in some cases, that
// peers are sending properly encoded ones, i.e. it can't be both optional and
// required.
func (fv *RawFeatureVector) SafeSet(feature FeatureBit) error {
if _, ok := fv.features[feature^1]; ok {
return ErrFeaturePairExists
}
fv.Set(feature)
return nil
}
// Unset marks a feature as disabled in the vector.
func (fv *RawFeatureVector) Unset(feature FeatureBit) {
delete(fv.features, feature)
}
// SerializeSize returns the number of bytes needed to represent feature vector
// in byte format.
func (fv *RawFeatureVector) SerializeSize() int {
// We calculate byte-length via the largest bit index.
return fv.serializeSize(8)
}
// SerializeSize32 returns the number of bytes needed to represent feature
// vector in base32 format.
func (fv *RawFeatureVector) SerializeSize32() int {
// We calculate base32-length via the largest bit index.
return fv.serializeSize(5)
}
// serializeSize returns the number of bytes required to encode the feature
// vector using at most width bits per encoded byte.
func (fv *RawFeatureVector) serializeSize(width int) int {
// Find the largest feature bit index
max := -1
for feature := range fv.features {
index := int(feature)
if index > max {
max = index
}
}
if max == -1 {
return 0
}
return max/width + 1
}
// Encode writes the feature vector in byte representation. Every feature
// encoded as a bit, and the bit vector is serialized using the least number of
// bytes. Since the bit vector length is variable, the first two bytes of the
// serialization represent the length.
func (fv *RawFeatureVector) Encode(w io.Writer) error {
// Write length of feature vector.
var l [2]byte
length := fv.SerializeSize()
binary.BigEndian.PutUint16(l[:], uint16(length))
if _, err := w.Write(l[:]); err != nil {
return err
}
return fv.encode(w, length, 8)
}
// EncodeBase256 writes the feature vector in base256 representation. Every
// feature is encoded as a bit, and the bit vector is serialized using the least
// number of bytes.
func (fv *RawFeatureVector) EncodeBase256(w io.Writer) error {
length := fv.SerializeSize()
return fv.encode(w, length, 8)
}
// EncodeBase32 writes the feature vector in base32 representation. Every feature
// is encoded as a bit, and the bit vector is serialized using the least number of
// bytes.
func (fv *RawFeatureVector) EncodeBase32(w io.Writer) error {
length := fv.SerializeSize32()
return fv.encode(w, length, 5)
}
// encode writes the feature vector
func (fv *RawFeatureVector) encode(w io.Writer, length, width int) error {
// Generate the data and write it.
data := make([]byte, length)
for feature := range fv.features {
byteIndex := int(feature) / width
bitIndex := int(feature) % width
data[length-byteIndex-1] |= 1 << uint(bitIndex)
}
_, err := w.Write(data)
return err
}
// Decode reads the feature vector from its byte representation. Every feature
// is encoded as a bit, and the bit vector is serialized using the least number
// of bytes. Since the bit vector length is variable, the first two bytes of the
// serialization represent the length.
func (fv *RawFeatureVector) Decode(r io.Reader) error {
// Read the length of the feature vector.
var l [2]byte
if _, err := io.ReadFull(r, l[:]); err != nil {
return err
}
length := binary.BigEndian.Uint16(l[:])
return fv.decode(r, int(length), 8)
}
// DecodeBase256 reads the feature vector from its base256 representation. Every
// feature encoded as a bit, and the bit vector is serialized using the least
// number of bytes.
func (fv *RawFeatureVector) DecodeBase256(r io.Reader, length int) error {
return fv.decode(r, length, 8)
}
// DecodeBase32 reads the feature vector from its base32 representation. Every
// feature encoded as a bit, and the bit vector is serialized using the least
// number of bytes.
func (fv *RawFeatureVector) DecodeBase32(r io.Reader, length int) error {
return fv.decode(r, length, 5)
}
// decode reads a feature vector from the next length bytes of the io.Reader,
// assuming each byte has width feature bits encoded per byte.
func (fv *RawFeatureVector) decode(r io.Reader, length, width int) error {
// Read the feature vector data.
data := make([]byte, length)
if _, err := io.ReadFull(r, data); err != nil {
return err
}
// Set feature bits from parsed data.
bitsNumber := len(data) * width
for i := 0; i < bitsNumber; i++ {
byteIndex := int(i / width)
bitIndex := uint(i % width)
if (data[length-byteIndex-1]>>bitIndex)&1 == 1 {
fv.Set(FeatureBit(i))
}
}
return nil
}
// FeatureVector represents a set of enabled features. The set stores
// information on enabled flags and metadata about the feature names. A feature
// vector is serializable to a compact byte representation that is included in
// Lightning network messages.
type FeatureVector struct {
*RawFeatureVector
featureNames map[FeatureBit]string
}
// NewFeatureVector constructs a new FeatureVector from a raw feature vector
// and mapping of feature definitions. If the feature vector argument is nil, a
// new one will be constructed with no enabled features.
func NewFeatureVector(featureVector *RawFeatureVector,
featureNames map[FeatureBit]string) *FeatureVector {
if featureVector == nil {
featureVector = NewRawFeatureVector()
}
return &FeatureVector{
RawFeatureVector: featureVector,
featureNames: featureNames,
}
}
// EmptyFeatureVector returns a feature vector with no bits set.
func EmptyFeatureVector() *FeatureVector {
return NewFeatureVector(nil, Features)
}
// HasFeature returns whether a particular feature is included in the set. The
// feature can be seen as set either if the bit is set directly OR the queried
// bit has the same meaning as its corresponding even/odd bit, which is set
// instead. The second case is because feature bits are generally assigned in
// pairs where both the even and odd position represent the same feature.
func (fv *FeatureVector) HasFeature(feature FeatureBit) bool {
return fv.IsSet(feature) ||
(fv.isFeatureBitPair(feature) && fv.IsSet(feature^1))
}
// RequiresFeature returns true if the referenced feature vector *requires*
// that the given required bit be set. This method can be used with both
// optional and required feature bits as a parameter.
func (fv *FeatureVector) RequiresFeature(feature FeatureBit) bool {
// If we weren't passed a required feature bit, then we'll flip the
// lowest bit to query for the required version of the feature. This
// lets callers pass in both the optional and required bits.
if !feature.IsRequired() {
feature ^= 1
}
return fv.IsSet(feature)
}
// UnknownRequiredFeatures returns a list of feature bits set in the vector
// that are unknown and in an even bit position. Feature bits with an even
// index must be known to a node receiving the feature vector in a message.
func (fv *FeatureVector) UnknownRequiredFeatures() []FeatureBit {
var unknown []FeatureBit
for feature := range fv.features {
if feature%2 == 0 && !fv.IsKnown(feature) {
unknown = append(unknown, feature)
}
}
return unknown
}
// Name returns a string identifier for the feature represented by this bit. If
// the bit does not represent a known feature, this returns a string indicating
// as such.
func (fv *FeatureVector) Name(bit FeatureBit) string {
name, known := fv.featureNames[bit]
if !known {
return "unknown"
}
return name
}
// IsKnown returns whether this feature bit represents a known feature.
func (fv *FeatureVector) IsKnown(bit FeatureBit) bool {
_, known := fv.featureNames[bit]
return known
}
// isFeatureBitPair returns whether this feature bit and its corresponding
// even/odd bit both represent the same feature. This may often be the case as
// bits are generally assigned in pairs, first being assigned an odd bit
// position then being promoted to an even bit position once the network is
// ready.
func (fv *FeatureVector) isFeatureBitPair(bit FeatureBit) bool {
name1, known1 := fv.featureNames[bit]
name2, known2 := fv.featureNames[bit^1]
return known1 && known2 && name1 == name2
}
// Features returns the set of raw features contained in the feature vector.
func (fv *FeatureVector) Features() map[FeatureBit]struct{} {
fs := make(map[FeatureBit]struct{}, len(fv.RawFeatureVector.features))
for b := range fv.RawFeatureVector.features {
fs[b] = struct{}{}
}
return fs
}
// Clone copies a feature vector, carrying over its feature bits. The feature
// names are not copied.
func (fv *FeatureVector) Clone() *FeatureVector {
features := fv.RawFeatureVector.Clone()
return NewFeatureVector(features, fv.featureNames)
}
package lnwire
import (
"io"
"github.com/btcsuite/btcd/wire"
)
// FundingCreated is sent from Alice (the initiator) to Bob (the responder),
// once Alice receives Bob's contributions as well as his channel constraints.
// Once bob receives this message, he'll gain access to an immediately
// broadcastable commitment transaction and will reply with a signature for
// Alice's version of the commitment transaction.
type FundingCreated struct {
// PendingChannelID serves to uniquely identify the future channel
// created by the initiated single funder workflow.
PendingChannelID [32]byte
// FundingPoint is the outpoint of the funding transaction created by
// Alice. With this, Bob is able to generate both his version and
// Alice's version of the commitment transaction.
FundingPoint wire.OutPoint
// CommitSig is Alice's signature from Bob's version of the commitment
// transaction.
CommitSig Sig
}
// A compile time check to ensure FundingCreated implements the lnwire.Message
// interface.
var _ Message = (*FundingCreated)(nil)
// Encode serializes the target FundingCreated into the passed io.Writer
// implementation. Serialization will observe the rules defined by the passed
// protocol version.
//
// This is part of the lnwire.Message interface.
func (f *FundingCreated) Encode(w io.Writer, pver uint32) error {
return WriteElements(w, f.PendingChannelID[:], f.FundingPoint, f.CommitSig)
}
// Decode deserializes the serialized FundingCreated stored in the passed
// io.Reader into the target FundingCreated using the deserialization rules
// defined by the passed protocol version.
//
// This is part of the lnwire.Message interface.
func (f *FundingCreated) Decode(r io.Reader, pver uint32) error {
return ReadElements(r, f.PendingChannelID[:], &f.FundingPoint, &f.CommitSig)
}
// MsgType returns the uint32 code which uniquely identifies this message as a
// FundingCreated on the wire.
//
// This is part of the lnwire.Message interface.
func (f *FundingCreated) MsgType() MessageType {
return MsgFundingCreated
}
// MaxPayloadLength returns the maximum allowed payload length for a
// FundingCreated message.
//
// This is part of the lnwire.Message interface.
func (f *FundingCreated) MaxPayloadLength(uint32) uint32 {
// 32 + 32 + 2 + 64
return 130
}
package lnwire
import (
"io"
"github.com/btcsuite/btcd/btcec/v2"
)
// FundingLocked is the message that both parties to a new channel creation
// send once they have observed the funding transaction being confirmed on the
// blockchain. FundingLocked contains the signatures necessary for the channel
// participants to advertise the existence of the channel to the rest of the
// network.
type FundingLocked struct {
// ChanID is the outpoint of the channel's funding transaction. This
// can be used to query for the channel in the database.
ChanID ChannelID
// NextPerCommitmentPoint is the secret that can be used to revoke the
// next commitment transaction for the channel.
NextPerCommitmentPoint *btcec.PublicKey
}
// NewFundingLocked creates a new FundingLocked message, populating it with the
// necessary IDs and revocation secret.
func NewFundingLocked(cid ChannelID, npcp *btcec.PublicKey) *FundingLocked {
return &FundingLocked{
ChanID: cid,
NextPerCommitmentPoint: npcp,
}
}
// A compile time check to ensure FundingLocked implements the lnwire.Message
// interface.
var _ Message = (*FundingLocked)(nil)
// Decode deserializes the serialized FundingLocked message stored in the
// passed io.Reader into the target FundingLocked using the deserialization
// rules defined by the passed protocol version.
//
// This is part of the lnwire.Message interface.
func (c *FundingLocked) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&c.ChanID,
&c.NextPerCommitmentPoint)
}
// Encode serializes the target FundingLocked message into the passed io.Writer
// implementation. Serialization will observe the rules defined by the passed
// protocol version.
//
// This is part of the lnwire.Message interface.
func (c *FundingLocked) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
c.ChanID,
c.NextPerCommitmentPoint)
}
// MsgType returns the uint32 code which uniquely identifies this message as a
// FundingLocked message on the wire.
//
// This is part of the lnwire.Message interface.
func (c *FundingLocked) MsgType() MessageType {
return MsgFundingLocked
}
// MaxPayloadLength returns the maximum allowed payload length for a
// FundingLocked message. This is calculated by summing the max length of all
// the fields within a FundingLocked message.
//
// This is part of the lnwire.Message interface.
func (c *FundingLocked) MaxPayloadLength(uint32) uint32 {
var length uint32
// ChanID - 32 bytes
length += 32
// NextPerCommitmentPoint - 33 bytes
length += 33
// 65 bytes
return length
}
package lnwire
import "io"
// FundingSigned is sent from Bob (the responder) to Alice (the initiator)
// after receiving the funding outpoint and her signature for Bob's version of
// the commitment transaction.
type FundingSigned struct {
// ChannelPoint is the particular active channel that this
// FundingSigned is bound to.
ChanID ChannelID
// CommitSig is Bob's signature for Alice's version of the commitment
// transaction.
CommitSig Sig
}
// A compile time check to ensure FundingSigned implements the lnwire.Message
// interface.
var _ Message = (*FundingSigned)(nil)
// Encode serializes the target FundingSigned into the passed io.Writer
// implementation. Serialization will observe the rules defined by the passed
// protocol version.
//
// This is part of the lnwire.Message interface.
func (f *FundingSigned) Encode(w io.Writer, pver uint32) error {
return WriteElements(w, f.ChanID, f.CommitSig)
}
// Decode deserializes the serialized FundingSigned stored in the passed
// io.Reader into the target FundingSigned using the deserialization rules
// defined by the passed protocol version.
//
// This is part of the lnwire.Message interface.
func (f *FundingSigned) Decode(r io.Reader, pver uint32) error {
return ReadElements(r, &f.ChanID, &f.CommitSig)
}
// MsgType returns the uint32 code which uniquely identifies this message as a
// FundingSigned on the wire.
//
// This is part of the lnwire.Message interface.
func (f *FundingSigned) MsgType() MessageType {
return MsgFundingSigned
}
// MaxPayloadLength returns the maximum allowed payload length for a
// FundingSigned message.
//
// This is part of the lnwire.Message interface.
func (f *FundingSigned) MaxPayloadLength(uint32) uint32 {
// 32 + 64
return 96
}
package lnwire
import (
"io"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// GossipTimestampRange is a message that allows the sender to restrict the set
// of future gossip announcements sent by the receiver. Nodes should send this
// if they have the gossip-queries feature bit active. Nodes are able to send
// new GossipTimestampRange messages to replace the prior window.
type GossipTimestampRange struct {
// ChainHash denotes the chain that the sender wishes to restrict the
// set of received announcements of.
ChainHash chainhash.Hash
// FirstTimestamp is the timestamp of the earliest announcement message
// that should be sent by the receiver.
FirstTimestamp uint32
// TimestampRange is the horizon beyond the FirstTimestamp that any
// announcement messages should be sent for. The receiving node MUST
// NOT send any announcements that have a timestamp greater than
// FirstTimestamp + TimestampRange.
TimestampRange uint32
}
// NewGossipTimestampRange creates a new empty GossipTimestampRange message.
func NewGossipTimestampRange() *GossipTimestampRange {
return &GossipTimestampRange{}
}
// A compile time check to ensure GossipTimestampRange implements the
// lnwire.Message interface.
var _ Message = (*GossipTimestampRange)(nil)
// Decode deserializes a serialized GossipTimestampRange message stored in the
// passed io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (g *GossipTimestampRange) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
g.ChainHash[:],
&g.FirstTimestamp,
&g.TimestampRange,
)
}
// Encode serializes the target GossipTimestampRange into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (g *GossipTimestampRange) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
g.ChainHash[:],
g.FirstTimestamp,
g.TimestampRange,
)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (g *GossipTimestampRange) MsgType() MessageType {
return MsgGossipTimestampRange
}
// MaxPayloadLength returns the maximum allowed payload size for a
// GossipTimestampRange complete message observing the specified protocol
// version.
//
// This is part of the lnwire.Message interface.
func (g *GossipTimestampRange) MaxPayloadLength(uint32) uint32 {
// 32 + 4 + 4
//
// TODO(roasbeef): update to 8 byte timestamps?
return 40
}
package lnwire
import "io"
// Init is the first message reveals the features supported or required by this
// node. Nodes wait for receipt of the other's features to simplify error
// diagnosis where features are incompatible. Each node MUST wait to receive
// init before sending any other messages.
type Init struct {
// GlobalFeatures is a legacy feature vector used for backwards
// compatibility with older nodes. Any features defined here should be
// merged with those presented in Features.
GlobalFeatures *RawFeatureVector
// Features is a feature vector containing the features supported by
// the remote node.
//
// NOTE: Older nodes may place some features in GlobalFeatures, but all
// new features are to be added in Features. When handling an Init
// message, any GlobalFeatures should be merged into the unified
// Features field.
Features *RawFeatureVector
}
// NewInitMessage creates new instance of init message object.
func NewInitMessage(gf *RawFeatureVector, f *RawFeatureVector) *Init {
return &Init{
GlobalFeatures: gf,
Features: f,
}
}
// A compile time check to ensure Init implements the lnwire.Message
// interface.
var _ Message = (*Init)(nil)
// Decode deserializes a serialized Init message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (msg *Init) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&msg.GlobalFeatures,
&msg.Features,
)
}
// Encode serializes the target Init into the passed io.Writer observing
// the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (msg *Init) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
msg.GlobalFeatures,
msg.Features,
)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (msg *Init) MsgType() MessageType {
return MsgInit
}
// MaxPayloadLength returns the maximum allowed payload size for an Init
// complete message observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (msg *Init) MaxPayloadLength(uint32) uint32 {
return 2 + 2 + maxAllowedSize + 2 + maxAllowedSize
}
package lnwire
import (
"bytes"
"encoding/binary"
"fmt"
"image/color"
"io"
"math"
"net"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/tor"
)
// MaxSliceLength is the maximum allowed length for any opaque byte slices in
// the wire protocol.
const MaxSliceLength = 65535
// PkScript is simple type definition which represents a raw serialized public
// key script.
type PkScript []byte
// addressType specifies the network protocol and version that should be used
// when connecting to a node at a particular address.
type addressType uint8
const (
// noAddr denotes a blank address. An address of this type indicates
// that a node doesn't have any advertised addresses.
noAddr addressType = 0
// tcp4Addr denotes an IPv4 TCP address.
tcp4Addr addressType = 1
// tcp6Addr denotes an IPv6 TCP address.
tcp6Addr addressType = 2
// v2OnionAddr denotes a version 2 Tor onion service address.
v2OnionAddr addressType = 3
// v3OnionAddr denotes a version 3 Tor (prop224) onion service address.
v3OnionAddr addressType = 4
)
// AddrLen returns the number of bytes that it takes to encode the target
// address.
func (a addressType) AddrLen() uint16 {
switch a {
case noAddr:
return 0
case tcp4Addr:
return 6
case tcp6Addr:
return 18
case v2OnionAddr:
return 12
case v3OnionAddr:
return 37
default:
return 0
}
}
// WriteElement is a one-stop shop to write the big endian representation of
// any element which is to be serialized for the wire protocol. The passed
// io.Writer should be backed by an appropriately sized byte slice, or be able
// to dynamically expand to accommodate additional data.
//
// TODO(roasbeef): this should eventually draw from a buffer pool for
// serialization.
func WriteElement(w io.Writer, element interface{}) error {
switch e := element.(type) {
case NodeAlias:
if _, err := w.Write(e[:]); err != nil {
return err
}
case ShortChanIDEncoding:
var b [1]byte
b[0] = uint8(e)
if _, err := w.Write(b[:]); err != nil {
return err
}
case uint8:
var b [1]byte
b[0] = e
if _, err := w.Write(b[:]); err != nil {
return err
}
case FundingFlag:
var b [1]byte
b[0] = uint8(e)
if _, err := w.Write(b[:]); err != nil {
return err
}
case uint16:
var b [2]byte
binary.BigEndian.PutUint16(b[:], e)
if _, err := w.Write(b[:]); err != nil {
return err
}
case ChanUpdateMsgFlags:
var b [1]byte
b[0] = uint8(e)
if _, err := w.Write(b[:]); err != nil {
return err
}
case ChanUpdateChanFlags:
var b [1]byte
b[0] = uint8(e)
if _, err := w.Write(b[:]); err != nil {
return err
}
case MilliSatoshi:
var b [8]byte
binary.BigEndian.PutUint64(b[:], uint64(e))
if _, err := w.Write(b[:]); err != nil {
return err
}
case btcutil.Amount:
var b [8]byte
binary.BigEndian.PutUint64(b[:], uint64(e))
if _, err := w.Write(b[:]); err != nil {
return err
}
case uint32:
var b [4]byte
binary.BigEndian.PutUint32(b[:], e)
if _, err := w.Write(b[:]); err != nil {
return err
}
case uint64:
var b [8]byte
binary.BigEndian.PutUint64(b[:], e)
if _, err := w.Write(b[:]); err != nil {
return err
}
case *btcec.PublicKey:
if e == nil {
return fmt.Errorf("cannot write nil pubkey")
}
var b [33]byte
serializedPubkey := e.SerializeCompressed()
copy(b[:], serializedPubkey)
if _, err := w.Write(b[:]); err != nil {
return err
}
case []Sig:
var b [2]byte
numSigs := uint16(len(e))
binary.BigEndian.PutUint16(b[:], numSigs)
if _, err := w.Write(b[:]); err != nil {
return err
}
for _, sig := range e {
if err := WriteElement(w, sig); err != nil {
return err
}
}
case Sig:
// Write buffer
if _, err := w.Write(e[:]); err != nil {
return err
}
case PingPayload:
var l [2]byte
binary.BigEndian.PutUint16(l[:], uint16(len(e)))
if _, err := w.Write(l[:]); err != nil {
return err
}
if _, err := w.Write(e[:]); err != nil {
return err
}
case PongPayload:
var l [2]byte
binary.BigEndian.PutUint16(l[:], uint16(len(e)))
if _, err := w.Write(l[:]); err != nil {
return err
}
if _, err := w.Write(e[:]); err != nil {
return err
}
case ErrorData:
var l [2]byte
binary.BigEndian.PutUint16(l[:], uint16(len(e)))
if _, err := w.Write(l[:]); err != nil {
return err
}
if _, err := w.Write(e[:]); err != nil {
return err
}
case OpaqueReason:
var l [2]byte
binary.BigEndian.PutUint16(l[:], uint16(len(e)))
if _, err := w.Write(l[:]); err != nil {
return err
}
if _, err := w.Write(e[:]); err != nil {
return err
}
case [33]byte:
if _, err := w.Write(e[:]); err != nil {
return err
}
case []byte:
if _, err := w.Write(e[:]); err != nil {
return err
}
case PkScript:
// The largest script we'll accept is a p2wsh which is exactly
// 34 bytes long.
scriptLength := len(e)
if scriptLength > 34 {
return fmt.Errorf("'PkScript' too long")
}
if err := wire.WriteVarBytes(w, 0, e); err != nil {
return err
}
case *RawFeatureVector:
if e == nil {
return fmt.Errorf("cannot write nil feature vector")
}
if err := e.Encode(w); err != nil {
return err
}
case wire.OutPoint:
var h [32]byte
copy(h[:], e.Hash[:])
if _, err := w.Write(h[:]); err != nil {
return err
}
if e.Index > math.MaxUint16 {
return fmt.Errorf("index for outpoint (%v) is "+
"greater than max index of %v", e.Index,
math.MaxUint16)
}
var idx [2]byte
binary.BigEndian.PutUint16(idx[:], uint16(e.Index))
if _, err := w.Write(idx[:]); err != nil {
return err
}
case ChannelID:
if _, err := w.Write(e[:]); err != nil {
return err
}
case FailCode:
if err := WriteElement(w, uint16(e)); err != nil {
return err
}
case ShortChannelID:
// Check that field fit in 3 bytes and write the blockHeight
if e.BlockHeight > ((1 << 24) - 1) {
return errors.New("block height should fit in 3 bytes")
}
var blockHeight [4]byte
binary.BigEndian.PutUint32(blockHeight[:], e.BlockHeight)
if _, err := w.Write(blockHeight[1:]); err != nil {
return err
}
// Check that field fit in 3 bytes and write the txIndex
if e.TxIndex > ((1 << 24) - 1) {
return errors.New("tx index should fit in 3 bytes")
}
var txIndex [4]byte
binary.BigEndian.PutUint32(txIndex[:], e.TxIndex)
if _, err := w.Write(txIndex[1:]); err != nil {
return err
}
// Write the txPosition
var txPosition [2]byte
binary.BigEndian.PutUint16(txPosition[:], e.TxPosition)
if _, err := w.Write(txPosition[:]); err != nil {
return err
}
case *net.TCPAddr:
if e == nil {
return fmt.Errorf("cannot write nil TCPAddr")
}
if e.IP.To4() != nil {
var descriptor [1]byte
descriptor[0] = uint8(tcp4Addr)
if _, err := w.Write(descriptor[:]); err != nil {
return err
}
var ip [4]byte
copy(ip[:], e.IP.To4())
if _, err := w.Write(ip[:]); err != nil {
return err
}
} else {
var descriptor [1]byte
descriptor[0] = uint8(tcp6Addr)
if _, err := w.Write(descriptor[:]); err != nil {
return err
}
var ip [16]byte
copy(ip[:], e.IP.To16())
if _, err := w.Write(ip[:]); err != nil {
return err
}
}
var port [2]byte
binary.BigEndian.PutUint16(port[:], uint16(e.Port))
if _, err := w.Write(port[:]); err != nil {
return err
}
case *tor.OnionAddr:
if e == nil {
return errors.New("cannot write nil onion address")
}
var suffixIndex int
switch len(e.OnionService) {
case tor.V2Len:
descriptor := []byte{byte(v2OnionAddr)}
if _, err := w.Write(descriptor); err != nil {
return err
}
suffixIndex = tor.V2Len - tor.OnionSuffixLen
case tor.V3Len:
descriptor := []byte{byte(v3OnionAddr)}
if _, err := w.Write(descriptor); err != nil {
return err
}
suffixIndex = tor.V3Len - tor.OnionSuffixLen
default:
return errors.New("unknown onion service length")
}
host, err := tor.Base32Encoding.DecodeString(
e.OnionService[:suffixIndex],
)
if err != nil {
return err
}
if _, err := w.Write(host); err != nil {
return err
}
var port [2]byte
binary.BigEndian.PutUint16(port[:], uint16(e.Port))
if _, err := w.Write(port[:]); err != nil {
return err
}
case []net.Addr:
// First, we'll encode all the addresses into an intermediate
// buffer. We need to do this in order to compute the total
// length of the addresses.
var addrBuf bytes.Buffer
for _, address := range e {
if err := WriteElement(&addrBuf, address); err != nil {
return err
}
}
// With the addresses fully encoded, we can now write out the
// number of bytes needed to encode them.
addrLen := addrBuf.Len()
if err := WriteElement(w, uint16(addrLen)); err != nil {
return err
}
// Finally, we'll write out the raw addresses themselves, but
// only if we have any bytes to write.
if addrLen > 0 {
if _, err := w.Write(addrBuf.Bytes()); err != nil {
return err
}
}
case color.RGBA:
if err := WriteElements(w, e.R, e.G, e.B); err != nil {
return err
}
case DeliveryAddress:
var length [2]byte
binary.BigEndian.PutUint16(length[:], uint16(len(e)))
if _, err := w.Write(length[:]); err != nil {
return err
}
if _, err := w.Write(e[:]); err != nil {
return err
}
case bool:
var b [1]byte
if e {
b[0] = 1
}
if _, err := w.Write(b[:]); err != nil {
return err
}
default:
return fmt.Errorf("unknown type in WriteElement: %T", e)
}
return nil
}
// WriteElements is writes each element in the elements slice to the passed
// io.Writer using WriteElement.
func WriteElements(w io.Writer, elements ...interface{}) error {
for _, element := range elements {
err := WriteElement(w, element)
if err != nil {
return err
}
}
return nil
}
// ReadElement is a one-stop utility function to deserialize any datastructure
// encoded using the serialization format of lnwire.
func ReadElement(r io.Reader, element interface{}) error {
var err error
switch e := element.(type) {
case *bool:
var b [1]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
if b[0] == 1 {
*e = true
}
case *NodeAlias:
var a [32]byte
if _, err := io.ReadFull(r, a[:]); err != nil {
return err
}
alias, err := NewNodeAlias(string(a[:]))
if err != nil {
return err
}
*e = alias
case *ShortChanIDEncoding:
var b [1]uint8
if _, err := r.Read(b[:]); err != nil {
return err
}
*e = ShortChanIDEncoding(b[0])
case *uint8:
var b [1]uint8
if _, err := r.Read(b[:]); err != nil {
return err
}
*e = b[0]
case *FundingFlag:
var b [1]uint8
if _, err := r.Read(b[:]); err != nil {
return err
}
*e = FundingFlag(b[0])
case *uint16:
var b [2]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
*e = binary.BigEndian.Uint16(b[:])
case *ChanUpdateMsgFlags:
var b [1]uint8
if _, err := r.Read(b[:]); err != nil {
return err
}
*e = ChanUpdateMsgFlags(b[0])
case *ChanUpdateChanFlags:
var b [1]uint8
if _, err := r.Read(b[:]); err != nil {
return err
}
*e = ChanUpdateChanFlags(b[0])
case *uint32:
var b [4]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
*e = binary.BigEndian.Uint32(b[:])
case *uint64:
var b [8]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
*e = binary.BigEndian.Uint64(b[:])
case *MilliSatoshi:
var b [8]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
*e = MilliSatoshi(int64(binary.BigEndian.Uint64(b[:])))
case *btcutil.Amount:
var b [8]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
*e = btcutil.Amount(int64(binary.BigEndian.Uint64(b[:])))
case **btcec.PublicKey:
var b [btcec.PubKeyBytesLenCompressed]byte
if _, err = io.ReadFull(r, b[:]); err != nil {
return err
}
pubKey, err := btcec.ParsePubKey(b[:])
if err != nil {
return err
}
*e = pubKey
case **RawFeatureVector:
f := NewRawFeatureVector()
err = f.Decode(r)
if err != nil {
return err
}
*e = f
case *[]Sig:
var l [2]byte
if _, err := io.ReadFull(r, l[:]); err != nil {
return err
}
numSigs := binary.BigEndian.Uint16(l[:])
var sigs []Sig
if numSigs > 0 {
sigs = make([]Sig, numSigs)
for i := 0; i < int(numSigs); i++ {
if err := ReadElement(r, &sigs[i]); err != nil {
return err
}
}
}
*e = sigs
case *Sig:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *OpaqueReason:
var l [2]byte
if _, err := io.ReadFull(r, l[:]); err != nil {
return err
}
reasonLen := binary.BigEndian.Uint16(l[:])
*e = OpaqueReason(make([]byte, reasonLen))
if _, err := io.ReadFull(r, *e); err != nil {
return err
}
case *ErrorData:
var l [2]byte
if _, err := io.ReadFull(r, l[:]); err != nil {
return err
}
errorLen := binary.BigEndian.Uint16(l[:])
*e = ErrorData(make([]byte, errorLen))
if _, err := io.ReadFull(r, *e); err != nil {
return err
}
case *PingPayload:
var l [2]byte
if _, err := io.ReadFull(r, l[:]); err != nil {
return err
}
pingLen := binary.BigEndian.Uint16(l[:])
*e = PingPayload(make([]byte, pingLen))
if _, err := io.ReadFull(r, *e); err != nil {
return err
}
case *PongPayload:
var l [2]byte
if _, err := io.ReadFull(r, l[:]); err != nil {
return err
}
pongLen := binary.BigEndian.Uint16(l[:])
*e = PongPayload(make([]byte, pongLen))
if _, err := io.ReadFull(r, *e); err != nil {
return err
}
case *[33]byte:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case []byte:
if _, err := io.ReadFull(r, e); err != nil {
return err
}
case *PkScript:
pkScript, err := wire.ReadVarBytes(r, 0, 34, "pkscript")
if err != nil {
return err
}
*e = pkScript
case *wire.OutPoint:
var h [32]byte
if _, err = io.ReadFull(r, h[:]); err != nil {
return err
}
hash, err := chainhash.NewHash(h[:])
if err != nil {
return err
}
var idxBytes [2]byte
_, err = io.ReadFull(r, idxBytes[:])
if err != nil {
return err
}
index := binary.BigEndian.Uint16(idxBytes[:])
*e = wire.OutPoint{
Hash: *hash,
Index: uint32(index),
}
case *FailCode:
if err := ReadElement(r, (*uint16)(e)); err != nil {
return err
}
case *ChannelID:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *ShortChannelID:
var blockHeight [4]byte
if _, err = io.ReadFull(r, blockHeight[1:]); err != nil {
return err
}
var txIndex [4]byte
if _, err = io.ReadFull(r, txIndex[1:]); err != nil {
return err
}
var txPosition [2]byte
if _, err = io.ReadFull(r, txPosition[:]); err != nil {
return err
}
*e = ShortChannelID{
BlockHeight: binary.BigEndian.Uint32(blockHeight[:]),
TxIndex: binary.BigEndian.Uint32(txIndex[:]),
TxPosition: binary.BigEndian.Uint16(txPosition[:]),
}
case *[]net.Addr:
// First, we'll read the number of total bytes that have been
// used to encode the set of addresses.
var numAddrsBytes [2]byte
if _, err = io.ReadFull(r, numAddrsBytes[:]); err != nil {
return err
}
addrsLen := binary.BigEndian.Uint16(numAddrsBytes[:])
// With the number of addresses, read, we'll now pull in the
// buffer of the encoded addresses into memory.
addrs := make([]byte, addrsLen)
if _, err := io.ReadFull(r, addrs[:]); err != nil {
return err
}
addrBuf := bytes.NewReader(addrs)
// Finally, we'll parse the remaining address payload in
// series, using the first byte to denote how to decode the
// address itself.
var (
addresses []net.Addr
addrBytesRead uint16
)
for addrBytesRead < addrsLen {
var descriptor [1]byte
if _, err = io.ReadFull(addrBuf, descriptor[:]); err != nil {
return err
}
addrBytesRead++
var address net.Addr
switch aType := addressType(descriptor[0]); aType {
case noAddr:
addrBytesRead += aType.AddrLen()
continue
case tcp4Addr:
var ip [4]byte
if _, err := io.ReadFull(addrBuf, ip[:]); err != nil {
return err
}
var port [2]byte
if _, err := io.ReadFull(addrBuf, port[:]); err != nil {
return err
}
address = &net.TCPAddr{
IP: net.IP(ip[:]),
Port: int(binary.BigEndian.Uint16(port[:])),
}
addrBytesRead += aType.AddrLen()
case tcp6Addr:
var ip [16]byte
if _, err := io.ReadFull(addrBuf, ip[:]); err != nil {
return err
}
var port [2]byte
if _, err := io.ReadFull(addrBuf, port[:]); err != nil {
return err
}
address = &net.TCPAddr{
IP: net.IP(ip[:]),
Port: int(binary.BigEndian.Uint16(port[:])),
}
addrBytesRead += aType.AddrLen()
case v2OnionAddr:
var h [tor.V2DecodedLen]byte
if _, err := io.ReadFull(addrBuf, h[:]); err != nil {
return err
}
var p [2]byte
if _, err := io.ReadFull(addrBuf, p[:]); err != nil {
return err
}
onionService := tor.Base32Encoding.EncodeToString(h[:])
onionService += tor.OnionSuffix
port := int(binary.BigEndian.Uint16(p[:]))
address = &tor.OnionAddr{
OnionService: onionService,
Port: port,
}
addrBytesRead += aType.AddrLen()
case v3OnionAddr:
var h [tor.V3DecodedLen]byte
if _, err := io.ReadFull(addrBuf, h[:]); err != nil {
return err
}
var p [2]byte
if _, err := io.ReadFull(addrBuf, p[:]); err != nil {
return err
}
onionService := tor.Base32Encoding.EncodeToString(h[:])
onionService += tor.OnionSuffix
port := int(binary.BigEndian.Uint16(p[:]))
address = &tor.OnionAddr{
OnionService: onionService,
Port: port,
}
addrBytesRead += aType.AddrLen()
default:
return &ErrUnknownAddrType{aType}
}
addresses = append(addresses, address)
}
*e = addresses
case *color.RGBA:
err := ReadElements(r,
&e.R,
&e.G,
&e.B,
)
if err != nil {
return err
}
case *DeliveryAddress:
var addrLen [2]byte
if _, err = io.ReadFull(r, addrLen[:]); err != nil {
return err
}
length := binary.BigEndian.Uint16(addrLen[:])
var addrBytes [deliveryAddressMaxSize]byte
if length > deliveryAddressMaxSize {
return fmt.Errorf("cannot read %d bytes into addrBytes", length)
}
if _, err = io.ReadFull(r, addrBytes[:length]); err != nil {
return err
}
*e = addrBytes[:length]
default:
return fmt.Errorf("unknown type in ReadElement: %T", e)
}
return nil
}
// ReadElements deserializes a variable number of elements into the passed
// io.Reader, with each element being deserialized according to the ReadElement
// function.
func ReadElements(r io.Reader, elements ...interface{}) error {
for _, element := range elements {
err := ReadElement(r, element)
if err != nil {
return err
}
}
return nil
}
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// code derived from https://github .com/btcsuite/btcd/blob/master/wire/message.go
// Copyright (C) 2015-2022 The Lightning Network Developers
package lnwire
import (
"bytes"
"encoding/binary"
"fmt"
"io"
)
// MaxMessagePayload is the maximum bytes a message can be regardless of other
// individual limits imposed by messages themselves.
const MaxMessagePayload = 65535 // 65KB
// MessageType is the unique 2 byte big-endian integer that indicates the type
// of message on the wire. All messages have a very simple header which
// consists simply of 2-byte message type. We omit a length field, and checksum
// as the Lightning Protocol is intended to be encapsulated within a
// confidential+authenticated cryptographic messaging protocol.
type MessageType uint16
// The currently defined message types within this current version of the
// Lightning protocol.
const (
MsgInit MessageType = 16
MsgError = 17
MsgPing = 18
MsgPong = 19
MsgOpenChannel = 32
MsgAcceptChannel = 33
MsgFundingCreated = 34
MsgFundingSigned = 35
MsgFundingLocked = 36
MsgShutdown = 38
MsgClosingSigned = 39
MsgUpdateAddHTLC = 128
MsgUpdateFulfillHTLC = 130
MsgUpdateFailHTLC = 131
MsgCommitSig = 132
MsgRevokeAndAck = 133
MsgUpdateFee = 134
MsgUpdateFailMalformedHTLC = 135
MsgChannelReestablish = 136
MsgChannelAnnouncement = 256
MsgNodeAnnouncement = 257
MsgChannelUpdate = 258
MsgAnnounceSignatures = 259
MsgQueryShortChanIDs = 261
MsgReplyShortChanIDsEnd = 262
MsgQueryChannelRange = 263
MsgReplyChannelRange = 264
MsgGossipTimestampRange = 265
)
// String return the string representation of message type.
func (t MessageType) String() string {
switch t {
case MsgInit:
return "Init"
case MsgOpenChannel:
return "MsgOpenChannel"
case MsgAcceptChannel:
return "MsgAcceptChannel"
case MsgFundingCreated:
return "MsgFundingCreated"
case MsgFundingSigned:
return "MsgFundingSigned"
case MsgFundingLocked:
return "FundingLocked"
case MsgShutdown:
return "Shutdown"
case MsgClosingSigned:
return "ClosingSigned"
case MsgUpdateAddHTLC:
return "UpdateAddHTLC"
case MsgUpdateFailHTLC:
return "UpdateFailHTLC"
case MsgUpdateFulfillHTLC:
return "UpdateFulfillHTLC"
case MsgCommitSig:
return "CommitSig"
case MsgRevokeAndAck:
return "RevokeAndAck"
case MsgUpdateFailMalformedHTLC:
return "UpdateFailMalformedHTLC"
case MsgChannelReestablish:
return "ChannelReestablish"
case MsgError:
return "Error"
case MsgChannelAnnouncement:
return "ChannelAnnouncement"
case MsgChannelUpdate:
return "ChannelUpdate"
case MsgNodeAnnouncement:
return "NodeAnnouncement"
case MsgPing:
return "Ping"
case MsgAnnounceSignatures:
return "AnnounceSignatures"
case MsgPong:
return "Pong"
case MsgUpdateFee:
return "UpdateFee"
case MsgQueryShortChanIDs:
return "QueryShortChanIDs"
case MsgReplyShortChanIDsEnd:
return "ReplyShortChanIDsEnd"
case MsgQueryChannelRange:
return "QueryChannelRange"
case MsgReplyChannelRange:
return "ReplyChannelRange"
case MsgGossipTimestampRange:
return "GossipTimestampRange"
default:
return "<unknown>"
}
}
// UnknownMessage is an implementation of the error interface that allows the
// creation of an error in response to an unknown message.
type UnknownMessage struct {
messageType MessageType
}
// Error returns a human readable string describing the error.
//
// This is part of the error interface.
func (u *UnknownMessage) Error() string {
return fmt.Sprintf("unable to parse message of unknown type: %v",
u.messageType)
}
// Serializable is an interface which defines a lightning wire serializable
// object.
type Serializable interface {
// Decode reads the bytes stream and converts it to the object.
Decode(io.Reader, uint32) error
// Encode converts object to the bytes stream and write it into the
// writer.
Encode(io.Writer, uint32) error
}
// Message is an interface that defines a lightning wire protocol message. The
// interface is general in order to allow implementing types full control over
// the representation of its data.
type Message interface {
Serializable
MsgType() MessageType
MaxPayloadLength(uint32) uint32
}
// makeEmptyMessage creates a new empty message of the proper concrete type
// based on the passed message type.
func makeEmptyMessage(msgType MessageType) (Message, error) {
var msg Message
switch msgType {
case MsgInit:
msg = &Init{}
case MsgOpenChannel:
msg = &OpenChannel{}
case MsgAcceptChannel:
msg = &AcceptChannel{}
case MsgFundingCreated:
msg = &FundingCreated{}
case MsgFundingSigned:
msg = &FundingSigned{}
case MsgFundingLocked:
msg = &FundingLocked{}
case MsgShutdown:
msg = &Shutdown{}
case MsgClosingSigned:
msg = &ClosingSigned{}
case MsgUpdateAddHTLC:
msg = &UpdateAddHTLC{}
case MsgUpdateFailHTLC:
msg = &UpdateFailHTLC{}
case MsgUpdateFulfillHTLC:
msg = &UpdateFulfillHTLC{}
case MsgCommitSig:
msg = &CommitSig{}
case MsgRevokeAndAck:
msg = &RevokeAndAck{}
case MsgUpdateFee:
msg = &UpdateFee{}
case MsgUpdateFailMalformedHTLC:
msg = &UpdateFailMalformedHTLC{}
case MsgChannelReestablish:
msg = &ChannelReestablish{}
case MsgError:
msg = &Error{}
case MsgChannelAnnouncement:
msg = &ChannelAnnouncement{}
case MsgChannelUpdate:
msg = &ChannelUpdate{}
case MsgNodeAnnouncement:
msg = &NodeAnnouncement{}
case MsgPing:
msg = &Ping{}
case MsgAnnounceSignatures:
msg = &AnnounceSignatures{}
case MsgPong:
msg = &Pong{}
case MsgQueryShortChanIDs:
msg = &QueryShortChanIDs{}
case MsgReplyShortChanIDsEnd:
msg = &ReplyShortChanIDsEnd{}
case MsgQueryChannelRange:
msg = &QueryChannelRange{}
case MsgReplyChannelRange:
msg = &ReplyChannelRange{}
case MsgGossipTimestampRange:
msg = &GossipTimestampRange{}
default:
return nil, &UnknownMessage{msgType}
}
return msg, nil
}
// WriteMessage writes a lightning Message to w including the necessary header
// information and returns the number of bytes written.
func WriteMessage(w io.Writer, msg Message, pver uint32) (int, error) {
totalBytes := 0
// Encode the message payload itself into a temporary buffer.
// TODO(roasbeef): create buffer pool
var bw bytes.Buffer
if err := msg.Encode(&bw, pver); err != nil {
return totalBytes, err
}
payload := bw.Bytes()
lenp := len(payload)
// Enforce maximum overall message payload.
if lenp > MaxMessagePayload {
return totalBytes, fmt.Errorf("message payload is too large - "+
"encoded %d bytes, but maximum message payload is %d bytes",
lenp, MaxMessagePayload)
}
// Enforce maximum message payload on the message type.
mpl := msg.MaxPayloadLength(pver)
if uint32(lenp) > mpl {
return totalBytes, fmt.Errorf("message payload is too large - "+
"encoded %d bytes, but maximum message payload of "+
"type %v is %d bytes", lenp, msg.MsgType(), mpl)
}
// With the initial sanity checks complete, we'll now write out the
// message type itself.
var mType [2]byte
binary.BigEndian.PutUint16(mType[:], uint16(msg.MsgType()))
n, err := w.Write(mType[:])
totalBytes += n
if err != nil {
return totalBytes, err
}
// With the message type written, we'll now write out the raw payload
// itself.
n, err = w.Write(payload)
totalBytes += n
return totalBytes, err
}
// ReadMessage reads, validates, and parses the next Lightning message from r
// for the provided protocol version.
func ReadMessage(r io.Reader, pver uint32) (Message, error) {
// First, we'll read out the first two bytes of the message so we can
// create the proper empty message.
var mType [2]byte
if _, err := io.ReadFull(r, mType[:]); err != nil {
return nil, err
}
msgType := MessageType(binary.BigEndian.Uint16(mType[:]))
// Now that we know the target message type, we can create the proper
// empty message type and decode the message into it.
msg, err := makeEmptyMessage(msgType)
if err != nil {
return nil, err
}
if err := msg.Decode(r, pver); err != nil {
return nil, err
}
return msg, nil
}
package lnwire
import (
"fmt"
"io"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// mSatScale is a value that's used to scale satoshis to milli-satoshis, and
// the other way around.
mSatScale uint64 = 1000
// MaxMilliSatoshi is the maximum number of msats that can be expressed
// in this data type.
MaxMilliSatoshi = ^MilliSatoshi(0)
)
// MilliSatoshi are the native unit of the Lightning Network. A milli-satoshi
// is simply 1/1000th of a satoshi. There are 1000 milli-satoshis in a single
// satoshi. Within the network, all HTLC payments are denominated in
// milli-satoshis. As milli-satoshis aren't deliverable on the native
// blockchain, before settling to broadcasting, the values are rounded down to
// the nearest satoshi.
type MilliSatoshi uint64
// NewMSatFromSatoshis creates a new MilliSatoshi instance from a target amount
// of satoshis.
func NewMSatFromSatoshis(sat btcutil.Amount) MilliSatoshi {
return MilliSatoshi(uint64(sat) * mSatScale)
}
// ToBTC converts the target MilliSatoshi amount to its corresponding value
// when expressed in BTC.
func (m MilliSatoshi) ToBTC() float64 {
sat := m.ToSatoshis()
return sat.ToBTC()
}
// ToSatoshis converts the target MilliSatoshi amount to satoshis. Simply, this
// sheds a factor of 1000 from the mSAT amount in order to convert it to SAT.
func (m MilliSatoshi) ToSatoshis() btcutil.Amount {
return btcutil.Amount(uint64(m) / mSatScale)
}
// String returns the string representation of the mSAT amount.
func (m MilliSatoshi) String() string {
return fmt.Sprintf("%v mSAT", uint64(m))
}
// TODO(roasbeef): extend with arithmetic operations?
// Record returns a TLV record that can be used to encode/decode a MilliSatoshi
// to/from a TLV stream.
func (m *MilliSatoshi) Record() tlv.Record {
return tlv.MakeDynamicRecord(
0, m, tlv.SizeBigSize(m), encodeMilliSatoshis,
decodeMilliSatoshis,
)
}
func encodeMilliSatoshis(w io.Writer, val interface{}, buf *[8]byte) error {
if v, ok := val.(*MilliSatoshi); ok {
bigSize := uint64(*v)
return tlv.EBigSize(w, &bigSize, buf)
}
return tlv.NewTypeForEncodingErr(val, "lnwire.MilliSatoshi")
}
func decodeMilliSatoshis(r io.Reader, val interface{}, buf *[8]byte,
l uint64) error {
if v, ok := val.(*MilliSatoshi); ok {
var bigSize uint64
err := tlv.DBigSize(r, &bigSize, buf, l)
if err != nil {
return err
}
*v = MilliSatoshi(bigSize)
return nil
}
return tlv.NewTypeForDecodingErr(val, "lnwire.MilliSatoshi", l, l)
}
package lnwire
import (
"fmt"
"net"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/wire"
)
// NetAddress represents information pertaining to the identity and network
// reachability of a peer. Information stored includes the node's identity
// public key for establishing a confidential+authenticated connection, the
// service bits it supports, and a TCP address the node is reachable at.
//
// TODO(roasbeef): merge with LinkNode in some fashion
type NetAddress struct {
// IdentityKey is the long-term static public key for a node. This node is
// used throughout the network as a node's identity key. It is used to
// authenticate any data sent to the network on behalf of the node, and
// additionally to establish a confidential+authenticated connection with
// the node.
IdentityKey *btcec.PublicKey
// Address is the IP address and port of the node. This is left
// general so that multiple implementations can be used.
Address net.Addr
// ChainNet is the Bitcoin network this node is associated with.
// TODO(roasbeef): make a slice in the future for multi-chain
ChainNet wire.BitcoinNet
}
// A compile time assertion to ensure that NetAddress meets the net.Addr
// interface.
var _ net.Addr = (*NetAddress)(nil)
// String returns a human readable string describing the target NetAddress. The
// current string format is: <pubkey>@host.
//
// This part of the net.Addr interface.
func (n *NetAddress) String() string {
// TODO(roasbeef): use base58?
pubkey := n.IdentityKey.SerializeCompressed()
return fmt.Sprintf("%x@%v", pubkey, n.Address)
}
// Network returns the name of the network this address is bound to.
//
// This part of the net.Addr interface.
func (n *NetAddress) Network() string {
return n.Address.Network()
}
package lnwire
import (
"bytes"
"fmt"
"image/color"
"io"
"net"
"unicode/utf8"
)
// ErrUnknownAddrType is an error returned if we encounter an unknown address type
// when parsing addresses.
type ErrUnknownAddrType struct {
addrType addressType
}
// Error returns a human readable string describing the error.
//
// NOTE: implements the error interface.
func (e ErrUnknownAddrType) Error() string {
return fmt.Sprintf("unknown address type: %v", e.addrType)
}
// ErrInvalidNodeAlias is an error returned if a node alias we parse on the
// wire is invalid, as in it has non UTF-8 characters.
type ErrInvalidNodeAlias struct{}
// Error returns a human readable string describing the error.
//
// NOTE: implements the error interface.
func (e ErrInvalidNodeAlias) Error() string {
return "node alias has non-utf8 characters"
}
// NodeAlias is a hex encoded UTF-8 string that may be displayed as an
// alternative to the node's ID. Notice that aliases are not unique and may be
// freely chosen by the node operators.
type NodeAlias [32]byte
// NewNodeAlias creates a new instance of a NodeAlias. Verification is
// performed on the passed string to ensure it meets the alias requirements.
func NewNodeAlias(s string) (NodeAlias, error) {
var n NodeAlias
if len(s) > 32 {
return n, fmt.Errorf("alias too large: max is %v, got %v", 32,
len(s))
}
if !utf8.ValidString(s) {
return n, &ErrInvalidNodeAlias{}
}
copy(n[:], []byte(s))
return n, nil
}
// String returns a utf8 string representation of the alias bytes.
func (n NodeAlias) String() string {
// Trim trailing zero-bytes for presentation
return string(bytes.Trim(n[:], "\x00"))
}
// NodeAnnouncement message is used to announce the presence of a Lightning
// node and also to signal that the node is accepting incoming connections.
// Each NodeAnnouncement authenticating the advertised information within the
// announcement via a signature using the advertised node pubkey.
type NodeAnnouncement struct {
// Signature is used to prove the ownership of node id.
Signature Sig
// Features is the list of protocol features this node supports.
Features *RawFeatureVector
// Timestamp allows ordering in the case of multiple announcements.
Timestamp uint32
// NodeID is a public key which is used as node identification.
NodeID [33]byte
// RGBColor is used to customize their node's appearance in maps and
// graphs
RGBColor color.RGBA
// Alias is used to customize their node's appearance in maps and
// graphs
Alias NodeAlias
// Address includes two specification fields: 'ipv6' and 'port' on
// which the node is accepting incoming connections.
Addresses []net.Addr
// ExtraOpaqueData is the set of data that was appended to this
// message, some of which we may not actually know how to iterate or
// parse. By holding onto this data, we ensure that we're able to
// properly validate the set of signatures that cover these new fields,
// and ensure we're able to make upgrades to the network in a forwards
// compatible manner.
ExtraOpaqueData []byte
}
// A compile time check to ensure NodeAnnouncement implements the
// lnwire.Message interface.
var _ Message = (*NodeAnnouncement)(nil)
// Decode deserializes a serialized NodeAnnouncement stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (a *NodeAnnouncement) Decode(r io.Reader, pver uint32) error {
err := ReadElements(r,
&a.Signature,
&a.Features,
&a.Timestamp,
&a.NodeID,
&a.RGBColor,
&a.Alias,
&a.Addresses,
)
if err != nil {
return err
}
// Now that we've read out all the fields that we explicitly know of,
// we'll collect the remainder into the ExtraOpaqueData field. If there
// aren't any bytes, then we'll snip off the slice to avoid carrying
// around excess capacity.
a.ExtraOpaqueData, err = io.ReadAll(r)
if err != nil {
return err
}
if len(a.ExtraOpaqueData) == 0 {
a.ExtraOpaqueData = nil
}
return nil
}
// Encode serializes the target NodeAnnouncement into the passed io.Writer
// observing the protocol version specified.
func (a *NodeAnnouncement) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
a.Signature,
a.Features,
a.Timestamp,
a.NodeID,
a.RGBColor,
a.Alias,
a.Addresses,
a.ExtraOpaqueData,
)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (a *NodeAnnouncement) MsgType() MessageType {
return MsgNodeAnnouncement
}
// MaxPayloadLength returns the maximum allowed payload size for this message
// observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (a *NodeAnnouncement) MaxPayloadLength(pver uint32) uint32 {
return 65533
}
// DataToSign returns the part of the message that should be signed.
func (a *NodeAnnouncement) DataToSign() ([]byte, error) {
// We should not include the signatures itself.
var w bytes.Buffer
err := WriteElements(&w,
a.Features,
a.Timestamp,
a.NodeID,
a.RGBColor,
a.Alias[:],
a.Addresses,
a.ExtraOpaqueData,
)
if err != nil {
return nil, err
}
return w.Bytes(), nil
}
package lnwire
import (
"bufio"
"bytes"
"crypto/sha256"
"encoding/binary"
"fmt"
"io"
"github.com/davecgh/go-spew/spew"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/tlv"
)
// FailureMessage represents the onion failure object identified by its unique
// failure code.
type FailureMessage interface {
// Code returns a failure code describing the exact nature of the
// error.
Code() FailCode
// Error returns a human readable string describing the error. With
// this method, the FailureMessage interface meets the built-in error
// interface.
Error() string
}
// FailureMessageLength is the size of the failure message plus the size of
// padding. The FailureMessage message should always be EXACTLY this size.
const FailureMessageLength = 256
const (
// FlagBadOnion error flag describes an unparsable, encrypted by
// previous node.
FlagBadOnion FailCode = 0x8000
// FlagPerm error flag indicates a permanent failure.
FlagPerm FailCode = 0x4000
// FlagNode error flag indicates a node failure.
FlagNode FailCode = 0x2000
// FlagUpdate error flag indicates a new channel update is enclosed
// within the error.
FlagUpdate FailCode = 0x1000
)
// FailCode specifies the precise reason that an upstream HTLC was canceled.
// Each UpdateFailHTLC message carries a FailCode which is to be passed
// backwards, encrypted at each step back to the source of the HTLC within the
// route.
type FailCode uint16
// The currently defined onion failure types within this current version of the
// Lightning protocol.
//
//nolint:ll
const (
CodeNone FailCode = 0
CodeInvalidRealm = FlagBadOnion | 1
CodeTemporaryNodeFailure = FlagNode | 2
CodePermanentNodeFailure = FlagPerm | FlagNode | 2
CodeRequiredNodeFeatureMissing = FlagPerm | FlagNode | 3
CodeInvalidOnionVersion = FlagBadOnion | FlagPerm | 4
CodeInvalidOnionHmac = FlagBadOnion | FlagPerm | 5
CodeInvalidOnionKey = FlagBadOnion | FlagPerm | 6
CodeTemporaryChannelFailure = FlagUpdate | 7
CodePermanentChannelFailure = FlagPerm | 8
CodeRequiredChannelFeatureMissing = FlagPerm | 9
CodeUnknownNextPeer = FlagPerm | 10
CodeAmountBelowMinimum = FlagUpdate | 11
CodeFeeInsufficient = FlagUpdate | 12
CodeIncorrectCltvExpiry = FlagUpdate | 13
CodeExpiryTooSoon = FlagUpdate | 14
CodeChannelDisabled = FlagUpdate | 20
CodeIncorrectOrUnknownPaymentDetails = FlagPerm | 15
CodeIncorrectPaymentAmount = FlagPerm | 16
CodeFinalExpiryTooSoon FailCode = 17
CodeFinalIncorrectCltvExpiry FailCode = 18
CodeFinalIncorrectHtlcAmount FailCode = 19
CodeExpiryTooFar FailCode = 21
CodeInvalidOnionPayload = FlagPerm | 22
CodeMPPTimeout FailCode = 23
CodeInvalidBlinding = FlagBadOnion | FlagPerm | 24
)
// String returns the string representation of the failure code.
func (c FailCode) String() string {
switch c {
case CodeInvalidRealm:
return "InvalidRealm"
case CodeTemporaryNodeFailure:
return "TemporaryNodeFailure"
case CodePermanentNodeFailure:
return "PermanentNodeFailure"
case CodeRequiredNodeFeatureMissing:
return "RequiredNodeFeatureMissing"
case CodeInvalidOnionVersion:
return "InvalidOnionVersion"
case CodeInvalidOnionHmac:
return "InvalidOnionHmac"
case CodeInvalidOnionKey:
return "InvalidOnionKey"
case CodeTemporaryChannelFailure:
return "TemporaryChannelFailure"
case CodePermanentChannelFailure:
return "PermanentChannelFailure"
case CodeRequiredChannelFeatureMissing:
return "RequiredChannelFeatureMissing"
case CodeUnknownNextPeer:
return "UnknownNextPeer"
case CodeAmountBelowMinimum:
return "AmountBelowMinimum"
case CodeFeeInsufficient:
return "FeeInsufficient"
case CodeIncorrectCltvExpiry:
return "IncorrectCltvExpiry"
case CodeIncorrectPaymentAmount:
return "IncorrectPaymentAmount"
case CodeExpiryTooSoon:
return "ExpiryTooSoon"
case CodeChannelDisabled:
return "ChannelDisabled"
case CodeIncorrectOrUnknownPaymentDetails:
return "IncorrectOrUnknownPaymentDetails"
case CodeFinalExpiryTooSoon:
return "FinalExpiryTooSoon"
case CodeFinalIncorrectCltvExpiry:
return "FinalIncorrectCltvExpiry"
case CodeFinalIncorrectHtlcAmount:
return "FinalIncorrectHtlcAmount"
case CodeExpiryTooFar:
return "ExpiryTooFar"
case CodeInvalidOnionPayload:
return "InvalidOnionPayload"
case CodeMPPTimeout:
return "MPPTimeout"
case CodeInvalidBlinding:
return "InvalidBlinding"
default:
return "<unknown>"
}
}
// FailInvalidRealm is returned if the realm byte is unknown.
//
// NOTE: May be returned by any node in the payment route.
type FailInvalidRealm struct{}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailInvalidRealm) Error() string {
return f.Code().String()
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailInvalidRealm) Code() FailCode {
return CodeInvalidRealm
}
// FailTemporaryNodeFailure is returned if an otherwise unspecified transient
// error occurs for the entire node.
//
// NOTE: May be returned by any node in the payment route.
type FailTemporaryNodeFailure struct{}
// Code returns the failure unique code.
// NOTE: Part of the FailureMessage interface.
func (f *FailTemporaryNodeFailure) Code() FailCode {
return CodeTemporaryNodeFailure
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailTemporaryNodeFailure) Error() string {
return f.Code().String()
}
// FailPermanentNodeFailure is returned if an otherwise unspecified permanent
// error occurs for the entire node.
//
// NOTE: May be returned by any node in the payment route.
type FailPermanentNodeFailure struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailPermanentNodeFailure) Code() FailCode {
return CodePermanentNodeFailure
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailPermanentNodeFailure) Error() string {
return f.Code().String()
}
// FailRequiredNodeFeatureMissing is returned if a node has requirement
// advertised in its node_announcement features which were not present in the
// onion.
//
// NOTE: May be returned by any node in the payment route.
type FailRequiredNodeFeatureMissing struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailRequiredNodeFeatureMissing) Code() FailCode {
return CodeRequiredNodeFeatureMissing
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailRequiredNodeFeatureMissing) Error() string {
return f.Code().String()
}
// FailPermanentChannelFailure is return if an otherwise unspecified permanent
// error occurs for the outgoing channel (eg. channel (recently).
//
// NOTE: May be returned by any node in the payment route.
type FailPermanentChannelFailure struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailPermanentChannelFailure) Code() FailCode {
return CodePermanentChannelFailure
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailPermanentChannelFailure) Error() string {
return f.Code().String()
}
// FailRequiredChannelFeatureMissing is returned if the outgoing channel has a
// requirement advertised in its channel announcement features which were not
// present in the onion.
//
// NOTE: May only be returned by intermediate nodes.
type FailRequiredChannelFeatureMissing struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailRequiredChannelFeatureMissing) Code() FailCode {
return CodeRequiredChannelFeatureMissing
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailRequiredChannelFeatureMissing) Error() string {
return f.Code().String()
}
// FailUnknownNextPeer is returned if the next peer specified by the onion is
// not known.
//
// NOTE: May only be returned by intermediate nodes.
type FailUnknownNextPeer struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailUnknownNextPeer) Code() FailCode {
return CodeUnknownNextPeer
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailUnknownNextPeer) Error() string {
return f.Code().String()
}
// FailIncorrectPaymentAmount is returned if the amount paid is less than the
// amount expected, the final node MUST fail the HTLC. If the amount paid is
// more than twice the amount expected, the final node SHOULD fail the HTLC.
// This allows the sender to reduce information leakage by altering the amount,
// without allowing accidental gross overpayment.
//
// NOTE: May only be returned by the final node in the path.
type FailIncorrectPaymentAmount struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailIncorrectPaymentAmount) Code() FailCode {
return CodeIncorrectPaymentAmount
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailIncorrectPaymentAmount) Error() string {
return f.Code().String()
}
// FailIncorrectDetails is returned for two reasons:
//
// 1) if the payment hash has already been paid, the final node MAY treat the
// payment hash as unknown, or may succeed in accepting the HTLC. If the
// payment hash is unknown, the final node MUST fail the HTLC.
//
// 2) if the amount paid is less than the amount expected, the final node MUST
// fail the HTLC. If the amount paid is more than twice the amount expected,
// the final node SHOULD fail the HTLC. This allows the sender to reduce
// information leakage by altering the amount, without allowing accidental
// gross overpayment.
//
// NOTE: May only be returned by the final node in the path.
type FailIncorrectDetails struct {
// amount is the value of the extended HTLC.
amount MilliSatoshi
// height is the block height when the htlc was received.
height uint32
}
// NewFailIncorrectDetails makes a new instance of the FailIncorrectDetails
// error bound to the specified HTLC amount and acceptance height.
func NewFailIncorrectDetails(amt MilliSatoshi,
height uint32) *FailIncorrectDetails {
return &FailIncorrectDetails{
amount: amt,
height: height,
}
}
// Amount is the value of the extended HTLC.
func (f *FailIncorrectDetails) Amount() MilliSatoshi {
return f.amount
}
// Height is the block height when the htlc was received.
func (f *FailIncorrectDetails) Height() uint32 {
return f.height
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailIncorrectDetails) Code() FailCode {
return CodeIncorrectOrUnknownPaymentDetails
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailIncorrectDetails) Error() string {
return fmt.Sprintf(
"%v(amt=%v, height=%v)", CodeIncorrectOrUnknownPaymentDetails,
f.amount, f.height,
)
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailIncorrectDetails) Decode(r io.Reader, pver uint32) error {
err := ReadElement(r, &f.amount)
switch {
// This is an optional tack on that was added later in the protocol. As
// a result, older nodes may not include this value. We'll account for
// this by checking for io.EOF here which means that no bytes were read
// at all.
case err == io.EOF:
return nil
case err != nil:
return err
}
// At a later stage, the height field was also tacked on. We need to
// check for io.EOF here as well.
err = ReadElement(r, &f.height)
switch {
case err == io.EOF:
return nil
case err != nil:
return err
}
return nil
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailIncorrectDetails) Encode(w io.Writer, pver uint32) error {
return WriteElements(w, f.amount, f.height)
}
// FailFinalExpiryTooSoon is returned if the cltv_expiry is too low, the final
// node MUST fail the HTLC.
//
// NOTE: May only be returned by the final node in the path.
type FailFinalExpiryTooSoon struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailFinalExpiryTooSoon) Code() FailCode {
return CodeFinalExpiryTooSoon
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailFinalExpiryTooSoon) Error() string {
return f.Code().String()
}
// NewFinalExpiryTooSoon creates new instance of the FailFinalExpiryTooSoon.
func NewFinalExpiryTooSoon() *FailFinalExpiryTooSoon {
return &FailFinalExpiryTooSoon{}
}
// FailInvalidOnionVersion is returned if the onion version byte is unknown.
//
// NOTE: May be returned only by intermediate nodes.
type FailInvalidOnionVersion struct {
// OnionSHA256 hash of the onion blob which haven't been proceeded.
OnionSHA256 [sha256.Size]byte
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailInvalidOnionVersion) Error() string {
return fmt.Sprintf("InvalidOnionVersion(onion_sha=%x)", f.OnionSHA256[:])
}
// NewInvalidOnionVersion creates new instance of the FailInvalidOnionVersion.
func NewInvalidOnionVersion(onion []byte) *FailInvalidOnionVersion {
return &FailInvalidOnionVersion{OnionSHA256: sha256.Sum256(onion)}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailInvalidOnionVersion) Code() FailCode {
return CodeInvalidOnionVersion
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailInvalidOnionVersion) Decode(r io.Reader, pver uint32) error {
return ReadElement(r, f.OnionSHA256[:])
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailInvalidOnionVersion) Encode(w io.Writer, pver uint32) error {
return WriteElement(w, f.OnionSHA256[:])
}
// FailInvalidOnionHmac is return if the onion HMAC is incorrect.
//
// NOTE: May only be returned by intermediate nodes.
type FailInvalidOnionHmac struct {
// OnionSHA256 hash of the onion blob which haven't been proceeded.
OnionSHA256 [sha256.Size]byte
}
// NewInvalidOnionHmac creates new instance of the FailInvalidOnionHmac.
func NewInvalidOnionHmac(onion []byte) *FailInvalidOnionHmac {
return &FailInvalidOnionHmac{OnionSHA256: sha256.Sum256(onion)}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailInvalidOnionHmac) Code() FailCode {
return CodeInvalidOnionHmac
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailInvalidOnionHmac) Decode(r io.Reader, pver uint32) error {
return ReadElement(r, f.OnionSHA256[:])
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailInvalidOnionHmac) Encode(w io.Writer, pver uint32) error {
return WriteElement(w, f.OnionSHA256[:])
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailInvalidOnionHmac) Error() string {
return fmt.Sprintf("InvalidOnionHMAC(onion_sha=%x)", f.OnionSHA256[:])
}
// FailInvalidOnionKey is return if the ephemeral key in the onion is
// unparsable.
//
// NOTE: May only be returned by intermediate nodes.
type FailInvalidOnionKey struct {
// OnionSHA256 hash of the onion blob which haven't been proceeded.
OnionSHA256 [sha256.Size]byte
}
// NewInvalidOnionKey creates new instance of the FailInvalidOnionKey.
func NewInvalidOnionKey(onion []byte) *FailInvalidOnionKey {
return &FailInvalidOnionKey{OnionSHA256: sha256.Sum256(onion)}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailInvalidOnionKey) Code() FailCode {
return CodeInvalidOnionKey
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailInvalidOnionKey) Decode(r io.Reader, pver uint32) error {
return ReadElement(r, f.OnionSHA256[:])
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailInvalidOnionKey) Encode(w io.Writer, pver uint32) error {
return WriteElement(w, f.OnionSHA256[:])
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailInvalidOnionKey) Error() string {
return fmt.Sprintf("InvalidOnionKey(onion_sha=%x)", f.OnionSHA256[:])
}
// FailInvalidBlinding is returned if there has been a route blinding related
// error.
type FailInvalidBlinding struct {
OnionSHA256 [sha256.Size]byte
}
// A compile-time check to ensure that FailInvalidBlinding implements the
// Serializable interface.
var _ Serializable = (*FailInvalidBlinding)(nil)
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailInvalidBlinding) Code() FailCode {
return CodeInvalidBlinding
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailInvalidBlinding) Error() string {
return f.Code().String()
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailInvalidBlinding) Decode(r io.Reader, _ uint32) error {
return ReadElement(r, f.OnionSHA256[:])
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailInvalidBlinding) Encode(w io.Writer, _ uint32) error {
return WriteElement(w, f.OnionSHA256[:])
}
// NewInvalidBlinding creates new instance of FailInvalidBlinding.
func NewInvalidBlinding(onion []byte) *FailInvalidBlinding {
// The spec allows empty onion hashes for invalid blinding, so we only
// include our onion hash if it's provided.
if onion == nil {
return &FailInvalidBlinding{}
}
return &FailInvalidBlinding{OnionSHA256: sha256.Sum256(onion)}
}
// parseChannelUpdateCompatabilityMode will attempt to parse a channel updated
// encoded into an onion error payload in two ways. First, we'll try the
// compatibility oriented version wherein we'll _skip_ the length prefixing on
// the channel update message. Older versions of c-lighting do this so we'll
// attempt to parse these messages in order to retain compatibility. If we're
// unable to pull out a fully valid version, then we'll fall back to the
// regular parsing mechanism which includes the length prefix an NO type byte.
func parseChannelUpdateCompatabilityMode(r *bufio.Reader,
chanUpdate *ChannelUpdate, pver uint32) error {
// We'll peek out two bytes from the buffer without advancing the
// buffer so we can decide how to parse the remainder of it.
maybeTypeBytes, err := r.Peek(2)
if err != nil {
return err
}
// Some nodes well prefix an additional set of bytes in front of their
// channel updates. These bytes will _almost_ always be 258 or the type
// of the ChannelUpdate message.
typeInt := binary.BigEndian.Uint16(maybeTypeBytes)
if typeInt == MsgChannelUpdate {
// At this point it's likely the case that this is a channel
// update message with its type prefixed, so we'll snip off the
// first two bytes and parse it as normal.
var throwAwayTypeBytes [2]byte
_, err := r.Read(throwAwayTypeBytes[:])
if err != nil {
return err
}
}
// At this pint, we've either decided to keep the entire thing, or snip
// off the first two bytes. In either case, we can just read it as
// normal.
return chanUpdate.Decode(r, pver)
}
// FailTemporaryChannelFailure is if an otherwise unspecified transient error
// occurs for the outgoing channel (eg. channel capacity reached, too many
// in-flight htlcs)
//
// NOTE: May only be returned by intermediate nodes.
type FailTemporaryChannelFailure struct {
// Update is used to update information about state of the channel
// which caused the failure.
//
// NOTE: This field is optional.
Update *ChannelUpdate
}
// NewTemporaryChannelFailure creates new instance of the FailTemporaryChannelFailure.
func NewTemporaryChannelFailure(update *ChannelUpdate) *FailTemporaryChannelFailure {
return &FailTemporaryChannelFailure{Update: update}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailTemporaryChannelFailure) Code() FailCode {
return CodeTemporaryChannelFailure
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailTemporaryChannelFailure) Error() string {
if f.Update == nil {
return f.Code().String()
}
return fmt.Sprintf("TemporaryChannelFailure(update=%v)",
spew.Sdump(f.Update))
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailTemporaryChannelFailure) Decode(r io.Reader, pver uint32) error {
var length uint16
err := ReadElement(r, &length)
if err != nil {
return err
}
if length != 0 {
f.Update = &ChannelUpdate{}
return parseChannelUpdateCompatabilityMode(
bufio.NewReader(r), f.Update, pver,
)
}
return nil
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailTemporaryChannelFailure) Encode(w io.Writer, pver uint32) error {
var payload []byte
if f.Update != nil {
var bw bytes.Buffer
if err := f.Update.Encode(&bw, pver); err != nil {
return err
}
payload = bw.Bytes()
}
if err := WriteElement(w, uint16(len(payload))); err != nil {
return err
}
_, err := w.Write(payload)
return err
}
// FailAmountBelowMinimum is returned if the HTLC does not reach the current
// minimum amount, we tell them the amount of the incoming HTLC and the current
// channel setting for the outgoing channel.
//
// NOTE: May only be returned by the intermediate nodes in the path.
type FailAmountBelowMinimum struct {
// HtlcMsat is the wrong amount of the incoming HTLC.
HtlcMsat MilliSatoshi
// Update is used to update information about state of the channel
// which caused the failure.
Update ChannelUpdate
}
// NewAmountBelowMinimum creates new instance of the FailAmountBelowMinimum.
func NewAmountBelowMinimum(htlcMsat MilliSatoshi,
update ChannelUpdate) *FailAmountBelowMinimum {
return &FailAmountBelowMinimum{
HtlcMsat: htlcMsat,
Update: update,
}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailAmountBelowMinimum) Code() FailCode {
return CodeAmountBelowMinimum
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailAmountBelowMinimum) Error() string {
return fmt.Sprintf("AmountBelowMinimum(amt=%v, update=%v", f.HtlcMsat,
spew.Sdump(f.Update))
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailAmountBelowMinimum) Decode(r io.Reader, pver uint32) error {
if err := ReadElement(r, &f.HtlcMsat); err != nil {
return err
}
var length uint16
if err := ReadElement(r, &length); err != nil {
return err
}
f.Update = ChannelUpdate{}
return parseChannelUpdateCompatabilityMode(
bufio.NewReader(r), &f.Update, pver,
)
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailAmountBelowMinimum) Encode(w io.Writer, pver uint32) error {
if err := WriteElement(w, f.HtlcMsat); err != nil {
return err
}
return writeOnionErrorChanUpdate(w, &f.Update, pver)
}
// FailFeeInsufficient is returned if the HTLC does not pay sufficient fee, we
// tell them the amount of the incoming HTLC and the current channel setting
// for the outgoing channel.
//
// NOTE: May only be returned by intermediate nodes.
type FailFeeInsufficient struct {
// HtlcMsat is the wrong amount of the incoming HTLC.
HtlcMsat MilliSatoshi
// Update is used to update information about state of the channel
// which caused the failure.
Update ChannelUpdate
}
// NewFeeInsufficient creates new instance of the FailFeeInsufficient.
func NewFeeInsufficient(htlcMsat MilliSatoshi,
update ChannelUpdate) *FailFeeInsufficient {
return &FailFeeInsufficient{
HtlcMsat: htlcMsat,
Update: update,
}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailFeeInsufficient) Code() FailCode {
return CodeFeeInsufficient
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailFeeInsufficient) Error() string {
return fmt.Sprintf("FeeInsufficient(htlc_amt==%v, update=%v", f.HtlcMsat,
spew.Sdump(f.Update))
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailFeeInsufficient) Decode(r io.Reader, pver uint32) error {
if err := ReadElement(r, &f.HtlcMsat); err != nil {
return err
}
var length uint16
if err := ReadElement(r, &length); err != nil {
return err
}
f.Update = ChannelUpdate{}
return parseChannelUpdateCompatabilityMode(
bufio.NewReader(r), &f.Update, pver,
)
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailFeeInsufficient) Encode(w io.Writer, pver uint32) error {
if err := WriteElement(w, f.HtlcMsat); err != nil {
return err
}
return writeOnionErrorChanUpdate(w, &f.Update, pver)
}
// FailIncorrectCltvExpiry is returned if outgoing cltv value does not match
// the update add htlc's cltv expiry minus cltv expiry delta for the outgoing
// channel, we tell them the cltv expiry and the current channel setting for
// the outgoing channel.
//
// NOTE: May only be returned by intermediate nodes.
type FailIncorrectCltvExpiry struct {
// CltvExpiry is the wrong absolute timeout in blocks, after which
// outgoing HTLC expires.
CltvExpiry uint32
// Update is used to update information about state of the channel
// which caused the failure.
Update ChannelUpdate
}
// NewIncorrectCltvExpiry creates new instance of the FailIncorrectCltvExpiry.
func NewIncorrectCltvExpiry(cltvExpiry uint32,
update ChannelUpdate) *FailIncorrectCltvExpiry {
return &FailIncorrectCltvExpiry{
CltvExpiry: cltvExpiry,
Update: update,
}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailIncorrectCltvExpiry) Code() FailCode {
return CodeIncorrectCltvExpiry
}
func (f *FailIncorrectCltvExpiry) Error() string {
return fmt.Sprintf("IncorrectCltvExpiry(expiry=%v, update=%v",
f.CltvExpiry, spew.Sdump(f.Update))
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailIncorrectCltvExpiry) Decode(r io.Reader, pver uint32) error {
if err := ReadElement(r, &f.CltvExpiry); err != nil {
return err
}
var length uint16
if err := ReadElement(r, &length); err != nil {
return err
}
f.Update = ChannelUpdate{}
return parseChannelUpdateCompatabilityMode(
bufio.NewReader(r), &f.Update, pver,
)
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailIncorrectCltvExpiry) Encode(w io.Writer, pver uint32) error {
if err := WriteElement(w, f.CltvExpiry); err != nil {
return err
}
return writeOnionErrorChanUpdate(w, &f.Update, pver)
}
// FailExpiryTooSoon is returned if the ctlv-expiry is too near, we tell them
// the current channel setting for the outgoing channel.
//
// NOTE: May only be returned by intermediate nodes.
type FailExpiryTooSoon struct {
// Update is used to update information about state of the channel
// which caused the failure.
Update ChannelUpdate
}
// NewExpiryTooSoon creates new instance of the FailExpiryTooSoon.
func NewExpiryTooSoon(update ChannelUpdate) *FailExpiryTooSoon {
return &FailExpiryTooSoon{
Update: update,
}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailExpiryTooSoon) Code() FailCode {
return CodeExpiryTooSoon
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailExpiryTooSoon) Error() string {
return fmt.Sprintf("ExpiryTooSoon(update=%v", spew.Sdump(f.Update))
}
// Decode decodes the failure from l stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailExpiryTooSoon) Decode(r io.Reader, pver uint32) error {
var length uint16
if err := ReadElement(r, &length); err != nil {
return err
}
f.Update = ChannelUpdate{}
return parseChannelUpdateCompatabilityMode(
bufio.NewReader(r), &f.Update, pver,
)
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailExpiryTooSoon) Encode(w io.Writer, pver uint32) error {
return writeOnionErrorChanUpdate(w, &f.Update, pver)
}
// FailChannelDisabled is returned if the channel is disabled, we tell them the
// current channel setting for the outgoing channel.
//
// NOTE: May only be returned by intermediate nodes.
type FailChannelDisabled struct {
// Flags least-significant bit must be set to 0 if the creating node
// corresponds to the first node in the previously sent channel
// announcement and 1 otherwise.
Flags uint16
// Update is used to update information about state of the channel
// which caused the failure.
Update ChannelUpdate
}
// NewChannelDisabled creates new instance of the FailChannelDisabled.
func NewChannelDisabled(flags uint16, update ChannelUpdate) *FailChannelDisabled {
return &FailChannelDisabled{
Flags: flags,
Update: update,
}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailChannelDisabled) Code() FailCode {
return CodeChannelDisabled
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailChannelDisabled) Error() string {
return fmt.Sprintf("ChannelDisabled(flags=%v, update=%v", f.Flags,
spew.Sdump(f.Update))
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailChannelDisabled) Decode(r io.Reader, pver uint32) error {
if err := ReadElement(r, &f.Flags); err != nil {
return err
}
var length uint16
if err := ReadElement(r, &length); err != nil {
return err
}
f.Update = ChannelUpdate{}
return parseChannelUpdateCompatabilityMode(
bufio.NewReader(r), &f.Update, pver,
)
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailChannelDisabled) Encode(w io.Writer, pver uint32) error {
if err := WriteElement(w, f.Flags); err != nil {
return err
}
return writeOnionErrorChanUpdate(w, &f.Update, pver)
}
// FailFinalIncorrectCltvExpiry is returned if the outgoing_cltv_value does not
// match the ctlv_expiry of the HTLC at the final hop.
//
// NOTE: might be returned by final node only.
type FailFinalIncorrectCltvExpiry struct {
// CltvExpiry is the wrong absolute timeout in blocks, after which
// outgoing HTLC expires.
CltvExpiry uint32
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailFinalIncorrectCltvExpiry) Error() string {
return fmt.Sprintf("FinalIncorrectCltvExpiry(expiry=%v)", f.CltvExpiry)
}
// NewFinalIncorrectCltvExpiry creates new instance of the
// FailFinalIncorrectCltvExpiry.
func NewFinalIncorrectCltvExpiry(cltvExpiry uint32) *FailFinalIncorrectCltvExpiry {
return &FailFinalIncorrectCltvExpiry{
CltvExpiry: cltvExpiry,
}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailFinalIncorrectCltvExpiry) Code() FailCode {
return CodeFinalIncorrectCltvExpiry
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailFinalIncorrectCltvExpiry) Decode(r io.Reader, pver uint32) error {
return ReadElement(r, &f.CltvExpiry)
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailFinalIncorrectCltvExpiry) Encode(w io.Writer, pver uint32) error {
return WriteElement(w, f.CltvExpiry)
}
// FailFinalIncorrectHtlcAmount is returned if the amt_to_forward is higher
// than incoming_htlc_amt of the HTLC at the final hop.
//
// NOTE: May only be returned by the final node.
type FailFinalIncorrectHtlcAmount struct {
// IncomingHTLCAmount is the wrong forwarded htlc amount.
IncomingHTLCAmount MilliSatoshi
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailFinalIncorrectHtlcAmount) Error() string {
return fmt.Sprintf("FinalIncorrectHtlcAmount(amt=%v)",
f.IncomingHTLCAmount)
}
// NewFinalIncorrectHtlcAmount creates new instance of the
// FailFinalIncorrectHtlcAmount.
func NewFinalIncorrectHtlcAmount(amount MilliSatoshi) *FailFinalIncorrectHtlcAmount {
return &FailFinalIncorrectHtlcAmount{
IncomingHTLCAmount: amount,
}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailFinalIncorrectHtlcAmount) Code() FailCode {
return CodeFinalIncorrectHtlcAmount
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailFinalIncorrectHtlcAmount) Decode(r io.Reader, pver uint32) error {
return ReadElement(r, &f.IncomingHTLCAmount)
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailFinalIncorrectHtlcAmount) Encode(w io.Writer, pver uint32) error {
return WriteElement(w, f.IncomingHTLCAmount)
}
// FailExpiryTooFar is returned if the CLTV expiry in the HTLC is too far in the
// future.
//
// NOTE: May be returned by any node in the payment route.
type FailExpiryTooFar struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailExpiryTooFar) Code() FailCode {
return CodeExpiryTooFar
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailExpiryTooFar) Error() string {
return f.Code().String()
}
// InvalidOnionPayload is returned if the hop could not process the TLV payload
// enclosed in the onion.
type InvalidOnionPayload struct {
// Type is the TLV type that caused the specific failure.
Type uint64
// Offset is the byte offset within the payload where the failure
// occurred.
Offset uint16
}
// NewInvalidOnionPayload initializes a new InvalidOnionPayload failure.
func NewInvalidOnionPayload(typ uint64, offset uint16) *InvalidOnionPayload {
return &InvalidOnionPayload{
Type: typ,
Offset: offset,
}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *InvalidOnionPayload) Code() FailCode {
return CodeInvalidOnionPayload
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *InvalidOnionPayload) Error() string {
return fmt.Sprintf("%v(type=%v, offset=%d)",
f.Code(), f.Type, f.Offset)
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *InvalidOnionPayload) Decode(r io.Reader, pver uint32) error {
var buf [8]byte
typ, err := tlv.ReadVarInt(r, &buf)
if err != nil {
return err
}
f.Type = typ
return ReadElements(r, &f.Offset)
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *InvalidOnionPayload) Encode(w io.Writer, pver uint32) error {
var buf [8]byte
if err := tlv.WriteVarInt(w, f.Type, &buf); err != nil {
return err
}
return WriteElements(w, f.Offset)
}
// FailMPPTimeout is returned if the complete amount for a multi part payment
// was not received within a reasonable time.
//
// NOTE: May only be returned by the final node in the path.
type FailMPPTimeout struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailMPPTimeout) Code() FailCode {
return CodeMPPTimeout
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailMPPTimeout) Error() string {
return f.Code().String()
}
// DecodeFailure decodes, validates, and parses the lnwire onion failure, for
// the provided protocol version.
func DecodeFailure(r io.Reader, pver uint32) (FailureMessage, error) {
// First, we'll parse out the encapsulated failure message itself. This
// is a 2 byte length followed by the payload itself.
var failureLength uint16
if err := ReadElement(r, &failureLength); err != nil {
return nil, fmt.Errorf("unable to read error len: %w", err)
}
if failureLength > FailureMessageLength {
return nil, fmt.Errorf("failure message is too "+
"long: %v", failureLength)
}
failureData := make([]byte, failureLength)
if _, err := io.ReadFull(r, failureData); err != nil {
return nil, fmt.Errorf("unable to full read payload of "+
"%v: %v", failureLength, err)
}
dataReader := bytes.NewReader(failureData)
return DecodeFailureMessage(dataReader, pver)
}
// DecodeFailureMessage decodes just the failure message, ignoring any padding
// that may be present at the end.
func DecodeFailureMessage(r io.Reader, pver uint32) (FailureMessage, error) {
// Once we have the failure data, we can obtain the failure code from
// the first two bytes of the buffer.
var codeBytes [2]byte
if _, err := io.ReadFull(r, codeBytes[:]); err != nil {
return nil, fmt.Errorf("unable to read failure code: %w", err)
}
failCode := FailCode(binary.BigEndian.Uint16(codeBytes[:]))
// Create the empty failure by given code and populate the failure with
// additional data if needed.
failure, err := makeEmptyOnionError(failCode)
if err != nil {
return nil, fmt.Errorf("unable to make empty error: %w", err)
}
// Finally, if this failure has a payload, then we'll read that now as
// well.
switch f := failure.(type) {
case Serializable:
if err := f.Decode(r, pver); err != nil {
return nil, fmt.Errorf("unable to decode error "+
"update (type=%T): %v", failure, err)
}
}
return failure, nil
}
// EncodeFailure encodes, including the necessary onion failure header
// information.
func EncodeFailure(w io.Writer, failure FailureMessage, pver uint32) error {
var failureMessageBuffer bytes.Buffer
err := EncodeFailureMessage(&failureMessageBuffer, failure, pver)
if err != nil {
return err
}
// The combined size of this message must be below the max allowed
// failure message length.
failureMessage := failureMessageBuffer.Bytes()
if len(failureMessage) > FailureMessageLength {
return fmt.Errorf("failure message exceed max "+
"available size: %v", len(failureMessage))
}
// Finally, we'll add some padding in order to ensure that all failure
// messages are fixed size.
pad := make([]byte, FailureMessageLength-len(failureMessage))
return WriteElements(w,
uint16(len(failureMessage)),
failureMessage,
uint16(len(pad)),
pad,
)
}
// EncodeFailureMessage encodes just the failure message without adding a length
// and padding the message for the onion protocol.
func EncodeFailureMessage(w io.Writer, failure FailureMessage, pver uint32) error {
// First, we'll write out the error code itself into the failure
// buffer.
var codeBytes [2]byte
code := uint16(failure.Code())
binary.BigEndian.PutUint16(codeBytes[:], code)
_, err := w.Write(codeBytes[:])
if err != nil {
return err
}
// Next, some message have an additional message payload, if this is
// one of those types, then we'll also encode the error payload as
// well.
switch failure := failure.(type) {
case Serializable:
if err := failure.Encode(w, pver); err != nil {
return err
}
}
return nil
}
// makeEmptyOnionError creates a new empty onion error of the proper concrete
// type based on the passed failure code.
func makeEmptyOnionError(code FailCode) (FailureMessage, error) {
switch code {
case CodeInvalidRealm:
return &FailInvalidRealm{}, nil
case CodeTemporaryNodeFailure:
return &FailTemporaryNodeFailure{}, nil
case CodePermanentNodeFailure:
return &FailPermanentNodeFailure{}, nil
case CodeRequiredNodeFeatureMissing:
return &FailRequiredNodeFeatureMissing{}, nil
case CodePermanentChannelFailure:
return &FailPermanentChannelFailure{}, nil
case CodeRequiredChannelFeatureMissing:
return &FailRequiredChannelFeatureMissing{}, nil
case CodeUnknownNextPeer:
return &FailUnknownNextPeer{}, nil
case CodeIncorrectOrUnknownPaymentDetails:
return &FailIncorrectDetails{}, nil
case CodeIncorrectPaymentAmount:
return &FailIncorrectPaymentAmount{}, nil
case CodeFinalExpiryTooSoon:
return &FailFinalExpiryTooSoon{}, nil
case CodeInvalidOnionVersion:
return &FailInvalidOnionVersion{}, nil
case CodeInvalidOnionHmac:
return &FailInvalidOnionHmac{}, nil
case CodeInvalidOnionKey:
return &FailInvalidOnionKey{}, nil
case CodeTemporaryChannelFailure:
return &FailTemporaryChannelFailure{}, nil
case CodeAmountBelowMinimum:
return &FailAmountBelowMinimum{}, nil
case CodeFeeInsufficient:
return &FailFeeInsufficient{}, nil
case CodeIncorrectCltvExpiry:
return &FailIncorrectCltvExpiry{}, nil
case CodeExpiryTooSoon:
return &FailExpiryTooSoon{}, nil
case CodeChannelDisabled:
return &FailChannelDisabled{}, nil
case CodeFinalIncorrectCltvExpiry:
return &FailFinalIncorrectCltvExpiry{}, nil
case CodeFinalIncorrectHtlcAmount:
return &FailFinalIncorrectHtlcAmount{}, nil
case CodeExpiryTooFar:
return &FailExpiryTooFar{}, nil
case CodeInvalidOnionPayload:
return &InvalidOnionPayload{}, nil
case CodeMPPTimeout:
return &FailMPPTimeout{}, nil
case CodeInvalidBlinding:
return &FailInvalidBlinding{}, nil
default:
return nil, errors.Errorf("unknown error code: %v", code)
}
}
// writeOnionErrorChanUpdate writes out a ChannelUpdate using the onion error
// format. The format is that we first write out the true serialized length of
// the channel update, followed by the serialized channel update itself.
func writeOnionErrorChanUpdate(w io.Writer, chanUpdate *ChannelUpdate,
pver uint32) error {
// First, we encode the channel update in a temporary buffer in order
// to get the exact serialized size.
var b bytes.Buffer
if err := chanUpdate.Encode(&b, pver); err != nil {
return err
}
// Now that we know the size, we can write the length out in the main
// writer.
updateLen := b.Len()
if err := WriteElement(w, uint16(updateLen)); err != nil {
return err
}
// With the length written, we'll then write out the serialized channel
// update.
if _, err := w.Write(b.Bytes()); err != nil {
return err
}
return nil
}
package lnwire
import (
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// FundingFlag represents the possible bit mask values for the ChannelFlags
// field within the OpenChannel struct.
type FundingFlag uint8
const (
// FFAnnounceChannel is a FundingFlag that when set, indicates the
// initiator of a funding flow wishes to announce the channel to the
// greater network.
FFAnnounceChannel FundingFlag = 1 << iota
)
// OpenChannel is the message Alice sends to Bob if we should like to create a
// channel with Bob where she's the sole provider of funds to the channel.
// Single funder channels simplify the initial funding workflow, are supported
// by nodes backed by SPV Bitcoin clients, and have a simpler security models
// than dual funded channels.
type OpenChannel struct {
// ChainHash is the target chain that the initiator wishes to open a
// channel within.
ChainHash chainhash.Hash
// PendingChannelID serves to uniquely identify the future channel
// created by the initiated single funder workflow.
PendingChannelID [32]byte
// FundingAmount is the amount of satoshis that the initiator of the
// channel wishes to use as the total capacity of the channel. The
// initial balance of the funding will be this value minus the push
// amount (if set).
FundingAmount btcutil.Amount
// PushAmount is the value that the initiating party wishes to "push"
// to the responding as part of the first commitment state. If the
// responder accepts, then this will be their initial balance.
PushAmount MilliSatoshi
// DustLimit is the specific dust limit the sender of this message
// would like enforced on their version of the commitment transaction.
// Any output below this value will be "trimmed" from the commitment
// transaction, with the amount of the HTLC going to dust.
DustLimit btcutil.Amount
// MaxValueInFlight represents the maximum amount of coins that can be
// pending within the channel at any given time. If the amount of funds
// in limbo exceeds this amount, then the channel will be failed.
MaxValueInFlight MilliSatoshi
// ChannelReserve is the amount of BTC that the receiving party MUST
// maintain a balance above at all times. This is a safety mechanism to
// ensure that both sides always have skin in the game during the
// channel's lifetime.
ChannelReserve btcutil.Amount
// HtlcMinimum is the smallest HTLC that the sender of this message
// will accept.
HtlcMinimum MilliSatoshi
// FeePerKiloWeight is the initial fee rate that the initiator suggests
// for both commitment transaction. This value is expressed in sat per
// kilo-weight.
//
// TODO(halseth): make SatPerKWeight when fee estimation is in own
// package. Currently this will cause an import cycle.
FeePerKiloWeight uint32
// CsvDelay is the number of blocks to use for the relative time lock
// in the pay-to-self output of both commitment transactions.
CsvDelay uint16
// MaxAcceptedHTLCs is the total number of incoming HTLC's that the
// sender of this channel will accept.
MaxAcceptedHTLCs uint16
// FundingKey is the key that should be used on behalf of the sender
// within the 2-of-2 multi-sig output that it contained within the
// funding transaction.
FundingKey *btcec.PublicKey
// RevocationPoint is the base revocation point for the sending party.
// Any commitment transaction belonging to the receiver of this message
// should use this key and their per-commitment point to derive the
// revocation key for the commitment transaction.
RevocationPoint *btcec.PublicKey
// PaymentPoint is the base payment point for the sending party. This
// key should be combined with the per commitment point for a
// particular commitment state in order to create the key that should
// be used in any output that pays directly to the sending party, and
// also within the HTLC covenant transactions.
PaymentPoint *btcec.PublicKey
// DelayedPaymentPoint is the delay point for the sending party. This
// key should be combined with the per commitment point to derive the
// keys that are used in outputs of the sender's commitment transaction
// where they claim funds.
DelayedPaymentPoint *btcec.PublicKey
// HtlcPoint is the base point used to derive the set of keys for this
// party that will be used within the HTLC public key scripts. This
// value is combined with the receiver's revocation base point in order
// to derive the keys that are used within HTLC scripts.
HtlcPoint *btcec.PublicKey
// FirstCommitmentPoint is the first commitment point for the sending
// party. This value should be combined with the receiver's revocation
// base point in order to derive the revocation keys that are placed
// within the commitment transaction of the sender.
FirstCommitmentPoint *btcec.PublicKey
// ChannelFlags is a bit-field which allows the initiator of the
// channel to specify further behavior surrounding the channel.
// Currently, the least significant bit of this bit field indicates the
// initiator of the channel wishes to advertise this channel publicly.
ChannelFlags FundingFlag
// UpfrontShutdownScript is the script to which the channel funds should
// be paid when mutually closing the channel. This field is optional, and
// and has a length prefix, so a zero will be written if it is not set
// and its length followed by the script will be written if it is set.
UpfrontShutdownScript DeliveryAddress
}
// A compile time check to ensure OpenChannel implements the lnwire.Message
// interface.
var _ Message = (*OpenChannel)(nil)
// Encode serializes the target OpenChannel into the passed io.Writer
// implementation. Serialization will observe the rules defined by the passed
// protocol version.
//
// This is part of the lnwire.Message interface.
func (o *OpenChannel) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
o.ChainHash[:],
o.PendingChannelID[:],
o.FundingAmount,
o.PushAmount,
o.DustLimit,
o.MaxValueInFlight,
o.ChannelReserve,
o.HtlcMinimum,
o.FeePerKiloWeight,
o.CsvDelay,
o.MaxAcceptedHTLCs,
o.FundingKey,
o.RevocationPoint,
o.PaymentPoint,
o.DelayedPaymentPoint,
o.HtlcPoint,
o.FirstCommitmentPoint,
o.ChannelFlags,
o.UpfrontShutdownScript,
)
}
// Decode deserializes the serialized OpenChannel stored in the passed
// io.Reader into the target OpenChannel using the deserialization rules
// defined by the passed protocol version.
//
// This is part of the lnwire.Message interface.
func (o *OpenChannel) Decode(r io.Reader, pver uint32) error {
if err := ReadElements(r,
o.ChainHash[:],
o.PendingChannelID[:],
&o.FundingAmount,
&o.PushAmount,
&o.DustLimit,
&o.MaxValueInFlight,
&o.ChannelReserve,
&o.HtlcMinimum,
&o.FeePerKiloWeight,
&o.CsvDelay,
&o.MaxAcceptedHTLCs,
&o.FundingKey,
&o.RevocationPoint,
&o.PaymentPoint,
&o.DelayedPaymentPoint,
&o.HtlcPoint,
&o.FirstCommitmentPoint,
&o.ChannelFlags,
); err != nil {
return err
}
// Check for the optional upfront shutdown script field. If it is not there,
// silence the EOF error.
err := ReadElement(r, &o.UpfrontShutdownScript)
if err != nil && err != io.EOF {
return err
}
return nil
}
// MsgType returns the MessageType code which uniquely identifies this message
// as an OpenChannel on the wire.
//
// This is part of the lnwire.Message interface.
func (o *OpenChannel) MsgType() MessageType {
return MsgOpenChannel
}
// MaxPayloadLength returns the maximum allowed payload length for a
// OpenChannel message.
//
// This is part of the lnwire.Message interface.
func (o *OpenChannel) MaxPayloadLength(uint32) uint32 {
// (32 * 2) + (8 * 6) + (4 * 1) + (2 * 2) + (33 * 6) + 1
var length uint32 = 319 // base length
// Upfront shutdown script max length.
length += 2 + deliveryAddressMaxSize
return length
}
package lnwire
import "io"
// PingPayload is a set of opaque bytes used to pad out a ping message.
type PingPayload []byte
// Ping defines a message which is sent by peers periodically to determine if
// the connection is still valid. Each ping message carries the number of bytes
// to pad the pong response with, and also a number of bytes to be ignored at
// the end of the ping message (which is padding).
type Ping struct {
// NumPongBytes is the number of bytes the pong response to this
// message should carry.
NumPongBytes uint16
// PaddingBytes is a set of opaque bytes used to pad out this ping
// message. Using this field in conjunction to the one above, it's
// possible for node to generate fake cover traffic.
PaddingBytes PingPayload
}
// NewPing returns a new Ping message.
func NewPing(numBytes uint16) *Ping {
return &Ping{
NumPongBytes: numBytes,
}
}
// A compile time check to ensure Ping implements the lnwire.Message interface.
var _ Message = (*Ping)(nil)
// Decode deserializes a serialized Ping message stored in the passed io.Reader
// observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (p *Ping) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&p.NumPongBytes,
&p.PaddingBytes)
}
// Encode serializes the target Ping into the passed io.Writer observing the
// protocol version specified.
//
// This is part of the lnwire.Message interface.
func (p *Ping) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
p.NumPongBytes,
p.PaddingBytes)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (p *Ping) MsgType() MessageType {
return MsgPing
}
// MaxPayloadLength returns the maximum allowed payload size for a Ping
// complete message observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (p Ping) MaxPayloadLength(uint32) uint32 {
return 65532
}
package lnwire
import "io"
// PongPayload is a set of opaque bytes sent in response to a ping message.
type PongPayload []byte
// Pong defines a message which is the direct response to a received Ping
// message. A Pong reply indicates that a connection is still active. The Pong
// reply to a Ping message should contain the nonce carried in the original
// Pong message.
type Pong struct {
// PongBytes is a set of opaque bytes that corresponds to the
// NumPongBytes defined in the ping message that this pong is
// replying to.
PongBytes PongPayload
}
// NewPong returns a new Pong message.
func NewPong(pongBytes []byte) *Pong {
return &Pong{
PongBytes: pongBytes,
}
}
// A compile time check to ensure Pong implements the lnwire.Message interface.
var _ Message = (*Pong)(nil)
// Decode deserializes a serialized Pong message stored in the passed io.Reader
// observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (p *Pong) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&p.PongBytes,
)
}
// Encode serializes the target Pong into the passed io.Writer observing the
// protocol version specified.
//
// This is part of the lnwire.Message interface.
func (p *Pong) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
p.PongBytes,
)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (p *Pong) MsgType() MessageType {
return MsgPong
}
// MaxPayloadLength returns the maximum allowed payload size for a Pong
// complete message observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (p *Pong) MaxPayloadLength(uint32) uint32 {
return 65532
}
package lnwire
import (
"io"
"math"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// QueryChannelRange is a message sent by a node in order to query the
// receiving node of the set of open channel they know of with short channel
// ID's after the specified block height, capped at the number of blocks beyond
// that block height. This will be used by nodes upon initial connect to
// synchronize their views of the network.
type QueryChannelRange struct {
// ChainHash denotes the target chain that we're trying to synchronize
// channel graph state for.
ChainHash chainhash.Hash
// FirstBlockHeight is the first block in the query range. The
// responder should send all new short channel IDs from this block
// until this block plus the specified number of blocks.
FirstBlockHeight uint32
// NumBlocks is the number of blocks beyond the first block that short
// channel ID's should be sent for.
NumBlocks uint32
}
// NewQueryChannelRange creates a new empty QueryChannelRange message.
func NewQueryChannelRange() *QueryChannelRange {
return &QueryChannelRange{}
}
// A compile time check to ensure QueryChannelRange implements the
// lnwire.Message interface.
var _ Message = (*QueryChannelRange)(nil)
// Decode deserializes a serialized QueryChannelRange message stored in the
// passed io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (q *QueryChannelRange) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
q.ChainHash[:],
&q.FirstBlockHeight,
&q.NumBlocks,
)
}
// Encode serializes the target QueryChannelRange into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (q *QueryChannelRange) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
q.ChainHash[:],
q.FirstBlockHeight,
q.NumBlocks,
)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (q *QueryChannelRange) MsgType() MessageType {
return MsgQueryChannelRange
}
// MaxPayloadLength returns the maximum allowed payload size for a
// QueryChannelRange complete message observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (q *QueryChannelRange) MaxPayloadLength(uint32) uint32 {
// 32 + 4 + 4
return 40
}
// LastBlockHeight returns the last block height covered by the range of a
// QueryChannelRange message.
func (q *QueryChannelRange) LastBlockHeight() uint32 {
// Handle overflows by casting to uint64.
lastBlockHeight := uint64(q.FirstBlockHeight) + uint64(q.NumBlocks) - 1
if lastBlockHeight > math.MaxUint32 {
return math.MaxUint32
}
return uint32(lastBlockHeight)
}
package lnwire
import (
"bytes"
"compress/zlib"
"fmt"
"io"
"sort"
"sync"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// ShortChanIDEncoding is an enum-like type that represents exactly how a set
// of short channel ID's is encoded on the wire. The set of encodings allows us
// to take advantage of the structure of a list of short channel ID's to
// achieving a high degree of compression.
type ShortChanIDEncoding uint8
const (
// EncodingSortedPlain signals that the set of short channel ID's is
// encoded using the regular encoding, in a sorted order.
EncodingSortedPlain ShortChanIDEncoding = 0
// EncodingSortedZlib signals that the set of short channel ID's is
// encoded by first sorting the set of channel ID's, as then
// compressing them using zlib.
EncodingSortedZlib ShortChanIDEncoding = 1
)
const (
// maxZlibBufSize is the max number of bytes that we'll accept from a
// zlib decoding instance. We do this in order to limit the total
// amount of memory allocated during a decoding instance.
maxZlibBufSize = 67413630
)
// ErrUnsortedSIDs is returned when decoding a QueryShortChannelID request whose
// items were not sorted.
type ErrUnsortedSIDs struct {
prevSID ShortChannelID
curSID ShortChannelID
}
// Error returns a human-readable description of the error.
func (e ErrUnsortedSIDs) Error() string {
return fmt.Sprintf("current sid: %v isn't greater than last sid: %v",
e.curSID, e.prevSID)
}
// zlibDecodeMtx is a package level mutex that we'll use in order to ensure
// that we'll only attempt a single zlib decoding instance at a time. This
// allows us to also further bound our memory usage.
var zlibDecodeMtx sync.Mutex
// ErrUnknownShortChanIDEncoding is a parametrized error that indicates that we
// came across an unknown short channel ID encoding, and therefore were unable
// to continue parsing.
func ErrUnknownShortChanIDEncoding(encoding ShortChanIDEncoding) error {
return fmt.Errorf("unknown short chan id encoding: %v", encoding)
}
// QueryShortChanIDs is a message that allows the sender to query a set of
// channel announcement and channel update messages that correspond to the set
// of encoded short channel ID's. The encoding of the short channel ID's is
// detailed in the query message ensuring that the receiver knows how to
// properly decode each encode short channel ID which may be encoded using a
// compression format. The receiver should respond with a series of channel
// announcement and channel updates, finally sending a ReplyShortChanIDsEnd
// message.
type QueryShortChanIDs struct {
// ChainHash denotes the target chain that we're querying for the
// channel ID's of.
ChainHash chainhash.Hash
// EncodingType is a signal to the receiver of the message that
// indicates exactly how the set of short channel ID's that follow have
// been encoded.
EncodingType ShortChanIDEncoding
// ShortChanIDs is a slice of decoded short channel ID's.
ShortChanIDs []ShortChannelID
// noSort indicates whether or not to sort the short channel ids before
// writing them out.
//
// NOTE: This should only be used during testing.
noSort bool
}
// NewQueryShortChanIDs creates a new QueryShortChanIDs message.
func NewQueryShortChanIDs(h chainhash.Hash, e ShortChanIDEncoding,
s []ShortChannelID) *QueryShortChanIDs {
return &QueryShortChanIDs{
ChainHash: h,
EncodingType: e,
ShortChanIDs: s,
}
}
// A compile time check to ensure QueryShortChanIDs implements the
// lnwire.Message interface.
var _ Message = (*QueryShortChanIDs)(nil)
// Decode deserializes a serialized QueryShortChanIDs message stored in the
// passed io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (q *QueryShortChanIDs) Decode(r io.Reader, pver uint32) error {
err := ReadElements(r, q.ChainHash[:])
if err != nil {
return err
}
q.EncodingType, q.ShortChanIDs, err = decodeShortChanIDs(r)
return err
}
// decodeShortChanIDs decodes a set of short channel ID's that have been
// encoded. The first byte of the body details how the short chan ID's were
// encoded. We'll use this type to govern exactly how we go about encoding the
// set of short channel ID's.
func decodeShortChanIDs(r io.Reader) (ShortChanIDEncoding, []ShortChannelID, error) {
// First, we'll attempt to read the number of bytes in the body of the
// set of encoded short channel ID's.
var numBytesResp uint16
err := ReadElements(r, &numBytesResp)
if err != nil {
return 0, nil, err
}
if numBytesResp == 0 {
return 0, nil, nil
}
queryBody := make([]byte, numBytesResp)
if _, err := io.ReadFull(r, queryBody); err != nil {
return 0, nil, err
}
// The first byte is the encoding type, so we'll extract that so we can
// continue our parsing.
encodingType := ShortChanIDEncoding(queryBody[0])
// Before continuing, we'll snip off the first byte of the query body
// as that was just the encoding type.
queryBody = queryBody[1:]
// Otherwise, depending on the encoding type, we'll decode the encode
// short channel ID's in a different manner.
switch encodingType {
// In this encoding, we'll simply read a sort array of encoded short
// channel ID's from the buffer.
case EncodingSortedPlain:
// If after extracting the encoding type, the number of
// remaining bytes is not a whole multiple of the size of an
// encoded short channel ID (8 bytes), then we'll return a
// parsing error.
if len(queryBody)%8 != 0 {
return 0, nil, fmt.Errorf("whole number of short "+
"chan ID's cannot be encoded in len=%v",
len(queryBody))
}
// As each short channel ID is encoded as 8 bytes, we can
// compute the number of bytes encoded based on the size of the
// query body.
numShortChanIDs := len(queryBody) / 8
if numShortChanIDs == 0 {
return encodingType, nil, nil
}
// Finally, we'll read out the exact number of short channel
// ID's to conclude our parsing.
shortChanIDs := make([]ShortChannelID, numShortChanIDs)
bodyReader := bytes.NewReader(queryBody)
var lastChanID ShortChannelID
for i := 0; i < numShortChanIDs; i++ {
if err := ReadElements(bodyReader, &shortChanIDs[i]); err != nil {
return 0, nil, fmt.Errorf("unable to parse "+
"short chan ID: %v", err)
}
// We'll ensure that this short chan ID is greater than
// the last one. This is a requirement within the
// encoding, and if violated can aide us in detecting
// malicious payloads. This can only be true starting
// at the second chanID.
cid := shortChanIDs[i]
if i > 0 && cid.ToUint64() <= lastChanID.ToUint64() {
return 0, nil, ErrUnsortedSIDs{lastChanID, cid}
}
lastChanID = cid
}
return encodingType, shortChanIDs, nil
// In this encoding, we'll use zlib to decode the compressed payload.
// However, we'll pay attention to ensure that we don't open our selves
// up to a memory exhaustion attack.
case EncodingSortedZlib:
// We'll obtain an ultimately release the zlib decode mutex.
// This guards us against allocating too much memory to decode
// each instance from concurrent peers.
zlibDecodeMtx.Lock()
defer zlibDecodeMtx.Unlock()
// At this point, if there's no body remaining, then only the encoding
// type was specified, meaning that there're no further bytes to be
// parsed.
if len(queryBody) == 0 {
return encodingType, nil, nil
}
// Before we start to decode, we'll create a limit reader over
// the current reader. This will ensure that we can control how
// much memory we're allocating during the decoding process.
limitedDecompressor, err := zlib.NewReader(&io.LimitedReader{
R: bytes.NewReader(queryBody),
N: maxZlibBufSize,
})
if err != nil {
return 0, nil, fmt.Errorf("unable to create zlib "+
"reader: %w", err)
}
var (
shortChanIDs []ShortChannelID
lastChanID ShortChannelID
i int
)
for {
// We'll now attempt to read the next short channel ID
// encoded in the payload.
var cid ShortChannelID
err := ReadElements(limitedDecompressor, &cid)
switch {
// If we get an EOF error, then that either means we've
// read all that's contained in the buffer, or have hit
// our limit on the number of bytes we'll read. In
// either case, we'll return what we have so far.
case err == io.ErrUnexpectedEOF || err == io.EOF:
return encodingType, shortChanIDs, nil
// Otherwise, we hit some other sort of error, possibly
// an invalid payload, so we'll exit early with the
// error.
case err != nil:
return 0, nil, fmt.Errorf("unable to "+
"deflate next short chan "+
"ID: %v", err)
}
// We successfully read the next ID, so we'll collect
// that in the set of final ID's to return.
shortChanIDs = append(shortChanIDs, cid)
// Finally, we'll ensure that this short chan ID is
// greater than the last one. This is a requirement
// within the encoding, and if violated can aide us in
// detecting malicious payloads. This can only be true
// starting at the second chanID.
if i > 0 && cid.ToUint64() <= lastChanID.ToUint64() {
return 0, nil, ErrUnsortedSIDs{lastChanID, cid}
}
lastChanID = cid
i++
}
default:
// If we've been sent an encoding type that we don't know of,
// then we'll return a parsing error as we can't continue if
// we're unable to encode them.
return 0, nil, ErrUnknownShortChanIDEncoding(encodingType)
}
}
// Encode serializes the target QueryShortChanIDs into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (q *QueryShortChanIDs) Encode(w io.Writer, pver uint32) error {
// First, we'll write out the chain hash.
err := WriteElements(w, q.ChainHash[:])
if err != nil {
return err
}
// Base on our encoding type, we'll write out the set of short channel
// ID's.
return encodeShortChanIDs(w, q.EncodingType, q.ShortChanIDs, q.noSort)
}
// encodeShortChanIDs encodes the passed short channel ID's into the passed
// io.Writer, respecting the specified encoding type.
func encodeShortChanIDs(w io.Writer, encodingType ShortChanIDEncoding,
shortChanIDs []ShortChannelID, noSort bool) error {
// For both of the current encoding types, the channel ID's are to be
// sorted in place, so we'll do that now. The sorting is applied unless
// we were specifically requested not to for testing purposes.
if !noSort {
sort.Slice(shortChanIDs, func(i, j int) bool {
return shortChanIDs[i].ToUint64() <
shortChanIDs[j].ToUint64()
})
}
switch encodingType {
// In this encoding, we'll simply write a sorted array of encoded short
// channel ID's from the buffer.
case EncodingSortedPlain:
// First, we'll write out the number of bytes of the query
// body. We add 1 as the response will have the encoding type
// prepended to it.
numBytesBody := uint16(len(shortChanIDs)*8) + 1
if err := WriteElements(w, numBytesBody); err != nil {
return err
}
// We'll then write out the encoding that that follows the
// actual encoded short channel ID's.
if err := WriteElements(w, encodingType); err != nil {
return err
}
// Now that we know they're sorted, we can write out each short
// channel ID to the buffer.
for _, chanID := range shortChanIDs {
if err := WriteElements(w, chanID); err != nil {
return fmt.Errorf("unable to write short chan "+
"ID: %v", err)
}
}
return nil
// For this encoding we'll first write out a serialized version of all
// the channel ID's into a buffer, then zlib encode that. The final
// payload is what we'll write out to the passed io.Writer.
//
// TODO(roasbeef): assumes the caller knows the proper chunk size to
// pass to avoid bin-packing here
case EncodingSortedZlib:
// We'll make a new buffer, then wrap that with a zlib writer
// so we can write directly to the buffer and encode in a
// streaming manner.
var buf bytes.Buffer
zlibWriter := zlib.NewWriter(&buf)
// If we don't have anything at all to write, then we'll write
// an empty payload so we don't include things like the zlib
// header when the remote party is expecting no actual short
// channel IDs.
var compressedPayload []byte
if len(shortChanIDs) > 0 {
// Next, we'll write out all the channel ID's directly
// into the zlib writer, which will do compressing on
// the fly.
for _, chanID := range shortChanIDs {
err := WriteElements(zlibWriter, chanID)
if err != nil {
return fmt.Errorf("unable to write short chan "+
"ID: %v", err)
}
}
// Now that we've written all the elements, we'll
// ensure the compressed stream is written to the
// underlying buffer.
if err := zlibWriter.Close(); err != nil {
return fmt.Errorf("unable to finalize "+
"compression: %v", err)
}
compressedPayload = buf.Bytes()
}
// Now that we have all the items compressed, we can compute
// what the total payload size will be. We add one to account
// for the byte to encode the type.
//
// If we don't have any actual bytes to write, then we'll end
// up emitting one byte for the length, followed by the
// encoding type, and nothing more. The spec isn't 100% clear
// in this area, but we do this as this is what most of the
// other implementations do.
numBytesBody := len(compressedPayload) + 1
// Finally, we can write out the number of bytes, the
// compression type, and finally the buffer itself.
if err := WriteElements(w, uint16(numBytesBody)); err != nil {
return err
}
if err := WriteElements(w, encodingType); err != nil {
return err
}
_, err := w.Write(compressedPayload)
return err
default:
// If we're trying to encode with an encoding type that we
// don't know of, then we'll return a parsing error as we can't
// continue if we're unable to encode them.
return ErrUnknownShortChanIDEncoding(encodingType)
}
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (q *QueryShortChanIDs) MsgType() MessageType {
return MsgQueryShortChanIDs
}
// MaxPayloadLength returns the maximum allowed payload size for a
// QueryShortChanIDs complete message observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (q *QueryShortChanIDs) MaxPayloadLength(uint32) uint32 {
return MaxMessagePayload
}
package lnwire
import "io"
// ReplyChannelRange is the response to the QueryChannelRange message. It
// includes the original query, and the next streaming chunk of encoded short
// channel ID's as the response. We'll also include a byte that indicates if
// this is the last query in the message.
type ReplyChannelRange struct {
// QueryChannelRange is the corresponding query to this response.
QueryChannelRange
// Complete denotes if this is the conclusion of the set of streaming
// responses to the original query.
Complete uint8
// EncodingType is a signal to the receiver of the message that
// indicates exactly how the set of short channel ID's that follow have
// been encoded.
EncodingType ShortChanIDEncoding
// ShortChanIDs is a slice of decoded short channel ID's.
ShortChanIDs []ShortChannelID
// noSort indicates whether or not to sort the short channel ids before
// writing them out.
//
// NOTE: This should only be used for testing.
noSort bool
}
// NewReplyChannelRange creates a new empty ReplyChannelRange message.
func NewReplyChannelRange() *ReplyChannelRange {
return &ReplyChannelRange{}
}
// A compile time check to ensure ReplyChannelRange implements the
// lnwire.Message interface.
var _ Message = (*ReplyChannelRange)(nil)
// Decode deserializes a serialized ReplyChannelRange message stored in the
// passed io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *ReplyChannelRange) Decode(r io.Reader, pver uint32) error {
err := c.QueryChannelRange.Decode(r, pver)
if err != nil {
return err
}
if err := ReadElements(r, &c.Complete); err != nil {
return err
}
c.EncodingType, c.ShortChanIDs, err = decodeShortChanIDs(r)
return err
}
// Encode serializes the target ReplyChannelRange into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *ReplyChannelRange) Encode(w io.Writer, pver uint32) error {
if err := c.QueryChannelRange.Encode(w, pver); err != nil {
return err
}
if err := WriteElements(w, c.Complete); err != nil {
return err
}
return encodeShortChanIDs(w, c.EncodingType, c.ShortChanIDs, c.noSort)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *ReplyChannelRange) MsgType() MessageType {
return MsgReplyChannelRange
}
// MaxPayloadLength returns the maximum allowed payload size for a
// ReplyChannelRange complete message observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *ReplyChannelRange) MaxPayloadLength(uint32) uint32 {
return MaxMessagePayload
}
package lnwire
import (
"io"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// ReplyShortChanIDsEnd is a message that marks the end of a streaming message
// response to an initial QueryShortChanIDs message. This marks that the
// receiver of the original QueryShortChanIDs for the target chain has either
// sent all adequate responses it knows of, or doesn't know of any short chan
// ID's for the target chain.
type ReplyShortChanIDsEnd struct {
// ChainHash denotes the target chain that we're respond to a short
// chan ID query for.
ChainHash chainhash.Hash
// Complete will be set to 0 if we don't know of the chain that the
// remote peer sent their query for. Otherwise, we'll set this to 1 in
// order to indicate that we've sent all known responses for the prior
// set of short chan ID's in the corresponding QueryShortChanIDs
// message.
Complete uint8
}
// NewReplyShortChanIDsEnd creates a new empty ReplyShortChanIDsEnd message.
func NewReplyShortChanIDsEnd() *ReplyShortChanIDsEnd {
return &ReplyShortChanIDsEnd{}
}
// A compile time check to ensure ReplyShortChanIDsEnd implements the
// lnwire.Message interface.
var _ Message = (*ReplyShortChanIDsEnd)(nil)
// Decode deserializes a serialized ReplyShortChanIDsEnd message stored in the
// passed io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *ReplyShortChanIDsEnd) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
c.ChainHash[:],
&c.Complete,
)
}
// Encode serializes the target ReplyShortChanIDsEnd into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *ReplyShortChanIDsEnd) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
c.ChainHash[:],
c.Complete,
)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *ReplyShortChanIDsEnd) MsgType() MessageType {
return MsgReplyShortChanIDsEnd
}
// MaxPayloadLength returns the maximum allowed payload size for a
// ReplyShortChanIDsEnd complete message observing the specified protocol
// version.
//
// This is part of the lnwire.Message interface.
func (c *ReplyShortChanIDsEnd) MaxPayloadLength(uint32) uint32 {
// 32 (chain hash) + 1 (complete)
return 33
}
package lnwire
import (
"io"
"github.com/btcsuite/btcd/btcec/v2"
)
// RevokeAndAck is sent by either side once a CommitSig message has been
// received, and validated. This message serves to revoke the prior commitment
// transaction, which was the most up to date version until a CommitSig message
// referencing the specified ChannelPoint was received. Additionally, this
// message also piggyback's the next revocation hash that Alice should use when
// constructing the Bob's version of the next commitment transaction (which
// would be done before sending a CommitSig message). This piggybacking allows
// Alice to send the next CommitSig message modifying Bob's commitment
// transaction without first asking for a revocation hash initially.
type RevokeAndAck struct {
// ChanID uniquely identifies to which currently active channel this
// RevokeAndAck applies to.
ChanID ChannelID
// Revocation is the preimage to the revocation hash of the now prior
// commitment transaction.
Revocation [32]byte
// NextRevocationKey is the next commitment point which should be used
// for the next commitment transaction the remote peer creates for us.
// This, in conjunction with revocation base point will be used to
// create the proper revocation key used within the commitment
// transaction.
NextRevocationKey *btcec.PublicKey
}
// NewRevokeAndAck creates a new RevokeAndAck message.
func NewRevokeAndAck() *RevokeAndAck {
return &RevokeAndAck{}
}
// A compile time check to ensure RevokeAndAck implements the lnwire.Message
// interface.
var _ Message = (*RevokeAndAck)(nil)
// Decode deserializes a serialized RevokeAndAck message stored in the
// passed io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *RevokeAndAck) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&c.ChanID,
c.Revocation[:],
&c.NextRevocationKey,
)
}
// Encode serializes the target RevokeAndAck into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *RevokeAndAck) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
c.ChanID,
c.Revocation[:],
c.NextRevocationKey,
)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *RevokeAndAck) MsgType() MessageType {
return MsgRevokeAndAck
}
// MaxPayloadLength returns the maximum allowed payload size for a RevokeAndAck
// complete message observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *RevokeAndAck) MaxPayloadLength(uint32) uint32 {
// 32 + 32 + 33
return 97
}
// TargetChanID returns the channel id of the link for which this message is
// intended.
//
// NOTE: Part of peer.LinkUpdater interface.
func (c *RevokeAndAck) TargetChanID() ChannelID {
return c.ChanID
}
package lnwire
import (
"fmt"
)
// ShortChannelID represents the set of data which is needed to retrieve all
// necessary data to validate the channel existence.
type ShortChannelID struct {
// BlockHeight is the height of the block where funding transaction
// located.
//
// NOTE: This field is limited to 3 bytes.
BlockHeight uint32
// TxIndex is a position of funding transaction within a block.
//
// NOTE: This field is limited to 3 bytes.
TxIndex uint32
// TxPosition indicating transaction output which pays to the channel.
TxPosition uint16
}
// NewShortChanIDFromInt returns a new ShortChannelID which is the decoded
// version of the compact channel ID encoded within the uint64. The format of
// the compact channel ID is as follows: 3 bytes for the block height, 3 bytes
// for the transaction index, and 2 bytes for the output index.
func NewShortChanIDFromInt(chanID uint64) ShortChannelID {
return ShortChannelID{
BlockHeight: uint32(chanID >> 40),
TxIndex: uint32(chanID>>16) & 0xFFFFFF,
TxPosition: uint16(chanID),
}
}
// ToUint64 converts the ShortChannelID into a compact format encoded within a
// uint64 (8 bytes).
func (c ShortChannelID) ToUint64() uint64 {
// TODO(roasbeef): explicit error on overflow?
return ((uint64(c.BlockHeight) << 40) | (uint64(c.TxIndex) << 16) |
(uint64(c.TxPosition)))
}
// String generates a human-readable representation of the channel ID.
func (c ShortChannelID) String() string {
return fmt.Sprintf("%d:%d:%d", c.BlockHeight, c.TxIndex, c.TxPosition)
}
package lnwire
import (
"io"
)
// Shutdown is sent by either side in order to initiate the cooperative closure
// of a channel. This message is sparse as both sides implicitly have the
// information necessary to construct a transaction that will send the settled
// funds of both parties to the final delivery addresses negotiated during the
// funding workflow.
type Shutdown struct {
// ChannelID serves to identify which channel is to be closed.
ChannelID ChannelID
// Address is the script to which the channel funds will be paid.
Address DeliveryAddress
}
// DeliveryAddress is used to communicate the address to which funds from a
// closed channel should be sent. The address can be a p2wsh, p2pkh, p2sh or
// p2wpkh.
type DeliveryAddress []byte
// deliveryAddressMaxSize is the maximum expected size in bytes of a
// DeliveryAddress based on the types of scripts we know.
// Following are the known scripts and their sizes in bytes.
// - pay to witness script hash: 34
// - pay to pubkey hash: 25
// - pay to script hash: 22
// - pay to witness pubkey hash: 22.
const deliveryAddressMaxSize = 34
// NewShutdown creates a new Shutdown message.
func NewShutdown(cid ChannelID, addr DeliveryAddress) *Shutdown {
return &Shutdown{
ChannelID: cid,
Address: addr,
}
}
// A compile-time check to ensure Shutdown implements the lnwire.Message
// interface.
var _ Message = (*Shutdown)(nil)
// Decode deserializes a serialized Shutdown stored in the passed io.Reader
// observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (s *Shutdown) Decode(r io.Reader, pver uint32) error {
return ReadElements(r, &s.ChannelID, &s.Address)
}
// Encode serializes the target Shutdown into the passed io.Writer observing
// the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (s *Shutdown) Encode(w io.Writer, pver uint32) error {
return WriteElements(w, s.ChannelID, s.Address)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (s *Shutdown) MsgType() MessageType {
return MsgShutdown
}
// MaxPayloadLength returns the maximum allowed payload size for this message
// observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (s *Shutdown) MaxPayloadLength(pver uint32) uint32 {
var length uint32
// ChannelID - 32bytes
length += 32
// Len - 2 bytes
length += 2
// ScriptPubKey - maximum delivery address size.
length += deliveryAddressMaxSize
return length
}
package lnwire
import (
"fmt"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/lightningnetwork/lnd/input"
)
// Sig is a fixed-sized ECDSA signature. Unlike Bitcoin, we use fixed sized
// signatures on the wire, instead of DER encoded signatures. This type
// provides several methods to convert to/from a regular Bitcoin DER encoded
// signature (raw bytes and *ecdsa.Signature).
type Sig [64]byte
// NewSigFromRawSignature returns a Sig from a Bitcoin raw signature encoded in
// the canonical DER encoding.
func NewSigFromRawSignature(sig []byte) (Sig, error) {
var b Sig
if len(sig) == 0 {
return b, fmt.Errorf("cannot decode empty signature")
}
// Extract lengths of R and S. The DER representation is laid out as
// 0x30 <length> 0x02 <length r> r 0x02 <length s> s
// which means the length of R is the 4th byte and the length of S
// is the second byte after R ends. 0x02 signifies a length-prefixed,
// zero-padded, big-endian bigint. 0x30 signifies a DER signature.
// See the Serialize() method for ecdsa.Signature for details.
rLen := sig[3]
sLen := sig[5+rLen]
// Check to make sure R and S can both fit into their intended buffers.
// We check S first because these code blocks decrement sLen and rLen
// in the case of a 33-byte 0-padded integer returned from Serialize()
// and rLen is used in calculating array indices for S. We can track
// this with additional variables, but it's more efficient to just
// check S first.
if sLen > 32 {
if (sLen > 33) || (sig[6+rLen] != 0x00) {
return b, fmt.Errorf("S is over 32 bytes long " +
"without padding")
}
sLen--
copy(b[64-sLen:], sig[7+rLen:])
} else {
copy(b[64-sLen:], sig[6+rLen:])
}
// Do the same for R as we did for S
if rLen > 32 {
if (rLen > 33) || (sig[4] != 0x00) {
return b, fmt.Errorf("R is over 32 bytes long " +
"without padding")
}
rLen--
copy(b[32-rLen:], sig[5:5+rLen])
} else {
copy(b[32-rLen:], sig[4:4+rLen])
}
return b, nil
}
// NewSigFromSignature creates a new signature as used on the wire, from an
// existing ecdsa.Signature.
func NewSigFromSignature(e input.Signature) (Sig, error) {
if e == nil {
return Sig{}, fmt.Errorf("cannot decode empty signature")
}
// Serialize the signature with all the checks that entails.
return NewSigFromRawSignature(e.Serialize())
}
// ToSignature converts the fixed-sized signature to a ecdsa.Signature objects
// which can be used for signature validation checks.
func (b *Sig) ToSignature() (*ecdsa.Signature, error) {
// Parse the signature with strict checks.
sigBytes := b.ToSignatureBytes()
sig, err := ecdsa.ParseDERSignature(sigBytes)
if err != nil {
return nil, err
}
return sig, nil
}
// ToSignatureBytes serializes the target fixed-sized signature into the raw
// bytes of a DER encoding.
func (b *Sig) ToSignatureBytes() []byte {
// Extract canonically-padded bigint representations from buffer
r := extractCanonicalPadding(b[0:32])
s := extractCanonicalPadding(b[32:64])
rLen := uint8(len(r))
sLen := uint8(len(s))
// Create a canonical serialized signature. DER format is:
// 0x30 <length> 0x02 <length r> r 0x02 <length s> s
sigBytes := make([]byte, 6+rLen+sLen)
sigBytes[0] = 0x30 // DER signature magic value
sigBytes[1] = 4 + rLen + sLen // Length of rest of signature
sigBytes[2] = 0x02 // Big integer magic value
sigBytes[3] = rLen // Length of R
sigBytes[rLen+4] = 0x02 // Big integer magic value
sigBytes[rLen+5] = sLen // Length of S
copy(sigBytes[4:], r) // Copy R
copy(sigBytes[rLen+6:], s) // Copy S
return sigBytes
}
// extractCanonicalPadding is a utility function to extract the canonical
// padding of a big-endian integer from the wire encoding (a 0-padded
// big-endian integer) such that it passes btcec.canonicalPadding test.
func extractCanonicalPadding(b []byte) []byte {
for i := 0; i < len(b); i++ {
// Found first non-zero byte.
if b[i] > 0 {
// If the MSB is set, we need zero padding.
if b[i]&0x80 == 0x80 {
return append([]byte{0x00}, b[i:]...)
}
return b[i:]
}
}
return []byte{0x00}
}
package lnwire
import (
"io"
"github.com/lightningnetwork/lnd/tlv"
)
// TrueBoolean is a record that indicates true or false using the presence of
// the record. If the record is absent, it indicates false. If it is present,
// it indicates true.
type TrueBoolean struct{}
// Record returns the tlv record for the boolean entry.
func (b *TrueBoolean) Record() tlv.Record {
return tlv.MakeStaticRecord(
0, b, 0, booleanEncoder, booleanDecoder,
)
}
func booleanEncoder(_ io.Writer, val interface{}, _ *[8]byte) error {
if _, ok := val.(*TrueBoolean); ok {
return nil
}
return tlv.NewTypeForEncodingErr(val, "TrueBoolean")
}
func booleanDecoder(_ io.Reader, val interface{}, _ *[8]byte,
l uint64) error {
if _, ok := val.(*TrueBoolean); ok && (l == 0 || l == 1) {
return nil
}
return tlv.NewTypeForEncodingErr(val, "TrueBoolean")
}
package lnwire
import (
"io"
)
// OnionPacketSize is the size of the serialized Sphinx onion packet included
// in each UpdateAddHTLC message. The breakdown of the onion packet is as
// follows: 1-byte version, 33-byte ephemeral public key (for ECDH), 1300-bytes
// of per-hop data, and a 32-byte HMAC over the entire packet.
const OnionPacketSize = 1366
// UpdateAddHTLC is the message sent by Alice to Bob when she wishes to add an
// HTLC to his remote commitment transaction. In addition to information
// detailing the value, the ID, expiry, and the onion blob is also included
// which allows Bob to derive the next hop in the route. The HTLC added by this
// message is to be added to the remote node's "pending" HTLC's. A subsequent
// CommitSig message will move the pending HTLC to the newly created commitment
// transaction, marking them as "staged".
type UpdateAddHTLC struct {
// ChanID is the particular active channel that this UpdateAddHTLC is
// bound to.
ChanID ChannelID
// ID is the identification server for this HTLC. This value is
// explicitly included as it allows nodes to survive single-sided
// restarts. The ID value for this sides starts at zero, and increases
// with each offered HTLC.
ID uint64
// Amount is the amount of millisatoshis this HTLC is worth.
Amount MilliSatoshi
// PaymentHash is the payment hash to be included in the HTLC this
// request creates. The pre-image to this HTLC must be revealed by the
// upstream peer in order to fully settle the HTLC.
PaymentHash [32]byte
// Expiry is the number of blocks after which this HTLC should expire.
// It is the receiver's duty to ensure that the outgoing HTLC has a
// sufficient expiry value to allow her to redeem the incoming HTLC.
Expiry uint32
// OnionBlob is the raw serialized mix header used to route an HTLC in
// a privacy-preserving manner. The mix header is defined currently to
// be parsed as a 4-tuple: (groupElement, routingInfo, headerMAC,
// body). First the receiving node should use the groupElement, and
// its current onion key to derive a shared secret with the source.
// Once the shared secret has been derived, the headerMAC should be
// checked FIRST. Note that the MAC only covers the routingInfo field.
// If the MAC matches, and the shared secret is fresh, then the node
// should strip off a layer of encryption, exposing the next hop to be
// used in the subsequent UpdateAddHTLC message.
OnionBlob [OnionPacketSize]byte
}
// NewUpdateAddHTLC returns a new empty UpdateAddHTLC message.
func NewUpdateAddHTLC() *UpdateAddHTLC {
return &UpdateAddHTLC{}
}
// A compile time check to ensure UpdateAddHTLC implements the lnwire.Message
// interface.
var _ Message = (*UpdateAddHTLC)(nil)
// Decode deserializes a serialized UpdateAddHTLC message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *UpdateAddHTLC) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&c.ChanID,
&c.ID,
&c.Amount,
c.PaymentHash[:],
&c.Expiry,
c.OnionBlob[:],
)
}
// Encode serializes the target UpdateAddHTLC into the passed io.Writer observing
// the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *UpdateAddHTLC) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
c.ChanID,
c.ID,
c.Amount,
c.PaymentHash[:],
c.Expiry,
c.OnionBlob[:],
)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *UpdateAddHTLC) MsgType() MessageType {
return MsgUpdateAddHTLC
}
// MaxPayloadLength returns the maximum allowed payload size for an UpdateAddHTLC
// complete message observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *UpdateAddHTLC) MaxPayloadLength(uint32) uint32 {
// 1450
return 32 + 8 + 4 + 8 + 32 + 1366
}
// TargetChanID returns the channel id of the link for which this message is
// intended.
//
// NOTE: Part of peer.LinkUpdater interface.
func (c *UpdateAddHTLC) TargetChanID() ChannelID {
return c.ChanID
}
package lnwire
import (
"io"
)
// OpaqueReason is an opaque encrypted byte slice that encodes the exact
// failure reason and additional some supplemental data. The contents of this
// slice can only be decrypted by the sender of the original HTLC.
type OpaqueReason []byte
// UpdateFailHTLC is sent by Alice to Bob in order to remove a previously added
// HTLC. Upon receipt of an UpdateFailHTLC the HTLC should be removed from the
// next commitment transaction, with the UpdateFailHTLC propagated backwards in
// the route to fully undo the HTLC.
type UpdateFailHTLC struct {
// ChanID is the particular active channel that this
// UpdateFailHTLC is bound to.
ChanID ChannelID
// ID references which HTLC on the remote node's commitment transaction
// has timed out.
ID uint64
// Reason is an onion-encrypted blob that details why the HTLC was
// failed. This blob is only fully decryptable by the initiator of the
// HTLC message.
Reason OpaqueReason
}
// A compile time check to ensure UpdateFailHTLC implements the lnwire.Message
// interface.
var _ Message = (*UpdateFailHTLC)(nil)
// Decode deserializes a serialized UpdateFailHTLC message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFailHTLC) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&c.ChanID,
&c.ID,
&c.Reason,
)
}
// Encode serializes the target UpdateFailHTLC into the passed io.Writer observing
// the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFailHTLC) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
c.ChanID,
c.ID,
c.Reason,
)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFailHTLC) MsgType() MessageType {
return MsgUpdateFailHTLC
}
// MaxPayloadLength returns the maximum allowed payload size for an UpdateFailHTLC
// complete message observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFailHTLC) MaxPayloadLength(uint32) uint32 {
var length uint32
// Length of the ChanID
length += 32
// Length of the ID
length += 8
// Length of the length opaque reason
length += 2
// Length of the Reason
length += 292
return length
}
// TargetChanID returns the channel id of the link for which this message is
// intended.
//
// NOTE: Part of peer.LinkUpdater interface.
func (c *UpdateFailHTLC) TargetChanID() ChannelID {
return c.ChanID
}
package lnwire
import (
"crypto/sha256"
"io"
)
// UpdateFailMalformedHTLC is sent by either the payment forwarder or by
// payment receiver to the payment sender in order to notify it that the onion
// blob can't be parsed. For that reason we send this message instead of
// obfuscate the onion failure.
type UpdateFailMalformedHTLC struct {
// ChanID is the particular active channel that this
// UpdateFailMalformedHTLC is bound to.
ChanID ChannelID
// ID references which HTLC on the remote node's commitment transaction
// has timed out.
ID uint64
// ShaOnionBlob hash of the onion blob on which can't be parsed by the
// node in the payment path.
ShaOnionBlob [sha256.Size]byte
// FailureCode the exact reason why onion blob haven't been parsed.
FailureCode FailCode
}
// A compile time check to ensure UpdateFailMalformedHTLC implements the
// lnwire.Message interface.
var _ Message = (*UpdateFailMalformedHTLC)(nil)
// Decode deserializes a serialized UpdateFailMalformedHTLC message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFailMalformedHTLC) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&c.ChanID,
&c.ID,
c.ShaOnionBlob[:],
&c.FailureCode,
)
}
// Encode serializes the target UpdateFailMalformedHTLC into the passed
// io.Writer observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFailMalformedHTLC) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
c.ChanID,
c.ID,
c.ShaOnionBlob[:],
c.FailureCode,
)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFailMalformedHTLC) MsgType() MessageType {
return MsgUpdateFailMalformedHTLC
}
// MaxPayloadLength returns the maximum allowed payload size for a
// UpdateFailMalformedHTLC complete message observing the specified protocol
// version.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFailMalformedHTLC) MaxPayloadLength(uint32) uint32 {
// 32 + 8 + 32 + 2
return 74
}
// TargetChanID returns the channel id of the link for which this message is
// intended.
//
// NOTE: Part of peer.LinkUpdater interface.
func (c *UpdateFailMalformedHTLC) TargetChanID() ChannelID {
return c.ChanID
}
package lnwire
import (
"io"
)
// UpdateFee is the message the channel initiator sends to the other peer if
// the channel commitment fee needs to be updated.
type UpdateFee struct {
// ChanID is the channel that this UpdateFee is meant for.
ChanID ChannelID
// FeePerKw is the fee-per-kw on commit transactions that the sender of
// this message wants to use for this channel.
//
// TODO(halseth): make SatPerKWeight when fee estimation is moved to
// own package. Currently this will cause an import cycle.
FeePerKw uint32
}
// NewUpdateFee creates a new UpdateFee message.
func NewUpdateFee(chanID ChannelID, feePerKw uint32) *UpdateFee {
return &UpdateFee{
ChanID: chanID,
FeePerKw: feePerKw,
}
}
// A compile time check to ensure UpdateFee implements the lnwire.Message
// interface.
var _ Message = (*UpdateFee)(nil)
// Decode deserializes a serialized UpdateFee message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFee) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&c.ChanID,
&c.FeePerKw,
)
}
// Encode serializes the target UpdateFee into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFee) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
c.ChanID,
c.FeePerKw,
)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFee) MsgType() MessageType {
return MsgUpdateFee
}
// MaxPayloadLength returns the maximum allowed payload size for an UpdateFee
// complete message observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFee) MaxPayloadLength(uint32) uint32 {
// 32 + 4
return 36
}
// TargetChanID returns the channel id of the link for which this message is
// intended.
//
// NOTE: Part of peer.LinkUpdater interface.
func (c *UpdateFee) TargetChanID() ChannelID {
return c.ChanID
}
package lnwire
import (
"io"
)
// UpdateFulfillHTLC is sent by Alice to Bob when she wishes to settle a
// particular HTLC referenced by its HTLCKey within a specific active channel
// referenced by ChannelPoint. A subsequent CommitSig message will be sent by
// Alice to "lock-in" the removal of the specified HTLC, possible containing a
// batch signature covering several settled HTLC's.
type UpdateFulfillHTLC struct {
// ChanID references an active channel which holds the HTLC to be
// settled.
ChanID ChannelID
// ID denotes the exact HTLC stage within the receiving node's
// commitment transaction to be removed.
ID uint64
// PaymentPreimage is the R-value preimage required to fully settle an
// HTLC.
PaymentPreimage [32]byte
}
// NewUpdateFulfillHTLC returns a new empty UpdateFulfillHTLC.
func NewUpdateFulfillHTLC(chanID ChannelID, id uint64,
preimage [32]byte) *UpdateFulfillHTLC {
return &UpdateFulfillHTLC{
ChanID: chanID,
ID: id,
PaymentPreimage: preimage,
}
}
// A compile time check to ensure UpdateFulfillHTLC implements the lnwire.Message
// interface.
var _ Message = (*UpdateFulfillHTLC)(nil)
// Decode deserializes a serialized UpdateFulfillHTLC message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFulfillHTLC) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&c.ChanID,
&c.ID,
c.PaymentPreimage[:],
)
}
// Encode serializes the target UpdateFulfillHTLC into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFulfillHTLC) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
c.ChanID,
c.ID,
c.PaymentPreimage[:],
)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFulfillHTLC) MsgType() MessageType {
return MsgUpdateFulfillHTLC
}
// MaxPayloadLength returns the maximum allowed payload size for an UpdateFulfillHTLC
// complete message observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFulfillHTLC) MaxPayloadLength(uint32) uint32 {
// 32 + 8 + 32
return 72
}
// TargetChanID returns the channel id of the link for which this message is
// intended.
//
// NOTE: Part of peer.LinkUpdater interface.
func (c *UpdateFulfillHTLC) TargetChanID() ChannelID {
return c.ChanID
}
package migration
import "github.com/btcsuite/btclog/v2"
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration12
import (
"bytes"
"encoding/binary"
"io"
"time"
"github.com/btcsuite/btcd/wire"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// MaxMemoSize is maximum size of the memo field within invoices stored
// in the database.
MaxMemoSize = 1024
// maxReceiptSize is the maximum size of the payment receipt stored
// within the database along side incoming/outgoing invoices.
maxReceiptSize = 1024
// MaxPaymentRequestSize is the max size of a payment request for
// this invoice.
// TODO(halseth): determine the max length payment request when field
// lengths are final.
MaxPaymentRequestSize = 4096
memoType tlv.Type = 0
payReqType tlv.Type = 1
createTimeType tlv.Type = 2
settleTimeType tlv.Type = 3
addIndexType tlv.Type = 4
settleIndexType tlv.Type = 5
preimageType tlv.Type = 6
valueType tlv.Type = 7
cltvDeltaType tlv.Type = 8
expiryType tlv.Type = 9
paymentAddrType tlv.Type = 10
featuresType tlv.Type = 11
invStateType tlv.Type = 12
amtPaidType tlv.Type = 13
)
var (
// invoiceBucket is the name of the bucket within the database that
// stores all data related to invoices no matter their final state.
// Within the invoice bucket, each invoice is keyed by its invoice ID
// which is a monotonically increasing uint32.
invoiceBucket = []byte("invoices")
// Big endian is the preferred byte order, due to cursor scans over
// integer keys iterating in order.
byteOrder = binary.BigEndian
)
// ContractState describes the state the invoice is in.
type ContractState uint8
// ContractTerm is a companion struct to the Invoice struct. This struct houses
// the necessary conditions required before the invoice can be considered fully
// settled by the payee.
type ContractTerm struct {
// PaymentPreimage is the preimage which is to be revealed in the
// occasion that an HTLC paying to the hash of this preimage is
// extended.
PaymentPreimage lntypes.Preimage
// Value is the expected amount of milli-satoshis to be paid to an HTLC
// which can be satisfied by the above preimage.
Value lnwire.MilliSatoshi
// State describes the state the invoice is in.
State ContractState
// PaymentAddr is a randomly generated value include in the MPP record
// by the sender to prevent probing of the receiver.
PaymentAddr [32]byte
// Features is the feature vectors advertised on the payment request.
Features *lnwire.FeatureVector
}
// Invoice is a payment invoice generated by a payee in order to request
// payment for some good or service. The inclusion of invoices within Lightning
// creates a payment work flow for merchants very similar to that of the
// existing financial system within PayPal, etc. Invoices are added to the
// database when a payment is requested, then can be settled manually once the
// payment is received at the upper layer. For record keeping purposes,
// invoices are never deleted from the database, instead a bit is toggled
// denoting the invoice has been fully settled. Within the database, all
// invoices must have a unique payment hash which is generated by taking the
// sha256 of the payment preimage.
type Invoice struct {
// Memo is an optional memo to be stored along side an invoice. The
// memo may contain further details pertaining to the invoice itself,
// or any other message which fits within the size constraints.
Memo []byte
// PaymentRequest is an optional field where a payment request created
// for this invoice can be stored.
PaymentRequest []byte
// FinalCltvDelta is the minimum required number of blocks before htlc
// expiry when the invoice is accepted.
FinalCltvDelta int32
// Expiry defines how long after creation this invoice should expire.
Expiry time.Duration
// CreationDate is the exact time the invoice was created.
CreationDate time.Time
// SettleDate is the exact time the invoice was settled.
SettleDate time.Time
// Terms are the contractual payment terms of the invoice. Once all the
// terms have been satisfied by the payer, then the invoice can be
// considered fully fulfilled.
//
// TODO(roasbeef): later allow for multiple terms to fulfill the final
// invoice: payment fragmentation, etc.
Terms ContractTerm
// AddIndex is an auto-incrementing integer that acts as a
// monotonically increasing sequence number for all invoices created.
// Clients can then use this field as a "checkpoint" of sorts when
// implementing a streaming RPC to notify consumers of instances where
// an invoice has been added before they re-connected.
//
// NOTE: This index starts at 1.
AddIndex uint64
// SettleIndex is an auto-incrementing integer that acts as a
// monotonically increasing sequence number for all settled invoices.
// Clients can then use this field as a "checkpoint" of sorts when
// implementing a streaming RPC to notify consumers of instances where
// an invoice has been settled before they re-connected.
//
// NOTE: This index starts at 1.
SettleIndex uint64
// AmtPaid is the final amount that we ultimately accepted for pay for
// this invoice. We specify this value independently as it's possible
// that the invoice originally didn't specify an amount, or the sender
// overpaid.
AmtPaid lnwire.MilliSatoshi
// Htlcs records all htlcs that paid to this invoice. Some of these
// htlcs may have been marked as canceled.
Htlcs []byte
}
// LegacyDeserializeInvoice decodes an invoice from the passed io.Reader using
// the pre-TLV serialization.
func LegacyDeserializeInvoice(r io.Reader) (Invoice, error) {
var err error
invoice := Invoice{}
// TODO(roasbeef): use read full everywhere
invoice.Memo, err = wire.ReadVarBytes(r, 0, MaxMemoSize, "")
if err != nil {
return invoice, err
}
_, err = wire.ReadVarBytes(r, 0, maxReceiptSize, "")
if err != nil {
return invoice, err
}
invoice.PaymentRequest, err = wire.ReadVarBytes(r, 0, MaxPaymentRequestSize, "")
if err != nil {
return invoice, err
}
if err := binary.Read(r, byteOrder, &invoice.FinalCltvDelta); err != nil {
return invoice, err
}
var expiry int64
if err := binary.Read(r, byteOrder, &expiry); err != nil {
return invoice, err
}
invoice.Expiry = time.Duration(expiry)
birthBytes, err := wire.ReadVarBytes(r, 0, 300, "birth")
if err != nil {
return invoice, err
}
if err := invoice.CreationDate.UnmarshalBinary(birthBytes); err != nil {
return invoice, err
}
settledBytes, err := wire.ReadVarBytes(r, 0, 300, "settled")
if err != nil {
return invoice, err
}
if err := invoice.SettleDate.UnmarshalBinary(settledBytes); err != nil {
return invoice, err
}
if _, err := io.ReadFull(r, invoice.Terms.PaymentPreimage[:]); err != nil {
return invoice, err
}
var scratch [8]byte
if _, err := io.ReadFull(r, scratch[:]); err != nil {
return invoice, err
}
invoice.Terms.Value = lnwire.MilliSatoshi(byteOrder.Uint64(scratch[:]))
if err := binary.Read(r, byteOrder, &invoice.Terms.State); err != nil {
return invoice, err
}
if err := binary.Read(r, byteOrder, &invoice.AddIndex); err != nil {
return invoice, err
}
if err := binary.Read(r, byteOrder, &invoice.SettleIndex); err != nil {
return invoice, err
}
if err := binary.Read(r, byteOrder, &invoice.AmtPaid); err != nil {
return invoice, err
}
invoice.Htlcs, err = deserializeHtlcs(r)
if err != nil {
return Invoice{}, err
}
return invoice, nil
}
// deserializeHtlcs reads a list of invoice htlcs from a reader and returns it
// as a flattened byte slice.
func deserializeHtlcs(r io.Reader) ([]byte, error) {
var b bytes.Buffer
_, err := io.Copy(&b, r)
return b.Bytes(), err
}
// SerializeInvoice serializes an invoice to a writer.
//
// nolint: dupl
func SerializeInvoice(w io.Writer, i *Invoice) error {
creationDateBytes, err := i.CreationDate.MarshalBinary()
if err != nil {
return err
}
settleDateBytes, err := i.SettleDate.MarshalBinary()
if err != nil {
return err
}
var fb bytes.Buffer
err = i.Terms.Features.EncodeBase256(&fb)
if err != nil {
return err
}
featureBytes := fb.Bytes()
preimage := [32]byte(i.Terms.PaymentPreimage)
value := uint64(i.Terms.Value)
cltvDelta := uint32(i.FinalCltvDelta)
expiry := uint64(i.Expiry)
amtPaid := uint64(i.AmtPaid)
state := uint8(i.Terms.State)
tlvStream, err := tlv.NewStream(
// Memo and payreq.
tlv.MakePrimitiveRecord(memoType, &i.Memo),
tlv.MakePrimitiveRecord(payReqType, &i.PaymentRequest),
// Add/settle metadata.
tlv.MakePrimitiveRecord(createTimeType, &creationDateBytes),
tlv.MakePrimitiveRecord(settleTimeType, &settleDateBytes),
tlv.MakePrimitiveRecord(addIndexType, &i.AddIndex),
tlv.MakePrimitiveRecord(settleIndexType, &i.SettleIndex),
// Terms.
tlv.MakePrimitiveRecord(preimageType, &preimage),
tlv.MakePrimitiveRecord(valueType, &value),
tlv.MakePrimitiveRecord(cltvDeltaType, &cltvDelta),
tlv.MakePrimitiveRecord(expiryType, &expiry),
tlv.MakePrimitiveRecord(paymentAddrType, &i.Terms.PaymentAddr),
tlv.MakePrimitiveRecord(featuresType, &featureBytes),
// Invoice state.
tlv.MakePrimitiveRecord(invStateType, &state),
tlv.MakePrimitiveRecord(amtPaidType, &amtPaid),
)
if err != nil {
return err
}
var b bytes.Buffer
if err = tlvStream.Encode(&b); err != nil {
return err
}
err = binary.Write(w, byteOrder, uint64(b.Len()))
if err != nil {
return err
}
if _, err = w.Write(b.Bytes()); err != nil {
return err
}
return serializeHtlcs(w, i.Htlcs)
}
// serializeHtlcs writes a serialized list of invoice htlcs into a writer.
func serializeHtlcs(w io.Writer, htlcs []byte) error {
_, err := w.Write(htlcs)
return err
}
package migration12
import (
"github.com/btcsuite/btclog/v2"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration12
import (
"bytes"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
"github.com/lightningnetwork/lnd/kvdb"
)
var emptyFeatures = lnwire.NewFeatureVector(nil, nil)
// MigrateInvoiceTLV migrates all existing invoice bodies over to be serialized
// in a single TLV stream. In the process, we drop the Receipt field and add
// PaymentAddr and Features to the invoice Terms.
func MigrateInvoiceTLV(tx kvdb.RwTx) error {
log.Infof("Migrating invoice bodies to TLV, " +
"adding payment addresses and feature vectors.")
invoiceB := tx.ReadWriteBucket(invoiceBucket)
if invoiceB == nil {
return nil
}
type keyedInvoice struct {
key []byte
invoice Invoice
}
// Read in all existing invoices using the old format.
var invoices []keyedInvoice
err := invoiceB.ForEach(func(k, v []byte) error {
if v == nil {
return nil
}
invoiceReader := bytes.NewReader(v)
invoice, err := LegacyDeserializeInvoice(invoiceReader)
if err != nil {
return err
}
// Insert an empty feature vector on all old payments.
invoice.Terms.Features = emptyFeatures
invoices = append(invoices, keyedInvoice{
key: k,
invoice: invoice,
})
return nil
})
if err != nil {
return err
}
// Write out each one under its original key using TLV.
for _, ki := range invoices {
var b bytes.Buffer
err = SerializeInvoice(&b, &ki.invoice)
if err != nil {
return err
}
err = invoiceB.Put(ki.key, b.Bytes())
if err != nil {
return err
}
}
log.Infof("Migration to TLV invoice bodies, " +
"payment address, and features complete!")
return nil
}
package migration13
import (
"github.com/btcsuite/btclog/v2"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration13
import (
"encoding/binary"
"fmt"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
paymentsRootBucket = []byte("payments-root-bucket")
// paymentCreationInfoKey is a key used in the payment's sub-bucket to
// store the creation info of the payment.
paymentCreationInfoKey = []byte("payment-creation-info")
// paymentFailInfoKey is a key used in the payment's sub-bucket to
// store information about the reason a payment failed.
paymentFailInfoKey = []byte("payment-fail-info")
// paymentAttemptInfoKey is a key used in the payment's sub-bucket to
// store the info about the latest attempt that was done for the
// payment in question.
paymentAttemptInfoKey = []byte("payment-attempt-info")
// paymentSettleInfoKey is a key used in the payment's sub-bucket to
// store the settle info of the payment.
paymentSettleInfoKey = []byte("payment-settle-info")
// paymentHtlcsBucket is a bucket where we'll store the information
// about the HTLCs that were attempted for a payment.
paymentHtlcsBucket = []byte("payment-htlcs-bucket")
// htlcAttemptInfoKey is a key used in a HTLC's sub-bucket to store the
// info about the attempt that was done for the HTLC in question.
htlcAttemptInfoKey = []byte("htlc-attempt-info")
// htlcSettleInfoKey is a key used in a HTLC's sub-bucket to store the
// settle info, if any.
htlcSettleInfoKey = []byte("htlc-settle-info")
// htlcFailInfoKey is a key used in a HTLC's sub-bucket to store
// failure information, if any.
htlcFailInfoKey = []byte("htlc-fail-info")
byteOrder = binary.BigEndian
)
// MigrateMPP migrates the payments to a new structure that accommodates for mpp
// payments.
func MigrateMPP(tx kvdb.RwTx) error {
log.Infof("Migrating payments to mpp structure")
// Iterate over all payments and store their indexing keys. This is
// needed, because no modifications are allowed inside a Bucket.ForEach
// loop.
paymentsBucket := tx.ReadWriteBucket(paymentsRootBucket)
if paymentsBucket == nil {
return nil
}
var paymentKeys [][]byte
err := paymentsBucket.ForEach(func(k, v []byte) error {
paymentKeys = append(paymentKeys, k)
return nil
})
if err != nil {
return err
}
// With all keys retrieved, start the migration.
for _, k := range paymentKeys {
bucket := paymentsBucket.NestedReadWriteBucket(k)
// We only expect sub-buckets to be found in
// this top-level bucket.
if bucket == nil {
return fmt.Errorf("non bucket element in " +
"payments bucket")
}
// Fetch old format creation info.
creationInfo := bucket.Get(paymentCreationInfoKey)
if creationInfo == nil {
return fmt.Errorf("creation info not found")
}
// Make a copy because bbolt doesn't allow this value to be
// changed in-place.
newCreationInfo := make([]byte, len(creationInfo))
copy(newCreationInfo, creationInfo)
// Convert to nano seconds.
timeBytes := newCreationInfo[32+8 : 32+8+8]
time := byteOrder.Uint64(timeBytes)
timeNs := time * 1000000000
byteOrder.PutUint64(timeBytes, timeNs)
// Write back new format creation info.
err := bucket.Put(paymentCreationInfoKey, newCreationInfo)
if err != nil {
return err
}
// No migration needed if there is no attempt stored.
attemptInfo := bucket.Get(paymentAttemptInfoKey)
if attemptInfo == nil {
continue
}
// Delete attempt info on the payment level.
if err := bucket.Delete(paymentAttemptInfoKey); err != nil {
return err
}
// Save attempt id for later use.
attemptID := attemptInfo[:8]
// Discard attempt id. It will become a bucket key in the new
// structure.
attemptInfo = attemptInfo[8:]
// Append unknown (zero) attempt time.
var zero [8]byte
attemptInfo = append(attemptInfo, zero[:]...)
// Create bucket that contains all htlcs.
htlcsBucket, err := bucket.CreateBucket(paymentHtlcsBucket)
if err != nil {
return err
}
// Create an htlc for this attempt.
htlcBucket, err := htlcsBucket.CreateBucket(attemptID)
if err != nil {
return err
}
// Save migrated attempt info.
err = htlcBucket.Put(htlcAttemptInfoKey, attemptInfo)
if err != nil {
return err
}
// Migrate settle info.
settleInfo := bucket.Get(paymentSettleInfoKey)
if settleInfo != nil {
// Payment-level settle info can be deleted.
err := bucket.Delete(paymentSettleInfoKey)
if err != nil {
return err
}
// Append unknown (zero) settle time.
settleInfo = append(settleInfo, zero[:]...)
// Save settle info.
err = htlcBucket.Put(htlcSettleInfoKey, settleInfo)
if err != nil {
return err
}
// Migration for settled htlc completed.
continue
}
// If there is no payment-level failure reason, the payment is
// still in flight and nothing else needs to be migrated.
// Otherwise the payment-level failure reason can remain
// unchanged.
inFlight := bucket.Get(paymentFailInfoKey) == nil
if inFlight {
continue
}
// The htlc failed. Add htlc fail info with reason unknown. We
// don't have access to the original failure reason anymore.
failInfo := []byte{
// Fail time unknown.
0, 0, 0, 0, 0, 0, 0, 0,
// Zero length wire message.
0,
// Failure reason unknown.
0,
// Failure source index zero.
0, 0, 0, 0,
}
// Save fail info.
err = htlcBucket.Put(htlcFailInfoKey, failInfo)
if err != nil {
return err
}
}
log.Infof("Migration of payments to mpp structure complete!")
return nil
}
package migration16
import (
"github.com/btcsuite/btclog/v2"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration16
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
paymentsRootBucket = []byte("payments-root-bucket")
paymentSequenceKey = []byte("payment-sequence-key")
duplicatePaymentsBucket = []byte("payment-duplicate-bucket")
paymentsIndexBucket = []byte("payments-index-bucket")
byteOrder = binary.BigEndian
)
// paymentIndexType indicates the type of index we have recorded in the payment
// indexes bucket.
type paymentIndexType uint8
// paymentIndexTypeHash is a payment index type which indicates that we have
// created an index of payment sequence number to payment hash.
const paymentIndexTypeHash paymentIndexType = 0
// paymentIndex stores all the information we require to create an index by
// sequence number for a payment.
type paymentIndex struct {
// paymentHash is the hash of the payment, which is its key in the
// payment root bucket.
paymentHash []byte
// sequenceNumbers is the set of sequence numbers associated with this
// payment hash. There will be more than one sequence number in the
// case where duplicate payments are present.
sequenceNumbers [][]byte
}
// MigrateSequenceIndex migrates the payments db to contain a new bucket which
// provides an index from sequence number to payment hash. This is required
// for more efficient sequential lookup of payments, which are keyed by payment
// hash before this migration.
func MigrateSequenceIndex(tx kvdb.RwTx) error {
log.Infof("Migrating payments to add sequence number index")
// Get a list of indices we need to write.
indexList, err := getPaymentIndexList(tx)
if err != nil {
return err
}
// Create the top level bucket that we will use to index payments in.
bucket, err := tx.CreateTopLevelBucket(paymentsIndexBucket)
if err != nil {
return err
}
// Write an index for each of our payments.
for _, index := range indexList {
// Write indexes for each of our sequence numbers.
for _, seqNr := range index.sequenceNumbers {
err := putIndex(bucket, seqNr, index.paymentHash)
if err != nil {
return err
}
}
}
return nil
}
// putIndex performs a sanity check that ensures we are not writing duplicate
// indexes to disk then creates the index provided.
func putIndex(bucket kvdb.RwBucket, sequenceNr, paymentHash []byte) error {
// Add a sanity check that we do not already have an entry with
// this sequence number.
existingEntry := bucket.Get(sequenceNr)
if existingEntry != nil {
return fmt.Errorf("sequence number: %x duplicated",
sequenceNr)
}
bytes, err := serializePaymentIndexEntry(paymentHash)
if err != nil {
return err
}
return bucket.Put(sequenceNr, bytes)
}
// serializePaymentIndexEntry serializes a payment hash typed index. The value
// produced contains a payment index type (which can be used in future to
// signal different payment index types) and the payment hash.
func serializePaymentIndexEntry(hash []byte) ([]byte, error) {
var b bytes.Buffer
err := binary.Write(&b, byteOrder, paymentIndexTypeHash)
if err != nil {
return nil, err
}
if err := wire.WriteVarBytes(&b, 0, hash); err != nil {
return nil, err
}
return b.Bytes(), nil
}
// getPaymentIndexList gets a list of indices we need to write for our current
// set of payments.
func getPaymentIndexList(tx kvdb.RTx) ([]paymentIndex, error) {
// Iterate over all payments and store their indexing keys. This is
// needed, because no modifications are allowed inside a Bucket.ForEach
// loop.
paymentsBucket := tx.ReadBucket(paymentsRootBucket)
if paymentsBucket == nil {
return nil, nil
}
var indexList []paymentIndex
err := paymentsBucket.ForEach(func(k, v []byte) error {
// Get the bucket which contains the payment, fail if the key
// does not have a bucket.
bucket := paymentsBucket.NestedReadBucket(k)
if bucket == nil {
return fmt.Errorf("non bucket element in " +
"payments bucket")
}
seqBytes := bucket.Get(paymentSequenceKey)
if seqBytes == nil {
return fmt.Errorf("nil sequence number bytes")
}
seqNrs, err := fetchSequenceNumbers(bucket)
if err != nil {
return err
}
// Create an index object with our payment hash and sequence
// numbers and append it to our set of indexes.
index := paymentIndex{
paymentHash: k,
sequenceNumbers: seqNrs,
}
indexList = append(indexList, index)
return nil
})
if err != nil {
return nil, err
}
return indexList, nil
}
// fetchSequenceNumbers fetches all the sequence numbers associated with a
// payment, including those belonging to any duplicate payments.
func fetchSequenceNumbers(paymentBucket kvdb.RBucket) ([][]byte, error) {
seqNum := paymentBucket.Get(paymentSequenceKey)
if seqNum == nil {
return nil, errors.New("expected sequence number")
}
sequenceNumbers := [][]byte{seqNum}
// Get the duplicate payments bucket, if it has no duplicates, just
// return early with the payment sequence number.
duplicates := paymentBucket.NestedReadBucket(duplicatePaymentsBucket)
if duplicates == nil {
return sequenceNumbers, nil
}
// If we do have duplicated, they are keyed by sequence number, so we
// iterate through the duplicates bucket and add them to our set of
// sequence numbers.
if err := duplicates.ForEach(func(k, v []byte) error {
sequenceNumbers = append(sequenceNumbers, k)
return nil
}); err != nil {
return nil, err
}
return sequenceNumbers, nil
}
package migration20
import (
"encoding/binary"
"io"
"github.com/btcsuite/btcd/wire"
)
var (
byteOrder = binary.BigEndian
)
// writeOutpoint writes an outpoint from the passed writer.
func writeOutpoint(w io.Writer, o *wire.OutPoint) error {
if _, err := w.Write(o.Hash[:]); err != nil {
return err
}
if err := binary.Write(w, byteOrder, o.Index); err != nil {
return err
}
return nil
}
// readOutpoint reads an outpoint from the passed reader.
func readOutpoint(r io.Reader, o *wire.OutPoint) error {
if _, err := io.ReadFull(r, o.Hash[:]); err != nil {
return err
}
if err := binary.Read(r, byteOrder, &o.Index); err != nil {
return err
}
return nil
}
package migration20
import (
"github.com/btcsuite/btclog/v2"
)
// log is a logger that is initialized as disabled. This means the package
// will not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration20
import (
"bytes"
"fmt"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/tlv"
)
var (
// openChanBucket stores all the open channel information.
openChanBucket = []byte("open-chan-bucket")
// closedChannelBucket stores all the closed channel information.
closedChannelBucket = []byte("closed-chan-bucket")
// outpointBucket is an index mapping outpoints to a tlv
// stream of channel data.
outpointBucket = []byte("outpoint-bucket")
)
const (
// A tlv type definition used to serialize an outpoint's indexStatus for
// use in the outpoint index.
indexStatusType tlv.Type = 0
)
// indexStatus is an enum-like type that describes what state the
// outpoint is in. Currently only two possible values.
type indexStatus uint8
const (
// outpointOpen represents an outpoint that is open in the outpoint index.
outpointOpen indexStatus = 0
// outpointClosed represents an outpoint that is closed in the outpoint
// index.
outpointClosed indexStatus = 1
)
// MigrateOutpointIndex populates the outpoint index with outpoints that
// the node already has. This takes every outpoint in the open channel
// bucket and every outpoint in the closed channel bucket and stores them
// in this index.
func MigrateOutpointIndex(tx kvdb.RwTx) error {
log.Info("Migrating to the outpoint index")
// First get the set of open outpoints.
openList, err := getOpenOutpoints(tx)
if err != nil {
return err
}
// Then get the set of closed outpoints.
closedList, err := getClosedOutpoints(tx)
if err != nil {
return err
}
// Get the outpoint bucket which was created in migration 19.
bucket := tx.ReadWriteBucket(outpointBucket)
// Store the set of open outpoints in the outpoint bucket.
if err := putOutpoints(bucket, openList, false); err != nil {
return err
}
// Store the set of closed outpoints in the outpoint bucket.
return putOutpoints(bucket, closedList, true)
}
// getOpenOutpoints traverses through the openChanBucket and returns the
// list of these channels' outpoints.
func getOpenOutpoints(tx kvdb.RwTx) ([]*wire.OutPoint, error) {
var ops []*wire.OutPoint
openBucket := tx.ReadBucket(openChanBucket)
if openBucket == nil {
return ops, nil
}
// Iterate through every node and chain bucket to get every open
// outpoint.
//
// The bucket tree:
// openChanBucket -> nodePub -> chainHash -> chanPoint
err := openBucket.ForEach(func(k, v []byte) error {
// Ensure that the key is the same size as a pubkey and the
// value is nil.
if len(k) != 33 || v != nil {
return nil
}
nodeBucket := openBucket.NestedReadBucket(k)
if nodeBucket == nil {
return nil
}
return nodeBucket.ForEach(func(k, v []byte) error {
// If there's a value it's not a bucket.
if v != nil {
return nil
}
chainBucket := nodeBucket.NestedReadBucket(k)
if chainBucket == nil {
return fmt.Errorf("unable to read "+
"bucket for chain: %x", k)
}
return chainBucket.ForEach(func(k, v []byte) error {
// If there's a value it's not a bucket.
if v != nil {
return nil
}
var op wire.OutPoint
r := bytes.NewReader(k)
if err := readOutpoint(r, &op); err != nil {
return err
}
ops = append(ops, &op)
return nil
})
})
})
if err != nil {
return nil, err
}
return ops, nil
}
// getClosedOutpoints traverses through the closedChanBucket and returns
// a list of closed outpoints.
func getClosedOutpoints(tx kvdb.RwTx) ([]*wire.OutPoint, error) {
var ops []*wire.OutPoint
closedBucket := tx.ReadBucket(closedChannelBucket)
if closedBucket == nil {
return ops, nil
}
// Iterate through every key-value pair to gather all outpoints.
err := closedBucket.ForEach(func(k, v []byte) error {
var op wire.OutPoint
r := bytes.NewReader(k)
if err := readOutpoint(r, &op); err != nil {
return err
}
ops = append(ops, &op)
return nil
})
if err != nil {
return nil, err
}
return ops, nil
}
// putOutpoints puts the set of outpoints into the outpoint bucket.
func putOutpoints(bucket kvdb.RwBucket, ops []*wire.OutPoint, isClosed bool) error {
status := uint8(outpointOpen)
if isClosed {
status = uint8(outpointClosed)
}
record := tlv.MakePrimitiveRecord(indexStatusType, &status)
stream, err := tlv.NewStream(record)
if err != nil {
return err
}
var b bytes.Buffer
if err := stream.Encode(&b); err != nil {
return err
}
// Store the set of outpoints with the encoded tlv stream.
for _, op := range ops {
var opBuf bytes.Buffer
if err := writeOutpoint(&opBuf, op); err != nil {
return err
}
if err := bucket.Put(opBuf.Bytes(), b.Bytes()); err != nil {
return err
}
}
return nil
}
package common
import (
"bytes"
"encoding/binary"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
"github.com/lightningnetwork/lnd/keychain"
)
// CircuitKey is used by a channel to uniquely identify the HTLCs it receives
// from the switch, and is used to purge our in-memory state of HTLCs that have
// already been processed by a link. Two list of CircuitKeys are included in
// each CommitDiff to allow a link to determine which in-memory htlcs directed
// the opening and closing of circuits in the switch's circuit map.
type CircuitKey struct {
// ChanID is the short chanid indicating the HTLC's origin.
//
// NOTE: It is fine for this value to be blank, as this indicates a
// locally-sourced payment.
ChanID lnwire.ShortChannelID
// HtlcID is the unique htlc index predominately assigned by links,
// though can also be assigned by switch in the case of locally-sourced
// payments.
HtlcID uint64
}
// HTLC is the on-disk representation of a hash time-locked contract. HTLCs are
// contained within ChannelDeltas which encode the current state of the
// commitment between state updates.
//
// TODO(roasbeef): save space by using smaller ints at tail end?
type HTLC struct {
// Signature is the signature for the second level covenant transaction
// for this HTLC. The second level transaction is a timeout tx in the
// case that this is an outgoing HTLC, and a success tx in the case
// that this is an incoming HTLC.
//
// TODO(roasbeef): make [64]byte instead?
Signature []byte
// RHash is the payment hash of the HTLC.
RHash [32]byte
// Amt is the amount of milli-satoshis this HTLC escrows.
Amt lnwire.MilliSatoshi
// RefundTimeout is the absolute timeout on the HTLC that the sender
// must wait before reclaiming the funds in limbo.
RefundTimeout uint32
// OutputIndex is the output index for this particular HTLC output
// within the commitment transaction.
OutputIndex int32
// Incoming denotes whether we're the receiver or the sender of this
// HTLC.
Incoming bool
// OnionBlob is an opaque blob which is used to complete multi-hop
// routing.
OnionBlob []byte
// HtlcIndex is the HTLC counter index of this active, outstanding
// HTLC. This differs from the LogIndex, as the HtlcIndex is only
// incremented for each offered HTLC, while they LogIndex is
// incremented for each update (includes settle+fail).
HtlcIndex uint64
// LogIndex is the cumulative log index of this HTLC. This differs
// from the HtlcIndex as this will be incremented for each new log
// update added.
LogIndex uint64
}
// ChannelCommitment is a snapshot of the commitment state at a particular
// point in the commitment chain. With each state transition, a snapshot of the
// current state along with all non-settled HTLCs are recorded. These snapshots
// detail the state of the _remote_ party's commitment at a particular state
// number. For ourselves (the local node) we ONLY store our most recent
// (unrevoked) state for safety purposes.
type ChannelCommitment struct {
// CommitHeight is the update number that this ChannelDelta represents
// the total number of commitment updates to this point. This can be
// viewed as sort of a "commitment height" as this number is
// monotonically increasing.
CommitHeight uint64
// LocalLogIndex is the cumulative log index index of the local node at
// this point in the commitment chain. This value will be incremented
// for each _update_ added to the local update log.
LocalLogIndex uint64
// LocalHtlcIndex is the current local running HTLC index. This value
// will be incremented for each outgoing HTLC the local node offers.
LocalHtlcIndex uint64
// RemoteLogIndex is the cumulative log index index of the remote node
// at this point in the commitment chain. This value will be
// incremented for each _update_ added to the remote update log.
RemoteLogIndex uint64
// RemoteHtlcIndex is the current remote running HTLC index. This value
// will be incremented for each outgoing HTLC the remote node offers.
RemoteHtlcIndex uint64
// LocalBalance is the current available settled balance within the
// channel directly spendable by us.
//
// NOTE: This is the balance *after* subtracting any commitment fee,
// AND anchor output values.
LocalBalance lnwire.MilliSatoshi
// RemoteBalance is the current available settled balance within the
// channel directly spendable by the remote node.
//
// NOTE: This is the balance *after* subtracting any commitment fee,
// AND anchor output values.
RemoteBalance lnwire.MilliSatoshi
// CommitFee is the amount calculated to be paid in fees for the
// current set of commitment transactions. The fee amount is persisted
// with the channel in order to allow the fee amount to be removed and
// recalculated with each channel state update, including updates that
// happen after a system restart.
CommitFee btcutil.Amount
// FeePerKw is the min satoshis/kilo-weight that should be paid within
// the commitment transaction for the entire duration of the channel's
// lifetime. This field may be updated during normal operation of the
// channel as on-chain conditions change.
//
// TODO(halseth): make this SatPerKWeight. Cannot be done atm because
// this will cause the import cycle lnwallet<->channeldb. Fee
// estimation stuff should be in its own package.
FeePerKw btcutil.Amount
// CommitTx is the latest version of the commitment state, broadcast
// able by us.
CommitTx *wire.MsgTx
// CommitSig is one half of the signature required to fully complete
// the script for the commitment transaction above. This is the
// signature signed by the remote party for our version of the
// commitment transactions.
CommitSig []byte
// Htlcs is the set of HTLC's that are pending at this particular
// commitment height.
Htlcs []HTLC
// TODO(roasbeef): pending commit pointer?
// * lets just walk through
}
// LogUpdate represents a pending update to the remote commitment chain. The
// log update may be an add, fail, or settle entry. We maintain this data in
// order to be able to properly retransmit our proposed
// state if necessary.
type LogUpdate struct {
// LogIndex is the log index of this proposed commitment update entry.
LogIndex uint64
// UpdateMsg is the update message that was included within the our
// local update log. The LogIndex value denotes the log index of this
// update which will be used when restoring our local update log if
// we're left with a dangling update on restart.
UpdateMsg lnwire.Message
}
// AddRef is used to identify a particular Add in a FwdPkg. The short channel ID
// is assumed to be that of the packager.
type AddRef struct {
// Height is the remote commitment height that locked in the Add.
Height uint64
// Index is the index of the Add within the fwd pkg's Adds.
//
// NOTE: This index is static over the lifetime of a forwarding package.
Index uint16
}
// SettleFailRef is used to locate a Settle/Fail in another channel's FwdPkg. A
// channel does not remove its own Settle/Fail htlcs, so the source is provided
// to locate a db bucket belonging to another channel.
type SettleFailRef struct {
// Source identifies the outgoing link that locked in the settle or
// fail. This is then used by the *incoming* link to find the settle
// fail in another link's forwarding packages.
Source lnwire.ShortChannelID
// Height is the remote commitment height that locked in this
// Settle/Fail.
Height uint64
// Index is the index of the Add with the fwd pkg's SettleFails.
//
// NOTE: This index is static over the lifetime of a forwarding package.
Index uint16
}
// CommitDiff represents the delta needed to apply the state transition between
// two subsequent commitment states. Given state N and state N+1, one is able
// to apply the set of messages contained within the CommitDiff to N to arrive
// at state N+1. Each time a new commitment is extended, we'll write a new
// commitment (along with the full commitment state) to disk so we can
// re-transmit the state in the case of a connection loss or message drop.
type CommitDiff struct {
// ChannelCommitment is the full commitment state that one would arrive
// at by applying the set of messages contained in the UpdateDiff to
// the prior accepted commitment.
Commitment ChannelCommitment
// LogUpdates is the set of messages sent prior to the commitment state
// transition in question. Upon reconnection, if we detect that they
// don't have the commitment, then we re-send this along with the
// proper signature.
LogUpdates []LogUpdate
// CommitSig is the exact CommitSig message that should be sent after
// the set of LogUpdates above has been retransmitted. The signatures
// within this message should properly cover the new commitment state
// and also the HTLC's within the new commitment state.
CommitSig *lnwire.CommitSig
// OpenedCircuitKeys is a set of unique identifiers for any downstream
// Add packets included in this commitment txn. After a restart, this
// set of htlcs is acked from the link's incoming mailbox to ensure
// there isn't an attempt to re-add them to this commitment txn.
OpenedCircuitKeys []CircuitKey
// ClosedCircuitKeys records the unique identifiers for any settle/fail
// packets that were resolved by this commitment txn. After a restart,
// this is used to ensure those circuits are removed from the circuit
// map, and the downstream packets in the link's mailbox are removed.
ClosedCircuitKeys []CircuitKey
// AddAcks specifies the locations (commit height, pkg index) of any
// Adds that were failed/settled in this commit diff. This will ack
// entries in *this* channel's forwarding packages.
//
// NOTE: This value is not serialized, it is used to atomically mark the
// resolution of adds, such that they will not be reprocessed after a
// restart.
AddAcks []AddRef
// SettleFailAcks specifies the locations (chan id, commit height, pkg
// index) of any Settles or Fails that were locked into this commit
// diff, and originate from *another* channel, i.e. the outgoing link.
//
// NOTE: This value is not serialized, it is used to atomically acks
// settles and fails from the forwarding packages of other channels,
// such that they will not be reforwarded internally after a restart.
SettleFailAcks []SettleFailRef
}
// NetworkResult is the raw result received from the network after a payment
// attempt has been made. Since the switch doesn't always have the necessary
// data to decode the raw message, we store it together with some meta data,
// and decode it when the router query for the final result.
type NetworkResult struct {
// Msg is the received result. This should be of type UpdateFulfillHTLC
// or UpdateFailHTLC.
Msg lnwire.Message
// unencrypted indicates whether the failure encoded in the message is
// unencrypted, and hence doesn't need to be decrypted.
Unencrypted bool
// IsResolution indicates whether this is a resolution message, in
// which the failure reason might not be included.
IsResolution bool
}
// ClosureType is an enum like structure that details exactly _how_ a channel
// was closed. Three closure types are currently possible: none, cooperative,
// local force close, remote force close, and (remote) breach.
type ClosureType uint8
// ChannelConstraints represents a set of constraints meant to allow a node to
// limit their exposure, enact flow control and ensure that all HTLCs are
// economically relevant. This struct will be mirrored for both sides of the
// channel, as each side will enforce various constraints that MUST be adhered
// to for the life time of the channel. The parameters for each of these
// constraints are static for the duration of the channel, meaning the channel
// must be torn down for them to change.
type ChannelConstraints struct {
// DustLimit is the threshold (in satoshis) below which any outputs
// should be trimmed. When an output is trimmed, it isn't materialized
// as an actual output, but is instead burned to miner's fees.
DustLimit btcutil.Amount
// ChanReserve is an absolute reservation on the channel for the
// owner of this set of constraints. This means that the current
// settled balance for this node CANNOT dip below the reservation
// amount. This acts as a defense against costless attacks when
// either side no longer has any skin in the game.
ChanReserve btcutil.Amount
// MaxPendingAmount is the maximum pending HTLC value that the
// owner of these constraints can offer the remote node at a
// particular time.
MaxPendingAmount lnwire.MilliSatoshi
// MinHTLC is the minimum HTLC value that the owner of these
// constraints can offer the remote node. If any HTLCs below this
// amount are offered, then the HTLC will be rejected. This, in
// tandem with the dust limit allows a node to regulate the
// smallest HTLC that it deems economically relevant.
MinHTLC lnwire.MilliSatoshi
// MaxAcceptedHtlcs is the maximum number of HTLCs that the owner of
// this set of constraints can offer the remote node. This allows each
// node to limit their over all exposure to HTLCs that may need to be
// acted upon in the case of a unilateral channel closure or a contract
// breach.
MaxAcceptedHtlcs uint16
// CsvDelay is the relative time lock delay expressed in blocks. Any
// settled outputs that pay to the owner of this channel configuration
// MUST ensure that the delay branch uses this value as the relative
// time lock. Similarly, any HTLC's offered by this node should use
// this value as well.
CsvDelay uint16
}
// ChannelConfig is a struct that houses the various configuration opens for
// channels. Each side maintains an instance of this configuration file as it
// governs: how the funding and commitment transaction to be created, the
// nature of HTLC's allotted, the keys to be used for delivery, and relative
// time lock parameters.
type ChannelConfig struct {
// ChannelConstraints is the set of constraints that must be upheld for
// the duration of the channel for the owner of this channel
// configuration. Constraints govern a number of flow control related
// parameters, also including the smallest HTLC that will be accepted
// by a participant.
ChannelConstraints
// MultiSigKey is the key to be used within the 2-of-2 output script
// for the owner of this channel config.
MultiSigKey keychain.KeyDescriptor
// RevocationBasePoint is the base public key to be used when deriving
// revocation keys for the remote node's commitment transaction. This
// will be combined along with a per commitment secret to derive a
// unique revocation key for each state.
RevocationBasePoint keychain.KeyDescriptor
// PaymentBasePoint is the base public key to be used when deriving
// the key used within the non-delayed pay-to-self output on the
// commitment transaction for a node. This will be combined with a
// tweak derived from the per-commitment point to ensure unique keys
// for each commitment transaction.
PaymentBasePoint keychain.KeyDescriptor
// DelayBasePoint is the base public key to be used when deriving the
// key used within the delayed pay-to-self output on the commitment
// transaction for a node. This will be combined with a tweak derived
// from the per-commitment point to ensure unique keys for each
// commitment transaction.
DelayBasePoint keychain.KeyDescriptor
// HtlcBasePoint is the base public key to be used when deriving the
// local HTLC key. The derived key (combined with the tweak derived
// from the per-commitment point) is used within the "to self" clause
// within any HTLC output scripts.
HtlcBasePoint keychain.KeyDescriptor
}
// ChannelCloseSummary contains the final state of a channel at the point it
// was closed. Once a channel is closed, all the information pertaining to that
// channel within the openChannelBucket is deleted, and a compact summary is
// put in place instead.
type ChannelCloseSummary struct {
// ChanPoint is the outpoint for this channel's funding transaction,
// and is used as a unique identifier for the channel.
ChanPoint wire.OutPoint
// ShortChanID encodes the exact location in the chain in which the
// channel was initially confirmed. This includes: the block height,
// transaction index, and the output within the target transaction.
ShortChanID lnwire.ShortChannelID
// ChainHash is the hash of the genesis block that this channel resides
// within.
ChainHash chainhash.Hash
// ClosingTXID is the txid of the transaction which ultimately closed
// this channel.
ClosingTXID chainhash.Hash
// RemotePub is the public key of the remote peer that we formerly had
// a channel with.
RemotePub *btcec.PublicKey
// Capacity was the total capacity of the channel.
Capacity btcutil.Amount
// CloseHeight is the height at which the funding transaction was
// spent.
CloseHeight uint32
// SettledBalance is our total balance settled balance at the time of
// channel closure. This _does not_ include the sum of any outputs that
// have been time-locked as a result of the unilateral channel closure.
SettledBalance btcutil.Amount
// TimeLockedBalance is the sum of all the time-locked outputs at the
// time of channel closure. If we triggered the force closure of this
// channel, then this value will be non-zero if our settled output is
// above the dust limit. If we were on the receiving side of a channel
// force closure, then this value will be non-zero if we had any
// outstanding outgoing HTLC's at the time of channel closure.
TimeLockedBalance btcutil.Amount
// CloseType details exactly _how_ the channel was closed. Five closure
// types are possible: cooperative, local force, remote force, breach
// and funding canceled.
CloseType ClosureType
// IsPending indicates whether this channel is in the 'pending close'
// state, which means the channel closing transaction has been
// confirmed, but not yet been fully resolved. In the case of a channel
// that has been cooperatively closed, it will go straight into the
// fully resolved state as soon as the closing transaction has been
// confirmed. However, for channels that have been force closed, they'll
// stay marked as "pending" until _all_ the pending funds have been
// swept.
IsPending bool
// RemoteCurrentRevocation is the current revocation for their
// commitment transaction. However, since this is the derived public key,
// we don't yet have the private key so we aren't yet able to verify
// that it's actually in the hash chain.
RemoteCurrentRevocation *btcec.PublicKey
// RemoteNextRevocation is the revocation key to be used for the *next*
// commitment transaction we create for the local node. Within the
// specification, this value is referred to as the
// per-commitment-point.
RemoteNextRevocation *btcec.PublicKey
// LocalChanConfig is the channel configuration for the local node.
LocalChanConfig ChannelConfig
// LastChanSyncMsg is the ChannelReestablish message for this channel
// for the state at the point where it was closed.
LastChanSyncMsg *lnwire.ChannelReestablish
}
// FwdState is an enum used to describe the lifecycle of a FwdPkg.
type FwdState byte
const (
// FwdStateLockedIn is the starting state for all forwarding packages.
// Packages in this state have not yet committed to the exact set of
// Adds to forward to the switch.
FwdStateLockedIn FwdState = iota
// FwdStateProcessed marks the state in which all Adds have been
// locally processed and the forwarding decision to the switch has been
// persisted.
FwdStateProcessed
// FwdStateCompleted signals that all Adds have been acked, and that all
// settles and fails have been delivered to their sources. Packages in
// this state can be removed permanently.
FwdStateCompleted
)
// PkgFilter is used to compactly represent a particular subset of the Adds in a
// forwarding package. Each filter is represented as a simple, statically-sized
// bitvector, where the elements are intended to be the indices of the Adds as
// they are written in the FwdPkg.
type PkgFilter struct {
count uint16
filter []byte
}
// NewPkgFilter initializes an empty PkgFilter supporting `count` elements.
func NewPkgFilter(count uint16) *PkgFilter {
// We add 7 to ensure that the integer division yields properly rounded
// values.
filterLen := (count + 7) / 8
return &PkgFilter{
count: count,
filter: make([]byte, filterLen),
}
}
// Count returns the number of elements represented by this PkgFilter.
func (f *PkgFilter) Count() uint16 {
return f.count
}
// Set marks the `i`-th element as included by this filter.
// NOTE: It is assumed that i is always less than count.
func (f *PkgFilter) Set(i uint16) {
byt := i / 8
bit := i % 8
// Set the i-th bit in the filter.
// TODO(conner): ignore if > count to prevent panic?
f.filter[byt] |= byte(1 << (7 - bit))
}
// Contains queries the filter for membership of index `i`.
// NOTE: It is assumed that i is always less than count.
func (f *PkgFilter) Contains(i uint16) bool {
byt := i / 8
bit := i % 8
// Read the i-th bit in the filter.
// TODO(conner): ignore if > count to prevent panic?
return f.filter[byt]&(1<<(7-bit)) != 0
}
// Equal checks two PkgFilters for equality.
func (f *PkgFilter) Equal(f2 *PkgFilter) bool {
if f == f2 {
return true
}
if f.count != f2.count {
return false
}
return bytes.Equal(f.filter, f2.filter)
}
// IsFull returns true if every element in the filter has been Set, and false
// otherwise.
func (f *PkgFilter) IsFull() bool {
// Batch validate bytes that are fully used.
for i := uint16(0); i < f.count/8; i++ {
if f.filter[i] != 0xFF {
return false
}
}
// If the count is not a multiple of 8, check that the filter contains
// all remaining bits.
rem := f.count % 8
for idx := f.count - rem; idx < f.count; idx++ {
if !f.Contains(idx) {
return false
}
}
return true
}
// Size returns number of bytes produced when the PkgFilter is serialized.
func (f *PkgFilter) Size() uint16 {
// 2 bytes for uint16 `count`, then round up number of bytes required to
// represent `count` bits.
return 2 + (f.count+7)/8
}
// Encode writes the filter to the provided io.Writer.
func (f *PkgFilter) Encode(w io.Writer) error {
if err := binary.Write(w, binary.BigEndian, f.count); err != nil {
return err
}
_, err := w.Write(f.filter)
return err
}
// Decode reads the filter from the provided io.Reader.
func (f *PkgFilter) Decode(r io.Reader) error {
if err := binary.Read(r, binary.BigEndian, &f.count); err != nil {
return err
}
f.filter = make([]byte, f.Size()-2)
_, err := io.ReadFull(r, f.filter)
return err
}
// FwdPkg records all adds, settles, and fails that were locked in as a result
// of the remote peer sending us a revocation. Each package is identified by
// the short chanid and remote commitment height corresponding to the revocation
// that locked in the HTLCs. For everything except a locally initiated payment,
// settles and fails in a forwarding package must have a corresponding Add in
// another package, and can be removed individually once the source link has
// received the fail/settle.
//
// Adds cannot be removed, as we need to present the same batch of Adds to
// properly handle replay protection. Instead, we use a PkgFilter to mark that
// we have finished processing a particular Add. A FwdPkg should only be deleted
// after the AckFilter is full and all settles and fails have been persistently
// removed.
type FwdPkg struct {
// Source identifies the channel that wrote this forwarding package.
Source lnwire.ShortChannelID
// Height is the height of the remote commitment chain that locked in
// this forwarding package.
Height uint64
// State signals the persistent condition of the package and directs how
// to reprocess the package in the event of failures.
State FwdState
// Adds contains all add messages which need to be processed and
// forwarded to the switch. Adds does not change over the life of a
// forwarding package.
Adds []LogUpdate
// FwdFilter is a filter containing the indices of all Adds that were
// forwarded to the switch.
FwdFilter *PkgFilter
// AckFilter is a filter containing the indices of all Adds for which
// the source has received a settle or fail and is reflected in the next
// commitment txn. A package should not be removed until IsFull()
// returns true.
AckFilter *PkgFilter
// SettleFails contains all settle and fail messages that should be
// forwarded to the switch.
SettleFails []LogUpdate
// SettleFailFilter is a filter containing the indices of all Settle or
// Fails originating in this package that have been received and locked
// into the incoming link's commitment state.
SettleFailFilter *PkgFilter
}
// NewFwdPkg initializes a new forwarding package in FwdStateLockedIn. This
// should be used to create a package at the time we receive a revocation.
func NewFwdPkg(source lnwire.ShortChannelID, height uint64,
addUpdates, settleFailUpdates []LogUpdate) *FwdPkg {
nAddUpdates := uint16(len(addUpdates))
nSettleFailUpdates := uint16(len(settleFailUpdates))
return &FwdPkg{
Source: source,
Height: height,
State: FwdStateLockedIn,
Adds: addUpdates,
FwdFilter: NewPkgFilter(nAddUpdates),
AckFilter: NewPkgFilter(nAddUpdates),
SettleFails: settleFailUpdates,
SettleFailFilter: NewPkgFilter(nSettleFailUpdates),
}
}
package current
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
"github.com/lightningnetwork/lnd/channeldb/migration21/common"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/shachain"
)
var (
// Big endian is the preferred byte order, due to cursor scans over
// integer keys iterating in order.
byteOrder = binary.BigEndian
)
// writeOutpoint writes an outpoint to the passed writer using the minimal
// amount of bytes possible.
func writeOutpoint(w io.Writer, o *wire.OutPoint) error {
if _, err := w.Write(o.Hash[:]); err != nil {
return err
}
if err := binary.Write(w, byteOrder, o.Index); err != nil {
return err
}
return nil
}
// readOutpoint reads an outpoint from the passed reader that was previously
// written using the writeOutpoint struct.
func readOutpoint(r io.Reader, o *wire.OutPoint) error {
if _, err := io.ReadFull(r, o.Hash[:]); err != nil {
return err
}
if err := binary.Read(r, byteOrder, &o.Index); err != nil {
return err
}
return nil
}
// UnknownElementType is an error returned when the codec is unable to encode or
// decode a particular type.
type UnknownElementType struct {
method string
element interface{}
}
// NewUnknownElementType creates a new UnknownElementType error from the passed
// method name and element.
func NewUnknownElementType(method string, el interface{}) UnknownElementType {
return UnknownElementType{method: method, element: el}
}
// Error returns the name of the method that encountered the error, as well as
// the type that was unsupported.
func (e UnknownElementType) Error() string {
return fmt.Sprintf("Unknown type in %s: %T", e.method, e.element)
}
// WriteElement is a one-stop shop to write the big endian representation of
// any element which is to be serialized for storage on disk. The passed
// io.Writer should be backed by an appropriately sized byte slice, or be able
// to dynamically expand to accommodate additional data.
func WriteElement(w io.Writer, element interface{}) error {
switch e := element.(type) {
case keychain.KeyDescriptor:
if err := binary.Write(w, byteOrder, e.Family); err != nil {
return err
}
if err := binary.Write(w, byteOrder, e.Index); err != nil {
return err
}
if e.PubKey != nil {
if err := binary.Write(w, byteOrder, true); err != nil {
return fmt.Errorf("error writing serialized "+
"element: %w", err)
}
return WriteElement(w, e.PubKey)
}
return binary.Write(w, byteOrder, false)
case chainhash.Hash:
if _, err := w.Write(e[:]); err != nil {
return err
}
case wire.OutPoint:
return writeOutpoint(w, &e)
case lnwire.ShortChannelID:
if err := binary.Write(w, byteOrder, e.ToUint64()); err != nil {
return err
}
case lnwire.ChannelID:
if _, err := w.Write(e[:]); err != nil {
return err
}
case int64, uint64:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case uint32:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case int32:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case uint16:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case uint8:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case bool:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case btcutil.Amount:
if err := binary.Write(w, byteOrder, uint64(e)); err != nil {
return err
}
case lnwire.MilliSatoshi:
if err := binary.Write(w, byteOrder, uint64(e)); err != nil {
return err
}
case *btcec.PrivateKey:
b := e.Serialize()
if _, err := w.Write(b); err != nil {
return err
}
case *btcec.PublicKey:
b := e.SerializeCompressed()
if _, err := w.Write(b); err != nil {
return err
}
case shachain.Producer:
return e.Encode(w)
case shachain.Store:
return e.Encode(w)
case *wire.MsgTx:
return e.Serialize(w)
case [32]byte:
if _, err := w.Write(e[:]); err != nil {
return err
}
case []byte:
if err := wire.WriteVarBytes(w, 0, e); err != nil {
return err
}
case lnwire.Message:
var msgBuf bytes.Buffer
if _, err := lnwire.WriteMessage(&msgBuf, e, 0); err != nil {
return err
}
msgLen := uint16(len(msgBuf.Bytes()))
if err := WriteElements(w, msgLen); err != nil {
return err
}
if _, err := w.Write(msgBuf.Bytes()); err != nil {
return err
}
case common.ClosureType:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case lnwire.FundingFlag:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
default:
return UnknownElementType{"WriteElement", e}
}
return nil
}
// WriteElements is writes each element in the elements slice to the passed
// io.Writer using WriteElement.
func WriteElements(w io.Writer, elements ...interface{}) error {
for _, element := range elements {
err := WriteElement(w, element)
if err != nil {
return err
}
}
return nil
}
// ReadElement is a one-stop utility function to deserialize any datastructure
// encoded using the serialization format of the database.
func ReadElement(r io.Reader, element interface{}) error {
switch e := element.(type) {
case *keychain.KeyDescriptor:
if err := binary.Read(r, byteOrder, &e.Family); err != nil {
return err
}
if err := binary.Read(r, byteOrder, &e.Index); err != nil {
return err
}
var hasPubKey bool
if err := binary.Read(r, byteOrder, &hasPubKey); err != nil {
return err
}
if hasPubKey {
return ReadElement(r, &e.PubKey)
}
case *chainhash.Hash:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *wire.OutPoint:
return readOutpoint(r, e)
case *lnwire.ShortChannelID:
var a uint64
if err := binary.Read(r, byteOrder, &a); err != nil {
return err
}
*e = lnwire.NewShortChanIDFromInt(a)
case *lnwire.ChannelID:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *int64, *uint64:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *uint32:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *int32:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *uint16:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *uint8:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *bool:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *btcutil.Amount:
var a uint64
if err := binary.Read(r, byteOrder, &a); err != nil {
return err
}
*e = btcutil.Amount(a)
case *lnwire.MilliSatoshi:
var a uint64
if err := binary.Read(r, byteOrder, &a); err != nil {
return err
}
*e = lnwire.MilliSatoshi(a)
case **btcec.PrivateKey:
var b [btcec.PrivKeyBytesLen]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
priv, _ := btcec.PrivKeyFromBytes(b[:])
*e = priv
case **btcec.PublicKey:
var b [btcec.PubKeyBytesLenCompressed]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
pubKey, err := btcec.ParsePubKey(b[:])
if err != nil {
return err
}
*e = pubKey
case *shachain.Producer:
var root [32]byte
if _, err := io.ReadFull(r, root[:]); err != nil {
return err
}
// TODO(roasbeef): remove
producer, err := shachain.NewRevocationProducerFromBytes(root[:])
if err != nil {
return err
}
*e = producer
case *shachain.Store:
store, err := shachain.NewRevocationStoreFromBytes(r)
if err != nil {
return err
}
*e = store
case **wire.MsgTx:
tx := wire.NewMsgTx(2)
if err := tx.Deserialize(r); err != nil {
return err
}
*e = tx
case *[32]byte:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *[]byte:
bytes, err := wire.ReadVarBytes(r, 0, 66000, "[]byte")
if err != nil {
return err
}
*e = bytes
case *lnwire.Message:
var msgLen uint16
if err := ReadElement(r, &msgLen); err != nil {
return err
}
msgReader := io.LimitReader(r, int64(msgLen))
msg, err := lnwire.ReadMessage(msgReader, 0)
if err != nil {
return err
}
*e = msg
case *common.ClosureType:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *lnwire.FundingFlag:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
default:
return UnknownElementType{"ReadElement", e}
}
return nil
}
// ReadElements deserializes a variable number of elements into the passed
// io.Reader, with each element being deserialized according to the ReadElement
// function.
func ReadElements(r io.Reader, elements ...interface{}) error {
for _, element := range elements {
err := ReadElement(r, element)
if err != nil {
return err
}
}
return nil
}
package current
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
"github.com/lightningnetwork/lnd/channeldb/migration21/common"
"github.com/lightningnetwork/lnd/kvdb"
)
func serializeChanCommit(w io.Writer, c *common.ChannelCommitment) error { // nolint: dupl
if err := WriteElements(w,
c.CommitHeight, c.LocalLogIndex, c.LocalHtlcIndex,
c.RemoteLogIndex, c.RemoteHtlcIndex, c.LocalBalance,
c.RemoteBalance, c.CommitFee, c.FeePerKw, c.CommitTx,
c.CommitSig,
); err != nil {
return err
}
return serializeHtlcs(w, c.Htlcs...)
}
func SerializeLogUpdates(w io.Writer, logUpdates []common.LogUpdate) error { // nolint: dupl
numUpdates := uint16(len(logUpdates))
if err := binary.Write(w, byteOrder, numUpdates); err != nil {
return err
}
for _, diff := range logUpdates {
err := WriteElements(w, diff.LogIndex, diff.UpdateMsg)
if err != nil {
return err
}
}
return nil
}
func serializeHtlcs(b io.Writer, htlcs ...common.HTLC) error { // nolint: dupl
numHtlcs := uint16(len(htlcs))
if err := WriteElement(b, numHtlcs); err != nil {
return err
}
for _, htlc := range htlcs {
if err := WriteElements(b,
htlc.Signature, htlc.RHash, htlc.Amt, htlc.RefundTimeout,
htlc.OutputIndex, htlc.Incoming, htlc.OnionBlob,
htlc.HtlcIndex, htlc.LogIndex,
); err != nil {
return err
}
}
return nil
}
func SerializeCommitDiff(w io.Writer, diff *common.CommitDiff) error { // nolint: dupl
if err := serializeChanCommit(w, &diff.Commitment); err != nil {
return err
}
if err := WriteElements(w, diff.CommitSig); err != nil {
return err
}
if err := SerializeLogUpdates(w, diff.LogUpdates); err != nil {
return err
}
numOpenRefs := uint16(len(diff.OpenedCircuitKeys))
if err := binary.Write(w, byteOrder, numOpenRefs); err != nil {
return err
}
for _, openRef := range diff.OpenedCircuitKeys {
err := WriteElements(w, openRef.ChanID, openRef.HtlcID)
if err != nil {
return err
}
}
numClosedRefs := uint16(len(diff.ClosedCircuitKeys))
if err := binary.Write(w, byteOrder, numClosedRefs); err != nil {
return err
}
for _, closedRef := range diff.ClosedCircuitKeys {
err := WriteElements(w, closedRef.ChanID, closedRef.HtlcID)
if err != nil {
return err
}
}
return nil
}
func deserializeHtlcs(r io.Reader) ([]common.HTLC, error) { // nolint: dupl
var numHtlcs uint16
if err := ReadElement(r, &numHtlcs); err != nil {
return nil, err
}
var htlcs []common.HTLC
if numHtlcs == 0 {
return htlcs, nil
}
htlcs = make([]common.HTLC, numHtlcs)
for i := uint16(0); i < numHtlcs; i++ {
if err := ReadElements(r,
&htlcs[i].Signature, &htlcs[i].RHash, &htlcs[i].Amt,
&htlcs[i].RefundTimeout, &htlcs[i].OutputIndex,
&htlcs[i].Incoming, &htlcs[i].OnionBlob,
&htlcs[i].HtlcIndex, &htlcs[i].LogIndex,
); err != nil {
return htlcs, err
}
}
return htlcs, nil
}
func deserializeChanCommit(r io.Reader) (common.ChannelCommitment, error) { // nolint: dupl
var c common.ChannelCommitment
err := ReadElements(r,
&c.CommitHeight, &c.LocalLogIndex, &c.LocalHtlcIndex, &c.RemoteLogIndex,
&c.RemoteHtlcIndex, &c.LocalBalance, &c.RemoteBalance,
&c.CommitFee, &c.FeePerKw, &c.CommitTx, &c.CommitSig,
)
if err != nil {
return c, err
}
c.Htlcs, err = deserializeHtlcs(r)
if err != nil {
return c, err
}
return c, nil
}
func DeserializeLogUpdates(r io.Reader) ([]common.LogUpdate, error) { // nolint: dupl
var numUpdates uint16
if err := binary.Read(r, byteOrder, &numUpdates); err != nil {
return nil, err
}
logUpdates := make([]common.LogUpdate, numUpdates)
for i := 0; i < int(numUpdates); i++ {
err := ReadElements(r,
&logUpdates[i].LogIndex, &logUpdates[i].UpdateMsg,
)
if err != nil {
return nil, err
}
}
return logUpdates, nil
}
func DeserializeCommitDiff(r io.Reader) (*common.CommitDiff, error) { // nolint: dupl
var (
d common.CommitDiff
err error
)
d.Commitment, err = deserializeChanCommit(r)
if err != nil {
return nil, err
}
var msg lnwire.Message
if err := ReadElements(r, &msg); err != nil {
return nil, err
}
commitSig, ok := msg.(*lnwire.CommitSig)
if !ok {
return nil, fmt.Errorf("expected lnwire.CommitSig, instead "+
"read: %T", msg)
}
d.CommitSig = commitSig
d.LogUpdates, err = DeserializeLogUpdates(r)
if err != nil {
return nil, err
}
var numOpenRefs uint16
if err := binary.Read(r, byteOrder, &numOpenRefs); err != nil {
return nil, err
}
d.OpenedCircuitKeys = make([]common.CircuitKey, numOpenRefs)
for i := 0; i < int(numOpenRefs); i++ {
err := ReadElements(r,
&d.OpenedCircuitKeys[i].ChanID,
&d.OpenedCircuitKeys[i].HtlcID)
if err != nil {
return nil, err
}
}
var numClosedRefs uint16
if err := binary.Read(r, byteOrder, &numClosedRefs); err != nil {
return nil, err
}
d.ClosedCircuitKeys = make([]common.CircuitKey, numClosedRefs)
for i := 0; i < int(numClosedRefs); i++ {
err := ReadElements(r,
&d.ClosedCircuitKeys[i].ChanID,
&d.ClosedCircuitKeys[i].HtlcID)
if err != nil {
return nil, err
}
}
return &d, nil
}
func SerializeNetworkResult(w io.Writer, n *common.NetworkResult) error { // nolint: dupl
return WriteElements(w, n.Msg, n.Unencrypted, n.IsResolution)
}
func DeserializeNetworkResult(r io.Reader) (*common.NetworkResult, error) { // nolint: dupl
n := &common.NetworkResult{}
if err := ReadElements(r,
&n.Msg, &n.Unencrypted, &n.IsResolution,
); err != nil {
return nil, err
}
return n, nil
}
func writeChanConfig(b io.Writer, c *common.ChannelConfig) error { // nolint: dupl
return WriteElements(b,
c.DustLimit, c.MaxPendingAmount, c.ChanReserve, c.MinHTLC,
c.MaxAcceptedHtlcs, c.CsvDelay, c.MultiSigKey,
c.RevocationBasePoint, c.PaymentBasePoint, c.DelayBasePoint,
c.HtlcBasePoint,
)
}
func SerializeChannelCloseSummary(w io.Writer, cs *common.ChannelCloseSummary) error { // nolint: dupl
err := WriteElements(w,
cs.ChanPoint, cs.ShortChanID, cs.ChainHash, cs.ClosingTXID,
cs.CloseHeight, cs.RemotePub, cs.Capacity, cs.SettledBalance,
cs.TimeLockedBalance, cs.CloseType, cs.IsPending,
)
if err != nil {
return err
}
// If this is a close channel summary created before the addition of
// the new fields, then we can exit here.
if cs.RemoteCurrentRevocation == nil {
return WriteElements(w, false)
}
// If fields are present, write boolean to indicate this, and continue.
if err := WriteElements(w, true); err != nil {
return err
}
if err := WriteElements(w, cs.RemoteCurrentRevocation); err != nil {
return err
}
if err := writeChanConfig(w, &cs.LocalChanConfig); err != nil {
return err
}
// The RemoteNextRevocation field is optional, as it's possible for a
// channel to be closed before we learn of the next unrevoked
// revocation point for the remote party. Write a boolean indicating
// whether this field is present or not.
if err := WriteElements(w, cs.RemoteNextRevocation != nil); err != nil {
return err
}
// Write the field, if present.
if cs.RemoteNextRevocation != nil {
if err = WriteElements(w, cs.RemoteNextRevocation); err != nil {
return err
}
}
// Write whether the channel sync message is present.
if err := WriteElements(w, cs.LastChanSyncMsg != nil); err != nil {
return err
}
// Write the channel sync message, if present.
if cs.LastChanSyncMsg != nil {
if err := WriteElements(w, cs.LastChanSyncMsg); err != nil {
return err
}
}
return nil
}
func readChanConfig(b io.Reader, c *common.ChannelConfig) error {
return ReadElements(b,
&c.DustLimit, &c.MaxPendingAmount, &c.ChanReserve,
&c.MinHTLC, &c.MaxAcceptedHtlcs, &c.CsvDelay,
&c.MultiSigKey, &c.RevocationBasePoint,
&c.PaymentBasePoint, &c.DelayBasePoint,
&c.HtlcBasePoint,
)
}
func DeserializeCloseChannelSummary(r io.Reader) (*common.ChannelCloseSummary, error) { // nolint: dupl
c := &common.ChannelCloseSummary{}
err := ReadElements(r,
&c.ChanPoint, &c.ShortChanID, &c.ChainHash, &c.ClosingTXID,
&c.CloseHeight, &c.RemotePub, &c.Capacity, &c.SettledBalance,
&c.TimeLockedBalance, &c.CloseType, &c.IsPending,
)
if err != nil {
return nil, err
}
// We'll now check to see if the channel close summary was encoded with
// any of the additional optional fields.
var hasNewFields bool
err = ReadElements(r, &hasNewFields)
if err != nil {
return nil, err
}
// If fields are not present, we can return.
if !hasNewFields {
return c, nil
}
// Otherwise read the new fields.
if err := ReadElements(r, &c.RemoteCurrentRevocation); err != nil {
return nil, err
}
if err := readChanConfig(r, &c.LocalChanConfig); err != nil {
return nil, err
}
// Finally, we'll attempt to read the next unrevoked commitment point
// for the remote party. If we closed the channel before receiving a
// funding locked message then this might not be present. A boolean
// indicating whether the field is present will come first.
var hasRemoteNextRevocation bool
err = ReadElements(r, &hasRemoteNextRevocation)
if err != nil {
return nil, err
}
// If this field was written, read it.
if hasRemoteNextRevocation {
err = ReadElements(r, &c.RemoteNextRevocation)
if err != nil {
return nil, err
}
}
// Check if we have a channel sync message to read.
var hasChanSyncMsg bool
err = ReadElements(r, &hasChanSyncMsg)
if err == io.EOF {
return c, nil
} else if err != nil {
return nil, err
}
// If a chan sync message is present, read it.
if hasChanSyncMsg {
// We must pass in reference to a lnwire.Message for the codec
// to support it.
var msg lnwire.Message
if err := ReadElements(r, &msg); err != nil {
return nil, err
}
chanSync, ok := msg.(*lnwire.ChannelReestablish)
if !ok {
return nil, errors.New("unable cast db Message to " +
"ChannelReestablish")
}
c.LastChanSyncMsg = chanSync
}
return c, nil
}
// ErrCorruptedFwdPkg signals that the on-disk structure of the forwarding
// package has potentially been mangled.
var ErrCorruptedFwdPkg = errors.New("fwding package db has been corrupted")
var (
// fwdPackagesKey is the root-level bucket that all forwarding packages
// are written. This bucket is further subdivided based on the short
// channel ID of each channel.
fwdPackagesKey = []byte("fwd-packages")
// addBucketKey is the bucket to which all Add log updates are written.
addBucketKey = []byte("add-updates")
// failSettleBucketKey is the bucket to which all Settle/Fail log
// updates are written.
failSettleBucketKey = []byte("fail-settle-updates")
// fwdFilterKey is a key used to write the set of Adds that passed
// validation and are to be forwarded to the switch.
// NOTE: The presence of this key within a forwarding package indicates
// that the package has reached FwdStateProcessed.
fwdFilterKey = []byte("fwd-filter-key")
// ackFilterKey is a key used to access the PkgFilter indicating which
// Adds have received a Settle/Fail. This response may come from a
// number of sources, including: exitHop settle/fails, switch failures,
// chain arbiter interjections, as well as settle/fails from the
// next hop in the route.
ackFilterKey = []byte("ack-filter-key")
// settleFailFilterKey is a key used to access the PkgFilter indicating
// which Settles/Fails in have been received and processed by the link
// that originally received the Add.
settleFailFilterKey = []byte("settle-fail-filter-key")
)
func makeLogKey(updateNum uint64) [8]byte {
var key [8]byte
byteOrder.PutUint64(key[:], updateNum)
return key
}
// uint16Key writes the provided 16-bit unsigned integer to a 2-byte slice.
func uint16Key(i uint16) []byte {
key := make([]byte, 2)
byteOrder.PutUint16(key, i)
return key
}
// ChannelPackager is used by a channel to manage the lifecycle of its forwarding
// packages. The packager is tied to a particular source channel ID, allowing it
// to create and edit its own packages. Each packager also has the ability to
// remove fail/settle htlcs that correspond to an add contained in one of
// source's packages.
type ChannelPackager struct {
source lnwire.ShortChannelID
}
// NewChannelPackager creates a new packager for a single channel.
func NewChannelPackager(source lnwire.ShortChannelID) *ChannelPackager {
return &ChannelPackager{
source: source,
}
}
// AddFwdPkg writes a newly locked in forwarding package to disk.
func (*ChannelPackager) AddFwdPkg(tx kvdb.RwTx, fwdPkg *common.FwdPkg) error { // nolint: dupl
fwdPkgBkt, err := tx.CreateTopLevelBucket(fwdPackagesKey)
if err != nil {
return err
}
source := makeLogKey(fwdPkg.Source.ToUint64())
sourceBkt, err := fwdPkgBkt.CreateBucketIfNotExists(source[:])
if err != nil {
return err
}
heightKey := makeLogKey(fwdPkg.Height)
heightBkt, err := sourceBkt.CreateBucketIfNotExists(heightKey[:])
if err != nil {
return err
}
// Write ADD updates we received at this commit height.
addBkt, err := heightBkt.CreateBucketIfNotExists(addBucketKey)
if err != nil {
return err
}
// Write SETTLE/FAIL updates we received at this commit height.
failSettleBkt, err := heightBkt.CreateBucketIfNotExists(failSettleBucketKey)
if err != nil {
return err
}
for i := range fwdPkg.Adds {
err = putLogUpdate(addBkt, uint16(i), &fwdPkg.Adds[i])
if err != nil {
return err
}
}
// Persist the initialized pkg filter, which will be used to determine
// when we can remove this forwarding package from disk.
var ackFilterBuf bytes.Buffer
if err := fwdPkg.AckFilter.Encode(&ackFilterBuf); err != nil {
return err
}
if err := heightBkt.Put(ackFilterKey, ackFilterBuf.Bytes()); err != nil {
return err
}
for i := range fwdPkg.SettleFails {
err = putLogUpdate(failSettleBkt, uint16(i), &fwdPkg.SettleFails[i])
if err != nil {
return err
}
}
var settleFailFilterBuf bytes.Buffer
err = fwdPkg.SettleFailFilter.Encode(&settleFailFilterBuf)
if err != nil {
return err
}
return heightBkt.Put(settleFailFilterKey, settleFailFilterBuf.Bytes())
}
// putLogUpdate writes an htlc to the provided `bkt`, using `index` as the key.
func putLogUpdate(bkt kvdb.RwBucket, idx uint16, htlc *common.LogUpdate) error {
var b bytes.Buffer
if err := serializeLogUpdate(&b, htlc); err != nil {
return err
}
return bkt.Put(uint16Key(idx), b.Bytes())
}
// LoadFwdPkgs scans the forwarding log for any packages that haven't been
// processed, and returns their deserialized log updates in a map indexed by the
// remote commitment height at which the updates were locked in.
func (p *ChannelPackager) LoadFwdPkgs(tx kvdb.RTx) ([]*common.FwdPkg, error) {
return loadChannelFwdPkgs(tx, p.source)
}
// loadChannelFwdPkgs loads all forwarding packages owned by `source`.
func loadChannelFwdPkgs(tx kvdb.RTx, source lnwire.ShortChannelID) ([]*common.FwdPkg, error) { // nolint: dupl
fwdPkgBkt := tx.ReadBucket(fwdPackagesKey)
if fwdPkgBkt == nil {
return nil, nil
}
sourceKey := makeLogKey(source.ToUint64())
sourceBkt := fwdPkgBkt.NestedReadBucket(sourceKey[:])
if sourceBkt == nil {
return nil, nil
}
var heights []uint64
if err := sourceBkt.ForEach(func(k, _ []byte) error {
if len(k) != 8 {
return ErrCorruptedFwdPkg
}
heights = append(heights, byteOrder.Uint64(k))
return nil
}); err != nil {
return nil, err
}
// Load the forwarding package for each retrieved height.
fwdPkgs := make([]*common.FwdPkg, 0, len(heights))
for _, height := range heights {
fwdPkg, err := loadFwdPkg(fwdPkgBkt, source, height)
if err != nil {
return nil, err
}
fwdPkgs = append(fwdPkgs, fwdPkg)
}
return fwdPkgs, nil
}
// loadFwdPkg reads the packager's fwd pkg at a given height, and determines the
// appropriate FwdState.
func loadFwdPkg(fwdPkgBkt kvdb.RBucket, source lnwire.ShortChannelID,
height uint64) (*common.FwdPkg, error) {
sourceKey := makeLogKey(source.ToUint64())
sourceBkt := fwdPkgBkt.NestedReadBucket(sourceKey[:])
if sourceBkt == nil {
return nil, ErrCorruptedFwdPkg
}
heightKey := makeLogKey(height)
heightBkt := sourceBkt.NestedReadBucket(heightKey[:])
if heightBkt == nil {
return nil, ErrCorruptedFwdPkg
}
// Load ADDs from disk.
addBkt := heightBkt.NestedReadBucket(addBucketKey)
if addBkt == nil {
return nil, ErrCorruptedFwdPkg
}
adds, err := loadHtlcs(addBkt)
if err != nil {
return nil, err
}
// Load ack filter from disk.
ackFilterBytes := heightBkt.Get(ackFilterKey)
if ackFilterBytes == nil {
return nil, ErrCorruptedFwdPkg
}
ackFilterReader := bytes.NewReader(ackFilterBytes)
ackFilter := &common.PkgFilter{}
if err := ackFilter.Decode(ackFilterReader); err != nil {
return nil, err
}
// Load SETTLE/FAILs from disk.
failSettleBkt := heightBkt.NestedReadBucket(failSettleBucketKey)
if failSettleBkt == nil {
return nil, ErrCorruptedFwdPkg
}
failSettles, err := loadHtlcs(failSettleBkt)
if err != nil {
return nil, err
}
// Load settle fail filter from disk.
settleFailFilterBytes := heightBkt.Get(settleFailFilterKey)
if settleFailFilterBytes == nil {
return nil, ErrCorruptedFwdPkg
}
settleFailFilterReader := bytes.NewReader(settleFailFilterBytes)
settleFailFilter := &common.PkgFilter{}
if err := settleFailFilter.Decode(settleFailFilterReader); err != nil {
return nil, err
}
// Initialize the fwding package, which always starts in the
// FwdStateLockedIn. We can determine what state the package was left in
// by examining constraints on the information loaded from disk.
fwdPkg := &common.FwdPkg{
Source: source,
State: common.FwdStateLockedIn,
Height: height,
Adds: adds,
AckFilter: ackFilter,
SettleFails: failSettles,
SettleFailFilter: settleFailFilter,
}
// Check to see if we have written the set exported filter adds to
// disk. If we haven't, processing of this package was never started, or
// failed during the last attempt.
fwdFilterBytes := heightBkt.Get(fwdFilterKey)
if fwdFilterBytes == nil {
nAdds := uint16(len(adds))
fwdPkg.FwdFilter = common.NewPkgFilter(nAdds)
return fwdPkg, nil
}
fwdFilterReader := bytes.NewReader(fwdFilterBytes)
fwdPkg.FwdFilter = &common.PkgFilter{}
if err := fwdPkg.FwdFilter.Decode(fwdFilterReader); err != nil {
return nil, err
}
// Otherwise, a complete round of processing was completed, and we
// advance the package to FwdStateProcessed.
fwdPkg.State = common.FwdStateProcessed
// If every add, settle, and fail has been fully acknowledged, we can
// safely set the package's state to FwdStateCompleted, signalling that
// it can be garbage collected.
if fwdPkg.AckFilter.IsFull() && fwdPkg.SettleFailFilter.IsFull() {
fwdPkg.State = common.FwdStateCompleted
}
return fwdPkg, nil
}
// loadHtlcs retrieves all serialized htlcs in a bucket, returning
// them in order of the indexes they were written under.
func loadHtlcs(bkt kvdb.RBucket) ([]common.LogUpdate, error) {
var htlcs []common.LogUpdate
if err := bkt.ForEach(func(_, v []byte) error {
htlc, err := deserializeLogUpdate(bytes.NewReader(v))
if err != nil {
return err
}
htlcs = append(htlcs, *htlc)
return nil
}); err != nil {
return nil, err
}
return htlcs, nil
}
// serializeLogUpdate writes a log update to the provided io.Writer.
func serializeLogUpdate(w io.Writer, l *common.LogUpdate) error {
return WriteElements(w, l.LogIndex, l.UpdateMsg)
}
// deserializeLogUpdate reads a log update from the provided io.Reader.
func deserializeLogUpdate(r io.Reader) (*common.LogUpdate, error) {
l := &common.LogUpdate{}
if err := ReadElements(r, &l.LogIndex, &l.UpdateMsg); err != nil {
return nil, err
}
return l, nil
}
package legacy
import (
"encoding/binary"
"fmt"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
"github.com/lightningnetwork/lnd/channeldb/migration21/common"
"github.com/lightningnetwork/lnd/keychain"
)
var (
// Big endian is the preferred byte order, due to cursor scans over
// integer keys iterating in order.
byteOrder = binary.BigEndian
)
// writeOutpoint writes an outpoint to the passed writer using the minimal
// amount of bytes possible.
func writeOutpoint(w io.Writer, o *wire.OutPoint) error {
if _, err := w.Write(o.Hash[:]); err != nil {
return err
}
if err := binary.Write(w, byteOrder, o.Index); err != nil {
return err
}
return nil
}
// readOutpoint reads an outpoint from the passed reader that was previously
// written using the writeOutpoint struct.
func readOutpoint(r io.Reader, o *wire.OutPoint) error {
if _, err := io.ReadFull(r, o.Hash[:]); err != nil {
return err
}
if err := binary.Read(r, byteOrder, &o.Index); err != nil {
return err
}
return nil
}
// UnknownElementType is an error returned when the codec is unable to encode or
// decode a particular type.
type UnknownElementType struct {
method string
element interface{}
}
// NewUnknownElementType creates a new UnknownElementType error from the passed
// method name and element.
func NewUnknownElementType(method string, el interface{}) UnknownElementType {
return UnknownElementType{method: method, element: el}
}
// Error returns the name of the method that encountered the error, as well as
// the type that was unsupported.
func (e UnknownElementType) Error() string {
return fmt.Sprintf("Unknown type in %s: %T", e.method, e.element)
}
// WriteElement is a one-stop shop to write the big endian representation of
// any element which is to be serialized for storage on disk. The passed
// io.Writer should be backed by an appropriately sized byte slice, or be able
// to dynamically expand to accommodate additional data.
func WriteElement(w io.Writer, element interface{}) error {
switch e := element.(type) {
case keychain.KeyDescriptor:
if err := binary.Write(w, byteOrder, e.Family); err != nil {
return err
}
if err := binary.Write(w, byteOrder, e.Index); err != nil {
return err
}
if e.PubKey != nil {
if err := binary.Write(w, byteOrder, true); err != nil {
return fmt.Errorf("error writing serialized "+
"element: %w", err)
}
return WriteElement(w, e.PubKey)
}
return binary.Write(w, byteOrder, false)
case chainhash.Hash:
if _, err := w.Write(e[:]); err != nil {
return err
}
case common.ClosureType:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case wire.OutPoint:
return writeOutpoint(w, &e)
case lnwire.ShortChannelID:
if err := binary.Write(w, byteOrder, e.ToUint64()); err != nil {
return err
}
case lnwire.ChannelID:
if _, err := w.Write(e[:]); err != nil {
return err
}
case int64, uint64:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case uint32:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case int32:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case uint16:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case uint8:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case bool:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case btcutil.Amount:
if err := binary.Write(w, byteOrder, uint64(e)); err != nil {
return err
}
case lnwire.MilliSatoshi:
if err := binary.Write(w, byteOrder, uint64(e)); err != nil {
return err
}
case *btcec.PublicKey:
b := e.SerializeCompressed()
if _, err := w.Write(b); err != nil {
return err
}
case *wire.MsgTx:
return e.Serialize(w)
case [32]byte:
if _, err := w.Write(e[:]); err != nil {
return err
}
case []byte:
if err := wire.WriteVarBytes(w, 0, e); err != nil {
return err
}
case lnwire.Message:
if _, err := lnwire.WriteMessage(w, e, 0); err != nil {
return err
}
case lnwire.FundingFlag:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
default:
return UnknownElementType{"WriteElement", e}
}
return nil
}
// WriteElements is writes each element in the elements slice to the passed
// io.Writer using WriteElement.
func WriteElements(w io.Writer, elements ...interface{}) error {
for _, element := range elements {
err := WriteElement(w, element)
if err != nil {
return err
}
}
return nil
}
// ReadElement is a one-stop utility function to deserialize any datastructure
// encoded using the serialization format of the database.
func ReadElement(r io.Reader, element interface{}) error {
switch e := element.(type) {
case *chainhash.Hash:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *wire.OutPoint:
return readOutpoint(r, e)
case *lnwire.ShortChannelID:
var a uint64
if err := binary.Read(r, byteOrder, &a); err != nil {
return err
}
*e = lnwire.NewShortChanIDFromInt(a)
case *lnwire.ChannelID:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *int64, *uint64:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *uint32:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *int32:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *uint16:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *uint8:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *bool:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *btcutil.Amount:
var a uint64
if err := binary.Read(r, byteOrder, &a); err != nil {
return err
}
*e = btcutil.Amount(a)
case *lnwire.MilliSatoshi:
var a uint64
if err := binary.Read(r, byteOrder, &a); err != nil {
return err
}
*e = lnwire.MilliSatoshi(a)
case **btcec.PublicKey:
var b [btcec.PubKeyBytesLenCompressed]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
pubKey, err := btcec.ParsePubKey(b[:])
if err != nil {
return err
}
*e = pubKey
case **wire.MsgTx:
tx := wire.NewMsgTx(2)
if err := tx.Deserialize(r); err != nil {
return err
}
*e = tx
case *[32]byte:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *[]byte:
bytes, err := wire.ReadVarBytes(r, 0, 66000, "[]byte")
if err != nil {
return err
}
*e = bytes
case *lnwire.Message:
msg, err := lnwire.ReadMessage(r, 0)
if err != nil {
return err
}
*e = msg
case *lnwire.FundingFlag:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *common.ClosureType:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *keychain.KeyDescriptor:
if err := binary.Read(r, byteOrder, &e.Family); err != nil {
return err
}
if err := binary.Read(r, byteOrder, &e.Index); err != nil {
return err
}
var hasPubKey bool
if err := binary.Read(r, byteOrder, &hasPubKey); err != nil {
return err
}
if hasPubKey {
return ReadElement(r, &e.PubKey)
}
default:
return UnknownElementType{"ReadElement", e}
}
return nil
}
// ReadElements deserializes a variable number of elements into the passed
// io.Reader, with each element being deserialized according to the ReadElement
// function.
func ReadElements(r io.Reader, elements ...interface{}) error {
for _, element := range elements {
err := ReadElement(r, element)
if err != nil {
return err
}
}
return nil
}
package legacy
import (
"bytes"
"encoding/binary"
"errors"
"io"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
"github.com/lightningnetwork/lnd/channeldb/migration21/common"
"github.com/lightningnetwork/lnd/kvdb"
)
func deserializeHtlcs(r io.Reader) ([]common.HTLC, error) {
var numHtlcs uint16
if err := ReadElement(r, &numHtlcs); err != nil {
return nil, err
}
var htlcs []common.HTLC
if numHtlcs == 0 {
return htlcs, nil
}
htlcs = make([]common.HTLC, numHtlcs)
for i := uint16(0); i < numHtlcs; i++ {
if err := ReadElements(r,
&htlcs[i].Signature, &htlcs[i].RHash, &htlcs[i].Amt,
&htlcs[i].RefundTimeout, &htlcs[i].OutputIndex,
&htlcs[i].Incoming, &htlcs[i].OnionBlob,
&htlcs[i].HtlcIndex, &htlcs[i].LogIndex,
); err != nil {
return htlcs, err
}
}
return htlcs, nil
}
func DeserializeLogUpdates(r io.Reader) ([]common.LogUpdate, error) {
var numUpdates uint16
if err := binary.Read(r, byteOrder, &numUpdates); err != nil {
return nil, err
}
logUpdates := make([]common.LogUpdate, numUpdates)
for i := 0; i < int(numUpdates); i++ {
err := ReadElements(r,
&logUpdates[i].LogIndex, &logUpdates[i].UpdateMsg,
)
if err != nil {
return nil, err
}
}
return logUpdates, nil
}
func deserializeChanCommit(r io.Reader) (common.ChannelCommitment, error) {
var c common.ChannelCommitment
err := ReadElements(r,
&c.CommitHeight, &c.LocalLogIndex, &c.LocalHtlcIndex, &c.RemoteLogIndex,
&c.RemoteHtlcIndex, &c.LocalBalance, &c.RemoteBalance,
&c.CommitFee, &c.FeePerKw, &c.CommitTx, &c.CommitSig,
)
if err != nil {
return c, err
}
c.Htlcs, err = deserializeHtlcs(r)
if err != nil {
return c, err
}
return c, nil
}
func DeserializeCommitDiff(r io.Reader) (*common.CommitDiff, error) {
var (
d common.CommitDiff
err error
)
d.Commitment, err = deserializeChanCommit(r)
if err != nil {
return nil, err
}
d.CommitSig = &lnwire.CommitSig{}
if err := d.CommitSig.Decode(r, 0); err != nil {
return nil, err
}
d.LogUpdates, err = DeserializeLogUpdates(r)
if err != nil {
return nil, err
}
var numOpenRefs uint16
if err := binary.Read(r, byteOrder, &numOpenRefs); err != nil {
return nil, err
}
d.OpenedCircuitKeys = make([]common.CircuitKey, numOpenRefs)
for i := 0; i < int(numOpenRefs); i++ {
err := ReadElements(r,
&d.OpenedCircuitKeys[i].ChanID,
&d.OpenedCircuitKeys[i].HtlcID)
if err != nil {
return nil, err
}
}
var numClosedRefs uint16
if err := binary.Read(r, byteOrder, &numClosedRefs); err != nil {
return nil, err
}
d.ClosedCircuitKeys = make([]common.CircuitKey, numClosedRefs)
for i := 0; i < int(numClosedRefs); i++ {
err := ReadElements(r,
&d.ClosedCircuitKeys[i].ChanID,
&d.ClosedCircuitKeys[i].HtlcID)
if err != nil {
return nil, err
}
}
return &d, nil
}
func serializeHtlcs(b io.Writer, htlcs ...common.HTLC) error {
numHtlcs := uint16(len(htlcs))
if err := WriteElement(b, numHtlcs); err != nil {
return err
}
for _, htlc := range htlcs {
if err := WriteElements(b,
htlc.Signature, htlc.RHash, htlc.Amt, htlc.RefundTimeout,
htlc.OutputIndex, htlc.Incoming, htlc.OnionBlob,
htlc.HtlcIndex, htlc.LogIndex,
); err != nil {
return err
}
}
return nil
}
func serializeChanCommit(w io.Writer, c *common.ChannelCommitment) error {
if err := WriteElements(w,
c.CommitHeight, c.LocalLogIndex, c.LocalHtlcIndex,
c.RemoteLogIndex, c.RemoteHtlcIndex, c.LocalBalance,
c.RemoteBalance, c.CommitFee, c.FeePerKw, c.CommitTx,
c.CommitSig,
); err != nil {
return err
}
return serializeHtlcs(w, c.Htlcs...)
}
func SerializeLogUpdates(w io.Writer, logUpdates []common.LogUpdate) error {
numUpdates := uint16(len(logUpdates))
if err := binary.Write(w, byteOrder, numUpdates); err != nil {
return err
}
for _, diff := range logUpdates {
err := WriteElements(w, diff.LogIndex, diff.UpdateMsg)
if err != nil {
return err
}
}
return nil
}
func SerializeCommitDiff(w io.Writer, diff *common.CommitDiff) error { // nolint: dupl
if err := serializeChanCommit(w, &diff.Commitment); err != nil {
return err
}
if err := diff.CommitSig.Encode(w, 0); err != nil {
return err
}
if err := SerializeLogUpdates(w, diff.LogUpdates); err != nil {
return err
}
numOpenRefs := uint16(len(diff.OpenedCircuitKeys))
if err := binary.Write(w, byteOrder, numOpenRefs); err != nil {
return err
}
for _, openRef := range diff.OpenedCircuitKeys {
err := WriteElements(w, openRef.ChanID, openRef.HtlcID)
if err != nil {
return err
}
}
numClosedRefs := uint16(len(diff.ClosedCircuitKeys))
if err := binary.Write(w, byteOrder, numClosedRefs); err != nil {
return err
}
for _, closedRef := range diff.ClosedCircuitKeys {
err := WriteElements(w, closedRef.ChanID, closedRef.HtlcID)
if err != nil {
return err
}
}
return nil
}
func DeserializeNetworkResult(r io.Reader) (*common.NetworkResult, error) {
var (
err error
)
n := &common.NetworkResult{}
n.Msg, err = lnwire.ReadMessage(r, 0)
if err != nil {
return nil, err
}
if err := ReadElements(r,
&n.Unencrypted, &n.IsResolution,
); err != nil {
return nil, err
}
return n, nil
}
func SerializeNetworkResult(w io.Writer, n *common.NetworkResult) error {
if _, err := lnwire.WriteMessage(w, n.Msg, 0); err != nil {
return err
}
return WriteElements(w, n.Unencrypted, n.IsResolution)
}
func readChanConfig(b io.Reader, c *common.ChannelConfig) error { // nolint: dupl
return ReadElements(b,
&c.DustLimit, &c.MaxPendingAmount, &c.ChanReserve,
&c.MinHTLC, &c.MaxAcceptedHtlcs, &c.CsvDelay,
&c.MultiSigKey, &c.RevocationBasePoint,
&c.PaymentBasePoint, &c.DelayBasePoint,
&c.HtlcBasePoint,
)
}
func DeserializeCloseChannelSummary(
r io.Reader) (*common.ChannelCloseSummary, error) { // nolint: dupl
c := &common.ChannelCloseSummary{}
err := ReadElements(r,
&c.ChanPoint, &c.ShortChanID, &c.ChainHash, &c.ClosingTXID,
&c.CloseHeight, &c.RemotePub, &c.Capacity, &c.SettledBalance,
&c.TimeLockedBalance, &c.CloseType, &c.IsPending,
)
if err != nil {
return nil, err
}
// We'll now check to see if the channel close summary was encoded with
// any of the additional optional fields.
var hasNewFields bool
err = ReadElements(r, &hasNewFields)
if err != nil {
return nil, err
}
// If fields are not present, we can return.
if !hasNewFields {
return c, nil
}
// Otherwise read the new fields.
if err := ReadElements(r, &c.RemoteCurrentRevocation); err != nil {
return nil, err
}
if err := readChanConfig(r, &c.LocalChanConfig); err != nil {
return nil, err
}
// Finally, we'll attempt to read the next unrevoked commitment point
// for the remote party. If we closed the channel before receiving a
// funding locked message then this might not be present. A boolean
// indicating whether the field is present will come first.
var hasRemoteNextRevocation bool
err = ReadElements(r, &hasRemoteNextRevocation)
if err != nil {
return nil, err
}
// If this field was written, read it.
if hasRemoteNextRevocation {
err = ReadElements(r, &c.RemoteNextRevocation)
if err != nil {
return nil, err
}
}
// Check if we have a channel sync message to read.
var hasChanSyncMsg bool
err = ReadElements(r, &hasChanSyncMsg)
if err == io.EOF {
return c, nil
} else if err != nil {
return nil, err
}
// If a chan sync message is present, read it.
if hasChanSyncMsg {
// We must pass in reference to a lnwire.Message for the codec
// to support it.
msg, err := lnwire.ReadMessage(r, 0)
if err != nil {
return nil, err
}
chanSync, ok := msg.(*lnwire.ChannelReestablish)
if !ok {
return nil, errors.New("unable cast db Message to " +
"ChannelReestablish")
}
c.LastChanSyncMsg = chanSync
}
return c, nil
}
func writeChanConfig(b io.Writer, c *common.ChannelConfig) error { // nolint: dupl
return WriteElements(b,
c.DustLimit, c.MaxPendingAmount, c.ChanReserve, c.MinHTLC,
c.MaxAcceptedHtlcs, c.CsvDelay, c.MultiSigKey,
c.RevocationBasePoint, c.PaymentBasePoint, c.DelayBasePoint,
c.HtlcBasePoint,
)
}
func SerializeChannelCloseSummary(w io.Writer, cs *common.ChannelCloseSummary) error {
err := WriteElements(w,
cs.ChanPoint, cs.ShortChanID, cs.ChainHash, cs.ClosingTXID,
cs.CloseHeight, cs.RemotePub, cs.Capacity, cs.SettledBalance,
cs.TimeLockedBalance, cs.CloseType, cs.IsPending,
)
if err != nil {
return err
}
// If this is a close channel summary created before the addition of
// the new fields, then we can exit here.
if cs.RemoteCurrentRevocation == nil {
return WriteElements(w, false)
}
// If fields are present, write boolean to indicate this, and continue.
if err := WriteElements(w, true); err != nil {
return err
}
if err := WriteElements(w, cs.RemoteCurrentRevocation); err != nil {
return err
}
if err := writeChanConfig(w, &cs.LocalChanConfig); err != nil {
return err
}
// The RemoteNextRevocation field is optional, as it's possible for a
// channel to be closed before we learn of the next unrevoked
// revocation point for the remote party. Write a boolean indicating
// whether this field is present or not.
if err := WriteElements(w, cs.RemoteNextRevocation != nil); err != nil {
return err
}
// Write the field, if present.
if cs.RemoteNextRevocation != nil {
if err = WriteElements(w, cs.RemoteNextRevocation); err != nil {
return err
}
}
// Write whether the channel sync message is present.
if err := WriteElements(w, cs.LastChanSyncMsg != nil); err != nil {
return err
}
// Write the channel sync message, if present.
if cs.LastChanSyncMsg != nil {
_, err = lnwire.WriteMessage(w, cs.LastChanSyncMsg, 0)
if err != nil {
return err
}
}
return nil
}
// ErrCorruptedFwdPkg signals that the on-disk structure of the forwarding
// package has potentially been mangled.
var ErrCorruptedFwdPkg = errors.New("fwding package db has been corrupted")
var (
// fwdPackagesKey is the root-level bucket that all forwarding packages
// are written. This bucket is further subdivided based on the short
// channel ID of each channel.
fwdPackagesKey = []byte("fwd-packages")
// addBucketKey is the bucket to which all Add log updates are written.
addBucketKey = []byte("add-updates")
// failSettleBucketKey is the bucket to which all Settle/Fail log
// updates are written.
failSettleBucketKey = []byte("fail-settle-updates")
// fwdFilterKey is a key used to write the set of Adds that passed
// validation and are to be forwarded to the switch.
// NOTE: The presence of this key within a forwarding package indicates
// that the package has reached FwdStateProcessed.
fwdFilterKey = []byte("fwd-filter-key")
// ackFilterKey is a key used to access the PkgFilter indicating which
// Adds have received a Settle/Fail. This response may come from a
// number of sources, including: exitHop settle/fails, switch failures,
// chain arbiter interjections, as well as settle/fails from the
// next hop in the route.
ackFilterKey = []byte("ack-filter-key")
// settleFailFilterKey is a key used to access the PkgFilter indicating
// which Settles/Fails in have been received and processed by the link
// that originally received the Add.
settleFailFilterKey = []byte("settle-fail-filter-key")
)
func makeLogKey(updateNum uint64) [8]byte {
var key [8]byte
byteOrder.PutUint64(key[:], updateNum)
return key
}
// uint16Key writes the provided 16-bit unsigned integer to a 2-byte slice.
func uint16Key(i uint16) []byte {
key := make([]byte, 2)
byteOrder.PutUint16(key, i)
return key
}
// ChannelPackager is used by a channel to manage the lifecycle of its forwarding
// packages. The packager is tied to a particular source channel ID, allowing it
// to create and edit its own packages. Each packager also has the ability to
// remove fail/settle htlcs that correspond to an add contained in one of
// source's packages.
type ChannelPackager struct {
source lnwire.ShortChannelID
}
// NewChannelPackager creates a new packager for a single channel.
func NewChannelPackager(source lnwire.ShortChannelID) *ChannelPackager {
return &ChannelPackager{
source: source,
}
}
// AddFwdPkg writes a newly locked in forwarding package to disk.
func (*ChannelPackager) AddFwdPkg(tx kvdb.RwTx, fwdPkg *common.FwdPkg) error { // nolint: dupl
fwdPkgBkt, err := tx.CreateTopLevelBucket(fwdPackagesKey)
if err != nil {
return err
}
source := makeLogKey(fwdPkg.Source.ToUint64())
sourceBkt, err := fwdPkgBkt.CreateBucketIfNotExists(source[:])
if err != nil {
return err
}
heightKey := makeLogKey(fwdPkg.Height)
heightBkt, err := sourceBkt.CreateBucketIfNotExists(heightKey[:])
if err != nil {
return err
}
// Write ADD updates we received at this commit height.
addBkt, err := heightBkt.CreateBucketIfNotExists(addBucketKey)
if err != nil {
return err
}
// Write SETTLE/FAIL updates we received at this commit height.
failSettleBkt, err := heightBkt.CreateBucketIfNotExists(failSettleBucketKey)
if err != nil {
return err
}
for i := range fwdPkg.Adds {
err = putLogUpdate(addBkt, uint16(i), &fwdPkg.Adds[i])
if err != nil {
return err
}
}
// Persist the initialized pkg filter, which will be used to determine
// when we can remove this forwarding package from disk.
var ackFilterBuf bytes.Buffer
if err := fwdPkg.AckFilter.Encode(&ackFilterBuf); err != nil {
return err
}
if err := heightBkt.Put(ackFilterKey, ackFilterBuf.Bytes()); err != nil {
return err
}
for i := range fwdPkg.SettleFails {
err = putLogUpdate(failSettleBkt, uint16(i), &fwdPkg.SettleFails[i])
if err != nil {
return err
}
}
var settleFailFilterBuf bytes.Buffer
err = fwdPkg.SettleFailFilter.Encode(&settleFailFilterBuf)
if err != nil {
return err
}
return heightBkt.Put(settleFailFilterKey, settleFailFilterBuf.Bytes())
}
// putLogUpdate writes an htlc to the provided `bkt`, using `index` as the key.
func putLogUpdate(bkt kvdb.RwBucket, idx uint16, htlc *common.LogUpdate) error {
var b bytes.Buffer
if err := serializeLogUpdate(&b, htlc); err != nil {
return err
}
return bkt.Put(uint16Key(idx), b.Bytes())
}
// LoadFwdPkgs scans the forwarding log for any packages that haven't been
// processed, and returns their deserialized log updates in a map indexed by the
// remote commitment height at which the updates were locked in.
func (p *ChannelPackager) LoadFwdPkgs(tx kvdb.RTx) ([]*common.FwdPkg, error) {
return loadChannelFwdPkgs(tx, p.source)
}
// loadChannelFwdPkgs loads all forwarding packages owned by `source`.
func loadChannelFwdPkgs(tx kvdb.RTx, source lnwire.ShortChannelID) ([]*common.FwdPkg, error) { // nolint: dupl
fwdPkgBkt := tx.ReadBucket(fwdPackagesKey)
if fwdPkgBkt == nil {
return nil, nil
}
sourceKey := makeLogKey(source.ToUint64())
sourceBkt := fwdPkgBkt.NestedReadBucket(sourceKey[:])
if sourceBkt == nil {
return nil, nil
}
var heights []uint64
if err := sourceBkt.ForEach(func(k, _ []byte) error {
if len(k) != 8 {
return ErrCorruptedFwdPkg
}
heights = append(heights, byteOrder.Uint64(k))
return nil
}); err != nil {
return nil, err
}
// Load the forwarding package for each retrieved height.
fwdPkgs := make([]*common.FwdPkg, 0, len(heights))
for _, height := range heights {
fwdPkg, err := loadFwdPkg(fwdPkgBkt, source, height)
if err != nil {
return nil, err
}
fwdPkgs = append(fwdPkgs, fwdPkg)
}
return fwdPkgs, nil
}
// loadFwdPkg reads the packager's fwd pkg at a given height, and determines the
// appropriate FwdState.
func loadFwdPkg(fwdPkgBkt kvdb.RBucket, source lnwire.ShortChannelID,
height uint64) (*common.FwdPkg, error) {
sourceKey := makeLogKey(source.ToUint64())
sourceBkt := fwdPkgBkt.NestedReadBucket(sourceKey[:])
if sourceBkt == nil {
return nil, ErrCorruptedFwdPkg
}
heightKey := makeLogKey(height)
heightBkt := sourceBkt.NestedReadBucket(heightKey[:])
if heightBkt == nil {
return nil, ErrCorruptedFwdPkg
}
// Load ADDs from disk.
addBkt := heightBkt.NestedReadBucket(addBucketKey)
if addBkt == nil {
return nil, ErrCorruptedFwdPkg
}
adds, err := loadHtlcs(addBkt)
if err != nil {
return nil, err
}
// Load ack filter from disk.
ackFilterBytes := heightBkt.Get(ackFilterKey)
if ackFilterBytes == nil {
return nil, ErrCorruptedFwdPkg
}
ackFilterReader := bytes.NewReader(ackFilterBytes)
ackFilter := &common.PkgFilter{}
if err := ackFilter.Decode(ackFilterReader); err != nil {
return nil, err
}
// Load SETTLE/FAILs from disk.
failSettleBkt := heightBkt.NestedReadBucket(failSettleBucketKey)
if failSettleBkt == nil {
return nil, ErrCorruptedFwdPkg
}
failSettles, err := loadHtlcs(failSettleBkt)
if err != nil {
return nil, err
}
// Load settle fail filter from disk.
settleFailFilterBytes := heightBkt.Get(settleFailFilterKey)
if settleFailFilterBytes == nil {
return nil, ErrCorruptedFwdPkg
}
settleFailFilterReader := bytes.NewReader(settleFailFilterBytes)
settleFailFilter := &common.PkgFilter{}
if err := settleFailFilter.Decode(settleFailFilterReader); err != nil {
return nil, err
}
// Initialize the fwding package, which always starts in the
// FwdStateLockedIn. We can determine what state the package was left in
// by examining constraints on the information loaded from disk.
fwdPkg := &common.FwdPkg{
Source: source,
State: common.FwdStateLockedIn,
Height: height,
Adds: adds,
AckFilter: ackFilter,
SettleFails: failSettles,
SettleFailFilter: settleFailFilter,
}
// Check to see if we have written the set exported filter adds to
// disk. If we haven't, processing of this package was never started, or
// failed during the last attempt.
fwdFilterBytes := heightBkt.Get(fwdFilterKey)
if fwdFilterBytes == nil {
nAdds := uint16(len(adds))
fwdPkg.FwdFilter = common.NewPkgFilter(nAdds)
return fwdPkg, nil
}
fwdFilterReader := bytes.NewReader(fwdFilterBytes)
fwdPkg.FwdFilter = &common.PkgFilter{}
if err := fwdPkg.FwdFilter.Decode(fwdFilterReader); err != nil {
return nil, err
}
// Otherwise, a complete round of processing was completed, and we
// advance the package to FwdStateProcessed.
fwdPkg.State = common.FwdStateProcessed
// If every add, settle, and fail has been fully acknowledged, we can
// safely set the package's state to FwdStateCompleted, signalling that
// it can be garbage collected.
if fwdPkg.AckFilter.IsFull() && fwdPkg.SettleFailFilter.IsFull() {
fwdPkg.State = common.FwdStateCompleted
}
return fwdPkg, nil
}
// loadHtlcs retrieves all serialized htlcs in a bucket, returning
// them in order of the indexes they were written under.
func loadHtlcs(bkt kvdb.RBucket) ([]common.LogUpdate, error) {
var htlcs []common.LogUpdate
if err := bkt.ForEach(func(_, v []byte) error {
htlc, err := deserializeLogUpdate(bytes.NewReader(v))
if err != nil {
return err
}
htlcs = append(htlcs, *htlc)
return nil
}); err != nil {
return nil, err
}
return htlcs, nil
}
// serializeLogUpdate writes a log update to the provided io.Writer.
func serializeLogUpdate(w io.Writer, l *common.LogUpdate) error {
return WriteElements(w, l.LogIndex, l.UpdateMsg)
}
// deserializeLogUpdate reads a log update from the provided io.Reader.
func deserializeLogUpdate(r io.Reader) (*common.LogUpdate, error) {
l := &common.LogUpdate{}
if err := ReadElements(r, &l.LogIndex, &l.UpdateMsg); err != nil {
return nil, err
}
return l, nil
}
package migration21
import (
"bytes"
"encoding/binary"
"fmt"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
"github.com/lightningnetwork/lnd/channeldb/migration21/common"
"github.com/lightningnetwork/lnd/channeldb/migration21/current"
"github.com/lightningnetwork/lnd/channeldb/migration21/legacy"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
byteOrder = binary.BigEndian
// openChanBucket stores all the currently open channels. This bucket
// has a second, nested bucket which is keyed by a node's ID. Within
// that node ID bucket, all attributes required to track, update, and
// close a channel are stored.
//
// openChan -> nodeID -> chanPoint
//
// TODO(roasbeef): flesh out comment
openChannelBucket = []byte("open-chan-bucket")
// commitDiffKey stores the current pending commitment state we've
// extended to the remote party (if any). Each time we propose a new
// state, we store the information necessary to reconstruct this state
// from the prior commitment. This allows us to resync the remote party
// to their expected state in the case of message loss.
//
// TODO(roasbeef): rename to commit chain?
commitDiffKey = []byte("commit-diff-key")
// unsignedAckedUpdatesKey is an entry in the channel bucket that
// contains the remote updates that we have acked, but not yet signed
// for in one of our remote commits.
unsignedAckedUpdatesKey = []byte("unsigned-acked-updates-key")
// remoteUnsignedLocalUpdatesKey is an entry in the channel bucket that
// contains the local updates that the remote party has acked, but
// has not yet signed for in one of their local commits.
remoteUnsignedLocalUpdatesKey = []byte("remote-unsigned-local-updates-key")
// networkResultStoreBucketKey is used for the root level bucket that
// stores the network result for each payment ID.
networkResultStoreBucketKey = []byte("network-result-store-bucket")
// closedChannelBucket stores summarization information concerning
// previously open, but now closed channels.
closedChannelBucket = []byte("closed-chan-bucket")
// fwdPackagesKey is the root-level bucket that all forwarding packages
// are written. This bucket is further subdivided based on the short
// channel ID of each channel.
fwdPackagesKey = []byte("fwd-packages")
)
// MigrateDatabaseWireMessages performs a migration in all areas that we
// currently store wire messages without length prefixes. This includes the
// CommitDiff struct, ChannelCloseSummary, LogUpdates, and also the
// networkResult struct as well.
func MigrateDatabaseWireMessages(tx kvdb.RwTx) error {
// The migration will proceed in three phases: we'll need to update any
// pending commit diffs, then any unsigned acked updates for all open
// channels, then finally we'll need to update all the current
// stored network results for payments in the switch.
//
// In this phase, we'll migrate the open channel data.
if err := migrateOpenChanBucket(tx); err != nil {
return err
}
// Next, we'll update all the present close channel summaries as well.
if err := migrateCloseChanSummaries(tx); err != nil {
return err
}
// We'll migrate forwarding packages, which have log updates as part of
// their serialized data.
if err := migrateForwardingPackages(tx); err != nil {
return err
}
// Finally, we'll update the pending network results as well.
return migrateNetworkResults(tx)
}
func migrateOpenChanBucket(tx kvdb.RwTx) error {
openChanBucket := tx.ReadWriteBucket(openChannelBucket)
// If no bucket is found, we can exit early.
if openChanBucket == nil {
return nil
}
type channelPath struct {
nodePub []byte
chainHash []byte
chanPoint []byte
}
var channelPaths []channelPath
err := openChanBucket.ForEach(func(nodePub, v []byte) error {
// Ensure that this is a key the same size as a pubkey, and
// also that it leads directly to a bucket.
if len(nodePub) != 33 || v != nil {
return nil
}
nodeChanBucket := openChanBucket.NestedReadBucket(nodePub)
if nodeChanBucket == nil {
return fmt.Errorf("no bucket for node %x", nodePub)
}
// The next layer down is all the chains that this node
// has channels on with us.
return nodeChanBucket.ForEach(func(chainHash, v []byte) error {
// If there's a value, it's not a bucket so
// ignore it.
if v != nil {
return nil
}
chainBucket := nodeChanBucket.NestedReadBucket(
chainHash,
)
if chainBucket == nil {
return fmt.Errorf("unable to read "+
"bucket for chain=%x", chainHash)
}
return chainBucket.ForEach(func(chanPoint, v []byte) error {
// If there's a value, it's not a bucket so
// ignore it.
if v != nil {
return nil
}
channelPaths = append(channelPaths, channelPath{
nodePub: nodePub,
chainHash: chainHash,
chanPoint: chanPoint,
})
return nil
})
})
})
if err != nil {
return err
}
// Now that we have all the paths of the channel we need to migrate,
// we'll update all the state in a distinct step to avoid weird
// behavior from modifying buckets in a ForEach statement.
for _, channelPath := range channelPaths {
// First, we'll extract it from the node's chain bucket.
nodeChanBucket := openChanBucket.NestedReadWriteBucket(
channelPath.nodePub,
)
chainBucket := nodeChanBucket.NestedReadWriteBucket(
channelPath.chainHash,
)
chanBucket := chainBucket.NestedReadWriteBucket(
channelPath.chanPoint,
)
// At this point, we have the channel bucket now, so we'll
// check to see if this channel has a pending commitment or
// not.
commitDiffBytes := chanBucket.Get(commitDiffKey)
if commitDiffBytes != nil {
// Now that we have the commit diff in the _old_
// encoding, we'll write it back to disk using the new
// encoding which has a length prefix in front of the
// CommitSig.
commitDiff, err := legacy.DeserializeCommitDiff(
bytes.NewReader(commitDiffBytes),
)
if err != nil {
return err
}
var b bytes.Buffer
err = current.SerializeCommitDiff(&b, commitDiff)
if err != nil {
return err
}
err = chanBucket.Put(commitDiffKey, b.Bytes())
if err != nil {
return err
}
}
// With the commit diff migrated, we'll now check to see if
// there're any un-acked updates we need to migrate as well.
updateBytes := chanBucket.Get(unsignedAckedUpdatesKey)
if updateBytes != nil {
// We have un-acked updates we need to migrate so we'll
// decode then re-encode them here using the new
// format.
legacyUnackedUpdates, err := legacy.DeserializeLogUpdates(
bytes.NewReader(updateBytes),
)
if err != nil {
return err
}
var b bytes.Buffer
err = current.SerializeLogUpdates(&b, legacyUnackedUpdates)
if err != nil {
return err
}
err = chanBucket.Put(unsignedAckedUpdatesKey, b.Bytes())
if err != nil {
return err
}
}
// Remote unsigned updates as well.
updateBytes = chanBucket.Get(remoteUnsignedLocalUpdatesKey)
if updateBytes != nil {
legacyUnsignedUpdates, err := legacy.DeserializeLogUpdates(
bytes.NewReader(updateBytes),
)
if err != nil {
return err
}
var b bytes.Buffer
err = current.SerializeLogUpdates(&b, legacyUnsignedUpdates)
if err != nil {
return err
}
err = chanBucket.Put(remoteUnsignedLocalUpdatesKey, b.Bytes())
if err != nil {
return err
}
}
}
return nil
}
func migrateCloseChanSummaries(tx kvdb.RwTx) error {
closedChanBucket := tx.ReadWriteBucket(closedChannelBucket)
// Exit early if bucket is not found.
if closedChannelBucket == nil {
return nil
}
type closedChan struct {
chanKey []byte
summaryBytes []byte
}
var closedChans []closedChan
err := closedChanBucket.ForEach(func(k, v []byte) error {
closedChans = append(closedChans, closedChan{
chanKey: k,
summaryBytes: v,
})
return nil
})
if err != nil {
return err
}
for _, closedChan := range closedChans {
oldSummary, err := legacy.DeserializeCloseChannelSummary(
bytes.NewReader(closedChan.summaryBytes),
)
if err != nil {
return err
}
var newSummaryBytes bytes.Buffer
err = current.SerializeChannelCloseSummary(
&newSummaryBytes, oldSummary,
)
if err != nil {
return err
}
err = closedChanBucket.Put(
closedChan.chanKey, newSummaryBytes.Bytes(),
)
if err != nil {
return err
}
}
return nil
}
func migrateForwardingPackages(tx kvdb.RwTx) error {
fwdPkgBkt := tx.ReadWriteBucket(fwdPackagesKey)
// Exit early if bucket is not found.
if fwdPkgBkt == nil {
return nil
}
// We Go through the bucket and fetches all short channel IDs.
var sources []lnwire.ShortChannelID
err := fwdPkgBkt.ForEach(func(k, v []byte) error {
source := lnwire.NewShortChanIDFromInt(byteOrder.Uint64(k))
sources = append(sources, source)
return nil
})
if err != nil {
return err
}
// Now load all forwarding packages using the legacy encoding.
var pkgsToMigrate []*common.FwdPkg
for _, source := range sources {
packager := legacy.NewChannelPackager(source)
fwdPkgs, err := packager.LoadFwdPkgs(tx)
if err != nil {
return err
}
pkgsToMigrate = append(pkgsToMigrate, fwdPkgs...)
}
// Add back the packages using the current encoding.
for _, pkg := range pkgsToMigrate {
packager := current.NewChannelPackager(pkg.Source)
err := packager.AddFwdPkg(tx, pkg)
if err != nil {
return err
}
}
return nil
}
func migrateNetworkResults(tx kvdb.RwTx) error {
networkResults := tx.ReadWriteBucket(networkResultStoreBucketKey)
// Exit early if bucket is not found.
if networkResults == nil {
return nil
}
// Similar to the prior migrations, we'll do this one in two phases:
// we'll first grab all the keys we need to migrate in one loop, then
// update them all in another loop.
var netResultsToMigrate [][2][]byte
err := networkResults.ForEach(func(k, v []byte) error {
netResultsToMigrate = append(netResultsToMigrate, [2][]byte{
k, v,
})
return nil
})
if err != nil {
return err
}
for _, netResult := range netResultsToMigrate {
resKey := netResult[0]
resBytes := netResult[1]
oldResult, err := legacy.DeserializeNetworkResult(
bytes.NewReader(resBytes),
)
if err != nil {
return err
}
var newResultBuf bytes.Buffer
err = current.SerializeNetworkResult(&newResultBuf, oldResult)
if err != nil {
return err
}
err = networkResults.Put(resKey, newResultBuf.Bytes())
if err != nil {
return err
}
}
return nil
}
package migration23
import (
"fmt"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
// paymentsRootBucket is the name of the top-level bucket within the
// database that stores all data related to payments.
paymentsRootBucket = []byte("payments-root-bucket")
// paymentHtlcsBucket is a bucket where we'll store the information
// about the HTLCs that were attempted for a payment.
paymentHtlcsBucket = []byte("payment-htlcs-bucket")
// oldAttemptInfoKey is a key used in a HTLC's sub-bucket to store the
// info about the attempt that was done for the HTLC in question.
oldAttemptInfoKey = []byte("htlc-attempt-info")
// oldSettleInfoKey is a key used in a HTLC's sub-bucket to store the
// settle info, if any.
oldSettleInfoKey = []byte("htlc-settle-info")
// oldFailInfoKey is a key used in a HTLC's sub-bucket to store
// failure information, if any.
oldFailInfoKey = []byte("htlc-fail-info")
// htlcAttemptInfoKey is the key used as the prefix of an HTLC attempt
// to store the info about the attempt that was done for the HTLC in
// question. The HTLC attempt ID is concatenated at the end.
htlcAttemptInfoKey = []byte("ai")
// htlcSettleInfoKey is the key used as the prefix of an HTLC attempt
// settle info, if any. The HTLC attempt ID is concatenated at the end.
htlcSettleInfoKey = []byte("si")
// htlcFailInfoKey is the key used as the prefix of an HTLC attempt
// failure information, if any.The HTLC attempt ID is concatenated at
// the end.
htlcFailInfoKey = []byte("fi")
)
// htlcBucketKey creates a composite key from prefix and id where the result is
// simply the two concatenated. This is the exact copy from payments.go.
func htlcBucketKey(prefix, id []byte) []byte {
key := make([]byte, len(prefix)+len(id))
copy(key, prefix)
copy(key[len(prefix):], id)
return key
}
// MigrateHtlcAttempts will gather all htlc-attempt-info's, htlcs-settle-info's
// and htlcs-fail-info's from the attempt ID buckes and re-store them using the
// flattened keys to each payment's payment-htlcs-bucket.
func MigrateHtlcAttempts(tx kvdb.RwTx) error {
payments := tx.ReadWriteBucket(paymentsRootBucket)
if payments == nil {
return nil
}
// Collect all payment hashes so we can migrate payments one-by-one to
// avoid any bugs bbolt might have when invalidating cursors.
// For 100 million payments, this would need about 3 GiB memory so we
// should hopefully be fine for very large nodes too.
var paymentHashes []string
if err := payments.ForEach(func(hash, v []byte) error {
// Get the bucket which contains the payment, fail if the key
// does not have a bucket.
bucket := payments.NestedReadBucket(hash)
if bucket == nil {
return fmt.Errorf("key must be a bucket: '%v'",
string(paymentsRootBucket))
}
paymentHashes = append(paymentHashes, string(hash))
return nil
}); err != nil {
return err
}
for _, paymentHash := range paymentHashes {
payment := payments.NestedReadWriteBucket([]byte(paymentHash))
if payment.Get(paymentHtlcsBucket) != nil {
return fmt.Errorf("key must be a bucket: '%v'",
string(paymentHtlcsBucket))
}
htlcs := payment.NestedReadWriteBucket(paymentHtlcsBucket)
if htlcs == nil {
// Nothing to migrate for this payment.
continue
}
if err := migrateHtlcsBucket(htlcs); err != nil {
return err
}
}
return nil
}
// migrateHtlcsBucket is a helper to gather, transform and re-store htlc attempt
// key/values.
func migrateHtlcsBucket(htlcs kvdb.RwBucket) error {
// Collect attempt ids so that we can migrate attempts one-by-one
// to avoid any bugs bbolt might have when invalidating cursors.
var aids []string
// First we collect all htlc attempt ids.
if err := htlcs.ForEach(func(aid, v []byte) error {
aids = append(aids, string(aid))
return nil
}); err != nil {
return err
}
// Next we go over these attempts, fetch all data and migrate.
for _, aid := range aids {
aidKey := []byte(aid)
attempt := htlcs.NestedReadWriteBucket(aidKey)
if attempt == nil {
return fmt.Errorf("non bucket element '%v' in '%v' "+
"bucket", aidKey, string(paymentHtlcsBucket))
}
// Collect attempt/settle/fail infos.
attemptInfo := attempt.Get(oldAttemptInfoKey)
if len(attemptInfo) > 0 {
newKey := htlcBucketKey(htlcAttemptInfoKey, aidKey)
if err := htlcs.Put(newKey, attemptInfo); err != nil {
return err
}
}
settleInfo := attempt.Get(oldSettleInfoKey)
if len(settleInfo) > 0 {
newKey := htlcBucketKey(htlcSettleInfoKey, aidKey)
if err := htlcs.Put(newKey, settleInfo); err != nil {
return err
}
}
failInfo := attempt.Get(oldFailInfoKey)
if len(failInfo) > 0 {
newKey := htlcBucketKey(htlcFailInfoKey, aidKey)
if err := htlcs.Put(newKey, failInfo); err != nil {
return err
}
}
}
// Finally we delete old attempt buckets.
for _, aid := range aids {
if err := htlcs.DeleteNestedBucket([]byte(aid)); err != nil {
return err
}
}
return nil
}
package migration24
import (
"github.com/btcsuite/btclog/v2"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration24
import (
"bytes"
"encoding/binary"
"io"
mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
// closedChannelBucket stores summarization information concerning
// previously open, but now closed channels.
closedChannelBucket = []byte("closed-chan-bucket")
// fwdPackagesKey is the root-level bucket that all forwarding packages
// are written. This bucket is further subdivided based on the short
// channel ID of each channel.
fwdPackagesKey = []byte("fwd-packages")
)
// MigrateFwdPkgCleanup deletes all the forwarding packages of closed channels.
// It determines the closed channels by iterating closedChannelBucket. The
// ShortChanID found in the ChannelCloseSummary is then used as a key to query
// the forwarding packages bucket. If a match is found, it will be deleted.
func MigrateFwdPkgCleanup(tx kvdb.RwTx) error {
log.Infof("Deleting forwarding packages for closed channels")
// Find all closed channel summaries, which are stored in the
// closeBucket.
closeBucket := tx.ReadBucket(closedChannelBucket)
if closeBucket == nil {
return nil
}
var chanSummaries []*mig.ChannelCloseSummary
// appendSummary is a function closure to help put deserialized close
// summeries into chanSummaries.
appendSummary := func(_ []byte, summaryBytes []byte) error {
summaryReader := bytes.NewReader(summaryBytes)
chanSummary, err := deserializeCloseChannelSummary(
summaryReader,
)
if err != nil {
return err
}
// Skip pending channels
if chanSummary.IsPending {
return nil
}
chanSummaries = append(chanSummaries, chanSummary)
return nil
}
if err := closeBucket.ForEach(appendSummary); err != nil {
return err
}
// Now we will load the forwarding packages bucket, delete all the
// nested buckets whose source matches the ShortChanID found in the
// closed channel summeraries.
fwdPkgBkt := tx.ReadWriteBucket(fwdPackagesKey)
if fwdPkgBkt == nil {
return nil
}
// Iterate over all close channels and remove their forwarding packages.
for _, summery := range chanSummaries {
sourceBytes := MakeLogKey(summery.ShortChanID.ToUint64())
// First, we will try to find the corresponding bucket. If there
// is not a nested bucket matching the ShortChanID, we will skip
// it.
if fwdPkgBkt.NestedReadBucket(sourceBytes[:]) == nil {
continue
}
// Otherwise, wipe all the forwarding packages.
if err := fwdPkgBkt.DeleteNestedBucket(
sourceBytes[:],
); err != nil {
return err
}
}
log.Infof("Deletion of forwarding packages of closed channels " +
"complete! DB compaction is recommended to free up the" +
"disk space.")
return nil
}
// deserializeCloseChannelSummary will decode a CloseChannelSummary with no
// optional fields.
func deserializeCloseChannelSummary(
r io.Reader) (*mig.ChannelCloseSummary, error) {
c := &mig.ChannelCloseSummary{}
err := mig.ReadElements(
r,
&c.ChanPoint, &c.ShortChanID, &c.ChainHash, &c.ClosingTXID,
&c.CloseHeight, &c.RemotePub, &c.Capacity, &c.SettledBalance,
&c.TimeLockedBalance, &c.CloseType, &c.IsPending,
)
if err != nil {
return nil, err
}
return c, nil
}
// makeLogKey converts a uint64 into an 8 byte array.
func MakeLogKey(updateNum uint64) [8]byte {
var key [8]byte
binary.BigEndian.PutUint64(key[:], updateNum)
return key
}
package migration25
import (
"bytes"
"fmt"
"io"
"strconv"
"strings"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
mig24 "github.com/lightningnetwork/lnd/channeldb/migration24"
mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// A tlv type definition used to serialize and deserialize a KeyLocator
// from the database.
keyLocType tlv.Type = 1
)
var (
// chanCommitmentKey can be accessed within the sub-bucket for a
// particular channel. This key stores the up to date commitment state
// for a particular channel party. Appending a 0 to the end of this key
// indicates it's the commitment for the local party, and appending a 1
// to the end of this key indicates it's the commitment for the remote
// party.
chanCommitmentKey = []byte("chan-commitment-key")
// revocationLogBucketLegacy is the legacy bucket where we store the
// revocation log in old format.
revocationLogBucketLegacy = []byte("revocation-log-key")
// localUpfrontShutdownKey can be accessed within the bucket for a
// channel (identified by its chanPoint). This key stores an optional
// upfront shutdown script for the local peer.
localUpfrontShutdownKey = []byte("local-upfront-shutdown-key")
// remoteUpfrontShutdownKey can be accessed within the bucket for a
// channel (identified by its chanPoint). This key stores an optional
// upfront shutdown script for the remote peer.
remoteUpfrontShutdownKey = []byte("remote-upfront-shutdown-key")
// lastWasRevokeKey is a key that stores true when the last update we
// sent was a revocation and false when it was a commitment signature.
// This is nil in the case of new channels with no updates exchanged.
lastWasRevokeKey = []byte("last-was-revoke")
// ErrNoChanInfoFound is returned when a particular channel does not
// have any channels state.
ErrNoChanInfoFound = fmt.Errorf("no chan info found")
// ErrNoPastDeltas is returned when the channel delta bucket hasn't been
// created.
ErrNoPastDeltas = fmt.Errorf("channel has no recorded deltas")
// ErrLogEntryNotFound is returned when we cannot find a log entry at
// the height requested in the revocation log.
ErrLogEntryNotFound = fmt.Errorf("log entry not found")
// ErrNoCommitmentsFound is returned when a channel has not set
// commitment states.
ErrNoCommitmentsFound = fmt.Errorf("no commitments found")
)
// ChannelType is an enum-like type that describes one of several possible
// channel types. Each open channel is associated with a particular type as the
// channel type may determine how higher level operations are conducted such as
// fee negotiation, channel closing, the format of HTLCs, etc. Structure-wise,
// a ChannelType is a bit field, with each bit denoting a modification from the
// base channel type of single funder.
type ChannelType uint8
const (
// NOTE: iota isn't used here for this enum needs to be stable
// long-term as it will be persisted to the database.
// SingleFunderBit represents a channel wherein one party solely funds
// the entire capacity of the channel.
SingleFunderBit ChannelType = 0
// DualFunderBit represents a channel wherein both parties contribute
// funds towards the total capacity of the channel. The channel may be
// funded symmetrically or asymmetrically.
DualFunderBit ChannelType = 1 << 0
// SingleFunderTweaklessBit is similar to the basic SingleFunder channel
// type, but it omits the tweak for one's key in the commitment
// transaction of the remote party.
SingleFunderTweaklessBit ChannelType = 1 << 1
// NoFundingTxBit denotes if we have the funding transaction locally on
// disk. This bit may be on if the funding transaction was crafted by a
// wallet external to the primary daemon.
NoFundingTxBit ChannelType = 1 << 2
// AnchorOutputsBit indicates that the channel makes use of anchor
// outputs to bump the commitment transaction's effective feerate. This
// channel type also uses a delayed to_remote output script.
AnchorOutputsBit ChannelType = 1 << 3
// FrozenBit indicates that the channel is a frozen channel, meaning
// that only the responder can decide to cooperatively close the
// channel.
FrozenBit ChannelType = 1 << 4
// ZeroHtlcTxFeeBit indicates that the channel should use zero-fee
// second-level HTLC transactions.
ZeroHtlcTxFeeBit ChannelType = 1 << 5
// LeaseExpirationBit indicates that the channel has been leased for a
// period of time, constraining every output that pays to the channel
// initiator with an additional CLTV of the lease maturity.
LeaseExpirationBit ChannelType = 1 << 6
)
// IsSingleFunder returns true if the channel type if one of the known single
// funder variants.
func (c ChannelType) IsSingleFunder() bool {
return c&DualFunderBit == 0
}
// IsDualFunder returns true if the ChannelType has the DualFunderBit set.
func (c ChannelType) IsDualFunder() bool {
return c&DualFunderBit == DualFunderBit
}
// IsTweakless returns true if the target channel uses a commitment that
// doesn't tweak the key for the remote party.
func (c ChannelType) IsTweakless() bool {
return c&SingleFunderTweaklessBit == SingleFunderTweaklessBit
}
// HasFundingTx returns true if this channel type is one that has a funding
// transaction stored locally.
func (c ChannelType) HasFundingTx() bool {
return c&NoFundingTxBit == 0
}
// HasAnchors returns true if this channel type has anchor outputs on its
// commitment.
func (c ChannelType) HasAnchors() bool {
return c&AnchorOutputsBit == AnchorOutputsBit
}
// ZeroHtlcTxFee returns true if this channel type uses second-level HTLC
// transactions signed with zero-fee.
func (c ChannelType) ZeroHtlcTxFee() bool {
return c&ZeroHtlcTxFeeBit == ZeroHtlcTxFeeBit
}
// IsFrozen returns true if the channel is considered to be "frozen". A frozen
// channel means that only the responder can initiate a cooperative channel
// closure.
func (c ChannelType) IsFrozen() bool {
return c&FrozenBit == FrozenBit
}
// HasLeaseExpiration returns true if the channel originated from a lease.
func (c ChannelType) HasLeaseExpiration() bool {
return c&LeaseExpirationBit == LeaseExpirationBit
}
// ChannelStatus is a bit vector used to indicate whether an OpenChannel is in
// the default usable state, or a state where it shouldn't be used.
type ChannelStatus uint8
var (
// ChanStatusDefault is the normal state of an open channel.
ChanStatusDefault ChannelStatus
// ChanStatusBorked indicates that the channel has entered an
// irreconcilable state, triggered by a state desynchronization or
// channel breach. Channels in this state should never be added to the
// htlc switch.
ChanStatusBorked ChannelStatus = 1
// ChanStatusCommitBroadcasted indicates that a commitment for this
// channel has been broadcasted.
ChanStatusCommitBroadcasted ChannelStatus = 1 << 1
// ChanStatusLocalDataLoss indicates that we have lost channel state
// for this channel, and broadcasting our latest commitment might be
// considered a breach.
//
// TODO(halseh): actually enforce that we are not force closing such a
// channel.
ChanStatusLocalDataLoss ChannelStatus = 1 << 2
// ChanStatusRestored is a status flag that signals that the channel
// has been restored, and doesn't have all the fields a typical channel
// will have.
ChanStatusRestored ChannelStatus = 1 << 3
// ChanStatusCoopBroadcasted indicates that a cooperative close for
// this channel has been broadcasted. Older cooperatively closed
// channels will only have this status set. Newer ones will also have
// close initiator information stored using the local/remote initiator
// status. This status is set in conjunction with the initiator status
// so that we do not need to check multiple channel statues for
// cooperative closes.
ChanStatusCoopBroadcasted ChannelStatus = 1 << 4
// ChanStatusLocalCloseInitiator indicates that we initiated closing
// the channel.
ChanStatusLocalCloseInitiator ChannelStatus = 1 << 5
// ChanStatusRemoteCloseInitiator indicates that the remote node
// initiated closing the channel.
ChanStatusRemoteCloseInitiator ChannelStatus = 1 << 6
)
// chanStatusStrings maps a ChannelStatus to a human friendly string that
// describes that status.
var chanStatusStrings = map[ChannelStatus]string{
ChanStatusDefault: "ChanStatusDefault",
ChanStatusBorked: "ChanStatusBorked",
ChanStatusCommitBroadcasted: "ChanStatusCommitBroadcasted",
ChanStatusLocalDataLoss: "ChanStatusLocalDataLoss",
ChanStatusRestored: "ChanStatusRestored",
ChanStatusCoopBroadcasted: "ChanStatusCoopBroadcasted",
ChanStatusLocalCloseInitiator: "ChanStatusLocalCloseInitiator",
ChanStatusRemoteCloseInitiator: "ChanStatusRemoteCloseInitiator",
}
// orderedChanStatusFlags is an in-order list of all that channel status flags.
var orderedChanStatusFlags = []ChannelStatus{
ChanStatusBorked,
ChanStatusCommitBroadcasted,
ChanStatusLocalDataLoss,
ChanStatusRestored,
ChanStatusCoopBroadcasted,
ChanStatusLocalCloseInitiator,
ChanStatusRemoteCloseInitiator,
}
// String returns a human-readable representation of the ChannelStatus.
func (c ChannelStatus) String() string {
// If no flags are set, then this is the default case.
if c == ChanStatusDefault {
return chanStatusStrings[ChanStatusDefault]
}
// Add individual bit flags.
statusStr := ""
for _, flag := range orderedChanStatusFlags {
if c&flag == flag {
statusStr += chanStatusStrings[flag] + "|"
c -= flag
}
}
// Remove anything to the right of the final bar, including it as well.
statusStr = strings.TrimRight(statusStr, "|")
// Add any remaining flags which aren't accounted for as hex.
if c != 0 {
statusStr += "|0x" + strconv.FormatUint(uint64(c), 16)
}
// If this was purely an unknown flag, then remove the extra bar at the
// start of the string.
statusStr = strings.TrimLeft(statusStr, "|")
return statusStr
}
// OpenChannel embeds a mig.OpenChannel with the extra update-to-date fields.
//
// NOTE: doesn't have the Packager field as it's not used in current migration.
type OpenChannel struct {
mig.OpenChannel
// ChanType denotes which type of channel this is.
ChanType ChannelType
// ChanStatus is the current status of this channel. If it is not in
// the state Default, it should not be used for forwarding payments.
//
// NOTE: In `channeldb.OpenChannel`, this field is private. We choose
// to export this private field such that following migrations can
// access this field directly.
ChanStatus ChannelStatus
// InitialLocalBalance is the balance we have during the channel
// opening. When we are not the initiator, this value represents the
// push amount.
InitialLocalBalance lnwire.MilliSatoshi
// InitialRemoteBalance is the balance they have during the channel
// opening.
InitialRemoteBalance lnwire.MilliSatoshi
// LocalShutdownScript is set to a pre-set script if the channel was
// opened by the local node with option_upfront_shutdown_script set. If
// the option was not set, the field is empty.
LocalShutdownScript lnwire.DeliveryAddress
// RemoteShutdownScript is set to a pre-set script if the channel was
// opened by the remote node with option_upfront_shutdown_script set.
// If the option was not set, the field is empty.
RemoteShutdownScript lnwire.DeliveryAddress
// ThawHeight is the height when a frozen channel once again becomes a
// normal channel. If this is zero, then there're no restrictions on
// this channel. If the value is lower than 500,000, then it's
// interpreted as a relative height, or an absolute height otherwise.
ThawHeight uint32
// LastWasRevoke is a boolean that determines if the last update we
// sent was a revocation (true) or a commitment signature (false).
LastWasRevoke bool
// RevocationKeyLocator stores the KeyLocator information that we will
// need to derive the shachain root for this channel. This allows us to
// have private key isolation from lnd.
RevocationKeyLocator keychain.KeyLocator
}
func (c *OpenChannel) hasChanStatus(status ChannelStatus) bool {
// Special case ChanStatusDefualt since it isn't actually flag, but a
// particular combination (or lack-there-of) of flags.
if status == ChanStatusDefault {
return c.ChanStatus == ChanStatusDefault
}
return c.ChanStatus&status == status
}
// FundingTxPresent returns true if expect the funding transcation to be found
// on disk or already populated within the passed open channel struct.
func (c *OpenChannel) FundingTxPresent() bool {
chanType := c.ChanType
return chanType.IsSingleFunder() && chanType.HasFundingTx() &&
c.IsInitiator &&
!c.hasChanStatus(ChanStatusRestored)
}
// fetchChanInfo deserializes the channel info based on the legacy boolean.
func fetchChanInfo(chanBucket kvdb.RBucket, c *OpenChannel, legacy bool) error {
infoBytes := chanBucket.Get(chanInfoKey)
if infoBytes == nil {
return ErrNoChanInfoFound
}
r := bytes.NewReader(infoBytes)
var (
chanType mig.ChannelType
chanStatus mig.ChannelStatus
)
if err := mig.ReadElements(r,
&chanType, &c.ChainHash, &c.FundingOutpoint,
&c.ShortChannelID, &c.IsPending, &c.IsInitiator,
&chanStatus, &c.FundingBroadcastHeight,
&c.NumConfsRequired, &c.ChannelFlags,
&c.IdentityPub, &c.Capacity, &c.TotalMSatSent,
&c.TotalMSatReceived,
); err != nil {
return err
}
c.ChanType = ChannelType(chanType)
c.ChanStatus = ChannelStatus(chanStatus)
// If this is not the legacy format, we need to read the extra two new
// fields.
if !legacy {
if err := mig.ReadElements(r,
&c.InitialLocalBalance, &c.InitialRemoteBalance,
); err != nil {
return err
}
}
// For single funder channels that we initiated and have the funding
// transaction to, read the funding txn.
if c.FundingTxPresent() {
if err := mig.ReadElement(r, &c.FundingTxn); err != nil {
return err
}
}
if err := mig.ReadChanConfig(r, &c.LocalChanCfg); err != nil {
return err
}
if err := mig.ReadChanConfig(r, &c.RemoteChanCfg); err != nil {
return err
}
// Retrieve the boolean stored under lastWasRevokeKey.
lastWasRevokeBytes := chanBucket.Get(lastWasRevokeKey)
if lastWasRevokeBytes == nil {
// If nothing has been stored under this key, we store false in
// the OpenChannel struct.
c.LastWasRevoke = false
} else {
// Otherwise, read the value into the LastWasRevoke field.
revokeReader := bytes.NewReader(lastWasRevokeBytes)
err := mig.ReadElements(revokeReader, &c.LastWasRevoke)
if err != nil {
return err
}
}
keyLocRecord := MakeKeyLocRecord(keyLocType, &c.RevocationKeyLocator)
tlvStream, err := tlv.NewStream(keyLocRecord)
if err != nil {
return err
}
if err := tlvStream.Decode(r); err != nil {
return err
}
// Finally, read the optional shutdown scripts.
if err := GetOptionalUpfrontShutdownScript(
chanBucket, localUpfrontShutdownKey, &c.LocalShutdownScript,
); err != nil {
return err
}
return GetOptionalUpfrontShutdownScript(
chanBucket, remoteUpfrontShutdownKey, &c.RemoteShutdownScript,
)
}
// fetchChanInfo serializes the channel info based on the legacy boolean and
// saves it to disk.
func putChanInfo(chanBucket kvdb.RwBucket, c *OpenChannel, legacy bool) error {
var w bytes.Buffer
if err := mig.WriteElements(&w,
mig.ChannelType(c.ChanType), c.ChainHash, c.FundingOutpoint,
c.ShortChannelID, c.IsPending, c.IsInitiator,
mig.ChannelStatus(c.ChanStatus), c.FundingBroadcastHeight,
c.NumConfsRequired, c.ChannelFlags,
c.IdentityPub, c.Capacity, c.TotalMSatSent,
c.TotalMSatReceived,
); err != nil {
return err
}
// If this is not legacy format, we need to write the extra two fields.
if !legacy {
if err := mig.WriteElements(&w,
c.InitialLocalBalance, c.InitialRemoteBalance,
); err != nil {
return err
}
}
// For single funder channels that we initiated, and we have the
// funding transaction, then write the funding txn.
if c.FundingTxPresent() {
if err := mig.WriteElement(&w, c.FundingTxn); err != nil {
return err
}
}
if err := mig.WriteChanConfig(&w, &c.LocalChanCfg); err != nil {
return err
}
if err := mig.WriteChanConfig(&w, &c.RemoteChanCfg); err != nil {
return err
}
// Write the RevocationKeyLocator as the first entry in a tlv stream.
keyLocRecord := MakeKeyLocRecord(
keyLocType, &c.RevocationKeyLocator,
)
tlvStream, err := tlv.NewStream(keyLocRecord)
if err != nil {
return err
}
if err := tlvStream.Encode(&w); err != nil {
return err
}
if err := chanBucket.Put(chanInfoKey, w.Bytes()); err != nil {
return err
}
// Finally, add optional shutdown scripts for the local and remote peer
// if they are present.
if err := PutOptionalUpfrontShutdownScript(
chanBucket, localUpfrontShutdownKey, c.LocalShutdownScript,
); err != nil {
return err
}
return PutOptionalUpfrontShutdownScript(
chanBucket, remoteUpfrontShutdownKey, c.RemoteShutdownScript,
)
}
// EKeyLocator is an encoder for keychain.KeyLocator.
func EKeyLocator(w io.Writer, val interface{}, buf *[8]byte) error {
if v, ok := val.(*keychain.KeyLocator); ok {
err := tlv.EUint32T(w, uint32(v.Family), buf)
if err != nil {
return err
}
return tlv.EUint32T(w, v.Index, buf)
}
return tlv.NewTypeForEncodingErr(val, "keychain.KeyLocator")
}
// DKeyLocator is a decoder for keychain.KeyLocator.
func DKeyLocator(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
if v, ok := val.(*keychain.KeyLocator); ok {
var family uint32
err := tlv.DUint32(r, &family, buf, 4)
if err != nil {
return err
}
v.Family = keychain.KeyFamily(family)
return tlv.DUint32(r, &v.Index, buf, 4)
}
return tlv.NewTypeForDecodingErr(val, "keychain.KeyLocator", l, 8)
}
// MakeKeyLocRecord creates a Record out of a KeyLocator using the passed
// Type and the EKeyLocator and DKeyLocator functions. The size will always be
// 8 as KeyFamily is uint32 and the Index is uint32.
func MakeKeyLocRecord(typ tlv.Type, keyLoc *keychain.KeyLocator) tlv.Record {
return tlv.MakeStaticRecord(typ, keyLoc, 8, EKeyLocator, DKeyLocator)
}
// PutOptionalUpfrontShutdownScript adds a shutdown script under the key
// provided if it has a non-zero length.
func PutOptionalUpfrontShutdownScript(chanBucket kvdb.RwBucket, key []byte,
script []byte) error {
// If the script is empty, we do not need to add anything.
if len(script) == 0 {
return nil
}
var w bytes.Buffer
if err := mig.WriteElement(&w, script); err != nil {
return err
}
return chanBucket.Put(key, w.Bytes())
}
// GetOptionalUpfrontShutdownScript reads the shutdown script stored under the
// key provided if it is present. Upfront shutdown scripts are optional, so the
// function returns with no error if the key is not present.
func GetOptionalUpfrontShutdownScript(chanBucket kvdb.RBucket, key []byte,
script *lnwire.DeliveryAddress) error {
// Return early if the bucket does not exit, a shutdown script was not
// set.
bs := chanBucket.Get(key)
if bs == nil {
return nil
}
var tempScript []byte
r := bytes.NewReader(bs)
if err := mig.ReadElement(r, &tempScript); err != nil {
return err
}
*script = tempScript
return nil
}
// FetchChanCommitments fetches both the local and remote commitments. This
// function is exported so it can be used by later migrations.
func FetchChanCommitments(chanBucket kvdb.RBucket, channel *OpenChannel) error {
var err error
// If this is a restored channel, then we don't have any commitments to
// read.
if channel.hasChanStatus(ChanStatusRestored) {
return nil
}
channel.LocalCommitment, err = FetchChanCommitment(chanBucket, true)
if err != nil {
return err
}
channel.RemoteCommitment, err = FetchChanCommitment(chanBucket, false)
if err != nil {
return err
}
return nil
}
// FetchChanCommitment fetches a channel commitment. This function is exported
// so it can be used by later migrations.
func FetchChanCommitment(chanBucket kvdb.RBucket,
local bool) (mig.ChannelCommitment, error) {
commitKey := chanCommitmentKey
if local {
commitKey = append(commitKey, byte(0x00))
} else {
commitKey = append(commitKey, byte(0x01))
}
commitBytes := chanBucket.Get(commitKey)
if commitBytes == nil {
return mig.ChannelCommitment{}, ErrNoCommitmentsFound
}
r := bytes.NewReader(commitBytes)
return mig.DeserializeChanCommit(r)
}
func PutChanCommitment(chanBucket kvdb.RwBucket, c *mig.ChannelCommitment,
local bool) error {
commitKey := chanCommitmentKey
if local {
commitKey = append(commitKey, byte(0x00))
} else {
commitKey = append(commitKey, byte(0x01))
}
var b bytes.Buffer
if err := mig.SerializeChanCommit(&b, c); err != nil {
return err
}
return chanBucket.Put(commitKey, b.Bytes())
}
func PutChanCommitments(chanBucket kvdb.RwBucket, channel *OpenChannel) error {
// If this is a restored channel, then we don't have any commitments to
// write.
if channel.hasChanStatus(ChanStatusRestored) {
return nil
}
err := PutChanCommitment(
chanBucket, &channel.LocalCommitment, true,
)
if err != nil {
return err
}
return PutChanCommitment(
chanBucket, &channel.RemoteCommitment, false,
)
}
// balancesAtHeight returns the local and remote balances on our commitment
// transactions as of a given height. This function is not exported as it's
// deprecated.
//
// NOTE: these are our balances *after* subtracting the commitment fee and
// anchor outputs.
func (c *OpenChannel) balancesAtHeight(chanBucket kvdb.RBucket,
height uint64) (lnwire.MilliSatoshi, lnwire.MilliSatoshi, error) {
// If our current commit is as the desired height, we can return our
// current balances.
if c.LocalCommitment.CommitHeight == height {
return c.LocalCommitment.LocalBalance,
c.LocalCommitment.RemoteBalance, nil
}
// If our current remote commit is at the desired height, we can return
// the current balances.
if c.RemoteCommitment.CommitHeight == height {
return c.RemoteCommitment.LocalBalance,
c.RemoteCommitment.RemoteBalance, nil
}
// If we are not currently on the height requested, we need to look up
// the previous height to obtain our balances at the given height.
commit, err := c.FindPreviousStateLegacy(chanBucket, height)
if err != nil {
return 0, 0, err
}
return commit.LocalBalance, commit.RemoteBalance, nil
}
// FindPreviousStateLegacy scans through the append-only log in an attempt to
// recover the previous channel state indicated by the update number. This
// method is intended to be used for obtaining the relevant data needed to
// claim all funds rightfully spendable in the case of an on-chain broadcast of
// the commitment transaction.
func (c *OpenChannel) FindPreviousStateLegacy(chanBucket kvdb.RBucket,
updateNum uint64) (*mig.ChannelCommitment, error) {
c.RLock()
defer c.RUnlock()
logBucket := chanBucket.NestedReadBucket(revocationLogBucketLegacy)
if logBucket == nil {
return nil, ErrNoPastDeltas
}
commit, err := fetchChannelLogEntry(logBucket, updateNum)
if err != nil {
return nil, err
}
return &commit, nil
}
func fetchChannelLogEntry(log kvdb.RBucket,
updateNum uint64) (mig.ChannelCommitment, error) {
logEntrykey := mig24.MakeLogKey(updateNum)
commitBytes := log.Get(logEntrykey[:])
if commitBytes == nil {
return mig.ChannelCommitment{}, ErrLogEntryNotFound
}
commitReader := bytes.NewReader(commitBytes)
return mig.DeserializeChanCommit(commitReader)
}
func CreateChanBucket(tx kvdb.RwTx, c *OpenChannel) (kvdb.RwBucket, error) {
// First fetch the top level bucket which stores all data related to
// current, active channels.
openChanBucket, err := tx.CreateTopLevelBucket(openChannelBucket)
if err != nil {
return nil, err
}
// Within this top level bucket, fetch the bucket dedicated to storing
// open channel data specific to the remote node.
nodePub := c.IdentityPub.SerializeCompressed()
nodeChanBucket, err := openChanBucket.CreateBucketIfNotExists(nodePub)
if err != nil {
return nil, err
}
// We'll then recurse down an additional layer in order to fetch the
// bucket for this particular chain.
chainBucket, err := nodeChanBucket.CreateBucketIfNotExists(
c.ChainHash[:],
)
if err != nil {
return nil, err
}
var chanPointBuf bytes.Buffer
err = mig.WriteOutpoint(&chanPointBuf, &c.FundingOutpoint)
if err != nil {
return nil, err
}
// With the bucket for the node fetched, we can now go down another
// level, creating the bucket for this channel itself.
return chainBucket.CreateBucketIfNotExists(chanPointBuf.Bytes())
}
package migration25
import (
"github.com/btcsuite/btclog/v2"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration25
import (
"bytes"
"fmt"
mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
// openChanBucket stores all the currently open channels. This bucket
// has a second, nested bucket which is keyed by a node's ID. Within
// that node ID bucket, all attributes required to track, update, and
// close a channel are stored.
openChannelBucket = []byte("open-chan-bucket")
// chanInfoKey can be accessed within the bucket for a channel
// (identified by its chanPoint). This key stores all the static
// information for a channel which is decided at the end of the
// funding flow.
chanInfoKey = []byte("chan-info-key")
// ErrNoChanDBExists is returned when a channel bucket hasn't been
// created.
ErrNoChanDBExists = fmt.Errorf("channel db has not yet been created")
// ErrNoActiveChannels is returned when there is no active (open)
// channels within the database.
ErrNoActiveChannels = fmt.Errorf("no active channels exist")
// ErrChannelNotFound is returned when we attempt to locate a channel
// for a specific chain, but it is not found.
ErrChannelNotFound = fmt.Errorf("channel not found")
)
// MigrateInitialBalances patches the two new fields, InitialLocalBalance and
// InitialRemoteBalance, for all the open channels. It does so by reading the
// revocation log at height 0 to learn the initial balances and then updates
// the channel's info.
// The channel info is saved in the nested bucket which is accessible via
// nodePub:chainHash:chanPoint. If any of the sub-buckets turns out to be nil,
// we will log the error and continue to process the rest.
func MigrateInitialBalances(tx kvdb.RwTx) error {
log.Infof("Migrating initial local and remote balances...")
openChanBucket := tx.ReadWriteBucket(openChannelBucket)
// If no bucket is found, we can exit early.
if openChanBucket == nil {
return nil
}
// Read a list of open channels.
channels, err := findOpenChannels(openChanBucket)
if err != nil {
return err
}
// Migrate the balances.
for _, c := range channels {
if err := migrateBalances(tx, c); err != nil {
return err
}
}
return err
}
// findOpenChannels finds all open channels.
func findOpenChannels(openChanBucket kvdb.RBucket) ([]*OpenChannel, error) {
channels := []*OpenChannel{}
// readChannel is a helper closure that reads the channel info from the
// channel bucket.
readChannel := func(chainBucket kvdb.RBucket, cp []byte) error {
c := &OpenChannel{}
// Read the sub-bucket level 3.
chanBucket := chainBucket.NestedReadBucket(
cp,
)
if chanBucket == nil {
log.Errorf("unable to read bucket for chanPoint=%x", cp)
return nil
}
// Get the old channel info.
if err := fetchChanInfo(chanBucket, c, true); err != nil {
return fmt.Errorf("unable to fetch chan info: %w", err)
}
// Fetch the channel commitments, which are useful for freshly
// open channels as they don't have any revocation logs and
// their current commitments reflect the initial balances.
if err := FetchChanCommitments(chanBucket, c); err != nil {
return fmt.Errorf("unable to fetch chan commits: %w",
err)
}
channels = append(channels, c)
return nil
}
// Iterate the root bucket.
err := openChanBucket.ForEach(func(nodePub, v []byte) error {
// Ensure that this is a key the same size as a pubkey, and
// also that it leads directly to a bucket.
if len(nodePub) != 33 || v != nil {
return nil
}
// Read the sub-bucket level 1.
nodeChanBucket := openChanBucket.NestedReadBucket(nodePub)
if nodeChanBucket == nil {
log.Errorf("no bucket for node %x", nodePub)
return nil
}
// Iterate the bucket.
return nodeChanBucket.ForEach(func(chainHash, _ []byte) error {
// Read the sub-bucket level 2.
chainBucket := nodeChanBucket.NestedReadBucket(
chainHash,
)
if chainBucket == nil {
log.Errorf("unable to read bucket for chain=%x",
chainHash)
return nil
}
// Iterate the bucket.
return chainBucket.ForEach(func(cp, _ []byte) error {
return readChannel(chainBucket, cp)
})
})
})
if err != nil {
return nil, err
}
return channels, nil
}
// migrateBalances queries the revocation log at height 0 to find the initial
// balances and save them to the channel info.
func migrateBalances(tx kvdb.RwTx, c *OpenChannel) error {
// Get the bucket.
chanBucket, err := FetchChanBucket(tx, c)
if err != nil {
return err
}
// Get the initial balances.
localAmt, remoteAmt, err := c.balancesAtHeight(chanBucket, 0)
if err != nil {
return fmt.Errorf("unable to get initial balances: %w", err)
}
c.InitialLocalBalance = localAmt
c.InitialRemoteBalance = remoteAmt
// Update the channel info.
if err := putChanInfo(chanBucket, c, false); err != nil {
return fmt.Errorf("unable to put chan info: %w", err)
}
return nil
}
// FetchChanBucket is a helper function that returns the bucket where a
// channel's data resides in given: the public key for the node, the outpoint,
// and the chainhash that the channel resides on.
func FetchChanBucket(tx kvdb.RwTx, c *OpenChannel) (kvdb.RwBucket, error) {
// First fetch the top level bucket which stores all data related to
// current, active channels.
openChanBucket := tx.ReadWriteBucket(openChannelBucket)
if openChanBucket == nil {
return nil, ErrNoChanDBExists
}
// Within this top level bucket, fetch the bucket dedicated to storing
// open channel data specific to the remote node.
nodePub := c.IdentityPub.SerializeCompressed()
nodeChanBucket := openChanBucket.NestedReadWriteBucket(nodePub)
if nodeChanBucket == nil {
return nil, ErrNoActiveChannels
}
// We'll then recurse down an additional layer in order to fetch the
// bucket for this particular chain.
chainBucket := nodeChanBucket.NestedReadWriteBucket(c.ChainHash[:])
if chainBucket == nil {
return nil, ErrNoActiveChannels
}
// With the bucket for the node and chain fetched, we can now go down
// another level, for this channel itself.
var chanPointBuf bytes.Buffer
err := mig.WriteOutpoint(&chanPointBuf, &c.FundingOutpoint)
if err != nil {
return nil, err
}
chanBucket := chainBucket.NestedReadWriteBucket(chanPointBuf.Bytes())
if chanBucket == nil {
return nil, ErrChannelNotFound
}
return chanBucket, nil
}
package migration26
import (
"bytes"
"fmt"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
mig25 "github.com/lightningnetwork/lnd/channeldb/migration25"
mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// A tlv type definition used to serialize and deserialize a KeyLocator
// from the database.
keyLocType tlv.Type = 1
// A tlv type used to serialize and deserialize the
// `InitialLocalBalance` field.
initialLocalBalanceType tlv.Type = 2
// A tlv type used to serialize and deserialize the
// `InitialRemoteBalance` field.
initialRemoteBalanceType tlv.Type = 3
)
var (
// chanInfoKey can be accessed within the bucket for a channel
// (identified by its chanPoint). This key stores all the static
// information for a channel which is decided at the end of the
// funding flow.
chanInfoKey = []byte("chan-info-key")
// localUpfrontShutdownKey can be accessed within the bucket for a
// channel (identified by its chanPoint). This key stores an optional
// upfront shutdown script for the local peer.
localUpfrontShutdownKey = []byte("local-upfront-shutdown-key")
// remoteUpfrontShutdownKey can be accessed within the bucket for a
// channel (identified by its chanPoint). This key stores an optional
// upfront shutdown script for the remote peer.
remoteUpfrontShutdownKey = []byte("remote-upfront-shutdown-key")
// lastWasRevokeKey is a key that stores true when the last update we
// sent was a revocation and false when it was a commitment signature.
// This is nil in the case of new channels with no updates exchanged.
lastWasRevokeKey = []byte("last-was-revoke")
// ErrNoChanInfoFound is returned when a particular channel does not
// have any channels state.
ErrNoChanInfoFound = fmt.Errorf("no chan info found")
// ErrNoPastDeltas is returned when the channel delta bucket hasn't been
// created.
ErrNoPastDeltas = fmt.Errorf("channel has no recorded deltas")
// ErrLogEntryNotFound is returned when we cannot find a log entry at
// the height requested in the revocation log.
ErrLogEntryNotFound = fmt.Errorf("log entry not found")
// ErrNoCommitmentsFound is returned when a channel has not set
// commitment states.
ErrNoCommitmentsFound = fmt.Errorf("no commitments found")
)
// OpenChannel embeds a mig25.OpenChannel with the extra update-to-date
// serialization and deserialization methods.
//
// NOTE: doesn't have the Packager field as it's not used in current migration.
type OpenChannel struct {
mig25.OpenChannel
}
// FetchChanInfo deserializes the channel info based on the legacy boolean.
// After migration25, the legacy format would have the fields
// `InitialLocalBalance` and `InitialRemoteBalance` directly encoded as bytes.
// For the new format, they will be put inside a tlv stream.
func FetchChanInfo(chanBucket kvdb.RBucket, c *OpenChannel, legacy bool) error {
infoBytes := chanBucket.Get(chanInfoKey)
if infoBytes == nil {
return ErrNoChanInfoFound
}
r := bytes.NewReader(infoBytes)
var (
chanType mig.ChannelType
chanStatus mig.ChannelStatus
)
if err := mig.ReadElements(r,
&chanType, &c.ChainHash, &c.FundingOutpoint,
&c.ShortChannelID, &c.IsPending, &c.IsInitiator,
&chanStatus, &c.FundingBroadcastHeight,
&c.NumConfsRequired, &c.ChannelFlags,
&c.IdentityPub, &c.Capacity, &c.TotalMSatSent,
&c.TotalMSatReceived,
); err != nil {
return err
}
c.ChanType = mig25.ChannelType(chanType)
c.ChanStatus = mig25.ChannelStatus(chanStatus)
// If this is the legacy format, we need to read the extra two new
// fields.
if legacy {
if err := mig.ReadElements(r,
&c.InitialLocalBalance, &c.InitialRemoteBalance,
); err != nil {
return err
}
}
// For single funder channels that we initiated and have the funding
// transaction to, read the funding txn.
if c.FundingTxPresent() {
if err := mig.ReadElement(r, &c.FundingTxn); err != nil {
return err
}
}
if err := mig.ReadChanConfig(r, &c.LocalChanCfg); err != nil {
return err
}
if err := mig.ReadChanConfig(r, &c.RemoteChanCfg); err != nil {
return err
}
// Retrieve the boolean stored under lastWasRevokeKey.
lastWasRevokeBytes := chanBucket.Get(lastWasRevokeKey)
if lastWasRevokeBytes == nil {
// If nothing has been stored under this key, we store false in
// the OpenChannel struct.
c.LastWasRevoke = false
} else {
// Otherwise, read the value into the LastWasRevoke field.
revokeReader := bytes.NewReader(lastWasRevokeBytes)
err := mig.ReadElements(revokeReader, &c.LastWasRevoke)
if err != nil {
return err
}
}
// Make the tlv stream based on the legacy param.
var (
ts *tlv.Stream
err error
localBalance uint64
remoteBalance uint64
)
keyLocRecord := mig25.MakeKeyLocRecord(
keyLocType, &c.RevocationKeyLocator,
)
// If it's legacy, create the stream with a single tlv record.
if legacy {
ts, err = tlv.NewStream(keyLocRecord)
} else {
// Otherwise, for the new format, we will encode the balance
// fields in the tlv stream too.
ts, err = tlv.NewStream(
keyLocRecord,
tlv.MakePrimitiveRecord(
initialLocalBalanceType, &localBalance,
),
tlv.MakePrimitiveRecord(
initialRemoteBalanceType, &remoteBalance,
),
)
}
if err != nil {
return err
}
if err := ts.Decode(r); err != nil {
return err
}
// For the new format, attach the balance fields.
if !legacy {
c.InitialLocalBalance = lnwire.MilliSatoshi(localBalance)
c.InitialRemoteBalance = lnwire.MilliSatoshi(remoteBalance)
}
// Finally, read the optional shutdown scripts.
if err := mig25.GetOptionalUpfrontShutdownScript(
chanBucket, localUpfrontShutdownKey, &c.LocalShutdownScript,
); err != nil {
return err
}
return mig25.GetOptionalUpfrontShutdownScript(
chanBucket, remoteUpfrontShutdownKey, &c.RemoteShutdownScript,
)
}
// MakeTlvStream creates a tlv stream based on whether we are deadling with
// legacy format or not. For the legacy format, we have a single record in the
// stream. For the new format, we have the extra balance records.
func MakeTlvStream(c *OpenChannel, legacy bool) (*tlv.Stream, error) {
keyLocRecord := mig25.MakeKeyLocRecord(
keyLocType, &c.RevocationKeyLocator,
)
// If it's legacy, return the stream with a single tlv record.
if legacy {
return tlv.NewStream(keyLocRecord)
}
// Otherwise, for the new format, we will encode the balance fields in
// the tlv stream too.
localBalance := uint64(c.InitialLocalBalance)
remoteBalance := uint64(c.InitialRemoteBalance)
// Create the tlv stream.
return tlv.NewStream(
keyLocRecord,
tlv.MakePrimitiveRecord(
initialLocalBalanceType, &localBalance,
),
tlv.MakePrimitiveRecord(
initialRemoteBalanceType, &remoteBalance,
),
)
}
// PutChanInfo serializes the channel info based on the legacy boolean. After
// migration25, the legacy format would have the fields `InitialLocalBalance`
// and `InitialRemoteBalance` directly encoded as bytes. For the new format,
// they will be put inside a tlv stream.
func PutChanInfo(chanBucket kvdb.RwBucket, c *OpenChannel, legacy bool) error {
var w bytes.Buffer
if err := mig.WriteElements(&w,
mig.ChannelType(c.ChanType), c.ChainHash, c.FundingOutpoint,
c.ShortChannelID, c.IsPending, c.IsInitiator,
mig.ChannelStatus(c.ChanStatus), c.FundingBroadcastHeight,
c.NumConfsRequired, c.ChannelFlags,
c.IdentityPub, c.Capacity, c.TotalMSatSent,
c.TotalMSatReceived,
); err != nil {
return err
}
// If this is legacy format, we need to write the extra two fields.
if legacy {
if err := mig.WriteElements(&w,
c.InitialLocalBalance, c.InitialRemoteBalance,
); err != nil {
return err
}
}
// For single funder channels that we initiated, and we have the
// funding transaction, then write the funding txn.
if c.FundingTxPresent() {
if err := mig.WriteElement(&w, c.FundingTxn); err != nil {
return err
}
}
if err := mig.WriteChanConfig(&w, &c.LocalChanCfg); err != nil {
return err
}
if err := mig.WriteChanConfig(&w, &c.RemoteChanCfg); err != nil {
return err
}
// Make the tlv stream based on the legacy param.
tlvStream, err := MakeTlvStream(c, legacy)
if err != nil {
return err
}
if err := tlvStream.Encode(&w); err != nil {
return err
}
if err := chanBucket.Put(chanInfoKey, w.Bytes()); err != nil {
return err
}
// Finally, add optional shutdown scripts for the local and remote peer
// if they are present.
if err := mig25.PutOptionalUpfrontShutdownScript(
chanBucket, localUpfrontShutdownKey, c.LocalShutdownScript,
); err != nil {
return err
}
return mig25.PutOptionalUpfrontShutdownScript(
chanBucket, remoteUpfrontShutdownKey, c.RemoteShutdownScript,
)
}
package migration26
import (
"github.com/btcsuite/btclog/v2"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration26
import (
"fmt"
mig25 "github.com/lightningnetwork/lnd/channeldb/migration25"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
// openChanBucket stores all the currently open channels. This bucket
// has a second, nested bucket which is keyed by a node's ID. Within
// that node ID bucket, all attributes required to track, update, and
// close a channel are stored.
openChannelBucket = []byte("open-chan-bucket")
// ErrNoChanDBExists is returned when a channel bucket hasn't been
// created.
ErrNoChanDBExists = fmt.Errorf("channel db has not yet been created")
// ErrNoActiveChannels is returned when there is no active (open)
// channels within the database.
ErrNoActiveChannels = fmt.Errorf("no active channels exist")
// ErrChannelNotFound is returned when we attempt to locate a channel
// for a specific chain, but it is not found.
ErrChannelNotFound = fmt.Errorf("channel not found")
)
// MigrateBalancesToTlvRecords migrates the balance fields into tlv records. It
// does so by first reading a list of open channels, then rewriting the channel
// info with the updated tlv stream.
func MigrateBalancesToTlvRecords(tx kvdb.RwTx) error {
log.Infof("Migrating local and remote balances into tlv records...")
openChanBucket := tx.ReadWriteBucket(openChannelBucket)
// If no bucket is found, we can exit early.
if openChanBucket == nil {
return nil
}
// Read a list of open channels.
channels, err := findOpenChannels(openChanBucket)
if err != nil {
return err
}
// Migrate the balances.
for _, c := range channels {
if err := migrateBalances(tx, c); err != nil {
return err
}
}
return err
}
// findOpenChannels finds all open channels.
func findOpenChannels(openChanBucket kvdb.RBucket) ([]*OpenChannel, error) {
channels := []*OpenChannel{}
// readChannel is a helper closure that reads the channel info from the
// channel bucket.
readChannel := func(chainBucket kvdb.RBucket, cp []byte) error {
c := &OpenChannel{}
// Read the sub-bucket level 3.
chanBucket := chainBucket.NestedReadBucket(
cp,
)
if chanBucket == nil {
log.Errorf("unable to read bucket for chanPoint=%x", cp)
return nil
}
// Get the old channel info.
if err := FetchChanInfo(chanBucket, c, true); err != nil {
return fmt.Errorf("unable to fetch chan info: %w", err)
}
channels = append(channels, c)
return nil
}
// Iterate the root bucket.
err := openChanBucket.ForEach(func(nodePub, v []byte) error {
// Ensure that this is a key the same size as a pubkey, and
// also that it leads directly to a bucket.
if len(nodePub) != 33 || v != nil {
return nil
}
// Read the sub-bucket level 1.
nodeChanBucket := openChanBucket.NestedReadBucket(nodePub)
if nodeChanBucket == nil {
log.Errorf("no bucket for node %x", nodePub)
return nil
}
// Iterate the bucket.
return nodeChanBucket.ForEach(func(chainHash, _ []byte) error {
// Read the sub-bucket level 2.
chainBucket := nodeChanBucket.NestedReadBucket(
chainHash,
)
if chainBucket == nil {
log.Errorf("unable to read bucket for chain=%x",
chainHash)
return nil
}
// Iterate the bucket.
return chainBucket.ForEach(func(cp, _ []byte) error {
return readChannel(chainBucket, cp)
})
})
})
if err != nil {
return nil, err
}
return channels, nil
}
// migrateBalances creates a new tlv stream which adds two more records to hold
// the balances info.
func migrateBalances(tx kvdb.RwTx, c *OpenChannel) error {
// Get the bucket.
chanBucket, err := mig25.FetchChanBucket(tx, &c.OpenChannel)
if err != nil {
return err
}
// Update the channel info. There isn't much to do here as the
// `PutChanInfo` will read the values from `c.InitialLocalBalance` and
// `c.InitialRemoteBalance` then create the new tlv stream as
// requested.
if err := PutChanInfo(chanBucket, c, false); err != nil {
return fmt.Errorf("unable to put chan info: %w", err)
}
return nil
}
package migration27
import (
"bytes"
"fmt"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
mig25 "github.com/lightningnetwork/lnd/channeldb/migration25"
mig26 "github.com/lightningnetwork/lnd/channeldb/migration26"
mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// A tlv type definition used to serialize and deserialize a KeyLocator
// from the database.
keyLocType tlv.Type = 1
// A tlv type used to serialize and deserialize the
// `InitialLocalBalance` field.
initialLocalBalanceType tlv.Type = 2
// A tlv type used to serialize and deserialize the
// `InitialRemoteBalance` field.
initialRemoteBalanceType tlv.Type = 3
)
var (
// chanInfoKey can be accessed within the bucket for a channel
// (identified by its chanPoint). This key stores all the static
// information for a channel which is decided at the end of the
// funding flow.
chanInfoKey = []byte("chan-info-key")
// localUpfrontShutdownKey can be accessed within the bucket for a
// channel (identified by its chanPoint). This key stores an optional
// upfront shutdown script for the local peer.
localUpfrontShutdownKey = []byte("local-upfront-shutdown-key")
// remoteUpfrontShutdownKey can be accessed within the bucket for a
// channel (identified by its chanPoint). This key stores an optional
// upfront shutdown script for the remote peer.
remoteUpfrontShutdownKey = []byte("remote-upfront-shutdown-key")
// lastWasRevokeKey is a key that stores true when the last update we
// sent was a revocation and false when it was a commitment signature.
// This is nil in the case of new channels with no updates exchanged.
lastWasRevokeKey = []byte("last-was-revoke")
// ErrNoChanInfoFound is returned when a particular channel does not
// have any channels state.
ErrNoChanInfoFound = fmt.Errorf("no chan info found")
)
// OpenChannel embeds a mig26.OpenChannel with the extra update-to-date
// serialization and deserialization methods.
//
// NOTE: doesn't have the Packager field as it's not used in current migration.
type OpenChannel struct {
mig26.OpenChannel
}
// FetchChanInfo deserializes the channel info based on the legacy boolean.
func FetchChanInfo(chanBucket kvdb.RBucket, c *OpenChannel, legacy bool) error {
infoBytes := chanBucket.Get(chanInfoKey)
if infoBytes == nil {
return ErrNoChanInfoFound
}
r := bytes.NewReader(infoBytes)
var (
chanType mig.ChannelType
chanStatus mig.ChannelStatus
)
if err := mig.ReadElements(r,
&chanType, &c.ChainHash, &c.FundingOutpoint,
&c.ShortChannelID, &c.IsPending, &c.IsInitiator,
&chanStatus, &c.FundingBroadcastHeight,
&c.NumConfsRequired, &c.ChannelFlags,
&c.IdentityPub, &c.Capacity, &c.TotalMSatSent,
&c.TotalMSatReceived,
); err != nil {
return fmt.Errorf("ReadElements got: %w", err)
}
c.ChanType = mig25.ChannelType(chanType)
c.ChanStatus = mig25.ChannelStatus(chanStatus)
// For single funder channels that we initiated and have the funding
// transaction to, read the funding txn.
if c.FundingTxPresent() {
if err := mig.ReadElement(r, &c.FundingTxn); err != nil {
return fmt.Errorf("read FundingTxn got: %w", err)
}
}
if err := mig.ReadChanConfig(r, &c.LocalChanCfg); err != nil {
return fmt.Errorf("read LocalChanCfg got: %w", err)
}
if err := mig.ReadChanConfig(r, &c.RemoteChanCfg); err != nil {
return fmt.Errorf("read RemoteChanCfg got: %w", err)
}
// Retrieve the boolean stored under lastWasRevokeKey.
lastWasRevokeBytes := chanBucket.Get(lastWasRevokeKey)
if lastWasRevokeBytes == nil {
// If nothing has been stored under this key, we store false in
// the OpenChannel struct.
c.LastWasRevoke = false
} else {
// Otherwise, read the value into the LastWasRevoke field.
revokeReader := bytes.NewReader(lastWasRevokeBytes)
err := mig.ReadElements(revokeReader, &c.LastWasRevoke)
if err != nil {
return fmt.Errorf("read LastWasRevoke got: %w", err)
}
}
// Make the tlv stream based on the legacy param.
var (
ts *tlv.Stream
err error
localBalance uint64
remoteBalance uint64
)
keyLocRecord := mig25.MakeKeyLocRecord(
keyLocType, &c.RevocationKeyLocator,
)
// If it's legacy, create the stream with a single tlv record.
if legacy {
ts, err = tlv.NewStream(keyLocRecord)
} else {
// Otherwise, for the new format, we will encode the balance
// fields in the tlv stream too.
ts, err = tlv.NewStream(
keyLocRecord,
tlv.MakePrimitiveRecord(
initialLocalBalanceType, &localBalance,
),
tlv.MakePrimitiveRecord(
initialRemoteBalanceType, &remoteBalance,
),
)
}
if err != nil {
return fmt.Errorf("create tlv stream got: %w", err)
}
if err := ts.Decode(r); err != nil {
return fmt.Errorf("decode tlv stream got: %w", err)
}
// For the new format, attach the balance fields.
if !legacy {
c.InitialLocalBalance = lnwire.MilliSatoshi(localBalance)
c.InitialRemoteBalance = lnwire.MilliSatoshi(remoteBalance)
}
// Finally, read the optional shutdown scripts.
if err := mig25.GetOptionalUpfrontShutdownScript(
chanBucket, localUpfrontShutdownKey, &c.LocalShutdownScript,
); err != nil {
return fmt.Errorf("local shutdown script got: %w", err)
}
return mig25.GetOptionalUpfrontShutdownScript(
chanBucket, remoteUpfrontShutdownKey, &c.RemoteShutdownScript,
)
}
// PutChanInfo serializes the channel info based on the legacy boolean.
func PutChanInfo(chanBucket kvdb.RwBucket, c *OpenChannel, legacy bool) error {
var w bytes.Buffer
if err := mig.WriteElements(&w,
mig.ChannelType(c.ChanType), c.ChainHash, c.FundingOutpoint,
c.ShortChannelID, c.IsPending, c.IsInitiator,
mig.ChannelStatus(c.ChanStatus), c.FundingBroadcastHeight,
c.NumConfsRequired, c.ChannelFlags,
c.IdentityPub, c.Capacity, c.TotalMSatSent,
c.TotalMSatReceived,
); err != nil {
return err
}
// For single funder channels that we initiated, and we have the
// funding transaction, then write the funding txn.
if c.FundingTxPresent() {
if err := mig.WriteElement(&w, c.FundingTxn); err != nil {
return err
}
}
if err := mig.WriteChanConfig(&w, &c.LocalChanCfg); err != nil {
return err
}
if err := mig.WriteChanConfig(&w, &c.RemoteChanCfg); err != nil {
return err
}
// Make the tlv stream based on the legacy param.
tlvStream, err := mig26.MakeTlvStream(&c.OpenChannel, legacy)
if err != nil {
return err
}
if err := tlvStream.Encode(&w); err != nil {
return err
}
if err := chanBucket.Put(chanInfoKey, w.Bytes()); err != nil {
return err
}
// Finally, add optional shutdown scripts for the local and remote peer
// if they are present.
if err := mig25.PutOptionalUpfrontShutdownScript(
chanBucket, localUpfrontShutdownKey, c.LocalShutdownScript,
); err != nil {
return err
}
return mig25.PutOptionalUpfrontShutdownScript(
chanBucket, remoteUpfrontShutdownKey, c.RemoteShutdownScript,
)
}
package migration27
import (
"github.com/btcsuite/btclog/v2"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration27
import (
"bytes"
"fmt"
mig26 "github.com/lightningnetwork/lnd/channeldb/migration26"
mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
// historicalChannelBucket stores all channels that have seen their
// commitment tx confirm. All information from their previous open state
// is retained.
historicalChannelBucket = []byte("historical-chan-bucket")
)
// MigrateHistoricalBalances patches the two new fields, `InitialLocalBalance`
// and `InitialRemoteBalance`, for all the open channels saved in historical
// channel bucket. Unlike migration 25, it will only read the old channel info
// first and then patch the new tlv records with empty values. For historical
// channels, we previously didn't save the initial balances anywhere and since
// it's corresponding open channel bucket is deleted after closure, we have
// lost that balance info.
func MigrateHistoricalBalances(tx kvdb.RwTx) error {
log.Infof("Migrating historical local and remote balances...")
// First fetch the top level bucket which stores all data related to
// historically stored channels.
rootBucket := tx.ReadWriteBucket(historicalChannelBucket)
// If no bucket is found, we can exit early.
if rootBucket == nil {
return nil
}
// Read a list of historical channels.
channels, err := findHistoricalChannels(rootBucket)
if err != nil {
return err
}
// Migrate the balances.
for _, c := range channels {
if err := migrateBalances(rootBucket, c); err != nil {
return err
}
}
return err
}
// findHistoricalChannels finds all historical channels.
func findHistoricalChannels(historicalBucket kvdb.RBucket) ([]*OpenChannel,
error) {
channels := []*OpenChannel{}
// readChannel is a helper closure that reads the channel info from the
// historical sub-bucket.
readChannel := func(rootBucket kvdb.RBucket, cp []byte) error {
c := &OpenChannel{}
chanPointBuf := bytes.NewBuffer(cp)
err := mig.ReadOutpoint(chanPointBuf, &c.FundingOutpoint)
if err != nil {
return fmt.Errorf("read funding outpoint got: %w", err)
}
// Read the sub-bucket.
chanBucket := rootBucket.NestedReadBucket(cp)
if chanBucket == nil {
log.Errorf("unable to read bucket for chanPoint=%s",
c.FundingOutpoint)
return nil
}
// Try to fetch channel info in old format.
err = fetchChanInfoCompatible(chanBucket, c, true)
if err != nil {
return fmt.Errorf("%s: fetch chan info got: %w",
c.FundingOutpoint, err)
}
channels = append(channels, c)
return nil
}
// Iterate the root bucket.
err := historicalBucket.ForEach(func(cp, _ []byte) error {
return readChannel(historicalBucket, cp)
})
if err != nil {
return nil, err
}
return channels, nil
}
// fetchChanInfoCompatible tries to fetch the channel info for a historical
// channel. It will first fetch the info assuming `InitialLocalBalance` and
// `InitialRemoteBalance` are not serialized. Upon receiving an error, it will
// then fetch it again assuming the two fields are present in db.
func fetchChanInfoCompatible(chanBucket kvdb.RBucket, c *OpenChannel,
legacy bool) error {
// Try to fetch the channel info assuming the historical channel in in
// the old format, where the two fields, `InitialLocalBalance` and
// `InitialRemoteBalance` are not saved to db.
err := FetchChanInfo(chanBucket, c, legacy)
if err == nil {
return err
}
// If we got an error above, the historical channel may already have
// the new fields saved. This could happen when a channel is closed
// after applying migration 25. In this case, we'll borrow the
// `FetchChanInfo` info method from migration 26 where we assume the
// two fields are saved.
return mig26.FetchChanInfo(chanBucket, &c.OpenChannel, legacy)
}
// migrateBalances serializes the channel info using the new tlv format where
// the two fields, `InitialLocalBalance` and `InitialRemoteBalance` are patched
// with empty values.
func migrateBalances(rootBucket kvdb.RwBucket, c *OpenChannel) error {
var chanPointBuf bytes.Buffer
err := mig.WriteOutpoint(&chanPointBuf, &c.FundingOutpoint)
if err != nil {
return err
}
// Get the channel bucket.
chanBucket := rootBucket.NestedReadWriteBucket(chanPointBuf.Bytes())
if chanBucket == nil {
return fmt.Errorf("empty historical chan bucket")
}
// Update the channel info.
if err := PutChanInfo(chanBucket, c, false); err != nil {
return fmt.Errorf("unable to put chan info: %w", err)
}
return nil
}
package migration29
import (
"encoding/binary"
"encoding/hex"
"io"
"github.com/btcsuite/btcd/wire"
)
var (
byteOrder = binary.BigEndian
)
// ChannelID is a series of 32-bytes that uniquely identifies all channels
// within the network. The ChannelID is computed using the outpoint of the
// funding transaction (the txid, and output index). Given a funding output the
// ChannelID can be calculated by XOR'ing the big-endian serialization of the
// txid and the big-endian serialization of the output index, truncated to
// 2 bytes.
type ChannelID [32]byte
// String returns the string representation of the ChannelID. This is just the
// hex string encoding of the ChannelID itself.
func (c ChannelID) String() string {
return hex.EncodeToString(c[:])
}
// NewChanIDFromOutPoint converts a target OutPoint into a ChannelID that is
// usable within the network. In order to convert the OutPoint into a ChannelID,
// we XOR the lower 2-bytes of the txid within the OutPoint with the big-endian
// serialization of the Index of the OutPoint, truncated to 2-bytes.
func NewChanIDFromOutPoint(op *wire.OutPoint) ChannelID {
// First we'll copy the txid of the outpoint into our channel ID slice.
var cid ChannelID
copy(cid[:], op.Hash[:])
// With the txid copied over, we'll now XOR the lower 2-bytes of the
// partial channelID with big-endian serialization of output index.
xorTxid(&cid, uint16(op.Index))
return cid
}
// xorTxid performs the transformation needed to transform an OutPoint into a
// ChannelID. To do this, we expect the cid parameter to contain the txid
// unaltered and the outputIndex to be the output index
func xorTxid(cid *ChannelID, outputIndex uint16) {
var buf [2]byte
binary.BigEndian.PutUint16(buf[:], outputIndex)
cid[30] ^= buf[0]
cid[31] ^= buf[1]
}
// readOutpoint reads an outpoint from the passed reader.
func readOutpoint(r io.Reader, o *wire.OutPoint) error {
if _, err := io.ReadFull(r, o.Hash[:]); err != nil {
return err
}
if err := binary.Read(r, byteOrder, &o.Index); err != nil {
return err
}
return nil
}
package migration29
import "github.com/btcsuite/btclog/v2"
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specific Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration29
import (
"bytes"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
// outpointBucket is the bucket that stores the set of outpoints we
// know about.
outpointBucket = []byte("outpoint-bucket")
// chanIDBucket is the bucket that stores the set of ChannelID's we
// know about.
chanIDBucket = []byte("chan-id-bucket")
)
// MigrateChanID populates the ChannelID index by using the set of outpoints
// retrieved from the outpoint bucket.
func MigrateChanID(tx kvdb.RwTx) error {
log.Info("Populating ChannelID index")
// First we'll retrieve the set of outpoints we know about.
ops, err := fetchOutPoints(tx)
if err != nil {
return err
}
return populateChanIDIndex(tx, ops)
}
// fetchOutPoints loops through the outpointBucket and returns each stored
// outpoint.
func fetchOutPoints(tx kvdb.RwTx) ([]*wire.OutPoint, error) {
var ops []*wire.OutPoint
bucket := tx.ReadBucket(outpointBucket)
err := bucket.ForEach(func(k, _ []byte) error {
var op wire.OutPoint
r := bytes.NewReader(k)
if err := readOutpoint(r, &op); err != nil {
return err
}
ops = append(ops, &op)
return nil
})
if err != nil {
return nil, err
}
return ops, nil
}
// populateChanIDIndex uses the set of retrieved outpoints and populates the
// ChannelID index.
func populateChanIDIndex(tx kvdb.RwTx, ops []*wire.OutPoint) error {
bucket := tx.ReadWriteBucket(chanIDBucket)
for _, op := range ops {
chanID := NewChanIDFromOutPoint(op)
if err := bucket.Put(chanID[:], []byte{}); err != nil {
return err
}
}
return nil
}
package migration30
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
mig25 "github.com/lightningnetwork/lnd/channeldb/migration25"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
// openChanBucket stores all the currently open channels. This bucket
// has a second, nested bucket which is keyed by a node's ID. Within
// that node ID bucket, all attributes required to track, update, and
// close a channel are stored.
openChannelBucket = []byte("open-chan-bucket")
// errExit is returned when the callback function used in iterator
// needs to exit the iteration.
errExit = errors.New("exit condition met")
)
// updateLocator defines a locator that can be used to find the next record to
// be migrated. This is useful when an interrupted migration that leads to a
// mixed revocation log formats saved in our database, we can then restart the
// migration using the locator to continue migrating the rest.
type updateLocator struct {
// nodePub, chainHash and fundingOutpoint are used to locate the
// channel bucket.
nodePub []byte
chainHash []byte
fundingOutpoint []byte
// nextHeight is used to locate the next old revocation log to be
// migrated. A nil value means we've finished the migration.
nextHeight []byte
}
// fetchChanBucket is a helper function that returns the bucket where a
// channel's data resides in given: the public key for the node, the outpoint,
// and the chainhash that the channel resides on.
func (ul *updateLocator) locateChanBucket(rootBucket kvdb.RwBucket) (
kvdb.RwBucket, error) {
// Within this top level bucket, fetch the bucket dedicated to storing
// open channel data specific to the remote node.
nodeChanBucket := rootBucket.NestedReadWriteBucket(ul.nodePub)
if nodeChanBucket == nil {
return nil, mig25.ErrNoActiveChannels
}
// We'll then recurse down an additional layer in order to fetch the
// bucket for this particular chain.
chainBucket := nodeChanBucket.NestedReadWriteBucket(ul.chainHash)
if chainBucket == nil {
return nil, mig25.ErrNoActiveChannels
}
// With the bucket for the node and chain fetched, we can now go down
// another level, for this channel itself.
chanBucket := chainBucket.NestedReadWriteBucket(ul.fundingOutpoint)
if chanBucket == nil {
return nil, mig25.ErrChannelNotFound
}
return chanBucket, nil
}
// findNextMigrateHeight finds the next commit height that's not migrated. It
// returns the commit height bytes found. A nil return value means the
// migration has been completed for this particular channel bucket.
func findNextMigrateHeight(chanBucket kvdb.RwBucket) []byte {
// Read the old log bucket. The old bucket doesn't exist, indicating
// either we don't have any old logs for this channel, or the migration
// has been finished and the old bucket has been deleted.
oldBucket := chanBucket.NestedReadBucket(
revocationLogBucketDeprecated,
)
if oldBucket == nil {
return nil
}
// Acquire a read cursor for the old bucket.
oldCursor := oldBucket.ReadCursor()
// Read the new log bucket. The sub-bucket hasn't been created yet,
// indicating we haven't migrated any logs under this channel. In this
// case, we'll return the first commit height found from the old
// revocation log bucket as the next height.
logBucket := chanBucket.NestedReadBucket(revocationLogBucket)
if logBucket == nil {
nextHeight, _ := oldCursor.First()
return nextHeight
}
// Acquire a read cursor for the new bucket.
cursor := logBucket.ReadCursor()
// Read the last migrated record. If the key is nil, we haven't
// migrated any logs yet. In this case we return the first commit
// height found from the old revocation log bucket. For instance,
// - old log: [1, 2]
// - new log: []
// We will return the first key [1].
migratedHeight, _ := cursor.Last()
if migratedHeight == nil {
nextHeight, _ := oldCursor.First()
return nextHeight
}
// Read the last height from the old log bucket.
endHeight, _ := oldCursor.Last()
switch bytes.Compare(migratedHeight, endHeight) {
// If the height of the last old revocation equals to the migrated
// height, we've done migrating for this channel. For instance,
// - old log: [1, 2]
// - new log: [1, 2]
case 0:
return nil
// If the migrated height is smaller, it means this is a resumed
// migration. In this case we will return the next height found in the
// old bucket. For instance,
// - old log: [1, 2]
// - new log: [1]
// We will return the key [2].
case -1:
// Now point the cursor to the migratedHeight. If we cannot
// find this key from the old log bucket, the database might be
// corrupted. In this case, we would return the first key so
// that we would redo the migration for this chan bucket.
matchedHeight, _ := oldCursor.Seek(migratedHeight)
// NOTE: because Seek will return the next key when the passed
// key cannot be found, we need to compare the `matchedHeight`
// to decide whether `migratedHeight` is found or not.
if !bytes.Equal(matchedHeight, migratedHeight) {
log.Warnf("Old revocation bucket doesn't have "+
"CommitHeight=%v yet it's found in the new "+
"bucket. It's likely the new revocation log "+
"bucket is corrupted. Migrations will be"+
"applied again.",
binary.BigEndian.Uint64(migratedHeight))
// Now return the first height found in the old bucket
// so we can redo the migration.
nextHeight, _ := oldCursor.First()
return nextHeight
}
// Otherwise, find the next height to be migrated.
nextHeight, _ := oldCursor.Next()
return nextHeight
// If the migrated height is greater, it means this node has new logs
// saved after v0.15.0. In this case, we need to further decide whether
// the old logs have been migrated or not.
case 1:
}
// If we ever reached here, it means we have a mixed of new and old
// logs saved. Suppose we have old logs as,
// - old log: [1, 2]
// We'd have four possible scenarios,
// - new log: [ 3, 4] <- no migration happened, return [1].
// - new log: [1, 3, 4] <- resumed migration, return [2].
// - new log: [ 2, 3, 4] <- corrupted migration, return [1].
// - new log: [1, 2, 3, 4] <- finished migration, return nil.
// To find the next migration height, we will iterate the old logs to
// grab the heights and query them in the new bucket until an height
// cannot be found, which is our next migration height. Or, if the old
// heights can all be found, it indicates a finished migration.
// Move the cursor to the first record.
oldKey, _ := oldCursor.First()
// NOTE: this action can be time-consuming as we are iterating the
// records and compare them. However, we would only ever hit here if
// this is a resumed migration with new logs created after v.0.15.0.
for {
// Try to locate the old key in the new bucket. If it cannot be
// found, it will be the next migrate height.
newKey, _ := cursor.Seek(oldKey)
// If the old key is not found in the new bucket, return it as
// our next migration height.
//
// NOTE: because Seek will return the next key when the passed
// key cannot be found, we need to compare the keys to deicde
// whether the old key is found or not.
if !bytes.Equal(newKey, oldKey) {
return oldKey
}
// Otherwise, keep iterating the old bucket.
oldKey, _ = oldCursor.Next()
// If we've done iterating, yet all the old keys can be found
// in the new bucket, this means the migration has been
// finished.
if oldKey == nil {
return nil
}
}
}
// locateNextUpdateNum returns a locator that's used to start our migration. A
// nil locator means the migration has been finished.
func locateNextUpdateNum(openChanBucket kvdb.RwBucket) (*updateLocator, error) {
locator := &updateLocator{}
// cb is the callback function to be used when iterating the buckets.
cb := func(chanBucket kvdb.RwBucket, l *updateLocator) error {
locator = l
updateNum := findNextMigrateHeight(chanBucket)
// We've found the next commit height and can now exit.
if updateNum != nil {
locator.nextHeight = updateNum
return errExit
}
return nil
}
// Iterate the buckets. If we received an exit signal, return the
// locator.
err := iterateBuckets(openChanBucket, nil, cb)
if err == errExit {
log.Debugf("found locator: nodePub=%x, fundingOutpoint=%x, "+
"nextHeight=%x", locator.nodePub, locator.chainHash,
locator.nextHeight)
return locator, nil
}
// If the err is nil, we've iterated all the sub-buckets and the
// migration is finished.
return nil, err
}
// callback defines a type that's used by the iterator.
type callback func(k, v []byte) error
// iterator is a helper function that iterates a given bucket and performs the
// callback function on each key. If a seeker is specified, it will move the
// cursor to the given position otherwise it will start from the first item.
func iterator(bucket kvdb.RBucket, seeker []byte, cb callback) error {
c := bucket.ReadCursor()
k, v := c.First()
// Move the cursor to the specified position if seeker is non-nil.
if seeker != nil {
k, v = c.Seek(seeker)
}
// Start the iteration and exit on condition.
for k, v := k, v; k != nil; k, v = c.Next() {
// cb might return errExit to signal exiting the iteration.
if err := cb(k, v); err != nil {
return err
}
}
return nil
}
// step defines the callback type that's used when iterating the buckets.
type step func(bucket kvdb.RwBucket, l *updateLocator) error
// iterateBuckets locates the cursor at a given position specified by the
// updateLocator and starts the iteration. If a nil locator is passed, it will
// start the iteration from the beginning. During each iteration, the callback
// function is called and it may exit the iteration when the callback returns
// an errExit to signal an exit condition.
func iterateBuckets(openChanBucket kvdb.RwBucket,
l *updateLocator, cb step) error {
// If the locator is nil, we will initiate an empty one, which is
// further used by the iterator.
if l == nil {
l = &updateLocator{}
}
// iterChanBucket iterates the chain bucket to act on each of the
// channel buckets.
iterChanBucket := func(chain kvdb.RwBucket,
k1, k2, _ []byte, cb step) error {
return iterator(
chain, l.fundingOutpoint,
func(k3, _ []byte) error {
// Read the sub-bucket level 3.
chanBucket := chain.NestedReadWriteBucket(k3)
if chanBucket == nil {
return fmt.Errorf("no bucket for "+
"chanPoint=%x", k3)
}
// Construct a new locator at this position.
locator := &updateLocator{
nodePub: k1,
chainHash: k2,
fundingOutpoint: k3,
}
// Set the seeker to nil so it won't affect
// other buckets.
l.fundingOutpoint = nil
return cb(chanBucket, locator)
})
}
return iterator(openChanBucket, l.nodePub, func(k1, v []byte) error {
// Read the sub-bucket level 1.
node := openChanBucket.NestedReadWriteBucket(k1)
if node == nil {
return fmt.Errorf("no bucket for node %x", k1)
}
return iterator(node, l.chainHash, func(k2, v []byte) error {
// Read the sub-bucket level 2.
chain := node.NestedReadWriteBucket(k2)
if chain == nil {
return fmt.Errorf("no bucket for chain=%x", k2)
}
// Set the seeker to nil so it won't affect other
// buckets.
l.chainHash = nil
return iterChanBucket(chain, k1, k2, v, cb)
})
})
}
package migration30
import (
"bytes"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
mig25 "github.com/lightningnetwork/lnd/channeldb/migration25"
mig26 "github.com/lightningnetwork/lnd/channeldb/migration26"
mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
"github.com/lightningnetwork/lnd/input"
)
// CommitmentKeyRing holds all derived keys needed to construct commitment and
// HTLC transactions. The keys are derived differently depending whether the
// commitment transaction is ours or the remote peer's. Private keys associated
// with each key may belong to the commitment owner or the "other party" which
// is referred to in the field comments, regardless of which is local and which
// is remote.
type CommitmentKeyRing struct {
// CommitPoint is the "per commitment point" used to derive the tweak
// for each base point.
CommitPoint *btcec.PublicKey
// LocalCommitKeyTweak is the tweak used to derive the local public key
// from the local payment base point or the local private key from the
// base point secret. This may be included in a SignDescriptor to
// generate signatures for the local payment key.
//
// NOTE: This will always refer to "our" local key, regardless of
// whether this is our commit or not.
LocalCommitKeyTweak []byte
// TODO(roasbeef): need delay tweak as well?
// LocalHtlcKeyTweak is the tweak used to derive the local HTLC key
// from the local HTLC base point. This value is needed in order to
// derive the final key used within the HTLC scripts in the commitment
// transaction.
//
// NOTE: This will always refer to "our" local HTLC key, regardless of
// whether this is our commit or not.
LocalHtlcKeyTweak []byte
// LocalHtlcKey is the key that will be used in any clause paying to
// our node of any HTLC scripts within the commitment transaction for
// this key ring set.
//
// NOTE: This will always refer to "our" local HTLC key, regardless of
// whether this is our commit or not.
LocalHtlcKey *btcec.PublicKey
// RemoteHtlcKey is the key that will be used in clauses within the
// HTLC script that send money to the remote party.
//
// NOTE: This will always refer to "their" remote HTLC key, regardless
// of whether this is our commit or not.
RemoteHtlcKey *btcec.PublicKey
// ToLocalKey is the commitment transaction owner's key which is
// included in HTLC success and timeout transaction scripts. This is
// the public key used for the to_local output of the commitment
// transaction.
//
// NOTE: Who's key this is depends on the current perspective. If this
// is our commitment this will be our key.
ToLocalKey *btcec.PublicKey
// ToRemoteKey is the non-owner's payment key in the commitment tx.
// This is the key used to generate the to_remote output within the
// commitment transaction.
//
// NOTE: Who's key this is depends on the current perspective. If this
// is our commitment this will be their key.
ToRemoteKey *btcec.PublicKey
// RevocationKey is the key that can be used by the other party to
// redeem outputs from a revoked commitment transaction if it were to
// be published.
//
// NOTE: Who can sign for this key depends on the current perspective.
// If this is our commitment, it means the remote node can sign for
// this key in case of a breach.
RevocationKey *btcec.PublicKey
}
// ScriptInfo holds a redeem script and hash.
type ScriptInfo struct {
// PkScript is the output's PkScript.
PkScript []byte
// WitnessScript is the full script required to properly redeem the
// output. This field should be set to the full script if a p2wsh
// output is being signed. For p2wkh it should be set equal to the
// PkScript.
WitnessScript []byte
}
// findOutputIndexesFromRemote finds the index of our and their outputs from
// the remote commitment transaction. It derives the key ring to compute the
// output scripts and compares them against the outputs inside the commitment
// to find the match.
func findOutputIndexesFromRemote(revocationPreimage *chainhash.Hash,
chanState *mig26.OpenChannel,
oldLog *mig.ChannelCommitment) (uint32, uint32, error) {
// Init the output indexes as empty.
ourIndex := uint32(OutputIndexEmpty)
theirIndex := uint32(OutputIndexEmpty)
chanCommit := oldLog
_, commitmentPoint := btcec.PrivKeyFromBytes(revocationPreimage[:])
// With the commitment point generated, we can now derive the king ring
// which will be used to generate the output scripts.
keyRing := DeriveCommitmentKeys(
commitmentPoint, false, chanState.ChanType,
&chanState.LocalChanCfg, &chanState.RemoteChanCfg,
)
// Since it's remote commitment chain, we'd used the mirrored values.
//
// We use the remote's channel config for the csv delay.
theirDelay := uint32(chanState.RemoteChanCfg.CsvDelay)
// If we are the initiator of this channel, then it's be false from the
// remote's PoV.
isRemoteInitiator := !chanState.IsInitiator
var leaseExpiry uint32
if chanState.ChanType.HasLeaseExpiration() {
leaseExpiry = chanState.ThawHeight
}
// Map the scripts from our PoV. When facing a local commitment, the to
// local output belongs to us and the to remote output belongs to them.
// When facing a remote commitment, the to local output belongs to them
// and the to remote output belongs to us.
// Compute the to local script. From our PoV, when facing a remote
// commitment, the to local output belongs to them.
theirScript, err := CommitScriptToSelf(
chanState.ChanType, isRemoteInitiator, keyRing.ToLocalKey,
keyRing.RevocationKey, theirDelay, leaseExpiry,
)
if err != nil {
return ourIndex, theirIndex, err
}
// Compute the to remote script. From our PoV, when facing a remote
// commitment, the to remote output belongs to us.
ourScript, _, err := CommitScriptToRemote(
chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey,
leaseExpiry,
)
if err != nil {
return ourIndex, theirIndex, err
}
// Now compare the scripts to find our/their output index.
for i, txOut := range chanCommit.CommitTx.TxOut {
switch {
case bytes.Equal(txOut.PkScript, ourScript.PkScript):
ourIndex = uint32(i)
case bytes.Equal(txOut.PkScript, theirScript.PkScript):
theirIndex = uint32(i)
}
}
return ourIndex, theirIndex, nil
}
// DeriveCommitmentKeys generates a new commitment key set using the base points
// and commitment point. The keys are derived differently depending on the type
// of channel, and whether the commitment transaction is ours or the remote
// peer's.
func DeriveCommitmentKeys(commitPoint *btcec.PublicKey,
isOurCommit bool, chanType mig25.ChannelType,
localChanCfg, remoteChanCfg *mig.ChannelConfig) *CommitmentKeyRing {
tweaklessCommit := chanType.IsTweakless()
// Depending on if this is our commit or not, we'll choose the correct
// base point.
localBasePoint := localChanCfg.PaymentBasePoint
if isOurCommit {
localBasePoint = localChanCfg.DelayBasePoint
}
// First, we'll derive all the keys that don't depend on the context of
// whose commitment transaction this is.
keyRing := &CommitmentKeyRing{
CommitPoint: commitPoint,
LocalCommitKeyTweak: input.SingleTweakBytes(
commitPoint, localBasePoint.PubKey,
),
LocalHtlcKeyTweak: input.SingleTweakBytes(
commitPoint, localChanCfg.HtlcBasePoint.PubKey,
),
LocalHtlcKey: input.TweakPubKey(
localChanCfg.HtlcBasePoint.PubKey, commitPoint,
),
RemoteHtlcKey: input.TweakPubKey(
remoteChanCfg.HtlcBasePoint.PubKey, commitPoint,
),
}
// We'll now compute the to_local, to_remote, and revocation key based
// on the current commitment point. All keys are tweaked each state in
// order to ensure the keys from each state are unlinkable. To create
// the revocation key, we take the opposite party's revocation base
// point and combine that with the current commitment point.
var (
toLocalBasePoint *btcec.PublicKey
toRemoteBasePoint *btcec.PublicKey
revocationBasePoint *btcec.PublicKey
)
if isOurCommit {
toLocalBasePoint = localChanCfg.DelayBasePoint.PubKey
toRemoteBasePoint = remoteChanCfg.PaymentBasePoint.PubKey
revocationBasePoint = remoteChanCfg.RevocationBasePoint.PubKey
} else {
toLocalBasePoint = remoteChanCfg.DelayBasePoint.PubKey
toRemoteBasePoint = localChanCfg.PaymentBasePoint.PubKey
revocationBasePoint = localChanCfg.RevocationBasePoint.PubKey
}
// With the base points assigned, we can now derive the actual keys
// using the base point, and the current commitment tweak.
keyRing.ToLocalKey = input.TweakPubKey(toLocalBasePoint, commitPoint)
keyRing.RevocationKey = input.DeriveRevocationPubkey(
revocationBasePoint, commitPoint,
)
// If this commitment should omit the tweak for the remote point, then
// we'll use that directly, and ignore the commitPoint tweak.
if tweaklessCommit {
keyRing.ToRemoteKey = toRemoteBasePoint
// If this is not our commitment, the above ToRemoteKey will be
// ours, and we blank out the local commitment tweak to
// indicate that the key should not be tweaked when signing.
if !isOurCommit {
keyRing.LocalCommitKeyTweak = nil
}
} else {
keyRing.ToRemoteKey = input.TweakPubKey(
toRemoteBasePoint, commitPoint,
)
}
return keyRing
}
// CommitScriptToRemote derives the appropriate to_remote script based on the
// channel's commitment type. The `initiator` argument should correspond to the
// owner of the commitment transaction which we are generating the to_remote
// script for. The second return value is the CSV delay of the output script,
// what must be satisfied in order to spend the output.
func CommitScriptToRemote(chanType mig25.ChannelType, initiator bool,
key *btcec.PublicKey, leaseExpiry uint32) (*ScriptInfo, uint32, error) {
switch {
// If we are not the initiator of a leased channel, then the remote
// party has an additional CLTV requirement in addition to the 1 block
// CSV requirement.
case chanType.HasLeaseExpiration() && !initiator:
script, err := input.LeaseCommitScriptToRemoteConfirmed(
key, leaseExpiry,
)
if err != nil {
return nil, 0, err
}
p2wsh, err := input.WitnessScriptHash(script)
if err != nil {
return nil, 0, err
}
return &ScriptInfo{
PkScript: p2wsh,
WitnessScript: script,
}, 1, nil
// If this channel type has anchors, we derive the delayed to_remote
// script.
case chanType.HasAnchors():
script, err := input.CommitScriptToRemoteConfirmed(key)
if err != nil {
return nil, 0, err
}
p2wsh, err := input.WitnessScriptHash(script)
if err != nil {
return nil, 0, err
}
return &ScriptInfo{
PkScript: p2wsh,
WitnessScript: script,
}, 1, nil
default:
// Otherwise the to_remote will be a simple p2wkh.
p2wkh, err := input.CommitScriptUnencumbered(key)
if err != nil {
return nil, 0, err
}
// Since this is a regular P2WKH, the WitnessScipt and PkScript
// should both be set to the script hash.
return &ScriptInfo{
WitnessScript: p2wkh,
PkScript: p2wkh,
}, 0, nil
}
}
// CommitScriptToSelf constructs the public key script for the output on the
// commitment transaction paying to the "owner" of said commitment transaction.
// The `initiator` argument should correspond to the owner of the commitment
// transaction which we are generating the to_local script for. If the other
// party learns of the preimage to the revocation hash, then they can claim all
// the settled funds in the channel, plus the unsettled funds.
func CommitScriptToSelf(chanType mig25.ChannelType, initiator bool,
selfKey, revokeKey *btcec.PublicKey, csvDelay, leaseExpiry uint32) (
*ScriptInfo, error) {
var (
toLocalRedeemScript []byte
err error
)
switch {
// If we are the initiator of a leased channel, then we have an
// additional CLTV requirement in addition to the usual CSV requirement.
case initiator && chanType.HasLeaseExpiration():
toLocalRedeemScript, err = input.LeaseCommitScriptToSelf(
selfKey, revokeKey, csvDelay, leaseExpiry,
)
default:
toLocalRedeemScript, err = input.CommitScriptToSelf(
csvDelay, selfKey, revokeKey,
)
}
if err != nil {
return nil, err
}
toLocalScriptHash, err := input.WitnessScriptHash(toLocalRedeemScript)
if err != nil {
return nil, err
}
return &ScriptInfo{
PkScript: toLocalScriptHash,
WitnessScript: toLocalRedeemScript,
}, nil
}
package migration30
import (
"github.com/btcsuite/btclog/v2"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration30
import (
"bytes"
"encoding/binary"
"fmt"
"math"
"sync"
mig24 "github.com/lightningnetwork/lnd/channeldb/migration24"
mig26 "github.com/lightningnetwork/lnd/channeldb/migration26"
mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
"github.com/lightningnetwork/lnd/kvdb"
)
// recordsPerTx specifies the number of records to be migrated in each database
// transaction. In the worst case, each old revocation log is 28,057 bytes.
// 20,000 records would consume 0.56 GB of ram, which is feasible for a modern
// machine.
//
// NOTE: we could've used more ram but it doesn't help with the speed of the
// migration since the most of the CPU time is used for calculating the output
// indexes.
const recordsPerTx = 20_000
// MigrateRevLogConfig is an interface that defines the config that should be
// passed to the MigrateRevocationLog function.
type MigrateRevLogConfig interface {
// GetNoAmountData returns true if the amount data of revoked commitment
// transactions should not be stored in the revocation log.
GetNoAmountData() bool
}
// MigrateRevLogConfigImpl implements the MigrationRevLogConfig interface.
type MigrateRevLogConfigImpl struct {
// NoAmountData if set to true will result in the amount data of revoked
// commitment transactions not being stored in the revocation log.
NoAmountData bool
}
// GetNoAmountData returns true if the amount data of revoked commitment
// transactions should not be stored in the revocation log.
func (c *MigrateRevLogConfigImpl) GetNoAmountData() bool {
return c.NoAmountData
}
// MigrateRevocationLog migrates the old revocation logs into the newer format
// and deletes them once finished, with the deletion only happens once ALL the
// old logs have been migrates.
func MigrateRevocationLog(db kvdb.Backend, cfg MigrateRevLogConfig) error {
log.Infof("Migrating revocation logs, might take a while...")
var (
err error
// finished is used to exit the for loop.
finished bool
// total is the number of total records.
total uint64
// migrated is the number of already migrated records.
migrated uint64
)
// First of all, read the stats of the revocation logs.
total, migrated, err = logMigrationStat(db)
if err != nil {
return err
}
log.Infof("Total logs=%d, migrated=%d", total, migrated)
// Exit early if the old logs have already been migrated and deleted.
if total == 0 {
log.Info("Migration already finished!")
return nil
}
for {
if finished {
log.Infof("Migrating old revocation logs finished, " +
"now checking the migration results...")
break
}
// Process the migration.
err = kvdb.Update(db, func(tx kvdb.RwTx) error {
finished, err = processMigration(tx, cfg)
if err != nil {
return err
}
return nil
}, func() {})
if err != nil {
return err
}
// Each time we finished the above process, we'd read the stats
// again to understand the current progress.
total, migrated, err = logMigrationStat(db)
if err != nil {
return err
}
// Calculate and log the progress if the progress is less than
// one.
progress := float64(migrated) / float64(total) * 100
if progress >= 100 {
continue
}
log.Infof("Migration progress: %.3f%%, still have: %d",
progress, total-migrated)
}
// Before we can safety delete the old buckets, we perform a check to
// make sure the logs are migrated as expected.
err = kvdb.Update(db, validateMigration, func() {})
if err != nil {
return fmt.Errorf("validate migration failed: %w", err)
}
log.Info("Migration check passed, now deleting the old logs...")
// Once the migration completes, we can now safety delete the old
// revocation logs.
if err := deleteOldBuckets(db); err != nil {
return fmt.Errorf("deleteOldBuckets err: %w", err)
}
log.Info("Old revocation log buckets removed!")
return nil
}
// processMigration finds the next un-migrated revocation logs, reads a max
// number of `recordsPerTx` records, converts them into the new revocation logs
// and save them to disk.
func processMigration(tx kvdb.RwTx, cfg MigrateRevLogConfig) (bool, error) {
openChanBucket := tx.ReadWriteBucket(openChannelBucket)
// If no bucket is found, we can exit early.
if openChanBucket == nil {
return false, fmt.Errorf("root bucket not found")
}
// Locate the next migration height.
locator, err := locateNextUpdateNum(openChanBucket)
if err != nil {
return false, fmt.Errorf("locator got error: %w", err)
}
// If the returned locator is nil, we've done migrating the logs.
if locator == nil {
return true, nil
}
// Read a list of old revocation logs.
entryMap, err := readOldRevocationLogs(openChanBucket, locator, cfg)
if err != nil {
return false, fmt.Errorf("read old logs err: %w", err)
}
// Migrate the revocation logs.
return false, writeRevocationLogs(openChanBucket, entryMap)
}
// deleteOldBuckets iterates all the channel buckets and deletes the old
// revocation buckets.
func deleteOldBuckets(db kvdb.Backend) error {
// locators records all the chan buckets found in the database.
var locators []*updateLocator
// reader is a helper closure that saves the locator found. Each
// locator is relatively small(33+32+36+8=109 bytes), assuming 1 GB of
// ram we can fit roughly 10 million records. Since each record
// corresponds to a channel, we should have more than enough memory to
// read them all.
reader := func(_ kvdb.RwBucket, l *updateLocator) error { // nolint:unparam
locators = append(locators, l)
return nil
}
// remover is a helper closure that removes the old revocation log
// bucket under the specified chan bucket by the given locator.
remover := func(rootBucket kvdb.RwBucket, l *updateLocator) error {
chanBucket, err := l.locateChanBucket(rootBucket)
if err != nil {
return err
}
return chanBucket.DeleteNestedBucket(
revocationLogBucketDeprecated,
)
}
// Perform the deletion in one db transaction. This should not cause
// any memory issue as the deletion doesn't load any data from the
// buckets.
return kvdb.Update(db, func(tx kvdb.RwTx) error {
openChanBucket := tx.ReadWriteBucket(openChannelBucket)
// Exit early if there's no bucket.
if openChanBucket == nil {
return nil
}
// Iterate the buckets to find all the locators.
err := iterateBuckets(openChanBucket, nil, reader)
if err != nil {
return err
}
// Iterate the locators and delete all the old revocation log
// buckets.
for _, l := range locators {
err := remover(openChanBucket, l)
// If the bucket doesn't exist, we can exit safety.
if err != nil && err != kvdb.ErrBucketNotFound {
return err
}
}
return nil
}, func() {})
}
// writeRevocationLogs unwraps the entryMap and writes the new revocation logs.
func writeRevocationLogs(openChanBucket kvdb.RwBucket,
entryMap logEntries) error {
for locator, logs := range entryMap {
// Find the channel bucket.
chanBucket, err := locator.locateChanBucket(openChanBucket)
if err != nil {
return fmt.Errorf("locateChanBucket err: %w", err)
}
// Create the new log bucket.
logBucket, err := chanBucket.CreateBucketIfNotExists(
revocationLogBucket,
)
if err != nil {
return fmt.Errorf("create log bucket err: %w", err)
}
// Write the new logs.
for _, entry := range logs {
var b bytes.Buffer
err := serializeRevocationLog(&b, entry.log)
if err != nil {
return err
}
logEntrykey := mig24.MakeLogKey(entry.commitHeight)
err = logBucket.Put(logEntrykey[:], b.Bytes())
if err != nil {
return fmt.Errorf("putRevocationLog err: %w",
err)
}
}
}
return nil
}
// logMigrationStat reads the buckets to provide stats over current migration
// progress. The returned values are the numbers of total records and already
// migrated records.
func logMigrationStat(db kvdb.Backend) (uint64, uint64, error) {
var (
err error
// total is the number of total records.
total uint64
// unmigrated is the number of unmigrated records.
unmigrated uint64
)
err = kvdb.Update(db, func(tx kvdb.RwTx) error {
total, unmigrated, err = fetchLogStats(tx)
return err
}, func() {})
log.Debugf("Total logs=%d, unmigrated=%d", total, unmigrated)
return total, total - unmigrated, err
}
// fetchLogStats iterates all the chan buckets to provide stats about the logs.
// The returned values are num of total records, and num of un-migrated
// records.
func fetchLogStats(tx kvdb.RwTx) (uint64, uint64, error) {
var (
total uint64
totalUnmigrated uint64
)
openChanBucket := tx.ReadWriteBucket(openChannelBucket)
// If no bucket is found, we can exit early.
if openChanBucket == nil {
return 0, 0, fmt.Errorf("root bucket not found")
}
// counter is a helper closure used to count the number of records
// based on the given bucket.
counter := func(chanBucket kvdb.RwBucket, bucket []byte) uint64 {
// Read the sub-bucket level 4.
logBucket := chanBucket.NestedReadBucket(bucket)
// Exit early if we don't have the bucket.
if logBucket == nil {
return 0
}
// Jump to the end of the cursor.
key, _ := logBucket.ReadCursor().Last()
// Since the CommitHeight is a zero-based monotonically
// increased index, its value plus one reflects the total
// records under this chan bucket.
lastHeight := binary.BigEndian.Uint64(key) + 1
return lastHeight
}
// countTotal is a callback function used to count the total number of
// records.
countTotal := func(chanBucket kvdb.RwBucket, l *updateLocator) error {
total += counter(chanBucket, revocationLogBucketDeprecated)
return nil
}
// countUnmigrated is a callback function used to count the total
// number of un-migrated records.
countUnmigrated := func(chanBucket kvdb.RwBucket,
l *updateLocator) error {
totalUnmigrated += counter(
chanBucket, revocationLogBucketDeprecated,
)
return nil
}
// Locate the next migration height.
locator, err := locateNextUpdateNum(openChanBucket)
if err != nil {
return 0, 0, fmt.Errorf("locator got error: %w", err)
}
// If the returned locator is not nil, we still have un-migrated
// records so we need to count them. Otherwise we've done migrating the
// logs.
if locator != nil {
err = iterateBuckets(openChanBucket, locator, countUnmigrated)
if err != nil {
return 0, 0, err
}
}
// Count the total number of records by supplying a nil locator.
err = iterateBuckets(openChanBucket, nil, countTotal)
if err != nil {
return 0, 0, err
}
return total, totalUnmigrated, err
}
// logEntry houses the info needed to write a new revocation log.
type logEntry struct {
log *RevocationLog
commitHeight uint64
ourIndex uint32
theirIndex uint32
locator *updateLocator
}
// logEntries maps a bucket locator to a list of entries under that bucket.
type logEntries map[*updateLocator][]*logEntry
// result is made of two channels that's used to send back the constructed new
// revocation log or an error.
type result struct {
newLog chan *logEntry
errChan chan error
}
// readOldRevocationLogs finds a list of old revocation logs and converts them
// into the new revocation logs.
func readOldRevocationLogs(openChanBucket kvdb.RwBucket,
locator *updateLocator, cfg MigrateRevLogConfig) (logEntries, error) {
entries := make(logEntries)
results := make([]*result, 0)
var wg sync.WaitGroup
// collectLogs is a helper closure that reads all newly created
// revocation logs sent over the result channels.
//
// NOTE: the order of the logs cannot be guaranteed, which is fine as
// boltdb will take care of the orders when saving them.
collectLogs := func() error {
wg.Wait()
for _, r := range results {
select {
case entry := <-r.newLog:
entries[entry.locator] = append(
entries[entry.locator], entry,
)
case err := <-r.errChan:
return err
}
}
return nil
}
// createLog is a helper closure that constructs a new revocation log.
//
// NOTE: used as a goroutine.
createLog := func(chanState *mig26.OpenChannel,
c mig.ChannelCommitment, l *updateLocator, r *result) {
defer wg.Done()
// Find the output indexes.
ourIndex, theirIndex, err := findOutputIndexes(chanState, &c)
if err != nil {
r.errChan <- err
}
// Convert the old logs into the new logs. We do this early in
// the read tx so the old large revocation log can be set to
// nil here so save us some memory space.
newLog, err := convertRevocationLog(
&c, ourIndex, theirIndex, cfg.GetNoAmountData(),
)
if err != nil {
r.errChan <- err
}
// Create the entry that will be used to create the new log.
entry := &logEntry{
log: newLog,
commitHeight: c.CommitHeight,
ourIndex: ourIndex,
theirIndex: theirIndex,
locator: l,
}
r.newLog <- entry
}
// innerCb is the stepping function used when iterating the old log
// bucket.
innerCb := func(chanState *mig26.OpenChannel, l *updateLocator,
_, v []byte) error {
reader := bytes.NewReader(v)
c, err := mig.DeserializeChanCommit(reader)
if err != nil {
return err
}
r := &result{
newLog: make(chan *logEntry, 1),
errChan: make(chan error, 1),
}
results = append(results, r)
// We perform the log creation in a goroutine as it takes some
// time to compute and find output indexes.
wg.Add(1)
go createLog(chanState, c, l, r)
// Check the records read so far and signals exit when we've
// reached our memory cap.
if len(results) >= recordsPerTx {
return errExit
}
return nil
}
// cb is the callback function to be used when iterating the buckets.
cb := func(chanBucket kvdb.RwBucket, l *updateLocator) error {
// Read the open channel.
c := &mig26.OpenChannel{}
err := mig26.FetchChanInfo(chanBucket, c, false)
if err != nil {
return fmt.Errorf("unable to fetch chan info: %w", err)
}
err = fetchChanRevocationState(chanBucket, c)
if err != nil {
return fmt.Errorf("unable to fetch revocation "+
"state: %v", err)
}
// Read the sub-bucket level 4.
logBucket := chanBucket.NestedReadBucket(
revocationLogBucketDeprecated,
)
// Exit early if we don't have the old bucket.
if logBucket == nil {
return nil
}
// Init the map key when needed.
_, ok := entries[l]
if !ok {
entries[l] = make([]*logEntry, 0, recordsPerTx)
}
return iterator(
logBucket, locator.nextHeight,
func(k, v []byte) error {
// Reset the nextHeight for following chan
// buckets.
locator.nextHeight = nil
return innerCb(c, l, k, v)
},
)
}
err := iterateBuckets(openChanBucket, locator, cb)
// If there's an error and it's not exit signal, we won't collect the
// logs from the result channels.
if err != nil && err != errExit {
return nil, err
}
// Otherwise, collect the logs.
err = collectLogs()
return entries, err
}
// convertRevocationLog uses the fields `CommitTx` and `Htlcs` from a
// ChannelCommitment to construct a revocation log entry.
func convertRevocationLog(commit *mig.ChannelCommitment,
ourOutputIndex, theirOutputIndex uint32,
noAmtData bool) (*RevocationLog, error) {
// Sanity check that the output indexes can be safely converted.
if ourOutputIndex > math.MaxUint16 {
return nil, ErrOutputIndexTooBig
}
if theirOutputIndex > math.MaxUint16 {
return nil, ErrOutputIndexTooBig
}
rl := &RevocationLog{
OurOutputIndex: uint16(ourOutputIndex),
TheirOutputIndex: uint16(theirOutputIndex),
CommitTxHash: commit.CommitTx.TxHash(),
HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)),
}
if !noAmtData {
rl.TheirBalance = &commit.RemoteBalance
rl.OurBalance = &commit.LocalBalance
}
for _, htlc := range commit.Htlcs {
// Skip dust HTLCs.
if htlc.OutputIndex < 0 {
continue
}
// Sanity check that the output indexes can be safely
// converted.
if htlc.OutputIndex > math.MaxUint16 {
return nil, ErrOutputIndexTooBig
}
entry := &HTLCEntry{
RHash: htlc.RHash,
RefundTimeout: htlc.RefundTimeout,
Incoming: htlc.Incoming,
OutputIndex: uint16(htlc.OutputIndex),
Amt: htlc.Amt.ToSatoshis(),
}
rl.HTLCEntries = append(rl.HTLCEntries, entry)
}
return rl, nil
}
// validateMigration checks that the data saved in the new buckets match those
// saved in the old buckets. It does so by checking the last keys saved in both
// buckets can match, given the assumption that the `CommitHeight` is
// monotonically increased value so the last key represents the total number of
// records saved.
func validateMigration(tx kvdb.RwTx) error {
openChanBucket := tx.ReadWriteBucket(openChannelBucket)
// If no bucket is found, we can exit early.
if openChanBucket == nil {
return nil
}
// exitWithErr is a helper closure that prepends an error message with
// the locator info.
exitWithErr := func(l *updateLocator, msg string) error {
return fmt.Errorf("unmatched records found under <nodePub=%x"+
", chainHash=%x, fundingOutpoint=%x>: %v", l.nodePub,
l.chainHash, l.fundingOutpoint, msg)
}
// cb is the callback function to be used when iterating the buckets.
cb := func(chanBucket kvdb.RwBucket, l *updateLocator) error {
// Read both the old and new revocation log buckets.
oldBucket := chanBucket.NestedReadBucket(
revocationLogBucketDeprecated,
)
newBucket := chanBucket.NestedReadBucket(revocationLogBucket)
// Exit early if the old bucket is nil.
//
// NOTE: the new bucket may not be nil here as new logs might
// have been created using lnd@v0.15.0.
if oldBucket == nil {
return nil
}
// Return an error if the expected new bucket cannot be found.
if newBucket == nil {
return exitWithErr(l, "expected new bucket")
}
// Acquire the cursors.
oldCursor := oldBucket.ReadCursor()
newCursor := newBucket.ReadCursor()
// Jump to the end of the cursors to do a quick check.
newKey, _ := oldCursor.Last()
oldKey, _ := newCursor.Last()
// We expected the CommitHeights to be matched for nodes prior
// to v0.15.0.
if bytes.Equal(newKey, oldKey) {
return nil
}
// If the keys do not match, it's likely the node is running
// v0.15.0 and have new logs created. In this case, we will
// validate that every record in the old bucket can be found in
// the new bucket.
oldKey, _ = oldCursor.First()
for {
// Try to locate the old key in the new bucket and we
// expect it to be found.
newKey, _ := newCursor.Seek(oldKey)
// If the old key is not found in the new bucket,
// return an error.
//
// NOTE: because Seek will return the next key when the
// passed key cannot be found, we need to compare the
// keys to deicde whether the old key is found or not.
if !bytes.Equal(newKey, oldKey) {
errMsg := fmt.Sprintf("old bucket has "+
"CommitHeight=%v cannot be found in "+
"new bucket", oldKey)
return exitWithErr(l, errMsg)
}
// Otherwise, keep iterating the old bucket.
oldKey, _ = oldCursor.Next()
// If we've done iterating, all keys have been matched
// and we can safely exit.
if oldKey == nil {
return nil
}
}
}
return iterateBuckets(openChanBucket, nil, cb)
}
package migration30
import (
"bytes"
"errors"
"io"
"math"
"github.com/btcsuite/btcd/btcutil"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
mig24 "github.com/lightningnetwork/lnd/channeldb/migration24"
mig25 "github.com/lightningnetwork/lnd/channeldb/migration25"
mig26 "github.com/lightningnetwork/lnd/channeldb/migration26"
mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// OutputIndexEmpty is used when the output index doesn't exist.
OutputIndexEmpty = math.MaxUint16
// A set of tlv type definitions used to serialize the body of
// revocation logs to the database.
//
// NOTE: A migration should be added whenever this list changes.
revLogOurOutputIndexType tlv.Type = 0
revLogTheirOutputIndexType tlv.Type = 1
revLogCommitTxHashType tlv.Type = 2
revLogOurBalanceType tlv.Type = 3
revLogTheirBalanceType tlv.Type = 4
)
var (
// revocationLogBucketDeprecated is dedicated for storing the necessary
// delta state between channel updates required to re-construct a past
// state in order to punish a counterparty attempting a non-cooperative
// channel closure. This key should be accessed from within the
// sub-bucket of a target channel, identified by its channel point.
//
// Deprecated: This bucket is kept for read-only in case the user
// choose not to migrate the old data.
revocationLogBucketDeprecated = []byte("revocation-log-key")
// revocationLogBucket is a sub-bucket under openChannelBucket. This
// sub-bucket is dedicated for storing the minimal info required to
// re-construct a past state in order to punish a counterparty
// attempting a non-cooperative channel closure.
revocationLogBucket = []byte("revocation-log")
// revocationStateKey stores their current revocation hash, our
// preimage producer and their preimage store.
revocationStateKey = []byte("revocation-state-key")
// ErrNoRevocationsFound is returned when revocation state for a
// particular channel cannot be found.
ErrNoRevocationsFound = errors.New("no revocations found")
// ErrLogEntryNotFound is returned when we cannot find a log entry at
// the height requested in the revocation log.
ErrLogEntryNotFound = errors.New("log entry not found")
// ErrOutputIndexTooBig is returned when the output index is greater
// than uint16.
ErrOutputIndexTooBig = errors.New("output index is over uint16")
)
// HTLCEntry specifies the minimal info needed to be stored on disk for ALL the
// historical HTLCs, which is useful for constructing RevocationLog when a
// breach is detected.
// The actual size of each HTLCEntry varies based on its RHash and Amt(sat),
// summarized as follows,
//
// | RHash empty | Amt<=252 | Amt<=65,535 | Amt<=4,294,967,295 | otherwise |
// |:-----------:|:--------:|:-----------:|:------------------:|:---------:|
// | true | 19 | 21 | 23 | 26 |
// | false | 51 | 53 | 55 | 58 |
//
// So the size varies from 19 bytes to 58 bytes, where most likely to be 23 or
// 55 bytes.
//
// NOTE: all the fields saved to disk use the primitive go types so they can be
// made into tlv records without further conversion.
type HTLCEntry struct {
// RHash is the payment hash of the HTLC.
RHash [32]byte
// RefundTimeout is the absolute timeout on the HTLC that the sender
// must wait before reclaiming the funds in limbo.
RefundTimeout uint32
// OutputIndex is the output index for this particular HTLC output
// within the commitment transaction.
//
// NOTE: we use uint16 instead of int32 here to save us 2 bytes, which
// gives us a max number of HTLCs of 65K.
OutputIndex uint16
// Incoming denotes whether we're the receiver or the sender of this
// HTLC.
//
// NOTE: this field is the memory representation of the field
// incomingUint.
Incoming bool
// Amt is the amount of satoshis this HTLC escrows.
//
// NOTE: this field is the memory representation of the field amtUint.
Amt btcutil.Amount
// amtTlv is the uint64 format of Amt. This field is created so we can
// easily make it into a tlv record and save it to disk.
//
// NOTE: we keep this field for accounting purpose only. If the disk
// space becomes an issue, we could delete this field to save us extra
// 8 bytes.
amtTlv uint64
// incomingTlv is the uint8 format of Incoming. This field is created
// so we can easily make it into a tlv record and save it to disk.
incomingTlv uint8
}
// RHashLen is used by MakeDynamicRecord to return the size of the RHash.
//
// NOTE: for zero hash, we return a length 0.
func (h *HTLCEntry) RHashLen() uint64 {
if h.RHash == lntypes.ZeroHash {
return 0
}
return 32
}
// RHashEncoder is the customized encoder which skips encoding the empty hash.
func RHashEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
v, ok := val.(*[32]byte)
if !ok {
return tlv.NewTypeForEncodingErr(val, "RHash")
}
// If the value is an empty hash, we will skip encoding it.
if *v == lntypes.ZeroHash {
return nil
}
return tlv.EBytes32(w, v, buf)
}
// RHashDecoder is the customized decoder which skips decoding the empty hash.
func RHashDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
v, ok := val.(*[32]byte)
if !ok {
return tlv.NewTypeForEncodingErr(val, "RHash")
}
// If the length is zero, we will skip encoding the empty hash.
if l == 0 {
return nil
}
return tlv.DBytes32(r, v, buf, 32)
}
// toTlvStream converts an HTLCEntry record into a tlv representation.
func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) {
const (
// A set of tlv type definitions used to serialize htlc entries
// to the database. We define it here instead of the head of
// the file to avoid naming conflicts.
//
// NOTE: A migration should be added whenever this list
// changes.
rHashType tlv.Type = 0
refundTimeoutType tlv.Type = 1
outputIndexType tlv.Type = 2
incomingType tlv.Type = 3
amtType tlv.Type = 4
)
return tlv.NewStream(
tlv.MakeDynamicRecord(
rHashType, &h.RHash, h.RHashLen,
RHashEncoder, RHashDecoder,
),
tlv.MakePrimitiveRecord(
refundTimeoutType, &h.RefundTimeout,
),
tlv.MakePrimitiveRecord(
outputIndexType, &h.OutputIndex,
),
tlv.MakePrimitiveRecord(incomingType, &h.incomingTlv),
// We will save 3 bytes if the amount is less or equal to
// 4,294,967,295 msat, or roughly 0.043 bitcoin.
tlv.MakeBigSizeRecord(amtType, &h.amtTlv),
)
}
// RevocationLog stores the info needed to construct a breach retribution. Its
// fields can be viewed as a subset of a ChannelCommitment's. In the database,
// all historical versions of the RevocationLog are saved using the
// CommitHeight as the key.
type RevocationLog struct {
// OurOutputIndex specifies our output index in this commitment. In a
// remote commitment transaction, this is the to remote output index.
OurOutputIndex uint16
// TheirOutputIndex specifies their output index in this commitment. In
// a remote commitment transaction, this is the to local output index.
TheirOutputIndex uint16
// CommitTxHash is the hash of the latest version of the commitment
// state, broadcast able by us.
CommitTxHash [32]byte
// HTLCEntries is the set of HTLCEntry's that are pending at this
// particular commitment height.
HTLCEntries []*HTLCEntry
// OurBalance is the current available balance within the channel
// directly spendable by us. In other words, it is the value of the
// to_remote output on the remote parties' commitment transaction.
//
// NOTE: this is a pointer so that it is clear if the value is zero or
// nil. Since migration 30 of the channeldb initially did not include
// this field, it could be the case that the field is not present for
// all revocation logs.
OurBalance *lnwire.MilliSatoshi
// TheirBalance is the current available balance within the channel
// directly spendable by the remote node. In other words, it is the
// value of the to_local output on the remote parties' commitment.
//
// NOTE: this is a pointer so that it is clear if the value is zero or
// nil. Since migration 30 of the channeldb initially did not include
// this field, it could be the case that the field is not present for
// all revocation logs.
TheirBalance *lnwire.MilliSatoshi
}
// putRevocationLog uses the fields `CommitTx` and `Htlcs` from a
// ChannelCommitment to construct a revocation log entry and saves them to
// disk. It also saves our output index and their output index, which are
// useful when creating breach retribution.
func putRevocationLog(bucket kvdb.RwBucket, commit *mig.ChannelCommitment,
ourOutputIndex, theirOutputIndex uint32, noAmtData bool) error {
// Sanity check that the output indexes can be safely converted.
if ourOutputIndex > math.MaxUint16 {
return ErrOutputIndexTooBig
}
if theirOutputIndex > math.MaxUint16 {
return ErrOutputIndexTooBig
}
rl := &RevocationLog{
OurOutputIndex: uint16(ourOutputIndex),
TheirOutputIndex: uint16(theirOutputIndex),
CommitTxHash: commit.CommitTx.TxHash(),
HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)),
}
if !noAmtData {
rl.OurBalance = &commit.LocalBalance
rl.TheirBalance = &commit.RemoteBalance
}
for _, htlc := range commit.Htlcs {
// Skip dust HTLCs.
if htlc.OutputIndex < 0 {
continue
}
// Sanity check that the output indexes can be safely
// converted.
if htlc.OutputIndex > math.MaxUint16 {
return ErrOutputIndexTooBig
}
entry := &HTLCEntry{
RHash: htlc.RHash,
RefundTimeout: htlc.RefundTimeout,
Incoming: htlc.Incoming,
OutputIndex: uint16(htlc.OutputIndex),
Amt: htlc.Amt.ToSatoshis(),
}
rl.HTLCEntries = append(rl.HTLCEntries, entry)
}
var b bytes.Buffer
err := serializeRevocationLog(&b, rl)
if err != nil {
return err
}
logEntrykey := mig24.MakeLogKey(commit.CommitHeight)
return bucket.Put(logEntrykey[:], b.Bytes())
}
// fetchRevocationLog queries the revocation log bucket to find an log entry.
// Return an error if not found.
func fetchRevocationLog(log kvdb.RBucket,
updateNum uint64) (RevocationLog, error) {
logEntrykey := mig24.MakeLogKey(updateNum)
commitBytes := log.Get(logEntrykey[:])
if commitBytes == nil {
return RevocationLog{}, ErrLogEntryNotFound
}
commitReader := bytes.NewReader(commitBytes)
return deserializeRevocationLog(commitReader)
}
// serializeRevocationLog serializes a RevocationLog record based on tlv
// format.
func serializeRevocationLog(w io.Writer, rl *RevocationLog) error {
// Add the tlv records for all non-optional fields.
records := []tlv.Record{
tlv.MakePrimitiveRecord(
revLogOurOutputIndexType, &rl.OurOutputIndex,
),
tlv.MakePrimitiveRecord(
revLogTheirOutputIndexType, &rl.TheirOutputIndex,
),
tlv.MakePrimitiveRecord(
revLogCommitTxHashType, &rl.CommitTxHash,
),
}
// Now we add any optional fields that are non-nil.
if rl.OurBalance != nil {
lb := uint64(*rl.OurBalance)
records = append(records, tlv.MakeBigSizeRecord(
revLogOurBalanceType, &lb,
))
}
if rl.TheirBalance != nil {
rb := uint64(*rl.TheirBalance)
records = append(records, tlv.MakeBigSizeRecord(
revLogTheirBalanceType, &rb,
))
}
// Create the tlv stream.
tlvStream, err := tlv.NewStream(records...)
if err != nil {
return err
}
// Write the tlv stream.
if err := writeTlvStream(w, tlvStream); err != nil {
return err
}
// Write the HTLCs.
return serializeHTLCEntries(w, rl.HTLCEntries)
}
// serializeHTLCEntries serializes a list of HTLCEntry records based on tlv
// format.
func serializeHTLCEntries(w io.Writer, htlcs []*HTLCEntry) error {
for _, htlc := range htlcs {
// Patch the incomingTlv field.
if htlc.Incoming {
htlc.incomingTlv = 1
}
// Patch the amtTlv field.
htlc.amtTlv = uint64(htlc.Amt)
// Create the tlv stream.
tlvStream, err := htlc.toTlvStream()
if err != nil {
return err
}
// Write the tlv stream.
if err := writeTlvStream(w, tlvStream); err != nil {
return err
}
}
return nil
}
// deserializeRevocationLog deserializes a RevocationLog based on tlv format.
func deserializeRevocationLog(r io.Reader) (RevocationLog, error) {
var (
rl RevocationLog
localBalance uint64
remoteBalance uint64
)
// Create the tlv stream.
tlvStream, err := tlv.NewStream(
tlv.MakePrimitiveRecord(
revLogOurOutputIndexType, &rl.OurOutputIndex,
),
tlv.MakePrimitiveRecord(
revLogTheirOutputIndexType, &rl.TheirOutputIndex,
),
tlv.MakePrimitiveRecord(
revLogCommitTxHashType, &rl.CommitTxHash,
),
tlv.MakeBigSizeRecord(revLogOurBalanceType, &localBalance),
tlv.MakeBigSizeRecord(
revLogTheirBalanceType, &remoteBalance,
),
)
if err != nil {
return rl, err
}
// Read the tlv stream.
parsedTypes, err := readTlvStream(r, tlvStream)
if err != nil {
return rl, err
}
if t, ok := parsedTypes[revLogOurBalanceType]; ok && t == nil {
lb := lnwire.MilliSatoshi(localBalance)
rl.OurBalance = &lb
}
if t, ok := parsedTypes[revLogTheirBalanceType]; ok && t == nil {
rb := lnwire.MilliSatoshi(remoteBalance)
rl.TheirBalance = &rb
}
// Read the HTLC entries.
rl.HTLCEntries, err = deserializeHTLCEntries(r)
return rl, err
}
// deserializeHTLCEntries deserializes a list of HTLC entries based on tlv
// format.
func deserializeHTLCEntries(r io.Reader) ([]*HTLCEntry, error) {
var htlcs []*HTLCEntry
for {
var htlc HTLCEntry
// Create the tlv stream.
tlvStream, err := htlc.toTlvStream()
if err != nil {
return nil, err
}
// Read the HTLC entry.
if _, err := readTlvStream(r, tlvStream); err != nil {
// We've reached the end when hitting an EOF.
if err == io.ErrUnexpectedEOF {
break
}
return nil, err
}
// Patch the Incoming field.
if htlc.incomingTlv == 1 {
htlc.Incoming = true
}
// Patch the Amt field.
htlc.Amt = btcutil.Amount(htlc.amtTlv)
// Append the entry.
htlcs = append(htlcs, &htlc)
}
return htlcs, nil
}
// writeTlvStream is a helper function that encodes the tlv stream into the
// writer.
func writeTlvStream(w io.Writer, s *tlv.Stream) error {
var b bytes.Buffer
if err := s.Encode(&b); err != nil {
return err
}
// Write the stream's length as a varint.
err := tlv.WriteVarInt(w, uint64(b.Len()), &[8]byte{})
if err != nil {
return err
}
if _, err = w.Write(b.Bytes()); err != nil {
return err
}
return nil
}
// readTlvStream is a helper function that decodes the tlv stream from the
// reader.
func readTlvStream(r io.Reader, s *tlv.Stream) (tlv.TypeMap, error) {
var bodyLen uint64
// Read the stream's length.
bodyLen, err := tlv.ReadVarInt(r, &[8]byte{})
switch {
// We'll convert any EOFs to ErrUnexpectedEOF, since this results in an
// invalid record.
case err == io.EOF:
return nil, io.ErrUnexpectedEOF
// Other unexpected errors.
case err != nil:
return nil, err
}
// TODO(yy): add overflow check.
lr := io.LimitReader(r, int64(bodyLen))
return s.DecodeWithParsedTypes(lr)
}
// fetchLogBucket returns a read bucket by visiting both the new and the old
// bucket.
func fetchLogBucket(chanBucket kvdb.RBucket) (kvdb.RBucket, error) {
logBucket := chanBucket.NestedReadBucket(revocationLogBucket)
if logBucket == nil {
logBucket = chanBucket.NestedReadBucket(
revocationLogBucketDeprecated,
)
if logBucket == nil {
return nil, mig25.ErrNoPastDeltas
}
}
return logBucket, nil
}
// putOldRevocationLog saves a revocation log using the old format.
func putOldRevocationLog(log kvdb.RwBucket,
commit *mig.ChannelCommitment) error {
var b bytes.Buffer
if err := mig.SerializeChanCommit(&b, commit); err != nil {
return err
}
logEntrykey := mig24.MakeLogKey(commit.CommitHeight)
return log.Put(logEntrykey[:], b.Bytes())
}
func putChanRevocationState(chanBucket kvdb.RwBucket,
channel *mig26.OpenChannel) error {
var b bytes.Buffer
err := mig.WriteElements(
&b, channel.RemoteCurrentRevocation, channel.RevocationProducer,
channel.RevocationStore,
)
if err != nil {
return err
}
// TODO(roasbeef): don't keep producer on disk
// If the next revocation is present, which is only the case after the
// FundingLocked message has been sent, then we'll write it to disk.
if channel.RemoteNextRevocation != nil {
err = mig.WriteElements(&b, channel.RemoteNextRevocation)
if err != nil {
return err
}
}
return chanBucket.Put(revocationStateKey, b.Bytes())
}
func fetchChanRevocationState(chanBucket kvdb.RBucket,
c *mig26.OpenChannel) error {
revBytes := chanBucket.Get(revocationStateKey)
if revBytes == nil {
return ErrNoRevocationsFound
}
r := bytes.NewReader(revBytes)
err := mig.ReadElements(
r, &c.RemoteCurrentRevocation, &c.RevocationProducer,
&c.RevocationStore,
)
if err != nil {
return err
}
// If there aren't any bytes left in the buffer, then we don't yet have
// the next remote revocation, so we can exit early here.
if r.Len() == 0 {
return nil
}
// Otherwise we'll read the next revocation for the remote party which
// is always the last item within the buffer.
return mig.ReadElements(r, &c.RemoteNextRevocation)
}
func findOutputIndexes(chanState *mig26.OpenChannel,
oldLog *mig.ChannelCommitment) (uint32, uint32, error) {
// With the state number broadcast known, we can now derive/restore the
// proper revocation preimage necessary to sweep the remote party's
// output.
revocationPreimage, err := chanState.RevocationStore.LookUp(
oldLog.CommitHeight,
)
if err != nil {
return 0, 0, err
}
return findOutputIndexesFromRemote(
revocationPreimage, chanState, oldLog,
)
}
package migration30
import (
"encoding/binary"
"io"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/shachain"
"github.com/stretchr/testify/mock"
)
// mockStore mocks the shachain.Store.
type mockStore struct {
mock.Mock
}
// A compile time check to ensure mockStore implements the Store interface.
var _ shachain.Store = (*mockStore)(nil)
func (m *mockStore) LookUp(height uint64) (*chainhash.Hash, error) {
args := m.Called(height)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*chainhash.Hash), args.Error(1)
}
func (m *mockStore) AddNextEntry(preimage *chainhash.Hash) error {
args := m.Called(preimage)
return args.Error(0)
}
// Encode encodes a series of dummy values to pass the serialize/deserialize
// process.
func (m *mockStore) Encode(w io.Writer) error {
err := binary.Write(w, binary.BigEndian, int8(1))
if err != nil {
return err
}
if err := binary.Write(w, binary.BigEndian, uint64(0)); err != nil {
return err
}
if _, err = w.Write(preimage2); err != nil {
return err
}
return binary.Write(w, binary.BigEndian, uint64(0))
}
package migration30
import (
"bytes"
"fmt"
"math/rand"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
mig25 "github.com/lightningnetwork/lnd/channeldb/migration25"
mig26 "github.com/lightningnetwork/lnd/channeldb/migration26"
mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/shachain"
)
var (
testChainHash = chainhash.Hash{1, 2, 3}
testChanType = mig25.SingleFunderTweaklessBit
// testOurIndex and testTheirIndex are artificial indexes that're saved
// to db during test setup. They are different from indexes populated
// from the actual migration process so we can check whether a new
// revocation log is overwritten or not.
testOurIndex = uint32(100)
testTheirIndex = uint32(200)
// dummyInput is used in our commit tx.
dummyInput = &wire.TxIn{
PreviousOutPoint: wire.OutPoint{
Hash: chainhash.Hash{},
Index: 0xffffffff,
},
Sequence: 0xffffffff,
}
// htlcScript is the PkScript used in the HTLC output. This script
// corresponds to revocation preimage2.
htlcScript = []byte{
0x0, 0x20, 0x3d, 0x51, 0x66, 0xda, 0x39, 0x93,
0x7b, 0x49, 0xaf, 0x2, 0xf2, 0x2f, 0x90, 0x52,
0x8e, 0x45, 0x24, 0x34, 0x8f, 0xd8, 0x76, 0x7,
0x5a, 0xfc, 0x52, 0x8d, 0x68, 0xdd, 0xbc, 0xce,
0x3e, 0x5d,
}
// toLocalScript is the PkScript used in to-local output.
toLocalScript = []byte{
0x0, 0x14, 0xc6, 0x9, 0x62, 0xab, 0x60, 0xbe,
0x40, 0xd, 0xab, 0x31, 0xc, 0x13, 0x14, 0x15,
0x93, 0xe6, 0xa2, 0x94, 0xe4, 0x2a,
}
// preimage1 defines a revocation preimage, generated from itest.
preimage1 = []byte{
0x95, 0xb4, 0x7c, 0x5a, 0x2b, 0xfd, 0x6f, 0xf4,
0x70, 0x8, 0xc, 0x70, 0x82, 0x36, 0xc8, 0x5,
0x88, 0x16, 0xaf, 0x29, 0xb5, 0x8, 0xfd, 0x5a,
0x40, 0x28, 0x24, 0xc, 0x2a, 0x7f, 0x96, 0xcd,
}
// commitTx1 is the tx saved in the first old revocation.
commitTx1 = &wire.MsgTx{
Version: 2,
// Add a dummy input.
TxIn: []*wire.TxIn{dummyInput},
TxOut: []*wire.TxOut{
{
Value: 990_950,
PkScript: toLocalScript,
},
},
}
// logHeight1 is the CommitHeight used by oldLog1.
logHeight1 = uint64(0)
// localBalance1 is the LocalBalance used in oldLog1.
localBalance1 = lnwire.MilliSatoshi(990_950_000)
// remoteBalance1 is the RemoteBalance used in oldLog1.
remoteBalance1 = lnwire.MilliSatoshi(0)
// oldLog1 defines an old revocation that has no HTLCs.
oldLog1 = mig.ChannelCommitment{
CommitHeight: logHeight1,
LocalLogIndex: 0,
LocalHtlcIndex: 0,
RemoteLogIndex: 0,
RemoteHtlcIndex: 0,
LocalBalance: localBalance1,
RemoteBalance: remoteBalance1,
CommitTx: commitTx1,
}
// newLog1 is the new version of oldLog1 in the case were we don't want
// to store any balance data.
newLog1 = RevocationLog{
OurOutputIndex: 0,
TheirOutputIndex: OutputIndexEmpty,
CommitTxHash: commitTx1.TxHash(),
}
// newLog1WithAmts is the new version of oldLog1 in the case were we do
// want to store balance data.
newLog1WithAmts = RevocationLog{
OurOutputIndex: 0,
TheirOutputIndex: OutputIndexEmpty,
CommitTxHash: commitTx1.TxHash(),
OurBalance: &localBalance1,
TheirBalance: &remoteBalance1,
}
// preimage2 defines the second revocation preimage used in the test,
// generated from itest.
preimage2 = []byte{
0xac, 0x60, 0x7a, 0x59, 0x9, 0xd6, 0x11, 0xb2,
0xf5, 0x6e, 0xaa, 0xc6, 0xb9, 0x0, 0x12, 0xdc,
0xf0, 0x89, 0x58, 0x90, 0x8a, 0xa2, 0xc6, 0xfc,
0xf1, 0x2, 0x74, 0x87, 0x30, 0x51, 0x5e, 0xea,
}
// commitTx2 is the tx saved in the second old revocation.
commitTx2 = &wire.MsgTx{
Version: 2,
// Add a dummy input.
TxIn: []*wire.TxIn{dummyInput},
TxOut: []*wire.TxOut{
{
Value: 100_000,
PkScript: htlcScript,
},
{
Value: 888_800,
PkScript: toLocalScript,
},
},
}
// rHash is the payment hash used in the htlc below.
rHash = [32]byte{
0x42, 0x5e, 0xd4, 0xe4, 0xa3, 0x6b, 0x30, 0xea,
0x21, 0xb9, 0xe, 0x21, 0xc7, 0x12, 0xc6, 0x49,
0xe8, 0x21, 0x4c, 0x29, 0xb7, 0xea, 0xf6, 0x80,
0x89, 0xd1, 0x3, 0x9c, 0x6e, 0x55, 0x38, 0x4c,
}
// htlc defines an HTLC that's saved in the old revocation log.
htlc = mig.HTLC{
RHash: rHash,
Amt: lnwire.MilliSatoshi(100_000_000),
RefundTimeout: 489,
OutputIndex: 0,
Incoming: false,
OnionBlob: bytes.Repeat([]byte{0xff}, 1366),
HtlcIndex: 0,
LogIndex: 0,
}
// logHeight2 is the CommitHeight used by oldLog2.
logHeight2 = uint64(1)
// localBalance2 is the LocalBalance used in oldLog2.
localBalance2 = lnwire.MilliSatoshi(888_800_000)
// remoteBalance2 is the RemoteBalance used in oldLog2.
remoteBalance2 = lnwire.MilliSatoshi(0)
// oldLog2 defines an old revocation that has one HTLC.
oldLog2 = mig.ChannelCommitment{
CommitHeight: logHeight2,
LocalLogIndex: 1,
LocalHtlcIndex: 1,
RemoteLogIndex: 0,
RemoteHtlcIndex: 0,
LocalBalance: localBalance2,
RemoteBalance: remoteBalance2,
CommitTx: commitTx2,
Htlcs: []mig.HTLC{htlc},
}
// newLog2 is the new version of the oldLog2 in the case were we don't
// want to store any balance data.
newLog2 = RevocationLog{
OurOutputIndex: 1,
TheirOutputIndex: OutputIndexEmpty,
CommitTxHash: commitTx2.TxHash(),
HTLCEntries: []*HTLCEntry{
{
RHash: rHash,
RefundTimeout: 489,
OutputIndex: 0,
Incoming: false,
Amt: btcutil.Amount(100_000),
},
},
}
// newLog2WithAmts is the new version of oldLog2 in the case were we do
// want to store balance data.
newLog2WithAmts = RevocationLog{
OurOutputIndex: 1,
TheirOutputIndex: OutputIndexEmpty,
CommitTxHash: commitTx2.TxHash(),
OurBalance: &localBalance2,
TheirBalance: &remoteBalance2,
HTLCEntries: []*HTLCEntry{
{
RHash: rHash,
RefundTimeout: 489,
OutputIndex: 0,
Incoming: false,
Amt: btcutil.Amount(100_000),
},
},
}
// newLog3 defines an revocation log that's been created after v0.15.0.
newLog3 = mig.ChannelCommitment{
CommitHeight: logHeight2 + 1,
LocalLogIndex: 1,
LocalHtlcIndex: 1,
RemoteLogIndex: 0,
RemoteHtlcIndex: 0,
LocalBalance: lnwire.MilliSatoshi(888_800_000),
RemoteBalance: 0,
CommitTx: commitTx2,
Htlcs: []mig.HTLC{htlc},
}
// The following public keys are taken from the itest results.
localMusigKey, _ = btcec.ParsePubKey([]byte{
0x2,
0xda, 0x42, 0xa4, 0x4a, 0x6b, 0x42, 0xfe, 0xcb,
0x2f, 0x7e, 0x35, 0x89, 0x99, 0xdd, 0x43, 0xba,
0x4b, 0xf1, 0x9c, 0xf, 0x18, 0xef, 0x9, 0x83,
0x35, 0x31, 0x59, 0xa4, 0x3b, 0xde, 0xa, 0xde,
})
localRevocationBasePoint, _ = btcec.ParsePubKey([]byte{
0x2,
0x6, 0x16, 0xd1, 0xb1, 0x4f, 0xee, 0x11, 0x86,
0x55, 0xfe, 0x31, 0x66, 0x6f, 0x43, 0x1, 0x80,
0xa8, 0xa7, 0x5c, 0x2, 0x92, 0xe5, 0x7c, 0x4,
0x31, 0xa6, 0xcf, 0x43, 0xb6, 0xdb, 0xe6, 0x10,
})
localPaymentBasePoint, _ = btcec.ParsePubKey([]byte{
0x2,
0x88, 0x65, 0x16, 0xc2, 0x37, 0x3f, 0xc5, 0x16,
0x62, 0x71, 0x0, 0xdd, 0x4d, 0x43, 0x28, 0x43,
0x32, 0x91, 0x75, 0xcc, 0xd8, 0x81, 0xb6, 0xb0,
0xd8, 0x96, 0x78, 0xad, 0x18, 0x3b, 0x16, 0xe1,
})
localDelayBasePoint, _ = btcec.ParsePubKey([]byte{
0x2,
0xea, 0x41, 0x48, 0x11, 0x2, 0x59, 0xe3, 0x5c,
0x51, 0x15, 0x90, 0x25, 0x4a, 0x61, 0x5, 0x51,
0xb3, 0x8, 0xe9, 0xd5, 0xf, 0xc6, 0x91, 0x25,
0x14, 0xd2, 0xcf, 0xc8, 0xc5, 0x5b, 0xd9, 0x88,
})
localHtlcBasePoint, _ = btcec.ParsePubKey([]byte{
0x3,
0xfa, 0x1f, 0x6, 0x3a, 0xa4, 0x75, 0x2e, 0x74,
0x3e, 0x55, 0x9, 0x20, 0x6e, 0xf6, 0xa8, 0xe1,
0xd7, 0x61, 0x50, 0x75, 0xa8, 0x34, 0x15, 0xc3,
0x6b, 0xdc, 0xb0, 0xbf, 0xaa, 0x66, 0xd7, 0xa7,
})
remoteMultiSigKey, _ = btcec.ParsePubKey([]byte{
0x2,
0x2b, 0x88, 0x7c, 0x6a, 0xf8, 0xb3, 0x51, 0x61,
0xd3, 0x1c, 0xf1, 0xe4, 0x43, 0xc2, 0x8c, 0x5e,
0xfa, 0x8e, 0xb5, 0xe9, 0xd0, 0x14, 0xb5, 0x33,
0x6a, 0xcc, 0xd, 0x11, 0x42, 0xb8, 0x4b, 0x7d,
})
remoteRevocationBasePoint, _ = btcec.ParsePubKey([]byte{
0x2,
0x6c, 0x39, 0xa3, 0x6d, 0x93, 0x69, 0xac, 0x14,
0x1f, 0xbb, 0x4, 0x86, 0x3, 0x82, 0x5, 0xe2,
0xcb, 0xb0, 0x62, 0x41, 0xa, 0x93, 0x3, 0x6c,
0x8d, 0xc0, 0x42, 0x4d, 0x9e, 0x51, 0x9b, 0x36,
})
remotePaymentBasePoint, _ = btcec.ParsePubKey([]byte{
0x3,
0xab, 0x74, 0x1e, 0x83, 0x48, 0xe3, 0xb5, 0x6,
0x25, 0x1c, 0x80, 0xe7, 0xf2, 0x3e, 0x7d, 0xb7,
0x7a, 0xc7, 0xd, 0x6, 0x3b, 0xbc, 0x74, 0x96,
0x8e, 0x9b, 0x2d, 0xd1, 0x42, 0x71, 0xa5, 0x2a,
})
remoteDelayBasePoint, _ = btcec.ParsePubKey([]byte{
0x2,
0x4b, 0xdd, 0x52, 0x46, 0x1b, 0x50, 0x89, 0xb9,
0x49, 0x4, 0xf2, 0xd2, 0x98, 0x7d, 0x51, 0xa1,
0xa6, 0x3f, 0x9b, 0xd0, 0x40, 0x7c, 0x93, 0x74,
0x3b, 0x8c, 0x4d, 0x63, 0x32, 0x90, 0xa, 0xca,
})
remoteHtlcBasePoint, _ = btcec.ParsePubKey([]byte{
0x3,
0x5b, 0x8f, 0x4a, 0x71, 0x4c, 0x2e, 0x71, 0x14,
0x86, 0x1f, 0x30, 0x96, 0xc0, 0xd4, 0x11, 0x76,
0xf8, 0xc3, 0xfc, 0x7, 0x2d, 0x15, 0x99, 0x55,
0x8, 0x69, 0xf6, 0x1, 0xa2, 0xcd, 0x6b, 0xa7,
})
// withAmtData if set, will result in the amount data of the revoked
// commitment transactions also being stored in the new revocation log.
// The value of this variable is set randomly in the init function of
// this package.
withAmtData bool
)
// setupTestLogs takes care of creating the related buckets and inserts testing
// records.
func setupTestLogs(db kvdb.Backend, c *mig26.OpenChannel,
oldLogs, newLogs []mig.ChannelCommitment) error {
return kvdb.Update(db, func(tx kvdb.RwTx) error {
// If the open channel is nil, only create the root
// bucket and skip creating the channel bucket.
if c == nil {
_, err := tx.CreateTopLevelBucket(openChannelBucket)
return err
}
// Create test buckets.
chanBucket, err := mig25.CreateChanBucket(tx, &c.OpenChannel)
if err != nil {
return err
}
// Save channel info.
if err := mig26.PutChanInfo(chanBucket, c, false); err != nil {
return fmt.Errorf("PutChanInfo got %w", err)
}
// Save revocation state.
if err := putChanRevocationState(chanBucket, c); err != nil {
return fmt.Errorf("putChanRevocationState got %w", err)
}
// Create old logs.
err = writeOldRevocationLogs(chanBucket, oldLogs)
if err != nil {
return fmt.Errorf("write old logs: %w", err)
}
// Create new logs.
return writeNewRevocationLogs(chanBucket, newLogs, !withAmtData)
}, func() {})
}
// createTestChannel creates an OpenChannel using the specified nodePub and
// outpoint. If any of the params is nil, a random value is populated.
func createTestChannel(nodePub *btcec.PublicKey) *mig26.OpenChannel {
// Create a random private key that's used to provide randomness.
priv, _ := btcec.NewPrivateKey()
// If passed public key is nil, use the random public key.
if nodePub == nil {
nodePub = priv.PubKey()
}
// Create a random channel point.
var op wire.OutPoint
copy(op.Hash[:], priv.Serialize())
testProducer := shachain.NewRevocationProducer(op.Hash)
store, _ := createTestStore()
localCfg := mig.ChannelConfig{
ChannelConstraints: mig.ChannelConstraints{
DustLimit: btcutil.Amount(354),
MaxAcceptedHtlcs: 483,
CsvDelay: 4,
},
MultiSigKey: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: 0,
Index: 0,
},
PubKey: localMusigKey,
},
RevocationBasePoint: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: 1,
Index: 0,
},
PubKey: localRevocationBasePoint,
},
HtlcBasePoint: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: 2,
Index: 0,
},
PubKey: localHtlcBasePoint,
},
PaymentBasePoint: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: 3,
Index: 0,
},
PubKey: localPaymentBasePoint,
},
DelayBasePoint: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: 4,
Index: 0,
},
PubKey: localDelayBasePoint,
},
}
remoteCfg := mig.ChannelConfig{
ChannelConstraints: mig.ChannelConstraints{
DustLimit: btcutil.Amount(354),
MaxAcceptedHtlcs: 483,
CsvDelay: 4,
},
MultiSigKey: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: 0,
Index: 0,
},
PubKey: remoteMultiSigKey,
},
RevocationBasePoint: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: 0,
Index: 0,
},
PubKey: remoteRevocationBasePoint,
},
HtlcBasePoint: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: 0,
Index: 0,
},
PubKey: remoteHtlcBasePoint,
},
PaymentBasePoint: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: 0,
Index: 0,
},
PubKey: remotePaymentBasePoint,
},
DelayBasePoint: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: 0,
Index: 0,
},
PubKey: remoteDelayBasePoint,
},
}
c := &mig26.OpenChannel{
OpenChannel: mig25.OpenChannel{
OpenChannel: mig.OpenChannel{
ChainHash: testChainHash,
IdentityPub: nodePub,
FundingOutpoint: op,
LocalChanCfg: localCfg,
RemoteChanCfg: remoteCfg,
// Assign dummy values.
RemoteCurrentRevocation: nodePub,
RevocationProducer: testProducer,
RevocationStore: store,
},
ChanType: testChanType,
},
}
return c
}
// writeOldRevocationLogs saves an old revocation log to db.
func writeOldRevocationLogs(chanBucket kvdb.RwBucket,
oldLogs []mig.ChannelCommitment) error {
// Don't bother continue if the logs are empty.
if len(oldLogs) == 0 {
return nil
}
logBucket, err := chanBucket.CreateBucketIfNotExists(
revocationLogBucketDeprecated,
)
if err != nil {
return err
}
for _, c := range oldLogs {
if err := putOldRevocationLog(logBucket, &c); err != nil {
return err
}
}
return nil
}
// writeNewRevocationLogs saves a new revocation log to db.
func writeNewRevocationLogs(chanBucket kvdb.RwBucket,
oldLogs []mig.ChannelCommitment, noAmtData bool) error {
// Don't bother continue if the logs are empty.
if len(oldLogs) == 0 {
return nil
}
logBucket, err := chanBucket.CreateBucketIfNotExists(
revocationLogBucket,
)
if err != nil {
return err
}
for _, c := range oldLogs {
// NOTE: we just blindly write the output indexes to db here
// whereas normally, we would find the correct indexes from the
// old commit tx. We do this intentionally so we can
// distinguish a newly created log from an already saved one.
err := putRevocationLog(
logBucket, &c, testOurIndex, testTheirIndex, noAmtData,
)
if err != nil {
return err
}
}
return nil
}
// createTestStore creates a revocation store and always saves the above
// defined two preimages into the store.
func createTestStore() (shachain.Store, error) {
var p chainhash.Hash
copy(p[:], preimage1)
testStore := shachain.NewRevocationStore()
if err := testStore.AddNextEntry(&p); err != nil {
return nil, err
}
copy(p[:], preimage2)
if err := testStore.AddNextEntry(&p); err != nil {
return nil, err
}
return testStore, nil
}
// createNotStarted will setup a situation where we haven't started the
// migration for the channel. We use the legacy to denote whether to simulate a
// node with v0.15.0.
func createNotStarted(cdb kvdb.Backend, c *mig26.OpenChannel,
legacy bool) error {
var newLogs []mig.ChannelCommitment
// Create test logs.
oldLogs := []mig.ChannelCommitment{oldLog1, oldLog2}
// Add a new log if the node is running with v0.15.0.
if !legacy {
newLogs = []mig.ChannelCommitment{newLog3}
}
return setupTestLogs(cdb, c, oldLogs, newLogs)
}
// createNotFinished will setup a situation where we have un-migrated logs and
// return the next migration height. We use the legacy to denote whether to
// simulate a node with v0.15.0.
func createNotFinished(cdb kvdb.Backend, c *mig26.OpenChannel,
legacy bool) error {
// Create test logs.
oldLogs := []mig.ChannelCommitment{oldLog1, oldLog2}
newLogs := []mig.ChannelCommitment{oldLog1}
// Add a new log if the node is running with v0.15.0.
if !legacy {
newLogs = append(newLogs, newLog3)
}
return setupTestLogs(cdb, c, oldLogs, newLogs)
}
// createFinished will setup a situation where all the old logs have been
// migrated and return a nil. We use the legacy to denote whether to simulate a
// node with v0.15.0.
func createFinished(cdb kvdb.Backend, c *mig26.OpenChannel,
legacy bool) error {
// Create test logs.
oldLogs := []mig.ChannelCommitment{oldLog1, oldLog2}
newLogs := []mig.ChannelCommitment{oldLog1, oldLog2}
// Add a new log if the node is running with v0.15.0.
if !legacy {
newLogs = append(newLogs, newLog3)
}
return setupTestLogs(cdb, c, oldLogs, newLogs)
}
func init() {
rand.Seed(time.Now().Unix())
if rand.Intn(2) == 0 {
withAmtData = true
}
if withAmtData {
newLog1 = newLog1WithAmts
newLog2 = newLog2WithAmts
}
}
package migration31
import (
"github.com/btcsuite/btclog/v2"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration31
import (
"errors"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/lightningnetwork/lnd/kvdb"
)
// DeleteLastPublishedTxTLB deletes the top level bucket with the key
// "sweeper-last-tx".
func DeleteLastPublishedTxTLB(tx kvdb.RwTx) error {
log.Infof("Deleting top-level bucket: %x ...", lastTxBucketKey)
err := tx.DeleteTopLevelBucket(lastTxBucketKey)
if err != nil && !errors.Is(err, walletdb.ErrBucketNotFound) {
return err
}
log.Infof("Deleted top-level bucket: %x", lastTxBucketKey)
return nil
}
package migration32
import (
"encoding/binary"
"fmt"
"io"
"github.com/btcsuite/btcd/wire"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
)
var (
// Big endian is the preferred byte order, due to cursor scans over
// integer keys iterating in order.
byteOrder = binary.BigEndian
)
// ReadElement is a one-stop utility function to deserialize any datastructure
// encoded using the serialization format of the database.
func ReadElement(r io.Reader, element interface{}) error {
switch e := element.(type) {
case *uint32:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *lnwire.MilliSatoshi:
var a uint64
if err := binary.Read(r, byteOrder, &a); err != nil {
return err
}
*e = lnwire.MilliSatoshi(a)
case *[]byte:
bytes, err := wire.ReadVarBytes(r, 0, 66000, "[]byte")
if err != nil {
return err
}
*e = bytes
case *int64, *uint64:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *bool:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *int32:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
default:
return UnknownElementType{"ReadElement", e}
}
return nil
}
// ReadElements deserializes a variable number of elements into the passed
// io.Reader, with each element being deserialized according to the ReadElement
// function.
func ReadElements(r io.Reader, elements ...interface{}) error {
for _, element := range elements {
err := ReadElement(r, element)
if err != nil {
return err
}
}
return nil
}
// UnknownElementType is an error returned when the codec is unable to encode or
// decode a particular type.
type UnknownElementType struct {
method string
element interface{}
}
// Error returns the name of the method that encountered the error, as well as
// the type that was unsupported.
func (e UnknownElementType) Error() string {
return fmt.Sprintf("Unknown type in %s: %T", e.method, e.element)
}
// WriteElement is a one-stop shop to write the big endian representation of
// any element which is to be serialized for storage on disk. The passed
// io.Writer should be backed by an appropriately sized byte slice, or be able
// to dynamically expand to accommodate additional data.
func WriteElement(w io.Writer, element interface{}) error {
switch e := element.(type) {
case int64, uint64:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case uint32:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case int32:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case bool:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case lnwire.MilliSatoshi:
if err := binary.Write(w, byteOrder, uint64(e)); err != nil {
return err
}
case []byte:
if err := wire.WriteVarBytes(w, 0, e); err != nil {
return err
}
default:
return UnknownElementType{"WriteElement", e}
}
return nil
}
// WriteElements is writes each element in the elements slice to the passed
// io.Writer using WriteElement.
func WriteElements(w io.Writer, elements ...interface{}) error {
for _, element := range elements {
err := WriteElement(w, element)
if err != nil {
return err
}
}
return nil
}
package migration32
import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// EncryptedDataOnionType is the type used to include encrypted data
// provided by the receiver in the onion for use in blinded paths.
EncryptedDataOnionType tlv.Type = 10
// BlindingPointOnionType is the type used to include receiver provided
// ephemeral keys in the onion that are used in blinded paths.
BlindingPointOnionType tlv.Type = 12
// MetadataOnionType is the type used in the onion for the payment
// metadata.
MetadataOnionType tlv.Type = 16
// TotalAmtMsatBlindedType is the type used in the onion for the total
// amount field that is included in the final hop for blinded payments.
TotalAmtMsatBlindedType tlv.Type = 18
)
// NewEncryptedDataRecord creates a tlv.Record that encodes the encrypted_data
// (type 10) record for an onion payload.
func NewEncryptedDataRecord(data *[]byte) tlv.Record {
return tlv.MakePrimitiveRecord(EncryptedDataOnionType, data)
}
// NewBlindingPointRecord creates a tlv.Record that encodes the blinding_point
// (type 12) record for an onion payload.
func NewBlindingPointRecord(point **btcec.PublicKey) tlv.Record {
return tlv.MakePrimitiveRecord(BlindingPointOnionType, point)
}
// NewMetadataRecord creates a tlv.Record that encodes the metadata (type 10)
// for an onion payload.
func NewMetadataRecord(metadata *[]byte) tlv.Record {
return tlv.MakeDynamicRecord(
MetadataOnionType, metadata,
func() uint64 {
return uint64(len(*metadata))
},
tlv.EVarBytes, tlv.DVarBytes,
)
}
// NewTotalAmtMsatBlinded creates a tlv.Record that encodes the
// total_amount_msat for the final an onion payload within a blinded route.
func NewTotalAmtMsatBlinded(amt *uint64) tlv.Record {
return tlv.MakeDynamicRecord(
TotalAmtMsatBlindedType, amt, func() uint64 {
return tlv.SizeTUint64(*amt)
},
tlv.ETUint64, tlv.DTUint64,
)
}
package migration32
import (
"github.com/btcsuite/btclog/v2"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration32
import (
"bytes"
"fmt"
"github.com/lightningnetwork/lnd/kvdb"
)
// MigrateMCRouteSerialisation reads all the mission control store entries and
// re-serializes them using a minimal route serialisation so that only the parts
// of the route that are actually required for mission control are persisted.
func MigrateMCRouteSerialisation(tx kvdb.RwTx) error {
log.Infof("Migrating Mission Control store to use a more minimal " +
"encoding for routes")
resultBucket := tx.ReadWriteBucket(resultsKey)
// If the results bucket does not exist then there are no entries in
// the mission control store yet and so there is nothing to migrate.
if resultBucket == nil {
return nil
}
// For each entry, read it into memory using the old encoding. Then,
// extract the more minimal route, re-encode and persist the entry.
return resultBucket.ForEach(func(k, v []byte) error {
// Read the entry using the old encoding.
resultOld, err := deserializeOldResult(k, v)
if err != nil {
return err
}
// Convert to the new payment result format with the minimal
// route.
resultNew := convertPaymentResult(resultOld)
// Serialise the new payment result using the new encoding.
key, resultNewBytes, err := serializeNewResult(resultNew)
if err != nil {
return err
}
// Make sure that the derived key is the same.
if !bytes.Equal(key, k) {
return fmt.Errorf("new payment result key (%v) is "+
"not the same as the old key (%v)", key, k)
}
// Finally, overwrite the previous value with the new encoding.
return resultBucket.Put(k, resultNewBytes)
})
}
package migration32
import (
"bytes"
"io"
"math"
"time"
"github.com/btcsuite/btcd/wire"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// unknownFailureSourceIdx is the database encoding of an unknown error
// source.
unknownFailureSourceIdx = -1
)
var (
// resultsKey is the fixed key under which the attempt results are
// stored.
resultsKey = []byte("missioncontrol-results")
)
// paymentResultOld is the information that becomes available when a payment
// attempt completes.
type paymentResultOld struct {
id uint64
timeFwd, timeReply time.Time
route *Route
success bool
failureSourceIdx *int
failure lnwire.FailureMessage
}
// deserializeOldResult deserializes a payment result using the old encoding.
func deserializeOldResult(k, v []byte) (*paymentResultOld, error) {
// Parse payment id.
result := paymentResultOld{
id: byteOrder.Uint64(k[8:]),
}
r := bytes.NewReader(v)
// Read timestamps, success status and failure source index.
var (
timeFwd, timeReply uint64
dbFailureSourceIdx int32
)
err := ReadElements(
r, &timeFwd, &timeReply, &result.success, &dbFailureSourceIdx,
)
if err != nil {
return nil, err
}
// Convert time stamps to local time zone for consistent logging.
result.timeFwd = time.Unix(0, int64(timeFwd)).Local()
result.timeReply = time.Unix(0, int64(timeReply)).Local()
// Convert from unknown index magic number to nil value.
if dbFailureSourceIdx != unknownFailureSourceIdx {
failureSourceIdx := int(dbFailureSourceIdx)
result.failureSourceIdx = &failureSourceIdx
}
// Read route.
route, err := DeserializeRoute(r)
if err != nil {
return nil, err
}
result.route = &route
// Read failure.
failureBytes, err := wire.ReadVarBytes(r, 0, math.MaxUint16, "failure")
if err != nil {
return nil, err
}
if len(failureBytes) > 0 {
result.failure, err = lnwire.DecodeFailureMessage(
bytes.NewReader(failureBytes), 0,
)
if err != nil {
return nil, err
}
}
return &result, nil
}
// convertPaymentResult converts a paymentResultOld to a paymentResultNew.
func convertPaymentResult(old *paymentResultOld) *paymentResultNew {
var failure *paymentFailure
if !old.success {
failure = newPaymentFailure(old.failureSourceIdx, old.failure)
}
return newPaymentResult(
old.id, extractMCRoute(old.route), old.timeFwd, old.timeReply,
failure,
)
}
// newPaymentResult constructs a new paymentResult.
func newPaymentResult(id uint64, rt *mcRoute, timeFwd, timeReply time.Time,
failure *paymentFailure) *paymentResultNew {
result := &paymentResultNew{
id: id,
timeFwd: tlv.NewPrimitiveRecord[tlv.TlvType0](
uint64(timeFwd.UnixNano()),
),
timeReply: tlv.NewPrimitiveRecord[tlv.TlvType1](
uint64(timeReply.UnixNano()),
),
route: tlv.NewRecordT[tlv.TlvType2](*rt),
}
if failure != nil {
result.failure = tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType3](*failure),
)
}
return result
}
// paymentResultNew is the information that becomes available when a payment
// attempt completes.
type paymentResultNew struct {
id uint64
timeFwd tlv.RecordT[tlv.TlvType0, uint64]
timeReply tlv.RecordT[tlv.TlvType1, uint64]
route tlv.RecordT[tlv.TlvType2, mcRoute]
// failure holds information related to the failure of a payment. The
// presence of this record indicates a payment failure. The absence of
// this record indicates a successful payment.
failure tlv.OptionalRecordT[tlv.TlvType3, paymentFailure]
}
// paymentFailure represents the presence of a payment failure. It may or may
// not include additional information about said failure.
type paymentFailure struct {
info tlv.OptionalRecordT[tlv.TlvType0, paymentFailureInfo]
}
// newPaymentFailure constructs a new paymentFailure struct. If the source
// index is nil, then an empty paymentFailure is returned. This represents a
// failure with unknown details. Otherwise, the index and failure message are
// used to populate the info field of the paymentFailure.
func newPaymentFailure(sourceIdx *int,
failureMsg lnwire.FailureMessage) *paymentFailure {
if sourceIdx == nil {
return &paymentFailure{}
}
info := paymentFailureInfo{
sourceIdx: tlv.NewPrimitiveRecord[tlv.TlvType0](
uint8(*sourceIdx),
),
msg: tlv.NewRecordT[tlv.TlvType1](failureMessage{failureMsg}),
}
return &paymentFailure{
info: tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType0](info)),
}
}
// Record returns a TLV record that can be used to encode/decode a
// paymentFailure to/from a TLV stream.
func (r *paymentFailure) Record() tlv.Record {
recordSize := func() uint64 {
var (
b bytes.Buffer
buf [8]byte
)
if err := encodePaymentFailure(&b, r, &buf); err != nil {
panic(err)
}
return uint64(len(b.Bytes()))
}
return tlv.MakeDynamicRecord(
0, r, recordSize, encodePaymentFailure, decodePaymentFailure,
)
}
func encodePaymentFailure(w io.Writer, val interface{}, _ *[8]byte) error {
if v, ok := val.(*paymentFailure); ok {
var recordProducers []tlv.RecordProducer
v.info.WhenSome(
func(r tlv.RecordT[tlv.TlvType0, paymentFailureInfo]) {
recordProducers = append(recordProducers, &r)
},
)
return lnwire.EncodeRecordsTo(
w, lnwire.ProduceRecordsSorted(recordProducers...),
)
}
return tlv.NewTypeForEncodingErr(val, "routing.paymentFailure")
}
func decodePaymentFailure(r io.Reader, val interface{}, _ *[8]byte,
l uint64) error {
if v, ok := val.(*paymentFailure); ok {
var h paymentFailure
info := tlv.ZeroRecordT[tlv.TlvType0, paymentFailureInfo]()
typeMap, err := lnwire.DecodeRecords(
r, lnwire.ProduceRecordsSorted(&info)...,
)
if err != nil {
return err
}
if _, ok := typeMap[h.info.TlvType()]; ok {
h.info = tlv.SomeRecordT(info)
}
*v = h
return nil
}
return tlv.NewTypeForDecodingErr(val, "routing.paymentFailure", l, l)
}
// paymentFailureInfo holds additional information about a payment failure.
type paymentFailureInfo struct {
sourceIdx tlv.RecordT[tlv.TlvType0, uint8]
msg tlv.RecordT[tlv.TlvType1, failureMessage]
}
// Record returns a TLV record that can be used to encode/decode a
// paymentFailureInfo to/from a TLV stream.
func (r *paymentFailureInfo) Record() tlv.Record {
recordSize := func() uint64 {
var (
b bytes.Buffer
buf [8]byte
)
if err := encodePaymentFailureInfo(&b, r, &buf); err != nil {
panic(err)
}
return uint64(len(b.Bytes()))
}
return tlv.MakeDynamicRecord(
0, r, recordSize, encodePaymentFailureInfo,
decodePaymentFailureInfo,
)
}
func encodePaymentFailureInfo(w io.Writer, val interface{}, _ *[8]byte) error {
if v, ok := val.(*paymentFailureInfo); ok {
return lnwire.EncodeRecordsTo(
w, lnwire.ProduceRecordsSorted(
&v.sourceIdx, &v.msg,
),
)
}
return tlv.NewTypeForEncodingErr(val, "routing.paymentFailureInfo")
}
func decodePaymentFailureInfo(r io.Reader, val interface{}, _ *[8]byte,
l uint64) error {
if v, ok := val.(*paymentFailureInfo); ok {
var h paymentFailureInfo
_, err := lnwire.DecodeRecords(
r,
lnwire.ProduceRecordsSorted(&h.sourceIdx, &h.msg)...,
)
if err != nil {
return err
}
*v = h
return nil
}
return tlv.NewTypeForDecodingErr(
val, "routing.paymentFailureInfo", l, l,
)
}
type failureMessage struct {
lnwire.FailureMessage
}
// Record returns a TLV record that can be used to encode/decode a list of
// failureMessage to/from a TLV stream.
func (r *failureMessage) Record() tlv.Record {
recordSize := func() uint64 {
var (
b bytes.Buffer
buf [8]byte
)
if err := encodeFailureMessage(&b, r, &buf); err != nil {
panic(err)
}
return uint64(len(b.Bytes()))
}
return tlv.MakeDynamicRecord(
0, r, recordSize, encodeFailureMessage, decodeFailureMessage,
)
}
func encodeFailureMessage(w io.Writer, val interface{}, _ *[8]byte) error {
if v, ok := val.(*failureMessage); ok {
var b bytes.Buffer
err := lnwire.EncodeFailureMessage(&b, v.FailureMessage, 0)
if err != nil {
return err
}
_, err = w.Write(b.Bytes())
return err
}
return tlv.NewTypeForEncodingErr(val, "routing.failureMessage")
}
func decodeFailureMessage(r io.Reader, val interface{}, _ *[8]byte,
l uint64) error {
if v, ok := val.(*failureMessage); ok {
msg, err := lnwire.DecodeFailureMessage(r, 0)
if err != nil {
return err
}
*v = failureMessage{
FailureMessage: msg,
}
return nil
}
return tlv.NewTypeForDecodingErr(val, "routing.failureMessage", l, l)
}
// extractMCRoute extracts the fields required by MC from the Route struct to
// create the more minimal mcRoute struct.
func extractMCRoute(r *Route) *mcRoute {
return &mcRoute{
sourcePubKey: tlv.NewRecordT[tlv.TlvType0](r.SourcePubKey),
totalAmount: tlv.NewRecordT[tlv.TlvType1](r.TotalAmount),
hops: tlv.NewRecordT[tlv.TlvType2](
extractMCHops(r.Hops),
),
}
}
// extractMCHops extracts the Hop fields that MC actually uses from a slice of
// Hops.
func extractMCHops(hops []*Hop) mcHops {
return fn.Map(hops, extractMCHop)
}
// extractMCHop extracts the Hop fields that MC actually uses from a Hop.
func extractMCHop(hop *Hop) *mcHop {
h := mcHop{
channelID: tlv.NewPrimitiveRecord[tlv.TlvType0, uint64](
hop.ChannelID,
),
pubKeyBytes: tlv.NewRecordT[tlv.TlvType1, Vertex](
hop.PubKeyBytes,
),
amtToFwd: tlv.NewRecordT[tlv.TlvType2, lnwire.MilliSatoshi](
hop.AmtToForward,
),
}
if hop.BlindingPoint != nil {
h.hasBlindingPoint = tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType3, lnwire.TrueBoolean](
lnwire.TrueBoolean{},
),
)
}
if len(hop.CustomRecords) != 0 {
h.hasCustomRecords = tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType4, lnwire.TrueBoolean](
lnwire.TrueBoolean{},
),
)
}
return &h
}
// mcRoute holds the bare minimum info about a payment attempt route that MC
// requires.
type mcRoute struct {
sourcePubKey tlv.RecordT[tlv.TlvType0, Vertex]
totalAmount tlv.RecordT[tlv.TlvType1, lnwire.MilliSatoshi]
hops tlv.RecordT[tlv.TlvType2, mcHops]
}
// Record returns a TLV record that can be used to encode/decode an mcRoute
// to/from a TLV stream.
func (r *mcRoute) Record() tlv.Record {
recordSize := func() uint64 {
var (
b bytes.Buffer
buf [8]byte
)
if err := encodeMCRoute(&b, r, &buf); err != nil {
panic(err)
}
return uint64(len(b.Bytes()))
}
return tlv.MakeDynamicRecord(
0, r, recordSize, encodeMCRoute, decodeMCRoute,
)
}
func encodeMCRoute(w io.Writer, val interface{}, _ *[8]byte) error {
if v, ok := val.(*mcRoute); ok {
return serializeRoute(w, v)
}
return tlv.NewTypeForEncodingErr(val, "routing.mcRoute")
}
func decodeMCRoute(r io.Reader, val interface{}, _ *[8]byte, l uint64) error {
if v, ok := val.(*mcRoute); ok {
route, err := deserializeRoute(io.LimitReader(r, int64(l)))
if err != nil {
return err
}
*v = *route
return nil
}
return tlv.NewTypeForDecodingErr(val, "routing.mcRoute", l, l)
}
// mcHops is a list of mcHop records.
type mcHops []*mcHop
// Record returns a TLV record that can be used to encode/decode a list of
// mcHop to/from a TLV stream.
func (h *mcHops) Record() tlv.Record {
recordSize := func() uint64 {
var (
b bytes.Buffer
buf [8]byte
)
if err := encodeMCHops(&b, h, &buf); err != nil {
panic(err)
}
return uint64(len(b.Bytes()))
}
return tlv.MakeDynamicRecord(
0, h, recordSize, encodeMCHops, decodeMCHops,
)
}
func encodeMCHops(w io.Writer, val interface{}, buf *[8]byte) error {
if v, ok := val.(*mcHops); ok {
// Encode the number of hops as a var int.
if err := tlv.WriteVarInt(w, uint64(len(*v)), buf); err != nil {
return err
}
// With that written out, we'll now encode the entries
// themselves as a sub-TLV record, which includes its _own_
// inner length prefix.
for _, hop := range *v {
var hopBytes bytes.Buffer
if err := serializeNewHop(&hopBytes, hop); err != nil {
return err
}
// We encode the record with a varint length followed by
// the _raw_ TLV bytes.
tlvLen := uint64(len(hopBytes.Bytes()))
if err := tlv.WriteVarInt(w, tlvLen, buf); err != nil {
return err
}
if _, err := w.Write(hopBytes.Bytes()); err != nil {
return err
}
}
return nil
}
return tlv.NewTypeForEncodingErr(val, "routing.mcHops")
}
func decodeMCHops(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
if v, ok := val.(*mcHops); ok {
// First, we'll decode the varint that encodes how many hops
// are encoded in the stream.
numHops, err := tlv.ReadVarInt(r, buf)
if err != nil {
return err
}
// Now that we know how many records we'll need to read, we can
// iterate and read them all out in series.
for i := uint64(0); i < numHops; i++ {
// Read out the varint that encodes the size of this
// inner TLV record.
hopSize, err := tlv.ReadVarInt(r, buf)
if err != nil {
return err
}
// Using this information, we'll create a new limited
// reader that'll return an EOF once the end has been
// reached so the stream stops consuming bytes.
innerTlvReader := &io.LimitedReader{
R: r,
N: int64(hopSize),
}
hop, err := deserializeNewHop(innerTlvReader)
if err != nil {
return err
}
*v = append(*v, hop)
}
return nil
}
return tlv.NewTypeForDecodingErr(val, "routing.mcHops", l, l)
}
// serializeRoute serializes a mcRoute and writes the resulting bytes to the
// given io.Writer.
func serializeRoute(w io.Writer, r *mcRoute) error {
records := lnwire.ProduceRecordsSorted(
&r.sourcePubKey,
&r.totalAmount,
&r.hops,
)
return lnwire.EncodeRecordsTo(w, records)
}
// deserializeRoute deserializes the mcRoute from the given io.Reader.
func deserializeRoute(r io.Reader) (*mcRoute, error) {
var rt mcRoute
records := lnwire.ProduceRecordsSorted(
&rt.sourcePubKey,
&rt.totalAmount,
&rt.hops,
)
_, err := lnwire.DecodeRecords(r, records...)
if err != nil {
return nil, err
}
return &rt, nil
}
// deserializeNewHop deserializes the mcHop from the given io.Reader.
func deserializeNewHop(r io.Reader) (*mcHop, error) {
var (
h mcHop
blinding = tlv.ZeroRecordT[tlv.TlvType3, lnwire.TrueBoolean]()
custom = tlv.ZeroRecordT[tlv.TlvType4, lnwire.TrueBoolean]()
)
records := lnwire.ProduceRecordsSorted(
&h.channelID,
&h.pubKeyBytes,
&h.amtToFwd,
&blinding,
&custom,
)
typeMap, err := lnwire.DecodeRecords(r, records...)
if err != nil {
return nil, err
}
if _, ok := typeMap[h.hasBlindingPoint.TlvType()]; ok {
h.hasBlindingPoint = tlv.SomeRecordT(blinding)
}
if _, ok := typeMap[h.hasCustomRecords.TlvType()]; ok {
h.hasCustomRecords = tlv.SomeRecordT(custom)
}
return &h, nil
}
// serializeNewHop serializes a mcHop and writes the resulting bytes to the
// given io.Writer.
func serializeNewHop(w io.Writer, h *mcHop) error {
recordProducers := []tlv.RecordProducer{
&h.channelID,
&h.pubKeyBytes,
&h.amtToFwd,
}
h.hasBlindingPoint.WhenSome(func(
hasBlinding tlv.RecordT[tlv.TlvType3, lnwire.TrueBoolean]) {
recordProducers = append(recordProducers, &hasBlinding)
})
h.hasCustomRecords.WhenSome(func(
hasCustom tlv.RecordT[tlv.TlvType4, lnwire.TrueBoolean]) {
recordProducers = append(recordProducers, &hasCustom)
})
return lnwire.EncodeRecordsTo(
w, lnwire.ProduceRecordsSorted(recordProducers...),
)
}
// mcHop holds the bare minimum info about a payment attempt route hop that MC
// requires.
type mcHop struct {
channelID tlv.RecordT[tlv.TlvType0, uint64]
pubKeyBytes tlv.RecordT[tlv.TlvType1, Vertex]
amtToFwd tlv.RecordT[tlv.TlvType2, lnwire.MilliSatoshi]
hasBlindingPoint tlv.OptionalRecordT[tlv.TlvType3, lnwire.TrueBoolean]
hasCustomRecords tlv.OptionalRecordT[tlv.TlvType4, lnwire.TrueBoolean]
}
// serializeOldResult serializes a payment result and returns a key and value
// byte slice to insert into the bucket.
func serializeOldResult(rp *paymentResultOld) ([]byte, []byte, error) {
// Write timestamps, success status, failure source index and route.
var b bytes.Buffer
var dbFailureSourceIdx int32
if rp.failureSourceIdx == nil {
dbFailureSourceIdx = unknownFailureSourceIdx
} else {
dbFailureSourceIdx = int32(*rp.failureSourceIdx)
}
err := WriteElements(
&b,
uint64(rp.timeFwd.UnixNano()),
uint64(rp.timeReply.UnixNano()),
rp.success, dbFailureSourceIdx,
)
if err != nil {
return nil, nil, err
}
if err := SerializeRoute(&b, *rp.route); err != nil {
return nil, nil, err
}
// Write failure. If there is no failure message, write an empty
// byte slice.
var failureBytes bytes.Buffer
if rp.failure != nil {
err := lnwire.EncodeFailureMessage(&failureBytes, rp.failure, 0)
if err != nil {
return nil, nil, err
}
}
err = wire.WriteVarBytes(&b, 0, failureBytes.Bytes())
if err != nil {
return nil, nil, err
}
// Compose key that identifies this result.
key := getResultKeyOld(rp)
return key, b.Bytes(), nil
}
// getResultKeyOld returns a byte slice representing a unique key for this
// payment result.
func getResultKeyOld(rp *paymentResultOld) []byte {
var keyBytes [8 + 8 + 33]byte
// Identify records by a combination of time, payment id and sender pub
// key. This allows importing mission control data from an external
// source without key collisions and keeps the records sorted
// chronologically.
byteOrder.PutUint64(keyBytes[:], uint64(rp.timeReply.UnixNano()))
byteOrder.PutUint64(keyBytes[8:], rp.id)
copy(keyBytes[16:], rp.route.SourcePubKey[:])
return keyBytes[:]
}
// serializeNewResult serializes a payment result and returns a key and value
// byte slice to insert into the bucket.
func serializeNewResult(rp *paymentResultNew) ([]byte, []byte, error) {
recordProducers := []tlv.RecordProducer{
&rp.timeFwd,
&rp.timeReply,
&rp.route,
}
rp.failure.WhenSome(
func(failure tlv.RecordT[tlv.TlvType3, paymentFailure]) {
recordProducers = append(recordProducers, &failure)
},
)
// Compose key that identifies this result.
key := getResultKeyNew(rp)
var buff bytes.Buffer
err := lnwire.EncodeRecordsTo(
&buff, lnwire.ProduceRecordsSorted(recordProducers...),
)
if err != nil {
return nil, nil, err
}
return key, buff.Bytes(), nil
}
// getResultKeyNew returns a byte slice representing a unique key for this
// payment result.
func getResultKeyNew(rp *paymentResultNew) []byte {
var keyBytes [8 + 8 + 33]byte
// Identify records by a combination of time, payment id and sender pub
// key. This allows importing mission control data from an external
// source without key collisions and keeps the records sorted
// chronologically.
byteOrder.PutUint64(keyBytes[:], rp.timeReply.Val)
byteOrder.PutUint64(keyBytes[8:], rp.id)
copy(keyBytes[16:], rp.route.Val.sourcePubKey.Val[:])
return keyBytes[:]
}
package migration32
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/wire"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// MPPOnionType is the type used in the onion to reference the MPP
// fields: total_amt and payment_addr.
MPPOnionType tlv.Type = 8
// AMPOnionType is the type used in the onion to reference the AMP
// fields: root_share, set_id, and child_index.
AMPOnionType tlv.Type = 14
)
// VertexSize is the size of the array to store a vertex.
const VertexSize = 33
// Vertex is a simple alias for the serialization of a compressed Bitcoin
// public key.
type Vertex [VertexSize]byte
// Record returns a TLV record that can be used to encode/decode a Vertex
// to/from a TLV stream.
func (v *Vertex) Record() tlv.Record {
return tlv.MakeStaticRecord(
0, v, VertexSize, encodeVertex, decodeVertex,
)
}
func encodeVertex(w io.Writer, val interface{}, _ *[8]byte) error {
if b, ok := val.(*Vertex); ok {
_, err := w.Write(b[:])
return err
}
return tlv.NewTypeForEncodingErr(val, "Vertex")
}
func decodeVertex(r io.Reader, val interface{}, _ *[8]byte, l uint64) error {
if b, ok := val.(*Vertex); ok {
_, err := io.ReadFull(r, b[:])
return err
}
return tlv.NewTypeForDecodingErr(val, "Vertex", l, VertexSize)
}
// Route represents a path through the channel graph which runs over one or
// more channels in succession. This struct carries all the information
// required to craft the Sphinx onion packet, and send the payment along the
// first hop in the path. A route is only selected as valid if all the channels
// have sufficient capacity to carry the initial payment amount after fees are
// accounted for.
type Route struct {
// TotalTimeLock is the cumulative (final) time lock across the entire
// route. This is the CLTV value that should be extended to the first
// hop in the route. All other hops will decrement the time-lock as
// advertised, leaving enough time for all hops to wait for or present
// the payment preimage to complete the payment.
TotalTimeLock uint32
// TotalAmount is the total amount of funds required to complete a
// payment over this route. This value includes the cumulative fees at
// each hop. As a result, the HTLC extended to the first-hop in the
// route will need to have at least this many satoshis, otherwise the
// route will fail at an intermediate node due to an insufficient
// amount of fees.
TotalAmount lnwire.MilliSatoshi
// SourcePubKey is the pubkey of the node where this route originates
// from.
SourcePubKey Vertex
// Hops contains details concerning the specific forwarding details at
// each hop.
Hops []*Hop
// FirstHopAmount is the amount that should actually be sent to the
// first hop in the route. This is only different from TotalAmount above
// for custom channels where the on-chain amount doesn't necessarily
// reflect all the value of an outgoing payment.
FirstHopAmount tlv.RecordT[
tlv.TlvType0, tlv.BigSizeT[lnwire.MilliSatoshi],
]
// FirstHopWireCustomRecords is a set of custom records that should be
// included in the wire message sent to the first hop. This is only set
// on custom channels and is used to include additional information
// about the actual value of the payment.
//
// NOTE: Since these records already represent TLV records, and we
// enforce them to be in the custom range (e.g. >= 65536), we don't use
// another parent record type here. Instead, when serializing the Route
// we merge the TLV records together with the custom records and encode
// everything as a single TLV stream.
FirstHopWireCustomRecords lnwire.CustomRecords
}
// Hop represents an intermediate or final node of the route. This naming
// is in line with the definition given in BOLT #4: Onion Routing Protocol.
// The struct houses the channel along which this hop can be reached and
// the values necessary to create the HTLC that needs to be sent to the
// next hop. It is also used to encode the per-hop payload included within
// the Sphinx packet.
type Hop struct {
// PubKeyBytes is the raw bytes of the public key of the target node.
PubKeyBytes Vertex
// ChannelID is the unique channel ID for the channel. The first 3
// bytes are the block height, the next 3 the index within the block,
// and the last 2 bytes are the output index for the channel.
ChannelID uint64
// OutgoingTimeLock is the timelock value that should be used when
// crafting the _outgoing_ HTLC from this hop.
OutgoingTimeLock uint32
// AmtToForward is the amount that this hop will forward to the next
// hop. This value is less than the value that the incoming HTLC
// carries as a fee will be subtracted by the hop.
AmtToForward lnwire.MilliSatoshi
// MPP encapsulates the data required for option_mpp. This field should
// only be set for the final hop.
MPP *MPP
// AMP encapsulates the data required for option_amp. This field should
// only be set for the final hop.
AMP *AMP
// CustomRecords if non-nil are a set of additional TLV records that
// should be included in the forwarding instructions for this node.
CustomRecords lnwire.CustomRecords
// LegacyPayload if true, then this signals that this node doesn't
// understand the new TLV payload, so we must instead use the legacy
// payload.
//
// NOTE: we should no longer ever create a Hop with Legacy set to true.
// The only reason we are keeping this member is that it could be the
// case that we have serialised hops persisted to disk where
// LegacyPayload is true.
LegacyPayload bool
// Metadata is additional data that is sent along with the payment to
// the payee.
Metadata []byte
// EncryptedData is an encrypted data blob includes for hops that are
// part of a blinded route.
EncryptedData []byte
// BlindingPoint is an ephemeral public key used by introduction nodes
// in blinded routes to unblind their portion of the route and pass on
// the next ephemeral key to the next blinded node to do the same.
BlindingPoint *btcec.PublicKey
// TotalAmtMsat is the total amount for a blinded payment, potentially
// spread over more than one HTLC. This field should only be set for
// the final hop in a blinded path.
TotalAmtMsat lnwire.MilliSatoshi
}
// MPP is a record that encodes the fields necessary for multi-path payments.
type MPP struct {
// paymentAddr is a random, receiver-generated value used to avoid
// collisions with concurrent payers.
paymentAddr [32]byte
// totalMsat is the total value of the payment, potentially spread
// across more than one HTLC.
totalMsat lnwire.MilliSatoshi
}
// Record returns a tlv.Record that can be used to encode or decode this record.
func (r *MPP) Record() tlv.Record {
// Fixed-size, 32 byte payment address followed by truncated 64-bit
// total msat.
size := func() uint64 {
return 32 + tlv.SizeTUint64(uint64(r.totalMsat))
}
return tlv.MakeDynamicRecord(
MPPOnionType, r, size, MPPEncoder, MPPDecoder,
)
}
const (
// minMPPLength is the minimum length of a serialized MPP TLV record,
// which occurs when the truncated encoding of total_amt_msat takes 0
// bytes, leaving only the payment_addr.
minMPPLength = 32
// maxMPPLength is the maximum length of a serialized MPP TLV record,
// which occurs when the truncated encoding of total_amt_msat takes 8
// bytes.
maxMPPLength = 40
)
// MPPEncoder writes the MPP record to the provided io.Writer.
func MPPEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
if v, ok := val.(*MPP); ok {
err := tlv.EBytes32(w, &v.paymentAddr, buf)
if err != nil {
return err
}
return tlv.ETUint64T(w, uint64(v.totalMsat), buf)
}
return tlv.NewTypeForEncodingErr(val, "MPP")
}
// MPPDecoder reads the MPP record to the provided io.Reader.
func MPPDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
if v, ok := val.(*MPP); ok && minMPPLength <= l && l <= maxMPPLength {
if err := tlv.DBytes32(r, &v.paymentAddr, buf, 32); err != nil {
return err
}
var total uint64
if err := tlv.DTUint64(r, &total, buf, l-32); err != nil {
return err
}
v.totalMsat = lnwire.MilliSatoshi(total)
return nil
}
return tlv.NewTypeForDecodingErr(val, "MPP", l, maxMPPLength)
}
// AMP is a record that encodes the fields necessary for atomic multi-path
// payments.
type AMP struct {
rootShare [32]byte
setID [32]byte
childIndex uint32
}
// AMPEncoder writes the AMP record to the provided io.Writer.
func AMPEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
if v, ok := val.(*AMP); ok {
if err := tlv.EBytes32(w, &v.rootShare, buf); err != nil {
return err
}
if err := tlv.EBytes32(w, &v.setID, buf); err != nil {
return err
}
return tlv.ETUint32T(w, v.childIndex, buf)
}
return tlv.NewTypeForEncodingErr(val, "AMP")
}
const (
// minAMPLength is the minimum length of a serialized AMP TLV record,
// which occurs when the truncated encoding of child_index takes 0
// bytes, leaving only the root_share and set_id.
minAMPLength = 64
// maxAMPLength is the maximum length of a serialized AMP TLV record,
// which occurs when the truncated encoding of a child_index takes 2
// bytes.
maxAMPLength = 68
)
// AMPDecoder reads the AMP record from the provided io.Reader.
func AMPDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
if v, ok := val.(*AMP); ok && minAMPLength <= l && l <= maxAMPLength {
if err := tlv.DBytes32(r, &v.rootShare, buf, 32); err != nil {
return err
}
if err := tlv.DBytes32(r, &v.setID, buf, 32); err != nil {
return err
}
return tlv.DTUint32(r, &v.childIndex, buf, l-minAMPLength)
}
return tlv.NewTypeForDecodingErr(val, "AMP", l, maxAMPLength)
}
// Record returns a tlv.Record that can be used to encode or decode this record.
func (a *AMP) Record() tlv.Record {
return tlv.MakeDynamicRecord(
AMPOnionType, a, a.PayloadSize, AMPEncoder, AMPDecoder,
)
}
// PayloadSize returns the size this record takes up in encoded form.
func (a *AMP) PayloadSize() uint64 {
return 32 + 32 + tlv.SizeTUint32(a.childIndex)
}
// SerializeRoute serializes a route.
func SerializeRoute(w io.Writer, r Route) error {
if err := WriteElements(w,
r.TotalTimeLock, r.TotalAmount, r.SourcePubKey[:],
); err != nil {
return err
}
if err := WriteElements(w, uint32(len(r.Hops))); err != nil {
return err
}
for _, h := range r.Hops {
if err := serializeHop(w, h); err != nil {
return err
}
}
// Any new/extra TLV data is encoded in serializeHTLCAttemptInfo!
return nil
}
func serializeHop(w io.Writer, h *Hop) error {
if err := WriteElements(w,
h.PubKeyBytes[:],
h.ChannelID,
h.OutgoingTimeLock,
h.AmtToForward,
); err != nil {
return err
}
if err := binary.Write(w, byteOrder, h.LegacyPayload); err != nil {
return err
}
// For legacy payloads, we don't need to write any TLV records, so
// we'll write a zero indicating the our serialized TLV map has no
// records.
if h.LegacyPayload {
return WriteElements(w, uint32(0))
}
// Gather all non-primitive TLV records so that they can be serialized
// as a single blob.
//
// TODO(conner): add migration to unify all fields in a single TLV
// blobs. The split approach will cause headaches down the road as more
// fields are added, which we can avoid by having a single TLV stream
// for all payload fields.
var records []tlv.Record
if h.MPP != nil {
records = append(records, h.MPP.Record())
}
// Add blinding point and encrypted data if present.
if h.EncryptedData != nil {
records = append(records, NewEncryptedDataRecord(
&h.EncryptedData,
))
}
if h.BlindingPoint != nil {
records = append(records, NewBlindingPointRecord(
&h.BlindingPoint,
))
}
if h.AMP != nil {
records = append(records, h.AMP.Record())
}
if h.Metadata != nil {
records = append(records, NewMetadataRecord(&h.Metadata))
}
if h.TotalAmtMsat != 0 {
totalMsatInt := uint64(h.TotalAmtMsat)
records = append(
records, NewTotalAmtMsatBlinded(&totalMsatInt),
)
}
// Final sanity check to absolutely rule out custom records that are not
// custom and write into the standard range.
if err := h.CustomRecords.Validate(); err != nil {
return err
}
// Convert custom records to tlv and add to the record list.
// MapToRecords sorts the list, so adding it here will keep the list
// canonical.
tlvRecords := tlv.MapToRecords(h.CustomRecords)
records = append(records, tlvRecords...)
// Otherwise, we'll transform our slice of records into a map of the
// raw bytes, then serialize them in-line with a length (number of
// elements) prefix.
mapRecords, err := tlv.RecordsToMap(records)
if err != nil {
return err
}
numRecords := uint32(len(mapRecords))
if err := WriteElements(w, numRecords); err != nil {
return err
}
for recordType, rawBytes := range mapRecords {
if err := WriteElements(w, recordType); err != nil {
return err
}
if err := wire.WriteVarBytes(w, 0, rawBytes); err != nil {
return err
}
}
return nil
}
// DeserializeRoute deserializes a route.
func DeserializeRoute(r io.Reader) (Route, error) {
rt := Route{}
if err := ReadElements(r,
&rt.TotalTimeLock, &rt.TotalAmount,
); err != nil {
return rt, err
}
var pub []byte
if err := ReadElements(r, &pub); err != nil {
return rt, err
}
copy(rt.SourcePubKey[:], pub)
var numHops uint32
if err := ReadElements(r, &numHops); err != nil {
return rt, err
}
var hops []*Hop
for i := uint32(0); i < numHops; i++ {
hop, err := deserializeHop(r)
if err != nil {
return rt, err
}
hops = append(hops, hop)
}
rt.Hops = hops
// Any new/extra TLV data is decoded in deserializeHTLCAttemptInfo!
return rt, nil
}
// maxOnionPayloadSize is the largest Sphinx payload possible, so we don't need
// to read/write a TLV stream larger than this.
const maxOnionPayloadSize = 1300
func deserializeHop(r io.Reader) (*Hop, error) {
h := &Hop{}
var pub []byte
if err := ReadElements(r, &pub); err != nil {
return nil, err
}
copy(h.PubKeyBytes[:], pub)
if err := ReadElements(r,
&h.ChannelID, &h.OutgoingTimeLock, &h.AmtToForward,
); err != nil {
return nil, err
}
// TODO(roasbeef): change field to allow LegacyPayload false to be the
// legacy default?
err := binary.Read(r, byteOrder, &h.LegacyPayload)
if err != nil {
return nil, err
}
var numElements uint32
if err := ReadElements(r, &numElements); err != nil {
return nil, err
}
// If there're no elements, then we can return early.
if numElements == 0 {
return h, nil
}
tlvMap := make(map[uint64][]byte)
for i := uint32(0); i < numElements; i++ {
var tlvType uint64
if err := ReadElements(r, &tlvType); err != nil {
return nil, err
}
rawRecordBytes, err := wire.ReadVarBytes(
r, 0, maxOnionPayloadSize, "tlv",
)
if err != nil {
return nil, err
}
tlvMap[tlvType] = rawRecordBytes
}
// If the MPP type is present, remove it from the generic TLV map and
// parse it back into a proper MPP struct.
//
// TODO(conner): add migration to unify all fields in a single TLV
// blobs. The split approach will cause headaches down the road as more
// fields are added, which we can avoid by having a single TLV stream
// for all payload fields.
mppType := uint64(MPPOnionType)
if mppBytes, ok := tlvMap[mppType]; ok {
delete(tlvMap, mppType)
var (
mpp = &MPP{}
mppRec = mpp.Record()
r = bytes.NewReader(mppBytes)
)
err := mppRec.Decode(r, uint64(len(mppBytes)))
if err != nil {
return nil, err
}
h.MPP = mpp
}
// If encrypted data or blinding key are present, remove them from
// the TLV map and parse into proper types.
encryptedDataType := uint64(EncryptedDataOnionType)
if data, ok := tlvMap[encryptedDataType]; ok {
delete(tlvMap, encryptedDataType)
h.EncryptedData = data
}
blindingType := uint64(BlindingPointOnionType)
if blindingPoint, ok := tlvMap[blindingType]; ok {
delete(tlvMap, blindingType)
h.BlindingPoint, err = btcec.ParsePubKey(blindingPoint)
if err != nil {
return nil, fmt.Errorf("invalid blinding point: %w",
err)
}
}
ampType := uint64(AMPOnionType)
if ampBytes, ok := tlvMap[ampType]; ok {
delete(tlvMap, ampType)
var (
amp = &{}
ampRec = amp.Record()
r = bytes.NewReader(ampBytes)
)
err := ampRec.Decode(r, uint64(len(ampBytes)))
if err != nil {
return nil, err
}
h.AMP = amp
}
// If the metadata type is present, remove it from the tlv map and
// populate directly on the hop.
metadataType := uint64(MetadataOnionType)
if metadata, ok := tlvMap[metadataType]; ok {
delete(tlvMap, metadataType)
h.Metadata = metadata
}
totalAmtMsatType := uint64(TotalAmtMsatBlindedType)
if totalAmtMsat, ok := tlvMap[totalAmtMsatType]; ok {
delete(tlvMap, totalAmtMsatType)
var (
totalAmtMsatInt uint64
buf [8]byte
)
if err := tlv.DTUint64(
bytes.NewReader(totalAmtMsat),
&totalAmtMsatInt,
&buf,
uint64(len(totalAmtMsat)),
); err != nil {
return nil, err
}
h.TotalAmtMsat = lnwire.MilliSatoshi(totalAmtMsatInt)
}
h.CustomRecords = tlvMap
return h, nil
}
package migration33
import (
"github.com/btcsuite/btclog/v2"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration33
import (
"bytes"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
// resultsKey is the fixed key under which the attempt results are
// stored.
resultsKey = []byte("missioncontrol-results")
// defaultMCNamespaceKey is the key of the default mission control store
// namespace.
defaultMCNamespaceKey = []byte("default")
)
// MigrateMCStoreNameSpacedResults reads in all the current mission control
// entries and re-writes them under a new default namespace.
func MigrateMCStoreNameSpacedResults(tx kvdb.RwTx) error {
log.Infof("Migrating Mission Control store to use namespaced results")
// Get the top level bucket. All the MC results are currently stored
// as KV pairs in this bucket
topLevelBucket := tx.ReadWriteBucket(resultsKey)
// If the results bucket does not exist then there are no entries in
// the mission control store yet and so there is nothing to migrate.
if topLevelBucket == nil {
return nil
}
// Create a new default namespace bucket under the top-level bucket.
defaultNSBkt, err := topLevelBucket.CreateBucket(defaultMCNamespaceKey)
if err != nil {
return err
}
// Iterate through each of the existing result pairs, write them to the
// new namespaced bucket. Also collect the set of keys so that we can
// later delete them from the top level bucket.
var keys [][]byte
err = topLevelBucket.ForEach(func(k, v []byte) error {
// Skip the new default namespace key.
if bytes.Equal(k, defaultMCNamespaceKey) {
return nil
}
// Collect the key.
keys = append(keys, k)
// Write the pair under the default namespace.
return defaultNSBkt.Put(k, v)
})
if err != nil {
return err
}
// Finally, iterate through the set of keys and delete them from the
// top level bucket.
for _, k := range keys {
if err := topLevelBucket.Delete(k); err != nil {
return err
}
}
return err
}
package migration_01_to_11
import (
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"github.com/lightningnetwork/lnd/tor"
)
// addressType specifies the network protocol and version that should be used
// when connecting to a node at a particular address.
type addressType uint8
const (
// tcp4Addr denotes an IPv4 TCP address.
tcp4Addr addressType = 0
// tcp6Addr denotes an IPv6 TCP address.
tcp6Addr addressType = 1
// v2OnionAddr denotes a version 2 Tor onion service address.
v2OnionAddr addressType = 2
// v3OnionAddr denotes a version 3 Tor (prop224) onion service address.
v3OnionAddr addressType = 3
)
// encodeTCPAddr serializes a TCP address into its compact raw bytes
// representation.
func encodeTCPAddr(w io.Writer, addr *net.TCPAddr) error {
var (
addrType byte
ip []byte
)
if addr.IP.To4() != nil {
addrType = byte(tcp4Addr)
ip = addr.IP.To4()
} else {
addrType = byte(tcp6Addr)
ip = addr.IP.To16()
}
if ip == nil {
return fmt.Errorf("unable to encode IP %v", addr.IP)
}
if _, err := w.Write([]byte{addrType}); err != nil {
return err
}
if _, err := w.Write(ip); err != nil {
return err
}
var port [2]byte
byteOrder.PutUint16(port[:], uint16(addr.Port))
if _, err := w.Write(port[:]); err != nil {
return err
}
return nil
}
// encodeOnionAddr serializes an onion address into its compact raw bytes
// representation.
func encodeOnionAddr(w io.Writer, addr *tor.OnionAddr) error {
var suffixIndex int
hostLen := len(addr.OnionService)
switch hostLen {
case tor.V2Len:
if _, err := w.Write([]byte{byte(v2OnionAddr)}); err != nil {
return err
}
suffixIndex = tor.V2Len - tor.OnionSuffixLen
case tor.V3Len:
if _, err := w.Write([]byte{byte(v3OnionAddr)}); err != nil {
return err
}
suffixIndex = tor.V3Len - tor.OnionSuffixLen
default:
return errors.New("unknown onion service length")
}
suffix := addr.OnionService[suffixIndex:]
if suffix != tor.OnionSuffix {
return fmt.Errorf("invalid suffix \"%v\"", suffix)
}
host, err := tor.Base32Encoding.DecodeString(
addr.OnionService[:suffixIndex],
)
if err != nil {
return err
}
// Sanity check the decoded length.
switch {
case hostLen == tor.V2Len && len(host) != tor.V2DecodedLen:
return fmt.Errorf("onion service %v decoded to invalid host %x",
addr.OnionService, host)
case hostLen == tor.V3Len && len(host) != tor.V3DecodedLen:
return fmt.Errorf("onion service %v decoded to invalid host %x",
addr.OnionService, host)
}
if _, err := w.Write(host); err != nil {
return err
}
var port [2]byte
byteOrder.PutUint16(port[:], uint16(addr.Port))
if _, err := w.Write(port[:]); err != nil {
return err
}
return nil
}
// deserializeAddr reads the serialized raw representation of an address and
// deserializes it into the actual address. This allows us to avoid address
// resolution within the channeldb package.
func deserializeAddr(r io.Reader) (net.Addr, error) {
var addrType [1]byte
if _, err := r.Read(addrType[:]); err != nil {
return nil, err
}
var address net.Addr
switch addressType(addrType[0]) {
case tcp4Addr:
var ip [4]byte
if _, err := r.Read(ip[:]); err != nil {
return nil, err
}
var port [2]byte
if _, err := r.Read(port[:]); err != nil {
return nil, err
}
address = &net.TCPAddr{
IP: net.IP(ip[:]),
Port: int(binary.BigEndian.Uint16(port[:])),
}
case tcp6Addr:
var ip [16]byte
if _, err := r.Read(ip[:]); err != nil {
return nil, err
}
var port [2]byte
if _, err := r.Read(port[:]); err != nil {
return nil, err
}
address = &net.TCPAddr{
IP: net.IP(ip[:]),
Port: int(binary.BigEndian.Uint16(port[:])),
}
case v2OnionAddr:
var h [tor.V2DecodedLen]byte
if _, err := r.Read(h[:]); err != nil {
return nil, err
}
var p [2]byte
if _, err := r.Read(p[:]); err != nil {
return nil, err
}
onionService := tor.Base32Encoding.EncodeToString(h[:])
onionService += tor.OnionSuffix
port := int(binary.BigEndian.Uint16(p[:]))
address = &tor.OnionAddr{
OnionService: onionService,
Port: port,
}
case v3OnionAddr:
var h [tor.V3DecodedLen]byte
if _, err := r.Read(h[:]); err != nil {
return nil, err
}
var p [2]byte
if _, err := r.Read(p[:]); err != nil {
return nil, err
}
onionService := tor.Base32Encoding.EncodeToString(h[:])
onionService += tor.OnionSuffix
port := int(binary.BigEndian.Uint16(p[:]))
address = &tor.OnionAddr{
OnionService: onionService,
Port: port,
}
default:
return nil, ErrUnknownAddressType
}
return address, nil
}
// serializeAddr serializes an address into its raw bytes representation so that
// it can be deserialized without requiring address resolution.
func serializeAddr(w io.Writer, address net.Addr) error {
switch addr := address.(type) {
case *net.TCPAddr:
return encodeTCPAddr(w, addr)
case *tor.OnionAddr:
return encodeOnionAddr(w, addr)
default:
return ErrUnknownAddressType
}
}
package migration_01_to_11
import (
"errors"
"fmt"
"io"
"strconv"
"strings"
"sync"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/shachain"
)
var (
// closedChannelBucket stores summarization information concerning
// previously open, but now closed channels.
closedChannelBucket = []byte("closed-chan-bucket")
// openChanBucket stores all the currently open channels. This bucket
// has a second, nested bucket which is keyed by a node's ID. Within
// that node ID bucket, all attributes required to track, update, and
// close a channel are stored.
//
// openChan -> nodeID -> chanPoint
//
// TODO(roasbeef): flesh out comment
openChannelBucket = []byte("open-chan-bucket")
)
// ChannelType is an enum-like type that describes one of several possible
// channel types. Each open channel is associated with a particular type as the
// channel type may determine how higher level operations are conducted such as
// fee negotiation, channel closing, the format of HTLCs, etc.
// TODO(roasbeef): split up per-chain?
type ChannelType uint8
const (
// NOTE: iota isn't used here for this enum needs to be stable
// long-term as it will be persisted to the database.
// SingleFunder represents a channel wherein one party solely funds the
// entire capacity of the channel.
SingleFunder ChannelType = 0
)
// ChannelConstraints represents a set of constraints meant to allow a node to
// limit their exposure, enact flow control and ensure that all HTLCs are
// economically relevant. This struct will be mirrored for both sides of the
// channel, as each side will enforce various constraints that MUST be adhered
// to for the life time of the channel. The parameters for each of these
// constraints are static for the duration of the channel, meaning the channel
// must be torn down for them to change.
type ChannelConstraints struct {
// DustLimit is the threshold (in satoshis) below which any outputs
// should be trimmed. When an output is trimmed, it isn't materialized
// as an actual output, but is instead burned to miner's fees.
DustLimit btcutil.Amount
// ChanReserve is an absolute reservation on the channel for the
// owner of this set of constraints. This means that the current
// settled balance for this node CANNOT dip below the reservation
// amount. This acts as a defense against costless attacks when
// either side no longer has any skin in the game.
ChanReserve btcutil.Amount
// MaxPendingAmount is the maximum pending HTLC value that the
// owner of these constraints can offer the remote node at a
// particular time.
MaxPendingAmount lnwire.MilliSatoshi
// MinHTLC is the minimum HTLC value that the owner of these
// constraints can offer the remote node. If any HTLCs below this
// amount are offered, then the HTLC will be rejected. This, in
// tandem with the dust limit allows a node to regulate the
// smallest HTLC that it deems economically relevant.
MinHTLC lnwire.MilliSatoshi
// MaxAcceptedHtlcs is the maximum number of HTLCs that the owner of
// this set of constraints can offer the remote node. This allows each
// node to limit their over all exposure to HTLCs that may need to be
// acted upon in the case of a unilateral channel closure or a contract
// breach.
MaxAcceptedHtlcs uint16
// CsvDelay is the relative time lock delay expressed in blocks. Any
// settled outputs that pay to the owner of this channel configuration
// MUST ensure that the delay branch uses this value as the relative
// time lock. Similarly, any HTLC's offered by this node should use
// this value as well.
CsvDelay uint16
}
// ChannelConfig is a struct that houses the various configuration opens for
// channels. Each side maintains an instance of this configuration file as it
// governs: how the funding and commitment transaction to be created, the
// nature of HTLC's allotted, the keys to be used for delivery, and relative
// time lock parameters.
type ChannelConfig struct {
// ChannelConstraints is the set of constraints that must be upheld for
// the duration of the channel for the owner of this channel
// configuration. Constraints govern a number of flow control related
// parameters, also including the smallest HTLC that will be accepted
// by a participant.
ChannelConstraints
// MultiSigKey is the key to be used within the 2-of-2 output script
// for the owner of this channel config.
MultiSigKey keychain.KeyDescriptor
// RevocationBasePoint is the base public key to be used when deriving
// revocation keys for the remote node's commitment transaction. This
// will be combined along with a per commitment secret to derive a
// unique revocation key for each state.
RevocationBasePoint keychain.KeyDescriptor
// PaymentBasePoint is the base public key to be used when deriving
// the key used within the non-delayed pay-to-self output on the
// commitment transaction for a node. This will be combined with a
// tweak derived from the per-commitment point to ensure unique keys
// for each commitment transaction.
PaymentBasePoint keychain.KeyDescriptor
// DelayBasePoint is the base public key to be used when deriving the
// key used within the delayed pay-to-self output on the commitment
// transaction for a node. This will be combined with a tweak derived
// from the per-commitment point to ensure unique keys for each
// commitment transaction.
DelayBasePoint keychain.KeyDescriptor
// HtlcBasePoint is the base public key to be used when deriving the
// local HTLC key. The derived key (combined with the tweak derived
// from the per-commitment point) is used within the "to self" clause
// within any HTLC output scripts.
HtlcBasePoint keychain.KeyDescriptor
}
// ChannelCommitment is a snapshot of the commitment state at a particular
// point in the commitment chain. With each state transition, a snapshot of the
// current state along with all non-settled HTLCs are recorded. These snapshots
// detail the state of the _remote_ party's commitment at a particular state
// number. For ourselves (the local node) we ONLY store our most recent
// (unrevoked) state for safety purposes.
type ChannelCommitment struct {
// CommitHeight is the update number that this ChannelDelta represents
// the total number of commitment updates to this point. This can be
// viewed as sort of a "commitment height" as this number is
// monotonically increasing.
CommitHeight uint64
// LocalLogIndex is the cumulative log index index of the local node at
// this point in the commitment chain. This value will be incremented
// for each _update_ added to the local update log.
LocalLogIndex uint64
// LocalHtlcIndex is the current local running HTLC index. This value
// will be incremented for each outgoing HTLC the local node offers.
LocalHtlcIndex uint64
// RemoteLogIndex is the cumulative log index index of the remote node
// at this point in the commitment chain. This value will be
// incremented for each _update_ added to the remote update log.
RemoteLogIndex uint64
// RemoteHtlcIndex is the current remote running HTLC index. This value
// will be incremented for each outgoing HTLC the remote node offers.
RemoteHtlcIndex uint64
// LocalBalance is the current available settled balance within the
// channel directly spendable by us.
LocalBalance lnwire.MilliSatoshi
// RemoteBalance is the current available settled balance within the
// channel directly spendable by the remote node.
RemoteBalance lnwire.MilliSatoshi
// CommitFee is the amount calculated to be paid in fees for the
// current set of commitment transactions. The fee amount is persisted
// with the channel in order to allow the fee amount to be removed and
// recalculated with each channel state update, including updates that
// happen after a system restart.
CommitFee btcutil.Amount
// FeePerKw is the min satoshis/kilo-weight that should be paid within
// the commitment transaction for the entire duration of the channel's
// lifetime. This field may be updated during normal operation of the
// channel as on-chain conditions change.
//
// TODO(halseth): make this SatPerKWeight. Cannot be done atm because
// this will cause the import cycle lnwallet<->channeldb. Fee
// estimation stuff should be in its own package.
FeePerKw btcutil.Amount
// CommitTx is the latest version of the commitment state, broadcast
// able by us.
CommitTx *wire.MsgTx
// CommitSig is one half of the signature required to fully complete
// the script for the commitment transaction above. This is the
// signature signed by the remote party for our version of the
// commitment transactions.
CommitSig []byte
// Htlcs is the set of HTLC's that are pending at this particular
// commitment height.
Htlcs []HTLC
// TODO(roasbeef): pending commit pointer?
// * lets just walk through
}
// ChannelStatus is a bit vector used to indicate whether an OpenChannel is in
// the default usable state, or a state where it shouldn't be used.
type ChannelStatus uint8
var (
// ChanStatusDefault is the normal state of an open channel.
ChanStatusDefault ChannelStatus
// ChanStatusBorked indicates that the channel has entered an
// irreconcilable state, triggered by a state desynchronization or
// channel breach. Channels in this state should never be added to the
// htlc switch.
ChanStatusBorked ChannelStatus = 1
// ChanStatusCommitBroadcasted indicates that a commitment for this
// channel has been broadcasted.
ChanStatusCommitBroadcasted ChannelStatus = 1 << 1
// ChanStatusLocalDataLoss indicates that we have lost channel state
// for this channel, and broadcasting our latest commitment might be
// considered a breach.
//
// TODO(halseh): actually enforce that we are not force closing such a
// channel.
ChanStatusLocalDataLoss ChannelStatus = 1 << 2
// ChanStatusRestored is a status flag that signals that the channel
// has been restored, and doesn't have all the fields a typical channel
// will have.
ChanStatusRestored ChannelStatus = 1 << 3
)
// chanStatusStrings maps a ChannelStatus to a human friendly string that
// describes that status.
var chanStatusStrings = map[ChannelStatus]string{
ChanStatusDefault: "ChanStatusDefault",
ChanStatusBorked: "ChanStatusBorked",
ChanStatusCommitBroadcasted: "ChanStatusCommitBroadcasted",
ChanStatusLocalDataLoss: "ChanStatusLocalDataLoss",
ChanStatusRestored: "ChanStatusRestored",
}
// orderedChanStatusFlags is an in-order list of all that channel status flags.
var orderedChanStatusFlags = []ChannelStatus{
ChanStatusDefault,
ChanStatusBorked,
ChanStatusCommitBroadcasted,
ChanStatusLocalDataLoss,
ChanStatusRestored,
}
// String returns a human-readable representation of the ChannelStatus.
func (c ChannelStatus) String() string {
// If no flags are set, then this is the default case.
if c == 0 {
return chanStatusStrings[ChanStatusDefault]
}
// Add individual bit flags.
statusStr := ""
for _, flag := range orderedChanStatusFlags {
if c&flag == flag {
statusStr += chanStatusStrings[flag] + "|"
c -= flag
}
}
// Remove anything to the right of the final bar, including it as well.
statusStr = strings.TrimRight(statusStr, "|")
// Add any remaining flags which aren't accounted for as hex.
if c != 0 {
statusStr += "|0x" + strconv.FormatUint(uint64(c), 16)
}
// If this was purely an unknown flag, then remove the extra bar at the
// start of the string.
statusStr = strings.TrimLeft(statusStr, "|")
return statusStr
}
// OpenChannel encapsulates the persistent and dynamic state of an open channel
// with a remote node. An open channel supports several options for on-disk
// serialization depending on the exact context. Full (upon channel creation)
// state commitments, and partial (due to a commitment update) writes are
// supported. Each partial write due to a state update appends the new update
// to an on-disk log, which can then subsequently be queried in order to
// "time-travel" to a prior state.
type OpenChannel struct {
// ChanType denotes which type of channel this is.
ChanType ChannelType
// ChainHash is a hash which represents the blockchain that this
// channel will be opened within. This value is typically the genesis
// hash. In the case that the original chain went through a contentious
// hard-fork, then this value will be tweaked using the unique fork
// point on each branch.
ChainHash chainhash.Hash
// FundingOutpoint is the outpoint of the final funding transaction.
// This value uniquely and globally identifies the channel within the
// target blockchain as specified by the chain hash parameter.
FundingOutpoint wire.OutPoint
// ShortChannelID encodes the exact location in the chain in which the
// channel was initially confirmed. This includes: the block height,
// transaction index, and the output within the target transaction.
ShortChannelID lnwire.ShortChannelID
// IsPending indicates whether a channel's funding transaction has been
// confirmed.
IsPending bool
// IsInitiator is a bool which indicates if we were the original
// initiator for the channel. This value may affect how higher levels
// negotiate fees, or close the channel.
IsInitiator bool
// FundingBroadcastHeight is the height in which the funding
// transaction was broadcast. This value can be used by higher level
// sub-systems to determine if a channel is stale and/or should have
// been confirmed before a certain height.
FundingBroadcastHeight uint32
// NumConfsRequired is the number of confirmations a channel's funding
// transaction must have received in order to be considered available
// for normal transactional use.
NumConfsRequired uint16
// ChannelFlags holds the flags that were sent as part of the
// open_channel message.
ChannelFlags lnwire.FundingFlag
// IdentityPub is the identity public key of the remote node this
// channel has been established with.
IdentityPub *btcec.PublicKey
// Capacity is the total capacity of this channel.
Capacity btcutil.Amount
// TotalMSatSent is the total number of milli-satoshis we've sent
// within this channel.
TotalMSatSent lnwire.MilliSatoshi
// TotalMSatReceived is the total number of milli-satoshis we've
// received within this channel.
TotalMSatReceived lnwire.MilliSatoshi
// LocalChanCfg is the channel configuration for the local node.
LocalChanCfg ChannelConfig
// RemoteChanCfg is the channel configuration for the remote node.
RemoteChanCfg ChannelConfig
// LocalCommitment is the current local commitment state for the local
// party. This is stored distinct from the state of the remote party
// as there are certain asymmetric parameters which affect the
// structure of each commitment.
LocalCommitment ChannelCommitment
// RemoteCommitment is the current remote commitment state for the
// remote party. This is stored distinct from the state of the local
// party as there are certain asymmetric parameters which affect the
// structure of each commitment.
RemoteCommitment ChannelCommitment
// RemoteCurrentRevocation is the current revocation for their
// commitment transaction. However, since this the derived public key,
// we don't yet have the private key so we aren't yet able to verify
// that it's actually in the hash chain.
RemoteCurrentRevocation *btcec.PublicKey
// RemoteNextRevocation is the revocation key to be used for the *next*
// commitment transaction we create for the local node. Within the
// specification, this value is referred to as the
// per-commitment-point.
RemoteNextRevocation *btcec.PublicKey
// RevocationProducer is used to generate the revocation in such a way
// that remote side might store it efficiently and have the ability to
// restore the revocation by index if needed. Current implementation of
// secret producer is shachain producer.
RevocationProducer shachain.Producer
// RevocationStore is used to efficiently store the revocations for
// previous channels states sent to us by remote side. Current
// implementation of secret store is shachain store.
RevocationStore shachain.Store
// FundingTxn is the transaction containing this channel's funding
// outpoint. Upon restarts, this txn will be rebroadcast if the channel
// is found to be pending.
//
// NOTE: This value will only be populated for single-funder channels
// for which we are the initiator.
FundingTxn *wire.MsgTx
// TODO(roasbeef): eww
Db *DB
// TODO(roasbeef): just need to store local and remote HTLC's?
sync.RWMutex
}
// ShortChanID returns the current ShortChannelID of this channel.
func (c *OpenChannel) ShortChanID() lnwire.ShortChannelID {
c.RLock()
defer c.RUnlock()
return c.ShortChannelID
}
// HTLC is the on-disk representation of a hash time-locked contract. HTLCs are
// contained within ChannelDeltas which encode the current state of the
// commitment between state updates.
//
// TODO(roasbeef): save space by using smaller ints at tail end?
type HTLC struct {
// Signature is the signature for the second level covenant transaction
// for this HTLC. The second level transaction is a timeout tx in the
// case that this is an outgoing HTLC, and a success tx in the case
// that this is an incoming HTLC.
//
// TODO(roasbeef): make [64]byte instead?
Signature []byte
// RHash is the payment hash of the HTLC.
RHash [32]byte
// Amt is the amount of milli-satoshis this HTLC escrows.
Amt lnwire.MilliSatoshi
// RefundTimeout is the absolute timeout on the HTLC that the sender
// must wait before reclaiming the funds in limbo.
RefundTimeout uint32
// OutputIndex is the output index for this particular HTLC output
// within the commitment transaction.
OutputIndex int32
// Incoming denotes whether we're the receiver or the sender of this
// HTLC.
Incoming bool
// OnionBlob is an opaque blob which is used to complete multi-hop
// routing.
OnionBlob []byte
// HtlcIndex is the HTLC counter index of this active, outstanding
// HTLC. This differs from the LogIndex, as the HtlcIndex is only
// incremented for each offered HTLC, while they LogIndex is
// incremented for each update (includes settle+fail).
HtlcIndex uint64
// LogIndex is the cumulative log index of this HTLC. This differs
// from the HtlcIndex as this will be incremented for each new log
// update added.
LogIndex uint64
}
// CircuitKey is used by a channel to uniquely identify the HTLCs it receives
// from the switch, and is used to purge our in-memory state of HTLCs that have
// already been processed by a link. Two list of CircuitKeys are included in
// each CommitDiff to allow a link to determine which in-memory htlcs directed
// the opening and closing of circuits in the switch's circuit map.
type CircuitKey struct {
// ChanID is the short chanid indicating the HTLC's origin.
//
// NOTE: It is fine for this value to be blank, as this indicates a
// locally-sourced payment.
ChanID lnwire.ShortChannelID
// HtlcID is the unique htlc index predominately assigned by links,
// though can also be assigned by switch in the case of locally-sourced
// payments.
HtlcID uint64
}
// String returns a string representation of the CircuitKey.
func (k CircuitKey) String() string {
return fmt.Sprintf("(Chan ID=%s, HTLC ID=%d)", k.ChanID, k.HtlcID)
}
// ClosureType is an enum like structure that details exactly _how_ a channel
// was closed. Three closure types are currently possible: none, cooperative,
// local force close, remote force close, and (remote) breach.
type ClosureType uint8
const (
// RemoteForceClose indicates that the remote peer has unilaterally
// broadcast their current commitment state on-chain.
RemoteForceClose ClosureType = 4
)
// ChannelCloseSummary contains the final state of a channel at the point it
// was closed. Once a channel is closed, all the information pertaining to that
// channel within the openChannelBucket is deleted, and a compact summary is
// put in place instead.
type ChannelCloseSummary struct {
// ChanPoint is the outpoint for this channel's funding transaction,
// and is used as a unique identifier for the channel.
ChanPoint wire.OutPoint
// ShortChanID encodes the exact location in the chain in which the
// channel was initially confirmed. This includes: the block height,
// transaction index, and the output within the target transaction.
ShortChanID lnwire.ShortChannelID
// ChainHash is the hash of the genesis block that this channel resides
// within.
ChainHash chainhash.Hash
// ClosingTXID is the txid of the transaction which ultimately closed
// this channel.
ClosingTXID chainhash.Hash
// RemotePub is the public key of the remote peer that we formerly had
// a channel with.
RemotePub *btcec.PublicKey
// Capacity was the total capacity of the channel.
Capacity btcutil.Amount
// CloseHeight is the height at which the funding transaction was
// spent.
CloseHeight uint32
// SettledBalance is our total balance settled balance at the time of
// channel closure. This _does not_ include the sum of any outputs that
// have been time-locked as a result of the unilateral channel closure.
SettledBalance btcutil.Amount
// TimeLockedBalance is the sum of all the time-locked outputs at the
// time of channel closure. If we triggered the force closure of this
// channel, then this value will be non-zero if our settled output is
// above the dust limit. If we were on the receiving side of a channel
// force closure, then this value will be non-zero if we had any
// outstanding outgoing HTLC's at the time of channel closure.
TimeLockedBalance btcutil.Amount
// CloseType details exactly _how_ the channel was closed. Five closure
// types are possible: cooperative, local force, remote force, breach
// and funding canceled.
CloseType ClosureType
// IsPending indicates whether this channel is in the 'pending close'
// state, which means the channel closing transaction has been
// confirmed, but not yet been fully resolved. In the case of a channel
// that has been cooperatively closed, it will go straight into the
// fully resolved state as soon as the closing transaction has been
// confirmed. However, for channels that have been force closed, they'll
// stay marked as "pending" until _all_ the pending funds have been
// swept.
IsPending bool
// RemoteCurrentRevocation is the current revocation for their
// commitment transaction. However, since this is the derived public key,
// we don't yet have the private key so we aren't yet able to verify
// that it's actually in the hash chain.
RemoteCurrentRevocation *btcec.PublicKey
// RemoteNextRevocation is the revocation key to be used for the *next*
// commitment transaction we create for the local node. Within the
// specification, this value is referred to as the
// per-commitment-point.
RemoteNextRevocation *btcec.PublicKey
// LocalChanConfig is the channel configuration for the local node.
LocalChanConfig ChannelConfig
// LastChanSyncMsg is the ChannelReestablish message for this channel
// for the state at the point where it was closed.
LastChanSyncMsg *lnwire.ChannelReestablish
}
func serializeChannelCloseSummary(w io.Writer, cs *ChannelCloseSummary) error {
err := WriteElements(w,
cs.ChanPoint, cs.ShortChanID, cs.ChainHash, cs.ClosingTXID,
cs.CloseHeight, cs.RemotePub, cs.Capacity, cs.SettledBalance,
cs.TimeLockedBalance, cs.CloseType, cs.IsPending,
)
if err != nil {
return err
}
// If this is a close channel summary created before the addition of
// the new fields, then we can exit here.
if cs.RemoteCurrentRevocation == nil {
return WriteElements(w, false)
}
// If fields are present, write boolean to indicate this, and continue.
if err := WriteElements(w, true); err != nil {
return err
}
if err := WriteElements(w, cs.RemoteCurrentRevocation); err != nil {
return err
}
if err := WriteChanConfig(w, &cs.LocalChanConfig); err != nil {
return err
}
// The RemoteNextRevocation field is optional, as it's possible for a
// channel to be closed before we learn of the next unrevoked
// revocation point for the remote party. Write a boolean indicating
// whether this field is present or not.
if err := WriteElements(w, cs.RemoteNextRevocation != nil); err != nil {
return err
}
// Write the field, if present.
if cs.RemoteNextRevocation != nil {
if err = WriteElements(w, cs.RemoteNextRevocation); err != nil {
return err
}
}
// Write whether the channel sync message is present.
if err := WriteElements(w, cs.LastChanSyncMsg != nil); err != nil {
return err
}
// Write the channel sync message, if present.
if cs.LastChanSyncMsg != nil {
if err := WriteElements(w, cs.LastChanSyncMsg); err != nil {
return err
}
}
return nil
}
func deserializeCloseChannelSummary(r io.Reader) (*ChannelCloseSummary, error) {
c := &ChannelCloseSummary{}
err := ReadElements(r,
&c.ChanPoint, &c.ShortChanID, &c.ChainHash, &c.ClosingTXID,
&c.CloseHeight, &c.RemotePub, &c.Capacity, &c.SettledBalance,
&c.TimeLockedBalance, &c.CloseType, &c.IsPending,
)
if err != nil {
return nil, err
}
// We'll now check to see if the channel close summary was encoded with
// any of the additional optional fields.
var hasNewFields bool
err = ReadElements(r, &hasNewFields)
if err != nil {
return nil, err
}
// If fields are not present, we can return.
if !hasNewFields {
return c, nil
}
// Otherwise read the new fields.
if err := ReadElements(r, &c.RemoteCurrentRevocation); err != nil {
return nil, err
}
if err := ReadChanConfig(r, &c.LocalChanConfig); err != nil {
return nil, err
}
// Finally, we'll attempt to read the next unrevoked commitment point
// for the remote party. If we closed the channel before receiving a
// funding locked message then this might not be present. A boolean
// indicating whether the field is present will come first.
var hasRemoteNextRevocation bool
err = ReadElements(r, &hasRemoteNextRevocation)
if err != nil {
return nil, err
}
// If this field was written, read it.
if hasRemoteNextRevocation {
err = ReadElements(r, &c.RemoteNextRevocation)
if err != nil {
return nil, err
}
}
// Check if we have a channel sync message to read.
var hasChanSyncMsg bool
err = ReadElements(r, &hasChanSyncMsg)
if err == io.EOF {
return c, nil
} else if err != nil {
return nil, err
}
// If a chan sync message is present, read it.
if hasChanSyncMsg {
// We must pass in reference to a lnwire.Message for the codec
// to support it.
var msg lnwire.Message
if err := ReadElements(r, &msg); err != nil {
return nil, err
}
chanSync, ok := msg.(*lnwire.ChannelReestablish)
if !ok {
return nil, errors.New("unable cast db Message to " +
"ChannelReestablish")
}
c.LastChanSyncMsg = chanSync
}
return c, nil
}
func WriteChanConfig(b io.Writer, c *ChannelConfig) error {
return WriteElements(b,
c.DustLimit, c.MaxPendingAmount, c.ChanReserve, c.MinHTLC,
c.MaxAcceptedHtlcs, c.CsvDelay, c.MultiSigKey,
c.RevocationBasePoint, c.PaymentBasePoint, c.DelayBasePoint,
c.HtlcBasePoint,
)
}
func ReadChanConfig(b io.Reader, c *ChannelConfig) error {
return ReadElements(b,
&c.DustLimit, &c.MaxPendingAmount, &c.ChanReserve,
&c.MinHTLC, &c.MaxAcceptedHtlcs, &c.CsvDelay,
&c.MultiSigKey, &c.RevocationBasePoint,
&c.PaymentBasePoint, &c.DelayBasePoint,
&c.HtlcBasePoint,
)
}
func DeserializeChanCommit(r io.Reader) (ChannelCommitment, error) {
var c ChannelCommitment
err := ReadElements(r,
&c.CommitHeight, &c.LocalLogIndex, &c.LocalHtlcIndex, &c.RemoteLogIndex,
&c.RemoteHtlcIndex, &c.LocalBalance, &c.RemoteBalance,
&c.CommitFee, &c.FeePerKw, &c.CommitTx, &c.CommitSig,
)
if err != nil {
return c, err
}
c.Htlcs, err = DeserializeHtlcs(r)
if err != nil {
return c, err
}
return c, nil
}
// DeserializeHtlcs attempts to read out a slice of HTLC's from the passed
// io.Reader. The bytes within the passed reader MUST have been previously
// written to using the SerializeHtlcs function.
//
// NOTE: This API is NOT stable, the on-disk format will likely change in the
// future.
func DeserializeHtlcs(r io.Reader) ([]HTLC, error) {
var numHtlcs uint16
if err := ReadElement(r, &numHtlcs); err != nil {
return nil, err
}
var htlcs []HTLC
if numHtlcs == 0 {
return htlcs, nil
}
htlcs = make([]HTLC, numHtlcs)
for i := uint16(0); i < numHtlcs; i++ {
if err := ReadElements(r,
&htlcs[i].Signature, &htlcs[i].RHash, &htlcs[i].Amt,
&htlcs[i].RefundTimeout, &htlcs[i].OutputIndex,
&htlcs[i].Incoming, &htlcs[i].OnionBlob,
&htlcs[i].HtlcIndex, &htlcs[i].LogIndex,
); err != nil {
return htlcs, err
}
}
return htlcs, nil
}
func SerializeChanCommit(w io.Writer, c *ChannelCommitment) error {
if err := WriteElements(w,
c.CommitHeight, c.LocalLogIndex, c.LocalHtlcIndex,
c.RemoteLogIndex, c.RemoteHtlcIndex, c.LocalBalance,
c.RemoteBalance, c.CommitFee, c.FeePerKw, c.CommitTx,
c.CommitSig,
); err != nil {
return err
}
return SerializeHtlcs(w, c.Htlcs...)
}
// SerializeHtlcs writes out the passed set of HTLC's into the passed writer
// using the current default on-disk serialization format.
//
// NOTE: This API is NOT stable, the on-disk format will likely change in the
// future.
func SerializeHtlcs(b io.Writer, htlcs ...HTLC) error {
numHtlcs := uint16(len(htlcs))
if err := WriteElement(b, numHtlcs); err != nil {
return err
}
for _, htlc := range htlcs {
if err := WriteElements(b,
htlc.Signature, htlc.RHash, htlc.Amt, htlc.RefundTimeout,
htlc.OutputIndex, htlc.Incoming, htlc.OnionBlob[:],
htlc.HtlcIndex, htlc.LogIndex,
); err != nil {
return err
}
}
return nil
}
package migration_01_to_11
import (
"encoding/binary"
"fmt"
"io"
"net"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/shachain"
)
// WriteOutpoint writes an outpoint to the passed writer using the minimal
// amount of bytes possible.
func WriteOutpoint(w io.Writer, o *wire.OutPoint) error {
if _, err := w.Write(o.Hash[:]); err != nil {
return err
}
if err := binary.Write(w, byteOrder, o.Index); err != nil {
return err
}
return nil
}
// ReadOutpoint reads an outpoint from the passed reader that was previously
// written using the writeOutpoint struct.
func ReadOutpoint(r io.Reader, o *wire.OutPoint) error {
if _, err := io.ReadFull(r, o.Hash[:]); err != nil {
return err
}
if err := binary.Read(r, byteOrder, &o.Index); err != nil {
return err
}
return nil
}
// UnknownElementType is an error returned when the codec is unable to encode or
// decode a particular type.
type UnknownElementType struct {
method string
element interface{}
}
// Error returns the name of the method that encountered the error, as well as
// the type that was unsupported.
func (e UnknownElementType) Error() string {
return fmt.Sprintf("Unknown type in %s: %T", e.method, e.element)
}
// WriteElement is a one-stop shop to write the big endian representation of
// any element which is to be serialized for storage on disk. The passed
// io.Writer should be backed by an appropriately sized byte slice, or be able
// to dynamically expand to accommodate additional data.
func WriteElement(w io.Writer, element interface{}) error {
switch e := element.(type) {
case keychain.KeyDescriptor:
if err := binary.Write(w, byteOrder, e.Family); err != nil {
return err
}
if err := binary.Write(w, byteOrder, e.Index); err != nil {
return err
}
if e.PubKey != nil {
if err := binary.Write(w, byteOrder, true); err != nil {
return fmt.Errorf("error writing serialized element: %w", err)
}
return WriteElement(w, e.PubKey)
}
return binary.Write(w, byteOrder, false)
case ChannelType:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case chainhash.Hash:
if _, err := w.Write(e[:]); err != nil {
return err
}
case wire.OutPoint:
return WriteOutpoint(w, &e)
case lnwire.ShortChannelID:
if err := binary.Write(w, byteOrder, e.ToUint64()); err != nil {
return err
}
case lnwire.ChannelID:
if _, err := w.Write(e[:]); err != nil {
return err
}
case int64, uint64:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case uint32:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case int32:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case uint16:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case uint8:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case bool:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case btcutil.Amount:
if err := binary.Write(w, byteOrder, uint64(e)); err != nil {
return err
}
case lnwire.MilliSatoshi:
if err := binary.Write(w, byteOrder, uint64(e)); err != nil {
return err
}
case *btcec.PrivateKey:
b := e.Serialize()
if _, err := w.Write(b); err != nil {
return err
}
case *btcec.PublicKey:
b := e.SerializeCompressed()
if _, err := w.Write(b); err != nil {
return err
}
case shachain.Producer:
return e.Encode(w)
case shachain.Store:
return e.Encode(w)
case *wire.MsgTx:
return e.Serialize(w)
case [32]byte:
if _, err := w.Write(e[:]); err != nil {
return err
}
case []byte:
if err := wire.WriteVarBytes(w, 0, e); err != nil {
return err
}
case lnwire.Message:
if _, err := lnwire.WriteMessage(w, e, 0); err != nil {
return err
}
case ChannelStatus:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case ClosureType:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case lnwire.FundingFlag:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case net.Addr:
if err := serializeAddr(w, e); err != nil {
return err
}
case []net.Addr:
if err := WriteElement(w, uint32(len(e))); err != nil {
return err
}
for _, addr := range e {
if err := serializeAddr(w, addr); err != nil {
return err
}
}
default:
return UnknownElementType{"WriteElement", e}
}
return nil
}
// WriteElements is writes each element in the elements slice to the passed
// io.Writer using WriteElement.
func WriteElements(w io.Writer, elements ...interface{}) error {
for _, element := range elements {
err := WriteElement(w, element)
if err != nil {
return err
}
}
return nil
}
// ReadElement is a one-stop utility function to deserialize any datastructure
// encoded using the serialization format of the database.
func ReadElement(r io.Reader, element interface{}) error {
switch e := element.(type) {
case *keychain.KeyDescriptor:
if err := binary.Read(r, byteOrder, &e.Family); err != nil {
return err
}
if err := binary.Read(r, byteOrder, &e.Index); err != nil {
return err
}
var hasPubKey bool
if err := binary.Read(r, byteOrder, &hasPubKey); err != nil {
return err
}
if hasPubKey {
return ReadElement(r, &e.PubKey)
}
case *ChannelType:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *chainhash.Hash:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *wire.OutPoint:
return ReadOutpoint(r, e)
case *lnwire.ShortChannelID:
var a uint64
if err := binary.Read(r, byteOrder, &a); err != nil {
return err
}
*e = lnwire.NewShortChanIDFromInt(a)
case *lnwire.ChannelID:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *int64, *uint64:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *uint32:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *int32:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *uint16:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *uint8:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *bool:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *btcutil.Amount:
var a uint64
if err := binary.Read(r, byteOrder, &a); err != nil {
return err
}
*e = btcutil.Amount(a)
case *lnwire.MilliSatoshi:
var a uint64
if err := binary.Read(r, byteOrder, &a); err != nil {
return err
}
*e = lnwire.MilliSatoshi(a)
case **btcec.PrivateKey:
var b [btcec.PrivKeyBytesLen]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
priv, _ := btcec.PrivKeyFromBytes(b[:])
*e = priv
case **btcec.PublicKey:
var b [btcec.PubKeyBytesLenCompressed]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
pubKey, err := btcec.ParsePubKey(b[:])
if err != nil {
return err
}
*e = pubKey
case *shachain.Producer:
var root [32]byte
if _, err := io.ReadFull(r, root[:]); err != nil {
return err
}
// TODO(roasbeef): remove
producer, err := shachain.NewRevocationProducerFromBytes(root[:])
if err != nil {
return err
}
*e = producer
case *shachain.Store:
store, err := shachain.NewRevocationStoreFromBytes(r)
if err != nil {
return err
}
*e = store
case **wire.MsgTx:
tx := wire.NewMsgTx(2)
if err := tx.Deserialize(r); err != nil {
return err
}
*e = tx
case *[32]byte:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *[]byte:
bytes, err := wire.ReadVarBytes(r, 0, 66000, "[]byte")
if err != nil {
return err
}
*e = bytes
case *lnwire.Message:
msg, err := lnwire.ReadMessage(r, 0)
if err != nil {
return err
}
*e = msg
case *ChannelStatus:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *ClosureType:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *lnwire.FundingFlag:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *net.Addr:
addr, err := deserializeAddr(r)
if err != nil {
return err
}
*e = addr
case *[]net.Addr:
var numAddrs uint32
if err := ReadElement(r, &numAddrs); err != nil {
return err
}
*e = make([]net.Addr, numAddrs)
for i := uint32(0); i < numAddrs; i++ {
addr, err := deserializeAddr(r)
if err != nil {
return err
}
(*e)[i] = addr
}
default:
return UnknownElementType{"ReadElement", e}
}
return nil
}
// ReadElements deserializes a variable number of elements into the passed
// io.Reader, with each element being deserialized according to the ReadElement
// function.
func ReadElements(r io.Reader, elements ...interface{}) error {
for _, element := range elements {
err := ReadElement(r, element)
if err != nil {
return err
}
}
return nil
}
package migration_01_to_11
import (
"bytes"
"encoding/binary"
"fmt"
"os"
"path/filepath"
"time"
"github.com/lightningnetwork/lnd/kvdb"
)
const (
dbName = "channel.db"
dbFilePermission = 0600
)
// migration is a function which takes a prior outdated version of the database
// instances and mutates the key/bucket structure to arrive at a more
// up-to-date version of the database.
type migration func(tx kvdb.RwTx) error
var (
// Big endian is the preferred byte order, due to cursor scans over
// integer keys iterating in order.
byteOrder = binary.BigEndian
)
// DB is the primary datastore for the lnd daemon. The database stores
// information related to nodes, routing data, open/closed channels, fee
// schedules, and reputation data.
type DB struct {
kvdb.Backend
dbPath string
graph *ChannelGraph
now func() time.Time
}
// Open opens an existing channeldb. Any necessary schemas migrations due to
// updates will take place as necessary.
func Open(dbPath string, modifiers ...OptionModifier) (*DB, error) {
path := filepath.Join(dbPath, dbName)
if !fileExists(path) {
if err := createChannelDB(dbPath); err != nil {
return nil, err
}
}
opts := DefaultOptions()
for _, modifier := range modifiers {
modifier(&opts)
}
// Specify bbolt freelist options to reduce heap pressure in case the
// freelist grows to be very large.
bdb, err := kvdb.Open(
kvdb.BoltBackendName, path,
opts.NoFreelistSync, opts.DBTimeout, false,
)
if err != nil {
return nil, err
}
chanDB := &DB{
Backend: bdb,
dbPath: dbPath,
now: time.Now,
}
chanDB.graph = newChannelGraph(
chanDB, opts.RejectCacheSize, opts.ChannelCacheSize,
)
return chanDB, nil
}
// createChannelDB creates and initializes a fresh version of channeldb. In
// the case that the target path has not yet been created or doesn't yet exist,
// then the path is created. Additionally, all required top-level buckets used
// within the database are created.
func createChannelDB(dbPath string) error {
if !fileExists(dbPath) {
if err := os.MkdirAll(dbPath, 0700); err != nil {
return err
}
}
path := filepath.Join(dbPath, dbName)
bdb, err := kvdb.Create(
kvdb.BoltBackendName, path, false, kvdb.DefaultDBTimeout,
false,
)
if err != nil {
return err
}
err = kvdb.Update(bdb, func(tx kvdb.RwTx) error {
if _, err := tx.CreateTopLevelBucket(openChannelBucket); err != nil {
return err
}
if _, err := tx.CreateTopLevelBucket(closedChannelBucket); err != nil {
return err
}
if _, err := tx.CreateTopLevelBucket(invoiceBucket); err != nil {
return err
}
if _, err := tx.CreateTopLevelBucket(paymentBucket); err != nil {
return err
}
nodes, err := tx.CreateTopLevelBucket(nodeBucket)
if err != nil {
return err
}
_, err = nodes.CreateBucket(aliasIndexBucket)
if err != nil {
return err
}
_, err = nodes.CreateBucket(nodeUpdateIndexBucket)
if err != nil {
return err
}
edges, err := tx.CreateTopLevelBucket(edgeBucket)
if err != nil {
return err
}
if _, err := edges.CreateBucket(edgeIndexBucket); err != nil {
return err
}
if _, err := edges.CreateBucket(edgeUpdateIndexBucket); err != nil {
return err
}
if _, err := edges.CreateBucket(channelPointBucket); err != nil {
return err
}
if _, err := edges.CreateBucket(zombieBucket); err != nil {
return err
}
graphMeta, err := tx.CreateTopLevelBucket(graphMetaBucket)
if err != nil {
return err
}
_, err = graphMeta.CreateBucket(pruneLogBucket)
if err != nil {
return err
}
if _, err := tx.CreateTopLevelBucket(metaBucket); err != nil {
return err
}
meta := &Meta{
DbVersionNumber: 0,
}
return putMeta(meta, tx)
}, func() {})
if err != nil {
return fmt.Errorf("unable to create new channeldb")
}
return bdb.Close()
}
// fileExists returns true if the file exists, and false otherwise.
func fileExists(path string) bool {
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
// FetchClosedChannels attempts to fetch all closed channels from the database.
// The pendingOnly bool toggles if channels that aren't yet fully closed should
// be returned in the response or not. When a channel was cooperatively closed,
// it becomes fully closed after a single confirmation. When a channel was
// forcibly closed, it will become fully closed after _all_ the pending funds
// (if any) have been swept.
func (d *DB) FetchClosedChannels(pendingOnly bool) ([]*ChannelCloseSummary, error) {
var chanSummaries []*ChannelCloseSummary
if err := kvdb.View(d, func(tx kvdb.RTx) error {
closeBucket := tx.ReadBucket(closedChannelBucket)
if closeBucket == nil {
return ErrNoClosedChannels
}
return closeBucket.ForEach(func(chanID []byte, summaryBytes []byte) error {
summaryReader := bytes.NewReader(summaryBytes)
chanSummary, err := deserializeCloseChannelSummary(summaryReader)
if err != nil {
return err
}
// If the query specified to only include pending
// channels, then we'll skip any channels which aren't
// currently pending.
if !chanSummary.IsPending && pendingOnly {
return nil
}
chanSummaries = append(chanSummaries, chanSummary)
return nil
})
}, func() {
chanSummaries = nil
}); err != nil {
return nil, err
}
return chanSummaries, nil
}
// ChannelGraph returns a new instance of the directed channel graph.
func (d *DB) ChannelGraph() *ChannelGraph {
return d.graph
}
package migration_01_to_11
import (
"fmt"
)
var (
// ErrNoInvoicesCreated is returned when we don't have invoices in
// our database to return.
ErrNoInvoicesCreated = fmt.Errorf("there are no existing invoices")
// ErrNoPaymentsCreated is returned when bucket of payments hasn't been
// created.
ErrNoPaymentsCreated = fmt.Errorf("there are no existing payments")
// ErrGraphNotFound is returned when at least one of the components of
// graph doesn't exist.
ErrGraphNotFound = fmt.Errorf("graph bucket not initialized")
// ErrSourceNodeNotSet is returned if the source node of the graph
// hasn't been added The source node is the center node within a
// star-graph.
ErrSourceNodeNotSet = fmt.Errorf("source node does not exist")
// ErrGraphNodeNotFound is returned when we're unable to find the target
// node.
ErrGraphNodeNotFound = fmt.Errorf("unable to find node")
// ErrEdgeNotFound is returned when an edge for the target chanID
// can't be found.
ErrEdgeNotFound = fmt.Errorf("edge not found")
// ErrUnknownAddressType is returned when a node's addressType is not
// an expected value.
ErrUnknownAddressType = fmt.Errorf("address type cannot be resolved")
// ErrNoClosedChannels is returned when a node is queries for all the
// channels it has closed, but it hasn't yet closed any channels.
ErrNoClosedChannels = fmt.Errorf("no channel have been closed yet")
// ErrEdgePolicyOptionalFieldNotFound is an error returned if a channel
// policy field is not found in the db even though its message flags
// indicate it should be.
ErrEdgePolicyOptionalFieldNotFound = fmt.Errorf("optional field not " +
"present")
)
// ErrTooManyExtraOpaqueBytes creates an error which should be returned if the
// caller attempts to write an announcement message which bares too many extra
// opaque bytes. We limit this value in order to ensure that we don't waste
// disk space due to nodes unnecessarily padding out their announcements with
// garbage data.
func ErrTooManyExtraOpaqueBytes(numBytes int) error {
return fmt.Errorf("max allowed number of opaque bytes is %v, received "+
"%v bytes", MaxAllowedExtraOpaqueBytes, numBytes)
}
package migration_01_to_11
import (
"bytes"
"encoding/binary"
"fmt"
"image/color"
"io"
"net"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
// nodeBucket is a bucket which houses all the vertices or nodes within
// the channel graph. This bucket has a single-sub bucket which adds an
// additional index from pubkey -> alias. Within the top-level of this
// bucket, the key space maps a node's compressed public key to the
// serialized information for that node. Additionally, there's a
// special key "source" which stores the pubkey of the source node. The
// source node is used as the starting point for all graph/queries and
// traversals. The graph is formed as a star-graph with the source node
// at the center.
//
// maps: pubKey -> nodeInfo
// maps: source -> selfPubKey
nodeBucket = []byte("graph-node")
// nodeUpdateIndexBucket is a sub-bucket of the nodeBucket. This bucket
// will be used to quickly look up the "freshness" of a node's last
// update to the network. The bucket only contains keys, and no values,
// it's mapping:
//
// maps: updateTime || nodeID -> nil
nodeUpdateIndexBucket = []byte("graph-node-update-index")
// sourceKey is a special key that resides within the nodeBucket. The
// sourceKey maps a key to the public key of the "self node".
sourceKey = []byte("source")
// aliasIndexBucket is a sub-bucket that's nested within the main
// nodeBucket. This bucket maps the public key of a node to its
// current alias. This bucket is provided as it can be used within a
// future UI layer to add an additional degree of confirmation.
aliasIndexBucket = []byte("alias")
// edgeBucket is a bucket which houses all of the edge or channel
// information within the channel graph. This bucket essentially acts
// as an adjacency list, which in conjunction with a range scan, can be
// used to iterate over all the incoming and outgoing edges for a
// particular node. Key in the bucket use a prefix scheme which leads
// with the node's public key and sends with the compact edge ID.
// For each chanID, there will be two entries within the bucket, as the
// graph is directed: nodes may have different policies w.r.t to fees
// for their respective directions.
//
// maps: pubKey || chanID -> channel edge policy for node
edgeBucket = []byte("graph-edge")
// unknownPolicy is represented as an empty slice. It is
// used as the value in edgeBucket for unknown channel edge policies.
// Unknown policies are still stored in the database to enable efficient
// lookup of incoming channel edges.
unknownPolicy = []byte{}
// edgeIndexBucket is an index which can be used to iterate all edges
// in the bucket, grouping them according to their in/out nodes.
// Additionally, the items in this bucket also contain the complete
// edge information for a channel. The edge information includes the
// capacity of the channel, the nodes that made the channel, etc. This
// bucket resides within the edgeBucket above. Creation of an edge
// proceeds in two phases: first the edge is added to the edge index,
// afterwards the edgeBucket can be updated with the latest details of
// the edge as they are announced on the network.
//
// maps: chanID -> pubKey1 || pubKey2 || restofEdgeInfo
edgeIndexBucket = []byte("edge-index")
// edgeUpdateIndexBucket is a sub-bucket of the main edgeBucket. This
// bucket contains an index which allows us to gauge the "freshness" of
// a channel's last updates.
//
// maps: updateTime || chanID -> nil
edgeUpdateIndexBucket = []byte("edge-update-index")
// channelPointBucket maps a channel's full outpoint (txid:index) to
// its short 8-byte channel ID. This bucket resides within the
// edgeBucket above, and can be used to quickly remove an edge due to
// the outpoint being spent, or to query for existence of a channel.
//
// maps: outPoint -> chanID
channelPointBucket = []byte("chan-index")
// zombieBucket is a sub-bucket of the main edgeBucket bucket
// responsible for maintaining an index of zombie channels. Each entry
// exists within the bucket as follows:
//
// maps: chanID -> pubKey1 || pubKey2
//
// The chanID represents the channel ID of the edge that is marked as a
// zombie and is used as the key, which maps to the public keys of the
// edge's participants.
zombieBucket = []byte("zombie-index")
// disabledEdgePolicyBucket is a sub-bucket of the main edgeBucket bucket
// responsible for maintaining an index of disabled edge policies. Each
// entry exists within the bucket as follows:
//
// maps: <chanID><direction> -> []byte{}
//
// The chanID represents the channel ID of the edge and the direction is
// one byte representing the direction of the edge. The main purpose of
// this index is to allow pruning disabled channels in a fast way without
// the need to iterate all over the graph.
disabledEdgePolicyBucket = []byte("disabled-edge-policy-index")
// graphMetaBucket is a top-level bucket which stores various meta-deta
// related to the on-disk channel graph. Data stored in this bucket
// includes the block to which the graph has been synced to, the total
// number of channels, etc.
graphMetaBucket = []byte("graph-meta")
// pruneLogBucket is a bucket within the graphMetaBucket that stores
// a mapping from the block height to the hash for the blocks used to
// prune the graph.
// Once a new block is discovered, any channels that have been closed
// (by spending the outpoint) can safely be removed from the graph, and
// the block is added to the prune log. We need to keep such a log for
// the case where a reorg happens, and we must "rewind" the state of the
// graph by removing channels that were previously confirmed. In such a
// case we'll remove all entries from the prune log with a block height
// that no longer exists.
pruneLogBucket = []byte("prune-log")
)
const (
// MaxAllowedExtraOpaqueBytes is the largest amount of opaque bytes that
// we'll permit to be written to disk. We limit this as otherwise, it
// would be possible for a node to create a ton of updates and slowly
// fill our disk, and also waste bandwidth due to relaying.
MaxAllowedExtraOpaqueBytes = 10000
)
// ChannelGraph is a persistent, on-disk graph representation of the Lightning
// Network. This struct can be used to implement path finding algorithms on top
// of, and also to update a node's view based on information received from the
// p2p network. Internally, the graph is stored using a modified adjacency list
// representation with some added object interaction possible with each
// serialized edge/node. The graph is stored is directed, meaning that are two
// edges stored for each channel: an inbound/outbound edge for each node pair.
// Nodes, edges, and edge information can all be added to the graph
// independently. Edge removal results in the deletion of all edge information
// for that edge.
type ChannelGraph struct {
db *DB
}
// newChannelGraph allocates a new ChannelGraph backed by a DB instance. The
// returned instance has its own unique reject cache and channel cache.
func newChannelGraph(db *DB, rejectCacheSize, chanCacheSize int) *ChannelGraph {
return &ChannelGraph{
db: db,
}
}
// SourceNode returns the source node of the graph. The source node is treated
// as the center node within a star-graph. This method may be used to kick off
// a path finding algorithm in order to explore the reachability of another
// node based off the source node.
func (c *ChannelGraph) SourceNode() (*LightningNode, error) {
var source *LightningNode
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
// First grab the nodes bucket which stores the mapping from
// pubKey to node information.
nodes := tx.ReadBucket(nodeBucket)
if nodes == nil {
return ErrGraphNotFound
}
node, err := c.sourceNode(nodes)
if err != nil {
return err
}
source = node
return nil
}, func() {
source = nil
})
if err != nil {
return nil, err
}
return source, nil
}
// sourceNode uses an existing database transaction and returns the source node
// of the graph. The source node is treated as the center node within a
// star-graph. This method may be used to kick off a path finding algorithm in
// order to explore the reachability of another node based off the source node.
func (c *ChannelGraph) sourceNode(nodes kvdb.RBucket) (*LightningNode, error) {
selfPub := nodes.Get(sourceKey)
if selfPub == nil {
return nil, ErrSourceNodeNotSet
}
// With the pubKey of the source node retrieved, we're able to
// fetch the full node information.
node, err := fetchLightningNode(nodes, selfPub)
if err != nil {
return nil, err
}
node.db = c.db
return &node, nil
}
// SetSourceNode sets the source node within the graph database. The source
// node is to be used as the center of a star-graph within path finding
// algorithms.
func (c *ChannelGraph) SetSourceNode(node *LightningNode) error {
nodePubBytes := node.PubKeyBytes[:]
return kvdb.Update(c.db, func(tx kvdb.RwTx) error {
// First grab the nodes bucket which stores the mapping from
// pubKey to node information.
nodes, err := tx.CreateTopLevelBucket(nodeBucket)
if err != nil {
return err
}
// Next we create the mapping from source to the targeted
// public key.
if err := nodes.Put(sourceKey, nodePubBytes); err != nil {
return err
}
// Finally, we commit the information of the lightning node
// itself.
return addLightningNode(tx, node)
}, func() {})
}
func addLightningNode(tx kvdb.RwTx, node *LightningNode) error {
nodes, err := tx.CreateTopLevelBucket(nodeBucket)
if err != nil {
return err
}
aliases, err := nodes.CreateBucketIfNotExists(aliasIndexBucket)
if err != nil {
return err
}
updateIndex, err := nodes.CreateBucketIfNotExists(
nodeUpdateIndexBucket,
)
if err != nil {
return err
}
return putLightningNode(nodes, aliases, updateIndex, node)
}
// updateEdgePolicy attempts to update an edge's policy within the relevant
// buckets using an existing database transaction. The returned boolean will be
// true if the updated policy belongs to node1, and false if the policy belonged
// to node2.
func updateEdgePolicy(tx kvdb.RwTx, edge *ChannelEdgePolicy) (bool, error) {
edges, err := tx.CreateTopLevelBucket(edgeBucket)
if err != nil {
return false, ErrEdgeNotFound
}
edgeIndex := edges.NestedReadWriteBucket(edgeIndexBucket)
if edgeIndex == nil {
return false, ErrEdgeNotFound
}
nodes, err := tx.CreateTopLevelBucket(nodeBucket)
if err != nil {
return false, err
}
// Create the channelID key be converting the channel ID
// integer into a byte slice.
var chanID [8]byte
byteOrder.PutUint64(chanID[:], edge.ChannelID)
// With the channel ID, we then fetch the value storing the two
// nodes which connect this channel edge.
nodeInfo := edgeIndex.Get(chanID[:])
if nodeInfo == nil {
return false, ErrEdgeNotFound
}
// Depending on the flags value passed above, either the first
// or second edge policy is being updated.
var fromNode, toNode []byte
var isUpdate1 bool
if edge.ChannelFlags&lnwire.ChanUpdateDirection == 0 {
fromNode = nodeInfo[:33]
toNode = nodeInfo[33:66]
isUpdate1 = true
} else {
fromNode = nodeInfo[33:66]
toNode = nodeInfo[:33]
isUpdate1 = false
}
// Finally, with the direction of the edge being updated
// identified, we update the on-disk edge representation.
err = putChanEdgePolicy(edges, nodes, edge, fromNode, toNode)
if err != nil {
return false, err
}
return isUpdate1, nil
}
// LightningNode represents an individual vertex/node within the channel graph.
// A node is connected to other nodes by one or more channel edges emanating
// from it. As the graph is directed, a node will also have an incoming edge
// attached to it for each outgoing edge.
type LightningNode struct {
// PubKeyBytes is the raw bytes of the public key of the target node.
PubKeyBytes [33]byte
pubKey *btcec.PublicKey
// HaveNodeAnnouncement indicates whether we received a node
// announcement for this particular node. If true, the remaining fields
// will be set, if false only the PubKey is known for this node.
HaveNodeAnnouncement bool
// LastUpdate is the last time the vertex information for this node has
// been updated.
LastUpdate time.Time
// Address is the TCP address this node is reachable over.
Addresses []net.Addr
// Color is the selected color for the node.
Color color.RGBA
// Alias is a nick-name for the node. The alias can be used to confirm
// a node's identity or to serve as a short ID for an address book.
Alias string
// AuthSigBytes is the raw signature under the advertised public key
// which serves to authenticate the attributes announced by this node.
AuthSigBytes []byte
// Features is the list of protocol features supported by this node.
Features *lnwire.FeatureVector
// ExtraOpaqueData is the set of data that was appended to this
// message, some of which we may not actually know how to iterate or
// parse. By holding onto this data, we ensure that we're able to
// properly validate the set of signatures that cover these new fields,
// and ensure we're able to make upgrades to the network in a forwards
// compatible manner.
ExtraOpaqueData []byte
db *DB
// TODO(roasbeef): discovery will need storage to keep it's last IP
// address and re-announce if interface changes?
// TODO(roasbeef): add update method and fetch?
}
// PubKey is the node's long-term identity public key. This key will be used to
// authenticated any advertisements/updates sent by the node.
//
// NOTE: By having this method to access an attribute, we ensure we only need
// to fully deserialize the pubkey if absolutely necessary.
func (l *LightningNode) PubKey() (*btcec.PublicKey, error) {
if l.pubKey != nil {
return l.pubKey, nil
}
key, err := btcec.ParsePubKey(l.PubKeyBytes[:])
if err != nil {
return nil, err
}
l.pubKey = key
return key, nil
}
// ChannelEdgeInfo represents a fully authenticated channel along with all its
// unique attributes. Once an authenticated channel announcement has been
// processed on the network, then an instance of ChannelEdgeInfo encapsulating
// the channels attributes is stored. The other portions relevant to routing
// policy of a channel are stored within a ChannelEdgePolicy for each direction
// of the channel.
type ChannelEdgeInfo struct {
// ChannelID is the unique channel ID for the channel. The first 3
// bytes are the block height, the next 3 the index within the block,
// and the last 2 bytes are the output index for the channel.
ChannelID uint64
// ChainHash is the hash that uniquely identifies the chain that this
// channel was opened within.
//
// TODO(roasbeef): need to modify db keying for multi-chain
// * must add chain hash to prefix as well
ChainHash chainhash.Hash
// NodeKey1Bytes is the raw public key of the first node.
NodeKey1Bytes [33]byte
// NodeKey2Bytes is the raw public key of the first node.
NodeKey2Bytes [33]byte
// BitcoinKey1Bytes is the raw public key of the first node.
BitcoinKey1Bytes [33]byte
// BitcoinKey2Bytes is the raw public key of the first node.
BitcoinKey2Bytes [33]byte
// Features is an opaque byte slice that encodes the set of channel
// specific features that this channel edge supports.
Features []byte
// AuthProof is the authentication proof for this channel. This proof
// contains a set of signatures binding four identities, which attests
// to the legitimacy of the advertised channel.
AuthProof *ChannelAuthProof
// ChannelPoint is the funding outpoint of the channel. This can be
// used to uniquely identify the channel within the channel graph.
ChannelPoint wire.OutPoint
// Capacity is the total capacity of the channel, this is determined by
// the value output in the outpoint that created this channel.
Capacity btcutil.Amount
// ExtraOpaqueData is the set of data that was appended to this
// message, some of which we may not actually know how to iterate or
// parse. By holding onto this data, we ensure that we're able to
// properly validate the set of signatures that cover these new fields,
// and ensure we're able to make upgrades to the network in a forwards
// compatible manner.
ExtraOpaqueData []byte
}
// ChannelAuthProof is the authentication proof (the signature portion) for a
// channel. Using the four signatures contained in the struct, and some
// auxiliary knowledge (the funding script, node identities, and outpoint) nodes
// on the network are able to validate the authenticity and existence of a
// channel. Each of these signatures signs the following digest: chanID ||
// nodeID1 || nodeID2 || bitcoinKey1|| bitcoinKey2 || 2-byte-feature-len ||
// features.
type ChannelAuthProof struct {
// NodeSig1Bytes are the raw bytes of the first node signature encoded
// in DER format.
NodeSig1Bytes []byte
// NodeSig2Bytes are the raw bytes of the second node signature
// encoded in DER format.
NodeSig2Bytes []byte
// BitcoinSig1Bytes are the raw bytes of the first bitcoin signature
// encoded in DER format.
BitcoinSig1Bytes []byte
// BitcoinSig2Bytes are the raw bytes of the second bitcoin signature
// encoded in DER format.
BitcoinSig2Bytes []byte
}
// IsEmpty check is the authentication proof is empty Proof is empty if at
// least one of the signatures are equal to nil.
func (c *ChannelAuthProof) IsEmpty() bool {
return len(c.NodeSig1Bytes) == 0 ||
len(c.NodeSig2Bytes) == 0 ||
len(c.BitcoinSig1Bytes) == 0 ||
len(c.BitcoinSig2Bytes) == 0
}
// ChannelEdgePolicy represents a *directed* edge within the channel graph. For
// each channel in the database, there are two distinct edges: one for each
// possible direction of travel along the channel. The edges themselves hold
// information concerning fees, and minimum time-lock information which is
// utilized during path finding.
type ChannelEdgePolicy struct {
// SigBytes is the raw bytes of the signature of the channel edge
// policy. We'll only parse these if the caller needs to access the
// signature for validation purposes. Do not set SigBytes directly, but
// use SetSigBytes instead to make sure that the cache is invalidated.
SigBytes []byte
// ChannelID is the unique channel ID for the channel. The first 3
// bytes are the block height, the next 3 the index within the block,
// and the last 2 bytes are the output index for the channel.
ChannelID uint64
// LastUpdate is the last time an authenticated edge for this channel
// was received.
LastUpdate time.Time
// MessageFlags is a bitfield which indicates the presence of optional
// fields (like max_htlc) in the policy.
MessageFlags lnwire.ChanUpdateMsgFlags
// ChannelFlags is a bitfield which signals the capabilities of the
// channel as well as the directed edge this update applies to.
ChannelFlags lnwire.ChanUpdateChanFlags
// TimeLockDelta is the number of blocks this node will subtract from
// the expiry of an incoming HTLC. This value expresses the time buffer
// the node would like to HTLC exchanges.
TimeLockDelta uint16
// MinHTLC is the smallest value HTLC this node will accept, expressed
// in millisatoshi.
MinHTLC lnwire.MilliSatoshi
// MaxHTLC is the largest value HTLC this node will accept, expressed
// in millisatoshi.
MaxHTLC lnwire.MilliSatoshi
// FeeBaseMSat is the base HTLC fee that will be charged for forwarding
// ANY HTLC, expressed in mSAT's.
FeeBaseMSat lnwire.MilliSatoshi
// FeeProportionalMillionths is the rate that the node will charge for
// HTLCs for each millionth of a satoshi forwarded.
FeeProportionalMillionths lnwire.MilliSatoshi
// Node is the LightningNode that this directed edge leads to. Using
// this pointer the channel graph can further be traversed.
Node *LightningNode
// ExtraOpaqueData is the set of data that was appended to this
// message, some of which we may not actually know how to iterate or
// parse. By holding onto this data, we ensure that we're able to
// properly validate the set of signatures that cover these new fields,
// and ensure we're able to make upgrades to the network in a forwards
// compatible manner.
ExtraOpaqueData []byte
}
// IsDisabled determines whether the edge has the disabled bit set.
func (c *ChannelEdgePolicy) IsDisabled() bool {
return c.ChannelFlags&lnwire.ChanUpdateDisabled ==
lnwire.ChanUpdateDisabled
}
func putLightningNode(nodeBucket kvdb.RwBucket, aliasBucket kvdb.RwBucket,
updateIndex kvdb.RwBucket, node *LightningNode) error {
var (
scratch [16]byte
b bytes.Buffer
)
pub, err := node.PubKey()
if err != nil {
return err
}
nodePub := pub.SerializeCompressed()
// If the node has the update time set, write it, else write 0.
updateUnix := uint64(0)
if node.LastUpdate.Unix() > 0 {
updateUnix = uint64(node.LastUpdate.Unix())
}
byteOrder.PutUint64(scratch[:8], updateUnix)
if _, err := b.Write(scratch[:8]); err != nil {
return err
}
if _, err := b.Write(nodePub); err != nil {
return err
}
// If we got a node announcement for this node, we will have the rest
// of the data available. If not we don't have more data to write.
if !node.HaveNodeAnnouncement {
// Write HaveNodeAnnouncement=0.
byteOrder.PutUint16(scratch[:2], 0)
if _, err := b.Write(scratch[:2]); err != nil {
return err
}
return nodeBucket.Put(nodePub, b.Bytes())
}
// Write HaveNodeAnnouncement=1.
byteOrder.PutUint16(scratch[:2], 1)
if _, err := b.Write(scratch[:2]); err != nil {
return err
}
if err := binary.Write(&b, byteOrder, node.Color.R); err != nil {
return err
}
if err := binary.Write(&b, byteOrder, node.Color.G); err != nil {
return err
}
if err := binary.Write(&b, byteOrder, node.Color.B); err != nil {
return err
}
if err := wire.WriteVarString(&b, 0, node.Alias); err != nil {
return err
}
if err := node.Features.Encode(&b); err != nil {
return err
}
numAddresses := uint16(len(node.Addresses))
byteOrder.PutUint16(scratch[:2], numAddresses)
if _, err := b.Write(scratch[:2]); err != nil {
return err
}
for _, address := range node.Addresses {
if err := serializeAddr(&b, address); err != nil {
return err
}
}
sigLen := len(node.AuthSigBytes)
if sigLen > 80 {
return fmt.Errorf("max sig len allowed is 80, had %v",
sigLen)
}
err = wire.WriteVarBytes(&b, 0, node.AuthSigBytes)
if err != nil {
return err
}
if len(node.ExtraOpaqueData) > MaxAllowedExtraOpaqueBytes {
return ErrTooManyExtraOpaqueBytes(len(node.ExtraOpaqueData))
}
err = wire.WriteVarBytes(&b, 0, node.ExtraOpaqueData)
if err != nil {
return err
}
if err := aliasBucket.Put(nodePub, []byte(node.Alias)); err != nil {
return err
}
// With the alias bucket updated, we'll now update the index that
// tracks the time series of node updates.
var indexKey [8 + 33]byte
byteOrder.PutUint64(indexKey[:8], updateUnix)
copy(indexKey[8:], nodePub)
// If there was already an old index entry for this node, then we'll
// delete the old one before we write the new entry.
if nodeBytes := nodeBucket.Get(nodePub); nodeBytes != nil {
// Extract out the old update time to we can reconstruct the
// prior index key to delete it from the index.
oldUpdateTime := nodeBytes[:8]
var oldIndexKey [8 + 33]byte
copy(oldIndexKey[:8], oldUpdateTime)
copy(oldIndexKey[8:], nodePub)
if err := updateIndex.Delete(oldIndexKey[:]); err != nil {
return err
}
}
if err := updateIndex.Put(indexKey[:], nil); err != nil {
return err
}
return nodeBucket.Put(nodePub, b.Bytes())
}
func fetchLightningNode(nodeBucket kvdb.RBucket,
nodePub []byte) (LightningNode, error) {
nodeBytes := nodeBucket.Get(nodePub)
if nodeBytes == nil {
return LightningNode{}, ErrGraphNodeNotFound
}
nodeReader := bytes.NewReader(nodeBytes)
return deserializeLightningNode(nodeReader)
}
func deserializeLightningNode(r io.Reader) (LightningNode, error) {
var (
node LightningNode
scratch [8]byte
err error
)
if _, err := r.Read(scratch[:]); err != nil {
return LightningNode{}, err
}
unix := int64(byteOrder.Uint64(scratch[:]))
node.LastUpdate = time.Unix(unix, 0)
if _, err := io.ReadFull(r, node.PubKeyBytes[:]); err != nil {
return LightningNode{}, err
}
if _, err := r.Read(scratch[:2]); err != nil {
return LightningNode{}, err
}
hasNodeAnn := byteOrder.Uint16(scratch[:2])
if hasNodeAnn == 1 {
node.HaveNodeAnnouncement = true
} else {
node.HaveNodeAnnouncement = false
}
// The rest of the data is optional, and will only be there if we got a node
// announcement for this node.
if !node.HaveNodeAnnouncement {
return node, nil
}
// We did get a node announcement for this node, so we'll have the rest
// of the data available.
if err := binary.Read(r, byteOrder, &node.Color.R); err != nil {
return LightningNode{}, err
}
if err := binary.Read(r, byteOrder, &node.Color.G); err != nil {
return LightningNode{}, err
}
if err := binary.Read(r, byteOrder, &node.Color.B); err != nil {
return LightningNode{}, err
}
node.Alias, err = wire.ReadVarString(r, 0)
if err != nil {
return LightningNode{}, err
}
fv := lnwire.NewFeatureVector(nil, nil)
err = fv.Decode(r)
if err != nil {
return LightningNode{}, err
}
node.Features = fv
if _, err := r.Read(scratch[:2]); err != nil {
return LightningNode{}, err
}
numAddresses := int(byteOrder.Uint16(scratch[:2]))
var addresses []net.Addr
for i := 0; i < numAddresses; i++ {
address, err := deserializeAddr(r)
if err != nil {
return LightningNode{}, err
}
addresses = append(addresses, address)
}
node.Addresses = addresses
node.AuthSigBytes, err = wire.ReadVarBytes(r, 0, 80, "sig")
if err != nil {
return LightningNode{}, err
}
// We'll try and see if there are any opaque bytes left, if not, then
// we'll ignore the EOF error and return the node as is.
node.ExtraOpaqueData, err = wire.ReadVarBytes(
r, 0, MaxAllowedExtraOpaqueBytes, "blob",
)
switch {
case err == io.ErrUnexpectedEOF:
case err == io.EOF:
case err != nil:
return LightningNode{}, err
}
return node, nil
}
func deserializeChanEdgeInfo(r io.Reader) (ChannelEdgeInfo, error) {
var (
err error
edgeInfo ChannelEdgeInfo
)
if _, err := io.ReadFull(r, edgeInfo.NodeKey1Bytes[:]); err != nil {
return ChannelEdgeInfo{}, err
}
if _, err := io.ReadFull(r, edgeInfo.NodeKey2Bytes[:]); err != nil {
return ChannelEdgeInfo{}, err
}
if _, err := io.ReadFull(r, edgeInfo.BitcoinKey1Bytes[:]); err != nil {
return ChannelEdgeInfo{}, err
}
if _, err := io.ReadFull(r, edgeInfo.BitcoinKey2Bytes[:]); err != nil {
return ChannelEdgeInfo{}, err
}
edgeInfo.Features, err = wire.ReadVarBytes(r, 0, 900, "features")
if err != nil {
return ChannelEdgeInfo{}, err
}
proof := &ChannelAuthProof{}
proof.NodeSig1Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs")
if err != nil {
return ChannelEdgeInfo{}, err
}
proof.NodeSig2Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs")
if err != nil {
return ChannelEdgeInfo{}, err
}
proof.BitcoinSig1Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs")
if err != nil {
return ChannelEdgeInfo{}, err
}
proof.BitcoinSig2Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs")
if err != nil {
return ChannelEdgeInfo{}, err
}
if !proof.IsEmpty() {
edgeInfo.AuthProof = proof
}
edgeInfo.ChannelPoint = wire.OutPoint{}
if err := ReadOutpoint(r, &edgeInfo.ChannelPoint); err != nil {
return ChannelEdgeInfo{}, err
}
if err := binary.Read(r, byteOrder, &edgeInfo.Capacity); err != nil {
return ChannelEdgeInfo{}, err
}
if err := binary.Read(r, byteOrder, &edgeInfo.ChannelID); err != nil {
return ChannelEdgeInfo{}, err
}
if _, err := io.ReadFull(r, edgeInfo.ChainHash[:]); err != nil {
return ChannelEdgeInfo{}, err
}
// We'll try and see if there are any opaque bytes left, if not, then
// we'll ignore the EOF error and return the edge as is.
edgeInfo.ExtraOpaqueData, err = wire.ReadVarBytes(
r, 0, MaxAllowedExtraOpaqueBytes, "blob",
)
switch {
case err == io.ErrUnexpectedEOF:
case err == io.EOF:
case err != nil:
return ChannelEdgeInfo{}, err
}
return edgeInfo, nil
}
func putChanEdgePolicy(edges, nodes kvdb.RwBucket, edge *ChannelEdgePolicy,
from, to []byte) error {
var edgeKey [33 + 8]byte
copy(edgeKey[:], from)
byteOrder.PutUint64(edgeKey[33:], edge.ChannelID)
var b bytes.Buffer
if err := serializeChanEdgePolicy(&b, edge, to); err != nil {
return err
}
// Before we write out the new edge, we'll create a new entry in the
// update index in order to keep it fresh.
updateUnix := uint64(edge.LastUpdate.Unix())
var indexKey [8 + 8]byte
byteOrder.PutUint64(indexKey[:8], updateUnix)
byteOrder.PutUint64(indexKey[8:], edge.ChannelID)
updateIndex, err := edges.CreateBucketIfNotExists(edgeUpdateIndexBucket)
if err != nil {
return err
}
// If there was already an entry for this edge, then we'll need to
// delete the old one to ensure we don't leave around any after-images.
// An unknown policy value does not have a update time recorded, so
// it also does not need to be removed.
if edgeBytes := edges.Get(edgeKey[:]); edgeBytes != nil &&
!bytes.Equal(edgeBytes[:], unknownPolicy) {
// In order to delete the old entry, we'll need to obtain the
// *prior* update time in order to delete it. To do this, we'll
// need to deserialize the existing policy within the database
// (now outdated by the new one), and delete its corresponding
// entry within the update index. We'll ignore any
// ErrEdgePolicyOptionalFieldNotFound error, as we only need
// the channel ID and update time to delete the entry.
// TODO(halseth): get rid of these invalid policies in a
// migration.
oldEdgePolicy, err := deserializeChanEdgePolicy(
bytes.NewReader(edgeBytes), nodes,
)
if err != nil && err != ErrEdgePolicyOptionalFieldNotFound {
return err
}
oldUpdateTime := uint64(oldEdgePolicy.LastUpdate.Unix())
var oldIndexKey [8 + 8]byte
byteOrder.PutUint64(oldIndexKey[:8], oldUpdateTime)
byteOrder.PutUint64(oldIndexKey[8:], edge.ChannelID)
if err := updateIndex.Delete(oldIndexKey[:]); err != nil {
return err
}
}
if err := updateIndex.Put(indexKey[:], nil); err != nil {
return err
}
updateEdgePolicyDisabledIndex(
edges, edge.ChannelID,
edge.ChannelFlags&lnwire.ChanUpdateDirection > 0,
edge.IsDisabled(),
)
return edges.Put(edgeKey[:], b.Bytes()[:])
}
// updateEdgePolicyDisabledIndex is used to update the disabledEdgePolicyIndex
// bucket by either add a new disabled ChannelEdgePolicy or remove an existing
// one.
// The direction represents the direction of the edge and disabled is used for
// deciding whether to remove or add an entry to the bucket.
// In general a channel is disabled if two entries for the same chanID exist
// in this bucket.
// Maintaining the bucket this way allows a fast retrieval of disabled
// channels, for example when prune is needed.
func updateEdgePolicyDisabledIndex(edges kvdb.RwBucket, chanID uint64,
direction bool, disabled bool) error {
var disabledEdgeKey [8 + 1]byte
byteOrder.PutUint64(disabledEdgeKey[0:], chanID)
if direction {
disabledEdgeKey[8] = 1
}
disabledEdgePolicyIndex, err := edges.CreateBucketIfNotExists(
disabledEdgePolicyBucket,
)
if err != nil {
return err
}
if disabled {
return disabledEdgePolicyIndex.Put(disabledEdgeKey[:], []byte{})
}
return disabledEdgePolicyIndex.Delete(disabledEdgeKey[:])
}
// putChanEdgePolicyUnknown marks the edge policy as unknown
// in the edges bucket.
func putChanEdgePolicyUnknown(edges kvdb.RwBucket, channelID uint64,
from []byte) error {
var edgeKey [33 + 8]byte
copy(edgeKey[:], from)
byteOrder.PutUint64(edgeKey[33:], channelID)
if edges.Get(edgeKey[:]) != nil {
return fmt.Errorf("Cannot write unknown policy for channel %v "+
" when there is already a policy present", channelID)
}
return edges.Put(edgeKey[:], unknownPolicy)
}
func fetchChanEdgePolicy(edges kvdb.RBucket, chanID []byte,
nodePub []byte, nodes kvdb.RBucket) (*ChannelEdgePolicy, error) {
var edgeKey [33 + 8]byte
copy(edgeKey[:], nodePub)
copy(edgeKey[33:], chanID[:])
edgeBytes := edges.Get(edgeKey[:])
if edgeBytes == nil {
return nil, ErrEdgeNotFound
}
// No need to deserialize unknown policy.
if bytes.Equal(edgeBytes[:], unknownPolicy) {
return nil, nil
}
edgeReader := bytes.NewReader(edgeBytes)
ep, err := deserializeChanEdgePolicy(edgeReader, nodes)
switch {
// If the db policy was missing an expected optional field, we return
// nil as if the policy was unknown.
case err == ErrEdgePolicyOptionalFieldNotFound:
return nil, nil
case err != nil:
return nil, err
}
return ep, nil
}
func serializeChanEdgePolicy(w io.Writer, edge *ChannelEdgePolicy,
to []byte) error {
err := wire.WriteVarBytes(w, 0, edge.SigBytes)
if err != nil {
return err
}
if err := binary.Write(w, byteOrder, edge.ChannelID); err != nil {
return err
}
var scratch [8]byte
updateUnix := uint64(edge.LastUpdate.Unix())
byteOrder.PutUint64(scratch[:], updateUnix)
if _, err := w.Write(scratch[:]); err != nil {
return err
}
if err := binary.Write(w, byteOrder, edge.MessageFlags); err != nil {
return err
}
if err := binary.Write(w, byteOrder, edge.ChannelFlags); err != nil {
return err
}
if err := binary.Write(w, byteOrder, edge.TimeLockDelta); err != nil {
return err
}
if err := binary.Write(w, byteOrder, uint64(edge.MinHTLC)); err != nil {
return err
}
if err := binary.Write(w, byteOrder, uint64(edge.FeeBaseMSat)); err != nil {
return err
}
if err := binary.Write(w, byteOrder, uint64(edge.FeeProportionalMillionths)); err != nil {
return err
}
if _, err := w.Write(to); err != nil {
return err
}
// If the max_htlc field is present, we write it. To be compatible with
// older versions that wasn't aware of this field, we write it as part
// of the opaque data.
// TODO(halseth): clean up when moving to TLV.
var opaqueBuf bytes.Buffer
if edge.MessageFlags.HasMaxHtlc() {
err := binary.Write(&opaqueBuf, byteOrder, uint64(edge.MaxHTLC))
if err != nil {
return err
}
}
if len(edge.ExtraOpaqueData) > MaxAllowedExtraOpaqueBytes {
return ErrTooManyExtraOpaqueBytes(len(edge.ExtraOpaqueData))
}
if _, err := opaqueBuf.Write(edge.ExtraOpaqueData); err != nil {
return err
}
if err := wire.WriteVarBytes(w, 0, opaqueBuf.Bytes()); err != nil {
return err
}
return nil
}
func deserializeChanEdgePolicy(r io.Reader,
nodes kvdb.RBucket) (*ChannelEdgePolicy, error) {
edge := &ChannelEdgePolicy{}
var err error
edge.SigBytes, err = wire.ReadVarBytes(r, 0, 80, "sig")
if err != nil {
return nil, err
}
if err := binary.Read(r, byteOrder, &edge.ChannelID); err != nil {
return nil, err
}
var scratch [8]byte
if _, err := r.Read(scratch[:]); err != nil {
return nil, err
}
unix := int64(byteOrder.Uint64(scratch[:]))
edge.LastUpdate = time.Unix(unix, 0)
if err := binary.Read(r, byteOrder, &edge.MessageFlags); err != nil {
return nil, err
}
if err := binary.Read(r, byteOrder, &edge.ChannelFlags); err != nil {
return nil, err
}
if err := binary.Read(r, byteOrder, &edge.TimeLockDelta); err != nil {
return nil, err
}
var n uint64
if err := binary.Read(r, byteOrder, &n); err != nil {
return nil, err
}
edge.MinHTLC = lnwire.MilliSatoshi(n)
if err := binary.Read(r, byteOrder, &n); err != nil {
return nil, err
}
edge.FeeBaseMSat = lnwire.MilliSatoshi(n)
if err := binary.Read(r, byteOrder, &n); err != nil {
return nil, err
}
edge.FeeProportionalMillionths = lnwire.MilliSatoshi(n)
var pub [33]byte
if _, err := r.Read(pub[:]); err != nil {
return nil, err
}
node, err := fetchLightningNode(nodes, pub[:])
if err != nil {
return nil, fmt.Errorf("unable to fetch node: %x, %w",
pub[:], err)
}
edge.Node = &node
// We'll try and see if there are any opaque bytes left, if not, then
// we'll ignore the EOF error and return the edge as is.
edge.ExtraOpaqueData, err = wire.ReadVarBytes(
r, 0, MaxAllowedExtraOpaqueBytes, "blob",
)
switch {
case err == io.ErrUnexpectedEOF:
case err == io.EOF:
case err != nil:
return nil, err
}
// See if optional fields are present.
if edge.MessageFlags.HasMaxHtlc() {
// The max_htlc field should be at the beginning of the opaque
// bytes.
opq := edge.ExtraOpaqueData
// If the max_htlc field is not present, it might be old data
// stored before this field was validated. We'll return the
// edge along with an error.
if len(opq) < 8 {
return edge, ErrEdgePolicyOptionalFieldNotFound
}
maxHtlc := byteOrder.Uint64(opq[:8])
edge.MaxHTLC = lnwire.MilliSatoshi(maxHtlc)
// Exclude the parsed field from the rest of the opaque data.
edge.ExtraOpaqueData = opq[8:]
}
return edge, nil
}
package migration_01_to_11
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"time"
"github.com/btcsuite/btcd/wire"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/tlv"
)
var (
// invoiceBucket is the name of the bucket within the database that
// stores all data related to invoices no matter their final state.
// Within the invoice bucket, each invoice is keyed by its invoice ID
// which is a monotonically increasing uint32.
invoiceBucket = []byte("invoices")
// addIndexBucket is an index bucket that we'll use to create a
// monotonically increasing set of add indexes. Each time we add a new
// invoice, this sequence number will be incremented and then populated
// within the new invoice.
//
// In addition to this sequence number, we map:
//
// addIndexNo => invoiceKey
addIndexBucket = []byte("invoice-add-index")
// settleIndexBucket is an index bucket that we'll use to create a
// monotonically increasing integer for tracking a "settle index". Each
// time an invoice is settled, this sequence number will be incremented
// as populate within the newly settled invoice.
//
// In addition to this sequence number, we map:
//
// settleIndexNo => invoiceKey
settleIndexBucket = []byte("invoice-settle-index")
)
const (
// MaxMemoSize is maximum size of the memo field within invoices stored
// in the database.
MaxMemoSize = 1024
// MaxReceiptSize is the maximum size of the payment receipt stored
// within the database along side incoming/outgoing invoices.
MaxReceiptSize = 1024
// MaxPaymentRequestSize is the max size of a payment request for
// this invoice.
// TODO(halseth): determine the max length payment request when field
// lengths are final.
MaxPaymentRequestSize = 4096
// A set of tlv type definitions used to serialize invoice htlcs to the
// database.
chanIDType tlv.Type = 1
htlcIDType tlv.Type = 3
amtType tlv.Type = 5
acceptHeightType tlv.Type = 7
acceptTimeType tlv.Type = 9
resolveTimeType tlv.Type = 11
expiryHeightType tlv.Type = 13
stateType tlv.Type = 15
)
// ContractState describes the state the invoice is in.
type ContractState uint8
const (
// ContractOpen means the invoice has only been created.
ContractOpen ContractState = 0
// ContractSettled means the htlc is settled and the invoice has been
// paid.
ContractSettled ContractState = 1
// ContractCanceled means the invoice has been canceled.
ContractCanceled ContractState = 2
// ContractAccepted means the HTLC has been accepted but not settled
// yet.
ContractAccepted ContractState = 3
)
// String returns a human readable identifier for the ContractState type.
func (c ContractState) String() string {
switch c {
case ContractOpen:
return "Open"
case ContractSettled:
return "Settled"
case ContractCanceled:
return "Canceled"
case ContractAccepted:
return "Accepted"
}
return "Unknown"
}
// ContractTerm is a companion struct to the Invoice struct. This struct houses
// the necessary conditions required before the invoice can be considered fully
// settled by the payee.
type ContractTerm struct {
// PaymentPreimage is the preimage which is to be revealed in the
// occasion that an HTLC paying to the hash of this preimage is
// extended.
PaymentPreimage lntypes.Preimage
// Value is the expected amount of milli-satoshis to be paid to an HTLC
// which can be satisfied by the above preimage.
Value lnwire.MilliSatoshi
// State describes the state the invoice is in.
State ContractState
}
// Invoice is a payment invoice generated by a payee in order to request
// payment for some good or service. The inclusion of invoices within Lightning
// creates a payment work flow for merchants very similar to that of the
// existing financial system within PayPal, etc. Invoices are added to the
// database when a payment is requested, then can be settled manually once the
// payment is received at the upper layer. For record keeping purposes,
// invoices are never deleted from the database, instead a bit is toggled
// denoting the invoice has been fully settled. Within the database, all
// invoices must have a unique payment hash which is generated by taking the
// sha256 of the payment preimage.
type Invoice struct {
// Memo is an optional memo to be stored along side an invoice. The
// memo may contain further details pertaining to the invoice itself,
// or any other message which fits within the size constraints.
Memo []byte
// Receipt is an optional field dedicated for storing a
// cryptographically binding receipt of payment.
//
// TODO(roasbeef): document scheme.
Receipt []byte
// PaymentRequest is an optional field where a payment request created
// for this invoice can be stored.
PaymentRequest []byte
// FinalCltvDelta is the minimum required number of blocks before htlc
// expiry when the invoice is accepted.
FinalCltvDelta int32
// Expiry defines how long after creation this invoice should expire.
Expiry time.Duration
// CreationDate is the exact time the invoice was created.
CreationDate time.Time
// SettleDate is the exact time the invoice was settled.
SettleDate time.Time
// Terms are the contractual payment terms of the invoice. Once all the
// terms have been satisfied by the payer, then the invoice can be
// considered fully fulfilled.
//
// TODO(roasbeef): later allow for multiple terms to fulfill the final
// invoice: payment fragmentation, etc.
Terms ContractTerm
// AddIndex is an auto-incrementing integer that acts as a
// monotonically increasing sequence number for all invoices created.
// Clients can then use this field as a "checkpoint" of sorts when
// implementing a streaming RPC to notify consumers of instances where
// an invoice has been added before they re-connected.
//
// NOTE: This index starts at 1.
AddIndex uint64
// SettleIndex is an auto-incrementing integer that acts as a
// monotonically increasing sequence number for all settled invoices.
// Clients can then use this field as a "checkpoint" of sorts when
// implementing a streaming RPC to notify consumers of instances where
// an invoice has been settled before they re-connected.
//
// NOTE: This index starts at 1.
SettleIndex uint64
// AmtPaid is the final amount that we ultimately accepted for pay for
// this invoice. We specify this value independently as it's possible
// that the invoice originally didn't specify an amount, or the sender
// overpaid.
AmtPaid lnwire.MilliSatoshi
// Htlcs records all htlcs that paid to this invoice. Some of these
// htlcs may have been marked as canceled.
Htlcs map[CircuitKey]*InvoiceHTLC
}
// HtlcState defines the states an htlc paying to an invoice can be in.
type HtlcState uint8
// InvoiceHTLC contains details about an htlc paying to this invoice.
type InvoiceHTLC struct {
// Amt is the amount that is carried by this htlc.
Amt lnwire.MilliSatoshi
// AcceptHeight is the block height at which the invoice registry
// decided to accept this htlc as a payment to the invoice. At this
// height, the invoice cltv delay must have been met.
AcceptHeight uint32
// AcceptTime is the wall clock time at which the invoice registry
// decided to accept the htlc.
AcceptTime time.Time
// ResolveTime is the wall clock time at which the invoice registry
// decided to settle the htlc.
ResolveTime time.Time
// Expiry is the expiry height of this htlc.
Expiry uint32
// State indicates the state the invoice htlc is currently in. A
// canceled htlc isn't just removed from the invoice htlcs map, because
// we need AcceptHeight to properly cancel the htlc back.
State HtlcState
}
func validateInvoice(i *Invoice) error {
if len(i.Memo) > MaxMemoSize {
return fmt.Errorf("max length a memo is %v, and invoice "+
"of length %v was provided", MaxMemoSize, len(i.Memo))
}
if len(i.Receipt) > MaxReceiptSize {
return fmt.Errorf("max length a receipt is %v, and invoice "+
"of length %v was provided", MaxReceiptSize,
len(i.Receipt))
}
if len(i.PaymentRequest) > MaxPaymentRequestSize {
return fmt.Errorf("max length of payment request is %v, length "+
"provided was %v", MaxPaymentRequestSize,
len(i.PaymentRequest))
}
return nil
}
// FetchAllInvoices returns all invoices currently stored within the database.
// If the pendingOnly param is true, then only unsettled invoices will be
// returned, skipping all invoices that are fully settled.
func (d *DB) FetchAllInvoices(pendingOnly bool) ([]Invoice, error) {
var invoices []Invoice
err := kvdb.View(d, func(tx kvdb.RTx) error {
invoiceB := tx.ReadBucket(invoiceBucket)
if invoiceB == nil {
return ErrNoInvoicesCreated
}
// Iterate through the entire key space of the top-level
// invoice bucket. If key with a non-nil value stores the next
// invoice ID which maps to the corresponding invoice.
return invoiceB.ForEach(func(k, v []byte) error {
if v == nil {
return nil
}
invoiceReader := bytes.NewReader(v)
invoice, err := deserializeInvoice(invoiceReader)
if err != nil {
return err
}
if pendingOnly &&
invoice.Terms.State == ContractSettled {
return nil
}
invoices = append(invoices, invoice)
return nil
})
}, func() {
invoices = nil
})
if err != nil {
return nil, err
}
return invoices, nil
}
// serializeInvoice serializes an invoice to a writer.
//
// Note: this function is in use for a migration. Before making changes that
// would modify the on disk format, make a copy of the original code and store
// it with the migration.
func serializeInvoice(w io.Writer, i *Invoice) error {
if err := wire.WriteVarBytes(w, 0, i.Memo[:]); err != nil {
return err
}
if err := wire.WriteVarBytes(w, 0, i.Receipt[:]); err != nil {
return err
}
if err := wire.WriteVarBytes(w, 0, i.PaymentRequest[:]); err != nil {
return err
}
if err := binary.Write(w, byteOrder, i.FinalCltvDelta); err != nil {
return err
}
if err := binary.Write(w, byteOrder, int64(i.Expiry)); err != nil {
return err
}
birthBytes, err := i.CreationDate.MarshalBinary()
if err != nil {
return err
}
if err := wire.WriteVarBytes(w, 0, birthBytes); err != nil {
return err
}
settleBytes, err := i.SettleDate.MarshalBinary()
if err != nil {
return err
}
if err := wire.WriteVarBytes(w, 0, settleBytes); err != nil {
return err
}
if _, err := w.Write(i.Terms.PaymentPreimage[:]); err != nil {
return err
}
var scratch [8]byte
byteOrder.PutUint64(scratch[:], uint64(i.Terms.Value))
if _, err := w.Write(scratch[:]); err != nil {
return err
}
if err := binary.Write(w, byteOrder, i.Terms.State); err != nil {
return err
}
if err := binary.Write(w, byteOrder, i.AddIndex); err != nil {
return err
}
if err := binary.Write(w, byteOrder, i.SettleIndex); err != nil {
return err
}
if err := binary.Write(w, byteOrder, int64(i.AmtPaid)); err != nil {
return err
}
if err := serializeHtlcs(w, i.Htlcs); err != nil {
return err
}
return nil
}
// serializeHtlcs serializes a map containing circuit keys and invoice htlcs to
// a writer.
func serializeHtlcs(w io.Writer, htlcs map[CircuitKey]*InvoiceHTLC) error {
for key, htlc := range htlcs {
// Encode the htlc in a tlv stream.
chanID := key.ChanID.ToUint64()
amt := uint64(htlc.Amt)
acceptTime := uint64(htlc.AcceptTime.UnixNano())
resolveTime := uint64(htlc.ResolveTime.UnixNano())
state := uint8(htlc.State)
tlvStream, err := tlv.NewStream(
tlv.MakePrimitiveRecord(chanIDType, &chanID),
tlv.MakePrimitiveRecord(htlcIDType, &key.HtlcID),
tlv.MakePrimitiveRecord(amtType, &amt),
tlv.MakePrimitiveRecord(
acceptHeightType, &htlc.AcceptHeight,
),
tlv.MakePrimitiveRecord(acceptTimeType, &acceptTime),
tlv.MakePrimitiveRecord(resolveTimeType, &resolveTime),
tlv.MakePrimitiveRecord(expiryHeightType, &htlc.Expiry),
tlv.MakePrimitiveRecord(stateType, &state),
)
if err != nil {
return err
}
var b bytes.Buffer
if err := tlvStream.Encode(&b); err != nil {
return err
}
// Write the length of the tlv stream followed by the stream
// bytes.
err = binary.Write(w, byteOrder, uint64(b.Len()))
if err != nil {
return err
}
if _, err := w.Write(b.Bytes()); err != nil {
return err
}
}
return nil
}
func deserializeInvoice(r io.Reader) (Invoice, error) {
var err error
invoice := Invoice{}
// TODO(roasbeef): use read full everywhere
invoice.Memo, err = wire.ReadVarBytes(r, 0, MaxMemoSize, "")
if err != nil {
return invoice, err
}
invoice.Receipt, err = wire.ReadVarBytes(r, 0, MaxReceiptSize, "")
if err != nil {
return invoice, err
}
invoice.PaymentRequest, err = wire.ReadVarBytes(r, 0, MaxPaymentRequestSize, "")
if err != nil {
return invoice, err
}
if err := binary.Read(r, byteOrder, &invoice.FinalCltvDelta); err != nil {
return invoice, err
}
var expiry int64
if err := binary.Read(r, byteOrder, &expiry); err != nil {
return invoice, err
}
invoice.Expiry = time.Duration(expiry)
birthBytes, err := wire.ReadVarBytes(r, 0, 300, "birth")
if err != nil {
return invoice, err
}
if err := invoice.CreationDate.UnmarshalBinary(birthBytes); err != nil {
return invoice, err
}
settledBytes, err := wire.ReadVarBytes(r, 0, 300, "settled")
if err != nil {
return invoice, err
}
if err := invoice.SettleDate.UnmarshalBinary(settledBytes); err != nil {
return invoice, err
}
if _, err := io.ReadFull(r, invoice.Terms.PaymentPreimage[:]); err != nil {
return invoice, err
}
var scratch [8]byte
if _, err := io.ReadFull(r, scratch[:]); err != nil {
return invoice, err
}
invoice.Terms.Value = lnwire.MilliSatoshi(byteOrder.Uint64(scratch[:]))
if err := binary.Read(r, byteOrder, &invoice.Terms.State); err != nil {
return invoice, err
}
if err := binary.Read(r, byteOrder, &invoice.AddIndex); err != nil {
return invoice, err
}
if err := binary.Read(r, byteOrder, &invoice.SettleIndex); err != nil {
return invoice, err
}
if err := binary.Read(r, byteOrder, &invoice.AmtPaid); err != nil {
return invoice, err
}
invoice.Htlcs, err = deserializeHtlcs(r)
if err != nil {
return Invoice{}, err
}
return invoice, nil
}
// deserializeHtlcs reads a list of invoice htlcs from a reader and returns it
// as a map.
func deserializeHtlcs(r io.Reader) (map[CircuitKey]*InvoiceHTLC, error) {
htlcs := make(map[CircuitKey]*InvoiceHTLC, 0)
for {
// Read the length of the tlv stream for this htlc.
var streamLen uint64
if err := binary.Read(r, byteOrder, &streamLen); err != nil {
if err == io.EOF {
break
}
return nil, err
}
streamBytes := make([]byte, streamLen)
if _, err := r.Read(streamBytes); err != nil {
return nil, err
}
streamReader := bytes.NewReader(streamBytes)
// Decode the contents into the htlc fields.
var (
htlc InvoiceHTLC
key CircuitKey
chanID uint64
state uint8
acceptTime, resolveTime uint64
amt uint64
)
tlvStream, err := tlv.NewStream(
tlv.MakePrimitiveRecord(chanIDType, &chanID),
tlv.MakePrimitiveRecord(htlcIDType, &key.HtlcID),
tlv.MakePrimitiveRecord(amtType, &amt),
tlv.MakePrimitiveRecord(
acceptHeightType, &htlc.AcceptHeight,
),
tlv.MakePrimitiveRecord(acceptTimeType, &acceptTime),
tlv.MakePrimitiveRecord(resolveTimeType, &resolveTime),
tlv.MakePrimitiveRecord(expiryHeightType, &htlc.Expiry),
tlv.MakePrimitiveRecord(stateType, &state),
)
if err != nil {
return nil, err
}
if err := tlvStream.Decode(streamReader); err != nil {
return nil, err
}
key.ChanID = lnwire.NewShortChanIDFromInt(chanID)
htlc.AcceptTime = time.Unix(0, int64(acceptTime))
htlc.ResolveTime = time.Unix(0, int64(resolveTime))
htlc.State = HtlcState(state)
htlc.Amt = lnwire.MilliSatoshi(amt)
htlcs[key] = &htlc
}
return htlcs, nil
}
package migration_01_to_11
import (
"io"
)
// deserializeCloseChannelSummaryV6 reads the v6 database format for
// ChannelCloseSummary.
//
// NOTE: deprecated, only for migration.
func deserializeCloseChannelSummaryV6(r io.Reader) (*ChannelCloseSummary, error) {
c := &ChannelCloseSummary{}
err := ReadElements(r,
&c.ChanPoint, &c.ShortChanID, &c.ChainHash, &c.ClosingTXID,
&c.CloseHeight, &c.RemotePub, &c.Capacity, &c.SettledBalance,
&c.TimeLockedBalance, &c.CloseType, &c.IsPending,
)
if err != nil {
return nil, err
}
// We'll now check to see if the channel close summary was encoded with
// any of the additional optional fields.
err = ReadElements(r, &c.RemoteCurrentRevocation)
switch {
case err == io.EOF:
return c, nil
// If we got a non-eof error, then we know there's an actually issue.
// Otherwise, it may have been the case that this summary didn't have
// the set of optional fields.
case err != nil:
return nil, err
}
if err := ReadChanConfig(r, &c.LocalChanConfig); err != nil {
return nil, err
}
// Finally, we'll attempt to read the next unrevoked commitment point
// for the remote party. If we closed the channel before receiving a
// funding locked message, then this can be nil. As a result, we'll use
// the same technique to read the field, only if there's still data
// left in the buffer.
err = ReadElements(r, &c.RemoteNextRevocation)
if err != nil && err != io.EOF {
// If we got a non-eof error, then we know there's an actually
// issue. Otherwise, it may have been the case that this
// summary didn't have the set of optional fields.
return nil, err
}
return c, nil
}
package migration_01_to_11
import (
"github.com/btcsuite/btclog/v2"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration_01_to_11
import (
"github.com/lightningnetwork/lnd/kvdb"
)
var (
// metaBucket stores all the meta information concerning the state of
// the database.
metaBucket = []byte("metadata")
// dbVersionKey is a boltdb key and it's used for storing/retrieving
// current database version.
dbVersionKey = []byte("dbp")
)
// Meta structure holds the database meta information.
type Meta struct {
// DbVersionNumber is the current schema version of the database.
DbVersionNumber uint32
}
// putMeta is an internal helper function used in order to allow callers to
// re-use a database transaction. See the publicly exported PutMeta method for
// more information.
func putMeta(meta *Meta, tx kvdb.RwTx) error {
metaBucket, err := tx.CreateTopLevelBucket(metaBucket)
if err != nil {
return err
}
return putDbVersion(metaBucket, meta)
}
func putDbVersion(metaBucket kvdb.RwBucket, meta *Meta) error {
scratch := make([]byte, 4)
byteOrder.PutUint32(scratch, meta.DbVersionNumber)
return metaBucket.Put(dbVersionKey, scratch)
}
package migration_01_to_11
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"sort"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes"
)
var (
// paymentBucket is the name of the bucket within the database that
// stores all data related to payments.
//
// Within the payments bucket, each invoice is keyed by its invoice ID
// which is a monotonically increasing uint64. BoltDB's sequence
// feature is used for generating monotonically increasing id.
//
// NOTE: Deprecated. Kept around for migration purposes.
paymentBucket = []byte("payments")
// paymentStatusBucket is the name of the bucket within the database
// that stores the status of a payment indexed by the payment's
// preimage.
//
// NOTE: Deprecated. Kept around for migration purposes.
paymentStatusBucket = []byte("payment-status")
)
// outgoingPayment represents a successful payment between the daemon and a
// remote node. Details such as the total fee paid, and the time of the payment
// are stored.
//
// NOTE: Deprecated. Kept around for migration purposes.
type outgoingPayment struct {
Invoice
// Fee is the total fee paid for the payment in milli-satoshis.
Fee lnwire.MilliSatoshi
// TotalTimeLock is the total cumulative time-lock in the HTLC extended
// from the second-to-last hop to the destination.
TimeLockLength uint32
// Path encodes the path the payment took through the network. The path
// excludes the outgoing node and consists of the hex-encoded
// compressed public key of each of the nodes involved in the payment.
Path [][33]byte
// PaymentPreimage is the preImage of a successful payment. This is used
// to calculate the PaymentHash as well as serve as a proof of payment.
PaymentPreimage [32]byte
}
// addPayment saves a successful payment to the database. It is assumed that
// all payment are sent using unique payment hashes.
//
// NOTE: Deprecated. Kept around for migration purposes.
func (db *DB) addPayment(payment *outgoingPayment) error {
// Validate the field of the inner voice within the outgoing payment,
// these must also adhere to the same constraints as regular invoices.
if err := validateInvoice(&payment.Invoice); err != nil {
return err
}
// We first serialize the payment before starting the database
// transaction so we can avoid creating a DB payment in the case of a
// serialization error.
var b bytes.Buffer
if err := serializeOutgoingPayment(&b, payment); err != nil {
return err
}
paymentBytes := b.Bytes()
return kvdb.Update(db, func(tx kvdb.RwTx) error {
payments, err := tx.CreateTopLevelBucket(paymentBucket)
if err != nil {
return err
}
// Obtain the new unique sequence number for this payment.
paymentID, err := payments.NextSequence()
if err != nil {
return err
}
// We use BigEndian for keys as it orders keys in
// ascending order. This allows bucket scans to order payments
// in the order in which they were created.
paymentIDBytes := make([]byte, 8)
binary.BigEndian.PutUint64(paymentIDBytes, paymentID)
return payments.Put(paymentIDBytes, paymentBytes)
}, func() {})
}
// fetchAllPayments returns all outgoing payments in DB.
//
// NOTE: Deprecated. Kept around for migration purposes.
func (db *DB) fetchAllPayments() ([]*outgoingPayment, error) {
var payments []*outgoingPayment
err := kvdb.View(db, func(tx kvdb.RTx) error {
bucket := tx.ReadBucket(paymentBucket)
if bucket == nil {
return ErrNoPaymentsCreated
}
return bucket.ForEach(func(k, v []byte) error {
// If the value is nil, then we ignore it as it may be
// a sub-bucket.
if v == nil {
return nil
}
r := bytes.NewReader(v)
payment, err := deserializeOutgoingPayment(r)
if err != nil {
return err
}
payments = append(payments, payment)
return nil
})
}, func() {
payments = nil
})
if err != nil {
return nil, err
}
return payments, nil
}
// fetchPaymentStatus returns the payment status for outgoing payment.
// If status of the payment isn't found, it will default to "StatusUnknown".
//
// NOTE: Deprecated. Kept around for migration purposes.
func (db *DB) fetchPaymentStatus(paymentHash [32]byte) (PaymentStatus, error) {
var paymentStatus = StatusUnknown
err := kvdb.View(db, func(tx kvdb.RTx) error {
var err error
paymentStatus, err = fetchPaymentStatusTx(tx, paymentHash)
return err
}, func() {
paymentStatus = StatusUnknown
})
if err != nil {
return StatusUnknown, err
}
return paymentStatus, nil
}
// fetchPaymentStatusTx is a helper method that returns the payment status for
// outgoing payment. If status of the payment isn't found, it will default to
// "StatusUnknown". It accepts the boltdb transactions such that this method
// can be composed into other atomic operations.
//
// NOTE: Deprecated. Kept around for migration purposes.
func fetchPaymentStatusTx(tx kvdb.RTx, paymentHash [32]byte) (PaymentStatus, error) {
// The default status for all payments that aren't recorded in database.
var paymentStatus = StatusUnknown
bucket := tx.ReadBucket(paymentStatusBucket)
if bucket == nil {
return paymentStatus, nil
}
paymentStatusBytes := bucket.Get(paymentHash[:])
if paymentStatusBytes == nil {
return paymentStatus, nil
}
paymentStatus.FromBytes(paymentStatusBytes)
return paymentStatus, nil
}
func serializeOutgoingPayment(w io.Writer, p *outgoingPayment) error {
var scratch [8]byte
if err := serializeInvoiceLegacy(w, &p.Invoice); err != nil {
return err
}
byteOrder.PutUint64(scratch[:], uint64(p.Fee))
if _, err := w.Write(scratch[:]); err != nil {
return err
}
// First write out the length of the bytes to prefix the value.
pathLen := uint32(len(p.Path))
byteOrder.PutUint32(scratch[:4], pathLen)
if _, err := w.Write(scratch[:4]); err != nil {
return err
}
// Then with the path written, we write out the series of public keys
// involved in the path.
for _, hop := range p.Path {
if _, err := w.Write(hop[:]); err != nil {
return err
}
}
byteOrder.PutUint32(scratch[:4], p.TimeLockLength)
if _, err := w.Write(scratch[:4]); err != nil {
return err
}
if _, err := w.Write(p.PaymentPreimage[:]); err != nil {
return err
}
return nil
}
func deserializeOutgoingPayment(r io.Reader) (*outgoingPayment, error) {
var scratch [8]byte
p := &outgoingPayment{}
inv, err := deserializeInvoiceLegacy(r)
if err != nil {
return nil, err
}
p.Invoice = inv
if _, err := r.Read(scratch[:]); err != nil {
return nil, err
}
p.Fee = lnwire.MilliSatoshi(byteOrder.Uint64(scratch[:]))
if _, err = r.Read(scratch[:4]); err != nil {
return nil, err
}
pathLen := byteOrder.Uint32(scratch[:4])
path := make([][33]byte, pathLen)
for i := uint32(0); i < pathLen; i++ {
if _, err := r.Read(path[i][:]); err != nil {
return nil, err
}
}
p.Path = path
if _, err = r.Read(scratch[:4]); err != nil {
return nil, err
}
p.TimeLockLength = byteOrder.Uint32(scratch[:4])
if _, err := r.Read(p.PaymentPreimage[:]); err != nil {
return nil, err
}
return p, nil
}
// serializePaymentAttemptInfoMigration9 is the serializePaymentAttemptInfo
// version as existed when migration #9 was created. We keep this around, along
// with the methods below to ensure that clients that upgrade will use the
// correct version of this method.
func serializePaymentAttemptInfoMigration9(w io.Writer, a *PaymentAttemptInfo) error {
if err := WriteElements(w, a.PaymentID, a.SessionKey); err != nil {
return err
}
if err := serializeRouteMigration9(w, a.Route); err != nil {
return err
}
return nil
}
func serializeHopMigration9(w io.Writer, h *Hop) error {
if err := WriteElements(w,
h.PubKeyBytes[:], h.ChannelID, h.OutgoingTimeLock,
h.AmtToForward,
); err != nil {
return err
}
return nil
}
func serializeRouteMigration9(w io.Writer, r Route) error {
if err := WriteElements(w,
r.TotalTimeLock, r.TotalAmount, r.SourcePubKey[:],
); err != nil {
return err
}
if err := WriteElements(w, uint32(len(r.Hops))); err != nil {
return err
}
for _, h := range r.Hops {
if err := serializeHopMigration9(w, h); err != nil {
return err
}
}
return nil
}
func deserializePaymentAttemptInfoMigration9(r io.Reader) (*PaymentAttemptInfo, error) {
a := &PaymentAttemptInfo{}
err := ReadElements(r, &a.PaymentID, &a.SessionKey)
if err != nil {
return nil, err
}
a.Route, err = deserializeRouteMigration9(r)
if err != nil {
return nil, err
}
return a, nil
}
func deserializeRouteMigration9(r io.Reader) (Route, error) {
rt := Route{}
if err := ReadElements(r,
&rt.TotalTimeLock, &rt.TotalAmount,
); err != nil {
return rt, err
}
var pub []byte
if err := ReadElements(r, &pub); err != nil {
return rt, err
}
copy(rt.SourcePubKey[:], pub)
var numHops uint32
if err := ReadElements(r, &numHops); err != nil {
return rt, err
}
var hops []*Hop
for i := uint32(0); i < numHops; i++ {
hop, err := deserializeHopMigration9(r)
if err != nil {
return rt, err
}
hops = append(hops, hop)
}
rt.Hops = hops
return rt, nil
}
func deserializeHopMigration9(r io.Reader) (*Hop, error) {
h := &Hop{}
var pub []byte
if err := ReadElements(r, &pub); err != nil {
return nil, err
}
copy(h.PubKeyBytes[:], pub)
if err := ReadElements(r,
&h.ChannelID, &h.OutgoingTimeLock, &h.AmtToForward,
); err != nil {
return nil, err
}
return h, nil
}
// fetchPaymentsMigration9 returns all sent payments found in the DB using the
// payment attempt info format that was present as of migration #9. We need
// this as otherwise, the current FetchPayments version will use the latest
// decoding format. Note that we only need this for the
// TestOutgoingPaymentsMigration migration test case.
func (db *DB) fetchPaymentsMigration9() ([]*Payment, error) {
var payments []*Payment
err := kvdb.View(db, func(tx kvdb.RTx) error {
paymentsBucket := tx.ReadBucket(paymentsRootBucket)
if paymentsBucket == nil {
return nil
}
return paymentsBucket.ForEach(func(k, v []byte) error {
bucket := paymentsBucket.NestedReadBucket(k)
if bucket == nil {
// We only expect sub-buckets to be found in
// this top-level bucket.
return fmt.Errorf("non bucket element in " +
"payments bucket")
}
p, err := fetchPaymentMigration9(bucket)
if err != nil {
return err
}
payments = append(payments, p)
// For older versions of lnd, duplicate payments to a
// payment has was possible. These will be found in a
// sub-bucket indexed by their sequence number if
// available.
dup := bucket.NestedReadBucket(paymentDuplicateBucket)
if dup == nil {
return nil
}
return dup.ForEach(func(k, v []byte) error {
subBucket := dup.NestedReadBucket(k)
if subBucket == nil {
// We one bucket for each duplicate to
// be found.
return fmt.Errorf("non bucket element" +
"in duplicate bucket")
}
p, err := fetchPaymentMigration9(subBucket)
if err != nil {
return err
}
payments = append(payments, p)
return nil
})
})
}, func() {
payments = nil
})
if err != nil {
return nil, err
}
// Before returning, sort the payments by their sequence number.
sort.Slice(payments, func(i, j int) bool {
return payments[i].sequenceNum < payments[j].sequenceNum
})
return payments, nil
}
func fetchPaymentMigration9(bucket kvdb.RBucket) (*Payment, error) {
var (
err error
p = &Payment{}
)
seqBytes := bucket.Get(paymentSequenceKey)
if seqBytes == nil {
return nil, fmt.Errorf("sequence number not found")
}
p.sequenceNum = binary.BigEndian.Uint64(seqBytes)
// Get the payment status.
p.Status = fetchPaymentStatus(bucket)
// Get the PaymentCreationInfo.
b := bucket.Get(paymentCreationInfoKey)
if b == nil {
return nil, fmt.Errorf("creation info not found")
}
r := bytes.NewReader(b)
p.Info, err = deserializePaymentCreationInfo(r)
if err != nil {
return nil, err
}
// Get the PaymentAttemptInfo. This can be unset.
b = bucket.Get(paymentAttemptInfoKey)
if b != nil {
r = bytes.NewReader(b)
p.Attempt, err = deserializePaymentAttemptInfoMigration9(r)
if err != nil {
return nil, err
}
}
// Get the payment preimage. This is only found for
// completed payments.
b = bucket.Get(paymentSettleInfoKey)
if b != nil {
var preimg lntypes.Preimage
copy(preimg[:], b[:])
p.PaymentPreimage = &preimg
}
// Get failure reason if available.
b = bucket.Get(paymentFailInfoKey)
if b != nil {
reason := FailureReason(b[0])
p.Failure = &reason
}
return p, nil
}
package migration_01_to_11
import (
"bytes"
"io"
"github.com/lightningnetwork/lnd/kvdb"
)
// MigrateRouteSerialization migrates the way we serialize routes across the
// entire database. At the time of writing of this migration, this includes our
// payment attempts, as well as the payment results in mission control.
func MigrateRouteSerialization(tx kvdb.RwTx) error {
// First, we'll do all the payment attempts.
rootPaymentBucket := tx.ReadWriteBucket(paymentsRootBucket)
if rootPaymentBucket == nil {
return nil
}
// As we can't mutate a bucket while we're iterating over it with
// ForEach, we'll need to collect all the known payment hashes in
// memory first.
var payHashes [][]byte
err := rootPaymentBucket.ForEach(func(k, v []byte) error {
if v != nil {
return nil
}
payHashes = append(payHashes, k)
return nil
})
if err != nil {
return err
}
// Now that we have all the payment hashes, we can carry out the
// migration itself.
for _, payHash := range payHashes {
payHashBucket := rootPaymentBucket.NestedReadWriteBucket(payHash)
// First, we'll migrate the main (non duplicate) payment to
// this hash.
err := migrateAttemptEncoding(tx, payHashBucket)
if err != nil {
return err
}
// Now that we've migrated the main payment, we'll also check
// for any duplicate payments to the same payment hash.
dupBucket := payHashBucket.NestedReadWriteBucket(paymentDuplicateBucket)
// If there's no dup bucket, then we can move on to the next
// payment.
if dupBucket == nil {
continue
}
// Otherwise, we'll now iterate through all the duplicate pay
// hashes and migrate those.
var dupSeqNos [][]byte
err = dupBucket.ForEach(func(k, v []byte) error {
dupSeqNos = append(dupSeqNos, k)
return nil
})
if err != nil {
return err
}
// Now in this second pass, we'll re-serialize their duplicate
// payment attempts under the new encoding.
for _, seqNo := range dupSeqNos {
dupPayHashBucket := dupBucket.NestedReadWriteBucket(seqNo)
err := migrateAttemptEncoding(tx, dupPayHashBucket)
if err != nil {
return err
}
}
}
log.Infof("Migration of route/hop serialization complete!")
log.Infof("Migrating to new mission control store by clearing " +
"existing data")
resultsKey := []byte("missioncontrol-results")
err = tx.DeleteTopLevelBucket(resultsKey)
if err != nil && err != kvdb.ErrBucketNotFound {
return err
}
log.Infof("Migration to new mission control completed!")
return nil
}
// migrateAttemptEncoding migrates payment attempts using the legacy format to
// the new format.
func migrateAttemptEncoding(tx kvdb.RwTx, payHashBucket kvdb.RwBucket) error {
payAttemptBytes := payHashBucket.Get(paymentAttemptInfoKey)
if payAttemptBytes == nil {
return nil
}
// For our migration, we'll first read out the existing payment attempt
// using the legacy serialization of the attempt.
payAttemptReader := bytes.NewReader(payAttemptBytes)
payAttempt, err := deserializePaymentAttemptInfoLegacy(
payAttemptReader,
)
if err != nil {
return err
}
// Now that we have the old attempts, we'll explicitly mark this as
// needing a legacy payload, since after this migration, the modern
// payload will be the default if signalled.
for _, hop := range payAttempt.Route.Hops {
hop.LegacyPayload = true
}
// Finally, we'll write out the payment attempt using the new encoding.
var b bytes.Buffer
err = serializePaymentAttemptInfo(&b, payAttempt)
if err != nil {
return err
}
return payHashBucket.Put(paymentAttemptInfoKey, b.Bytes())
}
func deserializePaymentAttemptInfoLegacy(r io.Reader) (*PaymentAttemptInfo, error) {
a := &PaymentAttemptInfo{}
err := ReadElements(r, &a.PaymentID, &a.SessionKey)
if err != nil {
return nil, err
}
a.Route, err = deserializeRouteLegacy(r)
if err != nil {
return nil, err
}
return a, nil
}
func serializePaymentAttemptInfoLegacy(w io.Writer, a *PaymentAttemptInfo) error {
if err := WriteElements(w, a.PaymentID, a.SessionKey); err != nil {
return err
}
if err := serializeRouteLegacy(w, a.Route); err != nil {
return err
}
return nil
}
func deserializeHopLegacy(r io.Reader) (*Hop, error) {
h := &Hop{}
var pub []byte
if err := ReadElements(r, &pub); err != nil {
return nil, err
}
copy(h.PubKeyBytes[:], pub)
if err := ReadElements(r,
&h.ChannelID, &h.OutgoingTimeLock, &h.AmtToForward,
); err != nil {
return nil, err
}
return h, nil
}
func serializeHopLegacy(w io.Writer, h *Hop) error {
if err := WriteElements(w,
h.PubKeyBytes[:], h.ChannelID, h.OutgoingTimeLock,
h.AmtToForward,
); err != nil {
return err
}
return nil
}
func deserializeRouteLegacy(r io.Reader) (Route, error) {
rt := Route{}
if err := ReadElements(r,
&rt.TotalTimeLock, &rt.TotalAmount,
); err != nil {
return rt, err
}
var pub []byte
if err := ReadElements(r, &pub); err != nil {
return rt, err
}
copy(rt.SourcePubKey[:], pub)
var numHops uint32
if err := ReadElements(r, &numHops); err != nil {
return rt, err
}
var hops []*Hop
for i := uint32(0); i < numHops; i++ {
hop, err := deserializeHopLegacy(r)
if err != nil {
return rt, err
}
hops = append(hops, hop)
}
rt.Hops = hops
return rt, nil
}
func serializeRouteLegacy(w io.Writer, r Route) error {
if err := WriteElements(w,
r.TotalTimeLock, r.TotalAmount, r.SourcePubKey[:],
); err != nil {
return err
}
if err := WriteElements(w, uint32(len(r.Hops))); err != nil {
return err
}
for _, h := range r.Hops {
if err := serializeHopLegacy(w, h); err != nil {
return err
}
}
return nil
}
package migration_01_to_11
import (
"bytes"
"encoding/binary"
"fmt"
"io"
bitcoinCfg "github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/wire"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
"github.com/lightningnetwork/lnd/channeldb/migration_01_to_11/zpay32"
"github.com/lightningnetwork/lnd/kvdb"
litecoinCfg "github.com/ltcsuite/ltcd/chaincfg"
)
// MigrateInvoices adds invoice htlcs and a separate cltv delta field to the
// invoices.
func MigrateInvoices(tx kvdb.RwTx) error {
log.Infof("Migrating invoices to new invoice format")
invoiceB := tx.ReadWriteBucket(invoiceBucket)
if invoiceB == nil {
return nil
}
// Iterate through the entire key space of the top-level invoice bucket.
// If key with a non-nil value stores the next invoice ID which maps to
// the corresponding invoice. Store those keys first, because it isn't
// safe to modify the bucket inside a ForEach loop.
var invoiceKeys [][]byte
err := invoiceB.ForEach(func(k, v []byte) error {
if v == nil {
return nil
}
invoiceKeys = append(invoiceKeys, k)
return nil
})
if err != nil {
return err
}
nets := []*bitcoinCfg.Params{
&bitcoinCfg.MainNetParams, &bitcoinCfg.SimNetParams,
&bitcoinCfg.RegressionNetParams, &bitcoinCfg.TestNet3Params,
}
ltcNets := []*litecoinCfg.Params{
&litecoinCfg.MainNetParams, &litecoinCfg.SimNetParams,
&litecoinCfg.RegressionNetParams, &litecoinCfg.TestNet4Params,
}
for _, net := range ltcNets {
var convertedNet bitcoinCfg.Params
convertedNet.Bech32HRPSegwit = net.Bech32HRPSegwit
nets = append(nets, &convertedNet)
}
// Iterate over all stored keys and migrate the invoices.
for _, k := range invoiceKeys {
v := invoiceB.Get(k)
// Deserialize the invoice with the deserializing function that
// was in use for this version of the database.
invoiceReader := bytes.NewReader(v)
invoice, err := deserializeInvoiceLegacy(invoiceReader)
if err != nil {
return err
}
if invoice.Terms.State == ContractAccepted {
return fmt.Errorf("cannot upgrade with invoice(s) " +
"in accepted state, see release notes")
}
// Try to decode the payment request for every possible net to
// avoid passing a the active network to channeldb. This would
// be a layering violation, while this migration is only running
// once and will likely be removed in the future.
var payReq *zpay32.Invoice
for _, net := range nets {
payReq, err = zpay32.Decode(
string(invoice.PaymentRequest), net,
)
if err == nil {
break
}
}
if payReq == nil {
return fmt.Errorf("cannot decode payreq")
}
invoice.FinalCltvDelta = int32(payReq.MinFinalCLTVExpiry())
invoice.Expiry = payReq.Expiry()
// Serialize the invoice in the new format and use it to replace
// the old invoice in the database.
var buf bytes.Buffer
if err := serializeInvoice(&buf, &invoice); err != nil {
return err
}
err = invoiceB.Put(k, buf.Bytes())
if err != nil {
return err
}
}
log.Infof("Migration of invoices completed!")
return nil
}
func deserializeInvoiceLegacy(r io.Reader) (Invoice, error) {
var err error
invoice := Invoice{}
// TODO(roasbeef): use read full everywhere
invoice.Memo, err = wire.ReadVarBytes(r, 0, MaxMemoSize, "")
if err != nil {
return invoice, err
}
invoice.Receipt, err = wire.ReadVarBytes(r, 0, MaxReceiptSize, "")
if err != nil {
return invoice, err
}
invoice.PaymentRequest, err = wire.ReadVarBytes(r, 0, MaxPaymentRequestSize, "")
if err != nil {
return invoice, err
}
birthBytes, err := wire.ReadVarBytes(r, 0, 300, "birth")
if err != nil {
return invoice, err
}
if err := invoice.CreationDate.UnmarshalBinary(birthBytes); err != nil {
return invoice, err
}
settledBytes, err := wire.ReadVarBytes(r, 0, 300, "settled")
if err != nil {
return invoice, err
}
if err := invoice.SettleDate.UnmarshalBinary(settledBytes); err != nil {
return invoice, err
}
if _, err := io.ReadFull(r, invoice.Terms.PaymentPreimage[:]); err != nil {
return invoice, err
}
var scratch [8]byte
if _, err := io.ReadFull(r, scratch[:]); err != nil {
return invoice, err
}
invoice.Terms.Value = lnwire.MilliSatoshi(byteOrder.Uint64(scratch[:]))
if err := binary.Read(r, byteOrder, &invoice.Terms.State); err != nil {
return invoice, err
}
if err := binary.Read(r, byteOrder, &invoice.AddIndex); err != nil {
return invoice, err
}
if err := binary.Read(r, byteOrder, &invoice.SettleIndex); err != nil {
return invoice, err
}
if err := binary.Read(r, byteOrder, &invoice.AmtPaid); err != nil {
return invoice, err
}
return invoice, nil
}
// serializeInvoiceLegacy serializes an invoice in the format of the previous db
// version.
func serializeInvoiceLegacy(w io.Writer, i *Invoice) error {
if err := wire.WriteVarBytes(w, 0, i.Memo[:]); err != nil {
return err
}
if err := wire.WriteVarBytes(w, 0, i.Receipt[:]); err != nil {
return err
}
if err := wire.WriteVarBytes(w, 0, i.PaymentRequest[:]); err != nil {
return err
}
birthBytes, err := i.CreationDate.MarshalBinary()
if err != nil {
return err
}
if err := wire.WriteVarBytes(w, 0, birthBytes); err != nil {
return err
}
settleBytes, err := i.SettleDate.MarshalBinary()
if err != nil {
return err
}
if err := wire.WriteVarBytes(w, 0, settleBytes); err != nil {
return err
}
if _, err := w.Write(i.Terms.PaymentPreimage[:]); err != nil {
return err
}
var scratch [8]byte
byteOrder.PutUint64(scratch[:], uint64(i.Terms.Value))
if _, err := w.Write(scratch[:]); err != nil {
return err
}
if err := binary.Write(w, byteOrder, i.Terms.State); err != nil {
return err
}
if err := binary.Write(w, byteOrder, i.AddIndex); err != nil {
return err
}
if err := binary.Write(w, byteOrder, i.SettleIndex); err != nil {
return err
}
if err := binary.Write(w, byteOrder, int64(i.AmtPaid)); err != nil {
return err
}
return nil
}
package migration_01_to_11
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
"github.com/lightningnetwork/lnd/kvdb"
)
// MigrateNodeAndEdgeUpdateIndex is a migration function that will update the
// database from version 0 to version 1. In version 1, we add two new indexes
// (one for nodes and one for edges) to keep track of the last time a node or
// edge was updated on the network. These new indexes allow us to implement the
// new graph sync protocol added.
func MigrateNodeAndEdgeUpdateIndex(tx kvdb.RwTx) error {
// First, we'll populating the node portion of the new index. Before we
// can add new values to the index, we'll first create the new bucket
// where these items will be housed.
nodes, err := tx.CreateTopLevelBucket(nodeBucket)
if err != nil {
return fmt.Errorf("unable to create node bucket: %w", err)
}
nodeUpdateIndex, err := nodes.CreateBucketIfNotExists(
nodeUpdateIndexBucket,
)
if err != nil {
return fmt.Errorf("unable to create node update index: %w", err)
}
log.Infof("Populating new node update index bucket")
// Now that we know the bucket has been created, we'll iterate over the
// entire node bucket so we can add the (updateTime || nodePub) key
// into the node update index.
err = nodes.ForEach(func(nodePub, nodeInfo []byte) error {
if len(nodePub) != 33 {
return nil
}
log.Tracef("Adding %x to node update index", nodePub)
// The first 8 bytes of a node's serialize data is the update
// time, so we can extract that without decoding the entire
// structure.
updateTime := nodeInfo[:8]
// Now that we have the update time, we can construct the key
// to insert into the index.
var indexKey [8 + 33]byte
copy(indexKey[:8], updateTime)
copy(indexKey[8:], nodePub)
return nodeUpdateIndex.Put(indexKey[:], nil)
})
if err != nil {
return fmt.Errorf("unable to update node indexes: %w", err)
}
log.Infof("Populating new edge update index bucket")
// With the set of nodes updated, we'll now update all edges to have a
// corresponding entry in the edge update index.
edges, err := tx.CreateTopLevelBucket(edgeBucket)
if err != nil {
return fmt.Errorf("unable to create edge bucket: %w", err)
}
edgeUpdateIndex, err := edges.CreateBucketIfNotExists(
edgeUpdateIndexBucket,
)
if err != nil {
return fmt.Errorf("unable to create edge update index: %w", err)
}
// We'll now run through each edge policy in the database, and update
// the index to ensure each edge has the proper record.
err = edges.ForEach(func(edgeKey, edgePolicyBytes []byte) error {
if len(edgeKey) != 41 {
return nil
}
// Now that we know this is the proper record, we'll grab the
// channel ID (last 8 bytes of the key), and then decode the
// edge policy so we can access the update time.
chanID := edgeKey[33:]
edgePolicyReader := bytes.NewReader(edgePolicyBytes)
edgePolicy, err := deserializeChanEdgePolicy(
edgePolicyReader, nodes,
)
if err != nil {
return err
}
log.Tracef("Adding chan_id=%v to edge update index",
edgePolicy.ChannelID)
// We'll now construct the index key using the channel ID, and
// the last time it was updated: (updateTime || chanID).
var indexKey [8 + 8]byte
byteOrder.PutUint64(
indexKey[:], uint64(edgePolicy.LastUpdate.Unix()),
)
copy(indexKey[8:], chanID)
return edgeUpdateIndex.Put(indexKey[:], nil)
})
if err != nil {
return fmt.Errorf("unable to update edge indexes: %w", err)
}
log.Infof("Migration to node and edge update indexes complete!")
return nil
}
// MigrateInvoiceTimeSeries is a database migration that assigns all existing
// invoices an index in the add and/or the settle index. Additionally, all
// existing invoices will have their bytes padded out in order to encode the
// add+settle index as well as the amount paid.
func MigrateInvoiceTimeSeries(tx kvdb.RwTx) error {
invoices, err := tx.CreateTopLevelBucket(invoiceBucket)
if err != nil {
return err
}
addIndex, err := invoices.CreateBucketIfNotExists(
addIndexBucket,
)
if err != nil {
return err
}
settleIndex, err := invoices.CreateBucketIfNotExists(
settleIndexBucket,
)
if err != nil {
return err
}
log.Infof("Migrating invoice database to new time series format")
// Now that we have all the buckets we need, we'll run through each
// invoice in the database, and update it to reflect the new format
// expected post migration.
// NOTE: we store the converted invoices and put them back into the
// database after the loop, since modifying the bucket within the
// ForEach loop is not safe.
var invoicesKeys [][]byte
var invoicesValues [][]byte
err = invoices.ForEach(func(invoiceNum, invoiceBytes []byte) error {
// If this is a sub bucket, then we'll skip it.
if invoiceBytes == nil {
return nil
}
// First, we'll make a copy of the encoded invoice bytes.
invoiceBytesCopy := make([]byte, len(invoiceBytes))
copy(invoiceBytesCopy, invoiceBytes)
// With the bytes copied over, we'll append 24 additional
// bytes. We do this so we can decode the invoice under the new
// serialization format.
padding := bytes.Repeat([]byte{0}, 24)
invoiceBytesCopy = append(invoiceBytesCopy, padding...)
invoiceReader := bytes.NewReader(invoiceBytesCopy)
invoice, err := deserializeInvoiceLegacy(invoiceReader)
if err != nil {
return fmt.Errorf("unable to decode invoice: %w", err)
}
// Now that we have the fully decoded invoice, we can update
// the various indexes that we're added, and finally the
// invoice itself before re-inserting it.
// First, we'll get the new sequence in the addIndex in order
// to create the proper mapping.
nextAddSeqNo, err := addIndex.NextSequence()
if err != nil {
return err
}
var seqNoBytes [8]byte
byteOrder.PutUint64(seqNoBytes[:], nextAddSeqNo)
err = addIndex.Put(seqNoBytes[:], invoiceNum[:])
if err != nil {
return err
}
log.Tracef("Adding invoice (preimage=%x, add_index=%v) to add "+
"time series", invoice.Terms.PaymentPreimage[:],
nextAddSeqNo)
// Next, we'll check if the invoice has been settled or not. If
// so, then we'll also add it to the settle index.
var nextSettleSeqNo uint64
if invoice.Terms.State == ContractSettled {
nextSettleSeqNo, err = settleIndex.NextSequence()
if err != nil {
return err
}
var seqNoBytes [8]byte
byteOrder.PutUint64(seqNoBytes[:], nextSettleSeqNo)
err := settleIndex.Put(seqNoBytes[:], invoiceNum)
if err != nil {
return err
}
invoice.AmtPaid = invoice.Terms.Value
log.Tracef("Adding invoice (preimage=%x, "+
"settle_index=%v) to add time series",
invoice.Terms.PaymentPreimage[:],
nextSettleSeqNo)
}
// Finally, we'll update the invoice itself with the new
// indexing information as well as the amount paid if it has
// been settled or not.
invoice.AddIndex = nextAddSeqNo
invoice.SettleIndex = nextSettleSeqNo
// We've fully migrated an invoice, so we'll now update the
// invoice in-place.
var b bytes.Buffer
if err := serializeInvoiceLegacy(&b, &invoice); err != nil {
return err
}
// Save the key and value pending update for after the ForEach
// is done.
invoicesKeys = append(invoicesKeys, invoiceNum)
invoicesValues = append(invoicesValues, b.Bytes())
return nil
})
if err != nil {
return err
}
// Now put the converted invoices into the DB.
for i := range invoicesKeys {
key := invoicesKeys[i]
value := invoicesValues[i]
if err := invoices.Put(key, value); err != nil {
return err
}
}
log.Infof("Migration to invoice time series index complete!")
return nil
}
// MigrateInvoiceTimeSeriesOutgoingPayments is a follow up to the
// migrateInvoiceTimeSeries migration. As at the time of writing, the
// OutgoingPayment struct embeddeds an instance of the Invoice struct. As a
// result, we also need to migrate the internal invoice to the new format.
func MigrateInvoiceTimeSeriesOutgoingPayments(tx kvdb.RwTx) error {
payBucket := tx.ReadWriteBucket(paymentBucket)
if payBucket == nil {
return nil
}
log.Infof("Migrating invoice database to new outgoing payment format")
// We store the keys and values we want to modify since it is not safe
// to modify them directly within the ForEach loop.
var paymentKeys [][]byte
var paymentValues [][]byte
err := payBucket.ForEach(func(payID, paymentBytes []byte) error {
log.Tracef("Migrating payment %x", payID[:])
// The internal invoices for each payment only contain a
// populated contract term, and creation date, as a result,
// most of the bytes will be "empty".
// We'll calculate the end of the invoice index assuming a
// "minimal" index that's embedded within the greater
// OutgoingPayment. The breakdown is:
// 3 bytes empty var bytes, 16 bytes creation date, 16 bytes
// settled date, 32 bytes payment pre-image, 8 bytes value, 1
// byte settled.
endOfInvoiceIndex := 1 + 1 + 1 + 16 + 16 + 32 + 8 + 1
// We'll now extract the prefix of the pure invoice embedded
// within.
invoiceBytes := paymentBytes[:endOfInvoiceIndex]
// With the prefix extracted, we'll copy over the invoice, and
// also add padding for the new 24 bytes of fields, and finally
// append the remainder of the outgoing payment.
paymentCopy := make([]byte, len(invoiceBytes))
copy(paymentCopy[:], invoiceBytes)
padding := bytes.Repeat([]byte{0}, 24)
paymentCopy = append(paymentCopy, padding...)
paymentCopy = append(
paymentCopy, paymentBytes[endOfInvoiceIndex:]...,
)
// At this point, we now have the new format of the outgoing
// payments, we'll attempt to deserialize it to ensure the
// bytes are properly formatted.
paymentReader := bytes.NewReader(paymentCopy)
_, err := deserializeOutgoingPayment(paymentReader)
if err != nil {
return fmt.Errorf("unable to deserialize payment: %w",
err)
}
// Now that we know the modifications was successful, we'll
// store it to our slice of keys and values, and write it back
// to disk in the new format after the ForEach loop is over.
paymentKeys = append(paymentKeys, payID)
paymentValues = append(paymentValues, paymentCopy)
return nil
})
if err != nil {
return err
}
// Finally store the updated payments to the bucket.
for i := range paymentKeys {
key := paymentKeys[i]
value := paymentValues[i]
if err := payBucket.Put(key, value); err != nil {
return err
}
}
log.Infof("Migration to outgoing payment invoices complete!")
return nil
}
// MigrateEdgePolicies is a migration function that will update the edges
// bucket. It ensure that edges with unknown policies will also have an entry
// in the bucket. After the migration, there will be two edge entries for
// every channel, regardless of whether the policies are known.
func MigrateEdgePolicies(tx kvdb.RwTx) error {
nodes := tx.ReadWriteBucket(nodeBucket)
if nodes == nil {
return nil
}
edges := tx.ReadWriteBucket(edgeBucket)
if edges == nil {
return nil
}
edgeIndex := edges.NestedReadWriteBucket(edgeIndexBucket)
if edgeIndex == nil {
return nil
}
// checkKey gets the policy from the database with a low-level call
// so that it is still possible to distinguish between unknown and
// not present.
checkKey := func(channelId uint64, keyBytes []byte) error {
var channelID [8]byte
byteOrder.PutUint64(channelID[:], channelId)
_, err := fetchChanEdgePolicy(edges,
channelID[:], keyBytes, nodes)
if err == ErrEdgeNotFound {
log.Tracef("Adding unknown edge policy present for node %x, channel %v",
keyBytes, channelId)
err := putChanEdgePolicyUnknown(edges, channelId, keyBytes)
if err != nil {
return err
}
return nil
}
return err
}
// Iterate over all channels and check both edge policies.
err := edgeIndex.ForEach(func(chanID, edgeInfoBytes []byte) error {
infoReader := bytes.NewReader(edgeInfoBytes)
edgeInfo, err := deserializeChanEdgeInfo(infoReader)
if err != nil {
return err
}
for _, key := range [][]byte{edgeInfo.NodeKey1Bytes[:],
edgeInfo.NodeKey2Bytes[:]} {
if err := checkKey(edgeInfo.ChannelID, key); err != nil {
return err
}
}
return nil
})
if err != nil {
return fmt.Errorf("unable to update edge policies: %w", err)
}
log.Infof("Migration of edge policies complete!")
return nil
}
// PaymentStatusesMigration is a database migration intended for adding payment
// statuses for each existing payment entity in bucket to be able control
// transitions of statuses and prevent cases such as double payment
func PaymentStatusesMigration(tx kvdb.RwTx) error {
// Get the bucket dedicated to storing statuses of payments,
// where a key is payment hash, value is payment status.
paymentStatuses, err := tx.CreateTopLevelBucket(paymentStatusBucket)
if err != nil {
return err
}
log.Infof("Migrating database to support payment statuses")
circuitAddKey := []byte("circuit-adds")
circuits := tx.ReadWriteBucket(circuitAddKey)
if circuits != nil {
log.Infof("Marking all known circuits with status InFlight")
err = circuits.ForEach(func(k, v []byte) error {
// Parse the first 8 bytes as the short chan ID for the
// circuit. We'll skip all short chan IDs are not
// locally initiated, which includes all non-zero short
// chan ids.
chanID := binary.BigEndian.Uint64(k[:8])
if chanID != 0 {
return nil
}
// The payment hash is the third item in the serialized
// payment circuit. The first two items are an AddRef
// (10 bytes) and the incoming circuit key (16 bytes).
const payHashOffset = 10 + 16
paymentHash := v[payHashOffset : payHashOffset+32]
return paymentStatuses.Put(
paymentHash[:], StatusInFlight.Bytes(),
)
})
if err != nil {
return err
}
}
log.Infof("Marking all existing payments with status Completed")
// Get the bucket dedicated to storing payments
bucket := tx.ReadWriteBucket(paymentBucket)
if bucket == nil {
return nil
}
// For each payment in the bucket, deserialize the payment and mark it
// as completed.
err = bucket.ForEach(func(k, v []byte) error {
// Ignores if it is sub-bucket.
if v == nil {
return nil
}
r := bytes.NewReader(v)
payment, err := deserializeOutgoingPayment(r)
if err != nil {
return err
}
// Calculate payment hash for current payment.
paymentHash := sha256.Sum256(payment.PaymentPreimage[:])
// Update status for current payment to completed. If it fails,
// the migration is aborted and the payment bucket is returned
// to its previous state.
return paymentStatuses.Put(paymentHash[:], StatusSucceeded.Bytes())
})
if err != nil {
return err
}
log.Infof("Migration of payment statuses complete!")
return nil
}
// MigratePruneEdgeUpdateIndex is a database migration that attempts to resolve
// some lingering bugs with regards to edge policies and their update index.
// Stale entries within the edge update index were not being properly pruned due
// to a miscalculation on the offset of an edge's policy last update. This
// migration also fixes the case where the public keys within edge policies were
// being serialized with an extra byte, causing an even greater error when
// attempting to perform the offset calculation described earlier.
func MigratePruneEdgeUpdateIndex(tx kvdb.RwTx) error {
// To begin the migration, we'll retrieve the update index bucket. If it
// does not exist, we have nothing left to do so we can simply exit.
edges := tx.ReadWriteBucket(edgeBucket)
if edges == nil {
return nil
}
edgeUpdateIndex := edges.NestedReadWriteBucket(edgeUpdateIndexBucket)
if edgeUpdateIndex == nil {
return nil
}
// Retrieve some buckets that will be needed later on. These should
// already exist given the assumption that the buckets above do as
// well.
edgeIndex, err := edges.CreateBucketIfNotExists(edgeIndexBucket)
if err != nil {
return fmt.Errorf("error creating edge index bucket: %w", err)
}
if edgeIndex == nil {
return fmt.Errorf("unable to create/fetch edge index " +
"bucket")
}
nodes, err := tx.CreateTopLevelBucket(nodeBucket)
if err != nil {
return fmt.Errorf("unable to make node bucket")
}
log.Info("Migrating database to properly prune edge update index")
// We'll need to properly prune all the outdated entries within the edge
// update index. To do so, we'll gather all of the existing policies
// within the graph to re-populate them later on.
var edgeKeys [][]byte
err = edges.ForEach(func(edgeKey, edgePolicyBytes []byte) error {
// All valid entries are indexed by a public key (33 bytes)
// followed by a channel ID (8 bytes), so we'll skip any entries
// with keys that do not match this.
if len(edgeKey) != 33+8 {
return nil
}
edgeKeys = append(edgeKeys, edgeKey)
return nil
})
if err != nil {
return fmt.Errorf("unable to gather existing edge policies: %w",
err)
}
log.Info("Constructing set of edge update entries to purge.")
// Build the set of keys that we will remove from the edge update index.
// This will include all keys contained within the bucket.
var updateKeysToRemove [][]byte
err = edgeUpdateIndex.ForEach(func(updKey, _ []byte) error {
updateKeysToRemove = append(updateKeysToRemove, updKey)
return nil
})
if err != nil {
return fmt.Errorf("unable to gather existing edge updates: %w",
err)
}
log.Infof("Removing %d entries from edge update index.",
len(updateKeysToRemove))
// With the set of keys contained in the edge update index constructed,
// we'll proceed in purging all of them from the index.
for _, updKey := range updateKeysToRemove {
if err := edgeUpdateIndex.Delete(updKey); err != nil {
return err
}
}
log.Infof("Repopulating edge update index with %d valid entries.",
len(edgeKeys))
// For each edge key, we'll retrieve the policy, deserialize it, and
// re-add it to the different buckets. By doing so, we'll ensure that
// all existing edge policies are serialized correctly within their
// respective buckets and that the correct entries are populated within
// the edge update index.
for _, edgeKey := range edgeKeys {
edgePolicyBytes := edges.Get(edgeKey)
// Skip any entries with unknown policies as there will not be
// any entries for them in the edge update index.
if bytes.Equal(edgePolicyBytes[:], unknownPolicy) {
continue
}
edgePolicy, err := deserializeChanEdgePolicy(
bytes.NewReader(edgePolicyBytes), nodes,
)
if err != nil {
return err
}
_, err = updateEdgePolicy(tx, edgePolicy)
if err != nil {
return err
}
}
log.Info("Migration to properly prune edge update index complete!")
return nil
}
// MigrateOptionalChannelCloseSummaryFields migrates the serialized format of
// ChannelCloseSummary to a format where optional fields' presence is indicated
// with boolean markers.
func MigrateOptionalChannelCloseSummaryFields(tx kvdb.RwTx) error {
closedChanBucket := tx.ReadWriteBucket(closedChannelBucket)
if closedChanBucket == nil {
return nil
}
log.Info("Migrating to new closed channel format...")
// We store the converted keys and values and put them back into the
// database after the loop, since modifying the bucket within the
// ForEach loop is not safe.
var closedChansKeys [][]byte
var closedChansValues [][]byte
err := closedChanBucket.ForEach(func(chanID, summary []byte) error {
r := bytes.NewReader(summary)
// Read the old (v6) format from the database.
c, err := deserializeCloseChannelSummaryV6(r)
if err != nil {
return err
}
// Serialize using the new format, and put back into the
// bucket.
var b bytes.Buffer
if err := serializeChannelCloseSummary(&b, c); err != nil {
return err
}
// Now that we know the modifications was successful, we'll
// Store the key and value to our slices, and write it back to
// disk in the new format after the ForEach loop is over.
closedChansKeys = append(closedChansKeys, chanID)
closedChansValues = append(closedChansValues, b.Bytes())
return nil
})
if err != nil {
return fmt.Errorf("unable to update closed channels: %w", err)
}
// Now put the new format back into the DB.
for i := range closedChansKeys {
key := closedChansKeys[i]
value := closedChansValues[i]
if err := closedChanBucket.Put(key, value); err != nil {
return err
}
}
log.Info("Migration to new closed channel format complete!")
return nil
}
var messageStoreBucket = []byte("message-store")
// MigrateGossipMessageStoreKeys migrates the key format for gossip messages
// found in the message store to a new one that takes into consideration the of
// the message being stored.
func MigrateGossipMessageStoreKeys(tx kvdb.RwTx) error {
// We'll start by retrieving the bucket in which these messages are
// stored within. If there isn't one, there's nothing left for us to do
// so we can avoid the migration.
messageStore := tx.ReadWriteBucket(messageStoreBucket)
if messageStore == nil {
return nil
}
log.Info("Migrating to the gossip message store new key format")
// Otherwise we'll proceed with the migration. We'll start by coalescing
// all the current messages within the store, which are indexed by the
// public key of the peer which they should be sent to, followed by the
// short channel ID of the channel for which the message belongs to. We
// should only expect to find channel announcement signatures as that
// was the only support message type previously.
msgs := make(map[[33 + 8]byte]*lnwire.AnnounceSignatures)
err := messageStore.ForEach(func(k, v []byte) error {
var msgKey [33 + 8]byte
copy(msgKey[:], k)
msg := &lnwire.AnnounceSignatures{}
if err := msg.Decode(bytes.NewReader(v), 0); err != nil {
return err
}
msgs[msgKey] = msg
return nil
})
if err != nil {
return err
}
// Then, we'll go over all of our messages, remove their previous entry,
// and add another with the new key format. Once we've done this for
// every message, we can consider the migration complete.
for oldMsgKey, msg := range msgs {
if err := messageStore.Delete(oldMsgKey[:]); err != nil {
return err
}
// Construct the new key for which we'll find this message with
// in the store. It'll be the same as the old, but we'll also
// include the message type.
var msgType [2]byte
binary.BigEndian.PutUint16(msgType[:], uint16(msg.MsgType()))
newMsgKey := append(oldMsgKey[:], msgType[:]...)
// Serialize the message with its wire encoding.
var b bytes.Buffer
if _, err := lnwire.WriteMessage(&b, msg, 0); err != nil {
return err
}
if err := messageStore.Put(newMsgKey, b.Bytes()); err != nil {
return err
}
}
log.Info("Migration to the gossip message store new key format complete!")
return nil
}
// MigrateOutgoingPayments moves the OutgoingPayments into a new bucket format
// where they all reside in a top-level bucket indexed by the payment hash. In
// this sub-bucket we store information relevant to this payment, such as the
// payment status.
//
// Since the router cannot handle resumed payments that have the status
// InFlight (we have no PaymentAttemptInfo available for pre-migration
// payments) we delete those statuses, so only Completed payments remain in the
// new bucket structure.
func MigrateOutgoingPayments(tx kvdb.RwTx) error {
log.Infof("Migrating outgoing payments to new bucket structure")
oldPayments := tx.ReadWriteBucket(paymentBucket)
// Return early if there are no payments to migrate.
if oldPayments == nil {
log.Infof("No outgoing payments found, nothing to migrate.")
return nil
}
newPayments, err := tx.CreateTopLevelBucket(paymentsRootBucket)
if err != nil {
return err
}
// Helper method to get the source pubkey. We define it such that we
// only attempt to fetch it if needed.
sourcePub := func() ([33]byte, error) {
var pub [33]byte
nodes := tx.ReadWriteBucket(nodeBucket)
if nodes == nil {
return pub, ErrGraphNotFound
}
selfPub := nodes.Get(sourceKey)
if selfPub == nil {
return pub, ErrSourceNodeNotSet
}
copy(pub[:], selfPub[:])
return pub, nil
}
err = oldPayments.ForEach(func(k, v []byte) error {
// Ignores if it is sub-bucket.
if v == nil {
return nil
}
// Read the old payment format.
r := bytes.NewReader(v)
payment, err := deserializeOutgoingPayment(r)
if err != nil {
return err
}
// Calculate payment hash from the payment preimage.
paymentHash := sha256.Sum256(payment.PaymentPreimage[:])
// Now create and add a PaymentCreationInfo to the bucket.
c := &PaymentCreationInfo{
PaymentHash: paymentHash,
Value: payment.Terms.Value,
CreationDate: payment.CreationDate,
PaymentRequest: payment.PaymentRequest,
}
var infoBuf bytes.Buffer
if err := serializePaymentCreationInfo(&infoBuf, c); err != nil {
return err
}
sourcePubKey, err := sourcePub()
if err != nil {
return err
}
// Do the same for the PaymentAttemptInfo.
totalAmt := payment.Terms.Value + payment.Fee
rt := Route{
TotalTimeLock: payment.TimeLockLength,
TotalAmount: totalAmt,
SourcePubKey: sourcePubKey,
Hops: []*Hop{},
}
for _, hop := range payment.Path {
rt.Hops = append(rt.Hops, &Hop{
PubKeyBytes: hop,
AmtToForward: totalAmt,
})
}
// Since the old format didn't store the fee for individual
// hops, we let the last hop eat the whole fee for the total to
// add up.
if len(rt.Hops) > 0 {
rt.Hops[len(rt.Hops)-1].AmtToForward = payment.Terms.Value
}
// Since we don't have the session key for old payments, we
// create a random one to be able to serialize the attempt
// info.
priv, _ := btcec.NewPrivateKey()
s := &PaymentAttemptInfo{
PaymentID: 0, // unknown.
SessionKey: priv, // unknown.
Route: rt,
}
var attemptBuf bytes.Buffer
if err := serializePaymentAttemptInfoMigration9(&attemptBuf, s); err != nil {
return err
}
// Reuse the existing payment sequence number.
var seqNum [8]byte
copy(seqNum[:], k)
// Create a bucket indexed by the payment hash.
bucket, err := newPayments.CreateBucket(paymentHash[:])
// If the bucket already exists, it means that we are migrating
// from a database containing duplicate payments to a payment
// hash. To keep this information, we store such duplicate
// payments in a sub-bucket.
if err == kvdb.ErrBucketExists {
pHashBucket := newPayments.NestedReadWriteBucket(paymentHash[:])
// Create a bucket for duplicate payments within this
// payment hash's bucket.
dup, err := pHashBucket.CreateBucketIfNotExists(
paymentDuplicateBucket,
)
if err != nil {
return err
}
// Each duplicate will get its own sub-bucket within
// this bucket, so use their sequence number to index
// them by.
bucket, err = dup.CreateBucket(seqNum[:])
if err != nil {
return err
}
} else if err != nil {
return err
}
// Store the payment's information to the bucket.
err = bucket.Put(paymentSequenceKey, seqNum[:])
if err != nil {
return err
}
err = bucket.Put(paymentCreationInfoKey, infoBuf.Bytes())
if err != nil {
return err
}
err = bucket.Put(paymentAttemptInfoKey, attemptBuf.Bytes())
if err != nil {
return err
}
err = bucket.Put(paymentSettleInfoKey, payment.PaymentPreimage[:])
if err != nil {
return err
}
return nil
})
if err != nil {
return err
}
// To continue producing unique sequence numbers, we set the sequence
// of the new bucket to that of the old one.
seq := oldPayments.Sequence()
if err := newPayments.SetSequence(seq); err != nil {
return err
}
// Now we delete the old buckets. Deleting the payment status buckets
// deletes all payment statuses other than Complete.
err = tx.DeleteTopLevelBucket(paymentStatusBucket)
if err != nil && err != kvdb.ErrBucketNotFound {
return err
}
// Finally delete the old payment bucket.
err = tx.DeleteTopLevelBucket(paymentBucket)
if err != nil && err != kvdb.ErrBucketNotFound {
return err
}
log.Infof("Migration of outgoing payment bucket structure completed!")
return nil
}
package migration_01_to_11
import "time"
const (
// DefaultRejectCacheSize is the default number of rejectCacheEntries to
// cache for use in the rejection cache of incoming gossip traffic. This
// produces a cache size of around 1MB.
DefaultRejectCacheSize = 50000
// DefaultChannelCacheSize is the default number of ChannelEdges cached
// in order to reply to gossip queries. This produces a cache size of
// around 40MB.
DefaultChannelCacheSize = 20000
// DefaultDBTimeout specifies the default timeout value when opening
// the bbolt database.
DefaultDBTimeout = time.Second * 60
)
// Options holds parameters for tuning and customizing a channeldb.DB.
type Options struct {
// RejectCacheSize is the maximum number of rejectCacheEntries to hold
// in the rejection cache.
RejectCacheSize int
// ChannelCacheSize is the maximum number of ChannelEdges to hold in the
// channel cache.
ChannelCacheSize int
// NoFreelistSync, if true, prevents the database from syncing its
// freelist to disk, resulting in improved performance at the expense of
// increased startup time.
NoFreelistSync bool
// DBTimeout specifies the timeout value to use when opening the wallet
// database.
DBTimeout time.Duration
}
// DefaultOptions returns an Options populated with default values.
func DefaultOptions() Options {
return Options{
RejectCacheSize: DefaultRejectCacheSize,
ChannelCacheSize: DefaultChannelCacheSize,
NoFreelistSync: true,
DBTimeout: DefaultDBTimeout,
}
}
// OptionModifier is a function signature for modifying the default Options.
type OptionModifier func(*Options)
package migration_01_to_11
import "github.com/lightningnetwork/lnd/kvdb"
// fetchPaymentStatus fetches the payment status of the payment. If the payment
// isn't found, it will default to "StatusUnknown".
func fetchPaymentStatus(bucket kvdb.RBucket) PaymentStatus {
if bucket.Get(paymentSettleInfoKey) != nil {
return StatusSucceeded
}
if bucket.Get(paymentFailInfoKey) != nil {
return StatusFailed
}
if bucket.Get(paymentCreationInfoKey) != nil {
return StatusInFlight
}
return StatusUnknown
}
package migration_01_to_11
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"sort"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/wire"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/tlv"
)
var (
// paymentsRootBucket is the name of the top-level bucket within the
// database that stores all data related to payments. Within this
// bucket, each payment hash its own sub-bucket keyed by its payment
// hash.
//
// Bucket hierarchy:
//
// root-bucket
// |
// |-- <paymenthash>
// | |--sequence-key: <sequence number>
// | |--creation-info-key: <creation info>
// | |--attempt-info-key: <attempt info>
// | |--settle-info-key: <settle info>
// | |--fail-info-key: <fail info>
// | |
// | |--duplicate-bucket (only for old, completed payments)
// | |
// | |-- <seq-num>
// | | |--sequence-key: <sequence number>
// | | |--creation-info-key: <creation info>
// | | |--attempt-info-key: <attempt info>
// | | |--settle-info-key: <settle info>
// | | |--fail-info-key: <fail info>
// | |
// | |-- <seq-num>
// | | |
// | ... ...
// |
// |-- <paymenthash>
// | |
// | ...
// ...
//
paymentsRootBucket = []byte("payments-root-bucket")
// paymentDublicateBucket is the name of a optional sub-bucket within
// the payment hash bucket, that is used to hold duplicate payments to
// a payment hash. This is needed to support information from earlier
// versions of lnd, where it was possible to pay to a payment hash more
// than once.
paymentDuplicateBucket = []byte("payment-duplicate-bucket")
// paymentSequenceKey is a key used in the payment's sub-bucket to
// store the sequence number of the payment.
paymentSequenceKey = []byte("payment-sequence-key")
// paymentCreationInfoKey is a key used in the payment's sub-bucket to
// store the creation info of the payment.
paymentCreationInfoKey = []byte("payment-creation-info")
// paymentAttemptInfoKey is a key used in the payment's sub-bucket to
// store the info about the latest attempt that was done for the
// payment in question.
paymentAttemptInfoKey = []byte("payment-attempt-info")
// paymentSettleInfoKey is a key used in the payment's sub-bucket to
// store the settle info of the payment.
paymentSettleInfoKey = []byte("payment-settle-info")
// paymentFailInfoKey is a key used in the payment's sub-bucket to
// store information about the reason a payment failed.
paymentFailInfoKey = []byte("payment-fail-info")
)
// FailureReason encodes the reason a payment ultimately failed.
type FailureReason byte
const (
// FailureReasonTimeout indicates that the payment did timeout before a
// successful payment attempt was made.
FailureReasonTimeout FailureReason = 0
// FailureReasonNoRoute indicates no successful route to the
// destination was found during path finding.
FailureReasonNoRoute FailureReason = 1
// FailureReasonError indicates that an unexpected error happened during
// payment.
FailureReasonError FailureReason = 2
// FailureReasonIncorrectPaymentDetails indicates that either the hash
// is unknown or the final cltv delta or amount is incorrect.
FailureReasonIncorrectPaymentDetails FailureReason = 3
// TODO(halseth): cancel state.
// TODO(joostjager): Add failure reasons for:
// LocalLiquidityInsufficient, RemoteCapacityInsufficient.
)
// String returns a human readable FailureReason
func (r FailureReason) String() string {
switch r {
case FailureReasonTimeout:
return "timeout"
case FailureReasonNoRoute:
return "no_route"
case FailureReasonError:
return "error"
case FailureReasonIncorrectPaymentDetails:
return "incorrect_payment_details"
}
return "unknown"
}
// PaymentStatus represent current status of payment
type PaymentStatus byte
const (
// StatusUnknown is the status where a payment has never been initiated
// and hence is unknown.
StatusUnknown PaymentStatus = 0
// StatusInFlight is the status where a payment has been initiated, but
// a response has not been received.
StatusInFlight PaymentStatus = 1
// StatusSucceeded is the status where a payment has been initiated and
// the payment was completed successfully.
StatusSucceeded PaymentStatus = 2
// StatusFailed is the status where a payment has been initiated and a
// failure result has come back.
StatusFailed PaymentStatus = 3
)
// Bytes returns status as slice of bytes.
func (ps PaymentStatus) Bytes() []byte {
return []byte{byte(ps)}
}
// FromBytes sets status from slice of bytes.
func (ps *PaymentStatus) FromBytes(status []byte) error {
if len(status) != 1 {
return errors.New("payment status is empty")
}
switch PaymentStatus(status[0]) {
case StatusUnknown, StatusInFlight, StatusSucceeded, StatusFailed:
*ps = PaymentStatus(status[0])
default:
return errors.New("unknown payment status")
}
return nil
}
// String returns readable representation of payment status.
func (ps PaymentStatus) String() string {
switch ps {
case StatusUnknown:
return "Unknown"
case StatusInFlight:
return "In Flight"
case StatusSucceeded:
return "Succeeded"
case StatusFailed:
return "Failed"
default:
return "Unknown"
}
}
// PaymentCreationInfo is the information necessary to have ready when
// initiating a payment, moving it into state InFlight.
type PaymentCreationInfo struct {
// PaymentHash is the hash this payment is paying to.
PaymentHash lntypes.Hash
// Value is the amount we are paying.
Value lnwire.MilliSatoshi
// CreatingDate is the time when this payment was initiated.
CreationDate time.Time
// PaymentRequest is the full payment request, if any.
PaymentRequest []byte
}
// PaymentAttemptInfo contains information about a specific payment attempt for
// a given payment. This information is used by the router to handle any errors
// coming back after an attempt is made, and to query the switch about the
// status of a payment. For settled payment this will be the information for
// the succeeding payment attempt.
type PaymentAttemptInfo struct {
// PaymentID is the unique ID used for this attempt.
PaymentID uint64
// SessionKey is the ephemeral key used for this payment attempt.
SessionKey *btcec.PrivateKey
// Route is the route attempted to send the HTLC.
Route Route
}
// Payment is a wrapper around a payment's PaymentCreationInfo,
// PaymentAttemptInfo, and preimage. All payments will have the
// PaymentCreationInfo set, the PaymentAttemptInfo will be set only if at least
// one payment attempt has been made, while only completed payments will have a
// non-zero payment preimage.
type Payment struct {
// sequenceNum is a unique identifier used to sort the payments in
// order of creation.
sequenceNum uint64
// Status is the current PaymentStatus of this payment.
Status PaymentStatus
// Info holds all static information about this payment, and is
// populated when the payment is initiated.
Info *PaymentCreationInfo
// Attempt is the information about the last payment attempt made.
//
// NOTE: Can be nil if no attempt is yet made.
Attempt *PaymentAttemptInfo
// PaymentPreimage is the preimage of a successful payment. This serves
// as a proof of payment. It will only be non-nil for settled payments.
//
// NOTE: Can be nil if payment is not settled.
PaymentPreimage *lntypes.Preimage
// Failure is a failure reason code indicating the reason the payment
// failed. It is only non-nil for failed payments.
//
// NOTE: Can be nil if payment is not failed.
Failure *FailureReason
}
// FetchPayments returns all sent payments found in the DB.
func (db *DB) FetchPayments() ([]*Payment, error) {
var payments []*Payment
err := kvdb.View(db, func(tx kvdb.RTx) error {
paymentsBucket := tx.ReadBucket(paymentsRootBucket)
if paymentsBucket == nil {
return nil
}
return paymentsBucket.ForEach(func(k, v []byte) error {
bucket := paymentsBucket.NestedReadBucket(k)
if bucket == nil {
// We only expect sub-buckets to be found in
// this top-level bucket.
return fmt.Errorf("non bucket element in " +
"payments bucket")
}
p, err := fetchPayment(bucket)
if err != nil {
return err
}
payments = append(payments, p)
// For older versions of lnd, duplicate payments to a
// payment has was possible. These will be found in a
// sub-bucket indexed by their sequence number if
// available.
dup := bucket.NestedReadBucket(paymentDuplicateBucket)
if dup == nil {
return nil
}
return dup.ForEach(func(k, v []byte) error {
subBucket := dup.NestedReadBucket(k)
if subBucket == nil {
// We one bucket for each duplicate to
// be found.
return fmt.Errorf("non bucket element" +
"in duplicate bucket")
}
p, err := fetchPayment(subBucket)
if err != nil {
return err
}
payments = append(payments, p)
return nil
})
})
}, func() {
payments = nil
})
if err != nil {
return nil, err
}
// Before returning, sort the payments by their sequence number.
sort.Slice(payments, func(i, j int) bool {
return payments[i].sequenceNum < payments[j].sequenceNum
})
return payments, nil
}
func fetchPayment(bucket kvdb.RBucket) (*Payment, error) {
var (
err error
p = &Payment{}
)
seqBytes := bucket.Get(paymentSequenceKey)
if seqBytes == nil {
return nil, fmt.Errorf("sequence number not found")
}
p.sequenceNum = binary.BigEndian.Uint64(seqBytes)
// Get the payment status.
p.Status = fetchPaymentStatus(bucket)
// Get the PaymentCreationInfo.
b := bucket.Get(paymentCreationInfoKey)
if b == nil {
return nil, fmt.Errorf("creation info not found")
}
r := bytes.NewReader(b)
p.Info, err = deserializePaymentCreationInfo(r)
if err != nil {
return nil, err
}
// Get the PaymentAttemptInfo. This can be unset.
b = bucket.Get(paymentAttemptInfoKey)
if b != nil {
r = bytes.NewReader(b)
p.Attempt, err = deserializePaymentAttemptInfo(r)
if err != nil {
return nil, err
}
}
// Get the payment preimage. This is only found for
// completed payments.
b = bucket.Get(paymentSettleInfoKey)
if b != nil {
var preimg lntypes.Preimage
copy(preimg[:], b[:])
p.PaymentPreimage = &preimg
}
// Get failure reason if available.
b = bucket.Get(paymentFailInfoKey)
if b != nil {
reason := FailureReason(b[0])
p.Failure = &reason
}
return p, nil
}
func serializePaymentCreationInfo(w io.Writer, c *PaymentCreationInfo) error {
var scratch [8]byte
if _, err := w.Write(c.PaymentHash[:]); err != nil {
return err
}
byteOrder.PutUint64(scratch[:], uint64(c.Value))
if _, err := w.Write(scratch[:]); err != nil {
return err
}
byteOrder.PutUint64(scratch[:], uint64(c.CreationDate.Unix()))
if _, err := w.Write(scratch[:]); err != nil {
return err
}
byteOrder.PutUint32(scratch[:4], uint32(len(c.PaymentRequest)))
if _, err := w.Write(scratch[:4]); err != nil {
return err
}
if _, err := w.Write(c.PaymentRequest[:]); err != nil {
return err
}
return nil
}
func deserializePaymentCreationInfo(r io.Reader) (*PaymentCreationInfo, error) {
var scratch [8]byte
c := &PaymentCreationInfo{}
if _, err := io.ReadFull(r, c.PaymentHash[:]); err != nil {
return nil, err
}
if _, err := io.ReadFull(r, scratch[:]); err != nil {
return nil, err
}
c.Value = lnwire.MilliSatoshi(byteOrder.Uint64(scratch[:]))
if _, err := io.ReadFull(r, scratch[:]); err != nil {
return nil, err
}
c.CreationDate = time.Unix(int64(byteOrder.Uint64(scratch[:])), 0)
if _, err := io.ReadFull(r, scratch[:4]); err != nil {
return nil, err
}
reqLen := uint32(byteOrder.Uint32(scratch[:4]))
payReq := make([]byte, reqLen)
if reqLen > 0 {
if _, err := io.ReadFull(r, payReq[:]); err != nil {
return nil, err
}
}
c.PaymentRequest = payReq
return c, nil
}
func serializePaymentAttemptInfo(w io.Writer, a *PaymentAttemptInfo) error {
if err := WriteElements(w, a.PaymentID, a.SessionKey); err != nil {
return err
}
if err := SerializeRoute(w, a.Route); err != nil {
return err
}
return nil
}
func deserializePaymentAttemptInfo(r io.Reader) (*PaymentAttemptInfo, error) {
a := &PaymentAttemptInfo{}
err := ReadElements(r, &a.PaymentID, &a.SessionKey)
if err != nil {
return nil, err
}
a.Route, err = DeserializeRoute(r)
if err != nil {
return nil, err
}
return a, nil
}
func serializeHop(w io.Writer, h *Hop) error {
if err := WriteElements(w,
h.PubKeyBytes[:], h.ChannelID, h.OutgoingTimeLock,
h.AmtToForward,
); err != nil {
return err
}
if err := binary.Write(w, byteOrder, h.LegacyPayload); err != nil {
return err
}
// For legacy payloads, we don't need to write any TLV records, so
// we'll write a zero indicating the our serialized TLV map has no
// records.
if h.LegacyPayload {
return WriteElements(w, uint32(0))
}
// Otherwise, we'll transform our slice of records into a map of the
// raw bytes, then serialize them in-line with a length (number of
// elements) prefix.
mapRecords, err := tlv.RecordsToMap(h.TLVRecords)
if err != nil {
return err
}
numRecords := uint32(len(mapRecords))
if err := WriteElements(w, numRecords); err != nil {
return err
}
for recordType, rawBytes := range mapRecords {
if err := WriteElements(w, recordType); err != nil {
return err
}
if err := wire.WriteVarBytes(w, 0, rawBytes); err != nil {
return err
}
}
return nil
}
// maxOnionPayloadSize is the largest Sphinx payload possible, so we don't need
// to read/write a TLV stream larger than this.
const maxOnionPayloadSize = 1300
func deserializeHop(r io.Reader) (*Hop, error) {
h := &Hop{}
var pub []byte
if err := ReadElements(r, &pub); err != nil {
return nil, err
}
copy(h.PubKeyBytes[:], pub)
if err := ReadElements(r,
&h.ChannelID, &h.OutgoingTimeLock, &h.AmtToForward,
); err != nil {
return nil, err
}
// TODO(roasbeef): change field to allow LegacyPayload false to be the
// legacy default?
err := binary.Read(r, byteOrder, &h.LegacyPayload)
if err != nil {
return nil, err
}
var numElements uint32
if err := ReadElements(r, &numElements); err != nil {
return nil, err
}
// If there're no elements, then we can return early.
if numElements == 0 {
return h, nil
}
tlvMap := make(map[uint64][]byte)
for i := uint32(0); i < numElements; i++ {
var tlvType uint64
if err := ReadElements(r, &tlvType); err != nil {
return nil, err
}
rawRecordBytes, err := wire.ReadVarBytes(
r, 0, maxOnionPayloadSize, "tlv",
)
if err != nil {
return nil, err
}
tlvMap[tlvType] = rawRecordBytes
}
h.TLVRecords = tlv.MapToRecords(tlvMap)
return h, nil
}
// SerializeRoute serializes a route.
func SerializeRoute(w io.Writer, r Route) error {
if err := WriteElements(w,
r.TotalTimeLock, r.TotalAmount, r.SourcePubKey[:],
); err != nil {
return err
}
if err := WriteElements(w, uint32(len(r.Hops))); err != nil {
return err
}
for _, h := range r.Hops {
if err := serializeHop(w, h); err != nil {
return err
}
}
return nil
}
// DeserializeRoute deserializes a route.
func DeserializeRoute(r io.Reader) (Route, error) {
rt := Route{}
if err := ReadElements(r,
&rt.TotalTimeLock, &rt.TotalAmount,
); err != nil {
return rt, err
}
var pub []byte
if err := ReadElements(r, &pub); err != nil {
return rt, err
}
copy(rt.SourcePubKey[:], pub)
var numHops uint32
if err := ReadElements(r, &numHops); err != nil {
return rt, err
}
var hops []*Hop
for i := uint32(0); i < numHops; i++ {
hop, err := deserializeHop(r)
if err != nil {
return rt, err
}
hops = append(hops, hop)
}
rt.Hops = hops
return rt, nil
}
package migration_01_to_11
import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"io"
"strconv"
"strings"
"github.com/btcsuite/btcd/btcec/v2"
sphinx "github.com/lightningnetwork/lightning-onion"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/tlv"
)
// VertexSize is the size of the array to store a vertex.
const VertexSize = 33
// ErrNoRouteHopsProvided is returned when a caller attempts to construct a new
// sphinx packet, but provides an empty set of hops for each route.
var ErrNoRouteHopsProvided = fmt.Errorf("empty route hops provided")
// Vertex is a simple alias for the serialization of a compressed Bitcoin
// public key.
type Vertex [VertexSize]byte
// NewVertex returns a new Vertex given a public key.
func NewVertex(pub *btcec.PublicKey) Vertex {
var v Vertex
copy(v[:], pub.SerializeCompressed())
return v
}
// NewVertexFromBytes returns a new Vertex based on a serialized pubkey in a
// byte slice.
func NewVertexFromBytes(b []byte) (Vertex, error) {
vertexLen := len(b)
if vertexLen != VertexSize {
return Vertex{}, fmt.Errorf("invalid vertex length of %v, "+
"want %v", vertexLen, VertexSize)
}
var v Vertex
copy(v[:], b)
return v, nil
}
// NewVertexFromStr returns a new Vertex given its hex-encoded string format.
func NewVertexFromStr(v string) (Vertex, error) {
// Return error if hex string is of incorrect length.
if len(v) != VertexSize*2 {
return Vertex{}, fmt.Errorf("invalid vertex string length of "+
"%v, want %v", len(v), VertexSize*2)
}
vertex, err := hex.DecodeString(v)
if err != nil {
return Vertex{}, err
}
return NewVertexFromBytes(vertex)
}
// String returns a human readable version of the Vertex which is the
// hex-encoding of the serialized compressed public key.
func (v Vertex) String() string {
return fmt.Sprintf("%x", v[:])
}
// Hop represents an intermediate or final node of the route. This naming
// is in line with the definition given in BOLT #4: Onion Routing Protocol.
// The struct houses the channel along which this hop can be reached and
// the values necessary to create the HTLC that needs to be sent to the
// next hop. It is also used to encode the per-hop payload included within
// the Sphinx packet.
type Hop struct {
// PubKeyBytes is the raw bytes of the public key of the target node.
PubKeyBytes Vertex
// ChannelID is the unique channel ID for the channel. The first 3
// bytes are the block height, the next 3 the index within the block,
// and the last 2 bytes are the output index for the channel.
ChannelID uint64
// OutgoingTimeLock is the timelock value that should be used when
// crafting the _outgoing_ HTLC from this hop.
OutgoingTimeLock uint32
// AmtToForward is the amount that this hop will forward to the next
// hop. This value is less than the value that the incoming HTLC
// carries as a fee will be subtracted by the hop.
AmtToForward lnwire.MilliSatoshi
// TLVRecords if non-nil are a set of additional TLV records that
// should be included in the forwarding instructions for this node.
TLVRecords []tlv.Record
// LegacyPayload if true, then this signals that this node doesn't
// understand the new TLV payload, so we must instead use the legacy
// payload.
LegacyPayload bool
}
// PackHopPayload writes to the passed io.Writer, the series of byes that can
// be placed directly into the per-hop payload (EOB) for this hop. This will
// include the required routing fields, as well as serializing any of the
// passed optional TLVRecords. nextChanID is the unique channel ID that
// references the _outgoing_ channel ID that follows this hop. This field
// follows the same semantics as the NextAddress field in the onion: it should
// be set to zero to indicate the terminal hop.
func (h *Hop) PackHopPayload(w io.Writer, nextChanID uint64) error {
// If this is a legacy payload, then we'll exit here as this method
// shouldn't be called.
if h.LegacyPayload == true {
return fmt.Errorf("cannot pack hop payloads for legacy " +
"payloads")
}
// Otherwise, we'll need to make a new stream that includes our
// required routing fields, as well as these optional values.
var records []tlv.Record
// Every hop must have an amount to forward and CLTV expiry.
amt := uint64(h.AmtToForward)
records = append(records,
record.NewAmtToFwdRecord(&amt),
record.NewLockTimeRecord(&h.OutgoingTimeLock),
)
// BOLT 04 says the next_hop_id should be omitted for the final hop,
// but present for all others.
//
// TODO(conner): test using hop.Exit once available
if nextChanID != 0 {
records = append(records,
record.NewNextHopIDRecord(&nextChanID),
)
}
// Append any custom types destined for this hop.
records = append(records, h.TLVRecords...)
// To ensure we produce a canonical stream, we'll sort the records
// before encoding them as a stream in the hop payload.
tlv.SortRecords(records)
tlvStream, err := tlv.NewStream(records...)
if err != nil {
return err
}
return tlvStream.Encode(w)
}
// Route represents a path through the channel graph which runs over one or
// more channels in succession. This struct carries all the information
// required to craft the Sphinx onion packet, and send the payment along the
// first hop in the path. A route is only selected as valid if all the channels
// have sufficient capacity to carry the initial payment amount after fees are
// accounted for.
type Route struct {
// TotalTimeLock is the cumulative (final) time lock across the entire
// route. This is the CLTV value that should be extended to the first
// hop in the route. All other hops will decrement the time-lock as
// advertised, leaving enough time for all hops to wait for or present
// the payment preimage to complete the payment.
TotalTimeLock uint32
// TotalAmount is the total amount of funds required to complete a
// payment over this route. This value includes the cumulative fees at
// each hop. As a result, the HTLC extended to the first-hop in the
// route will need to have at least this many satoshis, otherwise the
// route will fail at an intermediate node due to an insufficient
// amount of fees.
TotalAmount lnwire.MilliSatoshi
// SourcePubKey is the pubkey of the node where this route originates
// from.
SourcePubKey Vertex
// Hops contains details concerning the specific forwarding details at
// each hop.
Hops []*Hop
}
// HopFee returns the fee charged by the route hop indicated by hopIndex.
func (r *Route) HopFee(hopIndex int) lnwire.MilliSatoshi {
var incomingAmt lnwire.MilliSatoshi
if hopIndex == 0 {
incomingAmt = r.TotalAmount
} else {
incomingAmt = r.Hops[hopIndex-1].AmtToForward
}
// Fee is calculated as difference between incoming and outgoing amount.
return incomingAmt - r.Hops[hopIndex].AmtToForward
}
// TotalFees is the sum of the fees paid at each hop within the final route. In
// the case of a one-hop payment, this value will be zero as we don't need to
// pay a fee to ourself.
func (r *Route) TotalFees() lnwire.MilliSatoshi {
if len(r.Hops) == 0 {
return 0
}
return r.TotalAmount - r.Hops[len(r.Hops)-1].AmtToForward
}
// NewRouteFromHops creates a new Route structure from the minimally required
// information to perform the payment. It infers fee amounts and populates the
// node, chan and prev/next hop maps.
func NewRouteFromHops(amtToSend lnwire.MilliSatoshi, timeLock uint32,
sourceVertex Vertex, hops []*Hop) (*Route, error) {
if len(hops) == 0 {
return nil, ErrNoRouteHopsProvided
}
// First, we'll create a route struct and populate it with the fields
// for which the values are provided as arguments of this function.
// TotalFees is determined based on the difference between the amount
// that is send from the source and the final amount that is received
// by the destination.
route := &Route{
SourcePubKey: sourceVertex,
Hops: hops,
TotalTimeLock: timeLock,
TotalAmount: amtToSend,
}
return route, nil
}
// ToSphinxPath converts a complete route into a sphinx PaymentPath that
// contains the per-hop paylods used to encoding the HTLC routing data for each
// hop in the route. This method also accepts an optional EOB payload for the
// final hop.
func (r *Route) ToSphinxPath() (*sphinx.PaymentPath, error) {
var path sphinx.PaymentPath
// For each hop encoded within the route, we'll convert the hop struct
// to an OnionHop with matching per-hop payload within the path as used
// by the sphinx package.
for i, hop := range r.Hops {
pub, err := btcec.ParsePubKey(hop.PubKeyBytes[:])
if err != nil {
return nil, err
}
// As a base case, the next hop is set to all zeroes in order
// to indicate that the "last hop" as no further hops after it.
nextHop := uint64(0)
// If we aren't on the last hop, then we set the "next address"
// field to be the channel that directly follows it.
if i != len(r.Hops)-1 {
nextHop = r.Hops[i+1].ChannelID
}
var payload sphinx.HopPayload
// If this is the legacy payload, then we can just include the
// hop data as normal.
if hop.LegacyPayload {
// Before we encode this value, we'll pack the next hop
// into the NextAddress field of the hop info to ensure
// we point to the right now.
hopData := sphinx.HopData{
ForwardAmount: uint64(hop.AmtToForward),
OutgoingCltv: hop.OutgoingTimeLock,
}
binary.BigEndian.PutUint64(
hopData.NextAddress[:], nextHop,
)
payload, err = sphinx.NewLegacyHopPayload(&hopData)
if err != nil {
return nil, err
}
} else {
// For non-legacy payloads, we'll need to pack the
// routing information, along with any extra TLV
// information into the new per-hop payload format.
// We'll also pass in the chan ID of the hop this
// channel should be forwarded to so we can construct a
// valid payload.
var b bytes.Buffer
err := hop.PackHopPayload(&b, nextHop)
if err != nil {
return nil, err
}
payload, err = sphinx.NewTLVHopPayload(b.Bytes())
if err != nil {
return nil, err
}
}
path[i] = sphinx.OnionHop{
NodePub: *pub,
HopPayload: payload,
}
}
return &path, nil
}
// String returns a human readable representation of the route.
func (r *Route) String() string {
var b strings.Builder
for i, hop := range r.Hops {
if i > 0 {
b.WriteString(",")
}
b.WriteString(strconv.FormatUint(hop.ChannelID, 10))
}
return fmt.Sprintf("amt=%v, fees=%v, tl=%v, chans=%v",
r.TotalAmount-r.TotalFees(), r.TotalFees(), r.TotalTimeLock,
b.String(),
)
}
package zpay32
import (
"fmt"
"strconv"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
)
var (
// toMSat is a map from a unit to a function that converts an amount
// of that unit to millisatoshis.
toMSat = map[byte]func(uint64) (lnwire.MilliSatoshi, error){
'm': mBtcToMSat,
'u': uBtcToMSat,
'n': nBtcToMSat,
'p': pBtcToMSat,
}
// fromMSat is a map from a unit to a function that converts an amount
// in millisatoshis to an amount of that unit.
fromMSat = map[byte]func(lnwire.MilliSatoshi) (uint64, error){
'm': mSatToMBtc,
'u': mSatToUBtc,
'n': mSatToNBtc,
'p': mSatToPBtc,
}
)
// mBtcToMSat converts the given amount in milliBTC to millisatoshis.
func mBtcToMSat(m uint64) (lnwire.MilliSatoshi, error) {
return lnwire.MilliSatoshi(m) * 100000000, nil
}
// uBtcToMSat converts the given amount in microBTC to millisatoshis.
func uBtcToMSat(u uint64) (lnwire.MilliSatoshi, error) {
return lnwire.MilliSatoshi(u * 100000), nil
}
// nBtcToMSat converts the given amount in nanoBTC to millisatoshis.
func nBtcToMSat(n uint64) (lnwire.MilliSatoshi, error) {
return lnwire.MilliSatoshi(n * 100), nil
}
// pBtcToMSat converts the given amount in picoBTC to millisatoshis.
func pBtcToMSat(p uint64) (lnwire.MilliSatoshi, error) {
if p < 10 {
return 0, fmt.Errorf("minimum amount is 10p")
}
if p%10 != 0 {
return 0, fmt.Errorf("amount %d pBTC not expressible in msat",
p)
}
return lnwire.MilliSatoshi(p / 10), nil
}
// mSatToMBtc converts the given amount in millisatoshis to milliBTC.
func mSatToMBtc(msat lnwire.MilliSatoshi) (uint64, error) {
if msat%100000000 != 0 {
return 0, fmt.Errorf("%d msat not expressible "+
"in mBTC", msat)
}
return uint64(msat / 100000000), nil
}
// mSatToUBtc converts the given amount in millisatoshis to microBTC.
func mSatToUBtc(msat lnwire.MilliSatoshi) (uint64, error) {
if msat%100000 != 0 {
return 0, fmt.Errorf("%d msat not expressible "+
"in uBTC", msat)
}
return uint64(msat / 100000), nil
}
// mSatToNBtc converts the given amount in millisatoshis to nanoBTC.
func mSatToNBtc(msat lnwire.MilliSatoshi) (uint64, error) {
if msat%100 != 0 {
return 0, fmt.Errorf("%d msat not expressible in nBTC", msat)
}
return uint64(msat / 100), nil
}
// mSatToPBtc converts the given amount in millisatoshis to picoBTC.
func mSatToPBtc(msat lnwire.MilliSatoshi) (uint64, error) {
return uint64(msat * 10), nil
}
// decodeAmount returns the amount encoded by the provided string in
// millisatoshi.
func decodeAmount(amount string) (lnwire.MilliSatoshi, error) {
if len(amount) < 1 {
return 0, fmt.Errorf("amount must be non-empty")
}
// If last character is a digit, then the amount can just be
// interpreted as BTC.
char := amount[len(amount)-1]
digit := char - '0'
if digit >= 0 && digit <= 9 {
btc, err := strconv.ParseUint(amount, 10, 64)
if err != nil {
return 0, err
}
return lnwire.MilliSatoshi(btc) * mSatPerBtc, nil
}
// If not a digit, it must be part of the known units.
conv, ok := toMSat[char]
if !ok {
return 0, fmt.Errorf("unknown multiplier %c", char)
}
// Known unit.
num := amount[:len(amount)-1]
if len(num) < 1 {
return 0, fmt.Errorf("number must be non-empty")
}
am, err := strconv.ParseUint(num, 10, 64)
if err != nil {
return 0, err
}
return conv(am)
}
// encodeAmount encodes the provided millisatoshi amount using as few characters
// as possible.
func encodeAmount(msat lnwire.MilliSatoshi) (string, error) {
// If possible to express in BTC, that will always be the shortest
// representation.
if msat%mSatPerBtc == 0 {
return strconv.FormatInt(int64(msat/mSatPerBtc), 10), nil
}
// Should always be expressible in pico BTC.
pico, err := fromMSat['p'](msat)
if err != nil {
return "", fmt.Errorf("unable to express %d msat as pBTC: %w",
msat, err)
}
shortened := strconv.FormatUint(pico, 10) + "p"
for unit, conv := range fromMSat {
am, err := conv(msat)
if err != nil {
// Not expressible using this unit.
continue
}
// Save the shortest found representation.
str := strconv.FormatUint(am, 10) + string(unit)
if len(str) < len(shortened) {
shortened = str
}
}
return shortened, nil
}
package zpay32
import (
"fmt"
"strings"
)
const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
var gen = []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}
// NOTE: This method it a slight modification of the method bech32.Decode found
// btcutil, allowing strings to be more than 90 characters.
// decodeBech32 decodes a bech32 encoded string, returning the human-readable
// part and the data part excluding the checksum.
// Note: the data will be base32 encoded, that is each element of the returned
// byte array will encode 5 bits of data. Use the ConvertBits method to convert
// this to 8-bit representation.
func decodeBech32(bech string) (string, []byte, error) {
// The maximum allowed length for a bech32 string is 90. It must also
// be at least 8 characters, since it needs a non-empty HRP, a
// separator, and a 6 character checksum.
// NB: The 90 character check specified in BIP173 is skipped here, to
// allow strings longer than 90 characters.
if len(bech) < 8 {
return "", nil, fmt.Errorf("invalid bech32 string length %d",
len(bech))
}
// Only ASCII characters between 33 and 126 are allowed.
for i := 0; i < len(bech); i++ {
if bech[i] < 33 || bech[i] > 126 {
return "", nil, fmt.Errorf("invalid character in "+
"string: '%c'", bech[i])
}
}
// The characters must be either all lowercase or all uppercase.
lower := strings.ToLower(bech)
upper := strings.ToUpper(bech)
if bech != lower && bech != upper {
return "", nil, fmt.Errorf("string not all lowercase or all " +
"uppercase")
}
// We'll work with the lowercase string from now on.
bech = lower
// The string is invalid if the last '1' is non-existent, it is the
// first character of the string (no human-readable part) or one of the
// last 6 characters of the string (since checksum cannot contain '1'),
// or if the string is more than 90 characters in total.
one := strings.LastIndexByte(bech, '1')
if one < 1 || one+7 > len(bech) {
return "", nil, fmt.Errorf("invalid index of 1")
}
// The human-readable part is everything before the last '1'.
hrp := bech[:one]
data := bech[one+1:]
// Each character corresponds to the byte with value of the index in
// 'charset'.
decoded, err := toBytes(data)
if err != nil {
return "", nil, fmt.Errorf("failed converting data to bytes: "+
"%v", err)
}
if !bech32VerifyChecksum(hrp, decoded) {
moreInfo := ""
checksum := bech[len(bech)-6:]
expected, err := toChars(bech32Checksum(hrp,
decoded[:len(decoded)-6]))
if err == nil {
moreInfo = fmt.Sprintf("Expected %v, got %v.",
expected, checksum)
}
return "", nil, fmt.Errorf("checksum failed. " + moreInfo)
}
// We exclude the last 6 bytes, which is the checksum.
return hrp, decoded[:len(decoded)-6], nil
}
// toBytes converts each character in the string 'chars' to the value of the
// index of the corresponding character in 'charset'.
func toBytes(chars string) ([]byte, error) {
decoded := make([]byte, 0, len(chars))
for i := 0; i < len(chars); i++ {
index := strings.IndexByte(charset, chars[i])
if index < 0 {
return nil, fmt.Errorf("invalid character not part of "+
"charset: %v", chars[i])
}
decoded = append(decoded, byte(index))
}
return decoded, nil
}
// toChars converts the byte slice 'data' to a string where each byte in 'data'
// encodes the index of a character in 'charset'.
func toChars(data []byte) (string, error) {
result := make([]byte, 0, len(data))
for _, b := range data {
if int(b) >= len(charset) {
return "", fmt.Errorf("invalid data byte: %v", b)
}
result = append(result, charset[b])
}
return string(result), nil
}
// For more details on the checksum calculation, please refer to BIP 173.
func bech32Checksum(hrp string, data []byte) []byte {
// Convert the bytes to list of integers, as this is needed for the
// checksum calculation.
integers := make([]int, len(data))
for i, b := range data {
integers[i] = int(b)
}
values := append(bech32HrpExpand(hrp), integers...)
values = append(values, []int{0, 0, 0, 0, 0, 0}...)
polymod := bech32Polymod(values) ^ 1
var res []byte
for i := 0; i < 6; i++ {
res = append(res, byte((polymod>>uint(5*(5-i)))&31))
}
return res
}
// For more details on the polymod calculation, please refer to BIP 173.
func bech32Polymod(values []int) int {
chk := 1
for _, v := range values {
b := chk >> 25
chk = (chk&0x1ffffff)<<5 ^ v
for i := 0; i < 5; i++ {
if (b>>uint(i))&1 == 1 {
chk ^= gen[i]
}
}
}
return chk
}
// For more details on HRP expansion, please refer to BIP 173.
func bech32HrpExpand(hrp string) []int {
v := make([]int, 0, len(hrp)*2+1)
for i := 0; i < len(hrp); i++ {
v = append(v, int(hrp[i]>>5))
}
v = append(v, 0)
for i := 0; i < len(hrp); i++ {
v = append(v, int(hrp[i]&31))
}
return v
}
// For more details on the checksum verification, please refer to BIP 173.
func bech32VerifyChecksum(hrp string, data []byte) bool {
integers := make([]int, len(data))
for i, b := range data {
integers[i] = int(b)
}
concat := append(bech32HrpExpand(hrp), integers...)
return bech32Polymod(concat) == 1
}
package zpay32
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"strings"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/bech32"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
)
// Decode parses the provided encoded invoice and returns a decoded Invoice if
// it is valid by BOLT-0011 and matches the provided active network.
func Decode(invoice string, net *chaincfg.Params) (*Invoice, error) {
decodedInvoice := Invoice{}
// Before bech32 decoding the invoice, make sure that it is not too large.
// This is done as an anti-DoS measure since bech32 decoding is expensive.
if len(invoice) > maxInvoiceLength {
return nil, ErrInvoiceTooLarge
}
// Decode the invoice using the modified bech32 decoder.
hrp, data, err := decodeBech32(invoice)
if err != nil {
return nil, err
}
// We expect the human-readable part to at least have ln + one char
// encoding the network.
if len(hrp) < 3 {
return nil, fmt.Errorf("hrp too short")
}
// First two characters of HRP should be "ln".
if hrp[:2] != "ln" {
return nil, fmt.Errorf("prefix should be \"ln\"")
}
// The next characters should be a valid prefix for a segwit BIP173
// address that match the active network.
if !strings.HasPrefix(hrp[2:], net.Bech32HRPSegwit) {
return nil, fmt.Errorf(
"invoice not for current active network '%s'", net.Name)
}
decodedInvoice.Net = net
// Optionally, if there's anything left of the HRP after ln + the segwit
// prefix, we try to decode this as the payment amount.
var netPrefixLength = len(net.Bech32HRPSegwit) + 2
if len(hrp) > netPrefixLength {
amount, err := decodeAmount(hrp[netPrefixLength:])
if err != nil {
return nil, err
}
decodedInvoice.MilliSat = &amount
}
// Everything except the last 520 bits of the data encodes the invoice's
// timestamp and tagged fields.
if len(data) < signatureBase32Len {
return nil, errors.New("short invoice")
}
invoiceData := data[:len(data)-signatureBase32Len]
// Parse the timestamp and tagged fields, and fill the Invoice struct.
if err := parseData(&decodedInvoice, invoiceData, net); err != nil {
return nil, err
}
// The last 520 bits (104 groups) make up the signature.
sigBase32 := data[len(data)-signatureBase32Len:]
sigBase256, err := bech32.ConvertBits(sigBase32, 5, 8, true)
if err != nil {
return nil, err
}
var sig lnwire.Sig
copy(sig[:], sigBase256[:64])
recoveryID := sigBase256[64]
// The signature is over the hrp + the data the invoice, encoded in
// base 256.
taggedDataBytes, err := bech32.ConvertBits(invoiceData, 5, 8, true)
if err != nil {
return nil, err
}
toSign := append([]byte(hrp), taggedDataBytes...)
// We expect the signature to be over the single SHA-256 hash of that
// data.
hash := chainhash.HashB(toSign)
// If the destination pubkey was provided as a tagged field, use that
// to verify the signature, if not do public key recovery.
if decodedInvoice.Destination != nil {
signature, err := sig.ToSignature()
if err != nil {
return nil, fmt.Errorf("unable to deserialize "+
"signature: %v", err)
}
if !signature.Verify(hash, decodedInvoice.Destination) {
return nil, fmt.Errorf("invalid invoice signature")
}
} else {
headerByte := recoveryID + 27 + 4
compactSign := append([]byte{headerByte}, sig[:]...)
pubkey, _, err := ecdsa.RecoverCompact(compactSign, hash)
if err != nil {
return nil, err
}
decodedInvoice.Destination = pubkey
}
// If no feature vector was decoded, populate an empty one.
if decodedInvoice.Features == nil {
decodedInvoice.Features = lnwire.NewFeatureVector(
nil, lnwire.Features,
)
}
// Now that we have created the invoice, make sure it has the required
// fields set.
if err := validateInvoice(&decodedInvoice); err != nil {
return nil, err
}
return &decodedInvoice, nil
}
// parseData parses the data part of the invoice. It expects base32 data
// returned from the bech32.Decode method, except signature.
func parseData(invoice *Invoice, data []byte, net *chaincfg.Params) error {
// It must contain the timestamp, encoded using 35 bits (7 groups).
if len(data) < timestampBase32Len {
return fmt.Errorf("data too short: %d", len(data))
}
t, err := parseTimestamp(data[:timestampBase32Len])
if err != nil {
return err
}
invoice.Timestamp = time.Unix(int64(t), 0)
// The rest are tagged parts.
tagData := data[7:]
return parseTaggedFields(invoice, tagData, net)
}
// parseTimestamp converts a 35-bit timestamp (encoded in base32) to uint64.
func parseTimestamp(data []byte) (uint64, error) {
if len(data) != timestampBase32Len {
return 0, fmt.Errorf("timestamp must be 35 bits, was %d",
len(data)*5)
}
return base32ToUint64(data)
}
// parseTaggedFields takes the base32 encoded tagged fields of the invoice, and
// fills the Invoice struct accordingly.
func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) error {
index := 0
for len(fields)-index > 0 {
// If there are less than 3 groups to read, there cannot be more
// interesting information, as we need the type (1 group) and
// length (2 groups).
//
// This means the last tagged field is broken.
if len(fields)-index < 3 {
return ErrBrokenTaggedField
}
typ := fields[index]
dataLength, err := parseFieldDataLength(fields[index+1 : index+3])
if err != nil {
return err
}
// If we don't have enough field data left to read this length,
// return error.
if len(fields) < index+3+int(dataLength) {
return ErrInvalidFieldLength
}
base32Data := fields[index+3 : index+3+int(dataLength)]
// Advance the index in preparation for the next iteration.
index += 3 + int(dataLength)
switch typ {
case fieldTypeP:
if invoice.PaymentHash != nil {
// We skip the field if we have already seen a
// supported one.
continue
}
invoice.PaymentHash, err = parse32Bytes(base32Data)
case fieldTypeS:
if invoice.PaymentAddr != nil {
// We skip the field if we have already seen a
// supported one.
continue
}
invoice.PaymentAddr, err = parse32Bytes(base32Data)
case fieldTypeD:
if invoice.Description != nil {
// We skip the field if we have already seen a
// supported one.
continue
}
invoice.Description, err = parseDescription(base32Data)
case fieldTypeN:
if invoice.Destination != nil {
// We skip the field if we have already seen a
// supported one.
continue
}
invoice.Destination, err = parseDestination(base32Data)
case fieldTypeH:
if invoice.DescriptionHash != nil {
// We skip the field if we have already seen a
// supported one.
continue
}
invoice.DescriptionHash, err = parse32Bytes(base32Data)
case fieldTypeX:
if invoice.expiry != nil {
// We skip the field if we have already seen a
// supported one.
continue
}
invoice.expiry, err = parseExpiry(base32Data)
case fieldTypeC:
if invoice.minFinalCLTVExpiry != nil {
// We skip the field if we have already seen a
// supported one.
continue
}
invoice.minFinalCLTVExpiry, err = parseMinFinalCLTVExpiry(base32Data)
case fieldTypeF:
if invoice.FallbackAddr != nil {
// We skip the field if we have already seen a
// supported one.
continue
}
invoice.FallbackAddr, err = parseFallbackAddr(base32Data, net)
case fieldTypeR:
// An `r` field can be included in an invoice multiple
// times, so we won't skip it if we have already seen
// one.
routeHint, err := parseRouteHint(base32Data)
if err != nil {
return err
}
invoice.RouteHints = append(invoice.RouteHints, routeHint)
case fieldType9:
if invoice.Features != nil {
// We skip the field if we have already seen a
// supported one.
continue
}
invoice.Features, err = parseFeatures(base32Data)
default:
// Ignore unknown type.
}
// Check if there was an error from parsing any of the tagged
// fields and return it.
if err != nil {
return err
}
}
return nil
}
// parseFieldDataLength converts the two byte slice into a uint16.
func parseFieldDataLength(data []byte) (uint16, error) {
if len(data) != 2 {
return 0, fmt.Errorf("data length must be 2 bytes, was %d",
len(data))
}
return uint16(data[0])<<5 | uint16(data[1]), nil
}
// parse32Bytes converts a 256-bit value (encoded in base32) to *[32]byte. This
// can be used for payment hashes, description hashes, payment addresses, etc.
func parse32Bytes(data []byte) (*[32]byte, error) {
var paymentHash [32]byte
// As BOLT-11 states, a reader must skip over the 32-byte fields if
// it does not have a length of 52, so avoid returning an error.
if len(data) != hashBase32Len {
return nil, nil
}
hash, err := bech32.ConvertBits(data, 5, 8, false)
if err != nil {
return nil, err
}
copy(paymentHash[:], hash)
return &paymentHash, nil
}
// parseDescription converts the data (encoded in base32) into a string to use
// as the description.
func parseDescription(data []byte) (*string, error) {
base256Data, err := bech32.ConvertBits(data, 5, 8, false)
if err != nil {
return nil, err
}
description := string(base256Data)
return &description, nil
}
// parseDestination converts the data (encoded in base32) into a 33-byte public
// key of the payee node.
func parseDestination(data []byte) (*btcec.PublicKey, error) {
// As BOLT-11 states, a reader must skip over the destination field
// if it does not have a length of 53, so avoid returning an error.
if len(data) != pubKeyBase32Len {
return nil, nil
}
base256Data, err := bech32.ConvertBits(data, 5, 8, false)
if err != nil {
return nil, err
}
return btcec.ParsePubKey(base256Data)
}
// parseExpiry converts the data (encoded in base32) into the expiry time.
func parseExpiry(data []byte) (*time.Duration, error) {
expiry, err := base32ToUint64(data)
if err != nil {
return nil, err
}
duration := time.Duration(expiry) * time.Second
return &duration, nil
}
// parseMinFinalCLTVExpiry converts the data (encoded in base32) into a uint64
// to use as the minFinalCLTVExpiry.
func parseMinFinalCLTVExpiry(data []byte) (*uint64, error) {
expiry, err := base32ToUint64(data)
if err != nil {
return nil, err
}
return &expiry, nil
}
// parseFallbackAddr converts the data (encoded in base32) into a fallback
// on-chain address.
func parseFallbackAddr(data []byte, net *chaincfg.Params) (btcutil.Address, error) {
// Checks if the data is empty or contains a version without an address.
if len(data) < 2 {
return nil, fmt.Errorf("empty fallback address field")
}
var addr btcutil.Address
version := data[0]
switch version {
case 0:
witness, err := bech32.ConvertBits(data[1:], 5, 8, false)
if err != nil {
return nil, err
}
switch len(witness) {
case 20:
addr, err = btcutil.NewAddressWitnessPubKeyHash(witness, net)
case 32:
addr, err = btcutil.NewAddressWitnessScriptHash(witness, net)
default:
return nil, fmt.Errorf("unknown witness program length %d",
len(witness))
}
if err != nil {
return nil, err
}
case 17:
pubKeyHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
if err != nil {
return nil, err
}
addr, err = btcutil.NewAddressPubKeyHash(pubKeyHash, net)
if err != nil {
return nil, err
}
case 18:
scriptHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
if err != nil {
return nil, err
}
addr, err = btcutil.NewAddressScriptHashFromHash(scriptHash, net)
if err != nil {
return nil, err
}
default:
// Ignore unknown version.
}
return addr, nil
}
// parseRouteHint converts the data (encoded in base32) into an array containing
// one or more routing hop hints that represent a single route hint.
func parseRouteHint(data []byte) ([]HopHint, error) {
base256Data, err := bech32.ConvertBits(data, 5, 8, false)
if err != nil {
return nil, err
}
// Check that base256Data is a multiple of hopHintLen.
if len(base256Data)%hopHintLen != 0 {
return nil, fmt.Errorf("expected length multiple of %d bytes, "+
"got %d", hopHintLen, len(base256Data))
}
var routeHint []HopHint
for len(base256Data) > 0 {
hopHint := HopHint{}
hopHint.NodeID, err = btcec.ParsePubKey(base256Data[:33])
if err != nil {
return nil, err
}
hopHint.ChannelID = binary.BigEndian.Uint64(base256Data[33:41])
hopHint.FeeBaseMSat = binary.BigEndian.Uint32(base256Data[41:45])
hopHint.FeeProportionalMillionths = binary.BigEndian.Uint32(base256Data[45:49])
hopHint.CLTVExpiryDelta = binary.BigEndian.Uint16(base256Data[49:51])
routeHint = append(routeHint, hopHint)
base256Data = base256Data[51:]
}
return routeHint, nil
}
// parseFeatures decodes any feature bits directly from the base32
// representation.
func parseFeatures(data []byte) (*lnwire.FeatureVector, error) {
rawFeatures := lnwire.NewRawFeatureVector()
err := rawFeatures.DecodeBase32(bytes.NewReader(data), len(data))
if err != nil {
return nil, err
}
return lnwire.NewFeatureVector(rawFeatures, lnwire.Features), nil
}
// base32ToUint64 converts a base32 encoded number to uint64.
func base32ToUint64(data []byte) (uint64, error) {
// Maximum that fits in uint64 is ceil(64 / 5) = 12 groups.
if len(data) > 13 {
return 0, fmt.Errorf("cannot parse data of length %d as uint64",
len(data))
}
val := uint64(0)
for i := 0; i < len(data); i++ {
val = val<<5 | uint64(data[i])
}
return val, nil
}
package zpay32
import "github.com/btcsuite/btcd/btcec/v2"
const (
// DefaultFinalCLTVDelta is the default value to be used as the final
// CLTV delta for a route if one is unspecified.
DefaultFinalCLTVDelta = 9
)
// HopHint is a routing hint that contains the minimum information of a channel
// required for an intermediate hop in a route to forward the payment to the
// next. This should be ideally used for private channels, since they are not
// publicly advertised to the network for routing.
type HopHint struct {
// NodeID is the public key of the node at the start of the channel.
NodeID *btcec.PublicKey
// ChannelID is the unique identifier of the channel.
ChannelID uint64
// FeeBaseMSat is the base fee of the channel in millisatoshis.
FeeBaseMSat uint32
// FeeProportionalMillionths is the fee rate, in millionths of a
// satoshi, for every satoshi sent through the channel.
FeeProportionalMillionths uint32
// CLTVExpiryDelta is the time-lock delta of the channel.
CLTVExpiryDelta uint16
}
// Copy returns a deep copy of the hop hint.
func (h HopHint) Copy() HopHint {
nodeID := *h.NodeID
return HopHint{
NodeID: &nodeID,
ChannelID: h.ChannelID,
FeeBaseMSat: h.FeeBaseMSat,
FeeProportionalMillionths: h.FeeProportionalMillionths,
CLTVExpiryDelta: h.CLTVExpiryDelta,
}
}
package zpay32
import (
"errors"
"fmt"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
)
const (
// mSatPerBtc is the number of millisatoshis in 1 BTC.
mSatPerBtc = 100000000000
// signatureBase32Len is the number of 5-bit groups needed to encode
// the 512 bit signature + 8 bit recovery ID.
signatureBase32Len = 104
// timestampBase32Len is the number of 5-bit groups needed to encode
// the 35-bit timestamp.
timestampBase32Len = 7
// hashBase32Len is the number of 5-bit groups needed to encode a
// 256-bit hash. Note that the last group will be padded with zeroes.
hashBase32Len = 52
// pubKeyBase32Len is the number of 5-bit groups needed to encode a
// 33-byte compressed pubkey. Note that the last group will be padded
// with zeroes.
pubKeyBase32Len = 53
// hopHintLen is the number of bytes needed to encode the hop hint of a
// single private route.
hopHintLen = 51
// The following byte values correspond to the supported field types.
// The field name is the character representing that 5-bit value in the
// bech32 string.
// fieldTypeP is the field containing the payment hash.
fieldTypeP = 1
// fieldTypeD contains a short description of the payment.
fieldTypeD = 13
// fieldTypeN contains the pubkey of the target node.
fieldTypeN = 19
// fieldTypeH contains the hash of a description of the payment.
fieldTypeH = 23
// fieldTypeX contains the expiry in seconds of the invoice.
fieldTypeX = 6
// fieldTypeF contains a fallback on-chain address.
fieldTypeF = 9
// fieldTypeR contains extra routing information.
fieldTypeR = 3
// fieldTypeC contains an optional requested final CLTV delta.
fieldTypeC = 24
// fieldType9 contains one or more bytes for signaling features
// supported or required by the receiver.
fieldType9 = 5
// fieldTypeS contains a 32-byte payment address, which is a nonce
// included in the final hop's payload to prevent intermediaries from
// probing the recipient.
fieldTypeS = 16
// maxInvoiceLength is the maximum total length an invoice can have.
// This is chosen to be the maximum number of bytes that can fit into a
// single QR code: https://en.wikipedia.org/wiki/QR_code#Storage
maxInvoiceLength = 7089
// DefaultInvoiceExpiry is the default expiry duration from the creation
// timestamp if expiry is set to zero.
DefaultInvoiceExpiry = time.Hour
)
var (
// ErrInvoiceTooLarge is returned when an invoice exceeds
// maxInvoiceLength.
ErrInvoiceTooLarge = errors.New("invoice is too large")
// ErrInvalidFieldLength is returned when a tagged field was specified
// with a length larger than the left over bytes of the data field.
ErrInvalidFieldLength = errors.New("invalid field length")
// ErrBrokenTaggedField is returned when the last tagged field is
// incorrectly formatted and doesn't have enough bytes to be read.
ErrBrokenTaggedField = errors.New("last tagged field is broken")
)
// MessageSigner is passed to the Encode method to provide a signature
// corresponding to the node's pubkey.
type MessageSigner struct {
// SignCompact signs the passed hash with the node's privkey. The
// returned signature should be 65 bytes, where the last 64 are the
// compact signature, and the first one is a header byte. This is the
// format returned by ecdsa.SignCompact.
SignCompact func(hash []byte) ([]byte, error)
}
// Invoice represents a decoded invoice, or to-be-encoded invoice. Some of the
// fields are optional, and will only be non-nil if the invoice this was parsed
// from contains that field. When encoding, only the non-nil fields will be
// added to the encoded invoice.
type Invoice struct {
// Net specifies what network this Lightning invoice is meant for.
Net *chaincfg.Params
// MilliSat specifies the amount of this invoice in millisatoshi.
// Optional.
MilliSat *lnwire.MilliSatoshi
// Timestamp specifies the time this invoice was created.
// Mandatory
Timestamp time.Time
// PaymentHash is the payment hash to be used for a payment to this
// invoice.
PaymentHash *[32]byte
// PaymentAddr is the payment address to be used by payments to prevent
// probing of the destination.
PaymentAddr *[32]byte
// Destination is the public key of the target node. This will always
// be set after decoding, and can optionally be set before encoding to
// include the pubkey as an 'n' field. If this is not set before
// encoding then the destination pubkey won't be added as an 'n' field,
// and the pubkey will be extracted from the signature during decoding.
Destination *btcec.PublicKey
// minFinalCLTVExpiry is the value that the creator of the invoice
// expects to be used for the CLTV expiry of the HTLC extended to it in
// the last hop.
//
// NOTE: This value is optional, and should be set to nil if the
// invoice creator doesn't have a strong requirement on the CLTV expiry
// of the final HTLC extended to it.
//
// This field is un-exported and can only be read by the
// MinFinalCLTVExpiry() method. By forcing callers to read via this
// method, we can easily enforce the default if not specified.
minFinalCLTVExpiry *uint64
// Description is a short description of the purpose of this invoice.
// Optional. Non-nil iff DescriptionHash is nil.
Description *string
// DescriptionHash is the SHA256 hash of a description of the purpose of
// this invoice.
// Optional. Non-nil iff Description is nil.
DescriptionHash *[32]byte
// expiry specifies the timespan this invoice will be valid.
// Optional. If not set, a default expiry of 60 min will be implied.
//
// This field is unexported and can be read by the Expiry() method. This
// method makes sure the default expiry time is returned in case the
// field is not set.
expiry *time.Duration
// FallbackAddr is an on-chain address that can be used for payment in
// case the Lightning payment fails.
// Optional.
FallbackAddr btcutil.Address
// RouteHints represents one or more different route hints. Each route
// hint can be individually used to reach the destination. These usually
// represent private routes.
//
// NOTE: This is optional.
RouteHints [][]HopHint
// Features represents an optional field used to signal optional or
// required support for features by the receiver.
Features *lnwire.FeatureVector
}
// Amount is a functional option that allows callers of NewInvoice to set the
// amount in millisatoshis that the Invoice should encode.
func Amount(milliSat lnwire.MilliSatoshi) func(*Invoice) {
return func(i *Invoice) {
i.MilliSat = &milliSat
}
}
// Destination is a functional option that allows callers of NewInvoice to
// explicitly set the pubkey of the Invoice's destination node.
func Destination(destination *btcec.PublicKey) func(*Invoice) {
return func(i *Invoice) {
i.Destination = destination
}
}
// Description is a functional option that allows callers of NewInvoice to set
// the payment description of the created Invoice.
//
// NOTE: Must be used if and only if DescriptionHash is not used.
func Description(description string) func(*Invoice) {
return func(i *Invoice) {
i.Description = &description
}
}
// CLTVExpiry is an optional value which allows the receiver of the payment to
// specify the delta between the current height and the HTLC extended to the
// receiver.
func CLTVExpiry(delta uint64) func(*Invoice) {
return func(i *Invoice) {
i.minFinalCLTVExpiry = &delta
}
}
// DescriptionHash is a functional option that allows callers of NewInvoice to
// set the payment description hash of the created Invoice.
//
// NOTE: Must be used if and only if Description is not used.
func DescriptionHash(descriptionHash [32]byte) func(*Invoice) {
return func(i *Invoice) {
i.DescriptionHash = &descriptionHash
}
}
// Expiry is a functional option that allows callers of NewInvoice to set the
// expiry of the created Invoice. If not set, a default expiry of 60 min will
// be implied.
func Expiry(expiry time.Duration) func(*Invoice) {
return func(i *Invoice) {
i.expiry = &expiry
}
}
// FallbackAddr is a functional option that allows callers of NewInvoice to set
// the Invoice's fallback on-chain address that can be used for payment in case
// the Lightning payment fails
func FallbackAddr(fallbackAddr btcutil.Address) func(*Invoice) {
return func(i *Invoice) {
i.FallbackAddr = fallbackAddr
}
}
// RouteHint is a functional option that allows callers of NewInvoice to add
// one or more hop hints that represent a private route to the destination.
func RouteHint(routeHint []HopHint) func(*Invoice) {
return func(i *Invoice) {
i.RouteHints = append(i.RouteHints, routeHint)
}
}
// Features is a functional option that allows callers of NewInvoice to set the
// desired feature bits that are advertised on the invoice. If this option is
// not used, an empty feature vector will automatically be populated.
func Features(features *lnwire.FeatureVector) func(*Invoice) {
return func(i *Invoice) {
i.Features = features
}
}
// PaymentAddr is a functional option that allows callers of NewInvoice to set
// the desired payment address that is advertised on the invoice.
func PaymentAddr(addr [32]byte) func(*Invoice) {
return func(i *Invoice) {
i.PaymentAddr = &addr
}
}
// NewInvoice creates a new Invoice object. The last parameter is a set of
// variadic arguments for setting optional fields of the invoice.
//
// NOTE: Either Description or DescriptionHash must be provided for the Invoice
// to be considered valid.
func NewInvoice(net *chaincfg.Params, paymentHash [32]byte,
timestamp time.Time, options ...func(*Invoice)) (*Invoice, error) {
invoice := &Invoice{
Net: net,
PaymentHash: &paymentHash,
Timestamp: timestamp,
}
for _, option := range options {
option(invoice)
}
// If no features were set, we'll populate an empty feature vector.
if invoice.Features == nil {
invoice.Features = lnwire.NewFeatureVector(
nil, lnwire.Features,
)
}
if err := validateInvoice(invoice); err != nil {
return nil, err
}
return invoice, nil
}
// Expiry returns the expiry time for this invoice. If expiry time is not set
// explicitly, the default 3600 second expiry will be returned.
func (invoice *Invoice) Expiry() time.Duration {
if invoice.expiry != nil {
return *invoice.expiry
}
// If no expiry is set for this invoice, default is 3600 seconds.
return DefaultInvoiceExpiry
}
// MinFinalCLTVExpiry returns the minimum final CLTV expiry delta as specified
// by the creator of the invoice. This value specifies the delta between the
// current height and the expiry height of the HTLC extended in the last hop.
func (invoice *Invoice) MinFinalCLTVExpiry() uint64 {
if invoice.minFinalCLTVExpiry != nil {
return *invoice.minFinalCLTVExpiry
}
return DefaultFinalCLTVDelta
}
// validateInvoice does a sanity check of the provided Invoice, making sure it
// has all the necessary fields set for it to be considered valid by BOLT-0011.
func validateInvoice(invoice *Invoice) error {
// The net must be set.
if invoice.Net == nil {
return fmt.Errorf("net params not set")
}
// The invoice must contain a payment hash.
if invoice.PaymentHash == nil {
return fmt.Errorf("no payment hash found")
}
// Either Description or DescriptionHash must be set, not both.
if invoice.Description != nil && invoice.DescriptionHash != nil {
return fmt.Errorf("both description and description hash set")
}
if invoice.Description == nil && invoice.DescriptionHash == nil {
return fmt.Errorf("neither description nor description hash set")
}
// Check that we support the field lengths.
if len(invoice.PaymentHash) != 32 {
return fmt.Errorf("unsupported payment hash length: %d",
len(invoice.PaymentHash))
}
if invoice.DescriptionHash != nil && len(invoice.DescriptionHash) != 32 {
return fmt.Errorf("unsupported description hash length: %d",
len(invoice.DescriptionHash))
}
if invoice.Destination != nil &&
len(invoice.Destination.SerializeCompressed()) != 33 {
return fmt.Errorf("unsupported pubkey length: %d",
len(invoice.Destination.SerializeCompressed()))
}
// Ensure that all invoices have feature vectors.
if invoice.Features == nil {
return fmt.Errorf("missing feature vector")
}
return nil
}
package migtest
import (
"fmt"
"os"
"testing"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/stretchr/testify/require"
)
// MakeDB creates a new instance of the ChannelDB for testing purposes.
func MakeDB(t testing.TB) (kvdb.Backend, error) {
// Create temporary database for mission control.
file, err := os.CreateTemp("", "*.db")
if err != nil {
return nil, err
}
dbPath := file.Name()
t.Cleanup(func() {
require.NoError(t, file.Close())
require.NoError(t, os.Remove(dbPath))
})
db, err := kvdb.Open(
kvdb.BoltBackendName, dbPath, true, kvdb.DefaultDBTimeout,
false,
)
if err != nil {
return nil, err
}
t.Cleanup(func() {
require.NoError(t, db.Close())
})
return db, nil
}
// ApplyMigration is a helper test function that encapsulates the general steps
// which are needed to properly check the result of applying migration function.
func ApplyMigration(t *testing.T,
beforeMigration, afterMigration, migrationFunc func(tx kvdb.RwTx) error,
shouldFail bool) {
t.Helper()
cdb, err := MakeDB(t)
if err != nil {
t.Fatal(err)
}
// beforeMigration usually used for populating the database
// with test data.
err = kvdb.Update(cdb, beforeMigration, func() {})
if err != nil {
t.Fatal(err)
}
defer func() {
t.Helper()
if r := recover(); r != nil {
err = newError(r)
}
if err == nil && shouldFail {
t.Fatal("error wasn't received on migration stage")
} else if err != nil && !shouldFail {
t.Fatalf("error was received on migration stage: %v", err)
}
// afterMigration usually used for checking the database state and
// throwing the error if something went wrong.
err = kvdb.Update(cdb, afterMigration, func() {})
if err != nil {
t.Fatal(err)
}
}()
// Apply migration.
err = kvdb.Update(cdb, migrationFunc, func() {})
if err != nil {
t.Logf("migration error: %v", err)
}
}
// ApplyMigrationWithDB is a helper test function that encapsulates the general
// steps which are needed to properly check the result of applying migration
// function. This function differs from ApplyMigration as it requires the
// supplied migration functions to take a db instance and construct their own
// database transactions.
func ApplyMigrationWithDB(t testing.TB, beforeMigration,
afterMigration func(db kvdb.Backend) error,
migrationFunc func(db kvdb.Backend) error, shouldFail bool) {
t.Helper()
cdb, err := MakeDB(t)
if err != nil {
t.Fatal(err)
}
// beforeMigration usually used for populating the database
// with test data.
if err := beforeMigration(cdb); err != nil {
t.Fatalf("beforeMigration error: %v", err)
}
// Apply migration.
err = migrationFunc(cdb)
if shouldFail {
require.Error(t, err)
} else {
require.NoError(t, err)
}
// If there's no afterMigration, exit here.
if afterMigration == nil {
return
}
// afterMigration usually used for checking the database state
// and throwing the error if something went wrong.
if err := afterMigration(cdb); err != nil {
t.Fatalf("afterMigration error: %v", err)
}
}
func newError(e interface{}) error {
var err error
switch e := e.(type) {
case error:
err = e
default:
err = fmt.Errorf("%v", e)
}
return err
}
package migtest
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"strings"
"github.com/lightningnetwork/lnd/kvdb"
)
// DumpDB dumps go code describing the contents of the database to stdout. This
// function is only intended for use during development.
//
// Example output:
//
// map[string]interface{}{
// hex("1234"): map[string]interface{}{
// "human-readable": hex("102030"),
// hex("1111"): hex("5783492373"),
// },
// }
func DumpDB(tx kvdb.RTx, rootKey []byte) error {
bucket := tx.ReadBucket(rootKey)
if bucket == nil {
return fmt.Errorf("bucket %v not found", string(rootKey))
}
return dumpBucket(bucket)
}
func dumpBucket(bucket kvdb.RBucket) error {
fmt.Printf("map[string]interface{} {\n")
err := bucket.ForEach(func(k, v []byte) error {
key := toString(k)
fmt.Printf("%v: ", key)
subBucket := bucket.NestedReadBucket(k)
if subBucket != nil {
err := dumpBucket(subBucket)
if err != nil {
return err
}
} else {
fmt.Print(toHex(v))
}
fmt.Printf(",\n")
return nil
})
if err != nil {
return err
}
fmt.Printf("}")
return nil
}
// RestoreDB primes the database with the given data set.
func RestoreDB(tx kvdb.RwTx, rootKey []byte, data map[string]interface{}) error {
bucket, err := tx.CreateTopLevelBucket(rootKey)
if err != nil {
return err
}
return restoreDB(bucket, data)
}
func restoreDB(bucket kvdb.RwBucket, data map[string]interface{}) error {
for k, v := range data {
key := []byte(k)
switch value := v.(type) {
// Key contains value.
case string:
err := bucket.Put(key, []byte(value))
if err != nil {
return err
}
// Key contains a sub-bucket.
case map[string]interface{}:
subBucket, err := bucket.CreateBucket(key)
if err != nil {
return err
}
if err := restoreDB(subBucket, value); err != nil {
return err
}
default:
return errors.New("invalid type")
}
}
return nil
}
// VerifyDB verifies the database against the given data set.
func VerifyDB(tx kvdb.RTx, rootKey []byte, data map[string]interface{}) error {
bucket := tx.ReadBucket(rootKey)
if bucket == nil {
return fmt.Errorf("bucket %v not found", string(rootKey))
}
return verifyDB(bucket, data)
}
func verifyDB(bucket kvdb.RBucket, data map[string]interface{}) error {
for k, v := range data {
key := []byte(k)
switch value := v.(type) {
// Key contains value.
case string:
expectedValue := []byte(value)
dbValue := bucket.Get(key)
if !bytes.Equal(dbValue, expectedValue) {
return errors.New("value mismatch")
}
// Key contains a sub-bucket.
case map[string]interface{}:
subBucket := bucket.NestedReadBucket(key)
if subBucket == nil {
return fmt.Errorf("bucket %v not found", k)
}
err := verifyDB(subBucket, value)
if err != nil {
return err
}
default:
return errors.New("invalid type")
}
}
keyCount := 0
err := bucket.ForEach(func(k, v []byte) error {
keyCount++
return nil
})
if err != nil {
return err
}
if keyCount != len(data) {
return errors.New("unexpected keys in database")
}
return nil
}
func toHex(v []byte) string {
if len(v) == 0 {
return "nil"
}
return "hex(\"" + hex.EncodeToString(v) + "\")"
}
func toString(v []byte) string {
readableChars := "abcdefghijklmnopqrstuvwxyz0123456789-"
for _, c := range v {
if !strings.Contains(readableChars, string(c)) {
return toHex(v)
}
}
return "\"" + string(v) + "\""
}
// Hex is a test helper function to convert readable hex arrays to raw byte
// strings.
func Hex(value string) string {
b, err := hex.DecodeString(value)
if err != nil {
panic(err)
}
return string(b)
}
package channeldb
import (
"bytes"
"errors"
"fmt"
"io"
"math"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
// HTLCAttemptInfo contains static information about a specific HTLC attempt
// for a payment. This information is used by the router to handle any errors
// coming back after an attempt is made, and to query the switch about the
// status of the attempt.
type HTLCAttemptInfo struct {
// AttemptID is the unique ID used for this attempt.
AttemptID uint64
// sessionKey is the raw bytes ephemeral key used for this attempt.
// These bytes are lazily read off disk to save ourselves the expensive
// EC operations used by btcec.PrivKeyFromBytes.
sessionKey [btcec.PrivKeyBytesLen]byte
// cachedSessionKey is our fully deserialized sesionKey. This value
// may be nil if the attempt has just been read from disk and its
// session key has not been used yet.
cachedSessionKey *btcec.PrivateKey
// Route is the route attempted to send the HTLC.
Route route.Route
// AttemptTime is the time at which this HTLC was attempted.
AttemptTime time.Time
// Hash is the hash used for this single HTLC attempt. For AMP payments
// this will differ across attempts, for non-AMP payments each attempt
// will use the same hash. This can be nil for older payment attempts,
// in which the payment's PaymentHash in the PaymentCreationInfo should
// be used.
Hash *lntypes.Hash
// onionBlob is the cached value for onion blob created from the sphinx
// construction.
onionBlob [lnwire.OnionPacketSize]byte
// circuit is the cached value for sphinx circuit.
circuit *sphinx.Circuit
}
// NewHtlcAttempt creates a htlc attempt.
func NewHtlcAttempt(attemptID uint64, sessionKey *btcec.PrivateKey,
route route.Route, attemptTime time.Time,
hash *lntypes.Hash) (*HTLCAttempt, error) {
var scratch [btcec.PrivKeyBytesLen]byte
copy(scratch[:], sessionKey.Serialize())
info := HTLCAttemptInfo{
AttemptID: attemptID,
sessionKey: scratch,
cachedSessionKey: sessionKey,
Route: route,
AttemptTime: attemptTime,
Hash: hash,
}
if err := info.attachOnionBlobAndCircuit(); err != nil {
return nil, err
}
return &HTLCAttempt{HTLCAttemptInfo: info}, nil
}
// SessionKey returns the ephemeral key used for a htlc attempt. This function
// performs expensive ec-ops to obtain the session key if it is not cached.
func (h *HTLCAttemptInfo) SessionKey() *btcec.PrivateKey {
if h.cachedSessionKey == nil {
h.cachedSessionKey, _ = btcec.PrivKeyFromBytes(
h.sessionKey[:],
)
}
return h.cachedSessionKey
}
// OnionBlob returns the onion blob created from the sphinx construction.
func (h *HTLCAttemptInfo) OnionBlob() ([lnwire.OnionPacketSize]byte, error) {
var zeroBytes [lnwire.OnionPacketSize]byte
if h.onionBlob == zeroBytes {
if err := h.attachOnionBlobAndCircuit(); err != nil {
return zeroBytes, err
}
}
return h.onionBlob, nil
}
// Circuit returns the sphinx circuit for this attempt.
func (h *HTLCAttemptInfo) Circuit() (*sphinx.Circuit, error) {
if h.circuit == nil {
if err := h.attachOnionBlobAndCircuit(); err != nil {
return nil, err
}
}
return h.circuit, nil
}
// attachOnionBlobAndCircuit creates a sphinx packet and caches the onion blob
// and circuit for this attempt.
func (h *HTLCAttemptInfo) attachOnionBlobAndCircuit() error {
onionBlob, circuit, err := generateSphinxPacket(
&h.Route, h.Hash[:], h.SessionKey(),
)
if err != nil {
return err
}
copy(h.onionBlob[:], onionBlob)
h.circuit = circuit
return nil
}
// HTLCAttempt contains information about a specific HTLC attempt for a given
// payment. It contains the HTLCAttemptInfo used to send the HTLC, as well
// as a timestamp and any known outcome of the attempt.
type HTLCAttempt struct {
HTLCAttemptInfo
// Settle is the preimage of a successful payment. This serves as a
// proof of payment. It will only be non-nil for settled payments.
//
// NOTE: Can be nil if payment is not settled.
Settle *HTLCSettleInfo
// Fail is a failure reason code indicating the reason the payment
// failed. It is only non-nil for failed payments.
//
// NOTE: Can be nil if payment is not failed.
Failure *HTLCFailInfo
}
// HTLCSettleInfo encapsulates the information that augments an HTLCAttempt in
// the event that the HTLC is successful.
type HTLCSettleInfo struct {
// Preimage is the preimage of a successful HTLC. This serves as a proof
// of payment.
Preimage lntypes.Preimage
// SettleTime is the time at which this HTLC was settled.
SettleTime time.Time
}
// HTLCFailReason is the reason an htlc failed.
type HTLCFailReason byte
const (
// HTLCFailUnknown is recorded for htlcs that failed with an unknown
// reason.
HTLCFailUnknown HTLCFailReason = 0
// HTLCFailUnknown is recorded for htlcs that had a failure message that
// couldn't be decrypted.
HTLCFailUnreadable HTLCFailReason = 1
// HTLCFailInternal is recorded for htlcs that failed because of an
// internal error.
HTLCFailInternal HTLCFailReason = 2
// HTLCFailMessage is recorded for htlcs that failed with a network
// failure message.
HTLCFailMessage HTLCFailReason = 3
)
// HTLCFailInfo encapsulates the information that augments an HTLCAttempt in the
// event that the HTLC fails.
type HTLCFailInfo struct {
// FailTime is the time at which this HTLC was failed.
FailTime time.Time
// Message is the wire message that failed this HTLC. This field will be
// populated when the failure reason is HTLCFailMessage.
Message lnwire.FailureMessage
// Reason is the failure reason for this HTLC.
Reason HTLCFailReason
// The position in the path of the intermediate or final node that
// generated the failure message. Position zero is the sender node. This
// field will be populated when the failure reason is either
// HTLCFailMessage or HTLCFailUnknown.
FailureSourceIndex uint32
}
// MPPaymentState wraps a series of info needed for a given payment, which is
// used by both MPP and AMP. This is a memory representation of the payment's
// current state and is updated whenever the payment is read from disk.
type MPPaymentState struct {
// NumAttemptsInFlight specifies the number of HTLCs the payment is
// waiting results for.
NumAttemptsInFlight int
// RemainingAmt specifies how much more money to be sent.
RemainingAmt lnwire.MilliSatoshi
// FeesPaid specifies the total fees paid so far that can be used to
// calculate remaining fee budget.
FeesPaid lnwire.MilliSatoshi
// HasSettledHTLC is true if at least one of the payment's HTLCs is
// settled.
HasSettledHTLC bool
// PaymentFailed is true if the payment has been marked as failed with
// a reason.
PaymentFailed bool
}
// MPPayment is a wrapper around a payment's PaymentCreationInfo and
// HTLCAttempts. All payments will have the PaymentCreationInfo set, any
// HTLCs made in attempts to be completed will populated in the HTLCs slice.
// Each populated HTLCAttempt represents an attempted HTLC, each of which may
// have the associated Settle or Fail struct populated if the HTLC is no longer
// in-flight.
type MPPayment struct {
// SequenceNum is a unique identifier used to sort the payments in
// order of creation.
SequenceNum uint64
// Info holds all static information about this payment, and is
// populated when the payment is initiated.
Info *PaymentCreationInfo
// HTLCs holds the information about individual HTLCs that we send in
// order to make the payment.
HTLCs []HTLCAttempt
// FailureReason is the failure reason code indicating the reason the
// payment failed.
//
// NOTE: Will only be set once the daemon has given up on the payment
// altogether.
FailureReason *FailureReason
// Status is the current PaymentStatus of this payment.
Status PaymentStatus
// State is the current state of the payment that holds a number of key
// insights and is used to determine what to do on each payment loop
// iteration.
State *MPPaymentState
}
// Terminated returns a bool to specify whether the payment is in a terminal
// state.
func (m *MPPayment) Terminated() bool {
// If the payment is in terminal state, it cannot be updated.
return m.Status.updatable() != nil
}
// TerminalInfo returns any HTLC settle info recorded. If no settle info is
// recorded, any payment level failure will be returned. If neither a settle
// nor a failure is recorded, both return values will be nil.
func (m *MPPayment) TerminalInfo() (*HTLCAttempt, *FailureReason) {
for _, h := range m.HTLCs {
if h.Settle != nil {
return &h, nil
}
}
return nil, m.FailureReason
}
// SentAmt returns the sum of sent amount and fees for HTLCs that are either
// settled or still in flight.
func (m *MPPayment) SentAmt() (lnwire.MilliSatoshi, lnwire.MilliSatoshi) {
var sent, fees lnwire.MilliSatoshi
for _, h := range m.HTLCs {
if h.Failure != nil {
continue
}
// The attempt was not failed, meaning the amount was
// potentially sent to the receiver.
sent += h.Route.ReceiverAmt()
fees += h.Route.TotalFees()
}
return sent, fees
}
// InFlightHTLCs returns the HTLCs that are still in-flight, meaning they have
// not been settled or failed.
func (m *MPPayment) InFlightHTLCs() []HTLCAttempt {
var inflights []HTLCAttempt
for _, h := range m.HTLCs {
if h.Settle != nil || h.Failure != nil {
continue
}
inflights = append(inflights, h)
}
return inflights
}
// GetAttempt returns the specified htlc attempt on the payment.
func (m *MPPayment) GetAttempt(id uint64) (*HTLCAttempt, error) {
// TODO(yy): iteration can be slow, make it into a tree or use BS.
for _, htlc := range m.HTLCs {
htlc := htlc
if htlc.AttemptID == id {
return &htlc, nil
}
}
return nil, errors.New("htlc attempt not found on payment")
}
// Registrable returns an error to specify whether adding more HTLCs to the
// payment with its current status is allowed. A payment can accept new HTLC
// registrations when it's newly created, or none of its HTLCs is in a terminal
// state.
func (m *MPPayment) Registrable() error {
// If updating the payment is not allowed, we can't register new HTLCs.
// Otherwise, the status must be either `StatusInitiated` or
// `StatusInFlight`.
if err := m.Status.updatable(); err != nil {
return err
}
// Exit early if this is not inflight.
if m.Status != StatusInFlight {
return nil
}
// There are still inflight HTLCs and we need to check whether there
// are settled HTLCs or the payment is failed. If we already have
// settled HTLCs, we won't allow adding more HTLCs.
if m.State.HasSettledHTLC {
return ErrPaymentPendingSettled
}
// If the payment is already failed, we won't allow adding more HTLCs.
if m.State.PaymentFailed {
return ErrPaymentPendingFailed
}
// Otherwise we can add more HTLCs.
return nil
}
// setState creates and attaches a new MPPaymentState to the payment. It also
// updates the payment's status based on its current state.
func (m *MPPayment) setState() error {
// Fetch the total amount and fees that has already been sent in
// settled and still in-flight shards.
sentAmt, fees := m.SentAmt()
// Sanity check we haven't sent a value larger than the payment amount.
totalAmt := m.Info.Value
if sentAmt > totalAmt {
return fmt.Errorf("%w: sent=%v, total=%v", ErrSentExceedsTotal,
sentAmt, totalAmt)
}
// Get any terminal info for this payment.
settle, failure := m.TerminalInfo()
// Now determine the payment's status.
status, err := decidePaymentStatus(m.HTLCs, m.FailureReason)
if err != nil {
return err
}
// Update the payment state and status.
m.State = &MPPaymentState{
NumAttemptsInFlight: len(m.InFlightHTLCs()),
RemainingAmt: totalAmt - sentAmt,
FeesPaid: fees,
HasSettledHTLC: settle != nil,
PaymentFailed: failure != nil,
}
m.Status = status
return nil
}
// SetState calls the internal method setState. This is a temporary method
// to be used by the tests in routing. Once the tests are updated to use mocks,
// this method can be removed.
//
// TODO(yy): delete.
func (m *MPPayment) SetState() error {
return m.setState()
}
// NeedWaitAttempts decides whether we need to hold creating more HTLC attempts
// and wait for the results of the payment's inflight HTLCs. Return an error if
// the payment is in an unexpected state.
func (m *MPPayment) NeedWaitAttempts() (bool, error) {
// Check when the remainingAmt is not zero, which means we have more
// money to be sent.
if m.State.RemainingAmt != 0 {
switch m.Status {
// If the payment is newly created, no need to wait for HTLC
// results.
case StatusInitiated:
return false, nil
// If we have inflight HTLCs, we'll check if we have terminal
// states to decide if we need to wait.
case StatusInFlight:
// We still have money to send, and one of the HTLCs is
// settled. We'd stop sending money and wait for all
// inflight HTLC attempts to finish.
if m.State.HasSettledHTLC {
log.Warnf("payment=%v has remaining amount "+
"%v, yet at least one of its HTLCs is "+
"settled", m.Info.PaymentIdentifier,
m.State.RemainingAmt)
return true, nil
}
// The payment has a failure reason though we still
// have money to send, we'd stop sending money and wait
// for all inflight HTLC attempts to finish.
if m.State.PaymentFailed {
return true, nil
}
// Otherwise we don't need to wait for inflight HTLCs
// since we still have money to be sent.
return false, nil
// We need to send more money, yet the payment is already
// succeeded. Return an error in this case as the receiver is
// violating the protocol.
case StatusSucceeded:
return false, fmt.Errorf("%w: parts of the payment "+
"already succeeded but still have remaining "+
"amount %v", ErrPaymentInternal,
m.State.RemainingAmt)
// The payment is failed and we have no inflight HTLCs, no need
// to wait.
case StatusFailed:
return false, nil
// Unknown payment status.
default:
return false, fmt.Errorf("%w: %s",
ErrUnknownPaymentStatus, m.Status)
}
}
// Now we determine whether we need to wait when the remainingAmt is
// already zero.
switch m.Status {
// When the payment is newly created, yet the payment has no remaining
// amount, return an error.
case StatusInitiated:
return false, fmt.Errorf("%w: %v", ErrPaymentInternal, m.Status)
// If the payment is inflight, we must wait.
//
// NOTE: an edge case is when all HTLCs are failed while the payment is
// not failed we'd still be in this inflight state. However, since the
// remainingAmt is zero here, it means we cannot be in that state as
// otherwise the remainingAmt would not be zero.
case StatusInFlight:
return true, nil
// If the payment is already succeeded, no need to wait.
case StatusSucceeded:
return false, nil
// If the payment is already failed, yet the remaining amount is zero,
// return an error as this indicates an error state. We will only each
// this status when there are no inflight HTLCs and the payment is
// marked as failed with a reason, which means the remainingAmt must
// not be zero because our sentAmt is zero.
case StatusFailed:
return false, fmt.Errorf("%w: %v", ErrPaymentInternal, m.Status)
// Unknown payment status.
default:
return false, fmt.Errorf("%w: %s", ErrUnknownPaymentStatus,
m.Status)
}
}
// GetState returns the internal state of the payment.
func (m *MPPayment) GetState() *MPPaymentState {
return m.State
}
// Status returns the current status of the payment.
func (m *MPPayment) GetStatus() PaymentStatus {
return m.Status
}
// GetPayment returns all the HTLCs for this payment.
func (m *MPPayment) GetHTLCs() []HTLCAttempt {
return m.HTLCs
}
// AllowMoreAttempts is used to decide whether we can safely attempt more HTLCs
// for a given payment state. Return an error if the payment is in an
// unexpected state.
func (m *MPPayment) AllowMoreAttempts() (bool, error) {
// Now check whether the remainingAmt is zero or not. If we don't have
// any remainingAmt, no more HTLCs should be made.
if m.State.RemainingAmt == 0 {
// If the payment is newly created, yet we don't have any
// remainingAmt, return an error.
if m.Status == StatusInitiated {
return false, fmt.Errorf("%w: initiated payment has "+
"zero remainingAmt", ErrPaymentInternal)
}
// Otherwise, exit early since all other statuses with zero
// remainingAmt indicate no more HTLCs can be made.
return false, nil
}
// Otherwise, the remaining amount is not zero, we now decide whether
// to make more attempts based on the payment's current status.
//
// If at least one of the payment's attempts is settled, yet we haven't
// sent all the amount, it indicates something is wrong with the peer
// as the preimage is received. In this case, return an error state.
if m.Status == StatusSucceeded {
return false, fmt.Errorf("%w: payment already succeeded but "+
"still have remaining amount %v", ErrPaymentInternal,
m.State.RemainingAmt)
}
// Now check if we can register a new HTLC.
err := m.Registrable()
if err != nil {
log.Warnf("Payment(%v): cannot register HTLC attempt: %v, "+
"current status: %s", m.Info.PaymentIdentifier,
err, m.Status)
return false, nil
}
// Now we know we can register new HTLCs.
return true, nil
}
// serializeHTLCSettleInfo serializes the details of a settled htlc.
func serializeHTLCSettleInfo(w io.Writer, s *HTLCSettleInfo) error {
if _, err := w.Write(s.Preimage[:]); err != nil {
return err
}
if err := serializeTime(w, s.SettleTime); err != nil {
return err
}
return nil
}
// deserializeHTLCSettleInfo deserializes the details of a settled htlc.
func deserializeHTLCSettleInfo(r io.Reader) (*HTLCSettleInfo, error) {
s := &HTLCSettleInfo{}
if _, err := io.ReadFull(r, s.Preimage[:]); err != nil {
return nil, err
}
var err error
s.SettleTime, err = deserializeTime(r)
if err != nil {
return nil, err
}
return s, nil
}
// serializeHTLCFailInfo serializes the details of a failed htlc including the
// wire failure.
func serializeHTLCFailInfo(w io.Writer, f *HTLCFailInfo) error {
if err := serializeTime(w, f.FailTime); err != nil {
return err
}
// Write failure. If there is no failure message, write an empty
// byte slice.
var messageBytes bytes.Buffer
if f.Message != nil {
err := lnwire.EncodeFailureMessage(&messageBytes, f.Message, 0)
if err != nil {
return err
}
}
if err := wire.WriteVarBytes(w, 0, messageBytes.Bytes()); err != nil {
return err
}
return WriteElements(w, byte(f.Reason), f.FailureSourceIndex)
}
// deserializeHTLCFailInfo deserializes the details of a failed htlc including
// the wire failure.
func deserializeHTLCFailInfo(r io.Reader) (*HTLCFailInfo, error) {
f := &HTLCFailInfo{}
var err error
f.FailTime, err = deserializeTime(r)
if err != nil {
return nil, err
}
// Read failure.
failureBytes, err := wire.ReadVarBytes(
r, 0, math.MaxUint16, "failure",
)
if err != nil {
return nil, err
}
if len(failureBytes) > 0 {
f.Message, err = lnwire.DecodeFailureMessage(
bytes.NewReader(failureBytes), 0,
)
if err != nil {
return nil, err
}
}
var reason byte
err = ReadElements(r, &reason, &f.FailureSourceIndex)
if err != nil {
return nil, err
}
f.Reason = HTLCFailReason(reason)
return f, nil
}
// deserializeTime deserializes time as unix nanoseconds.
func deserializeTime(r io.Reader) (time.Time, error) {
var scratch [8]byte
if _, err := io.ReadFull(r, scratch[:]); err != nil {
return time.Time{}, err
}
// Convert to time.Time. Interpret unix nano time zero as a zero
// time.Time value.
unixNano := byteOrder.Uint64(scratch[:])
if unixNano == 0 {
return time.Time{}, nil
}
return time.Unix(0, int64(unixNano)), nil
}
// serializeTime serializes time as unix nanoseconds.
func serializeTime(w io.Writer, t time.Time) error {
var scratch [8]byte
// Convert to unix nano seconds, but only if time is non-zero. Calling
// UnixNano() on a zero time yields an undefined result.
var unixNano int64
if !t.IsZero() {
unixNano = t.UnixNano()
}
byteOrder.PutUint64(scratch[:], uint64(unixNano))
_, err := w.Write(scratch[:])
return err
}
// generateSphinxPacket generates then encodes a sphinx packet which encodes
// the onion route specified by the passed layer 3 route. The blob returned
// from this function can immediately be included within an HTLC add packet to
// be sent to the first hop within the route.
func generateSphinxPacket(rt *route.Route, paymentHash []byte,
sessionKey *btcec.PrivateKey) ([]byte, *sphinx.Circuit, error) {
// Now that we know we have an actual route, we'll map the route into a
// sphinx payment path which includes per-hop payloads for each hop
// that give each node within the route the necessary information
// (fees, CLTV value, etc.) to properly forward the payment.
sphinxPath, err := rt.ToSphinxPath()
if err != nil {
return nil, nil, err
}
log.Tracef("Constructed per-hop payloads for payment_hash=%x: %v",
paymentHash, lnutils.NewLogClosure(func() string {
path := make(
[]sphinx.OnionHop, sphinxPath.TrueRouteLength(),
)
for i := range path {
hopCopy := sphinxPath[i]
path[i] = hopCopy
}
return spew.Sdump(path)
}),
)
// Next generate the onion routing packet which allows us to perform
// privacy preserving source routing across the network.
sphinxPacket, err := sphinx.NewOnionPacket(
sphinxPath, sessionKey, paymentHash,
sphinx.DeterministicPacketFiller,
)
if err != nil {
return nil, nil, err
}
// Finally, encode Sphinx packet using its wire representation to be
// included within the HTLC add packet.
var onionBlob bytes.Buffer
if err := sphinxPacket.Encode(&onionBlob); err != nil {
return nil, nil, err
}
log.Tracef("Generated sphinx packet: %v",
lnutils.NewLogClosure(func() string {
// We make a copy of the ephemeral key and unset the
// internal curve here in order to keep the logs from
// getting noisy.
key := *sphinxPacket.EphemeralKey
packetCopy := *sphinxPacket
packetCopy.EphemeralKey = &key
return spew.Sdump(packetCopy)
}),
)
return onionBlob.Bytes(), &sphinx.Circuit{
SessionKey: sessionKey,
PaymentPath: sphinxPath.NodeKeys(),
}, nil
}
package channeldb
import (
"bytes"
"io"
"net"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/wire"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
// nodeInfoBucket stores metadata pertaining to nodes that we've had
// direct channel-based correspondence with. This bucket allows one to
// query for all open channels pertaining to the node by exploring each
// node's sub-bucket within the openChanBucket.
nodeInfoBucket = []byte("nib")
)
// LinkNode stores metadata related to node's that we have/had a direct
// channel open with. Information such as the Bitcoin network the node
// advertised, and its identity public key are also stored. Additionally, this
// struct and the bucket its stored within have store data similar to that of
// Bitcoin's addrmanager. The TCP address information stored within the struct
// can be used to establish persistent connections will all channel
// counterparties on daemon startup.
//
// TODO(roasbeef): also add current OnionKey plus rotation schedule?
// TODO(roasbeef): add bitfield for supported services
// - possibly add a wire.NetAddress type, type
type LinkNode struct {
// Network indicates the Bitcoin network that the LinkNode advertises
// for incoming channel creation.
Network wire.BitcoinNet
// IdentityPub is the node's current identity public key. Any
// channel/topology related information received by this node MUST be
// signed by this public key.
IdentityPub *btcec.PublicKey
// LastSeen tracks the last time this node was seen within the network.
// A node should be marked as seen if the daemon either is able to
// establish an outgoing connection to the node or receives a new
// incoming connection from the node. This timestamp (stored in unix
// epoch) may be used within a heuristic which aims to determine when a
// channel should be unilaterally closed due to inactivity.
//
// TODO(roasbeef): replace with block hash/height?
// * possibly add a time-value metric into the heuristic?
LastSeen time.Time
// Addresses is a list of IP address in which either we were able to
// reach the node over in the past, OR we received an incoming
// authenticated connection for the stored identity public key.
Addresses []net.Addr
// db is the database instance this node was fetched from. This is used
// to sync back the node's state if it is updated.
db *LinkNodeDB
}
// NewLinkNode creates a new LinkNode from the provided parameters, which is
// backed by an instance of a link node DB.
func NewLinkNode(db *LinkNodeDB, bitNet wire.BitcoinNet, pub *btcec.PublicKey,
addrs ...net.Addr) *LinkNode {
return &LinkNode{
Network: bitNet,
IdentityPub: pub,
LastSeen: time.Now(),
Addresses: addrs,
db: db,
}
}
// UpdateLastSeen updates the last time this node was directly encountered on
// the Lightning Network.
func (l *LinkNode) UpdateLastSeen(lastSeen time.Time) error {
l.LastSeen = lastSeen
return l.Sync()
}
// AddAddress appends the specified TCP address to the list of known addresses
// this node is/was known to be reachable at.
func (l *LinkNode) AddAddress(addr net.Addr) error {
for _, a := range l.Addresses {
if a.String() == addr.String() {
return nil
}
}
l.Addresses = append(l.Addresses, addr)
return l.Sync()
}
// Sync performs a full database sync which writes the current up-to-date data
// within the struct to the database.
func (l *LinkNode) Sync() error {
// Finally update the database by storing the link node and updating
// any relevant indexes.
return kvdb.Update(l.db.backend, func(tx kvdb.RwTx) error {
nodeMetaBucket := tx.ReadWriteBucket(nodeInfoBucket)
if nodeMetaBucket == nil {
return ErrLinkNodesNotFound
}
return putLinkNode(nodeMetaBucket, l)
}, func() {})
}
// putLinkNode serializes then writes the encoded version of the passed link
// node into the nodeMetaBucket. This function is provided in order to allow
// the ability to re-use a database transaction across many operations.
func putLinkNode(nodeMetaBucket kvdb.RwBucket, l *LinkNode) error {
// First serialize the LinkNode into its raw-bytes encoding.
var b bytes.Buffer
if err := serializeLinkNode(&b, l); err != nil {
return err
}
// Finally insert the link-node into the node metadata bucket keyed
// according to the its pubkey serialized in compressed form.
nodePub := l.IdentityPub.SerializeCompressed()
return nodeMetaBucket.Put(nodePub, b.Bytes())
}
// LinkNodeDB is a database that keeps track of all link nodes.
type LinkNodeDB struct {
backend kvdb.Backend
}
// DeleteLinkNode removes the link node with the given identity from the
// database.
func (l *LinkNodeDB) DeleteLinkNode(identity *btcec.PublicKey) error {
return kvdb.Update(l.backend, func(tx kvdb.RwTx) error {
return deleteLinkNode(tx, identity)
}, func() {})
}
func deleteLinkNode(tx kvdb.RwTx, identity *btcec.PublicKey) error {
nodeMetaBucket := tx.ReadWriteBucket(nodeInfoBucket)
if nodeMetaBucket == nil {
return ErrLinkNodesNotFound
}
pubKey := identity.SerializeCompressed()
return nodeMetaBucket.Delete(pubKey)
}
// FetchLinkNode attempts to lookup the data for a LinkNode based on a target
// identity public key. If a particular LinkNode for the passed identity public
// key cannot be found, then ErrNodeNotFound if returned.
func (l *LinkNodeDB) FetchLinkNode(identity *btcec.PublicKey) (*LinkNode, error) {
var linkNode *LinkNode
err := kvdb.View(l.backend, func(tx kvdb.RTx) error {
node, err := fetchLinkNode(tx, identity)
if err != nil {
return err
}
linkNode = node
return nil
}, func() {
linkNode = nil
})
return linkNode, err
}
func fetchLinkNode(tx kvdb.RTx, targetPub *btcec.PublicKey) (*LinkNode, error) {
// First fetch the bucket for storing node metadata, bailing out early
// if it hasn't been created yet.
nodeMetaBucket := tx.ReadBucket(nodeInfoBucket)
if nodeMetaBucket == nil {
return nil, ErrLinkNodesNotFound
}
// If a link node for that particular public key cannot be located,
// then exit early with an ErrNodeNotFound.
pubKey := targetPub.SerializeCompressed()
nodeBytes := nodeMetaBucket.Get(pubKey)
if nodeBytes == nil {
return nil, ErrNodeNotFound
}
// Finally, decode and allocate a fresh LinkNode object to be returned
// to the caller.
nodeReader := bytes.NewReader(nodeBytes)
return deserializeLinkNode(nodeReader)
}
// TODO(roasbeef): update link node addrs in server upon connection
// FetchAllLinkNodes starts a new database transaction to fetch all nodes with
// whom we have active channels with.
func (l *LinkNodeDB) FetchAllLinkNodes() ([]*LinkNode, error) {
var linkNodes []*LinkNode
err := kvdb.View(l.backend, func(tx kvdb.RTx) error {
nodes, err := fetchAllLinkNodes(tx)
if err != nil {
return err
}
linkNodes = nodes
return nil
}, func() {
linkNodes = nil
})
if err != nil {
return nil, err
}
return linkNodes, nil
}
// fetchAllLinkNodes uses an existing database transaction to fetch all nodes
// with whom we have active channels with.
func fetchAllLinkNodes(tx kvdb.RTx) ([]*LinkNode, error) {
nodeMetaBucket := tx.ReadBucket(nodeInfoBucket)
if nodeMetaBucket == nil {
return nil, ErrLinkNodesNotFound
}
var linkNodes []*LinkNode
err := nodeMetaBucket.ForEach(func(k, v []byte) error {
if v == nil {
return nil
}
nodeReader := bytes.NewReader(v)
linkNode, err := deserializeLinkNode(nodeReader)
if err != nil {
return err
}
linkNodes = append(linkNodes, linkNode)
return nil
})
if err != nil {
return nil, err
}
return linkNodes, nil
}
func serializeLinkNode(w io.Writer, l *LinkNode) error {
var buf [8]byte
byteOrder.PutUint32(buf[:4], uint32(l.Network))
if _, err := w.Write(buf[:4]); err != nil {
return err
}
serializedID := l.IdentityPub.SerializeCompressed()
if _, err := w.Write(serializedID); err != nil {
return err
}
seenUnix := uint64(l.LastSeen.Unix())
byteOrder.PutUint64(buf[:], seenUnix)
if _, err := w.Write(buf[:]); err != nil {
return err
}
numAddrs := uint32(len(l.Addresses))
byteOrder.PutUint32(buf[:4], numAddrs)
if _, err := w.Write(buf[:4]); err != nil {
return err
}
for _, addr := range l.Addresses {
if err := graphdb.SerializeAddr(w, addr); err != nil {
return err
}
}
return nil
}
func deserializeLinkNode(r io.Reader) (*LinkNode, error) {
var (
err error
buf [8]byte
)
node := &LinkNode{}
if _, err := io.ReadFull(r, buf[:4]); err != nil {
return nil, err
}
node.Network = wire.BitcoinNet(byteOrder.Uint32(buf[:4]))
var pub [33]byte
if _, err := io.ReadFull(r, pub[:]); err != nil {
return nil, err
}
node.IdentityPub, err = btcec.ParsePubKey(pub[:])
if err != nil {
return nil, err
}
if _, err := io.ReadFull(r, buf[:]); err != nil {
return nil, err
}
node.LastSeen = time.Unix(int64(byteOrder.Uint64(buf[:])), 0)
if _, err := io.ReadFull(r, buf[:4]); err != nil {
return nil, err
}
numAddrs := byteOrder.Uint32(buf[:4])
node.Addresses = make([]net.Addr, numAddrs)
for i := uint32(0); i < numAddrs; i++ {
addr, err := graphdb.DeserializeAddr(r)
if err != nil {
return nil, err
}
node.Addresses[i] = addr
}
return node, nil
}
package channeldb
import (
"github.com/lightningnetwork/lnd/clock"
)
const (
// DefaultRejectCacheSize is the default number of rejectCacheEntries to
// cache for use in the rejection cache of incoming gossip traffic. This
// produces a cache size of around 1MB.
DefaultRejectCacheSize = 50000
// DefaultChannelCacheSize is the default number of ChannelEdges cached
// in order to reply to gossip queries. This produces a cache size of
// around 40MB.
DefaultChannelCacheSize = 20000
// DefaultPreAllocCacheNumNodes is the default number of channels we
// assume for mainnet for pre-allocating the graph cache. As of
// September 2021, there currently are 14k nodes in a strictly pruned
// graph, so we choose a number that is slightly higher.
DefaultPreAllocCacheNumNodes = 15000
)
// OptionalMiragtionConfig defines the flags used to signal whether a
// particular migration needs to be applied.
type OptionalMiragtionConfig struct {
// PruneRevocationLog specifies that the revocation log migration needs
// to be applied.
PruneRevocationLog bool
}
// Options holds parameters for tuning and customizing a channeldb.DB.
type Options struct {
OptionalMiragtionConfig
// NoMigration specifies that underlying backend was opened in read-only
// mode and migrations shouldn't be performed. This can be useful for
// applications that use the channeldb package as a library.
NoMigration bool
// NoRevLogAmtData when set to true, indicates that amount data should
// not be stored in the revocation log.
NoRevLogAmtData bool
// clock is the time source used by the database.
clock clock.Clock
// dryRun will fail to commit a successful migration when opening the
// database if set to true.
dryRun bool
// keepFailedPaymentAttempts determines whether failed htlc attempts
// are kept on disk or removed to save space.
keepFailedPaymentAttempts bool
// storeFinalHtlcResolutions determines whether to persistently store
// the final resolution of incoming htlcs.
storeFinalHtlcResolutions bool
}
// DefaultOptions returns an Options populated with default values.
func DefaultOptions() Options {
return Options{
OptionalMiragtionConfig: OptionalMiragtionConfig{},
NoMigration: false,
clock: clock.NewDefaultClock(),
}
}
// OptionModifier is a function signature for modifying the default Options.
type OptionModifier func(*Options)
// OptionNoRevLogAmtData sets the NoRevLogAmtData option to the given value. If
// it is set to true then amount data will not be stored in the revocation log.
func OptionNoRevLogAmtData(noAmtData bool) OptionModifier {
return func(o *Options) {
o.NoRevLogAmtData = noAmtData
}
}
// OptionNoMigration allows the database to be opened in read only mode by
// disabling migrations.
func OptionNoMigration(b bool) OptionModifier {
return func(o *Options) {
o.NoMigration = b
}
}
// OptionClock sets a non-default clock dependency.
func OptionClock(clock clock.Clock) OptionModifier {
return func(o *Options) {
o.clock = clock
}
}
// OptionDryRunMigration controls whether or not to intentionally fail to commit a
// successful migration that occurs when opening the database.
func OptionDryRunMigration(dryRun bool) OptionModifier {
return func(o *Options) {
o.dryRun = dryRun
}
}
// OptionKeepFailedPaymentAttempts controls whether failed payment attempts are
// kept on disk after a payment settles.
func OptionKeepFailedPaymentAttempts(keepFailedPaymentAttempts bool) OptionModifier {
return func(o *Options) {
o.keepFailedPaymentAttempts = keepFailedPaymentAttempts
}
}
// OptionStoreFinalHtlcResolutions controls whether to persistently store the
// final resolution of incoming htlcs.
func OptionStoreFinalHtlcResolutions(
storeFinalHtlcResolutions bool) OptionModifier {
return func(o *Options) {
o.storeFinalHtlcResolutions = storeFinalHtlcResolutions
}
}
// OptionPruneRevocationLog specifies whether the migration for pruning
// revocation logs needs to be applied or not.
func OptionPruneRevocationLog(prune bool) OptionModifier {
return func(o *Options) {
o.OptionalMiragtionConfig.PruneRevocationLog = prune
}
}
package channeldb
import "github.com/lightningnetwork/lnd/kvdb"
type paginator struct {
// cursor is the cursor which we are using to iterate through a bucket.
cursor kvdb.RCursor
// reversed indicates whether we are paginating forwards or backwards.
reversed bool
// indexOffset is the index from which we will begin querying.
indexOffset uint64
// totalItems is the total number of items we allow in our response.
totalItems uint64
}
// newPaginator returns a struct which can be used to query an indexed bucket
// in pages.
func newPaginator(c kvdb.RCursor, reversed bool,
indexOffset, totalItems uint64) paginator {
return paginator{
cursor: c,
reversed: reversed,
indexOffset: indexOffset,
totalItems: totalItems,
}
}
// keyValueForIndex seeks our cursor to a given index and returns the key and
// value at that position.
func (p paginator) keyValueForIndex(index uint64) ([]byte, []byte) {
var keyIndex [8]byte
byteOrder.PutUint64(keyIndex[:], index)
return p.cursor.Seek(keyIndex[:])
}
// lastIndex returns the last value in our index, if our index is empty it
// returns 0.
func (p paginator) lastIndex() uint64 {
keyIndex, _ := p.cursor.Last()
if keyIndex == nil {
return 0
}
return byteOrder.Uint64(keyIndex)
}
// nextKey is a helper closure to determine what key we should use next when
// we are iterating, depending on whether we are iterating forwards or in
// reverse.
func (p paginator) nextKey() ([]byte, []byte) {
if p.reversed {
return p.cursor.Prev()
}
return p.cursor.Next()
}
// cursorStart gets the index key and value for the first item we are looking
// up, taking into account that we may be paginating in reverse. The index
// offset provided is *excusive* so we will start with the item after the offset
// for forwards queries, and the item before the index for backwards queries.
func (p paginator) cursorStart() ([]byte, []byte) {
indexKey, indexValue := p.keyValueForIndex(p.indexOffset + 1)
// If the query is specifying reverse iteration, then we must
// handle a few offset cases.
if p.reversed {
switch {
// This indicates the default case, where no offset was
// specified. In that case we just start from the last
// entry.
case p.indexOffset == 0:
indexKey, indexValue = p.cursor.Last()
// This indicates the offset being set to the very
// first entry. Since there are no entries before
// this offset, and the direction is reversed, we can
// return without adding any invoices to the response.
case p.indexOffset == 1:
return nil, nil
// If we have been given an index offset that is beyond our last
// index value, we just return the last indexed value in our set
// since we are querying in reverse. We do not cover the case
// where our index offset equals our last index value, because
// index offset is exclusive, so we would want to start at the
// value before our last index.
case p.indexOffset > p.lastIndex():
return p.cursor.Last()
// Otherwise we have an index offset which is within our set of
// indexed keys, and we want to start at the item before our
// offset. We seek to our index offset, then return the element
// before it. We do this rather than p.indexOffset-1 to account
// for indexes that have gaps.
default:
p.keyValueForIndex(p.indexOffset)
indexKey, indexValue = p.cursor.Prev()
}
}
return indexKey, indexValue
}
// query gets the start point for our index offset and iterates through keys
// in our index until we reach the total number of items required for the query
// or we run out of cursor values. This function takes a fetchAndAppend function
// which is responsible for looking up the entry at that index, adding the entry
// to its set of return items (if desired) and return a boolean which indicates
// whether the item was added. This is required to allow the paginator to
// determine when the response has the maximum number of required items.
func (p paginator) query(fetchAndAppend func(k, v []byte) (bool, error)) error {
indexKey, indexValue := p.cursorStart()
var totalItems int
for ; indexKey != nil; indexKey, indexValue = p.nextKey() {
// If our current return payload exceeds the max number
// of invoices, then we'll exit now.
if uint64(totalItems) >= p.totalItems {
break
}
added, err := fetchAndAppend(indexKey, indexValue)
if err != nil {
return err
}
// If we added an item to our set in the latest fetch and append
// we increment our total count.
if added {
totalItems++
}
}
return nil
}
package channeldb
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"sync"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes"
)
const (
// paymentSeqBlockSize is the block size used when we batch allocate
// payment sequences for future payments.
paymentSeqBlockSize = 1000
)
var (
// ErrAlreadyPaid signals we have already paid this payment hash.
ErrAlreadyPaid = errors.New("invoice is already paid")
// ErrPaymentInFlight signals that payment for this payment hash is
// already "in flight" on the network.
ErrPaymentInFlight = errors.New("payment is in transition")
// ErrPaymentExists is returned when we try to initialize an already
// existing payment that is not failed.
ErrPaymentExists = errors.New("payment already exists")
// ErrPaymentInternal is returned when performing the payment has a
// conflicting state, such as,
// - payment has StatusSucceeded but remaining amount is not zero.
// - payment has StatusInitiated but remaining amount is zero.
// - payment has StatusFailed but remaining amount is zero.
ErrPaymentInternal = errors.New("internal error")
// ErrPaymentNotInitiated is returned if the payment wasn't initiated.
ErrPaymentNotInitiated = errors.New("payment isn't initiated")
// ErrPaymentAlreadySucceeded is returned in the event we attempt to
// change the status of a payment already succeeded.
ErrPaymentAlreadySucceeded = errors.New("payment is already succeeded")
// ErrPaymentAlreadyFailed is returned in the event we attempt to alter
// a failed payment.
ErrPaymentAlreadyFailed = errors.New("payment has already failed")
// ErrUnknownPaymentStatus is returned when we do not recognize the
// existing state of a payment.
ErrUnknownPaymentStatus = errors.New("unknown payment status")
// ErrPaymentTerminal is returned if we attempt to alter a payment that
// already has reached a terminal condition.
ErrPaymentTerminal = errors.New("payment has reached terminal " +
"condition")
// ErrAttemptAlreadySettled is returned if we try to alter an already
// settled HTLC attempt.
ErrAttemptAlreadySettled = errors.New("attempt already settled")
// ErrAttemptAlreadyFailed is returned if we try to alter an already
// failed HTLC attempt.
ErrAttemptAlreadyFailed = errors.New("attempt already failed")
// ErrValueMismatch is returned if we try to register a non-MPP attempt
// with an amount that doesn't match the payment amount.
ErrValueMismatch = errors.New("attempted value doesn't match payment " +
"amount")
// ErrValueExceedsAmt is returned if we try to register an attempt that
// would take the total sent amount above the payment amount.
ErrValueExceedsAmt = errors.New("attempted value exceeds payment " +
"amount")
// ErrNonMPPayment is returned if we try to register an MPP attempt for
// a payment that already has a non-MPP attempt registered.
ErrNonMPPayment = errors.New("payment has non-MPP attempts")
// ErrMPPayment is returned if we try to register a non-MPP attempt for
// a payment that already has an MPP attempt registered.
ErrMPPayment = errors.New("payment has MPP attempts")
// ErrMPPRecordInBlindedPayment is returned if we try to register an
// attempt with an MPP record for a payment to a blinded path.
ErrMPPRecordInBlindedPayment = errors.New("blinded payment cannot " +
"contain MPP records")
// ErrBlindedPaymentTotalAmountMismatch is returned if we try to
// register an HTLC shard to a blinded route where the total amount
// doesn't match existing shards.
ErrBlindedPaymentTotalAmountMismatch = errors.New("blinded path " +
"total amount mismatch")
// ErrMPPPaymentAddrMismatch is returned if we try to register an MPP
// shard where the payment address doesn't match existing shards.
ErrMPPPaymentAddrMismatch = errors.New("payment address mismatch")
// ErrMPPTotalAmountMismatch is returned if we try to register an MPP
// shard where the total amount doesn't match existing shards.
ErrMPPTotalAmountMismatch = errors.New("mp payment total amount " +
"mismatch")
// ErrPaymentPendingSettled is returned when we try to add a new
// attempt to a payment that has at least one of its HTLCs settled.
ErrPaymentPendingSettled = errors.New("payment has settled htlcs")
// ErrPaymentPendingFailed is returned when we try to add a new attempt
// to a payment that already has a failure reason.
ErrPaymentPendingFailed = errors.New("payment has failure reason")
// ErrSentExceedsTotal is returned if the payment's current total sent
// amount exceed the total amount.
ErrSentExceedsTotal = errors.New("total sent exceeds total amount")
// errNoAttemptInfo is returned when no attempt info is stored yet.
errNoAttemptInfo = errors.New("unable to find attempt info for " +
"inflight payment")
// errNoSequenceNrIndex is returned when an attempt to lookup a payment
// index is made for a sequence number that is not indexed.
errNoSequenceNrIndex = errors.New("payment sequence number index " +
"does not exist")
)
// PaymentControl implements persistence for payments and payment attempts.
type PaymentControl struct {
paymentSeqMx sync.Mutex
currPaymentSeq uint64
storedPaymentSeq uint64
db *DB
}
// NewPaymentControl creates a new instance of the PaymentControl.
func NewPaymentControl(db *DB) *PaymentControl {
return &PaymentControl{
db: db,
}
}
// InitPayment checks or records the given PaymentCreationInfo with the DB,
// making sure it does not already exist as an in-flight payment. When this
// method returns successfully, the payment is guaranteed to be in the InFlight
// state.
func (p *PaymentControl) InitPayment(paymentHash lntypes.Hash,
info *PaymentCreationInfo) error {
// Obtain a new sequence number for this payment. This is used
// to sort the payments in order of creation, and also acts as
// a unique identifier for each payment.
sequenceNum, err := p.nextPaymentSequence()
if err != nil {
return err
}
var b bytes.Buffer
if err := serializePaymentCreationInfo(&b, info); err != nil {
return err
}
infoBytes := b.Bytes()
var updateErr error
err = kvdb.Batch(p.db.Backend, func(tx kvdb.RwTx) error {
// Reset the update error, to avoid carrying over an error
// from a previous execution of the batched db transaction.
updateErr = nil
prefetchPayment(tx, paymentHash)
bucket, err := createPaymentBucket(tx, paymentHash)
if err != nil {
return err
}
// Get the existing status of this payment, if any.
paymentStatus, err := fetchPaymentStatus(bucket)
switch {
// If no error is returned, it means we already have this
// payment. We'll check the status to decide whether we allow
// retrying the payment or return a specific error.
case err == nil:
if err := paymentStatus.initializable(); err != nil {
updateErr = err
return nil
}
// Otherwise, if the error is not `ErrPaymentNotInitiated`,
// we'll return the error.
case !errors.Is(err, ErrPaymentNotInitiated):
return err
}
// Before we set our new sequence number, we check whether this
// payment has a previously set sequence number and remove its
// index entry if it exists. This happens in the case where we
// have a previously attempted payment which was left in a state
// where we can retry.
seqBytes := bucket.Get(paymentSequenceKey)
if seqBytes != nil {
indexBucket := tx.ReadWriteBucket(paymentsIndexBucket)
if err := indexBucket.Delete(seqBytes); err != nil {
return err
}
}
// Once we have obtained a sequence number, we add an entry
// to our index bucket which will map the sequence number to
// our payment identifier.
err = createPaymentIndexEntry(
tx, sequenceNum, info.PaymentIdentifier,
)
if err != nil {
return err
}
err = bucket.Put(paymentSequenceKey, sequenceNum)
if err != nil {
return err
}
// Add the payment info to the bucket, which contains the
// static information for this payment
err = bucket.Put(paymentCreationInfoKey, infoBytes)
if err != nil {
return err
}
// We'll delete any lingering HTLCs to start with, in case we
// are initializing a payment that was attempted earlier, but
// left in a state where we could retry.
err = bucket.DeleteNestedBucket(paymentHtlcsBucket)
if err != nil && err != kvdb.ErrBucketNotFound {
return err
}
// Also delete any lingering failure info now that we are
// re-attempting.
return bucket.Delete(paymentFailInfoKey)
})
if err != nil {
return fmt.Errorf("unable to init payment: %w", err)
}
return updateErr
}
// DeleteFailedAttempts deletes all failed htlcs for a payment if configured
// by the PaymentControl db.
func (p *PaymentControl) DeleteFailedAttempts(hash lntypes.Hash) error {
if !p.db.keepFailedPaymentAttempts {
const failedHtlcsOnly = true
err := p.db.DeletePayment(hash, failedHtlcsOnly)
if err != nil {
return err
}
}
return nil
}
// paymentIndexTypeHash is a payment index type which indicates that we have
// created an index of payment sequence number to payment hash.
type paymentIndexType uint8
// paymentIndexTypeHash is a payment index type which indicates that we have
// created an index of payment sequence number to payment hash.
const paymentIndexTypeHash paymentIndexType = 0
// createPaymentIndexEntry creates a payment hash typed index for a payment. The
// index produced contains a payment index type (which can be used in future to
// signal different payment index types) and the payment identifier.
func createPaymentIndexEntry(tx kvdb.RwTx, sequenceNumber []byte,
id lntypes.Hash) error {
var b bytes.Buffer
if err := WriteElements(&b, paymentIndexTypeHash, id[:]); err != nil {
return err
}
indexes := tx.ReadWriteBucket(paymentsIndexBucket)
return indexes.Put(sequenceNumber, b.Bytes())
}
// deserializePaymentIndex deserializes a payment index entry. This function
// currently only supports deserialization of payment hash indexes, and will
// fail for other types.
func deserializePaymentIndex(r io.Reader) (lntypes.Hash, error) {
var (
indexType paymentIndexType
paymentHash []byte
)
if err := ReadElements(r, &indexType, &paymentHash); err != nil {
return lntypes.Hash{}, err
}
// While we only have on payment index type, we do not need to use our
// index type to deserialize the index. However, we sanity check that
// this type is as expected, since we had to read it out anyway.
if indexType != paymentIndexTypeHash {
return lntypes.Hash{}, fmt.Errorf("unknown payment index "+
"type: %v", indexType)
}
hash, err := lntypes.MakeHash(paymentHash)
if err != nil {
return lntypes.Hash{}, err
}
return hash, nil
}
// RegisterAttempt atomically records the provided HTLCAttemptInfo to the
// DB.
func (p *PaymentControl) RegisterAttempt(paymentHash lntypes.Hash,
attempt *HTLCAttemptInfo) (*MPPayment, error) {
// Serialize the information before opening the db transaction.
var a bytes.Buffer
err := serializeHTLCAttemptInfo(&a, attempt)
if err != nil {
return nil, err
}
htlcInfoBytes := a.Bytes()
htlcIDBytes := make([]byte, 8)
binary.BigEndian.PutUint64(htlcIDBytes, attempt.AttemptID)
var payment *MPPayment
err = kvdb.Batch(p.db.Backend, func(tx kvdb.RwTx) error {
prefetchPayment(tx, paymentHash)
bucket, err := fetchPaymentBucketUpdate(tx, paymentHash)
if err != nil {
return err
}
payment, err = fetchPayment(bucket)
if err != nil {
return err
}
// Check if registering a new attempt is allowed.
if err := payment.Registrable(); err != nil {
return err
}
// If the final hop has encrypted data, then we know this is a
// blinded payment. In blinded payments, MPP records are not set
// for split payments and the recipient is responsible for using
// a consistent PathID across the various encrypted data
// payloads that we received from them for this payment. All we
// need to check is that the total amount field for each HTLC
// in the split payment is correct.
isBlinded := len(attempt.Route.FinalHop().EncryptedData) != 0
// Make sure any existing shards match the new one with regards
// to MPP options.
mpp := attempt.Route.FinalHop().MPP
// MPP records should not be set for attempts to blinded paths.
if isBlinded && mpp != nil {
return ErrMPPRecordInBlindedPayment
}
for _, h := range payment.InFlightHTLCs() {
hMpp := h.Route.FinalHop().MPP
// If this is a blinded payment, then no existing HTLCs
// should have MPP records.
if isBlinded && hMpp != nil {
return ErrMPPRecordInBlindedPayment
}
// If this is a blinded payment, then we just need to
// check that the TotalAmtMsat field for this shard
// is equal to that of any other shard in the same
// payment.
if isBlinded {
if attempt.Route.FinalHop().TotalAmtMsat !=
h.Route.FinalHop().TotalAmtMsat {
//nolint:ll
return ErrBlindedPaymentTotalAmountMismatch
}
continue
}
switch {
// We tried to register a non-MPP attempt for a MPP
// payment.
case mpp == nil && hMpp != nil:
return ErrMPPayment
// We tried to register a MPP shard for a non-MPP
// payment.
case mpp != nil && hMpp == nil:
return ErrNonMPPayment
// Non-MPP payment, nothing more to validate.
case mpp == nil:
continue
}
// Check that MPP options match.
if mpp.PaymentAddr() != hMpp.PaymentAddr() {
return ErrMPPPaymentAddrMismatch
}
if mpp.TotalMsat() != hMpp.TotalMsat() {
return ErrMPPTotalAmountMismatch
}
}
// If this is a non-MPP attempt, it must match the total amount
// exactly. Note that a blinded payment is considered an MPP
// attempt.
amt := attempt.Route.ReceiverAmt()
if !isBlinded && mpp == nil && amt != payment.Info.Value {
return ErrValueMismatch
}
// Ensure we aren't sending more than the total payment amount.
sentAmt, _ := payment.SentAmt()
if sentAmt+amt > payment.Info.Value {
return fmt.Errorf("%w: attempted=%v, payment amount="+
"%v", ErrValueExceedsAmt, sentAmt+amt,
payment.Info.Value)
}
htlcsBucket, err := bucket.CreateBucketIfNotExists(
paymentHtlcsBucket,
)
if err != nil {
return err
}
err = htlcsBucket.Put(
htlcBucketKey(htlcAttemptInfoKey, htlcIDBytes),
htlcInfoBytes,
)
if err != nil {
return err
}
// Retrieve attempt info for the notification.
payment, err = fetchPayment(bucket)
return err
})
if err != nil {
return nil, err
}
return payment, err
}
// SettleAttempt marks the given attempt settled with the preimage. If this is
// a multi shard payment, this might implicitly mean that the full payment
// succeeded.
//
// After invoking this method, InitPayment should always return an error to
// prevent us from making duplicate payments to the same payment hash. The
// provided preimage is atomically saved to the DB for record keeping.
func (p *PaymentControl) SettleAttempt(hash lntypes.Hash,
attemptID uint64, settleInfo *HTLCSettleInfo) (*MPPayment, error) {
var b bytes.Buffer
if err := serializeHTLCSettleInfo(&b, settleInfo); err != nil {
return nil, err
}
settleBytes := b.Bytes()
return p.updateHtlcKey(hash, attemptID, htlcSettleInfoKey, settleBytes)
}
// FailAttempt marks the given payment attempt failed.
func (p *PaymentControl) FailAttempt(hash lntypes.Hash,
attemptID uint64, failInfo *HTLCFailInfo) (*MPPayment, error) {
var b bytes.Buffer
if err := serializeHTLCFailInfo(&b, failInfo); err != nil {
return nil, err
}
failBytes := b.Bytes()
return p.updateHtlcKey(hash, attemptID, htlcFailInfoKey, failBytes)
}
// updateHtlcKey updates a database key for the specified htlc.
func (p *PaymentControl) updateHtlcKey(paymentHash lntypes.Hash,
attemptID uint64, key, value []byte) (*MPPayment, error) {
aid := make([]byte, 8)
binary.BigEndian.PutUint64(aid, attemptID)
var payment *MPPayment
err := kvdb.Batch(p.db.Backend, func(tx kvdb.RwTx) error {
payment = nil
prefetchPayment(tx, paymentHash)
bucket, err := fetchPaymentBucketUpdate(tx, paymentHash)
if err != nil {
return err
}
p, err := fetchPayment(bucket)
if err != nil {
return err
}
// We can only update keys of in-flight payments. We allow
// updating keys even if the payment has reached a terminal
// condition, since the HTLC outcomes must still be updated.
if err := p.Status.updatable(); err != nil {
return err
}
htlcsBucket := bucket.NestedReadWriteBucket(paymentHtlcsBucket)
if htlcsBucket == nil {
return fmt.Errorf("htlcs bucket not found")
}
if htlcsBucket.Get(htlcBucketKey(htlcAttemptInfoKey, aid)) == nil {
return fmt.Errorf("HTLC with ID %v not registered",
attemptID)
}
// Make sure the shard is not already failed or settled.
if htlcsBucket.Get(htlcBucketKey(htlcFailInfoKey, aid)) != nil {
return ErrAttemptAlreadyFailed
}
if htlcsBucket.Get(htlcBucketKey(htlcSettleInfoKey, aid)) != nil {
return ErrAttemptAlreadySettled
}
// Add or update the key for this htlc.
err = htlcsBucket.Put(htlcBucketKey(key, aid), value)
if err != nil {
return err
}
// Retrieve attempt info for the notification.
payment, err = fetchPayment(bucket)
return err
})
if err != nil {
return nil, err
}
return payment, err
}
// Fail transitions a payment into the Failed state, and records the reason the
// payment failed. After invoking this method, InitPayment should return nil on
// its next call for this payment hash, allowing the switch to make a
// subsequent payment.
func (p *PaymentControl) Fail(paymentHash lntypes.Hash,
reason FailureReason) (*MPPayment, error) {
var (
updateErr error
payment *MPPayment
)
err := kvdb.Batch(p.db.Backend, func(tx kvdb.RwTx) error {
// Reset the update error, to avoid carrying over an error
// from a previous execution of the batched db transaction.
updateErr = nil
payment = nil
prefetchPayment(tx, paymentHash)
bucket, err := fetchPaymentBucketUpdate(tx, paymentHash)
if err == ErrPaymentNotInitiated {
updateErr = ErrPaymentNotInitiated
return nil
} else if err != nil {
return err
}
// We mark the payment as failed as long as it is known. This
// lets the last attempt to fail with a terminal write its
// failure to the PaymentControl without synchronizing with
// other attempts.
_, err = fetchPaymentStatus(bucket)
if errors.Is(err, ErrPaymentNotInitiated) {
updateErr = ErrPaymentNotInitiated
return nil
} else if err != nil {
return err
}
// Put the failure reason in the bucket for record keeping.
v := []byte{byte(reason)}
err = bucket.Put(paymentFailInfoKey, v)
if err != nil {
return err
}
// Retrieve attempt info for the notification, if available.
payment, err = fetchPayment(bucket)
if err != nil {
return err
}
return nil
})
if err != nil {
return nil, err
}
return payment, updateErr
}
// FetchPayment returns information about a payment from the database.
func (p *PaymentControl) FetchPayment(paymentHash lntypes.Hash) (
*MPPayment, error) {
var payment *MPPayment
err := kvdb.View(p.db, func(tx kvdb.RTx) error {
prefetchPayment(tx, paymentHash)
bucket, err := fetchPaymentBucket(tx, paymentHash)
if err != nil {
return err
}
payment, err = fetchPayment(bucket)
return err
}, func() {
payment = nil
})
if err != nil {
return nil, err
}
return payment, nil
}
// prefetchPayment attempts to prefetch as much of the payment as possible to
// reduce DB roundtrips.
func prefetchPayment(tx kvdb.RTx, paymentHash lntypes.Hash) {
rb := kvdb.RootBucket(tx)
kvdb.Prefetch(
rb,
[]string{
// Prefetch all keys in the payment's bucket.
string(paymentsRootBucket),
string(paymentHash[:]),
},
[]string{
// Prefetch all keys in the payment's htlc bucket.
string(paymentsRootBucket),
string(paymentHash[:]),
string(paymentHtlcsBucket),
},
)
}
// createPaymentBucket creates or fetches the sub-bucket assigned to this
// payment hash.
func createPaymentBucket(tx kvdb.RwTx, paymentHash lntypes.Hash) (
kvdb.RwBucket, error) {
payments, err := tx.CreateTopLevelBucket(paymentsRootBucket)
if err != nil {
return nil, err
}
return payments.CreateBucketIfNotExists(paymentHash[:])
}
// fetchPaymentBucket fetches the sub-bucket assigned to this payment hash. If
// the bucket does not exist, it returns ErrPaymentNotInitiated.
func fetchPaymentBucket(tx kvdb.RTx, paymentHash lntypes.Hash) (
kvdb.RBucket, error) {
payments := tx.ReadBucket(paymentsRootBucket)
if payments == nil {
return nil, ErrPaymentNotInitiated
}
bucket := payments.NestedReadBucket(paymentHash[:])
if bucket == nil {
return nil, ErrPaymentNotInitiated
}
return bucket, nil
}
// fetchPaymentBucketUpdate is identical to fetchPaymentBucket, but it returns a
// bucket that can be written to.
func fetchPaymentBucketUpdate(tx kvdb.RwTx, paymentHash lntypes.Hash) (
kvdb.RwBucket, error) {
payments := tx.ReadWriteBucket(paymentsRootBucket)
if payments == nil {
return nil, ErrPaymentNotInitiated
}
bucket := payments.NestedReadWriteBucket(paymentHash[:])
if bucket == nil {
return nil, ErrPaymentNotInitiated
}
return bucket, nil
}
// nextPaymentSequence returns the next sequence number to store for a new
// payment.
func (p *PaymentControl) nextPaymentSequence() ([]byte, error) {
p.paymentSeqMx.Lock()
defer p.paymentSeqMx.Unlock()
// Set a new upper bound in the DB every 1000 payments to avoid
// conflicts on the sequence when using etcd.
if p.currPaymentSeq == p.storedPaymentSeq {
var currPaymentSeq, newUpperBound uint64
if err := kvdb.Update(p.db.Backend, func(tx kvdb.RwTx) error {
paymentsBucket, err := tx.CreateTopLevelBucket(
paymentsRootBucket,
)
if err != nil {
return err
}
currPaymentSeq = paymentsBucket.Sequence()
newUpperBound = currPaymentSeq + paymentSeqBlockSize
return paymentsBucket.SetSequence(newUpperBound)
}, func() {}); err != nil {
return nil, err
}
// We lazy initialize the cached currPaymentSeq here using the
// first nextPaymentSequence() call. This if statement will auto
// initialize our stored currPaymentSeq, since by default both
// this variable and storedPaymentSeq are zero which in turn
// will have us fetch the current values from the DB.
if p.currPaymentSeq == 0 {
p.currPaymentSeq = currPaymentSeq
}
p.storedPaymentSeq = newUpperBound
}
p.currPaymentSeq++
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, p.currPaymentSeq)
return b, nil
}
// fetchPaymentStatus fetches the payment status of the payment. If the payment
// isn't found, it will return error `ErrPaymentNotInitiated`.
func fetchPaymentStatus(bucket kvdb.RBucket) (PaymentStatus, error) {
// Creation info should be set for all payments, regardless of state.
// If not, it is unknown.
if bucket.Get(paymentCreationInfoKey) == nil {
return 0, ErrPaymentNotInitiated
}
payment, err := fetchPayment(bucket)
if err != nil {
return 0, err
}
return payment.Status, nil
}
// FetchInFlightPayments returns all payments with status InFlight.
func (p *PaymentControl) FetchInFlightPayments() ([]*MPPayment, error) {
var inFlights []*MPPayment
err := kvdb.View(p.db, func(tx kvdb.RTx) error {
payments := tx.ReadBucket(paymentsRootBucket)
if payments == nil {
return nil
}
return payments.ForEach(func(k, _ []byte) error {
bucket := payments.NestedReadBucket(k)
if bucket == nil {
return fmt.Errorf("non bucket element")
}
p, err := fetchPayment(bucket)
if err != nil {
return err
}
// Skip the payment if it's terminated.
if p.Terminated() {
return nil
}
inFlights = append(inFlights, p)
return nil
})
}, func() {
inFlights = nil
})
if err != nil {
return nil, err
}
return inFlights, nil
}
package channeldb
import "fmt"
// PaymentStatus represent current status of payment.
type PaymentStatus byte
const (
// NOTE: PaymentStatus = 0 was previously used for status unknown and
// is now deprecated.
// StatusInitiated is the status where a payment has just been
// initiated.
StatusInitiated PaymentStatus = 1
// StatusInFlight is the status where a payment has been initiated, but
// a response has not been received.
StatusInFlight PaymentStatus = 2
// StatusSucceeded is the status where a payment has been initiated and
// the payment was completed successfully.
StatusSucceeded PaymentStatus = 3
// StatusFailed is the status where a payment has been initiated and a
// failure result has come back.
StatusFailed PaymentStatus = 4
)
// errPaymentStatusUnknown is returned when a payment has an unknown status.
var errPaymentStatusUnknown = fmt.Errorf("unknown payment status")
// String returns readable representation of payment status.
func (ps PaymentStatus) String() string {
switch ps {
case StatusInitiated:
return "Initiated"
case StatusInFlight:
return "In Flight"
case StatusSucceeded:
return "Succeeded"
case StatusFailed:
return "Failed"
default:
return "Unknown"
}
}
// initializable returns an error to specify whether initiating the payment
// with its current status is allowed. A payment can only be initialized if it
// hasn't been created yet or already failed.
func (ps PaymentStatus) initializable() error {
switch ps {
// The payment has been created already. We will disallow creating it
// again in case other goroutines have already been creating HTLCs for
// it.
case StatusInitiated:
return ErrPaymentExists
// We already have an InFlight payment on the network. We will disallow
// any new payments.
case StatusInFlight:
return ErrPaymentInFlight
// The payment has been attempted and is succeeded so we won't allow
// creating it again.
case StatusSucceeded:
return ErrAlreadyPaid
// We allow retrying failed payments.
case StatusFailed:
return nil
default:
return fmt.Errorf("%w: %v", ErrUnknownPaymentStatus, ps)
}
}
// removable returns an error to specify whether deleting the payment with its
// current status is allowed. A payment cannot be safely deleted if it has
// inflight HTLCs.
func (ps PaymentStatus) removable() error {
switch ps {
// The payment has been created but has no HTLCs and can be removed.
case StatusInitiated:
return nil
// There are still inflight HTLCs and the payment needs to wait for the
// final outcomes.
case StatusInFlight:
return ErrPaymentInFlight
// The payment has been attempted and is succeeded and is allowed to be
// removed.
case StatusSucceeded:
return nil
// Failed payments are allowed to be removed.
case StatusFailed:
return nil
default:
return fmt.Errorf("%w: %v", ErrUnknownPaymentStatus, ps)
}
}
// updatable returns an error to specify whether the payment's HTLCs can be
// updated. A payment can update its HTLCs when it has inflight HTLCs.
func (ps PaymentStatus) updatable() error {
switch ps {
// Newly created payments can be updated.
case StatusInitiated:
return nil
// Inflight payments can be updated.
case StatusInFlight:
return nil
// If the payment has a terminal condition, we won't allow any updates.
case StatusSucceeded:
return ErrPaymentAlreadySucceeded
case StatusFailed:
return ErrPaymentAlreadyFailed
default:
return fmt.Errorf("%w: %v", ErrUnknownPaymentStatus, ps)
}
}
// decidePaymentStatus uses the payment's DB state to determine a memory status
// that's used by the payment router to decide following actions.
// Together, we use four variables to determine the payment's status,
// - inflight: whether there are any pending HTLCs.
// - settled: whether any of the HTLCs has been settled.
// - htlc failed: whether any of the HTLCs has been failed.
// - payment failed: whether the payment has been marked as failed.
//
// Based on the above variables, we derive the status using the following
// table,
// | inflight | settled | htlc failed | payment failed | status |
// |:--------:|:-------:|:-----------:|:--------------:|:--------------------:|
// | true | true | true | true | StatusInFlight |
// | true | true | true | false | StatusInFlight |
// | true | true | false | true | StatusInFlight |
// | true | true | false | false | StatusInFlight |
// | true | false | true | true | StatusInFlight |
// | true | false | true | false | StatusInFlight |
// | true | false | false | true | StatusInFlight |
// | true | false | false | false | StatusInFlight |
// | false | true | true | true | StatusSucceeded |
// | false | true | true | false | StatusSucceeded |
// | false | true | false | true | StatusSucceeded |
// | false | true | false | false | StatusSucceeded |
// | false | false | true | true | StatusFailed |
// | false | false | true | false | StatusInFlight |
// | false | false | false | true | StatusFailed |
// | false | false | false | false | StatusInitiated |
//
// When `inflight`, `settled`, `htlc failed`, and `payment failed` are false,
// this indicates the payment is newly created and hasn't made any HTLCs yet.
// When `inflight` and `settled` are false, `htlc failed` is true yet `payment
// failed` is false, this indicates all the payment's HTLCs have occurred a
// temporarily failure and the payment is still in-flight.
func decidePaymentStatus(htlcs []HTLCAttempt,
reason *FailureReason) (PaymentStatus, error) {
var (
inflight bool
htlcSettled bool
htlcFailed bool
paymentFailed bool
)
// If we have a failure reason, the payment is failed.
if reason != nil {
paymentFailed = true
}
// Go through all HTLCs for this payment, check whether we have any
// settled HTLC, and any still in-flight.
for _, h := range htlcs {
if h.Failure != nil {
htlcFailed = true
continue
}
if h.Settle != nil {
htlcSettled = true
continue
}
// If any of the HTLCs are not failed nor settled, we
// still have inflight HTLCs.
inflight = true
}
// Use the DB state to determine the status of the payment.
switch {
// If we have inflight HTLCs, no matter we have settled or failed
// HTLCs, or the payment failed, we still consider it inflight so we
// inform upper systems to wait for the results.
case inflight:
return StatusInFlight, nil
// If we have no in-flight HTLCs, and at least one of the HTLCs is
// settled, the payment succeeded.
//
// NOTE: when reaching this case, paymentFailed could be true, which
// means we have a conflicting state for this payment. We choose to
// mark the payment as succeeded because it's the receiver's
// responsibility to only settle the payment iff all HTLCs are
// received.
case htlcSettled:
return StatusSucceeded, nil
// If we have no in-flight HTLCs, and the payment failure is set, the
// payment is considered failed.
//
// NOTE: when reaching this case, settled must be false.
case paymentFailed:
return StatusFailed, nil
// If we have no in-flight HTLCs, yet the payment is NOT failed, it
// means all the HTLCs are failed. In this case we can attempt more
// HTLCs.
//
// NOTE: when reaching this case, both settled and paymentFailed must
// be false.
case htlcFailed:
return StatusInFlight, nil
// If none of the HTLCs is either settled or failed, and we have no
// inflight HTLCs, this means the payment has no HTLCs created yet.
//
// NOTE: when reaching this case, both settled and paymentFailed must
// be false.
case !htlcFailed:
return StatusInitiated, nil
// Otherwise an impossible state is reached.
//
// NOTE: we should never end up here.
default:
log.Error("Impossible payment state reached")
return 0, fmt.Errorf("%w: payment is corrupted",
errPaymentStatusUnknown)
}
}
package channeldb
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"sort"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/tlv"
)
var (
// paymentsRootBucket is the name of the top-level bucket within the
// database that stores all data related to payments. Within this
// bucket, each payment hash its own sub-bucket keyed by its payment
// hash.
//
// Bucket hierarchy:
//
// root-bucket
// |
// |-- <paymenthash>
// | |--sequence-key: <sequence number>
// | |--creation-info-key: <creation info>
// | |--fail-info-key: <(optional) fail info>
// | |
// | |--payment-htlcs-bucket (shard-bucket)
// | | |
// | | |-- ai<htlc attempt ID>: <htlc attempt info>
// | | |-- si<htlc attempt ID>: <(optional) settle info>
// | | |-- fi<htlc attempt ID>: <(optional) fail info>
// | | |
// | | ...
// | |
// | |
// | |--duplicate-bucket (only for old, completed payments)
// | |
// | |-- <seq-num>
// | | |--sequence-key: <sequence number>
// | | |--creation-info-key: <creation info>
// | | |--ai: <attempt info>
// | | |--si: <settle info>
// | | |--fi: <fail info>
// | |
// | |-- <seq-num>
// | | |
// | ... ...
// |
// |-- <paymenthash>
// | |
// | ...
// ...
//
paymentsRootBucket = []byte("payments-root-bucket")
// paymentSequenceKey is a key used in the payment's sub-bucket to
// store the sequence number of the payment.
paymentSequenceKey = []byte("payment-sequence-key")
// paymentCreationInfoKey is a key used in the payment's sub-bucket to
// store the creation info of the payment.
paymentCreationInfoKey = []byte("payment-creation-info")
// paymentHtlcsBucket is a bucket where we'll store the information
// about the HTLCs that were attempted for a payment.
paymentHtlcsBucket = []byte("payment-htlcs-bucket")
// htlcAttemptInfoKey is the key used as the prefix of an HTLC attempt
// to store the info about the attempt that was done for the HTLC in
// question. The HTLC attempt ID is concatenated at the end.
htlcAttemptInfoKey = []byte("ai")
// htlcSettleInfoKey is the key used as the prefix of an HTLC attempt
// settle info, if any. The HTLC attempt ID is concatenated at the end.
htlcSettleInfoKey = []byte("si")
// htlcFailInfoKey is the key used as the prefix of an HTLC attempt
// failure information, if any.The HTLC attempt ID is concatenated at
// the end.
htlcFailInfoKey = []byte("fi")
// paymentFailInfoKey is a key used in the payment's sub-bucket to
// store information about the reason a payment failed.
paymentFailInfoKey = []byte("payment-fail-info")
// paymentsIndexBucket is the name of the top-level bucket within the
// database that stores an index of payment sequence numbers to its
// payment hash.
// payments-sequence-index-bucket
// |--<sequence-number>: <payment hash>
// |--...
// |--<sequence-number>: <payment hash>
paymentsIndexBucket = []byte("payments-index-bucket")
)
var (
// ErrNoSequenceNumber is returned if we look up a payment which does
// not have a sequence number.
ErrNoSequenceNumber = errors.New("sequence number not found")
// ErrDuplicateNotFound is returned when we lookup a payment by its
// index and cannot find a payment with a matching sequence number.
ErrDuplicateNotFound = errors.New("duplicate payment not found")
// ErrNoDuplicateBucket is returned when we expect to find duplicates
// when looking up a payment from its index, but the payment does not
// have any.
ErrNoDuplicateBucket = errors.New("expected duplicate bucket")
// ErrNoDuplicateNestedBucket is returned if we do not find duplicate
// payments in their own sub-bucket.
ErrNoDuplicateNestedBucket = errors.New("nested duplicate bucket not " +
"found")
)
// FailureReason encodes the reason a payment ultimately failed.
type FailureReason byte
const (
// FailureReasonTimeout indicates that the payment did timeout before a
// successful payment attempt was made.
FailureReasonTimeout FailureReason = 0
// FailureReasonNoRoute indicates no successful route to the
// destination was found during path finding.
FailureReasonNoRoute FailureReason = 1
// FailureReasonError indicates that an unexpected error happened during
// payment.
FailureReasonError FailureReason = 2
// FailureReasonPaymentDetails indicates that either the hash is unknown
// or the final cltv delta or amount is incorrect.
FailureReasonPaymentDetails FailureReason = 3
// FailureReasonInsufficientBalance indicates that we didn't have enough
// balance to complete the payment.
FailureReasonInsufficientBalance FailureReason = 4
// FailureReasonCanceled indicates that the payment was canceled by the
// user.
FailureReasonCanceled FailureReason = 5
// TODO(joostjager): Add failure reasons for:
// LocalLiquidityInsufficient, RemoteCapacityInsufficient.
)
// Error returns a human-readable error string for the FailureReason.
func (r FailureReason) Error() string {
return r.String()
}
// String returns a human-readable FailureReason.
func (r FailureReason) String() string {
switch r {
case FailureReasonTimeout:
return "timeout"
case FailureReasonNoRoute:
return "no_route"
case FailureReasonError:
return "error"
case FailureReasonPaymentDetails:
return "incorrect_payment_details"
case FailureReasonInsufficientBalance:
return "insufficient_balance"
case FailureReasonCanceled:
return "canceled"
}
return "unknown"
}
// PaymentCreationInfo is the information necessary to have ready when
// initiating a payment, moving it into state InFlight.
type PaymentCreationInfo struct {
// PaymentIdentifier is the hash this payment is paying to in case of
// non-AMP payments, and the SetID for AMP payments.
PaymentIdentifier lntypes.Hash
// Value is the amount we are paying.
Value lnwire.MilliSatoshi
// CreationTime is the time when this payment was initiated.
CreationTime time.Time
// PaymentRequest is the full payment request, if any.
PaymentRequest []byte
// FirstHopCustomRecords are the TLV records that are to be sent to the
// first hop of this payment. These records will be transmitted via the
// wire message only and therefore do not affect the onion payload size.
FirstHopCustomRecords lnwire.CustomRecords
}
// htlcBucketKey creates a composite key from prefix and id where the result is
// simply the two concatenated.
func htlcBucketKey(prefix, id []byte) []byte {
key := make([]byte, len(prefix)+len(id))
copy(key, prefix)
copy(key[len(prefix):], id)
return key
}
// FetchPayments returns all sent payments found in the DB.
//
// nolint: dupl
func (d *DB) FetchPayments() ([]*MPPayment, error) {
var payments []*MPPayment
err := kvdb.View(d, func(tx kvdb.RTx) error {
paymentsBucket := tx.ReadBucket(paymentsRootBucket)
if paymentsBucket == nil {
return nil
}
return paymentsBucket.ForEach(func(k, v []byte) error {
bucket := paymentsBucket.NestedReadBucket(k)
if bucket == nil {
// We only expect sub-buckets to be found in
// this top-level bucket.
return fmt.Errorf("non bucket element in " +
"payments bucket")
}
p, err := fetchPayment(bucket)
if err != nil {
return err
}
payments = append(payments, p)
// For older versions of lnd, duplicate payments to a
// payment has was possible. These will be found in a
// sub-bucket indexed by their sequence number if
// available.
duplicatePayments, err := fetchDuplicatePayments(bucket)
if err != nil {
return err
}
payments = append(payments, duplicatePayments...)
return nil
})
}, func() {
payments = nil
})
if err != nil {
return nil, err
}
// Before returning, sort the payments by their sequence number.
sort.Slice(payments, func(i, j int) bool {
return payments[i].SequenceNum < payments[j].SequenceNum
})
return payments, nil
}
func fetchCreationInfo(bucket kvdb.RBucket) (*PaymentCreationInfo, error) {
b := bucket.Get(paymentCreationInfoKey)
if b == nil {
return nil, fmt.Errorf("creation info not found")
}
r := bytes.NewReader(b)
return deserializePaymentCreationInfo(r)
}
func fetchPayment(bucket kvdb.RBucket) (*MPPayment, error) {
seqBytes := bucket.Get(paymentSequenceKey)
if seqBytes == nil {
return nil, fmt.Errorf("sequence number not found")
}
sequenceNum := binary.BigEndian.Uint64(seqBytes)
// Get the PaymentCreationInfo.
creationInfo, err := fetchCreationInfo(bucket)
if err != nil {
return nil, err
}
var htlcs []HTLCAttempt
htlcsBucket := bucket.NestedReadBucket(paymentHtlcsBucket)
if htlcsBucket != nil {
// Get the payment attempts. This can be empty.
htlcs, err = fetchHtlcAttempts(htlcsBucket)
if err != nil {
return nil, err
}
}
// Get failure reason if available.
var failureReason *FailureReason
b := bucket.Get(paymentFailInfoKey)
if b != nil {
reason := FailureReason(b[0])
failureReason = &reason
}
// Create a new payment.
payment := &MPPayment{
SequenceNum: sequenceNum,
Info: creationInfo,
HTLCs: htlcs,
FailureReason: failureReason,
}
// Set its state and status.
if err := payment.setState(); err != nil {
return nil, err
}
return payment, nil
}
// fetchHtlcAttempts retrieves all htlc attempts made for the payment found in
// the given bucket.
func fetchHtlcAttempts(bucket kvdb.RBucket) ([]HTLCAttempt, error) {
htlcsMap := make(map[uint64]*HTLCAttempt)
attemptInfoCount := 0
err := bucket.ForEach(func(k, v []byte) error {
aid := byteOrder.Uint64(k[len(k)-8:])
if _, ok := htlcsMap[aid]; !ok {
htlcsMap[aid] = &HTLCAttempt{}
}
var err error
switch {
case bytes.HasPrefix(k, htlcAttemptInfoKey):
attemptInfo, err := readHtlcAttemptInfo(v)
if err != nil {
return err
}
attemptInfo.AttemptID = aid
htlcsMap[aid].HTLCAttemptInfo = *attemptInfo
attemptInfoCount++
case bytes.HasPrefix(k, htlcSettleInfoKey):
htlcsMap[aid].Settle, err = readHtlcSettleInfo(v)
if err != nil {
return err
}
case bytes.HasPrefix(k, htlcFailInfoKey):
htlcsMap[aid].Failure, err = readHtlcFailInfo(v)
if err != nil {
return err
}
default:
return fmt.Errorf("unknown htlc attempt key")
}
return nil
})
if err != nil {
return nil, err
}
// Sanity check that all htlcs have an attempt info.
if attemptInfoCount != len(htlcsMap) {
return nil, errNoAttemptInfo
}
keys := make([]uint64, len(htlcsMap))
i := 0
for k := range htlcsMap {
keys[i] = k
i++
}
// Sort HTLC attempts by their attempt ID. This is needed because in the
// DB we store the attempts with keys prefixed by their status which
// changes order (groups them together by status).
sort.Slice(keys, func(i, j int) bool {
return keys[i] < keys[j]
})
htlcs := make([]HTLCAttempt, len(htlcsMap))
for i, key := range keys {
htlcs[i] = *htlcsMap[key]
}
return htlcs, nil
}
// readHtlcAttemptInfo reads the payment attempt info for this htlc.
func readHtlcAttemptInfo(b []byte) (*HTLCAttemptInfo, error) {
r := bytes.NewReader(b)
return deserializeHTLCAttemptInfo(r)
}
// readHtlcSettleInfo reads the settle info for the htlc. If the htlc isn't
// settled, nil is returned.
func readHtlcSettleInfo(b []byte) (*HTLCSettleInfo, error) {
r := bytes.NewReader(b)
return deserializeHTLCSettleInfo(r)
}
// readHtlcFailInfo reads the failure info for the htlc. If the htlc hasn't
// failed, nil is returned.
func readHtlcFailInfo(b []byte) (*HTLCFailInfo, error) {
r := bytes.NewReader(b)
return deserializeHTLCFailInfo(r)
}
// fetchFailedHtlcKeys retrieves the bucket keys of all failed HTLCs of a
// payment bucket.
func fetchFailedHtlcKeys(bucket kvdb.RBucket) ([][]byte, error) {
htlcsBucket := bucket.NestedReadBucket(paymentHtlcsBucket)
var htlcs []HTLCAttempt
var err error
if htlcsBucket != nil {
htlcs, err = fetchHtlcAttempts(htlcsBucket)
if err != nil {
return nil, err
}
}
// Now iterate though them and save the bucket keys for the failed
// HTLCs.
var htlcKeys [][]byte
for _, h := range htlcs {
if h.Failure == nil {
continue
}
htlcKeyBytes := make([]byte, 8)
binary.BigEndian.PutUint64(htlcKeyBytes, h.AttemptID)
htlcKeys = append(htlcKeys, htlcKeyBytes)
}
return htlcKeys, nil
}
// PaymentsQuery represents a query to the payments database starting or ending
// at a certain offset index. The number of retrieved records can be limited.
type PaymentsQuery struct {
// IndexOffset determines the starting point of the payments query and
// is always exclusive. In normal order, the query starts at the next
// higher (available) index compared to IndexOffset. In reversed order,
// the query ends at the next lower (available) index compared to the
// IndexOffset. In the case of a zero index_offset, the query will start
// with the oldest payment when paginating forwards, or will end with
// the most recent payment when paginating backwards.
IndexOffset uint64
// MaxPayments is the maximal number of payments returned in the
// payments query.
MaxPayments uint64
// Reversed gives a meaning to the IndexOffset. If reversed is set to
// true, the query will fetch payments with indices lower than the
// IndexOffset, otherwise, it will return payments with indices greater
// than the IndexOffset.
Reversed bool
// If IncludeIncomplete is true, then return payments that have not yet
// fully completed. This means that pending payments, as well as failed
// payments will show up if this field is set to true.
IncludeIncomplete bool
// CountTotal indicates that all payments currently present in the
// payment index (complete and incomplete) should be counted.
CountTotal bool
// CreationDateStart, expressed in Unix seconds, if set, filters out
// all payments with a creation date greater than or equal to it.
CreationDateStart int64
// CreationDateEnd, expressed in Unix seconds, if set, filters out all
// payments with a creation date less than or equal to it.
CreationDateEnd int64
}
// PaymentsResponse contains the result of a query to the payments database.
// It includes the set of payments that match the query and integers which
// represent the index of the first and last item returned in the series of
// payments. These integers allow callers to resume their query in the event
// that the query's response exceeds the max number of returnable events.
type PaymentsResponse struct {
// Payments is the set of payments returned from the database for the
// PaymentsQuery.
Payments []*MPPayment
// FirstIndexOffset is the index of the first element in the set of
// returned MPPayments. Callers can use this to resume their query
// in the event that the slice has too many events to fit into a single
// response. The offset can be used to continue reverse pagination.
FirstIndexOffset uint64
// LastIndexOffset is the index of the last element in the set of
// returned MPPayments. Callers can use this to resume their query
// in the event that the slice has too many events to fit into a single
// response. The offset can be used to continue forward pagination.
LastIndexOffset uint64
// TotalCount represents the total number of payments that are currently
// stored in the payment database. This will only be set if the
// CountTotal field in the query was set to true.
TotalCount uint64
}
// QueryPayments is a query to the payments database which is restricted
// to a subset of payments by the payments query, containing an offset
// index and a maximum number of returned payments.
func (d *DB) QueryPayments(query PaymentsQuery) (PaymentsResponse, error) {
var resp PaymentsResponse
if err := kvdb.View(d, func(tx kvdb.RTx) error {
// Get the root payments bucket.
paymentsBucket := tx.ReadBucket(paymentsRootBucket)
if paymentsBucket == nil {
return nil
}
// Get the index bucket which maps sequence number -> payment
// hash and duplicate bool. If we have a payments bucket, we
// should have an indexes bucket as well.
indexes := tx.ReadBucket(paymentsIndexBucket)
if indexes == nil {
return fmt.Errorf("index bucket does not exist")
}
// accumulatePayments gets payments with the sequence number
// and hash provided and adds them to our list of payments if
// they meet the criteria of our query. It returns the number
// of payments that were added.
accumulatePayments := func(sequenceKey, hash []byte) (bool,
error) {
r := bytes.NewReader(hash)
paymentHash, err := deserializePaymentIndex(r)
if err != nil {
return false, err
}
payment, err := fetchPaymentWithSequenceNumber(
tx, paymentHash, sequenceKey,
)
if err != nil {
return false, err
}
// To keep compatibility with the old API, we only
// return non-succeeded payments if requested.
if payment.Status != StatusSucceeded &&
!query.IncludeIncomplete {
return false, err
}
// Get the creation time in Unix seconds, this always
// rounds down the nanoseconds to full seconds.
createTime := payment.Info.CreationTime.Unix()
// Skip any payments that were created before the
// specified time.
if createTime < query.CreationDateStart {
return false, nil
}
// Skip any payments that were created after the
// specified time.
if query.CreationDateEnd != 0 &&
createTime > query.CreationDateEnd {
return false, nil
}
// At this point, we've exhausted the offset, so we'll
// begin collecting invoices found within the range.
resp.Payments = append(resp.Payments, payment)
return true, nil
}
// Create a paginator which reads from our sequence index bucket
// with the parameters provided by the payments query.
paginator := newPaginator(
indexes.ReadCursor(), query.Reversed, query.IndexOffset,
query.MaxPayments,
)
// Run a paginated query, adding payments to our response.
if err := paginator.query(accumulatePayments); err != nil {
return err
}
// Counting the total number of payments is expensive, since we
// literally have to traverse the cursor linearly, which can
// take quite a while. So it's an optional query parameter.
if query.CountTotal {
var (
totalPayments uint64
err error
)
countFn := func(_, _ []byte) error {
totalPayments++
return nil
}
// In non-boltdb database backends, there's a faster
// ForAll query that allows for batch fetching items.
if fastBucket, ok := indexes.(kvdb.ExtendedRBucket); ok {
err = fastBucket.ForAll(countFn)
} else {
err = indexes.ForEach(countFn)
}
if err != nil {
return fmt.Errorf("error counting payments: %w",
err)
}
resp.TotalCount = totalPayments
}
return nil
}, func() {
resp = PaymentsResponse{}
}); err != nil {
return resp, err
}
// Need to swap the payments slice order if reversed order.
if query.Reversed {
for l, r := 0, len(resp.Payments)-1; l < r; l, r = l+1, r-1 {
resp.Payments[l], resp.Payments[r] =
resp.Payments[r], resp.Payments[l]
}
}
// Set the first and last index of the returned payments so that the
// caller can resume from this point later on.
if len(resp.Payments) > 0 {
resp.FirstIndexOffset = resp.Payments[0].SequenceNum
resp.LastIndexOffset =
resp.Payments[len(resp.Payments)-1].SequenceNum
}
return resp, nil
}
// fetchPaymentWithSequenceNumber get the payment which matches the payment hash
// *and* sequence number provided from the database. This is required because
// we previously had more than one payment per hash, so we have multiple indexes
// pointing to a single payment; we want to retrieve the correct one.
func fetchPaymentWithSequenceNumber(tx kvdb.RTx, paymentHash lntypes.Hash,
sequenceNumber []byte) (*MPPayment, error) {
// We can now lookup the payment keyed by its hash in
// the payments root bucket.
bucket, err := fetchPaymentBucket(tx, paymentHash)
if err != nil {
return nil, err
}
// A single payment hash can have multiple payments associated with it.
// We lookup our sequence number first, to determine whether this is
// the payment we are actually looking for.
seqBytes := bucket.Get(paymentSequenceKey)
if seqBytes == nil {
return nil, ErrNoSequenceNumber
}
// If this top level payment has the sequence number we are looking for,
// return it.
if bytes.Equal(seqBytes, sequenceNumber) {
return fetchPayment(bucket)
}
// If we were not looking for the top level payment, we are looking for
// one of our duplicate payments. We need to iterate through the seq
// numbers in this bucket to find the correct payments. If we do not
// find a duplicate payments bucket here, something is wrong.
dup := bucket.NestedReadBucket(duplicatePaymentsBucket)
if dup == nil {
return nil, ErrNoDuplicateBucket
}
var duplicatePayment *MPPayment
err = dup.ForEach(func(k, v []byte) error {
subBucket := dup.NestedReadBucket(k)
if subBucket == nil {
// We one bucket for each duplicate to be found.
return ErrNoDuplicateNestedBucket
}
seqBytes := subBucket.Get(duplicatePaymentSequenceKey)
if seqBytes == nil {
return err
}
// If this duplicate payment is not the sequence number we are
// looking for, we can continue.
if !bytes.Equal(seqBytes, sequenceNumber) {
return nil
}
duplicatePayment, err = fetchDuplicatePayment(subBucket)
if err != nil {
return err
}
return nil
})
if err != nil {
return nil, err
}
// If none of the duplicate payments matched our sequence number, we
// failed to find the payment with this sequence number; something is
// wrong.
if duplicatePayment == nil {
return nil, ErrDuplicateNotFound
}
return duplicatePayment, nil
}
// DeletePayment deletes a payment from the DB given its payment hash. If
// failedHtlcsOnly is set, only failed HTLC attempts of the payment will be
// deleted.
func (d *DB) DeletePayment(paymentHash lntypes.Hash,
failedHtlcsOnly bool) error {
return kvdb.Update(d, func(tx kvdb.RwTx) error {
payments := tx.ReadWriteBucket(paymentsRootBucket)
if payments == nil {
return nil
}
bucket := payments.NestedReadWriteBucket(paymentHash[:])
if bucket == nil {
return fmt.Errorf("non bucket element in payments " +
"bucket")
}
// If the status is InFlight, we cannot safely delete
// the payment information, so we return early.
paymentStatus, err := fetchPaymentStatus(bucket)
if err != nil {
return err
}
// If the payment has inflight HTLCs, we cannot safely delete
// the payment information, so we return an error.
if err := paymentStatus.removable(); err != nil {
return fmt.Errorf("payment '%v' has inflight HTLCs"+
"and therefore cannot be deleted: %w",
paymentHash.String(), err)
}
// Delete the failed HTLC attempts we found.
if failedHtlcsOnly {
toDelete, err := fetchFailedHtlcKeys(bucket)
if err != nil {
return err
}
htlcsBucket := bucket.NestedReadWriteBucket(
paymentHtlcsBucket,
)
for _, htlcID := range toDelete {
err = htlcsBucket.Delete(
htlcBucketKey(htlcAttemptInfoKey, htlcID),
)
if err != nil {
return err
}
err = htlcsBucket.Delete(
htlcBucketKey(htlcFailInfoKey, htlcID),
)
if err != nil {
return err
}
err = htlcsBucket.Delete(
htlcBucketKey(htlcSettleInfoKey, htlcID),
)
if err != nil {
return err
}
}
return nil
}
seqNrs, err := fetchSequenceNumbers(bucket)
if err != nil {
return err
}
if err := payments.DeleteNestedBucket(paymentHash[:]); err != nil {
return err
}
indexBucket := tx.ReadWriteBucket(paymentsIndexBucket)
for _, k := range seqNrs {
if err := indexBucket.Delete(k); err != nil {
return err
}
}
return nil
}, func() {})
}
// DeletePayments deletes all completed and failed payments from the DB. If
// failedOnly is set, only failed payments will be considered for deletion. If
// failedHtlcsOnly is set, the payment itself won't be deleted, only failed HTLC
// attempts. The method returns the number of deleted payments, which is always
// 0 if failedHtlcsOnly is set.
func (d *DB) DeletePayments(failedOnly, failedHtlcsOnly bool) (int, error) {
var numPayments int
err := kvdb.Update(d, func(tx kvdb.RwTx) error {
payments := tx.ReadWriteBucket(paymentsRootBucket)
if payments == nil {
return nil
}
var (
// deleteBuckets is the set of payment buckets we need
// to delete.
deleteBuckets [][]byte
// deleteIndexes is the set of indexes pointing to these
// payments that need to be deleted.
deleteIndexes [][]byte
// deleteHtlcs maps a payment hash to the HTLC IDs we
// want to delete for that payment.
deleteHtlcs = make(map[lntypes.Hash][][]byte)
)
err := payments.ForEach(func(k, _ []byte) error {
bucket := payments.NestedReadBucket(k)
if bucket == nil {
// We only expect sub-buckets to be found in
// this top-level bucket.
return fmt.Errorf("non bucket element in " +
"payments bucket")
}
// If the status is InFlight, we cannot safely delete
// the payment information, so we return early.
paymentStatus, err := fetchPaymentStatus(bucket)
if err != nil {
return err
}
// If the payment has inflight HTLCs, we cannot safely
// delete the payment information, so we return an nil
// to skip it.
if err := paymentStatus.removable(); err != nil {
return nil
}
// If we requested to only delete failed payments, we
// can return if this one is not.
if failedOnly && paymentStatus != StatusFailed {
return nil
}
// If we are only deleting failed HTLCs, fetch them.
if failedHtlcsOnly {
toDelete, err := fetchFailedHtlcKeys(bucket)
if err != nil {
return err
}
hash, err := lntypes.MakeHash(k)
if err != nil {
return err
}
deleteHtlcs[hash] = toDelete
// We return, we are only deleting attempts.
return nil
}
// Add the bucket to the set of buckets we can delete.
deleteBuckets = append(deleteBuckets, k)
// Get all the sequence number associated with the
// payment, including duplicates.
seqNrs, err := fetchSequenceNumbers(bucket)
if err != nil {
return err
}
deleteIndexes = append(deleteIndexes, seqNrs...)
numPayments++
return nil
})
if err != nil {
return err
}
// Delete the failed HTLC attempts we found.
for hash, htlcIDs := range deleteHtlcs {
bucket := payments.NestedReadWriteBucket(hash[:])
htlcsBucket := bucket.NestedReadWriteBucket(
paymentHtlcsBucket,
)
for _, aid := range htlcIDs {
if err := htlcsBucket.Delete(
htlcBucketKey(htlcAttemptInfoKey, aid),
); err != nil {
return err
}
if err := htlcsBucket.Delete(
htlcBucketKey(htlcFailInfoKey, aid),
); err != nil {
return err
}
if err := htlcsBucket.Delete(
htlcBucketKey(htlcSettleInfoKey, aid),
); err != nil {
return err
}
}
}
for _, k := range deleteBuckets {
if err := payments.DeleteNestedBucket(k); err != nil {
return err
}
}
// Get our index bucket and delete all indexes pointing to the
// payments we are deleting.
indexBucket := tx.ReadWriteBucket(paymentsIndexBucket)
for _, k := range deleteIndexes {
if err := indexBucket.Delete(k); err != nil {
return err
}
}
return nil
}, func() {
numPayments = 0
})
if err != nil {
return 0, err
}
return numPayments, nil
}
// fetchSequenceNumbers fetches all the sequence numbers associated with a
// payment, including those belonging to any duplicate payments.
func fetchSequenceNumbers(paymentBucket kvdb.RBucket) ([][]byte, error) {
seqNum := paymentBucket.Get(paymentSequenceKey)
if seqNum == nil {
return nil, errors.New("expected sequence number")
}
sequenceNumbers := [][]byte{seqNum}
// Get the duplicate payments bucket, if it has no duplicates, just
// return early with the payment sequence number.
duplicates := paymentBucket.NestedReadBucket(duplicatePaymentsBucket)
if duplicates == nil {
return sequenceNumbers, nil
}
// If we do have duplicated, they are keyed by sequence number, so we
// iterate through the duplicates bucket and add them to our set of
// sequence numbers.
if err := duplicates.ForEach(func(k, v []byte) error {
sequenceNumbers = append(sequenceNumbers, k)
return nil
}); err != nil {
return nil, err
}
return sequenceNumbers, nil
}
// nolint: dupl
func serializePaymentCreationInfo(w io.Writer, c *PaymentCreationInfo) error {
var scratch [8]byte
if _, err := w.Write(c.PaymentIdentifier[:]); err != nil {
return err
}
byteOrder.PutUint64(scratch[:], uint64(c.Value))
if _, err := w.Write(scratch[:]); err != nil {
return err
}
if err := serializeTime(w, c.CreationTime); err != nil {
return err
}
byteOrder.PutUint32(scratch[:4], uint32(len(c.PaymentRequest)))
if _, err := w.Write(scratch[:4]); err != nil {
return err
}
if _, err := w.Write(c.PaymentRequest[:]); err != nil {
return err
}
// Any remaining bytes are TLV encoded records. Currently, these are
// only the custom records provided by the user to be sent to the first
// hop. But this can easily be extended with further records by merging
// the records into a single TLV stream.
err := c.FirstHopCustomRecords.SerializeTo(w)
if err != nil {
return err
}
return nil
}
func deserializePaymentCreationInfo(r io.Reader) (*PaymentCreationInfo,
error) {
var scratch [8]byte
c := &PaymentCreationInfo{}
if _, err := io.ReadFull(r, c.PaymentIdentifier[:]); err != nil {
return nil, err
}
if _, err := io.ReadFull(r, scratch[:]); err != nil {
return nil, err
}
c.Value = lnwire.MilliSatoshi(byteOrder.Uint64(scratch[:]))
creationTime, err := deserializeTime(r)
if err != nil {
return nil, err
}
c.CreationTime = creationTime
if _, err := io.ReadFull(r, scratch[:4]); err != nil {
return nil, err
}
reqLen := uint32(byteOrder.Uint32(scratch[:4]))
payReq := make([]byte, reqLen)
if reqLen > 0 {
if _, err := io.ReadFull(r, payReq); err != nil {
return nil, err
}
}
c.PaymentRequest = payReq
// Any remaining bytes are TLV encoded records. Currently, these are
// only the custom records provided by the user to be sent to the first
// hop. But this can easily be extended with further records by merging
// the records into a single TLV stream.
c.FirstHopCustomRecords, err = lnwire.ParseCustomRecordsFrom(r)
if err != nil {
return nil, err
}
return c, nil
}
func serializeHTLCAttemptInfo(w io.Writer, a *HTLCAttemptInfo) error {
if err := WriteElements(w, a.sessionKey); err != nil {
return err
}
if err := SerializeRoute(w, a.Route); err != nil {
return err
}
if err := serializeTime(w, a.AttemptTime); err != nil {
return err
}
// If the hash is nil we can just return.
if a.Hash == nil {
return nil
}
if _, err := w.Write(a.Hash[:]); err != nil {
return err
}
// Merge the fixed/known records together with the custom records to
// serialize them as a single blob. We can't do this in SerializeRoute
// because we're in the middle of the byte stream there. We can only do
// TLV serialization at the end of the stream, since EOF is allowed for
// a stream if no more data is expected.
producers := []tlv.RecordProducer{
&a.Route.FirstHopAmount,
}
tlvData, err := lnwire.MergeAndEncode(
producers, nil, a.Route.FirstHopWireCustomRecords,
)
if err != nil {
return err
}
if _, err := w.Write(tlvData); err != nil {
return err
}
return nil
}
func deserializeHTLCAttemptInfo(r io.Reader) (*HTLCAttemptInfo, error) {
a := &HTLCAttemptInfo{}
err := ReadElements(r, &a.sessionKey)
if err != nil {
return nil, err
}
a.Route, err = DeserializeRoute(r)
if err != nil {
return nil, err
}
a.AttemptTime, err = deserializeTime(r)
if err != nil {
return nil, err
}
hash := lntypes.Hash{}
_, err = io.ReadFull(r, hash[:])
switch {
// Older payment attempts wouldn't have the hash set, in which case we
// can just return.
case err == io.EOF, err == io.ErrUnexpectedEOF:
return a, nil
case err != nil:
return nil, err
default:
}
a.Hash = &hash
// Read any remaining data (if any) and parse it into the known records
// and custom records.
extraData, err := io.ReadAll(r)
if err != nil {
return nil, err
}
customRecords, _, _, err := lnwire.ParseAndExtractCustomRecords(
extraData, &a.Route.FirstHopAmount,
)
if err != nil {
return nil, err
}
a.Route.FirstHopWireCustomRecords = customRecords
return a, nil
}
func serializeHop(w io.Writer, h *route.Hop) error {
if err := WriteElements(w,
h.PubKeyBytes[:],
h.ChannelID,
h.OutgoingTimeLock,
h.AmtToForward,
); err != nil {
return err
}
if err := binary.Write(w, byteOrder, h.LegacyPayload); err != nil {
return err
}
// For legacy payloads, we don't need to write any TLV records, so
// we'll write a zero indicating the our serialized TLV map has no
// records.
if h.LegacyPayload {
return WriteElements(w, uint32(0))
}
// Gather all non-primitive TLV records so that they can be serialized
// as a single blob.
//
// TODO(conner): add migration to unify all fields in a single TLV
// blobs. The split approach will cause headaches down the road as more
// fields are added, which we can avoid by having a single TLV stream
// for all payload fields.
var records []tlv.Record
if h.MPP != nil {
records = append(records, h.MPP.Record())
}
// Add blinding point and encrypted data if present.
if h.EncryptedData != nil {
records = append(records, record.NewEncryptedDataRecord(
&h.EncryptedData,
))
}
if h.BlindingPoint != nil {
records = append(records, record.NewBlindingPointRecord(
&h.BlindingPoint,
))
}
if h.AMP != nil {
records = append(records, h.AMP.Record())
}
if h.Metadata != nil {
records = append(records, record.NewMetadataRecord(&h.Metadata))
}
if h.TotalAmtMsat != 0 {
totalMsatInt := uint64(h.TotalAmtMsat)
records = append(
records, record.NewTotalAmtMsatBlinded(&totalMsatInt),
)
}
// Final sanity check to absolutely rule out custom records that are not
// custom and write into the standard range.
if err := h.CustomRecords.Validate(); err != nil {
return err
}
// Convert custom records to tlv and add to the record list.
// MapToRecords sorts the list, so adding it here will keep the list
// canonical.
tlvRecords := tlv.MapToRecords(h.CustomRecords)
records = append(records, tlvRecords...)
// Otherwise, we'll transform our slice of records into a map of the
// raw bytes, then serialize them in-line with a length (number of
// elements) prefix.
mapRecords, err := tlv.RecordsToMap(records)
if err != nil {
return err
}
numRecords := uint32(len(mapRecords))
if err := WriteElements(w, numRecords); err != nil {
return err
}
for recordType, rawBytes := range mapRecords {
if err := WriteElements(w, recordType); err != nil {
return err
}
if err := wire.WriteVarBytes(w, 0, rawBytes); err != nil {
return err
}
}
return nil
}
// maxOnionPayloadSize is the largest Sphinx payload possible, so we don't need
// to read/write a TLV stream larger than this.
const maxOnionPayloadSize = 1300
func deserializeHop(r io.Reader) (*route.Hop, error) {
h := &route.Hop{}
var pub []byte
if err := ReadElements(r, &pub); err != nil {
return nil, err
}
copy(h.PubKeyBytes[:], pub)
if err := ReadElements(r,
&h.ChannelID, &h.OutgoingTimeLock, &h.AmtToForward,
); err != nil {
return nil, err
}
// TODO(roasbeef): change field to allow LegacyPayload false to be the
// legacy default?
err := binary.Read(r, byteOrder, &h.LegacyPayload)
if err != nil {
return nil, err
}
var numElements uint32
if err := ReadElements(r, &numElements); err != nil {
return nil, err
}
// If there're no elements, then we can return early.
if numElements == 0 {
return h, nil
}
tlvMap := make(map[uint64][]byte)
for i := uint32(0); i < numElements; i++ {
var tlvType uint64
if err := ReadElements(r, &tlvType); err != nil {
return nil, err
}
rawRecordBytes, err := wire.ReadVarBytes(
r, 0, maxOnionPayloadSize, "tlv",
)
if err != nil {
return nil, err
}
tlvMap[tlvType] = rawRecordBytes
}
// If the MPP type is present, remove it from the generic TLV map and
// parse it back into a proper MPP struct.
//
// TODO(conner): add migration to unify all fields in a single TLV
// blobs. The split approach will cause headaches down the road as more
// fields are added, which we can avoid by having a single TLV stream
// for all payload fields.
mppType := uint64(record.MPPOnionType)
if mppBytes, ok := tlvMap[mppType]; ok {
delete(tlvMap, mppType)
var (
mpp = &record.MPP{}
mppRec = mpp.Record()
r = bytes.NewReader(mppBytes)
)
err := mppRec.Decode(r, uint64(len(mppBytes)))
if err != nil {
return nil, err
}
h.MPP = mpp
}
// If encrypted data or blinding key are present, remove them from
// the TLV map and parse into proper types.
encryptedDataType := uint64(record.EncryptedDataOnionType)
if data, ok := tlvMap[encryptedDataType]; ok {
delete(tlvMap, encryptedDataType)
h.EncryptedData = data
}
blindingType := uint64(record.BlindingPointOnionType)
if blindingPoint, ok := tlvMap[blindingType]; ok {
delete(tlvMap, blindingType)
h.BlindingPoint, err = btcec.ParsePubKey(blindingPoint)
if err != nil {
return nil, fmt.Errorf("invalid blinding point: %w",
err)
}
}
ampType := uint64(record.AMPOnionType)
if ampBytes, ok := tlvMap[ampType]; ok {
delete(tlvMap, ampType)
var (
amp = &record.AMP{}
ampRec = amp.Record()
r = bytes.NewReader(ampBytes)
)
err := ampRec.Decode(r, uint64(len(ampBytes)))
if err != nil {
return nil, err
}
h.AMP = amp
}
// If the metadata type is present, remove it from the tlv map and
// populate directly on the hop.
metadataType := uint64(record.MetadataOnionType)
if metadata, ok := tlvMap[metadataType]; ok {
delete(tlvMap, metadataType)
h.Metadata = metadata
}
totalAmtMsatType := uint64(record.TotalAmtMsatBlindedType)
if totalAmtMsat, ok := tlvMap[totalAmtMsatType]; ok {
delete(tlvMap, totalAmtMsatType)
var (
totalAmtMsatInt uint64
buf [8]byte
)
if err := tlv.DTUint64(
bytes.NewReader(totalAmtMsat),
&totalAmtMsatInt,
&buf,
uint64(len(totalAmtMsat)),
); err != nil {
return nil, err
}
h.TotalAmtMsat = lnwire.MilliSatoshi(totalAmtMsatInt)
}
h.CustomRecords = tlvMap
return h, nil
}
// SerializeRoute serializes a route.
func SerializeRoute(w io.Writer, r route.Route) error {
if err := WriteElements(w,
r.TotalTimeLock, r.TotalAmount, r.SourcePubKey[:],
); err != nil {
return err
}
if err := WriteElements(w, uint32(len(r.Hops))); err != nil {
return err
}
for _, h := range r.Hops {
if err := serializeHop(w, h); err != nil {
return err
}
}
// Any new/extra TLV data is encoded in serializeHTLCAttemptInfo!
return nil
}
// DeserializeRoute deserializes a route.
func DeserializeRoute(r io.Reader) (route.Route, error) {
rt := route.Route{}
if err := ReadElements(r,
&rt.TotalTimeLock, &rt.TotalAmount,
); err != nil {
return rt, err
}
var pub []byte
if err := ReadElements(r, &pub); err != nil {
return rt, err
}
copy(rt.SourcePubKey[:], pub)
var numHops uint32
if err := ReadElements(r, &numHops); err != nil {
return rt, err
}
var hops []*route.Hop
for i := uint32(0); i < numHops; i++ {
hop, err := deserializeHop(r)
if err != nil {
return rt, err
}
hops = append(hops, hop)
}
rt.Hops = hops
// Any new/extra TLV data is decoded in deserializeHTLCAttemptInfo!
return rt, nil
}
package channeldb
import (
"bytes"
"errors"
"fmt"
"time"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/routing/route"
)
var (
// peersBucket is the name of a top level bucket in which we store
// information about our peers. Information for different peers is
// stored in buckets keyed by their public key.
//
//
// peers-bucket
// |
// |-- <peer-pubkey>
// | |--flap-count-key: <ts><flap count>
// |
// |-- <peer-pubkey>
// | |--flap-count-key: <ts><flap count>
peersBucket = []byte("peers-bucket")
// flapCountKey is a key used in the peer pubkey sub-bucket that stores
// the timestamp of a peer's last flap count and its all time flap
// count.
flapCountKey = []byte("flap-count")
)
var (
// ErrNoPeerBucket is returned when we try to read entries for a peer
// that is not tracked.
ErrNoPeerBucket = errors.New("peer bucket not found")
)
// FlapCount contains information about a peer's flap count.
type FlapCount struct {
// Count provides the total flap count for a peer.
Count uint32
// LastFlap is the timestamp of the last flap recorded for a peer.
LastFlap time.Time
}
// WriteFlapCounts writes the flap count for a set of peers to disk, creating a
// bucket for the peer's pubkey if necessary. Note that this function overwrites
// the current value.
func (d *DB) WriteFlapCounts(flapCounts map[route.Vertex]*FlapCount) error {
// Exit early if there are no updates.
if len(flapCounts) == 0 {
log.Debugf("No flap counts to write, skipped db update")
return nil
}
return kvdb.Update(d, func(tx kvdb.RwTx) error {
// Run through our set of flap counts and record them for
// each peer, creating a bucket for the peer pubkey if required.
for peer, flapCount := range flapCounts {
peers := tx.ReadWriteBucket(peersBucket)
peerBucket, err := peers.CreateBucketIfNotExists(
peer[:],
)
if err != nil {
return err
}
var b bytes.Buffer
err = serializeTime(&b, flapCount.LastFlap)
if err != nil {
return err
}
if err = WriteElement(&b, flapCount.Count); err != nil {
return err
}
err = peerBucket.Put(flapCountKey, b.Bytes())
if err != nil {
return err
}
}
return nil
}, func() {})
}
// ReadFlapCount attempts to read the flap count for a peer, failing if the
// peer is not found or we do not have flap count stored.
func (d *DB) ReadFlapCount(pubkey route.Vertex) (*FlapCount, error) {
var flapCount FlapCount
if err := kvdb.View(d, func(tx kvdb.RTx) error {
peers := tx.ReadBucket(peersBucket)
peerBucket := peers.NestedReadBucket(pubkey[:])
if peerBucket == nil {
return ErrNoPeerBucket
}
flapBytes := peerBucket.Get(flapCountKey)
if flapBytes == nil {
return fmt.Errorf("flap count not recorded for: %v",
pubkey)
}
var (
err error
r = bytes.NewReader(flapBytes)
)
flapCount.LastFlap, err = deserializeTime(r)
if err != nil {
return err
}
return ReadElements(r, &flapCount.Count)
}, func() {
flapCount = FlapCount{}
}); err != nil {
return nil, err
}
return &flapCount, nil
}
package channeldb
import (
"bytes"
"errors"
"io"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/tlv"
)
var (
// closeSummaryBucket is a top level bucket which holds additional
// information about channel closes. It nests channels by chainhash
// and channel point.
// [closeSummaryBucket]
// [chainHashBucket]
// [channelBucket]
// [resolversBucket]
closeSummaryBucket = []byte("close-summaries")
// resolversBucket holds the outcome of a channel's resolvers. It is
// nested under a channel and chainhash bucket in the close summaries
// bucket.
resolversBucket = []byte("resolvers-bucket")
)
var (
// ErrNoChainHashBucket is returned when we have not created a bucket
// for the current chain hash.
ErrNoChainHashBucket = errors.New("no chain hash bucket")
// ErrNoChannelSummaries is returned when a channel is not found in the
// chain hash bucket.
ErrNoChannelSummaries = errors.New("channel bucket not found")
amountType tlv.Type = 1
resolverType tlv.Type = 2
outcomeType tlv.Type = 3
spendTxIDType tlv.Type = 4
)
// ResolverType indicates the type of resolver that was resolved on chain.
type ResolverType uint8
const (
// ResolverTypeAnchor represents a resolver for an anchor output.
ResolverTypeAnchor ResolverType = 0
// ResolverTypeIncomingHtlc represents resolution of an incoming htlc.
ResolverTypeIncomingHtlc ResolverType = 1
// ResolverTypeOutgoingHtlc represents resolution of an outgoing htlc.
ResolverTypeOutgoingHtlc ResolverType = 2
// ResolverTypeCommit represents resolution of our time locked commit
// when we force close.
ResolverTypeCommit ResolverType = 3
)
// ResolverOutcome indicates the outcome for the resolver that that the contract
// court reached. This state is not necessarily final, since htlcs on our own
// commitment are resolved across two resolvers.
type ResolverOutcome uint8
const (
// ResolverOutcomeClaimed indicates that funds were claimed on chain.
ResolverOutcomeClaimed ResolverOutcome = 0
// ResolverOutcomeUnclaimed indicates that we did not claim our funds on
// chain. This may be the case for anchors that we did not sweep, or
// outputs that were not economical to sweep.
ResolverOutcomeUnclaimed ResolverOutcome = 1
// ResolverOutcomeAbandoned indicates that we did not attempt to claim
// an output on chain. This is the case for htlcs that we could not
// decode to claim, or invoice which we fail when an attempt is made
// to settle them on chain.
ResolverOutcomeAbandoned ResolverOutcome = 2
// ResolverOutcomeTimeout indicates that a contract was timed out on
// chain.
ResolverOutcomeTimeout ResolverOutcome = 3
// ResolverOutcomeFirstStage indicates that a htlc had to be claimed
// over two stages, with this outcome representing the confirmation
// of our success/timeout tx.
ResolverOutcomeFirstStage ResolverOutcome = 4
)
// ResolverReport provides an account of the outcome of a resolver. This differs
// from a ContractReport because it does not necessarily fully resolve the
// contract; each step of two stage htlc resolution is included.
type ResolverReport struct {
// OutPoint is the on chain outpoint that was spent as a result of this
// resolution. When an output is directly resolved (eg, commitment
// sweeps and single stage htlcs on the remote party's output) this
// is an output on the commitment tx that was broadcast. When we resolve
// across two stages (eg, htlcs on our own force close commit), the
// first stage outpoint is the output on our commitment and the second
// stage output is the spend from our htlc success/timeout tx.
OutPoint wire.OutPoint
// Amount is the value of the output referenced above.
Amount btcutil.Amount
// ResolverType indicates the type of resolution that occurred.
ResolverType
// ResolverOutcome indicates the outcome of the resolver.
ResolverOutcome
// SpendTxID is the transaction ID of the spending transaction that
// claimed the outpoint. This may be a sweep transaction, or a first
// stage success/timeout transaction.
SpendTxID *chainhash.Hash
}
// PutResolverReport creates and commits a transaction that is used to write a
// resolver report to disk.
func (d *DB) PutResolverReport(tx kvdb.RwTx, chainHash chainhash.Hash,
channelOutpoint *wire.OutPoint, report *ResolverReport) error {
putReportFunc := func(tx kvdb.RwTx) error {
return putReport(tx, chainHash, channelOutpoint, report)
}
// If the transaction is nil, we'll create a new one.
if tx == nil {
return kvdb.Update(d, putReportFunc, func() {})
}
// Otherwise, we can write the report to disk using the existing
// transaction.
return putReportFunc(tx)
}
// putReport puts a report in the bucket provided, with its outpoint as its key.
func putReport(tx kvdb.RwTx, chainHash chainhash.Hash,
channelOutpoint *wire.OutPoint, report *ResolverReport) error {
channelBucket, err := fetchReportWriteBucket(
tx, chainHash, channelOutpoint,
)
if err != nil {
return err
}
// If the resolvers bucket does not exist yet, create it.
resolvers, err := channelBucket.CreateBucketIfNotExists(
resolversBucket,
)
if err != nil {
return err
}
var valueBuf bytes.Buffer
if err := serializeReport(&valueBuf, report); err != nil {
return err
}
// Finally write our outpoint to be used as the key for this record.
var keyBuf bytes.Buffer
if err := graphdb.WriteOutpoint(&keyBuf, &report.OutPoint); err != nil {
return err
}
return resolvers.Put(keyBuf.Bytes(), valueBuf.Bytes())
}
// serializeReport serialized a report using a TLV stream to allow for optional
// fields.
func serializeReport(w io.Writer, report *ResolverReport) error {
amt := uint64(report.Amount)
resolver := uint8(report.ResolverType)
outcome := uint8(report.ResolverOutcome)
// Create a set of TLV records for the values we know to be present.
records := []tlv.Record{
tlv.MakePrimitiveRecord(amountType, &amt),
tlv.MakePrimitiveRecord(resolverType, &resolver),
tlv.MakePrimitiveRecord(outcomeType, &outcome),
}
// If our spend txid is non-nil, we add a tlv entry for it.
if report.SpendTxID != nil {
var spendBuf bytes.Buffer
err := WriteElement(&spendBuf, *report.SpendTxID)
if err != nil {
return err
}
spendBytes := spendBuf.Bytes()
records = append(records, tlv.MakePrimitiveRecord(
spendTxIDType, &spendBytes,
))
}
// Create our stream and encode it.
tlvStream, err := tlv.NewStream(records...)
if err != nil {
return err
}
return tlvStream.Encode(w)
}
// FetchChannelReports fetches the set of reports for a channel.
func (d DB) FetchChannelReports(chainHash chainhash.Hash,
outPoint *wire.OutPoint) ([]*ResolverReport, error) {
var reports []*ResolverReport
if err := kvdb.View(d.Backend, func(tx kvdb.RTx) error {
chanBucket, err := fetchReportReadBucket(
tx, chainHash, outPoint,
)
if err != nil {
return err
}
// If there are no resolvers for this channel, we simply
// return nil, because nothing has been persisted yet.
resolvers := chanBucket.NestedReadBucket(resolversBucket)
if resolvers == nil {
return nil
}
// Run through each resolution and add it to our set of
// resolutions.
return resolvers.ForEach(func(k, v []byte) error {
// Deserialize the contents of our field.
r := bytes.NewReader(v)
report, err := deserializeReport(r)
if err != nil {
return err
}
// Once we have read our values out, set the outpoint
// on the report using the key.
r = bytes.NewReader(k)
if err := ReadElement(r, &report.OutPoint); err != nil {
return err
}
reports = append(reports, report)
return nil
})
}, func() {
reports = nil
}); err != nil {
return nil, err
}
return reports, nil
}
// deserializeReport gets a resolver report from a tlv stream. The outpoint on
// the resolver will not be set because we key reports by their outpoint, and
// this function reads only the values saved in the stream.
func deserializeReport(r io.Reader) (*ResolverReport, error) {
var (
resolver, outcome uint8
amt uint64
spentTx []byte
)
tlvStream, err := tlv.NewStream(
tlv.MakePrimitiveRecord(amountType, &amt),
tlv.MakePrimitiveRecord(resolverType, &resolver),
tlv.MakePrimitiveRecord(outcomeType, &outcome),
tlv.MakePrimitiveRecord(spendTxIDType, &spentTx),
)
if err != nil {
return nil, err
}
if err := tlvStream.Decode(r); err != nil {
return nil, err
}
report := &ResolverReport{
Amount: btcutil.Amount(amt),
ResolverOutcome: ResolverOutcome(outcome),
ResolverType: ResolverType(resolver),
}
// If our spend tx is set, we set it on our report.
if len(spentTx) != 0 {
spendTx, err := chainhash.NewHash(spentTx)
if err != nil {
return nil, err
}
report.SpendTxID = spendTx
}
return report, nil
}
// fetchReportWriteBucket returns a write channel bucket within the reports
// top level bucket. If the channel's bucket does not yet exist, it will be
// created.
func fetchReportWriteBucket(tx kvdb.RwTx, chainHash chainhash.Hash,
outPoint *wire.OutPoint) (kvdb.RwBucket, error) {
// Get the channel close summary bucket.
closedBucket := tx.ReadWriteBucket(closeSummaryBucket)
// Create the chain hash bucket if it does not exist.
chainHashBkt, err := closedBucket.CreateBucketIfNotExists(chainHash[:])
if err != nil {
return nil, err
}
var chanPointBuf bytes.Buffer
if err := graphdb.WriteOutpoint(&chanPointBuf, outPoint); err != nil {
return nil, err
}
return chainHashBkt.CreateBucketIfNotExists(chanPointBuf.Bytes())
}
// fetchReportReadBucket returns a read channel bucket within the reports
// top level bucket. If any bucket along the way does not exist, it will error.
func fetchReportReadBucket(tx kvdb.RTx, chainHash chainhash.Hash,
outPoint *wire.OutPoint) (kvdb.RBucket, error) {
// First fetch the top level channel close summary bucket.
closeBucket := tx.ReadBucket(closeSummaryBucket)
// Next we get the chain hash bucket for our current chain.
chainHashBucket := closeBucket.NestedReadBucket(chainHash[:])
if chainHashBucket == nil {
return nil, ErrNoChainHashBucket
}
// With the bucket for the node and chain fetched, we can now go down
// another level, for the channel itself.
var chanPointBuf bytes.Buffer
if err := graphdb.WriteOutpoint(&chanPointBuf, outPoint); err != nil {
return nil, err
}
chanBucket := chainHashBucket.NestedReadBucket(chanPointBuf.Bytes())
if chanBucket == nil {
return nil, ErrNoChannelSummaries
}
return chanBucket, nil
}
package channeldb
import (
"bytes"
"errors"
"io"
"math"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// OutputIndexEmpty is used when the output index doesn't exist.
OutputIndexEmpty = math.MaxUint16
)
type (
// BigSizeAmount is a type alias for a TLV record of a btcutil.Amount.
BigSizeAmount = tlv.BigSizeT[btcutil.Amount]
// BigSizeMilliSatoshi is a type alias for a TLV record of a
// lnwire.MilliSatoshi.
BigSizeMilliSatoshi = tlv.BigSizeT[lnwire.MilliSatoshi]
)
var (
// revocationLogBucketDeprecated is dedicated for storing the necessary
// delta state between channel updates required to re-construct a past
// state in order to punish a counterparty attempting a non-cooperative
// channel closure. This key should be accessed from within the
// sub-bucket of a target channel, identified by its channel point.
//
// Deprecated: This bucket is kept for read-only in case the user
// choose not to migrate the old data.
revocationLogBucketDeprecated = []byte("revocation-log-key")
// revocationLogBucket is a sub-bucket under openChannelBucket. This
// sub-bucket is dedicated for storing the minimal info required to
// re-construct a past state in order to punish a counterparty
// attempting a non-cooperative channel closure.
revocationLogBucket = []byte("revocation-log")
// ErrLogEntryNotFound is returned when we cannot find a log entry at
// the height requested in the revocation log.
ErrLogEntryNotFound = errors.New("log entry not found")
// ErrOutputIndexTooBig is returned when the output index is greater
// than uint16.
ErrOutputIndexTooBig = errors.New("output index is over uint16")
)
// SparsePayHash is a type alias for a 32 byte array, which when serialized is
// able to save some space by not including an empty payment hash on disk.
type SparsePayHash [32]byte
// NewSparsePayHash creates a new SparsePayHash from a 32 byte array.
func NewSparsePayHash(rHash [32]byte) SparsePayHash {
return SparsePayHash(rHash)
}
// Record returns a tlv record for the SparsePayHash.
func (s *SparsePayHash) Record() tlv.Record {
// We use a zero for the type here, as this'll be used along with the
// RecordT type.
return tlv.MakeDynamicRecord(
0, s, s.hashLen,
sparseHashEncoder, sparseHashDecoder,
)
}
// hashLen is used by MakeDynamicRecord to return the size of the RHash.
//
// NOTE: for zero hash, we return a length 0.
func (s *SparsePayHash) hashLen() uint64 {
if bytes.Equal(s[:], lntypes.ZeroHash[:]) {
return 0
}
return 32
}
// sparseHashEncoder is the customized encoder which skips encoding the empty
// hash.
func sparseHashEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
v, ok := val.(*SparsePayHash)
if !ok {
return tlv.NewTypeForEncodingErr(val, "SparsePayHash")
}
// If the value is an empty hash, we will skip encoding it.
if bytes.Equal(v[:], lntypes.ZeroHash[:]) {
return nil
}
vArray := (*[32]byte)(v)
return tlv.EBytes32(w, vArray, buf)
}
// sparseHashDecoder is the customized decoder which skips decoding the empty
// hash.
func sparseHashDecoder(r io.Reader, val interface{}, buf *[8]byte,
l uint64) error {
v, ok := val.(*SparsePayHash)
if !ok {
return tlv.NewTypeForEncodingErr(val, "SparsePayHash")
}
// If the length is zero, we will skip encoding the empty hash.
if l == 0 {
return nil
}
vArray := (*[32]byte)(v)
return tlv.DBytes32(r, vArray, buf, 32)
}
// HTLCEntry specifies the minimal info needed to be stored on disk for ALL the
// historical HTLCs, which is useful for constructing RevocationLog when a
// breach is detected.
// The actual size of each HTLCEntry varies based on its RHash and Amt(sat),
// summarized as follows,
//
// | RHash empty | Amt<=252 | Amt<=65,535 | Amt<=4,294,967,295 | otherwise |
// |:-----------:|:--------:|:-----------:|:------------------:|:---------:|
// | true | 19 | 21 | 23 | 26 |
// | false | 51 | 53 | 55 | 58 |
//
// So the size varies from 19 bytes to 58 bytes, where most likely to be 23 or
// 55 bytes.
//
// NOTE: all the fields saved to disk use the primitive go types so they can be
// made into tlv records without further conversion.
type HTLCEntry struct {
// RHash is the payment hash of the HTLC.
RHash tlv.RecordT[tlv.TlvType0, SparsePayHash]
// RefundTimeout is the absolute timeout on the HTLC that the sender
// must wait before reclaiming the funds in limbo.
RefundTimeout tlv.RecordT[tlv.TlvType1, uint32]
// OutputIndex is the output index for this particular HTLC output
// within the commitment transaction.
//
// NOTE: we use uint16 instead of int32 here to save us 2 bytes, which
// gives us a max number of HTLCs of 65K.
OutputIndex tlv.RecordT[tlv.TlvType2, uint16]
// Incoming denotes whether we're the receiver or the sender of this
// HTLC.
Incoming tlv.RecordT[tlv.TlvType3, bool]
// Amt is the amount of satoshis this HTLC escrows.
Amt tlv.RecordT[tlv.TlvType4, tlv.BigSizeT[btcutil.Amount]]
// CustomBlob is an optional blob that can be used to store information
// specific to revocation handling for a custom channel type.
CustomBlob tlv.OptionalRecordT[tlv.TlvType5, tlv.Blob]
// HtlcIndex is the index of the HTLC in the channel.
HtlcIndex tlv.OptionalRecordT[tlv.TlvType6, uint16]
}
// toTlvStream converts an HTLCEntry record into a tlv representation.
func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) {
records := []tlv.Record{
h.RHash.Record(),
h.RefundTimeout.Record(),
h.OutputIndex.Record(),
h.Incoming.Record(),
h.Amt.Record(),
}
h.CustomBlob.WhenSome(func(r tlv.RecordT[tlv.TlvType5, tlv.Blob]) {
records = append(records, r.Record())
})
h.HtlcIndex.WhenSome(func(r tlv.RecordT[tlv.TlvType6, uint16]) {
records = append(records, r.Record())
})
tlv.SortRecords(records)
return tlv.NewStream(records...)
}
// NewHTLCEntryFromHTLC creates a new HTLCEntry from an HTLC.
func NewHTLCEntryFromHTLC(htlc HTLC) (*HTLCEntry, error) {
h := &HTLCEntry{
RHash: tlv.NewRecordT[tlv.TlvType0](
NewSparsePayHash(htlc.RHash),
),
RefundTimeout: tlv.NewPrimitiveRecord[tlv.TlvType1](
htlc.RefundTimeout,
),
OutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType2](
uint16(htlc.OutputIndex),
),
Incoming: tlv.NewPrimitiveRecord[tlv.TlvType3](htlc.Incoming),
Amt: tlv.NewRecordT[tlv.TlvType4](
tlv.NewBigSizeT(htlc.Amt.ToSatoshis()),
),
HtlcIndex: tlv.SomeRecordT(tlv.NewPrimitiveRecord[tlv.TlvType6](
uint16(htlc.HtlcIndex),
)),
}
if len(htlc.CustomRecords) != 0 {
blob, err := htlc.CustomRecords.Serialize()
if err != nil {
return nil, err
}
h.CustomBlob = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType5, tlv.Blob](blob),
)
}
return h, nil
}
// RevocationLog stores the info needed to construct a breach retribution. Its
// fields can be viewed as a subset of a ChannelCommitment's. In the database,
// all historical versions of the RevocationLog are saved using the
// CommitHeight as the key.
type RevocationLog struct {
// OurOutputIndex specifies our output index in this commitment. In a
// remote commitment transaction, this is the to remote output index.
OurOutputIndex tlv.RecordT[tlv.TlvType0, uint16]
// TheirOutputIndex specifies their output index in this commitment. In
// a remote commitment transaction, this is the to local output index.
TheirOutputIndex tlv.RecordT[tlv.TlvType1, uint16]
// CommitTxHash is the hash of the latest version of the commitment
// state, broadcast able by us.
CommitTxHash tlv.RecordT[tlv.TlvType2, [32]byte]
// HTLCEntries is the set of HTLCEntry's that are pending at this
// particular commitment height.
HTLCEntries []*HTLCEntry
// OurBalance is the current available balance within the channel
// directly spendable by us. In other words, it is the value of the
// to_remote output on the remote parties' commitment transaction.
//
// NOTE: this is an option so that it is clear if the value is zero or
// nil. Since migration 30 of the channeldb initially did not include
// this field, it could be the case that the field is not present for
// all revocation logs.
OurBalance tlv.OptionalRecordT[tlv.TlvType3, BigSizeMilliSatoshi]
// TheirBalance is the current available balance within the channel
// directly spendable by the remote node. In other words, it is the
// value of the to_local output on the remote parties' commitment.
//
// NOTE: this is an option so that it is clear if the value is zero or
// nil. Since migration 30 of the channeldb initially did not include
// this field, it could be the case that the field is not present for
// all revocation logs.
TheirBalance tlv.OptionalRecordT[tlv.TlvType4, BigSizeMilliSatoshi]
// CustomBlob is an optional blob that can be used to store information
// specific to a custom channel type. This information is only created
// at channel funding time, and after wards is to be considered
// immutable.
CustomBlob tlv.OptionalRecordT[tlv.TlvType5, tlv.Blob]
}
// NewRevocationLog creates a new RevocationLog from the given parameters.
func NewRevocationLog(ourOutputIndex uint16, theirOutputIndex uint16,
commitHash [32]byte, ourBalance,
theirBalance fn.Option[lnwire.MilliSatoshi], htlcs []*HTLCEntry,
customBlob fn.Option[tlv.Blob]) RevocationLog {
rl := RevocationLog{
OurOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType0](
ourOutputIndex,
),
TheirOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType1](
theirOutputIndex,
),
CommitTxHash: tlv.NewPrimitiveRecord[tlv.TlvType2](commitHash),
HTLCEntries: htlcs,
}
ourBalance.WhenSome(func(balance lnwire.MilliSatoshi) {
rl.OurBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType3](
tlv.NewBigSizeT(balance),
))
})
theirBalance.WhenSome(func(balance lnwire.MilliSatoshi) {
rl.TheirBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType4](
tlv.NewBigSizeT(balance),
))
})
customBlob.WhenSome(func(blob tlv.Blob) {
rl.CustomBlob = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType5, tlv.Blob](blob),
)
})
return rl
}
// putRevocationLog uses the fields `CommitTx` and `Htlcs` from a
// ChannelCommitment to construct a revocation log entry and saves them to
// disk. It also saves our output index and their output index, which are
// useful when creating breach retribution.
func putRevocationLog(bucket kvdb.RwBucket, commit *ChannelCommitment,
ourOutputIndex, theirOutputIndex uint32, noAmtData bool) error {
// Sanity check that the output indexes can be safely converted.
if ourOutputIndex > math.MaxUint16 {
return ErrOutputIndexTooBig
}
if theirOutputIndex > math.MaxUint16 {
return ErrOutputIndexTooBig
}
rl := &RevocationLog{
OurOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType0](
uint16(ourOutputIndex),
),
TheirOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType1](
uint16(theirOutputIndex),
),
CommitTxHash: tlv.NewPrimitiveRecord[tlv.TlvType2, [32]byte](
commit.CommitTx.TxHash(),
),
HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)),
}
commit.CustomBlob.WhenSome(func(blob tlv.Blob) {
rl.CustomBlob = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType5, tlv.Blob](blob),
)
})
if !noAmtData {
rl.OurBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType3](
tlv.NewBigSizeT(commit.LocalBalance),
))
rl.TheirBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType4](
tlv.NewBigSizeT(commit.RemoteBalance),
))
}
for _, htlc := range commit.Htlcs {
// Skip dust HTLCs.
if htlc.OutputIndex < 0 {
continue
}
// Sanity check that the output indexes can be safely
// converted.
if htlc.OutputIndex > math.MaxUint16 {
return ErrOutputIndexTooBig
}
entry, err := NewHTLCEntryFromHTLC(htlc)
if err != nil {
return err
}
rl.HTLCEntries = append(rl.HTLCEntries, entry)
}
var b bytes.Buffer
err := serializeRevocationLog(&b, rl)
if err != nil {
return err
}
logEntrykey := makeLogKey(commit.CommitHeight)
return bucket.Put(logEntrykey[:], b.Bytes())
}
// fetchRevocationLog queries the revocation log bucket to find an log entry.
// Return an error if not found.
func fetchRevocationLog(log kvdb.RBucket,
updateNum uint64) (RevocationLog, error) {
logEntrykey := makeLogKey(updateNum)
commitBytes := log.Get(logEntrykey[:])
if commitBytes == nil {
return RevocationLog{}, ErrLogEntryNotFound
}
commitReader := bytes.NewReader(commitBytes)
return deserializeRevocationLog(commitReader)
}
// serializeRevocationLog serializes a RevocationLog record based on tlv
// format.
func serializeRevocationLog(w io.Writer, rl *RevocationLog) error {
// Add the tlv records for all non-optional fields.
records := []tlv.Record{
rl.OurOutputIndex.Record(),
rl.TheirOutputIndex.Record(),
rl.CommitTxHash.Record(),
}
// Now we add any optional fields that are non-nil.
rl.OurBalance.WhenSome(
func(r tlv.RecordT[tlv.TlvType3, BigSizeMilliSatoshi]) {
records = append(records, r.Record())
},
)
rl.TheirBalance.WhenSome(
func(r tlv.RecordT[tlv.TlvType4, BigSizeMilliSatoshi]) {
records = append(records, r.Record())
},
)
rl.CustomBlob.WhenSome(func(r tlv.RecordT[tlv.TlvType5, tlv.Blob]) {
records = append(records, r.Record())
})
// Create the tlv stream.
tlvStream, err := tlv.NewStream(records...)
if err != nil {
return err
}
// Write the tlv stream.
if err := writeTlvStream(w, tlvStream); err != nil {
return err
}
// Write the HTLCs.
return serializeHTLCEntries(w, rl.HTLCEntries)
}
// serializeHTLCEntries serializes a list of HTLCEntry records based on tlv
// format.
func serializeHTLCEntries(w io.Writer, htlcs []*HTLCEntry) error {
for _, htlc := range htlcs {
// Create the tlv stream.
tlvStream, err := htlc.toTlvStream()
if err != nil {
return err
}
// Write the tlv stream.
if err := writeTlvStream(w, tlvStream); err != nil {
return err
}
}
return nil
}
// deserializeRevocationLog deserializes a RevocationLog based on tlv format.
func deserializeRevocationLog(r io.Reader) (RevocationLog, error) {
var rl RevocationLog
ourBalance := rl.OurBalance.Zero()
theirBalance := rl.TheirBalance.Zero()
customBlob := rl.CustomBlob.Zero()
// Create the tlv stream.
tlvStream, err := tlv.NewStream(
rl.OurOutputIndex.Record(),
rl.TheirOutputIndex.Record(),
rl.CommitTxHash.Record(),
ourBalance.Record(),
theirBalance.Record(),
customBlob.Record(),
)
if err != nil {
return rl, err
}
// Read the tlv stream.
parsedTypes, err := readTlvStream(r, tlvStream)
if err != nil {
return rl, err
}
if t, ok := parsedTypes[ourBalance.TlvType()]; ok && t == nil {
rl.OurBalance = tlv.SomeRecordT(ourBalance)
}
if t, ok := parsedTypes[theirBalance.TlvType()]; ok && t == nil {
rl.TheirBalance = tlv.SomeRecordT(theirBalance)
}
if t, ok := parsedTypes[customBlob.TlvType()]; ok && t == nil {
rl.CustomBlob = tlv.SomeRecordT(customBlob)
}
// Read the HTLC entries.
rl.HTLCEntries, err = deserializeHTLCEntries(r)
return rl, err
}
// deserializeHTLCEntries deserializes a list of HTLC entries based on tlv
// format.
func deserializeHTLCEntries(r io.Reader) ([]*HTLCEntry, error) {
var htlcs []*HTLCEntry
for {
var htlc HTLCEntry
customBlob := htlc.CustomBlob.Zero()
htlcIndex := htlc.HtlcIndex.Zero()
// Create the tlv stream.
records := []tlv.Record{
htlc.RHash.Record(),
htlc.RefundTimeout.Record(),
htlc.OutputIndex.Record(),
htlc.Incoming.Record(),
htlc.Amt.Record(),
customBlob.Record(),
htlcIndex.Record(),
}
tlvStream, err := tlv.NewStream(records...)
if err != nil {
return nil, err
}
// Read the HTLC entry.
parsedTypes, err := readTlvStream(r, tlvStream)
if err != nil {
// We've reached the end when hitting an EOF.
if err == io.ErrUnexpectedEOF {
break
}
return nil, err
}
if t, ok := parsedTypes[customBlob.TlvType()]; ok && t == nil {
htlc.CustomBlob = tlv.SomeRecordT(customBlob)
}
if t, ok := parsedTypes[htlcIndex.TlvType()]; ok && t == nil {
htlc.HtlcIndex = tlv.SomeRecordT(htlcIndex)
}
// Append the entry.
htlcs = append(htlcs, &htlc)
}
return htlcs, nil
}
// writeTlvStream is a helper function that encodes the tlv stream into the
// writer.
func writeTlvStream(w io.Writer, s *tlv.Stream) error {
var b bytes.Buffer
if err := s.Encode(&b); err != nil {
return err
}
// Write the stream's length as a varint.
err := tlv.WriteVarInt(w, uint64(b.Len()), &[8]byte{})
if err != nil {
return err
}
if _, err = w.Write(b.Bytes()); err != nil {
return err
}
return nil
}
// readTlvStream is a helper function that decodes the tlv stream from the
// reader.
func readTlvStream(r io.Reader, s *tlv.Stream) (tlv.TypeMap, error) {
var bodyLen uint64
// Read the stream's length.
bodyLen, err := tlv.ReadVarInt(r, &[8]byte{})
switch {
// We'll convert any EOFs to ErrUnexpectedEOF, since this results in an
// invalid record.
case err == io.EOF:
return nil, io.ErrUnexpectedEOF
// Other unexpected errors.
case err != nil:
return nil, err
}
// TODO(yy): add overflow check.
lr := io.LimitReader(r, int64(bodyLen))
return s.DecodeWithParsedTypes(lr)
}
// fetchOldRevocationLog finds the revocation log from the deprecated
// sub-bucket.
func fetchOldRevocationLog(log kvdb.RBucket,
updateNum uint64) (ChannelCommitment, error) {
logEntrykey := makeLogKey(updateNum)
commitBytes := log.Get(logEntrykey[:])
if commitBytes == nil {
return ChannelCommitment{}, ErrLogEntryNotFound
}
commitReader := bytes.NewReader(commitBytes)
return deserializeChanCommit(commitReader)
}
// fetchRevocationLogCompatible finds the revocation log from both the
// revocationLogBucket and revocationLogBucketDeprecated for compatibility
// concern. It returns three values,
// - RevocationLog, if this is non-nil, it means we've found the log in the
// new bucket.
// - ChannelCommitment, if this is non-nil, it means we've found the log in the
// old bucket.
// - error, this can happen if the log cannot be found in neither buckets.
func fetchRevocationLogCompatible(chanBucket kvdb.RBucket,
updateNum uint64) (*RevocationLog, *ChannelCommitment, error) {
// Look into the new bucket first.
logBucket := chanBucket.NestedReadBucket(revocationLogBucket)
if logBucket != nil {
rl, err := fetchRevocationLog(logBucket, updateNum)
// We've found the record, no need to visit the old bucket.
if err == nil {
return &rl, nil, nil
}
// Return the error if it doesn't say the log cannot be found.
if err != ErrLogEntryNotFound {
return nil, nil, err
}
}
// Otherwise, look into the old bucket and try to find the log there.
oldBucket := chanBucket.NestedReadBucket(revocationLogBucketDeprecated)
if oldBucket != nil {
c, err := fetchOldRevocationLog(oldBucket, updateNum)
if err != nil {
return nil, nil, err
}
// Found an old record and return it.
return nil, &c, nil
}
// If both the buckets are nil, then the sub-buckets haven't been
// created yet.
if logBucket == nil && oldBucket == nil {
return nil, nil, ErrNoPastDeltas
}
// Otherwise, we've tried to query the new bucket but the log cannot be
// found.
return nil, nil, ErrLogEntryNotFound
}
// fetchLogBucket returns a read bucket by visiting both the new and the old
// bucket.
func fetchLogBucket(chanBucket kvdb.RBucket) (kvdb.RBucket, error) {
logBucket := chanBucket.NestedReadBucket(revocationLogBucket)
if logBucket == nil {
logBucket = chanBucket.NestedReadBucket(
revocationLogBucketDeprecated,
)
if logBucket == nil {
return nil, ErrNoPastDeltas
}
}
return logBucket, nil
}
// deleteLogBucket deletes the both the new and old revocation log buckets.
func deleteLogBucket(chanBucket kvdb.RwBucket) error {
// Check if the bucket exists and delete it.
logBucket := chanBucket.NestedReadWriteBucket(
revocationLogBucket,
)
if logBucket != nil {
err := chanBucket.DeleteNestedBucket(revocationLogBucket)
if err != nil {
return err
}
}
// We also check whether the old revocation log bucket exists
// and delete it if so.
oldLogBucket := chanBucket.NestedReadWriteBucket(
revocationLogBucketDeprecated,
)
if oldLogBucket != nil {
err := chanBucket.DeleteNestedBucket(
revocationLogBucketDeprecated,
)
if err != nil {
return err
}
}
return nil
}
package channeldb
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"sync"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// waitingProofsBucketKey byte string name of the waiting proofs store.
waitingProofsBucketKey = []byte("waitingproofs")
// ErrWaitingProofNotFound is returned if waiting proofs haven't been
// found by db.
ErrWaitingProofNotFound = errors.New("waiting proofs haven't been " +
"found")
// ErrWaitingProofAlreadyExist is returned if waiting proofs haven't been
// found by db.
ErrWaitingProofAlreadyExist = errors.New("waiting proof with such " +
"key already exist")
)
// WaitingProofStore is the bold db map-like storage for half announcement
// signatures. The one responsibility of this storage is to be able to
// retrieve waiting proofs after client restart.
type WaitingProofStore struct {
// cache is used in order to reduce the number of redundant get
// calls, when object isn't stored in it.
cache map[WaitingProofKey]struct{}
db kvdb.Backend
mu sync.RWMutex
}
// NewWaitingProofStore creates new instance of proofs storage.
func NewWaitingProofStore(db kvdb.Backend) (*WaitingProofStore, error) {
s := &WaitingProofStore{
db: db,
}
if err := s.ForAll(func(proof *WaitingProof) error {
s.cache[proof.Key()] = struct{}{}
return nil
}, func() {
s.cache = make(map[WaitingProofKey]struct{})
}); err != nil && err != ErrWaitingProofNotFound {
return nil, err
}
return s, nil
}
// Add adds new waiting proof in the storage.
func (s *WaitingProofStore) Add(proof *WaitingProof) error {
s.mu.Lock()
defer s.mu.Unlock()
err := kvdb.Update(s.db, func(tx kvdb.RwTx) error {
var err error
var b bytes.Buffer
// Get or create the bucket.
bucket, err := tx.CreateTopLevelBucket(waitingProofsBucketKey)
if err != nil {
return err
}
// Encode the objects and place it in the bucket.
if err := proof.Encode(&b); err != nil {
return err
}
key := proof.Key()
return bucket.Put(key[:], b.Bytes())
}, func() {})
if err != nil {
return err
}
// Knowing that the write succeeded, we can now update the in-memory
// cache with the proof's key.
s.cache[proof.Key()] = struct{}{}
return nil
}
// Remove removes the proof from storage by its key.
func (s *WaitingProofStore) Remove(key WaitingProofKey) error {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.cache[key]; !ok {
return ErrWaitingProofNotFound
}
err := kvdb.Update(s.db, func(tx kvdb.RwTx) error {
// Get or create the top bucket.
bucket := tx.ReadWriteBucket(waitingProofsBucketKey)
if bucket == nil {
return ErrWaitingProofNotFound
}
return bucket.Delete(key[:])
}, func() {})
if err != nil {
return err
}
// Since the proof was successfully deleted from the store, we can now
// remove it from the in-memory cache.
delete(s.cache, key)
return nil
}
// ForAll iterates thought all waiting proofs and passing the waiting proof
// in the given callback.
func (s *WaitingProofStore) ForAll(cb func(*WaitingProof) error,
reset func()) error {
return kvdb.View(s.db, func(tx kvdb.RTx) error {
bucket := tx.ReadBucket(waitingProofsBucketKey)
if bucket == nil {
return ErrWaitingProofNotFound
}
// Iterate over objects buckets.
return bucket.ForEach(func(k, v []byte) error {
// Skip buckets fields.
if v == nil {
return nil
}
r := bytes.NewReader(v)
proof := &WaitingProof{}
if err := proof.Decode(r); err != nil {
return err
}
return cb(proof)
})
}, reset)
}
// Get returns the object which corresponds to the given index.
func (s *WaitingProofStore) Get(key WaitingProofKey) (*WaitingProof, error) {
var proof *WaitingProof
s.mu.RLock()
defer s.mu.RUnlock()
if _, ok := s.cache[key]; !ok {
return nil, ErrWaitingProofNotFound
}
err := kvdb.View(s.db, func(tx kvdb.RTx) error {
bucket := tx.ReadBucket(waitingProofsBucketKey)
if bucket == nil {
return ErrWaitingProofNotFound
}
// Iterate over objects buckets.
v := bucket.Get(key[:])
if v == nil {
return ErrWaitingProofNotFound
}
r := bytes.NewReader(v)
return proof.Decode(r)
}, func() {
proof = &WaitingProof{}
})
return proof, err
}
// WaitingProofKey is the proof key which uniquely identifies the waiting
// proof object. The goal of this key is distinguish the local and remote
// proof for the same channel id.
type WaitingProofKey [9]byte
// WaitingProof is the storable object, which encapsulate the half proof and
// the information about from which side this proof came. This structure is
// needed to make channel proof exchange persistent, so that after client
// restart we may receive remote/local half proof and process it.
type WaitingProof struct {
*lnwire.AnnounceSignatures1
isRemote bool
}
// NewWaitingProof constructs a new waiting prof instance.
func NewWaitingProof(isRemote bool,
proof *lnwire.AnnounceSignatures1) *WaitingProof {
return &WaitingProof{
AnnounceSignatures1: proof,
isRemote: isRemote,
}
}
// OppositeKey returns the key which uniquely identifies opposite waiting proof.
func (p *WaitingProof) OppositeKey() WaitingProofKey {
var key [9]byte
binary.BigEndian.PutUint64(key[:8], p.ShortChannelID.ToUint64())
if !p.isRemote {
key[8] = 1
}
return key
}
// Key returns the key which uniquely identifies waiting proof.
func (p *WaitingProof) Key() WaitingProofKey {
var key [9]byte
binary.BigEndian.PutUint64(key[:8], p.ShortChannelID.ToUint64())
if p.isRemote {
key[8] = 1
}
return key
}
// Encode writes the internal representation of waiting proof in byte stream.
func (p *WaitingProof) Encode(w io.Writer) error {
if err := binary.Write(w, byteOrder, p.isRemote); err != nil {
return err
}
// TODO(yy): remove the type assertion when we finished refactoring db
// into using write buffer.
buf, ok := w.(*bytes.Buffer)
if !ok {
return fmt.Errorf("expect io.Writer to be *bytes.Buffer")
}
if err := p.AnnounceSignatures1.Encode(buf, 0); err != nil {
return err
}
return nil
}
// Decode reads the data from the byte stream and initializes the
// waiting proof object with it.
func (p *WaitingProof) Decode(r io.Reader) error {
if err := binary.Read(r, byteOrder, &p.isRemote); err != nil {
return err
}
msg := &lnwire.AnnounceSignatures1{}
if err := msg.Decode(r, 0); err != nil {
return err
}
p.AnnounceSignatures1 = msg
return nil
}
package channeldb
import (
"fmt"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes"
)
var (
// ErrNoWitnesses is an error that's returned when no new witnesses have
// been added to the WitnessCache.
ErrNoWitnesses = fmt.Errorf("no witnesses")
// ErrUnknownWitnessType is returned if a caller attempts to
ErrUnknownWitnessType = fmt.Errorf("unknown witness type")
)
// WitnessType is enum that denotes what "type" of witness is being
// stored/retrieved. As the WitnessCache itself is agnostic and doesn't enforce
// any structure on added witnesses, we use this type to partition the
// witnesses on disk, and also to know how to map a witness to its look up key.
type WitnessType uint8
var (
// Sha256HashWitness is a witness that is simply the pre image to a
// hash image. In order to map to its key, we'll use sha256.
Sha256HashWitness WitnessType = 1
)
// toDBKey is a helper method that maps a witness type to the key that we'll
// use to store it within the database.
func (w WitnessType) toDBKey() ([]byte, error) {
switch w {
case Sha256HashWitness:
return []byte{byte(w)}, nil
default:
return nil, ErrUnknownWitnessType
}
}
var (
// witnessBucketKey is the name of the bucket that we use to store all
// witnesses encountered. Within this bucket, we'll create a sub-bucket for
// each witness type.
witnessBucketKey = []byte("byte")
)
// WitnessCache is a persistent cache of all witnesses we've encountered on the
// network. In the case of multi-hop, multi-step contracts, a cache of all
// witnesses can be useful in the case of partial contract resolution. If
// negotiations break down, we may be forced to locate the witness for a
// portion of the contract on-chain. In this case, we'll then add that witness
// to the cache so the incoming contract can fully resolve witness.
// Additionally, as one MUST always use a unique witness on the network, we may
// use this cache to detect duplicate witnesses.
//
// TODO(roasbeef): need expiry policy?
// - encrypt?
type WitnessCache struct {
db *DB
}
// NewWitnessCache returns a new instance of the witness cache.
func (d *DB) NewWitnessCache() *WitnessCache {
return &WitnessCache{
db: d,
}
}
// witnessEntry is a key-value struct that holds each key -> witness pair, used
// when inserting records into the cache.
type witnessEntry struct {
key []byte
witness []byte
}
// AddSha256Witnesses adds a batch of new sha256 preimages into the witness
// cache. This is an alias for AddWitnesses that uses Sha256HashWitness as the
// preimages' witness type.
func (w *WitnessCache) AddSha256Witnesses(preimages ...lntypes.Preimage) error {
// Optimistically compute the preimages' hashes before attempting to
// start the db transaction.
entries := make([]witnessEntry, 0, len(preimages))
for i := range preimages {
hash := preimages[i].Hash()
entries = append(entries, witnessEntry{
key: hash[:],
witness: preimages[i][:],
})
}
return w.addWitnessEntries(Sha256HashWitness, entries)
}
// addWitnessEntries inserts the witnessEntry key-value pairs into the cache,
// using the appropriate witness type to segment the namespace of possible
// witness types.
func (w *WitnessCache) addWitnessEntries(wType WitnessType,
entries []witnessEntry) error {
// Exit early if there are no witnesses to add.
if len(entries) == 0 {
return nil
}
return kvdb.Batch(w.db.Backend, func(tx kvdb.RwTx) error {
witnessBucket, err := tx.CreateTopLevelBucket(witnessBucketKey)
if err != nil {
return err
}
witnessTypeBucketKey, err := wType.toDBKey()
if err != nil {
return err
}
witnessTypeBucket, err := witnessBucket.CreateBucketIfNotExists(
witnessTypeBucketKey,
)
if err != nil {
return err
}
for _, entry := range entries {
err = witnessTypeBucket.Put(entry.key, entry.witness)
if err != nil {
return err
}
}
return nil
})
}
// LookupSha256Witness attempts to lookup the preimage for a sha256 hash. If
// the witness isn't found, ErrNoWitnesses will be returned.
func (w *WitnessCache) LookupSha256Witness(hash lntypes.Hash) (lntypes.Preimage, error) {
witness, err := w.lookupWitness(Sha256HashWitness, hash[:])
if err != nil {
return lntypes.Preimage{}, err
}
return lntypes.MakePreimage(witness)
}
// lookupWitness attempts to lookup a witness according to its type and also
// its witness key. In the case that the witness isn't found, ErrNoWitnesses
// will be returned.
func (w *WitnessCache) lookupWitness(wType WitnessType, witnessKey []byte) ([]byte, error) {
var witness []byte
err := kvdb.View(w.db, func(tx kvdb.RTx) error {
witnessBucket := tx.ReadBucket(witnessBucketKey)
if witnessBucket == nil {
return ErrNoWitnesses
}
witnessTypeBucketKey, err := wType.toDBKey()
if err != nil {
return err
}
witnessTypeBucket := witnessBucket.NestedReadBucket(witnessTypeBucketKey)
if witnessTypeBucket == nil {
return ErrNoWitnesses
}
dbWitness := witnessTypeBucket.Get(witnessKey)
if dbWitness == nil {
return ErrNoWitnesses
}
witness = make([]byte, len(dbWitness))
copy(witness[:], dbWitness)
return nil
}, func() {
witness = nil
})
if err != nil {
return nil, err
}
return witness, nil
}
// DeleteSha256Witness attempts to delete a sha256 preimage identified by hash.
func (w *WitnessCache) DeleteSha256Witness(hash lntypes.Hash) error {
return w.deleteWitness(Sha256HashWitness, hash[:])
}
// deleteWitness attempts to delete a particular witness from the database.
func (w *WitnessCache) deleteWitness(wType WitnessType, witnessKey []byte) error {
return kvdb.Batch(w.db.Backend, func(tx kvdb.RwTx) error {
witnessBucket, err := tx.CreateTopLevelBucket(witnessBucketKey)
if err != nil {
return err
}
witnessTypeBucketKey, err := wType.toDBKey()
if err != nil {
return err
}
witnessTypeBucket, err := witnessBucket.CreateBucketIfNotExists(
witnessTypeBucketKey,
)
if err != nil {
return err
}
return witnessTypeBucket.Delete(witnessKey)
})
}
// DeleteWitnessClass attempts to delete an *entire* class of witnesses. After
// this function return with a non-nil error,
func (w *WitnessCache) DeleteWitnessClass(wType WitnessType) error {
return kvdb.Batch(w.db.Backend, func(tx kvdb.RwTx) error {
witnessBucket, err := tx.CreateTopLevelBucket(witnessBucketKey)
if err != nil {
return err
}
witnessTypeBucketKey, err := wType.toDBKey()
if err != nil {
return err
}
return witnessBucket.DeleteNestedBucket(witnessTypeBucketKey)
})
}
package channelnotifier
import (
"sync"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/subscribe"
)
// ChannelNotifier is a subsystem which all active, inactive, and closed channel
// events pipe through. It takes subscriptions for its events, and whenever
// it receives a new event it notifies its subscribers over the proper channel.
type ChannelNotifier struct {
started sync.Once
stopped sync.Once
ntfnServer *subscribe.Server
chanDB *channeldb.ChannelStateDB
}
// PendingOpenChannelEvent represents a new event where a new channel has
// entered a pending open state.
type PendingOpenChannelEvent struct {
// ChannelPoint is the channel outpoint for the new channel.
ChannelPoint *wire.OutPoint
// PendingChannel is the channel configuration for the newly created
// channel. This might not have been persisted to the channel DB yet
// because we are still waiting for the final message from the remote
// peer.
PendingChannel *channeldb.OpenChannel
}
// OpenChannelEvent represents a new event where a channel goes from pending
// open to open.
type OpenChannelEvent struct {
// Channel is the channel that has become open.
Channel *channeldb.OpenChannel
}
// ActiveLinkEvent represents a new event where the link becomes active in the
// switch. This happens before the ActiveChannelEvent.
type ActiveLinkEvent struct {
// ChannelPoint is the channel point for the newly active channel.
ChannelPoint *wire.OutPoint
}
// InactiveLinkEvent represents a new event where the link becomes inactive in
// the switch.
type InactiveLinkEvent struct {
// ChannelPoint is the channel point for the inactive channel.
ChannelPoint *wire.OutPoint
}
// ActiveChannelEvent represents a new event where a channel becomes active.
type ActiveChannelEvent struct {
// ChannelPoint is the channelpoint for the newly active channel.
ChannelPoint *wire.OutPoint
}
// InactiveChannelEvent represents a new event where a channel becomes inactive.
type InactiveChannelEvent struct {
// ChannelPoint is the channelpoint for the newly inactive channel.
ChannelPoint *wire.OutPoint
}
// ClosedChannelEvent represents a new event where a channel becomes closed.
type ClosedChannelEvent struct {
// CloseSummary is the summary of the channel close that has occurred.
CloseSummary *channeldb.ChannelCloseSummary
}
// FullyResolvedChannelEvent represents a new event where a channel becomes
// fully resolved.
type FullyResolvedChannelEvent struct {
// ChannelPoint is the channelpoint for the newly fully resolved
// channel.
ChannelPoint *wire.OutPoint
}
// FundingTimeoutEvent represents a new event where a pending-open channel has
// timed out from the PoV of the funding manager because the funding tx
// has not confirmed in the allotted time.
type FundingTimeoutEvent struct {
// ChannelPoint is the channelpoint for the newly inactive channel.
ChannelPoint *wire.OutPoint
}
// New creates a new channel notifier. The ChannelNotifier gets channel
// events from peers and from the chain arbitrator, and dispatches them to
// its clients.
func New(chanDB *channeldb.ChannelStateDB) *ChannelNotifier {
return &ChannelNotifier{
ntfnServer: subscribe.NewServer(),
chanDB: chanDB,
}
}
// Start starts the ChannelNotifier and all goroutines it needs to carry out its task.
func (c *ChannelNotifier) Start() error {
var err error
c.started.Do(func() {
log.Info("ChannelNotifier starting")
err = c.ntfnServer.Start()
})
return err
}
// Stop signals the notifier for a graceful shutdown.
func (c *ChannelNotifier) Stop() error {
var err error
c.stopped.Do(func() {
log.Info("ChannelNotifier shutting down...")
defer log.Debug("ChannelNotifier shutdown complete")
err = c.ntfnServer.Stop()
})
return err
}
// SubscribeChannelEvents returns a subscribe.Client that will receive updates
// any time the Server is made aware of a new event. The subscription provides
// channel events from the point of subscription onwards.
//
// TODO(carlaKC): update to allow subscriptions to specify a block height from
// which we would like to subscribe to events.
func (c *ChannelNotifier) SubscribeChannelEvents() (*subscribe.Client, error) {
return c.ntfnServer.Subscribe()
}
// NotifyPendingOpenChannelEvent notifies the channelEventNotifier goroutine
// that a new channel is pending. The pending channel is passed as a parameter
// instead of read from the database because it might not yet have been
// persisted to the DB because we still wait for the final message from the
// remote peer.
func (c *ChannelNotifier) NotifyPendingOpenChannelEvent(chanPoint wire.OutPoint,
pendingChan *channeldb.OpenChannel) {
event := PendingOpenChannelEvent{
ChannelPoint: &chanPoint,
PendingChannel: pendingChan,
}
if err := c.ntfnServer.SendUpdate(event); err != nil {
log.Warnf("Unable to send pending open channel update: %v", err)
}
}
// NotifyOpenChannelEvent notifies the channelEventNotifier goroutine that a
// channel has gone from pending open to open.
func (c *ChannelNotifier) NotifyOpenChannelEvent(chanPoint wire.OutPoint) {
// Fetch the relevant channel from the database.
channel, err := c.chanDB.FetchChannel(chanPoint)
if err != nil {
log.Warnf("Unable to fetch open channel from the db: %v", err)
}
// Send the open event to all channel event subscribers.
event := OpenChannelEvent{Channel: channel}
if err := c.ntfnServer.SendUpdate(event); err != nil {
log.Warnf("Unable to send open channel update: %v", err)
}
}
// NotifyClosedChannelEvent notifies the channelEventNotifier goroutine that a
// channel has closed.
func (c *ChannelNotifier) NotifyClosedChannelEvent(chanPoint wire.OutPoint) {
// Fetch the relevant closed channel from the database.
closeSummary, err := c.chanDB.FetchClosedChannel(&chanPoint)
if err != nil {
log.Warnf("Unable to fetch closed channel summary from the db: %v", err)
}
// Send the closed event to all channel event subscribers.
event := ClosedChannelEvent{CloseSummary: closeSummary}
if err := c.ntfnServer.SendUpdate(event); err != nil {
log.Warnf("Unable to send closed channel update: %v", err)
}
}
// NotifyFullyResolvedChannelEvent notifies the channelEventNotifier goroutine
// that a channel was fully resolved on chain.
func (c *ChannelNotifier) NotifyFullyResolvedChannelEvent(
chanPoint wire.OutPoint) {
// Send the resolved event to all channel event subscribers.
event := FullyResolvedChannelEvent{ChannelPoint: &chanPoint}
if err := c.ntfnServer.SendUpdate(event); err != nil {
log.Warnf("Unable to send resolved channel update: %v", err)
}
}
// NotifyFundingTimeoutEvent notifies the channelEventNotifier goroutine that
// a funding timeout has occurred for a certain channel point.
func (c *ChannelNotifier) NotifyFundingTimeout(chanPoint wire.OutPoint) {
// Send this event to all channel event subscribers.
event := FundingTimeoutEvent{ChannelPoint: &chanPoint}
if err := c.ntfnServer.SendUpdate(event); err != nil {
log.Warnf("Unable to send funding timeout update: %v for "+
"ChanPoint(%v)", err, chanPoint)
}
}
// NotifyActiveLinkEvent notifies the channelEventNotifier goroutine that a
// link has been added to the switch.
func (c *ChannelNotifier) NotifyActiveLinkEvent(chanPoint wire.OutPoint) {
event := ActiveLinkEvent{ChannelPoint: &chanPoint}
if err := c.ntfnServer.SendUpdate(event); err != nil {
log.Warnf("Unable to send active link update: %v", err)
}
}
// NotifyActiveChannelEvent notifies the channelEventNotifier goroutine that a
// channel is active.
func (c *ChannelNotifier) NotifyActiveChannelEvent(chanPoint wire.OutPoint) {
event := ActiveChannelEvent{ChannelPoint: &chanPoint}
if err := c.ntfnServer.SendUpdate(event); err != nil {
log.Warnf("Unable to send active channel update: %v", err)
}
}
// NotifyInactiveLinkEvent notifies the channelEventNotifier goroutine that a
// link has been removed from the switch.
func (c *ChannelNotifier) NotifyInactiveLinkEvent(chanPoint wire.OutPoint) {
event := InactiveLinkEvent{ChannelPoint: &chanPoint}
if err := c.ntfnServer.SendUpdate(event); err != nil {
log.Warnf("Unable to send inactive link update: %v", err)
}
}
// NotifyInactiveChannelEvent notifies the channelEventNotifier goroutine that a
// channel is inactive.
func (c *ChannelNotifier) NotifyInactiveChannelEvent(chanPoint wire.OutPoint) {
event := InactiveChannelEvent{ChannelPoint: &chanPoint}
if err := c.ntfnServer.SendUpdate(event); err != nil {
log.Warnf("Unable to send inactive channel update: %v", err)
}
}
package channelnotifier
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This means the
// package will not perform any logging by default until the caller requests
// it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("CHNF", nil))
}
// DisableLog disables all library log output. Logging output is disabled by
// default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info. This
// should be used in preference to SetLogWriter if the caller is also using
// btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package lnd
import (
"fmt"
"math"
"net"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/shachain"
)
const (
// mainnetSCBLaunchBlock is the approximate block height of the bitcoin
// mainnet chain of the date when SCBs first were released in lnd
// (v0.6.0-beta). The block date is 4/15/2019, 10:54 PM UTC.
mainnetSCBLaunchBlock = 571800
// testnetSCBLaunchBlock is the approximate block height of the bitcoin
// testnet3 chain of the date when SCBs first were released in lnd
// (v0.6.0-beta). The block date is 4/16/2019, 08:04 AM UTC.
testnetSCBLaunchBlock = 1489300
)
// chanDBRestorer is an implementation of the chanbackup.ChannelRestorer
// interface that is able to properly map a Single backup, into a
// channeldb.ChannelShell which is required to fully restore a channel. We also
// need the secret key chain in order obtain the prior shachain root so we can
// verify the DLP protocol as initiated by the remote node.
type chanDBRestorer struct {
db *channeldb.ChannelStateDB
secretKeys keychain.SecretKeyRing
chainArb *contractcourt.ChainArbitrator
}
// openChannelShell maps the static channel back up into an open channel
// "shell". We say shell as this doesn't include all the information required
// to continue to use the channel, only the minimal amount of information to
// insert this shell channel back into the database.
func (c *chanDBRestorer) openChannelShell(backup chanbackup.Single) (
*channeldb.ChannelShell, error) {
var err error
// Each of the keys in our local channel config only have their
// locators populate, so we'll re-derive the raw key now as we'll need
// it in order to carry out the DLP protocol.
backup.LocalChanCfg.MultiSigKey, err = c.secretKeys.DeriveKey(
backup.LocalChanCfg.MultiSigKey.KeyLocator,
)
if err != nil {
return nil, fmt.Errorf("unable to derive multi sig key: %w",
err)
}
backup.LocalChanCfg.RevocationBasePoint, err = c.secretKeys.DeriveKey(
backup.LocalChanCfg.RevocationBasePoint.KeyLocator,
)
if err != nil {
return nil, fmt.Errorf("unable to derive revocation key: %w",
err)
}
backup.LocalChanCfg.PaymentBasePoint, err = c.secretKeys.DeriveKey(
backup.LocalChanCfg.PaymentBasePoint.KeyLocator,
)
if err != nil {
return nil, fmt.Errorf("unable to derive payment key: %w", err)
}
backup.LocalChanCfg.DelayBasePoint, err = c.secretKeys.DeriveKey(
backup.LocalChanCfg.DelayBasePoint.KeyLocator,
)
if err != nil {
return nil, fmt.Errorf("unable to derive delay key: %w", err)
}
backup.LocalChanCfg.HtlcBasePoint, err = c.secretKeys.DeriveKey(
backup.LocalChanCfg.HtlcBasePoint.KeyLocator,
)
if err != nil {
return nil, fmt.Errorf("unable to derive htlc key: %w", err)
}
// The shachain root that seeds RevocationProducer for this channel.
// It currently has two possible formats.
var revRoot *chainhash.Hash
// If the PubKey field is non-nil, then this shachain root is using the
// legacy non-ECDH scheme.
if backup.ShaChainRootDesc.PubKey != nil {
ltndLog.Debugf("Using legacy revocation producer format for "+
"channel point %v", backup.FundingOutpoint)
// Obtain the private key for the shachain root from the
// encoded public key.
privKey, err := c.secretKeys.DerivePrivKey(
backup.ShaChainRootDesc,
)
if err != nil {
return nil, fmt.Errorf("could not derive private key "+
"for legacy channel revocation root format: "+
"%v", err)
}
revRoot, err = chainhash.NewHash(privKey.Serialize())
if err != nil {
return nil, err
}
} else {
ltndLog.Debugf("Using new ECDH revocation producer format "+
"for channel point %v", backup.FundingOutpoint)
// This is the scheme in which the shachain root is derived via
// an ECDH operation on the private key of ShaChainRootDesc and
// our public multisig key.
ecdh, err := c.secretKeys.ECDH(
backup.ShaChainRootDesc,
backup.LocalChanCfg.MultiSigKey.PubKey,
)
if err != nil {
return nil, fmt.Errorf("unable to derive shachain "+
"root: %v", err)
}
ch := chainhash.Hash(ecdh)
revRoot = &ch
}
shaChainProducer := shachain.NewRevocationProducer(*revRoot)
var chanType channeldb.ChannelType
switch backup.Version {
case chanbackup.DefaultSingleVersion:
chanType = channeldb.SingleFunderBit
case chanbackup.TweaklessCommitVersion:
chanType = channeldb.SingleFunderTweaklessBit
case chanbackup.AnchorsCommitVersion:
chanType = channeldb.AnchorOutputsBit
chanType |= channeldb.SingleFunderTweaklessBit
case chanbackup.AnchorsZeroFeeHtlcTxCommitVersion:
chanType = channeldb.ZeroHtlcTxFeeBit
chanType |= channeldb.AnchorOutputsBit
chanType |= channeldb.SingleFunderTweaklessBit
case chanbackup.ScriptEnforcedLeaseVersion:
chanType = channeldb.LeaseExpirationBit
chanType |= channeldb.ZeroHtlcTxFeeBit
chanType |= channeldb.AnchorOutputsBit
chanType |= channeldb.SingleFunderTweaklessBit
case chanbackup.SimpleTaprootVersion:
chanType = channeldb.ZeroHtlcTxFeeBit
chanType |= channeldb.AnchorOutputsBit
chanType |= channeldb.SingleFunderTweaklessBit
chanType |= channeldb.SimpleTaprootFeatureBit
case chanbackup.TapscriptRootVersion:
chanType = channeldb.ZeroHtlcTxFeeBit
chanType |= channeldb.AnchorOutputsBit
chanType |= channeldb.SingleFunderTweaklessBit
chanType |= channeldb.SimpleTaprootFeatureBit
chanType |= channeldb.TapscriptRootBit
default:
return nil, fmt.Errorf("unknown Single version: %w", err)
}
ltndLog.Infof("SCB Recovery: created channel shell for ChannelPoint"+
"(%v), chan_type=%v", backup.FundingOutpoint, chanType)
chanShell := channeldb.ChannelShell{
NodeAddrs: backup.Addresses,
Chan: &channeldb.OpenChannel{
ChanType: chanType,
ChainHash: backup.ChainHash,
IsInitiator: backup.IsInitiator,
Capacity: backup.Capacity,
FundingOutpoint: backup.FundingOutpoint,
ShortChannelID: backup.ShortChannelID,
IdentityPub: backup.RemoteNodePub,
IsPending: false,
LocalChanCfg: backup.LocalChanCfg,
RemoteChanCfg: backup.RemoteChanCfg,
RemoteCurrentRevocation: backup.RemoteNodePub,
RevocationStore: shachain.NewRevocationStore(),
RevocationProducer: shaChainProducer,
ThawHeight: backup.LeaseExpiry,
},
}
return &chanShell, nil
}
// RestoreChansFromSingles attempts to map the set of single channel backups to
// channel shells that will be stored persistently. Once these shells have been
// stored on disk, we'll be able to connect to the channel peer an execute the
// data loss recovery protocol.
//
// NOTE: Part of the chanbackup.ChannelRestorer interface.
func (c *chanDBRestorer) RestoreChansFromSingles(backups ...chanbackup.Single) error {
channelShells := make([]*channeldb.ChannelShell, 0, len(backups))
firstChanHeight := uint32(math.MaxUint32)
for _, backup := range backups {
chanShell, err := c.openChannelShell(backup)
if err != nil {
return err
}
// Find the block height of the earliest channel in this backup.
chanHeight := chanShell.Chan.ShortChanID().BlockHeight
if chanHeight != 0 && chanHeight < firstChanHeight {
firstChanHeight = chanHeight
}
channelShells = append(channelShells, chanShell)
}
// In case there were only unconfirmed channels, we will have to scan
// the chain beginning from the launch date of SCBs.
if firstChanHeight == math.MaxUint32 {
chainHash := channelShells[0].Chan.ChainHash
switch {
case chainHash.IsEqual(chaincfg.MainNetParams.GenesisHash):
firstChanHeight = mainnetSCBLaunchBlock
case chainHash.IsEqual(chaincfg.TestNet3Params.GenesisHash):
firstChanHeight = testnetSCBLaunchBlock
default:
// Worst case: We have no height hint and start at
// block 1. Should only happen for SCBs in regtest
// and simnet.
firstChanHeight = 1
}
}
// If there were channels in the backup that were not confirmed at the
// time of the backup creation, they won't have a block height in the
// ShortChanID which would lead to an error in the chain watcher.
// We want to at least set the funding broadcast height that the chain
// watcher can use instead. We have two possible fallback values for
// the broadcast height that we are going to try here.
for _, chanShell := range channelShells {
channel := chanShell.Chan
switch {
// Fallback case 1: This is an unconfirmed channel from an old
// backup file where we didn't have any workaround in place and
// the short channel ID is 0:0:0. Best we can do here is set the
// funding broadcast height to a reasonable value that we
// determined earlier.
case channel.ShortChanID().BlockHeight == 0:
channel.SetBroadcastHeight(firstChanHeight)
// Fallback case 2: It is extremely unlikely at this point that
// a channel we are trying to restore has a coinbase funding TX.
// Therefore we can be quite certain that if the TxIndex is
// zero but the block height wasn't, it was an unconfirmed
// channel where we used the BlockHeight to encode the funding
// TX broadcast height. To not end up with an invalid short
// channel ID that looks valid, we restore the "original"
// unconfirmed one here.
case channel.ShortChannelID.TxIndex == 0:
broadcastHeight := channel.ShortChannelID.BlockHeight
channel.SetBroadcastHeight(broadcastHeight)
channel.ShortChannelID.BlockHeight = 0
}
}
ltndLog.Infof("Inserting %v SCB channel shells into DB",
len(channelShells))
// Now that we have all the backups mapped into a series of Singles,
// we'll insert them all into the database.
if err := c.db.RestoreChannelShells(channelShells...); err != nil {
return err
}
ltndLog.Infof("Informing chain watchers of new restored channels")
// Create a slice of channel points.
chanPoints := make([]wire.OutPoint, 0, len(channelShells))
// Finally, we'll need to inform the chain arbitrator of these new
// channels so we'll properly watch for their ultimate closure on chain
// and sweep them via the DLP.
for _, restoredChannel := range channelShells {
err := c.chainArb.WatchNewChannel(restoredChannel.Chan)
if err != nil {
return err
}
chanPoints = append(
chanPoints, restoredChannel.Chan.FundingOutpoint,
)
}
// With all the channels restored, we'll now re-send the blockbeat.
c.chainArb.RedispatchBlockbeat(chanPoints)
return nil
}
// A compile-time constraint to ensure chanDBRestorer implements
// chanbackup.ChannelRestorer.
var _ chanbackup.ChannelRestorer = (*chanDBRestorer)(nil)
// ConnectPeer attempts to connect to the target node at the set of available
// addresses. Once this method returns with a non-nil error, the connector
// should attempt to persistently connect to the target peer in the background
// as a persistent attempt.
//
// NOTE: Part of the chanbackup.PeerConnector interface.
func (s *server) ConnectPeer(nodePub *btcec.PublicKey, addrs []net.Addr) error {
// Before we connect to the remote peer, we'll remove any connections
// to ensure the new connection is created after this new link/channel
// is known.
if err := s.DisconnectPeer(nodePub); err != nil {
ltndLog.Infof("Peer(%x) is already connected, proceeding "+
"with chan restore", nodePub.SerializeCompressed())
}
// For each of the known addresses, we'll attempt to launch a
// persistent connection to the (pub, addr) pair. In the event that any
// of them connect, all the other stale requests will be canceled.
for _, addr := range addrs {
netAddr := &lnwire.NetAddress{
IdentityKey: nodePub,
Address: addr,
}
ltndLog.Infof("Attempting to connect to %v for SCB restore "+
"DLP", netAddr)
// Attempt to connect to the peer using this full address. If
// we're unable to connect to them, then we'll try the next
// address in place of it.
err := s.ConnectToPeer(netAddr, true, s.cfg.ConnectionTimeout)
// If we're already connected to this peer, then we don't
// consider this an error, so we'll exit here.
if _, ok := err.(*errPeerAlreadyConnected); ok {
return nil
} else if err != nil {
// Otherwise, something else happened, so we'll try the
// next address.
ltndLog.Errorf("unable to connect to %v to "+
"complete SCB restore: %v", netAddr, err)
continue
}
// If we connected no problem, then we can exit early as our
// job here is done.
return nil
}
return fmt.Errorf("unable to connect to peer %x for SCB restore",
nodePub.SerializeCompressed())
}
package cluster
import (
"context"
"fmt"
)
// leaderElectorFactoryFunc is a LeaderElector factory method type.
type leaderElectorFactoryFunc func(context.Context, ...interface{}) (
LeaderElector, error)
var leaderElectorFactories map[string]leaderElectorFactoryFunc
// RegisterLeaderElectorFactory will register a new LeaderElector factory
// method corresponding to the passed id.
func RegisterLeaderElectorFactory(id string, factory leaderElectorFactoryFunc) {
if leaderElectorFactories == nil {
leaderElectorFactories = make(
map[string]leaderElectorFactoryFunc,
)
}
leaderElectorFactories[id] = factory
}
// MakeLeaderElector will construct a LeaderElector identified by id with the
// passed arguments.
func MakeLeaderElector(ctx context.Context, id string, args ...interface{}) (
LeaderElector, error) {
if _, ok := leaderElectorFactories[id]; !ok {
return nil, fmt.Errorf("leader elector factory for '%v' "+
"not found", id)
}
return leaderElectorFactories[id](ctx, args...)
}
package cluster
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// Subsystem defines the logging code for this subsystem.
const Subsystem = "CLUS"
// log is a logger that is initialized with the btclog.Disabled logger.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all logging output.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package commands
import (
"regexp"
"strconv"
"strings"
"time"
)
// reTimeRange matches systemd.time-like short negative timeranges, e.g. "-200s".
var reTimeRange = regexp.MustCompile(`^-\d{1,18}[s|m|h|d|w|M|y]$`)
// secondsPer allows translating s(seconds), m(minutes), h(ours), d(ays),
// w(eeks), M(onths) and y(ears) into corresponding seconds.
var secondsPer = map[string]int64{
"s": 1,
"m": 60,
"h": 3600,
"d": 86400,
"w": 604800,
"M": 2630016, // 30.44 days
"y": 31557600, // 365.25 days
}
// parseTime parses UNIX timestamps or short timeranges inspired by systemd
// (when starting with "-"), e.g. "-1M" for one month (30.44 days) ago.
func parseTime(s string, base time.Time) (uint64, error) {
if reTimeRange.MatchString(s) {
last := len(s) - 1
d, err := strconv.ParseInt(s[1:last], 10, 64)
if err != nil {
return uint64(0), err
}
mul := secondsPer[string(s[last])]
return uint64(base.Unix() - d*mul), nil
}
return strconv.ParseUint(s, 10, 64)
}
var lightningPrefix = "lightning:"
// StripPrefix removes accidentally copied 'lightning:' prefix.
func StripPrefix(s string) string {
return strings.TrimSpace(strings.TrimPrefix(s, lightningPrefix))
}
//go:build !autopilotrpc
// +build !autopilotrpc
package commands
import "github.com/urfave/cli"
// autopilotCommands will return nil for non-autopilotrpc builds.
func autopilotCommands() []cli.Command {
return nil
}
//go:build !chainrpc
// +build !chainrpc
package commands
import "github.com/urfave/cli"
// chainCommands will return nil for non-chainrpc builds.
func chainCommands() []cli.Command {
return nil
}
package commands
import (
"encoding/hex"
"fmt"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/urfave/cli"
)
var sendCustomCommand = cli.Command{
Name: "sendcustom",
Category: "Peers",
Usage: "Send a custom p2p wire message to a peer",
Flags: []cli.Flag{
cli.StringFlag{
Name: "peer",
},
cli.Uint64Flag{
Name: "type",
},
cli.StringFlag{
Name: "data",
},
},
Action: actionDecorator(sendCustom),
}
func sendCustom(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
peer, err := hex.DecodeString(ctx.String("peer"))
if err != nil {
return err
}
msgType := ctx.Uint64("type")
data, err := hex.DecodeString(ctx.String("data"))
if err != nil {
return err
}
resp, err := client.SendCustomMessage(
ctxc, &lnrpc.SendCustomMessageRequest{
Peer: peer,
Type: uint32(msgType),
Data: data,
},
)
printRespJSON(resp)
return err
}
var subscribeCustomCommand = cli.Command{
Name: "subscribecustom",
Category: "Peers",
Usage: "Subscribe to incoming custom p2p wire messages from all " +
"peers",
Action: actionDecorator(subscribeCustom),
}
func subscribeCustom(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
stream, err := client.SubscribeCustomMessages(
ctxc, &lnrpc.SubscribeCustomMessagesRequest{},
)
if err != nil {
return err
}
for {
msg, err := stream.Recv()
if err != nil {
return err
}
fmt.Printf("Received from peer %x: type=%d, data=%x\n",
msg.Peer, msg.Type, msg.Data)
}
}
package commands
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"math"
"os"
"github.com/andybalholm/brotli"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd"
"github.com/lightningnetwork/lnd/lnencrypt"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/urfave/cli"
"google.golang.org/protobuf/proto"
)
var getDebugInfoCommand = cli.Command{
Name: "getdebuginfo",
Category: "Debug",
Usage: "Returns debug information related to the active daemon.",
Action: actionDecorator(getDebugInfo),
}
func getDebugInfo(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
req := &lnrpc.GetDebugInfoRequest{}
resp, err := client.GetDebugInfo(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
type DebugPackage struct {
EphemeralPubKey string `json:"ephemeral_public_key"`
EncryptedPayload string `json:"encrypted_payload"`
}
var encryptDebugPackageCommand = cli.Command{
Name: "encryptdebugpackage",
Category: "Debug",
Usage: "Collects a package of debug information and encrypts it.",
Description: `
When requesting support with lnd, it's often required to submit a lot of
debug information to the developer in order to track down a problem.
This command will collect all the relevant information and encrypt it
using the provided public key. The resulting file can then be sent to
the developer for further analysis.
Because the file is encrypted, it is safe to send it over insecure
channels or upload it to a GitHub issue.
The file by default contains the output of the following commands:
- lncli getinfo
- lncli getdebuginfo
- lncli getnetworkinfo
By specifying the following flags, additional information can be added
to the file (usually this will be requested by the developer depending
on the issue at hand):
--peers:
- lncli listpeers
--onchain:
- lncli listunspent
- lncli listchaintxns
--channels:
- lncli listchannels
- lncli pendingchannels
- lncli closedchannels
Use 'lncli encryptdebugpackage 0xxxxxx... > package.txt' to write the
encrypted package to a file called package.txt.
`,
ArgsUsage: "pubkey [--output_file F]",
Flags: []cli.Flag{
cli.StringFlag{
Name: "pubkey",
Usage: "the public key to encrypt the information " +
"for (hex-encoded, e.g. 02aabb..), this " +
"should be provided to you by the issue " +
"tracker or developer you're requesting " +
"support from",
},
cli.StringFlag{
Name: "output_file",
Usage: "(optional) the file to write the encrypted " +
"package to; if not specified, the debug " +
"package is printed to stdout",
},
cli.BoolFlag{
Name: "peers",
Usage: "include information about connected peers " +
"(lncli listpeers)",
},
cli.BoolFlag{
Name: "onchain",
Usage: "include information about on-chain " +
"transactions (lncli listunspent, " +
"lncli listchaintxns)",
},
cli.BoolFlag{
Name: "channels",
Usage: "include information about channels " +
"(lncli listchannels, lncli pendingchannels, " +
"lncli closedchannels)",
},
},
Action: actionDecorator(encryptDebugPackage),
}
func encryptDebugPackage(ctx *cli.Context) error {
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
return cli.ShowCommandHelp(ctx, "encryptdebugpackage")
}
var (
args = ctx.Args()
pubKeyBytes []byte
err error
)
switch {
case ctx.IsSet("pubkey"):
pubKeyBytes, err = hex.DecodeString(ctx.String("pubkey"))
case args.Present():
pubKeyBytes, err = hex.DecodeString(args.First())
}
if err != nil {
return fmt.Errorf("unable to decode pubkey argument: %w", err)
}
pubKey, err := btcec.ParsePubKey(pubKeyBytes)
if err != nil {
return fmt.Errorf("unable to parse pubkey: %w", err)
}
// Collect the information we want to send from the daemon.
payload, err := collectDebugPackageInfo(ctx)
if err != nil {
return fmt.Errorf("unable to collect debug package "+
"information: %w", err)
}
// We've collected the information we want to send, but before
// encrypting it, we want to compress it as much as possible to reduce
// the size of the final payload.
var (
compressBuf bytes.Buffer
options = brotli.WriterOptions{
Quality: brotli.BestCompression,
}
writer = brotli.NewWriterOptions(&compressBuf, options)
)
_, err = writer.Write(payload)
if err != nil {
return fmt.Errorf("unable to compress payload: %w", err)
}
if err := writer.Close(); err != nil {
return fmt.Errorf("unable to compress payload: %w", err)
}
// Now we have the full payload that we want to encrypt, so we'll create
// an ephemeral keypair to encrypt the payload with.
localKey, err := btcec.NewPrivateKey()
if err != nil {
return fmt.Errorf("unable to generate local key: %w", err)
}
enc, err := lnencrypt.ECDHEncrypter(localKey, pubKey)
if err != nil {
return fmt.Errorf("unable to create encrypter: %w", err)
}
var cipherBuf bytes.Buffer
err = enc.EncryptPayloadToWriter(compressBuf.Bytes(), &cipherBuf)
if err != nil {
return fmt.Errorf("unable to encrypt payload: %w", err)
}
response := DebugPackage{
EphemeralPubKey: hex.EncodeToString(
localKey.PubKey().SerializeCompressed(),
),
EncryptedPayload: hex.EncodeToString(
cipherBuf.Bytes(),
),
}
// If the user specified an output file, we'll write the encrypted
// payload to that file.
if ctx.IsSet("output_file") {
fileName := lnd.CleanAndExpandPath(ctx.String("output_file"))
jsonBytes, err := json.Marshal(response)
if err != nil {
return fmt.Errorf("unable to encode JSON: %w", err)
}
return os.WriteFile(fileName, jsonBytes, 0644)
}
// Finally, we'll print out the final payload as a JSON if no output
// file was specified.
printJSON(response)
return nil
}
// collectDebugPackageInfo collects the information we want to send to the
// developer(s) from the daemon.
func collectDebugPackageInfo(ctx *cli.Context) ([]byte, error) {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
info, err := client.GetInfo(ctxc, &lnrpc.GetInfoRequest{})
if err != nil {
return nil, fmt.Errorf("error getting info: %w", err)
}
debugInfo, err := client.GetDebugInfo(
ctxc, &lnrpc.GetDebugInfoRequest{},
)
if err != nil {
return nil, fmt.Errorf("error getting debug info: %w", err)
}
networkInfo, err := client.GetNetworkInfo(
ctxc, &lnrpc.NetworkInfoRequest{},
)
if err != nil {
return nil, fmt.Errorf("error getting network info: %w", err)
}
var payloadBuf bytes.Buffer
addToBuf := func(msgs ...proto.Message) error {
for _, msg := range msgs {
jsonBytes, err := lnrpc.ProtoJSONMarshalOpts.Marshal(
msg,
)
if err != nil {
return fmt.Errorf("error encoding response: %w",
err)
}
payloadBuf.Write(jsonBytes)
}
return nil
}
if err := addToBuf(info); err != nil {
return nil, err
}
if err := addToBuf(debugInfo); err != nil {
return nil, err
}
if err := addToBuf(info, debugInfo, networkInfo); err != nil {
return nil, err
}
// Add optional information to the payload.
if ctx.Bool("peers") {
peers, err := client.ListPeers(ctxc, &lnrpc.ListPeersRequest{
LatestError: true,
})
if err != nil {
return nil, fmt.Errorf("error getting peers: %w", err)
}
if err := addToBuf(peers); err != nil {
return nil, err
}
}
if ctx.Bool("onchain") {
unspent, err := client.ListUnspent(
ctxc, &lnrpc.ListUnspentRequest{
MaxConfs: math.MaxInt32,
},
)
if err != nil {
return nil, fmt.Errorf("error getting unspent: %w", err)
}
chainTxns, err := client.GetTransactions(
ctxc, &lnrpc.GetTransactionsRequest{},
)
if err != nil {
return nil, fmt.Errorf("error getting chain txns: %w",
err)
}
if err := addToBuf(unspent, chainTxns); err != nil {
return nil, err
}
}
if ctx.Bool("channels") {
channels, err := client.ListChannels(
ctxc, &lnrpc.ListChannelsRequest{},
)
if err != nil {
return nil, fmt.Errorf("error getting channels: %w",
err)
}
pendingChannels, err := client.PendingChannels(
ctxc, &lnrpc.PendingChannelsRequest{},
)
if err != nil {
return nil, fmt.Errorf("error getting pending "+
"channels: %w", err)
}
closedChannels, err := client.ClosedChannels(
ctxc, &lnrpc.ClosedChannelsRequest{},
)
if err != nil {
return nil, fmt.Errorf("error getting closed "+
"channels: %w", err)
}
if err := addToBuf(
channels, pendingChannels, closedChannels,
); err != nil {
return nil, err
}
}
return payloadBuf.Bytes(), nil
}
var decryptDebugPackageCommand = cli.Command{
Name: "decryptdebugpackage",
Category: "Debug",
Usage: "Decrypts a package of debug information.",
Description: `
Decrypt a debug package that was created with the encryptdebugpackage
command. Decryption requires the private key that corresponds to the
public key the package was encrypted to.
The command expects the encrypted package JSON to be provided on stdin.
If decryption is successful, the information will be printed to stdout.
Use 'lncli decryptdebugpackage 0xxxxxx... < package.txt > decrypted.txt'
to read the encrypted package from a file called package.txt and to
write the decrypted content to a file called decrypted.txt.
`,
ArgsUsage: "privkey [--input_file F]",
Flags: []cli.Flag{
cli.StringFlag{
Name: "privkey",
Usage: "the hex encoded private key to decrypt the " +
"debug package",
},
cli.StringFlag{
Name: "input_file",
Usage: "(optional) the file to read the encrypted " +
"package from; if not specified, the debug " +
"package is read from stdin",
},
},
Action: actionDecorator(decryptDebugPackage),
}
func decryptDebugPackage(ctx *cli.Context) error {
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
return cli.ShowCommandHelp(ctx, "decryptdebugpackage")
}
var (
args = ctx.Args()
privKeyBytes []byte
err error
)
switch {
case ctx.IsSet("pubkey"):
privKeyBytes, err = hex.DecodeString(ctx.String("pubkey"))
case args.Present():
privKeyBytes, err = hex.DecodeString(args.First())
}
if err != nil {
return fmt.Errorf("unable to decode privkey argument: %w", err)
}
privKey, _ := btcec.PrivKeyFromBytes(privKeyBytes)
// Read the file from stdin and decode the JSON into a DebugPackage.
var pkg DebugPackage
if ctx.IsSet("input_file") {
fileName := lnd.CleanAndExpandPath(ctx.String("input_file"))
jsonBytes, err := os.ReadFile(fileName)
if err != nil {
return fmt.Errorf("unable to read file '%s': %w",
fileName, err)
}
err = json.Unmarshal(jsonBytes, &pkg)
if err != nil {
return fmt.Errorf("unable to decode JSON: %w", err)
}
} else {
err = json.NewDecoder(os.Stdin).Decode(&pkg)
if err != nil {
return fmt.Errorf("unable to decode JSON: %w", err)
}
}
// Decode the ephemeral public key and encrypted payload.
ephemeralPubKeyBytes, err := hex.DecodeString(pkg.EphemeralPubKey)
if err != nil {
return fmt.Errorf("unable to decode ephemeral public key: %w",
err)
}
encryptedPayloadBytes, err := hex.DecodeString(pkg.EncryptedPayload)
if err != nil {
return fmt.Errorf("unable to decode encrypted payload: %w", err)
}
// Parse the ephemeral public key and create an encrypter.
ephemeralPubKey, err := btcec.ParsePubKey(ephemeralPubKeyBytes)
if err != nil {
return fmt.Errorf("unable to parse ephemeral public key: %w",
err)
}
enc, err := lnencrypt.ECDHEncrypter(privKey, ephemeralPubKey)
if err != nil {
return fmt.Errorf("unable to create encrypter: %w", err)
}
// Decrypt the payload.
decryptedPayload, err := enc.DecryptPayloadFromReader(
bytes.NewReader(encryptedPayloadBytes),
)
if err != nil {
return fmt.Errorf("unable to decrypt payload: %w", err)
}
// Decompress the payload.
reader := brotli.NewReader(bytes.NewBuffer(decryptedPayload))
decompressedPayload, err := io.ReadAll(reader)
if err != nil {
return fmt.Errorf("unable to decompress payload: %w", err)
}
fmt.Println(string(decompressedPayload))
return nil
}
package commands
import (
"context"
"errors"
"fmt"
"strconv"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/urfave/cli"
)
const argsStr = "[source node] [dest node] [unix ts seconds] [amount in msat]"
var importMissionControlCommand = cli.Command{
Name: "importmc",
Category: "Payments",
Usage: "Import a result to the internal mission control state.",
ArgsUsage: fmt.Sprintf("importmc %v", argsStr),
Action: actionDecorator(importMissionControl),
Flags: []cli.Flag{
cli.BoolFlag{
Name: "failure",
Usage: "whether the routing history entry was a failure",
},
cli.BoolFlag{
Name: "force",
Usage: "whether to force the history entry import",
},
},
}
func importMissionControl(ctx *cli.Context) error {
conn := getClientConn(ctx, false)
defer conn.Close()
if ctx.NArg() != 4 {
return fmt.Errorf("please provide args: %v", argsStr)
}
args := ctx.Args()
sourceNode, err := route.NewVertexFromStr(args[0])
if err != nil {
return fmt.Errorf("please provide valid source node: %w", err)
}
destNode, err := route.NewVertexFromStr(args[1])
if err != nil {
return fmt.Errorf("please provide valid dest node: %w", err)
}
ts, err := strconv.ParseInt(args[2], 10, 64)
if err != nil {
return fmt.Errorf("please provide unix timestamp "+
"in seconds: %v", err)
}
if ts <= 0 {
return errors.New("please provide positive timestamp")
}
amt, err := strconv.ParseInt(args[3], 10, 64)
if err != nil {
return fmt.Errorf("please provide amount in msat: %w", err)
}
// Allow 0 value as failure amount.
if !ctx.IsSet("failure") && amt <= 0 {
return errors.New("success amount must be >0")
}
client := routerrpc.NewRouterClient(conn)
importResult := &routerrpc.PairHistory{
NodeFrom: sourceNode[:],
NodeTo: destNode[:],
History: &routerrpc.PairData{},
}
if ctx.IsSet("failure") {
importResult.History.FailAmtMsat = amt
importResult.History.FailTime = ts
} else {
importResult.History.SuccessAmtMsat = amt
importResult.History.SuccessTime = ts
}
req := &routerrpc.XImportMissionControlRequest{
Pairs: []*routerrpc.PairHistory{
importResult,
},
Force: ctx.IsSet("force"),
}
rpcCtx := context.Background()
_, err = client.XImportMissionControl(rpcCtx, req)
return err
}
package commands
import (
"encoding/hex"
"fmt"
"strconv"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/urfave/cli"
)
var AddInvoiceCommand = cli.Command{
Name: "addinvoice",
Category: "Invoices",
Usage: "Add a new invoice.",
Description: `
Add a new invoice, expressing intent for a future payment.
Invoices without an amount can be created by not supplying any
parameters or providing an amount of 0. These invoices allow the payer
to specify the amount of satoshis they wish to send.`,
ArgsUsage: "value preimage",
Flags: []cli.Flag{
cli.StringFlag{
Name: "memo",
Usage: "a description of the payment to attach along " +
"with the invoice (default=\"\")",
},
cli.StringFlag{
Name: "preimage",
Usage: "the hex-encoded preimage (32 byte) which will " +
"allow settling an incoming HTLC payable to this " +
"preimage. If not set, a random preimage will be " +
"created.",
},
cli.Int64Flag{
Name: "amt",
Usage: "the amt of satoshis in this invoice",
},
cli.Int64Flag{
Name: "amt_msat",
Usage: "the amt of millisatoshis in this invoice",
},
cli.StringFlag{
Name: "description_hash",
Usage: "SHA-256 hash of the description of the payment. " +
"Used if the purpose of payment cannot naturally " +
"fit within the memo. If provided this will be " +
"used instead of the description(memo) field in " +
"the encoded invoice.",
},
cli.StringFlag{
Name: "fallback_addr",
Usage: "fallback on-chain address that can be used in " +
"case the lightning payment fails",
},
cli.Int64Flag{
Name: "expiry",
Usage: "the invoice's expiry time in seconds. If not " +
"specified, an expiry of " +
"86400 seconds (24 hours) is implied.",
},
cli.Uint64Flag{
Name: "cltv_expiry_delta",
Usage: "The minimum CLTV delta to use for the final " +
"hop. If this is set to 0, the default value " +
"is used. The default value for " +
"cltv_expiry_delta is configured by the " +
"'bitcoin.timelockdelta' option.",
},
cli.BoolFlag{
Name: "private",
Usage: "encode routing hints in the invoice with " +
"private channels in order to assist the " +
"payer in reaching you. If amt and amt_msat " +
"are zero, a large number of hints with " +
"these channels can be included, which " +
"might not be desirable.",
},
cli.BoolFlag{
Name: "amp",
Usage: "creates an AMP invoice. If true, preimage " +
"should not be set.",
},
cli.BoolFlag{
Name: "blind",
Usage: "creates an invoice that contains blinded " +
"paths. Note that invoices with blinded " +
"paths will be signed using a random " +
"ephemeral key so as not to reveal the real " +
"node ID of this node.",
},
cli.UintFlag{
Name: "min_real_blinded_hops",
Usage: "The minimum number of real hops to use in a " +
"blinded path. This option will only be used " +
"if `--blind` has also been set.",
},
cli.UintFlag{
Name: "num_blinded_hops",
Usage: "The number of hops to use for each " +
"blinded path included in the invoice. This " +
"option will only be used if `--blind` has " +
"also been set. Dummy hops will be used to " +
"pad paths shorter than this.",
},
cli.UintFlag{
Name: "max_blinded_paths",
Usage: "The maximum number of blinded paths to add " +
"to an invoice. This option will only be " +
"used if `--blind` has also been set.",
},
cli.StringSliceFlag{
Name: "blinded_path_omit_node",
Usage: "The pub key (in hex) of a node not to " +
"use on a blinded path. The flag may be " +
"specified multiple times.",
},
},
Action: actionDecorator(addInvoice),
}
func addInvoice(ctx *cli.Context) error {
var (
preimage []byte
descHash []byte
amt int64
amtMsat int64
err error
)
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
args := ctx.Args()
amt = ctx.Int64("amt")
amtMsat = ctx.Int64("amt_msat")
if !ctx.IsSet("amt") && !ctx.IsSet("amt_msat") && args.Present() {
amt, err = strconv.ParseInt(args.First(), 10, 64)
args = args.Tail()
if err != nil {
return fmt.Errorf("unable to decode amt argument: %w",
err)
}
}
switch {
case ctx.IsSet("preimage"):
preimage, err = hex.DecodeString(ctx.String("preimage"))
case args.Present():
preimage, err = hex.DecodeString(args.First())
}
if err != nil {
return fmt.Errorf("unable to parse preimage: %w", err)
}
descHash, err = hex.DecodeString(ctx.String("description_hash"))
if err != nil {
return fmt.Errorf("unable to parse description_hash: %w", err)
}
if ctx.IsSet("private") && ctx.IsSet("blind") {
return fmt.Errorf("cannot include both route hints and " +
"blinded paths in the same invoice")
}
blindedPathCfg, err := parseBlindedPathCfg(ctx)
if err != nil {
return fmt.Errorf("could not parse blinded path config: %w",
err)
}
invoice := &lnrpc.Invoice{
Memo: ctx.String("memo"),
RPreimage: preimage,
Value: amt,
ValueMsat: amtMsat,
DescriptionHash: descHash,
FallbackAddr: ctx.String("fallback_addr"),
Expiry: ctx.Int64("expiry"),
CltvExpiry: ctx.Uint64("cltv_expiry_delta"),
Private: ctx.Bool("private"),
IsAmp: ctx.Bool("amp"),
IsBlinded: ctx.Bool("blind"),
BlindedPathConfig: blindedPathCfg,
}
resp, err := client.AddInvoice(ctxc, invoice)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
func parseBlindedPathCfg(ctx *cli.Context) (*lnrpc.BlindedPathConfig, error) {
if !ctx.Bool("blind") {
if ctx.IsSet("min_real_blinded_hops") ||
ctx.IsSet("num_blinded_hops") ||
ctx.IsSet("max_blinded_paths") ||
ctx.IsSet("blinded_path_omit_node") {
return nil, fmt.Errorf("blinded path options are " +
"only used if the `--blind` options is set")
}
return nil, nil
}
var blindCfg lnrpc.BlindedPathConfig
if ctx.IsSet("min_real_blinded_hops") {
minNumRealHops := uint32(ctx.Uint("min_real_blinded_hops"))
blindCfg.MinNumRealHops = &minNumRealHops
}
if ctx.IsSet("num_blinded_hops") {
numHops := uint32(ctx.Uint("num_blinded_hops"))
blindCfg.NumHops = &numHops
}
if ctx.IsSet("max_blinded_paths") {
maxPaths := uint32(ctx.Uint("max_blinded_paths"))
blindCfg.MaxNumPaths = &maxPaths
}
for _, pubKey := range ctx.StringSlice("blinded_path_omit_node") {
pubKeyBytes, err := hex.DecodeString(pubKey)
if err != nil {
return nil, err
}
blindCfg.NodeOmissionList = append(
blindCfg.NodeOmissionList, pubKeyBytes,
)
}
return &blindCfg, nil
}
var lookupInvoiceCommand = cli.Command{
Name: "lookupinvoice",
Category: "Invoices",
Usage: "Lookup an existing invoice by its payment hash.",
ArgsUsage: "rhash",
Flags: []cli.Flag{
cli.StringFlag{
Name: "rhash",
Usage: "the 32 byte payment hash of the invoice to query for, the hash " +
"should be a hex-encoded string",
},
},
Action: actionDecorator(lookupInvoice),
}
func lookupInvoice(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
var (
rHash []byte
err error
)
switch {
case ctx.IsSet("rhash"):
rHash, err = hex.DecodeString(ctx.String("rhash"))
case ctx.Args().Present():
rHash, err = hex.DecodeString(ctx.Args().First())
default:
return fmt.Errorf("rhash argument missing")
}
if err != nil {
return fmt.Errorf("unable to decode rhash argument: %w", err)
}
req := &lnrpc.PaymentHash{
RHash: rHash,
}
invoice, err := client.LookupInvoice(ctxc, req)
if err != nil {
return err
}
printRespJSON(invoice)
return nil
}
var listInvoicesCommand = cli.Command{
Name: "listinvoices",
Category: "Invoices",
Usage: "List all invoices currently stored within the database. Any " +
"active debug invoices are ignored.",
Description: `
This command enables the retrieval of all invoices currently stored
within the database. It has full support for paginationed responses,
allowing users to query for specific invoices through their add_index.
This can be done by using either the first_index_offset or
last_index_offset fields included in the response as the index_offset of
the next request. Backward pagination is enabled by default to receive
current invoices first. If you wish to paginate forwards, set the
paginate-forwards flag. If none of the parameters are specified, then
the last 100 invoices will be returned.
For example: if you have 200 invoices, "lncli listinvoices" will return
the last 100 created. If you wish to retrieve the previous 100, the
first_offset_index of the response can be used as the index_offset of
the next listinvoices request.`,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "pending_only",
Usage: "toggles if all invoices should be returned, " +
"or only those that are currently unsettled",
},
cli.Uint64Flag{
Name: "index_offset",
Usage: "the index of an invoice that will be used as " +
"either the start or end of a query to " +
"determine which invoices should be returned " +
"in the response",
},
cli.Uint64Flag{
Name: "max_invoices",
Usage: "the max number of invoices to return",
},
cli.BoolFlag{
Name: "paginate-forwards",
Usage: "if set, invoices succeeding the " +
"index_offset will be returned",
},
cli.Uint64Flag{
Name: "creation_date_start",
Usage: "timestamp in seconds, if set, filter " +
"invoices with creation date greater than or " +
"equal to it",
},
cli.Uint64Flag{
Name: "creation_date_end",
Usage: "timestamp in seconds, if set, filter " +
"invoices with creation date less than or " +
"equal to it",
},
},
Action: actionDecorator(listInvoices),
}
func listInvoices(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
req := &lnrpc.ListInvoiceRequest{
PendingOnly: ctx.Bool("pending_only"),
IndexOffset: ctx.Uint64("index_offset"),
NumMaxInvoices: ctx.Uint64("max_invoices"),
Reversed: !ctx.Bool("paginate-forwards"),
CreationDateStart: ctx.Uint64("creation_date_start"),
CreationDateEnd: ctx.Uint64("creation_date_end"),
}
invoices, err := client.ListInvoices(ctxc, req)
if err != nil {
return err
}
printRespJSON(invoices)
return nil
}
var decodePayReqCommand = cli.Command{
Name: "decodepayreq",
Category: "Invoices",
Usage: "Decode a payment request.",
Description: "Decode the passed payment request revealing the destination, payment hash and value of the payment request",
ArgsUsage: "pay_req",
Flags: []cli.Flag{
cli.StringFlag{
Name: "pay_req",
Usage: "the bech32 encoded payment request",
},
},
Action: actionDecorator(decodePayReq),
}
func decodePayReq(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
var payreq string
switch {
case ctx.IsSet("pay_req"):
payreq = ctx.String("pay_req")
case ctx.Args().Present():
payreq = ctx.Args().First()
default:
return fmt.Errorf("pay_req argument missing")
}
resp, err := client.DecodePayReq(ctxc, &lnrpc.PayReqString{
PayReq: StripPrefix(payreq),
})
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
package commands
import (
"bytes"
"encoding/hex"
"fmt"
"net"
"os"
"strconv"
"strings"
"unicode"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/urfave/cli"
"google.golang.org/protobuf/proto"
"gopkg.in/macaroon-bakery.v2/bakery"
"gopkg.in/macaroon.v2"
)
var (
macTimeoutFlag = cli.Uint64Flag{
Name: "timeout",
Usage: "the number of seconds the macaroon will be " +
"valid before it times out",
}
macIPAddressFlag = cli.StringFlag{
Name: "ip_address",
Usage: "the IP address the macaroon will be bound to",
}
macIPRangeFlag = cli.StringFlag{
Name: "ip_range",
Usage: "the IP range the macaroon will be bound to",
}
macCustomCaveatNameFlag = cli.StringFlag{
Name: "custom_caveat_name",
Usage: "the name of the custom caveat to add",
}
macCustomCaveatConditionFlag = cli.StringFlag{
Name: "custom_caveat_condition",
Usage: "the condition of the custom caveat to add, can be " +
"empty if custom caveat doesn't need a value",
}
bakeFromRootKeyFlag = cli.StringFlag{
Name: "root_key",
Usage: "if the root key is known, it can be passed directly " +
"as a hex encoded string, turning the command into " +
"an offline operation",
}
)
var bakeMacaroonCommand = cli.Command{
Name: "bakemacaroon",
Category: "Macaroons",
Usage: "Bakes a new macaroon with the provided list of permissions " +
"and restrictions.",
ArgsUsage: "[--save_to=] [--timeout=] [--ip_address=] " +
"[--custom_caveat_name= [--custom_caveat_condition=]] " +
"[--root_key_id=] [--allow_external_permissions] " +
"[--root_key=] permissions...",
Description: `
Bake a new macaroon that grants the provided permissions and
optionally adds restrictions (timeout, IP address) to it.
The new macaroon can either be shown on command line in hex serialized
format or it can be saved directly to a file using the --save_to
argument.
A permission is a tuple of an entity and an action, separated by a
colon. Multiple operations can be added as arguments, for example:
lncli bakemacaroon info:read invoices:write foo:bar
For even more fine-grained permission control, it is also possible to
specify single RPC method URIs that are allowed to be accessed by a
macaroon. This can be achieved by specifying "uri:<methodURI>" pairs,
for example:
lncli bakemacaroon uri:/lnrpc.Lightning/GetInfo uri:/verrpc.Versioner/GetVersion
The macaroon created by this command would only be allowed to use the
"lncli getinfo" and "lncli version" commands.
To get a list of all available URIs and permissions, use the
"lncli listpermissions" command.
If the root key is known (for example because "lncli create" was used
with a custom --mac_root_key value), it can be passed directly as a
hex encoded string using the --root_key flag. This turns the command
into an offline operation and the macaroon will be created without
calling into the server's RPC endpoint.
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "save_to",
Usage: "save the created macaroon to this file " +
"using the default binary format",
},
macTimeoutFlag,
macIPAddressFlag,
macCustomCaveatNameFlag,
macCustomCaveatConditionFlag,
cli.Uint64Flag{
Name: "root_key_id",
Usage: "the numerical root key ID used to create the " +
"macaroon",
},
cli.BoolFlag{
Name: "allow_external_permissions",
Usage: "whether permissions lnd is not familiar with " +
"are allowed",
},
bakeFromRootKeyFlag,
},
Action: actionDecorator(bakeMacaroon),
}
func bakeMacaroon(ctx *cli.Context) error {
ctxc := getContext()
// Show command help if no arguments.
if ctx.NArg() == 0 {
return cli.ShowCommandHelp(ctx, "bakemacaroon")
}
args := ctx.Args()
var (
savePath string
rootKeyID uint64
parsedPermissions []*lnrpc.MacaroonPermission
err error
)
if ctx.String("save_to") != "" {
savePath = lncfg.CleanAndExpandPath(ctx.String("save_to"))
}
if ctx.IsSet("root_key_id") {
rootKeyID = ctx.Uint64("root_key_id")
}
// A command line argument can't be an empty string. So we'll check each
// entry if it's a valid entity:action tuple. The content itself is
// validated server side. We just make sure we can parse it correctly.
for _, permission := range args {
tuple := strings.Split(permission, ":")
if len(tuple) != 2 {
return fmt.Errorf("unable to parse "+
"permission tuple: %s", permission)
}
entity, action := tuple[0], tuple[1]
if entity == "" {
return fmt.Errorf("invalid permission [%s]. entity "+
"cannot be empty", permission)
}
if action == "" {
return fmt.Errorf("invalid permission [%s]. action "+
"cannot be empty", permission)
}
// No we can assume that we have a formally valid entity:action
// tuple. The rest of the validation happens server side.
parsedPermissions = append(
parsedPermissions, &lnrpc.MacaroonPermission{
Entity: entity,
Action: action,
},
)
}
var rawMacaroon *macaroon.Macaroon
switch {
case ctx.IsSet(bakeFromRootKeyFlag.Name):
macRootKey, err := hex.DecodeString(
ctx.String(bakeFromRootKeyFlag.Name),
)
if err != nil {
return fmt.Errorf("unable to parse macaroon root key: "+
"%w", err)
}
ops := fn.Map(
parsedPermissions,
func(p *lnrpc.MacaroonPermission) bakery.Op {
return bakery.Op{
Entity: p.Entity,
Action: p.Action,
}
},
)
rawMacaroon, err = macaroons.BakeFromRootKey(macRootKey, ops)
if err != nil {
return fmt.Errorf("unable to bake macaroon: %w", err)
}
default:
client, cleanUp := getClient(ctx)
defer cleanUp()
// Now we have gathered all the input we need and can do the
// actual RPC call.
req := &lnrpc.BakeMacaroonRequest{
Permissions: parsedPermissions,
RootKeyId: rootKeyID,
AllowExternalPermissions: ctx.Bool(
"allow_external_permissions",
),
}
resp, err := client.BakeMacaroon(ctxc, req)
if err != nil {
return err
}
// Now we should have gotten a valid macaroon. Unmarshal it so
// we can add first-party caveats (if necessary) to it.
macBytes, err := hex.DecodeString(resp.Macaroon)
if err != nil {
return err
}
rawMacaroon = &macaroon.Macaroon{}
if err = rawMacaroon.UnmarshalBinary(macBytes); err != nil {
return err
}
}
// Now apply the desired constraints to the macaroon. This will always
// create a new macaroon object, even if no constraints are added.
constrainedMac, err := applyMacaroonConstraints(ctx, rawMacaroon)
if err != nil {
return err
}
macBytes, err := constrainedMac.MarshalBinary()
if err != nil {
return err
}
// Now we can output the result. We either write it binary serialized to
// a file or write to the standard output using hex encoding.
switch {
case savePath != "":
err = os.WriteFile(savePath, macBytes, 0644)
if err != nil {
return err
}
fmt.Printf("Macaroon saved to %s\n", savePath)
default:
fmt.Printf("%s\n", hex.EncodeToString(macBytes))
}
return nil
}
var listMacaroonIDsCommand = cli.Command{
Name: "listmacaroonids",
Category: "Macaroons",
Usage: "List all macaroons root key IDs in use.",
Action: actionDecorator(listMacaroonIDs),
}
func listMacaroonIDs(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
req := &lnrpc.ListMacaroonIDsRequest{}
resp, err := client.ListMacaroonIDs(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var deleteMacaroonIDCommand = cli.Command{
Name: "deletemacaroonid",
Category: "Macaroons",
Usage: "Delete a specific macaroon ID.",
ArgsUsage: "root_key_id",
Description: `
Remove a macaroon ID using the specified root key ID. For example:
lncli deletemacaroonid 1
WARNING
When the ID is deleted, all macaroons created from that root key will
be invalidated.
Note that the default root key ID 0 cannot be deleted.
`,
Action: actionDecorator(deleteMacaroonID),
}
func deleteMacaroonID(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
// Validate args length. Only one argument is allowed.
if ctx.NArg() != 1 {
return cli.ShowCommandHelp(ctx, "deletemacaroonid")
}
rootKeyIDString := ctx.Args().First()
// Convert string into uint64.
rootKeyID, err := strconv.ParseUint(rootKeyIDString, 10, 64)
if err != nil {
return fmt.Errorf("root key ID must be a positive integer")
}
// Check that the value is not equal to DefaultRootKeyID. Note that the
// server also validates the root key ID when removing it. However, we check
// it here too so that we can give users a nice warning.
if bytes.Equal([]byte(rootKeyIDString), macaroons.DefaultRootKeyID) {
return fmt.Errorf("deleting the default root key ID 0 is not allowed")
}
// Make the actual RPC call.
req := &lnrpc.DeleteMacaroonIDRequest{
RootKeyId: rootKeyID,
}
resp, err := client.DeleteMacaroonID(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var listPermissionsCommand = cli.Command{
Name: "listpermissions",
Category: "Macaroons",
Usage: "Lists all RPC method URIs and the macaroon permissions they " +
"require to be invoked.",
Action: actionDecorator(listPermissions),
}
func listPermissions(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
request := &lnrpc.ListPermissionsRequest{}
response, err := client.ListPermissions(ctxc, request)
if err != nil {
return err
}
printRespJSON(response)
return nil
}
type macaroonContent struct {
Version uint16 `json:"version"`
Location string `json:"location"`
RootKeyID string `json:"root_key_id"`
Permissions []string `json:"permissions"`
Caveats []string `json:"caveats"`
}
var printMacaroonCommand = cli.Command{
Name: "printmacaroon",
Category: "Macaroons",
Usage: "Print the content of a macaroon in a human readable format.",
ArgsUsage: "[macaroon_content_hex]",
Description: `
Decode a macaroon and show its content in a more human readable format.
The macaroon can either be passed as a hex encoded positional parameter
or loaded from a file.
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "macaroon_file",
Usage: "load the macaroon from a file instead of the " +
"command line directly",
},
},
Action: actionDecorator(printMacaroon),
}
func printMacaroon(ctx *cli.Context) error {
// Show command help if no arguments or flags are set.
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
return cli.ShowCommandHelp(ctx, "printmacaroon")
}
var (
macBytes []byte
err error
args = ctx.Args()
)
switch {
case ctx.IsSet("macaroon_file"):
macPath := lncfg.CleanAndExpandPath(ctx.String("macaroon_file"))
// Load the specified macaroon file.
macBytes, err = os.ReadFile(macPath)
if err != nil {
return fmt.Errorf("unable to read macaroon path %v: %v",
macPath, err)
}
case args.Present():
macBytes, err = hex.DecodeString(args.First())
if err != nil {
return fmt.Errorf("unable to hex decode macaroon: %w",
err)
}
default:
return fmt.Errorf("macaroon parameter missing")
}
// Decode the macaroon and its protobuf encoded internal identifier.
mac := &macaroon.Macaroon{}
if err = mac.UnmarshalBinary(macBytes); err != nil {
return fmt.Errorf("unable to decode macaroon: %w", err)
}
rawID := mac.Id()
if rawID[0] != byte(bakery.LatestVersion) {
return fmt.Errorf("invalid macaroon version: %x", rawID)
}
decodedID := &lnrpc.MacaroonId{}
idProto := rawID[1:]
err = proto.Unmarshal(idProto, decodedID)
if err != nil {
return fmt.Errorf("unable to decode macaroon version: %w", err)
}
// Prepare everything to be printed in a more human-readable format.
content := &macaroonContent{
Version: uint16(mac.Version()),
Location: mac.Location(),
RootKeyID: string(decodedID.StorageId),
Permissions: nil,
Caveats: nil,
}
for _, caveat := range mac.Caveats() {
content.Caveats = append(content.Caveats, string(caveat.Id))
}
for _, op := range decodedID.Ops {
for _, action := range op.Actions {
permission := fmt.Sprintf("%s:%s", op.Entity, action)
content.Permissions = append(
content.Permissions, permission,
)
}
}
printJSON(content)
return nil
}
var constrainMacaroonCommand = cli.Command{
Name: "constrainmacaroon",
Category: "Macaroons",
Usage: "Adds one or more restriction(s) to an existing macaroon",
ArgsUsage: "[--timeout=] [--ip_address=] [--custom_caveat_name= " +
"[--custom_caveat_condition=]] input-macaroon-file " +
"constrained-macaroon-file",
Description: `
Add one or more first-party caveat(s) (a.k.a. constraints/restrictions)
to an existing macaroon.
`,
Flags: []cli.Flag{
macTimeoutFlag,
macIPAddressFlag,
macCustomCaveatNameFlag,
macCustomCaveatConditionFlag,
},
Action: actionDecorator(constrainMacaroon),
}
func constrainMacaroon(ctx *cli.Context) error {
// Show command help if not enough arguments.
if ctx.NArg() != 2 {
return cli.ShowCommandHelp(ctx, "constrainmacaroon")
}
args := ctx.Args()
sourceMacFile := lncfg.CleanAndExpandPath(args.First())
args = args.Tail()
sourceMacBytes, err := os.ReadFile(sourceMacFile)
if err != nil {
return fmt.Errorf("error trying to read source macaroon file "+
"%s: %v", sourceMacFile, err)
}
destMacFile := lncfg.CleanAndExpandPath(args.First())
// Now we should have gotten a valid macaroon. Unmarshal it so we can
// add first-party caveats (if necessary) to it.
sourceMac := &macaroon.Macaroon{}
if err = sourceMac.UnmarshalBinary(sourceMacBytes); err != nil {
return fmt.Errorf("error unmarshalling source macaroon file "+
"%s: %v", sourceMacFile, err)
}
// Now apply the desired constraints to the macaroon. This will always
// create a new macaroon object, even if no constraints are added.
constrainedMac, err := applyMacaroonConstraints(ctx, sourceMac)
if err != nil {
return err
}
destMacBytes, err := constrainedMac.MarshalBinary()
if err != nil {
return fmt.Errorf("error marshaling destination macaroon "+
"file: %v", err)
}
// Now we can output the result.
err = os.WriteFile(destMacFile, destMacBytes, 0644)
if err != nil {
return fmt.Errorf("error writing destination macaroon file "+
"%s: %v", destMacFile, err)
}
fmt.Printf("Macaroon saved to %s\n", destMacFile)
return nil
}
// applyMacaroonConstraints parses and applies all currently supported macaroon
// condition flags from the command line to the given macaroon and returns a new
// macaroon instance.
func applyMacaroonConstraints(ctx *cli.Context,
mac *macaroon.Macaroon) (*macaroon.Macaroon, error) {
macConstraints := make([]macaroons.Constraint, 0)
if ctx.IsSet(macTimeoutFlag.Name) {
timeout := ctx.Int64(macTimeoutFlag.Name)
if timeout <= 0 {
return nil, fmt.Errorf("timeout must be greater than 0")
}
macConstraints = append(
macConstraints, macaroons.TimeoutConstraint(timeout),
)
}
if ctx.IsSet(macIPAddressFlag.Name) {
ipAddress := net.ParseIP(ctx.String(macIPAddressFlag.Name))
if ipAddress == nil {
return nil, fmt.Errorf("unable to parse ip_address: %s",
ctx.String("ip_address"))
}
macConstraints = append(
macConstraints,
macaroons.IPLockConstraint(ipAddress.String()),
)
}
if ctx.IsSet(macIPRangeFlag.Name) {
_, net, err := net.ParseCIDR(ctx.String(macIPRangeFlag.Name))
if err != nil {
return nil, fmt.Errorf("unable to parse ip_range "+
"%s: %w", ctx.String("ip_range"), err)
}
macConstraints = append(
macConstraints,
macaroons.IPLockConstraint(net.String()),
)
}
if ctx.IsSet(macCustomCaveatNameFlag.Name) {
customCaveatName := ctx.String(macCustomCaveatNameFlag.Name)
if containsWhiteSpace(customCaveatName) {
return nil, fmt.Errorf("unexpected white space found " +
"in custom caveat name")
}
if customCaveatName == "" {
return nil, fmt.Errorf("invalid custom caveat name")
}
var customCaveatCond string
if ctx.IsSet(macCustomCaveatConditionFlag.Name) {
customCaveatCond = ctx.String(
macCustomCaveatConditionFlag.Name,
)
if containsWhiteSpace(customCaveatCond) {
return nil, fmt.Errorf("unexpected white " +
"space found in custom caveat " +
"condition")
}
if customCaveatCond == "" {
return nil, fmt.Errorf("invalid custom " +
"caveat condition")
}
}
// The custom caveat condition is optional, it could just be a
// marker tag in the macaroon with just a name. The interceptor
// itself doesn't care about the value anyway.
macConstraints = append(
macConstraints, macaroons.CustomConstraint(
customCaveatName, customCaveatCond,
),
)
}
constrainedMac, err := macaroons.AddConstraints(mac, macConstraints...)
if err != nil {
return nil, fmt.Errorf("error adding constraints: %w", err)
}
return constrainedMac, nil
}
// containsWhiteSpace returns true if the given string contains any character
// that is considered to be a white space or non-printable character such as
// space, tabulator, newline, carriage return and some more exotic ones.
func containsWhiteSpace(str string) bool {
return strings.IndexFunc(str, unicode.IsSpace) >= 0
}
package commands
import (
"fmt"
"strconv"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/urfave/cli"
)
var getCfgCommand = cli.Command{
Name: "getmccfg",
Category: "Mission Control",
Usage: "Display mission control's config.",
Description: `
Returns the config currently being used by mission control.
`,
Action: actionDecorator(getCfg),
}
func getCfg(ctx *cli.Context) error {
ctxc := getContext()
conn := getClientConn(ctx, false)
defer conn.Close()
client := routerrpc.NewRouterClient(conn)
resp, err := client.GetMissionControlConfig(
ctxc, &routerrpc.GetMissionControlConfigRequest{},
)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var setCfgCommand = cli.Command{
Name: "setmccfg",
Category: "Mission Control",
Usage: "Set mission control's config.",
Description: `
Update the config values being used by mission control to calculate the
probability that payment routes will succeed. The estimator type must be
provided to set estimator-related parameters.`,
Flags: []cli.Flag{
// General settings.
cli.UintFlag{
Name: "pmtnr",
Usage: "the number of payments mission control " +
"should store",
},
cli.DurationFlag{
Name: "failrelax",
Usage: "the amount of time to wait after a failure " +
"before raising failure amount",
},
// Probability estimator.
cli.StringFlag{
Name: "estimator",
Usage: "the probability estimator to use, choose " +
"between 'apriori' or 'bimodal' (bimodal is " +
"experimental)",
},
// Apriori config.
cli.DurationFlag{
Name: "apriorihalflife",
Usage: "the amount of time taken to restore a node " +
"or channel to 50% probability of success.",
},
cli.Float64Flag{
Name: "apriorihopprob",
Usage: "the probability of success assigned " +
"to hops that we have no information about",
},
cli.Float64Flag{
Name: "aprioriweight",
Usage: "the degree to which mission control should " +
"rely on historical results, expressed as " +
"value in [0, 1]",
},
cli.Float64Flag{
Name: "aprioricapacityfraction",
Usage: "the fraction of channels' capacities that is " +
"considered liquid in pathfinding, a value " +
"between [0.75-1.0]. a value of 1.0 disables " +
"this feature.",
},
// Bimodal config.
cli.DurationFlag{
Name: "bimodaldecaytime",
Usage: "the time span after which we phase out " +
"learnings from previous payment attempts",
},
cli.Uint64Flag{
Name: "bimodalscale",
Usage: "controls the assumed channel liquidity " +
"imbalance in the network, measured in msat. " +
"a low value (compared to typical channel " +
"capacity) anticipates unbalanced channels.",
},
cli.Float64Flag{
Name: "bimodalweight",
Usage: "controls the degree to which the probability " +
"estimator takes into account other channels " +
"of a router",
},
},
Action: actionDecorator(setCfg),
}
func setCfg(ctx *cli.Context) error {
ctxc := getContext()
conn := getClientConn(ctx, false)
defer conn.Close()
client := routerrpc.NewRouterClient(conn)
// Fetch current mission control config which we update to create our
// response.
mcCfg, err := client.GetMissionControlConfig(
ctxc, &routerrpc.GetMissionControlConfigRequest{},
)
if err != nil {
return err
}
// haveValue is a helper variable to determine if a flag has been set or
// the help should be displayed.
var haveValue bool
// Handle general mission control settings.
if ctx.IsSet("pmtnr") {
haveValue = true
mcCfg.Config.MaximumPaymentResults = uint32(ctx.Int("pmtnr"))
}
if ctx.IsSet("failrelax") {
haveValue = true
mcCfg.Config.MinimumFailureRelaxInterval = uint64(ctx.Duration(
"failrelax",
).Seconds())
}
// We switch between estimators and set corresponding configs. If
// estimator is not set, we ignore the values.
if ctx.IsSet("estimator") {
switch ctx.String("estimator") {
case routing.AprioriEstimatorName:
haveValue = true
// If we switch from another estimator, initialize with
// default values.
if mcCfg.Config.Model !=
routerrpc.MissionControlConfig_APRIORI {
dCfg := routing.DefaultAprioriConfig()
aParams := &routerrpc.AprioriParameters{
HalfLifeSeconds: uint64(
dCfg.PenaltyHalfLife.Seconds(),
),
HopProbability: dCfg.
AprioriHopProbability,
Weight: dCfg.AprioriWeight,
CapacityFraction: dCfg.CapacityFraction,
}
// We make sure the correct config is set.
mcCfg.Config.EstimatorConfig =
&routerrpc.MissionControlConfig_Apriori{
Apriori: aParams,
}
}
// We update all values for the apriori estimator.
mcCfg.Config.Model = routerrpc.
MissionControlConfig_APRIORI
aCfg := mcCfg.Config.GetApriori()
if ctx.IsSet("apriorihalflife") {
aCfg.HalfLifeSeconds = uint64(ctx.Duration(
"apriorihalflife",
).Seconds())
}
if ctx.IsSet("apriorihopprob") {
aCfg.HopProbability = ctx.Float64(
"apriorihopprob",
)
}
if ctx.IsSet("aprioriweight") {
aCfg.Weight = ctx.Float64("aprioriweight")
}
if ctx.IsSet("aprioricapacityfraction") {
aCfg.CapacityFraction =
ctx.Float64("aprioricapacityfraction")
}
case routing.BimodalEstimatorName:
haveValue = true
// If we switch from another estimator, initialize with
// default values.
if mcCfg.Config.Model !=
routerrpc.MissionControlConfig_BIMODAL {
dCfg := routing.DefaultBimodalConfig()
bParams := &routerrpc.BimodalParameters{
DecayTime: uint64(
dCfg.BimodalDecayTime.Seconds(),
),
ScaleMsat: uint64(
dCfg.BimodalScaleMsat,
),
NodeWeight: dCfg.BimodalNodeWeight,
}
// We make sure the correct config is set.
mcCfg.Config.EstimatorConfig =
&routerrpc.MissionControlConfig_Bimodal{
Bimodal: bParams,
}
}
// We update all values for the bimodal estimator.
mcCfg.Config.Model = routerrpc.
MissionControlConfig_BIMODAL
bCfg := mcCfg.Config.GetBimodal()
if ctx.IsSet("bimodaldecaytime") {
bCfg.DecayTime = uint64(ctx.Duration(
"bimodaldecaytime",
).Seconds())
}
if ctx.IsSet("bimodalscale") {
bCfg.ScaleMsat = ctx.Uint64("bimodalscale")
}
if ctx.IsSet("bimodalweight") {
bCfg.NodeWeight = ctx.Float64(
"bimodalweight",
)
}
default:
return fmt.Errorf("unknown estimator %v",
ctx.String("estimator"))
}
}
if !haveValue {
return cli.ShowCommandHelp(ctx, "setmccfg")
}
_, err = client.SetMissionControlConfig(
ctxc, &routerrpc.SetMissionControlConfigRequest{
Config: mcCfg.Config,
},
)
return err
}
var queryMissionControlCommand = cli.Command{
Name: "querymc",
Category: "Mission Control",
Usage: "Query the internal mission control state.",
Action: actionDecorator(queryMissionControl),
}
func queryMissionControl(ctx *cli.Context) error {
ctxc := getContext()
conn := getClientConn(ctx, false)
defer conn.Close()
client := routerrpc.NewRouterClient(conn)
req := &routerrpc.QueryMissionControlRequest{}
snapshot, err := client.QueryMissionControl(ctxc, req)
if err != nil {
return err
}
printRespJSON(snapshot)
return nil
}
var queryProbCommand = cli.Command{
Name: "queryprob",
Category: "Mission Control",
Usage: "Deprecated. Estimate a success probability.",
ArgsUsage: "from-node to-node amt",
Action: actionDecorator(queryProb),
Hidden: true,
}
func queryProb(ctx *cli.Context) error {
ctxc := getContext()
args := ctx.Args()
if len(args) != 3 {
return cli.ShowCommandHelp(ctx, "queryprob")
}
fromNode, err := route.NewVertexFromStr(args.Get(0))
if err != nil {
return fmt.Errorf("invalid from node key: %w", err)
}
toNode, err := route.NewVertexFromStr(args.Get(1))
if err != nil {
return fmt.Errorf("invalid to node key: %w", err)
}
amtSat, err := strconv.ParseUint(args.Get(2), 10, 64)
if err != nil {
return fmt.Errorf("invalid amt: %w", err)
}
amtMsat := lnwire.NewMSatFromSatoshis(
btcutil.Amount(amtSat),
)
conn := getClientConn(ctx, false)
defer conn.Close()
client := routerrpc.NewRouterClient(conn)
req := &routerrpc.QueryProbabilityRequest{
FromNode: fromNode[:],
ToNode: toNode[:],
AmtMsat: int64(amtMsat),
}
response, err := client.QueryProbability(ctxc, req)
if err != nil {
return err
}
printRespJSON(response)
return nil
}
var resetMissionControlCommand = cli.Command{
Name: "resetmc",
Category: "Mission Control",
Usage: "Reset internal mission control state.",
Action: actionDecorator(resetMissionControl),
}
func resetMissionControl(ctx *cli.Context) error {
ctxc := getContext()
conn := getClientConn(ctx, false)
defer conn.Close()
client := routerrpc.NewRouterClient(conn)
req := &routerrpc.ResetMissionControlRequest{}
_, err := client.ResetMissionControl(ctxc, req)
return err
}
package commands
import (
"bytes"
"context"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"os"
"strconv"
"strings"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
"github.com/urfave/cli"
)
const (
userMsgFund = `PSBT funding initiated with peer %x.
Please create a PSBT that sends %v (%d satoshi) to the funding address %s.
Note: The whole process should be completed within 10 minutes, otherwise there
is a risk of the remote node timing out and canceling the funding process.
Example with bitcoind:
bitcoin-cli walletcreatefundedpsbt [] '[{"%s":%.8f}]'
If you are using a wallet that can fund a PSBT directly (currently not possible
with bitcoind), you can use this PSBT that contains the same address and amount:
%s
!!! WARNING !!!
DO NOT PUBLISH the finished transaction by yourself or with another tool.
lnd MUST publish it in the proper funding flow order OR THE FUNDS CAN BE LOST!
Paste the funded PSBT here to continue the funding flow.
If your PSBT is very long (specifically, more than 4096 characters), please save
it to a file and paste the full file path here instead as some terminals will
truncate the pasted text if it's too long.
Base64 encoded PSBT (or path to file): `
userMsgSign = `
PSBT verified by lnd, please continue the funding flow by signing the PSBT by
all required parties/devices. Once the transaction is fully signed, paste it
again here either in base64 PSBT or hex encoded raw wire TX format.
Signed base64 encoded PSBT or hex encoded raw wire TX (or path to file): `
// psbtMaxFileSize is the maximum file size we allow a PSBT file to be
// in case we want to read a PSBT from a file. This is mainly to protect
// the user from choosing a large file by accident and running into out
// of memory issues or other weird errors.
psbtMaxFileSize = 1024 * 1024
channelTypeTweakless = "tweakless"
channelTypeAnchors = "anchors"
channelTypeSimpleTaproot = "taproot"
)
// TODO(roasbeef): change default number of confirmations.
var openChannelCommand = cli.Command{
Name: "openchannel",
Category: "Channels",
Usage: "Open a channel to a node or an existing peer.",
Description: `
Attempt to open a new channel to an existing peer with the key node-key
optionally blocking until the channel is 'open'.
One can also connect to a node before opening a new channel to it by
setting its host:port via the --connect argument. For this to work,
the node_key must be provided, rather than the peer_id. This is
optional.
The channel will be initialized with local-amt satoshis locally and
push-amt satoshis for the remote node. Note that the push-amt is
deducted from the specified local-amt which implies that the local-amt
must be greater than the push-amt. Also note that specifying push-amt
means you give that amount to the remote node as part of the channel
opening. Once the channel is open, a channelPoint (txid:vout) of the
funding output is returned.
If the remote peer supports the option upfront shutdown feature bit
(query listpeers to see their supported feature bits), an address to
enforce payout of funds on cooperative close can optionally be provided.
Note that if you set this value, you will not be able to cooperatively
close out to another address.
One can manually set the fee to be used for the funding transaction via
either the --conf_target or --sat_per_vbyte arguments. This is
optional.
One can also specify a short string memo to record some useful
information about the channel using the --memo argument. This is stored
locally only, and is purely for reference. It has no bearing on the
channel's operation. Max allowed length is 500 characters.`,
ArgsUsage: "node-key local-amt push-amt",
Flags: []cli.Flag{
cli.StringFlag{
Name: "node_key",
Usage: "the identity public key of the target " +
"node/peer serialized in compressed format",
},
cli.StringFlag{
Name: "connect",
Usage: "(optional) the host:port of the target node",
},
cli.IntFlag{
Name: "local_amt",
Usage: "the number of satoshis the wallet should " +
"commit to the channel",
},
cli.BoolFlag{
Name: "fundmax",
Usage: "if set, the wallet will attempt to commit " +
"the maximum possible local amount to the " +
"channel. This must not be set at the same " +
"time as local_amt",
},
cli.StringSliceFlag{
Name: "utxo",
Usage: "a utxo specified as outpoint(tx:idx) which " +
"will be used to fund a channel. This flag " +
"can be repeatedly used to fund a channel " +
"with a selection of utxos. The selected " +
"funds can either be entirely spent by " +
"specifying the fundmax flag or partially by " +
"selecting a fraction of the sum of the " +
"outpoints in local_amt",
},
cli.Uint64Flag{
Name: "base_fee_msat",
Usage: "the base fee in milli-satoshis that will " +
"be charged for each forwarded HTLC, " +
"regardless of payment size",
},
cli.Uint64Flag{
Name: "fee_rate_ppm",
Usage: "the fee rate ppm (parts per million) that " +
"will be charged proportionally based on the " +
"value of each forwarded HTLC, the lowest " +
"possible rate is 0 with a granularity of " +
"0.000001 (millionths)",
},
cli.IntFlag{
Name: "push_amt",
Usage: "the number of satoshis to give the remote " +
"side as part of the initial commitment " +
"state, this is equivalent to first opening " +
"a channel and sending the remote party " +
"funds, but done all in one step",
},
cli.BoolFlag{
Name: "block",
Usage: "block and wait until the channel is fully open",
},
cli.Int64Flag{
Name: "conf_target",
Usage: "(optional) the number of blocks that the " +
"transaction *should* confirm in, will be " +
"used for fee estimation",
},
cli.Int64Flag{
Name: "sat_per_byte",
Usage: "Deprecated, use sat_per_vbyte instead.",
Hidden: true,
},
cli.Int64Flag{
Name: "sat_per_vbyte",
Usage: "(optional) a manual fee expressed in " +
"sat/vbyte that should be used when crafting " +
"the transaction",
},
cli.BoolFlag{
Name: "private",
Usage: "make the channel private, such that it won't " +
"be announced to the greater network, and " +
"nodes other than the two channel endpoints " +
"must be explicitly told about it to be able " +
"to route through it",
},
cli.Int64Flag{
Name: "min_htlc_msat",
Usage: "(optional) the minimum value we will require " +
"for incoming HTLCs on the channel",
},
cli.Uint64Flag{
Name: "remote_csv_delay",
Usage: "(optional) the number of blocks we will " +
"require our channel counterparty to wait " +
"before accessing its funds in case of " +
"unilateral close. If this is not set, we " +
"will scale the value according to the " +
"channel size",
},
cli.Uint64Flag{
Name: "max_local_csv",
Usage: "(optional) the maximum number of blocks that " +
"we will allow the remote peer to require we " +
"wait before accessing our funds in the case " +
"of a unilateral close.",
},
cli.Uint64Flag{
Name: "min_confs",
Usage: "(optional) the minimum number of " +
"confirmations each one of your outputs used " +
"for the funding transaction must satisfy",
Value: defaultUtxoMinConf,
},
cli.StringFlag{
Name: "close_address",
Usage: "(optional) an address to enforce payout of " +
"our funds to on cooperative close. Note " +
"that if this value is set on channel open, " +
"you will *not* be able to cooperatively " +
"close to a different address.",
},
cli.BoolFlag{
Name: "psbt",
Usage: "start an interactive mode that initiates " +
"funding through a partially signed bitcoin " +
"transaction (PSBT), allowing the channel " +
"funds to be added and signed from a " +
"hardware or other offline device.",
},
cli.StringFlag{
Name: "base_psbt",
Usage: "when using the interactive PSBT mode to open " +
"a new channel, use this base64 encoded PSBT " +
"as a base and add the new channel output to " +
"it instead of creating a new, empty one.",
},
cli.BoolFlag{
Name: "no_publish",
Usage: "when using the interactive PSBT mode to open " +
"multiple channels in a batch, this flag " +
"instructs lnd to not publish the full batch " +
"transaction just yet. For safety reasons " +
"this flag should be set for each of the " +
"batch's transactions except the very last",
},
cli.Uint64Flag{
Name: "remote_max_value_in_flight_msat",
Usage: "(optional) the maximum value in msat that " +
"can be pending within the channel at any " +
"given time",
},
cli.StringFlag{
Name: "channel_type",
Usage: fmt.Sprintf("(optional) the type of channel to "+
"propose to the remote peer (%q, %q, %q)",
channelTypeTweakless, channelTypeAnchors,
channelTypeSimpleTaproot),
},
cli.BoolFlag{
Name: "zero_conf",
Usage: "(optional) whether a zero-conf channel open " +
"should be attempted.",
},
cli.BoolFlag{
Name: "scid_alias",
Usage: "(optional) whether a scid-alias channel type" +
" should be negotiated.",
},
cli.Uint64Flag{
Name: "remote_reserve_sats",
Usage: "(optional) the minimum number of satoshis we " +
"require the remote node to keep as a direct " +
"payment. If not specified, a default of 1% " +
"of the channel capacity will be used.",
},
cli.StringFlag{
Name: "memo",
Usage: `(optional) a note-to-self containing some useful
information about the channel. This is stored
locally only, and is purely for reference. It
has no bearing on the channel's operation. Max
allowed length is 500 characters`,
},
},
Action: actionDecorator(openChannel),
}
func openChannel(ctx *cli.Context) error {
// TODO(roasbeef): add deadline to context
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
args := ctx.Args()
var err error
// Show command help if no arguments provided
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
_ = cli.ShowCommandHelp(ctx, "openchannel")
return nil
}
// Check that only the field sat_per_vbyte or the deprecated field
// sat_per_byte is used.
feeRateFlag, err := checkNotBothSet(
ctx, "sat_per_vbyte", "sat_per_byte",
)
if err != nil {
return err
}
minConfs := int32(ctx.Uint64("min_confs"))
req := &lnrpc.OpenChannelRequest{
TargetConf: int32(ctx.Int64("conf_target")),
SatPerVbyte: ctx.Uint64(feeRateFlag),
MinHtlcMsat: ctx.Int64("min_htlc_msat"),
RemoteCsvDelay: uint32(ctx.Uint64("remote_csv_delay")),
MinConfs: minConfs,
SpendUnconfirmed: minConfs == 0,
CloseAddress: ctx.String("close_address"),
RemoteMaxValueInFlightMsat: ctx.Uint64("remote_max_value_in_flight_msat"),
MaxLocalCsv: uint32(ctx.Uint64("max_local_csv")),
ZeroConf: ctx.Bool("zero_conf"),
ScidAlias: ctx.Bool("scid_alias"),
RemoteChanReserveSat: ctx.Uint64("remote_reserve_sats"),
FundMax: ctx.Bool("fundmax"),
Memo: ctx.String("memo"),
}
switch {
case ctx.IsSet("node_key"):
nodePubHex, err := hex.DecodeString(ctx.String("node_key"))
if err != nil {
return fmt.Errorf("unable to decode node public key: "+
"%v", err)
}
req.NodePubkey = nodePubHex
case args.Present():
nodePubHex, err := hex.DecodeString(args.First())
if err != nil {
return fmt.Errorf("unable to decode node public key: "+
"%v", err)
}
args = args.Tail()
req.NodePubkey = nodePubHex
default:
return fmt.Errorf("node id argument missing")
}
// As soon as we can confirm that the node's node_key was set, rather
// than the peer_id, we can check if the host:port was also set to
// connect to it before opening the channel.
if req.NodePubkey != nil && ctx.IsSet("connect") {
addr := &lnrpc.LightningAddress{
Pubkey: hex.EncodeToString(req.NodePubkey),
Host: ctx.String("connect"),
}
req := &lnrpc.ConnectPeerRequest{
Addr: addr,
Perm: false,
}
// Check if connecting to the node was successful.
// We discard the peer id returned as it is not needed.
_, err := client.ConnectPeer(ctxc, req)
if err != nil &&
!strings.Contains(err.Error(), "already connected") {
return err
}
}
switch {
case ctx.IsSet("local_amt"):
req.LocalFundingAmount = int64(ctx.Int("local_amt"))
case args.Present():
req.LocalFundingAmount, err = strconv.ParseInt(
args.First(), 10, 64,
)
if err != nil {
return fmt.Errorf("unable to decode local amt: %w", err)
}
args = args.Tail()
case !ctx.Bool("fundmax"):
return fmt.Errorf("either local_amt or fundmax must be " +
"specified")
}
// The fundmax flag is NOT allowed to be combined with local_amt above.
// It is allowed to be combined with push_amt, but only if explicitly
// set.
if ctx.Bool("fundmax") && req.LocalFundingAmount != 0 {
return fmt.Errorf("local amount cannot be set if attempting " +
"to commit the maximum amount out of the wallet")
}
// The fundmax flag is NOT allowed to be combined with the psbt flag.
if ctx.Bool("fundmax") && ctx.Bool("psbt") {
return fmt.Errorf("psbt cannot be set if attempting " +
"to commit the maximum amount out of the wallet")
}
if ctx.IsSet("utxo") {
utxos := ctx.StringSlice("utxo")
outpoints, err := UtxosToOutpoints(utxos)
if err != nil {
return fmt.Errorf("unable to decode utxos: %w", err)
}
req.Outpoints = outpoints
}
if ctx.IsSet("push_amt") {
req.PushSat = int64(ctx.Int("push_amt"))
} else if args.Present() {
req.PushSat, err = strconv.ParseInt(args.First(), 10, 64)
if err != nil {
return fmt.Errorf("unable to decode push amt: %w", err)
}
}
if ctx.IsSet("base_fee_msat") {
req.BaseFee = ctx.Uint64("base_fee_msat")
req.UseBaseFee = true
}
if ctx.IsSet("fee_rate_ppm") {
req.FeeRate = ctx.Uint64("fee_rate_ppm")
req.UseFeeRate = true
}
req.Private = ctx.Bool("private")
// Parse the channel type and map it to its RPC representation.
channelType := ctx.String("channel_type")
switch channelType {
case "":
break
case channelTypeTweakless:
req.CommitmentType = lnrpc.CommitmentType_STATIC_REMOTE_KEY
case channelTypeAnchors:
req.CommitmentType = lnrpc.CommitmentType_ANCHORS
case channelTypeSimpleTaproot:
req.CommitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
default:
return fmt.Errorf("unsupported channel type %v", channelType)
}
// PSBT funding is a more involved, interactive process that is too
// large to also fit into this already long function.
if ctx.Bool("psbt") {
return openChannelPsbt(ctxc, ctx, client, req)
}
if !ctx.Bool("psbt") && ctx.Bool("no_publish") {
return fmt.Errorf("the --no_publish flag can only be used in " +
"combination with the --psbt flag")
}
stream, err := client.OpenChannel(ctxc, req)
if err != nil {
return err
}
for {
resp, err := stream.Recv()
if err == io.EOF {
return nil
} else if err != nil {
return err
}
switch update := resp.Update.(type) {
case *lnrpc.OpenStatusUpdate_ChanPending:
err := printChanPending(update)
if err != nil {
return err
}
if !ctx.Bool("block") {
return nil
}
case *lnrpc.OpenStatusUpdate_ChanOpen:
return printChanOpen(update)
}
}
}
// openChannelPsbt starts an interactive channel open protocol that uses a
// partially signed bitcoin transaction (PSBT) to fund the channel output. The
// protocol involves several steps between the RPC server and the CLI client:
//
// RPC server CLI client
//
// | |
// | |<------open channel (stream)-----|
// | |-------ready for funding----->| |
// | |<------PSBT verify------------| |
// | |-------ready for signing----->| |
// | |<------PSBT finalize----------| |
// | |-------channel pending------->| |
// | |-------channel open------------->|
// | |
func openChannelPsbt(rpcCtx context.Context, ctx *cli.Context,
client lnrpc.LightningClient,
req *lnrpc.OpenChannelRequest) error {
var (
pendingChanID [32]byte
shimPending = true
basePsbtBytes []byte
quit = make(chan struct{})
srvMsg = make(chan *lnrpc.OpenStatusUpdate, 1)
srvErr = make(chan error, 1)
ctxc, cancel = context.WithCancel(rpcCtx)
)
defer cancel()
// Make sure the user didn't supply any command line flags that are
// incompatible with PSBT funding.
err := checkPsbtFlags(req)
if err != nil {
return err
}
// If the user supplied a base PSBT, only make sure it's valid base64.
// The RPC server will make sure it's also a valid PSBT.
basePsbt := ctx.String("base_psbt")
if basePsbt != "" {
basePsbtBytes, err = base64.StdEncoding.DecodeString(basePsbt)
if err != nil {
return fmt.Errorf("error parsing base PSBT: %w", err)
}
}
// Generate a new, random pending channel ID that we'll use as the main
// identifier when sending update messages to the RPC server.
if _, err := rand.Read(pendingChanID[:]); err != nil {
return fmt.Errorf("unable to generate random chan ID: %w", err)
}
fmt.Printf("Starting PSBT funding flow with pending channel ID %x.\n",
pendingChanID)
// maybeCancelShim is a helper function that cancels the funding shim
// with the RPC server in case we end up aborting early.
maybeCancelShim := func() {
// If the user canceled while there was still a shim registered
// with the wallet, release the resources now.
if shimPending {
fmt.Printf("Canceling PSBT funding flow for pending "+
"channel ID %x.\n", pendingChanID)
cancelMsg := &lnrpc.FundingTransitionMsg{
Trigger: &lnrpc.FundingTransitionMsg_ShimCancel{
ShimCancel: &lnrpc.FundingShimCancel{
PendingChanId: pendingChanID[:],
},
},
}
err := sendFundingState(ctxc, ctx, cancelMsg)
if err != nil {
fmt.Printf("Error canceling shim: %v\n", err)
}
shimPending = false
}
// Abort the stream connection to the server.
cancel()
}
defer maybeCancelShim()
// Create the PSBT funding shim that will tell the funding manager we
// want to use a PSBT.
req.FundingShim = &lnrpc.FundingShim{
Shim: &lnrpc.FundingShim_PsbtShim{
PsbtShim: &lnrpc.PsbtShim{
PendingChanId: pendingChanID[:],
BasePsbt: basePsbtBytes,
NoPublish: ctx.Bool("no_publish"),
},
},
}
// Start the interactive process by opening the stream connection to the
// daemon. If the user cancels by pressing <Ctrl+C> we need to cancel
// the shim. To not just kill the process on interrupt, we need to
// explicitly capture the signal.
stream, err := client.OpenChannel(ctxc, req)
if err != nil {
return fmt.Errorf("opening stream to server failed: %w", err)
}
// We also need to spawn a goroutine that reads from the server. This
// will copy the messages to the channel as long as they come in or add
// exactly one error to the error stream and then bail out.
go func() {
for {
// Recv blocks until a message or error arrives.
resp, err := stream.Recv()
if err == io.EOF {
srvErr <- fmt.Errorf("lnd shutting down: %w",
err)
return
} else if err != nil {
srvErr <- fmt.Errorf("got error from server: "+
"%v", err)
return
}
// Don't block on sending in case of shutting down.
select {
case srvMsg <- resp:
case <-quit:
return
}
}
}()
// Spawn another goroutine that only handles abort from user or errors
// from the server. Both will trigger an attempt to cancel the shim with
// the server.
go func() {
select {
case <-rpcCtx.Done():
fmt.Printf("\nInterrupt signal received.\n")
close(quit)
case err := <-srvErr:
fmt.Printf("\nError received: %v\n", err)
// If the remote peer canceled on us, the reservation
// has already been deleted. We don't need to try to
// remove it again, this would just produce another
// error.
cancelErr := chanfunding.ErrRemoteCanceled.Error()
if err != nil && strings.Contains(
err.Error(), cancelErr,
) {
shimPending = false
}
close(quit)
case <-quit:
}
}()
// Our main event loop where we wait for triggers
for {
var srvResponse *lnrpc.OpenStatusUpdate
select {
case srvResponse = <-srvMsg:
case <-quit:
return nil
}
switch update := srvResponse.Update.(type) {
case *lnrpc.OpenStatusUpdate_PsbtFund:
// First tell the user how to create the PSBT with the
// address and amount we now know.
amt := btcutil.Amount(update.PsbtFund.FundingAmount)
addr := update.PsbtFund.FundingAddress
fmt.Printf(
userMsgFund, req.NodePubkey, amt, amt, addr,
addr, amt.ToBTC(),
base64.StdEncoding.EncodeToString(
update.PsbtFund.Psbt,
),
)
// Read the user's response and send it to the server to
// verify everything's correct before anything is
// signed.
inputPsbt, err := readTerminalOrFile(quit)
if err == io.EOF {
return nil
}
if err != nil {
return fmt.Errorf("reading from terminal or "+
"file failed: %v", err)
}
fundedPsbt, err := decodePsbt(inputPsbt)
if err != nil {
return fmt.Errorf("psbt decode failed: %w",
err)
}
verifyMsg := &lnrpc.FundingTransitionMsg{
Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{
PsbtVerify: &lnrpc.FundingPsbtVerify{
FundedPsbt: fundedPsbt,
PendingChanId: pendingChanID[:],
},
},
}
err = sendFundingState(ctxc, ctx, verifyMsg)
if err != nil {
return fmt.Errorf("verifying PSBT by lnd "+
"failed: %v", err)
}
// Now that we know the PSBT looks good, we can let it
// be signed by the user.
fmt.Print(userMsgSign)
// Read the signed PSBT and send it to lnd.
finalTxStr, err := readTerminalOrFile(quit)
if err == io.EOF {
return nil
}
if err != nil {
return fmt.Errorf("reading from terminal or "+
"file failed: %v", err)
}
finalizeMsg, err := finalizeMsgFromString(
finalTxStr, pendingChanID[:],
)
if err != nil {
return err
}
transitionMsg := &lnrpc.FundingTransitionMsg{
Trigger: finalizeMsg,
}
err = sendFundingState(ctxc, ctx, transitionMsg)
if err != nil {
return fmt.Errorf("finalizing PSBT funding "+
"flow failed: %v", err)
}
case *lnrpc.OpenStatusUpdate_ChanPending:
// As soon as the channel is pending, there is no more
// shim that needs to be canceled. If the user
// interrupts now, we don't need to clean up anything.
shimPending = false
err := printChanPending(update)
if err != nil {
return err
}
if !ctx.Bool("block") {
return nil
}
case *lnrpc.OpenStatusUpdate_ChanOpen:
return printChanOpen(update)
}
}
}
var batchOpenChannelCommand = cli.Command{
Name: "batchopenchannel",
Category: "Channels",
Usage: "Open multiple channels to existing peers in a single " +
"transaction.",
Description: `
Attempt to open one or more new channels to an existing peer with the
given node-keys.
Example:
lncli batchopenchannel --sat_per_vbyte=5 '[{
"node_pubkey": "02abcdef...",
"local_funding_amount": 500000,
"private": true,
"close_address": "bc1qxxx..."
}, {
"node_pubkey": "03fedcba...",
"local_funding_amount": 200000,
"remote_csv_delay": 288
}]'
All nodes listed must already be connected peers, otherwise funding will
fail.
The channel will be initialized with local_funding_amount satoshis
locally and push_sat satoshis for the remote node. Note that specifying
push_sat means you give that amount to the remote node as part of the
channel opening. Once the channel is open, a channelPoint (txid:vout) of
the funding output is returned.
If the remote peer supports the option upfront shutdown feature bit
(query listpeers to see their supported feature bits), an address to
enforce payout of funds on cooperative close can optionally be provided.
Note that if you set this value, you will not be able to cooperatively
close out to another address.
One can manually set the fee to be used for the funding transaction via
either the --conf_target or --sat_per_vbyte arguments. This is optional.
`,
ArgsUsage: "channels-json",
Flags: []cli.Flag{
cli.Int64Flag{
Name: "conf_target",
Usage: "(optional) the number of blocks that the " +
"transaction *should* confirm in, will be " +
"used for fee estimation",
},
cli.Int64Flag{
Name: "sat_per_vbyte",
Usage: "(optional) a manual fee expressed in " +
"sat/vByte that should be used when crafting " +
"the transaction",
},
cli.Uint64Flag{
Name: "min_confs",
Usage: "(optional) the minimum number of " +
"confirmations each one of your outputs used " +
"for the funding transaction must satisfy",
Value: defaultUtxoMinConf,
},
cli.StringFlag{
Name: "label",
Usage: "(optional) a label to attach to the batch " +
"transaction when storing it to the local " +
"wallet after publishing it",
},
coinSelectionStrategyFlag,
},
Action: actionDecorator(batchOpenChannel),
}
type batchChannelJSON struct {
NodePubkey string `json:"node_pubkey,omitempty"`
LocalFundingAmount int64 `json:"local_funding_amount,omitempty"`
PushSat int64 `json:"push_sat,omitempty"`
Private bool `json:"private,omitempty"`
MinHtlcMsat int64 `json:"min_htlc_msat,omitempty"`
RemoteCsvDelay uint32 `json:"remote_csv_delay,omitempty"`
CloseAddress string `json:"close_address,omitempty"`
PendingChanID string `json:"pending_chan_id,omitempty"`
}
func batchOpenChannel(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
args := ctx.Args()
// Show command help if no arguments provided
if ctx.NArg() == 0 {
_ = cli.ShowCommandHelp(ctx, "batchopenchannel")
return nil
}
coinSelectionStrategy, err := parseCoinSelectionStrategy(ctx)
if err != nil {
return err
}
minConfs := int32(ctx.Uint64("min_confs"))
req := &lnrpc.BatchOpenChannelRequest{
TargetConf: int32(ctx.Int64("conf_target")),
SatPerVbyte: int64(ctx.Uint64("sat_per_vbyte")),
MinConfs: minConfs,
SpendUnconfirmed: minConfs == 0,
Label: ctx.String("label"),
CoinSelectionStrategy: coinSelectionStrategy,
}
// Let's try and parse the JSON part of the CLI now. Fortunately we can
// parse it directly into the RPC struct if we use the correct
// marshaler that keeps the original snake case.
var jsonChannels []*batchChannelJSON
if err := json.Unmarshal([]byte(args.First()), &jsonChannels); err != nil {
return fmt.Errorf("error parsing channels JSON: %w", err)
}
req.Channels = make([]*lnrpc.BatchOpenChannel, len(jsonChannels))
for idx, jsonChannel := range jsonChannels {
pubKeyBytes, err := hex.DecodeString(jsonChannel.NodePubkey)
if err != nil {
return fmt.Errorf("error parsing node pubkey hex: %w",
err)
}
pendingChanBytes, err := hex.DecodeString(
jsonChannel.PendingChanID,
)
if err != nil {
return fmt.Errorf("error parsing pending chan ID: %w",
err)
}
req.Channels[idx] = &lnrpc.BatchOpenChannel{
NodePubkey: pubKeyBytes,
LocalFundingAmount: jsonChannel.LocalFundingAmount,
PushSat: jsonChannel.PushSat,
Private: jsonChannel.Private,
MinHtlcMsat: jsonChannel.MinHtlcMsat,
RemoteCsvDelay: jsonChannel.RemoteCsvDelay,
CloseAddress: jsonChannel.CloseAddress,
PendingChanId: pendingChanBytes,
}
}
resp, err := client.BatchOpenChannel(ctxc, req)
if err != nil {
return err
}
for _, pending := range resp.PendingChannels {
txid, err := chainhash.NewHash(pending.Txid)
if err != nil {
return err
}
printJSON(struct {
FundingTxid string `json:"funding_txid"`
FundingOutputIndex uint32 `json:"funding_output_index"`
}{
FundingTxid: txid.String(),
FundingOutputIndex: pending.OutputIndex,
})
}
return nil
}
// printChanOpen prints the channel point of the channel open message.
func printChanOpen(update *lnrpc.OpenStatusUpdate_ChanOpen) error {
channelPoint := update.ChanOpen.ChannelPoint
// A channel point's funding txid can be get/set as a
// byte slice or a string. In the case it is a string,
// decode it.
var txidHash []byte
switch channelPoint.GetFundingTxid().(type) {
case *lnrpc.ChannelPoint_FundingTxidBytes:
txidHash = channelPoint.GetFundingTxidBytes()
case *lnrpc.ChannelPoint_FundingTxidStr:
s := channelPoint.GetFundingTxidStr()
h, err := chainhash.NewHashFromStr(s)
if err != nil {
return err
}
txidHash = h[:]
}
txid, err := chainhash.NewHash(txidHash)
if err != nil {
return err
}
index := channelPoint.OutputIndex
printJSON(struct {
ChannelPoint string `json:"channel_point"`
}{
ChannelPoint: fmt.Sprintf("%v:%v", txid, index),
})
return nil
}
// printChanPending prints the funding transaction ID of the channel pending
// message.
func printChanPending(update *lnrpc.OpenStatusUpdate_ChanPending) error {
txid, err := chainhash.NewHash(update.ChanPending.Txid)
if err != nil {
return err
}
printJSON(struct {
FundingTxid string `json:"funding_txid"`
}{
FundingTxid: txid.String(),
})
return nil
}
// readTerminalOrFile reads a single line from the terminal. If the line read is
// short enough to be a file and a file with that exact name exists, the content
// of that file is read and returned as a string. If the content is longer or no
// file exists, the string read from the terminal is returned directly. This
// function can be used to circumvent the N_TTY_BUF_SIZE kernel parameter that
// prevents pasting more than 4096 characters (on most systems) into a terminal.
func readTerminalOrFile(quit chan struct{}) (string, error) {
maybeFile, err := readLine(quit)
if err != nil {
return "", err
}
// Absolute file paths normally can't be longer than 255 characters so
// we don't even check if it's a file in that case.
if len(maybeFile) > 255 {
return maybeFile, nil
}
// It might be a file since the length is small enough. Calling os.Stat
// should be safe with any arbitrary input as it will only query info
// about the file, not open or execute it directly.
stat, err := os.Stat(maybeFile)
// The file doesn't exist, we must assume this wasn't a file path after
// all.
if err != nil && os.IsNotExist(err) {
return maybeFile, nil
}
// Some other error, perhaps access denied or something similar, let's
// surface that to the user.
if err != nil {
return "", err
}
// Make sure we don't read a huge file by accident which might lead to
// undesired side effects. Even very large PSBTs should still only be a
// few hundred kilobytes so it makes sense to put a cap here.
if stat.Size() > psbtMaxFileSize {
return "", fmt.Errorf("error reading file %s: size of %d "+
"bytes exceeds max PSBT file size of %d", maybeFile,
stat.Size(), psbtMaxFileSize)
}
// If it's a path to an existing file and it's small enough, let's try
// to read its content now.
content, err := os.ReadFile(maybeFile)
if err != nil {
return "", err
}
return string(content), nil
}
// readLine reads a line from standard in but does not block in case of a
// system interrupt like syscall.SIGINT (Ctrl+C).
func readLine(quit chan struct{}) (string, error) {
msg := make(chan string, 1)
// In a normal console, reading from stdin won't signal EOF when the
// user presses Ctrl+C. That's why we need to put this in a separate
// goroutine so it doesn't block.
go func() {
for {
var str string
_, _ = fmt.Scan(&str)
msg <- str
return
}
}()
for {
select {
case <-quit:
return "", io.EOF
case str := <-msg:
return str, nil
}
}
}
// checkPsbtFlags make sure a request to open a channel doesn't set any
// parameters that are incompatible with the PSBT funding flow.
func checkPsbtFlags(req *lnrpc.OpenChannelRequest) error {
if req.MinConfs != defaultUtxoMinConf || req.SpendUnconfirmed {
return fmt.Errorf("specifying minimum confirmations for PSBT " +
"funding is not supported")
}
if req.TargetConf != 0 || req.SatPerByte != 0 || req.SatPerVbyte != 0 { // nolint:staticcheck
return fmt.Errorf("setting fee estimation parameters not " +
"supported for PSBT funding")
}
return nil
}
// sendFundingState sends a single funding state step message by using a new
// client connection. This is necessary if the whole funding flow takes longer
// than the default macaroon timeout, then we cannot use a single client
// connection.
func sendFundingState(cancelCtx context.Context, cliCtx *cli.Context,
msg *lnrpc.FundingTransitionMsg) error {
client, cleanUp := getClient(cliCtx)
defer cleanUp()
_, err := client.FundingStateStep(cancelCtx, msg)
return err
}
// finalizeMsgFromString creates the final message for the PsbtFinalize step
// from either a hex encoded raw wire transaction or a base64/binary encoded
// PSBT packet.
func finalizeMsgFromString(tx string,
pendingChanID []byte) (*lnrpc.FundingTransitionMsg_PsbtFinalize,
error) {
psbtBytes, err := decodePsbt(tx)
if err == nil {
return &lnrpc.FundingTransitionMsg_PsbtFinalize{
PsbtFinalize: &lnrpc.FundingPsbtFinalize{
SignedPsbt: psbtBytes,
PendingChanId: pendingChanID,
},
}, nil
}
// PSBT decode failed, try to parse it as a hex encoded Bitcoin
// transaction
rawTx, err := hex.DecodeString(strings.TrimSpace(tx))
if err != nil {
return nil, fmt.Errorf("hex decode failed: %w", err)
}
msgtx := &wire.MsgTx{}
err = msgtx.Deserialize(bytes.NewReader(rawTx))
if err != nil {
return nil, fmt.Errorf("deserializing as raw wire "+
"transaction failed: %v", err)
}
return &lnrpc.FundingTransitionMsg_PsbtFinalize{
PsbtFinalize: &lnrpc.FundingPsbtFinalize{
FinalRawTx: rawTx,
PendingChanId: pendingChanID,
},
}, nil
}
// decodePsbt tries to decode the input as a binary or base64 PSBT. If this
// succeeded, the PSBT bytes are returned, an error otherwise.
func decodePsbt(psbt string) ([]byte, error) {
switch {
case strings.HasPrefix(psbt, "psbt\xff"):
// A binary PSBT (read from a file) always starts with the PSBT
// magic "psbt\xff" according to BIP 174
return []byte(psbt), nil
case strings.HasPrefix(strings.TrimSpace(psbt), "cHNidP"):
// A base64 PSBT always starts with "cHNidP". This is the
// longest base64 representation of the PSBT magic that is not
// dependent on the byte after it.
psbtBytes, err := base64.StdEncoding.DecodeString(
strings.TrimSpace(psbt),
)
if err != nil {
return nil, fmt.Errorf("base64 decode failed: %w", err)
}
return psbtBytes, nil
default:
return nil, fmt.Errorf("not a PSBT")
}
}
package commands
import (
"bytes"
"context"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"io"
"os"
"runtime"
"strconv"
"strings"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/urfave/cli"
"google.golang.org/grpc"
"google.golang.org/protobuf/proto"
)
const (
// paymentTimeout is the default timeout for the payment loop in lnd.
// No new attempts will be started after the timeout.
paymentTimeout = time.Second * 60
)
var (
cltvLimitFlag = cli.UintFlag{
Name: "cltv_limit",
Usage: "the maximum time lock that may be used for " +
"this payment",
}
lastHopFlag = cli.StringFlag{
Name: "last_hop",
Usage: "pubkey of the last hop (penultimate node in the path) " +
"to route through for this payment",
}
dataFlag = cli.StringFlag{
Name: "data",
Usage: "attach custom data to the payment. The required " +
"format is: <record_id>=<hex_value>,<record_id>=" +
"<hex_value>,.. For example: --data 3438382=0a21ff. " +
"Custom record ids start from 65536.",
}
inflightUpdatesFlag = cli.BoolFlag{
Name: "inflight_updates",
Usage: "if set, intermediate payment state updates will be " +
"displayed. Only valid in combination with --json.",
}
maxPartsFlag = cli.UintFlag{
Name: "max_parts",
Usage: "the maximum number of partial payments that may be " +
"used",
Value: routerrpc.DefaultMaxParts,
}
jsonFlag = cli.BoolFlag{
Name: "json",
Usage: "if set, payment updates are printed as json " +
"messages. Set by default on Windows because table " +
"formatting is unsupported.",
}
maxShardSizeSatFlag = cli.UintFlag{
Name: "max_shard_size_sat",
Usage: "the largest payment split that should be attempted if " +
"payment splitting is required to attempt a payment, " +
"specified in satoshis",
}
maxShardSizeMsatFlag = cli.UintFlag{
Name: "max_shard_size_msat",
Usage: "the largest payment split that should be attempted if " +
"payment splitting is required to attempt a payment, " +
"specified in milli-satoshis",
}
ampFlag = cli.BoolFlag{
Name: "amp",
Usage: "if set to true, then AMP will be used to complete the " +
"payment",
}
timePrefFlag = cli.Float64Flag{
Name: "time_pref",
Usage: "(optional) expresses time preference (range -1 to 1)",
}
introductionNodeFlag = cli.StringFlag{
Name: "introduction_node",
Usage: "(blinded paths) the hex encoded, cleartext node ID " +
"of the node to use for queries to a blinded route",
}
blindingPointFlag = cli.StringFlag{
Name: "blinding_point",
Usage: "(blinded paths) the hex encoded blinding point to " +
"use if querying a route to a blinded path, this " +
"value *must* be set for queries to a blinded path",
}
blindedHopsFlag = cli.StringSliceFlag{
Name: "blinded_hops",
Usage: "(blinded paths) the blinded hops to include in the " +
"query, formatted as <blinded_node_id>:" +
"<hex_encrypted_data>. These hops must be provided " +
"*in order* starting with the introduction point and " +
"ending with the receiving node",
}
blindedBaseFlag = cli.Uint64Flag{
Name: "blinded_base_fee",
Usage: "(blinded paths) the aggregate base fee for the " +
"blinded portion of the route, expressed in msat",
}
blindedPPMFlag = cli.Uint64Flag{
Name: "blinded_ppm_fee",
Usage: "(blinded paths) the aggregate proportional fee for " +
"the blinded portion of the route, expressed in " +
"parts per million",
}
blindedCLTVFlag = cli.Uint64Flag{
Name: "blinded_cltv",
Usage: "(blinded paths) the total cltv delay for the " +
"blinded portion of the route",
}
cancelableFlag = cli.BoolFlag{
Name: "cancelable",
Usage: "if set to true, the payment loop can be interrupted " +
"by manually canceling the payment context, even " +
"before the payment timeout is reached. Note that " +
"the payment may still succeed after cancellation, " +
"as in-flight attempts can still settle afterwards. " +
"Canceling will only prevent further attempts from " +
"being sent",
}
)
// PaymentFlags returns common flags for sendpayment and payinvoice.
func PaymentFlags() []cli.Flag {
return []cli.Flag{
cli.StringFlag{
Name: "pay_req",
Usage: "a zpay32 encoded payment request to fulfill",
},
cli.Int64Flag{
Name: "fee_limit",
Usage: "maximum fee allowed in satoshis when " +
"sending the payment",
},
cli.Int64Flag{
Name: "fee_limit_percent",
Usage: "percentage of the payment's amount used as " +
"the maximum fee allowed when sending the " +
"payment",
},
cli.DurationFlag{
Name: "timeout",
Usage: "the maximum amount of time we should spend " +
"trying to fulfill the payment, failing " +
"after the timeout has elapsed",
Value: paymentTimeout,
},
cancelableFlag,
cltvLimitFlag,
lastHopFlag,
cli.Int64SliceFlag{
Name: "outgoing_chan_id",
Usage: "short channel id of the outgoing channel to " +
"use for the first hop of the payment; can " +
"be specified multiple times in the same " +
"command",
Value: &cli.Int64Slice{},
},
cli.BoolFlag{
Name: "force, f",
Usage: "will skip payment request confirmation",
},
cli.BoolFlag{
Name: "allow_self_payment",
Usage: "allow sending a circular payment to self",
},
dataFlag, inflightUpdatesFlag, maxPartsFlag, jsonFlag,
maxShardSizeSatFlag, maxShardSizeMsatFlag, ampFlag,
timePrefFlag,
}
}
var SendPaymentCommand = cli.Command{
Name: "sendpayment",
Category: "Payments",
Usage: "Send a payment over lightning.",
Description: `
Send a payment over Lightning. One can either specify the full
parameters of the payment, or just use a payment request which encodes
all the payment details.
If payment isn't manually specified, then only a payment request needs
to be passed using the --pay_req argument.
If the payment *is* manually specified, then the following arguments
need to be specified in order to complete the payment:
For invoice with keysend,
--dest=N --amt=A --final_cltv_delta=T --keysend
For invoice without payment address:
--dest=N --amt=A --payment_hash=H --final_cltv_delta=T
For invoice with payment address:
--dest=N --amt=A --payment_hash=H --final_cltv_delta=T --pay_addr=H
`,
ArgsUsage: "dest amt payment_hash final_cltv_delta pay_addr | " +
"--pay_req=R [--pay_addr=H]",
Flags: append(PaymentFlags(),
cli.StringFlag{
Name: "dest, d",
Usage: "the compressed identity pubkey of the " +
"payment recipient",
},
cli.Int64Flag{
Name: "amt, a",
Usage: "number of satoshis to send",
},
cli.StringFlag{
Name: "payment_hash, r",
Usage: "the hash to use within the payment's HTLC",
},
cli.Int64Flag{
Name: "final_cltv_delta",
Usage: "the number of blocks the last hop has to reveal the preimage",
},
cli.StringFlag{
Name: "pay_addr",
Usage: "the payment address of the generated invoice",
},
cli.BoolFlag{
Name: "keysend",
Usage: "will generate a pre-image and encode it in the sphinx packet, a dest must be set [experimental]",
},
),
Action: SendPayment,
}
// retrieveFeeLimit retrieves the fee limit based on the different fee limit
// flags passed. It always returns a value and doesn't rely on lnd applying a
// default.
func retrieveFeeLimit(ctx *cli.Context, amt int64) (int64, error) {
switch {
case ctx.IsSet("fee_limit") && ctx.IsSet("fee_limit_percent"):
return 0, fmt.Errorf("either fee_limit or fee_limit_percent " +
"can be set, but not both")
case ctx.IsSet("fee_limit"):
return ctx.Int64("fee_limit"), nil
case ctx.IsSet("fee_limit_percent"):
// Round up the fee limit to prevent hitting zero on small
// amounts.
feeLimitRoundedUp :=
(amt*ctx.Int64("fee_limit_percent") + 99) / 100
return feeLimitRoundedUp, nil
}
// If no fee limit is set, use a default value based on the amount.
amtMsat := lnwire.NewMSatFromSatoshis(btcutil.Amount(amt))
limitMsat := lnwallet.DefaultRoutingFeeLimitForAmount(amtMsat)
return int64(limitMsat.ToSatoshis()), nil
}
func confirmPayReq(resp *lnrpc.PayReq, amt, feeLimit int64) error {
fmt.Printf("Payment hash: %v\n", resp.GetPaymentHash())
fmt.Printf("Description: %v\n", resp.GetDescription())
fmt.Printf("Amount (in satoshis): %v\n", amt)
fmt.Printf("Fee limit (in satoshis): %v\n", feeLimit)
fmt.Printf("Destination: %v\n", resp.GetDestination())
confirm := promptForConfirmation("Confirm payment (yes/no): ")
if !confirm {
return fmt.Errorf("payment not confirmed")
}
return nil
}
func parsePayAddr(ctx *cli.Context, args cli.Args) ([]byte, error) {
var (
payAddr []byte
err error
)
switch {
case ctx.IsSet("pay_addr"):
payAddr, err = hex.DecodeString(ctx.String("pay_addr"))
case args.Present():
payAddr, err = hex.DecodeString(args.First())
}
if err != nil {
return nil, err
}
// payAddr may be not required if it's a legacy invoice.
if len(payAddr) != 0 && len(payAddr) != 32 {
return nil, fmt.Errorf("payment addr must be exactly 32 "+
"bytes, is instead %v", len(payAddr))
}
return payAddr, nil
}
func SendPayment(ctx *cli.Context) error {
// Show command help if no arguments provided
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
_ = cli.ShowCommandHelp(ctx, "sendpayment")
return nil
}
conn := getClientConn(ctx, false)
defer conn.Close()
args := ctx.Args()
// If a payment request was provided, we can exit early since all of the
// details of the payment are encoded within the request.
if ctx.IsSet("pay_req") {
req := &routerrpc.SendPaymentRequest{
PaymentRequest: StripPrefix(ctx.String("pay_req")),
Amt: ctx.Int64("amt"),
DestCustomRecords: make(map[uint64][]byte),
Amp: ctx.Bool(ampFlag.Name),
Cancelable: ctx.Bool(cancelableFlag.Name),
}
// We'll attempt to parse a payment address as well, given that
// if the user is using an AMP invoice, then they may be trying
// to specify that value manually.
//
// Don't parse unnamed arguments to prevent confusion with the
// main unnamed argument format for non-AMP payments.
payAddr, err := parsePayAddr(ctx, nil)
if err != nil {
return err
}
req.PaymentAddr = payAddr
return SendPaymentRequest(
ctx, req, conn, conn, routerRPCSendPayment,
)
}
var (
destNode []byte
amount int64
err error
)
switch {
case ctx.IsSet("dest"):
destNode, err = hex.DecodeString(ctx.String("dest"))
case args.Present():
destNode, err = hex.DecodeString(args.First())
args = args.Tail()
default:
return fmt.Errorf("destination txid argument missing")
}
if err != nil {
return err
}
if len(destNode) != 33 {
return fmt.Errorf("dest node pubkey must be exactly 33 bytes, is "+
"instead: %v", len(destNode))
}
if ctx.IsSet("amt") {
amount = ctx.Int64("amt")
} else if args.Present() {
amount, err = strconv.ParseInt(args.First(), 10, 64)
args = args.Tail()
if err != nil {
return fmt.Errorf("unable to decode payment amount: %w",
err)
}
}
req := &routerrpc.SendPaymentRequest{
Dest: destNode,
Amt: amount,
DestCustomRecords: make(map[uint64][]byte),
Amp: ctx.Bool(ampFlag.Name),
Cancelable: ctx.Bool(cancelableFlag.Name),
}
var rHash []byte
switch {
case ctx.Bool("keysend") && ctx.Bool(ampFlag.Name):
return errors.New("either keysend or amp may be set, but not both")
case ctx.Bool("keysend"):
if ctx.IsSet("payment_hash") {
return errors.New("cannot set payment hash when using " +
"keysend")
}
var preimage lntypes.Preimage
if _, err := rand.Read(preimage[:]); err != nil {
return err
}
// Set the preimage. If the user supplied a preimage with the
// data flag, the preimage that is set here will be overwritten
// later.
req.DestCustomRecords[record.KeySendType] = preimage[:]
hash := preimage.Hash()
rHash = hash[:]
case !ctx.Bool(ampFlag.Name):
switch {
case ctx.IsSet("payment_hash"):
rHash, err = hex.DecodeString(ctx.String("payment_hash"))
case args.Present():
rHash, err = hex.DecodeString(args.First())
args = args.Tail()
default:
return fmt.Errorf("payment hash argument missing")
}
}
if err != nil {
return err
}
if !req.Amp && len(rHash) != 32 {
return fmt.Errorf("payment hash must be exactly 32 "+
"bytes, is instead %v", len(rHash))
}
req.PaymentHash = rHash
switch {
case ctx.IsSet("final_cltv_delta"):
req.FinalCltvDelta = int32(ctx.Int64("final_cltv_delta"))
case args.Present():
delta, err := strconv.ParseInt(args.First(), 10, 64)
if err != nil {
return err
}
args = args.Tail()
req.FinalCltvDelta = int32(delta)
}
payAddr, err := parsePayAddr(ctx, args)
if err != nil {
return err
}
req.PaymentAddr = payAddr
return SendPaymentRequest(ctx, req, conn, conn, routerRPCSendPayment)
}
// SendPaymentFn is a function type that abstracts the SendPaymentV2 call of the
// router client.
type SendPaymentFn func(ctx context.Context, payConn grpc.ClientConnInterface,
req *routerrpc.SendPaymentRequest) (PaymentResultStream, error)
// routerRPCSendPayment is the default implementation of the SendPaymentFn type
// that uses the lnd routerrpc.SendPaymentV2 call.
func routerRPCSendPayment(ctx context.Context, payConn grpc.ClientConnInterface,
req *routerrpc.SendPaymentRequest) (PaymentResultStream, error) {
return routerrpc.NewRouterClient(payConn).SendPaymentV2(ctx, req)
}
func SendPaymentRequest(ctx *cli.Context, req *routerrpc.SendPaymentRequest,
lnConn, paymentConn grpc.ClientConnInterface,
callSendPayment SendPaymentFn) error {
ctxc := getContext()
lnClient := lnrpc.NewLightningClient(lnConn)
outChan := ctx.Int64Slice("outgoing_chan_id")
if len(outChan) != 0 {
req.OutgoingChanIds = make([]uint64, len(outChan))
for i, c := range outChan {
req.OutgoingChanIds[i] = uint64(c)
}
}
if ctx.IsSet(lastHopFlag.Name) {
lastHop, err := route.NewVertexFromStr(
ctx.String(lastHopFlag.Name),
)
if err != nil {
return err
}
req.LastHopPubkey = lastHop[:]
}
req.CltvLimit = int32(ctx.Int(cltvLimitFlag.Name))
pmtTimeout := ctx.Duration("timeout")
if pmtTimeout <= 0 {
return errors.New("payment timeout must be greater than zero")
}
req.TimeoutSeconds = int32(pmtTimeout.Seconds())
req.AllowSelfPayment = ctx.Bool("allow_self_payment")
req.MaxParts = uint32(ctx.Uint(maxPartsFlag.Name))
switch {
// If the max shard size is specified, then it should either be in sat
// or msat, but not both.
case ctx.Uint64(maxShardSizeMsatFlag.Name) != 0 &&
ctx.Uint64(maxShardSizeSatFlag.Name) != 0:
return fmt.Errorf("only --max_split_size_msat or " +
"--max_split_size_sat should be set, but not both")
case ctx.Uint64(maxShardSizeMsatFlag.Name) != 0:
req.MaxShardSizeMsat = ctx.Uint64(maxShardSizeMsatFlag.Name)
case ctx.Uint64(maxShardSizeSatFlag.Name) != 0:
req.MaxShardSizeMsat = uint64(lnwire.NewMSatFromSatoshis(
btcutil.Amount(ctx.Uint64(maxShardSizeSatFlag.Name)),
))
}
// Parse custom data records.
data := ctx.String(dataFlag.Name)
if data != "" {
records := strings.Split(data, ",")
for _, r := range records {
kv := strings.Split(r, "=")
if len(kv) != 2 {
return errors.New("invalid data format: " +
"multiple equal signs in record")
}
recordID, err := strconv.ParseUint(kv[0], 10, 64)
if err != nil {
return fmt.Errorf("invalid data format: %w",
err)
}
hexValue, err := hex.DecodeString(kv[1])
if err != nil {
return fmt.Errorf("invalid data format: %w",
err)
}
req.DestCustomRecords[recordID] = hexValue
}
}
var feeLimit int64
if req.PaymentRequest != "" {
// Decode payment request to find out the amount.
decodeReq := &lnrpc.PayReqString{PayReq: req.PaymentRequest}
decodeResp, err := lnClient.DecodePayReq(ctxc, decodeReq)
if err != nil {
return err
}
// If amount is present in the request, override the request
// amount.
amt := req.Amt
invoiceAmt := decodeResp.GetNumSatoshis()
if invoiceAmt != 0 {
amt = invoiceAmt
}
// Calculate fee limit based on the determined amount.
feeLimit, err = retrieveFeeLimit(ctx, amt)
if err != nil {
return err
}
// Ask for confirmation of amount and fee limit if payment is
// forced.
if !ctx.Bool("force") {
err := confirmPayReq(decodeResp, amt, feeLimit)
if err != nil {
return err
}
}
} else {
var err error
feeLimit, err = retrieveFeeLimit(ctx, req.Amt)
if err != nil {
return err
}
}
req.FeeLimitSat = feeLimit
// Set time pref.
req.TimePref = ctx.Float64(timePrefFlag.Name)
// Always print in-flight updates for the table output.
printJSON := ctx.Bool(jsonFlag.Name)
req.NoInflightUpdates = !ctx.Bool(inflightUpdatesFlag.Name) && printJSON
stream, err := callSendPayment(ctxc, paymentConn, req)
if err != nil {
return err
}
finalState, err := PrintLivePayment(ctxc, stream, lnClient, printJSON)
if err != nil {
return err
}
// If we get a payment error back, we pass an error up
// to main which eventually calls fatal() and returns
// with a non-zero exit code.
if finalState.Status != lnrpc.Payment_SUCCEEDED {
return errors.New(finalState.Status.String())
}
return nil
}
var trackPaymentCommand = cli.Command{
Name: "trackpayment",
Category: "Payments",
Usage: "Track progress of an existing payment.",
Description: `
Pick up monitoring the progression of a previously initiated payment
specified by the hash argument.
`,
ArgsUsage: "hash",
Flags: []cli.Flag{
jsonFlag,
},
Action: actionDecorator(trackPayment),
}
func trackPayment(ctx *cli.Context) error {
ctxc := getContext()
args := ctx.Args()
conn := getClientConn(ctx, false)
defer conn.Close()
routerClient := routerrpc.NewRouterClient(conn)
if !args.Present() {
return fmt.Errorf("hash argument missing")
}
hash, err := hex.DecodeString(args.First())
if err != nil {
return err
}
req := &routerrpc.TrackPaymentRequest{
PaymentHash: hash,
}
stream, err := routerClient.TrackPaymentV2(ctxc, req)
if err != nil {
return err
}
client := lnrpc.NewLightningClient(conn)
_, err = PrintLivePayment(ctxc, stream, client, ctx.Bool(jsonFlag.Name))
return err
}
// PaymentResultStream is an interface that abstracts the Recv method of the
// SendPaymentV2 or TrackPaymentV2 client stream.
type PaymentResultStream interface {
Recv() (*lnrpc.Payment, error)
}
// PrintLivePayment receives payment updates from the given stream and either
// outputs them as json or as a more user-friendly formatted table. The table
// option uses terminal control codes to rewrite the output. This call
// terminates when the payment reaches a final state.
func PrintLivePayment(ctxc context.Context, stream PaymentResultStream,
lnClient lnrpc.LightningClient, json bool) (*lnrpc.Payment, error) {
// Terminal escape codes aren't supported on Windows, fall back to json.
if !json && runtime.GOOS == "windows" {
json = true
}
aliases := newAliasCache(lnClient)
first := true
var lastLineCount int
for {
payment, err := stream.Recv()
if err != nil {
return nil, err
}
if json {
// Delimit json messages by newlines (inspired by
// grpc over rest chunking).
if first {
first = false
} else {
fmt.Println()
}
// Write raw json to stdout.
printRespJSON(payment)
} else {
resultTable := formatPayment(ctxc, payment, aliases)
// Clear all previously written lines and print the
// updated table.
clearLines(lastLineCount)
fmt.Print(resultTable)
// Store the number of lines written for the next update
// pass.
lastLineCount = 0
for _, b := range resultTable {
if b == '\n' {
lastLineCount++
}
}
}
// Terminate loop if payments state is final.
if payment.Status != lnrpc.Payment_IN_FLIGHT &&
payment.Status != lnrpc.Payment_INITIATED {
return payment, nil
}
}
}
// aliasCache allows cached retrieval of node aliases.
type aliasCache struct {
cache map[string]string
client lnrpc.LightningClient
}
func newAliasCache(client lnrpc.LightningClient) *aliasCache {
return &aliasCache{
client: client,
cache: make(map[string]string),
}
}
// get returns a node alias either from cache or freshly requested from lnd.
func (a *aliasCache) get(ctxc context.Context, pubkey string) string {
alias, ok := a.cache[pubkey]
if ok {
return alias
}
// Request node info.
resp, err := a.client.GetNodeInfo(
ctxc,
&lnrpc.NodeInfoRequest{
PubKey: pubkey,
},
)
if err != nil {
// If no info is available, use the
// pubkey as identifier.
alias = pubkey[:6]
} else {
alias = resp.Node.Alias
}
a.cache[pubkey] = alias
return alias
}
// formatMsat formats msat amounts as fractional sats.
func formatMsat(amt int64) string {
return strconv.FormatFloat(float64(amt)/1000.0, 'f', -1, 64)
}
// formatPayment formats the payment state as an ascii table.
func formatPayment(ctxc context.Context, payment *lnrpc.Payment,
aliases *aliasCache) string {
t := table.NewWriter()
// Build table header.
t.AppendHeader(table.Row{
"HTLC_STATE", "ATTEMPT_TIME", "RESOLVE_TIME", "RECEIVER_AMT",
"FEE", "TIMELOCK", "CHAN_OUT", "ROUTE",
})
t.SetColumnConfigs([]table.ColumnConfig{
{Name: "ATTEMPT_TIME", Align: text.AlignRight},
{Name: "RESOLVE_TIME", Align: text.AlignRight},
{Name: "CHAN_OUT", Align: text.AlignLeft,
AlignHeader: text.AlignLeft},
})
// Add all htlcs as rows.
createTime := time.Unix(0, payment.CreationTimeNs)
var totalPaid, totalFees int64
for _, htlc := range payment.Htlcs {
formatTime := func(timeNs int64) string {
if timeNs == 0 {
return "-"
}
resolveTime := time.Unix(0, timeNs)
resolveTimeDiff := resolveTime.Sub(createTime)
resolveTimeMs := resolveTimeDiff / time.Millisecond
return fmt.Sprintf(
"%.3f", float64(resolveTimeMs)/1000.0,
)
}
attemptTime := formatTime(htlc.AttemptTimeNs)
resolveTime := formatTime(htlc.ResolveTimeNs)
route := htlc.Route
lastHop := route.Hops[len(route.Hops)-1]
hops := []string{}
for _, h := range route.Hops {
alias := aliases.get(ctxc, h.PubKey)
hops = append(hops, alias)
}
state := htlc.Status.String()
if htlc.Failure != nil {
state = fmt.Sprintf(
"%v @ %s hop",
htlc.Failure.Code,
ordinalNumber(htlc.Failure.FailureSourceIndex),
)
}
t.AppendRow([]interface{}{
state, attemptTime, resolveTime,
formatMsat(lastHop.AmtToForwardMsat),
formatMsat(route.TotalFeesMsat),
route.TotalTimeLock, route.Hops[0].ChanId,
strings.Join(hops, "->")},
)
if htlc.Status == lnrpc.HTLCAttempt_SUCCEEDED {
totalPaid += lastHop.AmtToForwardMsat
totalFees += route.TotalFeesMsat
}
}
// Render table.
b := &bytes.Buffer{}
t.SetOutputMirror(b)
t.Render()
// Add additional payment-level data.
fmt.Fprintf(b, "Amount + fee: %v + %v sat\n",
formatMsat(totalPaid), formatMsat(totalFees))
fmt.Fprintf(b, "Payment hash: %v\n", payment.PaymentHash)
fmt.Fprintf(b, "Payment status: %v", payment.Status)
switch payment.Status {
case lnrpc.Payment_SUCCEEDED:
fmt.Fprintf(b, ", preimage: %v", payment.PaymentPreimage)
case lnrpc.Payment_FAILED:
fmt.Fprintf(b, ", reason: %v", payment.FailureReason)
}
fmt.Fprintf(b, "\n")
return b.String()
}
var payInvoiceCommand = cli.Command{
Name: "payinvoice",
Category: "Payments",
Usage: "Pay an invoice over lightning.",
Description: `
This command is a shortcut for 'sendpayment --pay_req='.
`,
ArgsUsage: "pay_req",
Flags: append(PaymentFlags(),
cli.Int64Flag{
Name: "amt",
Usage: "(optional) number of satoshis to fulfill the " +
"invoice",
},
),
Action: actionDecorator(payInvoice),
}
func payInvoice(ctx *cli.Context) error {
conn := getClientConn(ctx, false)
defer conn.Close()
args := ctx.Args()
var payReq string
switch {
case ctx.IsSet("pay_req"):
payReq = ctx.String("pay_req")
case args.Present():
payReq = args.First()
default:
return fmt.Errorf("pay_req argument missing")
}
req := &routerrpc.SendPaymentRequest{
PaymentRequest: StripPrefix(payReq),
Amt: ctx.Int64("amt"),
DestCustomRecords: make(map[uint64][]byte),
Amp: ctx.Bool(ampFlag.Name),
Cancelable: ctx.Bool(cancelableFlag.Name),
}
return SendPaymentRequest(ctx, req, conn, conn, routerRPCSendPayment)
}
var sendToRouteCommand = cli.Command{
Name: "sendtoroute",
Category: "Payments",
Usage: "Send a payment over a predefined route.",
Description: `
Send a payment over Lightning using a specific route. One must specify
the route to attempt and the payment hash. This command can even
be chained with the response to queryroutes or buildroute. This command
can be used to implement channel rebalancing by crafting a self-route,
or even atomic swaps using a self-route that crosses multiple chains.
There are three ways to specify a route:
* using the --routes parameter to manually specify a JSON encoded
route in the format of the return value of queryroutes or
buildroute:
(lncli sendtoroute --payment_hash=<pay_hash> --routes=<route>)
* passing the route as a positional argument:
(lncli sendtoroute --payment_hash=pay_hash <route>)
* or reading in the route from stdin, which can allow chaining the
response from queryroutes or buildroute, or even read in a file
with a pre-computed route:
(lncli queryroutes --args.. | lncli sendtoroute --payment_hash= -
notice the '-' at the end, which signals that lncli should read
the route in from stdin
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "payment_hash, pay_hash",
Usage: "the hash to use within the payment's HTLC",
},
cli.StringFlag{
Name: "routes, r",
Usage: "a json array string in the format of the response " +
"of queryroutes that denotes which routes to use",
},
cli.BoolFlag{
Name: "skip_temp_err",
Usage: "Whether the payment should be marked as " +
"failed when a temporary error occurred. Set " +
"it to true so the payment won't be failed " +
"unless a terminal error has occurred.",
},
},
Action: sendToRoute,
}
func sendToRoute(ctx *cli.Context) error {
// Show command help if no arguments provided.
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
_ = cli.ShowCommandHelp(ctx, "sendtoroute")
return nil
}
args := ctx.Args()
var (
rHash []byte
err error
)
switch {
case ctx.IsSet("payment_hash"):
rHash, err = hex.DecodeString(ctx.String("payment_hash"))
case args.Present():
rHash, err = hex.DecodeString(args.First())
args = args.Tail()
default:
return fmt.Errorf("payment hash argument missing")
}
if err != nil {
return err
}
if len(rHash) != 32 {
return fmt.Errorf("payment hash must be exactly 32 "+
"bytes, is instead %d", len(rHash))
}
var jsonRoutes string
switch {
// The user is specifying the routes explicitly via the key word
// argument.
case ctx.IsSet("routes"):
jsonRoutes = ctx.String("routes")
// The user is specifying the routes as a positional argument.
case args.Present() && args.First() != "-":
jsonRoutes = args.First()
// The user is signalling that we should read stdin in order to parse
// the set of target routes.
case args.Present() && args.First() == "-":
b, err := io.ReadAll(os.Stdin)
if err != nil {
return err
}
if len(b) == 0 {
return fmt.Errorf("queryroutes output is empty")
}
jsonRoutes = string(b)
}
// Try to parse the provided json both in the legacy QueryRoutes format
// that contains a list of routes and the single route BuildRoute
// format.
var route *lnrpc.Route
routes := &lnrpc.QueryRoutesResponse{}
err = lnrpc.ProtoJSONUnmarshalOpts.Unmarshal([]byte(jsonRoutes), routes)
if err == nil {
if len(routes.Routes) == 0 {
return fmt.Errorf("no routes provided")
}
if len(routes.Routes) != 1 {
return fmt.Errorf("expected a single route, but got %v",
len(routes.Routes))
}
route = routes.Routes[0]
} else {
routes := &routerrpc.BuildRouteResponse{}
err = lnrpc.ProtoJSONUnmarshalOpts.Unmarshal(
[]byte(jsonRoutes), routes,
)
if err != nil {
return fmt.Errorf("unable to unmarshal json string "+
"from incoming array of routes: %v", err)
}
route = routes.Route
}
req := &routerrpc.SendToRouteRequest{
PaymentHash: rHash,
Route: route,
SkipTempErr: ctx.Bool("skip_temp_err"),
}
return sendToRouteRequest(ctx, req)
}
func sendToRouteRequest(ctx *cli.Context, req *routerrpc.SendToRouteRequest) error {
ctxc := getContext()
conn := getClientConn(ctx, false)
defer conn.Close()
client := routerrpc.NewRouterClient(conn)
resp, err := client.SendToRouteV2(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var queryRoutesCommand = cli.Command{
Name: "queryroutes",
Category: "Payments",
Usage: "Query a route to a destination.",
Description: "Queries the channel router for a potential path to the destination that has sufficient flow for the amount including fees",
ArgsUsage: "dest amt",
Flags: []cli.Flag{
cli.StringFlag{
Name: "dest",
Usage: "the 33-byte hex-encoded public key for the payment " +
"destination",
},
cli.Int64Flag{
Name: "amt",
Usage: "the amount to send expressed in satoshis",
},
cli.Int64Flag{
Name: "fee_limit",
Usage: "maximum fee allowed in satoshis when sending " +
"the payment",
},
cli.Int64Flag{
Name: "fee_limit_percent",
Usage: "percentage of the payment's amount used as the " +
"maximum fee allowed when sending the payment",
},
cli.Int64Flag{
Name: "final_cltv_delta",
Usage: "(optional) number of blocks the last hop has " +
"to reveal the preimage. Note that this " +
"should not be set in the case where the " +
"path includes a blinded path since in " +
"that case, the receiver will already have " +
"accounted for this value in the " +
"blinded_cltv value",
},
cli.BoolFlag{
Name: "use_mc",
Usage: "use mission control probabilities",
},
cli.Uint64Flag{
Name: "outgoing_chan_id",
Usage: "(optional) the channel id of the channel " +
"that must be taken to the first hop",
},
cli.StringSliceFlag{
Name: "ignore_pair",
Usage: "ignore directional node pair " +
"<node1>:<node2>. This flag can be specified " +
"multiple times if multiple node pairs are " +
"to be ignored",
},
timePrefFlag,
cltvLimitFlag,
introductionNodeFlag,
blindingPointFlag,
blindedHopsFlag,
blindedBaseFlag,
blindedPPMFlag,
blindedCLTVFlag,
},
Action: actionDecorator(queryRoutes),
}
func queryRoutes(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
var (
dest string
amt int64
err error
)
args := ctx.Args()
switch {
case ctx.IsSet("dest"):
dest = ctx.String("dest")
case args.Present():
dest = args.First()
args = args.Tail()
// If we have a blinded path set, we don't have to specify a
// destination.
case ctx.IsSet(introductionNodeFlag.Name):
default:
return fmt.Errorf("dest argument missing")
}
switch {
case ctx.IsSet("amt"):
amt = ctx.Int64("amt")
case args.Present():
amt, err = strconv.ParseInt(args.First(), 10, 64)
if err != nil {
return fmt.Errorf("unable to decode amt argument: %w",
err)
}
default:
return fmt.Errorf("amt argument missing")
}
feeLimit, err := retrieveFeeLimitLegacy(ctx)
if err != nil {
return err
}
pairs := ctx.StringSlice("ignore_pair")
ignoredPairs := make([]*lnrpc.NodePair, len(pairs))
for i, pair := range pairs {
nodes := strings.Split(pair, ":")
if len(nodes) != 2 {
return fmt.Errorf("invalid node pair format. " +
"Expected <node1 pub key>:<node2 pub key>")
}
node1, err := hex.DecodeString(nodes[0])
if err != nil {
return err
}
node2, err := hex.DecodeString(nodes[1])
if err != nil {
return err
}
ignoredPairs[i] = &lnrpc.NodePair{
From: node1,
To: node2,
}
}
blindedRoutes, err := parseBlindedPaymentParameters(ctx)
if err != nil {
return err
}
req := &lnrpc.QueryRoutesRequest{
PubKey: dest,
Amt: amt,
FeeLimit: feeLimit,
FinalCltvDelta: int32(ctx.Int("final_cltv_delta")),
UseMissionControl: ctx.Bool("use_mc"),
CltvLimit: uint32(ctx.Uint64(cltvLimitFlag.Name)),
OutgoingChanId: ctx.Uint64("outgoing_chan_id"),
TimePref: ctx.Float64(timePrefFlag.Name),
IgnoredPairs: ignoredPairs,
BlindedPaymentPaths: blindedRoutes,
}
route, err := client.QueryRoutes(ctxc, req)
if err != nil {
return err
}
printRespJSON(route)
return nil
}
func parseBlindedPaymentParameters(ctx *cli.Context) (
[]*lnrpc.BlindedPaymentPath, error) {
// Return nil if we don't have a blinding set, as we don't have a
// blinded path.
if !ctx.IsSet(blindingPointFlag.Name) {
return nil, nil
}
// If a blinded path has been provided, then the final_cltv_delta flag
// should not be provided since this value will be ignored.
if ctx.IsSet("final_cltv_delta") {
return nil, fmt.Errorf("`final_cltv_delta` should not be " +
"provided if a blinded path is provided")
}
// If any one of our blinding related flags is set, we expect the
// full set to be set and we'll error out accordingly.
introNode, err := route.NewVertexFromStr(
ctx.String(introductionNodeFlag.Name),
)
if err != nil {
return nil, fmt.Errorf("decode introduction node: %w", err)
}
blindingPoint, err := route.NewVertexFromStr(ctx.String(
blindingPointFlag.Name,
))
if err != nil {
return nil, fmt.Errorf("decode blinding point: %w", err)
}
blindedHops := ctx.StringSlice(blindedHopsFlag.Name)
pmt := &lnrpc.BlindedPaymentPath{
BlindedPath: &lnrpc.BlindedPath{
IntroductionNode: introNode[:],
BlindingPoint: blindingPoint[:],
BlindedHops: make(
[]*lnrpc.BlindedHop, len(blindedHops),
),
},
BaseFeeMsat: ctx.Uint64(
blindedBaseFlag.Name,
),
ProportionalFeeRate: uint32(ctx.Uint64(
blindedPPMFlag.Name,
)),
TotalCltvDelta: uint32(ctx.Uint64(
blindedCLTVFlag.Name,
)),
}
for i, hop := range blindedHops {
parts := strings.Split(hop, ":")
if len(parts) != 2 {
return nil, fmt.Errorf("blinded hops should be "+
"expressed as "+
"blinded_node_id:hex_encrypted_data, got: %v",
hop)
}
hop, err := route.NewVertexFromStr(parts[0])
if err != nil {
return nil, fmt.Errorf("hop: %v node: %w", i, err)
}
data, err := hex.DecodeString(parts[1])
if err != nil {
return nil, fmt.Errorf("hop: %v data: %w", i, err)
}
pmt.BlindedPath.BlindedHops[i] = &lnrpc.BlindedHop{
BlindedNode: hop[:],
EncryptedData: data,
}
}
return []*lnrpc.BlindedPaymentPath{
pmt,
}, nil
}
// retrieveFeeLimitLegacy retrieves the fee limit based on the different fee
// limit flags passed. This function will eventually disappear in favor of
// retrieveFeeLimit and the new payment rpc.
func retrieveFeeLimitLegacy(ctx *cli.Context) (*lnrpc.FeeLimit, error) {
switch {
case ctx.IsSet("fee_limit") && ctx.IsSet("fee_limit_percent"):
return nil, fmt.Errorf("either fee_limit or fee_limit_percent " +
"can be set, but not both")
case ctx.IsSet("fee_limit"):
return &lnrpc.FeeLimit{
Limit: &lnrpc.FeeLimit_Fixed{
Fixed: ctx.Int64("fee_limit"),
},
}, nil
case ctx.IsSet("fee_limit_percent"):
feeLimitPercent := ctx.Int64("fee_limit_percent")
if feeLimitPercent < 0 {
return nil, errors.New("negative fee limit percentage " +
"provided")
}
return &lnrpc.FeeLimit{
Limit: &lnrpc.FeeLimit_Percent{
Percent: feeLimitPercent,
},
}, nil
}
// Since the fee limit flags aren't required, we don't return an error
// if they're not set.
return nil, nil
}
var listPaymentsCommand = cli.Command{
Name: "listpayments",
Category: "Payments",
Usage: "List all outgoing payments.",
Description: `
This command enables the retrieval of payments stored
in the database.
Pagination is supported by the usage of index_offset in combination with
the paginate_forwards flag.
Reversed pagination is enabled by default to receive current payments
first. Pagination can be resumed by using the returned last_index_offset
(for forwards order), or first_index_offset (for reversed order) as the
offset_index.
Because counting all payments in the payment database can take a long
time on systems with many payments, the count is not returned by
default. That feature can be turned on with the --count_total_payments
flag.
`,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "include_incomplete",
Usage: "if set to true, payments still in flight (or " +
"failed) will be returned as well, keeping" +
"indices for payments the same as without " +
"the flag",
},
cli.UintFlag{
Name: "index_offset",
Usage: "The index of a payment that will be used as " +
"either the start (in forwards mode) or end " +
"(in reverse mode) of a query to determine " +
"which payments should be returned in the " +
"response, where the index_offset is " +
"excluded. If index_offset is set to zero in " +
"reversed mode, the query will end with the " +
"last payment made.",
},
cli.UintFlag{
Name: "max_payments",
Usage: "the max number of payments to return, by " +
"default, all completed payments are returned",
},
cli.BoolFlag{
Name: "paginate_forwards",
Usage: "if set, payments succeeding the " +
"index_offset will be returned, allowing " +
"forwards pagination",
},
cli.BoolFlag{
Name: "count_total_payments",
Usage: "if set, all payments (complete or incomplete, " +
"independent of max_payments parameter) will " +
"be counted; can take a long time on systems " +
"with many payments",
},
cli.Uint64Flag{
Name: "creation_date_start",
Usage: "timestamp in seconds, if set, filter " +
"payments with creation date greater than or " +
"equal to it",
},
cli.Uint64Flag{
Name: "creation_date_end",
Usage: "timestamp in seconds, if set, filter " +
"payments with creation date less than or " +
"equal to it",
},
},
Action: actionDecorator(listPayments),
}
func listPayments(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
req := &lnrpc.ListPaymentsRequest{
IncludeIncomplete: ctx.Bool("include_incomplete"),
IndexOffset: uint64(ctx.Uint("index_offset")),
MaxPayments: uint64(ctx.Uint("max_payments")),
Reversed: !ctx.Bool("paginate_forwards"),
CountTotalPayments: ctx.Bool("count_total_payments"),
CreationDateStart: ctx.Uint64("creation_date_start"),
CreationDateEnd: ctx.Uint64("creation_date_end"),
}
payments, err := client.ListPayments(ctxc, req)
if err != nil {
return err
}
printRespJSON(payments)
return nil
}
var forwardingHistoryCommand = cli.Command{
Name: "fwdinghistory",
Category: "Payments",
Usage: "Query the history of all forwarded HTLCs.",
ArgsUsage: "start_time [end_time] [index_offset] [max_events]",
Description: `
Query the HTLC switch's internal forwarding log for all completed
payment circuits (HTLCs) over a particular time range (--start_time and
--end_time). The start and end times are meant to be expressed in
seconds since the Unix epoch.
Alternatively negative time ranges can be used, e.g. "-3d". Supports
s(seconds), m(minutes), h(ours), d(ays), w(eeks), M(onths), y(ears).
Month equals 30.44 days, year equals 365.25 days.
If --start_time isn't provided, then 24 hours ago is used. If
--end_time isn't provided, then the current time is used.
The max number of events returned is 50k. The default number is 100,
callers can use the --max_events param to modify this value.
Finally, callers can skip a series of events using the --index_offset
parameter. Each response will contain the offset index of the last
entry. Using this callers can manually paginate within a time slice.
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "start_time",
Usage: "the starting time for the query " +
`as unix timestamp or relative e.g. "-1w"`,
},
cli.StringFlag{
Name: "end_time",
Usage: "the end time for the query " +
`as unix timestamp or relative e.g. "-1w"`,
},
cli.Int64Flag{
Name: "index_offset",
Usage: "the number of events to skip",
},
cli.Int64Flag{
Name: "max_events",
Usage: "the max number of events to return",
},
cli.BoolFlag{
Name: "skip_peer_alias_lookup",
Usage: "skip the peer alias lookup per forwarding " +
"event in order to improve performance",
},
},
Action: actionDecorator(forwardingHistory),
}
func forwardingHistory(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
var (
startTime, endTime uint64
indexOffset, maxEvents uint32
err error
)
args := ctx.Args()
now := time.Now()
switch {
case ctx.IsSet("start_time"):
startTime, err = parseTime(ctx.String("start_time"), now)
case args.Present():
startTime, err = parseTime(args.First(), now)
args = args.Tail()
default:
now := time.Now()
startTime = uint64(now.Add(-time.Hour * 24).Unix())
}
if err != nil {
return fmt.Errorf("unable to decode start_time: %w", err)
}
switch {
case ctx.IsSet("end_time"):
endTime, err = parseTime(ctx.String("end_time"), now)
case args.Present():
endTime, err = parseTime(args.First(), now)
args = args.Tail()
default:
endTime = uint64(now.Unix())
}
if err != nil {
return fmt.Errorf("unable to decode end_time: %w", err)
}
switch {
case ctx.IsSet("index_offset"):
indexOffset = uint32(ctx.Int64("index_offset"))
case args.Present():
i, err := strconv.ParseInt(args.First(), 10, 64)
if err != nil {
return fmt.Errorf("unable to decode index_offset: %w",
err)
}
indexOffset = uint32(i)
args = args.Tail()
}
switch {
case ctx.IsSet("max_events"):
maxEvents = uint32(ctx.Int64("max_events"))
case args.Present():
m, err := strconv.ParseInt(args.First(), 10, 64)
if err != nil {
return fmt.Errorf("unable to decode max_events: %w",
err)
}
maxEvents = uint32(m)
}
// By default we will look up the peers' alias information unless the
// skip_peer_alias_lookup flag is specified.
lookupPeerAlias := !ctx.Bool("skip_peer_alias_lookup")
req := &lnrpc.ForwardingHistoryRequest{
StartTime: startTime,
EndTime: endTime,
IndexOffset: indexOffset,
NumMaxEvents: maxEvents,
PeerAliasLookup: lookupPeerAlias,
}
resp, err := client.ForwardingHistory(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var buildRouteCommand = cli.Command{
Name: "buildroute",
Category: "Payments",
Usage: "Build a route from a list of hop pubkeys.",
Description: `
Builds a sphinx route for the supplied hops (public keys). Make sure to
use a custom final_cltv_delta to create the route depending on the
restrictions in the invoice otherwise LND will use its default specified
via the bitcoin.timelockdelta setting (default 80).
If the final_cltv_delta mismatch you will likely see the error
INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS returned by the receiving node.
Moreover a payment_addr has to be provided if the invoice supplied it as
well otherwise the payment will be rejected by the receiving node.
`,
Action: actionDecorator(buildRoute),
Flags: []cli.Flag{
cli.Int64Flag{
Name: "amt",
Usage: "the amount to send expressed in satoshis. If" +
"not set, the minimum routable amount is used",
},
cli.Int64Flag{
Name: "final_cltv_delta",
Usage: "number of blocks the last hop has to reveal " +
"the preimage; if not set the default lnd " +
"final_cltv_delta is used",
},
cli.StringFlag{
Name: "hops",
Usage: "comma separated hex pubkeys",
},
cli.Uint64Flag{
Name: "outgoing_chan_id",
Usage: "short channel id of the outgoing channel to " +
"use for the first hop of the payment",
Value: 0,
},
cli.StringFlag{
Name: "payment_addr",
Usage: "hex encoded payment address to set in the " +
"last hop's mpp record",
},
},
}
func buildRoute(ctx *cli.Context) error {
ctxc := getContext()
conn := getClientConn(ctx, false)
defer conn.Close()
client := routerrpc.NewRouterClient(conn)
if !ctx.IsSet("hops") {
return errors.New("hops required")
}
// Build list of hop addresses for the rpc.
hops := strings.Split(ctx.String("hops"), ",")
rpcHops := make([][]byte, 0, len(hops))
for _, k := range hops {
pubkey, err := route.NewVertexFromStr(k)
if err != nil {
return fmt.Errorf("error parsing %v: %w", k, err)
}
rpcHops = append(rpcHops, pubkey[:])
}
var amtMsat int64
hasAmt := ctx.IsSet("amt")
if hasAmt {
amtMsat = ctx.Int64("amt") * 1000
if amtMsat == 0 {
return fmt.Errorf("non-zero amount required")
}
}
var (
payAddr []byte
err error
)
if ctx.IsSet("payment_addr") {
payAddr, err = hex.DecodeString(ctx.String("payment_addr"))
if err != nil {
return fmt.Errorf("error parsing payment_addr: %w", err)
}
}
// Call BuildRoute rpc.
req := &routerrpc.BuildRouteRequest{
AmtMsat: amtMsat,
FinalCltvDelta: int32(ctx.Int64("final_cltv_delta")),
HopPubkeys: rpcHops,
OutgoingChanId: ctx.Uint64("outgoing_chan_id"),
PaymentAddr: payAddr,
}
route, err := client.BuildRoute(ctxc, req)
if err != nil {
return err
}
printRespJSON(route)
return nil
}
var deletePaymentsCommand = cli.Command{
Name: "deletepayments",
Category: "Payments",
Usage: "Delete a single or multiple payments from the database.",
ArgsUsage: "--all [--failed_htlcs_only --include_non_failed] | " +
"--payment_hash hash [--failed_htlcs_only]",
Description: `
This command either deletes all failed payments or a single payment from
the database to reclaim disk space.
If the --all flag is used, then all failed payments are removed. If so
desired, _ALL_ payments (even the successful ones) can be deleted
by additionally specifying --include_non_failed.
If a --payment_hash is specified, that single payment is deleted,
independent of its state.
If --failed_htlcs_only is specified then the payments themselves (or the
single payment itself if used with --payment_hash) is not deleted, only
the information about any failed HTLC attempts during the payment.
NOTE: Removing payments from the database does free up disk space within
the internal bbolt database. But that disk space is only reclaimed after
compacting the database. Users might want to turn on auto compaction
(db.bolt.auto-compact=true in the config file or --db.bolt.auto-compact
as a command line flag) and restart lnd after deleting a large number of
payments to see a reduction in the file size of the channel.db file.
`,
Action: actionDecorator(deletePayments),
Flags: []cli.Flag{
cli.BoolFlag{
Name: "all",
Usage: "delete all failed payments",
},
cli.StringFlag{
Name: "payment_hash",
Usage: "delete a specific payment identified by its " +
"payment hash",
},
cli.BoolFlag{
Name: "failed_htlcs_only",
Usage: "only delete failed HTLCs from payments, not " +
"the payment itself",
},
cli.BoolFlag{
Name: "include_non_failed",
Usage: "delete ALL payments, not just the failed ones",
},
},
}
func deletePayments(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
// Show command help if arguments or no flags are provided.
if ctx.NArg() > 0 || ctx.NumFlags() == 0 {
_ = cli.ShowCommandHelp(ctx, "deletepayments")
return nil
}
var (
paymentHash []byte
all = ctx.Bool("all")
singlePayment = ctx.IsSet("payment_hash")
failedHTLCsOnly = ctx.Bool("failed_htlcs_only")
includeNonFailed = ctx.Bool("include_non_failed")
err error
resp proto.Message
)
// We pack two RPCs into the same CLI so there are a few non-valid
// combinations of the flags we need to filter out.
switch {
case all && singlePayment:
return fmt.Errorf("cannot use --all and --payment_hash at " +
"the same time")
case singlePayment && includeNonFailed:
return fmt.Errorf("cannot use --payment_hash and " +
"--include_non_failed at the same time, when using " +
"a payment hash the payment is deleted independent " +
"of its state")
}
// Deleting a single payment is implemented in a different RPC than
// removing all/multiple payments.
switch {
case singlePayment:
paymentHash, err = hex.DecodeString(ctx.String("payment_hash"))
if err != nil {
return fmt.Errorf("error decoding payment_hash: %w",
err)
}
resp, err = client.DeletePayment(
ctxc, &lnrpc.DeletePaymentRequest{
PaymentHash: paymentHash,
FailedHtlcsOnly: failedHTLCsOnly,
},
)
if err != nil {
return fmt.Errorf("error deleting single payment: %w",
err)
}
case all:
what := "failed"
if includeNonFailed {
what = "all"
}
if failedHTLCsOnly {
what = fmt.Sprintf("failed HTLCs from %s", what)
}
fmt.Printf("Removing %s payments, this might take a while...\n",
what)
resp, err = client.DeleteAllPayments(
ctxc, &lnrpc.DeleteAllPaymentsRequest{
AllPayments: includeNonFailed,
FailedPaymentsOnly: !includeNonFailed,
FailedHtlcsOnly: failedHTLCsOnly,
},
)
if err != nil {
return fmt.Errorf("error deleting payments: %w", err)
}
}
printJSON(resp)
return nil
}
var estimateRouteFeeCommand = cli.Command{
Name: "estimateroutefee",
Category: "Payments",
Usage: "Estimate routing fees based on a destination or an invoice.",
Action: actionDecorator(estimateRouteFee),
Flags: []cli.Flag{
cli.StringFlag{
Name: "dest",
Usage: "the 33-byte hex-encoded public key for the " +
"probe destination. If it is specified then " +
"the amt flag is required. If it isn't " +
"specified then the pay_req field has to.",
},
cli.Int64Flag{
Name: "amt",
Usage: "the payment amount expressed in satoshis " +
"that should be probed for. This field is " +
"mandatory if dest is specified.",
},
cli.StringFlag{
Name: "pay_req",
Usage: "a zpay32 encoded payment request which is " +
"used to probe. If the destination is " +
"not public then route hints are scanned for " +
"a public node.",
},
cli.DurationFlag{
Name: "timeout",
Usage: "a deadline for the probe attempt. Only " +
"applicable if pay_req is specified.",
Value: paymentTimeout,
},
},
}
func estimateRouteFee(ctx *cli.Context) error {
ctxc := getContext()
conn := getClientConn(ctx, false)
defer conn.Close()
client := routerrpc.NewRouterClient(conn)
req := &routerrpc.RouteFeeRequest{}
switch {
case ctx.IsSet("dest") && ctx.IsSet("pay_req"):
return fmt.Errorf("either dest or pay_req can be set")
case ctx.IsSet("dest") && !ctx.IsSet("amt"):
return fmt.Errorf("amt is required when dest is set")
case ctx.IsSet("dest"):
dest, err := hex.DecodeString(ctx.String("dest"))
if err != nil {
return err
}
if len(dest) != 33 {
return fmt.Errorf("dest node pubkey must be exactly "+
"33 bytes, is instead: %v", len(dest))
}
amtSat := ctx.Int64("amt")
if amtSat == 0 {
return fmt.Errorf("non-zero amount required")
}
req.Dest = dest
req.AmtSat = amtSat
case ctx.IsSet("pay_req"):
req.PaymentRequest = StripPrefix(ctx.String("pay_req"))
req.Timeout = uint32(ctx.Duration("timeout").Seconds())
default:
return fmt.Errorf("fee estimation arguments missing")
}
resp, err := client.EstimateRouteFee(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
// ESC is the ASCII code for escape character.
const ESC = 27
// clearCode defines a terminal escape code to clear the current line and move
// the cursor up.
var clearCode = fmt.Sprintf("%c[%dA%c[2K", ESC, 1, ESC)
// clearLines erases the last count lines in the terminal window.
func clearLines(count int) {
_, _ = fmt.Print(strings.Repeat(clearCode, count))
}
// ordinalNumber returns the ordinal number as a string of a number.
func ordinalNumber(num uint32) string {
switch num {
case 1:
return "1st"
case 2:
return "2nd"
case 3:
return "3rd"
default:
return fmt.Sprintf("%dth", num)
}
}
package commands
import (
"fmt"
"os"
"path"
"strings"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/urfave/cli"
"gopkg.in/macaroon.v2"
)
var (
// defaultLncliDir is the default directory to store the profile file
// in. This defaults to:
// C:\Users\<username>\AppData\Local\Lncli\ on Windows
// ~/.lncli/ on Linux
// ~/Library/Application Support/Lncli/ on MacOS
defaultLncliDir = btcutil.AppDataDir("lncli", false)
// defaultProfileFile is the full, absolute path of the profile file.
defaultProfileFile = path.Join(defaultLncliDir, "profiles.json")
)
var profileSubCommand = cli.Command{
Name: "profile",
Category: "Profiles",
Usage: "Create and manage lncli profiles.",
Description: `
Profiles for lncli are an easy and comfortable way to manage multiple
nodes from the command line by storing node specific parameters like RPC
host, network, TLS certificate path or macaroons in a named profile.
To use a predefined profile, just use the '--profile=myprofile' (or
short version '-p=myprofile') with any lncli command.
A default profile can also be defined, lncli will then always use the
connection/node parameters from that profile instead of the default
values.
WARNING: Setting a default profile changes the default behavior of
lncli! To disable the use of the default profile for a single command,
set '--profile= '.
The profiles are stored in a file called profiles.json in the user's
home directory, for example:
C:\Users\<username>\AppData\Local\Lncli\profiles.json on Windows
~/.lncli/profiles.json on Linux
~/Library/Application Support/Lncli/profiles.json on MacOS
`,
Subcommands: []cli.Command{
profileListCommand,
profileAddCommand,
profileRemoveCommand,
profileSetDefaultCommand,
profileUnsetDefaultCommand,
profileAddMacaroonCommand,
},
}
var profileListCommand = cli.Command{
Name: "list",
Usage: "Lists all lncli profiles",
Action: profileList,
}
func profileList(_ *cli.Context) error {
f, err := loadProfileFile(defaultProfileFile)
if err != nil {
return err
}
printJSON(f)
return nil
}
var profileAddCommand = cli.Command{
Name: "add",
Usage: "Add a new profile.",
ArgsUsage: "name",
Description: `
Add a new named profile to the main profiles.json. All global options
(see 'lncli --help') passed into this command are stored in that named
profile.
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "name",
Usage: "the name of the new profile",
},
cli.BoolFlag{
Name: "default",
Usage: "set the new profile to be the default profile",
},
},
Action: profileAdd,
}
func profileAdd(ctx *cli.Context) error {
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
return cli.ShowCommandHelp(ctx, "add")
}
// Load the default profile file or create a new one if it doesn't exist
// yet.
f, err := loadProfileFile(defaultProfileFile)
switch {
case err == errNoProfileFile:
f = &profileFile{}
_ = os.MkdirAll(path.Dir(defaultProfileFile), 0700)
case err != nil:
return err
}
// Create a profile struct from all the global options.
profile, err := profileFromContext(ctx, true, false)
if err != nil {
return fmt.Errorf("could not load global options: %w", err)
}
// Finally, all that's left is to get the profile name from either
// positional argument or flag.
args := ctx.Args()
switch {
case ctx.IsSet("name"):
profile.Name = ctx.String("name")
case args.Present():
profile.Name = args.First()
default:
return fmt.Errorf("name argument missing")
}
// Is there already a profile with that name?
for _, p := range f.Profiles {
if p.Name == profile.Name {
return fmt.Errorf("a profile with the name %s already "+
"exists", profile.Name)
}
}
// Do we need to update the default entry to be this one?
if ctx.Bool("default") {
f.Default = profile.Name
}
// All done, store the updated profile file.
f.Profiles = append(f.Profiles, profile)
if err = saveProfileFile(defaultProfileFile, f); err != nil {
return fmt.Errorf("error writing profile file %s: %w",
defaultProfileFile, err)
}
fmt.Printf("Profile %s added to file %s.\n", profile.Name,
defaultProfileFile)
return nil
}
var profileRemoveCommand = cli.Command{
Name: "remove",
Usage: "Remove a profile",
ArgsUsage: "name",
Description: `Remove the specified profile from the profile file.`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "name",
Usage: "the name of the profile to delete",
},
},
Action: profileRemove,
}
func profileRemove(ctx *cli.Context) error {
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
return cli.ShowCommandHelp(ctx, "remove")
}
// Load the default profile file.
f, err := loadProfileFile(defaultProfileFile)
if err != nil {
return fmt.Errorf("could not load profile file: %w", err)
}
// Get the profile name from either positional argument or flag.
var (
args = ctx.Args()
name string
found = false
)
switch {
case ctx.IsSet("name"):
name = ctx.String("name")
case args.Present():
name = args.First()
default:
return fmt.Errorf("name argument missing")
}
if len(f.Profiles) == 0 {
return fmt.Errorf("there are no existing profiles")
}
// Create a copy of all profiles but don't include the one to delete.
newProfiles := make([]*profileEntry, 0, len(f.Profiles)-1)
for _, p := range f.Profiles {
// Skip the one we want to delete.
if p.Name == name {
found = true
if p.Name == f.Default {
fmt.Println("Warning: removing default profile.")
}
continue
}
// Keep all others.
newProfiles = append(newProfiles, p)
}
// If what we were looking for didn't exist in the first place, there's
// no need for updating the file.
if !found {
return fmt.Errorf("profile with name %s not found in file",
name)
}
// Great, everything updated, now let's save the file.
f.Profiles = newProfiles
return saveProfileFile(defaultProfileFile, f)
}
var profileSetDefaultCommand = cli.Command{
Name: "setdefault",
Usage: "Set the default profile.",
ArgsUsage: "name",
Description: `
Set a specified profile to be used as the default profile.
WARNING: Setting a default profile changes the default behavior of
lncli! To disable the use of the default profile for a single command,
set '--profile= '.
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "name",
Usage: "the name of the profile to set as default",
},
},
Action: profileSetDefault,
}
func profileSetDefault(ctx *cli.Context) error {
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
return cli.ShowCommandHelp(ctx, "setdefault")
}
// Load the default profile file.
f, err := loadProfileFile(defaultProfileFile)
if err != nil {
return fmt.Errorf("could not load profile file: %w", err)
}
// Get the profile name from either positional argument or flag.
var (
args = ctx.Args()
name string
found = false
)
switch {
case ctx.IsSet("name"):
name = ctx.String("name")
case args.Present():
name = args.First()
default:
return fmt.Errorf("name argument missing")
}
// Make sure the new default profile actually exists.
for _, p := range f.Profiles {
if p.Name == name {
found = true
f.Default = p.Name
break
}
}
// If the default profile doesn't exist, there's no need for updating
// the file.
if !found {
return fmt.Errorf("profile with name %s not found in file",
name)
}
// Great, everything updated, now let's save the file.
return saveProfileFile(defaultProfileFile, f)
}
var profileUnsetDefaultCommand = cli.Command{
Name: "unsetdefault",
Usage: "Unsets the default profile.",
Description: `
Disables the use of a default profile and restores lncli to its original
behavior.
`,
Action: profileUnsetDefault,
}
func profileUnsetDefault(_ *cli.Context) error {
// Load the default profile file.
f, err := loadProfileFile(defaultProfileFile)
if err != nil {
return fmt.Errorf("could not load profile file: %w", err)
}
// Save the file with the flag disabled.
f.Default = ""
return saveProfileFile(defaultProfileFile, f)
}
var profileAddMacaroonCommand = cli.Command{
Name: "addmacaroon",
Usage: "Add a macaroon to a profile's macaroon jar.",
ArgsUsage: "macaroon-name",
Description: `
Add an additional macaroon specified by the global option --macaroonpath
to an existing profile's macaroon jar.
If no profile is selected, the macaroon is added to the default profile
(if one exists). To add a macaroon to a specific profile, use the global
--profile=myprofile option.
If multiple macaroons exist in a profile's macaroon jar, the one to use
can be specified with the global option --macfromjar=xyz.
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "name",
Usage: "the name of the macaroon",
},
cli.BoolFlag{
Name: "default",
Usage: "set the new macaroon to be the default " +
"macaroon in the jar",
},
},
Action: profileAddMacaroon,
}
func profileAddMacaroon(ctx *cli.Context) error {
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
return cli.ShowCommandHelp(ctx, "addmacaroon")
}
// Load the default profile file or create a new one if it doesn't exist
// yet.
f, err := loadProfileFile(defaultProfileFile)
if err != nil {
return fmt.Errorf("could not load profile file: %w", err)
}
// Finally, all that's left is to get the profile name from either
// positional argument or flag.
var (
args = ctx.Args()
profileName string
macName string
)
switch {
case ctx.IsSet("name"):
macName = ctx.String("name")
case args.Present():
macName = args.First()
default:
return fmt.Errorf("name argument missing")
}
// Make sure the user actually set a macaroon path to use.
if !ctx.GlobalIsSet("macaroonpath") {
return fmt.Errorf("macaroonpath global option missing")
}
// Find out which profile we should add the macaroon. The global flag
// takes precedence over the default profile.
if f.Default != "" {
profileName = f.Default
}
if ctx.GlobalIsSet("profile") {
profileName = ctx.GlobalString("profile")
}
if len(strings.TrimSpace(profileName)) == 0 {
return fmt.Errorf("no profile specified and no default " +
"profile exists")
}
// Is there a profile with that name?
var selectedProfile *profileEntry
for _, p := range f.Profiles {
if p.Name == profileName {
selectedProfile = p
break
}
}
if selectedProfile == nil {
return fmt.Errorf("profile with name %s not found", profileName)
}
// Does a macaroon with that name already exist?
for _, m := range selectedProfile.Macaroons.Jar {
if m.Name == macName {
return fmt.Errorf("a macaroon with the name %s "+
"already exists", macName)
}
}
// Do we need to update the default entry to be this one?
if ctx.Bool("default") {
selectedProfile.Macaroons.Default = macName
}
// Now load and possibly encrypt the macaroon file.
macPath := lncfg.CleanAndExpandPath(ctx.GlobalString("macaroonpath"))
macBytes, err := os.ReadFile(macPath)
if err != nil {
return fmt.Errorf("unable to read macaroon path: %w", err)
}
mac := &macaroon.Macaroon{}
if err = mac.UnmarshalBinary(macBytes); err != nil {
return fmt.Errorf("unable to decode macaroon: %w", err)
}
macEntry := &macaroonEntry{
Name: macName,
}
if err = macEntry.storeMacaroon(mac, nil); err != nil {
return fmt.Errorf("unable to store macaroon: %w", err)
}
// All done, store the updated profile file.
selectedProfile.Macaroons.Jar = append(
selectedProfile.Macaroons.Jar, macEntry,
)
if err = saveProfileFile(defaultProfileFile, f); err != nil {
return fmt.Errorf("error writing profile file %s: %w",
defaultProfileFile, err)
}
fmt.Printf("Macaroon %s added to profile %s in file %s.\n", macName,
selectedProfile.Name, defaultProfileFile)
return nil
}
package commands
import (
"context"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/urfave/cli"
)
var getStateCommand = cli.Command{
Name: "state",
Category: "Startup",
Usage: "Get the current state of the wallet and RPC",
Description: `
Get the current state of the wallet. The possible states are:
- WAITING_TO_START: node is waiting to become the leader in a cluster
and is not started yet.
- NON_EXISTING: wallet has not yet been initialized.
- LOCKED: wallet is locked.
- UNLOCKED: wallet was unlocked successfully, but RPC server isn't ready.
- RPC_ACTIVE: RPC server is active but not fully ready for calls.
- SERVER_ACTIVE: RPC server is available and ready to accept calls.
`,
Flags: []cli.Flag{},
Action: actionDecorator(getState),
}
func getState(ctx *cli.Context) error {
ctxb := context.Background()
client, cleanUp := getStateServiceClient(ctx)
defer cleanUp()
req := &lnrpc.SubscribeStateRequest{}
stream, err := client.SubscribeState(ctxb, req)
if err != nil {
return err
}
// Get a single state, then exit.
resp, err := stream.Recv()
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
package commands
import (
"errors"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/urfave/cli"
)
var updateChanStatusCommand = cli.Command{
Name: "updatechanstatus",
Category: "Channels",
Usage: "Set the status of an existing channel on the network.",
Description: `
Set the status of an existing channel on the network. The actions can
be "enable", "disable", or "auto". If the action changes the status, a
message will be broadcast over the network.
Note that enabling / disabling a channel using this command ONLY affects
what's advertised over the network. For example, disabling a channel
using this command does not close it.
If a channel is manually disabled, automatic / background requests to
re-enable the channel will be ignored. However, if a channel is
manually enabled, automatic / background requests to disable the
channel will succeed (such requests are usually made on channel close
or when the peer is down).
The "auto" action restores automatic channel state management. Per
the behavior described above, it's only needed to undo the effect of
a prior "disable" action, and will be a no-op otherwise.`,
ArgsUsage: "funding_txid [output_index] action",
Flags: []cli.Flag{
cli.StringFlag{
Name: "funding_txid",
Usage: "the txid of the channel's funding transaction",
},
cli.IntFlag{
Name: "output_index",
Usage: "the output index for the funding output of " +
"the funding transaction",
},
cli.StringFlag{
Name: "chan_point",
Usage: "the channel whose status should be updated. " +
"Takes the form of: txid:output_index",
},
cli.StringFlag{
Name: "action",
Usage: `the action to take: must be one of "enable", ` +
`"disable", or "auto"`,
},
},
Action: actionDecorator(updateChanStatus),
}
func updateChanStatus(ctx *cli.Context) error {
ctxc := getContext()
conn := getClientConn(ctx, false)
defer conn.Close()
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
_ = cli.ShowCommandHelp(ctx, "updatechanstatus")
return nil
}
channelPoint, err := parseChannelPoint(ctx)
if err != nil {
return err
}
var action routerrpc.ChanStatusAction
switch ctx.String("action") {
case "enable":
action = routerrpc.ChanStatusAction_ENABLE
case "disable":
action = routerrpc.ChanStatusAction_DISABLE
case "auto":
action = routerrpc.ChanStatusAction_AUTO
default:
return errors.New(`action must be one of "enable", "disable", ` +
`or "auto"`)
}
req := &routerrpc.UpdateChanStatusRequest{
ChanPoint: channelPoint,
Action: action,
}
client := routerrpc.NewRouterClient(conn)
resp, err := client.UpdateChanStatus(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
package commands
import (
"fmt"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/lnrpc/lnclipb"
"github.com/lightningnetwork/lnd/lnrpc/verrpc"
"github.com/urfave/cli"
)
var versionCommand = cli.Command{
Name: "version",
Usage: "Display lncli and lnd version info.",
Description: `
Returns version information about both lncli and lnd. If lncli is unable
to connect to lnd, the command fails but still prints the lncli version.
`,
Action: actionDecorator(version),
}
func version(ctx *cli.Context) error {
ctxc := getContext()
conn := getClientConn(ctx, false)
defer conn.Close()
versions := &lnclipb.VersionResponse{
Lncli: &verrpc.Version{
Commit: build.Commit,
CommitHash: build.CommitHash,
Version: build.Version(),
AppMajor: uint32(build.AppMajor),
AppMinor: uint32(build.AppMinor),
AppPatch: uint32(build.AppPatch),
AppPreRelease: build.AppPreRelease,
BuildTags: build.Tags(),
GoVersion: build.GoVersion,
},
}
client := verrpc.NewVersionerClient(conn)
lndVersion, err := client.GetVersion(ctxc, &verrpc.VersionRequest{})
if err != nil {
printRespJSON(versions)
return fmt.Errorf("unable fetch version from lnd: %w", err)
}
versions.Lnd = lndVersion
printRespJSON(versions)
return nil
}
package commands
import (
"bufio"
"bytes"
"encoding/hex"
"fmt"
"os"
"strconv"
"strings"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/lightningnetwork/lnd/walletunlocker"
"github.com/urfave/cli"
)
var (
statelessInitFlag = cli.BoolFlag{
Name: "stateless_init",
Usage: "do not create any macaroon files in the file " +
"system of the daemon",
}
saveToFlag = cli.StringFlag{
Name: "save_to",
Usage: "save returned admin macaroon to this file",
}
macRootKeyFlag = cli.StringFlag{
Name: "mac_root_key",
Usage: "macaroon root key to use when initializing the " +
"macaroon store; allows for deterministic macaroon " +
"generation; if not set, a random one will be " +
"created",
}
)
var createCommand = cli.Command{
Name: "create",
Category: "Startup",
Usage: "Initialize a wallet when starting lnd for the first time.",
Description: `
The create command is used to initialize an lnd wallet from scratch for
the very first time. This is an interactive command with one required
input (the password), and one optional input (the mnemonic passphrase).
The first input (the password) is required and MUST be greater than 8
characters. This will be used to encrypt the wallet within lnd. This
MUST be remembered as it will be required to fully start up the daemon.
The second input is an optional 24-word mnemonic derived from BIP 39.
If provided, then the internal wallet will use the seed derived from
this mnemonic to generate all keys.
This command returns a 24-word seed in the scenario that NO mnemonic
was provided by the user. This should be written down as it can be used
to potentially recover all on-chain funds, and most off-chain funds as
well.
If the --stateless_init flag is set, no macaroon files are created by
the daemon. Instead, the binary serialized admin macaroon is returned
in the answer. This answer MUST be stored somewhere, otherwise all
access to the RPC server will be lost and the wallet must be recreated
to re-gain access.
If the --save_to parameter is set, the macaroon is saved to this file,
otherwise it is printed to standard out.
Finally, it's also possible to use this command and a set of static
channel backups to trigger a recover attempt for the provided Static
Channel Backups. Only one of the three parameters will be accepted. See
the restorechanbackup command for further details w.r.t the format
accepted.
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "single_backup",
Usage: "a hex encoded single channel backup obtained " +
"from exportchanbackup",
},
cli.StringFlag{
Name: "multi_backup",
Usage: "a hex encoded multi-channel backup obtained " +
"from exportchanbackup",
},
cli.StringFlag{
Name: "multi_file",
Usage: "the path to a multi-channel back up file",
},
statelessInitFlag,
saveToFlag,
macRootKeyFlag,
},
Action: actionDecorator(create),
}
// monowidthColumns takes a set of words, and the number of desired columns,
// and returns a new set of words that have had white space appended to the
// word in order to create a mono-width column.
func monowidthColumns(words []string, ncols int) []string {
// Determine max size of words in each column.
colWidths := make([]int, ncols)
for i, word := range words {
col := i % ncols
curWidth := colWidths[col]
if len(word) > curWidth {
colWidths[col] = len(word)
}
}
// Append whitespace to each word to make columns mono-width.
finalWords := make([]string, len(words))
for i, word := range words {
col := i % ncols
width := colWidths[col]
diff := width - len(word)
finalWords[i] = word + strings.Repeat(" ", diff)
}
return finalWords
}
func create(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getWalletUnlockerClient(ctx)
defer cleanUp()
var (
chanBackups *lnrpc.ChanBackupSnapshot
// We use var restoreSCB to track if we will be including an SCB
// recovery in the init wallet request.
restoreSCB = false
)
backups, err := parseChanBackups(ctx)
// We'll check to see if the user provided any static channel backups (SCB),
// if so, we will warn the user that SCB recovery closes all open channels
// and ask them to confirm their intention.
// If the user agrees, we'll add the SCB recovery onto the final init wallet
// request.
switch {
// parseChanBackups returns an errMissingBackup error (which we ignore) if
// the user did not request a SCB recovery.
case err == errMissingChanBackup:
// Passed an invalid channel backup file.
case err != nil:
return fmt.Errorf("unable to parse chan backups: %w", err)
// We have an SCB recovery option with a valid backup file.
default:
warningLoop:
for {
fmt.Println()
fmt.Printf("WARNING: You are attempting to restore from a " +
"static channel backup (SCB) file.\nThis action will CLOSE " +
"all currently open channels, and you will pay on-chain fees." +
"\n\nAre you sure you want to recover funds from a" +
" static channel backup? (Enter y/n): ")
reader := bufio.NewReader(os.Stdin)
answer, err := reader.ReadString('\n')
if err != nil {
return err
}
answer = strings.TrimSpace(answer)
answer = strings.ToLower(answer)
switch answer {
case "y":
restoreSCB = true
break warningLoop
case "n":
fmt.Println("Aborting SCB recovery")
return nil
}
}
}
// Proceed with SCB recovery.
if restoreSCB {
fmt.Println("Static Channel Backup (SCB) recovery selected!")
if backups != nil {
switch {
case backups.GetChanBackups() != nil:
singleBackup := backups.GetChanBackups()
chanBackups = &lnrpc.ChanBackupSnapshot{
SingleChanBackups: singleBackup,
}
case backups.GetMultiChanBackup() != nil:
multiBackup := backups.GetMultiChanBackup()
chanBackups = &lnrpc.ChanBackupSnapshot{
MultiChanBackup: &lnrpc.MultiChanBackup{
MultiChanBackup: multiBackup,
},
}
}
}
}
// Should the daemon be initialized stateless? Then we expect an answer
// with the admin macaroon later. Because the --save_to is related to
// stateless init, it doesn't make sense to be set on its own.
statelessInit := ctx.Bool(statelessInitFlag.Name)
if !statelessInit && ctx.IsSet(saveToFlag.Name) {
return fmt.Errorf("cannot set save_to parameter without " +
"stateless_init")
}
walletPassword, err := capturePassword(
"Input wallet password: ", false, walletunlocker.ValidatePassword,
)
if err != nil {
return err
}
// Next, we'll see if the user has 24-word mnemonic they want to use to
// derive a seed within the wallet or if they want to specify an
// extended master root key (xprv) directly.
var (
hasMnemonic bool
hasXprv bool
)
mnemonicCheck:
for {
fmt.Println()
fmt.Printf("Do you have an existing cipher seed " +
"mnemonic or extended master root key you want to " +
"use?\nEnter 'y' to use an existing cipher seed " +
"mnemonic, 'x' to use an extended master root key " +
"\nor 'n' to create a new seed (Enter y/x/n): ")
reader := bufio.NewReader(os.Stdin)
answer, err := reader.ReadString('\n')
if err != nil {
return err
}
fmt.Println()
answer = strings.TrimSpace(answer)
answer = strings.ToLower(answer)
switch answer {
case "y":
hasMnemonic = true
break mnemonicCheck
case "x":
hasXprv = true
break mnemonicCheck
case "n":
break mnemonicCheck
}
}
// If the user *does* have an existing seed or root key they want to
// use, then we'll read that in directly from the terminal.
var (
cipherSeedMnemonic []string
aezeedPass []byte
extendedRootKey string
extendedRootKeyBirthday uint64
recoveryWindow int32
macRootKey []byte
)
switch {
// Use an existing cipher seed mnemonic in the aezeed format.
case hasMnemonic:
// We'll now prompt the user to enter in their 24-word
// mnemonic.
fmt.Printf("Input your 24-word mnemonic separated by spaces: ")
reader := bufio.NewReader(os.Stdin)
mnemonic, err := reader.ReadString('\n')
if err != nil {
return err
}
// We'll trim off extra spaces, and ensure the mnemonic is all
// lower case, then populate our request.
mnemonic = strings.TrimSpace(mnemonic)
mnemonic = strings.ToLower(mnemonic)
cipherSeedMnemonic = strings.Split(mnemonic, " ")
fmt.Println()
if len(cipherSeedMnemonic) != 24 {
return fmt.Errorf("wrong cipher seed mnemonic "+
"length: got %v words, expecting %v words",
len(cipherSeedMnemonic), 24)
}
// Additionally, the user may have a passphrase, that will also
// need to be provided so the daemon can properly decipher the
// cipher seed.
aezeedPass, err = readPassword("Input your cipher seed " +
"passphrase (press enter if your seed doesn't have a " +
"passphrase): ")
if err != nil {
return err
}
recoveryWindow, err = askRecoveryWindow()
if err != nil {
return err
}
// Use an existing extended master root key to create the wallet.
case hasXprv:
// We'll now prompt the user to enter in their extended master
// root key.
fmt.Printf("Input your extended master root key (usually " +
"starting with xprv... on mainnet): ")
reader := bufio.NewReader(os.Stdin)
extendedRootKey, err = reader.ReadString('\n')
if err != nil {
return err
}
extendedRootKey = strings.TrimSpace(extendedRootKey)
extendedRootKeyBirthday, err = askBirthdayTimestamp()
if err != nil {
return err
}
recoveryWindow, err = askRecoveryWindow()
if err != nil {
return err
}
// Neither a seed nor a master root key was specified, the user wants
// to create a new seed.
default:
// Otherwise, if the user doesn't have a mnemonic that they
// want to use, we'll generate a fresh one with the GenSeed
// command.
fmt.Println("Your cipher seed can optionally be encrypted.")
instruction := "Input your passphrase if you wish to encrypt it " +
"(or press enter to proceed without a cipher seed " +
"passphrase): "
aezeedPass, err = capturePassword(
instruction, true, func(_ []byte) error { return nil },
)
if err != nil {
return err
}
fmt.Println()
fmt.Println("Generating fresh cipher seed...")
fmt.Println()
genSeedReq := &lnrpc.GenSeedRequest{
AezeedPassphrase: aezeedPass,
}
seedResp, err := client.GenSeed(ctxc, genSeedReq)
if err != nil {
return fmt.Errorf("unable to generate seed: %w", err)
}
cipherSeedMnemonic = seedResp.CipherSeedMnemonic
}
// Before we initialize the wallet, we'll display the cipher seed to
// the user so they can write it down.
if len(cipherSeedMnemonic) > 0 {
printCipherSeedWords(cipherSeedMnemonic)
}
// Parse the macaroon root key if it was specified by the user.
if ctx.IsSet(macRootKeyFlag.Name) {
macRootKey, err = hex.DecodeString(
ctx.String(macRootKeyFlag.Name),
)
if err != nil {
return fmt.Errorf("unable to parse macaroon root key: "+
"%w", err)
}
if len(macRootKey) != macaroons.RootKeyLen {
return fmt.Errorf("macaroon root key must be exactly "+
"%v bytes, got %v", macaroons.RootKeyLen,
len(macRootKey))
}
}
// With either the user's prior cipher seed, or a newly generated one,
// we'll go ahead and initialize the wallet.
req := &lnrpc.InitWalletRequest{
WalletPassword: walletPassword,
CipherSeedMnemonic: cipherSeedMnemonic,
AezeedPassphrase: aezeedPass,
ExtendedMasterKey: extendedRootKey,
ExtendedMasterKeyBirthdayTimestamp: extendedRootKeyBirthday,
RecoveryWindow: recoveryWindow,
ChannelBackups: chanBackups,
StatelessInit: statelessInit,
MacaroonRootKey: macRootKey,
}
response, err := client.InitWallet(ctxc, req)
if err != nil {
return err
}
fmt.Println("\nlnd successfully initialized!")
if statelessInit {
return storeOrPrintAdminMac(ctx, response.AdminMacaroon)
}
return nil
}
// capturePassword returns a password value that has been entered twice by the
// user, to ensure that the user knows what password they have entered. The user
// will be prompted to retry until the passwords match. If the optional param is
// true, the function may return an empty byte array if the user opts against
// using a password.
func capturePassword(instruction string, optional bool,
validate func([]byte) error) ([]byte, error) {
for {
password, err := readPassword(instruction)
if err != nil {
return nil, err
}
// Do not require users to repeat password if
// it is optional and they are not using one.
if len(password) == 0 && optional {
return nil, nil
}
// If the password provided is not valid, restart
// password capture process from the beginning.
if err := validate(password); err != nil {
fmt.Println(err.Error())
fmt.Println()
continue
}
passwordConfirmed, err := readPassword("Confirm password: ")
if err != nil {
return nil, err
}
if bytes.Equal(password, passwordConfirmed) {
return password, nil
}
fmt.Println("Passwords don't match, please try again")
fmt.Println()
}
}
var unlockCommand = cli.Command{
Name: "unlock",
Category: "Startup",
Usage: "Unlock an encrypted wallet at startup.",
Description: `
The unlock command is used to decrypt lnd's wallet state in order to
start up. This command MUST be run after booting up lnd before it's
able to carry out its duties. An exception is if a user is running with
--noseedbackup, then a default passphrase will be used.
If the --stateless_init flag is set, no macaroon files are created by
the daemon. This should be set for every unlock if the daemon was
initially initialized stateless. Otherwise the daemon will create
unencrypted macaroon files which could leak information to the system
that the daemon runs on.
`,
Flags: []cli.Flag{
cli.IntFlag{
Name: "recovery_window",
Usage: "address lookahead to resume recovery rescan, " +
"value should be non-zero -- To recover all " +
"funds, this should be greater than the " +
"maximum number of consecutive, unused " +
"addresses ever generated by the wallet.",
},
cli.BoolFlag{
Name: "stdin",
Usage: "read password from standard input instead of " +
"prompting for it. THIS IS CONSIDERED TO " +
"BE DANGEROUS if the password is located in " +
"a file that can be read by another user. " +
"This flag should only be used in " +
"combination with some sort of password " +
"manager or secrets vault.",
},
statelessInitFlag,
},
Action: actionDecorator(unlock),
}
func unlock(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getWalletUnlockerClient(ctx)
defer cleanUp()
var (
pw []byte
err error
)
switch {
// Read the password from standard in as if it were a file. This should
// only be used if the password is piped into lncli from some sort of
// password manager. If the user types the password instead, it will be
// echoed in the console.
case ctx.IsSet("stdin"):
reader := bufio.NewReader(os.Stdin)
pw, err = reader.ReadBytes('\n')
// Remove carriage return and newline characters.
pw = bytes.Trim(pw, "\r\n")
// Read the password from a terminal by default. This requires the
// terminal to be a real tty and will fail if a string is piped into
// lncli.
default:
pw, err = readPassword("Input wallet password: ")
}
if err != nil {
return err
}
args := ctx.Args()
// Parse the optional recovery window if it is specified. By default,
// the recovery window will be 0, indicating no lookahead should be
// used.
var recoveryWindow int32
switch {
case ctx.IsSet("recovery_window"):
recoveryWindow = int32(ctx.Int64("recovery_window"))
case args.Present():
window, err := strconv.ParseInt(args.First(), 10, 64)
if err != nil {
return err
}
recoveryWindow = int32(window)
}
req := &lnrpc.UnlockWalletRequest{
WalletPassword: pw,
RecoveryWindow: recoveryWindow,
StatelessInit: ctx.Bool(statelessInitFlag.Name),
}
_, err = client.UnlockWallet(ctxc, req)
if err != nil {
return err
}
fmt.Println("\nlnd successfully unlocked!")
// TODO(roasbeef): add ability to accept hex single and multi backups
return nil
}
var changePasswordCommand = cli.Command{
Name: "changepassword",
Category: "Startup",
Usage: "Change an encrypted wallet's password at startup.",
Description: `
The changepassword command is used to Change lnd's encrypted wallet's
password. It will automatically unlock the daemon if the password change
is successful.
If one did not specify a password for their wallet (running lnd with
--noseedbackup), one must restart their daemon without
--noseedbackup and use this command. The "current password" field
should be left empty.
If the daemon was originally initialized stateless, then the
--stateless_init flag needs to be set for the change password request
as well! Otherwise the daemon will generate unencrypted macaroon files
in its file system again and possibly leak sensitive information.
Changing the password will by default not change the macaroon root key
(just re-encrypt the macaroon database with the new password). So all
macaroons will still be valid.
If one wants to make sure that all previously created macaroons are
invalidated, a new macaroon root key can be generated by using the
--new_mac_root_key flag.
After a successful password change with the --stateless_init flag set,
the current or new admin macaroon is returned binary serialized in the
answer. This answer MUST then be stored somewhere, otherwise
all access to the RPC server will be lost and the wallet must be re-
created to re-gain access. If the --save_to parameter is set, the
macaroon is saved to this file, otherwise it is printed to standard out.
`,
Flags: []cli.Flag{
statelessInitFlag,
saveToFlag,
cli.BoolFlag{
Name: "new_mac_root_key",
Usage: "rotate the macaroon root key resulting in " +
"all previously created macaroons to be " +
"invalidated",
},
},
Action: actionDecorator(changePassword),
}
func changePassword(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getWalletUnlockerClient(ctx)
defer cleanUp()
currentPw, err := readPassword("Input current wallet password: ")
if err != nil {
return err
}
newPw, err := readPassword("Input new wallet password: ")
if err != nil {
return err
}
confirmPw, err := readPassword("Confirm new wallet password: ")
if err != nil {
return err
}
if !bytes.Equal(newPw, confirmPw) {
return fmt.Errorf("passwords don't match")
}
// Should the daemon be initialized stateless? Then we expect an answer
// with the admin macaroon later. Because the --save_to is related to
// stateless init, it doesn't make sense to be set on its own.
statelessInit := ctx.Bool(statelessInitFlag.Name)
if !statelessInit && ctx.IsSet(saveToFlag.Name) {
return fmt.Errorf("cannot set save_to parameter without " +
"stateless_init")
}
req := &lnrpc.ChangePasswordRequest{
CurrentPassword: currentPw,
NewPassword: newPw,
StatelessInit: statelessInit,
NewMacaroonRootKey: ctx.Bool("new_mac_root_key"),
}
response, err := client.ChangePassword(ctxc, req)
if err != nil {
return err
}
if statelessInit {
return storeOrPrintAdminMac(ctx, response.AdminMacaroon)
}
return nil
}
var createWatchOnlyCommand = cli.Command{
Name: "createwatchonly",
Category: "Startup",
ArgsUsage: "accounts-json-file",
Usage: "Initialize a watch-only wallet after starting lnd for the " +
"first time.",
Description: `
The create command is used to initialize an lnd wallet from scratch for
the very first time, in watch-only mode. Watch-only means, there will be
no private keys in lnd's wallet. This is only useful in combination with
a remote signer or when lnd should be used as an on-chain wallet with
PSBT interaction only.
This is an interactive command that takes a JSON file as its first and
only argument. The JSON is in the same format as the output of the
'lncli wallet accounts list' command. This makes it easy to initialize
the remote signer with the seed, then export the extended public account
keys (xpubs) to import the watch-only wallet.
Example JSON (non-mandatory or ignored fields are omitted):
{
"accounts": [
{
"extended_public_key": "upub5Eep7....",
"derivation_path": "m/49'/0'/0'"
},
{
"extended_public_key": "vpub5ZU1PH...",
"derivation_path": "m/84'/0'/0'"
},
{
"extended_public_key": "tpubDDXFH...",
"derivation_path": "m/1017'/1'/0'"
},
...
{
"extended_public_key": "tpubDDXFH...",
"derivation_path": "m/1017'/1'/9'"
}
]
}
There must be an account for each of the existing key families that lnd
uses internally (currently 0-9, see keychain/derivation.go).
Read the documentation under docs/remote-signing.md for more information
on how to set up a remote signing node over RPC.
`,
Flags: []cli.Flag{
statelessInitFlag,
saveToFlag,
macRootKeyFlag,
},
Action: actionDecorator(createWatchOnly),
}
func createWatchOnly(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getWalletUnlockerClient(ctx)
defer cleanUp()
if ctx.NArg() != 1 {
return cli.ShowCommandHelp(ctx, "createwatchonly")
}
// Should the daemon be initialized stateless? Then we expect an answer
// with the admin macaroon later. Because the --save_to is related to
// stateless init, it doesn't make sense to be set on its own.
statelessInit := ctx.Bool(statelessInitFlag.Name)
if !statelessInit && ctx.IsSet(saveToFlag.Name) {
return fmt.Errorf("cannot set save_to parameter without " +
"stateless_init")
}
jsonFile := lncfg.CleanAndExpandPath(ctx.Args().First())
jsonBytes, err := os.ReadFile(jsonFile)
if err != nil {
return fmt.Errorf("error reading JSON from file %v: %v",
jsonFile, err)
}
jsonAccts := &walletrpc.ListAccountsResponse{}
err = lnrpc.ProtoJSONUnmarshalOpts.Unmarshal(jsonBytes, jsonAccts)
if err != nil {
return fmt.Errorf("error parsing JSON: %w", err)
}
if len(jsonAccts.Accounts) == 0 {
return fmt.Errorf("cannot import empty account list")
}
walletPassword, err := capturePassword(
"Input wallet password: ", false,
walletunlocker.ValidatePassword,
)
if err != nil {
return err
}
extendedRootKeyBirthday, err := askBirthdayTimestamp()
if err != nil {
return err
}
recoveryWindow, err := askRecoveryWindow()
if err != nil {
return err
}
rpcAccounts, err := walletrpc.AccountsToWatchOnly(jsonAccts.Accounts)
if err != nil {
return err
}
rpcResp := &lnrpc.WatchOnly{
MasterKeyBirthdayTimestamp: extendedRootKeyBirthday,
Accounts: rpcAccounts,
}
// We assume that all accounts were exported from the same master root
// key. So if one is set, we just forward that. If other accounts should
// be watched later on, they should be imported into the watch-only
// node, that then also forwards the import request to the remote
// signer.
for _, acct := range jsonAccts.Accounts {
if len(acct.MasterKeyFingerprint) > 0 {
rpcResp.MasterKeyFingerprint = acct.MasterKeyFingerprint
}
}
// Parse the macaroon root key if it was specified by the user.
var macRootKey []byte
if ctx.IsSet(macRootKeyFlag.Name) {
macRootKey, err = hex.DecodeString(
ctx.String(macRootKeyFlag.Name),
)
if err != nil {
return fmt.Errorf("unable to parse macaroon root key: "+
"%w", err)
}
if len(macRootKey) != macaroons.RootKeyLen {
return fmt.Errorf("macaroon root key must be exactly "+
"%v bytes, got %v", macaroons.RootKeyLen,
len(macRootKey))
}
}
initResp, err := client.InitWallet(ctxc, &lnrpc.InitWalletRequest{
WalletPassword: walletPassword,
WatchOnly: rpcResp,
RecoveryWindow: recoveryWindow,
StatelessInit: statelessInit,
MacaroonRootKey: macRootKey,
})
if err != nil {
return err
}
if statelessInit {
return storeOrPrintAdminMac(ctx, initResp.AdminMacaroon)
}
return nil
}
// storeOrPrintAdminMac either stores the admin macaroon to a file specified or
// prints it to standard out, depending on the user flags set.
func storeOrPrintAdminMac(ctx *cli.Context, adminMac []byte) error {
// The user specified the optional --save_to parameter. We'll save the
// macaroon to that file.
if ctx.IsSet(saveToFlag.Name) {
macSavePath := lncfg.CleanAndExpandPath(ctx.String(
saveToFlag.Name,
))
err := os.WriteFile(macSavePath, adminMac, 0644)
if err != nil {
_ = os.Remove(macSavePath)
return err
}
fmt.Printf("Admin macaroon saved to %s\n", macSavePath)
return nil
}
// Otherwise we just print it. The user MUST store this macaroon
// somewhere so we either save it to a provided file path or just print
// it to standard output.
fmt.Printf("Admin macaroon: %s\n", hex.EncodeToString(adminMac))
return nil
}
func askRecoveryWindow() (int32, error) {
for {
fmt.Println()
fmt.Printf("Input an optional address look-ahead used to scan "+
"for used keys (default %d): ", defaultRecoveryWindow)
reader := bufio.NewReader(os.Stdin)
answer, err := reader.ReadString('\n')
if err != nil {
return 0, err
}
fmt.Println()
answer = strings.TrimSpace(answer)
if len(answer) == 0 {
return defaultRecoveryWindow, nil
}
lookAhead, err := strconv.ParseInt(answer, 10, 32)
if err != nil {
fmt.Printf("Unable to parse recovery window: %v\n", err)
continue
}
return int32(lookAhead), nil
}
}
func askBirthdayTimestamp() (uint64, error) {
for {
fmt.Println()
fmt.Printf("Input an optional wallet birthday unix timestamp " +
"of first block to start scanning from (default 0): ")
reader := bufio.NewReader(os.Stdin)
answer, err := reader.ReadString('\n')
if err != nil {
return 0, err
}
fmt.Println()
answer = strings.TrimSpace(answer)
if len(answer) == 0 {
return 0, nil
}
birthdayTimestamp, err := strconv.ParseUint(answer, 10, 64)
if err != nil {
fmt.Printf("Unable to parse birthday timestamp: %v\n",
err)
continue
}
return birthdayTimestamp, nil
}
}
func printCipherSeedWords(mnemonicWords []string) {
fmt.Println("!!!YOU MUST WRITE DOWN THIS SEED TO BE ABLE TO " +
"RESTORE THE WALLET!!!")
fmt.Println()
fmt.Println("---------------BEGIN LND CIPHER SEED---------------")
numCols := 4
colWords := monowidthColumns(mnemonicWords, numCols)
for i := 0; i < len(colWords); i += numCols {
fmt.Printf("%2d. %3s %2d. %3s %2d. %3s %2d. %3s\n",
i+1, colWords[i], i+2, colWords[i+1], i+3,
colWords[i+2], i+4, colWords[i+3])
}
fmt.Println("---------------END LND CIPHER SEED-----------------")
fmt.Println("\n!!!YOU MUST WRITE DOWN THIS SEED TO BE ABLE TO " +
"RESTORE THE WALLET!!!")
}
package commands
import (
"bufio"
"bytes"
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"math"
"os"
"regexp"
"strconv"
"strings"
"sync"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/jessevdk/go-flags"
"github.com/lightningnetwork/lnd"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/rpcperms"
"github.com/lightningnetwork/lnd/signal"
"github.com/urfave/cli"
"golang.org/x/term"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// TODO(roasbeef): cli logic for supporting both positional and unix style
// arguments.
// TODO(roasbeef): expose all fee conf targets
const defaultRecoveryWindow int32 = 2500
const (
defaultUtxoMinConf = 1
)
var (
errBadChanPoint = errors.New(
"expecting chan_point to be in format of: txid:index",
)
customDataPattern = regexp.MustCompile(
`"custom_channel_data":\s*"([0-9a-f]+)"`,
)
chanIDPattern = regexp.MustCompile(
`"chan_id":\s*"(\d+)"`,
)
channelPointPattern = regexp.MustCompile(
`"channel_point":\s*"([0-9a-fA-F]+:[0-9]+)"`,
)
)
// replaceCustomData replaces the custom channel data hex string with the
// decoded custom channel data in the JSON response.
func replaceCustomData(jsonBytes []byte) []byte {
// If there's nothing to replace, return the original JSON.
if !customDataPattern.Match(jsonBytes) {
return jsonBytes
}
replacedBytes := customDataPattern.ReplaceAllFunc(
jsonBytes, func(match []byte) []byte {
encoded := customDataPattern.FindStringSubmatch(
string(match),
)[1]
decoded, err := hex.DecodeString(encoded)
if err != nil {
return match
}
return []byte("\"custom_channel_data\":" +
string(decoded))
},
)
var buf bytes.Buffer
err := json.Indent(&buf, replacedBytes, "", " ")
if err != nil {
// If we can't indent the JSON, it likely means the replacement
// data wasn't correct, so we return the original JSON.
return jsonBytes
}
return buf.Bytes()
}
// replaceAndAppendScid replaces the chan_id with scid and appends the human
// readable string representation of scid.
func replaceAndAppendScid(jsonBytes []byte) []byte {
// If there's nothing to replace, return the original JSON.
if !chanIDPattern.Match(jsonBytes) {
return jsonBytes
}
replacedBytes := chanIDPattern.ReplaceAllFunc(
jsonBytes, func(match []byte) []byte {
// Extract the captured scid group from the match.
chanID := chanIDPattern.FindStringSubmatch(
string(match),
)[1]
scid, err := strconv.ParseUint(chanID, 10, 64)
if err != nil {
return match
}
// Format a new JSON field for the scid (chan_id),
// including both its numeric representation and its
// string representation (scid_str).
scidStr := lnwire.NewShortChanIDFromInt(scid).
AltString()
updatedField := fmt.Sprintf(
`"scid": "%d", "scid_str": "%s"`, scid, scidStr,
)
// Replace the entire match with the new structure.
return []byte(updatedField)
},
)
var buf bytes.Buffer
err := json.Indent(&buf, replacedBytes, "", " ")
if err != nil {
// If we can't indent the JSON, it likely means the replacement
// data wasn't correct, so we return the original JSON.
return jsonBytes
}
return buf.Bytes()
}
// appendChanID appends the chan_id which is computed using the outpoint
// of the funding transaction (the txid, and output index).
func appendChanID(jsonBytes []byte) []byte {
// If there's nothing to replace, return the original JSON.
if !channelPointPattern.Match(jsonBytes) {
return jsonBytes
}
replacedBytes := channelPointPattern.ReplaceAllFunc(
jsonBytes, func(match []byte) []byte {
chanPoint := channelPointPattern.FindStringSubmatch(
string(match),
)[1]
chanOutpoint, err := wire.NewOutPointFromString(
chanPoint,
)
if err != nil {
return match
}
// Format a new JSON field computed from the
// channel_point (chan_id).
chanID := lnwire.NewChanIDFromOutPoint(*chanOutpoint)
updatedField := fmt.Sprintf(
`"channel_point": "%s", "chan_id": "%s"`,
chanPoint, chanID.String(),
)
// Replace the entire match with the new structure.
return []byte(updatedField)
},
)
var buf bytes.Buffer
err := json.Indent(&buf, replacedBytes, "", " ")
if err != nil {
// If we can't indent the JSON, it likely means the replacement
// data wasn't correct, so we return the original JSON.
return jsonBytes
}
return buf.Bytes()
}
func getContext() context.Context {
shutdownInterceptor, err := signal.Intercept()
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
ctxc, cancel := context.WithCancel(context.Background())
go func() {
<-shutdownInterceptor.ShutdownChannel()
cancel()
}()
return ctxc
}
func printJSON(resp interface{}) {
b, err := json.Marshal(resp)
if err != nil {
fatal(err)
}
var out bytes.Buffer
_ = json.Indent(&out, b, "", " ")
_, _ = out.WriteString("\n")
_, _ = out.WriteTo(os.Stdout)
}
// printRespJSON prints the response in a json format.
func printRespJSON(resp proto.Message) {
jsonBytes, err := lnrpc.ProtoJSONMarshalOpts.Marshal(resp)
if err != nil {
fmt.Println("unable to decode response: ", err)
return
}
// Make the custom data human readable.
jsonBytesReplaced := replaceCustomData(jsonBytes)
fmt.Printf("%s\n", jsonBytesReplaced)
}
// printModifiedProtoJSON prints the response with some additional formatting
// and replacements.
func printModifiedProtoJSON(resp proto.Message) {
jsonBytes, err := lnrpc.ProtoJSONMarshalOpts.Marshal(resp)
if err != nil {
fmt.Println("unable to decode response: ", err)
return
}
// Replace custom_channel_data in the JSON.
jsonBytesReplaced := replaceCustomData(jsonBytes)
// Replace chan_id with scid, and append scid_str and scid fields.
jsonBytesReplaced = replaceAndAppendScid(jsonBytesReplaced)
// Append the chan_id field to the JSON.
jsonBytesReplaced = appendChanID(jsonBytesReplaced)
fmt.Printf("%s\n", jsonBytesReplaced)
}
// actionDecorator is used to add additional information and error handling
// to command actions.
func actionDecorator(f func(*cli.Context) error) func(*cli.Context) error {
return func(c *cli.Context) error {
err := f(c)
// Exit early if there's no error.
if err == nil {
return nil
}
// Try to parse the Status representation from this error.
s, ok := status.FromError(err)
// If this cannot be represented by a Status, exit early.
if !ok {
return err
}
// If it's a command for the UnlockerService (like 'create' or
// 'unlock') but the wallet is already unlocked, then these
// methods aren't recognized any more because this service is
// shut down after successful unlock.
if s.Code() == codes.Unknown && strings.Contains(
s.Message(), rpcperms.ErrWalletUnlocked.Error(),
) && (c.Command.Name == "create" ||
c.Command.Name == "unlock" ||
c.Command.Name == "changepassword" ||
c.Command.Name == "createwatchonly") {
return errors.New("wallet is already unlocked")
}
// lnd might be active, but not possible to contact using RPC if
// the wallet is encrypted.
if s.Code() == codes.Unknown && strings.Contains(
s.Message(), rpcperms.ErrWalletLocked.Error(),
) {
return errors.New("wallet is encrypted - please " +
"unlock using 'lncli unlock', or set " +
"password using 'lncli create' if this is " +
"the first time starting lnd")
}
return s.Err()
}
}
var newAddressCommand = cli.Command{
Name: "newaddress",
Category: "Wallet",
Usage: "Generates a new address.",
ArgsUsage: "address-type",
Flags: []cli.Flag{
cli.StringFlag{
Name: "account",
Usage: "(optional) the name of the account to " +
"generate a new address for",
},
cli.BoolFlag{
Name: "unused",
Usage: "(optional) return the last unused address " +
"instead of generating a new one",
},
},
Description: `
Generate a wallet new address. Address-types has to be one of:
- p2wkh: Pay to witness key hash
- np2wkh: Pay to nested witness key hash
- p2tr: Pay to taproot pubkey`,
Action: actionDecorator(newAddress),
}
func newAddress(ctx *cli.Context) error {
ctxc := getContext()
// Display the command's help message if we do not have the expected
// number of arguments/flags.
if ctx.NArg() != 1 || ctx.NumFlags() > 1 {
return cli.ShowCommandHelp(ctx, "newaddress")
}
// Map the string encoded address type, to the concrete typed address
// type enum. An unrecognized address type will result in an error.
stringAddrType := ctx.Args().First()
unused := ctx.Bool("unused")
var addrType lnrpc.AddressType
switch stringAddrType { // TODO(roasbeef): make them ints on the cli?
case "p2wkh":
addrType = lnrpc.AddressType_WITNESS_PUBKEY_HASH
if unused {
addrType = lnrpc.AddressType_UNUSED_WITNESS_PUBKEY_HASH
}
case "np2wkh":
addrType = lnrpc.AddressType_NESTED_PUBKEY_HASH
if unused {
addrType = lnrpc.AddressType_UNUSED_NESTED_PUBKEY_HASH
}
case "p2tr":
addrType = lnrpc.AddressType_TAPROOT_PUBKEY
if unused {
addrType = lnrpc.AddressType_UNUSED_TAPROOT_PUBKEY
}
default:
return fmt.Errorf("invalid address type %v, support address type "+
"are: p2wkh, np2wkh, and p2tr", stringAddrType)
}
client, cleanUp := getClient(ctx)
defer cleanUp()
addr, err := client.NewAddress(ctxc, &lnrpc.NewAddressRequest{
Type: addrType,
Account: ctx.String("account"),
})
if err != nil {
return err
}
printRespJSON(addr)
return nil
}
var coinSelectionStrategyFlag = cli.StringFlag{
Name: "coin_selection_strategy",
Usage: "(optional) the strategy to use for selecting " +
"coins. Possible values are 'largest', 'random', or " +
"'global-config'. If either 'largest' or 'random' is " +
"specified, it will override the globally configured " +
"strategy in lnd.conf",
Value: "global-config",
}
var estimateFeeCommand = cli.Command{
Name: "estimatefee",
Category: "On-chain",
Usage: "Get fee estimates for sending bitcoin on-chain to multiple addresses.",
ArgsUsage: "send-json-string [--conf_target=N]",
Description: `
Get fee estimates for sending a transaction paying the specified amount(s) to the passed address(es).
The send-json-string' param decodes addresses and the amount to send respectively in the following format:
'{"ExampleAddr": NumCoinsInSatoshis, "SecondAddr": NumCoins}'
`,
Flags: []cli.Flag{
cli.Int64Flag{
Name: "conf_target",
Usage: "(optional) the number of blocks that the " +
"transaction *should* confirm in",
},
coinSelectionStrategyFlag,
},
Action: actionDecorator(estimateFees),
}
func estimateFees(ctx *cli.Context) error {
ctxc := getContext()
var amountToAddr map[string]int64
jsonMap := ctx.Args().First()
if err := json.Unmarshal([]byte(jsonMap), &amountToAddr); err != nil {
return err
}
coinSelectionStrategy, err := parseCoinSelectionStrategy(ctx)
if err != nil {
return err
}
client, cleanUp := getClient(ctx)
defer cleanUp()
resp, err := client.EstimateFee(ctxc, &lnrpc.EstimateFeeRequest{
AddrToAmount: amountToAddr,
TargetConf: int32(ctx.Int64("conf_target")),
CoinSelectionStrategy: coinSelectionStrategy,
})
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var txLabelFlag = cli.StringFlag{
Name: "label",
Usage: "(optional) a label for the transaction",
}
var sendCoinsCommand = cli.Command{
Name: "sendcoins",
Category: "On-chain",
Usage: "Send bitcoin on-chain to an address.",
ArgsUsage: "addr amt",
Description: `
Send amt coins in satoshis to the base58 or bech32 encoded bitcoin address addr.
Fees used when sending the transaction can be specified via the --conf_target, or
--sat_per_vbyte optional flags.
Positional arguments and flags can be used interchangeably but not at the same time!
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "addr",
Usage: "the base58 or bech32 encoded bitcoin address to send coins " +
"to on-chain",
},
cli.BoolFlag{
Name: "sweepall",
Usage: "if set, then the amount field should be " +
"unset. This indicates that the wallet will " +
"attempt to sweep all outputs within the " +
"wallet or all funds in select utxos (when " +
"supplied) to the target address",
},
cli.Int64Flag{
Name: "amt",
Usage: "the number of bitcoin denominated in satoshis to send",
},
cli.Int64Flag{
Name: "conf_target",
Usage: "(optional) the number of blocks that the " +
"transaction *should* confirm in, will be " +
"used for fee estimation",
},
cli.Int64Flag{
Name: "sat_per_byte",
Usage: "Deprecated, use sat_per_vbyte instead.",
Hidden: true,
},
cli.Int64Flag{
Name: "sat_per_vbyte",
Usage: "(optional) a manual fee expressed in " +
"sat/vbyte that should be used when crafting " +
"the transaction",
},
cli.Uint64Flag{
Name: "min_confs",
Usage: "(optional) the minimum number of confirmations " +
"each one of your outputs used for the transaction " +
"must satisfy",
Value: defaultUtxoMinConf,
},
cli.BoolFlag{
Name: "force, f",
Usage: "if set, the transaction will be broadcast " +
"without asking for confirmation; this is " +
"set to true by default if stdout is not a " +
"terminal avoid breaking existing shell " +
"scripts",
},
coinSelectionStrategyFlag,
cli.StringSliceFlag{
Name: "utxo",
Usage: "a utxo specified as outpoint(tx:idx) which " +
"will be used as input for the transaction. " +
"This flag can be repeatedly used to specify " +
"multiple utxos as inputs. The selected " +
"utxos can either be entirely spent by " +
"specifying the sweepall flag or a specified " +
"amount can be spent in the utxos through " +
"the amt flag",
},
txLabelFlag,
},
Action: actionDecorator(sendCoins),
}
func sendCoins(ctx *cli.Context) error {
var (
addr string
amt int64
err error
outpoints []*lnrpc.OutPoint
)
ctxc := getContext()
args := ctx.Args()
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
cli.ShowCommandHelp(ctx, "sendcoins")
return nil
}
// Check that only the field sat_per_vbyte or the deprecated field
// sat_per_byte is used.
feeRateFlag, err := checkNotBothSet(
ctx, "sat_per_vbyte", "sat_per_byte",
)
if err != nil {
return err
}
// Only fee rate flag or conf_target should be set, not both.
if _, err := checkNotBothSet(
ctx, feeRateFlag, "conf_target",
); err != nil {
return err
}
switch {
case ctx.IsSet("addr"):
addr = ctx.String("addr")
case args.Present():
addr = args.First()
args = args.Tail()
default:
return fmt.Errorf("Address argument missing")
}
switch {
case ctx.IsSet("amt"):
amt = ctx.Int64("amt")
case args.Present():
amt, err = strconv.ParseInt(args.First(), 10, 64)
case !ctx.Bool("sweepall"):
return fmt.Errorf("Amount argument missing")
}
if err != nil {
return fmt.Errorf("unable to decode amount: %w", err)
}
if amt != 0 && ctx.Bool("sweepall") {
return fmt.Errorf("amount cannot be set if attempting to " +
"sweep all coins out of the wallet")
}
coinSelectionStrategy, err := parseCoinSelectionStrategy(ctx)
if err != nil {
return err
}
client, cleanUp := getClient(ctx)
defer cleanUp()
minConfs := int32(ctx.Uint64("min_confs"))
// In case that the user has specified the sweepall flag, we'll
// calculate the amount to send based on the current wallet balance.
displayAmt := amt
if ctx.Bool("sweepall") && !ctx.IsSet("utxo") {
balanceResponse, err := client.WalletBalance(
ctxc, &lnrpc.WalletBalanceRequest{
MinConfs: minConfs,
},
)
if err != nil {
return fmt.Errorf("unable to retrieve wallet balance:"+
" %w", err)
}
displayAmt = balanceResponse.GetConfirmedBalance()
}
if ctx.IsSet("utxo") {
utxos := ctx.StringSlice("utxo")
outpoints, err = UtxosToOutpoints(utxos)
if err != nil {
return fmt.Errorf("unable to decode utxos: %w", err)
}
if ctx.Bool("sweepall") {
displayAmt = 0
// If we're sweeping all funds of the utxos, we'll need
// to set the display amount to the total amount of the
// utxos.
unspents, err := client.ListUnspent(
ctxc, &lnrpc.ListUnspentRequest{
MinConfs: 0,
MaxConfs: math.MaxInt32,
},
)
if err != nil {
return err
}
for _, utxo := range outpoints {
for _, unspent := range unspents.Utxos {
unspentUtxo := unspent.Outpoint
if isSameOutpoint(utxo, unspentUtxo) {
displayAmt += unspent.AmountSat
break
}
}
}
}
}
// Ask for confirmation if we're on an actual terminal and the output is
// not being redirected to another command. This prevents existing shell
// scripts from breaking.
if !ctx.Bool("force") && term.IsTerminal(int(os.Stdout.Fd())) {
fmt.Printf("Amount: %d\n", displayAmt)
fmt.Printf("Destination address: %v\n", addr)
confirm := promptForConfirmation("Confirm payment (yes/no): ")
if !confirm {
return nil
}
}
req := &lnrpc.SendCoinsRequest{
Addr: addr,
Amount: amt,
TargetConf: int32(ctx.Int64("conf_target")),
SatPerVbyte: ctx.Uint64(feeRateFlag),
SendAll: ctx.Bool("sweepall"),
Label: ctx.String(txLabelFlag.Name),
MinConfs: minConfs,
SpendUnconfirmed: minConfs == 0,
CoinSelectionStrategy: coinSelectionStrategy,
Outpoints: outpoints,
}
txid, err := client.SendCoins(ctxc, req)
if err != nil {
return err
}
printRespJSON(txid)
return nil
}
func isSameOutpoint(a, b *lnrpc.OutPoint) bool {
return a.TxidStr == b.TxidStr && a.OutputIndex == b.OutputIndex
}
var listUnspentCommand = cli.Command{
Name: "listunspent",
Category: "On-chain",
Usage: "List utxos available for spending.",
ArgsUsage: "[min-confs [max-confs]] [--unconfirmed_only]",
Description: `
For each spendable utxo currently in the wallet, with at least min_confs
confirmations, and at most max_confs confirmations, lists the txid,
index, amount, address, address type, scriptPubkey and number of
confirmations. Use --min_confs=0 to include unconfirmed coins. To list
all coins with at least min_confs confirmations, omit the second
argument or flag '--max_confs'. To list all confirmed and unconfirmed
coins, no arguments are required. To see only unconfirmed coins, use
'--unconfirmed_only' with '--min_confs' and '--max_confs' set to zero or
not present.
`,
Flags: []cli.Flag{
cli.Int64Flag{
Name: "min_confs",
Usage: "the minimum number of confirmations for a utxo",
},
cli.Int64Flag{
Name: "max_confs",
Usage: "the maximum number of confirmations for a utxo",
},
cli.BoolFlag{
Name: "unconfirmed_only",
Usage: "when min_confs and max_confs are zero, " +
"setting false implicitly overrides max_confs " +
"to be MaxInt32, otherwise max_confs remains " +
"zero. An error is returned if the value is " +
"true and both min_confs and max_confs are " +
"non-zero. (default: false)",
},
},
Action: actionDecorator(listUnspent),
}
func listUnspent(ctx *cli.Context) error {
var (
minConfirms int64
maxConfirms int64
err error
)
ctxc := getContext()
args := ctx.Args()
if ctx.IsSet("max_confs") && !ctx.IsSet("min_confs") {
return fmt.Errorf("max_confs cannot be set without " +
"min_confs being set")
}
switch {
case ctx.IsSet("min_confs"):
minConfirms = ctx.Int64("min_confs")
case args.Present():
minConfirms, err = strconv.ParseInt(args.First(), 10, 64)
if err != nil {
cli.ShowCommandHelp(ctx, "listunspent")
return nil
}
args = args.Tail()
}
switch {
case ctx.IsSet("max_confs"):
maxConfirms = ctx.Int64("max_confs")
case args.Present():
maxConfirms, err = strconv.ParseInt(args.First(), 10, 64)
if err != nil {
cli.ShowCommandHelp(ctx, "listunspent")
return nil
}
args = args.Tail()
}
unconfirmedOnly := ctx.Bool("unconfirmed_only")
// Force minConfirms and maxConfirms to be zero if unconfirmedOnly is
// true.
if unconfirmedOnly && (minConfirms != 0 || maxConfirms != 0) {
cli.ShowCommandHelp(ctx, "listunspent")
return nil
}
// When unconfirmedOnly is inactive, we will override maxConfirms to be
// a MaxInt32 to return all confirmed and unconfirmed utxos.
if maxConfirms == 0 && !unconfirmedOnly {
maxConfirms = math.MaxInt32
}
client, cleanUp := getClient(ctx)
defer cleanUp()
req := &lnrpc.ListUnspentRequest{
MinConfs: int32(minConfirms),
MaxConfs: int32(maxConfirms),
}
resp, err := client.ListUnspent(ctxc, req)
if err != nil {
return err
}
// Parse the response into the final json object that will be printed
// to stdout. At the moment, this filters out the raw txid bytes from
// each utxo's outpoint and only prints the txid string.
var listUnspentResp = struct {
Utxos []*Utxo `json:"utxos"`
}{
Utxos: make([]*Utxo, 0, len(resp.Utxos)),
}
for _, protoUtxo := range resp.Utxos {
utxo := NewUtxoFromProto(protoUtxo)
listUnspentResp.Utxos = append(listUnspentResp.Utxos, utxo)
}
printJSON(listUnspentResp)
return nil
}
var sendManyCommand = cli.Command{
Name: "sendmany",
Category: "On-chain",
Usage: "Send bitcoin on-chain to multiple addresses.",
ArgsUsage: "send-json-string [--conf_target=N] [--sat_per_vbyte=P]",
Description: `
Create and broadcast a transaction paying the specified amount(s) to the passed address(es).
The send-json-string' param decodes addresses and the amount to send
respectively in the following format:
'{"ExampleAddr": NumCoinsInSatoshis, "SecondAddr": NumCoins}'
`,
Flags: []cli.Flag{
cli.Int64Flag{
Name: "conf_target",
Usage: "(optional) the number of blocks that the transaction *should* " +
"confirm in, will be used for fee estimation",
},
cli.Int64Flag{
Name: "sat_per_byte",
Usage: "Deprecated, use sat_per_vbyte instead.",
Hidden: true,
},
cli.Int64Flag{
Name: "sat_per_vbyte",
Usage: "(optional) a manual fee expressed in " +
"sat/vbyte that should be used when crafting " +
"the transaction",
},
cli.Uint64Flag{
Name: "min_confs",
Usage: "(optional) the minimum number of confirmations " +
"each one of your outputs used for the transaction " +
"must satisfy",
Value: defaultUtxoMinConf,
},
coinSelectionStrategyFlag,
txLabelFlag,
},
Action: actionDecorator(sendMany),
}
func sendMany(ctx *cli.Context) error {
ctxc := getContext()
var amountToAddr map[string]int64
jsonMap := ctx.Args().First()
if err := json.Unmarshal([]byte(jsonMap), &amountToAddr); err != nil {
return err
}
// Check that only the field sat_per_vbyte or the deprecated field
// sat_per_byte is used.
feeRateFlag, err := checkNotBothSet(
ctx, "sat_per_vbyte", "sat_per_byte",
)
if err != nil {
return err
}
// Only fee rate flag or conf_target should be set, not both.
if _, err := checkNotBothSet(
ctx, feeRateFlag, "conf_target",
); err != nil {
return err
}
coinSelectionStrategy, err := parseCoinSelectionStrategy(ctx)
if err != nil {
return err
}
client, cleanUp := getClient(ctx)
defer cleanUp()
minConfs := int32(ctx.Uint64("min_confs"))
txid, err := client.SendMany(ctxc, &lnrpc.SendManyRequest{
AddrToAmount: amountToAddr,
TargetConf: int32(ctx.Int64("conf_target")),
SatPerVbyte: ctx.Uint64(feeRateFlag),
Label: ctx.String(txLabelFlag.Name),
MinConfs: minConfs,
SpendUnconfirmed: minConfs == 0,
CoinSelectionStrategy: coinSelectionStrategy,
})
if err != nil {
return err
}
printRespJSON(txid)
return nil
}
var connectCommand = cli.Command{
Name: "connect",
Category: "Peers",
Usage: "Connect to a remote lightning peer.",
ArgsUsage: "<pubkey>@host",
Description: `
Connect to a peer using its <pubkey> and host.
A custom timeout on the connection is supported. For instance, to timeout
the connection request in 30 seconds, use the following:
lncli connect <pubkey>@host --timeout 30s
`,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "perm",
Usage: "If set, the daemon will attempt to persistently " +
"connect to the target peer.\n" +
" If not, the call will be synchronous.",
},
cli.DurationFlag{
Name: "timeout",
Usage: "The connection timeout value for current request. " +
"Valid uints are {ms, s, m, h}.\n" +
"If not set, the global connection " +
"timeout value (default to 120s) is used.",
},
},
Action: actionDecorator(connectPeer),
}
func connectPeer(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
targetAddress := ctx.Args().First()
splitAddr := strings.Split(targetAddress, "@")
if len(splitAddr) != 2 {
return fmt.Errorf("target address expected in format: " +
"pubkey@host:port")
}
addr := &lnrpc.LightningAddress{
Pubkey: splitAddr[0],
Host: splitAddr[1],
}
req := &lnrpc.ConnectPeerRequest{
Addr: addr,
Perm: ctx.Bool("perm"),
Timeout: uint64(ctx.Duration("timeout").Seconds()),
}
lnid, err := client.ConnectPeer(ctxc, req)
if err != nil {
return err
}
printRespJSON(lnid)
return nil
}
var disconnectCommand = cli.Command{
Name: "disconnect",
Category: "Peers",
Usage: "Disconnect a remote lightning peer identified by " +
"public key.",
ArgsUsage: "<pubkey>",
Flags: []cli.Flag{
cli.StringFlag{
Name: "node_key",
Usage: "The hex-encoded compressed public key of the peer " +
"to disconnect from",
},
},
Action: actionDecorator(disconnectPeer),
}
func disconnectPeer(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
var pubKey string
switch {
case ctx.IsSet("node_key"):
pubKey = ctx.String("node_key")
case ctx.Args().Present():
pubKey = ctx.Args().First()
default:
return fmt.Errorf("must specify target public key")
}
req := &lnrpc.DisconnectPeerRequest{
PubKey: pubKey,
}
lnid, err := client.DisconnectPeer(ctxc, req)
if err != nil {
return err
}
printRespJSON(lnid)
return nil
}
// TODO(roasbeef): also allow short relative channel ID.
var closeChannelCommand = cli.Command{
Name: "closechannel",
Category: "Channels",
Usage: "Close an existing channel.",
Description: `
Close an existing channel. The channel can be closed either cooperatively,
or unilaterally (--force).
A unilateral channel closure means that the latest commitment
transaction will be broadcast to the network. As a result, any settled
funds will be time locked for a few blocks before they can be spent.
In the case of a cooperative closure, one can manually set the fee to
be used for the closing transaction via either the --conf_target or
--sat_per_vbyte arguments. This will be the starting value used during
fee negotiation. This is optional. The parameter --max_fee_rate in
comparison is the end boundary of the fee negotiation, if not specified
it's always x3 of the starting value. Increasing this value increases
the chance of a successful negotiation.
Moreover if the channel has active HTLCs on it, the coop close will
wait until all HTLCs are resolved and will not allow any new HTLCs on
the channel. The channel will appear as disabled in the listchannels
output. The command will block in that case until the channel close tx
is broadcasted.
In the case of a cooperative closure, one can manually set the address
to deliver funds to upon closure. This is optional, and may only be used
if an upfront shutdown address has not already been set. If neither are
set the funds will be delivered to a new wallet address.
To view which funding_txids/output_indexes can be used for a channel close,
see the channel_point values within the listchannels command output.
The format for a channel_point is 'funding_txid:output_index'.`,
ArgsUsage: "funding_txid output_index",
Flags: []cli.Flag{
cli.StringFlag{
Name: "funding_txid",
Usage: "the txid of the channel's funding transaction",
},
cli.IntFlag{
Name: "output_index",
Usage: "the output index for the funding output of the funding " +
"transaction",
},
cli.StringFlag{
Name: "chan_point",
Usage: "(optional) the channel point. If set, " +
"funding_txid and output_index flags and " +
"positional arguments will be ignored",
},
cli.BoolFlag{
Name: "force",
Usage: "attempt an uncooperative closure",
},
cli.BoolFlag{
Name: "block",
Usage: `block will wait for the channel to be closed,
"meaning that it will wait for the channel close tx to
get 1 confirmation.`,
},
cli.Int64Flag{
Name: "conf_target",
Usage: "(optional) the number of blocks that the " +
"transaction *should* confirm in, will be " +
"used for fee estimation. If not set, " +
"then the conf-target value set in the main " +
"lnd config will be used.",
},
cli.Int64Flag{
Name: "sat_per_byte",
Usage: "Deprecated, use sat_per_vbyte instead.",
Hidden: true,
},
cli.Int64Flag{
Name: "sat_per_vbyte",
Usage: "(optional) a manual fee expressed in " +
"sat/vbyte that should be used when crafting " +
"the transaction; default is a conf-target " +
"of 6 blocks",
},
cli.StringFlag{
Name: "delivery_addr",
Usage: "(optional) an address to deliver funds " +
"upon cooperative channel closing, may only " +
"be used if an upfront shutdown address is not " +
"already set",
},
cli.Uint64Flag{
Name: "max_fee_rate",
Usage: "(optional) maximum fee rate in sat/vbyte " +
"accepted during the negotiation (default is " +
"x3 of the desired fee rate); increases the " +
"success pobability of the negotiation if " +
"set higher",
},
},
Action: actionDecorator(closeChannel),
}
func closeChannel(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
// Show command help if no arguments and flags were provided.
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
cli.ShowCommandHelp(ctx, "closechannel")
return nil
}
// Check that only the field sat_per_vbyte or the deprecated field
// sat_per_byte is used.
feeRateFlag, err := checkNotBothSet(
ctx, "sat_per_vbyte", "sat_per_byte",
)
if err != nil {
return err
}
channelPoint, err := parseChannelPoint(ctx)
if err != nil {
return err
}
// TODO(roasbeef): implement time deadline within server
req := &lnrpc.CloseChannelRequest{
ChannelPoint: channelPoint,
Force: ctx.Bool("force"),
TargetConf: int32(ctx.Int64("conf_target")),
SatPerVbyte: ctx.Uint64(feeRateFlag),
DeliveryAddress: ctx.String("delivery_addr"),
MaxFeePerVbyte: ctx.Uint64("max_fee_rate"),
// This makes sure that a coop close will also be executed if
// active HTLCs are present on the channel.
NoWait: true,
}
// After parsing the request, we'll spin up a goroutine that will
// retrieve the closing transaction ID when attempting to close the
// channel. We do this to because `executeChannelClose` can block, so we
// would like to present the closing transaction ID to the user as soon
// as it is broadcasted.
var wg sync.WaitGroup
txidChan := make(chan string, 1)
wg.Add(1)
go func() {
defer wg.Done()
printJSON(struct {
ClosingTxid string `json:"closing_txid"`
}{
ClosingTxid: <-txidChan,
})
}()
err = executeChannelClose(ctxc, client, req, txidChan, ctx.Bool("block"))
if err != nil {
return err
}
// In the case that the user did not provide the `block` flag, then we
// need to wait for the goroutine to be done to prevent it from being
// destroyed when exiting before printing the closing transaction ID.
wg.Wait()
return nil
}
// executeChannelClose attempts to close the channel from a request. The closing
// transaction ID is sent through `txidChan` as soon as it is broadcasted to the
// network. The block boolean is used to determine if we should block until the
// closing transaction receives a confirmation of 1 block. The logging outputs
// are sent to stderr to avoid conflicts with the JSON output of the command
// and potential work flows which depend on a proper JSON output.
func executeChannelClose(ctxc context.Context, client lnrpc.LightningClient,
req *lnrpc.CloseChannelRequest, txidChan chan<- string, block bool) error {
stream, err := client.CloseChannel(ctxc, req)
if err != nil {
return err
}
for {
resp, err := stream.Recv()
if err == io.EOF {
return nil
} else if err != nil {
return err
}
switch update := resp.Update.(type) {
case *lnrpc.CloseStatusUpdate_CloseInstant:
fmt.Fprintln(os.Stderr, "Channel close successfully "+
"initiated")
pendingHtlcs := update.CloseInstant.NumPendingHtlcs
if pendingHtlcs > 0 {
fmt.Fprintf(os.Stderr, "Cooperative channel "+
"close waiting for %d HTLCs to be "+
"resolved before the close process "+
"can kick off\n", pendingHtlcs)
}
case *lnrpc.CloseStatusUpdate_ClosePending:
closingHash := update.ClosePending.Txid
txid, err := chainhash.NewHash(closingHash)
if err != nil {
return err
}
fmt.Fprintf(os.Stderr, "Channel close transaction "+
"broadcasted: %v\n", txid)
txidChan <- txid.String()
if !block {
return nil
}
fmt.Fprintln(os.Stderr, "Waiting for channel close "+
"confirmation ...")
case *lnrpc.CloseStatusUpdate_ChanClose:
fmt.Fprintln(os.Stderr, "Channel close successfully "+
"confirmed")
return nil
}
}
}
var closeAllChannelsCommand = cli.Command{
Name: "closeallchannels",
Category: "Channels",
Usage: "Close all existing channels.",
Description: `
Close all existing channels.
Channels will be closed either cooperatively or unilaterally, depending
on whether the channel is active or not. If the channel is inactive, any
settled funds within it will be time locked for a few blocks before they
can be spent.
One can request to close inactive channels only by using the
--inactive_only flag.
By default, one is prompted for confirmation every time an inactive
channel is requested to be closed. To avoid this, one can set the
--force flag, which will only prompt for confirmation once for all
inactive channels and proceed to close them.
In the case of cooperative closures, one can manually set the fee to
be used for the closing transactions via either the --conf_target or
--sat_per_vbyte arguments. This will be the starting value used during
fee negotiation. This is optional.`,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "inactive_only",
Usage: "close inactive channels only",
},
cli.BoolFlag{
Name: "force",
Usage: "ask for confirmation once before attempting " +
"to close existing channels",
},
cli.Int64Flag{
Name: "conf_target",
Usage: "(optional) the number of blocks that the " +
"closing transactions *should* confirm in, will be " +
"used for fee estimation",
},
cli.Int64Flag{
Name: "sat_per_byte",
Usage: "Deprecated, use sat_per_vbyte instead.",
Hidden: true,
},
cli.Int64Flag{
Name: "sat_per_vbyte",
Usage: "(optional) a manual fee expressed in " +
"sat/vbyte that should be used when crafting " +
"the closing transactions",
},
cli.BoolFlag{
Name: "s, skip_confirmation",
Usage: "Skip the confirmation prompt and close all " +
"channels immediately",
},
},
Action: actionDecorator(closeAllChannels),
}
func closeAllChannels(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
// Check that only the field sat_per_vbyte or the deprecated field
// sat_per_byte is used.
feeRateFlag, err := checkNotBothSet(
ctx, "sat_per_vbyte", "sat_per_byte",
)
if err != nil {
return err
}
prompt := "Do you really want to close ALL channels? (yes/no): "
if !ctx.Bool("skip_confirmation") && !promptForConfirmation(prompt) {
return errors.New("action aborted by user")
}
listReq := &lnrpc.ListChannelsRequest{}
openChannels, err := client.ListChannels(ctxc, listReq)
if err != nil {
return fmt.Errorf("unable to fetch open channels: %w", err)
}
if len(openChannels.Channels) == 0 {
return errors.New("no open channels to close")
}
var channelsToClose []*lnrpc.Channel
switch {
case ctx.Bool("force") && ctx.Bool("inactive_only"):
msg := "Unilaterally close all inactive channels? The funds " +
"within these channels will be locked for some blocks " +
"(CSV delay) before they can be spent. (yes/no): "
confirmed := promptForConfirmation(msg)
// We can safely exit if the user did not confirm.
if !confirmed {
return nil
}
// Go through the list of open channels and only add inactive
// channels to the closing list.
for _, channel := range openChannels.Channels {
if !channel.GetActive() {
channelsToClose = append(
channelsToClose, channel,
)
}
}
case ctx.Bool("force"):
msg := "Close all active and inactive channels? Inactive " +
"channels will be closed unilaterally, so funds " +
"within them will be locked for a few blocks (CSV " +
"delay) before they can be spent. (yes/no): "
confirmed := promptForConfirmation(msg)
// We can safely exit if the user did not confirm.
if !confirmed {
return nil
}
channelsToClose = openChannels.Channels
default:
// Go through the list of open channels and determine which
// should be added to the closing list.
for _, channel := range openChannels.Channels {
// If the channel is inactive, we'll attempt to
// unilaterally close the channel, so we should prompt
// the user for confirmation beforehand.
if !channel.GetActive() {
msg := fmt.Sprintf("Unilaterally close channel "+
"with node %s and channel point %s? "+
"The closing transaction will need %d "+
"confirmations before the funds can be "+
"spent. (yes/no): ", channel.RemotePubkey,
channel.ChannelPoint, channel.LocalConstraints.CsvDelay)
confirmed := promptForConfirmation(msg)
if confirmed {
channelsToClose = append(
channelsToClose, channel,
)
}
} else if !ctx.Bool("inactive_only") {
// Otherwise, we'll only add active channels if
// we were not requested to close inactive
// channels only.
channelsToClose = append(
channelsToClose, channel,
)
}
}
}
// result defines the result of closing a channel. The closing
// transaction ID is populated if a channel is successfully closed.
// Otherwise, the error that prevented closing the channel is populated.
type result struct {
RemotePubKey string `json:"remote_pub_key"`
ChannelPoint string `json:"channel_point"`
ClosingTxid string `json:"closing_txid"`
FailErr string `json:"error"`
}
// Launch each channel closure in a goroutine in order to execute them
// in parallel. Once they're all executed, we will print the results as
// they come.
resultChan := make(chan result, len(channelsToClose))
for _, channel := range channelsToClose {
go func(channel *lnrpc.Channel) {
res := result{}
res.RemotePubKey = channel.RemotePubkey
res.ChannelPoint = channel.ChannelPoint
defer func() {
resultChan <- res
}()
// Parse the channel point in order to create the close
// channel request.
s := strings.Split(res.ChannelPoint, ":")
if len(s) != 2 {
res.FailErr = "expected channel point with " +
"format txid:index"
return
}
index, err := strconv.ParseUint(s[1], 10, 32)
if err != nil {
res.FailErr = fmt.Sprintf("unable to parse "+
"channel point output index: %v", err)
return
}
req := &lnrpc.CloseChannelRequest{
ChannelPoint: &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{
FundingTxidStr: s[0],
},
OutputIndex: uint32(index),
},
Force: !channel.GetActive(),
TargetConf: int32(ctx.Int64("conf_target")),
SatPerVbyte: ctx.Uint64(feeRateFlag),
}
txidChan := make(chan string, 1)
err = executeChannelClose(ctxc, client, req, txidChan, false)
if err != nil {
res.FailErr = fmt.Sprintf("unable to close "+
"channel: %v", err)
return
}
res.ClosingTxid = <-txidChan
}(channel)
}
for range channelsToClose {
res := <-resultChan
printJSON(res)
}
return nil
}
// promptForConfirmation continuously prompts the user for the message until
// receiving a response of "yes" or "no" and returns their answer as a bool.
func promptForConfirmation(msg string) bool {
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print(msg)
answer, err := reader.ReadString('\n')
if err != nil {
return false
}
answer = strings.ToLower(strings.TrimSpace(answer))
switch {
case answer == "yes":
return true
case answer == "no":
return false
default:
continue
}
}
}
var abandonChannelCommand = cli.Command{
Name: "abandonchannel",
Category: "Channels",
Usage: "Abandons an existing channel.",
Description: `
Removes all channel state from the database except for a close
summary. This method can be used to get rid of permanently unusable
channels due to bugs fixed in newer versions of lnd.
Only available when lnd is built in debug mode. The flag
--i_know_what_i_am_doing can be set to override the debug/dev mode
requirement.
To view which funding_txids/output_indexes can be used for this command,
see the channel_point values within the listchannels command output.
The format for a channel_point is 'funding_txid:output_index'.`,
ArgsUsage: "funding_txid [output_index]",
Flags: []cli.Flag{
cli.StringFlag{
Name: "funding_txid",
Usage: "the txid of the channel's funding transaction",
},
cli.IntFlag{
Name: "output_index",
Usage: "the output index for the funding output of the funding " +
"transaction",
},
cli.StringFlag{
Name: "chan_point",
Usage: "(optional) the channel point. If set, " +
"funding_txid and output_index flags and " +
"positional arguments will be ignored",
},
cli.BoolFlag{
Name: "i_know_what_i_am_doing",
Usage: "override the requirement for lnd needing to " +
"be in dev/debug mode to use this command; " +
"when setting this the user attests that " +
"they know the danger of using this command " +
"on channels and that doing so can lead to " +
"loss of funds if the channel funding TX " +
"ever confirms (or was confirmed)",
},
},
Action: actionDecorator(abandonChannel),
}
func abandonChannel(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
// Show command help if no arguments and flags were provided.
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
cli.ShowCommandHelp(ctx, "abandonchannel")
return nil
}
channelPoint, err := parseChannelPoint(ctx)
if err != nil {
return err
}
req := &lnrpc.AbandonChannelRequest{
ChannelPoint: channelPoint,
IKnowWhatIAmDoing: ctx.Bool("i_know_what_i_am_doing"),
}
resp, err := client.AbandonChannel(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
// parseChannelPoint parses a funding txid and output index from the command
// line. Both named options and unnamed parameters are supported.
func parseChannelPoint(ctx *cli.Context) (*lnrpc.ChannelPoint, error) {
channelPoint := &lnrpc.ChannelPoint{}
var err error
args := ctx.Args()
switch {
case ctx.IsSet("chan_point"):
channelPoint, err = parseChanPoint(ctx.String("chan_point"))
if err != nil {
return nil, fmt.Errorf("unable to parse chan_point: "+
"%v", err)
}
return channelPoint, nil
case ctx.IsSet("funding_txid"):
channelPoint.FundingTxid = &lnrpc.ChannelPoint_FundingTxidStr{
FundingTxidStr: ctx.String("funding_txid"),
}
case args.Present():
channelPoint.FundingTxid = &lnrpc.ChannelPoint_FundingTxidStr{
FundingTxidStr: args.First(),
}
args = args.Tail()
default:
return nil, fmt.Errorf("funding txid argument missing")
}
switch {
case ctx.IsSet("output_index"):
channelPoint.OutputIndex = uint32(ctx.Int("output_index"))
case args.Present():
index, err := strconv.ParseUint(args.First(), 10, 32)
if err != nil {
return nil, fmt.Errorf("unable to decode output "+
"index: %w", err)
}
channelPoint.OutputIndex = uint32(index)
default:
channelPoint.OutputIndex = 0
}
return channelPoint, nil
}
var listPeersCommand = cli.Command{
Name: "listpeers",
Category: "Peers",
Usage: "List all active, currently connected peers.",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "list_errors",
Usage: "list a full set of most recent errors for the peer",
},
},
Action: actionDecorator(listPeers),
}
func listPeers(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
// By default, we display a single error on the cli. If the user
// specifically requests a full error set, then we will provide it.
req := &lnrpc.ListPeersRequest{
LatestError: !ctx.IsSet("list_errors"),
}
resp, err := client.ListPeers(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var walletBalanceCommand = cli.Command{
Name: "walletbalance",
Category: "Wallet",
Usage: "Compute and display the wallet's current balance.",
Flags: []cli.Flag{
cli.StringFlag{
Name: "account",
Usage: "(optional) the account for which the balance " +
"is shown",
Value: "",
},
},
Action: actionDecorator(walletBalance),
}
func walletBalance(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
req := &lnrpc.WalletBalanceRequest{
Account: ctx.String("account"),
}
resp, err := client.WalletBalance(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var ChannelBalanceCommand = cli.Command{
Name: "channelbalance",
Category: "Channels",
Usage: "Returns the sum of the total available channel balance across " +
"all open channels.",
Action: actionDecorator(ChannelBalance),
}
func ChannelBalance(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
req := &lnrpc.ChannelBalanceRequest{}
resp, err := client.ChannelBalance(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var generateManPageCommand = cli.Command{
Name: "generatemanpage",
Usage: "Generates a man page for lncli and lnd as " +
"lncli.1 and lnd.1 respectively.",
Hidden: true,
Action: actionDecorator(generateManPage),
}
func generateManPage(ctx *cli.Context) error {
// Generate the man pages for lncli as lncli.1.
manpages, err := ctx.App.ToMan()
if err != nil {
return err
}
err = os.WriteFile("lncli.1", []byte(manpages), 0644)
if err != nil {
return err
}
// Generate the man pages for lnd as lnd.1.
config := lnd.DefaultConfig()
fileParser := flags.NewParser(&config, flags.Default)
fileParser.Name = "lnd"
var buf bytes.Buffer
fileParser.WriteManPage(&buf)
err = os.WriteFile("lnd.1", buf.Bytes(), 0644)
if err != nil {
return err
}
return nil
}
var getInfoCommand = cli.Command{
Name: "getinfo",
Usage: "Returns basic information related to the active daemon.",
Action: actionDecorator(getInfo),
}
func getInfo(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
req := &lnrpc.GetInfoRequest{}
resp, err := client.GetInfo(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var getRecoveryInfoCommand = cli.Command{
Name: "getrecoveryinfo",
Usage: "Display information about an ongoing recovery attempt.",
Action: actionDecorator(getRecoveryInfo),
}
func getRecoveryInfo(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
req := &lnrpc.GetRecoveryInfoRequest{}
resp, err := client.GetRecoveryInfo(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var pendingChannelsCommand = cli.Command{
Name: "pendingchannels",
Category: "Channels",
Usage: "Display information pertaining to pending channels.",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "include_raw_tx",
Usage: "include the raw transaction hex for " +
"waiting_close_channels.",
},
},
Action: actionDecorator(pendingChannels),
}
func pendingChannels(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
includeRawTx := ctx.Bool("include_raw_tx")
req := &lnrpc.PendingChannelsRequest{
IncludeRawTx: includeRawTx,
}
resp, err := client.PendingChannels(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var ListChannelsCommand = cli.Command{
Name: "listchannels",
Category: "Channels",
Usage: "List all open channels.",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "active_only",
Usage: "only list channels which are currently active",
},
cli.BoolFlag{
Name: "inactive_only",
Usage: "only list channels which are currently inactive",
},
cli.BoolFlag{
Name: "public_only",
Usage: "only list channels which are currently public",
},
cli.BoolFlag{
Name: "private_only",
Usage: "only list channels which are currently private",
},
cli.StringFlag{
Name: "peer",
Usage: "(optional) only display channels with a " +
"particular peer, accepts 66-byte, " +
"hex-encoded pubkeys",
},
cli.BoolFlag{
Name: "skip_peer_alias_lookup",
Usage: "skip the peer alias lookup per channel in " +
"order to improve performance",
},
},
Action: actionDecorator(ListChannels),
}
var listAliasesCommand = cli.Command{
Name: "listaliases",
Category: "Channels",
Usage: "List all aliases.",
Flags: []cli.Flag{},
Action: actionDecorator(listAliases),
}
func listAliases(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
req := &lnrpc.ListAliasesRequest{}
resp, err := client.ListAliases(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
func ListChannels(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
peer := ctx.String("peer")
// If the user requested channels with a particular key, parse the
// provided pubkey.
var peerKey []byte
if len(peer) > 0 {
pk, err := route.NewVertexFromStr(peer)
if err != nil {
return fmt.Errorf("invalid --peer pubkey: %w", err)
}
peerKey = pk[:]
}
// By default, we will look up the peers' alias information unless the
// skip_peer_alias_lookup flag indicates otherwise.
lookupPeerAlias := !ctx.Bool("skip_peer_alias_lookup")
req := &lnrpc.ListChannelsRequest{
ActiveOnly: ctx.Bool("active_only"),
InactiveOnly: ctx.Bool("inactive_only"),
PublicOnly: ctx.Bool("public_only"),
PrivateOnly: ctx.Bool("private_only"),
Peer: peerKey,
PeerAliasLookup: lookupPeerAlias,
}
resp, err := client.ListChannels(ctxc, req)
if err != nil {
return err
}
printModifiedProtoJSON(resp)
return nil
}
var closedChannelsCommand = cli.Command{
Name: "closedchannels",
Category: "Channels",
Usage: "List all closed channels.",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "cooperative",
Usage: "list channels that were closed cooperatively",
},
cli.BoolFlag{
Name: "local_force",
Usage: "list channels that were force-closed " +
"by the local node",
},
cli.BoolFlag{
Name: "remote_force",
Usage: "list channels that were force-closed " +
"by the remote node",
},
cli.BoolFlag{
Name: "breach",
Usage: "list channels for which the remote node " +
"attempted to broadcast a prior " +
"revoked channel state",
},
cli.BoolFlag{
Name: "funding_canceled",
Usage: "list channels that were never fully opened",
},
cli.BoolFlag{
Name: "abandoned",
Usage: "list channels that were abandoned by " +
"the local node",
},
},
Action: actionDecorator(closedChannels),
}
func closedChannels(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
req := &lnrpc.ClosedChannelsRequest{
Cooperative: ctx.Bool("cooperative"),
LocalForce: ctx.Bool("local_force"),
RemoteForce: ctx.Bool("remote_force"),
Breach: ctx.Bool("breach"),
FundingCanceled: ctx.Bool("funding_canceled"),
Abandoned: ctx.Bool("abandoned"),
}
resp, err := client.ClosedChannels(ctxc, req)
if err != nil {
return err
}
printModifiedProtoJSON(resp)
return nil
}
var describeGraphCommand = cli.Command{
Name: "describegraph",
Category: "Graph",
Description: "Prints a human readable version of the known channel " +
"graph from the PoV of the node",
Usage: "Describe the network graph.",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "include_unannounced",
Usage: "If set, unannounced channels will be included in the " +
"graph. Unannounced channels are both private channels, and " +
"public channels that are not yet announced to the network.",
},
},
Action: actionDecorator(describeGraph),
}
func describeGraph(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
req := &lnrpc.ChannelGraphRequest{
IncludeUnannounced: ctx.Bool("include_unannounced"),
}
graph, err := client.DescribeGraph(ctxc, req)
if err != nil {
return err
}
printRespJSON(graph)
return nil
}
var getNodeMetricsCommand = cli.Command{
Name: "getnodemetrics",
Category: "Graph",
Description: "Prints out node metrics calculated from the current graph",
Usage: "Get node metrics.",
Action: actionDecorator(getNodeMetrics),
}
func getNodeMetrics(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
req := &lnrpc.NodeMetricsRequest{
Types: []lnrpc.NodeMetricType{lnrpc.NodeMetricType_BETWEENNESS_CENTRALITY},
}
nodeMetrics, err := client.GetNodeMetrics(ctxc, req)
if err != nil {
return err
}
printRespJSON(nodeMetrics)
return nil
}
var getChanInfoCommand = cli.Command{
Name: "getchaninfo",
Category: "Graph",
Usage: "Get the state of a channel.",
Description: "Prints out the latest authenticated state for a " +
"particular channel",
ArgsUsage: "chan_id",
Flags: []cli.Flag{
cli.Uint64Flag{
Name: "chan_id",
Usage: "The 8-byte compact channel ID to query for. " +
"If this is set the chan_point param is " +
"ignored.",
},
cli.StringFlag{
Name: "chan_point",
Usage: "The channel point in format txid:index. If " +
"the chan_id param is set this param is " +
"ignored.",
},
},
Action: actionDecorator(getChanInfo),
}
func getChanInfo(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
var (
chanID uint64
chanPoint string
err error
)
switch {
case ctx.IsSet("chan_id"):
chanID = ctx.Uint64("chan_id")
case ctx.Args().Present():
chanID, err = strconv.ParseUint(ctx.Args().First(), 10, 64)
if err != nil {
return fmt.Errorf("error parsing chan_id: %w", err)
}
case ctx.IsSet("chan_point"):
chanPoint = ctx.String("chan_point")
default:
return fmt.Errorf("chan_id or chan_point argument missing")
}
req := &lnrpc.ChanInfoRequest{
ChanId: chanID,
ChanPoint: chanPoint,
}
chanInfo, err := client.GetChanInfo(ctxc, req)
if err != nil {
return err
}
printRespJSON(chanInfo)
return nil
}
var getNodeInfoCommand = cli.Command{
Name: "getnodeinfo",
Category: "Graph",
Usage: "Get information on a specific node.",
Description: "Prints out the latest authenticated node state for an " +
"advertised node",
Flags: []cli.Flag{
cli.StringFlag{
Name: "pub_key",
Usage: "the 33-byte hex-encoded compressed public of the target " +
"node",
},
cli.BoolFlag{
Name: "include_channels",
Usage: "if true, will return all known channels " +
"associated with the node",
},
},
Action: actionDecorator(getNodeInfo),
}
func getNodeInfo(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
args := ctx.Args()
var pubKey string
switch {
case ctx.IsSet("pub_key"):
pubKey = ctx.String("pub_key")
case args.Present():
pubKey = args.First()
default:
return fmt.Errorf("pub_key argument missing")
}
req := &lnrpc.NodeInfoRequest{
PubKey: pubKey,
IncludeChannels: ctx.Bool("include_channels"),
}
nodeInfo, err := client.GetNodeInfo(ctxc, req)
if err != nil {
return err
}
printRespJSON(nodeInfo)
return nil
}
var getNetworkInfoCommand = cli.Command{
Name: "getnetworkinfo",
Category: "Channels",
Usage: "Get statistical information about the current " +
"state of the network.",
Description: "Returns a set of statistics pertaining to the known " +
"channel graph",
Action: actionDecorator(getNetworkInfo),
}
func getNetworkInfo(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
req := &lnrpc.NetworkInfoRequest{}
netInfo, err := client.GetNetworkInfo(ctxc, req)
if err != nil {
return err
}
printRespJSON(netInfo)
return nil
}
var debugLevelCommand = cli.Command{
Name: "debuglevel",
Usage: "Set the debug level.",
Description: `Logging level for all subsystems {trace, debug, info, warn, error, critical, off}
You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems
Use show to list available subsystems`,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "show",
Usage: "if true, then the list of available sub-systems will be printed out",
},
cli.StringFlag{
Name: "level",
Usage: "the level specification to target either a coarse logging level, or granular set of specific sub-systems with logging levels for each",
},
},
Action: actionDecorator(debugLevel),
}
func debugLevel(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
req := &lnrpc.DebugLevelRequest{
Show: ctx.Bool("show"),
LevelSpec: ctx.String("level"),
}
resp, err := client.DebugLevel(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var listChainTxnsCommand = cli.Command{
Name: "listchaintxns",
Category: "On-chain",
Usage: "List transactions from the wallet.",
Flags: []cli.Flag{
cli.Int64Flag{
Name: "start_height",
Usage: "the block height from which to list " +
"transactions, inclusive",
},
cli.Int64Flag{
Name: "end_height",
Usage: "the block height until which to list " +
"transactions, inclusive; by default this " +
"will return all transactions up to the " +
"chain tip including unconfirmed " +
"transactions",
Value: -1,
},
cli.UintFlag{
Name: "index_offset",
Usage: "the index of a transaction that will be " +
"used in a query to determine which " +
"transaction should be returned in the " +
"response",
},
cli.IntFlag{
Name: "max_transactions",
Usage: "the max number of transactions to " +
"return; leave at default of 0 to return " +
"all transactions",
Value: 0,
},
},
Description: `
List all transactions an address of the wallet was involved in.
This call will return a list of wallet related transactions that paid
to an address our wallet controls, or spent utxos that we held.
By default, this call will get all transactions until the chain tip,
including unconfirmed transactions (end_height=-1).`,
Action: actionDecorator(listChainTxns),
}
func parseBlockHeightInputs(ctx *cli.Context) (int32, int32, error) {
startHeight := int32(ctx.Int64("start_height"))
endHeight := int32(ctx.Int64("end_height"))
if ctx.IsSet("start_height") && ctx.IsSet("end_height") {
if endHeight != -1 && startHeight > endHeight {
return startHeight, endHeight,
errors.New("start_height should " +
"be less than end_height if " +
"end_height is not equal to -1")
}
}
if startHeight < 0 {
return startHeight, endHeight,
errors.New("start_height should " +
"be greater than or " +
"equal to 0")
}
return startHeight, endHeight, nil
}
func listChainTxns(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
startHeight, endHeight, err := parseBlockHeightInputs(ctx)
if err != nil {
return err
}
req := &lnrpc.GetTransactionsRequest{
IndexOffset: uint32(ctx.Uint64("index_offset")),
MaxTransactions: uint32(ctx.Uint64("max_transactions")),
StartHeight: startHeight,
EndHeight: endHeight,
}
resp, err := client.GetTransactions(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var stopCommand = cli.Command{
Name: "stop",
Usage: "Stop and shutdown the daemon.",
Description: `
Gracefully stop all daemon subsystems before stopping the daemon itself.
This is equivalent to stopping it using CTRL-C.`,
Action: actionDecorator(stopDaemon),
}
func stopDaemon(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
resp, err := client.StopDaemon(ctxc, &lnrpc.StopRequest{})
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var signMessageCommand = cli.Command{
Name: "signmessage",
Category: "Wallet",
Usage: "Sign a message with the node's private key.",
ArgsUsage: "msg",
Description: `
Sign msg with the resident node's private key.
Returns the signature as a zbase32 string.
Positional arguments and flags can be used interchangeably but not at the same time!`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "msg",
Usage: "the message to sign",
},
},
Action: actionDecorator(signMessage),
}
func signMessage(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
var msg []byte
switch {
case ctx.IsSet("msg"):
msg = []byte(ctx.String("msg"))
case ctx.Args().Present():
msg = []byte(ctx.Args().First())
default:
return fmt.Errorf("msg argument missing")
}
resp, err := client.SignMessage(ctxc, &lnrpc.SignMessageRequest{Msg: msg})
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var verifyMessageCommand = cli.Command{
Name: "verifymessage",
Category: "Wallet",
Usage: "Verify a message signed with the signature.",
ArgsUsage: "msg signature",
Description: `
Verify that the message was signed with a properly-formed signature
The signature must be zbase32 encoded and signed with the private key of
an active node in the resident node's channel database.
Positional arguments and flags can be used interchangeably but not at the same time!`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "msg",
Usage: "the message to verify",
},
cli.StringFlag{
Name: "sig",
Usage: "the zbase32 encoded signature of the message",
},
},
Action: actionDecorator(verifyMessage),
}
func verifyMessage(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
var (
msg []byte
sig string
)
args := ctx.Args()
switch {
case ctx.IsSet("msg"):
msg = []byte(ctx.String("msg"))
case args.Present():
msg = []byte(ctx.Args().First())
args = args.Tail()
default:
return fmt.Errorf("msg argument missing")
}
switch {
case ctx.IsSet("sig"):
sig = ctx.String("sig")
case args.Present():
sig = args.First()
default:
return fmt.Errorf("signature argument missing")
}
req := &lnrpc.VerifyMessageRequest{Msg: msg, Signature: sig}
resp, err := client.VerifyMessage(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var feeReportCommand = cli.Command{
Name: "feereport",
Category: "Channels",
Usage: "Display the current fee policies of all active channels.",
Description: `
Returns the current fee policies of all active channels.
Fee policies can be updated using the updatechanpolicy command.`,
Action: actionDecorator(feeReport),
}
func feeReport(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
req := &lnrpc.FeeReportRequest{}
resp, err := client.FeeReport(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var updateChannelPolicyCommand = cli.Command{
Name: "updatechanpolicy",
Category: "Channels",
Usage: "Update the channel policy for all channels, or a single " +
"channel.",
ArgsUsage: "base_fee_msat fee_rate time_lock_delta " +
"[--max_htlc_msat=N] [channel_point]",
Description: `
Updates the channel policy for all channels, or just a particular
channel identified by its channel point. The update will be committed,
and broadcast to the rest of the network within the next batch. Channel
points are encoded as: funding_txid:output_index
`,
Flags: []cli.Flag{
cli.Int64Flag{
Name: "base_fee_msat",
Usage: "the base fee in milli-satoshis that will be " +
"charged for each forwarded HTLC, regardless " +
"of payment size",
},
cli.StringFlag{
Name: "fee_rate",
Usage: "the fee rate that will be charged " +
"proportionally based on the value of each " +
"forwarded HTLC, the lowest possible rate is " +
"0 with a granularity of 0.000001 " +
"(millionths). Can not be set at the same " +
"time as fee_rate_ppm",
},
cli.Uint64Flag{
Name: "fee_rate_ppm",
Usage: "the fee rate ppm (parts per million) that " +
"will be charged proportionally based on the " +
"value of each forwarded HTLC, the lowest " +
"possible rate is 0 with a granularity of " +
"0.000001 (millionths). Can not be set at " +
"the same time as fee_rate",
},
cli.Int64Flag{
Name: "inbound_base_fee_msat",
Usage: "the base inbound fee in milli-satoshis that " +
"will be charged for each forwarded HTLC, " +
"regardless of payment size. Its value must " +
"be zero or negative - it is a discount " +
"for using a particular incoming channel. " +
"Note that forwards will be rejected if the " +
"discount exceeds the outbound fee " +
"(forward at a loss), and lead to " +
"penalization by the sender",
},
cli.Int64Flag{
Name: "inbound_fee_rate_ppm",
Usage: "the inbound fee rate that will be charged " +
"proportionally based on the value of each " +
"forwarded HTLC and the outbound fee. Fee " +
"rate is expressed in parts per million and " +
"must be zero or negative - it is a discount " +
"for using a particular incoming channel. " +
"Note that forwards will be rejected if the " +
"discount exceeds the outbound fee " +
"(forward at a loss), and lead to " +
"penalization by the sender",
},
cli.Uint64Flag{
Name: "time_lock_delta",
Usage: "the CLTV delta that will be applied to all " +
"forwarded HTLCs",
},
cli.Uint64Flag{
Name: "min_htlc_msat",
Usage: "if set, the min HTLC size that will be " +
"applied to all forwarded HTLCs. If unset, " +
"the min HTLC is left unchanged",
},
cli.Uint64Flag{
Name: "max_htlc_msat",
Usage: "if set, the max HTLC size that will be " +
"applied to all forwarded HTLCs. If unset, " +
"the max HTLC is left unchanged",
},
cli.StringFlag{
Name: "chan_point",
Usage: "the channel which this policy update should " +
"be applied to. If nil, the policies for all " +
"channels will be updated. Takes the form of " +
"txid:output_index",
},
cli.BoolFlag{
Name: "create_missing_edge",
Usage: "Under unknown circumstances a channel can " +
"exist with a missing edge in the graph " +
"database. This can cause an 'edge not " +
"found' error when calling `getchaninfo` " +
"and/or cause the default channel policy to " +
"be used during forwards. Setting this flag " +
"will recreate the edge if not found, " +
"allowing updating this channel policy and " +
"fixing the missing edge problem for this " +
"channel permanently. For fields not set in " +
"this command, the default policy will be " +
"created.",
},
},
Action: actionDecorator(updateChannelPolicy),
}
func parseChanPoint(s string) (*lnrpc.ChannelPoint, error) {
split := strings.Split(s, ":")
if len(split) != 2 || len(split[0]) == 0 || len(split[1]) == 0 {
return nil, errBadChanPoint
}
index, err := strconv.ParseInt(split[1], 10, 64)
if err != nil {
return nil, fmt.Errorf("unable to decode output index: %w", err)
}
txid, err := chainhash.NewHashFromStr(split[0])
if err != nil {
return nil, fmt.Errorf("unable to parse hex string: %w", err)
}
return &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: txid[:],
},
OutputIndex: uint32(index),
}, nil
}
// parseTimeLockDelta is expected to get a uint16 type of timeLockDelta. Its
// maximum value is MaxTimeLockDelta.
func parseTimeLockDelta(timeLockDeltaStr string) (uint16, error) {
timeLockDeltaUnCheck, err := strconv.ParseUint(timeLockDeltaStr, 10, 64)
if err != nil {
return 0, fmt.Errorf("failed to parse time_lock_delta: %s "+
"to uint64, err: %v", timeLockDeltaStr, err)
}
if timeLockDeltaUnCheck > routing.MaxCLTVDelta {
return 0, fmt.Errorf("time_lock_delta is too big, "+
"max value is %d", routing.MaxCLTVDelta)
}
return uint16(timeLockDeltaUnCheck), nil
}
func updateChannelPolicy(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
var (
baseFee int64
feeRate float64
feeRatePpm uint64
timeLockDelta uint16
err error
)
args := ctx.Args()
switch {
case ctx.IsSet("base_fee_msat"):
baseFee = ctx.Int64("base_fee_msat")
case args.Present():
baseFee, err = strconv.ParseInt(args.First(), 10, 64)
if err != nil {
return fmt.Errorf("unable to decode base_fee_msat: %w",
err)
}
args = args.Tail()
default:
return fmt.Errorf("base_fee_msat argument missing")
}
switch {
case ctx.IsSet("fee_rate") && ctx.IsSet("fee_rate_ppm"):
return fmt.Errorf("fee_rate or fee_rate_ppm can not both be set")
case ctx.IsSet("fee_rate"):
feeRate = ctx.Float64("fee_rate")
case ctx.IsSet("fee_rate_ppm"):
feeRatePpm = ctx.Uint64("fee_rate_ppm")
case args.Present():
feeRate, err = strconv.ParseFloat(args.First(), 64)
if err != nil {
return fmt.Errorf("unable to decode fee_rate: %w", err)
}
args = args.Tail()
default:
return fmt.Errorf("fee_rate or fee_rate_ppm argument missing")
}
switch {
case ctx.IsSet("time_lock_delta"):
timeLockDeltaStr := ctx.String("time_lock_delta")
timeLockDelta, err = parseTimeLockDelta(timeLockDeltaStr)
if err != nil {
return err
}
case args.Present():
timeLockDelta, err = parseTimeLockDelta(args.First())
if err != nil {
return err
}
args = args.Tail()
default:
return fmt.Errorf("time_lock_delta argument missing")
}
var (
chanPoint *lnrpc.ChannelPoint
chanPointStr string
)
switch {
case ctx.IsSet("chan_point"):
chanPointStr = ctx.String("chan_point")
case args.Present():
chanPointStr = args.First()
}
if chanPointStr != "" {
chanPoint, err = parseChanPoint(chanPointStr)
if err != nil {
return fmt.Errorf("unable to parse chan_point: %w", err)
}
}
inboundBaseFeeMsat := ctx.Int64("inbound_base_fee_msat")
if inboundBaseFeeMsat < math.MinInt32 ||
inboundBaseFeeMsat > math.MaxInt32 {
return errors.New("inbound_base_fee_msat out of range")
}
inboundFeeRatePpm := ctx.Int64("inbound_fee_rate_ppm")
if inboundFeeRatePpm < math.MinInt32 ||
inboundFeeRatePpm > math.MaxInt32 {
return errors.New("inbound_fee_rate_ppm out of range")
}
// Inbound fees are optional. However, if an update is required,
// both the base fee and the fee rate must be provided.
var inboundFee *lnrpc.InboundFee
if ctx.IsSet("inbound_base_fee_msat") !=
ctx.IsSet("inbound_fee_rate_ppm") {
return errors.New("both parameters must be provided: " +
"inbound_base_fee_msat and inbound_fee_rate_ppm")
}
if ctx.IsSet("inbound_fee_rate_ppm") {
inboundFee = &lnrpc.InboundFee{
BaseFeeMsat: int32(inboundBaseFeeMsat),
FeeRatePpm: int32(inboundFeeRatePpm),
}
}
createMissingEdge := ctx.Bool("create_missing_edge")
req := &lnrpc.PolicyUpdateRequest{
BaseFeeMsat: baseFee,
TimeLockDelta: uint32(timeLockDelta),
MaxHtlcMsat: ctx.Uint64("max_htlc_msat"),
InboundFee: inboundFee,
CreateMissingEdge: createMissingEdge,
}
if ctx.IsSet("min_htlc_msat") {
req.MinHtlcMsat = ctx.Uint64("min_htlc_msat")
req.MinHtlcMsatSpecified = true
}
if chanPoint != nil {
req.Scope = &lnrpc.PolicyUpdateRequest_ChanPoint{
ChanPoint: chanPoint,
}
} else {
req.Scope = &lnrpc.PolicyUpdateRequest_Global{
Global: true,
}
}
if feeRate != 0 {
req.FeeRate = feeRate
} else if feeRatePpm != 0 {
req.FeeRatePpm = uint32(feeRatePpm)
}
resp, err := client.UpdateChannelPolicy(ctxc, req)
if err != nil {
return err
}
// Parse the response into the final json object that will be printed
// to stdout. At the moment, this filters out the raw txid bytes from
// each failed update's outpoint and only prints the txid string.
var listFailedUpdateResp = struct {
FailedUpdates []*FailedUpdate `json:"failed_updates"`
}{
FailedUpdates: make([]*FailedUpdate, 0, len(resp.FailedUpdates)),
}
for _, protoUpdate := range resp.FailedUpdates {
failedUpdate := NewFailedUpdateFromProto(protoUpdate)
listFailedUpdateResp.FailedUpdates = append(
listFailedUpdateResp.FailedUpdates, failedUpdate)
}
printJSON(listFailedUpdateResp)
return nil
}
var fishCompletionCommand = cli.Command{
Name: "fish-completion",
Hidden: true,
Action: func(c *cli.Context) error {
completion, err := c.App.ToFishCompletion()
if err != nil {
return err
}
// We don't want to suggest files, so we add this
// first line to the completions.
_, err = fmt.Printf("complete -c %q -f \n%s", c.App.Name, completion)
return err
},
}
var exportChanBackupCommand = cli.Command{
Name: "exportchanbackup",
Category: "Channels",
Usage: "Obtain a static channel back up for a selected channels, " +
"or all known channels.",
ArgsUsage: "[chan_point] [--all] [--output_file]",
Description: `
This command allows a user to export a Static Channel Backup (SCB) for
a selected channel. SCB's are encrypted backups of a channel's initial
state that are encrypted with a key derived from the seed of a user. In
the case of partial or complete data loss, the SCB will allow the user
to reclaim settled funds in the channel at its final state. The
exported channel backups can be restored at a later time using the
restorechanbackup command.
This command will return one of two types of channel backups depending
on the set of passed arguments:
* If a target channel point is specified, then a single channel
backup containing only the information for that channel will be
returned.
* If the --all flag is passed, then a multi-channel backup will be
returned. A multi backup is a single encrypted blob (displayed in
hex encoding) that contains several channels in a single cipher
text.
Both of the backup types can be restored using the restorechanbackup
command.
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "chan_point",
Usage: "the target channel to obtain an SCB for",
},
cli.BoolFlag{
Name: "all",
Usage: "if specified, then a multi backup of all " +
"active channels will be returned",
},
cli.StringFlag{
Name: "output_file",
Usage: `
if specified, then rather than printing a JSON output
of the static channel backup, a serialized version of
the backup (either Single or Multi) will be written to
the target file, this is the same format used by lnd in
its channel.backup file `,
},
},
Action: actionDecorator(exportChanBackup),
}
func exportChanBackup(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
// Show command help if no arguments provided
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
cli.ShowCommandHelp(ctx, "exportchanbackup")
return nil
}
var (
err error
chanPointStr string
outputFileName string
)
args := ctx.Args()
switch {
case ctx.IsSet("chan_point"):
chanPointStr = ctx.String("chan_point")
case args.Present():
chanPointStr = args.First()
case !ctx.IsSet("all"):
return fmt.Errorf("must specify chan_point if --all isn't set")
}
if ctx.IsSet("output_file") {
outputFileName = ctx.String("output_file")
}
if chanPointStr != "" {
chanPointRPC, err := parseChanPoint(chanPointStr)
if err != nil {
return fmt.Errorf("unable to parse chan_point: %w", err)
}
chanBackup, err := client.ExportChannelBackup(
ctxc, &lnrpc.ExportChannelBackupRequest{
ChanPoint: chanPointRPC,
},
)
if err != nil {
return err
}
txid, err := chainhash.NewHash(
chanPointRPC.GetFundingTxidBytes(),
)
if err != nil {
return err
}
chanPoint := wire.OutPoint{
Hash: *txid,
Index: chanPointRPC.OutputIndex,
}
if outputFileName != "" {
return os.WriteFile(
outputFileName,
chanBackup.ChanBackup,
0666,
)
}
printJSON(struct {
ChanPoint string `json:"chan_point"`
ChanBackup string `json:"chan_backup"`
}{
ChanPoint: chanPoint.String(),
ChanBackup: hex.EncodeToString(chanBackup.ChanBackup),
})
return nil
}
if !ctx.IsSet("all") {
return fmt.Errorf("if a channel isn't specified, -all must be")
}
chanBackup, err := client.ExportAllChannelBackups(
ctxc, &lnrpc.ChanBackupExportRequest{},
)
if err != nil {
return err
}
if outputFileName != "" {
return os.WriteFile(
outputFileName,
chanBackup.MultiChanBackup.MultiChanBackup,
0666,
)
}
// TODO(roasbeef): support for export | restore ?
var chanPoints []string
for _, chanPoint := range chanBackup.MultiChanBackup.ChanPoints {
txid, err := chainhash.NewHash(chanPoint.GetFundingTxidBytes())
if err != nil {
return err
}
chanPoints = append(chanPoints, wire.OutPoint{
Hash: *txid,
Index: chanPoint.OutputIndex,
}.String())
}
printRespJSON(chanBackup)
return nil
}
var verifyChanBackupCommand = cli.Command{
Name: "verifychanbackup",
Category: "Channels",
Usage: "Verify an existing channel backup.",
ArgsUsage: "[--single_backup] [--multi_backup] [--multi_file]",
Description: `
This command allows a user to verify an existing Single or Multi channel
backup for integrity. This is useful when a user has a backup, but is
unsure as to if it's valid or for the target node.
The command will accept backups in one of four forms:
* A single channel packed SCB, which can be obtained from
exportchanbackup. This should be passed in hex encoded format.
* A packed multi-channel SCB, which couples several individual
static channel backups in single blob.
* A file path which points to a packed single-channel backup within a
file, using the same format that lnd does in its channel.backup file.
* A file path which points to a packed multi-channel backup within a
file, using the same format that lnd does in its channel.backup
file.
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "single_backup",
Usage: "a hex encoded single channel backup obtained " +
"from exportchanbackup",
},
cli.StringFlag{
Name: "multi_backup",
Usage: "a hex encoded multi-channel backup obtained " +
"from exportchanbackup",
},
cli.StringFlag{
Name: "single_file",
Usage: "the path to a single-channel backup file",
TakesFile: true,
},
cli.StringFlag{
Name: "multi_file",
Usage: "the path to a multi-channel back up file",
TakesFile: true,
},
},
Action: actionDecorator(verifyChanBackup),
}
func verifyChanBackup(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
// Show command help if no arguments provided
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
cli.ShowCommandHelp(ctx, "verifychanbackup")
return nil
}
backups, err := parseChanBackups(ctx)
if err != nil {
return err
}
verifyReq := lnrpc.ChanBackupSnapshot{}
if backups.GetChanBackups() != nil {
verifyReq.SingleChanBackups = backups.GetChanBackups()
}
if backups.GetMultiChanBackup() != nil {
verifyReq.MultiChanBackup = &lnrpc.MultiChanBackup{
MultiChanBackup: backups.GetMultiChanBackup(),
}
}
resp, err := client.VerifyChanBackup(ctxc, &verifyReq)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var restoreChanBackupCommand = cli.Command{
Name: "restorechanbackup",
Category: "Channels",
Usage: "Restore an existing single or multi-channel static channel " +
"backup.",
ArgsUsage: "[--single_backup] [--multi_backup] [--multi_file=",
Description: `
Allows a user to restore a Static Channel Backup (SCB) that was
obtained either via the exportchanbackup command, or from lnd's
automatically managed channel.backup file. This command should be used
if a user is attempting to restore a channel due to data loss on a
running node restored with the same seed as the node that created the
channel. If successful, this command will allows the user to recover
the settled funds stored in the recovered channels.
The command will accept backups in one of four forms:
* A single channel packed SCB, which can be obtained from
exportchanbackup. This should be passed in hex encoded format.
* A packed multi-channel SCB, which couples several individual
static channel backups in single blob.
* A file path which points to a packed single-channel backup within
a file, using the same format that lnd does in its channel.backup
file.
* A file path which points to a packed multi-channel backup within a
file, using the same format that lnd does in its channel.backup
file.
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "single_backup",
Usage: "a hex encoded single channel backup obtained " +
"from exportchanbackup",
},
cli.StringFlag{
Name: "multi_backup",
Usage: "a hex encoded multi-channel backup obtained " +
"from exportchanbackup",
},
cli.StringFlag{
Name: "single_file",
Usage: "the path to a single-channel backup file",
TakesFile: true,
},
cli.StringFlag{
Name: "multi_file",
Usage: "the path to a multi-channel back up file",
TakesFile: true,
},
},
Action: actionDecorator(restoreChanBackup),
}
// errMissingChanBackup is an error returned when we attempt to parse a channel
// backup from a CLI command, and it is missing.
var errMissingChanBackup = errors.New("missing channel backup")
func parseChanBackups(ctx *cli.Context) (*lnrpc.RestoreChanBackupRequest, error) {
switch {
case ctx.IsSet("single_backup"):
packedBackup, err := hex.DecodeString(
ctx.String("single_backup"),
)
if err != nil {
return nil, fmt.Errorf("unable to decode single packed "+
"backup: %v", err)
}
return &lnrpc.RestoreChanBackupRequest{
Backup: &lnrpc.RestoreChanBackupRequest_ChanBackups{
ChanBackups: &lnrpc.ChannelBackups{
ChanBackups: []*lnrpc.ChannelBackup{
{
ChanBackup: packedBackup,
},
},
},
},
}, nil
case ctx.IsSet("multi_backup"):
packedMulti, err := hex.DecodeString(
ctx.String("multi_backup"),
)
if err != nil {
return nil, fmt.Errorf("unable to decode multi packed "+
"backup: %v", err)
}
return &lnrpc.RestoreChanBackupRequest{
Backup: &lnrpc.RestoreChanBackupRequest_MultiChanBackup{
MultiChanBackup: packedMulti,
},
}, nil
case ctx.IsSet("single_file"):
packedSingle, err := os.ReadFile(ctx.String("single_file"))
if err != nil {
return nil, fmt.Errorf("unable to decode single "+
"packed backup: %v", err)
}
return &lnrpc.RestoreChanBackupRequest{
Backup: &lnrpc.RestoreChanBackupRequest_ChanBackups{
ChanBackups: &lnrpc.ChannelBackups{
ChanBackups: []*lnrpc.ChannelBackup{{
ChanBackup: packedSingle,
}},
},
},
}, nil
case ctx.IsSet("multi_file"):
packedMulti, err := os.ReadFile(ctx.String("multi_file"))
if err != nil {
return nil, fmt.Errorf("unable to decode multi packed "+
"backup: %v", err)
}
return &lnrpc.RestoreChanBackupRequest{
Backup: &lnrpc.RestoreChanBackupRequest_MultiChanBackup{
MultiChanBackup: packedMulti,
},
}, nil
default:
return nil, errMissingChanBackup
}
}
func restoreChanBackup(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getClient(ctx)
defer cleanUp()
// Show command help if no arguments provided
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
cli.ShowCommandHelp(ctx, "restorechanbackup")
return nil
}
var req lnrpc.RestoreChanBackupRequest
backups, err := parseChanBackups(ctx)
if err != nil {
return err
}
req.Backup = backups.Backup
resp, err := client.RestoreChannelBackups(ctxc, &req)
if err != nil {
return fmt.Errorf("unable to restore chan backups: %w", err)
}
printRespJSON(resp)
return nil
}
//go:build !dev
// +build !dev
package commands
import "github.com/urfave/cli"
// devCommands will return nil for non-devrpc builds.
func devCommands() []cli.Command {
return nil
}
//go:build !invoicesrpc
// +build !invoicesrpc
package commands
import "github.com/urfave/cli"
// invoicesCommands will return nil for non-invoicesrpc builds.
func invoicesCommands() []cli.Command {
return nil
}
package commands
import (
"encoding/base64"
"encoding/hex"
"fmt"
"strings"
"github.com/btcsuite/btcwallet/snacl"
"gopkg.in/macaroon.v2"
)
const (
encryptionPrefix = "snacl:"
)
// getPasswordFn is a function that asks the user to type a password after
// presenting it the given prompt.
type getPasswordFn func(prompt string) ([]byte, error)
// macaroonJar is a struct that represents all macaroons of a profile.
type macaroonJar struct {
Default string `json:"default,omitempty"`
Timeout int64 `json:"timeout,omitempty"`
IP string `json:"ip,omitempty"`
Jar []*macaroonEntry `json:"jar"`
}
// macaroonEntry is a struct that represents a single macaroon. Its content can
// either be cleartext (hex encoded) or encrypted (snacl secretbox).
type macaroonEntry struct {
Name string `json:"name"`
Data string `json:"data"`
}
// loadMacaroon returns the fully usable macaroon instance from the entry. This
// detects whether the macaroon needs to be decrypted and does so if necessary.
// An encrypted macaroon that needs to be decrypted will prompt for the user's
// password by calling the provided password callback. Normally that should
// result in the user being prompted for the password in the terminal.
func (e *macaroonEntry) loadMacaroon(
pwCallback getPasswordFn) (*macaroon.Macaroon, error) {
if len(strings.TrimSpace(e.Data)) == 0 {
return nil, fmt.Errorf("macaroon data is empty")
}
var (
macBytes []byte
err error
)
// Either decrypt or simply decode the macaroon data.
if strings.HasPrefix(e.Data, encryptionPrefix) {
parts := strings.Split(e.Data, ":")
if len(parts) != 3 {
return nil, fmt.Errorf("invalid encrypted macaroon " +
"format, expected 'snacl:<key_base64>:" +
"<encrypted_macaroon_base64>'")
}
pw, err := pwCallback("Enter macaroon encryption password: ")
if err != nil {
return nil, fmt.Errorf("could not read password from "+
"terminal: %v", err)
}
macBytes, err = decryptMacaroon(parts[1], parts[2], pw)
if err != nil {
return nil, fmt.Errorf("unable to decrypt macaroon: %w",
err)
}
} else {
macBytes, err = hex.DecodeString(e.Data)
if err != nil {
return nil, fmt.Errorf("unable to hex decode "+
"macaroon: %v", err)
}
}
// Parse the macaroon data into its native struct.
mac := &macaroon.Macaroon{}
if err := mac.UnmarshalBinary(macBytes); err != nil {
return nil, fmt.Errorf("unable to decode macaroon: %w", err)
}
return mac, nil
}
// storeMacaroon stores a native macaroon instance to the entry. If a non-nil
// password is provided, then the macaroon is encrypted with that password. If
// not, the macaroon is stored as plain text.
func (e *macaroonEntry) storeMacaroon(mac *macaroon.Macaroon, pw []byte) error {
// First of all, make sure we can serialize the macaroon.
macBytes, err := mac.MarshalBinary()
if err != nil {
return fmt.Errorf("unable to marshal macaroon: %w", err)
}
if len(pw) == 0 {
e.Data = hex.EncodeToString(macBytes)
return nil
}
// The user did set a password. Let's derive an encryption key from it.
key, err := snacl.NewSecretKey(
&pw, snacl.DefaultN, snacl.DefaultR, snacl.DefaultP,
)
if err != nil {
return fmt.Errorf("unable to create encryption key: %w", err)
}
// Encrypt the macaroon data with the derived key and store it in the
// human readable format snacl:<key_base64>:<encrypted_macaroon_base64>.
encryptedMac, err := key.Encrypt(macBytes)
if err != nil {
return fmt.Errorf("unable to encrypt macaroon: %w", err)
}
keyB64 := base64.StdEncoding.EncodeToString(key.Marshal())
dataB64 := base64.StdEncoding.EncodeToString(encryptedMac)
e.Data = fmt.Sprintf("%s%s:%s", encryptionPrefix, keyB64, dataB64)
return nil
}
// decryptMacaroon decrypts the cipher text macaroon by using the serialized
// encryption key and the password.
func decryptMacaroon(keyB64, dataB64 string, pw []byte) ([]byte, error) {
// Base64 decode both the marshalled encryption key and macaroon data.
keyData, err := base64.StdEncoding.DecodeString(keyB64)
if err != nil {
return nil, fmt.Errorf("could not base64 decode encryption "+
"key: %v", err)
}
encryptedMac, err := base64.StdEncoding.DecodeString(dataB64)
if err != nil {
return nil, fmt.Errorf("could not base64 decode macaroon "+
"data: %v", err)
}
// Unmarshal the encryption key and ask the user for the password.
key := &snacl.SecretKey{}
err = key.Unmarshal(keyData)
if err != nil {
return nil, fmt.Errorf("could not unmarshal encryption key: %w",
err)
}
// Derive the final encryption key and then decrypt the macaroon with
// it.
err = key.DeriveKey(&pw)
if err != nil {
return nil, fmt.Errorf("could not derive encryption key, "+
"possibly due to incorrect password: %v", err)
}
macBytes, err := key.Decrypt(encryptedMac)
if err != nil {
return nil, fmt.Errorf("could not decrypt macaroon data: %w",
err)
}
return macBytes, nil
}
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Copyright (C) 2015-2024 The Lightning Network Developers
package commands
import (
"context"
"crypto/tls"
"fmt"
"net"
"os"
"path/filepath"
"strings"
"syscall"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/lightningnetwork/lnd"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/lightningnetwork/lnd/tor"
"github.com/urfave/cli"
"golang.org/x/term"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
)
const (
defaultDataDir = "data"
defaultChainSubDir = "chain"
defaultTLSCertFilename = "tls.cert"
defaultMacaroonFilename = "admin.macaroon"
defaultRPCPort = "10009"
defaultRPCHostPort = "localhost:" + defaultRPCPort
envVarRPCServer = "LNCLI_RPCSERVER"
envVarLNDDir = "LNCLI_LNDDIR"
envVarSOCKSProxy = "LNCLI_SOCKSPROXY"
envVarTLSCertPath = "LNCLI_TLSCERTPATH"
envVarChain = "LNCLI_CHAIN"
envVarNetwork = "LNCLI_NETWORK"
envVarMacaroonPath = "LNCLI_MACAROONPATH"
envVarMacaroonTimeout = "LNCLI_MACAROONTIMEOUT"
envVarMacaroonIP = "LNCLI_MACAROONIP"
envVarProfile = "LNCLI_PROFILE"
envVarMacFromJar = "LNCLI_MACFROMJAR"
)
var (
DefaultLndDir = btcutil.AppDataDir("lnd", false)
defaultTLSCertPath = filepath.Join(
DefaultLndDir, defaultTLSCertFilename,
)
// maxMsgRecvSize is the largest message our client will receive. We
// set this to 200MiB atm.
maxMsgRecvSize = grpc.MaxCallRecvMsgSize(lnrpc.MaxGrpcMsgSize)
)
func fatal(err error) {
fmt.Fprintf(os.Stderr, "[lncli] %v\n", err)
os.Exit(1)
}
func getWalletUnlockerClient(ctx *cli.Context) (lnrpc.WalletUnlockerClient,
func()) {
conn := getClientConn(ctx, true)
cleanUp := func() {
conn.Close()
}
return lnrpc.NewWalletUnlockerClient(conn), cleanUp
}
func getStateServiceClient(ctx *cli.Context) (lnrpc.StateClient, func()) {
conn := getClientConn(ctx, true)
cleanUp := func() {
conn.Close()
}
return lnrpc.NewStateClient(conn), cleanUp
}
func getClient(ctx *cli.Context) (lnrpc.LightningClient, func()) {
conn := getClientConn(ctx, false)
cleanUp := func() {
conn.Close()
}
return lnrpc.NewLightningClient(conn), cleanUp
}
func getClientConn(ctx *cli.Context, skipMacaroons bool) *grpc.ClientConn {
// First, we'll get the selected stored profile or an ephemeral one
// created from the global options in the CLI context.
profile, err := getGlobalOptions(ctx, skipMacaroons)
if err != nil {
fatal(fmt.Errorf("could not load global options: %w", err))
}
// Create a dial options array.
opts := []grpc.DialOption{
grpc.WithUnaryInterceptor(
addMetadataUnaryInterceptor(profile.Metadata),
),
grpc.WithStreamInterceptor(
addMetaDataStreamInterceptor(profile.Metadata),
),
}
if profile.Insecure {
opts = append(opts, grpc.WithInsecure())
} else {
// Load the specified TLS certificate.
certPool, err := profile.cert()
if err != nil {
fatal(fmt.Errorf("could not create cert pool: %w", err))
}
// Build transport credentials from the certificate pool. If
// there is no certificate pool, we expect the server to use a
// non-self-signed certificate such as a certificate obtained
// from Let's Encrypt.
var creds credentials.TransportCredentials
if certPool != nil {
creds = credentials.NewClientTLSFromCert(certPool, "")
} else {
// Fallback to the system pool. Using an empty tls
// config is an alternative to x509.SystemCertPool().
// That call is not supported on Windows.
creds = credentials.NewTLS(&tls.Config{})
}
opts = append(opts, grpc.WithTransportCredentials(creds))
}
// Only process macaroon credentials if --no-macaroons isn't set and
// if we're not skipping macaroon processing.
if !profile.NoMacaroons && !skipMacaroons {
// Find out which macaroon to load.
macName := profile.Macaroons.Default
if ctx.GlobalIsSet("macfromjar") {
macName = ctx.GlobalString("macfromjar")
}
var macEntry *macaroonEntry
for _, entry := range profile.Macaroons.Jar {
if entry.Name == macName {
macEntry = entry
break
}
}
if macEntry == nil {
fatal(fmt.Errorf("macaroon with name '%s' not found "+
"in profile", macName))
}
// Get and possibly decrypt the specified macaroon.
//
// TODO(guggero): Make it possible to cache the password so we
// don't need to ask for it every time.
mac, err := macEntry.loadMacaroon(readPassword)
if err != nil {
fatal(fmt.Errorf("could not load macaroon: %w", err))
}
macConstraints := []macaroons.Constraint{
// We add a time-based constraint to prevent replay of
// the macaroon. It's good for 60 seconds by default to
// make up for any discrepancy between client and server
// clocks, but leaking the macaroon before it becomes
// invalid makes it possible for an attacker to reuse
// the macaroon. In addition, the validity time of the
// macaroon is extended by the time the server clock is
// behind the client clock, or shortened by the time the
// server clock is ahead of the client clock (or invalid
// altogether if, in the latter case, this time is more
// than 60 seconds).
// TODO(aakselrod): add better anti-replay protection.
macaroons.TimeoutConstraint(profile.Macaroons.Timeout),
// Lock macaroon down to a specific IP address.
macaroons.IPLockConstraint(profile.Macaroons.IP),
// ... Add more constraints if needed.
}
// Apply constraints to the macaroon.
constrainedMac, err := macaroons.AddConstraints(
mac, macConstraints...,
)
if err != nil {
fatal(err)
}
// Now we append the macaroon credentials to the dial options.
cred, err := macaroons.NewMacaroonCredential(constrainedMac)
if err != nil {
fatal(fmt.Errorf("error cloning mac: %w", err))
}
opts = append(opts, grpc.WithPerRPCCredentials(cred))
}
// If a socksproxy server is specified we use a tor dialer
// to connect to the grpc server.
if ctx.GlobalIsSet("socksproxy") {
socksProxy := ctx.GlobalString("socksproxy")
torDialer := func(_ context.Context, addr string) (net.Conn,
error) {
return tor.Dial(
addr, socksProxy, false, false,
tor.DefaultConnTimeout,
)
}
opts = append(opts, grpc.WithContextDialer(torDialer))
} else {
// We need to use a custom dialer so we can also connect to
// unix sockets and not just TCP addresses.
genericDialer := lncfg.ClientAddressDialer(defaultRPCPort)
opts = append(opts, grpc.WithContextDialer(genericDialer))
}
opts = append(opts, grpc.WithDefaultCallOptions(maxMsgRecvSize))
conn, err := grpc.Dial(profile.RPCServer, opts...)
if err != nil {
fatal(fmt.Errorf("unable to connect to RPC server: %w", err))
}
return conn
}
// addMetadataUnaryInterceptor returns a grpc client side interceptor that
// appends any key-value metadata strings to the outgoing context of a grpc
// unary call.
func addMetadataUnaryInterceptor(
md map[string]string) grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{},
cc *grpc.ClientConn, invoker grpc.UnaryInvoker,
opts ...grpc.CallOption) error {
outCtx := contextWithMetadata(ctx, md)
return invoker(outCtx, method, req, reply, cc, opts...)
}
}
// addMetaDataStreamInterceptor returns a grpc client side interceptor that
// appends any key-value metadata strings to the outgoing context of a grpc
// stream call.
func addMetaDataStreamInterceptor(
md map[string]string) grpc.StreamClientInterceptor {
return func(ctx context.Context, desc *grpc.StreamDesc,
cc *grpc.ClientConn, method string, streamer grpc.Streamer,
opts ...grpc.CallOption) (grpc.ClientStream, error) {
outCtx := contextWithMetadata(ctx, md)
return streamer(outCtx, desc, cc, method, opts...)
}
}
// contextWithMetaData appends the given metadata key-value pairs to the given
// context.
func contextWithMetadata(ctx context.Context,
md map[string]string) context.Context {
kvPairs := make([]string, 0, 2*len(md))
for k, v := range md {
kvPairs = append(kvPairs, k, v)
}
return metadata.AppendToOutgoingContext(ctx, kvPairs...)
}
// extractPathArgs parses the TLS certificate and macaroon paths from the
// command.
func extractPathArgs(ctx *cli.Context) (string, string, error) {
network := strings.ToLower(ctx.GlobalString("network"))
switch network {
case "mainnet", "testnet", "testnet4", "regtest", "simnet", "signet":
default:
return "", "", fmt.Errorf("unknown network: %v", network)
}
// We'll now fetch the lnddir so we can make a decision on how to
// properly read the macaroons (if needed) and also the cert. This will
// either be the default, or will have been overwritten by the end
// user.
lndDir := lncfg.CleanAndExpandPath(ctx.GlobalString("lnddir"))
// If the macaroon path as been manually provided, then we'll only
// target the specified file.
var macPath string
if ctx.GlobalString("macaroonpath") != "" {
macPath = lncfg.CleanAndExpandPath(ctx.GlobalString(
"macaroonpath",
))
} else {
// Otherwise, we'll go into the path:
// lnddir/data/chain/<chain>/<network> in order to fetch the
// macaroon that we need.
macPath = filepath.Join(
lndDir, defaultDataDir, defaultChainSubDir,
lnd.BitcoinChainName, network, defaultMacaroonFilename,
)
}
tlsCertPath := lncfg.CleanAndExpandPath(ctx.GlobalString("tlscertpath"))
// If a custom lnd directory was set, we'll also check if custom paths
// for the TLS cert and macaroon file were set as well. If not, we'll
// override their paths so they can be found within the custom lnd
// directory set. This allows us to set a custom lnd directory, along
// with custom paths to the TLS cert and macaroon file.
if lndDir != DefaultLndDir {
tlsCertPath = filepath.Join(lndDir, defaultTLSCertFilename)
}
return tlsCertPath, macPath, nil
}
// checkNotBothSet accepts two flag names, a and b, and checks that only flag a
// or flag b can be set, but not both. It returns the name of the flag or an
// error.
func checkNotBothSet(ctx *cli.Context, a, b string) (string, error) {
if ctx.IsSet(a) && ctx.IsSet(b) {
return "", fmt.Errorf(
"either %s or %s should be set, but not both", a, b,
)
}
if ctx.IsSet(a) {
return a, nil
}
return b, nil
}
func Main() {
app := cli.NewApp()
app.Name = "lncli"
app.Version = build.Version() + " commit=" + build.Commit
app.Usage = "control plane for your Lightning Network Daemon (lnd)"
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "rpcserver",
Value: defaultRPCHostPort,
Usage: "The host:port of LN daemon.",
EnvVar: envVarRPCServer,
},
cli.StringFlag{
Name: "lnddir",
Value: DefaultLndDir,
Usage: "The path to lnd's base directory.",
TakesFile: true,
EnvVar: envVarLNDDir,
},
cli.StringFlag{
Name: "socksproxy",
Usage: "The host:port of a SOCKS proxy through " +
"which all connections to the LN " +
"daemon will be established over.",
EnvVar: envVarSOCKSProxy,
},
cli.StringFlag{
Name: "tlscertpath",
Value: defaultTLSCertPath,
Usage: "The path to lnd's TLS certificate.",
TakesFile: true,
EnvVar: envVarTLSCertPath,
},
cli.StringFlag{
Name: "chain, c",
Usage: "The chain lnd is running on, e.g. bitcoin.",
Value: "bitcoin",
EnvVar: envVarChain,
},
cli.StringFlag{
Name: "network, n",
Usage: "The network lnd is running on; valid values " +
"are: mainnet, testnet, testnet4, regtest, " +
"signet and simnet.",
Value: "mainnet",
EnvVar: envVarNetwork,
},
cli.BoolFlag{
Name: "no-macaroons",
Usage: "Disable macaroon authentication.",
},
cli.StringFlag{
Name: "macaroonpath",
Usage: "The path to macaroon file.",
TakesFile: true,
EnvVar: envVarMacaroonPath,
},
cli.Int64Flag{
Name: "macaroontimeout",
Value: 60,
Usage: "Anti-replay macaroon validity time in " +
"seconds.",
EnvVar: envVarMacaroonTimeout,
},
cli.StringFlag{
Name: "macaroonip",
Usage: "If set, lock macaroon to specific IP address.",
EnvVar: envVarMacaroonIP,
},
cli.StringFlag{
Name: "profile, p",
Usage: "Instead of reading settings from command " +
"line parameters or using the default " +
"profile, use a specific profile. If " +
"a default profile is set, this flag can be " +
"set to an empty string to disable reading " +
"values from the profiles file.",
EnvVar: envVarProfile,
},
cli.StringFlag{
Name: "macfromjar",
Usage: "Use this macaroon from the profile's " +
"macaroon jar instead of the default one. " +
"Can only be used if profiles are defined.",
EnvVar: envVarMacFromJar,
},
cli.StringSliceFlag{
Name: "metadata",
Usage: "This flag can be used to specify a key-value " +
"pair that should be appended to the " +
"outgoing context before the request is sent " +
"to lnd. This flag may be specified multiple " +
"times. The format is: \"key:value\".",
},
cli.BoolFlag{
Name: "insecure",
Usage: "Connect to the rpc server without TLS " +
"authentication",
Hidden: true,
},
}
app.Commands = []cli.Command{
createCommand,
createWatchOnlyCommand,
unlockCommand,
changePasswordCommand,
newAddressCommand,
estimateFeeCommand,
sendManyCommand,
sendCoinsCommand,
listUnspentCommand,
connectCommand,
disconnectCommand,
openChannelCommand,
batchOpenChannelCommand,
closeChannelCommand,
closeAllChannelsCommand,
abandonChannelCommand,
listPeersCommand,
walletBalanceCommand,
ChannelBalanceCommand,
getInfoCommand,
getDebugInfoCommand,
encryptDebugPackageCommand,
decryptDebugPackageCommand,
getRecoveryInfoCommand,
pendingChannelsCommand,
SendPaymentCommand,
payInvoiceCommand,
sendToRouteCommand,
AddInvoiceCommand,
lookupInvoiceCommand,
listInvoicesCommand,
ListChannelsCommand,
closedChannelsCommand,
listPaymentsCommand,
describeGraphCommand,
getNodeMetricsCommand,
getChanInfoCommand,
getNodeInfoCommand,
queryRoutesCommand,
getNetworkInfoCommand,
debugLevelCommand,
decodePayReqCommand,
listChainTxnsCommand,
stopCommand,
signMessageCommand,
verifyMessageCommand,
feeReportCommand,
updateChannelPolicyCommand,
forwardingHistoryCommand,
exportChanBackupCommand,
verifyChanBackupCommand,
restoreChanBackupCommand,
bakeMacaroonCommand,
listMacaroonIDsCommand,
deleteMacaroonIDCommand,
listPermissionsCommand,
printMacaroonCommand,
constrainMacaroonCommand,
trackPaymentCommand,
versionCommand,
profileSubCommand,
getStateCommand,
deletePaymentsCommand,
sendCustomCommand,
subscribeCustomCommand,
fishCompletionCommand,
listAliasesCommand,
estimateRouteFeeCommand,
generateManPageCommand,
}
// Add any extra commands determined by build flags.
app.Commands = append(app.Commands, autopilotCommands()...)
app.Commands = append(app.Commands, invoicesCommands()...)
app.Commands = append(app.Commands, neutrinoCommands()...)
app.Commands = append(app.Commands, routerCommands()...)
app.Commands = append(app.Commands, walletCommands()...)
app.Commands = append(app.Commands, watchtowerCommands()...)
app.Commands = append(app.Commands, wtclientCommands()...)
app.Commands = append(app.Commands, devCommands()...)
app.Commands = append(app.Commands, peersCommands()...)
app.Commands = append(app.Commands, chainCommands()...)
if err := app.Run(os.Args); err != nil {
fatal(err)
}
}
// readPassword reads a password from the terminal. This requires there to be an
// actual TTY so passing in a password from stdin won't work.
func readPassword(text string) ([]byte, error) {
fmt.Print(text)
// The variable syscall.Stdin is of a different type in the Windows API
// that's why we need the explicit cast. And of course the linter
// doesn't like it either.
pw, err := term.ReadPassword(int(syscall.Stdin)) //nolint:unconvert
fmt.Println()
return pw, err
}
// networkParams parses the global network flag into a chaincfg.Params.
func networkParams(ctx *cli.Context) (*chaincfg.Params, error) {
network := strings.ToLower(ctx.GlobalString("network"))
switch network {
case "mainnet":
return &chaincfg.MainNetParams, nil
case "testnet", "testnet3":
return &chaincfg.TestNet3Params, nil
case "testnet4":
return &chaincfg.TestNet4Params, nil
case "regtest":
return &chaincfg.RegressionNetParams, nil
case "simnet":
return &chaincfg.SimNetParams, nil
case "signet":
return &chaincfg.SigNetParams, nil
default:
return nil, fmt.Errorf("unknown network: %v", network)
}
}
// parseCoinSelectionStrategy parses a coin selection strategy string
// from the CLI to its lnrpc.CoinSelectionStrategy counterpart proto type.
func parseCoinSelectionStrategy(ctx *cli.Context) (
lnrpc.CoinSelectionStrategy, error) {
strategy := ctx.String(coinSelectionStrategyFlag.Name)
if !ctx.IsSet(coinSelectionStrategyFlag.Name) {
return lnrpc.CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG,
nil
}
switch strategy {
case "global-config":
return lnrpc.CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG,
nil
case "largest":
return lnrpc.CoinSelectionStrategy_STRATEGY_LARGEST, nil
case "random":
return lnrpc.CoinSelectionStrategy_STRATEGY_RANDOM, nil
default:
return 0, fmt.Errorf("unknown coin selection strategy "+
"%v", strategy)
}
}
//go:build !neutrinorpc
// +build !neutrinorpc
package commands
import "github.com/urfave/cli"
// neutrinoCommands will return nil for non-neutrinorpc builds.
func neutrinoCommands() []cli.Command {
return nil
}
//go:build !peersrpc
// +build !peersrpc
package commands
import "github.com/urfave/cli"
// peersCommands will return nil for non-peersrpc builds.
func peersCommands() []cli.Command {
return nil
}
package commands
import (
"bytes"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"os"
"path"
"strings"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/walletunlocker"
"github.com/urfave/cli"
"gopkg.in/macaroon.v2"
)
var (
errNoProfileFile = errors.New("no profile file found")
)
// profileEntry is a struct that represents all settings for one specific
// profile.
type profileEntry struct {
Name string `json:"name"`
RPCServer string `json:"rpcserver"`
LndDir string `json:"lnddir"`
Network string `json:"network"`
NoMacaroons bool `json:"no-macaroons,omitempty"` // nolint:tagliatelle
TLSCert string `json:"tlscert"`
Macaroons *macaroonJar `json:"macaroons"`
Metadata map[string]string `json:"metadata,omitempty"`
Insecure bool `json:"insecure,omitempty"`
}
// cert returns the profile's TLS certificate as a x509 certificate pool.
func (e *profileEntry) cert() (*x509.CertPool, error) {
if e.TLSCert == "" {
return nil, nil
}
cp := x509.NewCertPool()
if !cp.AppendCertsFromPEM([]byte(e.TLSCert)) {
return nil, fmt.Errorf("credentials: failed to append " +
"certificate")
}
return cp, nil
}
// getGlobalOptions returns the global connection options. If a profile file
// exists, these global options might be read from a predefined profile. If no
// profile exists, the global options from the command line are returned as an
// ephemeral profile entry.
func getGlobalOptions(ctx *cli.Context, skipMacaroons bool) (*profileEntry,
error) {
var profileName string
// Try to load the default profile file and depending on its existence
// what profile to use.
f, err := loadProfileFile(defaultProfileFile)
switch {
// The legacy case where no profile file exists and the user also didn't
// request to use one. We only consider the global options here.
case err == errNoProfileFile && !ctx.GlobalIsSet("profile"):
return profileFromContext(ctx, false, skipMacaroons)
// The file doesn't exist but the user specified an explicit profile.
case err == errNoProfileFile && ctx.GlobalIsSet("profile"):
return nil, fmt.Errorf("profile file %s does not exist",
defaultProfileFile)
// There is a file but we couldn't read/parse it.
case err != nil:
return nil, fmt.Errorf("could not read profile file %s: "+
"%v", defaultProfileFile, err)
// The user explicitly disabled the use of profiles for this command by
// setting the flag to an empty string. We fall back to the default/old
// behavior.
case ctx.GlobalIsSet("profile") && ctx.GlobalString("profile") == "":
return profileFromContext(ctx, false, skipMacaroons)
// There is a file, but no default profile is specified. The user also
// didn't specify a profile to use so we fall back to the default/old
// behavior.
case !ctx.GlobalIsSet("profile") && len(f.Default) == 0:
return profileFromContext(ctx, false, skipMacaroons)
// The user didn't specify a profile but there is a default one defined.
case !ctx.GlobalIsSet("profile") && len(f.Default) > 0:
profileName = f.Default
// The user specified a specific profile to use.
case ctx.GlobalIsSet("profile"):
profileName = ctx.GlobalString("profile")
}
// If we got to here, we do have a profile file and know the name of the
// profile to use. Now we just need to make sure it does exist.
for _, prof := range f.Profiles {
if prof.Name == profileName {
return prof, nil
}
}
return nil, fmt.Errorf("profile '%s' not found in file %s", profileName,
defaultProfileFile)
}
// profileFromContext creates an ephemeral profile entry from the global options
// set in the CLI context.
func profileFromContext(ctx *cli.Context, store, skipMacaroons bool) (
*profileEntry, error) {
// Parse the paths of the cert and macaroon. This will validate the
// chain and network value as well.
tlsCertPath, macPath, err := extractPathArgs(ctx)
if err != nil {
return nil, err
}
insecure := ctx.GlobalBool("insecure")
// Load the certificate file now, if specified. We store it as plain PEM
// directly.
var tlsCert []byte
if tlsCertPath != "" && !insecure {
var err error
tlsCert, err = os.ReadFile(tlsCertPath)
if err != nil {
return nil, fmt.Errorf("could not load TLS cert "+
"file: %v", err)
}
}
metadata := make(map[string]string)
for _, m := range ctx.GlobalStringSlice("metadata") {
pair := strings.Split(m, ":")
if len(pair) != 2 {
return nil, fmt.Errorf("invalid format for metadata " +
"flag; expected \"key:value\"")
}
metadata[pair[0]] = pair[1]
}
entry := &profileEntry{
RPCServer: ctx.GlobalString("rpcserver"),
LndDir: lncfg.CleanAndExpandPath(
ctx.GlobalString("lnddir"),
),
Network: ctx.GlobalString("network"),
NoMacaroons: ctx.GlobalBool("no-macaroons"),
TLSCert: string(tlsCert),
Metadata: metadata,
Insecure: insecure,
}
// If we aren't using macaroons in general (flag --no-macaroons) or
// don't need macaroons for this command (wallet unlocker), we can now
// return already.
if skipMacaroons || ctx.GlobalBool("no-macaroons") {
return entry, nil
}
// Now load and possibly encrypt the macaroon file.
macBytes, err := os.ReadFile(macPath)
if err != nil {
return nil, fmt.Errorf("unable to read macaroon path (check "+
"the network setting!): %v", err)
}
mac := &macaroon.Macaroon{}
if err = mac.UnmarshalBinary(macBytes); err != nil {
return nil, fmt.Errorf("unable to decode macaroon: %w", err)
}
var pw []byte
if store {
// Read a password from the terminal. If it's empty, we won't
// encrypt the macaroon and store it plaintext.
pw, err = capturePassword(
"Enter password to encrypt macaroon with or leave "+
"blank to store in plaintext: ", true,
walletunlocker.ValidatePassword,
)
if err != nil {
return nil, fmt.Errorf("unable to get encryption "+
"password: %v", err)
}
}
macEntry := &macaroonEntry{}
if err = macEntry.storeMacaroon(mac, pw); err != nil {
return nil, fmt.Errorf("unable to store macaroon: %w", err)
}
// We determine the name of the macaroon from the file itself but cut
// off the ".macaroon" at the end.
macEntry.Name = path.Base(macPath)
if path.Ext(macEntry.Name) == "macaroon" {
macEntry.Name = strings.TrimSuffix(macEntry.Name, ".macaroon")
}
// Now that we have the macaroon jar as well, let's return the entry
// with all the values populated.
entry.Macaroons = &macaroonJar{
Default: macEntry.Name,
Timeout: ctx.GlobalInt64("macaroontimeout"),
IP: ctx.GlobalString("macaroonip"),
Jar: []*macaroonEntry{macEntry},
}
return entry, nil
}
// loadProfileFile tries to load the file specified and JSON deserialize it into
// the profile file struct.
func loadProfileFile(file string) (*profileFile, error) {
if !lnrpc.FileExists(file) {
return nil, errNoProfileFile
}
content, err := os.ReadFile(file)
if err != nil {
return nil, fmt.Errorf("could not load profile file %s: %w",
file, err)
}
f := &profileFile{}
err = f.unmarshalJSON(content)
if err != nil {
return nil, fmt.Errorf("could not unmarshal profile file %s: "+
"%v", file, err)
}
return f, nil
}
// saveProfileFile stores the given profile file struct in the specified file,
// overwriting it if it already existed.
func saveProfileFile(file string, f *profileFile) error {
content, err := f.marshalJSON()
if err != nil {
return fmt.Errorf("could not marshal profile: %w", err)
}
return os.WriteFile(file, content, 0644)
}
// profileFile is a struct that represents the whole content of a profile file.
type profileFile struct {
Default string `json:"default,omitempty"`
Profiles []*profileEntry `json:"profiles"`
}
// unmarshalJSON tries to parse the given JSON and unmarshal it into the
// receiving instance.
func (f *profileFile) unmarshalJSON(content []byte) error {
return json.Unmarshal(content, f)
}
// marshalJSON serializes the receiving instance to formatted/indented JSON.
func (f *profileFile) marshalJSON() ([]byte, error) {
b, err := json.Marshal(f)
if err != nil {
return nil, fmt.Errorf("error JSON marshalling profile: %w",
err)
}
var out bytes.Buffer
err = json.Indent(&out, b, "", " ")
if err != nil {
return nil, fmt.Errorf("error indenting profile JSON: %w", err)
}
out.WriteString("\n")
return out.Bytes(), nil
}
package commands
import "github.com/urfave/cli"
// routerCommands returns a list of routerrpc commands.
func routerCommands() []cli.Command {
return []cli.Command{
queryMissionControlCommand,
importMissionControlCommand,
queryProbCommand,
resetMissionControlCommand,
buildRouteCommand,
getCfgCommand,
setCfgCommand,
updateChanStatusCommand,
}
}
package commands
import (
"encoding/hex"
"errors"
"fmt"
"strconv"
"strings"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/lnrpc"
)
// OutPoint displays an outpoint string in the form "<txid>:<output-index>".
type OutPoint string
// NewOutPointFromProto formats the lnrpc.OutPoint into an OutPoint for display.
func NewOutPointFromProto(op *lnrpc.OutPoint) OutPoint {
var hash chainhash.Hash
copy(hash[:], op.TxidBytes)
return OutPoint(fmt.Sprintf("%v:%d", hash, op.OutputIndex))
}
// NewProtoOutPoint parses an OutPoint into its corresponding lnrpc.OutPoint
// type.
func NewProtoOutPoint(op string) (*lnrpc.OutPoint, error) {
parts := strings.Split(op, ":")
if len(parts) != 2 {
return nil, errors.New("outpoint should be of the form txid:index")
}
txid := parts[0]
if hex.DecodedLen(len(txid)) != chainhash.HashSize {
return nil, fmt.Errorf("invalid hex-encoded txid %v", txid)
}
outputIndex, err := strconv.Atoi(parts[1])
if err != nil {
return nil, fmt.Errorf("invalid output index: %w", err)
}
return &lnrpc.OutPoint{
TxidStr: txid,
OutputIndex: uint32(outputIndex),
}, nil
}
// Utxo displays information about an unspent output, including its address,
// amount, pkscript, and confirmations.
type Utxo struct {
Type lnrpc.AddressType `json:"address_type"`
Address string `json:"address"`
AmountSat int64 `json:"amount_sat"`
PkScript string `json:"pk_script"`
OutPoint OutPoint `json:"outpoint"`
Confirmations int64 `json:"confirmations"`
}
// NewUtxoFromProto creates a display Utxo from the Utxo proto. This filters out
// the raw txid bytes from the provided outpoint, which will otherwise be
// printed in base64.
func NewUtxoFromProto(utxo *lnrpc.Utxo) *Utxo {
return &Utxo{
Type: utxo.AddressType,
Address: utxo.Address,
AmountSat: utxo.AmountSat,
PkScript: utxo.PkScript,
OutPoint: NewOutPointFromProto(utxo.Outpoint),
Confirmations: utxo.Confirmations,
}
}
// FailedUpdate displays information about a failed update, including its
// address, reason and update error.
type FailedUpdate struct {
OutPoint OutPoint `json:"outpoint"`
Reason string `json:"reason"`
UpdateError string `json:"update_error"`
}
// NewFailedUpdateFromProto creates a display from the FailedUpdate
// proto. This filters out the raw txid bytes from the provided outpoint,
// which will otherwise be printed in base64.
func NewFailedUpdateFromProto(update *lnrpc.FailedUpdate) *FailedUpdate {
return &FailedUpdate{
OutPoint: NewOutPointFromProto(update.Outpoint),
Reason: update.Reason.String(),
UpdateError: update.UpdateError,
}
}
// UtxosToOutpoints converts a slice of UTXO strings into a slice of OutPoint
// protobuf objects. It returns an error if no UTXOs are specified or if any
// UTXO string cannot be parsed into an OutPoint.
func UtxosToOutpoints(utxos []string) ([]*lnrpc.OutPoint, error) {
var outpoints []*lnrpc.OutPoint
if len(utxos) == 0 {
return nil, fmt.Errorf("no utxos specified")
}
for _, utxo := range utxos {
outpoint, err := NewProtoOutPoint(utxo)
if err != nil {
return nil, err
}
outpoints = append(outpoints, outpoint)
}
return outpoints, nil
}
//go:build !walletrpc
// +build !walletrpc
package commands
import "github.com/urfave/cli"
// walletCommands will return nil for non-walletrpc builds.
func walletCommands() []cli.Command {
return nil
}
package commands
import "github.com/lightningnetwork/lnd/lnrpc/walletrpc"
// PendingSweep is a CLI-friendly type of the walletrpc.PendingSweep proto. We
// use this to show more useful string versions of byte slices and enums.
type PendingSweep struct {
OutPoint OutPoint `json:"outpoint"`
WitnessType string `json:"witness_type"`
AmountSat uint32 `json:"amount_sat"`
SatPerVByte uint32 `json:"sat_per_vbyte"`
BroadcastAttempts uint32 `json:"broadcast_attempts"`
RequestedSatPerVByte uint32 `json:"requested_sat_per_vbyte"`
Immediate bool `json:"immediate"`
Budget uint64 `json:"budget"`
DeadlineHeight uint32 `json:"deadline_height"`
NextBroadcastHeight uint32 `json:"next_broadcast_height"`
RequestedConfTarget uint32 `json:"requested_conf_target"`
Force bool `json:"force"`
}
// NewPendingSweepFromProto converts the walletrpc.PendingSweep proto type into
// its corresponding CLI-friendly type.
func NewPendingSweepFromProto(pendingSweep *walletrpc.PendingSweep) *PendingSweep {
return &PendingSweep{
OutPoint: NewOutPointFromProto(pendingSweep.Outpoint),
WitnessType: pendingSweep.WitnessType.String(),
AmountSat: pendingSweep.AmountSat,
SatPerVByte: uint32(pendingSweep.SatPerVbyte),
BroadcastAttempts: pendingSweep.BroadcastAttempts,
RequestedSatPerVByte: uint32(pendingSweep.RequestedSatPerVbyte),
Immediate: pendingSweep.Immediate,
Budget: pendingSweep.Budget,
DeadlineHeight: pendingSweep.DeadlineHeight,
// Deprecated fields.
NextBroadcastHeight: pendingSweep.NextBroadcastHeight,
RequestedConfTarget: pendingSweep.RequestedConfTarget,
Force: pendingSweep.Force,
}
}
//go:build !watchtowerrpc
// +build !watchtowerrpc
package commands
import "github.com/urfave/cli"
// watchtowerCommands will return nil for non-watchtowerrpc builds.
func watchtowerCommands() []cli.Command {
return nil
}
package commands
import (
"encoding/hex"
"errors"
"fmt"
"strings"
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
"github.com/urfave/cli"
)
// wtclientCommands is a list of commands that can be used to interact with the
// watchtower client.
func wtclientCommands() []cli.Command {
return []cli.Command{
{
Name: "wtclient",
Usage: "Interact with the watchtower client.",
Category: "Watchtower",
Subcommands: []cli.Command{
addTowerCommand,
removeTowerCommand,
deactivateTowerCommand,
listTowersCommand,
getTowerCommand,
statsCommand,
policyCommand,
sessionCommands,
},
},
}
}
// getWtclient initializes a connection to the watchtower client RPC in order to
// interact with it.
func getWtclient(ctx *cli.Context) (wtclientrpc.WatchtowerClientClient, func()) {
conn := getClientConn(ctx, false)
cleanUp := func() {
conn.Close()
}
return wtclientrpc.NewWatchtowerClientClient(conn), cleanUp
}
var addTowerCommand = cli.Command{
Name: "add",
Usage: "Register a watchtower to use for future sessions/backups.",
Description: "If the watchtower has already been registered, then " +
"this command serves as a way of updating the watchtower " +
"with new addresses it is reachable over.",
ArgsUsage: "pubkey@address",
Action: actionDecorator(addTower),
}
func addTower(ctx *cli.Context) error {
ctxc := getContext()
// Display the command's help message if the number of arguments/flags
// is not what we expect.
if ctx.NArg() != 1 || ctx.NumFlags() > 0 {
return cli.ShowCommandHelp(ctx, "add")
}
parts := strings.Split(ctx.Args().First(), "@")
if len(parts) != 2 {
return errors.New("expected tower of format pubkey@address")
}
pubKey, err := hex.DecodeString(parts[0])
if err != nil {
return fmt.Errorf("invalid public key: %w", err)
}
address := parts[1]
client, cleanUp := getWtclient(ctx)
defer cleanUp()
req := &wtclientrpc.AddTowerRequest{
Pubkey: pubKey,
Address: address,
}
resp, err := client.AddTower(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var deactivateTowerCommand = cli.Command{
Name: "deactivate",
Usage: "Deactivate a watchtower to temporarily prevent its use for " +
"sessions/backups.",
ArgsUsage: "pubkey",
Action: actionDecorator(deactivateTower),
}
func deactivateTower(ctx *cli.Context) error {
ctxc := getContext()
// Display the command's help message if the number of arguments/flags
// is not what we expect.
if ctx.NArg() != 1 || ctx.NumFlags() > 0 {
return cli.ShowCommandHelp(ctx, "deactivate")
}
pubKey, err := hex.DecodeString(ctx.Args().First())
if err != nil {
return fmt.Errorf("invalid public key: %w", err)
}
client, cleanUp := getWtclient(ctx)
defer cleanUp()
req := &wtclientrpc.DeactivateTowerRequest{
Pubkey: pubKey,
}
resp, err := client.DeactivateTower(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var removeTowerCommand = cli.Command{
Name: "remove",
Usage: "Remove a watchtower to prevent its use for future " +
"sessions/backups.",
Description: "An optional address can be provided to remove, " +
"indicating that the watchtower is no longer reachable at " +
"this address. If an address isn't provided, then the " +
"watchtower will no longer be used for future sessions/backups.",
ArgsUsage: "pubkey | pubkey@address",
Action: actionDecorator(removeTower),
}
func removeTower(ctx *cli.Context) error {
ctxc := getContext()
// Display the command's help message if the number of arguments/flags
// is not what we expect.
if ctx.NArg() != 1 || ctx.NumFlags() > 0 {
return cli.ShowCommandHelp(ctx, "remove")
}
// The command can have only one argument, but it can be interpreted in
// either of the following formats:
//
// pubkey or pubkey@address
//
// The hex-encoded public key of the watchtower is always required,
// while the second is an optional address we'll remove from the
// watchtower's database record.
parts := strings.Split(ctx.Args().First(), "@")
if len(parts) > 2 {
return errors.New("expected tower of format pubkey@address")
}
pubKey, err := hex.DecodeString(parts[0])
if err != nil {
return fmt.Errorf("invalid public key: %w", err)
}
var address string
if len(parts) == 2 {
address = parts[1]
}
client, cleanUp := getWtclient(ctx)
defer cleanUp()
req := &wtclientrpc.RemoveTowerRequest{
Pubkey: pubKey,
Address: address,
}
resp, err := client.RemoveTower(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var listTowersCommand = cli.Command{
Name: "towers",
Usage: "Display information about all registered watchtowers.",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "include_sessions",
Usage: "include sessions with the watchtower in the " +
"response",
},
cli.BoolFlag{
Name: "exclude_exhausted_sessions",
Usage: "Whether to exclude exhausted sessions in " +
"the response info. This option is only " +
"meaningful if include_sessions is true",
},
},
Action: actionDecorator(listTowers),
}
func listTowers(ctx *cli.Context) error {
ctxc := getContext()
// Display the command's help message if the number of arguments/flags
// is not what we expect.
if ctx.NArg() > 0 || ctx.NumFlags() > 2 {
return cli.ShowCommandHelp(ctx, "towers")
}
client, cleanUp := getWtclient(ctx)
defer cleanUp()
req := &wtclientrpc.ListTowersRequest{
IncludeSessions: ctx.Bool("include_sessions"),
ExcludeExhaustedSessions: ctx.Bool(
"exclude_exhausted_sessions",
),
}
resp, err := client.ListTowers(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var getTowerCommand = cli.Command{
Name: "tower",
Usage: "Display information about a specific registered watchtower.",
ArgsUsage: "pubkey",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "include_sessions",
Usage: "include sessions with the watchtower in the " +
"response",
},
cli.BoolFlag{
Name: "exclude_exhausted_sessions",
Usage: "Whether to exclude exhausted sessions in " +
"the response info. This option is only " +
"meaningful if include_sessions is true",
},
},
Action: actionDecorator(getTower),
}
func getTower(ctx *cli.Context) error {
ctxc := getContext()
// Display the command's help message if the number of arguments/flags
// is not what we expect.
if ctx.NArg() != 1 || ctx.NumFlags() > 2 {
return cli.ShowCommandHelp(ctx, "tower")
}
// The command only has one argument, which we expect to be the
// hex-encoded public key of the watchtower we'll display information
// about.
pubKey, err := hex.DecodeString(ctx.Args().Get(0))
if err != nil {
return fmt.Errorf("invalid public key: %w", err)
}
client, cleanUp := getWtclient(ctx)
defer cleanUp()
req := &wtclientrpc.GetTowerInfoRequest{
Pubkey: pubKey,
IncludeSessions: ctx.Bool("include_sessions"),
ExcludeExhaustedSessions: ctx.Bool(
"exclude_exhausted_sessions",
),
}
resp, err := client.GetTowerInfo(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var statsCommand = cli.Command{
Name: "stats",
Usage: "Display the session stats of the watchtower client.",
Action: actionDecorator(stats),
}
func stats(ctx *cli.Context) error {
ctxc := getContext()
// Display the command's help message if the number of arguments/flags
// is not what we expect.
if ctx.NArg() > 0 || ctx.NumFlags() > 0 {
return cli.ShowCommandHelp(ctx, "stats")
}
client, cleanUp := getWtclient(ctx)
defer cleanUp()
req := &wtclientrpc.StatsRequest{}
resp, err := client.Stats(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var policyCommand = cli.Command{
Name: "policy",
Usage: "Display the active watchtower client policy configuration.",
Action: actionDecorator(policy),
Flags: []cli.Flag{
cli.BoolFlag{
Name: "legacy",
Usage: "Retrieve the legacy tower client's current " +
"policy. (default)",
},
cli.BoolFlag{
Name: "anchor",
Usage: "Retrieve the anchor tower client's current " +
"policy.",
},
cli.BoolFlag{
Name: "taproot",
Usage: "Retrieve the taproot tower client's current " +
"policy.",
},
},
}
func policy(ctx *cli.Context) error {
ctxc := getContext()
// Display the command's help message if the number of arguments/flags
// is not what we expect.
if ctx.NArg() > 0 || ctx.NumFlags() > 1 {
return cli.ShowCommandHelp(ctx, "policy")
}
var policyType wtclientrpc.PolicyType
switch {
case ctx.Bool("anchor"):
policyType = wtclientrpc.PolicyType_ANCHOR
case ctx.Bool("legacy"):
policyType = wtclientrpc.PolicyType_LEGACY
case ctx.Bool("taproot"):
policyType = wtclientrpc.PolicyType_TAPROOT
// For backwards compatibility with original rpc behavior.
default:
policyType = wtclientrpc.PolicyType_LEGACY
}
client, cleanUp := getWtclient(ctx)
defer cleanUp()
req := &wtclientrpc.PolicyRequest{
PolicyType: policyType,
}
resp, err := client.Policy(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var sessionCommands = cli.Command{
Name: "session",
Subcommands: []cli.Command{
terminateSessionCommand,
},
}
var terminateSessionCommand = cli.Command{
Name: "terminate",
ArgsUsage: "id",
Action: actionDecorator(terminateSession),
}
func terminateSession(ctx *cli.Context) error {
ctxc := getContext()
// Display the command's help message if the number of arguments/flags
// is not what we expect.
if ctx.NArg() > 1 || ctx.NumFlags() != 0 {
return cli.ShowCommandHelp(ctx, "terminate")
}
client, cleanUp := getWtclient(ctx)
defer cleanUp()
sessionID, err := hex.DecodeString(ctx.Args().First())
if err != nil {
return fmt.Errorf("invalid session ID: %w", err)
}
resp, err := client.TerminateSession(
ctxc, &wtclientrpc.TerminateSessionRequest{
SessionId: sessionID,
},
)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Copyright (C) 2015-2024 The Lightning Network Developers
package main
import "github.com/lightningnetwork/lnd/cmd/commands"
func main() {
commands.Main()
}
package main
import (
"fmt"
"os"
"github.com/jessevdk/go-flags"
"github.com/lightningnetwork/lnd"
"github.com/lightningnetwork/lnd/signal"
)
func main() {
// Hook interceptor for os signals.
shutdownInterceptor, err := signal.Intercept()
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// Load the configuration, and parse any command line options. This
// function will also set up logging properly.
loadedConfig, err := lnd.LoadConfig(shutdownInterceptor)
if err != nil {
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
// Print error if not due to help request.
err = fmt.Errorf("failed to load config: %w", err)
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// Help was requested, exit normally.
os.Exit(0)
}
implCfg := loadedConfig.ImplementationConfig(shutdownInterceptor)
// Call the "real" main in a nested manner so the defers will properly
// be executed in the case of a graceful shutdown.
if err = lnd.Main(
loadedConfig, lnd.ListenerCfg{}, implCfg, shutdownInterceptor,
); err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Copyright (C) 2015-2022 The Lightning Network Developers
package lnd
import (
"encoding/hex"
"errors"
"fmt"
"io"
"net"
"os"
"os/user"
"path/filepath"
"reflect"
"regexp"
"strconv"
"strings"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
flags "github.com/jessevdk/go-flags"
"github.com/lightninglabs/neutrino"
"github.com/lightningnetwork/lnd/autopilot"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/discovery"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/htlcswitch/hodl"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/peersrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/signal"
"github.com/lightningnetwork/lnd/tor"
)
const (
defaultDataDirname = "data"
defaultChainSubDirname = "chain"
defaultGraphSubDirname = "graph"
defaultTowerSubDirname = "watchtower"
defaultTLSCertFilename = "tls.cert"
defaultTLSKeyFilename = "tls.key"
defaultAdminMacFilename = "admin.macaroon"
defaultReadMacFilename = "readonly.macaroon"
defaultInvoiceMacFilename = "invoice.macaroon"
defaultLogLevel = "info"
defaultLogDirname = "logs"
defaultLogFilename = "lnd.log"
defaultRPCPort = 10009
defaultRESTPort = 8080
defaultPeerPort = 9735
defaultRPCHost = "localhost"
defaultNoSeedBackup = false
defaultPaymentsExpirationGracePeriod = time.Duration(0)
defaultTrickleDelay = 90 * 1000
defaultChanStatusSampleInterval = time.Minute
defaultChanEnableTimeout = 19 * time.Minute
defaultChanDisableTimeout = 20 * time.Minute
defaultHeightHintCacheQueryDisable = false
defaultMinBackoff = time.Second
defaultMaxBackoff = time.Hour
defaultLetsEncryptDirname = "letsencrypt"
defaultLetsEncryptListen = ":80"
defaultTorSOCKSPort = 9050
defaultTorDNSHost = "soa.nodes.lightning.directory"
defaultTorDNSPort = 53
defaultTorControlPort = 9051
defaultTorV2PrivateKeyFilename = "v2_onion_private_key"
defaultTorV3PrivateKeyFilename = "v3_onion_private_key"
// defaultZMQReadDeadline is the default read deadline to be used for
// both the block and tx ZMQ subscriptions.
defaultZMQReadDeadline = 5 * time.Second
// DefaultAutogenValidity is the default validity of a self-signed
// certificate. The value corresponds to 14 months
// (14 months * 30 days * 24 hours).
defaultTLSCertDuration = 14 * 30 * 24 * time.Hour
// minTimeLockDelta is the minimum timelock we require for incoming
// HTLCs on our channels.
minTimeLockDelta = routing.MinCLTVDelta
// MaxTimeLockDelta is the maximum CLTV delta that can be applied to
// forwarded HTLCs.
MaxTimeLockDelta = routing.MaxCLTVDelta
// defaultAcceptorTimeout is the time after which an RPCAcceptor will time
// out and return false if it hasn't yet received a response.
defaultAcceptorTimeout = 15 * time.Second
defaultAlias = ""
defaultColor = "#3399FF"
// defaultCoopCloseTargetConfs is the default confirmation target
// that will be used to estimate a fee rate to use during a
// cooperative channel closure initiated by a remote peer. By default
// we'll set this to a lax value since we weren't the ones that
// initiated the channel closure.
defaultCoopCloseTargetConfs = 6
// defaultBlockCacheSize is the size (in bytes) of blocks that will be
// keep in memory if no size is specified.
defaultBlockCacheSize uint64 = 20 * 1024 * 1024 // 20 MB
// defaultHostSampleInterval is the default amount of time that the
// HostAnnouncer will wait between DNS resolutions to check if the
// backing IP of a host has changed.
defaultHostSampleInterval = time.Minute * 5
defaultChainInterval = time.Minute
defaultChainTimeout = time.Second * 30
defaultChainBackoff = time.Minute * 2
defaultChainAttempts = 3
// Set defaults for a health check which ensures that we have space
// available on disk. Although this check is off by default so that we
// avoid breaking any existing setups (particularly on mobile), we still
// set the other default values so that the health check can be easily
// enabled with sane defaults.
defaultRequiredDisk = 0.1
defaultDiskInterval = time.Hour * 12
defaultDiskTimeout = time.Second * 5
defaultDiskBackoff = time.Minute
defaultDiskAttempts = 0
// Set defaults for a health check which ensures that the TLS certificate
// is not expired. Although this check is off by default (not all setups
// require it), we still set the other default values so that the health
// check can be easily enabled with sane defaults.
defaultTLSInterval = time.Minute
defaultTLSTimeout = time.Second * 5
defaultTLSBackoff = time.Minute
defaultTLSAttempts = 0
// Set defaults for a health check which ensures that the tor
// connection is alive. Although this check is off by default (not all
// setups require it), we still set the other default values so that
// the health check can be easily enabled with sane defaults.
defaultTCInterval = time.Minute
defaultTCTimeout = time.Second * 5
defaultTCBackoff = time.Minute
defaultTCAttempts = 0
// Set defaults for a health check which ensures that the remote signer
// RPC connection is alive. Although this check is off by default (only
// active when remote signing is turned on), we still set the other
// default values so that the health check can be easily enabled with
// sane defaults.
defaultRSInterval = time.Minute
defaultRSTimeout = time.Second * 1
defaultRSBackoff = time.Second * 30
defaultRSAttempts = 1
// Set defaults for a health check which ensures that the leader
// election is functioning correctly. Although this check is off by
// default (as etcd leader election is only used in a clustered setup),
// we still set the default values so that the health check can be
// easily enabled with sane defaults. Note that by default we only run
// this check once, as it is critical for the node's operation.
defaultLeaderCheckInterval = time.Minute
defaultLeaderCheckTimeout = time.Second * 5
defaultLeaderCheckBackoff = time.Second * 5
defaultLeaderCheckAttempts = 1
// defaultRemoteMaxHtlcs specifies the default limit for maximum
// concurrent HTLCs the remote party may add to commitment transactions.
// This value can be overridden with --default-remote-max-htlcs.
defaultRemoteMaxHtlcs = 483
// defaultMaxLocalCSVDelay is the maximum delay we accept on our
// commitment output. The local csv delay maximum is now equal to
// the remote csv delay maximum we require for the remote commitment
// transaction.
defaultMaxLocalCSVDelay = 2016
// defaultChannelCommitInterval is the default maximum time between
// receiving a channel state update and signing a new commitment.
defaultChannelCommitInterval = 50 * time.Millisecond
// maxChannelCommitInterval is the maximum time the commit interval can
// be configured to.
maxChannelCommitInterval = time.Hour
// defaultPendingCommitInterval specifies the default timeout value
// while waiting for the remote party to revoke a locally initiated
// commitment state.
defaultPendingCommitInterval = 1 * time.Minute
// maxPendingCommitInterval specifies the max allowed duration when
// waiting for the remote party to revoke a locally initiated
// commitment state.
maxPendingCommitInterval = 5 * time.Minute
// defaultChannelCommitBatchSize is the default maximum number of
// channel state updates that is accumulated before signing a new
// commitment.
defaultChannelCommitBatchSize = 10
// defaultCoinSelectionStrategy is the coin selection strategy that is
// used by default to fund transactions.
defaultCoinSelectionStrategy = "largest"
// defaultKeepFailedPaymentAttempts is the default setting for whether
// to keep failed payments in the database.
defaultKeepFailedPaymentAttempts = false
// defaultGrpcServerPingTime is the default duration for the amount of
// time of no activity after which the server pings the client to see if
// the transport is still alive. If set below 1s, a minimum value of 1s
// will be used instead.
defaultGrpcServerPingTime = time.Minute
// defaultGrpcServerPingTimeout is the default duration the server waits
// after having pinged for keepalive check, and if no activity is seen
// even after that the connection is closed.
defaultGrpcServerPingTimeout = 20 * time.Second
// defaultGrpcClientPingMinWait is the default minimum amount of time a
// client should wait before sending a keepalive ping.
defaultGrpcClientPingMinWait = 5 * time.Second
// defaultHTTPHeaderTimeout is the default timeout for HTTP requests.
DefaultHTTPHeaderTimeout = 5 * time.Second
// DefaultNumRestrictedSlots is the default number of restricted slots
// we'll allocate in the server.
DefaultNumRestrictedSlots = 30
// BitcoinChainName is a string that represents the Bitcoin blockchain.
BitcoinChainName = "bitcoin"
bitcoindBackendName = "bitcoind"
btcdBackendName = "btcd"
neutrinoBackendName = "neutrino"
defaultPrunedNodeMaxPeers = 4
defaultNeutrinoMaxPeers = 8
)
var (
// DefaultLndDir is the default directory where lnd tries to find its
// configuration file and store its data. This is a directory in the
// user's application data, for example:
// C:\Users\<username>\AppData\Local\Lnd on Windows
// ~/.lnd on Linux
// ~/Library/Application Support/Lnd on MacOS
DefaultLndDir = btcutil.AppDataDir("lnd", false)
// DefaultConfigFile is the default full path of lnd's configuration
// file.
DefaultConfigFile = filepath.Join(DefaultLndDir, lncfg.DefaultConfigFilename)
defaultDataDir = filepath.Join(DefaultLndDir, defaultDataDirname)
defaultLogDir = filepath.Join(DefaultLndDir, defaultLogDirname)
defaultTowerDir = filepath.Join(defaultDataDir, defaultTowerSubDirname)
defaultTLSCertPath = filepath.Join(DefaultLndDir, defaultTLSCertFilename)
defaultTLSKeyPath = filepath.Join(DefaultLndDir, defaultTLSKeyFilename)
defaultLetsEncryptDir = filepath.Join(DefaultLndDir, defaultLetsEncryptDirname)
defaultBtcdDir = btcutil.AppDataDir(btcdBackendName, false)
defaultBtcdRPCCertFile = filepath.Join(defaultBtcdDir, "rpc.cert")
defaultBitcoindDir = btcutil.AppDataDir(BitcoinChainName, false)
defaultTorSOCKS = net.JoinHostPort("localhost", strconv.Itoa(defaultTorSOCKSPort))
defaultTorDNS = net.JoinHostPort(defaultTorDNSHost, strconv.Itoa(defaultTorDNSPort))
defaultTorControl = net.JoinHostPort("localhost", strconv.Itoa(defaultTorControlPort))
// bitcoindEsimateModes defines all the legal values for bitcoind's
// estimatesmartfee RPC call.
defaultBitcoindEstimateMode = "CONSERVATIVE"
bitcoindEstimateModes = [2]string{"ECONOMICAL", defaultBitcoindEstimateMode}
)
// Config defines the configuration options for lnd.
//
// See LoadConfig for further details regarding the configuration
// loading+parsing process.
//
//nolint:ll
type Config struct {
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
LndDir string `long:"lnddir" description:"The base directory that contains lnd's data, logs, configuration file, etc. This option overwrites all other directory options."`
ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"`
DataDir string `short:"b" long:"datadir" description:"The directory to store lnd's data within"`
SyncFreelist bool `long:"sync-freelist" description:"Whether the databases used within lnd should sync their freelist to disk. This is disabled by default resulting in improved memory performance during operation, but with an increase in startup time."`
TLSCertPath string `long:"tlscertpath" description:"Path to write the TLS certificate for lnd's RPC and REST services"`
TLSKeyPath string `long:"tlskeypath" description:"Path to write the TLS private key for lnd's RPC and REST services"`
TLSExtraIPs []string `long:"tlsextraip" description:"Adds an extra ip to the generated certificate"`
TLSExtraDomains []string `long:"tlsextradomain" description:"Adds an extra domain to the generated certificate"`
TLSAutoRefresh bool `long:"tlsautorefresh" description:"Re-generate TLS certificate and key if the IPs or domains are changed"`
TLSDisableAutofill bool `long:"tlsdisableautofill" description:"Do not include the interface IPs or the system hostname in TLS certificate, use first --tlsextradomain as Common Name instead, if set"`
TLSCertDuration time.Duration `long:"tlscertduration" description:"The duration for which the auto-generated TLS certificate will be valid for"`
TLSEncryptKey bool `long:"tlsencryptkey" description:"Automatically encrypts the TLS private key and generates ephemeral TLS key pairs when the wallet is locked or not initialized"`
NoMacaroons bool `long:"no-macaroons" description:"Disable macaroon authentication, can only be used if server is not listening on a public interface."`
AdminMacPath string `long:"adminmacaroonpath" description:"Path to write the admin macaroon for lnd's RPC and REST services if it doesn't exist"`
ReadMacPath string `long:"readonlymacaroonpath" description:"Path to write the read-only macaroon for lnd's RPC and REST services if it doesn't exist"`
InvoiceMacPath string `long:"invoicemacaroonpath" description:"Path to the invoice-only macaroon for lnd's RPC and REST services if it doesn't exist"`
LogDir string `long:"logdir" description:"Directory to log output."`
MaxLogFiles int `long:"maxlogfiles" description:"Maximum logfiles to keep (0 for no rotation). DEPRECATED: use --logging.file.max-files instead" hidden:"true"`
MaxLogFileSize int `long:"maxlogfilesize" description:"Maximum logfile size in MB. DEPRECATED: use --logging.file.max-file-size instead" hidden:"true"`
AcceptorTimeout time.Duration `long:"acceptortimeout" description:"Time after which an RPCAcceptor will time out and return false if it hasn't yet received a response"`
LetsEncryptDir string `long:"letsencryptdir" description:"The directory to store Let's Encrypt certificates within"`
LetsEncryptListen string `long:"letsencryptlisten" description:"The IP:port on which lnd will listen for Let's Encrypt challenges. Let's Encrypt will always try to contact on port 80. Often non-root processes are not allowed to bind to ports lower than 1024. This configuration option allows a different port to be used, but must be used in combination with port forwarding from port 80. This configuration can also be used to specify another IP address to listen on, for example an IPv6 address."`
LetsEncryptDomain string `long:"letsencryptdomain" description:"Request a Let's Encrypt certificate for this domain. Note that the certificate is only requested and stored when the first rpc connection comes in."`
// We'll parse these 'raw' string arguments into real net.Addrs in the
// loadConfig function. We need to expose the 'raw' strings so the
// command line library can access them.
// Only the parsed net.Addrs should be used!
RawRPCListeners []string `long:"rpclisten" description:"Add an interface/port/socket to listen for RPC connections"`
RawRESTListeners []string `long:"restlisten" description:"Add an interface/port/socket to listen for REST connections"`
RawListeners []string `long:"listen" description:"Add an interface/port to listen for peer connections"`
RawExternalIPs []string `long:"externalip" description:"Add an ip:port to the list of local addresses we claim to listen on to peers. If a port is not specified, the default (9735) will be used regardless of other parameters"`
ExternalHosts []string `long:"externalhosts" description:"Add a hostname:port that should be periodically resolved to announce IPs for. If a port is not specified, the default (9735) will be used."`
RPCListeners []net.Addr
RESTListeners []net.Addr
RestCORS []string `long:"restcors" description:"Add an ip:port/hostname to allow cross origin access from. To allow all origins, set as \"*\"."`
Listeners []net.Addr
ExternalIPs []net.Addr
DisableListen bool `long:"nolisten" description:"Disable listening for incoming peer connections"`
DisableRest bool `long:"norest" description:"Disable REST API"`
DisableRestTLS bool `long:"no-rest-tls" description:"Disable TLS for REST connections"`
WSPingInterval time.Duration `long:"ws-ping-interval" description:"The ping interval for REST based WebSocket connections, set to 0 to disable sending ping messages from the server side"`
WSPongWait time.Duration `long:"ws-pong-wait" description:"The time we wait for a pong response message on REST based WebSocket connections before the connection is closed as inactive"`
NAT bool `long:"nat" description:"Toggle NAT traversal support (using either UPnP or NAT-PMP) to automatically advertise your external IP address to the network -- NOTE this does not support devices behind multiple NATs"`
AddPeers []string `long:"addpeer" description:"Specify peers to connect to first"`
MinBackoff time.Duration `long:"minbackoff" description:"Shortest backoff when reconnecting to persistent peers. Valid time units are {s, m, h}."`
MaxBackoff time.Duration `long:"maxbackoff" description:"Longest backoff when reconnecting to persistent peers. Valid time units are {s, m, h}."`
ConnectionTimeout time.Duration `long:"connectiontimeout" description:"The timeout value for network connections. Valid time units are {ms, s, m, h}."`
DebugLevel string `short:"d" long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify <global-level>,<subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems"`
CPUProfile string `long:"cpuprofile" description:"DEPRECATED: Use 'pprof.cpuprofile' option. Write CPU profile to the specified file" hidden:"true"`
Profile string `long:"profile" description:"DEPRECATED: Use 'pprof.profile' option. Enable HTTP profiling on either a port or host:port" hidden:"true"`
BlockingProfile int `long:"blockingprofile" description:"DEPRECATED: Use 'pprof.blockingprofile' option. Used to enable a blocking profile to be served on the profiling port. This takes a value from 0 to 1, with 1 including every blocking event, and 0 including no events." hidden:"true"`
MutexProfile int `long:"mutexprofile" description:"DEPRECATED: Use 'pprof.mutexprofile' option. Used to Enable a mutex profile to be served on the profiling port. This takes a value from 0 to 1, with 1 including every mutex event, and 0 including no events." hidden:"true"`
Pprof *lncfg.Pprof `group:"Pprof" namespace:"pprof"`
UnsafeDisconnect bool `long:"unsafe-disconnect" description:"DEPRECATED: Allows the rpcserver to intentionally disconnect from peers with open channels. THIS FLAG WILL BE REMOVED IN 0.10.0" hidden:"true"`
UnsafeReplay bool `long:"unsafe-replay" description:"Causes a link to replay the adds on its commitment txn after starting up, this enables testing of the sphinx replay logic."`
MaxPendingChannels int `long:"maxpendingchannels" description:"The maximum number of incoming pending channels permitted per peer."`
BackupFilePath string `long:"backupfilepath" description:"The target location of the channel backup file"`
NoBackupArchive bool `long:"no-backup-archive" description:"If set to true, channel backups will be deleted or replaced rather than being archived to a separate location."`
FeeURL string `long:"feeurl" description:"DEPRECATED: Use 'fee.url' option. Optional URL for external fee estimation. If no URL is specified, the method for fee estimation will depend on the chosen backend and network. Must be set for neutrino on mainnet." hidden:"true"`
Bitcoin *lncfg.Chain `group:"Bitcoin" namespace:"bitcoin"`
BtcdMode *lncfg.Btcd `group:"btcd" namespace:"btcd"`
BitcoindMode *lncfg.Bitcoind `group:"bitcoind" namespace:"bitcoind"`
NeutrinoMode *lncfg.Neutrino `group:"neutrino" namespace:"neutrino"`
BlockCacheSize uint64 `long:"blockcachesize" description:"The maximum capacity of the block cache"`
Autopilot *lncfg.AutoPilot `group:"Autopilot" namespace:"autopilot"`
Tor *lncfg.Tor `group:"Tor" namespace:"tor"`
SubRPCServers *subRPCServerConfigs `group:"subrpc"`
Hodl *hodl.Config `group:"hodl" namespace:"hodl"`
NoNetBootstrap bool `long:"nobootstrap" description:"If true, then automatic network bootstrapping will not be attempted."`
NoSeedBackup bool `long:"noseedbackup" description:"If true, NO SEED WILL BE EXPOSED -- EVER, AND THE WALLET WILL BE ENCRYPTED USING THE DEFAULT PASSPHRASE. THIS FLAG IS ONLY FOR TESTING AND SHOULD NEVER BE USED ON MAINNET."`
WalletUnlockPasswordFile string `long:"wallet-unlock-password-file" description:"The full path to a file (or pipe/device) that contains the password for unlocking the wallet; if set, no unlocking through RPC is possible and lnd will exit if no wallet exists or the password is incorrect; if wallet-unlock-allow-create is also set then lnd will ignore this flag if no wallet exists and allow a wallet to be created through RPC."`
WalletUnlockAllowCreate bool `long:"wallet-unlock-allow-create" description:"Don't fail with an error if wallet-unlock-password-file is set but no wallet exists yet."`
ResetWalletTransactions bool `long:"reset-wallet-transactions" description:"Removes all transaction history from the on-chain wallet on startup, forcing a full chain rescan starting at the wallet's birthday. Implements the same functionality as btcwallet's dropwtxmgr command. Should be set to false after successful execution to avoid rescanning on every restart of lnd."`
CoinSelectionStrategy string `long:"coin-selection-strategy" description:"The strategy to use for selecting coins for wallet transactions." choice:"largest" choice:"random"`
PaymentsExpirationGracePeriod time.Duration `long:"payments-expiration-grace-period" description:"A period to wait before force closing channels with outgoing htlcs that have timed-out and are a result of this node initiated payments."`
TrickleDelay int `long:"trickledelay" description:"Time in milliseconds between each release of announcements to the network"`
ChanEnableTimeout time.Duration `long:"chan-enable-timeout" description:"The duration that a peer connection must be stable before attempting to send a channel update to re-enable or cancel a pending disables of the peer's channels on the network."`
ChanDisableTimeout time.Duration `long:"chan-disable-timeout" description:"The duration that must elapse after first detecting that an already active channel is actually inactive and sending channel update disabling it to the network. The pending disable can be canceled if the peer reconnects and becomes stable for chan-enable-timeout before the disable update is sent."`
ChanStatusSampleInterval time.Duration `long:"chan-status-sample-interval" description:"The polling interval between attempts to detect if an active channel has become inactive due to its peer going offline."`
HeightHintCacheQueryDisable bool `long:"height-hint-cache-query-disable" description:"Disable queries from the height-hint cache to try to recover channels stuck in the pending close state. Disabling height hint queries may cause longer chain rescans, resulting in a performance hit. Unset this after channels are unstuck so you can get better performance again."`
Alias string `long:"alias" description:"The node alias. Used as a moniker by peers and intelligence services"`
Color string `long:"color" description:"The color of the node in hex format (i.e. '#3399FF'). Used to customize node appearance in intelligence services"`
MinChanSize int64 `long:"minchansize" description:"The smallest channel size (in satoshis) that we should accept. Incoming channels smaller than this will be rejected"`
MaxChanSize int64 `long:"maxchansize" description:"The largest channel size (in satoshis) that we should accept. Incoming channels larger than this will be rejected"`
CoopCloseTargetConfs uint32 `long:"coop-close-target-confs" description:"The target number of blocks that a cooperative channel close transaction should confirm in. This is used to estimate the fee to use as the lower bound during fee negotiation for the channel closure."`
ChannelCommitInterval time.Duration `long:"channel-commit-interval" description:"The maximum time that is allowed to pass between receiving a channel state update and signing the next commitment. Setting this to a longer duration allows for more efficient channel operations at the cost of latency."`
PendingCommitInterval time.Duration `long:"pending-commit-interval" description:"The maximum time that is allowed to pass while waiting for the remote party to revoke a locally initiated commitment state. Setting this to a longer duration if a slow response is expected from the remote party or large number of payments are attempted at the same time."`
ChannelCommitBatchSize uint32 `long:"channel-commit-batch-size" description:"The maximum number of channel state updates that is accumulated before signing a new commitment."`
KeepFailedPaymentAttempts bool `long:"keep-failed-payment-attempts" description:"Keeps persistent record of all failed payment attempts for successfully settled payments."`
StoreFinalHtlcResolutions bool `long:"store-final-htlc-resolutions" description:"Persistently store the final resolution of incoming htlcs."`
DefaultRemoteMaxHtlcs uint16 `long:"default-remote-max-htlcs" description:"The default max_htlc applied when opening or accepting channels. This value limits the number of concurrent HTLCs that the remote party can add to the commitment. The maximum possible value is 483."`
NumGraphSyncPeers int `long:"numgraphsyncpeers" description:"The number of peers that we should receive new graph updates from. This option can be tuned to save bandwidth for light clients or routing nodes."`
HistoricalSyncInterval time.Duration `long:"historicalsyncinterval" description:"The polling interval between historical graph sync attempts. Each historical graph sync attempt ensures we reconcile with the remote peer's graph from the genesis block."`
IgnoreHistoricalGossipFilters bool `long:"ignore-historical-gossip-filters" description:"If true, will not reply with historical data that matches the range specified by a remote peer's gossip_timestamp_filter. Doing so will result in lower memory and bandwidth requirements."`
RejectPush bool `long:"rejectpush" description:"If true, lnd will not accept channel opening requests with non-zero push amounts. This should prevent accidental pushes to merchant nodes."`
RejectHTLC bool `long:"rejecthtlc" description:"If true, lnd will not forward any HTLCs that are meant as onward payments. This option will still allow lnd to send HTLCs and receive HTLCs but lnd won't be used as a hop."`
AcceptPositiveInboundFees bool `long:"accept-positive-inbound-fees" description:"If true, lnd will also allow setting positive inbound fees. By default, lnd only allows to set negative inbound fees (an inbound \"discount\") to remain backwards compatible with senders whose implementations do not yet support inbound fees."`
// RequireInterceptor determines whether the HTLC interceptor is
// registered regardless of whether the RPC is called or not.
RequireInterceptor bool `long:"requireinterceptor" description:"Whether to always intercept HTLCs, even if no stream is attached"`
StaggerInitialReconnect bool `long:"stagger-initial-reconnect" description:"If true, will apply a randomized staggering between 0s and 30s when reconnecting to persistent peers on startup. The first 10 reconnections will be attempted instantly, regardless of the flag's value"`
MaxOutgoingCltvExpiry uint32 `long:"max-cltv-expiry" description:"The maximum number of blocks funds could be locked up for when forwarding payments."`
MaxChannelFeeAllocation float64 `long:"max-channel-fee-allocation" description:"The maximum percentage of total funds that can be allocated to a channel's commitment fee. This only applies for the initiator of the channel. Valid values are within [0.1, 1]."`
MaxCommitFeeRateAnchors uint64 `long:"max-commit-fee-rate-anchors" description:"The maximum fee rate in sat/vbyte that will be used for commitments of channels of the anchors type. Must be large enough to ensure transaction propagation"`
DryRunMigration bool `long:"dry-run-migration" description:"If true, lnd will abort committing a migration if it would otherwise have been successful. This leaves the database unmodified, and still compatible with the previously active version of lnd."`
net tor.Net
EnableUpfrontShutdown bool `long:"enable-upfront-shutdown" description:"If true, option upfront shutdown script will be enabled. If peers that we open channels with support this feature, we will automatically set the script to which cooperative closes should be paid out to on channel open. This offers the partial protection of a channel peer disconnecting from us if cooperative close is attempted with a different script."`
AcceptKeySend bool `long:"accept-keysend" description:"If true, spontaneous payments through keysend will be accepted. [experimental]"`
AcceptAMP bool `long:"accept-amp" description:"If true, spontaneous payments via AMP will be accepted."`
KeysendHoldTime time.Duration `long:"keysend-hold-time" description:"If non-zero, keysend payments are accepted but not immediately settled. If the payment isn't settled manually after the specified time, it is canceled automatically. [experimental]"`
GcCanceledInvoicesOnStartup bool `long:"gc-canceled-invoices-on-startup" description:"If true, we'll attempt to garbage collect canceled invoices upon start."`
GcCanceledInvoicesOnTheFly bool `long:"gc-canceled-invoices-on-the-fly" description:"If true, we'll delete newly canceled invoices on the fly."`
DustThreshold uint64 `long:"dust-threshold" description:"DEPRECATED: Sets the max fee exposure in satoshis for a channel after which HTLC's will be failed." hidden:"true"`
MaxFeeExposure uint64 `long:"channel-max-fee-exposure" description:" Limits the maximum fee exposure in satoshis of a channel. This value is enforced for all channels and is independent of the channel initiator."`
Fee *lncfg.Fee `group:"fee" namespace:"fee"`
Invoices *lncfg.Invoices `group:"invoices" namespace:"invoices"`
Routing *lncfg.Routing `group:"routing" namespace:"routing"`
Gossip *lncfg.Gossip `group:"gossip" namespace:"gossip"`
Workers *lncfg.Workers `group:"workers" namespace:"workers"`
Caches *lncfg.Caches `group:"caches" namespace:"caches"`
Prometheus lncfg.Prometheus `group:"prometheus" namespace:"prometheus"`
WtClient *lncfg.WtClient `group:"wtclient" namespace:"wtclient"`
Watchtower *lncfg.Watchtower `group:"watchtower" namespace:"watchtower"`
ProtocolOptions *lncfg.ProtocolOptions `group:"protocol" namespace:"protocol"`
AllowCircularRoute bool `long:"allow-circular-route" description:"If true, our node will allow htlc forwards that arrive and depart on the same channel."`
HealthChecks *lncfg.HealthCheckConfig `group:"healthcheck" namespace:"healthcheck"`
DB *lncfg.DB `group:"db" namespace:"db"`
Cluster *lncfg.Cluster `group:"cluster" namespace:"cluster"`
RPCMiddleware *lncfg.RPCMiddleware `group:"rpcmiddleware" namespace:"rpcmiddleware"`
RemoteSigner *lncfg.RemoteSigner `group:"remotesigner" namespace:"remotesigner"`
Sweeper *lncfg.Sweeper `group:"sweeper" namespace:"sweeper"`
Htlcswitch *lncfg.Htlcswitch `group:"htlcswitch" namespace:"htlcswitch"`
GRPC *GRPCConfig `group:"grpc" namespace:"grpc"`
// SubLogMgr is the root logger that all the daemon's subloggers are
// hooked up to.
SubLogMgr *build.SubLoggerManager
LogRotator *build.RotatingLogWriter
LogConfig *build.LogConfig `group:"logging" namespace:"logging"`
// networkDir is the path to the directory of the currently active
// network. This path will hold the files related to each different
// network.
networkDir string
// ActiveNetParams contains parameters of the target chain.
ActiveNetParams chainreg.BitcoinNetParams
// Estimator is used to estimate routing probabilities.
Estimator routing.Estimator
// Dev specifies configs used for integration tests, which is always
// empty if not built with `integration` flag.
Dev *lncfg.DevConfig `group:"dev" namespace:"dev"`
// HTTPHeaderTimeout is the maximum duration that the server will wait
// before timing out reading the headers of an HTTP request.
HTTPHeaderTimeout time.Duration `long:"http-header-timeout" description:"The maximum duration that the server will wait before timing out reading the headers of an HTTP request."`
// NumRestrictedSlots is the number of restricted slots we'll allocate
// in the server.
NumRestrictedSlots uint64 `long:"num-restricted-slots" description:"The number of restricted slots we'll allocate in the server."`
}
// GRPCConfig holds the configuration options for the gRPC server.
// See https://github.com/grpc/grpc-go/blob/v1.41.0/keepalive/keepalive.go#L50
// for more details. Any value of 0 means we use the gRPC internal default
// values.
//
//nolint:ll
type GRPCConfig struct {
// ServerPingTime is a duration for the amount of time of no activity
// after which the server pings the client to see if the transport is
// still alive. If set below 1s, a minimum value of 1s will be used
// instead.
ServerPingTime time.Duration `long:"server-ping-time" description:"How long the server waits on a gRPC stream with no activity before pinging the client."`
// ServerPingTimeout is the duration the server waits after having
// pinged for keepalive check, and if no activity is seen even after
// that the connection is closed.
ServerPingTimeout time.Duration `long:"server-ping-timeout" description:"How long the server waits for the response from the client for the keepalive ping response."`
// ClientPingMinWait is the minimum amount of time a client should wait
// before sending a keepalive ping.
ClientPingMinWait time.Duration `long:"client-ping-min-wait" description:"The minimum amount of time the client should wait before sending a keepalive ping."`
// ClientAllowPingWithoutStream specifies whether pings from the client
// are allowed even if there are no active gRPC streams. This might be
// useful to keep the underlying HTTP/2 connection open for future
// requests.
ClientAllowPingWithoutStream bool `long:"client-allow-ping-without-stream" description:"If true, the server allows keepalive pings from the client even when there are no active gRPC streams. This might be useful to keep the underlying HTTP/2 connection open for future requests."`
}
// DefaultConfig returns all default values for the Config struct.
//
//nolint:ll
func DefaultConfig() Config {
return Config{
LndDir: DefaultLndDir,
ConfigFile: DefaultConfigFile,
DataDir: defaultDataDir,
DebugLevel: defaultLogLevel,
TLSCertPath: defaultTLSCertPath,
TLSKeyPath: defaultTLSKeyPath,
TLSCertDuration: defaultTLSCertDuration,
LetsEncryptDir: defaultLetsEncryptDir,
LetsEncryptListen: defaultLetsEncryptListen,
LogDir: defaultLogDir,
AcceptorTimeout: defaultAcceptorTimeout,
WSPingInterval: lnrpc.DefaultPingInterval,
WSPongWait: lnrpc.DefaultPongWait,
Bitcoin: &lncfg.Chain{
MinHTLCIn: chainreg.DefaultBitcoinMinHTLCInMSat,
MinHTLCOut: chainreg.DefaultBitcoinMinHTLCOutMSat,
BaseFee: chainreg.DefaultBitcoinBaseFeeMSat,
FeeRate: chainreg.DefaultBitcoinFeeRate,
TimeLockDelta: chainreg.DefaultBitcoinTimeLockDelta,
MaxLocalDelay: defaultMaxLocalCSVDelay,
Node: btcdBackendName,
},
BtcdMode: &lncfg.Btcd{
Dir: defaultBtcdDir,
RPCHost: defaultRPCHost,
RPCCert: defaultBtcdRPCCertFile,
},
BitcoindMode: &lncfg.Bitcoind{
Dir: defaultBitcoindDir,
RPCHost: defaultRPCHost,
EstimateMode: defaultBitcoindEstimateMode,
PrunedNodeMaxPeers: defaultPrunedNodeMaxPeers,
ZMQReadDeadline: defaultZMQReadDeadline,
},
NeutrinoMode: &lncfg.Neutrino{
UserAgentName: neutrino.UserAgentName,
UserAgentVersion: neutrino.UserAgentVersion,
MaxPeers: defaultNeutrinoMaxPeers,
},
BlockCacheSize: defaultBlockCacheSize,
MaxPendingChannels: lncfg.DefaultMaxPendingChannels,
NoSeedBackup: defaultNoSeedBackup,
MinBackoff: defaultMinBackoff,
MaxBackoff: defaultMaxBackoff,
ConnectionTimeout: tor.DefaultConnTimeout,
Fee: &lncfg.Fee{
MinUpdateTimeout: lncfg.DefaultMinUpdateTimeout,
MaxUpdateTimeout: lncfg.DefaultMaxUpdateTimeout,
},
SubRPCServers: &subRPCServerConfigs{
SignRPC: &signrpc.Config{},
RouterRPC: routerrpc.DefaultConfig(),
PeersRPC: &peersrpc.Config{},
},
Autopilot: &lncfg.AutoPilot{
MaxChannels: 5,
Allocation: 0.6,
MinChannelSize: int64(funding.MinChanFundingSize),
MaxChannelSize: int64(MaxFundingAmount),
MinConfs: 1,
ConfTarget: autopilot.DefaultConfTarget,
Heuristic: map[string]float64{
"top_centrality": 1.0,
},
},
PaymentsExpirationGracePeriod: defaultPaymentsExpirationGracePeriod,
TrickleDelay: defaultTrickleDelay,
ChanStatusSampleInterval: defaultChanStatusSampleInterval,
ChanEnableTimeout: defaultChanEnableTimeout,
ChanDisableTimeout: defaultChanDisableTimeout,
HeightHintCacheQueryDisable: defaultHeightHintCacheQueryDisable,
Alias: defaultAlias,
Color: defaultColor,
MinChanSize: int64(funding.MinChanFundingSize),
MaxChanSize: int64(0),
CoopCloseTargetConfs: defaultCoopCloseTargetConfs,
DefaultRemoteMaxHtlcs: defaultRemoteMaxHtlcs,
NumGraphSyncPeers: defaultMinPeers,
HistoricalSyncInterval: discovery.DefaultHistoricalSyncInterval,
Tor: &lncfg.Tor{
SOCKS: defaultTorSOCKS,
DNS: defaultTorDNS,
Control: defaultTorControl,
},
net: &tor.ClearNet{},
Workers: &lncfg.Workers{
Read: lncfg.DefaultReadWorkers,
Write: lncfg.DefaultWriteWorkers,
Sig: lncfg.DefaultSigWorkers,
},
Caches: &lncfg.Caches{
RejectCacheSize: channeldb.DefaultRejectCacheSize,
ChannelCacheSize: channeldb.DefaultChannelCacheSize,
},
Prometheus: lncfg.DefaultPrometheus(),
Watchtower: lncfg.DefaultWatchtowerCfg(defaultTowerDir),
HealthChecks: &lncfg.HealthCheckConfig{
ChainCheck: &lncfg.CheckConfig{
Interval: defaultChainInterval,
Timeout: defaultChainTimeout,
Attempts: defaultChainAttempts,
Backoff: defaultChainBackoff,
},
DiskCheck: &lncfg.DiskCheckConfig{
RequiredRemaining: defaultRequiredDisk,
CheckConfig: &lncfg.CheckConfig{
Interval: defaultDiskInterval,
Attempts: defaultDiskAttempts,
Timeout: defaultDiskTimeout,
Backoff: defaultDiskBackoff,
},
},
TLSCheck: &lncfg.CheckConfig{
Interval: defaultTLSInterval,
Timeout: defaultTLSTimeout,
Attempts: defaultTLSAttempts,
Backoff: defaultTLSBackoff,
},
TorConnection: &lncfg.CheckConfig{
Interval: defaultTCInterval,
Timeout: defaultTCTimeout,
Attempts: defaultTCAttempts,
Backoff: defaultTCBackoff,
},
RemoteSigner: &lncfg.CheckConfig{
Interval: defaultRSInterval,
Timeout: defaultRSTimeout,
Attempts: defaultRSAttempts,
Backoff: defaultRSBackoff,
},
LeaderCheck: &lncfg.CheckConfig{
Interval: defaultLeaderCheckInterval,
Timeout: defaultLeaderCheckTimeout,
Attempts: defaultLeaderCheckAttempts,
Backoff: defaultLeaderCheckBackoff,
},
},
Gossip: &lncfg.Gossip{
MaxChannelUpdateBurst: discovery.DefaultMaxChannelUpdateBurst,
ChannelUpdateInterval: discovery.DefaultChannelUpdateInterval,
SubBatchDelay: discovery.DefaultSubBatchDelay,
AnnouncementConf: discovery.DefaultProofMatureDelta,
MsgRateBytes: discovery.DefaultMsgBytesPerSecond,
MsgBurstBytes: discovery.DefaultMsgBytesBurst,
},
Invoices: &lncfg.Invoices{
HoldExpiryDelta: lncfg.DefaultHoldInvoiceExpiryDelta,
},
Routing: &lncfg.Routing{
BlindedPaths: lncfg.BlindedPaths{
MinNumRealHops: lncfg.DefaultMinNumRealBlindedPathHops,
NumHops: lncfg.DefaultNumBlindedPathHops,
MaxNumPaths: lncfg.DefaultMaxNumBlindedPaths,
PolicyIncreaseMultiplier: lncfg.DefaultBlindedPathPolicyIncreaseMultiplier,
PolicyDecreaseMultiplier: lncfg.DefaultBlindedPathPolicyDecreaseMultiplier,
},
},
MaxOutgoingCltvExpiry: htlcswitch.DefaultMaxOutgoingCltvExpiry,
MaxChannelFeeAllocation: htlcswitch.DefaultMaxLinkFeeAllocation,
MaxCommitFeeRateAnchors: lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte,
LogRotator: build.NewRotatingLogWriter(),
DB: lncfg.DefaultDB(),
Cluster: lncfg.DefaultCluster(),
RPCMiddleware: lncfg.DefaultRPCMiddleware(),
ActiveNetParams: chainreg.BitcoinTestNetParams,
ChannelCommitInterval: defaultChannelCommitInterval,
PendingCommitInterval: defaultPendingCommitInterval,
ChannelCommitBatchSize: defaultChannelCommitBatchSize,
CoinSelectionStrategy: defaultCoinSelectionStrategy,
KeepFailedPaymentAttempts: defaultKeepFailedPaymentAttempts,
RemoteSigner: &lncfg.RemoteSigner{
Timeout: lncfg.DefaultRemoteSignerRPCTimeout,
},
Sweeper: lncfg.DefaultSweeperConfig(),
Htlcswitch: &lncfg.Htlcswitch{
MailboxDeliveryTimeout: htlcswitch.DefaultMailboxDeliveryTimeout,
},
GRPC: &GRPCConfig{
ServerPingTime: defaultGrpcServerPingTime,
ServerPingTimeout: defaultGrpcServerPingTimeout,
ClientPingMinWait: defaultGrpcClientPingMinWait,
},
LogConfig: build.DefaultLogConfig(),
WtClient: lncfg.DefaultWtClientCfg(),
HTTPHeaderTimeout: DefaultHTTPHeaderTimeout,
NumRestrictedSlots: DefaultNumRestrictedSlots,
}
}
// LoadConfig initializes and parses the config using a config file and command
// line options.
//
// The configuration proceeds as follows:
// 1. Start with a default config with sane settings
// 2. Pre-parse the command line to check for an alternative config file
// 3. Load configuration file overwriting defaults with any specified options
// 4. Parse CLI options and overwrite/add any specified options
func LoadConfig(interceptor signal.Interceptor) (*Config, error) {
// Pre-parse the command line options to pick up an alternative config
// file.
preCfg := DefaultConfig()
if _, err := flags.Parse(&preCfg); err != nil {
return nil, err
}
// Show the version and exit if the version flag was specified.
appName := filepath.Base(os.Args[0])
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
usageMessage := fmt.Sprintf("Use %s -h to show usage", appName)
if preCfg.ShowVersion {
fmt.Println(appName, "version", build.Version(),
"commit="+build.Commit)
os.Exit(0)
}
// If the config file path has not been modified by the user, then we'll
// use the default config file path. However, if the user has modified
// their lnddir, then we should assume they intend to use the config
// file within it.
configFileDir := CleanAndExpandPath(preCfg.LndDir)
configFilePath := CleanAndExpandPath(preCfg.ConfigFile)
switch {
// User specified --lnddir but no --configfile. Update the config file
// path to the lnd config directory, but don't require it to exist.
case configFileDir != DefaultLndDir &&
configFilePath == DefaultConfigFile:
configFilePath = filepath.Join(
configFileDir, lncfg.DefaultConfigFilename,
)
// User did specify an explicit --configfile, so we check that it does
// exist under that path to avoid surprises.
case configFilePath != DefaultConfigFile:
if !lnrpc.FileExists(configFilePath) {
return nil, fmt.Errorf("specified config file does "+
"not exist in %s", configFilePath)
}
}
// Next, load any additional configuration options from the file.
var configFileError error
cfg := preCfg
fileParser := flags.NewParser(&cfg, flags.Default)
err := flags.NewIniParser(fileParser).ParseFile(configFilePath)
if err != nil {
// If it's a parsing related error, then we'll return
// immediately, otherwise we can proceed as possibly the config
// file doesn't exist which is OK.
if lnutils.ErrorAs[*flags.IniError](err) ||
lnutils.ErrorAs[*flags.Error](err) {
return nil, err
}
configFileError = err
}
// Finally, parse the remaining command line options again to ensure
// they take precedence.
flagParser := flags.NewParser(&cfg, flags.Default)
if _, err := flagParser.Parse(); err != nil {
return nil, err
}
// Make sure everything we just loaded makes sense.
cleanCfg, err := ValidateConfig(
cfg, interceptor, fileParser, flagParser,
)
var usageErr *lncfg.UsageError
if errors.As(err, &usageErr) {
// The logging system might not yet be initialized, so we also
// write to stderr to make sure the error appears somewhere.
_, _ = fmt.Fprintln(os.Stderr, usageMessage)
ltndLog.Warnf("Incorrect usage: %v", usageMessage)
// The log subsystem might not yet be initialized. But we still
// try to log the error there since some packaging solutions
// might only look at the log and not stdout/stderr.
ltndLog.Warnf("Error validating config: %v", err)
return nil, err
}
if err != nil {
// The log subsystem might not yet be initialized. But we still
// try to log the error there since some packaging solutions
// might only look at the log and not stdout/stderr.
ltndLog.Warnf("Error validating config: %v", err)
return nil, err
}
// Warn about missing config file only after all other configuration is
// done. This prevents the warning on help messages and invalid options.
// Note this should go directly before the return.
if configFileError != nil {
ltndLog.Warnf("%v", configFileError)
}
// Finally, log warnings for deprecated config options if they are set.
logWarningsForDeprecation(*cleanCfg)
return cleanCfg, nil
}
// ValidateConfig check the given configuration to be sane. This makes sure no
// illegal values or combination of values are set. All file system paths are
// normalized. The cleaned up config is returned on success.
func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser,
flagParser *flags.Parser) (*Config, error) {
// Special show command to list supported subsystems and exit.
if cfg.DebugLevel == "show" {
subLogMgr := build.NewSubLoggerManager()
// Initialize logging at the default logging level.
SetupLoggers(subLogMgr, interceptor)
fmt.Println("Supported subsystems",
subLogMgr.SupportedSubsystems())
os.Exit(0)
}
// If the provided lnd directory is not the default, we'll modify the
// path to all of the files and directories that will live within it.
lndDir := CleanAndExpandPath(cfg.LndDir)
if lndDir != DefaultLndDir {
cfg.DataDir = filepath.Join(lndDir, defaultDataDirname)
cfg.LetsEncryptDir = filepath.Join(
lndDir, defaultLetsEncryptDirname,
)
cfg.TLSCertPath = filepath.Join(lndDir, defaultTLSCertFilename)
cfg.TLSKeyPath = filepath.Join(lndDir, defaultTLSKeyFilename)
cfg.LogDir = filepath.Join(lndDir, defaultLogDirname)
// If the watchtower's directory is set to the default, i.e. the
// user has not requested a different location, we'll move the
// location to be relative to the specified lnd directory.
if cfg.Watchtower.TowerDir == defaultTowerDir {
cfg.Watchtower.TowerDir = filepath.Join(
cfg.DataDir, defaultTowerSubDirname,
)
}
}
funcName := "ValidateConfig"
mkErr := func(format string, args ...interface{}) error {
return fmt.Errorf(funcName+": "+format, args...)
}
makeDirectory := func(dir string) error {
err := os.MkdirAll(dir, 0700)
if err != nil {
// Show a nicer error message if it's because a symlink
// is linked to a directory that does not exist
// (probably because it's not mounted).
if e, ok := err.(*os.PathError); ok && os.IsExist(err) {
link, lerr := os.Readlink(e.Path)
if lerr == nil {
str := "is symlink %s -> %s mounted?"
err = fmt.Errorf(str, e.Path, link)
}
}
str := "Failed to create lnd directory '%s': %v"
return mkErr(str, dir, err)
}
return nil
}
// IsSet returns true if an option has been set in either the config
// file or by a flag.
isSet := func(field string) (bool, error) {
fieldName, ok := reflect.TypeOf(Config{}).FieldByName(field)
if !ok {
str := "could not find field %s"
return false, mkErr(str, field)
}
long, ok := fieldName.Tag.Lookup("long")
if !ok {
str := "field %s does not have a long tag"
return false, mkErr(str, field)
}
// The user has the option to set the flag in either the config
// file or as a command line flag. If any is set, we consider it
// to be set, not applying any precedence rules here (since it
// is a boolean the default is false anyway which would screw up
// any precedence rules). Additionally, we need to also support
// the use case where the config struct is embedded _within_
// another struct with a prefix (as is the case with
// lightning-terminal).
fileOption := fileParser.FindOptionByLongName(long)
fileOptionNested := fileParser.FindOptionByLongName(
"lnd." + long,
)
flagOption := flagParser.FindOptionByLongName(long)
flagOptionNested := flagParser.FindOptionByLongName(
"lnd." + long,
)
return (fileOption != nil && fileOption.IsSet()) ||
(fileOptionNested != nil && fileOptionNested.IsSet()) ||
(flagOption != nil && flagOption.IsSet()) ||
(flagOptionNested != nil && flagOptionNested.IsSet()),
nil
}
// As soon as we're done parsing configuration options, ensure all paths
// to directories and files are cleaned and expanded before attempting
// to use them later on.
cfg.DataDir = CleanAndExpandPath(cfg.DataDir)
cfg.TLSCertPath = CleanAndExpandPath(cfg.TLSCertPath)
cfg.TLSKeyPath = CleanAndExpandPath(cfg.TLSKeyPath)
cfg.LetsEncryptDir = CleanAndExpandPath(cfg.LetsEncryptDir)
cfg.AdminMacPath = CleanAndExpandPath(cfg.AdminMacPath)
cfg.ReadMacPath = CleanAndExpandPath(cfg.ReadMacPath)
cfg.InvoiceMacPath = CleanAndExpandPath(cfg.InvoiceMacPath)
cfg.LogDir = CleanAndExpandPath(cfg.LogDir)
cfg.BtcdMode.Dir = CleanAndExpandPath(cfg.BtcdMode.Dir)
cfg.BitcoindMode.Dir = CleanAndExpandPath(cfg.BitcoindMode.Dir)
cfg.BitcoindMode.ConfigPath = CleanAndExpandPath(
cfg.BitcoindMode.ConfigPath,
)
cfg.BitcoindMode.RPCCookie = CleanAndExpandPath(cfg.BitcoindMode.RPCCookie)
cfg.Tor.PrivateKeyPath = CleanAndExpandPath(cfg.Tor.PrivateKeyPath)
cfg.Tor.WatchtowerKeyPath = CleanAndExpandPath(cfg.Tor.WatchtowerKeyPath)
cfg.Watchtower.TowerDir = CleanAndExpandPath(cfg.Watchtower.TowerDir)
cfg.BackupFilePath = CleanAndExpandPath(cfg.BackupFilePath)
cfg.WalletUnlockPasswordFile = CleanAndExpandPath(
cfg.WalletUnlockPasswordFile,
)
// Ensure that the user didn't attempt to specify negative values for
// any of the autopilot params.
if cfg.Autopilot.MaxChannels < 0 {
str := "autopilot.maxchannels must be non-negative"
return nil, mkErr(str)
}
if cfg.Autopilot.Allocation < 0 {
str := "autopilot.allocation must be non-negative"
return nil, mkErr(str)
}
if cfg.Autopilot.MinChannelSize < 0 {
str := "autopilot.minchansize must be non-negative"
return nil, mkErr(str)
}
if cfg.Autopilot.MaxChannelSize < 0 {
str := "autopilot.maxchansize must be non-negative"
return nil, mkErr(str)
}
if cfg.Autopilot.MinConfs < 0 {
str := "autopilot.minconfs must be non-negative"
return nil, mkErr(str)
}
if cfg.Autopilot.ConfTarget < 1 {
str := "autopilot.conftarget must be positive"
return nil, mkErr(str)
}
// Ensure that the specified values for the min and max channel size
// are within the bounds of the normal chan size constraints.
if cfg.Autopilot.MinChannelSize < int64(funding.MinChanFundingSize) {
cfg.Autopilot.MinChannelSize = int64(funding.MinChanFundingSize)
}
if cfg.Autopilot.MaxChannelSize > int64(MaxFundingAmount) {
cfg.Autopilot.MaxChannelSize = int64(MaxFundingAmount)
}
if _, err := validateAtplCfg(cfg.Autopilot); err != nil {
return nil, mkErr("error validating autopilot: %v", err)
}
// Ensure that --maxchansize is properly handled when set by user.
// For non-Wumbo channels this limit remains 16777215 satoshis by default
// as specified in BOLT-02. For wumbo channels this limit is 1,000,000,000.
// satoshis (10 BTC). Always enforce --maxchansize explicitly set by user.
// If unset (marked by 0 value), then enforce proper default.
if cfg.MaxChanSize == 0 {
if cfg.ProtocolOptions.Wumbo() {
cfg.MaxChanSize = int64(funding.MaxBtcFundingAmountWumbo)
} else {
cfg.MaxChanSize = int64(funding.MaxBtcFundingAmount)
}
}
// Ensure that the user specified values for the min and max channel
// size make sense.
if cfg.MaxChanSize < cfg.MinChanSize {
return nil, mkErr("invalid channel size parameters: "+
"max channel size %v, must be no less than min chan "+
"size %v", cfg.MaxChanSize, cfg.MinChanSize,
)
}
// Don't allow superfluous --maxchansize greater than
// BOLT 02 soft-limit for non-wumbo channel
if !cfg.ProtocolOptions.Wumbo() &&
cfg.MaxChanSize > int64(MaxFundingAmount) {
return nil, mkErr("invalid channel size parameters: "+
"maximum channel size %v is greater than maximum "+
"non-wumbo channel size %v", cfg.MaxChanSize,
MaxFundingAmount,
)
}
// Ensure that the amount data for revoked commitment transactions is
// stored if the watchtower client is active.
if cfg.DB.NoRevLogAmtData && cfg.WtClient.Active {
return nil, mkErr("revocation log amount data must be stored " +
"if the watchtower client is active")
}
// Ensure a valid max channel fee allocation was set.
if cfg.MaxChannelFeeAllocation <= 0 || cfg.MaxChannelFeeAllocation > 1 {
return nil, mkErr("invalid max channel fee allocation: %v, "+
"must be within (0, 1]", cfg.MaxChannelFeeAllocation)
}
if cfg.MaxCommitFeeRateAnchors < 1 {
return nil, mkErr("invalid max commit fee rate anchors: %v, "+
"must be at least 1 sat/vByte",
cfg.MaxCommitFeeRateAnchors)
}
// Validate the Tor config parameters.
socks, err := lncfg.ParseAddressString(
cfg.Tor.SOCKS, strconv.Itoa(defaultTorSOCKSPort),
cfg.net.ResolveTCPAddr,
)
if err != nil {
return nil, err
}
cfg.Tor.SOCKS = socks.String()
// We'll only attempt to normalize and resolve the DNS host if it hasn't
// changed, as it doesn't need to be done for the default.
if cfg.Tor.DNS != defaultTorDNS {
dns, err := lncfg.ParseAddressString(
cfg.Tor.DNS, strconv.Itoa(defaultTorDNSPort),
cfg.net.ResolveTCPAddr,
)
if err != nil {
return nil, mkErr("error parsing tor dns: %v", err)
}
cfg.Tor.DNS = dns.String()
}
control, err := lncfg.ParseAddressString(
cfg.Tor.Control, strconv.Itoa(defaultTorControlPort),
cfg.net.ResolveTCPAddr,
)
if err != nil {
return nil, mkErr("error parsing tor control address: %v", err)
}
cfg.Tor.Control = control.String()
// Ensure that tor socks host:port is not equal to tor control
// host:port. This would lead to lnd not starting up properly.
if cfg.Tor.SOCKS == cfg.Tor.Control {
str := "tor.socks and tor.control can not us the same host:port"
return nil, mkErr(str)
}
switch {
case cfg.Tor.V2 && cfg.Tor.V3:
return nil, mkErr("either tor.v2 or tor.v3 can be set, " +
"but not both")
case cfg.DisableListen && (cfg.Tor.V2 || cfg.Tor.V3):
return nil, mkErr("listening must be enabled when enabling " +
"inbound connections over Tor")
}
if cfg.Tor.PrivateKeyPath == "" {
switch {
case cfg.Tor.V2:
cfg.Tor.PrivateKeyPath = filepath.Join(
lndDir, defaultTorV2PrivateKeyFilename,
)
case cfg.Tor.V3:
cfg.Tor.PrivateKeyPath = filepath.Join(
lndDir, defaultTorV3PrivateKeyFilename,
)
}
}
if cfg.Tor.WatchtowerKeyPath == "" {
switch {
case cfg.Tor.V2:
cfg.Tor.WatchtowerKeyPath = filepath.Join(
cfg.Watchtower.TowerDir,
defaultTorV2PrivateKeyFilename,
)
case cfg.Tor.V3:
cfg.Tor.WatchtowerKeyPath = filepath.Join(
cfg.Watchtower.TowerDir,
defaultTorV3PrivateKeyFilename,
)
}
}
// Set up the network-related functions that will be used throughout
// the daemon. We use the standard Go "net" package functions by
// default. If we should be proxying all traffic through Tor, then
// we'll use the Tor proxy specific functions in order to avoid leaking
// our real information.
if cfg.Tor.Active {
cfg.net = &tor.ProxyNet{
SOCKS: cfg.Tor.SOCKS,
DNS: cfg.Tor.DNS,
StreamIsolation: cfg.Tor.StreamIsolation,
SkipProxyForClearNetTargets: cfg.Tor.SkipProxyForClearNetTargets,
}
}
if cfg.DisableListen && cfg.NAT {
return nil, mkErr("NAT traversal cannot be used when " +
"listening is disabled")
}
if cfg.NAT && len(cfg.ExternalHosts) != 0 {
return nil, mkErr("NAT support and externalhosts are " +
"mutually exclusive, only one should be selected")
}
// Multiple networks can't be selected simultaneously. Count
// number of network flags passed; assign active network params
// while we're at it.
numNets := 0
if cfg.Bitcoin.MainNet {
numNets++
cfg.ActiveNetParams = chainreg.BitcoinMainNetParams
}
if cfg.Bitcoin.TestNet3 {
numNets++
cfg.ActiveNetParams = chainreg.BitcoinTestNetParams
}
if cfg.Bitcoin.TestNet4 {
numNets++
cfg.ActiveNetParams = chainreg.BitcoinTestNet4Params
}
if cfg.Bitcoin.RegTest {
numNets++
cfg.ActiveNetParams = chainreg.BitcoinRegTestNetParams
}
if cfg.Bitcoin.SimNet {
numNets++
cfg.ActiveNetParams = chainreg.BitcoinSimNetParams
// For simnet, the btcsuite chain params uses a
// cointype of 115. However, we override this in
// chainreg/chainparams.go, but the raw ChainParam
// field is used elsewhere. To ensure everything is
// consistent, we'll also override the cointype within
// the raw params.
targetCoinType := chainreg.BitcoinSigNetParams.CoinType
cfg.ActiveNetParams.Params.HDCoinType = targetCoinType
}
if cfg.Bitcoin.SigNet {
numNets++
cfg.ActiveNetParams = chainreg.BitcoinSigNetParams
// Let the user overwrite the default signet parameters.
// The challenge defines the actual signet network to
// join and the seed nodes are needed for network
// discovery.
sigNetChallenge := chaincfg.DefaultSignetChallenge
sigNetSeeds := chaincfg.DefaultSignetDNSSeeds
if cfg.Bitcoin.SigNetChallenge != "" {
challenge, err := hex.DecodeString(
cfg.Bitcoin.SigNetChallenge,
)
if err != nil {
return nil, mkErr("Invalid "+
"signet challenge, hex decode "+
"failed: %v", err)
}
sigNetChallenge = challenge
}
if len(cfg.Bitcoin.SigNetSeedNode) > 0 {
sigNetSeeds = make([]chaincfg.DNSSeed, len(
cfg.Bitcoin.SigNetSeedNode,
))
for idx, seed := range cfg.Bitcoin.SigNetSeedNode {
sigNetSeeds[idx] = chaincfg.DNSSeed{
Host: seed,
HasFiltering: false,
}
}
}
chainParams := chaincfg.CustomSignetParams(
sigNetChallenge, sigNetSeeds,
)
cfg.ActiveNetParams.Params = &chainParams
}
if numNets > 1 {
str := "The mainnet, testnet, testnet4, regtest, simnet and " +
"signet params can't be used together -- choose one " +
"of the five"
return nil, mkErr(str)
}
// The target network must be provided, otherwise, we won't
// know how to initialize the daemon.
if numNets == 0 {
str := "either --bitcoin.mainnet, or --bitcoin.testnet, " +
"--bitcoin.testnet4, --bitcoin.simnet, " +
"--bitcoin.regtest or --bitcoin.signet must be " +
"specified"
return nil, mkErr(str)
}
err = cfg.Bitcoin.Validate(minTimeLockDelta, funding.MinBtcRemoteDelay)
if err != nil {
return nil, mkErr("error validating bitcoin params: %v", err)
}
switch cfg.Bitcoin.Node {
case btcdBackendName:
err := parseRPCParams(
cfg.Bitcoin, cfg.BtcdMode, cfg.ActiveNetParams,
)
if err != nil {
return nil, mkErr("unable to load RPC "+
"credentials for btcd: %v", err)
}
case bitcoindBackendName:
if cfg.Bitcoin.SimNet {
return nil, mkErr("bitcoind does not " +
"support simnet")
}
err := parseRPCParams(
cfg.Bitcoin, cfg.BitcoindMode, cfg.ActiveNetParams,
)
if err != nil {
return nil, mkErr("unable to load RPC "+
"credentials for bitcoind: %v", err)
}
case neutrinoBackendName:
// No need to get RPC parameters.
case "nochainbackend":
// Nothing to configure, we're running without any chain
// backend whatsoever (pure signing mode).
default:
str := "only btcd, bitcoind, and neutrino mode " +
"supported for bitcoin at this time"
return nil, mkErr(str)
}
cfg.Bitcoin.ChainDir = filepath.Join(
cfg.DataDir, defaultChainSubDirname, BitcoinChainName,
)
// Ensure that the user didn't attempt to specify negative values for
// any of the autopilot params.
if cfg.Autopilot.MaxChannels < 0 {
str := "autopilot.maxchannels must be non-negative"
return nil, mkErr(str)
}
if cfg.Autopilot.Allocation < 0 {
str := "autopilot.allocation must be non-negative"
return nil, mkErr(str)
}
if cfg.Autopilot.MinChannelSize < 0 {
str := "autopilot.minchansize must be non-negative"
return nil, mkErr(str)
}
if cfg.Autopilot.MaxChannelSize < 0 {
str := "autopilot.maxchansize must be non-negative"
return nil, mkErr(str)
}
// Ensure that the specified values for the min and max channel size
// don't are within the bounds of the normal chan size constraints.
if cfg.Autopilot.MinChannelSize < int64(funding.MinChanFundingSize) {
cfg.Autopilot.MinChannelSize = int64(funding.MinChanFundingSize)
}
if cfg.Autopilot.MaxChannelSize > int64(MaxFundingAmount) {
cfg.Autopilot.MaxChannelSize = int64(MaxFundingAmount)
}
// We'll now construct the network directory which will be where we
// store all the data specific to this chain/network.
cfg.networkDir = filepath.Join(
cfg.DataDir, defaultChainSubDirname, BitcoinChainName,
lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
)
// If a custom macaroon directory wasn't specified and the data
// directory has changed from the default path, then we'll also update
// the path for the macaroons to be generated.
if cfg.AdminMacPath == "" {
cfg.AdminMacPath = filepath.Join(
cfg.networkDir, defaultAdminMacFilename,
)
}
if cfg.ReadMacPath == "" {
cfg.ReadMacPath = filepath.Join(
cfg.networkDir, defaultReadMacFilename,
)
}
if cfg.InvoiceMacPath == "" {
cfg.InvoiceMacPath = filepath.Join(
cfg.networkDir, defaultInvoiceMacFilename,
)
}
towerDir := filepath.Join(
cfg.Watchtower.TowerDir, BitcoinChainName,
lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
)
// Create the lnd directory and all other sub-directories if they don't
// already exist. This makes sure that directory trees are also created
// for files that point to outside the lnddir.
dirs := []string{
lndDir, cfg.DataDir, cfg.networkDir,
cfg.LetsEncryptDir, towerDir, cfg.graphDatabaseDir(),
filepath.Dir(cfg.TLSCertPath), filepath.Dir(cfg.TLSKeyPath),
filepath.Dir(cfg.AdminMacPath), filepath.Dir(cfg.ReadMacPath),
filepath.Dir(cfg.InvoiceMacPath),
filepath.Dir(cfg.Tor.PrivateKeyPath),
filepath.Dir(cfg.Tor.WatchtowerKeyPath),
}
for _, dir := range dirs {
if err := makeDirectory(dir); err != nil {
return nil, err
}
}
// Similarly, if a custom back up file path wasn't specified, then
// we'll update the file location to match our set network directory.
if cfg.BackupFilePath == "" {
cfg.BackupFilePath = filepath.Join(
cfg.networkDir, chanbackup.DefaultBackupFileName,
)
}
// Append the network type to the log directory so it is "namespaced"
// per network in the same fashion as the data directory.
cfg.LogDir = filepath.Join(
cfg.LogDir, BitcoinChainName,
lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
)
if err := cfg.LogConfig.Validate(); err != nil {
return nil, mkErr("error validating logging config: %w", err)
}
// If a sub-log manager was not already created, then we'll create one
// now using the default log handlers.
if cfg.SubLogMgr == nil {
cfg.SubLogMgr = build.NewSubLoggerManager(
build.NewDefaultLogHandlers(
cfg.LogConfig, cfg.LogRotator,
)...,
)
}
// Initialize logging at the default logging level.
SetupLoggers(cfg.SubLogMgr, interceptor)
if cfg.MaxLogFiles != 0 {
if cfg.LogConfig.File.MaxLogFiles !=
build.DefaultMaxLogFiles {
return nil, mkErr("cannot set both maxlogfiles and "+
"logging.file.max-files", err)
}
cfg.LogConfig.File.MaxLogFiles = cfg.MaxLogFiles
}
if cfg.MaxLogFileSize != 0 {
if cfg.LogConfig.File.MaxLogFileSize !=
build.DefaultMaxLogFileSize {
return nil, mkErr("cannot set both maxlogfilesize and "+
"logging.file.max-file-size", err)
}
cfg.LogConfig.File.MaxLogFileSize = cfg.MaxLogFileSize
}
err = cfg.LogRotator.InitLogRotator(
cfg.LogConfig.File,
filepath.Join(cfg.LogDir, defaultLogFilename),
)
if err != nil {
str := "log rotation setup failed: %v"
return nil, mkErr(str, err)
}
// Parse, validate, and set debug log level(s).
err = build.ParseAndSetDebugLevels(cfg.DebugLevel, cfg.SubLogMgr)
if err != nil {
str := "error parsing debug level: %v"
return nil, &lncfg.UsageError{Err: mkErr(str, err)}
}
// At least one RPCListener is required. So listen on localhost per
// default.
if len(cfg.RawRPCListeners) == 0 {
addr := fmt.Sprintf("localhost:%d", defaultRPCPort)
cfg.RawRPCListeners = append(cfg.RawRPCListeners, addr)
}
// Listen on localhost if no REST listeners were specified.
if len(cfg.RawRESTListeners) == 0 {
addr := fmt.Sprintf("localhost:%d", defaultRESTPort)
cfg.RawRESTListeners = append(cfg.RawRESTListeners, addr)
}
// Listen on the default interface/port if no listeners were specified.
// An empty address string means default interface/address, which on
// most unix systems is the same as 0.0.0.0. If Tor is active, we
// default to only listening on localhost for hidden service
// connections.
if len(cfg.RawListeners) == 0 {
addr := fmt.Sprintf(":%d", defaultPeerPort)
if cfg.Tor.Active && !cfg.Tor.SkipProxyForClearNetTargets {
addr = fmt.Sprintf("localhost:%d", defaultPeerPort)
}
cfg.RawListeners = append(cfg.RawListeners, addr)
}
// Add default port to all RPC listener addresses if needed and remove
// duplicate addresses.
cfg.RPCListeners, err = lncfg.NormalizeAddresses(
cfg.RawRPCListeners, strconv.Itoa(defaultRPCPort),
cfg.net.ResolveTCPAddr,
)
if err != nil {
return nil, mkErr("error normalizing RPC listen addrs: %v", err)
}
// Add default port to all REST listener addresses if needed and remove
// duplicate addresses.
cfg.RESTListeners, err = lncfg.NormalizeAddresses(
cfg.RawRESTListeners, strconv.Itoa(defaultRESTPort),
cfg.net.ResolveTCPAddr,
)
if err != nil {
return nil, mkErr("error normalizing REST listen addrs: %v", err)
}
switch {
// The no seed backup and auto unlock are mutually exclusive.
case cfg.NoSeedBackup && cfg.WalletUnlockPasswordFile != "":
return nil, mkErr("cannot set noseedbackup and " +
"wallet-unlock-password-file at the same time")
// The "allow-create" flag cannot be set without the auto unlock file.
case cfg.WalletUnlockAllowCreate && cfg.WalletUnlockPasswordFile == "":
return nil, mkErr("cannot set wallet-unlock-allow-create " +
"without wallet-unlock-password-file")
// If a password file was specified, we need it to exist.
case cfg.WalletUnlockPasswordFile != "" &&
!lnrpc.FileExists(cfg.WalletUnlockPasswordFile):
return nil, mkErr("wallet unlock password file %s does "+
"not exist", cfg.WalletUnlockPasswordFile)
}
// For each of the RPC listeners (REST+gRPC), we'll ensure that users
// have specified a safe combo for authentication. If not, we'll bail
// out with an error. Since we don't allow disabling TLS for gRPC
// connections we pass in tlsActive=true.
err = lncfg.EnforceSafeAuthentication(
cfg.RPCListeners, !cfg.NoMacaroons, true,
)
if err != nil {
return nil, mkErr("error enforcing safe authentication on "+
"RPC ports: %v", err)
}
if cfg.DisableRest {
ltndLog.Infof("REST API is disabled!")
cfg.RESTListeners = nil
} else {
err = lncfg.EnforceSafeAuthentication(
cfg.RESTListeners, !cfg.NoMacaroons, !cfg.DisableRestTLS,
)
if err != nil {
return nil, mkErr("error enforcing safe "+
"authentication on REST ports: %v", err)
}
}
// Remove the listening addresses specified if listening is disabled.
if cfg.DisableListen {
ltndLog.Infof("Listening on the p2p interface is disabled!")
cfg.Listeners = nil
cfg.ExternalIPs = nil
} else {
// Add default port to all listener addresses if needed and remove
// duplicate addresses.
cfg.Listeners, err = lncfg.NormalizeAddresses(
cfg.RawListeners, strconv.Itoa(defaultPeerPort),
cfg.net.ResolveTCPAddr,
)
if err != nil {
return nil, mkErr("error normalizing p2p listen "+
"addrs: %v", err)
}
// Add default port to all external IP addresses if needed and remove
// duplicate addresses.
cfg.ExternalIPs, err = lncfg.NormalizeAddresses(
cfg.RawExternalIPs, strconv.Itoa(defaultPeerPort),
cfg.net.ResolveTCPAddr,
)
if err != nil {
return nil, err
}
// For the p2p port it makes no sense to listen to an Unix socket.
// Also, we would need to refactor the brontide listener to support
// that.
for _, p2pListener := range cfg.Listeners {
if lncfg.IsUnix(p2pListener) {
return nil, mkErr("unix socket addresses "+
"cannot be used for the p2p "+
"connection listener: %s", p2pListener)
}
}
}
// Ensure that the specified minimum backoff is below or equal to the
// maximum backoff.
if cfg.MinBackoff > cfg.MaxBackoff {
return nil, mkErr("maxbackoff must be greater than minbackoff")
}
// Newer versions of lnd added a new sub-config for bolt-specific
// parameters. However, we want to also allow existing users to use the
// value on the top-level config. If the outer config value is set,
// then we'll use that directly.
flagSet, err := isSet("SyncFreelist")
if err != nil {
return nil, mkErr("error parsing freelist sync flag: %v", err)
}
if flagSet {
cfg.DB.Bolt.NoFreelistSync = !cfg.SyncFreelist
}
// Parse any extra sqlite pragma options that may have been provided
// to determine if they override any of the defaults that we will
// otherwise add.
var (
defaultSynchronous = true
defaultAutoVacuum = true
defaultFullfsync = true
)
for _, option := range cfg.DB.Sqlite.PragmaOptions {
switch {
case strings.HasPrefix(option, "synchronous="):
defaultSynchronous = false
case strings.HasPrefix(option, "auto_vacuum="):
defaultAutoVacuum = false
case strings.HasPrefix(option, "fullfsync="):
defaultFullfsync = false
default:
}
}
if defaultSynchronous {
cfg.DB.Sqlite.PragmaOptions = append(
cfg.DB.Sqlite.PragmaOptions, "synchronous=full",
)
}
if defaultAutoVacuum {
cfg.DB.Sqlite.PragmaOptions = append(
cfg.DB.Sqlite.PragmaOptions, "auto_vacuum=incremental",
)
}
if defaultFullfsync {
cfg.DB.Sqlite.PragmaOptions = append(
cfg.DB.Sqlite.PragmaOptions, "fullfsync=true",
)
}
// Ensure that the user hasn't chosen a remote-max-htlc value greater
// than the protocol maximum.
maxRemoteHtlcs := uint16(input.MaxHTLCNumber / 2)
if cfg.DefaultRemoteMaxHtlcs > maxRemoteHtlcs {
return nil, mkErr("default-remote-max-htlcs (%v) must be "+
"less than %v", cfg.DefaultRemoteMaxHtlcs,
maxRemoteHtlcs)
}
// Clamp the ChannelCommitInterval so that commitment updates can still
// happen in a reasonable timeframe.
if cfg.ChannelCommitInterval > maxChannelCommitInterval {
return nil, mkErr("channel-commit-interval (%v) must be less "+
"than %v", cfg.ChannelCommitInterval,
maxChannelCommitInterval)
}
// Limit PendingCommitInterval so we don't wait too long for the remote
// party to send back a revoke.
if cfg.PendingCommitInterval > maxPendingCommitInterval {
return nil, mkErr("pending-commit-interval (%v) must be less "+
"than %v", cfg.PendingCommitInterval,
maxPendingCommitInterval)
}
if err := cfg.Gossip.Parse(); err != nil {
return nil, mkErr("error parsing gossip syncer: %v", err)
}
// If the experimental protocol options specify any protocol messages
// that we want to handle as custom messages, set them now.
customMsg := cfg.ProtocolOptions.CustomMessageOverrides()
// We can safely set our custom override values during startup because
// startup is blocked on config parsing.
if err := lnwire.SetCustomOverrides(customMsg); err != nil {
return nil, mkErr("custom-message: %v", err)
}
// Map old pprof flags to new pprof group flags.
//
// NOTE: This is a temporary measure to ensure compatibility with old
// flags.
if cfg.CPUProfile != "" {
if cfg.Pprof.CPUProfile != "" {
return nil, mkErr("cpuprofile and pprof.cpuprofile " +
"are mutually exclusive")
}
cfg.Pprof.CPUProfile = cfg.CPUProfile
}
if cfg.Profile != "" {
if cfg.Pprof.Profile != "" {
return nil, mkErr("profile and pprof.profile " +
"are mutually exclusive")
}
cfg.Pprof.Profile = cfg.Profile
}
if cfg.BlockingProfile != 0 {
if cfg.Pprof.BlockingProfile != 0 {
return nil, mkErr("blockingprofile and " +
"pprof.blockingprofile are mutually exclusive")
}
cfg.Pprof.BlockingProfile = cfg.BlockingProfile
}
if cfg.MutexProfile != 0 {
if cfg.Pprof.MutexProfile != 0 {
return nil, mkErr("mutexprofile and " +
"pprof.mutexprofile are mutually exclusive")
}
cfg.Pprof.MutexProfile = cfg.MutexProfile
}
// Don't allow both the old dust-threshold and the new
// channel-max-fee-exposure to be set.
if cfg.DustThreshold != 0 && cfg.MaxFeeExposure != 0 {
return nil, mkErr("cannot set both dust-threshold and " +
"channel-max-fee-exposure")
}
switch {
// Use the old dust-threshold as the max fee exposure if it is set and
// the new option is not.
case cfg.DustThreshold != 0:
cfg.MaxFeeExposure = cfg.DustThreshold
// Use the default max fee exposure if the new option is not set and
// the old one is not set either.
case cfg.MaxFeeExposure == 0:
cfg.MaxFeeExposure = uint64(
htlcswitch.DefaultMaxFeeExposure.ToSatoshis(),
)
}
// Validate the subconfigs for workers, caches, and the tower client.
err = lncfg.Validate(
cfg.Workers,
cfg.Caches,
cfg.WtClient,
cfg.DB,
cfg.Cluster,
cfg.HealthChecks,
cfg.RPCMiddleware,
cfg.RemoteSigner,
cfg.Sweeper,
cfg.Htlcswitch,
cfg.Invoices,
cfg.Routing,
cfg.Pprof,
cfg.Gossip,
)
if err != nil {
return nil, err
}
// Finally, ensure that the user's color is correctly formatted,
// otherwise the server will not be able to start after the unlocking
// the wallet.
_, err = lncfg.ParseHexColor(cfg.Color)
if err != nil {
return nil, mkErr("unable to parse node color: %v", err)
}
// All good, return the sanitized result.
return &cfg, nil
}
// graphDatabaseDir returns the default directory where the local bolt graph db
// files are stored.
func (c *Config) graphDatabaseDir() string {
return filepath.Join(
c.DataDir, defaultGraphSubDirname,
lncfg.NormalizeNetwork(c.ActiveNetParams.Name),
)
}
// ImplementationConfig returns the configuration of what actual implementations
// should be used when creating the main lnd instance.
func (c *Config) ImplementationConfig(
interceptor signal.Interceptor) *ImplementationCfg {
// If we're using a remote signer, we still need the base wallet as a
// watch-only source of chain and address data. But we don't need any
// private key material in that btcwallet base wallet.
if c.RemoteSigner.Enable {
rpcImpl := NewRPCSignerWalletImpl(
c, ltndLog, interceptor,
c.RemoteSigner.MigrateWatchOnly,
)
return &ImplementationCfg{
GrpcRegistrar: rpcImpl,
RestRegistrar: rpcImpl,
ExternalValidator: rpcImpl,
DatabaseBuilder: NewDefaultDatabaseBuilder(
c, ltndLog,
),
WalletConfigBuilder: rpcImpl,
ChainControlBuilder: rpcImpl,
}
}
defaultImpl := NewDefaultWalletImpl(c, ltndLog, interceptor, false)
return &ImplementationCfg{
GrpcRegistrar: defaultImpl,
RestRegistrar: defaultImpl,
ExternalValidator: defaultImpl,
DatabaseBuilder: NewDefaultDatabaseBuilder(c, ltndLog),
WalletConfigBuilder: defaultImpl,
ChainControlBuilder: defaultImpl,
}
}
// CleanAndExpandPath expands environment variables and leading ~ in the
// passed path, cleans the result, and returns it.
// This function is taken from https://github.com/btcsuite/btcd
func CleanAndExpandPath(path string) string {
if path == "" {
return ""
}
// Expand initial ~ to OS specific home directory.
if strings.HasPrefix(path, "~") {
var homeDir string
u, err := user.Current()
if err == nil {
homeDir = u.HomeDir
} else {
homeDir = os.Getenv("HOME")
}
path = strings.Replace(path, "~", homeDir, 1)
}
// NOTE: The os.ExpandEnv doesn't work with Windows-style %VARIABLE%,
// but the variables can still be expanded via POSIX-style $VARIABLE.
return filepath.Clean(os.ExpandEnv(path))
}
func parseRPCParams(cConfig *lncfg.Chain, nodeConfig interface{},
netParams chainreg.BitcoinNetParams) error {
// First, we'll check our node config to make sure the RPC parameters
// were set correctly. We'll also determine the path to the conf file
// depending on the backend node.
var daemonName, confDir, confFile, confFileBase string
switch conf := nodeConfig.(type) {
case *lncfg.Btcd:
// Resolves environment variable references in RPCUser and
// RPCPass fields.
conf.RPCUser = supplyEnvValue(conf.RPCUser)
conf.RPCPass = supplyEnvValue(conf.RPCPass)
// If both RPCUser and RPCPass are set, we assume those
// credentials are good to use.
if conf.RPCUser != "" && conf.RPCPass != "" {
return nil
}
// Set the daemon name for displaying proper errors.
daemonName = btcdBackendName
confDir = conf.Dir
confFileBase = btcdBackendName
// If only ONE of RPCUser or RPCPass is set, we assume the
// user did that unintentionally.
if conf.RPCUser != "" || conf.RPCPass != "" {
return fmt.Errorf("please set both or neither of "+
"%[1]v.rpcuser, %[1]v.rpcpass", daemonName)
}
case *lncfg.Bitcoind:
// Ensure that if the ZMQ options are set, that they are not
// equal.
if conf.ZMQPubRawBlock != "" && conf.ZMQPubRawTx != "" {
err := checkZMQOptions(
conf.ZMQPubRawBlock, conf.ZMQPubRawTx,
)
if err != nil {
return err
}
}
// Ensure that if the estimate mode is set, that it is a legal
// value.
if conf.EstimateMode != "" {
err := checkEstimateMode(conf.EstimateMode)
if err != nil {
return err
}
}
// Set the daemon name for displaying proper errors.
daemonName = bitcoindBackendName
confDir = conf.Dir
confFile = conf.ConfigPath
confFileBase = BitcoinChainName
// Resolves environment variable references in RPCUser
// and RPCPass fields.
conf.RPCUser = supplyEnvValue(conf.RPCUser)
conf.RPCPass = supplyEnvValue(conf.RPCPass)
// Check that cookie and credentials don't contradict each
// other.
if (conf.RPCUser != "" || conf.RPCPass != "") &&
conf.RPCCookie != "" {
return fmt.Errorf("please only provide either "+
"%[1]v.rpccookie or %[1]v.rpcuser and "+
"%[1]v.rpcpass", daemonName)
}
// We convert the cookie into a user name and password.
if conf.RPCCookie != "" {
cookie, err := os.ReadFile(conf.RPCCookie)
if err != nil {
return fmt.Errorf("cannot read cookie file: %w",
err)
}
splitCookie := strings.Split(string(cookie), ":")
if len(splitCookie) != 2 {
return fmt.Errorf("cookie file has a wrong " +
"format")
}
conf.RPCUser = splitCookie[0]
conf.RPCPass = splitCookie[1]
}
if conf.RPCUser != "" && conf.RPCPass != "" {
// If all of RPCUser, RPCPass, ZMQBlockHost, and
// ZMQTxHost are set, we assume those parameters are
// good to use.
if conf.ZMQPubRawBlock != "" && conf.ZMQPubRawTx != "" {
return nil
}
// If RPCUser and RPCPass are set and RPCPolling is
// enabled, we assume the parameters are good to use.
if conf.RPCPolling {
return nil
}
}
// If not all of the parameters are set, we'll assume the user
// did this unintentionally.
if conf.RPCUser != "" || conf.RPCPass != "" ||
conf.ZMQPubRawBlock != "" || conf.ZMQPubRawTx != "" {
return fmt.Errorf("please set %[1]v.rpcuser and "+
"%[1]v.rpcpass (or %[1]v.rpccookie) together "+
"with %[1]v.zmqpubrawblock, %[1]v.zmqpubrawtx",
daemonName)
}
}
// If we're in simnet mode, then the running btcd instance won't read
// the RPC credentials from the configuration. So if lnd wasn't
// specified the parameters, then we won't be able to start.
if cConfig.SimNet {
return fmt.Errorf("rpcuser and rpcpass must be set to your " +
"btcd node's RPC parameters for simnet mode")
}
fmt.Println("Attempting automatic RPC configuration to " + daemonName)
if confFile == "" {
confFile = filepath.Join(confDir, fmt.Sprintf("%v.conf",
confFileBase))
}
switch cConfig.Node {
case btcdBackendName:
nConf := nodeConfig.(*lncfg.Btcd)
rpcUser, rpcPass, err := extractBtcdRPCParams(confFile)
if err != nil {
return fmt.Errorf("unable to extract RPC credentials: "+
"%v, cannot start w/o RPC connection", err)
}
nConf.RPCUser, nConf.RPCPass = rpcUser, rpcPass
case bitcoindBackendName:
nConf := nodeConfig.(*lncfg.Bitcoind)
rpcUser, rpcPass, zmqBlockHost, zmqTxHost, err :=
extractBitcoindRPCParams(netParams.Params.Name,
nConf.Dir, confFile, nConf.RPCCookie)
if err != nil {
return fmt.Errorf("unable to extract RPC credentials: "+
"%v, cannot start w/o RPC connection", err)
}
nConf.RPCUser, nConf.RPCPass = rpcUser, rpcPass
nConf.ZMQPubRawBlock, nConf.ZMQPubRawTx = zmqBlockHost, zmqTxHost
}
fmt.Printf("Automatically obtained %v's RPC credentials\n", daemonName)
return nil
}
// supplyEnvValue supplies the value of an environment variable from a string.
// It supports the following formats:
// 1) $ENV_VAR
// 2) ${ENV_VAR}
// 3) ${ENV_VAR:-DEFAULT}
//
// Standard environment variable naming conventions:
// - ENV_VAR contains letters, digits, and underscores, and does
// not start with a digit.
// - DEFAULT follows the rule that it can contain any characters except
// whitespace.
//
// Parameters:
// - value: The input string containing references to environment variables
// (if any).
//
// Returns:
// - string: The value of the specified environment variable, the default
// value if provided, or the original input string if no matching variable is
// found or set.
func supplyEnvValue(value string) string {
// Regex for $ENV_VAR format.
var reEnvVar = regexp.MustCompile(`^\$([a-zA-Z_][a-zA-Z0-9_]*)$`)
// Regex for ${ENV_VAR} format.
var reEnvVarWithBrackets = regexp.MustCompile(
`^\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}$`,
)
// Regex for ${ENV_VAR:-DEFAULT} format.
var reEnvVarWithDefault = regexp.MustCompile(
`^\$\{([a-zA-Z_][a-zA-Z0-9_]*):-([\S]+)\}$`,
)
// Match against supported formats.
switch {
case reEnvVarWithDefault.MatchString(value):
matches := reEnvVarWithDefault.FindStringSubmatch(value)
envVariable := matches[1]
defaultValue := matches[2]
if envValue := os.Getenv(envVariable); envValue != "" {
return envValue
}
return defaultValue
case reEnvVarWithBrackets.MatchString(value):
matches := reEnvVarWithBrackets.FindStringSubmatch(value)
envVariable := matches[1]
envValue := os.Getenv(envVariable)
return envValue
case reEnvVar.MatchString(value):
matches := reEnvVar.FindStringSubmatch(value)
envVariable := matches[1]
envValue := os.Getenv(envVariable)
return envValue
}
return value
}
// extractBtcdRPCParams attempts to extract the RPC credentials for an existing
// btcd instance. The passed path is expected to be the location of btcd's
// application data directory on the target system.
func extractBtcdRPCParams(btcdConfigPath string) (string, string, error) {
// First, we'll open up the btcd configuration file found at the target
// destination.
btcdConfigFile, err := os.Open(btcdConfigPath)
if err != nil {
return "", "", err
}
defer func() { _ = btcdConfigFile.Close() }()
// With the file open extract the contents of the configuration file so
// we can attempt to locate the RPC credentials.
configContents, err := io.ReadAll(btcdConfigFile)
if err != nil {
return "", "", err
}
// Attempt to locate the RPC user using a regular expression. If we
// don't have a match for our regular expression then we'll exit with
// an error.
rpcUserRegexp, err := regexp.Compile(`(?m)^\s*rpcuser\s*=\s*([^\s]+)`)
if err != nil {
return "", "", err
}
userSubmatches := rpcUserRegexp.FindSubmatch(configContents)
if userSubmatches == nil {
return "", "", fmt.Errorf("unable to find rpcuser in config")
}
// Similarly, we'll use another regular expression to find the set
// rpcpass (if any). If we can't find the pass, then we'll exit with an
// error.
rpcPassRegexp, err := regexp.Compile(`(?m)^\s*rpcpass\s*=\s*([^\s]+)`)
if err != nil {
return "", "", err
}
passSubmatches := rpcPassRegexp.FindSubmatch(configContents)
if passSubmatches == nil {
return "", "", fmt.Errorf("unable to find rpcuser in config")
}
return supplyEnvValue(string(userSubmatches[1])),
supplyEnvValue(string(passSubmatches[1])), nil
}
// extractBitcoindRPCParams attempts to extract the RPC credentials for an
// existing bitcoind node instance. The routine looks for a cookie first,
// optionally following the datadir configuration option in the bitcoin.conf. If
// it doesn't find one, it looks for rpcuser/rpcpassword.
func extractBitcoindRPCParams(networkName, bitcoindDataDir, bitcoindConfigPath,
rpcCookiePath string) (string, string, string, string, error) {
// First, we'll open up the bitcoind configuration file found at the
// target destination.
bitcoindConfigFile, err := os.Open(bitcoindConfigPath)
if err != nil {
return "", "", "", "", err
}
defer func() { _ = bitcoindConfigFile.Close() }()
// With the file open extract the contents of the configuration file so
// we can attempt to locate the RPC credentials.
configContents, err := io.ReadAll(bitcoindConfigFile)
if err != nil {
return "", "", "", "", err
}
// First, we'll look for the ZMQ hosts providing raw block and raw
// transaction notifications.
zmqBlockHostRE, err := regexp.Compile(
`(?m)^\s*zmqpubrawblock\s*=\s*([^\s]+)`,
)
if err != nil {
return "", "", "", "", err
}
zmqBlockHostSubmatches := zmqBlockHostRE.FindSubmatch(configContents)
if len(zmqBlockHostSubmatches) < 2 {
return "", "", "", "", fmt.Errorf("unable to find " +
"zmqpubrawblock in config")
}
zmqTxHostRE, err := regexp.Compile(`(?m)^\s*zmqpubrawtx\s*=\s*([^\s]+)`)
if err != nil {
return "", "", "", "", err
}
zmqTxHostSubmatches := zmqTxHostRE.FindSubmatch(configContents)
if len(zmqTxHostSubmatches) < 2 {
return "", "", "", "", errors.New("unable to find zmqpubrawtx " +
"in config")
}
zmqBlockHost := string(zmqBlockHostSubmatches[1])
zmqTxHost := string(zmqTxHostSubmatches[1])
if err := checkZMQOptions(zmqBlockHost, zmqTxHost); err != nil {
return "", "", "", "", err
}
// Next, we'll try to find an auth cookie. We need to detect the chain
// by seeing if one is specified in the configuration file.
dataDir := filepath.Dir(bitcoindConfigPath)
if bitcoindDataDir != "" {
dataDir = bitcoindDataDir
}
dataDirRE, err := regexp.Compile(`(?m)^\s*datadir\s*=\s*([^\s]+)`)
if err != nil {
return "", "", "", "", err
}
dataDirSubmatches := dataDirRE.FindSubmatch(configContents)
if dataDirSubmatches != nil {
dataDir = string(dataDirSubmatches[1])
}
var chainDir string
switch networkName {
case "mainnet":
chainDir = ""
case "regtest", "testnet3", "testnet4", "signet":
chainDir = networkName
default:
return "", "", "", "", fmt.Errorf("unexpected networkname %v", networkName)
}
cookiePath := filepath.Join(dataDir, chainDir, ".cookie")
if rpcCookiePath != "" {
cookiePath = rpcCookiePath
}
cookie, err := os.ReadFile(cookiePath)
if err == nil {
splitCookie := strings.Split(string(cookie), ":")
if len(splitCookie) == 2 {
return splitCookie[0], splitCookie[1], zmqBlockHost,
zmqTxHost, nil
}
}
// We didn't find a cookie, so we attempt to locate the RPC user using
// a regular expression. If we don't have a match for our regular
// expression then we'll exit with an error.
rpcUserRegexp, err := regexp.Compile(`(?m)^\s*rpcuser\s*=\s*([^\s]+)`)
if err != nil {
return "", "", "", "", err
}
userSubmatches := rpcUserRegexp.FindSubmatch(configContents)
// Similarly, we'll use another regular expression to find the set
// rpcpass (if any). If we can't find the pass, then we'll exit with an
// error.
rpcPassRegexp, err := regexp.Compile(`(?m)^\s*rpcpassword\s*=\s*([^\s]+)`)
if err != nil {
return "", "", "", "", err
}
passSubmatches := rpcPassRegexp.FindSubmatch(configContents)
// Exit with an error if the cookie file, is defined in config, and
// can not be found, with both rpcuser and rpcpassword undefined.
if rpcCookiePath != "" && userSubmatches == nil && passSubmatches == nil {
return "", "", "", "", fmt.Errorf("unable to open cookie file (%v)",
rpcCookiePath)
}
if userSubmatches == nil {
return "", "", "", "", fmt.Errorf("unable to find rpcuser in " +
"config")
}
if passSubmatches == nil {
return "", "", "", "", fmt.Errorf("unable to find rpcpassword " +
"in config")
}
return supplyEnvValue(string(userSubmatches[1])),
supplyEnvValue(string(passSubmatches[1])),
zmqBlockHost, zmqTxHost, nil
}
// checkZMQOptions ensures that the provided addresses to use as the hosts for
// ZMQ rawblock and rawtx notifications are different.
func checkZMQOptions(zmqBlockHost, zmqTxHost string) error {
if zmqBlockHost == zmqTxHost {
return errors.New("zmqpubrawblock and zmqpubrawtx must be set " +
"to different addresses")
}
return nil
}
// checkEstimateMode ensures that the provided estimate mode is legal.
func checkEstimateMode(estimateMode string) error {
for _, mode := range bitcoindEstimateModes {
if estimateMode == mode {
return nil
}
}
return fmt.Errorf("estimatemode must be one of the following: %v",
bitcoindEstimateModes[:])
}
// configToFlatMap converts the given config struct into a flat map of
// key/value pairs using the dot notation we are used to from the config file
// or command line flags. It also returns a map containing deprecated config
// options.
func configToFlatMap(cfg Config) (map[string]string,
map[string]struct{}, error) {
result := make(map[string]string)
// deprecated stores a map of deprecated options found in the config
// that are set by the users. A config option is considered as
// deprecated if it has a `hidden` flag.
deprecated := make(map[string]struct{})
// redact is the helper function that redacts sensitive values like
// passwords.
redact := func(key, value string) string {
sensitiveKeySuffixes := []string{
"pass",
"password",
"dsn",
}
for _, suffix := range sensitiveKeySuffixes {
if strings.HasSuffix(key, suffix) {
return "[redacted]"
}
}
return value
}
// printConfig is the helper function that goes into nested structs
// recursively. Because we call it recursively, we need to declare it
// before we define it.
var printConfig func(reflect.Value, string)
printConfig = func(obj reflect.Value, prefix string) {
// Turn struct pointers into the actual struct, so we can
// iterate over the fields as we would with a struct value.
if obj.Kind() == reflect.Ptr {
obj = obj.Elem()
}
// Abort on nil values.
if !obj.IsValid() {
return
}
// Loop over all fields of the struct and inspect the type.
for i := 0; i < obj.NumField(); i++ {
field := obj.Field(i)
fieldType := obj.Type().Field(i)
longName := fieldType.Tag.Get("long")
namespace := fieldType.Tag.Get("namespace")
group := fieldType.Tag.Get("group")
hidden := fieldType.Tag.Get("hidden")
switch {
// We have a long name defined, this is a config value.
case longName != "":
key := longName
if prefix != "" {
key = prefix + "." + key
}
// Add the value directly to the flattened map.
result[key] = redact(key, fmt.Sprintf(
"%v", field.Interface(),
))
// If there's a hidden flag, it's deprecated.
if hidden == "true" && !field.IsZero() {
deprecated[key] = struct{}{}
}
// We have no long name but a namespace, this is a
// nested struct.
case longName == "" && namespace != "":
key := namespace
if prefix != "" {
key = prefix + "." + key
}
printConfig(field, key)
// Just a group means this is a dummy struct to house
// multiple config values, the group name doesn't go
// into the final field name.
case longName == "" && group != "":
printConfig(field, prefix)
// Anonymous means embedded struct. We need to recurse
// into it but without adding anything to the prefix.
case fieldType.Anonymous:
printConfig(field, prefix)
default:
continue
}
}
}
// Turn the whole config struct into a flat map.
printConfig(reflect.ValueOf(cfg), "")
return result, deprecated, nil
}
// logWarningsForDeprecation logs a warning if a deprecated config option is
// set.
func logWarningsForDeprecation(cfg Config) {
_, deprecated, err := configToFlatMap(cfg)
if err != nil {
ltndLog.Errorf("Convert configs to map: %v", err)
}
for k := range deprecated {
ltndLog.Warnf("Config '%s' is deprecated, please remove it", k)
}
}
package lnd
import (
"bytes"
"context"
"database/sql"
"errors"
"fmt"
"net"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btclog/v2"
"github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/walletdb"
proxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/lightninglabs/neutrino"
"github.com/lightninglabs/neutrino/blockntfns"
"github.com/lightninglabs/neutrino/headerfs"
"github.com/lightninglabs/neutrino/pushtx"
"github.com/lightningnetwork/lnd/blockcache"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/funding"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
"github.com/lightningnetwork/lnd/lnwallet/rpcwallet"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/lightningnetwork/lnd/msgmux"
"github.com/lightningnetwork/lnd/rpcperms"
"github.com/lightningnetwork/lnd/signal"
"github.com/lightningnetwork/lnd/sqldb"
"github.com/lightningnetwork/lnd/sqldb/sqlc"
"github.com/lightningnetwork/lnd/sweep"
"github.com/lightningnetwork/lnd/walletunlocker"
"github.com/lightningnetwork/lnd/watchtower"
"github.com/lightningnetwork/lnd/watchtower/wtclient"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
"google.golang.org/grpc"
"gopkg.in/macaroon-bakery.v2/bakery"
)
const (
// invoiceMigrationBatchSize is the number of invoices that will be
// migrated in a single batch.
invoiceMigrationBatchSize = 1000
// invoiceMigration is the version of the migration that will be used to
// migrate invoices from the kvdb to the sql database.
invoiceMigration = 7
)
// GrpcRegistrar is an interface that must be satisfied by an external subserver
// that wants to be able to register its own gRPC server onto lnd's main
// grpc.Server instance.
type GrpcRegistrar interface {
// RegisterGrpcSubserver is called for each net.Listener on which lnd
// creates a grpc.Server instance. External subservers implementing this
// method can then register their own gRPC server structs to the main
// server instance.
RegisterGrpcSubserver(*grpc.Server) error
}
// RestRegistrar is an interface that must be satisfied by an external subserver
// that wants to be able to register its own REST mux onto lnd's main
// proxy.ServeMux instance.
type RestRegistrar interface {
// RegisterRestSubserver is called after lnd creates the main
// proxy.ServeMux instance. External subservers implementing this method
// can then register their own REST proxy stubs to the main server
// instance.
RegisterRestSubserver(context.Context, *proxy.ServeMux, string,
[]grpc.DialOption) error
}
// ExternalValidator is an interface that must be satisfied by an external
// macaroon validator.
type ExternalValidator interface {
macaroons.MacaroonValidator
// Permissions returns the permissions that the external validator is
// validating. It is a map between the full HTTP URI of each RPC and its
// required macaroon permissions. If multiple action/entity tuples are
// specified per URI, they are all required. See rpcserver.go for a list
// of valid action and entity values.
Permissions() map[string][]bakery.Op
}
// DatabaseBuilder is an interface that must be satisfied by the implementation
// that provides lnd's main database backend instances.
type DatabaseBuilder interface {
// BuildDatabase extracts the current databases that we'll use for
// normal operation in the daemon. A function closure that closes all
// opened databases is also returned.
BuildDatabase(ctx context.Context) (*DatabaseInstances, func(), error)
}
// WalletConfigBuilder is an interface that must be satisfied by a custom wallet
// implementation.
type WalletConfigBuilder interface {
// BuildWalletConfig is responsible for creating or unlocking and then
// fully initializing a wallet.
BuildWalletConfig(context.Context, *DatabaseInstances, *AuxComponents,
*rpcperms.InterceptorChain,
[]*ListenerWithSignal) (*chainreg.PartialChainControl,
*btcwallet.Config, func(), error)
}
// ChainControlBuilder is an interface that must be satisfied by a custom wallet
// implementation.
type ChainControlBuilder interface {
// BuildChainControl is responsible for creating a fully populated chain
// control instance from a wallet.
BuildChainControl(*chainreg.PartialChainControl,
*btcwallet.Config) (*chainreg.ChainControl, func(), error)
}
// ImplementationCfg is a struct that holds all configuration items for
// components that can be implemented outside lnd itself.
type ImplementationCfg struct {
// GrpcRegistrar is a type that can register additional gRPC subservers
// before the main gRPC server is started.
GrpcRegistrar
// RestRegistrar is a type that can register additional REST subservers
// before the main REST proxy is started.
RestRegistrar
// ExternalValidator is a type that can provide external macaroon
// validation.
ExternalValidator
// DatabaseBuilder is a type that can provide lnd's main database
// backend instances.
DatabaseBuilder
// WalletConfigBuilder is a type that can provide a wallet configuration
// with a fully loaded and unlocked wallet.
WalletConfigBuilder
// ChainControlBuilder is a type that can provide a custom wallet
// implementation.
ChainControlBuilder
// AuxComponents is a set of auxiliary components that can be used by
// lnd for certain custom channel types.
AuxComponents
}
// AuxComponents is a set of auxiliary components that can be used by lnd for
// certain custom channel types.
type AuxComponents struct {
// AuxLeafStore is an optional data source that can be used by custom
// channels to fetch+store various data.
AuxLeafStore fn.Option[lnwallet.AuxLeafStore]
// TrafficShaper is an optional traffic shaper that can be used to
// control the outgoing channel of a payment.
TrafficShaper fn.Option[htlcswitch.AuxTrafficShaper]
// MsgRouter is an optional message router that if set will be used in
// place of a new blank default message router.
MsgRouter fn.Option[msgmux.Router]
// AuxFundingController is an optional controller that can be used to
// modify the way we handle certain custom channel types. It's also
// able to automatically handle new custom protocol messages related to
// the funding process.
AuxFundingController fn.Option[funding.AuxFundingController]
// AuxSigner is an optional signer that can be used to sign auxiliary
// leaves for certain custom channel types.
AuxSigner fn.Option[lnwallet.AuxSigner]
// AuxDataParser is an optional data parser that can be used to parse
// auxiliary data for certain custom channel types.
AuxDataParser fn.Option[AuxDataParser]
// AuxChanCloser is an optional channel closer that can be used to
// modify the way a coop-close transaction is constructed.
AuxChanCloser fn.Option[chancloser.AuxChanCloser]
// AuxSweeper is an optional interface that can be used to modify the
// way sweep transaction are generated.
AuxSweeper fn.Option[sweep.AuxSweeper]
// AuxContractResolver is an optional interface that can be used to
// modify the way contracts are resolved.
AuxContractResolver fn.Option[lnwallet.AuxContractResolver]
}
// DefaultWalletImpl is the default implementation of our normal, btcwallet
// backed configuration.
type DefaultWalletImpl struct {
cfg *Config
logger btclog.Logger
interceptor signal.Interceptor
watchOnly bool
migrateWatchOnly bool
pwService *walletunlocker.UnlockerService
}
// NewDefaultWalletImpl creates a new default wallet implementation.
func NewDefaultWalletImpl(cfg *Config, logger btclog.Logger,
interceptor signal.Interceptor, watchOnly bool) *DefaultWalletImpl {
return &DefaultWalletImpl{
cfg: cfg,
logger: logger,
interceptor: interceptor,
watchOnly: watchOnly,
pwService: createWalletUnlockerService(cfg),
}
}
// RegisterRestSubserver is called after lnd creates the main proxy.ServeMux
// instance. External subservers implementing this method can then register
// their own REST proxy stubs to the main server instance.
//
// NOTE: This is part of the GrpcRegistrar interface.
func (d *DefaultWalletImpl) RegisterRestSubserver(ctx context.Context,
mux *proxy.ServeMux, restProxyDest string,
restDialOpts []grpc.DialOption) error {
return lnrpc.RegisterWalletUnlockerHandlerFromEndpoint(
ctx, mux, restProxyDest, restDialOpts,
)
}
// RegisterGrpcSubserver is called for each net.Listener on which lnd creates a
// grpc.Server instance. External subservers implementing this method can then
// register their own gRPC server structs to the main server instance.
//
// NOTE: This is part of the GrpcRegistrar interface.
func (d *DefaultWalletImpl) RegisterGrpcSubserver(s *grpc.Server) error {
lnrpc.RegisterWalletUnlockerServer(s, d.pwService)
return nil
}
// ValidateMacaroon extracts the macaroon from the context's gRPC metadata,
// checks its signature, makes sure all specified permissions for the called
// method are contained within and finally ensures all caveat conditions are
// met. A non-nil error is returned if any of the checks fail.
//
// NOTE: This is part of the ExternalValidator interface.
func (d *DefaultWalletImpl) ValidateMacaroon(ctx context.Context,
requiredPermissions []bakery.Op, fullMethod string) error {
// Because the default implementation does not return any permissions,
// we shouldn't be registered as an external validator at all and this
// should never be invoked.
return fmt.Errorf("default implementation does not support external " +
"macaroon validation")
}
// Permissions returns the permissions that the external validator is
// validating. It is a map between the full HTTP URI of each RPC and its
// required macaroon permissions. If multiple action/entity tuples are specified
// per URI, they are all required. See rpcserver.go for a list of valid action
// and entity values.
//
// NOTE: This is part of the ExternalValidator interface.
func (d *DefaultWalletImpl) Permissions() map[string][]bakery.Op {
return nil
}
// BuildWalletConfig is responsible for creating or unlocking and then
// fully initializing a wallet.
//
// NOTE: This is part of the WalletConfigBuilder interface.
func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context,
dbs *DatabaseInstances, aux *AuxComponents,
interceptorChain *rpcperms.InterceptorChain,
grpcListeners []*ListenerWithSignal) (*chainreg.PartialChainControl,
*btcwallet.Config, func(), error) {
// Keep track of our various cleanup functions. We use a defer function
// as well to not repeat ourselves with every return statement.
var (
cleanUpTasks []func()
earlyExit = true
cleanUp = func() {
for _, fn := range cleanUpTasks {
if fn == nil {
continue
}
fn()
}
}
)
defer func() {
if earlyExit {
cleanUp()
}
}()
// Initialize a new block cache.
blockCache := blockcache.NewBlockCache(d.cfg.BlockCacheSize)
// Before starting the wallet, we'll create and start our Neutrino
// light client instance, if enabled, in order to allow it to sync
// while the rest of the daemon continues startup.
mainChain := d.cfg.Bitcoin
var neutrinoCS *neutrino.ChainService
if mainChain.Node == "neutrino" {
neutrinoBackend, neutrinoCleanUp, err := initNeutrinoBackend(
ctx, d.cfg, mainChain.ChainDir, blockCache,
)
if err != nil {
err := fmt.Errorf("unable to initialize neutrino "+
"backend: %v", err)
d.logger.Error(err)
return nil, nil, nil, err
}
cleanUpTasks = append(cleanUpTasks, neutrinoCleanUp)
neutrinoCS = neutrinoBackend
}
var (
walletInitParams = walletunlocker.WalletUnlockParams{
// In case we do auto-unlock, we need to be able to send
// into the channel without blocking so we buffer it.
MacResponseChan: make(chan []byte, 1),
}
privateWalletPw = lnwallet.DefaultPrivatePassphrase
publicWalletPw = lnwallet.DefaultPublicPassphrase
)
// If the user didn't request a seed, then we'll manually assume a
// wallet birthday of now, as otherwise the seed would've specified
// this information.
walletInitParams.Birthday = time.Now()
d.pwService.SetLoaderOpts([]btcwallet.LoaderOption{dbs.WalletDB})
d.pwService.SetMacaroonDB(dbs.MacaroonDB)
walletExists, err := d.pwService.WalletExists()
if err != nil {
return nil, nil, nil, err
}
if !walletExists {
interceptorChain.SetWalletNotCreated()
} else {
interceptorChain.SetWalletLocked()
}
// If we've started in auto unlock mode, then a wallet should already
// exist because we don't want to enable the RPC unlocker in that case
// for security reasons (an attacker could inject their seed since the
// RPC is unauthenticated). Only if the user explicitly wants to allow
// wallet creation we don't error out here.
if d.cfg.WalletUnlockPasswordFile != "" && !walletExists &&
!d.cfg.WalletUnlockAllowCreate {
return nil, nil, nil, fmt.Errorf("wallet unlock password file " +
"was specified but wallet does not exist; initialize " +
"the wallet before using auto unlocking")
}
// What wallet mode are we running in? We've already made sure the no
// seed backup and auto unlock aren't both set during config parsing.
switch {
// No seed backup means we're also using the default password.
case d.cfg.NoSeedBackup:
// We continue normally, the default password has already been
// set above.
// A password for unlocking is provided in a file.
case d.cfg.WalletUnlockPasswordFile != "" && walletExists:
d.logger.Infof("Attempting automatic wallet unlock with " +
"password provided in file")
pwBytes, err := os.ReadFile(d.cfg.WalletUnlockPasswordFile)
if err != nil {
return nil, nil, nil, fmt.Errorf("error reading "+
"password from file %s: %v",
d.cfg.WalletUnlockPasswordFile, err)
}
// Remove any newlines at the end of the file. The lndinit tool
// won't ever write a newline but maybe the file was provisioned
// by another process or user.
pwBytes = bytes.TrimRight(pwBytes, "\r\n")
// We have the password now, we can ask the unlocker service to
// do the unlock for us.
unlockedWallet, unloadWalletFn, err := d.pwService.LoadAndUnlock(
pwBytes, 0,
)
if err != nil {
return nil, nil, nil, fmt.Errorf("error unlocking "+
"wallet with password from file: %v", err)
}
cleanUpTasks = append(cleanUpTasks, func() {
if err := unloadWalletFn(); err != nil {
d.logger.Errorf("Could not unload wallet: %v",
err)
}
})
privateWalletPw = pwBytes
publicWalletPw = pwBytes
walletInitParams.Wallet = unlockedWallet
walletInitParams.UnloadWallet = unloadWalletFn
// If none of the automatic startup options are selected, we fall back
// to the default behavior of waiting for the wallet creation/unlocking
// over RPC.
default:
if err := d.interceptor.Notifier.NotifyReady(false); err != nil {
return nil, nil, nil, err
}
params, err := waitForWalletPassword(
d.cfg, d.pwService, []btcwallet.LoaderOption{dbs.WalletDB},
d.interceptor.ShutdownChannel(),
)
if err != nil {
err := fmt.Errorf("unable to set up wallet password "+
"listeners: %v", err)
d.logger.Error(err)
return nil, nil, nil, err
}
walletInitParams = *params
privateWalletPw = walletInitParams.Password
publicWalletPw = walletInitParams.Password
cleanUpTasks = append(cleanUpTasks, func() {
if err := walletInitParams.UnloadWallet(); err != nil {
d.logger.Errorf("Could not unload wallet: %v",
err)
}
})
if walletInitParams.RecoveryWindow > 0 {
d.logger.Infof("Wallet recovery mode enabled with "+
"address lookahead of %d addresses",
walletInitParams.RecoveryWindow)
}
}
var macaroonService *macaroons.Service
if !d.cfg.NoMacaroons {
// Create the macaroon authentication/authorization service.
rootKeyStore, err := macaroons.NewRootKeyStorage(dbs.MacaroonDB)
if err != nil {
return nil, nil, nil, err
}
macaroonService, err = macaroons.NewService(
rootKeyStore, "lnd", walletInitParams.StatelessInit,
macaroons.IPLockChecker, macaroons.IPRangeLockChecker,
macaroons.CustomChecker(interceptorChain),
)
if err != nil {
err := fmt.Errorf("unable to set up macaroon "+
"authentication: %v", err)
d.logger.Error(err)
return nil, nil, nil, err
}
cleanUpTasks = append(cleanUpTasks, func() {
if err := macaroonService.Close(); err != nil {
d.logger.Errorf("Could not close macaroon "+
"service: %v", err)
}
})
// Try to unlock the macaroon store with the private password.
// Ignore ErrAlreadyUnlocked since it could be unlocked by the
// wallet unlocker.
err = macaroonService.CreateUnlock(&privateWalletPw)
if err != nil && err != macaroons.ErrAlreadyUnlocked {
err := fmt.Errorf("unable to unlock macaroons: %w", err)
d.logger.Error(err)
return nil, nil, nil, err
}
// If we have a macaroon root key from the init wallet params,
// set the root key before baking any macaroons.
if len(walletInitParams.MacRootKey) > 0 {
err := macaroonService.SetRootKey(
walletInitParams.MacRootKey,
)
if err != nil {
return nil, nil, nil, err
}
}
// Send an admin macaroon to all our listeners that requested
// one by setting a non-nil macaroon channel.
adminMacBytes, err := bakeMacaroon(
ctx, macaroonService, adminPermissions(),
)
if err != nil {
return nil, nil, nil, err
}
for _, lis := range grpcListeners {
if lis.MacChan != nil {
lis.MacChan <- adminMacBytes
}
}
// In case we actually needed to unlock the wallet, we now need
// to create an instance of the admin macaroon and send it to
// the unlocker so it can forward it to the user. In no seed
// backup mode, there's nobody listening on the channel and we'd
// block here forever.
if !d.cfg.NoSeedBackup {
// The channel is buffered by one element so writing
// should not block here.
walletInitParams.MacResponseChan <- adminMacBytes
}
// If the user requested a stateless initialization, no macaroon
// files should be created.
if !walletInitParams.StatelessInit {
// Create default macaroon files for lncli to use if
// they don't exist.
err = genDefaultMacaroons(
ctx, macaroonService, d.cfg.AdminMacPath,
d.cfg.ReadMacPath, d.cfg.InvoiceMacPath,
)
if err != nil {
err := fmt.Errorf("unable to create macaroons "+
"%v", err)
d.logger.Error(err)
return nil, nil, nil, err
}
}
// As a security service to the user, if they requested
// stateless initialization and there are macaroon files on disk
// we log a warning.
if walletInitParams.StatelessInit {
msg := "Found %s macaroon on disk (%s) even though " +
"--stateless_init was requested. Unencrypted " +
"state is accessible by the host system. You " +
"should change the password and use " +
"--new_mac_root_key with --stateless_init to " +
"clean up and invalidate old macaroons."
if lnrpc.FileExists(d.cfg.AdminMacPath) {
d.logger.Warnf(msg, "admin", d.cfg.AdminMacPath)
}
if lnrpc.FileExists(d.cfg.ReadMacPath) {
d.logger.Warnf(msg, "readonly", d.cfg.ReadMacPath)
}
if lnrpc.FileExists(d.cfg.InvoiceMacPath) {
d.logger.Warnf(msg, "invoice", d.cfg.InvoiceMacPath)
}
}
// We add the macaroon service to our RPC interceptor. This
// will start checking macaroons against permissions on every
// RPC invocation.
interceptorChain.AddMacaroonService(macaroonService)
}
// Now that the wallet password has been provided, transition the RPC
// state into Unlocked.
interceptorChain.SetWalletUnlocked()
// Since calls to the WalletUnlocker service wait for a response on the
// macaroon channel, we close it here to make sure they return in case
// we did not return the admin macaroon above. This will be the case if
// --no-macaroons is used.
close(walletInitParams.MacResponseChan)
// We'll also close all the macaroon channels since lnd is done sending
// macaroon data over it.
for _, lis := range grpcListeners {
if lis.MacChan != nil {
close(lis.MacChan)
}
}
// With the information parsed from the configuration, create valid
// instances of the pertinent interfaces required to operate the
// Lightning Network Daemon.
//
// When we create the chain control, we need storage for the height
// hints and also the wallet itself, for these two we want them to be
// replicated, so we'll pass in the remote channel DB instance.
chainControlCfg := &chainreg.Config{
Bitcoin: d.cfg.Bitcoin,
HeightHintCacheQueryDisable: d.cfg.HeightHintCacheQueryDisable,
NeutrinoMode: d.cfg.NeutrinoMode,
BitcoindMode: d.cfg.BitcoindMode,
BtcdMode: d.cfg.BtcdMode,
HeightHintDB: dbs.HeightHintDB,
ChanStateDB: dbs.ChanStateDB.ChannelStateDB(),
NeutrinoCS: neutrinoCS,
AuxLeafStore: aux.AuxLeafStore,
AuxSigner: aux.AuxSigner,
ActiveNetParams: d.cfg.ActiveNetParams,
FeeURL: d.cfg.FeeURL,
Fee: &lncfg.Fee{
URL: d.cfg.Fee.URL,
MinUpdateTimeout: d.cfg.Fee.MinUpdateTimeout,
MaxUpdateTimeout: d.cfg.Fee.MaxUpdateTimeout,
},
Dialer: func(addr string) (net.Conn, error) {
return d.cfg.net.Dial(
"tcp", addr, d.cfg.ConnectionTimeout,
)
},
BlockCache: blockCache,
WalletUnlockParams: &walletInitParams,
}
// Let's go ahead and create the partial chain control now that is only
// dependent on our configuration and doesn't require any wallet
// specific information.
partialChainControl, pccCleanup, err := chainreg.NewPartialChainControl(
chainControlCfg,
)
cleanUpTasks = append(cleanUpTasks, pccCleanup)
if err != nil {
err := fmt.Errorf("unable to create partial chain control: %w",
err)
d.logger.Error(err)
return nil, nil, nil, err
}
walletConfig := &btcwallet.Config{
PrivatePass: privateWalletPw,
PublicPass: publicWalletPw,
Birthday: walletInitParams.Birthday,
RecoveryWindow: walletInitParams.RecoveryWindow,
NetParams: d.cfg.ActiveNetParams.Params,
CoinType: d.cfg.ActiveNetParams.CoinType,
Wallet: walletInitParams.Wallet,
LoaderOptions: []btcwallet.LoaderOption{dbs.WalletDB},
ChainSource: partialChainControl.ChainSource,
WatchOnly: d.watchOnly,
MigrateWatchOnly: d.migrateWatchOnly,
}
// Parse coin selection strategy.
switch d.cfg.CoinSelectionStrategy {
case "largest":
walletConfig.CoinSelectionStrategy = wallet.CoinSelectionLargest
case "random":
walletConfig.CoinSelectionStrategy = wallet.CoinSelectionRandom
default:
return nil, nil, nil, fmt.Errorf("unknown coin selection "+
"strategy %v", d.cfg.CoinSelectionStrategy)
}
earlyExit = false
return partialChainControl, walletConfig, cleanUp, nil
}
// proxyBlockEpoch proxies a block epoch subsections to the underlying neutrino
// rebroadcaster client.
func proxyBlockEpoch(
notifier chainntnfs.ChainNotifier) func() (*blockntfns.Subscription,
error) {
return func() (*blockntfns.Subscription, error) {
blockEpoch, err := notifier.RegisterBlockEpochNtfn(
nil,
)
if err != nil {
return nil, err
}
sub := blockntfns.Subscription{
Notifications: make(chan blockntfns.BlockNtfn, 6),
Cancel: blockEpoch.Cancel,
}
go func() {
for blk := range blockEpoch.Epochs {
ntfn := blockntfns.NewBlockConnected(
*blk.BlockHeader,
uint32(blk.Height),
)
sub.Notifications <- ntfn
}
}()
return &sub, nil
}
}
// walletReBroadcaster is a simple wrapper around the pushtx.Broadcaster
// interface to adhere to the expanded lnwallet.Rebroadcaster interface.
type walletReBroadcaster struct {
started atomic.Bool
*pushtx.Broadcaster
}
// newWalletReBroadcaster creates a new instance of the walletReBroadcaster.
func newWalletReBroadcaster(
broadcaster *pushtx.Broadcaster) *walletReBroadcaster {
return &walletReBroadcaster{
Broadcaster: broadcaster,
}
}
// Start launches all goroutines the rebroadcaster needs to operate.
func (w *walletReBroadcaster) Start() error {
defer w.started.Store(true)
return w.Broadcaster.Start()
}
// Started returns true if the broadcaster is already active.
func (w *walletReBroadcaster) Started() bool {
return w.started.Load()
}
// BuildChainControl is responsible for creating a fully populated chain
// control instance from a wallet.
//
// NOTE: This is part of the ChainControlBuilder interface.
func (d *DefaultWalletImpl) BuildChainControl(
partialChainControl *chainreg.PartialChainControl,
walletConfig *btcwallet.Config) (*chainreg.ChainControl, func(), error) {
walletController, err := btcwallet.New(
*walletConfig, partialChainControl.Cfg.BlockCache,
)
if err != nil {
err := fmt.Errorf("unable to create wallet controller: %w", err)
d.logger.Error(err)
return nil, nil, err
}
keyRing := keychain.NewBtcWalletKeyRing(
walletController.InternalWallet(), walletConfig.CoinType,
)
// Create, and start the lnwallet, which handles the core payment
// channel logic, and exposes control via proxy state machines.
lnWalletConfig := lnwallet.Config{
Database: partialChainControl.Cfg.ChanStateDB,
Notifier: partialChainControl.ChainNotifier,
WalletController: walletController,
Signer: walletController,
FeeEstimator: partialChainControl.FeeEstimator,
SecretKeyRing: keyRing,
ChainIO: walletController,
NetParams: *walletConfig.NetParams,
CoinSelectionStrategy: walletConfig.CoinSelectionStrategy,
AuxLeafStore: partialChainControl.Cfg.AuxLeafStore,
AuxSigner: partialChainControl.Cfg.AuxSigner,
}
// The broadcast is already always active for neutrino nodes, so we
// don't want to create a rebroadcast loop.
if partialChainControl.Cfg.NeutrinoCS == nil {
cs := partialChainControl.ChainSource
broadcastCfg := pushtx.Config{
Broadcast: func(tx *wire.MsgTx) error {
_, err := cs.SendRawTransaction(
tx, true,
)
return err
},
SubscribeBlocks: proxyBlockEpoch(
partialChainControl.ChainNotifier,
),
RebroadcastInterval: pushtx.DefaultRebroadcastInterval,
// In case the backend is different from neutrino we
// make sure that broadcast backend errors are mapped
// to the neutrino broadcastErr.
MapCustomBroadcastError: func(err error) error {
rpcErr := cs.MapRPCErr(err)
return broadcastErrorMapper(rpcErr)
},
}
lnWalletConfig.Rebroadcaster = newWalletReBroadcaster(
pushtx.NewBroadcaster(&broadcastCfg),
)
}
// We've created the wallet configuration now, so we can finish
// initializing the main chain control.
activeChainControl, cleanUp, err := chainreg.NewChainControl(
lnWalletConfig, walletController, partialChainControl,
)
if err != nil {
err := fmt.Errorf("unable to create chain control: %w", err)
d.logger.Error(err)
return nil, nil, err
}
return activeChainControl, cleanUp, nil
}
// RPCSignerWalletImpl is a wallet implementation that uses a remote signer over
// an RPC interface.
type RPCSignerWalletImpl struct {
// DefaultWalletImpl is the embedded instance of the default
// implementation that the remote signer uses as its watch-only wallet
// for keeping track of addresses and UTXOs.
*DefaultWalletImpl
}
// NewRPCSignerWalletImpl creates a new instance of the remote signing wallet
// implementation.
func NewRPCSignerWalletImpl(cfg *Config, logger btclog.Logger,
interceptor signal.Interceptor,
migrateWatchOnly bool) *RPCSignerWalletImpl {
return &RPCSignerWalletImpl{
DefaultWalletImpl: &DefaultWalletImpl{
cfg: cfg,
logger: logger,
interceptor: interceptor,
watchOnly: true,
migrateWatchOnly: migrateWatchOnly,
pwService: createWalletUnlockerService(cfg),
},
}
}
// BuildChainControl is responsible for creating or unlocking and then fully
// initializing a wallet and returning it as part of a fully populated chain
// control instance.
//
// NOTE: This is part of the ChainControlBuilder interface.
func (d *RPCSignerWalletImpl) BuildChainControl(
partialChainControl *chainreg.PartialChainControl,
walletConfig *btcwallet.Config) (*chainreg.ChainControl, func(), error) {
walletController, err := btcwallet.New(
*walletConfig, partialChainControl.Cfg.BlockCache,
)
if err != nil {
err := fmt.Errorf("unable to create wallet controller: %w", err)
d.logger.Error(err)
return nil, nil, err
}
baseKeyRing := keychain.NewBtcWalletKeyRing(
walletController.InternalWallet(), walletConfig.CoinType,
)
rpcKeyRing, err := rpcwallet.NewRPCKeyRing(
baseKeyRing, walletController,
d.DefaultWalletImpl.cfg.RemoteSigner, walletConfig.NetParams,
)
if err != nil {
err := fmt.Errorf("unable to create RPC remote signing wallet "+
"%v", err)
d.logger.Error(err)
return nil, nil, err
}
// Create, and start the lnwallet, which handles the core payment
// channel logic, and exposes control via proxy state machines.
lnWalletConfig := lnwallet.Config{
Database: partialChainControl.Cfg.ChanStateDB,
Notifier: partialChainControl.ChainNotifier,
WalletController: rpcKeyRing,
Signer: rpcKeyRing,
FeeEstimator: partialChainControl.FeeEstimator,
SecretKeyRing: rpcKeyRing,
ChainIO: walletController,
NetParams: *walletConfig.NetParams,
CoinSelectionStrategy: walletConfig.CoinSelectionStrategy,
}
// We've created the wallet configuration now, so we can finish
// initializing the main chain control.
activeChainControl, cleanUp, err := chainreg.NewChainControl(
lnWalletConfig, rpcKeyRing, partialChainControl,
)
if err != nil {
err := fmt.Errorf("unable to create chain control: %w", err)
d.logger.Error(err)
return nil, nil, err
}
return activeChainControl, cleanUp, nil
}
// DatabaseInstances is a struct that holds all instances to the actual
// databases that are used in lnd.
type DatabaseInstances struct {
// GraphDB is the database that stores the channel graph used for path
// finding.
GraphDB *graphdb.ChannelGraph
// ChanStateDB is the database that stores all of our node's channel
// state.
ChanStateDB *channeldb.DB
// HeightHintDB is the database that stores height hints for spends.
HeightHintDB kvdb.Backend
// InvoiceDB is the database that stores information about invoices.
InvoiceDB invoices.InvoiceDB
// MacaroonDB is the database that stores macaroon root keys.
MacaroonDB kvdb.Backend
// DecayedLogDB is the database that stores p2p related encryption
// information.
DecayedLogDB kvdb.Backend
// TowerClientDB is the database that stores the watchtower client's
// configuration.
TowerClientDB wtclient.DB
// TowerServerDB is the database that stores the watchtower server's
// configuration.
TowerServerDB watchtower.DB
// WalletDB is the configuration for loading the wallet database using
// the btcwallet's loader.
WalletDB btcwallet.LoaderOption
// NativeSQLStore holds a reference to the native SQL store that can
// be used for native SQL queries for tables that already support it.
// This may be nil if the use-native-sql flag was not set.
NativeSQLStore sqldb.DB
}
// DefaultDatabaseBuilder is a type that builds the default database backends
// for lnd, using the given configuration to decide what actual implementation
// to use.
type DefaultDatabaseBuilder struct {
cfg *Config
logger btclog.Logger
}
// NewDefaultDatabaseBuilder returns a new instance of the default database
// builder.
func NewDefaultDatabaseBuilder(cfg *Config,
logger btclog.Logger) *DefaultDatabaseBuilder {
return &DefaultDatabaseBuilder{
cfg: cfg,
logger: logger,
}
}
// BuildDatabase extracts the current databases that we'll use for normal
// operation in the daemon. A function closure that closes all opened databases
// is also returned.
func (d *DefaultDatabaseBuilder) BuildDatabase(
ctx context.Context) (*DatabaseInstances, func(), error) {
d.logger.Infof("Opening the main database, this might take a few " +
"minutes...")
cfg := d.cfg
if cfg.DB.Backend == lncfg.BoltBackend {
d.logger.Infof("Opening bbolt database, sync_freelist=%v, "+
"auto_compact=%v", !cfg.DB.Bolt.NoFreelistSync,
cfg.DB.Bolt.AutoCompact)
}
startOpenTime := time.Now()
databaseBackends, err := cfg.DB.GetBackends(
ctx, cfg.graphDatabaseDir(), cfg.networkDir, filepath.Join(
cfg.Watchtower.TowerDir, BitcoinChainName,
lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
), cfg.WtClient.Active, cfg.Watchtower.Active, d.logger,
)
if err != nil {
return nil, nil, fmt.Errorf("unable to obtain database "+
"backends: %v", err)
}
// With the full remote mode we made sure both the graph and channel
// state DB point to the same local or remote DB and the same namespace
// within that DB.
dbs := &DatabaseInstances{
HeightHintDB: databaseBackends.HeightHintDB,
MacaroonDB: databaseBackends.MacaroonDB,
DecayedLogDB: databaseBackends.DecayedLogDB,
WalletDB: databaseBackends.WalletDB,
NativeSQLStore: databaseBackends.NativeSQLStore,
}
cleanUp := func() {
// We can just close the returned close functions directly. Even
// if we decorate the channel DB with an additional struct, its
// close function still just points to the kvdb backend.
for name, closeFunc := range databaseBackends.CloseFuncs {
if err := closeFunc(); err != nil {
d.logger.Errorf("Error closing %s "+
"database: %v", name, err)
}
}
}
if databaseBackends.Remote {
d.logger.Infof("Using remote %v database! Creating "+
"graph and channel state DB instances", cfg.DB.Backend)
} else {
d.logger.Infof("Creating local graph and channel state DB " +
"instances")
}
graphDBOptions := []graphdb.KVStoreOptionModifier{
graphdb.WithRejectCacheSize(cfg.Caches.RejectCacheSize),
graphdb.WithChannelCacheSize(cfg.Caches.ChannelCacheSize),
graphdb.WithBatchCommitInterval(cfg.DB.BatchCommitInterval),
}
chanGraphOpts := []graphdb.ChanGraphOption{
graphdb.WithUseGraphCache(!cfg.DB.NoGraphCache),
}
// We want to pre-allocate the channel graph cache according to what we
// expect for mainnet to speed up memory allocation.
if cfg.ActiveNetParams.Name == chaincfg.MainNetParams.Name {
chanGraphOpts = append(
chanGraphOpts, graphdb.WithPreAllocCacheNumNodes(
graphdb.DefaultPreAllocCacheNumNodes,
),
)
}
dbs.GraphDB, err = graphdb.NewChannelGraph(&graphdb.Config{
KVDB: databaseBackends.GraphDB,
KVStoreOpts: graphDBOptions,
}, chanGraphOpts...)
if err != nil {
cleanUp()
err = fmt.Errorf("unable to open graph DB: %w", err)
d.logger.Error(err)
return nil, nil, err
}
dbOptions := []channeldb.OptionModifier{
channeldb.OptionDryRunMigration(cfg.DryRunMigration),
channeldb.OptionKeepFailedPaymentAttempts(
cfg.KeepFailedPaymentAttempts,
),
channeldb.OptionStoreFinalHtlcResolutions(
cfg.StoreFinalHtlcResolutions,
),
channeldb.OptionPruneRevocationLog(cfg.DB.PruneRevocation),
channeldb.OptionNoRevLogAmtData(cfg.DB.NoRevLogAmtData),
}
// Otherwise, we'll open two instances, one for the state we only need
// locally, and the other for things we want to ensure are replicated.
dbs.ChanStateDB, err = channeldb.CreateWithBackend(
databaseBackends.ChanStateDB, dbOptions...,
)
switch {
// Give the DB a chance to dry run the migration. Since we know that
// both the channel state and graph DBs are still always behind the same
// backend, we know this would be applied to both of those DBs.
case err == channeldb.ErrDryRunMigrationOK:
d.logger.Infof("Channel DB dry run migration successful")
return nil, nil, err
case err != nil:
cleanUp()
err = fmt.Errorf("unable to open graph DB: %w", err)
d.logger.Error(err)
return nil, nil, err
}
// Instantiate a native SQL store if the flag is set.
if d.cfg.DB.UseNativeSQL {
migrations := sqldb.GetMigrations()
// If the user has not explicitly disabled the SQL invoice
// migration, attach the custom migration function to invoice
// migration (version 7). Even if this custom migration is
// disabled, the regular native SQL store migrations will still
// run. If the database version is already above this custom
// migration's version (7), it will be skipped permanently,
// regardless of the flag.
if !d.cfg.DB.SkipNativeSQLMigration {
migrationFn := func(tx *sqlc.Queries) error {
err := invoices.MigrateInvoicesToSQL(
ctx, dbs.ChanStateDB.Backend,
dbs.ChanStateDB, tx,
invoiceMigrationBatchSize,
)
if err != nil {
return fmt.Errorf("failed to migrate "+
"invoices to SQL: %w", err)
}
// Set the invoice bucket tombstone to indicate
// that the migration has been completed.
d.logger.Debugf("Setting invoice bucket " +
"tombstone")
return dbs.ChanStateDB.SetInvoiceBucketTombstone() //nolint:ll
}
// Make sure we attach the custom migration function to
// the correct migration version.
for i := 0; i < len(migrations); i++ {
if migrations[i].Version != invoiceMigration {
continue
}
migrations[i].MigrationFn = migrationFn
}
}
// We need to apply all migrations to the native SQL store
// before we can use it.
err = dbs.NativeSQLStore.ApplyAllMigrations(ctx, migrations)
if err != nil {
cleanUp()
err = fmt.Errorf("faild to run migrations for the "+
"native SQL store: %w", err)
d.logger.Error(err)
return nil, nil, err
}
// With the DB ready and migrations applied, we can now create
// the base DB and transaction executor for the native SQL
// invoice store.
baseDB := dbs.NativeSQLStore.GetBaseDB()
executor := sqldb.NewTransactionExecutor(
baseDB, func(tx *sql.Tx) invoices.SQLInvoiceQueries {
return baseDB.WithTx(tx)
},
)
sqlInvoiceDB := invoices.NewSQLStore(
executor, clock.NewDefaultClock(),
)
dbs.InvoiceDB = sqlInvoiceDB
} else {
// Check if the invoice bucket tombstone is set. If it is, we
// need to return and ask the user switch back to using the
// native SQL store.
ripInvoices, err := dbs.ChanStateDB.GetInvoiceBucketTombstone()
if err != nil {
err = fmt.Errorf("unable to check invoice bucket "+
"tombstone: %w", err)
d.logger.Error(err)
return nil, nil, err
}
if ripInvoices {
err = fmt.Errorf("invoices bucket tombstoned, please " +
"switch back to native SQL")
d.logger.Error(err)
return nil, nil, err
}
dbs.InvoiceDB = dbs.ChanStateDB
}
// Wrap the watchtower client DB and make sure we clean up.
if cfg.WtClient.Active {
dbs.TowerClientDB, err = wtdb.OpenClientDB(
databaseBackends.TowerClientDB,
)
if err != nil {
cleanUp()
err = fmt.Errorf("unable to open %s database: %w",
lncfg.NSTowerClientDB, err)
d.logger.Error(err)
return nil, nil, err
}
}
// Wrap the watchtower server DB and make sure we clean up.
if cfg.Watchtower.Active {
dbs.TowerServerDB, err = wtdb.OpenTowerDB(
databaseBackends.TowerServerDB,
)
if err != nil {
cleanUp()
err = fmt.Errorf("unable to open %s database: %w",
lncfg.NSTowerServerDB, err)
d.logger.Error(err)
return nil, nil, err
}
}
openTime := time.Since(startOpenTime)
d.logger.Infof("Database(s) now open (time_to_open=%v)!", openTime)
return dbs, cleanUp, nil
}
// waitForWalletPassword blocks until a password is provided by the user to
// this RPC server.
func waitForWalletPassword(cfg *Config,
pwService *walletunlocker.UnlockerService,
loaderOpts []btcwallet.LoaderOption, shutdownChan <-chan struct{}) (
*walletunlocker.WalletUnlockParams, error) {
// Wait for user to provide the password.
ltndLog.Infof("Waiting for wallet encryption password. Use `lncli " +
"create` to create a wallet, `lncli unlock` to unlock an " +
"existing wallet, or `lncli changepassword` to change the " +
"password of an existing wallet and unlock it.")
// We currently don't distinguish between getting a password to be used
// for creation or unlocking, as a new wallet db will be created if
// none exists when creating the chain control.
select {
// The wallet is being created for the first time, we'll check to see
// if the user provided any entropy for seed creation. If so, then
// we'll create the wallet early to load the seed.
case initMsg := <-pwService.InitMsgs:
password := initMsg.Passphrase
cipherSeed := initMsg.WalletSeed
extendedKey := initMsg.WalletExtendedKey
watchOnlyAccounts := initMsg.WatchOnlyAccounts
recoveryWindow := initMsg.RecoveryWindow
// Before we proceed, we'll check the internal version of the
// seed. If it's greater than the current key derivation
// version, then we'll return an error as we don't understand
// this.
if cipherSeed != nil &&
!keychain.IsKnownVersion(cipherSeed.InternalVersion) {
return nil, fmt.Errorf("invalid internal "+
"seed version %v, current max version is %v",
cipherSeed.InternalVersion,
keychain.CurrentKeyDerivationVersion)
}
loader, err := btcwallet.NewWalletLoader(
cfg.ActiveNetParams.Params, recoveryWindow,
loaderOpts...,
)
if err != nil {
return nil, err
}
// With the seed, we can now use the wallet loader to create
// the wallet, then pass it back to avoid unlocking it again.
var (
birthday time.Time
newWallet *wallet.Wallet
)
switch {
// A normal cipher seed was given, use the birthday encoded in
// it and create the wallet from that.
case cipherSeed != nil:
birthday = cipherSeed.BirthdayTime()
newWallet, err = loader.CreateNewWallet(
password, password, cipherSeed.Entropy[:],
birthday,
)
// No seed was given, we're importing a wallet from its extended
// private key.
case extendedKey != nil:
birthday = initMsg.ExtendedKeyBirthday
newWallet, err = loader.CreateNewWalletExtendedKey(
password, password, extendedKey, birthday,
)
// Neither seed nor extended private key was given, so maybe the
// third option was chosen, the watch-only initialization. In
// this case we need to import each of the xpubs individually.
case watchOnlyAccounts != nil:
if !cfg.RemoteSigner.Enable {
return nil, fmt.Errorf("cannot initialize " +
"watch only wallet with remote " +
"signer config disabled")
}
birthday = initMsg.WatchOnlyBirthday
newWallet, err = loader.CreateNewWatchingOnlyWallet(
password, birthday,
)
if err != nil {
break
}
err = importWatchOnlyAccounts(newWallet, initMsg)
default:
// The unlocker service made sure either the cipher seed
// or the extended key is set so, we shouldn't get here.
// The default case is just here for readability and
// completeness.
err = fmt.Errorf("cannot create wallet, neither seed " +
"nor extended key was given")
}
if err != nil {
// Don't leave the file open in case the new wallet
// could not be created for whatever reason.
if err := loader.UnloadWallet(); err != nil {
ltndLog.Errorf("Could not unload new "+
"wallet: %v", err)
}
return nil, err
}
// For new wallets, the ResetWalletTransactions flag is a no-op.
if cfg.ResetWalletTransactions {
ltndLog.Warnf("Ignoring reset-wallet-transactions " +
"flag for new wallet as it has no effect")
}
return &walletunlocker.WalletUnlockParams{
Password: password,
Birthday: birthday,
RecoveryWindow: recoveryWindow,
Wallet: newWallet,
ChansToRestore: initMsg.ChanBackups,
UnloadWallet: loader.UnloadWallet,
StatelessInit: initMsg.StatelessInit,
MacResponseChan: pwService.MacResponseChan,
MacRootKey: initMsg.MacRootKey,
}, nil
// The wallet has already been created in the past, and is simply being
// unlocked. So we'll just return these passphrases.
case unlockMsg := <-pwService.UnlockMsgs:
// Resetting the transactions is something the user likely only
// wants to do once so we add a prominent warning to the log to
// remind the user to turn off the setting again after
// successful completion.
if cfg.ResetWalletTransactions {
ltndLog.Warnf("Dropped all transaction history from " +
"on-chain wallet. Remember to disable " +
"reset-wallet-transactions flag for next " +
"start of lnd")
}
return &walletunlocker.WalletUnlockParams{
Password: unlockMsg.Passphrase,
RecoveryWindow: unlockMsg.RecoveryWindow,
Wallet: unlockMsg.Wallet,
ChansToRestore: unlockMsg.ChanBackups,
UnloadWallet: unlockMsg.UnloadWallet,
StatelessInit: unlockMsg.StatelessInit,
MacResponseChan: pwService.MacResponseChan,
}, nil
// If we got a shutdown signal we just return with an error immediately
case <-shutdownChan:
return nil, fmt.Errorf("shutting down")
}
}
// importWatchOnlyAccounts imports all individual account xpubs into our wallet
// which we created as watch-only.
func importWatchOnlyAccounts(wallet *wallet.Wallet,
initMsg *walletunlocker.WalletInitMsg) error {
scopes := make([]waddrmgr.ScopedIndex, 0, len(initMsg.WatchOnlyAccounts))
for scope := range initMsg.WatchOnlyAccounts {
scopes = append(scopes, scope)
}
// We need to import the accounts in the correct order, otherwise the
// indices will be incorrect.
sort.Slice(scopes, func(i, j int) bool {
return scopes[i].Scope.Purpose < scopes[j].Scope.Purpose ||
scopes[i].Index < scopes[j].Index
})
for _, scope := range scopes {
addrSchema := waddrmgr.ScopeAddrMap[waddrmgr.KeyScopeBIP0084]
// We want witness pubkey hash by default, except for BIP49
// where we want mixed and BIP86 where we want taproot address
// formats.
switch scope.Scope.Purpose {
case waddrmgr.KeyScopeBIP0049Plus.Purpose,
waddrmgr.KeyScopeBIP0086.Purpose:
addrSchema = waddrmgr.ScopeAddrMap[scope.Scope]
}
// We want a human-readable account name. But for the default
// on-chain wallet we actually need to call it "default" to make
// sure everything works correctly.
name := fmt.Sprintf("%s/%d'", scope.Scope.String(), scope.Index)
if scope.Index == 0 {
name = "default"
}
_, err := wallet.ImportAccountWithScope(
name, initMsg.WatchOnlyAccounts[scope],
initMsg.WatchOnlyMasterFingerprint, scope.Scope,
addrSchema,
)
if err != nil {
return fmt.Errorf("could not import account %v: %w",
name, err)
}
}
return nil
}
// handleNeutrinoPostgresDBMigration handles the migration of the neutrino db
// to postgres. Initially we kept the neutrino db in the bolt db when running
// with kvdb postgres backend. Now now move it to postgres as well. However we
// need to make a distinction whether the user migrated the neutrino db to
// postgres via lndinit or not. Currently if the db is not migrated we start
// with a fresh db in postgres.
//
// TODO(ziggie): Also migrate the db to postgres in case it is still not
// migrated ?
func handleNeutrinoPostgresDBMigration(dbName, dbPath string,
cfg *Config) error {
if !lnrpc.FileExists(dbName) {
return nil
}
// Open bolt db to check if it is tombstoned. If it is we assume that
// the neutrino db was successfully migrated to postgres. We open it
// in read-only mode to avoid long db open times.
boltDB, err := kvdb.Open(
kvdb.BoltBackendName, dbName, true,
cfg.DB.Bolt.DBTimeout, true,
)
if err != nil {
return fmt.Errorf("failed to open bolt db: %w", err)
}
defer boltDB.Close()
isTombstoned := false
err = boltDB.View(func(tx kvdb.RTx) error {
_, err = channeldb.CheckMarkerPresent(
tx, channeldb.TombstoneKey,
)
return err
}, func() {})
if err == nil {
isTombstoned = true
}
if isTombstoned {
ltndLog.Infof("Neutrino Bolt DB is tombstoned, assuming " +
"database was successfully migrated to postgres")
return nil
}
// If the db is not tombstoned, we remove the files and start fresh with
// postgres. This is the case when a user was running lnd with the
// postgres backend from the beginning without migrating from bolt.
ltndLog.Infof("Neutrino Bolt DB found but NOT tombstoned, removing " +
"it and starting fresh with postgres")
filesToRemove := []string{
filepath.Join(dbPath, "block_headers.bin"),
filepath.Join(dbPath, "reg_filter_headers.bin"),
dbName,
}
for _, file := range filesToRemove {
if err := os.Remove(file); err != nil {
ltndLog.Warnf("Could not remove %s: %v", file, err)
}
}
return nil
}
// initNeutrinoBackend inits a new instance of the neutrino light client
// backend given a target chain directory to store the chain state.
func initNeutrinoBackend(ctx context.Context, cfg *Config, chainDir string,
blockCache *blockcache.BlockCache) (*neutrino.ChainService,
func(), error) {
// Both channel validation flags are false by default but their meaning
// is the inverse of each other. Therefore both cannot be true. For
// every other case, the neutrino.validatechannels overwrites the
// routing.assumechanvalid value.
if cfg.NeutrinoMode.ValidateChannels && cfg.Routing.AssumeChannelValid {
return nil, nil, fmt.Errorf("can't set both " +
"neutrino.validatechannels and routing." +
"assumechanvalid to true at the same time")
}
cfg.Routing.AssumeChannelValid = !cfg.NeutrinoMode.ValidateChannels
// First we'll open the database file for neutrino, creating the
// database if needed. We append the normalized network name here to
// match the behavior of btcwallet.
dbPath := filepath.Join(
chainDir, lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
)
// Ensure that the neutrino db path exists.
if err := os.MkdirAll(dbPath, 0700); err != nil {
return nil, nil, err
}
var (
db walletdb.DB
err error
)
switch {
case cfg.DB.Backend == kvdb.SqliteBackendName:
sqliteConfig := lncfg.GetSqliteConfigKVDB(cfg.DB.Sqlite)
db, err = kvdb.Open(
kvdb.SqliteBackendName, ctx, sqliteConfig, dbPath,
lncfg.SqliteNeutrinoDBName, lncfg.NSNeutrinoDB,
)
case cfg.DB.Backend == kvdb.PostgresBackendName:
dbName := filepath.Join(dbPath, lncfg.NeutrinoDBName)
// This code needs to be in place because we did not start
// the postgres backend for neutrino at the beginning. Now we
// are also moving it into the postgres backend so we can phase
// out the bolt backend.
err = handleNeutrinoPostgresDBMigration(dbName, dbPath, cfg)
if err != nil {
return nil, nil, err
}
postgresConfig := lncfg.GetPostgresConfigKVDB(cfg.DB.Postgres)
db, err = kvdb.Open(
kvdb.PostgresBackendName, ctx, postgresConfig,
lncfg.NSNeutrinoDB,
)
default:
dbName := filepath.Join(dbPath, lncfg.NeutrinoDBName)
db, err = walletdb.Create(
kvdb.BoltBackendName, dbName, !cfg.SyncFreelist,
cfg.DB.Bolt.DBTimeout, false,
)
}
if err != nil {
return nil, nil, fmt.Errorf("unable to create "+
"neutrino database: %v", err)
}
headerStateAssertion, err := parseHeaderStateAssertion(
cfg.NeutrinoMode.AssertFilterHeader,
)
if err != nil {
db.Close()
return nil, nil, err
}
// With the database open, we can now create an instance of the
// neutrino light client. We pass in relevant configuration parameters
// required.
config := neutrino.Config{
DataDir: dbPath,
Database: db,
ChainParams: *cfg.ActiveNetParams.Params,
AddPeers: cfg.NeutrinoMode.AddPeers,
ConnectPeers: cfg.NeutrinoMode.ConnectPeers,
Dialer: func(addr net.Addr) (net.Conn, error) {
return cfg.net.Dial(
addr.Network(), addr.String(),
cfg.ConnectionTimeout,
)
},
NameResolver: func(host string) ([]net.IP, error) {
addrs, err := cfg.net.LookupHost(host)
if err != nil {
return nil, err
}
ips := make([]net.IP, 0, len(addrs))
for _, strIP := range addrs {
ip := net.ParseIP(strIP)
if ip == nil {
continue
}
ips = append(ips, ip)
}
return ips, nil
},
AssertFilterHeader: headerStateAssertion,
BlockCache: blockCache.Cache,
BroadcastTimeout: cfg.NeutrinoMode.BroadcastTimeout,
PersistToDisk: cfg.NeutrinoMode.PersistFilters,
}
if cfg.NeutrinoMode.MaxPeers <= 0 {
return nil, nil, fmt.Errorf("a non-zero number must be set " +
"for neutrino max peers")
}
neutrino.MaxPeers = cfg.NeutrinoMode.MaxPeers
neutrino.BanDuration = time.Hour * 48
neutrino.UserAgentName = cfg.NeutrinoMode.UserAgentName
neutrino.UserAgentVersion = cfg.NeutrinoMode.UserAgentVersion
neutrinoCS, err := neutrino.NewChainService(config)
if err != nil {
db.Close()
return nil, nil, fmt.Errorf("unable to create neutrino light "+
"client: %v", err)
}
if err := neutrinoCS.Start(); err != nil {
db.Close()
return nil, nil, err
}
cleanUp := func() {
if err := neutrinoCS.Stop(); err != nil {
ltndLog.Infof("Unable to stop neutrino light client: "+
"%v", err)
}
db.Close()
}
return neutrinoCS, cleanUp, nil
}
// parseHeaderStateAssertion parses the user-specified neutrino header state
// into a headerfs.FilterHeader.
func parseHeaderStateAssertion(state string) (*headerfs.FilterHeader, error) {
if len(state) == 0 {
return nil, nil
}
split := strings.Split(state, ":")
if len(split) != 2 {
return nil, fmt.Errorf("header state assertion %v in "+
"unexpected format, expected format height:hash", state)
}
height, err := strconv.ParseUint(split[0], 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid filter header height: %w", err)
}
hash, err := chainhash.NewHashFromStr(split[1])
if err != nil {
return nil, fmt.Errorf("invalid filter header hash: %w", err)
}
return &headerfs.FilterHeader{
Height: uint32(height),
FilterHash: *hash,
}, nil
}
// broadcastErrorMapper maps errors from bitcoin backends other than neutrino to
// the neutrino BroadcastError which allows the Rebroadcaster which currently
// resides in the neutrino package to use all of its functionalities.
func broadcastErrorMapper(err error) error {
var returnErr error
// We only filter for specific backend errors which are relevant for the
// Rebroadcaster.
switch {
// This makes sure the tx is removed from the rebroadcaster once it is
// confirmed.
case errors.Is(err, chain.ErrTxAlreadyKnown),
errors.Is(err, chain.ErrTxAlreadyConfirmed):
returnErr = &pushtx.BroadcastError{
Code: pushtx.Confirmed,
Reason: err.Error(),
}
// Transactions which are still in mempool but might fall out because
// of low fees are rebroadcasted despite of their backend error.
case errors.Is(err, chain.ErrTxAlreadyInMempool):
returnErr = &pushtx.BroadcastError{
Code: pushtx.Mempool,
Reason: err.Error(),
}
// Transactions which are not accepted into mempool because of low fees
// in the first place are rebroadcasted despite of their backend error.
// Mempool conditions change over time so it makes sense to retry
// publishing the transaction. Moreover we log the detailed error so the
// user can intervene and increase the size of his mempool.
case errors.Is(err, chain.ErrMempoolMinFeeNotMet):
ltndLog.Warnf("Error while broadcasting transaction: %v", err)
returnErr = &pushtx.BroadcastError{
Code: pushtx.Mempool,
Reason: err.Error(),
}
}
return returnErr
}
package contractcourt
import (
"errors"
"fmt"
"io"
"sync"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/sweep"
)
// anchorResolver is a resolver that will attempt to sweep our anchor output.
type anchorResolver struct {
// anchorSignDescriptor contains the information that is required to
// sweep the anchor.
anchorSignDescriptor input.SignDescriptor
// anchor is the outpoint on the commitment transaction.
anchor wire.OutPoint
// broadcastHeight is the height that the original contract was
// broadcast to the main-chain at. We'll use this value to bound any
// historical queries to the chain for spends/confirmations.
broadcastHeight uint32
// chanPoint is the channel point of the original contract.
chanPoint wire.OutPoint
// chanType denotes the type of channel the contract belongs to.
chanType channeldb.ChannelType
// currentReport stores the current state of the resolver for reporting
// over the rpc interface.
currentReport ContractReport
// reportLock prevents concurrent access to the resolver report.
reportLock sync.Mutex
contractResolverKit
}
// newAnchorResolver instantiates a new anchor resolver.
func newAnchorResolver(anchorSignDescriptor input.SignDescriptor,
anchor wire.OutPoint, broadcastHeight uint32,
chanPoint wire.OutPoint, resCfg ResolverConfig) *anchorResolver {
amt := btcutil.Amount(anchorSignDescriptor.Output.Value)
report := ContractReport{
Outpoint: anchor,
Type: ReportOutputAnchor,
Amount: amt,
LimboBalance: amt,
RecoveredBalance: 0,
}
r := &anchorResolver{
contractResolverKit: *newContractResolverKit(resCfg),
anchorSignDescriptor: anchorSignDescriptor,
anchor: anchor,
broadcastHeight: broadcastHeight,
chanPoint: chanPoint,
currentReport: report,
}
r.initLogger(fmt.Sprintf("%T(%v)", r, r.anchor))
return r
}
// ResolverKey returns an identifier which should be globally unique for this
// particular resolver within the chain the original contract resides within.
func (c *anchorResolver) ResolverKey() []byte {
// The anchor resolver is stateless and doesn't need a database key.
return nil
}
// Resolve waits for the output to be swept.
//
// NOTE: Part of the ContractResolver interface.
func (c *anchorResolver) Resolve() (ContractResolver, error) {
// If we're already resolved, then we can exit early.
if c.IsResolved() {
c.log.Errorf("already resolved")
return nil, nil
}
var (
outcome channeldb.ResolverOutcome
spendTx *chainhash.Hash
)
select {
case sweepRes := <-c.sweepResultChan:
err := sweepRes.Err
switch {
// Anchor was swept successfully.
case err == nil:
sweepTxID := sweepRes.Tx.TxHash()
spendTx = &sweepTxID
outcome = channeldb.ResolverOutcomeClaimed
// Anchor was swept by someone else. This is possible after the
// 16 block csv lock.
case errors.Is(err, sweep.ErrRemoteSpend),
errors.Is(err, sweep.ErrInputMissing):
c.log.Warnf("our anchor spent by someone else")
outcome = channeldb.ResolverOutcomeUnclaimed
// An unexpected error occurred.
default:
c.log.Errorf("unable to sweep anchor: %v", sweepRes.Err)
return nil, sweepRes.Err
}
case <-c.quit:
return nil, errResolverShuttingDown
}
c.log.Infof("resolved in tx %v", spendTx)
// Update report to reflect that funds are no longer in limbo.
c.reportLock.Lock()
if outcome == channeldb.ResolverOutcomeClaimed {
c.currentReport.RecoveredBalance = c.currentReport.LimboBalance
}
c.currentReport.LimboBalance = 0
report := c.currentReport.resolverReport(
spendTx, channeldb.ResolverTypeAnchor, outcome,
)
c.reportLock.Unlock()
c.markResolved()
return nil, c.PutResolverReport(nil, report)
}
// Stop signals the resolver to cancel any current resolution processes, and
// suspend.
//
// NOTE: Part of the ContractResolver interface.
func (c *anchorResolver) Stop() {
c.log.Debugf("stopping...")
defer c.log.Debugf("stopped")
close(c.quit)
}
// SupplementState allows the user of a ContractResolver to supplement it with
// state required for the proper resolution of a contract.
//
// NOTE: Part of the ContractResolver interface.
func (c *anchorResolver) SupplementState(state *channeldb.OpenChannel) {
c.chanType = state.ChanType
}
// report returns a report on the resolution state of the contract.
func (c *anchorResolver) report() *ContractReport {
c.reportLock.Lock()
defer c.reportLock.Unlock()
reportCopy := c.currentReport
return &reportCopy
}
func (c *anchorResolver) Encode(w io.Writer) error {
return errors.New("serialization not supported")
}
// A compile time assertion to ensure anchorResolver meets the
// ContractResolver interface.
var _ ContractResolver = (*anchorResolver)(nil)
// Launch offers the anchor output to the sweeper.
func (c *anchorResolver) Launch() error {
if c.isLaunched() {
c.log.Tracef("already launched")
return nil
}
c.log.Debugf("launching resolver...")
c.markLaunched()
// If we're already resolved, then we can exit early.
if c.IsResolved() {
c.log.Errorf("already resolved")
return nil
}
// Attempt to update the sweep parameters to the post-confirmation
// situation. We don't want to force sweep anymore, because the anchor
// lost its special purpose to get the commitment confirmed. It is just
// an output that we want to sweep only if it is economical to do so.
//
// An exclusive group is not necessary anymore, because we know that
// this is the only anchor that can be swept.
//
// We also clear the parent tx information for cpfp, because the
// commitment tx is confirmed.
//
// After a restart or when the remote force closes, the sweeper is not
// yet aware of the anchor. In that case, it will be added as new input
// to the sweeper.
witnessType := input.CommitmentAnchor
// For taproot channels, we need to use the proper witness type.
if c.chanType.IsTaproot() {
witnessType = input.TaprootAnchorSweepSpend
}
anchorInput := input.MakeBaseInput(
&c.anchor, witnessType, &c.anchorSignDescriptor,
c.broadcastHeight, nil,
)
resultChan, err := c.Sweeper.SweepInput(
&anchorInput,
sweep.Params{
// For normal anchor sweeping, the budget is 330 sats.
Budget: btcutil.Amount(
anchorInput.SignDesc().Output.Value,
),
// There's no rush to sweep the anchor, so we use a nil
// deadline here.
DeadlineHeight: fn.None[int32](),
},
)
if err != nil {
return err
}
c.sweepResultChan = resultChan
return nil
}
package contractcourt
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"sync"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/sweep"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// justiceTxConfTarget is the number of blocks we'll use as a
// confirmation target when creating the justice transaction. We'll
// choose an aggressive target, since we want to be sure it confirms
// quickly.
justiceTxConfTarget = 2
// blocksPassedSplitPublish is the number of blocks without
// confirmation of the justice tx we'll wait before starting to publish
// smaller variants of the justice tx. We do this to mitigate an attack
// the channel peer can do by pinning the HTLC outputs of the
// commitment with low-fee HTLC transactions.
blocksPassedSplitPublish = 4
)
var (
// retributionBucket stores retribution state on disk between detecting
// a contract breach, broadcasting a justice transaction that sweeps the
// channel, and finally witnessing the justice transaction confirm on
// the blockchain. It is critical that such state is persisted on disk,
// so that if our node restarts at any point during the retribution
// procedure, we can recover and continue from the persisted state.
retributionBucket = []byte("retribution")
// taprootRetributionBucket stores the tarpoot specific retribution
// information. This includes things like the control blocks for both
// commitment outputs, and the taptweak needed to sweep each HTLC (one
// for the first and one for the second level).
taprootRetributionBucket = []byte("tap-retribution")
// errBrarShuttingDown is an error returned if the BreachArbitrator has
// been signalled to exit.
errBrarShuttingDown = errors.New("BreachArbitrator shutting down")
)
// ContractBreachEvent is an event the BreachArbitrator will receive in case a
// contract breach is observed on-chain. It contains the necessary information
// to handle the breach, and a ProcessACK closure we will use to ACK the event
// when we have safely stored all the necessary information.
type ContractBreachEvent struct {
// ChanPoint is the channel point of the breached channel.
ChanPoint wire.OutPoint
// ProcessACK is an closure that should be called with a nil error iff
// the breach retribution info is safely stored in the retribution
// store. In case storing the information to the store fails, a non-nil
// error should be used. When this closure returns, it means that the
// contract court has marked the channel pending close in the DB, and
// it is safe for the BreachArbitrator to carry on its duty.
ProcessACK func(error)
// BreachRetribution is the information needed to act on this contract
// breach.
BreachRetribution *lnwallet.BreachRetribution
}
// ChannelCloseType is an enum which signals the type of channel closure the
// peer should execute.
type ChannelCloseType uint8
const (
// CloseRegular indicates a regular cooperative channel closure
// should be attempted.
CloseRegular ChannelCloseType = iota
// CloseBreach indicates that a channel breach has been detected, and
// the link should immediately be marked as unavailable.
CloseBreach
)
// RetributionStorer provides an interface for managing a persistent map from
// wire.OutPoint -> retributionInfo. Upon learning of a breach, a
// BreachArbitrator should record the retributionInfo for the breached channel,
// which serves a checkpoint in the event that retribution needs to be resumed
// after failure. A RetributionStore provides an interface for managing the
// persisted set, as well as mapping user defined functions over the entire
// on-disk contents.
//
// Calls to RetributionStore may occur concurrently. A concrete instance of
// RetributionStore should use appropriate synchronization primitives, or
// be otherwise safe for concurrent access.
type RetributionStorer interface {
// Add persists the retributionInfo to disk, using the information's
// chanPoint as the key. This method should overwrite any existing
// entries found under the same key, and an error should be raised if
// the addition fails.
Add(retInfo *retributionInfo) error
// IsBreached queries the retribution store to see if the breach arbiter
// is aware of any breaches for the provided channel point.
IsBreached(chanPoint *wire.OutPoint) (bool, error)
// Remove deletes the retributionInfo from disk, if any exists, under
// the given key. An error should be re raised if the removal fails.
Remove(key *wire.OutPoint) error
// ForAll iterates over the existing on-disk contents and applies a
// chosen, read-only callback to each. This method should ensure that it
// immediately propagate any errors generated by the callback.
ForAll(cb func(*retributionInfo) error, reset func()) error
}
// BreachConfig bundles the required subsystems used by the breach arbiter. An
// instance of BreachConfig is passed to NewBreachArbitrator during
// instantiation.
type BreachConfig struct {
// CloseLink allows the breach arbiter to shutdown any channel links for
// which it detects a breach, ensuring now further activity will
// continue across the link. The method accepts link's channel point and
// a close type to be included in the channel close summary.
CloseLink func(*wire.OutPoint, ChannelCloseType)
// DB provides access to the user's channels, allowing the breach
// arbiter to determine the current state of a user's channels, and how
// it should respond to channel closure.
DB *channeldb.ChannelStateDB
// Estimator is used by the breach arbiter to determine an appropriate
// fee level when generating, signing, and broadcasting sweep
// transactions.
Estimator chainfee.Estimator
// GenSweepScript generates the receiving scripts for swept outputs.
GenSweepScript func() fn.Result[lnwallet.AddrWithKey]
// Notifier provides a publish/subscribe interface for event driven
// notifications regarding the confirmation of txids.
Notifier chainntnfs.ChainNotifier
// PublishTransaction facilitates the process of broadcasting a
// transaction to the network.
PublishTransaction func(*wire.MsgTx, string) error
// ContractBreaches is a channel where the BreachArbitrator will receive
// notifications in the event of a contract breach being observed. A
// ContractBreachEvent must be ACKed by the BreachArbitrator, such that
// the sending subsystem knows that the event is properly handed off.
ContractBreaches <-chan *ContractBreachEvent
// Signer is used by the breach arbiter to generate sweep transactions,
// which move coins from previously open channels back to the user's
// wallet.
Signer input.Signer
// Store is a persistent resource that maintains information regarding
// breached channels. This is used in conjunction with DB to recover
// from crashes, restarts, or other failures.
Store RetributionStorer
// AuxSweeper is an optional interface that can be used to modify the
// way sweep transaction are generated.
AuxSweeper fn.Option[sweep.AuxSweeper]
}
// BreachArbitrator is a special subsystem which is responsible for watching and
// acting on the detection of any attempted uncooperative channel breaches by
// channel counterparties. This file essentially acts as deterrence code for
// those attempting to launch attacks against the daemon. In practice it's
// expected that the logic in this file never gets executed, but it is
// important to have it in place just in case we encounter cheating channel
// counterparties.
// TODO(roasbeef): closures in config for subsystem pointers to decouple?
type BreachArbitrator struct {
started sync.Once
stopped sync.Once
cfg *BreachConfig
subscriptions map[wire.OutPoint]chan struct{}
quit chan struct{}
wg sync.WaitGroup
sync.Mutex
}
// NewBreachArbitrator creates a new instance of a BreachArbitrator initialized
// with its dependent objects.
func NewBreachArbitrator(cfg *BreachConfig) *BreachArbitrator {
return &BreachArbitrator{
cfg: cfg,
subscriptions: make(map[wire.OutPoint]chan struct{}),
quit: make(chan struct{}),
}
}
// Start is an idempotent method that officially starts the BreachArbitrator
// along with all other goroutines it needs to perform its functions.
func (b *BreachArbitrator) Start() error {
var err error
b.started.Do(func() {
brarLog.Info("Breach arbiter starting")
err = b.start()
})
return err
}
func (b *BreachArbitrator) start() error {
// Load all retributions currently persisted in the retribution store.
var breachRetInfos map[wire.OutPoint]retributionInfo
if err := b.cfg.Store.ForAll(func(ret *retributionInfo) error {
breachRetInfos[ret.chanPoint] = *ret
return nil
}, func() {
breachRetInfos = make(map[wire.OutPoint]retributionInfo)
}); err != nil {
brarLog.Errorf("Unable to create retribution info: %v", err)
return err
}
// Load all currently closed channels from disk, we will use the
// channels that have been marked fully closed to filter the retribution
// information loaded from disk. This is necessary in the event that the
// channel was marked fully closed, but was not removed from the
// retribution store.
closedChans, err := b.cfg.DB.FetchClosedChannels(false)
if err != nil {
brarLog.Errorf("Unable to fetch closing channels: %v", err)
return err
}
brarLog.Debugf("Found %v closing channels, %v retribution records",
len(closedChans), len(breachRetInfos))
// Using the set of non-pending, closed channels, reconcile any
// discrepancies between the channeldb and the retribution store by
// removing any retribution information for which we have already
// finished our responsibilities. If the removal is successful, we also
// remove the entry from our in-memory map, to avoid any further action
// for this channel.
// TODO(halseth): no need continue on IsPending once closed channels
// actually means close transaction is confirmed.
for _, chanSummary := range closedChans {
brarLog.Debugf("Working on close channel: %v, is_pending: %v",
chanSummary.ChanPoint, chanSummary.IsPending)
if chanSummary.IsPending {
continue
}
chanPoint := &chanSummary.ChanPoint
if _, ok := breachRetInfos[*chanPoint]; ok {
if err := b.cfg.Store.Remove(chanPoint); err != nil {
brarLog.Errorf("Unable to remove closed "+
"chanid=%v from breach arbiter: %v",
chanPoint, err)
return err
}
delete(breachRetInfos, *chanPoint)
brarLog.Debugf("Skipped closed channel: %v",
chanSummary.ChanPoint)
}
}
// Spawn the exactRetribution tasks to monitor and resolve any breaches
// that were loaded from the retribution store.
for chanPoint := range breachRetInfos {
retInfo := breachRetInfos[chanPoint]
brarLog.Debugf("Handling breach handoff on startup "+
"for ChannelPoint(%v)", chanPoint)
// Register for a notification when the breach transaction is
// confirmed on chain.
breachTXID := retInfo.commitHash
breachScript := retInfo.breachedOutputs[0].signDesc.Output.PkScript
confChan, err := b.cfg.Notifier.RegisterConfirmationsNtfn(
&breachTXID, breachScript, 1, retInfo.breachHeight,
)
if err != nil {
brarLog.Errorf("Unable to register for conf updates "+
"for txid: %v, err: %v", breachTXID, err)
return err
}
// Launch a new goroutine which to finalize the channel
// retribution after the breach transaction confirms.
b.wg.Add(1)
go b.exactRetribution(confChan, &retInfo)
}
// Start watching the remaining active channels!
b.wg.Add(1)
go b.contractObserver()
return nil
}
// Stop is an idempotent method that signals the BreachArbitrator to execute a
// graceful shutdown. This function will block until all goroutines spawned by
// the BreachArbitrator have gracefully exited.
func (b *BreachArbitrator) Stop() error {
b.stopped.Do(func() {
brarLog.Infof("Breach arbiter shutting down...")
defer brarLog.Debug("Breach arbiter shutdown complete")
close(b.quit)
b.wg.Wait()
})
return nil
}
// IsBreached queries the breach arbiter's retribution store to see if it is
// aware of any channel breaches for a particular channel point.
func (b *BreachArbitrator) IsBreached(chanPoint *wire.OutPoint) (bool, error) {
return b.cfg.Store.IsBreached(chanPoint)
}
// SubscribeBreachComplete is used by outside subsystems to be notified of a
// successful breach resolution.
func (b *BreachArbitrator) SubscribeBreachComplete(chanPoint *wire.OutPoint,
c chan struct{}) (bool, error) {
breached, err := b.cfg.Store.IsBreached(chanPoint)
if err != nil {
// If an error occurs, no subscription will be registered.
return false, err
}
if !breached {
// If chanPoint no longer exists in the Store, then the breach
// was cleaned up successfully. Any subscription that occurs
// happens after the breach information was persisted to the
// underlying store.
return true, nil
}
// Otherwise since the channel point is not resolved, add a
// subscription. There can only be one subscription per channel point.
b.Lock()
defer b.Unlock()
b.subscriptions[*chanPoint] = c
return false, nil
}
// notifyBreachComplete is used by the BreachArbitrator to notify outside
// subsystems that the breach resolution process is complete.
func (b *BreachArbitrator) notifyBreachComplete(chanPoint *wire.OutPoint) {
b.Lock()
defer b.Unlock()
if c, ok := b.subscriptions[*chanPoint]; ok {
close(c)
}
// Remove the subscription.
delete(b.subscriptions, *chanPoint)
}
// contractObserver is the primary goroutine for the BreachArbitrator. This
// goroutine is responsible for handling breach events coming from the
// contractcourt on the ContractBreaches channel. If a channel breach is
// detected, then the contractObserver will execute the retribution logic
// required to sweep ALL outputs from a contested channel into the daemon's
// wallet.
//
// NOTE: This MUST be run as a goroutine.
func (b *BreachArbitrator) contractObserver() {
defer b.wg.Done()
brarLog.Infof("Starting contract observer, watching for breaches.")
for {
select {
case breachEvent := <-b.cfg.ContractBreaches:
// We have been notified about a contract breach!
// Handle the handoff, making sure we ACK the event
// after we have safely added it to the retribution
// store.
b.wg.Add(1)
go b.handleBreachHandoff(breachEvent)
case <-b.quit:
return
}
}
}
// spend is used to wrap the index of the retributionInfo output that gets
// spent together with the spend details.
type spend struct {
index int
detail *chainntnfs.SpendDetail
}
// waitForSpendEvent waits for any of the breached outputs to get spent, and
// returns the spend details for those outputs. The spendNtfns map is a cache
// used to store registered spend subscriptions, in case we must call this
// method multiple times.
func (b *BreachArbitrator) waitForSpendEvent(breachInfo *retributionInfo,
spendNtfns map[wire.OutPoint]*chainntnfs.SpendEvent) ([]spend, error) {
inputs := breachInfo.breachedOutputs
// We create a channel the first goroutine that gets a spend event can
// signal. We make it buffered in case multiple spend events come in at
// the same time.
anySpend := make(chan struct{}, len(inputs))
// The allSpends channel will be used to pass spend events from all the
// goroutines that detects a spend before they are signalled to exit.
allSpends := make(chan spend, len(inputs))
// exit will be used to signal the goroutines that they can exit.
exit := make(chan struct{})
var wg sync.WaitGroup
// We'll now launch a goroutine for each of the HTLC outputs, that will
// signal the moment they detect a spend event.
for i := range inputs {
breachedOutput := &inputs[i]
brarLog.Infof("Checking spend from %v(%v) for ChannelPoint(%v)",
breachedOutput.witnessType, breachedOutput.outpoint,
breachInfo.chanPoint)
// If we have already registered for a notification for this
// output, we'll reuse it.
spendNtfn, ok := spendNtfns[breachedOutput.outpoint]
if !ok {
var err error
spendNtfn, err = b.cfg.Notifier.RegisterSpendNtfn(
&breachedOutput.outpoint,
breachedOutput.signDesc.Output.PkScript,
breachInfo.breachHeight,
)
if err != nil {
brarLog.Errorf("Unable to check for spentness "+
"of outpoint=%v: %v",
breachedOutput.outpoint, err)
// Registration may have failed if we've been
// instructed to shutdown. If so, return here
// to avoid entering an infinite loop.
select {
case <-b.quit:
return nil, errBrarShuttingDown
default:
continue
}
}
spendNtfns[breachedOutput.outpoint] = spendNtfn
}
// Launch a goroutine waiting for a spend event.
b.wg.Add(1)
wg.Add(1)
go func(index int, spendEv *chainntnfs.SpendEvent) {
defer b.wg.Done()
defer wg.Done()
select {
// The output has been taken to the second level!
case sp, ok := <-spendEv.Spend:
if !ok {
return
}
brarLog.Infof("Detected spend on %s(%v) by "+
"txid(%v) for ChannelPoint(%v)",
inputs[index].witnessType,
inputs[index].outpoint,
sp.SpenderTxHash,
breachInfo.chanPoint)
// First we send the spend event on the
// allSpends channel, such that it can be
// handled after all go routines have exited.
allSpends <- spend{index, sp}
// Finally we'll signal the anySpend channel
// that a spend was detected, such that the
// other goroutines can be shut down.
anySpend <- struct{}{}
case <-exit:
return
case <-b.quit:
return
}
}(i, spendNtfn)
}
// We'll wait for any of the outputs to be spent, or that we are
// signalled to exit.
select {
// A goroutine have signalled that a spend occurred.
case <-anySpend:
// Signal for the remaining goroutines to exit.
close(exit)
wg.Wait()
// At this point all goroutines that can send on the allSpends
// channel have exited. We can therefore safely close the
// channel before ranging over its content.
close(allSpends)
// Gather all detected spends and return them.
var spends []spend
for s := range allSpends {
breachedOutput := &inputs[s.index]
delete(spendNtfns, breachedOutput.outpoint)
spends = append(spends, s)
}
return spends, nil
case <-b.quit:
return nil, errBrarShuttingDown
}
}
// convertToSecondLevelRevoke takes a breached output, and a transaction that
// spends it to the second level, and mutates the breach output into one that
// is able to properly sweep that second level output. We'll use this function
// when we go to sweep a breached commitment transaction, but the cheating
// party has already attempted to take it to the second level.
func convertToSecondLevelRevoke(bo *breachedOutput, breachInfo *retributionInfo,
spendDetails *chainntnfs.SpendDetail) {
// In this case, we'll modify the witness type of this output to
// actually prepare for a second level revoke.
isTaproot := txscript.IsPayToTaproot(bo.signDesc.Output.PkScript)
if isTaproot {
bo.witnessType = input.TaprootHtlcSecondLevelRevoke
} else {
bo.witnessType = input.HtlcSecondLevelRevoke
}
// We'll also redirect the outpoint to this second level output, so the
// spending transaction updates it inputs accordingly.
spendingTx := spendDetails.SpendingTx
spendInputIndex := spendDetails.SpenderInputIndex
oldOp := bo.outpoint
bo.outpoint = wire.OutPoint{
Hash: spendingTx.TxHash(),
Index: spendInputIndex,
}
// Next, we need to update the amount so we can do fee estimation
// properly, and also so we can generate a valid signature as we need
// to know the new input value (the second level transactions shaves
// off some funds to fees).
newAmt := spendingTx.TxOut[spendInputIndex].Value
bo.amt = btcutil.Amount(newAmt)
bo.signDesc.Output.Value = newAmt
bo.signDesc.Output.PkScript = spendingTx.TxOut[spendInputIndex].PkScript
// For taproot outputs, the taptweak also needs to be swapped out. We
// do this unconditionally as this field isn't used at all for segwit
// v0 outputs.
bo.signDesc.TapTweak = bo.secondLevelTapTweak[:]
// Finally, we'll need to adjust the witness program in the
// SignDescriptor.
bo.signDesc.WitnessScript = bo.secondLevelWitnessScript
brarLog.Warnf("HTLC(%v) for ChannelPoint(%v) has been spent to the "+
"second-level, adjusting -> %v", oldOp, breachInfo.chanPoint,
bo.outpoint)
}
// updateBreachInfo mutates the passed breachInfo by removing or converting any
// outputs among the spends. It also counts the total and revoked funds swept
// by our justice spends.
func updateBreachInfo(breachInfo *retributionInfo, spends []spend) (
btcutil.Amount, btcutil.Amount) {
inputs := breachInfo.breachedOutputs
doneOutputs := make(map[int]struct{})
var totalFunds, revokedFunds btcutil.Amount
for _, s := range spends {
breachedOutput := &inputs[s.index]
txIn := s.detail.SpendingTx.TxIn[s.detail.SpenderInputIndex]
switch breachedOutput.witnessType {
case input.TaprootHtlcAcceptedRevoke:
fallthrough
case input.TaprootHtlcOfferedRevoke:
fallthrough
case input.HtlcAcceptedRevoke:
fallthrough
case input.HtlcOfferedRevoke:
// If the HTLC output was spent using the revocation
// key, it is our own spend, and we can forget the
// output. Otherwise it has been taken to the second
// level.
signDesc := &breachedOutput.signDesc
ok, err := input.IsHtlcSpendRevoke(txIn, signDesc)
if err != nil {
brarLog.Errorf("Unable to determine if "+
"revoke spend: %v", err)
break
}
if ok {
brarLog.Debugf("HTLC spend was our own " +
"revocation spend")
break
}
brarLog.Infof("Spend on second-level "+
"%s(%v) for ChannelPoint(%v) "+
"transitions to second-level output",
breachedOutput.witnessType,
breachedOutput.outpoint, breachInfo.chanPoint)
// In this case we'll morph our initial revoke
// spend to instead point to the second level
// output, and update the sign descriptor in the
// process.
convertToSecondLevelRevoke(
breachedOutput, breachInfo, s.detail,
)
continue
}
// Now that we have determined the spend is done by us, we
// count the total and revoked funds swept depending on the
// input type.
switch breachedOutput.witnessType {
// If the output being revoked is the remote commitment output
// or an offered HTLC output, its amount contributes to the
// value of funds being revoked from the counter party.
case input.CommitmentRevoke, input.TaprootCommitmentRevoke,
input.HtlcSecondLevelRevoke,
input.TaprootHtlcSecondLevelRevoke,
input.TaprootHtlcOfferedRevoke, input.HtlcOfferedRevoke:
revokedFunds += breachedOutput.Amount()
}
totalFunds += breachedOutput.Amount()
brarLog.Infof("Spend on %s(%v) for ChannelPoint(%v) "+
"transitions output to terminal state, "+
"removing input from justice transaction",
breachedOutput.witnessType,
breachedOutput.outpoint, breachInfo.chanPoint)
doneOutputs[s.index] = struct{}{}
}
// Filter the inputs for which we can no longer proceed.
var nextIndex int
for i := range inputs {
if _, ok := doneOutputs[i]; ok {
continue
}
inputs[nextIndex] = inputs[i]
nextIndex++
}
// Update our remaining set of outputs before continuing with
// another attempt at publication.
breachInfo.breachedOutputs = inputs[:nextIndex]
return totalFunds, revokedFunds
}
// exactRetribution is a goroutine which is executed once a contract breach has
// been detected by a breachObserver. This function is responsible for
// punishing a counterparty for violating the channel contract by sweeping ALL
// the lingering funds within the channel into the daemon's wallet.
//
// NOTE: This MUST be run as a goroutine.
//
//nolint:funlen
func (b *BreachArbitrator) exactRetribution(
confChan *chainntnfs.ConfirmationEvent, breachInfo *retributionInfo) {
defer b.wg.Done()
// TODO(roasbeef): state needs to be checkpointed here
select {
case _, ok := <-confChan.Confirmed:
// If the second value is !ok, then the channel has been closed
// signifying a daemon shutdown, so we exit.
if !ok {
return
}
// Otherwise, if this is a real confirmation notification, then
// we fall through to complete our duty.
case <-b.quit:
return
}
brarLog.Debugf("Breach transaction %v has been confirmed, sweeping "+
"revoked funds", breachInfo.commitHash)
// We may have to wait for some of the HTLC outputs to be spent to the
// second level before broadcasting the justice tx. We'll store the
// SpendEvents between each attempt to not re-register unnecessarily.
spendNtfns := make(map[wire.OutPoint]*chainntnfs.SpendEvent)
// Compute both the total value of funds being swept and the
// amount of funds that were revoked from the counter party.
var totalFunds, revokedFunds btcutil.Amount
justiceTxBroadcast:
// With the breach transaction confirmed, we now create the
// justice tx which will claim ALL the funds within the
// channel.
justiceTxs, err := b.createJusticeTx(breachInfo.breachedOutputs)
if err != nil {
brarLog.Errorf("Unable to create justice tx: %v", err)
return
}
finalTx := justiceTxs.spendAll
brarLog.Debugf("Broadcasting justice tx: %v", lnutils.SpewLogClosure(
finalTx))
// As we're about to broadcast our breach transaction, we'll notify the
// aux sweeper of our broadcast attempt first.
err = fn.MapOptionZ(b.cfg.AuxSweeper, func(aux sweep.AuxSweeper) error {
bumpReq := sweep.BumpRequest{
Inputs: finalTx.inputs,
DeliveryAddress: finalTx.sweepAddr,
ExtraTxOut: finalTx.extraTxOut,
}
return aux.NotifyBroadcast(
&bumpReq, finalTx.justiceTx, finalTx.fee, nil,
)
})
if err != nil {
brarLog.Errorf("unable to notify broadcast: %w", err)
return
}
// We'll now attempt to broadcast the transaction which finalized the
// channel's retribution against the cheating counter party.
label := labels.MakeLabel(labels.LabelTypeJusticeTransaction, nil)
err = b.cfg.PublishTransaction(finalTx.justiceTx, label)
if err != nil {
brarLog.Errorf("Unable to broadcast justice tx: %v", err)
}
// Regardless of publication succeeded or not, we now wait for any of
// the inputs to be spent. If any input got spent by the remote, we
// must recreate our justice transaction.
var (
spendChan = make(chan []spend, 1)
errChan = make(chan error, 1)
wg sync.WaitGroup
)
wg.Add(1)
go func() {
defer wg.Done()
spends, err := b.waitForSpendEvent(breachInfo, spendNtfns)
if err != nil {
errChan <- err
return
}
spendChan <- spends
}()
// We'll also register for block notifications, such that in case our
// justice tx doesn't confirm within a reasonable timeframe, we can
// start to more aggressively sweep the time sensitive outputs.
newBlockChan, err := b.cfg.Notifier.RegisterBlockEpochNtfn(nil)
if err != nil {
brarLog.Errorf("Unable to register for block notifications: %v",
err)
return
}
defer newBlockChan.Cancel()
Loop:
for {
select {
case spends := <-spendChan:
// Update the breach info with the new spends.
t, r := updateBreachInfo(breachInfo, spends)
totalFunds += t
revokedFunds += r
brarLog.Infof("%v spends from breach tx for "+
"ChannelPoint(%v) has been detected, %v "+
"revoked funds (%v total) have been claimed",
len(spends), breachInfo.chanPoint,
revokedFunds, totalFunds)
if len(breachInfo.breachedOutputs) == 0 {
brarLog.Infof("Justice for ChannelPoint(%v) "+
"has been served, %v revoked funds "+
"(%v total) have been claimed. No "+
"more outputs to sweep, marking fully "+
"resolved", breachInfo.chanPoint,
revokedFunds, totalFunds)
err = b.cleanupBreach(&breachInfo.chanPoint)
if err != nil {
brarLog.Errorf("Failed to cleanup "+
"breached ChannelPoint(%v): %v",
breachInfo.chanPoint, err)
}
// TODO(roasbeef): add peer to blacklist?
// TODO(roasbeef): close other active channels
// with offending peer
break Loop
}
brarLog.Infof("Attempting another justice tx "+
"with %d inputs",
len(breachInfo.breachedOutputs))
wg.Wait()
goto justiceTxBroadcast
// On every new block, we check whether we should republish the
// transactions.
case epoch, ok := <-newBlockChan.Epochs:
if !ok {
return
}
// If less than four blocks have passed since the
// breach confirmed, we'll continue waiting. It was
// published with a 2-block fee estimate, so it's not
// unexpected that four blocks without confirmation can
// pass.
splitHeight := breachInfo.breachHeight +
blocksPassedSplitPublish
if uint32(epoch.Height) < splitHeight {
continue Loop
}
brarLog.Warnf("Block height %v arrived without "+
"justice tx confirming (breached at "+
"height %v), splitting justice tx.",
epoch.Height, breachInfo.breachHeight)
// Otherwise we'll attempt to publish the two separate
// justice transactions that sweeps the commitment
// outputs and the HTLC outputs separately. This is to
// mitigate the case where our "spend all" justice TX
// doesn't propagate because the HTLC outputs have been
// pinned by low fee HTLC txs.
label := labels.MakeLabel(
labels.LabelTypeJusticeTransaction, nil,
)
if justiceTxs.spendCommitOuts != nil {
tx := justiceTxs.spendCommitOuts
brarLog.Debugf("Broadcasting justice tx "+
"spending commitment outs: %v",
lnutils.SpewLogClosure(tx))
err = b.cfg.PublishTransaction(
tx.justiceTx, label,
)
if err != nil {
brarLog.Warnf("Unable to broadcast "+
"commit out spending justice "+
"tx: %v", err)
}
}
if justiceTxs.spendHTLCs != nil {
tx := justiceTxs.spendHTLCs
brarLog.Debugf("Broadcasting justice tx "+
"spending HTLC outs: %v",
lnutils.SpewLogClosure(tx))
err = b.cfg.PublishTransaction(
tx.justiceTx, label,
)
if err != nil {
brarLog.Warnf("Unable to broadcast "+
"HTLC out spending justice "+
"tx: %v", err)
}
}
for _, tx := range justiceTxs.spendSecondLevelHTLCs {
tx := tx
brarLog.Debugf("Broadcasting justice tx "+
"spending second-level HTLC output: %v",
lnutils.SpewLogClosure(tx))
err = b.cfg.PublishTransaction(
tx.justiceTx, label,
)
if err != nil {
brarLog.Warnf("Unable to broadcast "+
"second-level HTLC out "+
"spending justice tx: %v", err)
}
}
case err := <-errChan:
if err != errBrarShuttingDown {
brarLog.Errorf("error waiting for "+
"spend event: %v", err)
}
break Loop
case <-b.quit:
break Loop
}
}
// Wait for our go routine to exit.
wg.Wait()
}
// cleanupBreach marks the given channel point as fully resolved and removes the
// retribution for that the channel from the retribution store.
func (b *BreachArbitrator) cleanupBreach(chanPoint *wire.OutPoint) error {
// With the channel closed, mark it in the database as such.
err := b.cfg.DB.MarkChanFullyClosed(chanPoint)
if err != nil {
return fmt.Errorf("unable to mark chan as closed: %w", err)
}
// Justice has been carried out; we can safely delete the retribution
// info from the database.
err = b.cfg.Store.Remove(chanPoint)
if err != nil {
return fmt.Errorf("unable to remove retribution from db: %w",
err)
}
// This is after the Remove call so that the chan passed in via
// SubscribeBreachComplete is always notified, no matter when it is
// called. Otherwise, if notifyBreachComplete was before Remove, a
// very rare edge case could occur in which SubscribeBreachComplete
// is called after notifyBreachComplete and before Remove, meaning the
// caller would never be notified.
b.notifyBreachComplete(chanPoint)
return nil
}
// handleBreachHandoff handles a new breach event, by writing it to disk, then
// notifies the BreachArbitrator contract observer goroutine that a channel's
// contract has been breached by the prior counterparty. Once notified the
// BreachArbitrator will attempt to sweep ALL funds within the channel using the
// information provided within the BreachRetribution generated due to the
// breach of channel contract. The funds will be swept only after the breaching
// transaction receives a necessary number of confirmations.
//
// NOTE: This MUST be run as a goroutine.
func (b *BreachArbitrator) handleBreachHandoff(
breachEvent *ContractBreachEvent) {
defer b.wg.Done()
chanPoint := breachEvent.ChanPoint
brarLog.Debugf("Handling breach handoff for ChannelPoint(%v)",
chanPoint)
// A read from this channel indicates that a channel breach has been
// detected! So we notify the main coordination goroutine with the
// information needed to bring the counterparty to justice.
breachInfo := breachEvent.BreachRetribution
brarLog.Warnf("REVOKED STATE #%v FOR ChannelPoint(%v) "+
"broadcast, REMOTE PEER IS DOING SOMETHING "+
"SKETCHY!!!", breachInfo.RevokedStateNum,
chanPoint)
// Immediately notify the HTLC switch that this link has been
// breached in order to ensure any incoming or outgoing
// multi-hop HTLCs aren't sent over this link, nor any other
// links associated with this peer.
b.cfg.CloseLink(&chanPoint, CloseBreach)
// TODO(roasbeef): need to handle case of remote broadcast
// mid-local initiated state-transition, possible
// false-positive?
// Acquire the mutex to ensure consistency between the call to
// IsBreached and Add below.
b.Lock()
// We first check if this breach info is already added to the
// retribution store.
breached, err := b.cfg.Store.IsBreached(&chanPoint)
if err != nil {
b.Unlock()
brarLog.Errorf("Unable to check breach info in DB: %v", err)
// Notify about the failed lookup and return.
breachEvent.ProcessACK(err)
return
}
// If this channel is already marked as breached in the retribution
// store, we already have handled the handoff for this breach. In this
// case we can safely ACK the handoff, and return.
if breached {
b.Unlock()
breachEvent.ProcessACK(nil)
return
}
// Using the breach information provided by the wallet and the
// channel snapshot, construct the retribution information that
// will be persisted to disk.
retInfo := newRetributionInfo(&chanPoint, breachInfo)
// Persist the pending retribution state to disk.
err = b.cfg.Store.Add(retInfo)
b.Unlock()
if err != nil {
brarLog.Errorf("Unable to persist retribution "+
"info to db: %v", err)
}
// Now that the breach has been persisted, try to send an
// acknowledgment back to the close observer with the error. If
// the ack is successful, the close observer will mark the
// channel as pending-closed in the channeldb.
breachEvent.ProcessACK(err)
// Bail if we failed to persist retribution info.
if err != nil {
return
}
// Now that a new channel contract has been added to the retribution
// store, we first register for a notification to be dispatched once
// the breach transaction (the revoked commitment transaction) has been
// confirmed in the chain to ensure we're not dealing with a moving
// target.
breachTXID := &retInfo.commitHash
breachScript := retInfo.breachedOutputs[0].signDesc.Output.PkScript
cfChan, err := b.cfg.Notifier.RegisterConfirmationsNtfn(
breachTXID, breachScript, 1, retInfo.breachHeight,
)
if err != nil {
brarLog.Errorf("Unable to register for conf updates for "+
"txid: %v, err: %v", breachTXID, err)
return
}
brarLog.Warnf("A channel has been breached with txid: %v. Waiting "+
"for confirmation, then justice will be served!", breachTXID)
// With the retribution state persisted, channel close persisted, and
// notification registered, we launch a new goroutine which will
// finalize the channel retribution after the breach transaction has
// been confirmed.
b.wg.Add(1)
go b.exactRetribution(cfChan, retInfo)
}
// breachedOutput contains all the information needed to sweep a breached
// output. A breached output is an output that we are now entitled to due to a
// revoked commitment transaction being broadcast.
type breachedOutput struct {
amt btcutil.Amount
outpoint wire.OutPoint
witnessType input.StandardWitnessType
signDesc input.SignDescriptor
confHeight uint32
secondLevelWitnessScript []byte
secondLevelTapTweak [32]byte
witnessFunc input.WitnessGenerator
resolutionBlob fn.Option[tlv.Blob]
// TODO(roasbeef): function opt and hook into brar
}
// makeBreachedOutput assembles a new breachedOutput that can be used by the
// breach arbiter to construct a justice or sweep transaction.
func makeBreachedOutput(outpoint *wire.OutPoint,
witnessType input.StandardWitnessType, secondLevelScript []byte,
signDescriptor *input.SignDescriptor, confHeight uint32,
resolutionBlob fn.Option[tlv.Blob]) breachedOutput {
amount := signDescriptor.Output.Value
return breachedOutput{
amt: btcutil.Amount(amount),
outpoint: *outpoint,
secondLevelWitnessScript: secondLevelScript,
witnessType: witnessType,
signDesc: *signDescriptor,
confHeight: confHeight,
resolutionBlob: resolutionBlob,
}
}
// Amount returns the number of satoshis contained in the breached output.
func (bo *breachedOutput) Amount() btcutil.Amount {
return bo.amt
}
// OutPoint returns the breached output's identifier that is to be included as a
// transaction input.
func (bo *breachedOutput) OutPoint() wire.OutPoint {
return bo.outpoint
}
// RequiredTxOut returns a non-nil TxOut if input commits to a certain
// transaction output. This is used in the SINGLE|ANYONECANPAY case to make
// sure any presigned input is still valid by including the output.
func (bo *breachedOutput) RequiredTxOut() *wire.TxOut {
return nil
}
// RequiredLockTime returns whether this input commits to a tx locktime that
// must be used in the transaction including it.
func (bo *breachedOutput) RequiredLockTime() (uint32, bool) {
return 0, false
}
// WitnessType returns the type of witness that must be generated to spend the
// breached output.
func (bo *breachedOutput) WitnessType() input.WitnessType {
return bo.witnessType
}
// SignDesc returns the breached output's SignDescriptor, which is used during
// signing to compute the witness.
func (bo *breachedOutput) SignDesc() *input.SignDescriptor {
return &bo.signDesc
}
// Preimage returns the preimage that was used to create the breached output.
func (bo *breachedOutput) Preimage() fn.Option[lntypes.Preimage] {
return fn.None[lntypes.Preimage]()
}
// CraftInputScript computes a valid witness that allows us to spend from the
// breached output. It does so by first generating and memoizing the witness
// generation function, which parameterized primarily by the witness type and
// sign descriptor. The method then returns the witness computed by invoking
// this function on the first and subsequent calls.
func (bo *breachedOutput) CraftInputScript(signer input.Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (*input.Script, error) {
// First, we ensure that the witness generation function has been
// initialized for this breached output.
signDesc := bo.SignDesc()
signDesc.PrevOutputFetcher = prevOutputFetcher
bo.witnessFunc = bo.witnessType.WitnessGenerator(signer, signDesc)
// Now that we have ensured that the witness generation function has
// been initialized, we can proceed to execute it and generate the
// witness for this particular breached output.
return bo.witnessFunc(txn, hashCache, txinIdx)
}
// BlocksToMaturity returns the relative timelock, as a number of blocks, that
// must be built on top of the confirmation height before the output can be
// spent.
func (bo *breachedOutput) BlocksToMaturity() uint32 {
// If the output is a to_remote output we can claim, and it's of the
// confirmed type (or is a taproot channel that always has the CSV 1),
// we must wait one block before claiming it.
switch bo.witnessType {
case input.CommitmentToRemoteConfirmed, input.TaprootRemoteCommitSpend:
return 1
}
// All other breached outputs have no CSV delay.
return 0
}
// HeightHint returns the minimum height at which a confirmed spending tx can
// occur.
func (bo *breachedOutput) HeightHint() uint32 {
return bo.confHeight
}
// UnconfParent returns information about a possibly unconfirmed parent tx.
func (bo *breachedOutput) UnconfParent() *input.TxInfo {
return nil
}
// ResolutionBlob returns a special opaque blob to be used to sweep/resolve this
// input.
func (bo *breachedOutput) ResolutionBlob() fn.Option[tlv.Blob] {
return bo.resolutionBlob
}
// Add compile-time constraint ensuring breachedOutput implements the Input
// interface.
var _ input.Input = (*breachedOutput)(nil)
// retributionInfo encapsulates all the data needed to sweep all the contested
// funds within a channel whose contract has been breached by the prior
// counterparty. This struct is used to create the justice transaction which
// spends all outputs of the commitment transaction into an output controlled
// by the wallet.
type retributionInfo struct {
commitHash chainhash.Hash
chanPoint wire.OutPoint
chainHash chainhash.Hash
breachHeight uint32
breachedOutputs []breachedOutput
}
// newRetributionInfo constructs a retributionInfo containing all the
// information required by the breach arbiter to recover funds from breached
// channels. The information is primarily populated using the BreachRetribution
// delivered by the wallet when it detects a channel breach.
func newRetributionInfo(chanPoint *wire.OutPoint,
breachInfo *lnwallet.BreachRetribution) *retributionInfo {
// Determine the number of second layer HTLCs we will attempt to sweep.
nHtlcs := len(breachInfo.HtlcRetributions)
// Initialize a slice to hold the outputs we will attempt to sweep. The
// maximum capacity of the slice is set to 2+nHtlcs to handle the case
// where the local, remote, and all HTLCs are not dust outputs. All
// HTLC outputs provided by the wallet are guaranteed to be non-dust,
// though the commitment outputs are conditionally added depending on
// the nil-ness of their sign descriptors.
breachedOutputs := make([]breachedOutput, 0, nHtlcs+2)
isTaproot := func() bool {
if breachInfo.LocalOutputSignDesc != nil {
return txscript.IsPayToTaproot(
breachInfo.LocalOutputSignDesc.Output.PkScript,
)
}
return txscript.IsPayToTaproot(
breachInfo.RemoteOutputSignDesc.Output.PkScript,
)
}()
// First, record the breach information for the local channel point if
// it is not considered dust, which is signaled by a non-nil sign
// descriptor. Here we use CommitmentNoDelay (or
// CommitmentNoDelayTweakless for newer commitments) since this output
// belongs to us and has no time-based constraints on spending. For
// taproot channels, this is a normal spend from our output on the
// commitment of the remote party.
if breachInfo.LocalOutputSignDesc != nil {
var witnessType input.StandardWitnessType
switch {
case isTaproot:
witnessType = input.TaprootRemoteCommitSpend
case !isTaproot &&
breachInfo.LocalOutputSignDesc.SingleTweak == nil:
witnessType = input.CommitSpendNoDelayTweakless
case !isTaproot:
witnessType = input.CommitmentNoDelay
}
// If the local delay is non-zero, it means this output is of
// the confirmed to_remote type.
if !isTaproot && breachInfo.LocalDelay != 0 {
witnessType = input.CommitmentToRemoteConfirmed
}
localOutput := makeBreachedOutput(
&breachInfo.LocalOutpoint,
witnessType,
// No second level script as this is a commitment
// output.
nil,
breachInfo.LocalOutputSignDesc,
breachInfo.BreachHeight,
breachInfo.LocalResolutionBlob,
)
breachedOutputs = append(breachedOutputs, localOutput)
}
// Second, record the same information regarding the remote outpoint,
// again if it is not dust, which belongs to the party who tried to
// steal our money! Here we set witnessType of the breachedOutput to
// CommitmentRevoke, since we will be using a revoke key, withdrawing
// the funds from the commitment transaction immediately.
if breachInfo.RemoteOutputSignDesc != nil {
var witType input.StandardWitnessType
if isTaproot {
witType = input.TaprootCommitmentRevoke
} else {
witType = input.CommitmentRevoke
}
remoteOutput := makeBreachedOutput(
&breachInfo.RemoteOutpoint,
witType,
// No second level script as this is a commitment
// output.
nil,
breachInfo.RemoteOutputSignDesc,
breachInfo.BreachHeight,
breachInfo.RemoteResolutionBlob,
)
breachedOutputs = append(breachedOutputs, remoteOutput)
}
// Lastly, for each of the breached HTLC outputs, record each as a
// breached output with the appropriate witness type based on its
// directionality. All HTLC outputs provided by the wallet are assumed
// to be non-dust.
for i, breachedHtlc := range breachInfo.HtlcRetributions {
// Using the breachedHtlc's incoming flag, determine the
// appropriate witness type that needs to be generated in order
// to sweep the HTLC output.
var htlcWitnessType input.StandardWitnessType
switch {
case isTaproot && breachedHtlc.IsIncoming:
htlcWitnessType = input.TaprootHtlcAcceptedRevoke
case isTaproot && !breachedHtlc.IsIncoming:
htlcWitnessType = input.TaprootHtlcOfferedRevoke
case !isTaproot && breachedHtlc.IsIncoming:
htlcWitnessType = input.HtlcAcceptedRevoke
case !isTaproot && !breachedHtlc.IsIncoming:
htlcWitnessType = input.HtlcOfferedRevoke
}
htlcOutput := makeBreachedOutput(
&breachInfo.HtlcRetributions[i].OutPoint,
htlcWitnessType,
breachInfo.HtlcRetributions[i].SecondLevelWitnessScript,
&breachInfo.HtlcRetributions[i].SignDesc,
breachInfo.BreachHeight,
breachInfo.HtlcRetributions[i].ResolutionBlob,
)
// For taproot outputs, we also need to hold onto the second
// level tap tweak as well.
//nolint:ll
htlcOutput.secondLevelTapTweak = breachedHtlc.SecondLevelTapTweak
breachedOutputs = append(breachedOutputs, htlcOutput)
}
return &retributionInfo{
commitHash: breachInfo.BreachTxHash,
chainHash: breachInfo.ChainHash,
chanPoint: *chanPoint,
breachedOutputs: breachedOutputs,
breachHeight: breachInfo.BreachHeight,
}
}
// justiceTxVariants is a struct that holds transactions which exacts "justice"
// by sweeping ALL the funds within the channel which we are now entitled to
// due to a breach of the channel's contract by the counterparty. There are
// four variants of justice transactions:
//
// 1. The "normal" justice tx that spends all breached outputs.
// 2. A tx that spends only the breached to_local output and to_remote output
// (can be nil if none of these exist).
// 3. A tx that spends all the breached commitment level HTLC outputs (can be
// nil if none of these exist or if all have been taken to the second level).
// 4. A set of txs that spend all the second-level HTLC outputs (can be empty if
// no HTLC second-level txs have been confirmed).
//
// The reason we create these three variants, is that in certain cases (like
// with the anchor output HTLC malleability), the channel counter party can pin
// the HTLC outputs with low fee children, hindering our normal justice tx that
// attempts to spend these outputs from propagating. In this case we want to
// spend the to_local output and commitment level HTLC outputs separately,
// before the CSV locks expire.
type justiceTxVariants struct {
spendAll *justiceTxCtx
spendCommitOuts *justiceTxCtx
spendHTLCs *justiceTxCtx
spendSecondLevelHTLCs []*justiceTxCtx
}
// createJusticeTx creates transactions which exacts "justice" by sweeping ALL
// the funds within the channel which we are now entitled to due to a breach of
// the channel's contract by the counterparty. This function returns a *fully*
// signed transaction with the witness for each input fully in place.
func (b *BreachArbitrator) createJusticeTx(
breachedOutputs []breachedOutput) (*justiceTxVariants, error) {
var (
allInputs []input.Input
commitInputs []input.Input
htlcInputs []input.Input
secondLevelInputs []input.Input
)
for i := range breachedOutputs {
// Grab locally scoped reference to breached output.
inp := &breachedOutputs[i]
allInputs = append(allInputs, inp)
// Check if the input is from a commitment output, a commitment
// level HTLC output or a second level HTLC output.
switch inp.WitnessType() {
case input.HtlcAcceptedRevoke, input.HtlcOfferedRevoke,
input.TaprootHtlcAcceptedRevoke,
input.TaprootHtlcOfferedRevoke:
htlcInputs = append(htlcInputs, inp)
case input.HtlcSecondLevelRevoke,
input.TaprootHtlcSecondLevelRevoke:
secondLevelInputs = append(secondLevelInputs, inp)
default:
commitInputs = append(commitInputs, inp)
}
}
var (
txs = &justiceTxVariants{}
err error
)
// For each group of inputs, create a tx that spends them.
txs.spendAll, err = b.createSweepTx(allInputs...)
if err != nil {
return nil, err
}
txs.spendCommitOuts, err = b.createSweepTx(commitInputs...)
if err != nil {
brarLog.Errorf("could not create sweep tx for commitment "+
"outputs: %v", err)
}
txs.spendHTLCs, err = b.createSweepTx(htlcInputs...)
if err != nil {
brarLog.Errorf("could not create sweep tx for HTLC outputs: %v",
err)
}
// TODO(roasbeef): only register one of them?
secondLevelSweeps := make([]*justiceTxCtx, 0, len(secondLevelInputs))
for _, input := range secondLevelInputs {
sweepTx, err := b.createSweepTx(input)
if err != nil {
brarLog.Errorf("could not create sweep tx for "+
"second-level HTLC output: %v", err)
continue
}
secondLevelSweeps = append(secondLevelSweeps, sweepTx)
}
txs.spendSecondLevelHTLCs = secondLevelSweeps
return txs, nil
}
// justiceTxCtx contains the justice transaction along with other related meta
// data.
type justiceTxCtx struct {
justiceTx *wire.MsgTx
sweepAddr lnwallet.AddrWithKey
extraTxOut fn.Option[sweep.SweepOutput]
fee btcutil.Amount
inputs []input.Input
}
// createSweepTx creates a tx that sweeps the passed inputs back to our wallet.
func (b *BreachArbitrator) createSweepTx(
inputs ...input.Input) (*justiceTxCtx, error) {
if len(inputs) == 0 {
return nil, nil
}
// We will assemble the breached outputs into a slice of spendable
// outputs, while simultaneously computing the estimated weight of the
// transaction.
var (
spendableOutputs []input.Input
weightEstimate input.TxWeightEstimator
)
// Allocate enough space to potentially hold each of the breached
// outputs in the retribution info.
spendableOutputs = make([]input.Input, 0, len(inputs))
// The justice transaction we construct will be a segwit transaction
// that pays to a p2tr output. Components such as the version,
// nLockTime, and output are already included in the TxWeightEstimator.
weightEstimate.AddP2TROutput()
// If any of our inputs has a resolution blob, then we'll add another
// P2TR _output_, since we'll want to separate the custom channel
// outputs from the regular, BTC only outputs. So we only need one such
// output, which'll carry the custom channel "valuables" from both the
// breached commitment and HTLC outputs.
hasBlobs := fn.Any(inputs, func(i input.Input) bool {
return i.ResolutionBlob().IsSome()
})
if hasBlobs {
weightEstimate.AddP2TROutput()
}
// Next, we iterate over the breached outputs contained in the
// retribution info. For each, we switch over the witness type such
// that we contribute the appropriate weight for each input and
// witness, finally adding to our list of spendable outputs.
for i := range inputs {
// Grab locally scoped reference to breached output.
inp := inputs[i]
// First, determine the appropriate estimated witness weight
// for the give witness type of this breached output. If the
// witness weight cannot be estimated, we will omit it from the
// transaction.
witnessWeight, _, err := inp.WitnessType().SizeUpperBound()
if err != nil {
brarLog.Warnf("could not determine witness weight "+
"for breached output in retribution info: %v",
err)
continue
}
weightEstimate.AddWitnessInput(witnessWeight)
// Finally, append this input to our list of spendable outputs.
spendableOutputs = append(spendableOutputs, inp)
}
txWeight := weightEstimate.Weight()
return b.sweepSpendableOutputsTxn(txWeight, spendableOutputs...)
}
// sweepSpendableOutputsTxn creates a signed transaction from a sequence of
// spendable outputs by sweeping the funds into a single p2wkh output.
func (b *BreachArbitrator) sweepSpendableOutputsTxn(txWeight lntypes.WeightUnit,
inputs ...input.Input) (*justiceTxCtx, error) {
// First, we obtain a new public key script from the wallet which we'll
// sweep the funds to.
// TODO(roasbeef): possibly create many outputs to minimize change in
// the future?
pkScript, err := b.cfg.GenSweepScript().Unpack()
if err != nil {
return nil, err
}
// Compute the total amount contained in the inputs.
var totalAmt btcutil.Amount
for _, inp := range inputs {
totalAmt += btcutil.Amount(inp.SignDesc().Output.Value)
}
// We'll actually attempt to target inclusion within the next two
// blocks as we'd like to sweep these funds back into our wallet ASAP.
feePerKw, err := b.cfg.Estimator.EstimateFeePerKW(justiceTxConfTarget)
if err != nil {
return nil, err
}
txFee := feePerKw.FeeForWeight(txWeight)
// At this point, we'll check to see if we have any extra outputs to
// add from the aux sweeper.
extraChangeOut := fn.MapOptionZ(
b.cfg.AuxSweeper,
func(aux sweep.AuxSweeper) fn.Result[sweep.SweepOutput] {
return aux.DeriveSweepAddr(inputs, pkScript)
},
)
if err := extraChangeOut.Err(); err != nil {
return nil, err
}
// TODO(roasbeef): already start to siphon their funds into fees
sweepAmt := int64(totalAmt - txFee)
// With the fee calculated, we can now create the transaction using the
// information gathered above and the provided retribution information.
txn := wire.NewMsgTx(2)
// First, we'll add the extra sweep output if it exists, subtracting the
// amount from the sweep amt.
if b.cfg.AuxSweeper.IsSome() {
extraChangeOut.WhenOk(func(o sweep.SweepOutput) {
sweepAmt -= o.Value
txn.AddTxOut(&o.TxOut)
})
}
// Next, we'll add the output to which our funds will be deposited.
txn.AddTxOut(&wire.TxOut{
PkScript: pkScript.DeliveryAddress,
Value: sweepAmt,
})
// TODO(roasbeef): add other output change modify sweep amt
// Next, we add all of the spendable outputs as inputs to the
// transaction.
for _, inp := range inputs {
txn.AddTxIn(&wire.TxIn{
PreviousOutPoint: inp.OutPoint(),
Sequence: inp.BlocksToMaturity(),
})
}
// Before signing the transaction, check to ensure that it meets some
// basic validity requirements.
btx := btcutil.NewTx(txn)
if err := blockchain.CheckTransactionSanity(btx); err != nil {
return nil, err
}
// Create a sighash cache to improve the performance of hashing and
// signing SigHashAll inputs.
prevOutputFetcher, err := input.MultiPrevOutFetcher(inputs)
if err != nil {
return nil, err
}
hashCache := txscript.NewTxSigHashes(txn, prevOutputFetcher)
// Create a closure that encapsulates the process of initializing a
// particular output's witness generation function, computing the
// witness, and attaching it to the transaction. This function accepts
// an integer index representing the intended txin index, and the
// breached output from which it will spend.
addWitness := func(idx int, so input.Input) error {
// First, we construct a valid witness for this outpoint and
// transaction using the SpendableOutput's witness generation
// function.
inputScript, err := so.CraftInputScript(
b.cfg.Signer, txn, hashCache, prevOutputFetcher, idx,
)
if err != nil {
return err
}
// Then, we add the witness to the transaction at the
// appropriate txin index.
txn.TxIn[idx].Witness = inputScript.Witness
return nil
}
// Finally, generate a witness for each output and attach it to the
// transaction.
for i, inp := range inputs {
if err := addWitness(i, inp); err != nil {
return nil, err
}
}
return &justiceTxCtx{
justiceTx: txn,
sweepAddr: pkScript,
extraTxOut: extraChangeOut.OkToSome(),
fee: txFee,
inputs: inputs,
}, nil
}
// RetributionStore handles persistence of retribution states to disk and is
// backed by a boltdb bucket. The primary responsibility of the retribution
// store is to ensure that we can recover from a restart in the middle of a
// breached contract retribution.
type RetributionStore struct {
db kvdb.Backend
}
// NewRetributionStore creates a new instance of a RetributionStore.
func NewRetributionStore(db kvdb.Backend) *RetributionStore {
return &RetributionStore{
db: db,
}
}
// taprootBriefcaseFromRetInfo creates a taprootBriefcase from a retribution
// info struct. This stores all the tap tweak information we need to inrder to
// be able to hadnel breaches after a restart.
func taprootBriefcaseFromRetInfo(retInfo *retributionInfo) *taprootBriefcase {
tapCase := newTaprootBriefcase()
for _, bo := range retInfo.breachedOutputs {
switch bo.WitnessType() {
// For spending from our commitment output on the remote
// commitment, we'll need to stash the control block.
case input.TaprootRemoteCommitSpend:
//nolint:ll
tapCase.CtrlBlocks.Val.CommitSweepCtrlBlock = bo.signDesc.ControlBlock
bo.resolutionBlob.WhenSome(func(blob tlv.Blob) {
tapCase.SettledCommitBlob = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType2](
blob,
),
)
})
// To spend the revoked output again, we'll store the same
// control block value as above, but in a different place.
case input.TaprootCommitmentRevoke:
//nolint:ll
tapCase.CtrlBlocks.Val.RevokeSweepCtrlBlock = bo.signDesc.ControlBlock
bo.resolutionBlob.WhenSome(func(blob tlv.Blob) {
tapCase.BreachedCommitBlob = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType3](
blob,
),
)
})
// For spending the HTLC outputs, we'll store the first and
// second level tweak values.
case input.TaprootHtlcAcceptedRevoke:
fallthrough
case input.TaprootHtlcOfferedRevoke:
resID := newResolverID(bo.OutPoint())
var firstLevelTweak [32]byte
copy(firstLevelTweak[:], bo.signDesc.TapTweak)
secondLevelTweak := bo.secondLevelTapTweak
//nolint:ll
tapCase.TapTweaks.Val.BreachedHtlcTweaks[resID] = firstLevelTweak
//nolint:ll
tapCase.TapTweaks.Val.BreachedSecondLevelHltcTweaks[resID] = secondLevelTweak
}
}
return tapCase
}
// applyTaprootRetInfo attaches the taproot specific information in the tapCase
// to the passed retInfo struct.
func applyTaprootRetInfo(tapCase *taprootBriefcase,
retInfo *retributionInfo) error {
for i := range retInfo.breachedOutputs {
bo := retInfo.breachedOutputs[i]
switch bo.WitnessType() {
// For spending from our commitment output on the remote
// commitment, we'll apply the control block.
case input.TaprootRemoteCommitSpend:
//nolint:ll
bo.signDesc.ControlBlock = tapCase.CtrlBlocks.Val.CommitSweepCtrlBlock
tapCase.SettledCommitBlob.WhenSomeV(
func(blob tlv.Blob) {
bo.resolutionBlob = fn.Some(blob)
},
)
// To spend the revoked output again, we'll apply the same
// control block value as above, but to a different place.
case input.TaprootCommitmentRevoke:
//nolint:ll
bo.signDesc.ControlBlock = tapCase.CtrlBlocks.Val.RevokeSweepCtrlBlock
tapCase.BreachedCommitBlob.WhenSomeV(
func(blob tlv.Blob) {
bo.resolutionBlob = fn.Some(blob)
},
)
// For spending the HTLC outputs, we'll apply the first and
// second level tweak values.
case input.TaprootHtlcAcceptedRevoke:
fallthrough
case input.TaprootHtlcOfferedRevoke:
resID := newResolverID(bo.OutPoint())
//nolint:ll
tap1, ok := tapCase.TapTweaks.Val.BreachedHtlcTweaks[resID]
if !ok {
return fmt.Errorf("unable to find taproot "+
"tweak for: %v", bo.OutPoint())
}
bo.signDesc.TapTweak = tap1[:]
//nolint:ll
tap2, ok := tapCase.TapTweaks.Val.BreachedSecondLevelHltcTweaks[resID]
if !ok {
return fmt.Errorf("unable to find taproot "+
"tweak for: %v", bo.OutPoint())
}
bo.secondLevelTapTweak = tap2
}
retInfo.breachedOutputs[i] = bo
}
return nil
}
// Add adds a retribution state to the RetributionStore, which is then persisted
// to disk.
func (rs *RetributionStore) Add(ret *retributionInfo) error {
return kvdb.Update(rs.db, func(tx kvdb.RwTx) error {
// If this is our first contract breach, the retributionBucket
// won't exist, in which case, we just create a new bucket.
retBucket, err := tx.CreateTopLevelBucket(retributionBucket)
if err != nil {
return err
}
tapRetBucket, err := tx.CreateTopLevelBucket(
taprootRetributionBucket,
)
if err != nil {
return err
}
var outBuf bytes.Buffer
err = graphdb.WriteOutpoint(&outBuf, &ret.chanPoint)
if err != nil {
return err
}
var retBuf bytes.Buffer
if err := ret.Encode(&retBuf); err != nil {
return err
}
err = retBucket.Put(outBuf.Bytes(), retBuf.Bytes())
if err != nil {
return err
}
// If this isn't a taproot channel, then we can exit early here
// as there's no extra data to write.
switch {
case len(ret.breachedOutputs) == 0:
return nil
case !txscript.IsPayToTaproot(
ret.breachedOutputs[0].signDesc.Output.PkScript,
):
return nil
}
// We'll also map the ret info into the taproot storage
// structure we need for taproot channels.
var b bytes.Buffer
tapRetcase := taprootBriefcaseFromRetInfo(ret)
if err := tapRetcase.Encode(&b); err != nil {
return err
}
return tapRetBucket.Put(outBuf.Bytes(), b.Bytes())
}, func() {})
}
// IsBreached queries the retribution store to discern if this channel was
// previously breached. This is used when connecting to a peer to determine if
// it is safe to add a link to the htlcswitch, as we should never add a channel
// that has already been breached.
func (rs *RetributionStore) IsBreached(chanPoint *wire.OutPoint) (bool, error) {
var found bool
err := kvdb.View(rs.db, func(tx kvdb.RTx) error {
retBucket := tx.ReadBucket(retributionBucket)
if retBucket == nil {
return nil
}
var chanBuf bytes.Buffer
err := graphdb.WriteOutpoint(&chanBuf, chanPoint)
if err != nil {
return err
}
retInfo := retBucket.Get(chanBuf.Bytes())
if retInfo != nil {
found = true
}
return nil
}, func() {
found = false
})
return found, err
}
// Remove removes a retribution state and finalized justice transaction by
// channel point from the retribution store.
func (rs *RetributionStore) Remove(chanPoint *wire.OutPoint) error {
return kvdb.Update(rs.db, func(tx kvdb.RwTx) error {
retBucket := tx.ReadWriteBucket(retributionBucket)
tapRetBucket, err := tx.CreateTopLevelBucket(
taprootRetributionBucket,
)
if err != nil {
return err
}
// We return an error if the bucket is not already created,
// since normal operation of the breach arbiter should never
// try to remove a finalized retribution state that is not
// already stored in the db.
if retBucket == nil {
return errors.New("unable to remove retribution " +
"because the retribution bucket doesn't exist")
}
// Serialize the channel point we are intending to remove.
var chanBuf bytes.Buffer
err = graphdb.WriteOutpoint(&chanBuf, chanPoint)
if err != nil {
return err
}
chanBytes := chanBuf.Bytes()
// Remove the persisted retribution info and finalized justice
// transaction.
if err := retBucket.Delete(chanBytes); err != nil {
return err
}
return tapRetBucket.Delete(chanBytes)
}, func() {})
}
// ForAll iterates through all stored retributions and executes the passed
// callback function on each retribution.
func (rs *RetributionStore) ForAll(cb func(*retributionInfo) error,
reset func()) error {
return kvdb.View(rs.db, func(tx kvdb.RTx) error {
// If the bucket does not exist, then there are no pending
// retributions.
retBucket := tx.ReadBucket(retributionBucket)
if retBucket == nil {
return nil
}
tapRetBucket := tx.ReadBucket(
taprootRetributionBucket,
)
// Otherwise, we fetch each serialized retribution info,
// deserialize it, and execute the passed in callback function
// on it.
return retBucket.ForEach(func(k, retBytes []byte) error {
ret := &retributionInfo{}
err := ret.Decode(bytes.NewBuffer(retBytes))
if err != nil {
return err
}
tapInfoBytes := tapRetBucket.Get(k)
if tapInfoBytes != nil {
var tapCase taprootBriefcase
err := tapCase.Decode(
bytes.NewReader(tapInfoBytes),
)
if err != nil {
return err
}
err = applyTaprootRetInfo(&tapCase, ret)
if err != nil {
return err
}
}
return cb(ret)
})
}, reset)
}
// Encode serializes the retribution into the passed byte stream.
func (ret *retributionInfo) Encode(w io.Writer) error {
var scratch [4]byte
if _, err := w.Write(ret.commitHash[:]); err != nil {
return err
}
if err := graphdb.WriteOutpoint(w, &ret.chanPoint); err != nil {
return err
}
if _, err := w.Write(ret.chainHash[:]); err != nil {
return err
}
binary.BigEndian.PutUint32(scratch[:], ret.breachHeight)
if _, err := w.Write(scratch[:]); err != nil {
return err
}
nOutputs := len(ret.breachedOutputs)
if err := wire.WriteVarInt(w, 0, uint64(nOutputs)); err != nil {
return err
}
for _, output := range ret.breachedOutputs {
if err := output.Encode(w); err != nil {
return err
}
}
return nil
}
// Decode deserializes a retribution from the passed byte stream.
func (ret *retributionInfo) Decode(r io.Reader) error {
var scratch [32]byte
if _, err := io.ReadFull(r, scratch[:]); err != nil {
return err
}
hash, err := chainhash.NewHash(scratch[:])
if err != nil {
return err
}
ret.commitHash = *hash
if err := graphdb.ReadOutpoint(r, &ret.chanPoint); err != nil {
return err
}
if _, err := io.ReadFull(r, scratch[:]); err != nil {
return err
}
chainHash, err := chainhash.NewHash(scratch[:])
if err != nil {
return err
}
ret.chainHash = *chainHash
if _, err := io.ReadFull(r, scratch[:4]); err != nil {
return err
}
ret.breachHeight = binary.BigEndian.Uint32(scratch[:4])
nOutputsU64, err := wire.ReadVarInt(r, 0)
if err != nil {
return err
}
nOutputs := int(nOutputsU64)
ret.breachedOutputs = make([]breachedOutput, nOutputs)
for i := range ret.breachedOutputs {
if err := ret.breachedOutputs[i].Decode(r); err != nil {
return err
}
}
return nil
}
// Encode serializes a breachedOutput into the passed byte stream.
func (bo *breachedOutput) Encode(w io.Writer) error {
var scratch [8]byte
binary.BigEndian.PutUint64(scratch[:8], uint64(bo.amt))
if _, err := w.Write(scratch[:8]); err != nil {
return err
}
if err := graphdb.WriteOutpoint(w, &bo.outpoint); err != nil {
return err
}
err := input.WriteSignDescriptor(w, &bo.signDesc)
if err != nil {
return err
}
err = wire.WriteVarBytes(w, 0, bo.secondLevelWitnessScript)
if err != nil {
return err
}
binary.BigEndian.PutUint16(scratch[:2], uint16(bo.witnessType))
if _, err := w.Write(scratch[:2]); err != nil {
return err
}
return nil
}
// Decode deserializes a breachedOutput from the passed byte stream.
func (bo *breachedOutput) Decode(r io.Reader) error {
var scratch [8]byte
if _, err := io.ReadFull(r, scratch[:8]); err != nil {
return err
}
bo.amt = btcutil.Amount(binary.BigEndian.Uint64(scratch[:8]))
if err := graphdb.ReadOutpoint(r, &bo.outpoint); err != nil {
return err
}
if err := input.ReadSignDescriptor(r, &bo.signDesc); err != nil {
return err
}
wScript, err := wire.ReadVarBytes(r, 0, 1000, "witness script")
if err != nil {
return err
}
bo.secondLevelWitnessScript = wScript
if _, err := io.ReadFull(r, scratch[:2]); err != nil {
return err
}
bo.witnessType = input.StandardWitnessType(
binary.BigEndian.Uint16(scratch[:2]),
)
return nil
}
package contractcourt
import (
"encoding/binary"
"fmt"
"io"
"github.com/lightningnetwork/lnd/channeldb"
)
// breachResolver is a resolver that will handle breached closes. In the
// future, this will likely take over the duties the current BreachArbitrator
// has.
type breachResolver struct {
// subscribed denotes whether or not the breach resolver has subscribed
// to the BreachArbitrator for breach resolution.
subscribed bool
// replyChan is closed when the breach arbiter has completed serving
// justice.
replyChan chan struct{}
contractResolverKit
}
// newBreachResolver instantiates a new breach resolver.
func newBreachResolver(resCfg ResolverConfig) *breachResolver {
r := &breachResolver{
contractResolverKit: *newContractResolverKit(resCfg),
replyChan: make(chan struct{}),
}
r.initLogger(fmt.Sprintf("%T(%v)", r, r.ChanPoint))
return r
}
// ResolverKey returns the unique identifier for this resolver.
func (b *breachResolver) ResolverKey() []byte {
key := newResolverID(b.ChanPoint)
return key[:]
}
// Resolve queries the BreachArbitrator to see if the justice transaction has
// been broadcast.
//
// NOTE: Part of the ContractResolver interface.
//
// TODO(yy): let sweeper handle the breach inputs.
func (b *breachResolver) Resolve() (ContractResolver, error) {
if !b.subscribed {
complete, err := b.SubscribeBreachComplete(
&b.ChanPoint, b.replyChan,
)
if err != nil {
return nil, err
}
// If the breach resolution process is already complete, then
// we can cleanup and checkpoint the resolved state.
if complete {
b.markResolved()
return nil, b.Checkpoint(b)
}
// Prevent duplicate subscriptions.
b.subscribed = true
}
select {
case <-b.replyChan:
// The replyChan has been closed, signalling that the breach
// has been fully resolved. Checkpoint the resolved state and
// exit.
b.markResolved()
return nil, b.Checkpoint(b)
case <-b.quit:
}
return nil, errResolverShuttingDown
}
// Stop signals the breachResolver to stop.
func (b *breachResolver) Stop() {
b.log.Debugf("stopping...")
close(b.quit)
}
// SupplementState adds additional state to the breachResolver.
func (b *breachResolver) SupplementState(_ *channeldb.OpenChannel) {
}
// Encode encodes the breachResolver to the passed writer.
func (b *breachResolver) Encode(w io.Writer) error {
return binary.Write(w, endian, b.IsResolved())
}
// newBreachResolverFromReader attempts to decode an encoded breachResolver
// from the passed Reader instance, returning an active breachResolver.
func newBreachResolverFromReader(r io.Reader, resCfg ResolverConfig) (
*breachResolver, error) {
b := &breachResolver{
contractResolverKit: *newContractResolverKit(resCfg),
replyChan: make(chan struct{}),
}
var resolved bool
if err := binary.Read(r, endian, &resolved); err != nil {
return nil, err
}
if resolved {
b.markResolved()
}
b.initLogger(fmt.Sprintf("%T(%v)", b, b.ChanPoint))
return b, nil
}
// A compile time assertion to ensure breachResolver meets the ContractResolver
// interface.
var _ ContractResolver = (*breachResolver)(nil)
// Launch offers the breach outputs to the sweeper - currently it's a NOOP as
// the outputs here are not offered to the sweeper.
//
// NOTE: Part of the ContractResolver interface.
//
// TODO(yy): implement it once the outputs are offered to the sweeper.
func (b *breachResolver) Launch() error {
if b.isLaunched() {
b.log.Tracef("already launched")
return nil
}
b.log.Debugf("launching resolver...")
b.markLaunched()
return nil
}
package contractcourt
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/tlv"
)
// ContractResolutions is a wrapper struct around the two forms of resolutions
// we may need to carry out once a contract is closing: resolving the
// commitment output, and resolving any incoming+outgoing HTLC's still present
// in the commitment.
type ContractResolutions struct {
// CommitHash is the txid of the commitment transaction.
CommitHash chainhash.Hash
// CommitResolution contains all data required to fully resolve a
// commitment output.
CommitResolution *lnwallet.CommitOutputResolution
// HtlcResolutions contains all data required to fully resolve any
// incoming+outgoing HTLC's present within the commitment transaction.
HtlcResolutions lnwallet.HtlcResolutions
// AnchorResolution contains the data required to sweep the anchor
// output. If the channel type doesn't include anchors, the value of
// this field will be nil.
AnchorResolution *lnwallet.AnchorResolution
// BreachResolution contains the data required to manage the lifecycle
// of a breach in the ChannelArbitrator.
BreachResolution *BreachResolution
}
// IsEmpty returns true if the set of resolutions is "empty". A resolution is
// empty if: our commitment output has been trimmed, we don't have any
// incoming or outgoing HTLC's active, there is no anchor output to sweep, or
// there are no breached outputs to resolve.
func (c *ContractResolutions) IsEmpty() bool {
return c.CommitResolution == nil &&
len(c.HtlcResolutions.IncomingHTLCs) == 0 &&
len(c.HtlcResolutions.OutgoingHTLCs) == 0 &&
c.AnchorResolution == nil && c.BreachResolution == nil
}
// ArbitratorLog is the primary source of persistent storage for the
// ChannelArbitrator. The log stores the current state of the
// ChannelArbitrator's internal state machine, any items that are required to
// properly make a state transition, and any unresolved contracts.
type ArbitratorLog interface {
// TODO(roasbeef): document on interface the errors expected to be
// returned
// CurrentState returns the current state of the ChannelArbitrator. It
// takes an optional database transaction, which will be used if it is
// non-nil, otherwise the lookup will be done in its own transaction.
CurrentState(tx kvdb.RTx) (ArbitratorState, error)
// CommitState persists, the current state of the chain attendant.
CommitState(ArbitratorState) error
// InsertUnresolvedContracts inserts a set of unresolved contracts into
// the log. The log will then persistently store each contract until
// they've been swapped out, or resolved. It takes a set of report which
// should be written to disk if as well if it is non-nil.
InsertUnresolvedContracts(reports []*channeldb.ResolverReport,
resolvers ...ContractResolver) error
// FetchUnresolvedContracts returns all unresolved contracts that have
// been previously written to the log.
FetchUnresolvedContracts() ([]ContractResolver, error)
// SwapContract performs an atomic swap of the old contract for the new
// contract. This method is used when after a contract has been fully
// resolved, it produces another contract that needs to be resolved.
SwapContract(old ContractResolver, new ContractResolver) error
// ResolveContract marks a contract as fully resolved. Once a contract
// has been fully resolved, it is deleted from persistent storage.
ResolveContract(ContractResolver) error
// LogContractResolutions stores a complete contract resolution for the
// contract under watch. This method will be called once the
// ChannelArbitrator either force closes a channel, or detects that the
// remote party has broadcast their commitment on chain.
LogContractResolutions(*ContractResolutions) error
// FetchContractResolutions fetches the set of previously stored
// contract resolutions from persistent storage.
FetchContractResolutions() (*ContractResolutions, error)
// InsertConfirmedCommitSet stores the known set of active HTLCs at the
// time channel closure. We'll use this to reconstruct our set of chain
// actions anew based on the confirmed and pending commitment state.
InsertConfirmedCommitSet(c *CommitSet) error
// FetchConfirmedCommitSet fetches the known confirmed active HTLC set
// from the database. It takes an optional database transaction, which
// will be used if it is non-nil, otherwise the lookup will be done in
// its own transaction.
FetchConfirmedCommitSet(tx kvdb.RTx) (*CommitSet, error)
// FetchChainActions attempts to fetch the set of previously stored
// chain actions. We'll use this upon restart to properly advance our
// state machine forward.
//
// NOTE: This method only exists in order to be able to serve nodes had
// channels in the process of closing before the CommitSet struct was
// introduced.
FetchChainActions() (ChainActionMap, error)
// WipeHistory is to be called ONLY once *all* contracts have been
// fully resolved, and the channel closure if finalized. This method
// will delete all on-disk state within the persistent log.
WipeHistory() error
}
// ArbitratorState is an enum that details the current state of the
// ChannelArbitrator's state machine.
type ArbitratorState uint8
const (
// While some state transition is allowed, certain transitions are not
// possible. Listed below is the full state transition map which
// contains all possible paths. We start at StateDefault and end at
// StateFullyResolved, or StateError(not listed as its a possible state
// in every path). The format is,
// -> state: conditions we switch to this state.
//
// StateDefault
// |
// |-> StateDefault: no actions and chain trigger
// |
// |-> StateBroadcastCommit: chain/user trigger
// | |
// | |-> StateCommitmentBroadcasted: chain/user trigger
// | | |
// | | |-> StateCommitmentBroadcasted: chain/user trigger
// | | |
// | | |-> StateContractClosed: local/remote/breach close trigger
// | | | |
// | | | |-> StateWaitingFullResolution: contract resolutions not empty
// | | | | |
// | | | | |-> StateWaitingFullResolution: contract resolutions not empty
// | | | | |
// | | | | |-> StateFullyResolved: contract resolutions empty
// | | | |
// | | | |-> StateFullyResolved: contract resolutions empty
// | | |
// | | |-> StateFullyResolved: coop/breach(legacy) close trigger
// | |
// | |-> StateContractClosed: local/remote/breach close trigger
// | | |
// | | |-> StateWaitingFullResolution: contract resolutions not empty
// | | | |
// | | | |-> StateWaitingFullResolution: contract resolutions not empty
// | | | |
// | | | |-> StateFullyResolved: contract resolutions empty
// | | |
// | | |-> StateFullyResolved: contract resolutions empty
// | |
// | |-> StateFullyResolved: coop/breach(legacy) close trigger
// |
// |-> StateContractClosed: local/remote/breach close trigger
// | |
// | |-> StateWaitingFullResolution: contract resolutions not empty
// | | |
// | | |-> StateWaitingFullResolution: contract resolutions not empty
// | | |
// | | |-> StateFullyResolved: contract resolutions empty
// | |
// | |-> StateFullyResolved: contract resolutions empty
// |
// |-> StateFullyResolved: coop/breach(legacy) close trigger.
// StateDefault is the default state. In this state, no major actions
// need to be executed.
StateDefault ArbitratorState = 0
// StateBroadcastCommit is a state that indicates that the attendant
// has decided to broadcast the commitment transaction, but hasn't done
// so yet.
StateBroadcastCommit ArbitratorState = 1
// StateCommitmentBroadcasted is a state that indicates that the
// attendant has broadcasted the commitment transaction, and is now
// waiting for it to confirm.
StateCommitmentBroadcasted ArbitratorState = 6
// StateContractClosed is a state that indicates the contract has
// already been "closed", meaning the commitment is confirmed on chain.
// At this point, we can now examine our active contracts, in order to
// create the proper resolver for each one.
StateContractClosed ArbitratorState = 2
// StateWaitingFullResolution is a state that indicates that the
// commitment transaction has been confirmed, and the attendant is now
// waiting for all unresolved contracts to be fully resolved.
StateWaitingFullResolution ArbitratorState = 3
// StateFullyResolved is the final state of the attendant. In this
// state, all related contracts have been resolved, and the attendant
// can now be garbage collected.
StateFullyResolved ArbitratorState = 4
// StateError is the only error state of the resolver. If we enter this
// state, then we cannot proceed with manual intervention as a state
// transition failed.
StateError ArbitratorState = 5
)
// String returns a human readable string describing the ArbitratorState.
func (a ArbitratorState) String() string {
switch a {
case StateDefault:
return "StateDefault"
case StateBroadcastCommit:
return "StateBroadcastCommit"
case StateCommitmentBroadcasted:
return "StateCommitmentBroadcasted"
case StateContractClosed:
return "StateContractClosed"
case StateWaitingFullResolution:
return "StateWaitingFullResolution"
case StateFullyResolved:
return "StateFullyResolved"
case StateError:
return "StateError"
default:
return "unknown state"
}
}
// IsContractClosed returns a bool to indicate whether the closing/breaching tx
// has been confirmed onchain. If the state is StateContractClosed,
// StateWaitingFullResolution, or StateFullyResolved, it means the contract has
// been closed and all related contracts have been launched.
func (a ArbitratorState) IsContractClosed() bool {
return a == StateContractClosed || a == StateWaitingFullResolution ||
a == StateFullyResolved
}
// resolverType is an enum that enumerates the various types of resolvers. When
// writing resolvers to disk, we prepend this to the raw bytes stored. This
// allows us to properly decode the resolver into the proper type.
type resolverType uint8
const (
// resolverTimeout is the type of a resolver that's tasked with
// resolving an outgoing HTLC that is very close to timing out.
resolverTimeout resolverType = 0
// resolverSuccess is the type of a resolver that's tasked with
// resolving an incoming HTLC that we already know the preimage of.
resolverSuccess resolverType = 1
// resolverOutgoingContest is the type of a resolver that's tasked with
// resolving an outgoing HTLC that hasn't yet timed out.
resolverOutgoingContest resolverType = 2
// resolverIncomingContest is the type of a resolver that's tasked with
// resolving an incoming HTLC that we don't yet know the preimage to.
resolverIncomingContest resolverType = 3
// resolverUnilateralSweep is the type of resolver that's tasked with
// sweeping out direct commitment output form the remote party's
// commitment transaction.
resolverUnilateralSweep resolverType = 4
// resolverBreach is the type of resolver that manages a contract
// breach on-chain.
resolverBreach resolverType = 5
)
// resolverIDLen is the size of the resolver ID key. This is 36 bytes as we get
// 32 bytes from the hash of the prev tx, and 4 bytes for the output index.
const resolverIDLen = 36
// resolverID is a key that uniquely identifies a resolver within a particular
// chain. For this value we use the full outpoint of the resolver.
type resolverID [resolverIDLen]byte
// newResolverID returns a resolverID given the outpoint of a contract.
func newResolverID(op wire.OutPoint) resolverID {
var r resolverID
copy(r[:], op.Hash[:])
endian.PutUint32(r[32:], op.Index)
return r
}
// logScope is a key that we use to scope the storage of a ChannelArbitrator
// within the global log. We use this key to create a unique bucket within the
// database and ensure that we don't have any key collisions. The log's scope
// is define as: chainHash || chanPoint, where chanPoint is the chan point of
// the original channel.
type logScope [32 + 36]byte
// newLogScope creates a new logScope key from the passed chainhash and
// chanPoint.
func newLogScope(chain chainhash.Hash, op wire.OutPoint) (*logScope, error) {
var l logScope
b := bytes.NewBuffer(l[0:0])
if _, err := b.Write(chain[:]); err != nil {
return nil, err
}
if _, err := b.Write(op.Hash[:]); err != nil {
return nil, err
}
if err := binary.Write(b, endian, op.Index); err != nil {
return nil, err
}
return &l, nil
}
var (
// stateKey is the key that we use to store the current state of the
// arbitrator.
stateKey = []byte("state")
// contractsBucketKey is the bucket within the logScope that will store
// all the active unresolved contracts.
contractsBucketKey = []byte("contractkey")
// resolutionsKey is the key under the logScope that we'll use to store
// the full set of resolutions for a channel.
resolutionsKey = []byte("resolutions")
// resolutionsSignDetailsKey is the key under the logScope where we
// will store input.SignDetails for each HTLC resolution. If this is
// not found under the logScope, it means it was written before
// SignDetails was introduced, and should be set nil for each HTLC
// resolution.
resolutionsSignDetailsKey = []byte("resolutions-sign-details")
// anchorResolutionKey is the key under the logScope that we'll use to
// store the anchor resolution, if any.
anchorResolutionKey = []byte("anchor-resolution")
// breachResolutionKey is the key under the logScope that we'll use to
// store the breach resolution, if any. This is used rather than the
// resolutionsKey.
breachResolutionKey = []byte("breach-resolution")
// actionsBucketKey is the key under the logScope that we'll use to
// store all chain actions once they're determined.
actionsBucketKey = []byte("chain-actions")
// commitSetKey is the primary key under the logScope that we'll use to
// store the confirmed active HTLC sets once we learn that a channel
// has closed out on chain.
commitSetKey = []byte("commit-set")
// taprootDataKey is the key we'll use to store taproot specific data
// for the set of channels we'll need to sweep/claim.
taprootDataKey = []byte("taproot-data")
)
var (
// errScopeBucketNoExist is returned when we can't find the proper
// bucket for an arbitrator's scope.
errScopeBucketNoExist = fmt.Errorf("scope bucket not found")
// errNoContracts is returned when no contracts are found within the
// log.
errNoContracts = fmt.Errorf("no stored contracts")
// errNoResolutions is returned when the log doesn't contain any active
// chain resolutions.
errNoResolutions = fmt.Errorf("no contract resolutions exist")
// errNoActions is returned when the log doesn't contain any stored
// chain actions.
errNoActions = fmt.Errorf("no chain actions exist")
// errNoCommitSet is return when the log doesn't contained a CommitSet.
// This can happen if the channel hasn't closed yet, or a client is
// running an older version that didn't yet write this state.
errNoCommitSet = fmt.Errorf("no commit set exists")
)
// boltArbitratorLog is an implementation of the ArbitratorLog interface backed
// by a bolt DB instance.
type boltArbitratorLog struct {
db kvdb.Backend
cfg ChannelArbitratorConfig
scopeKey logScope
}
// newBoltArbitratorLog returns a new instance of the boltArbitratorLog given
// an arbitrator config, and the items needed to create its log scope.
func newBoltArbitratorLog(db kvdb.Backend, cfg ChannelArbitratorConfig,
chainHash chainhash.Hash, chanPoint wire.OutPoint) (*boltArbitratorLog, error) {
scope, err := newLogScope(chainHash, chanPoint)
if err != nil {
return nil, err
}
return &boltArbitratorLog{
db: db,
cfg: cfg,
scopeKey: *scope,
}, nil
}
// A compile time check to ensure boltArbitratorLog meets the ArbitratorLog
// interface.
var _ ArbitratorLog = (*boltArbitratorLog)(nil)
func fetchContractReadBucket(tx kvdb.RTx, scopeKey []byte) (kvdb.RBucket, error) {
scopeBucket := tx.ReadBucket(scopeKey)
if scopeBucket == nil {
return nil, errScopeBucketNoExist
}
contractBucket := scopeBucket.NestedReadBucket(contractsBucketKey)
if contractBucket == nil {
return nil, errNoContracts
}
return contractBucket, nil
}
func fetchContractWriteBucket(tx kvdb.RwTx, scopeKey []byte) (kvdb.RwBucket, error) {
scopeBucket, err := tx.CreateTopLevelBucket(scopeKey)
if err != nil {
return nil, err
}
contractBucket, err := scopeBucket.CreateBucketIfNotExists(
contractsBucketKey,
)
if err != nil {
return nil, err
}
return contractBucket, nil
}
// writeResolver is a helper method that writes a contract resolver and stores
// it it within the passed contractBucket using its unique resolutionsKey key.
func (b *boltArbitratorLog) writeResolver(contractBucket kvdb.RwBucket,
res ContractResolver) error {
// Only persist resolvers that are stateful. Stateless resolvers don't
// expose a resolver key.
resKey := res.ResolverKey()
if resKey == nil {
return nil
}
// First, we'll write to the buffer the type of this resolver. Using
// this byte, we can later properly deserialize the resolver properly.
var (
buf bytes.Buffer
rType resolverType
)
switch res.(type) {
case *htlcTimeoutResolver:
rType = resolverTimeout
case *htlcSuccessResolver:
rType = resolverSuccess
case *htlcOutgoingContestResolver:
rType = resolverOutgoingContest
case *htlcIncomingContestResolver:
rType = resolverIncomingContest
case *commitSweepResolver:
rType = resolverUnilateralSweep
case *breachResolver:
rType = resolverBreach
}
if _, err := buf.Write([]byte{byte(rType)}); err != nil {
return err
}
// With the type of the resolver written, we can then write out the raw
// bytes of the resolver itself.
if err := res.Encode(&buf); err != nil {
return err
}
return contractBucket.Put(resKey, buf.Bytes())
}
// CurrentState returns the current state of the ChannelArbitrator. It takes an
// optional database transaction, which will be used if it is non-nil, otherwise
// the lookup will be done in its own transaction.
//
// NOTE: Part of the ContractResolver interface.
func (b *boltArbitratorLog) CurrentState(tx kvdb.RTx) (ArbitratorState, error) {
var (
s ArbitratorState
err error
)
if tx != nil {
s, err = b.currentState(tx)
} else {
err = kvdb.View(b.db, func(tx kvdb.RTx) error {
s, err = b.currentState(tx)
return err
}, func() {
s = 0
})
}
if err != nil && err != errScopeBucketNoExist {
return s, err
}
return s, nil
}
func (b *boltArbitratorLog) currentState(tx kvdb.RTx) (ArbitratorState, error) {
scopeBucket := tx.ReadBucket(b.scopeKey[:])
if scopeBucket == nil {
return 0, errScopeBucketNoExist
}
stateBytes := scopeBucket.Get(stateKey)
if stateBytes == nil {
return 0, nil
}
return ArbitratorState(stateBytes[0]), nil
}
// CommitState persists, the current state of the chain attendant.
//
// NOTE: Part of the ContractResolver interface.
func (b *boltArbitratorLog) CommitState(s ArbitratorState) error {
return kvdb.Batch(b.db, func(tx kvdb.RwTx) error {
scopeBucket, err := tx.CreateTopLevelBucket(b.scopeKey[:])
if err != nil {
return err
}
return scopeBucket.Put(stateKey[:], []byte{uint8(s)})
})
}
// FetchUnresolvedContracts returns all unresolved contracts that have been
// previously written to the log.
//
// NOTE: Part of the ContractResolver interface.
func (b *boltArbitratorLog) FetchUnresolvedContracts() ([]ContractResolver, error) {
resolverCfg := ResolverConfig{
ChannelArbitratorConfig: b.cfg,
Checkpoint: b.checkpointContract,
}
var contracts []ContractResolver
err := kvdb.View(b.db, func(tx kvdb.RTx) error {
contractBucket, err := fetchContractReadBucket(tx, b.scopeKey[:])
if err != nil {
return err
}
return contractBucket.ForEach(func(resKey, resBytes []byte) error {
if len(resKey) != resolverIDLen {
return nil
}
var res ContractResolver
// We'll snip off the first byte of the raw resolver
// bytes in order to extract what type of resolver
// we're about to encode.
resType := resolverType(resBytes[0])
// Then we'll create a reader using the remaining
// bytes.
resReader := bytes.NewReader(resBytes[1:])
switch resType {
case resolverTimeout:
res, err = newTimeoutResolverFromReader(
resReader, resolverCfg,
)
case resolverSuccess:
res, err = newSuccessResolverFromReader(
resReader, resolverCfg,
)
case resolverOutgoingContest:
res, err = newOutgoingContestResolverFromReader(
resReader, resolverCfg,
)
case resolverIncomingContest:
res, err = newIncomingContestResolverFromReader(
resReader, resolverCfg,
)
case resolverUnilateralSweep:
res, err = newCommitSweepResolverFromReader(
resReader, resolverCfg,
)
case resolverBreach:
res, err = newBreachResolverFromReader(
resReader, resolverCfg,
)
default:
return fmt.Errorf("unknown resolver type: %v", resType)
}
if err != nil {
return err
}
contracts = append(contracts, res)
return nil
})
}, func() {
contracts = nil
})
if err != nil && err != errScopeBucketNoExist && err != errNoContracts {
return nil, err
}
return contracts, nil
}
// InsertUnresolvedContracts inserts a set of unresolved contracts into the
// log. The log will then persistently store each contract until they've been
// swapped out, or resolved.
//
// NOTE: Part of the ContractResolver interface.
func (b *boltArbitratorLog) InsertUnresolvedContracts(reports []*channeldb.ResolverReport,
resolvers ...ContractResolver) error {
return kvdb.Batch(b.db, func(tx kvdb.RwTx) error {
contractBucket, err := fetchContractWriteBucket(tx, b.scopeKey[:])
if err != nil {
return err
}
for _, resolver := range resolvers {
err = b.writeResolver(contractBucket, resolver)
if err != nil {
return err
}
}
// Persist any reports that are present.
for _, report := range reports {
err := b.cfg.PutResolverReport(tx, report)
if err != nil {
return err
}
}
return nil
})
}
// SwapContract performs an atomic swap of the old contract for the new
// contract. This method is used when after a contract has been fully resolved,
// it produces another contract that needs to be resolved.
//
// NOTE: Part of the ContractResolver interface.
func (b *boltArbitratorLog) SwapContract(oldContract, newContract ContractResolver) error {
return kvdb.Batch(b.db, func(tx kvdb.RwTx) error {
contractBucket, err := fetchContractWriteBucket(tx, b.scopeKey[:])
if err != nil {
return err
}
oldContractkey := oldContract.ResolverKey()
if err := contractBucket.Delete(oldContractkey); err != nil {
return err
}
return b.writeResolver(contractBucket, newContract)
})
}
// ResolveContract marks a contract as fully resolved. Once a contract has been
// fully resolved, it is deleted from persistent storage.
//
// NOTE: Part of the ContractResolver interface.
func (b *boltArbitratorLog) ResolveContract(res ContractResolver) error {
return kvdb.Batch(b.db, func(tx kvdb.RwTx) error {
contractBucket, err := fetchContractWriteBucket(tx, b.scopeKey[:])
if err != nil {
return err
}
resKey := res.ResolverKey()
return contractBucket.Delete(resKey)
})
}
// LogContractResolutions stores a set of chain actions which are derived from
// our set of active contracts, and the on-chain state. We'll write this et of
// cations when: we decide to go on-chain to resolve a contract, or we detect
// that the remote party has gone on-chain.
//
// NOTE: Part of the ContractResolver interface.
func (b *boltArbitratorLog) LogContractResolutions(c *ContractResolutions) error {
return kvdb.Batch(b.db, func(tx kvdb.RwTx) error {
scopeBucket, err := tx.CreateTopLevelBucket(b.scopeKey[:])
if err != nil {
return err
}
var b bytes.Buffer
if _, err := b.Write(c.CommitHash[:]); err != nil {
return err
}
// First, we'll write out the commit output's resolution.
if c.CommitResolution == nil {
if err := binary.Write(&b, endian, false); err != nil {
return err
}
} else {
if err := binary.Write(&b, endian, true); err != nil {
return err
}
err = encodeCommitResolution(&b, c.CommitResolution)
if err != nil {
return err
}
}
// As we write the HTLC resolutions, we'll serialize the sign
// details for each, to store under a new key.
var signDetailsBuf bytes.Buffer
// With the output for the commitment transaction written, we
// can now write out the resolutions for the incoming and
// outgoing HTLC's.
numIncoming := uint32(len(c.HtlcResolutions.IncomingHTLCs))
if err := binary.Write(&b, endian, numIncoming); err != nil {
return err
}
for _, htlc := range c.HtlcResolutions.IncomingHTLCs {
err := encodeIncomingResolution(&b, &htlc)
if err != nil {
return err
}
err = encodeSignDetails(&signDetailsBuf, htlc.SignDetails)
if err != nil {
return err
}
}
numOutgoing := uint32(len(c.HtlcResolutions.OutgoingHTLCs))
if err := binary.Write(&b, endian, numOutgoing); err != nil {
return err
}
for _, htlc := range c.HtlcResolutions.OutgoingHTLCs {
err := encodeOutgoingResolution(&b, &htlc)
if err != nil {
return err
}
err = encodeSignDetails(&signDetailsBuf, htlc.SignDetails)
if err != nil {
return err
}
}
// Put the resolutions under the resolutionsKey.
err = scopeBucket.Put(resolutionsKey, b.Bytes())
if err != nil {
return err
}
// We'll put the serialized sign details under its own key to
// stay backwards compatible.
err = scopeBucket.Put(
resolutionsSignDetailsKey, signDetailsBuf.Bytes(),
)
if err != nil {
return err
}
// Write out the anchor resolution if present.
if c.AnchorResolution != nil {
var b bytes.Buffer
err := encodeAnchorResolution(&b, c.AnchorResolution)
if err != nil {
return err
}
err = scopeBucket.Put(anchorResolutionKey, b.Bytes())
if err != nil {
return err
}
}
// Write out the breach resolution if present.
if c.BreachResolution != nil {
var b bytes.Buffer
err := encodeBreachResolution(&b, c.BreachResolution)
if err != nil {
return err
}
err = scopeBucket.Put(breachResolutionKey, b.Bytes())
if err != nil {
return err
}
}
// If this isn't a taproot channel, then we can exit early here
// as there's no extra data to write.
switch {
case c.AnchorResolution == nil:
return nil
case !txscript.IsPayToTaproot(
c.AnchorResolution.AnchorSignDescriptor.Output.PkScript,
):
return nil
}
// With everything else encoded, we'll now populate the taproot
// specific items we need to store for the musig2 channels.
var tb bytes.Buffer
err = encodeTaprootAuxData(&tb, c)
if err != nil {
return err
}
return scopeBucket.Put(taprootDataKey, tb.Bytes())
})
}
// FetchContractResolutions fetches the set of previously stored contract
// resolutions from persistent storage.
//
// NOTE: Part of the ContractResolver interface.
func (b *boltArbitratorLog) FetchContractResolutions() (*ContractResolutions, error) {
var c *ContractResolutions
err := kvdb.View(b.db, func(tx kvdb.RTx) error {
scopeBucket := tx.ReadBucket(b.scopeKey[:])
if scopeBucket == nil {
return errScopeBucketNoExist
}
resolutionBytes := scopeBucket.Get(resolutionsKey)
if resolutionBytes == nil {
return errNoResolutions
}
resReader := bytes.NewReader(resolutionBytes)
_, err := io.ReadFull(resReader, c.CommitHash[:])
if err != nil {
return err
}
// First, we'll attempt to read out the commit resolution (if
// it exists).
var haveCommitRes bool
err = binary.Read(resReader, endian, &haveCommitRes)
if err != nil {
return err
}
if haveCommitRes {
c.CommitResolution = &lnwallet.CommitOutputResolution{}
err = decodeCommitResolution(
resReader, c.CommitResolution,
)
if err != nil {
return fmt.Errorf("unable to decode "+
"commit res: %w", err)
}
}
var (
numIncoming uint32
numOutgoing uint32
)
// Next, we'll read out the incoming and outgoing HTLC
// resolutions.
err = binary.Read(resReader, endian, &numIncoming)
if err != nil {
return err
}
c.HtlcResolutions.IncomingHTLCs = make([]lnwallet.IncomingHtlcResolution, numIncoming)
for i := uint32(0); i < numIncoming; i++ {
err := decodeIncomingResolution(
resReader, &c.HtlcResolutions.IncomingHTLCs[i],
)
if err != nil {
return fmt.Errorf("unable to decode "+
"incoming res: %w", err)
}
}
err = binary.Read(resReader, endian, &numOutgoing)
if err != nil {
return err
}
c.HtlcResolutions.OutgoingHTLCs = make([]lnwallet.OutgoingHtlcResolution, numOutgoing)
for i := uint32(0); i < numOutgoing; i++ {
err := decodeOutgoingResolution(
resReader, &c.HtlcResolutions.OutgoingHTLCs[i],
)
if err != nil {
return fmt.Errorf("unable to decode "+
"outgoing res: %w", err)
}
}
// Now we attempt to get the sign details for our HTLC
// resolutions. If not present the channel is of a type that
// doesn't need them. If present there will be SignDetails
// encoded for each HTLC resolution.
signDetailsBytes := scopeBucket.Get(resolutionsSignDetailsKey)
if signDetailsBytes != nil {
r := bytes.NewReader(signDetailsBytes)
// They will be encoded in the same order as the
// resolutions: firs incoming HTLCs, then outgoing.
for i := uint32(0); i < numIncoming; i++ {
htlc := &c.HtlcResolutions.IncomingHTLCs[i]
htlc.SignDetails, err = decodeSignDetails(r)
if err != nil {
return fmt.Errorf("unable to decode "+
"incoming sign desc: %w", err)
}
}
for i := uint32(0); i < numOutgoing; i++ {
htlc := &c.HtlcResolutions.OutgoingHTLCs[i]
htlc.SignDetails, err = decodeSignDetails(r)
if err != nil {
return fmt.Errorf("unable to decode "+
"outgoing sign desc: %w", err)
}
}
}
anchorResBytes := scopeBucket.Get(anchorResolutionKey)
if anchorResBytes != nil {
c.AnchorResolution = &lnwallet.AnchorResolution{}
resReader := bytes.NewReader(anchorResBytes)
err := decodeAnchorResolution(
resReader, c.AnchorResolution,
)
if err != nil {
return fmt.Errorf("unable to read anchor "+
"data: %w", err)
}
}
breachResBytes := scopeBucket.Get(breachResolutionKey)
if breachResBytes != nil {
c.BreachResolution = &BreachResolution{}
resReader := bytes.NewReader(breachResBytes)
err := decodeBreachResolution(
resReader, c.BreachResolution,
)
if err != nil {
return fmt.Errorf("unable to read breach "+
"data: %w", err)
}
}
tapCaseBytes := scopeBucket.Get(taprootDataKey)
if tapCaseBytes != nil {
err = decodeTapRootAuxData(
bytes.NewReader(tapCaseBytes), c,
)
if err != nil {
return fmt.Errorf("unable to read taproot "+
"data: %w", err)
}
}
return nil
}, func() {
c = &ContractResolutions{}
})
if err != nil {
return nil, err
}
return c, err
}
// FetchChainActions attempts to fetch the set of previously stored chain
// actions. We'll use this upon restart to properly advance our state machine
// forward.
//
// NOTE: Part of the ContractResolver interface.
func (b *boltArbitratorLog) FetchChainActions() (ChainActionMap, error) {
var actionsMap ChainActionMap
err := kvdb.View(b.db, func(tx kvdb.RTx) error {
scopeBucket := tx.ReadBucket(b.scopeKey[:])
if scopeBucket == nil {
return errScopeBucketNoExist
}
actionsBucket := scopeBucket.NestedReadBucket(actionsBucketKey)
if actionsBucket == nil {
return errNoActions
}
return actionsBucket.ForEach(func(action, htlcBytes []byte) error {
if htlcBytes == nil {
return nil
}
chainAction := ChainAction(action[0])
htlcReader := bytes.NewReader(htlcBytes)
htlcs, err := channeldb.DeserializeHtlcs(htlcReader)
if err != nil {
return err
}
actionsMap[chainAction] = htlcs
return nil
})
}, func() {
actionsMap = make(ChainActionMap)
})
if err != nil {
return nil, err
}
return actionsMap, nil
}
// InsertConfirmedCommitSet stores the known set of active HTLCs at the time
// channel closure. We'll use this to reconstruct our set of chain actions anew
// based on the confirmed and pending commitment state.
//
// NOTE: Part of the ContractResolver interface.
func (b *boltArbitratorLog) InsertConfirmedCommitSet(c *CommitSet) error {
return kvdb.Batch(b.db, func(tx kvdb.RwTx) error {
scopeBucket, err := tx.CreateTopLevelBucket(b.scopeKey[:])
if err != nil {
return err
}
var b bytes.Buffer
if err := encodeCommitSet(&b, c); err != nil {
return err
}
return scopeBucket.Put(commitSetKey, b.Bytes())
})
}
// FetchConfirmedCommitSet fetches the known confirmed active HTLC set from the
// database. It takes an optional database transaction, which will be used if it
// is non-nil, otherwise the lookup will be done in its own transaction.
//
// NOTE: Part of the ContractResolver interface.
func (b *boltArbitratorLog) FetchConfirmedCommitSet(tx kvdb.RTx) (*CommitSet, error) {
if tx != nil {
return b.fetchConfirmedCommitSet(tx)
}
var c *CommitSet
err := kvdb.View(b.db, func(tx kvdb.RTx) error {
var err error
c, err = b.fetchConfirmedCommitSet(tx)
return err
}, func() {
c = nil
})
if err != nil {
return nil, err
}
return c, nil
}
func (b *boltArbitratorLog) fetchConfirmedCommitSet(tx kvdb.RTx) (*CommitSet,
error) {
scopeBucket := tx.ReadBucket(b.scopeKey[:])
if scopeBucket == nil {
return nil, errScopeBucketNoExist
}
commitSetBytes := scopeBucket.Get(commitSetKey)
if commitSetBytes == nil {
return nil, errNoCommitSet
}
return decodeCommitSet(bytes.NewReader(commitSetBytes))
}
// WipeHistory is to be called ONLY once *all* contracts have been fully
// resolved, and the channel closure if finalized. This method will delete all
// on-disk state within the persistent log.
//
// NOTE: Part of the ContractResolver interface.
func (b *boltArbitratorLog) WipeHistory() error {
return kvdb.Update(b.db, func(tx kvdb.RwTx) error {
scopeBucket, err := tx.CreateTopLevelBucket(b.scopeKey[:])
if err != nil {
return err
}
// Once we have the main top-level bucket, we'll delete the key
// that stores the state of the arbitrator.
if err := scopeBucket.Delete(stateKey[:]); err != nil {
return err
}
// Next, we'll delete any lingering contract state within the
// contracts bucket by removing the bucket itself.
err = scopeBucket.DeleteNestedBucket(contractsBucketKey)
if err != nil && err != kvdb.ErrBucketNotFound {
return err
}
// Next, we'll delete storage of any lingering contract
// resolutions.
if err := scopeBucket.Delete(resolutionsKey); err != nil {
return err
}
err = scopeBucket.Delete(resolutionsSignDetailsKey)
if err != nil {
return err
}
// We'll delete any chain actions that are still stored by
// removing the enclosing bucket.
err = scopeBucket.DeleteNestedBucket(actionsBucketKey)
if err != nil && err != kvdb.ErrBucketNotFound {
return err
}
// Finally, we'll delete the enclosing bucket itself.
return tx.DeleteTopLevelBucket(b.scopeKey[:])
}, func() {})
}
// checkpointContract is a private method that will be fed into
// ContractResolver instances to checkpoint their state once they reach
// milestones during contract resolution. If the report provided is non-nil,
// it should also be recorded.
func (b *boltArbitratorLog) checkpointContract(c ContractResolver,
reports ...*channeldb.ResolverReport) error {
return kvdb.Update(b.db, func(tx kvdb.RwTx) error {
contractBucket, err := fetchContractWriteBucket(tx, b.scopeKey[:])
if err != nil {
return err
}
if err := b.writeResolver(contractBucket, c); err != nil {
return err
}
for _, report := range reports {
if err := b.cfg.PutResolverReport(tx, report); err != nil {
return err
}
}
return nil
}, func() {})
}
// encodeSignDetails encodes the given SignDetails struct to the writer.
// SignDetails is allowed to be nil, in which we will encode that it is not
// present.
func encodeSignDetails(w io.Writer, s *input.SignDetails) error {
// If we don't have sign details, write false and return.
if s == nil {
return binary.Write(w, endian, false)
}
// Otherwise write true, and the contents of the SignDetails.
if err := binary.Write(w, endian, true); err != nil {
return err
}
err := input.WriteSignDescriptor(w, &s.SignDesc)
if err != nil {
return err
}
err = binary.Write(w, endian, uint32(s.SigHashType))
if err != nil {
return err
}
// Write the DER-encoded signature.
b := s.PeerSig.Serialize()
if err := wire.WriteVarBytes(w, 0, b); err != nil {
return err
}
return nil
}
// decodeSignDetails extracts a single SignDetails from the reader. It is
// allowed to return nil in case the SignDetails were empty.
func decodeSignDetails(r io.Reader) (*input.SignDetails, error) {
var present bool
if err := binary.Read(r, endian, &present); err != nil {
return nil, err
}
// Simply return nil if the next SignDetails was not present.
if !present {
return nil, nil
}
// Otherwise decode the elements of the SignDetails.
s := input.SignDetails{}
err := input.ReadSignDescriptor(r, &s.SignDesc)
if err != nil {
return nil, err
}
var sigHash uint32
err = binary.Read(r, endian, &sigHash)
if err != nil {
return nil, err
}
s.SigHashType = txscript.SigHashType(sigHash)
// Read DER-encoded signature.
rawSig, err := wire.ReadVarBytes(r, 0, 200, "signature")
if err != nil {
return nil, err
}
s.PeerSig, err = input.ParseSignature(rawSig)
if err != nil {
return nil, err
}
return &s, nil
}
func encodeIncomingResolution(w io.Writer, i *lnwallet.IncomingHtlcResolution) error {
if _, err := w.Write(i.Preimage[:]); err != nil {
return err
}
if i.SignedSuccessTx == nil {
if err := binary.Write(w, endian, false); err != nil {
return err
}
} else {
if err := binary.Write(w, endian, true); err != nil {
return err
}
if err := i.SignedSuccessTx.Serialize(w); err != nil {
return err
}
}
if err := binary.Write(w, endian, i.CsvDelay); err != nil {
return err
}
if _, err := w.Write(i.ClaimOutpoint.Hash[:]); err != nil {
return err
}
if err := binary.Write(w, endian, i.ClaimOutpoint.Index); err != nil {
return err
}
err := input.WriteSignDescriptor(w, &i.SweepSignDesc)
if err != nil {
return err
}
return nil
}
func decodeIncomingResolution(r io.Reader, h *lnwallet.IncomingHtlcResolution) error {
if _, err := io.ReadFull(r, h.Preimage[:]); err != nil {
return err
}
var txPresent bool
if err := binary.Read(r, endian, &txPresent); err != nil {
return err
}
if txPresent {
h.SignedSuccessTx = &wire.MsgTx{}
if err := h.SignedSuccessTx.Deserialize(r); err != nil {
return err
}
}
err := binary.Read(r, endian, &h.CsvDelay)
if err != nil {
return err
}
_, err = io.ReadFull(r, h.ClaimOutpoint.Hash[:])
if err != nil {
return err
}
err = binary.Read(r, endian, &h.ClaimOutpoint.Index)
if err != nil {
return err
}
return input.ReadSignDescriptor(r, &h.SweepSignDesc)
}
func encodeOutgoingResolution(w io.Writer, o *lnwallet.OutgoingHtlcResolution) error {
if err := binary.Write(w, endian, o.Expiry); err != nil {
return err
}
if o.SignedTimeoutTx == nil {
if err := binary.Write(w, endian, false); err != nil {
return err
}
} else {
if err := binary.Write(w, endian, true); err != nil {
return err
}
if err := o.SignedTimeoutTx.Serialize(w); err != nil {
return err
}
}
if err := binary.Write(w, endian, o.CsvDelay); err != nil {
return err
}
if _, err := w.Write(o.ClaimOutpoint.Hash[:]); err != nil {
return err
}
if err := binary.Write(w, endian, o.ClaimOutpoint.Index); err != nil {
return err
}
return input.WriteSignDescriptor(w, &o.SweepSignDesc)
}
func decodeOutgoingResolution(r io.Reader, o *lnwallet.OutgoingHtlcResolution) error {
err := binary.Read(r, endian, &o.Expiry)
if err != nil {
return err
}
var txPresent bool
if err := binary.Read(r, endian, &txPresent); err != nil {
return err
}
if txPresent {
o.SignedTimeoutTx = &wire.MsgTx{}
if err := o.SignedTimeoutTx.Deserialize(r); err != nil {
return err
}
}
err = binary.Read(r, endian, &o.CsvDelay)
if err != nil {
return err
}
_, err = io.ReadFull(r, o.ClaimOutpoint.Hash[:])
if err != nil {
return err
}
err = binary.Read(r, endian, &o.ClaimOutpoint.Index)
if err != nil {
return err
}
return input.ReadSignDescriptor(r, &o.SweepSignDesc)
}
func encodeCommitResolution(w io.Writer,
c *lnwallet.CommitOutputResolution) error {
if _, err := w.Write(c.SelfOutPoint.Hash[:]); err != nil {
return err
}
err := binary.Write(w, endian, c.SelfOutPoint.Index)
if err != nil {
return err
}
err = input.WriteSignDescriptor(w, &c.SelfOutputSignDesc)
if err != nil {
return err
}
return binary.Write(w, endian, c.MaturityDelay)
}
func decodeCommitResolution(r io.Reader,
c *lnwallet.CommitOutputResolution) error {
_, err := io.ReadFull(r, c.SelfOutPoint.Hash[:])
if err != nil {
return err
}
err = binary.Read(r, endian, &c.SelfOutPoint.Index)
if err != nil {
return err
}
err = input.ReadSignDescriptor(r, &c.SelfOutputSignDesc)
if err != nil {
return err
}
return binary.Read(r, endian, &c.MaturityDelay)
}
func encodeAnchorResolution(w io.Writer,
a *lnwallet.AnchorResolution) error {
if _, err := w.Write(a.CommitAnchor.Hash[:]); err != nil {
return err
}
err := binary.Write(w, endian, a.CommitAnchor.Index)
if err != nil {
return err
}
return input.WriteSignDescriptor(w, &a.AnchorSignDescriptor)
}
func decodeAnchorResolution(r io.Reader,
a *lnwallet.AnchorResolution) error {
_, err := io.ReadFull(r, a.CommitAnchor.Hash[:])
if err != nil {
return err
}
err = binary.Read(r, endian, &a.CommitAnchor.Index)
if err != nil {
return err
}
return input.ReadSignDescriptor(r, &a.AnchorSignDescriptor)
}
func encodeBreachResolution(w io.Writer, b *BreachResolution) error {
if _, err := w.Write(b.FundingOutPoint.Hash[:]); err != nil {
return err
}
return binary.Write(w, endian, b.FundingOutPoint.Index)
}
func decodeBreachResolution(r io.Reader, b *BreachResolution) error {
_, err := io.ReadFull(r, b.FundingOutPoint.Hash[:])
if err != nil {
return err
}
return binary.Read(r, endian, &b.FundingOutPoint.Index)
}
func encodeHtlcSetKey(w io.Writer, htlcSetKey HtlcSetKey) error {
err := binary.Write(w, endian, htlcSetKey.IsRemote)
if err != nil {
return err
}
return binary.Write(w, endian, htlcSetKey.IsPending)
}
func encodeCommitSet(w io.Writer, c *CommitSet) error {
confCommitKey, err := c.ConfCommitKey.UnwrapOrErr(
fmt.Errorf("HtlcSetKey is not set"),
)
if err != nil {
return err
}
if err := encodeHtlcSetKey(w, confCommitKey); err != nil {
return err
}
numSets := uint8(len(c.HtlcSets))
if err := binary.Write(w, endian, numSets); err != nil {
return err
}
for htlcSetKey, htlcs := range c.HtlcSets {
if err := encodeHtlcSetKey(w, htlcSetKey); err != nil {
return err
}
if err := channeldb.SerializeHtlcs(w, htlcs...); err != nil {
return err
}
}
return nil
}
func decodeHtlcSetKey(r io.Reader, h *HtlcSetKey) error {
err := binary.Read(r, endian, &h.IsRemote)
if err != nil {
return err
}
return binary.Read(r, endian, &h.IsPending)
}
func decodeCommitSet(r io.Reader) (*CommitSet, error) {
confCommitKey := HtlcSetKey{}
if err := decodeHtlcSetKey(r, &confCommitKey); err != nil {
return nil, err
}
c := &CommitSet{
ConfCommitKey: fn.Some(confCommitKey),
HtlcSets: make(map[HtlcSetKey][]channeldb.HTLC),
}
var numSets uint8
if err := binary.Read(r, endian, &numSets); err != nil {
return nil, err
}
for i := uint8(0); i < numSets; i++ {
var htlcSetKey HtlcSetKey
if err := decodeHtlcSetKey(r, &htlcSetKey); err != nil {
return nil, err
}
htlcs, err := channeldb.DeserializeHtlcs(r)
if err != nil {
return nil, err
}
c.HtlcSets[htlcSetKey] = htlcs
}
return c, nil
}
func encodeTaprootAuxData(w io.Writer, c *ContractResolutions) error {
tapCase := newTaprootBriefcase()
if c.CommitResolution != nil {
commitResolution := c.CommitResolution
commitSignDesc := commitResolution.SelfOutputSignDesc
//nolint:ll
tapCase.CtrlBlocks.Val.CommitSweepCtrlBlock = commitSignDesc.ControlBlock
c.CommitResolution.ResolutionBlob.WhenSome(func(b []byte) {
tapCase.SettledCommitBlob = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType2](b),
)
})
}
htlcBlobs := newAuxHtlcBlobs()
for _, htlc := range c.HtlcResolutions.IncomingHTLCs {
htlc := htlc
htlcSignDesc := htlc.SweepSignDesc
ctrlBlock := htlcSignDesc.ControlBlock
if ctrlBlock == nil {
continue
}
var resID resolverID
if htlc.SignedSuccessTx != nil {
resID = newResolverID(
htlc.SignedSuccessTx.TxIn[0].PreviousOutPoint,
)
//nolint:ll
tapCase.CtrlBlocks.Val.SecondLevelCtrlBlocks[resID] = ctrlBlock
// For HTLCs we need to go to the second level for, we
// also need to store the control block needed to
// publish the second level transaction.
if htlc.SignDetails != nil {
//nolint:ll
bridgeCtrlBlock := htlc.SignDetails.SignDesc.ControlBlock
//nolint:ll
tapCase.CtrlBlocks.Val.IncomingHtlcCtrlBlocks[resID] = bridgeCtrlBlock
}
} else {
resID = newResolverID(htlc.ClaimOutpoint)
//nolint:ll
tapCase.CtrlBlocks.Val.IncomingHtlcCtrlBlocks[resID] = ctrlBlock
}
htlc.ResolutionBlob.WhenSome(func(b []byte) {
htlcBlobs[resID] = b
})
}
for _, htlc := range c.HtlcResolutions.OutgoingHTLCs {
htlc := htlc
htlcSignDesc := htlc.SweepSignDesc
ctrlBlock := htlcSignDesc.ControlBlock
if ctrlBlock == nil {
continue
}
var resID resolverID
if htlc.SignedTimeoutTx != nil {
resID = newResolverID(
htlc.SignedTimeoutTx.TxIn[0].PreviousOutPoint,
)
//nolint:ll
tapCase.CtrlBlocks.Val.SecondLevelCtrlBlocks[resID] = ctrlBlock
// For HTLCs we need to go to the second level for, we
// also need to store the control block needed to
// publish the second level transaction.
//
//nolint:ll
if htlc.SignDetails != nil {
//nolint:ll
bridgeCtrlBlock := htlc.SignDetails.SignDesc.ControlBlock
//nolint:ll
tapCase.CtrlBlocks.Val.OutgoingHtlcCtrlBlocks[resID] = bridgeCtrlBlock
}
} else {
resID = newResolverID(htlc.ClaimOutpoint)
//nolint:ll
tapCase.CtrlBlocks.Val.OutgoingHtlcCtrlBlocks[resID] = ctrlBlock
}
htlc.ResolutionBlob.WhenSome(func(b []byte) {
htlcBlobs[resID] = b
})
}
if c.AnchorResolution != nil {
anchorSignDesc := c.AnchorResolution.AnchorSignDescriptor
tapCase.TapTweaks.Val.AnchorTweak = anchorSignDesc.TapTweak
}
if len(htlcBlobs) != 0 {
tapCase.HtlcBlobs = tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType4](htlcBlobs),
)
}
return tapCase.Encode(w)
}
func decodeTapRootAuxData(r io.Reader, c *ContractResolutions) error {
tapCase := newTaprootBriefcase()
if err := tapCase.Decode(r); err != nil {
return err
}
if c.CommitResolution != nil {
c.CommitResolution.SelfOutputSignDesc.ControlBlock =
tapCase.CtrlBlocks.Val.CommitSweepCtrlBlock
tapCase.SettledCommitBlob.WhenSomeV(func(b []byte) {
c.CommitResolution.ResolutionBlob = fn.Some(b)
})
}
htlcBlobs := tapCase.HtlcBlobs.ValOpt().UnwrapOr(newAuxHtlcBlobs())
for i := range c.HtlcResolutions.IncomingHTLCs {
htlc := c.HtlcResolutions.IncomingHTLCs[i]
var resID resolverID
if htlc.SignedSuccessTx != nil {
resID = newResolverID(
htlc.SignedSuccessTx.TxIn[0].PreviousOutPoint,
)
//nolint:ll
ctrlBlock := tapCase.CtrlBlocks.Val.SecondLevelCtrlBlocks[resID]
htlc.SweepSignDesc.ControlBlock = ctrlBlock
//nolint:ll
if htlc.SignDetails != nil {
bridgeCtrlBlock := tapCase.CtrlBlocks.Val.IncomingHtlcCtrlBlocks[resID]
htlc.SignDetails.SignDesc.ControlBlock = bridgeCtrlBlock
}
} else {
resID = newResolverID(htlc.ClaimOutpoint)
//nolint:ll
ctrlBlock := tapCase.CtrlBlocks.Val.IncomingHtlcCtrlBlocks[resID]
htlc.SweepSignDesc.ControlBlock = ctrlBlock
}
if htlcBlob, ok := htlcBlobs[resID]; ok {
htlc.ResolutionBlob = fn.Some(htlcBlob)
}
c.HtlcResolutions.IncomingHTLCs[i] = htlc
}
for i := range c.HtlcResolutions.OutgoingHTLCs {
htlc := c.HtlcResolutions.OutgoingHTLCs[i]
var resID resolverID
if htlc.SignedTimeoutTx != nil {
resID = newResolverID(
htlc.SignedTimeoutTx.TxIn[0].PreviousOutPoint,
)
//nolint:ll
ctrlBlock := tapCase.CtrlBlocks.Val.SecondLevelCtrlBlocks[resID]
htlc.SweepSignDesc.ControlBlock = ctrlBlock
//nolint:ll
if htlc.SignDetails != nil {
bridgeCtrlBlock := tapCase.CtrlBlocks.Val.OutgoingHtlcCtrlBlocks[resID]
htlc.SignDetails.SignDesc.ControlBlock = bridgeCtrlBlock
}
} else {
resID = newResolverID(htlc.ClaimOutpoint)
//nolint:ll
ctrlBlock := tapCase.CtrlBlocks.Val.OutgoingHtlcCtrlBlocks[resID]
htlc.SweepSignDesc.ControlBlock = ctrlBlock
}
if htlcBlob, ok := htlcBlobs[resID]; ok {
htlc.ResolutionBlob = fn.Some(htlcBlob)
}
c.HtlcResolutions.OutgoingHTLCs[i] = htlc
}
if c.AnchorResolution != nil {
c.AnchorResolution.AnchorSignDescriptor.TapTweak =
tapCase.TapTweaks.Val.AnchorTweak
}
return nil
}
package contractcourt
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/lightningnetwork/lnd/chainio"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
)
// ErrChainArbExiting signals that the chain arbitrator is shutting down.
var ErrChainArbExiting = errors.New("ChainArbitrator exiting")
// ResolutionMsg is a message sent by resolvers to outside sub-systems once an
// outgoing contract has been fully resolved. For multi-hop contracts, if we
// resolve the outgoing contract, we'll also need to ensure that the incoming
// contract is resolved as well. We package the items required to resolve the
// incoming contracts within this message.
type ResolutionMsg struct {
// SourceChan identifies the channel that this message is being sent
// from. This is the channel's short channel ID.
SourceChan lnwire.ShortChannelID
// HtlcIndex is the index of the contract within the original
// commitment trace.
HtlcIndex uint64
// Failure will be non-nil if the incoming contract should be canceled
// all together. This can happen if the outgoing contract was dust, if
// if the outgoing HTLC timed out.
Failure lnwire.FailureMessage
// PreImage will be non-nil if the incoming contract can successfully
// be redeemed. This can happen if we learn of the preimage from the
// outgoing HTLC on-chain.
PreImage *[32]byte
}
// ChainArbitratorConfig is a configuration struct that contains all the
// function closures and interface that required to arbitrate on-chain
// contracts for a particular chain.
type ChainArbitratorConfig struct {
// ChainHash is the chain that this arbitrator is to operate within.
ChainHash chainhash.Hash
// IncomingBroadcastDelta is the delta that we'll use to decide when to
// broadcast our commitment transaction if we have incoming htlcs. This
// value should be set based on our current fee estimation of the
// commitment transaction. We use this to determine when we should
// broadcast instead of just the HTLC timeout, as we want to ensure
// that the commitment transaction is already confirmed, by the time the
// HTLC expires. Otherwise we may end up not settling the htlc on-chain
// because the other party managed to time it out.
IncomingBroadcastDelta uint32
// OutgoingBroadcastDelta is the delta that we'll use to decide when to
// broadcast our commitment transaction if there are active outgoing
// htlcs. This value can be lower than the incoming broadcast delta.
OutgoingBroadcastDelta uint32
// NewSweepAddr is a function that returns a new address under control
// by the wallet. We'll use this to sweep any no-delay outputs as a
// result of unilateral channel closes.
//
// NOTE: This SHOULD return a p2wkh script.
NewSweepAddr func() ([]byte, error)
// PublishTx reliably broadcasts a transaction to the network. Once
// this function exits without an error, then they transaction MUST
// continually be rebroadcast if needed.
PublishTx func(*wire.MsgTx, string) error
// DeliverResolutionMsg is a function that will append an outgoing
// message to the "out box" for a ChannelLink. This is used to cancel
// backwards any HTLC's that are either dust, we're timing out, or
// settling on-chain to the incoming link.
DeliverResolutionMsg func(...ResolutionMsg) error
// MarkLinkInactive is a function closure that the ChainArbitrator will
// use to mark that active HTLC's shouldn't be attempted to be routed
// over a particular channel. This function will be called in that a
// ChannelArbitrator decides that it needs to go to chain in order to
// resolve contracts.
//
// TODO(roasbeef): rename, routing based
MarkLinkInactive func(wire.OutPoint) error
// ContractBreach is a function closure that the ChainArbitrator will
// use to notify the BreachArbitrator about a contract breach. It should
// only return a non-nil error when the BreachArbitrator has preserved
// the necessary breach info for this channel point. Once the breach
// resolution is persisted in the ChannelArbitrator, it will be safe
// to mark the channel closed.
ContractBreach func(wire.OutPoint, *lnwallet.BreachRetribution) error
// IsOurAddress is a function that returns true if the passed address
// is known to the underlying wallet. Otherwise, false should be
// returned.
IsOurAddress func(btcutil.Address) bool
// IncubateOutputs sends either an incoming HTLC, an outgoing HTLC, or
// both to the utxo nursery. Once this function returns, the nursery
// should have safely persisted the outputs to disk, and should start
// the process of incubation. This is used when a resolver wishes to
// pass off the output to the nursery as we're only waiting on an
// absolute/relative item block.
IncubateOutputs func(wire.OutPoint,
fn.Option[lnwallet.OutgoingHtlcResolution],
fn.Option[lnwallet.IncomingHtlcResolution],
uint32, fn.Option[int32]) error
// PreimageDB is a global store of all known pre-images. We'll use this
// to decide if we should broadcast a commitment transaction to claim
// an HTLC on-chain.
PreimageDB WitnessBeacon
// Notifier is an instance of a chain notifier we'll use to watch for
// certain on-chain events.
Notifier chainntnfs.ChainNotifier
// Mempool is the a mempool watcher that allows us to watch for events
// happened in mempool.
Mempool chainntnfs.MempoolWatcher
// Signer is a signer backed by the active lnd node. This should be
// capable of producing a signature as specified by a valid
// SignDescriptor.
Signer input.Signer
// FeeEstimator will be used to return fee estimates.
FeeEstimator chainfee.Estimator
// ChainIO allows us to query the state of the current main chain.
ChainIO lnwallet.BlockChainIO
// DisableChannel disables a channel, resulting in it not being able to
// forward payments.
DisableChannel func(wire.OutPoint) error
// Sweeper allows resolvers to sweep their final outputs.
Sweeper UtxoSweeper
// Registry is the invoice database that is used by resolvers to lookup
// preimages and settle invoices.
Registry Registry
// NotifyClosedChannel is a function closure that the ChainArbitrator
// will use to notify the ChannelNotifier about a newly closed channel.
NotifyClosedChannel func(wire.OutPoint)
// NotifyFullyResolvedChannel is a function closure that the
// ChainArbitrator will use to notify the ChannelNotifier about a newly
// resolved channel. The main difference to NotifyClosedChannel is that
// in case of a local force close the NotifyClosedChannel is called when
// the published commitment transaction confirms while
// NotifyFullyResolvedChannel is only called when the channel is fully
// resolved (which includes sweeping any time locked funds).
NotifyFullyResolvedChannel func(point wire.OutPoint)
// OnionProcessor is used to decode onion payloads for on-chain
// resolution.
OnionProcessor OnionProcessor
// PaymentsExpirationGracePeriod indicates a time window we let the
// other node to cancel an outgoing htlc that our node has initiated and
// has timed out.
PaymentsExpirationGracePeriod time.Duration
// IsForwardedHTLC checks for a given htlc, identified by channel id and
// htlcIndex, if it is a forwarded one.
IsForwardedHTLC func(chanID lnwire.ShortChannelID, htlcIndex uint64) bool
// Clock is the clock implementation that ChannelArbitrator uses.
// It is useful for testing.
Clock clock.Clock
// SubscribeBreachComplete is used by the breachResolver to register a
// subscription that notifies when the breach resolution process is
// complete.
SubscribeBreachComplete func(op *wire.OutPoint, c chan struct{}) (
bool, error)
// PutFinalHtlcOutcome stores the final outcome of an htlc in the
// database.
PutFinalHtlcOutcome func(chanId lnwire.ShortChannelID,
htlcId uint64, settled bool) error
// HtlcNotifier is an interface that htlc events are sent to.
HtlcNotifier HtlcNotifier
// Budget is the configured budget for the arbitrator.
Budget BudgetConfig
// QueryIncomingCircuit is used to find the outgoing HTLC's
// corresponding incoming HTLC circuit. It queries the circuit map for
// a given outgoing circuit key and returns the incoming circuit key.
//
// TODO(yy): this is a hacky way to get around the cycling import issue
// as we cannot import `htlcswitch` here. A proper way is to define an
// interface here that asks for method `LookupOpenCircuit`,
// meanwhile, turn `PaymentCircuit` into an interface or bring it to a
// lower package.
QueryIncomingCircuit func(circuit models.CircuitKey) *models.CircuitKey
// AuxLeafStore is an optional store that can be used to store auxiliary
// leaves for certain custom channel types.
AuxLeafStore fn.Option[lnwallet.AuxLeafStore]
// AuxSigner is an optional signer that can be used to sign auxiliary
// leaves for certain custom channel types.
AuxSigner fn.Option[lnwallet.AuxSigner]
// AuxResolver is an optional interface that can be used to modify the
// way contracts are resolved.
AuxResolver fn.Option[lnwallet.AuxContractResolver]
}
// ChainArbitrator is a sub-system that oversees the on-chain resolution of all
// active, and channel that are in the "pending close" state. Within the
// contractcourt package, the ChainArbitrator manages a set of active
// ContractArbitrators. Each ContractArbitrators is responsible for watching
// the chain for any activity that affects the state of the channel, and also
// for monitoring each contract in order to determine if any on-chain activity is
// required. Outside sub-systems interact with the ChainArbitrator in order to
// forcibly exit a contract, update the set of live signals for each contract,
// and to receive reports on the state of contract resolution.
type ChainArbitrator struct {
started int32 // To be used atomically.
stopped int32 // To be used atomically.
// Embed the blockbeat consumer struct to get access to the method
// `NotifyBlockProcessed` and the `BlockbeatChan`.
chainio.BeatConsumer
sync.Mutex
// activeChannels is a map of all the active contracts that are still
// open, and not fully resolved.
activeChannels map[wire.OutPoint]*ChannelArbitrator
// activeWatchers is a map of all the active chainWatchers for channels
// that are still considered open.
activeWatchers map[wire.OutPoint]*chainWatcher
// cfg is the config struct for the arbitrator that contains all
// methods and interface it needs to operate.
cfg ChainArbitratorConfig
// chanSource will be used by the ChainArbitrator to fetch all the
// active channels that it must still watch over.
chanSource *channeldb.DB
// beat is the current best known blockbeat.
beat chainio.Blockbeat
quit chan struct{}
wg sync.WaitGroup
}
// NewChainArbitrator returns a new instance of the ChainArbitrator using the
// passed config struct, and backing persistent database.
func NewChainArbitrator(cfg ChainArbitratorConfig,
db *channeldb.DB) *ChainArbitrator {
c := &ChainArbitrator{
cfg: cfg,
activeChannels: make(map[wire.OutPoint]*ChannelArbitrator),
activeWatchers: make(map[wire.OutPoint]*chainWatcher),
chanSource: db,
quit: make(chan struct{}),
}
// Mount the block consumer.
c.BeatConsumer = chainio.NewBeatConsumer(c.quit, c.Name())
return c
}
// Compile-time check for the chainio.Consumer interface.
var _ chainio.Consumer = (*ChainArbitrator)(nil)
// arbChannel is a wrapper around an open channel that channel arbitrators
// interact with.
type arbChannel struct {
// channel is the in-memory channel state.
channel *channeldb.OpenChannel
// c references the chain arbitrator and is used by arbChannel
// internally.
c *ChainArbitrator
}
// NewAnchorResolutions returns the anchor resolutions for currently valid
// commitment transactions.
//
// NOTE: Part of the ArbChannel interface.
func (a *arbChannel) NewAnchorResolutions() (*lnwallet.AnchorResolutions,
error) {
// Get a fresh copy of the database state to base the anchor resolutions
// on. Unfortunately the channel instance that we have here isn't the
// same instance that is used by the link.
chanPoint := a.channel.FundingOutpoint
channel, err := a.c.chanSource.ChannelStateDB().FetchChannel(chanPoint)
if err != nil {
return nil, err
}
var chanOpts []lnwallet.ChannelOpt
a.c.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) {
chanOpts = append(chanOpts, lnwallet.WithLeafStore(s))
})
a.c.cfg.AuxSigner.WhenSome(func(s lnwallet.AuxSigner) {
chanOpts = append(chanOpts, lnwallet.WithAuxSigner(s))
})
a.c.cfg.AuxResolver.WhenSome(func(s lnwallet.AuxContractResolver) {
chanOpts = append(chanOpts, lnwallet.WithAuxResolver(s))
})
chanMachine, err := lnwallet.NewLightningChannel(
a.c.cfg.Signer, channel, nil, chanOpts...,
)
if err != nil {
return nil, err
}
return chanMachine.NewAnchorResolutions()
}
// ForceCloseChan should force close the contract that this attendant is
// watching over. We'll use this when we decide that we need to go to chain. It
// should in addition tell the switch to remove the corresponding link, such
// that we won't accept any new updates.
//
// NOTE: Part of the ArbChannel interface.
func (a *arbChannel) ForceCloseChan() (*wire.MsgTx, error) {
// First, we mark the channel as borked, this ensure
// that no new state transitions can happen, and also
// that the link won't be loaded into the switch.
if err := a.channel.MarkBorked(); err != nil {
return nil, err
}
// With the channel marked as borked, we'll now remove
// the link from the switch if its there. If the link
// is active, then this method will block until it
// exits.
chanPoint := a.channel.FundingOutpoint
if err := a.c.cfg.MarkLinkInactive(chanPoint); err != nil {
log.Errorf("unable to mark link inactive: %v", err)
}
// Now that we know the link can't mutate the channel
// state, we'll read the channel from disk the target
// channel according to its channel point.
channel, err := a.c.chanSource.ChannelStateDB().FetchChannel(chanPoint)
if err != nil {
return nil, err
}
var chanOpts []lnwallet.ChannelOpt
a.c.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) {
chanOpts = append(chanOpts, lnwallet.WithLeafStore(s))
})
a.c.cfg.AuxSigner.WhenSome(func(s lnwallet.AuxSigner) {
chanOpts = append(chanOpts, lnwallet.WithAuxSigner(s))
})
a.c.cfg.AuxResolver.WhenSome(func(s lnwallet.AuxContractResolver) {
chanOpts = append(chanOpts, lnwallet.WithAuxResolver(s))
})
// Finally, we'll force close the channel completing
// the force close workflow.
chanMachine, err := lnwallet.NewLightningChannel(
a.c.cfg.Signer, channel, nil, chanOpts...,
)
if err != nil {
return nil, err
}
closeSummary, err := chanMachine.ForceClose(
lnwallet.WithSkipContractResolutions(),
)
if err != nil {
return nil, err
}
return closeSummary.CloseTx, nil
}
// newActiveChannelArbitrator creates a new instance of an active channel
// arbitrator given the state of the target channel.
func newActiveChannelArbitrator(channel *channeldb.OpenChannel,
c *ChainArbitrator, chanEvents *ChainEventSubscription) (*ChannelArbitrator, error) {
// TODO(roasbeef): fetch best height (or pass in) so can ensure block
// epoch delivers all the notifications to
chanPoint := channel.FundingOutpoint
log.Tracef("Creating ChannelArbitrator for ChannelPoint(%v)", chanPoint)
// Next we'll create the matching configuration struct that contains
// all interfaces and methods the arbitrator needs to do its job.
arbCfg := ChannelArbitratorConfig{
ChanPoint: chanPoint,
Channel: c.getArbChannel(channel),
ShortChanID: channel.ShortChanID(),
MarkCommitmentBroadcasted: channel.MarkCommitmentBroadcasted,
MarkChannelClosed: func(summary *channeldb.ChannelCloseSummary,
statuses ...channeldb.ChannelStatus) error {
err := channel.CloseChannel(summary, statuses...)
if err != nil {
return err
}
c.cfg.NotifyClosedChannel(summary.ChanPoint)
return nil
},
IsPendingClose: false,
ChainArbitratorConfig: c.cfg,
ChainEvents: chanEvents,
PutResolverReport: func(tx kvdb.RwTx,
report *channeldb.ResolverReport) error {
return c.chanSource.PutResolverReport(
tx, c.cfg.ChainHash, &chanPoint, report,
)
},
FetchHistoricalChannel: func() (*channeldb.OpenChannel, error) {
chanStateDB := c.chanSource.ChannelStateDB()
return chanStateDB.FetchHistoricalChannel(&chanPoint)
},
FindOutgoingHTLCDeadline: func(
htlc channeldb.HTLC) fn.Option[int32] {
return c.FindOutgoingHTLCDeadline(
channel.ShortChanID(), htlc,
)
},
}
// The final component needed is an arbitrator log that the arbitrator
// will use to keep track of its internal state using a backed
// persistent log.
//
// TODO(roasbeef); abstraction leak...
// * rework: adaptor method to set log scope w/ factory func
chanLog, err := newBoltArbitratorLog(
c.chanSource.Backend, arbCfg, c.cfg.ChainHash, chanPoint,
)
if err != nil {
return nil, err
}
arbCfg.MarkChannelResolved = func() error {
if c.cfg.NotifyFullyResolvedChannel != nil {
c.cfg.NotifyFullyResolvedChannel(chanPoint)
}
return c.ResolveContract(chanPoint)
}
// Finally, we'll need to construct a series of htlc Sets based on all
// currently known valid commitments.
htlcSets := make(map[HtlcSetKey]htlcSet)
htlcSets[LocalHtlcSet] = newHtlcSet(channel.LocalCommitment.Htlcs)
htlcSets[RemoteHtlcSet] = newHtlcSet(channel.RemoteCommitment.Htlcs)
pendingRemoteCommitment, err := channel.RemoteCommitChainTip()
if err != nil && err != channeldb.ErrNoPendingCommit {
return nil, err
}
if pendingRemoteCommitment != nil {
htlcSets[RemotePendingHtlcSet] = newHtlcSet(
pendingRemoteCommitment.Commitment.Htlcs,
)
}
return NewChannelArbitrator(
arbCfg, htlcSets, chanLog,
), nil
}
// getArbChannel returns an open channel wrapper for use by channel arbitrators.
func (c *ChainArbitrator) getArbChannel(
channel *channeldb.OpenChannel) *arbChannel {
return &arbChannel{
channel: channel,
c: c,
}
}
// ResolveContract marks a contract as fully resolved within the database.
// This is only to be done once all contracts which were live on the channel
// before hitting the chain have been resolved.
func (c *ChainArbitrator) ResolveContract(chanPoint wire.OutPoint) error {
log.Infof("Marking ChannelPoint(%v) fully resolved", chanPoint)
// First, we'll we'll mark the channel as fully closed from the PoV of
// the channel source.
err := c.chanSource.ChannelStateDB().MarkChanFullyClosed(&chanPoint)
if err != nil {
log.Errorf("ChainArbitrator: unable to mark ChannelPoint(%v) "+
"fully closed: %v", chanPoint, err)
return err
}
// Now that the channel has been marked as fully closed, we'll stop
// both the channel arbitrator and chain watcher for this channel if
// they're still active.
var arbLog ArbitratorLog
c.Lock()
chainArb := c.activeChannels[chanPoint]
delete(c.activeChannels, chanPoint)
chainWatcher := c.activeWatchers[chanPoint]
delete(c.activeWatchers, chanPoint)
c.Unlock()
if chainArb != nil {
arbLog = chainArb.log
if err := chainArb.Stop(); err != nil {
log.Warnf("unable to stop ChannelArbitrator(%v): %v",
chanPoint, err)
}
}
if chainWatcher != nil {
if err := chainWatcher.Stop(); err != nil {
log.Warnf("unable to stop ChainWatcher(%v): %v",
chanPoint, err)
}
}
// Once this has been marked as resolved, we'll wipe the log that the
// channel arbitrator was using to store its persistent state. We do
// this after marking the channel resolved, as otherwise, the
// arbitrator would be re-created, and think it was starting from the
// default state.
if arbLog != nil {
if err := arbLog.WipeHistory(); err != nil {
return err
}
}
return nil
}
// Start launches all goroutines that the ChainArbitrator needs to operate.
func (c *ChainArbitrator) Start(beat chainio.Blockbeat) error {
if !atomic.CompareAndSwapInt32(&c.started, 0, 1) {
return nil
}
// Set the current beat.
c.beat = beat
// First, we'll fetch all the channels that are still open, in order to
// collect them within our set of active contracts.
if err := c.loadOpenChannels(); err != nil {
return err
}
// In addition to the channels that we know to be open, we'll also
// launch arbitrators to finishing resolving any channels that are in
// the pending close state.
if err := c.loadPendingCloseChannels(); err != nil {
return err
}
// Now, we'll start all chain watchers in parallel to shorten start up
// duration. In neutrino mode, this allows spend registrations to take
// advantage of batch spend reporting, instead of doing a single rescan
// per chain watcher.
//
// NOTE: After this point, we Stop the chain arb to ensure that any
// lingering goroutines are cleaned up before exiting.
watcherErrs := make(chan error, len(c.activeWatchers))
var wg sync.WaitGroup
for _, watcher := range c.activeWatchers {
wg.Add(1)
go func(w *chainWatcher) {
defer wg.Done()
select {
case watcherErrs <- w.Start():
case <-c.quit:
watcherErrs <- ErrChainArbExiting
}
}(watcher)
}
// Once all chain watchers have been started, seal the err chan to
// signal the end of the err stream.
go func() {
wg.Wait()
close(watcherErrs)
}()
// stopAndLog is a helper function which shuts down the chain arb and
// logs errors if they occur.
stopAndLog := func() {
if err := c.Stop(); err != nil {
log.Errorf("ChainArbitrator could not shutdown: %v", err)
}
}
// Handle all errors returned from spawning our chain watchers. If any
// of them failed, we will stop the chain arb to shutdown any active
// goroutines.
for err := range watcherErrs {
if err != nil {
stopAndLog()
return err
}
}
// Before we start all of our arbitrators, we do a preliminary state
// lookup so that we can combine all of these lookups in a single db
// transaction.
var startStates map[wire.OutPoint]*chanArbStartState
err := kvdb.View(c.chanSource, func(tx walletdb.ReadTx) error {
for _, arbitrator := range c.activeChannels {
startState, err := arbitrator.getStartState(tx)
if err != nil {
return err
}
startStates[arbitrator.cfg.ChanPoint] = startState
}
return nil
}, func() {
startStates = make(
map[wire.OutPoint]*chanArbStartState,
len(c.activeChannels),
)
})
if err != nil {
stopAndLog()
return err
}
// Launch all the goroutines for each arbitrator so they can carry out
// their duties.
for _, arbitrator := range c.activeChannels {
startState, ok := startStates[arbitrator.cfg.ChanPoint]
if !ok {
stopAndLog()
return fmt.Errorf("arbitrator: %v has no start state",
arbitrator.cfg.ChanPoint)
}
if err := arbitrator.Start(startState, c.beat); err != nil {
stopAndLog()
return err
}
}
// Start our goroutine which will dispatch blocks to each arbitrator.
c.wg.Add(1)
go func() {
defer c.wg.Done()
c.dispatchBlocks()
}()
log.Infof("ChainArbitrator starting at height %d with %d chain "+
"watchers, %d channel arbitrators, and budget config=[%v]",
c.beat.Height(), len(c.activeWatchers), len(c.activeChannels),
&c.cfg.Budget)
// TODO(roasbeef): eventually move all breach watching here
return nil
}
// dispatchBlocks consumes a block epoch notification stream and dispatches
// blocks to each of the chain arb's active channel arbitrators. This function
// must be run in a goroutine.
func (c *ChainArbitrator) dispatchBlocks() {
// Consume block epochs until we receive the instruction to shutdown.
for {
select {
// Consume block epochs, exiting if our subscription is
// terminated.
case beat := <-c.BlockbeatChan:
// Set the current blockbeat.
c.beat = beat
// Send this blockbeat to all the active channels and
// wait for them to finish processing it.
c.handleBlockbeat(beat)
// Exit if the chain arbitrator is shutting down.
case <-c.quit:
return
}
}
}
// handleBlockbeat sends the blockbeat to all active channel arbitrator in
// parallel and wait for them to finish processing it.
func (c *ChainArbitrator) handleBlockbeat(beat chainio.Blockbeat) {
// Read the active channels in a lock.
c.Lock()
// Create a slice to record active channel arbitrator.
channels := make([]chainio.Consumer, 0, len(c.activeChannels))
watchers := make([]chainio.Consumer, 0, len(c.activeWatchers))
// Copy the active channels to the slice.
for _, channel := range c.activeChannels {
channels = append(channels, channel)
}
for _, watcher := range c.activeWatchers {
watchers = append(watchers, watcher)
}
c.Unlock()
// Iterate all the copied watchers and send the blockbeat to them.
err := chainio.DispatchConcurrent(beat, watchers)
if err != nil {
log.Errorf("Notify blockbeat for chainWatcher failed: %v", err)
}
// Iterate all the copied channels and send the blockbeat to them.
//
// NOTE: This method will timeout if the processing of blocks of the
// subsystems is too long (60s).
err = chainio.DispatchConcurrent(beat, channels)
if err != nil {
log.Errorf("Notify blockbeat for ChannelArbitrator failed: %v",
err)
}
// Notify the chain arbitrator has processed the block.
c.NotifyBlockProcessed(beat, err)
}
// republishClosingTxs will load any stored cooperative or unilateral closing
// transactions and republish them. This helps ensure propagation of the
// transactions in the event that prior publications failed.
func (c *ChainArbitrator) republishClosingTxs(
channel *channeldb.OpenChannel) error {
// If the channel has had its unilateral close broadcasted already,
// republish it in case it didn't propagate.
if channel.HasChanStatus(channeldb.ChanStatusCommitBroadcasted) {
err := c.rebroadcast(
channel, channeldb.ChanStatusCommitBroadcasted,
)
if err != nil {
return err
}
}
// If the channel has had its cooperative close broadcasted
// already, republish it in case it didn't propagate.
if channel.HasChanStatus(channeldb.ChanStatusCoopBroadcasted) {
err := c.rebroadcast(
channel, channeldb.ChanStatusCoopBroadcasted,
)
if err != nil {
return err
}
}
return nil
}
// rebroadcast is a helper method which will republish the unilateral or
// cooperative close transaction or a channel in a particular state.
//
// NOTE: There is no risk to calling this method if the channel isn't in either
// CommitmentBroadcasted or CoopBroadcasted, but the logs will be misleading.
func (c *ChainArbitrator) rebroadcast(channel *channeldb.OpenChannel,
state channeldb.ChannelStatus) error {
chanPoint := channel.FundingOutpoint
var (
closeTx *wire.MsgTx
kind string
err error
)
switch state {
case channeldb.ChanStatusCommitBroadcasted:
kind = "force"
closeTx, err = channel.BroadcastedCommitment()
case channeldb.ChanStatusCoopBroadcasted:
kind = "coop"
closeTx, err = channel.BroadcastedCooperative()
default:
return fmt.Errorf("unknown closing state: %v", state)
}
switch {
// This can happen for channels that had their closing tx published
// before we started storing it to disk.
case err == channeldb.ErrNoCloseTx:
log.Warnf("Channel %v is in state %v, but no %s closing tx "+
"to re-publish...", chanPoint, state, kind)
return nil
case err != nil:
return err
}
log.Infof("Re-publishing %s close tx(%v) for channel %v",
kind, closeTx.TxHash(), chanPoint)
label := labels.MakeLabel(
labels.LabelTypeChannelClose, &channel.ShortChannelID,
)
err = c.cfg.PublishTx(closeTx, label)
if err != nil && err != lnwallet.ErrDoubleSpend {
log.Warnf("Unable to broadcast %s close tx(%v): %v",
kind, closeTx.TxHash(), err)
}
return nil
}
// Stop signals the ChainArbitrator to trigger a graceful shutdown. Any active
// channel arbitrators will be signalled to exit, and this method will block
// until they've all exited.
func (c *ChainArbitrator) Stop() error {
if !atomic.CompareAndSwapInt32(&c.stopped, 0, 1) {
return nil
}
log.Info("ChainArbitrator shutting down...")
defer log.Debug("ChainArbitrator shutdown complete")
close(c.quit)
var (
activeWatchers = make(map[wire.OutPoint]*chainWatcher)
activeChannels = make(map[wire.OutPoint]*ChannelArbitrator)
)
// Copy the current set of active watchers and arbitrators to shutdown.
// We don't want to hold the lock when shutting down each watcher or
// arbitrator individually, as they may need to acquire this mutex.
c.Lock()
for chanPoint, watcher := range c.activeWatchers {
activeWatchers[chanPoint] = watcher
}
for chanPoint, arbitrator := range c.activeChannels {
activeChannels[chanPoint] = arbitrator
}
c.Unlock()
for chanPoint, watcher := range activeWatchers {
log.Tracef("Attempting to stop ChainWatcher(%v)",
chanPoint)
if err := watcher.Stop(); err != nil {
log.Errorf("unable to stop watcher for "+
"ChannelPoint(%v): %v", chanPoint, err)
}
}
for chanPoint, arbitrator := range activeChannels {
log.Tracef("Attempting to stop ChannelArbitrator(%v)",
chanPoint)
if err := arbitrator.Stop(); err != nil {
log.Errorf("unable to stop arbitrator for "+
"ChannelPoint(%v): %v", chanPoint, err)
}
}
c.wg.Wait()
return nil
}
// ContractUpdate is a message packages the latest set of active HTLCs on a
// commitment, and also identifies which commitment received a new set of
// HTLCs.
type ContractUpdate struct {
// HtlcKey identifies which commitment the HTLCs below are present on.
HtlcKey HtlcSetKey
// Htlcs are the of active HTLCs on the commitment identified by the
// above HtlcKey.
Htlcs []channeldb.HTLC
}
// ContractSignals is used by outside subsystems to notify a channel arbitrator
// of its ShortChannelID.
type ContractSignals struct {
// ShortChanID is the up to date short channel ID for a contract. This
// can change either if when the contract was added it didn't yet have
// a stable identifier, or in the case of a reorg.
ShortChanID lnwire.ShortChannelID
}
// UpdateContractSignals sends a set of active, up to date contract signals to
// the ChannelArbitrator which is has been assigned to the channel infield by
// the passed channel point.
func (c *ChainArbitrator) UpdateContractSignals(chanPoint wire.OutPoint,
signals *ContractSignals) error {
log.Infof("Attempting to update ContractSignals for ChannelPoint(%v)",
chanPoint)
c.Lock()
arbitrator, ok := c.activeChannels[chanPoint]
c.Unlock()
if !ok {
return fmt.Errorf("unable to find arbitrator")
}
arbitrator.UpdateContractSignals(signals)
return nil
}
// NotifyContractUpdate lets a channel arbitrator know that a new
// ContractUpdate is available. This calls the ChannelArbitrator's internal
// method NotifyContractUpdate which waits for a response on a done chan before
// returning. This method will return an error if the ChannelArbitrator is not
// in the activeChannels map. However, this only happens if the arbitrator is
// resolved and the related link would already be shut down.
func (c *ChainArbitrator) NotifyContractUpdate(chanPoint wire.OutPoint,
update *ContractUpdate) error {
c.Lock()
arbitrator, ok := c.activeChannels[chanPoint]
c.Unlock()
if !ok {
return fmt.Errorf("can't find arbitrator for %v", chanPoint)
}
arbitrator.notifyContractUpdate(update)
return nil
}
// GetChannelArbitrator safely returns the channel arbitrator for a given
// channel outpoint.
func (c *ChainArbitrator) GetChannelArbitrator(chanPoint wire.OutPoint) (
*ChannelArbitrator, error) {
c.Lock()
arbitrator, ok := c.activeChannels[chanPoint]
c.Unlock()
if !ok {
return nil, fmt.Errorf("unable to find arbitrator")
}
return arbitrator, nil
}
// forceCloseReq is a request sent from an outside sub-system to the arbitrator
// that watches a particular channel to broadcast the commitment transaction,
// and enter the resolution phase of the channel.
type forceCloseReq struct {
// errResp is a channel that will be sent upon either in the case of
// force close success (nil error), or in the case on an error.
//
// NOTE; This channel MUST be buffered.
errResp chan error
// closeTx is a channel that carries the transaction which ultimately
// closed out the channel.
closeTx chan *wire.MsgTx
}
// ForceCloseContract attempts to force close the channel infield by the passed
// channel point. A force close will immediately terminate the contract,
// causing it to enter the resolution phase. If the force close was successful,
// then the force close transaction itself will be returned.
//
// TODO(roasbeef): just return the summary itself?
func (c *ChainArbitrator) ForceCloseContract(chanPoint wire.OutPoint) (*wire.MsgTx, error) {
c.Lock()
arbitrator, ok := c.activeChannels[chanPoint]
c.Unlock()
if !ok {
return nil, fmt.Errorf("unable to find arbitrator")
}
log.Infof("Attempting to force close ChannelPoint(%v)", chanPoint)
// Before closing, we'll attempt to send a disable update for the
// channel. We do so before closing the channel as otherwise the current
// edge policy won't be retrievable from the graph.
if err := c.cfg.DisableChannel(chanPoint); err != nil {
log.Warnf("Unable to disable channel %v on "+
"close: %v", chanPoint, err)
}
errChan := make(chan error, 1)
respChan := make(chan *wire.MsgTx, 1)
// With the channel found, and the request crafted, we'll send over a
// force close request to the arbitrator that watches this channel.
select {
case arbitrator.forceCloseReqs <- &forceCloseReq{
errResp: errChan,
closeTx: respChan,
}:
case <-c.quit:
return nil, ErrChainArbExiting
}
// We'll await two responses: the error response, and the transaction
// that closed out the channel.
select {
case err := <-errChan:
if err != nil {
return nil, err
}
case <-c.quit:
return nil, ErrChainArbExiting
}
var closeTx *wire.MsgTx
select {
case closeTx = <-respChan:
case <-c.quit:
return nil, ErrChainArbExiting
}
return closeTx, nil
}
// WatchNewChannel sends the ChainArbitrator a message to create a
// ChannelArbitrator tasked with watching over a new channel. Once a new
// channel has finished its final funding flow, it should be registered with
// the ChainArbitrator so we can properly react to any on-chain events.
func (c *ChainArbitrator) WatchNewChannel(newChan *channeldb.OpenChannel) error {
c.Lock()
defer c.Unlock()
chanPoint := newChan.FundingOutpoint
log.Infof("Creating new chainWatcher and ChannelArbitrator for "+
"ChannelPoint(%v)", chanPoint)
// If we're already watching this channel, then we'll ignore this
// request.
if _, ok := c.activeChannels[chanPoint]; ok {
return nil
}
// First, also create an active chainWatcher for this channel to ensure
// that we detect any relevant on chain events.
chainWatcher, err := newChainWatcher(
chainWatcherConfig{
chanState: newChan,
notifier: c.cfg.Notifier,
signer: c.cfg.Signer,
isOurAddr: c.cfg.IsOurAddress,
contractBreach: func(
retInfo *lnwallet.BreachRetribution) error {
return c.cfg.ContractBreach(
chanPoint, retInfo,
)
},
extractStateNumHint: lnwallet.GetStateNumHint,
auxLeafStore: c.cfg.AuxLeafStore,
auxResolver: c.cfg.AuxResolver,
},
)
if err != nil {
return err
}
c.activeWatchers[chanPoint] = chainWatcher
// We'll also create a new channel arbitrator instance using this new
// channel, and our internal state.
channelArb, err := newActiveChannelArbitrator(
newChan, c, chainWatcher.SubscribeChannelEvents(),
)
if err != nil {
return err
}
// With the arbitrator created, we'll add it to our set of active
// arbitrators, then launch it.
c.activeChannels[chanPoint] = channelArb
if err := channelArb.Start(nil, c.beat); err != nil {
return err
}
return chainWatcher.Start()
}
// SubscribeChannelEvents returns a new active subscription for the set of
// possible on-chain events for a particular channel. The struct can be used by
// callers to be notified whenever an event that changes the state of the
// channel on-chain occurs.
func (c *ChainArbitrator) SubscribeChannelEvents(
chanPoint wire.OutPoint) (*ChainEventSubscription, error) {
// First, we'll attempt to look up the active watcher for this channel.
// If we can't find it, then we'll return an error back to the caller.
c.Lock()
watcher, ok := c.activeWatchers[chanPoint]
c.Unlock()
if !ok {
return nil, fmt.Errorf("unable to find watcher for: %v",
chanPoint)
}
// With the watcher located, we'll request for it to create a new chain
// event subscription client.
return watcher.SubscribeChannelEvents(), nil
}
// FindOutgoingHTLCDeadline returns the deadline in absolute block height for
// the specified outgoing HTLC. For an outgoing HTLC, its deadline is defined
// by the timeout height of its corresponding incoming HTLC - this is the
// expiry height the that remote peer can spend his/her outgoing HTLC via the
// timeout path.
func (c *ChainArbitrator) FindOutgoingHTLCDeadline(scid lnwire.ShortChannelID,
outgoingHTLC channeldb.HTLC) fn.Option[int32] {
// Find the outgoing HTLC's corresponding incoming HTLC in the circuit
// map.
rHash := outgoingHTLC.RHash
circuit := models.CircuitKey{
ChanID: scid,
HtlcID: outgoingHTLC.HtlcIndex,
}
incomingCircuit := c.cfg.QueryIncomingCircuit(circuit)
// If there's no incoming circuit found, we will use the default
// deadline.
if incomingCircuit == nil {
log.Warnf("ChannelArbitrator(%v): incoming circuit key not "+
"found for rHash=%x, using default deadline instead",
scid, rHash)
return fn.None[int32]()
}
// If this is a locally initiated HTLC, it means we are the first hop.
// In this case, we can relax the deadline.
if incomingCircuit.ChanID.IsDefault() {
log.Infof("ChannelArbitrator(%v): using default deadline for "+
"locally initiated HTLC for rHash=%x", scid, rHash)
return fn.None[int32]()
}
log.Debugf("Found incoming circuit %v for rHash=%x using outgoing "+
"circuit %v", incomingCircuit, rHash, circuit)
c.Lock()
defer c.Unlock()
// Iterate over all active channels to find the incoming HTLC specified
// by its circuit key.
for cp, channelArb := range c.activeChannels {
// Skip if the SCID doesn't match.
if channelArb.cfg.ShortChanID != incomingCircuit.ChanID {
continue
}
// Make sure the channel arbitrator has the latest view of its
// active HTLCs.
channelArb.updateActiveHTLCs()
// Iterate all the known HTLCs to find the targeted incoming
// HTLC.
for _, htlcs := range channelArb.activeHTLCs {
for _, htlc := range htlcs.incomingHTLCs {
// Skip if the index doesn't match.
if htlc.HtlcIndex != incomingCircuit.HtlcID {
continue
}
log.Debugf("ChannelArbitrator(%v): found "+
"incoming HTLC in channel=%v using "+
"rHash=%x, refundTimeout=%v", scid,
cp, rHash, htlc.RefundTimeout)
return fn.Some(int32(htlc.RefundTimeout))
}
}
}
// If there's no incoming HTLC found, yet we have the incoming circuit,
// something is wrong - in this case, we return the none deadline.
log.Errorf("ChannelArbitrator(%v): incoming HTLC not found for "+
"rHash=%x, using default deadline instead", scid, rHash)
return fn.None[int32]()
}
// TODO(roasbeef): arbitration reports
// * types: contested, waiting for success conf, etc
// NOTE: part of the `chainio.Consumer` interface.
func (c *ChainArbitrator) Name() string {
return "ChainArbitrator"
}
// loadOpenChannels loads all channels that are currently open in the database
// and registers them with the chainWatcher for future notification.
func (c *ChainArbitrator) loadOpenChannels() error {
openChannels, err := c.chanSource.ChannelStateDB().FetchAllChannels()
if err != nil {
return err
}
if len(openChannels) == 0 {
return nil
}
log.Infof("Creating ChannelArbitrators for %v active channels",
len(openChannels))
// For each open channel, we'll configure then launch a corresponding
// ChannelArbitrator.
for _, channel := range openChannels {
chanPoint := channel.FundingOutpoint
channel := channel
// First, we'll create an active chainWatcher for this channel
// to ensure that we detect any relevant on chain events.
breachClosure := func(ret *lnwallet.BreachRetribution) error {
return c.cfg.ContractBreach(chanPoint, ret)
}
chainWatcher, err := newChainWatcher(
chainWatcherConfig{
chanState: channel,
notifier: c.cfg.Notifier,
signer: c.cfg.Signer,
isOurAddr: c.cfg.IsOurAddress,
contractBreach: breachClosure,
extractStateNumHint: lnwallet.GetStateNumHint,
auxLeafStore: c.cfg.AuxLeafStore,
auxResolver: c.cfg.AuxResolver,
},
)
if err != nil {
return err
}
c.activeWatchers[chanPoint] = chainWatcher
channelArb, err := newActiveChannelArbitrator(
channel, c, chainWatcher.SubscribeChannelEvents(),
)
if err != nil {
return err
}
c.activeChannels[chanPoint] = channelArb
// Republish any closing transactions for this channel.
err = c.republishClosingTxs(channel)
if err != nil {
log.Errorf("Failed to republish closing txs for "+
"channel %v", chanPoint)
}
}
return nil
}
// loadPendingCloseChannels loads all channels that are currently pending
// closure in the database and registers them with the ChannelArbitrator to
// continue the resolution process.
func (c *ChainArbitrator) loadPendingCloseChannels() error {
chanStateDB := c.chanSource.ChannelStateDB()
closingChannels, err := chanStateDB.FetchClosedChannels(true)
if err != nil {
return err
}
if len(closingChannels) == 0 {
return nil
}
log.Infof("Creating ChannelArbitrators for %v closing channels",
len(closingChannels))
// Next, for each channel is the closing state, we'll launch a
// corresponding more restricted resolver, as we don't have to watch
// the chain any longer, only resolve the contracts on the confirmed
// commitment.
//nolint:ll
for _, closeChanInfo := range closingChannels {
// We can leave off the CloseContract and ForceCloseChan
// methods as the channel is already closed at this point.
chanPoint := closeChanInfo.ChanPoint
arbCfg := ChannelArbitratorConfig{
ChanPoint: chanPoint,
ShortChanID: closeChanInfo.ShortChanID,
ChainArbitratorConfig: c.cfg,
ChainEvents: &ChainEventSubscription{},
IsPendingClose: true,
ClosingHeight: closeChanInfo.CloseHeight,
CloseType: closeChanInfo.CloseType,
PutResolverReport: func(tx kvdb.RwTx,
report *channeldb.ResolverReport) error {
return c.chanSource.PutResolverReport(
tx, c.cfg.ChainHash, &chanPoint, report,
)
},
FetchHistoricalChannel: func() (*channeldb.OpenChannel, error) {
return chanStateDB.FetchHistoricalChannel(&chanPoint)
},
FindOutgoingHTLCDeadline: func(
htlc channeldb.HTLC) fn.Option[int32] {
return c.FindOutgoingHTLCDeadline(
closeChanInfo.ShortChanID, htlc,
)
},
}
chanLog, err := newBoltArbitratorLog(
c.chanSource.Backend, arbCfg, c.cfg.ChainHash, chanPoint,
)
if err != nil {
return err
}
arbCfg.MarkChannelResolved = func() error {
if c.cfg.NotifyFullyResolvedChannel != nil {
c.cfg.NotifyFullyResolvedChannel(chanPoint)
}
return c.ResolveContract(chanPoint)
}
// We create an empty map of HTLC's here since it's possible
// that the channel is in StateDefault and updateActiveHTLCs is
// called. We want to avoid writing to an empty map. Since the
// channel is already in the process of being resolved, no new
// HTLCs will be added.
c.activeChannels[chanPoint] = NewChannelArbitrator(
arbCfg, make(map[HtlcSetKey]htlcSet), chanLog,
)
}
return nil
}
// RedispatchBlockbeat resends the current blockbeat to the channels specified
// by the chanPoints. It is used when a channel is added to the chain
// arbitrator after it has been started, e.g., during the channel restore
// process.
func (c *ChainArbitrator) RedispatchBlockbeat(chanPoints []wire.OutPoint) {
// Get the current blockbeat.
beat := c.beat
// Prepare two sets of consumers.
channels := make([]chainio.Consumer, 0, len(chanPoints))
watchers := make([]chainio.Consumer, 0, len(chanPoints))
// Read the active channels in a lock.
c.Lock()
for _, op := range chanPoints {
if channel, ok := c.activeChannels[op]; ok {
channels = append(channels, channel)
}
if watcher, ok := c.activeWatchers[op]; ok {
watchers = append(watchers, watcher)
}
}
c.Unlock()
// Iterate all the copied watchers and send the blockbeat to them.
err := chainio.DispatchConcurrent(beat, watchers)
if err != nil {
log.Errorf("Notify blockbeat for chainWatcher failed: %v", err)
}
// Iterate all the copied channels and send the blockbeat to them.
err = chainio.DispatchConcurrent(beat, channels)
if err != nil {
// Shutdown lnd if there's an error processing the block.
log.Errorf("Notify blockbeat for ChannelArbitrator failed: %v",
err)
}
}
package contractcourt
import (
"bytes"
"fmt"
"slices"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/mempool"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainio"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
)
const (
// minCommitPointPollTimeout is the minimum time we'll wait before
// polling the database for a channel's commitpoint.
minCommitPointPollTimeout = 1 * time.Second
// maxCommitPointPollTimeout is the maximum time we'll wait before
// polling the database for a channel's commitpoint.
maxCommitPointPollTimeout = 10 * time.Minute
)
// LocalUnilateralCloseInfo encapsulates all the information we need to act on
// a local force close that gets confirmed.
type LocalUnilateralCloseInfo struct {
*chainntnfs.SpendDetail
*lnwallet.LocalForceCloseSummary
*channeldb.ChannelCloseSummary
// CommitSet is the set of known valid commitments at the time the
// remote party's commitment hit the chain.
CommitSet CommitSet
}
// CooperativeCloseInfo encapsulates all the information we need to act on a
// cooperative close that gets confirmed.
type CooperativeCloseInfo struct {
*channeldb.ChannelCloseSummary
}
// RemoteUnilateralCloseInfo wraps the normal UnilateralCloseSummary to couple
// the CommitSet at the time of channel closure.
type RemoteUnilateralCloseInfo struct {
*lnwallet.UnilateralCloseSummary
// CommitSet is the set of known valid commitments at the time the
// remote party's commitment hit the chain.
CommitSet CommitSet
}
// BreachResolution wraps the outpoint of the breached channel.
type BreachResolution struct {
FundingOutPoint wire.OutPoint
}
// BreachCloseInfo wraps the BreachResolution with a CommitSet for the latest,
// non-breached state, with the AnchorResolution for the breached state.
type BreachCloseInfo struct {
*BreachResolution
*lnwallet.AnchorResolution
// CommitHash is the hash of the commitment transaction.
CommitHash chainhash.Hash
// CommitSet is the set of known valid commitments at the time the
// breach occurred on-chain.
CommitSet CommitSet
// CloseSummary gives the recipient of the BreachCloseInfo information
// to mark the channel closed in the database.
CloseSummary channeldb.ChannelCloseSummary
}
// CommitSet is a collection of the set of known valid commitments at a given
// instant. If ConfCommitKey is set, then the commitment identified by the
// HtlcSetKey has hit the chain. This struct will be used to examine all live
// HTLCs to determine if any additional actions need to be made based on the
// remote party's commitments.
type CommitSet struct {
// When the ConfCommitKey is set, it signals that the commitment tx was
// confirmed in the chain.
ConfCommitKey fn.Option[HtlcSetKey]
// HtlcSets stores the set of all known active HTLC for each active
// commitment at the time of channel closure.
HtlcSets map[HtlcSetKey][]channeldb.HTLC
}
// IsEmpty returns true if there are no HTLCs at all within all commitments
// that are a part of this commitment diff.
func (c *CommitSet) IsEmpty() bool {
if c == nil {
return true
}
for _, htlcs := range c.HtlcSets {
if len(htlcs) != 0 {
return false
}
}
return true
}
// toActiveHTLCSets returns the set of all active HTLCs across all commitment
// transactions.
func (c *CommitSet) toActiveHTLCSets() map[HtlcSetKey]htlcSet {
htlcSets := make(map[HtlcSetKey]htlcSet)
for htlcSetKey, htlcs := range c.HtlcSets {
htlcSets[htlcSetKey] = newHtlcSet(htlcs)
}
return htlcSets
}
// String return a human-readable representation of the CommitSet.
func (c *CommitSet) String() string {
if c == nil {
return "nil"
}
// Create a descriptive string for the ConfCommitKey.
commitKey := "none"
c.ConfCommitKey.WhenSome(func(k HtlcSetKey) {
commitKey = k.String()
})
// Create a map to hold all the htlcs.
htlcSet := make(map[string]string)
for k, htlcs := range c.HtlcSets {
// Create a map for this particular set.
desc := make([]string, len(htlcs))
for i, htlc := range htlcs {
desc[i] = fmt.Sprintf("%x", htlc.RHash)
}
// Add the description to the set key.
htlcSet[k.String()] = fmt.Sprintf("count: %v, htlcs=%v",
len(htlcs), desc)
}
return fmt.Sprintf("ConfCommitKey=%v, HtlcSets=%v", commitKey, htlcSet)
}
// ChainEventSubscription is a struct that houses a subscription to be notified
// for any on-chain events related to a channel. There are three types of
// possible on-chain events: a cooperative channel closure, a unilateral
// channel closure, and a channel breach. The fourth type: a force close is
// locally initiated, so we don't provide any event stream for said event.
type ChainEventSubscription struct {
// ChanPoint is that channel that chain events will be dispatched for.
ChanPoint wire.OutPoint
// RemoteUnilateralClosure is a channel that will be sent upon in the
// event that the remote party's commitment transaction is confirmed.
RemoteUnilateralClosure chan *RemoteUnilateralCloseInfo
// LocalUnilateralClosure is a channel that will be sent upon in the
// event that our commitment transaction is confirmed.
LocalUnilateralClosure chan *LocalUnilateralCloseInfo
// CooperativeClosure is a signal that will be sent upon once a
// cooperative channel closure has been detected confirmed.
CooperativeClosure chan *CooperativeCloseInfo
// ContractBreach is a channel that will be sent upon if we detect a
// contract breach. The struct sent across the channel contains all the
// material required to bring the cheating channel peer to justice.
ContractBreach chan *BreachCloseInfo
// Cancel cancels the subscription to the event stream for a particular
// channel. This method should be called once the caller no longer needs to
// be notified of any on-chain events for a particular channel.
Cancel func()
}
// chainWatcherConfig encapsulates all the necessary functions and interfaces
// needed to watch and act on on-chain events for a particular channel.
type chainWatcherConfig struct {
// chanState is a snapshot of the persistent state of the channel that
// we're watching. In the event of an on-chain event, we'll query the
// database to ensure that we act using the most up to date state.
chanState *channeldb.OpenChannel
// notifier is a reference to the channel notifier that we'll use to be
// notified of output spends and when transactions are confirmed.
notifier chainntnfs.ChainNotifier
// signer is the main signer instances that will be responsible for
// signing any HTLC and commitment transaction generated by the state
// machine.
signer input.Signer
// contractBreach is a method that will be called by the watcher if it
// detects that a contract breach transaction has been confirmed. It
// will only return a non-nil error when the BreachArbitrator has
// preserved the necessary breach info for this channel point.
contractBreach func(*lnwallet.BreachRetribution) error
// isOurAddr is a function that returns true if the passed address is
// known to us.
isOurAddr func(btcutil.Address) bool
// extractStateNumHint extracts the encoded state hint using the passed
// obfuscater. This is used by the chain watcher to identify which
// state was broadcast and confirmed on-chain.
extractStateNumHint func(*wire.MsgTx, [lnwallet.StateHintSize]byte) uint64
// auxLeafStore can be used to fetch information for custom channels.
auxLeafStore fn.Option[lnwallet.AuxLeafStore]
// auxResolver is used to supplement contract resolution.
auxResolver fn.Option[lnwallet.AuxContractResolver]
}
// chainWatcher is a system that's assigned to every active channel. The duty
// of this system is to watch the chain for spends of the channels chan point.
// If a spend is detected then with chain watcher will notify all subscribers
// that the channel has been closed, and also give them the materials necessary
// to sweep the funds of the channel on chain eventually.
type chainWatcher struct {
started int32 // To be used atomically.
stopped int32 // To be used atomically.
// Embed the blockbeat consumer struct to get access to the method
// `NotifyBlockProcessed` and the `BlockbeatChan`.
chainio.BeatConsumer
quit chan struct{}
wg sync.WaitGroup
cfg chainWatcherConfig
// stateHintObfuscator is a 48-bit state hint that's used to obfuscate
// the current state number on the commitment transactions.
stateHintObfuscator [lnwallet.StateHintSize]byte
// All the fields below are protected by this mutex.
sync.Mutex
// clientID is an ephemeral counter used to keep track of each
// individual client subscription.
clientID uint64
// clientSubscriptions is a map that keeps track of all the active
// client subscriptions for events related to this channel.
clientSubscriptions map[uint64]*ChainEventSubscription
// fundingSpendNtfn is the spending notification subscription for the
// funding outpoint.
fundingSpendNtfn *chainntnfs.SpendEvent
// fundingConfirmedNtfn is the confirmation notification subscription
// for the funding outpoint. This is only created if the channel is
// both taproot and pending confirmation.
//
// For taproot pkscripts, `RegisterSpendNtfn` will only notify on the
// outpoint being spent and not the outpoint+pkscript due to
// `ComputePkScript` being unable to compute the pkscript if a key
// spend is used. We need to add a `RegisterConfirmationsNtfn` here to
// ensure that the outpoint+pkscript pair is confirmed before calling
// `RegisterSpendNtfn`.
fundingConfirmedNtfn *chainntnfs.ConfirmationEvent
}
// newChainWatcher returns a new instance of a chainWatcher for a channel given
// the chan point to watch, and also a notifier instance that will allow us to
// detect on chain events.
func newChainWatcher(cfg chainWatcherConfig) (*chainWatcher, error) {
// In order to be able to detect the nature of a potential channel
// closure we'll need to reconstruct the state hint bytes used to
// obfuscate the commitment state number encoded in the lock time and
// sequence fields.
var stateHint [lnwallet.StateHintSize]byte
chanState := cfg.chanState
if chanState.IsInitiator {
stateHint = lnwallet.DeriveStateHintObfuscator(
chanState.LocalChanCfg.PaymentBasePoint.PubKey,
chanState.RemoteChanCfg.PaymentBasePoint.PubKey,
)
} else {
stateHint = lnwallet.DeriveStateHintObfuscator(
chanState.RemoteChanCfg.PaymentBasePoint.PubKey,
chanState.LocalChanCfg.PaymentBasePoint.PubKey,
)
}
// Get the witness script for the funding output.
fundingPkScript, err := deriveFundingPkScript(chanState)
if err != nil {
return nil, err
}
// Get the channel opening block height.
heightHint := chanState.DeriveHeightHint()
// We'll register for a notification to be dispatched if the funding
// output is spent.
spendNtfn, err := cfg.notifier.RegisterSpendNtfn(
&chanState.FundingOutpoint, fundingPkScript, heightHint,
)
if err != nil {
return nil, err
}
c := &chainWatcher{
cfg: cfg,
stateHintObfuscator: stateHint,
quit: make(chan struct{}),
clientSubscriptions: make(map[uint64]*ChainEventSubscription),
fundingSpendNtfn: spendNtfn,
}
// If this is a pending taproot channel, we need to register for a
// confirmation notification of the funding tx. Check the docs in
// `fundingConfirmedNtfn` for details.
if c.cfg.chanState.IsPending && c.cfg.chanState.ChanType.IsTaproot() {
confNtfn, err := cfg.notifier.RegisterConfirmationsNtfn(
&chanState.FundingOutpoint.Hash, fundingPkScript, 1,
heightHint,
)
if err != nil {
return nil, err
}
c.fundingConfirmedNtfn = confNtfn
}
// Mount the block consumer.
c.BeatConsumer = chainio.NewBeatConsumer(c.quit, c.Name())
return c, nil
}
// Compile-time check for the chainio.Consumer interface.
var _ chainio.Consumer = (*chainWatcher)(nil)
// Name returns the name of the watcher.
//
// NOTE: part of the `chainio.Consumer` interface.
func (c *chainWatcher) Name() string {
return fmt.Sprintf("ChainWatcher(%v)", c.cfg.chanState.FundingOutpoint)
}
// Start starts all goroutines that the chainWatcher needs to perform its
// duties.
func (c *chainWatcher) Start() error {
if !atomic.CompareAndSwapInt32(&c.started, 0, 1) {
return nil
}
log.Debugf("Starting chain watcher for ChannelPoint(%v)",
c.cfg.chanState.FundingOutpoint)
c.wg.Add(1)
go c.closeObserver()
return nil
}
// Stop signals the close observer to gracefully exit.
func (c *chainWatcher) Stop() error {
if !atomic.CompareAndSwapInt32(&c.stopped, 0, 1) {
return nil
}
close(c.quit)
c.wg.Wait()
return nil
}
// SubscribeChannelEvents returns an active subscription to the set of channel
// events for the channel watched by this chain watcher. Once clients no longer
// require the subscription, they should call the Cancel() method to allow the
// watcher to regain those committed resources.
func (c *chainWatcher) SubscribeChannelEvents() *ChainEventSubscription {
c.Lock()
clientID := c.clientID
c.clientID++
c.Unlock()
log.Debugf("New ChainEventSubscription(id=%v) for ChannelPoint(%v)",
clientID, c.cfg.chanState.FundingOutpoint)
sub := &ChainEventSubscription{
ChanPoint: c.cfg.chanState.FundingOutpoint,
RemoteUnilateralClosure: make(chan *RemoteUnilateralCloseInfo, 1),
LocalUnilateralClosure: make(chan *LocalUnilateralCloseInfo, 1),
CooperativeClosure: make(chan *CooperativeCloseInfo, 1),
ContractBreach: make(chan *BreachCloseInfo, 1),
Cancel: func() {
c.Lock()
delete(c.clientSubscriptions, clientID)
c.Unlock()
},
}
c.Lock()
c.clientSubscriptions[clientID] = sub
c.Unlock()
return sub
}
// handleUnknownLocalState checks whether the passed spend _could_ be a local
// state that for some reason is unknown to us. This could be a state published
// by us before we lost state, which we will try to sweep. Or it could be one
// of our revoked states that somehow made it to the chain. If that's the case
// we cannot really hope that we'll be able to get our money back, but we'll
// try to sweep it anyway. If this is not an unknown local state, false is
// returned.
func (c *chainWatcher) handleUnknownLocalState(
commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
chainSet *chainSet) (bool, error) {
// If the spend was a local commitment, at this point it must either be
// a past state (we breached!) or a future state (we lost state!). In
// either case, the only thing we can do is to attempt to sweep what is
// there.
// First, we'll re-derive our commitment point for this state since
// this is what we use to randomize each of the keys for this state.
commitSecret, err := c.cfg.chanState.RevocationProducer.AtIndex(
broadcastStateNum,
)
if err != nil {
return false, err
}
commitPoint := input.ComputeCommitmentPoint(commitSecret[:])
// Now that we have the commit point, we'll derive the tweaked local
// and remote keys for this state. We use our point as only we can
// revoke our own commitment.
commitKeyRing := lnwallet.DeriveCommitmentKeys(
commitPoint, lntypes.Local, c.cfg.chanState.ChanType,
&c.cfg.chanState.LocalChanCfg, &c.cfg.chanState.RemoteChanCfg,
)
auxResult, err := fn.MapOptionZ(
c.cfg.auxLeafStore,
//nolint:ll
func(s lnwallet.AuxLeafStore) fn.Result[lnwallet.CommitDiffAuxResult] {
return s.FetchLeavesFromCommit(
lnwallet.NewAuxChanState(c.cfg.chanState),
c.cfg.chanState.LocalCommitment, *commitKeyRing,
lntypes.Local,
)
},
).Unpack()
if err != nil {
return false, fmt.Errorf("unable to fetch aux leaves: %w", err)
}
// With the keys derived, we'll construct the remote script that'll be
// present if they have a non-dust balance on the commitment.
var leaseExpiry uint32
if c.cfg.chanState.ChanType.HasLeaseExpiration() {
leaseExpiry = c.cfg.chanState.ThawHeight
}
remoteAuxLeaf := fn.FlatMapOption(
func(l lnwallet.CommitAuxLeaves) input.AuxTapLeaf {
return l.RemoteAuxLeaf
},
)(auxResult.AuxLeaves)
remoteScript, _, err := lnwallet.CommitScriptToRemote(
c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator,
commitKeyRing.ToRemoteKey, leaseExpiry,
remoteAuxLeaf,
)
if err != nil {
return false, err
}
// Next, we'll derive our script that includes the revocation base for
// the remote party allowing them to claim this output before the CSV
// delay if we breach.
localAuxLeaf := fn.FlatMapOption(
func(l lnwallet.CommitAuxLeaves) input.AuxTapLeaf {
return l.LocalAuxLeaf
},
)(auxResult.AuxLeaves)
localScript, err := lnwallet.CommitScriptToSelf(
c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator,
commitKeyRing.ToLocalKey, commitKeyRing.RevocationKey,
uint32(c.cfg.chanState.LocalChanCfg.CsvDelay), leaseExpiry,
localAuxLeaf,
)
if err != nil {
return false, err
}
// With all our scripts assembled, we'll examine the outputs of the
// commitment transaction to determine if this is a local force close
// or not.
ourCommit := false
for _, output := range commitSpend.SpendingTx.TxOut {
pkScript := output.PkScript
switch {
case bytes.Equal(localScript.PkScript(), pkScript):
ourCommit = true
case bytes.Equal(remoteScript.PkScript(), pkScript):
ourCommit = true
}
}
// If the script is not present, this cannot be our commit.
if !ourCommit {
return false, nil
}
log.Warnf("Detected local unilateral close of unknown state %v "+
"(our state=%v)", broadcastStateNum,
chainSet.localCommit.CommitHeight)
// If this is our commitment transaction, then we try to act even
// though we won't be able to sweep HTLCs.
chainSet.commitSet.ConfCommitKey = fn.Some(LocalHtlcSet)
if err := c.dispatchLocalForceClose(
commitSpend, broadcastStateNum, chainSet.commitSet,
); err != nil {
return false, fmt.Errorf("unable to handle local"+
"close for chan_point=%v: %v",
c.cfg.chanState.FundingOutpoint, err)
}
return true, nil
}
// chainSet includes all the information we need to dispatch a channel close
// event to any subscribers.
type chainSet struct {
// remoteStateNum is the commitment number of the lowest valid
// commitment the remote party holds from our PoV. This value is used
// to determine if the remote party is playing a state that's behind,
// in line, or ahead of the latest state we know for it.
remoteStateNum uint64
// commitSet includes information pertaining to the set of active HTLCs
// on each commitment.
commitSet CommitSet
// remoteCommit is the current commitment of the remote party.
remoteCommit channeldb.ChannelCommitment
// localCommit is our current commitment.
localCommit channeldb.ChannelCommitment
// remotePendingCommit points to the dangling commitment of the remote
// party, if it exists. If there's no dangling commitment, then this
// pointer will be nil.
remotePendingCommit *channeldb.ChannelCommitment
}
// newChainSet creates a new chainSet given the current up to date channel
// state.
func newChainSet(chanState *channeldb.OpenChannel) (*chainSet, error) {
// First, we'll grab the current unrevoked commitments for ourselves
// and the remote party.
localCommit, remoteCommit, err := chanState.LatestCommitments()
if err != nil {
return nil, fmt.Errorf("unable to fetch channel state for "+
"chan_point=%v: %v", chanState.FundingOutpoint, err)
}
log.Tracef("ChannelPoint(%v): local_commit_type=%v, local_commit=%v",
chanState.FundingOutpoint, chanState.ChanType,
spew.Sdump(localCommit))
log.Tracef("ChannelPoint(%v): remote_commit_type=%v, remote_commit=%v",
chanState.FundingOutpoint, chanState.ChanType,
spew.Sdump(remoteCommit))
// Fetch the current known commit height for the remote party, and
// their pending commitment chain tip if it exists.
remoteStateNum := remoteCommit.CommitHeight
remoteChainTip, err := chanState.RemoteCommitChainTip()
if err != nil && err != channeldb.ErrNoPendingCommit {
return nil, fmt.Errorf("unable to obtain chain tip for "+
"ChannelPoint(%v): %v",
chanState.FundingOutpoint, err)
}
// Now that we have all the possible valid commitments, we'll make the
// CommitSet the ChannelArbitrator will need in order to carry out its
// duty.
commitSet := CommitSet{
HtlcSets: map[HtlcSetKey][]channeldb.HTLC{
LocalHtlcSet: localCommit.Htlcs,
RemoteHtlcSet: remoteCommit.Htlcs,
},
}
var remotePendingCommit *channeldb.ChannelCommitment
if remoteChainTip != nil {
remotePendingCommit = &remoteChainTip.Commitment
log.Tracef("ChannelPoint(%v): remote_pending_commit_type=%v, "+
"remote_pending_commit=%v", chanState.FundingOutpoint,
chanState.ChanType,
spew.Sdump(remoteChainTip.Commitment))
htlcs := remoteChainTip.Commitment.Htlcs
commitSet.HtlcSets[RemotePendingHtlcSet] = htlcs
}
// We'll now retrieve the latest state of the revocation store so we
// can populate the revocation information within the channel state
// object that we have.
//
// TODO(roasbeef): mutation is bad mkay
_, err = chanState.RemoteRevocationStore()
if err != nil {
return nil, fmt.Errorf("unable to fetch revocation state for "+
"chan_point=%v", chanState.FundingOutpoint)
}
return &chainSet{
remoteStateNum: remoteStateNum,
commitSet: commitSet,
localCommit: *localCommit,
remoteCommit: *remoteCommit,
remotePendingCommit: remotePendingCommit,
}, nil
}
// closeObserver is a dedicated goroutine that will watch for any closes of the
// channel that it's watching on chain. In the event of an on-chain event, the
// close observer will assembled the proper materials required to claim the
// funds of the channel on-chain (if required), then dispatch these as
// notifications to all subscribers.
func (c *chainWatcher) closeObserver() {
defer c.wg.Done()
defer c.fundingSpendNtfn.Cancel()
log.Infof("Close observer for ChannelPoint(%v) active",
c.cfg.chanState.FundingOutpoint)
for {
select {
// A new block is received, we will check whether this block
// contains a spending tx that we are interested in.
case beat := <-c.BlockbeatChan:
log.Debugf("ChainWatcher(%v) received blockbeat %v",
c.cfg.chanState.FundingOutpoint, beat.Height())
// Process the block.
c.handleBlockbeat(beat)
// If the funding outpoint is spent, we now go ahead and handle
// it. Note that we cannot rely solely on the `block` event
// above to trigger a close event, as deep down, the receiving
// of block notifications and the receiving of spending
// notifications are done in two different goroutines, so the
// expected order: [receive block -> receive spend] is not
// guaranteed .
case spend, ok := <-c.fundingSpendNtfn.Spend:
// If the channel was closed, then this means that the
// notifier exited, so we will as well.
if !ok {
return
}
err := c.handleCommitSpend(spend)
if err != nil {
log.Errorf("Failed to handle commit spend: %v",
err)
}
// The chainWatcher has been signalled to exit, so we'll do so
// now.
case <-c.quit:
return
}
}
}
// handleKnownLocalState checks whether the passed spend is a local state that
// is known to us (the current state). If so we will act on this state using
// the passed chainSet. If this is not a known local state, false is returned.
func (c *chainWatcher) handleKnownLocalState(
commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
chainSet *chainSet) (bool, error) {
// If the channel is recovered, we won't have a local commit to check
// against, so immediately return.
if c.cfg.chanState.HasChanStatus(channeldb.ChanStatusRestored) {
return false, nil
}
commitTxBroadcast := commitSpend.SpendingTx
commitHash := commitTxBroadcast.TxHash()
// Check whether our latest local state hit the chain.
if chainSet.localCommit.CommitTx.TxHash() != commitHash {
return false, nil
}
chainSet.commitSet.ConfCommitKey = fn.Some(LocalHtlcSet)
if err := c.dispatchLocalForceClose(
commitSpend, broadcastStateNum, chainSet.commitSet,
); err != nil {
return false, fmt.Errorf("unable to handle local"+
"close for chan_point=%v: %v",
c.cfg.chanState.FundingOutpoint, err)
}
return true, nil
}
// handleKnownRemoteState checks whether the passed spend is a remote state
// that is known to us (a revoked, current or pending state). If so we will act
// on this state using the passed chainSet. If this is not a known remote
// state, false is returned.
func (c *chainWatcher) handleKnownRemoteState(
commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
chainSet *chainSet) (bool, error) {
// If the channel is recovered, we won't have any remote commit to
// check against, so imemdiately return.
if c.cfg.chanState.HasChanStatus(channeldb.ChanStatusRestored) {
return false, nil
}
commitTxBroadcast := commitSpend.SpendingTx
commitHash := commitTxBroadcast.TxHash()
switch {
// If the spending transaction matches the current latest state, then
// they've initiated a unilateral close. So we'll trigger the
// unilateral close signal so subscribers can clean up the state as
// necessary.
case chainSet.remoteCommit.CommitTx.TxHash() == commitHash:
log.Infof("Remote party broadcast base set, "+
"commit_num=%v", chainSet.remoteStateNum)
chainSet.commitSet.ConfCommitKey = fn.Some(RemoteHtlcSet)
err := c.dispatchRemoteForceClose(
commitSpend, chainSet.remoteCommit,
chainSet.commitSet,
c.cfg.chanState.RemoteCurrentRevocation,
)
if err != nil {
return false, fmt.Errorf("unable to handle remote "+
"close for chan_point=%v: %v",
c.cfg.chanState.FundingOutpoint, err)
}
return true, nil
// We'll also handle the case of the remote party broadcasting
// their commitment transaction which is one height above ours.
// This case can arise when we initiate a state transition, but
// the remote party has a fail crash _after_ accepting the new
// state, but _before_ sending their signature to us.
case chainSet.remotePendingCommit != nil &&
chainSet.remotePendingCommit.CommitTx.TxHash() == commitHash:
log.Infof("Remote party broadcast pending set, "+
"commit_num=%v", chainSet.remoteStateNum+1)
chainSet.commitSet.ConfCommitKey = fn.Some(RemotePendingHtlcSet)
err := c.dispatchRemoteForceClose(
commitSpend, *chainSet.remotePendingCommit,
chainSet.commitSet,
c.cfg.chanState.RemoteNextRevocation,
)
if err != nil {
return false, fmt.Errorf("unable to handle remote "+
"close for chan_point=%v: %v",
c.cfg.chanState.FundingOutpoint, err)
}
return true, nil
}
// This is neither a remote force close or a "future" commitment, we
// now check whether it's a remote breach and properly handle it.
return c.handlePossibleBreach(commitSpend, broadcastStateNum, chainSet)
}
// handlePossibleBreach checks whether the remote has breached and dispatches a
// breach resolution to claim funds.
func (c *chainWatcher) handlePossibleBreach(commitSpend *chainntnfs.SpendDetail,
broadcastStateNum uint64, chainSet *chainSet) (bool, error) {
// We check if we have a revoked state at this state num that matches
// the spend transaction.
spendHeight := uint32(commitSpend.SpendingHeight)
retribution, err := lnwallet.NewBreachRetribution(
c.cfg.chanState, broadcastStateNum, spendHeight,
commitSpend.SpendingTx, c.cfg.auxLeafStore, c.cfg.auxResolver,
)
switch {
// If we had no log entry at this height, this was not a revoked state.
case err == channeldb.ErrLogEntryNotFound:
return false, nil
case err == channeldb.ErrNoPastDeltas:
return false, nil
case err != nil:
return false, fmt.Errorf("unable to create breach "+
"retribution: %v", err)
}
// We found a revoked state at this height, but it could still be our
// own broadcasted state we are looking at. Therefore check that the
// commit matches before assuming it was a breach.
commitHash := commitSpend.SpendingTx.TxHash()
if retribution.BreachTxHash != commitHash {
return false, nil
}
// Create an AnchorResolution for the breached state.
anchorRes, err := lnwallet.NewAnchorResolution(
c.cfg.chanState, commitSpend.SpendingTx, retribution.KeyRing,
lntypes.Remote,
)
if err != nil {
return false, fmt.Errorf("unable to create anchor "+
"resolution: %v", err)
}
// We'll set the ConfCommitKey here as the remote htlc set. This is
// only used to ensure a nil-pointer-dereference doesn't occur and is
// not used otherwise. The HTLC's may not exist for the
// RemotePendingHtlcSet.
chainSet.commitSet.ConfCommitKey = fn.Some(RemoteHtlcSet)
// THEY'RE ATTEMPTING TO VIOLATE THE CONTRACT LAID OUT WITHIN THE
// PAYMENT CHANNEL. Therefore we close the signal indicating a revoked
// broadcast to allow subscribers to swiftly dispatch justice!!!
err = c.dispatchContractBreach(
commitSpend, chainSet, broadcastStateNum, retribution,
anchorRes,
)
if err != nil {
return false, fmt.Errorf("unable to handle channel "+
"breach for chan_point=%v: %v",
c.cfg.chanState.FundingOutpoint, err)
}
return true, nil
}
// handleUnknownRemoteState is the last attempt we make at reclaiming funds
// from the closed channel, by checkin whether the passed spend _could_ be a
// remote spend that is unknown to us (we lost state). We will try to initiate
// Data Loss Protection in order to restore our commit point and reclaim our
// funds from the channel. If we are not able to act on it, false is returned.
func (c *chainWatcher) handleUnknownRemoteState(
commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
chainSet *chainSet) (bool, error) {
log.Warnf("Remote node broadcast state #%v, "+
"which is more than 1 beyond best known "+
"state #%v!!! Attempting recovery...",
broadcastStateNum, chainSet.remoteStateNum)
// If this isn't a tweakless commitment, then we'll need to wait for
// the remote party's latest unrevoked commitment point to be presented
// to us as we need this to sweep. Otherwise, we can dispatch the
// remote close and sweep immediately using a fake commitPoint as it
// isn't actually needed for recovery anymore.
commitPoint := c.cfg.chanState.RemoteCurrentRevocation
tweaklessCommit := c.cfg.chanState.ChanType.IsTweakless()
if !tweaklessCommit {
commitPoint = c.waitForCommitmentPoint()
if commitPoint == nil {
return false, fmt.Errorf("unable to get commit point")
}
log.Infof("Recovered commit point(%x) for "+
"channel(%v)! Now attempting to use it to "+
"sweep our funds...",
commitPoint.SerializeCompressed(),
c.cfg.chanState.FundingOutpoint)
} else {
log.Infof("ChannelPoint(%v) is tweakless, "+
"moving to sweep directly on chain",
c.cfg.chanState.FundingOutpoint)
}
// Since we don't have the commitment stored for this state, we'll just
// pass an empty commitment within the commitment set. Note that this
// means we won't be able to recover any HTLC funds.
//
// TODO(halseth): can we try to recover some HTLCs?
chainSet.commitSet.ConfCommitKey = fn.Some(RemoteHtlcSet)
err := c.dispatchRemoteForceClose(
commitSpend, channeldb.ChannelCommitment{},
chainSet.commitSet, commitPoint,
)
if err != nil {
return false, fmt.Errorf("unable to handle remote "+
"close for chan_point=%v: %v",
c.cfg.chanState.FundingOutpoint, err)
}
return true, nil
}
// toSelfAmount takes a transaction and returns the sum of all outputs that pay
// to a script that the wallet controls or the channel defines as its delivery
// script . If no outputs pay to us (determined by these criteria), then we
// return zero. This is possible as our output may have been trimmed due to
// being dust.
func (c *chainWatcher) toSelfAmount(tx *wire.MsgTx) btcutil.Amount {
// There are two main cases we have to handle here. First, in the coop
// close case we will always have saved the delivery address we used
// whether it was from the upfront shutdown, from the delivery address
// requested at close time, or even an automatically generated one. All
// coop-close cases can be identified in the following manner:
shutdown, _ := c.cfg.chanState.ShutdownInfo()
oDeliveryAddr := fn.MapOption(
func(i channeldb.ShutdownInfo) lnwire.DeliveryAddress {
return i.DeliveryScript.Val
})(shutdown)
// Here we define a function capable of identifying whether an output
// corresponds with our local delivery script from a ShutdownInfo if we
// have a ShutdownInfo for this chainWatcher's underlying channel.
//
// isDeliveryOutput :: *TxOut -> bool
isDeliveryOutput := func(o *wire.TxOut) bool {
return fn.ElimOption(
oDeliveryAddr,
// If we don't have a delivery addr, then the output
// can't match it.
func() bool { return false },
// Otherwise if the PkScript of the TxOut matches our
// delivery script then this is a delivery output.
func(a lnwire.DeliveryAddress) bool {
return slices.Equal(a, o.PkScript)
},
)
}
// Here we define a function capable of identifying whether an output
// belongs to the LND wallet. We use this as a heuristic in the case
// where we might be looking for spendable force closure outputs.
//
// isWalletOutput :: *TxOut -> bool
isWalletOutput := func(out *wire.TxOut) bool {
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
// Doesn't matter what net we actually pass in.
out.PkScript, &chaincfg.TestNet3Params,
)
if err != nil {
return false
}
return fn.Any(addrs, c.cfg.isOurAddr)
}
// Grab all of the outputs that correspond with our delivery address
// or our wallet is aware of.
outs := fn.Filter(tx.TxOut, fn.PredOr(isDeliveryOutput, isWalletOutput))
// Grab the values for those outputs.
vals := fn.Map(outs, func(o *wire.TxOut) int64 { return o.Value })
// Return the sum.
return btcutil.Amount(fn.Sum(vals))
}
// dispatchCooperativeClose processed a detect cooperative channel closure.
// We'll use the spending transaction to locate our output within the
// transaction, then clean up the database state. We'll also dispatch a
// notification to all subscribers that the channel has been closed in this
// manner.
func (c *chainWatcher) dispatchCooperativeClose(commitSpend *chainntnfs.SpendDetail) error {
broadcastTx := commitSpend.SpendingTx
log.Infof("Cooperative closure for ChannelPoint(%v): %v",
c.cfg.chanState.FundingOutpoint, spew.Sdump(broadcastTx))
// If the input *is* final, then we'll check to see which output is
// ours.
localAmt := c.toSelfAmount(broadcastTx)
// Once this is known, we'll mark the state as fully closed in the
// database. We can do this as a cooperatively closed channel has all
// its outputs resolved after only one confirmation.
closeSummary := &channeldb.ChannelCloseSummary{
ChanPoint: c.cfg.chanState.FundingOutpoint,
ChainHash: c.cfg.chanState.ChainHash,
ClosingTXID: *commitSpend.SpenderTxHash,
RemotePub: c.cfg.chanState.IdentityPub,
Capacity: c.cfg.chanState.Capacity,
CloseHeight: uint32(commitSpend.SpendingHeight),
SettledBalance: localAmt,
CloseType: channeldb.CooperativeClose,
ShortChanID: c.cfg.chanState.ShortChanID(),
IsPending: true,
RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
RemoteNextRevocation: c.cfg.chanState.RemoteNextRevocation,
LocalChanConfig: c.cfg.chanState.LocalChanCfg,
}
// Attempt to add a channel sync message to the close summary.
chanSync, err := c.cfg.chanState.ChanSyncMsg()
if err != nil {
log.Errorf("ChannelPoint(%v): unable to create channel sync "+
"message: %v", c.cfg.chanState.FundingOutpoint, err)
} else {
closeSummary.LastChanSyncMsg = chanSync
}
// Create a summary of all the information needed to handle the
// cooperative closure.
closeInfo := &CooperativeCloseInfo{
ChannelCloseSummary: closeSummary,
}
// With the event processed, we'll now notify all subscribers of the
// event.
c.Lock()
for _, sub := range c.clientSubscriptions {
select {
case sub.CooperativeClosure <- closeInfo:
case <-c.quit:
c.Unlock()
return fmt.Errorf("exiting")
}
}
c.Unlock()
return nil
}
// dispatchLocalForceClose processes a unilateral close by us being confirmed.
func (c *chainWatcher) dispatchLocalForceClose(
commitSpend *chainntnfs.SpendDetail,
stateNum uint64, commitSet CommitSet) error {
log.Infof("Local unilateral close of ChannelPoint(%v) "+
"detected", c.cfg.chanState.FundingOutpoint)
forceClose, err := lnwallet.NewLocalForceCloseSummary(
c.cfg.chanState, c.cfg.signer, commitSpend.SpendingTx, stateNum,
c.cfg.auxLeafStore, c.cfg.auxResolver,
)
if err != nil {
return err
}
// As we've detected that the channel has been closed, immediately
// creating a close summary for future usage by related sub-systems.
chanSnapshot := forceClose.ChanSnapshot
closeSummary := &channeldb.ChannelCloseSummary{
ChanPoint: chanSnapshot.ChannelPoint,
ChainHash: chanSnapshot.ChainHash,
ClosingTXID: forceClose.CloseTx.TxHash(),
RemotePub: &chanSnapshot.RemoteIdentity,
Capacity: chanSnapshot.Capacity,
CloseType: channeldb.LocalForceClose,
IsPending: true,
ShortChanID: c.cfg.chanState.ShortChanID(),
CloseHeight: uint32(commitSpend.SpendingHeight),
RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
RemoteNextRevocation: c.cfg.chanState.RemoteNextRevocation,
LocalChanConfig: c.cfg.chanState.LocalChanCfg,
}
resolutions, err := forceClose.ContractResolutions.UnwrapOrErr(
fmt.Errorf("resolutions not found"),
)
if err != nil {
return err
}
// If our commitment output isn't dust or we have active HTLC's on the
// commitment transaction, then we'll populate the balances on the
// close channel summary.
if resolutions.CommitResolution != nil {
localBalance := chanSnapshot.LocalBalance.ToSatoshis()
closeSummary.SettledBalance = localBalance
closeSummary.TimeLockedBalance = localBalance
}
if resolutions.HtlcResolutions != nil {
for _, htlc := range resolutions.HtlcResolutions.OutgoingHTLCs {
htlcValue := btcutil.Amount(
htlc.SweepSignDesc.Output.Value,
)
closeSummary.TimeLockedBalance += htlcValue
}
}
// Attempt to add a channel sync message to the close summary.
chanSync, err := c.cfg.chanState.ChanSyncMsg()
if err != nil {
log.Errorf("ChannelPoint(%v): unable to create channel sync "+
"message: %v", c.cfg.chanState.FundingOutpoint, err)
} else {
closeSummary.LastChanSyncMsg = chanSync
}
// With the event processed, we'll now notify all subscribers of the
// event.
closeInfo := &LocalUnilateralCloseInfo{
SpendDetail: commitSpend,
LocalForceCloseSummary: forceClose,
ChannelCloseSummary: closeSummary,
CommitSet: commitSet,
}
c.Lock()
for _, sub := range c.clientSubscriptions {
select {
case sub.LocalUnilateralClosure <- closeInfo:
case <-c.quit:
c.Unlock()
return fmt.Errorf("exiting")
}
}
c.Unlock()
return nil
}
// dispatchRemoteForceClose processes a detected unilateral channel closure by
// the remote party. This function will prepare a UnilateralCloseSummary which
// will then be sent to any subscribers allowing them to resolve all our funds
// in the channel on chain. Once this close summary is prepared, all registered
// subscribers will receive a notification of this event. The commitPoint
// argument should be set to the per_commitment_point corresponding to the
// spending commitment.
//
// NOTE: The remoteCommit argument should be set to the stored commitment for
// this particular state. If we don't have the commitment stored (should only
// happen in case we have lost state) it should be set to an empty struct, in
// which case we will attempt to sweep the non-HTLC output using the passed
// commitPoint.
func (c *chainWatcher) dispatchRemoteForceClose(
commitSpend *chainntnfs.SpendDetail,
remoteCommit channeldb.ChannelCommitment,
commitSet CommitSet, commitPoint *btcec.PublicKey) error {
log.Infof("Unilateral close of ChannelPoint(%v) "+
"detected", c.cfg.chanState.FundingOutpoint)
// First, we'll create a closure summary that contains all the
// materials required to let each subscriber sweep the funds in the
// channel on-chain.
uniClose, err := lnwallet.NewUnilateralCloseSummary(
c.cfg.chanState, c.cfg.signer, commitSpend, remoteCommit,
commitPoint, c.cfg.auxLeafStore, c.cfg.auxResolver,
)
if err != nil {
return err
}
// With the event processed, we'll now notify all subscribers of the
// event.
c.Lock()
for _, sub := range c.clientSubscriptions {
select {
case sub.RemoteUnilateralClosure <- &RemoteUnilateralCloseInfo{
UnilateralCloseSummary: uniClose,
CommitSet: commitSet,
}:
case <-c.quit:
c.Unlock()
return fmt.Errorf("exiting")
}
}
c.Unlock()
return nil
}
// dispatchContractBreach processes a detected contract breached by the remote
// party. This method is to be called once we detect that the remote party has
// broadcast a prior revoked commitment state. This method well prepare all the
// materials required to bring the cheater to justice, then notify all
// registered subscribers of this event.
func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail,
chainSet *chainSet, broadcastStateNum uint64,
retribution *lnwallet.BreachRetribution,
anchorRes *lnwallet.AnchorResolution) error {
log.Warnf("Remote peer has breached the channel contract for "+
"ChannelPoint(%v). Revoked state #%v was broadcast!!!",
c.cfg.chanState.FundingOutpoint, broadcastStateNum)
if err := c.cfg.chanState.MarkBorked(); err != nil {
return fmt.Errorf("unable to mark channel as borked: %w", err)
}
spendHeight := uint32(spendEvent.SpendingHeight)
log.Debugf("Punishment breach retribution created: %v",
lnutils.NewLogClosure(func() string {
retribution.KeyRing.LocalHtlcKey = nil
retribution.KeyRing.RemoteHtlcKey = nil
retribution.KeyRing.ToLocalKey = nil
retribution.KeyRing.ToRemoteKey = nil
retribution.KeyRing.RevocationKey = nil
return spew.Sdump(retribution)
}))
settledBalance := chainSet.remoteCommit.LocalBalance.ToSatoshis()
closeSummary := channeldb.ChannelCloseSummary{
ChanPoint: c.cfg.chanState.FundingOutpoint,
ChainHash: c.cfg.chanState.ChainHash,
ClosingTXID: *spendEvent.SpenderTxHash,
CloseHeight: spendHeight,
RemotePub: c.cfg.chanState.IdentityPub,
Capacity: c.cfg.chanState.Capacity,
SettledBalance: settledBalance,
CloseType: channeldb.BreachClose,
IsPending: true,
ShortChanID: c.cfg.chanState.ShortChanID(),
RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
RemoteNextRevocation: c.cfg.chanState.RemoteNextRevocation,
LocalChanConfig: c.cfg.chanState.LocalChanCfg,
}
// Attempt to add a channel sync message to the close summary.
chanSync, err := c.cfg.chanState.ChanSyncMsg()
if err != nil {
log.Errorf("ChannelPoint(%v): unable to create channel sync "+
"message: %v", c.cfg.chanState.FundingOutpoint, err)
} else {
closeSummary.LastChanSyncMsg = chanSync
}
// Hand the retribution info over to the BreachArbitrator. This function
// will wait for a response from the breach arbiter and then proceed to
// send a BreachCloseInfo to the channel arbitrator. The channel arb
// will then mark the channel as closed after resolutions and the
// commit set are logged in the arbitrator log.
if err := c.cfg.contractBreach(retribution); err != nil {
log.Errorf("unable to hand breached contract off to "+
"BreachArbitrator: %v", err)
return err
}
breachRes := &BreachResolution{
FundingOutPoint: c.cfg.chanState.FundingOutpoint,
}
breachInfo := &BreachCloseInfo{
CommitHash: spendEvent.SpendingTx.TxHash(),
BreachResolution: breachRes,
AnchorResolution: anchorRes,
CommitSet: chainSet.commitSet,
CloseSummary: closeSummary,
}
// With the event processed and channel closed, we'll now notify all
// subscribers of the event.
c.Lock()
for _, sub := range c.clientSubscriptions {
select {
case sub.ContractBreach <- breachInfo:
case <-c.quit:
c.Unlock()
return fmt.Errorf("quitting")
}
}
c.Unlock()
return nil
}
// waitForCommitmentPoint waits for the commitment point to be inserted into
// the local database. We'll use this method in the DLP case, to wait for the
// remote party to send us their point, as we can't proceed until we have that.
func (c *chainWatcher) waitForCommitmentPoint() *btcec.PublicKey {
// If we are lucky, the remote peer sent us the correct commitment
// point during channel sync, such that we can sweep our funds. If we
// cannot find the commit point, there's not much we can do other than
// wait for us to retrieve it. We will attempt to retrieve it from the
// peer each time we connect to it.
//
// TODO(halseth): actively initiate re-connection to the peer?
backoff := minCommitPointPollTimeout
for {
commitPoint, err := c.cfg.chanState.DataLossCommitPoint()
if err == nil {
return commitPoint
}
log.Errorf("Unable to retrieve commitment point for "+
"channel(%v) with lost state: %v. Retrying in %v.",
c.cfg.chanState.FundingOutpoint, err, backoff)
select {
// Wait before retrying, with an exponential backoff.
case <-time.After(backoff):
backoff = 2 * backoff
if backoff > maxCommitPointPollTimeout {
backoff = maxCommitPointPollTimeout
}
case <-c.quit:
return nil
}
}
}
// deriveFundingPkScript derives the script used in the funding output.
func deriveFundingPkScript(chanState *channeldb.OpenChannel) ([]byte, error) {
localKey := chanState.LocalChanCfg.MultiSigKey.PubKey
remoteKey := chanState.RemoteChanCfg.MultiSigKey.PubKey
var (
err error
fundingPkScript []byte
)
if chanState.ChanType.IsTaproot() {
fundingPkScript, _, err = input.GenTaprootFundingScript(
localKey, remoteKey, 0, chanState.TapscriptRoot,
)
if err != nil {
return nil, err
}
} else {
multiSigScript, err := input.GenMultiSigScript(
localKey.SerializeCompressed(),
remoteKey.SerializeCompressed(),
)
if err != nil {
return nil, err
}
fundingPkScript, err = input.WitnessScriptHash(multiSigScript)
if err != nil {
return nil, err
}
}
return fundingPkScript, nil
}
// handleCommitSpend takes a spending tx of the funding output and handles the
// channel close based on the closure type.
func (c *chainWatcher) handleCommitSpend(
commitSpend *chainntnfs.SpendDetail) error {
commitTxBroadcast := commitSpend.SpendingTx
// First, we'll construct the chainset which includes all the data we
// need to dispatch an event to our subscribers about this possible
// channel close event.
chainSet, err := newChainSet(c.cfg.chanState)
if err != nil {
return fmt.Errorf("create commit set: %w", err)
}
// Decode the state hint encoded within the commitment transaction to
// determine if this is a revoked state or not.
obfuscator := c.stateHintObfuscator
broadcastStateNum := c.cfg.extractStateNumHint(
commitTxBroadcast, obfuscator,
)
// We'll go on to check whether it could be our own commitment that was
// published and know is confirmed.
ok, err := c.handleKnownLocalState(
commitSpend, broadcastStateNum, chainSet,
)
if err != nil {
return fmt.Errorf("handle known local state: %w", err)
}
if ok {
return nil
}
// Now that we know it is neither a non-cooperative closure nor a local
// close with the latest state, we check if it is the remote that
// closed with any prior or current state.
ok, err = c.handleKnownRemoteState(
commitSpend, broadcastStateNum, chainSet,
)
if err != nil {
return fmt.Errorf("handle known remote state: %w", err)
}
if ok {
return nil
}
// Next, we'll check to see if this is a cooperative channel closure or
// not. This is characterized by having an input sequence number that's
// finalized. This won't happen with regular commitment transactions
// due to the state hint encoding scheme.
switch commitTxBroadcast.TxIn[0].Sequence {
case wire.MaxTxInSequenceNum:
fallthrough
case mempool.MaxRBFSequence:
// TODO(roasbeef): rare but possible, need itest case for
err := c.dispatchCooperativeClose(commitSpend)
if err != nil {
return fmt.Errorf("handle coop close: %w", err)
}
return nil
}
log.Warnf("Unknown commitment broadcast for ChannelPoint(%v) ",
c.cfg.chanState.FundingOutpoint)
// We'll try to recover as best as possible from losing state. We
// first check if this was a local unknown state. This could happen if
// we force close, then lose state or attempt recovery before the
// commitment confirms.
ok, err = c.handleUnknownLocalState(
commitSpend, broadcastStateNum, chainSet,
)
if err != nil {
return fmt.Errorf("handle known local state: %w", err)
}
if ok {
return nil
}
// Since it was neither a known remote state, nor a local state that
// was published, it most likely mean we lost state and the remote node
// closed. In this case we must start the DLP protocol in hope of
// getting our money back.
ok, err = c.handleUnknownRemoteState(
commitSpend, broadcastStateNum, chainSet,
)
if err != nil {
return fmt.Errorf("handle unknown remote state: %w", err)
}
if ok {
return nil
}
log.Errorf("Unable to handle spending tx %v of channel point %v",
commitTxBroadcast.TxHash(), c.cfg.chanState.FundingOutpoint)
return nil
}
// checkFundingSpend performs a non-blocking read on the spendNtfn channel to
// check whether there's a commit spend already. Returns the spend details if
// found.
func (c *chainWatcher) checkFundingSpend() *chainntnfs.SpendDetail {
select {
// We've detected a spend of the channel onchain! Depending on the type
// of spend, we'll act accordingly, so we'll examine the spending
// transaction to determine what we should do.
//
// TODO(Roasbeef): need to be able to ensure this only triggers
// on confirmation, to ensure if multiple txns are broadcast, we
// act on the one that's timestamped
case spend, ok := <-c.fundingSpendNtfn.Spend:
// If the channel was closed, then this means that the notifier
// exited, so we will as well.
if !ok {
return nil
}
log.Debugf("Found spend details for funding output: %v",
spend.SpenderTxHash)
return spend
default:
}
return nil
}
// chanPointConfirmed checks whether the given channel point has confirmed.
// This is used to ensure that the funding output has confirmed on chain before
// we proceed with the rest of the close observer logic for taproot channels.
// Check the docs in `fundingConfirmedNtfn` for details.
func (c *chainWatcher) chanPointConfirmed() bool {
op := c.cfg.chanState.FundingOutpoint
select {
case _, ok := <-c.fundingConfirmedNtfn.Confirmed:
// If the channel was closed, then this means that the notifier
// exited, so we will as well.
if !ok {
return false
}
log.Debugf("Taproot ChannelPoint(%v) confirmed", op)
// The channel point has confirmed on chain. We now cancel the
// subscription.
c.fundingConfirmedNtfn.Cancel()
return true
default:
log.Infof("Taproot ChannelPoint(%v) not confirmed yet", op)
return false
}
}
// handleBlockbeat takes a blockbeat and queries for a spending tx for the
// funding output. If the spending tx is found, it will be handled based on the
// closure type.
func (c *chainWatcher) handleBlockbeat(beat chainio.Blockbeat) {
// Notify the chain watcher has processed the block.
defer c.NotifyBlockProcessed(beat, nil)
// If we have a fundingConfirmedNtfn, it means this is a taproot
// channel that is pending, before we proceed, we want to ensure that
// the expected funding output has confirmed on chain. Check the docs
// in `fundingConfirmedNtfn` for details.
if c.fundingConfirmedNtfn != nil {
// If the funding output hasn't confirmed in this block, we
// will check it again in the next block.
if !c.chanPointConfirmed() {
return
}
}
// Perform a non-blocking read to check whether the funding output was
// spent.
spend := c.checkFundingSpend()
if spend == nil {
log.Tracef("No spend found for ChannelPoint(%v) in block %v",
c.cfg.chanState.FundingOutpoint, beat.Height())
return
}
// The funding output was spent, we now handle it by sending a close
// event to the channel arbitrator.
err := c.handleCommitSpend(spend)
if err != nil {
log.Errorf("Failed to handle commit spend: %v", err)
}
}
package contractcourt
import (
"bytes"
"context"
"errors"
"fmt"
"math"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainio"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/sweep"
)
var (
// errAlreadyForceClosed is an error returned when we attempt to force
// close a channel that's already in the process of doing so.
errAlreadyForceClosed = errors.New("channel is already in the " +
"process of being force closed")
)
const (
// arbitratorBlockBufferSize is the size of the buffer we give to each
// channel arbitrator.
arbitratorBlockBufferSize = 20
// AnchorOutputValue is the output value for the anchor output of an
// anchor channel.
// See BOLT 03 for more details:
// https://github.com/lightning/bolts/blob/master/03-transactions.md
AnchorOutputValue = btcutil.Amount(330)
)
// WitnessSubscription represents an intent to be notified once new witnesses
// are discovered by various active contract resolvers. A contract resolver may
// use this to be notified of when it can satisfy an incoming contract after we
// discover the witness for an outgoing contract.
type WitnessSubscription struct {
// WitnessUpdates is a channel that newly discovered witnesses will be
// sent over.
//
// TODO(roasbeef): couple with WitnessType?
WitnessUpdates <-chan lntypes.Preimage
// CancelSubscription is a function closure that should be used by a
// client to cancel the subscription once they are no longer interested
// in receiving new updates.
CancelSubscription func()
}
// WitnessBeacon is a global beacon of witnesses. Contract resolvers will use
// this interface to lookup witnesses (preimages typically) of contracts
// they're trying to resolve, add new preimages they resolve, and finally
// receive new updates each new time a preimage is discovered.
//
// TODO(roasbeef): need to delete the pre-images once we've used them
// and have been sufficiently confirmed?
type WitnessBeacon interface {
// SubscribeUpdates returns a channel that will be sent upon *each* time
// a new preimage is discovered.
SubscribeUpdates(chanID lnwire.ShortChannelID, htlc *channeldb.HTLC,
payload *hop.Payload,
nextHopOnionBlob []byte) (*WitnessSubscription, error)
// LookupPreimage attempts to lookup a preimage in the global cache.
// True is returned for the second argument if the preimage is found.
LookupPreimage(payhash lntypes.Hash) (lntypes.Preimage, bool)
// AddPreimages adds a batch of newly discovered preimages to the global
// cache, and also signals any subscribers of the newly discovered
// witness.
AddPreimages(preimages ...lntypes.Preimage) error
}
// ArbChannel is an abstraction that allows the channel arbitrator to interact
// with an open channel.
type ArbChannel interface {
// ForceCloseChan should force close the contract that this attendant
// is watching over. We'll use this when we decide that we need to go
// to chain. It should in addition tell the switch to remove the
// corresponding link, such that we won't accept any new updates. The
// returned summary contains all items needed to eventually resolve all
// outputs on chain.
ForceCloseChan() (*wire.MsgTx, error)
// NewAnchorResolutions returns the anchor resolutions for currently
// valid commitment transactions.
NewAnchorResolutions() (*lnwallet.AnchorResolutions, error)
}
// ChannelArbitratorConfig contains all the functionality that the
// ChannelArbitrator needs in order to properly arbitrate any contract dispute
// on chain.
type ChannelArbitratorConfig struct {
// ChanPoint is the channel point that uniquely identifies this
// channel.
ChanPoint wire.OutPoint
// Channel is the full channel data structure. For legacy channels, this
// field may not always be set after a restart.
Channel ArbChannel
// ShortChanID describes the exact location of the channel within the
// chain. We'll use this to address any messages that we need to send
// to the switch during contract resolution.
ShortChanID lnwire.ShortChannelID
// ChainEvents is an active subscription to the chain watcher for this
// channel to be notified of any on-chain activity related to this
// channel.
ChainEvents *ChainEventSubscription
// MarkCommitmentBroadcasted should mark the channel as the commitment
// being broadcast, and we are waiting for the commitment to confirm.
MarkCommitmentBroadcasted func(*wire.MsgTx, lntypes.ChannelParty) error
// MarkChannelClosed marks the channel closed in the database, with the
// passed close summary. After this method successfully returns we can
// no longer expect to receive chain events for this channel, and must
// be able to recover from a failure without getting the close event
// again. It takes an optional channel status which will update the
// channel status in the record that we keep of historical channels.
MarkChannelClosed func(*channeldb.ChannelCloseSummary,
...channeldb.ChannelStatus) error
// IsPendingClose is a boolean indicating whether the channel is marked
// as pending close in the database.
IsPendingClose bool
// ClosingHeight is the height at which the channel was closed. Note
// that this value is only valid if IsPendingClose is true.
ClosingHeight uint32
// CloseType is the type of the close event in case IsPendingClose is
// true. Otherwise this value is unset.
CloseType channeldb.ClosureType
// MarkChannelResolved is a function closure that serves to mark a
// channel as "fully resolved". A channel itself can be considered
// fully resolved once all active contracts have individually been
// fully resolved.
//
// TODO(roasbeef): need RPC's to combine for pendingchannels RPC
MarkChannelResolved func() error
// PutResolverReport records a resolver report for the channel. If the
// transaction provided is nil, the function should write the report
// in a new transaction.
PutResolverReport func(tx kvdb.RwTx,
report *channeldb.ResolverReport) error
// FetchHistoricalChannel retrieves the historical state of a channel.
// This is mostly used to supplement the ContractResolvers with
// additional information required for proper contract resolution.
FetchHistoricalChannel func() (*channeldb.OpenChannel, error)
// FindOutgoingHTLCDeadline returns the deadline in absolute block
// height for the specified outgoing HTLC. For an outgoing HTLC, its
// deadline is defined by the timeout height of its corresponding
// incoming HTLC - this is the expiry height the that remote peer can
// spend his/her outgoing HTLC via the timeout path.
FindOutgoingHTLCDeadline func(htlc channeldb.HTLC) fn.Option[int32]
ChainArbitratorConfig
}
// ReportOutputType describes the type of output that is being reported
// on.
type ReportOutputType uint8
const (
// ReportOutputIncomingHtlc is an incoming hash time locked contract on
// the commitment tx.
ReportOutputIncomingHtlc ReportOutputType = iota
// ReportOutputOutgoingHtlc is an outgoing hash time locked contract on
// the commitment tx.
ReportOutputOutgoingHtlc
// ReportOutputUnencumbered is an uncontested output on the commitment
// transaction paying to us directly.
ReportOutputUnencumbered
// ReportOutputAnchor is an anchor output on the commitment tx.
ReportOutputAnchor
)
// ContractReport provides a summary of a commitment tx output.
type ContractReport struct {
// Outpoint is the final output that will be swept back to the wallet.
Outpoint wire.OutPoint
// Type indicates the type of the reported output.
Type ReportOutputType
// Amount is the final value that will be swept in back to the wallet.
Amount btcutil.Amount
// MaturityHeight is the absolute block height that this output will
// mature at.
MaturityHeight uint32
// Stage indicates whether the htlc is in the CLTV-timeout stage (1) or
// the CSV-delay stage (2). A stage 1 htlc's maturity height will be set
// to its expiry height, while a stage 2 htlc's maturity height will be
// set to its confirmation height plus the maturity requirement.
Stage uint32
// LimboBalance is the total number of frozen coins within this
// contract.
LimboBalance btcutil.Amount
// RecoveredBalance is the total value that has been successfully swept
// back to the user's wallet.
RecoveredBalance btcutil.Amount
}
// resolverReport creates a resolve report using some of the information in the
// contract report.
func (c *ContractReport) resolverReport(spendTx *chainhash.Hash,
resolverType channeldb.ResolverType,
outcome channeldb.ResolverOutcome) *channeldb.ResolverReport {
return &channeldb.ResolverReport{
OutPoint: c.Outpoint,
Amount: c.Amount,
ResolverType: resolverType,
ResolverOutcome: outcome,
SpendTxID: spendTx,
}
}
// htlcSet represents the set of active HTLCs on a given commitment
// transaction.
type htlcSet struct {
// incomingHTLCs is a map of all incoming HTLCs on the target
// commitment transaction. We may potentially go onchain to claim the
// funds sent to us within this set.
incomingHTLCs map[uint64]channeldb.HTLC
// outgoingHTLCs is a map of all outgoing HTLCs on the target
// commitment transaction. We may potentially go onchain to reclaim the
// funds that are currently in limbo.
outgoingHTLCs map[uint64]channeldb.HTLC
}
// newHtlcSet constructs a new HTLC set from a slice of HTLC's.
func newHtlcSet(htlcs []channeldb.HTLC) htlcSet {
outHTLCs := make(map[uint64]channeldb.HTLC)
inHTLCs := make(map[uint64]channeldb.HTLC)
for _, htlc := range htlcs {
if htlc.Incoming {
inHTLCs[htlc.HtlcIndex] = htlc
continue
}
outHTLCs[htlc.HtlcIndex] = htlc
}
return htlcSet{
incomingHTLCs: inHTLCs,
outgoingHTLCs: outHTLCs,
}
}
// HtlcSetKey is a two-tuple that uniquely identifies a set of HTLCs on a
// commitment transaction.
type HtlcSetKey struct {
// IsRemote denotes if the HTLCs are on the remote commitment
// transaction.
IsRemote bool
// IsPending denotes if the commitment transaction that HTLCS are on
// are pending (the higher of two unrevoked commitments).
IsPending bool
}
var (
// LocalHtlcSet is the HtlcSetKey used for local commitments.
LocalHtlcSet = HtlcSetKey{IsRemote: false, IsPending: false}
// RemoteHtlcSet is the HtlcSetKey used for remote commitments.
RemoteHtlcSet = HtlcSetKey{IsRemote: true, IsPending: false}
// RemotePendingHtlcSet is the HtlcSetKey used for dangling remote
// commitment transactions.
RemotePendingHtlcSet = HtlcSetKey{IsRemote: true, IsPending: true}
)
// String returns a human readable string describing the target HtlcSetKey.
func (h HtlcSetKey) String() string {
switch h {
case LocalHtlcSet:
return "LocalHtlcSet"
case RemoteHtlcSet:
return "RemoteHtlcSet"
case RemotePendingHtlcSet:
return "RemotePendingHtlcSet"
default:
return "unknown HtlcSetKey"
}
}
// ChannelArbitrator is the on-chain arbitrator for a particular channel. The
// struct will keep in sync with the current set of HTLCs on the commitment
// transaction. The job of the attendant is to go on-chain to either settle or
// cancel an HTLC as necessary iff: an HTLC times out, or we known the
// pre-image to an HTLC, but it wasn't settled by the link off-chain. The
// ChannelArbitrator will factor in an expected confirmation delta when
// broadcasting to ensure that we avoid any possibility of race conditions, and
// sweep the output(s) without contest.
type ChannelArbitrator struct {
started int32 // To be used atomically.
stopped int32 // To be used atomically.
// Embed the blockbeat consumer struct to get access to the method
// `NotifyBlockProcessed` and the `BlockbeatChan`.
chainio.BeatConsumer
// startTimestamp is the time when this ChannelArbitrator was started.
startTimestamp time.Time
// log is a persistent log that the attendant will use to checkpoint
// its next action, and the state of any unresolved contracts.
log ArbitratorLog
// activeHTLCs is the set of active incoming/outgoing HTLC's on all
// currently valid commitment transactions.
activeHTLCs map[HtlcSetKey]htlcSet
// unmergedSet is used to update the activeHTLCs map in two callsites:
// checkLocalChainActions and sweepAnchors. It contains the latest
// updates from the link. It is not deleted from, its entries may be
// replaced on subsequent calls to notifyContractUpdate.
unmergedSet map[HtlcSetKey]htlcSet
unmergedMtx sync.RWMutex
// cfg contains all the functionality that the ChannelArbitrator requires
// to do its duty.
cfg ChannelArbitratorConfig
// signalUpdates is a channel that any new live signals for the channel
// we're watching over will be sent.
signalUpdates chan *signalUpdateMsg
// activeResolvers is a slice of any active resolvers. This is used to
// be able to signal them for shutdown in the case that we shutdown.
activeResolvers []ContractResolver
// activeResolversLock prevents simultaneous read and write to the
// resolvers slice.
activeResolversLock sync.RWMutex
// resolutionSignal is a channel that will be sent upon by contract
// resolvers once their contract has been fully resolved. With each
// send, we'll check to see if the contract is fully resolved.
resolutionSignal chan struct{}
// forceCloseReqs is a channel that requests to forcibly close the
// contract will be sent over.
forceCloseReqs chan *forceCloseReq
// state is the current state of the arbitrator. This state is examined
// upon start up to decide which actions to take.
state ArbitratorState
wg sync.WaitGroup
quit chan struct{}
}
// NewChannelArbitrator returns a new instance of a ChannelArbitrator backed by
// the passed config struct.
func NewChannelArbitrator(cfg ChannelArbitratorConfig,
htlcSets map[HtlcSetKey]htlcSet, log ArbitratorLog) *ChannelArbitrator {
// Create a new map for unmerged HTLC's as we will overwrite the values
// and want to avoid modifying activeHTLCs directly. This soft copying
// is done to ensure that activeHTLCs isn't reset as an empty map later
// on.
unmerged := make(map[HtlcSetKey]htlcSet)
unmerged[LocalHtlcSet] = htlcSets[LocalHtlcSet]
unmerged[RemoteHtlcSet] = htlcSets[RemoteHtlcSet]
// If the pending set exists, write that as well.
if _, ok := htlcSets[RemotePendingHtlcSet]; ok {
unmerged[RemotePendingHtlcSet] = htlcSets[RemotePendingHtlcSet]
}
c := &ChannelArbitrator{
log: log,
signalUpdates: make(chan *signalUpdateMsg),
resolutionSignal: make(chan struct{}),
forceCloseReqs: make(chan *forceCloseReq),
activeHTLCs: htlcSets,
unmergedSet: unmerged,
cfg: cfg,
quit: make(chan struct{}),
}
// Mount the block consumer.
c.BeatConsumer = chainio.NewBeatConsumer(c.quit, c.Name())
return c
}
// Compile-time check for the chainio.Consumer interface.
var _ chainio.Consumer = (*ChannelArbitrator)(nil)
// chanArbStartState contains the information from disk that we need to start
// up a channel arbitrator.
type chanArbStartState struct {
currentState ArbitratorState
commitSet *CommitSet
}
// getStartState retrieves the information from disk that our channel arbitrator
// requires to start.
func (c *ChannelArbitrator) getStartState(tx kvdb.RTx) (*chanArbStartState,
error) {
// First, we'll read our last state from disk, so our internal state
// machine can act accordingly.
state, err := c.log.CurrentState(tx)
if err != nil {
return nil, err
}
// Next we'll fetch our confirmed commitment set. This will only exist
// if the channel has been closed out on chain for modern nodes. For
// older nodes, this won't be found at all, and will rely on the
// existing written chain actions. Additionally, if this channel hasn't
// logged any actions in the log, then this field won't be present.
commitSet, err := c.log.FetchConfirmedCommitSet(tx)
if err != nil && err != errNoCommitSet && err != errScopeBucketNoExist {
return nil, err
}
return &chanArbStartState{
currentState: state,
commitSet: commitSet,
}, nil
}
// Start starts all the goroutines that the ChannelArbitrator needs to operate.
// If takes a start state, which will be looked up on disk if it is not
// provided.
func (c *ChannelArbitrator) Start(state *chanArbStartState,
beat chainio.Blockbeat) error {
if !atomic.CompareAndSwapInt32(&c.started, 0, 1) {
return nil
}
c.startTimestamp = c.cfg.Clock.Now()
// If the state passed in is nil, we look it up now.
if state == nil {
var err error
state, err = c.getStartState(nil)
if err != nil {
return err
}
}
log.Tracef("Starting ChannelArbitrator(%v), htlc_set=%v, state=%v",
c.cfg.ChanPoint, lnutils.SpewLogClosure(c.activeHTLCs),
state.currentState)
// Set our state from our starting state.
c.state = state.currentState
// Get the starting height.
bestHeight := beat.Height()
c.wg.Add(1)
go c.channelAttendant(bestHeight, state.commitSet)
return nil
}
// progressStateMachineAfterRestart attempts to progress the state machine
// after a restart. This makes sure that if the state transition failed, we
// will try to progress the state machine again. Moreover it will relaunch
// resolvers if the channel is still in the pending close state and has not
// been fully resolved yet.
func (c *ChannelArbitrator) progressStateMachineAfterRestart(bestHeight int32,
commitSet *CommitSet) error {
// If the channel has been marked pending close in the database, and we
// haven't transitioned the state machine to StateContractClosed (or a
// succeeding state), then a state transition most likely failed. We'll
// try to recover from this by manually advancing the state by setting
// the corresponding close trigger.
trigger := chainTrigger
triggerHeight := uint32(bestHeight)
if c.cfg.IsPendingClose {
switch c.state {
case StateDefault:
fallthrough
case StateBroadcastCommit:
fallthrough
case StateCommitmentBroadcasted:
switch c.cfg.CloseType {
case channeldb.CooperativeClose:
trigger = coopCloseTrigger
case channeldb.BreachClose:
trigger = breachCloseTrigger
case channeldb.LocalForceClose:
trigger = localCloseTrigger
case channeldb.RemoteForceClose:
trigger = remoteCloseTrigger
}
log.Warnf("ChannelArbitrator(%v): detected stalled "+
"state=%v for closed channel",
c.cfg.ChanPoint, c.state)
}
triggerHeight = c.cfg.ClosingHeight
}
log.Infof("ChannelArbitrator(%v): starting state=%v, trigger=%v, "+
"triggerHeight=%v", c.cfg.ChanPoint, c.state, trigger,
triggerHeight)
// We'll now attempt to advance our state forward based on the current
// on-chain state, and our set of active contracts.
startingState := c.state
nextState, _, err := c.advanceState(
triggerHeight, trigger, commitSet,
)
if err != nil {
switch err {
// If we detect that we tried to fetch resolutions, but failed,
// this channel was marked closed in the database before
// resolutions successfully written. In this case there is not
// much we can do, so we don't return the error.
case errScopeBucketNoExist:
fallthrough
case errNoResolutions:
log.Warnf("ChannelArbitrator(%v): detected closed"+
"channel with no contract resolutions written.",
c.cfg.ChanPoint)
default:
return err
}
}
// If we start and ended at the awaiting full resolution state, then
// we'll relaunch our set of unresolved contracts.
if startingState == StateWaitingFullResolution &&
nextState == StateWaitingFullResolution {
// In order to relaunch the resolvers, we'll need to fetch the
// set of HTLCs that were present in the commitment transaction
// at the time it was confirmed. commitSet.ConfCommitKey can't
// be nil at this point since we're in
// StateWaitingFullResolution. We can only be in
// StateWaitingFullResolution after we've transitioned from
// StateContractClosed which can only be triggered by the local
// or remote close trigger. This trigger is only fired when we
// receive a chain event from the chain watcher that the
// commitment has been confirmed on chain, and before we
// advance our state step, we call InsertConfirmedCommitSet.
err := c.relaunchResolvers(commitSet, triggerHeight)
if err != nil {
return err
}
}
return nil
}
// maybeAugmentTaprootResolvers will update the contract resolution information
// for taproot channels. This ensures that all the resolvers have the latest
// resolution, which may also include data such as the control block and tap
// tweaks.
func maybeAugmentTaprootResolvers(chanType channeldb.ChannelType,
resolver ContractResolver,
contractResolutions *ContractResolutions) {
if !chanType.IsTaproot() {
return
}
// The on disk resolutions contains all the ctrl block
// information, so we'll set that now for the relevant
// resolvers.
switch r := resolver.(type) {
case *commitSweepResolver:
if contractResolutions.CommitResolution != nil {
//nolint:ll
r.commitResolution = *contractResolutions.CommitResolution
}
case *htlcOutgoingContestResolver:
//nolint:ll
htlcResolutions := contractResolutions.HtlcResolutions.OutgoingHTLCs
for _, htlcRes := range htlcResolutions {
htlcRes := htlcRes
if r.htlcResolution.ClaimOutpoint ==
htlcRes.ClaimOutpoint {
r.htlcResolution = htlcRes
}
}
case *htlcTimeoutResolver:
//nolint:ll
htlcResolutions := contractResolutions.HtlcResolutions.OutgoingHTLCs
for _, htlcRes := range htlcResolutions {
htlcRes := htlcRes
if r.htlcResolution.ClaimOutpoint ==
htlcRes.ClaimOutpoint {
r.htlcResolution = htlcRes
}
}
case *htlcIncomingContestResolver:
//nolint:ll
htlcResolutions := contractResolutions.HtlcResolutions.IncomingHTLCs
for _, htlcRes := range htlcResolutions {
htlcRes := htlcRes
if r.htlcResolution.ClaimOutpoint ==
htlcRes.ClaimOutpoint {
r.htlcResolution = htlcRes
}
}
case *htlcSuccessResolver:
//nolint:ll
htlcResolutions := contractResolutions.HtlcResolutions.IncomingHTLCs
for _, htlcRes := range htlcResolutions {
htlcRes := htlcRes
if r.htlcResolution.ClaimOutpoint ==
htlcRes.ClaimOutpoint {
r.htlcResolution = htlcRes
}
}
}
}
// relauchResolvers relaunches the set of resolvers for unresolved contracts in
// order to provide them with information that's not immediately available upon
// starting the ChannelArbitrator. This information should ideally be stored in
// the database, so this only serves as a intermediate work-around to prevent a
// migration.
func (c *ChannelArbitrator) relaunchResolvers(commitSet *CommitSet,
heightHint uint32) error {
// We'll now query our log to see if there are any active unresolved
// contracts. If this is the case, then we'll relaunch all contract
// resolvers.
unresolvedContracts, err := c.log.FetchUnresolvedContracts()
if err != nil {
return err
}
// Retrieve the commitment tx hash from the log.
contractResolutions, err := c.log.FetchContractResolutions()
if err != nil {
log.Errorf("unable to fetch contract resolutions: %v",
err)
return err
}
commitHash := contractResolutions.CommitHash
// In prior versions of lnd, the information needed to supplement the
// resolvers (in most cases, the full amount of the HTLC) was found in
// the chain action map, which is now deprecated. As a result, if the
// commitSet is nil (an older node with unresolved HTLCs at time of
// upgrade), then we'll use the chain action information in place. The
// chain actions may exclude some information, but we cannot recover it
// for these older nodes at the moment.
var confirmedHTLCs []channeldb.HTLC
if commitSet != nil && commitSet.ConfCommitKey.IsSome() {
confCommitKey, err := commitSet.ConfCommitKey.UnwrapOrErr(
fmt.Errorf("no commitKey available"),
)
if err != nil {
return err
}
confirmedHTLCs = commitSet.HtlcSets[confCommitKey]
} else {
chainActions, err := c.log.FetchChainActions()
if err != nil {
log.Errorf("unable to fetch chain actions: %v", err)
return err
}
for _, htlcs := range chainActions {
confirmedHTLCs = append(confirmedHTLCs, htlcs...)
}
}
// Reconstruct the htlc outpoints and data from the chain action log.
// The purpose of the constructed htlc map is to supplement to
// resolvers restored from database with extra data. Ideally this data
// is stored as part of the resolver in the log. This is a workaround
// to prevent a db migration. We use all available htlc sets here in
// order to ensure we have complete coverage.
htlcMap := make(map[wire.OutPoint]*channeldb.HTLC)
for _, htlc := range confirmedHTLCs {
htlc := htlc
outpoint := wire.OutPoint{
Hash: commitHash,
Index: uint32(htlc.OutputIndex),
}
htlcMap[outpoint] = &htlc
}
// We'll also fetch the historical state of this channel, as it should
// have been marked as closed by now, and supplement it to each resolver
// such that we can properly resolve our pending contracts.
var chanState *channeldb.OpenChannel
chanState, err = c.cfg.FetchHistoricalChannel()
switch {
// If we don't find this channel, then it may be the case that it
// was closed before we started to retain the final state
// information for open channels.
case err == channeldb.ErrNoHistoricalBucket:
fallthrough
case err == channeldb.ErrChannelNotFound:
log.Warnf("ChannelArbitrator(%v): unable to fetch historical "+
"state", c.cfg.ChanPoint)
case err != nil:
return err
}
log.Infof("ChannelArbitrator(%v): relaunching %v contract "+
"resolvers", c.cfg.ChanPoint, len(unresolvedContracts))
for i := range unresolvedContracts {
resolver := unresolvedContracts[i]
if chanState != nil {
resolver.SupplementState(chanState)
// For taproot channels, we'll need to also make sure
// the control block information was set properly.
maybeAugmentTaprootResolvers(
chanState.ChanType, resolver,
contractResolutions,
)
}
unresolvedContracts[i] = resolver
htlcResolver, ok := resolver.(htlcContractResolver)
if !ok {
continue
}
htlcPoint := htlcResolver.HtlcPoint()
htlc, ok := htlcMap[htlcPoint]
if !ok {
return fmt.Errorf(
"htlc resolver %T unavailable", resolver,
)
}
htlcResolver.Supplement(*htlc)
// If this is an outgoing HTLC, we will also need to supplement
// the resolver with the expiry block height of its
// corresponding incoming HTLC.
if !htlc.Incoming {
deadline := c.cfg.FindOutgoingHTLCDeadline(*htlc)
htlcResolver.SupplementDeadline(deadline)
}
}
// The anchor resolver is stateless and can always be re-instantiated.
if contractResolutions.AnchorResolution != nil {
anchorResolver := newAnchorResolver(
contractResolutions.AnchorResolution.AnchorSignDescriptor,
contractResolutions.AnchorResolution.CommitAnchor,
heightHint, c.cfg.ChanPoint,
ResolverConfig{
ChannelArbitratorConfig: c.cfg,
},
)
anchorResolver.SupplementState(chanState)
unresolvedContracts = append(unresolvedContracts, anchorResolver)
// TODO(roasbeef): this isn't re-launched?
}
c.resolveContracts(unresolvedContracts)
return nil
}
// Report returns htlc reports for the active resolvers.
func (c *ChannelArbitrator) Report() []*ContractReport {
c.activeResolversLock.RLock()
defer c.activeResolversLock.RUnlock()
var reports []*ContractReport
for _, resolver := range c.activeResolvers {
r, ok := resolver.(reportingContractResolver)
if !ok {
continue
}
report := r.report()
if report == nil {
continue
}
reports = append(reports, report)
}
return reports
}
// Stop signals the ChannelArbitrator for a graceful shutdown.
func (c *ChannelArbitrator) Stop() error {
if !atomic.CompareAndSwapInt32(&c.stopped, 0, 1) {
return nil
}
log.Debugf("Stopping ChannelArbitrator(%v)", c.cfg.ChanPoint)
if c.cfg.ChainEvents.Cancel != nil {
go c.cfg.ChainEvents.Cancel()
}
c.activeResolversLock.RLock()
for _, activeResolver := range c.activeResolvers {
activeResolver.Stop()
}
c.activeResolversLock.RUnlock()
close(c.quit)
c.wg.Wait()
return nil
}
// transitionTrigger is an enum that denotes exactly *why* a state transition
// was initiated. This is useful as depending on the initial trigger, we may
// skip certain states as those actions are expected to have already taken
// place as a result of the external trigger.
type transitionTrigger uint8
const (
// chainTrigger is a transition trigger that has been attempted due to
// changing on-chain conditions such as a block which times out HTLC's
// being attached.
chainTrigger transitionTrigger = iota
// userTrigger is a transition trigger driven by user action. Examples
// of such a trigger include a user requesting a force closure of the
// channel.
userTrigger
// remoteCloseTrigger is a transition trigger driven by the remote
// peer's commitment being confirmed.
remoteCloseTrigger
// localCloseTrigger is a transition trigger driven by our commitment
// being confirmed.
localCloseTrigger
// coopCloseTrigger is a transition trigger driven by a cooperative
// close transaction being confirmed.
coopCloseTrigger
// breachCloseTrigger is a transition trigger driven by a remote breach
// being confirmed. In this case the channel arbitrator will wait for
// the BreachArbitrator to finish and then clean up gracefully.
breachCloseTrigger
)
// String returns a human readable string describing the passed
// transitionTrigger.
func (t transitionTrigger) String() string {
switch t {
case chainTrigger:
return "chainTrigger"
case remoteCloseTrigger:
return "remoteCloseTrigger"
case userTrigger:
return "userTrigger"
case localCloseTrigger:
return "localCloseTrigger"
case coopCloseTrigger:
return "coopCloseTrigger"
case breachCloseTrigger:
return "breachCloseTrigger"
default:
return "unknown trigger"
}
}
// stateStep is a help method that examines our internal state, and attempts
// the appropriate state transition if necessary. The next state we transition
// to is returned, Additionally, if the next transition results in a commitment
// broadcast, the commitment transaction itself is returned.
func (c *ChannelArbitrator) stateStep(
triggerHeight uint32, trigger transitionTrigger,
confCommitSet *CommitSet) (ArbitratorState, *wire.MsgTx, error) {
var (
nextState ArbitratorState
closeTx *wire.MsgTx
)
switch c.state {
// If we're in the default state, then we'll check our set of actions
// to see if while we were down, conditions have changed.
case StateDefault:
log.Debugf("ChannelArbitrator(%v): examining active HTLCs in "+
"block %v, confCommitSet: %v", c.cfg.ChanPoint,
triggerHeight, lnutils.LogClosure(confCommitSet.String))
// As a new block has been connected to the end of the main
// chain, we'll check to see if we need to make any on-chain
// claims on behalf of the channel contract that we're
// arbitrating for. If a commitment has confirmed, then we'll
// use the set snapshot from the chain, otherwise we'll use our
// current set.
var (
chainActions ChainActionMap
err error
)
// Normally if we force close the channel locally we will have
// no confCommitSet. However when the remote commitment confirms
// without us ever broadcasting our local commitment we need to
// make sure we cancel all upstream HTLCs for outgoing dust
// HTLCs as well hence we need to fetch the chain actions here
// as well.
if confCommitSet == nil {
// Update the set of activeHTLCs so
// checkLocalChainActions has an up-to-date view of the
// commitments.
c.updateActiveHTLCs()
htlcs := c.activeHTLCs
chainActions, err = c.checkLocalChainActions(
triggerHeight, trigger, htlcs, false,
)
if err != nil {
return StateDefault, nil, err
}
} else {
chainActions, err = c.constructChainActions(
confCommitSet, triggerHeight, trigger,
)
if err != nil {
return StateDefault, nil, err
}
}
// If there are no actions to be made, then we'll remain in the
// default state. If this isn't a self initiated event (we're
// checking due to a chain update), then we'll exit now.
if len(chainActions) == 0 && trigger == chainTrigger {
log.Debugf("ChannelArbitrator(%v): no actions for "+
"chain trigger, terminating", c.cfg.ChanPoint)
return StateDefault, closeTx, nil
}
// Otherwise, we'll log that we checked the HTLC actions as the
// commitment transaction has already been broadcast.
log.Tracef("ChannelArbitrator(%v): logging chain_actions=%v",
c.cfg.ChanPoint, lnutils.SpewLogClosure(chainActions))
// Cancel upstream HTLCs for all outgoing dust HTLCs available
// either on the local or the remote/remote pending commitment
// transaction.
dustHTLCs := chainActions[HtlcFailDustAction]
if len(dustHTLCs) > 0 {
log.Debugf("ChannelArbitrator(%v): canceling %v dust "+
"HTLCs backwards", c.cfg.ChanPoint,
len(dustHTLCs))
getIdx := func(htlc channeldb.HTLC) uint64 {
return htlc.HtlcIndex
}
dustHTLCSet := fn.NewSet(fn.Map(dustHTLCs, getIdx)...)
err = c.abandonForwards(dustHTLCSet)
if err != nil {
return StateError, closeTx, err
}
}
// Depending on the type of trigger, we'll either "tunnel"
// through to a farther state, or just proceed linearly to the
// next state.
switch trigger {
// If this is a chain trigger, then we'll go straight to the
// next state, as we still need to broadcast the commitment
// transaction.
case chainTrigger:
fallthrough
case userTrigger:
nextState = StateBroadcastCommit
// If the trigger is a cooperative close being confirmed, then
// we can go straight to StateFullyResolved, as there won't be
// any contracts to resolve.
case coopCloseTrigger:
nextState = StateFullyResolved
// Otherwise, if this state advance was triggered by a
// commitment being confirmed on chain, then we'll jump
// straight to the state where the contract has already been
// closed, and we will inspect the set of unresolved contracts.
case localCloseTrigger:
log.Errorf("ChannelArbitrator(%v): unexpected local "+
"commitment confirmed while in StateDefault",
c.cfg.ChanPoint)
fallthrough
case remoteCloseTrigger:
nextState = StateContractClosed
case breachCloseTrigger:
nextContractState, err := c.checkLegacyBreach()
if nextContractState == StateError {
return nextContractState, nil, err
}
nextState = nextContractState
}
// If we're in this state, then we've decided to broadcast the
// commitment transaction. We enter this state either due to an outside
// sub-system, or because an on-chain action has been triggered.
case StateBroadcastCommit:
// Under normal operation, we can only enter
// StateBroadcastCommit via a user or chain trigger. On restart,
// this state may be reexecuted after closing the channel, but
// failing to commit to StateContractClosed or
// StateFullyResolved. In that case, one of the four close
// triggers will be presented, signifying that we should skip
// rebroadcasting, and go straight to resolving the on-chain
// contract or marking the channel resolved.
switch trigger {
case localCloseTrigger, remoteCloseTrigger:
log.Infof("ChannelArbitrator(%v): detected %s "+
"close after closing channel, fast-forwarding "+
"to %s to resolve contract",
c.cfg.ChanPoint, trigger, StateContractClosed)
return StateContractClosed, closeTx, nil
case breachCloseTrigger:
nextContractState, err := c.checkLegacyBreach()
if nextContractState == StateError {
log.Infof("ChannelArbitrator(%v): unable to "+
"advance breach close resolution: %v",
c.cfg.ChanPoint, nextContractState)
return StateError, closeTx, err
}
log.Infof("ChannelArbitrator(%v): detected %s close "+
"after closing channel, fast-forwarding to %s"+
" to resolve contract", c.cfg.ChanPoint,
trigger, nextContractState)
return nextContractState, closeTx, nil
case coopCloseTrigger:
log.Infof("ChannelArbitrator(%v): detected %s "+
"close after closing channel, fast-forwarding "+
"to %s to resolve contract",
c.cfg.ChanPoint, trigger, StateFullyResolved)
return StateFullyResolved, closeTx, nil
}
log.Infof("ChannelArbitrator(%v): force closing "+
"chan", c.cfg.ChanPoint)
// Now that we have all the actions decided for the set of
// HTLC's, we'll broadcast the commitment transaction, and
// signal the link to exit.
// We'll tell the switch that it should remove the link for
// this channel, in addition to fetching the force close
// summary needed to close this channel on chain.
forceCloseTx, err := c.cfg.Channel.ForceCloseChan()
if err != nil {
log.Errorf("ChannelArbitrator(%v): unable to "+
"force close: %v", c.cfg.ChanPoint, err)
// We tried to force close (HTLC may be expiring from
// our PoV, etc), but we think we've lost data. In this
// case, we'll not force close, but terminate the state
// machine here to wait to see what confirms on chain.
if errors.Is(err, lnwallet.ErrForceCloseLocalDataLoss) {
log.Error("ChannelArbitrator(%v): broadcast "+
"failed due to local data loss, "+
"waiting for on chain confimation...",
c.cfg.ChanPoint)
return StateBroadcastCommit, nil, nil
}
return StateError, closeTx, err
}
closeTx = forceCloseTx
// Before publishing the transaction, we store it to the
// database, such that we can re-publish later in case it
// didn't propagate. We initiated the force close, so we
// mark broadcast with local initiator set to true.
err = c.cfg.MarkCommitmentBroadcasted(closeTx, lntypes.Local)
if err != nil {
log.Errorf("ChannelArbitrator(%v): unable to "+
"mark commitment broadcasted: %v",
c.cfg.ChanPoint, err)
return StateError, closeTx, err
}
// With the close transaction in hand, broadcast the
// transaction to the network, thereby entering the post
// channel resolution state.
log.Infof("Broadcasting force close transaction %v, "+
"ChannelPoint(%v): %v", closeTx.TxHash(),
c.cfg.ChanPoint, lnutils.SpewLogClosure(closeTx))
// At this point, we'll now broadcast the commitment
// transaction itself.
label := labels.MakeLabel(
labels.LabelTypeChannelClose, &c.cfg.ShortChanID,
)
if err := c.cfg.PublishTx(closeTx, label); err != nil {
log.Errorf("ChannelArbitrator(%v): unable to broadcast "+
"close tx: %v", c.cfg.ChanPoint, err)
// This makes sure we don't fail at startup if the
// commitment transaction has too low fees to make it
// into mempool. The rebroadcaster makes sure this
// transaction is republished regularly until confirmed
// or replaced.
if !errors.Is(err, lnwallet.ErrDoubleSpend) &&
!errors.Is(err, lnwallet.ErrMempoolFee) {
return StateError, closeTx, err
}
}
// We go to the StateCommitmentBroadcasted state, where we'll
// be waiting for the commitment to be confirmed.
nextState = StateCommitmentBroadcasted
// In this state we have broadcasted our own commitment, and will need
// to wait for a commitment (not necessarily the one we broadcasted!)
// to be confirmed.
case StateCommitmentBroadcasted:
switch trigger {
// We are waiting for a commitment to be confirmed.
case chainTrigger, userTrigger:
// The commitment transaction has been broadcast, but it
// doesn't necessarily need to be the commitment
// transaction version that is going to be confirmed. To
// be sure that any of those versions can be anchored
// down, we now submit all anchor resolutions to the
// sweeper. The sweeper will keep trying to sweep all of
// them.
//
// Note that the sweeper is idempotent. If we ever
// happen to end up at this point in the code again, no
// harm is done by re-offering the anchors to the
// sweeper.
anchors, err := c.cfg.Channel.NewAnchorResolutions()
if err != nil {
return StateError, closeTx, err
}
err = c.sweepAnchors(anchors, triggerHeight)
if err != nil {
return StateError, closeTx, err
}
nextState = StateCommitmentBroadcasted
// If this state advance was triggered by any of the
// commitments being confirmed, then we'll jump to the state
// where the contract has been closed.
case localCloseTrigger, remoteCloseTrigger:
nextState = StateContractClosed
// If a coop close was confirmed, jump straight to the fully
// resolved state.
case coopCloseTrigger:
nextState = StateFullyResolved
case breachCloseTrigger:
nextContractState, err := c.checkLegacyBreach()
if nextContractState == StateError {
return nextContractState, closeTx, err
}
nextState = nextContractState
}
log.Infof("ChannelArbitrator(%v): trigger %v moving from "+
"state %v to %v", c.cfg.ChanPoint, trigger, c.state,
nextState)
// If we're in this state, then the contract has been fully closed to
// outside sub-systems, so we'll process the prior set of on-chain
// contract actions and launch a set of resolvers.
case StateContractClosed:
// First, we'll fetch our chain actions, and both sets of
// resolutions so we can process them.
contractResolutions, err := c.log.FetchContractResolutions()
if err != nil {
log.Errorf("unable to fetch contract resolutions: %v",
err)
return StateError, closeTx, err
}
// If the resolution is empty, and we have no HTLCs at all to
// send to, then we're done here. We don't need to launch any
// resolvers, and can go straight to our final state.
if contractResolutions.IsEmpty() && confCommitSet.IsEmpty() {
log.Infof("ChannelArbitrator(%v): contract "+
"resolutions empty, marking channel as fully resolved!",
c.cfg.ChanPoint)
nextState = StateFullyResolved
break
}
// First, we'll reconstruct a fresh set of chain actions as the
// set of actions we need to act on may differ based on if it
// was our commitment, or they're commitment that hit the chain.
htlcActions, err := c.constructChainActions(
confCommitSet, triggerHeight, trigger,
)
if err != nil {
return StateError, closeTx, err
}
// In case its a breach transaction we fail back all upstream
// HTLCs for their corresponding outgoing HTLCs on the remote
// commitment set (remote and remote pending set).
if contractResolutions.BreachResolution != nil {
// cancelBreachedHTLCs is a set which holds HTLCs whose
// corresponding incoming HTLCs will be failed back
// because the peer broadcasted an old state.
cancelBreachedHTLCs := fn.NewSet[uint64]()
// We'll use the CommitSet, we'll fail back all
// upstream HTLCs for their corresponding outgoing
// HTLC that exist on either of the remote commitments.
// The map is used to deduplicate any shared HTLC's.
for htlcSetKey, htlcs := range confCommitSet.HtlcSets {
if !htlcSetKey.IsRemote {
continue
}
for _, htlc := range htlcs {
// Only outgoing HTLCs have a
// corresponding incoming HTLC.
if htlc.Incoming {
continue
}
cancelBreachedHTLCs.Add(htlc.HtlcIndex)
}
}
err := c.abandonForwards(cancelBreachedHTLCs)
if err != nil {
return StateError, closeTx, err
}
} else {
// If it's not a breach, we resolve all incoming dust
// HTLCs immediately after the commitment is confirmed.
err = c.failIncomingDust(
htlcActions[HtlcIncomingDustFinalAction],
)
if err != nil {
return StateError, closeTx, err
}
// We fail the upstream HTLCs for all remote pending
// outgoing HTLCs as soon as the commitment is
// confirmed. The upstream HTLCs for outgoing dust
// HTLCs have already been resolved before we reach
// this point.
getIdx := func(htlc channeldb.HTLC) uint64 {
return htlc.HtlcIndex
}
remoteDangling := fn.NewSet(fn.Map(
htlcActions[HtlcFailDanglingAction], getIdx,
)...)
err := c.abandonForwards(remoteDangling)
if err != nil {
return StateError, closeTx, err
}
}
// Now that we know we'll need to act, we'll process all the
// resolvers, then create the structures we need to resolve all
// outstanding contracts.
resolvers, err := c.prepContractResolutions(
contractResolutions, triggerHeight, htlcActions,
)
if err != nil {
log.Errorf("ChannelArbitrator(%v): unable to "+
"resolve contracts: %v", c.cfg.ChanPoint, err)
return StateError, closeTx, err
}
log.Debugf("ChannelArbitrator(%v): inserting %v contract "+
"resolvers", c.cfg.ChanPoint, len(resolvers))
err = c.log.InsertUnresolvedContracts(nil, resolvers...)
if err != nil {
return StateError, closeTx, err
}
// Finally, we'll launch all the required contract resolvers.
// Once they're all resolved, we're no longer needed.
c.resolveContracts(resolvers)
nextState = StateWaitingFullResolution
// This is our terminal state. We'll keep returning this state until
// all contracts are fully resolved.
case StateWaitingFullResolution:
log.Infof("ChannelArbitrator(%v): still awaiting contract "+
"resolution", c.cfg.ChanPoint)
unresolved, err := c.log.FetchUnresolvedContracts()
if err != nil {
return StateError, closeTx, err
}
// If we have no unresolved contracts, then we can move to the
// final state.
if len(unresolved) == 0 {
nextState = StateFullyResolved
break
}
// Otherwise we still have unresolved contracts, then we'll
// stay alive to oversee their resolution.
nextState = StateWaitingFullResolution
// Add debug logs.
for _, r := range unresolved {
log.Debugf("ChannelArbitrator(%v): still have "+
"unresolved contract: %T", c.cfg.ChanPoint, r)
}
// If we start as fully resolved, then we'll end as fully resolved.
case StateFullyResolved:
// To ensure that the state of the contract in persistent
// storage is properly reflected, we'll mark the contract as
// fully resolved now.
nextState = StateFullyResolved
log.Infof("ChannelPoint(%v) has been fully resolved "+
"on-chain at height=%v", c.cfg.ChanPoint, triggerHeight)
if err := c.cfg.MarkChannelResolved(); err != nil {
log.Errorf("unable to mark channel resolved: %v", err)
return StateError, closeTx, err
}
}
log.Tracef("ChannelArbitrator(%v): next_state=%v", c.cfg.ChanPoint,
nextState)
return nextState, closeTx, nil
}
// sweepAnchors offers all given anchor resolutions to the sweeper. It requests
// sweeping at the minimum fee rate. This fee rate can be upped manually by the
// user via the BumpFee rpc.
func (c *ChannelArbitrator) sweepAnchors(anchors *lnwallet.AnchorResolutions,
heightHint uint32) error {
// Update the set of activeHTLCs so that the sweeping routine has an
// up-to-date view of the set of commitments.
c.updateActiveHTLCs()
// Prepare the sweeping requests for all possible versions of
// commitments.
sweepReqs, err := c.prepareAnchorSweeps(heightHint, anchors)
if err != nil {
return err
}
// Send out the sweeping requests to the sweeper.
for _, req := range sweepReqs {
_, err = c.cfg.Sweeper.SweepInput(req.input, req.params)
if err != nil {
return err
}
}
return nil
}
// findCommitmentDeadlineAndValue finds the deadline (relative block height)
// for a commitment transaction by extracting the minimum CLTV from its HTLCs.
// From our PoV, the deadline delta is defined to be the smaller of,
// - half of the least CLTV from outgoing HTLCs' corresponding incoming
// HTLCs, or,
// - half of the least CLTV from incoming HTLCs if the preimage is available.
//
// We use half of the CTLV value to ensure that we have enough time to sweep
// the second-level HTLCs.
//
// It also finds the total value that are time-sensitive, which is the sum of
// all the outgoing HTLCs plus incoming HTLCs whose preimages are known. It
// then returns the value left after subtracting the budget used for sweeping
// the time-sensitive HTLCs.
//
// NOTE: when the deadline turns out to be 0 blocks, we will replace it with 1
// block because our fee estimator doesn't allow a 0 conf target. This also
// means we've left behind and should increase our fee to make the transaction
// confirmed asap.
func (c *ChannelArbitrator) findCommitmentDeadlineAndValue(heightHint uint32,
htlcs htlcSet) (fn.Option[int32], btcutil.Amount, error) {
deadlineMinHeight := uint32(math.MaxUint32)
totalValue := btcutil.Amount(0)
// First, iterate through the outgoingHTLCs to find the lowest CLTV
// value.
for _, htlc := range htlcs.outgoingHTLCs {
// Skip if the HTLC is dust.
if htlc.OutputIndex < 0 {
log.Debugf("ChannelArbitrator(%v): skipped deadline "+
"for dust htlc=%x",
c.cfg.ChanPoint, htlc.RHash[:])
continue
}
value := htlc.Amt.ToSatoshis()
// Find the expiry height for this outgoing HTLC's incoming
// HTLC.
deadlineOpt := c.cfg.FindOutgoingHTLCDeadline(htlc)
// The deadline is default to the current deadlineMinHeight,
// and it's overwritten when it's not none.
deadline := deadlineMinHeight
deadlineOpt.WhenSome(func(d int32) {
deadline = uint32(d)
// We only consider the value is under protection when
// it's time-sensitive.
totalValue += value
})
if deadline < deadlineMinHeight {
deadlineMinHeight = deadline
log.Tracef("ChannelArbitrator(%v): outgoing HTLC has "+
"deadline=%v, value=%v", c.cfg.ChanPoint,
deadlineMinHeight, value)
}
}
// Then going through the incomingHTLCs, and update the minHeight when
// conditions met.
for _, htlc := range htlcs.incomingHTLCs {
// Skip if the HTLC is dust.
if htlc.OutputIndex < 0 {
log.Debugf("ChannelArbitrator(%v): skipped deadline "+
"for dust htlc=%x",
c.cfg.ChanPoint, htlc.RHash[:])
continue
}
// Since it's an HTLC sent to us, check if we have preimage for
// this HTLC.
preimageAvailable, err := c.isPreimageAvailable(htlc.RHash)
if err != nil {
return fn.None[int32](), 0, err
}
if !preimageAvailable {
continue
}
value := htlc.Amt.ToSatoshis()
totalValue += value
if htlc.RefundTimeout < deadlineMinHeight {
deadlineMinHeight = htlc.RefundTimeout
log.Tracef("ChannelArbitrator(%v): incoming HTLC has "+
"deadline=%v, amt=%v", c.cfg.ChanPoint,
deadlineMinHeight, value)
}
}
// Calculate the deadline. There are two cases to be handled here,
// - when the deadlineMinHeight never gets updated, which could
// happen when we have no outgoing HTLCs, and, for incoming HTLCs,
// * either we have none, or,
// * none of the HTLCs are preimageAvailable.
// - when our deadlineMinHeight is no greater than the heightHint,
// which means we are behind our schedule.
var deadline uint32
switch {
// When we couldn't find a deadline height from our HTLCs, we will fall
// back to the default value as there's no time pressure here.
case deadlineMinHeight == math.MaxUint32:
return fn.None[int32](), 0, nil
// When the deadline is passed, we will fall back to the smallest conf
// target (1 block).
case deadlineMinHeight <= heightHint:
log.Warnf("ChannelArbitrator(%v): deadline is passed with "+
"deadlineMinHeight=%d, heightHint=%d",
c.cfg.ChanPoint, deadlineMinHeight, heightHint)
deadline = 1
// Use half of the deadline delta, and leave the other half to be used
// to sweep the HTLCs.
default:
deadline = (deadlineMinHeight - heightHint) / 2
}
// Calculate the value left after subtracting the budget used for
// sweeping the time-sensitive HTLCs.
valueLeft := totalValue - calculateBudget(
totalValue, c.cfg.Budget.DeadlineHTLCRatio,
c.cfg.Budget.DeadlineHTLC,
)
log.Debugf("ChannelArbitrator(%v): calculated valueLeft=%v, "+
"deadline=%d, using deadlineMinHeight=%d, heightHint=%d",
c.cfg.ChanPoint, valueLeft, deadline, deadlineMinHeight,
heightHint)
return fn.Some(int32(deadline)), valueLeft, nil
}
// resolveContracts updates the activeResolvers list and starts to resolve each
// contract concurrently, and launches them.
func (c *ChannelArbitrator) resolveContracts(resolvers []ContractResolver) {
c.activeResolversLock.Lock()
c.activeResolvers = resolvers
c.activeResolversLock.Unlock()
// Launch all resolvers.
c.launchResolvers()
for _, contract := range resolvers {
c.wg.Add(1)
go c.resolveContract(contract)
}
}
// launchResolvers launches all the active resolvers concurrently.
func (c *ChannelArbitrator) launchResolvers() {
c.activeResolversLock.Lock()
resolvers := c.activeResolvers
c.activeResolversLock.Unlock()
// errChans is a map of channels that will be used to receive errors
// returned from launching the resolvers.
errChans := make(map[ContractResolver]chan error, len(resolvers))
// Launch each resolver in goroutines.
for _, r := range resolvers {
// If the contract is already resolved, there's no need to
// launch it again.
if r.IsResolved() {
log.Debugf("ChannelArbitrator(%v): skipping resolver "+
"%T as it's already resolved", c.cfg.ChanPoint,
r)
continue
}
// Create a signal chan.
errChan := make(chan error, 1)
errChans[r] = errChan
go func() {
err := r.Launch()
errChan <- err
}()
}
// Wait for all resolvers to finish launching.
for r, errChan := range errChans {
select {
case err := <-errChan:
if err == nil {
continue
}
log.Errorf("ChannelArbitrator(%v): unable to launch "+
"contract resolver(%T): %v", c.cfg.ChanPoint, r,
err)
case <-c.quit:
log.Debugf("ChannelArbitrator quit signal received, " +
"exit launchResolvers")
return
}
}
}
// advanceState is the main driver of our state machine. This method is an
// iterative function which repeatedly attempts to advance the internal state
// of the channel arbitrator. The state will be advanced until we reach a
// redundant transition, meaning that the state transition is a noop. The final
// param is a callback that allows the caller to execute an arbitrary action
// after each state transition.
func (c *ChannelArbitrator) advanceState(
triggerHeight uint32, trigger transitionTrigger,
confCommitSet *CommitSet) (ArbitratorState, *wire.MsgTx, error) {
var (
priorState ArbitratorState
forceCloseTx *wire.MsgTx
)
// We'll continue to advance our state forward until the state we
// transition to is that same state that we started at.
for {
priorState = c.state
log.Debugf("ChannelArbitrator(%v): attempting state step with "+
"trigger=%v from state=%v at height=%v",
c.cfg.ChanPoint, trigger, priorState, triggerHeight)
nextState, closeTx, err := c.stateStep(
triggerHeight, trigger, confCommitSet,
)
if err != nil {
log.Errorf("ChannelArbitrator(%v): unable to advance "+
"state: %v", c.cfg.ChanPoint, err)
return priorState, nil, err
}
if forceCloseTx == nil && closeTx != nil {
forceCloseTx = closeTx
}
// Our termination transition is a noop transition. If we get
// our prior state back as the next state, then we'll
// terminate.
if nextState == priorState {
log.Debugf("ChannelArbitrator(%v): terminating at "+
"state=%v", c.cfg.ChanPoint, nextState)
return nextState, forceCloseTx, nil
}
// As the prior state was successfully executed, we can now
// commit the next state. This ensures that we will re-execute
// the prior state if anything fails.
if err := c.log.CommitState(nextState); err != nil {
log.Errorf("ChannelArbitrator(%v): unable to commit "+
"next state(%v): %v", c.cfg.ChanPoint,
nextState, err)
return priorState, nil, err
}
c.state = nextState
}
}
// ChainAction is an enum that encompasses all possible on-chain actions
// we'll take for a set of HTLC's.
type ChainAction uint8
const (
// NoAction is the min chainAction type, indicating that no action
// needs to be taken for a given HTLC.
NoAction ChainAction = 0
// HtlcTimeoutAction indicates that the HTLC will timeout soon. As a
// result, we should get ready to sweep it on chain after the timeout.
HtlcTimeoutAction = 1
// HtlcClaimAction indicates that we should claim the HTLC on chain
// before its timeout period.
HtlcClaimAction = 2
// HtlcFailDustAction indicates that we should fail the upstream HTLC
// for an outgoing dust HTLC immediately (even before the commitment
// transaction is confirmed) because it has no output on the commitment
// transaction. This also includes remote pending outgoing dust HTLCs.
HtlcFailDustAction = 3
// HtlcOutgoingWatchAction indicates that we can't yet timeout this
// HTLC, but we had to go to chain on order to resolve an existing
// HTLC. In this case, we'll either: time it out once it expires, or
// will learn the pre-image if the remote party claims the output. In
// this case, well add the pre-image to our global store.
HtlcOutgoingWatchAction = 4
// HtlcIncomingWatchAction indicates that we don't yet have the
// pre-image to claim incoming HTLC, but we had to go to chain in order
// to resolve and existing HTLC. In this case, we'll either: let the
// other party time it out, or eventually learn of the pre-image, in
// which case we'll claim on chain.
HtlcIncomingWatchAction = 5
// HtlcIncomingDustFinalAction indicates that we should mark an incoming
// dust htlc as final because it can't be claimed on-chain.
HtlcIncomingDustFinalAction = 6
// HtlcFailDanglingAction indicates that we should fail the upstream
// HTLC for an outgoing HTLC immediately after the commitment
// transaction has confirmed because it has no corresponding output on
// the commitment transaction. This category does NOT include any dust
// HTLCs which are mapped in the "HtlcFailDustAction" category.
HtlcFailDanglingAction = 7
)
// String returns a human readable string describing a chain action.
func (c ChainAction) String() string {
switch c {
case NoAction:
return "NoAction"
case HtlcTimeoutAction:
return "HtlcTimeoutAction"
case HtlcClaimAction:
return "HtlcClaimAction"
case HtlcFailDustAction:
return "HtlcFailDustAction"
case HtlcOutgoingWatchAction:
return "HtlcOutgoingWatchAction"
case HtlcIncomingWatchAction:
return "HtlcIncomingWatchAction"
case HtlcIncomingDustFinalAction:
return "HtlcIncomingDustFinalAction"
case HtlcFailDanglingAction:
return "HtlcFailDanglingAction"
default:
return "<unknown action>"
}
}
// ChainActionMap is a map of a chain action, to the set of HTLC's that need to
// be acted upon for a given action type. The channel
type ChainActionMap map[ChainAction][]channeldb.HTLC
// Merge merges the passed chain actions with the target chain action map.
func (c ChainActionMap) Merge(actions ChainActionMap) {
for chainAction, htlcs := range actions {
c[chainAction] = append(c[chainAction], htlcs...)
}
}
// shouldGoOnChain takes into account the absolute timeout of the HTLC, if the
// confirmation delta that we need is close, and returns a bool indicating if
// we should go on chain to claim. We do this rather than waiting up until the
// last minute as we want to ensure that when we *need* (HTLC is timed out) to
// sweep, the commitment is already confirmed.
func (c *ChannelArbitrator) shouldGoOnChain(htlc channeldb.HTLC,
broadcastDelta, currentHeight uint32) bool {
// We'll calculate the broadcast cut off for this HTLC. This is the
// height that (based on our current fee estimation) we should
// broadcast in order to ensure the commitment transaction is confirmed
// before the HTLC fully expires.
broadcastCutOff := htlc.RefundTimeout - broadcastDelta
log.Tracef("ChannelArbitrator(%v): examining outgoing contract: "+
"expiry=%v, cutoff=%v, height=%v", c.cfg.ChanPoint, htlc.RefundTimeout,
broadcastCutOff, currentHeight)
// TODO(roasbeef): take into account default HTLC delta, don't need to
// broadcast immediately
// * can then batch with SINGLE | ANYONECANPAY
// We should on-chain for this HTLC, iff we're within out broadcast
// cutoff window.
if currentHeight < broadcastCutOff {
return false
}
// In case of incoming htlc we should go to chain.
if htlc.Incoming {
return true
}
// For htlcs that are result of our initiated payments we give some grace
// period before force closing the channel. During this time we expect
// both nodes to connect and give a chance to the other node to send its
// updates and cancel the htlc.
// This shouldn't add any security risk as there is no incoming htlc to
// fulfill at this case and the expectation is that when the channel is
// active the other node will send update_fail_htlc to remove the htlc
// without closing the channel. It is up to the user to force close the
// channel if the peer misbehaves and doesn't send the update_fail_htlc.
// It is useful when this node is most of the time not online and is
// likely to miss the time slot where the htlc may be cancelled.
isForwarded := c.cfg.IsForwardedHTLC(c.cfg.ShortChanID, htlc.HtlcIndex)
upTime := c.cfg.Clock.Now().Sub(c.startTimestamp)
return isForwarded || upTime > c.cfg.PaymentsExpirationGracePeriod
}
// checkCommitChainActions is called for each new block connected to the end of
// the main chain. Given the new block height, this new method will examine all
// active HTLC's, and determine if we need to go on-chain to claim any of them.
// A map of action -> []htlc is returned, detailing what action (if any) should
// be performed for each HTLC. For timed out HTLC's, once the commitment has
// been sufficiently confirmed, the HTLC's should be canceled backwards. For
// redeemed HTLC's, we should send the pre-image back to the incoming link.
func (c *ChannelArbitrator) checkCommitChainActions(height uint32,
trigger transitionTrigger, htlcs htlcSet) (ChainActionMap, error) {
// TODO(roasbeef): would need to lock channel? channel totem?
// * race condition if adding and we broadcast, etc
// * or would make each instance sync?
log.Debugf("ChannelArbitrator(%v): checking commit chain actions at "+
"height=%v, in_htlc_count=%v, out_htlc_count=%v",
c.cfg.ChanPoint, height,
len(htlcs.incomingHTLCs), len(htlcs.outgoingHTLCs))
actionMap := make(ChainActionMap)
// First, we'll make an initial pass over the set of incoming and
// outgoing HTLC's to decide if we need to go on chain at all.
haveChainActions := false
for _, htlc := range htlcs.outgoingHTLCs {
// We'll need to go on-chain for an outgoing HTLC if it was
// never resolved downstream, and it's "close" to timing out.
//
// TODO(yy): If there's no corresponding incoming HTLC, it
// means we are the first hop, hence the payer. This is a
// tricky case - unlike a forwarding hop, we don't have an
// incoming HTLC that will time out, which means as long as we
// can learn the preimage, we can settle the invoice (before it
// expires?).
toChain := c.shouldGoOnChain(
htlc, c.cfg.OutgoingBroadcastDelta, height,
)
if toChain {
// Convert to int64 in case of overflow.
remainingBlocks := int64(htlc.RefundTimeout) -
int64(height)
log.Infof("ChannelArbitrator(%v): go to chain for "+
"outgoing htlc %x: timeout=%v, amount=%v, "+
"blocks_until_expiry=%v, broadcast_delta=%v",
c.cfg.ChanPoint, htlc.RHash[:],
htlc.RefundTimeout, htlc.Amt, remainingBlocks,
c.cfg.OutgoingBroadcastDelta,
)
}
haveChainActions = haveChainActions || toChain
}
for _, htlc := range htlcs.incomingHTLCs {
// We'll need to go on-chain to pull an incoming HTLC iff we
// know the pre-image and it's close to timing out. We need to
// ensure that we claim the funds that are rightfully ours
// on-chain.
preimageAvailable, err := c.isPreimageAvailable(htlc.RHash)
if err != nil {
return nil, err
}
if !preimageAvailable {
continue
}
toChain := c.shouldGoOnChain(
htlc, c.cfg.IncomingBroadcastDelta, height,
)
if toChain {
// Convert to int64 in case of overflow.
remainingBlocks := int64(htlc.RefundTimeout) -
int64(height)
log.Infof("ChannelArbitrator(%v): go to chain for "+
"incoming htlc %x: timeout=%v, amount=%v, "+
"blocks_until_expiry=%v, broadcast_delta=%v",
c.cfg.ChanPoint, htlc.RHash[:],
htlc.RefundTimeout, htlc.Amt, remainingBlocks,
c.cfg.IncomingBroadcastDelta,
)
}
haveChainActions = haveChainActions || toChain
}
// If we don't have any actions to make, then we'll return an empty
// action map. We only do this if this was a chain trigger though, as
// if we're going to broadcast the commitment (or the remote party did)
// we're *forced* to act on each HTLC.
if !haveChainActions && trigger == chainTrigger {
log.Tracef("ChannelArbitrator(%v): no actions to take at "+
"height=%v", c.cfg.ChanPoint, height)
return actionMap, nil
}
// Now that we know we'll need to go on-chain, we'll examine all of our
// active outgoing HTLC's to see if we either need to: sweep them after
// a timeout (then cancel backwards), cancel them backwards
// immediately, or watch them as they're still active contracts.
for _, htlc := range htlcs.outgoingHTLCs {
switch {
// If the HTLC is dust, then we can cancel it backwards
// immediately as there's no matching contract to arbitrate
// on-chain. We know the HTLC is dust, if the OutputIndex
// negative.
case htlc.OutputIndex < 0:
log.Tracef("ChannelArbitrator(%v): immediately "+
"failing dust htlc=%x", c.cfg.ChanPoint,
htlc.RHash[:])
actionMap[HtlcFailDustAction] = append(
actionMap[HtlcFailDustAction], htlc,
)
// If we don't need to immediately act on this HTLC, then we'll
// mark it still "live". After we broadcast, we'll monitor it
// until the HTLC times out to see if we can also redeem it
// on-chain.
case !c.shouldGoOnChain(htlc, c.cfg.OutgoingBroadcastDelta,
height,
):
// TODO(roasbeef): also need to be able to query
// circuit map to see if HTLC hasn't been fully
// resolved
//
// * can't fail incoming until if outgoing not yet
// failed
log.Tracef("ChannelArbitrator(%v): watching chain to "+
"decide action for outgoing htlc=%x",
c.cfg.ChanPoint, htlc.RHash[:])
actionMap[HtlcOutgoingWatchAction] = append(
actionMap[HtlcOutgoingWatchAction], htlc,
)
// Otherwise, we'll update our actionMap to mark that we need
// to sweep this HTLC on-chain
default:
log.Tracef("ChannelArbitrator(%v): going on-chain to "+
"timeout htlc=%x", c.cfg.ChanPoint, htlc.RHash[:])
actionMap[HtlcTimeoutAction] = append(
actionMap[HtlcTimeoutAction], htlc,
)
}
}
// Similarly, for each incoming HTLC, now that we need to go on-chain,
// we'll either: sweep it immediately if we know the pre-image, or
// observe the output on-chain if we don't In this last, case we'll
// either learn of it eventually from the outgoing HTLC, or the sender
// will timeout the HTLC.
for _, htlc := range htlcs.incomingHTLCs {
// If the HTLC is dust, there is no action to be taken.
if htlc.OutputIndex < 0 {
log.Debugf("ChannelArbitrator(%v): no resolution "+
"needed for incoming dust htlc=%x",
c.cfg.ChanPoint, htlc.RHash[:])
actionMap[HtlcIncomingDustFinalAction] = append(
actionMap[HtlcIncomingDustFinalAction], htlc,
)
continue
}
log.Tracef("ChannelArbitrator(%v): watching chain to decide "+
"action for incoming htlc=%x", c.cfg.ChanPoint,
htlc.RHash[:])
actionMap[HtlcIncomingWatchAction] = append(
actionMap[HtlcIncomingWatchAction], htlc,
)
}
return actionMap, nil
}
// isPreimageAvailable returns whether the hash preimage is available in either
// the preimage cache or the invoice database.
func (c *ChannelArbitrator) isPreimageAvailable(hash lntypes.Hash) (bool,
error) {
// Start by checking the preimage cache for preimages of
// forwarded HTLCs.
_, preimageAvailable := c.cfg.PreimageDB.LookupPreimage(
hash,
)
if preimageAvailable {
return true, nil
}
// Then check if we have an invoice that can be settled by this HTLC.
//
// TODO(joostjager): Check that there are still more blocks remaining
// than the invoice cltv delta. We don't want to go to chain only to
// have the incoming contest resolver decide that we don't want to
// settle this invoice.
invoice, err := c.cfg.Registry.LookupInvoice(context.Background(), hash)
switch {
case err == nil:
case errors.Is(err, invoices.ErrInvoiceNotFound) ||
errors.Is(err, invoices.ErrNoInvoicesCreated):
return false, nil
default:
return false, err
}
preimageAvailable = invoice.Terms.PaymentPreimage != nil
return preimageAvailable, nil
}
// checkLocalChainActions is similar to checkCommitChainActions, but it also
// examines the set of HTLCs on the remote party's commitment. This allows us
// to ensure we're able to satisfy the HTLC timeout constraints for incoming vs
// outgoing HTLCs.
func (c *ChannelArbitrator) checkLocalChainActions(
height uint32, trigger transitionTrigger,
activeHTLCs map[HtlcSetKey]htlcSet,
commitsConfirmed bool) (ChainActionMap, error) {
// First, we'll check our local chain actions as normal. This will only
// examine HTLCs on our local commitment (timeout or settle).
localCommitActions, err := c.checkCommitChainActions(
height, trigger, activeHTLCs[LocalHtlcSet],
)
if err != nil {
return nil, err
}
// Next, we'll examine the remote commitment (and maybe a dangling one)
// to see if the set difference of our HTLCs is non-empty. If so, then
// we may need to cancel back some HTLCs if we decide go to chain.
remoteDanglingActions := c.checkRemoteDanglingActions(
height, activeHTLCs, commitsConfirmed,
)
// Finally, we'll merge the two set of chain actions.
localCommitActions.Merge(remoteDanglingActions)
return localCommitActions, nil
}
// checkRemoteDanglingActions examines the set of remote commitments for any
// HTLCs that are close to timing out. If we find any, then we'll return a set
// of chain actions for HTLCs that are on our commitment, but not theirs to
// cancel immediately.
func (c *ChannelArbitrator) checkRemoteDanglingActions(
height uint32, activeHTLCs map[HtlcSetKey]htlcSet,
commitsConfirmed bool) ChainActionMap {
var (
pendingRemoteHTLCs []channeldb.HTLC
localHTLCs = make(map[uint64]struct{})
remoteHTLCs = make(map[uint64]channeldb.HTLC)
actionMap = make(ChainActionMap)
)
// First, we'll construct two sets of the outgoing HTLCs: those on our
// local commitment, and those that are on the remote commitment(s).
for htlcSetKey, htlcs := range activeHTLCs {
if htlcSetKey.IsRemote {
for _, htlc := range htlcs.outgoingHTLCs {
remoteHTLCs[htlc.HtlcIndex] = htlc
}
} else {
for _, htlc := range htlcs.outgoingHTLCs {
localHTLCs[htlc.HtlcIndex] = struct{}{}
}
}
}
// With both sets constructed, we'll now compute the set difference of
// our two sets of HTLCs. This'll give us the HTLCs that exist on the
// remote commitment transaction, but not on ours.
for htlcIndex, htlc := range remoteHTLCs {
if _, ok := localHTLCs[htlcIndex]; ok {
continue
}
pendingRemoteHTLCs = append(pendingRemoteHTLCs, htlc)
}
// Finally, we'll examine all the pending remote HTLCs for those that
// have expired. If we find any, then we'll recommend that they be
// failed now so we can free up the incoming HTLC.
for _, htlc := range pendingRemoteHTLCs {
// We'll now check if we need to go to chain in order to cancel
// the incoming HTLC.
goToChain := c.shouldGoOnChain(htlc, c.cfg.OutgoingBroadcastDelta,
height,
)
// If we don't need to go to chain, and no commitments have
// been confirmed, then we can move on. Otherwise, if
// commitments have been confirmed, then we need to cancel back
// *all* of the pending remote HTLCS.
if !goToChain && !commitsConfirmed {
continue
}
preimageAvailable, err := c.isPreimageAvailable(htlc.RHash)
if err != nil {
log.Errorf("ChannelArbitrator(%v): failed to query "+
"preimage for dangling htlc=%x from remote "+
"commitments diff", c.cfg.ChanPoint,
htlc.RHash[:])
continue
}
if preimageAvailable {
continue
}
// Dust htlcs can be canceled back even before the commitment
// transaction confirms. Dust htlcs are not enforceable onchain.
// If another version of the commit tx would confirm we either
// gain or lose those dust amounts but there is no other way
// than cancelling the incoming back because we will never learn
// the preimage.
if htlc.OutputIndex < 0 {
log.Infof("ChannelArbitrator(%v): fail dangling dust "+
"htlc=%x from local/remote commitments diff",
c.cfg.ChanPoint, htlc.RHash[:])
actionMap[HtlcFailDustAction] = append(
actionMap[HtlcFailDustAction], htlc,
)
continue
}
log.Infof("ChannelArbitrator(%v): fail dangling htlc=%x from "+
"local/remote commitments diff",
c.cfg.ChanPoint, htlc.RHash[:])
actionMap[HtlcFailDanglingAction] = append(
actionMap[HtlcFailDanglingAction], htlc,
)
}
return actionMap
}
// checkRemoteChainActions examines the two possible remote commitment chains
// and returns the set of chain actions we need to carry out if the remote
// commitment (non pending) confirms. The pendingConf indicates if the pending
// remote commitment confirmed. This is similar to checkCommitChainActions, but
// we'll immediately fail any HTLCs on the pending remote commit, but not the
// remote commit (or the other way around).
func (c *ChannelArbitrator) checkRemoteChainActions(
height uint32, trigger transitionTrigger,
activeHTLCs map[HtlcSetKey]htlcSet,
pendingConf bool) (ChainActionMap, error) {
// First, we'll examine all the normal chain actions on the remote
// commitment that confirmed.
confHTLCs := activeHTLCs[RemoteHtlcSet]
if pendingConf {
confHTLCs = activeHTLCs[RemotePendingHtlcSet]
}
remoteCommitActions, err := c.checkCommitChainActions(
height, trigger, confHTLCs,
)
if err != nil {
return nil, err
}
// With these actions computed, we'll now check the diff of the HTLCs on
// the commitments, and cancel back any that are on the pending but not
// the non-pending.
remoteDiffActions := c.checkRemoteDiffActions(
activeHTLCs, pendingConf,
)
// Finally, we'll merge all the chain actions and the final set of
// chain actions.
remoteCommitActions.Merge(remoteDiffActions)
return remoteCommitActions, nil
}
// checkRemoteDiffActions checks the set difference of the HTLCs on the remote
// confirmed commit and remote pending commit for HTLCS that we need to cancel
// back. If we find any HTLCs on the remote pending but not the remote, then
// we'll mark them to be failed immediately.
func (c *ChannelArbitrator) checkRemoteDiffActions(
activeHTLCs map[HtlcSetKey]htlcSet,
pendingConf bool) ChainActionMap {
// First, we'll partition the HTLCs into those that are present on the
// confirmed commitment, and those on the dangling commitment.
confHTLCs := activeHTLCs[RemoteHtlcSet]
danglingHTLCs := activeHTLCs[RemotePendingHtlcSet]
if pendingConf {
confHTLCs = activeHTLCs[RemotePendingHtlcSet]
danglingHTLCs = activeHTLCs[RemoteHtlcSet]
}
// Next, we'll create a set of all the HTLCs confirmed commitment.
remoteHtlcs := make(map[uint64]struct{})
for _, htlc := range confHTLCs.outgoingHTLCs {
remoteHtlcs[htlc.HtlcIndex] = struct{}{}
}
// With the remote HTLCs assembled, we'll mark any HTLCs only on the
// remote pending commitment to be failed asap.
actionMap := make(ChainActionMap)
for _, htlc := range danglingHTLCs.outgoingHTLCs {
if _, ok := remoteHtlcs[htlc.HtlcIndex]; ok {
continue
}
preimageAvailable, err := c.isPreimageAvailable(htlc.RHash)
if err != nil {
log.Errorf("ChannelArbitrator(%v): failed to query "+
"preimage for dangling htlc=%x from remote "+
"commitments diff", c.cfg.ChanPoint,
htlc.RHash[:])
continue
}
if preimageAvailable {
continue
}
// Dust HTLCs on the remote commitment can be failed back.
if htlc.OutputIndex < 0 {
log.Infof("ChannelArbitrator(%v): fail dangling dust "+
"htlc=%x from remote commitments diff",
c.cfg.ChanPoint, htlc.RHash[:])
actionMap[HtlcFailDustAction] = append(
actionMap[HtlcFailDustAction], htlc,
)
continue
}
actionMap[HtlcFailDanglingAction] = append(
actionMap[HtlcFailDanglingAction], htlc,
)
log.Infof("ChannelArbitrator(%v): fail dangling htlc=%x from "+
"remote commitments diff",
c.cfg.ChanPoint, htlc.RHash[:])
}
return actionMap
}
// constructChainActions returns the set of actions that should be taken for
// confirmed HTLCs at the specified height. Our actions will depend on the set
// of HTLCs that were active across all channels at the time of channel
// closure.
func (c *ChannelArbitrator) constructChainActions(confCommitSet *CommitSet,
height uint32, trigger transitionTrigger) (ChainActionMap, error) {
// If we've reached this point and have not confirmed commitment set,
// then this is an older node that had a pending close channel before
// the CommitSet was introduced. In this case, we'll just return the
// existing ChainActionMap they had on disk.
if confCommitSet == nil || confCommitSet.ConfCommitKey.IsNone() {
return c.log.FetchChainActions()
}
// Otherwise, we have the full commitment set written to disk, and can
// proceed as normal.
htlcSets := confCommitSet.toActiveHTLCSets()
confCommitKey, err := confCommitSet.ConfCommitKey.UnwrapOrErr(
fmt.Errorf("no commitKey available"),
)
if err != nil {
return nil, err
}
switch confCommitKey {
// If the local commitment transaction confirmed, then we'll examine
// that as well as their commitments to the set of chain actions.
case LocalHtlcSet:
return c.checkLocalChainActions(
height, trigger, htlcSets, true,
)
// If the remote commitment confirmed, then we'll grab all the chain
// actions for the remote commit, and check the pending commit for any
// HTLCS we need to handle immediately (dust).
case RemoteHtlcSet:
return c.checkRemoteChainActions(
height, trigger, htlcSets, false,
)
// Otherwise, the remote pending commitment confirmed, so we'll examine
// the HTLCs on that unrevoked dangling commitment.
case RemotePendingHtlcSet:
return c.checkRemoteChainActions(
height, trigger, htlcSets, true,
)
}
return nil, fmt.Errorf("unable to locate chain actions")
}
// prepContractResolutions is called either in the case that we decide we need
// to go to chain, or the remote party goes to chain. Given a set of actions we
// need to take for each HTLC, this method will return a set of contract
// resolvers that will resolve the contracts on-chain if needed, and also a set
// of packets to send to the htlcswitch in order to ensure all incoming HTLC's
// are properly resolved.
func (c *ChannelArbitrator) prepContractResolutions(
contractResolutions *ContractResolutions, height uint32,
htlcActions ChainActionMap) ([]ContractResolver, error) {
// We'll also fetch the historical state of this channel, as it should
// have been marked as closed by now, and supplement it to each resolver
// such that we can properly resolve our pending contracts.
var chanState *channeldb.OpenChannel
chanState, err := c.cfg.FetchHistoricalChannel()
switch {
// If we don't find this channel, then it may be the case that it
// was closed before we started to retain the final state
// information for open channels.
case err == channeldb.ErrNoHistoricalBucket:
fallthrough
case err == channeldb.ErrChannelNotFound:
log.Warnf("ChannelArbitrator(%v): unable to fetch historical "+
"state", c.cfg.ChanPoint)
case err != nil:
return nil, err
}
incomingResolutions := contractResolutions.HtlcResolutions.IncomingHTLCs
outgoingResolutions := contractResolutions.HtlcResolutions.OutgoingHTLCs
// We'll use these two maps to quickly look up an active HTLC with its
// matching HTLC resolution.
outResolutionMap := make(map[wire.OutPoint]lnwallet.OutgoingHtlcResolution)
inResolutionMap := make(map[wire.OutPoint]lnwallet.IncomingHtlcResolution)
for i := 0; i < len(incomingResolutions); i++ {
inRes := incomingResolutions[i]
inResolutionMap[inRes.HtlcPoint()] = inRes
}
for i := 0; i < len(outgoingResolutions); i++ {
outRes := outgoingResolutions[i]
outResolutionMap[outRes.HtlcPoint()] = outRes
}
// We'll create the resolver kit that we'll be cloning for each
// resolver so they each can do their duty.
resolverCfg := ResolverConfig{
ChannelArbitratorConfig: c.cfg,
Checkpoint: func(res ContractResolver,
reports ...*channeldb.ResolverReport) error {
return c.log.InsertUnresolvedContracts(reports, res)
},
}
commitHash := contractResolutions.CommitHash
var htlcResolvers []ContractResolver
// We instantiate an anchor resolver if the commitment tx has an
// anchor.
if contractResolutions.AnchorResolution != nil {
anchorResolver := newAnchorResolver(
contractResolutions.AnchorResolution.AnchorSignDescriptor,
contractResolutions.AnchorResolution.CommitAnchor,
height, c.cfg.ChanPoint, resolverCfg,
)
anchorResolver.SupplementState(chanState)
htlcResolvers = append(htlcResolvers, anchorResolver)
}
// If this is a breach close, we'll create a breach resolver, determine
// the htlc's to fail back, and exit. This is done because the other
// steps taken for non-breach-closes do not matter for breach-closes.
if contractResolutions.BreachResolution != nil {
breachResolver := newBreachResolver(resolverCfg)
htlcResolvers = append(htlcResolvers, breachResolver)
return htlcResolvers, nil
}
// For each HTLC, we'll either act immediately, meaning we'll instantly
// fail the HTLC, or we'll act only once the transaction has been
// confirmed, in which case we'll need an HTLC resolver.
for htlcAction, htlcs := range htlcActions {
switch htlcAction {
// If we can claim this HTLC, we'll create an HTLC resolver to
// claim the HTLC (second-level or directly), then add the pre
case HtlcClaimAction:
for _, htlc := range htlcs {
htlc := htlc
htlcOp := wire.OutPoint{
Hash: commitHash,
Index: uint32(htlc.OutputIndex),
}
resolution, ok := inResolutionMap[htlcOp]
if !ok {
// TODO(roasbeef): panic?
log.Errorf("ChannelArbitrator(%v) unable to find "+
"incoming resolution: %v",
c.cfg.ChanPoint, htlcOp)
continue
}
resolver := newSuccessResolver(
resolution, height, htlc, resolverCfg,
)
if chanState != nil {
resolver.SupplementState(chanState)
}
htlcResolvers = append(htlcResolvers, resolver)
}
// If we can timeout the HTLC directly, then we'll create the
// proper resolver to do so, who will then cancel the packet
// backwards.
case HtlcTimeoutAction:
for _, htlc := range htlcs {
htlc := htlc
htlcOp := wire.OutPoint{
Hash: commitHash,
Index: uint32(htlc.OutputIndex),
}
resolution, ok := outResolutionMap[htlcOp]
if !ok {
log.Errorf("ChannelArbitrator(%v) unable to find "+
"outgoing resolution: %v", c.cfg.ChanPoint, htlcOp)
continue
}
resolver := newTimeoutResolver(
resolution, height, htlc, resolverCfg,
)
if chanState != nil {
resolver.SupplementState(chanState)
}
// For outgoing HTLCs, we will also need to
// supplement the resolver with the expiry
// block height of its corresponding incoming
// HTLC.
deadline := c.cfg.FindOutgoingHTLCDeadline(htlc)
resolver.SupplementDeadline(deadline)
htlcResolvers = append(htlcResolvers, resolver)
}
// If this is an incoming HTLC, but we can't act yet, then
// we'll create an incoming resolver to redeem the HTLC if we
// learn of the pre-image, or let the remote party time out.
case HtlcIncomingWatchAction:
for _, htlc := range htlcs {
htlc := htlc
htlcOp := wire.OutPoint{
Hash: commitHash,
Index: uint32(htlc.OutputIndex),
}
// TODO(roasbeef): need to handle incoming dust...
// TODO(roasbeef): can't be negative!!!
resolution, ok := inResolutionMap[htlcOp]
if !ok {
log.Errorf("ChannelArbitrator(%v) unable to find "+
"incoming resolution: %v",
c.cfg.ChanPoint, htlcOp)
continue
}
resolver := newIncomingContestResolver(
resolution, height, htlc,
resolverCfg,
)
if chanState != nil {
resolver.SupplementState(chanState)
}
htlcResolvers = append(htlcResolvers, resolver)
}
// Finally, if this is an outgoing HTLC we've sent, then we'll
// launch a resolver to watch for the pre-image (and settle
// backwards), or just timeout.
case HtlcOutgoingWatchAction:
for _, htlc := range htlcs {
htlc := htlc
htlcOp := wire.OutPoint{
Hash: commitHash,
Index: uint32(htlc.OutputIndex),
}
resolution, ok := outResolutionMap[htlcOp]
if !ok {
log.Errorf("ChannelArbitrator(%v) "+
"unable to find outgoing "+
"resolution: %v",
c.cfg.ChanPoint, htlcOp)
continue
}
resolver := newOutgoingContestResolver(
resolution, height, htlc, resolverCfg,
)
if chanState != nil {
resolver.SupplementState(chanState)
}
// For outgoing HTLCs, we will also need to
// supplement the resolver with the expiry
// block height of its corresponding incoming
// HTLC.
deadline := c.cfg.FindOutgoingHTLCDeadline(htlc)
resolver.SupplementDeadline(deadline)
htlcResolvers = append(htlcResolvers, resolver)
}
}
}
// If this is was an unilateral closure, then we'll also create a
// resolver to sweep our commitment output (but only if it wasn't
// trimmed).
if contractResolutions.CommitResolution != nil {
resolver := newCommitSweepResolver(
*contractResolutions.CommitResolution, height,
c.cfg.ChanPoint, resolverCfg,
)
if chanState != nil {
resolver.SupplementState(chanState)
}
htlcResolvers = append(htlcResolvers, resolver)
}
return htlcResolvers, nil
}
// replaceResolver replaces a in the list of active resolvers. If the resolver
// to be replaced is not found, it returns an error.
func (c *ChannelArbitrator) replaceResolver(oldResolver,
newResolver ContractResolver) error {
c.activeResolversLock.Lock()
defer c.activeResolversLock.Unlock()
oldKey := oldResolver.ResolverKey()
for i, r := range c.activeResolvers {
if bytes.Equal(r.ResolverKey(), oldKey) {
c.activeResolvers[i] = newResolver
return nil
}
}
return errors.New("resolver to be replaced not found")
}
// resolveContract is a goroutine tasked with fully resolving an unresolved
// contract. Either the initial contract will be resolved after a single step,
// or the contract will itself create another contract to be resolved. In
// either case, one the contract has been fully resolved, we'll signal back to
// the main goroutine so it can properly keep track of the set of unresolved
// contracts.
//
// NOTE: This MUST be run as a goroutine.
func (c *ChannelArbitrator) resolveContract(currentContract ContractResolver) {
defer c.wg.Done()
log.Tracef("ChannelArbitrator(%v): attempting to resolve %T",
c.cfg.ChanPoint, currentContract)
// Until the contract is fully resolved, we'll continue to iteratively
// resolve the contract one step at a time.
for !currentContract.IsResolved() {
log.Tracef("ChannelArbitrator(%v): contract %T not yet "+
"resolved", c.cfg.ChanPoint, currentContract)
select {
// If we've been signalled to quit, then we'll exit early.
case <-c.quit:
return
default:
// Otherwise, we'll attempt to resolve the current
// contract.
nextContract, err := currentContract.Resolve()
if err != nil {
if err == errResolverShuttingDown {
return
}
log.Errorf("ChannelArbitrator(%v): unable to "+
"progress %T: %v",
c.cfg.ChanPoint, currentContract, err)
return
}
switch {
// If this contract produced another, then this means
// the current contract was only able to be partially
// resolved in this step. So we'll do a contract swap
// within our logs: the new contract will take the
// place of the old one.
case nextContract != nil:
log.Debugf("ChannelArbitrator(%v): swapping "+
"out contract %T for %T ",
c.cfg.ChanPoint, currentContract,
nextContract)
// Swap contract in log.
err := c.log.SwapContract(
currentContract, nextContract,
)
if err != nil {
log.Errorf("unable to add recurse "+
"contract: %v", err)
}
// Swap contract in resolvers list. This is to
// make sure that reports are queried from the
// new resolver.
err = c.replaceResolver(
currentContract, nextContract,
)
if err != nil {
log.Errorf("unable to replace "+
"contract: %v", err)
}
// As this contract produced another, we'll
// re-assign, so we can continue our resolution
// loop.
currentContract = nextContract
// Launch the new contract.
err = currentContract.Launch()
if err != nil {
log.Errorf("Failed to launch %T: %v",
currentContract, err)
}
// If this contract is actually fully resolved, then
// we'll mark it as such within the database.
case currentContract.IsResolved():
log.Debugf("ChannelArbitrator(%v): marking "+
"contract %T fully resolved",
c.cfg.ChanPoint, currentContract)
err := c.log.ResolveContract(currentContract)
if err != nil {
log.Errorf("unable to resolve contract: %v",
err)
}
// Now that the contract has been resolved,
// well signal to the main goroutine.
select {
case c.resolutionSignal <- struct{}{}:
case <-c.quit:
return
}
}
}
}
}
// signalUpdateMsg is a struct that carries fresh signals to the
// ChannelArbitrator. We need to receive a message like this each time the
// channel becomes active, as it's internal state may change.
type signalUpdateMsg struct {
// newSignals is the set of new active signals to be sent to the
// arbitrator.
newSignals *ContractSignals
// doneChan is a channel that will be closed on the arbitrator has
// attached the new signals.
doneChan chan struct{}
}
// UpdateContractSignals updates the set of signals the ChannelArbitrator needs
// to receive from a channel in real-time in order to keep in sync with the
// latest state of the contract.
func (c *ChannelArbitrator) UpdateContractSignals(newSignals *ContractSignals) {
done := make(chan struct{})
select {
case c.signalUpdates <- &signalUpdateMsg{
newSignals: newSignals,
doneChan: done,
}:
case <-c.quit:
}
select {
case <-done:
case <-c.quit:
}
}
// notifyContractUpdate updates the ChannelArbitrator's unmerged mappings such
// that it can later be merged with activeHTLCs when calling
// checkLocalChainActions or sweepAnchors. These are the only two places that
// activeHTLCs is used.
func (c *ChannelArbitrator) notifyContractUpdate(upd *ContractUpdate) {
c.unmergedMtx.Lock()
defer c.unmergedMtx.Unlock()
// Update the mapping.
c.unmergedSet[upd.HtlcKey] = newHtlcSet(upd.Htlcs)
log.Tracef("ChannelArbitrator(%v): fresh set of htlcs=%v",
c.cfg.ChanPoint, lnutils.SpewLogClosure(upd))
}
// updateActiveHTLCs merges the unmerged set of HTLCs from the link with
// activeHTLCs.
func (c *ChannelArbitrator) updateActiveHTLCs() {
c.unmergedMtx.RLock()
defer c.unmergedMtx.RUnlock()
// Update the mapping.
c.activeHTLCs[LocalHtlcSet] = c.unmergedSet[LocalHtlcSet]
c.activeHTLCs[RemoteHtlcSet] = c.unmergedSet[RemoteHtlcSet]
// If the pending set exists, update that as well.
if _, ok := c.unmergedSet[RemotePendingHtlcSet]; ok {
pendingSet := c.unmergedSet[RemotePendingHtlcSet]
c.activeHTLCs[RemotePendingHtlcSet] = pendingSet
}
}
// channelAttendant is the primary goroutine that acts at the judicial
// arbitrator between our channel state, the remote channel peer, and the
// blockchain (Our judge). This goroutine will ensure that we faithfully execute
// all clauses of our contract in the case that we need to go on-chain for a
// dispute. Currently, two such conditions warrant our intervention: when an
// outgoing HTLC is about to timeout, and when we know the pre-image for an
// incoming HTLC, but it hasn't yet been settled off-chain. In these cases,
// we'll: broadcast our commitment, cancel/settle any HTLC's backwards after
// sufficient confirmation, and finally send our set of outputs to the UTXO
// Nursery for incubation, and ultimate sweeping.
//
// NOTE: This MUST be run as a goroutine.
func (c *ChannelArbitrator) channelAttendant(bestHeight int32,
commitSet *CommitSet) {
// TODO(roasbeef): tell top chain arb we're done
defer func() {
c.wg.Done()
}()
err := c.progressStateMachineAfterRestart(bestHeight, commitSet)
if err != nil {
// In case of an error, we return early but we do not shutdown
// LND, because there might be other channels that still can be
// resolved and we don't want to interfere with that.
// We continue to run the channel attendant in case the channel
// closes via other means for example the remote pary force
// closes the channel. So we log the error and continue.
log.Errorf("Unable to progress state machine after "+
"restart: %v", err)
}
for {
select {
// A new block has arrived, we'll examine all the active HTLC's
// to see if any of them have expired, and also update our
// track of the best current height.
case beat := <-c.BlockbeatChan:
bestHeight = beat.Height()
log.Debugf("ChannelArbitrator(%v): received new "+
"block: height=%v, processing...",
c.cfg.ChanPoint, bestHeight)
err := c.handleBlockbeat(beat)
if err != nil {
log.Errorf("Handle block=%v got err: %v",
bestHeight, err)
}
// If as a result of this trigger, the contract is
// fully resolved, then well exit.
if c.state == StateFullyResolved {
return
}
// A new signal update was just sent. This indicates that the
// channel under watch is now live, and may modify its internal
// state, so we'll get the most up to date signals to we can
// properly do our job.
case signalUpdate := <-c.signalUpdates:
log.Tracef("ChannelArbitrator(%v): got new signal "+
"update!", c.cfg.ChanPoint)
// We'll update the ShortChannelID.
c.cfg.ShortChanID = signalUpdate.newSignals.ShortChanID
// Now that the signal has been updated, we'll now
// close the done channel to signal to the caller we've
// registered the new ShortChannelID.
close(signalUpdate.doneChan)
// We've cooperatively closed the channel, so we're no longer
// needed. We'll mark the channel as resolved and exit.
case closeInfo := <-c.cfg.ChainEvents.CooperativeClosure:
err := c.handleCoopCloseEvent(closeInfo)
if err != nil {
log.Errorf("Failed to handle coop close: %v",
err)
return
}
// We have broadcasted our commitment, and it is now confirmed
// on-chain.
case closeInfo := <-c.cfg.ChainEvents.LocalUnilateralClosure:
if c.state != StateCommitmentBroadcasted {
log.Errorf("ChannelArbitrator(%v): unexpected "+
"local on-chain channel close",
c.cfg.ChanPoint)
}
err := c.handleLocalForceCloseEvent(closeInfo)
if err != nil {
log.Errorf("Failed to handle local force "+
"close: %v", err)
return
}
// The remote party has broadcast the commitment on-chain.
// We'll examine our state to determine if we need to act at
// all.
case uniClosure := <-c.cfg.ChainEvents.RemoteUnilateralClosure:
err := c.handleRemoteForceCloseEvent(uniClosure)
if err != nil {
log.Errorf("Failed to handle remote force "+
"close: %v", err)
return
}
// The remote has breached the channel. As this is handled by
// the ChainWatcher and BreachArbitrator, we don't have to do
// anything in particular, so just advance our state and
// gracefully exit.
case breachInfo := <-c.cfg.ChainEvents.ContractBreach:
err := c.handleContractBreach(breachInfo)
if err != nil {
log.Errorf("Failed to handle contract breach: "+
"%v", err)
return
}
// A new contract has just been resolved, we'll now check our
// log to see if all contracts have been resolved. If so, then
// we can exit as the contract is fully resolved.
case <-c.resolutionSignal:
log.Infof("ChannelArbitrator(%v): a contract has been "+
"fully resolved!", c.cfg.ChanPoint)
nextState, _, err := c.advanceState(
uint32(bestHeight), chainTrigger, nil,
)
if err != nil {
log.Errorf("Unable to advance state: %v", err)
}
// If we don't have anything further to do after
// advancing our state, then we'll exit.
if nextState == StateFullyResolved {
log.Infof("ChannelArbitrator(%v): all "+
"contracts fully resolved, exiting",
c.cfg.ChanPoint)
return
}
// We've just received a request to forcibly close out the
// channel. We'll
case closeReq := <-c.forceCloseReqs:
log.Infof("ChannelArbitrator(%v): received force "+
"close request", c.cfg.ChanPoint)
if c.state != StateDefault {
select {
case closeReq.closeTx <- nil:
case <-c.quit:
}
select {
case closeReq.errResp <- errAlreadyForceClosed:
case <-c.quit:
}
continue
}
nextState, closeTx, err := c.advanceState(
uint32(bestHeight), userTrigger, nil,
)
if err != nil {
log.Errorf("Unable to advance state: %v", err)
}
select {
case closeReq.closeTx <- closeTx:
case <-c.quit:
return
}
select {
case closeReq.errResp <- err:
case <-c.quit:
return
}
// If we don't have anything further to do after
// advancing our state, then we'll exit.
if nextState == StateFullyResolved {
log.Infof("ChannelArbitrator(%v): all "+
"contracts resolved, exiting",
c.cfg.ChanPoint)
return
}
case <-c.quit:
return
}
}
}
// handleBlockbeat processes a newly received blockbeat by advancing the
// arbitrator's internal state using the received block height.
func (c *ChannelArbitrator) handleBlockbeat(beat chainio.Blockbeat) error {
// Notify we've processed the block.
defer c.NotifyBlockProcessed(beat, nil)
// If the state is StateContractClosed, StateWaitingFullResolution, or
// StateFullyResolved, there's no need to read the close event channel
// since the arbitrator can only get to this state after processing a
// previous close event and launched all its resolvers.
if c.state.IsContractClosed() {
log.Infof("ChannelArbitrator(%v): skipping reading close "+
"events in state=%v", c.cfg.ChanPoint, c.state)
// Launch all active resolvers when a new blockbeat is
// received, even when the contract is closed, we still need
// this as the resolvers may transform into new ones. For
// already launched resolvers this will be NOOP as they track
// their own `launched` states.
c.launchResolvers()
return nil
}
// Perform a non-blocking read on the close events in case the channel
// is closed in this blockbeat.
c.receiveAndProcessCloseEvent()
// Try to advance the state if we are in StateDefault.
if c.state == StateDefault {
// Now that a new block has arrived, we'll attempt to advance
// our state forward.
_, _, err := c.advanceState(
uint32(beat.Height()), chainTrigger, nil,
)
if err != nil {
return fmt.Errorf("unable to advance state: %w", err)
}
}
// Launch all active resolvers when a new blockbeat is received.
c.launchResolvers()
return nil
}
// receiveAndProcessCloseEvent does a non-blocking read on all the channel
// close event channels. If an event is received, it will be further processed.
func (c *ChannelArbitrator) receiveAndProcessCloseEvent() {
select {
// Received a coop close event, we now mark the channel as resolved and
// exit.
case closeInfo := <-c.cfg.ChainEvents.CooperativeClosure:
err := c.handleCoopCloseEvent(closeInfo)
if err != nil {
log.Errorf("Failed to handle coop close: %v", err)
return
}
// We have broadcast our commitment, and it is now confirmed onchain.
case closeInfo := <-c.cfg.ChainEvents.LocalUnilateralClosure:
if c.state != StateCommitmentBroadcasted {
log.Errorf("ChannelArbitrator(%v): unexpected "+
"local on-chain channel close", c.cfg.ChanPoint)
}
err := c.handleLocalForceCloseEvent(closeInfo)
if err != nil {
log.Errorf("Failed to handle local force close: %v",
err)
return
}
// The remote party has broadcast the commitment. We'll examine our
// state to determine if we need to act at all.
case uniClosure := <-c.cfg.ChainEvents.RemoteUnilateralClosure:
err := c.handleRemoteForceCloseEvent(uniClosure)
if err != nil {
log.Errorf("Failed to handle remote force close: %v",
err)
return
}
// The remote has breached the channel! We now launch the breach
// contract resolvers.
case breachInfo := <-c.cfg.ChainEvents.ContractBreach:
err := c.handleContractBreach(breachInfo)
if err != nil {
log.Errorf("Failed to handle contract breach: %v", err)
return
}
default:
log.Infof("ChannelArbitrator(%v) no close event",
c.cfg.ChanPoint)
}
}
// Name returns a human-readable string for this subsystem.
//
// NOTE: Part of chainio.Consumer interface.
func (c *ChannelArbitrator) Name() string {
return fmt.Sprintf("ChannelArbitrator(%v)", c.cfg.ChanPoint)
}
// checkLegacyBreach returns StateFullyResolved if the channel was closed with
// a breach transaction before the channel arbitrator launched its own breach
// resolver. StateContractClosed is returned if this is a modern breach close
// with a breach resolver. StateError is returned if the log lookup failed.
func (c *ChannelArbitrator) checkLegacyBreach() (ArbitratorState, error) {
// A previous version of the channel arbitrator would make the breach
// close skip to StateFullyResolved. If there are no contract
// resolutions in the bolt arbitrator log, then this is an older breach
// close. Otherwise, if there are resolutions, the state should advance
// to StateContractClosed.
_, err := c.log.FetchContractResolutions()
if err == errNoResolutions {
// This is an older breach close still in the database.
return StateFullyResolved, nil
} else if err != nil {
return StateError, err
}
// This is a modern breach close with resolvers.
return StateContractClosed, nil
}
// sweepRequest wraps the arguments used when calling `SweepInput`.
type sweepRequest struct {
// input is the input to be swept.
input input.Input
// params holds the sweeping parameters.
params sweep.Params
}
// createSweepRequest creates an anchor sweeping request for a particular
// version (local/remote/remote pending) of the commitment.
func (c *ChannelArbitrator) createSweepRequest(
anchor *lnwallet.AnchorResolution, htlcs htlcSet, anchorPath string,
heightHint uint32) (sweepRequest, error) {
// Use the chan id as the exclusive group. This prevents any of the
// anchors from being batched together.
exclusiveGroup := c.cfg.ShortChanID.ToUint64()
// Find the deadline for this specific anchor.
deadline, value, err := c.findCommitmentDeadlineAndValue(
heightHint, htlcs,
)
if err != nil {
return sweepRequest{}, err
}
// If we cannot find a deadline, it means there's no HTLCs at stake,
// which means we can relax our anchor sweeping conditions as we don't
// have any time sensitive outputs to sweep. However we need to
// register the anchor output with the sweeper so we are later able to
// bump the close fee.
if deadline.IsNone() {
log.Infof("ChannelArbitrator(%v): no HTLCs at stake, "+
"sweeping anchor with default deadline",
c.cfg.ChanPoint)
}
witnessType := input.CommitmentAnchor
// For taproot channels, we need to use the proper witness type.
if txscript.IsPayToTaproot(
anchor.AnchorSignDescriptor.Output.PkScript,
) {
witnessType = input.TaprootAnchorSweepSpend
}
// Prepare anchor output for sweeping.
anchorInput := input.MakeBaseInput(
&anchor.CommitAnchor,
witnessType,
&anchor.AnchorSignDescriptor,
heightHint,
&input.TxInfo{
Fee: anchor.CommitFee,
Weight: anchor.CommitWeight,
},
)
// If we have a deadline, we'll use it to calculate the deadline
// height, otherwise default to none.
deadlineDesc := "None"
deadlineHeight := fn.MapOption(func(d int32) int32 {
deadlineDesc = fmt.Sprintf("%d", d)
return d + int32(heightHint)
})(deadline)
// Calculate the budget based on the value under protection, which is
// the sum of all HTLCs on this commitment subtracted by their budgets.
// The anchor output in itself has a small output value of 330 sats so
// we also include it in the budget to pay for the cpfp transaction.
budget := calculateBudget(
value, c.cfg.Budget.AnchorCPFPRatio, c.cfg.Budget.AnchorCPFP,
) + AnchorOutputValue
log.Infof("ChannelArbitrator(%v): offering anchor from %s commitment "+
"%v to sweeper with deadline=%v, budget=%v", c.cfg.ChanPoint,
anchorPath, anchor.CommitAnchor, deadlineDesc, budget)
// Sweep anchor output with a confirmation target fee preference.
// Because this is a cpfp-operation, the anchor will only be attempted
// to sweep when the current fee estimate for the confirmation target
// exceeds the commit fee rate.
return sweepRequest{
input: &anchorInput,
params: sweep.Params{
ExclusiveGroup: &exclusiveGroup,
Budget: budget,
DeadlineHeight: deadlineHeight,
},
}, nil
}
// prepareAnchorSweeps creates a list of requests to be used by the sweeper for
// all possible commitment versions.
func (c *ChannelArbitrator) prepareAnchorSweeps(heightHint uint32,
anchors *lnwallet.AnchorResolutions) ([]sweepRequest, error) {
// requests holds all the possible anchor sweep requests. We can have
// up to 3 different versions of commitments (local/remote/remote
// dangling) to be CPFPed by the anchors.
requests := make([]sweepRequest, 0, 3)
// remotePendingReq holds the request for sweeping the anchor output on
// the remote pending commitment. It's only set when there's an actual
// pending remote commitment and it's used to decide whether we need to
// update the fee budget when sweeping the anchor output on the local
// commitment.
remotePendingReq := fn.None[sweepRequest]()
// First we check on the remote pending commitment and optionally
// create an anchor sweeping request.
htlcs, ok := c.activeHTLCs[RemotePendingHtlcSet]
if ok && anchors.RemotePending != nil {
req, err := c.createSweepRequest(
anchors.RemotePending, htlcs, "remote pending",
heightHint,
)
if err != nil {
return nil, err
}
// Save the request.
requests = append(requests, req)
// Set the optional variable.
remotePendingReq = fn.Some(req)
}
// Check the local commitment and optionally create an anchor sweeping
// request. The params used in this request will be influenced by the
// anchor sweeping request made from the pending remote commitment.
htlcs, ok = c.activeHTLCs[LocalHtlcSet]
if ok && anchors.Local != nil {
req, err := c.createSweepRequest(
anchors.Local, htlcs, "local", heightHint,
)
if err != nil {
return nil, err
}
// If there's an anchor sweeping request from the pending
// remote commitment, we will compare its budget against the
// budget used here and choose the params that has a larger
// budget. The deadline when choosing the remote pending budget
// instead of the local one will always be earlier or equal to
// the local deadline because outgoing HTLCs are resolved on
// the local commitment first before they are removed from the
// remote one.
remotePendingReq.WhenSome(func(s sweepRequest) {
if s.params.Budget <= req.params.Budget {
return
}
log.Infof("ChannelArbitrator(%v): replaced local "+
"anchor(%v) sweep params with pending remote "+
"anchor sweep params, \nold:[%v], \nnew:[%v]",
c.cfg.ChanPoint, anchors.Local.CommitAnchor,
req.params, s.params)
req.params = s.params
})
// Save the request.
requests = append(requests, req)
}
// Check the remote commitment and create an anchor sweeping request if
// needed.
htlcs, ok = c.activeHTLCs[RemoteHtlcSet]
if ok && anchors.Remote != nil {
req, err := c.createSweepRequest(
anchors.Remote, htlcs, "remote", heightHint,
)
if err != nil {
return nil, err
}
requests = append(requests, req)
}
return requests, nil
}
// failIncomingDust resolves the incoming dust HTLCs because they do not have
// an output on the commitment transaction and cannot be resolved onchain. We
// mark them as failed here.
func (c *ChannelArbitrator) failIncomingDust(
incomingDustHTLCs []channeldb.HTLC) error {
for _, htlc := range incomingDustHTLCs {
if !htlc.Incoming || htlc.OutputIndex >= 0 {
return fmt.Errorf("htlc with index %v is not incoming "+
"dust", htlc.OutputIndex)
}
key := models.CircuitKey{
ChanID: c.cfg.ShortChanID,
HtlcID: htlc.HtlcIndex,
}
// Mark this dust htlc as final failed.
chainArbCfg := c.cfg.ChainArbitratorConfig
err := chainArbCfg.PutFinalHtlcOutcome(
key.ChanID, key.HtlcID, false,
)
if err != nil {
return err
}
// Send notification.
chainArbCfg.HtlcNotifier.NotifyFinalHtlcEvent(
key,
channeldb.FinalHtlcInfo{
Settled: false,
Offchain: false,
},
)
}
return nil
}
// abandonForwards cancels back the incoming HTLCs for their corresponding
// outgoing HTLCs. We use a set here to avoid sending duplicate failure messages
// for the same HTLC. This also needs to be done for locally initiated outgoing
// HTLCs they are special cased in the switch.
func (c *ChannelArbitrator) abandonForwards(htlcs fn.Set[uint64]) error {
log.Debugf("ChannelArbitrator(%v): cancelling back %v incoming "+
"HTLC(s)", c.cfg.ChanPoint,
len(htlcs))
msgsToSend := make([]ResolutionMsg, 0, len(htlcs))
failureMsg := &lnwire.FailPermanentChannelFailure{}
for idx := range htlcs {
failMsg := ResolutionMsg{
SourceChan: c.cfg.ShortChanID,
HtlcIndex: idx,
Failure: failureMsg,
}
msgsToSend = append(msgsToSend, failMsg)
}
// Send the msges to the switch, if there are any.
if len(msgsToSend) == 0 {
return nil
}
log.Debugf("ChannelArbitrator(%v): sending resolution message=%v",
c.cfg.ChanPoint, lnutils.SpewLogClosure(msgsToSend))
err := c.cfg.DeliverResolutionMsg(msgsToSend...)
if err != nil {
log.Errorf("Unable to send resolution msges to switch: %v", err)
return err
}
return nil
}
// handleCoopCloseEvent takes a coop close event from ChainEvents, marks the
// channel as closed and advances the state.
func (c *ChannelArbitrator) handleCoopCloseEvent(
closeInfo *CooperativeCloseInfo) error {
log.Infof("ChannelArbitrator(%v) marking channel cooperatively closed "+
"at height %v", c.cfg.ChanPoint, closeInfo.CloseHeight)
err := c.cfg.MarkChannelClosed(
closeInfo.ChannelCloseSummary,
channeldb.ChanStatusCoopBroadcasted,
)
if err != nil {
return fmt.Errorf("unable to mark channel closed: %w", err)
}
// We'll now advance our state machine until it reaches a terminal
// state, and the channel is marked resolved.
_, _, err = c.advanceState(closeInfo.CloseHeight, coopCloseTrigger, nil)
if err != nil {
log.Errorf("Unable to advance state: %v", err)
}
return nil
}
// handleLocalForceCloseEvent takes a local force close event from ChainEvents,
// saves the contract resolutions to disk, mark the channel as closed and
// advance the state.
func (c *ChannelArbitrator) handleLocalForceCloseEvent(
closeInfo *LocalUnilateralCloseInfo) error {
closeTx := closeInfo.CloseTx
resolutions, err := closeInfo.ContractResolutions.
UnwrapOrErr(
fmt.Errorf("resolutions not found"),
)
if err != nil {
return fmt.Errorf("unable to get resolutions: %w", err)
}
// We make sure that the htlc resolutions are present
// otherwise we would panic dereferencing the pointer.
//
// TODO(ziggie): Refactor ContractResolutions to use
// options.
if resolutions.HtlcResolutions == nil {
return fmt.Errorf("htlc resolutions is nil")
}
log.Infof("ChannelArbitrator(%v): local force close tx=%v confirmed",
c.cfg.ChanPoint, closeTx.TxHash())
contractRes := &ContractResolutions{
CommitHash: closeTx.TxHash(),
CommitResolution: resolutions.CommitResolution,
HtlcResolutions: *resolutions.HtlcResolutions,
AnchorResolution: resolutions.AnchorResolution,
}
// When processing a unilateral close event, we'll transition to the
// ContractClosed state. We'll log out the set of resolutions such that
// they are available to fetch in that state, we'll also write the
// commit set so we can reconstruct our chain actions on restart.
err = c.log.LogContractResolutions(contractRes)
if err != nil {
return fmt.Errorf("unable to write resolutions: %w", err)
}
err = c.log.InsertConfirmedCommitSet(&closeInfo.CommitSet)
if err != nil {
return fmt.Errorf("unable to write commit set: %w", err)
}
// After the set of resolutions are successfully logged, we can safely
// close the channel. After this succeeds we won't be getting chain
// events anymore, so we must make sure we can recover on restart after
// it is marked closed. If the next state transition fails, we'll start
// up in the prior state again, and we won't be longer getting chain
// events. In this case we must manually re-trigger the state
// transition into StateContractClosed based on the close status of the
// channel.
err = c.cfg.MarkChannelClosed(
closeInfo.ChannelCloseSummary,
channeldb.ChanStatusLocalCloseInitiator,
)
if err != nil {
return fmt.Errorf("unable to mark channel closed: %w", err)
}
// We'll now advance our state machine until it reaches a terminal
// state.
_, _, err = c.advanceState(
uint32(closeInfo.SpendingHeight),
localCloseTrigger, &closeInfo.CommitSet,
)
if err != nil {
log.Errorf("Unable to advance state: %v", err)
}
return nil
}
// handleRemoteForceCloseEvent takes a remote force close event from
// ChainEvents, saves the contract resolutions to disk, mark the channel as
// closed and advance the state.
func (c *ChannelArbitrator) handleRemoteForceCloseEvent(
closeInfo *RemoteUnilateralCloseInfo) error {
log.Infof("ChannelArbitrator(%v): remote party has force closed "+
"channel at height %v", c.cfg.ChanPoint,
closeInfo.SpendingHeight)
// If we don't have a self output, and there are no active HTLC's, then
// we can immediately mark the contract as fully resolved and exit.
contractRes := &ContractResolutions{
CommitHash: *closeInfo.SpenderTxHash,
CommitResolution: closeInfo.CommitResolution,
HtlcResolutions: *closeInfo.HtlcResolutions,
AnchorResolution: closeInfo.AnchorResolution,
}
// When processing a unilateral close event, we'll transition to the
// ContractClosed state. We'll log out the set of resolutions such that
// they are available to fetch in that state, we'll also write the
// commit set so we can reconstruct our chain actions on restart.
err := c.log.LogContractResolutions(contractRes)
if err != nil {
return fmt.Errorf("unable to write resolutions: %w", err)
}
err = c.log.InsertConfirmedCommitSet(&closeInfo.CommitSet)
if err != nil {
return fmt.Errorf("unable to write commit set: %w", err)
}
// After the set of resolutions are successfully logged, we can safely
// close the channel. After this succeeds we won't be getting chain
// events anymore, so we must make sure we can recover on restart after
// it is marked closed. If the next state transition fails, we'll start
// up in the prior state again, and we won't be longer getting chain
// events. In this case we must manually re-trigger the state
// transition into StateContractClosed based on the close status of the
// channel.
closeSummary := &closeInfo.ChannelCloseSummary
err = c.cfg.MarkChannelClosed(
closeSummary,
channeldb.ChanStatusRemoteCloseInitiator,
)
if err != nil {
return fmt.Errorf("unable to mark channel closed: %w", err)
}
// We'll now advance our state machine until it reaches a terminal
// state.
_, _, err = c.advanceState(
uint32(closeInfo.SpendingHeight),
remoteCloseTrigger, &closeInfo.CommitSet,
)
if err != nil {
log.Errorf("Unable to advance state: %v", err)
}
return nil
}
// handleContractBreach takes a breach close event from ChainEvents, saves the
// contract resolutions to disk, mark the channel as closed and advance the
// state.
func (c *ChannelArbitrator) handleContractBreach(
breachInfo *BreachCloseInfo) error {
closeSummary := &breachInfo.CloseSummary
log.Infof("ChannelArbitrator(%v): remote party has breached channel "+
"at height %v!", c.cfg.ChanPoint, closeSummary.CloseHeight)
// In the breach case, we'll only have anchor and breach resolutions.
contractRes := &ContractResolutions{
CommitHash: breachInfo.CommitHash,
BreachResolution: breachInfo.BreachResolution,
AnchorResolution: breachInfo.AnchorResolution,
}
// We'll transition to the ContractClosed state and log the set of
// resolutions such that they can be turned into resolvers later on.
// We'll also insert the CommitSet of the latest set of commitments.
err := c.log.LogContractResolutions(contractRes)
if err != nil {
return fmt.Errorf("unable to write resolutions: %w", err)
}
err = c.log.InsertConfirmedCommitSet(&breachInfo.CommitSet)
if err != nil {
return fmt.Errorf("unable to write commit set: %w", err)
}
// The channel is finally marked pending closed here as the
// BreachArbitrator and channel arbitrator have persisted the relevant
// states.
err = c.cfg.MarkChannelClosed(
closeSummary, channeldb.ChanStatusRemoteCloseInitiator,
)
if err != nil {
return fmt.Errorf("unable to mark channel closed: %w", err)
}
log.Infof("Breached channel=%v marked pending-closed",
breachInfo.BreachResolution.FundingOutPoint)
// We'll advance our state machine until it reaches a terminal state.
_, _, err = c.advanceState(
closeSummary.CloseHeight, breachCloseTrigger,
&breachInfo.CommitSet,
)
if err != nil {
log.Errorf("Unable to advance state: %v", err)
}
return nil
}
package contractcourt
import (
"encoding/binary"
"fmt"
"io"
"sync"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/sweep"
)
// commitSweepResolver is a resolver that will attempt to sweep the commitment
// output paying to us (local channel balance). In the case that the local
// party (we) broadcasts their version of the commitment transaction, we have
// to wait before sweeping it, as it has a CSV delay. For anchor channel
// type, even if the remote party broadcasts the commitment transaction,
// we have to wait one block after commitment transaction is confirmed,
// because CSV 1 is put into the script of UTXO representing local balance.
// Additionally, if the channel is a channel lease, we have to wait for
// CLTV to expire.
// https://docs.lightning.engineering/lightning-network-tools/pool/overview
type commitSweepResolver struct {
// localChanCfg is used to provide the resolver with the keys required
// to identify whether the commitment transaction was broadcast by the
// local or remote party.
localChanCfg channeldb.ChannelConfig
// commitResolution contains all data required to successfully sweep
// this HTLC on-chain.
commitResolution lnwallet.CommitOutputResolution
// broadcastHeight is the height that the original contract was
// broadcast to the main-chain at. We'll use this value to bound any
// historical queries to the chain for spends/confirmations.
broadcastHeight uint32
// chanPoint is the channel point of the original contract.
chanPoint wire.OutPoint
// channelInitiator denotes whether the party responsible for resolving
// the contract initiated the channel.
channelInitiator bool
// leaseExpiry denotes the additional waiting period the contract must
// hold until it can be resolved. This waiting period is known as the
// expiration of a script-enforced leased channel and only applies to
// the channel initiator.
//
// NOTE: This value should only be set when the contract belongs to a
// leased channel.
leaseExpiry uint32
// chanType denotes the type of channel the contract belongs to.
chanType channeldb.ChannelType
// currentReport stores the current state of the resolver for reporting
// over the rpc interface.
currentReport ContractReport
// reportLock prevents concurrent access to the resolver report.
reportLock sync.Mutex
contractResolverKit
}
// newCommitSweepResolver instantiates a new direct commit output resolver.
func newCommitSweepResolver(res lnwallet.CommitOutputResolution,
broadcastHeight uint32, chanPoint wire.OutPoint,
resCfg ResolverConfig) *commitSweepResolver {
r := &commitSweepResolver{
contractResolverKit: *newContractResolverKit(resCfg),
commitResolution: res,
broadcastHeight: broadcastHeight,
chanPoint: chanPoint,
}
r.initLogger(fmt.Sprintf("%T(%v)", r, r.commitResolution.SelfOutPoint))
r.initReport()
return r
}
// ResolverKey returns an identifier which should be globally unique for this
// particular resolver within the chain the original contract resides within.
func (c *commitSweepResolver) ResolverKey() []byte {
key := newResolverID(c.commitResolution.SelfOutPoint)
return key[:]
}
// waitForSpend waits for the given outpoint to be spent, and returns the
// details of the spending tx.
func waitForSpend(op *wire.OutPoint, pkScript []byte, heightHint uint32,
notifier chainntnfs.ChainNotifier, quit <-chan struct{}) (
*chainntnfs.SpendDetail, error) {
spendNtfn, err := notifier.RegisterSpendNtfn(
op, pkScript, heightHint,
)
if err != nil {
return nil, err
}
select {
case spendDetail, ok := <-spendNtfn.Spend:
if !ok {
return nil, errResolverShuttingDown
}
return spendDetail, nil
case <-quit:
return nil, errResolverShuttingDown
}
}
// getCommitTxConfHeight waits for confirmation of the commitment tx and
// returns the confirmation height.
func (c *commitSweepResolver) getCommitTxConfHeight() (uint32, error) {
txID := c.commitResolution.SelfOutPoint.Hash
signDesc := c.commitResolution.SelfOutputSignDesc
pkScript := signDesc.Output.PkScript
const confDepth = 1
confChan, err := c.Notifier.RegisterConfirmationsNtfn(
&txID, pkScript, confDepth, c.broadcastHeight,
)
if err != nil {
return 0, err
}
defer confChan.Cancel()
select {
case txConfirmation, ok := <-confChan.Confirmed:
if !ok {
return 0, fmt.Errorf("cannot get confirmation "+
"for commit tx %v", txID)
}
return txConfirmation.BlockHeight, nil
case <-c.quit:
return 0, errResolverShuttingDown
}
}
// Resolve instructs the contract resolver to resolve the output on-chain. Once
// the output has been *fully* resolved, the function should return immediately
// with a nil ContractResolver value for the first return value. In the case
// that the contract requires further resolution, then another resolve is
// returned.
//
// NOTE: This function MUST be run as a goroutine.
// TODO(yy): fix the funlen in the next PR.
//
//nolint:funlen
func (c *commitSweepResolver) Resolve() (ContractResolver, error) {
// If we're already resolved, then we can exit early.
if c.IsResolved() {
c.log.Errorf("already resolved")
return nil, nil
}
var sweepTxID chainhash.Hash
// Sweeper is going to join this input with other inputs if possible
// and publish the sweep tx. When the sweep tx confirms, it signals us
// through the result channel with the outcome. Wait for this to
// happen.
outcome := channeldb.ResolverOutcomeClaimed
select {
case sweepResult := <-c.sweepResultChan:
switch sweepResult.Err {
// If the remote party was able to sweep this output it's
// likely what we sent was actually a revoked commitment.
// Report the error and continue to wrap up the contract.
case sweep.ErrRemoteSpend:
c.log.Warnf("local commitment output was swept by "+
"remote party via %v", sweepResult.Tx.TxHash())
outcome = channeldb.ResolverOutcomeUnclaimed
// No errors, therefore continue processing.
case nil:
c.log.Infof("local commitment output fully resolved by "+
"sweep tx: %v", sweepResult.Tx.TxHash())
// Unknown errors.
default:
c.log.Errorf("unable to sweep input: %v",
sweepResult.Err)
return nil, sweepResult.Err
}
sweepTxID = sweepResult.Tx.TxHash()
case <-c.quit:
return nil, errResolverShuttingDown
}
// Funds have been swept and balance is no longer in limbo.
c.reportLock.Lock()
if outcome == channeldb.ResolverOutcomeClaimed {
// We only record the balance as recovered if it actually came
// back to us.
c.currentReport.RecoveredBalance = c.currentReport.LimboBalance
}
c.currentReport.LimboBalance = 0
c.reportLock.Unlock()
report := c.currentReport.resolverReport(
&sweepTxID, channeldb.ResolverTypeCommit, outcome,
)
c.markResolved()
// Checkpoint the resolver with a closure that will write the outcome
// of the resolver and its sweep transaction to disk.
return nil, c.Checkpoint(c, report)
}
// Stop signals the resolver to cancel any current resolution processes, and
// suspend.
//
// NOTE: Part of the ContractResolver interface.
func (c *commitSweepResolver) Stop() {
c.log.Debugf("stopping...")
defer c.log.Debugf("stopped")
close(c.quit)
}
// SupplementState allows the user of a ContractResolver to supplement it with
// state required for the proper resolution of a contract.
//
// NOTE: Part of the ContractResolver interface.
func (c *commitSweepResolver) SupplementState(state *channeldb.OpenChannel) {
if state.ChanType.HasLeaseExpiration() {
c.leaseExpiry = state.ThawHeight
}
c.localChanCfg = state.LocalChanCfg
c.channelInitiator = state.IsInitiator
c.chanType = state.ChanType
}
// hasCLTV denotes whether the resolver must wait for an additional CLTV to
// expire before resolving the contract.
func (c *commitSweepResolver) hasCLTV() bool {
return c.channelInitiator && c.leaseExpiry > 0
}
// Encode writes an encoded version of the ContractResolver into the passed
// Writer.
//
// NOTE: Part of the ContractResolver interface.
func (c *commitSweepResolver) Encode(w io.Writer) error {
if err := encodeCommitResolution(w, &c.commitResolution); err != nil {
return err
}
if err := binary.Write(w, endian, c.IsResolved()); err != nil {
return err
}
if err := binary.Write(w, endian, c.broadcastHeight); err != nil {
return err
}
if _, err := w.Write(c.chanPoint.Hash[:]); err != nil {
return err
}
err := binary.Write(w, endian, c.chanPoint.Index)
if err != nil {
return err
}
// Previously a sweep tx was serialized at this point. Refactoring
// removed this, but keep in mind that this data may still be present in
// the database.
return nil
}
// newCommitSweepResolverFromReader attempts to decode an encoded
// ContractResolver from the passed Reader instance, returning an active
// ContractResolver instance.
func newCommitSweepResolverFromReader(r io.Reader, resCfg ResolverConfig) (
*commitSweepResolver, error) {
c := &commitSweepResolver{
contractResolverKit: *newContractResolverKit(resCfg),
}
if err := decodeCommitResolution(r, &c.commitResolution); err != nil {
return nil, err
}
var resolved bool
if err := binary.Read(r, endian, &resolved); err != nil {
return nil, err
}
if resolved {
c.markResolved()
}
if err := binary.Read(r, endian, &c.broadcastHeight); err != nil {
return nil, err
}
_, err := io.ReadFull(r, c.chanPoint.Hash[:])
if err != nil {
return nil, err
}
err = binary.Read(r, endian, &c.chanPoint.Index)
if err != nil {
return nil, err
}
// Previously a sweep tx was deserialized at this point. Refactoring
// removed this, but keep in mind that this data may still be present in
// the database.
c.initLogger(fmt.Sprintf("%T(%v)", c, c.commitResolution.SelfOutPoint))
c.initReport()
return c, nil
}
// report returns a report on the resolution state of the contract.
func (c *commitSweepResolver) report() *ContractReport {
c.reportLock.Lock()
defer c.reportLock.Unlock()
cpy := c.currentReport
return &cpy
}
// initReport initializes the pending channels report for this resolver.
func (c *commitSweepResolver) initReport() {
amt := btcutil.Amount(
c.commitResolution.SelfOutputSignDesc.Output.Value,
)
// Set the initial report. All fields are filled in, except for the
// maturity height which remains 0 until Resolve() is executed.
//
// TODO(joostjager): Resolvers only activate after the commit tx
// confirms. With more refactoring in channel arbitrator, it would be
// possible to make the confirmation height part of ResolverConfig and
// populate MaturityHeight here.
c.currentReport = ContractReport{
Outpoint: c.commitResolution.SelfOutPoint,
Type: ReportOutputUnencumbered,
Amount: amt,
LimboBalance: amt,
RecoveredBalance: 0,
}
}
// A compile time assertion to ensure commitSweepResolver meets the
// ContractResolver interface.
var _ reportingContractResolver = (*commitSweepResolver)(nil)
// Launch constructs a commit input and offers it to the sweeper.
func (c *commitSweepResolver) Launch() error {
if c.isLaunched() {
c.log.Tracef("already launched")
return nil
}
c.log.Debugf("launching resolver...")
c.markLaunched()
// If we're already resolved, then we can exit early.
if c.IsResolved() {
c.log.Errorf("already resolved")
return nil
}
confHeight, err := c.getCommitTxConfHeight()
if err != nil {
return err
}
// Wait up until the CSV expires, unless we also have a CLTV that
// expires after.
unlockHeight := confHeight + c.commitResolution.MaturityDelay
if c.hasCLTV() {
unlockHeight = max(unlockHeight, c.leaseExpiry)
}
// Update report now that we learned the confirmation height.
c.reportLock.Lock()
c.currentReport.MaturityHeight = unlockHeight
c.reportLock.Unlock()
// Derive the witness type for this input.
witnessType, err := c.decideWitnessType()
if err != nil {
return err
}
// We'll craft an input with all the information required for the
// sweeper to create a fully valid sweeping transaction to recover
// these coins.
var inp *input.BaseInput
if c.hasCLTV() {
inp = input.NewCsvInputWithCltv(
&c.commitResolution.SelfOutPoint, witnessType,
&c.commitResolution.SelfOutputSignDesc,
c.broadcastHeight, c.commitResolution.MaturityDelay,
c.leaseExpiry, input.WithResolutionBlob(
c.commitResolution.ResolutionBlob,
),
)
} else {
inp = input.NewCsvInput(
&c.commitResolution.SelfOutPoint, witnessType,
&c.commitResolution.SelfOutputSignDesc,
c.broadcastHeight, c.commitResolution.MaturityDelay,
input.WithResolutionBlob(
c.commitResolution.ResolutionBlob,
),
)
}
// TODO(roasbeef): instead of adding ctrl block to the sign desc, make
// new input type, have sweeper set it?
// Calculate the budget for the sweeping this input.
budget := calculateBudget(
btcutil.Amount(inp.SignDesc().Output.Value),
c.Budget.ToLocalRatio, c.Budget.ToLocal,
)
c.log.Infof("sweeping commit output %v using budget=%v", witnessType,
budget)
// With our input constructed, we'll now offer it to the sweeper.
resultChan, err := c.Sweeper.SweepInput(
inp, sweep.Params{
Budget: budget,
// Specify a nil deadline here as there's no time
// pressure.
DeadlineHeight: fn.None[int32](),
},
)
if err != nil {
c.log.Errorf("unable to sweep input: %v", err)
return err
}
c.sweepResultChan = resultChan
return nil
}
// decideWitnessType returns the witness type for the input.
func (c *commitSweepResolver) decideWitnessType() (input.WitnessType, error) {
var (
isLocalCommitTx bool
signDesc = c.commitResolution.SelfOutputSignDesc
)
switch {
// For taproot channels, we'll know if this is the local commit based
// on the timelock value. For remote commitment transactions, the
// witness script has a timelock of 1.
case c.chanType.IsTaproot():
delayKey := c.localChanCfg.DelayBasePoint.PubKey
nonDelayKey := c.localChanCfg.PaymentBasePoint.PubKey
signKey := c.commitResolution.SelfOutputSignDesc.KeyDesc.PubKey
// If the key in the script is neither of these, we shouldn't
// proceed. This should be impossible.
if !signKey.IsEqual(delayKey) && !signKey.IsEqual(nonDelayKey) {
return nil, fmt.Errorf("unknown sign key %v", signKey)
}
// The commitment transaction is ours iff the signing key is
// the delay key.
isLocalCommitTx = signKey.IsEqual(delayKey)
// The output is on our local commitment if the script starts with
// OP_IF for the revocation clause. On the remote commitment it will
// either be a regular P2WKH or a simple sig spend with a CSV delay.
default:
isLocalCommitTx = signDesc.WitnessScript[0] == txscript.OP_IF
}
isDelayedOutput := c.commitResolution.MaturityDelay != 0
c.log.Debugf("isDelayedOutput=%v, isLocalCommitTx=%v", isDelayedOutput,
isLocalCommitTx)
// There're three types of commitments, those that have tweaks for the
// remote key (us in this case), those that don't, and a third where
// there is no tweak and the output is delayed. On the local commitment
// our output will always be delayed. We'll rely on the presence of the
// commitment tweak to discern which type of commitment this is.
var witnessType input.WitnessType
switch {
// The local delayed output for a taproot channel.
case isLocalCommitTx && c.chanType.IsTaproot():
witnessType = input.TaprootLocalCommitSpend
// The CSV 1 delayed output for a taproot channel.
case !isLocalCommitTx && c.chanType.IsTaproot():
witnessType = input.TaprootRemoteCommitSpend
// Delayed output to us on our local commitment for a channel lease in
// which we are the initiator.
case isLocalCommitTx && c.hasCLTV():
witnessType = input.LeaseCommitmentTimeLock
// Delayed output to us on our local commitment.
case isLocalCommitTx:
witnessType = input.CommitmentTimeLock
// A confirmed output to us on the remote commitment for a channel lease
// in which we are the initiator.
case isDelayedOutput && c.hasCLTV():
witnessType = input.LeaseCommitmentToRemoteConfirmed
// A confirmed output to us on the remote commitment.
case isDelayedOutput:
witnessType = input.CommitmentToRemoteConfirmed
// A non-delayed output on the remote commitment where the key is
// tweakless.
case c.commitResolution.SelfOutputSignDesc.SingleTweak == nil:
witnessType = input.CommitSpendNoDelayTweakless
// A non-delayed output on the remote commitment where the key is
// tweaked.
default:
witnessType = input.CommitmentNoDelay
}
return witnessType, nil
}
package contractcourt
import (
"fmt"
"github.com/btcsuite/btcd/btcutil"
)
const (
// MinBudgetValue is the minimal budget that we allow when configuring
// the budget used in sweeping outputs. The actual budget can be lower
// if the user decides to NOT set this value.
//
// NOTE: This value is chosen so the linear fee function can increase
// at least 1 sat/kw per block.
MinBudgetValue btcutil.Amount = 1008
// MinBudgetRatio is the minimal ratio that we allow when configuring
// the budget ratio used in sweeping outputs.
MinBudgetRatio = 0.001
// DefaultBudgetRatio defines a default budget ratio to be used when
// sweeping inputs. This is a large value, which is fine as the final
// fee rate is capped at the max fee rate configured.
DefaultBudgetRatio = 0.5
)
// BudgetConfig is a struct that holds the configuration when offering outputs
// to the sweeper.
//
//nolint:ll
type BudgetConfig struct {
ToLocal btcutil.Amount `long:"tolocal" description:"The amount in satoshis to allocate as the budget to pay fees when sweeping the to_local output. If set, the budget calculated using the ratio (if set) will be capped at this value."`
ToLocalRatio float64 `long:"tolocalratio" description:"The ratio of the value in to_local output to allocate as the budget to pay fees when sweeping it."`
AnchorCPFP btcutil.Amount `long:"anchorcpfp" description:"The amount in satoshis to allocate as the budget to pay fees when CPFPing a force close tx using the anchor output. If set, the budget calculated using the ratio (if set) will be capped at this value."`
AnchorCPFPRatio float64 `long:"anchorcpfpratio" description:"The ratio of a special value to allocate as the budget to pay fees when CPFPing a force close tx using the anchor output. The special value is the sum of all time-sensitive HTLCs on this commitment subtracted by their budgets."`
DeadlineHTLC btcutil.Amount `long:"deadlinehtlc" description:"The amount in satoshis to allocate as the budget to pay fees when sweeping a time-sensitive (first-level) HTLC. If set, the budget calculated using the ratio (if set) will be capped at this value."`
DeadlineHTLCRatio float64 `long:"deadlinehtlcratio" description:"The ratio of the value in a time-sensitive (first-level) HTLC to allocate as the budget to pay fees when sweeping it."`
NoDeadlineHTLC btcutil.Amount `long:"nodeadlinehtlc" description:"The amount in satoshis to allocate as the budget to pay fees when sweeping a non-time-sensitive (second-level) HTLC. If set, the budget calculated using the ratio (if set) will be capped at this value."`
NoDeadlineHTLCRatio float64 `long:"nodeadlinehtlcratio" description:"The ratio of the value in a non-time-sensitive (second-level) HTLC to allocate as the budget to pay fees when sweeping it."`
}
// Validate checks the budget configuration for any invalid values.
func (b *BudgetConfig) Validate() error {
// Exit early if no budget config is set.
if b == nil {
return fmt.Errorf("no budget config set")
}
// Sanity check all fields.
if b.ToLocal != 0 && b.ToLocal < MinBudgetValue {
return fmt.Errorf("tolocal must be at least %v",
MinBudgetValue)
}
if b.ToLocalRatio != 0 && b.ToLocalRatio < MinBudgetRatio {
return fmt.Errorf("tolocalratio must be at least %v",
MinBudgetRatio)
}
if b.AnchorCPFP != 0 && b.AnchorCPFP < MinBudgetValue {
return fmt.Errorf("anchorcpfp must be at least %v",
MinBudgetValue)
}
if b.AnchorCPFPRatio != 0 && b.AnchorCPFPRatio < MinBudgetRatio {
return fmt.Errorf("anchorcpfpratio must be at least %v",
MinBudgetRatio)
}
if b.DeadlineHTLC != 0 && b.DeadlineHTLC < MinBudgetValue {
return fmt.Errorf("deadlinehtlc must be at least %v",
MinBudgetValue)
}
if b.DeadlineHTLCRatio != 0 && b.DeadlineHTLCRatio < MinBudgetRatio {
return fmt.Errorf("deadlinehtlcratio must be at least %v",
MinBudgetRatio)
}
if b.NoDeadlineHTLC != 0 && b.NoDeadlineHTLC < MinBudgetValue {
return fmt.Errorf("nodeadlinehtlc must be at least %v",
MinBudgetValue)
}
if b.NoDeadlineHTLCRatio != 0 &&
b.NoDeadlineHTLCRatio < MinBudgetRatio {
return fmt.Errorf("nodeadlinehtlcratio must be at least %v",
MinBudgetRatio)
}
return nil
}
// String returns a human-readable description of the budget configuration.
func (b *BudgetConfig) String() string {
return fmt.Sprintf("tolocal=%v tolocalratio=%v anchorcpfp=%v "+
"anchorcpfpratio=%v deadlinehtlc=%v deadlinehtlcratio=%v "+
"nodeadlinehtlc=%v nodeadlinehtlcratio=%v",
b.ToLocal, b.ToLocalRatio, b.AnchorCPFP, b.AnchorCPFPRatio,
b.DeadlineHTLC, b.DeadlineHTLCRatio, b.NoDeadlineHTLC,
b.NoDeadlineHTLCRatio)
}
// DefaultSweeperConfig returns the default configuration for the sweeper.
func DefaultBudgetConfig() *BudgetConfig {
return &BudgetConfig{
ToLocalRatio: DefaultBudgetRatio,
AnchorCPFPRatio: DefaultBudgetRatio,
DeadlineHTLCRatio: DefaultBudgetRatio,
NoDeadlineHTLCRatio: DefaultBudgetRatio,
}
}
// calculateBudget takes an output value, a configured ratio and budget value,
// and returns the budget to use for sweeping the output. If the budget value
// is set, it will be used as cap.
func calculateBudget(value btcutil.Amount, ratio float64,
max btcutil.Amount) btcutil.Amount {
// If ratio is not set, using the default value.
if ratio == 0 {
ratio = DefaultBudgetRatio
}
budget := value.MulF64(ratio)
log.Tracef("Calculated budget=%v using value=%v, ratio=%v, cap=%v",
budget, value, ratio, max)
if max != 0 && budget > max {
log.Debugf("Calculated budget=%v is capped at %v", budget, max)
return max
}
return budget
}
package contractcourt
import (
"encoding/binary"
"errors"
"fmt"
"io"
"sync/atomic"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/sweep"
)
var (
endian = binary.BigEndian
)
const (
// sweepConfTarget is the default number of blocks that we'll use as a
// confirmation target when sweeping.
sweepConfTarget = 6
)
// ContractResolver is an interface which packages a state machine which is
// able to carry out the necessary steps required to fully resolve a Bitcoin
// contract on-chain. Resolvers are fully encodable to ensure callers are able
// to persist them properly. A resolver may produce another resolver in the
// case that claiming an HTLC is a multi-stage process. In this case, we may
// partially resolve the contract, then persist, and set up for an additional
// resolution.
type ContractResolver interface {
// ResolverKey returns an identifier which should be globally unique
// for this particular resolver within the chain the original contract
// resides within.
ResolverKey() []byte
// Launch starts the resolver by constructing an input and offering it
// to the sweeper. Once offered, it's expected to monitor the sweeping
// result in a goroutine invoked by calling Resolve.
//
// NOTE: We can call `Resolve` inside a goroutine at the end of this
// method to avoid calling it in the ChannelArbitrator. However, there
// are some DB-related operations such as SwapContract/ResolveContract
// which need to be done inside the resolvers instead, which needs a
// deeper refactoring.
Launch() error
// Resolve instructs the contract resolver to resolve the output
// on-chain. Once the output has been *fully* resolved, the function
// should return immediately with a nil ContractResolver value for the
// first return value. In the case that the contract requires further
// resolution, then another resolve is returned.
//
// NOTE: This function MUST be run as a goroutine.
Resolve() (ContractResolver, error)
// SupplementState allows the user of a ContractResolver to supplement
// it with state required for the proper resolution of a contract.
SupplementState(*channeldb.OpenChannel)
// IsResolved returns true if the stored state in the resolve is fully
// resolved. In this case the target output can be forgotten.
IsResolved() bool
// Encode writes an encoded version of the ContractResolver into the
// passed Writer.
Encode(w io.Writer) error
// Stop signals the resolver to cancel any current resolution
// processes, and suspend.
Stop()
}
// htlcContractResolver is the required interface for htlc resolvers.
type htlcContractResolver interface {
ContractResolver
// HtlcPoint returns the htlc's outpoint on the commitment tx.
HtlcPoint() wire.OutPoint
// Supplement adds additional information to the resolver that is
// required before Resolve() is called.
Supplement(htlc channeldb.HTLC)
// SupplementDeadline gives the deadline height for the HTLC output.
// This is only useful for outgoing HTLCs.
SupplementDeadline(deadlineHeight fn.Option[int32])
}
// reportingContractResolver is a ContractResolver that also exposes a report on
// the resolution state of the contract.
type reportingContractResolver interface {
ContractResolver
report() *ContractReport
}
// ResolverConfig contains the externally supplied configuration items that are
// required by a ContractResolver implementation.
type ResolverConfig struct {
// ChannelArbitratorConfig contains all the interfaces and closures
// required for the resolver to interact with outside sub-systems.
ChannelArbitratorConfig
// Checkpoint allows a resolver to check point its state. This function
// should write the state of the resolver to persistent storage, and
// return a non-nil error upon success. It takes a resolver report,
// which contains information about the outcome and should be written
// to disk if non-nil.
Checkpoint func(ContractResolver, ...*channeldb.ResolverReport) error
}
// contractResolverKit is meant to be used as a mix-in struct to be embedded within a
// given ContractResolver implementation. It contains all the common items that
// a resolver requires to carry out its duties.
type contractResolverKit struct {
ResolverConfig
log btclog.Logger
quit chan struct{}
// sweepResultChan is the result chan returned from calling
// `SweepInput`. It should be mounted to the specific resolver once the
// input has been offered to the sweeper.
sweepResultChan chan sweep.Result
// launched specifies whether the resolver has been launched. Calling
// `Launch` will be a no-op if this is true. This value is not saved to
// db, as it's fine to relaunch a resolver after a restart. It's only
// used to avoid resending requests to the sweeper when a new blockbeat
// is received.
launched atomic.Bool
// resolved reflects if the contract has been fully resolved or not.
resolved atomic.Bool
}
// newContractResolverKit instantiates the mix-in struct.
func newContractResolverKit(cfg ResolverConfig) *contractResolverKit {
return &contractResolverKit{
ResolverConfig: cfg,
quit: make(chan struct{}),
}
}
// initLogger initializes the resolver-specific logger.
func (r *contractResolverKit) initLogger(prefix string) {
logPrefix := fmt.Sprintf("ChannelArbitrator(%v): %s:", r.ChanPoint,
prefix)
r.log = log.WithPrefix(logPrefix)
}
// IsResolved returns true if the stored state in the resolve is fully
// resolved. In this case the target output can be forgotten.
//
// NOTE: Part of the ContractResolver interface.
func (r *contractResolverKit) IsResolved() bool {
return r.resolved.Load()
}
// markResolved marks the resolver as resolved.
func (r *contractResolverKit) markResolved() {
r.resolved.Store(true)
}
// isLaunched returns true if the resolver has been launched.
func (r *contractResolverKit) isLaunched() bool {
return r.launched.Load()
}
// markLaunched marks the resolver as launched.
func (r *contractResolverKit) markLaunched() {
r.launched.Store(true)
}
var (
// errResolverShuttingDown is returned when the resolver stops
// progressing because it received the quit signal.
errResolverShuttingDown = errors.New("resolver shutting down")
)
package contractcourt
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/txscript"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/queue"
)
// htlcIncomingContestResolver is a ContractResolver that's able to resolve an
// incoming HTLC that is still contested. An HTLC is still contested, if at the
// time of commitment broadcast, we don't know of the preimage for it yet, and
// it hasn't expired. In this case, we can resolve the HTLC if we learn of the
// preimage, otherwise the remote party will sweep it after it expires.
//
// TODO(roasbeef): just embed the other resolver?
type htlcIncomingContestResolver struct {
// htlcExpiry is the absolute expiry of this incoming HTLC. We use this
// value to determine if we can exit early as if the HTLC times out,
// before we learn of the preimage then we can't claim it on chain
// successfully.
htlcExpiry uint32
// htlcSuccessResolver is the inner resolver that may be utilized if we
// learn of the preimage.
*htlcSuccessResolver
}
// newIncomingContestResolver instantiates a new incoming htlc contest resolver.
func newIncomingContestResolver(
res lnwallet.IncomingHtlcResolution, broadcastHeight uint32,
htlc channeldb.HTLC, resCfg ResolverConfig) *htlcIncomingContestResolver {
success := newSuccessResolver(
res, broadcastHeight, htlc, resCfg,
)
return &htlcIncomingContestResolver{
htlcExpiry: htlc.RefundTimeout,
htlcSuccessResolver: success,
}
}
func (h *htlcIncomingContestResolver) processFinalHtlcFail() error {
// Mark the htlc as final failed.
err := h.ChainArbitratorConfig.PutFinalHtlcOutcome(
h.ChannelArbitratorConfig.ShortChanID, h.htlc.HtlcIndex, false,
)
if err != nil {
return err
}
// Send notification.
h.ChainArbitratorConfig.HtlcNotifier.NotifyFinalHtlcEvent(
models.CircuitKey{
ChanID: h.ShortChanID,
HtlcID: h.htlc.HtlcIndex,
},
channeldb.FinalHtlcInfo{
Settled: false,
Offchain: false,
},
)
return nil
}
// Launch will call the inner resolver's launch method if the preimage can be
// found, otherwise it's a no-op.
func (h *htlcIncomingContestResolver) Launch() error {
// NOTE: we don't mark this resolver as launched as the inner resolver
// will set it when it's launched.
if h.isLaunched() {
h.log.Tracef("already launched")
return nil
}
h.log.Debugf("launching contest resolver...")
// Query the preimage and apply it if we already know it.
applied, err := h.findAndapplyPreimage()
if err != nil {
return err
}
// No preimage found, leave it to be handled by the resolver.
if !applied {
return nil
}
h.log.Debugf("found preimage for htlc=%x, transforming into success "+
"resolver and launching it", h.htlc.RHash)
// Once we've applied the preimage, we'll launch the inner resolver to
// attempt to claim the HTLC.
return h.htlcSuccessResolver.Launch()
}
// Resolve attempts to resolve this contract. As we don't yet know of the
// preimage for the contract, we'll wait for one of two things to happen:
//
// 1. We learn of the preimage! In this case, we can sweep the HTLC incoming
// and ensure that if this was a multi-hop HTLC we are made whole. In this
// case, an additional ContractResolver will be returned to finish the
// job.
//
// 2. The HTLC expires. If this happens, then the contract is fully resolved
// as we have no remaining actions left at our disposal.
//
// NOTE: Part of the ContractResolver interface.
func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
// If we're already full resolved, then we don't have anything further
// to do.
if h.IsResolved() {
h.log.Errorf("already resolved")
return nil, nil
}
// First try to parse the payload. If that fails, we can stop resolution
// now.
payload, nextHopOnionBlob, err := h.decodePayload()
if err != nil {
h.log.Debugf("cannot decode payload of htlc %v", h.HtlcPoint())
// If we've locked in an htlc with an invalid payload on our
// commitment tx, we don't need to resolve it. The other party
// will time it out and get their funds back. This situation
// can present itself when we crash before processRemoteAdds in
// the link has ran.
h.markResolved()
if err := h.processFinalHtlcFail(); err != nil {
return nil, err
}
// We write a report to disk that indicates we could not decode
// the htlc.
resReport := h.report().resolverReport(
nil, channeldb.ResolverTypeIncomingHtlc,
channeldb.ResolverOutcomeAbandoned,
)
return nil, h.PutResolverReport(nil, resReport)
}
// Register for block epochs. After registration, the current height
// will be sent on the channel immediately.
blockEpochs, err := h.Notifier.RegisterBlockEpochNtfn(nil)
if err != nil {
return nil, err
}
defer blockEpochs.Cancel()
var currentHeight int32
select {
case newBlock, ok := <-blockEpochs.Epochs:
if !ok {
return nil, errResolverShuttingDown
}
currentHeight = newBlock.Height
case <-h.quit:
return nil, errResolverShuttingDown
}
log.Debugf("%T(%v): Resolving incoming HTLC(expiry=%v, height=%v)", h,
h.htlcResolution.ClaimOutpoint, h.htlcExpiry, currentHeight)
// We'll first check if this HTLC has been timed out, if so, we can
// return now and mark ourselves as resolved. If we're past the point of
// expiry of the HTLC, then at this point the sender can sweep it, so
// we'll end our lifetime. Here we deliberately forego the chance that
// the sender doesn't sweep and we already have or will learn the
// preimage. Otherwise the resolver could potentially stay active
// indefinitely and the channel will never close properly.
if uint32(currentHeight) >= h.htlcExpiry {
// TODO(roasbeef): should also somehow check if outgoing is
// resolved or not
// * may need to hook into the circuit map
// * can't timeout before the outgoing has been
log.Infof("%T(%v): HTLC has timed out (expiry=%v, height=%v), "+
"abandoning", h, h.htlcResolution.ClaimOutpoint,
h.htlcExpiry, currentHeight)
h.markResolved()
if err := h.processFinalHtlcFail(); err != nil {
return nil, err
}
// Finally, get our report and checkpoint our resolver with a
// timeout outcome report.
report := h.report().resolverReport(
nil, channeldb.ResolverTypeIncomingHtlc,
channeldb.ResolverOutcomeTimeout,
)
return nil, h.Checkpoint(h, report)
}
// Define a closure to process htlc resolutions either directly or
// triggered by future notifications.
processHtlcResolution := func(e invoices.HtlcResolution) (
ContractResolver, error) {
// Take action based on the type of resolution we have
// received.
switch resolution := e.(type) {
// If the htlc resolution was a settle, apply the
// preimage and return a success resolver.
case *invoices.HtlcSettleResolution:
err := h.applyPreimage(resolution.Preimage)
if err != nil {
return nil, err
}
return h.htlcSuccessResolver, nil
// If the htlc was failed, mark the htlc as
// resolved.
case *invoices.HtlcFailResolution:
log.Infof("%T(%v): Exit hop HTLC canceled "+
"(expiry=%v, height=%v), abandoning", h,
h.htlcResolution.ClaimOutpoint,
h.htlcExpiry, currentHeight)
h.markResolved()
if err := h.processFinalHtlcFail(); err != nil {
return nil, err
}
// Checkpoint our resolver with an abandoned outcome
// because we take no further action on this htlc.
report := h.report().resolverReport(
nil, channeldb.ResolverTypeIncomingHtlc,
channeldb.ResolverOutcomeAbandoned,
)
return nil, h.Checkpoint(h, report)
// Error if the resolution type is unknown, we are only
// expecting settles and fails.
default:
return nil, fmt.Errorf("unknown resolution"+
" type: %v", e)
}
}
var (
hodlChan <-chan interface{}
witnessUpdates <-chan lntypes.Preimage
)
if payload.FwdInfo.NextHop == hop.Exit {
// Create a buffered hodl chan to prevent deadlock.
hodlQueue := queue.NewConcurrentQueue(10)
hodlQueue.Start()
hodlChan = hodlQueue.ChanOut()
// Notify registry that we are potentially resolving as an exit
// hop on-chain. If this HTLC indeed pays to an existing
// invoice, the invoice registry will tell us what to do with
// the HTLC. This is identical to HTLC resolution in the link.
circuitKey := models.CircuitKey{
ChanID: h.ShortChanID,
HtlcID: h.htlc.HtlcIndex,
}
resolution, err := h.Registry.NotifyExitHopHtlc(
h.htlc.RHash, h.htlc.Amt, h.htlcExpiry, currentHeight,
circuitKey, hodlQueue.ChanIn(), h.htlc.CustomRecords,
payload,
)
if err != nil {
return nil, err
}
h.log.Debugf("received resolution from registry: %v",
resolution)
defer func() {
h.Registry.HodlUnsubscribeAll(hodlQueue.ChanIn())
hodlQueue.Stop()
}()
// Take action based on the resolution we received. If the htlc
// was settled, or a htlc for a known invoice failed we can
// resolve it directly. If the resolution is nil, the htlc was
// neither accepted nor failed, so we cannot take action yet.
switch res := resolution.(type) {
case *invoices.HtlcFailResolution:
// In the case where the htlc failed, but the invoice
// was known to the registry, we can directly resolve
// the htlc.
if res.Outcome != invoices.ResultInvoiceNotFound {
return processHtlcResolution(resolution)
}
// If we settled the htlc, we can resolve it.
case *invoices.HtlcSettleResolution:
return processHtlcResolution(resolution)
// If the resolution is nil, the htlc was neither settled nor
// failed so we cannot take action at present.
case nil:
default:
return nil, fmt.Errorf("unknown htlc resolution type: %T",
resolution)
}
} else {
// If the HTLC hasn't expired yet, then we may still be able to
// claim it if we learn of the pre-image, so we'll subscribe to
// the preimage database to see if it turns up, or the HTLC
// times out.
//
// NOTE: This is done BEFORE opportunistically querying the db,
// to ensure the preimage can't be delivered between querying
// and registering for the preimage subscription.
preimageSubscription, err := h.PreimageDB.SubscribeUpdates(
h.htlcSuccessResolver.ShortChanID, &h.htlc,
payload, nextHopOnionBlob,
)
if err != nil {
return nil, err
}
defer preimageSubscription.CancelSubscription()
// With the epochs and preimage subscriptions initialized, we'll
// query to see if we already know the preimage.
preimage, ok := h.PreimageDB.LookupPreimage(h.htlc.RHash)
if ok {
// If we do, then this means we can claim the HTLC!
// However, we don't know how to ourselves, so we'll
// return our inner resolver which has the knowledge to
// do so.
h.log.Debugf("Found preimage for htlc=%x", h.htlc.RHash)
if err := h.applyPreimage(preimage); err != nil {
return nil, err
}
return h.htlcSuccessResolver, nil
}
witnessUpdates = preimageSubscription.WitnessUpdates
}
for {
select {
case preimage := <-witnessUpdates:
// We received a new preimage, but we need to ignore
// all except the preimage we are waiting for.
if !preimage.Matches(h.htlc.RHash) {
continue
}
h.log.Debugf("Received preimage for htlc=%x",
h.htlc.RHash)
if err := h.applyPreimage(preimage); err != nil {
return nil, err
}
// We've learned of the preimage and this information
// has been added to our inner resolver. We return it so
// it can continue contract resolution.
return h.htlcSuccessResolver, nil
case hodlItem := <-hodlChan:
htlcResolution := hodlItem.(invoices.HtlcResolution)
return processHtlcResolution(htlcResolution)
case newBlock, ok := <-blockEpochs.Epochs:
if !ok {
return nil, errResolverShuttingDown
}
// If this new height expires the HTLC, then this means
// we never found out the preimage, so we can mark
// resolved and exit.
newHeight := uint32(newBlock.Height)
if newHeight >= h.htlcExpiry {
log.Infof("%T(%v): HTLC has timed out "+
"(expiry=%v, height=%v), abandoning", h,
h.htlcResolution.ClaimOutpoint,
h.htlcExpiry, currentHeight)
h.markResolved()
if err := h.processFinalHtlcFail(); err != nil {
return nil, err
}
report := h.report().resolverReport(
nil,
channeldb.ResolverTypeIncomingHtlc,
channeldb.ResolverOutcomeTimeout,
)
return nil, h.Checkpoint(h, report)
}
case <-h.quit:
return nil, errResolverShuttingDown
}
}
}
// applyPreimage is a helper function that will populate our internal resolver
// with the preimage we learn of. This should be called once the preimage is
// revealed so the inner resolver can properly complete its duties. The error
// return value indicates whether the preimage was properly applied.
func (h *htlcIncomingContestResolver) applyPreimage(
preimage lntypes.Preimage) error {
// Sanity check to see if this preimage matches our htlc. At this point
// it should never happen that it does not match.
if !preimage.Matches(h.htlc.RHash) {
return errors.New("preimage does not match hash")
}
// We may already have the preimage since both the `Launch` and
// `Resolve` methods will look for it.
if h.htlcResolution.Preimage != lntypes.ZeroHash {
h.log.Debugf("already applied preimage for htlc=%x",
h.htlc.RHash)
return nil
}
// Update htlcResolution with the matching preimage.
h.htlcResolution.Preimage = preimage
log.Infof("%T(%v): applied preimage=%v", h,
h.htlcResolution.ClaimOutpoint, preimage)
isSecondLevel := h.htlcResolution.SignedSuccessTx != nil
// If we didn't have to go to the second level to claim (this
// is the remote commitment transaction), then we don't need to
// modify our canned witness.
if !isSecondLevel {
return nil
}
isTaproot := txscript.IsPayToTaproot(
h.htlcResolution.SignedSuccessTx.TxOut[0].PkScript,
)
// If this is our commitment transaction, then we'll need to
// populate the witness for the second-level HTLC transaction.
switch {
// For taproot channels, the witness for sweeping with success
// looks like:
// - <sender sig> <receiver sig> <preimage> <success_script>
// <control_block>
//
// So we'll insert it at the 3rd index of the witness.
case isTaproot:
//nolint:ll
h.htlcResolution.SignedSuccessTx.TxIn[0].Witness[2] = preimage[:]
// Within the witness for the success transaction, the
// preimage is the 4th element as it looks like:
//
// * <0> <sender sig> <recvr sig> <preimage> <witness script>
//
// We'll populate it within the witness, as since this
// was a "contest" resolver, we didn't yet know of the
// preimage.
case !isTaproot:
//nolint:ll
h.htlcResolution.SignedSuccessTx.TxIn[0].Witness[3] = preimage[:]
}
return nil
}
// report returns a report on the resolution state of the contract.
func (h *htlcIncomingContestResolver) report() *ContractReport {
// No locking needed as these values are read-only.
finalAmt := h.htlc.Amt.ToSatoshis()
if h.htlcResolution.SignedSuccessTx != nil {
finalAmt = btcutil.Amount(
h.htlcResolution.SignedSuccessTx.TxOut[0].Value,
)
}
return &ContractReport{
Outpoint: h.htlcResolution.ClaimOutpoint,
Type: ReportOutputIncomingHtlc,
Amount: finalAmt,
MaturityHeight: h.htlcExpiry,
LimboBalance: finalAmt,
Stage: 1,
}
}
// Stop signals the resolver to cancel any current resolution processes, and
// suspend.
//
// NOTE: Part of the ContractResolver interface.
func (h *htlcIncomingContestResolver) Stop() {
h.log.Debugf("stopping...")
defer h.log.Debugf("stopped")
close(h.quit)
}
// Encode writes an encoded version of the ContractResolver into the passed
// Writer.
//
// NOTE: Part of the ContractResolver interface.
func (h *htlcIncomingContestResolver) Encode(w io.Writer) error {
// We'll first write out the one field unique to this resolver.
if err := binary.Write(w, endian, h.htlcExpiry); err != nil {
return err
}
// Then we'll write out our internal resolver.
return h.htlcSuccessResolver.Encode(w)
}
// newIncomingContestResolverFromReader attempts to decode an encoded ContractResolver
// from the passed Reader instance, returning an active ContractResolver
// instance.
func newIncomingContestResolverFromReader(r io.Reader, resCfg ResolverConfig) (
*htlcIncomingContestResolver, error) {
h := &htlcIncomingContestResolver{}
// We'll first read the one field unique to this resolver.
if err := binary.Read(r, endian, &h.htlcExpiry); err != nil {
return nil, err
}
// Then we'll decode our internal resolver.
successResolver, err := newSuccessResolverFromReader(r, resCfg)
if err != nil {
return nil, err
}
h.htlcSuccessResolver = successResolver
return h, nil
}
// Supplement adds additional information to the resolver that is required
// before Resolve() is called.
//
// NOTE: Part of the htlcContractResolver interface.
func (h *htlcIncomingContestResolver) Supplement(htlc channeldb.HTLC) {
h.htlc = htlc
}
// SupplementDeadline does nothing for an incoming htlc resolver.
//
// NOTE: Part of the htlcContractResolver interface.
func (h *htlcIncomingContestResolver) SupplementDeadline(_ fn.Option[int32]) {
}
// decodePayload (re)decodes the hop payload of a received htlc.
func (h *htlcIncomingContestResolver) decodePayload() (*hop.Payload,
[]byte, error) {
blindingInfo := hop.ReconstructBlindingInfo{
IncomingAmt: h.htlc.Amt,
IncomingExpiry: h.htlc.RefundTimeout,
BlindingKey: h.htlc.BlindingPoint,
}
onionReader := bytes.NewReader(h.htlc.OnionBlob[:])
iterator, err := h.OnionProcessor.ReconstructHopIterator(
onionReader, h.htlc.RHash[:], blindingInfo,
)
if err != nil {
return nil, nil, err
}
payload, _, err := iterator.HopPayload()
if err != nil {
return nil, nil, err
}
// Transform onion blob for the next hop.
var onionBlob [lnwire.OnionPacketSize]byte
buf := bytes.NewBuffer(onionBlob[0:0])
err = iterator.EncodeNextHop(buf)
if err != nil {
return nil, nil, err
}
return payload, onionBlob[:], nil
}
// A compile time assertion to ensure htlcIncomingContestResolver meets the
// ContractResolver interface.
var _ htlcContractResolver = (*htlcIncomingContestResolver)(nil)
// findAndapplyPreimage performs a non-blocking read to find the preimage for
// the incoming HTLC. If found, it will be applied to the resolver. This method
// is used for the resolver to decide whether it wants to transform into a
// success resolver during launching.
//
// NOTE: Since we have two places to query the preimage, we need to check both
// the preimage db and the invoice db to look up the preimage.
func (h *htlcIncomingContestResolver) findAndapplyPreimage() (bool, error) {
// Query to see if we already know the preimage.
preimage, ok := h.PreimageDB.LookupPreimage(h.htlc.RHash)
// If the preimage is known, we'll apply it.
if ok {
if err := h.applyPreimage(preimage); err != nil {
return false, err
}
// Successfully applied the preimage, we can now return.
return true, nil
}
// First try to parse the payload.
payload, _, err := h.decodePayload()
if err != nil {
h.log.Errorf("Cannot decode payload of htlc %v", h.HtlcPoint())
// If we cannot decode the payload, we will return a nil error
// and let it to be handled in `Resolve`.
return false, nil
}
// Exit early if this is not the exit hop, which means we are not the
// payment receiver and don't have preimage.
if payload.FwdInfo.NextHop != hop.Exit {
return false, nil
}
// Notify registry that we are potentially resolving as an exit hop
// on-chain. If this HTLC indeed pays to an existing invoice, the
// invoice registry will tell us what to do with the HTLC. This is
// identical to HTLC resolution in the link.
circuitKey := models.CircuitKey{
ChanID: h.ShortChanID,
HtlcID: h.htlc.HtlcIndex,
}
// Try get the resolution - if it doesn't give us a resolution
// immediately, we'll assume we don't know it yet and let the `Resolve`
// handle the waiting.
//
// NOTE: we use a nil subscriber here and a zero current height as we
// are only interested in the settle resolution.
//
// TODO(yy): move this logic to link and let the preimage be accessed
// via the preimage beacon.
resolution, err := h.Registry.NotifyExitHopHtlc(
h.htlc.RHash, h.htlc.Amt, h.htlcExpiry, 0,
circuitKey, nil, h.htlc.CustomRecords, payload,
)
if err != nil {
return false, err
}
res, ok := resolution.(*invoices.HtlcSettleResolution)
// Exit early if it's not a settle resolution.
if !ok {
return false, nil
}
// Otherwise we have a settle resolution, apply the preimage.
err = h.applyPreimage(res.Preimage)
if err != nil {
return false, err
}
return true, nil
}
package contractcourt
import (
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/tlv"
)
// htlcLeaseResolver is a struct that houses the lease specific HTLC resolution
// logic. This includes deriving the _true_ waiting height, as well as the
// input to offer to the sweeper.
type htlcLeaseResolver struct {
// channelInitiator denotes whether the party responsible for resolving
// the contract initiated the channel.
channelInitiator bool
// leaseExpiry denotes the additional waiting period the contract must
// hold until it can be resolved. This waiting period is known as the
// expiration of a script-enforced leased channel and only applies to
// the channel initiator.
//
// NOTE: This value should only be set when the contract belongs to a
// leased channel.
leaseExpiry uint32
}
// hasCLTV denotes whether the resolver must wait for an additional CLTV to
// expire before resolving the contract.
func (h *htlcLeaseResolver) hasCLTV() bool {
return h.channelInitiator && h.leaseExpiry > 0
}
// deriveWaitHeight computes the height the resolver needs to wait until it can
// sweep the input.
func (h *htlcLeaseResolver) deriveWaitHeight(csvDelay uint32,
commitSpend *chainntnfs.SpendDetail) uint32 {
waitHeight := uint32(commitSpend.SpendingHeight) + csvDelay - 1
if h.hasCLTV() {
waitHeight = max(waitHeight, h.leaseExpiry)
}
return waitHeight
}
// makeSweepInput constructs the type of input (either just csv or csv+ctlv) to
// send to the sweeper so the output can ultimately be swept.
func (h *htlcLeaseResolver) makeSweepInput(op *wire.OutPoint,
wType, cltvWtype input.StandardWitnessType,
signDesc *input.SignDescriptor, csvDelay, broadcastHeight uint32,
payHash [32]byte, resBlob fn.Option[tlv.Blob]) *input.BaseInput {
log.Infof("%T(%x): offering second-layer output to sweeper: %v", h,
payHash, op)
if h.hasCLTV() {
return input.NewCsvInputWithCltv(
op, cltvWtype, signDesc, broadcastHeight, csvDelay,
h.leaseExpiry, input.WithResolutionBlob(resBlob),
)
}
log.Infof("%T(%x): CSV lock expired, offering second-layer output to "+
"sweeper: %v", h, payHash, op)
return input.NewCsvInput(
op, wType, signDesc, broadcastHeight, csvDelay,
input.WithResolutionBlob(resBlob),
)
}
// SupplementState allows the user of a ContractResolver to supplement it with
// state required for the proper resolution of a contract.
//
// NOTE: Part of the ContractResolver interface.
func (h *htlcLeaseResolver) SupplementState(state *channeldb.OpenChannel) {
if state.ChanType.HasLeaseExpiration() {
h.leaseExpiry = state.ThawHeight
}
h.channelInitiator = state.IsInitiator
}
package contractcourt
import (
"io"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnwallet"
)
// htlcOutgoingContestResolver is a ContractResolver that's able to resolve an
// outgoing HTLC that is still contested. An HTLC is still contested, if at the
// time that we broadcast the commitment transaction, it isn't able to be fully
// resolved. In this case, we'll either wait for the HTLC to timeout, or for
// us to learn of the preimage.
type htlcOutgoingContestResolver struct {
// htlcTimeoutResolver is the inner solver that this resolver may turn
// into. This only happens if the HTLC expires on-chain.
*htlcTimeoutResolver
}
// newOutgoingContestResolver instantiates a new outgoing contested htlc
// resolver.
func newOutgoingContestResolver(res lnwallet.OutgoingHtlcResolution,
broadcastHeight uint32, htlc channeldb.HTLC,
resCfg ResolverConfig) *htlcOutgoingContestResolver {
timeout := newTimeoutResolver(
res, broadcastHeight, htlc, resCfg,
)
return &htlcOutgoingContestResolver{
htlcTimeoutResolver: timeout,
}
}
// Launch will call the inner resolver's launch method if the expiry height has
// been reached, otherwise it's a no-op.
func (h *htlcOutgoingContestResolver) Launch() error {
// NOTE: we don't mark this resolver as launched as the inner resolver
// will set it when it's launched.
if h.isLaunched() {
h.log.Tracef("already launched")
return nil
}
h.log.Debugf("launching contest resolver...")
_, bestHeight, err := h.ChainIO.GetBestBlock()
if err != nil {
return err
}
if uint32(bestHeight) < h.htlcResolution.Expiry {
return nil
}
// If the current height is >= expiry, then a timeout path spend will
// be valid to be included in the next block, and we can immediately
// return the resolver.
h.log.Infof("expired (height=%v, expiry=%v), transforming into "+
"timeout resolver and launching it", bestHeight,
h.htlcResolution.Expiry)
return h.htlcTimeoutResolver.Launch()
}
// Resolve commences the resolution of this contract. As this contract hasn't
// yet timed out, we'll wait for one of two things to happen
//
// 1. The HTLC expires. In this case, we'll sweep the funds and send a clean
// up cancel message to outside sub-systems.
//
// 2. The remote party sweeps this HTLC on-chain, in which case we'll add the
// pre-image to our global cache, then send a clean up settle message
// backwards.
//
// When either of these two things happens, we'll create a new resolver which
// is able to handle the final resolution of the contract. We're only the pivot
// point.
func (h *htlcOutgoingContestResolver) Resolve() (ContractResolver, error) {
// If we're already full resolved, then we don't have anything further
// to do.
if h.IsResolved() {
h.log.Errorf("already resolved")
return nil, nil
}
// Otherwise, we'll watch for two external signals to decide if we'll
// morph into another resolver, or fully resolve the contract.
//
// The output we'll be watching for is the *direct* spend from the HTLC
// output. If this isn't our commitment transaction, it'll be right on
// the resolution. Otherwise, we fetch this pointer from the input of
// the time out transaction.
outPointToWatch, scriptToWatch, err := h.chainDetailsToWatch()
if err != nil {
return nil, err
}
// First, we'll register for a spend notification for this output. If
// the remote party sweeps with the pre-image, we'll be notified.
spendNtfn, err := h.Notifier.RegisterSpendNtfn(
outPointToWatch, scriptToWatch, h.broadcastHeight,
)
if err != nil {
return nil, err
}
// We'll quickly check to see if the output has already been spent.
select {
// If the output has already been spent, then we can stop early and
// sweep the pre-image from the output.
case commitSpend, ok := <-spendNtfn.Spend:
if !ok {
return nil, errResolverShuttingDown
}
return nil, h.claimCleanUp(commitSpend)
// If it hasn't, then we'll watch for both the expiration, and the
// sweeping out this output.
default:
}
// If we reach this point, then we can't fully act yet, so we'll await
// either of our signals triggering: the HTLC expires, or we learn of
// the preimage.
blockEpochs, err := h.Notifier.RegisterBlockEpochNtfn(nil)
if err != nil {
return nil, err
}
defer blockEpochs.Cancel()
for {
select {
// A new block has arrived, we'll check to see if this leads to
// HTLC expiration.
case newBlock, ok := <-blockEpochs.Epochs:
if !ok {
return nil, errResolverShuttingDown
}
// If the current height is >= expiry, then a timeout
// path spend will be valid to be included in the next
// block, and we can immediately return the resolver.
//
// NOTE: when broadcasting this transaction, btcd will
// check the timelock in `CheckTransactionStandard`,
// which requires `expiry < currentHeight+1`. If the
// check doesn't pass, error `transaction is not
// finalized` will be returned and the broadcast will
// fail.
newHeight := uint32(newBlock.Height)
expiry := h.htlcResolution.Expiry
// Check if the expiry height is about to be reached.
// We offer this HTLC one block earlier to make sure
// when the next block arrives, the sweeper will pick
// up this input and sweep it immediately. The sweeper
// will handle the waiting for the one last block till
// expiry.
if newHeight >= expiry-1 {
h.log.Infof("HTLC about to expire "+
"(height=%v, expiry=%v), transforming "+
"into timeout resolver", newHeight,
h.htlcResolution.Expiry)
return h.htlcTimeoutResolver, nil
}
// The output has been spent! This means the preimage has been
// revealed on-chain.
case commitSpend, ok := <-spendNtfn.Spend:
if !ok {
return nil, errResolverShuttingDown
}
// The only way this output can be spent by the remote
// party is by revealing the preimage. So we'll perform
// our duties to clean up the contract once it has been
// claimed.
return nil, h.claimCleanUp(commitSpend)
case <-h.quit:
return nil, errResolverShuttingDown
}
}
}
// report returns a report on the resolution state of the contract.
func (h *htlcOutgoingContestResolver) report() *ContractReport {
// No locking needed as these values are read-only.
finalAmt := h.htlc.Amt.ToSatoshis()
if h.htlcResolution.SignedTimeoutTx != nil {
finalAmt = btcutil.Amount(
h.htlcResolution.SignedTimeoutTx.TxOut[0].Value,
)
}
return &ContractReport{
Outpoint: h.htlcResolution.ClaimOutpoint,
Type: ReportOutputOutgoingHtlc,
Amount: finalAmt,
MaturityHeight: h.htlcResolution.Expiry,
LimboBalance: finalAmt,
Stage: 1,
}
}
// Stop signals the resolver to cancel any current resolution processes, and
// suspend.
//
// NOTE: Part of the ContractResolver interface.
func (h *htlcOutgoingContestResolver) Stop() {
h.log.Debugf("stopping...")
defer h.log.Debugf("stopped")
close(h.quit)
}
// Encode writes an encoded version of the ContractResolver into the passed
// Writer.
//
// NOTE: Part of the ContractResolver interface.
func (h *htlcOutgoingContestResolver) Encode(w io.Writer) error {
return h.htlcTimeoutResolver.Encode(w)
}
// SupplementDeadline does nothing for an incoming htlc resolver.
//
// NOTE: Part of the htlcContractResolver interface.
func (h *htlcOutgoingContestResolver) SupplementDeadline(_ fn.Option[int32]) {
}
// newOutgoingContestResolverFromReader attempts to decode an encoded ContractResolver
// from the passed Reader instance, returning an active ContractResolver
// instance.
func newOutgoingContestResolverFromReader(r io.Reader, resCfg ResolverConfig) (
*htlcOutgoingContestResolver, error) {
h := &htlcOutgoingContestResolver{}
timeoutResolver, err := newTimeoutResolverFromReader(r, resCfg)
if err != nil {
return nil, err
}
h.htlcTimeoutResolver = timeoutResolver
return h, nil
}
// A compile time assertion to ensure htlcOutgoingContestResolver meets the
// ContractResolver interface.
var _ htlcContractResolver = (*htlcOutgoingContestResolver)(nil)
package contractcourt
import (
"encoding/binary"
"fmt"
"io"
"sync"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/sweep"
)
// htlcSuccessResolver is a resolver that's capable of sweeping an incoming
// HTLC output on-chain. If this is the remote party's commitment, we'll sweep
// it directly from the commitment output *immediately*. If this is our
// commitment, we'll first broadcast the success transaction, then send it to
// the incubator for sweeping. That's it, no need to send any clean up
// messages.
//
// TODO(roasbeef): don't need to broadcast?
type htlcSuccessResolver struct {
// htlcResolution is the incoming HTLC resolution for this HTLC. It
// contains everything we need to properly resolve this HTLC.
htlcResolution lnwallet.IncomingHtlcResolution
// outputIncubating returns true if we've sent the output to the output
// incubator (utxo nursery). In case the htlcResolution has non-nil
// SignDetails, it means we will let the Sweeper handle broadcasting
// the secondd-level transaction, and sweeping its output. In this case
// we let this field indicate whether we need to broadcast the
// second-level tx (false) or if it has confirmed and we must sweep the
// second-level output (true).
outputIncubating bool
// broadcastHeight is the height that the original contract was
// broadcast to the main-chain at. We'll use this value to bound any
// historical queries to the chain for spends/confirmations.
broadcastHeight uint32
// htlc contains information on the htlc that we are resolving on-chain.
htlc channeldb.HTLC
// currentReport stores the current state of the resolver for reporting
// over the rpc interface. This should only be reported in case we have
// a non-nil SignDetails on the htlcResolution, otherwise the nursery
// will produce reports.
currentReport ContractReport
// reportLock prevents concurrent access to the resolver report.
reportLock sync.Mutex
contractResolverKit
htlcLeaseResolver
}
// newSuccessResolver instanties a new htlc success resolver.
func newSuccessResolver(res lnwallet.IncomingHtlcResolution,
broadcastHeight uint32, htlc channeldb.HTLC,
resCfg ResolverConfig) *htlcSuccessResolver {
h := &htlcSuccessResolver{
contractResolverKit: *newContractResolverKit(resCfg),
htlcResolution: res,
broadcastHeight: broadcastHeight,
htlc: htlc,
}
h.initReport()
h.initLogger(fmt.Sprintf("%T(%v)", h, h.outpoint()))
return h
}
// outpoint returns the outpoint of the HTLC output we're attempting to sweep.
func (h *htlcSuccessResolver) outpoint() wire.OutPoint {
// The primary key for this resolver will be the outpoint of the HTLC
// on the commitment transaction itself. If this is our commitment,
// then the output can be found within the signed success tx,
// otherwise, it's just the ClaimOutpoint.
if h.htlcResolution.SignedSuccessTx != nil {
return h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint
}
return h.htlcResolution.ClaimOutpoint
}
// ResolverKey returns an identifier which should be globally unique for this
// particular resolver within the chain the original contract resides within.
//
// NOTE: Part of the ContractResolver interface.
func (h *htlcSuccessResolver) ResolverKey() []byte {
key := newResolverID(h.outpoint())
return key[:]
}
// Resolve attempts to resolve an unresolved incoming HTLC that we know the
// preimage to. If the HTLC is on the commitment of the remote party, then we'll
// simply sweep it directly. Otherwise, we'll hand this off to the utxo nursery
// to do its duty. There is no need to make a call to the invoice registry
// anymore. Every HTLC has already passed through the incoming contest resolver
// and in there the invoice was already marked as settled.
//
// NOTE: Part of the ContractResolver interface.
//
// TODO(yy): refactor the interface method to return an error only.
func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
var err error
switch {
// If we're already resolved, then we can exit early.
case h.IsResolved():
h.log.Errorf("already resolved")
// If this is an output on the remote party's commitment transaction,
// use the direct-spend path to sweep the htlc.
case h.isRemoteCommitOutput():
err = h.resolveRemoteCommitOutput()
// If this is an output on our commitment transaction using post-anchor
// channel type, it will be handled by the sweeper.
case h.isZeroFeeOutput():
err = h.resolveSuccessTx()
// If this is an output on our own commitment using pre-anchor channel
// type, we will publish the success tx and offer the output to the
// nursery.
default:
err = h.resolveLegacySuccessTx()
}
return nil, err
}
// resolveRemoteCommitOutput handles sweeping an HTLC output on the remote
// commitment with the preimage. In this case we can sweep the output directly,
// and don't have to broadcast a second-level transaction.
func (h *htlcSuccessResolver) resolveRemoteCommitOutput() error {
h.log.Info("waiting for direct-preimage spend of the htlc to confirm")
// Wait for the direct-preimage HTLC sweep tx to confirm.
//
// TODO(yy): use the result chan returned from `SweepInput`.
sweepTxDetails, err := waitForSpend(
&h.htlcResolution.ClaimOutpoint,
h.htlcResolution.SweepSignDesc.Output.PkScript,
h.broadcastHeight, h.Notifier, h.quit,
)
if err != nil {
return err
}
// TODO(yy): should also update the `RecoveredBalance` and
// `LimboBalance` like other paths?
// Checkpoint the resolver, and write the outcome to disk.
return h.checkpointClaim(sweepTxDetails.SpenderTxHash)
}
// checkpointClaim checkpoints the success resolver with the reports it needs.
// If this htlc was claimed two stages, it will write reports for both stages,
// otherwise it will just write for the single htlc claim.
func (h *htlcSuccessResolver) checkpointClaim(spendTx *chainhash.Hash) error {
// Mark the htlc as final settled.
err := h.ChainArbitratorConfig.PutFinalHtlcOutcome(
h.ChannelArbitratorConfig.ShortChanID, h.htlc.HtlcIndex, true,
)
if err != nil {
return err
}
// Send notification.
h.ChainArbitratorConfig.HtlcNotifier.NotifyFinalHtlcEvent(
models.CircuitKey{
ChanID: h.ShortChanID,
HtlcID: h.htlc.HtlcIndex,
},
channeldb.FinalHtlcInfo{
Settled: true,
Offchain: false,
},
)
// Create a resolver report for claiming of the htlc itself.
amt := btcutil.Amount(h.htlcResolution.SweepSignDesc.Output.Value)
reports := []*channeldb.ResolverReport{
{
OutPoint: h.htlcResolution.ClaimOutpoint,
Amount: amt,
ResolverType: channeldb.ResolverTypeIncomingHtlc,
ResolverOutcome: channeldb.ResolverOutcomeClaimed,
SpendTxID: spendTx,
},
}
// If we have a success tx, we append a report to represent our first
// stage claim.
if h.htlcResolution.SignedSuccessTx != nil {
// If the SignedSuccessTx is not nil, we are claiming the htlc
// in two stages, so we need to create a report for the first
// stage transaction as well.
spendTx := h.htlcResolution.SignedSuccessTx
spendTxID := spendTx.TxHash()
report := &channeldb.ResolverReport{
OutPoint: spendTx.TxIn[0].PreviousOutPoint,
Amount: h.htlc.Amt.ToSatoshis(),
ResolverType: channeldb.ResolverTypeIncomingHtlc,
ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
SpendTxID: &spendTxID,
}
reports = append(reports, report)
}
// Finally, we checkpoint the resolver with our report(s).
h.markResolved()
return h.Checkpoint(h, reports...)
}
// Stop signals the resolver to cancel any current resolution processes, and
// suspend.
//
// NOTE: Part of the ContractResolver interface.
func (h *htlcSuccessResolver) Stop() {
h.log.Debugf("stopping...")
defer h.log.Debugf("stopped")
close(h.quit)
}
// report returns a report on the resolution state of the contract.
func (h *htlcSuccessResolver) report() *ContractReport {
// If the sign details are nil, the report will be created by handled
// by the nursery.
if h.htlcResolution.SignDetails == nil {
return nil
}
h.reportLock.Lock()
defer h.reportLock.Unlock()
cpy := h.currentReport
return &cpy
}
func (h *htlcSuccessResolver) initReport() {
// We create the initial report. This will only be reported for
// resolvers not handled by the nursery.
finalAmt := h.htlc.Amt.ToSatoshis()
if h.htlcResolution.SignedSuccessTx != nil {
finalAmt = btcutil.Amount(
h.htlcResolution.SignedSuccessTx.TxOut[0].Value,
)
}
h.currentReport = ContractReport{
Outpoint: h.htlcResolution.ClaimOutpoint,
Type: ReportOutputIncomingHtlc,
Amount: finalAmt,
MaturityHeight: h.htlcResolution.CsvDelay,
LimboBalance: finalAmt,
Stage: 1,
}
}
// Encode writes an encoded version of the ContractResolver into the passed
// Writer.
//
// NOTE: Part of the ContractResolver interface.
func (h *htlcSuccessResolver) Encode(w io.Writer) error {
// First we'll encode our inner HTLC resolution.
if err := encodeIncomingResolution(w, &h.htlcResolution); err != nil {
return err
}
// Next, we'll write out the fields that are specified to the contract
// resolver.
if err := binary.Write(w, endian, h.outputIncubating); err != nil {
return err
}
if err := binary.Write(w, endian, h.IsResolved()); err != nil {
return err
}
if err := binary.Write(w, endian, h.broadcastHeight); err != nil {
return err
}
if _, err := w.Write(h.htlc.RHash[:]); err != nil {
return err
}
// We encode the sign details last for backwards compatibility.
err := encodeSignDetails(w, h.htlcResolution.SignDetails)
if err != nil {
return err
}
return nil
}
// newSuccessResolverFromReader attempts to decode an encoded ContractResolver
// from the passed Reader instance, returning an active ContractResolver
// instance.
func newSuccessResolverFromReader(r io.Reader, resCfg ResolverConfig) (
*htlcSuccessResolver, error) {
h := &htlcSuccessResolver{
contractResolverKit: *newContractResolverKit(resCfg),
}
// First we'll decode our inner HTLC resolution.
if err := decodeIncomingResolution(r, &h.htlcResolution); err != nil {
return nil, err
}
// Next, we'll read all the fields that are specified to the contract
// resolver.
if err := binary.Read(r, endian, &h.outputIncubating); err != nil {
return nil, err
}
var resolved bool
if err := binary.Read(r, endian, &resolved); err != nil {
return nil, err
}
if resolved {
h.markResolved()
}
if err := binary.Read(r, endian, &h.broadcastHeight); err != nil {
return nil, err
}
if _, err := io.ReadFull(r, h.htlc.RHash[:]); err != nil {
return nil, err
}
// Sign details is a new field that was added to the htlc resolution,
// so it is serialized last for backwards compatibility. We try to read
// it, but don't error out if there are not bytes left.
signDetails, err := decodeSignDetails(r)
if err == nil {
h.htlcResolution.SignDetails = signDetails
} else if err != io.EOF && err != io.ErrUnexpectedEOF {
return nil, err
}
h.initReport()
h.initLogger(fmt.Sprintf("%T(%v)", h, h.outpoint()))
return h, nil
}
// Supplement adds additional information to the resolver that is required
// before Resolve() is called.
//
// NOTE: Part of the htlcContractResolver interface.
func (h *htlcSuccessResolver) Supplement(htlc channeldb.HTLC) {
h.htlc = htlc
}
// HtlcPoint returns the htlc's outpoint on the commitment tx.
//
// NOTE: Part of the htlcContractResolver interface.
func (h *htlcSuccessResolver) HtlcPoint() wire.OutPoint {
return h.htlcResolution.HtlcPoint()
}
// SupplementDeadline does nothing for an incoming htlc resolver.
//
// NOTE: Part of the htlcContractResolver interface.
func (h *htlcSuccessResolver) SupplementDeadline(_ fn.Option[int32]) {
}
// A compile time assertion to ensure htlcSuccessResolver meets the
// ContractResolver interface.
var _ htlcContractResolver = (*htlcSuccessResolver)(nil)
// isRemoteCommitOutput returns a bool to indicate whether the htlc output is
// on the remote commitment.
func (h *htlcSuccessResolver) isRemoteCommitOutput() bool {
// If we don't have a success transaction, then this means that this is
// an output on the remote party's commitment transaction.
return h.htlcResolution.SignedSuccessTx == nil
}
// isZeroFeeOutput returns a boolean indicating whether the htlc output is from
// a anchor-enabled channel, which uses the sighash SINGLE|ANYONECANPAY.
func (h *htlcSuccessResolver) isZeroFeeOutput() bool {
// If we have non-nil SignDetails, this means it has a 2nd level HTLC
// transaction that is signed using sighash SINGLE|ANYONECANPAY (the
// case for anchor type channels). In this case we can re-sign it and
// attach fees at will.
return h.htlcResolution.SignedSuccessTx != nil &&
h.htlcResolution.SignDetails != nil
}
// isTaproot returns true if the resolver is for a taproot output.
func (h *htlcSuccessResolver) isTaproot() bool {
return txscript.IsPayToTaproot(
h.htlcResolution.SweepSignDesc.Output.PkScript,
)
}
// sweepRemoteCommitOutput creates a sweep request to sweep the HTLC output on
// the remote commitment via the direct preimage-spend.
func (h *htlcSuccessResolver) sweepRemoteCommitOutput() error {
// Before we can craft out sweeping transaction, we need to create an
// input which contains all the items required to add this input to a
// sweeping transaction, and generate a witness.
var inp input.Input
if h.isTaproot() {
inp = lnutils.Ptr(input.MakeTaprootHtlcSucceedInput(
&h.htlcResolution.ClaimOutpoint,
&h.htlcResolution.SweepSignDesc,
h.htlcResolution.Preimage[:],
h.broadcastHeight,
h.htlcResolution.CsvDelay,
input.WithResolutionBlob(
h.htlcResolution.ResolutionBlob,
),
))
} else {
inp = lnutils.Ptr(input.MakeHtlcSucceedInput(
&h.htlcResolution.ClaimOutpoint,
&h.htlcResolution.SweepSignDesc,
h.htlcResolution.Preimage[:],
h.broadcastHeight,
h.htlcResolution.CsvDelay,
))
}
// Calculate the budget for this sweep.
budget := calculateBudget(
btcutil.Amount(inp.SignDesc().Output.Value),
h.Budget.DeadlineHTLCRatio,
h.Budget.DeadlineHTLC,
)
deadline := fn.Some(int32(h.htlc.RefundTimeout))
log.Infof("%T(%x): offering direct-preimage HTLC output to sweeper "+
"with deadline=%v, budget=%v", h, h.htlc.RHash[:],
h.htlc.RefundTimeout, budget)
// We'll now offer the direct preimage HTLC to the sweeper.
_, err := h.Sweeper.SweepInput(
inp,
sweep.Params{
Budget: budget,
DeadlineHeight: deadline,
},
)
return err
}
// sweepSuccessTx attempts to sweep the second level success tx.
func (h *htlcSuccessResolver) sweepSuccessTx() error {
var secondLevelInput input.HtlcSecondLevelAnchorInput
if h.isTaproot() {
secondLevelInput = input.MakeHtlcSecondLevelSuccessTaprootInput(
h.htlcResolution.SignedSuccessTx,
h.htlcResolution.SignDetails, h.htlcResolution.Preimage,
h.broadcastHeight, input.WithResolutionBlob(
h.htlcResolution.ResolutionBlob,
),
)
} else {
secondLevelInput = input.MakeHtlcSecondLevelSuccessAnchorInput(
h.htlcResolution.SignedSuccessTx,
h.htlcResolution.SignDetails, h.htlcResolution.Preimage,
h.broadcastHeight,
)
}
// Calculate the budget for this sweep.
value := btcutil.Amount(secondLevelInput.SignDesc().Output.Value)
budget := calculateBudget(
value, h.Budget.DeadlineHTLCRatio, h.Budget.DeadlineHTLC,
)
// The deadline would be the CLTV in this HTLC output. If we are the
// initiator of this force close, with the default
// `IncomingBroadcastDelta`, it means we have 10 blocks left when going
// onchain.
deadline := fn.Some(int32(h.htlc.RefundTimeout))
h.log.Infof("offering second-level HTLC success tx to sweeper with "+
"deadline=%v, budget=%v", h.htlc.RefundTimeout, budget)
// We'll now offer the second-level transaction to the sweeper.
_, err := h.Sweeper.SweepInput(
&secondLevelInput,
sweep.Params{
Budget: budget,
DeadlineHeight: deadline,
},
)
return err
}
// sweepSuccessTxOutput attempts to sweep the output of the second level
// success tx.
func (h *htlcSuccessResolver) sweepSuccessTxOutput() error {
h.log.Debugf("sweeping output %v from 2nd-level HTLC success tx",
h.htlcResolution.ClaimOutpoint)
// This should be non-blocking as we will only attempt to sweep the
// output when the second level tx has already been confirmed. In other
// words, waitForSpend will return immediately.
commitSpend, err := waitForSpend(
&h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint,
h.htlcResolution.SignDetails.SignDesc.Output.PkScript,
h.broadcastHeight, h.Notifier, h.quit,
)
if err != nil {
return err
}
// The HTLC success tx has a CSV lock that we must wait for, and if
// this is a lease enforced channel and we're the imitator, we may need
// to wait for longer.
waitHeight := h.deriveWaitHeight(h.htlcResolution.CsvDelay, commitSpend)
// Now that the sweeper has broadcasted the second-level transaction,
// it has confirmed, and we have checkpointed our state, we'll sweep
// the second level output. We report the resolver has moved the next
// stage.
h.reportLock.Lock()
h.currentReport.Stage = 2
h.currentReport.MaturityHeight = waitHeight
h.reportLock.Unlock()
if h.hasCLTV() {
log.Infof("%T(%x): waiting for CSV and CLTV lock to expire at "+
"height %v", h, h.htlc.RHash[:], waitHeight)
} else {
log.Infof("%T(%x): waiting for CSV lock to expire at height %v",
h, h.htlc.RHash[:], waitHeight)
}
// We'll use this input index to determine the second-level output
// index on the transaction, as the signatures requires the indexes to
// be the same. We don't look for the second-level output script
// directly, as there might be more than one HTLC output to the same
// pkScript.
op := &wire.OutPoint{
Hash: *commitSpend.SpenderTxHash,
Index: commitSpend.SpenderInputIndex,
}
// Let the sweeper sweep the second-level output now that the
// CSV/CLTV locks have expired.
var witType input.StandardWitnessType
if h.isTaproot() {
witType = input.TaprootHtlcAcceptedSuccessSecondLevel
} else {
witType = input.HtlcAcceptedSuccessSecondLevel
}
inp := h.makeSweepInput(
op, witType,
input.LeaseHtlcAcceptedSuccessSecondLevel,
&h.htlcResolution.SweepSignDesc,
h.htlcResolution.CsvDelay, uint32(commitSpend.SpendingHeight),
h.htlc.RHash, h.htlcResolution.ResolutionBlob,
)
// Calculate the budget for this sweep.
budget := calculateBudget(
btcutil.Amount(inp.SignDesc().Output.Value),
h.Budget.NoDeadlineHTLCRatio,
h.Budget.NoDeadlineHTLC,
)
log.Infof("%T(%x): offering second-level success tx output to sweeper "+
"with no deadline and budget=%v at height=%v", h,
h.htlc.RHash[:], budget, waitHeight)
// TODO(yy): use the result chan returned from SweepInput.
_, err = h.Sweeper.SweepInput(
inp,
sweep.Params{
Budget: budget,
// For second level success tx, there's no rush to get
// it confirmed, so we use a nil deadline.
DeadlineHeight: fn.None[int32](),
},
)
return err
}
// resolveLegacySuccessTx handles an HTLC output from a pre-anchor type channel
// by broadcasting the second-level success transaction.
func (h *htlcSuccessResolver) resolveLegacySuccessTx() error {
// Otherwise we'll publish the second-level transaction directly and
// offer the resolution to the nursery to handle.
h.log.Infof("broadcasting legacy second-level success tx: %v",
h.htlcResolution.SignedSuccessTx.TxHash())
// We'll now broadcast the second layer transaction so we can kick off
// the claiming process.
//
// TODO(yy): offer it to the sweeper instead.
label := labels.MakeLabel(
labels.LabelTypeChannelClose, &h.ShortChanID,
)
err := h.PublishTx(h.htlcResolution.SignedSuccessTx, label)
if err != nil {
return err
}
// Fast-forward to resolve the output from the success tx if the it has
// already been sent to the UtxoNursery.
if h.outputIncubating {
return h.resolveSuccessTxOutput(h.htlcResolution.ClaimOutpoint)
}
h.log.Infof("incubating incoming htlc output")
// Send the output to the incubator.
err = h.IncubateOutputs(
h.ChanPoint, fn.None[lnwallet.OutgoingHtlcResolution](),
fn.Some(h.htlcResolution),
h.broadcastHeight, fn.Some(int32(h.htlc.RefundTimeout)),
)
if err != nil {
return err
}
// Mark the output as incubating and checkpoint it.
h.outputIncubating = true
if err := h.Checkpoint(h); err != nil {
return err
}
// Move to resolve the output.
return h.resolveSuccessTxOutput(h.htlcResolution.ClaimOutpoint)
}
// resolveSuccessTx waits for the sweeping tx of the second-level success tx to
// confirm and offers the output from the success tx to the sweeper.
func (h *htlcSuccessResolver) resolveSuccessTx() error {
h.log.Infof("waiting for 2nd-level HTLC success transaction to confirm")
// Create aliases to make the code more readable.
outpoint := h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint
pkScript := h.htlcResolution.SignDetails.SignDesc.Output.PkScript
// Wait for the second level transaction to confirm.
commitSpend, err := waitForSpend(
&outpoint, pkScript, h.broadcastHeight, h.Notifier, h.quit,
)
if err != nil {
return err
}
// We'll use this input index to determine the second-level output
// index on the transaction, as the signatures requires the indexes to
// be the same. We don't look for the second-level output script
// directly, as there might be more than one HTLC output to the same
// pkScript.
op := wire.OutPoint{
Hash: *commitSpend.SpenderTxHash,
Index: commitSpend.SpenderInputIndex,
}
// If the 2nd-stage sweeping has already been started, we can
// fast-forward to start the resolving process for the stage two
// output.
if h.outputIncubating {
return h.resolveSuccessTxOutput(op)
}
// Now that the second-level transaction has confirmed, we checkpoint
// the state so we'll go to the next stage in case of restarts.
h.outputIncubating = true
if err := h.Checkpoint(h); err != nil {
log.Errorf("unable to Checkpoint: %v", err)
return err
}
h.log.Infof("2nd-level HTLC success tx=%v confirmed",
commitSpend.SpenderTxHash)
// Send the sweep request for the output from the success tx.
if err := h.sweepSuccessTxOutput(); err != nil {
return err
}
return h.resolveSuccessTxOutput(op)
}
// resolveSuccessTxOutput waits for the spend of the output from the 2nd-level
// success tx.
func (h *htlcSuccessResolver) resolveSuccessTxOutput(op wire.OutPoint) error {
// To wrap this up, we'll wait until the second-level transaction has
// been spent, then fully resolve the contract.
log.Infof("%T(%x): waiting for second-level HTLC output to be spent "+
"after csv_delay=%v", h, h.htlc.RHash[:],
h.htlcResolution.CsvDelay)
spend, err := waitForSpend(
&op, h.htlcResolution.SweepSignDesc.Output.PkScript,
h.broadcastHeight, h.Notifier, h.quit,
)
if err != nil {
return err
}
h.reportLock.Lock()
h.currentReport.RecoveredBalance = h.currentReport.LimboBalance
h.currentReport.LimboBalance = 0
h.reportLock.Unlock()
return h.checkpointClaim(spend.SpenderTxHash)
}
// Launch creates an input based on the details of the incoming htlc resolution
// and offers it to the sweeper.
func (h *htlcSuccessResolver) Launch() error {
if h.isLaunched() {
h.log.Tracef("already launched")
return nil
}
h.log.Debugf("launching resolver...")
h.markLaunched()
switch {
// If we're already resolved, then we can exit early.
case h.IsResolved():
h.log.Errorf("already resolved")
return nil
// If this is an output on the remote party's commitment transaction,
// use the direct-spend path.
case h.isRemoteCommitOutput():
return h.sweepRemoteCommitOutput()
// If this is an anchor type channel, we now sweep either the
// second-level success tx or the output from the second-level success
// tx.
case h.isZeroFeeOutput():
// If the second-level success tx has already been swept, we
// can go ahead and sweep its output.
if h.outputIncubating {
return h.sweepSuccessTxOutput()
}
// Otherwise, sweep the second level tx.
return h.sweepSuccessTx()
// If this is a legacy channel type, the output is handled by the
// nursery via the Resolve so we do nothing here.
//
// TODO(yy): handle the legacy output by offering it to the sweeper.
default:
return nil
}
}
package contractcourt
import (
"encoding/binary"
"fmt"
"io"
"sync"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/sweep"
)
// htlcTimeoutResolver is a ContractResolver that's capable of resolving an
// outgoing HTLC. The HTLC may be on our commitment transaction, or on the
// commitment transaction of the remote party. An output on our commitment
// transaction is considered fully resolved once the second-level transaction
// has been confirmed (and reached a sufficient depth). An output on the
// commitment transaction of the remote party is resolved once we detect a
// spend of the direct HTLC output using the timeout clause.
type htlcTimeoutResolver struct {
// htlcResolution contains all the information required to properly
// resolve this outgoing HTLC.
htlcResolution lnwallet.OutgoingHtlcResolution
// outputIncubating returns true if we've sent the output to the output
// incubator (utxo nursery).
outputIncubating bool
// broadcastHeight is the height that the original contract was
// broadcast to the main-chain at. We'll use this value to bound any
// historical queries to the chain for spends/confirmations.
//
// TODO(roasbeef): wrap above into definite resolution embedding?
broadcastHeight uint32
// htlc contains information on the htlc that we are resolving on-chain.
htlc channeldb.HTLC
// currentReport stores the current state of the resolver for reporting
// over the rpc interface. This should only be reported in case we have
// a non-nil SignDetails on the htlcResolution, otherwise the nursery
// will produce reports.
currentReport ContractReport
// reportLock prevents concurrent access to the resolver report.
reportLock sync.Mutex
contractResolverKit
htlcLeaseResolver
// incomingHTLCExpiryHeight is the absolute block height at which the
// incoming HTLC will expire. This is used as the deadline height as
// the outgoing HTLC must be swept before its incoming HTLC expires.
incomingHTLCExpiryHeight fn.Option[int32]
}
// newTimeoutResolver instantiates a new timeout htlc resolver.
func newTimeoutResolver(res lnwallet.OutgoingHtlcResolution,
broadcastHeight uint32, htlc channeldb.HTLC,
resCfg ResolverConfig) *htlcTimeoutResolver {
h := &htlcTimeoutResolver{
contractResolverKit: *newContractResolverKit(resCfg),
htlcResolution: res,
broadcastHeight: broadcastHeight,
htlc: htlc,
}
h.initReport()
h.initLogger(fmt.Sprintf("%T(%v)", h, h.outpoint()))
return h
}
// isTaproot returns true if the htlc output is a taproot output.
func (h *htlcTimeoutResolver) isTaproot() bool {
return txscript.IsPayToTaproot(
h.htlcResolution.SweepSignDesc.Output.PkScript,
)
}
// outpoint returns the outpoint of the HTLC output we're attempting to sweep.
func (h *htlcTimeoutResolver) outpoint() wire.OutPoint {
// The primary key for this resolver will be the outpoint of the HTLC
// on the commitment transaction itself. If this is our commitment,
// then the output can be found within the signed timeout tx,
// otherwise, it's just the ClaimOutpoint.
if h.htlcResolution.SignedTimeoutTx != nil {
return h.htlcResolution.SignedTimeoutTx.TxIn[0].PreviousOutPoint
}
return h.htlcResolution.ClaimOutpoint
}
// ResolverKey returns an identifier which should be globally unique for this
// particular resolver within the chain the original contract resides within.
//
// NOTE: Part of the ContractResolver interface.
func (h *htlcTimeoutResolver) ResolverKey() []byte {
key := newResolverID(h.outpoint())
return key[:]
}
const (
// expectedRemoteWitnessSuccessSize is the expected size of the witness
// on the remote commitment transaction for an outgoing HTLC that is
// swept on-chain by them with pre-image.
expectedRemoteWitnessSuccessSize = 5
// expectedLocalWitnessSuccessSize is the expected size of the witness
// on the local commitment transaction for an outgoing HTLC that is
// swept on-chain by them with pre-image.
expectedLocalWitnessSuccessSize = 3
// remotePreimageIndex index within the witness on the remote
// commitment transaction that will hold they pre-image if they go to
// sweep it on chain.
remotePreimageIndex = 3
// localPreimageIndex is the index within the witness on the local
// commitment transaction for an outgoing HTLC that will hold the
// pre-image if the remote party sweeps it.
localPreimageIndex = 1
// remoteTaprootWitnessSuccessSize is the expected size of the witness
// on the remote commitment for taproot channels. The spend path will
// look like
// - <sender sig> <receiver sig> <preimage> <success_script>
// <control_block>
remoteTaprootWitnessSuccessSize = 5
// localTaprootWitnessSuccessSize is the expected size of the witness
// on the local commitment for taproot channels. The spend path will
// look like
// - <receiver sig> <preimage> <success_script> <control_block>
localTaprootWitnessSuccessSize = 4
// taprootRemotePreimageIndex is the index within the witness on the
// taproot remote commitment spend that'll hold the pre-image if the
// remote party sweeps it.
taprootRemotePreimageIndex = 2
)
// claimCleanUp is a helper method that's called once the HTLC output is spent
// by the remote party. It'll extract the preimage, add it to the global cache,
// and finally send the appropriate clean up message.
func (h *htlcTimeoutResolver) claimCleanUp(
commitSpend *chainntnfs.SpendDetail) error {
// Depending on if this is our commitment or not, then we'll be looking
// for a different witness pattern.
spenderIndex := commitSpend.SpenderInputIndex
spendingInput := commitSpend.SpendingTx.TxIn[spenderIndex]
log.Infof("%T(%v): extracting preimage! remote party spent "+
"HTLC with tx=%v", h, h.htlcResolution.ClaimOutpoint,
spew.Sdump(commitSpend.SpendingTx))
// If this is the remote party's commitment, then we'll be looking for
// them to spend using the second-level success transaction.
var preimageBytes []byte
switch {
// For taproot channels, if the remote party has swept the HTLC, then
// the witness stack will look like:
//
// - <sender sig> <receiver sig> <preimage> <success_script>
// <control_block>
case h.isTaproot() && h.htlcResolution.SignedTimeoutTx == nil:
//nolint:ll
preimageBytes = spendingInput.Witness[taprootRemotePreimageIndex]
// The witness stack when the remote party sweeps the output on a
// regular channel to them looks like:
//
// - <0> <sender sig> <recvr sig> <preimage> <witness script>
case !h.isTaproot() && h.htlcResolution.SignedTimeoutTx == nil:
preimageBytes = spendingInput.Witness[remotePreimageIndex]
// If this is a taproot channel, and there's only a single witness
// element, then we're actually on the losing side of a breach
// attempt...
case h.isTaproot() && len(spendingInput.Witness) == 1:
return fmt.Errorf("breach attempt failed")
// Otherwise, they'll be spending directly from our commitment output.
// In which case the witness stack looks like:
//
// - <sig> <preimage> <witness script>
//
// For taproot channels, this looks like:
// - <receiver sig> <preimage> <success_script> <control_block>
//
// So we can target the same index.
default:
preimageBytes = spendingInput.Witness[localPreimageIndex]
}
preimage, err := lntypes.MakePreimage(preimageBytes)
if err != nil {
return fmt.Errorf("unable to create pre-image from witness: %w",
err)
}
log.Infof("%T(%v): extracting preimage=%v from on-chain "+
"spend!", h, h.htlcResolution.ClaimOutpoint, preimage)
// With the preimage obtained, we can now add it to the global cache.
if err := h.PreimageDB.AddPreimages(preimage); err != nil {
log.Errorf("%T(%v): unable to add witness to cache",
h, h.htlcResolution.ClaimOutpoint)
}
var pre [32]byte
copy(pre[:], preimage[:])
// Finally, we'll send the clean up message, mark ourselves as
// resolved, then exit.
if err := h.DeliverResolutionMsg(ResolutionMsg{
SourceChan: h.ShortChanID,
HtlcIndex: h.htlc.HtlcIndex,
PreImage: &pre,
}); err != nil {
return err
}
h.markResolved()
// Checkpoint our resolver with a report which reflects the preimage
// claim by the remote party.
amt := btcutil.Amount(h.htlcResolution.SweepSignDesc.Output.Value)
report := &channeldb.ResolverReport{
OutPoint: h.htlcResolution.ClaimOutpoint,
Amount: amt,
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
ResolverOutcome: channeldb.ResolverOutcomeClaimed,
SpendTxID: commitSpend.SpenderTxHash,
}
return h.Checkpoint(h, report)
}
// chainDetailsToWatch returns the output and script which we use to watch for
// spends from the direct HTLC output on the commitment transaction.
func (h *htlcTimeoutResolver) chainDetailsToWatch() (*wire.OutPoint, []byte, error) {
// If there's no timeout transaction, it means we are spending from a
// remote commit, then the claim output is the output directly on the
// commitment transaction, so we'll just use that.
if h.htlcResolution.SignedTimeoutTx == nil {
outPointToWatch := h.htlcResolution.ClaimOutpoint
scriptToWatch := h.htlcResolution.SweepSignDesc.Output.PkScript
return &outPointToWatch, scriptToWatch, nil
}
// If SignedTimeoutTx is not nil, this is the local party's commitment,
// and we'll need to grab watch the output that our timeout transaction
// points to. We can directly grab the outpoint, then also extract the
// witness script (the last element of the witness stack) to
// re-construct the pkScript we need to watch.
//
//nolint:ll
outPointToWatch := h.htlcResolution.SignedTimeoutTx.TxIn[0].PreviousOutPoint
witness := h.htlcResolution.SignedTimeoutTx.TxIn[0].Witness
var (
scriptToWatch []byte
err error
)
switch {
// For taproot channels, then final witness element is the control
// block, and the one before it the witness script. We can use both of
// these together to reconstruct the taproot output key, then map that
// into a v1 witness program.
case h.isTaproot():
// First, we'll parse the control block into something we can
// use.
ctrlBlockBytes := witness[len(witness)-1]
ctrlBlock, err := txscript.ParseControlBlock(ctrlBlockBytes)
if err != nil {
return nil, nil, err
}
// With the control block, we'll grab the witness script, then
// use that to derive the tapscript root.
witnessScript := witness[len(witness)-2]
tapscriptRoot := ctrlBlock.RootHash(witnessScript)
// Once we have the root, then we can derive the output key
// from the internal key, then turn that into a witness
// program.
outputKey := txscript.ComputeTaprootOutputKey(
ctrlBlock.InternalKey, tapscriptRoot,
)
scriptToWatch, err = txscript.PayToTaprootScript(outputKey)
if err != nil {
return nil, nil, err
}
// For regular channels, the witness script is the last element on the
// stack. We can then use this to re-derive the output that we're
// watching on chain.
default:
scriptToWatch, err = input.WitnessScriptHash(
witness[len(witness)-1],
)
}
if err != nil {
return nil, nil, err
}
return &outPointToWatch, scriptToWatch, nil
}
// isPreimageSpend returns true if the passed spend on the specified commitment
// is a success spend that reveals the pre-image or not.
func isPreimageSpend(isTaproot bool, spend *chainntnfs.SpendDetail,
localCommit bool) bool {
// Based on the spending input index and transaction, obtain the
// witness that tells us what type of spend this is.
spenderIndex := spend.SpenderInputIndex
spendingInput := spend.SpendingTx.TxIn[spenderIndex]
spendingWitness := spendingInput.Witness
switch {
// If this is a taproot remote commitment, then we can detect the type
// of spend via the leaf revealed in the control block and the witness
// itself.
//
// The keyspend (revocation path) is just a single signature, while the
// timeout and success paths are most distinct.
//
// The success path will look like:
//
// - <sender sig> <receiver sig> <preimage> <success_script>
// <control_block>
case isTaproot && !localCommit:
return checkSizeAndIndex(
spendingWitness, remoteTaprootWitnessSuccessSize,
taprootRemotePreimageIndex,
)
// Otherwise, then if this is our local commitment transaction, then if
// they're sweeping the transaction, it'll be directly from the output,
// skipping the second level.
//
// In this case, then there're two main tapscript paths, with the
// success case look like:
//
// - <receiver sig> <preimage> <success_script> <control_block>
case isTaproot && localCommit:
return checkSizeAndIndex(
spendingWitness, localTaprootWitnessSuccessSize,
localPreimageIndex,
)
// If this is the non-taproot, remote commitment then the only possible
// spends for outgoing HTLCs are:
//
// RECVR: <0> <sender sig> <recvr sig> <preimage> (2nd level success spend)
// REVOK: <sig> <key>
// SENDR: <sig> 0
//
// In this case, if 5 witness elements are present (factoring the
// witness script), and the 3rd element is the size of the pre-image,
// then this is a remote spend. If not, then we swept it ourselves, or
// revoked their output.
case !isTaproot && !localCommit:
return checkSizeAndIndex(
spendingWitness, expectedRemoteWitnessSuccessSize,
remotePreimageIndex,
)
// Otherwise, for our non-taproot commitment, the only possible spends
// for an outgoing HTLC are:
//
// SENDR: <0> <sendr sig> <recvr sig> <0> (2nd level timeout)
// RECVR: <recvr sig> <preimage>
// REVOK: <revoke sig> <revoke key>
//
// So the only success case has the pre-image as the 2nd (index 1)
// element in the witness.
case !isTaproot:
fallthrough
default:
return checkSizeAndIndex(
spendingWitness, expectedLocalWitnessSuccessSize,
localPreimageIndex,
)
}
}
// checkSizeAndIndex checks that the witness is of the expected size and that
// the witness element at the specified index is of the expected size.
func checkSizeAndIndex(witness wire.TxWitness, size, index int) bool {
if len(witness) != size {
return false
}
return len(witness[index]) == lntypes.HashSize
}
// Resolve kicks off full resolution of an outgoing HTLC output. If it's our
// commitment, it isn't resolved until we see the second level HTLC txn
// confirmed. If it's the remote party's commitment, we don't resolve until we
// see a direct sweep via the timeout clause.
//
// NOTE: Part of the ContractResolver interface.
func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
// If we're already resolved, then we can exit early.
if h.IsResolved() {
h.log.Errorf("already resolved")
return nil, nil
}
// If this is an output on the remote party's commitment transaction,
// use the direct-spend path to sweep the htlc.
if h.isRemoteCommitOutput() {
return nil, h.resolveRemoteCommitOutput()
}
// If this is a zero-fee HTLC, we now handle the spend from our
// commitment transaction.
if h.isZeroFeeOutput() {
return nil, h.resolveTimeoutTx()
}
// If this is an output on our own commitment using pre-anchor channel
// type, we will let the utxo nursery handle it.
return nil, h.resolveSecondLevelTxLegacy()
}
// sweepTimeoutTx sends a second level timeout transaction to the sweeper.
// This transaction uses the SINGLE|ANYONECANPAY flag.
func (h *htlcTimeoutResolver) sweepTimeoutTx() error {
var inp input.Input
if h.isTaproot() {
inp = lnutils.Ptr(input.MakeHtlcSecondLevelTimeoutTaprootInput(
h.htlcResolution.SignedTimeoutTx,
h.htlcResolution.SignDetails,
h.broadcastHeight,
input.WithResolutionBlob(
h.htlcResolution.ResolutionBlob,
),
))
} else {
inp = lnutils.Ptr(input.MakeHtlcSecondLevelTimeoutAnchorInput(
h.htlcResolution.SignedTimeoutTx,
h.htlcResolution.SignDetails,
h.broadcastHeight,
))
}
// Calculate the budget.
budget := calculateBudget(
btcutil.Amount(inp.SignDesc().Output.Value),
h.Budget.DeadlineHTLCRatio, h.Budget.DeadlineHTLC,
)
h.log.Infof("offering 2nd-level HTLC timeout tx to sweeper "+
"with deadline=%v, budget=%v", h.incomingHTLCExpiryHeight,
budget)
// For an outgoing HTLC, it must be swept before the RefundTimeout of
// its incoming HTLC is reached.
_, err := h.Sweeper.SweepInput(
inp,
sweep.Params{
Budget: budget,
DeadlineHeight: h.incomingHTLCExpiryHeight,
},
)
if err != nil {
return err
}
return nil
}
// resolveSecondLevelTxLegacy sends a second level timeout transaction to the
// utxo nursery. This transaction uses the legacy SIGHASH_ALL flag.
func (h *htlcTimeoutResolver) resolveSecondLevelTxLegacy() error {
h.log.Debug("incubating htlc output")
// The utxo nursery will take care of broadcasting the second-level
// timeout tx and sweeping its output once it confirms.
err := h.IncubateOutputs(
h.ChanPoint, fn.Some(h.htlcResolution),
fn.None[lnwallet.IncomingHtlcResolution](),
h.broadcastHeight, h.incomingHTLCExpiryHeight,
)
if err != nil {
return err
}
return h.resolveTimeoutTx()
}
// sweepDirectHtlcOutput sends the direct spend of the HTLC output to the
// sweeper. This is used when the remote party goes on chain, and we're able to
// sweep an HTLC we offered after a timeout. Only the CLTV encumbered outputs
// are resolved via this path.
func (h *htlcTimeoutResolver) sweepDirectHtlcOutput() error {
var htlcWitnessType input.StandardWitnessType
if h.isTaproot() {
htlcWitnessType = input.TaprootHtlcOfferedRemoteTimeout
} else {
htlcWitnessType = input.HtlcOfferedRemoteTimeout
}
sweepInput := input.NewCsvInputWithCltv(
&h.htlcResolution.ClaimOutpoint, htlcWitnessType,
&h.htlcResolution.SweepSignDesc, h.broadcastHeight,
h.htlcResolution.CsvDelay, h.htlcResolution.Expiry,
input.WithResolutionBlob(h.htlcResolution.ResolutionBlob),
)
// Calculate the budget.
budget := calculateBudget(
btcutil.Amount(sweepInput.SignDesc().Output.Value),
h.Budget.DeadlineHTLCRatio, h.Budget.DeadlineHTLC,
)
log.Infof("%T(%x): offering offered remote timeout HTLC output to "+
"sweeper with deadline %v and budget=%v at height=%v",
h, h.htlc.RHash[:], h.incomingHTLCExpiryHeight, budget,
h.broadcastHeight)
_, err := h.Sweeper.SweepInput(
sweepInput,
sweep.Params{
Budget: budget,
// This is an outgoing HTLC, so we want to make sure
// that we sweep it before the incoming HTLC expires.
DeadlineHeight: h.incomingHTLCExpiryHeight,
},
)
if err != nil {
return err
}
return nil
}
// watchHtlcSpend watches for a spend of the HTLC output. For neutrino backend,
// it will check blocks for the confirmed spend. For btcd and bitcoind, it will
// check both the mempool and the blocks.
func (h *htlcTimeoutResolver) watchHtlcSpend() (*chainntnfs.SpendDetail,
error) {
// TODO(yy): outpointToWatch is always h.HtlcOutpoint(), can refactor
// to remove the redundancy.
outpointToWatch, scriptToWatch, err := h.chainDetailsToWatch()
if err != nil {
return nil, err
}
// If there's no mempool configured, which is the case for SPV node
// such as neutrino, then we will watch for confirmed spend only.
if h.Mempool == nil {
return h.waitForConfirmedSpend(outpointToWatch, scriptToWatch)
}
// Watch for a spend of the HTLC output in both the mempool and blocks.
return h.waitForMempoolOrBlockSpend(*outpointToWatch, scriptToWatch)
}
// waitForConfirmedSpend waits for the HTLC output to be spent and confirmed in
// a block, returns the spend details.
func (h *htlcTimeoutResolver) waitForConfirmedSpend(op *wire.OutPoint,
pkScript []byte) (*chainntnfs.SpendDetail, error) {
// We'll block here until either we exit, or the HTLC output on the
// commitment transaction has been spent.
spend, err := waitForSpend(
op, pkScript, h.broadcastHeight, h.Notifier, h.quit,
)
if err != nil {
return nil, err
}
return spend, nil
}
// Stop signals the resolver to cancel any current resolution processes, and
// suspend.
//
// NOTE: Part of the ContractResolver interface.
func (h *htlcTimeoutResolver) Stop() {
h.log.Debugf("stopping...")
defer h.log.Debugf("stopped")
close(h.quit)
}
// report returns a report on the resolution state of the contract.
func (h *htlcTimeoutResolver) report() *ContractReport {
// If we have a SignedTimeoutTx but no SignDetails, this is a local
// commitment for a non-anchor channel, which was handled by the utxo
// nursery.
if h.htlcResolution.SignDetails == nil && h.
htlcResolution.SignedTimeoutTx != nil {
return nil
}
h.reportLock.Lock()
defer h.reportLock.Unlock()
cpy := h.currentReport
return &cpy
}
func (h *htlcTimeoutResolver) initReport() {
// We create the initial report. This will only be reported for
// resolvers not handled by the nursery.
finalAmt := h.htlc.Amt.ToSatoshis()
if h.htlcResolution.SignedTimeoutTx != nil {
finalAmt = btcutil.Amount(
h.htlcResolution.SignedTimeoutTx.TxOut[0].Value,
)
}
// If there's no timeout transaction, then we're already effectively in
// level two.
stage := uint32(1)
if h.htlcResolution.SignedTimeoutTx == nil {
stage = 2
}
h.currentReport = ContractReport{
Outpoint: h.htlcResolution.ClaimOutpoint,
Type: ReportOutputOutgoingHtlc,
Amount: finalAmt,
MaturityHeight: h.htlcResolution.Expiry,
LimboBalance: finalAmt,
Stage: stage,
}
}
// Encode writes an encoded version of the ContractResolver into the passed
// Writer.
//
// NOTE: Part of the ContractResolver interface.
func (h *htlcTimeoutResolver) Encode(w io.Writer) error {
// First, we'll write out the relevant fields of the
// OutgoingHtlcResolution to the writer.
if err := encodeOutgoingResolution(w, &h.htlcResolution); err != nil {
return err
}
// With that portion written, we can now write out the fields specific
// to the resolver itself.
if err := binary.Write(w, endian, h.outputIncubating); err != nil {
return err
}
if err := binary.Write(w, endian, h.IsResolved()); err != nil {
return err
}
if err := binary.Write(w, endian, h.broadcastHeight); err != nil {
return err
}
if err := binary.Write(w, endian, h.htlc.HtlcIndex); err != nil {
return err
}
// We encode the sign details last for backwards compatibility.
err := encodeSignDetails(w, h.htlcResolution.SignDetails)
if err != nil {
return err
}
return nil
}
// newTimeoutResolverFromReader attempts to decode an encoded ContractResolver
// from the passed Reader instance, returning an active ContractResolver
// instance.
func newTimeoutResolverFromReader(r io.Reader, resCfg ResolverConfig) (
*htlcTimeoutResolver, error) {
h := &htlcTimeoutResolver{
contractResolverKit: *newContractResolverKit(resCfg),
}
// First, we'll read out all the mandatory fields of the
// OutgoingHtlcResolution that we store.
if err := decodeOutgoingResolution(r, &h.htlcResolution); err != nil {
return nil, err
}
// With those fields read, we can now read back the fields that are
// specific to the resolver itself.
if err := binary.Read(r, endian, &h.outputIncubating); err != nil {
return nil, err
}
var resolved bool
if err := binary.Read(r, endian, &resolved); err != nil {
return nil, err
}
if resolved {
h.markResolved()
}
if err := binary.Read(r, endian, &h.broadcastHeight); err != nil {
return nil, err
}
if err := binary.Read(r, endian, &h.htlc.HtlcIndex); err != nil {
return nil, err
}
// Sign details is a new field that was added to the htlc resolution,
// so it is serialized last for backwards compatibility. We try to read
// it, but don't error out if there are not bytes left.
signDetails, err := decodeSignDetails(r)
if err == nil {
h.htlcResolution.SignDetails = signDetails
} else if err != io.EOF && err != io.ErrUnexpectedEOF {
return nil, err
}
h.initReport()
h.initLogger(fmt.Sprintf("%T(%v)", h, h.outpoint()))
return h, nil
}
// Supplement adds additional information to the resolver that is required
// before Resolve() is called.
//
// NOTE: Part of the htlcContractResolver interface.
func (h *htlcTimeoutResolver) Supplement(htlc channeldb.HTLC) {
h.htlc = htlc
}
// HtlcPoint returns the htlc's outpoint on the commitment tx.
//
// NOTE: Part of the htlcContractResolver interface.
func (h *htlcTimeoutResolver) HtlcPoint() wire.OutPoint {
return h.htlcResolution.HtlcPoint()
}
// SupplementDeadline sets the incomingHTLCExpiryHeight for this outgoing htlc
// resolver.
//
// NOTE: Part of the htlcContractResolver interface.
func (h *htlcTimeoutResolver) SupplementDeadline(d fn.Option[int32]) {
h.incomingHTLCExpiryHeight = d
}
// A compile time assertion to ensure htlcTimeoutResolver meets the
// ContractResolver interface.
var _ htlcContractResolver = (*htlcTimeoutResolver)(nil)
// spendResult is used to hold the result of a spend event from either a
// mempool spend or a block spend.
type spendResult struct {
// spend contains the details of the spend.
spend *chainntnfs.SpendDetail
// err is the error that occurred during the spend notification.
err error
}
// waitForMempoolOrBlockSpend waits for the htlc output to be spent by a
// transaction that's either be found in the mempool or in a block.
func (h *htlcTimeoutResolver) waitForMempoolOrBlockSpend(op wire.OutPoint,
pkScript []byte) (*chainntnfs.SpendDetail, error) {
log.Infof("%T(%v): waiting for spent of HTLC output %v to be found "+
"in mempool or block", h, h.htlcResolution.ClaimOutpoint, op)
// Subscribe for block spent(confirmed).
blockSpent, err := h.Notifier.RegisterSpendNtfn(
&op, pkScript, h.broadcastHeight,
)
if err != nil {
return nil, fmt.Errorf("register spend: %w", err)
}
// Subscribe for mempool spent(unconfirmed).
mempoolSpent, err := h.Mempool.SubscribeMempoolSpent(op)
if err != nil {
return nil, fmt.Errorf("register mempool spend: %w", err)
}
// Create a result chan that will be used to receive the spending
// events.
result := make(chan *spendResult, 2)
// Create a goroutine that will wait for either a mempool spend or a
// block spend.
//
// NOTE: no need to use waitgroup here as when the resolver exits, the
// goroutine will return on the quit channel.
go h.consumeSpendEvents(result, blockSpent.Spend, mempoolSpent.Spend)
// Wait for the spend event to be received.
select {
case event := <-result:
// Cancel the mempool subscription as we don't need it anymore.
h.Mempool.CancelMempoolSpendEvent(mempoolSpent)
return event.spend, event.err
case <-h.quit:
return nil, errResolverShuttingDown
}
}
// consumeSpendEvents consumes the spend events from the block and mempool
// subscriptions. It exits when a spend event is received from the block, or
// the resolver itself quits. When a spend event is received from the mempool,
// however, it won't exit but continuing to wait for a spend event from the
// block subscription.
//
// NOTE: there could be a case where we found the preimage in the mempool,
// which will be added to our preimage beacon and settle the incoming link,
// meanwhile the timeout sweep tx confirms. This outgoing HTLC is "free" money
// and is not swept here.
//
// TODO(yy): sweep the outgoing htlc if it's confirmed.
func (h *htlcTimeoutResolver) consumeSpendEvents(resultChan chan *spendResult,
blockSpent, mempoolSpent <-chan *chainntnfs.SpendDetail) {
op := h.HtlcPoint()
// Create a result chan to hold the results.
result := &spendResult{}
// Wait for a spend event to arrive.
for {
select {
// If a spend event is received from the block, this outgoing
// htlc is spent either by the remote via the preimage or by us
// via the timeout. We can exit the loop and `claimCleanUp`
// will feed the preimage to the beacon if found. This treats
// the block as the final judge and the preimage spent won't
// appear in the mempool afterwards.
//
// NOTE: if a reorg happens, the preimage spend can appear in
// the mempool again. Though a rare case, we should handle it
// in a dedicated reorg system.
case spendDetail, ok := <-blockSpent:
if !ok {
result.err = fmt.Errorf("block spent err: %w",
errResolverShuttingDown)
} else {
log.Debugf("Found confirmed spend of HTLC "+
"output %s in tx=%s", op,
spendDetail.SpenderTxHash)
result.spend = spendDetail
// Once confirmed, persist the state on disk if
// we haven't seen the output's spending tx in
// mempool before.
}
// Send the result and exit the loop.
resultChan <- result
return
// If a spend event is received from the mempool, this can be
// either the 2nd stage timeout tx or a preimage spend from the
// remote. We will further check whether the spend reveals the
// preimage and add it to the preimage beacon to settle the
// incoming link.
//
// NOTE: we won't exit the loop here so we can continue to
// watch for the block spend to check point the resolution.
case spendDetail, ok := <-mempoolSpent:
if !ok {
result.err = fmt.Errorf("mempool spent err: %w",
errResolverShuttingDown)
// This is an internal error so we exit.
resultChan <- result
return
}
log.Debugf("Found mempool spend of HTLC output %s "+
"in tx=%s", op, spendDetail.SpenderTxHash)
// Check whether the spend reveals the preimage, if not
// continue the loop.
hasPreimage := isPreimageSpend(
h.isTaproot(), spendDetail,
!h.isRemoteCommitOutput(),
)
if !hasPreimage {
log.Debugf("HTLC output %s spent doesn't "+
"reveal preimage", op)
continue
}
// Found the preimage spend, send the result and
// continue the loop.
result.spend = spendDetail
resultChan <- result
continue
// If the resolver exits, we exit the goroutine.
case <-h.quit:
result.err = errResolverShuttingDown
resultChan <- result
return
}
}
}
// isRemoteCommitOutput returns a bool to indicate whether the htlc output is
// on the remote commitment.
func (h *htlcTimeoutResolver) isRemoteCommitOutput() bool {
// If we don't have a timeout transaction, then this means that this is
// an output on the remote party's commitment transaction.
return h.htlcResolution.SignedTimeoutTx == nil
}
// isZeroFeeOutput returns a boolean indicating whether the htlc output is from
// a anchor-enabled channel, which uses the sighash SINGLE|ANYONECANPAY.
func (h *htlcTimeoutResolver) isZeroFeeOutput() bool {
// If we have non-nil SignDetails, this means it has a 2nd level HTLC
// transaction that is signed using sighash SINGLE|ANYONECANPAY (the
// case for anchor type channels). In this case we can re-sign it and
// attach fees at will.
return h.htlcResolution.SignedTimeoutTx != nil &&
h.htlcResolution.SignDetails != nil
}
// waitHtlcSpendAndCheckPreimage waits for the htlc output to be spent and
// checks whether the spending reveals the preimage. If the preimage is found,
// it will be added to the preimage beacon to settle the incoming link, and a
// nil spend details will be returned. Otherwise, the spend details will be
// returned, indicating this is a non-preimage spend.
func (h *htlcTimeoutResolver) waitHtlcSpendAndCheckPreimage() (
*chainntnfs.SpendDetail, error) {
// Wait for the htlc output to be spent, which can happen in one of the
// paths,
// 1. The remote party spends the htlc output using the preimage.
// 2. The local party spends the htlc timeout tx from the local
// commitment.
// 3. The local party spends the htlc output directlt from the remote
// commitment.
spend, err := h.watchHtlcSpend()
if err != nil {
return nil, err
}
// If the spend reveals the pre-image, then we'll enter the clean up
// workflow to pass the preimage back to the incoming link, add it to
// the witness cache, and exit.
if isPreimageSpend(h.isTaproot(), spend, !h.isRemoteCommitOutput()) {
return nil, h.claimCleanUp(spend)
}
return spend, nil
}
// sweepTimeoutTxOutput attempts to sweep the output of the second level
// timeout tx.
func (h *htlcTimeoutResolver) sweepTimeoutTxOutput() error {
h.log.Debugf("sweeping output %v from 2nd-level HTLC timeout tx",
h.htlcResolution.ClaimOutpoint)
// This should be non-blocking as we will only attempt to sweep the
// output when the second level tx has already been confirmed. In other
// words, waitHtlcSpendAndCheckPreimage will return immediately.
commitSpend, err := h.waitHtlcSpendAndCheckPreimage()
if err != nil {
return err
}
// Exit early if the spend is nil, as this means it's a remote spend
// using the preimage path, which is handled in claimCleanUp.
if commitSpend == nil {
h.log.Infof("preimage spend detected, skipping 2nd-level " +
"HTLC output sweep")
return nil
}
waitHeight := h.deriveWaitHeight(h.htlcResolution.CsvDelay, commitSpend)
// Now that the sweeper has broadcasted the second-level transaction,
// it has confirmed, and we have checkpointed our state, we'll sweep
// the second level output. We report the resolver has moved the next
// stage.
h.reportLock.Lock()
h.currentReport.Stage = 2
h.currentReport.MaturityHeight = waitHeight
h.reportLock.Unlock()
if h.hasCLTV() {
h.log.Infof("waiting for CSV and CLTV lock to expire at "+
"height %v", waitHeight)
} else {
h.log.Infof("waiting for CSV lock to expire at height %v",
waitHeight)
}
// We'll use this input index to determine the second-level output
// index on the transaction, as the signatures requires the indexes to
// be the same. We don't look for the second-level output script
// directly, as there might be more than one HTLC output to the same
// pkScript.
op := &wire.OutPoint{
Hash: *commitSpend.SpenderTxHash,
Index: commitSpend.SpenderInputIndex,
}
var witType input.StandardWitnessType
if h.isTaproot() {
witType = input.TaprootHtlcOfferedTimeoutSecondLevel
} else {
witType = input.HtlcOfferedTimeoutSecondLevel
}
// Let the sweeper sweep the second-level output now that the CSV/CLTV
// locks have expired.
inp := h.makeSweepInput(
op, witType,
input.LeaseHtlcOfferedTimeoutSecondLevel,
&h.htlcResolution.SweepSignDesc,
h.htlcResolution.CsvDelay, uint32(commitSpend.SpendingHeight),
h.htlc.RHash, h.htlcResolution.ResolutionBlob,
)
// Calculate the budget for this sweep.
budget := calculateBudget(
btcutil.Amount(inp.SignDesc().Output.Value),
h.Budget.NoDeadlineHTLCRatio,
h.Budget.NoDeadlineHTLC,
)
h.log.Infof("offering output from 2nd-level timeout tx to sweeper "+
"with no deadline and budget=%v", budget)
// TODO(yy): use the result chan returned from SweepInput to get the
// confirmation status of this sweeping tx so we don't need to make
// another subscription via `RegisterSpendNtfn` for this outpoint here
// in the resolver.
_, err = h.Sweeper.SweepInput(
inp,
sweep.Params{
Budget: budget,
// For second level success tx, there's no rush
// to get it confirmed, so we use a nil
// deadline.
DeadlineHeight: fn.None[int32](),
},
)
return err
}
// checkpointStageOne creates a checkpoint for the first stage of the htlc
// timeout transaction. This is used to ensure that the resolver can resume
// watching for the second stage spend in case of a restart.
func (h *htlcTimeoutResolver) checkpointStageOne(
spendTxid chainhash.Hash) error {
h.log.Debugf("checkpoint stage one spend of HTLC output %v, spent "+
"in tx %v", h.outpoint(), spendTxid)
// Now that the second-level transaction has confirmed, we checkpoint
// the state so we'll go to the next stage in case of restarts.
h.outputIncubating = true
// Create stage-one report.
report := &channeldb.ResolverReport{
OutPoint: h.outpoint(),
Amount: h.htlc.Amt.ToSatoshis(),
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
SpendTxID: &spendTxid,
}
// At this point, the second-level transaction is sufficiently
// confirmed. We can now send back our clean up message, failing the
// HTLC on the incoming link.
failureMsg := &lnwire.FailPermanentChannelFailure{}
err := h.DeliverResolutionMsg(ResolutionMsg{
SourceChan: h.ShortChanID,
HtlcIndex: h.htlc.HtlcIndex,
Failure: failureMsg,
})
if err != nil {
return err
}
return h.Checkpoint(h, report)
}
// checkpointClaim checkpoints the timeout resolver with the reports it needs.
func (h *htlcTimeoutResolver) checkpointClaim(
spendDetail *chainntnfs.SpendDetail) error {
h.log.Infof("resolving htlc with incoming fail msg, output=%v "+
"confirmed in tx=%v", spendDetail.SpentOutPoint,
spendDetail.SpenderTxHash)
// Create a resolver report for the claiming of the HTLC.
amt := btcutil.Amount(h.htlcResolution.SweepSignDesc.Output.Value)
report := &channeldb.ResolverReport{
OutPoint: *spendDetail.SpentOutPoint,
Amount: amt,
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
ResolverOutcome: channeldb.ResolverOutcomeTimeout,
SpendTxID: spendDetail.SpenderTxHash,
}
// Finally, we checkpoint the resolver with our report(s).
h.markResolved()
return h.Checkpoint(h, report)
}
// resolveRemoteCommitOutput handles sweeping an HTLC output on the remote
// commitment with via the timeout path. In this case we can sweep the output
// directly, and don't have to broadcast a second-level transaction.
func (h *htlcTimeoutResolver) resolveRemoteCommitOutput() error {
h.log.Debug("waiting for direct-timeout spend of the htlc to confirm")
// Wait for the direct-timeout HTLC sweep tx to confirm.
spend, err := h.watchHtlcSpend()
if err != nil {
return err
}
// If the spend reveals the preimage, then we'll enter the clean up
// workflow to pass the preimage back to the incoming link, add it to
// the witness cache, and exit.
if isPreimageSpend(h.isTaproot(), spend, !h.isRemoteCommitOutput()) {
return h.claimCleanUp(spend)
}
// Send the clean up msg to fail the incoming HTLC.
failureMsg := &lnwire.FailPermanentChannelFailure{}
err = h.DeliverResolutionMsg(ResolutionMsg{
SourceChan: h.ShortChanID,
HtlcIndex: h.htlc.HtlcIndex,
Failure: failureMsg,
})
if err != nil {
return err
}
// TODO(yy): should also update the `RecoveredBalance` and
// `LimboBalance` like other paths?
// Checkpoint the resolver, and write the outcome to disk.
return h.checkpointClaim(spend)
}
// resolveTimeoutTx waits for the sweeping tx of the second-level
// timeout tx to confirm and offers the output from the timeout tx to the
// sweeper.
func (h *htlcTimeoutResolver) resolveTimeoutTx() error {
h.log.Debug("waiting for first-stage 2nd-level HTLC timeout tx to " +
"confirm")
// Wait for the second level transaction to confirm.
spend, err := h.watchHtlcSpend()
if err != nil {
return err
}
// If the spend reveals the preimage, then we'll enter the clean up
// workflow to pass the preimage back to the incoming link, add it to
// the witness cache, and exit.
if isPreimageSpend(h.isTaproot(), spend, !h.isRemoteCommitOutput()) {
return h.claimCleanUp(spend)
}
op := h.htlcResolution.ClaimOutpoint
spenderTxid := *spend.SpenderTxHash
// If the timeout tx is a re-signed tx, we will need to find the actual
// spent outpoint from the spending tx.
if h.isZeroFeeOutput() {
op = wire.OutPoint{
Hash: spenderTxid,
Index: spend.SpenderInputIndex,
}
}
// If the 2nd-stage sweeping has already been started, we can
// fast-forward to start the resolving process for the stage two
// output.
if h.outputIncubating {
return h.resolveTimeoutTxOutput(op)
}
h.log.Infof("2nd-level HTLC timeout tx=%v confirmed", spenderTxid)
// Start the process to sweep the output from the timeout tx.
if h.isZeroFeeOutput() {
err = h.sweepTimeoutTxOutput()
if err != nil {
return err
}
}
// Create a checkpoint since the timeout tx is confirmed and the sweep
// request has been made.
if err := h.checkpointStageOne(spenderTxid); err != nil {
return err
}
// Start the resolving process for the stage two output.
return h.resolveTimeoutTxOutput(op)
}
// resolveTimeoutTxOutput waits for the spend of the output from the 2nd-level
// timeout tx.
func (h *htlcTimeoutResolver) resolveTimeoutTxOutput(op wire.OutPoint) error {
h.log.Debugf("waiting for second-stage 2nd-level timeout tx output %v "+
"to be spent after csv_delay=%v", op, h.htlcResolution.CsvDelay)
spend, err := waitForSpend(
&op, h.htlcResolution.SweepSignDesc.Output.PkScript,
h.broadcastHeight, h.Notifier, h.quit,
)
if err != nil {
return err
}
h.reportLock.Lock()
h.currentReport.RecoveredBalance = h.currentReport.LimboBalance
h.currentReport.LimboBalance = 0
h.reportLock.Unlock()
return h.checkpointClaim(spend)
}
// Launch creates an input based on the details of the outgoing htlc resolution
// and offers it to the sweeper.
func (h *htlcTimeoutResolver) Launch() error {
if h.isLaunched() {
h.log.Tracef("already launched")
return nil
}
h.log.Debugf("launching resolver...")
h.markLaunched()
switch {
// If we're already resolved, then we can exit early.
case h.IsResolved():
h.log.Errorf("already resolved")
return nil
// If this is an output on the remote party's commitment transaction,
// use the direct timeout spend path.
//
// NOTE: When the outputIncubating is false, it means that the output
// has been offered to the utxo nursery as starting in 0.18.4, we
// stopped marking this flag for direct timeout spends (#9062). In that
// case, we will do nothing and let the utxo nursery handle it.
case h.isRemoteCommitOutput() && !h.outputIncubating:
return h.sweepDirectHtlcOutput()
// If this is an anchor type channel, we now sweep either the
// second-level timeout tx or the output from the second-level timeout
// tx.
case h.isZeroFeeOutput():
// If the second-level timeout tx has already been swept, we
// can go ahead and sweep its output.
if h.outputIncubating {
return h.sweepTimeoutTxOutput()
}
// Otherwise, sweep the second level tx.
return h.sweepTimeoutTx()
// If this is an output on our own commitment using pre-anchor channel
// type, we will let the utxo nursery handle it via Resolve.
//
// TODO(yy): handle the legacy output by offering it to the sweeper.
default:
return nil
}
}
package contractcourt
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
var (
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
log btclog.Logger
// brarLog is the logger used by the breach arb.
brarLog btclog.Logger
// utxnLog is the logger used by the utxo nursary.
utxnLog btclog.Logger
)
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("CNCT", nil))
UseBreachLogger(build.NewSubLogger("BRAR", nil))
UseNurseryLogger(build.NewSubLogger("UTXN", nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
// UseBreachLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseBreachLogger(logger btclog.Logger) {
brarLog = logger
}
// UseNurseryLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseNurseryLogger(logger btclog.Logger) {
utxnLog = logger
}
package contractcourt
import (
"bytes"
"errors"
"fmt"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/kvdb"
)
// Overview of Nursery Store Storage Hierarchy
//
// CHAIN SEGMENTATION
//
// The root directory of a nursery store is bucketed by the chain hash and
// the 'utxn' prefix. This allows multiple utxo nurseries for distinct chains
// to simultaneously use the same channel.DB instance. This is critical for
// providing replay protection and more to isolate chain-specific data in the
// multichain setting.
//
// utxn<chain-hash>/
// |
// | CHANNEL INDEX
// |
// | The channel index contains a directory for each channel that has a
// | non-zero number of outputs being tracked by the nursery store.
// | Inside each channel directory are files containing serialized spendable
// | outputs that are awaiting some state transition. The name of each file
// | contains the outpoint of the spendable output in the file, and is
// | prefixed with 4-byte state prefix, indicating whether the spendable
// | output is a crib, preschool, or kindergarten, or graduated output. The
// | nursery store supports the ability to enumerate all outputs for a
// | particular channel, which is useful in constructing nursery reports.
// |
// ├── channel-index-key/
// │ ├── <chan-point-1>/ <- CHANNEL BUCKET
// | | ├── <state-prefix><outpoint-1>: <spendable-output-1>
// | | └── <state-prefix><outpoint-2>: <spendable-output-2>
// │ ├── <chan-point-2>/
// | | └── <state-prefix><outpoint-3>: <spendable-output-3>
// │ └── <chan-point-3>/
// | ├── <state-prefix><outpoint-4>: <spendable-output-4>
// | └── <state-prefix><outpoint-5>: <spendable-output-5>
// |
// | HEIGHT INDEX
// |
// | The height index contains a directory for each height at which the
// | nursery still has scheduled actions. If an output is a crib or
// | kindergarten output, it will have an associated entry in the height
// | index. Inside a particular height directory, the structure is similar
// | to that of the channel index, containing multiple channel directories,
// | each of which contains subdirectories named with a prefixed outpoint
// | belonging to the channel. Enumerating these combinations yields a
// | relative file path:
// | e.g. <chan-point-3>/<prefix><outpoint-2>/
// | that can be queried in the channel index to retrieve the serialized
// | output.
// |
// └── height-index-key/
// ├── <height-1>/ <- HEIGHT BUCKET
// | ├── <chan-point-3>/ <- HEIGHT-CHANNEL BUCKET
// | | ├── <state-prefix><outpoint-4>: "" <- PREFIXED OUTPOINT
// | | └── <state-prefix><outpoint-5>: ""
// | ├── <chan-point-2>/
// | | └── <state-prefix><outpoint-3>: ""
// └── <height-2>/
// └── <chan-point-1>/
// └── <state-prefix><outpoint-1>: ""
// └── <state-prefix><outpoint-2>: ""
// TODO(joostjager): Add database migration to clean up now unused last
// graduated height and finalized txes. This also prevents people downgrading
// and surprising the downgraded nursery with missing data.
// NurseryStorer abstracts the persistent storage layer for the utxo nursery.
// Concretely, it stores commitment and htlc outputs until any time-bounded
// constraints have fully matured. The store exposes methods for enumerating its
// contents, and persisting state transitions detected by the utxo nursery.
type NurseryStorer interface {
// Incubate registers a set of CSV delayed outputs (incoming HTLC's on
// our commitment transaction, or a commitment output), and a slice of
// outgoing htlc outputs to be swept back into the user's wallet. The
// event is persisted to disk, such that the nursery can resume the
// incubation process after a potential crash.
Incubate([]kidOutput, []babyOutput) error
// CribToKinder atomically moves a babyOutput in the crib bucket to the
// kindergarten bucket. Baby outputs are outgoing HTLC's which require
// us to go to the second-layer to claim. The now mature kidOutput
// contained in the babyOutput will be stored as it waits out the
// kidOutput's CSV delay.
CribToKinder(*babyOutput) error
// PreschoolToKinder atomically moves a kidOutput from the preschool
// bucket to the kindergarten bucket. This transition should be executed
// after receiving confirmation of the preschool output. Incoming HTLC's
// we need to go to the second-layer to claim, and also our commitment
// outputs fall into this class.
//
// An additional parameter specifies the last graduated height. This is
// used in case of late registration. It schedules the output for sweep
// at the next epoch even though it has already expired earlier.
PreschoolToKinder(kid *kidOutput, lastGradHeight uint32) error
// GraduateKinder atomically moves an output at the provided height into
// the graduated status. This involves removing the kindergarten entries
// from both the height and channel indexes. The height bucket will be
// opportunistically pruned from the height index as outputs are
// removed.
GraduateKinder(height uint32, output *kidOutput) error
// FetchPreschools returns a list of all outputs currently stored in
// the preschool bucket.
FetchPreschools() ([]kidOutput, error)
// FetchClass returns a list of kindergarten and crib outputs whose
// timelocks expire at the given height.
FetchClass(height uint32) ([]kidOutput, []babyOutput, error)
// HeightsBelowOrEqual returns the lowest non-empty heights in the
// height index, that exist at or below the provided upper bound.
HeightsBelowOrEqual(height uint32) ([]uint32, error)
// ForChanOutputs iterates over all outputs being incubated for a
// particular channel point. This method accepts a callback that allows
// the caller to process each key-value pair. The key will be a prefixed
// outpoint, and the value will be the serialized bytes for an output,
// whose type should be inferred from the key's prefix.
ForChanOutputs(*wire.OutPoint, func([]byte, []byte) error, func()) error
// ListChannels returns all channels the nursery is currently tracking.
ListChannels() ([]wire.OutPoint, error)
// IsMatureChannel determines the whether or not all of the outputs in a
// particular channel bucket have been marked as graduated.
IsMatureChannel(*wire.OutPoint) (bool, error)
// RemoveChannel channel erases all entries from the channel bucket for
// the provided channel point, this method should only be called if
// IsMatureChannel indicates the channel is ready for removal.
RemoveChannel(*wire.OutPoint) error
}
var (
// utxnChainPrefix is used to prefix a particular chain hash and create
// the root-level, chain-segmented bucket for each nursery store.
utxnChainPrefix = []byte("utxn")
// channelIndexKey is a static key used to lookup the bucket containing
// all of the nursery's active channels.
channelIndexKey = []byte("channel-index")
// channelIndexKey is a static key used to retrieve a directory
// containing all heights for which the nursery will need to take
// action.
heightIndexKey = []byte("height-index")
)
// Defines the state prefixes that will be used to persistently track an
// output's progress through the nursery.
// NOTE: Each state prefix MUST be exactly 4 bytes in length, the nursery logic
// depends on the ability to create keys for a different state by overwriting
// an existing state prefix.
var (
// cribPrefix is the state prefix given to htlc outputs waiting for
// their first-stage, absolute locktime to elapse.
cribPrefix = []byte("crib")
// psclPrefix is the state prefix given to commitment outputs awaiting
// the confirmation of the commitment transaction, as this solidifies
// the absolute height at which they can be spent.
psclPrefix = []byte("pscl")
// kndrPrefix is the state prefix given to all CSV delayed outputs,
// either from the commitment transaction, or a stage-one htlc
// transaction, whose maturity height has solidified. Outputs marked in
// this state are in their final stage of incubation within the nursery,
// and will be swept into the wallet after waiting out the relative
// timelock.
kndrPrefix = []byte("kndr")
// gradPrefix is the state prefix given to all outputs that have been
// completely incubated. Once all outputs have been marked as graduated,
// this serves as a persistent marker that the nursery should mark the
// channel fully closed in the channeldb.
gradPrefix = []byte("grad")
)
// prefixChainKey creates the root level keys for the nursery store. The keys
// are comprised of a nursery-specific prefix and the intended chain hash that
// this nursery store will be used for. This allows multiple nursery stores to
// isolate their state when operating on multiple chains or forks.
func prefixChainKey(sysPrefix []byte, hash *chainhash.Hash) ([]byte, error) {
// Create a buffer to which we will write the system prefix, e.g.
// "utxn", followed by the provided chain hash.
var pfxChainBuffer bytes.Buffer
if _, err := pfxChainBuffer.Write(sysPrefix); err != nil {
return nil, err
}
if _, err := pfxChainBuffer.Write(hash[:]); err != nil {
return nil, err
}
return pfxChainBuffer.Bytes(), nil
}
// prefixOutputKey creates a serialized key that prefixes the serialized
// outpoint with the provided state prefix. The returned bytes will be of the
// form <prefix><outpoint>.
func prefixOutputKey(statePrefix []byte,
outpoint wire.OutPoint) ([]byte, error) {
// Create a buffer to which we will first write the state prefix,
// followed by the outpoint.
var pfxOutputBuffer bytes.Buffer
if _, err := pfxOutputBuffer.Write(statePrefix); err != nil {
return nil, err
}
err := graphdb.WriteOutpoint(&pfxOutputBuffer, &outpoint)
if err != nil {
return nil, err
}
return pfxOutputBuffer.Bytes(), nil
}
// NurseryStore is a concrete instantiation of a NurseryStore that is backed by
// a channeldb.DB instance.
type NurseryStore struct {
chainHash chainhash.Hash
db *channeldb.DB
pfxChainKey []byte
}
// NewNurseryStore accepts a chain hash and a channeldb.DB instance, returning
// an instance of NurseryStore who's database is properly segmented for the
// given chain.
func NewNurseryStore(chainHash *chainhash.Hash,
db *channeldb.DB) (*NurseryStore, error) {
// Prefix the provided chain hash with "utxn" to create the key for the
// nursery store's root bucket, ensuring each one has proper chain
// segmentation.
pfxChainKey, err := prefixChainKey(utxnChainPrefix, chainHash)
if err != nil {
return nil, err
}
return &NurseryStore{
chainHash: *chainHash,
db: db,
pfxChainKey: pfxChainKey,
}, nil
}
// Incubate persists the beginning of the incubation process for the
// CSV-delayed outputs (commitment and incoming HTLC's), commitment output and
// a list of outgoing two-stage htlc outputs.
func (ns *NurseryStore) Incubate(kids []kidOutput, babies []babyOutput) error {
return kvdb.Update(ns.db, func(tx kvdb.RwTx) error {
// If we have any kid outputs to incubate, then we'll attempt
// to add each of them to the nursery store. Any duplicate
// outputs will be ignored.
for _, kid := range kids {
if err := ns.enterPreschool(tx, &kid); err != nil {
return err
}
}
// Next, we'll Add all htlc outputs to the crib bucket.
// Similarly, we'll ignore any outputs that have already been
// inserted.
for _, baby := range babies {
if err := ns.enterCrib(tx, &baby); err != nil {
return err
}
}
return nil
}, func() {})
}
// CribToKinder atomically moves a babyOutput in the crib bucket to the
// kindergarten bucket. The now mature kidOutput contained in the babyOutput
// will be stored as it waits out the kidOutput's CSV delay.
func (ns *NurseryStore) CribToKinder(bby *babyOutput) error {
return kvdb.Update(ns.db, func(tx kvdb.RwTx) error {
// First, retrieve or create the channel bucket corresponding to
// the baby output's origin channel point.
chanPoint := bby.OriginChanPoint()
chanBucket, err := ns.createChannelBucket(tx, chanPoint)
if err != nil {
return err
}
// The babyOutput should currently be stored in the crib bucket.
// So, we create a key that prefixes the babyOutput's outpoint
// with the crib prefix, allowing us to reference it in the
// store.
pfxOutputKey, err := prefixOutputKey(cribPrefix, bby.OutPoint())
if err != nil {
return err
}
// Since the babyOutput is being moved to the kindergarten
// bucket, we remove the entry from the channel bucket under the
// crib-prefixed outpoint key.
if err := chanBucket.Delete(pfxOutputKey); err != nil {
return err
}
// Remove the crib output's entry in the height index.
err = ns.removeOutputFromHeight(tx, bby.expiry, chanPoint,
pfxOutputKey)
if err != nil {
return err
}
// Since we are moving this output from the crib bucket to the
// kindergarten bucket, we overwrite the existing prefix of this
// key with the kindergarten prefix.
copy(pfxOutputKey, kndrPrefix)
// Now, serialize babyOutput's encapsulated kidOutput such that
// it can be written to the channel bucket under the new
// kindergarten-prefixed key.
var kidBuffer bytes.Buffer
if err := bby.kidOutput.Encode(&kidBuffer); err != nil {
return err
}
kidBytes := kidBuffer.Bytes()
// Persist the serialized kidOutput under the
// kindergarten-prefixed outpoint key.
if err := chanBucket.Put(pfxOutputKey, kidBytes); err != nil {
return err
}
// Now, compute the height at which this kidOutput's CSV delay
// will expire. This is done by adding the required delay to
// the block height at which the output was confirmed.
maturityHeight := bby.ConfHeight() + bby.BlocksToMaturity()
// Retrieve or create a height-channel bucket corresponding to
// the kidOutput's maturity height.
hghtChanBucketCsv, err := ns.createHeightChanBucket(tx,
maturityHeight, chanPoint)
if err != nil {
return err
}
utxnLog.Tracef("Transitioning (crib -> baby) output for "+
"chan_point=%v at height_index=%v", chanPoint,
maturityHeight)
// Register the kindergarten output's prefixed output key in the
// height-channel bucket corresponding to its maturity height.
// This informs the utxo nursery that it should attempt to spend
// this output when the blockchain reaches the maturity height.
return hghtChanBucketCsv.Put(pfxOutputKey, []byte{})
}, func() {})
}
// PreschoolToKinder atomically moves a kidOutput from the preschool bucket to
// the kindergarten bucket. This transition should be executed after receiving
// confirmation of the preschool output's commitment transaction.
func (ns *NurseryStore) PreschoolToKinder(kid *kidOutput,
lastGradHeight uint32) error {
return kvdb.Update(ns.db, func(tx kvdb.RwTx) error {
// Create or retrieve the channel bucket corresponding to the
// kid output's origin channel point.
chanPoint := kid.OriginChanPoint()
chanBucket, err := ns.createChannelBucket(tx, chanPoint)
if err != nil {
return err
}
// First, we will attempt to remove the existing serialized
// output from the channel bucket, where the kid's outpoint will
// be prefixed by a preschool prefix.
// Generate the key of existing serialized kid output by
// prefixing its outpoint with the preschool prefix...
pfxOutputKey, err := prefixOutputKey(psclPrefix, kid.OutPoint())
if err != nil {
return err
}
// And remove the old serialized output from the database.
if err := chanBucket.Delete(pfxOutputKey); err != nil {
return err
}
// Next, we will write the provided kid outpoint to the channel
// bucket, using a key prefixed by the kindergarten prefix.
// Convert the preschool prefix key into a kindergarten key for
// the same outpoint.
copy(pfxOutputKey, kndrPrefix)
// Reserialize the kid here to capture any differences in the
// new and old kid output, such as the confirmation height.
var kidBuffer bytes.Buffer
if err := kid.Encode(&kidBuffer); err != nil {
return err
}
kidBytes := kidBuffer.Bytes()
// And store the kid output in its channel bucket using the
// kindergarten prefixed key.
if err := chanBucket.Put(pfxOutputKey, kidBytes); err != nil {
return err
}
// If this output has an absolute time lock, then we'll set the
// maturity height directly.
var maturityHeight uint32
if kid.BlocksToMaturity() == 0 {
maturityHeight = kid.absoluteMaturity
} else {
// Otherwise, since the CSV delay on the kid output has
// now begun ticking, we must insert a record of in the
// height index to remind us to revisit this output
// once it has fully matured.
//
// Compute the maturity height, by adding the output's
// CSV delay to its confirmation height.
maturityHeight = kid.ConfHeight() + kid.BlocksToMaturity()
}
if maturityHeight <= lastGradHeight {
utxnLog.Debugf("Late Registration for kid output=%v "+
"detected: class_height=%v, "+
"last_graduated_height=%v", kid.OutPoint(),
maturityHeight, lastGradHeight)
maturityHeight = lastGradHeight + 1
}
utxnLog.Infof("Transitioning (crib -> kid) output for "+
"chan_point=%v at height_index=%v", chanPoint,
maturityHeight)
// Create or retrieve the height-channel bucket for this
// channel. This method will first create a height bucket for
// the given maturity height if none exists.
hghtChanBucket, err := ns.createHeightChanBucket(tx,
maturityHeight, chanPoint)
if err != nil {
return err
}
// Finally, we touch a key in the height-channel created above.
// The key is named using a kindergarten prefixed key, signaling
// that this CSV delayed output will be ready to broadcast at
// the maturity height, after a brief period of incubation.
return hghtChanBucket.Put(pfxOutputKey, []byte{})
}, func() {})
}
// GraduateKinder atomically moves an output at the provided height into the
// graduated status. This involves removing the kindergarten entries from both
// the height and channel indexes. The height bucket will be opportunistically
// pruned from the height index as outputs are removed.
func (ns *NurseryStore) GraduateKinder(height uint32, kid *kidOutput) error {
return kvdb.Update(ns.db, func(tx kvdb.RwTx) error {
hghtBucket := ns.getHeightBucket(tx, height)
if hghtBucket == nil {
// Nothing to delete, bucket has already been removed.
return nil
}
// For the kindergarten output, delete its entry from the
// height and channel index, and create a new grad output in the
// channel index.
outpoint := kid.OutPoint()
chanPoint := kid.OriginChanPoint()
// Construct the key under which the output is
// currently stored height and channel indexes.
pfxOutputKey, err := prefixOutputKey(kndrPrefix,
outpoint)
if err != nil {
return err
}
// Remove the grad output's entry in the height
// index.
err = ns.removeOutputFromHeight(tx, height,
chanPoint, pfxOutputKey)
if err != nil {
return err
}
chanBucket := ns.getChannelBucketWrite(tx, chanPoint)
if chanBucket == nil {
return ErrContractNotFound
}
// Remove previous output with kindergarten
// prefix.
err = chanBucket.Delete(pfxOutputKey)
if err != nil {
return err
}
// Convert kindergarten key to graduate key.
copy(pfxOutputKey, gradPrefix)
var gradBuffer bytes.Buffer
if err := kid.Encode(&gradBuffer); err != nil {
return err
}
// Insert serialized output into channel bucket
// using graduate-prefixed key.
return chanBucket.Put(pfxOutputKey,
gradBuffer.Bytes())
}, func() {})
}
// FetchClass returns a list of babyOutputs in the crib bucket whose CLTV
// delay expires at the provided block height.
// FetchClass returns a list of the kindergarten and crib outputs whose timeouts
// are expiring
func (ns *NurseryStore) FetchClass(
height uint32) ([]kidOutput, []babyOutput, error) { // nolint:revive
// Construct list of all crib and kindergarten outputs that need to be
// processed at the provided block height.
var kids []kidOutput
var babies []babyOutput
if err := kvdb.View(ns.db, func(tx kvdb.RTx) error {
// Append each crib output to our list of babyOutputs.
if err := ns.forEachHeightPrefix(tx, cribPrefix, height,
func(buf []byte) error {
// We will attempt to deserialize all outputs
// stored with the crib prefix into babyOutputs,
// since this is the expected type that would
// have been serialized previously.
var baby babyOutput
babyReader := bytes.NewReader(buf)
if err := baby.Decode(babyReader); err != nil {
return err
}
babies = append(babies, baby)
return nil
},
); err != nil {
return err
}
// Append each kindergarten output to our list of kidOutputs.
return ns.forEachHeightPrefix(tx, kndrPrefix, height,
func(buf []byte) error {
// We will attempt to deserialize all outputs
// stored with the kindergarten prefix into
// kidOutputs, since this is the expected type
// that would have been serialized previously.
var kid kidOutput
kidReader := bytes.NewReader(buf)
if err := kid.Decode(kidReader); err != nil {
return err
}
kids = append(kids, kid)
return nil
})
}, func() {
kids = nil
babies = nil
}); err != nil {
return nil, nil, err
}
return kids, babies, nil
}
// FetchPreschools returns a list of all outputs currently stored in the
// preschool bucket.
func (ns *NurseryStore) FetchPreschools() ([]kidOutput, error) { // nolint:revive
var kids []kidOutput
if err := kvdb.View(ns.db, func(tx kvdb.RTx) error {
// Retrieve the existing chain bucket for this nursery store.
chainBucket := tx.ReadBucket(ns.pfxChainKey)
if chainBucket == nil {
return nil
}
// Load the existing channel index from the chain bucket.
chanIndex := chainBucket.NestedReadBucket(channelIndexKey)
if chanIndex == nil {
return nil
}
// Construct a list of all channels in the channel index that
// are currently being tracked by the nursery store.
var activeChannels [][]byte
if err := chanIndex.ForEach(func(chanBytes, _ []byte) error {
activeChannels = append(activeChannels, chanBytes)
return nil
}); err != nil {
return err
}
// Iterate over all of the accumulated channels, and do a prefix
// scan inside of each channel bucket. Each output found that
// has a preschool prefix will be deserialized into a kidOutput,
// and added to our list of preschool outputs to return to the
// caller.
for _, chanBytes := range activeChannels {
// Retrieve the channel bucket associated with this
// channel.
chanBucket := chanIndex.NestedReadBucket(chanBytes)
if chanBucket == nil {
continue
}
// All of the outputs of interest will start with the
// "pscl" prefix. So, we will perform a prefix scan of
// the channel bucket to efficiently enumerate all the
// desired outputs.
c := chanBucket.ReadCursor()
for k, v := c.Seek(psclPrefix); bytes.HasPrefix(
k, psclPrefix); k, v = c.Next() {
// Deserialize each output as a kidOutput, since
// this should have been the type that was
// serialized when it was written to disk.
var psclOutput kidOutput
psclReader := bytes.NewReader(v)
err := psclOutput.Decode(psclReader)
if err != nil {
return err
}
// Add the deserialized output to our list of
// preschool outputs.
kids = append(kids, psclOutput)
}
}
return nil
}, func() {
kids = nil
}); err != nil {
return nil, err
}
return kids, nil
}
// HeightsBelowOrEqual returns a slice of all non-empty heights in the height
// index at or below the provided upper bound.
func (ns *NurseryStore) HeightsBelowOrEqual(height uint32) ([]uint32, error) {
var activeHeights []uint32
err := kvdb.View(ns.db, func(tx kvdb.RTx) error {
// Ensure that the chain bucket for this nursery store exists.
chainBucket := tx.ReadBucket(ns.pfxChainKey)
if chainBucket == nil {
return nil
}
// Ensure that the height index has been properly initialized for this
// chain.
hghtIndex := chainBucket.NestedReadBucket(heightIndexKey)
if hghtIndex == nil {
return nil
}
// Serialize the provided height, as this will form the name of the
// bucket.
var lower, upper [4]byte
byteOrder.PutUint32(upper[:], height)
c := hghtIndex.ReadCursor()
for k, _ := c.Seek(lower[:]); bytes.Compare(k, upper[:]) <= 0 &&
len(k) == 4; k, _ = c.Next() {
activeHeights = append(activeHeights, byteOrder.Uint32(k))
}
return nil
}, func() {
activeHeights = nil
})
if err != nil {
return nil, err
}
return activeHeights, nil
}
// ForChanOutputs iterates over all outputs being incubated for a particular
// channel point. This method accepts a callback that allows the caller to
// process each key-value pair. The key will be a prefixed outpoint, and the
// value will be the serialized bytes for an output, whose type should be
// inferred from the key's prefix.
// NOTE: The callback should not modify the provided byte slices and is
// preferably non-blocking.
func (ns *NurseryStore) ForChanOutputs(chanPoint *wire.OutPoint,
callback func([]byte, []byte) error, reset func()) error {
return kvdb.View(ns.db, func(tx kvdb.RTx) error {
return ns.forChanOutputs(tx, chanPoint, callback)
}, reset)
}
// ListChannels returns all channels the nursery is currently tracking.
func (ns *NurseryStore) ListChannels() ([]wire.OutPoint, error) {
var activeChannels []wire.OutPoint
if err := kvdb.View(ns.db, func(tx kvdb.RTx) error {
// Retrieve the existing chain bucket for this nursery store.
chainBucket := tx.ReadBucket(ns.pfxChainKey)
if chainBucket == nil {
return nil
}
// Retrieve the existing channel index.
chanIndex := chainBucket.NestedReadBucket(channelIndexKey)
if chanIndex == nil {
return nil
}
return chanIndex.ForEach(func(chanBytes, _ []byte) error {
var chanPoint wire.OutPoint
err := graphdb.ReadOutpoint(
bytes.NewReader(chanBytes), &chanPoint,
)
if err != nil {
return err
}
activeChannels = append(activeChannels, chanPoint)
return nil
})
}, func() {
activeChannels = nil
}); err != nil {
return nil, err
}
return activeChannels, nil
}
// IsMatureChannel determines the whether or not all of the outputs in a
// particular channel bucket have been marked as graduated.
func (ns *NurseryStore) IsMatureChannel(chanPoint *wire.OutPoint) (bool, error) {
err := kvdb.View(ns.db, func(tx kvdb.RTx) error {
// Iterate over the contents of the channel bucket, computing
// both total number of outputs, and those that have the grad
// prefix.
return ns.forChanOutputs(tx, chanPoint,
func(pfxKey, _ []byte) error {
if !bytes.HasPrefix(pfxKey, gradPrefix) {
return ErrImmatureChannel
}
return nil
})
}, func() {})
if err != nil && err != ErrImmatureChannel {
return false, err
}
return err == nil, nil
}
// ErrImmatureChannel signals a channel cannot be removed because not all of its
// outputs have graduated.
var ErrImmatureChannel = errors.New("cannot remove immature channel, " +
"still has ungraduated outputs")
// RemoveChannel channel erases all entries from the channel bucket for the
// provided channel point.
// NOTE: The channel's entries in the height index are assumed to be removed.
func (ns *NurseryStore) RemoveChannel(chanPoint *wire.OutPoint) error {
return kvdb.Update(ns.db, func(tx kvdb.RwTx) error {
// Retrieve the existing chain bucket for this nursery store.
chainBucket := tx.ReadWriteBucket(ns.pfxChainKey)
if chainBucket == nil {
return nil
}
// Retrieve the channel index stored in the chain bucket.
chanIndex := chainBucket.NestedReadWriteBucket(channelIndexKey)
if chanIndex == nil {
return nil
}
// Serialize the provided channel point, such that we can delete
// the mature channel bucket.
var chanBuffer bytes.Buffer
err := graphdb.WriteOutpoint(&chanBuffer, chanPoint)
if err != nil {
return err
}
chanBytes := chanBuffer.Bytes()
err = ns.forChanOutputs(tx, chanPoint, func(k, v []byte) error {
if !bytes.HasPrefix(k, gradPrefix) {
return ErrImmatureChannel
}
// Construct a kindergarten prefixed key, since this
// would have been the preceding state for a grad
// output.
kndrKey := make([]byte, len(k))
copy(kndrKey, k)
copy(kndrKey[:4], kndrPrefix)
// Decode each to retrieve the output's maturity height.
var kid kidOutput
if err := kid.Decode(bytes.NewReader(v)); err != nil {
return err
}
maturityHeight := kid.ConfHeight() + kid.BlocksToMaturity()
hghtBucket := ns.getHeightBucketWrite(tx, maturityHeight)
if hghtBucket == nil {
return nil
}
return removeBucketIfExists(hghtBucket, chanBytes)
})
if err != nil {
return err
}
return removeBucketIfExists(chanIndex, chanBytes)
}, func() {})
}
// Helper Methods
// enterCrib accepts a new htlc output that the nursery will incubate through
// its two-stage process of sweeping funds back to the user's wallet. These
// outputs are persisted in the nursery store in the crib state, and will be
// revisited after the first-stage output's CLTV has expired.
func (ns *NurseryStore) enterCrib(tx kvdb.RwTx, baby *babyOutput) error {
// First, retrieve or create the channel bucket corresponding to the
// baby output's origin channel point.
chanPoint := baby.OriginChanPoint()
chanBucket, err := ns.createChannelBucket(tx, chanPoint)
if err != nil {
return err
}
// Since we are inserting this output into the crib bucket, we create a
// key that prefixes the baby output's outpoint with the crib prefix.
pfxOutputKey, err := prefixOutputKey(cribPrefix, baby.OutPoint())
if err != nil {
return err
}
// We'll first check that we don't already have an entry for this
// output. If we do, then we can exit early.
if rawBytes := chanBucket.Get(pfxOutputKey); rawBytes != nil {
return nil
}
// Next, retrieve or create the height-channel bucket located in the
// height bucket corresponding to the baby output's CLTV expiry height.
// TODO: Handle late registration.
hghtChanBucket, err := ns.createHeightChanBucket(tx,
baby.expiry, chanPoint)
if err != nil {
return err
}
// Serialize the baby output so that it can be written to the
// underlying key-value store.
var babyBuffer bytes.Buffer
if err := baby.Encode(&babyBuffer); err != nil {
return err
}
babyBytes := babyBuffer.Bytes()
// Now, insert the serialized output into its channel bucket under the
// prefixed key created above.
if err := chanBucket.Put(pfxOutputKey, babyBytes); err != nil {
return err
}
// Finally, create a corresponding bucket in the height-channel bucket
// for this crib output. The existence of this bucket indicates that
// the serialized output can be retrieved from the channel bucket using
// the same prefix key.
return hghtChanBucket.Put(pfxOutputKey, []byte{})
}
// enterPreschool accepts a new commitment output that the nursery will incubate
// through a single stage before sweeping. Outputs are stored in the preschool
// bucket until the commitment transaction has been confirmed, at which point
// they will be moved to the kindergarten bucket.
func (ns *NurseryStore) enterPreschool(tx kvdb.RwTx, kid *kidOutput) error {
// First, retrieve or create the channel bucket corresponding to the
// baby output's origin channel point.
chanPoint := kid.OriginChanPoint()
chanBucket, err := ns.createChannelBucket(tx, chanPoint)
if err != nil {
return err
}
// Since the kidOutput is being inserted into the preschool bucket, we
// create a key that prefixes its outpoint with the preschool prefix.
pfxOutputKey, err := prefixOutputKey(psclPrefix, kid.OutPoint())
if err != nil {
return err
}
// We'll first check if an entry for this key is already stored. If so,
// then we'll ignore this request, and return a nil error.
if rawBytes := chanBucket.Get(pfxOutputKey); rawBytes != nil {
return nil
}
// Serialize the kidOutput and insert it into the channel bucket.
var kidBuffer bytes.Buffer
if err := kid.Encode(&kidBuffer); err != nil {
return err
}
return chanBucket.Put(pfxOutputKey, kidBuffer.Bytes())
}
// createChannelBucket creates or retrieves a channel bucket for the provided
// channel point.
func (ns *NurseryStore) createChannelBucket(tx kvdb.RwTx,
chanPoint *wire.OutPoint) (kvdb.RwBucket, error) {
// Ensure that the chain bucket for this nursery store exists.
chainBucket, err := tx.CreateTopLevelBucket(ns.pfxChainKey)
if err != nil {
return nil, err
}
// Ensure that the channel index has been properly initialized for this
// chain.
chanIndex, err := chainBucket.CreateBucketIfNotExists(channelIndexKey)
if err != nil {
return nil, err
}
// Serialize the provided channel point, as this provides the name of
// the channel bucket of interest.
var chanBuffer bytes.Buffer
if err := graphdb.WriteOutpoint(&chanBuffer, chanPoint); err != nil {
return nil, err
}
// Finally, create or retrieve the channel bucket using the serialized
// key.
return chanIndex.CreateBucketIfNotExists(chanBuffer.Bytes())
}
// getChannelBucket retrieves an existing channel bucket from the nursery store,
// using the given channel point. If the bucket does not exist, or any bucket
// along its path does not exist, a nil value is returned.
func (ns *NurseryStore) getChannelBucket(tx kvdb.RTx,
chanPoint *wire.OutPoint) kvdb.RBucket {
// Retrieve the existing chain bucket for this nursery store.
chainBucket := tx.ReadBucket(ns.pfxChainKey)
if chainBucket == nil {
return nil
}
// Retrieve the existing channel index.
chanIndex := chainBucket.NestedReadBucket(channelIndexKey)
if chanIndex == nil {
return nil
}
// Serialize the provided channel point and return the bucket matching
// the serialized key.
var chanBuffer bytes.Buffer
if err := graphdb.WriteOutpoint(&chanBuffer, chanPoint); err != nil {
return nil
}
return chanIndex.NestedReadBucket(chanBuffer.Bytes())
}
// getChannelBucketWrite retrieves an existing channel bucket from the nursery store,
// using the given channel point. If the bucket does not exist, or any bucket
// along its path does not exist, a nil value is returned.
func (ns *NurseryStore) getChannelBucketWrite(tx kvdb.RwTx,
chanPoint *wire.OutPoint) kvdb.RwBucket {
// Retrieve the existing chain bucket for this nursery store.
chainBucket := tx.ReadWriteBucket(ns.pfxChainKey)
if chainBucket == nil {
return nil
}
// Retrieve the existing channel index.
chanIndex := chainBucket.NestedReadWriteBucket(channelIndexKey)
if chanIndex == nil {
return nil
}
// Serialize the provided channel point and return the bucket matching
// the serialized key.
var chanBuffer bytes.Buffer
if err := graphdb.WriteOutpoint(&chanBuffer, chanPoint); err != nil {
return nil
}
return chanIndex.NestedReadWriteBucket(chanBuffer.Bytes())
}
// createHeightBucket creates or retrieves an existing bucket from the height
// index, corresponding to the provided height.
func (ns *NurseryStore) createHeightBucket(tx kvdb.RwTx,
height uint32) (kvdb.RwBucket, error) {
// Ensure that the chain bucket for this nursery store exists.
chainBucket, err := tx.CreateTopLevelBucket(ns.pfxChainKey)
if err != nil {
return nil, err
}
// Ensure that the height index has been properly initialized for this
// chain.
hghtIndex, err := chainBucket.CreateBucketIfNotExists(heightIndexKey)
if err != nil {
return nil, err
}
// Serialize the provided height, as this will form the name of the
// bucket.
var heightBytes [4]byte
byteOrder.PutUint32(heightBytes[:], height)
// Finally, create or retrieve the bucket in question.
return hghtIndex.CreateBucketIfNotExists(heightBytes[:])
}
// getHeightBucketPath retrieves an existing height bucket from the nursery
// store, using the provided block height. If the bucket does not exist, or any
// bucket along its path does not exist, a nil value is returned.
func (ns *NurseryStore) getHeightBucketPath(tx kvdb.RTx,
height uint32) (kvdb.RBucket, kvdb.RBucket) {
// Retrieve the existing chain bucket for this nursery store.
chainBucket := tx.ReadBucket(ns.pfxChainKey)
if chainBucket == nil {
return nil, nil
}
// Retrieve the existing channel index.
hghtIndex := chainBucket.NestedReadBucket(heightIndexKey)
if hghtIndex == nil {
return nil, nil
}
// Serialize the provided block height and return the bucket matching
// the serialized key.
var heightBytes [4]byte
byteOrder.PutUint32(heightBytes[:], height)
return chainBucket, hghtIndex.NestedReadBucket(heightBytes[:])
}
// getHeightBucketPathWrite retrieves an existing height bucket from the nursery
// store, using the provided block height. If the bucket does not exist, or any
// bucket along its path does not exist, a nil value is returned.
func (ns *NurseryStore) getHeightBucketPathWrite(tx kvdb.RwTx,
height uint32) (kvdb.RwBucket, kvdb.RwBucket) {
// Retrieve the existing chain bucket for this nursery store.
chainBucket := tx.ReadWriteBucket(ns.pfxChainKey)
if chainBucket == nil {
return nil, nil
}
// Retrieve the existing channel index.
hghtIndex := chainBucket.NestedReadWriteBucket(heightIndexKey)
if hghtIndex == nil {
return nil, nil
}
// Serialize the provided block height and return the bucket matching
// the serialized key.
var heightBytes [4]byte
byteOrder.PutUint32(heightBytes[:], height)
return hghtIndex, hghtIndex.NestedReadWriteBucket(
heightBytes[:],
)
}
// getHeightBucket retrieves an existing height bucket from the nursery store,
// using the provided block height. If the bucket does not exist, or any bucket
// along its path does not exist, a nil value is returned.
func (ns *NurseryStore) getHeightBucket(tx kvdb.RTx,
height uint32) kvdb.RBucket {
_, hghtBucket := ns.getHeightBucketPath(tx, height)
return hghtBucket
}
// getHeightBucketWrite retrieves an existing height bucket from the nursery store,
// using the provided block height. If the bucket does not exist, or any bucket
// along its path does not exist, a nil value is returned.
func (ns *NurseryStore) getHeightBucketWrite(tx kvdb.RwTx,
height uint32) kvdb.RwBucket {
_, hghtBucket := ns.getHeightBucketPathWrite(tx, height)
return hghtBucket
}
// createHeightChanBucket creates or retrieves an existing height-channel bucket
// for the provided block height and channel point. This method will attempt to
// instantiate all buckets along the path if required.
func (ns *NurseryStore) createHeightChanBucket(tx kvdb.RwTx,
height uint32, chanPoint *wire.OutPoint) (kvdb.RwBucket, error) {
// Ensure that the height bucket for this nursery store exists.
hghtBucket, err := ns.createHeightBucket(tx, height)
if err != nil {
return nil, err
}
// Serialize the provided channel point, as this generates the name of
// the subdirectory corresponding to the channel of interest.
var chanBuffer bytes.Buffer
if err := graphdb.WriteOutpoint(&chanBuffer, chanPoint); err != nil {
return nil, err
}
chanBytes := chanBuffer.Bytes()
// Finally, create or retrieve an existing height-channel bucket for
// this channel point.
return hghtBucket.CreateBucketIfNotExists(chanBytes)
}
// getHeightChanBucketWrite retrieves an existing height-channel bucket from the
// nursery store, using the provided block height and channel point. if the
// bucket does not exist, or any bucket along its path does not exist, a nil
// value is returned.
func (ns *NurseryStore) getHeightChanBucketWrite(tx kvdb.RwTx,
height uint32, chanPoint *wire.OutPoint) kvdb.RwBucket {
// Retrieve the existing height bucket from this nursery store.
hghtBucket := ns.getHeightBucketWrite(tx, height)
if hghtBucket == nil {
return nil
}
// Serialize the provided channel point, which generates the key for
// looking up the proper height-channel bucket inside the height bucket.
var chanBuffer bytes.Buffer
if err := graphdb.WriteOutpoint(&chanBuffer, chanPoint); err != nil {
return nil
}
chanBytes := chanBuffer.Bytes()
// Finally, return the height bucket specified by the serialized channel
// point.
return hghtBucket.NestedReadWriteBucket(chanBytes)
}
// forEachHeightPrefix enumerates all outputs at the given height whose state
// prefix matches that which is provided. This is used as a subroutine to help
// enumerate crib and kindergarten outputs at a particular height. The callback
// is invoked with serialized bytes retrieved for each output of interest,
// allowing the caller to deserialize them into the appropriate type.
func (ns *NurseryStore) forEachHeightPrefix(tx kvdb.RTx, prefix []byte,
height uint32, callback func([]byte) error) error {
// Start by retrieving the height bucket corresponding to the provided
// block height.
chainBucket, hghtBucket := ns.getHeightBucketPath(tx, height)
if hghtBucket == nil {
return nil
}
// Using the height bucket as a starting point, we will traverse its
// entire two-tier directory structure, and filter for outputs that have
// the provided prefix. The first layer of the height bucket contains
// buckets identified by a channel point, thus we first create list of
// channels contained in this height bucket.
var channelsAtHeight [][]byte
if err := hghtBucket.ForEach(func(chanBytes, v []byte) error {
if v == nil {
channelsAtHeight = append(channelsAtHeight, chanBytes)
}
return nil
}); err != nil {
return err
}
// Additionally, grab the chain index, which we will facilitate queries
// for each of the channel buckets of each of the channels in the list
// we assembled above.
chanIndex := chainBucket.NestedReadBucket(channelIndexKey)
if chanIndex == nil {
return errors.New("unable to retrieve channel index")
}
// Now, we are ready to enumerate all outputs with the desired prefix at
// this block height. We do so by iterating over our list of channels at
// this height, filtering for outputs in each height-channel bucket that
// begin with the given prefix, and then retrieving the serialized
// outputs from the appropriate channel bucket.
for _, chanBytes := range channelsAtHeight {
// Retrieve the height-channel bucket for this channel, which
// holds a sub-bucket for all outputs maturing at this height.
hghtChanBucket := hghtBucket.NestedReadBucket(chanBytes)
if hghtChanBucket == nil {
return fmt.Errorf("unable to retrieve height-channel "+
"bucket at height %d for %x", height, chanBytes)
}
// Load the appropriate channel bucket from the channel index,
// this will allow us to retrieve the individual serialized
// outputs.
chanBucket := chanIndex.NestedReadBucket(chanBytes)
if chanBucket == nil {
return fmt.Errorf("unable to retrieve channel "+
"bucket: '%x'", chanBytes)
}
// Since all of the outputs of interest will start with the same
// prefix, we will perform a prefix scan of the buckets
// contained in the height-channel bucket, efficiently
// enumerating the desired outputs.
c := hghtChanBucket.ReadCursor()
for k, _ := c.Seek(prefix); bytes.HasPrefix(
k, prefix); k, _ = c.Next() {
// Use the prefix output key emitted from our scan to
// load the serialized babyOutput from the appropriate
// channel bucket.
outputBytes := chanBucket.Get(k)
if outputBytes == nil {
return errors.New("unable to retrieve output")
}
// Present the serialized bytes to our call back
// function, which is responsible for deserializing the
// bytes into the appropriate type.
if err := callback(outputBytes); err != nil {
return err
}
}
}
return nil
}
// forChanOutputs enumerates the outputs contained in a channel bucket to the
// provided callback. The callback accepts a key-value pair of byte slices
// corresponding to the prefixed-output key and the serialized output,
// respectively.
func (ns *NurseryStore) forChanOutputs(tx kvdb.RTx, chanPoint *wire.OutPoint,
callback func([]byte, []byte) error) error {
chanBucket := ns.getChannelBucket(tx, chanPoint)
if chanBucket == nil {
return ErrContractNotFound
}
return chanBucket.ForEach(callback)
}
// errBucketNotEmpty signals that an attempt to prune a particular
// bucket failed because it still has active outputs.
var errBucketNotEmpty = errors.New("bucket is not empty, cannot be pruned")
// removeOutputFromHeight will delete the given output from the specified
// height-channel bucket, and attempt to prune the upstream directories if they
// are empty.
func (ns *NurseryStore) removeOutputFromHeight(tx kvdb.RwTx, height uint32,
chanPoint *wire.OutPoint, pfxKey []byte) error {
// Retrieve the height-channel bucket and delete the prefixed output.
hghtChanBucket := ns.getHeightChanBucketWrite(tx, height, chanPoint)
if hghtChanBucket == nil {
// Height-channel bucket already removed.
return nil
}
// Try to delete the prefixed output from the target height-channel
// bucket.
if err := hghtChanBucket.Delete(pfxKey); err != nil {
return err
}
// Retrieve the height bucket that contains the height-channel bucket.
hghtBucket := ns.getHeightBucketWrite(tx, height)
if hghtBucket == nil {
return errors.New("height bucket not found")
}
var chanBuffer bytes.Buffer
if err := graphdb.WriteOutpoint(&chanBuffer, chanPoint); err != nil {
return err
}
// Try to remove the channel-height bucket if it this was the last
// output in the bucket.
err := removeBucketIfEmpty(hghtBucket, chanBuffer.Bytes())
if err != nil && err != errBucketNotEmpty {
return err
} else if err == errBucketNotEmpty {
return nil
}
// Attempt to prune the height bucket matching the kid output's
// confirmation height in case that was the last height-chan bucket.
pruned, err := ns.pruneHeight(tx, height)
if err != nil && err != errBucketNotEmpty {
return err
} else if err == nil && pruned {
utxnLog.Infof("Height bucket %d pruned", height)
}
return nil
}
// pruneHeight removes the height bucket at the provided height if and only if
// all active outputs at this height have been removed from their respective
// height-channel buckets. The returned boolean value indicated whether or not
// this invocation successfully pruned the height bucket.
func (ns *NurseryStore) pruneHeight(tx kvdb.RwTx, height uint32) (bool, error) {
// Fetch the existing height index and height bucket.
hghtIndex, hghtBucket := ns.getHeightBucketPathWrite(tx, height)
if hghtBucket == nil {
return false, nil
}
// Iterate over all channels stored at this block height. We will
// attempt to remove each one if they are empty, keeping track of the
// number of height-channel buckets that still have active outputs.
if err := hghtBucket.ForEach(func(chanBytes, v []byte) error {
// Skip the finalized txn key if it still exists from a previous
// db version.
if v != nil {
return nil
}
// Attempt to each height-channel bucket from the height bucket
// located above.
hghtChanBucket := hghtBucket.NestedReadWriteBucket(chanBytes)
if hghtChanBucket == nil {
return errors.New("unable to find height-channel bucket")
}
return isBucketEmpty(hghtChanBucket)
}); err != nil {
return false, err
}
// Serialize the provided block height, such that it can be used as the
// key to delete desired height bucket.
var heightBytes [4]byte
byteOrder.PutUint32(heightBytes[:], height)
// All of the height-channel buckets are empty or have been previously
// removed, proceed by removing the height bucket
// altogether.
if err := removeBucketIfExists(hghtIndex, heightBytes[:]); err != nil {
return false, err
}
return true, nil
}
// removeBucketIfEmpty attempts to delete a bucket specified by name from the
// provided parent bucket.
func removeBucketIfEmpty(parent kvdb.RwBucket, bktName []byte) error {
// Attempt to fetch the named bucket from its parent.
bkt := parent.NestedReadWriteBucket(bktName)
if bkt == nil {
// No bucket was found, already removed?
return nil
}
// The bucket exists, fail if it still has children.
if err := isBucketEmpty(bkt); err != nil {
return err
}
return parent.DeleteNestedBucket(bktName)
}
// removeBucketIfExists safely deletes the named bucket by first checking
// that it exists in the parent bucket.
func removeBucketIfExists(parent kvdb.RwBucket, bktName []byte) error {
// Attempt to fetch the named bucket from its parent.
bkt := parent.NestedReadWriteBucket(bktName)
if bkt == nil {
// No bucket was found, already removed?
return nil
}
return parent.DeleteNestedBucket(bktName)
}
// isBucketEmpty returns errBucketNotEmpty if the bucket has a non-zero number
// of children.
func isBucketEmpty(parent kvdb.RBucket) error {
return parent.ForEach(func(_, _ []byte) error {
return errBucketNotEmpty
})
}
// Compile-time constraint to ensure NurseryStore implements NurseryStorer.
var _ NurseryStorer = (*NurseryStore)(nil)
package contractcourt
import (
"bytes"
"io"
"github.com/lightningnetwork/lnd/tlv"
)
const (
commitCtrlBlockType tlv.Type = 0
revokeCtrlBlockType tlv.Type = 1
outgoingHtlcCtrlBlockType tlv.Type = 2
incomingHtlcCtrlBlockType tlv.Type = 3
secondLevelCtrlBlockType tlv.Type = 4
anchorTapTweakType tlv.Type = 0
htlcTweakCtrlBlockType tlv.Type = 1
secondLevelHtlcTweakCtrlBlockType tlv.Type = 2
)
// taprootBriefcase is a supplemental storage struct that contains all the
// information we need to sweep taproot outputs.
type taprootBriefcase struct {
// CtrlBlock is the set of control block for the taproot outputs.
CtrlBlocks tlv.RecordT[tlv.TlvType0, ctrlBlocks]
// TapTweaks is the set of taproot tweaks for the taproot outputs that
// are to be spent via a keyspend path. This includes anchors, and any
// revocation paths.
TapTweaks tlv.RecordT[tlv.TlvType1, tapTweaks]
// SettledCommitBlob is an optional record that contains an opaque blob
// that may be used to properly sweep commitment outputs on a force
// close transaction.
SettledCommitBlob tlv.OptionalRecordT[tlv.TlvType2, tlv.Blob]
// BreachCommitBlob is an optional record that contains an opaque blob
// used to sweep a remote party's breached output.
BreachedCommitBlob tlv.OptionalRecordT[tlv.TlvType3, tlv.Blob]
// HtlcBlobs is an optikonal record that contains the opaque blobs for
// the set of active HTLCs on the commitment transaction.
HtlcBlobs tlv.OptionalRecordT[tlv.TlvType4, htlcAuxBlobs]
}
// TODO(roasbeef): morph into new tlv record
// newTaprootBriefcase returns a new instance of the taproot specific briefcase
// variant.
func newTaprootBriefcase() *taprootBriefcase {
return &taprootBriefcase{
CtrlBlocks: tlv.NewRecordT[tlv.TlvType0](newCtrlBlocks()),
TapTweaks: tlv.NewRecordT[tlv.TlvType1](newTapTweaks()),
}
}
// EncodeRecords returns a slice of TLV records that should be encoded.
func (t *taprootBriefcase) EncodeRecords() []tlv.Record {
records := []tlv.Record{
t.CtrlBlocks.Record(),
t.TapTweaks.Record(),
}
t.SettledCommitBlob.WhenSome(
func(r tlv.RecordT[tlv.TlvType2, tlv.Blob]) {
records = append(records, r.Record())
},
)
t.BreachedCommitBlob.WhenSome(
func(r tlv.RecordT[tlv.TlvType3, tlv.Blob]) {
records = append(records, r.Record())
},
)
t.HtlcBlobs.WhenSome(func(r tlv.RecordT[tlv.TlvType4, htlcAuxBlobs]) {
records = append(records, r.Record())
})
return records
}
// DecodeRecords returns a slice of TLV records that should be decoded.
func (t *taprootBriefcase) DecodeRecords() []tlv.Record {
return []tlv.Record{
t.CtrlBlocks.Record(),
t.TapTweaks.Record(),
}
}
// Encode records returns a slice of TLV records that should be encoded.
func (t *taprootBriefcase) Encode(w io.Writer) error {
stream, err := tlv.NewStream(t.EncodeRecords()...)
if err != nil {
return err
}
return stream.Encode(w)
}
// Decode decodes the given reader into the target struct.
func (t *taprootBriefcase) Decode(r io.Reader) error {
settledCommitBlob := t.SettledCommitBlob.Zero()
breachedCommitBlob := t.BreachedCommitBlob.Zero()
htlcBlobs := t.HtlcBlobs.Zero()
records := append(
t.DecodeRecords(), settledCommitBlob.Record(),
breachedCommitBlob.Record(), htlcBlobs.Record(),
)
stream, err := tlv.NewStream(records...)
if err != nil {
return err
}
typeMap, err := stream.DecodeWithParsedTypes(r)
if err != nil {
return err
}
if val, ok := typeMap[t.SettledCommitBlob.TlvType()]; ok && val == nil {
t.SettledCommitBlob = tlv.SomeRecordT(settledCommitBlob)
}
if v, ok := typeMap[t.BreachedCommitBlob.TlvType()]; ok && v == nil {
t.BreachedCommitBlob = tlv.SomeRecordT(breachedCommitBlob)
}
if v, ok := typeMap[t.HtlcBlobs.TlvType()]; ok && v == nil {
t.HtlcBlobs = tlv.SomeRecordT(htlcBlobs)
}
return nil
}
// resolverCtrlBlocks is a map of resolver IDs to their corresponding control
// block.
type resolverCtrlBlocks map[resolverID][]byte
// newResolverCtrlBlockss returns a new instance of the resolverCtrlBlocks.
func newResolverCtrlBlocks() resolverCtrlBlocks {
return make(resolverCtrlBlocks)
}
// recordSize returns the size of the record in bytes.
func (r *resolverCtrlBlocks) recordSize() uint64 {
// Each record will be serialized as: <num_total_records> || <record>,
// where <record> is serialized as: <resolver_key> || <length> ||
// <ctrl_block>.
numBlocks := uint64(len(*r))
baseSize := tlv.VarIntSize(numBlocks)
recordSize := baseSize
for _, ctrlBlock := range *r {
recordSize += resolverIDLen
recordSize += tlv.VarIntSize(uint64(len(ctrlBlock)))
recordSize += uint64(len(ctrlBlock))
}
return recordSize
}
// Encode encodes the control blocks into the target writer.
func (r *resolverCtrlBlocks) Encode(w io.Writer) error {
numBlocks := uint64(len(*r))
var buf [8]byte
if err := tlv.WriteVarInt(w, numBlocks, &buf); err != nil {
return err
}
for id, ctrlBlock := range *r {
ctrlBlock := ctrlBlock
if _, err := w.Write(id[:]); err != nil {
return err
}
if err := varBytesEncoder(w, &ctrlBlock, &buf); err != nil {
return err
}
}
return nil
}
// Decode decodes the given reader into the target struct.
func (r *resolverCtrlBlocks) Decode(reader io.Reader) error {
var buf [8]byte
numBlocks, err := tlv.ReadVarInt(reader, &buf)
if err != nil {
return err
}
for i := uint64(0); i < numBlocks; i++ {
var id resolverID
if _, err := io.ReadFull(reader, id[:]); err != nil {
return err
}
var ctrlBlock []byte
err := varBytesDecoder(reader, &ctrlBlock, &buf, 0)
if err != nil {
return err
}
(*r)[id] = ctrlBlock
}
return nil
}
// resolverCtrlBlocksEncoder is a custom TLV encoder for the
// resolverCtrlBlocks.
func resolverCtrlBlocksEncoder(w io.Writer, val any, _ *[8]byte) error {
if typ, ok := val.(*resolverCtrlBlocks); ok {
return (*typ).Encode(w)
}
return tlv.NewTypeForEncodingErr(val, "resolverCtrlBlocks")
}
// rsolverCtrlBlocksDecoder is a custom TLV decoder for the resolverCtrlBlocks.
func resolverCtrlBlocksDecoder(r io.Reader, val any, _ *[8]byte,
l uint64) error {
if typ, ok := val.(*resolverCtrlBlocks); ok {
blockReader := io.LimitReader(r, int64(l))
resolverBlocks := newResolverCtrlBlocks()
err := resolverBlocks.Decode(blockReader)
if err != nil {
return err
}
*typ = resolverBlocks
return nil
}
return tlv.NewTypeForDecodingErr(val, "resolverCtrlBlocks", l, l)
}
// ctrlBlocks is the set of control blocks we need to sweep all the output for
// a taproot/musig2 channel.
type ctrlBlocks struct {
// CommitSweepCtrlBlock is the serialized control block needed to sweep
// our commitment output.
CommitSweepCtrlBlock []byte
// RevokeSweepCtrlBlock is the serialized control block that's used to
// sweep the reovked output of a breaching party.
RevokeSweepCtrlBlock []byte
// OutgoingHtlcCtrlBlocks is the set of serialized control blocks for
// all outgoing HTLCs. This is the set of HTLCs that we offered to the
// remote party. Depending on which commitment transaction was
// broadcast, we'll either sweep here and be done, or also need to go
// to the second level.
OutgoingHtlcCtrlBlocks resolverCtrlBlocks
// IncomingHtlcCtrlBlocks is the set of serialized control blocks for
// all incoming HTLCs
IncomingHtlcCtrlBlocks resolverCtrlBlocks
// SecondLevelCtrlBlocks is the set of serialized control blocks for
// need to sweep the second level HTLCs on our commitment transaction.
SecondLevelCtrlBlocks resolverCtrlBlocks
}
// newCtrlBlocks returns a new instance of the ctrlBlocks struct.
func newCtrlBlocks() ctrlBlocks {
return ctrlBlocks{
OutgoingHtlcCtrlBlocks: newResolverCtrlBlocks(),
IncomingHtlcCtrlBlocks: newResolverCtrlBlocks(),
SecondLevelCtrlBlocks: newResolverCtrlBlocks(),
}
}
// varBytesEncoder is a custom TLV encoder for a variable length byte slice.
func varBytesEncoder(w io.Writer, val any, buf *[8]byte) error {
if t, ok := val.(*[]byte); ok {
if err := tlv.WriteVarInt(w, uint64(len(*t)), buf); err != nil {
return err
}
return tlv.EVarBytes(w, t, buf)
}
return tlv.NewTypeForEncodingErr(val, "[]byte")
}
// varBytesDecoder is a custom TLV decoder for a variable length byte slice.
func varBytesDecoder(r io.Reader, val any, buf *[8]byte, l uint64) error {
if typ, ok := val.(*[]byte); ok {
bytesLen, err := tlv.ReadVarInt(r, buf)
if err != nil {
return err
}
var bytes []byte
if err := tlv.DVarBytes(r, &bytes, buf, bytesLen); err != nil {
return err
}
*typ = bytes
return nil
}
return tlv.NewTypeForDecodingErr(val, "[]byte", l, l)
}
// ctrlBlockEncoder is a custom TLV encoder for the ctrlBlocks struct.
func ctrlBlockEncoder(w io.Writer, val any, _ *[8]byte) error {
if t, ok := val.(*ctrlBlocks); ok {
return (*t).Encode(w)
}
return tlv.NewTypeForEncodingErr(val, "ctrlBlocks")
}
// ctrlBlockDecoder is a custom TLV decoder for the ctrlBlocks struct.
func ctrlBlockDecoder(r io.Reader, val any, _ *[8]byte, l uint64) error {
if typ, ok := val.(*ctrlBlocks); ok {
ctrlReader := io.LimitReader(r, int64(l))
var ctrlBlocks ctrlBlocks
err := ctrlBlocks.Decode(ctrlReader)
if err != nil {
return err
}
*typ = ctrlBlocks
return nil
}
return tlv.NewTypeForDecodingErr(val, "ctrlBlocks", l, l)
}
// EncodeRecords returns the set of TLV records that encode the control block
// for the commitment transaction.
func (c *ctrlBlocks) EncodeRecords() []tlv.Record {
var records []tlv.Record
if len(c.CommitSweepCtrlBlock) > 0 {
records = append(records, tlv.MakePrimitiveRecord(
commitCtrlBlockType, &c.CommitSweepCtrlBlock,
))
}
if len(c.RevokeSweepCtrlBlock) > 0 {
records = append(records, tlv.MakePrimitiveRecord(
revokeCtrlBlockType, &c.RevokeSweepCtrlBlock,
))
}
if c.OutgoingHtlcCtrlBlocks != nil {
records = append(records, tlv.MakeDynamicRecord(
outgoingHtlcCtrlBlockType, &c.OutgoingHtlcCtrlBlocks,
c.OutgoingHtlcCtrlBlocks.recordSize,
resolverCtrlBlocksEncoder, resolverCtrlBlocksDecoder,
))
}
if c.IncomingHtlcCtrlBlocks != nil {
records = append(records, tlv.MakeDynamicRecord(
incomingHtlcCtrlBlockType, &c.IncomingHtlcCtrlBlocks,
c.IncomingHtlcCtrlBlocks.recordSize,
resolverCtrlBlocksEncoder, resolverCtrlBlocksDecoder,
))
}
if c.SecondLevelCtrlBlocks != nil {
records = append(records, tlv.MakeDynamicRecord(
secondLevelCtrlBlockType, &c.SecondLevelCtrlBlocks,
c.SecondLevelCtrlBlocks.recordSize,
resolverCtrlBlocksEncoder, resolverCtrlBlocksDecoder,
))
}
return records
}
// DecodeRecords returns the set of TLV records that decode the control block.
func (c *ctrlBlocks) DecodeRecords() []tlv.Record {
return []tlv.Record{
tlv.MakePrimitiveRecord(
commitCtrlBlockType, &c.CommitSweepCtrlBlock,
),
tlv.MakePrimitiveRecord(
revokeCtrlBlockType, &c.RevokeSweepCtrlBlock,
),
tlv.MakeDynamicRecord(
outgoingHtlcCtrlBlockType, &c.OutgoingHtlcCtrlBlocks,
c.OutgoingHtlcCtrlBlocks.recordSize,
resolverCtrlBlocksEncoder, resolverCtrlBlocksDecoder,
),
tlv.MakeDynamicRecord(
incomingHtlcCtrlBlockType, &c.IncomingHtlcCtrlBlocks,
c.IncomingHtlcCtrlBlocks.recordSize,
resolverCtrlBlocksEncoder, resolverCtrlBlocksDecoder,
),
tlv.MakeDynamicRecord(
secondLevelCtrlBlockType, &c.SecondLevelCtrlBlocks,
c.SecondLevelCtrlBlocks.recordSize,
resolverCtrlBlocksEncoder, resolverCtrlBlocksDecoder,
),
}
}
// Record returns a TLV record that can be used to encode/decode the control
// blocks. type from a given TLV stream.
func (c *ctrlBlocks) Record() tlv.Record {
recordSize := func() uint64 {
var (
b bytes.Buffer
buf [8]byte
)
if err := ctrlBlockEncoder(&b, c, &buf); err != nil {
panic(err)
}
return uint64(len(b.Bytes()))
}
return tlv.MakeDynamicRecord(
0, c, recordSize, ctrlBlockEncoder, ctrlBlockDecoder,
)
}
// Encode encodes the set of control blocks.
func (c *ctrlBlocks) Encode(w io.Writer) error {
stream, err := tlv.NewStream(c.EncodeRecords()...)
if err != nil {
return err
}
return stream.Encode(w)
}
// Decode decodes the set of control blocks.
func (c *ctrlBlocks) Decode(r io.Reader) error {
stream, err := tlv.NewStream(c.DecodeRecords()...)
if err != nil {
return err
}
return stream.Decode(r)
}
// htlcTapTweakss maps an outpoint (the same format as the resolver ID) to the
// tap tweak needed to sweep a breached HTLC output. This is used for both the
// first and second level HTLC outputs.
type htlcTapTweaks map[resolverID][32]byte
// newHtlcTapTweaks returns a new instance of the htlcTapTweaks struct.
func newHtlcTapTweaks() htlcTapTweaks {
return make(htlcTapTweaks)
}
// recordSize returns the size of the record in bytes.
func (h *htlcTapTweaks) recordSize() uint64 {
// Each record will be serialized as: <num_tweaks> || <tweak>, where
// <tweak> is serialized as: <resolver_key> || <tweak>.
numTweaks := uint64(len(*h))
baseSize := tlv.VarIntSize(numTweaks)
recordSize := baseSize
for range *h {
// Each tweak is a fixed 32 bytes, so we just tally that an the
// size of the resolver ID.
recordSize += resolverIDLen
recordSize += 32
}
return recordSize
}
// Encode encodes the tap tweaks into the target writer.
func (h *htlcTapTweaks) Encode(w io.Writer) error {
numTweaks := uint64(len(*h))
var buf [8]byte
if err := tlv.WriteVarInt(w, numTweaks, &buf); err != nil {
return err
}
for id, tweak := range *h {
tweak := tweak
if _, err := w.Write(id[:]); err != nil {
return err
}
if _, err := w.Write(tweak[:]); err != nil {
return err
}
}
return nil
}
// htlcTapTweaksEncoder is a custom TLV encoder for the htlcTapTweaks struct.
func htlcTapTweaksEncoder(w io.Writer, val any, _ *[8]byte) error {
if t, ok := val.(*htlcTapTweaks); ok {
return (*t).Encode(w)
}
return tlv.NewTypeForEncodingErr(val, "htlcTapTweaks")
}
// htlcTapTweaksDecoder is a custom TLV decoder for the htlcTapTweaks struct.
func htlcTapTweaksDecoder(r io.Reader, val any, _ *[8]byte,
l uint64) error {
if typ, ok := val.(*htlcTapTweaks); ok {
tweakReader := io.LimitReader(r, int64(l))
htlcTweaks := newHtlcTapTweaks()
err := htlcTweaks.Decode(tweakReader)
if err != nil {
return err
}
*typ = htlcTweaks
return nil
}
return tlv.NewTypeForDecodingErr(val, "htlcTapTweaks", l, l)
}
// Decode decodes the tap tweaks into the target struct.
func (h *htlcTapTweaks) Decode(reader io.Reader) error {
var buf [8]byte
numTweaks, err := tlv.ReadVarInt(reader, &buf)
if err != nil {
return err
}
for i := uint64(0); i < numTweaks; i++ {
var id resolverID
if _, err := io.ReadFull(reader, id[:]); err != nil {
return err
}
var tweak [32]byte
if _, err := io.ReadFull(reader, tweak[:]); err != nil {
return err
}
(*h)[id] = tweak
}
return nil
}
// tapTweaks stores the set of taptweaks needed to perform keyspends for the
// commitment outputs.
type tapTweaks struct {
// AnchorTweak is the tweak used to derive the key used to spend the
// anchor output.
AnchorTweak []byte
// BreachedHtlcTweaks stores the set of tweaks needed to sweep the
// revoked first level output of an HTLC.
BreachedHtlcTweaks htlcTapTweaks
// BreachedSecondLevelHtlcTweaks stores the set of tweaks needed to
// sweep the revoked *second* level output of an HTLC.
BreachedSecondLevelHltcTweaks htlcTapTweaks
}
// newTapTweaks returns a new tapTweaks struct.
func newTapTweaks() tapTweaks {
return tapTweaks{
BreachedHtlcTweaks: make(htlcTapTweaks),
BreachedSecondLevelHltcTweaks: make(htlcTapTweaks),
}
}
// tapTweaksEncoder is a custom TLV encoder for the tapTweaks struct.
func tapTweaksEncoder(w io.Writer, val any, _ *[8]byte) error {
if t, ok := val.(*tapTweaks); ok {
return (*t).Encode(w)
}
return tlv.NewTypeForEncodingErr(val, "tapTweaks")
}
// tapTweaksDecoder is a custom TLV decoder for the tapTweaks struct.
func tapTweaksDecoder(r io.Reader, val any, _ *[8]byte, l uint64) error {
if typ, ok := val.(*tapTweaks); ok {
tweakReader := io.LimitReader(r, int64(l))
var tapTweaks tapTweaks
err := tapTweaks.Decode(tweakReader)
if err != nil {
return err
}
*typ = tapTweaks
return nil
}
return tlv.NewTypeForDecodingErr(val, "tapTweaks", l, l)
}
// EncodeRecords returns the set of TLV records that encode the tweaks.
func (t *tapTweaks) EncodeRecords() []tlv.Record {
var records []tlv.Record
if len(t.AnchorTweak) > 0 {
records = append(records, tlv.MakePrimitiveRecord(
anchorTapTweakType, &t.AnchorTweak,
))
}
if len(t.BreachedHtlcTweaks) > 0 {
records = append(records, tlv.MakeDynamicRecord(
htlcTweakCtrlBlockType, &t.BreachedHtlcTweaks,
t.BreachedHtlcTweaks.recordSize,
htlcTapTweaksEncoder, htlcTapTweaksDecoder,
))
}
if len(t.BreachedSecondLevelHltcTweaks) > 0 {
records = append(records, tlv.MakeDynamicRecord(
secondLevelHtlcTweakCtrlBlockType,
&t.BreachedSecondLevelHltcTweaks,
t.BreachedSecondLevelHltcTweaks.recordSize,
htlcTapTweaksEncoder, htlcTapTweaksDecoder,
))
}
return records
}
// DecodeRecords returns the set of TLV records that decode the tweaks.
func (t *tapTweaks) DecodeRecords() []tlv.Record {
return []tlv.Record{
tlv.MakePrimitiveRecord(anchorTapTweakType, &t.AnchorTweak),
tlv.MakeDynamicRecord(
htlcTweakCtrlBlockType, &t.BreachedHtlcTweaks,
t.BreachedHtlcTweaks.recordSize,
htlcTapTweaksEncoder, htlcTapTweaksDecoder,
),
tlv.MakeDynamicRecord(
secondLevelHtlcTweakCtrlBlockType,
&t.BreachedSecondLevelHltcTweaks,
t.BreachedSecondLevelHltcTweaks.recordSize,
htlcTapTweaksEncoder, htlcTapTweaksDecoder,
),
}
}
// Record returns a TLV record that can be used to encode/decode the tap
// tweaks.
func (t *tapTweaks) Record() tlv.Record {
recordSize := func() uint64 {
var (
b bytes.Buffer
buf [8]byte
)
if err := tapTweaksEncoder(&b, t, &buf); err != nil {
panic(err)
}
return uint64(len(b.Bytes()))
}
return tlv.MakeDynamicRecord(
0, t, recordSize, tapTweaksEncoder, tapTweaksDecoder,
)
}
// Encode encodes the set of tap tweaks.
func (t *tapTweaks) Encode(w io.Writer) error {
stream, err := tlv.NewStream(t.EncodeRecords()...)
if err != nil {
return err
}
return stream.Encode(w)
}
// Decode decodes the set of tap tweaks.
func (t *tapTweaks) Decode(r io.Reader) error {
stream, err := tlv.NewStream(t.DecodeRecords()...)
if err != nil {
return err
}
return stream.Decode(r)
}
// htlcAuxBlobs is a map of resolver IDs to their corresponding HTLC blobs.
// This is used to store the resolution blobs for HTLCs that are not yet
// resolved.
type htlcAuxBlobs map[resolverID]tlv.Blob
// newAuxHtlcBlobs returns a new instance of the htlcAuxBlobs struct.
func newAuxHtlcBlobs() htlcAuxBlobs {
return make(htlcAuxBlobs)
}
// Encode encodes the set of HTLC blobs into the target writer.
func (h *htlcAuxBlobs) Encode(w io.Writer) error {
var buf [8]byte
numBlobs := uint64(len(*h))
if err := tlv.WriteVarInt(w, numBlobs, &buf); err != nil {
return err
}
for id, blob := range *h {
if _, err := w.Write(id[:]); err != nil {
return err
}
if err := varBytesEncoder(w, &blob, &buf); err != nil {
return err
}
}
return nil
}
// Decode decodes the set of HTLC blobs from the target reader.
func (h *htlcAuxBlobs) Decode(r io.Reader) error {
var buf [8]byte
numBlobs, err := tlv.ReadVarInt(r, &buf)
if err != nil {
return err
}
for i := uint64(0); i < numBlobs; i++ {
var id resolverID
if _, err := io.ReadFull(r, id[:]); err != nil {
return err
}
var blob tlv.Blob
if err := varBytesDecoder(r, &blob, &buf, 0); err != nil {
return err
}
(*h)[id] = blob
}
return nil
}
// eHtlcAuxBlobsEncoder is a custom TLV encoder for the htlcAuxBlobs struct.
func htlcAuxBlobsEncoder(w io.Writer, val any, _ *[8]byte) error {
if t, ok := val.(*htlcAuxBlobs); ok {
return (*t).Encode(w)
}
return tlv.NewTypeForEncodingErr(val, "htlcAuxBlobs")
}
// dHtlcAuxBlobsDecoder is a custom TLV decoder for the htlcAuxBlobs struct.
func htlcAuxBlobsDecoder(r io.Reader, val any, _ *[8]byte,
l uint64) error {
if typ, ok := val.(*htlcAuxBlobs); ok {
blobReader := io.LimitReader(r, int64(l))
htlcBlobs := newAuxHtlcBlobs()
err := htlcBlobs.Decode(blobReader)
if err != nil {
return err
}
*typ = htlcBlobs
return nil
}
return tlv.NewTypeForDecodingErr(val, "htlcAuxBlobs", l, l)
}
// Record returns a tlv.Record for the htlcAuxBlobs struct.
func (h *htlcAuxBlobs) Record() tlv.Record {
recordSize := func() uint64 {
var (
b bytes.Buffer
buf [8]byte
)
if err := htlcAuxBlobsEncoder(&b, h, &buf); err != nil {
panic(err)
}
return uint64(len(b.Bytes()))
}
return tlv.MakeDynamicRecord(
0, h, recordSize, htlcAuxBlobsEncoder, htlcAuxBlobsDecoder,
)
}
package contractcourt
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"sync"
"sync/atomic"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/sweep"
"github.com/lightningnetwork/lnd/tlv"
)
// SUMMARY OF OUTPUT STATES
//
// - CRIB
// - SerializedType: babyOutput
// - OriginalOutputType: HTLC
// - Awaiting: First-stage HTLC CLTV expiry
// - HeightIndexEntry: Absolute block height of CLTV expiry.
// - NextState: KNDR
// - PSCL
// - SerializedType: kidOutput
// - OriginalOutputType: Commitment
// - Awaiting: Confirmation of commitment txn
// - HeightIndexEntry: None.
// - NextState: KNDR
// - KNDR
// - SerializedType: kidOutput
// - OriginalOutputType: Commitment or HTLC
// - Awaiting: Commitment CSV expiry or second-stage HTLC CSV expiry.
// - HeightIndexEntry: Input confirmation height + relative CSV delay
// - NextState: GRAD
// - GRAD:
// - SerializedType: kidOutput
// - OriginalOutputType: Commitment or HTLC
// - Awaiting: All other outputs in channel to become GRAD.
// - NextState: Mark channel fully closed in channeldb and remove.
//
// DESCRIPTION OF OUTPUT STATES
//
// TODO(roasbeef): update comment with both new output types
//
// - CRIB (babyOutput) outputs are two-stage htlc outputs that are initially
// locked using a CLTV delay, followed by a CSV delay. The first stage of a
// crib output requires broadcasting a presigned htlc timeout txn generated
// by the wallet after an absolute expiry height. Since the timeout txns are
// predetermined, they cannot be batched after-the-fact, meaning that all
// CRIB outputs are broadcast and confirmed independently. After the first
// stage is complete, a CRIB output is moved to the KNDR state, which will
// finishing sweeping the second-layer CSV delay.
//
// - PSCL (kidOutput) outputs are commitment outputs locked under a CSV delay.
// These outputs are stored temporarily in this state until the commitment
// transaction confirms, as this solidifies an absolute height that the
// relative time lock will expire. Once this maturity height is determined,
// the PSCL output is moved into KNDR.
//
// - KNDR (kidOutput) outputs are CSV delayed outputs for which the maturity
// height has been fully determined. This results from having received
// confirmation of the UTXO we are trying to spend, contained in either the
// commitment txn or htlc timeout txn. Once the maturity height is reached,
// the utxo nursery will sweep all KNDR outputs scheduled for that height
// using a single txn.
//
// - GRAD (kidOutput) outputs are KNDR outputs that have successfully been
// swept into the user's wallet. A channel is considered mature once all of
// its outputs, including two-stage htlcs, have entered the GRAD state,
// indicating that it safe to mark the channel as fully closed.
//
//
// OUTPUT STATE TRANSITIONS IN UTXO NURSERY
//
// ┌────────────────┐ ┌──────────────┐
// │ Commit Outputs │ │ HTLC Outputs │
// └────────────────┘ └──────────────┘
// │ │
// │ │
// │ │ UTXO NURSERY
// ┌───────────┼────────────────┬───────────┼───────────────────────────────┐
// │ │ │ │
// │ │ │ │ │
// │ │ │ CLTV-Delayed │
// │ │ │ V babyOutputs │
// │ │ ┌──────┐ │
// │ │ │ │ CRIB │ │
// │ │ └──────┘ │
// │ │ │ │ │
// │ │ │ │
// │ │ │ | │
// │ │ V Wait CLTV │
// │ │ │ [ ] + │
// │ │ | Publish Txn │
// │ │ │ │ │
// │ │ │ │
// │ │ │ V ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┐ │
// │ │ ( ) waitForTimeoutConf │
// │ │ │ | └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┘ │
// │ │ │ │
// │ │ │ │ │
// │ │ │ │
// │ V │ │ │
// │ ┌──────┐ │ │
// │ │ PSCL │ └ ── ── ─┼ ── ── ── ── ── ── ── ─┤
// │ └──────┘ │ │
// │ │ │ │
// │ │ │ │
// │ V ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┐ │ CSV-Delayed │
// │ ( ) waitForCommitConf │ kidOutputs │
// │ | └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┘ │ │
// │ │ │ │
// │ │ │ │
// │ │ V │
// │ │ ┌──────┐ │
// │ └─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─▶│ KNDR │ │
// │ └──────┘ │
// │ │ │
// │ │ │
// │ | │
// │ V Wait CSV │
// │ [ ] + │
// │ | Publish Txn │
// │ │ │
// │ │ │
// │ V ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │
// │ ( ) waitForSweepConf │
// │ | └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │
// │ │ │
// │ │ │
// │ V │
// │ ┌──────┐ │
// │ │ GRAD │ │
// │ └──────┘ │
// │ │ │
// │ │ │
// │ │ │
// └────────────────────────────────────────┼───────────────────────────────┘
// │
// │
// │
// │
// V
// ┌────────────────┐
// │ Wallet Outputs │
// └────────────────┘
var byteOrder = binary.BigEndian
const (
// kgtnOutputConfTarget is the default confirmation target we'll use for
// sweeps of CSV delayed outputs.
kgtnOutputConfTarget = 6
)
var (
// ErrContractNotFound is returned when the nursery is unable to
// retrieve information about a queried contract.
ErrContractNotFound = fmt.Errorf("unable to locate contract")
)
// NurseryConfig abstracts the required subsystems used by the utxo nursery. An
// instance of NurseryConfig is passed to newUtxoNursery during instantiation.
type NurseryConfig struct {
// ChainIO is used by the utxo nursery to determine the current block
// height, which drives the incubation of the nursery's outputs.
ChainIO lnwallet.BlockChainIO
// ConfDepth is the number of blocks the nursery store waits before
// determining outputs in the chain as confirmed.
ConfDepth uint32
// FetchClosedChannels provides access to a user's channels, such that
// they can be marked fully closed after incubation has concluded.
FetchClosedChannels func(pendingOnly bool) (
[]*channeldb.ChannelCloseSummary, error)
// FetchClosedChannel provides access to the close summary to extract a
// height hint from.
FetchClosedChannel func(chanID *wire.OutPoint) (
*channeldb.ChannelCloseSummary, error)
// Notifier provides the utxo nursery the ability to subscribe to
// transaction confirmation events, which advance outputs through their
// persistence state transitions.
Notifier chainntnfs.ChainNotifier
// PublishTransaction facilitates the process of broadcasting a signed
// transaction to the appropriate network.
PublishTransaction func(*wire.MsgTx, string) error
// Store provides access to and modification of the persistent state
// maintained about the utxo nursery's incubating outputs.
Store NurseryStorer
// Sweep sweeps an input back to the wallet.
SweepInput func(input.Input, sweep.Params) (chan sweep.Result, error)
// Budget is the configured budget for the nursery.
Budget *BudgetConfig
}
// UtxoNursery is a system dedicated to incubating time-locked outputs created
// by the broadcast of a commitment transaction either by us, or the remote
// peer. The nursery accepts outputs and "incubates" them until they've reached
// maturity, then sweep the outputs into the source wallet. An output is
// considered mature after the relative time-lock within the pkScript has
// passed. As outputs reach their maturity age, they're swept in batches into
// the source wallet, returning the outputs so they can be used within future
// channels, or regular Bitcoin transactions.
type UtxoNursery struct {
started uint32 // To be used atomically.
stopped uint32 // To be used atomically.
cfg *NurseryConfig
mu sync.Mutex
bestHeight uint32
quit chan struct{}
wg sync.WaitGroup
}
// NewUtxoNursery creates a new instance of the UtxoNursery from a
// ChainNotifier and LightningWallet instance.
func NewUtxoNursery(cfg *NurseryConfig) *UtxoNursery {
return &UtxoNursery{
cfg: cfg,
quit: make(chan struct{}),
}
}
// Start launches all goroutines the UtxoNursery needs to properly carry out
// its duties.
func (u *UtxoNursery) Start() error {
if !atomic.CompareAndSwapUint32(&u.started, 0, 1) {
return nil
}
utxnLog.Info("UTXO nursery starting")
// Retrieve the currently best known block. This is needed to have the
// state machine catch up with the blocks we missed when we were down.
bestHash, bestHeight, err := u.cfg.ChainIO.GetBestBlock()
if err != nil {
return err
}
// Set best known height to schedule late registrations properly.
atomic.StoreUint32(&u.bestHeight, uint32(bestHeight))
// 2. Flush all fully-graduated channels from the pipeline.
// Load any pending close channels, which represents the super set of
// all channels that may still be incubating.
pendingCloseChans, err := u.cfg.FetchClosedChannels(true)
if err != nil {
return err
}
// Ensure that all mature channels have been marked as fully closed in
// the channeldb.
for _, pendingClose := range pendingCloseChans {
err := u.closeAndRemoveIfMature(&pendingClose.ChanPoint)
if err != nil {
return err
}
}
// TODO(conner): check if any fully closed channels can be removed from
// utxn.
// 2. Restart spend ntfns for any preschool outputs, which are waiting
// for the force closed commitment txn to confirm, or any second-layer
// HTLC success transactions.
//
// NOTE: The next two steps *may* spawn go routines, thus from this
// point forward, we must close the nursery's quit channel if we detect
// any failures during startup to ensure they terminate.
if err := u.reloadPreschool(); err != nil {
close(u.quit)
return err
}
// 3. Replay all crib and kindergarten outputs up to the current best
// height.
if err := u.reloadClasses(uint32(bestHeight)); err != nil {
close(u.quit)
return err
}
// Start watching for new blocks, as this will drive the nursery store's
// state machine.
newBlockChan, err := u.cfg.Notifier.RegisterBlockEpochNtfn(&chainntnfs.BlockEpoch{
Height: bestHeight,
Hash: bestHash,
})
if err != nil {
close(u.quit)
return err
}
u.wg.Add(1)
go u.incubator(newBlockChan)
return nil
}
// Stop gracefully shuts down any lingering goroutines launched during normal
// operation of the UtxoNursery.
func (u *UtxoNursery) Stop() error {
if !atomic.CompareAndSwapUint32(&u.stopped, 0, 1) {
return nil
}
utxnLog.Infof("UTXO nursery shutting down...")
defer utxnLog.Debug("UTXO nursery shutdown complete")
close(u.quit)
u.wg.Wait()
return nil
}
// IncubateOutputs sends a request to the UtxoNursery to incubate a set of
// outputs from an existing commitment transaction. Outputs need to incubate if
// they're CLTV absolute time locked, or if they're CSV relative time locked.
// Once all outputs reach maturity, they'll be swept back into the wallet.
func (u *UtxoNursery) IncubateOutputs(chanPoint wire.OutPoint,
outgoingHtlc fn.Option[lnwallet.OutgoingHtlcResolution],
incomingHtlc fn.Option[lnwallet.IncomingHtlcResolution],
broadcastHeight uint32, deadlineHeight fn.Option[int32]) error {
// Add to wait group because nursery might shut down during execution of
// this function. Otherwise it could happen that nursery thinks it is
// shut down, but in this function new goroutines were started and stay
// around.
u.wg.Add(1)
defer u.wg.Done()
// Check quit channel for the case where the waitgroup wait was finished
// right before this function's add call was made.
select {
case <-u.quit:
return fmt.Errorf("nursery shutting down")
default:
}
var (
// Kid outputs can be swept after an initial confirmation
// followed by a maturity period.Baby outputs are two stage and
// will need to wait for an absolute time out to reach a
// confirmation, then require a relative confirmation delay.
kidOutputs = make([]kidOutput, 0)
babyOutputs = make([]babyOutput, 0)
)
// 1. Build all the spendable outputs that we will try to incubate.
// TODO(roasbeef): query and see if we already have, if so don't add?
// For each incoming HTLC, we'll register a kid output marked as a
// second-layer HTLC output. We effectively skip the baby stage (as the
// timelock is zero), and enter the kid stage.
incomingHtlc.WhenSome(func(htlcRes lnwallet.IncomingHtlcResolution) {
// Based on the input pk script of the sign descriptor, we can
// determine if this is a taproot output or not. This'll
// determine the witness type we try to set below.
isTaproot := txscript.IsPayToTaproot(
htlcRes.SweepSignDesc.Output.PkScript,
)
var witType input.StandardWitnessType
if isTaproot {
witType = input.TaprootHtlcAcceptedSuccessSecondLevel
} else {
witType = input.HtlcAcceptedSuccessSecondLevel
}
htlcOutput := makeKidOutput(
&htlcRes.ClaimOutpoint, &chanPoint, htlcRes.CsvDelay,
witType, &htlcRes.SweepSignDesc, 0, deadlineHeight,
)
if htlcOutput.Amount() > 0 {
kidOutputs = append(kidOutputs, htlcOutput)
}
})
// For each outgoing HTLC, we'll create a baby output. If this is our
// commitment transaction, then we'll broadcast a second-layer
// transaction to transition to a kid output. Otherwise, we'll directly
// spend once the CLTV delay us up.
outgoingHtlc.WhenSome(func(htlcRes lnwallet.OutgoingHtlcResolution) {
// If this HTLC is on our commitment transaction, then it'll be
// a baby output as we need to go to the second level to sweep
// it.
if htlcRes.SignedTimeoutTx != nil {
htlcOutput := makeBabyOutput(
&chanPoint, &htlcRes, deadlineHeight,
)
if htlcOutput.Amount() > 0 {
babyOutputs = append(babyOutputs, htlcOutput)
}
return
}
// Based on the input pk script of the sign descriptor, we can
// determine if this is a taproot output or not. This'll
// determine the witness type we try to set below.
isTaproot := txscript.IsPayToTaproot(
htlcRes.SweepSignDesc.Output.PkScript,
)
var witType input.StandardWitnessType
if isTaproot {
witType = input.TaprootHtlcOfferedRemoteTimeout
} else {
witType = input.HtlcOfferedRemoteTimeout
}
// Otherwise, this is actually a kid output as we can sweep it
// once the commitment transaction confirms, and the absolute
// CLTV lock has expired. We set the CSV delay what the
// resolution encodes, since the sequence number must be set
// accordingly.
htlcOutput := makeKidOutput(
&htlcRes.ClaimOutpoint, &chanPoint, htlcRes.CsvDelay,
witType, &htlcRes.SweepSignDesc, htlcRes.Expiry,
deadlineHeight,
)
kidOutputs = append(kidOutputs, htlcOutput)
})
// TODO(roasbeef): if want to handle outgoing on remote commit
// * need ability to cancel in the case that we learn of pre-image or
// remote party pulls
numHtlcs := len(babyOutputs) + len(kidOutputs)
utxnLog.Infof("Incubating Channel(%s) num-htlcs=%d",
chanPoint, numHtlcs)
u.mu.Lock()
defer u.mu.Unlock()
// 2. Persist the outputs we intended to sweep in the nursery store
if err := u.cfg.Store.Incubate(kidOutputs, babyOutputs); err != nil {
utxnLog.Errorf("unable to begin incubation of Channel(%s): %v",
chanPoint, err)
return err
}
// As an intermediate step, we'll now check to see if any of the baby
// outputs has actually _already_ expired. This may be the case if
// blocks were mined while we processed this message.
_, bestHeight, err := u.cfg.ChainIO.GetBestBlock()
if err != nil {
return err
}
// We'll examine all the baby outputs just inserted into the database,
// if the output has already expired, then we'll *immediately* sweep
// it. This may happen if the caller raced a block to call this method.
for i, babyOutput := range babyOutputs {
if uint32(bestHeight) >= babyOutput.expiry {
err = u.sweepCribOutput(
babyOutput.expiry, &babyOutputs[i],
)
if err != nil {
return err
}
}
}
// 3. If we are incubating any preschool outputs, register for a
// confirmation notification that will transition it to the
// kindergarten bucket.
if len(kidOutputs) != 0 {
for i := range kidOutputs {
err := u.registerPreschoolConf(
&kidOutputs[i], broadcastHeight,
)
if err != nil {
return err
}
}
}
return nil
}
// NurseryReport attempts to return a nursery report stored for the target
// outpoint. A nursery report details the maturity/sweeping progress for a
// contract that was previously force closed. If a report entry for the target
// chanPoint is unable to be constructed, then an error will be returned.
func (u *UtxoNursery) NurseryReport(
chanPoint *wire.OutPoint) (*ContractMaturityReport, error) {
u.mu.Lock()
defer u.mu.Unlock()
utxnLog.Debugf("NurseryReport: building nursery report for channel %v",
chanPoint)
var report *ContractMaturityReport
if err := u.cfg.Store.ForChanOutputs(chanPoint, func(k, v []byte) error {
switch {
case bytes.HasPrefix(k, cribPrefix):
// Cribs outputs are the only kind currently stored as
// baby outputs.
var baby babyOutput
err := baby.Decode(bytes.NewReader(v))
if err != nil {
return err
}
// Each crib output represents a stage one htlc, and
// will contribute towards the limbo balance.
report.AddLimboStage1TimeoutHtlc(&baby)
case bytes.HasPrefix(k, psclPrefix),
bytes.HasPrefix(k, kndrPrefix),
bytes.HasPrefix(k, gradPrefix):
// All others states can be deserialized as kid outputs.
var kid kidOutput
err := kid.Decode(bytes.NewReader(v))
if err != nil {
return err
}
// Now, use the state prefixes to determine how the
// this output should be represented in the nursery
// report. An output's funds are always in limbo until
// reaching the graduate state.
switch {
case bytes.HasPrefix(k, psclPrefix):
// Preschool outputs are awaiting the
// confirmation of the commitment transaction.
switch kid.WitnessType() {
//nolint:ll
case input.TaprootHtlcAcceptedSuccessSecondLevel:
fallthrough
case input.HtlcAcceptedSuccessSecondLevel:
// An HTLC output on our commitment
// transaction where the second-layer
// transaction hasn't
// yet confirmed.
report.AddLimboStage1SuccessHtlc(&kid)
case input.HtlcOfferedRemoteTimeout,
input.TaprootHtlcOfferedRemoteTimeout:
// This is an HTLC output on the
// commitment transaction of the remote
// party. We are waiting for the CLTV
// timelock expire.
report.AddLimboDirectHtlc(&kid)
}
case bytes.HasPrefix(k, kndrPrefix):
// Kindergarten outputs may originate from
// either the commitment transaction or an htlc.
// We can distinguish them via their witness
// types.
switch kid.WitnessType() {
case input.HtlcOfferedRemoteTimeout,
input.TaprootHtlcOfferedRemoteTimeout:
// This is an HTLC output on the
// commitment transaction of the remote
// party. The CLTV timelock has
// expired, and we only need to sweep
// it.
report.AddLimboDirectHtlc(&kid)
//nolint:ll
case input.TaprootHtlcAcceptedSuccessSecondLevel:
fallthrough
case input.TaprootHtlcOfferedTimeoutSecondLevel:
fallthrough
case input.HtlcAcceptedSuccessSecondLevel:
fallthrough
case input.HtlcOfferedTimeoutSecondLevel:
// The htlc timeout or success
// transaction has confirmed, and the
// CSV delay has begun ticking.
report.AddLimboStage2Htlc(&kid)
}
case bytes.HasPrefix(k, gradPrefix):
// Graduate outputs are those whose funds have
// been swept back into the wallet. Each output
// will contribute towards the recovered
// balance.
switch kid.WitnessType() {
//nolint:ll
case input.TaprootHtlcAcceptedSuccessSecondLevel:
fallthrough
case input.TaprootHtlcOfferedTimeoutSecondLevel:
fallthrough
case input.HtlcAcceptedSuccessSecondLevel:
fallthrough
case input.HtlcOfferedTimeoutSecondLevel:
fallthrough
case input.TaprootHtlcOfferedRemoteTimeout:
fallthrough
case input.HtlcOfferedRemoteTimeout:
// This htlc output successfully
// resides in a p2wkh output belonging
// to the user.
report.AddRecoveredHtlc(&kid)
}
}
default:
}
return nil
}, func() {
report = &ContractMaturityReport{}
}); err != nil {
return nil, err
}
return report, nil
}
// reloadPreschool re-initializes the chain notifier with all of the outputs
// that had been saved to the "preschool" database bucket prior to shutdown.
func (u *UtxoNursery) reloadPreschool() error {
psclOutputs, err := u.cfg.Store.FetchPreschools()
if err != nil {
return err
}
// For each of the preschool outputs stored in the nursery store, load
// its close summary from disk so that we can get an accurate height
// hint from which to start our range for spend notifications.
for i := range psclOutputs {
kid := &psclOutputs[i]
chanPoint := kid.OriginChanPoint()
// Load the close summary for this output's channel point.
closeSummary, err := u.cfg.FetchClosedChannel(chanPoint)
if err == channeldb.ErrClosedChannelNotFound {
// This should never happen since the close summary
// should only be removed after the channel has been
// swept completely.
utxnLog.Warnf("Close summary not found for "+
"chan_point=%v, can't determine height hint"+
"to sweep commit txn", chanPoint)
continue
} else if err != nil {
return err
}
// Use the close height from the channel summary as our height
// hint to drive our spend notifications, with our confirmation
// depth as a buffer for reorgs.
heightHint := closeSummary.CloseHeight - u.cfg.ConfDepth
err = u.registerPreschoolConf(kid, heightHint)
if err != nil {
return err
}
}
return nil
}
// reloadClasses reinitializes any height-dependent state transitions for which
// the utxonursery has not received confirmation, and replays the graduation of
// all kindergarten and crib outputs for all heights up to the current block.
// This allows the nursery to reinitialize all state to continue sweeping
// outputs, even in the event that we missed blocks while offline. reloadClasses
// is called during the startup of the UTXO Nursery.
func (u *UtxoNursery) reloadClasses(bestHeight uint32) error {
// Loading all active heights up to and including the current block.
activeHeights, err := u.cfg.Store.HeightsBelowOrEqual(
uint32(bestHeight))
if err != nil {
return err
}
// Return early if nothing to sweep.
if len(activeHeights) == 0 {
return nil
}
utxnLog.Infof("(Re)-sweeping %d heights below height=%d",
len(activeHeights), bestHeight)
// Attempt to re-register notifications for any outputs still at these
// heights.
for _, classHeight := range activeHeights {
utxnLog.Debugf("Attempting to sweep outputs at height=%v",
classHeight)
if err = u.graduateClass(classHeight); err != nil {
utxnLog.Errorf("Failed to sweep outputs at "+
"height=%v: %v", classHeight, err)
return err
}
}
utxnLog.Infof("UTXO Nursery is now fully synced")
return nil
}
// incubator is tasked with driving all state transitions that are dependent on
// the current height of the blockchain. As new blocks arrive, the incubator
// will attempt spend outputs at the latest height. The asynchronous
// confirmation of these spends will either 1) move a crib output into the
// kindergarten bucket or 2) move a kindergarten output into the graduated
// bucket.
func (u *UtxoNursery) incubator(newBlockChan *chainntnfs.BlockEpochEvent) {
defer u.wg.Done()
defer newBlockChan.Cancel()
for {
select {
case epoch, ok := <-newBlockChan.Epochs:
// If the epoch channel has been closed, then the
// ChainNotifier is exiting which means the daemon is
// as well. Therefore, we exit early also in order to
// ensure the daemon shuts down gracefully, yet
// swiftly.
if !ok {
return
}
// TODO(roasbeef): if the BlockChainIO is rescanning
// will give stale data
// A new block has just been connected to the main
// chain, which means we might be able to graduate crib
// or kindergarten outputs at this height. This involves
// broadcasting any presigned htlc timeout txns, as well
// as signing and broadcasting a sweep txn that spends
// from all kindergarten outputs at this height.
height := uint32(epoch.Height)
// Update best known block height for late registrations
// to be scheduled properly.
atomic.StoreUint32(&u.bestHeight, height)
if err := u.graduateClass(height); err != nil {
utxnLog.Errorf("error while graduating "+
"class at height=%d: %v", height, err)
// TODO(conner): signal fatal error to daemon
}
case <-u.quit:
return
}
}
}
// graduateClass handles the steps involved in spending outputs whose CSV or
// CLTV delay expires at the nursery's current height. This method is called
// each time a new block arrives, or during startup to catch up on heights we
// may have missed while the nursery was offline.
func (u *UtxoNursery) graduateClass(classHeight uint32) error {
// Record this height as the nursery's current best height.
u.mu.Lock()
defer u.mu.Unlock()
// Fetch all information about the crib and kindergarten outputs at
// this height.
kgtnOutputs, cribOutputs, err := u.cfg.Store.FetchClass(
classHeight,
)
if err != nil {
return err
}
utxnLog.Debugf("Attempting to graduate height=%v: num_kids=%v, "+
"num_babies=%v", classHeight, len(kgtnOutputs), len(cribOutputs))
// Offer the outputs to the sweeper and set up notifications that will
// transition the swept kindergarten outputs and cltvCrib into graduated
// outputs.
if len(kgtnOutputs) > 0 {
if err := u.sweepMatureOutputs(classHeight, kgtnOutputs); err != nil {
utxnLog.Errorf("Failed to sweep %d kindergarten "+
"outputs at height=%d: %v",
len(kgtnOutputs), classHeight, err)
return err
}
}
// Now, we broadcast all pre-signed htlc txns from the csv crib outputs
// at this height.
for i := range cribOutputs {
err := u.sweepCribOutput(classHeight, &cribOutputs[i])
if err != nil {
utxnLog.Errorf("Failed to sweep first-stage HTLC "+
"(CLTV-delayed) output %v",
cribOutputs[i].OutPoint())
return err
}
}
return nil
}
// decideDeadlineAndBudget returns the deadline and budget for a given output.
func (u *UtxoNursery) decideDeadlineAndBudget(k kidOutput) (fn.Option[int32],
btcutil.Amount) {
// Assume this is a to_local output and use a None deadline.
deadline := fn.None[int32]()
// Exit early if this is not HTLC.
if !k.isHtlc {
budget := calculateBudget(
k.amt, u.cfg.Budget.ToLocalRatio, u.cfg.Budget.ToLocal,
)
return deadline, budget
}
// Otherwise it's the first-level HTLC output, we'll use the
// time-sensitive settings for it.
budget := calculateBudget(
k.amt, u.cfg.Budget.DeadlineHTLCRatio,
u.cfg.Budget.DeadlineHTLC,
)
return k.deadlineHeight, budget
}
// sweepMatureOutputs generates and broadcasts the transaction that transfers
// control of funds from a prior channel commitment transaction to the user's
// wallet. The outputs swept were previously time locked (either absolute or
// relative), but are not mature enough to sweep into the wallet.
func (u *UtxoNursery) sweepMatureOutputs(classHeight uint32,
kgtnOutputs []kidOutput) error {
utxnLog.Infof("Sweeping %v CSV-delayed outputs with sweep tx for "+
"height %v", len(kgtnOutputs), classHeight)
for _, output := range kgtnOutputs {
// Create local copy to prevent pointer to loop variable to be
// passed in with disastrous consequences.
local := output
// Calculate the deadline height and budget for this output.
deadline, budget := u.decideDeadlineAndBudget(local)
resultChan, err := u.cfg.SweepInput(&local, sweep.Params{
DeadlineHeight: deadline,
Budget: budget,
})
if err != nil {
return err
}
u.wg.Add(1)
go u.waitForSweepConf(classHeight, &local, resultChan)
}
return nil
}
// waitForSweepConf watches for the confirmation of a sweep transaction
// containing a batch of kindergarten outputs. Once confirmation has been
// received, the nursery will mark those outputs as fully graduated, and proceed
// to mark any mature channels as fully closed in channeldb.
// NOTE(conner): this method MUST be called as a go routine.
func (u *UtxoNursery) waitForSweepConf(classHeight uint32,
output *kidOutput, resultChan chan sweep.Result) {
defer u.wg.Done()
select {
case result, ok := <-resultChan:
if !ok {
utxnLog.Errorf("Notification chan closed, can't" +
" advance graduating output")
return
}
// In case of a remote spend, still graduate the output. There
// is no way to sweep it anymore.
if result.Err == sweep.ErrRemoteSpend {
utxnLog.Infof("Output %v was spend by remote party",
output.OutPoint())
break
}
if result.Err != nil {
utxnLog.Errorf("Failed to sweep %v at "+
"height=%d", output.OutPoint(),
classHeight)
return
}
case <-u.quit:
return
}
u.mu.Lock()
defer u.mu.Unlock()
// TODO(conner): add retry utxnLogic?
// Mark the confirmed kindergarten output as graduated.
if err := u.cfg.Store.GraduateKinder(classHeight, output); err != nil {
utxnLog.Errorf("Unable to graduate kindergarten output %v: %v",
output.OutPoint(), err)
return
}
utxnLog.Infof("Graduated kindergarten output from height=%d",
classHeight)
// Attempt to close the channel, only doing so if all of the channel's
// outputs have been graduated.
chanPoint := output.OriginChanPoint()
if err := u.closeAndRemoveIfMature(chanPoint); err != nil {
utxnLog.Errorf("Failed to close and remove channel %v",
*chanPoint)
return
}
}
// sweepCribOutput broadcasts the crib output's htlc timeout txn, and sets up a
// notification that will advance it to the kindergarten bucket upon
// confirmation.
func (u *UtxoNursery) sweepCribOutput(classHeight uint32, baby *babyOutput) error {
utxnLog.Infof("Publishing CLTV-delayed HTLC output using timeout tx "+
"(txid=%v): %v", baby.timeoutTx.TxHash(),
lnutils.SpewLogClosure(baby.timeoutTx))
// We'll now broadcast the HTLC transaction, then wait for it to be
// confirmed before transitioning it to kindergarten.
label := labels.MakeLabel(labels.LabelTypeSweepTransaction, nil)
err := u.cfg.PublishTransaction(baby.timeoutTx, label)
// In case the tx does not meet mempool fee requirements we continue
// because the tx is rebroadcasted in the background and there is
// nothing we can do to bump this transaction anyways.
if err != nil && !errors.Is(err, lnwallet.ErrDoubleSpend) &&
!errors.Is(err, lnwallet.ErrMempoolFee) {
utxnLog.Errorf("Unable to broadcast baby tx: "+
"%v, %v", err, spew.Sdump(baby.timeoutTx))
return err
}
return u.registerTimeoutConf(baby, classHeight)
}
// registerTimeoutConf is responsible for subscribing to confirmation
// notification for an htlc timeout transaction. If successful, a goroutine
// will be spawned that will transition the provided baby output into the
// kindergarten state within the nursery store.
func (u *UtxoNursery) registerTimeoutConf(baby *babyOutput,
heightHint uint32) error {
birthTxID := baby.timeoutTx.TxHash()
// Register for the confirmation of presigned htlc txn.
confChan, err := u.cfg.Notifier.RegisterConfirmationsNtfn(
&birthTxID, baby.timeoutTx.TxOut[0].PkScript, u.cfg.ConfDepth,
heightHint,
)
if err != nil {
return err
}
utxnLog.Infof("Htlc output %v registered for promotion "+
"notification.", baby.OutPoint())
u.wg.Add(1)
go u.waitForTimeoutConf(baby, confChan)
return nil
}
// waitForTimeoutConf watches for the confirmation of an htlc timeout
// transaction, and attempts to move the htlc output from the crib bucket to the
// kindergarten bucket upon success.
func (u *UtxoNursery) waitForTimeoutConf(baby *babyOutput,
confChan *chainntnfs.ConfirmationEvent) {
defer u.wg.Done()
select {
case txConfirmation, ok := <-confChan.Confirmed:
if !ok {
utxnLog.Debugf("Notification chan "+
"closed, can't advance baby output %v",
baby.OutPoint())
return
}
baby.SetConfHeight(txConfirmation.BlockHeight)
case <-u.quit:
return
}
u.mu.Lock()
defer u.mu.Unlock()
// TODO(conner): add retry utxnLogic?
err := u.cfg.Store.CribToKinder(baby)
if err != nil {
utxnLog.Errorf("Unable to move htlc output from "+
"crib to kindergarten bucket: %v", err)
return
}
utxnLog.Infof("Htlc output %v promoted to "+
"kindergarten", baby.OutPoint())
}
// registerPreschoolConf is responsible for subscribing to the confirmation of
// a commitment transaction, or an htlc success transaction for an incoming
// HTLC on our commitment transaction.. If successful, the provided preschool
// output will be moved persistently into the kindergarten state within the
// nursery store.
func (u *UtxoNursery) registerPreschoolConf(kid *kidOutput, heightHint uint32) error {
txID := kid.OutPoint().Hash
// TODO(roasbeef): ensure we don't already have one waiting, need to
// de-duplicate
// * need to do above?
pkScript := kid.signDesc.Output.PkScript
confChan, err := u.cfg.Notifier.RegisterConfirmationsNtfn(
&txID, pkScript, u.cfg.ConfDepth, heightHint,
)
if err != nil {
return err
}
var outputType string
if kid.isHtlc {
outputType = "HTLC"
} else {
outputType = "Commitment"
}
utxnLog.Infof("%v outpoint %v registered for "+
"confirmation notification.", outputType, kid.OutPoint())
u.wg.Add(1)
go u.waitForPreschoolConf(kid, confChan)
return nil
}
// waitForPreschoolConf is intended to be run as a goroutine that will wait until
// a channel force close commitment transaction, or a second layer HTLC success
// transaction has been included in a confirmed block. Once the transaction has
// been confirmed (as reported by the Chain Notifier), waitForPreschoolConf
// will delete the output from the "preschool" database bucket and atomically
// add it to the "kindergarten" database bucket. This is the second step in
// the output incubation process.
func (u *UtxoNursery) waitForPreschoolConf(kid *kidOutput,
confChan *chainntnfs.ConfirmationEvent) {
defer u.wg.Done()
select {
case txConfirmation, ok := <-confChan.Confirmed:
if !ok {
utxnLog.Errorf("Notification chan "+
"closed, can't advance output %v",
kid.OutPoint())
return
}
kid.SetConfHeight(txConfirmation.BlockHeight)
case <-u.quit:
return
}
u.mu.Lock()
defer u.mu.Unlock()
// TODO(conner): add retry utxnLogic?
var outputType string
if kid.isHtlc {
outputType = "HTLC"
} else {
outputType = "Commitment"
}
bestHeight := atomic.LoadUint32(&u.bestHeight)
err := u.cfg.Store.PreschoolToKinder(kid, bestHeight)
if err != nil {
utxnLog.Errorf("Unable to move %v output "+
"from preschool to kindergarten bucket: %v",
outputType, err)
return
}
}
// RemoveChannel channel erases all entries from the channel bucket for the
// provided channel point.
func (u *UtxoNursery) RemoveChannel(op *wire.OutPoint) error {
return u.cfg.Store.RemoveChannel(op)
}
// ContractMaturityReport is a report that details the maturity progress of a
// particular force closed contract.
type ContractMaturityReport struct {
// limboBalance is the total number of frozen coins within this
// contract.
LimboBalance btcutil.Amount
// recoveredBalance is the total value that has been successfully swept
// back to the user's wallet.
RecoveredBalance btcutil.Amount
// htlcs records a maturity report for each htlc output in this channel.
Htlcs []HtlcMaturityReport
}
// HtlcMaturityReport provides a summary of a single htlc output, and is
// embedded as party of the overarching ContractMaturityReport.
type HtlcMaturityReport struct {
// Outpoint is the final output that will be swept back to the wallet.
Outpoint wire.OutPoint
// Amount is the final value that will be swept in back to the wallet.
Amount btcutil.Amount
// MaturityHeight is the absolute block height that this output will
// mature at.
MaturityHeight uint32
// Stage indicates whether the htlc is in the CLTV-timeout stage (1) or
// the CSV-delay stage (2). A stage 1 htlc's maturity height will be set
// to its expiry height, while a stage 2 htlc's maturity height will be
// set to its confirmation height plus the maturity requirement.
Stage uint32
}
// AddLimboStage1TimeoutHtlc adds an htlc crib output to the maturity report's
// htlcs, and contributes its amount to the limbo balance.
func (c *ContractMaturityReport) AddLimboStage1TimeoutHtlc(baby *babyOutput) {
c.LimboBalance += baby.Amount()
// TODO(roasbeef): bool to indicate stage 1 vs stage 2?
c.Htlcs = append(c.Htlcs, HtlcMaturityReport{
Outpoint: baby.OutPoint(),
Amount: baby.Amount(),
MaturityHeight: baby.expiry,
Stage: 1,
})
}
// AddLimboDirectHtlc adds a direct HTLC on the commitment transaction of the
// remote party to the maturity report. This a CLTV time-locked output that
// has or hasn't expired yet.
func (c *ContractMaturityReport) AddLimboDirectHtlc(kid *kidOutput) {
c.LimboBalance += kid.Amount()
htlcReport := HtlcMaturityReport{
Outpoint: kid.OutPoint(),
Amount: kid.Amount(),
MaturityHeight: kid.absoluteMaturity,
Stage: 2,
}
c.Htlcs = append(c.Htlcs, htlcReport)
}
// AddLimboStage1SuccessHtlcHtlc adds an htlc crib output to the maturity
// report's set of HTLC's. We'll use this to report any incoming HTLC sweeps
// where the second level transaction hasn't yet confirmed.
func (c *ContractMaturityReport) AddLimboStage1SuccessHtlc(kid *kidOutput) {
c.LimboBalance += kid.Amount()
c.Htlcs = append(c.Htlcs, HtlcMaturityReport{
Outpoint: kid.OutPoint(),
Amount: kid.Amount(),
Stage: 1,
})
}
// AddLimboStage2Htlc adds an htlc kindergarten output to the maturity report's
// htlcs, and contributes its amount to the limbo balance.
func (c *ContractMaturityReport) AddLimboStage2Htlc(kid *kidOutput) {
c.LimboBalance += kid.Amount()
htlcReport := HtlcMaturityReport{
Outpoint: kid.OutPoint(),
Amount: kid.Amount(),
Stage: 2,
}
// If the confirmation height is set, then this means the first stage
// has been confirmed, and we know the final maturity height of the CSV
// delay.
if kid.ConfHeight() != 0 {
htlcReport.MaturityHeight = kid.ConfHeight() + kid.BlocksToMaturity()
}
c.Htlcs = append(c.Htlcs, htlcReport)
}
// AddRecoveredHtlc adds a graduate output to the maturity report's htlcs, and
// contributes its amount to the recovered balance.
func (c *ContractMaturityReport) AddRecoveredHtlc(kid *kidOutput) {
c.RecoveredBalance += kid.Amount()
c.Htlcs = append(c.Htlcs, HtlcMaturityReport{
Outpoint: kid.OutPoint(),
Amount: kid.Amount(),
MaturityHeight: kid.ConfHeight() + kid.BlocksToMaturity(),
})
}
// closeAndRemoveIfMature removes a particular channel from the channel index
// if and only if all of its outputs have been marked graduated. If the channel
// still has ungraduated outputs, the method will succeed without altering the
// database state.
func (u *UtxoNursery) closeAndRemoveIfMature(chanPoint *wire.OutPoint) error {
isMature, err := u.cfg.Store.IsMatureChannel(chanPoint)
if err == ErrContractNotFound {
return nil
} else if err != nil {
utxnLog.Errorf("Unable to determine maturity of "+
"channel=%s", chanPoint)
return err
}
// Nothing to do if we are still incubating.
if !isMature {
return nil
}
// Now that the channel is fully closed, we remove the channel from the
// nursery store here. This preserves the invariant that we never remove
// a channel unless it is mature, as this is the only place the utxo
// nursery removes a channel.
if err := u.cfg.Store.RemoveChannel(chanPoint); err != nil {
utxnLog.Errorf("Unable to remove channel=%s from "+
"nursery store: %v", chanPoint, err)
return err
}
utxnLog.Infof("Removed channel %v from nursery store", chanPoint)
return nil
}
// babyOutput represents a two-stage CSV locked output, and is used to track
// htlc outputs through incubation. The first stage requires broadcasting a
// presigned timeout txn that spends from the CLTV locked output on the
// commitment txn. A babyOutput is treated as a subset of CsvSpendableOutputs,
// with the additional constraint that a transaction must be broadcast before
// it can be spent. Each baby transaction embeds the kidOutput that can later
// be used to spend the CSV output contained in the timeout txn.
//
// TODO(roasbeef): re-rename to timeout tx
// - create CltvCsvSpendableOutput
type babyOutput struct {
// expiry is the absolute block height at which the secondLevelTx
// should be broadcast to the network.
//
// NOTE: This value will be zero if this is a baby output for a prior
// incoming HTLC.
expiry uint32
// timeoutTx is a fully-signed transaction that, upon confirmation,
// transitions the htlc into the delay+claim stage.
timeoutTx *wire.MsgTx
// kidOutput represents the CSV output to be swept from the
// secondLevelTx after it has been broadcast and confirmed.
kidOutput
}
// makeBabyOutput constructs a baby output that wraps a future kidOutput. The
// provided sign descriptors and witness types will be used once the output
// reaches the delay and claim stage.
func makeBabyOutput(chanPoint *wire.OutPoint,
htlcResolution *lnwallet.OutgoingHtlcResolution,
deadlineHeight fn.Option[int32]) babyOutput {
htlcOutpoint := htlcResolution.ClaimOutpoint
blocksToMaturity := htlcResolution.CsvDelay
isTaproot := txscript.IsPayToTaproot(
htlcResolution.SweepSignDesc.Output.PkScript,
)
var witnessType input.StandardWitnessType
if isTaproot {
witnessType = input.TaprootHtlcOfferedTimeoutSecondLevel
} else {
witnessType = input.HtlcOfferedTimeoutSecondLevel
}
kid := makeKidOutput(
&htlcOutpoint, chanPoint, blocksToMaturity, witnessType,
&htlcResolution.SweepSignDesc, 0, deadlineHeight,
)
return babyOutput{
kidOutput: kid,
expiry: htlcResolution.Expiry,
timeoutTx: htlcResolution.SignedTimeoutTx,
}
}
// Encode writes the baby output to the given io.Writer.
func (bo *babyOutput) Encode(w io.Writer) error {
var scratch [4]byte
byteOrder.PutUint32(scratch[:], bo.expiry)
if _, err := w.Write(scratch[:]); err != nil {
return err
}
if err := bo.timeoutTx.Serialize(w); err != nil {
return err
}
return bo.kidOutput.Encode(w)
}
// Decode reconstructs a baby output using the provided io.Reader.
func (bo *babyOutput) Decode(r io.Reader) error {
var scratch [4]byte
if _, err := r.Read(scratch[:]); err != nil {
return err
}
bo.expiry = byteOrder.Uint32(scratch[:])
bo.timeoutTx = new(wire.MsgTx)
if err := bo.timeoutTx.Deserialize(r); err != nil {
return err
}
return bo.kidOutput.Decode(r)
}
// kidOutput represents an output that's waiting for a required blockheight
// before its funds will be available to be moved into the user's wallet. The
// struct includes a WitnessGenerator closure which will be used to generate
// the witness required to sweep the output once it's mature.
//
// TODO(roasbeef): rename to immatureOutput?
type kidOutput struct {
breachedOutput
originChanPoint wire.OutPoint
// isHtlc denotes if this kid output is an HTLC output or not. This
// value will be used to determine how to report this output within the
// nursery report.
isHtlc bool
// blocksToMaturity is the relative CSV delay required after initial
// confirmation of the commitment transaction before we can sweep this
// output.
//
// NOTE: This will be set for: commitment outputs, and incoming HTLC's.
// Otherwise, this will be zero. It will also be non-zero for
// commitment types which requires confirmed spends.
blocksToMaturity uint32
// absoluteMaturity is the absolute height that this output will be
// mature at. In order to sweep the output after this height, the
// locktime of sweep transaction will need to be set to this value.
//
// NOTE: This will only be set for: outgoing HTLC's on the commitment
// transaction of the remote party.
absoluteMaturity uint32
// deadlineHeight is the absolute height that this output should be
// confirmed at. For an incoming HTLC, this is the CLTV expiry height.
// For outgoing HTLC, this is its corresponding incoming HTLC's CLTV
// expiry height.
deadlineHeight fn.Option[int32]
}
func makeKidOutput(outpoint, originChanPoint *wire.OutPoint,
blocksToMaturity uint32, witnessType input.StandardWitnessType,
signDescriptor *input.SignDescriptor, absoluteMaturity uint32,
deadlineHeight fn.Option[int32]) kidOutput {
// This is an HTLC either if it's an incoming HTLC on our commitment
// transaction, or is an outgoing HTLC on the commitment transaction of
// the remote peer.
isHtlc := (witnessType == input.HtlcAcceptedSuccessSecondLevel ||
witnessType == input.TaprootHtlcAcceptedSuccessSecondLevel ||
witnessType == input.TaprootHtlcOfferedRemoteTimeout ||
witnessType == input.HtlcOfferedRemoteTimeout)
// heightHint can be safely set to zero here, because after this
// function returns, nursery will set a proper confirmation height in
// waitForTimeoutConf or waitForPreschoolConf.
heightHint := uint32(0)
return kidOutput{
breachedOutput: makeBreachedOutput(
outpoint, witnessType, nil, signDescriptor, heightHint,
fn.None[tlv.Blob](),
),
isHtlc: isHtlc,
originChanPoint: *originChanPoint,
blocksToMaturity: blocksToMaturity,
absoluteMaturity: absoluteMaturity,
deadlineHeight: deadlineHeight,
}
}
func (k *kidOutput) OriginChanPoint() *wire.OutPoint {
return &k.originChanPoint
}
func (k *kidOutput) BlocksToMaturity() uint32 {
return k.blocksToMaturity
}
func (k *kidOutput) SetConfHeight(height uint32) {
k.confHeight = height
}
func (k *kidOutput) ConfHeight() uint32 {
return k.confHeight
}
func (k *kidOutput) RequiredLockTime() (uint32, bool) {
return k.absoluteMaturity, k.absoluteMaturity > 0
}
// Encode converts a KidOutput struct into a form suitable for on-disk database
// storage. Note that the signDescriptor struct field is included so that the
// output's witness can be generated by createSweepTx() when the output becomes
// spendable.
func (k *kidOutput) Encode(w io.Writer) error {
var scratch [8]byte
byteOrder.PutUint64(scratch[:], uint64(k.Amount()))
if _, err := w.Write(scratch[:]); err != nil {
return err
}
op := k.OutPoint()
if err := graphdb.WriteOutpoint(w, &op); err != nil {
return err
}
if err := graphdb.WriteOutpoint(w, k.OriginChanPoint()); err != nil {
return err
}
if err := binary.Write(w, byteOrder, k.isHtlc); err != nil {
return err
}
byteOrder.PutUint32(scratch[:4], k.BlocksToMaturity())
if _, err := w.Write(scratch[:4]); err != nil {
return err
}
byteOrder.PutUint32(scratch[:4], k.absoluteMaturity)
if _, err := w.Write(scratch[:4]); err != nil {
return err
}
byteOrder.PutUint32(scratch[:4], k.ConfHeight())
if _, err := w.Write(scratch[:4]); err != nil {
return err
}
byteOrder.PutUint16(scratch[:2], uint16(k.witnessType))
if _, err := w.Write(scratch[:2]); err != nil {
return err
}
if err := input.WriteSignDescriptor(w, k.SignDesc()); err != nil {
return err
}
if k.SignDesc().ControlBlock == nil {
return nil
}
// If this is a taproot output, then it'll also have a control block,
// so we'll go ahead and write that now.
return wire.WriteVarBytes(w, 1000, k.SignDesc().ControlBlock)
}
// Decode takes a byte array representation of a kidOutput and converts it to an
// struct. Note that the witnessFunc method isn't added during deserialization
// and must be added later based on the value of the witnessType field.
func (k *kidOutput) Decode(r io.Reader) error {
var scratch [8]byte
if _, err := r.Read(scratch[:]); err != nil {
return err
}
k.amt = btcutil.Amount(byteOrder.Uint64(scratch[:]))
err := graphdb.ReadOutpoint(io.LimitReader(r, 40), &k.outpoint)
if err != nil {
return err
}
err = graphdb.ReadOutpoint(io.LimitReader(r, 40), &k.originChanPoint)
if err != nil {
return err
}
if err := binary.Read(r, byteOrder, &k.isHtlc); err != nil {
return err
}
if _, err := r.Read(scratch[:4]); err != nil {
return err
}
k.blocksToMaturity = byteOrder.Uint32(scratch[:4])
if _, err := r.Read(scratch[:4]); err != nil {
return err
}
k.absoluteMaturity = byteOrder.Uint32(scratch[:4])
if _, err := r.Read(scratch[:4]); err != nil {
return err
}
k.confHeight = byteOrder.Uint32(scratch[:4])
if _, err := r.Read(scratch[:2]); err != nil {
return err
}
k.witnessType = input.StandardWitnessType(byteOrder.Uint16(scratch[:2]))
if err := input.ReadSignDescriptor(r, &k.signDesc); err != nil {
return err
}
// If there's anything left in the reader, then this is a taproot
// output that also wrote a control block.
ctrlBlock, err := wire.ReadVarBytes(r, 0, 1000, "control block")
switch {
// If there're no bytes remaining, then we'll return early.
case errors.Is(err, io.EOF):
fallthrough
case errors.Is(err, io.ErrUnexpectedEOF):
return nil
case err != nil:
return err
}
k.signDesc.ControlBlock = ctrlBlock
return nil
}
// Compile-time constraint to ensure kidOutput implements the
// Input interface.
var _ input.Input = (*kidOutput)(nil)
package discovery
import (
"errors"
"sync"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightninglabs/neutrino/cache"
"github.com/lightninglabs/neutrino/cache/lru"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwire"
)
const (
// maxBannedPeers limits the maximum number of banned pubkeys that
// we'll store.
// TODO(eugene): tune.
maxBannedPeers = 10_000
// banThreshold is the point at which non-channel peers will be banned.
// TODO(eugene): tune.
banThreshold = 100
// banTime is the amount of time that the non-channel peer will be
// banned for. Channel announcements from channel peers will be dropped
// if it's not one of our channels.
// TODO(eugene): tune.
banTime = time.Hour * 48
// resetDelta is the time after a peer's last ban update that we'll
// reset its ban score.
// TODO(eugene): tune.
resetDelta = time.Hour * 48
// purgeInterval is how often we'll remove entries from the
// peerBanIndex and allow peers to be un-banned. This interval is also
// used to reset ban scores of peers that aren't banned.
purgeInterval = time.Minute * 10
)
var ErrPeerBanned = errors.New("peer has bypassed ban threshold - banning")
// ClosedChannelTracker handles closed channels being gossiped to us.
type ClosedChannelTracker interface {
// GraphCloser is used to mark channels as closed and to check whether
// certain channels are closed.
GraphCloser
// IsChannelPeer checks whether we have a channel with a peer.
IsChannelPeer(*btcec.PublicKey) (bool, error)
}
// GraphCloser handles tracking closed channels by their scid.
type GraphCloser interface {
// PutClosedScid marks a channel as closed so that we won't validate
// channel announcements for it again.
PutClosedScid(lnwire.ShortChannelID) error
// IsClosedScid checks if a short channel id is closed.
IsClosedScid(lnwire.ShortChannelID) (bool, error)
}
// NodeInfoInquirier handles queries relating to specific nodes and channels
// they may have with us.
type NodeInfoInquirer interface {
// FetchOpenChannels returns the set of channels that we have with the
// peer identified by the passed-in public key.
FetchOpenChannels(*btcec.PublicKey) ([]*channeldb.OpenChannel, error)
}
// ScidCloserMan helps the gossiper handle closed channels that are in the
// ChannelGraph.
type ScidCloserMan struct {
graph GraphCloser
channelDB NodeInfoInquirer
}
// NewScidCloserMan creates a new ScidCloserMan.
func NewScidCloserMan(graph GraphCloser,
channelDB NodeInfoInquirer) *ScidCloserMan {
return &ScidCloserMan{
graph: graph,
channelDB: channelDB,
}
}
// PutClosedScid marks scid as closed so the gossiper can ignore this channel
// in the future.
func (s *ScidCloserMan) PutClosedScid(scid lnwire.ShortChannelID) error {
return s.graph.PutClosedScid(scid)
}
// IsClosedScid checks whether scid is closed so that the gossiper can ignore
// it.
func (s *ScidCloserMan) IsClosedScid(scid lnwire.ShortChannelID) (bool,
error) {
return s.graph.IsClosedScid(scid)
}
// IsChannelPeer checks whether we have a channel with the peer.
func (s *ScidCloserMan) IsChannelPeer(peerKey *btcec.PublicKey) (bool, error) {
chans, err := s.channelDB.FetchOpenChannels(peerKey)
if err != nil {
return false, err
}
return len(chans) > 0, nil
}
// A compile-time constraint to ensure ScidCloserMan implements
// ClosedChannelTracker.
var _ ClosedChannelTracker = (*ScidCloserMan)(nil)
// cachedBanInfo is used to track a peer's ban score and if it is banned.
type cachedBanInfo struct {
score uint64
lastUpdate time.Time
}
// Size returns the "size" of an entry.
func (c *cachedBanInfo) Size() (uint64, error) {
return 1, nil
}
// isBanned returns true if the ban score is greater than the ban threshold.
func (c *cachedBanInfo) isBanned() bool {
return c.score >= banThreshold
}
// banman is responsible for banning peers that are misbehaving. The banman is
// in-memory and will be reset upon restart of LND. If a node's pubkey is in
// the peerBanIndex, it has a ban score. Ban scores start at 1 and are
// incremented by 1 for each instance of misbehavior. It uses an LRU cache to
// cut down on memory usage in case there are many banned peers and to protect
// against DoS.
type banman struct {
// peerBanIndex tracks our peers' ban scores and if they are banned and
// for how long. The ban score is incremented when our peer gives us
// gossip messages that are invalid.
peerBanIndex *lru.Cache[[33]byte, *cachedBanInfo]
wg sync.WaitGroup
quit chan struct{}
}
// newBanman creates a new banman with the default maxBannedPeers.
func newBanman() *banman {
return &banman{
peerBanIndex: lru.NewCache[[33]byte, *cachedBanInfo](
maxBannedPeers,
),
quit: make(chan struct{}),
}
}
// start kicks off the banman by calling purgeExpiredBans.
func (b *banman) start() {
b.wg.Add(1)
go b.purgeExpiredBans()
}
// stop halts the banman.
func (b *banman) stop() {
close(b.quit)
b.wg.Wait()
}
// purgeOldEntries removes ban entries if their ban has expired.
func (b *banman) purgeExpiredBans() {
defer b.wg.Done()
purgeTicker := time.NewTicker(purgeInterval)
defer purgeTicker.Stop()
for {
select {
case <-purgeTicker.C:
b.purgeBanEntries()
case <-b.quit:
return
}
}
}
// purgeBanEntries does two things:
// - removes peers from our ban list whose ban timer is up
// - removes peers whose ban scores have expired.
func (b *banman) purgeBanEntries() {
keysToRemove := make([][33]byte, 0)
sweepEntries := func(pubkey [33]byte, banInfo *cachedBanInfo) bool {
if banInfo.isBanned() {
// If the peer is banned, check if the ban timer has
// expired.
if banInfo.lastUpdate.Add(banTime).Before(time.Now()) {
keysToRemove = append(keysToRemove, pubkey)
}
return true
}
if banInfo.lastUpdate.Add(resetDelta).Before(time.Now()) {
// Remove non-banned peers whose ban scores have
// expired.
keysToRemove = append(keysToRemove, pubkey)
}
return true
}
b.peerBanIndex.Range(sweepEntries)
for _, key := range keysToRemove {
b.peerBanIndex.Delete(key)
}
}
// isBanned checks whether the peer identified by the pubkey is banned.
func (b *banman) isBanned(pubkey [33]byte) bool {
banInfo, err := b.peerBanIndex.Get(pubkey)
switch {
case errors.Is(err, cache.ErrElementNotFound):
return false
default:
return banInfo.isBanned()
}
}
// incrementBanScore increments a peer's ban score.
func (b *banman) incrementBanScore(pubkey [33]byte) {
banInfo, err := b.peerBanIndex.Get(pubkey)
switch {
case errors.Is(err, cache.ErrElementNotFound):
cachedInfo := &cachedBanInfo{
score: 1,
lastUpdate: time.Now(),
}
_, _ = b.peerBanIndex.Put(pubkey, cachedInfo)
default:
cachedInfo := &cachedBanInfo{
score: banInfo.score + 1,
lastUpdate: time.Now(),
}
_, _ = b.peerBanIndex.Put(pubkey, cachedInfo)
}
}
package discovery
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"errors"
"fmt"
prand "math/rand"
"net"
"strconv"
"strings"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil/bech32"
"github.com/lightningnetwork/lnd/autopilot"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tor"
"github.com/miekg/dns"
)
func init() {
prand.Seed(time.Now().Unix())
}
// NetworkPeerBootstrapper is an interface that represents an initial peer
// bootstrap mechanism. This interface is to be used to bootstrap a new peer to
// the connection by providing it with the pubkey+address of a set of existing
// peers on the network. Several bootstrap mechanisms can be implemented such
// as DNS, in channel graph, DHT's, etc.
type NetworkPeerBootstrapper interface {
// SampleNodeAddrs uniformly samples a set of specified address from
// the network peer bootstrapper source. The num addrs field passed in
// denotes how many valid peer addresses to return. The passed set of
// node nodes allows the caller to ignore a set of nodes perhaps
// because they already have connections established.
SampleNodeAddrs(numAddrs uint32,
ignore map[autopilot.NodeID]struct{}) ([]*lnwire.NetAddress, error)
// Name returns a human readable string which names the concrete
// implementation of the NetworkPeerBootstrapper.
Name() string
}
// MultiSourceBootstrap attempts to utilize a set of NetworkPeerBootstrapper
// passed in to return the target (numAddrs) number of peer addresses that can
// be used to bootstrap a peer just joining the Lightning Network. Each
// bootstrapper will be queried successively until the target amount is met. If
// the ignore map is populated, then the bootstrappers will be instructed to
// skip those nodes.
func MultiSourceBootstrap(ignore map[autopilot.NodeID]struct{}, numAddrs uint32,
bootstrappers ...NetworkPeerBootstrapper) ([]*lnwire.NetAddress, error) {
// We'll randomly shuffle our bootstrappers before querying them in
// order to avoid from querying the same bootstrapper method over and
// over, as some of these might tend to provide better/worse results
// than others.
bootstrappers = shuffleBootstrappers(bootstrappers)
var addrs []*lnwire.NetAddress
for _, bootstrapper := range bootstrappers {
// If we already have enough addresses, then we can exit early
// w/o querying the additional bootstrappers.
if uint32(len(addrs)) >= numAddrs {
break
}
log.Infof("Attempting to bootstrap with: %v", bootstrapper.Name())
// If we still need additional addresses, then we'll compute
// the number of address remaining that we need to fetch.
numAddrsLeft := numAddrs - uint32(len(addrs))
log.Tracef("Querying for %v addresses", numAddrsLeft)
netAddrs, err := bootstrapper.SampleNodeAddrs(numAddrsLeft, ignore)
if err != nil {
// If we encounter an error with a bootstrapper, then
// we'll continue on to the next available
// bootstrapper.
log.Errorf("Unable to query bootstrapper %v: %v",
bootstrapper.Name(), err)
continue
}
addrs = append(addrs, netAddrs...)
}
if len(addrs) == 0 {
return nil, errors.New("no addresses found")
}
log.Infof("Obtained %v addrs to bootstrap network with", len(addrs))
return addrs, nil
}
// shuffleBootstrappers shuffles the set of bootstrappers in order to avoid
// querying the same bootstrapper over and over. To shuffle the set of
// candidates, we use a version of the Fisher–Yates shuffle algorithm.
func shuffleBootstrappers(candidates []NetworkPeerBootstrapper) []NetworkPeerBootstrapper {
shuffled := make([]NetworkPeerBootstrapper, len(candidates))
perm := prand.Perm(len(candidates))
for i, v := range perm {
shuffled[v] = candidates[i]
}
return shuffled
}
// ChannelGraphBootstrapper is an implementation of the NetworkPeerBootstrapper
// which attempts to retrieve advertised peers directly from the active channel
// graph. This instance requires a backing autopilot.ChannelGraph instance in
// order to operate properly.
type ChannelGraphBootstrapper struct {
chanGraph autopilot.ChannelGraph
// hashAccumulator is a set of 32 random bytes that are read upon the
// creation of the channel graph bootstrapper. We use this value to
// randomly select nodes within the known graph to connect to. After
// each selection, we rotate the accumulator by hashing it with itself.
hashAccumulator [32]byte
tried map[autopilot.NodeID]struct{}
}
// A compile time assertion to ensure that ChannelGraphBootstrapper meets the
// NetworkPeerBootstrapper interface.
var _ NetworkPeerBootstrapper = (*ChannelGraphBootstrapper)(nil)
// NewGraphBootstrapper returns a new instance of a ChannelGraphBootstrapper
// backed by an active autopilot.ChannelGraph instance. This type of network
// peer bootstrapper will use the authenticated nodes within the known channel
// graph to bootstrap connections.
func NewGraphBootstrapper(cg autopilot.ChannelGraph) (NetworkPeerBootstrapper, error) {
c := &ChannelGraphBootstrapper{
chanGraph: cg,
tried: make(map[autopilot.NodeID]struct{}),
}
if _, err := rand.Read(c.hashAccumulator[:]); err != nil {
return nil, err
}
return c, nil
}
// SampleNodeAddrs uniformly samples a set of specified address from the
// network peer bootstrapper source. The num addrs field passed in denotes how
// many valid peer addresses to return.
//
// NOTE: Part of the NetworkPeerBootstrapper interface.
func (c *ChannelGraphBootstrapper) SampleNodeAddrs(numAddrs uint32,
ignore map[autopilot.NodeID]struct{}) ([]*lnwire.NetAddress, error) {
// We'll merge the ignore map with our currently selected map in order
// to ensure we don't return any duplicate nodes.
for n := range ignore {
log.Tracef("Ignored node %x for bootstrapping", n)
c.tried[n] = struct{}{}
}
// In order to bootstrap, we'll iterate all the nodes in the channel
// graph, accumulating nodes until either we go through all active
// nodes, or we reach our limit. We ensure that we meet the randomly
// sample constraint as we maintain an xor accumulator to ensure we
// randomly sample nodes independent of the iteration of the channel
// graph.
sampleAddrs := func() ([]*lnwire.NetAddress, error) {
var (
a []*lnwire.NetAddress
// We'll create a special error so we can return early
// and abort the transaction once we find a match.
errFound = fmt.Errorf("found node")
)
err := c.chanGraph.ForEachNode(func(node autopilot.Node) error {
nID := autopilot.NodeID(node.PubKey())
if _, ok := c.tried[nID]; ok {
return nil
}
// We'll select the first node we come across who's
// public key is less than our current accumulator
// value. When comparing, we skip the first byte as
// it's 50/50. If it isn't less, than then we'll
// continue forward.
nodePubKeyBytes := node.PubKey()
if bytes.Compare(c.hashAccumulator[:], nodePubKeyBytes[1:]) > 0 {
return nil
}
for _, nodeAddr := range node.Addrs() {
// If we haven't yet reached our limit, then
// we'll copy over the details of this node
// into the set of addresses to be returned.
switch nodeAddr.(type) {
case *net.TCPAddr, *tor.OnionAddr:
default:
// If this isn't a valid address
// supported by the protocol, then we'll
// skip this node.
return nil
}
nodePub, err := btcec.ParsePubKey(
nodePubKeyBytes[:],
)
if err != nil {
return err
}
// At this point, we've found an eligible node,
// so we'll return early with our shibboleth
// error.
a = append(a, &lnwire.NetAddress{
IdentityKey: nodePub,
Address: nodeAddr,
})
}
c.tried[nID] = struct{}{}
return errFound
})
if err != nil && err != errFound {
return nil, err
}
return a, nil
}
// We'll loop and sample new addresses from the graph source until
// we've reached our target number of outbound connections or we hit 50
// attempts, which ever comes first.
var (
addrs []*lnwire.NetAddress
tries uint32
)
for tries < 30 && uint32(len(addrs)) < numAddrs {
sampleAddrs, err := sampleAddrs()
if err != nil {
return nil, err
}
tries++
// We'll now rotate our hash accumulator one value forwards.
c.hashAccumulator = sha256.Sum256(c.hashAccumulator[:])
// If this attempt didn't yield any addresses, then we'll exit
// early.
if len(sampleAddrs) == 0 {
continue
}
addrs = append(addrs, sampleAddrs...)
}
log.Tracef("Ending hash accumulator state: %x", c.hashAccumulator)
return addrs, nil
}
// Name returns a human readable string which names the concrete implementation
// of the NetworkPeerBootstrapper.
//
// NOTE: Part of the NetworkPeerBootstrapper interface.
func (c *ChannelGraphBootstrapper) Name() string {
return "Authenticated Channel Graph"
}
// DNSSeedBootstrapper as an implementation of the NetworkPeerBootstrapper
// interface which implements peer bootstrapping via a special DNS seed as
// defined in BOLT-0010. For further details concerning Lightning's current DNS
// boot strapping protocol, see this link:
// - https://github.com/lightningnetwork/lightning-rfc/blob/master/10-dns-bootstrap.md
type DNSSeedBootstrapper struct {
// dnsSeeds is an array of two tuples we'll use for bootstrapping. The
// first item in the tuple is the primary host we'll use to attempt the
// SRV lookup we require. If we're unable to receive a response over
// UDP, then we'll fall back to manual TCP resolution. The second item
// in the tuple is a special A record that we'll query in order to
// receive the IP address of the current authoritative DNS server for
// the network seed.
dnsSeeds [][2]string
net tor.Net
// timeout is the maximum amount of time a dial will wait for a connect to
// complete.
timeout time.Duration
}
// A compile time assertion to ensure that DNSSeedBootstrapper meets the
// NetworkPeerjBootstrapper interface.
var _ NetworkPeerBootstrapper = (*ChannelGraphBootstrapper)(nil)
// NewDNSSeedBootstrapper returns a new instance of the DNSSeedBootstrapper.
// The set of passed seeds should point to DNS servers that properly implement
// Lightning's DNS peer bootstrapping protocol as defined in BOLT-0010. The set
// of passed DNS seeds should come in pairs, with the second host name to be
// used as a fallback for manual TCP resolution in the case of an error
// receiving the UDP response. The second host should return a single A record
// with the IP address of the authoritative name server.
func NewDNSSeedBootstrapper(
seeds [][2]string, net tor.Net,
timeout time.Duration) NetworkPeerBootstrapper {
return &DNSSeedBootstrapper{dnsSeeds: seeds, net: net, timeout: timeout}
}
// fallBackSRVLookup attempts to manually query for SRV records we need to
// properly bootstrap. We do this by querying the special record at the "soa."
// sub-domain of supporting DNS servers. The returned IP address will be the IP
// address of the authoritative DNS server. Once we have this IP address, we'll
// connect manually over TCP to request the SRV record. This is necessary as
// the records we return are currently too large for a class of resolvers,
// causing them to be filtered out. The targetEndPoint is the original end
// point that was meant to be hit.
func (d *DNSSeedBootstrapper) fallBackSRVLookup(soaShim string,
targetEndPoint string) ([]*net.SRV, error) {
log.Tracef("Attempting to query fallback DNS seed")
// First, we'll lookup the IP address of the server that will act as
// our shim.
addrs, err := d.net.LookupHost(soaShim)
if err != nil {
return nil, err
}
// Once we have the IP address, we'll establish a TCP connection using
// port 53.
dnsServer := net.JoinHostPort(addrs[0], "53")
conn, err := d.net.Dial("tcp", dnsServer, d.timeout)
if err != nil {
return nil, err
}
dnsHost := fmt.Sprintf("_nodes._tcp.%v.", targetEndPoint)
dnsConn := &dns.Conn{Conn: conn}
defer dnsConn.Close()
// With the connection established, we'll craft our SRV query, write
// toe request, then wait for the server to give our response.
msg := new(dns.Msg)
msg.SetQuestion(dnsHost, dns.TypeSRV)
if err := dnsConn.WriteMsg(msg); err != nil {
return nil, err
}
resp, err := dnsConn.ReadMsg()
if err != nil {
return nil, err
}
// If the message response code was not the success code, fail.
if resp.Rcode != dns.RcodeSuccess {
return nil, fmt.Errorf("unsuccessful SRV request, "+
"received: %v", resp.Rcode)
}
// Retrieve the RR(s) of the Answer section, and convert to the format
// that net.LookupSRV would normally return.
var rrs []*net.SRV
for _, rr := range resp.Answer {
srv := rr.(*dns.SRV)
rrs = append(rrs, &net.SRV{
Target: srv.Target,
Port: srv.Port,
Priority: srv.Priority,
Weight: srv.Weight,
})
}
return rrs, nil
}
// SampleNodeAddrs uniformly samples a set of specified address from the
// network peer bootstrapper source. The num addrs field passed in denotes how
// many valid peer addresses to return. The set of DNS seeds are used
// successively to retrieve eligible target nodes.
func (d *DNSSeedBootstrapper) SampleNodeAddrs(numAddrs uint32,
ignore map[autopilot.NodeID]struct{}) ([]*lnwire.NetAddress, error) {
var netAddrs []*lnwire.NetAddress
// We'll try all the registered DNS seeds, exiting early if one of them
// gives us all the peers we need.
//
// TODO(roasbeef): should combine results from both
search:
for _, dnsSeedTuple := range d.dnsSeeds {
// We'll first query the seed with an SRV record so we can
// obtain a random sample of the encoded public keys of nodes.
// We use the lndLookupSRV function for this task.
primarySeed := dnsSeedTuple[0]
_, addrs, err := d.net.LookupSRV(
"nodes", "tcp", primarySeed, d.timeout,
)
if err != nil {
log.Tracef("Unable to lookup SRV records via "+
"primary seed (%v): %v", primarySeed, err)
log.Trace("Falling back to secondary")
// If the host of the secondary seed is blank, then
// we'll bail here as we can't proceed.
if dnsSeedTuple[1] == "" {
log.Tracef("DNS seed %v has no secondary, "+
"skipping fallback", primarySeed)
continue
}
// If we get an error when trying to query via the
// primary seed, we'll fallback to the secondary seed
// before concluding failure.
soaShim := dnsSeedTuple[1]
addrs, err = d.fallBackSRVLookup(
soaShim, primarySeed,
)
if err != nil {
log.Tracef("Unable to query fall "+
"back dns seed (%v): %v", soaShim, err)
continue
}
log.Tracef("Successfully queried fallback DNS seed")
}
log.Tracef("Retrieved SRV records from dns seed: %v",
lnutils.SpewLogClosure(addrs))
// Next, we'll need to issue an A record request for each of
// the nodes, skipping it if nothing comes back.
for _, nodeSrv := range addrs {
if uint32(len(netAddrs)) >= numAddrs {
break search
}
// With the SRV target obtained, we'll now perform
// another query to obtain the IP address for the
// matching bech32 encoded node key. We use the
// lndLookup function for this task.
bechNodeHost := nodeSrv.Target
addrs, err := d.net.LookupHost(bechNodeHost)
if err != nil {
return nil, err
}
if len(addrs) == 0 {
log.Tracef("No addresses for %v, skipping",
bechNodeHost)
continue
}
log.Tracef("Attempting to convert: %v", bechNodeHost)
// If the host isn't correctly formatted, then we'll
// skip it.
if len(bechNodeHost) == 0 ||
!strings.Contains(bechNodeHost, ".") {
continue
}
// If we have a set of valid addresses, then we'll need
// to parse the public key from the original bech32
// encoded string.
bechNode := strings.Split(bechNodeHost, ".")
_, nodeBytes5Bits, err := bech32.Decode(bechNode[0])
if err != nil {
return nil, err
}
// Once we have the bech32 decoded pubkey, we'll need
// to convert the 5-bit word grouping into our regular
// 8-bit word grouping so we can convert it into a
// public key.
nodeBytes, err := bech32.ConvertBits(
nodeBytes5Bits, 5, 8, false,
)
if err != nil {
return nil, err
}
nodeKey, err := btcec.ParsePubKey(nodeBytes)
if err != nil {
return nil, err
}
// If we have an ignore list, and this node is in the
// ignore list, then we'll go to the next candidate.
if ignore != nil {
nID := autopilot.NewNodeID(nodeKey)
if _, ok := ignore[nID]; ok {
continue
}
}
// Finally we'll convert the host:port peer to a proper
// TCP address to use within the lnwire.NetAddress. We
// don't need to use the lndResolveTCP function here
// because we already have the host:port peer.
addr := net.JoinHostPort(
addrs[0],
strconv.FormatUint(uint64(nodeSrv.Port), 10),
)
tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
return nil, err
}
// Finally, with all the information parsed, we'll
// return this fully valid address as a connection
// attempt.
lnAddr := &lnwire.NetAddress{
IdentityKey: nodeKey,
Address: tcpAddr,
}
log.Tracef("Obtained %v as valid reachable "+
"node", lnAddr)
netAddrs = append(netAddrs, lnAddr)
}
}
return netAddrs, nil
}
// Name returns a human readable string which names the concrete
// implementation of the NetworkPeerBootstrapper.
func (d *DNSSeedBootstrapper) Name() string {
return fmt.Sprintf("BOLT-0010 DNS Seed: %v", d.dnsSeeds)
}
package discovery
import (
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/routing/route"
)
// ChannelGraphTimeSeries is an interface that provides time and block based
// querying into our view of the channel graph. New channels will have
// monotonically increasing block heights, and new channel updates will have
// increasing timestamps. Once we connect to a peer, we'll use the methods in
// this interface to determine if we're already in sync, or need to request
// some new information from them.
type ChannelGraphTimeSeries interface {
// HighestChanID should return the channel ID of the channel we know of
// that's furthest in the target chain. This channel will have a block
// height that's close to the current tip of the main chain as we
// know it. We'll use this to start our QueryChannelRange dance with
// the remote node.
HighestChanID(chain chainhash.Hash) (*lnwire.ShortChannelID, error)
// UpdatesInHorizon returns all known channel and node updates with an
// update timestamp between the start time and end time. We'll use this
// to catch up a remote node to the set of channel updates that they
// may have missed out on within the target chain.
UpdatesInHorizon(chain chainhash.Hash,
startTime time.Time, endTime time.Time) ([]lnwire.Message, error)
// FilterKnownChanIDs takes a target chain, and a set of channel ID's,
// and returns a filtered set of chan ID's. This filtered set of chan
// ID's represents the ID's that we don't know of which were in the
// passed superSet.
FilterKnownChanIDs(chain chainhash.Hash,
superSet []graphdb.ChannelUpdateInfo,
isZombieChan func(time.Time, time.Time) bool) (
[]lnwire.ShortChannelID, error)
// FilterChannelRange returns the set of channels that we created
// between the start height and the end height. The channel IDs are
// grouped by their common block height. We'll use this to to a remote
// peer's QueryChannelRange message.
FilterChannelRange(chain chainhash.Hash, startHeight, endHeight uint32,
withTimestamps bool) ([]graphdb.BlockChannelRange, error)
// FetchChanAnns returns a full set of channel announcements as well as
// their updates that match the set of specified short channel ID's.
// We'll use this to reply to a QueryShortChanIDs message sent by a
// remote peer. The response will contain a unique set of
// ChannelAnnouncements, the latest ChannelUpdate for each of the
// announcements, and a unique set of NodeAnnouncements.
FetchChanAnns(chain chainhash.Hash,
shortChanIDs []lnwire.ShortChannelID) ([]lnwire.Message, error)
// FetchChanUpdates returns the latest channel update messages for the
// specified short channel ID. If no channel updates are known for the
// channel, then an empty slice will be returned.
FetchChanUpdates(chain chainhash.Hash,
shortChanID lnwire.ShortChannelID) ([]*lnwire.ChannelUpdate1,
error)
}
// ChanSeries is an implementation of the ChannelGraphTimeSeries
// interface backed by the channeldb ChannelGraph database. We'll provide this
// implementation to the AuthenticatedGossiper so it can properly use the
// in-protocol channel range queries to quickly and efficiently synchronize our
// channel state with all peers.
type ChanSeries struct {
graph *graphdb.ChannelGraph
}
// NewChanSeries constructs a new ChanSeries backed by a channeldb.ChannelGraph.
// The returned ChanSeries implements the ChannelGraphTimeSeries interface.
func NewChanSeries(graph *graphdb.ChannelGraph) *ChanSeries {
return &ChanSeries{
graph: graph,
}
}
// HighestChanID should return is the channel ID of the channel we know of
// that's furthest in the target chain. This channel will have a block height
// that's close to the current tip of the main chain as we know it. We'll use
// this to start our QueryChannelRange dance with the remote node.
//
// NOTE: This is part of the ChannelGraphTimeSeries interface.
func (c *ChanSeries) HighestChanID(chain chainhash.Hash) (*lnwire.ShortChannelID, error) {
chanID, err := c.graph.HighestChanID()
if err != nil {
return nil, err
}
shortChanID := lnwire.NewShortChanIDFromInt(chanID)
return &shortChanID, nil
}
// UpdatesInHorizon returns all known channel and node updates with an update
// timestamp between the start time and end time. We'll use this to catch up a
// remote node to the set of channel updates that they may have missed out on
// within the target chain.
//
// NOTE: This is part of the ChannelGraphTimeSeries interface.
func (c *ChanSeries) UpdatesInHorizon(chain chainhash.Hash,
startTime time.Time, endTime time.Time) ([]lnwire.Message, error) {
var updates []lnwire.Message
// First, we'll query for all the set of channels that have an update
// that falls within the specified horizon.
chansInHorizon, err := c.graph.ChanUpdatesInHorizon(
startTime, endTime,
)
if err != nil {
return nil, err
}
for _, channel := range chansInHorizon {
// If the channel hasn't been fully advertised yet, or is a
// private channel, then we'll skip it as we can't construct a
// full authentication proof if one is requested.
if channel.Info.AuthProof == nil {
continue
}
chanAnn, edge1, edge2, err := netann.CreateChanAnnouncement(
channel.Info.AuthProof, channel.Info, channel.Policy1,
channel.Policy2,
)
if err != nil {
return nil, err
}
updates = append(updates, chanAnn)
if edge1 != nil {
// We don't want to send channel updates that don't
// conform to the spec (anymore).
err := netann.ValidateChannelUpdateFields(0, edge1)
if err != nil {
log.Errorf("not sending invalid channel "+
"update %v: %v", edge1, err)
} else {
updates = append(updates, edge1)
}
}
if edge2 != nil {
err := netann.ValidateChannelUpdateFields(0, edge2)
if err != nil {
log.Errorf("not sending invalid channel "+
"update %v: %v", edge2, err)
} else {
updates = append(updates, edge2)
}
}
}
// Next, we'll send out all the node announcements that have an update
// within the horizon as well. We send these second to ensure that they
// follow any active channels they have.
nodeAnnsInHorizon, err := c.graph.NodeUpdatesInHorizon(
startTime, endTime,
)
if err != nil {
return nil, err
}
for _, nodeAnn := range nodeAnnsInHorizon {
nodeAnn := nodeAnn
// Ensure we only forward nodes that are publicly advertised to
// prevent leaking information about nodes.
isNodePublic, err := c.graph.IsPublicNode(nodeAnn.PubKeyBytes)
if err != nil {
log.Errorf("Unable to determine if node %x is "+
"advertised: %v", nodeAnn.PubKeyBytes, err)
continue
}
if !isNodePublic {
log.Tracef("Skipping forwarding announcement for "+
"node %x due to being unadvertised",
nodeAnn.PubKeyBytes)
continue
}
nodeUpdate, err := nodeAnn.NodeAnnouncement(true)
if err != nil {
return nil, err
}
updates = append(updates, nodeUpdate)
}
return updates, nil
}
// FilterKnownChanIDs takes a target chain, and a set of channel ID's, and
// returns a filtered set of chan ID's. This filtered set of chan ID's
// represents the ID's that we don't know of which were in the passed superSet.
//
// NOTE: This is part of the ChannelGraphTimeSeries interface.
func (c *ChanSeries) FilterKnownChanIDs(_ chainhash.Hash,
superSet []graphdb.ChannelUpdateInfo,
isZombieChan func(time.Time, time.Time) bool) (
[]lnwire.ShortChannelID, error) {
newChanIDs, err := c.graph.FilterKnownChanIDs(superSet, isZombieChan)
if err != nil {
return nil, err
}
filteredIDs := make([]lnwire.ShortChannelID, 0, len(newChanIDs))
for _, chanID := range newChanIDs {
filteredIDs = append(
filteredIDs, lnwire.NewShortChanIDFromInt(chanID),
)
}
return filteredIDs, nil
}
// FilterChannelRange returns the set of channels that we created between the
// start height and the end height. The channel IDs are grouped by their common
// block height. We'll use this respond to a remote peer's QueryChannelRange
// message.
//
// NOTE: This is part of the ChannelGraphTimeSeries interface.
func (c *ChanSeries) FilterChannelRange(_ chainhash.Hash, startHeight,
endHeight uint32, withTimestamps bool) ([]graphdb.BlockChannelRange,
error) {
return c.graph.FilterChannelRange(
startHeight, endHeight, withTimestamps,
)
}
// FetchChanAnns returns a full set of channel announcements as well as their
// updates that match the set of specified short channel ID's. We'll use this
// to reply to a QueryShortChanIDs message sent by a remote peer. The response
// will contain a unique set of ChannelAnnouncements, the latest ChannelUpdate
// for each of the announcements, and a unique set of NodeAnnouncements.
//
// NOTE: This is part of the ChannelGraphTimeSeries interface.
func (c *ChanSeries) FetchChanAnns(chain chainhash.Hash,
shortChanIDs []lnwire.ShortChannelID) ([]lnwire.Message, error) {
chanIDs := make([]uint64, 0, len(shortChanIDs))
for _, chanID := range shortChanIDs {
chanIDs = append(chanIDs, chanID.ToUint64())
}
channels, err := c.graph.FetchChanInfos(chanIDs)
if err != nil {
return nil, err
}
// We'll use this map to ensure we don't send the same node
// announcement more than one time as one node may have many channel
// anns we'll need to send.
nodePubsSent := make(map[route.Vertex]struct{})
chanAnns := make([]lnwire.Message, 0, len(channels)*3)
for _, channel := range channels {
// If the channel doesn't have an authentication proof, then we
// won't send it over as it may not yet be finalized, or be a
// non-advertised channel.
if channel.Info.AuthProof == nil {
continue
}
chanAnn, edge1, edge2, err := netann.CreateChanAnnouncement(
channel.Info.AuthProof, channel.Info, channel.Policy1,
channel.Policy2,
)
if err != nil {
return nil, err
}
chanAnns = append(chanAnns, chanAnn)
if edge1 != nil {
chanAnns = append(chanAnns, edge1)
// If this edge has a validated node announcement, that
// we haven't yet sent, then we'll send that as well.
nodePub := channel.Node2.PubKeyBytes
hasNodeAnn := channel.Node2.HaveNodeAnnouncement
if _, ok := nodePubsSent[nodePub]; !ok && hasNodeAnn {
nodeAnn, err := channel.Node2.NodeAnnouncement(
true,
)
if err != nil {
return nil, err
}
chanAnns = append(chanAnns, nodeAnn)
nodePubsSent[nodePub] = struct{}{}
}
}
if edge2 != nil {
chanAnns = append(chanAnns, edge2)
// If this edge has a validated node announcement, that
// we haven't yet sent, then we'll send that as well.
nodePub := channel.Node1.PubKeyBytes
hasNodeAnn := channel.Node1.HaveNodeAnnouncement
if _, ok := nodePubsSent[nodePub]; !ok && hasNodeAnn {
nodeAnn, err := channel.Node1.NodeAnnouncement(
true,
)
if err != nil {
return nil, err
}
chanAnns = append(chanAnns, nodeAnn)
nodePubsSent[nodePub] = struct{}{}
}
}
}
return chanAnns, nil
}
// FetchChanUpdates returns the latest channel update messages for the
// specified short channel ID. If no channel updates are known for the channel,
// then an empty slice will be returned.
//
// NOTE: This is part of the ChannelGraphTimeSeries interface.
func (c *ChanSeries) FetchChanUpdates(chain chainhash.Hash,
shortChanID lnwire.ShortChannelID) ([]*lnwire.ChannelUpdate1, error) {
chanInfo, e1, e2, err := c.graph.FetchChannelEdgesByID(
shortChanID.ToUint64(),
)
if err != nil {
return nil, err
}
chanUpdates := make([]*lnwire.ChannelUpdate1, 0, 2)
if e1 != nil {
chanUpdate, err := netann.ChannelUpdateFromEdge(chanInfo, e1)
if err != nil {
return nil, err
}
chanUpdates = append(chanUpdates, chanUpdate)
}
if e2 != nil {
chanUpdate, err := netann.ChannelUpdateFromEdge(chanInfo, e2)
if err != nil {
return nil, err
}
chanUpdates = append(chanUpdates, chanUpdate)
}
return chanUpdates, nil
}
// A compile-time assertion to ensure that ChanSeries meets the
// ChannelGraphTimeSeries interface.
var _ ChannelGraphTimeSeries = (*ChanSeries)(nil)
package discovery
import (
"bytes"
"errors"
"fmt"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
"github.com/lightninglabs/neutrino/cache"
"github.com/lightninglabs/neutrino/cache/lru"
"github.com/lightningnetwork/lnd/batch"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnpeer"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"github.com/lightningnetwork/lnd/lnwallet/chanvalidate"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/multimutex"
"github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/ticker"
"golang.org/x/time/rate"
)
const (
// DefaultMaxChannelUpdateBurst is the default maximum number of updates
// for a specific channel and direction that we'll accept over an
// interval.
DefaultMaxChannelUpdateBurst = 10
// DefaultChannelUpdateInterval is the default interval we'll use to
// determine how often we should allow a new update for a specific
// channel and direction.
DefaultChannelUpdateInterval = time.Minute
// maxPrematureUpdates tracks the max amount of premature channel
// updates that we'll hold onto.
maxPrematureUpdates = 100
// maxFutureMessages tracks the max amount of future messages that
// we'll hold onto.
maxFutureMessages = 1000
// DefaultSubBatchDelay is the default delay we'll use when
// broadcasting the next announcement batch.
DefaultSubBatchDelay = 5 * time.Second
// maxRejectedUpdates tracks the max amount of rejected channel updates
// we'll maintain. This is the global size across all peers. We'll
// allocate ~3 MB max to the cache.
maxRejectedUpdates = 10_000
// DefaultProofMatureDelta specifies the default value used for
// ProofMatureDelta, which is the number of confirmations needed before
// processing the announcement signatures.
DefaultProofMatureDelta = 6
)
var (
// ErrGossiperShuttingDown is an error that is returned if the gossiper
// is in the process of being shut down.
ErrGossiperShuttingDown = errors.New("gossiper is shutting down")
// ErrGossipSyncerNotFound signals that we were unable to find an active
// gossip syncer corresponding to a gossip query message received from
// the remote peer.
ErrGossipSyncerNotFound = errors.New("gossip syncer not found")
// ErrNoFundingTransaction is returned when we are unable to find the
// funding transaction described by the short channel ID on chain.
ErrNoFundingTransaction = errors.New(
"unable to find the funding transaction",
)
// ErrInvalidFundingOutput is returned if the channel funding output
// fails validation.
ErrInvalidFundingOutput = errors.New(
"channel funding output validation failed",
)
// ErrChannelSpent is returned when we go to validate a channel, but
// the purported funding output has actually already been spent on
// chain.
ErrChannelSpent = errors.New("channel output has been spent")
// emptyPubkey is used to compare compressed pubkeys against an empty
// byte array.
emptyPubkey [33]byte
)
// optionalMsgFields is a set of optional message fields that external callers
// can provide that serve useful when processing a specific network
// announcement.
type optionalMsgFields struct {
capacity *btcutil.Amount
channelPoint *wire.OutPoint
remoteAlias *lnwire.ShortChannelID
tapscriptRoot fn.Option[chainhash.Hash]
}
// apply applies the optional fields within the functional options.
func (f *optionalMsgFields) apply(optionalMsgFields ...OptionalMsgField) {
for _, optionalMsgField := range optionalMsgFields {
optionalMsgField(f)
}
}
// OptionalMsgField is a functional option parameter that can be used to provide
// external information that is not included within a network message but serves
// useful when processing it.
type OptionalMsgField func(*optionalMsgFields)
// ChannelCapacity is an optional field that lets the gossiper know of the
// capacity of a channel.
func ChannelCapacity(capacity btcutil.Amount) OptionalMsgField {
return func(f *optionalMsgFields) {
f.capacity = &capacity
}
}
// ChannelPoint is an optional field that lets the gossiper know of the outpoint
// of a channel.
func ChannelPoint(op wire.OutPoint) OptionalMsgField {
return func(f *optionalMsgFields) {
f.channelPoint = &op
}
}
// TapscriptRoot is an optional field that lets the gossiper know of the root of
// the tapscript tree for a custom channel.
func TapscriptRoot(root fn.Option[chainhash.Hash]) OptionalMsgField {
return func(f *optionalMsgFields) {
f.tapscriptRoot = root
}
}
// RemoteAlias is an optional field that lets the gossiper know that a locally
// sent channel update is actually an update for the peer that should replace
// the ShortChannelID field with the remote's alias. This is only used for
// channels with peers where the option-scid-alias feature bit was negotiated.
// The channel update will be added to the graph under the original SCID, but
// will be modified and re-signed with this alias.
func RemoteAlias(alias *lnwire.ShortChannelID) OptionalMsgField {
return func(f *optionalMsgFields) {
f.remoteAlias = alias
}
}
// networkMsg couples a routing related wire message with the peer that
// originally sent it.
type networkMsg struct {
peer lnpeer.Peer
source *btcec.PublicKey
msg lnwire.Message
optionalMsgFields *optionalMsgFields
isRemote bool
err chan error
}
// chanPolicyUpdateRequest is a request that is sent to the server when a caller
// wishes to update a particular set of channels. New ChannelUpdate messages
// will be crafted to be sent out during the next broadcast epoch and the fee
// updates committed to the lower layer.
type chanPolicyUpdateRequest struct {
edgesToUpdate []EdgeWithInfo
errChan chan error
}
// PinnedSyncers is a set of node pubkeys for which we will maintain an active
// syncer at all times.
type PinnedSyncers map[route.Vertex]struct{}
// Config defines the configuration for the service. ALL elements within the
// configuration MUST be non-nil for the service to carry out its duties.
type Config struct {
// ChainHash is a hash that indicates which resident chain of the
// AuthenticatedGossiper. Any announcements that don't match this
// chain hash will be ignored.
//
// TODO(roasbeef): eventually make into map so can de-multiplex
// incoming announcements
// * also need to do same for Notifier
ChainHash chainhash.Hash
// Graph is the subsystem which is responsible for managing the
// topology of lightning network. After incoming channel, node, channel
// updates announcements are validated they are sent to the router in
// order to be included in the LN graph.
Graph graph.ChannelGraphSource
// ChainIO represents an abstraction over a source that can query the
// blockchain.
ChainIO lnwallet.BlockChainIO
// ChanSeries is an interfaces that provides access to a time series
// view of the current known channel graph. Each GossipSyncer enabled
// peer will utilize this in order to create and respond to channel
// graph time series queries.
ChanSeries ChannelGraphTimeSeries
// Notifier is used for receiving notifications of incoming blocks.
// With each new incoming block found we process previously premature
// announcements.
//
// TODO(roasbeef): could possibly just replace this with an epoch
// channel.
Notifier chainntnfs.ChainNotifier
// Broadcast broadcasts a particular set of announcements to all peers
// that the daemon is connected to. If supplied, the exclude parameter
// indicates that the target peer should be excluded from the
// broadcast.
Broadcast func(skips map[route.Vertex]struct{},
msg ...lnwire.Message) error
// NotifyWhenOnline is a function that allows the gossiper to be
// notified when a certain peer comes online, allowing it to
// retry sending a peer message.
//
// NOTE: The peerChan channel must be buffered.
NotifyWhenOnline func(peerPubKey [33]byte, peerChan chan<- lnpeer.Peer)
// NotifyWhenOffline is a function that allows the gossiper to be
// notified when a certain peer disconnects, allowing it to request a
// notification for when it reconnects.
NotifyWhenOffline func(peerPubKey [33]byte) <-chan struct{}
// FetchSelfAnnouncement retrieves our current node announcement, for
// use when determining whether we should update our peers about our
// presence in the network.
FetchSelfAnnouncement func() lnwire.NodeAnnouncement
// UpdateSelfAnnouncement produces a new announcement for our node with
// an updated timestamp which can be broadcast to our peers.
UpdateSelfAnnouncement func() (lnwire.NodeAnnouncement, error)
// ProofMatureDelta the number of confirmations which is needed before
// exchange the channel announcement proofs.
ProofMatureDelta uint32
// TrickleDelay the period of trickle timer which flushes to the
// network the pending batch of new announcements we've received since
// the last trickle tick.
TrickleDelay time.Duration
// RetransmitTicker is a ticker that ticks with a period which
// indicates that we should check if we need re-broadcast any of our
// personal channels.
RetransmitTicker ticker.Ticker
// RebroadcastInterval is the maximum time we wait between sending out
// channel updates for our active channels and our own node
// announcement. We do this to ensure our active presence on the
// network is known, and we are not being considered a zombie node or
// having zombie channels.
RebroadcastInterval time.Duration
// WaitingProofStore is a persistent storage of partial channel proof
// announcement messages. We use it to buffer half of the material
// needed to reconstruct a full authenticated channel announcement.
// Once we receive the other half the channel proof, we'll be able to
// properly validate it and re-broadcast it out to the network.
//
// TODO(wilmer): make interface to prevent channeldb dependency.
WaitingProofStore *channeldb.WaitingProofStore
// MessageStore is a persistent storage of gossip messages which we will
// use to determine which messages need to be resent for a given peer.
MessageStore GossipMessageStore
// AnnSigner is an instance of the MessageSigner interface which will
// be used to manually sign any outgoing channel updates. The signer
// implementation should be backed by the public key of the backing
// Lightning node.
//
// TODO(roasbeef): extract ann crafting + sign from fundingMgr into
// here?
AnnSigner lnwallet.MessageSigner
// ScidCloser is an instance of ClosedChannelTracker that helps the
// gossiper cut down on spam channel announcements for already closed
// channels.
ScidCloser ClosedChannelTracker
// NumActiveSyncers is the number of peers for which we should have
// active syncers with. After reaching NumActiveSyncers, any future
// gossip syncers will be passive.
NumActiveSyncers int
// NoTimestampQueries will prevent the GossipSyncer from querying
// timestamps of announcement messages from the peer and from replying
// to timestamp queries.
NoTimestampQueries bool
// RotateTicker is a ticker responsible for notifying the SyncManager
// when it should rotate its active syncers. A single active syncer with
// a chansSynced state will be exchanged for a passive syncer in order
// to ensure we don't keep syncing with the same peers.
RotateTicker ticker.Ticker
// HistoricalSyncTicker is a ticker responsible for notifying the
// syncManager when it should attempt a historical sync with a gossip
// sync peer.
HistoricalSyncTicker ticker.Ticker
// ActiveSyncerTimeoutTicker is a ticker responsible for notifying the
// syncManager when it should attempt to start the next pending
// activeSyncer due to the current one not completing its state machine
// within the timeout.
ActiveSyncerTimeoutTicker ticker.Ticker
// MinimumBatchSize is minimum size of a sub batch of announcement
// messages.
MinimumBatchSize int
// SubBatchDelay is the delay between sending sub batches of
// gossip messages.
SubBatchDelay time.Duration
// IgnoreHistoricalFilters will prevent syncers from replying with
// historical data when the remote peer sets a gossip_timestamp_range.
// This prevents ranges with old start times from causing us to dump the
// graph on connect.
IgnoreHistoricalFilters bool
// PinnedSyncers is a set of peers that will always transition to
// ActiveSync upon connection. These peers will never transition to
// PassiveSync.
PinnedSyncers PinnedSyncers
// MaxChannelUpdateBurst specifies the maximum number of updates for a
// specific channel and direction that we'll accept over an interval.
MaxChannelUpdateBurst int
// ChannelUpdateInterval specifies the interval we'll use to determine
// how often we should allow a new update for a specific channel and
// direction.
ChannelUpdateInterval time.Duration
// IsAlias returns true if a given ShortChannelID is an alias for
// option_scid_alias channels.
IsAlias func(scid lnwire.ShortChannelID) bool
// SignAliasUpdate is used to re-sign a channel update using the
// remote's alias if the option-scid-alias feature bit was negotiated.
SignAliasUpdate func(u *lnwire.ChannelUpdate1) (*ecdsa.Signature,
error)
// FindBaseByAlias finds the SCID stored in the graph by an alias SCID.
// This is used for channels that have negotiated the option-scid-alias
// feature bit.
FindBaseByAlias func(alias lnwire.ShortChannelID) (
lnwire.ShortChannelID, error)
// GetAlias allows the gossiper to look up the peer's alias for a given
// ChannelID. This is used to sign updates for them if the channel has
// no AuthProof and the option-scid-alias feature bit was negotiated.
GetAlias func(lnwire.ChannelID) (lnwire.ShortChannelID, error)
// FindChannel allows the gossiper to find a channel that we're party
// to without iterating over the entire set of open channels.
FindChannel func(node *btcec.PublicKey, chanID lnwire.ChannelID) (
*channeldb.OpenChannel, error)
// IsStillZombieChannel takes the timestamps of the latest channel
// updates for a channel and returns true if the channel should be
// considered a zombie based on these timestamps.
IsStillZombieChannel func(time.Time, time.Time) bool
// AssumeChannelValid toggles whether the gossiper will check for
// spent-ness of channel outpoints. For neutrino, this saves long
// rescans from blocking initial usage of the daemon.
AssumeChannelValid bool
// MsgRateBytes is the rate limit for the number of bytes per second
// that we'll allocate to outbound gossip messages.
MsgRateBytes uint64
// MsgBurstBytes is the allotted burst amount in bytes. This is the
// number of starting tokens in our token bucket algorithm.
MsgBurstBytes uint64
}
// processedNetworkMsg is a wrapper around networkMsg and a boolean. It is
// used to let the caller of the lru.Cache know if a message has already been
// processed or not.
type processedNetworkMsg struct {
processed bool
msg *networkMsg
}
// cachedNetworkMsg is a wrapper around a network message that can be used with
// *lru.Cache.
type cachedNetworkMsg struct {
msgs []*processedNetworkMsg
}
// Size returns the "size" of an entry. We return the number of items as we
// just want to limit the total amount of entries rather than do accurate size
// accounting.
func (c *cachedNetworkMsg) Size() (uint64, error) {
return uint64(len(c.msgs)), nil
}
// rejectCacheKey is the cache key that we'll use to track announcements we've
// recently rejected.
type rejectCacheKey struct {
pubkey [33]byte
chanID uint64
}
// newRejectCacheKey returns a new cache key for the reject cache.
func newRejectCacheKey(cid uint64, pub [33]byte) rejectCacheKey {
k := rejectCacheKey{
chanID: cid,
pubkey: pub,
}
return k
}
// sourceToPub returns a serialized-compressed public key for use in the reject
// cache.
func sourceToPub(pk *btcec.PublicKey) [33]byte {
var pub [33]byte
copy(pub[:], pk.SerializeCompressed())
return pub
}
// cachedReject is the empty value used to track the value for rejects.
type cachedReject struct {
}
// Size returns the "size" of an entry. We return 1 as we just want to limit
// the total size.
func (c *cachedReject) Size() (uint64, error) {
return 1, nil
}
// AuthenticatedGossiper is a subsystem which is responsible for receiving
// announcements, validating them and applying the changes to router, syncing
// lightning network with newly connected nodes, broadcasting announcements
// after validation, negotiating the channel announcement proofs exchange and
// handling the premature announcements. All outgoing announcements are
// expected to be properly signed as dictated in BOLT#7, additionally, all
// incoming message are expected to be well formed and signed. Invalid messages
// will be rejected by this struct.
type AuthenticatedGossiper struct {
// Parameters which are needed to properly handle the start and stop of
// the service.
started sync.Once
stopped sync.Once
// bestHeight is the height of the block at the tip of the main chain
// as we know it. Accesses *MUST* be done with the gossiper's lock
// held.
bestHeight uint32
quit chan struct{}
wg sync.WaitGroup
// cfg is a copy of the configuration struct that the gossiper service
// was initialized with.
cfg *Config
// blockEpochs encapsulates a stream of block epochs that are sent at
// every new block height.
blockEpochs *chainntnfs.BlockEpochEvent
// prematureChannelUpdates is a map of ChannelUpdates we have received
// that wasn't associated with any channel we know about. We store
// them temporarily, such that we can reprocess them when a
// ChannelAnnouncement for the channel is received.
prematureChannelUpdates *lru.Cache[uint64, *cachedNetworkMsg]
// banman tracks our peer's ban status.
banman *banman
// networkMsgs is a channel that carries new network broadcasted
// message from outside the gossiper service to be processed by the
// networkHandler.
networkMsgs chan *networkMsg
// futureMsgs is a list of premature network messages that have a block
// height specified in the future. We will save them and resend it to
// the chan networkMsgs once the block height has reached. The cached
// map format is,
// {msgID1: msg1, msgID2: msg2, ...}
futureMsgs *futureMsgCache
// chanPolicyUpdates is a channel that requests to update the
// forwarding policy of a set of channels is sent over.
chanPolicyUpdates chan *chanPolicyUpdateRequest
// selfKey is the identity public key of the backing Lightning node.
selfKey *btcec.PublicKey
// selfKeyLoc is the locator for the identity public key of the backing
// Lightning node.
selfKeyLoc keychain.KeyLocator
// channelMtx is used to restrict the database access to one
// goroutine per channel ID. This is done to ensure that when
// the gossiper is handling an announcement, the db state stays
// consistent between when the DB is first read until it's written.
channelMtx *multimutex.Mutex[uint64]
recentRejects *lru.Cache[rejectCacheKey, *cachedReject]
// syncMgr is a subsystem responsible for managing the gossip syncers
// for peers currently connected. When a new peer is connected, the
// manager will create its accompanying gossip syncer and determine
// whether it should have an activeSync or passiveSync sync type based
// on how many other gossip syncers are currently active. Any activeSync
// gossip syncers are started in a round-robin manner to ensure we're
// not syncing with multiple peers at the same time.
syncMgr *SyncManager
// reliableSender is a subsystem responsible for handling reliable
// message send requests to peers. This should only be used for channels
// that are unadvertised at the time of handling the message since if it
// is advertised, then peers should be able to get the message from the
// network.
reliableSender *reliableSender
// chanUpdateRateLimiter contains rate limiters for each direction of
// a channel update we've processed. We'll use these to determine
// whether we should accept a new update for a specific channel and
// direction.
//
// NOTE: This map must be synchronized with the main
// AuthenticatedGossiper lock.
chanUpdateRateLimiter map[uint64][2]*rate.Limiter
// vb is used to enforce job dependency ordering of gossip messages.
vb *ValidationBarrier
sync.Mutex
}
// New creates a new AuthenticatedGossiper instance, initialized with the
// passed configuration parameters.
func New(cfg Config, selfKeyDesc *keychain.KeyDescriptor) *AuthenticatedGossiper {
gossiper := &AuthenticatedGossiper{
selfKey: selfKeyDesc.PubKey,
selfKeyLoc: selfKeyDesc.KeyLocator,
cfg: &cfg,
networkMsgs: make(chan *networkMsg),
futureMsgs: newFutureMsgCache(maxFutureMessages),
quit: make(chan struct{}),
chanPolicyUpdates: make(chan *chanPolicyUpdateRequest),
prematureChannelUpdates: lru.NewCache[uint64, *cachedNetworkMsg]( //nolint: ll
maxPrematureUpdates,
),
channelMtx: multimutex.NewMutex[uint64](),
recentRejects: lru.NewCache[rejectCacheKey, *cachedReject](
maxRejectedUpdates,
),
chanUpdateRateLimiter: make(map[uint64][2]*rate.Limiter),
banman: newBanman(),
}
gossiper.vb = NewValidationBarrier(1000, gossiper.quit)
gossiper.syncMgr = newSyncManager(&SyncManagerCfg{
ChainHash: cfg.ChainHash,
ChanSeries: cfg.ChanSeries,
RotateTicker: cfg.RotateTicker,
HistoricalSyncTicker: cfg.HistoricalSyncTicker,
NumActiveSyncers: cfg.NumActiveSyncers,
NoTimestampQueries: cfg.NoTimestampQueries,
IgnoreHistoricalFilters: cfg.IgnoreHistoricalFilters,
BestHeight: gossiper.latestHeight,
PinnedSyncers: cfg.PinnedSyncers,
IsStillZombieChannel: cfg.IsStillZombieChannel,
AllotedMsgBytesPerSecond: cfg.MsgRateBytes,
AllotedMsgBytesBurst: cfg.MsgBurstBytes,
})
gossiper.reliableSender = newReliableSender(&reliableSenderCfg{
NotifyWhenOnline: cfg.NotifyWhenOnline,
NotifyWhenOffline: cfg.NotifyWhenOffline,
MessageStore: cfg.MessageStore,
IsMsgStale: gossiper.isMsgStale,
})
return gossiper
}
// EdgeWithInfo contains the information that is required to update an edge.
type EdgeWithInfo struct {
// Info describes the channel.
Info *models.ChannelEdgeInfo
// Edge describes the policy in one direction of the channel.
Edge *models.ChannelEdgePolicy
}
// PropagateChanPolicyUpdate signals the AuthenticatedGossiper to perform the
// specified edge updates. Updates are done in two stages: first, the
// AuthenticatedGossiper ensures the update has been committed by dependent
// sub-systems, then it signs and broadcasts new updates to the network. A
// mapping between outpoints and updated channel policies is returned, which is
// used to update the forwarding policies of the underlying links.
func (d *AuthenticatedGossiper) PropagateChanPolicyUpdate(
edgesToUpdate []EdgeWithInfo) error {
errChan := make(chan error, 1)
policyUpdate := &chanPolicyUpdateRequest{
edgesToUpdate: edgesToUpdate,
errChan: errChan,
}
select {
case d.chanPolicyUpdates <- policyUpdate:
err := <-errChan
return err
case <-d.quit:
return fmt.Errorf("AuthenticatedGossiper shutting down")
}
}
// Start spawns network messages handler goroutine and registers on new block
// notifications in order to properly handle the premature announcements.
func (d *AuthenticatedGossiper) Start() error {
var err error
d.started.Do(func() {
log.Info("Authenticated Gossiper starting")
err = d.start()
})
return err
}
func (d *AuthenticatedGossiper) start() error {
// First we register for new notifications of newly discovered blocks.
// We do this immediately so we'll later be able to consume any/all
// blocks which were discovered.
blockEpochs, err := d.cfg.Notifier.RegisterBlockEpochNtfn(nil)
if err != nil {
return err
}
d.blockEpochs = blockEpochs
height, err := d.cfg.Graph.CurrentBlockHeight()
if err != nil {
return err
}
d.bestHeight = height
// Start the reliable sender. In case we had any pending messages ready
// to be sent when the gossiper was last shut down, we must continue on
// our quest to deliver them to their respective peers.
if err := d.reliableSender.Start(); err != nil {
return err
}
d.syncMgr.Start()
d.banman.start()
// Start receiving blocks in its dedicated goroutine.
d.wg.Add(2)
go d.syncBlockHeight()
go d.networkHandler()
return nil
}
// syncBlockHeight syncs the best block height for the gossiper by reading
// blockEpochs.
//
// NOTE: must be run as a goroutine.
func (d *AuthenticatedGossiper) syncBlockHeight() {
defer d.wg.Done()
for {
select {
// A new block has arrived, so we can re-process the previously
// premature announcements.
case newBlock, ok := <-d.blockEpochs.Epochs:
// If the channel has been closed, then this indicates
// the daemon is shutting down, so we exit ourselves.
if !ok {
return
}
// Once a new block arrives, we update our running
// track of the height of the chain tip.
d.Lock()
blockHeight := uint32(newBlock.Height)
d.bestHeight = blockHeight
d.Unlock()
log.Debugf("New block: height=%d, hash=%s", blockHeight,
newBlock.Hash)
// Resend future messages, if any.
d.resendFutureMessages(blockHeight)
case <-d.quit:
return
}
}
}
// futureMsgCache embeds a `lru.Cache` with a message counter that's served as
// the unique ID when saving the message.
type futureMsgCache struct {
*lru.Cache[uint64, *cachedFutureMsg]
// msgID is a monotonically increased integer.
msgID atomic.Uint64
}
// nextMsgID returns a unique message ID.
func (f *futureMsgCache) nextMsgID() uint64 {
return f.msgID.Add(1)
}
// newFutureMsgCache creates a new future message cache with the underlying lru
// cache being initialized with the specified capacity.
func newFutureMsgCache(capacity uint64) *futureMsgCache {
// Create a new cache.
cache := lru.NewCache[uint64, *cachedFutureMsg](capacity)
return &futureMsgCache{
Cache: cache,
}
}
// cachedFutureMsg is a future message that's saved to the `futureMsgCache`.
type cachedFutureMsg struct {
// msg is the network message.
msg *networkMsg
// height is the block height.
height uint32
}
// Size returns the size of the message.
func (c *cachedFutureMsg) Size() (uint64, error) {
// Return a constant 1.
return 1, nil
}
// resendFutureMessages takes a block height, resends all the future messages
// found below and equal to that height and deletes those messages found in the
// gossiper's futureMsgs.
func (d *AuthenticatedGossiper) resendFutureMessages(height uint32) {
var (
// msgs are the target messages.
msgs []*networkMsg
// keys are the target messages' caching keys.
keys []uint64
)
// filterMsgs is the visitor used when iterating the future cache.
filterMsgs := func(k uint64, cmsg *cachedFutureMsg) bool {
if cmsg.height <= height {
msgs = append(msgs, cmsg.msg)
keys = append(keys, k)
}
return true
}
// Filter out the target messages.
d.futureMsgs.Range(filterMsgs)
// Return early if no messages found.
if len(msgs) == 0 {
return
}
// Remove the filtered messages.
for _, key := range keys {
d.futureMsgs.Delete(key)
}
log.Debugf("Resending %d network messages at height %d",
len(msgs), height)
for _, msg := range msgs {
select {
case d.networkMsgs <- msg:
case <-d.quit:
msg.err <- ErrGossiperShuttingDown
}
}
}
// Stop signals any active goroutines for a graceful closure.
func (d *AuthenticatedGossiper) Stop() error {
d.stopped.Do(func() {
log.Info("Authenticated gossiper shutting down...")
defer log.Debug("Authenticated gossiper shutdown complete")
d.stop()
})
return nil
}
func (d *AuthenticatedGossiper) stop() {
log.Debug("Authenticated Gossiper is stopping")
defer log.Debug("Authenticated Gossiper stopped")
// `blockEpochs` is only initialized in the start routine so we make
// sure we don't panic here in the case where the `Stop` method is
// called when the `Start` method does not complete.
if d.blockEpochs != nil {
d.blockEpochs.Cancel()
}
d.syncMgr.Stop()
d.banman.stop()
close(d.quit)
d.wg.Wait()
// We'll stop our reliable sender after all of the gossiper's goroutines
// have exited to ensure nothing can cause it to continue executing.
d.reliableSender.Stop()
}
// TODO(roasbeef): need method to get current gossip timestamp?
// * using mtx, check time rotate forward is needed?
// ProcessRemoteAnnouncement sends a new remote announcement message along with
// the peer that sent the routing message. The announcement will be processed
// then added to a queue for batched trickled announcement to all connected
// peers. Remote channel announcements should contain the announcement proof
// and be fully validated.
func (d *AuthenticatedGossiper) ProcessRemoteAnnouncement(msg lnwire.Message,
peer lnpeer.Peer) chan error {
log.Debugf("Processing remote msg %T from peer=%x", msg, peer.PubKey())
errChan := make(chan error, 1)
// For messages in the known set of channel series queries, we'll
// dispatch the message directly to the GossipSyncer, and skip the main
// processing loop.
switch m := msg.(type) {
case *lnwire.QueryShortChanIDs,
*lnwire.QueryChannelRange,
*lnwire.ReplyChannelRange,
*lnwire.ReplyShortChanIDsEnd:
syncer, ok := d.syncMgr.GossipSyncer(peer.PubKey())
if !ok {
log.Warnf("Gossip syncer for peer=%x not found",
peer.PubKey())
errChan <- ErrGossipSyncerNotFound
return errChan
}
// If we've found the message target, then we'll dispatch the
// message directly to it.
err := syncer.ProcessQueryMsg(m, peer.QuitSignal())
if err != nil {
log.Errorf("Process query msg from peer %x got %v",
peer.PubKey(), err)
}
errChan <- err
return errChan
// If a peer is updating its current update horizon, then we'll dispatch
// that directly to the proper GossipSyncer.
case *lnwire.GossipTimestampRange:
syncer, ok := d.syncMgr.GossipSyncer(peer.PubKey())
if !ok {
log.Warnf("Gossip syncer for peer=%x not found",
peer.PubKey())
errChan <- ErrGossipSyncerNotFound
return errChan
}
// If we've found the message target, then we'll dispatch the
// message directly to it.
if err := syncer.ApplyGossipFilter(m); err != nil {
log.Warnf("Unable to apply gossip filter for peer=%x: "+
"%v", peer.PubKey(), err)
errChan <- err
return errChan
}
errChan <- nil
return errChan
// To avoid inserting edges in the graph for our own channels that we
// have already closed, we ignore such channel announcements coming
// from the remote.
case *lnwire.ChannelAnnouncement1:
ownKey := d.selfKey.SerializeCompressed()
ownErr := fmt.Errorf("ignoring remote ChannelAnnouncement1 " +
"for own channel")
if bytes.Equal(m.NodeID1[:], ownKey) ||
bytes.Equal(m.NodeID2[:], ownKey) {
log.Warn(ownErr)
errChan <- ownErr
return errChan
}
}
nMsg := &networkMsg{
msg: msg,
isRemote: true,
peer: peer,
source: peer.IdentityKey(),
err: errChan,
}
select {
case d.networkMsgs <- nMsg:
// If the peer that sent us this error is quitting, then we don't need
// to send back an error and can return immediately.
case <-peer.QuitSignal():
return nil
case <-d.quit:
nMsg.err <- ErrGossiperShuttingDown
}
return nMsg.err
}
// ProcessLocalAnnouncement sends a new remote announcement message along with
// the peer that sent the routing message. The announcement will be processed
// then added to a queue for batched trickled announcement to all connected
// peers. Local channel announcements don't contain the announcement proof and
// will not be fully validated. Once the channel proofs are received, the
// entire channel announcement and update messages will be re-constructed and
// broadcast to the rest of the network.
func (d *AuthenticatedGossiper) ProcessLocalAnnouncement(msg lnwire.Message,
optionalFields ...OptionalMsgField) chan error {
optionalMsgFields := &optionalMsgFields{}
optionalMsgFields.apply(optionalFields...)
nMsg := &networkMsg{
msg: msg,
optionalMsgFields: optionalMsgFields,
isRemote: false,
source: d.selfKey,
err: make(chan error, 1),
}
select {
case d.networkMsgs <- nMsg:
case <-d.quit:
nMsg.err <- ErrGossiperShuttingDown
}
return nMsg.err
}
// channelUpdateID is a unique identifier for ChannelUpdate messages, as
// channel updates can be identified by the (ShortChannelID, ChannelFlags)
// tuple.
type channelUpdateID struct {
// channelID represents the set of data which is needed to
// retrieve all necessary data to validate the channel existence.
channelID lnwire.ShortChannelID
// Flags least-significant bit must be set to 0 if the creating node
// corresponds to the first node in the previously sent channel
// announcement and 1 otherwise.
flags lnwire.ChanUpdateChanFlags
}
// msgWithSenders is a wrapper struct around a message, and the set of peers
// that originally sent us this message. Using this struct, we can ensure that
// we don't re-send a message to the peer that sent it to us in the first
// place.
type msgWithSenders struct {
// msg is the wire message itself.
msg lnwire.Message
// isLocal is true if this was a message that originated locally. We'll
// use this to bypass our normal checks to ensure we prioritize sending
// out our own updates.
isLocal bool
// sender is the set of peers that sent us this message.
senders map[route.Vertex]struct{}
}
// mergeSyncerMap is used to merge the set of senders of a particular message
// with peers that we have an active GossipSyncer with. We do this to ensure
// that we don't broadcast messages to any peers that we have active gossip
// syncers for.
func (m *msgWithSenders) mergeSyncerMap(syncers map[route.Vertex]*GossipSyncer) {
for peerPub := range syncers {
m.senders[peerPub] = struct{}{}
}
}
// deDupedAnnouncements de-duplicates announcements that have been added to the
// batch. Internally, announcements are stored in three maps
// (one each for channel announcements, channel updates, and node
// announcements). These maps keep track of unique announcements and ensure no
// announcements are duplicated. We keep the three message types separate, such
// that we can send channel announcements first, then channel updates, and
// finally node announcements when it's time to broadcast them.
type deDupedAnnouncements struct {
// channelAnnouncements are identified by the short channel id field.
channelAnnouncements map[lnwire.ShortChannelID]msgWithSenders
// channelUpdates are identified by the channel update id field.
channelUpdates map[channelUpdateID]msgWithSenders
// nodeAnnouncements are identified by the Vertex field.
nodeAnnouncements map[route.Vertex]msgWithSenders
sync.Mutex
}
// Reset operates on deDupedAnnouncements to reset the storage of
// announcements.
func (d *deDupedAnnouncements) Reset() {
d.Lock()
defer d.Unlock()
d.reset()
}
// reset is the private version of the Reset method. We have this so we can
// call this method within method that are already holding the lock.
func (d *deDupedAnnouncements) reset() {
// Storage of each type of announcement (channel announcements, channel
// updates, node announcements) is set to an empty map where the
// appropriate key points to the corresponding lnwire.Message.
d.channelAnnouncements = make(map[lnwire.ShortChannelID]msgWithSenders)
d.channelUpdates = make(map[channelUpdateID]msgWithSenders)
d.nodeAnnouncements = make(map[route.Vertex]msgWithSenders)
}
// addMsg adds a new message to the current batch. If the message is already
// present in the current batch, then this new instance replaces the latter,
// and the set of senders is updated to reflect which node sent us this
// message.
func (d *deDupedAnnouncements) addMsg(message networkMsg) {
log.Tracef("Adding network message: %v to batch", message.msg.MsgType())
// Depending on the message type (channel announcement, channel update,
// or node announcement), the message is added to the corresponding map
// in deDupedAnnouncements. Because each identifying key can have at
// most one value, the announcements are de-duplicated, with newer ones
// replacing older ones.
switch msg := message.msg.(type) {
// Channel announcements are identified by the short channel id field.
case *lnwire.ChannelAnnouncement1:
deDupKey := msg.ShortChannelID
sender := route.NewVertex(message.source)
mws, ok := d.channelAnnouncements[deDupKey]
if !ok {
mws = msgWithSenders{
msg: msg,
isLocal: !message.isRemote,
senders: make(map[route.Vertex]struct{}),
}
mws.senders[sender] = struct{}{}
d.channelAnnouncements[deDupKey] = mws
return
}
mws.msg = msg
mws.senders[sender] = struct{}{}
d.channelAnnouncements[deDupKey] = mws
// Channel updates are identified by the (short channel id,
// channelflags) tuple.
case *lnwire.ChannelUpdate1:
sender := route.NewVertex(message.source)
deDupKey := channelUpdateID{
msg.ShortChannelID,
msg.ChannelFlags,
}
oldTimestamp := uint32(0)
mws, ok := d.channelUpdates[deDupKey]
if ok {
// If we already have seen this message, record its
// timestamp.
update, ok := mws.msg.(*lnwire.ChannelUpdate1)
if !ok {
log.Errorf("Expected *lnwire.ChannelUpdate1, "+
"got: %T", mws.msg)
return
}
oldTimestamp = update.Timestamp
}
// If we already had this message with a strictly newer
// timestamp, then we'll just discard the message we got.
if oldTimestamp > msg.Timestamp {
log.Debugf("Ignored outdated network message: "+
"peer=%v, msg=%s", message.peer, msg.MsgType())
return
}
// If the message we just got is newer than what we previously
// have seen, or this is the first time we see it, then we'll
// add it to our map of announcements.
if oldTimestamp < msg.Timestamp {
mws = msgWithSenders{
msg: msg,
isLocal: !message.isRemote,
senders: make(map[route.Vertex]struct{}),
}
// We'll mark the sender of the message in the
// senders map.
mws.senders[sender] = struct{}{}
d.channelUpdates[deDupKey] = mws
return
}
// Lastly, if we had seen this exact message from before, with
// the same timestamp, we'll add the sender to the map of
// senders, such that we can skip sending this message back in
// the next batch.
mws.msg = msg
mws.senders[sender] = struct{}{}
d.channelUpdates[deDupKey] = mws
// Node announcements are identified by the Vertex field. Use the
// NodeID to create the corresponding Vertex.
case *lnwire.NodeAnnouncement:
sender := route.NewVertex(message.source)
deDupKey := route.Vertex(msg.NodeID)
// We do the same for node announcements as we did for channel
// updates, as they also carry a timestamp.
oldTimestamp := uint32(0)
mws, ok := d.nodeAnnouncements[deDupKey]
if ok {
oldTimestamp = mws.msg.(*lnwire.NodeAnnouncement).Timestamp
}
// Discard the message if it's old.
if oldTimestamp > msg.Timestamp {
return
}
// Replace if it's newer.
if oldTimestamp < msg.Timestamp {
mws = msgWithSenders{
msg: msg,
isLocal: !message.isRemote,
senders: make(map[route.Vertex]struct{}),
}
mws.senders[sender] = struct{}{}
d.nodeAnnouncements[deDupKey] = mws
return
}
// Add to senders map if it's the same as we had.
mws.msg = msg
mws.senders[sender] = struct{}{}
d.nodeAnnouncements[deDupKey] = mws
}
}
// AddMsgs is a helper method to add multiple messages to the announcement
// batch.
func (d *deDupedAnnouncements) AddMsgs(msgs ...networkMsg) {
d.Lock()
defer d.Unlock()
for _, msg := range msgs {
d.addMsg(msg)
}
}
// msgsToBroadcast is returned by Emit() and partitions the messages we'd like
// to broadcast next into messages that are locally sourced and those that are
// sourced remotely.
type msgsToBroadcast struct {
// localMsgs is the set of messages we created locally.
localMsgs []msgWithSenders
// remoteMsgs is the set of messages that we received from a remote
// party.
remoteMsgs []msgWithSenders
}
// addMsg adds a new message to the appropriate sub-slice.
func (m *msgsToBroadcast) addMsg(msg msgWithSenders) {
if msg.isLocal {
m.localMsgs = append(m.localMsgs, msg)
} else {
m.remoteMsgs = append(m.remoteMsgs, msg)
}
}
// isEmpty returns true if the batch is empty.
func (m *msgsToBroadcast) isEmpty() bool {
return len(m.localMsgs) == 0 && len(m.remoteMsgs) == 0
}
// length returns the length of the combined message set.
func (m *msgsToBroadcast) length() int {
return len(m.localMsgs) + len(m.remoteMsgs)
}
// Emit returns the set of de-duplicated announcements to be sent out during
// the next announcement epoch, in the order of channel announcements, channel
// updates, and node announcements. Each message emitted, contains the set of
// peers that sent us the message. This way, we can ensure that we don't waste
// bandwidth by re-sending a message to the peer that sent it to us in the
// first place. Additionally, the set of stored messages are reset.
func (d *deDupedAnnouncements) Emit() msgsToBroadcast {
d.Lock()
defer d.Unlock()
// Get the total number of announcements.
numAnnouncements := len(d.channelAnnouncements) + len(d.channelUpdates) +
len(d.nodeAnnouncements)
// Create an empty array of lnwire.Messages with a length equal to
// the total number of announcements.
msgs := msgsToBroadcast{
localMsgs: make([]msgWithSenders, 0, numAnnouncements),
remoteMsgs: make([]msgWithSenders, 0, numAnnouncements),
}
// Add the channel announcements to the array first.
for _, message := range d.channelAnnouncements {
msgs.addMsg(message)
}
// Then add the channel updates.
for _, message := range d.channelUpdates {
msgs.addMsg(message)
}
// Finally add the node announcements.
for _, message := range d.nodeAnnouncements {
msgs.addMsg(message)
}
d.reset()
// Return the array of lnwire.messages.
return msgs
}
// calculateSubBatchSize is a helper function that calculates the size to break
// down the batchSize into.
func calculateSubBatchSize(totalDelay, subBatchDelay time.Duration,
minimumBatchSize, batchSize int) int {
if subBatchDelay > totalDelay {
return batchSize
}
subBatchSize := (batchSize*int(subBatchDelay) +
int(totalDelay) - 1) / int(totalDelay)
if subBatchSize < minimumBatchSize {
return minimumBatchSize
}
return subBatchSize
}
// batchSizeCalculator maps to the function `calculateSubBatchSize`. We create
// this variable so the function can be mocked in our test.
var batchSizeCalculator = calculateSubBatchSize
// splitAnnouncementBatches takes an exiting list of announcements and
// decomposes it into sub batches controlled by the `subBatchSize`.
func (d *AuthenticatedGossiper) splitAnnouncementBatches(
announcementBatch []msgWithSenders) [][]msgWithSenders {
subBatchSize := batchSizeCalculator(
d.cfg.TrickleDelay, d.cfg.SubBatchDelay,
d.cfg.MinimumBatchSize, len(announcementBatch),
)
var splitAnnouncementBatch [][]msgWithSenders
for subBatchSize < len(announcementBatch) {
// For slicing with minimal allocation
// https://github.com/golang/go/wiki/SliceTricks
announcementBatch, splitAnnouncementBatch =
announcementBatch[subBatchSize:],
append(splitAnnouncementBatch,
announcementBatch[0:subBatchSize:subBatchSize])
}
splitAnnouncementBatch = append(
splitAnnouncementBatch, announcementBatch,
)
return splitAnnouncementBatch
}
// splitAndSendAnnBatch takes a batch of messages, computes the proper batch
// split size, and then sends out all items to the set of target peers. Locally
// generated announcements are always sent before remotely generated
// announcements.
func (d *AuthenticatedGossiper) splitAndSendAnnBatch(
annBatch msgsToBroadcast) {
// delayNextBatch is a helper closure that blocks for `SubBatchDelay`
// duration to delay the sending of next announcement batch.
delayNextBatch := func() {
select {
case <-time.After(d.cfg.SubBatchDelay):
case <-d.quit:
return
}
}
// Fetch the local and remote announcements.
localBatches := d.splitAnnouncementBatches(annBatch.localMsgs)
remoteBatches := d.splitAnnouncementBatches(annBatch.remoteMsgs)
d.wg.Add(1)
go func() {
defer d.wg.Done()
log.Debugf("Broadcasting %v new local announcements in %d "+
"sub batches", len(annBatch.localMsgs),
len(localBatches))
// Send out the local announcements first.
for _, annBatch := range localBatches {
d.sendLocalBatch(annBatch)
delayNextBatch()
}
log.Debugf("Broadcasting %v new remote announcements in %d "+
"sub batches", len(annBatch.remoteMsgs),
len(remoteBatches))
// Now send the remote announcements.
for _, annBatch := range remoteBatches {
d.sendRemoteBatch(annBatch)
delayNextBatch()
}
}()
}
// sendLocalBatch broadcasts a list of locally generated announcements to our
// peers. For local announcements, we skip the filter and dedup logic and just
// send the announcements out to all our coonnected peers.
func (d *AuthenticatedGossiper) sendLocalBatch(annBatch []msgWithSenders) {
msgsToSend := lnutils.Map(
annBatch, func(m msgWithSenders) lnwire.Message {
return m.msg
},
)
err := d.cfg.Broadcast(nil, msgsToSend...)
if err != nil {
log.Errorf("Unable to send local batch announcements: %v", err)
}
}
// sendRemoteBatch broadcasts a list of remotely generated announcements to our
// peers.
func (d *AuthenticatedGossiper) sendRemoteBatch(annBatch []msgWithSenders) {
syncerPeers := d.syncMgr.GossipSyncers()
// We'll first attempt to filter out this new message for all peers
// that have active gossip syncers active.
for pub, syncer := range syncerPeers {
log.Tracef("Sending messages batch to GossipSyncer(%s)", pub)
syncer.FilterGossipMsgs(annBatch...)
}
for _, msgChunk := range annBatch {
msgChunk := msgChunk
// With the syncers taken care of, we'll merge the sender map
// with the set of syncers, so we don't send out duplicate
// messages.
msgChunk.mergeSyncerMap(syncerPeers)
err := d.cfg.Broadcast(msgChunk.senders, msgChunk.msg)
if err != nil {
log.Errorf("Unable to send batch "+
"announcements: %v", err)
continue
}
}
}
// networkHandler is the primary goroutine that drives this service. The roles
// of this goroutine includes answering queries related to the state of the
// network, syncing up newly connected peers, and also periodically
// broadcasting our latest topology state to all connected peers.
//
// NOTE: This MUST be run as a goroutine.
func (d *AuthenticatedGossiper) networkHandler() {
defer d.wg.Done()
// Initialize empty deDupedAnnouncements to store announcement batch.
announcements := deDupedAnnouncements{}
announcements.Reset()
d.cfg.RetransmitTicker.Resume()
defer d.cfg.RetransmitTicker.Stop()
trickleTimer := time.NewTicker(d.cfg.TrickleDelay)
defer trickleTimer.Stop()
// To start, we'll first check to see if there are any stale channel or
// node announcements that we need to re-transmit.
if err := d.retransmitStaleAnns(time.Now()); err != nil {
log.Errorf("Unable to rebroadcast stale announcements: %v", err)
}
for {
select {
// A new policy update has arrived. We'll commit it to the
// sub-systems below us, then craft, sign, and broadcast a new
// ChannelUpdate for the set of affected clients.
case policyUpdate := <-d.chanPolicyUpdates:
log.Tracef("Received channel %d policy update requests",
len(policyUpdate.edgesToUpdate))
// First, we'll now create new fully signed updates for
// the affected channels and also update the underlying
// graph with the new state.
newChanUpdates, err := d.processChanPolicyUpdate(
policyUpdate.edgesToUpdate,
)
policyUpdate.errChan <- err
if err != nil {
log.Errorf("Unable to craft policy updates: %v",
err)
continue
}
// Finally, with the updates committed, we'll now add
// them to the announcement batch to be flushed at the
// start of the next epoch.
announcements.AddMsgs(newChanUpdates...)
case announcement := <-d.networkMsgs:
log.Tracef("Received network message: "+
"peer=%v, msg=%s, is_remote=%v",
announcement.peer, announcement.msg.MsgType(),
announcement.isRemote)
switch announcement.msg.(type) {
// Channel announcement signatures are amongst the only
// messages that we'll process serially.
case *lnwire.AnnounceSignatures1:
emittedAnnouncements, _ := d.processNetworkAnnouncement(
announcement,
)
log.Debugf("Processed network message %s, "+
"returned len(announcements)=%v",
announcement.msg.MsgType(),
len(emittedAnnouncements))
if emittedAnnouncements != nil {
announcements.AddMsgs(
emittedAnnouncements...,
)
}
continue
}
// If this message was recently rejected, then we won't
// attempt to re-process it.
if announcement.isRemote && d.isRecentlyRejectedMsg(
announcement.msg,
sourceToPub(announcement.source),
) {
announcement.err <- fmt.Errorf("recently " +
"rejected")
continue
}
// We'll set up any dependent, and wait until a free
// slot for this job opens up, this allow us to not
// have thousands of goroutines active.
annJobID, err := d.vb.InitJobDependencies(
announcement.msg,
)
if err != nil {
announcement.err <- err
continue
}
d.wg.Add(1)
go d.handleNetworkMessages(
announcement, &announcements, annJobID,
)
// The trickle timer has ticked, which indicates we should
// flush to the network the pending batch of new announcements
// we've received since the last trickle tick.
case <-trickleTimer.C:
// Emit the current batch of announcements from
// deDupedAnnouncements.
announcementBatch := announcements.Emit()
// If the current announcements batch is nil, then we
// have no further work here.
if announcementBatch.isEmpty() {
continue
}
// At this point, we have the set of local and remote
// announcements we want to send out. We'll do the
// batching as normal for both, but for local
// announcements, we'll blast them out w/o regard for
// our peer's policies so we ensure they propagate
// properly.
d.splitAndSendAnnBatch(announcementBatch)
// The retransmission timer has ticked which indicates that we
// should check if we need to prune or re-broadcast any of our
// personal channels or node announcement. This addresses the
// case of "zombie" channels and channel advertisements that
// have been dropped, or not properly propagated through the
// network.
case tick := <-d.cfg.RetransmitTicker.Ticks():
if err := d.retransmitStaleAnns(tick); err != nil {
log.Errorf("unable to rebroadcast stale "+
"announcements: %v", err)
}
// The gossiper has been signalled to exit, to we exit our
// main loop so the wait group can be decremented.
case <-d.quit:
return
}
}
}
// handleNetworkMessages is responsible for waiting for dependencies for a
// given network message and processing the message. Once processed, it will
// signal its dependants and add the new announcements to the announce batch.
//
// NOTE: must be run as a goroutine.
func (d *AuthenticatedGossiper) handleNetworkMessages(nMsg *networkMsg,
deDuped *deDupedAnnouncements, jobID JobID) {
defer d.wg.Done()
defer d.vb.CompleteJob()
// We should only broadcast this message forward if it originated from
// us or it wasn't received as part of our initial historical sync.
shouldBroadcast := !nMsg.isRemote || d.syncMgr.IsGraphSynced()
// If this message has an existing dependency, then we'll wait until
// that has been fully validated before we proceed.
err := d.vb.WaitForParents(jobID, nMsg.msg)
if err != nil {
log.Debugf("Validating network message %s got err: %v",
nMsg.msg.MsgType(), err)
if errors.Is(err, ErrVBarrierShuttingDown) {
log.Warnf("unexpected error during validation "+
"barrier shutdown: %v", err)
}
nMsg.err <- err
return
}
// Process the network announcement to determine if this is either a
// new announcement from our PoV or an edges to a prior vertex/edge we
// previously proceeded.
newAnns, allow := d.processNetworkAnnouncement(nMsg)
log.Tracef("Processed network message %s, returned "+
"len(announcements)=%v, allowDependents=%v",
nMsg.msg.MsgType(), len(newAnns), allow)
// If this message had any dependencies, then we can now signal them to
// continue.
err = d.vb.SignalDependents(nMsg.msg, jobID)
if err != nil {
// Something is wrong if SignalDependents returns an error.
log.Errorf("SignalDependents returned error for msg=%v with "+
"JobID=%v", spew.Sdump(nMsg.msg), jobID)
nMsg.err <- err
return
}
// If the announcement was accepted, then add the emitted announcements
// to our announce batch to be broadcast once the trickle timer ticks
// gain.
if newAnns != nil && shouldBroadcast {
// TODO(roasbeef): exclude peer that sent.
deDuped.AddMsgs(newAnns...)
} else if newAnns != nil {
log.Trace("Skipping broadcast of announcements received " +
"during initial graph sync")
}
}
// TODO(roasbeef): d/c peers that send updates not on our chain
// InitSyncState is called by outside sub-systems when a connection is
// established to a new peer that understands how to perform channel range
// queries. We'll allocate a new gossip syncer for it, and start any goroutines
// needed to handle new queries.
func (d *AuthenticatedGossiper) InitSyncState(syncPeer lnpeer.Peer) {
d.syncMgr.InitSyncState(syncPeer)
}
// PruneSyncState is called by outside sub-systems once a peer that we were
// previously connected to has been disconnected. In this case we can stop the
// existing GossipSyncer assigned to the peer and free up resources.
func (d *AuthenticatedGossiper) PruneSyncState(peer route.Vertex) {
d.syncMgr.PruneSyncState(peer)
}
// isRecentlyRejectedMsg returns true if we recently rejected a message, and
// false otherwise, This avoids expensive reprocessing of the message.
func (d *AuthenticatedGossiper) isRecentlyRejectedMsg(msg lnwire.Message,
peerPub [33]byte) bool {
var scid uint64
switch m := msg.(type) {
case *lnwire.ChannelUpdate1:
scid = m.ShortChannelID.ToUint64()
case *lnwire.ChannelAnnouncement1:
scid = m.ShortChannelID.ToUint64()
default:
return false
}
_, err := d.recentRejects.Get(newRejectCacheKey(scid, peerPub))
return err != cache.ErrElementNotFound
}
// retransmitStaleAnns examines all outgoing channels that the source node is
// known to maintain to check to see if any of them are "stale". A channel is
// stale iff, the last timestamp of its rebroadcast is older than the
// RebroadcastInterval. We also check if a refreshed node announcement should
// be resent.
func (d *AuthenticatedGossiper) retransmitStaleAnns(now time.Time) error {
// Iterate over all of our channels and check if any of them fall
// within the prune interval or re-broadcast interval.
type updateTuple struct {
info *models.ChannelEdgeInfo
edge *models.ChannelEdgePolicy
}
var (
havePublicChannels bool
edgesToUpdate []updateTuple
)
err := d.cfg.Graph.ForAllOutgoingChannels(func(
info *models.ChannelEdgeInfo,
edge *models.ChannelEdgePolicy) error {
// If there's no auth proof attached to this edge, it means
// that it is a private channel not meant to be announced to
// the greater network, so avoid sending channel updates for
// this channel to not leak its
// existence.
if info.AuthProof == nil {
log.Debugf("Skipping retransmission of channel "+
"without AuthProof: %v", info.ChannelID)
return nil
}
// We make a note that we have at least one public channel. We
// use this to determine whether we should send a node
// announcement below.
havePublicChannels = true
// If this edge has a ChannelUpdate that was created before the
// introduction of the MaxHTLC field, then we'll update this
// edge to propagate this information in the network.
if !edge.MessageFlags.HasMaxHtlc() {
// We'll make sure we support the new max_htlc field if
// not already present.
edge.MessageFlags |= lnwire.ChanUpdateRequiredMaxHtlc
edge.MaxHTLC = lnwire.NewMSatFromSatoshis(info.Capacity)
edgesToUpdate = append(edgesToUpdate, updateTuple{
info: info,
edge: edge,
})
return nil
}
timeElapsed := now.Sub(edge.LastUpdate)
// If it's been longer than RebroadcastInterval since we've
// re-broadcasted the channel, add the channel to the set of
// edges we need to update.
if timeElapsed >= d.cfg.RebroadcastInterval {
edgesToUpdate = append(edgesToUpdate, updateTuple{
info: info,
edge: edge,
})
}
return nil
})
if err != nil && !errors.Is(err, graphdb.ErrGraphNoEdgesFound) {
return fmt.Errorf("unable to retrieve outgoing channels: %w",
err)
}
var signedUpdates []lnwire.Message
for _, chanToUpdate := range edgesToUpdate {
// Re-sign and update the channel on disk and retrieve our
// ChannelUpdate to broadcast.
chanAnn, chanUpdate, err := d.updateChannel(
chanToUpdate.info, chanToUpdate.edge,
)
if err != nil {
return fmt.Errorf("unable to update channel: %w", err)
}
// If we have a valid announcement to transmit, then we'll send
// that along with the update.
if chanAnn != nil {
signedUpdates = append(signedUpdates, chanAnn)
}
signedUpdates = append(signedUpdates, chanUpdate)
}
// If we don't have any public channels, we return as we don't want to
// broadcast anything that would reveal our existence.
if !havePublicChannels {
return nil
}
// We'll also check that our NodeAnnouncement is not too old.
currentNodeAnn := d.cfg.FetchSelfAnnouncement()
timestamp := time.Unix(int64(currentNodeAnn.Timestamp), 0)
timeElapsed := now.Sub(timestamp)
// If it's been a full day since we've re-broadcasted the
// node announcement, refresh it and resend it.
nodeAnnStr := ""
if timeElapsed >= d.cfg.RebroadcastInterval {
newNodeAnn, err := d.cfg.UpdateSelfAnnouncement()
if err != nil {
return fmt.Errorf("unable to get refreshed node "+
"announcement: %v", err)
}
signedUpdates = append(signedUpdates, &newNodeAnn)
nodeAnnStr = " and our refreshed node announcement"
// Before broadcasting the refreshed node announcement, add it
// to our own graph.
if err := d.addNode(&newNodeAnn); err != nil {
log.Errorf("Unable to add refreshed node announcement "+
"to graph: %v", err)
}
}
// If we don't have any updates to re-broadcast, then we'll exit
// early.
if len(signedUpdates) == 0 {
return nil
}
log.Infof("Retransmitting %v outgoing channels%v",
len(edgesToUpdate), nodeAnnStr)
// With all the wire announcements properly crafted, we'll broadcast
// our known outgoing channels to all our immediate peers.
if err := d.cfg.Broadcast(nil, signedUpdates...); err != nil {
return fmt.Errorf("unable to re-broadcast channels: %w", err)
}
return nil
}
// processChanPolicyUpdate generates a new set of channel updates for the
// provided list of edges and updates the backing ChannelGraphSource.
func (d *AuthenticatedGossiper) processChanPolicyUpdate(
edgesToUpdate []EdgeWithInfo) ([]networkMsg, error) {
var chanUpdates []networkMsg
for _, edgeInfo := range edgesToUpdate {
// Now that we've collected all the channels we need to update,
// we'll re-sign and update the backing ChannelGraphSource, and
// retrieve our ChannelUpdate to broadcast.
_, chanUpdate, err := d.updateChannel(
edgeInfo.Info, edgeInfo.Edge,
)
if err != nil {
return nil, err
}
// We'll avoid broadcasting any updates for private channels to
// avoid directly giving away their existence. Instead, we'll
// send the update directly to the remote party.
if edgeInfo.Info.AuthProof == nil {
// If AuthProof is nil and an alias was found for this
// ChannelID (meaning the option-scid-alias feature was
// negotiated), we'll replace the ShortChannelID in the
// update with the peer's alias. We do this after
// updateChannel so that the alias isn't persisted to
// the database.
chanID := lnwire.NewChanIDFromOutPoint(
edgeInfo.Info.ChannelPoint,
)
var defaultAlias lnwire.ShortChannelID
foundAlias, _ := d.cfg.GetAlias(chanID)
if foundAlias != defaultAlias {
chanUpdate.ShortChannelID = foundAlias
sig, err := d.cfg.SignAliasUpdate(chanUpdate)
if err != nil {
log.Errorf("Unable to sign alias "+
"update: %v", err)
continue
}
lnSig, err := lnwire.NewSigFromSignature(sig)
if err != nil {
log.Errorf("Unable to create sig: %v",
err)
continue
}
chanUpdate.Signature = lnSig
}
remotePubKey := remotePubFromChanInfo(
edgeInfo.Info, chanUpdate.ChannelFlags,
)
err := d.reliableSender.sendMessage(
chanUpdate, remotePubKey,
)
if err != nil {
log.Errorf("Unable to reliably send %v for "+
"channel=%v to peer=%x: %v",
chanUpdate.MsgType(),
chanUpdate.ShortChannelID,
remotePubKey, err)
}
continue
}
// We set ourselves as the source of this message to indicate
// that we shouldn't skip any peers when sending this message.
chanUpdates = append(chanUpdates, networkMsg{
source: d.selfKey,
isRemote: false,
msg: chanUpdate,
})
}
return chanUpdates, nil
}
// remotePubFromChanInfo returns the public key of the remote peer given a
// ChannelEdgeInfo that describe a channel we have with them.
func remotePubFromChanInfo(chanInfo *models.ChannelEdgeInfo,
chanFlags lnwire.ChanUpdateChanFlags) [33]byte {
var remotePubKey [33]byte
switch {
case chanFlags&lnwire.ChanUpdateDirection == 0:
remotePubKey = chanInfo.NodeKey2Bytes
case chanFlags&lnwire.ChanUpdateDirection == 1:
remotePubKey = chanInfo.NodeKey1Bytes
}
return remotePubKey
}
// processRejectedEdge examines a rejected edge to see if we can extract any
// new announcements from it. An edge will get rejected if we already added
// the same edge without AuthProof to the graph. If the received announcement
// contains a proof, we can add this proof to our edge. We can end up in this
// situation in the case where we create a channel, but for some reason fail
// to receive the remote peer's proof, while the remote peer is able to fully
// assemble the proof and craft the ChannelAnnouncement.
func (d *AuthenticatedGossiper) processRejectedEdge(
chanAnnMsg *lnwire.ChannelAnnouncement1,
proof *models.ChannelAuthProof) ([]networkMsg, error) {
// First, we'll fetch the state of the channel as we know if from the
// database.
chanInfo, e1, e2, err := d.cfg.Graph.GetChannelByID(
chanAnnMsg.ShortChannelID,
)
if err != nil {
return nil, err
}
// The edge is in the graph, and has a proof attached, then we'll just
// reject it as normal.
if chanInfo.AuthProof != nil {
return nil, nil
}
// Otherwise, this means that the edge is within the graph, but it
// doesn't yet have a proper proof attached. If we did not receive
// the proof such that we now can add it, there's nothing more we
// can do.
if proof == nil {
return nil, nil
}
// We'll then create then validate the new fully assembled
// announcement.
chanAnn, e1Ann, e2Ann, err := netann.CreateChanAnnouncement(
proof, chanInfo, e1, e2,
)
if err != nil {
return nil, err
}
err = netann.ValidateChannelAnn(chanAnn, d.fetchPKScript)
if err != nil {
err := fmt.Errorf("assembled channel announcement proof "+
"for shortChanID=%v isn't valid: %v",
chanAnnMsg.ShortChannelID, err)
log.Error(err)
return nil, err
}
// If everything checks out, then we'll add the fully assembled proof
// to the database.
err = d.cfg.Graph.AddProof(chanAnnMsg.ShortChannelID, proof)
if err != nil {
err := fmt.Errorf("unable add proof to shortChanID=%v: %w",
chanAnnMsg.ShortChannelID, err)
log.Error(err)
return nil, err
}
// As we now have a complete channel announcement for this channel,
// we'll construct the announcement so they can be broadcast out to all
// our peers.
announcements := make([]networkMsg, 0, 3)
announcements = append(announcements, networkMsg{
source: d.selfKey,
msg: chanAnn,
})
if e1Ann != nil {
announcements = append(announcements, networkMsg{
source: d.selfKey,
msg: e1Ann,
})
}
if e2Ann != nil {
announcements = append(announcements, networkMsg{
source: d.selfKey,
msg: e2Ann,
})
}
return announcements, nil
}
// fetchPKScript fetches the output script for the given SCID.
func (d *AuthenticatedGossiper) fetchPKScript(chanID *lnwire.ShortChannelID) (
[]byte, error) {
return lnwallet.FetchPKScriptWithQuit(d.cfg.ChainIO, chanID, d.quit)
}
// addNode processes the given node announcement, and adds it to our channel
// graph.
func (d *AuthenticatedGossiper) addNode(msg *lnwire.NodeAnnouncement,
op ...batch.SchedulerOption) error {
if err := netann.ValidateNodeAnn(msg); err != nil {
return fmt.Errorf("unable to validate node announcement: %w",
err)
}
return d.cfg.Graph.AddNode(models.NodeFromWireAnnouncement(msg), op...)
}
// isPremature decides whether a given network message has a block height+delta
// value specified in the future. If so, the message will be added to the
// future message map and be processed when the block height as reached.
//
// NOTE: must be used inside a lock.
func (d *AuthenticatedGossiper) isPremature(chanID lnwire.ShortChannelID,
delta uint32, msg *networkMsg) bool {
// The channel is already confirmed at chanID.BlockHeight so we minus
// one block. For instance, if the required confirmation for this
// channel announcement is 6, we then only need to wait for 5 more
// blocks once the funding tx is confirmed.
if delta > 0 {
delta--
}
msgHeight := chanID.BlockHeight + delta
// The message height is smaller or equal to our best known height,
// thus the message is mature.
if msgHeight <= d.bestHeight {
return false
}
// Add the premature message to our future messages which will be
// resent once the block height has reached.
//
// Copy the networkMsgs since the old message's err chan will be
// consumed.
copied := &networkMsg{
peer: msg.peer,
source: msg.source,
msg: msg.msg,
optionalMsgFields: msg.optionalMsgFields,
isRemote: msg.isRemote,
err: make(chan error, 1),
}
// Create the cached message.
cachedMsg := &cachedFutureMsg{
msg: copied,
height: msgHeight,
}
// Increment the msg ID and add it to the cache.
nextMsgID := d.futureMsgs.nextMsgID()
_, err := d.futureMsgs.Put(nextMsgID, cachedMsg)
if err != nil {
log.Errorf("Adding future message got error: %v", err)
}
log.Debugf("Network message: %v added to future messages for "+
"msgHeight=%d, bestHeight=%d", msg.msg.MsgType(),
msgHeight, d.bestHeight)
return true
}
// processNetworkAnnouncement processes a new network relate authenticated
// channel or node announcement or announcements proofs. If the announcement
// didn't affect the internal state due to either being out of date, invalid,
// or redundant, then nil is returned. Otherwise, the set of announcements will
// be returned which should be broadcasted to the rest of the network. The
// boolean returned indicates whether any dependents of the announcement should
// attempt to be processed as well.
func (d *AuthenticatedGossiper) processNetworkAnnouncement(
nMsg *networkMsg) ([]networkMsg, bool) {
// If this is a remote update, we set the scheduler option to lazily
// add it to the graph.
var schedulerOp []batch.SchedulerOption
if nMsg.isRemote {
schedulerOp = append(schedulerOp, batch.LazyAdd())
}
switch msg := nMsg.msg.(type) {
// A new node announcement has arrived which either presents new
// information about a node in one of the channels we know about, or a
// updating previously advertised information.
case *lnwire.NodeAnnouncement:
return d.handleNodeAnnouncement(nMsg, msg, schedulerOp)
// A new channel announcement has arrived, this indicates the
// *creation* of a new channel within the network. This only advertises
// the existence of a channel and not yet the routing policies in
// either direction of the channel.
case *lnwire.ChannelAnnouncement1:
return d.handleChanAnnouncement(nMsg, msg, schedulerOp...)
// A new authenticated channel edge update has arrived. This indicates
// that the directional information for an already known channel has
// been updated.
case *lnwire.ChannelUpdate1:
return d.handleChanUpdate(nMsg, msg, schedulerOp)
// A new signature announcement has been received. This indicates
// willingness of nodes involved in the funding of a channel to
// announce this new channel to the rest of the world.
case *lnwire.AnnounceSignatures1:
return d.handleAnnSig(nMsg, msg)
default:
err := errors.New("wrong type of the announcement")
nMsg.err <- err
return nil, false
}
}
// processZombieUpdate determines whether the provided channel update should
// resurrect a given zombie edge.
//
// NOTE: only the NodeKey1Bytes and NodeKey2Bytes members of the ChannelEdgeInfo
// should be inspected.
func (d *AuthenticatedGossiper) processZombieUpdate(
chanInfo *models.ChannelEdgeInfo, scid lnwire.ShortChannelID,
msg *lnwire.ChannelUpdate1) error {
// The least-significant bit in the flag on the channel update tells us
// which edge is being updated.
isNode1 := msg.ChannelFlags&lnwire.ChanUpdateDirection == 0
// Since we've deemed the update as not stale above, before marking it
// live, we'll make sure it has been signed by the correct party. If we
// have both pubkeys, either party can resurrect the channel. If we've
// already marked this with the stricter, single-sided resurrection we
// will only have the pubkey of the node with the oldest timestamp.
var pubKey *btcec.PublicKey
switch {
case isNode1 && chanInfo.NodeKey1Bytes != emptyPubkey:
pubKey, _ = chanInfo.NodeKey1()
case !isNode1 && chanInfo.NodeKey2Bytes != emptyPubkey:
pubKey, _ = chanInfo.NodeKey2()
}
if pubKey == nil {
return fmt.Errorf("incorrect pubkey to resurrect zombie "+
"with chan_id=%v", msg.ShortChannelID)
}
err := netann.VerifyChannelUpdateSignature(msg, pubKey)
if err != nil {
return fmt.Errorf("unable to verify channel "+
"update signature: %v", err)
}
// With the signature valid, we'll proceed to mark the
// edge as live and wait for the channel announcement to
// come through again.
err = d.cfg.Graph.MarkEdgeLive(scid)
switch {
case errors.Is(err, graphdb.ErrZombieEdgeNotFound):
log.Errorf("edge with chan_id=%v was not found in the "+
"zombie index: %v", err)
return nil
case err != nil:
return fmt.Errorf("unable to remove edge with "+
"chan_id=%v from zombie index: %v",
msg.ShortChannelID, err)
default:
}
log.Debugf("Removed edge with chan_id=%v from zombie "+
"index", msg.ShortChannelID)
return nil
}
// fetchNodeAnn fetches the latest signed node announcement from our point of
// view for the node with the given public key.
func (d *AuthenticatedGossiper) fetchNodeAnn(
pubKey [33]byte) (*lnwire.NodeAnnouncement, error) {
node, err := d.cfg.Graph.FetchLightningNode(pubKey)
if err != nil {
return nil, err
}
return node.NodeAnnouncement(true)
}
// isMsgStale determines whether a message retrieved from the backing
// MessageStore is seen as stale by the current graph.
func (d *AuthenticatedGossiper) isMsgStale(msg lnwire.Message) bool {
switch msg := msg.(type) {
case *lnwire.AnnounceSignatures1:
chanInfo, _, _, err := d.cfg.Graph.GetChannelByID(
msg.ShortChannelID,
)
// If the channel cannot be found, it is most likely a leftover
// message for a channel that was closed, so we can consider it
// stale.
if errors.Is(err, graphdb.ErrEdgeNotFound) {
return true
}
if err != nil {
log.Debugf("Unable to retrieve channel=%v from graph: "+
"%v", chanInfo.ChannelID, err)
return false
}
// If the proof exists in the graph, then we have successfully
// received the remote proof and assembled the full proof, so we
// can safely delete the local proof from the database.
return chanInfo.AuthProof != nil
case *lnwire.ChannelUpdate1:
_, p1, p2, err := d.cfg.Graph.GetChannelByID(msg.ShortChannelID)
// If the channel cannot be found, it is most likely a leftover
// message for a channel that was closed, so we can consider it
// stale.
if errors.Is(err, graphdb.ErrEdgeNotFound) {
return true
}
if err != nil {
log.Debugf("Unable to retrieve channel=%v from graph: "+
"%v", msg.ShortChannelID, err)
return false
}
// Otherwise, we'll retrieve the correct policy that we
// currently have stored within our graph to check if this
// message is stale by comparing its timestamp.
var p *models.ChannelEdgePolicy
if msg.ChannelFlags&lnwire.ChanUpdateDirection == 0 {
p = p1
} else {
p = p2
}
// If the policy is still unknown, then we can consider this
// policy fresh.
if p == nil {
return false
}
timestamp := time.Unix(int64(msg.Timestamp), 0)
return p.LastUpdate.After(timestamp)
default:
// We'll make sure to not mark any unsupported messages as stale
// to ensure they are not removed.
return false
}
}
// updateChannel creates a new fully signed update for the channel, and updates
// the underlying graph with the new state.
func (d *AuthenticatedGossiper) updateChannel(info *models.ChannelEdgeInfo,
edge *models.ChannelEdgePolicy) (*lnwire.ChannelAnnouncement1,
*lnwire.ChannelUpdate1, error) {
// Parse the unsigned edge into a channel update.
chanUpdate := netann.UnsignedChannelUpdateFromEdge(info, edge)
// We'll generate a new signature over a digest of the channel
// announcement itself and update the timestamp to ensure it propagate.
err := netann.SignChannelUpdate(
d.cfg.AnnSigner, d.selfKeyLoc, chanUpdate,
netann.ChanUpdSetTimestamp,
)
if err != nil {
return nil, nil, err
}
// Next, we'll set the new signature in place, and update the reference
// in the backing slice.
edge.LastUpdate = time.Unix(int64(chanUpdate.Timestamp), 0)
edge.SigBytes = chanUpdate.Signature.ToSignatureBytes()
// To ensure that our signature is valid, we'll verify it ourself
// before committing it to the slice returned.
err = netann.ValidateChannelUpdateAnn(
d.selfKey, info.Capacity, chanUpdate,
)
if err != nil {
return nil, nil, fmt.Errorf("generated invalid channel "+
"update sig: %v", err)
}
// Finally, we'll write the new edge policy to disk.
if err := d.cfg.Graph.UpdateEdge(edge); err != nil {
return nil, nil, err
}
// We'll also create the original channel announcement so the two can
// be broadcast along side each other (if necessary), but only if we
// have a full channel announcement for this channel.
var chanAnn *lnwire.ChannelAnnouncement1
if info.AuthProof != nil {
chanID := lnwire.NewShortChanIDFromInt(info.ChannelID)
chanAnn = &lnwire.ChannelAnnouncement1{
ShortChannelID: chanID,
NodeID1: info.NodeKey1Bytes,
NodeID2: info.NodeKey2Bytes,
ChainHash: info.ChainHash,
BitcoinKey1: info.BitcoinKey1Bytes,
Features: lnwire.NewRawFeatureVector(),
BitcoinKey2: info.BitcoinKey2Bytes,
ExtraOpaqueData: info.ExtraOpaqueData,
}
chanAnn.NodeSig1, err = lnwire.NewSigFromECDSARawSignature(
info.AuthProof.NodeSig1Bytes,
)
if err != nil {
return nil, nil, err
}
chanAnn.NodeSig2, err = lnwire.NewSigFromECDSARawSignature(
info.AuthProof.NodeSig2Bytes,
)
if err != nil {
return nil, nil, err
}
chanAnn.BitcoinSig1, err = lnwire.NewSigFromECDSARawSignature(
info.AuthProof.BitcoinSig1Bytes,
)
if err != nil {
return nil, nil, err
}
chanAnn.BitcoinSig2, err = lnwire.NewSigFromECDSARawSignature(
info.AuthProof.BitcoinSig2Bytes,
)
if err != nil {
return nil, nil, err
}
}
return chanAnn, chanUpdate, err
}
// SyncManager returns the gossiper's SyncManager instance.
func (d *AuthenticatedGossiper) SyncManager() *SyncManager {
return d.syncMgr
}
// IsKeepAliveUpdate determines whether this channel update is considered a
// keep-alive update based on the previous channel update processed for the same
// direction.
func IsKeepAliveUpdate(update *lnwire.ChannelUpdate1,
prev *models.ChannelEdgePolicy) bool {
// Both updates should be from the same direction.
if update.ChannelFlags&lnwire.ChanUpdateDirection !=
prev.ChannelFlags&lnwire.ChanUpdateDirection {
return false
}
// The timestamp should always increase for a keep-alive update.
timestamp := time.Unix(int64(update.Timestamp), 0)
if !timestamp.After(prev.LastUpdate) {
return false
}
// None of the remaining fields should change for a keep-alive update.
if update.ChannelFlags.IsDisabled() != prev.ChannelFlags.IsDisabled() {
return false
}
if lnwire.MilliSatoshi(update.BaseFee) != prev.FeeBaseMSat {
return false
}
if lnwire.MilliSatoshi(update.FeeRate) != prev.FeeProportionalMillionths {
return false
}
if update.TimeLockDelta != prev.TimeLockDelta {
return false
}
if update.HtlcMinimumMsat != prev.MinHTLC {
return false
}
if update.MessageFlags.HasMaxHtlc() && !prev.MessageFlags.HasMaxHtlc() {
return false
}
if update.HtlcMaximumMsat != prev.MaxHTLC {
return false
}
if !bytes.Equal(update.ExtraOpaqueData, prev.ExtraOpaqueData) {
return false
}
return true
}
// latestHeight returns the gossiper's latest height known of the chain.
func (d *AuthenticatedGossiper) latestHeight() uint32 {
d.Lock()
defer d.Unlock()
return d.bestHeight
}
// handleNodeAnnouncement processes a new node announcement.
func (d *AuthenticatedGossiper) handleNodeAnnouncement(nMsg *networkMsg,
nodeAnn *lnwire.NodeAnnouncement,
ops []batch.SchedulerOption) ([]networkMsg, bool) {
timestamp := time.Unix(int64(nodeAnn.Timestamp), 0)
log.Debugf("Processing NodeAnnouncement: peer=%v, timestamp=%v, "+
"node=%x, source=%x", nMsg.peer, timestamp, nodeAnn.NodeID,
nMsg.source.SerializeCompressed())
// We'll quickly ask the router if it already has a newer update for
// this node so we can skip validating signatures if not required.
if d.cfg.Graph.IsStaleNode(nodeAnn.NodeID, timestamp) {
log.Debugf("Skipped processing stale node: %x", nodeAnn.NodeID)
nMsg.err <- nil
return nil, true
}
if err := d.addNode(nodeAnn, ops...); err != nil {
log.Debugf("Adding node: %x got error: %v", nodeAnn.NodeID,
err)
if !graph.IsError(
err,
graph.ErrOutdated,
graph.ErrIgnored,
) {
log.Error(err)
}
nMsg.err <- err
return nil, false
}
// In order to ensure we don't leak unadvertised nodes, we'll make a
// quick check to ensure this node intends to publicly advertise itself
// to the network.
isPublic, err := d.cfg.Graph.IsPublicNode(nodeAnn.NodeID)
if err != nil {
log.Errorf("Unable to determine if node %x is advertised: %v",
nodeAnn.NodeID, err)
nMsg.err <- err
return nil, false
}
var announcements []networkMsg
// If it does, we'll add their announcement to our batch so that it can
// be broadcast to the rest of our peers.
if isPublic {
announcements = append(announcements, networkMsg{
peer: nMsg.peer,
isRemote: nMsg.isRemote,
source: nMsg.source,
msg: nodeAnn,
})
} else {
log.Tracef("Skipping broadcasting node announcement for %x "+
"due to being unadvertised", nodeAnn.NodeID)
}
nMsg.err <- nil
// TODO(roasbeef): get rid of the above
log.Debugf("Processed NodeAnnouncement: peer=%v, timestamp=%v, "+
"node=%x, source=%x", nMsg.peer, timestamp, nodeAnn.NodeID,
nMsg.source.SerializeCompressed())
return announcements, true
}
// handleChanAnnouncement processes a new channel announcement.
func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
ann *lnwire.ChannelAnnouncement1,
ops ...batch.SchedulerOption) ([]networkMsg, bool) {
scid := ann.ShortChannelID
log.Debugf("Processing ChannelAnnouncement1: peer=%v, short_chan_id=%v",
nMsg.peer, scid.ToUint64())
// We'll ignore any channel announcements that target any chain other
// than the set of chains we know of.
if !bytes.Equal(ann.ChainHash[:], d.cfg.ChainHash[:]) {
err := fmt.Errorf("ignoring ChannelAnnouncement1 from chain=%v"+
", gossiper on chain=%v", ann.ChainHash,
d.cfg.ChainHash)
log.Errorf(err.Error())
key := newRejectCacheKey(
scid.ToUint64(),
sourceToPub(nMsg.source),
)
_, _ = d.recentRejects.Put(key, &cachedReject{})
nMsg.err <- err
return nil, false
}
// If this is a remote ChannelAnnouncement with an alias SCID, we'll
// reject the announcement. Since the router accepts alias SCIDs,
// not erroring out would be a DoS vector.
if nMsg.isRemote && d.cfg.IsAlias(scid) {
err := fmt.Errorf("ignoring remote alias channel=%v", scid)
log.Errorf(err.Error())
key := newRejectCacheKey(
scid.ToUint64(),
sourceToPub(nMsg.source),
)
_, _ = d.recentRejects.Put(key, &cachedReject{})
nMsg.err <- err
return nil, false
}
// If the advertised inclusionary block is beyond our knowledge of the
// chain tip, then we'll ignore it for now.
d.Lock()
if nMsg.isRemote && d.isPremature(scid, 0, nMsg) {
log.Warnf("Announcement for chan_id=(%v), is premature: "+
"advertises height %v, only height %v is known",
scid.ToUint64(), scid.BlockHeight, d.bestHeight)
d.Unlock()
nMsg.err <- nil
return nil, false
}
d.Unlock()
// At this point, we'll now ask the router if this is a zombie/known
// edge. If so we can skip all the processing below.
if d.cfg.Graph.IsKnownEdge(scid) {
nMsg.err <- nil
return nil, true
}
// Check if the channel is already closed in which case we can ignore
// it.
closed, err := d.cfg.ScidCloser.IsClosedScid(scid)
if err != nil {
log.Errorf("failed to check if scid %v is closed: %v", scid,
err)
nMsg.err <- err
return nil, false
}
if closed {
err = fmt.Errorf("ignoring closed channel %v", scid)
log.Error(err)
// If this is an announcement from us, we'll just ignore it.
if !nMsg.isRemote {
nMsg.err <- err
return nil, false
}
// Increment the peer's ban score if they are sending closed
// channel announcements.
d.banman.incrementBanScore(nMsg.peer.PubKey())
// If the peer is banned and not a channel peer, we'll
// disconnect them.
shouldDc, dcErr := d.ShouldDisconnect(nMsg.peer.IdentityKey())
if dcErr != nil {
log.Errorf("failed to check if we should disconnect "+
"peer: %v", dcErr)
nMsg.err <- dcErr
return nil, false
}
if shouldDc {
nMsg.peer.Disconnect(ErrPeerBanned)
}
nMsg.err <- err
return nil, false
}
// If this is a remote channel announcement, then we'll validate all
// the signatures within the proof as it should be well formed.
var proof *models.ChannelAuthProof
if nMsg.isRemote {
err := netann.ValidateChannelAnn(ann, d.fetchPKScript)
if err != nil {
err := fmt.Errorf("unable to validate announcement: "+
"%v", err)
key := newRejectCacheKey(
scid.ToUint64(),
sourceToPub(nMsg.source),
)
_, _ = d.recentRejects.Put(key, &cachedReject{})
log.Error(err)
nMsg.err <- err
return nil, false
}
// If the proof checks out, then we'll save the proof itself to
// the database so we can fetch it later when gossiping with
// other nodes.
proof = &models.ChannelAuthProof{
NodeSig1Bytes: ann.NodeSig1.ToSignatureBytes(),
NodeSig2Bytes: ann.NodeSig2.ToSignatureBytes(),
BitcoinSig1Bytes: ann.BitcoinSig1.ToSignatureBytes(),
BitcoinSig2Bytes: ann.BitcoinSig2.ToSignatureBytes(),
}
}
// With the proof validated (if necessary), we can now store it within
// the database for our path finding and syncing needs.
var featureBuf bytes.Buffer
if err := ann.Features.Encode(&featureBuf); err != nil {
log.Errorf("unable to encode features: %v", err)
nMsg.err <- err
return nil, false
}
edge := &models.ChannelEdgeInfo{
ChannelID: scid.ToUint64(),
ChainHash: ann.ChainHash,
NodeKey1Bytes: ann.NodeID1,
NodeKey2Bytes: ann.NodeID2,
BitcoinKey1Bytes: ann.BitcoinKey1,
BitcoinKey2Bytes: ann.BitcoinKey2,
AuthProof: proof,
Features: featureBuf.Bytes(),
ExtraOpaqueData: ann.ExtraOpaqueData,
}
// If there were any optional message fields provided, we'll include
// them in its serialized disk representation now.
var tapscriptRoot fn.Option[chainhash.Hash]
if nMsg.optionalMsgFields != nil {
if nMsg.optionalMsgFields.capacity != nil {
edge.Capacity = *nMsg.optionalMsgFields.capacity
}
if nMsg.optionalMsgFields.channelPoint != nil {
cp := *nMsg.optionalMsgFields.channelPoint
edge.ChannelPoint = cp
}
// Optional tapscript root for custom channels.
tapscriptRoot = nMsg.optionalMsgFields.tapscriptRoot
}
// Before we start validation or add the edge to the database, we obtain
// the mutex for this channel ID. We do this to ensure no other
// goroutine has read the database and is now making decisions based on
// this DB state, before it writes to the DB. It also ensures that we
// don't perform the expensive validation check on the same channel
// announcement at the same time.
d.channelMtx.Lock(scid.ToUint64())
// If AssumeChannelValid is present, then we are unable to perform any
// of the expensive checks below, so we'll short-circuit our path
// straight to adding the edge to our graph. If the passed
// ShortChannelID is an alias, then we'll skip validation as it will
// not map to a legitimate tx. This is not a DoS vector as only we can
// add an alias ChannelAnnouncement from the gossiper.
if !(d.cfg.AssumeChannelValid || d.cfg.IsAlias(scid)) { //nolint:nestif
op, capacity, script, err := d.validateFundingTransaction(
ann, tapscriptRoot,
)
if err != nil {
defer d.channelMtx.Unlock(scid.ToUint64())
switch {
case errors.Is(err, ErrNoFundingTransaction),
errors.Is(err, ErrInvalidFundingOutput):
key := newRejectCacheKey(
scid.ToUint64(),
sourceToPub(nMsg.source),
)
_, _ = d.recentRejects.Put(
key, &cachedReject{},
)
// Increment the peer's ban score. We check
// isRemote so we don't actually ban the peer in
// case of a local bug.
if nMsg.isRemote {
d.banman.incrementBanScore(
nMsg.peer.PubKey(),
)
}
case errors.Is(err, ErrChannelSpent):
key := newRejectCacheKey(
scid.ToUint64(),
sourceToPub(nMsg.source),
)
_, _ = d.recentRejects.Put(key, &cachedReject{})
// Since this channel has already been closed,
// we'll add it to the graph's closed channel
// index such that we won't attempt to do
// expensive validation checks on it again.
// TODO: Populate the ScidCloser by using closed
// channel notifications.
dbErr := d.cfg.ScidCloser.PutClosedScid(scid)
if dbErr != nil {
log.Errorf("failed to mark scid(%v) "+
"as closed: %v", scid, dbErr)
nMsg.err <- dbErr
return nil, false
}
// Increment the peer's ban score. We check
// isRemote so we don't accidentally ban
// ourselves in case of a bug.
if nMsg.isRemote {
d.banman.incrementBanScore(
nMsg.peer.PubKey(),
)
}
default:
// Otherwise, this is just a regular rejected
// edge.
key := newRejectCacheKey(
scid.ToUint64(),
sourceToPub(nMsg.source),
)
_, _ = d.recentRejects.Put(key, &cachedReject{})
}
if !nMsg.isRemote {
log.Errorf("failed to add edge for local "+
"channel: %v", err)
nMsg.err <- err
return nil, false
}
shouldDc, dcErr := d.ShouldDisconnect(
nMsg.peer.IdentityKey(),
)
if dcErr != nil {
log.Errorf("failed to check if we should "+
"disconnect peer: %v", dcErr)
nMsg.err <- dcErr
return nil, false
}
if shouldDc {
nMsg.peer.Disconnect(ErrPeerBanned)
}
nMsg.err <- err
return nil, false
}
edge.FundingScript = fn.Some(script)
// TODO(roasbeef): this is a hack, needs to be removed after
// commitment fees are dynamic.
edge.Capacity = capacity
edge.ChannelPoint = op
}
log.Debugf("Adding edge for short_chan_id: %v", scid.ToUint64())
// We will add the edge to the channel router. If the nodes present in
// this channel are not present in the database, a partial node will be
// added to represent each node while we wait for a node announcement.
err = d.cfg.Graph.AddEdge(edge, ops...)
if err != nil {
log.Debugf("Graph rejected edge for short_chan_id(%v): %v",
scid.ToUint64(), err)
defer d.channelMtx.Unlock(scid.ToUint64())
// If the edge was rejected due to already being known, then it
// may be the case that this new message has a fresh channel
// proof, so we'll check.
if graph.IsError(err, graph.ErrIgnored) {
// Attempt to process the rejected message to see if we
// get any new announcements.
anns, rErr := d.processRejectedEdge(ann, proof)
if rErr != nil {
key := newRejectCacheKey(
scid.ToUint64(),
sourceToPub(nMsg.source),
)
cr := &cachedReject{}
_, _ = d.recentRejects.Put(key, cr)
nMsg.err <- rErr
return nil, false
}
log.Debugf("Extracted %v announcements from rejected "+
"msgs", len(anns))
// If while processing this rejected edge, we realized
// there's a set of announcements we could extract,
// then we'll return those directly.
//
// NOTE: since this is an ErrIgnored, we can return
// true here to signal "allow" to its dependants.
nMsg.err <- nil
return anns, true
}
// Otherwise, this is just a regular rejected edge.
key := newRejectCacheKey(
scid.ToUint64(),
sourceToPub(nMsg.source),
)
_, _ = d.recentRejects.Put(key, &cachedReject{})
if !nMsg.isRemote {
log.Errorf("failed to add edge for local channel: %v",
err)
nMsg.err <- err
return nil, false
}
shouldDc, dcErr := d.ShouldDisconnect(nMsg.peer.IdentityKey())
if dcErr != nil {
log.Errorf("failed to check if we should disconnect "+
"peer: %v", dcErr)
nMsg.err <- dcErr
return nil, false
}
if shouldDc {
nMsg.peer.Disconnect(ErrPeerBanned)
}
nMsg.err <- err
return nil, false
}
// If err is nil, release the lock immediately.
d.channelMtx.Unlock(scid.ToUint64())
log.Debugf("Finish adding edge for short_chan_id: %v", scid.ToUint64())
// If we earlier received any ChannelUpdates for this channel, we can
// now process them, as the channel is added to the graph.
var channelUpdates []*processedNetworkMsg
earlyChanUpdates, err := d.prematureChannelUpdates.Get(scid.ToUint64())
if err == nil {
// There was actually an entry in the map, so we'll accumulate
// it. We don't worry about deletion, since it'll eventually
// fall out anyway.
chanMsgs := earlyChanUpdates
channelUpdates = append(channelUpdates, chanMsgs.msgs...)
}
// Launch a new goroutine to handle each ChannelUpdate, this is to
// ensure we don't block here, as we can handle only one announcement
// at a time.
for _, cu := range channelUpdates {
// Skip if already processed.
if cu.processed {
continue
}
// Mark the ChannelUpdate as processed. This ensures that a
// subsequent announcement in the option-scid-alias case does
// not re-use an old ChannelUpdate.
cu.processed = true
d.wg.Add(1)
go func(updMsg *networkMsg) {
defer d.wg.Done()
switch msg := updMsg.msg.(type) {
// Reprocess the message, making sure we return an
// error to the original caller in case the gossiper
// shuts down.
case *lnwire.ChannelUpdate1:
log.Debugf("Reprocessing ChannelUpdate for "+
"shortChanID=%v", scid.ToUint64())
select {
case d.networkMsgs <- updMsg:
case <-d.quit:
updMsg.err <- ErrGossiperShuttingDown
}
// We don't expect any other message type than
// ChannelUpdate to be in this cache.
default:
log.Errorf("Unsupported message type found "+
"among ChannelUpdates: %T", msg)
}
}(cu.msg)
}
// Channel announcement was successfully processed and now it might be
// broadcast to other connected nodes if it was an announcement with
// proof (remote).
var announcements []networkMsg
if proof != nil {
announcements = append(announcements, networkMsg{
peer: nMsg.peer,
isRemote: nMsg.isRemote,
source: nMsg.source,
msg: ann,
})
}
nMsg.err <- nil
log.Debugf("Processed ChannelAnnouncement1: peer=%v, short_chan_id=%v",
nMsg.peer, scid.ToUint64())
return announcements, true
}
// handleChanUpdate processes a new channel update.
func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
upd *lnwire.ChannelUpdate1,
ops []batch.SchedulerOption) ([]networkMsg, bool) {
log.Debugf("Processing ChannelUpdate: peer=%v, short_chan_id=%v, ",
nMsg.peer, upd.ShortChannelID.ToUint64())
// We'll ignore any channel updates that target any chain other than
// the set of chains we know of.
if !bytes.Equal(upd.ChainHash[:], d.cfg.ChainHash[:]) {
err := fmt.Errorf("ignoring ChannelUpdate from chain=%v, "+
"gossiper on chain=%v", upd.ChainHash, d.cfg.ChainHash)
log.Errorf(err.Error())
key := newRejectCacheKey(
upd.ShortChannelID.ToUint64(),
sourceToPub(nMsg.source),
)
_, _ = d.recentRejects.Put(key, &cachedReject{})
nMsg.err <- err
return nil, false
}
blockHeight := upd.ShortChannelID.BlockHeight
shortChanID := upd.ShortChannelID.ToUint64()
// If the advertised inclusionary block is beyond our knowledge of the
// chain tip, then we'll put the announcement in limbo to be fully
// verified once we advance forward in the chain. If the update has an
// alias SCID, we'll skip the isPremature check. This is necessary
// since aliases start at block height 16_000_000.
d.Lock()
if nMsg.isRemote && !d.cfg.IsAlias(upd.ShortChannelID) &&
d.isPremature(upd.ShortChannelID, 0, nMsg) {
log.Warnf("Update announcement for short_chan_id(%v), is "+
"premature: advertises height %v, only height %v is "+
"known", shortChanID, blockHeight, d.bestHeight)
d.Unlock()
nMsg.err <- nil
return nil, false
}
d.Unlock()
// Before we perform any of the expensive checks below, we'll check
// whether this update is stale or is for a zombie channel in order to
// quickly reject it.
timestamp := time.Unix(int64(upd.Timestamp), 0)
// Fetch the SCID we should be using to lock the channelMtx and make
// graph queries with.
graphScid, err := d.cfg.FindBaseByAlias(upd.ShortChannelID)
if err != nil {
// Fallback and set the graphScid to the peer-provided SCID.
// This will occur for non-option-scid-alias channels and for
// public option-scid-alias channels after 6 confirmations.
// Once public option-scid-alias channels have 6 confs, we'll
// ignore ChannelUpdates with one of their aliases.
graphScid = upd.ShortChannelID
}
// We make sure to obtain the mutex for this channel ID before we access
// the database. This ensures the state we read from the database has
// not changed between this point and when we call UpdateEdge() later.
d.channelMtx.Lock(graphScid.ToUint64())
defer d.channelMtx.Unlock(graphScid.ToUint64())
if d.cfg.Graph.IsStaleEdgePolicy(
graphScid, timestamp, upd.ChannelFlags,
) {
log.Debugf("Ignored stale edge policy for short_chan_id(%v): "+
"peer=%v, msg=%s, is_remote=%v", shortChanID,
nMsg.peer, nMsg.msg.MsgType(), nMsg.isRemote,
)
nMsg.err <- nil
return nil, true
}
// Check that the ChanUpdate is not too far into the future, this could
// reveal some faulty implementation therefore we log an error.
if time.Until(timestamp) > graph.DefaultChannelPruneExpiry {
log.Errorf("Skewed timestamp (%v) for edge policy of "+
"short_chan_id(%v), timestamp too far in the future: "+
"peer=%v, msg=%s, is_remote=%v", timestamp.Unix(),
shortChanID, nMsg.peer, nMsg.msg.MsgType(),
nMsg.isRemote,
)
nMsg.err <- fmt.Errorf("skewed timestamp of edge policy, "+
"timestamp too far in the future: %v", timestamp.Unix())
return nil, false
}
// Get the node pub key as far since we don't have it in the channel
// update announcement message. We'll need this to properly verify the
// message's signature.
chanInfo, e1, e2, err := d.cfg.Graph.GetChannelByID(graphScid)
switch {
// No error, break.
case err == nil:
break
case errors.Is(err, graphdb.ErrZombieEdge):
err = d.processZombieUpdate(chanInfo, graphScid, upd)
if err != nil {
log.Debug(err)
nMsg.err <- err
return nil, false
}
// We'll fallthrough to ensure we stash the update until we
// receive its corresponding ChannelAnnouncement. This is
// needed to ensure the edge exists in the graph before
// applying the update.
fallthrough
case errors.Is(err, graphdb.ErrGraphNotFound):
fallthrough
case errors.Is(err, graphdb.ErrGraphNoEdgesFound):
fallthrough
case errors.Is(err, graphdb.ErrEdgeNotFound):
// If the edge corresponding to this ChannelUpdate was not
// found in the graph, this might be a channel in the process
// of being opened, and we haven't processed our own
// ChannelAnnouncement yet, hence it is not not found in the
// graph. This usually gets resolved after the channel proofs
// are exchanged and the channel is broadcasted to the rest of
// the network, but in case this is a private channel this
// won't ever happen. This can also happen in the case of a
// zombie channel with a fresh update for which we don't have a
// ChannelAnnouncement for since we reject them. Because of
// this, we temporarily add it to a map, and reprocess it after
// our own ChannelAnnouncement has been processed.
//
// The shortChanID may be an alias, but it is fine to use here
// since we don't have an edge in the graph and if the peer is
// not buggy, we should be able to use it once the gossiper
// receives the local announcement.
pMsg := &processedNetworkMsg{msg: nMsg}
earlyMsgs, err := d.prematureChannelUpdates.Get(shortChanID)
switch {
// Nothing in the cache yet, we can just directly insert this
// element.
case err == cache.ErrElementNotFound:
_, _ = d.prematureChannelUpdates.Put(
shortChanID, &cachedNetworkMsg{
msgs: []*processedNetworkMsg{pMsg},
})
// There's already something in the cache, so we'll combine the
// set of messages into a single value.
default:
msgs := earlyMsgs.msgs
msgs = append(msgs, pMsg)
_, _ = d.prematureChannelUpdates.Put(
shortChanID, &cachedNetworkMsg{
msgs: msgs,
})
}
log.Debugf("Got ChannelUpdate for edge not found in graph"+
"(shortChanID=%v), saving for reprocessing later",
shortChanID)
// NOTE: We don't return anything on the error channel for this
// message, as we expect that will be done when this
// ChannelUpdate is later reprocessed.
return nil, false
default:
err := fmt.Errorf("unable to validate channel update "+
"short_chan_id=%v: %v", shortChanID, err)
log.Error(err)
nMsg.err <- err
key := newRejectCacheKey(
upd.ShortChannelID.ToUint64(),
sourceToPub(nMsg.source),
)
_, _ = d.recentRejects.Put(key, &cachedReject{})
return nil, false
}
// The least-significant bit in the flag on the channel update
// announcement tells us "which" side of the channels directed edge is
// being updated.
var (
pubKey *btcec.PublicKey
edgeToUpdate *models.ChannelEdgePolicy
)
direction := upd.ChannelFlags & lnwire.ChanUpdateDirection
switch direction {
case 0:
pubKey, _ = chanInfo.NodeKey1()
edgeToUpdate = e1
case 1:
pubKey, _ = chanInfo.NodeKey2()
edgeToUpdate = e2
}
log.Debugf("Validating ChannelUpdate: channel=%v, for node=%x, has "+
"edge policy=%v", chanInfo.ChannelID,
pubKey.SerializeCompressed(), edgeToUpdate != nil)
// Validate the channel announcement with the expected public key and
// channel capacity. In the case of an invalid channel update, we'll
// return an error to the caller and exit early.
err = netann.ValidateChannelUpdateAnn(pubKey, chanInfo.Capacity, upd)
if err != nil {
rErr := fmt.Errorf("unable to validate channel update "+
"announcement for short_chan_id=%v: %v",
spew.Sdump(upd.ShortChannelID), err)
log.Error(rErr)
nMsg.err <- rErr
return nil, false
}
// If we have a previous version of the edge being updated, we'll want
// to rate limit its updates to prevent spam throughout the network.
if nMsg.isRemote && edgeToUpdate != nil {
// If it's a keep-alive update, we'll only propagate one if
// it's been a day since the previous. This follows our own
// heuristic of sending keep-alive updates after the same
// duration (see retransmitStaleAnns).
timeSinceLastUpdate := timestamp.Sub(edgeToUpdate.LastUpdate)
if IsKeepAliveUpdate(upd, edgeToUpdate) {
if timeSinceLastUpdate < d.cfg.RebroadcastInterval {
log.Debugf("Ignoring keep alive update not "+
"within %v period for channel %v",
d.cfg.RebroadcastInterval, shortChanID)
nMsg.err <- nil
return nil, false
}
} else {
// If it's not, we'll allow an update per minute with a
// maximum burst of 10. If we haven't seen an update
// for this channel before, we'll need to initialize a
// rate limiter for each direction.
//
// Since the edge exists in the graph, we'll create a
// rate limiter for chanInfo.ChannelID rather then the
// SCID the peer sent. This is because there may be
// multiple aliases for a channel and we may otherwise
// rate-limit only a single alias of the channel,
// instead of the whole channel.
baseScid := chanInfo.ChannelID
d.Lock()
rls, ok := d.chanUpdateRateLimiter[baseScid]
if !ok {
r := rate.Every(d.cfg.ChannelUpdateInterval)
b := d.cfg.MaxChannelUpdateBurst
rls = [2]*rate.Limiter{
rate.NewLimiter(r, b),
rate.NewLimiter(r, b),
}
d.chanUpdateRateLimiter[baseScid] = rls
}
d.Unlock()
if !rls[direction].Allow() {
log.Debugf("Rate limiting update for channel "+
"%v from direction %x", shortChanID,
pubKey.SerializeCompressed())
nMsg.err <- nil
return nil, false
}
}
}
// We'll use chanInfo.ChannelID rather than the peer-supplied
// ShortChannelID in the ChannelUpdate to avoid the router having to
// lookup the stored SCID. If we're sending the update, we'll always
// use the SCID stored in the database rather than a potentially
// different alias. This might mean that SigBytes is incorrect as it
// signs a different SCID than the database SCID, but since there will
// only be a difference if AuthProof == nil, this is fine.
update := &models.ChannelEdgePolicy{
SigBytes: upd.Signature.ToSignatureBytes(),
ChannelID: chanInfo.ChannelID,
LastUpdate: timestamp,
MessageFlags: upd.MessageFlags,
ChannelFlags: upd.ChannelFlags,
TimeLockDelta: upd.TimeLockDelta,
MinHTLC: upd.HtlcMinimumMsat,
MaxHTLC: upd.HtlcMaximumMsat,
FeeBaseMSat: lnwire.MilliSatoshi(upd.BaseFee),
FeeProportionalMillionths: lnwire.MilliSatoshi(upd.FeeRate),
ExtraOpaqueData: upd.ExtraOpaqueData,
}
if err := d.cfg.Graph.UpdateEdge(update, ops...); err != nil {
if graph.IsError(
err, graph.ErrOutdated,
graph.ErrIgnored,
) {
log.Debugf("Update edge for short_chan_id(%v) got: %v",
shortChanID, err)
} else {
// Since we know the stored SCID in the graph, we'll
// cache that SCID.
key := newRejectCacheKey(
chanInfo.ChannelID,
sourceToPub(nMsg.source),
)
_, _ = d.recentRejects.Put(key, &cachedReject{})
log.Errorf("Update edge for short_chan_id(%v) got: %v",
shortChanID, err)
}
nMsg.err <- err
return nil, false
}
// If this is a local ChannelUpdate without an AuthProof, it means it
// is an update to a channel that is not (yet) supposed to be announced
// to the greater network. However, our channel counter party will need
// to be given the update, so we'll try sending the update directly to
// the remote peer.
if !nMsg.isRemote && chanInfo.AuthProof == nil {
if nMsg.optionalMsgFields != nil {
remoteAlias := nMsg.optionalMsgFields.remoteAlias
if remoteAlias != nil {
// The remoteAlias field was specified, meaning
// that we should replace the SCID in the
// update with the remote's alias. We'll also
// need to re-sign the channel update. This is
// required for option-scid-alias feature-bit
// negotiated channels.
upd.ShortChannelID = *remoteAlias
sig, err := d.cfg.SignAliasUpdate(upd)
if err != nil {
log.Error(err)
nMsg.err <- err
return nil, false
}
lnSig, err := lnwire.NewSigFromSignature(sig)
if err != nil {
log.Error(err)
nMsg.err <- err
return nil, false
}
upd.Signature = lnSig
}
}
// Get our peer's public key.
remotePubKey := remotePubFromChanInfo(
chanInfo, upd.ChannelFlags,
)
log.Debugf("The message %v has no AuthProof, sending the "+
"update to remote peer %x", upd.MsgType(), remotePubKey)
// Now we'll attempt to send the channel update message
// reliably to the remote peer in the background, so that we
// don't block if the peer happens to be offline at the moment.
err := d.reliableSender.sendMessage(upd, remotePubKey)
if err != nil {
err := fmt.Errorf("unable to reliably send %v for "+
"channel=%v to peer=%x: %v", upd.MsgType(),
upd.ShortChannelID, remotePubKey, err)
nMsg.err <- err
return nil, false
}
}
// Channel update announcement was successfully processed and now it
// can be broadcast to the rest of the network. However, we'll only
// broadcast the channel update announcement if it has an attached
// authentication proof. We also won't broadcast the update if it
// contains an alias because the network would reject this.
var announcements []networkMsg
if chanInfo.AuthProof != nil && !d.cfg.IsAlias(upd.ShortChannelID) {
announcements = append(announcements, networkMsg{
peer: nMsg.peer,
source: nMsg.source,
isRemote: nMsg.isRemote,
msg: upd,
})
}
nMsg.err <- nil
log.Debugf("Processed ChannelUpdate: peer=%v, short_chan_id=%v, "+
"timestamp=%v", nMsg.peer, upd.ShortChannelID.ToUint64(),
timestamp)
return announcements, true
}
// handleAnnSig processes a new announcement signatures message.
func (d *AuthenticatedGossiper) handleAnnSig(nMsg *networkMsg,
ann *lnwire.AnnounceSignatures1) ([]networkMsg, bool) {
needBlockHeight := ann.ShortChannelID.BlockHeight +
d.cfg.ProofMatureDelta
shortChanID := ann.ShortChannelID.ToUint64()
prefix := "local"
if nMsg.isRemote {
prefix = "remote"
}
log.Infof("Received new %v announcement signature for %v", prefix,
ann.ShortChannelID)
// By the specification, channel announcement proofs should be sent
// after some number of confirmations after channel was registered in
// bitcoin blockchain. Therefore, we check if the proof is mature.
d.Lock()
premature := d.isPremature(
ann.ShortChannelID, d.cfg.ProofMatureDelta, nMsg,
)
if premature {
log.Warnf("Premature proof announcement, current block height"+
"lower than needed: %v < %v", d.bestHeight,
needBlockHeight)
d.Unlock()
nMsg.err <- nil
return nil, false
}
d.Unlock()
// Ensure that we know of a channel with the target channel ID before
// proceeding further.
//
// We must acquire the mutex for this channel ID before getting the
// channel from the database, to ensure what we read does not change
// before we call AddProof() later.
d.channelMtx.Lock(ann.ShortChannelID.ToUint64())
defer d.channelMtx.Unlock(ann.ShortChannelID.ToUint64())
chanInfo, e1, e2, err := d.cfg.Graph.GetChannelByID(
ann.ShortChannelID,
)
if err != nil {
_, err = d.cfg.FindChannel(nMsg.source, ann.ChannelID)
if err != nil {
err := fmt.Errorf("unable to store the proof for "+
"short_chan_id=%v: %v", shortChanID, err)
log.Error(err)
nMsg.err <- err
return nil, false
}
proof := channeldb.NewWaitingProof(nMsg.isRemote, ann)
err := d.cfg.WaitingProofStore.Add(proof)
if err != nil {
err := fmt.Errorf("unable to store the proof for "+
"short_chan_id=%v: %v", shortChanID, err)
log.Error(err)
nMsg.err <- err
return nil, false
}
log.Infof("Orphan %v proof announcement with short_chan_id=%v"+
", adding to waiting batch", prefix, shortChanID)
nMsg.err <- nil
return nil, false
}
nodeID := nMsg.source.SerializeCompressed()
isFirstNode := bytes.Equal(nodeID, chanInfo.NodeKey1Bytes[:])
isSecondNode := bytes.Equal(nodeID, chanInfo.NodeKey2Bytes[:])
// Ensure that channel that was retrieved belongs to the peer which
// sent the proof announcement.
if !(isFirstNode || isSecondNode) {
err := fmt.Errorf("channel that was received doesn't belong "+
"to the peer which sent the proof, short_chan_id=%v",
shortChanID)
log.Error(err)
nMsg.err <- err
return nil, false
}
// If proof was sent by a local sub-system, then we'll send the
// announcement signature to the remote node so they can also
// reconstruct the full channel announcement.
if !nMsg.isRemote {
var remotePubKey [33]byte
if isFirstNode {
remotePubKey = chanInfo.NodeKey2Bytes
} else {
remotePubKey = chanInfo.NodeKey1Bytes
}
// Since the remote peer might not be online we'll call a
// method that will attempt to deliver the proof when it comes
// online.
err := d.reliableSender.sendMessage(ann, remotePubKey)
if err != nil {
err := fmt.Errorf("unable to reliably send %v for "+
"channel=%v to peer=%x: %v", ann.MsgType(),
ann.ShortChannelID, remotePubKey, err)
nMsg.err <- err
return nil, false
}
}
// Check if we already have the full proof for this channel.
if chanInfo.AuthProof != nil {
// If we already have the fully assembled proof, then the peer
// sending us their proof has probably not received our local
// proof yet. So be kind and send them the full proof.
if nMsg.isRemote {
peerID := nMsg.source.SerializeCompressed()
log.Debugf("Got AnnounceSignatures for channel with " +
"full proof.")
d.wg.Add(1)
go func() {
defer d.wg.Done()
log.Debugf("Received half proof for channel "+
"%v with existing full proof. Sending"+
" full proof to peer=%x",
ann.ChannelID, peerID)
ca, _, _, err := netann.CreateChanAnnouncement(
chanInfo.AuthProof, chanInfo, e1, e2,
)
if err != nil {
log.Errorf("unable to gen ann: %v",
err)
return
}
err = nMsg.peer.SendMessage(false, ca)
if err != nil {
log.Errorf("Failed sending full proof"+
" to peer=%x: %v", peerID, err)
return
}
log.Debugf("Full proof sent to peer=%x for "+
"chanID=%v", peerID, ann.ChannelID)
}()
}
log.Debugf("Already have proof for channel with chanID=%v",
ann.ChannelID)
nMsg.err <- nil
return nil, true
}
// Check that we received the opposite proof. If so, then we're now
// able to construct the full proof, and create the channel
// announcement. If we didn't receive the opposite half of the proof
// then we should store this one, and wait for the opposite to be
// received.
proof := channeldb.NewWaitingProof(nMsg.isRemote, ann)
oppProof, err := d.cfg.WaitingProofStore.Get(proof.OppositeKey())
if err != nil && err != channeldb.ErrWaitingProofNotFound {
err := fmt.Errorf("unable to get the opposite proof for "+
"short_chan_id=%v: %v", shortChanID, err)
log.Error(err)
nMsg.err <- err
return nil, false
}
if err == channeldb.ErrWaitingProofNotFound {
err := d.cfg.WaitingProofStore.Add(proof)
if err != nil {
err := fmt.Errorf("unable to store the proof for "+
"short_chan_id=%v: %v", shortChanID, err)
log.Error(err)
nMsg.err <- err
return nil, false
}
log.Infof("1/2 of channel ann proof received for "+
"short_chan_id=%v, waiting for other half",
shortChanID)
nMsg.err <- nil
return nil, false
}
// We now have both halves of the channel announcement proof, then
// we'll reconstruct the initial announcement so we can validate it
// shortly below.
var dbProof models.ChannelAuthProof
if isFirstNode {
dbProof.NodeSig1Bytes = ann.NodeSignature.ToSignatureBytes()
dbProof.NodeSig2Bytes = oppProof.NodeSignature.ToSignatureBytes()
dbProof.BitcoinSig1Bytes = ann.BitcoinSignature.ToSignatureBytes()
dbProof.BitcoinSig2Bytes = oppProof.BitcoinSignature.ToSignatureBytes()
} else {
dbProof.NodeSig1Bytes = oppProof.NodeSignature.ToSignatureBytes()
dbProof.NodeSig2Bytes = ann.NodeSignature.ToSignatureBytes()
dbProof.BitcoinSig1Bytes = oppProof.BitcoinSignature.ToSignatureBytes()
dbProof.BitcoinSig2Bytes = ann.BitcoinSignature.ToSignatureBytes()
}
chanAnn, e1Ann, e2Ann, err := netann.CreateChanAnnouncement(
&dbProof, chanInfo, e1, e2,
)
if err != nil {
log.Error(err)
nMsg.err <- err
return nil, false
}
// With all the necessary components assembled validate the full
// channel announcement proof.
err = netann.ValidateChannelAnn(chanAnn, d.fetchPKScript)
if err != nil {
err := fmt.Errorf("channel announcement proof for "+
"short_chan_id=%v isn't valid: %v", shortChanID, err)
log.Error(err)
nMsg.err <- err
return nil, false
}
// If the channel was returned by the router it means that existence of
// funding point and inclusion of nodes bitcoin keys in it already
// checked by the router. In this stage we should check that node keys
// attest to the bitcoin keys by validating the signatures of
// announcement. If proof is valid then we'll populate the channel edge
// with it, so we can announce it on peer connect.
err = d.cfg.Graph.AddProof(ann.ShortChannelID, &dbProof)
if err != nil {
err := fmt.Errorf("unable add proof to the channel chanID=%v:"+
" %v", ann.ChannelID, err)
log.Error(err)
nMsg.err <- err
return nil, false
}
err = d.cfg.WaitingProofStore.Remove(proof.OppositeKey())
if err != nil {
err := fmt.Errorf("unable to remove opposite proof for the "+
"channel with chanID=%v: %v", ann.ChannelID, err)
log.Error(err)
nMsg.err <- err
return nil, false
}
// Proof was successfully created and now can announce the channel to
// the remain network.
log.Infof("Fully valid channel proof for short_chan_id=%v constructed"+
", adding to next ann batch", shortChanID)
// Assemble the necessary announcements to add to the next broadcasting
// batch.
var announcements []networkMsg
announcements = append(announcements, networkMsg{
peer: nMsg.peer,
source: nMsg.source,
msg: chanAnn,
})
if src, err := chanInfo.NodeKey1(); err == nil && e1Ann != nil {
announcements = append(announcements, networkMsg{
peer: nMsg.peer,
source: src,
msg: e1Ann,
})
}
if src, err := chanInfo.NodeKey2(); err == nil && e2Ann != nil {
announcements = append(announcements, networkMsg{
peer: nMsg.peer,
source: src,
msg: e2Ann,
})
}
// We'll also send along the node announcements for each channel
// participant if we know of them. To ensure our node announcement
// propagates to our channel counterparty, we'll set the source for
// each announcement to the node it belongs to, otherwise we won't send
// it since the source gets skipped. This isn't necessary for channel
// updates and announcement signatures since we send those directly to
// our channel counterparty through the gossiper's reliable sender.
node1Ann, err := d.fetchNodeAnn(chanInfo.NodeKey1Bytes)
if err != nil {
log.Debugf("Unable to fetch node announcement for %x: %v",
chanInfo.NodeKey1Bytes, err)
} else {
if nodeKey1, err := chanInfo.NodeKey1(); err == nil {
announcements = append(announcements, networkMsg{
peer: nMsg.peer,
source: nodeKey1,
msg: node1Ann,
})
}
}
node2Ann, err := d.fetchNodeAnn(chanInfo.NodeKey2Bytes)
if err != nil {
log.Debugf("Unable to fetch node announcement for %x: %v",
chanInfo.NodeKey2Bytes, err)
} else {
if nodeKey2, err := chanInfo.NodeKey2(); err == nil {
announcements = append(announcements, networkMsg{
peer: nMsg.peer,
source: nodeKey2,
msg: node2Ann,
})
}
}
nMsg.err <- nil
return announcements, true
}
// isBanned returns true if the peer identified by pubkey is banned for sending
// invalid channel announcements.
func (d *AuthenticatedGossiper) isBanned(pubkey [33]byte) bool {
return d.banman.isBanned(pubkey)
}
// ShouldDisconnect returns true if we should disconnect the peer identified by
// pubkey.
func (d *AuthenticatedGossiper) ShouldDisconnect(pubkey *btcec.PublicKey) (
bool, error) {
pubkeySer := pubkey.SerializeCompressed()
var pubkeyBytes [33]byte
copy(pubkeyBytes[:], pubkeySer)
// If the public key is banned, check whether or not this is a channel
// peer.
if d.isBanned(pubkeyBytes) {
isChanPeer, err := d.cfg.ScidCloser.IsChannelPeer(pubkey)
if err != nil {
return false, err
}
// We should only disconnect non-channel peers.
if !isChanPeer {
return true, nil
}
}
return false, nil
}
// validateFundingTransaction fetches the channel announcements claimed funding
// transaction from chain to ensure that it exists, is not spent and matches
// the channel announcement proof. The transaction's outpoint and value are
// returned if we can glean them from the work done in this method.
func (d *AuthenticatedGossiper) validateFundingTransaction(
ann *lnwire.ChannelAnnouncement1,
tapscriptRoot fn.Option[chainhash.Hash]) (wire.OutPoint, btcutil.Amount,
[]byte, error) {
scid := ann.ShortChannelID
// Before we can add the channel to the channel graph, we need to obtain
// the full funding outpoint that's encoded within the channel ID.
fundingTx, err := lnwallet.FetchFundingTxWrapper(
d.cfg.ChainIO, &scid, d.quit,
)
if err != nil {
//nolint:ll
//
// In order to ensure we don't erroneously mark a channel as a
// zombie due to an RPC failure, we'll attempt to string match
// for the relevant errors.
//
// * btcd:
// * https://github.com/btcsuite/btcd/blob/master/rpcserver.go#L1316
// * https://github.com/btcsuite/btcd/blob/master/rpcserver.go#L1086
// * bitcoind:
// * https://github.com/bitcoin/bitcoin/blob/7fcf53f7b4524572d1d0c9a5fdc388e87eb02416/src/rpc/blockchain.cpp#L770
// * https://github.com/bitcoin/bitcoin/blob/7fcf53f7b4524572d1d0c9a5fdc388e87eb02416/src/rpc/blockchain.cpp#L954
switch {
case strings.Contains(err.Error(), "not found"):
fallthrough
case strings.Contains(err.Error(), "out of range"):
// If the funding transaction isn't found at all, then
// we'll mark the edge itself as a zombie so we don't
// continue to request it. We use the "zero key" for
// both node pubkeys so this edge can't be resurrected.
zErr := d.cfg.Graph.MarkZombieEdge(scid.ToUint64())
if zErr != nil {
return wire.OutPoint{}, 0, nil, zErr
}
default:
}
return wire.OutPoint{}, 0, nil, fmt.Errorf("%w: %w",
ErrNoFundingTransaction, err)
}
// Recreate witness output to be sure that declared in channel edge
// bitcoin keys and channel value corresponds to the reality.
fundingPkScript, err := makeFundingScript(
ann.BitcoinKey1[:], ann.BitcoinKey2[:], ann.Features,
tapscriptRoot,
)
if err != nil {
return wire.OutPoint{}, 0, nil, err
}
// Next we'll validate that this channel is actually well formed. If
// this check fails, then this channel either doesn't exist, or isn't
// the one that was meant to be created according to the passed channel
// proofs.
fundingPoint, err := chanvalidate.Validate(
&chanvalidate.Context{
Locator: &chanvalidate.ShortChanIDChanLocator{
ID: scid,
},
MultiSigPkScript: fundingPkScript,
FundingTx: fundingTx,
},
)
if err != nil {
// Mark the edge as a zombie so we won't try to re-validate it
// on start up.
zErr := d.cfg.Graph.MarkZombieEdge(scid.ToUint64())
if zErr != nil {
return wire.OutPoint{}, 0, nil, zErr
}
return wire.OutPoint{}, 0, nil, fmt.Errorf("%w: %w",
ErrInvalidFundingOutput, err)
}
// Now that we have the funding outpoint of the channel, ensure
// that it hasn't yet been spent. If so, then this channel has
// been closed so we'll ignore it.
chanUtxo, err := d.cfg.ChainIO.GetUtxo(
fundingPoint, fundingPkScript, scid.BlockHeight, d.quit,
)
if err != nil {
if errors.Is(err, btcwallet.ErrOutputSpent) {
zErr := d.cfg.Graph.MarkZombieEdge(scid.ToUint64())
if zErr != nil {
return wire.OutPoint{}, 0, nil, zErr
}
}
return wire.OutPoint{}, 0, nil, fmt.Errorf("%w: unable to "+
"fetch utxo for chan_id=%v, chan_point=%v: %w",
ErrChannelSpent, scid.ToUint64(), fundingPoint, err)
}
return *fundingPoint, btcutil.Amount(chanUtxo.Value), fundingPkScript,
nil
}
// makeFundingScript is used to make the funding script for both segwit v0 and
// segwit v1 (taproot) channels.
func makeFundingScript(bitcoinKey1, bitcoinKey2 []byte,
features *lnwire.RawFeatureVector,
tapscriptRoot fn.Option[chainhash.Hash]) ([]byte, error) {
legacyFundingScript := func() ([]byte, error) {
witnessScript, err := input.GenMultiSigScript(
bitcoinKey1, bitcoinKey2,
)
if err != nil {
return nil, err
}
pkScript, err := input.WitnessScriptHash(witnessScript)
if err != nil {
return nil, err
}
return pkScript, nil
}
if features.IsEmpty() {
return legacyFundingScript()
}
chanFeatureBits := lnwire.NewFeatureVector(features, lnwire.Features)
if chanFeatureBits.HasFeature(
lnwire.SimpleTaprootChannelsOptionalStaging,
) {
pubKey1, err := btcec.ParsePubKey(bitcoinKey1)
if err != nil {
return nil, err
}
pubKey2, err := btcec.ParsePubKey(bitcoinKey2)
if err != nil {
return nil, err
}
fundingScript, _, err := input.GenTaprootFundingScript(
pubKey1, pubKey2, 0, tapscriptRoot,
)
if err != nil {
return nil, err
}
// TODO(roasbeef): add tapscript root to gossip v1.5
return fundingScript, nil
}
return legacyFundingScript()
}
package discovery
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("DISC", nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package discovery
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// messageStoreBucket is a key used to create a top level bucket in the
// gossiper database, used for storing messages that are to be sent to
// peers. Upon restarts, these messages will be read and resent to their
// respective peers.
//
// maps:
// pubKey (33 bytes) + msgShortChanID (8 bytes) + msgType (2 bytes) -> msg
messageStoreBucket = []byte("message-store")
// ErrUnsupportedMessage is an error returned when we attempt to add a
// message to the store that is not supported.
ErrUnsupportedMessage = errors.New("unsupported message type")
// ErrCorruptedMessageStore indicates that the on-disk bucketing
// structure has altered since the gossip message store instance was
// initialized.
ErrCorruptedMessageStore = errors.New("gossip message store has been " +
"corrupted")
)
// GossipMessageStore is a store responsible for storing gossip messages which
// we should reliably send to our peers.
type GossipMessageStore interface {
// AddMessage adds a message to the store for this peer.
AddMessage(lnwire.Message, [33]byte) error
// DeleteMessage deletes a message from the store for this peer.
DeleteMessage(lnwire.Message, [33]byte) error
// Messages returns the total set of messages that exist within the
// store for all peers.
Messages() (map[[33]byte][]lnwire.Message, error)
// Peers returns the public key of all peers with messages within the
// store.
Peers() (map[[33]byte]struct{}, error)
// MessagesForPeer returns the set of messages that exists within the
// store for the given peer.
MessagesForPeer([33]byte) ([]lnwire.Message, error)
}
// MessageStore is an implementation of the GossipMessageStore interface backed
// by a channeldb instance. By design, this store will only keep the latest
// version of a message (like in the case of multiple ChannelUpdate's) for a
// channel with a peer.
type MessageStore struct {
db kvdb.Backend
}
// A compile-time assertion to ensure messageStore implements the
// GossipMessageStore interface.
var _ GossipMessageStore = (*MessageStore)(nil)
// NewMessageStore creates a new message store backed by a channeldb instance.
func NewMessageStore(db kvdb.Backend) (*MessageStore, error) {
err := kvdb.Batch(db, func(tx kvdb.RwTx) error {
_, err := tx.CreateTopLevelBucket(messageStoreBucket)
return err
})
if err != nil {
return nil, fmt.Errorf("unable to create required buckets: %w",
err)
}
return &MessageStore{db}, nil
}
// msgShortChanID retrieves the short channel ID of the message.
func msgShortChanID(msg lnwire.Message) (lnwire.ShortChannelID, error) {
var shortChanID lnwire.ShortChannelID
switch msg := msg.(type) {
case *lnwire.AnnounceSignatures1:
shortChanID = msg.ShortChannelID
case *lnwire.ChannelUpdate1:
shortChanID = msg.ShortChannelID
default:
return shortChanID, ErrUnsupportedMessage
}
return shortChanID, nil
}
// messageStoreKey constructs the database key for the message to be stored.
func messageStoreKey(msg lnwire.Message, peerPubKey [33]byte) ([]byte, error) {
shortChanID, err := msgShortChanID(msg)
if err != nil {
return nil, err
}
var k [33 + 8 + 2]byte
copy(k[:33], peerPubKey[:])
binary.BigEndian.PutUint64(k[33:41], shortChanID.ToUint64())
binary.BigEndian.PutUint16(k[41:43], uint16(msg.MsgType()))
return k[:], nil
}
// AddMessage adds a message to the store for this peer.
func (s *MessageStore) AddMessage(msg lnwire.Message, peerPubKey [33]byte) error {
log.Tracef("Adding message of type %v to store for peer %x",
msg.MsgType(), peerPubKey)
// Construct the key for which we'll find this message with in the
// store.
msgKey, err := messageStoreKey(msg, peerPubKey)
if err != nil {
return err
}
// Serialize the message with its wire encoding.
var b bytes.Buffer
if _, err := lnwire.WriteMessage(&b, msg, 0); err != nil {
return err
}
return kvdb.Batch(s.db, func(tx kvdb.RwTx) error {
messageStore := tx.ReadWriteBucket(messageStoreBucket)
if messageStore == nil {
return ErrCorruptedMessageStore
}
return messageStore.Put(msgKey, b.Bytes())
})
}
// DeleteMessage deletes a message from the store for this peer.
func (s *MessageStore) DeleteMessage(msg lnwire.Message,
peerPubKey [33]byte) error {
log.Tracef("Deleting message of type %v from store for peer %x",
msg.MsgType(), peerPubKey)
// Construct the key for which we'll find this message with in the
// store.
msgKey, err := messageStoreKey(msg, peerPubKey)
if err != nil {
return err
}
return kvdb.Batch(s.db, func(tx kvdb.RwTx) error {
messageStore := tx.ReadWriteBucket(messageStoreBucket)
if messageStore == nil {
return ErrCorruptedMessageStore
}
// In the event that we're attempting to delete a ChannelUpdate
// from the store, we'll make sure that we're actually deleting
// the correct one as it can be overwritten.
if msg, ok := msg.(*lnwire.ChannelUpdate1); ok {
// Deleting a value from a bucket that doesn't exist
// acts as a NOP, so we'll return if a message doesn't
// exist under this key.
v := messageStore.Get(msgKey)
if v == nil {
return nil
}
dbMsg, err := lnwire.ReadMessage(bytes.NewReader(v), 0)
if err != nil {
return err
}
// If the timestamps don't match, then the update stored
// should be the latest one, so we'll avoid deleting it.
m, ok := dbMsg.(*lnwire.ChannelUpdate1)
if !ok {
return fmt.Errorf("expected "+
"*lnwire.ChannelUpdate1, got: %T",
dbMsg)
}
if msg.Timestamp != m.Timestamp {
return nil
}
}
return messageStore.Delete(msgKey)
})
}
// readMessage reads a message from its serialized form and ensures its
// supported by the current version of the message store.
func readMessage(msgBytes []byte) (lnwire.Message, error) {
msg, err := lnwire.ReadMessage(bytes.NewReader(msgBytes), 0)
if err != nil {
return nil, err
}
// Check if the message is supported by the store. We can reuse the
// check for ShortChannelID as its a dependency on messages stored.
if _, err := msgShortChanID(msg); err != nil {
return nil, err
}
return msg, nil
}
// Messages returns the total set of messages that exist within the store for
// all peers.
func (s *MessageStore) Messages() (map[[33]byte][]lnwire.Message, error) {
var msgs map[[33]byte][]lnwire.Message
err := kvdb.View(s.db, func(tx kvdb.RTx) error {
messageStore := tx.ReadBucket(messageStoreBucket)
if messageStore == nil {
return ErrCorruptedMessageStore
}
return messageStore.ForEach(func(k, v []byte) error {
var pubKey [33]byte
copy(pubKey[:], k[:33])
// Deserialize the message from its raw bytes and filter
// out any which are not currently supported by the
// store.
msg, err := readMessage(v)
if err == ErrUnsupportedMessage {
return nil
}
if err != nil {
return err
}
msgs[pubKey] = append(msgs[pubKey], msg)
return nil
})
}, func() {
msgs = make(map[[33]byte][]lnwire.Message)
})
if err != nil {
return nil, err
}
return msgs, nil
}
// MessagesForPeer returns the set of messages that exists within the store for
// the given peer.
func (s *MessageStore) MessagesForPeer(
peerPubKey [33]byte) ([]lnwire.Message, error) {
var msgs []lnwire.Message
err := kvdb.View(s.db, func(tx kvdb.RTx) error {
messageStore := tx.ReadBucket(messageStoreBucket)
if messageStore == nil {
return ErrCorruptedMessageStore
}
c := messageStore.ReadCursor()
k, v := c.Seek(peerPubKey[:])
for ; bytes.HasPrefix(k, peerPubKey[:]); k, v = c.Next() {
// Deserialize the message from its raw bytes and filter
// out any which are not currently supported by the
// store.
msg, err := readMessage(v)
if err == ErrUnsupportedMessage {
continue
}
if err != nil {
return err
}
msgs = append(msgs, msg)
}
return nil
}, func() {
msgs = nil
})
if err != nil {
return nil, err
}
return msgs, nil
}
// Peers returns the public key of all peers with messages within the store.
func (s *MessageStore) Peers() (map[[33]byte]struct{}, error) {
var peers map[[33]byte]struct{}
err := kvdb.View(s.db, func(tx kvdb.RTx) error {
messageStore := tx.ReadBucket(messageStoreBucket)
if messageStore == nil {
return ErrCorruptedMessageStore
}
return messageStore.ForEach(func(k, _ []byte) error {
var pubKey [33]byte
copy(pubKey[:], k[:33])
peers[pubKey] = struct{}{}
return nil
})
}, func() {
peers = make(map[[33]byte]struct{})
})
if err != nil {
return nil, err
}
return peers, nil
}
package discovery
import (
"sync"
"github.com/lightningnetwork/lnd/lnpeer"
"github.com/lightningnetwork/lnd/lnwire"
)
// reliableSenderCfg contains all of necessary items for the reliableSender to
// carry out its duties.
type reliableSenderCfg struct {
// NotifyWhenOnline is a function that allows the gossiper to be
// notified when a certain peer comes online, allowing it to
// retry sending a peer message.
//
// NOTE: The peerChan channel must be buffered.
NotifyWhenOnline func(peerPubKey [33]byte, peerChan chan<- lnpeer.Peer)
// NotifyWhenOffline is a function that allows the gossiper to be
// notified when a certain peer disconnects, allowing it to request a
// notification for when it reconnects.
NotifyWhenOffline func(peerPubKey [33]byte) <-chan struct{}
// MessageStore is a persistent storage of gossip messages which we will
// use to determine which messages need to be resent for a given peer.
MessageStore GossipMessageStore
// IsMsgStale determines whether a message retrieved from the backing
// MessageStore is seen as stale by the current graph.
IsMsgStale func(lnwire.Message) bool
}
// peerManager contains the set of channels required for the peerHandler to
// properly carry out its duties.
type peerManager struct {
// msgs is the channel through which messages will be streamed to the
// handler in order to send the message to the peer while they're
// online.
msgs chan lnwire.Message
// done is a channel that will be closed to signal that the handler for
// the given peer has been torn down for whatever reason.
done chan struct{}
}
// reliableSender is a small subsystem of the gossiper used to reliably send
// gossip messages to peers.
type reliableSender struct {
start sync.Once
stop sync.Once
cfg reliableSenderCfg
// activePeers keeps track of whether a peerHandler exists for a given
// peer. A peerHandler is tasked with handling requests for messages
// that should be reliably sent to peers while also taking into account
// the peer's connection lifecycle.
activePeers map[[33]byte]peerManager
activePeersMtx sync.Mutex
wg sync.WaitGroup
quit chan struct{}
}
// newReliableSender returns a new reliableSender backed by the given config.
func newReliableSender(cfg *reliableSenderCfg) *reliableSender {
return &reliableSender{
cfg: *cfg,
activePeers: make(map[[33]byte]peerManager),
quit: make(chan struct{}),
}
}
// Start spawns message handlers for any peers with pending messages.
func (s *reliableSender) Start() error {
var err error
s.start.Do(func() {
err = s.resendPendingMsgs()
})
return err
}
// Stop halts the reliable sender from sending messages to peers.
func (s *reliableSender) Stop() {
s.stop.Do(func() {
log.Debugf("reliableSender is stopping")
defer log.Debugf("reliableSender stopped")
close(s.quit)
s.wg.Wait()
})
}
// sendMessage constructs a request to send a message reliably to a peer. In the
// event that the peer is currently offline, this will only write the message to
// disk. Once the peer reconnects, this message, along with any others pending,
// will be sent to the peer.
func (s *reliableSender) sendMessage(msg lnwire.Message, peerPubKey [33]byte) error {
// We'll start by persisting the message to disk. This allows us to
// resend the message upon restarts and peer reconnections.
if err := s.cfg.MessageStore.AddMessage(msg, peerPubKey); err != nil {
return err
}
// Then, we'll spawn a peerHandler for this peer to handle resending its
// pending messages while taking into account its connection lifecycle.
spawnHandler:
msgHandler, ok := s.spawnPeerHandler(peerPubKey)
// If the handler wasn't previously active, we can exit now as we know
// that the message will be sent once the peer online notification is
// received. This prevents us from potentially sending the message
// twice.
if !ok {
return nil
}
// Otherwise, we'll attempt to stream the message to the handler.
// There's a subtle race condition where the handler can be torn down
// due to all of the messages sent being stale, so we'll handle this
// gracefully by spawning another one to prevent blocking.
select {
case msgHandler.msgs <- msg:
case <-msgHandler.done:
goto spawnHandler
case <-s.quit:
return ErrGossiperShuttingDown
}
return nil
}
// spawnPeerMsgHandler spawns a peerHandler for the given peer if there isn't
// one already active. The boolean returned signals whether there was already
// one active or not.
func (s *reliableSender) spawnPeerHandler(
peerPubKey [33]byte) (peerManager, bool) {
s.activePeersMtx.Lock()
msgHandler, ok := s.activePeers[peerPubKey]
if !ok {
msgHandler = peerManager{
msgs: make(chan lnwire.Message),
done: make(chan struct{}),
}
s.activePeers[peerPubKey] = msgHandler
}
s.activePeersMtx.Unlock()
// If this is a newly initiated peerManager, we will create a
// peerHandler.
if !ok {
s.wg.Add(1)
go s.peerHandler(msgHandler, peerPubKey)
}
return msgHandler, ok
}
// peerHandler is responsible for handling our reliable message send requests
// for a given peer while also taking into account the peer's connection
// lifecycle. Any messages that are attempted to be sent while the peer is
// offline will be queued and sent once the peer reconnects.
//
// NOTE: This must be run as a goroutine.
func (s *reliableSender) peerHandler(peerMgr peerManager, peerPubKey [33]byte) {
defer s.wg.Done()
// We'll start by requesting a notification for when the peer
// reconnects.
peerChan := make(chan lnpeer.Peer, 1)
waitUntilOnline:
log.Debugf("Requesting online notification for peer=%x", peerPubKey)
s.cfg.NotifyWhenOnline(peerPubKey, peerChan)
var peer lnpeer.Peer
out:
for {
select {
// While we're waiting, we'll also consume any messages that
// must be sent to prevent blocking the caller. These can be
// ignored for now since the peer is currently offline. Once
// they reconnect, the messages will be sent since they should
// have been persisted to disk.
case msg := <-peerMgr.msgs:
// Retrieve the short channel ID for which this message
// applies for logging purposes. The error can be
// ignored as the store can only contain messages which
// have a ShortChannelID field.
shortChanID, _ := msgShortChanID(msg)
log.Debugf("Received request to send %v message for "+
"channel=%v while peer=%x is offline",
msg.MsgType(), shortChanID, peerPubKey)
case peer = <-peerChan:
break out
case <-s.quit:
return
}
}
log.Debugf("Peer=%x is now online, proceeding to send pending messages",
peerPubKey)
// Once we detect the peer has reconnected, we'll also request a
// notification for when they disconnect. We'll use this to make sure
// they haven't disconnected (in the case of a flappy peer, etc.) by the
// time we attempt to send them the pending messages.
log.Debugf("Requesting offline notification for peer=%x", peerPubKey)
offlineChan := s.cfg.NotifyWhenOffline(peerPubKey)
pendingMsgs, err := s.cfg.MessageStore.MessagesForPeer(peerPubKey)
if err != nil {
log.Errorf("Unable to retrieve pending messages for peer %x: %v",
peerPubKey, err)
return
}
// With the peer online, we can now proceed to send our pending messages
// for them.
for _, msg := range pendingMsgs {
// Retrieve the short channel ID for which this message applies
// for logging purposes. The error can be ignored as the store
// can only contain messages which have a ShortChannelID field.
shortChanID, _ := msgShortChanID(msg)
// Ensure the peer is still online right before sending the
// message.
select {
case <-offlineChan:
goto waitUntilOnline
default:
}
if err := peer.SendMessage(false, msg); err != nil {
log.Errorf("Unable to send %v message for channel=%v "+
"to %x: %v", msg.MsgType(), shortChanID,
peerPubKey, err)
goto waitUntilOnline
}
log.Debugf("Successfully sent %v message for channel=%v with "+
"peer=%x upon reconnection", msg.MsgType(), shortChanID,
peerPubKey)
// Now that the message has at least been sent once, we can
// check whether it's stale. This guarantees that
// AnnounceSignatures are sent at least once if we happen to
// already have signatures for both parties.
if s.cfg.IsMsgStale(msg) {
err := s.cfg.MessageStore.DeleteMessage(msg, peerPubKey)
if err != nil {
log.Errorf("Unable to remove stale %v message "+
"for channel=%v with peer %x: %v",
msg.MsgType(), shortChanID, peerPubKey,
err)
continue
}
log.Debugf("Removed stale %v message for channel=%v "+
"with peer=%x", msg.MsgType(), shortChanID,
peerPubKey)
}
}
// If all of our messages were stale, then there's no need for this
// handler to continue running, so we can exit now.
pendingMsgs, err = s.cfg.MessageStore.MessagesForPeer(peerPubKey)
if err != nil {
log.Errorf("Unable to retrieve pending messages for peer %x: %v",
peerPubKey, err)
return
}
if len(pendingMsgs) == 0 {
log.Debugf("No pending messages left for peer=%x", peerPubKey)
s.activePeersMtx.Lock()
delete(s.activePeers, peerPubKey)
s.activePeersMtx.Unlock()
close(peerMgr.done)
return
}
// Once the pending messages are sent, we can continue to send any
// future messages while the peer remains connected.
for {
select {
case msg := <-peerMgr.msgs:
// Retrieve the short channel ID for which this message
// applies for logging purposes. The error can be
// ignored as the store can only contain messages which
// have a ShortChannelID field.
shortChanID, _ := msgShortChanID(msg)
if err := peer.SendMessage(false, msg); err != nil {
log.Errorf("Unable to send %v message for "+
"channel=%v to %x: %v", msg.MsgType(),
shortChanID, peerPubKey, err)
}
log.Debugf("Successfully sent %v message for "+
"channel=%v with peer=%x", msg.MsgType(),
shortChanID, peerPubKey)
case <-offlineChan:
goto waitUntilOnline
case <-s.quit:
return
}
}
}
// resendPendingMsgs retrieves and sends all of the messages within the message
// store that should be reliably sent to their respective peers.
func (s *reliableSender) resendPendingMsgs() error {
// Fetch all of the peers for which we have pending messages for and
// spawn a peerMsgHandler for each. Once the peer is seen as online, all
// of the pending messages will be sent.
peers, err := s.cfg.MessageStore.Peers()
if err != nil {
return err
}
for peer := range peers {
s.spawnPeerHandler(peer)
}
return nil
}
package discovery
import (
"context"
"errors"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/lnpeer"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/ticker"
"golang.org/x/time/rate"
)
const (
// DefaultSyncerRotationInterval is the default interval in which we'll
// rotate a single active syncer.
DefaultSyncerRotationInterval = 20 * time.Minute
// DefaultHistoricalSyncInterval is the default interval in which we'll
// force a historical sync to ensure we have as much of the public
// network as possible.
DefaultHistoricalSyncInterval = time.Hour
// filterSemaSize is the capacity of gossipFilterSema.
filterSemaSize = 5
// DefaultMsgBytesBurst is the allotted burst in bytes we'll permit.
// This is the most that can be sent in a given go. Requests beyond
// this, will block indefinitely. Once tokens (bytes are depleted),
// they'll be refilled at the DefaultMsgBytesPerSecond rate.
DefaultMsgBytesBurst = 2 * 100 * 1_024
// DefaultMsgBytesPerSecond is the max bytes/s we'll permit for outgoing
// messages. Once tokens (bytes) have been taken from the bucket,
// they'll be refilled at this rate.
DefaultMsgBytesPerSecond = 100 * 1_024
// assumedMsgSize is the assumed size of a message if we can't compute
// its serialized size. This comes out to 1 KB.
assumedMsgSize = 1_024
)
var (
// ErrSyncManagerExiting is an error returned when we attempt to
// start/stop a gossip syncer for a connected/disconnected peer, but the
// SyncManager has already been stopped.
ErrSyncManagerExiting = errors.New("sync manager exiting")
)
// newSyncer in an internal message we'll use within the SyncManager to signal
// that we should create a GossipSyncer for a newly connected peer.
type newSyncer struct {
// peer is the newly connected peer.
peer lnpeer.Peer
// doneChan serves as a signal to the caller that the SyncManager's
// internal state correctly reflects the stale active syncer.
doneChan chan struct{}
}
// staleSyncer is an internal message we'll use within the SyncManager to signal
// that a peer has disconnected and its GossipSyncer should be removed.
type staleSyncer struct {
// peer is the peer that has disconnected.
peer route.Vertex
// doneChan serves as a signal to the caller that the SyncManager's
// internal state correctly reflects the stale active syncer. This is
// needed to ensure we always create a new syncer for a flappy peer
// after they disconnect if they happened to be an active syncer.
doneChan chan struct{}
}
// SyncManagerCfg contains all of the dependencies required for the SyncManager
// to carry out its duties.
type SyncManagerCfg struct {
// ChainHash is a hash that indicates the specific network of the active
// chain.
ChainHash chainhash.Hash
// ChanSeries is an interface that provides access to a time series view
// of the current known channel graph. Each GossipSyncer enabled peer
// will utilize this in order to create and respond to channel graph
// time series queries.
ChanSeries ChannelGraphTimeSeries
// NumActiveSyncers is the number of peers for which we should have
// active syncers with. After reaching NumActiveSyncers, any future
// gossip syncers will be passive.
NumActiveSyncers int
// NoTimestampQueries will prevent the GossipSyncer from querying
// timestamps of announcement messages from the peer and from responding
// to timestamp queries
NoTimestampQueries bool
// RotateTicker is a ticker responsible for notifying the SyncManager
// when it should rotate its active syncers. A single active syncer with
// a chansSynced state will be exchanged for a passive syncer in order
// to ensure we don't keep syncing with the same peers.
RotateTicker ticker.Ticker
// HistoricalSyncTicker is a ticker responsible for notifying the
// SyncManager when it should attempt a historical sync with a gossip
// sync peer.
HistoricalSyncTicker ticker.Ticker
// IgnoreHistoricalFilters will prevent syncers from replying with
// historical data when the remote peer sets a gossip_timestamp_range.
// This prevents ranges with old start times from causing us to dump the
// graph on connect.
IgnoreHistoricalFilters bool
// BestHeight returns the latest height known of the chain.
BestHeight func() uint32
// PinnedSyncers is a set of peers that will always transition to
// ActiveSync upon connection. These peers will never transition to
// PassiveSync.
PinnedSyncers PinnedSyncers
// IsStillZombieChannel takes the timestamps of the latest channel
// updates for a channel and returns true if the channel should be
// considered a zombie based on these timestamps.
IsStillZombieChannel func(time.Time, time.Time) bool
// AllotedMsgBytesPerSecond is the allotted bandwidth rate, expressed in
// bytes/second that the gossip manager can consume. Once we exceed this
// rate, message sending will block until we're below the rate.
AllotedMsgBytesPerSecond uint64
// AllotedMsgBytesBurst is the amount of burst bytes we'll permit, if
// we've exceeded the hard upper limit.
AllotedMsgBytesBurst uint64
}
// SyncManager is a subsystem of the gossiper that manages the gossip syncers
// for peers currently connected. When a new peer is connected, the manager will
// create its accompanying gossip syncer and determine whether it should have an
// ActiveSync or PassiveSync sync type based on how many other gossip syncers
// are currently active. Any ActiveSync gossip syncers are started in a
// round-robin manner to ensure we're not syncing with multiple peers at the
// same time. The first GossipSyncer registered with the SyncManager will
// attempt a historical sync to ensure we have as much of the public channel
// graph as possible.
type SyncManager struct {
// initialHistoricalSyncCompleted serves as a barrier when initializing
// new active GossipSyncers. If 0, the initial historical sync has not
// completed, so we'll defer initializing any active GossipSyncers. If
// 1, then we can transition the GossipSyncer immediately. We set up
// this barrier to ensure we have most of the graph before attempting to
// accept new updates at tip.
//
// NOTE: This must be used atomically.
initialHistoricalSyncCompleted int32
start sync.Once
stop sync.Once
cfg SyncManagerCfg
// newSyncers is a channel we'll use to process requests to create
// GossipSyncers for newly connected peers.
newSyncers chan *newSyncer
// staleSyncers is a channel we'll use to process requests to tear down
// GossipSyncers for disconnected peers.
staleSyncers chan *staleSyncer
// syncersMu guards the read and write access to the activeSyncers and
// inactiveSyncers maps below.
syncersMu sync.Mutex
// activeSyncers is the set of all syncers for which we are currently
// receiving graph updates from. The number of possible active syncers
// is bounded by NumActiveSyncers.
activeSyncers map[route.Vertex]*GossipSyncer
// inactiveSyncers is the set of all syncers for which we are not
// currently receiving new graph updates from.
inactiveSyncers map[route.Vertex]*GossipSyncer
// pinnedActiveSyncers is the set of all syncers which are pinned into
// an active sync. Pinned peers performan an initial historical sync on
// each connection and will continue to receive graph updates for the
// duration of the connection.
pinnedActiveSyncers map[route.Vertex]*GossipSyncer
// gossipFilterSema contains semaphores for the gossip timestamp
// queries.
gossipFilterSema chan struct{}
// rateLimiter dictates the frequency with which we will reply to gossip
// queries from a peer. This is used to delay responses to peers to
// prevent DOS vulnerabilities if they are spamming with an unreasonable
// number of queries.
rateLimiter *rate.Limiter
wg sync.WaitGroup
quit chan struct{}
}
// newSyncManager constructs a new SyncManager backed by the given config.
func newSyncManager(cfg *SyncManagerCfg) *SyncManager {
filterSema := make(chan struct{}, filterSemaSize)
for i := 0; i < filterSemaSize; i++ {
filterSema <- struct{}{}
}
bytesPerSecond := cfg.AllotedMsgBytesPerSecond
if bytesPerSecond == 0 {
bytesPerSecond = DefaultMsgBytesPerSecond
}
bytesBurst := cfg.AllotedMsgBytesBurst
if bytesBurst == 0 {
bytesBurst = DefaultMsgBytesBurst
}
// We'll use this rate limiter to limit our total outbound bandwidth for
// gossip queries peers.
rateLimiter := rate.NewLimiter(
rate.Limit(bytesPerSecond), int(bytesBurst),
)
return &SyncManager{
cfg: *cfg,
rateLimiter: rateLimiter,
newSyncers: make(chan *newSyncer),
staleSyncers: make(chan *staleSyncer),
activeSyncers: make(
map[route.Vertex]*GossipSyncer, cfg.NumActiveSyncers,
),
inactiveSyncers: make(map[route.Vertex]*GossipSyncer),
pinnedActiveSyncers: make(
map[route.Vertex]*GossipSyncer, len(cfg.PinnedSyncers),
),
gossipFilterSema: filterSema,
quit: make(chan struct{}),
}
}
// Start starts the SyncManager in order to properly carry out its duties.
func (m *SyncManager) Start() {
m.start.Do(func() {
m.wg.Add(1)
go m.syncerHandler()
})
}
// Stop stops the SyncManager from performing its duties.
func (m *SyncManager) Stop() {
m.stop.Do(func() {
log.Debugf("SyncManager is stopping")
defer log.Debugf("SyncManager stopped")
close(m.quit)
m.wg.Wait()
for _, syncer := range m.inactiveSyncers {
syncer.Stop()
}
for _, syncer := range m.activeSyncers {
syncer.Stop()
}
})
}
// syncerHandler is the SyncManager's main event loop responsible for:
//
// 1. Creating and tearing down GossipSyncers for connected/disconnected peers.
// 2. Finding new peers to receive graph updates from to ensure we don't only
// receive them from the same set of peers.
// 3. Finding new peers to force a historical sync with to ensure we have as
// much of the public network as possible.
//
// NOTE: This must be run as a goroutine.
func (m *SyncManager) syncerHandler() {
defer m.wg.Done()
m.cfg.RotateTicker.Resume()
defer m.cfg.RotateTicker.Stop()
defer m.cfg.HistoricalSyncTicker.Stop()
var (
// initialHistoricalSyncer is the syncer we are currently
// performing an initial historical sync with.
initialHistoricalSyncer *GossipSyncer
// initialHistoricalSyncSignal is a signal that will fire once
// the initial historical sync has been completed. This is
// crucial to ensure that another historical sync isn't
// attempted just because the initialHistoricalSyncer was
// disconnected.
initialHistoricalSyncSignal chan struct{}
)
setInitialHistoricalSyncer := func(s *GossipSyncer) {
initialHistoricalSyncer = s
initialHistoricalSyncSignal = s.ResetSyncedSignal()
// Restart the timer for our new historical sync peer. This will
// ensure that all initial syncers receive an equivalent
// duration before attempting the next sync. Without doing so we
// might attempt two historical sync back to back if a peer
// disconnects just before the ticker fires.
m.cfg.HistoricalSyncTicker.Pause()
m.cfg.HistoricalSyncTicker.Resume()
}
for {
select {
// A new peer has been connected, so we'll create its
// accompanying GossipSyncer.
case newSyncer := <-m.newSyncers:
// If we already have a syncer, then we'll exit early as
// we don't want to override it.
if _, ok := m.GossipSyncer(newSyncer.peer.PubKey()); ok {
close(newSyncer.doneChan)
continue
}
s := m.createGossipSyncer(newSyncer.peer)
isPinnedSyncer := m.isPinnedSyncer(s)
// attemptHistoricalSync determines whether we should
// attempt an initial historical sync when a new peer
// connects.
attemptHistoricalSync := false
m.syncersMu.Lock()
switch {
// For pinned syncers, we will immediately transition
// the peer into an active (pinned) sync state.
case isPinnedSyncer:
attemptHistoricalSync = true
s.setSyncType(PinnedSync)
s.setSyncState(syncerIdle)
m.pinnedActiveSyncers[s.cfg.peerPub] = s
// Regardless of whether the initial historical sync
// has completed, we'll re-trigger a historical sync if
// we no longer have any syncers. This might be
// necessary if we lost all our peers at one point, and
// now we finally have one again.
case len(m.activeSyncers) == 0 &&
len(m.inactiveSyncers) == 0:
attemptHistoricalSync =
m.cfg.NumActiveSyncers > 0
fallthrough
// If we've exceeded our total number of active syncers,
// we'll initialize this GossipSyncer as passive.
case len(m.activeSyncers) >= m.cfg.NumActiveSyncers:
fallthrough
// If the initial historical sync has yet to complete,
// then we'll declare it as passive and attempt to
// transition it when the initial historical sync
// completes.
case !m.IsGraphSynced():
s.setSyncType(PassiveSync)
m.inactiveSyncers[s.cfg.peerPub] = s
// The initial historical sync has completed, so we can
// immediately start the GossipSyncer as active.
default:
s.setSyncType(ActiveSync)
m.activeSyncers[s.cfg.peerPub] = s
}
m.syncersMu.Unlock()
s.Start()
// Once we create the GossipSyncer, we'll signal to the
// caller that they can proceed since the SyncManager's
// internal state has been updated.
close(newSyncer.doneChan)
// We'll force a historical sync with the first peer we
// connect to, to ensure we get as much of the graph as
// possible.
if !attemptHistoricalSync {
continue
}
log.Debugf("Attempting initial historical sync with "+
"GossipSyncer(%x)", s.cfg.peerPub)
if err := s.historicalSync(); err != nil {
log.Errorf("Unable to attempt initial "+
"historical sync with "+
"GossipSyncer(%x): %v", s.cfg.peerPub,
err)
continue
}
// Once the historical sync has started, we'll get a
// keep track of the corresponding syncer to properly
// handle disconnects. We'll also use a signal to know
// when the historical sync completed.
if !isPinnedSyncer {
setInitialHistoricalSyncer(s)
}
// An existing peer has disconnected, so we'll tear down its
// corresponding GossipSyncer.
case staleSyncer := <-m.staleSyncers:
// Once the corresponding GossipSyncer has been stopped
// and removed, we'll signal to the caller that they can
// proceed since the SyncManager's internal state has
// been updated.
m.removeGossipSyncer(staleSyncer.peer)
close(staleSyncer.doneChan)
// If we don't have an initialHistoricalSyncer, or we do
// but it is not the peer being disconnected, then we
// have nothing left to do and can proceed.
switch {
case initialHistoricalSyncer == nil:
fallthrough
case staleSyncer.peer != initialHistoricalSyncer.cfg.peerPub:
fallthrough
case m.cfg.NumActiveSyncers == 0:
continue
}
// Otherwise, our initialHistoricalSyncer corresponds to
// the peer being disconnected, so we'll have to find a
// replacement.
log.Debug("Finding replacement for initial " +
"historical sync")
s := m.forceHistoricalSync()
if s == nil {
log.Debug("No eligible replacement found " +
"for initial historical sync")
continue
}
log.Debugf("Replaced initial historical "+
"GossipSyncer(%v) with GossipSyncer(%x)",
staleSyncer.peer, s.cfg.peerPub)
setInitialHistoricalSyncer(s)
// Our initial historical sync signal has completed, so we'll
// nil all of the relevant fields as they're no longer needed.
case <-initialHistoricalSyncSignal:
initialHistoricalSyncer = nil
initialHistoricalSyncSignal = nil
log.Debug("Initial historical sync completed")
// With the initial historical sync complete, we can
// begin receiving new graph updates at tip. We'll
// determine whether we can have any more active
// GossipSyncers. If we do, we'll randomly select some
// that are currently passive to transition.
m.syncersMu.Lock()
numActiveLeft := m.cfg.NumActiveSyncers - len(m.activeSyncers)
if numActiveLeft <= 0 {
m.syncersMu.Unlock()
continue
}
// We may not even have enough inactive syncers to be
// transitted. In that case, we will transit all the
// inactive syncers.
if len(m.inactiveSyncers) < numActiveLeft {
numActiveLeft = len(m.inactiveSyncers)
}
log.Debugf("Attempting to transition %v passive "+
"GossipSyncers to active", numActiveLeft)
for i := 0; i < numActiveLeft; i++ {
chooseRandomSyncer(
m.inactiveSyncers, m.transitionPassiveSyncer,
)
}
m.syncersMu.Unlock()
// Our RotateTicker has ticked, so we'll attempt to rotate a
// single active syncer with a passive one.
case <-m.cfg.RotateTicker.Ticks():
m.rotateActiveSyncerCandidate()
// Our HistoricalSyncTicker has ticked, so we'll randomly select
// a peer and force a historical sync with them.
case <-m.cfg.HistoricalSyncTicker.Ticks():
// To be extra cautious, gate the forceHistoricalSync
// call such that it can only execute if we are
// configured to have a non-zero number of sync peers.
// This way even if the historical sync ticker manages
// to tick we can be sure that a historical sync won't
// accidentally begin.
if m.cfg.NumActiveSyncers == 0 {
continue
}
// If we don't have a syncer available we have nothing
// to do.
s := m.forceHistoricalSync()
if s == nil {
continue
}
// If we've already completed a historical sync, we'll
// skip setting the initial historical syncer.
if m.IsGraphSynced() {
continue
}
// Otherwise, we'll track the peer we've performed a
// historical sync with in order to handle the case
// where our previous historical sync peer did not
// respond to our queries and we haven't ingested as
// much of the graph as we should.
setInitialHistoricalSyncer(s)
case <-m.quit:
return
}
}
}
// isPinnedSyncer returns true if the passed GossipSyncer is one of our pinned
// sync peers.
func (m *SyncManager) isPinnedSyncer(s *GossipSyncer) bool {
_, isPinnedSyncer := m.cfg.PinnedSyncers[s.cfg.peerPub]
return isPinnedSyncer
}
// deriveRateLimitReservation will take the current message and derive a
// reservation that can be used to wait on the rate limiter.
func (m *SyncManager) deriveRateLimitReservation(msg lnwire.Message,
) (*rate.Reservation, error) {
var (
msgSize uint32
err error
)
// Figure out the serialized size of the message. If we can't easily
// compute it, then we'll used the assumed msg size.
if sMsg, ok := msg.(lnwire.SizeableMessage); ok {
msgSize, err = sMsg.SerializedSize()
if err != nil {
return nil, err
}
} else {
log.Warnf("Unable to compute serialized size of %T", msg)
msgSize = assumedMsgSize
}
return m.rateLimiter.ReserveN(time.Now(), int(msgSize)), nil
}
// waitMsgDelay takes a delay, and waits until it has finished.
func (m *SyncManager) waitMsgDelay(ctx context.Context, peerPub [33]byte,
limitReservation *rate.Reservation) error {
// If we've already replied a handful of times, we will start to delay
// responses back to the remote peer. This can help prevent DOS attacks
// where the remote peer spams us endlessly.
//
// We skip checking for reservation.OK() here, as during config
// validation, we ensure that the burst is enough for a single message
// to be sent.
delay := limitReservation.Delay()
if delay > 0 {
log.Infof("GossipSyncer(%x): rate limiting gossip replies, "+
"responding in %s", peerPub, delay)
select {
case <-time.After(delay):
case <-ctx.Done():
limitReservation.Cancel()
return ErrGossipSyncerExiting
case <-m.quit:
limitReservation.Cancel()
return ErrGossipSyncerExiting
}
}
return nil
}
// maybeRateLimitMsg takes a message, and may wait a period of time to rate
// limit the msg.
func (m *SyncManager) maybeRateLimitMsg(ctx context.Context, peerPub [33]byte,
msg lnwire.Message) error {
delay, err := m.deriveRateLimitReservation(msg)
if err != nil {
return nil
}
return m.waitMsgDelay(ctx, peerPub, delay)
}
// sendMessages sends a set of messages to the remote peer.
func (m *SyncManager) sendMessages(ctx context.Context, sync bool,
peer lnpeer.Peer, nodeID route.Vertex, msgs ...lnwire.Message) error {
for _, msg := range msgs {
if err := m.maybeRateLimitMsg(ctx, nodeID, msg); err != nil {
return err
}
if err := peer.SendMessageLazy(sync, msg); err != nil {
return err
}
}
return nil
}
// createGossipSyncer creates the GossipSyncer for a newly connected peer.
func (m *SyncManager) createGossipSyncer(peer lnpeer.Peer) *GossipSyncer {
nodeID := route.Vertex(peer.PubKey())
log.Infof("Creating new GossipSyncer for peer=%x", nodeID[:])
encoding := lnwire.EncodingSortedPlain
s := newGossipSyncer(gossipSyncerCfg{
chainHash: m.cfg.ChainHash,
peerPub: nodeID,
channelSeries: m.cfg.ChanSeries,
encodingType: encoding,
chunkSize: encodingTypeToChunkSize[encoding],
batchSize: requestBatchSize,
sendToPeer: func(ctx context.Context,
msgs ...lnwire.Message) error {
return m.sendMessages(ctx, false, peer, nodeID, msgs...)
},
sendToPeerSync: func(ctx context.Context,
msgs ...lnwire.Message) error {
return m.sendMessages(ctx, true, peer, nodeID, msgs...)
},
ignoreHistoricalFilters: m.cfg.IgnoreHistoricalFilters,
bestHeight: m.cfg.BestHeight,
markGraphSynced: m.markGraphSynced,
maxQueryChanRangeReplies: maxQueryChanRangeReplies,
noTimestampQueryOption: m.cfg.NoTimestampQueries,
isStillZombieChannel: m.cfg.IsStillZombieChannel,
}, m.gossipFilterSema)
// Gossip syncers are initialized by default in a PassiveSync type
// and chansSynced state so that they can reply to any peer queries or
// handle any sync transitions.
s.setSyncState(chansSynced)
s.setSyncType(PassiveSync)
log.Debugf("Created new GossipSyncer[state=%s type=%s] for peer=%x",
s.syncState(), s.SyncType(), peer.PubKey())
return s
}
// removeGossipSyncer removes all internal references to the disconnected peer's
// GossipSyncer and stops it. In the event of an active GossipSyncer being
// disconnected, a passive GossipSyncer, if any, will take its place.
func (m *SyncManager) removeGossipSyncer(peer route.Vertex) {
m.syncersMu.Lock()
defer m.syncersMu.Unlock()
s, ok := m.gossipSyncer(peer)
if !ok {
return
}
log.Infof("Removing GossipSyncer for peer=%v", peer)
// We'll stop the GossipSyncer for the disconnected peer in a goroutine
// to prevent blocking the SyncManager.
go s.Stop()
// If it's a non-active syncer, then we can just exit now.
if _, ok := m.inactiveSyncers[peer]; ok {
delete(m.inactiveSyncers, peer)
return
}
// If it's a pinned syncer, then we can just exit as this doesn't
// affect our active syncer count.
if _, ok := m.pinnedActiveSyncers[peer]; ok {
delete(m.pinnedActiveSyncers, peer)
return
}
// Otherwise, we'll need find a new one to replace it, if any.
delete(m.activeSyncers, peer)
newActiveSyncer := chooseRandomSyncer(
m.inactiveSyncers, m.transitionPassiveSyncer,
)
if newActiveSyncer == nil {
return
}
log.Debugf("Replaced active GossipSyncer(%v) with GossipSyncer(%x)",
peer, newActiveSyncer.cfg.peerPub)
}
// rotateActiveSyncerCandidate rotates a single active syncer. In order to
// achieve this, the active syncer must be in a chansSynced state in order to
// process the sync transition.
func (m *SyncManager) rotateActiveSyncerCandidate() {
m.syncersMu.Lock()
defer m.syncersMu.Unlock()
// If we couldn't find an eligible active syncer to rotate, we can
// return early.
activeSyncer := chooseRandomSyncer(m.activeSyncers, nil)
if activeSyncer == nil {
log.Debug("No eligible active syncer to rotate")
return
}
// Similarly, if we don't have a candidate to rotate with, we can return
// early as well.
candidate := chooseRandomSyncer(m.inactiveSyncers, nil)
if candidate == nil {
log.Debug("No eligible candidate to rotate active syncer")
return
}
// Otherwise, we'll attempt to transition each syncer to their
// respective new sync type.
log.Debugf("Rotating active GossipSyncer(%x) with GossipSyncer(%x)",
activeSyncer.cfg.peerPub, candidate.cfg.peerPub)
if err := m.transitionActiveSyncer(activeSyncer); err != nil {
log.Errorf("Unable to transition active GossipSyncer(%x): %v",
activeSyncer.cfg.peerPub, err)
return
}
if err := m.transitionPassiveSyncer(candidate); err != nil {
log.Errorf("Unable to transition passive GossipSyncer(%x): %v",
activeSyncer.cfg.peerPub, err)
return
}
}
// transitionActiveSyncer transitions an active syncer to a passive one.
//
// NOTE: This must be called with the syncersMu lock held.
func (m *SyncManager) transitionActiveSyncer(s *GossipSyncer) error {
log.Debugf("Transitioning active GossipSyncer(%x) to passive",
s.cfg.peerPub)
if err := s.ProcessSyncTransition(PassiveSync); err != nil {
return err
}
delete(m.activeSyncers, s.cfg.peerPub)
m.inactiveSyncers[s.cfg.peerPub] = s
return nil
}
// transitionPassiveSyncer transitions a passive syncer to an active one.
//
// NOTE: This must be called with the syncersMu lock held.
func (m *SyncManager) transitionPassiveSyncer(s *GossipSyncer) error {
log.Debugf("Transitioning passive GossipSyncer(%x) to active",
s.cfg.peerPub)
if err := s.ProcessSyncTransition(ActiveSync); err != nil {
return err
}
delete(m.inactiveSyncers, s.cfg.peerPub)
m.activeSyncers[s.cfg.peerPub] = s
return nil
}
// forceHistoricalSync chooses a syncer with a remote peer at random and forces
// a historical sync with it.
func (m *SyncManager) forceHistoricalSync() *GossipSyncer {
m.syncersMu.Lock()
defer m.syncersMu.Unlock()
// We'll sample from both sets of active and inactive syncers in the
// event that we don't have any inactive syncers.
return chooseRandomSyncer(m.gossipSyncers(), func(s *GossipSyncer) error {
return s.historicalSync()
})
}
// chooseRandomSyncer iterates through the set of syncers given and returns the
// first one which was able to successfully perform the action enclosed in the
// function closure.
//
// NOTE: It's possible for a nil value to be returned if there are no eligible
// candidate syncers.
func chooseRandomSyncer(syncers map[route.Vertex]*GossipSyncer,
action func(*GossipSyncer) error) *GossipSyncer {
for _, s := range syncers {
// Only syncers in a chansSynced state are viable for sync
// transitions, so skip any that aren't.
if s.syncState() != chansSynced {
continue
}
if action != nil {
if err := action(s); err != nil {
log.Debugf("Skipping eligible candidate "+
"GossipSyncer(%x): %v", s.cfg.peerPub,
err)
continue
}
}
return s
}
return nil
}
// InitSyncState is called by outside sub-systems when a connection is
// established to a new peer that understands how to perform channel range
// queries. We'll allocate a new GossipSyncer for it, and start any goroutines
// needed to handle new queries. The first GossipSyncer registered with the
// SyncManager will attempt a historical sync to ensure we have as much of the
// public channel graph as possible.
//
// TODO(wilmer): Only mark as ActiveSync if this isn't a channel peer.
func (m *SyncManager) InitSyncState(peer lnpeer.Peer) error {
done := make(chan struct{})
select {
case m.newSyncers <- &newSyncer{
peer: peer,
doneChan: done,
}:
case <-m.quit:
return ErrSyncManagerExiting
}
select {
case <-done:
return nil
case <-m.quit:
return ErrSyncManagerExiting
}
}
// PruneSyncState is called by outside sub-systems once a peer that we were
// previously connected to has been disconnected. In this case we can stop the
// existing GossipSyncer assigned to the peer and free up resources.
func (m *SyncManager) PruneSyncState(peer route.Vertex) {
done := make(chan struct{})
// We avoid returning an error when the SyncManager is stopped since the
// GossipSyncer will be stopped then anyway.
select {
case m.staleSyncers <- &staleSyncer{
peer: peer,
doneChan: done,
}:
case <-m.quit:
return
}
select {
case <-done:
case <-m.quit:
}
}
// GossipSyncer returns the associated gossip syncer of a peer. The boolean
// returned signals whether there exists a gossip syncer for the peer.
func (m *SyncManager) GossipSyncer(peer route.Vertex) (*GossipSyncer, bool) {
m.syncersMu.Lock()
defer m.syncersMu.Unlock()
return m.gossipSyncer(peer)
}
// gossipSyncer returns the associated gossip syncer of a peer. The boolean
// returned signals whether there exists a gossip syncer for the peer.
func (m *SyncManager) gossipSyncer(peer route.Vertex) (*GossipSyncer, bool) {
syncer, ok := m.inactiveSyncers[peer]
if ok {
return syncer, true
}
syncer, ok = m.activeSyncers[peer]
if ok {
return syncer, true
}
syncer, ok = m.pinnedActiveSyncers[peer]
if ok {
return syncer, true
}
return nil, false
}
// GossipSyncers returns all of the currently initialized gossip syncers.
func (m *SyncManager) GossipSyncers() map[route.Vertex]*GossipSyncer {
m.syncersMu.Lock()
defer m.syncersMu.Unlock()
return m.gossipSyncers()
}
// gossipSyncers returns all of the currently initialized gossip syncers.
func (m *SyncManager) gossipSyncers() map[route.Vertex]*GossipSyncer {
numSyncers := len(m.inactiveSyncers) + len(m.activeSyncers)
syncers := make(map[route.Vertex]*GossipSyncer, numSyncers)
for _, syncer := range m.inactiveSyncers {
syncers[syncer.cfg.peerPub] = syncer
}
for _, syncer := range m.activeSyncers {
syncers[syncer.cfg.peerPub] = syncer
}
return syncers
}
// markGraphSynced allows us to report that the initial historical sync has
// completed.
func (m *SyncManager) markGraphSynced() {
atomic.StoreInt32(&m.initialHistoricalSyncCompleted, 1)
}
// IsGraphSynced determines whether we've completed our initial historical sync.
// The initial historical sync is done to ensure we've ingested as much of the
// public graph as possible.
func (m *SyncManager) IsGraphSynced() bool {
return atomic.LoadInt32(&m.initialHistoricalSyncCompleted) == 1
}
package discovery
import (
"context"
"errors"
"fmt"
"math"
"math/rand"
"sort"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/lnpeer"
"github.com/lightningnetwork/lnd/lnwire"
)
// SyncerType encapsulates the different types of syncing mechanisms for a
// gossip syncer.
type SyncerType uint8
const (
// ActiveSync denotes that a gossip syncer:
//
// 1. Should not attempt to synchronize with the remote peer for
// missing channels.
// 2. Should respond to queries from the remote peer.
// 3. Should receive new updates from the remote peer.
//
// They are started in a chansSynced state in order to accomplish their
// responsibilities above.
ActiveSync SyncerType = iota
// PassiveSync denotes that a gossip syncer:
//
// 1. Should not attempt to synchronize with the remote peer for
// missing channels.
// 2. Should respond to queries from the remote peer.
// 3. Should not receive new updates from the remote peer.
//
// They are started in a chansSynced state in order to accomplish their
// responsibilities above.
PassiveSync
// PinnedSync denotes an ActiveSync that doesn't count towards the
// default active syncer limits and is always active throughout the
// duration of the peer's connection. Each pinned syncer will begin by
// performing a historical sync to ensure we are well synchronized with
// their routing table.
PinnedSync
)
// String returns a human readable string describing the target SyncerType.
func (t SyncerType) String() string {
switch t {
case ActiveSync:
return "ActiveSync"
case PassiveSync:
return "PassiveSync"
case PinnedSync:
return "PinnedSync"
default:
return fmt.Sprintf("unknown sync type %d", t)
}
}
// IsActiveSync returns true if the SyncerType should set a GossipTimestampRange
// allowing new gossip messages to be received from the peer.
func (t SyncerType) IsActiveSync() bool {
switch t {
case ActiveSync, PinnedSync:
return true
default:
return false
}
}
// syncerState is an enum that represents the current state of the GossipSyncer.
// As the syncer is a state machine, we'll gate our actions based off of the
// current state and the next incoming message.
type syncerState uint32
const (
// syncingChans is the default state of the GossipSyncer. We start in
// this state when a new peer first connects and we don't yet know if
// we're fully synchronized.
syncingChans syncerState = iota
// waitingQueryRangeReply is the second main phase of the GossipSyncer.
// We enter this state after we send out our first QueryChannelRange
// reply. We'll stay in this state until the remote party sends us a
// ReplyShortChanIDsEnd message that indicates they've responded to our
// query entirely. After this state, we'll transition to
// waitingQueryChanReply after we send out requests for all the new
// chan ID's to us.
waitingQueryRangeReply
// queryNewChannels is the third main phase of the GossipSyncer. In
// this phase we'll send out all of our QueryShortChanIDs messages in
// response to the new channels that we don't yet know about.
queryNewChannels
// waitingQueryChanReply is the fourth main phase of the GossipSyncer.
// We enter this phase once we've sent off a query chink to the remote
// peer. We'll stay in this phase until we receive a
// ReplyShortChanIDsEnd message which indicates that the remote party
// has responded to all of our requests.
waitingQueryChanReply
// chansSynced is the terminal stage of the GossipSyncer. Once we enter
// this phase, we'll send out our update horizon, which filters out the
// set of channel updates that we're interested in. In this state,
// we'll be able to accept any outgoing messages from the
// AuthenticatedGossiper, and decide if we should forward them to our
// target peer based on its update horizon.
chansSynced
// syncerIdle is a state in which the gossip syncer can handle external
// requests to transition or perform historical syncs. It is used as the
// initial state for pinned syncers, as well as a fallthrough case for
// chansSynced allowing fully synced peers to facilitate requests.
syncerIdle
)
// String returns a human readable string describing the target syncerState.
func (s syncerState) String() string {
switch s {
case syncingChans:
return "syncingChans"
case waitingQueryRangeReply:
return "waitingQueryRangeReply"
case queryNewChannels:
return "queryNewChannels"
case waitingQueryChanReply:
return "waitingQueryChanReply"
case chansSynced:
return "chansSynced"
case syncerIdle:
return "syncerIdle"
default:
return "UNKNOWN STATE"
}
}
const (
// maxQueryChanRangeReplies specifies the default limit of replies to
// process for a single QueryChannelRange request.
maxQueryChanRangeReplies = 500
// maxQueryChanRangeRepliesZlibFactor specifies the factor applied to
// the maximum number of replies allowed for zlib encoded replies.
maxQueryChanRangeRepliesZlibFactor = 4
// chanRangeQueryBuffer is the number of blocks back that we'll go when
// asking the remote peer for their any channels they know of beyond
// our highest known channel ID.
chanRangeQueryBuffer = 144
// syncTransitionTimeout is the default timeout in which we'll wait up
// to when attempting to perform a sync transition.
syncTransitionTimeout = 5 * time.Second
// requestBatchSize is the maximum number of channels we will query the
// remote peer for in a QueryShortChanIDs message.
requestBatchSize = 500
// syncerBufferSize is the size of the syncer's buffers.
syncerBufferSize = 5
)
var (
// encodingTypeToChunkSize maps an encoding type, to the max number of
// short chan ID's using the encoding type that we can fit into a
// single message safely.
encodingTypeToChunkSize = map[lnwire.QueryEncoding]int32{
lnwire.EncodingSortedPlain: 8000,
}
// ErrGossipSyncerExiting signals that the syncer has been killed.
ErrGossipSyncerExiting = errors.New("gossip syncer exiting")
// ErrSyncTransitionTimeout is an error returned when we've timed out
// attempting to perform a sync transition.
ErrSyncTransitionTimeout = errors.New("timed out attempting to " +
"transition sync type")
// zeroTimestamp is the timestamp we'll use when we want to indicate to
// peers that we do not want to receive any new graph updates.
zeroTimestamp time.Time
)
// syncTransitionReq encapsulates a request for a gossip syncer sync transition.
type syncTransitionReq struct {
newSyncType SyncerType
errChan chan error
}
// historicalSyncReq encapsulates a request for a gossip syncer to perform a
// historical sync.
type historicalSyncReq struct {
// doneChan is a channel that serves as a signal and is closed to ensure
// the historical sync is attempted by the time we return to the caller.
doneChan chan struct{}
}
// gossipSyncerCfg is a struct that packages all the information a GossipSyncer
// needs to carry out its duties.
type gossipSyncerCfg struct {
// chainHash is the chain that this syncer is responsible for.
chainHash chainhash.Hash
// peerPub is the public key of the peer we're syncing with, serialized
// in compressed format.
peerPub [33]byte
// channelSeries is the primary interface that we'll use to generate
// our queries and respond to the queries of the remote peer.
channelSeries ChannelGraphTimeSeries
// encodingType is the current encoding type we're aware of. Requests
// with different encoding types will be rejected.
encodingType lnwire.QueryEncoding
// chunkSize is the max number of short chan IDs using the syncer's
// encoding type that we can fit into a single message safely.
chunkSize int32
// batchSize is the max number of channels the syncer will query from
// the remote node in a single QueryShortChanIDs request.
batchSize int32
// sendToPeer sends a variadic number of messages to the remote peer.
// This method should not block while waiting for sends to be written
// to the wire.
sendToPeer func(context.Context, ...lnwire.Message) error
// sendToPeerSync sends a variadic number of messages to the remote
// peer, blocking until all messages have been sent successfully or a
// write error is encountered.
sendToPeerSync func(context.Context, ...lnwire.Message) error
// noSyncChannels will prevent the GossipSyncer from spawning a
// channelGraphSyncer, meaning we will not try to reconcile unknown
// channels with the remote peer.
noSyncChannels bool
// noReplyQueries will prevent the GossipSyncer from spawning a
// replyHandler, meaning we will not reply to queries from our remote
// peer.
noReplyQueries bool
// noTimestampQueryOption will prevent the GossipSyncer from querying
// timestamps of announcement messages from the peer, and it will
// prevent it from responding to timestamp queries.
noTimestampQueryOption bool
// ignoreHistoricalFilters will prevent syncers from replying with
// historical data when the remote peer sets a gossip_timestamp_range.
// This prevents ranges with old start times from causing us to dump the
// graph on connect.
ignoreHistoricalFilters bool
// bestHeight returns the latest height known of the chain.
bestHeight func() uint32
// markGraphSynced updates the SyncManager's perception of whether we
// have completed at least one historical sync.
markGraphSynced func()
// maxQueryChanRangeReplies is the maximum number of replies we'll allow
// for a single QueryChannelRange request.
maxQueryChanRangeReplies uint32
// isStillZombieChannel takes the timestamps of the latest channel
// updates for a channel and returns true if the channel should be
// considered a zombie based on these timestamps.
isStillZombieChannel func(time.Time, time.Time) bool
}
// GossipSyncer is a struct that handles synchronizing the channel graph state
// with a remote peer. The GossipSyncer implements a state machine that will
// progressively ensure we're synchronized with the channel state of the remote
// node. Once both nodes have been synchronized, we'll use an update filter to
// filter out which messages should be sent to a remote peer based on their
// update horizon. If the update horizon isn't specified, then we won't send
// them any channel updates at all.
type GossipSyncer struct {
started sync.Once
stopped sync.Once
// state is the current state of the GossipSyncer.
//
// NOTE: This variable MUST be used atomically.
state uint32
// syncType denotes the SyncerType the gossip syncer is currently
// exercising.
//
// NOTE: This variable MUST be used atomically.
syncType uint32
// remoteUpdateHorizon is the update horizon of the remote peer. We'll
// use this to properly filter out any messages.
remoteUpdateHorizon *lnwire.GossipTimestampRange
// localUpdateHorizon is our local update horizon, we'll use this to
// determine if we've already sent out our update.
localUpdateHorizon *lnwire.GossipTimestampRange
// syncTransitions is a channel through which new sync type transition
// requests will be sent through. These requests should only be handled
// when the gossip syncer is in a chansSynced state to ensure its state
// machine behaves as expected.
syncTransitionReqs chan *syncTransitionReq
// historicalSyncReqs is a channel that serves as a signal for the
// gossip syncer to perform a historical sync. These can only be done
// once the gossip syncer is in a chansSynced state to ensure its state
// machine behaves as expected.
historicalSyncReqs chan *historicalSyncReq
// genHistoricalChanRangeQuery when true signals to the gossip syncer
// that it should request the remote peer for all of its known channel
// IDs starting from the genesis block of the chain. This can only
// happen if the gossip syncer receives a request to attempt a
// historical sync. It can be unset if the syncer ever transitions from
// PassiveSync to ActiveSync.
genHistoricalChanRangeQuery bool
// gossipMsgs is a channel that all responses to our queries from the
// target peer will be sent over, these will be read by the
// channelGraphSyncer.
gossipMsgs chan lnwire.Message
// queryMsgs is a channel that all queries from the remote peer will be
// received over, these will be read by the replyHandler.
queryMsgs chan lnwire.Message
// curQueryRangeMsg keeps track of the latest QueryChannelRange message
// we've sent to a peer to ensure we've consumed all expected replies.
// This field is primarily used within the waitingQueryChanReply state.
curQueryRangeMsg *lnwire.QueryChannelRange
// prevReplyChannelRange keeps track of the previous ReplyChannelRange
// message we've received from a peer to ensure they've fully replied to
// our query by ensuring they covered our requested block range. This
// field is primarily used within the waitingQueryChanReply state.
prevReplyChannelRange *lnwire.ReplyChannelRange
// bufferedChanRangeReplies is used in the waitingQueryChanReply to
// buffer all the chunked response to our query.
bufferedChanRangeReplies []graphdb.ChannelUpdateInfo
// numChanRangeRepliesRcvd is used to track the number of replies
// received as part of a QueryChannelRange. This field is primarily used
// within the waitingQueryChanReply state.
numChanRangeRepliesRcvd uint32
// newChansToQuery is used to pass the set of channels we should query
// for from the waitingQueryChanReply state to the queryNewChannels
// state.
newChansToQuery []lnwire.ShortChannelID
cfg gossipSyncerCfg
// syncedSignal is a channel that, if set, will be closed when the
// GossipSyncer reaches its terminal chansSynced state.
syncedSignal chan struct{}
// syncerSema is used to more finely control the syncer's ability to
// respond to gossip timestamp range messages.
syncerSema chan struct{}
sync.Mutex
// cg is a helper that encapsulates a wait group and quit channel and
// allows contexts that either block or cancel on those depending on
// the use case.
cg *fn.ContextGuard
}
// newGossipSyncer returns a new instance of the GossipSyncer populated using
// the passed config.
func newGossipSyncer(cfg gossipSyncerCfg, sema chan struct{}) *GossipSyncer {
return &GossipSyncer{
cfg: cfg,
syncTransitionReqs: make(chan *syncTransitionReq),
historicalSyncReqs: make(chan *historicalSyncReq),
gossipMsgs: make(chan lnwire.Message, syncerBufferSize),
queryMsgs: make(chan lnwire.Message, syncerBufferSize),
syncerSema: sema,
cg: fn.NewContextGuard(),
}
}
// Start starts the GossipSyncer and any goroutines that it needs to carry out
// its duties.
func (g *GossipSyncer) Start() {
g.started.Do(func() {
log.Debugf("Starting GossipSyncer(%x)", g.cfg.peerPub[:])
// TODO(conner): only spawn channelGraphSyncer if remote
// supports gossip queries, and only spawn replyHandler if we
// advertise support
if !g.cfg.noSyncChannels {
g.cg.WgAdd(1)
go g.channelGraphSyncer()
}
if !g.cfg.noReplyQueries {
g.cg.WgAdd(1)
go g.replyHandler()
}
})
}
// Stop signals the GossipSyncer for a graceful exit, then waits until it has
// exited.
func (g *GossipSyncer) Stop() {
g.stopped.Do(func() {
log.Debugf("Stopping GossipSyncer(%x)", g.cfg.peerPub[:])
defer log.Debugf("GossipSyncer(%x) stopped", g.cfg.peerPub[:])
g.cg.Quit()
})
}
// handleSyncingChans handles the state syncingChans for the GossipSyncer. When
// in this state, we will send a QueryChannelRange msg to our peer and advance
// the syncer's state to waitingQueryRangeReply.
func (g *GossipSyncer) handleSyncingChans() {
// Prepare the query msg.
queryRangeMsg, err := g.genChanRangeQuery(g.genHistoricalChanRangeQuery)
if err != nil {
log.Errorf("Unable to gen chan range query: %v", err)
return
}
// Acquire a lock so the following state transition is atomic.
//
// NOTE: We must lock the following steps as it's possible we get an
// immediate response (ReplyChannelRange) after sending the query msg.
// The response is handled in ProcessQueryMsg, which requires the
// current state to be waitingQueryRangeReply.
g.Lock()
defer g.Unlock()
// Send the msg to the remote peer, which is non-blocking as
// `sendToPeer` only queues the msg in Brontide.
ctx, _ := g.cg.Create(context.Background())
err = g.cfg.sendToPeer(ctx, queryRangeMsg)
if err != nil {
log.Errorf("Unable to send chan range query: %v", err)
return
}
// With the message sent successfully, we'll transition into the next
// state where we wait for their reply.
g.setSyncState(waitingQueryRangeReply)
}
// channelGraphSyncer is the main goroutine responsible for ensuring that we
// properly channel graph state with the remote peer, and also that we only
// send them messages which actually pass their defined update horizon.
func (g *GossipSyncer) channelGraphSyncer() {
defer g.cg.WgDone()
for {
state := g.syncState()
syncType := g.SyncType()
log.Debugf("GossipSyncer(%x): state=%v, type=%v",
g.cfg.peerPub[:], state, syncType)
switch state {
// When we're in this state, we're trying to synchronize our
// view of the network with the remote peer. We'll kick off
// this sync by asking them for the set of channels they
// understand, as we'll as responding to any other queries by
// them.
case syncingChans:
g.handleSyncingChans()
// In this state, we've sent out our initial channel range
// query and are waiting for the final response from the remote
// peer before we perform a diff to see with channels they know
// of that we don't.
case waitingQueryRangeReply:
// We'll wait to either process a new message from the
// remote party, or exit due to the gossiper exiting,
// or us being signalled to do so.
select {
case msg := <-g.gossipMsgs:
// The remote peer is sending a response to our
// initial query, we'll collate this response,
// and see if it's the final one in the series.
// If so, we can then transition to querying
// for the new channels.
queryReply, ok := msg.(*lnwire.ReplyChannelRange)
if ok {
err := g.processChanRangeReply(queryReply)
if err != nil {
log.Errorf("Unable to "+
"process chan range "+
"query: %v", err)
return
}
continue
}
log.Warnf("Unexpected message: %T in state=%v",
msg, state)
case <-g.cg.Done():
return
}
// We'll enter this state once we've discovered which channels
// the remote party knows of that we don't yet know of
// ourselves.
case queryNewChannels:
// First, we'll attempt to continue our channel
// synchronization by continuing to send off another
// query chunk.
done := g.synchronizeChanIDs()
// If this wasn't our last query, then we'll need to
// transition to our waiting state.
if !done {
continue
}
// If we're fully synchronized, then we can transition
// to our terminal state.
g.setSyncState(chansSynced)
// Ensure that the sync manager becomes aware that the
// historical sync completed so synced_to_graph is
// updated over rpc.
g.cfg.markGraphSynced()
// In this state, we've just sent off a new query for channels
// that we don't yet know of. We'll remain in this state until
// the remote party signals they've responded to our query in
// totality.
case waitingQueryChanReply:
// Once we've sent off our query, we'll wait for either
// an ending reply, or just another query from the
// remote peer.
select {
case msg := <-g.gossipMsgs:
// If this is the final reply to one of our
// queries, then we'll loop back into our query
// state to send of the remaining query chunks.
_, ok := msg.(*lnwire.ReplyShortChanIDsEnd)
if ok {
g.setSyncState(queryNewChannels)
continue
}
log.Warnf("Unexpected message: %T in state=%v",
msg, state)
case <-g.cg.Done():
return
}
// This is our final terminal state where we'll only reply to
// any further queries by the remote peer.
case chansSynced:
g.Lock()
if g.syncedSignal != nil {
close(g.syncedSignal)
g.syncedSignal = nil
}
g.Unlock()
// If we haven't yet sent out our update horizon, and
// we want to receive real-time channel updates, we'll
// do so now.
if g.localUpdateHorizon == nil &&
syncType.IsActiveSync() {
err := g.sendGossipTimestampRange(
time.Now(), math.MaxUint32,
)
if err != nil {
log.Errorf("Unable to send update "+
"horizon to %x: %v",
g.cfg.peerPub, err)
}
}
// With our horizon set, we'll simply reply to any new
// messages or process any state transitions and exit if
// needed.
fallthrough
// Pinned peers will begin in this state, since they will
// immediately receive a request to perform a historical sync.
// Otherwise, we fall through after ending in chansSynced to
// facilitate new requests.
case syncerIdle:
select {
case req := <-g.syncTransitionReqs:
req.errChan <- g.handleSyncTransition(req)
case req := <-g.historicalSyncReqs:
g.handleHistoricalSync(req)
case <-g.cg.Done():
return
}
}
}
}
// replyHandler is an event loop whose sole purpose is to reply to the remote
// peers queries. Our replyHandler will respond to messages generated by their
// channelGraphSyncer, and vice versa. Each party's channelGraphSyncer drives
// the other's replyHandler, allowing the replyHandler to operate independently
// from the state machine maintained on the same node.
//
// NOTE: This method MUST be run as a goroutine.
func (g *GossipSyncer) replyHandler() {
defer g.cg.WgDone()
for {
select {
case msg := <-g.queryMsgs:
err := g.replyPeerQueries(msg)
switch {
case err == ErrGossipSyncerExiting:
return
case err == lnpeer.ErrPeerExiting:
return
case err != nil:
log.Errorf("Unable to reply to peer "+
"query: %v", err)
}
case <-g.cg.Done():
return
}
}
}
// sendGossipTimestampRange constructs and sets a GossipTimestampRange for the
// syncer and sends it to the remote peer.
func (g *GossipSyncer) sendGossipTimestampRange(firstTimestamp time.Time,
timestampRange uint32) error {
endTimestamp := firstTimestamp.Add(
time.Duration(timestampRange) * time.Second,
)
log.Infof("GossipSyncer(%x): applying gossipFilter(start=%v, end=%v)",
g.cfg.peerPub[:], firstTimestamp, endTimestamp)
localUpdateHorizon := &lnwire.GossipTimestampRange{
ChainHash: g.cfg.chainHash,
FirstTimestamp: uint32(firstTimestamp.Unix()),
TimestampRange: timestampRange,
}
ctx, _ := g.cg.Create(context.Background())
if err := g.cfg.sendToPeer(ctx, localUpdateHorizon); err != nil {
return err
}
if firstTimestamp == zeroTimestamp && timestampRange == 0 {
g.localUpdateHorizon = nil
} else {
g.localUpdateHorizon = localUpdateHorizon
}
return nil
}
// synchronizeChanIDs is called by the channelGraphSyncer when we need to query
// the remote peer for its known set of channel IDs within a particular block
// range. This method will be called continually until the entire range has
// been queried for with a response received. We'll chunk our requests as
// required to ensure they fit into a single message. We may re-renter this
// state in the case that chunking is required.
func (g *GossipSyncer) synchronizeChanIDs() bool {
// If we're in this state yet there are no more new channels to query
// for, then we'll transition to our final synced state and return true
// to signal that we're fully synchronized.
if len(g.newChansToQuery) == 0 {
log.Infof("GossipSyncer(%x): no more chans to query",
g.cfg.peerPub[:])
return true
}
// Otherwise, we'll issue our next chunked query to receive replies
// for.
var queryChunk []lnwire.ShortChannelID
// If the number of channels to query for is less than the chunk size,
// then we can issue a single query.
if int32(len(g.newChansToQuery)) < g.cfg.batchSize {
queryChunk = g.newChansToQuery
g.newChansToQuery = nil
} else {
// Otherwise, we'll need to only query for the next chunk.
// We'll slice into our query chunk, then slide down our main
// pointer down by the chunk size.
queryChunk = g.newChansToQuery[:g.cfg.batchSize]
g.newChansToQuery = g.newChansToQuery[g.cfg.batchSize:]
}
log.Infof("GossipSyncer(%x): querying for %v new channels",
g.cfg.peerPub[:], len(queryChunk))
// Change the state before sending the query msg.
g.setSyncState(waitingQueryChanReply)
// With our chunk obtained, we'll send over our next query, then return
// false indicating that we're net yet fully synced.
ctx, _ := g.cg.Create(context.Background())
err := g.cfg.sendToPeer(ctx, &lnwire.QueryShortChanIDs{
ChainHash: g.cfg.chainHash,
EncodingType: lnwire.EncodingSortedPlain,
ShortChanIDs: queryChunk,
})
if err != nil {
log.Errorf("Unable to sync chan IDs: %v", err)
}
return false
}
// isLegacyReplyChannelRange determines where a ReplyChannelRange message is
// considered legacy. There was a point where lnd used to include the same query
// over multiple replies, rather than including the portion of the query the
// reply is handling. We'll use this as a way of detecting whether we are
// communicating with a legacy node so we can properly sync with them.
func isLegacyReplyChannelRange(query *lnwire.QueryChannelRange,
reply *lnwire.ReplyChannelRange) bool {
return (reply.ChainHash == query.ChainHash &&
reply.FirstBlockHeight == query.FirstBlockHeight &&
reply.NumBlocks == query.NumBlocks)
}
// processChanRangeReply is called each time the GossipSyncer receives a new
// reply to the initial range query to discover new channels that it didn't
// previously know of.
func (g *GossipSyncer) processChanRangeReply(msg *lnwire.ReplyChannelRange) error {
// isStale returns whether the timestamp is too far into the past.
isStale := func(timestamp time.Time) bool {
return time.Since(timestamp) > graph.DefaultChannelPruneExpiry
}
// isSkewed returns whether the timestamp is too far into the future.
isSkewed := func(timestamp time.Time) bool {
return time.Until(timestamp) > graph.DefaultChannelPruneExpiry
}
// If we're not communicating with a legacy node, we'll apply some
// further constraints on their reply to ensure it satisfies our query.
if !isLegacyReplyChannelRange(g.curQueryRangeMsg, msg) {
// The first block should be within our original request.
if msg.FirstBlockHeight < g.curQueryRangeMsg.FirstBlockHeight {
return fmt.Errorf("reply includes channels for height "+
"%v prior to query %v", msg.FirstBlockHeight,
g.curQueryRangeMsg.FirstBlockHeight)
}
// The last block should also be. We don't need to check the
// intermediate ones because they should already be in sorted
// order.
replyLastHeight := msg.LastBlockHeight()
queryLastHeight := g.curQueryRangeMsg.LastBlockHeight()
if replyLastHeight > queryLastHeight {
return fmt.Errorf("reply includes channels for height "+
"%v after query %v", replyLastHeight,
queryLastHeight)
}
// If we've previously received a reply for this query, look at
// its last block to ensure the current reply properly follows
// it.
if g.prevReplyChannelRange != nil {
prevReply := g.prevReplyChannelRange
prevReplyLastHeight := prevReply.LastBlockHeight()
// The current reply can either start from the previous
// reply's last block, if there are still more channels
// for the same block, or the block after.
if msg.FirstBlockHeight != prevReplyLastHeight &&
msg.FirstBlockHeight != prevReplyLastHeight+1 {
return fmt.Errorf("first block of reply %v "+
"does not continue from last block of "+
"previous %v", msg.FirstBlockHeight,
prevReplyLastHeight)
}
}
}
g.prevReplyChannelRange = msg
for i, scid := range msg.ShortChanIDs {
info := graphdb.NewChannelUpdateInfo(
scid, time.Time{}, time.Time{},
)
if len(msg.Timestamps) != 0 {
t1 := time.Unix(int64(msg.Timestamps[i].Timestamp1), 0)
info.Node1UpdateTimestamp = t1
t2 := time.Unix(int64(msg.Timestamps[i].Timestamp2), 0)
info.Node2UpdateTimestamp = t2
// Sort out all channels with outdated or skewed
// timestamps. Both timestamps need to be out of
// boundaries for us to skip the channel and not query
// it later on.
switch {
case isStale(info.Node1UpdateTimestamp) &&
isStale(info.Node2UpdateTimestamp):
continue
case isSkewed(info.Node1UpdateTimestamp) &&
isSkewed(info.Node2UpdateTimestamp):
continue
case isStale(info.Node1UpdateTimestamp) &&
isSkewed(info.Node2UpdateTimestamp):
continue
case isStale(info.Node2UpdateTimestamp) &&
isSkewed(info.Node1UpdateTimestamp):
continue
}
}
g.bufferedChanRangeReplies = append(
g.bufferedChanRangeReplies, info,
)
}
switch g.cfg.encodingType {
case lnwire.EncodingSortedPlain:
g.numChanRangeRepliesRcvd++
case lnwire.EncodingSortedZlib:
g.numChanRangeRepliesRcvd += maxQueryChanRangeRepliesZlibFactor
default:
return fmt.Errorf("unhandled encoding type %v", g.cfg.encodingType)
}
log.Infof("GossipSyncer(%x): buffering chan range reply of size=%v",
g.cfg.peerPub[:], len(msg.ShortChanIDs))
// If this isn't the last response and we can continue to receive more,
// then we can exit as we've already buffered the latest portion of the
// streaming reply.
maxReplies := g.cfg.maxQueryChanRangeReplies
switch {
// If we're communicating with a legacy node, we'll need to look at the
// complete field.
case isLegacyReplyChannelRange(g.curQueryRangeMsg, msg):
if msg.Complete == 0 && g.numChanRangeRepliesRcvd < maxReplies {
return nil
}
// Otherwise, we'll look at the reply's height range.
default:
replyLastHeight := msg.LastBlockHeight()
queryLastHeight := g.curQueryRangeMsg.LastBlockHeight()
// TODO(wilmer): This might require some padding if the remote
// node is not aware of the last height we sent them, i.e., is
// behind a few blocks from us.
if replyLastHeight < queryLastHeight &&
g.numChanRangeRepliesRcvd < maxReplies {
return nil
}
}
log.Infof("GossipSyncer(%x): filtering through %v chans",
g.cfg.peerPub[:], len(g.bufferedChanRangeReplies))
// Otherwise, this is the final response, so we'll now check to see
// which channels they know of that we don't.
newChans, err := g.cfg.channelSeries.FilterKnownChanIDs(
g.cfg.chainHash, g.bufferedChanRangeReplies,
g.cfg.isStillZombieChannel,
)
if err != nil {
return fmt.Errorf("unable to filter chan ids: %w", err)
}
// As we've received the entirety of the reply, we no longer need to
// hold on to the set of buffered replies or the original query that
// prompted the replies, so we'll let that be garbage collected now.
g.curQueryRangeMsg = nil
g.prevReplyChannelRange = nil
g.bufferedChanRangeReplies = nil
g.numChanRangeRepliesRcvd = 0
// If there aren't any channels that we don't know of, then we can
// switch straight to our terminal state.
if len(newChans) == 0 {
log.Infof("GossipSyncer(%x): remote peer has no new chans",
g.cfg.peerPub[:])
g.setSyncState(chansSynced)
// Ensure that the sync manager becomes aware that the
// historical sync completed so synced_to_graph is updated over
// rpc.
g.cfg.markGraphSynced()
return nil
}
// Otherwise, we'll set the set of channels that we need to query for
// the next state, and also transition our state.
g.newChansToQuery = newChans
g.setSyncState(queryNewChannels)
log.Infof("GossipSyncer(%x): starting query for %v new chans",
g.cfg.peerPub[:], len(newChans))
return nil
}
// genChanRangeQuery generates the initial message we'll send to the remote
// party when we're kicking off the channel graph synchronization upon
// connection. The historicalQuery boolean can be used to generate a query from
// the genesis block of the chain.
func (g *GossipSyncer) genChanRangeQuery(
historicalQuery bool) (*lnwire.QueryChannelRange, error) {
// First, we'll query our channel graph time series for its highest
// known channel ID.
newestChan, err := g.cfg.channelSeries.HighestChanID(g.cfg.chainHash)
if err != nil {
return nil, err
}
// Once we have the chan ID of the newest, we'll obtain the block height
// of the channel, then subtract our default horizon to ensure we don't
// miss any channels. By default, we go back 1 day from the newest
// channel, unless we're attempting a historical sync, where we'll
// actually start from the genesis block instead.
var startHeight uint32
switch {
case historicalQuery:
fallthrough
case newestChan.BlockHeight <= chanRangeQueryBuffer:
startHeight = 0
default:
startHeight = newestChan.BlockHeight - chanRangeQueryBuffer
}
// Determine the number of blocks to request based on our best height.
// We'll take into account any potential underflows and explicitly set
// numBlocks to its minimum value of 1 if so.
bestHeight := g.cfg.bestHeight()
numBlocks := bestHeight - startHeight
if int64(numBlocks) < 1 {
numBlocks = 1
}
log.Infof("GossipSyncer(%x): requesting new chans from height=%v "+
"and %v blocks after", g.cfg.peerPub[:], startHeight, numBlocks)
// Finally, we'll craft the channel range query, using our starting
// height, then asking for all known channels to the foreseeable end of
// the main chain.
query := &lnwire.QueryChannelRange{
ChainHash: g.cfg.chainHash,
FirstBlockHeight: startHeight,
NumBlocks: numBlocks,
}
if !g.cfg.noTimestampQueryOption {
query.QueryOptions = lnwire.NewTimestampQueryOption()
}
g.curQueryRangeMsg = query
return query, nil
}
// replyPeerQueries is called in response to any query by the remote peer.
// We'll examine our state and send back our best response.
func (g *GossipSyncer) replyPeerQueries(msg lnwire.Message) error {
switch msg := msg.(type) {
// In this state, we'll also handle any incoming channel range queries
// from the remote peer as they're trying to sync their state as well.
case *lnwire.QueryChannelRange:
return g.replyChanRangeQuery(msg)
// If the remote peer skips straight to requesting new channels that
// they don't know of, then we'll ensure that we also handle this case.
case *lnwire.QueryShortChanIDs:
return g.replyShortChanIDs(msg)
default:
return fmt.Errorf("unknown message: %T", msg)
}
}
// replyChanRangeQuery will be dispatched in response to a channel range query
// by the remote node. We'll query the channel time series for channels that
// meet the channel range, then chunk our responses to the remote node. We also
// ensure that our final fragment carries the "complete" bit to indicate the
// end of our streaming response.
func (g *GossipSyncer) replyChanRangeQuery(query *lnwire.QueryChannelRange) error {
// Before responding, we'll check to ensure that the remote peer is
// querying for the same chain that we're on. If not, we'll send back a
// response with a complete value of zero to indicate we're on a
// different chain.
if g.cfg.chainHash != query.ChainHash {
log.Warnf("Remote peer requested QueryChannelRange for "+
"chain=%v, we're on chain=%v", query.ChainHash,
g.cfg.chainHash)
ctx, _ := g.cg.Create(context.Background())
return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyChannelRange{
ChainHash: query.ChainHash,
FirstBlockHeight: query.FirstBlockHeight,
NumBlocks: query.NumBlocks,
Complete: 0,
EncodingType: g.cfg.encodingType,
ShortChanIDs: nil,
})
}
log.Infof("GossipSyncer(%x): filtering chan range: start_height=%v, "+
"num_blocks=%v", g.cfg.peerPub[:], query.FirstBlockHeight,
query.NumBlocks)
// Check if the query asked for timestamps. We will only serve
// timestamps if this has not been disabled with
// noTimestampQueryOption.
withTimestamps := query.WithTimestamps() &&
!g.cfg.noTimestampQueryOption
// Next, we'll consult the time series to obtain the set of known
// channel ID's that match their query.
startBlock := query.FirstBlockHeight
endBlock := query.LastBlockHeight()
channelRanges, err := g.cfg.channelSeries.FilterChannelRange(
query.ChainHash, startBlock, endBlock, withTimestamps,
)
if err != nil {
return err
}
// TODO(roasbeef): means can't send max uint above?
// * or make internal 64
// We'll send our response in a streaming manner, chunk-by-chunk. We do
// this as there's a transport message size limit which we'll need to
// adhere to. We also need to make sure all of our replies cover the
// expected range of the query.
sendReplyForChunk := func(channelChunk []graphdb.ChannelUpdateInfo,
firstHeight, lastHeight uint32, finalChunk bool) error {
// The number of blocks contained in the current chunk (the
// total span) is the difference between the last channel ID and
// the first in the range. We add one as even if all channels
// returned are in the same block, we need to count that.
numBlocks := lastHeight - firstHeight + 1
complete := uint8(0)
if finalChunk {
complete = 1
}
var timestamps lnwire.Timestamps
if withTimestamps {
timestamps = make(lnwire.Timestamps, len(channelChunk))
}
scids := make([]lnwire.ShortChannelID, len(channelChunk))
for i, info := range channelChunk {
scids[i] = info.ShortChannelID
if !withTimestamps {
continue
}
timestamps[i].Timestamp1 = uint32(
info.Node1UpdateTimestamp.Unix(),
)
timestamps[i].Timestamp2 = uint32(
info.Node2UpdateTimestamp.Unix(),
)
}
ctx, _ := g.cg.Create(context.Background())
return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyChannelRange{
ChainHash: query.ChainHash,
NumBlocks: numBlocks,
FirstBlockHeight: firstHeight,
Complete: complete,
EncodingType: g.cfg.encodingType,
ShortChanIDs: scids,
Timestamps: timestamps,
})
}
var (
firstHeight = query.FirstBlockHeight
lastHeight uint32
channelChunk []graphdb.ChannelUpdateInfo
)
// chunkSize is the maximum number of SCIDs that we can safely put in a
// single message. If we also need to include timestamps though, then
// this number is halved since encoding two timestamps takes the same
// number of bytes as encoding an SCID.
chunkSize := g.cfg.chunkSize
if withTimestamps {
chunkSize /= 2
}
for _, channelRange := range channelRanges {
channels := channelRange.Channels
numChannels := int32(len(channels))
numLeftToAdd := chunkSize - int32(len(channelChunk))
// Include the current block in the ongoing chunk if it can fit
// and move on to the next block.
if numChannels <= numLeftToAdd {
channelChunk = append(channelChunk, channels...)
continue
}
// Otherwise, we need to send our existing channel chunk as is
// as its own reply and start a new one for the current block.
// We'll mark the end of our current chunk as the height before
// the current block to ensure the whole query range is replied
// to.
log.Infof("GossipSyncer(%x): sending range chunk of size=%v",
g.cfg.peerPub[:], len(channelChunk))
lastHeight = channelRange.Height - 1
err := sendReplyForChunk(
channelChunk, firstHeight, lastHeight, false,
)
if err != nil {
return err
}
// With the reply constructed, we'll start tallying channels for
// our next one keeping in mind our chunk size. This may result
// in channels for this block being left out from the reply, but
// this isn't an issue since we'll randomly shuffle them and we
// assume a historical gossip sync is performed at a later time.
firstHeight = channelRange.Height
finalChunkSize := numChannels
exceedsChunkSize := numChannels > chunkSize
if exceedsChunkSize {
rand.Shuffle(len(channels), func(i, j int) {
channels[i], channels[j] = channels[j], channels[i]
})
finalChunkSize = chunkSize
}
channelChunk = channels[:finalChunkSize]
// Sort the chunk once again if we had to shuffle it.
if exceedsChunkSize {
sort.Slice(channelChunk, func(i, j int) bool {
id1 := channelChunk[i].ShortChannelID.ToUint64()
id2 := channelChunk[j].ShortChannelID.ToUint64()
return id1 < id2
})
}
}
// Send the remaining chunk as the final reply.
log.Infof("GossipSyncer(%x): sending final chan range chunk, size=%v",
g.cfg.peerPub[:], len(channelChunk))
return sendReplyForChunk(
channelChunk, firstHeight, query.LastBlockHeight(), true,
)
}
// replyShortChanIDs will be dispatched in response to a query by the remote
// node for information concerning a set of short channel ID's. Our response
// will be sent in a streaming chunked manner to ensure that we remain below
// the current transport level message size.
func (g *GossipSyncer) replyShortChanIDs(query *lnwire.QueryShortChanIDs) error {
// Before responding, we'll check to ensure that the remote peer is
// querying for the same chain that we're on. If not, we'll send back a
// response with a complete value of zero to indicate we're on a
// different chain.
if g.cfg.chainHash != query.ChainHash {
log.Warnf("Remote peer requested QueryShortChanIDs for "+
"chain=%v, we're on chain=%v", query.ChainHash,
g.cfg.chainHash)
ctx, _ := g.cg.Create(context.Background())
return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyShortChanIDsEnd{
ChainHash: query.ChainHash,
Complete: 0,
})
}
if len(query.ShortChanIDs) == 0 {
log.Infof("GossipSyncer(%x): ignoring query for blank short chan ID's",
g.cfg.peerPub[:])
return nil
}
log.Infof("GossipSyncer(%x): fetching chan anns for %v chans",
g.cfg.peerPub[:], len(query.ShortChanIDs))
// Now that we know we're on the same chain, we'll query the channel
// time series for the set of messages that we know of which satisfies
// the requirement of being a chan ann, chan update, or a node ann
// related to the set of queried channels.
replyMsgs, err := g.cfg.channelSeries.FetchChanAnns(
query.ChainHash, query.ShortChanIDs,
)
if err != nil {
return fmt.Errorf("unable to fetch chan anns for %v..., %w",
query.ShortChanIDs[0].ToUint64(), err)
}
// Reply with any messages related to those channel ID's, we'll write
// each one individually and synchronously to throttle the sends and
// perform buffering of responses in the syncer as opposed to the peer.
for _, msg := range replyMsgs {
ctx, _ := g.cg.Create(context.Background())
err := g.cfg.sendToPeerSync(ctx, msg)
if err != nil {
return err
}
}
// Regardless of whether we had any messages to reply with, send over
// the sentinel message to signal that the stream has terminated.
ctx, _ := g.cg.Create(context.Background())
return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyShortChanIDsEnd{
ChainHash: query.ChainHash,
Complete: 1,
})
}
// ApplyGossipFilter applies a gossiper filter sent by the remote node to the
// state machine. Once applied, we'll ensure that we don't forward any messages
// to the peer that aren't within the time range of the filter.
func (g *GossipSyncer) ApplyGossipFilter(filter *lnwire.GossipTimestampRange) error {
g.Lock()
g.remoteUpdateHorizon = filter
startTime := time.Unix(int64(g.remoteUpdateHorizon.FirstTimestamp), 0)
endTime := startTime.Add(
time.Duration(g.remoteUpdateHorizon.TimestampRange) * time.Second,
)
g.Unlock()
// If requested, don't reply with historical gossip data when the remote
// peer sets their gossip timestamp range.
if g.cfg.ignoreHistoricalFilters {
return nil
}
select {
case <-g.syncerSema:
case <-g.cg.Done():
return ErrGossipSyncerExiting
}
// We don't put this in a defer because if the goroutine is launched,
// it needs to be called when the goroutine is stopped.
returnSema := func() {
g.syncerSema <- struct{}{}
}
// Now that the remote peer has applied their filter, we'll query the
// database for all the messages that are beyond this filter.
newUpdatestoSend, err := g.cfg.channelSeries.UpdatesInHorizon(
g.cfg.chainHash, startTime, endTime,
)
if err != nil {
returnSema()
return err
}
log.Infof("GossipSyncer(%x): applying new remote update horizon: "+
"start=%v, end=%v, backlog_size=%v", g.cfg.peerPub[:],
startTime, endTime, len(newUpdatestoSend))
// If we don't have any to send, then we can return early.
if len(newUpdatestoSend) == 0 {
returnSema()
return nil
}
// We'll conclude by launching a goroutine to send out any updates.
g.cg.WgAdd(1)
go func() {
defer g.cg.WgDone()
defer returnSema()
for _, msg := range newUpdatestoSend {
ctx, _ := g.cg.Create(context.Background())
err := g.cfg.sendToPeerSync(ctx, msg)
switch {
case err == ErrGossipSyncerExiting:
return
case err == lnpeer.ErrPeerExiting:
return
case err != nil:
log.Errorf("Unable to send message for "+
"peer catch up: %v", err)
}
}
}()
return nil
}
// FilterGossipMsgs takes a set of gossip messages, and only send it to a peer
// iff the message is within the bounds of their set gossip filter. If the peer
// doesn't have a gossip filter set, then no messages will be forwarded.
func (g *GossipSyncer) FilterGossipMsgs(msgs ...msgWithSenders) {
// If the peer doesn't have an update horizon set, then we won't send
// it any new update messages.
if g.remoteUpdateHorizon == nil {
log.Tracef("GossipSyncer(%x): skipped due to nil "+
"remoteUpdateHorizon", g.cfg.peerPub[:])
return
}
// If we've been signaled to exit, or are exiting, then we'll stop
// short.
select {
case <-g.cg.Done():
return
default:
}
// TODO(roasbeef): need to ensure that peer still online...send msg to
// gossiper on peer termination to signal peer disconnect?
var err error
// Before we filter out the messages, we'll construct an index over the
// set of channel announcements and channel updates. This will allow us
// to quickly check if we should forward a chan ann, based on the known
// channel updates for a channel.
chanUpdateIndex := make(
map[lnwire.ShortChannelID][]*lnwire.ChannelUpdate1,
)
for _, msg := range msgs {
chanUpdate, ok := msg.msg.(*lnwire.ChannelUpdate1)
if !ok {
continue
}
chanUpdateIndex[chanUpdate.ShortChannelID] = append(
chanUpdateIndex[chanUpdate.ShortChannelID], chanUpdate,
)
}
// We'll construct a helper function that we'll us below to determine
// if a given messages passes the gossip msg filter.
g.Lock()
startTime := time.Unix(int64(g.remoteUpdateHorizon.FirstTimestamp), 0)
endTime := startTime.Add(
time.Duration(g.remoteUpdateHorizon.TimestampRange) * time.Second,
)
g.Unlock()
passesFilter := func(timeStamp uint32) bool {
t := time.Unix(int64(timeStamp), 0)
return t.Equal(startTime) ||
(t.After(startTime) && t.Before(endTime))
}
msgsToSend := make([]lnwire.Message, 0, len(msgs))
for _, msg := range msgs {
// If the target peer is the peer that sent us this message,
// then we'll exit early as we don't need to filter this
// message.
if _, ok := msg.senders[g.cfg.peerPub]; ok {
continue
}
switch msg := msg.msg.(type) {
// For each channel announcement message, we'll only send this
// message if the channel updates for the channel are between
// our time range.
case *lnwire.ChannelAnnouncement1:
// First, we'll check if the channel updates are in
// this message batch.
chanUpdates, ok := chanUpdateIndex[msg.ShortChannelID]
if !ok {
// If not, we'll attempt to query the database
// to see if we know of the updates.
chanUpdates, err = g.cfg.channelSeries.FetchChanUpdates(
g.cfg.chainHash, msg.ShortChannelID,
)
if err != nil {
log.Warnf("no channel updates found for "+
"short_chan_id=%v",
msg.ShortChannelID)
continue
}
}
for _, chanUpdate := range chanUpdates {
if passesFilter(chanUpdate.Timestamp) {
msgsToSend = append(msgsToSend, msg)
break
}
}
if len(chanUpdates) == 0 {
msgsToSend = append(msgsToSend, msg)
}
// For each channel update, we'll only send if it the timestamp
// is between our time range.
case *lnwire.ChannelUpdate1:
if passesFilter(msg.Timestamp) {
msgsToSend = append(msgsToSend, msg)
}
// Similarly, we only send node announcements if the update
// timestamp ifs between our set gossip filter time range.
case *lnwire.NodeAnnouncement:
if passesFilter(msg.Timestamp) {
msgsToSend = append(msgsToSend, msg)
}
}
}
log.Tracef("GossipSyncer(%x): filtered gossip msgs: set=%v, sent=%v",
g.cfg.peerPub[:], len(msgs), len(msgsToSend))
if len(msgsToSend) == 0 {
return
}
ctx, _ := g.cg.Create(context.Background())
if err = g.cfg.sendToPeer(ctx, msgsToSend...); err != nil {
log.Errorf("unable to send gossip msgs: %v", err)
}
}
// ProcessQueryMsg is used by outside callers to pass new channel time series
// queries to the internal processing goroutine.
func (g *GossipSyncer) ProcessQueryMsg(msg lnwire.Message, peerQuit <-chan struct{}) error {
var msgChan chan lnwire.Message
switch msg.(type) {
case *lnwire.QueryChannelRange, *lnwire.QueryShortChanIDs:
msgChan = g.queryMsgs
// Reply messages should only be expected in states where we're waiting
// for a reply.
case *lnwire.ReplyChannelRange, *lnwire.ReplyShortChanIDsEnd:
g.Lock()
syncState := g.syncState()
g.Unlock()
if syncState != waitingQueryRangeReply &&
syncState != waitingQueryChanReply {
return fmt.Errorf("unexpected msg %T received in "+
"state %v", msg, syncState)
}
msgChan = g.gossipMsgs
default:
msgChan = g.gossipMsgs
}
select {
case msgChan <- msg:
case <-peerQuit:
case <-g.cg.Done():
}
return nil
}
// setSyncState sets the gossip syncer's state to the given state.
func (g *GossipSyncer) setSyncState(state syncerState) {
atomic.StoreUint32(&g.state, uint32(state))
}
// syncState returns the current syncerState of the target GossipSyncer.
func (g *GossipSyncer) syncState() syncerState {
return syncerState(atomic.LoadUint32(&g.state))
}
// ResetSyncedSignal returns a channel that will be closed in order to serve as
// a signal for when the GossipSyncer has reached its chansSynced state.
func (g *GossipSyncer) ResetSyncedSignal() chan struct{} {
g.Lock()
defer g.Unlock()
syncedSignal := make(chan struct{})
syncState := syncerState(atomic.LoadUint32(&g.state))
if syncState == chansSynced {
close(syncedSignal)
return syncedSignal
}
g.syncedSignal = syncedSignal
return g.syncedSignal
}
// ProcessSyncTransition sends a request to the gossip syncer to transition its
// sync type to a new one.
//
// NOTE: This can only be done once the gossip syncer has reached its final
// chansSynced state.
func (g *GossipSyncer) ProcessSyncTransition(newSyncType SyncerType) error {
errChan := make(chan error, 1)
select {
case g.syncTransitionReqs <- &syncTransitionReq{
newSyncType: newSyncType,
errChan: errChan,
}:
case <-time.After(syncTransitionTimeout):
return ErrSyncTransitionTimeout
case <-g.cg.Done():
return ErrGossipSyncerExiting
}
select {
case err := <-errChan:
return err
case <-g.cg.Done():
return ErrGossipSyncerExiting
}
}
// handleSyncTransition handles a new sync type transition request.
//
// NOTE: The gossip syncer might have another sync state as a result of this
// transition.
func (g *GossipSyncer) handleSyncTransition(req *syncTransitionReq) error {
// Return early from any NOP sync transitions.
syncType := g.SyncType()
if syncType == req.newSyncType {
return nil
}
log.Debugf("GossipSyncer(%x): transitioning from %v to %v",
g.cfg.peerPub, syncType, req.newSyncType)
var (
firstTimestamp time.Time
timestampRange uint32
)
switch req.newSyncType {
// If an active sync has been requested, then we should resume receiving
// new graph updates from the remote peer.
case ActiveSync, PinnedSync:
firstTimestamp = time.Now()
timestampRange = math.MaxUint32
// If a PassiveSync transition has been requested, then we should no
// longer receive any new updates from the remote peer. We can do this
// by setting our update horizon to a range in the past ensuring no
// graph updates match the timestamp range.
case PassiveSync:
firstTimestamp = zeroTimestamp
timestampRange = 0
default:
return fmt.Errorf("unhandled sync transition %v",
req.newSyncType)
}
err := g.sendGossipTimestampRange(firstTimestamp, timestampRange)
if err != nil {
return fmt.Errorf("unable to send local update horizon: %w",
err)
}
g.setSyncType(req.newSyncType)
return nil
}
// setSyncType sets the gossip syncer's sync type to the given type.
func (g *GossipSyncer) setSyncType(syncType SyncerType) {
atomic.StoreUint32(&g.syncType, uint32(syncType))
}
// SyncType returns the current SyncerType of the target GossipSyncer.
func (g *GossipSyncer) SyncType() SyncerType {
return SyncerType(atomic.LoadUint32(&g.syncType))
}
// historicalSync sends a request to the gossip syncer to perofmr a historical
// sync.
//
// NOTE: This can only be done once the gossip syncer has reached its final
// chansSynced state.
func (g *GossipSyncer) historicalSync() error {
done := make(chan struct{})
select {
case g.historicalSyncReqs <- &historicalSyncReq{
doneChan: done,
}:
case <-time.After(syncTransitionTimeout):
return ErrSyncTransitionTimeout
case <-g.cg.Done():
return ErrGossiperShuttingDown
}
select {
case <-done:
return nil
case <-g.cg.Done():
return ErrGossiperShuttingDown
}
}
// handleHistoricalSync handles a request to the gossip syncer to perform a
// historical sync.
func (g *GossipSyncer) handleHistoricalSync(req *historicalSyncReq) {
// We'll go back to our initial syncingChans state in order to request
// the remote peer to give us all of the channel IDs they know of
// starting from the genesis block.
g.genHistoricalChanRangeQuery = true
g.setSyncState(syncingChans)
close(req.doneChan)
}
package discovery
import (
"fmt"
"sync"
"sync/atomic"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
var (
// ErrVBarrierShuttingDown signals that the barrier has been requested
// to shutdown, and that the caller should not treat the wait condition
// as fulfilled.
ErrVBarrierShuttingDown = errors.New("ValidationBarrier shutting down")
)
// JobID identifies an active job in the validation barrier. It is large so
// that we don't need to worry about overflows.
type JobID uint64
// jobInfo stores job dependency info for a set of dependent gossip messages.
type jobInfo struct {
// activeParentJobIDs is the set of active parent job ids.
activeParentJobIDs fn.Set[JobID]
// activeDependentJobs is the set of active dependent job ids.
activeDependentJobs fn.Set[JobID]
}
// ValidationBarrier is a barrier used to enforce a strict validation order
// while concurrently validating other updates for channel edges. It uses a set
// of maps to track validation dependencies. This is needed in practice because
// gossip messages for a given channel may arive in order, but then due to
// scheduling in different goroutines, may be validated in the wrong order.
// With the ValidationBarrier, the dependent update will wait until the parent
// update completes.
type ValidationBarrier struct {
// validationSemaphore is a channel of structs which is used as a
// semaphore. Initially we'll fill this with a buffered channel of the
// size of the number of active requests. Each new job will consume
// from this channel, then restore the value upon completion.
validationSemaphore chan struct{}
// jobInfoMap stores the set of job ids for each channel.
// NOTE: This MUST be used with the mutex.
// NOTE: This currently stores string representations of
// lnwire.ShortChannelID and route.Vertex. Since these are of different
// lengths, collision cannot occur in their string representations.
// N.B.: Check that any new string-converted types don't collide with
// existing string-converted types.
jobInfoMap map[string]*jobInfo
// jobDependencies is a mapping from a child's JobID to the set of
// parent JobID that it depends on.
// NOTE: This MUST be used with the mutex.
jobDependencies map[JobID]fn.Set[JobID]
// childJobChans stores the notification channel that each child job
// listens on for parent job completions.
// NOTE: This MUST be used with the mutex.
childJobChans map[JobID]chan struct{}
// idCtr is an atomic integer that is used to assign JobIDs.
idCtr atomic.Uint64
quit chan struct{}
sync.Mutex
}
// NewValidationBarrier creates a new instance of a validation barrier given
// the total number of active requests, and a quit channel which will be used
// to know when to kill pending, but unfilled jobs.
func NewValidationBarrier(numActiveReqs int,
quitChan chan struct{}) *ValidationBarrier {
v := &ValidationBarrier{
jobInfoMap: make(map[string]*jobInfo),
jobDependencies: make(map[JobID]fn.Set[JobID]),
childJobChans: make(map[JobID]chan struct{}),
quit: quitChan,
}
// We'll first initialize a set of semaphores to limit our concurrency
// when validating incoming requests in parallel.
v.validationSemaphore = make(chan struct{}, numActiveReqs)
for i := 0; i < numActiveReqs; i++ {
v.validationSemaphore <- struct{}{}
}
return v
}
// InitJobDependencies will wait for a new job slot to become open, and then
// sets up any dependent signals/trigger for the new job.
func (v *ValidationBarrier) InitJobDependencies(job interface{}) (JobID,
error) {
// We'll wait for either a new slot to become open, or for the quit
// channel to be closed.
select {
case <-v.validationSemaphore:
case <-v.quit:
}
v.Lock()
defer v.Unlock()
// updateOrCreateJobInfo modifies the set of activeParentJobs for this
// annID and updates jobInfoMap.
updateOrCreateJobInfo := func(annID string, annJobID JobID) {
info, ok := v.jobInfoMap[annID]
if ok {
// If an entry already exists for annID, then a job
// related to it is being validated. Add to the set of
// parent job ids. This addition will only affect
// _later_, _child_ jobs for the annID.
info.activeParentJobIDs.Add(annJobID)
return
}
// No entry exists for annID, meaning that we should create
// one.
parentJobSet := fn.NewSet(annJobID)
info = &jobInfo{
activeParentJobIDs: parentJobSet,
activeDependentJobs: fn.NewSet[JobID](),
}
v.jobInfoMap[annID] = info
}
// populateDependencies populates the job dependency mappings (i.e.
// which should complete after another) for the (annID, childJobID)
// tuple.
populateDependencies := func(annID string, childJobID JobID) {
// If there is no entry in the jobInfoMap, we don't have to
// wait on any parent jobs to finish.
info, ok := v.jobInfoMap[annID]
if !ok {
return
}
// We want to see a snapshot of active parent jobs for this
// annID that are already registered in activeParentJobIDs. The
// child job identified by childJobID can only run after these
// parent jobs have run. After grabbing the snapshot, we then
// want to persist a slice of these jobs.
// Create the notification chan that parent jobs will send (or
// close) on when they complete.
jobChan := make(chan struct{})
// Add to set of activeDependentJobs for this annID.
info.activeDependentJobs.Add(childJobID)
// Store in childJobChans. The parent jobs will fetch this chan
// to notify on. The child job will later fetch this chan to
// listen on when WaitForParents is called.
v.childJobChans[childJobID] = jobChan
// Copy over the parent job IDs at this moment for this annID.
// This job must be processed AFTER those parent IDs.
parentJobs := info.activeParentJobIDs.Copy()
// Populate the jobDependencies mapping.
v.jobDependencies[childJobID] = parentJobs
}
// Once a slot is open, we'll examine the message of the job, to see if
// there need to be any dependent barriers set up.
switch msg := job.(type) {
case *lnwire.ChannelAnnouncement1:
id := JobID(v.idCtr.Add(1))
updateOrCreateJobInfo(msg.ShortChannelID.String(), id)
updateOrCreateJobInfo(route.Vertex(msg.NodeID1).String(), id)
updateOrCreateJobInfo(route.Vertex(msg.NodeID2).String(), id)
return id, nil
// Populate the dependency mappings for the below child jobs.
case *lnwire.ChannelUpdate1:
childJobID := JobID(v.idCtr.Add(1))
populateDependencies(msg.ShortChannelID.String(), childJobID)
return childJobID, nil
case *lnwire.NodeAnnouncement:
childJobID := JobID(v.idCtr.Add(1))
populateDependencies(
route.Vertex(msg.NodeID).String(), childJobID,
)
return childJobID, nil
case *lnwire.AnnounceSignatures1:
// TODO(roasbeef): need to wait on chan ann?
// - We can do the above by calling populateDependencies. For
// now, while we evaluate potential side effects, don't do
// anything with childJobID and just return it.
childJobID := JobID(v.idCtr.Add(1))
return childJobID, nil
default:
// An invalid message was passed into InitJobDependencies.
// Return an error.
return JobID(0), errors.New("invalid message")
}
}
// CompleteJob returns a free slot to the set of available job slots. This
// should be called once a job has been fully completed. Otherwise, slots may
// not be returned to the internal scheduling, causing a deadlock when a new
// overflow job is attempted.
func (v *ValidationBarrier) CompleteJob() {
select {
case v.validationSemaphore <- struct{}{}:
case <-v.quit:
}
}
// WaitForParents will block until all parent job dependencies have went
// through the validation pipeline. This allows us a graceful way to run jobs
// in goroutines and still have strict ordering guarantees. If this job doesn't
// have any parent job dependencies, then this function will return
// immediately.
func (v *ValidationBarrier) WaitForParents(childJobID JobID,
job interface{}) error {
var (
ok bool
jobDesc string
parentJobIDs fn.Set[JobID]
annID string
jobChan chan struct{}
)
// Acquire a lock to read ValidationBarrier.
v.Lock()
switch msg := job.(type) {
// Any ChannelUpdate or NodeAnnouncement jobs will need to wait on the
// completion of any active ChannelAnnouncement jobs related to them.
case *lnwire.ChannelUpdate1:
annID = msg.ShortChannelID.String()
parentJobIDs, ok = v.jobDependencies[childJobID]
if !ok {
// If ok is false, it means that this child job never
// had any parent jobs to wait on.
v.Unlock()
return nil
}
jobDesc = fmt.Sprintf("job=lnwire.ChannelUpdate, scid=%v",
msg.ShortChannelID.ToUint64())
case *lnwire.NodeAnnouncement:
annID = route.Vertex(msg.NodeID).String()
parentJobIDs, ok = v.jobDependencies[childJobID]
if !ok {
// If ok is false, it means that this child job never
// had any parent jobs to wait on.
v.Unlock()
return nil
}
jobDesc = fmt.Sprintf("job=lnwire.NodeAnnouncement, pub=%s",
route.Vertex(msg.NodeID))
// Other types of jobs can be executed immediately, so we'll just
// return directly.
case *lnwire.AnnounceSignatures1:
// TODO(roasbeef): need to wait on chan ann?
v.Unlock()
return nil
case *lnwire.ChannelAnnouncement1:
v.Unlock()
return nil
}
// Release the lock once the above read is finished.
v.Unlock()
log.Debugf("Waiting for dependent on %s", jobDesc)
v.Lock()
jobChan, ok = v.childJobChans[childJobID]
if !ok {
v.Unlock()
// The entry may not exist because this job does not depend on
// any parent jobs.
return nil
}
v.Unlock()
for {
select {
case <-v.quit:
return ErrVBarrierShuttingDown
case <-jobChan:
// Every time this is sent on or if it's closed, a
// parent job has finished. The parent jobs have to
// also potentially close the channel because if all
// the parent jobs finish and call SignalDependents
// before the goroutine running WaitForParents has a
// chance to grab the notification chan from
// childJobChans, then the running goroutine will wait
// here for a notification forever. By having the last
// parent job close the notificiation chan, we avoid
// this issue.
// Check and see if we have any parent jobs left. If we
// don't, we can finish up.
v.Lock()
info, found := v.jobInfoMap[annID]
if !found {
v.Unlock()
// No parent job info found, proceed with
// validation.
return nil
}
x := parentJobIDs.Intersect(info.activeParentJobIDs)
v.Unlock()
if x.IsEmpty() {
// The parent jobs have all completed. We can
// proceed with validation.
return nil
}
// If we've reached this point, we are still waiting on
// a parent job to complete.
}
}
}
// SignalDependents signals to any child jobs that this parent job has
// finished.
func (v *ValidationBarrier) SignalDependents(job interface{}, id JobID) error {
v.Lock()
defer v.Unlock()
// removeJob either removes a child job or a parent job. If it is
// removing a child job, then it removes the child's JobID from the set
// of dependent jobs for the announcement ID. If this is removing a
// parent job, then it removes the parentJobID from the set of active
// parent jobs and notifies the child jobs that it has finished
// validating.
removeJob := func(annID string, id JobID, child bool) error {
if child {
// If we're removing a child job, check jobInfoMap and
// remove this job from activeDependentJobs.
info, ok := v.jobInfoMap[annID]
if ok {
info.activeDependentJobs.Remove(id)
}
// Remove the notification chan from childJobChans.
delete(v.childJobChans, id)
// Remove this job's dependency mapping.
delete(v.jobDependencies, id)
return nil
}
// Otherwise, we are removing a parent job.
jobInfo, found := v.jobInfoMap[annID]
if !found {
// NOTE: Some sort of consistency guarantee has been
// broken.
return fmt.Errorf("no job info found for "+
"identifier(%v)", id)
}
jobInfo.activeParentJobIDs.Remove(id)
lastJob := jobInfo.activeParentJobIDs.IsEmpty()
// Notify all dependent jobs that a parent job has completed.
for child := range jobInfo.activeDependentJobs {
notifyChan, ok := v.childJobChans[child]
if !ok {
// NOTE: Some sort of consistency guarantee has
// been broken.
return fmt.Errorf("no job info found for "+
"identifier(%v)", id)
}
// We don't want to block when sending out the signal.
select {
case notifyChan <- struct{}{}:
default:
}
// If this is the last parent job for this annID, also
// close the channel. This is needed because it's
// possible that the parent job cleans up the job
// mappings before the goroutine handling the child job
// has a chance to call WaitForParents and catch the
// signal sent above. We are allowed to close because
// no other parent job will be able to send along the
// channel (or close) as we're removing the entry from
// the jobInfoMap below.
if lastJob {
close(notifyChan)
}
}
// Remove from jobInfoMap if last job.
if lastJob {
delete(v.jobInfoMap, annID)
}
return nil
}
switch msg := job.(type) {
case *lnwire.ChannelAnnouncement1:
// Signal to the child jobs that parent validation has
// finished. We have to call removeJob for each annID
// that this ChannelAnnouncement can be associated with.
err := removeJob(msg.ShortChannelID.String(), id, false)
if err != nil {
return err
}
err = removeJob(route.Vertex(msg.NodeID1).String(), id, false)
if err != nil {
return err
}
err = removeJob(route.Vertex(msg.NodeID2).String(), id, false)
if err != nil {
return err
}
return nil
case *lnwire.NodeAnnouncement:
// Remove child job info.
return removeJob(route.Vertex(msg.NodeID).String(), id, true)
case *lnwire.ChannelUpdate1:
// Remove child job info.
return removeJob(msg.ShortChannelID.String(), id, true)
case *lnwire.AnnounceSignatures1:
// No dependency mappings are stored for AnnounceSignatures1,
// so do nothing.
return nil
}
return errors.New("invalid message - no job dependencies")
}
package feature
import (
"fmt"
"github.com/lightningnetwork/lnd/lnwire"
)
type (
// featureSet contains a set of feature bits.
featureSet map[lnwire.FeatureBit]struct{}
// supportedFeatures maps the feature bit from a feature vector to a
// boolean indicating if this features dependencies have already been
// verified. This allows us to short circuit verification if multiple
// features have common dependencies, or map traversal starts verifying
// from the bottom up.
supportedFeatures map[lnwire.FeatureBit]bool
// depDesc maps a features to its set of dependent features, which must
// also be present for the vector to be valid. This can be used to
// recursively check the dependency chain for features in a feature
// vector.
depDesc map[lnwire.FeatureBit]featureSet
)
// ErrMissingFeatureDep is an error signaling that a transitive dependency in a
// feature vector is not set properly.
type ErrMissingFeatureDep struct {
dep lnwire.FeatureBit
}
// NewErrMissingFeatureDep creates a new ErrMissingFeatureDep error.
func NewErrMissingFeatureDep(dep lnwire.FeatureBit) ErrMissingFeatureDep {
return ErrMissingFeatureDep{dep: dep}
}
// Error returns a human-readable description of the missing dep error.
func (e ErrMissingFeatureDep) Error() string {
return fmt.Sprintf("missing feature dependency: %v", e.dep)
}
// deps is the default set of dependencies for assigned feature bits. If a
// feature is not present in the depDesc it is assumed to have no dependencies.
//
// NOTE: For proper functioning, only the optional variant of feature bits
// should be used in the following descriptor. In the future it may be necessary
// to distinguish the dependencies for optional and required bits, but for now
// the validation code maps required bits to optional ones since it simplifies
// the number of constraints.
var deps = depDesc{
lnwire.PaymentAddrOptional: {
lnwire.TLVOnionPayloadOptional: {},
},
lnwire.MPPOptional: {
lnwire.PaymentAddrOptional: {},
},
lnwire.AnchorsOptional: {
lnwire.StaticRemoteKeyOptional: {},
},
lnwire.AnchorsZeroFeeHtlcTxOptional: {
lnwire.StaticRemoteKeyOptional: {},
},
lnwire.AMPOptional: {
lnwire.PaymentAddrOptional: {},
},
lnwire.ExplicitChannelTypeOptional: {},
lnwire.ScriptEnforcedLeaseOptional: {
lnwire.ExplicitChannelTypeOptional: {},
lnwire.AnchorsZeroFeeHtlcTxOptional: {},
},
lnwire.KeysendOptional: {
lnwire.TLVOnionPayloadOptional: {},
},
lnwire.ZeroConfOptional: {
lnwire.ScidAliasOptional: {},
},
lnwire.SimpleTaprootChannelsOptionalStaging: {
lnwire.AnchorsZeroFeeHtlcTxOptional: {},
lnwire.ExplicitChannelTypeOptional: {},
},
lnwire.SimpleTaprootOverlayChansOptional: {
lnwire.SimpleTaprootChannelsOptionalStaging: {},
lnwire.TLVOnionPayloadOptional: {},
lnwire.ScidAliasOptional: {},
},
lnwire.RouteBlindingOptional: {
lnwire.TLVOnionPayloadOptional: {},
},
lnwire.Bolt11BlindedPathsOptional: {
lnwire.RouteBlindingOptional: {},
},
}
// ValidateDeps asserts that a feature vector sets all features and their
// transitive dependencies properly. It assumes that the dependencies between
// optional and required features are identical, e.g. if a feature is required
// but its dependency is optional, that is sufficient.
func ValidateDeps(fv *lnwire.FeatureVector) error {
features := fv.Features()
supported := initSupported(features)
return validateDeps(features, supported)
}
// SetBit sets the given feature bit on the given feature bit vector along with
// any of its dependencies. If the bit is required, then all the dependencies
// are also set to required, otherwise, the optional dependency bits are set.
// Existing bits are only upgraded from optional to required but never
// downgraded from required to optional.
func SetBit(vector *lnwire.FeatureVector,
bit lnwire.FeatureBit) *lnwire.FeatureVector {
fv := vector.Clone()
// Get the optional version of the bit since that is what the deps map
// uses.
optBit := mapToOptional(bit)
// If the bit we are setting is optional, then we set it (in its
// optional form) and also set all its dependents as optional if they
// are not already set (they may already be set in a required form in
// which case they should not be overridden).
if !bit.IsRequired() {
// Set the bit itself if it does not already exist. We use
// SafeSet here so that if the bit already exists in the
// required form, then this is not overwritten.
_ = fv.SafeSet(bit)
// Do the same for all the dependent bits.
for depBit := range deps[optBit] {
fv = SetBit(fv, depBit)
}
return fv
}
// The bit is required. In this case, we do want to override any
// existing optional bit for both the bit itself and for the dependent
// bits.
fv.Unset(optBit)
fv.Set(bit)
// Do the same for all the dependent bits.
for depBit := range deps[optBit] {
// The deps map only contains the optional versions of bits, so
// there is no need to first map the bit to the optional
// version.
fv.Unset(depBit)
// Set the required version of the bit instead.
fv = SetBit(fv, mapToRequired(depBit))
}
return fv
}
// validateDeps is a subroutine that recursively checks that the passed features
// have all of their associated dependencies in the supported map.
func validateDeps(features featureSet, supported supportedFeatures) error {
for bit := range features {
// Convert any required bits to optional.
bit = mapToOptional(bit)
// If the supported features doesn't contain the dependency, this
// vector is invalid.
checked, ok := supported[bit]
if !ok {
return NewErrMissingFeatureDep(bit)
}
// Alternatively, if we know that this dependency is valid, we
// can short circuit and continue verifying other bits.
if checked {
continue
}
// Recursively validate dependencies, since this method ranges
// over the subDeps. This method will return true even if
// subDeps is nil.
subDeps := deps[bit]
if err := validateDeps(subDeps, supported); err != nil {
return err
}
// Once we've confirmed that this feature's dependencies, if
// any, are sound, we record this so other paths taken through
// `bit` return early when inspecting the supported map.
supported[bit] = true
}
return nil
}
// initSupported sets all bits from the feature vector as supported but not
// checked. This signals that the validity of their dependencies has not been
// verified. All required bits are mapped to optional to simplify the DAG.
func initSupported(features featureSet) supportedFeatures {
supported := make(supportedFeatures)
for bit := range features {
bit = mapToOptional(bit)
supported[bit] = false
}
return supported
}
// mapToOptional returns the optional variant of a given feature bit pair. Our
// dependency graph is described using only optional feature bits, which
// reduces the number of constraints we need to express in the descriptor.
func mapToOptional(bit lnwire.FeatureBit) lnwire.FeatureBit {
if bit.IsRequired() {
bit ^= 0x01
}
return bit
}
// mapToRequired returns the required variant of a given feature bit pair.
func mapToRequired(bit lnwire.FeatureBit) lnwire.FeatureBit {
if bit.IsRequired() {
return bit
}
bit ^= 0x01
return bit
}
package feature
import (
"errors"
"fmt"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// ErrUnknownSet is returned if a proposed feature vector contains a
// set that is unknown to LND.
ErrUnknownSet = errors.New("unknown feature bit set")
// ErrFeatureConfigured is returned if an attempt is made to unset a
// feature that was configured at startup.
ErrFeatureConfigured = errors.New("can't unset configured feature")
)
// Config houses any runtime modifications to the default set descriptors. For
// our purposes, this typically means disabling certain features to test legacy
// protocol interoperability or functionality.
type Config struct {
// NoTLVOnion unsets any optional or required TLVOnionPaylod bits from
// all feature sets.
NoTLVOnion bool
// NoStaticRemoteKey unsets any optional or required StaticRemoteKey
// bits from all feature sets.
NoStaticRemoteKey bool
// NoAnchors unsets any bits signaling support for anchor outputs.
NoAnchors bool
// NoWumbo unsets any bits signalling support for wumbo channels.
NoWumbo bool
// NoTaprootChans unsets any bits signaling support for taproot
// channels.
NoTaprootChans bool
// NoScriptEnforcementLease unsets any bits signaling support for script
// enforced leases.
NoScriptEnforcementLease bool
// NoKeysend unsets any bits signaling support for accepting keysend
// payments.
NoKeysend bool
// NoOptionScidAlias unsets any bits signalling support for
// option_scid_alias. This also implicitly disables zero-conf channels.
NoOptionScidAlias bool
// NoZeroConf unsets any bits signalling support for zero-conf
// channels. This should be used instead of NoOptionScidAlias to still
// keep option-scid-alias support.
NoZeroConf bool
// NoAnySegwit unsets any bits that signal support for using other
// segwit witness versions for co-op closes.
NoAnySegwit bool
// NoRouteBlinding unsets route blinding feature bits.
NoRouteBlinding bool
// NoQuiescence unsets quiescence feature bits.
NoQuiescence bool
// NoTaprootOverlay unsets the taproot overlay channel feature bits.
NoTaprootOverlay bool
// NoExperimentalEndorsement unsets any bits that signal support for
// forwarding experimental endorsement.
NoExperimentalEndorsement bool
// NoRbfCoopClose unsets any bits that signal support for using RBF for
// coop close.
NoRbfCoopClose bool
// CustomFeatures is a set of custom features to advertise in each
// set.
CustomFeatures map[Set][]lnwire.FeatureBit
}
// Manager is responsible for generating feature vectors for different requested
// feature sets.
type Manager struct {
// fsets is a static map of feature set to raw feature vectors. Requests
// are fulfilled by cloning these internal feature vectors.
fsets map[Set]*lnwire.RawFeatureVector
// configFeatures is a set of custom features that were "hard set" in
// lnd's config that cannot be updated at runtime (as is the case with
// our "standard" features that are defined in LND).
configFeatures map[Set]*lnwire.FeatureVector
}
// NewManager creates a new feature Manager, applying any custom modifications
// to its feature sets before returning.
func NewManager(cfg Config) (*Manager, error) {
return newManager(cfg, defaultSetDesc)
}
// newManager creates a new feature Manager, applying any custom modifications
// to its feature sets before returning. This method accepts the setDesc as its
// own parameter so that it can be unit tested.
func newManager(cfg Config, desc setDesc) (*Manager, error) {
// First build the default feature vector for all known sets.
fsets := make(map[Set]*lnwire.RawFeatureVector)
for bit, sets := range desc {
for set := range sets {
// Fetch the feature vector for this set, allocating a
// new one if it doesn't exist.
fv, ok := fsets[set]
if !ok {
fv = lnwire.NewRawFeatureVector()
}
// Set the configured bit on the feature vector,
// ensuring that we don't set two feature bits for the
// same pair.
err := fv.SafeSet(bit)
if err != nil {
return nil, fmt.Errorf("unable to set "+
"%v in %v: %v", bit, set, err)
}
// Write the updated feature vector under its set.
fsets[set] = fv
}
}
// Now, remove any features as directed by the config.
configFeatures := make(map[Set]*lnwire.FeatureVector)
for set, raw := range fsets {
if cfg.NoTLVOnion {
raw.Unset(lnwire.TLVOnionPayloadOptional)
raw.Unset(lnwire.TLVOnionPayloadRequired)
raw.Unset(lnwire.PaymentAddrOptional)
raw.Unset(lnwire.PaymentAddrRequired)
raw.Unset(lnwire.MPPOptional)
raw.Unset(lnwire.MPPRequired)
raw.Unset(lnwire.RouteBlindingOptional)
raw.Unset(lnwire.RouteBlindingRequired)
raw.Unset(lnwire.Bolt11BlindedPathsOptional)
raw.Unset(lnwire.Bolt11BlindedPathsRequired)
raw.Unset(lnwire.AMPOptional)
raw.Unset(lnwire.AMPRequired)
raw.Unset(lnwire.KeysendOptional)
raw.Unset(lnwire.KeysendRequired)
}
if cfg.NoStaticRemoteKey {
raw.Unset(lnwire.StaticRemoteKeyOptional)
raw.Unset(lnwire.StaticRemoteKeyRequired)
}
if cfg.NoAnchors {
raw.Unset(lnwire.AnchorsZeroFeeHtlcTxOptional)
raw.Unset(lnwire.AnchorsZeroFeeHtlcTxRequired)
// If anchors are disabled, then we also need to
// disable all other features that depend on it as
// well, as otherwise we may create an invalid feature
// bit set.
for bit, depFeatures := range deps {
for depFeature := range depFeatures {
switch {
case depFeature == lnwire.AnchorsZeroFeeHtlcTxRequired:
fallthrough
case depFeature == lnwire.AnchorsZeroFeeHtlcTxOptional:
raw.Unset(bit)
}
}
}
}
if cfg.NoWumbo {
raw.Unset(lnwire.WumboChannelsOptional)
raw.Unset(lnwire.WumboChannelsRequired)
}
if cfg.NoScriptEnforcementLease {
raw.Unset(lnwire.ScriptEnforcedLeaseOptional)
raw.Unset(lnwire.ScriptEnforcedLeaseRequired)
}
if cfg.NoKeysend {
raw.Unset(lnwire.KeysendOptional)
raw.Unset(lnwire.KeysendRequired)
}
if cfg.NoOptionScidAlias {
raw.Unset(lnwire.ScidAliasOptional)
raw.Unset(lnwire.ScidAliasRequired)
}
if cfg.NoZeroConf {
raw.Unset(lnwire.ZeroConfOptional)
raw.Unset(lnwire.ZeroConfRequired)
}
if cfg.NoAnySegwit {
raw.Unset(lnwire.ShutdownAnySegwitOptional)
raw.Unset(lnwire.ShutdownAnySegwitRequired)
}
if cfg.NoTaprootChans {
raw.Unset(lnwire.SimpleTaprootChannelsOptionalStaging)
raw.Unset(lnwire.SimpleTaprootChannelsRequiredStaging)
}
if cfg.NoRouteBlinding {
raw.Unset(lnwire.RouteBlindingOptional)
raw.Unset(lnwire.RouteBlindingRequired)
raw.Unset(lnwire.Bolt11BlindedPathsOptional)
raw.Unset(lnwire.Bolt11BlindedPathsRequired)
}
if cfg.NoQuiescence {
raw.Unset(lnwire.QuiescenceOptional)
}
if cfg.NoTaprootOverlay {
raw.Unset(lnwire.SimpleTaprootOverlayChansOptional)
raw.Unset(lnwire.SimpleTaprootOverlayChansRequired)
}
if cfg.NoExperimentalEndorsement {
raw.Unset(lnwire.ExperimentalEndorsementOptional)
raw.Unset(lnwire.ExperimentalEndorsementRequired)
}
if cfg.NoRbfCoopClose {
raw.Unset(lnwire.RbfCoopCloseOptionalStaging)
}
for _, custom := range cfg.CustomFeatures[set] {
if custom > set.Maximum() {
return nil, fmt.Errorf("feature bit: %v "+
"exceeds set: %v maximum: %v", custom,
set, set.Maximum())
}
if raw.IsSet(custom) {
return nil, fmt.Errorf("feature bit: %v "+
"already set", custom)
}
if err := raw.SafeSet(custom); err != nil {
return nil, fmt.Errorf("%w: could not set "+
"feature: %d", err, custom)
}
}
// Track custom features separately so that we can check that
// they aren't unset in subsequent updates. If there is no
// entry for the set, the vector will just be empty.
configFeatures[set] = lnwire.NewFeatureVector(
lnwire.NewRawFeatureVector(cfg.CustomFeatures[set]...),
lnwire.Features,
)
// Ensure that all of our feature sets properly set any
// dependent features.
fv := lnwire.NewFeatureVector(raw, lnwire.Features)
err := ValidateDeps(fv)
if err != nil {
return nil, fmt.Errorf("invalid feature set %v: %w",
set, err)
}
}
return &Manager{
fsets: fsets,
configFeatures: configFeatures,
}, nil
}
// GetRaw returns a raw feature vector for the passed set. If no set is known,
// an empty raw feature vector is returned.
func (m *Manager) GetRaw(set Set) *lnwire.RawFeatureVector {
if fv, ok := m.fsets[set]; ok {
return fv.Clone()
}
return lnwire.NewRawFeatureVector()
}
// setRaw sets a new raw feature vector for the given set.
func (m *Manager) setRaw(set Set, raw *lnwire.RawFeatureVector) {
m.fsets[set] = raw
}
// Get returns a feature vector for the passed set. If no set is known, an empty
// feature vector is returned.
func (m *Manager) Get(set Set) *lnwire.FeatureVector {
raw := m.GetRaw(set)
return lnwire.NewFeatureVector(raw, lnwire.Features)
}
// ListSets returns a list of the feature sets that our node supports.
func (m *Manager) ListSets() []Set {
var sets []Set
for set := range m.fsets {
sets = append(sets, set)
}
return sets
}
// UpdateFeatureSets accepts a map of new feature vectors for each of the
// manager's known sets, validates that the update can be applied and modifies
// the feature manager's internal state. If a set is not included in the update
// map, it is left unchanged. The feature vectors provided are expected to
// include the current set of features, updated with desired bits added/removed.
func (m *Manager) UpdateFeatureSets(
updates map[Set]*lnwire.RawFeatureVector) error {
for set, newFeatures := range updates {
if !set.valid() {
return fmt.Errorf("%w: set: %d", ErrUnknownSet, set)
}
if err := newFeatures.ValidatePairs(); err != nil {
return err
}
if err := m.Get(set).ValidateUpdate(
newFeatures, set.Maximum(),
); err != nil {
return err
}
// If any features were configured for this set, ensure that
// they are still set in the new feature vector.
if cfgFeat, haveCfgFeat := m.configFeatures[set]; haveCfgFeat {
for feature := range cfgFeat.Features() {
if !newFeatures.IsSet(feature) {
return fmt.Errorf("%w: can't unset: "+
"%d", ErrFeatureConfigured,
feature)
}
}
}
fv := lnwire.NewFeatureVector(newFeatures, lnwire.Features)
if err := ValidateDeps(fv); err != nil {
return err
}
}
// Only update the current feature sets once every proposed set has
// passed validation so that we don't partially update any sets then
// fail out on a later set's validation.
for set, features := range updates {
m.setRaw(set, features.Clone())
}
return nil
}
package feature
import (
"fmt"
"github.com/lightningnetwork/lnd/lnwire"
)
// ErrUnknownRequired signals that a feature vector requires certain features
// that our node is unaware of or does not implement.
type ErrUnknownRequired struct {
unknown []lnwire.FeatureBit
}
// NewErrUnknownRequired initializes an ErrUnknownRequired with the unknown
// feature bits.
func NewErrUnknownRequired(unknown []lnwire.FeatureBit) ErrUnknownRequired {
return ErrUnknownRequired{
unknown: unknown,
}
}
// Error returns a human-readable description of the error.
func (e ErrUnknownRequired) Error() string {
return fmt.Sprintf("feature vector contains unknown required "+
"features: %v", e.unknown)
}
// ValidateRequired returns an error if the feature vector contains a non-zero
// number of unknown, required feature bits.
func ValidateRequired(fv *lnwire.FeatureVector) error {
unknown := fv.UnknownRequiredFeatures()
if len(unknown) > 0 {
return NewErrUnknownRequired(unknown)
}
return nil
}
package feature
import (
"math"
"github.com/lightningnetwork/lnd/lnwire"
)
// Set is an enum identifying various feature sets, which separates the single
// feature namespace into distinct categories depending what context a feature
// vector is being used.
type Set uint8
const (
// SetInit identifies features that should be sent in an Init message to
// a remote peer.
SetInit Set = iota
// SetLegacyGlobal identifies features that should be set in the legacy
// GlobalFeatures field of an Init message, which maintains backwards
// compatibility with nodes that haven't implemented flat features.
SetLegacyGlobal
// SetNodeAnn identifies features that should be advertised on node
// announcements.
SetNodeAnn
// SetInvoice identifies features that should be advertised on invoices
// generated by the daemon.
SetInvoice
// SetInvoiceAmp identifies the features that should be advertised on
// AMP invoices generated by the daemon.
SetInvoiceAmp
// setSentinel is used to mark the end of our known sets. This enum
// member must *always* be the last item in the iota list to ensure
// that validation works as expected.
setSentinel
)
// valid returns a boolean indicating whether a set value is one of our
// predefined feature sets.
func (s Set) valid() bool {
return s < setSentinel
}
// String returns a human-readable description of a Set.
func (s Set) String() string {
switch s {
case SetInit:
return "SetInit"
case SetLegacyGlobal:
return "SetLegacyGlobal"
case SetNodeAnn:
return "SetNodeAnn"
case SetInvoice:
return "SetInvoice"
case SetInvoiceAmp:
return "SetInvoiceAmp"
default:
return "SetUnknown"
}
}
// Maximum returns the maximum allowable value for a feature bit in the context
// of a set. The maximum feature value we can express differs by set context
// because the amount of space available varies between protocol messages. In
// practice this should never be a problem (reasonably one would never hit
// these high ranges), but we enforce these maximums for the sake of sane
// validation.
func (s Set) Maximum() lnwire.FeatureBit {
switch s {
case SetInvoice, SetInvoiceAmp:
return lnwire.MaxBolt11Feature
// The space available in other sets is > math.MaxUint16, so we just
// return the maximum value our expression of a feature bit allows so
// that any value will pass.
default:
return math.MaxUint16
}
}
package funding
import (
"bytes"
"context"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
"golang.org/x/sync/errgroup"
)
var (
// errShuttingDown is the error that is returned if a signal on the
// quit channel is received which means the whole server is shutting
// down.
errShuttingDown = errors.New("shutting down")
// emptyChannelID is a channel ID that consists of all zeros.
emptyChannelID = [32]byte{}
)
// batchChannel is a struct that keeps track of a single channel's state within
// the batch funding process.
type batchChannel struct {
fundingReq *InitFundingMsg
pendingChanID [32]byte
updateChan chan *lnrpc.OpenStatusUpdate
errChan chan error
fundingAddr string
chanPoint *wire.OutPoint
isPending bool
}
// processPsbtUpdate processes the first channel update message that is sent
// once the initial part of the negotiation has completed and the funding output
// (and therefore address) is known.
func (c *batchChannel) processPsbtUpdate(u *lnrpc.OpenStatusUpdate) error {
psbtUpdate := u.GetPsbtFund()
if psbtUpdate == nil {
return fmt.Errorf("got unexpected channel update %v", u.Update)
}
if psbtUpdate.FundingAmount != int64(c.fundingReq.LocalFundingAmt) {
return fmt.Errorf("got unexpected funding amount %d, wanted "+
"%d", psbtUpdate.FundingAmount,
c.fundingReq.LocalFundingAmt)
}
c.fundingAddr = psbtUpdate.FundingAddress
return nil
}
// processPendingUpdate is the second channel update message that is sent once
// the negotiation with the peer has completed and the channel is now pending.
func (c *batchChannel) processPendingUpdate(u *lnrpc.OpenStatusUpdate) error {
pendingUpd := u.GetChanPending()
if pendingUpd == nil {
return fmt.Errorf("got unexpected channel update %v", u.Update)
}
hash, err := chainhash.NewHash(pendingUpd.Txid)
if err != nil {
return fmt.Errorf("could not parse outpoint TX hash: %w", err)
}
c.chanPoint = &wire.OutPoint{
Index: pendingUpd.OutputIndex,
Hash: *hash,
}
c.isPending = true
return nil
}
// RequestParser is a function that parses an incoming RPC request into the
// internal funding initialization message.
type RequestParser func(*lnrpc.OpenChannelRequest) (*InitFundingMsg, error)
// ChannelOpener is a function that kicks off the initial channel open
// negotiation with the peer.
type ChannelOpener func(*InitFundingMsg) (chan *lnrpc.OpenStatusUpdate,
chan error)
// ChannelAbandoner is a function that can abandon a channel in the local
// database, graph and arbitrator state.
type ChannelAbandoner func(*wire.OutPoint) error
// WalletKitServer is a local interface that abstracts away the methods we need
// from the wallet kit sub server instance.
type WalletKitServer interface {
// FundPsbt creates a fully populated PSBT that contains enough inputs
// to fund the outputs specified in the template.
FundPsbt(context.Context,
*walletrpc.FundPsbtRequest) (*walletrpc.FundPsbtResponse, error)
// FinalizePsbt expects a partial transaction with all inputs and
// outputs fully declared and tries to sign all inputs that belong to
// the wallet.
FinalizePsbt(context.Context,
*walletrpc.FinalizePsbtRequest) (*walletrpc.FinalizePsbtResponse,
error)
// ReleaseOutput unlocks an output, allowing it to be available for coin
// selection if it remains unspent. The ID should match the one used to
// originally lock the output.
ReleaseOutput(context.Context,
*walletrpc.ReleaseOutputRequest) (*walletrpc.ReleaseOutputResponse,
error)
}
// Wallet is a local interface that abstracts away the methods we need from the
// internal lightning wallet instance.
type Wallet interface {
// PsbtFundingVerify looks up a previously registered funding intent by
// its pending channel ID and tries to advance the state machine by
// verifying the passed PSBT.
PsbtFundingVerify([32]byte, *psbt.Packet, bool) error
// PsbtFundingFinalize looks up a previously registered funding intent
// by its pending channel ID and tries to advance the state machine by
// finalizing the passed PSBT.
PsbtFundingFinalize([32]byte, *psbt.Packet, *wire.MsgTx) error
// PublishTransaction performs cursory validation (dust checks, etc),
// then finally broadcasts the passed transaction to the Bitcoin
// network.
PublishTransaction(*wire.MsgTx, string) error
// CancelFundingIntent allows a caller to cancel a previously registered
// funding intent. If no intent was found, then an error will be
// returned.
CancelFundingIntent([32]byte) error
}
// BatchConfig is the configuration for executing a single batch transaction for
// opening multiple channels atomically.
type BatchConfig struct {
// RequestParser is the function that parses an incoming RPC request
// into the internal funding initialization message.
RequestParser RequestParser
// ChannelOpener is the function that kicks off the initial channel open
// negotiation with the peer.
ChannelOpener ChannelOpener
// ChannelAbandoner is the function that can abandon a channel in the
// local database, graph and arbitrator state.
ChannelAbandoner ChannelAbandoner
// WalletKitServer is an instance of the wallet kit sub server that can
// handle PSBT funding and finalization.
WalletKitServer WalletKitServer
// Wallet is an instance of the internal lightning wallet.
Wallet Wallet
// NetParams contains the current bitcoin network parameters.
NetParams *chaincfg.Params
// Quit is the channel that is selected on to recognize if the main
// server is shutting down.
Quit chan struct{}
}
// Batcher is a type that can be used to perform an atomic funding of multiple
// channels within a single on-chain transaction.
type Batcher struct {
cfg *BatchConfig
channels []*batchChannel
lockedUTXOs []*walletrpc.UtxoLease
didPublish bool
}
// NewBatcher returns a new batch channel funding helper.
func NewBatcher(cfg *BatchConfig) *Batcher {
return &Batcher{
cfg: cfg,
}
}
// BatchFund starts the atomic batch channel funding process.
//
// NOTE: This method should only be called once per instance.
func (b *Batcher) BatchFund(ctx context.Context,
req *lnrpc.BatchOpenChannelRequest) ([]*lnrpc.PendingUpdate, error) {
label, err := labels.ValidateAPI(req.Label)
if err != nil {
return nil, err
}
// Parse and validate each individual channel.
b.channels = make([]*batchChannel, 0, len(req.Channels))
for idx, rpcChannel := range req.Channels {
// If the user specifies a channel ID, it must be exactly 32
// bytes long.
if len(rpcChannel.PendingChanId) > 0 &&
len(rpcChannel.PendingChanId) != 32 {
return nil, fmt.Errorf("invalid temp chan ID %x",
rpcChannel.PendingChanId)
}
var pendingChanID [32]byte
if len(rpcChannel.PendingChanId) == 32 {
copy(pendingChanID[:], rpcChannel.PendingChanId)
// Don't allow the user to be clever by just setting an
// all zero channel ID, we need a "real" value here.
if pendingChanID == emptyChannelID {
return nil, fmt.Errorf("invalid empty temp " +
"chan ID")
}
} else if _, err := rand.Read(pendingChanID[:]); err != nil {
return nil, fmt.Errorf("error making temp chan ID: %w",
err)
}
//nolint:ll
fundingReq, err := b.cfg.RequestParser(&lnrpc.OpenChannelRequest{
SatPerVbyte: uint64(req.SatPerVbyte),
TargetConf: req.TargetConf,
MinConfs: req.MinConfs,
SpendUnconfirmed: req.SpendUnconfirmed,
NodePubkey: rpcChannel.NodePubkey,
LocalFundingAmount: rpcChannel.LocalFundingAmount,
PushSat: rpcChannel.PushSat,
Private: rpcChannel.Private,
MinHtlcMsat: rpcChannel.MinHtlcMsat,
RemoteCsvDelay: rpcChannel.RemoteCsvDelay,
CloseAddress: rpcChannel.CloseAddress,
RemoteMaxValueInFlightMsat: rpcChannel.RemoteMaxValueInFlightMsat,
RemoteMaxHtlcs: rpcChannel.RemoteMaxHtlcs,
MaxLocalCsv: rpcChannel.MaxLocalCsv,
CommitmentType: rpcChannel.CommitmentType,
ZeroConf: rpcChannel.ZeroConf,
ScidAlias: rpcChannel.ScidAlias,
BaseFee: rpcChannel.BaseFee,
FeeRate: rpcChannel.FeeRate,
UseBaseFee: rpcChannel.UseBaseFee,
UseFeeRate: rpcChannel.UseFeeRate,
RemoteChanReserveSat: rpcChannel.RemoteChanReserveSat,
Memo: rpcChannel.Memo,
FundingShim: &lnrpc.FundingShim{
Shim: &lnrpc.FundingShim_PsbtShim{
PsbtShim: &lnrpc.PsbtShim{
PendingChanId: pendingChanID[:],
NoPublish: true,
},
},
},
})
if err != nil {
return nil, fmt.Errorf("error parsing channel %d: %w",
idx, err)
}
// Prepare the stuff that we'll need for the internal PSBT
// funding.
fundingReq.PendingChanID = pendingChanID
fundingReq.ChanFunder = chanfunding.NewPsbtAssembler(
btcutil.Amount(rpcChannel.LocalFundingAmount), nil,
b.cfg.NetParams, false,
)
b.channels = append(b.channels, &batchChannel{
pendingChanID: pendingChanID,
fundingReq: fundingReq,
})
}
// From this point on we can fail for any of the channels and for any
// number of reasons. This deferred function makes sure that the full
// operation is actually atomic: We either succeed and publish a
// transaction for the full batch or we clean up everything.
defer b.cleanup(ctx)
// Now that we know the user input is sane, we need to kick off the
// channel funding negotiation with the peers. Because we specified a
// PSBT assembler, we'll get a special response in the channel once the
// funding output script is known (which we need to craft the TX).
eg := &errgroup.Group{}
for _, channel := range b.channels {
channel.updateChan, channel.errChan = b.cfg.ChannelOpener(
channel.fundingReq,
)
// Launch a goroutine that waits for the initial response on
// either the update or error chan.
channel := channel
eg.Go(func() error {
return b.waitForUpdate(channel, true)
})
}
// Wait for all goroutines to report back. Any error at this stage means
// we need to abort.
if err := eg.Wait(); err != nil {
return nil, fmt.Errorf("error batch opening channel, initial "+
"negotiation failed: %v", err)
}
// We can now assemble all outputs that we're going to give to the PSBT
// funding method of the wallet kit server.
txTemplate := &walletrpc.TxTemplate{
Outputs: make(map[string]uint64),
}
for _, channel := range b.channels {
txTemplate.Outputs[channel.fundingAddr] = uint64(
channel.fundingReq.LocalFundingAmt,
)
}
// Great, we've now started the channel negotiation successfully with
// all peers. This means we know the channel outputs for all channels
// and can craft our PSBT now. We take the fee rate and min conf
// settings from the first request as all of them should be equal
// anyway.
firstReq := b.channels[0].fundingReq
feeRateSatPerVByte := firstReq.FundingFeePerKw.FeePerVByte()
changeType := walletrpc.ChangeAddressType_CHANGE_ADDRESS_TYPE_P2TR
fundPsbtReq := &walletrpc.FundPsbtRequest{
Template: &walletrpc.FundPsbtRequest_Raw{
Raw: txTemplate,
},
Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{
SatPerVbyte: uint64(feeRateSatPerVByte),
},
MinConfs: firstReq.MinConfs,
SpendUnconfirmed: firstReq.MinConfs == 0,
ChangeType: changeType,
CoinSelectionStrategy: req.CoinSelectionStrategy,
}
fundPsbtResp, err := b.cfg.WalletKitServer.FundPsbt(ctx, fundPsbtReq)
if err != nil {
return nil, fmt.Errorf("error funding PSBT for batch channel "+
"open: %v", err)
}
// Funding was successful. This means there are some UTXOs that are now
// locked for us. We need to make sure we release them if we don't
// complete the publish process.
b.lockedUTXOs = fundPsbtResp.LockedUtxos
// Parse and log the funded PSBT for debugging purposes.
unsignedPacket, err := psbt.NewFromRawBytes(
bytes.NewReader(fundPsbtResp.FundedPsbt), false,
)
if err != nil {
return nil, fmt.Errorf("error parsing funded PSBT for batch "+
"channel open: %v", err)
}
log.Tracef("[batchopenchannel] funded PSBT: %s",
base64.StdEncoding.EncodeToString(fundPsbtResp.FundedPsbt))
// With the funded PSBT we can now advance the funding state machine of
// each of the channels.
for _, channel := range b.channels {
err = b.cfg.Wallet.PsbtFundingVerify(
channel.pendingChanID, unsignedPacket, false,
)
if err != nil {
return nil, fmt.Errorf("error verifying PSBT: %w", err)
}
}
// The funded PSBT was accepted by each of the assemblers, let's now
// sign/finalize it.
finalizePsbtResp, err := b.cfg.WalletKitServer.FinalizePsbt(
ctx, &walletrpc.FinalizePsbtRequest{
FundedPsbt: fundPsbtResp.FundedPsbt,
},
)
if err != nil {
return nil, fmt.Errorf("error finalizing PSBT for batch "+
"channel open: %v", err)
}
finalTx := &wire.MsgTx{}
txReader := bytes.NewReader(finalizePsbtResp.RawFinalTx)
if err := finalTx.Deserialize(txReader); err != nil {
return nil, fmt.Errorf("error parsing signed raw TX: %w", err)
}
log.Tracef("[batchopenchannel] signed PSBT: %s",
base64.StdEncoding.EncodeToString(finalizePsbtResp.SignedPsbt))
// Advance the funding state machine of each of the channels a last time
// to complete the negotiation with the now signed funding TX.
for _, channel := range b.channels {
err = b.cfg.Wallet.PsbtFundingFinalize(
channel.pendingChanID, nil, finalTx,
)
if err != nil {
return nil, fmt.Errorf("error finalizing PSBT: %w", err)
}
}
// Now every channel should be ready for the funding transaction to be
// broadcast. Let's wait for the updates that actually confirm this
// state.
eg = &errgroup.Group{}
for _, channel := range b.channels {
// Launch another goroutine that waits for the channel pending
// response on the update chan.
channel := channel
eg.Go(func() error {
return b.waitForUpdate(channel, false)
})
}
// Wait for all updates and make sure we're still good to proceed.
if err := eg.Wait(); err != nil {
return nil, fmt.Errorf("error batch opening channel, final "+
"negotiation failed: %v", err)
}
// Great, we're now finally ready to publish the transaction.
err = b.cfg.Wallet.PublishTransaction(finalTx, label)
if err != nil {
return nil, fmt.Errorf("error publishing final batch "+
"transaction: %v", err)
}
b.didPublish = true
rpcPoints := make([]*lnrpc.PendingUpdate, len(b.channels))
for idx, channel := range b.channels {
rpcPoints[idx] = &lnrpc.PendingUpdate{
Txid: channel.chanPoint.Hash.CloneBytes(),
OutputIndex: channel.chanPoint.Index,
}
}
return rpcPoints, nil
}
// waitForUpdate waits for an incoming channel update (or error) for a single
// channel.
//
// NOTE: Must be called in a goroutine as this blocks until an update or error
// is received.
func (b *Batcher) waitForUpdate(channel *batchChannel, firstUpdate bool) error {
select {
// If an error occurs then immediately return the error to the client.
case err := <-channel.errChan:
log.Errorf("unable to open channel to NodeKey(%x): %v",
channel.fundingReq.TargetPubkey.SerializeCompressed(),
err)
return err
// Otherwise, wait for the next channel update. The first update sent
// must be the signal to start the PSBT funding in our case since we
// specified a PSBT shim. The second update will be the signal that the
// channel is now pending.
case fundingUpdate := <-channel.updateChan:
log.Tracef("[batchopenchannel] received update: %v",
fundingUpdate)
// Depending on what update we were waiting for the batch
// channel knows what to do with it.
if firstUpdate {
return channel.processPsbtUpdate(fundingUpdate)
}
return channel.processPendingUpdate(fundingUpdate)
case <-b.cfg.Quit:
return errShuttingDown
}
}
// cleanup tries to remove any pending state or UTXO locks in case we had to
// abort before finalizing and publishing the funding transaction.
func (b *Batcher) cleanup(ctx context.Context) {
// Did we publish a transaction? Then there's nothing to clean up since
// we succeeded.
if b.didPublish {
return
}
// Make sure the error message doesn't sound too scary. These might be
// logged quite frequently depending on where exactly things were
// aborted. We could just not log any cleanup errors though it might be
// helpful to debug things if something doesn't go as expected.
const errMsgTpl = "Attempted to clean up after failed batch channel " +
"open but could not %s: %v"
// If we failed, we clean up in reverse order. First, let's unlock the
// leased outputs.
for _, lockedUTXO := range b.lockedUTXOs {
rpcOP := &lnrpc.OutPoint{
OutputIndex: lockedUTXO.Outpoint.OutputIndex,
TxidBytes: lockedUTXO.Outpoint.TxidBytes,
TxidStr: lockedUTXO.Outpoint.TxidStr,
}
_, err := b.cfg.WalletKitServer.ReleaseOutput(
ctx, &walletrpc.ReleaseOutputRequest{
Id: lockedUTXO.Id,
Outpoint: rpcOP,
},
)
if err != nil {
log.Debugf(errMsgTpl, "release locked output "+
lockedUTXO.Outpoint.String(), err)
}
}
// Then go through all channels that ever got into a pending state and
// remove the pending channel by abandoning them.
for _, channel := range b.channels {
if !channel.isPending {
continue
}
err := b.cfg.ChannelAbandoner(channel.chanPoint)
if err != nil {
log.Debugf(errMsgTpl, "abandon pending open channel",
err)
}
}
// And finally clean up the funding shim for each channel that didn't
// make it into a pending state.
for _, channel := range b.channels {
if channel.isPending {
continue
}
err := b.cfg.Wallet.CancelFundingIntent(channel.pendingChanID)
if err != nil {
log.Debugf(errMsgTpl, "cancel funding shim", err)
}
}
}
package funding
import (
"errors"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// errUnsupportedCommitmentType is an error returned when a specific
// channel commitment type is being explicitly negotiated but either
// peer of the channel does not support it.
errUnsupportedChannelType = errors.New("requested channel type " +
"not supported")
)
// negotiateCommitmentType negotiates the commitment type of a newly opened
// channel. If a desiredChanType is provided, explicit negotiation for said type
// will be attempted if the set of both local and remote features support it.
// Otherwise, implicit negotiation will be attempted.
//
// The returned ChannelType is nil when implicit negotiation is used. An error
// is only returned if desiredChanType is not supported.
func negotiateCommitmentType(desiredChanType *lnwire.ChannelType, local,
remote *lnwire.FeatureVector) (*lnwire.ChannelType,
lnwallet.CommitmentType, error) {
// BOLT#2 specifies we MUST use explicit negotiation if both peers
// signal for it.
explicitNegotiation := hasFeatures(
local, remote, lnwire.ExplicitChannelTypeOptional,
)
chanTypeRequested := desiredChanType != nil
switch {
case explicitNegotiation && chanTypeRequested:
commitType, err := explicitNegotiateCommitmentType(
*desiredChanType, local, remote,
)
return desiredChanType, commitType, err
// We don't have a specific channel type requested, so we select a
// default type as if implicit negotiation were used, and then we
// explicitly signal that default type.
case explicitNegotiation && !chanTypeRequested:
defaultChanType, commitType := implicitNegotiateCommitmentType(
local, remote,
)
return defaultChanType, commitType, nil
// A specific channel type was requested, but we can't explicitly signal
// it. So if implicit negotiation wouldn't select the desired channel
// type, we must return an error.
case !explicitNegotiation && chanTypeRequested:
implicitChanType, commitType := implicitNegotiateCommitmentType(
local, remote,
)
expected := lnwire.RawFeatureVector(*desiredChanType)
actual := lnwire.RawFeatureVector(*implicitChanType)
if !expected.Equals(&actual) {
return nil, 0, errUnsupportedChannelType
}
return nil, commitType, nil
default: // !explicitNegotiation && !chanTypeRequested
_, commitType := implicitNegotiateCommitmentType(local, remote)
return nil, commitType, nil
}
}
// explicitNegotiateCommitmentType attempts to explicitly negotiate for a
// specific channel type. Since the channel type is comprised of a set of even
// feature bits, we also make sure each feature is supported by both peers. An
// error is returned if either peer does not support said channel type.
func explicitNegotiateCommitmentType(channelType lnwire.ChannelType, local,
remote *lnwire.FeatureVector) (lnwallet.CommitmentType, error) {
channelFeatures := lnwire.RawFeatureVector(channelType)
switch {
// Lease script enforcement + anchors zero fee + static remote key +
// zero conf + scid alias features only.
case channelFeatures.OnlyContains(
lnwire.ZeroConfRequired,
lnwire.ScidAliasRequired,
lnwire.ScriptEnforcedLeaseRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
if !hasFeatures(
local, remote,
lnwire.ZeroConfOptional,
lnwire.ScriptEnforcedLeaseOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.StaticRemoteKeyOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeScriptEnforcedLease, nil
// Anchors zero fee + static remote key + zero conf + scid alias
// features only.
case channelFeatures.OnlyContains(
lnwire.ZeroConfRequired,
lnwire.ScidAliasRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
if !hasFeatures(
local, remote,
lnwire.ZeroConfOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.StaticRemoteKeyOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, nil
// Lease script enforcement + anchors zero fee + static remote key +
// zero conf features only.
case channelFeatures.OnlyContains(
lnwire.ZeroConfRequired,
lnwire.ScriptEnforcedLeaseRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
if !hasFeatures(
local, remote,
lnwire.ZeroConfOptional,
lnwire.ScriptEnforcedLeaseOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.StaticRemoteKeyOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeScriptEnforcedLease, nil
// Anchors zero fee + static remote key + zero conf features only.
case channelFeatures.OnlyContains(
lnwire.ZeroConfRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
if !hasFeatures(
local, remote,
lnwire.ZeroConfOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.StaticRemoteKeyOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, nil
// Lease script enforcement + anchors zero fee + static remote key +
// option-scid-alias features only.
case channelFeatures.OnlyContains(
lnwire.ScidAliasRequired,
lnwire.ScriptEnforcedLeaseRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
if !hasFeatures(
local, remote,
lnwire.ScidAliasOptional,
lnwire.ScriptEnforcedLeaseOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.StaticRemoteKeyOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeScriptEnforcedLease, nil
// Anchors zero fee + static remote key + option-scid-alias features
// only.
case channelFeatures.OnlyContains(
lnwire.ScidAliasRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
if !hasFeatures(
local, remote,
lnwire.ScidAliasOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.StaticRemoteKeyOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, nil
// Lease script enforcement + anchors zero fee + static remote key
// features only.
case channelFeatures.OnlyContains(
lnwire.ScriptEnforcedLeaseRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
if !hasFeatures(
local, remote,
lnwire.ScriptEnforcedLeaseOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.StaticRemoteKeyOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeScriptEnforcedLease, nil
// Anchors zero fee + static remote key features only.
case channelFeatures.OnlyContains(
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
if !hasFeatures(
local, remote,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.StaticRemoteKeyOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, nil
// Static remote key feature only.
case channelFeatures.OnlyContains(lnwire.StaticRemoteKeyRequired):
if !hasFeatures(local, remote, lnwire.StaticRemoteKeyOptional) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeTweakless, nil
// Simple taproot channels only.
case channelFeatures.OnlyContains(
lnwire.SimpleTaprootChannelsRequiredStaging,
):
if !hasFeatures(
local, remote,
lnwire.SimpleTaprootChannelsOptionalStaging,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeSimpleTaproot, nil
// Simple taproot channels with scid only.
case channelFeatures.OnlyContains(
lnwire.SimpleTaprootChannelsRequiredStaging,
lnwire.ScidAliasRequired,
):
if !hasFeatures(
local, remote,
lnwire.SimpleTaprootChannelsOptionalStaging,
lnwire.ScidAliasOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeSimpleTaproot, nil
// Simple taproot channels with zero conf only.
case channelFeatures.OnlyContains(
lnwire.SimpleTaprootChannelsRequiredStaging,
lnwire.ZeroConfRequired,
):
if !hasFeatures(
local, remote,
lnwire.SimpleTaprootChannelsOptionalStaging,
lnwire.ZeroConfOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeSimpleTaproot, nil
// Simple taproot channels with scid and zero conf.
case channelFeatures.OnlyContains(
lnwire.SimpleTaprootChannelsRequiredStaging,
lnwire.ZeroConfRequired,
lnwire.ScidAliasRequired,
):
if !hasFeatures(
local, remote,
lnwire.SimpleTaprootChannelsOptionalStaging,
lnwire.ZeroConfOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeSimpleTaproot, nil
// Simple taproot channels overlay only.
case channelFeatures.OnlyContains(
lnwire.SimpleTaprootOverlayChansRequired,
):
if !hasFeatures(
local, remote,
lnwire.SimpleTaprootOverlayChansOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeSimpleTaprootOverlay, nil
// Simple taproot overlay channels with scid only.
case channelFeatures.OnlyContains(
lnwire.SimpleTaprootOverlayChansRequired,
lnwire.ScidAliasRequired,
):
if !hasFeatures(
local, remote,
lnwire.SimpleTaprootOverlayChansOptional,
lnwire.ScidAliasOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeSimpleTaprootOverlay, nil
// Simple taproot overlay channels with zero conf only.
case channelFeatures.OnlyContains(
lnwire.SimpleTaprootOverlayChansRequired,
lnwire.ZeroConfRequired,
):
if !hasFeatures(
local, remote,
lnwire.SimpleTaprootOverlayChansOptional,
lnwire.ZeroConfOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeSimpleTaprootOverlay, nil
// Simple taproot overlay channels with scid and zero conf.
case channelFeatures.OnlyContains(
lnwire.SimpleTaprootOverlayChansRequired,
lnwire.ZeroConfRequired,
lnwire.ScidAliasRequired,
):
if !hasFeatures(
local, remote,
lnwire.SimpleTaprootOverlayChansOptional,
lnwire.ZeroConfOptional,
lnwire.ScidAliasOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeSimpleTaprootOverlay, nil
// No features, use legacy commitment type.
case channelFeatures.IsEmpty():
return lnwallet.CommitmentTypeLegacy, nil
default:
return 0, errUnsupportedChannelType
}
}
// implicitNegotiateCommitmentType negotiates the commitment type of a channel
// implicitly by choosing the latest type supported by the local and remote
// features.
func implicitNegotiateCommitmentType(local,
remote *lnwire.FeatureVector) (*lnwire.ChannelType,
lnwallet.CommitmentType) {
// If both peers are signalling support for anchor commitments with
// zero-fee HTLC transactions, we'll use this type.
if hasFeatures(local, remote, lnwire.AnchorsZeroFeeHtlcTxOptional) {
chanType := lnwire.ChannelType(*lnwire.NewRawFeatureVector(
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
))
return &chanType, lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx
}
// Since we don't want to support the "legacy" anchor type, we will fall
// back to static remote key if the nodes don't support the zero fee
// HTLC tx anchor type.
//
// If both nodes are signaling the proper feature bit for tweakless
// commitments, we'll use that.
if hasFeatures(local, remote, lnwire.StaticRemoteKeyOptional) {
chanType := lnwire.ChannelType(*lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyRequired,
))
return &chanType, lnwallet.CommitmentTypeTweakless
}
// Otherwise we'll fall back to the legacy type.
chanType := lnwire.ChannelType(*lnwire.NewRawFeatureVector())
return &chanType, lnwallet.CommitmentTypeLegacy
}
// hasFeatures determines whether a set of features is supported by both the set
// of local and remote features.
func hasFeatures(local, remote *lnwire.FeatureVector,
features ...lnwire.FeatureBit) bool {
for _, feature := range features {
if !local.HasFeature(feature) || !remote.HasFeature(feature) {
return false
}
}
return true
}
package funding
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// Subsystem defines the logging code for this subsystem.
const Subsystem = "FNDG"
// log is a logger that is initialized with the btclog.Disabled logger.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all logging output.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package funding
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/chanacceptor"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/discovery"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnpeer"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
"github.com/lightningnetwork/lnd/lnwire"
"golang.org/x/crypto/salsa20"
)
var (
// byteOrder defines the endian-ness we use for encoding to and from
// buffers.
byteOrder = binary.BigEndian
// checkPeerChannelReadyInterval is used when we are waiting for the
// peer to send us ChannelReady. We will check every 1 second to see
// if the message is received.
//
// NOTE: for itest, this value is changed to 10ms.
checkPeerChannelReadyInterval = 1 * time.Second
// errNoLocalNonce is returned when a local nonce is not found in the
// expected TLV.
errNoLocalNonce = fmt.Errorf("local nonce not found")
// errNoPartialSig is returned when a partial sig is not found in the
// expected TLV.
errNoPartialSig = fmt.Errorf("partial sig not found")
)
// WriteOutpoint writes an outpoint to an io.Writer. This is not the same as
// the channeldb variant as this uses WriteVarBytes for the Hash.
func WriteOutpoint(w io.Writer, o *wire.OutPoint) error {
scratch := make([]byte, 4)
if err := wire.WriteVarBytes(w, 0, o.Hash[:]); err != nil {
return err
}
byteOrder.PutUint32(scratch, o.Index)
_, err := w.Write(scratch)
return err
}
const (
// MinBtcRemoteDelay is the minimum CSV delay we will require the remote
// to use for its commitment transaction.
MinBtcRemoteDelay uint16 = 144
// MaxBtcRemoteDelay is the maximum CSV delay we will require the remote
// to use for its commitment transaction.
MaxBtcRemoteDelay uint16 = 2016
// MinChanFundingSize is the smallest channel that we'll allow to be
// created over the RPC interface.
MinChanFundingSize = btcutil.Amount(20000)
// MaxBtcFundingAmount is a soft-limit of the maximum channel size
// currently accepted on the Bitcoin chain within the Lightning
// Protocol. This limit is defined in BOLT-0002, and serves as an
// initial precautionary limit while implementations are battle tested
// in the real world.
MaxBtcFundingAmount = btcutil.Amount(1<<24) - 1
// MaxBtcFundingAmountWumbo is a soft-limit on the maximum size of wumbo
// channels. This limit is 10 BTC and is the only thing standing between
// you and limitless channel size (apart from 21 million cap).
MaxBtcFundingAmountWumbo = btcutil.Amount(1000000000)
msgBufferSize = 50
// pendingChansLimit is the maximum number of pending channels that we
// can have. After this point, pending channel opens will start to be
// rejected.
pendingChansLimit = 50
)
var (
// ErrFundingManagerShuttingDown is an error returned when attempting to
// process a funding request/message but the funding manager has already
// been signaled to shut down.
ErrFundingManagerShuttingDown = errors.New("funding manager shutting " +
"down")
// ErrConfirmationTimeout is an error returned when we as a responder
// are waiting for a funding transaction to confirm, but too many
// blocks pass without confirmation.
ErrConfirmationTimeout = errors.New("timeout waiting for funding " +
"confirmation")
// errUpfrontShutdownScriptNotSupported is returned if an upfront
// shutdown script is set for a peer that does not support the feature
// bit.
errUpfrontShutdownScriptNotSupported = errors.New("peer does not " +
"support option upfront shutdown script")
zeroID [32]byte
)
// reservationWithCtx encapsulates a pending channel reservation. This wrapper
// struct is used internally within the funding manager to track and progress
// the funding workflow initiated by incoming/outgoing methods from the target
// peer. Additionally, this struct houses a response and error channel which is
// used to respond to the caller in the case a channel workflow is initiated
// via a local signal such as RPC.
//
// TODO(roasbeef): actually use the context package
// - deadlines, etc.
type reservationWithCtx struct {
reservation *lnwallet.ChannelReservation
peer lnpeer.Peer
chanAmt btcutil.Amount
// forwardingPolicy is the policy provided by the initFundingMsg.
forwardingPolicy models.ForwardingPolicy
// Constraints we require for the remote.
remoteCsvDelay uint16
remoteMinHtlc lnwire.MilliSatoshi
remoteMaxValue lnwire.MilliSatoshi
remoteMaxHtlcs uint16
remoteChanReserve btcutil.Amount
// maxLocalCsv is the maximum csv we will accept from the remote.
maxLocalCsv uint16
// channelType is the explicit channel type proposed by the initiator of
// the channel.
channelType *lnwire.ChannelType
updateMtx sync.RWMutex
lastUpdated time.Time
updates chan *lnrpc.OpenStatusUpdate
err chan error
}
// isLocked checks the reservation's timestamp to determine whether it is
// locked.
func (r *reservationWithCtx) isLocked() bool {
r.updateMtx.RLock()
defer r.updateMtx.RUnlock()
// The time zero value represents a locked reservation.
return r.lastUpdated.IsZero()
}
// updateTimestamp updates the reservation's timestamp with the current time.
func (r *reservationWithCtx) updateTimestamp() {
r.updateMtx.Lock()
defer r.updateMtx.Unlock()
r.lastUpdated = time.Now()
}
// InitFundingMsg is sent by an outside subsystem to the funding manager in
// order to kick off a funding workflow with a specified target peer. The
// original request which defines the parameters of the funding workflow are
// embedded within this message giving the funding manager full context w.r.t
// the workflow.
type InitFundingMsg struct {
// Peer is the peer that we want to open a channel to.
Peer lnpeer.Peer
// TargetPubkey is the public key of the peer.
TargetPubkey *btcec.PublicKey
// ChainHash is the target genesis hash for this channel.
ChainHash chainhash.Hash
// SubtractFees set to true means that fees will be subtracted
// from the LocalFundingAmt.
SubtractFees bool
// LocalFundingAmt is the size of the channel.
LocalFundingAmt btcutil.Amount
// BaseFee is the base fee charged for routing payments regardless of
// the number of milli-satoshis sent.
BaseFee *uint64
// FeeRate is the fee rate in ppm (parts per million) that will be
// charged proportionally based on the value of each forwarded HTLC, the
// lowest possible rate is 0 with a granularity of 0.000001
// (millionths).
FeeRate *uint64
// PushAmt is the amount pushed to the counterparty.
PushAmt lnwire.MilliSatoshi
// FundingFeePerKw is the fee for the funding transaction.
FundingFeePerKw chainfee.SatPerKWeight
// Private determines whether or not this channel will be private.
Private bool
// MinHtlcIn is the minimum incoming HTLC that we accept.
MinHtlcIn lnwire.MilliSatoshi
// RemoteCsvDelay is the CSV delay we require for the remote peer.
RemoteCsvDelay uint16
// RemoteChanReserve is the channel reserve we required for the remote
// peer.
RemoteChanReserve btcutil.Amount
// MinConfs indicates the minimum number of confirmations that each
// output selected to fund the channel should satisfy.
MinConfs int32
// ShutdownScript is an optional upfront shutdown script for the
// channel. This value is optional, so may be nil.
ShutdownScript lnwire.DeliveryAddress
// MaxValueInFlight is the maximum amount of coins in MilliSatoshi
// that can be pending within the channel. It only applies to the
// remote party.
MaxValueInFlight lnwire.MilliSatoshi
// MaxHtlcs is the maximum number of HTLCs that the remote peer
// can offer us.
MaxHtlcs uint16
// MaxLocalCsv is the maximum local csv delay we will accept from our
// peer.
MaxLocalCsv uint16
// FundUpToMaxAmt is the maximum amount to try to commit to. If set, the
// MinFundAmt field denotes the acceptable minimum amount to commit to,
// while trying to commit as many coins as possible up to this value.
FundUpToMaxAmt btcutil.Amount
// MinFundAmt must be set iff FundUpToMaxAmt is set. It denotes the
// minimum amount to commit to.
MinFundAmt btcutil.Amount
// Outpoints is a list of client-selected outpoints that should be used
// for funding a channel. If LocalFundingAmt is specified then this
// amount is allocated from the sum of outpoints towards funding. If
// the FundUpToMaxAmt is specified the entirety of selected funds is
// allocated towards channel funding.
Outpoints []wire.OutPoint
// ChanFunder is an optional channel funder that allows the caller to
// control exactly how the channel funding is carried out. If not
// specified, then the default chanfunding.WalletAssembler will be
// used.
ChanFunder chanfunding.Assembler
// PendingChanID is not all zeroes (the default value), then this will
// be the pending channel ID used for the funding flow within the wire
// protocol.
PendingChanID PendingChanID
// ChannelType allows the caller to use an explicit channel type for the
// funding negotiation. This type will only be observed if BOTH sides
// support explicit channel type negotiation.
ChannelType *lnwire.ChannelType
// Memo is any arbitrary information we wish to store locally about the
// channel that will be useful to our future selves.
Memo []byte
// Updates is a channel which updates to the opening status of the
// channel are sent on.
Updates chan *lnrpc.OpenStatusUpdate
// Err is a channel which errors encountered during the funding flow are
// sent on.
Err chan error
}
// fundingMsg is sent by the ProcessFundingMsg function and packages a
// funding-specific lnwire.Message along with the lnpeer.Peer that sent it.
type fundingMsg struct {
msg lnwire.Message
peer lnpeer.Peer
}
// pendingChannels is a map instantiated per-peer which tracks all active
// pending single funded channels indexed by their pending channel identifier,
// which is a set of 32-bytes generated via a CSPRNG.
type pendingChannels map[PendingChanID]*reservationWithCtx
// serializedPubKey is used within the FundingManager's activeReservations list
// to identify the nodes with which the FundingManager is actively working to
// initiate new channels.
type serializedPubKey [33]byte
// newSerializedKey creates a new serialized public key from an instance of a
// live pubkey object.
func newSerializedKey(pubKey *btcec.PublicKey) serializedPubKey {
var s serializedPubKey
copy(s[:], pubKey.SerializeCompressed())
return s
}
// DevConfig specifies configs used for integration test only.
type DevConfig struct {
// ProcessChannelReadyWait is the duration to sleep before processing
// remote node's channel ready message once the channel as been marked
// as `channelReadySent`.
ProcessChannelReadyWait time.Duration
// MaxWaitNumBlocksFundingConf is the maximum number of blocks to wait
// for the funding transaction to be confirmed before forgetting
// channels that aren't initiated by us.
MaxWaitNumBlocksFundingConf uint32
}
// Config defines the configuration for the FundingManager. All elements
// within the configuration MUST be non-nil for the FundingManager to carry out
// its duties.
type Config struct {
// Dev specifies config values used in integration test. For
// production, this config will always be an empty struct.
Dev *DevConfig
// NoWumboChans indicates if we're to reject all incoming wumbo channel
// requests, and also reject all outgoing wumbo channel requests.
NoWumboChans bool
// IDKey is the PublicKey that is used to identify this node within the
// Lightning Network.
IDKey *btcec.PublicKey
// IDKeyLoc is the locator for the key that is used to identify this
// node within the LightningNetwork.
IDKeyLoc keychain.KeyLocator
// Wallet handles the parts of the funding process that involves moving
// funds from on-chain transaction outputs into Lightning channels.
Wallet *lnwallet.LightningWallet
// PublishTransaction facilitates the process of broadcasting a
// transaction to the network.
PublishTransaction func(*wire.MsgTx, string) error
// UpdateLabel updates the label that a transaction has in our wallet,
// overwriting any existing labels.
UpdateLabel func(chainhash.Hash, string) error
// FeeEstimator calculates appropriate fee rates based on historical
// transaction information.
FeeEstimator chainfee.Estimator
// Notifier is used by the FundingManager to determine when the
// channel's funding transaction has been confirmed on the blockchain
// so that the channel creation process can be completed.
Notifier chainntnfs.ChainNotifier
// ChannelDB is the database that keeps track of all channel state.
ChannelDB *channeldb.ChannelStateDB
// SignMessage signs an arbitrary message with a given public key. The
// actual digest signed is the double sha-256 of the message. In the
// case that the private key corresponding to the passed public key
// cannot be located, then an error is returned.
//
// TODO(roasbeef): should instead pass on this responsibility to a
// distinct sub-system?
SignMessage func(keyLoc keychain.KeyLocator,
msg []byte, doubleHash bool) (*ecdsa.Signature, error)
// CurrentNodeAnnouncement should return the latest, fully signed node
// announcement from the backing Lightning Network node with a fresh
// timestamp.
CurrentNodeAnnouncement func() (lnwire.NodeAnnouncement, error)
// SendAnnouncement is used by the FundingManager to send announcement
// messages to the Gossiper to possibly broadcast to the greater
// network. A set of optional message fields can be provided to populate
// any information within the graph that is not included in the gossip
// message.
SendAnnouncement func(msg lnwire.Message,
optionalFields ...discovery.OptionalMsgField) chan error
// NotifyWhenOnline allows the FundingManager to register with a
// subsystem that will notify it when the peer comes online. This is
// used when sending the channelReady message, since it MUST be
// delivered after the funding transaction is confirmed.
//
// NOTE: The peerChan channel must be buffered.
NotifyWhenOnline func(peer [33]byte, peerChan chan<- lnpeer.Peer)
// FindChannel queries the database for the channel with the given
// channel ID. Providing the node's public key is an optimization that
// prevents deserializing and scanning through all possible channels.
FindChannel func(node *btcec.PublicKey,
chanID lnwire.ChannelID) (*channeldb.OpenChannel, error)
// TempChanIDSeed is a cryptographically random string of bytes that's
// used as a seed to generate pending channel ID's.
TempChanIDSeed [32]byte
// DefaultRoutingPolicy is the default routing policy used when
// initially announcing channels.
DefaultRoutingPolicy models.ForwardingPolicy
// DefaultMinHtlcIn is the default minimum incoming htlc value that is
// set as a channel parameter.
DefaultMinHtlcIn lnwire.MilliSatoshi
// NumRequiredConfs is a function closure that helps the funding
// manager decide how many confirmations it should require for a
// channel extended to it. The function is able to take into account
// the amount of the channel, and any funds we'll be pushed in the
// process to determine how many confirmations we'll require.
NumRequiredConfs func(btcutil.Amount, lnwire.MilliSatoshi) uint16
// RequiredRemoteDelay is a function that maps the total amount in a
// proposed channel to the CSV delay that we'll require for the remote
// party. Naturally a larger channel should require a higher CSV delay
// in order to give us more time to claim funds in the case of a
// contract breach.
RequiredRemoteDelay func(btcutil.Amount) uint16
// RequiredRemoteChanReserve is a function closure that, given the
// channel capacity and dust limit, will return an appropriate amount
// for the remote peer's required channel reserve that is to be adhered
// to at all times.
RequiredRemoteChanReserve func(capacity,
dustLimit btcutil.Amount) btcutil.Amount
// RequiredRemoteMaxValue is a function closure that, given the channel
// capacity, returns the amount of MilliSatoshis that our remote peer
// can have in total outstanding HTLCs with us.
RequiredRemoteMaxValue func(btcutil.Amount) lnwire.MilliSatoshi
// RequiredRemoteMaxHTLCs is a function closure that, given the channel
// capacity, returns the number of maximum HTLCs the remote peer can
// offer us.
RequiredRemoteMaxHTLCs func(btcutil.Amount) uint16
// WatchNewChannel is to be called once a new channel enters the final
// funding stage: waiting for on-chain confirmation. This method sends
// the channel to the ChainArbitrator so it can watch for any on-chain
// events related to the channel. We also provide the public key of the
// node we're establishing a channel with for reconnection purposes.
WatchNewChannel func(*channeldb.OpenChannel, *btcec.PublicKey) error
// ReportShortChanID allows the funding manager to report the confirmed
// short channel ID of a formerly pending zero-conf channel to outside
// sub-systems.
ReportShortChanID func(wire.OutPoint) error
// ZombieSweeperInterval is the periodic time interval in which the
// zombie sweeper is run.
ZombieSweeperInterval time.Duration
// ReservationTimeout is the length of idle time that must pass before
// a reservation is considered a zombie.
ReservationTimeout time.Duration
// MinChanSize is the smallest channel size that we'll accept as an
// inbound channel. We have such a parameter, as otherwise, nodes could
// flood us with very small channels that would never really be usable
// due to fees.
MinChanSize btcutil.Amount
// MaxChanSize is the largest channel size that we'll accept as an
// inbound channel. We have such a parameter, so that you may decide how
// WUMBO you would like your channel.
MaxChanSize btcutil.Amount
// MaxPendingChannels is the maximum number of pending channels we
// allow for each peer.
MaxPendingChannels int
// RejectPush is set true if the fundingmanager should reject any
// incoming channels having a non-zero push amount.
RejectPush bool
// MaxLocalCSVDelay is the maximum csv delay we will allow for our
// commit output. Channels that exceed this value will be failed.
MaxLocalCSVDelay uint16
// NotifyOpenChannelEvent informs the ChannelNotifier when channels
// transition from pending open to open.
NotifyOpenChannelEvent func(wire.OutPoint, *btcec.PublicKey) error
// OpenChannelPredicate is a predicate on the lnwire.OpenChannel message
// and on the requesting node's public key that returns a bool which
// tells the funding manager whether or not to accept the channel.
OpenChannelPredicate chanacceptor.ChannelAcceptor
// NotifyPendingOpenChannelEvent informs the ChannelNotifier when
// channels enter a pending state.
NotifyPendingOpenChannelEvent func(wire.OutPoint,
*channeldb.OpenChannel, *btcec.PublicKey) error
// NotifyFundingTimeout informs the ChannelNotifier when a pending-open
// channel times out because the funding transaction hasn't confirmed.
// This is only called for the fundee and only if the channel is
// zero-conf.
NotifyFundingTimeout func(wire.OutPoint, *btcec.PublicKey) error
// EnableUpfrontShutdown specifies whether the upfront shutdown script
// is enabled.
EnableUpfrontShutdown bool
// MaxAnchorsCommitFeeRate is the max commitment fee rate we'll use as
// the initiator for channels of the anchor type.
MaxAnchorsCommitFeeRate chainfee.SatPerKWeight
// DeleteAliasEdge allows the Manager to delete an alias channel edge
// from the graph. It also returns our local to-be-deleted policy.
DeleteAliasEdge func(scid lnwire.ShortChannelID) (
*models.ChannelEdgePolicy, error)
// AliasManager is an implementation of the aliasHandler interface that
// abstracts away the handling of many alias functions.
AliasManager aliasHandler
// IsSweeperOutpoint queries the sweeper store for successfully
// published sweeps. This is useful to decide for the internal wallet
// backed funding flow to not use utxos still being swept by the sweeper
// subsystem.
IsSweeperOutpoint func(wire.OutPoint) bool
// AuxLeafStore is an optional store that can be used to store auxiliary
// leaves for certain custom channel types.
AuxLeafStore fn.Option[lnwallet.AuxLeafStore]
// AuxFundingController is an optional controller that can be used to
// modify the way we handle certain custom channel types. It's also
// able to automatically handle new custom protocol messages related to
// the funding process.
AuxFundingController fn.Option[AuxFundingController]
// AuxSigner is an optional signer that can be used to sign auxiliary
// leaves for certain custom channel types.
AuxSigner fn.Option[lnwallet.AuxSigner]
// AuxResolver is an optional interface that can be used to modify the
// way contracts are resolved.
AuxResolver fn.Option[lnwallet.AuxContractResolver]
}
// Manager acts as an orchestrator/bridge between the wallet's
// 'ChannelReservation' workflow, and the wire protocol's funding initiation
// messages. Any requests to initiate the funding workflow for a channel,
// either kicked-off locally or remotely are handled by the funding manager.
// Once a channel's funding workflow has been completed, any local callers, the
// local peer, and possibly the remote peer are notified of the completion of
// the channel workflow. Additionally, any temporary or permanent access
// controls between the wallet and remote peers are enforced via the funding
// manager.
type Manager struct {
started sync.Once
stopped sync.Once
// cfg is a copy of the configuration struct that the FundingManager
// was initialized with.
cfg *Config
// chanIDKey is a cryptographically random key that's used to generate
// temporary channel ID's.
chanIDKey [32]byte
// chanIDNonce is a nonce that's incremented for each new funding
// reservation created.
chanIDNonce atomic.Uint64
// nonceMtx is a mutex that guards the pendingMusigNonces.
nonceMtx sync.RWMutex
// pendingMusigNonces is used to store the musig2 nonce we generate to
// send funding locked until we receive a funding locked message from
// the remote party. We'll use this to keep track of the nonce we
// generated, so we send the local+remote nonces to the peer state
// machine.
//
// NOTE: This map is protected by the nonceMtx above.
//
// TODO(roasbeef): replace w/ generic concurrent map
pendingMusigNonces map[lnwire.ChannelID]*musig2.Nonces
// activeReservations is a map which houses the state of all pending
// funding workflows.
activeReservations map[serializedPubKey]pendingChannels
// signedReservations is a utility map that maps the permanent channel
// ID of a funding reservation to its temporary channel ID. This is
// required as mid funding flow, we switch to referencing the channel
// by its full channel ID once the commitment transactions have been
// signed by both parties.
signedReservations map[lnwire.ChannelID]PendingChanID
// resMtx guards both of the maps above to ensure that all access is
// goroutine safe.
resMtx sync.RWMutex
// fundingMsgs is a channel that relays fundingMsg structs from
// external sub-systems using the ProcessFundingMsg call.
fundingMsgs chan *fundingMsg
// fundingRequests is a channel used to receive channel initiation
// requests from a local subsystem within the daemon.
fundingRequests chan *InitFundingMsg
localDiscoverySignals *lnutils.SyncMap[lnwire.ChannelID, chan struct{}]
handleChannelReadyBarriers *lnutils.SyncMap[lnwire.ChannelID, struct{}]
quit chan struct{}
wg sync.WaitGroup
}
// channelOpeningState represents the different states a channel can be in
// between the funding transaction has been confirmed and the channel is
// announced to the network and ready to be used.
type channelOpeningState uint8
const (
// markedOpen is the opening state of a channel if the funding
// transaction is confirmed on-chain, but channelReady is not yet
// successfully sent to the other peer.
markedOpen channelOpeningState = iota
// channelReadySent is the opening state of a channel if the
// channelReady message has successfully been sent to the other peer,
// but we still haven't announced the channel to the network.
channelReadySent
// addedToGraph is the opening state of a channel if the channel has
// been successfully added to the graph immediately after the
// channelReady message has been sent, but we still haven't announced
// the channel to the network.
addedToGraph
)
func (c channelOpeningState) String() string {
switch c {
case markedOpen:
return "markedOpen"
case channelReadySent:
return "channelReadySent"
case addedToGraph:
return "addedToGraph"
default:
return "unknown"
}
}
// NewFundingManager creates and initializes a new instance of the
// fundingManager.
func NewFundingManager(cfg Config) (*Manager, error) {
return &Manager{
cfg: &cfg,
chanIDKey: cfg.TempChanIDSeed,
activeReservations: make(
map[serializedPubKey]pendingChannels,
),
signedReservations: make(
map[lnwire.ChannelID][32]byte,
),
fundingMsgs: make(
chan *fundingMsg, msgBufferSize,
),
fundingRequests: make(
chan *InitFundingMsg, msgBufferSize,
),
localDiscoverySignals: &lnutils.SyncMap[
lnwire.ChannelID, chan struct{},
]{},
handleChannelReadyBarriers: &lnutils.SyncMap[
lnwire.ChannelID, struct{},
]{},
pendingMusigNonces: make(
map[lnwire.ChannelID]*musig2.Nonces,
),
quit: make(chan struct{}),
}, nil
}
// Start launches all helper goroutines required for handling requests sent
// to the funding manager.
func (f *Manager) Start() error {
var err error
f.started.Do(func() {
log.Info("Funding manager starting")
err = f.start()
})
return err
}
func (f *Manager) start() error {
// Upon restart, the Funding Manager will check the database to load any
// channels that were waiting for their funding transactions to be
// confirmed on the blockchain at the time when the daemon last went
// down.
// TODO(roasbeef): store height that funding finished?
// * would then replace call below
allChannels, err := f.cfg.ChannelDB.FetchAllChannels()
if err != nil {
return err
}
for _, channel := range allChannels {
chanID := lnwire.NewChanIDFromOutPoint(channel.FundingOutpoint)
// For any channels that were in a pending state when the
// daemon was last connected, the Funding Manager will
// re-initialize the channel barriers, and republish the
// funding transaction if we're the initiator.
if channel.IsPending {
log.Tracef("Loading pending ChannelPoint(%v), "+
"creating chan barrier",
channel.FundingOutpoint)
f.localDiscoverySignals.Store(
chanID, make(chan struct{}),
)
// Rebroadcast the funding transaction for any pending
// channel that we initiated. No error will be returned
// if the transaction already has been broadcast.
chanType := channel.ChanType
if chanType.IsSingleFunder() &&
chanType.HasFundingTx() &&
channel.IsInitiator {
f.rebroadcastFundingTx(channel)
}
} else if channel.ChanType.IsSingleFunder() &&
channel.ChanType.HasFundingTx() &&
channel.IsZeroConf() && channel.IsInitiator &&
!channel.ZeroConfConfirmed() {
// Rebroadcast the funding transaction for unconfirmed
// zero-conf channels if we have the funding tx and are
// also the initiator.
f.rebroadcastFundingTx(channel)
}
// We will restart the funding state machine for all channels,
// which will wait for the channel's funding transaction to be
// confirmed on the blockchain, and transmit the messages
// necessary for the channel to be operational.
f.wg.Add(1)
go f.advanceFundingState(channel, chanID, nil)
}
f.wg.Add(1) // TODO(roasbeef): tune
go f.reservationCoordinator()
return nil
}
// Stop signals all helper goroutines to execute a graceful shutdown. This
// method will block until all goroutines have exited.
func (f *Manager) Stop() error {
f.stopped.Do(func() {
log.Info("Funding manager shutting down...")
defer log.Debug("Funding manager shutdown complete")
close(f.quit)
f.wg.Wait()
})
return nil
}
// rebroadcastFundingTx publishes the funding tx on startup for each
// unconfirmed channel.
func (f *Manager) rebroadcastFundingTx(c *channeldb.OpenChannel) {
var fundingTxBuf bytes.Buffer
err := c.FundingTxn.Serialize(&fundingTxBuf)
if err != nil {
log.Errorf("Unable to serialize funding transaction %v: %v",
c.FundingTxn.TxHash(), err)
// Clear the buffer of any bytes that were written before the
// serialization error to prevent logging an incomplete
// transaction.
fundingTxBuf.Reset()
} else {
log.Debugf("Rebroadcasting funding tx for ChannelPoint(%v): "+
"%x", c.FundingOutpoint, fundingTxBuf.Bytes())
}
// Set a nil short channel ID at this stage because we do not know it
// until our funding tx confirms.
label := labels.MakeLabel(labels.LabelTypeChannelOpen, nil)
err = f.cfg.PublishTransaction(c.FundingTxn, label)
if err != nil {
log.Errorf("Unable to rebroadcast funding tx %x for "+
"ChannelPoint(%v): %v", fundingTxBuf.Bytes(),
c.FundingOutpoint, err)
}
}
// PendingChanID is a type that represents a pending channel ID. This might be
// selected by the caller, but if not, will be automatically selected.
type PendingChanID = [32]byte
// nextPendingChanID returns the next free pending channel ID to be used to
// identify a particular future channel funding workflow.
func (f *Manager) nextPendingChanID() PendingChanID {
// Obtain a fresh nonce. We do this by encoding the incremented nonce.
nextNonce := f.chanIDNonce.Add(1)
var nonceBytes [8]byte
binary.LittleEndian.PutUint64(nonceBytes[:], nextNonce)
// We'll generate the next pending channelID by "encrypting" 32-bytes
// of zeroes which'll extract 32 random bytes from our stream cipher.
var (
nextChanID PendingChanID
zeroes [32]byte
)
salsa20.XORKeyStream(
nextChanID[:], zeroes[:], nonceBytes[:], &f.chanIDKey,
)
return nextChanID
}
// CancelPeerReservations cancels all active reservations associated with the
// passed node. This will ensure any outputs which have been pre committed,
// (and thus locked from coin selection), are properly freed.
func (f *Manager) CancelPeerReservations(nodePub [33]byte) {
log.Debugf("Cancelling all reservations for peer %x", nodePub[:])
f.resMtx.Lock()
defer f.resMtx.Unlock()
// We'll attempt to look up this node in the set of active
// reservations. If they don't have any, then there's no further work
// to be done.
nodeReservations, ok := f.activeReservations[nodePub]
if !ok {
log.Debugf("No active reservations for node: %x", nodePub[:])
return
}
// If they do have any active reservations, then we'll cancel all of
// them (which releases any locked UTXO's), and also delete it from the
// reservation map.
for pendingID, resCtx := range nodeReservations {
if err := resCtx.reservation.Cancel(); err != nil {
log.Errorf("unable to cancel reservation for "+
"node=%x: %v", nodePub[:], err)
}
resCtx.err <- fmt.Errorf("peer disconnected")
delete(nodeReservations, pendingID)
}
// Finally, we'll delete the node itself from the set of reservations.
delete(f.activeReservations, nodePub)
}
// chanIdentifier wraps pending channel ID and channel ID into one struct so
// it's easier to identify a specific channel.
//
// TODO(yy): move to a different package to hide the private fields so direct
// access is disabled.
type chanIdentifier struct {
// tempChanID is the pending channel ID created by the funder when
// initializing the funding flow. For fundee, it's received from the
// `open_channel` message.
tempChanID lnwire.ChannelID
// chanID is the channel ID created by the funder once the
// `accept_channel` message is received. For fundee, it's received from
// the `funding_created` message.
chanID lnwire.ChannelID
// chanIDSet is a boolean indicates whether the active channel ID is
// set for this identifier. For zero conf channels, the `chanID` can be
// all-zero, which is the same as the empty value of `ChannelID`. To
// avoid the confusion, we use this boolean to explicitly signal
// whether the `chanID` is set or not.
chanIDSet bool
}
// newChanIdentifier creates a new chanIdentifier.
func newChanIdentifier(tempChanID lnwire.ChannelID) *chanIdentifier {
return &chanIdentifier{
tempChanID: tempChanID,
}
}
// setChanID updates the `chanIdentifier` with the active channel ID.
func (c *chanIdentifier) setChanID(chanID lnwire.ChannelID) {
c.chanID = chanID
c.chanIDSet = true
}
// hasChanID returns true if the active channel ID has been set.
func (c *chanIdentifier) hasChanID() bool {
return c.chanIDSet
}
// failFundingFlow will fail the active funding flow with the target peer,
// identified by its unique temporary channel ID. This method will send an
// error to the remote peer, and also remove the reservation from our set of
// pending reservations.
//
// TODO(roasbeef): if peer disconnects, and haven't yet broadcast funding
// transaction, then all reservations should be cleared.
func (f *Manager) failFundingFlow(peer lnpeer.Peer, cid *chanIdentifier,
fundingErr error) {
log.Debugf("Failing funding flow for pending_id=%v: %v",
cid.tempChanID, fundingErr)
// First, notify Brontide to remove the pending channel.
//
// NOTE: depending on where we fail the flow, we may not have the
// active channel ID yet.
if cid.hasChanID() {
err := peer.RemovePendingChannel(cid.chanID)
if err != nil {
log.Errorf("Unable to remove channel %v with peer %x: "+
"%v", cid,
peer.IdentityKey().SerializeCompressed(), err)
}
}
ctx, err := f.cancelReservationCtx(
peer.IdentityKey(), cid.tempChanID, false,
)
if err != nil {
log.Errorf("unable to cancel reservation: %v", err)
}
// In case the case where the reservation existed, send the funding
// error on the error channel.
if ctx != nil {
ctx.err <- fundingErr
}
// We only send the exact error if it is part of out whitelisted set of
// errors (lnwire.FundingError or lnwallet.ReservationError).
var msg lnwire.ErrorData
switch e := fundingErr.(type) {
// Let the actual error message be sent to the remote for the
// whitelisted types.
case lnwallet.ReservationError:
msg = lnwire.ErrorData(e.Error())
case lnwire.FundingError:
msg = lnwire.ErrorData(e.Error())
case chanacceptor.ChanAcceptError:
msg = lnwire.ErrorData(e.Error())
// For all other error types we just send a generic error.
default:
msg = lnwire.ErrorData("funding failed due to internal error")
}
errMsg := &lnwire.Error{
ChanID: cid.tempChanID,
Data: msg,
}
log.Debugf("Sending funding error to peer (%x): %v",
peer.IdentityKey().SerializeCompressed(), spew.Sdump(errMsg))
if err := peer.SendMessage(false, errMsg); err != nil {
log.Errorf("unable to send error message to peer %v", err)
}
}
// sendWarning sends a new warning message to the target peer, targeting the
// specified cid with the passed funding error.
func (f *Manager) sendWarning(peer lnpeer.Peer, cid *chanIdentifier,
fundingErr error) {
msg := fundingErr.Error()
errMsg := &lnwire.Warning{
ChanID: cid.tempChanID,
Data: lnwire.WarningData(msg),
}
log.Debugf("Sending funding warning to peer (%x): %v",
peer.IdentityKey().SerializeCompressed(),
spew.Sdump(errMsg),
)
if err := peer.SendMessage(false, errMsg); err != nil {
log.Errorf("unable to send error message to peer %v", err)
}
}
// reservationCoordinator is the primary goroutine tasked with progressing the
// funding workflow between the wallet, and any outside peers or local callers.
//
// NOTE: This MUST be run as a goroutine.
func (f *Manager) reservationCoordinator() {
defer f.wg.Done()
zombieSweepTicker := time.NewTicker(f.cfg.ZombieSweeperInterval)
defer zombieSweepTicker.Stop()
for {
select {
case fmsg := <-f.fundingMsgs:
switch msg := fmsg.msg.(type) {
case *lnwire.OpenChannel:
f.fundeeProcessOpenChannel(fmsg.peer, msg)
case *lnwire.AcceptChannel:
f.funderProcessAcceptChannel(fmsg.peer, msg)
case *lnwire.FundingCreated:
f.fundeeProcessFundingCreated(fmsg.peer, msg)
case *lnwire.FundingSigned:
f.funderProcessFundingSigned(fmsg.peer, msg)
case *lnwire.ChannelReady:
f.wg.Add(1)
go f.handleChannelReady(fmsg.peer, msg)
case *lnwire.Warning:
f.handleWarningMsg(fmsg.peer, msg)
case *lnwire.Error:
f.handleErrorMsg(fmsg.peer, msg)
}
case req := <-f.fundingRequests:
f.handleInitFundingMsg(req)
case <-zombieSweepTicker.C:
f.pruneZombieReservations()
case <-f.quit:
return
}
}
}
// advanceFundingState will advance the channel through the steps after the
// funding transaction is broadcasted, up until the point where the channel is
// ready for operation. This includes waiting for the funding transaction to
// confirm, sending channel_ready to the peer, adding the channel to the graph,
// and announcing the channel. The updateChan can be set non-nil to get
// OpenStatusUpdates.
//
// NOTE: This MUST be run as a goroutine.
func (f *Manager) advanceFundingState(channel *channeldb.OpenChannel,
pendingChanID PendingChanID,
updateChan chan<- *lnrpc.OpenStatusUpdate) {
defer f.wg.Done()
// If the channel is still pending we must wait for the funding
// transaction to confirm.
if channel.IsPending {
err := f.advancePendingChannelState(channel, pendingChanID)
if err != nil {
log.Errorf("Unable to advance pending state of "+
"ChannelPoint(%v): %v",
channel.FundingOutpoint, err)
return
}
}
var chanOpts []lnwallet.ChannelOpt
f.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) {
chanOpts = append(chanOpts, lnwallet.WithLeafStore(s))
})
f.cfg.AuxSigner.WhenSome(func(s lnwallet.AuxSigner) {
chanOpts = append(chanOpts, lnwallet.WithAuxSigner(s))
})
f.cfg.AuxResolver.WhenSome(func(s lnwallet.AuxContractResolver) {
chanOpts = append(chanOpts, lnwallet.WithAuxResolver(s))
})
// We create the state-machine object which wraps the database state.
lnChannel, err := lnwallet.NewLightningChannel(
nil, channel, nil, chanOpts...,
)
if err != nil {
log.Errorf("Unable to create LightningChannel(%v): %v",
channel.FundingOutpoint, err)
return
}
for {
channelState, shortChanID, err := f.getChannelOpeningState(
&channel.FundingOutpoint,
)
if err == channeldb.ErrChannelNotFound {
// Channel not in fundingManager's opening database,
// meaning it was successfully announced to the
// network.
// TODO(halseth): could do graph consistency check
// here, and re-add the edge if missing.
log.Debugf("ChannelPoint(%v) with chan_id=%x not "+
"found in opening database, assuming already "+
"announced to the network",
channel.FundingOutpoint, pendingChanID)
return
} else if err != nil {
log.Errorf("Unable to query database for "+
"channel opening state(%v): %v",
channel.FundingOutpoint, err)
return
}
// If we did find the channel in the opening state database, we
// have seen the funding transaction being confirmed, but there
// are still steps left of the setup procedure. We continue the
// procedure where we left off.
err = f.stateStep(
channel, lnChannel, shortChanID, pendingChanID,
channelState, updateChan,
)
if err != nil {
log.Errorf("Unable to advance state(%v): %v",
channel.FundingOutpoint, err)
return
}
}
}
// stateStep advances the confirmed channel one step in the funding state
// machine. This method is synchronous and the new channel opening state will
// have been written to the database when it successfully returns. The
// updateChan can be set non-nil to get OpenStatusUpdates.
func (f *Manager) stateStep(channel *channeldb.OpenChannel,
lnChannel *lnwallet.LightningChannel,
shortChanID *lnwire.ShortChannelID, pendingChanID PendingChanID,
channelState channelOpeningState,
updateChan chan<- *lnrpc.OpenStatusUpdate) error {
chanID := lnwire.NewChanIDFromOutPoint(channel.FundingOutpoint)
log.Debugf("Channel(%v) with ShortChanID %v has opening state %v",
chanID, shortChanID, channelState)
switch channelState {
// The funding transaction was confirmed, but we did not successfully
// send the channelReady message to the peer, so let's do that now.
case markedOpen:
err := f.sendChannelReady(channel, lnChannel)
if err != nil {
return fmt.Errorf("failed sending channelReady: %w",
err)
}
// As the channelReady message is now sent to the peer, the
// channel is moved to the next state of the state machine. It
// will be moved to the last state (actually deleted from the
// database) after the channel is finally announced.
err = f.saveChannelOpeningState(
&channel.FundingOutpoint, channelReadySent,
shortChanID,
)
if err != nil {
return fmt.Errorf("error setting channel state to"+
" channelReadySent: %w", err)
}
log.Debugf("Channel(%v) with ShortChanID %v: successfully "+
"sent ChannelReady", chanID, shortChanID)
return nil
// channelReady was sent to peer, but the channel was not added to the
// graph and the channel announcement was not sent.
case channelReadySent:
// We must wait until we've received the peer's channel_ready
// before sending a channel_update according to BOLT#07.
received, err := f.receivedChannelReady(
channel.IdentityPub, chanID,
)
if err != nil {
return fmt.Errorf("failed to check if channel_ready "+
"was received: %v", err)
}
if !received {
// We haven't received ChannelReady, so we'll continue
// to the next iteration of the loop after sleeping for
// checkPeerChannelReadyInterval.
select {
case <-time.After(checkPeerChannelReadyInterval):
case <-f.quit:
return ErrFundingManagerShuttingDown
}
return nil
}
return f.handleChannelReadyReceived(
channel, shortChanID, pendingChanID, updateChan,
)
// The channel was added to the Router's topology, but the channel
// announcement was not sent.
case addedToGraph:
if channel.IsZeroConf() {
// If this is a zero-conf channel, then we will wait
// for it to be confirmed before announcing it to the
// greater network.
err := f.waitForZeroConfChannel(channel)
if err != nil {
return fmt.Errorf("failed waiting for zero "+
"channel: %v", err)
}
// Update the local shortChanID variable such that
// annAfterSixConfs uses the confirmed SCID.
confirmedScid := channel.ZeroConfRealScid()
shortChanID = &confirmedScid
}
err := f.annAfterSixConfs(channel, shortChanID)
if err != nil {
return fmt.Errorf("error sending channel "+
"announcement: %v", err)
}
// We delete the channel opening state from our internal
// database as the opening process has succeeded. We can do
// this because we assume the AuthenticatedGossiper queues the
// announcement messages, and persists them in case of a daemon
// shutdown.
err = f.deleteChannelOpeningState(&channel.FundingOutpoint)
if err != nil {
return fmt.Errorf("error deleting channel state: %w",
err)
}
// After the fee parameters have been stored in the
// announcement we can delete them from the database. For
// private channels we do not announce the channel policy to
// the network but still need to delete them from the database.
err = f.deleteInitialForwardingPolicy(chanID)
if err != nil {
log.Infof("Could not delete initial policy for chanId "+
"%x", chanID)
}
log.Debugf("Channel(%v) with ShortChanID %v: successfully "+
"announced", chanID, shortChanID)
return nil
}
return fmt.Errorf("undefined channelState: %v", channelState)
}
// advancePendingChannelState waits for a pending channel's funding tx to
// confirm, and marks it open in the database when that happens.
func (f *Manager) advancePendingChannelState(channel *channeldb.OpenChannel,
pendingChanID PendingChanID) error {
if channel.IsZeroConf() {
// Persist the alias to the alias database.
baseScid := channel.ShortChannelID
err := f.cfg.AliasManager.AddLocalAlias(
baseScid, baseScid, true, false,
)
if err != nil {
return fmt.Errorf("error adding local alias to "+
"store: %v", err)
}
// We don't wait for zero-conf channels to be confirmed and
// instead immediately proceed with the rest of the funding
// flow. The channel opening state is stored under the alias
// SCID.
err = f.saveChannelOpeningState(
&channel.FundingOutpoint, markedOpen,
&channel.ShortChannelID,
)
if err != nil {
return fmt.Errorf("error setting zero-conf channel "+
"state to markedOpen: %v", err)
}
// The ShortChannelID is already set since it's an alias, but
// we still need to mark the channel as no longer pending.
err = channel.MarkAsOpen(channel.ShortChannelID)
if err != nil {
return fmt.Errorf("error setting zero-conf channel's "+
"pending flag to false: %v", err)
}
// Inform the ChannelNotifier that the channel has transitioned
// from pending open to open.
if err := f.cfg.NotifyOpenChannelEvent(
channel.FundingOutpoint, channel.IdentityPub,
); err != nil {
log.Errorf("Unable to notify open channel event for "+
"ChannelPoint(%v): %v",
channel.FundingOutpoint, err)
}
// Find and close the discoverySignal for this channel such
// that ChannelReady messages will be processed.
chanID := lnwire.NewChanIDFromOutPoint(channel.FundingOutpoint)
discoverySignal, ok := f.localDiscoverySignals.Load(chanID)
if ok {
close(discoverySignal)
}
return nil
}
confChannel, err := f.waitForFundingWithTimeout(channel)
if err == ErrConfirmationTimeout {
return f.fundingTimeout(channel, pendingChanID)
} else if err != nil {
return fmt.Errorf("error waiting for funding "+
"confirmation for ChannelPoint(%v): %v",
channel.FundingOutpoint, err)
}
if blockchain.IsCoinBaseTx(confChannel.fundingTx) {
// If it's a coinbase transaction, we need to wait for it to
// mature. We wait out an additional MinAcceptDepth on top of
// the coinbase maturity as an extra margin of safety.
maturity := f.cfg.Wallet.Cfg.NetParams.CoinbaseMaturity
numCoinbaseConfs := uint32(maturity)
if channel.NumConfsRequired > maturity {
numCoinbaseConfs = uint32(channel.NumConfsRequired)
}
txid := &channel.FundingOutpoint.Hash
fundingScript, err := makeFundingScript(channel)
if err != nil {
log.Errorf("unable to create funding script for "+
"ChannelPoint(%v): %v",
channel.FundingOutpoint, err)
return err
}
confNtfn, err := f.cfg.Notifier.RegisterConfirmationsNtfn(
txid, fundingScript, numCoinbaseConfs,
channel.BroadcastHeight(),
)
if err != nil {
log.Errorf("Unable to register for confirmation of "+
"ChannelPoint(%v): %v",
channel.FundingOutpoint, err)
return err
}
select {
case _, ok := <-confNtfn.Confirmed:
if !ok {
return fmt.Errorf("ChainNotifier shutting "+
"down, can't complete funding flow "+
"for ChannelPoint(%v)",
channel.FundingOutpoint)
}
case <-f.quit:
return ErrFundingManagerShuttingDown
}
}
// Success, funding transaction was confirmed.
chanID := lnwire.NewChanIDFromOutPoint(channel.FundingOutpoint)
log.Debugf("ChannelID(%v) is now fully confirmed! "+
"(shortChanID=%v)", chanID, confChannel.shortChanID)
err = f.handleFundingConfirmation(channel, confChannel)
if err != nil {
return fmt.Errorf("unable to handle funding "+
"confirmation for ChannelPoint(%v): %v",
channel.FundingOutpoint, err)
}
return nil
}
// ProcessFundingMsg sends a message to the internal fundingManager goroutine,
// allowing it to handle the lnwire.Message.
func (f *Manager) ProcessFundingMsg(msg lnwire.Message, peer lnpeer.Peer) {
select {
case f.fundingMsgs <- &fundingMsg{msg, peer}:
case <-f.quit:
return
}
}
// fundeeProcessOpenChannel creates an initial 'ChannelReservation' within the
// wallet, then responds to the source peer with an accept channel message
// progressing the funding workflow.
//
// TODO(roasbeef): add error chan to all, let channelManager handle
// error+propagate.
//
//nolint:funlen
func (f *Manager) fundeeProcessOpenChannel(peer lnpeer.Peer,
msg *lnwire.OpenChannel) {
// Check number of pending channels to be smaller than maximum allowed
// number and send ErrorGeneric to remote peer if condition is
// violated.
peerPubKey := peer.IdentityKey()
peerIDKey := newSerializedKey(peerPubKey)
amt := msg.FundingAmount
// We get all pending channels for this peer. This is the list of the
// active reservations and the channels pending open in the database.
f.resMtx.RLock()
reservations := f.activeReservations[peerIDKey]
// We don't count reservations that were created from a canned funding
// shim. The user has registered the shim and therefore expects this
// channel to arrive.
numPending := 0
for _, res := range reservations {
if !res.reservation.IsCannedShim() {
numPending++
}
}
f.resMtx.RUnlock()
// Create the channel identifier.
cid := newChanIdentifier(msg.PendingChannelID)
// Also count the channels that are already pending. There we don't know
// the underlying intent anymore, unfortunately.
channels, err := f.cfg.ChannelDB.FetchOpenChannels(peerPubKey)
if err != nil {
f.failFundingFlow(peer, cid, err)
return
}
for _, c := range channels {
// Pending channels that have a non-zero thaw height were also
// created through a canned funding shim. Those also don't
// count towards the DoS protection limit.
//
// TODO(guggero): Properly store the funding type (wallet, shim,
// PSBT) on the channel so we don't need to use the thaw height.
if c.IsPending && c.ThawHeight == 0 {
numPending++
}
}
// TODO(roasbeef): modify to only accept a _single_ pending channel per
// block unless white listed
if numPending >= f.cfg.MaxPendingChannels {
f.failFundingFlow(peer, cid, lnwire.ErrMaxPendingChannels)
return
}
// Ensure that the pendingChansLimit is respected.
pendingChans, err := f.cfg.ChannelDB.FetchPendingChannels()
if err != nil {
f.failFundingFlow(peer, cid, err)
return
}
if len(pendingChans) > pendingChansLimit {
f.failFundingFlow(peer, cid, lnwire.ErrMaxPendingChannels)
return
}
// We'll also reject any requests to create channels until we're fully
// synced to the network as we won't be able to properly validate the
// confirmation of the funding transaction.
isSynced, _, err := f.cfg.Wallet.IsSynced()
if err != nil || !isSynced {
if err != nil {
log.Errorf("unable to query wallet: %v", err)
}
err := errors.New("Synchronizing blockchain")
f.failFundingFlow(peer, cid, err)
return
}
// Ensure that the remote party respects our maximum channel size.
if amt > f.cfg.MaxChanSize {
f.failFundingFlow(
peer, cid,
lnwallet.ErrChanTooLarge(amt, f.cfg.MaxChanSize),
)
return
}
// We'll, also ensure that the remote party isn't attempting to propose
// a channel that's below our current min channel size.
if amt < f.cfg.MinChanSize {
f.failFundingFlow(
peer, cid,
lnwallet.ErrChanTooSmall(amt, f.cfg.MinChanSize),
)
return
}
// If request specifies non-zero push amount and 'rejectpush' is set,
// signal an error.
if f.cfg.RejectPush && msg.PushAmount > 0 {
f.failFundingFlow(peer, cid, lnwallet.ErrNonZeroPushAmount())
return
}
// Send the OpenChannel request to the ChannelAcceptor to determine
// whether this node will accept the channel.
chanReq := &chanacceptor.ChannelAcceptRequest{
Node: peer.IdentityKey(),
OpenChanMsg: msg,
}
// Query our channel acceptor to determine whether we should reject
// the channel.
acceptorResp := f.cfg.OpenChannelPredicate.Accept(chanReq)
if acceptorResp.RejectChannel() {
f.failFundingFlow(peer, cid, acceptorResp.ChanAcceptError)
return
}
log.Infof("Recv'd fundingRequest(amt=%v, push=%v, delay=%v, "+
"pendingId=%x) from peer(%x)", amt, msg.PushAmount,
msg.CsvDelay, msg.PendingChannelID,
peer.IdentityKey().SerializeCompressed())
// Attempt to initialize a reservation within the wallet. If the wallet
// has insufficient resources to create the channel, then the
// reservation attempt may be rejected. Note that since we're on the
// responding side of a single funder workflow, we don't commit any
// funds to the channel ourselves.
//
// Before we init the channel, we'll also check to see what commitment
// format we can use with this peer. This is dependent on *both* us and
// the remote peer are signaling the proper feature bit if we're using
// implicit negotiation, and simply the channel type sent over if we're
// using explicit negotiation.
chanType, commitType, err := negotiateCommitmentType(
msg.ChannelType, peer.LocalFeatures(), peer.RemoteFeatures(),
)
if err != nil {
// TODO(roasbeef): should be using soft errors
log.Errorf("channel type negotiation failed: %v", err)
f.failFundingFlow(peer, cid, err)
return
}
var scidFeatureVal bool
if hasFeatures(
peer.LocalFeatures(), peer.RemoteFeatures(),
lnwire.ScidAliasOptional,
) {
scidFeatureVal = true
}
var (
zeroConf bool
scid bool
)
// Only echo back a channel type in AcceptChannel if we actually used
// explicit negotiation above.
if chanType != nil {
// Check if the channel type includes the zero-conf or
// scid-alias bits.
featureVec := lnwire.RawFeatureVector(*chanType)
zeroConf = featureVec.IsSet(lnwire.ZeroConfRequired)
scid = featureVec.IsSet(lnwire.ScidAliasRequired)
// If the zero-conf channel type was negotiated, ensure that
// the acceptor allows it.
if zeroConf && !acceptorResp.ZeroConf {
// Fail the funding flow.
flowErr := fmt.Errorf("channel acceptor blocked " +
"zero-conf channel negotiation")
log.Errorf("Cancelling funding flow for %v based on "+
"channel acceptor response: %v", cid, flowErr)
f.failFundingFlow(peer, cid, flowErr)
return
}
// If the zero-conf channel type wasn't negotiated and the
// fundee still wants a zero-conf channel, perform more checks.
// Require that both sides have the scid-alias feature bit set.
// We don't require anchors here - this is for compatibility
// with LDK.
if !zeroConf && acceptorResp.ZeroConf {
if !scidFeatureVal {
// Fail the funding flow.
flowErr := fmt.Errorf("scid-alias feature " +
"must be negotiated for zero-conf")
log.Errorf("Cancelling funding flow for "+
"zero-conf channel %v: %v", cid,
flowErr)
f.failFundingFlow(peer, cid, flowErr)
return
}
// Set zeroConf to true to enable the zero-conf flow.
zeroConf = true
}
}
public := msg.ChannelFlags&lnwire.FFAnnounceChannel != 0
switch {
// Sending the option-scid-alias channel type for a public channel is
// disallowed.
case public && scid:
err = fmt.Errorf("option-scid-alias chantype for public " +
"channel")
log.Errorf("Cancelling funding flow for public channel %v "+
"with scid-alias: %v", cid, err)
f.failFundingFlow(peer, cid, err)
return
// The current variant of taproot channels can only be used with
// unadvertised channels for now.
case commitType.IsTaproot() && public:
err = fmt.Errorf("taproot channel type for public channel")
log.Errorf("Cancelling funding flow for public taproot "+
"channel %v: %v", cid, err)
f.failFundingFlow(peer, cid, err)
return
}
// At this point, if we have an AuxFundingController active, we'll
// check to see if we have a special tapscript root to use in our
// MuSig funding output.
tapscriptRoot, err := fn.MapOptionZ(
f.cfg.AuxFundingController,
func(c AuxFundingController) AuxTapscriptResult {
return c.DeriveTapscriptRoot(msg.PendingChannelID)
},
).Unpack()
if err != nil {
err = fmt.Errorf("error deriving tapscript root: %w", err)
log.Error(err)
f.failFundingFlow(peer, cid, err)
return
}
req := &lnwallet.InitFundingReserveMsg{
ChainHash: &msg.ChainHash,
PendingChanID: msg.PendingChannelID,
NodeID: peer.IdentityKey(),
NodeAddr: peer.Address(),
LocalFundingAmt: 0,
RemoteFundingAmt: amt,
CommitFeePerKw: chainfee.SatPerKWeight(msg.FeePerKiloWeight),
FundingFeePerKw: 0,
PushMSat: msg.PushAmount,
Flags: msg.ChannelFlags,
MinConfs: 1,
CommitType: commitType,
ZeroConf: zeroConf,
OptionScidAlias: scid,
ScidAliasFeature: scidFeatureVal,
TapscriptRoot: tapscriptRoot,
}
reservation, err := f.cfg.Wallet.InitChannelReservation(req)
if err != nil {
log.Errorf("Unable to initialize reservation: %v", err)
f.failFundingFlow(peer, cid, err)
return
}
log.Debugf("Initialized channel reservation: zeroConf=%v, psbt=%v, "+
"cannedShim=%v", reservation.IsZeroConf(),
reservation.IsPsbt(), reservation.IsCannedShim())
if zeroConf {
// Store an alias for zero-conf channels. Other option-scid
// channels will do this at a later point.
aliasScid, err := f.cfg.AliasManager.RequestAlias()
if err != nil {
log.Errorf("Unable to request alias: %v", err)
f.failFundingFlow(peer, cid, err)
return
}
reservation.AddAlias(aliasScid)
}
// As we're the responder, we get to specify the number of confirmations
// that we require before both of us consider the channel open. We'll
// use our mapping to derive the proper number of confirmations based on
// the amount of the channel, and also if any funds are being pushed to
// us. If a depth value was set by our channel acceptor, we will use
// that value instead.
numConfsReq := f.cfg.NumRequiredConfs(msg.FundingAmount, msg.PushAmount)
if acceptorResp.MinAcceptDepth != 0 {
numConfsReq = acceptorResp.MinAcceptDepth
}
// We'll ignore the min_depth calculated above if this is a zero-conf
// channel.
if zeroConf {
numConfsReq = 0
}
reservation.SetNumConfsRequired(numConfsReq)
// We'll also validate and apply all the constraints the initiating
// party is attempting to dictate for our commitment transaction.
stateBounds := &channeldb.ChannelStateBounds{
ChanReserve: msg.ChannelReserve,
MaxPendingAmount: msg.MaxValueInFlight,
MinHTLC: msg.HtlcMinimum,
MaxAcceptedHtlcs: msg.MaxAcceptedHTLCs,
}
commitParams := &channeldb.CommitmentParams{
DustLimit: msg.DustLimit,
CsvDelay: msg.CsvDelay,
}
err = reservation.CommitConstraints(
stateBounds, commitParams, f.cfg.MaxLocalCSVDelay, true,
)
if err != nil {
log.Errorf("Unacceptable channel constraints: %v", err)
f.failFundingFlow(peer, cid, err)
return
}
// Check whether the peer supports upfront shutdown, and get a new
// wallet address if our node is configured to set shutdown addresses by
// default. We use the upfront shutdown script provided by our channel
// acceptor (if any) in lieu of user input.
shutdown, err := getUpfrontShutdownScript(
f.cfg.EnableUpfrontShutdown, peer, acceptorResp.UpfrontShutdown,
f.selectShutdownScript,
)
if err != nil {
f.failFundingFlow(
peer, cid,
fmt.Errorf("getUpfrontShutdownScript error: %w", err),
)
return
}
reservation.SetOurUpfrontShutdown(shutdown)
// If a script enforced channel lease is being proposed, we'll need to
// validate its custom TLV records.
if commitType == lnwallet.CommitmentTypeScriptEnforcedLease {
if msg.LeaseExpiry == nil {
err := errors.New("missing lease expiry")
f.failFundingFlow(peer, cid, err)
return
}
// If we had a shim registered for this channel prior to
// receiving its corresponding OpenChannel message, then we'll
// validate the proposed LeaseExpiry against what was registered
// in our shim.
if reservation.LeaseExpiry() != 0 {
if uint32(*msg.LeaseExpiry) !=
reservation.LeaseExpiry() {
err := errors.New("lease expiry mismatch")
f.failFundingFlow(peer, cid, err)
return
}
}
}
log.Infof("Requiring %v confirmations for pendingChan(%x): "+
"amt=%v, push_amt=%v, committype=%v, upfrontShutdown=%x",
numConfsReq, msg.PendingChannelID, amt, msg.PushAmount,
commitType, msg.UpfrontShutdownScript)
// Generate our required constraints for the remote party, using the
// values provided by the channel acceptor if they are non-zero.
remoteCsvDelay := f.cfg.RequiredRemoteDelay(amt)
if acceptorResp.CSVDelay != 0 {
remoteCsvDelay = acceptorResp.CSVDelay
}
// If our default dust limit was above their ChannelReserve, we change
// it to the ChannelReserve. We must make sure the ChannelReserve we
// send in the AcceptChannel message is above both dust limits.
// Therefore, take the maximum of msg.DustLimit and our dust limit.
//
// NOTE: Even with this bounding, the ChannelAcceptor may return an
// BOLT#02-invalid ChannelReserve.
maxDustLimit := reservation.OurContribution().DustLimit
if msg.DustLimit > maxDustLimit {
maxDustLimit = msg.DustLimit
}
chanReserve := f.cfg.RequiredRemoteChanReserve(amt, maxDustLimit)
if acceptorResp.Reserve != 0 {
chanReserve = acceptorResp.Reserve
}
remoteMaxValue := f.cfg.RequiredRemoteMaxValue(amt)
if acceptorResp.InFlightTotal != 0 {
remoteMaxValue = acceptorResp.InFlightTotal
}
maxHtlcs := f.cfg.RequiredRemoteMaxHTLCs(amt)
if acceptorResp.HtlcLimit != 0 {
maxHtlcs = acceptorResp.HtlcLimit
}
// Default to our default minimum hltc value, replacing it with the
// channel acceptor's value if it is set.
minHtlc := f.cfg.DefaultMinHtlcIn
if acceptorResp.MinHtlcIn != 0 {
minHtlc = acceptorResp.MinHtlcIn
}
// If we are handling a FundingOpen request then we need to specify the
// default channel fees since they are not provided by the responder
// interactively.
ourContribution := reservation.OurContribution()
forwardingPolicy := f.defaultForwardingPolicy(
ourContribution.ChannelStateBounds,
)
// Once the reservation has been created successfully, we add it to
// this peer's map of pending reservations to track this particular
// reservation until either abort or completion.
f.resMtx.Lock()
if _, ok := f.activeReservations[peerIDKey]; !ok {
f.activeReservations[peerIDKey] = make(pendingChannels)
}
resCtx := &reservationWithCtx{
reservation: reservation,
chanAmt: amt,
forwardingPolicy: *forwardingPolicy,
remoteCsvDelay: remoteCsvDelay,
remoteMinHtlc: minHtlc,
remoteMaxValue: remoteMaxValue,
remoteMaxHtlcs: maxHtlcs,
remoteChanReserve: chanReserve,
maxLocalCsv: f.cfg.MaxLocalCSVDelay,
channelType: chanType,
err: make(chan error, 1),
peer: peer,
}
f.activeReservations[peerIDKey][msg.PendingChannelID] = resCtx
f.resMtx.Unlock()
// Update the timestamp once the fundingOpenMsg has been handled.
defer resCtx.updateTimestamp()
cfg := channeldb.ChannelConfig{
ChannelStateBounds: channeldb.ChannelStateBounds{
MaxPendingAmount: remoteMaxValue,
ChanReserve: chanReserve,
MinHTLC: minHtlc,
MaxAcceptedHtlcs: maxHtlcs,
},
CommitmentParams: channeldb.CommitmentParams{
DustLimit: msg.DustLimit,
CsvDelay: remoteCsvDelay,
},
MultiSigKey: keychain.KeyDescriptor{
PubKey: copyPubKey(msg.FundingKey),
},
RevocationBasePoint: keychain.KeyDescriptor{
PubKey: copyPubKey(msg.RevocationPoint),
},
PaymentBasePoint: keychain.KeyDescriptor{
PubKey: copyPubKey(msg.PaymentPoint),
},
DelayBasePoint: keychain.KeyDescriptor{
PubKey: copyPubKey(msg.DelayedPaymentPoint),
},
HtlcBasePoint: keychain.KeyDescriptor{
PubKey: copyPubKey(msg.HtlcPoint),
},
}
// With our parameters set, we'll now process their contribution so we
// can move the funding workflow ahead.
remoteContribution := &lnwallet.ChannelContribution{
FundingAmount: amt,
FirstCommitmentPoint: msg.FirstCommitmentPoint,
ChannelConfig: &cfg,
UpfrontShutdown: msg.UpfrontShutdownScript,
}
if resCtx.reservation.IsTaproot() {
localNonce, err := msg.LocalNonce.UnwrapOrErrV(errNoLocalNonce)
if err != nil {
log.Error(errNoLocalNonce)
f.failFundingFlow(resCtx.peer, cid, errNoLocalNonce)
return
}
remoteContribution.LocalNonce = &musig2.Nonces{
PubNonce: localNonce,
}
}
err = reservation.ProcessSingleContribution(remoteContribution)
if err != nil {
log.Errorf("unable to add contribution reservation: %v", err)
f.failFundingFlow(peer, cid, err)
return
}
log.Infof("Sending fundingResp for pending_id(%x)",
msg.PendingChannelID)
bounds := remoteContribution.ChannelConfig.ChannelStateBounds
log.Debugf("Remote party accepted channel state space bounds: %v",
lnutils.SpewLogClosure(bounds))
params := remoteContribution.ChannelConfig.CommitmentParams
log.Debugf("Remote party accepted commitment rendering params: %v",
lnutils.SpewLogClosure(params))
reservation.SetState(lnwallet.SentAcceptChannel)
// With the initiator's contribution recorded, respond with our
// contribution in the next message of the workflow.
fundingAccept := lnwire.AcceptChannel{
PendingChannelID: msg.PendingChannelID,
DustLimit: ourContribution.DustLimit,
MaxValueInFlight: remoteMaxValue,
ChannelReserve: chanReserve,
MinAcceptDepth: uint32(numConfsReq),
HtlcMinimum: minHtlc,
CsvDelay: remoteCsvDelay,
MaxAcceptedHTLCs: maxHtlcs,
FundingKey: ourContribution.MultiSigKey.PubKey,
RevocationPoint: ourContribution.RevocationBasePoint.PubKey,
PaymentPoint: ourContribution.PaymentBasePoint.PubKey,
DelayedPaymentPoint: ourContribution.DelayBasePoint.PubKey,
HtlcPoint: ourContribution.HtlcBasePoint.PubKey,
FirstCommitmentPoint: ourContribution.FirstCommitmentPoint,
UpfrontShutdownScript: ourContribution.UpfrontShutdown,
ChannelType: chanType,
LeaseExpiry: msg.LeaseExpiry,
}
if commitType.IsTaproot() {
fundingAccept.LocalNonce = lnwire.SomeMusig2Nonce(
ourContribution.LocalNonce.PubNonce,
)
}
if err := peer.SendMessage(true, &fundingAccept); err != nil {
log.Errorf("unable to send funding response to peer: %v", err)
f.failFundingFlow(peer, cid, err)
return
}
}
// funderProcessAcceptChannel processes a response to the workflow initiation
// sent by the remote peer. This message then queues a message with the funding
// outpoint, and a commitment signature to the remote peer.
//
//nolint:funlen
func (f *Manager) funderProcessAcceptChannel(peer lnpeer.Peer,
msg *lnwire.AcceptChannel) {
pendingChanID := msg.PendingChannelID
peerKey := peer.IdentityKey()
var peerKeyBytes []byte
if peerKey != nil {
peerKeyBytes = peerKey.SerializeCompressed()
}
resCtx, err := f.getReservationCtx(peerKey, pendingChanID)
if err != nil {
log.Warnf("Can't find reservation (peerKey:%x, chan_id:%v)",
peerKeyBytes, pendingChanID)
return
}
// Update the timestamp once the fundingAcceptMsg has been handled.
defer resCtx.updateTimestamp()
if resCtx.reservation.State() != lnwallet.SentOpenChannel {
return
}
log.Infof("Recv'd fundingResponse for pending_id(%x)",
pendingChanID[:])
// Create the channel identifier.
cid := newChanIdentifier(msg.PendingChannelID)
// Perform some basic validation of any custom TLV records included.
//
// TODO: Return errors as funding.Error to give context to remote peer?
if resCtx.channelType != nil {
// We'll want to quickly check that the ChannelType echoed by
// the channel request recipient matches what we proposed.
if msg.ChannelType == nil {
err := errors.New("explicit channel type not echoed " +
"back")
f.failFundingFlow(peer, cid, err)
return
}
proposedFeatures := lnwire.RawFeatureVector(*resCtx.channelType)
ackedFeatures := lnwire.RawFeatureVector(*msg.ChannelType)
if !proposedFeatures.Equals(&ackedFeatures) {
err := errors.New("channel type mismatch")
f.failFundingFlow(peer, cid, err)
return
}
// We'll want to do the same with the LeaseExpiry if one should
// be set.
if resCtx.reservation.LeaseExpiry() != 0 {
if msg.LeaseExpiry == nil {
err := errors.New("lease expiry not echoed " +
"back")
f.failFundingFlow(peer, cid, err)
return
}
if uint32(*msg.LeaseExpiry) !=
resCtx.reservation.LeaseExpiry() {
err := errors.New("lease expiry mismatch")
f.failFundingFlow(peer, cid, err)
return
}
}
} else if msg.ChannelType != nil {
// The spec isn't too clear about whether it's okay to set the
// channel type in the accept_channel response if we didn't
// explicitly set it in the open_channel message. For now, we
// check that it's the same type we'd have arrived through
// implicit negotiation. If it's another type, we fail the flow.
_, implicitCommitType := implicitNegotiateCommitmentType(
peer.LocalFeatures(), peer.RemoteFeatures(),
)
_, negotiatedCommitType, err := negotiateCommitmentType(
msg.ChannelType, peer.LocalFeatures(),
peer.RemoteFeatures(),
)
if err != nil {
err := errors.New("received unexpected channel type")
f.failFundingFlow(peer, cid, err)
return
}
if implicitCommitType != negotiatedCommitType {
err := errors.New("negotiated unexpected channel type")
f.failFundingFlow(peer, cid, err)
return
}
}
// The required number of confirmations should not be greater than the
// maximum number of confirmations required by the ChainNotifier to
// properly dispatch confirmations.
if msg.MinAcceptDepth > chainntnfs.MaxNumConfs {
err := lnwallet.ErrNumConfsTooLarge(
msg.MinAcceptDepth, chainntnfs.MaxNumConfs,
)
log.Warnf("Unacceptable channel constraints: %v", err)
f.failFundingFlow(peer, cid, err)
return
}
// Check that zero-conf channels have minimum depth set to 0.
if resCtx.reservation.IsZeroConf() && msg.MinAcceptDepth != 0 {
err = fmt.Errorf("zero-conf channel has min_depth non-zero")
log.Warn(err)
f.failFundingFlow(peer, cid, err)
return
}
// If this is not a zero-conf channel but the peer responded with a
// min-depth of zero, we will use our minimum of 1 instead.
minDepth := msg.MinAcceptDepth
if !resCtx.reservation.IsZeroConf() && minDepth == 0 {
log.Infof("Responder to pending_id=%v sent a minimum "+
"confirmation depth of 0 for non-zero-conf channel. "+
"We will use a minimum depth of 1 instead.",
cid.tempChanID)
minDepth = 1
}
// We'll also specify the responder's preference for the number of
// required confirmations, and also the set of channel constraints
// they've specified for commitment states we can create.
resCtx.reservation.SetNumConfsRequired(uint16(minDepth))
bounds := channeldb.ChannelStateBounds{
ChanReserve: msg.ChannelReserve,
MaxPendingAmount: msg.MaxValueInFlight,
MinHTLC: msg.HtlcMinimum,
MaxAcceptedHtlcs: msg.MaxAcceptedHTLCs,
}
commitParams := channeldb.CommitmentParams{
DustLimit: msg.DustLimit,
CsvDelay: msg.CsvDelay,
}
err = resCtx.reservation.CommitConstraints(
&bounds, &commitParams, resCtx.maxLocalCsv, false,
)
if err != nil {
log.Warnf("Unacceptable channel constraints: %v", err)
f.failFundingFlow(peer, cid, err)
return
}
cfg := channeldb.ChannelConfig{
ChannelStateBounds: channeldb.ChannelStateBounds{
MaxPendingAmount: resCtx.remoteMaxValue,
ChanReserve: resCtx.remoteChanReserve,
MinHTLC: resCtx.remoteMinHtlc,
MaxAcceptedHtlcs: resCtx.remoteMaxHtlcs,
},
CommitmentParams: channeldb.CommitmentParams{
DustLimit: msg.DustLimit,
CsvDelay: resCtx.remoteCsvDelay,
},
MultiSigKey: keychain.KeyDescriptor{
PubKey: copyPubKey(msg.FundingKey),
},
RevocationBasePoint: keychain.KeyDescriptor{
PubKey: copyPubKey(msg.RevocationPoint),
},
PaymentBasePoint: keychain.KeyDescriptor{
PubKey: copyPubKey(msg.PaymentPoint),
},
DelayBasePoint: keychain.KeyDescriptor{
PubKey: copyPubKey(msg.DelayedPaymentPoint),
},
HtlcBasePoint: keychain.KeyDescriptor{
PubKey: copyPubKey(msg.HtlcPoint),
},
}
// The remote node has responded with their portion of the channel
// contribution. At this point, we can process their contribution which
// allows us to construct and sign both the commitment transaction, and
// the funding transaction.
remoteContribution := &lnwallet.ChannelContribution{
FirstCommitmentPoint: msg.FirstCommitmentPoint,
ChannelConfig: &cfg,
UpfrontShutdown: msg.UpfrontShutdownScript,
}
if resCtx.reservation.IsTaproot() {
localNonce, err := msg.LocalNonce.UnwrapOrErrV(errNoLocalNonce)
if err != nil {
log.Error(errNoLocalNonce)
f.failFundingFlow(resCtx.peer, cid, errNoLocalNonce)
return
}
remoteContribution.LocalNonce = &musig2.Nonces{
PubNonce: localNonce,
}
}
err = resCtx.reservation.ProcessContribution(remoteContribution)
// The wallet has detected that a PSBT funding process was requested by
// the user and has halted the funding process after negotiating the
// multisig keys. We now have everything that is needed for the user to
// start constructing a PSBT that sends to the multisig funding address.
var psbtIntent *chanfunding.PsbtIntent
if psbtErr, ok := err.(*lnwallet.PsbtFundingRequired); ok {
// Return the information that is needed by the user to
// construct the PSBT back to the caller.
addr, amt, packet, err := psbtErr.Intent.FundingParams()
if err != nil {
log.Errorf("Unable to process PSBT funding params "+
"for contribution from %x: %v", peerKeyBytes,
err)
f.failFundingFlow(peer, cid, err)
return
}
var buf bytes.Buffer
err = packet.Serialize(&buf)
if err != nil {
log.Errorf("Unable to serialize PSBT for "+
"contribution from %x: %v", peerKeyBytes, err)
f.failFundingFlow(peer, cid, err)
return
}
resCtx.updates <- &lnrpc.OpenStatusUpdate{
PendingChanId: pendingChanID[:],
Update: &lnrpc.OpenStatusUpdate_PsbtFund{
PsbtFund: &lnrpc.ReadyForPsbtFunding{
FundingAddress: addr.EncodeAddress(),
FundingAmount: amt,
Psbt: buf.Bytes(),
},
},
}
psbtIntent = psbtErr.Intent
} else if err != nil {
log.Errorf("Unable to process contribution from %x: %v",
peerKeyBytes, err)
f.failFundingFlow(peer, cid, err)
return
}
log.Infof("pendingChan(%x): remote party proposes num_confs=%v, "+
"csv_delay=%v", pendingChanID[:], msg.MinAcceptDepth,
msg.CsvDelay)
bounds = remoteContribution.ChannelConfig.ChannelStateBounds
log.Debugf("Remote party accepted channel state space bounds: %v",
lnutils.SpewLogClosure(bounds))
commitParams = remoteContribution.ChannelConfig.CommitmentParams
log.Debugf("Remote party accepted commitment rendering params: %v",
lnutils.SpewLogClosure(commitParams))
// If the user requested funding through a PSBT, we cannot directly
// continue now and need to wait for the fully funded and signed PSBT
// to arrive. To not block any other channels from opening, we wait in
// a separate goroutine.
if psbtIntent != nil {
f.wg.Add(1)
go func() {
defer f.wg.Done()
f.waitForPsbt(psbtIntent, resCtx, cid)
}()
// With the new goroutine spawned, we can now exit to unblock
// the main event loop.
return
}
// In a normal, non-PSBT funding flow, we can jump directly to the next
// step where we expect our contribution to be finalized.
f.continueFundingAccept(resCtx, cid)
}
// waitForPsbt blocks until either a signed PSBT arrives, an error occurs or
// the funding manager shuts down. In the case of a valid PSBT, the funding flow
// is continued.
//
// NOTE: This method must be called as a goroutine.
func (f *Manager) waitForPsbt(intent *chanfunding.PsbtIntent,
resCtx *reservationWithCtx, cid *chanIdentifier) {
// failFlow is a helper that logs an error message with the current
// context and then fails the funding flow.
peerKey := resCtx.peer.IdentityKey()
failFlow := func(errMsg string, cause error) {
log.Errorf("Unable to handle funding accept message "+
"for peer_key=%x, pending_chan_id=%x: %s: %v",
peerKey.SerializeCompressed(), cid.tempChanID, errMsg,
cause)
f.failFundingFlow(resCtx.peer, cid, cause)
}
// We'll now wait until the intent has received the final and complete
// funding transaction. If the channel is closed without any error being
// sent, we know everything's going as expected.
select {
case err := <-intent.PsbtReady:
switch err {
// If the user canceled the funding reservation, we need to
// inform the other peer about us canceling the reservation.
case chanfunding.ErrUserCanceled:
failFlow("aborting PSBT flow", err)
return
// If the remote canceled the funding reservation, we don't need
// to send another fail message. But we want to inform the user
// about what happened.
case chanfunding.ErrRemoteCanceled:
log.Infof("Remote canceled, aborting PSBT flow "+
"for peer_key=%x, pending_chan_id=%x",
peerKey.SerializeCompressed(), cid.tempChanID)
return
// Nil error means the flow continues normally now.
case nil:
// For any other error, we'll fail the funding flow.
default:
failFlow("error waiting for PSBT flow", err)
return
}
// At this point, we'll see if there's an AuxFundingDesc we
// need to deliver so the funding process can continue
// properly.
auxFundingDesc, err := fn.MapOptionZ(
f.cfg.AuxFundingController,
func(c AuxFundingController) AuxFundingDescResult {
return c.DescFromPendingChanID(
cid.tempChanID,
lnwallet.NewAuxChanState(
resCtx.reservation.ChanState(),
),
resCtx.reservation.CommitmentKeyRings(),
true,
)
},
).Unpack()
if err != nil {
failFlow("error continuing PSBT flow", err)
return
}
// A non-nil error means we can continue the funding flow.
// Notify the wallet so it can prepare everything we need to
// continue.
//
// We'll also pass along the aux funding controller as well,
// which may be used to help process the finalized PSBT.
err = resCtx.reservation.ProcessPsbt(auxFundingDesc)
if err != nil {
failFlow("error continuing PSBT flow", err)
return
}
// We are now ready to continue the funding flow.
f.continueFundingAccept(resCtx, cid)
// Handle a server shutdown as well because the reservation won't
// survive a restart as it's in memory only.
case <-f.quit:
log.Errorf("Unable to handle funding accept message "+
"for peer_key=%x, pending_chan_id=%x: funding manager "+
"shutting down", peerKey.SerializeCompressed(),
cid.tempChanID)
return
}
}
// continueFundingAccept continues the channel funding flow once our
// contribution is finalized, the channel output is known and the funding
// transaction is signed.
func (f *Manager) continueFundingAccept(resCtx *reservationWithCtx,
cid *chanIdentifier) {
// Now that we have their contribution, we can extract, then send over
// both the funding out point and our signature for their version of
// the commitment transaction to the remote peer.
outPoint := resCtx.reservation.FundingOutpoint()
_, sig := resCtx.reservation.OurSignatures()
// A new channel has almost finished the funding process. In order to
// properly synchronize with the writeHandler goroutine, we add a new
// channel to the barriers map which will be closed once the channel is
// fully open.
channelID := lnwire.NewChanIDFromOutPoint(*outPoint)
log.Debugf("Creating chan barrier for ChanID(%v)", channelID)
// The next message that advances the funding flow will reference the
// channel via its permanent channel ID, so we'll set up this mapping
// so we can retrieve the reservation context once we get the
// FundingSigned message.
f.resMtx.Lock()
f.signedReservations[channelID] = cid.tempChanID
f.resMtx.Unlock()
log.Infof("Generated ChannelPoint(%v) for pending_id(%x)", outPoint,
cid.tempChanID[:])
// Before sending FundingCreated sent, we notify Brontide to keep track
// of this pending open channel.
err := resCtx.peer.AddPendingChannel(channelID, f.quit)
if err != nil {
pubKey := resCtx.peer.IdentityKey().SerializeCompressed()
log.Errorf("Unable to add pending channel %v with peer %x: %v",
channelID, pubKey, err)
}
// Once Brontide is aware of this channel, we need to set it in
// chanIdentifier so this channel will be removed from Brontide if the
// funding flow fails.
cid.setChanID(channelID)
// Send the FundingCreated msg.
fundingCreated := &lnwire.FundingCreated{
PendingChannelID: cid.tempChanID,
FundingPoint: *outPoint,
}
// If this is a taproot channel, then we'll need to populate the musig2
// partial sig field instead of the regular commit sig field.
if resCtx.reservation.IsTaproot() {
partialSig, ok := sig.(*lnwallet.MusigPartialSig)
if !ok {
err := fmt.Errorf("expected musig partial sig, got %T",
sig)
log.Error(err)
f.failFundingFlow(resCtx.peer, cid, err)
return
}
fundingCreated.PartialSig = lnwire.MaybePartialSigWithNonce(
partialSig.ToWireSig(),
)
} else {
fundingCreated.CommitSig, err = lnwire.NewSigFromSignature(sig)
if err != nil {
log.Errorf("Unable to parse signature: %v", err)
f.failFundingFlow(resCtx.peer, cid, err)
return
}
}
resCtx.reservation.SetState(lnwallet.SentFundingCreated)
if err := resCtx.peer.SendMessage(true, fundingCreated); err != nil {
log.Errorf("Unable to send funding complete message: %v", err)
f.failFundingFlow(resCtx.peer, cid, err)
return
}
}
// fundeeProcessFundingCreated progresses the funding workflow when the daemon
// is on the responding side of a single funder workflow. Once this message has
// been processed, a signature is sent to the remote peer allowing it to
// broadcast the funding transaction, progressing the workflow into the final
// stage.
//
//nolint:funlen
func (f *Manager) fundeeProcessFundingCreated(peer lnpeer.Peer,
msg *lnwire.FundingCreated) {
peerKey := peer.IdentityKey()
pendingChanID := msg.PendingChannelID
resCtx, err := f.getReservationCtx(peerKey, pendingChanID)
if err != nil {
log.Warnf("can't find reservation (peer_id:%v, chan_id:%x)",
peerKey, pendingChanID[:])
return
}
// The channel initiator has responded with the funding outpoint of the
// final funding transaction, as well as a signature for our version of
// the commitment transaction. So at this point, we can validate the
// initiator's commitment transaction, then send our own if it's valid.
fundingOut := msg.FundingPoint
log.Infof("completing pending_id(%x) with ChannelPoint(%v)",
pendingChanID[:], fundingOut)
if resCtx.reservation.State() != lnwallet.SentAcceptChannel {
return
}
// Create the channel identifier without setting the active channel ID.
cid := newChanIdentifier(pendingChanID)
// For taproot channels, the commit signature is actually the partial
// signature. Otherwise, we can convert the ECDSA commit signature into
// our internal input.Signature type.
var commitSig input.Signature
if resCtx.reservation.IsTaproot() {
partialSig, err := msg.PartialSig.UnwrapOrErrV(errNoPartialSig)
if err != nil {
f.failFundingFlow(peer, cid, err)
return
}
commitSig = new(lnwallet.MusigPartialSig).FromWireSig(
&partialSig,
)
} else {
commitSig, err = msg.CommitSig.ToSignature()
if err != nil {
log.Errorf("unable to parse signature: %v", err)
f.failFundingFlow(peer, cid, err)
return
}
}
// At this point, we'll see if there's an AuxFundingDesc we need to
// deliver so the funding process can continue properly.
auxFundingDesc, err := fn.MapOptionZ(
f.cfg.AuxFundingController,
func(c AuxFundingController) AuxFundingDescResult {
return c.DescFromPendingChanID(
cid.tempChanID, lnwallet.NewAuxChanState(
resCtx.reservation.ChanState(),
), resCtx.reservation.CommitmentKeyRings(),
true,
)
},
).Unpack()
if err != nil {
log.Errorf("error continuing PSBT flow: %v", err)
f.failFundingFlow(peer, cid, err)
return
}
// With all the necessary data available, attempt to advance the
// funding workflow to the next stage. If this succeeds then the
// funding transaction will broadcast after our next message.
// CompleteReservationSingle will also mark the channel as 'IsPending'
// in the database.
//
// We'll also directly pass in the AuxFunding controller as well,
// which may be used by the reservation system to finalize funding our
// side.
completeChan, err := resCtx.reservation.CompleteReservationSingle(
&fundingOut, commitSig, auxFundingDesc,
)
if err != nil {
log.Errorf("unable to complete single reservation: %v", err)
f.failFundingFlow(peer, cid, err)
return
}
// Get forwarding policy before deleting the reservation context.
forwardingPolicy := resCtx.forwardingPolicy
// The channel is marked IsPending in the database, and can be removed
// from the set of active reservations.
f.deleteReservationCtx(peerKey, cid.tempChanID)
// If something goes wrong before the funding transaction is confirmed,
// we use this convenience method to delete the pending OpenChannel
// from the database.
deleteFromDatabase := func() {
localBalance := completeChan.LocalCommitment.LocalBalance.ToSatoshis()
closeInfo := &channeldb.ChannelCloseSummary{
ChanPoint: completeChan.FundingOutpoint,
ChainHash: completeChan.ChainHash,
RemotePub: completeChan.IdentityPub,
CloseType: channeldb.FundingCanceled,
Capacity: completeChan.Capacity,
SettledBalance: localBalance,
RemoteCurrentRevocation: completeChan.RemoteCurrentRevocation,
RemoteNextRevocation: completeChan.RemoteNextRevocation,
LocalChanConfig: completeChan.LocalChanCfg,
}
// Close the channel with us as the initiator because we are
// deciding to exit the funding flow due to an internal error.
if err := completeChan.CloseChannel(
closeInfo, channeldb.ChanStatusLocalCloseInitiator,
); err != nil {
log.Errorf("Failed closing channel %v: %v",
completeChan.FundingOutpoint, err)
}
}
// A new channel has almost finished the funding process. In order to
// properly synchronize with the writeHandler goroutine, we add a new
// channel to the barriers map which will be closed once the channel is
// fully open.
channelID := lnwire.NewChanIDFromOutPoint(fundingOut)
log.Debugf("Creating chan barrier for ChanID(%v)", channelID)
fundingSigned := &lnwire.FundingSigned{}
// For taproot channels, we'll need to send over a partial signature
// that includes the nonce along side the signature.
_, sig := resCtx.reservation.OurSignatures()
if resCtx.reservation.IsTaproot() {
partialSig, ok := sig.(*lnwallet.MusigPartialSig)
if !ok {
err := fmt.Errorf("expected musig partial sig, got %T",
sig)
log.Error(err)
f.failFundingFlow(resCtx.peer, cid, err)
deleteFromDatabase()
return
}
fundingSigned.PartialSig = lnwire.MaybePartialSigWithNonce(
partialSig.ToWireSig(),
)
} else {
fundingSigned.CommitSig, err = lnwire.NewSigFromSignature(sig)
if err != nil {
log.Errorf("unable to parse signature: %v", err)
f.failFundingFlow(peer, cid, err)
deleteFromDatabase()
return
}
}
// Before sending FundingSigned, we notify Brontide first to keep track
// of this pending open channel.
if err := peer.AddPendingChannel(channelID, f.quit); err != nil {
pubKey := peer.IdentityKey().SerializeCompressed()
log.Errorf("Unable to add pending channel %v with peer %x: %v",
cid.chanID, pubKey, err)
}
// Once Brontide is aware of this channel, we need to set it in
// chanIdentifier so this channel will be removed from Brontide if the
// funding flow fails.
cid.setChanID(channelID)
fundingSigned.ChanID = cid.chanID
log.Infof("sending FundingSigned for pending_id(%x) over "+
"ChannelPoint(%v)", pendingChanID[:], fundingOut)
// With their signature for our version of the commitment transaction
// verified, we can now send over our signature to the remote peer.
if err := peer.SendMessage(true, fundingSigned); err != nil {
log.Errorf("unable to send FundingSigned message: %v", err)
f.failFundingFlow(peer, cid, err)
deleteFromDatabase()
return
}
// With a permanent channel id established we can save the respective
// forwarding policy in the database. In the channel announcement phase
// this forwarding policy is retrieved and applied.
err = f.saveInitialForwardingPolicy(cid.chanID, &forwardingPolicy)
if err != nil {
log.Errorf("Unable to store the forwarding policy: %v", err)
}
// Now that we've sent over our final signature for this channel, we'll
// send it to the ChainArbitrator so it can watch for any on-chain
// actions during this final confirmation stage.
if err := f.cfg.WatchNewChannel(completeChan, peerKey); err != nil {
log.Errorf("Unable to send new ChannelPoint(%v) for "+
"arbitration: %v", fundingOut, err)
}
// Create an entry in the local discovery map so we can ensure that we
// process the channel confirmation fully before we receive a
// channel_ready message.
f.localDiscoverySignals.Store(cid.chanID, make(chan struct{}))
// Inform the ChannelNotifier that the channel has entered
// pending open state.
if err := f.cfg.NotifyPendingOpenChannelEvent(
fundingOut, completeChan, completeChan.IdentityPub,
); err != nil {
log.Errorf("Unable to send pending-open channel event for "+
"ChannelPoint(%v) %v", fundingOut, err)
}
// At this point we have sent our last funding message to the
// initiating peer before the funding transaction will be broadcast.
// With this last message, our job as the responder is now complete.
// We'll wait for the funding transaction to reach the specified number
// of confirmations, then start normal operations.
//
// When we get to this point we have sent the signComplete message to
// the channel funder, and BOLT#2 specifies that we MUST remember the
// channel for reconnection. The channel is already marked
// as pending in the database, so in case of a disconnect or restart,
// we will continue waiting for the confirmation the next time we start
// the funding manager. In case the funding transaction never appears
// on the blockchain, we must forget this channel. We therefore
// completely forget about this channel if we haven't seen the funding
// transaction in 288 blocks (~ 48 hrs), by canceling the reservation
// and canceling the wait for the funding confirmation.
f.wg.Add(1)
go f.advanceFundingState(completeChan, pendingChanID, nil)
}
// funderProcessFundingSigned processes the final message received in a single
// funder workflow. Once this message is processed, the funding transaction is
// broadcast. Once the funding transaction reaches a sufficient number of
// confirmations, a message is sent to the responding peer along with a compact
// encoding of the location of the channel within the blockchain.
func (f *Manager) funderProcessFundingSigned(peer lnpeer.Peer,
msg *lnwire.FundingSigned) {
// As the funding signed message will reference the reservation by its
// permanent channel ID, we'll need to perform an intermediate look up
// before we can obtain the reservation.
f.resMtx.Lock()
pendingChanID, ok := f.signedReservations[msg.ChanID]
delete(f.signedReservations, msg.ChanID)
f.resMtx.Unlock()
// Create the channel identifier and set the channel ID.
//
// NOTE: we may get an empty pending channel ID here if the key cannot
// be found, which means when we cancel the reservation context in
// `failFundingFlow`, we will get an error. In this case, we will send
// an error msg to our peer using the active channel ID.
//
// TODO(yy): refactor the funding flow to fix this case.
cid := newChanIdentifier(pendingChanID)
cid.setChanID(msg.ChanID)
// If the pending channel ID is not found, fail the funding flow.
if !ok {
// NOTE: we directly overwrite the pending channel ID here for
// this rare case since we don't have a valid pending channel
// ID.
cid.tempChanID = msg.ChanID
err := fmt.Errorf("unable to find signed reservation for "+
"chan_id=%x", msg.ChanID)
log.Warnf(err.Error())
f.failFundingFlow(peer, cid, err)
return
}
peerKey := peer.IdentityKey()
resCtx, err := f.getReservationCtx(peerKey, pendingChanID)
if err != nil {
log.Warnf("Unable to find reservation (peer_id:%v, "+
"chan_id:%x)", peerKey, pendingChanID[:])
// TODO: add ErrChanNotFound?
f.failFundingFlow(peer, cid, err)
return
}
if resCtx.reservation.State() != lnwallet.SentFundingCreated {
err := fmt.Errorf("unable to find reservation for chan_id=%x",
msg.ChanID)
f.failFundingFlow(peer, cid, err)
return
}
// Create an entry in the local discovery map so we can ensure that we
// process the channel confirmation fully before we receive a
// channel_ready message.
fundingPoint := resCtx.reservation.FundingOutpoint()
permChanID := lnwire.NewChanIDFromOutPoint(*fundingPoint)
f.localDiscoverySignals.Store(permChanID, make(chan struct{}))
// We have to store the forwardingPolicy before the reservation context
// is deleted. The policy will then be read and applied in
// newChanAnnouncement.
err = f.saveInitialForwardingPolicy(
permChanID, &resCtx.forwardingPolicy,
)
if err != nil {
log.Errorf("Unable to store the forwarding policy: %v", err)
}
// For taproot channels, the commit signature is actually the partial
// signature. Otherwise, we can convert the ECDSA commit signature into
// our internal input.Signature type.
var commitSig input.Signature
if resCtx.reservation.IsTaproot() {
partialSig, err := msg.PartialSig.UnwrapOrErrV(errNoPartialSig)
if err != nil {
f.failFundingFlow(peer, cid, err)
return
}
commitSig = new(lnwallet.MusigPartialSig).FromWireSig(
&partialSig,
)
} else {
commitSig, err = msg.CommitSig.ToSignature()
if err != nil {
log.Errorf("unable to parse signature: %v", err)
f.failFundingFlow(peer, cid, err)
return
}
}
completeChan, err := resCtx.reservation.CompleteReservation(
nil, commitSig,
)
if err != nil {
log.Errorf("Unable to complete reservation sign "+
"complete: %v", err)
f.failFundingFlow(peer, cid, err)
return
}
// The channel is now marked IsPending in the database, and we can
// delete it from our set of active reservations.
f.deleteReservationCtx(peerKey, pendingChanID)
// Broadcast the finalized funding transaction to the network, but only
// if we actually have the funding transaction.
if completeChan.ChanType.HasFundingTx() {
fundingTx := completeChan.FundingTxn
var fundingTxBuf bytes.Buffer
if err := fundingTx.Serialize(&fundingTxBuf); err != nil {
log.Errorf("Unable to serialize funding "+
"transaction %v: %v", fundingTx.TxHash(), err)
// Clear the buffer of any bytes that were written
// before the serialization error to prevent logging an
// incomplete transaction.
fundingTxBuf.Reset()
}
log.Infof("Broadcasting funding tx for ChannelPoint(%v): %x",
completeChan.FundingOutpoint, fundingTxBuf.Bytes())
// Set a nil short channel ID at this stage because we do not
// know it until our funding tx confirms.
label := labels.MakeLabel(
labels.LabelTypeChannelOpen, nil,
)
err = f.cfg.PublishTransaction(fundingTx, label)
if err != nil {
log.Errorf("Unable to broadcast funding tx %x for "+
"ChannelPoint(%v): %v", fundingTxBuf.Bytes(),
completeChan.FundingOutpoint, err)
// We failed to broadcast the funding transaction, but
// watch the channel regardless, in case the
// transaction made it to the network. We will retry
// broadcast at startup.
//
// TODO(halseth): retry more often? Handle with CPFP?
// Just delete from the DB?
}
}
// Before we proceed, if we have a funding hook that wants a
// notification that it's safe to broadcast the funding transaction,
// then we'll send that now.
err = fn.MapOptionZ(
f.cfg.AuxFundingController,
func(controller AuxFundingController) error {
return controller.ChannelFinalized(cid.tempChanID)
},
)
if err != nil {
log.Errorf("Failed to inform aux funding controller about "+
"ChannelPoint(%v) being finalized: %v", fundingPoint,
err)
}
// Now that we have a finalized reservation for this funding flow,
// we'll send the to be active channel to the ChainArbitrator so it can
// watch for any on-chain actions before the channel has fully
// confirmed.
if err := f.cfg.WatchNewChannel(completeChan, peerKey); err != nil {
log.Errorf("Unable to send new ChannelPoint(%v) for "+
"arbitration: %v", fundingPoint, err)
}
log.Infof("Finalizing pending_id(%x) over ChannelPoint(%v), "+
"waiting for channel open on-chain", pendingChanID[:],
fundingPoint)
// Send an update to the upstream client that the negotiation process
// is over.
upd := &lnrpc.OpenStatusUpdate{
Update: &lnrpc.OpenStatusUpdate_ChanPending{
ChanPending: &lnrpc.PendingUpdate{
Txid: fundingPoint.Hash[:],
OutputIndex: fundingPoint.Index,
},
},
PendingChanId: pendingChanID[:],
}
select {
case resCtx.updates <- upd:
// Inform the ChannelNotifier that the channel has entered
// pending open state.
if err := f.cfg.NotifyPendingOpenChannelEvent(
*fundingPoint, completeChan, completeChan.IdentityPub,
); err != nil {
log.Errorf("Unable to send pending-open channel "+
"event for ChannelPoint(%v) %v", fundingPoint,
err)
}
case <-f.quit:
return
}
// At this point we have broadcast the funding transaction and done all
// necessary processing.
f.wg.Add(1)
go f.advanceFundingState(completeChan, pendingChanID, resCtx.updates)
}
// confirmedChannel wraps a confirmed funding transaction, as well as the short
// channel ID which identifies that channel into a single struct. We'll use
// this to pass around the final state of a channel after it has been
// confirmed.
type confirmedChannel struct {
// shortChanID expresses where in the block the funding transaction was
// located.
shortChanID lnwire.ShortChannelID
// fundingTx is the funding transaction that created the channel.
fundingTx *wire.MsgTx
}
// fundingTimeout is called when callers of waitForFundingWithTimeout receive
// an ErrConfirmationTimeout. It is used to clean-up channel state and mark the
// channel as closed. The error is only returned for the responder of the
// channel flow.
func (f *Manager) fundingTimeout(c *channeldb.OpenChannel,
pendingID PendingChanID) error {
// We'll get a timeout if the number of blocks mined since the channel
// was initiated reaches MaxWaitNumBlocksFundingConf and we are not the
// channel initiator.
localBalance := c.LocalCommitment.LocalBalance.ToSatoshis()
closeInfo := &channeldb.ChannelCloseSummary{
ChainHash: c.ChainHash,
ChanPoint: c.FundingOutpoint,
RemotePub: c.IdentityPub,
Capacity: c.Capacity,
SettledBalance: localBalance,
CloseType: channeldb.FundingCanceled,
RemoteCurrentRevocation: c.RemoteCurrentRevocation,
RemoteNextRevocation: c.RemoteNextRevocation,
LocalChanConfig: c.LocalChanCfg,
}
// Close the channel with us as the initiator because we are timing the
// channel out.
if err := c.CloseChannel(
closeInfo, channeldb.ChanStatusLocalCloseInitiator,
); err != nil {
return fmt.Errorf("failed closing channel %v: %w",
c.FundingOutpoint, err)
}
// Notify other subsystems about the funding timeout.
err := f.cfg.NotifyFundingTimeout(c.FundingOutpoint, c.IdentityPub)
if err != nil {
log.Errorf("failed to notify of funding timeout for "+
"ChanPoint(%v): %v", c.FundingOutpoint, err)
}
timeoutErr := fmt.Errorf("timeout waiting for funding tx (%v) to "+
"confirm", c.FundingOutpoint)
// When the peer comes online, we'll notify it that we are now
// considering the channel flow canceled.
f.wg.Add(1)
go func() {
defer f.wg.Done()
peer, err := f.waitForPeerOnline(c.IdentityPub)
switch err {
// We're already shutting down, so we can just return.
case ErrFundingManagerShuttingDown:
return
// nil error means we continue on.
case nil:
// For unexpected errors, we print the error and still try to
// fail the funding flow.
default:
log.Errorf("Unexpected error while waiting for peer "+
"to come online: %v", err)
}
// Create channel identifier and set the channel ID.
cid := newChanIdentifier(pendingID)
cid.setChanID(lnwire.NewChanIDFromOutPoint(c.FundingOutpoint))
// TODO(halseth): should this send be made
// reliable?
// The reservation won't exist at this point, but we'll send an
// Error message over anyways with ChanID set to pendingID.
f.failFundingFlow(peer, cid, timeoutErr)
}()
return timeoutErr
}
// waitForFundingWithTimeout is a wrapper around waitForFundingConfirmation and
// waitForTimeout that will return ErrConfirmationTimeout if we are not the
// channel initiator and the MaxWaitNumBlocksFundingConf has passed from the
// funding broadcast height. In case of confirmation, the short channel ID of
// the channel and the funding transaction will be returned.
func (f *Manager) waitForFundingWithTimeout(
ch *channeldb.OpenChannel) (*confirmedChannel, error) {
confChan := make(chan *confirmedChannel)
timeoutChan := make(chan error, 1)
cancelChan := make(chan struct{})
f.wg.Add(1)
go f.waitForFundingConfirmation(ch, cancelChan, confChan)
// If we are not the initiator, we have no money at stake and will
// timeout waiting for the funding transaction to confirm after a
// while.
if !ch.IsInitiator && !ch.IsZeroConf() {
f.wg.Add(1)
go f.waitForTimeout(ch, cancelChan, timeoutChan)
}
defer close(cancelChan)
select {
case err := <-timeoutChan:
if err != nil {
return nil, err
}
return nil, ErrConfirmationTimeout
case <-f.quit:
// The fundingManager is shutting down, and will resume wait on
// startup.
return nil, ErrFundingManagerShuttingDown
case confirmedChannel, ok := <-confChan:
if !ok {
return nil, fmt.Errorf("waiting for funding" +
"confirmation failed")
}
return confirmedChannel, nil
}
}
// makeFundingScript re-creates the funding script for the funding transaction
// of the target channel.
func makeFundingScript(channel *channeldb.OpenChannel) ([]byte, error) {
localKey := channel.LocalChanCfg.MultiSigKey.PubKey
remoteKey := channel.RemoteChanCfg.MultiSigKey.PubKey
if channel.ChanType.IsTaproot() {
pkScript, _, err := input.GenTaprootFundingScript(
localKey, remoteKey, int64(channel.Capacity),
channel.TapscriptRoot,
)
if err != nil {
return nil, err
}
return pkScript, nil
}
multiSigScript, err := input.GenMultiSigScript(
localKey.SerializeCompressed(),
remoteKey.SerializeCompressed(),
)
if err != nil {
return nil, err
}
return input.WitnessScriptHash(multiSigScript)
}
// waitForFundingConfirmation handles the final stages of the channel funding
// process once the funding transaction has been broadcast. The primary
// function of waitForFundingConfirmation is to wait for blockchain
// confirmation, and then to notify the other systems that must be notified
// when a channel has become active for lightning transactions.
// The wait can be canceled by closing the cancelChan. In case of success,
// a *lnwire.ShortChannelID will be passed to confChan.
//
// NOTE: This MUST be run as a goroutine.
func (f *Manager) waitForFundingConfirmation(
completeChan *channeldb.OpenChannel, cancelChan <-chan struct{},
confChan chan<- *confirmedChannel) {
defer f.wg.Done()
defer close(confChan)
// Register with the ChainNotifier for a notification once the funding
// transaction reaches `numConfs` confirmations.
txid := completeChan.FundingOutpoint.Hash
fundingScript, err := makeFundingScript(completeChan)
if err != nil {
log.Errorf("unable to create funding script for "+
"ChannelPoint(%v): %v", completeChan.FundingOutpoint,
err)
return
}
numConfs := uint32(completeChan.NumConfsRequired)
// If the underlying channel is a zero-conf channel, we'll set numConfs
// to 6, since it will be zero here.
if completeChan.IsZeroConf() {
numConfs = 6
}
confNtfn, err := f.cfg.Notifier.RegisterConfirmationsNtfn(
&txid, fundingScript, numConfs,
completeChan.BroadcastHeight(),
)
if err != nil {
log.Errorf("Unable to register for confirmation of "+
"ChannelPoint(%v): %v", completeChan.FundingOutpoint,
err)
return
}
log.Infof("Waiting for funding tx (%v) to reach %v confirmations",
txid, numConfs)
var confDetails *chainntnfs.TxConfirmation
var ok bool
// Wait until the specified number of confirmations has been reached,
// we get a cancel signal, or the wallet signals a shutdown.
select {
case confDetails, ok = <-confNtfn.Confirmed:
// fallthrough
case <-cancelChan:
log.Warnf("canceled waiting for funding confirmation, "+
"stopping funding flow for ChannelPoint(%v)",
completeChan.FundingOutpoint)
return
case <-f.quit:
log.Warnf("fundingManager shutting down, stopping funding "+
"flow for ChannelPoint(%v)",
completeChan.FundingOutpoint)
return
}
if !ok {
log.Warnf("ChainNotifier shutting down, cannot complete "+
"funding flow for ChannelPoint(%v)",
completeChan.FundingOutpoint)
return
}
fundingPoint := completeChan.FundingOutpoint
log.Infof("ChannelPoint(%v) is now active: ChannelID(%v)",
fundingPoint, lnwire.NewChanIDFromOutPoint(fundingPoint))
// With the block height and the transaction index known, we can
// construct the compact chanID which is used on the network to unique
// identify channels.
shortChanID := lnwire.ShortChannelID{
BlockHeight: confDetails.BlockHeight,
TxIndex: confDetails.TxIndex,
TxPosition: uint16(fundingPoint.Index),
}
select {
case confChan <- &confirmedChannel{
shortChanID: shortChanID,
fundingTx: confDetails.Tx,
}:
case <-f.quit:
return
}
}
// waitForTimeout will close the timeout channel if MaxWaitNumBlocksFundingConf
// has passed from the broadcast height of the given channel. In case of error,
// the error is sent on timeoutChan. The wait can be canceled by closing the
// cancelChan.
//
// NOTE: timeoutChan MUST be buffered.
// NOTE: This MUST be run as a goroutine.
func (f *Manager) waitForTimeout(completeChan *channeldb.OpenChannel,
cancelChan <-chan struct{}, timeoutChan chan<- error) {
defer f.wg.Done()
epochClient, err := f.cfg.Notifier.RegisterBlockEpochNtfn(nil)
if err != nil {
timeoutChan <- fmt.Errorf("unable to register for epoch "+
"notification: %v", err)
return
}
defer epochClient.Cancel()
// The value of waitBlocksForFundingConf is adjusted in a development
// environment to enhance test capabilities. Otherwise, it is set to
// DefaultMaxWaitNumBlocksFundingConf.
waitBlocksForFundingConf := uint32(
lncfg.DefaultMaxWaitNumBlocksFundingConf,
)
if lncfg.IsDevBuild() {
waitBlocksForFundingConf =
f.cfg.Dev.MaxWaitNumBlocksFundingConf
}
// On block maxHeight we will cancel the funding confirmation wait.
broadcastHeight := completeChan.BroadcastHeight()
maxHeight := broadcastHeight + waitBlocksForFundingConf
for {
select {
case epoch, ok := <-epochClient.Epochs:
if !ok {
timeoutChan <- fmt.Errorf("epoch client " +
"shutting down")
return
}
// Close the timeout channel and exit if the block is
// above the max height.
if uint32(epoch.Height) >= maxHeight {
log.Warnf("Waited for %v blocks without "+
"seeing funding transaction confirmed,"+
" cancelling.",
waitBlocksForFundingConf)
// Notify the caller of the timeout.
close(timeoutChan)
return
}
// TODO: If we are the channel initiator implement
// a method for recovering the funds from the funding
// transaction
case <-cancelChan:
return
case <-f.quit:
// The fundingManager is shutting down, will resume
// waiting for the funding transaction on startup.
return
}
}
}
// makeLabelForTx updates the label for the confirmed funding transaction. If
// we opened the channel, and lnd's wallet published our funding tx (which is
// not the case for some channels) then we update our transaction label with
// our short channel ID, which is known now that our funding transaction has
// confirmed. We do not label transactions we did not publish, because our
// wallet has no knowledge of them.
func (f *Manager) makeLabelForTx(c *channeldb.OpenChannel) {
if c.IsInitiator && c.ChanType.HasFundingTx() {
shortChanID := c.ShortChanID()
// For zero-conf channels, we'll use the actually-confirmed
// short channel id.
if c.IsZeroConf() {
shortChanID = c.ZeroConfRealScid()
}
label := labels.MakeLabel(
labels.LabelTypeChannelOpen, &shortChanID,
)
err := f.cfg.UpdateLabel(c.FundingOutpoint.Hash, label)
if err != nil {
log.Errorf("unable to update label: %v", err)
}
}
}
// handleFundingConfirmation marks a channel as open in the database, and set
// the channelOpeningState markedOpen. In addition it will report the now
// decided short channel ID to the switch, and close the local discovery signal
// for this channel.
func (f *Manager) handleFundingConfirmation(
completeChan *channeldb.OpenChannel,
confChannel *confirmedChannel) error {
fundingPoint := completeChan.FundingOutpoint
chanID := lnwire.NewChanIDFromOutPoint(fundingPoint)
// TODO(roasbeef): ideally persistent state update for chan above
// should be abstracted
// Now that that the channel has been fully confirmed, we'll request
// that the wallet fully verify this channel to ensure that it can be
// used.
err := f.cfg.Wallet.ValidateChannel(completeChan, confChannel.fundingTx)
if err != nil {
// TODO(roasbeef): delete chan state?
return fmt.Errorf("unable to validate channel: %w", err)
}
// Now that the channel has been validated, we'll persist an alias for
// this channel if the option-scid-alias feature-bit was negotiated.
if completeChan.NegotiatedAliasFeature() {
aliasScid, err := f.cfg.AliasManager.RequestAlias()
if err != nil {
return fmt.Errorf("unable to request alias: %w", err)
}
err = f.cfg.AliasManager.AddLocalAlias(
aliasScid, confChannel.shortChanID, true, false,
)
if err != nil {
return fmt.Errorf("unable to request alias: %w", err)
}
}
// The funding transaction now being confirmed, we add this channel to
// the fundingManager's internal persistent state machine that we use
// to track the remaining process of the channel opening. This is
// useful to resume the opening process in case of restarts. We set the
// opening state before we mark the channel opened in the database,
// such that we can receover from one of the db writes failing.
err = f.saveChannelOpeningState(
&fundingPoint, markedOpen, &confChannel.shortChanID,
)
if err != nil {
return fmt.Errorf("error setting channel state to "+
"markedOpen: %v", err)
}
// Now that the channel has been fully confirmed and we successfully
// saved the opening state, we'll mark it as open within the database.
err = completeChan.MarkAsOpen(confChannel.shortChanID)
if err != nil {
return fmt.Errorf("error setting channel pending flag to "+
"false: %v", err)
}
// Update the confirmed funding transaction label.
f.makeLabelForTx(completeChan)
// Inform the ChannelNotifier that the channel has transitioned from
// pending open to open.
if err := f.cfg.NotifyOpenChannelEvent(
completeChan.FundingOutpoint, completeChan.IdentityPub,
); err != nil {
log.Errorf("Unable to notify open channel event for "+
"ChannelPoint(%v): %v", completeChan.FundingOutpoint,
err)
}
// Close the discoverySignal channel, indicating to a separate
// goroutine that the channel now is marked as open in the database
// and that it is acceptable to process channel_ready messages
// from the peer.
if discoverySignal, ok := f.localDiscoverySignals.Load(chanID); ok {
close(discoverySignal)
}
return nil
}
// sendChannelReady creates and sends the channelReady message.
// This should be called after the funding transaction has been confirmed,
// and the channelState is 'markedOpen'.
func (f *Manager) sendChannelReady(completeChan *channeldb.OpenChannel,
channel *lnwallet.LightningChannel) error {
chanID := lnwire.NewChanIDFromOutPoint(completeChan.FundingOutpoint)
var peerKey [33]byte
copy(peerKey[:], completeChan.IdentityPub.SerializeCompressed())
// Next, we'll send over the channel_ready message which marks that we
// consider the channel open by presenting the remote party with our
// next revocation key. Without the revocation key, the remote party
// will be unable to propose state transitions.
nextRevocation, err := channel.NextRevocationKey()
if err != nil {
return fmt.Errorf("unable to create next revocation: %w", err)
}
channelReadyMsg := lnwire.NewChannelReady(chanID, nextRevocation)
// If this is a taproot channel, then we also need to send along our
// set of musig2 nonces as well.
if completeChan.ChanType.IsTaproot() {
log.Infof("ChanID(%v): generating musig2 nonces...",
chanID)
f.nonceMtx.Lock()
localNonce, ok := f.pendingMusigNonces[chanID]
if !ok {
// If we don't have any nonces generated yet for this
// first state, then we'll generate them now and stow
// them away. When we receive the funding locked
// message, we'll then pass along this same set of
// nonces.
newNonce, err := channel.GenMusigNonces()
if err != nil {
f.nonceMtx.Unlock()
return err
}
// Now that we've generated the nonce for this channel,
// we'll store it in the set of pending nonces.
localNonce = newNonce
f.pendingMusigNonces[chanID] = localNonce
}
f.nonceMtx.Unlock()
channelReadyMsg.NextLocalNonce = lnwire.SomeMusig2Nonce(
localNonce.PubNonce,
)
}
// If the channel negotiated the option-scid-alias feature bit, we'll
// send a TLV segment that includes an alias the peer can use in their
// invoice hop hints. We'll send the first alias we find for the
// channel since it does not matter which alias we send. We'll error
// out in the odd case that no aliases are found.
if completeChan.NegotiatedAliasFeature() {
aliases := f.cfg.AliasManager.GetAliases(
completeChan.ShortChanID(),
)
if len(aliases) == 0 {
return fmt.Errorf("no aliases found")
}
// We can use a pointer to aliases since GetAliases returns a
// copy of the alias slice.
channelReadyMsg.AliasScid = &aliases[0]
}
// If the peer has disconnected before we reach this point, we will need
// to wait for him to come back online before sending the channelReady
// message. This is special for channelReady, since failing to send any
// of the previous messages in the funding flow just cancels the flow.
// But now the funding transaction is confirmed, the channel is open
// and we have to make sure the peer gets the channelReady message when
// it comes back online. This is also crucial during restart of lnd,
// where we might try to resend the channelReady message before the
// server has had the time to connect to the peer. We keep trying to
// send channelReady until we succeed, or the fundingManager is shut
// down.
for {
peer, err := f.waitForPeerOnline(completeChan.IdentityPub)
if err != nil {
return err
}
localAlias := peer.LocalFeatures().HasFeature(
lnwire.ScidAliasOptional,
)
remoteAlias := peer.RemoteFeatures().HasFeature(
lnwire.ScidAliasOptional,
)
// We could also refresh the channel state instead of checking
// whether the feature was negotiated, but this saves us a
// database read.
if channelReadyMsg.AliasScid == nil && localAlias &&
remoteAlias {
// If an alias was not assigned above and the scid
// alias feature was negotiated, check if we already
// have an alias stored in case handleChannelReady was
// called before this. If an alias exists, use that in
// channel_ready. Otherwise, request and store an
// alias and use that.
aliases := f.cfg.AliasManager.GetAliases(
completeChan.ShortChannelID,
)
if len(aliases) == 0 {
// No aliases were found.
alias, err := f.cfg.AliasManager.RequestAlias()
if err != nil {
return err
}
err = f.cfg.AliasManager.AddLocalAlias(
alias, completeChan.ShortChannelID,
false, false,
)
if err != nil {
return err
}
channelReadyMsg.AliasScid = &alias
} else {
channelReadyMsg.AliasScid = &aliases[0]
}
}
log.Infof("Peer(%x) is online, sending ChannelReady "+
"for ChannelID(%v)", peerKey, chanID)
if err := peer.SendMessage(true, channelReadyMsg); err == nil {
// Sending succeeded, we can break out and continue the
// funding flow.
break
}
log.Warnf("Unable to send channelReady to peer %x: %v. "+
"Will retry when online", peerKey, err)
}
return nil
}
// receivedChannelReady checks whether or not we've received a ChannelReady
// from the remote peer. If we have, RemoteNextRevocation will be set.
func (f *Manager) receivedChannelReady(node *btcec.PublicKey,
chanID lnwire.ChannelID) (bool, error) {
// If the funding manager has exited, return an error to stop looping.
// Note that the peer may appear as online while the funding manager
// has stopped due to the shutdown order in the server.
select {
case <-f.quit:
return false, ErrFundingManagerShuttingDown
default:
}
// Avoid a tight loop if peer is offline.
if _, err := f.waitForPeerOnline(node); err != nil {
log.Errorf("Wait for peer online failed: %v", err)
return false, err
}
// If we cannot find the channel, then we haven't processed the
// remote's channelReady message.
channel, err := f.cfg.FindChannel(node, chanID)
if err != nil {
log.Errorf("Unable to locate ChannelID(%v) to determine if "+
"ChannelReady was received", chanID)
return false, err
}
// If we haven't insert the next revocation point, we haven't finished
// processing the channel ready message.
if channel.RemoteNextRevocation == nil {
return false, nil
}
// Finally, the barrier signal is removed once we finish
// `handleChannelReady`. If we can still find the signal, we haven't
// finished processing it yet.
_, loaded := f.handleChannelReadyBarriers.Load(chanID)
return !loaded, nil
}
// extractAnnounceParams extracts the various channel announcement and update
// parameters that will be needed to construct a ChannelAnnouncement and a
// ChannelUpdate.
func (f *Manager) extractAnnounceParams(c *channeldb.OpenChannel) (
lnwire.MilliSatoshi, lnwire.MilliSatoshi) {
// We'll obtain the min HTLC value we can forward in our direction, as
// we'll use this value within our ChannelUpdate. This constraint is
// originally set by the remote node, as it will be the one that will
// need to determine the smallest HTLC it deems economically relevant.
fwdMinHTLC := c.LocalChanCfg.MinHTLC
// We don't necessarily want to go as low as the remote party allows.
// Check it against our default forwarding policy.
if fwdMinHTLC < f.cfg.DefaultRoutingPolicy.MinHTLCOut {
fwdMinHTLC = f.cfg.DefaultRoutingPolicy.MinHTLCOut
}
// We'll obtain the max HTLC value we can forward in our direction, as
// we'll use this value within our ChannelUpdate. This value must be <=
// channel capacity and <= the maximum in-flight msats set by the peer.
fwdMaxHTLC := c.LocalChanCfg.MaxPendingAmount
capacityMSat := lnwire.NewMSatFromSatoshis(c.Capacity)
if fwdMaxHTLC > capacityMSat {
fwdMaxHTLC = capacityMSat
}
return fwdMinHTLC, fwdMaxHTLC
}
// addToGraph sends a ChannelAnnouncement and a ChannelUpdate to the
// gossiper so that the channel is added to the graph builder's internal graph.
// These announcement messages are NOT broadcasted to the greater network,
// only to the channel counter party. The proofs required to announce the
// channel to the greater network will be created and sent in annAfterSixConfs.
// The peerAlias is used for zero-conf channels to give the counter-party a
// ChannelUpdate they understand. ourPolicy may be set for various
// option-scid-alias channels to re-use the same policy.
func (f *Manager) addToGraph(completeChan *channeldb.OpenChannel,
shortChanID *lnwire.ShortChannelID,
peerAlias *lnwire.ShortChannelID,
ourPolicy *models.ChannelEdgePolicy) error {
chanID := lnwire.NewChanIDFromOutPoint(completeChan.FundingOutpoint)
fwdMinHTLC, fwdMaxHTLC := f.extractAnnounceParams(completeChan)
ann, err := f.newChanAnnouncement(
f.cfg.IDKey, completeChan.IdentityPub,
&completeChan.LocalChanCfg.MultiSigKey,
completeChan.RemoteChanCfg.MultiSigKey.PubKey, *shortChanID,
chanID, fwdMinHTLC, fwdMaxHTLC, ourPolicy,
completeChan.ChanType,
)
if err != nil {
return fmt.Errorf("error generating channel "+
"announcement: %v", err)
}
// Send ChannelAnnouncement and ChannelUpdate to the gossiper to add
// to the Router's topology.
errChan := f.cfg.SendAnnouncement(
ann.chanAnn, discovery.ChannelCapacity(completeChan.Capacity),
discovery.ChannelPoint(completeChan.FundingOutpoint),
discovery.TapscriptRoot(completeChan.TapscriptRoot),
)
select {
case err := <-errChan:
if err != nil {
if graph.IsError(err, graph.ErrOutdated,
graph.ErrIgnored) {
log.Debugf("Graph rejected "+
"ChannelAnnouncement: %v", err)
} else {
return fmt.Errorf("error sending channel "+
"announcement: %v", err)
}
}
case <-f.quit:
return ErrFundingManagerShuttingDown
}
errChan = f.cfg.SendAnnouncement(
ann.chanUpdateAnn, discovery.RemoteAlias(peerAlias),
)
select {
case err := <-errChan:
if err != nil {
if graph.IsError(err, graph.ErrOutdated,
graph.ErrIgnored) {
log.Debugf("Graph rejected "+
"ChannelUpdate: %v", err)
} else {
return fmt.Errorf("error sending channel "+
"update: %v", err)
}
}
case <-f.quit:
return ErrFundingManagerShuttingDown
}
return nil
}
// annAfterSixConfs broadcasts the necessary channel announcement messages to
// the network after 6 confs. Should be called after the channelReady message
// is sent and the channel is added to the graph (channelState is
// 'addedToGraph') and the channel is ready to be used. This is the last
// step in the channel opening process, and the opening state will be deleted
// from the database if successful.
func (f *Manager) annAfterSixConfs(completeChan *channeldb.OpenChannel,
shortChanID *lnwire.ShortChannelID) error {
// If this channel is not meant to be announced to the greater network,
// we'll only send our NodeAnnouncement to our counterparty to ensure we
// don't leak any of our information.
announceChan := completeChan.ChannelFlags&lnwire.FFAnnounceChannel != 0
if !announceChan {
log.Debugf("Will not announce private channel %v.",
shortChanID.ToUint64())
peer, err := f.waitForPeerOnline(completeChan.IdentityPub)
if err != nil {
return err
}
nodeAnn, err := f.cfg.CurrentNodeAnnouncement()
if err != nil {
return fmt.Errorf("unable to retrieve current node "+
"announcement: %v", err)
}
chanID := lnwire.NewChanIDFromOutPoint(
completeChan.FundingOutpoint,
)
pubKey := peer.PubKey()
log.Debugf("Sending our NodeAnnouncement for "+
"ChannelID(%v) to %x", chanID, pubKey)
// TODO(halseth): make reliable. If the peer is not online this
// will fail, and the opening process will stop. Should instead
// block here, waiting for the peer to come online.
if err := peer.SendMessage(true, &nodeAnn); err != nil {
return fmt.Errorf("unable to send node announcement "+
"to peer %x: %v", pubKey, err)
}
} else {
// Otherwise, we'll wait until the funding transaction has
// reached 6 confirmations before announcing it.
numConfs := uint32(completeChan.NumConfsRequired)
if numConfs < 6 {
numConfs = 6
}
txid := completeChan.FundingOutpoint.Hash
log.Debugf("Will announce channel %v after ChannelPoint"+
"(%v) has gotten %d confirmations",
shortChanID.ToUint64(), completeChan.FundingOutpoint,
numConfs)
fundingScript, err := makeFundingScript(completeChan)
if err != nil {
return fmt.Errorf("unable to create funding script "+
"for ChannelPoint(%v): %v",
completeChan.FundingOutpoint, err)
}
// Register with the ChainNotifier for a notification once the
// funding transaction reaches at least 6 confirmations.
confNtfn, err := f.cfg.Notifier.RegisterConfirmationsNtfn(
&txid, fundingScript, numConfs,
completeChan.BroadcastHeight(),
)
if err != nil {
return fmt.Errorf("unable to register for "+
"confirmation of ChannelPoint(%v): %v",
completeChan.FundingOutpoint, err)
}
// Wait until 6 confirmations has been reached or the wallet
// signals a shutdown.
select {
case _, ok := <-confNtfn.Confirmed:
if !ok {
return fmt.Errorf("ChainNotifier shutting "+
"down, cannot complete funding flow "+
"for ChannelPoint(%v)",
completeChan.FundingOutpoint)
}
// Fallthrough.
case <-f.quit:
return fmt.Errorf("%v, stopping funding flow for "+
"ChannelPoint(%v)",
ErrFundingManagerShuttingDown,
completeChan.FundingOutpoint)
}
fundingPoint := completeChan.FundingOutpoint
chanID := lnwire.NewChanIDFromOutPoint(fundingPoint)
log.Infof("Announcing ChannelPoint(%v), short_chan_id=%v",
&fundingPoint, shortChanID)
// If this is a non-zero-conf option-scid-alias channel, we'll
// delete the mappings the gossiper uses so that ChannelUpdates
// with aliases won't be accepted. This is done elsewhere for
// zero-conf channels.
isScidFeature := completeChan.NegotiatedAliasFeature()
isZeroConf := completeChan.IsZeroConf()
if isScidFeature && !isZeroConf {
baseScid := completeChan.ShortChanID()
err := f.cfg.AliasManager.DeleteSixConfs(baseScid)
if err != nil {
return fmt.Errorf("failed deleting six confs "+
"maps: %v", err)
}
// We'll delete the edge and add it again via
// addToGraph. This is because the peer may have
// sent us a ChannelUpdate with an alias and we don't
// want to relay this.
ourPolicy, err := f.cfg.DeleteAliasEdge(baseScid)
if err != nil {
return fmt.Errorf("failed deleting real edge "+
"for alias channel from graph: %v",
err)
}
err = f.addToGraph(
completeChan, &baseScid, nil, ourPolicy,
)
if err != nil {
return fmt.Errorf("failed to re-add to "+
"graph: %v", err)
}
}
// Create and broadcast the proofs required to make this channel
// public and usable for other nodes for routing.
err = f.announceChannel(
f.cfg.IDKey, completeChan.IdentityPub,
&completeChan.LocalChanCfg.MultiSigKey,
completeChan.RemoteChanCfg.MultiSigKey.PubKey,
*shortChanID, chanID, completeChan.ChanType,
)
if err != nil {
return fmt.Errorf("channel announcement failed: %w",
err)
}
log.Debugf("Channel with ChannelPoint(%v), short_chan_id=%v "+
"sent to gossiper", &fundingPoint, shortChanID)
}
return nil
}
// waitForZeroConfChannel is called when the state is addedToGraph with
// a zero-conf channel. This will wait for the real confirmation, add the
// confirmed SCID to the router graph, and then announce after six confs.
func (f *Manager) waitForZeroConfChannel(c *channeldb.OpenChannel) error {
// First we'll check whether the channel is confirmed on-chain. If it
// is already confirmed, the chainntnfs subsystem will return with the
// confirmed tx. Otherwise, we'll wait here until confirmation occurs.
confChan, err := f.waitForFundingWithTimeout(c)
if err != nil {
return fmt.Errorf("error waiting for zero-conf funding "+
"confirmation for ChannelPoint(%v): %v",
c.FundingOutpoint, err)
}
// We'll need to refresh the channel state so that things are properly
// populated when validating the channel state. Otherwise, a panic may
// occur due to inconsistency in the OpenChannel struct.
err = c.Refresh()
if err != nil {
return fmt.Errorf("unable to refresh channel state: %w", err)
}
// Now that we have the confirmed transaction and the proper SCID,
// we'll call ValidateChannel to ensure the confirmed tx is properly
// formatted.
err = f.cfg.Wallet.ValidateChannel(c, confChan.fundingTx)
if err != nil {
return fmt.Errorf("unable to validate zero-conf channel: "+
"%v", err)
}
// Once we know the confirmed ShortChannelID, we'll need to save it to
// the database and refresh the OpenChannel struct with it.
err = c.MarkRealScid(confChan.shortChanID)
if err != nil {
return fmt.Errorf("unable to set confirmed SCID for zero "+
"channel: %v", err)
}
// Six confirmations have been reached. If this channel is public,
// we'll delete some of the alias mappings the gossiper uses.
isPublic := c.ChannelFlags&lnwire.FFAnnounceChannel != 0
if isPublic {
err = f.cfg.AliasManager.DeleteSixConfs(c.ShortChannelID)
if err != nil {
return fmt.Errorf("unable to delete base alias after "+
"six confirmations: %v", err)
}
// TODO: Make this atomic!
ourPolicy, err := f.cfg.DeleteAliasEdge(c.ShortChanID())
if err != nil {
return fmt.Errorf("unable to delete alias edge from "+
"graph: %v", err)
}
// We'll need to update the graph with the new ShortChannelID
// via an addToGraph call. We don't pass in the peer's
// alias since we'll be using the confirmed SCID from now on
// regardless if it's public or not.
err = f.addToGraph(
c, &confChan.shortChanID, nil, ourPolicy,
)
if err != nil {
return fmt.Errorf("failed adding confirmed zero-conf "+
"SCID to graph: %v", err)
}
}
// Since we have now marked down the confirmed SCID, we'll also need to
// tell the Switch to refresh the relevant ChannelLink so that forwards
// under the confirmed SCID are possible if this is a public channel.
err = f.cfg.ReportShortChanID(c.FundingOutpoint)
if err != nil {
// This should only fail if the link is not found in the
// Switch's linkIndex map. If this is the case, then the peer
// has gone offline and the next time the link is loaded, it
// will have a refreshed state. Just log an error here.
log.Errorf("unable to report scid for zero-conf channel "+
"channel: %v", err)
}
// Update the confirmed transaction's label.
f.makeLabelForTx(c)
return nil
}
// genFirstStateMusigNonce generates a nonces for the "first" local state. This
// is the verification nonce for the state created for us after the initial
// commitment transaction signed as part of the funding flow.
func genFirstStateMusigNonce(channel *channeldb.OpenChannel,
) (*musig2.Nonces, error) {
musig2ShaChain, err := channeldb.DeriveMusig2Shachain(
channel.RevocationProducer,
)
if err != nil {
return nil, fmt.Errorf("unable to generate musig channel "+
"nonces: %v", err)
}
// We use the _next_ commitment height here as we need to generate the
// nonce for the next state the remote party will sign for us.
verNonce, err := channeldb.NewMusigVerificationNonce(
channel.LocalChanCfg.MultiSigKey.PubKey,
channel.LocalCommitment.CommitHeight+1,
musig2ShaChain,
)
if err != nil {
return nil, fmt.Errorf("unable to generate musig channel "+
"nonces: %v", err)
}
return verNonce, nil
}
// handleChannelReady finalizes the channel funding process and enables the
// channel to enter normal operating mode.
func (f *Manager) handleChannelReady(peer lnpeer.Peer, //nolint:funlen
msg *lnwire.ChannelReady) {
defer f.wg.Done()
// If we are in development mode, we'll wait for specified duration
// before processing the channel ready message.
if f.cfg.Dev != nil {
duration := f.cfg.Dev.ProcessChannelReadyWait
log.Warnf("Channel(%v): sleeping %v before processing "+
"channel_ready", msg.ChanID, duration)
select {
case <-time.After(duration):
log.Warnf("Channel(%v): slept %v before processing "+
"channel_ready", msg.ChanID, duration)
case <-f.quit:
log.Warnf("Channel(%v): quit sleeping", msg.ChanID)
return
}
}
log.Debugf("Received ChannelReady for ChannelID(%v) from "+
"peer %x", msg.ChanID,
peer.IdentityKey().SerializeCompressed())
// We now load or create a new channel barrier for this channel.
_, loaded := f.handleChannelReadyBarriers.LoadOrStore(
msg.ChanID, struct{}{},
)
// If we are currently in the process of handling a channel_ready
// message for this channel, ignore.
if loaded {
log.Infof("Already handling channelReady for "+
"ChannelID(%v), ignoring.", msg.ChanID)
return
}
// If not already handling channelReady for this channel, then the
// `LoadOrStore` has set up a barrier, and it will be removed once this
// function exits.
defer f.handleChannelReadyBarriers.Delete(msg.ChanID)
localDiscoverySignal, ok := f.localDiscoverySignals.Load(msg.ChanID)
if ok {
// Before we proceed with processing the channel_ready
// message, we'll wait for the local waitForFundingConfirmation
// goroutine to signal that it has the necessary state in
// place. Otherwise, we may be missing critical information
// required to handle forwarded HTLC's.
select {
case <-localDiscoverySignal:
// Fallthrough
case <-f.quit:
return
}
// With the signal received, we can now safely delete the entry
// from the map.
f.localDiscoverySignals.Delete(msg.ChanID)
}
// First, we'll attempt to locate the channel whose funding workflow is
// being finalized by this message. We go to the database rather than
// our reservation map as we may have restarted, mid funding flow. Also
// provide the node's public key to make the search faster.
chanID := msg.ChanID
channel, err := f.cfg.FindChannel(peer.IdentityKey(), chanID)
if err != nil {
log.Errorf("Unable to locate ChannelID(%v), cannot complete "+
"funding", chanID)
return
}
// If this is a taproot channel, then we can generate the set of nonces
// the remote party needs to send the next remote commitment here.
var firstVerNonce *musig2.Nonces
if channel.ChanType.IsTaproot() {
firstVerNonce, err = genFirstStateMusigNonce(channel)
if err != nil {
log.Error(err)
return
}
}
// We'll need to store the received TLV alias if the option_scid_alias
// feature was negotiated. This will be used to provide route hints
// during invoice creation. In the zero-conf case, it is also used to
// provide a ChannelUpdate to the remote peer. This is done before the
// call to InsertNextRevocation in case the call to PutPeerAlias fails.
// If it were to fail on the first call to handleChannelReady, we
// wouldn't want the channel to be usable yet.
if channel.NegotiatedAliasFeature() {
// If the AliasScid field is nil, we must fail out. We will
// most likely not be able to route through the peer.
if msg.AliasScid == nil {
log.Debugf("Consider closing ChannelID(%v), peer "+
"does not implement the option-scid-alias "+
"feature properly", chanID)
return
}
// We'll store the AliasScid so that invoice creation can use
// it.
err = f.cfg.AliasManager.PutPeerAlias(chanID, *msg.AliasScid)
if err != nil {
log.Errorf("unable to store peer's alias: %v", err)
return
}
// If we do not have an alias stored, we'll create one now.
// This is only used in the upgrade case where a user toggles
// the option-scid-alias feature-bit to on. We'll also send the
// channel_ready message here in case the link is created
// before sendChannelReady is called.
aliases := f.cfg.AliasManager.GetAliases(
channel.ShortChannelID,
)
if len(aliases) == 0 {
// No aliases were found so we'll request and store an
// alias and use it in the channel_ready message.
alias, err := f.cfg.AliasManager.RequestAlias()
if err != nil {
log.Errorf("unable to request alias: %v", err)
return
}
err = f.cfg.AliasManager.AddLocalAlias(
alias, channel.ShortChannelID, false, false,
)
if err != nil {
log.Errorf("unable to add local alias: %v",
err)
return
}
secondPoint, err := channel.SecondCommitmentPoint()
if err != nil {
log.Errorf("unable to fetch second "+
"commitment point: %v", err)
return
}
channelReadyMsg := lnwire.NewChannelReady(
chanID, secondPoint,
)
channelReadyMsg.AliasScid = &alias
if firstVerNonce != nil {
channelReadyMsg.NextLocalNonce = lnwire.SomeMusig2Nonce( //nolint:ll
firstVerNonce.PubNonce,
)
}
err = peer.SendMessage(true, channelReadyMsg)
if err != nil {
log.Errorf("unable to send channel_ready: %v",
err)
return
}
}
}
// If the RemoteNextRevocation is non-nil, it means that we have
// already processed channelReady for this channel, so ignore. This
// check is after the alias logic so we store the peer's most recent
// alias. The spec requires us to validate that subsequent
// channel_ready messages use the same per commitment point (the
// second), but it is not actually necessary since we'll just end up
// ignoring it. We are, however, required to *send* the same per
// commitment point, since another pedantic implementation might
// verify it.
if channel.RemoteNextRevocation != nil {
log.Infof("Received duplicate channelReady for "+
"ChannelID(%v), ignoring.", chanID)
return
}
// If this is a taproot channel, then we'll need to map the received
// nonces to a nonce pair, and also fetch our pending nonces, which are
// required in order to make the channel whole.
var chanOpts []lnwallet.ChannelOpt
if channel.ChanType.IsTaproot() {
f.nonceMtx.Lock()
localNonce, ok := f.pendingMusigNonces[chanID]
if !ok {
// If there's no pending nonce for this channel ID,
// we'll use the one generated above.
localNonce = firstVerNonce
f.pendingMusigNonces[chanID] = firstVerNonce
}
f.nonceMtx.Unlock()
log.Infof("ChanID(%v): applying local+remote musig2 nonces",
chanID)
remoteNonce, err := msg.NextLocalNonce.UnwrapOrErrV(
errNoLocalNonce,
)
if err != nil {
cid := newChanIdentifier(msg.ChanID)
f.sendWarning(peer, cid, err)
return
}
chanOpts = append(
chanOpts,
lnwallet.WithLocalMusigNonces(localNonce),
lnwallet.WithRemoteMusigNonces(&musig2.Nonces{
PubNonce: remoteNonce,
}),
)
// Inform the aux funding controller that the liquidity in the
// custom channel is now ready to be advertised. We potentially
// haven't sent our own channel ready message yet, but other
// than that the channel is ready to count toward available
// liquidity.
err = fn.MapOptionZ(
f.cfg.AuxFundingController,
func(controller AuxFundingController) error {
return controller.ChannelReady(
lnwallet.NewAuxChanState(channel),
)
},
)
if err != nil {
cid := newChanIdentifier(msg.ChanID)
f.sendWarning(peer, cid, err)
return
}
}
// The channel_ready message contains the next commitment point we'll
// need to create the next commitment state for the remote party. So
// we'll insert that into the channel now before passing it along to
// other sub-systems.
err = channel.InsertNextRevocation(msg.NextPerCommitmentPoint)
if err != nil {
log.Errorf("unable to insert next commitment point: %v", err)
return
}
// Before we can add the channel to the peer, we'll need to ensure that
// we have an initial forwarding policy set.
if err := f.ensureInitialForwardingPolicy(chanID, channel); err != nil {
log.Errorf("Unable to ensure initial forwarding policy: %v",
err)
}
err = peer.AddNewChannel(&lnpeer.NewChannel{
OpenChannel: channel,
ChanOpts: chanOpts,
}, f.quit)
if err != nil {
log.Errorf("Unable to add new channel %v with peer %x: %v",
channel.FundingOutpoint,
peer.IdentityKey().SerializeCompressed(), err,
)
}
}
// handleChannelReadyReceived is called once the remote's channelReady message
// is received and processed. At this stage, we must have sent out our
// channelReady message, once the remote's channelReady is processed, the
// channel is now active, thus we change its state to `addedToGraph` to
// let the channel start handling routing.
func (f *Manager) handleChannelReadyReceived(channel *channeldb.OpenChannel,
scid *lnwire.ShortChannelID, pendingChanID PendingChanID,
updateChan chan<- *lnrpc.OpenStatusUpdate) error {
chanID := lnwire.NewChanIDFromOutPoint(channel.FundingOutpoint)
// Since we've sent+received funding locked at this point, we
// can clean up the pending musig2 nonce state.
f.nonceMtx.Lock()
delete(f.pendingMusigNonces, chanID)
f.nonceMtx.Unlock()
var peerAlias *lnwire.ShortChannelID
if channel.IsZeroConf() {
// We'll need to wait until channel_ready has been received and
// the peer lets us know the alias they want to use for the
// channel. With this information, we can then construct a
// ChannelUpdate for them. If an alias does not yet exist,
// we'll just return, letting the next iteration of the loop
// check again.
var defaultAlias lnwire.ShortChannelID
chanID := lnwire.NewChanIDFromOutPoint(channel.FundingOutpoint)
foundAlias, _ := f.cfg.AliasManager.GetPeerAlias(chanID)
if foundAlias == defaultAlias {
return nil
}
peerAlias = &foundAlias
}
err := f.addToGraph(channel, scid, peerAlias, nil)
if err != nil {
return fmt.Errorf("failed adding to graph: %w", err)
}
// As the channel is now added to the ChannelRouter's topology, the
// channel is moved to the next state of the state machine. It will be
// moved to the last state (actually deleted from the database) after
// the channel is finally announced.
err = f.saveChannelOpeningState(
&channel.FundingOutpoint, addedToGraph, scid,
)
if err != nil {
return fmt.Errorf("error setting channel state to"+
" addedToGraph: %w", err)
}
log.Debugf("Channel(%v) with ShortChanID %v: successfully "+
"added to graph", chanID, scid)
err = fn.MapOptionZ(
f.cfg.AuxFundingController,
func(controller AuxFundingController) error {
return controller.ChannelReady(
lnwallet.NewAuxChanState(channel),
)
},
)
if err != nil {
return fmt.Errorf("failed notifying aux funding controller "+
"about channel ready: %w", err)
}
// Give the caller a final update notifying them that the channel is
fundingPoint := channel.FundingOutpoint
cp := &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: fundingPoint.Hash[:],
},
OutputIndex: fundingPoint.Index,
}
if updateChan != nil {
upd := &lnrpc.OpenStatusUpdate{
Update: &lnrpc.OpenStatusUpdate_ChanOpen{
ChanOpen: &lnrpc.ChannelOpenUpdate{
ChannelPoint: cp,
},
},
PendingChanId: pendingChanID[:],
}
select {
case updateChan <- upd:
case <-f.quit:
return ErrFundingManagerShuttingDown
}
}
return nil
}
// ensureInitialForwardingPolicy ensures that we have an initial forwarding
// policy set for the given channel. If we don't, we'll fall back to the default
// values.
func (f *Manager) ensureInitialForwardingPolicy(chanID lnwire.ChannelID,
channel *channeldb.OpenChannel) error {
// Before we can add the channel to the peer, we'll need to ensure that
// we have an initial forwarding policy set. This should always be the
// case except for a channel that was created with lnd <= 0.15.5 and
// is still pending while updating to this version.
var needDBUpdate bool
forwardingPolicy, err := f.getInitialForwardingPolicy(chanID)
if err != nil {
log.Errorf("Unable to fetch initial forwarding policy, "+
"falling back to default values: %v", err)
forwardingPolicy = f.defaultForwardingPolicy(
channel.LocalChanCfg.ChannelStateBounds,
)
needDBUpdate = true
}
// We only started storing the actual values for MinHTLCOut and MaxHTLC
// after 0.16.x, so if a channel was opened with such a version and is
// still pending while updating to this version, we'll need to set the
// values to the default values.
if forwardingPolicy.MinHTLCOut == 0 {
forwardingPolicy.MinHTLCOut = channel.LocalChanCfg.MinHTLC
needDBUpdate = true
}
if forwardingPolicy.MaxHTLC == 0 {
forwardingPolicy.MaxHTLC = channel.LocalChanCfg.MaxPendingAmount
needDBUpdate = true
}
// And finally, if we found that the values currently stored aren't
// sufficient for the link, we'll update the database.
if needDBUpdate {
err := f.saveInitialForwardingPolicy(chanID, forwardingPolicy)
if err != nil {
return fmt.Errorf("unable to update initial "+
"forwarding policy: %v", err)
}
}
return nil
}
// chanAnnouncement encapsulates the two authenticated announcements that we
// send out to the network after a new channel has been created locally.
type chanAnnouncement struct {
chanAnn *lnwire.ChannelAnnouncement1
chanUpdateAnn *lnwire.ChannelUpdate1
chanProof *lnwire.AnnounceSignatures1
}
// newChanAnnouncement creates the authenticated channel announcement messages
// required to broadcast a newly created channel to the network. The
// announcement is two part: the first part authenticates the existence of the
// channel and contains four signatures binding the funding pub keys and
// identity pub keys of both parties to the channel, and the second segment is
// authenticated only by us and contains our directional routing policy for the
// channel. ourPolicy may be set in order to re-use an existing, non-default
// policy.
func (f *Manager) newChanAnnouncement(localPubKey,
remotePubKey *btcec.PublicKey, localFundingKey *keychain.KeyDescriptor,
remoteFundingKey *btcec.PublicKey, shortChanID lnwire.ShortChannelID,
chanID lnwire.ChannelID, fwdMinHTLC, fwdMaxHTLC lnwire.MilliSatoshi,
ourPolicy *models.ChannelEdgePolicy,
chanType channeldb.ChannelType) (*chanAnnouncement, error) {
chainHash := *f.cfg.Wallet.Cfg.NetParams.GenesisHash
// The unconditional section of the announcement is the ShortChannelID
// itself which compactly encodes the location of the funding output
// within the blockchain.
chanAnn := &lnwire.ChannelAnnouncement1{
ShortChannelID: shortChanID,
Features: lnwire.NewRawFeatureVector(),
ChainHash: chainHash,
}
// If this is a taproot channel, then we'll set a special bit in the
// feature vector to indicate to the routing layer that this needs a
// slightly different type of validation.
//
// TODO(roasbeef): temp, remove after gossip 1.5
if chanType.IsTaproot() {
log.Debugf("Applying taproot feature bit to "+
"ChannelAnnouncement for %v", chanID)
chanAnn.Features.Set(
lnwire.SimpleTaprootChannelsRequiredStaging,
)
}
// The chanFlags field indicates which directed edge of the channel is
// being updated within the ChannelUpdateAnnouncement announcement
// below. A value of zero means it's the edge of the "first" node and 1
// being the other node.
var chanFlags lnwire.ChanUpdateChanFlags
// The lexicographical ordering of the two identity public keys of the
// nodes indicates which of the nodes is "first". If our serialized
// identity key is lower than theirs then we're the "first" node and
// second otherwise.
selfBytes := localPubKey.SerializeCompressed()
remoteBytes := remotePubKey.SerializeCompressed()
if bytes.Compare(selfBytes, remoteBytes) == -1 {
copy(chanAnn.NodeID1[:], localPubKey.SerializeCompressed())
copy(chanAnn.NodeID2[:], remotePubKey.SerializeCompressed())
copy(
chanAnn.BitcoinKey1[:],
localFundingKey.PubKey.SerializeCompressed(),
)
copy(
chanAnn.BitcoinKey2[:],
remoteFundingKey.SerializeCompressed(),
)
// If we're the first node then update the chanFlags to
// indicate the "direction" of the update.
chanFlags = 0
} else {
copy(chanAnn.NodeID1[:], remotePubKey.SerializeCompressed())
copy(chanAnn.NodeID2[:], localPubKey.SerializeCompressed())
copy(
chanAnn.BitcoinKey1[:],
remoteFundingKey.SerializeCompressed(),
)
copy(
chanAnn.BitcoinKey2[:],
localFundingKey.PubKey.SerializeCompressed(),
)
// If we're the second node then update the chanFlags to
// indicate the "direction" of the update.
chanFlags = 1
}
// Our channel update message flags will signal that we support the
// max_htlc field.
msgFlags := lnwire.ChanUpdateRequiredMaxHtlc
// We announce the channel with the default values. Some of
// these values can later be changed by crafting a new ChannelUpdate.
chanUpdateAnn := &lnwire.ChannelUpdate1{
ShortChannelID: shortChanID,
ChainHash: chainHash,
Timestamp: uint32(time.Now().Unix()),
MessageFlags: msgFlags,
ChannelFlags: chanFlags,
TimeLockDelta: uint16(
f.cfg.DefaultRoutingPolicy.TimeLockDelta,
),
HtlcMinimumMsat: fwdMinHTLC,
HtlcMaximumMsat: fwdMaxHTLC,
}
// The caller of newChanAnnouncement is expected to provide the initial
// forwarding policy to be announced. If no persisted initial policy
// values are found, then we will use the default policy values in the
// channel announcement.
storedFwdingPolicy, err := f.getInitialForwardingPolicy(chanID)
if err != nil && !errors.Is(err, channeldb.ErrChannelNotFound) {
return nil, errors.Errorf("unable to generate channel "+
"update announcement: %v", err)
}
switch {
case ourPolicy != nil:
// If ourPolicy is non-nil, modify the default parameters of the
// ChannelUpdate.
chanUpdateAnn.MessageFlags = ourPolicy.MessageFlags
chanUpdateAnn.ChannelFlags = ourPolicy.ChannelFlags
chanUpdateAnn.TimeLockDelta = ourPolicy.TimeLockDelta
chanUpdateAnn.HtlcMinimumMsat = ourPolicy.MinHTLC
chanUpdateAnn.HtlcMaximumMsat = ourPolicy.MaxHTLC
chanUpdateAnn.BaseFee = uint32(ourPolicy.FeeBaseMSat)
chanUpdateAnn.FeeRate = uint32(
ourPolicy.FeeProportionalMillionths,
)
case storedFwdingPolicy != nil:
chanUpdateAnn.BaseFee = uint32(storedFwdingPolicy.BaseFee)
chanUpdateAnn.FeeRate = uint32(storedFwdingPolicy.FeeRate)
default:
log.Infof("No channel forwarding policy specified for channel "+
"announcement of ChannelID(%v). "+
"Assuming default fee parameters.", chanID)
chanUpdateAnn.BaseFee = uint32(
f.cfg.DefaultRoutingPolicy.BaseFee,
)
chanUpdateAnn.FeeRate = uint32(
f.cfg.DefaultRoutingPolicy.FeeRate,
)
}
// With the channel update announcement constructed, we'll generate a
// signature that signs a double-sha digest of the announcement.
// This'll serve to authenticate this announcement and any other future
// updates we may send.
chanUpdateMsg, err := chanUpdateAnn.DataToSign()
if err != nil {
return nil, err
}
sig, err := f.cfg.SignMessage(f.cfg.IDKeyLoc, chanUpdateMsg, true)
if err != nil {
return nil, errors.Errorf("unable to generate channel "+
"update announcement signature: %v", err)
}
chanUpdateAnn.Signature, err = lnwire.NewSigFromSignature(sig)
if err != nil {
return nil, errors.Errorf("unable to generate channel "+
"update announcement signature: %v", err)
}
// The channel existence proofs itself is currently announced in
// distinct message. In order to properly authenticate this message, we
// need two signatures: one under the identity public key used which
// signs the message itself and another signature of the identity
// public key under the funding key itself.
//
// TODO(roasbeef): use SignAnnouncement here instead?
chanAnnMsg, err := chanAnn.DataToSign()
if err != nil {
return nil, err
}
nodeSig, err := f.cfg.SignMessage(f.cfg.IDKeyLoc, chanAnnMsg, true)
if err != nil {
return nil, errors.Errorf("unable to generate node "+
"signature for channel announcement: %v", err)
}
bitcoinSig, err := f.cfg.SignMessage(
localFundingKey.KeyLocator, chanAnnMsg, true,
)
if err != nil {
return nil, errors.Errorf("unable to generate bitcoin "+
"signature for node public key: %v", err)
}
// Finally, we'll generate the announcement proof which we'll use to
// provide the other side with the necessary signatures required to
// allow them to reconstruct the full channel announcement.
proof := &lnwire.AnnounceSignatures1{
ChannelID: chanID,
ShortChannelID: shortChanID,
}
proof.NodeSignature, err = lnwire.NewSigFromSignature(nodeSig)
if err != nil {
return nil, err
}
proof.BitcoinSignature, err = lnwire.NewSigFromSignature(bitcoinSig)
if err != nil {
return nil, err
}
return &chanAnnouncement{
chanAnn: chanAnn,
chanUpdateAnn: chanUpdateAnn,
chanProof: proof,
}, nil
}
// announceChannel announces a newly created channel to the rest of the network
// by crafting the two authenticated announcements required for the peers on
// the network to recognize the legitimacy of the channel. The crafted
// announcements are then sent to the channel router to handle broadcasting to
// the network during its next trickle.
// This method is synchronous and will return when all the network requests
// finish, either successfully or with an error.
func (f *Manager) announceChannel(localIDKey, remoteIDKey *btcec.PublicKey,
localFundingKey *keychain.KeyDescriptor,
remoteFundingKey *btcec.PublicKey, shortChanID lnwire.ShortChannelID,
chanID lnwire.ChannelID, chanType channeldb.ChannelType) error {
// First, we'll create the batch of announcements to be sent upon
// initial channel creation. This includes the channel announcement
// itself, the channel update announcement, and our half of the channel
// proof needed to fully authenticate the channel.
//
// We can pass in zeroes for the min and max htlc policy, because we
// only use the channel announcement message from the returned struct.
ann, err := f.newChanAnnouncement(
localIDKey, remoteIDKey, localFundingKey, remoteFundingKey,
shortChanID, chanID, 0, 0, nil, chanType,
)
if err != nil {
log.Errorf("can't generate channel announcement: %v", err)
return err
}
// We only send the channel proof announcement and the node announcement
// because addToGraph previously sent the ChannelAnnouncement and
// the ChannelUpdate announcement messages. The channel proof and node
// announcements are broadcast to the greater network.
errChan := f.cfg.SendAnnouncement(ann.chanProof)
select {
case err := <-errChan:
if err != nil {
if graph.IsError(err, graph.ErrOutdated,
graph.ErrIgnored) {
log.Debugf("Graph rejected "+
"AnnounceSignatures: %v", err)
} else {
log.Errorf("Unable to send channel "+
"proof: %v", err)
return err
}
}
case <-f.quit:
return ErrFundingManagerShuttingDown
}
// Now that the channel is announced to the network, we will also
// obtain and send a node announcement. This is done since a node
// announcement is only accepted after a channel is known for that
// particular node, and this might be our first channel.
nodeAnn, err := f.cfg.CurrentNodeAnnouncement()
if err != nil {
log.Errorf("can't generate node announcement: %v", err)
return err
}
errChan = f.cfg.SendAnnouncement(&nodeAnn)
select {
case err := <-errChan:
if err != nil {
if graph.IsError(err, graph.ErrOutdated,
graph.ErrIgnored) {
log.Debugf("Graph rejected "+
"NodeAnnouncement: %v", err)
} else {
log.Errorf("Unable to send node "+
"announcement: %v", err)
return err
}
}
case <-f.quit:
return ErrFundingManagerShuttingDown
}
return nil
}
// InitFundingWorkflow sends a message to the funding manager instructing it
// to initiate a single funder workflow with the source peer.
func (f *Manager) InitFundingWorkflow(msg *InitFundingMsg) {
f.fundingRequests <- msg
}
// getUpfrontShutdownScript takes a user provided script and a getScript
// function which can be used to generate an upfront shutdown script. If our
// peer does not support the feature, this function will error if a non-zero
// script was provided by the user, and return an empty script otherwise. If
// our peer does support the feature, we will return the user provided script
// if non-zero, or a freshly generated script if our node is configured to set
// upfront shutdown scripts automatically.
func getUpfrontShutdownScript(enableUpfrontShutdown bool, peer lnpeer.Peer,
script lnwire.DeliveryAddress,
getScript func(bool) (lnwire.DeliveryAddress, error)) (lnwire.DeliveryAddress,
error) {
// Check whether the remote peer supports upfront shutdown scripts.
remoteUpfrontShutdown := peer.RemoteFeatures().HasFeature(
lnwire.UpfrontShutdownScriptOptional,
)
// If the peer does not support upfront shutdown scripts, and one has been
// provided, return an error because the feature is not supported.
if !remoteUpfrontShutdown && len(script) != 0 {
return nil, errUpfrontShutdownScriptNotSupported
}
// If the peer does not support upfront shutdown, return an empty address.
if !remoteUpfrontShutdown {
return nil, nil
}
// If the user has provided an script and the peer supports the feature,
// return it. Note that user set scripts override the enable upfront
// shutdown flag.
if len(script) > 0 {
return script, nil
}
// If we do not have setting of upfront shutdown script enabled, return
// an empty script.
if !enableUpfrontShutdown {
return nil, nil
}
// We can safely send a taproot address iff, both sides have negotiated
// the shutdown-any-segwit feature.
taprootOK := peer.RemoteFeatures().HasFeature(lnwire.ShutdownAnySegwitOptional) &&
peer.LocalFeatures().HasFeature(lnwire.ShutdownAnySegwitOptional)
return getScript(taprootOK)
}
// handleInitFundingMsg creates a channel reservation within the daemon's
// wallet, then sends a funding request to the remote peer kicking off the
// funding workflow.
func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
var (
peerKey = msg.Peer.IdentityKey()
localAmt = msg.LocalFundingAmt
baseFee = msg.BaseFee
feeRate = msg.FeeRate
minHtlcIn = msg.MinHtlcIn
remoteCsvDelay = msg.RemoteCsvDelay
maxValue = msg.MaxValueInFlight
maxHtlcs = msg.MaxHtlcs
maxCSV = msg.MaxLocalCsv
chanReserve = msg.RemoteChanReserve
outpoints = msg.Outpoints
)
// If no maximum CSV delay was set for this channel, we use our default
// value.
if maxCSV == 0 {
maxCSV = f.cfg.MaxLocalCSVDelay
}
log.Infof("Initiating fundingRequest(local_amt=%v "+
"(subtract_fees=%v), push_amt=%v, chain_hash=%v, peer=%x, "+
"min_confs=%v)", localAmt, msg.SubtractFees, msg.PushAmt,
msg.ChainHash, peerKey.SerializeCompressed(), msg.MinConfs)
// We set the channel flags to indicate whether we want this channel to
// be announced to the network.
var channelFlags lnwire.FundingFlag
if !msg.Private {
// This channel will be announced.
channelFlags = lnwire.FFAnnounceChannel
}
// If the caller specified their own channel ID, then we'll use that.
// Otherwise we'll generate a fresh one as normal. This will be used
// to track this reservation throughout its lifetime.
var chanID PendingChanID
if msg.PendingChanID == zeroID {
chanID = f.nextPendingChanID()
} else {
// If the user specified their own pending channel ID, then
// we'll ensure it doesn't collide with any existing pending
// channel ID.
chanID = msg.PendingChanID
if _, err := f.getReservationCtx(peerKey, chanID); err == nil {
msg.Err <- fmt.Errorf("pendingChannelID(%x) "+
"already present", chanID[:])
return
}
}
// Check whether the peer supports upfront shutdown, and get an address
// which should be used (either a user specified address or a new
// address from the wallet if our node is configured to set shutdown
// address by default).
shutdown, err := getUpfrontShutdownScript(
f.cfg.EnableUpfrontShutdown, msg.Peer, msg.ShutdownScript,
f.selectShutdownScript,
)
if err != nil {
msg.Err <- err
return
}
// Initialize a funding reservation with the local wallet. If the
// wallet doesn't have enough funds to commit to this channel, then the
// request will fail, and be aborted.
//
// Before we init the channel, we'll also check to see what commitment
// format we can use with this peer. This is dependent on *both* us and
// the remote peer are signaling the proper feature bit.
chanType, commitType, err := negotiateCommitmentType(
msg.ChannelType, msg.Peer.LocalFeatures(),
msg.Peer.RemoteFeatures(),
)
if err != nil {
log.Errorf("channel type negotiation failed: %v", err)
msg.Err <- err
return
}
var (
zeroConf bool
scid bool
)
if chanType != nil {
// Check if the returned chanType includes either the zero-conf
// or scid-alias bits.
featureVec := lnwire.RawFeatureVector(*chanType)
zeroConf = featureVec.IsSet(lnwire.ZeroConfRequired)
scid = featureVec.IsSet(lnwire.ScidAliasRequired)
// The option-scid-alias channel type for a public channel is
// disallowed.
if scid && !msg.Private {
err = fmt.Errorf("option-scid-alias chantype for " +
"public channel")
log.Error(err)
msg.Err <- err
return
}
}
// First, we'll query the fee estimator for a fee that should get the
// commitment transaction confirmed by the next few blocks (conf target
// of 3). We target the near blocks here to ensure that we'll be able
// to execute a timely unilateral channel closure if needed.
commitFeePerKw, err := f.cfg.FeeEstimator.EstimateFeePerKW(3)
if err != nil {
msg.Err <- err
return
}
// For anchor channels cap the initial commit fee rate at our defined
// maximum.
if commitType.HasAnchors() &&
commitFeePerKw > f.cfg.MaxAnchorsCommitFeeRate {
commitFeePerKw = f.cfg.MaxAnchorsCommitFeeRate
}
var scidFeatureVal bool
if hasFeatures(
msg.Peer.LocalFeatures(), msg.Peer.RemoteFeatures(),
lnwire.ScidAliasOptional,
) {
scidFeatureVal = true
}
// At this point, if we have an AuxFundingController active, we'll check
// to see if we have a special tapscript root to use in our MuSig2
// funding output.
tapscriptRoot, err := fn.MapOptionZ(
f.cfg.AuxFundingController,
func(c AuxFundingController) AuxTapscriptResult {
return c.DeriveTapscriptRoot(chanID)
},
).Unpack()
if err != nil {
err = fmt.Errorf("error deriving tapscript root: %w", err)
log.Error(err)
msg.Err <- err
return
}
req := &lnwallet.InitFundingReserveMsg{
ChainHash: &msg.ChainHash,
PendingChanID: chanID,
NodeID: peerKey,
NodeAddr: msg.Peer.Address(),
SubtractFees: msg.SubtractFees,
LocalFundingAmt: localAmt,
RemoteFundingAmt: 0,
FundUpToMaxAmt: msg.FundUpToMaxAmt,
MinFundAmt: msg.MinFundAmt,
RemoteChanReserve: chanReserve,
Outpoints: outpoints,
CommitFeePerKw: commitFeePerKw,
FundingFeePerKw: msg.FundingFeePerKw,
PushMSat: msg.PushAmt,
Flags: channelFlags,
MinConfs: msg.MinConfs,
CommitType: commitType,
ChanFunder: msg.ChanFunder,
// Unconfirmed Utxos which are marked by the sweeper subsystem
// are excluded from the coin selection because they are not
// final and can be RBFed by the sweeper subsystem.
AllowUtxoForFunding: func(u lnwallet.Utxo) bool {
// Utxos with at least 1 confirmation are safe to use
// for channel openings because they don't bare the risk
// of being replaced (BIP 125 RBF).
if u.Confirmations > 0 {
return true
}
// Query the sweeper storage to make sure we don't use
// an unconfirmed utxo still in use by the sweeper
// subsystem.
return !f.cfg.IsSweeperOutpoint(u.OutPoint)
},
ZeroConf: zeroConf,
OptionScidAlias: scid,
ScidAliasFeature: scidFeatureVal,
Memo: msg.Memo,
TapscriptRoot: tapscriptRoot,
}
reservation, err := f.cfg.Wallet.InitChannelReservation(req)
if err != nil {
msg.Err <- err
return
}
if zeroConf {
// Store the alias for zero-conf channels in the underlying
// partial channel state.
aliasScid, err := f.cfg.AliasManager.RequestAlias()
if err != nil {
msg.Err <- err
return
}
reservation.AddAlias(aliasScid)
}
// Set our upfront shutdown address in the existing reservation.
reservation.SetOurUpfrontShutdown(shutdown)
// Now that we have successfully reserved funds for this channel in the
// wallet, we can fetch the final channel capacity. This is done at
// this point since the final capacity might change in case of
// SubtractFees=true.
capacity := reservation.Capacity()
log.Infof("Target commit tx sat/kw for pendingID(%x): %v", chanID,
int64(commitFeePerKw))
// If the remote CSV delay was not set in the open channel request,
// we'll use the RequiredRemoteDelay closure to compute the delay we
// require given the total amount of funds within the channel.
if remoteCsvDelay == 0 {
remoteCsvDelay = f.cfg.RequiredRemoteDelay(capacity)
}
// If no minimum HTLC value was specified, use the default one.
if minHtlcIn == 0 {
minHtlcIn = f.cfg.DefaultMinHtlcIn
}
// If no max value was specified, use the default one.
if maxValue == 0 {
maxValue = f.cfg.RequiredRemoteMaxValue(capacity)
}
if maxHtlcs == 0 {
maxHtlcs = f.cfg.RequiredRemoteMaxHTLCs(capacity)
}
// Once the reservation has been created, and indexed, queue a funding
// request to the remote peer, kicking off the funding workflow.
ourContribution := reservation.OurContribution()
// Prepare the optional channel fee values from the initFundingMsg. If
// useBaseFee or useFeeRate are false the client did not provide fee
// values hence we assume default fee settings from the config.
forwardingPolicy := f.defaultForwardingPolicy(
ourContribution.ChannelStateBounds,
)
if baseFee != nil {
forwardingPolicy.BaseFee = lnwire.MilliSatoshi(*baseFee)
}
if feeRate != nil {
forwardingPolicy.FeeRate = lnwire.MilliSatoshi(*feeRate)
}
// Fetch our dust limit which is part of the default channel
// constraints, and log it.
ourDustLimit := ourContribution.DustLimit
log.Infof("Dust limit for pendingID(%x): %v", chanID, ourDustLimit)
// If the channel reserve is not specified, then we calculate an
// appropriate amount here.
if chanReserve == 0 {
chanReserve = f.cfg.RequiredRemoteChanReserve(
capacity, ourDustLimit,
)
}
// If a pending channel map for this peer isn't already created, then
// we create one, ultimately allowing us to track this pending
// reservation within the target peer.
peerIDKey := newSerializedKey(peerKey)
f.resMtx.Lock()
if _, ok := f.activeReservations[peerIDKey]; !ok {
f.activeReservations[peerIDKey] = make(pendingChannels)
}
resCtx := &reservationWithCtx{
chanAmt: capacity,
forwardingPolicy: *forwardingPolicy,
remoteCsvDelay: remoteCsvDelay,
remoteMinHtlc: minHtlcIn,
remoteMaxValue: maxValue,
remoteMaxHtlcs: maxHtlcs,
remoteChanReserve: chanReserve,
maxLocalCsv: maxCSV,
channelType: chanType,
reservation: reservation,
peer: msg.Peer,
updates: msg.Updates,
err: msg.Err,
}
f.activeReservations[peerIDKey][chanID] = resCtx
f.resMtx.Unlock()
// Update the timestamp once the InitFundingMsg has been handled.
defer resCtx.updateTimestamp()
// Check the sanity of the selected channel constraints.
bounds := &channeldb.ChannelStateBounds{
ChanReserve: chanReserve,
MaxPendingAmount: maxValue,
MinHTLC: minHtlcIn,
MaxAcceptedHtlcs: maxHtlcs,
}
commitParams := &channeldb.CommitmentParams{
DustLimit: ourDustLimit,
CsvDelay: remoteCsvDelay,
}
err = lnwallet.VerifyConstraints(
bounds, commitParams, resCtx.maxLocalCsv, capacity,
)
if err != nil {
_, reserveErr := f.cancelReservationCtx(peerKey, chanID, false)
if reserveErr != nil {
log.Errorf("unable to cancel reservation: %v",
reserveErr)
}
msg.Err <- err
return
}
// When opening a script enforced channel lease, include the required
// expiry TLV record in our proposal.
var leaseExpiry *lnwire.LeaseExpiry
if commitType == lnwallet.CommitmentTypeScriptEnforcedLease {
leaseExpiry = new(lnwire.LeaseExpiry)
*leaseExpiry = lnwire.LeaseExpiry(reservation.LeaseExpiry())
}
log.Infof("Starting funding workflow with %v for pending_id(%x), "+
"committype=%v", msg.Peer.Address(), chanID, commitType)
reservation.SetState(lnwallet.SentOpenChannel)
fundingOpen := lnwire.OpenChannel{
ChainHash: *f.cfg.Wallet.Cfg.NetParams.GenesisHash,
PendingChannelID: chanID,
FundingAmount: capacity,
PushAmount: msg.PushAmt,
DustLimit: ourDustLimit,
MaxValueInFlight: maxValue,
ChannelReserve: chanReserve,
HtlcMinimum: minHtlcIn,
FeePerKiloWeight: uint32(commitFeePerKw),
CsvDelay: remoteCsvDelay,
MaxAcceptedHTLCs: maxHtlcs,
FundingKey: ourContribution.MultiSigKey.PubKey,
RevocationPoint: ourContribution.RevocationBasePoint.PubKey,
PaymentPoint: ourContribution.PaymentBasePoint.PubKey,
HtlcPoint: ourContribution.HtlcBasePoint.PubKey,
DelayedPaymentPoint: ourContribution.DelayBasePoint.PubKey,
FirstCommitmentPoint: ourContribution.FirstCommitmentPoint,
ChannelFlags: channelFlags,
UpfrontShutdownScript: shutdown,
ChannelType: chanType,
LeaseExpiry: leaseExpiry,
}
if commitType.IsTaproot() {
fundingOpen.LocalNonce = lnwire.SomeMusig2Nonce(
ourContribution.LocalNonce.PubNonce,
)
}
if err := msg.Peer.SendMessage(true, &fundingOpen); err != nil {
e := fmt.Errorf("unable to send funding request message: %w",
err)
log.Errorf(e.Error())
// Since we were unable to send the initial message to the peer
// and start the funding flow, we'll cancel this reservation.
_, err := f.cancelReservationCtx(peerKey, chanID, false)
if err != nil {
log.Errorf("unable to cancel reservation: %v", err)
}
msg.Err <- e
return
}
}
// handleWarningMsg processes the warning which was received from remote peer.
func (f *Manager) handleWarningMsg(peer lnpeer.Peer, msg *lnwire.Warning) {
log.Warnf("received warning message from peer %x: %v",
peer.IdentityKey().SerializeCompressed(), msg.Warning())
}
// handleErrorMsg processes the error which was received from remote peer,
// depending on the type of error we should do different clean up steps and
// inform the user about it.
func (f *Manager) handleErrorMsg(peer lnpeer.Peer, msg *lnwire.Error) {
chanID := msg.ChanID
peerKey := peer.IdentityKey()
// First, we'll attempt to retrieve and cancel the funding workflow
// that this error was tied to. If we're unable to do so, then we'll
// exit early as this was an unwarranted error.
resCtx, err := f.cancelReservationCtx(peerKey, chanID, true)
if err != nil {
log.Warnf("Received error for non-existent funding "+
"flow: %v (%v)", err, msg.Error())
return
}
// If we did indeed find the funding workflow, then we'll return the
// error back to the caller (if any), and cancel the workflow itself.
fundingErr := fmt.Errorf("received funding error from %x: %v",
peerKey.SerializeCompressed(), msg.Error(),
)
log.Errorf(fundingErr.Error())
// If this was a PSBT funding flow, the remote likely timed out because
// we waited too long. Return a nice error message to the user in that
// case so the user knows what's the problem.
if resCtx.reservation.IsPsbt() {
fundingErr = fmt.Errorf("%w: %v", chanfunding.ErrRemoteCanceled,
fundingErr)
}
resCtx.err <- fundingErr
}
// pruneZombieReservations loops through all pending reservations and fails the
// funding flow for any reservations that have not been updated since the
// ReservationTimeout and are not locked waiting for the funding transaction.
func (f *Manager) pruneZombieReservations() {
zombieReservations := make(pendingChannels)
f.resMtx.RLock()
for _, pendingReservations := range f.activeReservations {
for pendingChanID, resCtx := range pendingReservations {
if resCtx.isLocked() {
continue
}
// We don't want to expire PSBT funding reservations.
// These reservations are always initiated by us and the
// remote peer is likely going to cancel them after some
// idle time anyway. So no need for us to also prune
// them.
sinceLastUpdate := time.Since(resCtx.lastUpdated)
isExpired := sinceLastUpdate > f.cfg.ReservationTimeout
if !resCtx.reservation.IsPsbt() && isExpired {
zombieReservations[pendingChanID] = resCtx
}
}
}
f.resMtx.RUnlock()
for pendingChanID, resCtx := range zombieReservations {
err := fmt.Errorf("reservation timed out waiting for peer "+
"(peer_id:%x, chan_id:%x)",
resCtx.peer.IdentityKey().SerializeCompressed(),
pendingChanID[:])
log.Warnf(err.Error())
chanID := lnwire.NewChanIDFromOutPoint(
*resCtx.reservation.FundingOutpoint(),
)
// Create channel identifier and set the channel ID.
cid := newChanIdentifier(pendingChanID)
cid.setChanID(chanID)
f.failFundingFlow(resCtx.peer, cid, err)
}
}
// cancelReservationCtx does all needed work in order to securely cancel the
// reservation.
func (f *Manager) cancelReservationCtx(peerKey *btcec.PublicKey,
pendingChanID PendingChanID,
byRemote bool) (*reservationWithCtx, error) {
log.Infof("Cancelling funding reservation for node_key=%x, "+
"chan_id=%x", peerKey.SerializeCompressed(), pendingChanID[:])
peerIDKey := newSerializedKey(peerKey)
f.resMtx.Lock()
defer f.resMtx.Unlock()
nodeReservations, ok := f.activeReservations[peerIDKey]
if !ok {
// No reservations for this node.
return nil, errors.Errorf("no active reservations for peer(%x)",
peerIDKey[:])
}
ctx, ok := nodeReservations[pendingChanID]
if !ok {
return nil, errors.Errorf("unknown channel (id: %x) for "+
"peer(%x)", pendingChanID[:], peerIDKey[:])
}
// If the reservation was a PSBT funding flow and it was canceled by the
// remote peer, then we need to thread through a different error message
// to the subroutine that's waiting for the user input so it can return
// a nice error message to the user.
if ctx.reservation.IsPsbt() && byRemote {
ctx.reservation.RemoteCanceled()
}
if err := ctx.reservation.Cancel(); err != nil {
return nil, errors.Errorf("unable to cancel reservation: %v",
err)
}
delete(nodeReservations, pendingChanID)
// If this was the last active reservation for this peer, delete the
// peer's entry altogether.
if len(nodeReservations) == 0 {
delete(f.activeReservations, peerIDKey)
}
return ctx, nil
}
// deleteReservationCtx deletes the reservation uniquely identified by the
// target public key of the peer, and the specified pending channel ID.
func (f *Manager) deleteReservationCtx(peerKey *btcec.PublicKey,
pendingChanID PendingChanID) {
peerIDKey := newSerializedKey(peerKey)
f.resMtx.Lock()
defer f.resMtx.Unlock()
nodeReservations, ok := f.activeReservations[peerIDKey]
if !ok {
// No reservations for this node.
return
}
delete(nodeReservations, pendingChanID)
// If this was the last active reservation for this peer, delete the
// peer's entry altogether.
if len(nodeReservations) == 0 {
delete(f.activeReservations, peerIDKey)
}
}
// getReservationCtx returns the reservation context for a particular pending
// channel ID for a target peer.
func (f *Manager) getReservationCtx(peerKey *btcec.PublicKey,
pendingChanID PendingChanID) (*reservationWithCtx, error) {
peerIDKey := newSerializedKey(peerKey)
f.resMtx.RLock()
resCtx, ok := f.activeReservations[peerIDKey][pendingChanID]
f.resMtx.RUnlock()
if !ok {
return nil, errors.Errorf("unknown channel (id: %x) for "+
"peer(%x)", pendingChanID[:], peerIDKey[:])
}
return resCtx, nil
}
// IsPendingChannel returns a boolean indicating whether the channel identified
// by the pendingChanID and given peer is pending, meaning it is in the process
// of being funded. After the funding transaction has been confirmed, the
// channel will receive a new, permanent channel ID, and will no longer be
// considered pending.
func (f *Manager) IsPendingChannel(pendingChanID PendingChanID,
peer lnpeer.Peer) bool {
peerIDKey := newSerializedKey(peer.IdentityKey())
f.resMtx.RLock()
_, ok := f.activeReservations[peerIDKey][pendingChanID]
f.resMtx.RUnlock()
return ok
}
func copyPubKey(pub *btcec.PublicKey) *btcec.PublicKey {
var tmp btcec.JacobianPoint
pub.AsJacobian(&tmp)
tmp.ToAffine()
return btcec.NewPublicKey(&tmp.X, &tmp.Y)
}
// defaultForwardingPolicy returns the default forwarding policy based on the
// default routing policy and our local channel constraints.
func (f *Manager) defaultForwardingPolicy(
bounds channeldb.ChannelStateBounds) *models.ForwardingPolicy {
return &models.ForwardingPolicy{
MinHTLCOut: bounds.MinHTLC,
MaxHTLC: bounds.MaxPendingAmount,
BaseFee: f.cfg.DefaultRoutingPolicy.BaseFee,
FeeRate: f.cfg.DefaultRoutingPolicy.FeeRate,
TimeLockDelta: f.cfg.DefaultRoutingPolicy.TimeLockDelta,
}
}
// saveInitialForwardingPolicy saves the forwarding policy for the provided
// chanPoint in the channelOpeningStateBucket.
func (f *Manager) saveInitialForwardingPolicy(chanID lnwire.ChannelID,
forwardingPolicy *models.ForwardingPolicy) error {
return f.cfg.ChannelDB.SaveInitialForwardingPolicy(
chanID, forwardingPolicy,
)
}
// getInitialForwardingPolicy fetches the initial forwarding policy for a given
// channel id from the database which will be applied during the channel
// announcement phase.
func (f *Manager) getInitialForwardingPolicy(
chanID lnwire.ChannelID) (*models.ForwardingPolicy, error) {
return f.cfg.ChannelDB.GetInitialForwardingPolicy(chanID)
}
// deleteInitialForwardingPolicy removes channel fees for this chanID from
// the database.
func (f *Manager) deleteInitialForwardingPolicy(chanID lnwire.ChannelID) error {
return f.cfg.ChannelDB.DeleteInitialForwardingPolicy(chanID)
}
// saveChannelOpeningState saves the channelOpeningState for the provided
// chanPoint to the channelOpeningStateBucket.
func (f *Manager) saveChannelOpeningState(chanPoint *wire.OutPoint,
state channelOpeningState, shortChanID *lnwire.ShortChannelID) error {
var outpointBytes bytes.Buffer
if err := WriteOutpoint(&outpointBytes, chanPoint); err != nil {
return err
}
// Save state and the uint64 representation of the shortChanID
// for later use.
scratch := make([]byte, 10)
byteOrder.PutUint16(scratch[:2], uint16(state))
byteOrder.PutUint64(scratch[2:], shortChanID.ToUint64())
return f.cfg.ChannelDB.SaveChannelOpeningState(
outpointBytes.Bytes(), scratch,
)
}
// getChannelOpeningState fetches the channelOpeningState for the provided
// chanPoint from the database, or returns ErrChannelNotFound if the channel
// is not found.
func (f *Manager) getChannelOpeningState(chanPoint *wire.OutPoint) (
channelOpeningState, *lnwire.ShortChannelID, error) {
var outpointBytes bytes.Buffer
if err := WriteOutpoint(&outpointBytes, chanPoint); err != nil {
return 0, nil, err
}
value, err := f.cfg.ChannelDB.GetChannelOpeningState(
outpointBytes.Bytes(),
)
if err != nil {
return 0, nil, err
}
state := channelOpeningState(byteOrder.Uint16(value[:2]))
shortChanID := lnwire.NewShortChanIDFromInt(byteOrder.Uint64(value[2:]))
return state, &shortChanID, nil
}
// deleteChannelOpeningState removes any state for chanPoint from the database.
func (f *Manager) deleteChannelOpeningState(chanPoint *wire.OutPoint) error {
var outpointBytes bytes.Buffer
if err := WriteOutpoint(&outpointBytes, chanPoint); err != nil {
return err
}
return f.cfg.ChannelDB.DeleteChannelOpeningState(
outpointBytes.Bytes(),
)
}
// selectShutdownScript selects the shutdown script we should send to the peer.
// If we can use taproot, then we prefer that, otherwise we'll use a p2wkh
// script.
func (f *Manager) selectShutdownScript(taprootOK bool,
) (lnwire.DeliveryAddress, error) {
addrType := lnwallet.WitnessPubKey
if taprootOK {
addrType = lnwallet.TaprootPubkey
}
addr, err := f.cfg.Wallet.NewAddress(
addrType, false, lnwallet.DefaultAccountName,
)
if err != nil {
return nil, err
}
return txscript.PayToAddrScript(addr)
}
// waitForPeerOnline blocks until the peer specified by peerPubkey comes online
// and then returns the online peer.
func (f *Manager) waitForPeerOnline(peerPubkey *btcec.PublicKey) (lnpeer.Peer,
error) {
peerChan := make(chan lnpeer.Peer, 1)
var peerKey [33]byte
copy(peerKey[:], peerPubkey.SerializeCompressed())
f.cfg.NotifyWhenOnline(peerKey, peerChan)
var peer lnpeer.Peer
select {
case peer = <-peerChan:
case <-f.quit:
return peer, ErrFundingManagerShuttingDown
}
return peer, nil
}
package graph
import (
"fmt"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/wire"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/batch"
"github.com/lightningnetwork/lnd/chainntnfs"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/multimutex"
"github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/routing/chainview"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/ticker"
)
const (
// DefaultChannelPruneExpiry is the default duration used to determine
// if a channel should be pruned or not.
DefaultChannelPruneExpiry = time.Hour * 24 * 14
// DefaultFirstTimePruneDelay is the time we'll wait after startup
// before attempting to prune the graph for zombie channels. We don't
// do it immediately after startup to allow lnd to start up without
// getting blocked by this job.
DefaultFirstTimePruneDelay = 30 * time.Second
// defaultStatInterval governs how often the router will log non-empty
// stats related to processing new channels, updates, or node
// announcements.
defaultStatInterval = time.Minute
)
var (
// ErrGraphBuilderShuttingDown is returned if the graph builder is in
// the process of shutting down.
ErrGraphBuilderShuttingDown = fmt.Errorf("graph builder shutting down")
)
// Config holds the configuration required by the Builder.
type Config struct {
// SelfNode is the public key of the node that this channel router
// belongs to.
SelfNode route.Vertex
// Graph is the channel graph that the ChannelRouter will use to gather
// metrics from and also to carry out path finding queries.
Graph DB
// Chain is the router's source to the most up-to-date blockchain data.
// All incoming advertised channels will be checked against the chain
// to ensure that the channels advertised are still open.
Chain lnwallet.BlockChainIO
// ChainView is an instance of a FilteredChainView which is used to
// watch the sub-set of the UTXO set (the set of active channels) that
// we need in order to properly maintain the channel graph.
ChainView chainview.FilteredChainView
// Notifier is a reference to the ChainNotifier, used to grab
// the latest blocks if the router is missing any.
Notifier chainntnfs.ChainNotifier
// ChannelPruneExpiry is the duration used to determine if a channel
// should be pruned or not. If the delta between now and when the
// channel was last updated is greater than ChannelPruneExpiry, then
// the channel is marked as a zombie channel eligible for pruning.
ChannelPruneExpiry time.Duration
// GraphPruneInterval is used as an interval to determine how often we
// should examine the channel graph to garbage collect zombie channels.
GraphPruneInterval time.Duration
// FirstTimePruneDelay is the time we'll wait after startup before
// attempting to prune the graph for zombie channels. We don't do it
// immediately after startup to allow lnd to start up without getting
// blocked by this job.
FirstTimePruneDelay time.Duration
// AssumeChannelValid toggles whether the builder will prune channels
// based on their spentness vs using the fact that they are considered
// zombies.
AssumeChannelValid bool
// StrictZombiePruning determines if we attempt to prune zombie
// channels according to a stricter criteria. If true, then we'll prune
// a channel if only *one* of the edges is considered a zombie.
// Otherwise, we'll only prune the channel when both edges have a very
// dated last update.
StrictZombiePruning bool
// IsAlias returns whether a passed ShortChannelID is an alias. This is
// only used for our local channels.
IsAlias func(scid lnwire.ShortChannelID) bool
}
// Builder builds and maintains a view of the Lightning Network graph.
type Builder struct {
started atomic.Bool
stopped atomic.Bool
bestHeight atomic.Uint32
cfg *Config
// newBlocks is a channel in which new blocks connected to the end of
// the main chain are sent over, and blocks updated after a call to
// UpdateFilter.
newBlocks <-chan *chainview.FilteredBlock
// staleBlocks is a channel in which blocks disconnected from the end
// of our currently known best chain are sent over.
staleBlocks <-chan *chainview.FilteredBlock
// channelEdgeMtx is a mutex we use to make sure we process only one
// ChannelEdgePolicy at a time for a given channelID, to ensure
// consistency between the various database accesses.
channelEdgeMtx *multimutex.Mutex[uint64]
// statTicker is a resumable ticker that logs the router's progress as
// it discovers channels or receives updates.
statTicker ticker.Ticker
// stats tracks newly processed channels, updates, and node
// announcements over a window of defaultStatInterval.
stats *builderStats
quit chan struct{}
wg sync.WaitGroup
}
// A compile time check to ensure Builder implements the
// ChannelGraphSource interface.
var _ ChannelGraphSource = (*Builder)(nil)
// NewBuilder constructs a new Builder.
func NewBuilder(cfg *Config) (*Builder, error) {
return &Builder{
cfg: cfg,
channelEdgeMtx: multimutex.NewMutex[uint64](),
statTicker: ticker.New(defaultStatInterval),
stats: new(builderStats),
quit: make(chan struct{}),
}, nil
}
// Start launches all the goroutines the Builder requires to carry out its
// duties. If the builder has already been started, then this method is a noop.
func (b *Builder) Start() error {
if !b.started.CompareAndSwap(false, true) {
return nil
}
log.Info("Builder starting")
bestHash, bestHeight, err := b.cfg.Chain.GetBestBlock()
if err != nil {
return err
}
// If the graph has never been pruned, or hasn't fully been created yet,
// then we don't treat this as an explicit error.
if _, _, err := b.cfg.Graph.PruneTip(); err != nil {
switch {
case errors.Is(err, graphdb.ErrGraphNeverPruned):
fallthrough
case errors.Is(err, graphdb.ErrGraphNotFound):
// If the graph has never been pruned, then we'll set
// the prune height to the current best height of the
// chain backend.
_, err = b.cfg.Graph.PruneGraph(
nil, bestHash, uint32(bestHeight),
)
if err != nil {
return err
}
default:
return err
}
}
// If AssumeChannelValid is present, then we won't rely on pruning
// channels from the graph based on their spentness, but whether they
// are considered zombies or not. We will start zombie pruning after a
// small delay, to avoid slowing down startup of lnd.
if b.cfg.AssumeChannelValid { //nolint:nestif
time.AfterFunc(b.cfg.FirstTimePruneDelay, func() {
select {
case <-b.quit:
return
default:
}
log.Info("Initial zombie prune starting")
if err := b.pruneZombieChans(); err != nil {
log.Errorf("Unable to prune zombies: %v", err)
}
})
} else {
// Otherwise, we'll use our filtered chain view to prune
// channels as soon as they are detected as spent on-chain.
if err := b.cfg.ChainView.Start(); err != nil {
return err
}
// Once the instance is active, we'll fetch the channel we'll
// receive notifications over.
b.newBlocks = b.cfg.ChainView.FilteredBlocks()
b.staleBlocks = b.cfg.ChainView.DisconnectedBlocks()
// Before we perform our manual block pruning, we'll construct
// and apply a fresh chain filter to the active
// FilteredChainView instance. We do this before, as otherwise
// we may miss on-chain events as the filter hasn't properly
// been applied.
channelView, err := b.cfg.Graph.ChannelView()
if err != nil && !errors.Is(
err, graphdb.ErrGraphNoEdgesFound,
) {
return err
}
log.Infof("Filtering chain using %v channels active",
len(channelView))
if len(channelView) != 0 {
err = b.cfg.ChainView.UpdateFilter(
channelView, uint32(bestHeight),
)
if err != nil {
return err
}
}
// The graph pruning might have taken a while and there could be
// new blocks available.
_, bestHeight, err = b.cfg.Chain.GetBestBlock()
if err != nil {
return err
}
b.bestHeight.Store(uint32(bestHeight))
// Before we begin normal operation of the router, we first need
// to synchronize the channel graph to the latest state of the
// UTXO set.
if err := b.syncGraphWithChain(); err != nil {
return err
}
// Finally, before we proceed, we'll prune any unconnected nodes
// from the graph in order to ensure we maintain a tight graph
// of "useful" nodes.
err = b.cfg.Graph.PruneGraphNodes()
if err != nil &&
!errors.Is(err, graphdb.ErrGraphNodesNotFound) {
return err
}
}
b.wg.Add(1)
go b.networkHandler()
log.Debug("Builder started")
return nil
}
// Stop signals to the Builder that it should halt all routines. This method
// will *block* until all goroutines have excited. If the builder has already
// stopped then this method will return immediately.
func (b *Builder) Stop() error {
if !b.stopped.CompareAndSwap(false, true) {
return nil
}
log.Info("Builder shutting down...")
// Our filtered chain view could've only been started if
// AssumeChannelValid isn't present.
if !b.cfg.AssumeChannelValid {
if err := b.cfg.ChainView.Stop(); err != nil {
return err
}
}
close(b.quit)
b.wg.Wait()
log.Debug("Builder shutdown complete")
return nil
}
// syncGraphWithChain attempts to synchronize the current channel graph with
// the latest UTXO set state. This process involves pruning from the channel
// graph any channels which have been closed by spending their funding output
// since we've been down.
func (b *Builder) syncGraphWithChain() error {
// First, we'll need to check to see if we're already in sync with the
// latest state of the UTXO set.
bestHash, bestHeight, err := b.cfg.Chain.GetBestBlock()
if err != nil {
return err
}
b.bestHeight.Store(uint32(bestHeight))
pruneHash, pruneHeight, err := b.cfg.Graph.PruneTip()
if err != nil {
switch {
// If the graph has never been pruned, or hasn't fully been
// created yet, then we don't treat this as an explicit error.
case errors.Is(err, graphdb.ErrGraphNeverPruned):
case errors.Is(err, graphdb.ErrGraphNotFound):
default:
return err
}
}
log.Infof("Prune tip for Channel Graph: height=%v, hash=%v",
pruneHeight, pruneHash)
switch {
// If the graph has never been pruned, then we can exit early as this
// entails it's being created for the first time and hasn't seen any
// block or created channels.
case pruneHeight == 0 || pruneHash == nil:
return nil
// If the block hashes and heights match exactly, then we don't need to
// prune the channel graph as we're already fully in sync.
case bestHash.IsEqual(pruneHash) && uint32(bestHeight) == pruneHeight:
return nil
}
// If the main chain blockhash at prune height is different from the
// prune hash, this might indicate the database is on a stale branch.
mainBlockHash, err := b.cfg.Chain.GetBlockHash(int64(pruneHeight))
if err != nil {
return err
}
// While we are on a stale branch of the chain, walk backwards to find
// first common block.
for !pruneHash.IsEqual(mainBlockHash) {
log.Infof("channel graph is stale. Disconnecting block %v "+
"(hash=%v)", pruneHeight, pruneHash)
// Prune the graph for every channel that was opened at height
// >= pruneHeight.
_, err := b.cfg.Graph.DisconnectBlockAtHeight(pruneHeight)
if err != nil {
return err
}
pruneHash, pruneHeight, err = b.cfg.Graph.PruneTip()
switch {
// If at this point the graph has never been pruned, we can exit
// as this entails we are back to the point where it hasn't seen
// any block or created channels, alas there's nothing left to
// prune.
case errors.Is(err, graphdb.ErrGraphNeverPruned):
return nil
case errors.Is(err, graphdb.ErrGraphNotFound):
return nil
case err != nil:
return err
default:
}
mainBlockHash, err = b.cfg.Chain.GetBlockHash(
int64(pruneHeight),
)
if err != nil {
return err
}
}
log.Infof("Syncing channel graph from height=%v (hash=%v) to "+
"height=%v (hash=%v)", pruneHeight, pruneHash, bestHeight,
bestHash)
// If we're not yet caught up, then we'll walk forward in the chain
// pruning the channel graph with each new block that hasn't yet been
// consumed by the channel graph.
var spentOutputs []*wire.OutPoint
for nextHeight := pruneHeight + 1; nextHeight <= uint32(bestHeight); nextHeight++ { //nolint:ll
// Break out of the rescan early if a shutdown has been
// requested, otherwise long rescans will block the daemon from
// shutting down promptly.
select {
case <-b.quit:
return ErrGraphBuilderShuttingDown
default:
}
// Using the next height, request a manual block pruning from
// the chainview for the particular block hash.
log.Infof("Filtering block for closed channels, at height: %v",
int64(nextHeight))
nextHash, err := b.cfg.Chain.GetBlockHash(int64(nextHeight))
if err != nil {
return err
}
log.Tracef("Running block filter on block with hash: %v",
nextHash)
filterBlock, err := b.cfg.ChainView.FilterBlock(nextHash)
if err != nil {
return err
}
// We're only interested in all prior outputs that have been
// spent in the block, so collate all the referenced previous
// outpoints within each tx and input.
for _, tx := range filterBlock.Transactions {
for _, txIn := range tx.TxIn {
spentOutputs = append(spentOutputs,
&txIn.PreviousOutPoint)
}
}
}
// With the spent outputs gathered, attempt to prune the channel graph,
// also passing in the best hash+height so the prune tip can be updated.
closedChans, err := b.cfg.Graph.PruneGraph(
spentOutputs, bestHash, uint32(bestHeight),
)
if err != nil {
return err
}
log.Infof("Graph pruning complete: %v channels were closed since "+
"height %v", len(closedChans), pruneHeight)
return nil
}
// isZombieChannel takes two edge policy updates and determines if the
// corresponding channel should be considered a zombie. The first boolean is
// true if the policy update from node 1 is considered a zombie, the second
// boolean is that of node 2, and the final boolean is true if the channel
// is considered a zombie.
func (b *Builder) isZombieChannel(e1,
e2 *models.ChannelEdgePolicy) (bool, bool, bool) {
chanExpiry := b.cfg.ChannelPruneExpiry
e1Zombie := e1 == nil || time.Since(e1.LastUpdate) >= chanExpiry
e2Zombie := e2 == nil || time.Since(e2.LastUpdate) >= chanExpiry
var e1Time, e2Time time.Time
if e1 != nil {
e1Time = e1.LastUpdate
}
if e2 != nil {
e2Time = e2.LastUpdate
}
return e1Zombie, e2Zombie, b.IsZombieChannel(e1Time, e2Time)
}
// IsZombieChannel takes the timestamps of the latest channel updates for a
// channel and returns true if the channel should be considered a zombie based
// on these timestamps.
func (b *Builder) IsZombieChannel(updateTime1,
updateTime2 time.Time) bool {
chanExpiry := b.cfg.ChannelPruneExpiry
e1Zombie := updateTime1.IsZero() ||
time.Since(updateTime1) >= chanExpiry
e2Zombie := updateTime2.IsZero() ||
time.Since(updateTime2) >= chanExpiry
// If we're using strict zombie pruning, then a channel is only
// considered live if both edges have a recent update we know of.
if b.cfg.StrictZombiePruning {
return e1Zombie || e2Zombie
}
// Otherwise, if we're using the less strict variant, then a channel is
// considered live if either of the edges have a recent update.
return e1Zombie && e2Zombie
}
// pruneZombieChans is a method that will be called periodically to prune out
// any "zombie" channels. We consider channels zombies if *both* edges haven't
// been updated since our zombie horizon. If AssumeChannelValid is present,
// we'll also consider channels zombies if *both* edges are disabled. This
// usually signals that a channel has been closed on-chain. We do this
// periodically to keep a healthy, lively routing table.
func (b *Builder) pruneZombieChans() error {
chansToPrune := make(map[uint64]struct{})
chanExpiry := b.cfg.ChannelPruneExpiry
log.Infof("Examining channel graph for zombie channels")
// A helper method to detect if the channel belongs to this node
isSelfChannelEdge := func(info *models.ChannelEdgeInfo) bool {
return info.NodeKey1Bytes == b.cfg.SelfNode ||
info.NodeKey2Bytes == b.cfg.SelfNode
}
// First, we'll collect all the channels which are eligible for garbage
// collection due to being zombies.
filterPruneChans := func(info *models.ChannelEdgeInfo,
e1, e2 *models.ChannelEdgePolicy) error {
// Exit early in case this channel is already marked to be
// pruned
_, markedToPrune := chansToPrune[info.ChannelID]
if markedToPrune {
return nil
}
// We'll ensure that we don't attempt to prune our *own*
// channels from the graph, as in any case this should be
// re-advertised by the sub-system above us.
if isSelfChannelEdge(info) {
return nil
}
e1Zombie, e2Zombie, isZombieChan := b.isZombieChannel(e1, e2)
if e1Zombie {
log.Tracef("Node1 pubkey=%x of chan_id=%v is zombie",
info.NodeKey1Bytes, info.ChannelID)
}
if e2Zombie {
log.Tracef("Node2 pubkey=%x of chan_id=%v is zombie",
info.NodeKey2Bytes, info.ChannelID)
}
// If either edge hasn't been updated for a period of
// chanExpiry, then we'll mark the channel itself as eligible
// for graph pruning.
if !isZombieChan {
return nil
}
log.Debugf("ChannelID(%v) is a zombie, collecting to prune",
info.ChannelID)
// TODO(roasbeef): add ability to delete single directional edge
chansToPrune[info.ChannelID] = struct{}{}
return nil
}
// If AssumeChannelValid is present we'll look at the disabled bit for
// both edges. If they're both disabled, then we can interpret this as
// the channel being closed and can prune it from our graph.
if b.cfg.AssumeChannelValid {
disabledChanIDs, err := b.cfg.Graph.DisabledChannelIDs()
if err != nil {
return fmt.Errorf("unable to get disabled channels "+
"ids chans: %v", err)
}
disabledEdges, err := b.cfg.Graph.FetchChanInfos(
disabledChanIDs,
)
if err != nil {
return fmt.Errorf("unable to fetch disabled channels "+
"edges chans: %v", err)
}
// Ensuring we won't prune our own channel from the graph.
for _, disabledEdge := range disabledEdges {
if !isSelfChannelEdge(disabledEdge.Info) {
chansToPrune[disabledEdge.Info.ChannelID] =
struct{}{}
}
}
}
startTime := time.Unix(0, 0)
endTime := time.Now().Add(-1 * chanExpiry)
oldEdges, err := b.cfg.Graph.ChanUpdatesInHorizon(startTime, endTime)
if err != nil {
return fmt.Errorf("unable to fetch expired channel updates "+
"chans: %v", err)
}
for _, u := range oldEdges {
err = filterPruneChans(u.Info, u.Policy1, u.Policy2)
if err != nil {
return fmt.Errorf("error filtering channels to "+
"prune: %w", err)
}
}
log.Infof("Pruning %v zombie channels", len(chansToPrune))
if len(chansToPrune) == 0 {
return nil
}
// With the set of zombie-like channels obtained, we'll do another pass
// to delete them from the channel graph.
toPrune := make([]uint64, 0, len(chansToPrune))
for chanID := range chansToPrune {
toPrune = append(toPrune, chanID)
log.Tracef("Pruning zombie channel with ChannelID(%v)", chanID)
}
err = b.cfg.Graph.DeleteChannelEdges(
b.cfg.StrictZombiePruning, true, toPrune...,
)
if err != nil {
return fmt.Errorf("unable to delete zombie channels: %w", err)
}
// With the channels pruned, we'll also attempt to prune any nodes that
// were a part of them.
err = b.cfg.Graph.PruneGraphNodes()
if err != nil && !errors.Is(err, graphdb.ErrGraphNodesNotFound) {
return fmt.Errorf("unable to prune graph nodes: %w", err)
}
return nil
}
// networkHandler is the primary goroutine for the Builder. The roles of
// this goroutine include answering queries related to the state of the
// network, pruning the graph on new block notification, applying network
// updates, and registering new topology clients.
//
// NOTE: This MUST be run as a goroutine.
func (b *Builder) networkHandler() {
defer b.wg.Done()
graphPruneTicker := time.NewTicker(b.cfg.GraphPruneInterval)
defer graphPruneTicker.Stop()
defer b.statTicker.Stop()
b.stats.Reset()
for {
// If there are stats, resume the statTicker.
if !b.stats.Empty() {
b.statTicker.Resume()
}
select {
case chainUpdate, ok := <-b.staleBlocks:
// If the channel has been closed, then this indicates
// the daemon is shutting down, so we exit ourselves.
if !ok {
return
}
// Since this block is stale, we update our best height
// to the previous block.
blockHeight := chainUpdate.Height
b.bestHeight.Store(blockHeight - 1)
// Update the channel graph to reflect that this block
// was disconnected.
_, err := b.cfg.Graph.DisconnectBlockAtHeight(
blockHeight,
)
if err != nil {
log.Errorf("unable to prune graph with stale "+
"block: %v", err)
continue
}
// TODO(halseth): notify client about the reorg?
// A new block has arrived, so we can prune the channel graph
// of any channels which were closed in the block.
case chainUpdate, ok := <-b.newBlocks:
// If the channel has been closed, then this indicates
// the daemon is shutting down, so we exit ourselves.
if !ok {
return
}
// We'll ensure that any new blocks received attach
// directly to the end of our main chain. If not, then
// we've somehow missed some blocks. Here we'll catch
// up the chain with the latest blocks.
currentHeight := b.bestHeight.Load()
switch {
case chainUpdate.Height == currentHeight+1:
err := b.updateGraphWithClosedChannels(
chainUpdate,
)
if err != nil {
log.Errorf("unable to prune graph "+
"with closed channels: %v", err)
}
case chainUpdate.Height > currentHeight+1:
log.Errorf("out of order block: expecting "+
"height=%v, got height=%v",
currentHeight+1, chainUpdate.Height)
err := b.getMissingBlocks(
currentHeight, chainUpdate,
)
if err != nil {
log.Errorf("unable to retrieve missing"+
"blocks: %v", err)
}
case chainUpdate.Height < currentHeight+1:
log.Errorf("out of order block: expecting "+
"height=%v, got height=%v",
currentHeight+1, chainUpdate.Height)
log.Infof("Skipping channel pruning since "+
"received block height %v was already"+
" processed.", chainUpdate.Height)
}
// The graph prune ticker has ticked, so we'll examine the
// state of the known graph to filter out any zombie channels
// for pruning.
case <-graphPruneTicker.C:
if err := b.pruneZombieChans(); err != nil {
log.Errorf("Unable to prune zombies: %v", err)
}
// Log any stats if we've processed a non-empty number of
// channels, updates, or nodes. We'll only pause the ticker if
// the last window contained no updates to avoid resuming and
// pausing while consecutive windows contain new info.
case <-b.statTicker.Ticks():
if !b.stats.Empty() {
log.Infof(b.stats.String())
} else {
b.statTicker.Pause()
}
b.stats.Reset()
// The router has been signalled to exit, to we exit our main
// loop so the wait group can be decremented.
case <-b.quit:
return
}
}
}
// getMissingBlocks walks through all missing blocks and updates the graph
// closed channels accordingly.
func (b *Builder) getMissingBlocks(currentHeight uint32,
chainUpdate *chainview.FilteredBlock) error {
outdatedHash, err := b.cfg.Chain.GetBlockHash(int64(currentHeight))
if err != nil {
return err
}
outdatedBlock := &chainntnfs.BlockEpoch{
Height: int32(currentHeight),
Hash: outdatedHash,
}
epochClient, err := b.cfg.Notifier.RegisterBlockEpochNtfn(
outdatedBlock,
)
if err != nil {
return err
}
defer epochClient.Cancel()
blockDifference := int(chainUpdate.Height - currentHeight)
// We'll walk through all the outdated blocks and make sure we're able
// to update the graph with any closed channels from them.
for i := 0; i < blockDifference; i++ {
var (
missingBlock *chainntnfs.BlockEpoch
ok bool
)
select {
case missingBlock, ok = <-epochClient.Epochs:
if !ok {
return nil
}
case <-b.quit:
return nil
}
filteredBlock, err := b.cfg.ChainView.FilterBlock(
missingBlock.Hash,
)
if err != nil {
return err
}
err = b.updateGraphWithClosedChannels(
filteredBlock,
)
if err != nil {
return err
}
}
return nil
}
// updateGraphWithClosedChannels prunes the channel graph of closed channels
// that are no longer needed.
func (b *Builder) updateGraphWithClosedChannels(
chainUpdate *chainview.FilteredBlock) error {
// Once a new block arrives, we update our running track of the height
// of the chain tip.
blockHeight := chainUpdate.Height
b.bestHeight.Store(blockHeight)
log.Infof("Pruning channel graph using block %v (height=%v)",
chainUpdate.Hash, blockHeight)
// We're only interested in all prior outputs that have been spent in
// the block, so collate all the referenced previous outpoints within
// each tx and input.
var spentOutputs []*wire.OutPoint
for _, tx := range chainUpdate.Transactions {
for _, txIn := range tx.TxIn {
spentOutputs = append(spentOutputs,
&txIn.PreviousOutPoint)
}
}
// With the spent outputs gathered, attempt to prune the channel graph,
// also passing in the hash+height of the block being pruned so the
// prune tip can be updated.
chansClosed, err := b.cfg.Graph.PruneGraph(spentOutputs,
&chainUpdate.Hash, chainUpdate.Height)
if err != nil {
log.Errorf("unable to prune routing table: %v", err)
return err
}
log.Infof("Block %v (height=%v) closed %v channels", chainUpdate.Hash,
blockHeight, len(chansClosed))
return nil
}
// assertNodeAnnFreshness returns a non-nil error if we have an announcement in
// the database for the passed node with a timestamp newer than the passed
// timestamp. ErrIgnored will be returned if we already have the node, and
// ErrOutdated will be returned if we have a timestamp that's after the new
// timestamp.
func (b *Builder) assertNodeAnnFreshness(node route.Vertex,
msgTimestamp time.Time) error {
// If we are not already aware of this node, it means that we don't
// know about any channel using this node. To avoid a DoS attack by
// node announcements, we will ignore such nodes. If we do know about
// this node, check that this update brings info newer than what we
// already have.
lastUpdate, exists, err := b.cfg.Graph.HasLightningNode(node)
if err != nil {
return errors.Errorf("unable to query for the "+
"existence of node: %v", err)
}
if !exists {
return NewErrf(ErrIgnored, "Ignoring node announcement"+
" for node not found in channel graph (%x)",
node[:])
}
// If we've reached this point then we're aware of the vertex being
// advertised. So we now check if the new message has a new time stamp,
// if not then we won't accept the new data as it would override newer
// data.
if !lastUpdate.Before(msgTimestamp) {
return NewErrf(ErrOutdated, "Ignoring outdated "+
"announcement for %x", node[:])
}
return nil
}
// MarkZombieEdge adds a channel that failed complete validation into the zombie
// index so we can avoid having to re-validate it in the future.
func (b *Builder) MarkZombieEdge(chanID uint64) error {
// If the edge fails validation we'll mark the edge itself as a zombie
// so we don't continue to request it. We use the "zero key" for both
// node pubkeys so this edge can't be resurrected.
var zeroKey [33]byte
err := b.cfg.Graph.MarkEdgeZombie(chanID, zeroKey, zeroKey)
if err != nil {
return fmt.Errorf("unable to mark spent chan(id=%v) as a "+
"zombie: %w", chanID, err)
}
return nil
}
// ApplyChannelUpdate validates a channel update and if valid, applies it to the
// database. It returns a bool indicating whether the updates were successful.
func (b *Builder) ApplyChannelUpdate(msg *lnwire.ChannelUpdate1) bool {
ch, _, _, err := b.GetChannelByID(msg.ShortChannelID)
if err != nil {
log.Errorf("Unable to retrieve channel by id: %v", err)
return false
}
var pubKey *btcec.PublicKey
switch msg.ChannelFlags & lnwire.ChanUpdateDirection {
case 0:
pubKey, _ = ch.NodeKey1()
case 1:
pubKey, _ = ch.NodeKey2()
}
// Exit early if the pubkey cannot be decided.
if pubKey == nil {
log.Errorf("Unable to decide pubkey with ChannelFlags=%v",
msg.ChannelFlags)
return false
}
err = netann.ValidateChannelUpdateAnn(pubKey, ch.Capacity, msg)
if err != nil {
log.Errorf("Unable to validate channel update: %v", err)
return false
}
err = b.UpdateEdge(&models.ChannelEdgePolicy{
SigBytes: msg.Signature.ToSignatureBytes(),
ChannelID: msg.ShortChannelID.ToUint64(),
LastUpdate: time.Unix(int64(msg.Timestamp), 0),
MessageFlags: msg.MessageFlags,
ChannelFlags: msg.ChannelFlags,
TimeLockDelta: msg.TimeLockDelta,
MinHTLC: msg.HtlcMinimumMsat,
MaxHTLC: msg.HtlcMaximumMsat,
FeeBaseMSat: lnwire.MilliSatoshi(msg.BaseFee),
FeeProportionalMillionths: lnwire.MilliSatoshi(msg.FeeRate),
ExtraOpaqueData: msg.ExtraOpaqueData,
})
if err != nil && !IsError(err, ErrIgnored, ErrOutdated) {
log.Errorf("Unable to apply channel update: %v", err)
return false
}
return true
}
// AddNode is used to add information about a node to the router database. If
// the node with this pubkey is not present in an existing channel, it will
// be ignored.
//
// NOTE: This method is part of the ChannelGraphSource interface.
func (b *Builder) AddNode(node *models.LightningNode,
op ...batch.SchedulerOption) error {
err := b.addNode(node, op...)
if err != nil {
logNetworkMsgProcessError(err)
return err
}
return nil
}
// addNode does some basic checks on the given LightningNode against what we
// currently have persisted in the graph, and then adds it to the graph. If we
// already know about the node, then we only update our DB if the new update
// has a newer timestamp than the last one we received.
func (b *Builder) addNode(node *models.LightningNode,
op ...batch.SchedulerOption) error {
// Before we add the node to the database, we'll check to see if the
// announcement is "fresh" or not. If it isn't, then we'll return an
// error.
err := b.assertNodeAnnFreshness(node.PubKeyBytes, node.LastUpdate)
if err != nil {
return err
}
if err := b.cfg.Graph.AddLightningNode(node, op...); err != nil {
return errors.Errorf("unable to add node %x to the "+
"graph: %v", node.PubKeyBytes, err)
}
log.Tracef("Updated vertex data for node=%x", node.PubKeyBytes)
b.stats.incNumNodeUpdates()
return nil
}
// AddEdge is used to add edge/channel to the topology of the router, after all
// information about channel will be gathered this edge/channel might be used
// in construction of payment path.
//
// NOTE: This method is part of the ChannelGraphSource interface.
func (b *Builder) AddEdge(edge *models.ChannelEdgeInfo,
op ...batch.SchedulerOption) error {
err := b.addEdge(edge, op...)
if err != nil {
logNetworkMsgProcessError(err)
return err
}
return nil
}
// addEdge does some validation on the new channel edge against what we
// currently have persisted in the graph, and then adds it to the graph. The
// Chain View is updated with the new edge if it is successfully added to the
// graph. We only persist the channel if we currently dont have it at all in
// our graph.
//
// TODO(elle): this currently also does funding-transaction validation. But this
// should be moved to the gossiper instead.
func (b *Builder) addEdge(edge *models.ChannelEdgeInfo,
op ...batch.SchedulerOption) error {
log.Debugf("Received ChannelEdgeInfo for channel %v", edge.ChannelID)
// Prior to processing the announcement we first check if we
// already know of this channel, if so, then we can exit early.
_, _, exists, isZombie, err := b.cfg.Graph.HasChannelEdge(
edge.ChannelID,
)
if err != nil && !errors.Is(err, graphdb.ErrGraphNoEdgesFound) {
return errors.Errorf("unable to check for edge existence: %v",
err)
}
if isZombie {
return NewErrf(ErrIgnored, "ignoring msg for zombie chan_id=%v",
edge.ChannelID)
}
if exists {
return NewErrf(ErrIgnored, "ignoring msg for known chan_id=%v",
edge.ChannelID)
}
if err := b.cfg.Graph.AddChannelEdge(edge, op...); err != nil {
return fmt.Errorf("unable to add edge: %w", err)
}
b.stats.incNumEdgesDiscovered()
// If AssumeChannelValid is present, of if the SCID is an alias, then
// the gossiper would not have done the expensive work of fetching
// a funding transaction and validating it. So we won't have the channel
// capacity nor the funding script. So we just log and return here.
scid := lnwire.NewShortChanIDFromInt(edge.ChannelID)
if b.cfg.AssumeChannelValid || b.cfg.IsAlias(scid) {
log.Tracef("New channel discovered! Link connects %x and %x "+
"with ChannelID(%v)", edge.NodeKey1Bytes,
edge.NodeKey2Bytes, edge.ChannelID)
return nil
}
log.Debugf("New channel discovered! Link connects %x and %x with "+
"ChannelPoint(%v): chan_id=%v, capacity=%v", edge.NodeKey1Bytes,
edge.NodeKey2Bytes, edge.ChannelPoint, edge.ChannelID,
edge.Capacity)
// Otherwise, then we expect the funding script to be present on the
// edge since it would have been fetched when the gossiper validated the
// announcement.
fundingPkScript, err := edge.FundingScript.UnwrapOrErr(fmt.Errorf(
"expected the funding transaction script to be set",
))
if err != nil {
return err
}
// As a new edge has been added to the channel graph, we'll update the
// current UTXO filter within our active FilteredChainView so we are
// notified if/when this channel is closed.
filterUpdate := []graphdb.EdgePoint{
{
FundingPkScript: fundingPkScript,
OutPoint: edge.ChannelPoint,
},
}
err = b.cfg.ChainView.UpdateFilter(filterUpdate, b.bestHeight.Load())
if err != nil {
return errors.Errorf("unable to update chain "+
"view: %v", err)
}
return nil
}
// UpdateEdge is used to update edge information, without this message edge
// considered as not fully constructed.
//
// NOTE: This method is part of the ChannelGraphSource interface.
func (b *Builder) UpdateEdge(update *models.ChannelEdgePolicy,
op ...batch.SchedulerOption) error {
err := b.updateEdge(update, op...)
if err != nil {
logNetworkMsgProcessError(err)
return err
}
return nil
}
// updateEdge validates the new edge policy against what we currently have
// persisted in the graph, and then applies it to the graph if the update is
// considered fresh enough and if we actually have a channel persisted for the
// given update.
func (b *Builder) updateEdge(policy *models.ChannelEdgePolicy,
op ...batch.SchedulerOption) error {
log.Debugf("Received ChannelEdgePolicy for channel %v",
policy.ChannelID)
// We make sure to hold the mutex for this channel ID, such that no
// other goroutine is concurrently doing database accesses for the same
// channel ID.
b.channelEdgeMtx.Lock(policy.ChannelID)
defer b.channelEdgeMtx.Unlock(policy.ChannelID)
edge1Timestamp, edge2Timestamp, exists, isZombie, err :=
b.cfg.Graph.HasChannelEdge(policy.ChannelID)
if err != nil && !errors.Is(err, graphdb.ErrGraphNoEdgesFound) {
return errors.Errorf("unable to check for edge existence: %v",
err)
}
// If the channel is marked as a zombie in our database, and
// we consider this a stale update, then we should not apply the
// policy.
isStaleUpdate := time.Since(policy.LastUpdate) >
b.cfg.ChannelPruneExpiry
if isZombie && isStaleUpdate {
return NewErrf(ErrIgnored, "ignoring stale update "+
"(flags=%v|%v) for zombie chan_id=%v",
policy.MessageFlags, policy.ChannelFlags,
policy.ChannelID)
}
// If the channel doesn't exist in our database, we cannot apply the
// updated policy.
if !exists {
return NewErrf(ErrIgnored, "ignoring update (flags=%v|%v) for "+
"unknown chan_id=%v", policy.MessageFlags,
policy.ChannelFlags, policy.ChannelID)
}
log.Debugf("Found edge1Timestamp=%v, edge2Timestamp=%v",
edge1Timestamp, edge2Timestamp)
// As edges are directional edge node has a unique policy for the
// direction of the edge they control. Therefore, we first check if we
// already have the most up-to-date information for that edge. If this
// message has a timestamp not strictly newer than what we already know
// of we can exit early.
switch policy.ChannelFlags & lnwire.ChanUpdateDirection {
// A flag set of 0 indicates this is an announcement for the "first"
// node in the channel.
case 0:
// Ignore outdated message.
if !edge1Timestamp.Before(policy.LastUpdate) {
return NewErrf(ErrOutdated, "Ignoring "+
"outdated update (flags=%v|%v) for "+
"known chan_id=%v", policy.MessageFlags,
policy.ChannelFlags, policy.ChannelID)
}
// Similarly, a flag set of 1 indicates this is an announcement
// for the "second" node in the channel.
case 1:
// Ignore outdated message.
if !edge2Timestamp.Before(policy.LastUpdate) {
return NewErrf(ErrOutdated, "Ignoring "+
"outdated update (flags=%v|%v) for "+
"known chan_id=%v", policy.MessageFlags,
policy.ChannelFlags, policy.ChannelID)
}
}
// Now that we know this isn't a stale update, we'll apply the new edge
// policy to the proper directional edge within the channel graph.
if err = b.cfg.Graph.UpdateEdgePolicy(policy, op...); err != nil {
err := errors.Errorf("unable to add channel: %v", err)
log.Error(err)
return err
}
log.Tracef("New channel update applied: %v",
lnutils.SpewLogClosure(policy))
b.stats.incNumChannelUpdates()
return nil
}
// logNetworkMsgProcessError logs the error received from processing a network
// message. It logs as a debug message if the error is not critical.
func logNetworkMsgProcessError(err error) {
if IsError(err, ErrIgnored, ErrOutdated) {
log.Debugf("process network updates got: %v", err)
return
}
log.Errorf("process network updates got: %v", err)
}
// CurrentBlockHeight returns the block height from POV of the router subsystem.
//
// NOTE: This method is part of the ChannelGraphSource interface.
func (b *Builder) CurrentBlockHeight() (uint32, error) {
_, height, err := b.cfg.Chain.GetBestBlock()
return uint32(height), err
}
// SyncedHeight returns the block height to which the router subsystem currently
// is synced to. This can differ from the above chain height if the goroutine
// responsible for processing the blocks isn't yet up to speed.
func (b *Builder) SyncedHeight() uint32 {
return b.bestHeight.Load()
}
// GetChannelByID return the channel by the channel id.
//
// NOTE: This method is part of the ChannelGraphSource interface.
func (b *Builder) GetChannelByID(chanID lnwire.ShortChannelID) (
*models.ChannelEdgeInfo,
*models.ChannelEdgePolicy,
*models.ChannelEdgePolicy, error) {
return b.cfg.Graph.FetchChannelEdgesByID(chanID.ToUint64())
}
// FetchLightningNode attempts to look up a target node by its identity public
// key. graphdb.ErrGraphNodeNotFound is returned if the node doesn't exist
// within the graph.
//
// NOTE: This method is part of the ChannelGraphSource interface.
func (b *Builder) FetchLightningNode(
node route.Vertex) (*models.LightningNode, error) {
return b.cfg.Graph.FetchLightningNode(node)
}
// ForAllOutgoingChannels is used to iterate over all outgoing channels owned by
// the router.
//
// NOTE: This method is part of the ChannelGraphSource interface.
func (b *Builder) ForAllOutgoingChannels(cb func(*models.ChannelEdgeInfo,
*models.ChannelEdgePolicy) error) error {
return b.cfg.Graph.ForEachNodeChannel(b.cfg.SelfNode,
func(_ kvdb.RTx, c *models.ChannelEdgeInfo,
e *models.ChannelEdgePolicy,
_ *models.ChannelEdgePolicy) error {
if e == nil {
return fmt.Errorf("channel from self node " +
"has no policy")
}
return cb(c, e)
},
)
}
// AddProof updates the channel edge info with proof which is needed to
// properly announce the edge to the rest of the network.
//
// NOTE: This method is part of the ChannelGraphSource interface.
func (b *Builder) AddProof(chanID lnwire.ShortChannelID,
proof *models.ChannelAuthProof) error {
return b.cfg.Graph.AddEdgeProof(chanID, proof)
}
// IsStaleNode returns true if the graph source has a node announcement for the
// target node with a more recent timestamp.
//
// NOTE: This method is part of the ChannelGraphSource interface.
func (b *Builder) IsStaleNode(node route.Vertex,
timestamp time.Time) bool {
// If our attempt to assert that the node announcement is fresh fails,
// then we know that this is actually a stale announcement.
err := b.assertNodeAnnFreshness(node, timestamp)
if err != nil {
log.Debugf("Checking stale node %x got %v", node, err)
return true
}
return false
}
// IsPublicNode determines whether the given vertex is seen as a public node in
// the graph from the graph's source node's point of view.
//
// NOTE: This method is part of the ChannelGraphSource interface.
func (b *Builder) IsPublicNode(node route.Vertex) (bool, error) {
return b.cfg.Graph.IsPublicNode(node)
}
// IsKnownEdge returns true if the graph source already knows of the passed
// channel ID either as a live or zombie edge.
//
// NOTE: This method is part of the ChannelGraphSource interface.
func (b *Builder) IsKnownEdge(chanID lnwire.ShortChannelID) bool {
_, _, exists, isZombie, _ := b.cfg.Graph.HasChannelEdge(
chanID.ToUint64(),
)
return exists || isZombie
}
// IsZombieEdge returns true if the graph source has marked the given channel ID
// as a zombie edge.
//
// NOTE: This method is part of the ChannelGraphSource interface.
func (b *Builder) IsZombieEdge(chanID lnwire.ShortChannelID) (bool, error) {
_, _, _, isZombie, err := b.cfg.Graph.HasChannelEdge(chanID.ToUint64())
return isZombie, err
}
// IsStaleEdgePolicy returns true if the graph source has a channel edge for
// the passed channel ID (and flags) that have a more recent timestamp.
//
// NOTE: This method is part of the ChannelGraphSource interface.
func (b *Builder) IsStaleEdgePolicy(chanID lnwire.ShortChannelID,
timestamp time.Time, flags lnwire.ChanUpdateChanFlags) bool {
edge1Timestamp, edge2Timestamp, exists, isZombie, err :=
b.cfg.Graph.HasChannelEdge(chanID.ToUint64())
if err != nil {
log.Debugf("Check stale edge policy got error: %v", err)
return false
}
// If we know of the edge as a zombie, then we'll make some additional
// checks to determine if the new policy is fresh.
if isZombie {
// When running with AssumeChannelValid, we also prune channels
// if both of their edges are disabled. We'll mark the new
// policy as stale if it remains disabled.
if b.cfg.AssumeChannelValid {
isDisabled := flags&lnwire.ChanUpdateDisabled ==
lnwire.ChanUpdateDisabled
if isDisabled {
return true
}
}
// Otherwise, we'll fall back to our usual ChannelPruneExpiry.
return time.Since(timestamp) > b.cfg.ChannelPruneExpiry
}
// If we don't know of the edge, then it means it's fresh (thus not
// stale).
if !exists {
return false
}
// As edges are directional edge node has a unique policy for the
// direction of the edge they control. Therefore, we first check if we
// already have the most up-to-date information for that edge. If so,
// then we can exit early.
switch {
// A flag set of 0 indicates this is an announcement for the "first"
// node in the channel.
case flags&lnwire.ChanUpdateDirection == 0:
return !edge1Timestamp.Before(timestamp)
// Similarly, a flag set of 1 indicates this is an announcement for the
// "second" node in the channel.
case flags&lnwire.ChanUpdateDirection == 1:
return !edge2Timestamp.Before(timestamp)
}
return false
}
// MarkEdgeLive clears an edge from our zombie index, deeming it as live.
//
// NOTE: This method is part of the ChannelGraphSource interface.
func (b *Builder) MarkEdgeLive(chanID lnwire.ShortChannelID) error {
return b.cfg.Graph.MarkEdgeLive(chanID.ToUint64())
}
package graphdb
import (
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tor"
)
// addressType specifies the network protocol and version that should be used
// when connecting to a node at a particular address.
type addressType uint8
const (
// tcp4Addr denotes an IPv4 TCP address.
tcp4Addr addressType = 0
// tcp6Addr denotes an IPv6 TCP address.
tcp6Addr addressType = 1
// v2OnionAddr denotes a version 2 Tor onion service address.
v2OnionAddr addressType = 2
// v3OnionAddr denotes a version 3 Tor (prop224) onion service address.
v3OnionAddr addressType = 3
// opaqueAddrs denotes an address (or a set of addresses) that LND was
// not able to parse since LND is not yet aware of the address type.
opaqueAddrs addressType = 4
)
// encodeTCPAddr serializes a TCP address into its compact raw bytes
// representation.
func encodeTCPAddr(w io.Writer, addr *net.TCPAddr) error {
var (
addrType byte
ip []byte
)
if addr.IP.To4() != nil {
addrType = byte(tcp4Addr)
ip = addr.IP.To4()
} else {
addrType = byte(tcp6Addr)
ip = addr.IP.To16()
}
if ip == nil {
return fmt.Errorf("unable to encode IP %v", addr.IP)
}
if _, err := w.Write([]byte{addrType}); err != nil {
return err
}
if _, err := w.Write(ip); err != nil {
return err
}
var port [2]byte
byteOrder.PutUint16(port[:], uint16(addr.Port))
if _, err := w.Write(port[:]); err != nil {
return err
}
return nil
}
// encodeOnionAddr serializes an onion address into its compact raw bytes
// representation.
func encodeOnionAddr(w io.Writer, addr *tor.OnionAddr) error {
var suffixIndex int
hostLen := len(addr.OnionService)
switch hostLen {
case tor.V2Len:
if _, err := w.Write([]byte{byte(v2OnionAddr)}); err != nil {
return err
}
suffixIndex = tor.V2Len - tor.OnionSuffixLen
case tor.V3Len:
if _, err := w.Write([]byte{byte(v3OnionAddr)}); err != nil {
return err
}
suffixIndex = tor.V3Len - tor.OnionSuffixLen
default:
return errors.New("unknown onion service length")
}
suffix := addr.OnionService[suffixIndex:]
if suffix != tor.OnionSuffix {
return fmt.Errorf("invalid suffix \"%v\"", suffix)
}
host, err := tor.Base32Encoding.DecodeString(
addr.OnionService[:suffixIndex],
)
if err != nil {
return err
}
// Sanity check the decoded length.
switch {
case hostLen == tor.V2Len && len(host) != tor.V2DecodedLen:
return fmt.Errorf("onion service %v decoded to invalid host %x",
addr.OnionService, host)
case hostLen == tor.V3Len && len(host) != tor.V3DecodedLen:
return fmt.Errorf("onion service %v decoded to invalid host %x",
addr.OnionService, host)
}
if _, err := w.Write(host); err != nil {
return err
}
var port [2]byte
byteOrder.PutUint16(port[:], uint16(addr.Port))
if _, err := w.Write(port[:]); err != nil {
return err
}
return nil
}
// encodeOpaqueAddrs serializes the lnwire.OpaqueAddrs type to a raw set of
// bytes that we will persist.
func encodeOpaqueAddrs(w io.Writer, addr *lnwire.OpaqueAddrs) error {
// Write the type byte.
if _, err := w.Write([]byte{byte(opaqueAddrs)}); err != nil {
return err
}
// Write the length of the payload.
var l [2]byte
binary.BigEndian.PutUint16(l[:], uint16(len(addr.Payload)))
if _, err := w.Write(l[:]); err != nil {
return err
}
// Write the payload.
_, err := w.Write(addr.Payload)
return err
}
// DeserializeAddr reads the serialized raw representation of an address and
// deserializes it into the actual address. This allows us to avoid address
// resolution within the channeldb package.
func DeserializeAddr(r io.Reader) (net.Addr, error) {
var addrType [1]byte
if _, err := r.Read(addrType[:]); err != nil {
return nil, err
}
var address net.Addr
switch addressType(addrType[0]) {
case tcp4Addr:
var ip [4]byte
if _, err := r.Read(ip[:]); err != nil {
return nil, err
}
var port [2]byte
if _, err := r.Read(port[:]); err != nil {
return nil, err
}
address = &net.TCPAddr{
IP: net.IP(ip[:]),
Port: int(binary.BigEndian.Uint16(port[:])),
}
case tcp6Addr:
var ip [16]byte
if _, err := r.Read(ip[:]); err != nil {
return nil, err
}
var port [2]byte
if _, err := r.Read(port[:]); err != nil {
return nil, err
}
address = &net.TCPAddr{
IP: net.IP(ip[:]),
Port: int(binary.BigEndian.Uint16(port[:])),
}
case v2OnionAddr:
var h [tor.V2DecodedLen]byte
if _, err := r.Read(h[:]); err != nil {
return nil, err
}
var p [2]byte
if _, err := r.Read(p[:]); err != nil {
return nil, err
}
onionService := tor.Base32Encoding.EncodeToString(h[:])
onionService += tor.OnionSuffix
port := int(binary.BigEndian.Uint16(p[:]))
address = &tor.OnionAddr{
OnionService: onionService,
Port: port,
}
case v3OnionAddr:
var h [tor.V3DecodedLen]byte
if _, err := r.Read(h[:]); err != nil {
return nil, err
}
var p [2]byte
if _, err := r.Read(p[:]); err != nil {
return nil, err
}
onionService := tor.Base32Encoding.EncodeToString(h[:])
onionService += tor.OnionSuffix
port := int(binary.BigEndian.Uint16(p[:]))
address = &tor.OnionAddr{
OnionService: onionService,
Port: port,
}
case opaqueAddrs:
// Read the length of the payload.
var l [2]byte
if _, err := r.Read(l[:]); err != nil {
return nil, err
}
// Read the payload.
payload := make([]byte, binary.BigEndian.Uint16(l[:]))
if _, err := r.Read(payload); err != nil {
return nil, err
}
address = &lnwire.OpaqueAddrs{
Payload: payload,
}
default:
return nil, ErrUnknownAddressType
}
return address, nil
}
// SerializeAddr serializes an address into its raw bytes representation so that
// it can be deserialized without requiring address resolution.
func SerializeAddr(w io.Writer, address net.Addr) error {
switch addr := address.(type) {
case *net.TCPAddr:
return encodeTCPAddr(w, addr)
case *tor.OnionAddr:
return encodeOnionAddr(w, addr)
case *lnwire.OpaqueAddrs:
return encodeOpaqueAddrs(w, addr)
default:
return ErrUnknownAddressType
}
}
package graphdb
// channelCache is an in-memory cache used to improve the performance of
// ChanUpdatesInHorizon. It caches the chan info and edge policies for a
// particular channel.
type channelCache struct {
n int
channels map[uint64]ChannelEdge
}
// newChannelCache creates a new channelCache with maximum capacity of n
// channels.
func newChannelCache(n int) *channelCache {
return &channelCache{
n: n,
channels: make(map[uint64]ChannelEdge),
}
}
// get returns the channel from the cache, if it exists.
func (c *channelCache) get(chanid uint64) (ChannelEdge, bool) {
channel, ok := c.channels[chanid]
return channel, ok
}
// insert adds the entry to the channel cache. If an entry for chanid already
// exists, it will be replaced with the new entry. If the entry doesn't exist,
// it will be inserted to the cache, performing a random eviction if the cache
// is at capacity.
func (c *channelCache) insert(chanid uint64, channel ChannelEdge) {
// If entry exists, replace it.
if _, ok := c.channels[chanid]; ok {
c.channels[chanid] = channel
return
}
// Otherwise, evict an entry at random and insert.
if len(c.channels) == c.n {
for id := range c.channels {
delete(c.channels, id)
break
}
}
c.channels[chanid] = channel
}
// remove deletes an edge for chanid from the cache, if it exists.
func (c *channelCache) remove(chanid uint64) {
delete(c.channels, chanid)
}
package graphdb
import (
"encoding/binary"
"io"
"github.com/btcsuite/btcd/wire"
)
var (
// byteOrder defines the preferred byte order, which is Big Endian.
byteOrder = binary.BigEndian
)
// WriteOutpoint writes an outpoint to the passed writer using the minimal
// amount of bytes possible.
func WriteOutpoint(w io.Writer, o *wire.OutPoint) error {
if _, err := w.Write(o.Hash[:]); err != nil {
return err
}
if err := binary.Write(w, byteOrder, o.Index); err != nil {
return err
}
return nil
}
// ReadOutpoint reads an outpoint from the passed reader that was previously
// written using the WriteOutpoint struct.
func ReadOutpoint(r io.Reader, o *wire.OutPoint) error {
if _, err := io.ReadFull(r, o.Hash[:]); err != nil {
return err
}
if err := binary.Read(r, byteOrder, &o.Index); err != nil {
return err
}
return nil
}
package graphdb
import (
"errors"
"fmt"
)
var (
// ErrEdgePolicyOptionalFieldNotFound is an error returned if a channel
// policy field is not found in the db even though its message flags
// indicate it should be.
ErrEdgePolicyOptionalFieldNotFound = fmt.Errorf("optional field not " +
"present")
// ErrGraphNotFound is returned when at least one of the components of
// graph doesn't exist.
ErrGraphNotFound = fmt.Errorf("graph bucket not initialized")
// ErrGraphNeverPruned is returned when graph was never pruned.
ErrGraphNeverPruned = fmt.Errorf("graph never pruned")
// ErrSourceNodeNotSet is returned if the source node of the graph
// hasn't been added The source node is the center node within a
// star-graph.
ErrSourceNodeNotSet = fmt.Errorf("source node does not exist")
// ErrGraphNodesNotFound is returned in case none of the nodes has
// been added in graph node bucket.
ErrGraphNodesNotFound = fmt.Errorf("no graph nodes exist")
// ErrGraphNoEdgesFound is returned in case of none of the channel/edges
// has been added in graph edge bucket.
ErrGraphNoEdgesFound = fmt.Errorf("no graph edges exist")
// ErrGraphNodeNotFound is returned when we're unable to find the target
// node.
ErrGraphNodeNotFound = fmt.Errorf("unable to find node")
// ErrZombieEdge is an error returned when we attempt to look up an edge
// but it is marked as a zombie within the zombie index.
ErrZombieEdge = errors.New("edge marked as zombie")
// ErrEdgeNotFound is returned when an edge for the target chanID
// can't be found.
ErrEdgeNotFound = fmt.Errorf("edge not found")
// ErrEdgeAlreadyExist is returned when edge with specific
// channel id can't be added because it already exist.
ErrEdgeAlreadyExist = fmt.Errorf("edge already exist")
// ErrNodeAliasNotFound is returned when alias for node can't be found.
ErrNodeAliasNotFound = fmt.Errorf("alias for node not found")
// ErrClosedScidsNotFound is returned when the closed scid bucket
// hasn't been created.
ErrClosedScidsNotFound = fmt.Errorf("closed scid bucket doesn't exist")
// ErrZombieEdgeNotFound is an error returned when we attempt to find an
// edge in the zombie index which is not there.
ErrZombieEdgeNotFound = errors.New("edge not found in zombie index")
// ErrUnknownAddressType is returned when a node's addressType is not
// an expected value.
ErrUnknownAddressType = fmt.Errorf("address type cannot be resolved")
)
// ErrTooManyExtraOpaqueBytes creates an error which should be returned if the
// caller attempts to write an announcement message which bares too many extra
// opaque bytes. We limit this value in order to ensure that we don't waste
// disk space due to nodes unnecessarily padding out their announcements with
// garbage data.
func ErrTooManyExtraOpaqueBytes(numBytes int) error {
return fmt.Errorf("max allowed number of opaque bytes is %v, received "+
"%v bytes", MaxAllowedExtraOpaqueBytes, numBytes)
}
package graphdb
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/batch"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
// ErrChanGraphShuttingDown indicates that the ChannelGraph has shutdown or is
// busy shutting down.
var ErrChanGraphShuttingDown = fmt.Errorf("ChannelGraph shutting down")
// Config is a struct that holds all the necessary dependencies for a
// ChannelGraph.
type Config struct {
// KVDB is the kvdb.Backend that will be used for initializing the
// KVStore CRUD layer.
KVDB kvdb.Backend
// KVStoreOpts is a list of functional options that will be used when
// initializing the KVStore.
KVStoreOpts []KVStoreOptionModifier
}
// ChannelGraph is a layer above the graph's CRUD layer.
//
// NOTE: currently, this is purely a pass-through layer directly to the backing
// KVStore. Upcoming commits will move the graph cache out of the KVStore and
// into this layer so that the KVStore is only responsible for CRUD operations.
type ChannelGraph struct {
started atomic.Bool
stopped atomic.Bool
// cacheMu guards any writes to the graphCache. It should be held
// across the DB write call and the graphCache update to make the
// two updates as atomic as possible.
cacheMu sync.Mutex
graphCache *GraphCache
*KVStore
*topologyManager
quit chan struct{}
wg sync.WaitGroup
}
// NewChannelGraph creates a new ChannelGraph instance with the given backend.
func NewChannelGraph(cfg *Config, options ...ChanGraphOption) (*ChannelGraph,
error) {
opts := defaultChanGraphOptions()
for _, o := range options {
o(opts)
}
store, err := NewKVStore(cfg.KVDB, cfg.KVStoreOpts...)
if err != nil {
return nil, err
}
g := &ChannelGraph{
KVStore: store,
topologyManager: newTopologyManager(),
quit: make(chan struct{}),
}
// The graph cache can be turned off (e.g. for mobile users) for a
// speed/memory usage tradeoff.
if opts.useGraphCache {
g.graphCache = NewGraphCache(opts.preAllocCacheNumNodes)
}
return g, nil
}
// Start kicks off any goroutines required for the ChannelGraph to function.
// If the graph cache is enabled, then it will be populated with the contents of
// the database.
func (c *ChannelGraph) Start() error {
if !c.started.CompareAndSwap(false, true) {
return nil
}
log.Debugf("ChannelGraph starting")
defer log.Debug("ChannelGraph started")
if c.graphCache != nil {
if err := c.populateCache(); err != nil {
return fmt.Errorf("could not populate the graph "+
"cache: %w", err)
}
}
c.wg.Add(1)
go c.handleTopologySubscriptions()
return nil
}
// Stop signals any active goroutines for a graceful closure.
func (c *ChannelGraph) Stop() error {
if !c.stopped.CompareAndSwap(false, true) {
return nil
}
log.Debugf("ChannelGraph shutting down...")
defer log.Debug("ChannelGraph shutdown complete")
close(c.quit)
c.wg.Wait()
return nil
}
// handleTopologySubscriptions ensures that topology client subscriptions,
// subscription cancellations and topology notifications are handled
// synchronously.
//
// NOTE: this MUST be run in a goroutine.
func (c *ChannelGraph) handleTopologySubscriptions() {
defer c.wg.Done()
for {
select {
// A new fully validated topology update has just arrived.
// We'll notify any registered clients.
case update := <-c.topologyUpdate:
// TODO(elle): change topology handling to be handled
// synchronously so that we can guarantee the order of
// notification delivery.
c.wg.Add(1)
go c.handleTopologyUpdate(update)
// TODO(roasbeef): remove all unconnected vertexes
// after N blocks pass with no corresponding
// announcements.
// A new notification client update has arrived. We're either
// gaining a new client, or cancelling notifications for an
// existing client.
case ntfnUpdate := <-c.ntfnClientUpdates:
clientID := ntfnUpdate.clientID
if ntfnUpdate.cancel {
client, ok := c.topologyClients.LoadAndDelete(
clientID,
)
if ok {
close(client.exit)
client.wg.Wait()
close(client.ntfnChan)
}
continue
}
c.topologyClients.Store(clientID, &topologyClient{
ntfnChan: ntfnUpdate.ntfnChan,
exit: make(chan struct{}),
})
case <-c.quit:
return
}
}
}
// populateCache loads the entire channel graph into the in-memory graph cache.
//
// NOTE: This should only be called if the graphCache has been constructed.
func (c *ChannelGraph) populateCache() error {
startTime := time.Now()
log.Info("Populating in-memory channel graph, this might take a " +
"while...")
err := c.KVStore.ForEachNodeCacheable(func(node route.Vertex,
features *lnwire.FeatureVector) error {
c.graphCache.AddNodeFeatures(node, features)
return nil
})
if err != nil {
return err
}
err = c.KVStore.ForEachChannel(func(info *models.ChannelEdgeInfo,
policy1, policy2 *models.ChannelEdgePolicy) error {
c.graphCache.AddChannel(info, policy1, policy2)
return nil
})
if err != nil {
return err
}
log.Infof("Finished populating in-memory channel graph (took %v, %s)",
time.Since(startTime), c.graphCache.Stats())
return nil
}
// ForEachNodeDirectedChannel iterates through all channels of a given node,
// executing the passed callback on the directed edge representing the channel
// and its incoming policy. If the callback returns an error, then the iteration
// is halted with the error propagated back up to the caller. If the graphCache
// is available, then it will be used to retrieve the node's channels instead
// of the database.
//
// Unknown policies are passed into the callback as nil values.
//
// NOTE: this is part of the graphdb.NodeTraverser interface.
func (c *ChannelGraph) ForEachNodeDirectedChannel(node route.Vertex,
cb func(channel *DirectedChannel) error) error {
if c.graphCache != nil {
return c.graphCache.ForEachChannel(node, cb)
}
return c.KVStore.ForEachNodeDirectedChannel(node, cb)
}
// FetchNodeFeatures returns the features of the given node. If no features are
// known for the node, an empty feature vector is returned.
// If the graphCache is available, then it will be used to retrieve the node's
// features instead of the database.
//
// NOTE: this is part of the graphdb.NodeTraverser interface.
func (c *ChannelGraph) FetchNodeFeatures(node route.Vertex) (
*lnwire.FeatureVector, error) {
if c.graphCache != nil {
return c.graphCache.GetFeatures(node), nil
}
return c.KVStore.FetchNodeFeatures(node)
}
// GraphSession will provide the call-back with access to a NodeTraverser
// instance which can be used to perform queries against the channel graph. If
// the graph cache is not enabled, then the call-back will be provided with
// access to the graph via a consistent read-only transaction.
func (c *ChannelGraph) GraphSession(cb func(graph NodeTraverser) error) error {
if c.graphCache != nil {
return cb(c)
}
return c.KVStore.GraphSession(cb)
}
// ForEachNodeCached iterates through all the stored vertices/nodes in the
// graph, executing the passed callback with each node encountered.
//
// NOTE: The callback contents MUST not be modified.
func (c *ChannelGraph) ForEachNodeCached(cb func(node route.Vertex,
chans map[uint64]*DirectedChannel) error) error {
if c.graphCache != nil {
return c.graphCache.ForEachNode(cb)
}
return c.KVStore.ForEachNodeCached(cb)
}
// AddLightningNode adds a vertex/node to the graph database. If the node is not
// in the database from before, this will add a new, unconnected one to the
// graph. If it is present from before, this will update that node's
// information. Note that this method is expected to only be called to update an
// already present node from a node announcement, or to insert a node found in a
// channel update.
func (c *ChannelGraph) AddLightningNode(node *models.LightningNode,
op ...batch.SchedulerOption) error {
c.cacheMu.Lock()
defer c.cacheMu.Unlock()
err := c.KVStore.AddLightningNode(node, op...)
if err != nil {
return err
}
if c.graphCache != nil {
c.graphCache.AddNodeFeatures(
node.PubKeyBytes, node.Features,
)
}
select {
case c.topologyUpdate <- node:
case <-c.quit:
return ErrChanGraphShuttingDown
}
return nil
}
// DeleteLightningNode starts a new database transaction to remove a vertex/node
// from the database according to the node's public key.
func (c *ChannelGraph) DeleteLightningNode(nodePub route.Vertex) error {
c.cacheMu.Lock()
defer c.cacheMu.Unlock()
err := c.KVStore.DeleteLightningNode(nodePub)
if err != nil {
return err
}
if c.graphCache != nil {
c.graphCache.RemoveNode(nodePub)
}
return nil
}
// AddChannelEdge adds a new (undirected, blank) edge to the graph database. An
// undirected edge from the two target nodes are created. The information stored
// denotes the static attributes of the channel, such as the channelID, the keys
// involved in creation of the channel, and the set of features that the channel
// supports. The chanPoint and chanID are used to uniquely identify the edge
// globally within the database.
func (c *ChannelGraph) AddChannelEdge(edge *models.ChannelEdgeInfo,
op ...batch.SchedulerOption) error {
c.cacheMu.Lock()
defer c.cacheMu.Unlock()
err := c.KVStore.AddChannelEdge(edge, op...)
if err != nil {
return err
}
if c.graphCache != nil {
c.graphCache.AddChannel(edge, nil, nil)
}
select {
case c.topologyUpdate <- edge:
case <-c.quit:
return ErrChanGraphShuttingDown
}
return nil
}
// MarkEdgeLive clears an edge from our zombie index, deeming it as live.
// If the cache is enabled, the edge will be added back to the graph cache if
// we still have a record of this channel in the DB.
func (c *ChannelGraph) MarkEdgeLive(chanID uint64) error {
c.cacheMu.Lock()
defer c.cacheMu.Unlock()
err := c.KVStore.MarkEdgeLive(chanID)
if err != nil {
return err
}
if c.graphCache != nil {
// We need to add the channel back into our graph cache,
// otherwise we won't use it for path finding.
infos, err := c.KVStore.FetchChanInfos([]uint64{chanID})
if err != nil {
return err
}
if len(infos) == 0 {
return nil
}
info := infos[0]
c.graphCache.AddChannel(info.Info, info.Policy1, info.Policy2)
}
return nil
}
// DeleteChannelEdges removes edges with the given channel IDs from the
// database and marks them as zombies. This ensures that we're unable to re-add
// it to our database once again. If an edge does not exist within the
// database, then ErrEdgeNotFound will be returned. If strictZombiePruning is
// true, then when we mark these edges as zombies, we'll set up the keys such
// that we require the node that failed to send the fresh update to be the one
// that resurrects the channel from its zombie state. The markZombie bool
// denotes whether to mark the channel as a zombie.
func (c *ChannelGraph) DeleteChannelEdges(strictZombiePruning, markZombie bool,
chanIDs ...uint64) error {
c.cacheMu.Lock()
defer c.cacheMu.Unlock()
infos, err := c.KVStore.DeleteChannelEdges(
strictZombiePruning, markZombie, chanIDs...,
)
if err != nil {
return err
}
if c.graphCache != nil {
for _, info := range infos {
c.graphCache.RemoveChannel(
info.NodeKey1Bytes, info.NodeKey2Bytes,
info.ChannelID,
)
}
}
return err
}
// DisconnectBlockAtHeight is used to indicate that the block specified
// by the passed height has been disconnected from the main chain. This
// will "rewind" the graph back to the height below, deleting channels
// that are no longer confirmed from the graph. The prune log will be
// set to the last prune height valid for the remaining chain.
// Channels that were removed from the graph resulting from the
// disconnected block are returned.
func (c *ChannelGraph) DisconnectBlockAtHeight(height uint32) (
[]*models.ChannelEdgeInfo, error) {
c.cacheMu.Lock()
defer c.cacheMu.Unlock()
edges, err := c.KVStore.DisconnectBlockAtHeight(height)
if err != nil {
return nil, err
}
if c.graphCache != nil {
for _, edge := range edges {
c.graphCache.RemoveChannel(
edge.NodeKey1Bytes, edge.NodeKey2Bytes,
edge.ChannelID,
)
}
}
return edges, nil
}
// PruneGraph prunes newly closed channels from the channel graph in response
// to a new block being solved on the network. Any transactions which spend the
// funding output of any known channels within he graph will be deleted.
// Additionally, the "prune tip", or the last block which has been used to
// prune the graph is stored so callers can ensure the graph is fully in sync
// with the current UTXO state. A slice of channels that have been closed by
// the target block are returned if the function succeeds without error.
func (c *ChannelGraph) PruneGraph(spentOutputs []*wire.OutPoint,
blockHash *chainhash.Hash, blockHeight uint32) (
[]*models.ChannelEdgeInfo, error) {
c.cacheMu.Lock()
defer c.cacheMu.Unlock()
edges, nodes, err := c.KVStore.PruneGraph(
spentOutputs, blockHash, blockHeight,
)
if err != nil {
return nil, err
}
if c.graphCache != nil {
for _, edge := range edges {
c.graphCache.RemoveChannel(
edge.NodeKey1Bytes, edge.NodeKey2Bytes,
edge.ChannelID,
)
}
for _, node := range nodes {
c.graphCache.RemoveNode(node)
}
log.Debugf("Pruned graph, cache now has %s",
c.graphCache.Stats())
}
if len(edges) != 0 {
// Notify all currently registered clients of the newly closed
// channels.
closeSummaries := createCloseSummaries(
blockHeight, edges...,
)
c.notifyTopologyChange(&TopologyChange{
ClosedChannels: closeSummaries,
})
}
return edges, nil
}
// PruneGraphNodes is a garbage collection method which attempts to prune out
// any nodes from the channel graph that are currently unconnected. This ensure
// that we only maintain a graph of reachable nodes. In the event that a pruned
// node gains more channels, it will be re-added back to the graph.
func (c *ChannelGraph) PruneGraphNodes() error {
c.cacheMu.Lock()
defer c.cacheMu.Unlock()
nodes, err := c.KVStore.PruneGraphNodes()
if err != nil {
return err
}
if c.graphCache != nil {
for _, node := range nodes {
c.graphCache.RemoveNode(node)
}
}
return nil
}
// FilterKnownChanIDs takes a set of channel IDs and return the subset of chan
// ID's that we don't know and are not known zombies of the passed set. In other
// words, we perform a set difference of our set of chan ID's and the ones
// passed in. This method can be used by callers to determine the set of
// channels another peer knows of that we don't.
func (c *ChannelGraph) FilterKnownChanIDs(chansInfo []ChannelUpdateInfo,
isZombieChan func(time.Time, time.Time) bool) ([]uint64, error) {
unknown, knownZombies, err := c.KVStore.FilterKnownChanIDs(chansInfo)
if err != nil {
return nil, err
}
for _, info := range knownZombies {
// TODO(ziggie): Make sure that for the strict pruning case we
// compare the pubkeys and whether the right timestamp is not
// older than the `ChannelPruneExpiry`.
//
// NOTE: The timestamp data has no verification attached to it
// in the `ReplyChannelRange` msg so we are trusting this data
// at this point. However it is not critical because we are just
// removing the channel from the db when the timestamps are more
// recent. During the querying of the gossip msg verification
// happens as usual. However we should start punishing peers
// when they don't provide us honest data ?
isStillZombie := isZombieChan(
info.Node1UpdateTimestamp, info.Node2UpdateTimestamp,
)
if isStillZombie {
continue
}
// If we have marked it as a zombie but the latest update
// timestamps could bring it back from the dead, then we mark it
// alive, and we let it be added to the set of IDs to query our
// peer for.
err := c.KVStore.MarkEdgeLive(
info.ShortChannelID.ToUint64(),
)
// Since there is a chance that the edge could have been marked
// as "live" between the FilterKnownChanIDs call and the
// MarkEdgeLive call, we ignore the error if the edge is already
// marked as live.
if err != nil && !errors.Is(err, ErrZombieEdgeNotFound) {
return nil, err
}
}
return unknown, nil
}
// MarkEdgeZombie attempts to mark a channel identified by its channel ID as a
// zombie. This method is used on an ad-hoc basis, when channels need to be
// marked as zombies outside the normal pruning cycle.
func (c *ChannelGraph) MarkEdgeZombie(chanID uint64,
pubKey1, pubKey2 [33]byte) error {
c.cacheMu.Lock()
defer c.cacheMu.Unlock()
err := c.KVStore.MarkEdgeZombie(chanID, pubKey1, pubKey2)
if err != nil {
return err
}
if c.graphCache != nil {
c.graphCache.RemoveChannel(pubKey1, pubKey2, chanID)
}
return nil
}
// UpdateEdgePolicy updates the edge routing policy for a single directed edge
// within the database for the referenced channel. The `flags` attribute within
// the ChannelEdgePolicy determines which of the directed edges are being
// updated. If the flag is 1, then the first node's information is being
// updated, otherwise it's the second node's information. The node ordering is
// determined by the lexicographical ordering of the identity public keys of the
// nodes on either side of the channel.
func (c *ChannelGraph) UpdateEdgePolicy(edge *models.ChannelEdgePolicy,
op ...batch.SchedulerOption) error {
c.cacheMu.Lock()
defer c.cacheMu.Unlock()
from, to, err := c.KVStore.UpdateEdgePolicy(edge, op...)
if err != nil {
return err
}
if c.graphCache != nil {
var isUpdate1 bool
if edge.ChannelFlags&lnwire.ChanUpdateDirection == 0 {
isUpdate1 = true
}
c.graphCache.UpdatePolicy(edge, from, to, isUpdate1)
}
select {
case c.topologyUpdate <- edge:
case <-c.quit:
return ErrChanGraphShuttingDown
}
return nil
}
package graphdb
import (
"fmt"
"sync"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
// DirectedChannel is a type that stores the channel information as seen from
// one side of the channel.
type DirectedChannel struct {
// ChannelID is the unique identifier of this channel.
ChannelID uint64
// IsNode1 indicates if this is the node with the smaller public key.
IsNode1 bool
// OtherNode is the public key of the node on the other end of this
// channel.
OtherNode route.Vertex
// Capacity is the announced capacity of this channel in satoshis.
Capacity btcutil.Amount
// OutPolicySet is a boolean that indicates whether the node has an
// outgoing policy set. For pathfinding only the existence of the policy
// is important to know, not the actual content.
OutPolicySet bool
// InPolicy is the incoming policy *from* the other node to this node.
// In path finding, we're walking backward from the destination to the
// source, so we're always interested in the edge that arrives to us
// from the other node.
InPolicy *models.CachedEdgePolicy
// Inbound fees of this node.
InboundFee lnwire.Fee
}
// DeepCopy creates a deep copy of the channel, including the incoming policy.
func (c *DirectedChannel) DeepCopy() *DirectedChannel {
channelCopy := *c
if channelCopy.InPolicy != nil {
inPolicyCopy := *channelCopy.InPolicy
channelCopy.InPolicy = &inPolicyCopy
// The fields for the ToNode can be overwritten by the path
// finding algorithm, which is why we need a deep copy in the
// first place. So we always start out with nil values, just to
// be sure they don't contain any old data.
channelCopy.InPolicy.ToNodePubKey = nil
channelCopy.InPolicy.ToNodeFeatures = nil
}
return &channelCopy
}
// GraphCache is a type that holds a minimal set of information of the public
// channel graph that can be used for pathfinding.
type GraphCache struct {
nodeChannels map[route.Vertex]map[uint64]*DirectedChannel
nodeFeatures map[route.Vertex]*lnwire.FeatureVector
mtx sync.RWMutex
}
// NewGraphCache creates a new graphCache.
func NewGraphCache(preAllocNumNodes int) *GraphCache {
return &GraphCache{
nodeChannels: make(
map[route.Vertex]map[uint64]*DirectedChannel,
// A channel connects two nodes, so we can look it up
// from both sides, meaning we get double the number of
// entries.
preAllocNumNodes*2,
),
nodeFeatures: make(
map[route.Vertex]*lnwire.FeatureVector,
preAllocNumNodes,
),
}
}
// Stats returns statistics about the current cache size.
func (c *GraphCache) Stats() string {
c.mtx.RLock()
defer c.mtx.RUnlock()
numChannels := 0
for node := range c.nodeChannels {
numChannels += len(c.nodeChannels[node])
}
return fmt.Sprintf("num_node_features=%d, num_nodes=%d, "+
"num_channels=%d", len(c.nodeFeatures), len(c.nodeChannels),
numChannels)
}
// AddNodeFeatures adds a graph node and its features to the cache.
func (c *GraphCache) AddNodeFeatures(node route.Vertex,
features *lnwire.FeatureVector) {
c.mtx.Lock()
defer c.mtx.Unlock()
c.nodeFeatures[node] = features
}
// AddChannel adds a non-directed channel, meaning that the order of policy 1
// and policy 2 does not matter, the directionality is extracted from the info
// and policy flags automatically. The policy will be set as the outgoing policy
// on one node and the incoming policy on the peer's side.
func (c *GraphCache) AddChannel(info *models.ChannelEdgeInfo,
policy1 *models.ChannelEdgePolicy, policy2 *models.ChannelEdgePolicy) {
if info == nil {
return
}
if policy1 != nil && policy1.IsDisabled() &&
policy2 != nil && policy2.IsDisabled() {
return
}
// Create the edge entry for both nodes.
c.mtx.Lock()
c.updateOrAddEdge(info.NodeKey1Bytes, &DirectedChannel{
ChannelID: info.ChannelID,
IsNode1: true,
OtherNode: info.NodeKey2Bytes,
Capacity: info.Capacity,
})
c.updateOrAddEdge(info.NodeKey2Bytes, &DirectedChannel{
ChannelID: info.ChannelID,
IsNode1: false,
OtherNode: info.NodeKey1Bytes,
Capacity: info.Capacity,
})
c.mtx.Unlock()
// The policy's node is always the to_node. So if policy 1 has to_node
// of node 2 then we have the policy 1 as seen from node 1.
if policy1 != nil {
fromNode, toNode := info.NodeKey1Bytes, info.NodeKey2Bytes
if policy1.ToNode != info.NodeKey2Bytes {
fromNode, toNode = toNode, fromNode
}
isEdge1 := policy1.ChannelFlags&lnwire.ChanUpdateDirection == 0
c.UpdatePolicy(policy1, fromNode, toNode, isEdge1)
}
if policy2 != nil {
fromNode, toNode := info.NodeKey2Bytes, info.NodeKey1Bytes
if policy2.ToNode != info.NodeKey1Bytes {
fromNode, toNode = toNode, fromNode
}
isEdge1 := policy2.ChannelFlags&lnwire.ChanUpdateDirection == 0
c.UpdatePolicy(policy2, fromNode, toNode, isEdge1)
}
}
// updateOrAddEdge makes sure the edge information for a node is either updated
// if it already exists or is added to that node's list of channels.
func (c *GraphCache) updateOrAddEdge(node route.Vertex, edge *DirectedChannel) {
if len(c.nodeChannels[node]) == 0 {
c.nodeChannels[node] = make(map[uint64]*DirectedChannel)
}
c.nodeChannels[node][edge.ChannelID] = edge
}
// UpdatePolicy updates a single policy on both the from and to node. The order
// of the from and to node is not strictly important. But we assume that a
// channel edge was added beforehand so that the directed channel struct already
// exists in the cache.
func (c *GraphCache) UpdatePolicy(policy *models.ChannelEdgePolicy, fromNode,
toNode route.Vertex, edge1 bool) {
// Extract inbound fee if possible and available. If there is a decoding
// error, ignore this policy.
var inboundFee lnwire.Fee
_, err := policy.ExtraOpaqueData.ExtractRecords(&inboundFee)
if err != nil {
log.Errorf("Failed to extract records from edge policy %v: %v",
policy.ChannelID, err)
return
}
c.mtx.Lock()
defer c.mtx.Unlock()
updatePolicy := func(nodeKey route.Vertex) {
if len(c.nodeChannels[nodeKey]) == 0 {
log.Warnf("Node=%v not found in graph cache", nodeKey)
return
}
channel, ok := c.nodeChannels[nodeKey][policy.ChannelID]
if !ok {
log.Warnf("Channel=%v not found in graph cache",
policy.ChannelID)
return
}
// Edge 1 is defined as the policy for the direction of node1 to
// node2.
switch {
// This is node 1, and it is edge 1, so this is the outgoing
// policy for node 1.
case channel.IsNode1 && edge1:
channel.OutPolicySet = true
channel.InboundFee = inboundFee
// This is node 2, and it is edge 2, so this is the outgoing
// policy for node 2.
case !channel.IsNode1 && !edge1:
channel.OutPolicySet = true
channel.InboundFee = inboundFee
// The other two cases left mean it's the inbound policy for the
// node.
default:
channel.InPolicy = models.NewCachedPolicy(policy)
}
}
updatePolicy(fromNode)
updatePolicy(toNode)
}
// RemoveNode completely removes a node and all its channels (including the
// peer's side).
func (c *GraphCache) RemoveNode(node route.Vertex) {
c.mtx.Lock()
defer c.mtx.Unlock()
delete(c.nodeFeatures, node)
// First remove all channels from the other nodes' lists.
for _, channel := range c.nodeChannels[node] {
c.removeChannelIfFound(channel.OtherNode, channel.ChannelID)
}
// Then remove our whole node completely.
delete(c.nodeChannels, node)
}
// RemoveChannel removes a single channel between two nodes.
func (c *GraphCache) RemoveChannel(node1, node2 route.Vertex, chanID uint64) {
c.mtx.Lock()
defer c.mtx.Unlock()
// Remove that one channel from both sides.
c.removeChannelIfFound(node1, chanID)
c.removeChannelIfFound(node2, chanID)
}
// removeChannelIfFound removes a single channel from one side.
func (c *GraphCache) removeChannelIfFound(node route.Vertex, chanID uint64) {
if len(c.nodeChannels[node]) == 0 {
return
}
delete(c.nodeChannels[node], chanID)
}
// UpdateChannel updates the channel edge information for a specific edge. We
// expect the edge to already exist and be known. If it does not yet exist, this
// call is a no-op.
func (c *GraphCache) UpdateChannel(info *models.ChannelEdgeInfo) {
c.mtx.Lock()
defer c.mtx.Unlock()
if len(c.nodeChannels[info.NodeKey1Bytes]) == 0 ||
len(c.nodeChannels[info.NodeKey2Bytes]) == 0 {
return
}
channel, ok := c.nodeChannels[info.NodeKey1Bytes][info.ChannelID]
if ok {
// We only expect to be called when the channel is already
// known.
channel.Capacity = info.Capacity
channel.OtherNode = info.NodeKey2Bytes
}
channel, ok = c.nodeChannels[info.NodeKey2Bytes][info.ChannelID]
if ok {
channel.Capacity = info.Capacity
channel.OtherNode = info.NodeKey1Bytes
}
}
// getChannels returns a copy of the passed node's channels or nil if there
// isn't any.
func (c *GraphCache) getChannels(node route.Vertex) []*DirectedChannel {
c.mtx.RLock()
defer c.mtx.RUnlock()
channels, ok := c.nodeChannels[node]
if !ok {
return nil
}
features, ok := c.nodeFeatures[node]
if !ok {
// If the features were set to nil explicitly, that's fine here.
// The router will overwrite the features of the destination
// node with those found in the invoice if necessary. But if we
// didn't yet get a node announcement we want to mimic the
// behavior of the old DB based code that would always set an
// empty feature vector instead of leaving it nil.
features = lnwire.EmptyFeatureVector()
}
toNodeCallback := func() route.Vertex {
return node
}
i := 0
channelsCopy := make([]*DirectedChannel, len(channels))
for _, channel := range channels {
// We need to copy the channel and policy to avoid it being
// updated in the cache if the path finding algorithm sets
// fields on it (currently only the ToNodeFeatures of the
// policy).
channelCopy := channel.DeepCopy()
if channelCopy.InPolicy != nil {
channelCopy.InPolicy.ToNodePubKey = toNodeCallback
channelCopy.InPolicy.ToNodeFeatures = features
}
channelsCopy[i] = channelCopy
i++
}
return channelsCopy
}
// ForEachChannel invokes the given callback for each channel of the given node.
func (c *GraphCache) ForEachChannel(node route.Vertex,
cb func(channel *DirectedChannel) error) error {
// Obtain a copy of the node's channels. We need do this in order to
// avoid deadlocks caused by interaction with the graph cache, channel
// state and the graph database from multiple goroutines. This snapshot
// is only used for path finding where being stale is acceptable since
// the real world graph and our representation may always become
// slightly out of sync for a short time and the actual channel state
// is stored separately.
channels := c.getChannels(node)
for _, channel := range channels {
if err := cb(channel); err != nil {
return err
}
}
return nil
}
// ForEachNode iterates over the adjacency list of the graph, executing the
// call back for each node and the set of channels that emanate from the given
// node.
//
// NOTE: This method should be considered _read only_, the channels or nodes
// passed in MUST NOT be modified.
func (c *GraphCache) ForEachNode(cb func(node route.Vertex,
channels map[uint64]*DirectedChannel) error) error {
c.mtx.RLock()
defer c.mtx.RUnlock()
for node, channels := range c.nodeChannels {
// We don't make a copy here since this is a read-only RPC
// call. We also don't need the node features either for this
// call.
if err := cb(node, channels); err != nil {
return err
}
}
return nil
}
// GetFeatures returns the features of the node with the given ID. If no
// features are known for the node, an empty feature vector is returned.
func (c *GraphCache) GetFeatures(node route.Vertex) *lnwire.FeatureVector {
c.mtx.RLock()
defer c.mtx.RUnlock()
features, ok := c.nodeFeatures[node]
if !ok || features == nil {
// The router expects the features to never be nil, so we return
// an empty feature set instead.
return lnwire.EmptyFeatureVector()
}
return features
}
package graphdb
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"net"
"sort"
"sync"
"testing"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/lightningnetwork/lnd/aliasmgr"
"github.com/lightningnetwork/lnd/batch"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/stretchr/testify/require"
)
var (
// nodeBucket is a bucket which houses all the vertices or nodes within
// the channel graph. This bucket has a single-sub bucket which adds an
// additional index from pubkey -> alias. Within the top-level of this
// bucket, the key space maps a node's compressed public key to the
// serialized information for that node. Additionally, there's a
// special key "source" which stores the pubkey of the source node. The
// source node is used as the starting point for all graph/queries and
// traversals. The graph is formed as a star-graph with the source node
// at the center.
//
// maps: pubKey -> nodeInfo
// maps: source -> selfPubKey
nodeBucket = []byte("graph-node")
// nodeUpdateIndexBucket is a sub-bucket of the nodeBucket. This bucket
// will be used to quickly look up the "freshness" of a node's last
// update to the network. The bucket only contains keys, and no values,
// it's mapping:
//
// maps: updateTime || nodeID -> nil
nodeUpdateIndexBucket = []byte("graph-node-update-index")
// sourceKey is a special key that resides within the nodeBucket. The
// sourceKey maps a key to the public key of the "self node".
sourceKey = []byte("source")
// aliasIndexBucket is a sub-bucket that's nested within the main
// nodeBucket. This bucket maps the public key of a node to its
// current alias. This bucket is provided as it can be used within a
// future UI layer to add an additional degree of confirmation.
aliasIndexBucket = []byte("alias")
// edgeBucket is a bucket which houses all of the edge or channel
// information within the channel graph. This bucket essentially acts
// as an adjacency list, which in conjunction with a range scan, can be
// used to iterate over all the incoming and outgoing edges for a
// particular node. Key in the bucket use a prefix scheme which leads
// with the node's public key and sends with the compact edge ID.
// For each chanID, there will be two entries within the bucket, as the
// graph is directed: nodes may have different policies w.r.t to fees
// for their respective directions.
//
// maps: pubKey || chanID -> channel edge policy for node
edgeBucket = []byte("graph-edge")
// unknownPolicy is represented as an empty slice. It is
// used as the value in edgeBucket for unknown channel edge policies.
// Unknown policies are still stored in the database to enable efficient
// lookup of incoming channel edges.
unknownPolicy = []byte{}
// chanStart is an array of all zero bytes which is used to perform
// range scans within the edgeBucket to obtain all of the outgoing
// edges for a particular node.
chanStart [8]byte
// edgeIndexBucket is an index which can be used to iterate all edges
// in the bucket, grouping them according to their in/out nodes.
// Additionally, the items in this bucket also contain the complete
// edge information for a channel. The edge information includes the
// capacity of the channel, the nodes that made the channel, etc. This
// bucket resides within the edgeBucket above. Creation of an edge
// proceeds in two phases: first the edge is added to the edge index,
// afterwards the edgeBucket can be updated with the latest details of
// the edge as they are announced on the network.
//
// maps: chanID -> pubKey1 || pubKey2 || restofEdgeInfo
edgeIndexBucket = []byte("edge-index")
// edgeUpdateIndexBucket is a sub-bucket of the main edgeBucket. This
// bucket contains an index which allows us to gauge the "freshness" of
// a channel's last updates.
//
// maps: updateTime || chanID -> nil
edgeUpdateIndexBucket = []byte("edge-update-index")
// channelPointBucket maps a channel's full outpoint (txid:index) to
// its short 8-byte channel ID. This bucket resides within the
// edgeBucket above, and can be used to quickly remove an edge due to
// the outpoint being spent, or to query for existence of a channel.
//
// maps: outPoint -> chanID
channelPointBucket = []byte("chan-index")
// zombieBucket is a sub-bucket of the main edgeBucket bucket
// responsible for maintaining an index of zombie channels. Each entry
// exists within the bucket as follows:
//
// maps: chanID -> pubKey1 || pubKey2
//
// The chanID represents the channel ID of the edge that is marked as a
// zombie and is used as the key, which maps to the public keys of the
// edge's participants.
zombieBucket = []byte("zombie-index")
// disabledEdgePolicyBucket is a sub-bucket of the main edgeBucket
// bucket responsible for maintaining an index of disabled edge
// policies. Each entry exists within the bucket as follows:
//
// maps: <chanID><direction> -> []byte{}
//
// The chanID represents the channel ID of the edge and the direction is
// one byte representing the direction of the edge. The main purpose of
// this index is to allow pruning disabled channels in a fast way
// without the need to iterate all over the graph.
disabledEdgePolicyBucket = []byte("disabled-edge-policy-index")
// graphMetaBucket is a top-level bucket which stores various meta-deta
// related to the on-disk channel graph. Data stored in this bucket
// includes the block to which the graph has been synced to, the total
// number of channels, etc.
graphMetaBucket = []byte("graph-meta")
// pruneLogBucket is a bucket within the graphMetaBucket that stores
// a mapping from the block height to the hash for the blocks used to
// prune the graph.
// Once a new block is discovered, any channels that have been closed
// (by spending the outpoint) can safely be removed from the graph, and
// the block is added to the prune log. We need to keep such a log for
// the case where a reorg happens, and we must "rewind" the state of the
// graph by removing channels that were previously confirmed. In such a
// case we'll remove all entries from the prune log with a block height
// that no longer exists.
pruneLogBucket = []byte("prune-log")
// closedScidBucket is a top-level bucket that stores scids for
// channels that we know to be closed. This is used so that we don't
// need to perform expensive validation checks if we receive a channel
// announcement for the channel again.
//
// maps: scid -> []byte{}
closedScidBucket = []byte("closed-scid")
)
const (
// MaxAllowedExtraOpaqueBytes is the largest amount of opaque bytes that
// we'll permit to be written to disk. We limit this as otherwise, it
// would be possible for a node to create a ton of updates and slowly
// fill our disk, and also waste bandwidth due to relaying.
MaxAllowedExtraOpaqueBytes = 10000
)
// KVStore is a persistent, on-disk graph representation of the Lightning
// Network. This struct can be used to implement path finding algorithms on top
// of, and also to update a node's view based on information received from the
// p2p network. Internally, the graph is stored using a modified adjacency list
// representation with some added object interaction possible with each
// serialized edge/node. The graph is stored is directed, meaning that are two
// edges stored for each channel: an inbound/outbound edge for each node pair.
// Nodes, edges, and edge information can all be added to the graph
// independently. Edge removal results in the deletion of all edge information
// for that edge.
type KVStore struct {
db kvdb.Backend
// cacheMu guards all caches (rejectCache and chanCache). If
// this mutex will be acquired at the same time as the DB mutex then
// the cacheMu MUST be acquired first to prevent deadlock.
cacheMu sync.RWMutex
rejectCache *rejectCache
chanCache *channelCache
chanScheduler batch.Scheduler
nodeScheduler batch.Scheduler
}
// NewKVStore allocates a new KVStore backed by a DB instance. The
// returned instance has its own unique reject cache and channel cache.
func NewKVStore(db kvdb.Backend, options ...KVStoreOptionModifier) (*KVStore,
error) {
opts := DefaultOptions()
for _, o := range options {
o(opts)
}
if !opts.NoMigration {
if err := initKVStore(db); err != nil {
return nil, err
}
}
g := &KVStore{
db: db,
rejectCache: newRejectCache(opts.RejectCacheSize),
chanCache: newChannelCache(opts.ChannelCacheSize),
}
g.chanScheduler = batch.NewTimeScheduler(
db, &g.cacheMu, opts.BatchCommitInterval,
)
g.nodeScheduler = batch.NewTimeScheduler(
db, nil, opts.BatchCommitInterval,
)
return g, nil
}
// channelMapKey is the key structure used for storing channel edge policies.
type channelMapKey struct {
nodeKey route.Vertex
chanID [8]byte
}
// getChannelMap loads all channel edge policies from the database and stores
// them in a map.
func (c *KVStore) getChannelMap(edges kvdb.RBucket) (
map[channelMapKey]*models.ChannelEdgePolicy, error) {
// Create a map to store all channel edge policies.
channelMap := make(map[channelMapKey]*models.ChannelEdgePolicy)
err := kvdb.ForAll(edges, func(k, edgeBytes []byte) error {
// Skip embedded buckets.
if bytes.Equal(k, edgeIndexBucket) ||
bytes.Equal(k, edgeUpdateIndexBucket) ||
bytes.Equal(k, zombieBucket) ||
bytes.Equal(k, disabledEdgePolicyBucket) ||
bytes.Equal(k, channelPointBucket) {
return nil
}
// Validate key length.
if len(k) != 33+8 {
return fmt.Errorf("invalid edge key %x encountered", k)
}
var key channelMapKey
copy(key.nodeKey[:], k[:33])
copy(key.chanID[:], k[33:])
// No need to deserialize unknown policy.
if bytes.Equal(edgeBytes, unknownPolicy) {
return nil
}
edgeReader := bytes.NewReader(edgeBytes)
edge, err := deserializeChanEdgePolicyRaw(
edgeReader,
)
switch {
// If the db policy was missing an expected optional field, we
// return nil as if the policy was unknown.
case errors.Is(err, ErrEdgePolicyOptionalFieldNotFound):
return nil
case err != nil:
return err
}
channelMap[key] = edge
return nil
})
if err != nil {
return nil, err
}
return channelMap, nil
}
var graphTopLevelBuckets = [][]byte{
nodeBucket,
edgeBucket,
graphMetaBucket,
closedScidBucket,
}
// Wipe completely deletes all saved state within all used buckets within the
// database. The deletion is done in a single transaction, therefore this
// operation is fully atomic.
func (c *KVStore) Wipe() error {
err := kvdb.Update(c.db, func(tx kvdb.RwTx) error {
for _, tlb := range graphTopLevelBuckets {
err := tx.DeleteTopLevelBucket(tlb)
if err != nil &&
!errors.Is(err, kvdb.ErrBucketNotFound) {
return err
}
}
return nil
}, func() {})
if err != nil {
return err
}
return initKVStore(c.db)
}
// createChannelDB creates and initializes a fresh version of In
// the case that the target path has not yet been created or doesn't yet exist,
// then the path is created. Additionally, all required top-level buckets used
// within the database are created.
func initKVStore(db kvdb.Backend) error {
err := kvdb.Update(db, func(tx kvdb.RwTx) error {
for _, tlb := range graphTopLevelBuckets {
if _, err := tx.CreateTopLevelBucket(tlb); err != nil {
return err
}
}
nodes := tx.ReadWriteBucket(nodeBucket)
_, err := nodes.CreateBucketIfNotExists(aliasIndexBucket)
if err != nil {
return err
}
_, err = nodes.CreateBucketIfNotExists(nodeUpdateIndexBucket)
if err != nil {
return err
}
edges := tx.ReadWriteBucket(edgeBucket)
_, err = edges.CreateBucketIfNotExists(edgeIndexBucket)
if err != nil {
return err
}
_, err = edges.CreateBucketIfNotExists(edgeUpdateIndexBucket)
if err != nil {
return err
}
_, err = edges.CreateBucketIfNotExists(channelPointBucket)
if err != nil {
return err
}
_, err = edges.CreateBucketIfNotExists(zombieBucket)
if err != nil {
return err
}
graphMeta := tx.ReadWriteBucket(graphMetaBucket)
_, err = graphMeta.CreateBucketIfNotExists(pruneLogBucket)
return err
}, func() {})
if err != nil {
return fmt.Errorf("unable to create new channel graph: %w", err)
}
return nil
}
// AddrsForNode returns all known addresses for the target node public key that
// the graph DB is aware of. The returned boolean indicates if the given node is
// unknown to the graph DB or not.
//
// NOTE: this is part of the channeldb.AddrSource interface.
func (c *KVStore) AddrsForNode(nodePub *btcec.PublicKey) (bool, []net.Addr,
error) {
pubKey, err := route.NewVertexFromBytes(nodePub.SerializeCompressed())
if err != nil {
return false, nil, err
}
node, err := c.FetchLightningNode(pubKey)
// We don't consider it an error if the graph is unaware of the node.
switch {
case err != nil && !errors.Is(err, ErrGraphNodeNotFound):
return false, nil, err
case errors.Is(err, ErrGraphNodeNotFound):
return false, nil, nil
}
return true, node.Addresses, nil
}
// ForEachChannel iterates through all the channel edges stored within the
// graph and invokes the passed callback for each edge. The callback takes two
// edges as since this is a directed graph, both the in/out edges are visited.
// If the callback returns an error, then the transaction is aborted and the
// iteration stops early.
//
// NOTE: If an edge can't be found, or wasn't advertised, then a nil pointer
// for that particular channel edge routing policy will be passed into the
// callback.
func (c *KVStore) ForEachChannel(cb func(*models.ChannelEdgeInfo,
*models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error) error {
return c.db.View(func(tx kvdb.RTx) error {
edges := tx.ReadBucket(edgeBucket)
if edges == nil {
return ErrGraphNoEdgesFound
}
// First, load all edges in memory indexed by node and channel
// id.
channelMap, err := c.getChannelMap(edges)
if err != nil {
return err
}
edgeIndex := edges.NestedReadBucket(edgeIndexBucket)
if edgeIndex == nil {
return ErrGraphNoEdgesFound
}
// Load edge index, recombine each channel with the policies
// loaded above and invoke the callback.
return kvdb.ForAll(
edgeIndex, func(k, edgeInfoBytes []byte) error {
var chanID [8]byte
copy(chanID[:], k)
edgeInfoReader := bytes.NewReader(edgeInfoBytes)
info, err := deserializeChanEdgeInfo(
edgeInfoReader,
)
if err != nil {
return err
}
policy1 := channelMap[channelMapKey{
nodeKey: info.NodeKey1Bytes,
chanID: chanID,
}]
policy2 := channelMap[channelMapKey{
nodeKey: info.NodeKey2Bytes,
chanID: chanID,
}]
return cb(&info, policy1, policy2)
},
)
}, func() {})
}
// forEachNodeDirectedChannel iterates through all channels of a given node,
// executing the passed callback on the directed edge representing the channel
// and its incoming policy. If the callback returns an error, then the iteration
// is halted with the error propagated back up to the caller. An optional read
// transaction may be provided. If none is provided, a new one will be created.
//
// Unknown policies are passed into the callback as nil values.
func (c *KVStore) forEachNodeDirectedChannel(tx kvdb.RTx,
node route.Vertex, cb func(channel *DirectedChannel) error) error {
// Fallback that uses the database.
toNodeCallback := func() route.Vertex {
return node
}
toNodeFeatures, err := c.fetchNodeFeatures(tx, node)
if err != nil {
return err
}
dbCallback := func(tx kvdb.RTx, e *models.ChannelEdgeInfo, p1,
p2 *models.ChannelEdgePolicy) error {
var cachedInPolicy *models.CachedEdgePolicy
if p2 != nil {
cachedInPolicy = models.NewCachedPolicy(p2)
cachedInPolicy.ToNodePubKey = toNodeCallback
cachedInPolicy.ToNodeFeatures = toNodeFeatures
}
var inboundFee lnwire.Fee
if p1 != nil {
// Extract inbound fee. If there is a decoding error,
// skip this edge.
_, err := p1.ExtraOpaqueData.ExtractRecords(&inboundFee)
if err != nil {
return nil
}
}
directedChannel := &DirectedChannel{
ChannelID: e.ChannelID,
IsNode1: node == e.NodeKey1Bytes,
OtherNode: e.NodeKey2Bytes,
Capacity: e.Capacity,
OutPolicySet: p1 != nil,
InPolicy: cachedInPolicy,
InboundFee: inboundFee,
}
if node == e.NodeKey2Bytes {
directedChannel.OtherNode = e.NodeKey1Bytes
}
return cb(directedChannel)
}
return nodeTraversal(tx, node[:], c.db, dbCallback)
}
// fetchNodeFeatures returns the features of a given node. If no features are
// known for the node, an empty feature vector is returned. An optional read
// transaction may be provided. If none is provided, a new one will be created.
func (c *KVStore) fetchNodeFeatures(tx kvdb.RTx,
node route.Vertex) (*lnwire.FeatureVector, error) {
// Fallback that uses the database.
targetNode, err := c.FetchLightningNodeTx(tx, node)
switch {
// If the node exists and has features, return them directly.
case err == nil:
return targetNode.Features, nil
// If we couldn't find a node announcement, populate a blank feature
// vector.
case errors.Is(err, ErrGraphNodeNotFound):
return lnwire.EmptyFeatureVector(), nil
// Otherwise, bubble the error up.
default:
return nil, err
}
}
// ForEachNodeDirectedChannel iterates through all channels of a given node,
// executing the passed callback on the directed edge representing the channel
// and its incoming policy. If the callback returns an error, then the iteration
// is halted with the error propagated back up to the caller.
//
// Unknown policies are passed into the callback as nil values.
//
// NOTE: this is part of the graphdb.NodeTraverser interface.
func (c *KVStore) ForEachNodeDirectedChannel(nodePub route.Vertex,
cb func(channel *DirectedChannel) error) error {
return c.forEachNodeDirectedChannel(nil, nodePub, cb)
}
// FetchNodeFeatures returns the features of the given node. If no features are
// known for the node, an empty feature vector is returned.
//
// NOTE: this is part of the graphdb.NodeTraverser interface.
func (c *KVStore) FetchNodeFeatures(nodePub route.Vertex) (
*lnwire.FeatureVector, error) {
return c.fetchNodeFeatures(nil, nodePub)
}
// ForEachNodeCached is similar to forEachNode, but it returns DirectedChannel
// data to the call-back.
//
// NOTE: The callback contents MUST not be modified.
func (c *KVStore) ForEachNodeCached(cb func(node route.Vertex,
chans map[uint64]*DirectedChannel) error) error {
// Otherwise call back to a version that uses the database directly.
// We'll iterate over each node, then the set of channels for each
// node, and construct a similar callback functiopn signature as the
// main funcotin expects.
return c.forEachNode(func(tx kvdb.RTx,
node *models.LightningNode) error {
channels := make(map[uint64]*DirectedChannel)
err := c.ForEachNodeChannelTx(tx, node.PubKeyBytes,
func(tx kvdb.RTx, e *models.ChannelEdgeInfo,
p1 *models.ChannelEdgePolicy,
p2 *models.ChannelEdgePolicy) error {
toNodeCallback := func() route.Vertex {
return node.PubKeyBytes
}
toNodeFeatures, err := c.fetchNodeFeatures(
tx, node.PubKeyBytes,
)
if err != nil {
return err
}
var cachedInPolicy *models.CachedEdgePolicy
if p2 != nil {
cachedInPolicy =
models.NewCachedPolicy(p2)
cachedInPolicy.ToNodePubKey =
toNodeCallback
cachedInPolicy.ToNodeFeatures =
toNodeFeatures
}
directedChannel := &DirectedChannel{
ChannelID: e.ChannelID,
IsNode1: node.PubKeyBytes ==
e.NodeKey1Bytes,
OtherNode: e.NodeKey2Bytes,
Capacity: e.Capacity,
OutPolicySet: p1 != nil,
InPolicy: cachedInPolicy,
}
if node.PubKeyBytes == e.NodeKey2Bytes {
directedChannel.OtherNode =
e.NodeKey1Bytes
}
channels[e.ChannelID] = directedChannel
return nil
})
if err != nil {
return err
}
return cb(node.PubKeyBytes, channels)
})
}
// DisabledChannelIDs returns the channel ids of disabled channels.
// A channel is disabled when two of the associated ChanelEdgePolicies
// have their disabled bit on.
func (c *KVStore) DisabledChannelIDs() ([]uint64, error) {
var disabledChanIDs []uint64
var chanEdgeFound map[uint64]struct{}
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
edges := tx.ReadBucket(edgeBucket)
if edges == nil {
return ErrGraphNoEdgesFound
}
disabledEdgePolicyIndex := edges.NestedReadBucket(
disabledEdgePolicyBucket,
)
if disabledEdgePolicyIndex == nil {
return nil
}
// We iterate over all disabled policies and we add each channel
// that has more than one disabled policy to disabledChanIDs
// array.
return disabledEdgePolicyIndex.ForEach(
func(k, v []byte) error {
chanID := byteOrder.Uint64(k[:8])
_, edgeFound := chanEdgeFound[chanID]
if edgeFound {
delete(chanEdgeFound, chanID)
disabledChanIDs = append(
disabledChanIDs, chanID,
)
return nil
}
chanEdgeFound[chanID] = struct{}{}
return nil
},
)
}, func() {
disabledChanIDs = nil
chanEdgeFound = make(map[uint64]struct{})
})
if err != nil {
return nil, err
}
return disabledChanIDs, nil
}
// ForEachNode iterates through all the stored vertices/nodes in the graph,
// executing the passed callback with each node encountered. If the callback
// returns an error, then the transaction is aborted and the iteration stops
// early. Any operations performed on the NodeTx passed to the call-back are
// executed under the same read transaction and so, methods on the NodeTx object
// _MUST_ only be called from within the call-back.
func (c *KVStore) ForEachNode(cb func(tx NodeRTx) error) error {
return c.forEachNode(func(tx kvdb.RTx,
node *models.LightningNode) error {
return cb(newChanGraphNodeTx(tx, c, node))
})
}
// forEachNode iterates through all the stored vertices/nodes in the graph,
// executing the passed callback with each node encountered. If the callback
// returns an error, then the transaction is aborted and the iteration stops
// early.
//
// TODO(roasbeef): add iterator interface to allow for memory efficient graph
// traversal when graph gets mega.
func (c *KVStore) forEachNode(
cb func(kvdb.RTx, *models.LightningNode) error) error {
traversal := func(tx kvdb.RTx) error {
// First grab the nodes bucket which stores the mapping from
// pubKey to node information.
nodes := tx.ReadBucket(nodeBucket)
if nodes == nil {
return ErrGraphNotFound
}
return nodes.ForEach(func(pubKey, nodeBytes []byte) error {
// If this is the source key, then we skip this
// iteration as the value for this key is a pubKey
// rather than raw node information.
if bytes.Equal(pubKey, sourceKey) || len(pubKey) != 33 {
return nil
}
nodeReader := bytes.NewReader(nodeBytes)
node, err := deserializeLightningNode(nodeReader)
if err != nil {
return err
}
// Execute the callback, the transaction will abort if
// this returns an error.
return cb(tx, &node)
})
}
return kvdb.View(c.db, traversal, func() {})
}
// ForEachNodeCacheable iterates through all the stored vertices/nodes in the
// graph, executing the passed callback with each node encountered. If the
// callback returns an error, then the transaction is aborted and the iteration
// stops early.
func (c *KVStore) ForEachNodeCacheable(cb func(route.Vertex,
*lnwire.FeatureVector) error) error {
traversal := func(tx kvdb.RTx) error {
// First grab the nodes bucket which stores the mapping from
// pubKey to node information.
nodes := tx.ReadBucket(nodeBucket)
if nodes == nil {
return ErrGraphNotFound
}
return nodes.ForEach(func(pubKey, nodeBytes []byte) error {
// If this is the source key, then we skip this
// iteration as the value for this key is a pubKey
// rather than raw node information.
if bytes.Equal(pubKey, sourceKey) || len(pubKey) != 33 {
return nil
}
nodeReader := bytes.NewReader(nodeBytes)
node, features, err := deserializeLightningNodeCacheable( //nolint:ll
nodeReader,
)
if err != nil {
return err
}
// Execute the callback, the transaction will abort if
// this returns an error.
return cb(node, features)
})
}
return kvdb.View(c.db, traversal, func() {})
}
// SourceNode returns the source node of the graph. The source node is treated
// as the center node within a star-graph. This method may be used to kick off
// a path finding algorithm in order to explore the reachability of another
// node based off the source node.
func (c *KVStore) SourceNode() (*models.LightningNode, error) {
var source *models.LightningNode
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
// First grab the nodes bucket which stores the mapping from
// pubKey to node information.
nodes := tx.ReadBucket(nodeBucket)
if nodes == nil {
return ErrGraphNotFound
}
node, err := c.sourceNode(nodes)
if err != nil {
return err
}
source = node
return nil
}, func() {
source = nil
})
if err != nil {
return nil, err
}
return source, nil
}
// sourceNode uses an existing database transaction and returns the source node
// of the graph. The source node is treated as the center node within a
// star-graph. This method may be used to kick off a path finding algorithm in
// order to explore the reachability of another node based off the source node.
func (c *KVStore) sourceNode(nodes kvdb.RBucket) (*models.LightningNode,
error) {
selfPub := nodes.Get(sourceKey)
if selfPub == nil {
return nil, ErrSourceNodeNotSet
}
// With the pubKey of the source node retrieved, we're able to
// fetch the full node information.
node, err := fetchLightningNode(nodes, selfPub)
if err != nil {
return nil, err
}
return &node, nil
}
// SetSourceNode sets the source node within the graph database. The source
// node is to be used as the center of a star-graph within path finding
// algorithms.
func (c *KVStore) SetSourceNode(node *models.LightningNode) error {
nodePubBytes := node.PubKeyBytes[:]
return kvdb.Update(c.db, func(tx kvdb.RwTx) error {
// First grab the nodes bucket which stores the mapping from
// pubKey to node information.
nodes, err := tx.CreateTopLevelBucket(nodeBucket)
if err != nil {
return err
}
// Next we create the mapping from source to the targeted
// public key.
if err := nodes.Put(sourceKey, nodePubBytes); err != nil {
return err
}
// Finally, we commit the information of the lightning node
// itself.
return addLightningNode(tx, node)
}, func() {})
}
// AddLightningNode adds a vertex/node to the graph database. If the node is not
// in the database from before, this will add a new, unconnected one to the
// graph. If it is present from before, this will update that node's
// information. Note that this method is expected to only be called to update an
// already present node from a node announcement, or to insert a node found in a
// channel update.
//
// TODO(roasbeef): also need sig of announcement.
func (c *KVStore) AddLightningNode(node *models.LightningNode,
op ...batch.SchedulerOption) error {
r := &batch.Request{
Update: func(tx kvdb.RwTx) error {
return addLightningNode(tx, node)
},
}
for _, f := range op {
f(r)
}
return c.nodeScheduler.Execute(r)
}
func addLightningNode(tx kvdb.RwTx, node *models.LightningNode) error {
nodes, err := tx.CreateTopLevelBucket(nodeBucket)
if err != nil {
return err
}
aliases, err := nodes.CreateBucketIfNotExists(aliasIndexBucket)
if err != nil {
return err
}
updateIndex, err := nodes.CreateBucketIfNotExists(
nodeUpdateIndexBucket,
)
if err != nil {
return err
}
return putLightningNode(nodes, aliases, updateIndex, node)
}
// LookupAlias attempts to return the alias as advertised by the target node.
// TODO(roasbeef): currently assumes that aliases are unique...
func (c *KVStore) LookupAlias(pub *btcec.PublicKey) (string, error) {
var alias string
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
nodes := tx.ReadBucket(nodeBucket)
if nodes == nil {
return ErrGraphNodesNotFound
}
aliases := nodes.NestedReadBucket(aliasIndexBucket)
if aliases == nil {
return ErrGraphNodesNotFound
}
nodePub := pub.SerializeCompressed()
a := aliases.Get(nodePub)
if a == nil {
return ErrNodeAliasNotFound
}
// TODO(roasbeef): should actually be using the utf-8
// package...
alias = string(a)
return nil
}, func() {
alias = ""
})
if err != nil {
return "", err
}
return alias, nil
}
// DeleteLightningNode starts a new database transaction to remove a vertex/node
// from the database according to the node's public key.
func (c *KVStore) DeleteLightningNode(nodePub route.Vertex) error {
// TODO(roasbeef): ensure dangling edges are removed...
return kvdb.Update(c.db, func(tx kvdb.RwTx) error {
nodes := tx.ReadWriteBucket(nodeBucket)
if nodes == nil {
return ErrGraphNodeNotFound
}
return c.deleteLightningNode(nodes, nodePub[:])
}, func() {})
}
// deleteLightningNode uses an existing database transaction to remove a
// vertex/node from the database according to the node's public key.
func (c *KVStore) deleteLightningNode(nodes kvdb.RwBucket,
compressedPubKey []byte) error {
aliases := nodes.NestedReadWriteBucket(aliasIndexBucket)
if aliases == nil {
return ErrGraphNodesNotFound
}
if err := aliases.Delete(compressedPubKey); err != nil {
return err
}
// Before we delete the node, we'll fetch its current state so we can
// determine when its last update was to clear out the node update
// index.
node, err := fetchLightningNode(nodes, compressedPubKey)
if err != nil {
return err
}
if err := nodes.Delete(compressedPubKey); err != nil {
return err
}
// Finally, we'll delete the index entry for the node within the
// nodeUpdateIndexBucket as this node is no longer active, so we don't
// need to track its last update.
nodeUpdateIndex := nodes.NestedReadWriteBucket(nodeUpdateIndexBucket)
if nodeUpdateIndex == nil {
return ErrGraphNodesNotFound
}
// In order to delete the entry, we'll need to reconstruct the key for
// its last update.
updateUnix := uint64(node.LastUpdate.Unix())
var indexKey [8 + 33]byte
byteOrder.PutUint64(indexKey[:8], updateUnix)
copy(indexKey[8:], compressedPubKey)
return nodeUpdateIndex.Delete(indexKey[:])
}
// AddChannelEdge adds a new (undirected, blank) edge to the graph database. An
// undirected edge from the two target nodes are created. The information stored
// denotes the static attributes of the channel, such as the channelID, the keys
// involved in creation of the channel, and the set of features that the channel
// supports. The chanPoint and chanID are used to uniquely identify the edge
// globally within the database.
func (c *KVStore) AddChannelEdge(edge *models.ChannelEdgeInfo,
op ...batch.SchedulerOption) error {
var alreadyExists bool
r := &batch.Request{
Reset: func() {
alreadyExists = false
},
Update: func(tx kvdb.RwTx) error {
err := c.addChannelEdge(tx, edge)
// Silence ErrEdgeAlreadyExist so that the batch can
// succeed, but propagate the error via local state.
if errors.Is(err, ErrEdgeAlreadyExist) {
alreadyExists = true
return nil
}
return err
},
OnCommit: func(err error) error {
switch {
case err != nil:
return err
case alreadyExists:
return ErrEdgeAlreadyExist
default:
c.rejectCache.remove(edge.ChannelID)
c.chanCache.remove(edge.ChannelID)
return nil
}
},
}
for _, f := range op {
if f == nil {
return fmt.Errorf("nil scheduler option was used")
}
f(r)
}
return c.chanScheduler.Execute(r)
}
// addChannelEdge is the private form of AddChannelEdge that allows callers to
// utilize an existing db transaction.
func (c *KVStore) addChannelEdge(tx kvdb.RwTx,
edge *models.ChannelEdgeInfo) error {
// Construct the channel's primary key which is the 8-byte channel ID.
var chanKey [8]byte
binary.BigEndian.PutUint64(chanKey[:], edge.ChannelID)
nodes, err := tx.CreateTopLevelBucket(nodeBucket)
if err != nil {
return err
}
edges, err := tx.CreateTopLevelBucket(edgeBucket)
if err != nil {
return err
}
edgeIndex, err := edges.CreateBucketIfNotExists(edgeIndexBucket)
if err != nil {
return err
}
chanIndex, err := edges.CreateBucketIfNotExists(channelPointBucket)
if err != nil {
return err
}
// First, attempt to check if this edge has already been created. If
// so, then we can exit early as this method is meant to be idempotent.
if edgeInfo := edgeIndex.Get(chanKey[:]); edgeInfo != nil {
return ErrEdgeAlreadyExist
}
// Before we insert the channel into the database, we'll ensure that
// both nodes already exist in the channel graph. If either node
// doesn't, then we'll insert a "shell" node that just includes its
// public key, so subsequent validation and queries can work properly.
_, node1Err := fetchLightningNode(nodes, edge.NodeKey1Bytes[:])
switch {
case errors.Is(node1Err, ErrGraphNodeNotFound):
node1Shell := models.LightningNode{
PubKeyBytes: edge.NodeKey1Bytes,
HaveNodeAnnouncement: false,
}
err := addLightningNode(tx, &node1Shell)
if err != nil {
return fmt.Errorf("unable to create shell node "+
"for: %x: %w", edge.NodeKey1Bytes, err)
}
case node1Err != nil:
return node1Err
}
_, node2Err := fetchLightningNode(nodes, edge.NodeKey2Bytes[:])
switch {
case errors.Is(node2Err, ErrGraphNodeNotFound):
node2Shell := models.LightningNode{
PubKeyBytes: edge.NodeKey2Bytes,
HaveNodeAnnouncement: false,
}
err := addLightningNode(tx, &node2Shell)
if err != nil {
return fmt.Errorf("unable to create shell node "+
"for: %x: %w", edge.NodeKey2Bytes, err)
}
case node2Err != nil:
return node2Err
}
// If the edge hasn't been created yet, then we'll first add it to the
// edge index in order to associate the edge between two nodes and also
// store the static components of the channel.
if err := putChanEdgeInfo(edgeIndex, edge, chanKey); err != nil {
return err
}
// Mark edge policies for both sides as unknown. This is to enable
// efficient incoming channel lookup for a node.
keys := []*[33]byte{
&edge.NodeKey1Bytes,
&edge.NodeKey2Bytes,
}
for _, key := range keys {
err := putChanEdgePolicyUnknown(edges, edge.ChannelID, key[:])
if err != nil {
return err
}
}
// Finally we add it to the channel index which maps channel points
// (outpoints) to the shorter channel ID's.
var b bytes.Buffer
if err := WriteOutpoint(&b, &edge.ChannelPoint); err != nil {
return err
}
return chanIndex.Put(b.Bytes(), chanKey[:])
}
// HasChannelEdge returns true if the database knows of a channel edge with the
// passed channel ID, and false otherwise. If an edge with that ID is found
// within the graph, then two time stamps representing the last time the edge
// was updated for both directed edges are returned along with the boolean. If
// it is not found, then the zombie index is checked and its result is returned
// as the second boolean.
func (c *KVStore) HasChannelEdge(
chanID uint64) (time.Time, time.Time, bool, bool, error) {
var (
upd1Time time.Time
upd2Time time.Time
exists bool
isZombie bool
)
// We'll query the cache with the shared lock held to allow multiple
// readers to access values in the cache concurrently if they exist.
c.cacheMu.RLock()
if entry, ok := c.rejectCache.get(chanID); ok {
c.cacheMu.RUnlock()
upd1Time = time.Unix(entry.upd1Time, 0)
upd2Time = time.Unix(entry.upd2Time, 0)
exists, isZombie = entry.flags.unpack()
return upd1Time, upd2Time, exists, isZombie, nil
}
c.cacheMu.RUnlock()
c.cacheMu.Lock()
defer c.cacheMu.Unlock()
// The item was not found with the shared lock, so we'll acquire the
// exclusive lock and check the cache again in case another method added
// the entry to the cache while no lock was held.
if entry, ok := c.rejectCache.get(chanID); ok {
upd1Time = time.Unix(entry.upd1Time, 0)
upd2Time = time.Unix(entry.upd2Time, 0)
exists, isZombie = entry.flags.unpack()
return upd1Time, upd2Time, exists, isZombie, nil
}
if err := kvdb.View(c.db, func(tx kvdb.RTx) error {
edges := tx.ReadBucket(edgeBucket)
if edges == nil {
return ErrGraphNoEdgesFound
}
edgeIndex := edges.NestedReadBucket(edgeIndexBucket)
if edgeIndex == nil {
return ErrGraphNoEdgesFound
}
var channelID [8]byte
byteOrder.PutUint64(channelID[:], chanID)
// If the edge doesn't exist, then we'll also check our zombie
// index.
if edgeIndex.Get(channelID[:]) == nil {
exists = false
zombieIndex := edges.NestedReadBucket(zombieBucket)
if zombieIndex != nil {
isZombie, _, _ = isZombieEdge(
zombieIndex, chanID,
)
}
return nil
}
exists = true
isZombie = false
// If the channel has been found in the graph, then retrieve
// the edges itself so we can return the last updated
// timestamps.
nodes := tx.ReadBucket(nodeBucket)
if nodes == nil {
return ErrGraphNodeNotFound
}
e1, e2, err := fetchChanEdgePolicies(
edgeIndex, edges, channelID[:],
)
if err != nil {
return err
}
// As we may have only one of the edges populated, only set the
// update time if the edge was found in the database.
if e1 != nil {
upd1Time = e1.LastUpdate
}
if e2 != nil {
upd2Time = e2.LastUpdate
}
return nil
}, func() {}); err != nil {
return time.Time{}, time.Time{}, exists, isZombie, err
}
c.rejectCache.insert(chanID, rejectCacheEntry{
upd1Time: upd1Time.Unix(),
upd2Time: upd2Time.Unix(),
flags: packRejectFlags(exists, isZombie),
})
return upd1Time, upd2Time, exists, isZombie, nil
}
// AddEdgeProof sets the proof of an existing edge in the graph database.
func (c *KVStore) AddEdgeProof(chanID lnwire.ShortChannelID,
proof *models.ChannelAuthProof) error {
// Construct the channel's primary key which is the 8-byte channel ID.
var chanKey [8]byte
binary.BigEndian.PutUint64(chanKey[:], chanID.ToUint64())
return kvdb.Update(c.db, func(tx kvdb.RwTx) error {
edges := tx.ReadWriteBucket(edgeBucket)
if edges == nil {
return ErrEdgeNotFound
}
edgeIndex := edges.NestedReadWriteBucket(edgeIndexBucket)
if edgeIndex == nil {
return ErrEdgeNotFound
}
edge, err := fetchChanEdgeInfo(edgeIndex, chanKey[:])
if err != nil {
return err
}
edge.AuthProof = proof
return putChanEdgeInfo(edgeIndex, &edge, chanKey)
}, func() {})
}
const (
// pruneTipBytes is the total size of the value which stores a prune
// entry of the graph in the prune log. The "prune tip" is the last
// entry in the prune log, and indicates if the channel graph is in
// sync with the current UTXO state. The structure of the value
// is: blockHash, taking 32 bytes total.
pruneTipBytes = 32
)
// PruneGraph prunes newly closed channels from the channel graph in response
// to a new block being solved on the network. Any transactions which spend the
// funding output of any known channels within he graph will be deleted.
// Additionally, the "prune tip", or the last block which has been used to
// prune the graph is stored so callers can ensure the graph is fully in sync
// with the current UTXO state. A slice of channels that have been closed by
// the target block along with any pruned nodes are returned if the function
// succeeds without error.
func (c *KVStore) PruneGraph(spentOutputs []*wire.OutPoint,
blockHash *chainhash.Hash, blockHeight uint32) (
[]*models.ChannelEdgeInfo, []route.Vertex, error) {
c.cacheMu.Lock()
defer c.cacheMu.Unlock()
var (
chansClosed []*models.ChannelEdgeInfo
prunedNodes []route.Vertex
)
err := kvdb.Update(c.db, func(tx kvdb.RwTx) error {
// First grab the edges bucket which houses the information
// we'd like to delete
edges, err := tx.CreateTopLevelBucket(edgeBucket)
if err != nil {
return err
}
// Next grab the two edge indexes which will also need to be
// updated.
edgeIndex, err := edges.CreateBucketIfNotExists(edgeIndexBucket)
if err != nil {
return err
}
chanIndex, err := edges.CreateBucketIfNotExists(
channelPointBucket,
)
if err != nil {
return err
}
nodes := tx.ReadWriteBucket(nodeBucket)
if nodes == nil {
return ErrSourceNodeNotSet
}
zombieIndex, err := edges.CreateBucketIfNotExists(zombieBucket)
if err != nil {
return err
}
// For each of the outpoints that have been spent within the
// block, we attempt to delete them from the graph as if that
// outpoint was a channel, then it has now been closed.
for _, chanPoint := range spentOutputs {
// TODO(roasbeef): load channel bloom filter, continue
// if NOT if filter
var opBytes bytes.Buffer
err := WriteOutpoint(&opBytes, chanPoint)
if err != nil {
return err
}
// First attempt to see if the channel exists within
// the database, if not, then we can exit early.
chanID := chanIndex.Get(opBytes.Bytes())
if chanID == nil {
continue
}
// Attempt to delete the channel, an ErrEdgeNotFound
// will be returned if that outpoint isn't known to be
// a channel. If no error is returned, then a channel
// was successfully pruned.
edgeInfo, err := c.delChannelEdgeUnsafe(
edges, edgeIndex, chanIndex, zombieIndex,
chanID, false, false,
)
if err != nil && !errors.Is(err, ErrEdgeNotFound) {
return err
}
chansClosed = append(chansClosed, edgeInfo)
}
metaBucket, err := tx.CreateTopLevelBucket(graphMetaBucket)
if err != nil {
return err
}
pruneBucket, err := metaBucket.CreateBucketIfNotExists(
pruneLogBucket,
)
if err != nil {
return err
}
// With the graph pruned, add a new entry to the prune log,
// which can be used to check if the graph is fully synced with
// the current UTXO state.
var blockHeightBytes [4]byte
byteOrder.PutUint32(blockHeightBytes[:], blockHeight)
var newTip [pruneTipBytes]byte
copy(newTip[:], blockHash[:])
err = pruneBucket.Put(blockHeightBytes[:], newTip[:])
if err != nil {
return err
}
// Now that the graph has been pruned, we'll also attempt to
// prune any nodes that have had a channel closed within the
// latest block.
prunedNodes, err = c.pruneGraphNodes(nodes, edgeIndex)
return err
}, func() {
chansClosed = nil
prunedNodes = nil
})
if err != nil {
return nil, nil, err
}
for _, channel := range chansClosed {
c.rejectCache.remove(channel.ChannelID)
c.chanCache.remove(channel.ChannelID)
}
return chansClosed, prunedNodes, nil
}
// PruneGraphNodes is a garbage collection method which attempts to prune out
// any nodes from the channel graph that are currently unconnected. This ensure
// that we only maintain a graph of reachable nodes. In the event that a pruned
// node gains more channels, it will be re-added back to the graph.
func (c *KVStore) PruneGraphNodes() ([]route.Vertex, error) {
var prunedNodes []route.Vertex
err := kvdb.Update(c.db, func(tx kvdb.RwTx) error {
nodes := tx.ReadWriteBucket(nodeBucket)
if nodes == nil {
return ErrGraphNodesNotFound
}
edges := tx.ReadWriteBucket(edgeBucket)
if edges == nil {
return ErrGraphNotFound
}
edgeIndex := edges.NestedReadWriteBucket(edgeIndexBucket)
if edgeIndex == nil {
return ErrGraphNoEdgesFound
}
var err error
prunedNodes, err = c.pruneGraphNodes(nodes, edgeIndex)
if err != nil {
return err
}
return nil
}, func() {
prunedNodes = nil
})
return prunedNodes, err
}
// pruneGraphNodes attempts to remove any nodes from the graph who have had a
// channel closed within the current block. If the node still has existing
// channels in the graph, this will act as a no-op.
func (c *KVStore) pruneGraphNodes(nodes kvdb.RwBucket,
edgeIndex kvdb.RwBucket) ([]route.Vertex, error) {
log.Trace("Pruning nodes from graph with no open channels")
// We'll retrieve the graph's source node to ensure we don't remove it
// even if it no longer has any open channels.
sourceNode, err := c.sourceNode(nodes)
if err != nil {
return nil, err
}
// We'll use this map to keep count the number of references to a node
// in the graph. A node should only be removed once it has no more
// references in the graph.
nodeRefCounts := make(map[[33]byte]int)
err = nodes.ForEach(func(pubKey, nodeBytes []byte) error {
// If this is the source key, then we skip this
// iteration as the value for this key is a pubKey
// rather than raw node information.
if bytes.Equal(pubKey, sourceKey) || len(pubKey) != 33 {
return nil
}
var nodePub [33]byte
copy(nodePub[:], pubKey)
nodeRefCounts[nodePub] = 0
return nil
})
if err != nil {
return nil, err
}
// To ensure we never delete the source node, we'll start off by
// bumping its ref count to 1.
nodeRefCounts[sourceNode.PubKeyBytes] = 1
// Next, we'll run through the edgeIndex which maps a channel ID to the
// edge info. We'll use this scan to populate our reference count map
// above.
err = edgeIndex.ForEach(func(chanID, edgeInfoBytes []byte) error {
// The first 66 bytes of the edge info contain the pubkeys of
// the nodes that this edge attaches. We'll extract them, and
// add them to the ref count map.
var node1, node2 [33]byte
copy(node1[:], edgeInfoBytes[:33])
copy(node2[:], edgeInfoBytes[33:])
// With the nodes extracted, we'll increase the ref count of
// each of the nodes.
nodeRefCounts[node1]++
nodeRefCounts[node2]++
return nil
})
if err != nil {
return nil, err
}
// Finally, we'll make a second pass over the set of nodes, and delete
// any nodes that have a ref count of zero.
var pruned []route.Vertex
for nodePubKey, refCount := range nodeRefCounts {
// If the ref count of the node isn't zero, then we can safely
// skip it as it still has edges to or from it within the
// graph.
if refCount != 0 {
continue
}
// If we reach this point, then there are no longer any edges
// that connect this node, so we can delete it.
err := c.deleteLightningNode(nodes, nodePubKey[:])
if err != nil {
if errors.Is(err, ErrGraphNodeNotFound) ||
errors.Is(err, ErrGraphNodesNotFound) {
log.Warnf("Unable to prune node %x from the "+
"graph: %v", nodePubKey, err)
continue
}
return nil, err
}
log.Infof("Pruned unconnected node %x from channel graph",
nodePubKey[:])
pruned = append(pruned, nodePubKey)
}
if len(pruned) > 0 {
log.Infof("Pruned %v unconnected nodes from the channel graph",
len(pruned))
}
return pruned, err
}
// DisconnectBlockAtHeight is used to indicate that the block specified
// by the passed height has been disconnected from the main chain. This
// will "rewind" the graph back to the height below, deleting channels
// that are no longer confirmed from the graph. The prune log will be
// set to the last prune height valid for the remaining chain.
// Channels that were removed from the graph resulting from the
// disconnected block are returned.
func (c *KVStore) DisconnectBlockAtHeight(height uint32) (
[]*models.ChannelEdgeInfo, error) {
// Every channel having a ShortChannelID starting at 'height'
// will no longer be confirmed.
startShortChanID := lnwire.ShortChannelID{
BlockHeight: height,
}
// Delete everything after this height from the db up until the
// SCID alias range.
endShortChanID := aliasmgr.StartingAlias
// The block height will be the 3 first bytes of the channel IDs.
var chanIDStart [8]byte
byteOrder.PutUint64(chanIDStart[:], startShortChanID.ToUint64())
var chanIDEnd [8]byte
byteOrder.PutUint64(chanIDEnd[:], endShortChanID.ToUint64())
c.cacheMu.Lock()
defer c.cacheMu.Unlock()
// Keep track of the channels that are removed from the graph.
var removedChans []*models.ChannelEdgeInfo
if err := kvdb.Update(c.db, func(tx kvdb.RwTx) error {
edges, err := tx.CreateTopLevelBucket(edgeBucket)
if err != nil {
return err
}
edgeIndex, err := edges.CreateBucketIfNotExists(edgeIndexBucket)
if err != nil {
return err
}
chanIndex, err := edges.CreateBucketIfNotExists(
channelPointBucket,
)
if err != nil {
return err
}
zombieIndex, err := edges.CreateBucketIfNotExists(zombieBucket)
if err != nil {
return err
}
// Scan from chanIDStart to chanIDEnd, deleting every
// found edge.
// NOTE: we must delete the edges after the cursor loop, since
// modifying the bucket while traversing is not safe.
// NOTE: We use a < comparison in bytes.Compare instead of <=
// so that the StartingAlias itself isn't deleted.
var keys [][]byte
cursor := edgeIndex.ReadWriteCursor()
//nolint:ll
for k, _ := cursor.Seek(chanIDStart[:]); k != nil &&
bytes.Compare(k, chanIDEnd[:]) < 0; k, _ = cursor.Next() {
keys = append(keys, k)
}
for _, k := range keys {
edgeInfo, err := c.delChannelEdgeUnsafe(
edges, edgeIndex, chanIndex, zombieIndex,
k, false, false,
)
if err != nil && !errors.Is(err, ErrEdgeNotFound) {
return err
}
removedChans = append(removedChans, edgeInfo)
}
// Delete all the entries in the prune log having a height
// greater or equal to the block disconnected.
metaBucket, err := tx.CreateTopLevelBucket(graphMetaBucket)
if err != nil {
return err
}
pruneBucket, err := metaBucket.CreateBucketIfNotExists(
pruneLogBucket,
)
if err != nil {
return err
}
var pruneKeyStart [4]byte
byteOrder.PutUint32(pruneKeyStart[:], height)
var pruneKeyEnd [4]byte
byteOrder.PutUint32(pruneKeyEnd[:], math.MaxUint32)
// To avoid modifying the bucket while traversing, we delete
// the keys in a second loop.
var pruneKeys [][]byte
pruneCursor := pruneBucket.ReadWriteCursor()
//nolint:ll
for k, _ := pruneCursor.Seek(pruneKeyStart[:]); k != nil &&
bytes.Compare(k, pruneKeyEnd[:]) <= 0; k, _ = pruneCursor.Next() {
pruneKeys = append(pruneKeys, k)
}
for _, k := range pruneKeys {
if err := pruneBucket.Delete(k); err != nil {
return err
}
}
return nil
}, func() {
removedChans = nil
}); err != nil {
return nil, err
}
for _, channel := range removedChans {
c.rejectCache.remove(channel.ChannelID)
c.chanCache.remove(channel.ChannelID)
}
return removedChans, nil
}
// PruneTip returns the block height and hash of the latest block that has been
// used to prune channels in the graph. Knowing the "prune tip" allows callers
// to tell if the graph is currently in sync with the current best known UTXO
// state.
func (c *KVStore) PruneTip() (*chainhash.Hash, uint32, error) {
var (
tipHash chainhash.Hash
tipHeight uint32
)
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
graphMeta := tx.ReadBucket(graphMetaBucket)
if graphMeta == nil {
return ErrGraphNotFound
}
pruneBucket := graphMeta.NestedReadBucket(pruneLogBucket)
if pruneBucket == nil {
return ErrGraphNeverPruned
}
pruneCursor := pruneBucket.ReadCursor()
// The prune key with the largest block height will be our
// prune tip.
k, v := pruneCursor.Last()
if k == nil {
return ErrGraphNeverPruned
}
// Once we have the prune tip, the value will be the block hash,
// and the key the block height.
copy(tipHash[:], v)
tipHeight = byteOrder.Uint32(k)
return nil
}, func() {})
if err != nil {
return nil, 0, err
}
return &tipHash, tipHeight, nil
}
// DeleteChannelEdges removes edges with the given channel IDs from the
// database and marks them as zombies. This ensures that we're unable to re-add
// it to our database once again. If an edge does not exist within the
// database, then ErrEdgeNotFound will be returned. If strictZombiePruning is
// true, then when we mark these edges as zombies, we'll set up the keys such
// that we require the node that failed to send the fresh update to be the one
// that resurrects the channel from its zombie state. The markZombie bool
// denotes whether or not to mark the channel as a zombie.
func (c *KVStore) DeleteChannelEdges(strictZombiePruning, markZombie bool,
chanIDs ...uint64) ([]*models.ChannelEdgeInfo, error) {
// TODO(roasbeef): possibly delete from node bucket if node has no more
// channels
// TODO(roasbeef): don't delete both edges?
c.cacheMu.Lock()
defer c.cacheMu.Unlock()
var infos []*models.ChannelEdgeInfo
err := kvdb.Update(c.db, func(tx kvdb.RwTx) error {
edges := tx.ReadWriteBucket(edgeBucket)
if edges == nil {
return ErrEdgeNotFound
}
edgeIndex := edges.NestedReadWriteBucket(edgeIndexBucket)
if edgeIndex == nil {
return ErrEdgeNotFound
}
chanIndex := edges.NestedReadWriteBucket(channelPointBucket)
if chanIndex == nil {
return ErrEdgeNotFound
}
nodes := tx.ReadWriteBucket(nodeBucket)
if nodes == nil {
return ErrGraphNodeNotFound
}
zombieIndex, err := edges.CreateBucketIfNotExists(zombieBucket)
if err != nil {
return err
}
var rawChanID [8]byte
for _, chanID := range chanIDs {
byteOrder.PutUint64(rawChanID[:], chanID)
edgeInfo, err := c.delChannelEdgeUnsafe(
edges, edgeIndex, chanIndex, zombieIndex,
rawChanID[:], markZombie, strictZombiePruning,
)
if err != nil {
return err
}
infos = append(infos, edgeInfo)
}
return nil
}, func() {
infos = nil
})
if err != nil {
return nil, err
}
for _, chanID := range chanIDs {
c.rejectCache.remove(chanID)
c.chanCache.remove(chanID)
}
return infos, nil
}
// ChannelID attempt to lookup the 8-byte compact channel ID which maps to the
// passed channel point (outpoint). If the passed channel doesn't exist within
// the database, then ErrEdgeNotFound is returned.
func (c *KVStore) ChannelID(chanPoint *wire.OutPoint) (uint64, error) {
var chanID uint64
if err := kvdb.View(c.db, func(tx kvdb.RTx) error {
var err error
chanID, err = getChanID(tx, chanPoint)
return err
}, func() {
chanID = 0
}); err != nil {
return 0, err
}
return chanID, nil
}
// getChanID returns the assigned channel ID for a given channel point.
func getChanID(tx kvdb.RTx, chanPoint *wire.OutPoint) (uint64, error) {
var b bytes.Buffer
if err := WriteOutpoint(&b, chanPoint); err != nil {
return 0, err
}
edges := tx.ReadBucket(edgeBucket)
if edges == nil {
return 0, ErrGraphNoEdgesFound
}
chanIndex := edges.NestedReadBucket(channelPointBucket)
if chanIndex == nil {
return 0, ErrGraphNoEdgesFound
}
chanIDBytes := chanIndex.Get(b.Bytes())
if chanIDBytes == nil {
return 0, ErrEdgeNotFound
}
chanID := byteOrder.Uint64(chanIDBytes)
return chanID, nil
}
// TODO(roasbeef): allow updates to use Batch?
// HighestChanID returns the "highest" known channel ID in the channel graph.
// This represents the "newest" channel from the PoV of the chain. This method
// can be used by peers to quickly determine if they're graphs are in sync.
func (c *KVStore) HighestChanID() (uint64, error) {
var cid uint64
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
edges := tx.ReadBucket(edgeBucket)
if edges == nil {
return ErrGraphNoEdgesFound
}
edgeIndex := edges.NestedReadBucket(edgeIndexBucket)
if edgeIndex == nil {
return ErrGraphNoEdgesFound
}
// In order to find the highest chan ID, we'll fetch a cursor
// and use that to seek to the "end" of our known rage.
cidCursor := edgeIndex.ReadCursor()
lastChanID, _ := cidCursor.Last()
// If there's no key, then this means that we don't actually
// know of any channels, so we'll return a predicable error.
if lastChanID == nil {
return ErrGraphNoEdgesFound
}
// Otherwise, we'll de serialize the channel ID and return it
// to the caller.
cid = byteOrder.Uint64(lastChanID)
return nil
}, func() {
cid = 0
})
if err != nil && !errors.Is(err, ErrGraphNoEdgesFound) {
return 0, err
}
return cid, nil
}
// ChannelEdge represents the complete set of information for a channel edge in
// the known channel graph. This struct couples the core information of the
// edge as well as each of the known advertised edge policies.
type ChannelEdge struct {
// Info contains all the static information describing the channel.
Info *models.ChannelEdgeInfo
// Policy1 points to the "first" edge policy of the channel containing
// the dynamic information required to properly route through the edge.
Policy1 *models.ChannelEdgePolicy
// Policy2 points to the "second" edge policy of the channel containing
// the dynamic information required to properly route through the edge.
Policy2 *models.ChannelEdgePolicy
// Node1 is "node 1" in the channel. This is the node that would have
// produced Policy1 if it exists.
Node1 *models.LightningNode
// Node2 is "node 2" in the channel. This is the node that would have
// produced Policy2 if it exists.
Node2 *models.LightningNode
}
// ChanUpdatesInHorizon returns all the known channel edges which have at least
// one edge that has an update timestamp within the specified horizon.
func (c *KVStore) ChanUpdatesInHorizon(startTime,
endTime time.Time) ([]ChannelEdge, error) {
// To ensure we don't return duplicate ChannelEdges, we'll use an
// additional map to keep track of the edges already seen to prevent
// re-adding it.
var edgesSeen map[uint64]struct{}
var edgesToCache map[uint64]ChannelEdge
var edgesInHorizon []ChannelEdge
c.cacheMu.Lock()
defer c.cacheMu.Unlock()
var hits int
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
edges := tx.ReadBucket(edgeBucket)
if edges == nil {
return ErrGraphNoEdgesFound
}
edgeIndex := edges.NestedReadBucket(edgeIndexBucket)
if edgeIndex == nil {
return ErrGraphNoEdgesFound
}
edgeUpdateIndex := edges.NestedReadBucket(edgeUpdateIndexBucket)
if edgeUpdateIndex == nil {
return ErrGraphNoEdgesFound
}
nodes := tx.ReadBucket(nodeBucket)
if nodes == nil {
return ErrGraphNodesNotFound
}
// We'll now obtain a cursor to perform a range query within
// the index to find all channels within the horizon.
updateCursor := edgeUpdateIndex.ReadCursor()
var startTimeBytes, endTimeBytes [8 + 8]byte
byteOrder.PutUint64(
startTimeBytes[:8], uint64(startTime.Unix()),
)
byteOrder.PutUint64(
endTimeBytes[:8], uint64(endTime.Unix()),
)
// With our start and end times constructed, we'll step through
// the index collecting the info and policy of each update of
// each channel that has a last update within the time range.
//
//nolint:ll
for indexKey, _ := updateCursor.Seek(startTimeBytes[:]); indexKey != nil &&
bytes.Compare(indexKey, endTimeBytes[:]) <= 0; indexKey, _ = updateCursor.Next() {
// We have a new eligible entry, so we'll slice of the
// chan ID so we can query it in the DB.
chanID := indexKey[8:]
// If we've already retrieved the info and policies for
// this edge, then we can skip it as we don't need to do
// so again.
chanIDInt := byteOrder.Uint64(chanID)
if _, ok := edgesSeen[chanIDInt]; ok {
continue
}
if channel, ok := c.chanCache.get(chanIDInt); ok {
hits++
edgesSeen[chanIDInt] = struct{}{}
edgesInHorizon = append(edgesInHorizon, channel)
continue
}
// First, we'll fetch the static edge information.
edgeInfo, err := fetchChanEdgeInfo(edgeIndex, chanID)
if err != nil {
chanID := byteOrder.Uint64(chanID)
return fmt.Errorf("unable to fetch info for "+
"edge with chan_id=%v: %v", chanID, err)
}
// With the static information obtained, we'll now
// fetch the dynamic policy info.
edge1, edge2, err := fetchChanEdgePolicies(
edgeIndex, edges, chanID,
)
if err != nil {
chanID := byteOrder.Uint64(chanID)
return fmt.Errorf("unable to fetch policies "+
"for edge with chan_id=%v: %v", chanID,
err)
}
node1, err := fetchLightningNode(
nodes, edgeInfo.NodeKey1Bytes[:],
)
if err != nil {
return err
}
node2, err := fetchLightningNode(
nodes, edgeInfo.NodeKey2Bytes[:],
)
if err != nil {
return err
}
// Finally, we'll collate this edge with the rest of
// edges to be returned.
edgesSeen[chanIDInt] = struct{}{}
channel := ChannelEdge{
Info: &edgeInfo,
Policy1: edge1,
Policy2: edge2,
Node1: &node1,
Node2: &node2,
}
edgesInHorizon = append(edgesInHorizon, channel)
edgesToCache[chanIDInt] = channel
}
return nil
}, func() {
edgesSeen = make(map[uint64]struct{})
edgesToCache = make(map[uint64]ChannelEdge)
edgesInHorizon = nil
})
switch {
case errors.Is(err, ErrGraphNoEdgesFound):
fallthrough
case errors.Is(err, ErrGraphNodesNotFound):
break
case err != nil:
return nil, err
}
// Insert any edges loaded from disk into the cache.
for chanid, channel := range edgesToCache {
c.chanCache.insert(chanid, channel)
}
log.Debugf("ChanUpdatesInHorizon hit percentage: %f (%d/%d)",
float64(hits)/float64(len(edgesInHorizon)), hits,
len(edgesInHorizon))
return edgesInHorizon, nil
}
// NodeUpdatesInHorizon returns all the known lightning node which have an
// update timestamp within the passed range. This method can be used by two
// nodes to quickly determine if they have the same set of up to date node
// announcements.
func (c *KVStore) NodeUpdatesInHorizon(startTime,
endTime time.Time) ([]models.LightningNode, error) {
var nodesInHorizon []models.LightningNode
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
nodes := tx.ReadBucket(nodeBucket)
if nodes == nil {
return ErrGraphNodesNotFound
}
nodeUpdateIndex := nodes.NestedReadBucket(nodeUpdateIndexBucket)
if nodeUpdateIndex == nil {
return ErrGraphNodesNotFound
}
// We'll now obtain a cursor to perform a range query within
// the index to find all node announcements within the horizon.
updateCursor := nodeUpdateIndex.ReadCursor()
var startTimeBytes, endTimeBytes [8 + 33]byte
byteOrder.PutUint64(
startTimeBytes[:8], uint64(startTime.Unix()),
)
byteOrder.PutUint64(
endTimeBytes[:8], uint64(endTime.Unix()),
)
// With our start and end times constructed, we'll step through
// the index collecting info for each node within the time
// range.
//
//nolint:ll
for indexKey, _ := updateCursor.Seek(startTimeBytes[:]); indexKey != nil &&
bytes.Compare(indexKey, endTimeBytes[:]) <= 0; indexKey, _ = updateCursor.Next() {
nodePub := indexKey[8:]
node, err := fetchLightningNode(nodes, nodePub)
if err != nil {
return err
}
nodesInHorizon = append(nodesInHorizon, node)
}
return nil
}, func() {
nodesInHorizon = nil
})
switch {
case errors.Is(err, ErrGraphNoEdgesFound):
fallthrough
case errors.Is(err, ErrGraphNodesNotFound):
break
case err != nil:
return nil, err
}
return nodesInHorizon, nil
}
// FilterKnownChanIDs takes a set of channel IDs and return the subset of chan
// ID's that we don't know and are not known zombies of the passed set. In other
// words, we perform a set difference of our set of chan ID's and the ones
// passed in. This method can be used by callers to determine the set of
// channels another peer knows of that we don't. The ChannelUpdateInfos for the
// known zombies is also returned.
func (c *KVStore) FilterKnownChanIDs(chansInfo []ChannelUpdateInfo) ([]uint64,
[]ChannelUpdateInfo, error) {
var (
newChanIDs []uint64
knownZombies []ChannelUpdateInfo
)
c.cacheMu.Lock()
defer c.cacheMu.Unlock()
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
edges := tx.ReadBucket(edgeBucket)
if edges == nil {
return ErrGraphNoEdgesFound
}
edgeIndex := edges.NestedReadBucket(edgeIndexBucket)
if edgeIndex == nil {
return ErrGraphNoEdgesFound
}
// Fetch the zombie index, it may not exist if no edges have
// ever been marked as zombies. If the index has been
// initialized, we will use it later to skip known zombie edges.
zombieIndex := edges.NestedReadBucket(zombieBucket)
// We'll run through the set of chanIDs and collate only the
// set of channel that are unable to be found within our db.
var cidBytes [8]byte
for _, info := range chansInfo {
scid := info.ShortChannelID.ToUint64()
byteOrder.PutUint64(cidBytes[:], scid)
// If the edge is already known, skip it.
if v := edgeIndex.Get(cidBytes[:]); v != nil {
continue
}
// If the edge is a known zombie, skip it.
if zombieIndex != nil {
isZombie, _, _ := isZombieEdge(
zombieIndex, scid,
)
if isZombie {
knownZombies = append(
knownZombies, info,
)
continue
}
}
newChanIDs = append(newChanIDs, scid)
}
return nil
}, func() {
newChanIDs = nil
knownZombies = nil
})
switch {
// If we don't know of any edges yet, then we'll return the entire set
// of chan IDs specified.
case errors.Is(err, ErrGraphNoEdgesFound):
ogChanIDs := make([]uint64, len(chansInfo))
for i, info := range chansInfo {
ogChanIDs[i] = info.ShortChannelID.ToUint64()
}
return ogChanIDs, nil, nil
case err != nil:
return nil, nil, err
}
return newChanIDs, knownZombies, nil
}
// ChannelUpdateInfo couples the SCID of a channel with the timestamps of the
// latest received channel updates for the channel.
type ChannelUpdateInfo struct {
// ShortChannelID is the SCID identifier of the channel.
ShortChannelID lnwire.ShortChannelID
// Node1UpdateTimestamp is the timestamp of the latest received update
// from the node 1 channel peer. This will be set to zero time if no
// update has yet been received from this node.
Node1UpdateTimestamp time.Time
// Node2UpdateTimestamp is the timestamp of the latest received update
// from the node 2 channel peer. This will be set to zero time if no
// update has yet been received from this node.
Node2UpdateTimestamp time.Time
}
// NewChannelUpdateInfo is a constructor which makes sure we initialize the
// timestamps with zero seconds unix timestamp which equals
// `January 1, 1970, 00:00:00 UTC` in case the value is `time.Time{}`.
func NewChannelUpdateInfo(scid lnwire.ShortChannelID, node1Timestamp,
node2Timestamp time.Time) ChannelUpdateInfo {
chanInfo := ChannelUpdateInfo{
ShortChannelID: scid,
Node1UpdateTimestamp: node1Timestamp,
Node2UpdateTimestamp: node2Timestamp,
}
if node1Timestamp.IsZero() {
chanInfo.Node1UpdateTimestamp = time.Unix(0, 0)
}
if node2Timestamp.IsZero() {
chanInfo.Node2UpdateTimestamp = time.Unix(0, 0)
}
return chanInfo
}
// BlockChannelRange represents a range of channels for a given block height.
type BlockChannelRange struct {
// Height is the height of the block all of the channels below were
// included in.
Height uint32
// Channels is the list of channels identified by their short ID
// representation known to us that were included in the block height
// above. The list may include channel update timestamp information if
// requested.
Channels []ChannelUpdateInfo
}
// FilterChannelRange returns the channel ID's of all known channels which were
// mined in a block height within the passed range. The channel IDs are grouped
// by their common block height. This method can be used to quickly share with a
// peer the set of channels we know of within a particular range to catch them
// up after a period of time offline. If withTimestamps is true then the
// timestamp info of the latest received channel update messages of the channel
// will be included in the response.
func (c *KVStore) FilterChannelRange(startHeight,
endHeight uint32, withTimestamps bool) ([]BlockChannelRange, error) {
startChanID := &lnwire.ShortChannelID{
BlockHeight: startHeight,
}
endChanID := lnwire.ShortChannelID{
BlockHeight: endHeight,
TxIndex: math.MaxUint32 & 0x00ffffff,
TxPosition: math.MaxUint16,
}
// As we need to perform a range scan, we'll convert the starting and
// ending height to their corresponding values when encoded using short
// channel ID's.
var chanIDStart, chanIDEnd [8]byte
byteOrder.PutUint64(chanIDStart[:], startChanID.ToUint64())
byteOrder.PutUint64(chanIDEnd[:], endChanID.ToUint64())
var channelsPerBlock map[uint32][]ChannelUpdateInfo
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
edges := tx.ReadBucket(edgeBucket)
if edges == nil {
return ErrGraphNoEdgesFound
}
edgeIndex := edges.NestedReadBucket(edgeIndexBucket)
if edgeIndex == nil {
return ErrGraphNoEdgesFound
}
cursor := edgeIndex.ReadCursor()
// We'll now iterate through the database, and find each
// channel ID that resides within the specified range.
//
//nolint:ll
for k, v := cursor.Seek(chanIDStart[:]); k != nil &&
bytes.Compare(k, chanIDEnd[:]) <= 0; k, v = cursor.Next() {
// Don't send alias SCIDs during gossip sync.
edgeReader := bytes.NewReader(v)
edgeInfo, err := deserializeChanEdgeInfo(edgeReader)
if err != nil {
return err
}
if edgeInfo.AuthProof == nil {
continue
}
// This channel ID rests within the target range, so
// we'll add it to our returned set.
rawCid := byteOrder.Uint64(k)
cid := lnwire.NewShortChanIDFromInt(rawCid)
chanInfo := NewChannelUpdateInfo(
cid, time.Time{}, time.Time{},
)
if !withTimestamps {
channelsPerBlock[cid.BlockHeight] = append(
channelsPerBlock[cid.BlockHeight],
chanInfo,
)
continue
}
node1Key, node2Key := computeEdgePolicyKeys(&edgeInfo)
rawPolicy := edges.Get(node1Key)
if len(rawPolicy) != 0 {
r := bytes.NewReader(rawPolicy)
edge, err := deserializeChanEdgePolicyRaw(r)
if err != nil && !errors.Is(
err, ErrEdgePolicyOptionalFieldNotFound,
) {
return err
}
chanInfo.Node1UpdateTimestamp = edge.LastUpdate
}
rawPolicy = edges.Get(node2Key)
if len(rawPolicy) != 0 {
r := bytes.NewReader(rawPolicy)
edge, err := deserializeChanEdgePolicyRaw(r)
if err != nil && !errors.Is(
err, ErrEdgePolicyOptionalFieldNotFound,
) {
return err
}
chanInfo.Node2UpdateTimestamp = edge.LastUpdate
}
channelsPerBlock[cid.BlockHeight] = append(
channelsPerBlock[cid.BlockHeight], chanInfo,
)
}
return nil
}, func() {
channelsPerBlock = make(map[uint32][]ChannelUpdateInfo)
})
switch {
// If we don't know of any channels yet, then there's nothing to
// filter, so we'll return an empty slice.
case errors.Is(err, ErrGraphNoEdgesFound) || len(channelsPerBlock) == 0:
return nil, nil
case err != nil:
return nil, err
}
// Return the channel ranges in ascending block height order.
blocks := make([]uint32, 0, len(channelsPerBlock))
for block := range channelsPerBlock {
blocks = append(blocks, block)
}
sort.Slice(blocks, func(i, j int) bool {
return blocks[i] < blocks[j]
})
channelRanges := make([]BlockChannelRange, 0, len(channelsPerBlock))
for _, block := range blocks {
channelRanges = append(channelRanges, BlockChannelRange{
Height: block,
Channels: channelsPerBlock[block],
})
}
return channelRanges, nil
}
// FetchChanInfos returns the set of channel edges that correspond to the passed
// channel ID's. If an edge is the query is unknown to the database, it will
// skipped and the result will contain only those edges that exist at the time
// of the query. This can be used to respond to peer queries that are seeking to
// fill in gaps in their view of the channel graph.
func (c *KVStore) FetchChanInfos(chanIDs []uint64) ([]ChannelEdge, error) {
return c.fetchChanInfos(nil, chanIDs)
}
// fetchChanInfos returns the set of channel edges that correspond to the passed
// channel ID's. If an edge is the query is unknown to the database, it will
// skipped and the result will contain only those edges that exist at the time
// of the query. This can be used to respond to peer queries that are seeking to
// fill in gaps in their view of the channel graph.
//
// NOTE: An optional transaction may be provided. If none is provided, then a
// new one will be created.
func (c *KVStore) fetchChanInfos(tx kvdb.RTx, chanIDs []uint64) (
[]ChannelEdge, error) {
// TODO(roasbeef): sort cids?
var (
chanEdges []ChannelEdge
cidBytes [8]byte
)
fetchChanInfos := func(tx kvdb.RTx) error {
edges := tx.ReadBucket(edgeBucket)
if edges == nil {
return ErrGraphNoEdgesFound
}
edgeIndex := edges.NestedReadBucket(edgeIndexBucket)
if edgeIndex == nil {
return ErrGraphNoEdgesFound
}
nodes := tx.ReadBucket(nodeBucket)
if nodes == nil {
return ErrGraphNotFound
}
for _, cid := range chanIDs {
byteOrder.PutUint64(cidBytes[:], cid)
// First, we'll fetch the static edge information. If
// the edge is unknown, we will skip the edge and
// continue gathering all known edges.
edgeInfo, err := fetchChanEdgeInfo(
edgeIndex, cidBytes[:],
)
switch {
case errors.Is(err, ErrEdgeNotFound):
continue
case err != nil:
return err
}
// With the static information obtained, we'll now
// fetch the dynamic policy info.
edge1, edge2, err := fetchChanEdgePolicies(
edgeIndex, edges, cidBytes[:],
)
if err != nil {
return err
}
node1, err := fetchLightningNode(
nodes, edgeInfo.NodeKey1Bytes[:],
)
if err != nil {
return err
}
node2, err := fetchLightningNode(
nodes, edgeInfo.NodeKey2Bytes[:],
)
if err != nil {
return err
}
chanEdges = append(chanEdges, ChannelEdge{
Info: &edgeInfo,
Policy1: edge1,
Policy2: edge2,
Node1: &node1,
Node2: &node2,
})
}
return nil
}
if tx == nil {
err := kvdb.View(c.db, fetchChanInfos, func() {
chanEdges = nil
})
if err != nil {
return nil, err
}
return chanEdges, nil
}
err := fetchChanInfos(tx)
if err != nil {
return nil, err
}
return chanEdges, nil
}
func delEdgeUpdateIndexEntry(edgesBucket kvdb.RwBucket, chanID uint64,
edge1, edge2 *models.ChannelEdgePolicy) error {
// First, we'll fetch the edge update index bucket which currently
// stores an entry for the channel we're about to delete.
updateIndex := edgesBucket.NestedReadWriteBucket(edgeUpdateIndexBucket)
if updateIndex == nil {
// No edges in bucket, return early.
return nil
}
// Now that we have the bucket, we'll attempt to construct a template
// for the index key: updateTime || chanid.
var indexKey [8 + 8]byte
byteOrder.PutUint64(indexKey[8:], chanID)
// With the template constructed, we'll attempt to delete an entry that
// would have been created by both edges: we'll alternate the update
// times, as one may had overridden the other.
if edge1 != nil {
byteOrder.PutUint64(
indexKey[:8], uint64(edge1.LastUpdate.Unix()),
)
if err := updateIndex.Delete(indexKey[:]); err != nil {
return err
}
}
// We'll also attempt to delete the entry that may have been created by
// the second edge.
if edge2 != nil {
byteOrder.PutUint64(
indexKey[:8], uint64(edge2.LastUpdate.Unix()),
)
if err := updateIndex.Delete(indexKey[:]); err != nil {
return err
}
}
return nil
}
// delChannelEdgeUnsafe deletes the edge with the given chanID from the graph
// cache. It then goes on to delete any policy info and edge info for this
// channel from the DB and finally, if isZombie is true, it will add an entry
// for this channel in the zombie index.
//
// NOTE: this method MUST only be called if the cacheMu has already been
// acquired.
func (c *KVStore) delChannelEdgeUnsafe(edges, edgeIndex, chanIndex,
zombieIndex kvdb.RwBucket, chanID []byte, isZombie,
strictZombie bool) (*models.ChannelEdgeInfo, error) {
edgeInfo, err := fetchChanEdgeInfo(edgeIndex, chanID)
if err != nil {
return nil, err
}
// We'll also remove the entry in the edge update index bucket before
// we delete the edges themselves so we can access their last update
// times.
cid := byteOrder.Uint64(chanID)
edge1, edge2, err := fetchChanEdgePolicies(edgeIndex, edges, chanID)
if err != nil {
return nil, err
}
err = delEdgeUpdateIndexEntry(edges, cid, edge1, edge2)
if err != nil {
return nil, err
}
// The edge key is of the format pubKey || chanID. First we construct
// the latter half, populating the channel ID.
var edgeKey [33 + 8]byte
copy(edgeKey[33:], chanID)
// With the latter half constructed, copy over the first public key to
// delete the edge in this direction, then the second to delete the
// edge in the opposite direction.
copy(edgeKey[:33], edgeInfo.NodeKey1Bytes[:])
if edges.Get(edgeKey[:]) != nil {
if err := edges.Delete(edgeKey[:]); err != nil {
return nil, err
}
}
copy(edgeKey[:33], edgeInfo.NodeKey2Bytes[:])
if edges.Get(edgeKey[:]) != nil {
if err := edges.Delete(edgeKey[:]); err != nil {
return nil, err
}
}
// As part of deleting the edge we also remove all disabled entries
// from the edgePolicyDisabledIndex bucket. We do that for both
// directions.
err = updateEdgePolicyDisabledIndex(edges, cid, false, false)
if err != nil {
return nil, err
}
err = updateEdgePolicyDisabledIndex(edges, cid, true, false)
if err != nil {
return nil, err
}
// With the edge data deleted, we can purge the information from the two
// edge indexes.
if err := edgeIndex.Delete(chanID); err != nil {
return nil, err
}
var b bytes.Buffer
if err := WriteOutpoint(&b, &edgeInfo.ChannelPoint); err != nil {
return nil, err
}
if err := chanIndex.Delete(b.Bytes()); err != nil {
return nil, err
}
// Finally, we'll mark the edge as a zombie within our index if it's
// being removed due to the channel becoming a zombie. We do this to
// ensure we don't store unnecessary data for spent channels.
if !isZombie {
return &edgeInfo, nil
}
nodeKey1, nodeKey2 := edgeInfo.NodeKey1Bytes, edgeInfo.NodeKey2Bytes
if strictZombie {
nodeKey1, nodeKey2 = makeZombiePubkeys(&edgeInfo, edge1, edge2)
}
return &edgeInfo, markEdgeZombie(
zombieIndex, byteOrder.Uint64(chanID), nodeKey1, nodeKey2,
)
}
// makeZombiePubkeys derives the node pubkeys to store in the zombie index for a
// particular pair of channel policies. The return values are one of:
// 1. (pubkey1, pubkey2)
// 2. (pubkey1, blank)
// 3. (blank, pubkey2)
//
// A blank pubkey means that corresponding node will be unable to resurrect a
// channel on its own. For example, node1 may continue to publish recent
// updates, but node2 has fallen way behind. After marking an edge as a zombie,
// we don't want another fresh update from node1 to resurrect, as the edge can
// only become live once node2 finally sends something recent.
//
// In the case where we have neither update, we allow either party to resurrect
// the channel. If the channel were to be marked zombie again, it would be
// marked with the correct lagging channel since we received an update from only
// one side.
func makeZombiePubkeys(info *models.ChannelEdgeInfo,
e1, e2 *models.ChannelEdgePolicy) ([33]byte, [33]byte) {
switch {
// If we don't have either edge policy, we'll return both pubkeys so
// that the channel can be resurrected by either party.
case e1 == nil && e2 == nil:
return info.NodeKey1Bytes, info.NodeKey2Bytes
// If we're missing edge1, or if both edges are present but edge1 is
// older, we'll return edge1's pubkey and a blank pubkey for edge2. This
// means that only an update from edge1 will be able to resurrect the
// channel.
case e1 == nil || (e2 != nil && e1.LastUpdate.Before(e2.LastUpdate)):
return info.NodeKey1Bytes, [33]byte{}
// Otherwise, we're missing edge2 or edge2 is the older side, so we
// return a blank pubkey for edge1. In this case, only an update from
// edge2 can resurect the channel.
default:
return [33]byte{}, info.NodeKey2Bytes
}
}
// UpdateEdgePolicy updates the edge routing policy for a single directed edge
// within the database for the referenced channel. The `flags` attribute within
// the ChannelEdgePolicy determines which of the directed edges are being
// updated. If the flag is 1, then the first node's information is being
// updated, otherwise it's the second node's information. The node ordering is
// determined by the lexicographical ordering of the identity public keys of the
// nodes on either side of the channel.
func (c *KVStore) UpdateEdgePolicy(edge *models.ChannelEdgePolicy,
op ...batch.SchedulerOption) (route.Vertex, route.Vertex, error) {
var (
isUpdate1 bool
edgeNotFound bool
from, to route.Vertex
)
r := &batch.Request{
Reset: func() {
isUpdate1 = false
edgeNotFound = false
},
Update: func(tx kvdb.RwTx) error {
var err error
from, to, isUpdate1, err = updateEdgePolicy(tx, edge)
if err != nil {
log.Errorf("UpdateEdgePolicy faild: %v", err)
}
// Silence ErrEdgeNotFound so that the batch can
// succeed, but propagate the error via local state.
if errors.Is(err, ErrEdgeNotFound) {
edgeNotFound = true
return nil
}
return err
},
OnCommit: func(err error) error {
switch {
case err != nil:
return err
case edgeNotFound:
return ErrEdgeNotFound
default:
c.updateEdgeCache(edge, isUpdate1)
return nil
}
},
}
for _, f := range op {
f(r)
}
err := c.chanScheduler.Execute(r)
return from, to, err
}
func (c *KVStore) updateEdgeCache(e *models.ChannelEdgePolicy,
isUpdate1 bool) {
// If an entry for this channel is found in reject cache, we'll modify
// the entry with the updated timestamp for the direction that was just
// written. If the edge doesn't exist, we'll load the cache entry lazily
// during the next query for this edge.
if entry, ok := c.rejectCache.get(e.ChannelID); ok {
if isUpdate1 {
entry.upd1Time = e.LastUpdate.Unix()
} else {
entry.upd2Time = e.LastUpdate.Unix()
}
c.rejectCache.insert(e.ChannelID, entry)
}
// If an entry for this channel is found in channel cache, we'll modify
// the entry with the updated policy for the direction that was just
// written. If the edge doesn't exist, we'll defer loading the info and
// policies and lazily read from disk during the next query.
if channel, ok := c.chanCache.get(e.ChannelID); ok {
if isUpdate1 {
channel.Policy1 = e
} else {
channel.Policy2 = e
}
c.chanCache.insert(e.ChannelID, channel)
}
}
// updateEdgePolicy attempts to update an edge's policy within the relevant
// buckets using an existing database transaction. The returned boolean will be
// true if the updated policy belongs to node1, and false if the policy belonged
// to node2.
func updateEdgePolicy(tx kvdb.RwTx, edge *models.ChannelEdgePolicy) (
route.Vertex, route.Vertex, bool, error) {
var noVertex route.Vertex
edges := tx.ReadWriteBucket(edgeBucket)
if edges == nil {
return noVertex, noVertex, false, ErrEdgeNotFound
}
edgeIndex := edges.NestedReadWriteBucket(edgeIndexBucket)
if edgeIndex == nil {
return noVertex, noVertex, false, ErrEdgeNotFound
}
// Create the channelID key be converting the channel ID
// integer into a byte slice.
var chanID [8]byte
byteOrder.PutUint64(chanID[:], edge.ChannelID)
// With the channel ID, we then fetch the value storing the two
// nodes which connect this channel edge.
nodeInfo := edgeIndex.Get(chanID[:])
if nodeInfo == nil {
return noVertex, noVertex, false, ErrEdgeNotFound
}
// Depending on the flags value passed above, either the first
// or second edge policy is being updated.
var fromNode, toNode []byte
var isUpdate1 bool
if edge.ChannelFlags&lnwire.ChanUpdateDirection == 0 {
fromNode = nodeInfo[:33]
toNode = nodeInfo[33:66]
isUpdate1 = true
} else {
fromNode = nodeInfo[33:66]
toNode = nodeInfo[:33]
isUpdate1 = false
}
// Finally, with the direction of the edge being updated
// identified, we update the on-disk edge representation.
err := putChanEdgePolicy(edges, edge, fromNode, toNode)
if err != nil {
return noVertex, noVertex, false, err
}
var (
fromNodePubKey route.Vertex
toNodePubKey route.Vertex
)
copy(fromNodePubKey[:], fromNode)
copy(toNodePubKey[:], toNode)
return fromNodePubKey, toNodePubKey, isUpdate1, nil
}
// isPublic determines whether the node is seen as public within the graph from
// the source node's point of view. An existing database transaction can also be
// specified.
func (c *KVStore) isPublic(tx kvdb.RTx, nodePub route.Vertex,
sourcePubKey []byte) (bool, error) {
// In order to determine whether this node is publicly advertised within
// the graph, we'll need to look at all of its edges and check whether
// they extend to any other node than the source node. errDone will be
// used to terminate the check early.
nodeIsPublic := false
errDone := errors.New("done")
err := c.ForEachNodeChannelTx(tx, nodePub, func(tx kvdb.RTx,
info *models.ChannelEdgeInfo, _ *models.ChannelEdgePolicy,
_ *models.ChannelEdgePolicy) error {
// If this edge doesn't extend to the source node, we'll
// terminate our search as we can now conclude that the node is
// publicly advertised within the graph due to the local node
// knowing of the current edge.
if !bytes.Equal(info.NodeKey1Bytes[:], sourcePubKey) &&
!bytes.Equal(info.NodeKey2Bytes[:], sourcePubKey) {
nodeIsPublic = true
return errDone
}
// Since the edge _does_ extend to the source node, we'll also
// need to ensure that this is a public edge.
if info.AuthProof != nil {
nodeIsPublic = true
return errDone
}
// Otherwise, we'll continue our search.
return nil
})
if err != nil && !errors.Is(err, errDone) {
return false, err
}
return nodeIsPublic, nil
}
// FetchLightningNodeTx attempts to look up a target node by its identity
// public key. If the node isn't found in the database, then
// ErrGraphNodeNotFound is returned. An optional transaction may be provided.
// If none is provided, then a new one will be created.
func (c *KVStore) FetchLightningNodeTx(tx kvdb.RTx, nodePub route.Vertex) (
*models.LightningNode, error) {
return c.fetchLightningNode(tx, nodePub)
}
// FetchLightningNode attempts to look up a target node by its identity public
// key. If the node isn't found in the database, then ErrGraphNodeNotFound is
// returned.
func (c *KVStore) FetchLightningNode(nodePub route.Vertex) (
*models.LightningNode, error) {
return c.fetchLightningNode(nil, nodePub)
}
// fetchLightningNode attempts to look up a target node by its identity public
// key. If the node isn't found in the database, then ErrGraphNodeNotFound is
// returned. An optional transaction may be provided. If none is provided, then
// a new one will be created.
func (c *KVStore) fetchLightningNode(tx kvdb.RTx,
nodePub route.Vertex) (*models.LightningNode, error) {
var node *models.LightningNode
fetch := func(tx kvdb.RTx) error {
// First grab the nodes bucket which stores the mapping from
// pubKey to node information.
nodes := tx.ReadBucket(nodeBucket)
if nodes == nil {
return ErrGraphNotFound
}
// If a key for this serialized public key isn't found, then
// the target node doesn't exist within the database.
nodeBytes := nodes.Get(nodePub[:])
if nodeBytes == nil {
return ErrGraphNodeNotFound
}
// If the node is found, then we can de deserialize the node
// information to return to the user.
nodeReader := bytes.NewReader(nodeBytes)
n, err := deserializeLightningNode(nodeReader)
if err != nil {
return err
}
node = &n
return nil
}
if tx == nil {
err := kvdb.View(
c.db, fetch, func() {
node = nil
},
)
if err != nil {
return nil, err
}
return node, nil
}
err := fetch(tx)
if err != nil {
return nil, err
}
return node, nil
}
// HasLightningNode determines if the graph has a vertex identified by the
// target node identity public key. If the node exists in the database, a
// timestamp of when the data for the node was lasted updated is returned along
// with a true boolean. Otherwise, an empty time.Time is returned with a false
// boolean.
func (c *KVStore) HasLightningNode(nodePub [33]byte) (time.Time, bool,
error) {
var (
updateTime time.Time
exists bool
)
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
// First grab the nodes bucket which stores the mapping from
// pubKey to node information.
nodes := tx.ReadBucket(nodeBucket)
if nodes == nil {
return ErrGraphNotFound
}
// If a key for this serialized public key isn't found, we can
// exit early.
nodeBytes := nodes.Get(nodePub[:])
if nodeBytes == nil {
exists = false
return nil
}
// Otherwise we continue on to obtain the time stamp
// representing the last time the data for this node was
// updated.
nodeReader := bytes.NewReader(nodeBytes)
node, err := deserializeLightningNode(nodeReader)
if err != nil {
return err
}
exists = true
updateTime = node.LastUpdate
return nil
}, func() {
updateTime = time.Time{}
exists = false
})
if err != nil {
return time.Time{}, exists, err
}
return updateTime, exists, nil
}
// nodeTraversal is used to traverse all channels of a node given by its
// public key and passes channel information into the specified callback.
func nodeTraversal(tx kvdb.RTx, nodePub []byte, db kvdb.Backend,
cb func(kvdb.RTx, *models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
*models.ChannelEdgePolicy) error) error {
traversal := func(tx kvdb.RTx) error {
edges := tx.ReadBucket(edgeBucket)
if edges == nil {
return ErrGraphNotFound
}
edgeIndex := edges.NestedReadBucket(edgeIndexBucket)
if edgeIndex == nil {
return ErrGraphNoEdgesFound
}
// In order to reach all the edges for this node, we take
// advantage of the construction of the key-space within the
// edge bucket. The keys are stored in the form: pubKey ||
// chanID. Therefore, starting from a chanID of zero, we can
// scan forward in the bucket, grabbing all the edges for the
// node. Once the prefix no longer matches, then we know we're
// done.
var nodeStart [33 + 8]byte
copy(nodeStart[:], nodePub)
copy(nodeStart[33:], chanStart[:])
// Starting from the key pubKey || 0, we seek forward in the
// bucket until the retrieved key no longer has the public key
// as its prefix. This indicates that we've stepped over into
// another node's edges, so we can terminate our scan.
edgeCursor := edges.ReadCursor()
for nodeEdge, _ := edgeCursor.Seek(nodeStart[:]); bytes.HasPrefix(nodeEdge, nodePub); nodeEdge, _ = edgeCursor.Next() { //nolint:ll
// If the prefix still matches, the channel id is
// returned in nodeEdge. Channel id is used to lookup
// the node at the other end of the channel and both
// edge policies.
chanID := nodeEdge[33:]
edgeInfo, err := fetchChanEdgeInfo(edgeIndex, chanID)
if err != nil {
return err
}
outgoingPolicy, err := fetchChanEdgePolicy(
edges, chanID, nodePub,
)
if err != nil {
return err
}
otherNode, err := edgeInfo.OtherNodeKeyBytes(nodePub)
if err != nil {
return err
}
incomingPolicy, err := fetchChanEdgePolicy(
edges, chanID, otherNode[:],
)
if err != nil {
return err
}
// Finally, we execute the callback.
err = cb(tx, &edgeInfo, outgoingPolicy, incomingPolicy)
if err != nil {
return err
}
}
return nil
}
// If no transaction was provided, then we'll create a new transaction
// to execute the transaction within.
if tx == nil {
return kvdb.View(db, traversal, func() {})
}
// Otherwise, we re-use the existing transaction to execute the graph
// traversal.
return traversal(tx)
}
// ForEachNodeChannel iterates through all channels of the given node,
// executing the passed callback with an edge info structure and the policies
// of each end of the channel. The first edge policy is the outgoing edge *to*
// the connecting node, while the second is the incoming edge *from* the
// connecting node. If the callback returns an error, then the iteration is
// halted with the error propagated back up to the caller.
//
// Unknown policies are passed into the callback as nil values.
func (c *KVStore) ForEachNodeChannel(nodePub route.Vertex,
cb func(kvdb.RTx, *models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
*models.ChannelEdgePolicy) error) error {
return nodeTraversal(nil, nodePub[:], c.db, cb)
}
// ForEachNodeChannelTx iterates through all channels of the given node,
// executing the passed callback with an edge info structure and the policies
// of each end of the channel. The first edge policy is the outgoing edge *to*
// the connecting node, while the second is the incoming edge *from* the
// connecting node. If the callback returns an error, then the iteration is
// halted with the error propagated back up to the caller.
//
// Unknown policies are passed into the callback as nil values.
//
// If the caller wishes to re-use an existing boltdb transaction, then it
// should be passed as the first argument. Otherwise, the first argument should
// be nil and a fresh transaction will be created to execute the graph
// traversal.
func (c *KVStore) ForEachNodeChannelTx(tx kvdb.RTx,
nodePub route.Vertex, cb func(kvdb.RTx, *models.ChannelEdgeInfo,
*models.ChannelEdgePolicy,
*models.ChannelEdgePolicy) error) error {
return nodeTraversal(tx, nodePub[:], c.db, cb)
}
// FetchOtherNode attempts to fetch the full LightningNode that's opposite of
// the target node in the channel. This is useful when one knows the pubkey of
// one of the nodes, and wishes to obtain the full LightningNode for the other
// end of the channel.
func (c *KVStore) FetchOtherNode(tx kvdb.RTx,
channel *models.ChannelEdgeInfo, thisNodeKey []byte) (
*models.LightningNode, error) {
// Ensure that the node passed in is actually a member of the channel.
var targetNodeBytes [33]byte
switch {
case bytes.Equal(channel.NodeKey1Bytes[:], thisNodeKey):
targetNodeBytes = channel.NodeKey2Bytes
case bytes.Equal(channel.NodeKey2Bytes[:], thisNodeKey):
targetNodeBytes = channel.NodeKey1Bytes
default:
return nil, fmt.Errorf("node not participating in this channel")
}
var targetNode *models.LightningNode
fetchNodeFunc := func(tx kvdb.RTx) error {
// First grab the nodes bucket which stores the mapping from
// pubKey to node information.
nodes := tx.ReadBucket(nodeBucket)
if nodes == nil {
return ErrGraphNotFound
}
node, err := fetchLightningNode(nodes, targetNodeBytes[:])
if err != nil {
return err
}
targetNode = &node
return nil
}
// If the transaction is nil, then we'll need to create a new one,
// otherwise we can use the existing db transaction.
var err error
if tx == nil {
err = kvdb.View(c.db, fetchNodeFunc, func() {
targetNode = nil
})
} else {
err = fetchNodeFunc(tx)
}
return targetNode, err
}
// computeEdgePolicyKeys is a helper function that can be used to compute the
// keys used to index the channel edge policy info for the two nodes of the
// edge. The keys for node 1 and node 2 are returned respectively.
func computeEdgePolicyKeys(info *models.ChannelEdgeInfo) ([]byte, []byte) {
var (
node1Key [33 + 8]byte
node2Key [33 + 8]byte
)
copy(node1Key[:], info.NodeKey1Bytes[:])
copy(node2Key[:], info.NodeKey2Bytes[:])
byteOrder.PutUint64(node1Key[33:], info.ChannelID)
byteOrder.PutUint64(node2Key[33:], info.ChannelID)
return node1Key[:], node2Key[:]
}
// FetchChannelEdgesByOutpoint attempts to lookup the two directed edges for
// the channel identified by the funding outpoint. If the channel can't be
// found, then ErrEdgeNotFound is returned. A struct which houses the general
// information for the channel itself is returned as well as two structs that
// contain the routing policies for the channel in either direction.
func (c *KVStore) FetchChannelEdgesByOutpoint(op *wire.OutPoint) (
*models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
*models.ChannelEdgePolicy, error) {
var (
edgeInfo *models.ChannelEdgeInfo
policy1 *models.ChannelEdgePolicy
policy2 *models.ChannelEdgePolicy
)
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
// First, grab the node bucket. This will be used to populate
// the Node pointers in each edge read from disk.
nodes := tx.ReadBucket(nodeBucket)
if nodes == nil {
return ErrGraphNotFound
}
// Next, grab the edge bucket which stores the edges, and also
// the index itself so we can group the directed edges together
// logically.
edges := tx.ReadBucket(edgeBucket)
if edges == nil {
return ErrGraphNoEdgesFound
}
edgeIndex := edges.NestedReadBucket(edgeIndexBucket)
if edgeIndex == nil {
return ErrGraphNoEdgesFound
}
// If the channel's outpoint doesn't exist within the outpoint
// index, then the edge does not exist.
chanIndex := edges.NestedReadBucket(channelPointBucket)
if chanIndex == nil {
return ErrGraphNoEdgesFound
}
var b bytes.Buffer
if err := WriteOutpoint(&b, op); err != nil {
return err
}
chanID := chanIndex.Get(b.Bytes())
if chanID == nil {
return fmt.Errorf("%w: op=%v", ErrEdgeNotFound, op)
}
// If the channel is found to exists, then we'll first retrieve
// the general information for the channel.
edge, err := fetchChanEdgeInfo(edgeIndex, chanID)
if err != nil {
return fmt.Errorf("%w: chanID=%x", err, chanID)
}
edgeInfo = &edge
// Once we have the information about the channels' parameters,
// we'll fetch the routing policies for each for the directed
// edges.
e1, e2, err := fetchChanEdgePolicies(edgeIndex, edges, chanID)
if err != nil {
return fmt.Errorf("failed to find policy: %w", err)
}
policy1 = e1
policy2 = e2
return nil
}, func() {
edgeInfo = nil
policy1 = nil
policy2 = nil
})
if err != nil {
return nil, nil, nil, err
}
return edgeInfo, policy1, policy2, nil
}
// FetchChannelEdgesByID attempts to lookup the two directed edges for the
// channel identified by the channel ID. If the channel can't be found, then
// ErrEdgeNotFound is returned. A struct which houses the general information
// for the channel itself is returned as well as two structs that contain the
// routing policies for the channel in either direction.
//
// ErrZombieEdge an be returned if the edge is currently marked as a zombie
// within the database. In this case, the ChannelEdgePolicy's will be nil, and
// the ChannelEdgeInfo will only include the public keys of each node.
func (c *KVStore) FetchChannelEdgesByID(chanID uint64) (
*models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
*models.ChannelEdgePolicy, error) {
var (
edgeInfo *models.ChannelEdgeInfo
policy1 *models.ChannelEdgePolicy
policy2 *models.ChannelEdgePolicy
channelID [8]byte
)
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
// First, grab the node bucket. This will be used to populate
// the Node pointers in each edge read from disk.
nodes := tx.ReadBucket(nodeBucket)
if nodes == nil {
return ErrGraphNotFound
}
// Next, grab the edge bucket which stores the edges, and also
// the index itself so we can group the directed edges together
// logically.
edges := tx.ReadBucket(edgeBucket)
if edges == nil {
return ErrGraphNoEdgesFound
}
edgeIndex := edges.NestedReadBucket(edgeIndexBucket)
if edgeIndex == nil {
return ErrGraphNoEdgesFound
}
byteOrder.PutUint64(channelID[:], chanID)
// Now, attempt to fetch edge.
edge, err := fetchChanEdgeInfo(edgeIndex, channelID[:])
// If it doesn't exist, we'll quickly check our zombie index to
// see if we've previously marked it as so.
if errors.Is(err, ErrEdgeNotFound) {
// If the zombie index doesn't exist, or the edge is not
// marked as a zombie within it, then we'll return the
// original ErrEdgeNotFound error.
zombieIndex := edges.NestedReadBucket(zombieBucket)
if zombieIndex == nil {
return ErrEdgeNotFound
}
isZombie, pubKey1, pubKey2 := isZombieEdge(
zombieIndex, chanID,
)
if !isZombie {
return ErrEdgeNotFound
}
// Otherwise, the edge is marked as a zombie, so we'll
// populate the edge info with the public keys of each
// party as this is the only information we have about
// it and return an error signaling so.
edgeInfo = &models.ChannelEdgeInfo{
NodeKey1Bytes: pubKey1,
NodeKey2Bytes: pubKey2,
}
return ErrZombieEdge
}
// Otherwise, we'll just return the error if any.
if err != nil {
return err
}
edgeInfo = &edge
// Then we'll attempt to fetch the accompanying policies of this
// edge.
e1, e2, err := fetchChanEdgePolicies(
edgeIndex, edges, channelID[:],
)
if err != nil {
return err
}
policy1 = e1
policy2 = e2
return nil
}, func() {
edgeInfo = nil
policy1 = nil
policy2 = nil
})
if errors.Is(err, ErrZombieEdge) {
return edgeInfo, nil, nil, err
}
if err != nil {
return nil, nil, nil, err
}
return edgeInfo, policy1, policy2, nil
}
// IsPublicNode is a helper method that determines whether the node with the
// given public key is seen as a public node in the graph from the graph's
// source node's point of view.
func (c *KVStore) IsPublicNode(pubKey [33]byte) (bool, error) {
var nodeIsPublic bool
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
nodes := tx.ReadBucket(nodeBucket)
if nodes == nil {
return ErrGraphNodesNotFound
}
ourPubKey := nodes.Get(sourceKey)
if ourPubKey == nil {
return ErrSourceNodeNotSet
}
node, err := fetchLightningNode(nodes, pubKey[:])
if err != nil {
return err
}
nodeIsPublic, err = c.isPublic(tx, node.PubKeyBytes, ourPubKey)
return err
}, func() {
nodeIsPublic = false
})
if err != nil {
return false, err
}
return nodeIsPublic, nil
}
// genMultiSigP2WSH generates the p2wsh'd multisig script for 2 of 2 pubkeys.
func genMultiSigP2WSH(aPub, bPub []byte) ([]byte, error) {
witnessScript, err := input.GenMultiSigScript(aPub, bPub)
if err != nil {
return nil, err
}
// With the witness script generated, we'll now turn it into a p2wsh
// script:
// * OP_0 <sha256(script)>
bldr := txscript.NewScriptBuilder(
txscript.WithScriptAllocSize(input.P2WSHSize),
)
bldr.AddOp(txscript.OP_0)
scriptHash := sha256.Sum256(witnessScript)
bldr.AddData(scriptHash[:])
return bldr.Script()
}
// EdgePoint couples the outpoint of a channel with the funding script that it
// creates. The FilteredChainView will use this to watch for spends of this
// edge point on chain. We require both of these values as depending on the
// concrete implementation, either the pkScript, or the out point will be used.
type EdgePoint struct {
// FundingPkScript is the p2wsh multi-sig script of the target channel.
FundingPkScript []byte
// OutPoint is the outpoint of the target channel.
OutPoint wire.OutPoint
}
// String returns a human readable version of the target EdgePoint. We return
// the outpoint directly as it is enough to uniquely identify the edge point.
func (e *EdgePoint) String() string {
return e.OutPoint.String()
}
// ChannelView returns the verifiable edge information for each active channel
// within the known channel graph. The set of UTXO's (along with their scripts)
// returned are the ones that need to be watched on chain to detect channel
// closes on the resident blockchain.
func (c *KVStore) ChannelView() ([]EdgePoint, error) {
var edgePoints []EdgePoint
if err := kvdb.View(c.db, func(tx kvdb.RTx) error {
// We're going to iterate over the entire channel index, so
// we'll need to fetch the edgeBucket to get to the index as
// it's a sub-bucket.
edges := tx.ReadBucket(edgeBucket)
if edges == nil {
return ErrGraphNoEdgesFound
}
chanIndex := edges.NestedReadBucket(channelPointBucket)
if chanIndex == nil {
return ErrGraphNoEdgesFound
}
edgeIndex := edges.NestedReadBucket(edgeIndexBucket)
if edgeIndex == nil {
return ErrGraphNoEdgesFound
}
// Once we have the proper bucket, we'll range over each key
// (which is the channel point for the channel) and decode it,
// accumulating each entry.
return chanIndex.ForEach(
func(chanPointBytes, chanID []byte) error {
chanPointReader := bytes.NewReader(
chanPointBytes,
)
var chanPoint wire.OutPoint
err := ReadOutpoint(chanPointReader, &chanPoint)
if err != nil {
return err
}
edgeInfo, err := fetchChanEdgeInfo(
edgeIndex, chanID,
)
if err != nil {
return err
}
pkScript, err := genMultiSigP2WSH(
edgeInfo.BitcoinKey1Bytes[:],
edgeInfo.BitcoinKey2Bytes[:],
)
if err != nil {
return err
}
edgePoints = append(edgePoints, EdgePoint{
FundingPkScript: pkScript,
OutPoint: chanPoint,
})
return nil
},
)
}, func() {
edgePoints = nil
}); err != nil {
return nil, err
}
return edgePoints, nil
}
// MarkEdgeZombie attempts to mark a channel identified by its channel ID as a
// zombie. This method is used on an ad-hoc basis, when channels need to be
// marked as zombies outside the normal pruning cycle.
func (c *KVStore) MarkEdgeZombie(chanID uint64,
pubKey1, pubKey2 [33]byte) error {
c.cacheMu.Lock()
defer c.cacheMu.Unlock()
err := kvdb.Batch(c.db, func(tx kvdb.RwTx) error {
edges := tx.ReadWriteBucket(edgeBucket)
if edges == nil {
return ErrGraphNoEdgesFound
}
zombieIndex, err := edges.CreateBucketIfNotExists(zombieBucket)
if err != nil {
return fmt.Errorf("unable to create zombie "+
"bucket: %w", err)
}
return markEdgeZombie(zombieIndex, chanID, pubKey1, pubKey2)
})
if err != nil {
return err
}
c.rejectCache.remove(chanID)
c.chanCache.remove(chanID)
return nil
}
// markEdgeZombie marks an edge as a zombie within our zombie index. The public
// keys should represent the node public keys of the two parties involved in the
// edge.
func markEdgeZombie(zombieIndex kvdb.RwBucket, chanID uint64, pubKey1,
pubKey2 [33]byte) error {
var k [8]byte
byteOrder.PutUint64(k[:], chanID)
var v [66]byte
copy(v[:33], pubKey1[:])
copy(v[33:], pubKey2[:])
return zombieIndex.Put(k[:], v[:])
}
// MarkEdgeLive clears an edge from our zombie index, deeming it as live.
func (c *KVStore) MarkEdgeLive(chanID uint64) error {
c.cacheMu.Lock()
defer c.cacheMu.Unlock()
return c.markEdgeLiveUnsafe(nil, chanID)
}
// markEdgeLiveUnsafe clears an edge from the zombie index. This method can be
// called with an existing kvdb.RwTx or the argument can be set to nil in which
// case a new transaction will be created.
//
// NOTE: this method MUST only be called if the cacheMu has already been
// acquired.
func (c *KVStore) markEdgeLiveUnsafe(tx kvdb.RwTx, chanID uint64) error {
dbFn := func(tx kvdb.RwTx) error {
edges := tx.ReadWriteBucket(edgeBucket)
if edges == nil {
return ErrGraphNoEdgesFound
}
zombieIndex := edges.NestedReadWriteBucket(zombieBucket)
if zombieIndex == nil {
return nil
}
var k [8]byte
byteOrder.PutUint64(k[:], chanID)
if len(zombieIndex.Get(k[:])) == 0 {
return ErrZombieEdgeNotFound
}
return zombieIndex.Delete(k[:])
}
// If the transaction is nil, we'll create a new one. Otherwise, we use
// the existing transaction
var err error
if tx == nil {
err = kvdb.Update(c.db, dbFn, func() {})
} else {
err = dbFn(tx)
}
if err != nil {
return err
}
c.rejectCache.remove(chanID)
c.chanCache.remove(chanID)
return nil
}
// IsZombieEdge returns whether the edge is considered zombie. If it is a
// zombie, then the two node public keys corresponding to this edge are also
// returned.
func (c *KVStore) IsZombieEdge(chanID uint64) (bool, [33]byte, [33]byte) {
var (
isZombie bool
pubKey1, pubKey2 [33]byte
)
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
edges := tx.ReadBucket(edgeBucket)
if edges == nil {
return ErrGraphNoEdgesFound
}
zombieIndex := edges.NestedReadBucket(zombieBucket)
if zombieIndex == nil {
return nil
}
isZombie, pubKey1, pubKey2 = isZombieEdge(zombieIndex, chanID)
return nil
}, func() {
isZombie = false
pubKey1 = [33]byte{}
pubKey2 = [33]byte{}
})
if err != nil {
return false, [33]byte{}, [33]byte{}
}
return isZombie, pubKey1, pubKey2
}
// isZombieEdge returns whether an entry exists for the given channel in the
// zombie index. If an entry exists, then the two node public keys corresponding
// to this edge are also returned.
func isZombieEdge(zombieIndex kvdb.RBucket,
chanID uint64) (bool, [33]byte, [33]byte) {
var k [8]byte
byteOrder.PutUint64(k[:], chanID)
v := zombieIndex.Get(k[:])
if v == nil {
return false, [33]byte{}, [33]byte{}
}
var pubKey1, pubKey2 [33]byte
copy(pubKey1[:], v[:33])
copy(pubKey2[:], v[33:])
return true, pubKey1, pubKey2
}
// NumZombies returns the current number of zombie channels in the graph.
func (c *KVStore) NumZombies() (uint64, error) {
var numZombies uint64
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
edges := tx.ReadBucket(edgeBucket)
if edges == nil {
return nil
}
zombieIndex := edges.NestedReadBucket(zombieBucket)
if zombieIndex == nil {
return nil
}
return zombieIndex.ForEach(func(_, _ []byte) error {
numZombies++
return nil
})
}, func() {
numZombies = 0
})
if err != nil {
return 0, err
}
return numZombies, nil
}
// PutClosedScid stores a SCID for a closed channel in the database. This is so
// that we can ignore channel announcements that we know to be closed without
// having to validate them and fetch a block.
func (c *KVStore) PutClosedScid(scid lnwire.ShortChannelID) error {
return kvdb.Update(c.db, func(tx kvdb.RwTx) error {
closedScids, err := tx.CreateTopLevelBucket(closedScidBucket)
if err != nil {
return err
}
var k [8]byte
byteOrder.PutUint64(k[:], scid.ToUint64())
return closedScids.Put(k[:], []byte{})
}, func() {})
}
// IsClosedScid checks whether a channel identified by the passed in scid is
// closed. This helps avoid having to perform expensive validation checks.
// TODO: Add an LRU cache to cut down on disc reads.
func (c *KVStore) IsClosedScid(scid lnwire.ShortChannelID) (bool, error) {
var isClosed bool
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
closedScids := tx.ReadBucket(closedScidBucket)
if closedScids == nil {
return ErrClosedScidsNotFound
}
var k [8]byte
byteOrder.PutUint64(k[:], scid.ToUint64())
if closedScids.Get(k[:]) != nil {
isClosed = true
return nil
}
return nil
}, func() {
isClosed = false
})
if err != nil {
return false, err
}
return isClosed, nil
}
// GraphSession will provide the call-back with access to a NodeTraverser
// instance which can be used to perform queries against the channel graph.
func (c *KVStore) GraphSession(cb func(graph NodeTraverser) error) error {
return c.db.View(func(tx walletdb.ReadTx) error {
return cb(&nodeTraverserSession{
db: c,
tx: tx,
})
}, func() {})
}
// nodeTraverserSession implements the NodeTraverser interface but with a
// backing read only transaction for a consistent view of the graph.
type nodeTraverserSession struct {
tx kvdb.RTx
db *KVStore
}
// ForEachNodeDirectedChannel calls the callback for every channel of the given
// node.
//
// NOTE: Part of the NodeTraverser interface.
func (c *nodeTraverserSession) ForEachNodeDirectedChannel(nodePub route.Vertex,
cb func(channel *DirectedChannel) error) error {
return c.db.forEachNodeDirectedChannel(c.tx, nodePub, cb)
}
// FetchNodeFeatures returns the features of the given node. If the node is
// unknown, assume no additional features are supported.
//
// NOTE: Part of the NodeTraverser interface.
func (c *nodeTraverserSession) FetchNodeFeatures(nodePub route.Vertex) (
*lnwire.FeatureVector, error) {
return c.db.fetchNodeFeatures(c.tx, nodePub)
}
func putLightningNode(nodeBucket, aliasBucket, updateIndex kvdb.RwBucket,
node *models.LightningNode) error {
var (
scratch [16]byte
b bytes.Buffer
)
pub, err := node.PubKey()
if err != nil {
return err
}
nodePub := pub.SerializeCompressed()
// If the node has the update time set, write it, else write 0.
updateUnix := uint64(0)
if node.LastUpdate.Unix() > 0 {
updateUnix = uint64(node.LastUpdate.Unix())
}
byteOrder.PutUint64(scratch[:8], updateUnix)
if _, err := b.Write(scratch[:8]); err != nil {
return err
}
if _, err := b.Write(nodePub); err != nil {
return err
}
// If we got a node announcement for this node, we will have the rest
// of the data available. If not we don't have more data to write.
if !node.HaveNodeAnnouncement {
// Write HaveNodeAnnouncement=0.
byteOrder.PutUint16(scratch[:2], 0)
if _, err := b.Write(scratch[:2]); err != nil {
return err
}
return nodeBucket.Put(nodePub, b.Bytes())
}
// Write HaveNodeAnnouncement=1.
byteOrder.PutUint16(scratch[:2], 1)
if _, err := b.Write(scratch[:2]); err != nil {
return err
}
if err := binary.Write(&b, byteOrder, node.Color.R); err != nil {
return err
}
if err := binary.Write(&b, byteOrder, node.Color.G); err != nil {
return err
}
if err := binary.Write(&b, byteOrder, node.Color.B); err != nil {
return err
}
if err := wire.WriteVarString(&b, 0, node.Alias); err != nil {
return err
}
if err := node.Features.Encode(&b); err != nil {
return err
}
numAddresses := uint16(len(node.Addresses))
byteOrder.PutUint16(scratch[:2], numAddresses)
if _, err := b.Write(scratch[:2]); err != nil {
return err
}
for _, address := range node.Addresses {
if err := SerializeAddr(&b, address); err != nil {
return err
}
}
sigLen := len(node.AuthSigBytes)
if sigLen > 80 {
return fmt.Errorf("max sig len allowed is 80, had %v",
sigLen)
}
err = wire.WriteVarBytes(&b, 0, node.AuthSigBytes)
if err != nil {
return err
}
if len(node.ExtraOpaqueData) > MaxAllowedExtraOpaqueBytes {
return ErrTooManyExtraOpaqueBytes(len(node.ExtraOpaqueData))
}
err = wire.WriteVarBytes(&b, 0, node.ExtraOpaqueData)
if err != nil {
return err
}
if err := aliasBucket.Put(nodePub, []byte(node.Alias)); err != nil {
return err
}
// With the alias bucket updated, we'll now update the index that
// tracks the time series of node updates.
var indexKey [8 + 33]byte
byteOrder.PutUint64(indexKey[:8], updateUnix)
copy(indexKey[8:], nodePub)
// If there was already an old index entry for this node, then we'll
// delete the old one before we write the new entry.
if nodeBytes := nodeBucket.Get(nodePub); nodeBytes != nil {
// Extract out the old update time to we can reconstruct the
// prior index key to delete it from the index.
oldUpdateTime := nodeBytes[:8]
var oldIndexKey [8 + 33]byte
copy(oldIndexKey[:8], oldUpdateTime)
copy(oldIndexKey[8:], nodePub)
if err := updateIndex.Delete(oldIndexKey[:]); err != nil {
return err
}
}
if err := updateIndex.Put(indexKey[:], nil); err != nil {
return err
}
return nodeBucket.Put(nodePub, b.Bytes())
}
func fetchLightningNode(nodeBucket kvdb.RBucket,
nodePub []byte) (models.LightningNode, error) {
nodeBytes := nodeBucket.Get(nodePub)
if nodeBytes == nil {
return models.LightningNode{}, ErrGraphNodeNotFound
}
nodeReader := bytes.NewReader(nodeBytes)
return deserializeLightningNode(nodeReader)
}
func deserializeLightningNodeCacheable(r io.Reader) (route.Vertex,
*lnwire.FeatureVector, error) {
var (
pubKey route.Vertex
features = lnwire.EmptyFeatureVector()
nodeScratch [8]byte
)
// Skip ahead:
// - LastUpdate (8 bytes)
if _, err := r.Read(nodeScratch[:]); err != nil {
return pubKey, nil, err
}
if _, err := io.ReadFull(r, pubKey[:]); err != nil {
return pubKey, nil, err
}
// Read the node announcement flag.
if _, err := r.Read(nodeScratch[:2]); err != nil {
return pubKey, nil, err
}
hasNodeAnn := byteOrder.Uint16(nodeScratch[:2])
// The rest of the data is optional, and will only be there if we got a
// node announcement for this node.
if hasNodeAnn == 0 {
return pubKey, features, nil
}
// We did get a node announcement for this node, so we'll have the rest
// of the data available.
var rgb uint8
if err := binary.Read(r, byteOrder, &rgb); err != nil {
return pubKey, nil, err
}
if err := binary.Read(r, byteOrder, &rgb); err != nil {
return pubKey, nil, err
}
if err := binary.Read(r, byteOrder, &rgb); err != nil {
return pubKey, nil, err
}
if _, err := wire.ReadVarString(r, 0); err != nil {
return pubKey, nil, err
}
if err := features.Decode(r); err != nil {
return pubKey, nil, err
}
return pubKey, features, nil
}
func deserializeLightningNode(r io.Reader) (models.LightningNode, error) {
var (
node models.LightningNode
scratch [8]byte
err error
)
// Always populate a feature vector, even if we don't have a node
// announcement and short circuit below.
node.Features = lnwire.EmptyFeatureVector()
if _, err := r.Read(scratch[:]); err != nil {
return models.LightningNode{}, err
}
unix := int64(byteOrder.Uint64(scratch[:]))
node.LastUpdate = time.Unix(unix, 0)
if _, err := io.ReadFull(r, node.PubKeyBytes[:]); err != nil {
return models.LightningNode{}, err
}
if _, err := r.Read(scratch[:2]); err != nil {
return models.LightningNode{}, err
}
hasNodeAnn := byteOrder.Uint16(scratch[:2])
if hasNodeAnn == 1 {
node.HaveNodeAnnouncement = true
} else {
node.HaveNodeAnnouncement = false
}
// The rest of the data is optional, and will only be there if we got a
// node announcement for this node.
if !node.HaveNodeAnnouncement {
return node, nil
}
// We did get a node announcement for this node, so we'll have the rest
// of the data available.
if err := binary.Read(r, byteOrder, &node.Color.R); err != nil {
return models.LightningNode{}, err
}
if err := binary.Read(r, byteOrder, &node.Color.G); err != nil {
return models.LightningNode{}, err
}
if err := binary.Read(r, byteOrder, &node.Color.B); err != nil {
return models.LightningNode{}, err
}
node.Alias, err = wire.ReadVarString(r, 0)
if err != nil {
return models.LightningNode{}, err
}
err = node.Features.Decode(r)
if err != nil {
return models.LightningNode{}, err
}
if _, err := r.Read(scratch[:2]); err != nil {
return models.LightningNode{}, err
}
numAddresses := int(byteOrder.Uint16(scratch[:2]))
var addresses []net.Addr
for i := 0; i < numAddresses; i++ {
address, err := DeserializeAddr(r)
if err != nil {
return models.LightningNode{}, err
}
addresses = append(addresses, address)
}
node.Addresses = addresses
node.AuthSigBytes, err = wire.ReadVarBytes(r, 0, 80, "sig")
if err != nil {
return models.LightningNode{}, err
}
// We'll try and see if there are any opaque bytes left, if not, then
// we'll ignore the EOF error and return the node as is.
node.ExtraOpaqueData, err = wire.ReadVarBytes(
r, 0, MaxAllowedExtraOpaqueBytes, "blob",
)
switch {
case errors.Is(err, io.ErrUnexpectedEOF):
case errors.Is(err, io.EOF):
case err != nil:
return models.LightningNode{}, err
}
return node, nil
}
func putChanEdgeInfo(edgeIndex kvdb.RwBucket,
edgeInfo *models.ChannelEdgeInfo, chanID [8]byte) error {
var b bytes.Buffer
if _, err := b.Write(edgeInfo.NodeKey1Bytes[:]); err != nil {
return err
}
if _, err := b.Write(edgeInfo.NodeKey2Bytes[:]); err != nil {
return err
}
if _, err := b.Write(edgeInfo.BitcoinKey1Bytes[:]); err != nil {
return err
}
if _, err := b.Write(edgeInfo.BitcoinKey2Bytes[:]); err != nil {
return err
}
if err := wire.WriteVarBytes(&b, 0, edgeInfo.Features); err != nil {
return err
}
authProof := edgeInfo.AuthProof
var nodeSig1, nodeSig2, bitcoinSig1, bitcoinSig2 []byte
if authProof != nil {
nodeSig1 = authProof.NodeSig1Bytes
nodeSig2 = authProof.NodeSig2Bytes
bitcoinSig1 = authProof.BitcoinSig1Bytes
bitcoinSig2 = authProof.BitcoinSig2Bytes
}
if err := wire.WriteVarBytes(&b, 0, nodeSig1); err != nil {
return err
}
if err := wire.WriteVarBytes(&b, 0, nodeSig2); err != nil {
return err
}
if err := wire.WriteVarBytes(&b, 0, bitcoinSig1); err != nil {
return err
}
if err := wire.WriteVarBytes(&b, 0, bitcoinSig2); err != nil {
return err
}
if err := WriteOutpoint(&b, &edgeInfo.ChannelPoint); err != nil {
return err
}
err := binary.Write(&b, byteOrder, uint64(edgeInfo.Capacity))
if err != nil {
return err
}
if _, err := b.Write(chanID[:]); err != nil {
return err
}
if _, err := b.Write(edgeInfo.ChainHash[:]); err != nil {
return err
}
if len(edgeInfo.ExtraOpaqueData) > MaxAllowedExtraOpaqueBytes {
return ErrTooManyExtraOpaqueBytes(len(edgeInfo.ExtraOpaqueData))
}
err = wire.WriteVarBytes(&b, 0, edgeInfo.ExtraOpaqueData)
if err != nil {
return err
}
return edgeIndex.Put(chanID[:], b.Bytes())
}
func fetchChanEdgeInfo(edgeIndex kvdb.RBucket,
chanID []byte) (models.ChannelEdgeInfo, error) {
edgeInfoBytes := edgeIndex.Get(chanID)
if edgeInfoBytes == nil {
return models.ChannelEdgeInfo{}, ErrEdgeNotFound
}
edgeInfoReader := bytes.NewReader(edgeInfoBytes)
return deserializeChanEdgeInfo(edgeInfoReader)
}
func deserializeChanEdgeInfo(r io.Reader) (models.ChannelEdgeInfo, error) {
var (
err error
edgeInfo models.ChannelEdgeInfo
)
if _, err := io.ReadFull(r, edgeInfo.NodeKey1Bytes[:]); err != nil {
return models.ChannelEdgeInfo{}, err
}
if _, err := io.ReadFull(r, edgeInfo.NodeKey2Bytes[:]); err != nil {
return models.ChannelEdgeInfo{}, err
}
if _, err := io.ReadFull(r, edgeInfo.BitcoinKey1Bytes[:]); err != nil {
return models.ChannelEdgeInfo{}, err
}
if _, err := io.ReadFull(r, edgeInfo.BitcoinKey2Bytes[:]); err != nil {
return models.ChannelEdgeInfo{}, err
}
edgeInfo.Features, err = wire.ReadVarBytes(r, 0, 900, "features")
if err != nil {
return models.ChannelEdgeInfo{}, err
}
proof := &models.ChannelAuthProof{}
proof.NodeSig1Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs")
if err != nil {
return models.ChannelEdgeInfo{}, err
}
proof.NodeSig2Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs")
if err != nil {
return models.ChannelEdgeInfo{}, err
}
proof.BitcoinSig1Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs")
if err != nil {
return models.ChannelEdgeInfo{}, err
}
proof.BitcoinSig2Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs")
if err != nil {
return models.ChannelEdgeInfo{}, err
}
if !proof.IsEmpty() {
edgeInfo.AuthProof = proof
}
edgeInfo.ChannelPoint = wire.OutPoint{}
if err := ReadOutpoint(r, &edgeInfo.ChannelPoint); err != nil {
return models.ChannelEdgeInfo{}, err
}
if err := binary.Read(r, byteOrder, &edgeInfo.Capacity); err != nil {
return models.ChannelEdgeInfo{}, err
}
if err := binary.Read(r, byteOrder, &edgeInfo.ChannelID); err != nil {
return models.ChannelEdgeInfo{}, err
}
if _, err := io.ReadFull(r, edgeInfo.ChainHash[:]); err != nil {
return models.ChannelEdgeInfo{}, err
}
// We'll try and see if there are any opaque bytes left, if not, then
// we'll ignore the EOF error and return the edge as is.
edgeInfo.ExtraOpaqueData, err = wire.ReadVarBytes(
r, 0, MaxAllowedExtraOpaqueBytes, "blob",
)
switch {
case errors.Is(err, io.ErrUnexpectedEOF):
case errors.Is(err, io.EOF):
case err != nil:
return models.ChannelEdgeInfo{}, err
}
return edgeInfo, nil
}
func putChanEdgePolicy(edges kvdb.RwBucket, edge *models.ChannelEdgePolicy,
from, to []byte) error {
var edgeKey [33 + 8]byte
copy(edgeKey[:], from)
byteOrder.PutUint64(edgeKey[33:], edge.ChannelID)
var b bytes.Buffer
if err := serializeChanEdgePolicy(&b, edge, to); err != nil {
return err
}
// Before we write out the new edge, we'll create a new entry in the
// update index in order to keep it fresh.
updateUnix := uint64(edge.LastUpdate.Unix())
var indexKey [8 + 8]byte
byteOrder.PutUint64(indexKey[:8], updateUnix)
byteOrder.PutUint64(indexKey[8:], edge.ChannelID)
updateIndex, err := edges.CreateBucketIfNotExists(edgeUpdateIndexBucket)
if err != nil {
return err
}
// If there was already an entry for this edge, then we'll need to
// delete the old one to ensure we don't leave around any after-images.
// An unknown policy value does not have a update time recorded, so
// it also does not need to be removed.
if edgeBytes := edges.Get(edgeKey[:]); edgeBytes != nil &&
!bytes.Equal(edgeBytes, unknownPolicy) {
// In order to delete the old entry, we'll need to obtain the
// *prior* update time in order to delete it. To do this, we'll
// need to deserialize the existing policy within the database
// (now outdated by the new one), and delete its corresponding
// entry within the update index. We'll ignore any
// ErrEdgePolicyOptionalFieldNotFound error, as we only need
// the channel ID and update time to delete the entry.
// TODO(halseth): get rid of these invalid policies in a
// migration.
oldEdgePolicy, err := deserializeChanEdgePolicy(
bytes.NewReader(edgeBytes),
)
if err != nil &&
!errors.Is(err, ErrEdgePolicyOptionalFieldNotFound) {
return err
}
oldUpdateTime := uint64(oldEdgePolicy.LastUpdate.Unix())
var oldIndexKey [8 + 8]byte
byteOrder.PutUint64(oldIndexKey[:8], oldUpdateTime)
byteOrder.PutUint64(oldIndexKey[8:], edge.ChannelID)
if err := updateIndex.Delete(oldIndexKey[:]); err != nil {
return err
}
}
if err := updateIndex.Put(indexKey[:], nil); err != nil {
return err
}
err = updateEdgePolicyDisabledIndex(
edges, edge.ChannelID,
edge.ChannelFlags&lnwire.ChanUpdateDirection > 0,
edge.IsDisabled(),
)
if err != nil {
return err
}
return edges.Put(edgeKey[:], b.Bytes())
}
// updateEdgePolicyDisabledIndex is used to update the disabledEdgePolicyIndex
// bucket by either add a new disabled ChannelEdgePolicy or remove an existing
// one.
// The direction represents the direction of the edge and disabled is used for
// deciding whether to remove or add an entry to the bucket.
// In general a channel is disabled if two entries for the same chanID exist
// in this bucket.
// Maintaining the bucket this way allows a fast retrieval of disabled
// channels, for example when prune is needed.
func updateEdgePolicyDisabledIndex(edges kvdb.RwBucket, chanID uint64,
direction bool, disabled bool) error {
var disabledEdgeKey [8 + 1]byte
byteOrder.PutUint64(disabledEdgeKey[0:], chanID)
if direction {
disabledEdgeKey[8] = 1
}
disabledEdgePolicyIndex, err := edges.CreateBucketIfNotExists(
disabledEdgePolicyBucket,
)
if err != nil {
return err
}
if disabled {
return disabledEdgePolicyIndex.Put(disabledEdgeKey[:], []byte{})
}
return disabledEdgePolicyIndex.Delete(disabledEdgeKey[:])
}
// putChanEdgePolicyUnknown marks the edge policy as unknown
// in the edges bucket.
func putChanEdgePolicyUnknown(edges kvdb.RwBucket, channelID uint64,
from []byte) error {
var edgeKey [33 + 8]byte
copy(edgeKey[:], from)
byteOrder.PutUint64(edgeKey[33:], channelID)
if edges.Get(edgeKey[:]) != nil {
return fmt.Errorf("cannot write unknown policy for channel %v "+
" when there is already a policy present", channelID)
}
return edges.Put(edgeKey[:], unknownPolicy)
}
func fetchChanEdgePolicy(edges kvdb.RBucket, chanID []byte,
nodePub []byte) (*models.ChannelEdgePolicy, error) {
var edgeKey [33 + 8]byte
copy(edgeKey[:], nodePub)
copy(edgeKey[33:], chanID)
edgeBytes := edges.Get(edgeKey[:])
if edgeBytes == nil {
return nil, ErrEdgeNotFound
}
// No need to deserialize unknown policy.
if bytes.Equal(edgeBytes, unknownPolicy) {
return nil, nil
}
edgeReader := bytes.NewReader(edgeBytes)
ep, err := deserializeChanEdgePolicy(edgeReader)
switch {
// If the db policy was missing an expected optional field, we return
// nil as if the policy was unknown.
case errors.Is(err, ErrEdgePolicyOptionalFieldNotFound):
return nil, nil
case err != nil:
return nil, err
}
return ep, nil
}
func fetchChanEdgePolicies(edgeIndex kvdb.RBucket, edges kvdb.RBucket,
chanID []byte) (*models.ChannelEdgePolicy, *models.ChannelEdgePolicy,
error) {
edgeInfo := edgeIndex.Get(chanID)
if edgeInfo == nil {
return nil, nil, fmt.Errorf("%w: chanID=%x", ErrEdgeNotFound,
chanID)
}
// The first node is contained within the first half of the edge
// information. We only propagate the error here and below if it's
// something other than edge non-existence.
node1Pub := edgeInfo[:33]
edge1, err := fetchChanEdgePolicy(edges, chanID, node1Pub)
if err != nil {
return nil, nil, fmt.Errorf("%w: node1Pub=%x", ErrEdgeNotFound,
node1Pub)
}
// Similarly, the second node is contained within the latter
// half of the edge information.
node2Pub := edgeInfo[33:66]
edge2, err := fetchChanEdgePolicy(edges, chanID, node2Pub)
if err != nil {
return nil, nil, fmt.Errorf("%w: node2Pub=%x", ErrEdgeNotFound,
node2Pub)
}
return edge1, edge2, nil
}
func serializeChanEdgePolicy(w io.Writer, edge *models.ChannelEdgePolicy,
to []byte) error {
err := wire.WriteVarBytes(w, 0, edge.SigBytes)
if err != nil {
return err
}
if err := binary.Write(w, byteOrder, edge.ChannelID); err != nil {
return err
}
var scratch [8]byte
updateUnix := uint64(edge.LastUpdate.Unix())
byteOrder.PutUint64(scratch[:], updateUnix)
if _, err := w.Write(scratch[:]); err != nil {
return err
}
if err := binary.Write(w, byteOrder, edge.MessageFlags); err != nil {
return err
}
if err := binary.Write(w, byteOrder, edge.ChannelFlags); err != nil {
return err
}
if err := binary.Write(w, byteOrder, edge.TimeLockDelta); err != nil {
return err
}
if err := binary.Write(w, byteOrder, uint64(edge.MinHTLC)); err != nil {
return err
}
err = binary.Write(w, byteOrder, uint64(edge.FeeBaseMSat))
if err != nil {
return err
}
err = binary.Write(
w, byteOrder, uint64(edge.FeeProportionalMillionths),
)
if err != nil {
return err
}
if _, err := w.Write(to); err != nil {
return err
}
// If the max_htlc field is present, we write it. To be compatible with
// older versions that wasn't aware of this field, we write it as part
// of the opaque data.
// TODO(halseth): clean up when moving to TLV.
var opaqueBuf bytes.Buffer
if edge.MessageFlags.HasMaxHtlc() {
err := binary.Write(&opaqueBuf, byteOrder, uint64(edge.MaxHTLC))
if err != nil {
return err
}
}
if len(edge.ExtraOpaqueData) > MaxAllowedExtraOpaqueBytes {
return ErrTooManyExtraOpaqueBytes(len(edge.ExtraOpaqueData))
}
if _, err := opaqueBuf.Write(edge.ExtraOpaqueData); err != nil {
return err
}
if err := wire.WriteVarBytes(w, 0, opaqueBuf.Bytes()); err != nil {
return err
}
return nil
}
func deserializeChanEdgePolicy(r io.Reader) (*models.ChannelEdgePolicy, error) {
// Deserialize the policy. Note that in case an optional field is not
// found, both an error and a populated policy object are returned.
edge, deserializeErr := deserializeChanEdgePolicyRaw(r)
if deserializeErr != nil &&
!errors.Is(deserializeErr, ErrEdgePolicyOptionalFieldNotFound) {
return nil, deserializeErr
}
return edge, deserializeErr
}
func deserializeChanEdgePolicyRaw(r io.Reader) (*models.ChannelEdgePolicy,
error) {
edge := &models.ChannelEdgePolicy{}
var err error
edge.SigBytes, err = wire.ReadVarBytes(r, 0, 80, "sig")
if err != nil {
return nil, err
}
if err := binary.Read(r, byteOrder, &edge.ChannelID); err != nil {
return nil, err
}
var scratch [8]byte
if _, err := r.Read(scratch[:]); err != nil {
return nil, err
}
unix := int64(byteOrder.Uint64(scratch[:]))
edge.LastUpdate = time.Unix(unix, 0)
if err := binary.Read(r, byteOrder, &edge.MessageFlags); err != nil {
return nil, err
}
if err := binary.Read(r, byteOrder, &edge.ChannelFlags); err != nil {
return nil, err
}
if err := binary.Read(r, byteOrder, &edge.TimeLockDelta); err != nil {
return nil, err
}
var n uint64
if err := binary.Read(r, byteOrder, &n); err != nil {
return nil, err
}
edge.MinHTLC = lnwire.MilliSatoshi(n)
if err := binary.Read(r, byteOrder, &n); err != nil {
return nil, err
}
edge.FeeBaseMSat = lnwire.MilliSatoshi(n)
if err := binary.Read(r, byteOrder, &n); err != nil {
return nil, err
}
edge.FeeProportionalMillionths = lnwire.MilliSatoshi(n)
if _, err := r.Read(edge.ToNode[:]); err != nil {
return nil, err
}
// We'll try and see if there are any opaque bytes left, if not, then
// we'll ignore the EOF error and return the edge as is.
edge.ExtraOpaqueData, err = wire.ReadVarBytes(
r, 0, MaxAllowedExtraOpaqueBytes, "blob",
)
switch {
case errors.Is(err, io.ErrUnexpectedEOF):
case errors.Is(err, io.EOF):
case err != nil:
return nil, err
}
// See if optional fields are present.
if edge.MessageFlags.HasMaxHtlc() {
// The max_htlc field should be at the beginning of the opaque
// bytes.
opq := edge.ExtraOpaqueData
// If the max_htlc field is not present, it might be old data
// stored before this field was validated. We'll return the
// edge along with an error.
if len(opq) < 8 {
return edge, ErrEdgePolicyOptionalFieldNotFound
}
maxHtlc := byteOrder.Uint64(opq[:8])
edge.MaxHTLC = lnwire.MilliSatoshi(maxHtlc)
// Exclude the parsed field from the rest of the opaque data.
edge.ExtraOpaqueData = opq[8:]
}
return edge, nil
}
// chanGraphNodeTx is an implementation of the NodeRTx interface backed by the
// KVStore and a kvdb.RTx.
type chanGraphNodeTx struct {
tx kvdb.RTx
db *KVStore
node *models.LightningNode
}
// A compile-time constraint to ensure chanGraphNodeTx implements the NodeRTx
// interface.
var _ NodeRTx = (*chanGraphNodeTx)(nil)
func newChanGraphNodeTx(tx kvdb.RTx, db *KVStore,
node *models.LightningNode) *chanGraphNodeTx {
return &chanGraphNodeTx{
tx: tx,
db: db,
node: node,
}
}
// Node returns the raw information of the node.
//
// NOTE: This is a part of the NodeRTx interface.
func (c *chanGraphNodeTx) Node() *models.LightningNode {
return c.node
}
// FetchNode fetches the node with the given pub key under the same transaction
// used to fetch the current node. The returned node is also a NodeRTx and any
// operations on that NodeRTx will also be done under the same transaction.
//
// NOTE: This is a part of the NodeRTx interface.
func (c *chanGraphNodeTx) FetchNode(nodePub route.Vertex) (NodeRTx, error) {
node, err := c.db.FetchLightningNodeTx(c.tx, nodePub)
if err != nil {
return nil, err
}
return newChanGraphNodeTx(c.tx, c.db, node), nil
}
// ForEachChannel can be used to iterate over the node's channels under
// the same transaction used to fetch the node.
//
// NOTE: This is a part of the NodeRTx interface.
func (c *chanGraphNodeTx) ForEachChannel(f func(*models.ChannelEdgeInfo,
*models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error) error {
return c.db.ForEachNodeChannelTx(c.tx, c.node.PubKeyBytes,
func(_ kvdb.RTx, info *models.ChannelEdgeInfo, policy1,
policy2 *models.ChannelEdgePolicy) error {
return f(info, policy1, policy2)
},
)
}
// MakeTestGraph creates a new instance of the KVStore for testing
// purposes.
func MakeTestGraph(t testing.TB, modifiers ...KVStoreOptionModifier) (
*ChannelGraph, error) {
opts := DefaultOptions()
for _, modifier := range modifiers {
modifier(opts)
}
// Next, create KVStore for the first time.
backend, backendCleanup, err := kvdb.GetTestBackend(t.TempDir(), "cgr")
if err != nil {
backendCleanup()
return nil, err
}
graph, err := NewChannelGraph(&Config{
KVDB: backend,
KVStoreOpts: modifiers,
})
if err != nil {
backendCleanup()
return nil, err
}
require.NoError(t, graph.Start())
t.Cleanup(func() {
_ = backend.Close()
backendCleanup()
require.NoError(t, graph.Stop())
})
return graph, nil
}
package graphdb
import (
"github.com/btcsuite/btclog"
"github.com/lightningnetwork/lnd/build"
)
// Subsystem defines the logging code for this subsystem.
const Subsystem = "GRDB"
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package models
import (
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
const (
// feeRateParts is the total number of parts used to express fee rates.
feeRateParts = 1e6
)
// CachedEdgePolicy is a struct that only caches the information of a
// ChannelEdgePolicy that we actually use for pathfinding and therefore need to
// store in the cache.
type CachedEdgePolicy struct {
// ChannelID is the unique channel ID for the channel. The first 3
// bytes are the block height, the next 3 the index within the block,
// and the last 2 bytes are the output index for the channel.
ChannelID uint64
// MessageFlags is a bitfield which indicates the presence of optional
// fields (like max_htlc) in the policy.
MessageFlags lnwire.ChanUpdateMsgFlags
// ChannelFlags is a bitfield which signals the capabilities of the
// channel as well as the directed edge this update applies to.
ChannelFlags lnwire.ChanUpdateChanFlags
// TimeLockDelta is the number of blocks this node will subtract from
// the expiry of an incoming HTLC. This value expresses the time buffer
// the node would like to HTLC exchanges.
TimeLockDelta uint16
// MinHTLC is the smallest value HTLC this node will forward, expressed
// in millisatoshi.
MinHTLC lnwire.MilliSatoshi
// MaxHTLC is the largest value HTLC this node will forward, expressed
// in millisatoshi.
MaxHTLC lnwire.MilliSatoshi
// FeeBaseMSat is the base HTLC fee that will be charged for forwarding
// ANY HTLC, expressed in mSAT's.
FeeBaseMSat lnwire.MilliSatoshi
// FeeProportionalMillionths is the rate that the node will charge for
// HTLCs for each millionth of a satoshi forwarded.
FeeProportionalMillionths lnwire.MilliSatoshi
// ToNodePubKey is a function that returns the to node of a policy.
// Since we only ever store the inbound policy, this is always the node
// that we query the channels for in ForEachChannel(). Therefore, we can
// save a lot of space by not storing this information in the memory and
// instead just set this function when we copy the policy from cache in
// ForEachChannel().
ToNodePubKey func() route.Vertex
// ToNodeFeatures are the to node's features. They are never set while
// the edge is in the cache, only on the copy that is returned in
// ForEachChannel().
ToNodeFeatures *lnwire.FeatureVector
}
// ComputeFee computes the fee to forward an HTLC of `amt` milli-satoshis over
// the passed active payment channel. This value is currently computed as
// specified in BOLT07, but will likely change in the near future.
func (c *CachedEdgePolicy) ComputeFee(
amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {
return c.FeeBaseMSat + (amt*c.FeeProportionalMillionths)/feeRateParts
}
// NewCachedPolicy turns a full policy into a minimal one that can be cached.
func NewCachedPolicy(policy *ChannelEdgePolicy) *CachedEdgePolicy {
return &CachedEdgePolicy{
ChannelID: policy.ChannelID,
MessageFlags: policy.MessageFlags,
ChannelFlags: policy.ChannelFlags,
TimeLockDelta: policy.TimeLockDelta,
MinHTLC: policy.MinHTLC,
MaxHTLC: policy.MaxHTLC,
FeeBaseMSat: policy.FeeBaseMSat,
FeeProportionalMillionths: policy.FeeProportionalMillionths,
}
}
package models
import (
"encoding/binary"
"fmt"
"io"
"github.com/lightningnetwork/lnd/lnwire"
)
const (
// serializedCircuitKeyLen is the exact length needed to encode a
// serialized CircuitKey.
serializedCircuitKeyLen = 16
)
var (
// ErrInvalidCircuitKeyLen signals that a circuit key could not be
// decoded because the byte slice is of an invalid length.
ErrInvalidCircuitKeyLen = fmt.Errorf("length of serialized circuit " +
"key must be 16 bytes")
)
// CircuitKey is used by a channel to uniquely identify the HTLCs it receives
// from the switch, and is used to purge our in-memory state of HTLCs that have
// already been processed by a link. Two list of CircuitKeys are included in
// each CommitDiff to allow a link to determine which in-memory htlcs directed
// the opening and closing of circuits in the switch's circuit map.
type CircuitKey struct {
// ChanID is the short chanid indicating the HTLC's origin.
//
// NOTE: It is fine for this value to be blank, as this indicates a
// locally-sourced payment.
ChanID lnwire.ShortChannelID
// HtlcID is the unique htlc index predominately assigned by links,
// though can also be assigned by switch in the case of locally-sourced
// payments.
HtlcID uint64
}
// SetBytes deserializes the given bytes into this CircuitKey.
func (k *CircuitKey) SetBytes(bs []byte) error {
if len(bs) != serializedCircuitKeyLen {
return ErrInvalidCircuitKeyLen
}
k.ChanID = lnwire.NewShortChanIDFromInt(
binary.BigEndian.Uint64(bs[:8]))
k.HtlcID = binary.BigEndian.Uint64(bs[8:])
return nil
}
// Bytes returns the serialized bytes for this circuit key.
func (k CircuitKey) Bytes() []byte {
bs := make([]byte, serializedCircuitKeyLen)
binary.BigEndian.PutUint64(bs[:8], k.ChanID.ToUint64())
binary.BigEndian.PutUint64(bs[8:], k.HtlcID)
return bs
}
// Encode writes a CircuitKey to the provided io.Writer.
func (k *CircuitKey) Encode(w io.Writer) error {
var scratch [serializedCircuitKeyLen]byte
binary.BigEndian.PutUint64(scratch[:8], k.ChanID.ToUint64())
binary.BigEndian.PutUint64(scratch[8:], k.HtlcID)
_, err := w.Write(scratch[:])
return err
}
// Decode reads a CircuitKey from the provided io.Reader.
func (k *CircuitKey) Decode(r io.Reader) error {
var scratch [serializedCircuitKeyLen]byte
if _, err := io.ReadFull(r, scratch[:]); err != nil {
return err
}
k.ChanID = lnwire.NewShortChanIDFromInt(
binary.BigEndian.Uint64(scratch[:8]))
k.HtlcID = binary.BigEndian.Uint64(scratch[8:])
return nil
}
// String returns a string representation of the CircuitKey.
func (k CircuitKey) String() string {
return fmt.Sprintf("(Chan ID=%s, HTLC ID=%d)", k.ChanID, k.HtlcID)
}
// ForwardingPolicy describes the set of constraints that a given ChannelLink
// is to adhere to when forwarding HTLC's. For each incoming HTLC, this set of
// constraints will be consulted in order to ensure that adequate fees are
// paid, and our time-lock parameters are respected. In the event that an
// incoming HTLC violates any of these constraints, it is to be _rejected_ with
// the error possibly carrying along a ChannelUpdate message that includes the
// latest policy.
type ForwardingPolicy struct {
// MinHTLCOut is the smallest HTLC that is to be forwarded.
MinHTLCOut lnwire.MilliSatoshi
// MaxHTLC is the largest HTLC that is to be forwarded.
MaxHTLC lnwire.MilliSatoshi
// BaseFee is the base fee, expressed in milli-satoshi that must be
// paid for each incoming HTLC. This field, combined with FeeRate is
// used to compute the required fee for a given HTLC.
BaseFee lnwire.MilliSatoshi
// FeeRate is the fee rate, expressed in milli-satoshi that must be
// paid for each incoming HTLC. This field combined with BaseFee is
// used to compute the required fee for a given HTLC.
FeeRate lnwire.MilliSatoshi
// InboundFee is the fee that must be paid for incoming HTLCs.
InboundFee InboundFee
// TimeLockDelta is the absolute time-lock value, expressed in blocks,
// that will be subtracted from an incoming HTLC's timelock value to
// create the time-lock value for the forwarded outgoing HTLC. The
// following constraint MUST hold for an HTLC to be forwarded:
//
// * incomingHtlc.timeLock - timeLockDelta = fwdInfo.OutgoingCTLV
//
// where fwdInfo is the forwarding information extracted from the
// per-hop payload of the incoming HTLC's onion packet.
TimeLockDelta uint32
// TODO(roasbeef): add fee module inside of switch
}
package models
import "github.com/btcsuite/btcd/btcec/v2/ecdsa"
// ChannelAuthProof is the authentication proof (the signature portion) for a
// channel. Using the four signatures contained in the struct, and some
// auxiliary knowledge (the funding script, node identities, and outpoint) nodes
// on the network are able to validate the authenticity and existence of a
// channel. Each of these signatures signs the following digest: chanID ||
// nodeID1 || nodeID2 || bitcoinKey1|| bitcoinKey2 || 2-byte-feature-len ||
// features.
type ChannelAuthProof struct {
// nodeSig1 is a cached instance of the first node signature.
nodeSig1 *ecdsa.Signature
// NodeSig1Bytes are the raw bytes of the first node signature encoded
// in DER format.
NodeSig1Bytes []byte
// nodeSig2 is a cached instance of the second node signature.
nodeSig2 *ecdsa.Signature
// NodeSig2Bytes are the raw bytes of the second node signature
// encoded in DER format.
NodeSig2Bytes []byte
// bitcoinSig1 is a cached instance of the first bitcoin signature.
bitcoinSig1 *ecdsa.Signature
// BitcoinSig1Bytes are the raw bytes of the first bitcoin signature
// encoded in DER format.
BitcoinSig1Bytes []byte
// bitcoinSig2 is a cached instance of the second bitcoin signature.
bitcoinSig2 *ecdsa.Signature
// BitcoinSig2Bytes are the raw bytes of the second bitcoin signature
// encoded in DER format.
BitcoinSig2Bytes []byte
}
// Node1Sig is the signature using the identity key of the node that is first
// in a lexicographical ordering of the serialized public keys of the two nodes
// that created the channel.
//
// NOTE: By having this method to access an attribute, we ensure we only need
// to fully deserialize the signature if absolutely necessary.
func (c *ChannelAuthProof) Node1Sig() (*ecdsa.Signature, error) {
if c.nodeSig1 != nil {
return c.nodeSig1, nil
}
sig, err := ecdsa.ParseSignature(c.NodeSig1Bytes)
if err != nil {
return nil, err
}
c.nodeSig1 = sig
return sig, nil
}
// Node2Sig is the signature using the identity key of the node that is second
// in a lexicographical ordering of the serialized public keys of the two nodes
// that created the channel.
//
// NOTE: By having this method to access an attribute, we ensure we only need
// to fully deserialize the signature if absolutely necessary.
func (c *ChannelAuthProof) Node2Sig() (*ecdsa.Signature, error) {
if c.nodeSig2 != nil {
return c.nodeSig2, nil
}
sig, err := ecdsa.ParseSignature(c.NodeSig2Bytes)
if err != nil {
return nil, err
}
c.nodeSig2 = sig
return sig, nil
}
// BitcoinSig1 is the signature using the public key of the first node that was
// used in the channel's multi-sig output.
//
// NOTE: By having this method to access an attribute, we ensure we only need
// to fully deserialize the signature if absolutely necessary.
func (c *ChannelAuthProof) BitcoinSig1() (*ecdsa.Signature, error) {
if c.bitcoinSig1 != nil {
return c.bitcoinSig1, nil
}
sig, err := ecdsa.ParseSignature(c.BitcoinSig1Bytes)
if err != nil {
return nil, err
}
c.bitcoinSig1 = sig
return sig, nil
}
// BitcoinSig2 is the signature using the public key of the second node that
// was used in the channel's multi-sig output.
//
// NOTE: By having this method to access an attribute, we ensure we only need
// to fully deserialize the signature if absolutely necessary.
func (c *ChannelAuthProof) BitcoinSig2() (*ecdsa.Signature, error) {
if c.bitcoinSig2 != nil {
return c.bitcoinSig2, nil
}
sig, err := ecdsa.ParseSignature(c.BitcoinSig2Bytes)
if err != nil {
return nil, err
}
c.bitcoinSig2 = sig
return sig, nil
}
// IsEmpty check is the authentication proof is empty Proof is empty if at
// least one of the signatures are equal to nil.
func (c *ChannelAuthProof) IsEmpty() bool {
return len(c.NodeSig1Bytes) == 0 ||
len(c.NodeSig2Bytes) == 0 ||
len(c.BitcoinSig1Bytes) == 0 ||
len(c.BitcoinSig2Bytes) == 0
}
package models
import (
"bytes"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/fn/v2"
)
// ChannelEdgeInfo represents a fully authenticated channel along with all its
// unique attributes. Once an authenticated channel announcement has been
// processed on the network, then an instance of ChannelEdgeInfo encapsulating
// the channels attributes is stored. The other portions relevant to routing
// policy of a channel are stored within a ChannelEdgePolicy for each direction
// of the channel.
type ChannelEdgeInfo struct {
// ChannelID is the unique channel ID for the channel. The first 3
// bytes are the block height, the next 3 the index within the block,
// and the last 2 bytes are the output index for the channel.
ChannelID uint64
// ChainHash is the hash that uniquely identifies the chain that this
// channel was opened within.
//
// TODO(roasbeef): need to modify db keying for multi-chain
// * must add chain hash to prefix as well
ChainHash chainhash.Hash
// NodeKey1Bytes is the raw public key of the first node.
NodeKey1Bytes [33]byte
nodeKey1 *btcec.PublicKey
// NodeKey2Bytes is the raw public key of the first node.
NodeKey2Bytes [33]byte
nodeKey2 *btcec.PublicKey
// BitcoinKey1Bytes is the raw public key of the first node.
BitcoinKey1Bytes [33]byte
bitcoinKey1 *btcec.PublicKey
// BitcoinKey2Bytes is the raw public key of the first node.
BitcoinKey2Bytes [33]byte
bitcoinKey2 *btcec.PublicKey
// Features is an opaque byte slice that encodes the set of channel
// specific features that this channel edge supports.
Features []byte
// AuthProof is the authentication proof for this channel. This proof
// contains a set of signatures binding four identities, which attests
// to the legitimacy of the advertised channel.
AuthProof *ChannelAuthProof
// ChannelPoint is the funding outpoint of the channel. This can be
// used to uniquely identify the channel within the channel graph.
ChannelPoint wire.OutPoint
// Capacity is the total capacity of the channel, this is determined by
// the value output in the outpoint that created this channel.
Capacity btcutil.Amount
// FundingScript holds the script of the channel's funding transaction.
//
// NOTE: this is not currently persisted and so will not be present if
// the edge object is loaded from the database.
FundingScript fn.Option[[]byte]
// ExtraOpaqueData is the set of data that was appended to this
// message, some of which we may not actually know how to iterate or
// parse. By holding onto this data, we ensure that we're able to
// properly validate the set of signatures that cover these new fields,
// and ensure we're able to make upgrades to the network in a forwards
// compatible manner.
ExtraOpaqueData []byte
}
// AddNodeKeys is a setter-like method that can be used to replace the set of
// keys for the target ChannelEdgeInfo.
func (c *ChannelEdgeInfo) AddNodeKeys(nodeKey1, nodeKey2, bitcoinKey1,
bitcoinKey2 *btcec.PublicKey) {
c.nodeKey1 = nodeKey1
copy(c.NodeKey1Bytes[:], c.nodeKey1.SerializeCompressed())
c.nodeKey2 = nodeKey2
copy(c.NodeKey2Bytes[:], nodeKey2.SerializeCompressed())
c.bitcoinKey1 = bitcoinKey1
copy(c.BitcoinKey1Bytes[:], c.bitcoinKey1.SerializeCompressed())
c.bitcoinKey2 = bitcoinKey2
copy(c.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
}
// NodeKey1 is the identity public key of the "first" node that was involved in
// the creation of this channel. A node is considered "first" if the
// lexicographical ordering the its serialized public key is "smaller" than
// that of the other node involved in channel creation.
//
// NOTE: By having this method to access an attribute, we ensure we only need
// to fully deserialize the pubkey if absolutely necessary.
func (c *ChannelEdgeInfo) NodeKey1() (*btcec.PublicKey, error) {
if c.nodeKey1 != nil {
return c.nodeKey1, nil
}
key, err := btcec.ParsePubKey(c.NodeKey1Bytes[:])
if err != nil {
return nil, err
}
c.nodeKey1 = key
return key, nil
}
// NodeKey2 is the identity public key of the "second" node that was involved in
// the creation of this channel. A node is considered "second" if the
// lexicographical ordering the its serialized public key is "larger" than that
// of the other node involved in channel creation.
//
// NOTE: By having this method to access an attribute, we ensure we only need
// to fully deserialize the pubkey if absolutely necessary.
func (c *ChannelEdgeInfo) NodeKey2() (*btcec.PublicKey, error) {
if c.nodeKey2 != nil {
return c.nodeKey2, nil
}
key, err := btcec.ParsePubKey(c.NodeKey2Bytes[:])
if err != nil {
return nil, err
}
c.nodeKey2 = key
return key, nil
}
// BitcoinKey1 is the Bitcoin multi-sig key belonging to the first node, that
// was involved in the funding transaction that originally created the channel
// that this struct represents.
//
// NOTE: By having this method to access an attribute, we ensure we only need
// to fully deserialize the pubkey if absolutely necessary.
func (c *ChannelEdgeInfo) BitcoinKey1() (*btcec.PublicKey, error) {
if c.bitcoinKey1 != nil {
return c.bitcoinKey1, nil
}
key, err := btcec.ParsePubKey(c.BitcoinKey1Bytes[:])
if err != nil {
return nil, err
}
c.bitcoinKey1 = key
return key, nil
}
// BitcoinKey2 is the Bitcoin multi-sig key belonging to the second node, that
// was involved in the funding transaction that originally created the channel
// that this struct represents.
//
// NOTE: By having this method to access an attribute, we ensure we only need
// to fully deserialize the pubkey if absolutely necessary.
func (c *ChannelEdgeInfo) BitcoinKey2() (*btcec.PublicKey, error) {
if c.bitcoinKey2 != nil {
return c.bitcoinKey2, nil
}
key, err := btcec.ParsePubKey(c.BitcoinKey2Bytes[:])
if err != nil {
return nil, err
}
c.bitcoinKey2 = key
return key, nil
}
// OtherNodeKeyBytes returns the node key bytes of the other end of the channel.
func (c *ChannelEdgeInfo) OtherNodeKeyBytes(thisNodeKey []byte) (
[33]byte, error) {
switch {
case bytes.Equal(c.NodeKey1Bytes[:], thisNodeKey):
return c.NodeKey2Bytes, nil
case bytes.Equal(c.NodeKey2Bytes[:], thisNodeKey):
return c.NodeKey1Bytes, nil
default:
return [33]byte{}, fmt.Errorf("node not participating in " +
"this channel")
}
}
package models
import (
"fmt"
"time"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/lightningnetwork/lnd/lnwire"
)
// ChannelEdgePolicy represents a *directed* edge within the channel graph. For
// each channel in the database, there are two distinct edges: one for each
// possible direction of travel along the channel. The edges themselves hold
// information concerning fees, and minimum time-lock information which is
// utilized during path finding.
type ChannelEdgePolicy struct {
// SigBytes is the raw bytes of the signature of the channel edge
// policy. We'll only parse these if the caller needs to access the
// signature for validation purposes. Do not set SigBytes directly, but
// use SetSigBytes instead to make sure that the cache is invalidated.
SigBytes []byte
// sig is a cached fully parsed signature.
sig *ecdsa.Signature
// ChannelID is the unique channel ID for the channel. The first 3
// bytes are the block height, the next 3 the index within the block,
// and the last 2 bytes are the output index for the channel.
ChannelID uint64
// LastUpdate is the last time an authenticated edge for this channel
// was received.
LastUpdate time.Time
// MessageFlags is a bitfield which indicates the presence of optional
// fields (like max_htlc) in the policy.
MessageFlags lnwire.ChanUpdateMsgFlags
// ChannelFlags is a bitfield which signals the capabilities of the
// channel as well as the directed edge this update applies to.
ChannelFlags lnwire.ChanUpdateChanFlags
// TimeLockDelta is the number of blocks this node will subtract from
// the expiry of an incoming HTLC. This value expresses the time buffer
// the node would like to HTLC exchanges.
TimeLockDelta uint16
// MinHTLC is the smallest value HTLC this node will forward, expressed
// in millisatoshi.
MinHTLC lnwire.MilliSatoshi
// MaxHTLC is the largest value HTLC this node will forward, expressed
// in millisatoshi.
MaxHTLC lnwire.MilliSatoshi
// FeeBaseMSat is the base HTLC fee that will be charged for forwarding
// ANY HTLC, expressed in mSAT's.
FeeBaseMSat lnwire.MilliSatoshi
// FeeProportionalMillionths is the rate that the node will charge for
// HTLCs for each millionth of a satoshi forwarded.
FeeProportionalMillionths lnwire.MilliSatoshi
// ToNode is the public key of the node that this directed edge leads
// to. Using this pub key, the channel graph can further be traversed.
ToNode [33]byte
// ExtraOpaqueData is the set of data that was appended to this
// message, some of which we may not actually know how to iterate or
// parse. By holding onto this data, we ensure that we're able to
// properly validate the set of signatures that cover these new fields,
// and ensure we're able to make upgrades to the network in a forwards
// compatible manner.
ExtraOpaqueData lnwire.ExtraOpaqueData
}
// Signature is a channel announcement signature, which is needed for proper
// edge policy announcement.
//
// NOTE: By having this method to access an attribute, we ensure we only need
// to fully deserialize the signature if absolutely necessary.
func (c *ChannelEdgePolicy) Signature() (*ecdsa.Signature, error) {
if c.sig != nil {
return c.sig, nil
}
sig, err := ecdsa.ParseSignature(c.SigBytes)
if err != nil {
return nil, err
}
c.sig = sig
return sig, nil
}
// SetSigBytes updates the signature and invalidates the cached parsed
// signature.
func (c *ChannelEdgePolicy) SetSigBytes(sig []byte) {
c.SigBytes = sig
c.sig = nil
}
// IsDisabled determines whether the edge has the disabled bit set.
func (c *ChannelEdgePolicy) IsDisabled() bool {
return c.ChannelFlags.IsDisabled()
}
// ComputeFee computes the fee to forward an HTLC of `amt` milli-satoshis over
// the passed active payment channel. This value is currently computed as
// specified in BOLT07, but will likely change in the near future.
func (c *ChannelEdgePolicy) ComputeFee(
amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {
return c.FeeBaseMSat + (amt*c.FeeProportionalMillionths)/feeRateParts
}
// String returns a human-readable version of the channel edge policy.
func (c *ChannelEdgePolicy) String() string {
return fmt.Sprintf("ChannelID=%v, MessageFlags=%v, ChannelFlags=%v, "+
"LastUpdate=%v", c.ChannelID, c.MessageFlags, c.ChannelFlags,
c.LastUpdate)
}
package models
import "github.com/lightningnetwork/lnd/lnwire"
const (
// maxFeeRate is the maximum fee rate that we allow. It is set to allow
// a variable fee component of up to 10x the payment amount.
maxFeeRate = 10 * feeRateParts
)
type InboundFee struct {
Base int32
Rate int32
}
// NewInboundFeeFromWire constructs an inbound fee structure from a wire fee.
func NewInboundFeeFromWire(fee lnwire.Fee) InboundFee {
return InboundFee{
Base: fee.BaseFee,
Rate: fee.FeeRate,
}
}
// ToWire converts the inbound fee to a wire fee structure.
func (i *InboundFee) ToWire() lnwire.Fee {
return lnwire.Fee{
BaseFee: i.Base,
FeeRate: i.Rate,
}
}
// CalcFee calculates what the inbound fee should minimally be for forwarding
// the given amount. This amount is the total of the outgoing amount plus the
// outbound fee, which is what the inbound fee is based on.
func (i *InboundFee) CalcFee(amt lnwire.MilliSatoshi) int64 {
fee := int64(i.Base)
rate := int64(i.Rate)
// Cap the rate to prevent overflows.
switch {
case rate > maxFeeRate:
rate = maxFeeRate
case rate < -maxFeeRate:
rate = -maxFeeRate
}
// Calculate proportional component. To keep the integer math simple,
// positive fees are rounded down while negative fees are rounded up.
fee += rate * int64(amt) / feeRateParts
return fee
}
package models
import (
"fmt"
"image/color"
"net"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/lightningnetwork/lnd/lnwire"
)
// LightningNode represents an individual vertex/node within the channel graph.
// A node is connected to other nodes by one or more channel edges emanating
// from it. As the graph is directed, a node will also have an incoming edge
// attached to it for each outgoing edge.
type LightningNode struct {
// PubKeyBytes is the raw bytes of the public key of the target node.
PubKeyBytes [33]byte
pubKey *btcec.PublicKey
// HaveNodeAnnouncement indicates whether we received a node
// announcement for this particular node. If true, the remaining fields
// will be set, if false only the PubKey is known for this node.
HaveNodeAnnouncement bool
// LastUpdate is the last time the vertex information for this node has
// been updated.
LastUpdate time.Time
// Address is the TCP address this node is reachable over.
Addresses []net.Addr
// Color is the selected color for the node.
Color color.RGBA
// Alias is a nick-name for the node. The alias can be used to confirm
// a node's identity or to serve as a short ID for an address book.
Alias string
// AuthSigBytes is the raw signature under the advertised public key
// which serves to authenticate the attributes announced by this node.
AuthSigBytes []byte
// Features is the list of protocol features supported by this node.
Features *lnwire.FeatureVector
// ExtraOpaqueData is the set of data that was appended to this
// message, some of which we may not actually know how to iterate or
// parse. By holding onto this data, we ensure that we're able to
// properly validate the set of signatures that cover these new fields,
// and ensure we're able to make upgrades to the network in a forwards
// compatible manner.
ExtraOpaqueData []byte
// TODO(roasbeef): discovery will need storage to keep it's last IP
// address and re-announce if interface changes?
// TODO(roasbeef): add update method and fetch?
}
// PubKey is the node's long-term identity public key. This key will be used to
// authenticated any advertisements/updates sent by the node.
//
// NOTE: By having this method to access an attribute, we ensure we only need
// to fully deserialize the pubkey if absolutely necessary.
func (l *LightningNode) PubKey() (*btcec.PublicKey, error) {
if l.pubKey != nil {
return l.pubKey, nil
}
key, err := btcec.ParsePubKey(l.PubKeyBytes[:])
if err != nil {
return nil, err
}
l.pubKey = key
return key, nil
}
// AuthSig is a signature under the advertised public key which serves to
// authenticate the attributes announced by this node.
//
// NOTE: By having this method to access an attribute, we ensure we only need
// to fully deserialize the signature if absolutely necessary.
func (l *LightningNode) AuthSig() (*ecdsa.Signature, error) {
return ecdsa.ParseSignature(l.AuthSigBytes)
}
// AddPubKey is a setter-link method that can be used to swap out the public
// key for a node.
func (l *LightningNode) AddPubKey(key *btcec.PublicKey) {
l.pubKey = key
copy(l.PubKeyBytes[:], key.SerializeCompressed())
}
// NodeAnnouncement retrieves the latest node announcement of the node.
func (l *LightningNode) NodeAnnouncement(signed bool) (*lnwire.NodeAnnouncement,
error) {
if !l.HaveNodeAnnouncement {
return nil, fmt.Errorf("node does not have node announcement")
}
alias, err := lnwire.NewNodeAlias(l.Alias)
if err != nil {
return nil, err
}
nodeAnn := &lnwire.NodeAnnouncement{
Features: l.Features.RawFeatureVector,
NodeID: l.PubKeyBytes,
RGBColor: l.Color,
Alias: alias,
Addresses: l.Addresses,
Timestamp: uint32(l.LastUpdate.Unix()),
ExtraOpaqueData: l.ExtraOpaqueData,
}
if !signed {
return nodeAnn, nil
}
sig, err := lnwire.NewSigFromECDSARawSignature(l.AuthSigBytes)
if err != nil {
return nil, err
}
nodeAnn.Signature = sig
return nodeAnn, nil
}
// NodeFromWireAnnouncement creates a LightningNode instance from an
// lnwire.NodeAnnouncement message.
func NodeFromWireAnnouncement(msg *lnwire.NodeAnnouncement) *LightningNode {
timestamp := time.Unix(int64(msg.Timestamp), 0)
features := lnwire.NewFeatureVector(msg.Features, lnwire.Features)
return &LightningNode{
HaveNodeAnnouncement: true,
LastUpdate: timestamp,
Addresses: msg.Addresses,
PubKeyBytes: msg.NodeID,
Alias: msg.Alias.String(),
AuthSigBytes: msg.Signature.ToSignatureBytes(),
Features: features,
Color: msg.RGBColor,
ExtraOpaqueData: msg.ExtraOpaqueData,
}
}
package graphdb
import (
"fmt"
"image/color"
"net"
"sync"
"sync/atomic"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwire"
)
// topologyManager holds all the fields required to manage the network topology
// subscriptions and notifications.
type topologyManager struct {
// ntfnClientCounter is an atomic counter that's used to assign unique
// notification client IDs to new clients.
ntfnClientCounter atomic.Uint64
// topologyUpdate is a channel that carries new topology updates
// messages from outside the ChannelGraph to be processed by the
// networkHandler.
topologyUpdate chan any
// topologyClients maps a client's unique notification ID to a
// topologyClient client that contains its notification dispatch
// channel.
topologyClients *lnutils.SyncMap[uint64, *topologyClient]
// ntfnClientUpdates is a channel that's used to send new updates to
// topology notification clients to the ChannelGraph. Updates either
// add a new notification client, or cancel notifications for an
// existing client.
ntfnClientUpdates chan *topologyClientUpdate
}
// newTopologyManager creates a new instance of the topologyManager.
func newTopologyManager() *topologyManager {
return &topologyManager{
topologyUpdate: make(chan any),
topologyClients: &lnutils.SyncMap[uint64, *topologyClient]{},
ntfnClientUpdates: make(chan *topologyClientUpdate),
}
}
// TopologyClient represents an intent to receive notifications from the
// channel router regarding changes to the topology of the channel graph. The
// TopologyChanges channel will be sent upon with new updates to the channel
// graph in real-time as they're encountered.
type TopologyClient struct {
// TopologyChanges is a receive only channel that new channel graph
// updates will be sent over.
//
// TODO(roasbeef): chan for each update type instead?
TopologyChanges <-chan *TopologyChange
// Cancel is a function closure that should be executed when the client
// wishes to cancel their notification intent. Doing so allows the
// ChannelRouter to free up resources.
Cancel func()
}
// topologyClientUpdate is a message sent to the channel router to either
// register a new topology client or re-register an existing client.
type topologyClientUpdate struct {
// cancel indicates if the update to the client is cancelling an
// existing client's notifications. If not then this update will be to
// register a new set of notifications.
cancel bool
// clientID is the unique identifier for this client. Any further
// updates (deleting or adding) to this notification client will be
// dispatched according to the target clientID.
clientID uint64
// ntfnChan is a *send-only* channel in which notifications should be
// sent over from router -> client.
ntfnChan chan<- *TopologyChange
}
// SubscribeTopology returns a new topology client which can be used by the
// caller to receive notifications whenever a change in the channel graph
// topology occurs. Changes that will be sent at notifications include: new
// nodes appearing, node updating their attributes, new channels, channels
// closing, and updates in the routing policies of a channel's directed edges.
func (c *ChannelGraph) SubscribeTopology() (*TopologyClient, error) {
// If the router is not yet started, return an error to avoid a
// deadlock waiting for it to handle the subscription request.
if !c.started.Load() {
return nil, fmt.Errorf("router not started")
}
// We'll first atomically obtain the next ID for this client from the
// incrementing client ID counter.
clientID := c.ntfnClientCounter.Add(1)
log.Debugf("New graph topology client subscription, client %v",
clientID)
ntfnChan := make(chan *TopologyChange, 10)
select {
case c.ntfnClientUpdates <- &topologyClientUpdate{
cancel: false,
clientID: clientID,
ntfnChan: ntfnChan,
}:
case <-c.quit:
return nil, errors.New("ChannelRouter shutting down")
}
return &TopologyClient{
TopologyChanges: ntfnChan,
Cancel: func() {
select {
case c.ntfnClientUpdates <- &topologyClientUpdate{
cancel: true,
clientID: clientID,
}:
case <-c.quit:
return
}
},
}, nil
}
// topologyClient is a data-structure use by the channel router to couple the
// client's notification channel along with a special "exit" channel that can
// be used to cancel all lingering goroutines blocked on a send to the
// notification channel.
type topologyClient struct {
// ntfnChan is a send-only channel that's used to propagate
// notification s from the channel router to an instance of a
// topologyClient client.
ntfnChan chan<- *TopologyChange
// exit is a channel that is used internally by the channel router to
// cancel any active un-consumed goroutine notifications.
exit chan struct{}
wg sync.WaitGroup
}
// notifyTopologyChange notifies all registered clients of a new change in
// graph topology in a non-blocking.
func (c *ChannelGraph) notifyTopologyChange(topologyDiff *TopologyChange) {
// notifyClient is a helper closure that will send topology updates to
// the given client.
notifyClient := func(clientID uint64, client *topologyClient) bool {
client.wg.Add(1)
log.Tracef("Sending topology notification to client=%v, "+
"NodeUpdates=%v, ChannelEdgeUpdates=%v, "+
"ClosedChannels=%v", clientID,
len(topologyDiff.NodeUpdates),
len(topologyDiff.ChannelEdgeUpdates),
len(topologyDiff.ClosedChannels))
go func(t *topologyClient) {
defer t.wg.Done()
select {
// In this case we'll try to send the notification
// directly to the upstream client consumer.
case t.ntfnChan <- topologyDiff:
// If the client cancels the notifications, then we'll
// exit early.
case <-t.exit:
// Similarly, if the ChannelRouter itself exists early,
// then we'll also exit ourselves.
case <-c.quit:
}
}(client)
// Always return true here so the following Range will iterate
// all clients.
return true
}
// Range over the set of active clients, and attempt to send the
// topology updates.
c.topologyClients.Range(notifyClient)
}
// handleTopologyUpdate is responsible for sending any topology changes
// notifications to registered clients.
//
// NOTE: must be run inside goroutine.
func (c *ChannelGraph) handleTopologyUpdate(update any) {
defer c.wg.Done()
topChange := &TopologyChange{}
err := c.addToTopologyChange(topChange, update)
if err != nil {
log.Errorf("unable to update topology change notification: %v",
err)
return
}
if topChange.isEmpty() {
return
}
c.notifyTopologyChange(topChange)
}
// TopologyChange represents a new set of modifications to the channel graph.
// Topology changes will be dispatched in real-time as the ChannelGraph
// validates and process modifications to the authenticated channel graph.
type TopologyChange struct {
// NodeUpdates is a slice of nodes which are either new to the channel
// graph, or have had their attributes updated in an authenticated
// manner.
NodeUpdates []*NetworkNodeUpdate
// ChanelEdgeUpdates is a slice of channel edges which are either newly
// opened and authenticated, or have had their routing policies
// updated.
ChannelEdgeUpdates []*ChannelEdgeUpdate
// ClosedChannels contains a slice of close channel summaries which
// described which block a channel was closed at, and also carry
// supplemental information such as the capacity of the former channel.
ClosedChannels []*ClosedChanSummary
}
// isEmpty returns true if the TopologyChange is empty. A TopologyChange is
// considered empty, if it contains no *new* updates of any type.
func (t *TopologyChange) isEmpty() bool {
return len(t.NodeUpdates) == 0 && len(t.ChannelEdgeUpdates) == 0 &&
len(t.ClosedChannels) == 0
}
// ClosedChanSummary is a summary of a channel that was detected as being
// closed by monitoring the blockchain. Once a channel's funding point has been
// spent, the channel will automatically be marked as closed by the
// ChainNotifier.
//
// TODO(roasbeef): add nodes involved?
type ClosedChanSummary struct {
// ChanID is the short-channel ID which uniquely identifies the
// channel.
ChanID uint64
// Capacity was the total capacity of the channel before it was closed.
Capacity btcutil.Amount
// ClosedHeight is the height in the chain that the channel was closed
// at.
ClosedHeight uint32
// ChanPoint is the funding point, or the multi-sig utxo which
// previously represented the channel.
ChanPoint wire.OutPoint
}
// createCloseSummaries takes in a slice of channels closed at the target block
// height and creates a slice of summaries which of each channel closure.
func createCloseSummaries(blockHeight uint32,
closedChans ...*models.ChannelEdgeInfo) []*ClosedChanSummary {
closeSummaries := make([]*ClosedChanSummary, len(closedChans))
for i, closedChan := range closedChans {
closeSummaries[i] = &ClosedChanSummary{
ChanID: closedChan.ChannelID,
Capacity: closedChan.Capacity,
ClosedHeight: blockHeight,
ChanPoint: closedChan.ChannelPoint,
}
}
return closeSummaries
}
// NetworkNodeUpdate is an update for a node within the Lightning Network. A
// NetworkNodeUpdate is sent out either when a new node joins the network, or a
// node broadcasts a new update with a newer time stamp that supersedes its
// old update. All updates are properly authenticated.
type NetworkNodeUpdate struct {
// Addresses is a slice of all the node's known addresses.
Addresses []net.Addr
// IdentityKey is the identity public key of the target node. This is
// used to encrypt onion blobs as well as to authenticate any new
// updates.
IdentityKey *btcec.PublicKey
// Alias is the alias or nick name of the node.
Alias string
// Color is the node's color in hex code format.
Color string
// Features holds the set of features the node supports.
Features *lnwire.FeatureVector
}
// ChannelEdgeUpdate is an update for a new channel within the ChannelGraph.
// This update is sent out once a new authenticated channel edge is discovered
// within the network. These updates are directional, so if a channel is fully
// public, then there will be two updates sent out: one for each direction
// within the channel. Each update will carry that particular routing edge
// policy for the channel direction.
//
// An edge is a channel in the direction of AdvertisingNode -> ConnectingNode.
type ChannelEdgeUpdate struct {
// ChanID is the unique short channel ID for the channel. This encodes
// where in the blockchain the channel's funding transaction was
// originally confirmed.
ChanID uint64
// ChanPoint is the outpoint which represents the multi-sig funding
// output for the channel.
ChanPoint wire.OutPoint
// Capacity is the capacity of the newly created channel.
Capacity btcutil.Amount
// MinHTLC is the minimum HTLC amount that this channel will forward.
MinHTLC lnwire.MilliSatoshi
// MaxHTLC is the maximum HTLC amount that this channel will forward.
MaxHTLC lnwire.MilliSatoshi
// BaseFee is the base fee that will charged for all HTLC's forwarded
// across the this channel direction.
BaseFee lnwire.MilliSatoshi
// FeeRate is the fee rate that will be shared for all HTLC's forwarded
// across this channel direction.
FeeRate lnwire.MilliSatoshi
// TimeLockDelta is the time-lock expressed in blocks that will be
// added to outgoing HTLC's from incoming HTLC's. This value is the
// difference of the incoming and outgoing HTLC's time-locks routed
// through this hop.
TimeLockDelta uint16
// AdvertisingNode is the node that's advertising this edge.
AdvertisingNode *btcec.PublicKey
// ConnectingNode is the node that the advertising node connects to.
ConnectingNode *btcec.PublicKey
// Disabled, if true, signals that the channel is unavailable to relay
// payments.
Disabled bool
// ExtraOpaqueData is the set of data that was appended to this message
// to fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraOpaqueData lnwire.ExtraOpaqueData
}
// appendTopologyChange appends the passed update message to the passed
// TopologyChange, properly identifying which type of update the message
// constitutes. This function will also fetch any required auxiliary
// information required to create the topology change update from the graph
// database.
func (c *ChannelGraph) addToTopologyChange(update *TopologyChange,
msg any) error {
switch m := msg.(type) {
// Any node announcement maps directly to a NetworkNodeUpdate struct.
// No further data munging or db queries are required.
case *models.LightningNode:
pubKey, err := m.PubKey()
if err != nil {
return err
}
nodeUpdate := &NetworkNodeUpdate{
Addresses: m.Addresses,
IdentityKey: pubKey,
Alias: m.Alias,
Color: EncodeHexColor(m.Color),
Features: m.Features.Clone(),
}
update.NodeUpdates = append(update.NodeUpdates, nodeUpdate)
return nil
// We ignore initial channel announcements as we'll only send out
// updates once the individual edges themselves have been updated.
case *models.ChannelEdgeInfo:
return nil
// Any new ChannelUpdateAnnouncements will generate a corresponding
// ChannelEdgeUpdate notification.
case *models.ChannelEdgePolicy:
// We'll need to fetch the edge's information from the database
// in order to get the information concerning which nodes are
// being connected.
edgeInfo, _, _, err := c.FetchChannelEdgesByID(m.ChannelID)
if err != nil {
return errors.Errorf("unable fetch channel edge: %v",
err)
}
// If the flag is one, then the advertising node is actually
// the second node.
sourceNode := edgeInfo.NodeKey1
connectingNode := edgeInfo.NodeKey2
if m.ChannelFlags&lnwire.ChanUpdateDirection == 1 {
sourceNode = edgeInfo.NodeKey2
connectingNode = edgeInfo.NodeKey1
}
aNode, err := sourceNode()
if err != nil {
return err
}
cNode, err := connectingNode()
if err != nil {
return err
}
edgeUpdate := &ChannelEdgeUpdate{
ChanID: m.ChannelID,
ChanPoint: edgeInfo.ChannelPoint,
TimeLockDelta: m.TimeLockDelta,
Capacity: edgeInfo.Capacity,
MinHTLC: m.MinHTLC,
MaxHTLC: m.MaxHTLC,
BaseFee: m.FeeBaseMSat,
FeeRate: m.FeeProportionalMillionths,
AdvertisingNode: aNode,
ConnectingNode: cNode,
Disabled: m.ChannelFlags&lnwire.ChanUpdateDisabled != 0,
ExtraOpaqueData: m.ExtraOpaqueData,
}
// TODO(roasbeef): add bit to toggle
update.ChannelEdgeUpdates = append(update.ChannelEdgeUpdates,
edgeUpdate)
return nil
default:
return fmt.Errorf("unable to add to topology change, "+
"unknown message type %T", msg)
}
}
// EncodeHexColor takes a color and returns it in hex code format.
func EncodeHexColor(color color.RGBA) string {
return fmt.Sprintf("#%02x%02x%02x", color.R, color.G, color.B)
}
package graphdb
import "time"
const (
// DefaultRejectCacheSize is the default number of rejectCacheEntries to
// cache for use in the rejection cache of incoming gossip traffic. This
// produces a cache size of around 1MB.
DefaultRejectCacheSize = 50000
// DefaultChannelCacheSize is the default number of ChannelEdges cached
// in order to reply to gossip queries. This produces a cache size of
// around 40MB.
DefaultChannelCacheSize = 20000
// DefaultPreAllocCacheNumNodes is the default number of channels we
// assume for mainnet for pre-allocating the graph cache. As of
// September 2021, there currently are 14k nodes in a strictly pruned
// graph, so we choose a number that is slightly higher.
DefaultPreAllocCacheNumNodes = 15000
)
// chanGraphOptions holds parameters for tuning and customizing the
// ChannelGraph.
type chanGraphOptions struct {
// useGraphCache denotes whether the in-memory graph cache should be
// used or a fallback version that uses the underlying database for
// path finding.
useGraphCache bool
// preAllocCacheNumNodes is the number of nodes we expect to be in the
// graph cache, so we can pre-allocate the map accordingly.
preAllocCacheNumNodes int
}
// defaultChanGraphOptions returns a new chanGraphOptions instance populated
// with default values.
func defaultChanGraphOptions() *chanGraphOptions {
return &chanGraphOptions{
useGraphCache: true,
preAllocCacheNumNodes: DefaultPreAllocCacheNumNodes,
}
}
// ChanGraphOption describes the signature of a functional option that can be
// used to customize a ChannelGraph instance.
type ChanGraphOption func(*chanGraphOptions)
// WithUseGraphCache sets whether the in-memory graph cache should be used.
func WithUseGraphCache(use bool) ChanGraphOption {
return func(o *chanGraphOptions) {
o.useGraphCache = use
}
}
// WithPreAllocCacheNumNodes sets the number of nodes we expect to be in the
// graph cache, so we can pre-allocate the map accordingly.
func WithPreAllocCacheNumNodes(n int) ChanGraphOption {
return func(o *chanGraphOptions) {
o.preAllocCacheNumNodes = n
}
}
// KVStoreOptions holds parameters for tuning and customizing a graph.DB.
type KVStoreOptions struct {
// RejectCacheSize is the maximum number of rejectCacheEntries to hold
// in the rejection cache.
RejectCacheSize int
// ChannelCacheSize is the maximum number of ChannelEdges to hold in the
// channel cache.
ChannelCacheSize int
// BatchCommitInterval is the maximum duration the batch schedulers will
// wait before attempting to commit a pending set of updates.
BatchCommitInterval time.Duration
// NoMigration specifies that underlying backend was opened in read-only
// mode and migrations shouldn't be performed. This can be useful for
// applications that use the channeldb package as a library.
NoMigration bool
}
// DefaultOptions returns a KVStoreOptions populated with default values.
func DefaultOptions() *KVStoreOptions {
return &KVStoreOptions{
RejectCacheSize: DefaultRejectCacheSize,
ChannelCacheSize: DefaultChannelCacheSize,
NoMigration: false,
}
}
// KVStoreOptionModifier is a function signature for modifying the default
// KVStoreOptions.
type KVStoreOptionModifier func(*KVStoreOptions)
// WithRejectCacheSize sets the RejectCacheSize to n.
func WithRejectCacheSize(n int) KVStoreOptionModifier {
return func(o *KVStoreOptions) {
o.RejectCacheSize = n
}
}
// WithChannelCacheSize sets the ChannelCacheSize to n.
func WithChannelCacheSize(n int) KVStoreOptionModifier {
return func(o *KVStoreOptions) {
o.ChannelCacheSize = n
}
}
// WithBatchCommitInterval sets the batch commit interval for the interval batch
// schedulers.
func WithBatchCommitInterval(interval time.Duration) KVStoreOptionModifier {
return func(o *KVStoreOptions) {
o.BatchCommitInterval = interval
}
}
package graphdb
// rejectFlags is a compact representation of various metadata stored by the
// reject cache about a particular channel.
type rejectFlags uint8
const (
// rejectFlagExists is a flag indicating whether the channel exists,
// i.e. the channel is open and has a recent channel update. If this
// flag is not set, the channel is either a zombie or unknown.
rejectFlagExists rejectFlags = 1 << iota
// rejectFlagZombie is a flag indicating whether the channel is a
// zombie, i.e. the channel is open but has no recent channel updates.
rejectFlagZombie
)
// packRejectFlags computes the rejectFlags corresponding to the passed boolean
// values indicating whether the edge exists or is a zombie.
func packRejectFlags(exists, isZombie bool) rejectFlags {
var flags rejectFlags
if exists {
flags |= rejectFlagExists
}
if isZombie {
flags |= rejectFlagZombie
}
return flags
}
// unpack returns the booleans packed into the rejectFlags. The first indicates
// if the edge exists in our graph, the second indicates if the edge is a
// zombie.
func (f rejectFlags) unpack() (bool, bool) {
return f&rejectFlagExists == rejectFlagExists,
f&rejectFlagZombie == rejectFlagZombie
}
// rejectCacheEntry caches frequently accessed information about a channel,
// including the timestamps of its latest edge policies and whether or not the
// channel exists in the graph.
type rejectCacheEntry struct {
upd1Time int64
upd2Time int64
flags rejectFlags
}
// rejectCache is an in-memory cache used to improve the performance of
// HasChannelEdge. It caches information about the whether or channel exists, as
// well as the most recent timestamps for each policy (if they exists).
type rejectCache struct {
n int
edges map[uint64]rejectCacheEntry
}
// newRejectCache creates a new rejectCache with maximum capacity of n entries.
func newRejectCache(n int) *rejectCache {
return &rejectCache{
n: n,
edges: make(map[uint64]rejectCacheEntry, n),
}
}
// get returns the entry from the cache for chanid, if it exists.
func (c *rejectCache) get(chanid uint64) (rejectCacheEntry, bool) {
entry, ok := c.edges[chanid]
return entry, ok
}
// insert adds the entry to the reject cache. If an entry for chanid already
// exists, it will be replaced with the new entry. If the entry doesn't exists,
// it will be inserted to the cache, performing a random eviction if the cache
// is at capacity.
func (c *rejectCache) insert(chanid uint64, entry rejectCacheEntry) {
// If entry exists, replace it.
if _, ok := c.edges[chanid]; ok {
c.edges[chanid] = entry
return
}
// Otherwise, evict an entry at random and insert.
if len(c.edges) == c.n {
for id := range c.edges {
delete(c.edges, id)
break
}
}
c.edges[chanid] = entry
}
// remove deletes an entry for chanid from the cache, if it exists.
func (c *rejectCache) remove(chanid uint64) {
delete(c.edges, chanid)
}
package graph
import "github.com/go-errors/errors"
// ErrorCode is used to represent the various errors that can occur within this
// package.
type ErrorCode uint8
const (
// ErrOutdated is returned when the routing update already have
// been applied, or a newer update is already known.
ErrOutdated ErrorCode = iota
// ErrIgnored is returned when the update have been ignored because
// this update can't bring us something new, or because a node
// announcement was given for node not found in any channel.
ErrIgnored
)
// Error is a structure that represent the error inside the graph package,
// this structure carries additional information about error code in order to
// be able distinguish errors outside of the current package.
type Error struct {
err *errors.Error
code ErrorCode
}
// Error represents errors as the string
// NOTE: Part of the error interface.
func (e *Error) Error() string {
return e.err.Error()
}
// A compile time check to ensure Error implements the error interface.
var _ error = (*Error)(nil)
// NewErrf creates a Error by the given error formatted description and
// its corresponding error code.
func NewErrf(code ErrorCode, format string, a ...interface{}) *Error {
return &Error{
code: code,
err: errors.Errorf(format, a...),
}
}
// IsError is a helper function which is needed to have ability to check that
// returned error has specific error code.
func IsError(e interface{}, codes ...ErrorCode) bool {
err, ok := e.(*Error)
if !ok {
return false
}
for _, code := range codes {
if err.code == code {
return true
}
}
return false
}
package graph
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This means the
// package will not perform any logging by default until the caller requests
// it.
var log btclog.Logger
const Subsystem = "GRPH"
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled by
// default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info. This
// should be used in preference to SetLogWriter if the caller is also using
// btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package graph
import (
"fmt"
"sync"
"time"
)
// builderStats is a struct that tracks various updates to the graph and
// facilitates aggregate logging of the statistics.
type builderStats struct {
numChannels uint32
numUpdates uint32
numNodes uint32
lastReset time.Time
mu sync.RWMutex
}
// incNumEdges increments the number of discovered edges.
func (g *builderStats) incNumEdgesDiscovered() {
g.mu.Lock()
g.numChannels++
g.mu.Unlock()
}
// incNumUpdates increments the number of channel updates processed.
func (g *builderStats) incNumChannelUpdates() {
g.mu.Lock()
g.numUpdates++
g.mu.Unlock()
}
// incNumNodeUpdates increments the number of node updates processed.
func (g *builderStats) incNumNodeUpdates() {
g.mu.Lock()
g.numNodes++
g.mu.Unlock()
}
// Empty returns true if all stats are zero.
func (g *builderStats) Empty() bool {
g.mu.RLock()
isEmpty := g.numChannels == 0 &&
g.numUpdates == 0 &&
g.numNodes == 0
g.mu.RUnlock()
return isEmpty
}
// Reset clears any stats and sets the lastReset field to now.
func (g *builderStats) Reset() {
g.mu.Lock()
g.numChannels = 0
g.numUpdates = 0
g.numNodes = 0
g.lastReset = time.Now()
g.mu.Unlock()
}
// String returns a human-readable description of the stats.
func (g *builderStats) String() string {
g.mu.RLock()
str := fmt.Sprintf("Processed channels=%d updates=%d nodes=%d in "+
"last %v", g.numChannels, g.numUpdates, g.numNodes,
time.Since(g.lastReset))
g.mu.RUnlock()
return str
}
package htlcswitch
import (
"encoding/binary"
"io"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/lnwire"
)
// EmptyCircuitKey is a default value for an outgoing circuit key returned when
// a circuit's keystone has not been set. Note that this value is invalid for
// use as a keystone, since the outgoing channel id can never be equal to
// sourceHop.
var EmptyCircuitKey CircuitKey
// CircuitKey is a tuple of channel ID and HTLC ID, used to uniquely identify
// HTLCs in a circuit. Circuits are identified primarily by the circuit key of
// the incoming HTLC. However, a circuit may also be referenced by its outgoing
// circuit key after the HTLC has been forwarded via the outgoing link.
type CircuitKey = models.CircuitKey
// PaymentCircuit is used by the switch as placeholder between when the
// switch makes a forwarding decision and the outgoing link determines the
// proper HTLC ID for the local log. After the outgoing HTLC ID has been
// determined, the half circuit will be converted into a full PaymentCircuit.
type PaymentCircuit struct {
// AddRef is the forward reference of the Add update in the incoming
// link's forwarding package. This value is set on the htlcPacket of the
// returned settle/fail so that it can be removed from disk.
AddRef channeldb.AddRef
// Incoming is the circuit key identifying the incoming channel and htlc
// index from which this ADD originates.
Incoming CircuitKey
// Outgoing is the circuit key identifying the outgoing channel, and the
// HTLC index that was used to forward the ADD. It will be nil if this
// circuit's keystone has not been set.
Outgoing *CircuitKey
// PaymentHash used as unique identifier of payment.
PaymentHash [32]byte
// IncomingAmount is the value of the HTLC from the incoming link.
IncomingAmount lnwire.MilliSatoshi
// OutgoingAmount specifies the value of the HTLC leaving the switch,
// either as a payment or forwarded amount.
OutgoingAmount lnwire.MilliSatoshi
// ErrorEncrypter is used to re-encrypt the onion failure before
// sending it back to the originator of the payment.
ErrorEncrypter hop.ErrorEncrypter
// LoadedFromDisk is set true for any circuits loaded after the circuit
// map is reloaded from disk.
//
// NOTE: This value is determined implicitly during a restart. It is not
// persisted, and should never be set outside the circuit map.
LoadedFromDisk bool
}
// HasKeystone returns true if an outgoing link has assigned this circuit's
// outgoing circuit key.
func (c *PaymentCircuit) HasKeystone() bool {
return c.Outgoing != nil
}
// newPaymentCircuit initializes a payment circuit on the heap using the payment
// hash and an in-memory htlc packet.
func newPaymentCircuit(hash *[32]byte, pkt *htlcPacket) *PaymentCircuit {
var addRef channeldb.AddRef
if pkt.sourceRef != nil {
addRef = *pkt.sourceRef
}
return &PaymentCircuit{
AddRef: addRef,
Incoming: CircuitKey{
ChanID: pkt.incomingChanID,
HtlcID: pkt.incomingHTLCID,
},
PaymentHash: *hash,
IncomingAmount: pkt.incomingAmount,
OutgoingAmount: pkt.amount,
ErrorEncrypter: pkt.obfuscator,
}
}
// makePaymentCircuit initializes a payment circuit on the stack using the
// payment hash and an in-memory htlc packet.
func makePaymentCircuit(hash *[32]byte, pkt *htlcPacket) PaymentCircuit {
var addRef channeldb.AddRef
if pkt.sourceRef != nil {
addRef = *pkt.sourceRef
}
return PaymentCircuit{
AddRef: addRef,
Incoming: CircuitKey{
ChanID: pkt.incomingChanID,
HtlcID: pkt.incomingHTLCID,
},
PaymentHash: *hash,
IncomingAmount: pkt.incomingAmount,
OutgoingAmount: pkt.amount,
ErrorEncrypter: pkt.obfuscator,
}
}
// Encode writes a PaymentCircuit to the provided io.Writer.
func (c *PaymentCircuit) Encode(w io.Writer) error {
if err := c.AddRef.Encode(w); err != nil {
return err
}
if err := c.Incoming.Encode(w); err != nil {
return err
}
if _, err := w.Write(c.PaymentHash[:]); err != nil {
return err
}
var scratch [8]byte
binary.BigEndian.PutUint64(scratch[:], uint64(c.IncomingAmount))
if _, err := w.Write(scratch[:]); err != nil {
return err
}
binary.BigEndian.PutUint64(scratch[:], uint64(c.OutgoingAmount))
if _, err := w.Write(scratch[:]); err != nil {
return err
}
// Defaults to EncrypterTypeNone.
var encrypterType hop.EncrypterType
if c.ErrorEncrypter != nil {
encrypterType = c.ErrorEncrypter.Type()
}
err := binary.Write(w, binary.BigEndian, encrypterType)
if err != nil {
return err
}
// Skip encoding of error encrypter if this half add does not have one.
if encrypterType == hop.EncrypterTypeNone {
return nil
}
return c.ErrorEncrypter.Encode(w)
}
// Decode reads a PaymentCircuit from the provided io.Reader.
func (c *PaymentCircuit) Decode(r io.Reader) error {
if err := c.AddRef.Decode(r); err != nil {
return err
}
if err := c.Incoming.Decode(r); err != nil {
return err
}
if _, err := io.ReadFull(r, c.PaymentHash[:]); err != nil {
return err
}
var scratch [8]byte
if _, err := io.ReadFull(r, scratch[:]); err != nil {
return err
}
c.IncomingAmount = lnwire.MilliSatoshi(
binary.BigEndian.Uint64(scratch[:]))
if _, err := io.ReadFull(r, scratch[:]); err != nil {
return err
}
c.OutgoingAmount = lnwire.MilliSatoshi(
binary.BigEndian.Uint64(scratch[:]))
// Read the encrypter type used for this circuit.
var encrypterType hop.EncrypterType
err := binary.Read(r, binary.BigEndian, &encrypterType)
if err != nil {
return err
}
switch encrypterType {
case hop.EncrypterTypeNone:
// No encrypter was provided, such as when the payment is
// locally initiated.
return nil
case hop.EncrypterTypeSphinx:
// Sphinx encrypter was used as this is a forwarded HTLC.
c.ErrorEncrypter = hop.NewSphinxErrorEncrypter()
case hop.EncrypterTypeMock:
// Test encrypter.
c.ErrorEncrypter = NewMockObfuscator()
case hop.EncrypterTypeIntroduction:
c.ErrorEncrypter = hop.NewIntroductionErrorEncrypter()
case hop.EncrypterTypeRelaying:
c.ErrorEncrypter = hop.NewRelayingErrorEncrypter()
default:
return UnknownEncrypterType(encrypterType)
}
return c.ErrorEncrypter.Decode(r)
}
// InKey returns the primary identifier for the circuit corresponding to the
// incoming HTLC.
func (c *PaymentCircuit) InKey() CircuitKey {
return c.Incoming
}
// OutKey returns the keystone identifying the outgoing link and HTLC ID. If the
// circuit hasn't been completed, this method returns an EmptyKeystone, which is
// an invalid outgoing circuit key. Only call this method if HasKeystone returns
// true.
func (c *PaymentCircuit) OutKey() CircuitKey {
if c.Outgoing != nil {
return *c.Outgoing
}
return EmptyCircuitKey
}
package htlcswitch
import (
"bytes"
"fmt"
"sync"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// ErrCorruptedCircuitMap indicates that the on-disk bucketing structure
// has altered since the circuit map instance was initialized.
ErrCorruptedCircuitMap = errors.New("circuit map has been corrupted")
// ErrCircuitNotInHashIndex indicates that a particular circuit did not
// appear in the in-memory hash index.
ErrCircuitNotInHashIndex = errors.New("payment circuit not found in " +
"hash index")
// ErrUnknownCircuit signals that circuit could not be removed from the
// map because it was not found.
ErrUnknownCircuit = errors.New("unknown payment circuit")
// ErrCircuitClosing signals that an htlc has already closed this
// circuit in-memory.
ErrCircuitClosing = errors.New("circuit has already been closed")
// ErrDuplicateCircuit signals that this circuit was previously
// added.
ErrDuplicateCircuit = errors.New("duplicate circuit add")
// ErrUnknownKeystone signals that no circuit was found using the
// outgoing circuit key.
ErrUnknownKeystone = errors.New("unknown circuit keystone")
// ErrDuplicateKeystone signals that this circuit was previously
// assigned a keystone.
ErrDuplicateKeystone = errors.New("cannot add duplicate keystone")
)
// CircuitModifier is a common interface used by channel links to modify the
// contents of the circuit map maintained by the switch.
type CircuitModifier interface {
// OpenCircuits preemptively records a batch keystones that will mark
// currently pending circuits as open. These changes can be rolled back
// on restart if the outgoing Adds do not make it into a commitment
// txn.
OpenCircuits(...Keystone) error
// TrimOpenCircuits removes a channel's open channels with htlc indexes
// above `start`.
TrimOpenCircuits(chanID lnwire.ShortChannelID, start uint64) error
// DeleteCircuits removes the incoming circuit key to remove all
// persistent references to a circuit. Returns a ErrUnknownCircuit if
// any of the incoming keys are not known.
DeleteCircuits(inKeys ...CircuitKey) error
}
// CircuitLookup is a common interface used to lookup information that is stored
// in the circuit map.
type CircuitLookup interface {
// LookupCircuit queries the circuit map for the circuit identified by
// inKey.
LookupCircuit(inKey CircuitKey) *PaymentCircuit
// LookupOpenCircuit queries the circuit map for a circuit identified
// by its outgoing circuit key.
LookupOpenCircuit(outKey CircuitKey) *PaymentCircuit
}
// CircuitFwdActions represents the forwarding decision made by the circuit
// map, and is returned from CommitCircuits. The sequence of circuits provided
// to CommitCircuits is split into three sub-sequences, allowing the caller to
// do an in-order scan, comparing the head of each subsequence, to determine
// the decision made by the circuit map.
type CircuitFwdActions struct {
// Adds is the subsequence of circuits that were successfully committed
// in the circuit map.
Adds []*PaymentCircuit
// Drops is the subsequence of circuits for which no action should be
// done.
Drops []*PaymentCircuit
// Fails is the subsequence of circuits that should be failed back by
// the calling link.
Fails []*PaymentCircuit
}
// CircuitMap is an interface for managing the construction and teardown of
// payment circuits used by the switch.
type CircuitMap interface {
CircuitModifier
CircuitLookup
// CommitCircuits attempts to add the given circuits to the circuit
// map. The list of circuits is split into three distinct
// sub-sequences, corresponding to adds, drops, and fails. Adds should
// be forwarded to the switch, while fails should be failed back
// locally within the calling link.
CommitCircuits(circuit ...*PaymentCircuit) (*CircuitFwdActions, error)
// CloseCircuit marks the circuit identified by `outKey` as closing
// in-memory, which prevents duplicate settles/fails from completing an
// open circuit twice.
CloseCircuit(outKey CircuitKey) (*PaymentCircuit, error)
// FailCircuit is used by locally failed HTLCs to mark the circuit
// identified by `inKey` as closing in-memory, which prevents duplicate
// settles/fails from being accepted for the same circuit.
FailCircuit(inKey CircuitKey) (*PaymentCircuit, error)
// LookupByPaymentHash queries the circuit map and returns all open
// circuits that use the given payment hash.
LookupByPaymentHash(hash [32]byte) []*PaymentCircuit
// NumPending returns the total number of active circuits added by
// CommitCircuits.
NumPending() int
// NumOpen returns the number of circuits with HTLCs that have been
// forwarded via an outgoing link.
NumOpen() int
}
var (
// circuitAddKey is the key used to retrieve the bucket containing
// payment circuits. A circuit records information about how to return
// a packet to the source link, potentially including an error
// encrypter for applying this hop's encryption to the payload in the
// reverse direction.
//
// Bucket hierarchy:
//
// circuitAddKey(root-bucket)
// |
// |-- <incoming-circuit-key>: <encoded bytes of PaymentCircuit>
// |-- <incoming-circuit-key>: <encoded bytes of PaymentCircuit>
// |
// ...
//
circuitAddKey = []byte("circuit-adds")
// circuitKeystoneKey is used to retrieve the bucket containing circuit
// keystones, which are set in place once a forwarded packet is
// assigned an index on an outgoing commitment txn.
//
// Bucket hierarchy:
//
// circuitKeystoneKey(root-bucket)
// |
// |-- <outgoing-circuit-key>: <incoming-circuit-key>
// |-- <outgoing-circuit-key>: <incoming-circuit-key>
// |
// ...
//
circuitKeystoneKey = []byte("circuit-keystones")
)
// circuitMap is a data structure that implements thread safe, persistent
// storage of circuit routing information. The switch consults a circuit map to
// determine where to forward returning HTLC update messages. Circuits are
// always identifiable by their incoming CircuitKey, in addition to their
// outgoing CircuitKey if the circuit is fully-opened.
type circuitMap struct {
cfg *CircuitMapConfig
mtx sync.RWMutex
// pending is an in-memory mapping of all half payment circuits, and is
// kept in sync with the on-disk contents of the circuit map.
pending map[CircuitKey]*PaymentCircuit
// opened is an in-memory mapping of all full payment circuits, which
// is also synchronized with the persistent state of the circuit map.
opened map[CircuitKey]*PaymentCircuit
// closed is an in-memory set of circuits for which the switch has
// received a settle or fail. This precedes the actual deletion of a
// circuit from disk.
closed map[CircuitKey]struct{}
// hashIndex is a volatile index that facilitates fast queries by
// payment hash against the contents of circuits. This index can be
// reconstructed entirely from the set of persisted full circuits on
// startup.
hashIndex map[[32]byte]map[CircuitKey]struct{}
}
// CircuitMapConfig houses the critical interfaces and references necessary to
// parameterize an instance of circuitMap.
type CircuitMapConfig struct {
// DB provides the persistent storage engine for the circuit map.
DB kvdb.Backend
// FetchAllOpenChannels is a function that fetches all currently open
// channels from the channel database.
FetchAllOpenChannels func() ([]*channeldb.OpenChannel, error)
// FetchClosedChannels is a function that fetches all closed channels
// from the channel database.
FetchClosedChannels func(
pendingOnly bool) ([]*channeldb.ChannelCloseSummary, error)
// ExtractErrorEncrypter derives the shared secret used to encrypt
// errors from the obfuscator's ephemeral public key.
ExtractErrorEncrypter hop.ErrorEncrypterExtracter
// CheckResolutionMsg checks whether a given resolution message exists
// for the passed CircuitKey.
CheckResolutionMsg func(outKey *CircuitKey) error
}
// NewCircuitMap creates a new instance of the circuitMap.
func NewCircuitMap(cfg *CircuitMapConfig) (CircuitMap, error) {
cm := &circuitMap{
cfg: cfg,
}
// Initialize the on-disk buckets used by the circuit map.
if err := cm.initBuckets(); err != nil {
return nil, err
}
// Delete old circuits and keystones of closed channels.
if err := cm.cleanClosedChannels(); err != nil {
return nil, err
}
// Load any previously persisted circuit into back into memory.
if err := cm.restoreMemState(); err != nil {
return nil, err
}
// Trim any keystones that were not committed in an outgoing commit txn.
//
// NOTE: This operation will be applied to the persistent state of all
// active channels. Therefore, it must be called before any links are
// created to avoid interfering with normal operation.
if err := cm.trimAllOpenCircuits(); err != nil {
return nil, err
}
return cm, nil
}
// initBuckets ensures that the primary buckets used by the circuit are
// initialized so that we can assume their existence after startup.
func (cm *circuitMap) initBuckets() error {
return kvdb.Update(cm.cfg.DB, func(tx kvdb.RwTx) error {
_, err := tx.CreateTopLevelBucket(circuitKeystoneKey)
if err != nil {
return err
}
_, err = tx.CreateTopLevelBucket(circuitAddKey)
return err
}, func() {})
}
// cleanClosedChannels deletes all circuits and keystones related to closed
// channels. It first reads all the closed channels and caches the ShortChanIDs
// into a map for fast lookup. Then it iterates the circuit bucket and keystone
// bucket and deletes items whose ChanID matches the ShortChanID.
//
// NOTE: this operation can also be built into restoreMemState since the latter
// already opens and iterates the two root buckets, circuitAddKey and
// circuitKeystoneKey. Depending on the size of the buckets, this marginal gain
// may be worth investigating. Atm, for clarity, this operation is wrapped into
// its own function.
func (cm *circuitMap) cleanClosedChannels() error {
log.Infof("Cleaning circuits from disk for closed channels")
// closedChanIDSet stores the short channel IDs for closed channels.
closedChanIDSet := make(map[lnwire.ShortChannelID]struct{})
// circuitKeySet stores the incoming circuit keys of the payment
// circuits that need to be deleted.
circuitKeySet := make(map[CircuitKey]struct{})
// keystoneKeySet stores the outgoing keys of the keystones that need
// to be deleted.
keystoneKeySet := make(map[CircuitKey]struct{})
// isClosedChannel is a helper closure that returns a bool indicating
// the chanID belongs to a closed channel.
isClosedChannel := func(chanID lnwire.ShortChannelID) bool {
// Skip if the channel ID is zero value. This has the effect
// that a zero value incoming or outgoing key will never be
// matched and its corresponding circuits or keystones are not
// deleted.
if chanID.ToUint64() == 0 {
return false
}
_, ok := closedChanIDSet[chanID]
return ok
}
// Find closed channels and cache their ShortChannelIDs into a map.
// This map will be used for looking up relative circuits and keystones.
closedChannels, err := cm.cfg.FetchClosedChannels(false)
if err != nil {
return err
}
for _, closedChannel := range closedChannels {
// Skip if the channel close is pending.
if closedChannel.IsPending {
continue
}
closedChanIDSet[closedChannel.ShortChanID] = struct{}{}
}
log.Debugf("Found %v closed channels", len(closedChanIDSet))
// Exit early if there are no closed channels.
if len(closedChanIDSet) == 0 {
log.Infof("Finished cleaning: no closed channels found, " +
"no actions taken.",
)
return nil
}
// Find the payment circuits and keystones that need to be deleted.
if err := kvdb.View(cm.cfg.DB, func(tx kvdb.RTx) error {
circuitBkt := tx.ReadBucket(circuitAddKey)
if circuitBkt == nil {
return ErrCorruptedCircuitMap
}
keystoneBkt := tx.ReadBucket(circuitKeystoneKey)
if keystoneBkt == nil {
return ErrCorruptedCircuitMap
}
// If a circuit's incoming/outgoing key prefix matches the
// ShortChanID, it will be deleted. However, if the ShortChanID
// of the incoming key is zero, the circuit will be kept as it
// indicates a locally initiated payment.
if err := circuitBkt.ForEach(func(_, v []byte) error {
circuit, err := cm.decodeCircuit(v)
if err != nil {
return err
}
// Check if the incoming channel ID can be found in the
// closed channel ID map.
if !isClosedChannel(circuit.Incoming.ChanID) {
return nil
}
circuitKeySet[circuit.Incoming] = struct{}{}
return nil
}); err != nil {
return err
}
// If a keystone's InKey or OutKey matches the short channel id
// in the closed channel ID map, it will be deleted.
err := keystoneBkt.ForEach(func(k, v []byte) error {
var (
inKey CircuitKey
outKey CircuitKey
)
// Decode the incoming and outgoing circuit keys.
if err := inKey.SetBytes(v); err != nil {
return err
}
if err := outKey.SetBytes(k); err != nil {
return err
}
// Check if the incoming channel ID can be found in the
// closed channel ID map.
if isClosedChannel(inKey.ChanID) {
// If the incoming channel is closed, we can
// skip checking on outgoing channel ID because
// this keystone will be deleted.
keystoneKeySet[outKey] = struct{}{}
// Technically the incoming keys found in
// keystone bucket should be a subset of
// circuit bucket. So a previous loop should
// have this inKey put inside circuitAddKey map
// already. We do this again to be sure the
// circuits are properly cleaned. Even this
// inKey doesn't exist in circuit bucket, we
// are fine as db deletion is a noop.
circuitKeySet[inKey] = struct{}{}
return nil
}
// Check if the outgoing channel ID can be found in the
// closed channel ID map. Notice that we need to store
// the outgoing key because it's used for db query.
//
// NOTE: We skip this if a resolution message can be
// found under the outKey. This means that there is an
// existing resolution message(s) that need to get to
// the incoming links.
if isClosedChannel(outKey.ChanID) {
// Check the resolution message store. A return
// value of nil means we need to skip deleting
// these circuits.
if cm.cfg.CheckResolutionMsg(&outKey) == nil {
return nil
}
keystoneKeySet[outKey] = struct{}{}
// Also update circuitKeySet to mark the
// payment circuit needs to be deleted.
circuitKeySet[inKey] = struct{}{}
}
return nil
})
return err
}, func() {
// Reset the sets.
circuitKeySet = make(map[CircuitKey]struct{})
keystoneKeySet = make(map[CircuitKey]struct{})
}); err != nil {
return err
}
log.Debugf("To be deleted: num_circuits=%v, num_keystones=%v",
len(circuitKeySet), len(keystoneKeySet),
)
numCircuitsDeleted := 0
numKeystonesDeleted := 0
// Delete all the circuits and keystones for closed channels.
if err := kvdb.Update(cm.cfg.DB, func(tx kvdb.RwTx) error {
circuitBkt := tx.ReadWriteBucket(circuitAddKey)
if circuitBkt == nil {
return ErrCorruptedCircuitMap
}
keystoneBkt := tx.ReadWriteBucket(circuitKeystoneKey)
if keystoneBkt == nil {
return ErrCorruptedCircuitMap
}
// Delete the circuit.
for inKey := range circuitKeySet {
if err := circuitBkt.Delete(inKey.Bytes()); err != nil {
return err
}
numCircuitsDeleted++
}
// Delete the keystone using the outgoing key.
for outKey := range keystoneKeySet {
err := keystoneBkt.Delete(outKey.Bytes())
if err != nil {
return err
}
numKeystonesDeleted++
}
return nil
}, func() {}); err != nil {
numCircuitsDeleted = 0
numKeystonesDeleted = 0
return err
}
log.Infof("Finished cleaning: num_closed_channel=%v, "+
"num_circuits=%v, num_keystone=%v",
len(closedChannels), numCircuitsDeleted, numKeystonesDeleted,
)
return nil
}
// restoreMemState loads the contents of the half circuit and full circuit
// buckets from disk and reconstructs the in-memory representation of the
// circuit map. Afterwards, the state of the hash index is reconstructed using
// the recovered set of full circuits. This method will also remove any stray
// keystones, which are those that appear fully-opened, but have no pending
// circuit related to the intended incoming link.
func (cm *circuitMap) restoreMemState() error {
log.Infof("Restoring in-memory circuit state from disk")
var (
opened map[CircuitKey]*PaymentCircuit
pending map[CircuitKey]*PaymentCircuit
)
if err := kvdb.Update(cm.cfg.DB, func(tx kvdb.RwTx) error {
// Restore any of the circuits persisted in the circuit bucket
// back into memory.
circuitBkt := tx.ReadWriteBucket(circuitAddKey)
if circuitBkt == nil {
return ErrCorruptedCircuitMap
}
if err := circuitBkt.ForEach(func(_, v []byte) error {
circuit, err := cm.decodeCircuit(v)
if err != nil {
return err
}
circuit.LoadedFromDisk = true
pending[circuit.Incoming] = circuit
return nil
}); err != nil {
return err
}
// Furthermore, load the keystone bucket and resurrect the
// keystones used in any open circuits.
keystoneBkt := tx.ReadWriteBucket(circuitKeystoneKey)
if keystoneBkt == nil {
return ErrCorruptedCircuitMap
}
var strayKeystones []Keystone
if err := keystoneBkt.ForEach(func(k, v []byte) error {
var (
inKey CircuitKey
outKey = &CircuitKey{}
)
// Decode the incoming and outgoing circuit keys.
if err := inKey.SetBytes(v); err != nil {
return err
}
if err := outKey.SetBytes(k); err != nil {
return err
}
// Retrieve the pending circuit, set its keystone, then
// add it to the opened map.
circuit, ok := pending[inKey]
if ok {
circuit.Outgoing = outKey
opened[*outKey] = circuit
} else {
strayKeystones = append(strayKeystones, Keystone{
InKey: inKey,
OutKey: *outKey,
})
}
return nil
}); err != nil {
return err
}
// If any stray keystones were found, we'll proceed to prune
// them from the circuit map's persistent storage. This may
// manifest on older nodes that had updated channels before
// their short channel id was set properly. We believe this
// issue has been fixed, though this will allow older nodes to
// recover without additional intervention.
for _, strayKeystone := range strayKeystones {
// As a precaution, we will only cleanup keystones
// related to locally-initiated payments. If a
// documented case of stray keystones emerges for
// forwarded payments, this check should be removed, but
// with extreme caution.
if strayKeystone.OutKey.ChanID != hop.Source {
continue
}
log.Infof("Removing stray keystone: %v", strayKeystone)
err := keystoneBkt.Delete(strayKeystone.OutKey.Bytes())
if err != nil {
return err
}
}
return nil
}, func() {
opened = make(map[CircuitKey]*PaymentCircuit)
pending = make(map[CircuitKey]*PaymentCircuit)
}); err != nil {
return err
}
cm.pending = pending
cm.opened = opened
cm.closed = make(map[CircuitKey]struct{})
log.Infof("Payment circuits loaded: num_pending=%v, num_open=%v",
len(pending), len(opened))
// Finally, reconstruct the hash index by running through our set of
// open circuits.
cm.hashIndex = make(map[[32]byte]map[CircuitKey]struct{})
for _, circuit := range opened {
cm.addCircuitToHashIndex(circuit)
}
return nil
}
// decodeCircuit reconstructs an in-memory payment circuit from a byte slice.
// The byte slice is assumed to have been generated by the circuit's Encode
// method. If the decoding is successful, the onion obfuscator will be
// reextracted, since it is not stored in plaintext on disk.
func (cm *circuitMap) decodeCircuit(v []byte) (*PaymentCircuit, error) {
var circuit = &PaymentCircuit{}
circuitReader := bytes.NewReader(v)
if err := circuit.Decode(circuitReader); err != nil {
return nil, err
}
// If the error encrypter is nil, this is locally-source payment so
// there is no encrypter.
if circuit.ErrorEncrypter == nil {
return circuit, nil
}
// Otherwise, we need to reextract the encrypter, so that the shared
// secret is rederived from what was decoded.
err := circuit.ErrorEncrypter.Reextract(
cm.cfg.ExtractErrorEncrypter,
)
if err != nil {
return nil, err
}
return circuit, nil
}
// trimAllOpenCircuits reads the set of active channels from disk and trims
// keystones for any non-pending channels using the next unallocated htlc index.
// This method is intended to be called on startup. Each link will also trim
// it's own circuits upon startup.
//
// NOTE: This operation will be applied to the persistent state of all active
// channels. Therefore, it must be called before any links are created to avoid
// interfering with normal operation.
func (cm *circuitMap) trimAllOpenCircuits() error {
activeChannels, err := cm.cfg.FetchAllOpenChannels()
if err != nil {
return err
}
for _, activeChannel := range activeChannels {
if activeChannel.IsPending {
continue
}
// First, skip any channels that have not been assigned their
// final channel identifier, otherwise we would try to trim
// htlcs belonging to the all-zero, hop.Source ID.
chanID := activeChannel.ShortChanID()
if chanID == hop.Source {
continue
}
// Next, retrieve the next unallocated htlc index, which bounds
// the cutoff of confirmed htlc indexes.
start, err := activeChannel.NextLocalHtlcIndex()
if err != nil {
return err
}
// Finally, remove all pending circuits above at or above the
// next unallocated local htlc indexes. This has the effect of
// reverting any circuits that have either not been locked in,
// or had not been included in a pending commitment.
err = cm.TrimOpenCircuits(chanID, start)
if err != nil {
return err
}
}
return nil
}
// TrimOpenCircuits removes a channel's keystones above the short chan id's
// highest committed htlc index. This has the effect of returning those
// circuits to a half-open state. Since opening of circuits is done in advance
// of actually committing the Add htlcs into a commitment txn, this allows
// circuits to be opened preemptively, since we can roll them back after any
// failures.
func (cm *circuitMap) TrimOpenCircuits(chanID lnwire.ShortChannelID,
start uint64) error {
log.Infof("Trimming open circuits for chan_id=%v, start_htlc_id=%v",
chanID, start)
var trimmedOutKeys []CircuitKey
// Scan forward from the last unacked htlc id, stopping as soon as we
// don't find any more. Outgoing htlc id's must be assigned in order,
// so there should never be disjoint segments of keystones to trim.
cm.mtx.Lock()
for i := start; ; i++ {
outKey := CircuitKey{
ChanID: chanID,
HtlcID: i,
}
circuit, ok := cm.opened[outKey]
if !ok {
break
}
circuit.Outgoing = nil
delete(cm.opened, outKey)
trimmedOutKeys = append(trimmedOutKeys, outKey)
cm.removeCircuitFromHashIndex(circuit)
}
cm.mtx.Unlock()
if len(trimmedOutKeys) == 0 {
return nil
}
return kvdb.Update(cm.cfg.DB, func(tx kvdb.RwTx) error {
keystoneBkt := tx.ReadWriteBucket(circuitKeystoneKey)
if keystoneBkt == nil {
return ErrCorruptedCircuitMap
}
for _, outKey := range trimmedOutKeys {
err := keystoneBkt.Delete(outKey.Bytes())
if err != nil {
return err
}
}
return nil
}, func() {})
}
// LookupCircuit queries the circuit map for the circuit identified by its
// incoming circuit key. Returns nil if there is no such circuit.
func (cm *circuitMap) LookupCircuit(inKey CircuitKey) *PaymentCircuit {
cm.mtx.RLock()
defer cm.mtx.RUnlock()
return cm.pending[inKey]
}
// LookupOpenCircuit searches for the circuit identified by its outgoing circuit
// key.
func (cm *circuitMap) LookupOpenCircuit(outKey CircuitKey) *PaymentCircuit {
cm.mtx.RLock()
defer cm.mtx.RUnlock()
return cm.opened[outKey]
}
// LookupByPaymentHash looks up and returns any payment circuits with a given
// payment hash.
func (cm *circuitMap) LookupByPaymentHash(hash [32]byte) []*PaymentCircuit {
cm.mtx.RLock()
defer cm.mtx.RUnlock()
var circuits []*PaymentCircuit
if circuitSet, ok := cm.hashIndex[hash]; ok {
// Iterate over the outgoing circuit keys found with this hash,
// and retrieve the circuit from the opened map.
circuits = make([]*PaymentCircuit, 0, len(circuitSet))
for key := range circuitSet {
if circuit, ok := cm.opened[key]; ok {
circuits = append(circuits, circuit)
}
}
}
return circuits
}
// CommitCircuits accepts any number of circuits and persistently adds them to
// the switch's circuit map. The method returns a list of circuits that had not
// been seen prior by the switch. A link should only forward HTLCs corresponding
// to the returned circuits to the switch.
//
// NOTE: This method uses batched writes to improve performance, gains will only
// be realized if it is called concurrently from separate goroutines.
func (cm *circuitMap) CommitCircuits(circuits ...*PaymentCircuit) (
*CircuitFwdActions, error) {
inKeys := make([]CircuitKey, 0, len(circuits))
for _, circuit := range circuits {
inKeys = append(inKeys, circuit.Incoming)
}
log.Tracef("Committing fresh circuits: %v", lnutils.SpewLogClosure(
inKeys))
actions := &CircuitFwdActions{}
// If an empty list was passed, return early to avoid grabbing the lock.
if len(circuits) == 0 {
return actions, nil
}
// First, we reconcile the provided circuits with our set of pending
// circuits to construct a set of new circuits that need to be written
// to disk. The circuit's pointer is stored so that we only permit this
// exact circuit to be forwarded through the switch. If a circuit is
// already pending, the htlc will be reforwarded by the switch.
//
// NOTE: We track an additional addFails subsequence, which permits us
// to fail back all packets that weren't dropped if we encounter an
// error when committing the circuits.
cm.mtx.Lock()
var adds, drops, fails, addFails []*PaymentCircuit
for _, circuit := range circuits {
inKey := circuit.InKey()
if foundCircuit, ok := cm.pending[inKey]; ok {
switch {
// This circuit has a keystone, it's waiting for a
// response from the remote peer on the outgoing link.
// Drop it like it's hot, ensure duplicates get caught.
case foundCircuit.HasKeystone():
drops = append(drops, circuit)
// If no keystone is set and the switch has not been
// restarted, the corresponding packet should still be
// in the outgoing link's mailbox. It will be delivered
// if it comes online before the switch goes down.
//
// NOTE: Dropping here prevents a flapping, incoming
// link from failing a duplicate add while it is still
// in the server's memory mailboxes.
case !foundCircuit.LoadedFromDisk:
drops = append(drops, circuit)
// Otherwise, the in-mem packet has been lost due to a
// restart. It is now safe to send back a failure along
// the incoming link. The incoming link should be able
// detect and ignore duplicate packets of this type.
default:
fails = append(fails, circuit)
addFails = append(addFails, circuit)
}
continue
}
cm.pending[inKey] = circuit
adds = append(adds, circuit)
addFails = append(addFails, circuit)
}
cm.mtx.Unlock()
// If all circuits are dropped or failed, we are done.
if len(adds) == 0 {
actions.Drops = drops
actions.Fails = fails
return actions, nil
}
// Now, optimistically serialize the circuits to add.
var bs = make([]bytes.Buffer, len(adds))
for i, circuit := range adds {
if err := circuit.Encode(&bs[i]); err != nil {
actions.Drops = drops
actions.Fails = addFails
return actions, err
}
}
// Write the entire batch of circuits to the persistent circuit bucket
// using bolt's Batch write. This method must be called from multiple,
// distinct goroutines to have any impact on performance.
err := kvdb.Batch(cm.cfg.DB, func(tx kvdb.RwTx) error {
circuitBkt := tx.ReadWriteBucket(circuitAddKey)
if circuitBkt == nil {
return ErrCorruptedCircuitMap
}
for i, circuit := range adds {
inKeyBytes := circuit.InKey().Bytes()
circuitBytes := bs[i].Bytes()
err := circuitBkt.Put(inKeyBytes, circuitBytes)
if err != nil {
return err
}
}
return nil
})
// Return if the write succeeded.
if err == nil {
actions.Adds = adds
actions.Drops = drops
actions.Fails = fails
return actions, nil
}
// Otherwise, rollback the circuits added to the pending set if the
// write failed.
cm.mtx.Lock()
for _, circuit := range adds {
delete(cm.pending, circuit.InKey())
}
cm.mtx.Unlock()
// Since our write failed, we will return the dropped packets and mark
// all other circuits as failed.
actions.Drops = drops
actions.Fails = addFails
return actions, err
}
// Keystone is a tuple binding an incoming and outgoing CircuitKey. Keystones
// are preemptively written by an outgoing link before signing a new commitment
// state, and cements which HTLCs we are awaiting a response from a remote
// peer.
type Keystone struct {
InKey CircuitKey
OutKey CircuitKey
}
// String returns a human readable description of the Keystone.
func (k Keystone) String() string {
return fmt.Sprintf("%s --> %s", k.InKey, k.OutKey)
}
// OpenCircuits sets the outgoing circuit key for the circuit identified by
// inKey, persistently marking the circuit as opened. After the changes have
// been persisted, the circuit map's in-memory indexes are updated so that this
// circuit can be queried using LookupByKeystone or LookupByPaymentHash.
func (cm *circuitMap) OpenCircuits(keystones ...Keystone) error {
if len(keystones) == 0 {
return nil
}
log.Tracef("Opening finalized circuits: %v", lnutils.SpewLogClosure(
keystones))
// Check that all keystones correspond to committed-but-unopened
// circuits.
cm.mtx.RLock()
openedCircuits := make([]*PaymentCircuit, 0, len(keystones))
for _, ks := range keystones {
if _, ok := cm.opened[ks.OutKey]; ok {
cm.mtx.RUnlock()
return ErrDuplicateKeystone
}
circuit, ok := cm.pending[ks.InKey]
if !ok {
cm.mtx.RUnlock()
return ErrUnknownCircuit
}
openedCircuits = append(openedCircuits, circuit)
}
cm.mtx.RUnlock()
err := kvdb.Update(cm.cfg.DB, func(tx kvdb.RwTx) error {
// Now, load the circuit bucket to which we will write the
// already serialized circuit.
keystoneBkt := tx.ReadWriteBucket(circuitKeystoneKey)
if keystoneBkt == nil {
return ErrCorruptedCircuitMap
}
for _, ks := range keystones {
outBytes := ks.OutKey.Bytes()
inBytes := ks.InKey.Bytes()
err := keystoneBkt.Put(outBytes, inBytes)
if err != nil {
return err
}
}
return nil
}, func() {})
if err != nil {
return err
}
cm.mtx.Lock()
for i, circuit := range openedCircuits {
ks := keystones[i]
// Since our persistent operation was successful, we can now
// modify the in memory representations. Set the outgoing
// circuit key on our pending circuit, add the same circuit to
// set of opened circuits, and add this circuit to the hash
// index.
circuit.Outgoing = &CircuitKey{}
*circuit.Outgoing = ks.OutKey
cm.opened[ks.OutKey] = circuit
cm.addCircuitToHashIndex(circuit)
}
cm.mtx.Unlock()
return nil
}
// addCirciutToHashIndex inserts a circuit into the circuit map's hash index, so
// that it can be queried using LookupByPaymentHash.
func (cm *circuitMap) addCircuitToHashIndex(c *PaymentCircuit) {
if _, ok := cm.hashIndex[c.PaymentHash]; !ok {
cm.hashIndex[c.PaymentHash] = make(map[CircuitKey]struct{})
}
cm.hashIndex[c.PaymentHash][c.OutKey()] = struct{}{}
}
// FailCircuit marks the circuit identified by `inKey` as closing in-memory,
// which prevents duplicate settles/fails from completing an open circuit twice.
func (cm *circuitMap) FailCircuit(inKey CircuitKey) (*PaymentCircuit, error) {
cm.mtx.Lock()
defer cm.mtx.Unlock()
circuit, ok := cm.pending[inKey]
if !ok {
return nil, ErrUnknownCircuit
}
_, ok = cm.closed[inKey]
if ok {
return nil, ErrCircuitClosing
}
cm.closed[inKey] = struct{}{}
return circuit, nil
}
// CloseCircuit marks the circuit identified by `outKey` as closing in-memory,
// which prevents duplicate settles/fails from completing an open
// circuit twice.
func (cm *circuitMap) CloseCircuit(outKey CircuitKey) (*PaymentCircuit, error) {
cm.mtx.Lock()
defer cm.mtx.Unlock()
circuit, ok := cm.opened[outKey]
if !ok {
return nil, ErrUnknownCircuit
}
_, ok = cm.closed[circuit.Incoming]
if ok {
return nil, ErrCircuitClosing
}
cm.closed[circuit.Incoming] = struct{}{}
return circuit, nil
}
// DeleteCircuits destroys the target circuits by removing them from the circuit
// map, additionally removing the circuits' keystones if any HTLCs were
// forwarded through an outgoing link. The circuits should be identified by its
// incoming circuit key. If a given circuit is not found in the circuit map, it
// will be ignored from the query. This would typically indicate that the
// circuit was already cleaned up at a different point in time.
func (cm *circuitMap) DeleteCircuits(inKeys ...CircuitKey) error {
log.Tracef("Deleting resolved circuits: %v", lnutils.SpewLogClosure(
inKeys))
var (
closingCircuits = make(map[CircuitKey]struct{})
removedCircuits = make(map[CircuitKey]*PaymentCircuit)
)
cm.mtx.Lock()
// Remove any references to the circuits from memory, keeping track of
// which circuits were removed, and which ones had been marked closed.
// This can be used to restore these entries later if the persistent
// removal fails.
for _, inKey := range inKeys {
circuit, ok := cm.pending[inKey]
if !ok {
continue
}
delete(cm.pending, inKey)
if _, ok := cm.closed[inKey]; ok {
closingCircuits[inKey] = struct{}{}
delete(cm.closed, inKey)
}
if circuit.HasKeystone() {
delete(cm.opened, circuit.OutKey())
cm.removeCircuitFromHashIndex(circuit)
}
removedCircuits[inKey] = circuit
}
cm.mtx.Unlock()
err := kvdb.Batch(cm.cfg.DB, func(tx kvdb.RwTx) error {
for _, circuit := range removedCircuits {
// If this htlc made it to an outgoing link, load the
// keystone bucket from which we will remove the
// outgoing circuit key.
if circuit.HasKeystone() {
keystoneBkt := tx.ReadWriteBucket(circuitKeystoneKey)
if keystoneBkt == nil {
return ErrCorruptedCircuitMap
}
outKey := circuit.OutKey()
err := keystoneBkt.Delete(outKey.Bytes())
if err != nil {
return err
}
}
// Remove the circuit itself based on the incoming
// circuit key.
circuitBkt := tx.ReadWriteBucket(circuitAddKey)
if circuitBkt == nil {
return ErrCorruptedCircuitMap
}
inKey := circuit.InKey()
if err := circuitBkt.Delete(inKey.Bytes()); err != nil {
return err
}
}
return nil
})
// Return if the write succeeded.
if err == nil {
return nil
}
// If the persistent changes failed, restore the circuit map to it's
// previous state.
cm.mtx.Lock()
for inKey, circuit := range removedCircuits {
cm.pending[inKey] = circuit
if _, ok := closingCircuits[inKey]; ok {
cm.closed[inKey] = struct{}{}
}
if circuit.HasKeystone() {
cm.opened[circuit.OutKey()] = circuit
cm.addCircuitToHashIndex(circuit)
}
}
cm.mtx.Unlock()
return err
}
// removeCircuitFromHashIndex removes the given circuit from the hash index,
// pruning any unnecessary memory optimistically.
func (cm *circuitMap) removeCircuitFromHashIndex(c *PaymentCircuit) {
// Locate bucket containing this circuit's payment hashes.
circuitsWithHash, ok := cm.hashIndex[c.PaymentHash]
if !ok {
return
}
outKey := c.OutKey()
// Remove this circuit from the set of circuitsWithHash.
delete(circuitsWithHash, outKey)
// Prune the payment hash bucket if no other entries remain.
if len(circuitsWithHash) == 0 {
delete(cm.hashIndex, c.PaymentHash)
}
}
// NumPending returns the number of active circuits added to the circuit map.
func (cm *circuitMap) NumPending() int {
cm.mtx.RLock()
defer cm.mtx.RUnlock()
return len(cm.pending)
}
// NumOpen returns the number of circuits that have been opened by way of
// setting their keystones. This is the number of HTLCs that are waiting for a
// settle/fail response from a remote peer.
func (cm *circuitMap) NumOpen() int {
cm.mtx.RLock()
defer cm.mtx.RUnlock()
return len(cm.opened)
}
package htlcswitch
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"sync"
"sync/atomic"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/kvdb"
)
const (
// defaultDbDirectory is the default directory where our decayed log
// will store our (sharedHash, CLTV) key-value pairs.
defaultDbDirectory = "sharedhashes"
)
var (
// sharedHashBucket is a bucket which houses the first HashPrefixSize
// bytes of a received HTLC's hashed shared secret as the key and the HTLC's
// CLTV expiry as the value.
sharedHashBucket = []byte("shared-hash")
// batchReplayBucket is a bucket that maps batch identifiers to
// serialized ReplaySets. This is used to give idempotency in the event
// that a batch is processed more than once.
batchReplayBucket = []byte("batch-replay")
)
var (
// ErrDecayedLogInit is used to indicate a decayed log failed to create
// the proper bucketing structure on startup.
ErrDecayedLogInit = errors.New("unable to initialize decayed log")
// ErrDecayedLogCorrupted signals that the anticipated bucketing
// structure has diverged since initialization.
ErrDecayedLogCorrupted = errors.New("decayed log structure corrupted")
)
// NewBoltBackendCreator returns a function that creates a new bbolt backend for
// the decayed logs database.
func NewBoltBackendCreator(dbPath,
dbFileName string) func(boltCfg *kvdb.BoltConfig) (kvdb.Backend, error) {
return func(boltCfg *kvdb.BoltConfig) (kvdb.Backend, error) {
cfg := &kvdb.BoltBackendConfig{
DBPath: dbPath,
DBFileName: dbFileName,
NoFreelistSync: boltCfg.NoFreelistSync,
AutoCompact: boltCfg.AutoCompact,
AutoCompactMinAge: boltCfg.AutoCompactMinAge,
DBTimeout: boltCfg.DBTimeout,
}
// Use default path for log database.
if dbPath == "" {
cfg.DBPath = defaultDbDirectory
}
db, err := kvdb.GetBoltBackend(cfg)
if err != nil {
return nil, fmt.Errorf("could not open boltdb: %w", err)
}
return db, nil
}
}
// DecayedLog implements the PersistLog interface. It stores the first
// HashPrefixSize bytes of a sha256-hashed shared secret along with a node's
// CLTV value. It is a decaying log meaning there will be a garbage collector
// to collect entries which are expired according to their stored CLTV value
// and the current block height. DecayedLog wraps boltdb for simplicity and
// batches writes to the database to decrease write contention.
type DecayedLog struct {
started int32 // To be used atomically.
stopped int32 // To be used atomically.
db kvdb.Backend
notifier chainntnfs.ChainNotifier
wg sync.WaitGroup
quit chan struct{}
}
// NewDecayedLog creates a new DecayedLog, which caches recently seen hash
// shared secrets. Entries are evicted as their cltv expires using block epochs
// from the given notifier.
func NewDecayedLog(db kvdb.Backend,
notifier chainntnfs.ChainNotifier) *DecayedLog {
return &DecayedLog{
db: db,
notifier: notifier,
quit: make(chan struct{}),
}
}
// Start opens the database we will be using to store hashed shared secrets.
// It also starts the garbage collector in a goroutine to remove stale
// database entries.
func (d *DecayedLog) Start() error {
if !atomic.CompareAndSwapInt32(&d.started, 0, 1) {
return nil
}
// Initialize the primary buckets used by the decayed log.
if err := d.initBuckets(); err != nil {
return err
}
// Start garbage collector.
if d.notifier != nil {
epochClient, err := d.notifier.RegisterBlockEpochNtfn(nil)
if err != nil {
return fmt.Errorf("unable to register for epoch "+
"notifications: %v", err)
}
d.wg.Add(1)
go d.garbageCollector(epochClient)
}
return nil
}
// initBuckets initializes the primary buckets used by the decayed log, namely
// the shared hash bucket, and batch replay
func (d *DecayedLog) initBuckets() error {
return kvdb.Update(d.db, func(tx kvdb.RwTx) error {
_, err := tx.CreateTopLevelBucket(sharedHashBucket)
if err != nil {
return ErrDecayedLogInit
}
_, err = tx.CreateTopLevelBucket(batchReplayBucket)
if err != nil {
return ErrDecayedLogInit
}
return nil
}, func() {})
}
// Stop halts the garbage collector and closes boltdb.
func (d *DecayedLog) Stop() error {
log.Debugf("DecayedLog shutting down...")
defer log.Debugf("DecayedLog shutdown complete")
if !atomic.CompareAndSwapInt32(&d.stopped, 0, 1) {
return nil
}
// Stop garbage collector.
close(d.quit)
d.wg.Wait()
return nil
}
// garbageCollector deletes entries from sharedHashBucket whose expiry height
// has already past. This function MUST be run as a goroutine.
func (d *DecayedLog) garbageCollector(epochClient *chainntnfs.BlockEpochEvent) {
defer d.wg.Done()
defer epochClient.Cancel()
for {
select {
case epoch, ok := <-epochClient.Epochs:
if !ok {
// Block epoch was canceled, shutting down.
log.Infof("Block epoch canceled, " +
"decaying hash log shutting down")
return
}
// Perform a bout of garbage collection using the
// epoch's block height.
height := uint32(epoch.Height)
numExpired, err := d.gcExpiredHashes(height)
if err != nil {
log.Errorf("unable to expire hashes at "+
"height=%d", height)
}
if numExpired > 0 {
log.Infof("Garbage collected %v shared "+
"secret hashes at height=%v",
numExpired, height)
}
case <-d.quit:
// Received shutdown request.
log.Infof("Decaying hash log received " +
"shutdown request")
return
}
}
}
// gcExpiredHashes purges the decaying log of all entries whose CLTV expires
// below the provided height.
func (d *DecayedLog) gcExpiredHashes(height uint32) (uint32, error) {
var numExpiredHashes uint32
err := kvdb.Batch(d.db, func(tx kvdb.RwTx) error {
numExpiredHashes = 0
// Grab the shared hash bucket
sharedHashes := tx.ReadWriteBucket(sharedHashBucket)
if sharedHashes == nil {
return fmt.Errorf("sharedHashBucket " +
"is nil")
}
var expiredCltv [][]byte
if err := sharedHashes.ForEach(func(k, v []byte) error {
// Deserialize the CLTV value for this entry.
cltv := uint32(binary.BigEndian.Uint32(v))
if cltv < height {
// This CLTV is expired. We must add it to an
// array which we'll loop over and delete every
// hash contained from the db.
expiredCltv = append(expiredCltv, k)
numExpiredHashes++
}
return nil
}); err != nil {
return err
}
// Delete every item in the array. This must
// be done explicitly outside of the ForEach
// function for safety reasons.
for _, hash := range expiredCltv {
err := sharedHashes.Delete(hash)
if err != nil {
return err
}
}
return nil
})
if err != nil {
return 0, err
}
return numExpiredHashes, nil
}
// Delete removes a <shared secret hash, CLTV> key-pair from the
// sharedHashBucket.
func (d *DecayedLog) Delete(hash *sphinx.HashPrefix) error {
return kvdb.Batch(d.db, func(tx kvdb.RwTx) error {
sharedHashes := tx.ReadWriteBucket(sharedHashBucket)
if sharedHashes == nil {
return ErrDecayedLogCorrupted
}
return sharedHashes.Delete(hash[:])
})
}
// Get retrieves the CLTV of a processed HTLC given the first 20 bytes of the
// Sha-256 hash of the shared secret.
func (d *DecayedLog) Get(hash *sphinx.HashPrefix) (uint32, error) {
var value uint32
err := kvdb.View(d.db, func(tx kvdb.RTx) error {
// Grab the shared hash bucket which stores the mapping from
// truncated sha-256 hashes of shared secrets to CLTV's.
sharedHashes := tx.ReadBucket(sharedHashBucket)
if sharedHashes == nil {
return fmt.Errorf("sharedHashes is nil, could " +
"not retrieve CLTV value")
}
// Retrieve the bytes which represents the CLTV
valueBytes := sharedHashes.Get(hash[:])
if valueBytes == nil {
return sphinx.ErrLogEntryNotFound
}
// The first 4 bytes represent the CLTV, store it in value.
value = uint32(binary.BigEndian.Uint32(valueBytes))
return nil
}, func() {
value = 0
})
if err != nil {
return value, err
}
return value, nil
}
// Put stores a shared secret hash as the key and the CLTV as the value.
func (d *DecayedLog) Put(hash *sphinx.HashPrefix, cltv uint32) error {
// Optimisitically serialize the cltv value into the scratch buffer.
var scratch [4]byte
binary.BigEndian.PutUint32(scratch[:], cltv)
return kvdb.Batch(d.db, func(tx kvdb.RwTx) error {
sharedHashes := tx.ReadWriteBucket(sharedHashBucket)
if sharedHashes == nil {
return ErrDecayedLogCorrupted
}
// Check to see if this hash prefix has been recorded before. If
// a value is found, this packet is being replayed.
valueBytes := sharedHashes.Get(hash[:])
if valueBytes != nil {
return sphinx.ErrReplayedPacket
}
return sharedHashes.Put(hash[:], scratch[:])
})
}
// PutBatch accepts a pending batch of hashed secret entries to write to disk.
// Each hashed secret is inserted with a corresponding time value, dictating
// when the entry will be evicted from the log.
// NOTE: This method enforces idempotency by writing the replay set obtained
// from the first attempt for a particular batch ID, and decoding the return
// value to subsequent calls. For the indices of the replay set to be aligned
// properly, the batch MUST be constructed identically to the first attempt,
// pruning will cause the indices to become invalid.
func (d *DecayedLog) PutBatch(b *sphinx.Batch) (*sphinx.ReplaySet, error) {
// Since batched boltdb txns may be executed multiple times before
// succeeding, we will create a new replay set for each invocation to
// avoid any side-effects. If the txn is successful, this replay set
// will be merged with the replay set computed during batch construction
// to generate the complete replay set. If this batch was previously
// processed, the replay set will be deserialized from disk.
var replays *sphinx.ReplaySet
if err := kvdb.Batch(d.db, func(tx kvdb.RwTx) error {
sharedHashes := tx.ReadWriteBucket(sharedHashBucket)
if sharedHashes == nil {
return ErrDecayedLogCorrupted
}
// Load the batch replay bucket, which will be used to either
// retrieve the result of previously processing this batch, or
// to write the result of this operation.
batchReplayBkt := tx.ReadWriteBucket(batchReplayBucket)
if batchReplayBkt == nil {
return ErrDecayedLogCorrupted
}
// Check for the existence of this batch's id in the replay
// bucket. If a non-nil value is found, this indicates that we
// have already processed this batch before. We deserialize the
// resulting and return it to ensure calls to put batch are
// idempotent.
replayBytes := batchReplayBkt.Get(b.ID)
if replayBytes != nil {
replays = sphinx.NewReplaySet()
return replays.Decode(bytes.NewReader(replayBytes))
}
// The CLTV will be stored into scratch and then stored into the
// sharedHashBucket.
var scratch [4]byte
replays = sphinx.NewReplaySet()
err := b.ForEach(func(seqNum uint16, hashPrefix *sphinx.HashPrefix, cltv uint32) error {
// Retrieve the bytes which represents the CLTV
valueBytes := sharedHashes.Get(hashPrefix[:])
if valueBytes != nil {
replays.Add(seqNum)
return nil
}
// Serialize the cltv value and write an entry keyed by
// the hash prefix.
binary.BigEndian.PutUint32(scratch[:], cltv)
return sharedHashes.Put(hashPrefix[:], scratch[:])
})
if err != nil {
return err
}
// Merge the replay set computed from checking the on-disk
// entries with the in-batch replays computed during this
// batch's construction.
replays.Merge(b.ReplaySet)
// Write the replay set under the batch identifier to the batch
// replays bucket. This can be used during recovery to test (1)
// that a particular batch was successfully processed and (2)
// recover the indexes of the adds that were rejected as
// replays.
var replayBuf bytes.Buffer
if err := replays.Encode(&replayBuf); err != nil {
return err
}
return batchReplayBkt.Put(b.ID, replayBuf.Bytes())
}); err != nil {
return nil, err
}
b.ReplaySet = replays
b.IsCommitted = true
return replays, nil
}
// A compile time check to see if DecayedLog adheres to the PersistLog
// interface.
var _ sphinx.ReplayLog = (*DecayedLog)(nil)
package htlcswitch
import (
"bytes"
"fmt"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/lnwire"
)
// ClearTextError is an interface which is implemented by errors that occur
// when we know the underlying wire failure message. These errors are the
// opposite to opaque errors which are onion-encrypted blobs only understandable
// to the initiating node. ClearTextErrors are used when we fail a htlc at our
// node, or one of our initiated payments failed and we can decrypt the onion
// encrypted error fully.
type ClearTextError interface {
error
// WireMessage extracts a valid wire failure message from an internal
// error which may contain additional metadata (which should not be
// exposed to the network). This value may be nil in the case where
// an unknown wire error is returned by one of our peers.
WireMessage() lnwire.FailureMessage
}
// LinkError is an implementation of the ClearTextError interface which
// represents failures that occur on our incoming or outgoing link.
type LinkError struct {
// msg returns the wire failure associated with the error.
// This value should *not* be nil, because we should always
// know the failure type for failures which occur at our own
// node.
msg lnwire.FailureMessage
// FailureDetail enriches the wire error with additional information.
FailureDetail
}
// NewLinkError returns a LinkError with the failure message provided.
// The failure message provided should *not* be nil, because we should
// always know the failure type for failures which occur at our own node.
func NewLinkError(msg lnwire.FailureMessage) *LinkError {
return &LinkError{msg: msg}
}
// NewDetailedLinkError returns a link error that enriches a wire message with
// a failure detail.
func NewDetailedLinkError(msg lnwire.FailureMessage,
detail FailureDetail) *LinkError {
return &LinkError{
msg: msg,
FailureDetail: detail,
}
}
// WireMessage extracts a valid wire failure message from an internal
// error which may contain additional metadata (which should not be
// exposed to the network). This value should never be nil for LinkErrors,
// because we are the ones failing the htlc.
//
// Note this is part of the ClearTextError interface.
func (l *LinkError) WireMessage() lnwire.FailureMessage {
return l.msg
}
// Error returns the string representation of a link error.
//
// Note this is part of the ClearTextError interface.
func (l *LinkError) Error() string {
// If the link error has no failure detail, return the wire message's
// error.
if l.FailureDetail == nil {
return l.msg.Error()
}
return l.FailureDetail.FailureString()
}
// ForwardingError wraps an lnwire.FailureMessage in a struct that also
// includes the source of the error.
type ForwardingError struct {
// FailureSourceIdx is the index of the node that sent the failure. With
// this information, the dispatcher of a payment can modify their set of
// candidate routes in response to the type of failure extracted. Index
// zero is the self node.
FailureSourceIdx int
// msg is the wire message associated with the error. This value may
// be nil in the case where we fail to decode failure message sent by
// a peer.
msg lnwire.FailureMessage
}
// WireMessage extracts a valid wire failure message from an internal
// error which may contain additional metadata (which should not be
// exposed to the network). This value may be nil in the case where
// an unknown wire error is returned by one of our peers.
//
// Note this is part of the ClearTextError interface.
func (f *ForwardingError) WireMessage() lnwire.FailureMessage {
return f.msg
}
// Error implements the built-in error interface. We use this method to allow
// the switch or any callers to insert additional context to the error message
// returned.
func (f *ForwardingError) Error() string {
return fmt.Sprintf(
"%v@%v", f.msg, f.FailureSourceIdx,
)
}
// NewForwardingError creates a new payment error which wraps a wire error
// with additional metadata.
func NewForwardingError(failure lnwire.FailureMessage,
index int) *ForwardingError {
return &ForwardingError{
FailureSourceIdx: index,
msg: failure,
}
}
// NewUnknownForwardingError returns a forwarding error which has a nil failure
// message. This constructor should only be used in the case where we cannot
// decode the failure we have received from a peer.
func NewUnknownForwardingError(index int) *ForwardingError {
return &ForwardingError{
FailureSourceIdx: index,
}
}
// ErrorDecrypter is an interface that is used to decrypt the onion encrypted
// failure reason an extra out a well formed error.
type ErrorDecrypter interface {
// DecryptError peels off each layer of onion encryption from the first
// hop, to the source of the error. A fully populated
// lnwire.FailureMessage is returned along with the source of the
// error.
DecryptError(lnwire.OpaqueReason) (*ForwardingError, error)
}
// UnknownEncrypterType is an error message used to signal that an unexpected
// EncrypterType was encountered during decoding.
type UnknownEncrypterType hop.EncrypterType
// Error returns a formatted error indicating the invalid EncrypterType.
func (e UnknownEncrypterType) Error() string {
return fmt.Sprintf("unknown error encrypter type: %d", e)
}
// OnionErrorDecrypter is the interface that provides onion level error
// decryption.
type OnionErrorDecrypter interface {
// DecryptError attempts to decrypt the passed encrypted error response.
// The onion failure is encrypted in backward manner, starting from the
// node where error have occurred. As a result, in order to decrypt the
// error we need get all shared secret and apply decryption in the
// reverse order.
DecryptError(encryptedData []byte) (*sphinx.DecryptedError, error)
}
// SphinxErrorDecrypter wraps the sphinx data SphinxErrorDecrypter and maps the
// returned errors to concrete lnwire.FailureMessage instances.
type SphinxErrorDecrypter struct {
OnionErrorDecrypter
}
// DecryptError peels off each layer of onion encryption from the first hop, to
// the source of the error. A fully populated lnwire.FailureMessage is returned
// along with the source of the error.
//
// NOTE: Part of the ErrorDecrypter interface.
func (s *SphinxErrorDecrypter) DecryptError(reason lnwire.OpaqueReason) (
*ForwardingError, error) {
failure, err := s.OnionErrorDecrypter.DecryptError(reason)
if err != nil {
return nil, err
}
// Decode the failure. If an error occurs, we leave the failure message
// field nil.
r := bytes.NewReader(failure.Message)
failureMsg, err := lnwire.DecodeFailure(r, 0)
if err != nil {
return NewUnknownForwardingError(failure.SenderIdx), nil
}
return NewForwardingError(failureMsg, failure.SenderIdx), nil
}
// A compile time check to ensure ErrorDecrypter implements the Deobfuscator
// interface.
var _ ErrorDecrypter = (*SphinxErrorDecrypter)(nil)
package htlcswitch
// FailureDetail is an interface implemented by failures that occur on
// our incoming or outgoing link, or within the switch itself.
type FailureDetail interface {
// FailureString returns the string representation of a failure
// detail.
FailureString() string
}
// OutgoingFailure is an enum which is used to enrich failures which occur in
// the switch or on our outgoing link with additional metadata.
type OutgoingFailure int
const (
// OutgoingFailureNone is returned when the wire message contains
// sufficient information.
OutgoingFailureNone OutgoingFailure = iota
// OutgoingFailureDecodeError indicates that we could not decode the
// failure reason provided for a failed payment.
OutgoingFailureDecodeError
// OutgoingFailureLinkNotEligible indicates that a routing attempt was
// made over a link that is not eligible for routing.
OutgoingFailureLinkNotEligible
// OutgoingFailureOnChainTimeout indicates that a payment had to be
// timed out on chain before it got past the first hop by us or the
// remote party.
OutgoingFailureOnChainTimeout
// OutgoingFailureHTLCExceedsMax is returned when a htlc exceeds our
// policy's maximum htlc amount.
OutgoingFailureHTLCExceedsMax
// OutgoingFailureInsufficientBalance is returned when we cannot route a
// htlc due to insufficient outgoing capacity.
OutgoingFailureInsufficientBalance
// OutgoingFailureCircularRoute is returned when an attempt is made
// to forward a htlc through our node which arrives and leaves on the
// same channel.
OutgoingFailureCircularRoute
// OutgoingFailureIncompleteForward is returned when we cancel an incomplete
// forward.
OutgoingFailureIncompleteForward
// OutgoingFailureDownstreamHtlcAdd is returned when we fail to add a
// downstream htlc to our outgoing link.
OutgoingFailureDownstreamHtlcAdd
// OutgoingFailureForwardsDisabled is returned when the switch is
// configured to disallow forwards.
OutgoingFailureForwardsDisabled
)
// FailureString returns the string representation of a failure detail.
//
// Note: it is part of the FailureDetail interface.
func (fd OutgoingFailure) FailureString() string {
switch fd {
case OutgoingFailureNone:
return "no failure detail"
case OutgoingFailureDecodeError:
return "could not decode wire failure"
case OutgoingFailureLinkNotEligible:
return "link not eligible"
case OutgoingFailureOnChainTimeout:
return "payment was resolved on-chain, then canceled back"
case OutgoingFailureHTLCExceedsMax:
return "htlc exceeds maximum policy amount"
case OutgoingFailureInsufficientBalance:
return "insufficient bandwidth to route htlc"
case OutgoingFailureCircularRoute:
return "same incoming and outgoing channel"
case OutgoingFailureIncompleteForward:
return "failed after detecting incomplete forward"
case OutgoingFailureDownstreamHtlcAdd:
return "could not add downstream htlc"
case OutgoingFailureForwardsDisabled:
return "node configured to disallow forwards"
default:
return "unknown failure detail"
}
}
package htlcswitch
import (
"errors"
"fmt"
"github.com/lightningnetwork/lnd/graph/db/models"
)
// heldHtlcSet keeps track of outstanding intercepted forwards. It exposes
// several methods to manipulate the underlying map structure in a consistent
// way.
type heldHtlcSet struct {
set map[models.CircuitKey]InterceptedForward
}
func newHeldHtlcSet() *heldHtlcSet {
return &heldHtlcSet{
set: make(map[models.CircuitKey]InterceptedForward),
}
}
// forEach iterates over all held forwards and calls the given callback for each
// of them.
func (h *heldHtlcSet) forEach(cb func(InterceptedForward)) {
for _, fwd := range h.set {
cb(fwd)
}
}
// popAll calls the callback for each forward and removes them from the set.
func (h *heldHtlcSet) popAll(cb func(InterceptedForward)) {
for _, fwd := range h.set {
cb(fwd)
}
h.set = make(map[models.CircuitKey]InterceptedForward)
}
// popAutoFails calls the callback for each forward that has an auto-fail height
// equal or less then the specified pop height and removes them from the set.
func (h *heldHtlcSet) popAutoFails(height uint32, cb func(InterceptedForward)) {
for key, fwd := range h.set {
if uint32(fwd.Packet().AutoFailHeight) > height {
continue
}
cb(fwd)
delete(h.set, key)
}
}
// pop returns the specified forward and removes it from the set.
func (h *heldHtlcSet) pop(key models.CircuitKey) (InterceptedForward, error) {
intercepted, ok := h.set[key]
if !ok {
return nil, fmt.Errorf("fwd %v not found", key)
}
delete(h.set, key)
return intercepted, nil
}
// exists tests whether the specified forward is part of the set.
func (h *heldHtlcSet) exists(key models.CircuitKey) bool {
_, ok := h.set[key]
return ok
}
// push adds the specified forward to the set. An error is returned if the
// forward exists already.
func (h *heldHtlcSet) push(key models.CircuitKey,
fwd InterceptedForward) error {
if fwd == nil {
return errors.New("nil fwd pushed")
}
if h.exists(key) {
return errors.New("htlc already exists in set")
}
h.set[key] = fwd
return nil
}
//go:build !dev
// +build !dev
package hodl
// Config is an empty struct disabling command line hodl flags in production.
type Config struct{}
// Mask in production always returns MaskNone.
func (c *Config) Mask() Mask {
return MaskNone
}
package hodl
import "fmt"
// MaskNone represents the empty Mask, in which no breakpoints are
// active.
const MaskNone = Mask(0)
type (
// Flag represents a single breakpoint where an HTLC should be dropped
// during forwarding. Flags can be composed into a Mask to express more
// complex combinations.
Flag uint32
// Mask is a bitvector combining multiple Flags that can be queried to
// see which breakpoints are active.
Mask uint32
)
const (
// ExitSettle drops an incoming ADD for which we are the exit node,
// before processing in the link.
ExitSettle Flag = 1 << iota
// AddIncoming drops an incoming ADD before processing if we are not
// the exit node.
AddIncoming
// SettleIncoming drops an incoming SETTLE before processing if we
// are not the exit node.
SettleIncoming
// FailIncoming drops an incoming FAIL before processing if we are
// not the exit node.
FailIncoming
// TODO(conner): add modes for switch breakpoints
// AddOutgoing drops an outgoing ADD before it is added to the
// in-memory commitment state of the link.
AddOutgoing
// SettleOutgoing drops an SETTLE before it is added to the
// in-memory commitment state of the link.
SettleOutgoing
// FailOutgoing drops an outgoing FAIL before is is added to the
// in-memory commitment state of the link.
FailOutgoing
// Commit drops all HTLC after any outgoing circuits have been
// opened, but before the in-memory commitment state is persisted.
Commit
// BogusSettle attempts to settle back any incoming HTLC for which we
// are the exit node with a bogus preimage.
BogusSettle
)
// String returns a human-readable identifier for a given Flag.
func (f Flag) String() string {
switch f {
case ExitSettle:
return "ExitSettle"
case AddIncoming:
return "AddIncoming"
case SettleIncoming:
return "SettleIncoming"
case FailIncoming:
return "FailIncoming"
case AddOutgoing:
return "AddOutgoing"
case SettleOutgoing:
return "SettleOutgoing"
case FailOutgoing:
return "FailOutgoing"
case Commit:
return "Commit"
case BogusSettle:
return "BogusSettle"
default:
return "UnknownHodlFlag"
}
}
// Warning generates a warning message to log if a particular breakpoint is
// triggered during execution.
func (f Flag) Warning() string {
var msg string
switch f {
case ExitSettle:
msg = "will not attempt to settle ADD with sender"
case AddIncoming:
msg = "will not attempt to forward ADD to switch"
case SettleIncoming:
msg = "will not attempt to forward SETTLE to switch"
case FailIncoming:
msg = "will not attempt to forward FAIL to switch"
case AddOutgoing:
msg = "will not update channel state with downstream ADD"
case SettleOutgoing:
msg = "will not update channel state with downstream SETTLE"
case FailOutgoing:
msg = "will not update channel state with downstream FAIL"
case Commit:
msg = "will not commit pending channel updates"
case BogusSettle:
msg = "will settle HTLC with bogus preimage"
default:
msg = "incorrect hodl flag usage"
}
return fmt.Sprintf("%s mode enabled -- %s", f, msg)
}
// Mask returns the Mask consisting solely of this Flag.
func (f Flag) Mask() Mask {
return Mask(f)
}
//go:build !dev
// +build !dev
package hodl
// MaskFromFlags in production always returns MaskNone.
func MaskFromFlags(_ ...Flag) Mask {
return MaskNone
}
// Active in production always returns false for all Flags.
func (m Mask) Active(_ Flag) bool {
return false
}
// String returns the human-readable identifier for MaskNone.
func (m Mask) String() string {
return "hodl.Mask(NONE)"
}
package hop
import (
"bytes"
"fmt"
"io"
"github.com/btcsuite/btcd/btcec/v2"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/lnwire"
)
// EncrypterType establishes an enum used in serialization to indicate how to
// decode a concrete instance of the ErrorEncrypter interface.
type EncrypterType byte
const (
// EncrypterTypeNone signals that no error encyrpter is present, this
// can happen if the htlc is originates in the switch.
EncrypterTypeNone EncrypterType = 0
// EncrypterTypeSphinx is used to identify a sphinx onion error
// encrypter instance.
EncrypterTypeSphinx = 1
// EncrypterTypeMock is used to identify a mock obfuscator instance.
EncrypterTypeMock = 2
// EncrypterTypeIntroduction is used to identify a sphinx onion error
// encrypter where we are the introduction node in a blinded route. It
// has the same functionality as EncrypterTypeSphinx, but is used to
// mark our special-case error handling.
EncrypterTypeIntroduction = 3
// EncrypterTypeRelaying is used to identify a sphinx onion error
// encryper where we are a relaying node in a blinded route. It has
// the same functionality as a EncrypterTypeSphinx, but is used to mark
// our special-case error handling.
EncrypterTypeRelaying = 4
)
// IsBlinded returns a boolean indicating whether the error encrypter belongs
// to a blinded route.
func (e EncrypterType) IsBlinded() bool {
return e == EncrypterTypeIntroduction || e == EncrypterTypeRelaying
}
// ErrorEncrypterExtracter defines a function signature that extracts an
// ErrorEncrypter from an sphinx OnionPacket.
type ErrorEncrypterExtracter func(*btcec.PublicKey) (ErrorEncrypter,
lnwire.FailCode)
// ErrorEncrypter is an interface that is used to encrypt HTLC related errors
// at the source of the error, and also at each intermediate hop all the way
// back to the source of the payment.
type ErrorEncrypter interface {
// EncryptFirstHop transforms a concrete failure message into an
// encrypted opaque failure reason. This method will be used at the
// source that the error occurs. It differs from IntermediateEncrypt
// slightly, in that it computes a proper MAC over the error.
EncryptFirstHop(lnwire.FailureMessage) (lnwire.OpaqueReason, error)
// EncryptMalformedError is similar to EncryptFirstHop (it adds the
// MAC), but it accepts an opaque failure reason rather than a failure
// message. This method is used when we receive an
// UpdateFailMalformedHTLC from the remote peer and then need to
// convert that into a proper error from only the raw bytes.
EncryptMalformedError(lnwire.OpaqueReason) lnwire.OpaqueReason
// IntermediateEncrypt wraps an already encrypted opaque reason error
// in an additional layer of onion encryption. This process repeats
// until the error arrives at the source of the payment.
IntermediateEncrypt(lnwire.OpaqueReason) lnwire.OpaqueReason
// Type returns an enum indicating the underlying concrete instance
// backing this interface.
Type() EncrypterType
// Encode serializes the encrypter's ephemeral public key to the given
// io.Writer.
Encode(io.Writer) error
// Decode deserializes the encrypter' ephemeral public key from the
// given io.Reader.
Decode(io.Reader) error
// Reextract rederives the encrypter using the extracter, performing an
// ECDH with the sphinx router's key and the ephemeral public key.
//
// NOTE: This should be called shortly after Decode to properly
// reinitialize the error encrypter.
Reextract(ErrorEncrypterExtracter) error
}
// SphinxErrorEncrypter is a concrete implementation of both the ErrorEncrypter
// interface backed by an implementation of the Sphinx packet format. As a
// result, all errors handled are themselves wrapped in layers of onion
// encryption and must be treated as such accordingly.
type SphinxErrorEncrypter struct {
*sphinx.OnionErrorEncrypter
EphemeralKey *btcec.PublicKey
}
// NewSphinxErrorEncrypter initializes a blank sphinx error encrypter, that
// should be used to deserialize an encoded SphinxErrorEncrypter. Since the
// actual encrypter is not stored in plaintext while at rest, reconstructing the
// error encrypter requires:
// 1. Decode: to deserialize the ephemeral public key.
// 2. Reextract: to "unlock" the actual error encrypter using an active
// OnionProcessor.
func NewSphinxErrorEncrypter() *SphinxErrorEncrypter {
return &SphinxErrorEncrypter{
OnionErrorEncrypter: nil,
EphemeralKey: &btcec.PublicKey{},
}
}
// EncryptFirstHop transforms a concrete failure message into an encrypted
// opaque failure reason. This method will be used at the source that the error
// occurs. It differs from BackwardObfuscate slightly, in that it computes a
// proper MAC over the error.
//
// NOTE: Part of the ErrorEncrypter interface.
func (s *SphinxErrorEncrypter) EncryptFirstHop(
failure lnwire.FailureMessage) (lnwire.OpaqueReason, error) {
var b bytes.Buffer
if err := lnwire.EncodeFailure(&b, failure, 0); err != nil {
return nil, err
}
// We pass a true as the first parameter to indicate that a MAC should
// be added.
return s.EncryptError(true, b.Bytes()), nil
}
// EncryptMalformedError is similar to EncryptFirstHop (it adds the MAC), but
// it accepts an opaque failure reason rather than a failure message. This
// method is used when we receive an UpdateFailMalformedHTLC from the remote
// peer and then need to convert that into an proper error from only the raw
// bytes.
//
// NOTE: Part of the ErrorEncrypter interface.
func (s *SphinxErrorEncrypter) EncryptMalformedError(
reason lnwire.OpaqueReason) lnwire.OpaqueReason {
return s.EncryptError(true, reason)
}
// IntermediateEncrypt wraps an already encrypted opaque reason error in an
// additional layer of onion encryption. This process repeats until the error
// arrives at the source of the payment. We re-encrypt the message on the
// backwards path to ensure that the error is indistinguishable from any other
// error seen.
//
// NOTE: Part of the ErrorEncrypter interface.
func (s *SphinxErrorEncrypter) IntermediateEncrypt(
reason lnwire.OpaqueReason) lnwire.OpaqueReason {
return s.EncryptError(false, reason)
}
// Type returns the identifier for a sphinx error encrypter.
func (s *SphinxErrorEncrypter) Type() EncrypterType {
return EncrypterTypeSphinx
}
// Encode serializes the error encrypter' ephemeral public key to the provided
// io.Writer.
func (s *SphinxErrorEncrypter) Encode(w io.Writer) error {
ephemeral := s.EphemeralKey.SerializeCompressed()
_, err := w.Write(ephemeral)
return err
}
// Decode reconstructs the error encrypter's ephemeral public key from the
// provided io.Reader.
func (s *SphinxErrorEncrypter) Decode(r io.Reader) error {
var ephemeral [33]byte
if _, err := io.ReadFull(r, ephemeral[:]); err != nil {
return err
}
var err error
s.EphemeralKey, err = btcec.ParsePubKey(ephemeral[:])
if err != nil {
return err
}
return nil
}
// Reextract rederives the error encrypter from the currently held EphemeralKey.
// This intended to be used shortly after Decode, to fully initialize a
// SphinxErrorEncrypter.
func (s *SphinxErrorEncrypter) Reextract(
extract ErrorEncrypterExtracter) error {
obfuscator, failcode := extract(s.EphemeralKey)
if failcode != lnwire.CodeNone {
// This should never happen, since we already validated that
// this obfuscator can be extracted when it was received in the
// link.
return fmt.Errorf("unable to reconstruct onion "+
"obfuscator, got failcode: %d", failcode)
}
sphinxEncrypter, ok := obfuscator.(*SphinxErrorEncrypter)
if !ok {
return fmt.Errorf("incorrect onion error extracter")
}
// Copy the freshly extracted encrypter.
s.OnionErrorEncrypter = sphinxEncrypter.OnionErrorEncrypter
return nil
}
// A compile time check to ensure SphinxErrorEncrypter implements the
// ErrorEncrypter interface.
var _ ErrorEncrypter = (*SphinxErrorEncrypter)(nil)
// A compile time check to ensure that IntroductionErrorEncrypter implements
// the ErrorEncrypter interface.
var _ ErrorEncrypter = (*IntroductionErrorEncrypter)(nil)
// IntroductionErrorEncrypter is a wrapper type on SphinxErrorEncrypter which
// is used to signal that we have special HTLC error handling for this hop.
type IntroductionErrorEncrypter struct {
// ErrorEncrypter is the underlying error encrypter, embedded
// directly in the struct so that we don't have to re-implement the
// ErrorEncrypter interface.
ErrorEncrypter
}
// NewIntroductionErrorEncrypter returns a blank IntroductionErrorEncrypter.
func NewIntroductionErrorEncrypter() *IntroductionErrorEncrypter {
return &IntroductionErrorEncrypter{
ErrorEncrypter: NewSphinxErrorEncrypter(),
}
}
// Type returns the identifier for an introduction error encrypter.
func (i *IntroductionErrorEncrypter) Type() EncrypterType {
return EncrypterTypeIntroduction
}
// Reextract rederives the error encrypter from the currently held EphemeralKey,
// relying on the logic in the underlying SphinxErrorEncrypter.
func (i *IntroductionErrorEncrypter) Reextract(
extract ErrorEncrypterExtracter) error {
return i.ErrorEncrypter.Reextract(extract)
}
// A compile time check to ensure that RelayingErrorEncrypte implements
// the ErrorEncrypter interface.
var _ ErrorEncrypter = (*RelayingErrorEncrypter)(nil)
// RelayingErrorEncrypter is a wrapper type on SphinxErrorEncrypter which
// is used to signal that we have special HTLC error handling for this hop.
type RelayingErrorEncrypter struct {
ErrorEncrypter
}
// NewRelayingErrorEncrypter returns a blank RelayingErrorEncrypter with
// an underlying SphinxErrorEncrypter.
func NewRelayingErrorEncrypter() *RelayingErrorEncrypter {
return &RelayingErrorEncrypter{
ErrorEncrypter: NewSphinxErrorEncrypter(),
}
}
// Type returns the identifier for a relaying error encrypter.
func (r *RelayingErrorEncrypter) Type() EncrypterType {
return EncrypterTypeRelaying
}
// Reextract rederives the error encrypter from the currently held EphemeralKey,
// relying on the logic in the underlying SphinxErrorEncrypter.
func (r *RelayingErrorEncrypter) Reextract(
extract ErrorEncrypterExtracter) error {
return r.ErrorEncrypter.Reextract(extract)
}
package hop
import (
"bytes"
"errors"
"fmt"
"io"
"sync"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/tlv"
)
var (
// ErrDecodeFailed is returned when we can't decode blinded data.
ErrDecodeFailed = errors.New("could not decode blinded data")
// ErrNoBlindingPoint is returned when we have not provided a blinding
// point for a validated payload with encrypted data set.
ErrNoBlindingPoint = errors.New("no blinding point set for validated " +
"blinded hop")
)
// RouteRole represents the different types of roles a node can have as a
// recipient of a HTLC.
type RouteRole uint8
const (
// RouteRoleCleartext represents a regular route hop.
RouteRoleCleartext RouteRole = iota
// RouteRoleIntroduction represents an introduction node in a blinded
// path, characterized by a blinding point in the onion payload.
RouteRoleIntroduction
// RouteRoleRelaying represents a relaying node in a blinded path,
// characterized by a blinding point in update_add_htlc.
RouteRoleRelaying
)
// String representation of a role in a route.
func (h RouteRole) String() string {
switch h {
case RouteRoleCleartext:
return "cleartext"
case RouteRoleRelaying:
return "blinded relay"
case RouteRoleIntroduction:
return "introduction node"
default:
return fmt.Sprintf("unknown route role: %d", h)
}
}
// NewRouteRole returns the role we're playing in a route depending on the
// blinding points set (or not). If we are in the situation where we received
// blinding points in both the update add message and the payload:
// - We must have had a valid update add blinding point, because we were able
// to decrypt our onion to get the payload blinding point.
// - We return a relaying node role, because an introduction node (by
// definition) does not receive a blinding point in update add.
// - We assume the sending node to be buggy (including a payload blinding
// where it shouldn't), and rely on validation elsewhere to handle this.
func NewRouteRole(updateAddBlinding, payloadBlinding bool) RouteRole {
switch {
case updateAddBlinding:
return RouteRoleRelaying
case payloadBlinding:
return RouteRoleIntroduction
default:
return RouteRoleCleartext
}
}
// Iterator is an interface that abstracts away the routing information
// included in HTLC's which includes the entirety of the payment path of an
// HTLC. This interface provides two basic method which carry out: how to
// interpret the forwarding information encoded within the HTLC packet, and hop
// to encode the forwarding information for the _next_ hop.
type Iterator interface {
// HopPayload returns the set of fields that detail exactly _how_ this
// hop should forward the HTLC to the next hop. Additionally, the
// information encoded within the returned ForwardingInfo is to be used
// by each hop to authenticate the information given to it by the prior
// hop. The payload will also contain any additional TLV fields provided
// by the sender. The role that this hop plays in the context of
// route blinding (regular, introduction or relaying) is returned
// whenever the payload is successfully parsed, even if we subsequently
// face a validation error.
HopPayload() (*Payload, RouteRole, error)
// EncodeNextHop encodes the onion packet destined for the next hop
// into the passed io.Writer.
EncodeNextHop(w io.Writer) error
// ExtractErrorEncrypter returns the ErrorEncrypter needed for this hop,
// along with a failure code to signal if the decoding was successful.
ExtractErrorEncrypter(extractor ErrorEncrypterExtracter,
introductionNode bool) (ErrorEncrypter, lnwire.FailCode)
}
// sphinxHopIterator is the Sphinx implementation of hop iterator which uses
// onion routing to encode the payment route in such a way so that node might
// see only the next hop in the route.
type sphinxHopIterator struct {
// ogPacket is the original packet from which the processed packet is
// derived.
ogPacket *sphinx.OnionPacket
// processedPacket is the outcome of processing an onion packet. It
// includes the information required to properly forward the packet to
// the next hop.
processedPacket *sphinx.ProcessedPacket
// blindingKit contains the elements required to process hops that are
// part of a blinded route.
blindingKit BlindingKit
// rHash holds the payment hash for this payment. This is needed for
// when a new hop iterator is constructed.
rHash []byte
// router holds the router which can be used to decrypt onion payloads.
// This is required for peeling of dummy hops in a blinded path where
// the same node will iteratively need to unwrap the onion.
router *sphinx.Router
}
// makeSphinxHopIterator converts a processed packet returned from a sphinx
// router and converts it into an hop iterator for usage in the link. A
// blinding kit is passed through for the link to obtain forwarding information
// for blinded routes.
func makeSphinxHopIterator(router *sphinx.Router, ogPacket *sphinx.OnionPacket,
packet *sphinx.ProcessedPacket, blindingKit BlindingKit,
rHash []byte) *sphinxHopIterator {
return &sphinxHopIterator{
router: router,
ogPacket: ogPacket,
processedPacket: packet,
blindingKit: blindingKit,
rHash: rHash,
}
}
// A compile time check to ensure sphinxHopIterator implements the HopIterator
// interface.
var _ Iterator = (*sphinxHopIterator)(nil)
// Encode encodes iterator and writes it to the writer.
//
// NOTE: Part of the HopIterator interface.
func (r *sphinxHopIterator) EncodeNextHop(w io.Writer) error {
return r.processedPacket.NextPacket.Encode(w)
}
// HopPayload returns the set of fields that detail exactly _how_ this hop
// should forward the HTLC to the next hop. Additionally, the information
// encoded within the returned ForwardingInfo is to be used by each hop to
// authenticate the information given to it by the prior hop. The role that
// this hop plays in the context of route blinding (regular, introduction or
// relaying) is returned whenever the payload is successfully parsed, even if
// we subsequently face a validation error. The payload will also contain any
// additional TLV fields provided by the sender.
//
// NOTE: Part of the HopIterator interface.
func (r *sphinxHopIterator) HopPayload() (*Payload, RouteRole, error) {
switch r.processedPacket.Payload.Type {
// If this is the legacy payload, then we'll extract the information
// directly from the pre-populated ForwardingInstructions field.
case sphinx.PayloadLegacy:
fwdInst := r.processedPacket.ForwardingInstructions
return NewLegacyPayload(fwdInst), RouteRoleCleartext, nil
// Otherwise, if this is the TLV payload, then we'll make a new stream
// to decode only what we need to make routing decisions.
case sphinx.PayloadTLV:
return extractTLVPayload(r)
default:
return nil, RouteRoleCleartext,
fmt.Errorf("unknown sphinx payload type: %v",
r.processedPacket.Payload.Type)
}
}
// extractTLVPayload parses the hop payload and assumes that it uses the TLV
// format. It returns the parsed payload along with the RouteRole that this hop
// plays given the contents of the payload.
func extractTLVPayload(r *sphinxHopIterator) (*Payload, RouteRole, error) {
isFinal := r.processedPacket.Action == sphinx.ExitNode
// Initial payload parsing and validation
payload, routeRole, recipientData, err := parseAndValidateSenderPayload(
r.processedPacket.Payload.Payload, isFinal,
r.blindingKit.UpdateAddBlinding.IsSome(),
)
if err != nil {
return nil, routeRole, err
}
// If the payload contained no recipient data, then we can exit now.
if !recipientData {
return payload, routeRole, nil
}
return parseAndValidateRecipientData(r, payload, isFinal, routeRole)
}
// parseAndValidateRecipientData decrypts the payload from the recipient and
// then continues handling and validation based on if we are a forwarding node
// in this blinded path or the final destination node.
func parseAndValidateRecipientData(r *sphinxHopIterator, payload *Payload,
isFinal bool, routeRole RouteRole) (*Payload, RouteRole, error) {
// Decrypt and validate the blinded route data
routeData, blindingPoint, err := decryptAndValidateBlindedRouteData(
r, payload,
)
if err != nil {
return nil, routeRole, err
}
// This is the final node in the blinded route.
if isFinal {
return deriveBlindedRouteFinalHopForwardingInfo(
routeData, payload, routeRole,
)
}
// Else, we are a forwarding node in this blinded path.
return deriveBlindedRouteForwardingInfo(
r, routeData, payload, routeRole, blindingPoint,
)
}
// deriveBlindedRouteFinalHopForwardingInfo extracts the PathID from the
// routeData and constructs the ForwardingInfo accordingly.
func deriveBlindedRouteFinalHopForwardingInfo(
routeData *record.BlindedRouteData, payload *Payload,
routeRole RouteRole) (*Payload, RouteRole, error) {
var pathID *chainhash.Hash
routeData.PathID.WhenSome(func(r tlv.RecordT[tlv.TlvType6, []byte]) {
var id chainhash.Hash
copy(id[:], r.Val)
pathID = &id
})
if pathID == nil {
return nil, routeRole, ErrInvalidPayload{
Type: tlv.Type(6),
Violation: InsufficientViolation,
}
}
payload.FwdInfo = ForwardingInfo{
PathID: pathID,
}
return payload, routeRole, nil
}
// deriveBlindedRouteForwardingInfo uses the parsed BlindedRouteData from the
// recipient to derive the ForwardingInfo for the payment.
func deriveBlindedRouteForwardingInfo(r *sphinxHopIterator,
routeData *record.BlindedRouteData, payload *Payload,
routeRole RouteRole, blindingPoint *btcec.PublicKey) (*Payload,
RouteRole, error) {
relayInfo, err := routeData.RelayInfo.UnwrapOrErr(
fmt.Errorf("relay info not set for non-final blinded hop"),
)
if err != nil {
return nil, routeRole, err
}
fwdAmt, err := calculateForwardingAmount(
r.blindingKit.IncomingAmount, relayInfo.Val.BaseFee,
relayInfo.Val.FeeRate,
)
if err != nil {
return nil, routeRole, err
}
nextEph, err := routeData.NextBlindingOverride.UnwrapOrFuncErr(
func() (tlv.RecordT[tlv.TlvType8, *btcec.PublicKey], error) {
next, err := r.blindingKit.Processor.NextEphemeral(
blindingPoint,
)
if err != nil {
return routeData.NextBlindingOverride.Zero(),
err
}
return tlv.NewPrimitiveRecord[tlv.TlvType8](next), nil
})
if err != nil {
return nil, routeRole, err
}
// If the payload signals that the following hop is a dummy hop, then
// we will iteratively peel the dummy hop until we reach the final
// payload.
if checkForDummyHop(routeData, r.router.OnionPublicKey()) {
return peelBlindedPathDummyHop(
r, uint32(relayInfo.Val.CltvExpiryDelta), fwdAmt,
routeRole, nextEph,
)
}
nextSCID, err := routeData.ShortChannelID.UnwrapOrErr(
fmt.Errorf("next SCID not set for non-final blinded hop"),
)
if err != nil {
return nil, routeRole, err
}
payload.FwdInfo = ForwardingInfo{
NextHop: nextSCID.Val,
AmountToForward: fwdAmt,
OutgoingCTLV: r.blindingKit.IncomingCltv - uint32(
relayInfo.Val.CltvExpiryDelta,
),
// Remap from blinding override type to blinding point type.
NextBlinding: tlv.SomeRecordT(
tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType](
nextEph.Val,
),
),
}
return payload, routeRole, nil
}
// checkForDummyHop returns whether the given BlindedRouteData packet indicates
// the presence of a dummy hop.
func checkForDummyHop(routeData *record.BlindedRouteData,
routerPubKey *btcec.PublicKey) bool {
var isDummy bool
routeData.NextNodeID.WhenSome(
func(r tlv.RecordT[tlv.TlvType4, *btcec.PublicKey]) {
isDummy = r.Val.IsEqual(routerPubKey)
},
)
return isDummy
}
// peelBlindedPathDummyHop packages the next onion packet and then constructs
// a new hop iterator using our router and then proceeds to process the next
// packet. This can only be done for blinded route dummy hops since we expect
// to be the final hop on the path.
func peelBlindedPathDummyHop(r *sphinxHopIterator, cltvExpiryDelta uint32,
fwdAmt lnwire.MilliSatoshi, routeRole RouteRole,
nextEph tlv.RecordT[tlv.TlvType8, *btcec.PublicKey]) (*Payload,
RouteRole, error) {
onionPkt := r.processedPacket.NextPacket
sphinxPacket, err := r.router.ReconstructOnionPacket(
onionPkt, r.rHash, sphinx.WithBlindingPoint(nextEph.Val),
)
if err != nil {
return nil, routeRole, err
}
iterator := makeSphinxHopIterator(
r.router, onionPkt, sphinxPacket, BlindingKit{
Processor: r.router,
UpdateAddBlinding: tlv.SomeRecordT(
tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType]( //nolint:ll
nextEph.Val,
),
),
IncomingAmount: fwdAmt,
IncomingCltv: r.blindingKit.IncomingCltv -
cltvExpiryDelta,
}, r.rHash,
)
return extractTLVPayload(iterator)
}
// decryptAndValidateBlindedRouteData decrypts the encrypted payload from the
// payment recipient using a blinding key. The incoming HTLC amount and CLTV
// values are then verified against the policy values from the recipient.
func decryptAndValidateBlindedRouteData(r *sphinxHopIterator,
payload *Payload) (*record.BlindedRouteData, *btcec.PublicKey, error) {
blindingPoint, err := r.blindingKit.getBlindingPoint(
payload.blindingPoint,
)
if err != nil {
return nil, nil, err
}
decrypted, err := r.blindingKit.Processor.DecryptBlindedHopData(
blindingPoint, payload.encryptedData,
)
if err != nil {
return nil, nil, fmt.Errorf("decrypt blinded data: %w", err)
}
buf := bytes.NewBuffer(decrypted)
routeData, err := record.DecodeBlindedRouteData(buf)
if err != nil {
return nil, nil, fmt.Errorf("%w: %w", ErrDecodeFailed, err)
}
err = ValidateBlindedRouteData(
routeData, r.blindingKit.IncomingAmount,
r.blindingKit.IncomingCltv,
)
if err != nil {
return nil, nil, err
}
return routeData, blindingPoint, nil
}
// parseAndValidateSenderPayload parses the payload bytes received from the
// onion constructor (the sender) and validates that various fields have been
// set. It also uses the presence of a blinding key in either the
// update_add_htlc message or in the payload to determine the RouteRole.
// The RouteRole is returned even if an error is returned. The boolean return
// value indicates that the sender payload includes encrypted data from the
// recipient that should be parsed.
func parseAndValidateSenderPayload(payloadBytes []byte, isFinalHop,
updateAddBlindingSet bool) (*Payload, RouteRole, bool, error) {
// Extract TLVs from the packet constructor (the sender).
payload, parsed, err := ParseTLVPayload(bytes.NewReader(payloadBytes))
if err != nil {
// If we couldn't even parse our payload then we do a
// best-effort of determining our role in a blinded route,
// accepting that we can't know whether we were the introduction
// node (as the payload is not parseable).
routeRole := RouteRoleCleartext
if updateAddBlindingSet {
routeRole = RouteRoleRelaying
}
return nil, routeRole, false, err
}
// Now that we've parsed our payload we can determine which role we're
// playing in the route.
_, payloadBlinding := parsed[record.BlindingPointOnionType]
routeRole := NewRouteRole(updateAddBlindingSet, payloadBlinding)
// Validate the presence of the various payload fields we received from
// the sender.
err = ValidateTLVPayload(parsed, isFinalHop, updateAddBlindingSet)
if err != nil {
return nil, routeRole, false, err
}
// If there is no encrypted data from the receiver then return the
// payload as is since the forwarding info would have been received
// from the sender.
if payload.encryptedData == nil {
return payload, routeRole, false, nil
}
// Validate the presence of various fields in the sender payload given
// that we now know that this is a hop with instructions from the
// recipient.
err = ValidatePayloadWithBlinded(isFinalHop, parsed)
if err != nil {
return payload, routeRole, true, err
}
return payload, routeRole, true, nil
}
// ExtractErrorEncrypter decodes and returns the ErrorEncrypter for this hop,
// along with a failure code to signal if the decoding was successful. The
// ErrorEncrypter is used to encrypt errors back to the sender in the event that
// a payment fails.
//
// NOTE: Part of the HopIterator interface.
func (r *sphinxHopIterator) ExtractErrorEncrypter(
extracter ErrorEncrypterExtracter, introductionNode bool) (
ErrorEncrypter, lnwire.FailCode) {
encrypter, errCode := extracter(r.ogPacket.EphemeralKey)
if errCode != lnwire.CodeNone {
return nil, errCode
}
// If we're in a blinded path, wrap the error encrypter that we just
// derived in a "marker" type which we'll use to know what type of
// error we're handling.
switch {
case introductionNode:
return &IntroductionErrorEncrypter{
ErrorEncrypter: encrypter,
}, errCode
case r.blindingKit.UpdateAddBlinding.IsSome():
return &RelayingErrorEncrypter{
ErrorEncrypter: encrypter,
}, errCode
default:
return encrypter, errCode
}
}
// BlindingProcessor is an interface that provides the cryptographic operations
// required for processing blinded hops.
//
// This interface is extracted to allow more granular testing of blinded
// forwarding calculations.
type BlindingProcessor interface {
// DecryptBlindedHopData decrypts a blinded blob of data using the
// ephemeral key provided.
DecryptBlindedHopData(ephemPub *btcec.PublicKey,
encryptedData []byte) ([]byte, error)
// NextEphemeral returns the next hop's ephemeral key, calculated
// from the current ephemeral key provided.
NextEphemeral(*btcec.PublicKey) (*btcec.PublicKey, error)
}
// BlindingKit contains the components required to extract forwarding
// information for hops in a blinded route.
type BlindingKit struct {
// Processor provides the low-level cryptographic operations to
// handle an encrypted blob of data in a blinded forward.
Processor BlindingProcessor
// UpdateAddBlinding holds a blinding point that was passed to the
// node via update_add_htlc's TLVs.
UpdateAddBlinding lnwire.BlindingPointRecord
// IncomingCltv is the expiry of the incoming HTLC.
IncomingCltv uint32
// IncomingAmount is the amount of the incoming HTLC.
IncomingAmount lnwire.MilliSatoshi
}
// getBlindingPoint returns either the payload or updateAddHtlc blinding point,
// assuming that validation that these values are appropriately set has already
// been handled elsewhere.
func (b *BlindingKit) getBlindingPoint(payloadBlinding *btcec.PublicKey) (
*btcec.PublicKey, error) {
payloadBlindingSet := payloadBlinding != nil
updateBlindingSet := b.UpdateAddBlinding.IsSome()
switch {
case payloadBlindingSet:
return payloadBlinding, nil
case updateBlindingSet:
pk, err := b.UpdateAddBlinding.UnwrapOrErr(
fmt.Errorf("expected update add blinding"),
)
if err != nil {
return nil, err
}
return pk.Val, nil
default:
return nil, ErrNoBlindingPoint
}
}
// calculateForwardingAmount calculates the amount to forward for a blinded
// hop based on the incoming amount and forwarding parameters.
//
// When forwarding a payment, the fee we take is calculated, not on the
// incoming amount, but rather on the amount we forward. We charge fees based
// on our own liquidity we are forwarding downstream.
//
// With route blinding, we are NOT given the amount to forward. This
// unintuitive looking formula comes from the fact that without the amount to
// forward, we cannot compute the fees taken directly.
//
// The amount to be forwarded can be computed as follows:
//
// amt_to_forward = incoming_amount - total_fees
// total_fees = base_fee + amt_to_forward*(fee_rate/1000000)
//
// Solving for amount_to_forward:
// amt_to_forward = incoming_amount - base_fee - (amount_to_forward * fee_rate)/1e6
// amt_to_forward + (amount_to_forward * fee_rate) / 1e6 = incoming_amount - base_fee
// amt_to_forward * 1e6 + (amount_to_forward * fee_rate) = (incoming_amount - base_fee) * 1e6
// amt_to_forward * (1e6 + fee_rate) = (incoming_amount - base_fee) * 1e6
// amt_to_forward = ((incoming_amount - base_fee) * 1e6) / (1e6 + fee_rate)
//
// From there we use a ceiling formula for integer division so that we always
// round up, otherwise the sender may receive slightly less than intended:
//
// ceil(a/b) = (a + b - 1)/(b).
//
//nolint:ll,dupword
func calculateForwardingAmount(incomingAmount, baseFee lnwire.MilliSatoshi,
proportionalFee uint32) (lnwire.MilliSatoshi, error) {
// Sanity check to prevent overflow.
if incomingAmount < baseFee {
return 0, fmt.Errorf("incoming amount: %v < base fee: %v",
incomingAmount, baseFee)
}
numerator := (uint64(incomingAmount) - uint64(baseFee)) * 1e6
denominator := 1e6 + uint64(proportionalFee)
ceiling := (numerator + denominator - 1) / denominator
return lnwire.MilliSatoshi(ceiling), nil
}
// OnionProcessor is responsible for keeping all sphinx dependent parts inside
// and expose only decoding function. With such approach we give freedom for
// subsystems which wants to decode sphinx path to not be dependable from
// sphinx at all.
//
// NOTE: The reason for keeping decoder separated from hop iterator is too
// maintain the hop iterator abstraction. Without it the structures which using
// the hop iterator should contain sphinx router which makes their creations in
// tests dependent from the sphinx internal parts.
type OnionProcessor struct {
router *sphinx.Router
}
// NewOnionProcessor creates new instance of decoder.
func NewOnionProcessor(router *sphinx.Router) *OnionProcessor {
return &OnionProcessor{router}
}
// Start spins up the onion processor's sphinx router.
func (p *OnionProcessor) Start() error {
log.Info("Onion processor starting")
return p.router.Start()
}
// Stop shutsdown the onion processor's sphinx router.
func (p *OnionProcessor) Stop() error {
log.Info("Onion processor shutting down...")
defer log.Debug("Onion processor shutdown complete")
p.router.Stop()
return nil
}
// ReconstructBlindingInfo contains the information required to reconstruct a
// blinded onion.
type ReconstructBlindingInfo struct {
// BlindingKey is the blinding point set in UpdateAddHTLC.
BlindingKey lnwire.BlindingPointRecord
// IncomingAmt is the amount for the incoming HTLC.
IncomingAmt lnwire.MilliSatoshi
// IncomingExpiry is the expiry height of the incoming HTLC.
IncomingExpiry uint32
}
// ReconstructHopIterator attempts to decode a valid sphinx packet from the
// passed io.Reader instance using the rHash as the associated data when
// checking the relevant MACs during the decoding process.
func (p *OnionProcessor) ReconstructHopIterator(r io.Reader, rHash []byte,
blindingInfo ReconstructBlindingInfo) (Iterator, error) {
onionPkt := &sphinx.OnionPacket{}
if err := onionPkt.Decode(r); err != nil {
return nil, err
}
var opts []sphinx.ProcessOnionOpt
blindingInfo.BlindingKey.WhenSome(func(
r tlv.RecordT[lnwire.BlindingPointTlvType, *btcec.PublicKey]) {
opts = append(opts, sphinx.WithBlindingPoint(r.Val))
})
// Attempt to process the Sphinx packet. We include the payment hash of
// the HTLC as it's authenticated within the Sphinx packet itself as
// associated data in order to thwart attempts a replay attacks. In the
// case of a replay, an attacker is *forced* to use the same payment
// hash twice, thereby losing their money entirely.
sphinxPacket, err := p.router.ReconstructOnionPacket(
onionPkt, rHash, opts...,
)
if err != nil {
return nil, err
}
return makeSphinxHopIterator(p.router, onionPkt, sphinxPacket,
BlindingKit{
Processor: p.router,
UpdateAddBlinding: blindingInfo.BlindingKey,
IncomingAmount: blindingInfo.IncomingAmt,
IncomingCltv: blindingInfo.IncomingExpiry,
}, rHash,
), nil
}
// DecodeHopIteratorRequest encapsulates all date necessary to process an onion
// packet, perform sphinx replay detection, and schedule the entry for garbage
// collection.
type DecodeHopIteratorRequest struct {
OnionReader io.Reader
RHash []byte
IncomingCltv uint32
IncomingAmount lnwire.MilliSatoshi
BlindingPoint lnwire.BlindingPointRecord
}
// DecodeHopIteratorResponse encapsulates the outcome of a batched sphinx onion
// processing.
type DecodeHopIteratorResponse struct {
HopIterator Iterator
FailCode lnwire.FailCode
}
// Result returns the (HopIterator, lnwire.FailCode) tuple, which should
// correspond to the index of a particular DecodeHopIteratorRequest.
//
// NOTE: The HopIterator should be considered invalid if the fail code is
// anything but lnwire.CodeNone.
func (r *DecodeHopIteratorResponse) Result() (Iterator, lnwire.FailCode) {
return r.HopIterator, r.FailCode
}
// DecodeHopIterators performs batched decoding and validation of incoming
// sphinx packets. For the same `id`, this method will return the same iterators
// and failcodes upon subsequent invocations.
//
// NOTE: In order for the responses to be valid, the caller must guarantee that
// the presented readers and rhashes *NEVER* deviate across invocations for the
// same id.
func (p *OnionProcessor) DecodeHopIterators(id []byte,
reqs []DecodeHopIteratorRequest) ([]DecodeHopIteratorResponse, error) {
var (
batchSize = len(reqs)
onionPkts = make([]sphinx.OnionPacket, batchSize)
resps = make([]DecodeHopIteratorResponse, batchSize)
)
tx := p.router.BeginTxn(id, batchSize)
decode := func(seqNum uint16, onionPkt *sphinx.OnionPacket,
req DecodeHopIteratorRequest) lnwire.FailCode {
err := onionPkt.Decode(req.OnionReader)
switch err {
case nil:
// success
case sphinx.ErrInvalidOnionVersion:
return lnwire.CodeInvalidOnionVersion
case sphinx.ErrInvalidOnionKey:
return lnwire.CodeInvalidOnionKey
default:
log.Errorf("unable to decode onion packet: %v", err)
return lnwire.CodeInvalidOnionKey
}
var opts []sphinx.ProcessOnionOpt
req.BlindingPoint.WhenSome(func(
b tlv.RecordT[lnwire.BlindingPointTlvType,
*btcec.PublicKey]) {
opts = append(opts, sphinx.WithBlindingPoint(
b.Val,
))
})
err = tx.ProcessOnionPacket(
seqNum, onionPkt, req.RHash, req.IncomingCltv, opts...,
)
switch err {
case nil:
// success
return lnwire.CodeNone
case sphinx.ErrInvalidOnionVersion:
return lnwire.CodeInvalidOnionVersion
case sphinx.ErrInvalidOnionHMAC:
return lnwire.CodeInvalidOnionHmac
case sphinx.ErrInvalidOnionKey:
return lnwire.CodeInvalidOnionKey
default:
log.Errorf("unable to process onion packet: %v", err)
return lnwire.CodeInvalidOnionKey
}
}
// Execute cpu-heavy onion decoding in parallel.
var wg sync.WaitGroup
for i := range reqs {
wg.Add(1)
go func(seqNum uint16) {
defer wg.Done()
onionPkt := &onionPkts[seqNum]
resps[seqNum].FailCode = decode(
seqNum, onionPkt, reqs[seqNum],
)
}(uint16(i))
}
wg.Wait()
// With that batch created, we will now attempt to write the shared
// secrets to disk. This operation will returns the set of indices that
// were detected as replays, and the computed sphinx packets for all
// indices that did not fail the above loop. Only indices that are not
// in the replay set should be considered valid, as they are
// opportunistically computed.
packets, replays, err := tx.Commit()
if err != nil {
log.Errorf("unable to process onion packet batch %x: %v",
id, err)
// If we failed to commit the batch to the secret share log, we
// will mark all not-yet-failed channels with a temporary
// channel failure and exit since we cannot proceed.
for i := range resps {
resp := &resps[i]
// Skip any indexes that already failed onion decoding.
if resp.FailCode != lnwire.CodeNone {
continue
}
log.Errorf("unable to process onion packet %x-%v",
id, i)
resp.FailCode = lnwire.CodeTemporaryChannelFailure
}
// TODO(conner): return real errors to caller so link can fail?
return resps, err
}
// Otherwise, the commit was successful. Now we will post process any
// remaining packets, additionally failing any that were included in the
// replay set.
for i := range resps {
resp := &resps[i]
// Skip any indexes that already failed onion decoding.
if resp.FailCode != lnwire.CodeNone {
continue
}
// If this index is contained in the replay set, mark it with a
// temporary channel failure error code. We infer that the
// offending error was due to a replayed packet because this
// index was found in the replay set.
if replays.Contains(uint16(i)) {
log.Errorf("unable to process onion packet: %v",
sphinx.ErrReplayedPacket)
// We set FailCode to CodeInvalidOnionVersion even
// though the ephemeral key isn't the problem. We need
// to set the BADONION bit since we're sending back a
// malformed packet, but as there isn't a specific
// failure code for replays, we reuse one of the
// failure codes that has BADONION.
resp.FailCode = lnwire.CodeInvalidOnionVersion
continue
}
// Finally, construct a hop iterator from our processed sphinx
// packet, simultaneously caching the original onion packet.
resp.HopIterator = makeSphinxHopIterator(
p.router, &onionPkts[i], &packets[i], BlindingKit{
Processor: p.router,
UpdateAddBlinding: reqs[i].BlindingPoint,
IncomingAmount: reqs[i].IncomingAmount,
IncomingCltv: reqs[i].IncomingCltv,
}, reqs[i].RHash,
)
}
return resps, nil
}
// ExtractErrorEncrypter takes an io.Reader which should contain the onion
// packet as original received by a forwarding node and creates an
// ErrorEncrypter instance using the derived shared secret. In the case that en
// error occurs, a lnwire failure code detailing the parsing failure will be
// returned.
func (p *OnionProcessor) ExtractErrorEncrypter(ephemeralKey *btcec.PublicKey) (
ErrorEncrypter, lnwire.FailCode) {
onionObfuscator, err := sphinx.NewOnionErrorEncrypter(
p.router, ephemeralKey,
)
if err != nil {
switch err {
case sphinx.ErrInvalidOnionVersion:
return nil, lnwire.CodeInvalidOnionVersion
case sphinx.ErrInvalidOnionHMAC:
return nil, lnwire.CodeInvalidOnionHmac
case sphinx.ErrInvalidOnionKey:
return nil, lnwire.CodeInvalidOnionKey
default:
log.Errorf("unable to process onion packet: %v", err)
return nil, lnwire.CodeInvalidOnionKey
}
}
return &SphinxErrorEncrypter{
OnionErrorEncrypter: onionObfuscator,
EphemeralKey: ephemeralKey,
}, lnwire.CodeNone
}
package hop
import (
"github.com/btcsuite/btclog/v2"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// UseLogger uses a specified Logger to output package logging info. This
// function is called from the parent package htlcswitch logger initialization.
func UseLogger(logger btclog.Logger) {
log = logger
}
package hop
import (
"encoding/binary"
"fmt"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/tlv"
)
// PayloadViolation is an enum encapsulating the possible invalid payload
// violations that can occur when processing or validating a payload.
type PayloadViolation byte
const (
// OmittedViolation indicates that a type was expected to be found the
// payload but was absent.
OmittedViolation PayloadViolation = iota
// IncludedViolation indicates that a type was expected to be omitted
// from the payload but was present.
IncludedViolation
// RequiredViolation indicates that an unknown even type was found in
// the payload that we could not process.
RequiredViolation
// InsufficientViolation indicates that the provided type does
// not satisfy constraints.
InsufficientViolation
)
// String returns a human-readable description of the violation as a verb.
func (v PayloadViolation) String() string {
switch v {
case OmittedViolation:
return "omitted"
case IncludedViolation:
return "included"
case RequiredViolation:
return "required"
case InsufficientViolation:
return "insufficient"
default:
return "unknown violation"
}
}
// ErrInvalidPayload is an error returned when a parsed onion payload either
// included or omitted incorrect records for a particular hop type.
type ErrInvalidPayload struct {
// Type the record's type that cause the violation.
Type tlv.Type
// Violation is an enum indicating the type of violation detected in
// processing Type.
Violation PayloadViolation
// FinalHop if true, indicates that the violation is for the final hop
// in the route (identified by next hop id), otherwise the violation is
// for an intermediate hop.
FinalHop bool
}
// Error returns a human-readable description of the invalid payload error.
func (e ErrInvalidPayload) Error() string {
hopType := "intermediate"
if e.FinalHop {
hopType = "final"
}
return fmt.Sprintf("onion payload for %s hop %v record with type %d",
hopType, e.Violation, e.Type)
}
// Payload encapsulates all information delivered to a hop in an onion payload.
// A Hop can represent either a TLV or legacy payload. The primary forwarding
// instruction can be accessed via ForwardingInfo, and additional records can be
// accessed by other member functions.
type Payload struct {
// FwdInfo holds the basic parameters required for HTLC forwarding, e.g.
// amount, cltv, and next hop.
FwdInfo ForwardingInfo
// MPP holds the info provided in an option_mpp record when parsed from
// a TLV onion payload.
MPP *record.MPP
// AMP holds the info provided in an option_amp record when parsed from
// a TLV onion payload.
AMP *record.AMP
// customRecords are user-defined records in the custom type range that
// were included in the payload.
customRecords record.CustomSet
// encryptedData is a blob of data encrypted by the receiver for use
// in blinded routes.
encryptedData []byte
// blindingPoint is an ephemeral pubkey for use in blinded routes.
blindingPoint *btcec.PublicKey
// metadata is additional data that is sent along with the payment to
// the payee.
metadata []byte
// totalAmtMsat holds the info provided in total_amount_msat when
// parsed from a TLV onion payload.
totalAmtMsat lnwire.MilliSatoshi
}
// NewLegacyPayload builds a Payload from the amount, cltv, and next hop
// parameters provided by leegacy onion payloads.
func NewLegacyPayload(f *sphinx.HopData) *Payload {
nextHop := binary.BigEndian.Uint64(f.NextAddress[:])
return &Payload{
FwdInfo: ForwardingInfo{
NextHop: lnwire.NewShortChanIDFromInt(nextHop),
AmountToForward: lnwire.MilliSatoshi(f.ForwardAmount),
OutgoingCTLV: f.OutgoingCltv,
},
customRecords: make(record.CustomSet),
}
}
// ParseTLVPayload builds a new Hop from the passed io.Reader and returns
// a map of all the types that were found in the payload. This function
// does not perform validation of TLV types included in the payload.
func ParseTLVPayload(r io.Reader) (*Payload, map[tlv.Type][]byte, error) {
var (
cid uint64
amt uint64
totalAmtMsat uint64
cltv uint32
mpp = &record.MPP{}
amp = &record.AMP{}
encryptedData []byte
blindingPoint *btcec.PublicKey
metadata []byte
)
tlvStream, err := tlv.NewStream(
record.NewAmtToFwdRecord(&amt),
record.NewLockTimeRecord(&cltv),
record.NewNextHopIDRecord(&cid),
mpp.Record(),
record.NewEncryptedDataRecord(&encryptedData),
record.NewBlindingPointRecord(&blindingPoint),
amp.Record(),
record.NewMetadataRecord(&metadata),
record.NewTotalAmtMsatBlinded(&totalAmtMsat),
)
if err != nil {
return nil, nil, err
}
// Since this data is provided by a potentially malicious peer, pass it
// into the P2P decoding variant.
parsedTypes, err := tlvStream.DecodeWithParsedTypesP2P(r)
if err != nil {
return nil, nil, err
}
// If no MPP field was parsed, set the MPP field on the resulting
// payload to nil.
if _, ok := parsedTypes[record.MPPOnionType]; !ok {
mpp = nil
}
// If no AMP field was parsed, set the MPP field on the resulting
// payload to nil.
if _, ok := parsedTypes[record.AMPOnionType]; !ok {
amp = nil
}
// If no encrypted data was parsed, set the field on our resulting
// payload to nil.
if _, ok := parsedTypes[record.EncryptedDataOnionType]; !ok {
encryptedData = nil
}
// If no metadata field was parsed, set the metadata field on the
// resulting payload to nil.
if _, ok := parsedTypes[record.MetadataOnionType]; !ok {
metadata = nil
}
// Filter out the custom records.
customRecords := NewCustomRecords(parsedTypes)
return &Payload{
FwdInfo: ForwardingInfo{
NextHop: lnwire.NewShortChanIDFromInt(cid),
AmountToForward: lnwire.MilliSatoshi(amt),
OutgoingCTLV: cltv,
},
MPP: mpp,
AMP: amp,
metadata: metadata,
encryptedData: encryptedData,
blindingPoint: blindingPoint,
customRecords: customRecords,
totalAmtMsat: lnwire.MilliSatoshi(totalAmtMsat),
}, parsedTypes, nil
}
// ValidateTLVPayload validates the TLV fields that were included in a TLV
// payload.
func ValidateTLVPayload(parsedTypes map[tlv.Type][]byte,
finalHop bool, updateAddBlinding bool) error {
// Validate whether the sender properly included or omitted tlv records
// in accordance with BOLT 04.
err := ValidateParsedPayloadTypes(
parsedTypes, finalHop, updateAddBlinding,
)
if err != nil {
return err
}
// Check for violation of the rules for mandatory fields.
violatingType := getMinRequiredViolation(parsedTypes)
if violatingType != nil {
return ErrInvalidPayload{
Type: *violatingType,
Violation: RequiredViolation,
FinalHop: finalHop,
}
}
return nil
}
// ForwardingInfo returns the basic parameters required for HTLC forwarding,
// e.g. amount, cltv, and next hop.
func (h *Payload) ForwardingInfo() ForwardingInfo {
return h.FwdInfo
}
// NewCustomRecords filters the types parsed from the tlv stream for custom
// records.
func NewCustomRecords(parsedTypes tlv.TypeMap) record.CustomSet {
customRecords := make(record.CustomSet)
for t, parseResult := range parsedTypes {
if parseResult == nil || t < record.CustomTypeStart {
continue
}
customRecords[uint64(t)] = parseResult
}
return customRecords
}
// ValidateParsedPayloadTypes checks the types parsed from a hop payload to
// ensure that the proper fields are either included or omitted. The finalHop
// boolean should be true if the payload was parsed for an exit hop. The
// requirements for this method are described in BOLT 04.
func ValidateParsedPayloadTypes(parsedTypes tlv.TypeMap,
isFinalHop, updateAddBlinding bool) error {
_, hasAmt := parsedTypes[record.AmtOnionType]
_, hasLockTime := parsedTypes[record.LockTimeOnionType]
_, hasNextHop := parsedTypes[record.NextHopOnionType]
_, hasMPP := parsedTypes[record.MPPOnionType]
_, hasAMP := parsedTypes[record.AMPOnionType]
_, hasEncryptedData := parsedTypes[record.EncryptedDataOnionType]
_, hasBlinding := parsedTypes[record.BlindingPointOnionType]
// All cleartext hops (including final hop) and the final hop in a
// blinded path require the forwading amount and expiry TLVs to be set.
needFwdInfo := isFinalHop || !hasEncryptedData
// No blinded hops should have a next hop specified, and only the final
// hop in a cleartext route should exclude it.
needNextHop := !(hasEncryptedData || isFinalHop)
switch {
// Both blinding point being set is invalid.
case hasBlinding && updateAddBlinding:
return ErrInvalidPayload{
Type: record.BlindingPointOnionType,
Violation: IncludedViolation,
FinalHop: isFinalHop,
}
// If encrypted data is not provided, blinding points should not be
// set.
case !hasEncryptedData && (hasBlinding || updateAddBlinding):
return ErrInvalidPayload{
Type: record.EncryptedDataOnionType,
Violation: OmittedViolation,
FinalHop: isFinalHop,
}
// If encrypted data is present, we require that one blinding point
// is set.
case hasEncryptedData && !(hasBlinding || updateAddBlinding):
return ErrInvalidPayload{
Type: record.EncryptedDataOnionType,
Violation: IncludedViolation,
FinalHop: isFinalHop,
}
// Hops that need forwarding info must include an amount to forward.
case needFwdInfo && !hasAmt:
return ErrInvalidPayload{
Type: record.AmtOnionType,
Violation: OmittedViolation,
FinalHop: isFinalHop,
}
// Hops that need forwarding info must include a cltv expiry.
case needFwdInfo && !hasLockTime:
return ErrInvalidPayload{
Type: record.LockTimeOnionType,
Violation: OmittedViolation,
FinalHop: isFinalHop,
}
// Hops that don't need forwarding info shouldn't have an amount TLV.
case !needFwdInfo && hasAmt:
return ErrInvalidPayload{
Type: record.AmtOnionType,
Violation: IncludedViolation,
FinalHop: isFinalHop,
}
// Hops that don't need forwarding info shouldn't have a cltv TLV.
case !needFwdInfo && hasLockTime:
return ErrInvalidPayload{
Type: record.LockTimeOnionType,
Violation: IncludedViolation,
FinalHop: isFinalHop,
}
// The exit hop and all blinded hops should omit the next hop id.
case !needNextHop && hasNextHop:
return ErrInvalidPayload{
Type: record.NextHopOnionType,
Violation: IncludedViolation,
FinalHop: isFinalHop,
}
// Require that the next hop is set for intermediate hops in regular
// routes.
case needNextHop && !hasNextHop:
return ErrInvalidPayload{
Type: record.NextHopOnionType,
Violation: OmittedViolation,
FinalHop: isFinalHop,
}
// Intermediate nodes should never receive MPP fields.
case !isFinalHop && hasMPP:
return ErrInvalidPayload{
Type: record.MPPOnionType,
Violation: IncludedViolation,
FinalHop: isFinalHop,
}
// Intermediate nodes should never receive AMP fields.
case !isFinalHop && hasAMP:
return ErrInvalidPayload{
Type: record.AMPOnionType,
Violation: IncludedViolation,
FinalHop: isFinalHop,
}
}
return nil
}
// MultiPath returns the record corresponding the option_mpp parsed from the
// onion payload.
func (h *Payload) MultiPath() *record.MPP {
return h.MPP
}
// AMPRecord returns the record corresponding with option_amp parsed from the
// onion payload.
func (h *Payload) AMPRecord() *record.AMP {
return h.AMP
}
// CustomRecords returns the custom tlv type records that were parsed from the
// payload.
func (h *Payload) CustomRecords() record.CustomSet {
return h.customRecords
}
// EncryptedData returns the route blinding encrypted data parsed from the
// onion payload.
func (h *Payload) EncryptedData() []byte {
return h.encryptedData
}
// BlindingPoint returns the route blinding point parsed from the onion payload.
func (h *Payload) BlindingPoint() *btcec.PublicKey {
return h.blindingPoint
}
// PathID returns the path ID that was encoded in the final hop payload of a
// blinded payment.
func (h *Payload) PathID() *chainhash.Hash {
return h.FwdInfo.PathID
}
// Metadata returns the additional data that is sent along with the
// payment to the payee.
func (h *Payload) Metadata() []byte {
return h.metadata
}
// TotalAmtMsat returns the total amount sent to the final hop, as set by the
// payee.
func (h *Payload) TotalAmtMsat() lnwire.MilliSatoshi {
return h.totalAmtMsat
}
// getMinRequiredViolation checks for unrecognized required (even) fields in the
// standard range and returns the lowest required type. Always returning the
// lowest required type allows a failure message to be deterministic.
func getMinRequiredViolation(set tlv.TypeMap) *tlv.Type {
var (
requiredViolation bool
minRequiredViolationType tlv.Type
)
for t, parseResult := range set {
// If a type is even but not known to us, we cannot process the
// payload. We are required to understand a field that we don't
// support.
//
// We always accept custom fields, because a higher level
// application may understand them.
if parseResult == nil || t%2 != 0 ||
t >= record.CustomTypeStart {
continue
}
if !requiredViolation || t < minRequiredViolationType {
minRequiredViolationType = t
}
requiredViolation = true
}
if requiredViolation {
return &minRequiredViolationType
}
return nil
}
// ValidateBlindedRouteData performs the additional validation that is
// required for payments that rely on data provided in an encrypted blob to
// be forwarded. We enforce the blinded route's maximum expiry height so that
// the route "expires" and a malicious party does not have endless opportunity
// to probe the blinded route and compare it to updated channel policies in
// the network.
func ValidateBlindedRouteData(blindedData *record.BlindedRouteData,
incomingAmount lnwire.MilliSatoshi, incomingTimelock uint32) error {
// Bolt 04 notes that we should enforce payment constraints _if_ they
// are present, so we do not fail if not provided.
var err error
blindedData.Constraints.WhenSome(
func(c tlv.RecordT[tlv.TlvType12, record.PaymentConstraints]) {
// MUST fail if the expiry is greater than
// max_cltv_expiry.
if incomingTimelock > c.Val.MaxCltvExpiry {
err = ErrInvalidPayload{
Type: record.LockTimeOnionType,
Violation: InsufficientViolation,
}
}
// MUST fail if the amount is below htlc_minimum_msat.
if incomingAmount < c.Val.HtlcMinimumMsat {
err = ErrInvalidPayload{
Type: record.AmtOnionType,
Violation: InsufficientViolation,
}
}
},
)
if err != nil {
return err
}
// Fail if we don't understand any features (even or odd), because we
// expect the features to have been set from our announcement. If the
// feature vector TLV is not included, it's interpreted as an empty
// vector (no validation required).
// expect the features to have been set from our announcement.
//
// Note that we do not yet check the features that the blinded payment
// is using against our own features, because there are currently no
// payment-related features that they utilize other than tlv-onion,
// which is implicitly supported.
blindedData.Features.WhenSome(
func(f tlv.RecordT[tlv.TlvType14, lnwire.FeatureVector]) {
if f.Val.UnknownFeatures() {
err = ErrInvalidPayload{
Type: 14,
Violation: IncludedViolation,
}
}
},
)
if err != nil {
return err
}
return nil
}
// ValidatePayloadWithBlinded validates a payload against the contents of
// its encrypted data blob.
func ValidatePayloadWithBlinded(isFinalHop bool,
payloadParsed map[tlv.Type][]byte) error {
// Blinded routes restrict the presence of TLVs more strictly than
// regular routes, check that intermediate and final hops only have
// the TLVs the spec allows them to have.
allowedTLVs := map[tlv.Type]bool{
record.EncryptedDataOnionType: true,
record.BlindingPointOnionType: true,
}
if isFinalHop {
allowedTLVs[record.AmtOnionType] = true
allowedTLVs[record.LockTimeOnionType] = true
allowedTLVs[record.TotalAmtMsatBlindedType] = true
}
for tlvType := range payloadParsed {
if _, ok := allowedTLVs[tlvType]; ok {
continue
}
return ErrInvalidPayload{
Type: tlvType,
Violation: IncludedViolation,
FinalHop: isFinalHop,
}
}
return nil
}
package htlcswitch
import (
"fmt"
"strings"
"sync"
"time"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/subscribe"
)
// HtlcNotifier notifies clients of htlc forwards, failures and settles for
// htlcs that the switch handles. It takes subscriptions for its events and
// notifies them when htlc events occur. These are served on a best-effort
// basis; events are not persisted, delivery is not guaranteed (in the event
// of a crash in the switch, forward events may be lost) and some events may
// be replayed upon restart. Events consumed from this package should be
// de-duplicated by the htlc's unique combination of incoming+outgoing circuit
// and not relied upon for critical operations.
//
// The htlc notifier sends the following kinds of events:
// Forwarding Event:
// - Represents a htlc which is forwarded onward from our node.
// - Present for htlc forwards through our node and local sends.
//
// Link Failure Event:
// - Indicates that a htlc has failed on our incoming or outgoing link,
// with an incoming boolean which indicates where the failure occurred.
// - Incoming link failures are present for failed attempts to pay one of
// our invoices (insufficient amount or mpp timeout, for example) and for
// forwards that we cannot decode to forward onwards.
// - Outgoing link failures are present for forwards or local payments that
// do not meet our outgoing link's policy (insufficient fees, for example)
// and when we fail to forward the payment on (insufficient outgoing
// capacity, or an unknown outgoing link).
//
// Forwarding Failure Event:
// - Forwarding failures indicate that a htlc we forwarded has failed at
// another node down the route.
// - Present for local sends and htlc forwards which fail after they left
// our node.
//
// Settle event:
// - Settle events are present when a htlc which we added is settled through
// the release of a preimage.
// - Present for local receives, and successful local sends or forwards.
//
// Each htlc is identified by its incoming and outgoing circuit key. Htlcs,
// and their subsequent settles or fails, can be identified by the combination
// of incoming and outgoing circuits. Note that receives to our node will
// have a zero outgoing circuit key because the htlc terminates at our
// node, and sends from our node will have a zero incoming circuit key because
// the send originates at our node.
type HtlcNotifier struct {
started sync.Once
stopped sync.Once
// now returns the current time, it is set in the htlcnotifier to allow
// for timestamp mocking in tests.
now func() time.Time
ntfnServer *subscribe.Server
}
// NewHtlcNotifier creates a new HtlcNotifier which gets htlc forwarded,
// failed and settled events from links our node has established with peers
// and sends notifications to subscribing clients.
func NewHtlcNotifier(now func() time.Time) *HtlcNotifier {
return &HtlcNotifier{
now: now,
ntfnServer: subscribe.NewServer(),
}
}
// Start starts the HtlcNotifier and all goroutines it needs to consume
// events and provide subscriptions to clients.
func (h *HtlcNotifier) Start() error {
var err error
h.started.Do(func() {
log.Info("HtlcNotifier starting")
err = h.ntfnServer.Start()
})
return err
}
// Stop signals the notifier for a graceful shutdown.
func (h *HtlcNotifier) Stop() error {
var err error
h.stopped.Do(func() {
log.Info("HtlcNotifier shutting down...")
defer log.Debug("HtlcNotifier shutdown complete")
if err = h.ntfnServer.Stop(); err != nil {
log.Warnf("error stopping htlc notifier: %v", err)
}
})
return err
}
// SubscribeHtlcEvents returns a subscribe.Client that will receive updates
// any time the server is made aware of a new event.
func (h *HtlcNotifier) SubscribeHtlcEvents() (*subscribe.Client, error) {
return h.ntfnServer.Subscribe()
}
// HtlcKey uniquely identifies the htlc.
type HtlcKey struct {
// IncomingCircuit is the channel an htlc id of the incoming htlc.
IncomingCircuit models.CircuitKey
// OutgoingCircuit is the channel and htlc id of the outgoing htlc.
OutgoingCircuit models.CircuitKey
}
// String returns a string representation of a htlc key.
func (k HtlcKey) String() string {
switch {
case k.IncomingCircuit.ChanID == hop.Source:
return k.OutgoingCircuit.String()
case k.OutgoingCircuit.ChanID == hop.Exit:
return k.IncomingCircuit.String()
default:
return fmt.Sprintf("%v -> %v", k.IncomingCircuit,
k.OutgoingCircuit)
}
}
// HtlcInfo provides the details of a htlc that our node has processed. For
// forwards, incoming and outgoing values are set, whereas sends and receives
// will only have outgoing or incoming details set.
type HtlcInfo struct {
// IncomingTimelock is the time lock of the htlc on our incoming
// channel.
IncomingTimeLock uint32
// OutgoingTimelock is the time lock the htlc on our outgoing channel.
OutgoingTimeLock uint32
// IncomingAmt is the amount of the htlc on our incoming channel.
IncomingAmt lnwire.MilliSatoshi
// OutgoingAmt is the amount of the htlc on our outgoing channel.
OutgoingAmt lnwire.MilliSatoshi
}
// String returns a string representation of a htlc.
func (h HtlcInfo) String() string {
var details []string
// If the incoming information is not zero, as is the case for a send,
// we include the incoming amount and timelock.
if h.IncomingAmt != 0 || h.IncomingTimeLock != 0 {
str := fmt.Sprintf("incoming amount: %v, "+
"incoming timelock: %v", h.IncomingAmt,
h.IncomingTimeLock)
details = append(details, str)
}
// If the outgoing information is not zero, as is the case for a
// receive, we include the outgoing amount and timelock.
if h.OutgoingAmt != 0 || h.OutgoingTimeLock != 0 {
str := fmt.Sprintf("outgoing amount: %v, "+
"outgoing timelock: %v", h.OutgoingAmt,
h.OutgoingTimeLock)
details = append(details, str)
}
return strings.Join(details, ", ")
}
// HtlcEventType represents the type of event that a htlc was part of.
type HtlcEventType int
const (
// HtlcEventTypeSend represents a htlc that was part of a send from
// our node.
HtlcEventTypeSend HtlcEventType = iota
// HtlcEventTypeReceive represents a htlc that was part of a receive
// to our node.
HtlcEventTypeReceive
// HtlcEventTypeForward represents a htlc that was forwarded through
// our node.
HtlcEventTypeForward
)
// String returns a string representation of a htlc event type.
func (h HtlcEventType) String() string {
switch h {
case HtlcEventTypeSend:
return "send"
case HtlcEventTypeReceive:
return "receive"
case HtlcEventTypeForward:
return "forward"
default:
return "unknown"
}
}
// ForwardingEvent represents a htlc that was forwarded onwards from our node.
// Sends which originate from our node will report forward events with zero
// incoming circuits in their htlc key.
type ForwardingEvent struct {
// HtlcKey uniquely identifies the htlc, and can be used to match the
// forwarding event with subsequent settle/fail events.
HtlcKey
// HtlcInfo contains details about the htlc.
HtlcInfo
// HtlcEventType classifies the event as part of a local send or
// receive, or as part of a forward.
HtlcEventType
// Timestamp is the time when this htlc was forwarded.
Timestamp time.Time
}
// LinkFailEvent describes a htlc that failed on our incoming or outgoing
// link. The incoming bool is true for failures on incoming links, and false
// for failures on outgoing links. The failure reason is provided by a lnwire
// failure message which is enriched with a failure detail in the cases where
// the wire failure message does not contain full information about the
// failure.
type LinkFailEvent struct {
// HtlcKey uniquely identifies the htlc.
HtlcKey
// HtlcInfo contains details about the htlc.
HtlcInfo
// HtlcEventType classifies the event as part of a local send or
// receive, or as part of a forward.
HtlcEventType
// LinkError is the reason that we failed the htlc.
LinkError *LinkError
// Incoming is true if the htlc was failed on an incoming link.
// If it failed on the outgoing link, it is false.
Incoming bool
// Timestamp is the time when the link failure occurred.
Timestamp time.Time
}
// ForwardingFailEvent represents a htlc failure which occurred down the line
// after we forwarded a htlc onwards. An error is not included in this event
// because errors returned down the route are encrypted. HtlcInfo is not
// reliably available for forwarding failures, so it is omitted. These events
// should be matched with their corresponding forward event to obtain this
// information.
type ForwardingFailEvent struct {
// HtlcKey uniquely identifies the htlc, and can be used to match the
// htlc with its corresponding forwarding event.
HtlcKey
// HtlcEventType classifies the event as part of a local send or
// receive, or as part of a forward.
HtlcEventType
// Timestamp is the time when the forwarding failure was received.
Timestamp time.Time
}
// SettleEvent represents a htlc that was settled. HtlcInfo is not reliably
// available for forwarding failures, so it is omitted. These events should
// be matched with corresponding forward events or invoices (for receives)
// to obtain additional information about the htlc.
type SettleEvent struct {
// HtlcKey uniquely identifies the htlc, and can be used to match
// forwards with their corresponding forwarding event.
HtlcKey
// Preimage that was released for settling the htlc.
Preimage lntypes.Preimage
// HtlcEventType classifies the event as part of a local send or
// receive, or as part of a forward.
HtlcEventType
// Timestamp is the time when this htlc was settled.
Timestamp time.Time
}
type FinalHtlcEvent struct {
CircuitKey
Settled bool
// Offchain is indicating whether the htlc was resolved off-chain.
Offchain bool
// Timestamp is the time when this htlc was settled.
Timestamp time.Time
}
// NotifyForwardingEvent notifies the HtlcNotifier than a htlc has been
// forwarded.
//
// Note this is part of the htlcNotifier interface.
func (h *HtlcNotifier) NotifyForwardingEvent(key HtlcKey, info HtlcInfo,
eventType HtlcEventType) {
event := &ForwardingEvent{
HtlcKey: key,
HtlcInfo: info,
HtlcEventType: eventType,
Timestamp: h.now(),
}
log.Tracef("Notifying forward event: %v over %v, %v", eventType, key,
info)
if err := h.ntfnServer.SendUpdate(event); err != nil {
log.Warnf("Unable to send forwarding event: %v", err)
}
}
// NotifyLinkFailEvent notifies that a htlc has failed on our incoming
// or outgoing link.
//
// Note this is part of the htlcNotifier interface.
func (h *HtlcNotifier) NotifyLinkFailEvent(key HtlcKey, info HtlcInfo,
eventType HtlcEventType, linkErr *LinkError, incoming bool) {
event := &LinkFailEvent{
HtlcKey: key,
HtlcInfo: info,
HtlcEventType: eventType,
LinkError: linkErr,
Incoming: incoming,
Timestamp: h.now(),
}
log.Tracef("Notifying link failure event: %v over %v, %v", eventType,
key, info)
if err := h.ntfnServer.SendUpdate(event); err != nil {
log.Warnf("Unable to send link fail event: %v", err)
}
}
// NotifyForwardingFailEvent notifies the HtlcNotifier that a htlc we
// forwarded has failed down the line.
//
// Note this is part of the htlcNotifier interface.
func (h *HtlcNotifier) NotifyForwardingFailEvent(key HtlcKey,
eventType HtlcEventType) {
event := &ForwardingFailEvent{
HtlcKey: key,
HtlcEventType: eventType,
Timestamp: h.now(),
}
log.Tracef("Notifying forwarding failure event: %v over %v", eventType,
key)
if err := h.ntfnServer.SendUpdate(event); err != nil {
log.Warnf("Unable to send forwarding fail event: %v", err)
}
}
// NotifySettleEvent notifies the HtlcNotifier that a htlc that we committed
// to as part of a forward or a receive to our node has been settled.
//
// Note this is part of the htlcNotifier interface.
func (h *HtlcNotifier) NotifySettleEvent(key HtlcKey,
preimage lntypes.Preimage, eventType HtlcEventType) {
event := &SettleEvent{
HtlcKey: key,
Preimage: preimage,
HtlcEventType: eventType,
Timestamp: h.now(),
}
log.Tracef("Notifying settle event: %v over %v", eventType, key)
if err := h.ntfnServer.SendUpdate(event); err != nil {
log.Warnf("Unable to send settle event: %v", err)
}
}
// NotifyFinalHtlcEvent notifies the HtlcNotifier that the final outcome for an
// htlc has been determined.
//
// Note this is part of the htlcNotifier interface.
func (h *HtlcNotifier) NotifyFinalHtlcEvent(key models.CircuitKey,
info channeldb.FinalHtlcInfo) {
event := &FinalHtlcEvent{
CircuitKey: key,
Settled: info.Settled,
Offchain: info.Offchain,
Timestamp: h.now(),
}
log.Tracef("Notifying final settle event: %v", key)
if err := h.ntfnServer.SendUpdate(event); err != nil {
log.Warnf("Unable to send settle event: %v", err)
}
}
// newHtlc key returns a htlc key for the packet provided. If the packet
// has a zero incoming channel ID, the packet is for one of our own sends,
// which has the payment id stashed in the incoming htlc id. If this is the
// case, we replace the incoming htlc id with zero so that the notifier
// consistently reports zero circuit keys for events that terminate or
// originate at our node.
func newHtlcKey(pkt *htlcPacket) HtlcKey {
htlcKey := HtlcKey{
IncomingCircuit: models.CircuitKey{
ChanID: pkt.incomingChanID,
HtlcID: pkt.incomingHTLCID,
},
OutgoingCircuit: CircuitKey{
ChanID: pkt.outgoingChanID,
HtlcID: pkt.outgoingHTLCID,
},
}
// If the packet has a zero incoming channel ID, it is a send that was
// initiated at our node. If this is the case, our internal pid is in
// the incoming htlc ID, so we overwrite it with 0 for notification
// purposes.
if pkt.incomingChanID == hop.Source {
htlcKey.IncomingCircuit.HtlcID = 0
}
return htlcKey
}
// newHtlcInfo returns HtlcInfo for the packet provided.
func newHtlcInfo(pkt *htlcPacket) HtlcInfo {
return HtlcInfo{
IncomingTimeLock: pkt.incomingTimeout,
OutgoingTimeLock: pkt.outgoingTimeout,
IncomingAmt: pkt.incomingAmount,
OutgoingAmt: pkt.amount,
}
}
// getEventType returns the htlc type based on the fields set in the htlc
// packet. Sends that originate at our node have the source (zero) incoming
// channel ID. Receives to our node have the exit (zero) outgoing channel ID
// and forwards have both fields set.
func getEventType(pkt *htlcPacket) HtlcEventType {
switch {
case pkt.incomingChanID == hop.Source:
return HtlcEventTypeSend
case pkt.outgoingChanID == hop.Exit:
return HtlcEventTypeReceive
default:
return HtlcEventTypeForward
}
}
package htlcswitch
import (
"crypto/sha256"
"fmt"
"sync"
"sync/atomic"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// ErrFwdNotExists is an error returned when the caller tries to resolve
// a forward that doesn't exist anymore.
ErrFwdNotExists = errors.New("forward does not exist")
// ErrUnsupportedFailureCode when processing of an unsupported failure
// code is attempted.
ErrUnsupportedFailureCode = errors.New("unsupported failure code")
errBlockStreamStopped = errors.New("block epoch stream stopped")
)
// InterceptableSwitch is an implementation of ForwardingSwitch interface.
// This implementation is used like a proxy that wraps the switch and
// intercepts forward requests. A reference to the Switch is held in order
// to communicate back the interception result where the options are:
// Resume - forwards the original request to the switch as is.
// Settle - routes UpdateFulfillHTLC to the originating link.
// Fail - routes UpdateFailHTLC to the originating link.
type InterceptableSwitch struct {
started atomic.Bool
stopped atomic.Bool
// htlcSwitch is the underline switch
htlcSwitch *Switch
// intercepted is where we stream all intercepted packets coming from
// the switch.
intercepted chan *interceptedPackets
// resolutionChan is where we stream all responses coming from the
// interceptor client.
resolutionChan chan *fwdResolution
onchainIntercepted chan InterceptedForward
// interceptorRegistration is a channel that we use to synchronize
// client connect and disconnect.
interceptorRegistration chan ForwardInterceptor
// requireInterceptor indicates whether processing should block if no
// interceptor is connected.
requireInterceptor bool
// interceptor is the handler for intercepted packets.
interceptor ForwardInterceptor
// heldHtlcSet keeps track of outstanding intercepted forwards.
heldHtlcSet *heldHtlcSet
// cltvRejectDelta defines the number of blocks before the expiry of the
// htlc where we no longer intercept it and instead cancel it back.
cltvRejectDelta uint32
// cltvInterceptDelta defines the number of blocks before the expiry of
// the htlc where we don't intercept anymore. This value must be greater
// than CltvRejectDelta, because we don't want to offer htlcs to the
// interceptor client for which there is no time left to resolve them
// anymore.
cltvInterceptDelta uint32
// notifier is an instance of a chain notifier that we'll use to signal
// the switch when a new block has arrived.
notifier chainntnfs.ChainNotifier
// blockEpochStream is an active block epoch event stream backed by an
// active ChainNotifier instance. This will be used to retrieve the
// latest height of the chain.
blockEpochStream *chainntnfs.BlockEpochEvent
// currentHeight is the currently best known height.
currentHeight int32
wg sync.WaitGroup
quit chan struct{}
}
type interceptedPackets struct {
packets []*htlcPacket
linkQuit <-chan struct{}
isReplay bool
}
// FwdAction defines the various resolution types.
type FwdAction int
const (
// FwdActionResume forwards the intercepted packet to the switch.
FwdActionResume FwdAction = iota
// FwdActionSettle settles the intercepted packet with a preimage.
FwdActionSettle
// FwdActionFail fails the intercepted packet back to the sender.
FwdActionFail
// FwdActionResumeModified forwards the intercepted packet to the switch
// with modifications.
FwdActionResumeModified
)
// FwdResolution defines the action to be taken on an intercepted packet.
type FwdResolution struct {
// Key is the incoming circuit key of the htlc.
Key models.CircuitKey
// Action is the action to take on the intercepted htlc.
Action FwdAction
// Preimage is the preimage that is to be used for settling if Action is
// FwdActionSettle.
Preimage lntypes.Preimage
// InAmountMsat is the amount that is to be used for validating if
// Action is FwdActionResumeModified.
InAmountMsat fn.Option[lnwire.MilliSatoshi]
// OutAmountMsat is the amount that is to be used for forwarding if
// Action is FwdActionResumeModified.
OutAmountMsat fn.Option[lnwire.MilliSatoshi]
// OutWireCustomRecords is the custom records that are to be used for
// forwarding if Action is FwdActionResumeModified.
OutWireCustomRecords fn.Option[lnwire.CustomRecords]
// FailureMessage is the encrypted failure message that is to be passed
// back to the sender if action is FwdActionFail.
FailureMessage []byte
// FailureCode is the failure code that is to be passed back to the
// sender if action is FwdActionFail.
FailureCode lnwire.FailCode
}
type fwdResolution struct {
resolution *FwdResolution
errChan chan error
}
// InterceptableSwitchConfig contains the configuration of InterceptableSwitch.
type InterceptableSwitchConfig struct {
// Switch is a reference to the actual switch implementation that
// packets get sent to on resume.
Switch *Switch
// Notifier is an instance of a chain notifier that we'll use to signal
// the switch when a new block has arrived.
Notifier chainntnfs.ChainNotifier
// CltvRejectDelta defines the number of blocks before the expiry of the
// htlc where we auto-fail an intercepted htlc to prevent channel
// force-closure.
CltvRejectDelta uint32
// CltvInterceptDelta defines the number of blocks before the expiry of
// the htlc where we don't intercept anymore. This value must be greater
// than CltvRejectDelta, because we don't want to offer htlcs to the
// interceptor client for which there is no time left to resolve them
// anymore.
CltvInterceptDelta uint32
// RequireInterceptor indicates whether processing should block if no
// interceptor is connected.
RequireInterceptor bool
}
// NewInterceptableSwitch returns an instance of InterceptableSwitch.
func NewInterceptableSwitch(cfg *InterceptableSwitchConfig) (
*InterceptableSwitch, error) {
if cfg.CltvInterceptDelta <= cfg.CltvRejectDelta {
return nil, fmt.Errorf("cltv intercept delta %v not greater "+
"than cltv reject delta %v",
cfg.CltvInterceptDelta, cfg.CltvRejectDelta)
}
return &InterceptableSwitch{
htlcSwitch: cfg.Switch,
intercepted: make(chan *interceptedPackets),
onchainIntercepted: make(chan InterceptedForward),
interceptorRegistration: make(chan ForwardInterceptor),
heldHtlcSet: newHeldHtlcSet(),
resolutionChan: make(chan *fwdResolution),
requireInterceptor: cfg.RequireInterceptor,
cltvRejectDelta: cfg.CltvRejectDelta,
cltvInterceptDelta: cfg.CltvInterceptDelta,
notifier: cfg.Notifier,
quit: make(chan struct{}),
}, nil
}
// SetInterceptor sets the ForwardInterceptor to be used. A nil argument
// unregisters the current interceptor.
func (s *InterceptableSwitch) SetInterceptor(
interceptor ForwardInterceptor) {
// Synchronize setting the handler with the main loop to prevent race
// conditions.
select {
case s.interceptorRegistration <- interceptor:
case <-s.quit:
}
}
func (s *InterceptableSwitch) Start() error {
log.Info("InterceptableSwitch starting...")
if s.started.Swap(true) {
return fmt.Errorf("InterceptableSwitch started more than once")
}
blockEpochStream, err := s.notifier.RegisterBlockEpochNtfn(nil)
if err != nil {
return err
}
s.blockEpochStream = blockEpochStream
s.wg.Add(1)
go func() {
defer s.wg.Done()
err := s.run()
if err != nil {
log.Errorf("InterceptableSwitch stopped: %v", err)
}
}()
log.Debug("InterceptableSwitch started")
return nil
}
func (s *InterceptableSwitch) Stop() error {
log.Info("InterceptableSwitch shutting down...")
if s.stopped.Swap(true) {
return fmt.Errorf("InterceptableSwitch stopped more than once")
}
close(s.quit)
s.wg.Wait()
// We need to check whether the start routine run and initialized the
// `blockEpochStream`.
if s.blockEpochStream != nil {
s.blockEpochStream.Cancel()
}
log.Debug("InterceptableSwitch shutdown complete")
return nil
}
func (s *InterceptableSwitch) run() error {
// The block epoch stream will immediately stream the current height.
// Read it out here.
select {
case currentBlock, ok := <-s.blockEpochStream.Epochs:
if !ok {
return errBlockStreamStopped
}
s.currentHeight = currentBlock.Height
case <-s.quit:
return nil
}
log.Debugf("InterceptableSwitch running: height=%v, "+
"requireInterceptor=%v", s.currentHeight, s.requireInterceptor)
for {
select {
// An interceptor registration or de-registration came in.
case interceptor := <-s.interceptorRegistration:
s.setInterceptor(interceptor)
case packets := <-s.intercepted:
var notIntercepted []*htlcPacket
for _, p := range packets.packets {
intercepted, err := s.interceptForward(
p, packets.isReplay,
)
if err != nil {
return err
}
if !intercepted {
notIntercepted = append(
notIntercepted, p,
)
}
}
err := s.htlcSwitch.ForwardPackets(
packets.linkQuit, notIntercepted...,
)
if err != nil {
log.Errorf("Cannot forward packets: %v", err)
}
case fwd := <-s.onchainIntercepted:
// For on-chain interceptions, we don't know if it has
// already been offered before. This information is in
// the forwarding package which isn't easily accessible
// from contractcourt. It is likely though that it was
// already intercepted in the off-chain flow. And even
// if not, it is safe to signal replay so that we won't
// unexpectedly skip over this htlc.
if _, err := s.forward(fwd, true); err != nil {
return err
}
case res := <-s.resolutionChan:
res.errChan <- s.resolve(res.resolution)
case currentBlock, ok := <-s.blockEpochStream.Epochs:
if !ok {
return errBlockStreamStopped
}
s.currentHeight = currentBlock.Height
// A new block is appended. Fail any held htlcs that
// expire at this height to prevent channel force-close.
s.failExpiredHtlcs()
case <-s.quit:
return nil
}
}
}
func (s *InterceptableSwitch) failExpiredHtlcs() {
s.heldHtlcSet.popAutoFails(
uint32(s.currentHeight),
func(fwd InterceptedForward) {
err := fwd.FailWithCode(
lnwire.CodeTemporaryChannelFailure,
)
if err != nil {
log.Errorf("Cannot fail packet: %v", err)
}
},
)
}
func (s *InterceptableSwitch) sendForward(fwd InterceptedForward) {
err := s.interceptor(fwd.Packet())
if err != nil {
// Only log the error. If we couldn't send the packet, we assume
// that the interceptor will reconnect so that we can retry.
log.Debugf("Interceptor cannot handle forward: %v", err)
}
}
func (s *InterceptableSwitch) setInterceptor(interceptor ForwardInterceptor) {
s.interceptor = interceptor
// Replay all currently held htlcs. When an interceptor is not required,
// there may be none because they've been cleared after the previous
// disconnect.
if interceptor != nil {
log.Debugf("Interceptor connected")
s.heldHtlcSet.forEach(s.sendForward)
return
}
// The interceptor disconnects. If an interceptor is required, keep the
// held htlcs.
if s.requireInterceptor {
log.Infof("Interceptor disconnected, retaining held packets")
return
}
// Interceptor is not required. Release held forwards.
log.Infof("Interceptor disconnected, resolving held packets")
s.heldHtlcSet.popAll(func(fwd InterceptedForward) {
err := fwd.Resume()
if err != nil {
log.Errorf("Failed to resume hold forward %v", err)
}
})
}
// resolve processes a HTLC given the resolution type specified by the
// intercepting client.
func (s *InterceptableSwitch) resolve(res *FwdResolution) error {
intercepted, err := s.heldHtlcSet.pop(res.Key)
if err != nil {
return err
}
switch res.Action {
case FwdActionResume:
return intercepted.Resume()
case FwdActionResumeModified:
return intercepted.ResumeModified(
res.InAmountMsat, res.OutAmountMsat,
res.OutWireCustomRecords,
)
case FwdActionSettle:
return intercepted.Settle(res.Preimage)
case FwdActionFail:
if len(res.FailureMessage) > 0 {
return intercepted.Fail(res.FailureMessage)
}
return intercepted.FailWithCode(res.FailureCode)
default:
return fmt.Errorf("unrecognized action %v", res.Action)
}
}
// Resolve resolves an intercepted packet.
func (s *InterceptableSwitch) Resolve(res *FwdResolution) error {
internalRes := &fwdResolution{
resolution: res,
errChan: make(chan error, 1),
}
select {
case s.resolutionChan <- internalRes:
case <-s.quit:
return errors.New("switch shutting down")
}
select {
case err := <-internalRes.errChan:
return err
case <-s.quit:
return errors.New("switch shutting down")
}
}
// ForwardPackets attempts to forward the batch of htlcs to a connected
// interceptor. If the interceptor signals the resume action, the htlcs are
// forwarded to the switch. The link's quit signal should be provided to allow
// cancellation of forwarding during link shutdown.
func (s *InterceptableSwitch) ForwardPackets(linkQuit <-chan struct{},
isReplay bool, packets ...*htlcPacket) error {
// Synchronize with the main event loop. This should be light in the
// case where there is no interceptor.
select {
case s.intercepted <- &interceptedPackets{
packets: packets,
linkQuit: linkQuit,
isReplay: isReplay,
}:
case <-linkQuit:
log.Debugf("Forward cancelled because link quit")
case <-s.quit:
return errors.New("interceptable switch quit")
}
return nil
}
// ForwardPacket forwards a single htlc to the external interceptor.
func (s *InterceptableSwitch) ForwardPacket(
fwd InterceptedForward) error {
select {
case s.onchainIntercepted <- fwd:
case <-s.quit:
return errors.New("interceptable switch quit")
}
return nil
}
// interceptForward forwards the packet to the external interceptor after
// checking the interception criteria.
func (s *InterceptableSwitch) interceptForward(packet *htlcPacket,
isReplay bool) (bool, error) {
switch htlc := packet.htlc.(type) {
case *lnwire.UpdateAddHTLC:
// We are not interested in intercepting initiated payments.
if packet.incomingChanID == hop.Source {
return false, nil
}
intercepted := &interceptedForward{
htlc: htlc,
packet: packet,
htlcSwitch: s.htlcSwitch,
autoFailHeight: int32(packet.incomingTimeout -
s.cltvRejectDelta),
}
// Handle forwards that are too close to expiry.
handled, err := s.handleExpired(intercepted)
if err != nil {
log.Errorf("Error handling intercepted htlc "+
"that expires too soon: circuit=%v, "+
"incoming_timeout=%v, err=%v",
packet.inKey(), packet.incomingTimeout, err)
// Return false so that the packet is offered as normal
// to the switch. This isn't ideal because interception
// may be configured as always-on and is skipped now.
// Returning true isn't great either, because the htlc
// will remain stuck and potentially force-close the
// channel. But in the end, we should never get here, so
// the actual return value doesn't matter that much.
return false, nil
}
if handled {
return true, nil
}
return s.forward(intercepted, isReplay)
default:
return false, nil
}
}
// forward records the intercepted htlc and forwards it to the interceptor.
func (s *InterceptableSwitch) forward(
fwd InterceptedForward, isReplay bool) (bool, error) {
inKey := fwd.Packet().IncomingCircuit
// Ignore already held htlcs.
if s.heldHtlcSet.exists(inKey) {
return true, nil
}
// If there is no interceptor currently registered, configuration and packet
// replay status determine how the packet is handled.
if s.interceptor == nil {
// Process normally if an interceptor is not required.
if !s.requireInterceptor {
return false, nil
}
// We are in interceptor-required mode. If this is a new packet, it is
// still safe to fail back. The interceptor has never seen this packet
// yet. This limits the backlog of htlcs when the interceptor is down.
if !isReplay {
err := fwd.FailWithCode(
lnwire.CodeTemporaryChannelFailure,
)
if err != nil {
log.Errorf("Cannot fail packet: %v", err)
}
return true, nil
}
// This packet is a replay. It is not safe to fail back, because the
// interceptor may still signal otherwise upon reconnect. Keep the
// packet in the queue until then.
if err := s.heldHtlcSet.push(inKey, fwd); err != nil {
return false, err
}
return true, nil
}
// There is an interceptor registered. We can forward the packet right now.
// Hold it in the queue too to track what is outstanding.
if err := s.heldHtlcSet.push(inKey, fwd); err != nil {
return false, err
}
s.sendForward(fwd)
return true, nil
}
// handleExpired checks that the htlc isn't too close to the channel
// force-close broadcast height. If it is, it is cancelled back.
func (s *InterceptableSwitch) handleExpired(fwd *interceptedForward) (
bool, error) {
height := uint32(s.currentHeight)
if fwd.packet.incomingTimeout >= height+s.cltvInterceptDelta {
return false, nil
}
log.Debugf("Interception rejected because htlc "+
"expires too soon: circuit=%v, "+
"height=%v, incoming_timeout=%v",
fwd.packet.inKey(), height,
fwd.packet.incomingTimeout)
err := fwd.FailWithCode(
lnwire.CodeExpiryTooSoon,
)
if err != nil {
return false, err
}
return true, nil
}
// interceptedForward implements the InterceptedForward interface.
// It is passed from the switch to external interceptors that are interested
// in holding forwards and resolve them manually.
type interceptedForward struct {
htlc *lnwire.UpdateAddHTLC
packet *htlcPacket
htlcSwitch *Switch
autoFailHeight int32
}
// Packet returns the intercepted htlc packet.
func (f *interceptedForward) Packet() InterceptedPacket {
return InterceptedPacket{
IncomingCircuit: models.CircuitKey{
ChanID: f.packet.incomingChanID,
HtlcID: f.packet.incomingHTLCID,
},
OutgoingChanID: f.packet.outgoingChanID,
Hash: f.htlc.PaymentHash,
OutgoingExpiry: f.htlc.Expiry,
OutgoingAmount: f.htlc.Amount,
IncomingAmount: f.packet.incomingAmount,
IncomingExpiry: f.packet.incomingTimeout,
InOnionCustomRecords: f.packet.inOnionCustomRecords,
OnionBlob: f.htlc.OnionBlob,
AutoFailHeight: f.autoFailHeight,
InWireCustomRecords: f.packet.inWireCustomRecords,
}
}
// Resume resumes the default behavior as if the packet was not intercepted.
func (f *interceptedForward) Resume() error {
// Forward to the switch. A link quit channel isn't needed, because we
// are on a different thread now.
return f.htlcSwitch.ForwardPackets(nil, f.packet)
}
// ResumeModified resumes the default behavior with field modifications. The
// input amount (if provided) specifies that the value of the inbound HTLC
// should be interpreted differently from the on-chain amount during further
// validation. The presence of an output amount and/or custom records indicates
// that those values should be modified on the outgoing HTLC.
func (f *interceptedForward) ResumeModified(
inAmountMsat fn.Option[lnwire.MilliSatoshi],
outAmountMsat fn.Option[lnwire.MilliSatoshi],
outWireCustomRecords fn.Option[lnwire.CustomRecords]) error {
// Convert the optional custom records to the correct type and validate
// them.
validatedRecords, err := fn.MapOptionZ(
outWireCustomRecords,
func(cr lnwire.CustomRecords) fn.Result[lnwire.CustomRecords] {
if len(cr) == 0 {
return fn.Ok[lnwire.CustomRecords](nil)
}
// Type cast and validate custom records.
err := cr.Validate()
if err != nil {
return fn.Err[lnwire.CustomRecords](
fmt.Errorf("failed to validate "+
"custom records: %w", err),
)
}
return fn.Ok(cr)
},
).Unpack()
if err != nil {
return fmt.Errorf("failed to encode custom records: %w",
err)
}
// Set the incoming amount, if it is provided, on the packet.
inAmountMsat.WhenSome(func(amount lnwire.MilliSatoshi) {
f.packet.incomingAmount = amount
})
// Modify the wire message contained in the packet.
switch htlc := f.packet.htlc.(type) {
case *lnwire.UpdateAddHTLC:
outAmountMsat.WhenSome(func(amount lnwire.MilliSatoshi) {
f.packet.amount = amount
htlc.Amount = amount
})
// Merge custom records with any validated records that were
// added in the modify request, overwriting any existing values
// with those supplied in the modifier API.
htlc.CustomRecords = htlc.CustomRecords.MergedCopy(
validatedRecords,
)
case *lnwire.UpdateFulfillHTLC:
if len(validatedRecords) > 0 {
htlc.CustomRecords = validatedRecords
}
}
log.Tracef("Forwarding packet %v", lnutils.SpewLogClosure(f.packet))
// Forward to the switch. A link quit channel isn't needed, because we
// are on a different thread now.
return f.htlcSwitch.ForwardPackets(nil, f.packet)
}
// Fail notifies the intention to Fail an existing hold forward with an
// encrypted failure reason.
func (f *interceptedForward) Fail(reason []byte) error {
obfuscatedReason := f.packet.obfuscator.IntermediateEncrypt(reason)
return f.resolve(&lnwire.UpdateFailHTLC{
Reason: obfuscatedReason,
})
}
// FailWithCode notifies the intention to fail an existing hold forward with the
// specified failure code.
func (f *interceptedForward) FailWithCode(code lnwire.FailCode) error {
shaOnionBlob := func() [32]byte {
return sha256.Sum256(f.htlc.OnionBlob[:])
}
// Create a local failure.
var failureMsg lnwire.FailureMessage
switch code {
case lnwire.CodeInvalidOnionVersion:
failureMsg = &lnwire.FailInvalidOnionVersion{
OnionSHA256: shaOnionBlob(),
}
case lnwire.CodeInvalidOnionHmac:
failureMsg = &lnwire.FailInvalidOnionHmac{
OnionSHA256: shaOnionBlob(),
}
case lnwire.CodeInvalidOnionKey:
failureMsg = &lnwire.FailInvalidOnionKey{
OnionSHA256: shaOnionBlob(),
}
case lnwire.CodeTemporaryChannelFailure:
update := f.htlcSwitch.failAliasUpdate(
f.packet.incomingChanID, true,
)
if update == nil {
// Fallback to the original, non-alias behavior.
var err error
update, err = f.htlcSwitch.cfg.FetchLastChannelUpdate(
f.packet.incomingChanID,
)
if err != nil {
return err
}
}
failureMsg = lnwire.NewTemporaryChannelFailure(update)
case lnwire.CodeExpiryTooSoon:
update, err := f.htlcSwitch.cfg.FetchLastChannelUpdate(
f.packet.incomingChanID,
)
if err != nil {
return err
}
failureMsg = lnwire.NewExpiryTooSoon(*update)
default:
return ErrUnsupportedFailureCode
}
// Encrypt the failure for the first hop. This node will be the origin
// of the failure.
reason, err := f.packet.obfuscator.EncryptFirstHop(failureMsg)
if err != nil {
return fmt.Errorf("failed to encrypt failure reason %w", err)
}
return f.resolve(&lnwire.UpdateFailHTLC{
Reason: reason,
})
}
// Settle forwards a settled packet to the switch.
func (f *interceptedForward) Settle(preimage lntypes.Preimage) error {
if !preimage.Matches(f.htlc.PaymentHash) {
return errors.New("preimage does not match hash")
}
return f.resolve(&lnwire.UpdateFulfillHTLC{
PaymentPreimage: preimage,
})
}
// resolve is used for both Settle and Fail and forwards the message to the
// switch.
func (f *interceptedForward) resolve(message lnwire.Message) error {
pkt := &htlcPacket{
incomingChanID: f.packet.incomingChanID,
incomingHTLCID: f.packet.incomingHTLCID,
outgoingChanID: f.packet.outgoingChanID,
outgoingHTLCID: f.packet.outgoingHTLCID,
isResolution: true,
circuit: f.packet.circuit,
htlc: message,
obfuscator: f.packet.obfuscator,
sourceRef: f.packet.sourceRef,
}
return f.htlcSwitch.mailOrchestrator.Deliver(pkt.incomingChanID, pkt)
}
package htlcswitch
import (
"bytes"
"context"
crand "crypto/rand"
"crypto/sha256"
"errors"
"fmt"
prand "math/rand"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/htlcswitch/hodl"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/lnpeer"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/queue"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/ticker"
"github.com/lightningnetwork/lnd/tlv"
)
func init() {
prand.Seed(time.Now().UnixNano())
}
const (
// DefaultMaxOutgoingCltvExpiry is the maximum outgoing time lock that
// the node accepts for forwarded payments. The value is relative to the
// current block height. The reason to have a maximum is to prevent
// funds getting locked up unreasonably long. Otherwise, an attacker
// willing to lock its own funds too, could force the funds of this node
// to be locked up for an indefinite (max int32) number of blocks.
//
// The value 2016 corresponds to on average two weeks worth of blocks
// and is based on the maximum number of hops (20), the default CLTV
// delta (40), and some extra margin to account for the other lightning
// implementations and past lnd versions which used to have a default
// CLTV delta of 144.
DefaultMaxOutgoingCltvExpiry = 2016
// DefaultMinLinkFeeUpdateTimeout represents the minimum interval in
// which a link should propose to update its commitment fee rate.
DefaultMinLinkFeeUpdateTimeout = 10 * time.Minute
// DefaultMaxLinkFeeUpdateTimeout represents the maximum interval in
// which a link should propose to update its commitment fee rate.
DefaultMaxLinkFeeUpdateTimeout = 60 * time.Minute
// DefaultMaxLinkFeeAllocation is the highest allocation we'll allow
// a channel's commitment fee to be of its balance. This only applies to
// the initiator of the channel.
DefaultMaxLinkFeeAllocation float64 = 0.5
)
// ExpectedFee computes the expected fee for a given htlc amount. The value
// returned from this function is to be used as a sanity check when forwarding
// HTLC's to ensure that an incoming HTLC properly adheres to our propagated
// forwarding policy.
//
// TODO(roasbeef): also add in current available channel bandwidth, inverse
// func
func ExpectedFee(f models.ForwardingPolicy,
htlcAmt lnwire.MilliSatoshi) lnwire.MilliSatoshi {
return f.BaseFee + (htlcAmt*f.FeeRate)/1000000
}
// ChannelLinkConfig defines the configuration for the channel link. ALL
// elements within the configuration MUST be non-nil for channel link to carry
// out its duties.
type ChannelLinkConfig struct {
// FwrdingPolicy is the initial forwarding policy to be used when
// deciding whether to forwarding incoming HTLC's or not. This value
// can be updated with subsequent calls to UpdateForwardingPolicy
// targeted at a given ChannelLink concrete interface implementation.
FwrdingPolicy models.ForwardingPolicy
// Circuits provides restricted access to the switch's circuit map,
// allowing the link to open and close circuits.
Circuits CircuitModifier
// BestHeight returns the best known height.
BestHeight func() uint32
// ForwardPackets attempts to forward the batch of htlcs through the
// switch. The function returns and error in case it fails to send one or
// more packets. The link's quit signal should be provided to allow
// cancellation of forwarding during link shutdown.
ForwardPackets func(<-chan struct{}, bool, ...*htlcPacket) error
// DecodeHopIterators facilitates batched decoding of HTLC Sphinx onion
// blobs, which are then used to inform how to forward an HTLC.
//
// NOTE: This function assumes the same set of readers and preimages
// are always presented for the same identifier.
DecodeHopIterators func([]byte, []hop.DecodeHopIteratorRequest) (
[]hop.DecodeHopIteratorResponse, error)
// ExtractErrorEncrypter function is responsible for decoding HTLC
// Sphinx onion blob, and creating onion failure obfuscator.
ExtractErrorEncrypter hop.ErrorEncrypterExtracter
// FetchLastChannelUpdate retrieves the latest routing policy for a
// target channel. This channel will typically be the outgoing channel
// specified when we receive an incoming HTLC. This will be used to
// provide payment senders our latest policy when sending encrypted
// error messages.
FetchLastChannelUpdate func(lnwire.ShortChannelID) (
*lnwire.ChannelUpdate1, error)
// Peer is a lightning network node with which we have the channel link
// opened.
Peer lnpeer.Peer
// Registry is a sub-system which responsible for managing the invoices
// in thread-safe manner.
Registry InvoiceDatabase
// PreimageCache is a global witness beacon that houses any new
// preimages discovered by other links. We'll use this to add new
// witnesses that we discover which will notify any sub-systems
// subscribed to new events.
PreimageCache contractcourt.WitnessBeacon
// OnChannelFailure is a function closure that we'll call if the
// channel failed for some reason. Depending on the severity of the
// error, the closure potentially must force close this channel and
// disconnect the peer.
//
// NOTE: The method must return in order for the ChannelLink to be able
// to shut down properly.
OnChannelFailure func(lnwire.ChannelID, lnwire.ShortChannelID,
LinkFailureError)
// UpdateContractSignals is a function closure that we'll use to update
// outside sub-systems with this channel's latest ShortChannelID.
UpdateContractSignals func(*contractcourt.ContractSignals) error
// NotifyContractUpdate is a function closure that we'll use to update
// the contractcourt and more specifically the ChannelArbitrator of the
// latest channel state.
NotifyContractUpdate func(*contractcourt.ContractUpdate) error
// ChainEvents is an active subscription to the chain watcher for this
// channel to be notified of any on-chain activity related to this
// channel.
ChainEvents *contractcourt.ChainEventSubscription
// FeeEstimator is an instance of a live fee estimator which will be
// used to dynamically regulate the current fee of the commitment
// transaction to ensure timely confirmation.
FeeEstimator chainfee.Estimator
// hodl.Mask is a bitvector composed of hodl.Flags, specifying breakpoints
// for HTLC forwarding internal to the switch.
//
// NOTE: This should only be used for testing.
HodlMask hodl.Mask
// SyncStates is used to indicate that we need send the channel
// reestablishment message to the remote peer. It should be done if our
// clients have been restarted, or remote peer have been reconnected.
SyncStates bool
// BatchTicker is the ticker that determines the interval that we'll
// use to check the batch to see if there're any updates we should
// flush out. By batching updates into a single commit, we attempt to
// increase throughput by maximizing the number of updates coalesced
// into a single commit.
BatchTicker ticker.Ticker
// FwdPkgGCTicker is the ticker determining the frequency at which
// garbage collection of forwarding packages occurs. We use a
// time-based approach, as opposed to block epochs, as to not hinder
// syncing.
FwdPkgGCTicker ticker.Ticker
// PendingCommitTicker is a ticker that allows the link to determine if
// a locally initiated commitment dance gets stuck waiting for the
// remote party to revoke.
PendingCommitTicker ticker.Ticker
// BatchSize is the max size of a batch of updates done to the link
// before we do a state update.
BatchSize uint32
// UnsafeReplay will cause a link to replay the adds in its latest
// commitment txn after the link is restarted. This should only be used
// in testing, it is here to ensure the sphinx replay detection on the
// receiving node is persistent.
UnsafeReplay bool
// MinUpdateTimeout represents the minimum interval in which a link
// will propose to update its commitment fee rate. A random timeout will
// be selected between this and MaxUpdateTimeout.
MinUpdateTimeout time.Duration
// MaxUpdateTimeout represents the maximum interval in which a link
// will propose to update its commitment fee rate. A random timeout will
// be selected between this and MinUpdateTimeout.
MaxUpdateTimeout time.Duration
// OutgoingCltvRejectDelta defines the number of blocks before expiry of
// an htlc where we don't offer an htlc anymore. This should be at least
// the outgoing broadcast delta, because in any case we don't want to
// risk offering an htlc that triggers channel closure.
OutgoingCltvRejectDelta uint32
// TowerClient is an optional engine that manages the signing,
// encrypting, and uploading of justice transactions to the daemon's
// configured set of watchtowers for legacy channels.
TowerClient TowerClient
// MaxOutgoingCltvExpiry is the maximum outgoing timelock that the link
// should accept for a forwarded HTLC. The value is relative to the
// current block height.
MaxOutgoingCltvExpiry uint32
// MaxFeeAllocation is the highest allocation we'll allow a channel's
// commitment fee to be of its balance. This only applies to the
// initiator of the channel.
MaxFeeAllocation float64
// MaxAnchorsCommitFeeRate is the max commitment fee rate we'll use as
// the initiator for channels of the anchor type.
MaxAnchorsCommitFeeRate chainfee.SatPerKWeight
// NotifyActiveLink allows the link to tell the ChannelNotifier when a
// link is first started.
NotifyActiveLink func(wire.OutPoint)
// NotifyActiveChannel allows the link to tell the ChannelNotifier when
// channels becomes active.
NotifyActiveChannel func(wire.OutPoint)
// NotifyInactiveChannel allows the switch to tell the ChannelNotifier
// when channels become inactive.
NotifyInactiveChannel func(wire.OutPoint)
// NotifyInactiveLinkEvent allows the switch to tell the
// ChannelNotifier when a channel link become inactive.
NotifyInactiveLinkEvent func(wire.OutPoint)
// HtlcNotifier is an instance of a htlcNotifier which we will pipe htlc
// events through.
HtlcNotifier htlcNotifier
// FailAliasUpdate is a function used to fail an HTLC for an
// option_scid_alias channel.
FailAliasUpdate func(sid lnwire.ShortChannelID,
incoming bool) *lnwire.ChannelUpdate1
// GetAliases is used by the link and switch to fetch the set of
// aliases for a given link.
GetAliases func(base lnwire.ShortChannelID) []lnwire.ShortChannelID
// PreviouslySentShutdown is an optional value that is set if, at the
// time of the link being started, persisted shutdown info was found for
// the channel. This value being set means that we previously sent a
// Shutdown message to our peer, and so we should do so again on
// re-establish and should not allow anymore HTLC adds on the outgoing
// direction of the link.
PreviouslySentShutdown fn.Option[lnwire.Shutdown]
// Adds the option to disable forwarding payments in blinded routes
// by failing back any blinding-related payloads as if they were
// invalid.
DisallowRouteBlinding bool
// DisallowQuiescence is a flag that can be used to disable the
// quiescence protocol.
DisallowQuiescence bool
// MaxFeeExposure is the threshold in milli-satoshis after which we'll
// restrict the flow of HTLCs and fee updates.
MaxFeeExposure lnwire.MilliSatoshi
// ShouldFwdExpEndorsement is a closure that indicates whether the link
// should forward experimental endorsement signals.
ShouldFwdExpEndorsement func() bool
// AuxTrafficShaper is an optional auxiliary traffic shaper that can be
// used to manage the bandwidth of the link.
AuxTrafficShaper fn.Option[AuxTrafficShaper]
}
// channelLink is the service which drives a channel's commitment update
// state-machine. In the event that an HTLC needs to be propagated to another
// link, the forward handler from config is used which sends HTLC to the
// switch. Additionally, the link encapsulate logic of commitment protocol
// message ordering and updates.
type channelLink struct {
// The following fields are only meant to be used *atomically*
started int32
reestablished int32
shutdown int32
// failed should be set to true in case a link error happens, making
// sure we don't process any more updates.
failed bool
// keystoneBatch represents a volatile list of keystones that must be
// written before attempting to sign the next commitment txn. These
// represent all the HTLC's forwarded to the link from the switch. Once
// we lock them into our outgoing commitment, then the circuit has a
// keystone, and is fully opened.
keystoneBatch []Keystone
// openedCircuits is the set of all payment circuits that will be open
// once we make our next commitment. After making the commitment we'll
// ACK all these from our mailbox to ensure that they don't get
// re-delivered if we reconnect.
openedCircuits []CircuitKey
// closedCircuits is the set of all payment circuits that will be
// closed once we make our next commitment. After taking the commitment
// we'll ACK all these to ensure that they don't get re-delivered if we
// reconnect.
closedCircuits []CircuitKey
// channel is a lightning network channel to which we apply htlc
// updates.
channel *lnwallet.LightningChannel
// cfg is a structure which carries all dependable fields/handlers
// which may affect behaviour of the service.
cfg ChannelLinkConfig
// mailBox is the main interface between the outside world and the
// link. All incoming messages will be sent over this mailBox. Messages
// include new updates from our connected peer, and new packets to be
// forwarded sent by the switch.
mailBox MailBox
// upstream is a channel that new messages sent from the remote peer to
// the local peer will be sent across.
upstream chan lnwire.Message
// downstream is a channel in which new multi-hop HTLC's to be
// forwarded will be sent across. Messages from this channel are sent
// by the HTLC switch.
downstream chan *htlcPacket
// updateFeeTimer is the timer responsible for updating the link's
// commitment fee every time it fires.
updateFeeTimer *time.Timer
// uncommittedPreimages stores a list of all preimages that have been
// learned since receiving the last CommitSig from the remote peer. The
// batch will be flushed just before accepting the subsequent CommitSig
// or on shutdown to avoid doing a write for each preimage received.
uncommittedPreimages []lntypes.Preimage
sync.RWMutex
// hodlQueue is used to receive exit hop htlc resolutions from invoice
// registry.
hodlQueue *queue.ConcurrentQueue
// hodlMap stores related htlc data for a circuit key. It allows
// resolving those htlcs when we receive a message on hodlQueue.
hodlMap map[models.CircuitKey]hodlHtlc
// log is a link-specific logging instance.
log btclog.Logger
// isOutgoingAddBlocked tracks whether the channelLink can send an
// UpdateAddHTLC.
isOutgoingAddBlocked atomic.Bool
// isIncomingAddBlocked tracks whether the channelLink can receive an
// UpdateAddHTLC.
isIncomingAddBlocked atomic.Bool
// flushHooks is a hookMap that is triggered when we reach a channel
// state with no live HTLCs.
flushHooks hookMap
// outgoingCommitHooks is a hookMap that is triggered after we send our
// next CommitSig.
outgoingCommitHooks hookMap
// incomingCommitHooks is a hookMap that is triggered after we receive
// our next CommitSig.
incomingCommitHooks hookMap
// quiescer is the state machine that tracks where this channel is with
// respect to the quiescence protocol.
quiescer Quiescer
// quiescenceReqs is a queue of requests to quiesce this link. The
// members of the queue are send-only channels we should call back with
// the result.
quiescenceReqs chan StfuReq
// cg is a helper that encapsulates a wait group and quit channel and
// allows contexts that either block or cancel on those depending on
// the use case.
cg *fn.ContextGuard
}
// hookMap is a data structure that is used to track the hooks that need to be
// called in various parts of the channelLink's lifecycle.
//
// WARNING: NOT thread-safe.
type hookMap struct {
// allocIdx keeps track of the next id we haven't yet allocated.
allocIdx atomic.Uint64
// transient is a map of hooks that are only called the next time invoke
// is called. These hooks are deleted during invoke.
transient map[uint64]func()
// newTransients is a channel that we use to accept new hooks into the
// hookMap.
newTransients chan func()
}
// newHookMap initializes a new empty hookMap.
func newHookMap() hookMap {
return hookMap{
allocIdx: atomic.Uint64{},
transient: make(map[uint64]func()),
newTransients: make(chan func()),
}
}
// alloc allocates space in the hook map for the supplied hook, the second
// argument determines whether it goes into the transient or persistent part
// of the hookMap.
func (m *hookMap) alloc(hook func()) uint64 {
// We assume we never overflow a uint64. Seems OK.
hookID := m.allocIdx.Add(1)
if hookID == 0 {
panic("hookMap allocIdx overflow")
}
m.transient[hookID] = hook
return hookID
}
// invoke is used on a hook map to call all the registered hooks and then clear
// out the transient hooks so they are not called again.
func (m *hookMap) invoke() {
for _, hook := range m.transient {
hook()
}
m.transient = make(map[uint64]func())
}
// hodlHtlc contains htlc data that is required for resolution.
type hodlHtlc struct {
add lnwire.UpdateAddHTLC
sourceRef channeldb.AddRef
obfuscator hop.ErrorEncrypter
}
// NewChannelLink creates a new instance of a ChannelLink given a configuration
// and active channel that will be used to verify/apply updates to.
func NewChannelLink(cfg ChannelLinkConfig,
channel *lnwallet.LightningChannel) ChannelLink {
logPrefix := fmt.Sprintf("ChannelLink(%v):", channel.ChannelPoint())
// If the max fee exposure isn't set, use the default.
if cfg.MaxFeeExposure == 0 {
cfg.MaxFeeExposure = DefaultMaxFeeExposure
}
var qsm Quiescer
if !cfg.DisallowQuiescence {
qsm = NewQuiescer(QuiescerCfg{
chanID: lnwire.NewChanIDFromOutPoint(
channel.ChannelPoint(),
),
channelInitiator: channel.Initiator(),
sendMsg: func(s lnwire.Stfu) error {
return cfg.Peer.SendMessage(false, &s)
},
timeoutDuration: defaultQuiescenceTimeout,
onTimeout: func() {
cfg.Peer.Disconnect(ErrQuiescenceTimeout)
},
})
} else {
qsm = &quiescerNoop{}
}
quiescenceReqs := make(
chan fn.Req[fn.Unit, fn.Result[lntypes.ChannelParty]], 1,
)
return &channelLink{
cfg: cfg,
channel: channel,
hodlMap: make(map[models.CircuitKey]hodlHtlc),
hodlQueue: queue.NewConcurrentQueue(10),
log: log.WithPrefix(logPrefix),
flushHooks: newHookMap(),
outgoingCommitHooks: newHookMap(),
incomingCommitHooks: newHookMap(),
quiescer: qsm,
quiescenceReqs: quiescenceReqs,
cg: fn.NewContextGuard(),
}
}
// A compile time check to ensure channelLink implements the ChannelLink
// interface.
var _ ChannelLink = (*channelLink)(nil)
// Start starts all helper goroutines required for the operation of the channel
// link.
//
// NOTE: Part of the ChannelLink interface.
func (l *channelLink) Start() error {
if !atomic.CompareAndSwapInt32(&l.started, 0, 1) {
err := fmt.Errorf("channel link(%v): already started", l)
l.log.Warn("already started")
return err
}
l.log.Info("starting")
// If the config supplied watchtower client, ensure the channel is
// registered before trying to use it during operation.
if l.cfg.TowerClient != nil {
err := l.cfg.TowerClient.RegisterChannel(
l.ChanID(), l.channel.State().ChanType,
)
if err != nil {
return err
}
}
l.mailBox.ResetMessages()
l.hodlQueue.Start()
// Before launching the htlcManager messages, revert any circuits that
// were marked open in the switch's circuit map, but did not make it
// into a commitment txn. We use the next local htlc index as the cut
// off point, since all indexes below that are committed. This action
// is only performed if the link's final short channel ID has been
// assigned, otherwise we would try to trim the htlcs belonging to the
// all-zero, hop.Source ID.
if l.ShortChanID() != hop.Source {
localHtlcIndex, err := l.channel.NextLocalHtlcIndex()
if err != nil {
return fmt.Errorf("unable to retrieve next local "+
"htlc index: %v", err)
}
// NOTE: This is automatically done by the switch when it
// starts up, but is necessary to prevent inconsistencies in
// the case that the link flaps. This is a result of a link's
// life-cycle being shorter than that of the switch.
chanID := l.ShortChanID()
err = l.cfg.Circuits.TrimOpenCircuits(chanID, localHtlcIndex)
if err != nil {
return fmt.Errorf("unable to trim circuits above "+
"local htlc index %d: %v", localHtlcIndex, err)
}
// Since the link is live, before we start the link we'll update
// the ChainArbitrator with the set of new channel signals for
// this channel.
//
// TODO(roasbeef): split goroutines within channel arb to avoid
go func() {
signals := &contractcourt.ContractSignals{
ShortChanID: l.channel.ShortChanID(),
}
err := l.cfg.UpdateContractSignals(signals)
if err != nil {
l.log.Errorf("unable to update signals")
}
}()
}
l.updateFeeTimer = time.NewTimer(l.randomFeeUpdateTimeout())
l.cg.WgAdd(1)
go l.htlcManager(context.TODO())
return nil
}
// Stop gracefully stops all active helper goroutines, then waits until they've
// exited.
//
// NOTE: Part of the ChannelLink interface.
func (l *channelLink) Stop() {
if !atomic.CompareAndSwapInt32(&l.shutdown, 0, 1) {
l.log.Warn("already stopped")
return
}
l.log.Info("stopping")
// As the link is stopping, we are no longer interested in htlc
// resolutions coming from the invoice registry.
l.cfg.Registry.HodlUnsubscribeAll(l.hodlQueue.ChanIn())
if l.cfg.ChainEvents.Cancel != nil {
l.cfg.ChainEvents.Cancel()
}
// Ensure the channel for the timer is drained.
if l.updateFeeTimer != nil {
if !l.updateFeeTimer.Stop() {
select {
case <-l.updateFeeTimer.C:
default:
}
}
}
if l.hodlQueue != nil {
l.hodlQueue.Stop()
}
l.cg.Quit()
l.cg.WgWait()
// Now that the htlcManager has completely exited, reset the packet
// courier. This allows the mailbox to revaluate any lingering Adds that
// were delivered but didn't make it on a commitment to be failed back
// if the link is offline for an extended period of time. The error is
// ignored since it can only fail when the daemon is exiting.
_ = l.mailBox.ResetPackets()
// As a final precaution, we will attempt to flush any uncommitted
// preimages to the preimage cache. The preimages should be re-delivered
// after channel reestablishment, however this adds an extra layer of
// protection in case the peer never returns. Without this, we will be
// unable to settle any contracts depending on the preimages even though
// we had learned them at some point.
err := l.cfg.PreimageCache.AddPreimages(l.uncommittedPreimages...)
if err != nil {
l.log.Errorf("unable to add preimages=%v to cache: %v",
l.uncommittedPreimages, err)
}
}
// WaitForShutdown blocks until the link finishes shutting down, which includes
// termination of all dependent goroutines.
func (l *channelLink) WaitForShutdown() {
l.cg.WgWait()
}
// EligibleToForward returns a bool indicating if the channel is able to
// actively accept requests to forward HTLC's. We're able to forward HTLC's if
// we are eligible to update AND the channel isn't currently flushing the
// outgoing half of the channel.
//
// NOTE: MUST NOT be called from the main event loop.
func (l *channelLink) EligibleToForward() bool {
l.RLock()
defer l.RUnlock()
return l.eligibleToForward()
}
// eligibleToForward returns a bool indicating if the channel is able to
// actively accept requests to forward HTLC's. We're able to forward HTLC's if
// we are eligible to update AND the channel isn't currently flushing the
// outgoing half of the channel.
//
// NOTE: MUST be called from the main event loop.
func (l *channelLink) eligibleToForward() bool {
return l.eligibleToUpdate() && !l.IsFlushing(Outgoing)
}
// eligibleToUpdate returns a bool indicating if the channel is able to update
// channel state. We're able to update channel state if we know the remote
// party's next revocation point. Otherwise, we can't initiate new channel
// state. We also require that the short channel ID not be the all-zero source
// ID, meaning that the channel has had its ID finalized.
//
// NOTE: MUST be called from the main event loop.
func (l *channelLink) eligibleToUpdate() bool {
return l.channel.RemoteNextRevocation() != nil &&
l.channel.ShortChanID() != hop.Source &&
l.isReestablished() &&
l.quiescer.CanSendUpdates()
}
// EnableAdds sets the ChannelUpdateHandler state to allow UpdateAddHtlc's in
// the specified direction. It returns true if the state was changed and false
// if the desired state was already set before the method was called.
func (l *channelLink) EnableAdds(linkDirection LinkDirection) bool {
if linkDirection == Outgoing {
return l.isOutgoingAddBlocked.Swap(false)
}
return l.isIncomingAddBlocked.Swap(false)
}
// DisableAdds sets the ChannelUpdateHandler state to allow UpdateAddHtlc's in
// the specified direction. It returns true if the state was changed and false
// if the desired state was already set before the method was called.
func (l *channelLink) DisableAdds(linkDirection LinkDirection) bool {
if linkDirection == Outgoing {
return !l.isOutgoingAddBlocked.Swap(true)
}
return !l.isIncomingAddBlocked.Swap(true)
}
// IsFlushing returns true when UpdateAddHtlc's are disabled in the direction of
// the argument.
func (l *channelLink) IsFlushing(linkDirection LinkDirection) bool {
if linkDirection == Outgoing {
return l.isOutgoingAddBlocked.Load()
}
return l.isIncomingAddBlocked.Load()
}
// OnFlushedOnce adds a hook that will be called the next time the channel
// state reaches zero htlcs. This hook will only ever be called once. If the
// channel state already has zero htlcs, then this will be called immediately.
func (l *channelLink) OnFlushedOnce(hook func()) {
select {
case l.flushHooks.newTransients <- hook:
case <-l.cg.Done():
}
}
// OnCommitOnce adds a hook that will be called the next time a CommitSig
// message is sent in the argument's LinkDirection. This hook will only ever be
// called once. If no CommitSig is owed in the argument's LinkDirection, then
// we will call this hook be run immediately.
func (l *channelLink) OnCommitOnce(direction LinkDirection, hook func()) {
var queue chan func()
if direction == Outgoing {
queue = l.outgoingCommitHooks.newTransients
} else {
queue = l.incomingCommitHooks.newTransients
}
select {
case queue <- hook:
case <-l.cg.Done():
}
}
// InitStfu allows us to initiate quiescence on this link. It returns a receive
// only channel that will block until quiescence has been achieved, or
// definitively fails.
//
// This operation has been added to allow channels to be quiesced via RPC. It
// may be removed or reworked in the future as RPC initiated quiescence is a
// holdover until we have downstream protocols that use it.
func (l *channelLink) InitStfu() <-chan fn.Result[lntypes.ChannelParty] {
req, out := fn.NewReq[fn.Unit, fn.Result[lntypes.ChannelParty]](
fn.Unit{},
)
select {
case l.quiescenceReqs <- req:
case <-l.cg.Done():
req.Resolve(fn.Err[lntypes.ChannelParty](ErrLinkShuttingDown))
}
return out
}
// isReestablished returns true if the link has successfully completed the
// channel reestablishment dance.
func (l *channelLink) isReestablished() bool {
return atomic.LoadInt32(&l.reestablished) == 1
}
// markReestablished signals that the remote peer has successfully exchanged
// channel reestablish messages and that the channel is ready to process
// subsequent messages.
func (l *channelLink) markReestablished() {
atomic.StoreInt32(&l.reestablished, 1)
}
// IsUnadvertised returns true if the underlying channel is unadvertised.
func (l *channelLink) IsUnadvertised() bool {
state := l.channel.State()
return state.ChannelFlags&lnwire.FFAnnounceChannel == 0
}
// sampleNetworkFee samples the current fee rate on the network to get into the
// chain in a timely manner. The returned value is expressed in fee-per-kw, as
// this is the native rate used when computing the fee for commitment
// transactions, and the second-level HTLC transactions.
func (l *channelLink) sampleNetworkFee() (chainfee.SatPerKWeight, error) {
// We'll first query for the sat/kw recommended to be confirmed within 3
// blocks.
feePerKw, err := l.cfg.FeeEstimator.EstimateFeePerKW(3)
if err != nil {
return 0, err
}
l.log.Debugf("sampled fee rate for 3 block conf: %v sat/kw",
int64(feePerKw))
return feePerKw, nil
}
// shouldAdjustCommitFee returns true if we should update our commitment fee to
// match that of the network fee. We'll only update our commitment fee if the
// network fee is +/- 10% to our commitment fee or if our current commitment
// fee is below the minimum relay fee.
func shouldAdjustCommitFee(netFee, chanFee,
minRelayFee chainfee.SatPerKWeight) bool {
switch {
// If the network fee is greater than our current commitment fee and
// our current commitment fee is below the minimum relay fee then
// we should switch to it no matter if it is less than a 10% increase.
case netFee > chanFee && chanFee < minRelayFee:
return true
// If the network fee is greater than the commitment fee, then we'll
// switch to it if it's at least 10% greater than the commit fee.
case netFee > chanFee && netFee >= (chanFee+(chanFee*10)/100):
return true
// If the network fee is less than our commitment fee, then we'll
// switch to it if it's at least 10% less than the commitment fee.
case netFee < chanFee && netFee <= (chanFee-(chanFee*10)/100):
return true
// Otherwise, we won't modify our fee.
default:
return false
}
}
// failCb is used to cut down on the argument verbosity.
type failCb func(update *lnwire.ChannelUpdate1) lnwire.FailureMessage
// createFailureWithUpdate creates a ChannelUpdate when failing an incoming or
// outgoing HTLC. It may return a FailureMessage that references a channel's
// alias. If the channel does not have an alias, then the regular channel
// update from disk will be returned.
func (l *channelLink) createFailureWithUpdate(incoming bool,
outgoingScid lnwire.ShortChannelID, cb failCb) lnwire.FailureMessage {
// Determine which SCID to use in case we need to use aliases in the
// ChannelUpdate.
scid := outgoingScid
if incoming {
scid = l.ShortChanID()
}
// Try using the FailAliasUpdate function. If it returns nil, fallback
// to the non-alias behavior.
update := l.cfg.FailAliasUpdate(scid, incoming)
if update == nil {
// Fallback to the non-alias behavior.
var err error
update, err = l.cfg.FetchLastChannelUpdate(l.ShortChanID())
if err != nil {
return &lnwire.FailTemporaryNodeFailure{}
}
}
return cb(update)
}
// syncChanState attempts to synchronize channel states with the remote party.
// This method is to be called upon reconnection after the initial funding
// flow. We'll compare out commitment chains with the remote party, and re-send
// either a danging commit signature, a revocation, or both.
func (l *channelLink) syncChanStates(ctx context.Context) error {
chanState := l.channel.State()
l.log.Infof("Attempting to re-synchronize channel: %v", chanState)
// First, we'll generate our ChanSync message to send to the other
// side. Based on this message, the remote party will decide if they
// need to retransmit any data or not.
localChanSyncMsg, err := chanState.ChanSyncMsg()
if err != nil {
return fmt.Errorf("unable to generate chan sync message for "+
"ChannelPoint(%v)", l.channel.ChannelPoint())
}
if err := l.cfg.Peer.SendMessage(true, localChanSyncMsg); err != nil {
return fmt.Errorf("unable to send chan sync message for "+
"ChannelPoint(%v): %v", l.channel.ChannelPoint(), err)
}
var msgsToReSend []lnwire.Message
// Next, we'll wait indefinitely to receive the ChanSync message. The
// first message sent MUST be the ChanSync message.
select {
case msg := <-l.upstream:
l.log.Tracef("Received msg=%v from peer(%x)", msg.MsgType(),
l.cfg.Peer.PubKey())
remoteChanSyncMsg, ok := msg.(*lnwire.ChannelReestablish)
if !ok {
return fmt.Errorf("first message sent to sync "+
"should be ChannelReestablish, instead "+
"received: %T", msg)
}
// If the remote party indicates that they think we haven't
// done any state updates yet, then we'll retransmit the
// channel_ready message first. We do this, as at this point
// we can't be sure if they've really received the
// ChannelReady message.
if remoteChanSyncMsg.NextLocalCommitHeight == 1 &&
localChanSyncMsg.NextLocalCommitHeight == 1 &&
!l.channel.IsPending() {
l.log.Infof("resending ChannelReady message to peer")
nextRevocation, err := l.channel.NextRevocationKey()
if err != nil {
return fmt.Errorf("unable to create next "+
"revocation: %v", err)
}
channelReadyMsg := lnwire.NewChannelReady(
l.ChanID(), nextRevocation,
)
// If this is a taproot channel, then we'll send the
// very same nonce that we sent above, as they should
// take the latest verification nonce we send.
if chanState.ChanType.IsTaproot() {
//nolint:ll
channelReadyMsg.NextLocalNonce = localChanSyncMsg.LocalNonce
}
// For channels that negotiated the option-scid-alias
// feature bit, ensure that we send over the alias in
// the channel_ready message. We'll send the first
// alias we find for the channel since it does not
// matter which alias we send. We'll error out if no
// aliases are found.
if l.negotiatedAliasFeature() {
aliases := l.getAliases()
if len(aliases) == 0 {
// This shouldn't happen since we
// always add at least one alias before
// the channel reaches the link.
return fmt.Errorf("no aliases found")
}
// getAliases returns a copy of the alias slice
// so it is ok to use a pointer to the first
// entry.
channelReadyMsg.AliasScid = &aliases[0]
}
err = l.cfg.Peer.SendMessage(false, channelReadyMsg)
if err != nil {
return fmt.Errorf("unable to re-send "+
"ChannelReady: %v", err)
}
}
// In any case, we'll then process their ChanSync message.
l.log.Info("received re-establishment message from remote side")
var (
openedCircuits []CircuitKey
closedCircuits []CircuitKey
)
// We've just received a ChanSync message from the remote
// party, so we'll process the message in order to determine
// if we need to re-transmit any messages to the remote party.
ctx, cancel := l.cg.Create(ctx)
defer cancel()
msgsToReSend, openedCircuits, closedCircuits, err =
l.channel.ProcessChanSyncMsg(ctx, remoteChanSyncMsg)
if err != nil {
return err
}
// Repopulate any identifiers for circuits that may have been
// opened or unclosed. This may happen if we needed to
// retransmit a commitment signature message.
l.openedCircuits = openedCircuits
l.closedCircuits = closedCircuits
// Ensure that all packets have been have been removed from the
// link's mailbox.
if err := l.ackDownStreamPackets(); err != nil {
return err
}
if len(msgsToReSend) > 0 {
l.log.Infof("sending %v updates to synchronize the "+
"state", len(msgsToReSend))
}
// If we have any messages to retransmit, we'll do so
// immediately so we return to a synchronized state as soon as
// possible.
for _, msg := range msgsToReSend {
l.cfg.Peer.SendMessage(false, msg)
}
case <-l.cg.Done():
return ErrLinkShuttingDown
}
return nil
}
// resolveFwdPkgs loads any forwarding packages for this link from disk, and
// reprocesses them in order. The primary goal is to make sure that any HTLCs
// we previously received are reinstated in memory, and forwarded to the switch
// if necessary. After a restart, this will also delete any previously
// completed packages.
func (l *channelLink) resolveFwdPkgs(ctx context.Context) error {
fwdPkgs, err := l.channel.LoadFwdPkgs()
if err != nil {
return err
}
l.log.Debugf("loaded %d fwd pks", len(fwdPkgs))
for _, fwdPkg := range fwdPkgs {
if err := l.resolveFwdPkg(fwdPkg); err != nil {
return err
}
}
// If any of our reprocessing steps require an update to the commitment
// txn, we initiate a state transition to capture all relevant changes.
if l.channel.NumPendingUpdates(lntypes.Local, lntypes.Remote) > 0 {
return l.updateCommitTx(ctx)
}
return nil
}
// resolveFwdPkg interprets the FwdState of the provided package, either
// reprocesses any outstanding htlcs in the package, or performs garbage
// collection on the package.
func (l *channelLink) resolveFwdPkg(fwdPkg *channeldb.FwdPkg) error {
// Remove any completed packages to clear up space.
if fwdPkg.State == channeldb.FwdStateCompleted {
l.log.Debugf("removing completed fwd pkg for height=%d",
fwdPkg.Height)
err := l.channel.RemoveFwdPkgs(fwdPkg.Height)
if err != nil {
l.log.Errorf("unable to remove fwd pkg for height=%d: "+
"%v", fwdPkg.Height, err)
return err
}
}
// Otherwise this is either a new package or one has gone through
// processing, but contains htlcs that need to be restored in memory.
// We replay this forwarding package to make sure our local mem state
// is resurrected, we mimic any original responses back to the remote
// party, and re-forward the relevant HTLCs to the switch.
// If the package is fully acked but not completed, it must still have
// settles and fails to propagate.
if !fwdPkg.SettleFailFilter.IsFull() {
l.processRemoteSettleFails(fwdPkg)
}
// Finally, replay *ALL ADDS* in this forwarding package. The
// downstream logic is able to filter out any duplicates, but we must
// shove the entire, original set of adds down the pipeline so that the
// batch of adds presented to the sphinx router does not ever change.
if !fwdPkg.AckFilter.IsFull() {
l.processRemoteAdds(fwdPkg)
// If the link failed during processing the adds, we must
// return to ensure we won't attempted to update the state
// further.
if l.failed {
return fmt.Errorf("link failed while " +
"processing remote adds")
}
}
return nil
}
// fwdPkgGarbager periodically reads all forwarding packages from disk and
// removes those that can be discarded. It is safe to do this entirely in the
// background, since all state is coordinated on disk. This also ensures the
// link can continue to process messages and interleave database accesses.
//
// NOTE: This MUST be run as a goroutine.
func (l *channelLink) fwdPkgGarbager() {
defer l.cg.WgDone()
l.cfg.FwdPkgGCTicker.Resume()
defer l.cfg.FwdPkgGCTicker.Stop()
if err := l.loadAndRemove(); err != nil {
l.log.Warnf("unable to run initial fwd pkgs gc: %v", err)
}
for {
select {
case <-l.cfg.FwdPkgGCTicker.Ticks():
if err := l.loadAndRemove(); err != nil {
l.log.Warnf("unable to remove fwd pkgs: %v",
err)
continue
}
case <-l.cg.Done():
return
}
}
}
// loadAndRemove loads all the channels forwarding packages and determines if
// they can be removed. It is called once before the FwdPkgGCTicker ticks so that
// a longer tick interval can be used.
func (l *channelLink) loadAndRemove() error {
fwdPkgs, err := l.channel.LoadFwdPkgs()
if err != nil {
return err
}
var removeHeights []uint64
for _, fwdPkg := range fwdPkgs {
if fwdPkg.State != channeldb.FwdStateCompleted {
continue
}
removeHeights = append(removeHeights, fwdPkg.Height)
}
// If removeHeights is empty, return early so we don't use a db
// transaction.
if len(removeHeights) == 0 {
return nil
}
return l.channel.RemoveFwdPkgs(removeHeights...)
}
// handleChanSyncErr performs the error handling logic in the case where we
// could not successfully syncChanStates with our channel peer.
func (l *channelLink) handleChanSyncErr(err error) {
l.log.Warnf("error when syncing channel states: %v", err)
var errDataLoss *lnwallet.ErrCommitSyncLocalDataLoss
switch {
case errors.Is(err, ErrLinkShuttingDown):
l.log.Debugf("unable to sync channel states, link is " +
"shutting down")
return
// We failed syncing the commit chains, probably because the remote has
// lost state. We should force close the channel.
case errors.Is(err, lnwallet.ErrCommitSyncRemoteDataLoss):
fallthrough
// The remote sent us an invalid last commit secret, we should force
// close the channel.
// TODO(halseth): and permanently ban the peer?
case errors.Is(err, lnwallet.ErrInvalidLastCommitSecret):
fallthrough
// The remote sent us a commit point different from what they sent us
// before.
// TODO(halseth): ban peer?
case errors.Is(err, lnwallet.ErrInvalidLocalUnrevokedCommitPoint):
// We'll fail the link and tell the peer to force close the
// channel. Note that the database state is not updated here,
// but will be updated when the close transaction is ready to
// avoid that we go down before storing the transaction in the
// db.
l.failf(
LinkFailureError{
code: ErrSyncError,
FailureAction: LinkFailureForceClose,
},
"unable to synchronize channel states: %v", err,
)
// We have lost state and cannot safely force close the channel. Fail
// the channel and wait for the remote to hopefully force close it. The
// remote has sent us its latest unrevoked commitment point, and we'll
// store it in the database, such that we can attempt to recover the
// funds if the remote force closes the channel.
case errors.As(err, &errDataLoss):
err := l.channel.MarkDataLoss(
errDataLoss.CommitPoint,
)
if err != nil {
l.log.Errorf("unable to mark channel data loss: %v",
err)
}
// We determined the commit chains were not possible to sync. We
// cautiously fail the channel, but don't force close.
// TODO(halseth): can we safely force close in any cases where this
// error is returned?
case errors.Is(err, lnwallet.ErrCannotSyncCommitChains):
if err := l.channel.MarkBorked(); err != nil {
l.log.Errorf("unable to mark channel borked: %v", err)
}
// Other, unspecified error.
default:
}
l.failf(
LinkFailureError{
code: ErrRecoveryError,
FailureAction: LinkFailureForceNone,
},
"unable to synchronize channel states: %v", err,
)
}
// htlcManager is the primary goroutine which drives a channel's commitment
// update state-machine in response to messages received via several channels.
// This goroutine reads messages from the upstream (remote) peer, and also from
// downstream channel managed by the channel link. In the event that an htlc
// needs to be forwarded, then send-only forward handler is used which sends
// htlc packets to the switch. Additionally, this goroutine handles acting upon
// all timeouts for any active HTLCs, manages the channel's revocation window,
// and also the htlc trickle queue+timer for this active channels.
//
// NOTE: This MUST be run as a goroutine.
//
//nolint:funlen
func (l *channelLink) htlcManager(ctx context.Context) {
defer func() {
l.cfg.BatchTicker.Stop()
l.cg.WgDone()
l.log.Infof("exited")
}()
l.log.Infof("HTLC manager started, bandwidth=%v", l.Bandwidth())
// Notify any clients that the link is now in the switch via an
// ActiveLinkEvent. We'll also defer an inactive link notification for
// when the link exits to ensure that every active notification is
// matched by an inactive one.
l.cfg.NotifyActiveLink(l.ChannelPoint())
defer l.cfg.NotifyInactiveLinkEvent(l.ChannelPoint())
// TODO(roasbeef): need to call wipe chan whenever D/C?
// If this isn't the first time that this channel link has been
// created, then we'll need to check to see if we need to
// re-synchronize state with the remote peer. settledHtlcs is a map of
// HTLC's that we re-settled as part of the channel state sync.
if l.cfg.SyncStates {
err := l.syncChanStates(ctx)
if err != nil {
l.handleChanSyncErr(err)
return
}
}
// If a shutdown message has previously been sent on this link, then we
// need to make sure that we have disabled any HTLC adds on the outgoing
// direction of the link and that we re-resend the same shutdown message
// that we previously sent.
l.cfg.PreviouslySentShutdown.WhenSome(func(shutdown lnwire.Shutdown) {
// Immediately disallow any new outgoing HTLCs.
if !l.DisableAdds(Outgoing) {
l.log.Warnf("Outgoing link adds already disabled")
}
// Re-send the shutdown message the peer. Since syncChanStates
// would have sent any outstanding CommitSig, it is fine for us
// to immediately queue the shutdown message now.
err := l.cfg.Peer.SendMessage(false, &shutdown)
if err != nil {
l.log.Warnf("Error sending shutdown message: %v", err)
}
})
// We've successfully reestablished the channel, mark it as such to
// allow the switch to forward HTLCs in the outbound direction.
l.markReestablished()
// Now that we've received both channel_ready and channel reestablish,
// we can go ahead and send the active channel notification. We'll also
// defer the inactive notification for when the link exits to ensure
// that every active notification is matched by an inactive one.
l.cfg.NotifyActiveChannel(l.ChannelPoint())
defer l.cfg.NotifyInactiveChannel(l.ChannelPoint())
// With the channel states synced, we now reset the mailbox to ensure
// we start processing all unacked packets in order. This is done here
// to ensure that all acknowledgments that occur during channel
// resynchronization have taken affect, causing us only to pull unacked
// packets after starting to read from the downstream mailbox.
l.mailBox.ResetPackets()
// After cleaning up any memory pertaining to incoming packets, we now
// replay our forwarding packages to handle any htlcs that can be
// processed locally, or need to be forwarded out to the switch. We will
// only attempt to resolve packages if our short chan id indicates that
// the channel is not pending, otherwise we should have no htlcs to
// reforward.
if l.ShortChanID() != hop.Source {
err := l.resolveFwdPkgs(ctx)
switch err {
// No error was encountered, success.
case nil:
// If the duplicate keystone error was encountered, we'll fail
// without sending an Error message to the peer.
case ErrDuplicateKeystone:
l.failf(LinkFailureError{code: ErrCircuitError},
"temporary circuit error: %v", err)
return
// A non-nil error was encountered, send an Error message to
// the peer.
default:
l.failf(LinkFailureError{code: ErrInternalError},
"unable to resolve fwd pkgs: %v", err)
return
}
// With our link's in-memory state fully reconstructed, spawn a
// goroutine to manage the reclamation of disk space occupied by
// completed forwarding packages.
l.cg.WgAdd(1)
go l.fwdPkgGarbager()
}
for {
// We must always check if we failed at some point processing
// the last update before processing the next.
if l.failed {
l.log.Errorf("link failed, exiting htlcManager")
return
}
// If the previous event resulted in a non-empty batch, resume
// the batch ticker so that it can be cleared. Otherwise pause
// the ticker to prevent waking up the htlcManager while the
// batch is empty.
numUpdates := l.channel.NumPendingUpdates(
lntypes.Local, lntypes.Remote,
)
if numUpdates > 0 {
l.cfg.BatchTicker.Resume()
l.log.Tracef("BatchTicker resumed, "+
"NumPendingUpdates(Local, Remote)=%d",
numUpdates,
)
} else {
l.cfg.BatchTicker.Pause()
l.log.Trace("BatchTicker paused due to zero " +
"NumPendingUpdates(Local, Remote)")
}
select {
// We have a new hook that needs to be run when we reach a clean
// channel state.
case hook := <-l.flushHooks.newTransients:
if l.channel.IsChannelClean() {
hook()
} else {
l.flushHooks.alloc(hook)
}
// We have a new hook that needs to be run when we have
// committed all of our updates.
case hook := <-l.outgoingCommitHooks.newTransients:
if !l.channel.OweCommitment() {
hook()
} else {
l.outgoingCommitHooks.alloc(hook)
}
// We have a new hook that needs to be run when our peer has
// committed all of their updates.
case hook := <-l.incomingCommitHooks.newTransients:
if !l.channel.NeedCommitment() {
hook()
} else {
l.incomingCommitHooks.alloc(hook)
}
// Our update fee timer has fired, so we'll check the network
// fee to see if we should adjust our commitment fee.
case <-l.updateFeeTimer.C:
l.updateFeeTimer.Reset(l.randomFeeUpdateTimeout())
// If we're not the initiator of the channel, don't we
// don't control the fees, so we can ignore this.
if !l.channel.IsInitiator() {
continue
}
// If we are the initiator, then we'll sample the
// current fee rate to get into the chain within 3
// blocks.
netFee, err := l.sampleNetworkFee()
if err != nil {
l.log.Errorf("unable to sample network fee: %v",
err)
continue
}
minRelayFee := l.cfg.FeeEstimator.RelayFeePerKW()
newCommitFee := l.channel.IdealCommitFeeRate(
netFee, minRelayFee,
l.cfg.MaxAnchorsCommitFeeRate,
l.cfg.MaxFeeAllocation,
)
// We determine if we should adjust the commitment fee
// based on the current commitment fee, the suggested
// new commitment fee and the current minimum relay fee
// rate.
commitFee := l.channel.CommitFeeRate()
if !shouldAdjustCommitFee(
newCommitFee, commitFee, minRelayFee,
) {
continue
}
// If we do, then we'll send a new UpdateFee message to
// the remote party, to be locked in with a new update.
err = l.updateChannelFee(ctx, newCommitFee)
if err != nil {
l.log.Errorf("unable to update fee rate: %v",
err)
continue
}
// The underlying channel has notified us of a unilateral close
// carried out by the remote peer. In the case of such an
// event, we'll wipe the channel state from the peer, and mark
// the contract as fully settled. Afterwards we can exit.
//
// TODO(roasbeef): add force closure? also breach?
case <-l.cfg.ChainEvents.RemoteUnilateralClosure:
l.log.Warnf("remote peer has closed on-chain")
// TODO(roasbeef): remove all together
go func() {
chanPoint := l.channel.ChannelPoint()
l.cfg.Peer.WipeChannel(&chanPoint)
}()
return
case <-l.cfg.BatchTicker.Ticks():
// Attempt to extend the remote commitment chain
// including all the currently pending entries. If the
// send was unsuccessful, then abandon the update,
// waiting for the revocation window to open up.
if !l.updateCommitTxOrFail(ctx) {
return
}
case <-l.cfg.PendingCommitTicker.Ticks():
l.failf(
LinkFailureError{
code: ErrRemoteUnresponsive,
FailureAction: LinkFailureDisconnect,
},
"unable to complete dance",
)
return
// A message from the switch was just received. This indicates
// that the link is an intermediate hop in a multi-hop HTLC
// circuit.
case pkt := <-l.downstream:
l.handleDownstreamPkt(ctx, pkt)
// A message from the connected peer was just received. This
// indicates that we have a new incoming HTLC, either directly
// for us, or part of a multi-hop HTLC circuit.
case msg := <-l.upstream:
l.handleUpstreamMsg(ctx, msg)
// A htlc resolution is received. This means that we now have a
// resolution for a previously accepted htlc.
case hodlItem := <-l.hodlQueue.ChanOut():
htlcResolution := hodlItem.(invoices.HtlcResolution)
err := l.processHodlQueue(ctx, htlcResolution)
switch err {
// No error, success.
case nil:
// If the duplicate keystone error was encountered,
// fail back gracefully.
case ErrDuplicateKeystone:
l.failf(LinkFailureError{
code: ErrCircuitError,
}, "process hodl queue: "+
"temporary circuit error: %v",
err,
)
// Send an Error message to the peer.
default:
l.failf(LinkFailureError{
code: ErrInternalError,
}, "process hodl queue: unable to update "+
"commitment: %v", err,
)
}
case qReq := <-l.quiescenceReqs:
l.quiescer.InitStfu(qReq)
if l.noDanglingUpdates(lntypes.Local) {
err := l.quiescer.SendOwedStfu()
if err != nil {
l.stfuFailf(
"SendOwedStfu: %s", err.Error(),
)
res := fn.Err[lntypes.ChannelParty](err)
qReq.Resolve(res)
}
}
case <-l.cg.Done():
return
}
}
}
// processHodlQueue processes a received htlc resolution and continues reading
// from the hodl queue until no more resolutions remain. When this function
// returns without an error, the commit tx should be updated.
func (l *channelLink) processHodlQueue(ctx context.Context,
firstResolution invoices.HtlcResolution) error {
// Try to read all waiting resolution messages, so that they can all be
// processed in a single commitment tx update.
htlcResolution := firstResolution
loop:
for {
// Lookup all hodl htlcs that can be failed or settled with this event.
// The hodl htlc must be present in the map.
circuitKey := htlcResolution.CircuitKey()
hodlHtlc, ok := l.hodlMap[circuitKey]
if !ok {
return fmt.Errorf("hodl htlc not found: %v", circuitKey)
}
if err := l.processHtlcResolution(htlcResolution, hodlHtlc); err != nil {
return err
}
// Clean up hodl map.
delete(l.hodlMap, circuitKey)
select {
case item := <-l.hodlQueue.ChanOut():
htlcResolution = item.(invoices.HtlcResolution)
// No need to process it if the link is broken.
case <-l.cg.Done():
return ErrLinkShuttingDown
default:
break loop
}
}
// Update the commitment tx.
if err := l.updateCommitTx(ctx); err != nil {
return err
}
return nil
}
// processHtlcResolution applies a received htlc resolution to the provided
// htlc. When this function returns without an error, the commit tx should be
// updated.
func (l *channelLink) processHtlcResolution(resolution invoices.HtlcResolution,
htlc hodlHtlc) error {
circuitKey := resolution.CircuitKey()
// Determine required action for the resolution based on the type of
// resolution we have received.
switch res := resolution.(type) {
// Settle htlcs that returned a settle resolution using the preimage
// in the resolution.
case *invoices.HtlcSettleResolution:
l.log.Debugf("received settle resolution for %v "+
"with outcome: %v", circuitKey, res.Outcome)
return l.settleHTLC(
res.Preimage, htlc.add.ID, htlc.sourceRef,
)
// For htlc failures, we get the relevant failure message based
// on the failure resolution and then fail the htlc.
case *invoices.HtlcFailResolution:
l.log.Debugf("received cancel resolution for "+
"%v with outcome: %v", circuitKey, res.Outcome)
// Get the lnwire failure message based on the resolution
// result.
failure := getResolutionFailure(res, htlc.add.Amount)
l.sendHTLCError(
htlc.add, htlc.sourceRef, failure, htlc.obfuscator,
true,
)
return nil
// Fail if we do not get a settle of fail resolution, since we
// are only expecting to handle settles and fails.
default:
return fmt.Errorf("unknown htlc resolution type: %T",
resolution)
}
}
// getResolutionFailure returns the wire message that a htlc resolution should
// be failed with.
func getResolutionFailure(resolution *invoices.HtlcFailResolution,
amount lnwire.MilliSatoshi) *LinkError {
// If the resolution has been resolved as part of a MPP timeout,
// we need to fail the htlc with lnwire.FailMppTimeout.
if resolution.Outcome == invoices.ResultMppTimeout {
return NewDetailedLinkError(
&lnwire.FailMPPTimeout{}, resolution.Outcome,
)
}
// If the htlc is not a MPP timeout, we fail it with
// FailIncorrectDetails. This error is sent for invoice payment
// failures such as underpayment/ expiry too soon and hodl invoices
// (which return FailIncorrectDetails to avoid leaking information).
incorrectDetails := lnwire.NewFailIncorrectDetails(
amount, uint32(resolution.AcceptHeight),
)
return NewDetailedLinkError(incorrectDetails, resolution.Outcome)
}
// randomFeeUpdateTimeout returns a random timeout between the bounds defined
// within the link's configuration that will be used to determine when the link
// should propose an update to its commitment fee rate.
func (l *channelLink) randomFeeUpdateTimeout() time.Duration {
lower := int64(l.cfg.MinUpdateTimeout)
upper := int64(l.cfg.MaxUpdateTimeout)
return time.Duration(prand.Int63n(upper-lower) + lower)
}
// handleDownstreamUpdateAdd processes an UpdateAddHTLC packet sent from the
// downstream HTLC Switch.
func (l *channelLink) handleDownstreamUpdateAdd(ctx context.Context,
pkt *htlcPacket) error {
htlc, ok := pkt.htlc.(*lnwire.UpdateAddHTLC)
if !ok {
return errors.New("not an UpdateAddHTLC packet")
}
// If we are flushing the link in the outgoing direction or we have
// already sent Stfu, then we can't add new htlcs to the link and we
// need to bounce it.
if l.IsFlushing(Outgoing) || !l.quiescer.CanSendUpdates() {
l.mailBox.FailAdd(pkt)
return NewDetailedLinkError(
&lnwire.FailTemporaryChannelFailure{},
OutgoingFailureLinkNotEligible,
)
}
// If hodl.AddOutgoing mode is active, we exit early to simulate
// arbitrary delays between the switch adding an ADD to the
// mailbox, and the HTLC being added to the commitment state.
if l.cfg.HodlMask.Active(hodl.AddOutgoing) {
l.log.Warnf(hodl.AddOutgoing.Warning())
l.mailBox.AckPacket(pkt.inKey())
return nil
}
// Check if we can add the HTLC here without exceededing the max fee
// exposure threshold.
if l.isOverexposedWithHtlc(htlc, false) {
l.log.Debugf("Unable to handle downstream HTLC - max fee " +
"exposure exceeded")
l.mailBox.FailAdd(pkt)
return NewDetailedLinkError(
lnwire.NewTemporaryChannelFailure(nil),
OutgoingFailureDownstreamHtlcAdd,
)
}
// A new payment has been initiated via the downstream channel,
// so we add the new HTLC to our local log, then update the
// commitment chains.
htlc.ChanID = l.ChanID()
openCircuitRef := pkt.inKey()
// We enforce the fee buffer for the commitment transaction because
// we are in control of adding this htlc. Nothing has locked-in yet so
// we can securely enforce the fee buffer which is only relevant if we
// are the initiator of the channel.
index, err := l.channel.AddHTLC(htlc, &openCircuitRef)
if err != nil {
// The HTLC was unable to be added to the state machine,
// as a result, we'll signal the switch to cancel the
// pending payment.
l.log.Warnf("Unable to handle downstream add HTLC: %v",
err)
// Remove this packet from the link's mailbox, this
// prevents it from being reprocessed if the link
// restarts and resets it mailbox. If this response
// doesn't make it back to the originating link, it will
// be rejected upon attempting to reforward the Add to
// the switch, since the circuit was never fully opened,
// and the forwarding package shows it as
// unacknowledged.
l.mailBox.FailAdd(pkt)
return NewDetailedLinkError(
lnwire.NewTemporaryChannelFailure(nil),
OutgoingFailureDownstreamHtlcAdd,
)
}
l.log.Tracef("received downstream htlc: payment_hash=%x, "+
"local_log_index=%v, pend_updates=%v",
htlc.PaymentHash[:], index,
l.channel.NumPendingUpdates(lntypes.Local, lntypes.Remote))
pkt.outgoingChanID = l.ShortChanID()
pkt.outgoingHTLCID = index
htlc.ID = index
l.log.Debugf("queueing keystone of ADD open circuit: %s->%s",
pkt.inKey(), pkt.outKey())
l.openedCircuits = append(l.openedCircuits, pkt.inKey())
l.keystoneBatch = append(l.keystoneBatch, pkt.keystone())
_ = l.cfg.Peer.SendMessage(false, htlc)
// Send a forward event notification to htlcNotifier.
l.cfg.HtlcNotifier.NotifyForwardingEvent(
newHtlcKey(pkt),
HtlcInfo{
IncomingTimeLock: pkt.incomingTimeout,
IncomingAmt: pkt.incomingAmount,
OutgoingTimeLock: htlc.Expiry,
OutgoingAmt: htlc.Amount,
},
getEventType(pkt),
)
l.tryBatchUpdateCommitTx(ctx)
return nil
}
// handleDownstreamPkt processes an HTLC packet sent from the downstream HTLC
// Switch. Possible messages sent by the switch include requests to forward new
// HTLCs, timeout previously cleared HTLCs, and finally to settle currently
// cleared HTLCs with the upstream peer.
//
// TODO(roasbeef): add sync ntfn to ensure switch always has consistent view?
func (l *channelLink) handleDownstreamPkt(ctx context.Context,
pkt *htlcPacket) {
if pkt.htlc.MsgType().IsChannelUpdate() &&
!l.quiescer.CanSendUpdates() {
l.log.Warnf("unable to process channel update. "+
"ChannelID=%v is quiescent.", l.ChanID)
return
}
switch htlc := pkt.htlc.(type) {
case *lnwire.UpdateAddHTLC:
// Handle add message. The returned error can be ignored,
// because it is also sent through the mailbox.
_ = l.handleDownstreamUpdateAdd(ctx, pkt)
case *lnwire.UpdateFulfillHTLC:
// If hodl.SettleOutgoing mode is active, we exit early to
// simulate arbitrary delays between the switch adding the
// SETTLE to the mailbox, and the HTLC being added to the
// commitment state.
if l.cfg.HodlMask.Active(hodl.SettleOutgoing) {
l.log.Warnf(hodl.SettleOutgoing.Warning())
l.mailBox.AckPacket(pkt.inKey())
return
}
// An HTLC we forward to the switch has just settled somewhere
// upstream. Therefore we settle the HTLC within the our local
// state machine.
inKey := pkt.inKey()
err := l.channel.SettleHTLC(
htlc.PaymentPreimage,
pkt.incomingHTLCID,
pkt.sourceRef,
pkt.destRef,
&inKey,
)
if err != nil {
l.log.Errorf("unable to settle incoming HTLC for "+
"circuit-key=%v: %v", inKey, err)
// If the HTLC index for Settle response was not known
// to our commitment state, it has already been
// cleaned up by a prior response. We'll thus try to
// clean up any lingering state to ensure we don't
// continue reforwarding.
if _, ok := err.(lnwallet.ErrUnknownHtlcIndex); ok {
l.cleanupSpuriousResponse(pkt)
}
// Remove the packet from the link's mailbox to ensure
// it doesn't get replayed after a reconnection.
l.mailBox.AckPacket(inKey)
return
}
l.log.Debugf("queueing removal of SETTLE closed circuit: "+
"%s->%s", pkt.inKey(), pkt.outKey())
l.closedCircuits = append(l.closedCircuits, pkt.inKey())
// With the HTLC settled, we'll need to populate the wire
// message to target the specific channel and HTLC to be
// canceled.
htlc.ChanID = l.ChanID()
htlc.ID = pkt.incomingHTLCID
// Then we send the HTLC settle message to the connected peer
// so we can continue the propagation of the settle message.
l.cfg.Peer.SendMessage(false, htlc)
// Send a settle event notification to htlcNotifier.
l.cfg.HtlcNotifier.NotifySettleEvent(
newHtlcKey(pkt),
htlc.PaymentPreimage,
getEventType(pkt),
)
// Immediately update the commitment tx to minimize latency.
l.updateCommitTxOrFail(ctx)
case *lnwire.UpdateFailHTLC:
// If hodl.FailOutgoing mode is active, we exit early to
// simulate arbitrary delays between the switch adding a FAIL to
// the mailbox, and the HTLC being added to the commitment
// state.
if l.cfg.HodlMask.Active(hodl.FailOutgoing) {
l.log.Warnf(hodl.FailOutgoing.Warning())
l.mailBox.AckPacket(pkt.inKey())
return
}
// An HTLC cancellation has been triggered somewhere upstream,
// we'll remove then HTLC from our local state machine.
inKey := pkt.inKey()
err := l.channel.FailHTLC(
pkt.incomingHTLCID,
htlc.Reason,
pkt.sourceRef,
pkt.destRef,
&inKey,
)
if err != nil {
l.log.Errorf("unable to cancel incoming HTLC for "+
"circuit-key=%v: %v", inKey, err)
// If the HTLC index for Fail response was not known to
// our commitment state, it has already been cleaned up
// by a prior response. We'll thus try to clean up any
// lingering state to ensure we don't continue
// reforwarding.
if _, ok := err.(lnwallet.ErrUnknownHtlcIndex); ok {
l.cleanupSpuriousResponse(pkt)
}
// Remove the packet from the link's mailbox to ensure
// it doesn't get replayed after a reconnection.
l.mailBox.AckPacket(inKey)
return
}
l.log.Debugf("queueing removal of FAIL closed circuit: %s->%s",
pkt.inKey(), pkt.outKey())
l.closedCircuits = append(l.closedCircuits, pkt.inKey())
// With the HTLC removed, we'll need to populate the wire
// message to target the specific channel and HTLC to be
// canceled. The "Reason" field will have already been set
// within the switch.
htlc.ChanID = l.ChanID()
htlc.ID = pkt.incomingHTLCID
// We send the HTLC message to the peer which initially created
// the HTLC. If the incoming blinding point is non-nil, we
// know that we are a relaying node in a blinded path.
// Otherwise, we're either an introduction node or not part of
// a blinded path at all.
if err := l.sendIncomingHTLCFailureMsg(
htlc.ID,
pkt.obfuscator,
htlc.Reason,
); err != nil {
l.log.Errorf("unable to send HTLC failure: %v",
err)
return
}
// If the packet does not have a link failure set, it failed
// further down the route so we notify a forwarding failure.
// Otherwise, we notify a link failure because it failed at our
// node.
if pkt.linkFailure != nil {
l.cfg.HtlcNotifier.NotifyLinkFailEvent(
newHtlcKey(pkt),
newHtlcInfo(pkt),
getEventType(pkt),
pkt.linkFailure,
false,
)
} else {
l.cfg.HtlcNotifier.NotifyForwardingFailEvent(
newHtlcKey(pkt), getEventType(pkt),
)
}
// Immediately update the commitment tx to minimize latency.
l.updateCommitTxOrFail(ctx)
}
}
// tryBatchUpdateCommitTx updates the commitment transaction if the batch is
// full.
func (l *channelLink) tryBatchUpdateCommitTx(ctx context.Context) {
pending := l.channel.NumPendingUpdates(lntypes.Local, lntypes.Remote)
if pending < uint64(l.cfg.BatchSize) {
return
}
l.updateCommitTxOrFail(ctx)
}
// cleanupSpuriousResponse attempts to ack any AddRef or SettleFailRef
// associated with this packet. If successful in doing so, it will also purge
// the open circuit from the circuit map and remove the packet from the link's
// mailbox.
func (l *channelLink) cleanupSpuriousResponse(pkt *htlcPacket) {
inKey := pkt.inKey()
l.log.Debugf("cleaning up spurious response for incoming "+
"circuit-key=%v", inKey)
// If the htlc packet doesn't have a source reference, it is unsafe to
// proceed, as skipping this ack may cause the htlc to be reforwarded.
if pkt.sourceRef == nil {
l.log.Errorf("unable to cleanup response for incoming "+
"circuit-key=%v, does not contain source reference",
inKey)
return
}
// If the source reference is present, we will try to prevent this link
// from resending the packet to the switch. To do so, we ack the AddRef
// of the incoming HTLC belonging to this link.
err := l.channel.AckAddHtlcs(*pkt.sourceRef)
if err != nil {
l.log.Errorf("unable to ack AddRef for incoming "+
"circuit-key=%v: %v", inKey, err)
// If this operation failed, it is unsafe to attempt removal of
// the destination reference or circuit, so we exit early. The
// cleanup may proceed with a different packet in the future
// that succeeds on this step.
return
}
// Now that we know this link will stop retransmitting Adds to the
// switch, we can begin to teardown the response reference and circuit
// map.
//
// If the packet includes a destination reference, then a response for
// this HTLC was locked into the outgoing channel. Attempt to remove
// this reference, so we stop retransmitting the response internally.
// Even if this fails, we will proceed in trying to delete the circuit.
// When retransmitting responses, the destination references will be
// cleaned up if an open circuit is not found in the circuit map.
if pkt.destRef != nil {
err := l.channel.AckSettleFails(*pkt.destRef)
if err != nil {
l.log.Errorf("unable to ack SettleFailRef "+
"for incoming circuit-key=%v: %v",
inKey, err)
}
}
l.log.Debugf("deleting circuit for incoming circuit-key=%x", inKey)
// With all known references acked, we can now safely delete the circuit
// from the switch's circuit map, as the state is no longer needed.
err = l.cfg.Circuits.DeleteCircuits(inKey)
if err != nil {
l.log.Errorf("unable to delete circuit for "+
"circuit-key=%v: %v", inKey, err)
}
}
// handleUpstreamMsg processes wire messages related to commitment state
// updates from the upstream peer. The upstream peer is the peer whom we have a
// direct channel with, updating our respective commitment chains.
//
//nolint:funlen
func (l *channelLink) handleUpstreamMsg(ctx context.Context,
msg lnwire.Message) {
l.log.Tracef("receive upstream msg %v, handling now... ", msg.MsgType())
defer l.log.Tracef("handled upstream msg %v", msg.MsgType())
// First check if the message is an update and we are capable of
// receiving updates right now.
if msg.MsgType().IsChannelUpdate() && !l.quiescer.CanRecvUpdates() {
l.stfuFailf("update received after stfu: %T", msg)
return
}
switch msg := msg.(type) {
case *lnwire.UpdateAddHTLC:
if l.IsFlushing(Incoming) {
// This is forbidden by the protocol specification.
// The best chance we have to deal with this is to drop
// the connection. This should roll back the channel
// state to the last CommitSig. If the remote has
// already sent a CommitSig we haven't received yet,
// channel state will be re-synchronized with a
// ChannelReestablish message upon reconnection and the
// protocol state that caused us to flush the link will
// be rolled back. In the event that there was some
// non-deterministic behavior in the remote that caused
// them to violate the protocol, we have a decent shot
// at correcting it this way, since reconnecting will
// put us in the cleanest possible state to try again.
//
// In addition to the above, it is possible for us to
// hit this case in situations where we improperly
// handle message ordering due to concurrency choices.
// An issue has been filed to address this here:
// https://github.com/lightningnetwork/lnd/issues/8393
l.failf(
LinkFailureError{
code: ErrInvalidUpdate,
FailureAction: LinkFailureDisconnect,
PermanentFailure: false,
Warning: true,
},
"received add while link is flushing",
)
return
}
// Disallow htlcs with blinding points set if we haven't
// enabled the feature. This saves us from having to process
// the onion at all, but will only catch blinded payments
// where we are a relaying node (as the blinding point will
// be in the payload when we're the introduction node).
if msg.BlindingPoint.IsSome() && l.cfg.DisallowRouteBlinding {
l.failf(LinkFailureError{code: ErrInvalidUpdate},
"blinding point included when route blinding "+
"is disabled")
return
}
// We have to check the limit here rather than later in the
// switch because the counterparty can keep sending HTLC's
// without sending a revoke. This would mean that the switch
// check would only occur later.
if l.isOverexposedWithHtlc(msg, true) {
l.failf(LinkFailureError{code: ErrInternalError},
"peer sent us an HTLC that exceeded our max "+
"fee exposure")
return
}
// We just received an add request from an upstream peer, so we
// add it to our state machine, then add the HTLC to our
// "settle" list in the event that we know the preimage.
index, err := l.channel.ReceiveHTLC(msg)
if err != nil {
l.failf(LinkFailureError{code: ErrInvalidUpdate},
"unable to handle upstream add HTLC: %v", err)
return
}
l.log.Tracef("receive upstream htlc with payment hash(%x), "+
"assigning index: %v", msg.PaymentHash[:], index)
case *lnwire.UpdateFulfillHTLC:
pre := msg.PaymentPreimage
idx := msg.ID
// Before we pipeline the settle, we'll check the set of active
// htlc's to see if the related UpdateAddHTLC has been fully
// locked-in.
var lockedin bool
htlcs := l.channel.ActiveHtlcs()
for _, add := range htlcs {
// The HTLC will be outgoing and match idx.
if !add.Incoming && add.HtlcIndex == idx {
lockedin = true
break
}
}
if !lockedin {
l.failf(
LinkFailureError{code: ErrInvalidUpdate},
"unable to handle upstream settle",
)
return
}
if err := l.channel.ReceiveHTLCSettle(pre, idx); err != nil {
l.failf(
LinkFailureError{
code: ErrInvalidUpdate,
FailureAction: LinkFailureForceClose,
},
"unable to handle upstream settle HTLC: %v", err,
)
return
}
settlePacket := &htlcPacket{
outgoingChanID: l.ShortChanID(),
outgoingHTLCID: idx,
htlc: &lnwire.UpdateFulfillHTLC{
PaymentPreimage: pre,
},
}
// Add the newly discovered preimage to our growing list of
// uncommitted preimage. These will be written to the witness
// cache just before accepting the next commitment signature
// from the remote peer.
l.uncommittedPreimages = append(l.uncommittedPreimages, pre)
// Pipeline this settle, send it to the switch.
go l.forwardBatch(false, settlePacket)
case *lnwire.UpdateFailMalformedHTLC:
// Convert the failure type encoded within the HTLC fail
// message to the proper generic lnwire error code.
var failure lnwire.FailureMessage
switch msg.FailureCode {
case lnwire.CodeInvalidOnionVersion:
failure = &lnwire.FailInvalidOnionVersion{
OnionSHA256: msg.ShaOnionBlob,
}
case lnwire.CodeInvalidOnionHmac:
failure = &lnwire.FailInvalidOnionHmac{
OnionSHA256: msg.ShaOnionBlob,
}
case lnwire.CodeInvalidOnionKey:
failure = &lnwire.FailInvalidOnionKey{
OnionSHA256: msg.ShaOnionBlob,
}
// Handle malformed errors that are part of a blinded route.
// This case is slightly different, because we expect every
// relaying node in the blinded portion of the route to send
// malformed errors. If we're also a relaying node, we're
// likely going to switch this error out anyway for our own
// malformed error, but we handle the case here for
// completeness.
case lnwire.CodeInvalidBlinding:
failure = &lnwire.FailInvalidBlinding{
OnionSHA256: msg.ShaOnionBlob,
}
default:
l.log.Warnf("unexpected failure code received in "+
"UpdateFailMailformedHTLC: %v", msg.FailureCode)
// We don't just pass back the error we received from
// our successor. Otherwise we might report a failure
// that penalizes us more than needed. If the onion that
// we forwarded was correct, the node should have been
// able to send back its own failure. The node did not
// send back its own failure, so we assume there was a
// problem with the onion and report that back. We reuse
// the invalid onion key failure because there is no
// specific error for this case.
failure = &lnwire.FailInvalidOnionKey{
OnionSHA256: msg.ShaOnionBlob,
}
}
// With the error parsed, we'll convert the into it's opaque
// form.
var b bytes.Buffer
if err := lnwire.EncodeFailure(&b, failure, 0); err != nil {
l.log.Errorf("unable to encode malformed error: %v", err)
return
}
// If remote side have been unable to parse the onion blob we
// have sent to it, than we should transform the malformed HTLC
// message to the usual HTLC fail message.
err := l.channel.ReceiveFailHTLC(msg.ID, b.Bytes())
if err != nil {
l.failf(LinkFailureError{code: ErrInvalidUpdate},
"unable to handle upstream fail HTLC: %v", err)
return
}
case *lnwire.UpdateFailHTLC:
// Verify that the failure reason is at least 256 bytes plus
// overhead.
const minimumFailReasonLength = lnwire.FailureMessageLength +
2 + 2 + 32
if len(msg.Reason) < minimumFailReasonLength {
// We've received a reason with a non-compliant length.
// Older nodes happily relay back these failures that
// may originate from a node further downstream.
// Therefore we can't just fail the channel.
//
// We want to be compliant ourselves, so we also can't
// pass back the reason unmodified. And we must make
// sure that we don't hit the magic length check of 260
// bytes in processRemoteSettleFails either.
//
// Because the reason is unreadable for the payer
// anyway, we just replace it by a compliant-length
// series of random bytes.
msg.Reason = make([]byte, minimumFailReasonLength)
_, err := crand.Read(msg.Reason[:])
if err != nil {
l.log.Errorf("Random generation error: %v", err)
return
}
}
// Add fail to the update log.
idx := msg.ID
err := l.channel.ReceiveFailHTLC(idx, msg.Reason[:])
if err != nil {
l.failf(LinkFailureError{code: ErrInvalidUpdate},
"unable to handle upstream fail HTLC: %v", err)
return
}
case *lnwire.CommitSig:
// Since we may have learned new preimages for the first time,
// we'll add them to our preimage cache. By doing this, we
// ensure any contested contracts watched by any on-chain
// arbitrators can now sweep this HTLC on-chain. We delay
// committing the preimages until just before accepting the new
// remote commitment, as afterwards the peer won't resend the
// Settle messages on the next channel reestablishment. Doing so
// allows us to more effectively batch this operation, instead
// of doing a single write per preimage.
err := l.cfg.PreimageCache.AddPreimages(
l.uncommittedPreimages...,
)
if err != nil {
l.failf(
LinkFailureError{code: ErrInternalError},
"unable to add preimages=%v to cache: %v",
l.uncommittedPreimages, err,
)
return
}
// Instead of truncating the slice to conserve memory
// allocations, we simply set the uncommitted preimage slice to
// nil so that a new one will be initialized if any more
// witnesses are discovered. We do this because the maximum size
// that the slice can occupy is 15KB, and we want to ensure we
// release that memory back to the runtime.
l.uncommittedPreimages = nil
// We just received a new updates to our local commitment
// chain, validate this new commitment, closing the link if
// invalid.
auxSigBlob, err := msg.CustomRecords.Serialize()
if err != nil {
l.failf(
LinkFailureError{code: ErrInvalidCommitment},
"unable to serialize custom records: %v", err,
)
return
}
err = l.channel.ReceiveNewCommitment(&lnwallet.CommitSigs{
CommitSig: msg.CommitSig,
HtlcSigs: msg.HtlcSigs,
PartialSig: msg.PartialSig,
AuxSigBlob: auxSigBlob,
})
if err != nil {
// If we were unable to reconstruct their proposed
// commitment, then we'll examine the type of error. If
// it's an InvalidCommitSigError, then we'll send a
// direct error.
var sendData []byte
switch err.(type) {
case *lnwallet.InvalidCommitSigError:
sendData = []byte(err.Error())
case *lnwallet.InvalidHtlcSigError:
sendData = []byte(err.Error())
}
l.failf(
LinkFailureError{
code: ErrInvalidCommitment,
FailureAction: LinkFailureForceClose,
SendData: sendData,
},
"ChannelPoint(%v): unable to accept new "+
"commitment: %v",
l.channel.ChannelPoint(), err,
)
return
}
// As we've just accepted a new state, we'll now
// immediately send the remote peer a revocation for our prior
// state.
nextRevocation, currentHtlcs, finalHTLCs, err :=
l.channel.RevokeCurrentCommitment()
if err != nil {
l.log.Errorf("unable to revoke commitment: %v", err)
// We need to fail the channel in case revoking our
// local commitment does not succeed. We might have
// already advanced our channel state which would lead
// us to proceed with an unclean state.
//
// NOTE: We do not trigger a force close because this
// could resolve itself in case our db was just busy
// not accepting new transactions.
l.failf(
LinkFailureError{
code: ErrInternalError,
Warning: true,
FailureAction: LinkFailureDisconnect,
},
"ChannelPoint(%v): unable to accept new "+
"commitment: %v",
l.channel.ChannelPoint(), err,
)
return
}
// As soon as we are ready to send our next revocation, we can
// invoke the incoming commit hooks.
l.RWMutex.Lock()
l.incomingCommitHooks.invoke()
l.RWMutex.Unlock()
l.cfg.Peer.SendMessage(false, nextRevocation)
// Notify the incoming htlcs of which the resolutions were
// locked in.
for id, settled := range finalHTLCs {
l.cfg.HtlcNotifier.NotifyFinalHtlcEvent(
models.CircuitKey{
ChanID: l.ShortChanID(),
HtlcID: id,
},
channeldb.FinalHtlcInfo{
Settled: settled,
Offchain: true,
},
)
}
// Since we just revoked our commitment, we may have a new set
// of HTLC's on our commitment, so we'll send them using our
// function closure NotifyContractUpdate.
newUpdate := &contractcourt.ContractUpdate{
HtlcKey: contractcourt.LocalHtlcSet,
Htlcs: currentHtlcs,
}
err = l.cfg.NotifyContractUpdate(newUpdate)
if err != nil {
l.log.Errorf("unable to notify contract update: %v",
err)
return
}
select {
case <-l.cg.Done():
return
default:
}
// If the remote party initiated the state transition,
// we'll reply with a signature to provide them with their
// version of the latest commitment. Otherwise, both commitment
// chains are fully synced from our PoV, then we don't need to
// reply with a signature as both sides already have a
// commitment with the latest accepted.
if l.channel.OweCommitment() {
if !l.updateCommitTxOrFail(ctx) {
return
}
}
// If we need to send out an Stfu, this would be the time to do
// so.
if l.noDanglingUpdates(lntypes.Local) {
err = l.quiescer.SendOwedStfu()
if err != nil {
l.stfuFailf("sendOwedStfu: %v", err.Error())
}
}
// Now that we have finished processing the incoming CommitSig
// and sent out our RevokeAndAck, we invoke the flushHooks if
// the channel state is clean.
l.RWMutex.Lock()
if l.channel.IsChannelClean() {
l.flushHooks.invoke()
}
l.RWMutex.Unlock()
case *lnwire.RevokeAndAck:
// We've received a revocation from the remote chain, if valid,
// this moves the remote chain forward, and expands our
// revocation window.
// We now process the message and advance our remote commit
// chain.
fwdPkg, remoteHTLCs, err := l.channel.ReceiveRevocation(msg)
if err != nil {
// TODO(halseth): force close?
l.failf(
LinkFailureError{
code: ErrInvalidRevocation,
FailureAction: LinkFailureDisconnect,
},
"unable to accept revocation: %v", err,
)
return
}
// The remote party now has a new primary commitment, so we'll
// update the contract court to be aware of this new set (the
// prior old remote pending).
newUpdate := &contractcourt.ContractUpdate{
HtlcKey: contractcourt.RemoteHtlcSet,
Htlcs: remoteHTLCs,
}
err = l.cfg.NotifyContractUpdate(newUpdate)
if err != nil {
l.log.Errorf("unable to notify contract update: %v",
err)
return
}
select {
case <-l.cg.Done():
return
default:
}
// If we have a tower client for this channel type, we'll
// create a backup for the current state.
if l.cfg.TowerClient != nil {
state := l.channel.State()
chanID := l.ChanID()
err = l.cfg.TowerClient.BackupState(
&chanID, state.RemoteCommitment.CommitHeight-1,
)
if err != nil {
l.failf(LinkFailureError{
code: ErrInternalError,
}, "unable to queue breach backup: %v", err)
return
}
}
// If we can send updates then we can process adds in case we
// are the exit hop and need to send back resolutions, or in
// case there are validity issues with the packets. Otherwise
// we defer the action until resume.
//
// We are free to process the settles and fails without this
// check since processing those can't result in further updates
// to this channel link.
if l.quiescer.CanSendUpdates() {
l.processRemoteAdds(fwdPkg)
} else {
l.quiescer.OnResume(func() {
l.processRemoteAdds(fwdPkg)
})
}
l.processRemoteSettleFails(fwdPkg)
// If the link failed during processing the adds, we must
// return to ensure we won't attempted to update the state
// further.
if l.failed {
return
}
// The revocation window opened up. If there are pending local
// updates, try to update the commit tx. Pending updates could
// already have been present because of a previously failed
// update to the commit tx or freshly added in by
// processRemoteAdds. Also in case there are no local updates,
// but there are still remote updates that are not in the remote
// commit tx yet, send out an update.
if l.channel.OweCommitment() {
if !l.updateCommitTxOrFail(ctx) {
return
}
}
// Now that we have finished processing the RevokeAndAck, we
// can invoke the flushHooks if the channel state is clean.
l.RWMutex.Lock()
if l.channel.IsChannelClean() {
l.flushHooks.invoke()
}
l.RWMutex.Unlock()
case *lnwire.UpdateFee:
// Check and see if their proposed fee-rate would make us
// exceed the fee threshold.
fee := chainfee.SatPerKWeight(msg.FeePerKw)
isDust, err := l.exceedsFeeExposureLimit(fee)
if err != nil {
// This shouldn't typically happen. If it does, it
// indicates something is wrong with our channel state.
l.log.Errorf("Unable to determine if fee threshold " +
"exceeded")
l.failf(LinkFailureError{code: ErrInternalError},
"error calculating fee exposure: %v", err)
return
}
if isDust {
// The proposed fee-rate makes us exceed the fee
// threshold.
l.failf(LinkFailureError{code: ErrInternalError},
"fee threshold exceeded: %v", err)
return
}
// We received fee update from peer. If we are the initiator we
// will fail the channel, if not we will apply the update.
if err := l.channel.ReceiveUpdateFee(fee); err != nil {
l.failf(LinkFailureError{code: ErrInvalidUpdate},
"error receiving fee update: %v", err)
return
}
// Update the mailbox's feerate as well.
l.mailBox.SetFeeRate(fee)
case *lnwire.Stfu:
err := l.handleStfu(msg)
if err != nil {
l.stfuFailf("handleStfu: %v", err.Error())
}
// In the case where we receive a warning message from our peer, just
// log it and move on. We choose not to disconnect from our peer,
// although we "MAY" do so according to the specification.
case *lnwire.Warning:
l.log.Warnf("received warning message from peer: %v",
msg.Warning())
case *lnwire.Error:
// Error received from remote, MUST fail channel, but should
// only print the contents of the error message if all
// characters are printable ASCII.
l.failf(
LinkFailureError{
code: ErrRemoteError,
// TODO(halseth): we currently don't fail the
// channel permanently, as there are some sync
// issues with other implementations that will
// lead to them sending an error message, but
// we can recover from on next connection. See
// https://github.com/ElementsProject/lightning/issues/4212
PermanentFailure: false,
},
"ChannelPoint(%v): received error from peer: %v",
l.channel.ChannelPoint(), msg.Error(),
)
default:
l.log.Warnf("received unknown message of type %T", msg)
}
}
// handleStfu implements the top-level logic for handling the Stfu message from
// our peer.
func (l *channelLink) handleStfu(stfu *lnwire.Stfu) error {
if !l.noDanglingUpdates(lntypes.Remote) {
return ErrPendingRemoteUpdates
}
err := l.quiescer.RecvStfu(*stfu)
if err != nil {
return err
}
// If we can immediately send an Stfu response back, we will.
if l.noDanglingUpdates(lntypes.Local) {
return l.quiescer.SendOwedStfu()
}
return nil
}
// stfuFailf fails the link in the case where the requirements of the quiescence
// protocol are violated. In all cases we opt to drop the connection as only
// link state (as opposed to channel state) is affected.
func (l *channelLink) stfuFailf(format string, args ...interface{}) {
l.failf(LinkFailureError{
code: ErrStfuViolation,
FailureAction: LinkFailureDisconnect,
PermanentFailure: false,
Warning: true,
}, format, args...)
}
// noDanglingUpdates returns true when there are 0 updates that were originally
// issued by whose on either the Local or Remote commitment transaction.
func (l *channelLink) noDanglingUpdates(whose lntypes.ChannelParty) bool {
pendingOnLocal := l.channel.NumPendingUpdates(
whose, lntypes.Local,
)
pendingOnRemote := l.channel.NumPendingUpdates(
whose, lntypes.Remote,
)
return pendingOnLocal == 0 && pendingOnRemote == 0
}
// ackDownStreamPackets is responsible for removing htlcs from a link's mailbox
// for packets delivered from server, and cleaning up any circuits closed by
// signing a previous commitment txn. This method ensures that the circuits are
// removed from the circuit map before removing them from the link's mailbox,
// otherwise it could be possible for some circuit to be missed if this link
// flaps.
func (l *channelLink) ackDownStreamPackets() error {
// First, remove the downstream Add packets that were included in the
// previous commitment signature. This will prevent the Adds from being
// replayed if this link disconnects.
for _, inKey := range l.openedCircuits {
// In order to test the sphinx replay logic of the remote
// party, unsafe replay does not acknowledge the packets from
// the mailbox. We can then force a replay of any Add packets
// held in memory by disconnecting and reconnecting the link.
if l.cfg.UnsafeReplay {
continue
}
l.log.Debugf("removing Add packet %s from mailbox", inKey)
l.mailBox.AckPacket(inKey)
}
// Now, we will delete all circuits closed by the previous commitment
// signature, which is the result of downstream Settle/Fail packets. We
// batch them here to ensure circuits are closed atomically and for
// performance.
err := l.cfg.Circuits.DeleteCircuits(l.closedCircuits...)
switch err {
case nil:
// Successful deletion.
default:
l.log.Errorf("unable to delete %d circuits: %v",
len(l.closedCircuits), err)
return err
}
// With the circuits removed from memory and disk, we now ack any
// Settle/Fails in the mailbox to ensure they do not get redelivered
// after startup. If forgive is enabled and we've reached this point,
// the circuits must have been removed at some point, so it is now safe
// to un-queue the corresponding Settle/Fails.
for _, inKey := range l.closedCircuits {
l.log.Debugf("removing Fail/Settle packet %s from mailbox",
inKey)
l.mailBox.AckPacket(inKey)
}
// Lastly, reset our buffers to be empty while keeping any acquired
// growth in the backing array.
l.openedCircuits = l.openedCircuits[:0]
l.closedCircuits = l.closedCircuits[:0]
return nil
}
// updateCommitTxOrFail updates the commitment tx and if that fails, it fails
// the link.
func (l *channelLink) updateCommitTxOrFail(ctx context.Context) bool {
err := l.updateCommitTx(ctx)
switch err {
// No error encountered, success.
case nil:
// A duplicate keystone error should be resolved and is not fatal, so
// we won't send an Error message to the peer.
case ErrDuplicateKeystone:
l.failf(LinkFailureError{code: ErrCircuitError},
"temporary circuit error: %v", err)
return false
// Any other error is treated results in an Error message being sent to
// the peer.
default:
l.failf(LinkFailureError{code: ErrInternalError},
"unable to update commitment: %v", err)
return false
}
return true
}
// updateCommitTx signs, then sends an update to the remote peer adding a new
// commitment to their commitment chain which includes all the latest updates
// we've received+processed up to this point.
func (l *channelLink) updateCommitTx(ctx context.Context) error {
// Preemptively write all pending keystones to disk, just in case the
// HTLCs we have in memory are included in the subsequent attempt to
// sign a commitment state.
err := l.cfg.Circuits.OpenCircuits(l.keystoneBatch...)
if err != nil {
// If ErrDuplicateKeystone is returned, the caller will catch
// it.
return err
}
// Reset the batch, but keep the backing buffer to avoid reallocating.
l.keystoneBatch = l.keystoneBatch[:0]
// If hodl.Commit mode is active, we will refrain from attempting to
// commit any in-memory modifications to the channel state. Exiting here
// permits testing of either the switch or link's ability to trim
// circuits that have been opened, but unsuccessfully committed.
if l.cfg.HodlMask.Active(hodl.Commit) {
l.log.Warnf(hodl.Commit.Warning())
return nil
}
ctx, done := l.cg.Create(ctx)
defer done()
newCommit, err := l.channel.SignNextCommitment(ctx)
if err == lnwallet.ErrNoWindow {
l.cfg.PendingCommitTicker.Resume()
l.log.Trace("PendingCommitTicker resumed")
n := l.channel.NumPendingUpdates(lntypes.Local, lntypes.Remote)
l.log.Tracef("revocation window exhausted, unable to send: "+
"%v, pend_updates=%v, dangling_closes%v", n,
lnutils.SpewLogClosure(l.openedCircuits),
lnutils.SpewLogClosure(l.closedCircuits))
return nil
} else if err != nil {
return err
}
if err := l.ackDownStreamPackets(); err != nil {
return err
}
l.cfg.PendingCommitTicker.Pause()
l.log.Trace("PendingCommitTicker paused after ackDownStreamPackets")
// The remote party now has a new pending commitment, so we'll update
// the contract court to be aware of this new set (the prior old remote
// pending).
newUpdate := &contractcourt.ContractUpdate{
HtlcKey: contractcourt.RemotePendingHtlcSet,
Htlcs: newCommit.PendingHTLCs,
}
err = l.cfg.NotifyContractUpdate(newUpdate)
if err != nil {
l.log.Errorf("unable to notify contract update: %v", err)
return err
}
select {
case <-l.cg.Done():
return ErrLinkShuttingDown
default:
}
auxBlobRecords, err := lnwire.ParseCustomRecords(newCommit.AuxSigBlob)
if err != nil {
return fmt.Errorf("error parsing aux sigs: %w", err)
}
commitSig := &lnwire.CommitSig{
ChanID: l.ChanID(),
CommitSig: newCommit.CommitSig,
HtlcSigs: newCommit.HtlcSigs,
PartialSig: newCommit.PartialSig,
CustomRecords: auxBlobRecords,
}
l.cfg.Peer.SendMessage(false, commitSig)
// Now that we have sent out a new CommitSig, we invoke the outgoing set
// of commit hooks.
l.RWMutex.Lock()
l.outgoingCommitHooks.invoke()
l.RWMutex.Unlock()
return nil
}
// Peer returns the representation of remote peer with which we have the
// channel link opened.
//
// NOTE: Part of the ChannelLink interface.
func (l *channelLink) PeerPubKey() [33]byte {
return l.cfg.Peer.PubKey()
}
// ChannelPoint returns the channel outpoint for the channel link.
// NOTE: Part of the ChannelLink interface.
func (l *channelLink) ChannelPoint() wire.OutPoint {
return l.channel.ChannelPoint()
}
// ShortChanID returns the short channel ID for the channel link. The short
// channel ID encodes the exact location in the main chain that the original
// funding output can be found.
//
// NOTE: Part of the ChannelLink interface.
func (l *channelLink) ShortChanID() lnwire.ShortChannelID {
l.RLock()
defer l.RUnlock()
return l.channel.ShortChanID()
}
// UpdateShortChanID updates the short channel ID for a link. This may be
// required in the event that a link is created before the short chan ID for it
// is known, or a re-org occurs, and the funding transaction changes location
// within the chain.
//
// NOTE: Part of the ChannelLink interface.
func (l *channelLink) UpdateShortChanID() (lnwire.ShortChannelID, error) {
chanID := l.ChanID()
// Refresh the channel state's short channel ID by loading it from disk.
// This ensures that the channel state accurately reflects the updated
// short channel ID.
err := l.channel.State().Refresh()
if err != nil {
l.log.Errorf("unable to refresh short_chan_id for chan_id=%v: "+
"%v", chanID, err)
return hop.Source, err
}
return hop.Source, nil
}
// ChanID returns the channel ID for the channel link. The channel ID is a more
// compact representation of a channel's full outpoint.
//
// NOTE: Part of the ChannelLink interface.
func (l *channelLink) ChanID() lnwire.ChannelID {
return lnwire.NewChanIDFromOutPoint(l.channel.ChannelPoint())
}
// Bandwidth returns the total amount that can flow through the channel link at
// this given instance. The value returned is expressed in millisatoshi and can
// be used by callers when making forwarding decisions to determine if a link
// can accept an HTLC.
//
// NOTE: Part of the ChannelLink interface.
func (l *channelLink) Bandwidth() lnwire.MilliSatoshi {
// Get the balance available on the channel for new HTLCs. This takes
// the channel reserve into account so HTLCs up to this value won't
// violate it.
return l.channel.AvailableBalance()
}
// MayAddOutgoingHtlc indicates whether we can add an outgoing htlc with the
// amount provided to the link. This check does not reserve a space, since
// forwards or other payments may use the available slot, so it should be
// considered best-effort.
func (l *channelLink) MayAddOutgoingHtlc(amt lnwire.MilliSatoshi) error {
return l.channel.MayAddOutgoingHtlc(amt)
}
// getDustSum is a wrapper method that calls the underlying channel's dust sum
// method.
//
// NOTE: Part of the dustHandler interface.
func (l *channelLink) getDustSum(whoseCommit lntypes.ChannelParty,
dryRunFee fn.Option[chainfee.SatPerKWeight]) lnwire.MilliSatoshi {
return l.channel.GetDustSum(whoseCommit, dryRunFee)
}
// getFeeRate is a wrapper method that retrieves the underlying channel's
// feerate.
//
// NOTE: Part of the dustHandler interface.
func (l *channelLink) getFeeRate() chainfee.SatPerKWeight {
return l.channel.CommitFeeRate()
}
// getDustClosure returns a closure that can be used by the switch or mailbox
// to evaluate whether a given HTLC is dust.
//
// NOTE: Part of the dustHandler interface.
func (l *channelLink) getDustClosure() dustClosure {
localDustLimit := l.channel.State().LocalChanCfg.DustLimit
remoteDustLimit := l.channel.State().RemoteChanCfg.DustLimit
chanType := l.channel.State().ChanType
return dustHelper(chanType, localDustLimit, remoteDustLimit)
}
// getCommitFee returns either the local or remote CommitFee in satoshis. This
// is used so that the Switch can have access to the commitment fee without
// needing to have a *LightningChannel. This doesn't include dust.
//
// NOTE: Part of the dustHandler interface.
func (l *channelLink) getCommitFee(remote bool) btcutil.Amount {
if remote {
return l.channel.State().RemoteCommitment.CommitFee
}
return l.channel.State().LocalCommitment.CommitFee
}
// exceedsFeeExposureLimit returns whether or not the new proposed fee-rate
// increases the total dust and fees within the channel past the configured
// fee threshold. It first calculates the dust sum over every update in the
// update log with the proposed fee-rate and taking into account both the local
// and remote dust limits. It uses every update in the update log instead of
// what is actually on the local and remote commitments because it is assumed
// that in a worst-case scenario, every update in the update log could
// theoretically be on either commitment transaction and this needs to be
// accounted for with this fee-rate. It then calculates the local and remote
// commitment fees given the proposed fee-rate. Finally, it tallies the results
// and determines if the fee threshold has been exceeded.
func (l *channelLink) exceedsFeeExposureLimit(
feePerKw chainfee.SatPerKWeight) (bool, error) {
dryRunFee := fn.Some[chainfee.SatPerKWeight](feePerKw)
// Get the sum of dust for both the local and remote commitments using
// this "dry-run" fee.
localDustSum := l.getDustSum(lntypes.Local, dryRunFee)
remoteDustSum := l.getDustSum(lntypes.Remote, dryRunFee)
// Calculate the local and remote commitment fees using this dry-run
// fee.
localFee, remoteFee, err := l.channel.CommitFeeTotalAt(feePerKw)
if err != nil {
return false, err
}
// Finally, check whether the max fee exposure was exceeded on either
// future commitment transaction with the fee-rate.
totalLocalDust := localDustSum + lnwire.NewMSatFromSatoshis(localFee)
if totalLocalDust > l.cfg.MaxFeeExposure {
l.log.Debugf("ChannelLink(%v): exceeds fee exposure limit: "+
"local dust: %v, local fee: %v", l.ShortChanID(),
totalLocalDust, localFee)
return true, nil
}
totalRemoteDust := remoteDustSum + lnwire.NewMSatFromSatoshis(
remoteFee,
)
if totalRemoteDust > l.cfg.MaxFeeExposure {
l.log.Debugf("ChannelLink(%v): exceeds fee exposure limit: "+
"remote dust: %v, remote fee: %v", l.ShortChanID(),
totalRemoteDust, remoteFee)
return true, nil
}
return false, nil
}
// isOverexposedWithHtlc calculates whether the proposed HTLC will make the
// channel exceed the fee threshold. It first fetches the largest fee-rate that
// may be on any unrevoked commitment transaction. Then, using this fee-rate,
// determines if the to-be-added HTLC is dust. If the HTLC is dust, it adds to
// the overall dust sum. If it is not dust, it contributes to weight, which
// also adds to the overall dust sum by an increase in fees. If the dust sum on
// either commitment exceeds the configured fee threshold, this function
// returns true.
func (l *channelLink) isOverexposedWithHtlc(htlc *lnwire.UpdateAddHTLC,
incoming bool) bool {
dustClosure := l.getDustClosure()
feeRate := l.channel.WorstCaseFeeRate()
amount := htlc.Amount.ToSatoshis()
// See if this HTLC is dust on both the local and remote commitments.
isLocalDust := dustClosure(feeRate, incoming, lntypes.Local, amount)
isRemoteDust := dustClosure(feeRate, incoming, lntypes.Remote, amount)
// Calculate the dust sum for the local and remote commitments.
localDustSum := l.getDustSum(
lntypes.Local, fn.None[chainfee.SatPerKWeight](),
)
remoteDustSum := l.getDustSum(
lntypes.Remote, fn.None[chainfee.SatPerKWeight](),
)
// Grab the larger of the local and remote commitment fees w/o dust.
commitFee := l.getCommitFee(false)
if l.getCommitFee(true) > commitFee {
commitFee = l.getCommitFee(true)
}
commitFeeMSat := lnwire.NewMSatFromSatoshis(commitFee)
localDustSum += commitFeeMSat
remoteDustSum += commitFeeMSat
// Calculate the additional fee increase if this is a non-dust HTLC.
weight := lntypes.WeightUnit(input.HTLCWeight)
additional := lnwire.NewMSatFromSatoshis(
feeRate.FeeForWeight(weight),
)
if isLocalDust {
// If this is dust, it doesn't contribute to weight but does
// contribute to the overall dust sum.
localDustSum += lnwire.NewMSatFromSatoshis(amount)
} else {
// Account for the fee increase that comes with an increase in
// weight.
localDustSum += additional
}
if localDustSum > l.cfg.MaxFeeExposure {
// The max fee exposure was exceeded.
l.log.Debugf("ChannelLink(%v): HTLC %v makes the channel "+
"overexposed, total local dust: %v (current commit "+
"fee: %v)", l.ShortChanID(), htlc, localDustSum)
return true
}
if isRemoteDust {
// If this is dust, it doesn't contribute to weight but does
// contribute to the overall dust sum.
remoteDustSum += lnwire.NewMSatFromSatoshis(amount)
} else {
// Account for the fee increase that comes with an increase in
// weight.
remoteDustSum += additional
}
if remoteDustSum > l.cfg.MaxFeeExposure {
// The max fee exposure was exceeded.
l.log.Debugf("ChannelLink(%v): HTLC %v makes the channel "+
"overexposed, total remote dust: %v (current commit "+
"fee: %v)", l.ShortChanID(), htlc, remoteDustSum)
return true
}
return false
}
// dustClosure is a function that evaluates whether an HTLC is dust. It returns
// true if the HTLC is dust. It takes in a feerate, a boolean denoting whether
// the HTLC is incoming (i.e. one that the remote sent), a boolean denoting
// whether to evaluate on the local or remote commit, and finally an HTLC
// amount to test.
type dustClosure func(feerate chainfee.SatPerKWeight, incoming bool,
whoseCommit lntypes.ChannelParty, amt btcutil.Amount) bool
// dustHelper is used to construct the dustClosure.
func dustHelper(chantype channeldb.ChannelType, localDustLimit,
remoteDustLimit btcutil.Amount) dustClosure {
isDust := func(feerate chainfee.SatPerKWeight, incoming bool,
whoseCommit lntypes.ChannelParty, amt btcutil.Amount) bool {
var dustLimit btcutil.Amount
if whoseCommit.IsLocal() {
dustLimit = localDustLimit
} else {
dustLimit = remoteDustLimit
}
return lnwallet.HtlcIsDust(
chantype, incoming, whoseCommit, feerate, amt,
dustLimit,
)
}
return isDust
}
// zeroConfConfirmed returns whether or not the zero-conf channel has
// confirmed on-chain.
//
// Part of the scidAliasHandler interface.
func (l *channelLink) zeroConfConfirmed() bool {
return l.channel.State().ZeroConfConfirmed()
}
// confirmedScid returns the confirmed SCID for a zero-conf channel. This
// should not be called for non-zero-conf channels.
//
// Part of the scidAliasHandler interface.
func (l *channelLink) confirmedScid() lnwire.ShortChannelID {
return l.channel.State().ZeroConfRealScid()
}
// isZeroConf returns whether or not the underlying channel is a zero-conf
// channel.
//
// Part of the scidAliasHandler interface.
func (l *channelLink) isZeroConf() bool {
return l.channel.State().IsZeroConf()
}
// negotiatedAliasFeature returns whether or not the underlying channel has
// negotiated the option-scid-alias feature bit. This will be true for both
// option-scid-alias and zero-conf channel-types. It will also be true for
// channels with the feature bit but without the above channel-types.
//
// Part of the scidAliasFeature interface.
func (l *channelLink) negotiatedAliasFeature() bool {
return l.channel.State().NegotiatedAliasFeature()
}
// getAliases returns the set of aliases for the underlying channel.
//
// Part of the scidAliasHandler interface.
func (l *channelLink) getAliases() []lnwire.ShortChannelID {
return l.cfg.GetAliases(l.ShortChanID())
}
// attachFailAliasUpdate sets the link's FailAliasUpdate function.
//
// Part of the scidAliasHandler interface.
func (l *channelLink) attachFailAliasUpdate(closure func(
sid lnwire.ShortChannelID, incoming bool) *lnwire.ChannelUpdate1) {
l.Lock()
l.cfg.FailAliasUpdate = closure
l.Unlock()
}
// AttachMailBox updates the current mailbox used by this link, and hooks up
// the mailbox's message and packet outboxes to the link's upstream and
// downstream chans, respectively.
func (l *channelLink) AttachMailBox(mailbox MailBox) {
l.Lock()
l.mailBox = mailbox
l.upstream = mailbox.MessageOutBox()
l.downstream = mailbox.PacketOutBox()
l.Unlock()
// Set the mailbox's fee rate. This may be refreshing a feerate that was
// never committed.
l.mailBox.SetFeeRate(l.getFeeRate())
// Also set the mailbox's dust closure so that it can query whether HTLC's
// are dust given the current feerate.
l.mailBox.SetDustClosure(l.getDustClosure())
}
// UpdateForwardingPolicy updates the forwarding policy for the target
// ChannelLink. Once updated, the link will use the new forwarding policy to
// govern if it an incoming HTLC should be forwarded or not. We assume that
// fields that are zero are intentionally set to zero, so we'll use newPolicy to
// update all of the link's FwrdingPolicy's values.
//
// NOTE: Part of the ChannelLink interface.
func (l *channelLink) UpdateForwardingPolicy(
newPolicy models.ForwardingPolicy) {
l.Lock()
defer l.Unlock()
l.cfg.FwrdingPolicy = newPolicy
}
// CheckHtlcForward should return a nil error if the passed HTLC details
// satisfy the current forwarding policy fo the target link. Otherwise,
// a LinkError with a valid protocol failure message should be returned
// in order to signal to the source of the HTLC, the policy consistency
// issue.
//
// NOTE: Part of the ChannelLink interface.
func (l *channelLink) CheckHtlcForward(payHash [32]byte, incomingHtlcAmt,
amtToForward lnwire.MilliSatoshi, incomingTimeout,
outgoingTimeout uint32, inboundFee models.InboundFee,
heightNow uint32, originalScid lnwire.ShortChannelID,
customRecords lnwire.CustomRecords) *LinkError {
l.RLock()
policy := l.cfg.FwrdingPolicy
l.RUnlock()
// Using the outgoing HTLC amount, we'll calculate the outgoing
// fee this incoming HTLC must carry in order to satisfy the constraints
// of the outgoing link.
outFee := ExpectedFee(policy, amtToForward)
// Then calculate the inbound fee that we charge based on the sum of
// outgoing HTLC amount and outgoing fee.
inFee := inboundFee.CalcFee(amtToForward + outFee)
// Add up both fee components. It is important to calculate both fees
// separately. An alternative way of calculating is to first determine
// an aggregate fee and apply that to the outgoing HTLC amount. However,
// rounding may cause the result to be slightly higher than in the case
// of separately rounded fee components. This potentially causes failed
// forwards for senders and is something to be avoided.
expectedFee := inFee + int64(outFee)
// If the actual fee is less than our expected fee, then we'll reject
// this HTLC as it didn't provide a sufficient amount of fees, or the
// values have been tampered with, or the send used incorrect/dated
// information to construct the forwarding information for this hop. In
// any case, we'll cancel this HTLC.
actualFee := int64(incomingHtlcAmt) - int64(amtToForward)
if incomingHtlcAmt < amtToForward || actualFee < expectedFee {
l.log.Warnf("outgoing htlc(%x) has insufficient fee: "+
"expected %v, got %v: incoming=%v, outgoing=%v, "+
"inboundFee=%v",
payHash[:], expectedFee, actualFee,
incomingHtlcAmt, amtToForward, inboundFee,
)
// As part of the returned error, we'll send our latest routing
// policy so the sending node obtains the most up to date data.
cb := func(upd *lnwire.ChannelUpdate1) lnwire.FailureMessage {
return lnwire.NewFeeInsufficient(amtToForward, *upd)
}
failure := l.createFailureWithUpdate(false, originalScid, cb)
return NewLinkError(failure)
}
// Check whether the outgoing htlc satisfies the channel policy.
err := l.canSendHtlc(
policy, payHash, amtToForward, outgoingTimeout, heightNow,
originalScid, customRecords,
)
if err != nil {
return err
}
// Finally, we'll ensure that the time-lock on the outgoing HTLC meets
// the following constraint: the incoming time-lock minus our time-lock
// delta should equal the outgoing time lock. Otherwise, whether the
// sender messed up, or an intermediate node tampered with the HTLC.
timeDelta := policy.TimeLockDelta
if incomingTimeout < outgoingTimeout+timeDelta {
l.log.Warnf("incoming htlc(%x) has incorrect time-lock value: "+
"expected at least %v block delta, got %v block delta",
payHash[:], timeDelta, incomingTimeout-outgoingTimeout)
// Grab the latest routing policy so the sending node is up to
// date with our current policy.
cb := func(upd *lnwire.ChannelUpdate1) lnwire.FailureMessage {
return lnwire.NewIncorrectCltvExpiry(
incomingTimeout, *upd,
)
}
failure := l.createFailureWithUpdate(false, originalScid, cb)
return NewLinkError(failure)
}
return nil
}
// CheckHtlcTransit should return a nil error if the passed HTLC details
// satisfy the current channel policy. Otherwise, a LinkError with a
// valid protocol failure message should be returned in order to signal
// the violation. This call is intended to be used for locally initiated
// payments for which there is no corresponding incoming htlc.
func (l *channelLink) CheckHtlcTransit(payHash [32]byte,
amt lnwire.MilliSatoshi, timeout uint32, heightNow uint32,
customRecords lnwire.CustomRecords) *LinkError {
l.RLock()
policy := l.cfg.FwrdingPolicy
l.RUnlock()
// We pass in hop.Source here as this is only used in the Switch when
// trying to send over a local link. This causes the fallback mechanism
// to occur.
return l.canSendHtlc(
policy, payHash, amt, timeout, heightNow, hop.Source,
customRecords,
)
}
// canSendHtlc checks whether the given htlc parameters satisfy
// the channel's amount and time lock constraints.
func (l *channelLink) canSendHtlc(policy models.ForwardingPolicy,
payHash [32]byte, amt lnwire.MilliSatoshi, timeout uint32,
heightNow uint32, originalScid lnwire.ShortChannelID,
customRecords lnwire.CustomRecords) *LinkError {
// As our first sanity check, we'll ensure that the passed HTLC isn't
// too small for the next hop. If so, then we'll cancel the HTLC
// directly.
if amt < policy.MinHTLCOut {
l.log.Warnf("outgoing htlc(%x) is too small: min_htlc=%v, "+
"htlc_value=%v", payHash[:], policy.MinHTLCOut,
amt)
// As part of the returned error, we'll send our latest routing
// policy so the sending node obtains the most up to date data.
cb := func(upd *lnwire.ChannelUpdate1) lnwire.FailureMessage {
return lnwire.NewAmountBelowMinimum(amt, *upd)
}
failure := l.createFailureWithUpdate(false, originalScid, cb)
return NewLinkError(failure)
}
// Next, ensure that the passed HTLC isn't too large. If so, we'll
// cancel the HTLC directly.
if policy.MaxHTLC != 0 && amt > policy.MaxHTLC {
l.log.Warnf("outgoing htlc(%x) is too large: max_htlc=%v, "+
"htlc_value=%v", payHash[:], policy.MaxHTLC, amt)
// As part of the returned error, we'll send our latest routing
// policy so the sending node obtains the most up-to-date data.
cb := func(upd *lnwire.ChannelUpdate1) lnwire.FailureMessage {
return lnwire.NewTemporaryChannelFailure(upd)
}
failure := l.createFailureWithUpdate(false, originalScid, cb)
return NewDetailedLinkError(failure, OutgoingFailureHTLCExceedsMax)
}
// We want to avoid offering an HTLC which will expire in the near
// future, so we'll reject an HTLC if the outgoing expiration time is
// too close to the current height.
if timeout <= heightNow+l.cfg.OutgoingCltvRejectDelta {
l.log.Warnf("htlc(%x) has an expiry that's too soon: "+
"outgoing_expiry=%v, best_height=%v", payHash[:],
timeout, heightNow)
cb := func(upd *lnwire.ChannelUpdate1) lnwire.FailureMessage {
return lnwire.NewExpiryTooSoon(*upd)
}
failure := l.createFailureWithUpdate(false, originalScid, cb)
return NewLinkError(failure)
}
// Check absolute max delta.
if timeout > l.cfg.MaxOutgoingCltvExpiry+heightNow {
l.log.Warnf("outgoing htlc(%x) has a time lock too far in "+
"the future: got %v, but maximum is %v", payHash[:],
timeout-heightNow, l.cfg.MaxOutgoingCltvExpiry)
return NewLinkError(&lnwire.FailExpiryTooFar{})
}
// We now check the available bandwidth to see if this HTLC can be
// forwarded.
availableBandwidth := l.Bandwidth()
auxBandwidth, err := fn.MapOptionZ(
l.cfg.AuxTrafficShaper,
func(ts AuxTrafficShaper) fn.Result[OptionalBandwidth] {
var htlcBlob fn.Option[tlv.Blob]
blob, err := customRecords.Serialize()
if err != nil {
return fn.Err[OptionalBandwidth](
fmt.Errorf("unable to serialize "+
"custom records: %w", err))
}
if len(blob) > 0 {
htlcBlob = fn.Some(blob)
}
return l.AuxBandwidth(amt, originalScid, htlcBlob, ts)
},
).Unpack()
if err != nil {
l.log.Errorf("Unable to determine aux bandwidth: %v", err)
return NewLinkError(&lnwire.FailTemporaryNodeFailure{})
}
if auxBandwidth.IsHandled && auxBandwidth.Bandwidth.IsSome() {
auxBandwidth.Bandwidth.WhenSome(
func(bandwidth lnwire.MilliSatoshi) {
availableBandwidth = bandwidth
},
)
}
// Check to see if there is enough balance in this channel.
if amt > availableBandwidth {
l.log.Warnf("insufficient bandwidth to route htlc: %v is "+
"larger than %v", amt, availableBandwidth)
cb := func(upd *lnwire.ChannelUpdate1) lnwire.FailureMessage {
return lnwire.NewTemporaryChannelFailure(upd)
}
failure := l.createFailureWithUpdate(false, originalScid, cb)
return NewDetailedLinkError(
failure, OutgoingFailureInsufficientBalance,
)
}
return nil
}
// AuxBandwidth returns the bandwidth that can be used for a channel, expressed
// in milli-satoshi. This might be different from the regular BTC bandwidth for
// custom channels. This will always return fn.None() for a regular (non-custom)
// channel.
func (l *channelLink) AuxBandwidth(amount lnwire.MilliSatoshi,
cid lnwire.ShortChannelID, htlcBlob fn.Option[tlv.Blob],
ts AuxTrafficShaper) fn.Result[OptionalBandwidth] {
fundingBlob := l.FundingCustomBlob()
shouldHandle, err := ts.ShouldHandleTraffic(cid, fundingBlob, htlcBlob)
if err != nil {
return fn.Err[OptionalBandwidth](fmt.Errorf("traffic shaper "+
"failed to decide whether to handle traffic: %w", err))
}
log.Debugf("ShortChannelID=%v: aux traffic shaper is handling "+
"traffic: %v", cid, shouldHandle)
// If this channel isn't handled by the aux traffic shaper, we'll return
// early.
if !shouldHandle {
return fn.Ok(OptionalBandwidth{
IsHandled: false,
})
}
// Ask for a specific bandwidth to be used for the channel.
commitmentBlob := l.CommitmentCustomBlob()
auxBandwidth, err := ts.PaymentBandwidth(
htlcBlob, commitmentBlob, l.Bandwidth(), amount,
l.channel.FetchLatestAuxHTLCView(),
)
if err != nil {
return fn.Err[OptionalBandwidth](fmt.Errorf("failed to get "+
"bandwidth from external traffic shaper: %w", err))
}
log.Debugf("ShortChannelID=%v: aux traffic shaper reported available "+
"bandwidth: %v", cid, auxBandwidth)
return fn.Ok(OptionalBandwidth{
IsHandled: true,
Bandwidth: fn.Some(auxBandwidth),
})
}
// Stats returns the statistics of channel link.
//
// NOTE: Part of the ChannelLink interface.
func (l *channelLink) Stats() (uint64, lnwire.MilliSatoshi, lnwire.MilliSatoshi) {
snapshot := l.channel.StateSnapshot()
return snapshot.ChannelCommitment.CommitHeight,
snapshot.TotalMSatSent,
snapshot.TotalMSatReceived
}
// String returns the string representation of channel link.
//
// NOTE: Part of the ChannelLink interface.
func (l *channelLink) String() string {
return l.channel.ChannelPoint().String()
}
// handleSwitchPacket handles the switch packets. This packets which might be
// forwarded to us from another channel link in case the htlc update came from
// another peer or if the update was created by user
//
// NOTE: Part of the packetHandler interface.
func (l *channelLink) handleSwitchPacket(pkt *htlcPacket) error {
l.log.Tracef("received switch packet inkey=%v, outkey=%v",
pkt.inKey(), pkt.outKey())
return l.mailBox.AddPacket(pkt)
}
// HandleChannelUpdate handles the htlc requests as settle/add/fail which sent
// to us from remote peer we have a channel with.
//
// NOTE: Part of the ChannelLink interface.
func (l *channelLink) HandleChannelUpdate(message lnwire.Message) {
select {
case <-l.cg.Done():
// Return early if the link is already in the process of
// quitting. It doesn't make sense to hand the message to the
// mailbox here.
return
default:
}
err := l.mailBox.AddMessage(message)
if err != nil {
l.log.Errorf("failed to add Message to mailbox: %v", err)
}
}
// updateChannelFee updates the commitment fee-per-kw on this channel by
// committing to an update_fee message.
func (l *channelLink) updateChannelFee(ctx context.Context,
feePerKw chainfee.SatPerKWeight) error {
l.log.Infof("updating commit fee to %v", feePerKw)
// We skip sending the UpdateFee message if the channel is not
// currently eligible to forward messages.
if !l.eligibleToUpdate() {
l.log.Debugf("skipping fee update for inactive channel")
return nil
}
// Check and see if our proposed fee-rate would make us exceed the fee
// threshold.
thresholdExceeded, err := l.exceedsFeeExposureLimit(feePerKw)
if err != nil {
// This shouldn't typically happen. If it does, it indicates
// something is wrong with our channel state.
return err
}
if thresholdExceeded {
return fmt.Errorf("link fee threshold exceeded")
}
// First, we'll update the local fee on our commitment.
if err := l.channel.UpdateFee(feePerKw); err != nil {
return err
}
// The fee passed the channel's validation checks, so we update the
// mailbox feerate.
l.mailBox.SetFeeRate(feePerKw)
// We'll then attempt to send a new UpdateFee message, and also lock it
// in immediately by triggering a commitment update.
msg := lnwire.NewUpdateFee(l.ChanID(), uint32(feePerKw))
if err := l.cfg.Peer.SendMessage(false, msg); err != nil {
return err
}
return l.updateCommitTx(ctx)
}
// processRemoteSettleFails accepts a batch of settle/fail payment descriptors
// after receiving a revocation from the remote party, and reprocesses them in
// the context of the provided forwarding package. Any settles or fails that
// have already been acknowledged in the forwarding package will not be sent to
// the switch.
func (l *channelLink) processRemoteSettleFails(fwdPkg *channeldb.FwdPkg) {
if len(fwdPkg.SettleFails) == 0 {
return
}
l.log.Debugf("settle-fail-filter: %v", fwdPkg.SettleFailFilter)
var switchPackets []*htlcPacket
for i, update := range fwdPkg.SettleFails {
destRef := fwdPkg.DestRef(uint16(i))
// Skip any settles or fails that have already been
// acknowledged by the incoming link that originated the
// forwarded Add.
if fwdPkg.SettleFailFilter.Contains(uint16(i)) {
continue
}
// TODO(roasbeef): rework log entries to a shared
// interface.
switch msg := update.UpdateMsg.(type) {
// A settle for an HTLC we previously forwarded HTLC has been
// received. So we'll forward the HTLC to the switch which will
// handle propagating the settle to the prior hop.
case *lnwire.UpdateFulfillHTLC:
// If hodl.SettleIncoming is requested, we will not
// forward the SETTLE to the switch and will not signal
// a free slot on the commitment transaction.
if l.cfg.HodlMask.Active(hodl.SettleIncoming) {
l.log.Warnf(hodl.SettleIncoming.Warning())
continue
}
settlePacket := &htlcPacket{
outgoingChanID: l.ShortChanID(),
outgoingHTLCID: msg.ID,
destRef: &destRef,
htlc: msg,
}
// Add the packet to the batch to be forwarded, and
// notify the overflow queue that a spare spot has been
// freed up within the commitment state.
switchPackets = append(switchPackets, settlePacket)
// A failureCode message for a previously forwarded HTLC has
// been received. As a result a new slot will be freed up in
// our commitment state, so we'll forward this to the switch so
// the backwards undo can continue.
case *lnwire.UpdateFailHTLC:
// If hodl.SettleIncoming is requested, we will not
// forward the FAIL to the switch and will not signal a
// free slot on the commitment transaction.
if l.cfg.HodlMask.Active(hodl.FailIncoming) {
l.log.Warnf(hodl.FailIncoming.Warning())
continue
}
// Fetch the reason the HTLC was canceled so we can
// continue to propagate it. This failure originated
// from another node, so the linkFailure field is not
// set on the packet.
failPacket := &htlcPacket{
outgoingChanID: l.ShortChanID(),
outgoingHTLCID: msg.ID,
destRef: &destRef,
htlc: msg,
}
l.log.Debugf("Failed to send HTLC with ID=%d", msg.ID)
// If the failure message lacks an HMAC (but includes
// the 4 bytes for encoding the message and padding
// lengths, then this means that we received it as an
// UpdateFailMalformedHTLC. As a result, we'll signal
// that we need to convert this error within the switch
// to an actual error, by encrypting it as if we were
// the originating hop.
convertedErrorSize := lnwire.FailureMessageLength + 4
if len(msg.Reason) == convertedErrorSize {
failPacket.convertedError = true
}
// Add the packet to the batch to be forwarded, and
// notify the overflow queue that a spare spot has been
// freed up within the commitment state.
switchPackets = append(switchPackets, failPacket)
}
}
// Only spawn the task forward packets we have a non-zero number.
if len(switchPackets) > 0 {
go l.forwardBatch(false, switchPackets...)
}
}
// processRemoteAdds serially processes each of the Add payment descriptors
// which have been "locked-in" by receiving a revocation from the remote party.
// The forwarding package provided instructs how to process this batch,
// indicating whether this is the first time these Adds are being processed, or
// whether we are reprocessing as a result of a failure or restart. Adds that
// have already been acknowledged in the forwarding package will be ignored.
//
//nolint:funlen
func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) {
l.log.Tracef("processing %d remote adds for height %d",
len(fwdPkg.Adds), fwdPkg.Height)
decodeReqs := make(
[]hop.DecodeHopIteratorRequest, 0, len(fwdPkg.Adds),
)
for _, update := range fwdPkg.Adds {
if msg, ok := update.UpdateMsg.(*lnwire.UpdateAddHTLC); ok {
// Before adding the new htlc to the state machine,
// parse the onion object in order to obtain the
// routing information with DecodeHopIterator function
// which process the Sphinx packet.
onionReader := bytes.NewReader(msg.OnionBlob[:])
req := hop.DecodeHopIteratorRequest{
OnionReader: onionReader,
RHash: msg.PaymentHash[:],
IncomingCltv: msg.Expiry,
IncomingAmount: msg.Amount,
BlindingPoint: msg.BlindingPoint,
}
decodeReqs = append(decodeReqs, req)
}
}
// Atomically decode the incoming htlcs, simultaneously checking for
// replay attempts. A particular index in the returned, spare list of
// channel iterators should only be used if the failure code at the
// same index is lnwire.FailCodeNone.
decodeResps, sphinxErr := l.cfg.DecodeHopIterators(
fwdPkg.ID(), decodeReqs,
)
if sphinxErr != nil {
l.failf(LinkFailureError{code: ErrInternalError},
"unable to decode hop iterators: %v", sphinxErr)
return
}
var switchPackets []*htlcPacket
for i, update := range fwdPkg.Adds {
idx := uint16(i)
//nolint:forcetypeassert
add := *update.UpdateMsg.(*lnwire.UpdateAddHTLC)
sourceRef := fwdPkg.SourceRef(idx)
if fwdPkg.State == channeldb.FwdStateProcessed &&
fwdPkg.AckFilter.Contains(idx) {
// If this index is already found in the ack filter,
// the response to this forwarding decision has already
// been committed by one of our commitment txns. ADDs
// in this state are waiting for the rest of the fwding
// package to get acked before being garbage collected.
continue
}
// An incoming HTLC add has been full-locked in. As a result we
// can now examine the forwarding details of the HTLC, and the
// HTLC itself to decide if: we should forward it, cancel it,
// or are able to settle it (and it adheres to our fee related
// constraints).
// Before adding the new htlc to the state machine, parse the
// onion object in order to obtain the routing information with
// DecodeHopIterator function which process the Sphinx packet.
chanIterator, failureCode := decodeResps[i].Result()
if failureCode != lnwire.CodeNone {
// If we're unable to process the onion blob then we
// should send the malformed htlc error to payment
// sender.
l.sendMalformedHTLCError(
add.ID, failureCode, add.OnionBlob, &sourceRef,
)
l.log.Errorf("unable to decode onion hop "+
"iterator: %v", failureCode)
continue
}
heightNow := l.cfg.BestHeight()
pld, routeRole, pldErr := chanIterator.HopPayload()
if pldErr != nil {
// If we're unable to process the onion payload, or we
// received invalid onion payload failure, then we
// should send an error back to the caller so the HTLC
// can be canceled.
var failedType uint64
// We need to get the underlying error value, so we
// can't use errors.As as suggested by the linter.
//nolint:errorlint
if e, ok := pldErr.(hop.ErrInvalidPayload); ok {
failedType = uint64(e.Type)
}
// If we couldn't parse the payload, make our best
// effort at creating an error encrypter that knows
// what blinding type we were, but if we couldn't
// parse the payload we have no way of knowing whether
// we were the introduction node or not.
//
//nolint:ll
obfuscator, failCode := chanIterator.ExtractErrorEncrypter(
l.cfg.ExtractErrorEncrypter,
// We need our route role here because we
// couldn't parse or validate the payload.
routeRole == hop.RouteRoleIntroduction,
)
if failCode != lnwire.CodeNone {
l.log.Errorf("could not extract error "+
"encrypter: %v", pldErr)
// We can't process this htlc, send back
// malformed.
l.sendMalformedHTLCError(
add.ID, failureCode, add.OnionBlob,
&sourceRef,
)
continue
}
// TODO: currently none of the test unit infrastructure
// is setup to handle TLV payloads, so testing this
// would require implementing a separate mock iterator
// for TLV payloads that also supports injecting invalid
// payloads. Deferring this non-trival effort till a
// later date
failure := lnwire.NewInvalidOnionPayload(failedType, 0)
l.sendHTLCError(
add, sourceRef, NewLinkError(failure),
obfuscator, false,
)
l.log.Errorf("unable to decode forwarding "+
"instructions: %v", pldErr)
continue
}
// Retrieve onion obfuscator from onion blob in order to
// produce initial obfuscation of the onion failureCode.
obfuscator, failureCode := chanIterator.ExtractErrorEncrypter(
l.cfg.ExtractErrorEncrypter,
routeRole == hop.RouteRoleIntroduction,
)
if failureCode != lnwire.CodeNone {
// If we're unable to process the onion blob than we
// should send the malformed htlc error to payment
// sender.
l.sendMalformedHTLCError(
add.ID, failureCode, add.OnionBlob,
&sourceRef,
)
l.log.Errorf("unable to decode onion "+
"obfuscator: %v", failureCode)
continue
}
fwdInfo := pld.ForwardingInfo()
// Check whether the payload we've just processed uses our
// node as the introduction point (gave us a blinding key in
// the payload itself) and fail it back if we don't support
// route blinding.
if fwdInfo.NextBlinding.IsSome() &&
l.cfg.DisallowRouteBlinding {
failure := lnwire.NewInvalidBlinding(
fn.Some(add.OnionBlob),
)
l.sendHTLCError(
add, sourceRef, NewLinkError(failure),
obfuscator, false,
)
l.log.Error("rejected htlc that uses use as an " +
"introduction point when we do not support " +
"route blinding")
continue
}
switch fwdInfo.NextHop {
case hop.Exit:
err := l.processExitHop(
add, sourceRef, obfuscator, fwdInfo,
heightNow, pld,
)
if err != nil {
l.failf(LinkFailureError{
code: ErrInternalError,
}, err.Error()) //nolint
return
}
// There are additional channels left within this route. So
// we'll simply do some forwarding package book-keeping.
default:
// If hodl.AddIncoming is requested, we will not
// validate the forwarded ADD, nor will we send the
// packet to the htlc switch.
if l.cfg.HodlMask.Active(hodl.AddIncoming) {
l.log.Warnf(hodl.AddIncoming.Warning())
continue
}
endorseValue := l.experimentalEndorsement(
record.CustomSet(add.CustomRecords),
)
endorseType := uint64(
lnwire.ExperimentalEndorsementType,
)
switch fwdPkg.State {
case channeldb.FwdStateProcessed:
// This add was not forwarded on the previous
// processing phase, run it through our
// validation pipeline to reproduce an error.
// This may trigger a different error due to
// expiring timelocks, but we expect that an
// error will be reproduced.
if !fwdPkg.FwdFilter.Contains(idx) {
break
}
// Otherwise, it was already processed, we can
// can collect it and continue.
outgoingAdd := &lnwire.UpdateAddHTLC{
Expiry: fwdInfo.OutgoingCTLV,
Amount: fwdInfo.AmountToForward,
PaymentHash: add.PaymentHash,
BlindingPoint: fwdInfo.NextBlinding,
}
endorseValue.WhenSome(func(e byte) {
custRecords := map[uint64][]byte{
endorseType: {e},
}
outgoingAdd.CustomRecords = custRecords
})
// Finally, we'll encode the onion packet for
// the _next_ hop using the hop iterator
// decoded for the current hop.
buf := bytes.NewBuffer(
outgoingAdd.OnionBlob[0:0],
)
// We know this cannot fail, as this ADD
// was marked forwarded in a previous
// round of processing.
chanIterator.EncodeNextHop(buf)
inboundFee := l.cfg.FwrdingPolicy.InboundFee
//nolint:ll
updatePacket := &htlcPacket{
incomingChanID: l.ShortChanID(),
incomingHTLCID: add.ID,
outgoingChanID: fwdInfo.NextHop,
sourceRef: &sourceRef,
incomingAmount: add.Amount,
amount: outgoingAdd.Amount,
htlc: outgoingAdd,
obfuscator: obfuscator,
incomingTimeout: add.Expiry,
outgoingTimeout: fwdInfo.OutgoingCTLV,
inOnionCustomRecords: pld.CustomRecords(),
inboundFee: inboundFee,
inWireCustomRecords: add.CustomRecords.Copy(),
}
switchPackets = append(
switchPackets, updatePacket,
)
continue
}
// TODO(roasbeef): ensure don't accept outrageous
// timeout for htlc
// With all our forwarding constraints met, we'll
// create the outgoing HTLC using the parameters as
// specified in the forwarding info.
addMsg := &lnwire.UpdateAddHTLC{
Expiry: fwdInfo.OutgoingCTLV,
Amount: fwdInfo.AmountToForward,
PaymentHash: add.PaymentHash,
BlindingPoint: fwdInfo.NextBlinding,
}
endorseValue.WhenSome(func(e byte) {
addMsg.CustomRecords = map[uint64][]byte{
endorseType: {e},
}
})
// Finally, we'll encode the onion packet for the
// _next_ hop using the hop iterator decoded for the
// current hop.
buf := bytes.NewBuffer(addMsg.OnionBlob[0:0])
err := chanIterator.EncodeNextHop(buf)
if err != nil {
l.log.Errorf("unable to encode the "+
"remaining route %v", err)
cb := func(upd *lnwire.ChannelUpdate1) lnwire.FailureMessage { //nolint:ll
return lnwire.NewTemporaryChannelFailure(upd)
}
failure := l.createFailureWithUpdate(
true, hop.Source, cb,
)
l.sendHTLCError(
add, sourceRef, NewLinkError(failure),
obfuscator, false,
)
continue
}
// Now that this add has been reprocessed, only append
// it to our list of packets to forward to the switch
// this is the first time processing the add. If the
// fwd pkg has already been processed, then we entered
// the above section to recreate a previous error. If
// the packet had previously been forwarded, it would
// have been added to switchPackets at the top of this
// section.
if fwdPkg.State == channeldb.FwdStateLockedIn {
inboundFee := l.cfg.FwrdingPolicy.InboundFee
//nolint:ll
updatePacket := &htlcPacket{
incomingChanID: l.ShortChanID(),
incomingHTLCID: add.ID,
outgoingChanID: fwdInfo.NextHop,
sourceRef: &sourceRef,
incomingAmount: add.Amount,
amount: addMsg.Amount,
htlc: addMsg,
obfuscator: obfuscator,
incomingTimeout: add.Expiry,
outgoingTimeout: fwdInfo.OutgoingCTLV,
inOnionCustomRecords: pld.CustomRecords(),
inboundFee: inboundFee,
inWireCustomRecords: add.CustomRecords.Copy(),
}
fwdPkg.FwdFilter.Set(idx)
switchPackets = append(switchPackets,
updatePacket)
}
}
}
// Commit the htlcs we are intending to forward if this package has not
// been fully processed.
if fwdPkg.State == channeldb.FwdStateLockedIn {
err := l.channel.SetFwdFilter(fwdPkg.Height, fwdPkg.FwdFilter)
if err != nil {
l.failf(LinkFailureError{code: ErrInternalError},
"unable to set fwd filter: %v", err)
return
}
}
if len(switchPackets) == 0 {
return
}
replay := fwdPkg.State != channeldb.FwdStateLockedIn
l.log.Debugf("forwarding %d packets to switch: replay=%v",
len(switchPackets), replay)
// NOTE: This call is made synchronous so that we ensure all circuits
// are committed in the exact order that they are processed in the link.
// Failing to do this could cause reorderings/gaps in the range of
// opened circuits, which violates assumptions made by the circuit
// trimming.
l.forwardBatch(replay, switchPackets...)
}
// experimentalEndorsement returns the value to set for our outgoing
// experimental endorsement field, and a boolean indicating whether it should
// be populated on the outgoing htlc.
func (l *channelLink) experimentalEndorsement(
customUpdateAdd record.CustomSet) fn.Option[byte] {
// Only relay experimental signal if we are within the experiment
// period.
if !l.cfg.ShouldFwdExpEndorsement() {
return fn.None[byte]()
}
// If we don't have any custom records or the experimental field is
// not set, just forward a zero value.
if len(customUpdateAdd) == 0 {
return fn.Some[byte](lnwire.ExperimentalUnendorsed)
}
t := uint64(lnwire.ExperimentalEndorsementType)
value, set := customUpdateAdd[t]
if !set {
return fn.Some[byte](lnwire.ExperimentalUnendorsed)
}
// We expect at least one byte for this field, consider it invalid if
// it has no data and just forward a zero value.
if len(value) == 0 {
return fn.Some[byte](lnwire.ExperimentalUnendorsed)
}
// Only forward endorsed if the incoming link is endorsed.
if value[0] == lnwire.ExperimentalEndorsed {
return fn.Some[byte](lnwire.ExperimentalEndorsed)
}
// Forward as unendorsed otherwise, including cases where we've
// received an invalid value that uses more than 3 bits of information.
return fn.Some[byte](lnwire.ExperimentalUnendorsed)
}
// processExitHop handles an htlc for which this link is the exit hop. It
// returns a boolean indicating whether the commitment tx needs an update.
func (l *channelLink) processExitHop(add lnwire.UpdateAddHTLC,
sourceRef channeldb.AddRef, obfuscator hop.ErrorEncrypter,
fwdInfo hop.ForwardingInfo, heightNow uint32,
payload invoices.Payload) error {
// If hodl.ExitSettle is requested, we will not validate the final hop's
// ADD, nor will we settle the corresponding invoice or respond with the
// preimage.
if l.cfg.HodlMask.Active(hodl.ExitSettle) {
l.log.Warnf("%s for htlc(rhash=%x,htlcIndex=%v)",
hodl.ExitSettle.Warning(), add.PaymentHash, add.ID)
return nil
}
// In case the traffic shaper is active, we'll check if the HTLC has
// custom records and skip the amount check in the onion payload below.
isCustomHTLC := fn.MapOptionZ(
l.cfg.AuxTrafficShaper,
func(ts AuxTrafficShaper) bool {
return ts.IsCustomHTLC(add.CustomRecords)
},
)
// As we're the exit hop, we'll double check the hop-payload included in
// the HTLC to ensure that it was crafted correctly by the sender and
// is compatible with the HTLC we were extended. If an external
// validator is active we might bypass the amount check.
if !isCustomHTLC && add.Amount < fwdInfo.AmountToForward {
l.log.Errorf("onion payload of incoming htlc(%x) has "+
"incompatible value: expected <=%v, got %v",
add.PaymentHash, add.Amount, fwdInfo.AmountToForward)
failure := NewLinkError(
lnwire.NewFinalIncorrectHtlcAmount(add.Amount),
)
l.sendHTLCError(add, sourceRef, failure, obfuscator, true)
return nil
}
// We'll also ensure that our time-lock value has been computed
// correctly.
if add.Expiry < fwdInfo.OutgoingCTLV {
l.log.Errorf("onion payload of incoming htlc(%x) has "+
"incompatible time-lock: expected <=%v, got %v",
add.PaymentHash, add.Expiry, fwdInfo.OutgoingCTLV)
failure := NewLinkError(
lnwire.NewFinalIncorrectCltvExpiry(add.Expiry),
)
l.sendHTLCError(add, sourceRef, failure, obfuscator, true)
return nil
}
// Notify the invoiceRegistry of the exit hop htlc. If we crash right
// after this, this code will be re-executed after restart. We will
// receive back a resolution event.
invoiceHash := lntypes.Hash(add.PaymentHash)
circuitKey := models.CircuitKey{
ChanID: l.ShortChanID(),
HtlcID: add.ID,
}
event, err := l.cfg.Registry.NotifyExitHopHtlc(
invoiceHash, add.Amount, add.Expiry, int32(heightNow),
circuitKey, l.hodlQueue.ChanIn(), add.CustomRecords, payload,
)
if err != nil {
return err
}
// Create a hodlHtlc struct and decide either resolved now or later.
htlc := hodlHtlc{
add: add,
sourceRef: sourceRef,
obfuscator: obfuscator,
}
// If the event is nil, the invoice is being held, so we save payment
// descriptor for future reference.
if event == nil {
l.hodlMap[circuitKey] = htlc
return nil
}
// Process the received resolution.
return l.processHtlcResolution(event, htlc)
}
// settleHTLC settles the HTLC on the channel.
func (l *channelLink) settleHTLC(preimage lntypes.Preimage,
htlcIndex uint64, sourceRef channeldb.AddRef) error {
hash := preimage.Hash()
l.log.Infof("settling htlc %v as exit hop", hash)
err := l.channel.SettleHTLC(
preimage, htlcIndex, &sourceRef, nil, nil,
)
if err != nil {
return fmt.Errorf("unable to settle htlc: %w", err)
}
// If the link is in hodl.BogusSettle mode, replace the preimage with a
// fake one before sending it to the peer.
if l.cfg.HodlMask.Active(hodl.BogusSettle) {
l.log.Warnf(hodl.BogusSettle.Warning())
preimage = [32]byte{}
copy(preimage[:], bytes.Repeat([]byte{2}, 32))
}
// HTLC was successfully settled locally send notification about it
// remote peer.
l.cfg.Peer.SendMessage(false, &lnwire.UpdateFulfillHTLC{
ChanID: l.ChanID(),
ID: htlcIndex,
PaymentPreimage: preimage,
})
// Once we have successfully settled the htlc, notify a settle event.
l.cfg.HtlcNotifier.NotifySettleEvent(
HtlcKey{
IncomingCircuit: models.CircuitKey{
ChanID: l.ShortChanID(),
HtlcID: htlcIndex,
},
},
preimage,
HtlcEventTypeReceive,
)
return nil
}
// forwardBatch forwards the given htlcPackets to the switch, and waits on the
// err chan for the individual responses. This method is intended to be spawned
// as a goroutine so the responses can be handled in the background.
func (l *channelLink) forwardBatch(replay bool, packets ...*htlcPacket) {
// Don't forward packets for which we already have a response in our
// mailbox. This could happen if a packet fails and is buffered in the
// mailbox, and the incoming link flaps.
var filteredPkts = make([]*htlcPacket, 0, len(packets))
for _, pkt := range packets {
if l.mailBox.HasPacket(pkt.inKey()) {
continue
}
filteredPkts = append(filteredPkts, pkt)
}
err := l.cfg.ForwardPackets(l.cg.Done(), replay, filteredPkts...)
if err != nil {
log.Errorf("Unhandled error while reforwarding htlc "+
"settle/fail over htlcswitch: %v", err)
}
}
// sendHTLCError functions cancels HTLC and send cancel message back to the
// peer from which HTLC was received.
func (l *channelLink) sendHTLCError(add lnwire.UpdateAddHTLC,
sourceRef channeldb.AddRef, failure *LinkError,
e hop.ErrorEncrypter, isReceive bool) {
reason, err := e.EncryptFirstHop(failure.WireMessage())
if err != nil {
l.log.Errorf("unable to obfuscate error: %v", err)
return
}
err = l.channel.FailHTLC(add.ID, reason, &sourceRef, nil, nil)
if err != nil {
l.log.Errorf("unable cancel htlc: %v", err)
return
}
// Send the appropriate failure message depending on whether we're
// in a blinded route or not.
if err := l.sendIncomingHTLCFailureMsg(
add.ID, e, reason,
); err != nil {
l.log.Errorf("unable to send HTLC failure: %v", err)
return
}
// Notify a link failure on our incoming link. Outgoing htlc information
// is not available at this point, because we have not decrypted the
// onion, so it is excluded.
var eventType HtlcEventType
if isReceive {
eventType = HtlcEventTypeReceive
} else {
eventType = HtlcEventTypeForward
}
l.cfg.HtlcNotifier.NotifyLinkFailEvent(
HtlcKey{
IncomingCircuit: models.CircuitKey{
ChanID: l.ShortChanID(),
HtlcID: add.ID,
},
},
HtlcInfo{
IncomingTimeLock: add.Expiry,
IncomingAmt: add.Amount,
},
eventType,
failure,
true,
)
}
// sendPeerHTLCFailure handles sending a HTLC failure message back to the
// peer from which the HTLC was received. This function is primarily used to
// handle the special requirements of route blinding, specifically:
// - Forwarding nodes must switch out any errors with MalformedFailHTLC
// - Introduction nodes should return regular HTLC failure messages.
//
// It accepts the original opaque failure, which will be used in the case
// that we're not part of a blinded route and an error encrypter that'll be
// used if we are the introduction node and need to present an error as if
// we're the failing party.
func (l *channelLink) sendIncomingHTLCFailureMsg(htlcIndex uint64,
e hop.ErrorEncrypter,
originalFailure lnwire.OpaqueReason) error {
var msg lnwire.Message
switch {
// Our circuit's error encrypter will be nil if this was a locally
// initiated payment. We can only hit a blinded error for a locally
// initiated payment if we allow ourselves to be picked as the
// introduction node for our own payments and in that case we
// shouldn't reach this code. To prevent the HTLC getting stuck,
// we fail it back and log an error.
// code.
case e == nil:
msg = &lnwire.UpdateFailHTLC{
ChanID: l.ChanID(),
ID: htlcIndex,
Reason: originalFailure,
}
l.log.Errorf("Unexpected blinded failure when "+
"we are the sending node, incoming htlc: %v(%v)",
l.ShortChanID(), htlcIndex)
// For cleartext hops (ie, non-blinded/normal) we don't need any
// transformation on the error message and can just send the original.
case !e.Type().IsBlinded():
msg = &lnwire.UpdateFailHTLC{
ChanID: l.ChanID(),
ID: htlcIndex,
Reason: originalFailure,
}
// When we're the introduction node, we need to convert the error to
// a UpdateFailHTLC.
case e.Type() == hop.EncrypterTypeIntroduction:
l.log.Debugf("Introduction blinded node switching out failure "+
"error: %v", htlcIndex)
// The specification does not require that we set the onion
// blob.
failureMsg := lnwire.NewInvalidBlinding(
fn.None[[lnwire.OnionPacketSize]byte](),
)
reason, err := e.EncryptFirstHop(failureMsg)
if err != nil {
return err
}
msg = &lnwire.UpdateFailHTLC{
ChanID: l.ChanID(),
ID: htlcIndex,
Reason: reason,
}
// If we are a relaying node, we need to switch out any error that
// we've received to a malformed HTLC error.
case e.Type() == hop.EncrypterTypeRelaying:
l.log.Debugf("Relaying blinded node switching out malformed "+
"error: %v", htlcIndex)
msg = &lnwire.UpdateFailMalformedHTLC{
ChanID: l.ChanID(),
ID: htlcIndex,
FailureCode: lnwire.CodeInvalidBlinding,
}
default:
return fmt.Errorf("unexpected encrypter: %d", e)
}
if err := l.cfg.Peer.SendMessage(false, msg); err != nil {
l.log.Warnf("Send update fail failed: %v", err)
}
return nil
}
// sendMalformedHTLCError helper function which sends the malformed HTLC update
// to the payment sender.
func (l *channelLink) sendMalformedHTLCError(htlcIndex uint64,
code lnwire.FailCode, onionBlob [lnwire.OnionPacketSize]byte,
sourceRef *channeldb.AddRef) {
shaOnionBlob := sha256.Sum256(onionBlob[:])
err := l.channel.MalformedFailHTLC(htlcIndex, code, shaOnionBlob, sourceRef)
if err != nil {
l.log.Errorf("unable cancel htlc: %v", err)
return
}
l.cfg.Peer.SendMessage(false, &lnwire.UpdateFailMalformedHTLC{
ChanID: l.ChanID(),
ID: htlcIndex,
ShaOnionBlob: shaOnionBlob,
FailureCode: code,
})
}
// failf is a function which is used to encapsulate the action necessary for
// properly failing the link. It takes a LinkFailureError, which will be passed
// to the OnChannelFailure closure, in order for it to determine if we should
// force close the channel, and if we should send an error message to the
// remote peer.
func (l *channelLink) failf(linkErr LinkFailureError, format string,
a ...interface{}) {
reason := fmt.Errorf(format, a...)
// Return if we have already notified about a failure.
if l.failed {
l.log.Warnf("ignoring link failure (%v), as link already "+
"failed", reason)
return
}
l.log.Errorf("failing link: %s with error: %v", reason, linkErr)
// Set failed, such that we won't process any more updates, and notify
// the peer about the failure.
l.failed = true
l.cfg.OnChannelFailure(l.ChanID(), l.ShortChanID(), linkErr)
}
// FundingCustomBlob returns the custom funding blob of the channel that this
// link is associated with. The funding blob represents static information about
// the channel that was created at channel funding time.
func (l *channelLink) FundingCustomBlob() fn.Option[tlv.Blob] {
if l.channel == nil {
return fn.None[tlv.Blob]()
}
if l.channel.State() == nil {
return fn.None[tlv.Blob]()
}
return l.channel.State().CustomBlob
}
// CommitmentCustomBlob returns the custom blob of the current local commitment
// of the channel that this link is associated with.
func (l *channelLink) CommitmentCustomBlob() fn.Option[tlv.Blob] {
if l.channel == nil {
return fn.None[tlv.Blob]()
}
return l.channel.LocalCommitmentBlob()
}
package htlcswitch
import "github.com/go-errors/errors"
var (
// ErrLinkShuttingDown signals that the link is shutting down.
ErrLinkShuttingDown = errors.New("link shutting down")
// ErrLinkFailedShutdown signals that a requested shutdown failed.
ErrLinkFailedShutdown = errors.New("link failed to shutdown")
)
// errorCode encodes the possible types of errors that will make us fail the
// current link.
type errorCode uint8
const (
// ErrInternalError indicates that something internal in the link
// failed. In this case we will send a generic error to our peer.
ErrInternalError errorCode = iota
// ErrRemoteError indicates that our peer sent an error, prompting up
// to fail the link.
ErrRemoteError
// ErrRemoteUnresponsive indicates that our peer took too long to
// complete a commitment dance.
ErrRemoteUnresponsive
// ErrSyncError indicates that we failed synchronizing the state of the
// channel with our peer.
ErrSyncError
// ErrInvalidUpdate indicates that the peer send us an invalid update.
ErrInvalidUpdate
// ErrInvalidCommitment indicates that the remote peer sent us an
// invalid commitment signature.
ErrInvalidCommitment
// ErrInvalidRevocation indicates that the remote peer send us an
// invalid revocation message.
ErrInvalidRevocation
// ErrRecoveryError the channel was unable to be resumed, we need the
// remote party to force close the channel out on chain now as a
// result.
ErrRecoveryError
// ErrCircuitError indicates a duplicate keystone error was hit in the
// circuit map. This is non-fatal and will resolve itself (usually
// within several minutes).
ErrCircuitError
// ErrStfuViolation indicates that the quiescence protocol has been
// violated, either because Stfu has been sent/received at an invalid
// time, or that an update has been sent/received while the channel is
// quiesced.
ErrStfuViolation
)
// LinkFailureAction is an enum-like type that describes the action that should
// be taken in response to a link failure.
type LinkFailureAction uint8
const (
// LinkFailureForceNone indicates no action is to be taken.
LinkFailureForceNone LinkFailureAction = iota
// LinkFailureForceClose indicates that the channel should be force
// closed.
LinkFailureForceClose
// LinkFailureDisconnect indicates that we should disconnect in an
// attempt to recycle the connection. This can be useful if we think a
// TCP connection or state machine is stalled.
LinkFailureDisconnect
)
// LinkFailureError encapsulates an error that will make us fail the current
// link. It contains the necessary information needed to determine if we should
// force close the channel in the process, and if any error data should be sent
// to the peer.
type LinkFailureError struct {
// code is the type of error this LinkFailureError encapsulates.
code errorCode
// FailureAction describes what we should do to fail the channel.
FailureAction LinkFailureAction
// PermanentFailure indicates whether this failure is permanent, and
// the channel should not be attempted loaded again.
PermanentFailure bool
// Warning denotes if this is a non-terminal error that doesn't warrant
// failing the channel all together.
Warning bool
// SendData is a byte slice that will be sent to the peer. If nil a
// generic error will be sent.
SendData []byte
}
// A compile time check to ensure LinkFailureError implements the error
// interface.
var _ error = (*LinkFailureError)(nil)
// Error returns a generic error for the LinkFailureError.
//
// NOTE: Part of the error interface.
func (e LinkFailureError) Error() string {
switch e.code {
case ErrInternalError:
return "internal error"
case ErrRemoteError:
return "remote error"
case ErrRemoteUnresponsive:
return "remote unresponsive"
case ErrSyncError:
return "sync error"
case ErrInvalidUpdate:
return "invalid update"
case ErrInvalidCommitment:
return "invalid commitment"
case ErrInvalidRevocation:
return "invalid revocation"
case ErrRecoveryError:
return "unable to resume channel, recovery required"
case ErrCircuitError:
return "non-fatal circuit map error"
case ErrStfuViolation:
return "quiescence protocol executed improperly"
default:
return "unknown error"
}
}
// ShouldSendToPeer indicates whether we should send an error to the peer if
// the link fails with this LinkFailureError.
func (e LinkFailureError) ShouldSendToPeer() bool {
switch e.code {
// Since sending an error can lead some nodes to force close the
// channel, create a whitelist of the failures we want to send so that
// newly added error codes aren't automatically sent to the remote peer.
case
ErrInternalError,
ErrRemoteError,
ErrSyncError,
ErrInvalidUpdate,
ErrInvalidCommitment,
ErrInvalidRevocation,
ErrRecoveryError:
return true
// In all other cases we will not attempt to send our peer an error.
default:
return false
}
}
package htlcswitch
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
logger := build.NewSubLogger("HSWC", nil)
UseLogger(logger)
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
hop.UseLogger(logger)
}
package htlcswitch
import (
"bytes"
"container/list"
"errors"
"fmt"
"sync"
"time"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// ErrMailBoxShuttingDown is returned when the mailbox is interrupted by
// a shutdown request.
ErrMailBoxShuttingDown = errors.New("mailbox is shutting down")
// ErrPacketAlreadyExists signals that an attempt to add a packet failed
// because it already exists in the mailbox.
ErrPacketAlreadyExists = errors.New("mailbox already has packet")
)
// MailBox is an interface which represents a concurrent-safe, in-order
// delivery queue for messages from the network and also from the main switch.
// This struct serves as a buffer between incoming messages, and messages to
// the handled by the link. Each of the mutating methods within this interface
// should be implemented in a non-blocking manner.
type MailBox interface {
// AddMessage appends a new message to the end of the message queue.
AddMessage(msg lnwire.Message) error
// AddPacket appends a new message to the end of the packet queue.
AddPacket(pkt *htlcPacket) error
// HasPacket queries the packets for a circuit key, this is used to drop
// packets bound for the switch that already have a queued response.
HasPacket(CircuitKey) bool
// AckPacket removes a packet from the mailboxes in-memory replay
// buffer. This will prevent a packet from being delivered after a link
// restarts if the switch has remained online. The returned boolean
// indicates whether or not a packet with the passed incoming circuit
// key was removed.
AckPacket(CircuitKey) bool
// FailAdd fails an UpdateAddHTLC that exists within the mailbox,
// removing it from the in-memory replay buffer. This will prevent the
// packet from being delivered after the link restarts if the switch has
// remained online. The generated LinkError will show an
// OutgoingFailureDownstreamHtlcAdd FailureDetail.
FailAdd(pkt *htlcPacket)
// MessageOutBox returns a channel that any new messages ready for
// delivery will be sent on.
MessageOutBox() chan lnwire.Message
// PacketOutBox returns a channel that any new packets ready for
// delivery will be sent on.
PacketOutBox() chan *htlcPacket
// Clears any pending wire messages from the inbox.
ResetMessages() error
// Reset the packet head to point at the first element in the list.
ResetPackets() error
// SetDustClosure takes in a closure that is used to evaluate whether
// mailbox HTLC's are dust.
SetDustClosure(isDust dustClosure)
// SetFeeRate sets the feerate to be used when evaluating dust.
SetFeeRate(feerate chainfee.SatPerKWeight)
// DustPackets returns the dust sum for Adds in the mailbox for the
// local and remote commitments.
DustPackets() (lnwire.MilliSatoshi, lnwire.MilliSatoshi)
// Start starts the mailbox and any goroutines it needs to operate
// properly.
Start()
// Stop signals the mailbox and its goroutines for a graceful shutdown.
Stop()
}
type mailBoxConfig struct {
// shortChanID is the short channel id of the channel this mailbox
// belongs to.
shortChanID lnwire.ShortChannelID
// forwardPackets send a varidic number of htlcPackets to the switch to
// be routed. A quit channel should be provided so that the call can
// properly exit during shutdown.
forwardPackets func(<-chan struct{}, ...*htlcPacket) error
// clock is a time source for the mailbox.
clock clock.Clock
// expiry is the interval after which Adds will be cancelled if they
// have not been yet been delivered. The computed deadline will expiry
// this long after the Adds are added via AddPacket.
expiry time.Duration
// failMailboxUpdate is used to fail an expired HTLC and use the
// correct SCID if the underlying channel uses aliases.
failMailboxUpdate func(outScid,
mailboxScid lnwire.ShortChannelID) lnwire.FailureMessage
}
// memoryMailBox is an implementation of the MailBox struct backed by purely
// in-memory queues.
//
// TODO(morehouse): use typed lists instead of list.Lists to avoid type asserts.
type memoryMailBox struct {
started sync.Once
stopped sync.Once
cfg *mailBoxConfig
wireMessages *list.List
wireMtx sync.Mutex
wireCond *sync.Cond
messageOutbox chan lnwire.Message
msgReset chan chan struct{}
// repPkts is a queue for reply packets, e.g. Settles and Fails.
repPkts *list.List
repIndex map[CircuitKey]*list.Element
repHead *list.Element
// addPkts is a dedicated queue for Adds.
addPkts *list.List
addIndex map[CircuitKey]*list.Element
addHead *list.Element
pktMtx sync.Mutex
pktCond *sync.Cond
pktOutbox chan *htlcPacket
pktReset chan chan struct{}
wireShutdown chan struct{}
pktShutdown chan struct{}
quit chan struct{}
// feeRate is set when the link receives or sends out fee updates. It
// is refreshed when AttachMailBox is called in case a fee update did
// not get committed. In some cases it may be out of sync with the
// channel's feerate, but it should eventually get back in sync.
feeRate chainfee.SatPerKWeight
// isDust is set when AttachMailBox is called and serves to evaluate
// the outstanding dust in the memoryMailBox given the current set
// feeRate.
isDust dustClosure
}
// newMemoryMailBox creates a new instance of the memoryMailBox.
func newMemoryMailBox(cfg *mailBoxConfig) *memoryMailBox {
box := &memoryMailBox{
cfg: cfg,
wireMessages: list.New(),
repPkts: list.New(),
addPkts: list.New(),
messageOutbox: make(chan lnwire.Message),
pktOutbox: make(chan *htlcPacket),
msgReset: make(chan chan struct{}, 1),
pktReset: make(chan chan struct{}, 1),
repIndex: make(map[CircuitKey]*list.Element),
addIndex: make(map[CircuitKey]*list.Element),
wireShutdown: make(chan struct{}),
pktShutdown: make(chan struct{}),
quit: make(chan struct{}),
}
box.wireCond = sync.NewCond(&box.wireMtx)
box.pktCond = sync.NewCond(&box.pktMtx)
return box
}
// A compile time assertion to ensure that memoryMailBox meets the MailBox
// interface.
var _ MailBox = (*memoryMailBox)(nil)
// courierType is an enum that reflects the distinct types of messages a
// MailBox can handle. Each type will be placed in an isolated mail box and
// will have a dedicated goroutine for delivering the messages.
type courierType uint8
const (
// wireCourier is a type of courier that handles wire messages.
wireCourier courierType = iota
// pktCourier is a type of courier that handles htlc packets.
pktCourier
)
// Start starts the mailbox and any goroutines it needs to operate properly.
//
// NOTE: This method is part of the MailBox interface.
func (m *memoryMailBox) Start() {
m.started.Do(func() {
go m.wireMailCourier()
go m.pktMailCourier()
})
}
// ResetMessages blocks until all buffered wire messages are cleared.
func (m *memoryMailBox) ResetMessages() error {
msgDone := make(chan struct{})
select {
case m.msgReset <- msgDone:
return m.signalUntilReset(wireCourier, msgDone)
case <-m.quit:
return ErrMailBoxShuttingDown
}
}
// ResetPackets blocks until the head of packets buffer is reset, causing the
// packets to be redelivered in order.
func (m *memoryMailBox) ResetPackets() error {
pktDone := make(chan struct{})
select {
case m.pktReset <- pktDone:
return m.signalUntilReset(pktCourier, pktDone)
case <-m.quit:
return ErrMailBoxShuttingDown
}
}
// signalUntilReset strobes the condition variable for the specified inbox type
// until receiving a response that the mailbox has processed a reset.
func (m *memoryMailBox) signalUntilReset(cType courierType,
done chan struct{}) error {
for {
switch cType {
case wireCourier:
m.wireCond.Signal()
case pktCourier:
m.pktCond.Signal()
}
select {
case <-time.After(time.Millisecond):
continue
case <-done:
return nil
case <-m.quit:
return ErrMailBoxShuttingDown
}
}
}
// AckPacket removes the packet identified by it's incoming circuit key from the
// queue of packets to be delivered. The returned boolean indicates whether or
// not a packet with the passed incoming circuit key was removed.
//
// NOTE: It is safe to call this method multiple times for the same circuit key.
func (m *memoryMailBox) AckPacket(inKey CircuitKey) bool {
m.pktCond.L.Lock()
defer m.pktCond.L.Unlock()
if entry, ok := m.repIndex[inKey]; ok {
// Check whether we are removing the head of the queue. If so,
// we must advance the head to the next packet before removing.
// It's possible that the courier has already advanced the
// repHead, so this check prevents the repHead from getting
// desynchronized.
if entry == m.repHead {
m.repHead = entry.Next()
}
m.repPkts.Remove(entry)
delete(m.repIndex, inKey)
return true
}
if entry, ok := m.addIndex[inKey]; ok {
// Check whether we are removing the head of the queue. If so,
// we must advance the head to the next add before removing.
// It's possible that the courier has already advanced the
// addHead, so this check prevents the addHead from getting
// desynchronized.
//
// NOTE: While this event is rare for Settles or Fails, it could
// be very common for Adds since the mailbox has the ability to
// cancel Adds before they are delivered. When that occurs, the
// head of addPkts has only been peeked and we expect to be
// removing the head of the queue.
if entry == m.addHead {
m.addHead = entry.Next()
}
m.addPkts.Remove(entry)
delete(m.addIndex, inKey)
return true
}
return false
}
// HasPacket queries the packets for a circuit key, this is used to drop packets
// bound for the switch that already have a queued response.
func (m *memoryMailBox) HasPacket(inKey CircuitKey) bool {
m.pktCond.L.Lock()
_, ok := m.repIndex[inKey]
m.pktCond.L.Unlock()
return ok
}
// Stop signals the mailbox and its goroutines for a graceful shutdown.
//
// NOTE: This method is part of the MailBox interface.
func (m *memoryMailBox) Stop() {
m.stopped.Do(func() {
close(m.quit)
m.signalUntilShutdown(wireCourier)
m.signalUntilShutdown(pktCourier)
})
}
// signalUntilShutdown strobes the condition variable of the passed courier
// type, blocking until the worker has exited.
func (m *memoryMailBox) signalUntilShutdown(cType courierType) {
var (
cond *sync.Cond
shutdown chan struct{}
)
switch cType {
case wireCourier:
cond = m.wireCond
shutdown = m.wireShutdown
case pktCourier:
cond = m.pktCond
shutdown = m.pktShutdown
}
for {
select {
case <-time.After(time.Millisecond):
cond.Signal()
case <-shutdown:
return
}
}
}
// pktWithExpiry wraps an incoming packet and records the time at which it it
// should be canceled from the mailbox. This will be used to detect if it gets
// stuck in the mailbox and inform when to cancel back.
type pktWithExpiry struct {
pkt *htlcPacket
expiry time.Time
}
func (p *pktWithExpiry) deadline(clock clock.Clock) <-chan time.Time {
return clock.TickAfter(p.expiry.Sub(clock.Now()))
}
// wireMailCourier is a dedicated goroutine whose job is to reliably deliver
// wire messages.
func (m *memoryMailBox) wireMailCourier() {
defer close(m.wireShutdown)
for {
// First, we'll check our condition. If our mailbox is empty,
// then we'll wait until a new item is added.
m.wireCond.L.Lock()
for m.wireMessages.Front() == nil {
m.wireCond.Wait()
select {
case msgDone := <-m.msgReset:
m.wireMessages.Init()
close(msgDone)
case <-m.quit:
m.wireCond.L.Unlock()
return
default:
}
}
// Grab the datum off the front of the queue, shifting the
// slice's reference down one in order to remove the datum from
// the queue.
entry := m.wireMessages.Front()
//nolint:forcetypeassert
nextMsg := m.wireMessages.Remove(entry).(lnwire.Message)
// Now that we're done with the condition, we can unlock it to
// allow any callers to append to the end of our target queue.
m.wireCond.L.Unlock()
// With the next message obtained, we'll now select to attempt
// to deliver the message. If we receive a kill signal, then
// we'll bail out.
select {
case m.messageOutbox <- nextMsg:
case msgDone := <-m.msgReset:
m.wireCond.L.Lock()
m.wireMessages.Init()
m.wireCond.L.Unlock()
close(msgDone)
case <-m.quit:
return
}
}
}
// pktMailCourier is a dedicated goroutine whose job is to reliably deliver
// packet messages.
func (m *memoryMailBox) pktMailCourier() {
defer close(m.pktShutdown)
for {
// First, we'll check our condition. If our mailbox is empty,
// then we'll wait until a new item is added.
m.pktCond.L.Lock()
for m.repHead == nil && m.addHead == nil {
m.pktCond.Wait()
select {
// Resetting the packet queue means just moving our
// pointer to the front. This ensures that any un-ACK'd
// messages are re-delivered upon reconnect.
case pktDone := <-m.pktReset:
m.repHead = m.repPkts.Front()
m.addHead = m.addPkts.Front()
close(pktDone)
case <-m.quit:
m.pktCond.L.Unlock()
return
default:
}
}
var (
nextRep *htlcPacket
nextRepEl *list.Element
nextAdd *pktWithExpiry
nextAddEl *list.Element
)
// For packets, we actually never remove an item until it has
// been ACK'd by the link. This ensures that if a read packet
// doesn't make it into a commitment, then it'll be
// re-delivered once the link comes back online.
// Peek at the head of the Settle/Fails and Add queues. We peak
// both even if there is a Settle/Fail present because we need
// to set a deadline for the next pending Add if it's present.
// Due to clock monotonicity, we know that the head of the Adds
// is the next to expire.
if m.repHead != nil {
//nolint:forcetypeassert
nextRep = m.repHead.Value.(*htlcPacket)
nextRepEl = m.repHead
}
if m.addHead != nil {
//nolint:forcetypeassert
nextAdd = m.addHead.Value.(*pktWithExpiry)
nextAddEl = m.addHead
}
// Now that we're done with the condition, we can unlock it to
// allow any callers to append to the end of our target queue.
m.pktCond.L.Unlock()
var (
pktOutbox chan *htlcPacket
addOutbox chan *htlcPacket
add *htlcPacket
deadline <-chan time.Time
)
// Prioritize delivery of Settle/Fail packets over Adds. This
// ensures that we actively clear the commitment of existing
// HTLCs before trying to add new ones. This can help to improve
// forwarding performance since the time to sign a commitment is
// linear in the number of HTLCs manifested on the commitments.
//
// NOTE: Both types are eventually delivered over the same
// channel, but we can control which is delivered by exclusively
// making one nil and the other non-nil. We know from our loop
// condition that at least one nextRep and nextAdd are non-nil.
if nextRep != nil {
pktOutbox = m.pktOutbox
} else {
addOutbox = m.pktOutbox
}
// If we have a pending Add, we'll also construct the deadline
// so we can fail it back if we are unable to deliver any
// message in time. We also dereference the nextAdd's packet,
// since we will need access to it in the case we are delivering
// it and/or if the deadline expires.
//
// NOTE: It's possible after this point for add to be nil, but
// this can only occur when addOutbox is also nil, hence we
// won't accidentally deliver a nil packet.
if nextAdd != nil {
add = nextAdd.pkt
deadline = nextAdd.deadline(m.cfg.clock)
}
select {
case pktOutbox <- nextRep:
m.pktCond.L.Lock()
// Only advance the repHead if this Settle or Fail is
// still at the head of the queue.
if m.repHead != nil && m.repHead == nextRepEl {
m.repHead = m.repHead.Next()
}
m.pktCond.L.Unlock()
case addOutbox <- add:
m.pktCond.L.Lock()
// Only advance the addHead if this Add is still at the
// head of the queue.
if m.addHead != nil && m.addHead == nextAddEl {
m.addHead = m.addHead.Next()
}
m.pktCond.L.Unlock()
case <-deadline:
log.Debugf("Expiring add htlc with "+
"keystone=%v", add.keystone())
m.FailAdd(add)
case pktDone := <-m.pktReset:
m.pktCond.L.Lock()
m.repHead = m.repPkts.Front()
m.addHead = m.addPkts.Front()
m.pktCond.L.Unlock()
close(pktDone)
case <-m.quit:
return
}
}
}
// AddMessage appends a new message to the end of the message queue.
//
// NOTE: This method is safe for concrete use and part of the MailBox
// interface.
func (m *memoryMailBox) AddMessage(msg lnwire.Message) error {
// First, we'll lock the condition, and add the message to the end of
// the wire message inbox.
m.wireCond.L.Lock()
m.wireMessages.PushBack(msg)
m.wireCond.L.Unlock()
// With the message added, we signal to the mailCourier that there are
// additional messages to deliver.
m.wireCond.Signal()
return nil
}
// AddPacket appends a new message to the end of the packet queue.
//
// NOTE: This method is safe for concrete use and part of the MailBox
// interface.
func (m *memoryMailBox) AddPacket(pkt *htlcPacket) error {
m.pktCond.L.Lock()
switch htlc := pkt.htlc.(type) {
// Split off Settle/Fail packets into the repPkts queue.
case *lnwire.UpdateFulfillHTLC, *lnwire.UpdateFailHTLC:
if _, ok := m.repIndex[pkt.inKey()]; ok {
m.pktCond.L.Unlock()
return ErrPacketAlreadyExists
}
entry := m.repPkts.PushBack(pkt)
m.repIndex[pkt.inKey()] = entry
if m.repHead == nil {
m.repHead = entry
}
// Split off Add packets into the addPkts queue.
case *lnwire.UpdateAddHTLC:
if _, ok := m.addIndex[pkt.inKey()]; ok {
m.pktCond.L.Unlock()
return ErrPacketAlreadyExists
}
entry := m.addPkts.PushBack(&pktWithExpiry{
pkt: pkt,
expiry: m.cfg.clock.Now().Add(m.cfg.expiry),
})
m.addIndex[pkt.inKey()] = entry
if m.addHead == nil {
m.addHead = entry
}
default:
m.pktCond.L.Unlock()
return fmt.Errorf("unknown htlc type: %T", htlc)
}
m.pktCond.L.Unlock()
// With the packet added, we signal to the mailCourier that there are
// additional packets to consume.
m.pktCond.Signal()
return nil
}
// SetFeeRate sets the memoryMailBox's feerate for use in DustPackets.
func (m *memoryMailBox) SetFeeRate(feeRate chainfee.SatPerKWeight) {
m.pktCond.L.Lock()
defer m.pktCond.L.Unlock()
m.feeRate = feeRate
}
// SetDustClosure sets the memoryMailBox's dustClosure for use in DustPackets.
func (m *memoryMailBox) SetDustClosure(isDust dustClosure) {
m.pktCond.L.Lock()
defer m.pktCond.L.Unlock()
m.isDust = isDust
}
// DustPackets returns the dust sum for add packets in the mailbox. The first
// return value is the local dust sum and the second is the remote dust sum.
// This will keep track of a given dust HTLC from the time it is added via
// AddPacket until it is removed via AckPacket.
func (m *memoryMailBox) DustPackets() (lnwire.MilliSatoshi,
lnwire.MilliSatoshi) {
m.pktCond.L.Lock()
defer m.pktCond.L.Unlock()
var (
localDustSum lnwire.MilliSatoshi
remoteDustSum lnwire.MilliSatoshi
)
// Run through the map of HTLC's and determine the dust sum with calls
// to the memoryMailBox's isDust closure. Note that all mailbox packets
// are outgoing so the second argument to isDust will be false.
for _, e := range m.addIndex {
addPkt := e.Value.(*pktWithExpiry).pkt
// Evaluate whether this HTLC is dust on the local commitment.
if m.isDust(
m.feeRate, false, lntypes.Local,
addPkt.amount.ToSatoshis(),
) {
localDustSum += addPkt.amount
}
// Evaluate whether this HTLC is dust on the remote commitment.
if m.isDust(
m.feeRate, false, lntypes.Remote,
addPkt.amount.ToSatoshis(),
) {
remoteDustSum += addPkt.amount
}
}
return localDustSum, remoteDustSum
}
// FailAdd fails an UpdateAddHTLC that exists within the mailbox, removing it
// from the in-memory replay buffer. This will prevent the packet from being
// delivered after the link restarts if the switch has remained online. The
// generated LinkError will show an OutgoingFailureDownstreamHtlcAdd
// FailureDetail.
func (m *memoryMailBox) FailAdd(pkt *htlcPacket) {
// First, remove the packet from mailbox. If we didn't find the packet
// because it has already been acked, we'll exit early to avoid sending
// a duplicate fail message through the switch.
if !m.AckPacket(pkt.inKey()) {
return
}
var (
localFailure = false
reason lnwire.OpaqueReason
)
// Create a temporary channel failure which we will send back to our
// peer if this is a forward, or report to the user if the failed
// payment was locally initiated.
failure := m.cfg.failMailboxUpdate(
pkt.originalOutgoingChanID, m.cfg.shortChanID,
)
// If the payment was locally initiated (which is indicated by a nil
// obfuscator), we do not need to encrypt it back to the sender.
if pkt.obfuscator == nil {
var b bytes.Buffer
err := lnwire.EncodeFailure(&b, failure, 0)
if err != nil {
log.Errorf("Unable to encode failure: %v", err)
return
}
reason = lnwire.OpaqueReason(b.Bytes())
localFailure = true
} else {
// If the packet is part of a forward, (identified by a non-nil
// obfuscator) we need to encrypt the error back to the source.
var err error
reason, err = pkt.obfuscator.EncryptFirstHop(failure)
if err != nil {
log.Errorf("Unable to obfuscate error: %v", err)
return
}
}
// Create a link error containing the temporary channel failure and a
// detail which indicates the we failed to add the htlc.
linkError := NewDetailedLinkError(
failure, OutgoingFailureDownstreamHtlcAdd,
)
failPkt := &htlcPacket{
incomingChanID: pkt.incomingChanID,
incomingHTLCID: pkt.incomingHTLCID,
circuit: pkt.circuit,
sourceRef: pkt.sourceRef,
hasSource: true,
localFailure: localFailure,
obfuscator: pkt.obfuscator,
linkFailure: linkError,
htlc: &lnwire.UpdateFailHTLC{
Reason: reason,
},
}
if err := m.cfg.forwardPackets(m.quit, failPkt); err != nil {
log.Errorf("Unhandled error while reforwarding packets "+
"settle/fail over htlcswitch: %v", err)
}
}
// MessageOutBox returns a channel that any new messages ready for delivery
// will be sent on.
//
// NOTE: This method is part of the MailBox interface.
func (m *memoryMailBox) MessageOutBox() chan lnwire.Message {
return m.messageOutbox
}
// PacketOutBox returns a channel that any new packets ready for delivery will
// be sent on.
//
// NOTE: This method is part of the MailBox interface.
func (m *memoryMailBox) PacketOutBox() chan *htlcPacket {
return m.pktOutbox
}
// mailOrchestrator is responsible for coordinating the creation and lifecycle
// of mailboxes used within the switch. It supports the ability to create
// mailboxes, reassign their short channel id's, deliver htlc packets, and
// queue packets for mailboxes that have not been created due to a link's late
// registration.
type mailOrchestrator struct {
mu sync.RWMutex
cfg *mailOrchConfig
// mailboxes caches exactly one mailbox for all known channels.
mailboxes map[lnwire.ChannelID]MailBox
// liveIndex maps a live short chan id to the primary mailbox key.
// An index in liveIndex map is only entered under two conditions:
// 1. A link has a non-zero short channel id at time of AddLink.
// 2. A link receives a non-zero short channel via UpdateShortChanID.
liveIndex map[lnwire.ShortChannelID]lnwire.ChannelID
// TODO(conner): add another pair of indexes:
// chan_id -> short_chan_id
// short_chan_id -> mailbox
// so that Deliver can lookup mailbox directly once live,
// but still queryable by channel_id.
// unclaimedPackets maps a live short chan id to queue of packets if no
// mailbox has been created.
unclaimedPackets map[lnwire.ShortChannelID][]*htlcPacket
}
type mailOrchConfig struct {
// forwardPackets send a varidic number of htlcPackets to the switch to
// be routed. A quit channel should be provided so that the call can
// properly exit during shutdown.
forwardPackets func(<-chan struct{}, ...*htlcPacket) error
// clock is a time source for the generated mailboxes.
clock clock.Clock
// expiry is the interval after which Adds will be cancelled if they
// have not been yet been delivered. The computed deadline will expiry
// this long after the Adds are added to a mailbox via AddPacket.
expiry time.Duration
// failMailboxUpdate is used to fail an expired HTLC and use the
// correct SCID if the underlying channel uses aliases.
failMailboxUpdate func(outScid,
mailboxScid lnwire.ShortChannelID) lnwire.FailureMessage
}
// newMailOrchestrator initializes a fresh mailOrchestrator.
func newMailOrchestrator(cfg *mailOrchConfig) *mailOrchestrator {
return &mailOrchestrator{
cfg: cfg,
mailboxes: make(map[lnwire.ChannelID]MailBox),
liveIndex: make(map[lnwire.ShortChannelID]lnwire.ChannelID),
unclaimedPackets: make(map[lnwire.ShortChannelID][]*htlcPacket),
}
}
// Stop instructs the orchestrator to stop all active mailboxes.
func (mo *mailOrchestrator) Stop() {
for _, mailbox := range mo.mailboxes {
mailbox.Stop()
}
}
// GetOrCreateMailBox returns an existing mailbox belonging to `chanID`, or
// creates and returns a new mailbox if none is found.
func (mo *mailOrchestrator) GetOrCreateMailBox(chanID lnwire.ChannelID,
shortChanID lnwire.ShortChannelID) MailBox {
// First, try lookup the mailbox directly using only the shared mutex.
mo.mu.RLock()
mailbox, ok := mo.mailboxes[chanID]
if ok {
mo.mu.RUnlock()
return mailbox
}
mo.mu.RUnlock()
// Otherwise, we will try again with exclusive lock, creating a mailbox
// if one still has not been created.
mo.mu.Lock()
mailbox = mo.exclusiveGetOrCreateMailBox(chanID, shortChanID)
mo.mu.Unlock()
return mailbox
}
// exclusiveGetOrCreateMailBox checks for the existence of a mailbox for the
// given channel id. If none is found, a new one is creates, started, and
// recorded.
//
// NOTE: This method MUST be invoked with the mailOrchestrator's exclusive lock.
func (mo *mailOrchestrator) exclusiveGetOrCreateMailBox(
chanID lnwire.ChannelID, shortChanID lnwire.ShortChannelID) MailBox {
mailbox, ok := mo.mailboxes[chanID]
if !ok {
mailbox = newMemoryMailBox(&mailBoxConfig{
shortChanID: shortChanID,
forwardPackets: mo.cfg.forwardPackets,
clock: mo.cfg.clock,
expiry: mo.cfg.expiry,
failMailboxUpdate: mo.cfg.failMailboxUpdate,
})
mailbox.Start()
mo.mailboxes[chanID] = mailbox
}
return mailbox
}
// BindLiveShortChanID registers that messages bound for a particular short
// channel id should be forwarded to the mailbox corresponding to the given
// channel id. This method also checks to see if there are any unclaimed
// packets for this short_chan_id. If any are found, they are delivered to the
// mailbox and removed (marked as claimed).
func (mo *mailOrchestrator) BindLiveShortChanID(mailbox MailBox,
cid lnwire.ChannelID, sid lnwire.ShortChannelID) {
mo.mu.Lock()
// Update the mapping from short channel id to mailbox's channel id.
mo.liveIndex[sid] = cid
// Retrieve any unclaimed packets destined for this mailbox.
pkts := mo.unclaimedPackets[sid]
delete(mo.unclaimedPackets, sid)
mo.mu.Unlock()
// Deliver the unclaimed packets.
for _, pkt := range pkts {
mailbox.AddPacket(pkt)
}
}
// Deliver lookups the target mailbox using the live index from short_chan_id
// to channel_id. If the mailbox is found, the message is delivered directly.
// Otherwise the packet is recorded as unclaimed, and will be delivered to the
// mailbox upon the subsequent call to BindLiveShortChanID.
func (mo *mailOrchestrator) Deliver(
sid lnwire.ShortChannelID, pkt *htlcPacket) error {
var (
mailbox MailBox
found bool
)
// First, try to find the channel id for the target short_chan_id. If
// the link is live, we will also look up the created mailbox.
mo.mu.RLock()
chanID, isLive := mo.liveIndex[sid]
if isLive {
mailbox, found = mo.mailboxes[chanID]
}
mo.mu.RUnlock()
// The link is live and target mailbox was found, deliver immediately.
if isLive && found {
return mailbox.AddPacket(pkt)
}
// If we detected that the link has not been made live, we will acquire
// the exclusive lock preemptively in order to queue this packet in the
// list of unclaimed packets.
mo.mu.Lock()
// Double check to see if the mailbox has been not made live since the
// release of the shared lock.
//
// NOTE: Checking again with the exclusive lock held prevents a race
// condition where BindLiveShortChanID is interleaved between the
// release of the shared lock, and acquiring the exclusive lock. The
// result would be stuck packets, as they wouldn't be redelivered until
// the next call to BindLiveShortChanID, which is expected to occur
// infrequently.
chanID, isLive = mo.liveIndex[sid]
if isLive {
// Reaching this point indicates the mailbox is actually live.
// We'll try to load the mailbox using the fresh channel id.
//
// NOTE: This should never create a new mailbox, as the live
// index should only be set if the mailbox had been initialized
// beforehand. However, this does ensure that this case is
// handled properly in the event that it could happen.
mailbox = mo.exclusiveGetOrCreateMailBox(chanID, sid)
mo.mu.Unlock()
// Deliver the packet to the mailbox if it was found or created.
return mailbox.AddPacket(pkt)
}
// Finally, if the channel id is still not found in the live index,
// we'll add this to the list of unclaimed packets. These will be
// delivered upon the next call to BindLiveShortChanID.
mo.unclaimedPackets[sid] = append(mo.unclaimedPackets[sid], pkt)
mo.mu.Unlock()
return nil
}
package htlcswitch
import (
"bytes"
"context"
"crypto/sha256"
"encoding/binary"
"fmt"
"io"
"net"
"path/filepath"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"
"github.com/go-errors/errors"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/lnpeer"
"github.com/lightningnetwork/lnd/lntest/mock"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/ticker"
"github.com/lightningnetwork/lnd/tlv"
)
func isAlias(scid lnwire.ShortChannelID) bool {
return scid.BlockHeight >= 16_000_000 && scid.BlockHeight < 16_250_000
}
type mockPreimageCache struct {
sync.Mutex
preimageMap map[lntypes.Hash]lntypes.Preimage
}
func newMockPreimageCache() *mockPreimageCache {
return &mockPreimageCache{
preimageMap: make(map[lntypes.Hash]lntypes.Preimage),
}
}
func (m *mockPreimageCache) LookupPreimage(
hash lntypes.Hash) (lntypes.Preimage, bool) {
m.Lock()
defer m.Unlock()
p, ok := m.preimageMap[hash]
return p, ok
}
func (m *mockPreimageCache) AddPreimages(preimages ...lntypes.Preimage) error {
m.Lock()
defer m.Unlock()
for _, preimage := range preimages {
m.preimageMap[preimage.Hash()] = preimage
}
return nil
}
func (m *mockPreimageCache) SubscribeUpdates(
chanID lnwire.ShortChannelID, htlc *channeldb.HTLC,
payload *hop.Payload,
nextHopOnionBlob []byte) (*contractcourt.WitnessSubscription, error) {
return nil, nil
}
// TODO(yy): replace it with chainfee.MockEstimator.
type mockFeeEstimator struct {
byteFeeIn chan chainfee.SatPerKWeight
relayFee chan chainfee.SatPerKWeight
quit chan struct{}
}
func newMockFeeEstimator() *mockFeeEstimator {
return &mockFeeEstimator{
byteFeeIn: make(chan chainfee.SatPerKWeight),
relayFee: make(chan chainfee.SatPerKWeight),
quit: make(chan struct{}),
}
}
func (m *mockFeeEstimator) EstimateFeePerKW(
numBlocks uint32) (chainfee.SatPerKWeight, error) {
select {
case feeRate := <-m.byteFeeIn:
return feeRate, nil
case <-m.quit:
return 0, fmt.Errorf("exiting")
}
}
func (m *mockFeeEstimator) RelayFeePerKW() chainfee.SatPerKWeight {
select {
case feeRate := <-m.relayFee:
return feeRate
case <-m.quit:
return 0
}
}
func (m *mockFeeEstimator) Start() error {
return nil
}
func (m *mockFeeEstimator) Stop() error {
close(m.quit)
return nil
}
var _ chainfee.Estimator = (*mockFeeEstimator)(nil)
type mockForwardingLog struct {
sync.Mutex
events map[time.Time]channeldb.ForwardingEvent
}
func (m *mockForwardingLog) AddForwardingEvents(events []channeldb.ForwardingEvent) error {
m.Lock()
defer m.Unlock()
for _, event := range events {
m.events[event.Timestamp] = event
}
return nil
}
type mockServer struct {
started int32 // To be used atomically.
shutdown int32 // To be used atomically.
wg sync.WaitGroup
quit chan struct{}
t testing.TB
name string
messages chan lnwire.Message
protocolTraceMtx sync.Mutex
protocolTrace []lnwire.Message
id [33]byte
htlcSwitch *Switch
registry *mockInvoiceRegistry
pCache *mockPreimageCache
interceptorFuncs []messageInterceptor
}
var _ lnpeer.Peer = (*mockServer)(nil)
func initSwitchWithDB(startingHeight uint32, db *channeldb.DB) (*Switch, error) {
signAliasUpdate := func(u *lnwire.ChannelUpdate1) (*ecdsa.Signature,
error) {
return testSig, nil
}
cfg := Config{
DB: db,
FetchAllOpenChannels: db.ChannelStateDB().FetchAllOpenChannels,
FetchAllChannels: db.ChannelStateDB().FetchAllChannels,
FetchClosedChannels: db.ChannelStateDB().FetchClosedChannels,
SwitchPackager: channeldb.NewSwitchPackager(),
FwdingLog: &mockForwardingLog{
events: make(map[time.Time]channeldb.ForwardingEvent),
},
FetchLastChannelUpdate: func(scid lnwire.ShortChannelID) (
*lnwire.ChannelUpdate1, error) {
return &lnwire.ChannelUpdate1{
ShortChannelID: scid,
}, nil
},
Notifier: &mock.ChainNotifier{
SpendChan: make(chan *chainntnfs.SpendDetail),
EpochChan: make(chan *chainntnfs.BlockEpoch),
ConfChan: make(chan *chainntnfs.TxConfirmation),
},
FwdEventTicker: ticker.NewForce(
DefaultFwdEventInterval,
),
LogEventTicker: ticker.NewForce(DefaultLogInterval),
AckEventTicker: ticker.NewForce(DefaultAckInterval),
HtlcNotifier: &mockHTLCNotifier{},
Clock: clock.NewDefaultClock(),
MailboxDeliveryTimeout: time.Hour,
MaxFeeExposure: DefaultMaxFeeExposure,
SignAliasUpdate: signAliasUpdate,
IsAlias: isAlias,
}
return New(cfg, startingHeight)
}
func initSwitchWithTempDB(t testing.TB, startingHeight uint32) (*Switch,
error) {
tempPath := filepath.Join(t.TempDir(), "switchdb")
db := channeldb.OpenForTesting(t, tempPath)
s, err := initSwitchWithDB(startingHeight, db)
if err != nil {
return nil, err
}
return s, nil
}
func newMockServer(t testing.TB, name string, startingHeight uint32,
db *channeldb.DB, defaultDelta uint32) (*mockServer, error) {
var id [33]byte
h := sha256.Sum256([]byte(name))
copy(id[:], h[:])
pCache := newMockPreimageCache()
var (
htlcSwitch *Switch
err error
)
if db == nil {
htlcSwitch, err = initSwitchWithTempDB(t, startingHeight)
} else {
htlcSwitch, err = initSwitchWithDB(startingHeight, db)
}
if err != nil {
return nil, err
}
t.Cleanup(func() { _ = htlcSwitch.Stop() })
registry := newMockRegistry(t)
return &mockServer{
t: t,
id: id,
name: name,
messages: make(chan lnwire.Message, 3000),
quit: make(chan struct{}),
registry: registry,
htlcSwitch: htlcSwitch,
pCache: pCache,
interceptorFuncs: make([]messageInterceptor, 0),
}, nil
}
func (s *mockServer) Start() error {
if !atomic.CompareAndSwapInt32(&s.started, 0, 1) {
return errors.New("mock server already started")
}
if err := s.htlcSwitch.Start(); err != nil {
return err
}
s.wg.Add(1)
go func() {
defer s.wg.Done()
defer func() {
s.htlcSwitch.Stop()
}()
for {
select {
case msg := <-s.messages:
s.protocolTraceMtx.Lock()
s.protocolTrace = append(s.protocolTrace, msg)
s.protocolTraceMtx.Unlock()
var shouldSkip bool
for _, interceptor := range s.interceptorFuncs {
skip, err := interceptor(msg)
if err != nil {
s.t.Fatalf("%v: error in the "+
"interceptor: %v", s.name, err)
return
}
shouldSkip = shouldSkip || skip
}
if shouldSkip {
continue
}
if err := s.readHandler(msg); err != nil {
s.t.Fatal(err)
return
}
case <-s.quit:
return
}
}
}()
return nil
}
func (s *mockServer) QuitSignal() <-chan struct{} {
return s.quit
}
// mockHopIterator represents the test version of hop iterator which instead
// of encrypting the path in onion blob just stores the path as a list of hops.
type mockHopIterator struct {
hops []*hop.Payload
}
func newMockHopIterator(hops ...*hop.Payload) hop.Iterator {
return &mockHopIterator{hops: hops}
}
func (r *mockHopIterator) HopPayload() (*hop.Payload, hop.RouteRole, error) {
h := r.hops[0]
r.hops = r.hops[1:]
return h, hop.RouteRoleCleartext, nil
}
func (r *mockHopIterator) ExtraOnionBlob() []byte {
return nil
}
func (r *mockHopIterator) ExtractErrorEncrypter(
extracter hop.ErrorEncrypterExtracter, _ bool) (hop.ErrorEncrypter,
lnwire.FailCode) {
return extracter(nil)
}
func (r *mockHopIterator) EncodeNextHop(w io.Writer) error {
var hopLength [4]byte
binary.BigEndian.PutUint32(hopLength[:], uint32(len(r.hops)))
if _, err := w.Write(hopLength[:]); err != nil {
return err
}
for _, hop := range r.hops {
fwdInfo := hop.ForwardingInfo()
if err := encodeFwdInfo(w, &fwdInfo); err != nil {
return err
}
}
return nil
}
func encodeFwdInfo(w io.Writer, f *hop.ForwardingInfo) error {
if err := binary.Write(w, binary.BigEndian, f.NextHop); err != nil {
return err
}
if err := binary.Write(w, binary.BigEndian, f.AmountToForward); err != nil {
return err
}
if err := binary.Write(w, binary.BigEndian, f.OutgoingCTLV); err != nil {
return err
}
return nil
}
var _ hop.Iterator = (*mockHopIterator)(nil)
// mockObfuscator mock implementation of the failure obfuscator which only
// encodes the failure and do not makes any onion obfuscation.
type mockObfuscator struct {
ogPacket *sphinx.OnionPacket
failure lnwire.FailureMessage
}
// NewMockObfuscator initializes a dummy mockObfuscator used for testing.
func NewMockObfuscator() hop.ErrorEncrypter {
return &mockObfuscator{}
}
func (o *mockObfuscator) OnionPacket() *sphinx.OnionPacket {
return o.ogPacket
}
func (o *mockObfuscator) Type() hop.EncrypterType {
return hop.EncrypterTypeMock
}
func (o *mockObfuscator) Encode(w io.Writer) error {
return nil
}
func (o *mockObfuscator) Decode(r io.Reader) error {
return nil
}
func (o *mockObfuscator) Reextract(
extracter hop.ErrorEncrypterExtracter) error {
return nil
}
var fakeHmac = []byte("hmachmachmachmachmachmachmachmac")
func (o *mockObfuscator) EncryptFirstHop(failure lnwire.FailureMessage) (
lnwire.OpaqueReason, error) {
o.failure = failure
var b bytes.Buffer
b.Write(fakeHmac)
if err := lnwire.EncodeFailure(&b, failure, 0); err != nil {
return nil, err
}
return b.Bytes(), nil
}
func (o *mockObfuscator) IntermediateEncrypt(reason lnwire.OpaqueReason) lnwire.OpaqueReason {
return reason
}
func (o *mockObfuscator) EncryptMalformedError(reason lnwire.OpaqueReason) lnwire.OpaqueReason {
var b bytes.Buffer
b.Write(fakeHmac)
b.Write(reason)
return b.Bytes()
}
// mockDeobfuscator mock implementation of the failure deobfuscator which
// only decodes the failure do not makes any onion obfuscation.
type mockDeobfuscator struct{}
func newMockDeobfuscator() ErrorDecrypter {
return &mockDeobfuscator{}
}
func (o *mockDeobfuscator) DecryptError(reason lnwire.OpaqueReason) (
*ForwardingError, error) {
if !bytes.Equal(reason[:32], fakeHmac) {
return nil, errors.New("fake decryption error")
}
reason = reason[32:]
r := bytes.NewReader(reason)
failure, err := lnwire.DecodeFailure(r, 0)
if err != nil {
return nil, err
}
return NewForwardingError(failure, 1), nil
}
var _ ErrorDecrypter = (*mockDeobfuscator)(nil)
// mockIteratorDecoder test version of hop iterator decoder which decodes the
// encoded array of hops.
type mockIteratorDecoder struct {
mu sync.RWMutex
responses map[[32]byte][]hop.DecodeHopIteratorResponse
decodeFail bool
}
func newMockIteratorDecoder() *mockIteratorDecoder {
return &mockIteratorDecoder{
responses: make(map[[32]byte][]hop.DecodeHopIteratorResponse),
}
}
func (p *mockIteratorDecoder) DecodeHopIterator(r io.Reader, rHash []byte,
cltv uint32) (hop.Iterator, lnwire.FailCode) {
var b [4]byte
_, err := r.Read(b[:])
if err != nil {
return nil, lnwire.CodeTemporaryChannelFailure
}
hopLength := binary.BigEndian.Uint32(b[:])
hops := make([]*hop.Payload, hopLength)
for i := uint32(0); i < hopLength; i++ {
var f hop.ForwardingInfo
if err := decodeFwdInfo(r, &f); err != nil {
return nil, lnwire.CodeTemporaryChannelFailure
}
var nextHopBytes [8]byte
binary.BigEndian.PutUint64(nextHopBytes[:], f.NextHop.ToUint64())
hops[i] = hop.NewLegacyPayload(&sphinx.HopData{
Realm: [1]byte{}, // hop.BitcoinNetwork
NextAddress: nextHopBytes,
ForwardAmount: uint64(f.AmountToForward),
OutgoingCltv: f.OutgoingCTLV,
})
}
return newMockHopIterator(hops...), lnwire.CodeNone
}
func (p *mockIteratorDecoder) DecodeHopIterators(id []byte,
reqs []hop.DecodeHopIteratorRequest) (
[]hop.DecodeHopIteratorResponse, error) {
idHash := sha256.Sum256(id)
p.mu.RLock()
if resps, ok := p.responses[idHash]; ok {
p.mu.RUnlock()
return resps, nil
}
p.mu.RUnlock()
batchSize := len(reqs)
resps := make([]hop.DecodeHopIteratorResponse, 0, batchSize)
for _, req := range reqs {
iterator, failcode := p.DecodeHopIterator(
req.OnionReader, req.RHash, req.IncomingCltv,
)
if p.decodeFail {
failcode = lnwire.CodeTemporaryChannelFailure
}
resp := hop.DecodeHopIteratorResponse{
HopIterator: iterator,
FailCode: failcode,
}
resps = append(resps, resp)
}
p.mu.Lock()
p.responses[idHash] = resps
p.mu.Unlock()
return resps, nil
}
func decodeFwdInfo(r io.Reader, f *hop.ForwardingInfo) error {
if err := binary.Read(r, binary.BigEndian, &f.NextHop); err != nil {
return err
}
if err := binary.Read(r, binary.BigEndian, &f.AmountToForward); err != nil {
return err
}
if err := binary.Read(r, binary.BigEndian, &f.OutgoingCTLV); err != nil {
return err
}
return nil
}
// messageInterceptor is function that handles the incoming peer messages and
// may decide should the peer skip the message or not.
type messageInterceptor func(m lnwire.Message) (bool, error)
// Record is used to set the function which will be triggered when new
// lnwire message was received.
func (s *mockServer) intersect(f messageInterceptor) {
s.interceptorFuncs = append(s.interceptorFuncs, f)
}
func (s *mockServer) SendMessage(sync bool, msgs ...lnwire.Message) error {
for _, msg := range msgs {
select {
case s.messages <- msg:
case <-s.quit:
return errors.New("server is stopped")
}
}
return nil
}
func (s *mockServer) SendMessageLazy(sync bool, msgs ...lnwire.Message) error {
panic("not implemented")
}
func (s *mockServer) readHandler(message lnwire.Message) error {
var targetChan lnwire.ChannelID
switch msg := message.(type) {
case *lnwire.UpdateAddHTLC:
targetChan = msg.ChanID
case *lnwire.UpdateFulfillHTLC:
targetChan = msg.ChanID
case *lnwire.UpdateFailHTLC:
targetChan = msg.ChanID
case *lnwire.UpdateFailMalformedHTLC:
targetChan = msg.ChanID
case *lnwire.RevokeAndAck:
targetChan = msg.ChanID
case *lnwire.CommitSig:
targetChan = msg.ChanID
case *lnwire.ChannelReady:
// Ignore
return nil
case *lnwire.ChannelReestablish:
targetChan = msg.ChanID
case *lnwire.UpdateFee:
targetChan = msg.ChanID
case *lnwire.Stfu:
targetChan = msg.ChanID
default:
return fmt.Errorf("unknown message type: %T", msg)
}
// Dispatch the commitment update message to the proper channel link
// dedicated to this channel. If the link is not found, we will discard
// the message.
link, err := s.htlcSwitch.GetLink(targetChan)
if err != nil {
return nil
}
// Create goroutine for this, in order to be able to properly stop
// the server when handler stacked (server unavailable)
link.HandleChannelUpdate(message)
return nil
}
func (s *mockServer) PubKey() [33]byte {
return s.id
}
func (s *mockServer) IdentityKey() *btcec.PublicKey {
pubkey, _ := btcec.ParsePubKey(s.id[:])
return pubkey
}
func (s *mockServer) Address() net.Addr {
return nil
}
func (s *mockServer) AddNewChannel(channel *lnpeer.NewChannel,
cancel <-chan struct{}) error {
return nil
}
func (s *mockServer) AddPendingChannel(_ lnwire.ChannelID,
cancel <-chan struct{}) error {
return nil
}
func (s *mockServer) RemovePendingChannel(_ lnwire.ChannelID) error {
return nil
}
func (s *mockServer) WipeChannel(*wire.OutPoint) {}
func (s *mockServer) LocalFeatures() *lnwire.FeatureVector {
return nil
}
func (s *mockServer) RemoteFeatures() *lnwire.FeatureVector {
return nil
}
func (s *mockServer) Disconnect(err error) {}
func (s *mockServer) Stop() error {
if !atomic.CompareAndSwapInt32(&s.shutdown, 0, 1) {
return nil
}
close(s.quit)
s.wg.Wait()
return nil
}
func (s *mockServer) String() string {
return s.name
}
type mockChannelLink struct {
htlcSwitch *Switch
shortChanID lnwire.ShortChannelID
// Only used for zero-conf channels.
realScid lnwire.ShortChannelID
aliases []lnwire.ShortChannelID
chanID lnwire.ChannelID
peer lnpeer.Peer
mailBox MailBox
packets chan *htlcPacket
eligible bool
unadvertised bool
zeroConf bool
optionFeature bool
htlcID uint64
checkHtlcTransitResult *LinkError
checkHtlcForwardResult *LinkError
failAliasUpdate func(sid lnwire.ShortChannelID,
incoming bool) *lnwire.ChannelUpdate1
confirmedZC bool
}
// completeCircuit is a helper method for adding the finalized payment circuit
// to the switch's circuit map. In testing, this should be executed after
// receiving an htlc from the downstream packets channel.
func (f *mockChannelLink) completeCircuit(pkt *htlcPacket) error {
switch htlc := pkt.htlc.(type) {
case *lnwire.UpdateAddHTLC:
pkt.outgoingChanID = f.shortChanID
pkt.outgoingHTLCID = f.htlcID
htlc.ID = f.htlcID
keystone := Keystone{pkt.inKey(), pkt.outKey()}
err := f.htlcSwitch.circuits.OpenCircuits(keystone)
if err != nil {
return err
}
f.htlcID++
case *lnwire.UpdateFulfillHTLC, *lnwire.UpdateFailHTLC:
if pkt.circuit != nil {
err := f.htlcSwitch.teardownCircuit(pkt)
if err != nil {
return err
}
}
}
f.mailBox.AckPacket(pkt.inKey())
return nil
}
func (f *mockChannelLink) deleteCircuit(pkt *htlcPacket) error {
return f.htlcSwitch.circuits.DeleteCircuits(pkt.inKey())
}
func newMockChannelLink(htlcSwitch *Switch, chanID lnwire.ChannelID,
shortChanID, realScid lnwire.ShortChannelID, peer lnpeer.Peer,
eligible, unadvertised, zeroConf, optionFeature bool,
) *mockChannelLink {
aliases := make([]lnwire.ShortChannelID, 0)
var realConfirmed bool
if zeroConf {
aliases = append(aliases, shortChanID)
}
if realScid != hop.Source {
realConfirmed = true
}
return &mockChannelLink{
htlcSwitch: htlcSwitch,
chanID: chanID,
shortChanID: shortChanID,
realScid: realScid,
peer: peer,
eligible: eligible,
unadvertised: unadvertised,
zeroConf: zeroConf,
optionFeature: optionFeature,
aliases: aliases,
confirmedZC: realConfirmed,
}
}
// addAlias is not part of any interface method.
func (f *mockChannelLink) addAlias(alias lnwire.ShortChannelID) {
f.aliases = append(f.aliases, alias)
}
func (f *mockChannelLink) handleSwitchPacket(pkt *htlcPacket) error {
f.mailBox.AddPacket(pkt)
return nil
}
func (f *mockChannelLink) getDustSum(whoseCommit lntypes.ChannelParty,
dryRunFee fn.Option[chainfee.SatPerKWeight]) lnwire.MilliSatoshi {
return 0
}
func (f *mockChannelLink) getFeeRate() chainfee.SatPerKWeight {
return 0
}
func (f *mockChannelLink) getDustClosure() dustClosure {
dustLimit := btcutil.Amount(400)
return dustHelper(
channeldb.SingleFunderTweaklessBit, dustLimit, dustLimit,
)
}
func (f *mockChannelLink) getCommitFee(remote bool) btcutil.Amount {
return 0
}
func (f *mockChannelLink) HandleChannelUpdate(lnwire.Message) {
}
func (f *mockChannelLink) UpdateForwardingPolicy(_ models.ForwardingPolicy) {
}
func (f *mockChannelLink) CheckHtlcForward([32]byte, lnwire.MilliSatoshi,
lnwire.MilliSatoshi, uint32, uint32, models.InboundFee, uint32,
lnwire.ShortChannelID, lnwire.CustomRecords) *LinkError {
return f.checkHtlcForwardResult
}
func (f *mockChannelLink) CheckHtlcTransit(payHash [32]byte,
amt lnwire.MilliSatoshi, timeout uint32,
heightNow uint32, _ lnwire.CustomRecords) *LinkError {
return f.checkHtlcTransitResult
}
func (f *mockChannelLink) Stats() (
uint64, lnwire.MilliSatoshi, lnwire.MilliSatoshi) {
return 0, 0, 0
}
func (f *mockChannelLink) AttachMailBox(mailBox MailBox) {
f.mailBox = mailBox
f.packets = mailBox.PacketOutBox()
mailBox.SetDustClosure(f.getDustClosure())
}
func (f *mockChannelLink) attachFailAliasUpdate(closure func(
sid lnwire.ShortChannelID, incoming bool) *lnwire.ChannelUpdate1) {
f.failAliasUpdate = closure
}
func (f *mockChannelLink) getAliases() []lnwire.ShortChannelID {
return f.aliases
}
func (f *mockChannelLink) isZeroConf() bool {
return f.zeroConf
}
func (f *mockChannelLink) negotiatedAliasFeature() bool {
return f.optionFeature
}
func (f *mockChannelLink) confirmedScid() lnwire.ShortChannelID {
return f.realScid
}
func (f *mockChannelLink) zeroConfConfirmed() bool {
return f.confirmedZC
}
func (f *mockChannelLink) Start() error {
f.mailBox.ResetMessages()
f.mailBox.ResetPackets()
return nil
}
func (f *mockChannelLink) ChanID() lnwire.ChannelID {
return f.chanID
}
func (f *mockChannelLink) ShortChanID() lnwire.ShortChannelID {
return f.shortChanID
}
func (f *mockChannelLink) Bandwidth() lnwire.MilliSatoshi {
return 99999999
}
func (f *mockChannelLink) PeerPubKey() [33]byte {
return f.peer.PubKey()
}
func (f *mockChannelLink) ChannelPoint() wire.OutPoint {
return wire.OutPoint{}
}
func (f *mockChannelLink) Stop() {}
func (f *mockChannelLink) EligibleToForward() bool { return f.eligible }
func (f *mockChannelLink) MayAddOutgoingHtlc(lnwire.MilliSatoshi) error { return nil }
func (f *mockChannelLink) setLiveShortChanID(sid lnwire.ShortChannelID) { f.shortChanID = sid }
func (f *mockChannelLink) IsUnadvertised() bool { return f.unadvertised }
func (f *mockChannelLink) UpdateShortChanID() (lnwire.ShortChannelID, error) {
f.eligible = true
return f.shortChanID, nil
}
func (f *mockChannelLink) EnableAdds(linkDirection LinkDirection) bool {
// TODO(proofofkeags): Implement
return true
}
func (f *mockChannelLink) DisableAdds(linkDirection LinkDirection) bool {
// TODO(proofofkeags): Implement
return true
}
func (f *mockChannelLink) IsFlushing(linkDirection LinkDirection) bool {
// TODO(proofofkeags): Implement
return false
}
func (f *mockChannelLink) OnFlushedOnce(func()) {
// TODO(proofofkeags): Implement
}
func (f *mockChannelLink) OnCommitOnce(LinkDirection, func()) {
// TODO(proofofkeags): Implement
}
func (f *mockChannelLink) InitStfu() <-chan fn.Result[lntypes.ChannelParty] {
// TODO(proofofkeags): Implement
c := make(chan fn.Result[lntypes.ChannelParty], 1)
c <- fn.Errf[lntypes.ChannelParty]("InitStfu not implemented")
return c
}
func (f *mockChannelLink) FundingCustomBlob() fn.Option[tlv.Blob] {
return fn.None[tlv.Blob]()
}
func (f *mockChannelLink) CommitmentCustomBlob() fn.Option[tlv.Blob] {
return fn.None[tlv.Blob]()
}
// AuxBandwidth returns the bandwidth that can be used for a channel,
// expressed in milli-satoshi. This might be different from the regular
// BTC bandwidth for custom channels. This will always return fn.None()
// for a regular (non-custom) channel.
func (f *mockChannelLink) AuxBandwidth(lnwire.MilliSatoshi,
lnwire.ShortChannelID,
fn.Option[tlv.Blob], AuxTrafficShaper) fn.Result[OptionalBandwidth] {
return fn.Ok(OptionalBandwidth{})
}
var _ ChannelLink = (*mockChannelLink)(nil)
const testInvoiceCltvExpiry = 6
type mockInvoiceRegistry struct {
settleChan chan lntypes.Hash
registry *invoices.InvoiceRegistry
}
type mockChainNotifier struct {
chainntnfs.ChainNotifier
}
// RegisterBlockEpochNtfn mocks a successful call to register block
// notifications.
func (m *mockChainNotifier) RegisterBlockEpochNtfn(*chainntnfs.BlockEpoch) (
*chainntnfs.BlockEpochEvent, error) {
return &chainntnfs.BlockEpochEvent{
Cancel: func() {},
}, nil
}
func newMockRegistry(t testing.TB) *mockInvoiceRegistry {
cdb := channeldb.OpenForTesting(t, t.TempDir())
modifierMock := &invoices.MockHtlcModifier{}
registry := invoices.NewRegistry(
cdb,
invoices.NewInvoiceExpiryWatcher(
clock.NewDefaultClock(), 0, 0, nil,
&mockChainNotifier{},
),
&invoices.RegistryConfig{
FinalCltvRejectDelta: 5,
HtlcInterceptor: modifierMock,
},
)
registry.Start()
return &mockInvoiceRegistry{
registry: registry,
}
}
func (i *mockInvoiceRegistry) LookupInvoice(ctx context.Context,
rHash lntypes.Hash) (invoices.Invoice, error) {
return i.registry.LookupInvoice(ctx, rHash)
}
func (i *mockInvoiceRegistry) SettleHodlInvoice(
ctx context.Context, preimage lntypes.Preimage) error {
return i.registry.SettleHodlInvoice(ctx, preimage)
}
func (i *mockInvoiceRegistry) NotifyExitHopHtlc(rhash lntypes.Hash,
amt lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
circuitKey models.CircuitKey, hodlChan chan<- interface{},
wireCustomRecords lnwire.CustomRecords,
payload invoices.Payload) (invoices.HtlcResolution, error) {
event, err := i.registry.NotifyExitHopHtlc(
rhash, amt, expiry, currentHeight, circuitKey,
hodlChan, wireCustomRecords, payload,
)
if err != nil {
return nil, err
}
if i.settleChan != nil {
i.settleChan <- rhash
}
return event, nil
}
func (i *mockInvoiceRegistry) CancelInvoice(ctx context.Context,
payHash lntypes.Hash) error {
return i.registry.CancelInvoice(ctx, payHash)
}
func (i *mockInvoiceRegistry) AddInvoice(ctx context.Context,
invoice invoices.Invoice, paymentHash lntypes.Hash) error {
_, err := i.registry.AddInvoice(ctx, &invoice, paymentHash)
return err
}
func (i *mockInvoiceRegistry) HodlUnsubscribeAll(
subscriber chan<- interface{}) {
i.registry.HodlUnsubscribeAll(subscriber)
}
var _ InvoiceDatabase = (*mockInvoiceRegistry)(nil)
type mockCircuitMap struct {
lookup chan *PaymentCircuit
}
var _ CircuitMap = (*mockCircuitMap)(nil)
func (m *mockCircuitMap) OpenCircuits(...Keystone) error {
return nil
}
func (m *mockCircuitMap) TrimOpenCircuits(chanID lnwire.ShortChannelID,
start uint64) error {
return nil
}
func (m *mockCircuitMap) DeleteCircuits(inKeys ...CircuitKey) error {
return nil
}
func (m *mockCircuitMap) CommitCircuits(
circuit ...*PaymentCircuit) (*CircuitFwdActions, error) {
return nil, nil
}
func (m *mockCircuitMap) CloseCircuit(outKey CircuitKey) (*PaymentCircuit,
error) {
return nil, nil
}
func (m *mockCircuitMap) FailCircuit(inKey CircuitKey) (*PaymentCircuit,
error) {
return nil, nil
}
func (m *mockCircuitMap) LookupCircuit(inKey CircuitKey) *PaymentCircuit {
return <-m.lookup
}
func (m *mockCircuitMap) LookupOpenCircuit(outKey CircuitKey) *PaymentCircuit {
return nil
}
func (m *mockCircuitMap) LookupByPaymentHash(hash [32]byte) []*PaymentCircuit {
return nil
}
func (m *mockCircuitMap) NumPending() int {
return 0
}
func (m *mockCircuitMap) NumOpen() int {
return 0
}
type mockOnionErrorDecryptor struct {
sourceIdx int
message []byte
err error
}
func (m *mockOnionErrorDecryptor) DecryptError(encryptedData []byte) (
*sphinx.DecryptedError, error) {
return &sphinx.DecryptedError{
SenderIdx: m.sourceIdx,
Message: m.message,
}, m.err
}
var _ htlcNotifier = (*mockHTLCNotifier)(nil)
type mockHTLCNotifier struct {
htlcNotifier //nolint:unused
}
func (h *mockHTLCNotifier) NotifyForwardingEvent(key HtlcKey, info HtlcInfo,
eventType HtlcEventType) {
}
func (h *mockHTLCNotifier) NotifyLinkFailEvent(key HtlcKey, info HtlcInfo,
eventType HtlcEventType, linkErr *LinkError,
incoming bool) {
}
func (h *mockHTLCNotifier) NotifyForwardingFailEvent(key HtlcKey,
eventType HtlcEventType) {
}
func (h *mockHTLCNotifier) NotifySettleEvent(key HtlcKey,
preimage lntypes.Preimage, eventType HtlcEventType) {
}
func (h *mockHTLCNotifier) NotifyFinalHtlcEvent(key models.CircuitKey,
info channeldb.FinalHtlcInfo) {
}
package htlcswitch
import (
"fmt"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
)
// htlcPacket is a wrapper around htlc lnwire update, which adds additional
// information which is needed by this package.
type htlcPacket struct {
// incomingChanID is the ID of the channel that we have received an incoming
// HTLC on.
incomingChanID lnwire.ShortChannelID
// outgoingChanID is the ID of the channel that we have offered or will
// offer an outgoing HTLC on.
outgoingChanID lnwire.ShortChannelID
// incomingHTLCID is the ID of the HTLC that we have received from the peer
// on the incoming channel.
incomingHTLCID uint64
// outgoingHTLCID is the ID of the HTLC that we offered to the peer on the
// outgoing channel.
outgoingHTLCID uint64
// sourceRef is used by forwarded htlcPackets to locate incoming Add
// entry in a fwdpkg owned by the incoming link. This value can be nil
// if there is no such entry, e.g. switch initiated payments.
sourceRef *channeldb.AddRef
// destRef is used to locate a settle/fail entry in the outgoing link's
// fwdpkg. If sourceRef is non-nil, this reference should be to a
// settle/fail in response to the sourceRef.
destRef *channeldb.SettleFailRef
// incomingAmount is the value in milli-satoshis that arrived on an
// incoming link.
incomingAmount lnwire.MilliSatoshi
// amount is the value of the HTLC that is being created or modified.
amount lnwire.MilliSatoshi
// htlc lnwire message type of which depends on switch request type.
htlc lnwire.Message
// obfuscator contains the necessary state to allow the switch to wrap
// any forwarded errors in an additional layer of encryption.
obfuscator hop.ErrorEncrypter
// localFailure is set to true if an HTLC fails for a local payment before
// the first hop. In this case, the failure reason is simply encoded, not
// encrypted with any shared secret.
localFailure bool
// linkFailure is non-nil for htlcs that fail at our node. This may
// occur for our own payments which fail on the outgoing link,
// or for forwards which fail in the switch or on the outgoing link.
linkFailure *LinkError
// convertedError is set to true if this is an HTLC fail that was
// created using an UpdateFailMalformedHTLC from the remote party. If
// this is true, then when forwarding this failure packet, we'll need
// to wrap it as if we were the first hop if it's a multi-hop HTLC. If
// it's a direct HTLC, then we'll decode the error as no encryption has
// taken place.
convertedError bool
// hasSource is set to true if the incomingChanID and incomingHTLCID
// fields of a forwarded fail packet are already set and do not need to
// be looked up in the circuit map.
hasSource bool
// isResolution is set to true if this packet was actually an incoming
// resolution message from an outside sub-system. We'll treat these as
// if they emanated directly from the switch. As a result, we'll
// encrypt all errors related to this packet as if we were the first
// hop.
isResolution bool
// circuit holds a reference to an Add's circuit which is persisted in
// the switch during successful forwarding.
circuit *PaymentCircuit
// incomingTimeout is the timeout that the incoming HTLC carried. This
// is the timeout of the HTLC applied to the incoming link.
incomingTimeout uint32
// outgoingTimeout is the timeout of the proposed outgoing HTLC. This
// will be extracted from the hop payload received by the incoming
// link.
outgoingTimeout uint32
// inOnionCustomRecords are user-defined records in the custom type
// range that were included in the onion payload.
inOnionCustomRecords record.CustomSet
// inWireCustomRecords are custom type range TLVs that are included
// in the incoming update_add_htlc wire message.
inWireCustomRecords lnwire.CustomRecords
// originalOutgoingChanID is used when sending back failure messages.
// It is only used for forwarded Adds on option_scid_alias channels.
// This is to avoid possible confusion if a payer uses the public SCID
// but receives a channel_update with the alias SCID. Instead, the
// payer should receive a channel_update with the public SCID.
originalOutgoingChanID lnwire.ShortChannelID
// inboundFee is the fee schedule of the incoming channel.
inboundFee models.InboundFee
}
// inKey returns the circuit key used to identify the incoming htlc.
func (p *htlcPacket) inKey() CircuitKey {
return CircuitKey{
ChanID: p.incomingChanID,
HtlcID: p.incomingHTLCID,
}
}
// outKey returns the circuit key used to identify the outgoing, forwarded htlc.
func (p *htlcPacket) outKey() CircuitKey {
return CircuitKey{
ChanID: p.outgoingChanID,
HtlcID: p.outgoingHTLCID,
}
}
// keystone returns a tuple containing the incoming and outgoing circuit keys.
func (p *htlcPacket) keystone() Keystone {
return Keystone{
InKey: p.inKey(),
OutKey: p.outKey(),
}
}
// String returns a human-readable description of the packet.
func (p *htlcPacket) String() string {
return fmt.Sprintf("keystone=%v, sourceRef=%v, destRef=%v, "+
"incomingAmount=%v, amount=%v, localFailure=%v, hasSource=%v "+
"isResolution=%v", p.keystone(), p.sourceRef, p.destRef,
p.incomingAmount, p.amount, p.localFailure, p.hasSource,
p.isResolution)
}
package htlcswitch
import (
"bytes"
"encoding/binary"
"errors"
"io"
"sync"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/multimutex"
)
var (
// networkResultStoreBucketKey is used for the root level bucket that
// stores the network result for each payment ID.
networkResultStoreBucketKey = []byte("network-result-store-bucket")
// ErrPaymentIDNotFound is an error returned if the given paymentID is
// not found.
ErrPaymentIDNotFound = errors.New("paymentID not found")
// ErrPaymentIDAlreadyExists is returned if we try to write a pending
// payment whose paymentID already exists.
ErrPaymentIDAlreadyExists = errors.New("paymentID already exists")
)
// PaymentResult wraps a decoded result received from the network after a
// payment attempt was made. This is what is eventually handed to the router
// for processing.
type PaymentResult struct {
// Preimage is set by the switch in case a sent HTLC was settled.
Preimage [32]byte
// Error is non-nil in case a HTLC send failed, and the HTLC is now
// irrevocably canceled. If the payment failed during forwarding, this
// error will be a *ForwardingError.
Error error
}
// networkResult is the raw result received from the network after a payment
// attempt has been made. Since the switch doesn't always have the necessary
// data to decode the raw message, we store it together with some meta data,
// and decode it when the router query for the final result.
type networkResult struct {
// msg is the received result. This should be of type UpdateFulfillHTLC
// or UpdateFailHTLC.
msg lnwire.Message
// unencrypted indicates whether the failure encoded in the message is
// unencrypted, and hence doesn't need to be decrypted.
unencrypted bool
// isResolution indicates whether this is a resolution message, in
// which the failure reason might not be included.
isResolution bool
}
// serializeNetworkResult serializes the networkResult.
func serializeNetworkResult(w io.Writer, n *networkResult) error {
return channeldb.WriteElements(w, n.msg, n.unencrypted, n.isResolution)
}
// deserializeNetworkResult deserializes the networkResult.
func deserializeNetworkResult(r io.Reader) (*networkResult, error) {
n := &networkResult{}
if err := channeldb.ReadElements(r,
&n.msg, &n.unencrypted, &n.isResolution,
); err != nil {
return nil, err
}
return n, nil
}
// networkResultStore is a persistent store that stores any results of HTLCs in
// flight on the network. Since payment results are inherently asynchronous, it
// is used as a common access point for senders of HTLCs, to know when a result
// is back. The Switch will checkpoint any received result to the store, and
// the store will keep results and notify the callers about them.
type networkResultStore struct {
backend kvdb.Backend
// results is a map from paymentIDs to channels where subscribers to
// payment results will be notified.
results map[uint64][]chan *networkResult
resultsMtx sync.Mutex
// attemptIDMtx is a multimutex used to make sure the database and
// result subscribers map is consistent for each attempt ID in case of
// concurrent callers.
attemptIDMtx *multimutex.Mutex[uint64]
}
func newNetworkResultStore(db kvdb.Backend) *networkResultStore {
return &networkResultStore{
backend: db,
results: make(map[uint64][]chan *networkResult),
attemptIDMtx: multimutex.NewMutex[uint64](),
}
}
// storeResult stores the networkResult for the given attemptID, and notifies
// any subscribers.
func (store *networkResultStore) storeResult(attemptID uint64,
result *networkResult) error {
// We get a mutex for this attempt ID. This is needed to ensure
// consistency between the database state and the subscribers in case
// of concurrent calls.
store.attemptIDMtx.Lock(attemptID)
defer store.attemptIDMtx.Unlock(attemptID)
log.Debugf("Storing result for attemptID=%v", attemptID)
// Serialize the payment result.
var b bytes.Buffer
if err := serializeNetworkResult(&b, result); err != nil {
return err
}
var attemptIDBytes [8]byte
binary.BigEndian.PutUint64(attemptIDBytes[:], attemptID)
err := kvdb.Batch(store.backend, func(tx kvdb.RwTx) error {
networkResults, err := tx.CreateTopLevelBucket(
networkResultStoreBucketKey,
)
if err != nil {
return err
}
return networkResults.Put(attemptIDBytes[:], b.Bytes())
})
if err != nil {
return err
}
// Now that the result is stored in the database, we can notify any
// active subscribers.
store.resultsMtx.Lock()
for _, res := range store.results[attemptID] {
res <- result
}
delete(store.results, attemptID)
store.resultsMtx.Unlock()
return nil
}
// subscribeResult is used to get the HTLC attempt result for the given attempt
// ID. It returns a channel on which the result will be delivered when ready.
func (store *networkResultStore) subscribeResult(attemptID uint64) (
<-chan *networkResult, error) {
// We get a mutex for this payment ID. This is needed to ensure
// consistency between the database state and the subscribers in case
// of concurrent calls.
store.attemptIDMtx.Lock(attemptID)
defer store.attemptIDMtx.Unlock(attemptID)
log.Debugf("Subscribing to result for attemptID=%v", attemptID)
var (
result *networkResult
resultChan = make(chan *networkResult, 1)
)
err := kvdb.View(store.backend, func(tx kvdb.RTx) error {
var err error
result, err = fetchResult(tx, attemptID)
switch {
// Result not yet available, we will notify once a result is
// available.
case err == ErrPaymentIDNotFound:
return nil
case err != nil:
return err
// The result was found, and will be returned immediately.
default:
return nil
}
}, func() {
result = nil
})
if err != nil {
return nil, err
}
// If the result was found, we can send it on the result channel
// imemdiately.
if result != nil {
resultChan <- result
return resultChan, nil
}
// Otherwise we store the result channel for when the result is
// available.
store.resultsMtx.Lock()
store.results[attemptID] = append(
store.results[attemptID], resultChan,
)
store.resultsMtx.Unlock()
return resultChan, nil
}
// getResult attempts to immediately fetch the result for the given pid from
// the store. If no result is available, ErrPaymentIDNotFound is returned.
func (store *networkResultStore) getResult(pid uint64) (
*networkResult, error) {
var result *networkResult
err := kvdb.View(store.backend, func(tx kvdb.RTx) error {
var err error
result, err = fetchResult(tx, pid)
return err
}, func() {
result = nil
})
if err != nil {
return nil, err
}
return result, nil
}
func fetchResult(tx kvdb.RTx, pid uint64) (*networkResult, error) {
var attemptIDBytes [8]byte
binary.BigEndian.PutUint64(attemptIDBytes[:], pid)
networkResults := tx.ReadBucket(networkResultStoreBucketKey)
if networkResults == nil {
return nil, ErrPaymentIDNotFound
}
// Check whether a result is already available.
resultBytes := networkResults.Get(attemptIDBytes[:])
if resultBytes == nil {
return nil, ErrPaymentIDNotFound
}
// Decode the result we found.
r := bytes.NewReader(resultBytes)
return deserializeNetworkResult(r)
}
// cleanStore removes all entries from the store, except the payment IDs given.
// NOTE: Since every result not listed in the keep map will be deleted, care
// should be taken to ensure no new payment attempts are being made
// concurrently while this process is ongoing, as its result might end up being
// deleted.
func (store *networkResultStore) cleanStore(keep map[uint64]struct{}) error {
return kvdb.Update(store.backend, func(tx kvdb.RwTx) error {
networkResults, err := tx.CreateTopLevelBucket(
networkResultStoreBucketKey,
)
if err != nil {
return err
}
// Iterate through the bucket, deleting all items not in the
// keep map.
var toClean [][]byte
if err := networkResults.ForEach(func(k, _ []byte) error {
pid := binary.BigEndian.Uint64(k)
if _, ok := keep[pid]; ok {
return nil
}
toClean = append(toClean, k)
return nil
}); err != nil {
return err
}
for _, k := range toClean {
err := networkResults.Delete(k)
if err != nil {
return err
}
}
if len(toClean) > 0 {
log.Infof("Removed %d stale entries from network "+
"result store", len(toClean))
}
return nil
}, func() {})
}
package htlcswitch
import (
"fmt"
"sync"
"time"
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// ErrInvalidStfu indicates that the Stfu we have received is invalid.
// This can happen in instances where we have not sent Stfu but we have
// received one with the initiator field set to false.
ErrInvalidStfu = fmt.Errorf("stfu received is invalid")
// ErrStfuAlreadySent indicates that this channel has already sent an
// Stfu message for this negotiation.
ErrStfuAlreadySent = fmt.Errorf("stfu already sent")
// ErrStfuAlreadyRcvd indicates that this channel has already received
// an Stfu message for this negotiation.
ErrStfuAlreadyRcvd = fmt.Errorf("stfu already received")
// ErrNoQuiescenceInitiator indicates that the caller has requested the
// quiescence initiator for a channel that is not yet quiescent.
ErrNoQuiescenceInitiator = fmt.Errorf(
"indeterminate quiescence initiator: channel is not quiescent",
)
// ErrPendingRemoteUpdates indicates that we have received an Stfu while
// the remote party has issued updates that are not yet bilaterally
// committed.
ErrPendingRemoteUpdates = fmt.Errorf(
"stfu received with pending remote updates",
)
// ErrPendingLocalUpdates indicates that we are attempting to send an
// Stfu while we have issued updates that are not yet bilaterally
// committed.
ErrPendingLocalUpdates = fmt.Errorf(
"stfu send attempted with pending local updates",
)
// ErrQuiescenceTimeout indicates that the quiescer has been quiesced
// beyond the allotted time.
ErrQuiescenceTimeout = fmt.Errorf(
"quiescence timeout",
)
)
const defaultQuiescenceTimeout = 30 * time.Second
type StfuReq = fn.Req[fn.Unit, fn.Result[lntypes.ChannelParty]]
// Quiescer is the public interface of the quiescence mechanism. Callers of the
// quiescence API should not need any methods besides the ones detailed here.
type Quiescer interface {
// IsQuiescent returns true if the state machine has been driven all the
// way to completion. If this returns true, processes that depend on
// channel quiescence may proceed.
IsQuiescent() bool
// QuiescenceInitiator determines which ChannelParty is the initiator of
// quiescence for the purposes of downstream protocols. If the channel
// is not currently quiescent, this method will return
// ErrNoDownstreamLeader.
QuiescenceInitiator() fn.Result[lntypes.ChannelParty]
// InitStfu instructs the quiescer that we intend to begin a quiescence
// negotiation where we are the initiator. We don't yet send stfu yet
// because we need to wait for the link to give us a valid opportunity
// to do so.
InitStfu(req StfuReq)
// RecvStfu is called when we receive an Stfu message from the remote.
RecvStfu(stfu lnwire.Stfu) error
// CanRecvUpdates returns true if we haven't yet received an Stfu which
// would mark the end of the remote's ability to send updates.
CanRecvUpdates() bool
// CanSendUpdates returns true if we haven't yet sent an Stfu which
// would mark the end of our ability to send updates.
CanSendUpdates() bool
// SendOwedStfu sends Stfu if it owes one. It returns an error if the
// state machine is in an invalid state.
SendOwedStfu() error
// OnResume accepts a no return closure that will run when the quiescer
// is resumed.
OnResume(hook func())
// Resume runs all of the deferred actions that have accumulated while
// the channel has been quiescent and then resets the quiescer state to
// its initial state.
Resume()
}
// QuiescerCfg is a config structure used to initialize a quiescer giving it the
// appropriate functionality to interact with the channel state that the
// quiescer must syncrhonize with.
type QuiescerCfg struct {
// chanID marks what channel we are managing the state machine for. This
// is important because the quiescer needs to know the ChannelID to
// construct the Stfu message.
chanID lnwire.ChannelID
// channelInitiator indicates which ChannelParty originally opened the
// channel. This is used to break ties when both sides of the channel
// send Stfu claiming to be the initiator.
channelInitiator lntypes.ChannelParty
// sendMsg is a function that can be used to send an Stfu message over
// the wire.
sendMsg func(lnwire.Stfu) error
// timeoutDuration is the Duration that we will wait from the moment the
// channel is considered quiescent before we call the onTimeout function
timeoutDuration time.Duration
// onTimeout is a function that will be called in the event that the
// Quiescer has not been resumed before the timeout is reached. If
// Quiescer.Resume is called before the timeout has been raeached, then
// onTimeout will not be called until the quiescer reaches a quiescent
// state again.
onTimeout func()
}
// QuiescerLive is a state machine that tracks progression through the
// quiescence protocol.
type QuiescerLive struct {
cfg QuiescerCfg
// log is a quiescer-scoped logging instance.
log btclog.Logger
// localInit indicates whether our path through this state machine was
// initiated by our node. This can be true or false independently of
// remoteInit.
localInit bool
// remoteInit indicates whether we received Stfu from our peer where the
// message indicated that the remote node believes it was the initiator.
// This can be true or false independently of localInit.
remoteInit bool
// sent tracks whether or not we have emitted Stfu for sending.
sent bool
// received tracks whether or not we have received Stfu from our peer.
received bool
// activeQuiescenceRequest is a possibly None Request that we should
// resolve when we complete quiescence.
activeQuiescenceReq fn.Option[StfuReq]
// resumeQueue is a slice of hooks that will be called when the quiescer
// is resumed. These are actions that needed to be deferred while the
// channel was quiescent.
resumeQueue []func()
// timeoutTimer is a field that is used to hold onto the timeout job
// when we reach quiescence.
timeoutTimer *time.Timer
sync.RWMutex
}
// NewQuiescer creates a new quiescer for the given channel.
func NewQuiescer(cfg QuiescerCfg) Quiescer {
logPrefix := fmt.Sprintf("Quiescer(%v):", cfg.chanID)
return &QuiescerLive{
cfg: cfg,
log: log.WithPrefix(logPrefix),
}
}
// RecvStfu is called when we receive an Stfu message from the remote.
func (q *QuiescerLive) RecvStfu(msg lnwire.Stfu) error {
q.Lock()
defer q.Unlock()
return q.recvStfu(msg)
}
// recvStfu is called when we receive an Stfu message from the remote.
func (q *QuiescerLive) recvStfu(msg lnwire.Stfu) error {
// At the time of this writing, this check that we have already received
// an Stfu is not strictly necessary, according to the specification.
// However, it is fishy if we do and it is unclear how we should handle
// such a case so we will err on the side of caution.
if q.received {
return fmt.Errorf("%w for channel %v", ErrStfuAlreadyRcvd,
q.cfg.chanID)
}
// We need to check that the Stfu we are receiving is valid.
if !q.sent && !msg.Initiator {
return fmt.Errorf("%w for channel %v", ErrInvalidStfu,
q.cfg.chanID)
}
if !q.canRecvStfu() {
return fmt.Errorf("%w for channel %v", ErrPendingRemoteUpdates,
q.cfg.chanID)
}
q.received = true
// If the remote party sets the initiator bit to true then we will
// remember that they are making a claim to the initiator role. This
// does not necessarily mean they will get it, though.
q.remoteInit = msg.Initiator
// Since we just received an Stfu, we may have a newly quiesced state.
// If so, we will try to resolve any outstanding StfuReqs.
q.tryResolveStfuReq()
if q.isQuiescent() {
q.startTimeout()
}
return nil
}
// MakeStfu is called when we are ready to send an Stfu message. It returns the
// Stfu message to be sent.
func (q *QuiescerLive) MakeStfu() fn.Result[lnwire.Stfu] {
q.RLock()
defer q.RUnlock()
return q.makeStfu()
}
// makeStfu is called when we are ready to send an Stfu message. It returns the
// Stfu message to be sent.
func (q *QuiescerLive) makeStfu() fn.Result[lnwire.Stfu] {
if q.sent {
return fn.Errf[lnwire.Stfu]("%w for channel %v",
ErrStfuAlreadySent, q.cfg.chanID)
}
if !q.canSendStfu() {
return fn.Errf[lnwire.Stfu]("%w for channel %v",
ErrPendingLocalUpdates, q.cfg.chanID)
}
stfu := lnwire.Stfu{
ChanID: q.cfg.chanID,
Initiator: q.localInit,
}
return fn.Ok(stfu)
}
// OweStfu returns true if we owe the other party an Stfu. We owe the remote an
// Stfu when we have received but not yet sent an Stfu, or we are the initiator
// but have not yet sent an Stfu.
func (q *QuiescerLive) OweStfu() bool {
q.RLock()
defer q.RUnlock()
return q.oweStfu()
}
// oweStfu returns true if we owe the other party an Stfu. We owe the remote an
// Stfu when we have received but not yet sent an Stfu, or we are the initiator
// but have not yet sent an Stfu.
func (q *QuiescerLive) oweStfu() bool {
return (q.received || q.localInit) && !q.sent
}
// NeedStfu returns true if the remote owes us an Stfu. They owe us an Stfu when
// we have sent but not yet received an Stfu.
func (q *QuiescerLive) NeedStfu() bool {
q.RLock()
defer q.RUnlock()
return q.needStfu()
}
// needStfu returns true if the remote owes us an Stfu. They owe us an Stfu when
// we have sent but not yet received an Stfu.
func (q *QuiescerLive) needStfu() bool {
q.RLock()
defer q.RUnlock()
return q.sent && !q.received
}
// IsQuiescent returns true if the state machine has been driven all the way to
// completion. If this returns true, processes that depend on channel quiescence
// may proceed.
func (q *QuiescerLive) IsQuiescent() bool {
q.RLock()
defer q.RUnlock()
return q.isQuiescent()
}
// isQuiescent returns true if the state machine has been driven all the way to
// completion. If this returns true, processes that depend on channel quiescence
// may proceed.
func (q *QuiescerLive) isQuiescent() bool {
return q.sent && q.received
}
// QuiescenceInitiator determines which ChannelParty is the initiator of
// quiescence for the purposes of downstream protocols. If the channel is not
// currently quiescent, this method will return ErrNoQuiescenceInitiator.
func (q *QuiescerLive) QuiescenceInitiator() fn.Result[lntypes.ChannelParty] {
q.RLock()
defer q.RUnlock()
return q.quiescenceInitiator()
}
// quiescenceInitiator determines which ChannelParty is the initiator of
// quiescence for the purposes of downstream protocols. If the channel is not
// currently quiescent, this method will return ErrNoQuiescenceInitiator.
func (q *QuiescerLive) quiescenceInitiator() fn.Result[lntypes.ChannelParty] {
switch {
case !q.isQuiescent():
return fn.Err[lntypes.ChannelParty](ErrNoQuiescenceInitiator)
case q.localInit && q.remoteInit:
// In the case of a tie, the channel initiator wins.
return fn.Ok(q.cfg.channelInitiator)
case q.localInit:
return fn.Ok(lntypes.Local)
case q.remoteInit:
return fn.Ok(lntypes.Remote)
}
// unreachable
return fn.Err[lntypes.ChannelParty](ErrNoQuiescenceInitiator)
}
// CanSendUpdates returns true if we haven't yet sent an Stfu which would mark
// the end of our ability to send updates.
func (q *QuiescerLive) CanSendUpdates() bool {
q.RLock()
defer q.RUnlock()
return q.canSendUpdates()
}
// canSendUpdates returns true if we haven't yet sent an Stfu which would mark
// the end of our ability to send updates.
func (q *QuiescerLive) canSendUpdates() bool {
return !q.sent && !q.localInit
}
// CanRecvUpdates returns true if we haven't yet received an Stfu which would
// mark the end of the remote's ability to send updates.
func (q *QuiescerLive) CanRecvUpdates() bool {
q.RLock()
defer q.RUnlock()
return q.canRecvUpdates()
}
// canRecvUpdates returns true if we haven't yet received an Stfu which would
// mark the end of the remote's ability to send updates.
func (q *QuiescerLive) canRecvUpdates() bool {
return !q.received
}
// CanSendStfu returns true if we can send an Stfu.
func (q *QuiescerLive) CanSendStfu(numPendingLocalUpdates uint64) bool {
q.RLock()
defer q.RUnlock()
return q.canSendStfu()
}
// canSendStfu returns true if we can send an Stfu.
func (q *QuiescerLive) canSendStfu() bool {
return !q.sent
}
// CanRecvStfu returns true if we can receive an Stfu.
func (q *QuiescerLive) CanRecvStfu() bool {
q.RLock()
defer q.RUnlock()
return q.canRecvStfu()
}
// canRecvStfu returns true if we can receive an Stfu.
func (q *QuiescerLive) canRecvStfu() bool {
return !q.received
}
// SendOwedStfu sends Stfu if it owes one. It returns an error if the state
// machine is in an invalid state.
func (q *QuiescerLive) SendOwedStfu() error {
q.Lock()
defer q.Unlock()
return q.sendOwedStfu()
}
// sendOwedStfu sends Stfu if it owes one. It returns an error if the state
// machine is in an invalid state.
func (q *QuiescerLive) sendOwedStfu() error {
if !q.oweStfu() || !q.canSendStfu() {
return nil
}
err := q.makeStfu().Sink(q.cfg.sendMsg)
if err == nil {
q.sent = true
// Since we just sent an Stfu, we may have a newly quiesced
// state. If so, we will try to resolve any outstanding
// StfuReqs.
q.tryResolveStfuReq()
if q.isQuiescent() {
q.startTimeout()
}
}
return err
}
// TryResolveStfuReq attempts to resolve the active quiescence request if the
// state machine has reached a quiescent state.
func (q *QuiescerLive) TryResolveStfuReq() {
q.Lock()
defer q.Unlock()
q.tryResolveStfuReq()
}
// tryResolveStfuReq attempts to resolve the active quiescence request if the
// state machine has reached a quiescent state.
func (q *QuiescerLive) tryResolveStfuReq() {
q.activeQuiescenceReq.WhenSome(
func(req StfuReq) {
if q.isQuiescent() {
req.Resolve(q.quiescenceInitiator())
q.activeQuiescenceReq = fn.None[StfuReq]()
}
},
)
}
// InitStfu instructs the quiescer that we intend to begin a quiescence
// negotiation where we are the initiator. We don't yet send stfu yet because
// we need to wait for the link to give us a valid opportunity to do so.
func (q *QuiescerLive) InitStfu(req StfuReq) {
q.Lock()
defer q.Unlock()
q.initStfu(req)
}
// initStfu instructs the quiescer that we intend to begin a quiescence
// negotiation where we are the initiator. We don't yet send stfu yet because
// we need to wait for the link to give us a valid opportunity to do so.
func (q *QuiescerLive) initStfu(req StfuReq) {
if q.localInit {
req.Resolve(fn.Errf[lntypes.ChannelParty](
"quiescence already requested",
))
return
}
q.localInit = true
q.activeQuiescenceReq = fn.Some(req)
}
// OnResume accepts a no return closure that will run when the quiescer is
// resumed.
func (q *QuiescerLive) OnResume(hook func()) {
q.Lock()
defer q.Unlock()
q.onResume(hook)
}
// onResume accepts a no return closure that will run when the quiescer is
// resumed.
func (q *QuiescerLive) onResume(hook func()) {
q.resumeQueue = append(q.resumeQueue, hook)
}
// Resume runs all of the deferred actions that have accumulated while the
// channel has been quiescent and then resets the quiescer state to its initial
// state.
func (q *QuiescerLive) Resume() {
q.Lock()
defer q.Unlock()
q.resume()
}
// resume runs all of the deferred actions that have accumulated while the
// channel has been quiescent and then resets the quiescer state to its initial
// state.
func (q *QuiescerLive) resume() {
q.log.Debug("quiescence terminated, resuming htlc traffic")
// since we are resuming we want to cancel the quiescence timeout
// action.
q.cancelTimeout()
for _, hook := range q.resumeQueue {
hook()
}
q.localInit = false
q.remoteInit = false
q.sent = false
q.received = false
q.resumeQueue = nil
}
// startTimeout starts the timeout function that fires if the quiescer remains
// in a quiesced state for too long. If this function is called multiple times
// only the last one will have an effect.
func (q *QuiescerLive) startTimeout() {
if q.cfg.onTimeout == nil {
return
}
old := q.timeoutTimer
q.timeoutTimer = time.AfterFunc(q.cfg.timeoutDuration, q.cfg.onTimeout)
if old != nil {
old.Stop()
}
}
// cancelTimeout cancels the timeout function that would otherwise fire if the
// quiescer remains in a quiesced state too long. If this function is called
// before startTimeout or after another call to cancelTimeout, the effect will
// be a noop.
func (q *QuiescerLive) cancelTimeout() {
if q.timeoutTimer != nil {
q.timeoutTimer.Stop()
q.timeoutTimer = nil
}
}
type quiescerNoop struct{}
var _ Quiescer = (*quiescerNoop)(nil)
func (q *quiescerNoop) InitStfu(req StfuReq) {
req.Resolve(fn.Errf[lntypes.ChannelParty]("quiescence not supported"))
}
func (q *quiescerNoop) RecvStfu(_ lnwire.Stfu) error { return nil }
func (q *quiescerNoop) CanRecvUpdates() bool { return true }
func (q *quiescerNoop) CanSendUpdates() bool { return true }
func (q *quiescerNoop) SendOwedStfu() error { return nil }
func (q *quiescerNoop) IsQuiescent() bool { return false }
func (q *quiescerNoop) OnResume(hook func()) { hook() }
func (q *quiescerNoop) Resume() {}
func (q *quiescerNoop) QuiescenceInitiator() fn.Result[lntypes.ChannelParty] {
return fn.Err[lntypes.ChannelParty](ErrNoQuiescenceInitiator)
}
package htlcswitch
import (
"bytes"
"io"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// resBucketKey is used for the root level bucket that stores the
// CircuitKey -> ResolutionMsg mapping.
resBucketKey = []byte("resolution-store-bucket-key")
// errResMsgNotFound is used to let callers know that the resolution
// message was not found for the given CircuitKey. This is used in the
// checkResolutionMsg function.
errResMsgNotFound = errors.New("resolution message not found")
)
// resolutionStore contains ResolutionMsgs received from the contractcourt. The
// Switch deletes these from the store when the underlying circuit has been
// removed via DeleteCircuits. If the circuit hasn't been deleted, the Switch
// will dispatch the ResolutionMsg to a link if this was a multi-hop HTLC or to
// itself if the Switch initiated the payment.
type resolutionStore struct {
backend kvdb.Backend
}
func newResolutionStore(db kvdb.Backend) *resolutionStore {
return &resolutionStore{
backend: db,
}
}
// addResolutionMsg persists a ResolutionMsg to the resolutionStore.
func (r *resolutionStore) addResolutionMsg(
resMsg *contractcourt.ResolutionMsg) error {
// The outKey will be the database key.
outKey := &CircuitKey{
ChanID: resMsg.SourceChan,
HtlcID: resMsg.HtlcIndex,
}
var resBuf bytes.Buffer
if err := serializeResolutionMsg(&resBuf, resMsg); err != nil {
return err
}
err := kvdb.Update(r.backend, func(tx kvdb.RwTx) error {
resBucket, err := tx.CreateTopLevelBucket(resBucketKey)
if err != nil {
return err
}
return resBucket.Put(outKey.Bytes(), resBuf.Bytes())
}, func() {})
if err != nil {
return err
}
return nil
}
// checkResolutionMsg returns nil if the resolution message is found in the
// store. It returns an error if no resolution message was found for the
// passed outKey or if a database error occurred.
func (r *resolutionStore) checkResolutionMsg(outKey *CircuitKey) error {
err := kvdb.View(r.backend, func(tx kvdb.RTx) error {
resBucket := tx.ReadBucket(resBucketKey)
if resBucket == nil {
// Return an error if the bucket doesn't exist.
return errResMsgNotFound
}
msg := resBucket.Get(outKey.Bytes())
if msg == nil {
// Return the not found error since no message exists
// for this CircuitKey.
return errResMsgNotFound
}
// Return nil to indicate that the message was found.
return nil
}, func() {})
if err != nil {
return err
}
return nil
}
// fetchAllResolutionMsg returns a slice of all stored ResolutionMsgs. This is
// used by the Switch on start-up.
func (r *resolutionStore) fetchAllResolutionMsg() (
[]*contractcourt.ResolutionMsg, error) {
var msgs []*contractcourt.ResolutionMsg
err := kvdb.View(r.backend, func(tx kvdb.RTx) error {
resBucket := tx.ReadBucket(resBucketKey)
if resBucket == nil {
return nil
}
return resBucket.ForEach(func(k, v []byte) error {
kr := bytes.NewReader(k)
outKey := &CircuitKey{}
if err := outKey.Decode(kr); err != nil {
return err
}
vr := bytes.NewReader(v)
resMsg, err := deserializeResolutionMsg(vr)
if err != nil {
return err
}
// Set the CircuitKey values on the ResolutionMsg.
resMsg.SourceChan = outKey.ChanID
resMsg.HtlcIndex = outKey.HtlcID
msgs = append(msgs, resMsg)
return nil
})
}, func() {
msgs = nil
})
if err != nil {
return nil, err
}
return msgs, nil
}
// deleteResolutionMsg removes a ResolutionMsg with the passed-in CircuitKey.
func (r *resolutionStore) deleteResolutionMsg(outKey *CircuitKey) error {
err := kvdb.Update(r.backend, func(tx kvdb.RwTx) error {
resBucket, err := tx.CreateTopLevelBucket(resBucketKey)
if err != nil {
return err
}
return resBucket.Delete(outKey.Bytes())
}, func() {})
return err
}
// serializeResolutionMsg writes part of a ResolutionMsg to the passed
// io.Writer.
func serializeResolutionMsg(w io.Writer,
resMsg *contractcourt.ResolutionMsg) error {
isFail := resMsg.Failure != nil
if err := channeldb.WriteElement(w, isFail); err != nil {
return err
}
// If this is a failure message, then we're done serializing.
if isFail {
return nil
}
// Else this is a settle message, and we need to write the preimage.
return channeldb.WriteElement(w, *resMsg.PreImage)
}
// deserializeResolutionMsg reads part of a ResolutionMsg from the passed
// io.Reader.
func deserializeResolutionMsg(r io.Reader) (*contractcourt.ResolutionMsg,
error) {
resMsg := &contractcourt.ResolutionMsg{}
var isFail bool
if err := channeldb.ReadElements(r, &isFail); err != nil {
return nil, err
}
// If a failure resolution msg was stored, set the Failure field.
if isFail {
failureMsg := &lnwire.FailPermanentChannelFailure{}
resMsg.Failure = failureMsg
return resMsg, nil
}
var preimage [32]byte
resMsg.PreImage = &preimage
// Else this is a settle resolution msg and we will read the preimage.
if err := channeldb.ReadElement(r, resMsg.PreImage); err != nil {
return nil, err
}
return resMsg, nil
}
package htlcswitch
import (
"sync"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/kvdb"
)
// defaultSequenceBatchSize specifies the window of sequence numbers that are
// allocated for each write to disk made by the sequencer.
const defaultSequenceBatchSize = 1000
// Sequencer emits sequence numbers for locally initiated HTLCs. These are
// only used internally for tracking pending payments, however they must be
// unique in order to avoid circuit key collision in the circuit map.
type Sequencer interface {
// NextID returns a unique sequence number for each invocation.
NextID() (uint64, error)
}
var (
// nextPaymentIDKey identifies the bucket that will keep track of the
// persistent sequence numbers for payments.
nextPaymentIDKey = []byte("next-payment-id-key")
// ErrSequencerCorrupted signals that the persistence engine was not
// initialized, or has been corrupted since startup.
ErrSequencerCorrupted = errors.New(
"sequencer database has been corrupted")
)
// persistentSequencer is a concrete implementation of IDGenerator, that uses
// channeldb to allocate sequence numbers.
type persistentSequencer struct {
db *channeldb.DB
mu sync.Mutex
nextID uint64
horizonID uint64
}
// NewPersistentSequencer initializes a new sequencer using a channeldb backend.
func NewPersistentSequencer(db *channeldb.DB) (Sequencer, error) {
g := &persistentSequencer{
db: db,
}
// Ensure the database bucket is created before any updates are
// performed.
if err := g.initDB(); err != nil {
return nil, err
}
return g, nil
}
// NextID returns a unique sequence number for every invocation, persisting the
// assignment to avoid reuse.
func (s *persistentSequencer) NextID() (uint64, error) {
// nextID will be the unique sequence number returned if no errors are
// encountered.
var nextID uint64
// If our sequence batch has not been exhausted, we can allocate the
// next identifier in the range.
s.mu.Lock()
defer s.mu.Unlock()
if s.nextID < s.horizonID {
nextID = s.nextID
s.nextID++
return nextID, nil
}
// Otherwise, our sequence batch has been exhausted. We use the last
// known sequence number on disk to mark the beginning of the next
// sequence batch, and allocate defaultSequenceBatchSize (1000) at a
// time.
//
// NOTE: This also will happen on the first invocation after startup,
// i.e. when nextID and horizonID are both 0. The next sequence batch to be
// allocated will start from the last known tip on disk, which is fine
// as we only require uniqueness of the allocated numbers.
var nextHorizonID uint64
if err := kvdb.Update(s.db, func(tx kvdb.RwTx) error {
nextIDBkt := tx.ReadWriteBucket(nextPaymentIDKey)
if nextIDBkt == nil {
return ErrSequencerCorrupted
}
nextID = nextIDBkt.Sequence()
nextHorizonID = nextID + defaultSequenceBatchSize
// Cannot fail when used in Update.
nextIDBkt.SetSequence(nextHorizonID)
return nil
}, func() {
nextHorizonID = 0
}); err != nil {
return 0, err
}
// Never assign index zero, to avoid collisions with the EmptyKeystone.
if nextID == 0 {
nextID++
}
// If our batch sequence allocation succeed, update our in-memory values
// so we can continue to allocate sequence numbers without hitting disk.
// The nextID is incremented by one in memory so the in can be used
// issued directly on the next invocation.
s.nextID = nextID + 1
s.horizonID = nextHorizonID
return nextID, nil
}
// initDB populates the bucket used to generate payment sequence numbers.
func (s *persistentSequencer) initDB() error {
return kvdb.Update(s.db, func(tx kvdb.RwTx) error {
_, err := tx.CreateTopLevelBucket(nextPaymentIDKey)
return err
}, func() {})
}
package htlcswitch
import (
"bytes"
"context"
"errors"
"fmt"
"math/rand"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/ticker"
)
const (
// DefaultFwdEventInterval is the duration between attempts to flush
// pending forwarding events to disk.
DefaultFwdEventInterval = 15 * time.Second
// DefaultLogInterval is the duration between attempts to log statistics
// about forwarding events.
DefaultLogInterval = 10 * time.Second
// DefaultAckInterval is the duration between attempts to ack any settle
// fails in a forwarding package.
DefaultAckInterval = 15 * time.Second
// DefaultMailboxDeliveryTimeout is the duration after which Adds will
// be cancelled if they could not get added to an outgoing commitment.
DefaultMailboxDeliveryTimeout = time.Minute
)
var (
// ErrChannelLinkNotFound is used when channel link hasn't been found.
ErrChannelLinkNotFound = errors.New("channel link not found")
// ErrDuplicateAdd signals that the ADD htlc was already forwarded
// through the switch and is locked into another commitment txn.
ErrDuplicateAdd = errors.New("duplicate add HTLC detected")
// ErrUnknownErrorDecryptor signals that we were unable to locate the
// error decryptor for this payment. This is likely due to restarting
// the daemon.
ErrUnknownErrorDecryptor = errors.New("unknown error decryptor")
// ErrSwitchExiting signaled when the switch has received a shutdown
// request.
ErrSwitchExiting = errors.New("htlcswitch shutting down")
// ErrNoLinksFound is an error returned when we attempt to retrieve the
// active links in the switch for a specific destination.
ErrNoLinksFound = errors.New("no channel links found")
// ErrUnreadableFailureMessage is returned when the failure message
// cannot be decrypted.
ErrUnreadableFailureMessage = errors.New("unreadable failure message")
// ErrLocalAddFailed signals that the ADD htlc for a local payment
// failed to be processed.
ErrLocalAddFailed = errors.New("local add HTLC failed")
// errFeeExposureExceeded is only surfaced to callers of SendHTLC and
// signals that sending the HTLC would exceed the outgoing link's fee
// exposure threshold.
errFeeExposureExceeded = errors.New("fee exposure exceeded")
// DefaultMaxFeeExposure is the default threshold after which we'll
// fail payments if they increase our fee exposure. This is currently
// set to 500m msats.
DefaultMaxFeeExposure = lnwire.MilliSatoshi(500_000_000)
)
// plexPacket encapsulates switch packet and adds error channel to receive
// error from request handler.
type plexPacket struct {
pkt *htlcPacket
err chan error
}
// ChanClose represents a request which close a particular channel specified by
// its id.
type ChanClose struct {
// CloseType is a variable which signals the type of channel closure the
// peer should execute.
CloseType contractcourt.ChannelCloseType
// ChanPoint represent the id of the channel which should be closed.
ChanPoint *wire.OutPoint
// TargetFeePerKw is the ideal fee that was specified by the caller.
// This value is only utilized if the closure type is CloseRegular.
// This will be the starting offered fee when the fee negotiation
// process for the cooperative closure transaction kicks off.
TargetFeePerKw chainfee.SatPerKWeight
// MaxFee is the highest fee the caller is willing to pay.
//
// NOTE: This field is only respected if the caller is the initiator of
// the channel.
MaxFee chainfee.SatPerKWeight
// DeliveryScript is an optional delivery script to pay funds out to.
DeliveryScript lnwire.DeliveryAddress
// Updates is used by request creator to receive the notifications about
// execution of the close channel request.
Updates chan interface{}
// Err is used by request creator to receive request execution error.
Err chan error
// Ctx is a context linked to the lifetime of the caller.
Ctx context.Context //nolint:containedctx
}
// Config defines the configuration for the service. ALL elements within the
// configuration MUST be non-nil for the service to carry out its duties.
type Config struct {
// FwdingLog is an interface that will be used by the switch to log
// forwarding events. A forwarding event happens each time a payment
// circuit is successfully completed. So when we forward an HTLC, and a
// settle is eventually received.
FwdingLog ForwardingLog
// LocalChannelClose kicks-off the workflow to execute a cooperative or
// forced unilateral closure of the channel initiated by a local
// subsystem.
LocalChannelClose func(pubKey []byte, request *ChanClose)
// DB is the database backend that will be used to back the switch's
// persistent circuit map.
DB kvdb.Backend
// FetchAllOpenChannels is a function that fetches all currently open
// channels from the channel database.
FetchAllOpenChannels func() ([]*channeldb.OpenChannel, error)
// FetchAllChannels is a function that fetches all pending open, open,
// and waiting close channels from the database.
FetchAllChannels func() ([]*channeldb.OpenChannel, error)
// FetchClosedChannels is a function that fetches all closed channels
// from the channel database.
FetchClosedChannels func(
pendingOnly bool) ([]*channeldb.ChannelCloseSummary, error)
// SwitchPackager provides access to the forwarding packages of all
// active channels. This gives the switch the ability to read arbitrary
// forwarding packages, and ack settles and fails contained within them.
SwitchPackager channeldb.FwdOperator
// ExtractErrorEncrypter is an interface allowing switch to reextract
// error encrypters stored in the circuit map on restarts, since they
// are not stored directly within the database.
ExtractErrorEncrypter hop.ErrorEncrypterExtracter
// FetchLastChannelUpdate retrieves the latest routing policy for a
// target channel. This channel will typically be the outgoing channel
// specified when we receive an incoming HTLC. This will be used to
// provide payment senders our latest policy when sending encrypted
// error messages.
FetchLastChannelUpdate func(lnwire.ShortChannelID) (
*lnwire.ChannelUpdate1, error)
// Notifier is an instance of a chain notifier that we'll use to signal
// the switch when a new block has arrived.
Notifier chainntnfs.ChainNotifier
// HtlcNotifier is an instance of a htlcNotifier which we will pipe htlc
// events through.
HtlcNotifier htlcNotifier
// FwdEventTicker is a signal that instructs the htlcswitch to flush any
// pending forwarding events.
FwdEventTicker ticker.Ticker
// LogEventTicker is a signal instructing the htlcswitch to log
// aggregate stats about it's forwarding during the last interval.
LogEventTicker ticker.Ticker
// AckEventTicker is a signal instructing the htlcswitch to ack any settle
// fails in forwarding packages.
AckEventTicker ticker.Ticker
// AllowCircularRoute is true if the user has configured their node to
// allow forwards that arrive and depart our node over the same channel.
AllowCircularRoute bool
// RejectHTLC is a flag that instructs the htlcswitch to reject any
// HTLCs that are not from the source hop.
RejectHTLC bool
// Clock is a time source for the switch.
Clock clock.Clock
// MailboxDeliveryTimeout is the interval after which Adds will be
// cancelled if they have not been yet been delivered to a link. The
// computed deadline will expiry this long after the Adds are added to
// a mailbox via AddPacket.
MailboxDeliveryTimeout time.Duration
// MaxFeeExposure is the threshold in milli-satoshis after which we'll
// fail incoming or outgoing payments for a particular channel.
MaxFeeExposure lnwire.MilliSatoshi
// SignAliasUpdate is used when sending FailureMessages backwards for
// option_scid_alias channels. This avoids a potential privacy leak by
// replacing the public, confirmed SCID with the alias in the
// ChannelUpdate.
SignAliasUpdate func(u *lnwire.ChannelUpdate1) (*ecdsa.Signature,
error)
// IsAlias returns whether or not a given SCID is an alias.
IsAlias func(scid lnwire.ShortChannelID) bool
}
// Switch is the central messaging bus for all incoming/outgoing HTLCs.
// Connected peers with active channels are treated as named interfaces which
// refer to active channels as links. A link is the switch's message
// communication point with the goroutine that manages an active channel. New
// links are registered each time a channel is created, and unregistered once
// the channel is closed. The switch manages the hand-off process for multi-hop
// HTLCs, forwarding HTLCs initiated from within the daemon, and finally
// notifies users local-systems concerning their outstanding payment requests.
type Switch struct {
started int32 // To be used atomically.
shutdown int32 // To be used atomically.
// bestHeight is the best known height of the main chain. The links will
// be used this information to govern decisions based on HTLC timeouts.
// This will be retrieved by the registered links atomically.
bestHeight uint32
wg sync.WaitGroup
quit chan struct{}
// cfg is a copy of the configuration struct that the htlc switch
// service was initialized with.
cfg *Config
// networkResults stores the results of payments initiated by the user.
// The store is used to later look up the payments and notify the
// user of the result when they are complete. Each payment attempt
// should be given a unique integer ID when it is created, otherwise
// results might be overwritten.
networkResults *networkResultStore
// circuits is storage for payment circuits which are used to
// forward the settle/fail htlc updates back to the add htlc initiator.
circuits CircuitMap
// mailOrchestrator manages the lifecycle of mailboxes used throughout
// the switch, and facilitates delayed delivery of packets to links that
// later come online.
mailOrchestrator *mailOrchestrator
// indexMtx is a read/write mutex that protects the set of indexes
// below.
indexMtx sync.RWMutex
// pendingLinkIndex holds links that have not had their final, live
// short_chan_id assigned.
pendingLinkIndex map[lnwire.ChannelID]ChannelLink
// links is a map of channel id and channel link which manages
// this channel.
linkIndex map[lnwire.ChannelID]ChannelLink
// forwardingIndex is an index which is consulted by the switch when it
// needs to locate the next hop to forward an incoming/outgoing HTLC
// update to/from.
//
// TODO(roasbeef): eventually add a NetworkHop mapping before the
// ChannelLink
forwardingIndex map[lnwire.ShortChannelID]ChannelLink
// interfaceIndex maps the compressed public key of a peer to all the
// channels that the switch maintains with that peer.
interfaceIndex map[[33]byte]map[lnwire.ChannelID]ChannelLink
// linkStopIndex stores the currently stopping ChannelLinks,
// represented by their ChannelID. The key is the link's ChannelID and
// the value is a chan that is closed when the link has fully stopped.
// This map is only added to if RemoveLink is called and is not added
// to when the Switch is shutting down and calls Stop() on each link.
//
// MUST be used with the indexMtx.
linkStopIndex map[lnwire.ChannelID]chan struct{}
// htlcPlex is the channel which all connected links use to coordinate
// the setup/teardown of Sphinx (onion routing) payment circuits.
// Active links forward any add/settle messages over this channel each
// state transition, sending new adds/settles which are fully locked
// in.
htlcPlex chan *plexPacket
// chanCloseRequests is used to transfer the channel close request to
// the channel close handler.
chanCloseRequests chan *ChanClose
// resolutionMsgs is the channel that all external contract resolution
// messages will be sent over.
resolutionMsgs chan *resolutionMsg
// pendingFwdingEvents is the set of forwarding events which have been
// collected during the current interval, but hasn't yet been written
// to the forwarding log.
fwdEventMtx sync.Mutex
pendingFwdingEvents []channeldb.ForwardingEvent
// blockEpochStream is an active block epoch event stream backed by an
// active ChainNotifier instance. This will be used to retrieve the
// latest height of the chain.
blockEpochStream *chainntnfs.BlockEpochEvent
// pendingSettleFails is the set of settle/fail entries that we need to
// ack in the forwarding package of the outgoing link. This was added to
// make pipelining settles more efficient.
pendingSettleFails []channeldb.SettleFailRef
// resMsgStore is used to store the set of ResolutionMsg that come from
// contractcourt. This is used so the Switch can properly forward them,
// even on restarts.
resMsgStore *resolutionStore
// aliasToReal is a map used for option-scid-alias feature-bit links.
// The alias SCID is the key and the real, confirmed SCID is the value.
// If the channel is unconfirmed, there will not be a mapping for it.
// Since channels can have multiple aliases, this map is essentially a
// N->1 mapping for a channel. This MUST be accessed with the indexMtx.
aliasToReal map[lnwire.ShortChannelID]lnwire.ShortChannelID
// baseIndex is a map used for option-scid-alias feature-bit links.
// The value is the SCID of the link's ShortChannelID. This value may
// be an alias for zero-conf channels or a confirmed SCID for
// non-zero-conf channels with the option-scid-alias feature-bit. The
// key includes the value itself and also any other aliases. This MUST
// be accessed with the indexMtx.
baseIndex map[lnwire.ShortChannelID]lnwire.ShortChannelID
}
// New creates the new instance of htlc switch.
func New(cfg Config, currentHeight uint32) (*Switch, error) {
resStore := newResolutionStore(cfg.DB)
circuitMap, err := NewCircuitMap(&CircuitMapConfig{
DB: cfg.DB,
FetchAllOpenChannels: cfg.FetchAllOpenChannels,
FetchClosedChannels: cfg.FetchClosedChannels,
ExtractErrorEncrypter: cfg.ExtractErrorEncrypter,
CheckResolutionMsg: resStore.checkResolutionMsg,
})
if err != nil {
return nil, err
}
s := &Switch{
bestHeight: currentHeight,
cfg: &cfg,
circuits: circuitMap,
linkIndex: make(map[lnwire.ChannelID]ChannelLink),
forwardingIndex: make(map[lnwire.ShortChannelID]ChannelLink),
interfaceIndex: make(map[[33]byte]map[lnwire.ChannelID]ChannelLink),
pendingLinkIndex: make(map[lnwire.ChannelID]ChannelLink),
linkStopIndex: make(map[lnwire.ChannelID]chan struct{}),
networkResults: newNetworkResultStore(cfg.DB),
htlcPlex: make(chan *plexPacket),
chanCloseRequests: make(chan *ChanClose),
resolutionMsgs: make(chan *resolutionMsg),
resMsgStore: resStore,
quit: make(chan struct{}),
}
s.aliasToReal = make(map[lnwire.ShortChannelID]lnwire.ShortChannelID)
s.baseIndex = make(map[lnwire.ShortChannelID]lnwire.ShortChannelID)
s.mailOrchestrator = newMailOrchestrator(&mailOrchConfig{
forwardPackets: s.ForwardPackets,
clock: s.cfg.Clock,
expiry: s.cfg.MailboxDeliveryTimeout,
failMailboxUpdate: s.failMailboxUpdate,
})
return s, nil
}
// resolutionMsg is a struct that wraps an existing ResolutionMsg with a done
// channel. We'll use this channel to synchronize delivery of the message with
// the caller.
type resolutionMsg struct {
contractcourt.ResolutionMsg
errChan chan error
}
// ProcessContractResolution is called by active contract resolvers once a
// contract they are watching over has been fully resolved. The message carries
// an external signal that *would* have been sent if the outgoing channel
// didn't need to go to the chain in order to fulfill a contract. We'll process
// this message just as if it came from an active outgoing channel.
func (s *Switch) ProcessContractResolution(msg contractcourt.ResolutionMsg) error {
errChan := make(chan error, 1)
select {
case s.resolutionMsgs <- &resolutionMsg{
ResolutionMsg: msg,
errChan: errChan,
}:
case <-s.quit:
return ErrSwitchExiting
}
select {
case err := <-errChan:
return err
case <-s.quit:
return ErrSwitchExiting
}
}
// HasAttemptResult reads the network result store to fetch the specified
// attempt. Returns true if the attempt result exists.
func (s *Switch) HasAttemptResult(attemptID uint64) (bool, error) {
_, err := s.networkResults.getResult(attemptID)
if err == nil {
return true, nil
}
if !errors.Is(err, ErrPaymentIDNotFound) {
return false, err
}
return false, nil
}
// GetAttemptResult returns the result of the HTLC attempt with the given
// attemptID. The paymentHash should be set to the payment's overall hash, or
// in case of AMP payments the payment's unique identifier.
//
// The method returns a channel where the HTLC attempt result will be sent when
// available, or an error is encountered during forwarding. When a result is
// received on the channel, the HTLC is guaranteed to no longer be in flight.
// The switch shutting down is signaled by closing the channel. If the
// attemptID is unknown, ErrPaymentIDNotFound will be returned.
func (s *Switch) GetAttemptResult(attemptID uint64, paymentHash lntypes.Hash,
deobfuscator ErrorDecrypter) (<-chan *PaymentResult, error) {
var (
nChan <-chan *networkResult
err error
inKey = CircuitKey{
ChanID: hop.Source,
HtlcID: attemptID,
}
)
// If the HTLC is not found in the circuit map, check whether a result
// is already available.
// Assumption: no one will add this attempt ID other than the caller.
if s.circuits.LookupCircuit(inKey) == nil {
res, err := s.networkResults.getResult(attemptID)
if err != nil {
return nil, err
}
c := make(chan *networkResult, 1)
c <- res
nChan = c
} else {
// The HTLC was committed to the circuits, subscribe for a
// result.
nChan, err = s.networkResults.subscribeResult(attemptID)
if err != nil {
return nil, err
}
}
resultChan := make(chan *PaymentResult, 1)
// Since the attempt was known, we can start a goroutine that can
// extract the result when it is available, and pass it on to the
// caller.
s.wg.Add(1)
go func() {
defer s.wg.Done()
var n *networkResult
select {
case n = <-nChan:
case <-s.quit:
// We close the result channel to signal a shutdown. We
// don't send any result in this case since the HTLC is
// still in flight.
close(resultChan)
return
}
log.Debugf("Received network result %T for attemptID=%v", n.msg,
attemptID)
// Extract the result and pass it to the result channel.
result, err := s.extractResult(
deobfuscator, n, attemptID, paymentHash,
)
if err != nil {
e := fmt.Errorf("unable to extract result: %w", err)
log.Error(e)
resultChan <- &PaymentResult{
Error: e,
}
return
}
resultChan <- result
}()
return resultChan, nil
}
// CleanStore calls the underlying result store, telling it is safe to delete
// all entries except the ones in the keepPids map. This should be called
// preiodically to let the switch clean up payment results that we have
// handled.
func (s *Switch) CleanStore(keepPids map[uint64]struct{}) error {
return s.networkResults.cleanStore(keepPids)
}
// SendHTLC is used by other subsystems which aren't belong to htlc switch
// package in order to send the htlc update. The attemptID used MUST be unique
// for this HTLC, and MUST be used only once, otherwise the switch might reject
// it.
func (s *Switch) SendHTLC(firstHop lnwire.ShortChannelID, attemptID uint64,
htlc *lnwire.UpdateAddHTLC) error {
// Generate and send new update packet, if error will be received on
// this stage it means that packet haven't left boundaries of our
// system and something wrong happened.
packet := &htlcPacket{
incomingChanID: hop.Source,
incomingHTLCID: attemptID,
outgoingChanID: firstHop,
htlc: htlc,
amount: htlc.Amount,
}
// Attempt to fetch the target link before creating a circuit so that
// we don't leave dangling circuits. The getLocalLink method does not
// require the circuit variable to be set on the *htlcPacket.
link, linkErr := s.getLocalLink(packet, htlc)
if linkErr != nil {
// Notify the htlc notifier of a link failure on our outgoing
// link. Incoming timelock/amount values are not set because
// they are not present for local sends.
s.cfg.HtlcNotifier.NotifyLinkFailEvent(
newHtlcKey(packet),
HtlcInfo{
OutgoingTimeLock: htlc.Expiry,
OutgoingAmt: htlc.Amount,
},
HtlcEventTypeSend,
linkErr,
false,
)
return linkErr
}
// Evaluate whether this HTLC would bypass our fee exposure. If it
// does, don't send it out and instead return an error.
if s.dustExceedsFeeThreshold(link, htlc.Amount, false) {
// Notify the htlc notifier of a link failure on our outgoing
// link. We use the FailTemporaryChannelFailure in place of a
// more descriptive error message.
linkErr := NewLinkError(
&lnwire.FailTemporaryChannelFailure{},
)
s.cfg.HtlcNotifier.NotifyLinkFailEvent(
newHtlcKey(packet),
HtlcInfo{
OutgoingTimeLock: htlc.Expiry,
OutgoingAmt: htlc.Amount,
},
HtlcEventTypeSend,
linkErr,
false,
)
return errFeeExposureExceeded
}
circuit := newPaymentCircuit(&htlc.PaymentHash, packet)
actions, err := s.circuits.CommitCircuits(circuit)
if err != nil {
log.Errorf("unable to commit circuit in switch: %v", err)
return err
}
// Drop duplicate packet if it has already been seen.
switch {
case len(actions.Drops) == 1:
return ErrDuplicateAdd
case len(actions.Fails) == 1:
return ErrLocalAddFailed
}
// Give the packet to the link's mailbox so that HTLC's are properly
// canceled back if the mailbox timeout elapses.
packet.circuit = circuit
return link.handleSwitchPacket(packet)
}
// UpdateForwardingPolicies sends a message to the switch to update the
// forwarding policies for the set of target channels, keyed in chanPolicies.
//
// NOTE: This function is synchronous and will block until either the
// forwarding policies for all links have been updated, or the switch shuts
// down.
func (s *Switch) UpdateForwardingPolicies(
chanPolicies map[wire.OutPoint]models.ForwardingPolicy) {
log.Tracef("Updating link policies: %v", lnutils.SpewLogClosure(
chanPolicies))
s.indexMtx.RLock()
// Update each link in chanPolicies.
for targetLink, policy := range chanPolicies {
cid := lnwire.NewChanIDFromOutPoint(targetLink)
link, ok := s.linkIndex[cid]
if !ok {
log.Debugf("Unable to find ChannelPoint(%v) to update "+
"link policy", targetLink)
continue
}
link.UpdateForwardingPolicy(policy)
}
s.indexMtx.RUnlock()
}
// IsForwardedHTLC checks for a given channel and htlc index if it is related
// to an opened circuit that represents a forwarded payment.
func (s *Switch) IsForwardedHTLC(chanID lnwire.ShortChannelID,
htlcIndex uint64) bool {
circuit := s.circuits.LookupOpenCircuit(models.CircuitKey{
ChanID: chanID,
HtlcID: htlcIndex,
})
return circuit != nil && circuit.Incoming.ChanID != hop.Source
}
// ForwardPackets adds a list of packets to the switch for processing. Fails
// and settles are added on a first past, simultaneously constructing circuits
// for any adds. After persisting the circuits, another pass of the adds is
// given to forward them through the router. The sending link's quit channel is
// used to prevent deadlocks when the switch stops a link in the midst of
// forwarding.
func (s *Switch) ForwardPackets(linkQuit <-chan struct{},
packets ...*htlcPacket) error {
var (
// fwdChan is a buffered channel used to receive err msgs from
// the htlcPlex when forwarding this batch.
fwdChan = make(chan error, len(packets))
// numSent keeps a running count of how many packets are
// forwarded to the switch, which determines how many responses
// we will wait for on the fwdChan..
numSent int
)
// No packets, nothing to do.
if len(packets) == 0 {
return nil
}
// Setup a barrier to prevent the background tasks from processing
// responses until this function returns to the user.
var wg sync.WaitGroup
wg.Add(1)
defer wg.Done()
// Before spawning the following goroutine to proxy our error responses,
// check to see if we have already been issued a shutdown request. If
// so, we exit early to avoid incrementing the switch's waitgroup while
// it is already in the process of shutting down.
select {
case <-linkQuit:
return nil
case <-s.quit:
return nil
default:
// Spawn a goroutine to log the errors returned from failed packets.
s.wg.Add(1)
go s.logFwdErrs(&numSent, &wg, fwdChan)
}
// Make a first pass over the packets, forwarding any settles or fails.
// As adds are found, we create a circuit and append it to our set of
// circuits to be written to disk.
var circuits []*PaymentCircuit
var addBatch []*htlcPacket
for _, packet := range packets {
switch htlc := packet.htlc.(type) {
case *lnwire.UpdateAddHTLC:
circuit := newPaymentCircuit(&htlc.PaymentHash, packet)
packet.circuit = circuit
circuits = append(circuits, circuit)
addBatch = append(addBatch, packet)
default:
err := s.routeAsync(packet, fwdChan, linkQuit)
if err != nil {
return fmt.Errorf("failed to forward packet %w",
err)
}
numSent++
}
}
// If this batch did not contain any circuits to commit, we can return
// early.
if len(circuits) == 0 {
return nil
}
// Write any circuits that we found to disk.
actions, err := s.circuits.CommitCircuits(circuits...)
if err != nil {
log.Errorf("unable to commit circuits in switch: %v", err)
}
// Split the htlc packets by comparing an in-order seek to the head of
// the added, dropped, or failed circuits.
//
// NOTE: This assumes each list is guaranteed to be a subsequence of the
// circuits, and that the union of the sets results in the original set
// of circuits.
var addedPackets, failedPackets []*htlcPacket
for _, packet := range addBatch {
switch {
case len(actions.Adds) > 0 && packet.circuit == actions.Adds[0]:
addedPackets = append(addedPackets, packet)
actions.Adds = actions.Adds[1:]
case len(actions.Drops) > 0 && packet.circuit == actions.Drops[0]:
actions.Drops = actions.Drops[1:]
case len(actions.Fails) > 0 && packet.circuit == actions.Fails[0]:
failedPackets = append(failedPackets, packet)
actions.Fails = actions.Fails[1:]
}
}
// Now, forward any packets for circuits that were successfully added to
// the switch's circuit map.
for _, packet := range addedPackets {
err := s.routeAsync(packet, fwdChan, linkQuit)
if err != nil {
return fmt.Errorf("failed to forward packet %w", err)
}
numSent++
}
// Lastly, for any packets that failed, this implies that they were
// left in a half added state, which can happen when recovering from
// failures.
if len(failedPackets) > 0 {
var failure lnwire.FailureMessage
incomingID := failedPackets[0].incomingChanID
// If the incoming channel is an option_scid_alias channel,
// then we'll need to replace the SCID in the ChannelUpdate.
update := s.failAliasUpdate(incomingID, true)
if update == nil {
// Fallback to the original non-option behavior.
update, err := s.cfg.FetchLastChannelUpdate(
incomingID,
)
if err != nil {
failure = &lnwire.FailTemporaryNodeFailure{}
} else {
failure = lnwire.NewTemporaryChannelFailure(
update,
)
}
} else {
// This is an option_scid_alias channel.
failure = lnwire.NewTemporaryChannelFailure(update)
}
linkError := NewDetailedLinkError(
failure, OutgoingFailureIncompleteForward,
)
for _, packet := range failedPackets {
// We don't handle the error here since this method
// always returns an error.
_ = s.failAddPacket(packet, linkError)
}
}
return nil
}
// logFwdErrs logs any errors received on `fwdChan`.
func (s *Switch) logFwdErrs(num *int, wg *sync.WaitGroup, fwdChan chan error) {
defer s.wg.Done()
// Wait here until the outer function has finished persisting
// and routing the packets. This guarantees we don't read from num until
// the value is accurate.
wg.Wait()
numSent := *num
for i := 0; i < numSent; i++ {
select {
case err := <-fwdChan:
if err != nil {
log.Errorf("Unhandled error while reforwarding htlc "+
"settle/fail over htlcswitch: %v", err)
}
case <-s.quit:
log.Errorf("unable to forward htlc packet " +
"htlc switch was stopped")
return
}
}
}
// routeAsync sends a packet through the htlc switch, using the provided err
// chan to propagate errors back to the caller. The link's quit channel is
// provided so that the send can be canceled if either the link or the switch
// receive a shutdown requuest. This method does not wait for a response from
// the htlcForwarder before returning.
func (s *Switch) routeAsync(packet *htlcPacket, errChan chan error,
linkQuit <-chan struct{}) error {
command := &plexPacket{
pkt: packet,
err: errChan,
}
select {
case s.htlcPlex <- command:
return nil
case <-linkQuit:
return ErrLinkShuttingDown
case <-s.quit:
return errors.New("htlc switch was stopped")
}
}
// getLocalLink handles the addition of a htlc for a send that originates from
// our node. It returns the link that the htlc should be forwarded outwards on,
// and a link error if the htlc cannot be forwarded.
func (s *Switch) getLocalLink(pkt *htlcPacket, htlc *lnwire.UpdateAddHTLC) (
ChannelLink, *LinkError) {
// Try to find links by node destination.
s.indexMtx.RLock()
link, err := s.getLinkByShortID(pkt.outgoingChanID)
defer s.indexMtx.RUnlock()
if err != nil {
// If the link was not found for the outgoingChanID, an outside
// subsystem may be using the confirmed SCID of a zero-conf
// channel. In this case, we'll consult the Switch maps to see
// if an alias exists and use the alias to lookup the link.
// This extra step is a consequence of not updating the Switch
// forwardingIndex when a zero-conf channel is confirmed. We
// don't need to change the outgoingChanID since the link will
// do that upon receiving the packet.
baseScid, ok := s.baseIndex[pkt.outgoingChanID]
if !ok {
log.Errorf("Link %v not found", pkt.outgoingChanID)
return nil, NewLinkError(&lnwire.FailUnknownNextPeer{})
}
// The base SCID was found, so we'll use that to fetch the
// link.
link, err = s.getLinkByShortID(baseScid)
if err != nil {
log.Errorf("Link %v not found", baseScid)
return nil, NewLinkError(&lnwire.FailUnknownNextPeer{})
}
}
if !link.EligibleToForward() {
log.Errorf("Link %v is not available to forward",
pkt.outgoingChanID)
// The update does not need to be populated as the error
// will be returned back to the router.
return nil, NewDetailedLinkError(
lnwire.NewTemporaryChannelFailure(nil),
OutgoingFailureLinkNotEligible,
)
}
// Ensure that the htlc satisfies the outgoing channel policy.
currentHeight := atomic.LoadUint32(&s.bestHeight)
htlcErr := link.CheckHtlcTransit(
htlc.PaymentHash, htlc.Amount, htlc.Expiry, currentHeight,
htlc.CustomRecords,
)
if htlcErr != nil {
log.Errorf("Link %v policy for local forward not "+
"satisfied", pkt.outgoingChanID)
return nil, htlcErr
}
return link, nil
}
// handleLocalResponse processes a Settle or Fail responding to a
// locally-initiated payment. This is handled asynchronously to avoid blocking
// the main event loop within the switch, as these operations can require
// multiple db transactions. The guarantees of the circuit map are stringent
// enough such that we are able to tolerate reordering of these operations
// without side effects. The primary operations handled are:
// 1. Save the payment result to the pending payment store.
// 2. Notify subscribers about the payment result.
// 3. Ack settle/fail references, to avoid resending this response internally
// 4. Teardown the closing circuit in the circuit map
//
// NOTE: This method MUST be spawned as a goroutine.
func (s *Switch) handleLocalResponse(pkt *htlcPacket) {
defer s.wg.Done()
attemptID := pkt.incomingHTLCID
// The error reason will be unencypted in case this a local
// failure or a converted error.
unencrypted := pkt.localFailure || pkt.convertedError
n := &networkResult{
msg: pkt.htlc,
unencrypted: unencrypted,
isResolution: pkt.isResolution,
}
// Store the result to the db. This will also notify subscribers about
// the result.
if err := s.networkResults.storeResult(attemptID, n); err != nil {
log.Errorf("Unable to store attempt result for pid=%v: %v",
attemptID, err)
return
}
// First, we'll clean up any fwdpkg references, circuit entries, and
// mark in our db that the payment for this payment hash has either
// succeeded or failed.
//
// If this response is contained in a forwarding package, we'll start by
// acking the settle/fail so that we don't continue to retransmit the
// HTLC internally.
if pkt.destRef != nil {
if err := s.ackSettleFail(*pkt.destRef); err != nil {
log.Warnf("Unable to ack settle/fail reference: %s: %v",
*pkt.destRef, err)
return
}
}
// Next, we'll remove the circuit since we are about to complete an
// fulfill/fail of this HTLC. Since we've already removed the
// settle/fail fwdpkg reference, the response from the peer cannot be
// replayed internally if this step fails. If this happens, this logic
// will be executed when a provided resolution message comes through.
// This can only happen if the circuit is still open, which is why this
// ordering is chosen.
if err := s.teardownCircuit(pkt); err != nil {
log.Errorf("Unable to teardown circuit %s: %v",
pkt.inKey(), err)
return
}
// Finally, notify on the htlc failure or success that has been handled.
key := newHtlcKey(pkt)
eventType := getEventType(pkt)
switch htlc := pkt.htlc.(type) {
case *lnwire.UpdateFulfillHTLC:
s.cfg.HtlcNotifier.NotifySettleEvent(key, htlc.PaymentPreimage,
eventType)
case *lnwire.UpdateFailHTLC:
s.cfg.HtlcNotifier.NotifyForwardingFailEvent(key, eventType)
}
}
// extractResult uses the given deobfuscator to extract the payment result from
// the given network message.
func (s *Switch) extractResult(deobfuscator ErrorDecrypter, n *networkResult,
attemptID uint64, paymentHash lntypes.Hash) (*PaymentResult, error) {
switch htlc := n.msg.(type) {
// We've received a settle update which means we can finalize the user
// payment and return successful response.
case *lnwire.UpdateFulfillHTLC:
return &PaymentResult{
Preimage: htlc.PaymentPreimage,
}, nil
// We've received a fail update which means we can finalize the
// user payment and return fail response.
case *lnwire.UpdateFailHTLC:
// TODO(yy): construct deobfuscator here to avoid creating it
// in paymentLifecycle even for settled HTLCs.
paymentErr := s.parseFailedPayment(
deobfuscator, attemptID, paymentHash, n.unencrypted,
n.isResolution, htlc,
)
return &PaymentResult{
Error: paymentErr,
}, nil
default:
return nil, fmt.Errorf("received unknown response type: %T",
htlc)
}
}
// parseFailedPayment determines the appropriate failure message to return to
// a user initiated payment. The three cases handled are:
// 1. An unencrypted failure, which should already plaintext.
// 2. A resolution from the chain arbitrator, which possibly has no failure
// reason attached.
// 3. A failure from the remote party, which will need to be decrypted using
// the payment deobfuscator.
func (s *Switch) parseFailedPayment(deobfuscator ErrorDecrypter,
attemptID uint64, paymentHash lntypes.Hash, unencrypted,
isResolution bool, htlc *lnwire.UpdateFailHTLC) error {
switch {
// The payment never cleared the link, so we don't need to
// decrypt the error, simply decode it them report back to the
// user.
case unencrypted:
r := bytes.NewReader(htlc.Reason)
failureMsg, err := lnwire.DecodeFailure(r, 0)
if err != nil {
// If we could not decode the failure reason, return a link
// error indicating that we failed to decode the onion.
linkError := NewDetailedLinkError(
// As this didn't even clear the link, we don't
// need to apply an update here since it goes
// directly to the router.
lnwire.NewTemporaryChannelFailure(nil),
OutgoingFailureDecodeError,
)
log.Errorf("%v: (hash=%v, pid=%d): %v",
linkError.FailureDetail.FailureString(),
paymentHash, attemptID, err)
return linkError
}
// If we successfully decoded the failure reason, return it.
return NewLinkError(failureMsg)
// A payment had to be timed out on chain before it got past
// the first hop. In this case, we'll report a permanent
// channel failure as this means us, or the remote party had to
// go on chain.
case isResolution && htlc.Reason == nil:
linkError := NewDetailedLinkError(
&lnwire.FailPermanentChannelFailure{},
OutgoingFailureOnChainTimeout,
)
log.Infof("%v: hash=%v, pid=%d",
linkError.FailureDetail.FailureString(),
paymentHash, attemptID)
return linkError
// A regular multi-hop payment error that we'll need to
// decrypt.
default:
// We'll attempt to fully decrypt the onion encrypted
// error. If we're unable to then we'll bail early.
failure, err := deobfuscator.DecryptError(htlc.Reason)
if err != nil {
log.Errorf("unable to de-obfuscate onion failure "+
"(hash=%v, pid=%d): %v",
paymentHash, attemptID, err)
return ErrUnreadableFailureMessage
}
return failure
}
}
// handlePacketForward is used in cases when we need forward the htlc update
// from one channel link to another and be able to propagate the settle/fail
// updates back. This behaviour is achieved by creation of payment circuits.
func (s *Switch) handlePacketForward(packet *htlcPacket) error {
switch htlc := packet.htlc.(type) {
// Channel link forwarded us a new htlc, therefore we initiate the
// payment circuit within our internal state so we can properly forward
// the ultimate settle message back latter.
case *lnwire.UpdateAddHTLC:
return s.handlePacketAdd(packet, htlc)
case *lnwire.UpdateFulfillHTLC:
return s.handlePacketSettle(packet)
// Channel link forwarded us an update_fail_htlc message.
//
// NOTE: when the channel link receives an update_fail_malformed_htlc
// from upstream, it will convert the message into update_fail_htlc and
// forward it. Thus there's no need to catch `UpdateFailMalformedHTLC`
// here.
case *lnwire.UpdateFailHTLC:
return s.handlePacketFail(packet, htlc)
default:
return fmt.Errorf("wrong update type: %T", htlc)
}
}
// checkCircularForward checks whether a forward is circular (arrives and
// departs on the same link) and returns a link error if the switch is
// configured to disallow this behaviour.
func (s *Switch) checkCircularForward(incoming, outgoing lnwire.ShortChannelID,
allowCircular bool, paymentHash lntypes.Hash) *LinkError {
log.Tracef("Checking for circular route: incoming=%v, outgoing=%v "+
"(payment hash: %x)", incoming, outgoing, paymentHash[:])
// If they are equal, we can skip the alias mapping checks.
if incoming == outgoing {
// The switch may be configured to allow circular routes, so
// just log and return nil.
if allowCircular {
log.Debugf("allowing circular route over link: %v "+
"(payment hash: %x)", incoming, paymentHash)
return nil
}
// Otherwise, we'll return a temporary channel failure.
return NewDetailedLinkError(
lnwire.NewTemporaryChannelFailure(nil),
OutgoingFailureCircularRoute,
)
}
// We'll fetch the "base" SCID from the baseIndex for the incoming and
// outgoing SCIDs. If either one does not have a base SCID, then the
// two channels are not equal since one will be a channel that does not
// need a mapping and SCID equality was checked above. If the "base"
// SCIDs are equal, then this is a circular route. Otherwise, it isn't.
s.indexMtx.RLock()
incomingBaseScid, ok := s.baseIndex[incoming]
if !ok {
// This channel does not use baseIndex, bail out.
s.indexMtx.RUnlock()
return nil
}
outgoingBaseScid, ok := s.baseIndex[outgoing]
if !ok {
// This channel does not use baseIndex, bail out.
s.indexMtx.RUnlock()
return nil
}
s.indexMtx.RUnlock()
// Check base SCID equality.
if incomingBaseScid != outgoingBaseScid {
log.Tracef("Incoming base SCID %v does not match outgoing "+
"base SCID %v (payment hash: %x)", incomingBaseScid,
outgoingBaseScid, paymentHash[:])
// The base SCIDs are not equal so these are not the same
// channel.
return nil
}
// If the incoming and outgoing link are equal, the htlc is part of a
// circular route which may be used to lock up our liquidity. If the
// switch is configured to allow circular routes, log that we are
// allowing the route then return nil.
if allowCircular {
log.Debugf("allowing circular route over link: %v "+
"(payment hash: %x)", incoming, paymentHash)
return nil
}
// If our node disallows circular routes, return a temporary channel
// failure. There is nothing wrong with the policy used by the remote
// node, so we do not include a channel update.
return NewDetailedLinkError(
lnwire.NewTemporaryChannelFailure(nil),
OutgoingFailureCircularRoute,
)
}
// failAddPacket encrypts a fail packet back to an add packet's source.
// The ciphertext will be derived from the failure message proivded by context.
// This method returns the failErr if all other steps complete successfully.
func (s *Switch) failAddPacket(packet *htlcPacket, failure *LinkError) error {
// Encrypt the failure so that the sender will be able to read the error
// message. Since we failed this packet, we use EncryptFirstHop to
// obfuscate the failure for their eyes only.
reason, err := packet.obfuscator.EncryptFirstHop(failure.WireMessage())
if err != nil {
err := fmt.Errorf("unable to obfuscate "+
"error: %v", err)
log.Error(err)
return err
}
log.Error(failure.Error())
// Create a failure packet for this htlc. The full set of
// information about the htlc failure is included so that they can
// be included in link failure notifications.
failPkt := &htlcPacket{
sourceRef: packet.sourceRef,
incomingChanID: packet.incomingChanID,
incomingHTLCID: packet.incomingHTLCID,
outgoingChanID: packet.outgoingChanID,
outgoingHTLCID: packet.outgoingHTLCID,
incomingAmount: packet.incomingAmount,
amount: packet.amount,
incomingTimeout: packet.incomingTimeout,
outgoingTimeout: packet.outgoingTimeout,
circuit: packet.circuit,
obfuscator: packet.obfuscator,
linkFailure: failure,
htlc: &lnwire.UpdateFailHTLC{
Reason: reason,
},
}
// Route a fail packet back to the source link.
err = s.mailOrchestrator.Deliver(failPkt.incomingChanID, failPkt)
if err != nil {
err = fmt.Errorf("source chanid=%v unable to "+
"handle switch packet: %v",
packet.incomingChanID, err)
log.Error(err)
return err
}
return failure
}
// closeCircuit accepts a settle or fail htlc and the associated htlc packet and
// attempts to determine the source that forwarded this htlc. This method will
// set the incoming chan and htlc ID of the given packet if the source was
// found, and will properly [re]encrypt any failure messages.
func (s *Switch) closeCircuit(pkt *htlcPacket) (*PaymentCircuit, error) {
// If the packet has its source, that means it was failed locally by
// the outgoing link. We fail it here to make sure only one response
// makes it through the switch.
if pkt.hasSource {
circuit, err := s.circuits.FailCircuit(pkt.inKey())
switch err {
// Circuit successfully closed.
case nil:
return circuit, nil
// Circuit was previously closed, but has not been deleted.
// We'll just drop this response until the circuit has been
// fully removed.
case ErrCircuitClosing:
return nil, err
// Failed to close circuit because it does not exist. This is
// likely because the circuit was already successfully closed.
// Since this packet failed locally, there is no forwarding
// package entry to acknowledge.
case ErrUnknownCircuit:
return nil, err
// Unexpected error.
default:
return nil, err
}
}
// Otherwise, this is packet was received from the remote party. Use
// circuit map to find the incoming link to receive the settle/fail.
circuit, err := s.circuits.CloseCircuit(pkt.outKey())
switch err {
// Open circuit successfully closed.
case nil:
pkt.incomingChanID = circuit.Incoming.ChanID
pkt.incomingHTLCID = circuit.Incoming.HtlcID
pkt.circuit = circuit
pkt.sourceRef = &circuit.AddRef
pktType := "SETTLE"
if _, ok := pkt.htlc.(*lnwire.UpdateFailHTLC); ok {
pktType = "FAIL"
}
log.Debugf("Closed completed %s circuit for %x: "+
"(%s, %d) <-> (%s, %d)", pktType, pkt.circuit.PaymentHash,
pkt.incomingChanID, pkt.incomingHTLCID,
pkt.outgoingChanID, pkt.outgoingHTLCID)
return circuit, nil
// Circuit was previously closed, but has not been deleted. We'll just
// drop this response until the circuit has been removed.
case ErrCircuitClosing:
return nil, err
// Failed to close circuit because it does not exist. This is likely
// because the circuit was already successfully closed.
case ErrUnknownCircuit:
if pkt.destRef != nil {
// Add this SettleFailRef to the set of pending settle/fail entries
// awaiting acknowledgement.
s.pendingSettleFails = append(s.pendingSettleFails, *pkt.destRef)
}
// If this is a settle, we will not log an error message as settles
// are expected to hit the ErrUnknownCircuit case. The only way fails
// can hit this case if the link restarts after having just sent a fail
// to the switch.
_, isSettle := pkt.htlc.(*lnwire.UpdateFulfillHTLC)
if !isSettle {
err := fmt.Errorf("unable to find target channel "+
"for HTLC fail: channel ID = %s, "+
"HTLC ID = %d", pkt.outgoingChanID,
pkt.outgoingHTLCID)
log.Error(err)
return nil, err
}
return nil, nil
// Unexpected error.
default:
return nil, err
}
}
// ackSettleFail is used by the switch to ACK any settle/fail entries in the
// forwarding package of the outgoing link for a payment circuit. We do this if
// we're the originator of the payment, so the link stops attempting to
// re-broadcast.
func (s *Switch) ackSettleFail(settleFailRefs ...channeldb.SettleFailRef) error {
return kvdb.Batch(s.cfg.DB, func(tx kvdb.RwTx) error {
return s.cfg.SwitchPackager.AckSettleFails(tx, settleFailRefs...)
})
}
// teardownCircuit removes a pending or open circuit from the switch's circuit
// map and prints useful logging statements regarding the outcome.
func (s *Switch) teardownCircuit(pkt *htlcPacket) error {
var pktType string
switch htlc := pkt.htlc.(type) {
case *lnwire.UpdateFulfillHTLC:
pktType = "SETTLE"
case *lnwire.UpdateFailHTLC:
pktType = "FAIL"
default:
return fmt.Errorf("cannot tear down packet of type: %T", htlc)
}
var paymentHash lntypes.Hash
// Perform a defensive check to make sure we don't try to access a nil
// circuit.
circuit := pkt.circuit
if circuit != nil {
copy(paymentHash[:], circuit.PaymentHash[:])
}
log.Debugf("Tearing down circuit with %s pkt, removing circuit=%v "+
"with keystone=%v", pktType, pkt.inKey(), pkt.outKey())
err := s.circuits.DeleteCircuits(pkt.inKey())
if err != nil {
log.Warnf("Failed to tear down circuit (%s, %d) <-> (%s, %d) "+
"with payment_hash=%v using %s pkt", pkt.incomingChanID,
pkt.incomingHTLCID, pkt.outgoingChanID,
pkt.outgoingHTLCID, pkt.circuit.PaymentHash, pktType)
return err
}
log.Debugf("Closed %s circuit for %v: (%s, %d) <-> (%s, %d)", pktType,
paymentHash, pkt.incomingChanID, pkt.incomingHTLCID,
pkt.outgoingChanID, pkt.outgoingHTLCID)
return nil
}
// CloseLink creates and sends the close channel command to the target link
// directing the specified closure type. If the closure type is CloseRegular,
// targetFeePerKw parameter should be the ideal fee-per-kw that will be used as
// a starting point for close negotiation. The deliveryScript parameter is an
// optional parameter which sets a user specified script to close out to.
func (s *Switch) CloseLink(ctx context.Context, chanPoint *wire.OutPoint,
closeType contractcourt.ChannelCloseType,
targetFeePerKw, maxFee chainfee.SatPerKWeight,
deliveryScript lnwire.DeliveryAddress) (chan interface{}, chan error) {
// TODO(roasbeef) abstract out the close updates.
updateChan := make(chan interface{}, 2)
errChan := make(chan error, 1)
command := &ChanClose{
CloseType: closeType,
ChanPoint: chanPoint,
Updates: updateChan,
TargetFeePerKw: targetFeePerKw,
DeliveryScript: deliveryScript,
Err: errChan,
MaxFee: maxFee,
Ctx: ctx,
}
select {
case s.chanCloseRequests <- command:
return updateChan, errChan
case <-s.quit:
errChan <- ErrSwitchExiting
close(updateChan)
return updateChan, errChan
}
}
// htlcForwarder is responsible for optimally forwarding (and possibly
// fragmenting) incoming/outgoing HTLCs amongst all active interfaces and their
// links. The duties of the forwarder are similar to that of a network switch,
// in that it facilitates multi-hop payments by acting as a central messaging
// bus. The switch communicates will active links to create, manage, and tear
// down active onion routed payments. Each active channel is modeled as
// networked device with metadata such as the available payment bandwidth, and
// total link capacity.
//
// NOTE: This MUST be run as a goroutine.
func (s *Switch) htlcForwarder() {
defer s.wg.Done()
defer func() {
s.blockEpochStream.Cancel()
// Remove all links once we've been signalled for shutdown.
var linksToStop []ChannelLink
s.indexMtx.Lock()
for _, link := range s.linkIndex {
activeLink := s.removeLink(link.ChanID())
if activeLink == nil {
log.Errorf("unable to remove ChannelLink(%v) "+
"on stop", link.ChanID())
continue
}
linksToStop = append(linksToStop, activeLink)
}
for _, link := range s.pendingLinkIndex {
pendingLink := s.removeLink(link.ChanID())
if pendingLink == nil {
log.Errorf("unable to remove ChannelLink(%v) "+
"on stop", link.ChanID())
continue
}
linksToStop = append(linksToStop, pendingLink)
}
s.indexMtx.Unlock()
// Now that all pending and live links have been removed from
// the forwarding indexes, stop each one before shutting down.
// We'll shut them down in parallel to make exiting as fast as
// possible.
var wg sync.WaitGroup
for _, link := range linksToStop {
wg.Add(1)
go func(l ChannelLink) {
defer wg.Done()
l.Stop()
}(link)
}
wg.Wait()
// Before we exit fully, we'll attempt to flush out any
// forwarding events that may still be lingering since the last
// batch flush.
if err := s.FlushForwardingEvents(); err != nil {
log.Errorf("unable to flush forwarding events: %v", err)
}
}()
// TODO(roasbeef): cleared vs settled distinction
var (
totalNumUpdates uint64
totalSatSent btcutil.Amount
totalSatRecv btcutil.Amount
)
s.cfg.LogEventTicker.Resume()
defer s.cfg.LogEventTicker.Stop()
// Every 15 seconds, we'll flush out the forwarding events that
// occurred during that period.
s.cfg.FwdEventTicker.Resume()
defer s.cfg.FwdEventTicker.Stop()
defer s.cfg.AckEventTicker.Stop()
out:
for {
// If the set of pending settle/fail entries is non-zero,
// reinstate the ack ticker so we can batch ack them.
if len(s.pendingSettleFails) > 0 {
s.cfg.AckEventTicker.Resume()
}
select {
case blockEpoch, ok := <-s.blockEpochStream.Epochs:
if !ok {
break out
}
atomic.StoreUint32(&s.bestHeight, uint32(blockEpoch.Height))
// A local close request has arrived, we'll forward this to the
// relevant link (if it exists) so the channel can be
// cooperatively closed (if possible).
case req := <-s.chanCloseRequests:
chanID := lnwire.NewChanIDFromOutPoint(*req.ChanPoint)
s.indexMtx.RLock()
link, ok := s.linkIndex[chanID]
if !ok {
s.indexMtx.RUnlock()
req.Err <- fmt.Errorf("no peer for channel with "+
"chan_id=%x", chanID[:])
continue
}
s.indexMtx.RUnlock()
peerPub := link.PeerPubKey()
log.Debugf("Requesting local channel close: peer=%x, "+
"chan_id=%x", link.PeerPubKey(), chanID[:])
go s.cfg.LocalChannelClose(peerPub[:], req)
case resolutionMsg := <-s.resolutionMsgs:
// We'll persist the resolution message to the Switch's
// resolution store.
resMsg := resolutionMsg.ResolutionMsg
err := s.resMsgStore.addResolutionMsg(&resMsg)
if err != nil {
// This will only fail if there is a database
// error or a serialization error. Sending the
// error prevents the contractcourt from being
// in a state where it believes the send was
// successful, when it wasn't.
log.Errorf("unable to add resolution msg: %v",
err)
resolutionMsg.errChan <- err
continue
}
// At this point, the resolution message has been
// persisted. It is safe to signal success by sending
// a nil error since the Switch will re-deliver the
// resolution message on restart.
resolutionMsg.errChan <- nil
// Create a htlc packet for this resolution. We do
// not have some of the information that we'll need
// for blinded error handling here , so we'll rely on
// our forwarding logic to fill it in later.
pkt := &htlcPacket{
outgoingChanID: resolutionMsg.SourceChan,
outgoingHTLCID: resolutionMsg.HtlcIndex,
isResolution: true,
}
// Resolution messages will either be cancelling
// backwards an existing HTLC, or settling a previously
// outgoing HTLC. Based on this, we'll map the message
// to the proper htlcPacket.
if resolutionMsg.Failure != nil {
pkt.htlc = &lnwire.UpdateFailHTLC{}
} else {
pkt.htlc = &lnwire.UpdateFulfillHTLC{
PaymentPreimage: *resolutionMsg.PreImage,
}
}
log.Debugf("Received outside contract resolution, "+
"mapping to: %v", spew.Sdump(pkt))
// We don't check the error, as the only failure we can
// encounter is due to the circuit already being
// closed. This is fine, as processing this message is
// meant to be idempotent.
err = s.handlePacketForward(pkt)
if err != nil {
log.Errorf("Unable to forward resolution msg: %v", err)
}
// A new packet has arrived for forwarding, we'll interpret the
// packet concretely, then either forward it along, or
// interpret a return packet to a locally initialized one.
case cmd := <-s.htlcPlex:
cmd.err <- s.handlePacketForward(cmd.pkt)
// When this time ticks, then it indicates that we should
// collect all the forwarding events since the last internal,
// and write them out to our log.
case <-s.cfg.FwdEventTicker.Ticks():
s.wg.Add(1)
go func() {
defer s.wg.Done()
if err := s.FlushForwardingEvents(); err != nil {
log.Errorf("Unable to flush "+
"forwarding events: %v", err)
}
}()
// The log ticker has fired, so we'll calculate some forwarding
// stats for the last 10 seconds to display within the logs to
// users.
case <-s.cfg.LogEventTicker.Ticks():
// First, we'll collate the current running tally of
// our forwarding stats.
prevSatSent := totalSatSent
prevSatRecv := totalSatRecv
prevNumUpdates := totalNumUpdates
var (
newNumUpdates uint64
newSatSent btcutil.Amount
newSatRecv btcutil.Amount
)
// Next, we'll run through all the registered links and
// compute their up-to-date forwarding stats.
s.indexMtx.RLock()
for _, link := range s.linkIndex {
// TODO(roasbeef): when links first registered
// stats printed.
updates, sent, recv := link.Stats()
newNumUpdates += updates
newSatSent += sent.ToSatoshis()
newSatRecv += recv.ToSatoshis()
}
s.indexMtx.RUnlock()
var (
diffNumUpdates uint64
diffSatSent btcutil.Amount
diffSatRecv btcutil.Amount
)
// If this is the first time we're computing these
// stats, then the diff is just the new value. We do
// this in order to avoid integer underflow issues.
if prevNumUpdates == 0 {
diffNumUpdates = newNumUpdates
diffSatSent = newSatSent
diffSatRecv = newSatRecv
} else {
diffNumUpdates = newNumUpdates - prevNumUpdates
diffSatSent = newSatSent - prevSatSent
diffSatRecv = newSatRecv - prevSatRecv
}
// If the diff of num updates is zero, then we haven't
// forwarded anything in the last 10 seconds, so we can
// skip this update.
if diffNumUpdates == 0 {
continue
}
// If the diff of num updates is negative, then some
// links may have been unregistered from the switch, so
// we'll update our stats to only include our registered
// links.
if int64(diffNumUpdates) < 0 {
totalNumUpdates = newNumUpdates
totalSatSent = newSatSent
totalSatRecv = newSatRecv
continue
}
// Otherwise, we'll log this diff, then accumulate the
// new stats into the running total.
log.Debugf("Sent %d satoshis and received %d satoshis "+
"in the last 10 seconds (%f tx/sec)",
diffSatSent, diffSatRecv,
float64(diffNumUpdates)/10)
totalNumUpdates += diffNumUpdates
totalSatSent += diffSatSent
totalSatRecv += diffSatRecv
// The ack ticker has fired so if we have any settle/fail entries
// for a forwarding package to ack, we will do so here in a batch
// db call.
case <-s.cfg.AckEventTicker.Ticks():
// If the current set is empty, pause the ticker.
if len(s.pendingSettleFails) == 0 {
s.cfg.AckEventTicker.Pause()
continue
}
// Batch ack the settle/fail entries.
if err := s.ackSettleFail(s.pendingSettleFails...); err != nil {
log.Errorf("Unable to ack batch of settle/fails: %v", err)
continue
}
log.Tracef("Acked %d settle fails: %v",
len(s.pendingSettleFails),
lnutils.SpewLogClosure(s.pendingSettleFails))
// Reset the pendingSettleFails buffer while keeping acquired
// memory.
s.pendingSettleFails = s.pendingSettleFails[:0]
case <-s.quit:
return
}
}
}
// Start starts all helper goroutines required for the operation of the switch.
func (s *Switch) Start() error {
if !atomic.CompareAndSwapInt32(&s.started, 0, 1) {
log.Warn("Htlc Switch already started")
return errors.New("htlc switch already started")
}
log.Infof("HTLC Switch starting")
blockEpochStream, err := s.cfg.Notifier.RegisterBlockEpochNtfn(nil)
if err != nil {
return err
}
s.blockEpochStream = blockEpochStream
s.wg.Add(1)
go s.htlcForwarder()
if err := s.reforwardResponses(); err != nil {
s.Stop()
log.Errorf("unable to reforward responses: %v", err)
return err
}
if err := s.reforwardResolutions(); err != nil {
// We are already stopping so we can ignore the error.
_ = s.Stop()
log.Errorf("unable to reforward resolutions: %v", err)
return err
}
return nil
}
// reforwardResolutions fetches the set of resolution messages stored on-disk
// and reforwards them if their circuits are still open. If the circuits have
// been deleted, then we will delete the resolution message from the database.
func (s *Switch) reforwardResolutions() error {
// Fetch all stored resolution messages, deleting the ones that are
// resolved.
resMsgs, err := s.resMsgStore.fetchAllResolutionMsg()
if err != nil {
return err
}
switchPackets := make([]*htlcPacket, 0, len(resMsgs))
for _, resMsg := range resMsgs {
// If the open circuit no longer exists, then we can remove the
// message from the store.
outKey := CircuitKey{
ChanID: resMsg.SourceChan,
HtlcID: resMsg.HtlcIndex,
}
if s.circuits.LookupOpenCircuit(outKey) == nil {
// The open circuit doesn't exist.
err := s.resMsgStore.deleteResolutionMsg(&outKey)
if err != nil {
return err
}
continue
}
// The circuit is still open, so we can assume that the link or
// switch (if we are the source) hasn't cleaned it up yet.
// We rely on our forwarding logic to fill in details that
// are not currently available to us.
resPkt := &htlcPacket{
outgoingChanID: resMsg.SourceChan,
outgoingHTLCID: resMsg.HtlcIndex,
isResolution: true,
}
if resMsg.Failure != nil {
resPkt.htlc = &lnwire.UpdateFailHTLC{}
} else {
resPkt.htlc = &lnwire.UpdateFulfillHTLC{
PaymentPreimage: *resMsg.PreImage,
}
}
switchPackets = append(switchPackets, resPkt)
}
// We'll now dispatch the set of resolution messages to the proper
// destination. An error is only encountered here if the switch is
// shutting down.
if err := s.ForwardPackets(nil, switchPackets...); err != nil {
return err
}
return nil
}
// reforwardResponses for every known, non-pending channel, loads all associated
// forwarding packages and reforwards any Settle or Fail HTLCs found. This is
// used to resurrect the switch's mailboxes after a restart. This also runs for
// waiting close channels since there may be settles or fails that need to be
// reforwarded before they completely close.
func (s *Switch) reforwardResponses() error {
openChannels, err := s.cfg.FetchAllChannels()
if err != nil {
return err
}
for _, openChannel := range openChannels {
shortChanID := openChannel.ShortChanID()
// Locally-initiated payments never need reforwarding.
if shortChanID == hop.Source {
continue
}
// If the channel is pending, it should have no forwarding
// packages, and nothing to reforward.
if openChannel.IsPending {
continue
}
// Channels in open or waiting-close may still have responses in
// their forwarding packages. We will continue to reattempt
// forwarding on startup until the channel is fully-closed.
//
// Load this channel's forwarding packages, and deliver them to
// the switch.
fwdPkgs, err := s.loadChannelFwdPkgs(shortChanID)
if err != nil {
log.Errorf("unable to load forwarding "+
"packages for %v: %v", shortChanID, err)
return err
}
s.reforwardSettleFails(fwdPkgs)
}
return nil
}
// loadChannelFwdPkgs loads all forwarding packages owned by the `source` short
// channel identifier.
func (s *Switch) loadChannelFwdPkgs(source lnwire.ShortChannelID) ([]*channeldb.FwdPkg, error) {
var fwdPkgs []*channeldb.FwdPkg
if err := kvdb.View(s.cfg.DB, func(tx kvdb.RTx) error {
var err error
fwdPkgs, err = s.cfg.SwitchPackager.LoadChannelFwdPkgs(
tx, source,
)
return err
}, func() {
fwdPkgs = nil
}); err != nil {
return nil, err
}
return fwdPkgs, nil
}
// reforwardSettleFails parses the Settle and Fail HTLCs from the list of
// forwarding packages, and reforwards those that have not been acknowledged.
// This is intended to occur on startup, in order to recover the switch's
// mailboxes, and to ensure that responses can be propagated in case the
// outgoing link never comes back online.
//
// NOTE: This should mimic the behavior processRemoteSettleFails.
func (s *Switch) reforwardSettleFails(fwdPkgs []*channeldb.FwdPkg) {
for _, fwdPkg := range fwdPkgs {
switchPackets := make([]*htlcPacket, 0, len(fwdPkg.SettleFails))
for i, update := range fwdPkg.SettleFails {
// Skip any settles or fails that have already been
// acknowledged by the incoming link that originated the
// forwarded Add.
if fwdPkg.SettleFailFilter.Contains(uint16(i)) {
continue
}
switch msg := update.UpdateMsg.(type) {
// A settle for an HTLC we previously forwarded HTLC has
// been received. So we'll forward the HTLC to the
// switch which will handle propagating the settle to
// the prior hop.
case *lnwire.UpdateFulfillHTLC:
destRef := fwdPkg.DestRef(uint16(i))
settlePacket := &htlcPacket{
outgoingChanID: fwdPkg.Source,
outgoingHTLCID: msg.ID,
destRef: &destRef,
htlc: msg,
}
// Add the packet to the batch to be forwarded, and
// notify the overflow queue that a spare spot has been
// freed up within the commitment state.
switchPackets = append(switchPackets, settlePacket)
// A failureCode message for a previously forwarded HTLC has been
// received. As a result a new slot will be freed up in our
// commitment state, so we'll forward this to the switch so the
// backwards undo can continue.
case *lnwire.UpdateFailHTLC:
// Fetch the reason the HTLC was canceled so
// we can continue to propagate it. This
// failure originated from another node, so
// the linkFailure field is not set on this
// packet. We rely on the link to fill in
// additional circuit information for us.
failPacket := &htlcPacket{
outgoingChanID: fwdPkg.Source,
outgoingHTLCID: msg.ID,
destRef: &channeldb.SettleFailRef{
Source: fwdPkg.Source,
Height: fwdPkg.Height,
Index: uint16(i),
},
htlc: msg,
}
// Add the packet to the batch to be forwarded, and
// notify the overflow queue that a spare spot has been
// freed up within the commitment state.
switchPackets = append(switchPackets, failPacket)
}
}
// Since this send isn't tied to a specific link, we pass a nil
// link quit channel, meaning the send will fail only if the
// switch receives a shutdown request.
if err := s.ForwardPackets(nil, switchPackets...); err != nil {
log.Errorf("Unhandled error while reforwarding packets "+
"settle/fail over htlcswitch: %v", err)
}
}
}
// Stop gracefully stops all active helper goroutines, then waits until they've
// exited.
func (s *Switch) Stop() error {
if !atomic.CompareAndSwapInt32(&s.shutdown, 0, 1) {
log.Warn("Htlc Switch already stopped")
return errors.New("htlc switch already shutdown")
}
log.Info("HTLC Switch shutting down...")
defer log.Debug("HTLC Switch shutdown complete")
close(s.quit)
s.wg.Wait()
// Wait until all active goroutines have finished exiting before
// stopping the mailboxes, otherwise the mailbox map could still be
// accessed and modified.
s.mailOrchestrator.Stop()
return nil
}
// CreateAndAddLink will create a link and then add it to the internal maps
// when given a ChannelLinkConfig and LightningChannel.
func (s *Switch) CreateAndAddLink(linkCfg ChannelLinkConfig,
lnChan *lnwallet.LightningChannel) error {
link := NewChannelLink(linkCfg, lnChan)
return s.AddLink(link)
}
// AddLink is used to initiate the handling of the add link command. The
// request will be propagated and handled in the main goroutine.
func (s *Switch) AddLink(link ChannelLink) error {
s.indexMtx.Lock()
defer s.indexMtx.Unlock()
chanID := link.ChanID()
// First, ensure that this link is not already active in the switch.
_, err := s.getLink(chanID)
if err == nil {
return fmt.Errorf("unable to add ChannelLink(%v), already "+
"active", chanID)
}
// Get and attach the mailbox for this link, which buffers packets in
// case there packets that we tried to deliver while this link was
// offline.
shortChanID := link.ShortChanID()
mailbox := s.mailOrchestrator.GetOrCreateMailBox(chanID, shortChanID)
link.AttachMailBox(mailbox)
// Attach the Switch's failAliasUpdate function to the link.
link.attachFailAliasUpdate(s.failAliasUpdate)
if err := link.Start(); err != nil {
log.Errorf("AddLink failed to start link with chanID=%v: %v",
chanID, err)
s.removeLink(chanID)
return err
}
if shortChanID == hop.Source {
log.Infof("Adding pending link chan_id=%v, short_chan_id=%v",
chanID, shortChanID)
s.pendingLinkIndex[chanID] = link
} else {
log.Infof("Adding live link chan_id=%v, short_chan_id=%v",
chanID, shortChanID)
s.addLiveLink(link)
s.mailOrchestrator.BindLiveShortChanID(
mailbox, chanID, shortChanID,
)
}
return nil
}
// addLiveLink adds a link to all associated forwarding index, this makes it a
// candidate for forwarding HTLCs.
func (s *Switch) addLiveLink(link ChannelLink) {
linkScid := link.ShortChanID()
// We'll add the link to the linkIndex which lets us quickly
// look up a channel when we need to close or register it, and
// the forwarding index which'll be used when forwarding HTLC's
// in the multi-hop setting.
s.linkIndex[link.ChanID()] = link
s.forwardingIndex[linkScid] = link
// Next we'll add the link to the interface index so we can
// quickly look up all the channels for a particular node.
peerPub := link.PeerPubKey()
if _, ok := s.interfaceIndex[peerPub]; !ok {
s.interfaceIndex[peerPub] = make(map[lnwire.ChannelID]ChannelLink)
}
s.interfaceIndex[peerPub][link.ChanID()] = link
s.updateLinkAliases(link)
}
// UpdateLinkAliases is the externally exposed wrapper for updating link
// aliases. It acquires the indexMtx and calls the internal method.
func (s *Switch) UpdateLinkAliases(link ChannelLink) {
s.indexMtx.Lock()
defer s.indexMtx.Unlock()
s.updateLinkAliases(link)
}
// updateLinkAliases updates the aliases for a given link. This will cause the
// htlcswitch to consult the alias manager on the up to date values of its
// alias maps.
//
// NOTE: this MUST be called with the indexMtx held.
func (s *Switch) updateLinkAliases(link ChannelLink) {
linkScid := link.ShortChanID()
aliases := link.getAliases()
if link.isZeroConf() {
if link.zeroConfConfirmed() {
// Since the zero-conf channel has confirmed, we can
// populate the aliasToReal mapping.
confirmedScid := link.confirmedScid()
for _, alias := range aliases {
s.aliasToReal[alias] = confirmedScid
}
// Add the confirmed SCID as a key in the baseIndex.
s.baseIndex[confirmedScid] = linkScid
}
// Now we populate the baseIndex which will be used to fetch
// the link given any of the channel's alias SCIDs or the real
// SCID. The link's SCID is an alias, so we don't need to
// special-case it like the option-scid-alias feature-bit case
// further down.
for _, alias := range aliases {
s.baseIndex[alias] = linkScid
}
} else if link.negotiatedAliasFeature() {
// First, we flush any alias mappings for this link's scid
// before we populate the map again, in order to get rid of old
// values that no longer exist.
for alias, real := range s.aliasToReal {
if real == linkScid {
delete(s.aliasToReal, alias)
}
}
for alias, real := range s.baseIndex {
if real == linkScid {
delete(s.baseIndex, alias)
}
}
// The link's SCID is the confirmed SCID for non-zero-conf
// option-scid-alias feature bit channels.
for _, alias := range aliases {
s.aliasToReal[alias] = linkScid
s.baseIndex[alias] = linkScid
}
// Since the link's SCID is confirmed, it was not included in
// the baseIndex above as a key. Add it now.
s.baseIndex[linkScid] = linkScid
}
}
// GetLink is used to initiate the handling of the get link command. The
// request will be propagated/handled to/in the main goroutine.
func (s *Switch) GetLink(chanID lnwire.ChannelID) (ChannelUpdateHandler,
error) {
s.indexMtx.RLock()
defer s.indexMtx.RUnlock()
return s.getLink(chanID)
}
// getLink returns the link stored in either the pending index or the live
// lindex.
func (s *Switch) getLink(chanID lnwire.ChannelID) (ChannelLink, error) {
link, ok := s.linkIndex[chanID]
if !ok {
link, ok = s.pendingLinkIndex[chanID]
if !ok {
return nil, ErrChannelLinkNotFound
}
}
return link, nil
}
// GetLinkByShortID attempts to return the link which possesses the target short
// channel ID.
func (s *Switch) GetLinkByShortID(chanID lnwire.ShortChannelID) (ChannelLink,
error) {
s.indexMtx.RLock()
defer s.indexMtx.RUnlock()
link, err := s.getLinkByShortID(chanID)
if err != nil {
// If we failed to find the link under the passed-in SCID, we
// consult the Switch's baseIndex map to see if the confirmed
// SCID was used for a zero-conf channel.
aliasID, ok := s.baseIndex[chanID]
if !ok {
return nil, err
}
// An alias was found, use it to lookup if a link exists.
return s.getLinkByShortID(aliasID)
}
return link, nil
}
// getLinkByShortID attempts to return the link which possesses the target
// short channel ID.
//
// NOTE: This MUST be called with the indexMtx held.
func (s *Switch) getLinkByShortID(chanID lnwire.ShortChannelID) (ChannelLink, error) {
link, ok := s.forwardingIndex[chanID]
if !ok {
return nil, ErrChannelLinkNotFound
}
return link, nil
}
// getLinkByMapping attempts to fetch the link via the htlcPacket's
// outgoingChanID, possibly using a mapping. If it finds the link via mapping,
// the outgoingChanID will be changed so that an error can be properly
// attributed when looping over linkErrs in handlePacketForward.
//
// * If the outgoingChanID is an alias, we'll fetch the link regardless if it's
// public or not.
//
// * If the outgoingChanID is a confirmed SCID, we'll need to do more checks.
// - If there is no entry found in baseIndex, fetch the link. This channel
// did not have the option-scid-alias feature negotiated (which includes
// zero-conf and option-scid-alias channel-types).
// - If there is an entry found, fetch the link from forwardingIndex and
// fail if this is a private link.
//
// NOTE: This MUST be called with the indexMtx read lock held.
func (s *Switch) getLinkByMapping(pkt *htlcPacket) (ChannelLink, error) {
// Determine if this ShortChannelID is an alias or a confirmed SCID.
chanID := pkt.outgoingChanID
aliasID := s.cfg.IsAlias(chanID)
log.Debugf("Querying outgoing link using chanID=%v, aliasID=%v", chanID,
aliasID)
// Set the originalOutgoingChanID so the proper channel_update can be
// sent back if the option-scid-alias feature bit was negotiated.
pkt.originalOutgoingChanID = chanID
if aliasID {
// Since outgoingChanID is an alias, we'll fetch the link via
// baseIndex.
baseScid, ok := s.baseIndex[chanID]
if !ok {
// No mapping exists, bail.
return nil, ErrChannelLinkNotFound
}
// A mapping exists, so use baseScid to find the link in the
// forwardingIndex.
link, ok := s.forwardingIndex[baseScid]
if !ok {
// Link not found, bail.
return nil, ErrChannelLinkNotFound
}
// Change the packet's outgoingChanID field so that errors are
// properly attributed.
pkt.outgoingChanID = baseScid
// Return the link without checking if it's private or not.
return link, nil
}
// The outgoingChanID is a confirmed SCID. Attempt to fetch the base
// SCID from baseIndex.
baseScid, ok := s.baseIndex[chanID]
if !ok {
// outgoingChanID is not a key in base index meaning this
// channel did not have the option-scid-alias feature bit
// negotiated. We'll fetch the link and return it.
link, ok := s.forwardingIndex[chanID]
if !ok {
// The link wasn't found, bail out.
return nil, ErrChannelLinkNotFound
}
return link, nil
}
// Fetch the link whose internal SCID is baseScid.
link, ok := s.forwardingIndex[baseScid]
if !ok {
// Link wasn't found, bail out.
return nil, ErrChannelLinkNotFound
}
// If the link is unadvertised, we fail since the real SCID was used to
// forward over it and this is a channel where the option-scid-alias
// feature bit was negotiated.
if link.IsUnadvertised() {
log.Debugf("Link is unadvertised, chanID=%v, baseScid=%v",
chanID, baseScid)
return nil, ErrChannelLinkNotFound
}
// The link is public so the confirmed SCID can be used to forward over
// it. We'll also replace pkt's outgoingChanID field so errors can
// properly be attributed in the calling function.
pkt.outgoingChanID = baseScid
return link, nil
}
// HasActiveLink returns true if the given channel ID has a link in the link
// index AND the link is eligible to forward.
func (s *Switch) HasActiveLink(chanID lnwire.ChannelID) bool {
s.indexMtx.RLock()
defer s.indexMtx.RUnlock()
if link, ok := s.linkIndex[chanID]; ok {
return link.EligibleToForward()
}
return false
}
// RemoveLink purges the switch of any link associated with chanID. If a pending
// or active link is not found, this method does nothing. Otherwise, the method
// returns after the link has been completely shutdown.
func (s *Switch) RemoveLink(chanID lnwire.ChannelID) {
s.indexMtx.Lock()
link, err := s.getLink(chanID)
if err != nil {
// If err is non-nil, this means that link is also nil. The
// link variable cannot be nil without err being non-nil.
s.indexMtx.Unlock()
log.Tracef("Unable to remove link for ChannelID(%v): %v",
chanID, err)
return
}
// Check if the link is already stopping and grab the stop chan if it
// is.
stopChan, ok := s.linkStopIndex[chanID]
if !ok {
// If the link is non-nil, it is not currently stopping, so
// we'll add a stop chan to the linkStopIndex.
stopChan = make(chan struct{})
s.linkStopIndex[chanID] = stopChan
}
s.indexMtx.Unlock()
if ok {
// If the stop chan exists, we will wait for it to be closed.
// Once it is closed, we will exit.
select {
case <-stopChan:
return
case <-s.quit:
return
}
}
// Stop the link before removing it from the maps.
link.Stop()
s.indexMtx.Lock()
_ = s.removeLink(chanID)
// Close stopChan and remove this link from the linkStopIndex.
// Deleting from the index and removing from the link must be done
// in the same block while the mutex is held.
close(stopChan)
delete(s.linkStopIndex, chanID)
s.indexMtx.Unlock()
}
// removeLink is used to remove and stop the channel link.
//
// NOTE: This MUST be called with the indexMtx held.
func (s *Switch) removeLink(chanID lnwire.ChannelID) ChannelLink {
log.Infof("Removing channel link with ChannelID(%v)", chanID)
link, err := s.getLink(chanID)
if err != nil {
return nil
}
// Remove the channel from live link indexes.
delete(s.pendingLinkIndex, link.ChanID())
delete(s.linkIndex, link.ChanID())
delete(s.forwardingIndex, link.ShortChanID())
// If the link has been added to the peer index, then we'll move to
// delete the entry within the index.
peerPub := link.PeerPubKey()
if peerIndex, ok := s.interfaceIndex[peerPub]; ok {
delete(peerIndex, link.ChanID())
// If after deletion, there are no longer any links, then we'll
// remove the interface map all together.
if len(peerIndex) == 0 {
delete(s.interfaceIndex, peerPub)
}
}
return link
}
// UpdateShortChanID locates the link with the passed-in chanID and updates the
// underlying channel state. This is only used in zero-conf channels to allow
// the confirmed SCID to be updated.
func (s *Switch) UpdateShortChanID(chanID lnwire.ChannelID) error {
s.indexMtx.Lock()
defer s.indexMtx.Unlock()
// Locate the target link in the link index. If no such link exists,
// then we will ignore the request.
link, ok := s.linkIndex[chanID]
if !ok {
return fmt.Errorf("link %v not found", chanID)
}
// Try to update the link's underlying channel state, returning early
// if this update failed.
_, err := link.UpdateShortChanID()
if err != nil {
return err
}
// Since the zero-conf channel is confirmed, we should populate the
// aliasToReal map and update the baseIndex.
aliases := link.getAliases()
confirmedScid := link.confirmedScid()
for _, alias := range aliases {
s.aliasToReal[alias] = confirmedScid
}
s.baseIndex[confirmedScid] = link.ShortChanID()
return nil
}
// GetLinksByInterface fetches all the links connected to a particular node
// identified by the serialized compressed form of its public key.
func (s *Switch) GetLinksByInterface(hop [33]byte) ([]ChannelUpdateHandler,
error) {
s.indexMtx.RLock()
defer s.indexMtx.RUnlock()
var handlers []ChannelUpdateHandler
links, err := s.getLinks(hop)
if err != nil {
return nil, err
}
// Range over the returned []ChannelLink to convert them into
// []ChannelUpdateHandler.
for _, link := range links {
handlers = append(handlers, link)
}
return handlers, nil
}
// getLinks is function which returns the channel links of the peer by hop
// destination id.
//
// NOTE: This MUST be called with the indexMtx held.
func (s *Switch) getLinks(destination [33]byte) ([]ChannelLink, error) {
links, ok := s.interfaceIndex[destination]
if !ok {
return nil, ErrNoLinksFound
}
channelLinks := make([]ChannelLink, 0, len(links))
for _, link := range links {
channelLinks = append(channelLinks, link)
}
return channelLinks, nil
}
// CircuitModifier returns a reference to subset of the interfaces provided by
// the circuit map, to allow links to open and close circuits.
func (s *Switch) CircuitModifier() CircuitModifier {
return s.circuits
}
// CircuitLookup returns a reference to subset of the interfaces provided by the
// circuit map, to allow looking up circuits.
func (s *Switch) CircuitLookup() CircuitLookup {
return s.circuits
}
// commitCircuits persistently adds a circuit to the switch's circuit map.
func (s *Switch) commitCircuits(circuits ...*PaymentCircuit) (
*CircuitFwdActions, error) {
return s.circuits.CommitCircuits(circuits...)
}
// FlushForwardingEvents flushes out the set of pending forwarding events to
// the persistent log. This will be used by the switch to periodically flush
// out the set of forwarding events to disk. External callers can also use this
// method to ensure all data is flushed to dis before querying the log.
func (s *Switch) FlushForwardingEvents() error {
// First, we'll obtain a copy of the current set of pending forwarding
// events.
s.fwdEventMtx.Lock()
// If we won't have any forwarding events, then we can exit early.
if len(s.pendingFwdingEvents) == 0 {
s.fwdEventMtx.Unlock()
return nil
}
events := make([]channeldb.ForwardingEvent, len(s.pendingFwdingEvents))
copy(events[:], s.pendingFwdingEvents[:])
// With the copy obtained, we can now clear out the header pointer of
// the current slice. This way, we can re-use the underlying storage
// allocated for the slice.
s.pendingFwdingEvents = s.pendingFwdingEvents[:0]
s.fwdEventMtx.Unlock()
// Finally, we'll write out the copied events to the persistent
// forwarding log.
return s.cfg.FwdingLog.AddForwardingEvents(events)
}
// BestHeight returns the best height known to the switch.
func (s *Switch) BestHeight() uint32 {
return atomic.LoadUint32(&s.bestHeight)
}
// dustExceedsFeeThreshold takes in a ChannelLink, HTLC amount, and a boolean
// to determine whether the default fee threshold has been exceeded. This
// heuristic takes into account the trimmed-to-dust mechanism. The sum of the
// commitment's dust with the mailbox's dust with the amount is checked against
// the fee exposure threshold. If incoming is true, then the amount is not
// included in the sum as it was already included in the commitment's dust. A
// boolean is returned telling the caller whether the HTLC should be failed
// back.
func (s *Switch) dustExceedsFeeThreshold(link ChannelLink,
amount lnwire.MilliSatoshi, incoming bool) bool {
// Retrieve the link's current commitment feerate and dustClosure.
feeRate := link.getFeeRate()
isDust := link.getDustClosure()
// Evaluate if the HTLC is dust on either sides' commitment.
isLocalDust := isDust(
feeRate, incoming, lntypes.Local, amount.ToSatoshis(),
)
isRemoteDust := isDust(
feeRate, incoming, lntypes.Remote, amount.ToSatoshis(),
)
if !(isLocalDust || isRemoteDust) {
// If the HTLC is not dust on either commitment, it's fine to
// forward.
return false
}
// Fetch the dust sums currently in the mailbox for this link.
cid := link.ChanID()
sid := link.ShortChanID()
mailbox := s.mailOrchestrator.GetOrCreateMailBox(cid, sid)
localMailDust, remoteMailDust := mailbox.DustPackets()
// If the htlc is dust on the local commitment, we'll obtain the dust
// sum for it.
if isLocalDust {
localSum := link.getDustSum(
lntypes.Local, fn.None[chainfee.SatPerKWeight](),
)
localSum += localMailDust
// Optionally include the HTLC amount only for outgoing
// HTLCs.
if !incoming {
localSum += amount
}
// Finally check against the defined fee threshold.
if localSum > s.cfg.MaxFeeExposure {
return true
}
}
// Also check if the htlc is dust on the remote commitment, if we've
// reached this point.
if isRemoteDust {
remoteSum := link.getDustSum(
lntypes.Remote, fn.None[chainfee.SatPerKWeight](),
)
remoteSum += remoteMailDust
// Optionally include the HTLC amount only for outgoing
// HTLCs.
if !incoming {
remoteSum += amount
}
// Finally check against the defined fee threshold.
if remoteSum > s.cfg.MaxFeeExposure {
return true
}
}
// If we reached this point, this HTLC is fine to forward.
return false
}
// failMailboxUpdate is passed to the mailbox orchestrator which in turn passes
// it to individual mailboxes. It allows the mailboxes to construct a
// FailureMessage when failing back HTLC's due to expiry and may include an
// alias in the ShortChannelID field. The outgoingScid is the SCID originally
// used in the onion. The mailboxScid is the SCID that the mailbox and link
// use. The mailboxScid is only used in the non-alias case, so it is always
// the confirmed SCID.
func (s *Switch) failMailboxUpdate(outgoingScid,
mailboxScid lnwire.ShortChannelID) lnwire.FailureMessage {
// Try to use the failAliasUpdate function in case this is a channel
// that uses aliases. If it returns nil, we'll fallback to the original
// pre-alias behavior.
update := s.failAliasUpdate(outgoingScid, false)
if update == nil {
// Execute the fallback behavior.
var err error
update, err = s.cfg.FetchLastChannelUpdate(mailboxScid)
if err != nil {
return &lnwire.FailTemporaryNodeFailure{}
}
}
return lnwire.NewTemporaryChannelFailure(update)
}
// failAliasUpdate prepares a ChannelUpdate for a failed incoming or outgoing
// HTLC on a channel where the option-scid-alias feature bit was negotiated. If
// the associated channel is not one of these, this function will return nil
// and the caller is expected to handle this properly. In this case, a return
// to the original non-alias behavior is expected.
func (s *Switch) failAliasUpdate(scid lnwire.ShortChannelID,
incoming bool) *lnwire.ChannelUpdate1 {
// This function does not defer the unlocking because of the database
// lookups for ChannelUpdate.
s.indexMtx.RLock()
if s.cfg.IsAlias(scid) {
// The alias SCID was used. In the incoming case this means
// the channel is zero-conf as the link sets the scid. In the
// outgoing case, the sender set the scid to use and may be
// either the alias or the confirmed one, if it exists.
realScid, ok := s.aliasToReal[scid]
if !ok {
// The real, confirmed SCID does not exist yet. Find
// the "base" SCID that the link uses via the
// baseIndex. If we can't find it, return nil. This
// means the channel is zero-conf.
baseScid, ok := s.baseIndex[scid]
s.indexMtx.RUnlock()
if !ok {
return nil
}
update, err := s.cfg.FetchLastChannelUpdate(baseScid)
if err != nil {
return nil
}
// Replace the baseScid with the passed-in alias.
update.ShortChannelID = scid
sig, err := s.cfg.SignAliasUpdate(update)
if err != nil {
return nil
}
update.Signature, err = lnwire.NewSigFromSignature(sig)
if err != nil {
return nil
}
return update
}
s.indexMtx.RUnlock()
// Fetch the SCID via the confirmed SCID and replace it with
// the alias.
update, err := s.cfg.FetchLastChannelUpdate(realScid)
if err != nil {
return nil
}
// In the incoming case, we want to ensure that we don't leak
// the UTXO in case the channel is private. In the outgoing
// case, since the alias was used, we do the same thing.
update.ShortChannelID = scid
sig, err := s.cfg.SignAliasUpdate(update)
if err != nil {
return nil
}
update.Signature, err = lnwire.NewSigFromSignature(sig)
if err != nil {
return nil
}
return update
}
// If the confirmed SCID is not in baseIndex, this is not an
// option-scid-alias or zero-conf channel.
baseScid, ok := s.baseIndex[scid]
if !ok {
s.indexMtx.RUnlock()
return nil
}
// Fetch the link so we can get an alias to use in the ShortChannelID
// of the ChannelUpdate.
link, ok := s.forwardingIndex[baseScid]
s.indexMtx.RUnlock()
if !ok {
// This should never happen, but if it does for some reason,
// fallback to the old behavior.
return nil
}
aliases := link.getAliases()
if len(aliases) == 0 {
// This should never happen, but if it does, fallback.
return nil
}
// Fetch the ChannelUpdate via the real, confirmed SCID.
update, err := s.cfg.FetchLastChannelUpdate(scid)
if err != nil {
return nil
}
// The incoming case will replace the ShortChannelID in the retrieved
// ChannelUpdate with the alias to ensure no privacy leak occurs. This
// would happen if a private non-zero-conf option-scid-alias
// feature-bit channel leaked its UTXO here rather than supplying an
// alias. In the outgoing case, the confirmed SCID was actually used
// for forwarding in the onion, so no replacement is necessary as the
// sender knows the scid.
if incoming {
// We will replace and sign the update with the first alias.
// Since this happens on the incoming side, it's not actually
// possible to know what the sender used in the onion.
update.ShortChannelID = aliases[0]
sig, err := s.cfg.SignAliasUpdate(update)
if err != nil {
return nil
}
update.Signature, err = lnwire.NewSigFromSignature(sig)
if err != nil {
return nil
}
}
return update
}
// AddAliasForLink instructs the Switch to update its in-memory maps to reflect
// that a link has a new alias.
func (s *Switch) AddAliasForLink(chanID lnwire.ChannelID,
alias lnwire.ShortChannelID) error {
// Fetch the link so that we can update the underlying channel's set of
// aliases.
s.indexMtx.RLock()
link, err := s.getLink(chanID)
s.indexMtx.RUnlock()
if err != nil {
return err
}
// If the link is a channel where the option-scid-alias feature bit was
// not negotiated, we'll return an error.
if !link.negotiatedAliasFeature() {
return fmt.Errorf("attempted to update non-alias channel")
}
linkScid := link.ShortChanID()
// We'll update the maps so the Switch includes this alias in its
// forwarding decisions.
if link.isZeroConf() {
if link.zeroConfConfirmed() {
// If the channel has confirmed on-chain, we'll
// add this alias to the aliasToReal map.
confirmedScid := link.confirmedScid()
s.aliasToReal[alias] = confirmedScid
}
// Add this alias to the baseIndex mapping.
s.baseIndex[alias] = linkScid
} else if link.negotiatedAliasFeature() {
// The channel is confirmed, so we'll populate the aliasToReal
// and baseIndex maps.
s.aliasToReal[alias] = linkScid
s.baseIndex[alias] = linkScid
}
return nil
}
// handlePacketAdd handles forwarding an Add packet.
func (s *Switch) handlePacketAdd(packet *htlcPacket,
htlc *lnwire.UpdateAddHTLC) error {
// Check if the node is set to reject all onward HTLCs and also make
// sure that HTLC is not from the source node.
if s.cfg.RejectHTLC {
failure := NewDetailedLinkError(
&lnwire.FailChannelDisabled{},
OutgoingFailureForwardsDisabled,
)
return s.failAddPacket(packet, failure)
}
// Before we attempt to find a non-strict forwarding path for this
// htlc, check whether the htlc is being routed over the same incoming
// and outgoing channel. If our node does not allow forwards of this
// nature, we fail the htlc early. This check is in place to disallow
// inefficiently routed htlcs from locking up our balance. With
// channels where the option-scid-alias feature was negotiated, we also
// have to be sure that the IDs aren't the same since one or both could
// be an alias.
linkErr := s.checkCircularForward(
packet.incomingChanID, packet.outgoingChanID,
s.cfg.AllowCircularRoute, htlc.PaymentHash,
)
if linkErr != nil {
return s.failAddPacket(packet, linkErr)
}
s.indexMtx.RLock()
targetLink, err := s.getLinkByMapping(packet)
if err != nil {
s.indexMtx.RUnlock()
log.Debugf("unable to find link with "+
"destination %v", packet.outgoingChanID)
// If packet was forwarded from another channel link than we
// should notify this link that some error occurred.
linkError := NewLinkError(
&lnwire.FailUnknownNextPeer{},
)
return s.failAddPacket(packet, linkError)
}
targetPeerKey := targetLink.PeerPubKey()
interfaceLinks, _ := s.getLinks(targetPeerKey)
s.indexMtx.RUnlock()
// We'll keep track of any HTLC failures during the link selection
// process. This way we can return the error for precise link that the
// sender selected, while optimistically trying all links to utilize
// our available bandwidth.
linkErrs := make(map[lnwire.ShortChannelID]*LinkError)
// Find all destination channel links with appropriate bandwidth.
var destinations []ChannelLink
for _, link := range interfaceLinks {
var failure *LinkError
// We'll skip any links that aren't yet eligible for
// forwarding.
if !link.EligibleToForward() {
failure = NewDetailedLinkError(
&lnwire.FailUnknownNextPeer{},
OutgoingFailureLinkNotEligible,
)
} else {
// We'll ensure that the HTLC satisfies the current
// forwarding conditions of this target link.
currentHeight := atomic.LoadUint32(&s.bestHeight)
failure = link.CheckHtlcForward(
htlc.PaymentHash, packet.incomingAmount,
packet.amount, packet.incomingTimeout,
packet.outgoingTimeout, packet.inboundFee,
currentHeight, packet.originalOutgoingChanID,
htlc.CustomRecords,
)
}
// If this link can forward the htlc, add it to the set of
// destinations.
if failure == nil {
destinations = append(destinations, link)
continue
}
linkErrs[link.ShortChanID()] = failure
}
// If we had a forwarding failure due to the HTLC not satisfying the
// current policy, then we'll send back an error, but ensure we send
// back the error sourced at the *target* link.
if len(destinations) == 0 {
// At this point, some or all of the links rejected the HTLC so
// we couldn't forward it. So we'll try to look up the error
// that came from the source.
linkErr, ok := linkErrs[packet.outgoingChanID]
if !ok {
// If we can't find the error of the source, then we'll
// return an unknown next peer, though this should
// never happen.
linkErr = NewLinkError(
&lnwire.FailUnknownNextPeer{},
)
log.Warnf("unable to find err source for "+
"outgoing_link=%v, errors=%v",
packet.outgoingChanID,
lnutils.SpewLogClosure(linkErrs))
}
log.Tracef("incoming HTLC(%x) violated "+
"target outgoing link (id=%v) policy: %v",
htlc.PaymentHash[:], packet.outgoingChanID,
linkErr)
return s.failAddPacket(packet, linkErr)
}
// Choose a random link out of the set of links that can forward this
// htlc. The reason for randomization is to evenly distribute the htlc
// load without making assumptions about what the best channel is.
//nolint:gosec
destination := destinations[rand.Intn(len(destinations))]
// Retrieve the incoming link by its ShortChannelID. Note that the
// incomingChanID is never set to hop.Source here.
s.indexMtx.RLock()
incomingLink, err := s.getLinkByShortID(packet.incomingChanID)
s.indexMtx.RUnlock()
if err != nil {
// If we couldn't find the incoming link, we can't evaluate the
// incoming's exposure to dust, so we just fail the HTLC back.
linkErr := NewLinkError(
&lnwire.FailTemporaryChannelFailure{},
)
return s.failAddPacket(packet, linkErr)
}
// Evaluate whether this HTLC would increase our fee exposure over the
// threshold on the incoming link. If it does, fail it backwards.
if s.dustExceedsFeeThreshold(
incomingLink, packet.incomingAmount, true,
) {
// The incoming dust exceeds the threshold, so we fail the add
// back.
linkErr := NewLinkError(
&lnwire.FailTemporaryChannelFailure{},
)
return s.failAddPacket(packet, linkErr)
}
// Also evaluate whether this HTLC would increase our fee exposure over
// the threshold on the destination link. If it does, fail it back.
if s.dustExceedsFeeThreshold(
destination, packet.amount, false,
) {
// The outgoing dust exceeds the threshold, so we fail the add
// back.
linkErr := NewLinkError(
&lnwire.FailTemporaryChannelFailure{},
)
return s.failAddPacket(packet, linkErr)
}
// Send the packet to the destination channel link which manages the
// channel.
packet.outgoingChanID = destination.ShortChanID()
return destination.handleSwitchPacket(packet)
}
// handlePacketSettle handles forwarding a settle packet.
func (s *Switch) handlePacketSettle(packet *htlcPacket) error {
// If the source of this packet has not been set, use the circuit map
// to lookup the origin.
circuit, err := s.closeCircuit(packet)
// If the circuit is in the process of closing, we will return a nil as
// there's another packet handling undergoing.
if errors.Is(err, ErrCircuitClosing) {
log.Debugf("Circuit is closing for packet=%v", packet)
return nil
}
// Exit early if there's another error.
if err != nil {
return err
}
// closeCircuit returns a nil circuit when a settle packet returns an
// ErrUnknownCircuit error upon the inner call to CloseCircuit.
//
// NOTE: We can only get a nil circuit when it has already been deleted
// and when `UpdateFulfillHTLC` is received. After which `RevokeAndAck`
// is received, which invokes `processRemoteSettleFails` in its link.
if circuit == nil {
log.Debugf("Circuit already closed for packet=%v", packet)
return nil
}
localHTLC := packet.incomingChanID == hop.Source
// If this is a locally initiated HTLC, we need to handle the packet by
// storing the network result.
//
// A blank IncomingChanID in a circuit indicates that it is a pending
// user-initiated payment.
//
// NOTE: `closeCircuit` modifies the state of `packet`.
if localHTLC {
// TODO(yy): remove the goroutine and send back the error here.
s.wg.Add(1)
go s.handleLocalResponse(packet)
// If this is a locally initiated HTLC, there's no need to
// forward it so we exit.
return nil
}
// If this is an HTLC settle, and it wasn't from a locally initiated
// HTLC, then we'll log a forwarding event so we can flush it to disk
// later.
if circuit.Outgoing != nil {
log.Infof("Forwarded HTLC(%x) of %v (fee: %v) "+
"from IncomingChanID(%v) to OutgoingChanID(%v)",
circuit.PaymentHash[:], circuit.OutgoingAmount,
circuit.IncomingAmount-circuit.OutgoingAmount,
circuit.Incoming.ChanID, circuit.Outgoing.ChanID)
s.fwdEventMtx.Lock()
s.pendingFwdingEvents = append(
s.pendingFwdingEvents,
channeldb.ForwardingEvent{
Timestamp: time.Now(),
IncomingChanID: circuit.Incoming.ChanID,
OutgoingChanID: circuit.Outgoing.ChanID,
AmtIn: circuit.IncomingAmount,
AmtOut: circuit.OutgoingAmount,
},
)
s.fwdEventMtx.Unlock()
}
// Deliver this packet.
return s.mailOrchestrator.Deliver(packet.incomingChanID, packet)
}
// handlePacketFail handles forwarding a fail packet.
func (s *Switch) handlePacketFail(packet *htlcPacket,
htlc *lnwire.UpdateFailHTLC) error {
// If the source of this packet has not been set, use the circuit map
// to lookup the origin.
circuit, err := s.closeCircuit(packet)
if err != nil {
return err
}
// If this is a locally initiated HTLC, we need to handle the packet by
// storing the network result.
//
// A blank IncomingChanID in a circuit indicates that it is a pending
// user-initiated payment.
//
// NOTE: `closeCircuit` modifies the state of `packet`.
if packet.incomingChanID == hop.Source {
// TODO(yy): remove the goroutine and send back the error here.
s.wg.Add(1)
go s.handleLocalResponse(packet)
// If this is a locally initiated HTLC, there's no need to
// forward it so we exit.
return nil
}
// Exit early if this hasSource is true. This flag is only set via
// mailbox's `FailAdd`. This method has two callsites,
// - the packet has timed out after `MailboxDeliveryTimeout`, defaults
// to 1 min.
// - the HTLC fails the validation in `channel.AddHTLC`.
// In either case, the `Reason` field is populated. Thus there's no
// need to proceed and extract the failure reason below.
if packet.hasSource {
// Deliver this packet.
return s.mailOrchestrator.Deliver(packet.incomingChanID, packet)
}
// HTLC resolutions and messages restored from disk don't have the
// obfuscator set from the original htlc add packet - set it here for
// use in blinded errors.
packet.obfuscator = circuit.ErrorEncrypter
switch {
// No message to encrypt, locally sourced payment.
case circuit.ErrorEncrypter == nil:
// TODO(yy) further check this case as we shouldn't end up here
// as `isLocal` is already false.
// If this is a resolution message, then we'll need to encrypt it as
// it's actually internally sourced.
case packet.isResolution:
var err error
// TODO(roasbeef): don't need to pass actually?
failure := &lnwire.FailPermanentChannelFailure{}
htlc.Reason, err = circuit.ErrorEncrypter.EncryptFirstHop(
failure,
)
if err != nil {
err = fmt.Errorf("unable to obfuscate error: %w", err)
log.Error(err)
}
// Alternatively, if the remote party sends us an
// UpdateFailMalformedHTLC, then we'll need to convert this into a
// proper well formatted onion error as there's no HMAC currently.
case packet.convertedError:
log.Infof("Converting malformed HTLC error for circuit for "+
"Circuit(%x: (%s, %d) <-> (%s, %d))",
packet.circuit.PaymentHash,
packet.incomingChanID, packet.incomingHTLCID,
packet.outgoingChanID, packet.outgoingHTLCID)
htlc.Reason = circuit.ErrorEncrypter.EncryptMalformedError(
htlc.Reason,
)
default:
// Otherwise, it's a forwarded error, so we'll perform a
// wrapper encryption as normal.
htlc.Reason = circuit.ErrorEncrypter.IntermediateEncrypt(
htlc.Reason,
)
}
// Deliver this packet.
return s.mailOrchestrator.Deliver(packet.incomingChanID, packet)
}
package htlcswitch
import (
"bytes"
"context"
crand "crypto/rand"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"fmt"
"net"
"os"
"runtime"
"runtime/pprof"
"sync/atomic"
"testing"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/go-errors/errors"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnpeer"
"github.com/lightningnetwork/lnd/lntest/channels"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/shachain"
"github.com/lightningnetwork/lnd/ticker"
"github.com/stretchr/testify/require"
)
// maxInflightHtlcs specifies the max number of inflight HTLCs. This number is
// chosen to be smaller than the default 483 so the test can run faster.
const maxInflightHtlcs = 50
var (
alicePrivKey = []byte("alice priv key")
bobPrivKey = []byte("bob priv key")
carolPrivKey = []byte("carol priv key")
testRBytes, _ = hex.DecodeString("8ce2bc69281ce27da07e6683571319d18e949ddfa2965fb6caa1bf0314f882d7")
testSBytes, _ = hex.DecodeString("299105481d63e0f4bc2a88121167221b6700d72a0ead154c03be696a292d24ae")
testRScalar = new(btcec.ModNScalar)
testSScalar = new(btcec.ModNScalar)
_ = testRScalar.SetByteSlice(testRBytes)
_ = testSScalar.SetByteSlice(testSBytes)
testSig = ecdsa.NewSignature(testRScalar, testSScalar)
wireSig, _ = lnwire.NewSigFromSignature(testSig)
testBatchTimeout = 50 * time.Millisecond
)
var idSeqNum uint64
// genID generates a unique tuple to identify a test channel.
func genID() (lnwire.ChannelID, lnwire.ShortChannelID) {
id := atomic.AddUint64(&idSeqNum, 1)
var scratch [8]byte
binary.BigEndian.PutUint64(scratch[:], id)
hash1, _ := chainhash.NewHash(bytes.Repeat(scratch[:], 4))
chanPoint1 := wire.NewOutPoint(hash1, uint32(id))
chanID1 := lnwire.NewChanIDFromOutPoint(*chanPoint1)
aliceChanID := lnwire.NewShortChanIDFromInt(id)
return chanID1, aliceChanID
}
// genIDs generates ids for two test channels.
func genIDs() (lnwire.ChannelID, lnwire.ChannelID, lnwire.ShortChannelID,
lnwire.ShortChannelID) {
chanID1, aliceChanID := genID()
chanID2, bobChanID := genID()
return chanID1, chanID2, aliceChanID, bobChanID
}
// mockGetChanUpdateMessage helper function which returns topology update of
// the channel
func mockGetChanUpdateMessage(_ lnwire.ShortChannelID) (*lnwire.ChannelUpdate1,
error) {
return &lnwire.ChannelUpdate1{
Signature: wireSig,
}, nil
}
// generateRandomBytes returns securely generated random bytes.
// It will return an error if the system's secure random
// number generator fails to function correctly, in which
// case the caller should not continue.
func generateRandomBytes(n int) ([]byte, error) {
b := make([]byte, n)
// TODO(roasbeef): should use counter in tests (atomic) rather than
// this
_, err := crand.Read(b)
// Note that Err == nil only if we read len(b) bytes.
if err != nil {
return nil, err
}
return b, nil
}
type testLightningChannel struct {
channel *lnwallet.LightningChannel
restore func() (*lnwallet.LightningChannel, error)
}
// createTestChannel creates the channel and returns our and remote channels
// representations.
//
// TODO(roasbeef): need to factor out, similar func re-used in many parts of codebase
func createTestChannel(t *testing.T, alicePrivKey, bobPrivKey []byte,
aliceAmount, bobAmount, aliceReserve, bobReserve btcutil.Amount,
chanID lnwire.ShortChannelID) (*testLightningChannel,
*testLightningChannel, error) {
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(alicePrivKey)
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(bobPrivKey)
channelCapacity := aliceAmount + bobAmount
csvTimeoutAlice := uint32(5)
csvTimeoutBob := uint32(4)
isAliceInitiator := true
aliceBounds := channeldb.ChannelStateBounds{
MaxPendingAmount: lnwire.NewMSatFromSatoshis(
channelCapacity),
ChanReserve: aliceReserve,
MinHTLC: 0,
MaxAcceptedHtlcs: maxInflightHtlcs,
}
aliceCommitParams := channeldb.CommitmentParams{
DustLimit: btcutil.Amount(200),
CsvDelay: uint16(csvTimeoutAlice),
}
bobBounds := channeldb.ChannelStateBounds{
MaxPendingAmount: lnwire.NewMSatFromSatoshis(
channelCapacity),
ChanReserve: bobReserve,
MinHTLC: 0,
MaxAcceptedHtlcs: maxInflightHtlcs,
}
bobCommitParams := channeldb.CommitmentParams{
DustLimit: btcutil.Amount(800),
CsvDelay: uint16(csvTimeoutBob),
}
var hash [sha256.Size]byte
randomSeed, err := generateRandomBytes(sha256.Size)
if err != nil {
return nil, nil, err
}
copy(hash[:], randomSeed)
prevOut := &wire.OutPoint{
Hash: chainhash.Hash(hash),
Index: 0,
}
fundingTxIn := wire.NewTxIn(prevOut, nil, nil)
aliceCfg := channeldb.ChannelConfig{
ChannelStateBounds: aliceBounds,
CommitmentParams: aliceCommitParams,
MultiSigKey: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
},
RevocationBasePoint: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
},
PaymentBasePoint: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
},
DelayBasePoint: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
},
HtlcBasePoint: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
},
}
bobCfg := channeldb.ChannelConfig{
ChannelStateBounds: bobBounds,
CommitmentParams: bobCommitParams,
MultiSigKey: keychain.KeyDescriptor{
PubKey: bobKeyPub,
},
RevocationBasePoint: keychain.KeyDescriptor{
PubKey: bobKeyPub,
},
PaymentBasePoint: keychain.KeyDescriptor{
PubKey: bobKeyPub,
},
DelayBasePoint: keychain.KeyDescriptor{
PubKey: bobKeyPub,
},
HtlcBasePoint: keychain.KeyDescriptor{
PubKey: bobKeyPub,
},
}
bobRoot, err := chainhash.NewHash(bobKeyPriv.Serialize())
if err != nil {
return nil, nil, err
}
bobPreimageProducer := shachain.NewRevocationProducer(*bobRoot)
bobFirstRevoke, err := bobPreimageProducer.AtIndex(0)
if err != nil {
return nil, nil, err
}
bobCommitPoint := input.ComputeCommitmentPoint(bobFirstRevoke[:])
aliceRoot, err := chainhash.NewHash(aliceKeyPriv.Serialize())
if err != nil {
return nil, nil, err
}
alicePreimageProducer := shachain.NewRevocationProducer(*aliceRoot)
aliceFirstRevoke, err := alicePreimageProducer.AtIndex(0)
if err != nil {
return nil, nil, err
}
aliceCommitPoint := input.ComputeCommitmentPoint(aliceFirstRevoke[:])
aliceCommitTx, bobCommitTx, err := lnwallet.CreateCommitmentTxns(
aliceAmount, bobAmount, &aliceCfg, &bobCfg, aliceCommitPoint,
bobCommitPoint, *fundingTxIn, channeldb.SingleFunderTweaklessBit,
isAliceInitiator, 0,
)
if err != nil {
return nil, nil, err
}
dbAlice := channeldb.OpenForTesting(t, t.TempDir())
dbBob := channeldb.OpenForTesting(t, t.TempDir())
estimator := chainfee.NewStaticEstimator(6000, 0)
feePerKw, err := estimator.EstimateFeePerKW(1)
if err != nil {
return nil, nil, err
}
commitFee := feePerKw.FeeForWeight(724)
const broadcastHeight = 1
bobAddr := &net.TCPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 18555,
}
aliceAddr := &net.TCPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 18556,
}
aliceCommit := channeldb.ChannelCommitment{
CommitHeight: 0,
LocalBalance: lnwire.NewMSatFromSatoshis(aliceAmount - commitFee),
RemoteBalance: lnwire.NewMSatFromSatoshis(bobAmount),
CommitFee: commitFee,
FeePerKw: btcutil.Amount(feePerKw),
CommitTx: aliceCommitTx,
CommitSig: bytes.Repeat([]byte{1}, 71),
}
bobCommit := channeldb.ChannelCommitment{
CommitHeight: 0,
LocalBalance: lnwire.NewMSatFromSatoshis(bobAmount),
RemoteBalance: lnwire.NewMSatFromSatoshis(aliceAmount - commitFee),
CommitFee: commitFee,
FeePerKw: btcutil.Amount(feePerKw),
CommitTx: bobCommitTx,
CommitSig: bytes.Repeat([]byte{1}, 71),
}
aliceChannelState := &channeldb.OpenChannel{
LocalChanCfg: aliceCfg,
RemoteChanCfg: bobCfg,
IdentityPub: aliceKeyPub,
FundingOutpoint: *prevOut,
ChanType: channeldb.SingleFunderTweaklessBit,
IsInitiator: isAliceInitiator,
Capacity: channelCapacity,
RemoteCurrentRevocation: bobCommitPoint,
RevocationProducer: alicePreimageProducer,
RevocationStore: shachain.NewRevocationStore(),
LocalCommitment: aliceCommit,
RemoteCommitment: aliceCommit,
ShortChannelID: chanID,
Db: dbAlice.ChannelStateDB(),
Packager: channeldb.NewChannelPackager(chanID),
FundingTxn: channels.TestFundingTx,
}
bobChannelState := &channeldb.OpenChannel{
LocalChanCfg: bobCfg,
RemoteChanCfg: aliceCfg,
IdentityPub: bobKeyPub,
FundingOutpoint: *prevOut,
ChanType: channeldb.SingleFunderTweaklessBit,
IsInitiator: !isAliceInitiator,
Capacity: channelCapacity,
RemoteCurrentRevocation: aliceCommitPoint,
RevocationProducer: bobPreimageProducer,
RevocationStore: shachain.NewRevocationStore(),
LocalCommitment: bobCommit,
RemoteCommitment: bobCommit,
ShortChannelID: chanID,
Db: dbBob.ChannelStateDB(),
Packager: channeldb.NewChannelPackager(chanID),
}
if err := aliceChannelState.SyncPending(bobAddr, broadcastHeight); err != nil {
return nil, nil, err
}
if err := bobChannelState.SyncPending(aliceAddr, broadcastHeight); err != nil {
return nil, nil, err
}
aliceSigner := input.NewMockSigner(
[]*btcec.PrivateKey{aliceKeyPriv}, nil,
)
bobSigner := input.NewMockSigner(
[]*btcec.PrivateKey{bobKeyPriv}, nil,
)
alicePool := lnwallet.NewSigPool(runtime.NumCPU(), aliceSigner)
signerMock := lnwallet.NewDefaultAuxSignerMock(t)
channelAlice, err := lnwallet.NewLightningChannel(
aliceSigner, aliceChannelState, alicePool,
lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}),
lnwallet.WithAuxSigner(signerMock),
)
if err != nil {
return nil, nil, err
}
alicePool.Start()
bobPool := lnwallet.NewSigPool(runtime.NumCPU(), bobSigner)
channelBob, err := lnwallet.NewLightningChannel(
bobSigner, bobChannelState, bobPool,
lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}),
lnwallet.WithAuxSigner(signerMock),
)
if err != nil {
return nil, nil, err
}
bobPool.Start()
// Now that the channel are open, simulate the start of a session by
// having Alice and Bob extend their revocation windows to each other.
aliceNextRevoke, err := channelAlice.NextRevocationKey()
if err != nil {
return nil, nil, err
}
if err := channelBob.InitNextRevocation(aliceNextRevoke); err != nil {
return nil, nil, err
}
bobNextRevoke, err := channelBob.NextRevocationKey()
if err != nil {
return nil, nil, err
}
if err := channelAlice.InitNextRevocation(bobNextRevoke); err != nil {
return nil, nil, err
}
restoreAlice := func() (*lnwallet.LightningChannel, error) {
aliceStoredChannels, err := dbAlice.ChannelStateDB().
FetchOpenChannels(aliceKeyPub)
switch err {
case nil:
case kvdb.ErrDatabaseNotOpen:
dbAlice = channeldb.OpenForTesting(t, dbAlice.Path())
aliceStoredChannels, err = dbAlice.ChannelStateDB().
FetchOpenChannels(aliceKeyPub)
if err != nil {
return nil, errors.Errorf("unable to fetch alice "+
"channel: %v", err)
}
default:
return nil, errors.Errorf("unable to fetch alice channel: "+
"%v", err)
}
var aliceStoredChannel *channeldb.OpenChannel
for _, channel := range aliceStoredChannels {
if channel.FundingOutpoint.String() == prevOut.String() {
aliceStoredChannel = channel
break
}
}
if aliceStoredChannel == nil {
return nil, errors.New("unable to find stored alice channel")
}
newAliceChannel, err := lnwallet.NewLightningChannel(
aliceSigner, aliceStoredChannel, alicePool,
lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}),
lnwallet.WithAuxSigner(signerMock),
)
if err != nil {
return nil, errors.Errorf("unable to create new channel: %v",
err)
}
return newAliceChannel, nil
}
restoreBob := func() (*lnwallet.LightningChannel, error) {
bobStoredChannels, err := dbBob.ChannelStateDB().
FetchOpenChannels(bobKeyPub)
switch err {
case nil:
case kvdb.ErrDatabaseNotOpen:
dbBob = channeldb.OpenForTesting(t, dbBob.Path())
if err != nil {
return nil, errors.Errorf("unable to reopen bob "+
"db: %v", err)
}
bobStoredChannels, err = dbBob.ChannelStateDB().
FetchOpenChannels(bobKeyPub)
if err != nil {
return nil, errors.Errorf("unable to fetch bob "+
"channel: %v", err)
}
default:
return nil, errors.Errorf("unable to fetch bob channel: "+
"%v", err)
}
var bobStoredChannel *channeldb.OpenChannel
for _, channel := range bobStoredChannels {
if channel.FundingOutpoint.String() == prevOut.String() {
bobStoredChannel = channel
break
}
}
if bobStoredChannel == nil {
return nil, errors.New("unable to find stored bob channel")
}
newBobChannel, err := lnwallet.NewLightningChannel(
bobSigner, bobStoredChannel, bobPool,
lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}),
lnwallet.WithAuxSigner(signerMock),
)
if err != nil {
return nil, errors.Errorf("unable to create new channel: %v",
err)
}
return newBobChannel, nil
}
testLightningChannelAlice := &testLightningChannel{
channel: channelAlice,
restore: restoreAlice,
}
testLightningChannelBob := &testLightningChannel{
channel: channelBob,
restore: restoreBob,
}
return testLightningChannelAlice, testLightningChannelBob, nil
}
// getChanID retrieves the channel point from an lnnwire message.
func getChanID(msg lnwire.Message) (lnwire.ChannelID, error) {
var chanID lnwire.ChannelID
switch msg := msg.(type) {
case *lnwire.UpdateAddHTLC:
chanID = msg.ChanID
case *lnwire.UpdateFulfillHTLC:
chanID = msg.ChanID
case *lnwire.UpdateFailHTLC:
chanID = msg.ChanID
case *lnwire.RevokeAndAck:
chanID = msg.ChanID
case *lnwire.CommitSig:
chanID = msg.ChanID
case *lnwire.ChannelReestablish:
chanID = msg.ChanID
case *lnwire.ChannelReady:
chanID = msg.ChanID
case *lnwire.UpdateFee:
chanID = msg.ChanID
default:
return chanID, fmt.Errorf("unknown type: %T", msg)
}
return chanID, nil
}
// generateHoldPayment generates the htlc add request by given path blob and
// invoice which should be added by destination peer.
func generatePaymentWithPreimage(invoiceAmt, htlcAmt lnwire.MilliSatoshi,
timelock uint32, blob [lnwire.OnionPacketSize]byte,
preimage *lntypes.Preimage, rhash, payAddr [32]byte) (
*invoices.Invoice, *lnwire.UpdateAddHTLC, uint64, error) {
// Create the db invoice. Normally the payment requests needs to be set,
// because it is decoded in InvoiceRegistry to obtain the cltv expiry.
// But because the mock registry used in tests is mocking the decode
// step and always returning the value of testInvoiceCltvExpiry, we
// don't need to bother here with creating and signing a payment
// request.
invoice := &invoices.Invoice{
CreationDate: time.Now(),
Terms: invoices.ContractTerm{
FinalCltvDelta: testInvoiceCltvExpiry,
Value: invoiceAmt,
PaymentPreimage: preimage,
PaymentAddr: payAddr,
Features: lnwire.NewFeatureVector(
nil, lnwire.Features,
),
},
HodlInvoice: preimage == nil,
}
htlc := &lnwire.UpdateAddHTLC{
PaymentHash: rhash,
Amount: htlcAmt,
Expiry: timelock,
OnionBlob: blob,
}
pid, err := generateRandomBytes(8)
if err != nil {
return nil, nil, 0, err
}
paymentID := binary.BigEndian.Uint64(pid)
return invoice, htlc, paymentID, nil
}
// generatePayment generates the htlc add request by given path blob and
// invoice which should be added by destination peer.
func generatePayment(invoiceAmt, htlcAmt lnwire.MilliSatoshi, timelock uint32,
blob [lnwire.OnionPacketSize]byte) (*invoices.Invoice,
*lnwire.UpdateAddHTLC, uint64, error) {
var preimage lntypes.Preimage
r, err := generateRandomBytes(sha256.Size)
if err != nil {
return nil, nil, 0, err
}
copy(preimage[:], r)
rhash := sha256.Sum256(preimage[:])
var payAddr [sha256.Size]byte
r, err = generateRandomBytes(sha256.Size)
if err != nil {
return nil, nil, 0, err
}
copy(payAddr[:], r)
return generatePaymentWithPreimage(
invoiceAmt, htlcAmt, timelock, blob, &preimage, rhash, payAddr,
)
}
// generateRoute generates the path blob by given array of peers.
func generateRoute(hops ...*hop.Payload) (
[lnwire.OnionPacketSize]byte, error) {
var blob [lnwire.OnionPacketSize]byte
if len(hops) == 0 {
return blob, errors.New("empty path")
}
iterator := newMockHopIterator(hops...)
w := bytes.NewBuffer(blob[0:0])
if err := iterator.EncodeNextHop(w); err != nil {
return blob, err
}
return blob, nil
}
// threeHopNetwork is used for managing the created cluster of 3 hops.
type threeHopNetwork struct {
aliceServer *mockServer
aliceChannelLink *channelLink
aliceOnionDecoder *mockIteratorDecoder
bobServer *mockServer
firstBobChannelLink *channelLink
secondBobChannelLink *channelLink
bobOnionDecoder *mockIteratorDecoder
carolServer *mockServer
carolChannelLink *channelLink
carolOnionDecoder *mockIteratorDecoder
hopNetwork
}
// generateHops creates the per hop payload, the total amount to be sent, and
// also the time lock value needed to route an HTLC with the target amount over
// the specified path.
func generateHops(payAmt lnwire.MilliSatoshi, startingHeight uint32,
path ...*channelLink) (lnwire.MilliSatoshi, uint32, []*hop.Payload) {
totalTimelock := startingHeight
runningAmt := payAmt
hops := make([]*hop.Payload, len(path))
for i := len(path) - 1; i >= 0; i-- {
// If this is the last hop, then the next hop is the special
// "exit node". Otherwise, we look to the "prior" hop.
nextHop := hop.Exit
if i != len(path)-1 {
nextHop = path[i+1].channel.ShortChanID()
}
var timeLock uint32
// If this is the last, hop, then the time lock will be their
// specified delta policy plus our starting height.
if i == len(path)-1 {
totalTimelock += testInvoiceCltvExpiry
timeLock = totalTimelock
} else {
// Otherwise, the outgoing time lock should be the
// incoming timelock minus their specified delta.
delta := path[i+1].cfg.FwrdingPolicy.TimeLockDelta
totalTimelock += delta
timeLock = totalTimelock - delta
}
// Finally, we'll need to calculate the amount to forward. For
// the last hop, it's just the payment amount.
amount := payAmt
if i != len(path)-1 {
prevHop := hops[i+1]
prevAmount := prevHop.ForwardingInfo().AmountToForward
fee := ExpectedFee(path[i].cfg.FwrdingPolicy, prevAmount)
runningAmt += fee
// Otherwise, for a node to forward an HTLC, then
// following inequality most hold true:
// * amt_in - fee >= amt_to_forward
amount = runningAmt - fee
}
var nextHopBytes [8]byte
binary.BigEndian.PutUint64(nextHopBytes[:], nextHop.ToUint64())
hops[i] = hop.NewLegacyPayload(&sphinx.HopData{
Realm: [1]byte{}, // hop.BitcoinNetwork
NextAddress: nextHopBytes,
ForwardAmount: uint64(amount),
OutgoingCltv: timeLock,
})
}
return runningAmt, totalTimelock, hops
}
type paymentResponse struct {
rhash lntypes.Hash
err chan error
}
func (r *paymentResponse) Wait(d time.Duration) (lntypes.Hash, error) {
return r.rhash, waitForPaymentResult(r.err, d)
}
// waitForPaymentResult waits for either an error to be received on c or a
// timeout.
func waitForPaymentResult(c chan error, d time.Duration) error {
select {
case err := <-c:
close(c)
return err
case <-time.After(d):
return errors.New("htlc was not settled in time")
}
}
// waitForPayFuncResult executes the given function and waits for a result with
// a timeout.
func waitForPayFuncResult(payFunc func() error, d time.Duration) error {
errChan := make(chan error)
go func() {
errChan <- payFunc()
}()
return waitForPaymentResult(errChan, d)
}
// makePayment takes the destination node and amount as input, sends the
// payment and returns the error channel to wait for error to be received and
// invoice in order to check its status after the payment finished.
//
// With this function you can send payments:
// * from Alice to Bob
// * from Alice to Carol through the Bob
// * from Alice to some another peer through the Bob
func makePayment(sendingPeer, receivingPeer lnpeer.Peer,
firstHop lnwire.ShortChannelID, hops []*hop.Payload,
invoiceAmt, htlcAmt lnwire.MilliSatoshi,
timelock uint32) *paymentResponse {
paymentErr := make(chan error, 1)
var rhash lntypes.Hash
invoice, payFunc, err := preparePayment(sendingPeer, receivingPeer,
firstHop, hops, invoiceAmt, htlcAmt, timelock,
)
if err != nil {
paymentErr <- err
return &paymentResponse{
rhash: rhash,
err: paymentErr,
}
}
rhash = invoice.Terms.PaymentPreimage.Hash()
// Send payment and expose err channel.
go func() {
paymentErr <- payFunc()
}()
return &paymentResponse{
rhash: rhash,
err: paymentErr,
}
}
// preparePayment creates an invoice at the receivingPeer and returns a function
// that, when called, launches the payment from the sendingPeer.
func preparePayment(sendingPeer, receivingPeer lnpeer.Peer,
firstHop lnwire.ShortChannelID, hops []*hop.Payload,
invoiceAmt, htlcAmt lnwire.MilliSatoshi,
timelock uint32) (*invoices.Invoice, func() error, error) {
sender := sendingPeer.(*mockServer)
receiver := receivingPeer.(*mockServer)
// Generate route convert it to blob, and return next destination for
// htlc add request.
blob, err := generateRoute(hops...)
if err != nil {
return nil, nil, err
}
// Generate payment: invoice and htlc.
invoice, htlc, pid, err := generatePayment(
invoiceAmt, htlcAmt, timelock, blob,
)
if err != nil {
return nil, nil, err
}
// Check who is last in the route and add invoice to server registry.
hash := invoice.Terms.PaymentPreimage.Hash()
if err := receiver.registry.AddInvoice(
context.Background(), *invoice, hash,
); err != nil {
return nil, nil, err
}
// Send payment and expose err channel.
return invoice, func() error {
err := sender.htlcSwitch.SendHTLC(
firstHop, pid, htlc,
)
if err != nil {
return err
}
resultChan, err := sender.htlcSwitch.GetAttemptResult(
pid, hash, newMockDeobfuscator(),
)
if err != nil {
return err
}
result, ok := <-resultChan
if !ok {
return fmt.Errorf("shutting down")
}
if result.Error != nil {
return result.Error
}
return nil
}, nil
}
// start starts the three hop network alice,bob,carol servers.
func (n *threeHopNetwork) start() error {
if err := n.aliceServer.Start(); err != nil {
return err
}
if err := n.bobServer.Start(); err != nil {
return err
}
if err := n.carolServer.Start(); err != nil {
return err
}
return waitLinksEligible(map[string]*channelLink{
"alice": n.aliceChannelLink,
"bob first": n.firstBobChannelLink,
"bob second": n.secondBobChannelLink,
"carol": n.carolChannelLink,
})
}
// stop stops nodes and cleanup its databases.
func (n *threeHopNetwork) stop() {
done := make(chan struct{})
go func() {
n.aliceServer.Stop()
done <- struct{}{}
}()
go func() {
n.bobServer.Stop()
done <- struct{}{}
}()
go func() {
n.carolServer.Stop()
done <- struct{}{}
}()
for i := 0; i < 3; i++ {
<-done
}
}
type clusterChannels struct {
aliceToBob *lnwallet.LightningChannel
bobToAlice *lnwallet.LightningChannel
bobToCarol *lnwallet.LightningChannel
carolToBob *lnwallet.LightningChannel
}
// createClusterChannels creates lightning channels which are needed for
// network cluster to be initialized.
func createClusterChannels(t *testing.T, aliceToBob, bobToCarol btcutil.Amount) (
*clusterChannels, func() (*clusterChannels, error), error) {
_, _, firstChanID, secondChanID := genIDs()
// Create lightning channels between Alice<->Bob and Bob<->Carol
aliceChannel, firstBobChannel, err := createTestChannel(t, alicePrivKey,
bobPrivKey, aliceToBob, aliceToBob, 0, 0, firstChanID,
)
if err != nil {
return nil, nil, errors.Errorf("unable to create "+
"alice<->bob channel: %v", err)
}
secondBobChannel, carolChannel, err := createTestChannel(t, bobPrivKey,
carolPrivKey, bobToCarol, bobToCarol, 0, 0, secondChanID,
)
if err != nil {
return nil, nil, errors.Errorf("unable to create "+
"bob<->carol channel: %v", err)
}
restoreFromDb := func() (*clusterChannels, error) {
a2b, err := aliceChannel.restore()
if err != nil {
return nil, err
}
b2a, err := firstBobChannel.restore()
if err != nil {
return nil, err
}
b2c, err := secondBobChannel.restore()
if err != nil {
return nil, err
}
c2b, err := carolChannel.restore()
if err != nil {
return nil, err
}
return &clusterChannels{
aliceToBob: a2b,
bobToAlice: b2a,
bobToCarol: b2c,
carolToBob: c2b,
}, nil
}
return &clusterChannels{
aliceToBob: aliceChannel.channel,
bobToAlice: firstBobChannel.channel,
bobToCarol: secondBobChannel.channel,
carolToBob: carolChannel.channel,
}, restoreFromDb, nil
}
// newThreeHopNetwork function creates the following topology and returns the
// control object to manage this cluster:
//
// alice bob carol
// server - <-connection-> - server - - <-connection-> - - - server
//
// | | |
//
// alice htlc bob htlc carol htlc
// switch switch \ switch
//
// | | \ |
// | | \ |
//
// alice first bob second bob carol
// channel link channel link channel link channel link
//
// This function takes server options which can be used to apply custom
// settings to alice, bob and carol.
func newThreeHopNetwork(t testing.TB, aliceChannel, firstBobChannel,
secondBobChannel, carolChannel *lnwallet.LightningChannel,
startingHeight uint32, opts ...serverOption) *threeHopNetwork {
aliceDb := aliceChannel.State().Db.GetParentDB()
bobDb := firstBobChannel.State().Db.GetParentDB()
carolDb := carolChannel.State().Db.GetParentDB()
hopNetwork := newHopNetwork()
// Create three peers/servers.
aliceServer, err := newMockServer(
t, "alice", startingHeight, aliceDb, hopNetwork.defaultDelta,
)
require.NoError(t, err, "unable to create alice server")
bobServer, err := newMockServer(
t, "bob", startingHeight, bobDb, hopNetwork.defaultDelta,
)
require.NoError(t, err, "unable to create bob server")
carolServer, err := newMockServer(
t, "carol", startingHeight, carolDb, hopNetwork.defaultDelta,
)
require.NoError(t, err, "unable to create carol server")
// Apply all additional functional options to the servers before
// creating any links.
for _, option := range opts {
option(aliceServer, bobServer, carolServer)
}
// Create mock decoder instead of sphinx one in order to mock the route
// which htlc should follow.
aliceDecoder := newMockIteratorDecoder()
bobDecoder := newMockIteratorDecoder()
carolDecoder := newMockIteratorDecoder()
aliceChannelLink, err := hopNetwork.createChannelLink(aliceServer,
bobServer, aliceChannel, aliceDecoder,
)
if err != nil {
t.Fatal(err)
}
firstBobChannelLink, err := hopNetwork.createChannelLink(bobServer,
aliceServer, firstBobChannel, bobDecoder)
if err != nil {
t.Fatal(err)
}
secondBobChannelLink, err := hopNetwork.createChannelLink(bobServer,
carolServer, secondBobChannel, bobDecoder)
if err != nil {
t.Fatal(err)
}
carolChannelLink, err := hopNetwork.createChannelLink(carolServer,
bobServer, carolChannel, carolDecoder)
if err != nil {
t.Fatal(err)
}
return &threeHopNetwork{
aliceServer: aliceServer,
aliceChannelLink: aliceChannelLink.(*channelLink),
aliceOnionDecoder: aliceDecoder,
bobServer: bobServer,
firstBobChannelLink: firstBobChannelLink.(*channelLink),
secondBobChannelLink: secondBobChannelLink.(*channelLink),
bobOnionDecoder: bobDecoder,
carolServer: carolServer,
carolChannelLink: carolChannelLink.(*channelLink),
carolOnionDecoder: carolDecoder,
hopNetwork: *hopNetwork,
}
}
// serverOption is a function which alters the three servers created for
// a three hop network to allow custom settings on each server.
type serverOption func(aliceServer, bobServer, carolServer *mockServer)
// serverOptionWithHtlcNotifier is a functional option for the creation of
// three hop network servers which allows setting of htlc notifiers.
// Note that these notifiers should be started and stopped by the calling
// function.
func serverOptionWithHtlcNotifier(alice, bob,
carol *HtlcNotifier) serverOption {
return func(aliceServer, bobServer, carolServer *mockServer) {
aliceServer.htlcSwitch.cfg.HtlcNotifier = alice
bobServer.htlcSwitch.cfg.HtlcNotifier = bob
carolServer.htlcSwitch.cfg.HtlcNotifier = carol
}
}
// serverOptionRejectHtlc is the functional option for setting the reject
// htlc config option in each server's switch.
func serverOptionRejectHtlc(alice, bob, carol bool) serverOption {
return func(aliceServer, bobServer, carolServer *mockServer) {
aliceServer.htlcSwitch.cfg.RejectHTLC = alice
bobServer.htlcSwitch.cfg.RejectHTLC = bob
carolServer.htlcSwitch.cfg.RejectHTLC = carol
}
}
// createMirroredChannel creates two LightningChannel objects which represent
// the state machines on either side of a single channel between alice and bob.
func createMirroredChannel(t *testing.T, aliceToBob,
bobToAlice btcutil.Amount) (*testLightningChannel,
*testLightningChannel, error) {
_, _, firstChanID, _ := genIDs()
// Create lightning channels between Alice<->Bob for Alice and Bob
alice, bob, err := createTestChannel(t, alicePrivKey, bobPrivKey,
aliceToBob, bobToAlice, 0, 0, firstChanID,
)
if err != nil {
return nil, nil, errors.Errorf("unable to create "+
"alice<->bob channel: %v", err)
}
return alice, bob, nil
}
// hopNetwork is the base struct for two and three hop networks
type hopNetwork struct {
feeEstimator *mockFeeEstimator
globalPolicy models.ForwardingPolicy
obfuscator hop.ErrorEncrypter
defaultDelta uint32
}
func newHopNetwork() *hopNetwork {
defaultDelta := uint32(6)
globalPolicy := models.ForwardingPolicy{
MinHTLCOut: lnwire.NewMSatFromSatoshis(5),
BaseFee: lnwire.NewMSatFromSatoshis(1),
TimeLockDelta: defaultDelta,
}
obfuscator := NewMockObfuscator()
return &hopNetwork{
feeEstimator: newMockFeeEstimator(),
globalPolicy: globalPolicy,
obfuscator: obfuscator,
defaultDelta: defaultDelta,
}
}
func (h *hopNetwork) createChannelLink(server, peer *mockServer,
channel *lnwallet.LightningChannel,
decoder *mockIteratorDecoder) (ChannelLink, error) {
const (
fwdPkgTimeout = 15 * time.Second
minFeeUpdateTimeout = 30 * time.Minute
maxFeeUpdateTimeout = 40 * time.Minute
)
notifyUpdateChan := make(chan *contractcourt.ContractUpdate)
doneChan := make(chan struct{})
notifyContractUpdate := func(u *contractcourt.ContractUpdate) error {
select {
case notifyUpdateChan <- u:
case <-doneChan:
}
return nil
}
getAliases := func(
base lnwire.ShortChannelID) []lnwire.ShortChannelID {
return nil
}
forwardPackets := func(linkQuit <-chan struct{}, _ bool,
packets ...*htlcPacket) error {
return server.htlcSwitch.ForwardPackets(linkQuit, packets...)
}
//nolint:ll
link := NewChannelLink(
ChannelLinkConfig{
BestHeight: server.htlcSwitch.BestHeight,
FwrdingPolicy: h.globalPolicy,
Peer: peer,
Circuits: server.htlcSwitch.CircuitModifier(),
ForwardPackets: forwardPackets,
DecodeHopIterators: decoder.DecodeHopIterators,
ExtractErrorEncrypter: func(*btcec.PublicKey) (
hop.ErrorEncrypter, lnwire.FailCode) {
return h.obfuscator, lnwire.CodeNone
},
FetchLastChannelUpdate: mockGetChanUpdateMessage,
Registry: server.registry,
FeeEstimator: h.feeEstimator,
PreimageCache: server.pCache,
UpdateContractSignals: func(*contractcourt.ContractSignals) error {
return nil
},
NotifyContractUpdate: notifyContractUpdate,
ChainEvents: &contractcourt.ChainEventSubscription{},
SyncStates: true,
BatchSize: 10,
BatchTicker: ticker.NewForce(testBatchTimeout),
FwdPkgGCTicker: ticker.NewForce(fwdPkgTimeout),
PendingCommitTicker: ticker.New(2 * time.Minute),
MinUpdateTimeout: minFeeUpdateTimeout,
MaxUpdateTimeout: maxFeeUpdateTimeout,
OnChannelFailure: func(lnwire.ChannelID, lnwire.ShortChannelID, LinkFailureError) {},
OutgoingCltvRejectDelta: 3,
MaxOutgoingCltvExpiry: DefaultMaxOutgoingCltvExpiry,
MaxFeeAllocation: DefaultMaxLinkFeeAllocation,
MaxAnchorsCommitFeeRate: chainfee.SatPerKVByte(10 * 1000).FeePerKWeight(),
NotifyActiveLink: func(wire.OutPoint) {},
NotifyActiveChannel: func(wire.OutPoint) {},
NotifyInactiveChannel: func(wire.OutPoint) {},
NotifyInactiveLinkEvent: func(wire.OutPoint) {},
HtlcNotifier: server.htlcSwitch.cfg.HtlcNotifier,
GetAliases: getAliases,
ShouldFwdExpEndorsement: func() bool { return true },
},
channel,
)
if err := server.htlcSwitch.AddLink(link); err != nil {
return nil, fmt.Errorf("unable to add channel link: %w", err)
}
go func() {
if chanLink, ok := link.(*channelLink); ok {
for {
select {
case <-notifyUpdateChan:
case <-chanLink.cg.Done():
close(doneChan)
return
}
}
}
}()
return link, nil
}
// twoHopNetwork is used for managing the created cluster of 2 hops.
type twoHopNetwork struct {
hopNetwork
aliceServer *mockServer
aliceChannelLink *channelLink
bobServer *mockServer
bobChannelLink *channelLink
}
// newTwoHopNetwork function creates and starts the following topology and
// returns the control object to manage this cluster:
//
// alice bob
// server - <-connection-> - server
//
// | |
//
// alice htlc bob htlc
// switch switch
//
// | |
// | |
//
// alice bob
// channel link channel link.
func newTwoHopNetwork(t testing.TB,
aliceChannel, bobChannel *lnwallet.LightningChannel,
startingHeight uint32) *twoHopNetwork {
aliceDb := aliceChannel.State().Db.GetParentDB()
bobDb := bobChannel.State().Db.GetParentDB()
hopNetwork := newHopNetwork()
// Create two peers/servers.
aliceServer, err := newMockServer(
t, "alice", startingHeight, aliceDb, hopNetwork.defaultDelta,
)
require.NoError(t, err, "unable to create alice server")
bobServer, err := newMockServer(
t, "bob", startingHeight, bobDb, hopNetwork.defaultDelta,
)
require.NoError(t, err, "unable to create bob server")
// Create mock decoder instead of sphinx one in order to mock the route
// which htlc should follow.
aliceDecoder := newMockIteratorDecoder()
bobDecoder := newMockIteratorDecoder()
aliceChannelLink, err := hopNetwork.createChannelLink(
aliceServer, bobServer, aliceChannel, aliceDecoder,
)
if err != nil {
t.Fatal(err)
}
bobChannelLink, err := hopNetwork.createChannelLink(
bobServer, aliceServer, bobChannel, bobDecoder,
)
if err != nil {
t.Fatal(err)
}
n := &twoHopNetwork{
aliceServer: aliceServer,
aliceChannelLink: aliceChannelLink.(*channelLink),
bobServer: bobServer,
bobChannelLink: bobChannelLink.(*channelLink),
hopNetwork: *hopNetwork,
}
require.NoError(t, n.start())
t.Cleanup(n.stop)
return n
}
// start starts the two hop network alice,bob servers.
func (n *twoHopNetwork) start() error {
if err := n.aliceServer.Start(); err != nil {
return err
}
if err := n.bobServer.Start(); err != nil {
n.aliceServer.Stop()
return err
}
return waitLinksEligible(map[string]*channelLink{
"alice": n.aliceChannelLink,
"bob": n.bobChannelLink,
})
}
// stop stops nodes and cleanup its databases.
func (n *twoHopNetwork) stop() {
done := make(chan struct{})
go func() {
n.aliceServer.Stop()
done <- struct{}{}
}()
go func() {
n.bobServer.Stop()
done <- struct{}{}
}()
for i := 0; i < 2; i++ {
<-done
}
}
func (n *twoHopNetwork) makeHoldPayment(sendingPeer, receivingPeer lnpeer.Peer,
firstHop lnwire.ShortChannelID, hops []*hop.Payload,
invoiceAmt, htlcAmt lnwire.MilliSatoshi,
timelock uint32, preimage lntypes.Preimage) chan error {
paymentErr := make(chan error, 1)
sender := sendingPeer.(*mockServer)
receiver := receivingPeer.(*mockServer)
// Generate route convert it to blob, and return next destination for
// htlc add request.
blob, err := generateRoute(hops...)
if err != nil {
paymentErr <- err
return paymentErr
}
rhash := preimage.Hash()
var payAddr [32]byte
if _, err := crand.Read(payAddr[:]); err != nil {
panic(err)
}
// Generate payment: invoice and htlc.
invoice, htlc, pid, err := generatePaymentWithPreimage(
invoiceAmt, htlcAmt, timelock, blob,
nil, rhash, payAddr,
)
if err != nil {
paymentErr <- err
return paymentErr
}
// Check who is last in the route and add invoice to server registry.
if err := receiver.registry.AddInvoice(
context.Background(), *invoice, rhash,
); err != nil {
paymentErr <- err
return paymentErr
}
// Send payment and expose err channel.
err = sender.htlcSwitch.SendHTLC(firstHop, pid, htlc)
if err != nil {
paymentErr <- err
return paymentErr
}
go func() {
resultChan, err := sender.htlcSwitch.GetAttemptResult(
pid, rhash, newMockDeobfuscator(),
)
if err != nil {
paymentErr <- err
return
}
result, ok := <-resultChan
if !ok {
paymentErr <- fmt.Errorf("shutting down")
return
}
if result.Error != nil {
paymentErr <- result.Error
return
}
paymentErr <- nil
}()
return paymentErr
}
// waitLinksEligible blocks until all links the provided name-to-link map are
// eligible to forward HTLCs.
func waitLinksEligible(links map[string]*channelLink) error {
return wait.NoError(func() error {
for name, link := range links {
if link.EligibleToForward() {
continue
}
return fmt.Errorf("%s channel link not eligible", name)
}
return nil
}, 3*time.Second)
}
// timeout implements a test level timeout.
func timeout() func() {
done := make(chan struct{})
go func() {
select {
case <-time.After(20 * time.Second):
pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
panic("test timeout")
case <-done:
}
}()
return func() {
close(done)
}
}
package input
import (
"fmt"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/tlv"
)
// EmptyOutPoint is a zeroed outpoint.
var EmptyOutPoint wire.OutPoint
// Input represents an abstract UTXO which is to be spent using a sweeping
// transaction. The method provided give the caller all information needed to
// construct a valid input within a sweeping transaction to sweep this
// lingering UTXO.
type Input interface {
// OutPoint returns the reference to the output being spent, used to
// construct the corresponding transaction input.
OutPoint() wire.OutPoint
// RequiredTxOut returns a non-nil TxOut if input commits to a certain
// transaction output. This is used in the SINGLE|ANYONECANPAY case to
// make sure any presigned input is still valid by including the
// output.
RequiredTxOut() *wire.TxOut
// RequiredLockTime returns whether this input commits to a tx locktime
// that must be used in the transaction including it.
RequiredLockTime() (uint32, bool)
// WitnessType returns an enum specifying the type of witness that must
// be generated in order to spend this output.
WitnessType() WitnessType
// SignDesc returns a reference to a spendable output's sign
// descriptor, which is used during signing to compute a valid witness
// that spends this output.
SignDesc() *SignDescriptor
// CraftInputScript returns a valid set of input scripts allowing this
// output to be spent. The returns input scripts should target the
// input at location txIndex within the passed transaction. The input
// scripts generated by this method support spending p2wkh, p2wsh, and
// also nested p2sh outputs.
CraftInputScript(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (*Script, error)
// BlocksToMaturity returns the relative timelock, as a number of
// blocks, that must be built on top of the confirmation height before
// the output can be spent. For non-CSV locked inputs this is always
// zero.
BlocksToMaturity() uint32
// HeightHint returns the minimum height at which a confirmed spending
// tx can occur.
HeightHint() uint32
// UnconfParent returns information about a possibly unconfirmed parent
// tx.
UnconfParent() *TxInfo
// ResolutionBlob returns a special opaque blob to be used to
// sweep/resolve this input.
ResolutionBlob() fn.Option[tlv.Blob]
// Preimage returns the preimage for the input if it is an HTLC input.
Preimage() fn.Option[lntypes.Preimage]
}
// TxInfo describes properties of a parent tx that are relevant for CPFP.
type TxInfo struct {
// Fee is the fee of the tx.
Fee btcutil.Amount
// Weight is the weight of the tx.
Weight lntypes.WeightUnit
}
// String returns a human readable version of the tx info.
func (t *TxInfo) String() string {
return fmt.Sprintf("fee=%v, weight=%v", t.Fee, t.Weight)
}
// SignDetails is a struct containing information needed to resign certain
// inputs. It is used to re-sign 2nd level HTLC transactions that uses the
// SINGLE|ANYONECANPAY sighash type, as we have a signature provided by our
// peer, but we can aggregate multiple of these 2nd level transactions into a
// new transaction, that needs to be signed by us.
type SignDetails struct {
// SignDesc is the sign descriptor needed for us to sign the input.
SignDesc SignDescriptor
// PeerSig is the peer's signature for this input.
PeerSig Signature
// SigHashType is the sighash signed by the peer.
SigHashType txscript.SigHashType
}
type inputKit struct {
outpoint wire.OutPoint
witnessType WitnessType
signDesc SignDescriptor
heightHint uint32
blockToMaturity uint32
cltvExpiry uint32
// unconfParent contains information about a potential unconfirmed
// parent transaction.
unconfParent *TxInfo
// resolutionBlob is an optional blob that can be used to resolve an
// input.
resolutionBlob fn.Option[tlv.Blob]
}
// OutPoint returns the breached output's identifier that is to be included as
// a transaction input.
func (i *inputKit) OutPoint() wire.OutPoint {
return i.outpoint
}
// RequiredTxOut returns a nil for the base input type.
func (i *inputKit) RequiredTxOut() *wire.TxOut {
return nil
}
// RequiredLockTime returns whether this input commits to a tx locktime that
// must be used in the transaction including it. This will be false for the
// base input type since we can re-sign for any lock time.
func (i *inputKit) RequiredLockTime() (uint32, bool) {
return i.cltvExpiry, i.cltvExpiry > 0
}
// WitnessType returns the type of witness that must be generated to spend the
// breached output.
func (i *inputKit) WitnessType() WitnessType {
return i.witnessType
}
// SignDesc returns the breached output's SignDescriptor, which is used during
// signing to compute the witness.
func (i *inputKit) SignDesc() *SignDescriptor {
return &i.signDesc
}
// HeightHint returns the minimum height at which a confirmed spending
// tx can occur.
func (i *inputKit) HeightHint() uint32 {
return i.heightHint
}
// BlocksToMaturity returns the relative timelock, as a number of blocks, that
// must be built on top of the confirmation height before the output can be
// spent. For non-CSV locked inputs this is always zero.
func (i *inputKit) BlocksToMaturity() uint32 {
return i.blockToMaturity
}
// Cpfp returns information about a possibly unconfirmed parent tx.
func (i *inputKit) UnconfParent() *TxInfo {
return i.unconfParent
}
// ResolutionBlob returns a special opaque blob to be used to sweep/resolve
// this input.
func (i *inputKit) ResolutionBlob() fn.Option[tlv.Blob] {
return i.resolutionBlob
}
// inputOpts contains options for constructing a new input.
type inputOpts struct {
// resolutionBlob is an optional blob that can be used to resolve an
// input.
resolutionBlob fn.Option[tlv.Blob]
}
// defaultInputOpts returns a new inputOpts with default values.
func defaultInputOpts() *inputOpts {
return &inputOpts{}
}
// InputOpt is a functional option that can be used to modify the default input
// options.
type InputOpt func(*inputOpts) //nolint:revive
// WithResolutionBlob is an option that can be used to set a resolution blob on
// for an input.
func WithResolutionBlob(b fn.Option[tlv.Blob]) InputOpt {
return func(o *inputOpts) {
o.resolutionBlob = b
}
}
// BaseInput contains all the information needed to sweep a basic
// output (CSV/CLTV/no time lock).
type BaseInput struct {
inputKit
}
// MakeBaseInput assembles a new BaseInput that can be used to construct a
// sweep transaction.
func MakeBaseInput(outpoint *wire.OutPoint, witnessType WitnessType,
signDescriptor *SignDescriptor, heightHint uint32,
unconfParent *TxInfo, opts ...InputOpt) BaseInput {
opt := defaultInputOpts()
for _, optF := range opts {
optF(opt)
}
return BaseInput{
inputKit{
outpoint: *outpoint,
witnessType: witnessType,
signDesc: *signDescriptor,
heightHint: heightHint,
unconfParent: unconfParent,
resolutionBlob: opt.resolutionBlob,
},
}
}
// NewBaseInput allocates and assembles a new *BaseInput that can be used to
// construct a sweep transaction.
func NewBaseInput(outpoint *wire.OutPoint, witnessType WitnessType,
signDescriptor *SignDescriptor, heightHint uint32,
opts ...InputOpt) *BaseInput {
input := MakeBaseInput(
outpoint, witnessType, signDescriptor, heightHint, nil, opts...,
)
return &input
}
// NewCsvInput assembles a new csv-locked input that can be used to
// construct a sweep transaction.
func NewCsvInput(outpoint *wire.OutPoint, witnessType WitnessType,
signDescriptor *SignDescriptor, heightHint uint32,
blockToMaturity uint32, opts ...InputOpt) *BaseInput {
input := MakeBaseInput(
outpoint, witnessType, signDescriptor, heightHint, nil, opts...,
)
input.blockToMaturity = blockToMaturity
return &input
}
// NewCsvInputWithCltv assembles a new csv and cltv locked input that can be
// used to construct a sweep transaction.
func NewCsvInputWithCltv(outpoint *wire.OutPoint, witnessType WitnessType,
signDescriptor *SignDescriptor, heightHint uint32,
csvDelay uint32, cltvExpiry uint32, opts ...InputOpt) *BaseInput {
input := MakeBaseInput(
outpoint, witnessType, signDescriptor, heightHint, nil, opts...,
)
input.blockToMaturity = csvDelay
input.cltvExpiry = cltvExpiry
return &input
}
// CraftInputScript returns a valid set of input scripts allowing this output
// to be spent. The returned input scripts should target the input at location
// txIndex within the passed transaction. The input scripts generated by this
// method support spending p2wkh, p2wsh, and also nested p2sh outputs.
func (bi *BaseInput) CraftInputScript(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script,
error) {
signDesc := bi.SignDesc()
signDesc.PrevOutputFetcher = prevOutputFetcher
witnessFunc := bi.witnessType.WitnessGenerator(signer, signDesc)
return witnessFunc(txn, hashCache, txinIdx)
}
// Preimage returns the preimage for the input if it is an HTLC input.
func (bi *BaseInput) Preimage() fn.Option[lntypes.Preimage] {
return fn.None[lntypes.Preimage]()
}
// HtlcSucceedInput constitutes a sweep input that needs a pre-image. The input
// is expected to reside on the commitment tx of the remote party and should
// not be a second level tx output.
type HtlcSucceedInput struct {
inputKit
preimage []byte
}
// MakeHtlcSucceedInput assembles a new redeem input that can be used to
// construct a sweep transaction.
func MakeHtlcSucceedInput(outpoint *wire.OutPoint,
signDescriptor *SignDescriptor, preimage []byte, heightHint,
blocksToMaturity uint32, opts ...InputOpt) HtlcSucceedInput {
input := MakeBaseInput(
outpoint, HtlcAcceptedRemoteSuccess, signDescriptor,
heightHint, nil, opts...,
)
input.blockToMaturity = blocksToMaturity
return HtlcSucceedInput{
inputKit: input.inputKit,
preimage: preimage,
}
}
// MakeTaprootHtlcSucceedInput creates a new HtlcSucceedInput that can be used
// to spend an HTLC output for a taproot channel on the remote party's
// commitment transaction.
func MakeTaprootHtlcSucceedInput(op *wire.OutPoint, signDesc *SignDescriptor,
preimage []byte, heightHint, blocksToMaturity uint32,
opts ...InputOpt) HtlcSucceedInput {
input := MakeBaseInput(
op, TaprootHtlcAcceptedRemoteSuccess, signDesc,
heightHint, nil, opts...,
)
input.blockToMaturity = blocksToMaturity
return HtlcSucceedInput{
inputKit: input.inputKit,
preimage: preimage,
}
}
// CraftInputScript returns a valid set of input scripts allowing this output
// to be spent. The returns input scripts should target the input at location
// txIndex within the passed transaction. The input scripts generated by this
// method support spending p2wkh, p2wsh, and also nested p2sh outputs.
func (h *HtlcSucceedInput) CraftInputScript(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script,
error) {
desc := h.signDesc
desc.SigHashes = hashCache
desc.InputIndex = txinIdx
desc.PrevOutputFetcher = prevOutputFetcher
isTaproot := txscript.IsPayToTaproot(desc.Output.PkScript)
var (
witness wire.TxWitness
err error
)
if isTaproot {
if desc.ControlBlock == nil {
return nil, fmt.Errorf("ctrl block must be set")
}
desc.SignMethod = TaprootScriptSpendSignMethod
witness, err = SenderHTLCScriptTaprootRedeem(
signer, &desc, txn, h.preimage, nil, nil,
)
} else {
witness, err = SenderHtlcSpendRedeem(
signer, &desc, txn, h.preimage,
)
}
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
}
// Preimage returns the preimage for the input if it is an HTLC input.
func (h *HtlcSucceedInput) Preimage() fn.Option[lntypes.Preimage] {
if len(h.preimage) == 0 {
return fn.None[lntypes.Preimage]()
}
return fn.Some(lntypes.Preimage(h.preimage))
}
// HtlcSecondLevelAnchorInput is an input type used to spend HTLC outputs
// using a re-signed second level transaction, either via the timeout or success
// paths.
type HtlcSecondLevelAnchorInput struct {
inputKit
// SignedTx is the original second level transaction signed by the
// channel peer.
SignedTx *wire.MsgTx
// createWitness creates a witness allowing the passed transaction to
// spend the input.
createWitness func(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (wire.TxWitness, error)
preimage []byte
}
// RequiredTxOut returns the tx out needed to be present on the sweep tx for
// the spend of the input to be valid.
func (i *HtlcSecondLevelAnchorInput) RequiredTxOut() *wire.TxOut {
return i.SignedTx.TxOut[0]
}
// RequiredLockTime returns the locktime needed for the sweep tx for the spend
// of the input to be valid. For a second level HTLC timeout this will be the
// CLTV expiry, for HTLC success it will be zero.
func (i *HtlcSecondLevelAnchorInput) RequiredLockTime() (uint32, bool) {
return i.SignedTx.LockTime, true
}
// CraftInputScript returns a valid set of input scripts allowing this output
// to be spent. The returns input scripts should target the input at location
// txIndex within the passed transaction. The input scripts generated by this
// method support spending p2wkh, p2wsh, and also nested p2sh outputs.
func (i *HtlcSecondLevelAnchorInput) CraftInputScript(signer Signer,
txn *wire.MsgTx, hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (*Script,
error) {
witness, err := i.createWitness(
signer, txn, hashCache, prevOutputFetcher, txinIdx,
)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
}
// Preimage returns the preimage for the input if it is an HTLC input.
func (i *HtlcSecondLevelAnchorInput) Preimage() fn.Option[lntypes.Preimage] {
if len(i.preimage) == 0 {
return fn.None[lntypes.Preimage]()
}
return fn.Some(lntypes.Preimage(i.preimage))
}
// MakeHtlcSecondLevelTimeoutAnchorInput creates an input allowing the sweeper
// to spend the HTLC output on our commit using the second level timeout
// transaction.
func MakeHtlcSecondLevelTimeoutAnchorInput(signedTx *wire.MsgTx,
signDetails *SignDetails, heightHint uint32,
opts ...InputOpt) HtlcSecondLevelAnchorInput {
// Spend an HTLC output on our local commitment tx using the
// 2nd timeout transaction.
createWitness := func(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (wire.TxWitness, error) {
desc := signDetails.SignDesc
desc.SigHashes = txscript.NewTxSigHashes(txn, prevOutputFetcher)
desc.InputIndex = txinIdx
desc.PrevOutputFetcher = prevOutputFetcher
return SenderHtlcSpendTimeout(
signDetails.PeerSig, signDetails.SigHashType, signer,
&desc, txn,
)
}
input := MakeBaseInput(
&signedTx.TxIn[0].PreviousOutPoint,
HtlcOfferedTimeoutSecondLevelInputConfirmed,
&signDetails.SignDesc, heightHint, nil, opts...,
)
input.blockToMaturity = 1
return HtlcSecondLevelAnchorInput{
inputKit: input.inputKit,
SignedTx: signedTx,
createWitness: createWitness,
}
}
// MakeHtlcSecondLevelTimeoutTaprootInput creates an input that allows the
// sweeper to spend an HTLC output to the second level on our commitment
// transaction. The sweeper is also able to generate witnesses on demand to
// sweep the second level HTLC aggregated with other transactions.
func MakeHtlcSecondLevelTimeoutTaprootInput(signedTx *wire.MsgTx,
signDetails *SignDetails,
heightHint uint32, opts ...InputOpt) HtlcSecondLevelAnchorInput {
createWitness := func(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (wire.TxWitness, error) {
desc := signDetails.SignDesc
if desc.ControlBlock == nil {
return nil, fmt.Errorf("ctrl block must be set")
}
desc.SigHashes = txscript.NewTxSigHashes(txn, prevOutputFetcher)
desc.InputIndex = txinIdx
desc.PrevOutputFetcher = prevOutputFetcher
desc.SignMethod = TaprootScriptSpendSignMethod
return SenderHTLCScriptTaprootTimeout(
signDetails.PeerSig, signDetails.SigHashType, signer,
&desc, txn, nil, nil,
)
}
input := MakeBaseInput(
&signedTx.TxIn[0].PreviousOutPoint,
TaprootHtlcLocalOfferedTimeout,
&signDetails.SignDesc, heightHint, nil, opts...,
)
input.blockToMaturity = 1
return HtlcSecondLevelAnchorInput{
inputKit: input.inputKit,
SignedTx: signedTx,
createWitness: createWitness,
}
}
// MakeHtlcSecondLevelSuccessAnchorInput creates an input allowing the sweeper
// to spend the HTLC output on our commit using the second level success
// transaction.
func MakeHtlcSecondLevelSuccessAnchorInput(signedTx *wire.MsgTx,
signDetails *SignDetails, preimage lntypes.Preimage,
heightHint uint32, opts ...InputOpt) HtlcSecondLevelAnchorInput {
// Spend an HTLC output on our local commitment tx using the 2nd
// success transaction.
createWitness := func(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (wire.TxWitness, error) {
desc := signDetails.SignDesc
desc.SigHashes = hashCache
desc.InputIndex = txinIdx
desc.PrevOutputFetcher = prevOutputFetcher
return ReceiverHtlcSpendRedeem(
signDetails.PeerSig, signDetails.SigHashType,
preimage[:], signer, &desc, txn,
)
}
input := MakeBaseInput(
&signedTx.TxIn[0].PreviousOutPoint,
HtlcAcceptedSuccessSecondLevelInputConfirmed,
&signDetails.SignDesc, heightHint, nil, opts...,
)
input.blockToMaturity = 1
return HtlcSecondLevelAnchorInput{
SignedTx: signedTx,
inputKit: input.inputKit,
createWitness: createWitness,
preimage: preimage[:],
}
}
// MakeHtlcSecondLevelSuccessTaprootInput creates an input that allows the
// sweeper to spend an HTLC output to the second level on our taproot
// commitment transaction.
func MakeHtlcSecondLevelSuccessTaprootInput(signedTx *wire.MsgTx,
signDetails *SignDetails, preimage lntypes.Preimage,
heightHint uint32, opts ...InputOpt) HtlcSecondLevelAnchorInput {
createWitness := func(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (wire.TxWitness, error) {
desc := signDetails.SignDesc
if desc.ControlBlock == nil {
return nil, fmt.Errorf("ctrl block must be set")
}
desc.SigHashes = txscript.NewTxSigHashes(txn, prevOutputFetcher)
desc.InputIndex = txinIdx
desc.PrevOutputFetcher = prevOutputFetcher
desc.SignMethod = TaprootScriptSpendSignMethod
return ReceiverHTLCScriptTaprootRedeem(
signDetails.PeerSig, signDetails.SigHashType,
preimage[:], signer, &desc, txn, nil, nil,
)
}
input := MakeBaseInput(
&signedTx.TxIn[0].PreviousOutPoint,
TaprootHtlcAcceptedLocalSuccess,
&signDetails.SignDesc, heightHint, nil, opts...,
)
input.blockToMaturity = 1
return HtlcSecondLevelAnchorInput{
inputKit: input.inputKit,
SignedTx: signedTx,
createWitness: createWitness,
preimage: preimage[:],
}
}
// Compile-time constraints to ensure each input struct implement the Input
// interface.
var _ Input = (*BaseInput)(nil)
var _ Input = (*HtlcSucceedInput)(nil)
var _ Input = (*HtlcSecondLevelAnchorInput)(nil)
package input
import (
"crypto/sha256"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/tlv"
"github.com/stretchr/testify/mock"
)
// MockInput implements the `Input` interface and is used by other packages for
// mock testing.
type MockInput struct {
mock.Mock
}
// Compile time assertion that MockInput implements Input.
var _ Input = (*MockInput)(nil)
// OutPoint returns the reference to the output being spent, used to construct
// the corresponding transaction input.
func (m *MockInput) OutPoint() wire.OutPoint {
args := m.Called()
op := args.Get(0)
return op.(wire.OutPoint)
}
// RequiredTxOut returns a non-nil TxOut if input commits to a certain
// transaction output. This is used in the SINGLE|ANYONECANPAY case to make
// sure any presigned input is still valid by including the output.
func (m *MockInput) RequiredTxOut() *wire.TxOut {
args := m.Called()
txOut := args.Get(0)
if txOut == nil {
return nil
}
return txOut.(*wire.TxOut)
}
// RequiredLockTime returns whether this input commits to a tx locktime that
// must be used in the transaction including it.
func (m *MockInput) RequiredLockTime() (uint32, bool) {
args := m.Called()
return args.Get(0).(uint32), args.Bool(1)
}
// WitnessType returns an enum specifying the type of witness that must be
// generated in order to spend this output.
func (m *MockInput) WitnessType() WitnessType {
args := m.Called()
wt := args.Get(0)
if wt == nil {
return nil
}
return wt.(WitnessType)
}
// SignDesc returns a reference to a spendable output's sign descriptor, which
// is used during signing to compute a valid witness that spends this output.
func (m *MockInput) SignDesc() *SignDescriptor {
args := m.Called()
sd := args.Get(0)
if sd == nil {
return nil
}
return sd.(*SignDescriptor)
}
// CraftInputScript returns a valid set of input scripts allowing this output
// to be spent. The returns input scripts should target the input at location
// txIndex within the passed transaction. The input scripts generated by this
// method support spending p2wkh, p2wsh, and also nested p2sh outputs.
func (m *MockInput) CraftInputScript(signer Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
prevOutputFetcher txscript.PrevOutputFetcher,
txinIdx int) (*Script, error) {
args := m.Called(signer, txn, hashCache, prevOutputFetcher, txinIdx)
s := args.Get(0)
if s == nil {
return nil, args.Error(1)
}
return s.(*Script), args.Error(1)
}
// BlocksToMaturity returns the relative timelock, as a number of blocks, that
// must be built on top of the confirmation height before the output can be
// spent. For non-CSV locked inputs this is always zero.
func (m *MockInput) BlocksToMaturity() uint32 {
args := m.Called()
return args.Get(0).(uint32)
}
// HeightHint returns the minimum height at which a confirmed spending tx can
// occur.
func (m *MockInput) HeightHint() uint32 {
args := m.Called()
return args.Get(0).(uint32)
}
// UnconfParent returns information about a possibly unconfirmed parent tx.
func (m *MockInput) UnconfParent() *TxInfo {
args := m.Called()
info := args.Get(0)
if info == nil {
return nil
}
return info.(*TxInfo)
}
func (m *MockInput) ResolutionBlob() fn.Option[tlv.Blob] {
args := m.Called()
info := args.Get(0)
if info == nil {
return fn.None[tlv.Blob]()
}
return info.(fn.Option[tlv.Blob])
}
func (m *MockInput) Preimage() fn.Option[lntypes.Preimage] {
args := m.Called()
info := args.Get(0)
if info == nil {
return fn.None[lntypes.Preimage]()
}
return info.(fn.Option[lntypes.Preimage])
}
// MockWitnessType implements the `WitnessType` interface and is used by other
// packages for mock testing.
type MockWitnessType struct {
mock.Mock
}
// Compile time assertion that MockWitnessType implements WitnessType.
var _ WitnessType = (*MockWitnessType)(nil)
// String returns a human readable version of the WitnessType.
func (m *MockWitnessType) String() string {
args := m.Called()
return args.String(0)
}
// WitnessGenerator will return a WitnessGenerator function that an output uses
// to generate the witness and optionally the sigScript for a sweep
// transaction.
func (m *MockWitnessType) WitnessGenerator(signer Signer,
descriptor *SignDescriptor) WitnessGenerator {
args := m.Called()
return args.Get(0).(WitnessGenerator)
}
// SizeUpperBound returns the maximum length of the witness of this WitnessType
// if it would be included in a tx. It also returns if the output itself is a
// nested p2sh output, if so then we need to take into account the extra
// sigScript data size.
func (m *MockWitnessType) SizeUpperBound() (lntypes.WeightUnit, bool, error) {
args := m.Called()
return args.Get(0).(lntypes.WeightUnit), args.Bool(1), args.Error(2)
}
// AddWeightEstimation adds the estimated size of the witness in bytes to the
// given weight estimator.
func (m *MockWitnessType) AddWeightEstimation(e *TxWeightEstimator) error {
args := m.Called()
return args.Error(0)
}
// MockInputSigner is a mock implementation of the Signer interface.
type MockInputSigner struct {
mock.Mock
}
// Compile-time constraint to ensure MockInputSigner implements Signer.
var _ Signer = (*MockInputSigner)(nil)
// SignOutputRaw generates a signature for the passed transaction according to
// the data within the passed SignDescriptor.
func (m *MockInputSigner) SignOutputRaw(tx *wire.MsgTx,
signDesc *SignDescriptor) (Signature, error) {
args := m.Called(tx, signDesc)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(Signature), args.Error(1)
}
// ComputeInputScript generates a complete InputIndex for the passed
// transaction with the signature as defined within the passed SignDescriptor.
func (m *MockInputSigner) ComputeInputScript(tx *wire.MsgTx,
signDesc *SignDescriptor) (*Script, error) {
args := m.Called(tx, signDesc)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*Script), args.Error(1)
}
// MuSig2CreateSession creates a new MuSig2 signing session using the local key
// identified by the key locator.
func (m *MockInputSigner) MuSig2CreateSession(version MuSig2Version,
locator keychain.KeyLocator, pubkey []*btcec.PublicKey,
tweak *MuSig2Tweaks, pubNonces [][musig2.PubNonceSize]byte,
nonces *musig2.Nonces) (*MuSig2SessionInfo, error) {
args := m.Called(version, locator, pubkey, tweak, pubNonces, nonces)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*MuSig2SessionInfo), args.Error(1)
}
// MuSig2RegisterNonces registers one or more public nonces of other signing
// participants for a session identified by its ID.
func (m *MockInputSigner) MuSig2RegisterNonces(versio MuSig2SessionID,
pubNonces [][musig2.PubNonceSize]byte) (bool, error) {
args := m.Called(versio, pubNonces)
if args.Get(0) == nil {
return false, args.Error(1)
}
return args.Bool(0), args.Error(1)
}
// MuSig2Sign creates a partial signature using the local signing key that was
// specified when the session was created.
func (m *MockInputSigner) MuSig2Sign(sessionID MuSig2SessionID,
msg [sha256.Size]byte, withSortedKeys bool) (
*musig2.PartialSignature, error) {
args := m.Called(sessionID, msg, withSortedKeys)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*musig2.PartialSignature), args.Error(1)
}
// MuSig2CombineSig combines the given partial signature(s) with the local one,
// if it already exists.
func (m *MockInputSigner) MuSig2CombineSig(sessionID MuSig2SessionID,
partialSig []*musig2.PartialSignature) (
*schnorr.Signature, bool, error) {
args := m.Called(sessionID, partialSig)
if args.Get(0) == nil {
return nil, false, args.Error(2)
}
return args.Get(0).(*schnorr.Signature), args.Bool(1), args.Error(2)
}
// MuSig2Cleanup removes a session from memory to free up resources.
func (m *MockInputSigner) MuSig2Cleanup(sessionID MuSig2SessionID) error {
args := m.Called(sessionID)
return args.Error(0)
}
package input
import (
"bytes"
"crypto/sha256"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/lightningnetwork/lnd/internal/musig2v040"
"github.com/lightningnetwork/lnd/keychain"
)
// MuSig2Version is a type that defines the different versions of the MuSig2
// as defined in the BIP draft:
// (https://github.com/jonasnick/bips/blob/musig2/bip-musig2.mediawiki)
type MuSig2Version uint8
const (
// MuSig2Version040 is version 0.4.0 of the MuSig2 BIP draft. This will
// use the lnd internal/musig2v040 package.
MuSig2Version040 MuSig2Version = 0
// MuSig2Version100RC2 is version 1.0.0rc2 of the MuSig2 BIP draft. This
// uses the github.com/btcsuite/btcd/btcec/v2/schnorr/musig2 package
// at git tag `btcec/v2.3.1`.
MuSig2Version100RC2 MuSig2Version = 1
)
const (
// MuSig2PartialSigSize is the size of a MuSig2 partial signature.
// Because a partial signature is just the s value, this corresponds to
// the length of a scalar.
MuSig2PartialSigSize = 32
)
// MuSig2SessionID is a type for a session ID that is just a hash of the MuSig2
// combined key and the local public nonces.
type MuSig2SessionID [sha256.Size]byte
// MuSig2Signer is an interface that declares all methods that a MuSig2
// compatible signer needs to implement.
type MuSig2Signer interface {
// MuSig2CreateSession creates a new MuSig2 signing session using the
// local key identified by the key locator. The complete list of all
// public keys of all signing parties must be provided, including the
// public key of the local signing key. If nonces of other parties are
// already known, they can be submitted as well to reduce the number of
// method calls necessary later on.
//
// The localNonces field is optional. If it is set, then the specified
// nonces will be used instead of generating from scratch. This is
// useful in instances where the nonces are generated ahead of time
// before the set of signers is known.
MuSig2CreateSession(MuSig2Version, keychain.KeyLocator,
[]*btcec.PublicKey, *MuSig2Tweaks, [][musig2.PubNonceSize]byte,
*musig2.Nonces) (*MuSig2SessionInfo, error)
// MuSig2RegisterNonces registers one or more public nonces of other
// signing participants for a session identified by its ID. This method
// returns true once we have all nonces for all other signing
// participants.
MuSig2RegisterNonces(MuSig2SessionID,
[][musig2.PubNonceSize]byte) (bool, error)
// MuSig2Sign creates a partial signature using the local signing key
// that was specified when the session was created. This can only be
// called when all public nonces of all participants are known and have
// been registered with the session. If this node isn't responsible for
// combining all the partial signatures, then the cleanup parameter
// should be set, indicating that the session can be removed from memory
// once the signature was produced.
MuSig2Sign(MuSig2SessionID, [sha256.Size]byte,
bool) (*musig2.PartialSignature, error)
// MuSig2CombineSig combines the given partial signature(s) with the
// local one, if it already exists. Once a partial signature of all
// participants is registered, the final signature will be combined and
// returned.
MuSig2CombineSig(MuSig2SessionID,
[]*musig2.PartialSignature) (*schnorr.Signature, bool, error)
// MuSig2Cleanup removes a session from memory to free up resources.
MuSig2Cleanup(MuSig2SessionID) error
}
// MuSig2Context is an interface that is an abstraction over the MuSig2 signing
// context. This interface does not contain all of the methods the underlying
// implementations have because those use package specific types which cannot
// easily be made compatible. Those calls (such as NewSession) are implemented
// in this package instead and do the necessary type switch (see
// MuSig2CreateContext).
type MuSig2Context interface {
// SigningKeys returns the set of keys used for signing.
SigningKeys() []*btcec.PublicKey
// CombinedKey returns the combined public key that will be used to
// generate multi-signatures against.
CombinedKey() (*btcec.PublicKey, error)
// TaprootInternalKey returns the internal taproot key, which is the
// aggregated key _before_ the tweak is applied. If a taproot tweak was
// specified, then CombinedKey() will return the fully tweaked output
// key, with this method returning the internal key. If a taproot tweak
// wasn't specified, then this method will return an error.
TaprootInternalKey() (*btcec.PublicKey, error)
}
// MuSig2Session is an interface that is an abstraction over the MuSig2 signing
// session. This interface does not contain all of the methods the underlying
// implementations have because those use package specific types which cannot
// easily be made compatible. Those calls (such as CombineSig or Sign) are
// implemented in this package instead and do the necessary type switch (see
// MuSig2CombineSig or MuSig2Sign).
type MuSig2Session interface {
// FinalSig returns the final combined multi-signature, if present.
FinalSig() *schnorr.Signature
// PublicNonce returns the public nonce for a signer. This should be
// sent to other parties before signing begins, so they can compute the
// aggregated public nonce.
PublicNonce() [musig2.PubNonceSize]byte
// NumRegisteredNonces returns the total number of nonces that have been
// registered so far.
NumRegisteredNonces() int
// RegisterPubNonce should be called for each public nonce from the set
// of signers. This method returns true once all the public nonces have
// been accounted for.
RegisterPubNonce(nonce [musig2.PubNonceSize]byte) (bool, error)
}
// MuSig2SessionInfo is a struct for keeping track of a signing session
// information in memory.
type MuSig2SessionInfo struct {
// SessionID is the wallet's internal unique ID of this session. The ID
// is the hash over the combined public key and the local public nonces.
SessionID [32]byte
// Version is the version of the MuSig2 BIP this signing session is
// using.
Version MuSig2Version
// PublicNonce contains the public nonce of the local signer session.
PublicNonce [musig2.PubNonceSize]byte
// CombinedKey is the combined public key with all tweaks applied to it.
CombinedKey *btcec.PublicKey
// TaprootTweak indicates whether a taproot tweak (BIP-0086 or script
// path) was used. The TaprootInternalKey will only be set if this is
// set to true.
TaprootTweak bool
// TaprootInternalKey is the raw combined public key without any tweaks
// applied to it. This is only set if TaprootTweak is true.
TaprootInternalKey *btcec.PublicKey
// HaveAllNonces indicates whether this session already has all nonces
// of all other signing participants registered.
HaveAllNonces bool
// HaveAllSigs indicates whether this session already has all partial
// signatures of all other signing participants registered.
HaveAllSigs bool
}
// MuSig2Tweaks is a struct that contains all tweaks that can be applied to a
// MuSig2 combined public key.
type MuSig2Tweaks struct {
// GenericTweaks is a list of normal tweaks to apply to the combined
// public key (and to the private key when signing).
GenericTweaks []musig2.KeyTweakDesc
// TaprootBIP0086Tweak indicates that the final key should use the
// taproot tweak as defined in BIP 341, with the BIP 86 modification:
// outputKey = internalKey + h_tapTweak(internalKey)*G.
// In this case, the aggregated key before the tweak will be used as the
// internal key. If this is set to true then TaprootTweak will be
// ignored.
TaprootBIP0086Tweak bool
// TaprootTweak specifies that the final key should use the taproot
// tweak as defined in BIP 341:
// outputKey = internalKey + h_tapTweak(internalKey || scriptRoot).
// In this case, the aggregated key before the tweak will be used as the
// internal key. Will be ignored if TaprootBIP0086Tweak is set to true.
TaprootTweak []byte
}
// HasTaprootTweak returns true if either a taproot BIP0086 tweak or a taproot
// script root tweak is set.
func (t *MuSig2Tweaks) HasTaprootTweak() bool {
return t.TaprootBIP0086Tweak || len(t.TaprootTweak) > 0
}
// ToContextOptions converts the tweak descriptor to context options.
func (t *MuSig2Tweaks) ToContextOptions() []musig2.ContextOption {
var tweakOpts []musig2.ContextOption
if len(t.GenericTweaks) > 0 {
tweakOpts = append(tweakOpts, musig2.WithTweakedContext(
t.GenericTweaks...,
))
}
// The BIP0086 tweak and the taproot script tweak are mutually
// exclusive.
if t.TaprootBIP0086Tweak {
tweakOpts = append(tweakOpts, musig2.WithBip86TweakCtx())
} else if len(t.TaprootTweak) > 0 {
tweakOpts = append(tweakOpts, musig2.WithTaprootTweakCtx(
t.TaprootTweak,
))
}
return tweakOpts
}
// ToV040ContextOptions converts the tweak descriptor to v0.4.0 context options.
func (t *MuSig2Tweaks) ToV040ContextOptions() []musig2v040.ContextOption {
var tweakOpts []musig2v040.ContextOption
if len(t.GenericTweaks) > 0 {
genericTweaksCopy := make(
[]musig2v040.KeyTweakDesc, len(t.GenericTweaks),
)
for idx := range t.GenericTweaks {
genericTweaksCopy[idx] = musig2v040.KeyTweakDesc{
Tweak: t.GenericTweaks[idx].Tweak,
IsXOnly: t.GenericTweaks[idx].IsXOnly,
}
}
tweakOpts = append(tweakOpts, musig2v040.WithTweakedContext(
genericTweaksCopy...,
))
}
// The BIP0086 tweak and the taproot script tweak are mutually
// exclusive.
if t.TaprootBIP0086Tweak {
tweakOpts = append(tweakOpts, musig2v040.WithBip86TweakCtx())
} else if len(t.TaprootTweak) > 0 {
tweakOpts = append(tweakOpts, musig2v040.WithTaprootTweakCtx(
t.TaprootTweak,
))
}
return tweakOpts
}
// MuSig2ParsePubKeys parses a list of raw public keys as the signing keys of a
// MuSig2 signing session.
func MuSig2ParsePubKeys(bipVersion MuSig2Version,
rawPubKeys [][]byte) ([]*btcec.PublicKey, error) {
allSignerPubKeys := make([]*btcec.PublicKey, len(rawPubKeys))
if len(rawPubKeys) < 2 {
return nil, fmt.Errorf("need at least two signing public keys")
}
for idx, pubKeyBytes := range rawPubKeys {
switch bipVersion {
case MuSig2Version040:
pubKey, err := schnorr.ParsePubKey(pubKeyBytes)
if err != nil {
return nil, fmt.Errorf("error parsing signer "+
"public key %d for v0.4.0 (x-only "+
"format): %v", idx, err)
}
allSignerPubKeys[idx] = pubKey
case MuSig2Version100RC2:
pubKey, err := btcec.ParsePubKey(pubKeyBytes)
if err != nil {
return nil, fmt.Errorf("error parsing signer "+
"public key %d for v1.0.0rc2 ("+
"compressed format): %v", idx, err)
}
allSignerPubKeys[idx] = pubKey
default:
return nil, fmt.Errorf("unknown MuSig2 version: <%d>",
bipVersion)
}
}
return allSignerPubKeys, nil
}
// MuSig2CombineKeys combines the given set of public keys into a single
// combined MuSig2 combined public key, applying the given tweaks.
func MuSig2CombineKeys(bipVersion MuSig2Version,
allSignerPubKeys []*btcec.PublicKey, sortKeys bool,
tweaks *MuSig2Tweaks) (*musig2.AggregateKey, error) {
switch bipVersion {
case MuSig2Version040:
return combineKeysV040(allSignerPubKeys, sortKeys, tweaks)
case MuSig2Version100RC2:
return combineKeysV100RC2(allSignerPubKeys, sortKeys, tweaks)
default:
return nil, fmt.Errorf("unknown MuSig2 version: <%d>",
bipVersion)
}
}
// combineKeysV100rc1 implements the MuSigCombineKeys logic for the MuSig2 BIP
// draft version 1.0.0rc2.
func combineKeysV100RC2(allSignerPubKeys []*btcec.PublicKey, sortKeys bool,
tweaks *MuSig2Tweaks) (*musig2.AggregateKey, error) {
// Convert the tweak options into the appropriate MuSig2 API functional
// options.
var keyAggOpts []musig2.KeyAggOption
switch {
case tweaks.TaprootBIP0086Tweak:
keyAggOpts = append(keyAggOpts, musig2.WithBIP86KeyTweak())
case len(tweaks.TaprootTweak) > 0:
keyAggOpts = append(keyAggOpts, musig2.WithTaprootKeyTweak(
tweaks.TaprootTweak,
))
case len(tweaks.GenericTweaks) > 0:
keyAggOpts = append(keyAggOpts, musig2.WithKeyTweaks(
tweaks.GenericTweaks...,
))
}
// Then we'll use this information to compute the aggregated public key.
combinedKey, _, _, err := musig2.AggregateKeys(
allSignerPubKeys, sortKeys, keyAggOpts...,
)
return combinedKey, err
}
// combineKeysV040 implements the MuSigCombineKeys logic for the MuSig2 BIP
// draft version 0.4.0.
func combineKeysV040(allSignerPubKeys []*btcec.PublicKey, sortKeys bool,
tweaks *MuSig2Tweaks) (*musig2.AggregateKey, error) {
// Convert the tweak options into the appropriate MuSig2 API functional
// options.
var keyAggOpts []musig2v040.KeyAggOption
switch {
case tweaks.TaprootBIP0086Tweak:
keyAggOpts = append(keyAggOpts, musig2v040.WithBIP86KeyTweak())
case len(tweaks.TaprootTweak) > 0:
keyAggOpts = append(keyAggOpts, musig2v040.WithTaprootKeyTweak(
tweaks.TaprootTweak,
))
case len(tweaks.GenericTweaks) > 0:
genericTweaksCopy := make(
[]musig2v040.KeyTweakDesc, len(tweaks.GenericTweaks),
)
for idx := range tweaks.GenericTweaks {
genericTweaksCopy[idx] = musig2v040.KeyTweakDesc{
Tweak: tweaks.GenericTweaks[idx].Tweak,
IsXOnly: tweaks.GenericTweaks[idx].IsXOnly,
}
}
keyAggOpts = append(keyAggOpts, musig2v040.WithKeyTweaks(
genericTweaksCopy...,
))
}
// Then we'll use this information to compute the aggregated public key.
combinedKey, _, _, err := musig2v040.AggregateKeys(
allSignerPubKeys, sortKeys, keyAggOpts...,
)
// Copy the result back into the default version's native type.
return &musig2.AggregateKey{
FinalKey: combinedKey.FinalKey,
PreTweakedKey: combinedKey.PreTweakedKey,
}, err
}
// MuSig2CreateContext creates a new MuSig2 signing context.
func MuSig2CreateContext(bipVersion MuSig2Version, privKey *btcec.PrivateKey,
allSignerPubKeys []*btcec.PublicKey, tweaks *MuSig2Tweaks,
localNonces *musig2.Nonces,
) (MuSig2Context, MuSig2Session, error) {
switch bipVersion {
case MuSig2Version040:
return createContextV040(
privKey, allSignerPubKeys, tweaks, localNonces,
)
case MuSig2Version100RC2:
return createContextV100RC2(
privKey, allSignerPubKeys, tweaks, localNonces,
)
default:
return nil, nil, fmt.Errorf("unknown MuSig2 version: <%d>",
bipVersion)
}
}
// createContextV100RC2 implements the MuSig2CreateContext logic for the MuSig2
// BIP draft version 1.0.0rc2.
func createContextV100RC2(privKey *btcec.PrivateKey,
allSignerPubKeys []*btcec.PublicKey, tweaks *MuSig2Tweaks,
localNonces *musig2.Nonces,
) (*musig2.Context, *musig2.Session, error) {
// The context keeps track of all signing keys and our local key.
allOpts := append(
[]musig2.ContextOption{
musig2.WithKnownSigners(allSignerPubKeys),
},
tweaks.ToContextOptions()...,
)
muSigContext, err := musig2.NewContext(privKey, true, allOpts...)
if err != nil {
return nil, nil, fmt.Errorf("error creating MuSig2 signing "+
"context: %v", err)
}
var sessionOpts []musig2.SessionOption
if localNonces != nil {
sessionOpts = append(
sessionOpts, musig2.WithPreGeneratedNonce(localNonces),
)
}
muSigSession, err := muSigContext.NewSession(sessionOpts...)
if err != nil {
return nil, nil, fmt.Errorf("error creating MuSig2 signing "+
"session: %v", err)
}
return muSigContext, muSigSession, nil
}
// createContextV040 implements the MuSig2CreateContext logic for the MuSig2 BIP
// draft version 0.4.0.
func createContextV040(privKey *btcec.PrivateKey,
allSignerPubKeys []*btcec.PublicKey, tweaks *MuSig2Tweaks,
_ *musig2.Nonces,
) (*musig2v040.Context, *musig2v040.Session, error) {
// The context keeps track of all signing keys and our local key.
allOpts := append(
[]musig2v040.ContextOption{
musig2v040.WithKnownSigners(allSignerPubKeys),
},
tweaks.ToV040ContextOptions()...,
)
muSigContext, err := musig2v040.NewContext(privKey, true, allOpts...)
if err != nil {
return nil, nil, fmt.Errorf("error creating MuSig2 signing "+
"context: %v", err)
}
muSigSession, err := muSigContext.NewSession()
if err != nil {
return nil, nil, fmt.Errorf("error creating MuSig2 signing "+
"session: %v", err)
}
return muSigContext, muSigSession, nil
}
// MuSig2Sign calls the Sign() method on the given versioned signing session and
// returns the result in the most recent version of the MuSig2 API.
func MuSig2Sign(session MuSig2Session, msg [32]byte,
withSortedKeys bool) (*musig2.PartialSignature, error) {
switch s := session.(type) {
case *musig2.Session:
var opts []musig2.SignOption
if withSortedKeys {
opts = append(opts, musig2.WithSortedKeys())
}
partialSig, err := s.Sign(msg, opts...)
if err != nil {
return nil, fmt.Errorf("error signing with local key: "+
"%v", err)
}
return partialSig, nil
case *musig2v040.Session:
var opts []musig2v040.SignOption
if withSortedKeys {
opts = append(opts, musig2v040.WithSortedKeys())
}
partialSig, err := s.Sign(msg, opts...)
if err != nil {
return nil, fmt.Errorf("error signing with local key: "+
"%v", err)
}
return &musig2.PartialSignature{
S: partialSig.S,
R: partialSig.R,
}, nil
default:
return nil, fmt.Errorf("invalid session type <%T>", s)
}
}
// MuSig2CombineSig calls the CombineSig() method on the given versioned signing
// session and returns the result in the most recent version of the MuSig2 API.
func MuSig2CombineSig(session MuSig2Session,
otherPartialSig *musig2.PartialSignature) (bool, error) {
switch s := session.(type) {
case *musig2.Session:
haveAllSigs, err := s.CombineSig(otherPartialSig)
if err != nil {
return false, fmt.Errorf("error combining partial "+
"signature: %v", err)
}
return haveAllSigs, nil
case *musig2v040.Session:
haveAllSigs, err := s.CombineSig(&musig2v040.PartialSignature{
S: otherPartialSig.S,
R: otherPartialSig.R,
})
if err != nil {
return false, fmt.Errorf("error combining partial "+
"signature: %v", err)
}
return haveAllSigs, nil
default:
return false, fmt.Errorf("invalid session type <%T>", s)
}
}
// NewMuSig2SessionID returns the unique ID of a MuSig2 session by using the
// combined key and the local public nonces and hashing that data.
func NewMuSig2SessionID(combinedKey *btcec.PublicKey,
publicNonces [musig2.PubNonceSize]byte) MuSig2SessionID {
// We hash the data to save some bytes in memory.
hash := sha256.New()
_, _ = hash.Write(combinedKey.SerializeCompressed())
_, _ = hash.Write(publicNonces[:])
id := MuSig2SessionID{}
copy(id[:], hash.Sum(nil))
return id
}
// SerializePartialSignature encodes the partial signature to a fixed size byte
// array.
func SerializePartialSignature(
sig *musig2.PartialSignature) ([MuSig2PartialSigSize]byte, error) {
var (
buf bytes.Buffer
result [MuSig2PartialSigSize]byte
)
if err := sig.Encode(&buf); err != nil {
return result, fmt.Errorf("error encoding partial signature: "+
"%v", err)
}
if buf.Len() != MuSig2PartialSigSize {
return result, fmt.Errorf("invalid partial signature length, "+
"got %d wanted %d", buf.Len(), MuSig2PartialSigSize)
}
copy(result[:], buf.Bytes())
return result, nil
}
// DeserializePartialSignature decodes a partial signature from a byte slice.
func DeserializePartialSignature(scalarBytes []byte) (*musig2.PartialSignature,
error) {
if len(scalarBytes) != MuSig2PartialSigSize {
return nil, fmt.Errorf("invalid partial signature length, got "+
"%d wanted %d", len(scalarBytes), MuSig2PartialSigSize)
}
sig := &musig2.PartialSignature{}
if err := sig.Decode(bytes.NewReader(scalarBytes)); err != nil {
return nil, fmt.Errorf("error decoding partial signature: %w",
err)
}
return sig, nil
}
package input
import (
"crypto/sha256"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/multimutex"
)
// MuSig2State is a struct that holds on to the internal signing session state
// of a MuSig2 session.
type MuSig2State struct {
// MuSig2SessionInfo is the associated meta information of the signing
// session.
MuSig2SessionInfo
// context is the signing context responsible for keeping track of the
// public keys involved in the signing process.
context MuSig2Context
// session is the signing session responsible for keeping track of the
// nonces and partial signatures involved in the signing process.
session MuSig2Session
}
// PrivKeyFetcher is used to fetch a private key that matches a given key desc.
type PrivKeyFetcher func(*keychain.KeyDescriptor) (*btcec.PrivateKey, error)
// MusigSessionMusigSessionManager houses the state needed to manage concurrent
// musig sessions. Each session is identified by a unique session ID which is
// used by callers to interact with a given session.
type MusigSessionManager struct {
keyFetcher PrivKeyFetcher
sessionMtx *multimutex.Mutex[MuSig2SessionID]
musig2Sessions *lnutils.SyncMap[MuSig2SessionID, *MuSig2State]
}
// NewMusigSessionManager creates a new musig manager given an abstract key
// fetcher.
func NewMusigSessionManager(keyFetcher PrivKeyFetcher) *MusigSessionManager {
return &MusigSessionManager{
keyFetcher: keyFetcher,
musig2Sessions: &lnutils.SyncMap[
MuSig2SessionID, *MuSig2State,
]{},
sessionMtx: multimutex.NewMutex[MuSig2SessionID](),
}
}
// MuSig2CreateSession creates a new MuSig2 signing session using the local key
// identified by the key locator. The complete list of all public keys of all
// signing parties must be provided, including the public key of the local
// signing key. If nonces of other parties are already known, they can be
// submitted as well to reduce the number of method calls necessary later on.
//
// The set of sessionOpts are _optional_ and allow a caller to modify the
// generated sessions. As an example the local nonce might already be generated
// ahead of time.
func (m *MusigSessionManager) MuSig2CreateSession(bipVersion MuSig2Version,
keyLoc keychain.KeyLocator, allSignerPubKeys []*btcec.PublicKey,
tweaks *MuSig2Tweaks, otherSignerNonces [][musig2.PubNonceSize]byte,
localNonces *musig2.Nonces) (*MuSig2SessionInfo, error) {
// We need to derive the private key for signing. In the remote signing
// setup, this whole RPC call will be forwarded to the signing
// instance, which requires it to be stateful.
privKey, err := m.keyFetcher(&keychain.KeyDescriptor{
KeyLocator: keyLoc,
})
if err != nil {
return nil, fmt.Errorf("error deriving private key: %w", err)
}
// Create a signing context and session with the given private key and
// list of all known signer public keys.
musigContext, musigSession, err := MuSig2CreateContext(
bipVersion, privKey, allSignerPubKeys, tweaks, localNonces,
)
if err != nil {
return nil, fmt.Errorf("error creating signing context: %w",
err)
}
// Add all nonces we might've learned so far.
haveAllNonces := false
for _, otherSignerNonce := range otherSignerNonces {
haveAllNonces, err = musigSession.RegisterPubNonce(
otherSignerNonce,
)
if err != nil {
return nil, fmt.Errorf("error registering other "+
"signer public nonce: %v", err)
}
}
// Register the new session.
combinedKey, err := musigContext.CombinedKey()
if err != nil {
return nil, fmt.Errorf("error getting combined key: %w", err)
}
session := &MuSig2State{
MuSig2SessionInfo: MuSig2SessionInfo{
SessionID: NewMuSig2SessionID(
combinedKey, musigSession.PublicNonce(),
),
Version: bipVersion,
PublicNonce: musigSession.PublicNonce(),
CombinedKey: combinedKey,
TaprootTweak: tweaks.HasTaprootTweak(),
HaveAllNonces: haveAllNonces,
},
context: musigContext,
session: musigSession,
}
// The internal key is only calculated if we are using a taproot tweak
// and need to know it for a potential script spend.
if tweaks.HasTaprootTweak() {
internalKey, err := musigContext.TaprootInternalKey()
if err != nil {
return nil, fmt.Errorf("error getting internal key: %w",
err)
}
session.TaprootInternalKey = internalKey
}
// Since we generate new nonces for every session, there is no way that
// a session with the same ID already exists. So even if we call the API
// twice with the same signers, we still get a new ID.
//
// We'll use just all zeroes as the session ID for the mutex, as this
// is a "global" action.
m.musig2Sessions.Store(session.SessionID, session)
return &session.MuSig2SessionInfo, nil
}
// MuSig2Sign creates a partial signature using the local signing key
// that was specified when the session was created. This can only be
// called when all public nonces of all participants are known and have
// been registered with the session. If this node isn't responsible for
// combining all the partial signatures, then the cleanup parameter
// should be set, indicating that the session can be removed from memory
// once the signature was produced.
func (m *MusigSessionManager) MuSig2Sign(sessionID MuSig2SessionID,
msg [sha256.Size]byte, cleanUp bool) (*musig2.PartialSignature, error) {
// We hold the lock during the whole operation, we don't want any
// interference with calls that might come through in parallel for the
// same session.
m.sessionMtx.Lock(sessionID)
defer m.sessionMtx.Unlock(sessionID)
session, ok := m.musig2Sessions.Load(sessionID)
if !ok {
return nil, fmt.Errorf("session with ID %x not found",
sessionID[:])
}
// We can only sign once we have all other signer's nonces.
if !session.HaveAllNonces {
return nil, fmt.Errorf("only have %d of %d required nonces",
session.session.NumRegisteredNonces(),
len(session.context.SigningKeys()))
}
// Create our own partial signature with the local signing key.
partialSig, err := MuSig2Sign(session.session, msg, true)
if err != nil {
return nil, fmt.Errorf("error signing with local key: %w", err)
}
// Clean up our local state if requested.
if cleanUp {
m.musig2Sessions.Delete(sessionID)
}
return partialSig, nil
}
// MuSig2CombineSig combines the given partial signature(s) with the
// local one, if it already exists. Once a partial signature of all
// participants is registered, the final signature will be combined and
// returned.
func (m *MusigSessionManager) MuSig2CombineSig(sessionID MuSig2SessionID,
partialSigs []*musig2.PartialSignature) (*schnorr.Signature, bool,
error) {
// We hold the lock during the whole operation, we don't want any
// interference with calls that might come through in parallel for the
// same session.
m.sessionMtx.Lock(sessionID)
defer m.sessionMtx.Unlock(sessionID)
session, ok := m.musig2Sessions.Load(sessionID)
if !ok {
return nil, false, fmt.Errorf("session with ID %x not found",
sessionID[:])
}
// Make sure we don't exceed the number of expected partial signatures
// as that would indicate something is wrong with the signing setup.
if session.HaveAllSigs {
return nil, true, fmt.Errorf("already have all partial" +
"signatures")
}
// Add all sigs we got so far.
var (
finalSig *schnorr.Signature
err error
)
for _, otherPartialSig := range partialSigs {
session.HaveAllSigs, err = MuSig2CombineSig(
session.session, otherPartialSig,
)
if err != nil {
return nil, false, fmt.Errorf("error combining "+
"partial signature: %w", err)
}
}
// If we have all partial signatures, we should be able to get the
// complete signature now. We also remove this session from memory since
// there is nothing more left to do.
if session.HaveAllSigs {
finalSig = session.session.FinalSig()
m.musig2Sessions.Delete(sessionID)
}
return finalSig, session.HaveAllSigs, nil
}
// MuSig2Cleanup removes a session from memory to free up resources.
func (m *MusigSessionManager) MuSig2Cleanup(sessionID MuSig2SessionID) error {
// We hold the lock during the whole operation, we don't want any
// interference with calls that might come through in parallel for the
// same session.
m.sessionMtx.Lock(sessionID)
defer m.sessionMtx.Unlock(sessionID)
_, ok := m.musig2Sessions.Load(sessionID)
if !ok {
return fmt.Errorf("session with ID %x not found", sessionID[:])
}
m.musig2Sessions.Delete(sessionID)
return nil
}
// MuSig2RegisterNonces registers one or more public nonces of other signing
// participants for a session identified by its ID. This method returns true
// once we have all nonces for all other signing participants.
func (m *MusigSessionManager) MuSig2RegisterNonces(sessionID MuSig2SessionID,
otherSignerNonces [][musig2.PubNonceSize]byte) (bool, error) {
// We hold the lock during the whole operation, we don't want any
// interference with calls that might come through in parallel for the
// same session.
m.sessionMtx.Lock(sessionID)
defer m.sessionMtx.Unlock(sessionID)
session, ok := m.musig2Sessions.Load(sessionID)
if !ok {
return false, fmt.Errorf("session with ID %x not found",
sessionID[:])
}
// Make sure we don't exceed the number of expected nonces as that would
// indicate something is wrong with the signing setup.
if session.HaveAllNonces {
return true, fmt.Errorf("already have all nonces")
}
numSigners := len(session.context.SigningKeys())
remainingNonces := numSigners - session.session.NumRegisteredNonces()
if len(otherSignerNonces) > remainingNonces {
return false, fmt.Errorf("only %d other nonces remaining but "+
"trying to register %d more", remainingNonces,
len(otherSignerNonces))
}
// Add all nonces we've learned so far.
var err error
for _, otherSignerNonce := range otherSignerNonces {
session.HaveAllNonces, err = session.session.RegisterPubNonce(
otherSignerNonce,
)
if err != nil {
return false, fmt.Errorf("error registering other "+
"signer public nonce: %v", err)
}
}
return session.HaveAllNonces, nil
}
package input
import (
"errors"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/txscript"
"github.com/lightningnetwork/lnd/lnutils"
)
// ErrUnknownScriptType is returned when an unknown script type is encountered.
var ErrUnknownScriptType = errors.New("unknown script type")
// ScriptPath is used to indicate the spending path of a given script. Possible
// paths include: timeout, success, revocation, and others.
type ScriptPath uint8
const (
// ScriptPathTimeout is a script path that can be taken only after a
// timeout has elapsed.
ScriptPathTimeout ScriptPath = iota
// ScriptPathSuccess is a script path that can be taken only with some
// secret data.
ScriptPathSuccess
// ScriptPathRevocation is a script path used when a contract has been
// breached.
ScriptPathRevocation
// ScriptPathDelay is a script path used when a contract has relative
// delay that must elapse before it can be swept.
ScriptPathDelay
)
// ScriptDescriptor is an interface that abstracts over the various ways a
// pkScript can be spent from an output. This supports both normal p2wsh
// (witness script, etc.), and also tapscript paths which have distinct
// tapscript leaves.
type ScriptDescriptor interface {
// PkScript is the public key script that commits to the final
// contract.
PkScript() []byte
// WitnessScriptToSign returns the witness script that we'll use when
// signing for the remote party, and also verifying signatures on our
// transactions. As an example, when we create an outgoing HTLC for the
// remote party, we want to sign their success path.
//
// TODO(roasbeef): break out into HTLC specific desc? or Branching Desc
// w/ the below?
WitnessScriptToSign() []byte
// WitnessScriptForPath returns the witness script for the given
// spending path. An error is returned if the path is unknown. This is
// useful as when constructing a control block for a given path, one
// also needs witness script being signed.
WitnessScriptForPath(path ScriptPath) ([]byte, error)
}
// TapscriptDescriptor is a super-set of the normal script multiplexer that
// adds in taproot specific details such as the control block, or top-level tap
// tweak.
type TapscriptDescriptor interface {
ScriptDescriptor
// CtrlBlockForPath returns the control block for the given spending
// path. For unknown paths, an error is returned.
CtrlBlockForPath(path ScriptPath) (*txscript.ControlBlock, error)
// TapTweak returns the top-level taproot tweak for the script.
TapTweak() []byte
// TapScriptTree returns the underlying tapscript tree.
TapScriptTree() *txscript.IndexedTapScriptTree
// Tree returns the underlying ScriptTree.
Tree() ScriptTree
}
// ScriptTree holds the contents needed to spend a script within a tapscript
// tree.
type ScriptTree struct {
// InternalKey is the internal key of the Taproot output key.
InternalKey *btcec.PublicKey
// TaprootKey is the key that will be used to generate the taproot
// output.
TaprootKey *btcec.PublicKey
// TapscriptTree is the full tapscript tree that also includes the
// control block needed to spend each of the leaves.
TapscriptTree *txscript.IndexedTapScriptTree
// TapscriptTreeRoot is the root hash of the tapscript tree.
TapscriptRoot []byte
}
// PkScript is the public key script that commits to the final contract.
func (s *ScriptTree) PkScript() []byte {
// Script building can never internally return an error, so we ignore
// the error to simplify the interface.
pkScript, _ := PayToTaprootScript(s.TaprootKey)
return pkScript
}
// TapTweak returns the top-level taproot tweak for the script.
func (s *ScriptTree) TapTweak() []byte {
return lnutils.ByteSlice(s.TapscriptTree.RootNode.TapHash())
}
// TapScriptTree returns the underlying tapscript tree.
func (s *ScriptTree) TapScriptTree() *txscript.IndexedTapScriptTree {
return s.TapscriptTree
}
package input
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnutils"
"golang.org/x/crypto/ripemd160"
)
var (
// TODO(roasbeef): remove these and use the one's defined in txscript
// within testnet-L.
// SequenceLockTimeSeconds is the 22nd bit which indicates the lock
// time is in seconds.
SequenceLockTimeSeconds = uint32(1 << 22)
)
// MustParsePubKey parses a hex encoded public key string into a public key and
// panic if parsing fails.
func MustParsePubKey(pubStr string) btcec.PublicKey {
pubBytes, err := hex.DecodeString(pubStr)
if err != nil {
panic(err)
}
pub, err := btcec.ParsePubKey(pubBytes)
if err != nil {
panic(err)
}
return *pub
}
// TaprootNUMSHex is the hex encoded version of the taproot NUMs key.
const TaprootNUMSHex = "02dca094751109d0bd055d03565874e8276dd53e926b44e3bd1bb" +
"6bf4bc130a279"
var (
// TaprootNUMSKey is a NUMS key (nothing up my sleeves number) that has
// no known private key. This was generated using the following script:
// https://github.com/lightninglabs/lightning-node-connect/tree/
// master/mailbox/numsgen, with the seed phrase "Lightning Simple
// Taproot".
TaprootNUMSKey = MustParsePubKey(TaprootNUMSHex)
)
// Signature is an interface for objects that can populate signatures during
// witness construction.
type Signature interface {
// Serialize returns a DER-encoded ECDSA signature.
Serialize() []byte
// Verify return true if the ECDSA signature is valid for the passed
// message digest under the provided public key.
Verify([]byte, *btcec.PublicKey) bool
}
// ParseSignature parses a raw signature into an input.Signature instance. This
// routine supports parsing normal ECDSA DER encoded signatures, as well as
// schnorr signatures.
func ParseSignature(rawSig []byte) (Signature, error) {
if len(rawSig) == schnorr.SignatureSize {
return schnorr.ParseSignature(rawSig)
}
return ecdsa.ParseDERSignature(rawSig)
}
// WitnessScriptHash generates a pay-to-witness-script-hash public key script
// paying to a version 0 witness program paying to the passed redeem script.
func WitnessScriptHash(witnessScript []byte) ([]byte, error) {
bldr := txscript.NewScriptBuilder(
txscript.WithScriptAllocSize(P2WSHSize),
)
bldr.AddOp(txscript.OP_0)
scriptHash := sha256.Sum256(witnessScript)
bldr.AddData(scriptHash[:])
return bldr.Script()
}
// WitnessPubKeyHash generates a pay-to-witness-pubkey-hash public key script
// paying to a version 0 witness program containing the passed serialized
// public key.
func WitnessPubKeyHash(pubkey []byte) ([]byte, error) {
bldr := txscript.NewScriptBuilder(
txscript.WithScriptAllocSize(P2WPKHSize),
)
bldr.AddOp(txscript.OP_0)
pkhash := btcutil.Hash160(pubkey)
bldr.AddData(pkhash)
return bldr.Script()
}
// GenerateP2SH generates a pay-to-script-hash public key script paying to the
// passed redeem script.
func GenerateP2SH(script []byte) ([]byte, error) {
bldr := txscript.NewScriptBuilder(
txscript.WithScriptAllocSize(NestedP2WPKHSize),
)
bldr.AddOp(txscript.OP_HASH160)
scripthash := btcutil.Hash160(script)
bldr.AddData(scripthash)
bldr.AddOp(txscript.OP_EQUAL)
return bldr.Script()
}
// GenerateP2PKH generates a pay-to-public-key-hash public key script paying to
// the passed serialized public key.
func GenerateP2PKH(pubkey []byte) ([]byte, error) {
bldr := txscript.NewScriptBuilder(
txscript.WithScriptAllocSize(P2PKHSize),
)
bldr.AddOp(txscript.OP_DUP)
bldr.AddOp(txscript.OP_HASH160)
pkhash := btcutil.Hash160(pubkey)
bldr.AddData(pkhash)
bldr.AddOp(txscript.OP_EQUALVERIFY)
bldr.AddOp(txscript.OP_CHECKSIG)
return bldr.Script()
}
// GenerateUnknownWitness generates the maximum-sized witness public key script
// consisting of a version push and a 40-byte data push.
func GenerateUnknownWitness() ([]byte, error) {
bldr := txscript.NewScriptBuilder()
bldr.AddOp(txscript.OP_0)
witnessScript := make([]byte, 40)
bldr.AddData(witnessScript)
return bldr.Script()
}
// GenMultiSigScript generates the non-p2sh'd multisig script for 2 of 2
// pubkeys.
func GenMultiSigScript(aPub, bPub []byte) ([]byte, error) {
if len(aPub) != 33 || len(bPub) != 33 {
return nil, fmt.Errorf("pubkey size error: compressed " +
"pubkeys only")
}
// Swap to sort pubkeys if needed. Keys are sorted in lexicographical
// order. The signatures within the scriptSig must also adhere to the
// order, ensuring that the signatures for each public key appears in
// the proper order on the stack.
if bytes.Compare(aPub, bPub) == 1 {
aPub, bPub = bPub, aPub
}
bldr := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
MultiSigSize,
))
bldr.AddOp(txscript.OP_2)
bldr.AddData(aPub) // Add both pubkeys (sorted).
bldr.AddData(bPub)
bldr.AddOp(txscript.OP_2)
bldr.AddOp(txscript.OP_CHECKMULTISIG)
return bldr.Script()
}
// GenFundingPkScript creates a redeem script, and its matching p2wsh
// output for the funding transaction.
func GenFundingPkScript(aPub, bPub []byte, amt int64) ([]byte, *wire.TxOut, error) {
// As a sanity check, ensure that the passed amount is above zero.
if amt <= 0 {
return nil, nil, fmt.Errorf("can't create FundTx script with " +
"zero, or negative coins")
}
// First, create the 2-of-2 multi-sig script itself.
witnessScript, err := GenMultiSigScript(aPub, bPub)
if err != nil {
return nil, nil, err
}
// With the 2-of-2 script in had, generate a p2wsh script which pays
// to the funding script.
pkScript, err := WitnessScriptHash(witnessScript)
if err != nil {
return nil, nil, err
}
return witnessScript, wire.NewTxOut(amt, pkScript), nil
}
// GenTaprootFundingScript constructs the taproot-native funding output that
// uses MuSig2 to create a single aggregated key to anchor the channel.
func GenTaprootFundingScript(aPub, bPub *btcec.PublicKey,
amt int64, tapscriptRoot fn.Option[chainhash.Hash]) ([]byte,
*wire.TxOut, error) {
muSig2Opt := musig2.WithBIP86KeyTweak()
tapscriptRoot.WhenSome(func(scriptRoot chainhash.Hash) {
muSig2Opt = musig2.WithTaprootKeyTweak(scriptRoot[:])
})
// Similar to the existing p2wsh funding script, we'll always make sure
// we sort the keys before any major operations. In order to ensure
// that there's no other way this output can be spent, we'll use a BIP
// 86 tweak here during aggregation, unless the user has explicitly
// specified a tapscript root.
combinedKey, _, _, err := musig2.AggregateKeys(
[]*btcec.PublicKey{aPub, bPub}, true, muSig2Opt,
)
if err != nil {
return nil, nil, fmt.Errorf("unable to combine keys: %w", err)
}
// Now that we have the combined key, we can create a taproot pkScript
// from this, and then make the txOut given the amount.
pkScript, err := PayToTaprootScript(combinedKey.FinalKey)
if err != nil {
return nil, nil, fmt.Errorf("unable to make taproot "+
"pkscript: %w", err)
}
txOut := wire.NewTxOut(amt, pkScript)
// For the "witness program" we just return the raw pkScript since the
// output we create can _only_ be spent with a MuSig2 signature.
return pkScript, txOut, nil
}
// SpendMultiSig generates the witness stack required to redeem the 2-of-2 p2wsh
// multi-sig output.
func SpendMultiSig(witnessScript, pubA []byte, sigA Signature,
pubB []byte, sigB Signature) [][]byte {
witness := make([][]byte, 4)
// When spending a p2wsh multi-sig script, rather than an OP_0, we add
// a nil stack element to eat the extra pop.
witness[0] = nil
// When initially generating the witnessScript, we sorted the serialized
// public keys in descending order. So we do a quick comparison in order
// ensure the signatures appear on the Script Virtual Machine stack in
// the correct order.
if bytes.Compare(pubA, pubB) == 1 {
witness[1] = append(sigB.Serialize(), byte(txscript.SigHashAll))
witness[2] = append(sigA.Serialize(), byte(txscript.SigHashAll))
} else {
witness[1] = append(sigA.Serialize(), byte(txscript.SigHashAll))
witness[2] = append(sigB.Serialize(), byte(txscript.SigHashAll))
}
// Finally, add the preimage as the last witness element.
witness[3] = witnessScript
return witness
}
// FindScriptOutputIndex finds the index of the public key script output
// matching 'script'. Additionally, a boolean is returned indicating if a
// matching output was found at all.
//
// NOTE: The search stops after the first matching script is found.
func FindScriptOutputIndex(tx *wire.MsgTx, script []byte) (bool, uint32) {
found := false
index := uint32(0)
for i, txOut := range tx.TxOut {
if bytes.Equal(txOut.PkScript, script) {
found = true
index = uint32(i)
break
}
}
return found, index
}
// Ripemd160H calculates the ripemd160 of the passed byte slice. This is used to
// calculate the intermediate hash for payment pre-images. Payment hashes are
// the result of ripemd160(sha256(paymentPreimage)). As a result, the value
// passed in should be the sha256 of the payment hash.
func Ripemd160H(d []byte) []byte {
h := ripemd160.New()
h.Write(d)
return h.Sum(nil)
}
// SenderHTLCScript constructs the public key script for an outgoing HTLC
// output payment for the sender's version of the commitment transaction. The
// possible script paths from this output include:
//
// - The sender timing out the HTLC using the second level HTLC timeout
// transaction.
// - The receiver of the HTLC claiming the output on-chain with the payment
// preimage.
// - The receiver of the HTLC sweeping all the funds in the case that a
// revoked commitment transaction bearing this HTLC was broadcast.
//
// If confirmedSpend=true, a 1 OP_CSV check will be added to the non-revocation
// cases, to allow sweeping only after confirmation.
//
// Possible Input Scripts:
//
// SENDR: <0> <sendr sig> <recvr sig> <0> (spend using HTLC timeout transaction)
// RECVR: <recvr sig> <preimage>
// REVOK: <revoke sig> <revoke key>
// * receiver revoke
//
// Offered HTLC Output Script:
//
// OP_DUP OP_HASH160 <revocation key hash160> OP_EQUAL
// OP_IF
// OP_CHECKSIG
// OP_ELSE
// <recv htlc key>
// OP_SWAP OP_SIZE 32 OP_EQUAL
// OP_NOTIF
// OP_DROP 2 OP_SWAP <sender htlc key> 2 OP_CHECKMULTISIG
// OP_ELSE
// OP_HASH160 <ripemd160(payment hash)> OP_EQUALVERIFY
// OP_CHECKSIG
// OP_ENDIF
// [1 OP_CHECKSEQUENCEVERIFY OP_DROP] <- if allowing confirmed
// spend only.
// OP_ENDIF
func SenderHTLCScript(senderHtlcKey, receiverHtlcKey,
revocationKey *btcec.PublicKey, paymentHash []byte,
confirmedSpend bool) ([]byte, error) {
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
OfferedHtlcScriptSizeConfirmed,
))
// The opening operations are used to determine if this is the receiver
// of the HTLC attempting to sweep all the funds due to a contract
// breach. In this case, they'll place the revocation key at the top of
// the stack.
builder.AddOp(txscript.OP_DUP)
builder.AddOp(txscript.OP_HASH160)
builder.AddData(btcutil.Hash160(revocationKey.SerializeCompressed()))
builder.AddOp(txscript.OP_EQUAL)
// If the hash matches, then this is the revocation clause. The output
// can be spent if the check sig operation passes.
builder.AddOp(txscript.OP_IF)
builder.AddOp(txscript.OP_CHECKSIG)
// Otherwise, this may either be the receiver of the HTLC claiming with
// the pre-image, or the sender of the HTLC sweeping the output after
// it has timed out.
builder.AddOp(txscript.OP_ELSE)
// We'll do a bit of set up by pushing the receiver's key on the top of
// the stack. This will be needed later if we decide that this is the
// sender activating the time out clause with the HTLC timeout
// transaction.
builder.AddData(receiverHtlcKey.SerializeCompressed())
// Atm, the top item of the stack is the receiverKey's so we use a swap
// to expose what is either the payment pre-image or a signature.
builder.AddOp(txscript.OP_SWAP)
// With the top item swapped, check if it's 32 bytes. If so, then this
// *may* be the payment pre-image.
builder.AddOp(txscript.OP_SIZE)
builder.AddInt64(32)
builder.AddOp(txscript.OP_EQUAL)
// If it isn't then this might be the sender of the HTLC activating the
// time out clause.
builder.AddOp(txscript.OP_NOTIF)
// We'll drop the OP_IF return value off the top of the stack so we can
// reconstruct the multi-sig script used as an off-chain covenant. If
// two valid signatures are provided, then the output will be deemed as
// spendable.
builder.AddOp(txscript.OP_DROP)
builder.AddOp(txscript.OP_2)
builder.AddOp(txscript.OP_SWAP)
builder.AddData(senderHtlcKey.SerializeCompressed())
builder.AddOp(txscript.OP_2)
builder.AddOp(txscript.OP_CHECKMULTISIG)
// Otherwise, then the only other case is that this is the receiver of
// the HTLC sweeping it on-chain with the payment pre-image.
builder.AddOp(txscript.OP_ELSE)
// Hash the top item of the stack and compare it with the hash160 of
// the payment hash, which is already the sha256 of the payment
// pre-image. By using this little trick we're able to save space
// on-chain as the witness includes a 20-byte hash rather than a
// 32-byte hash.
builder.AddOp(txscript.OP_HASH160)
builder.AddData(Ripemd160H(paymentHash))
builder.AddOp(txscript.OP_EQUALVERIFY)
// This checks the receiver's signature so that a third party with
// knowledge of the payment preimage still cannot steal the output.
builder.AddOp(txscript.OP_CHECKSIG)
// Close out the OP_IF statement above.
builder.AddOp(txscript.OP_ENDIF)
// Add 1 block CSV delay if a confirmation is required for the
// non-revocation clauses.
if confirmedSpend {
builder.AddOp(txscript.OP_1)
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)
}
// Close out the OP_IF statement at the top of the script.
builder.AddOp(txscript.OP_ENDIF)
return builder.Script()
}
// SenderHtlcSpendRevokeWithKey constructs a valid witness allowing the receiver of an
// HTLC to claim the output with knowledge of the revocation private key in the
// scenario that the sender of the HTLC broadcasts a previously revoked
// commitment transaction. A valid spend requires knowledge of the private key
// that corresponds to their revocation base point and also the private key from
// the per commitment point, and a valid signature under the combined public
// key.
func SenderHtlcSpendRevokeWithKey(signer Signer, signDesc *SignDescriptor,
revokeKey *btcec.PublicKey, sweepTx *wire.MsgTx) (wire.TxWitness, error) {
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// The stack required to sweep a revoke HTLC output consists simply of
// the exact witness stack as one of a regular p2wkh spend. The only
// difference is that the keys used were derived in an adversarial
// manner in order to encode the revocation contract into a sig+key
// pair.
witnessStack := wire.TxWitness(make([][]byte, 3))
witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType))
witnessStack[1] = revokeKey.SerializeCompressed()
witnessStack[2] = signDesc.WitnessScript
return witnessStack, nil
}
// SenderHtlcSpendRevoke constructs a valid witness allowing the receiver of an
// HTLC to claim the output with knowledge of the revocation private key in the
// scenario that the sender of the HTLC broadcasts a previously revoked
// commitment transaction. This method first derives the appropriate revocation
// key, and requires that the provided SignDescriptor has a local revocation
// basepoint and commitment secret in the PubKey and DoubleTweak fields,
// respectively.
func SenderHtlcSpendRevoke(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx) (wire.TxWitness, error) {
revokeKey, err := deriveRevokePubKey(signDesc)
if err != nil {
return nil, err
}
return SenderHtlcSpendRevokeWithKey(signer, signDesc, revokeKey, sweepTx)
}
// IsHtlcSpendRevoke is used to determine if the passed spend is spending a
// HTLC output using the revocation key.
func IsHtlcSpendRevoke(txIn *wire.TxIn, signDesc *SignDescriptor) (
bool, error) {
// For taproot channels, the revocation path only has a single witness,
// as that's the key spend path.
isTaproot := txscript.IsPayToTaproot(signDesc.Output.PkScript)
if isTaproot {
return len(txIn.Witness) == 1, nil
}
revokeKey, err := deriveRevokePubKey(signDesc)
if err != nil {
return false, err
}
if len(txIn.Witness) == 3 &&
bytes.Equal(txIn.Witness[1], revokeKey.SerializeCompressed()) {
return true, nil
}
return false, nil
}
// SenderHtlcSpendRedeem constructs a valid witness allowing the receiver of an
// HTLC to redeem the pending output in the scenario that the sender broadcasts
// their version of the commitment transaction. A valid spend requires
// knowledge of the payment preimage, and a valid signature under the receivers
// public key.
func SenderHtlcSpendRedeem(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx, paymentPreimage []byte) (wire.TxWitness, error) {
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// The stack required to spend this output is simply the signature
// generated above under the receiver's public key, and the payment
// pre-image.
witnessStack := wire.TxWitness(make([][]byte, 3))
witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType))
witnessStack[1] = paymentPreimage
witnessStack[2] = signDesc.WitnessScript
return witnessStack, nil
}
// SenderHtlcSpendTimeout constructs a valid witness allowing the sender of an
// HTLC to activate the time locked covenant clause of a soon to be expired
// HTLC. This script simply spends the multi-sig output using the
// pre-generated HTLC timeout transaction.
func SenderHtlcSpendTimeout(receiverSig Signature,
receiverSigHash txscript.SigHashType, signer Signer,
signDesc *SignDescriptor, htlcTimeoutTx *wire.MsgTx) (
wire.TxWitness, error) {
sweepSig, err := signer.SignOutputRaw(htlcTimeoutTx, signDesc)
if err != nil {
return nil, err
}
// We place a zero as the first item of the evaluated witness stack in
// order to force Script execution to the HTLC timeout clause. The
// second zero is required to consume the extra pop due to a bug in the
// original OP_CHECKMULTISIG.
witnessStack := wire.TxWitness(make([][]byte, 5))
witnessStack[0] = nil
witnessStack[1] = append(receiverSig.Serialize(), byte(receiverSigHash))
witnessStack[2] = append(sweepSig.Serialize(), byte(signDesc.HashType))
witnessStack[3] = nil
witnessStack[4] = signDesc.WitnessScript
return witnessStack, nil
}
// SenderHTLCTapLeafTimeout returns the full tapscript leaf for the timeout
// path of the sender HTLC. This is a small script that allows the sender to
// timeout the HTLC after a period of time:
//
// <local_key> OP_CHECKSIGVERIFY
// <remote_key> OP_CHECKSIG
func SenderHTLCTapLeafTimeout(senderHtlcKey,
receiverHtlcKey *btcec.PublicKey) (txscript.TapLeaf, error) {
builder := txscript.NewScriptBuilder()
builder.AddData(schnorr.SerializePubKey(senderHtlcKey))
builder.AddOp(txscript.OP_CHECKSIGVERIFY)
builder.AddData(schnorr.SerializePubKey(receiverHtlcKey))
builder.AddOp(txscript.OP_CHECKSIG)
timeoutLeafScript, err := builder.Script()
if err != nil {
return txscript.TapLeaf{}, err
}
return txscript.NewBaseTapLeaf(timeoutLeafScript), nil
}
// SenderHTLCTapLeafSuccess returns the full tapscript leaf for the success
// path of the sender HTLC. This is a small script that allows the receiver to
// redeem the HTLC with a pre-image:
//
// OP_SIZE 32 OP_EQUALVERIFY OP_HASH160
// <RIPEMD160(payment_hash)> OP_EQUALVERIFY
// <remote_htlcpubkey> OP_CHECKSIG
// 1 OP_CHECKSEQUENCEVERIFY OP_DROP
func SenderHTLCTapLeafSuccess(receiverHtlcKey *btcec.PublicKey,
paymentHash []byte) (txscript.TapLeaf, error) {
builder := txscript.NewScriptBuilder()
// Check that the pre-image is 32 bytes as required.
builder.AddOp(txscript.OP_SIZE)
builder.AddInt64(32)
builder.AddOp(txscript.OP_EQUALVERIFY)
// Check that the specified pre-image matches what we hard code into
// the script.
builder.AddOp(txscript.OP_HASH160)
builder.AddData(Ripemd160H(paymentHash))
builder.AddOp(txscript.OP_EQUALVERIFY)
// Verify the remote party's signature, then make them wait 1 block
// after confirmation to properly sweep.
builder.AddData(schnorr.SerializePubKey(receiverHtlcKey))
builder.AddOp(txscript.OP_CHECKSIG)
builder.AddOp(txscript.OP_1)
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)
successLeafScript, err := builder.Script()
if err != nil {
return txscript.TapLeaf{}, err
}
return txscript.NewBaseTapLeaf(successLeafScript), nil
}
// htlcType is an enum value that denotes what type of HTLC script this is.
type htlcType uint8
const (
// htlcLocalIncoming represents an incoming HTLC on the local
// commitment transaction.
htlcLocalIncoming htlcType = iota
// htlcLocalOutgoing represents an outgoing HTLC on the local
// commitment transaction.
htlcLocalOutgoing
// htlcRemoteIncoming represents an incoming HTLC on the remote
// commitment transaction.
htlcRemoteIncoming
// htlcRemoteOutgoing represents an outgoing HTLC on the remote
// commitment transaction.
htlcRemoteOutgoing
)
// HtlcScriptTree holds the taproot output key, as well as the two script path
// leaves that every taproot HTLC script depends on.
type HtlcScriptTree struct {
ScriptTree
// SuccessTapLeaf is the tapleaf for the redemption path.
SuccessTapLeaf txscript.TapLeaf
// TimeoutTapLeaf is the tapleaf for the timeout path.
TimeoutTapLeaf txscript.TapLeaf
// AuxLeaf is an auxiliary leaf that can be used to extend the base
// HTLC script tree with new spend paths, or just as extra commitment
// space. When present, this leaf will always be in the right-most area
// of the tapscript tree.
AuxLeaf AuxTapLeaf
// htlcType is the type of HTLC script this is.
htlcType htlcType
}
// WitnessScriptToSign returns the witness script that we'll use when signing
// for the remote party, and also verifying signatures on our transactions. As
// an example, when we create an outgoing HTLC for the remote party, we want to
// sign the success path for them, so we'll return the success path leaf.
func (h *HtlcScriptTree) WitnessScriptToSign() []byte {
switch h.htlcType {
// For incoming HLTCs on our local commitment, we care about verifying
// the success path.
case htlcLocalIncoming:
return h.SuccessTapLeaf.Script
// For incoming HTLCs on the remote party's commitment, we want to sign
// the timeout path for them.
case htlcRemoteIncoming:
return h.TimeoutTapLeaf.Script
// For outgoing HTLCs on our local commitment, we want to verify the
// timeout path.
case htlcLocalOutgoing:
return h.TimeoutTapLeaf.Script
// For outgoing HTLCs on the remote party's commitment, we want to sign
// the success path for them.
case htlcRemoteOutgoing:
return h.SuccessTapLeaf.Script
default:
panic(fmt.Sprintf("unknown htlc type: %v", h.htlcType))
}
}
// WitnessScriptForPath returns the witness script for the given spending path.
// An error is returned if the path is unknown.
func (h *HtlcScriptTree) WitnessScriptForPath(path ScriptPath) ([]byte, error) {
switch path {
case ScriptPathSuccess:
return h.SuccessTapLeaf.Script, nil
case ScriptPathTimeout:
return h.TimeoutTapLeaf.Script, nil
default:
return nil, fmt.Errorf("unknown script path: %v", path)
}
}
// CtrlBlockForPath returns the control block for the given spending path. For
// script types that don't have a control block, nil is returned.
func (h *HtlcScriptTree) CtrlBlockForPath(
path ScriptPath) (*txscript.ControlBlock, error) {
switch path {
case ScriptPathSuccess:
return lnutils.Ptr(MakeTaprootCtrlBlock(
h.SuccessTapLeaf.Script, h.InternalKey,
h.TapscriptTree,
)), nil
case ScriptPathTimeout:
return lnutils.Ptr(MakeTaprootCtrlBlock(
h.TimeoutTapLeaf.Script, h.InternalKey,
h.TapscriptTree,
)), nil
default:
return nil, fmt.Errorf("unknown script path: %v", path)
}
}
// Tree returns the underlying ScriptTree of the HtlcScriptTree.
func (h *HtlcScriptTree) Tree() ScriptTree {
return h.ScriptTree
}
// A compile time check to ensure HtlcScriptTree implements the
// TapscriptMultiplexer interface.
var _ TapscriptDescriptor = (*HtlcScriptTree)(nil)
// senderHtlcTapScriptTree builds the tapscript tree which is used to anchor
// the HTLC key for HTLCs on the sender's commitment.
func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
revokeKey *btcec.PublicKey, payHash []byte, hType htlcType,
auxLeaf AuxTapLeaf) (*HtlcScriptTree, error) {
// First, we'll obtain the tap leaves for both the success and timeout
// path.
successTapLeaf, err := SenderHTLCTapLeafSuccess(
receiverHtlcKey, payHash,
)
if err != nil {
return nil, err
}
timeoutTapLeaf, err := SenderHTLCTapLeafTimeout(
senderHtlcKey, receiverHtlcKey,
)
if err != nil {
return nil, err
}
tapLeaves := []txscript.TapLeaf{successTapLeaf, timeoutTapLeaf}
auxLeaf.WhenSome(func(l txscript.TapLeaf) {
tapLeaves = append(tapLeaves, l)
})
// With the two leaves obtained, we'll now make the tapscript tree,
// then obtain the root from that
tapscriptTree := txscript.AssembleTaprootScriptTree(tapLeaves...)
tapScriptRoot := tapscriptTree.RootNode.TapHash()
// With the tapscript root obtained, we'll tweak the revocation key
// with this value to obtain the key that HTLCs will be sent to.
htlcKey := txscript.ComputeTaprootOutputKey(
revokeKey, tapScriptRoot[:],
)
return &HtlcScriptTree{
ScriptTree: ScriptTree{
TaprootKey: htlcKey,
TapscriptTree: tapscriptTree,
TapscriptRoot: tapScriptRoot[:],
InternalKey: revokeKey,
},
SuccessTapLeaf: successTapLeaf,
TimeoutTapLeaf: timeoutTapLeaf,
AuxLeaf: auxLeaf,
htlcType: hType,
}, nil
}
// SenderHTLCScriptTaproot constructs the taproot witness program (schnorr key)
// for an outgoing HTLC on the sender's version of the commitment transaction.
// This method returns the top level tweaked public key that commits to both
// the script paths. This is also known as an offered HTLC.
//
// The returned key commits to a tapscript tree with two possible paths:
//
// - Timeout path:
// <local_key> OP_CHECKSIGVERIFY
// <remote_key> OP_CHECKSIG
//
// - Success path:
// OP_SIZE 32 OP_EQUALVERIFY
// OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY
// <remote_htlcpubkey> OP_CHECKSIG
// 1 OP_CHECKSEQUENCEVERIFY OP_DROP
//
// The timeout path can be spent with a witness of (sender timeout):
//
// <receiver sig> <local sig> <timeout_script> <control_block>
//
// The success path can be spent with a valid control block, and a witness of
// (receiver redeem):
//
// <receiver sig> <preimage> <success_script> <control_block>
//
// The top level keyspend key is the revocation key, which allows a defender to
// unilaterally spend the created output.
func SenderHTLCScriptTaproot(senderHtlcKey, receiverHtlcKey,
revokeKey *btcec.PublicKey, payHash []byte,
whoseCommit lntypes.ChannelParty, auxLeaf AuxTapLeaf) (*HtlcScriptTree,
error) {
var hType htlcType
if whoseCommit.IsLocal() {
hType = htlcLocalOutgoing
} else {
hType = htlcRemoteIncoming
}
// Given all the necessary parameters, we'll return the HTLC script
// tree that includes the top level output script, as well as the two
// tap leaf paths.
return senderHtlcTapScriptTree(
senderHtlcKey, receiverHtlcKey, revokeKey, payHash, hType,
auxLeaf,
)
}
// maybeAppendSighashType appends a sighash type to the end of a signature if
// the sighash type isn't sighash default.
func maybeAppendSighash(sig Signature, sigHash txscript.SigHashType) []byte {
sigBytes := sig.Serialize()
if sigHash == txscript.SigHashDefault {
return sigBytes
}
return append(sigBytes, byte(sigHash))
}
// SenderHTLCScriptTaprootRedeem creates a valid witness needed to redeem a
// sender taproot HTLC with the pre-image. The returned witness is valid and
// includes the control block required to spend the output. This is the offered
// HTLC claimed by the remote party.
func SenderHTLCScriptTaprootRedeem(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx, preimage []byte, revokeKey *btcec.PublicKey,
tapscriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) {
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// In addition to the signature and the witness/leaf script, we also
// need to make a control block proof using the tapscript tree.
var ctrlBlock []byte
if signDesc.ControlBlock == nil {
successControlBlock := MakeTaprootCtrlBlock(
signDesc.WitnessScript, revokeKey, tapscriptTree,
)
ctrlBytes, err := successControlBlock.ToBytes()
if err != nil {
return nil, err
}
ctrlBlock = ctrlBytes
} else {
ctrlBlock = signDesc.ControlBlock
}
// The final witness stack is:
// <receiver sig> <preimage> <success_script> <control_block>
witnessStack := make(wire.TxWitness, 4)
witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType)
witnessStack[1] = preimage
witnessStack[2] = signDesc.WitnessScript
witnessStack[3] = ctrlBlock
return witnessStack, nil
}
// SenderHTLCScriptTaprootTimeout creates a valid witness needed to timeout an
// HTLC on the sender's commitment transaction. The returned witness is valid
// and includes the control block required to spend the output. This is a
// timeout of the offered HTLC by the sender.
func SenderHTLCScriptTaprootTimeout(receiverSig Signature,
receiverSigHash txscript.SigHashType, signer Signer,
signDesc *SignDescriptor, htlcTimeoutTx *wire.MsgTx,
revokeKey *btcec.PublicKey,
tapscriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) {
sweepSig, err := signer.SignOutputRaw(htlcTimeoutTx, signDesc)
if err != nil {
return nil, err
}
// With the sweep signature obtained, we'll obtain the control block
// proof needed to perform a valid spend for the timeout path.
var ctrlBlockBytes []byte
if signDesc.ControlBlock == nil {
timeoutControlBlock := MakeTaprootCtrlBlock(
signDesc.WitnessScript, revokeKey, tapscriptTree,
)
ctrlBytes, err := timeoutControlBlock.ToBytes()
if err != nil {
return nil, err
}
ctrlBlockBytes = ctrlBytes
} else {
ctrlBlockBytes = signDesc.ControlBlock
}
// The final witness stack is:
// <receiver sig> <local sig> <timeout_script> <control_block>
witnessStack := make(wire.TxWitness, 4)
witnessStack[0] = maybeAppendSighash(receiverSig, receiverSigHash)
witnessStack[1] = maybeAppendSighash(sweepSig, signDesc.HashType)
witnessStack[2] = signDesc.WitnessScript
witnessStack[3] = ctrlBlockBytes
return witnessStack, nil
}
// SenderHTLCScriptTaprootRevoke creates a valid witness needed to spend the
// revocation path of the HTLC. This uses a plain keyspend using the specified
// revocation key.
func SenderHTLCScriptTaprootRevoke(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx) (wire.TxWitness, error) {
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// The witness stack in this case is pretty simple: we only need to
// specify the signature generated.
witnessStack := make(wire.TxWitness, 1)
witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType)
return witnessStack, nil
}
// ReceiverHTLCScript constructs the public key script for an incoming HTLC
// output payment for the receiver's version of the commitment transaction. The
// possible execution paths from this script include:
// - The receiver of the HTLC uses its second level HTLC transaction to
// advance the state of the HTLC into the delay+claim state.
// - The sender of the HTLC sweeps all the funds of the HTLC as a breached
// commitment was broadcast.
// - The sender of the HTLC sweeps the HTLC on-chain after the timeout period
// of the HTLC has passed.
//
// If confirmedSpend=true, a 1 OP_CSV check will be added to the non-revocation
// cases, to allow sweeping only after confirmation.
//
// Possible Input Scripts:
//
// RECVR: <0> <sender sig> <recvr sig> <preimage> (spend using HTLC success transaction)
// REVOK: <sig> <key>
// SENDR: <sig> 0
//
// Received HTLC Output Script:
//
// OP_DUP OP_HASH160 <revocation key hash160> OP_EQUAL
// OP_IF
// OP_CHECKSIG
// OP_ELSE
// <sendr htlc key>
// OP_SWAP OP_SIZE 32 OP_EQUAL
// OP_IF
// OP_HASH160 <ripemd160(payment hash)> OP_EQUALVERIFY
// 2 OP_SWAP <recvr htlc key> 2 OP_CHECKMULTISIG
// OP_ELSE
// OP_DROP <cltv expiry> OP_CHECKLOCKTIMEVERIFY OP_DROP
// OP_CHECKSIG
// OP_ENDIF
// [1 OP_CHECKSEQUENCEVERIFY OP_DROP] <- if allowing confirmed
// spend only.
// OP_ENDIF
func ReceiverHTLCScript(cltvExpiry uint32, senderHtlcKey,
receiverHtlcKey, revocationKey *btcec.PublicKey,
paymentHash []byte, confirmedSpend bool) ([]byte, error) {
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
AcceptedHtlcScriptSizeConfirmed,
))
// The opening operations are used to determine if this is the sender
// of the HTLC attempting to sweep all the funds due to a contract
// breach. In this case, they'll place the revocation key at the top of
// the stack.
builder.AddOp(txscript.OP_DUP)
builder.AddOp(txscript.OP_HASH160)
builder.AddData(btcutil.Hash160(revocationKey.SerializeCompressed()))
builder.AddOp(txscript.OP_EQUAL)
// If the hash matches, then this is the revocation clause. The output
// can be spent if the check sig operation passes.
builder.AddOp(txscript.OP_IF)
builder.AddOp(txscript.OP_CHECKSIG)
// Otherwise, this may either be the receiver of the HTLC starting the
// claiming process via the second level HTLC success transaction and
// the pre-image, or the sender of the HTLC sweeping the output after
// it has timed out.
builder.AddOp(txscript.OP_ELSE)
// We'll do a bit of set up by pushing the sender's key on the top of
// the stack. This will be needed later if we decide that this is the
// receiver transitioning the output to the claim state using their
// second-level HTLC success transaction.
builder.AddData(senderHtlcKey.SerializeCompressed())
// Atm, the top item of the stack is the sender's key so we use a swap
// to expose what is either the payment pre-image or something else.
builder.AddOp(txscript.OP_SWAP)
// With the top item swapped, check if it's 32 bytes. If so, then this
// *may* be the payment pre-image.
builder.AddOp(txscript.OP_SIZE)
builder.AddInt64(32)
builder.AddOp(txscript.OP_EQUAL)
// If the item on the top of the stack is 32-bytes, then it is the
// proper size, so this indicates that the receiver of the HTLC is
// attempting to claim the output on-chain by transitioning the state
// of the HTLC to delay+claim.
builder.AddOp(txscript.OP_IF)
// Next we'll hash the item on the top of the stack, if it matches the
// payment pre-image, then we'll continue. Otherwise, we'll end the
// script here as this is the invalid payment pre-image.
builder.AddOp(txscript.OP_HASH160)
builder.AddData(Ripemd160H(paymentHash))
builder.AddOp(txscript.OP_EQUALVERIFY)
// If the payment hash matches, then we'll also need to satisfy the
// multi-sig covenant by providing both signatures of the sender and
// receiver. If the convenient is met, then we'll allow the spending of
// this output, but only by the HTLC success transaction.
builder.AddOp(txscript.OP_2)
builder.AddOp(txscript.OP_SWAP)
builder.AddData(receiverHtlcKey.SerializeCompressed())
builder.AddOp(txscript.OP_2)
builder.AddOp(txscript.OP_CHECKMULTISIG)
// Otherwise, this might be the sender of the HTLC attempting to sweep
// it on-chain after the timeout.
builder.AddOp(txscript.OP_ELSE)
// We'll drop the extra item (which is the output from evaluating the
// OP_EQUAL) above from the stack.
builder.AddOp(txscript.OP_DROP)
// With that item dropped off, we can now enforce the absolute
// lock-time required to timeout the HTLC. If the time has passed, then
// we'll proceed with a checksig to ensure that this is actually the
// sender of he original HTLC.
builder.AddInt64(int64(cltvExpiry))
builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY)
builder.AddOp(txscript.OP_DROP)
builder.AddOp(txscript.OP_CHECKSIG)
// Close out the inner if statement.
builder.AddOp(txscript.OP_ENDIF)
// Add 1 block CSV delay for non-revocation clauses if confirmation is
// required.
if confirmedSpend {
builder.AddOp(txscript.OP_1)
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)
}
// Close out the outer if statement.
builder.AddOp(txscript.OP_ENDIF)
return builder.Script()
}
// ReceiverHtlcSpendRedeem constructs a valid witness allowing the receiver of
// an HTLC to redeem the conditional payment in the event that their commitment
// transaction is broadcast. This clause transitions the state of the HLTC
// output into the delay+claim state by activating the off-chain covenant bound
// by the 2-of-2 multi-sig output. The HTLC success timeout transaction being
// signed has a relative timelock delay enforced by its sequence number. This
// delay give the sender of the HTLC enough time to revoke the output if this
// is a breach commitment transaction.
func ReceiverHtlcSpendRedeem(senderSig Signature,
senderSigHash txscript.SigHashType, paymentPreimage []byte,
signer Signer, signDesc *SignDescriptor, htlcSuccessTx *wire.MsgTx) (
wire.TxWitness, error) {
// First, we'll generate a signature for the HTLC success transaction.
// The signDesc should be signing with the public key used as the
// receiver's public key and also the correct single tweak.
sweepSig, err := signer.SignOutputRaw(htlcSuccessTx, signDesc)
if err != nil {
return nil, err
}
// The final witness stack is used the provide the script with the
// payment pre-image, and also execute the multi-sig clause after the
// pre-images matches. We add a nil item at the bottom of the stack in
// order to consume the extra pop within OP_CHECKMULTISIG.
witnessStack := wire.TxWitness(make([][]byte, 5))
witnessStack[0] = nil
witnessStack[1] = append(senderSig.Serialize(), byte(senderSigHash))
witnessStack[2] = append(sweepSig.Serialize(), byte(signDesc.HashType))
witnessStack[3] = paymentPreimage
witnessStack[4] = signDesc.WitnessScript
return witnessStack, nil
}
// ReceiverHtlcSpendRevokeWithKey constructs a valid witness allowing the sender of an
// HTLC within a previously revoked commitment transaction to re-claim the
// pending funds in the case that the receiver broadcasts this revoked
// commitment transaction.
func ReceiverHtlcSpendRevokeWithKey(signer Signer, signDesc *SignDescriptor,
revokeKey *btcec.PublicKey, sweepTx *wire.MsgTx) (wire.TxWitness, error) {
// First, we'll generate a signature for the sweep transaction. The
// signDesc should be signing with the public key used as the fully
// derived revocation public key and also the correct double tweak
// value.
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// We place a zero, then one as the first items in the evaluated
// witness stack in order to force script execution to the HTLC
// revocation clause.
witnessStack := wire.TxWitness(make([][]byte, 3))
witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType))
witnessStack[1] = revokeKey.SerializeCompressed()
witnessStack[2] = signDesc.WitnessScript
return witnessStack, nil
}
func deriveRevokePubKey(signDesc *SignDescriptor) (*btcec.PublicKey, error) {
if signDesc.KeyDesc.PubKey == nil {
return nil, fmt.Errorf("cannot generate witness with nil " +
"KeyDesc pubkey")
}
// Derive the revocation key using the local revocation base point and
// commitment point.
revokeKey := DeriveRevocationPubkey(
signDesc.KeyDesc.PubKey,
signDesc.DoubleTweak.PubKey(),
)
return revokeKey, nil
}
// ReceiverHtlcSpendRevoke constructs a valid witness allowing the sender of an
// HTLC within a previously revoked commitment transaction to re-claim the
// pending funds in the case that the receiver broadcasts this revoked
// commitment transaction. This method first derives the appropriate revocation
// key, and requires that the provided SignDescriptor has a local revocation
// basepoint and commitment secret in the PubKey and DoubleTweak fields,
// respectively.
func ReceiverHtlcSpendRevoke(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx) (wire.TxWitness, error) {
revokeKey, err := deriveRevokePubKey(signDesc)
if err != nil {
return nil, err
}
return ReceiverHtlcSpendRevokeWithKey(signer, signDesc, revokeKey, sweepTx)
}
// ReceiverHtlcSpendTimeout constructs a valid witness allowing the sender of
// an HTLC to recover the pending funds after an absolute timeout in the
// scenario that the receiver of the HTLC broadcasts their version of the
// commitment transaction. If the caller has already set the lock time on the
// spending transaction, than a value of -1 can be passed for the cltvExpiry
// value.
//
// NOTE: The target input of the passed transaction MUST NOT have a final
// sequence number. Otherwise, the OP_CHECKLOCKTIMEVERIFY check will fail.
func ReceiverHtlcSpendTimeout(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx, cltvExpiry int32) (wire.TxWitness, error) {
// If the caller set a proper timeout value, then we'll apply it
// directly to the transaction.
if cltvExpiry != -1 {
// The HTLC output has an absolute time period before we are
// permitted to recover the pending funds. Therefore we need to
// set the locktime on this sweeping transaction in order to
// pass Script verification.
sweepTx.LockTime = uint32(cltvExpiry)
}
// With the lock time on the transaction set, we'll not generate a
// signature for the sweep transaction. The passed sign descriptor
// should be created using the raw public key of the sender (w/o the
// single tweak applied), and the single tweak set to the proper value
// taking into account the current state's point.
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
witnessStack := wire.TxWitness(make([][]byte, 3))
witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType))
witnessStack[1] = nil
witnessStack[2] = signDesc.WitnessScript
return witnessStack, nil
}
// ReceiverHtlcTapLeafTimeout returns the full tapscript leaf for the timeout
// path of the sender HTLC. This is a small script that allows the sender
// timeout the HTLC after expiry:
//
// <sender_htlcpubkey> OP_CHECKSIG
// 1 OP_CHECKSEQUENCEVERIFY OP_DROP
// <cltv_expiry> OP_CHECKLOCKTIMEVERIFY OP_DROP
func ReceiverHtlcTapLeafTimeout(senderHtlcKey *btcec.PublicKey,
cltvExpiry uint32) (txscript.TapLeaf, error) {
builder := txscript.NewScriptBuilder()
// The first part of the script will verify a signature from the
// sender authorizing the spend (the timeout).
builder.AddData(schnorr.SerializePubKey(senderHtlcKey))
builder.AddOp(txscript.OP_CHECKSIG)
builder.AddOp(txscript.OP_1)
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)
// The second portion will ensure that the CLTV expiry on the spending
// transaction is correct.
builder.AddInt64(int64(cltvExpiry))
builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY)
builder.AddOp(txscript.OP_DROP)
timeoutLeafScript, err := builder.Script()
if err != nil {
return txscript.TapLeaf{}, err
}
return txscript.NewBaseTapLeaf(timeoutLeafScript), nil
}
// ReceiverHtlcTapLeafSuccess returns the full tapscript leaf for the success
// path for an HTLC on the receiver's commitment transaction. This script
// allows the receiver to redeem an HTLC with knowledge of the preimage:
//
// OP_SIZE 32 OP_EQUALVERIFY OP_HASH160
// <RIPEMD160(payment_hash)> OP_EQUALVERIFY
// <receiver_htlcpubkey> OP_CHECKSIGVERIFY
// <sender_htlcpubkey> OP_CHECKSIG
func ReceiverHtlcTapLeafSuccess(receiverHtlcKey *btcec.PublicKey,
senderHtlcKey *btcec.PublicKey,
paymentHash []byte) (txscript.TapLeaf, error) {
builder := txscript.NewScriptBuilder()
// Check that the pre-image is 32 bytes as required.
builder.AddOp(txscript.OP_SIZE)
builder.AddInt64(32)
builder.AddOp(txscript.OP_EQUALVERIFY)
// Check that the specified pre-image matches what we hard code into
// the script.
builder.AddOp(txscript.OP_HASH160)
builder.AddData(Ripemd160H(paymentHash))
builder.AddOp(txscript.OP_EQUALVERIFY)
// Verify the "2-of-2" multi-sig that requires both parties to sign
// off.
builder.AddData(schnorr.SerializePubKey(receiverHtlcKey))
builder.AddOp(txscript.OP_CHECKSIGVERIFY)
builder.AddData(schnorr.SerializePubKey(senderHtlcKey))
builder.AddOp(txscript.OP_CHECKSIG)
successLeafScript, err := builder.Script()
if err != nil {
return txscript.TapLeaf{}, err
}
return txscript.NewBaseTapLeaf(successLeafScript), nil
}
// receiverHtlcTapScriptTree builds the tapscript tree which is used to anchor
// the HTLC key for HTLCs on the receiver's commitment.
func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
revokeKey *btcec.PublicKey, payHash []byte, cltvExpiry uint32,
hType htlcType, auxLeaf AuxTapLeaf) (*HtlcScriptTree, error) {
// First, we'll obtain the tap leaves for both the success and timeout
// path.
successTapLeaf, err := ReceiverHtlcTapLeafSuccess(
receiverHtlcKey, senderHtlcKey, payHash,
)
if err != nil {
return nil, err
}
timeoutTapLeaf, err := ReceiverHtlcTapLeafTimeout(
senderHtlcKey, cltvExpiry,
)
if err != nil {
return nil, err
}
tapLeaves := []txscript.TapLeaf{timeoutTapLeaf, successTapLeaf}
auxLeaf.WhenSome(func(l txscript.TapLeaf) {
tapLeaves = append(tapLeaves, l)
})
// With the two leaves obtained, we'll now make the tapscript tree,
// then obtain the root from that
tapscriptTree := txscript.AssembleTaprootScriptTree(tapLeaves...)
tapScriptRoot := tapscriptTree.RootNode.TapHash()
// With the tapscript root obtained, we'll tweak the revocation key
// with this value to obtain the key that HTLCs will be sent to.
htlcKey := txscript.ComputeTaprootOutputKey(
revokeKey, tapScriptRoot[:],
)
return &HtlcScriptTree{
ScriptTree: ScriptTree{
TaprootKey: htlcKey,
TapscriptTree: tapscriptTree,
TapscriptRoot: tapScriptRoot[:],
InternalKey: revokeKey,
},
SuccessTapLeaf: successTapLeaf,
TimeoutTapLeaf: timeoutTapLeaf,
AuxLeaf: auxLeaf,
htlcType: hType,
}, nil
}
// ReceiverHTLCScriptTaproot constructs the taproot witness program (schnor
// key) for an incoming HTLC on the receiver's version of the commitment
// transaction. This method returns the top level tweaked public key that
// commits to both the script paths. From the PoV of the receiver, this is an
// accepted HTLC.
//
// The returned key commits to a tapscript tree with two possible paths:
//
// - The timeout path:
// <remote_htlcpubkey> OP_CHECKSIG
// 1 OP_CHECKSEQUENCEVERIFY OP_DROP
// <cltv_expiry> OP_CHECKLOCKTIMEVERIFY OP_DROP
//
// - Success path:
// OP_SIZE 32 OP_EQUALVERIFY
// OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY
// <local_htlcpubkey> OP_CHECKSIGVERIFY
// <remote_htlcpubkey> OP_CHECKSIG
//
// The timeout path can be spent with a witness of:
// - <sender sig> <timeout_script> <control_block>
//
// The success path can be spent with a witness of:
// - <sender sig> <receiver sig> <preimage> <success_script> <control_block>
//
// The top level keyspend key is the revocation key, which allows a defender to
// unilaterally spend the created output. Both the final output key as well as
// the tap leaf are returned.
func ReceiverHTLCScriptTaproot(cltvExpiry uint32,
senderHtlcKey, receiverHtlcKey, revocationKey *btcec.PublicKey,
payHash []byte, whoseCommit lntypes.ChannelParty,
auxLeaf AuxTapLeaf) (*HtlcScriptTree, error) {
var hType htlcType
if whoseCommit.IsLocal() {
hType = htlcLocalIncoming
} else {
hType = htlcRemoteOutgoing
}
// Given all the necessary parameters, we'll return the HTLC script
// tree that includes the top level output script, as well as the two
// tap leaf paths.
return receiverHtlcTapScriptTree(
senderHtlcKey, receiverHtlcKey, revocationKey, payHash,
cltvExpiry, hType, auxLeaf,
)
}
// ReceiverHTLCScriptTaprootRedeem creates a valid witness needed to redeem a
// receiver taproot HTLC with the pre-image. The returned witness is valid and
// includes the control block required to spend the output.
func ReceiverHTLCScriptTaprootRedeem(senderSig Signature,
senderSigHash txscript.SigHashType, paymentPreimage []byte,
signer Signer, signDesc *SignDescriptor,
htlcSuccessTx *wire.MsgTx, revokeKey *btcec.PublicKey,
tapscriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) {
// First, we'll generate a signature for the HTLC success transaction.
// The signDesc should be signing with the public key used as the
// receiver's public key and also the correct single tweak.
sweepSig, err := signer.SignOutputRaw(htlcSuccessTx, signDesc)
if err != nil {
return nil, err
}
// In addition to the signature and the witness/leaf script, we also
// need to make a control block proof using the tapscript tree.
var ctrlBlock []byte
if signDesc.ControlBlock == nil {
redeemControlBlock := MakeTaprootCtrlBlock(
signDesc.WitnessScript, revokeKey, tapscriptTree,
)
ctrlBytes, err := redeemControlBlock.ToBytes()
if err != nil {
return nil, err
}
ctrlBlock = ctrlBytes
} else {
ctrlBlock = signDesc.ControlBlock
}
// The final witness stack is:
// * <sender sig> <receiver sig> <preimage> <success_script>
// <control_block>
witnessStack := wire.TxWitness(make([][]byte, 5))
witnessStack[0] = maybeAppendSighash(senderSig, senderSigHash)
witnessStack[1] = maybeAppendSighash(sweepSig, signDesc.HashType)
witnessStack[2] = paymentPreimage
witnessStack[3] = signDesc.WitnessScript
witnessStack[4] = ctrlBlock
return witnessStack, nil
}
// ReceiverHTLCScriptTaprootTimeout creates a valid witness needed to timeout
// an HTLC on the receiver's commitment transaction after the timeout has
// elapsed.
func ReceiverHTLCScriptTaprootTimeout(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx, cltvExpiry int32, revokeKey *btcec.PublicKey,
tapscriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) {
// If the caller set a proper timeout value, then we'll apply it
// directly to the transaction.
//
// TODO(roasbeef): helper func
if cltvExpiry != -1 {
// The HTLC output has an absolute time period before we are
// permitted to recover the pending funds. Therefore we need to
// set the locktime on this sweeping transaction in order to
// pass Script verification.
sweepTx.LockTime = uint32(cltvExpiry)
}
// With the lock time on the transaction set, we'll now generate a
// signature for the sweep transaction. The passed sign descriptor
// should be created using the raw public key of the sender (w/o the
// single tweak applied), and the single tweak set to the proper value
// taking into account the current state's point.
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// In addition to the signature and the witness/leaf script, we also
// need to make a control block proof using the tapscript tree.
var ctrlBlock []byte
if signDesc.ControlBlock == nil {
timeoutControlBlock := MakeTaprootCtrlBlock(
signDesc.WitnessScript, revokeKey, tapscriptTree,
)
ctrlBlock, err = timeoutControlBlock.ToBytes()
if err != nil {
return nil, err
}
} else {
ctrlBlock = signDesc.ControlBlock
}
// The final witness is pretty simple, we just need to present a valid
// signature for the script, and then provide the control block.
witnessStack := make(wire.TxWitness, 3)
witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType)
witnessStack[1] = signDesc.WitnessScript
witnessStack[2] = ctrlBlock
return witnessStack, nil
}
// ReceiverHTLCScriptTaprootRevoke creates a valid witness needed to spend the
// revocation path of the HTLC from the PoV of the sender (offerer) of the
// HTLC. This uses a plain keyspend using the specified revocation key.
func ReceiverHTLCScriptTaprootRevoke(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx) (wire.TxWitness, error) {
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// The witness stack in this case is pretty simple: we only need to
// specify the signature generated.
witnessStack := make(wire.TxWitness, 1)
witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType)
return witnessStack, nil
}
// SecondLevelHtlcScript is the uniform script that's used as the output for
// the second-level HTLC transactions. The second level transaction act as a
// sort of covenant, ensuring that a 2-of-2 multi-sig output can only be
// spent in a particular way, and to a particular output.
//
// Possible Input Scripts:
//
// - To revoke an HTLC output that has been transitioned to the claim+delay
// state:
// <revoke sig> 1
//
// - To claim and HTLC output, either with a pre-image or due to a timeout:
// <delay sig> 0
//
// Output Script:
//
// OP_IF
// <revoke key>
// OP_ELSE
// <delay in blocks>
// OP_CHECKSEQUENCEVERIFY
// OP_DROP
// <delay key>
// OP_ENDIF
// OP_CHECKSIG
//
// TODO(roasbeef): possible renames for second-level
// - transition?
// - covenant output
func SecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey,
csvDelay uint32) ([]byte, error) {
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
ToLocalScriptSize,
))
// If this is the revocation clause for this script is to be executed,
// the spender will push a 1, forcing us to hit the true clause of this
// if statement.
builder.AddOp(txscript.OP_IF)
// If this is the revocation case, then we'll push the revocation
// public key on the stack.
builder.AddData(revocationKey.SerializeCompressed())
// Otherwise, this is either the sender or receiver of the HTLC
// attempting to claim the HTLC output.
builder.AddOp(txscript.OP_ELSE)
// In order to give the other party time to execute the revocation
// clause above, we require a relative timeout to pass before the
// output can be spent.
builder.AddInt64(int64(csvDelay))
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)
// If the relative timelock passes, then we'll add the delay key to the
// stack to ensure that we properly authenticate the spending party.
builder.AddData(delayKey.SerializeCompressed())
// Close out the if statement.
builder.AddOp(txscript.OP_ENDIF)
// In either case, we'll ensure that only either the party possessing
// the revocation private key, or the delay private key is able to
// spend this output.
builder.AddOp(txscript.OP_CHECKSIG)
return builder.Script()
}
// TODO(roasbeef): move all taproot stuff to new file?
// TaprootSecondLevelTapLeaf constructs the tap leaf used as the sole script
// path for a second level HTLC spend.
//
// The final script used is:
//
// <local_delay_key> OP_CHECKSIG
// <to_self_delay> OP_CHECKSEQUENCEVERIFY OP_DROP
func TaprootSecondLevelTapLeaf(delayKey *btcec.PublicKey,
csvDelay uint32) (txscript.TapLeaf, error) {
builder := txscript.NewScriptBuilder()
// Ensure the proper party can sign for this output.
builder.AddData(schnorr.SerializePubKey(delayKey))
builder.AddOp(txscript.OP_CHECKSIG)
// Assuming the above passes, then we'll now ensure that the CSV delay
// has been upheld, dropping the int we pushed on. If the sig above is
// valid, then a 1 will be left on the stack.
builder.AddInt64(int64(csvDelay))
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)
secondLevelLeafScript, err := builder.Script()
if err != nil {
return txscript.TapLeaf{}, err
}
return txscript.NewBaseTapLeaf(secondLevelLeafScript), nil
}
// SecondLevelHtlcTapscriptTree construct the indexed tapscript tree needed to
// generate the tap tweak to create the final output and also control block.
func SecondLevelHtlcTapscriptTree(delayKey *btcec.PublicKey, csvDelay uint32,
auxLeaf AuxTapLeaf) (*txscript.IndexedTapScriptTree, error) {
// First grab the second level leaf script we need to create the top
// level output.
secondLevelTapLeaf, err := TaprootSecondLevelTapLeaf(delayKey, csvDelay)
if err != nil {
return nil, err
}
tapLeaves := []txscript.TapLeaf{secondLevelTapLeaf}
auxLeaf.WhenSome(func(l txscript.TapLeaf) {
tapLeaves = append(tapLeaves, l)
})
// Now that we have the sole second level script, we can create the
// tapscript tree that commits to both the leaves.
return txscript.AssembleTaprootScriptTree(tapLeaves...), nil
}
// TaprootSecondLevelHtlcScript is the uniform script that's used as the output
// for the second-level HTLC transaction. The second level transaction acts as
// an off-chain 2-of-2 covenant that can only be spent a particular way and to
// a particular output.
//
// Possible Input Scripts:
// - revocation sig
// - <local_delay_sig>
//
// The script main script lets the broadcaster spend after a delay the script
// path:
//
// <local_delay_key> OP_CHECKSIG
// <to_self_delay> OP_CHECKSEQUENCEVERIFY OP_DROP
//
// The keyspend path require knowledge of the top level revocation private key.
func TaprootSecondLevelHtlcScript(revokeKey, delayKey *btcec.PublicKey,
csvDelay uint32, auxLeaf AuxTapLeaf) (*btcec.PublicKey, error) {
// First, we'll make the tapscript tree that commits to the redemption
// path.
tapScriptTree, err := SecondLevelHtlcTapscriptTree(
delayKey, csvDelay, auxLeaf,
)
if err != nil {
return nil, err
}
tapScriptRoot := tapScriptTree.RootNode.TapHash()
// With the tapscript root obtained, we'll tweak the revocation key
// with this value to obtain the key that the second level spend will
// create.
redemptionKey := txscript.ComputeTaprootOutputKey(
revokeKey, tapScriptRoot[:],
)
return redemptionKey, nil
}
// SecondLevelScriptTree is a tapscript tree used to spend the second level
// HTLC output after the CSV delay has passed.
type SecondLevelScriptTree struct {
ScriptTree
// SuccessTapLeaf is the tapleaf for the redemption path.
SuccessTapLeaf txscript.TapLeaf
// AuxLeaf is an optional leaf that can be used to extend the script
// tree.
AuxLeaf AuxTapLeaf
}
// TaprootSecondLevelScriptTree constructs the tapscript tree used to spend the
// second level HTLC output.
func TaprootSecondLevelScriptTree(revokeKey, delayKey *btcec.PublicKey,
csvDelay uint32, auxLeaf AuxTapLeaf) (*SecondLevelScriptTree, error) {
// First, we'll make the tapscript tree that commits to the redemption
// path.
tapScriptTree, err := SecondLevelHtlcTapscriptTree(
delayKey, csvDelay, auxLeaf,
)
if err != nil {
return nil, err
}
// With the tree constructed, we can make the pkscript which is the
// taproot output key itself.
tapScriptRoot := tapScriptTree.RootNode.TapHash()
outputKey := txscript.ComputeTaprootOutputKey(
revokeKey, tapScriptRoot[:],
)
return &SecondLevelScriptTree{
ScriptTree: ScriptTree{
TaprootKey: outputKey,
TapscriptTree: tapScriptTree,
TapscriptRoot: tapScriptRoot[:],
InternalKey: revokeKey,
},
SuccessTapLeaf: tapScriptTree.LeafMerkleProofs[0].TapLeaf,
AuxLeaf: auxLeaf,
}, nil
}
// WitnessScriptToSign returns the witness script that we'll use when signing
// for the remote party, and also verifying signatures on our transactions. As
// an example, when we create an outgoing HTLC for the remote party, we want to
// sign their success path.
func (s *SecondLevelScriptTree) WitnessScriptToSign() []byte {
return s.SuccessTapLeaf.Script
}
// WitnessScriptForPath returns the witness script for the given spending path.
// An error is returned if the path is unknown.
func (s *SecondLevelScriptTree) WitnessScriptForPath(
path ScriptPath) ([]byte, error) {
switch path {
case ScriptPathDelay:
fallthrough
case ScriptPathSuccess:
return s.SuccessTapLeaf.Script, nil
default:
return nil, fmt.Errorf("unknown script path: %v", path)
}
}
// CtrlBlockForPath returns the control block for the given spending path. For
// script types that don't have a control block, nil is returned.
func (s *SecondLevelScriptTree) CtrlBlockForPath(
path ScriptPath) (*txscript.ControlBlock, error) {
switch path {
case ScriptPathDelay:
fallthrough
case ScriptPathSuccess:
return lnutils.Ptr(MakeTaprootCtrlBlock(
s.SuccessTapLeaf.Script, s.InternalKey,
s.TapscriptTree,
)), nil
default:
return nil, fmt.Errorf("unknown script path: %v", path)
}
}
// Tree returns the underlying ScriptTree of the SecondLevelScriptTree.
func (s *SecondLevelScriptTree) Tree() ScriptTree {
return s.ScriptTree
}
// A compile time check to ensure SecondLevelScriptTree implements the
// TapscriptDescriptor interface.
var _ TapscriptDescriptor = (*SecondLevelScriptTree)(nil)
// TaprootHtlcSpendRevoke spends a second-level HTLC output via the revocation
// path. This uses the top level keyspend path to redeem the contested output.
//
// The passed SignDescriptor MUST have the proper witness script and also the
// proper top-level tweak derived from the tapscript tree for the second level
// output.
func TaprootHtlcSpendRevoke(signer Signer, signDesc *SignDescriptor,
revokeTx *wire.MsgTx) (wire.TxWitness, error) {
// We don't need any spacial modifications to the transaction as this
// is just sweeping a revoked HTLC output. So we'll generate a regular
// schnorr signature.
sweepSig, err := signer.SignOutputRaw(revokeTx, signDesc)
if err != nil {
return nil, err
}
// The witness stack in this case is pretty simple: we only need to
// specify the signature generated.
witnessStack := make(wire.TxWitness, 1)
witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType)
return witnessStack, nil
}
// TaprootHtlcSpendSuccess spends a second-level HTLC output via the redemption
// path. This should be used to sweep funds after the pre-image is known.
//
// NOTE: The caller MUST set the txn version, sequence number, and sign
// descriptor's sig hash cache before invocation.
func TaprootHtlcSpendSuccess(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx, revokeKey *btcec.PublicKey,
tapscriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) {
// First, we'll generate the sweep signature based on the populated
// sign desc. This should give us a valid schnorr signature for the
// sole script path leaf.
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
var ctrlBlock []byte
if signDesc.ControlBlock == nil {
// Now that we have the sweep signature, we'll construct the
// control block needed to spend the script path.
redeemControlBlock := MakeTaprootCtrlBlock(
signDesc.WitnessScript, revokeKey, tapscriptTree,
)
ctrlBlock, err = redeemControlBlock.ToBytes()
if err != nil {
return nil, err
}
} else {
ctrlBlock = signDesc.ControlBlock
}
// Now that we have the redeem control block, we can construct the
// final witness needed to spend the script:
//
// <success sig> <success script> <control_block>
witnessStack := make(wire.TxWitness, 3)
witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType)
witnessStack[1] = signDesc.WitnessScript
witnessStack[2] = ctrlBlock
return witnessStack, nil
}
// LeaseSecondLevelHtlcScript is the uniform script that's used as the output
// for the second-level HTLC transactions. The second level transaction acts as
// a sort of covenant, ensuring that a 2-of-2 multi-sig output can only be
// spent in a particular way, and to a particular output.
//
// Possible Input Scripts:
//
// - To revoke an HTLC output that has been transitioned to the claim+delay
// state:
// <revoke sig> 1
//
// - To claim an HTLC output, either with a pre-image or due to a timeout:
// <delay sig> 0
//
// Output Script:
//
// OP_IF
// <revoke key>
// OP_ELSE
// <lease maturity in blocks>
// OP_CHECKLOCKTIMEVERIFY
// OP_DROP
// <delay in blocks>
// OP_CHECKSEQUENCEVERIFY
// OP_DROP
// <delay key>
// OP_ENDIF
// OP_CHECKSIG.
func LeaseSecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey,
csvDelay, cltvExpiry uint32) ([]byte, error) {
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
ToLocalScriptSize + LeaseWitnessScriptSizeOverhead,
))
// If this is the revocation clause for this script is to be executed,
// the spender will push a 1, forcing us to hit the true clause of this
// if statement.
builder.AddOp(txscript.OP_IF)
// If this this is the revocation case, then we'll push the revocation
// public key on the stack.
builder.AddData(revocationKey.SerializeCompressed())
// Otherwise, this is either the sender or receiver of the HTLC
// attempting to claim the HTLC output.
builder.AddOp(txscript.OP_ELSE)
// The channel initiator always has the additional channel lease
// expiration constraint for outputs that pay to them which must be
// satisfied.
builder.AddInt64(int64(cltvExpiry))
builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY)
builder.AddOp(txscript.OP_DROP)
// In order to give the other party time to execute the revocation
// clause above, we require a relative timeout to pass before the
// output can be spent.
builder.AddInt64(int64(csvDelay))
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)
// If the relative timelock passes, then we'll add the delay key to the
// stack to ensure that we properly authenticate the spending party.
builder.AddData(delayKey.SerializeCompressed())
// Close out the if statement.
builder.AddOp(txscript.OP_ENDIF)
// In either case, we'll ensure that only either the party possessing
// the revocation private key, or the delay private key is able to
// spend this output.
builder.AddOp(txscript.OP_CHECKSIG)
return builder.Script()
}
// HtlcSpendSuccess spends a second-level HTLC output. This function is to be
// used by the sender of an HTLC to claim the output after a relative timeout
// or the receiver of the HTLC to claim on-chain with the pre-image.
func HtlcSpendSuccess(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx, csvDelay uint32) (wire.TxWitness, error) {
// We're required to wait a relative period of time before we can sweep
// the output in order to allow the other party to contest our claim of
// validity to this version of the commitment transaction.
sweepTx.TxIn[0].Sequence = LockTimeToSequence(false, csvDelay)
// Finally, OP_CSV requires that the version of the transaction
// spending a pkscript with OP_CSV within it *must* be >= 2.
sweepTx.Version = 2
// As we mutated the transaction, we'll re-calculate the sighashes for
// this instance.
signDesc.SigHashes = NewTxSigHashesV0Only(sweepTx)
// With the proper sequence and version set, we'll now sign the timeout
// transaction using the passed signed descriptor. In order to generate
// a valid signature, then signDesc should be using the base delay
// public key, and the proper single tweak bytes.
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// We set a zero as the first element the witness stack (ignoring the
// witness script), in order to force execution to the second portion
// of the if clause.
witnessStack := wire.TxWitness(make([][]byte, 3))
witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType))
witnessStack[1] = nil
witnessStack[2] = signDesc.WitnessScript
return witnessStack, nil
}
// HtlcSpendRevoke spends a second-level HTLC output. This function is to be
// used by the sender or receiver of an HTLC to claim the HTLC after a revoked
// commitment transaction was broadcast.
func HtlcSpendRevoke(signer Signer, signDesc *SignDescriptor,
revokeTx *wire.MsgTx) (wire.TxWitness, error) {
// We don't need any spacial modifications to the transaction as this
// is just sweeping a revoked HTLC output. So we'll generate a regular
// witness signature.
sweepSig, err := signer.SignOutputRaw(revokeTx, signDesc)
if err != nil {
return nil, err
}
// We set a one as the first element the witness stack (ignoring the
// witness script), in order to force execution to the revocation
// clause in the second level HTLC script.
witnessStack := wire.TxWitness(make([][]byte, 3))
witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType))
witnessStack[1] = []byte{1}
witnessStack[2] = signDesc.WitnessScript
return witnessStack, nil
}
// HtlcSecondLevelSpend exposes the public witness generation function for
// spending an HTLC success transaction, either due to an expiring time lock or
// having had the payment preimage. This method is able to spend any
// second-level HTLC transaction, assuming the caller sets the locktime or
// seqno properly.
//
// NOTE: The caller MUST set the txn version, sequence number, and sign
// descriptor's sig hash cache before invocation.
func HtlcSecondLevelSpend(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx) (wire.TxWitness, error) {
// With the proper sequence and version set, we'll now sign the timeout
// transaction using the passed signed descriptor. In order to generate
// a valid signature, then signDesc should be using the base delay
// public key, and the proper single tweak bytes.
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// We set a zero as the first element the witness stack (ignoring the
// witness script), in order to force execution to the second portion
// of the if clause.
witnessStack := wire.TxWitness(make([][]byte, 3))
witnessStack[0] = append(sweepSig.Serialize(), byte(txscript.SigHashAll))
witnessStack[1] = nil
witnessStack[2] = signDesc.WitnessScript
return witnessStack, nil
}
// LockTimeToSequence converts the passed relative locktime to a sequence
// number in accordance to BIP-68.
// See: https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki
// - (Compatibility)
func LockTimeToSequence(isSeconds bool, locktime uint32) uint32 {
if !isSeconds {
// The locktime is to be expressed in confirmations.
return locktime
}
// Set the 22nd bit which indicates the lock time is in seconds, then
// shift the locktime over by 9 since the time granularity is in
// 512-second intervals (2^9). This results in a max lock-time of
// 33,554,431 seconds, or 1.06 years.
return SequenceLockTimeSeconds | (locktime >> 9)
}
// CommitScriptToSelf constructs the public key script for the output on the
// commitment transaction paying to the "owner" of said commitment transaction.
// If the other party learns of the preimage to the revocation hash, then they
// can claim all the settled funds in the channel, plus the unsettled funds.
//
// Possible Input Scripts:
//
// REVOKE: <sig> 1
// SENDRSWEEP: <sig> <emptyvector>
//
// Output Script:
//
// OP_IF
// <revokeKey>
// OP_ELSE
// <numRelativeBlocks> OP_CHECKSEQUENCEVERIFY OP_DROP
// <selfKey>
// OP_ENDIF
// OP_CHECKSIG
func CommitScriptToSelf(csvTimeout uint32, selfKey, revokeKey *btcec.PublicKey) ([]byte, error) {
// This script is spendable under two conditions: either the
// 'csvTimeout' has passed and we can redeem our funds, or they can
// produce a valid signature with the revocation public key. The
// revocation public key will *only* be known to the other party if we
// have divulged the revocation hash, allowing them to homomorphically
// derive the proper private key which corresponds to the revoke public
// key.
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
ToLocalScriptSize,
))
builder.AddOp(txscript.OP_IF)
// If a valid signature using the revocation key is presented, then
// allow an immediate spend provided the proper signature.
builder.AddData(revokeKey.SerializeCompressed())
builder.AddOp(txscript.OP_ELSE)
// Otherwise, we can re-claim our funds after a CSV delay of
// 'csvTimeout' timeout blocks, and a valid signature.
builder.AddInt64(int64(csvTimeout))
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)
builder.AddData(selfKey.SerializeCompressed())
builder.AddOp(txscript.OP_ENDIF)
// Finally, we'll validate the signature against the public key that's
// left on the top of the stack.
builder.AddOp(txscript.OP_CHECKSIG)
return builder.Script()
}
// CommitScriptTree holds the taproot output key (in this case the revocation
// key, or a NUMs point for the remote output) along with the tapscript leaf
// that can spend the output after a delay.
type CommitScriptTree struct {
ScriptTree
// SettleLeaf is the leaf used to settle the output after the delay.
SettleLeaf txscript.TapLeaf
// RevocationLeaf is the leaf used to spend the output with the
// revocation key signature.
RevocationLeaf txscript.TapLeaf
// AuxLeaf is an auxiliary leaf that can be used to extend the base
// commitment script tree with new spend paths, or just as extra
// commitment space. When present, this leaf will always be in the
// left-most or right-most area of the tapscript tree.
AuxLeaf AuxTapLeaf
}
// A compile time check to ensure CommitScriptTree implements the
// TapscriptDescriptor interface.
var _ TapscriptDescriptor = (*CommitScriptTree)(nil)
// WitnessScriptToSign returns the witness script that we'll use when signing
// for the remote party, and also verifying signatures on our transactions. As
// an example, when we create an outgoing HTLC for the remote party, we want to
// sign their success path.
func (c *CommitScriptTree) WitnessScriptToSign() []byte {
// TODO(roasbeef): abstraction leak here? always dependent
return nil
}
// WitnessScriptForPath returns the witness script for the given spending path.
// An error is returned if the path is unknown.
func (c *CommitScriptTree) WitnessScriptForPath(
path ScriptPath) ([]byte, error) {
switch path {
// For the commitment output, the delay and success path are the same,
// so we'll fall through here to success.
case ScriptPathDelay:
fallthrough
case ScriptPathSuccess:
return c.SettleLeaf.Script, nil
case ScriptPathRevocation:
return c.RevocationLeaf.Script, nil
default:
return nil, fmt.Errorf("unknown script path: %v", path)
}
}
// CtrlBlockForPath returns the control block for the given spending path. For
// script types that don't have a control block, nil is returned.
func (c *CommitScriptTree) CtrlBlockForPath(
path ScriptPath) (*txscript.ControlBlock, error) {
switch path {
case ScriptPathDelay:
fallthrough
case ScriptPathSuccess:
return lnutils.Ptr(MakeTaprootCtrlBlock(
c.SettleLeaf.Script, c.InternalKey,
c.TapscriptTree,
)), nil
case ScriptPathRevocation:
return lnutils.Ptr(MakeTaprootCtrlBlock(
c.RevocationLeaf.Script, c.InternalKey,
c.TapscriptTree,
)), nil
default:
return nil, fmt.Errorf("unknown script path: %v", path)
}
}
// Tree returns the underlying ScriptTree of the CommitScriptTree.
func (c *CommitScriptTree) Tree() ScriptTree {
return c.ScriptTree
}
// NewLocalCommitScriptTree returns a new CommitScript tree that can be used to
// create and spend the commitment output for the local party.
func NewLocalCommitScriptTree(csvTimeout uint32, selfKey,
revokeKey *btcec.PublicKey, auxLeaf AuxTapLeaf) (*CommitScriptTree,
error) {
// First, we'll need to construct the tapLeaf that'll be our delay CSV
// clause.
delayScript, err := TaprootLocalCommitDelayScript(csvTimeout, selfKey)
if err != nil {
return nil, err
}
// Next, we'll need to construct the revocation path, which is just a
// simple checksig script.
revokeScript, err := TaprootLocalCommitRevokeScript(selfKey, revokeKey)
if err != nil {
return nil, err
}
// With both scripts computed, we'll now create a tapscript tree with
// the two leaves, and then obtain a root from that.
delayTapLeaf := txscript.NewBaseTapLeaf(delayScript)
revokeTapLeaf := txscript.NewBaseTapLeaf(revokeScript)
tapLeaves := []txscript.TapLeaf{delayTapLeaf, revokeTapLeaf}
auxLeaf.WhenSome(func(l txscript.TapLeaf) {
tapLeaves = append(tapLeaves, l)
})
tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaves...)
tapScriptRoot := tapScriptTree.RootNode.TapHash()
// Now that we have our root, we can arrive at the final output script
// by tweaking the internal key with this root.
toLocalOutputKey := txscript.ComputeTaprootOutputKey(
&TaprootNUMSKey, tapScriptRoot[:],
)
return &CommitScriptTree{
ScriptTree: ScriptTree{
TaprootKey: toLocalOutputKey,
TapscriptTree: tapScriptTree,
TapscriptRoot: tapScriptRoot[:],
InternalKey: &TaprootNUMSKey,
},
SettleLeaf: delayTapLeaf,
RevocationLeaf: revokeTapLeaf,
AuxLeaf: auxLeaf,
}, nil
}
// TaprootLocalCommitDelayScript builds the tap leaf with the CSV delay script
// for the to-local output.
func TaprootLocalCommitDelayScript(csvTimeout uint32,
selfKey *btcec.PublicKey) ([]byte, error) {
builder := txscript.NewScriptBuilder()
builder.AddData(schnorr.SerializePubKey(selfKey))
builder.AddOp(txscript.OP_CHECKSIG)
builder.AddInt64(int64(csvTimeout))
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)
return builder.Script()
}
// TaprootLocalCommitRevokeScript builds the tap leaf with the revocation path
// for the to-local output.
func TaprootLocalCommitRevokeScript(selfKey, revokeKey *btcec.PublicKey) (
[]byte, error) {
builder := txscript.NewScriptBuilder()
builder.AddData(schnorr.SerializePubKey(selfKey))
builder.AddOp(txscript.OP_DROP)
builder.AddData(schnorr.SerializePubKey(revokeKey))
builder.AddOp(txscript.OP_CHECKSIG)
return builder.Script()
}
// TaprootCommitScriptToSelf creates the taproot witness program that commits
// to the revocation (script path) and delay path (script path) in a single
// taproot output key. Both the delay script and the revocation script are part
// of the tapscript tree to ensure that the internal key (the local delay key)
// is always revealed. This ensures that a 3rd party can always sweep the set
// of anchor outputs.
//
// For the delay path we have the following tapscript leaf script:
//
// <local_delayedpubkey> OP_CHECKSIG
// <to_self_delay> OP_CHECKSEQUENCEVERIFY OP_DROP
//
// This can then be spent with just:
//
// <local_delayedsig> <to_delay_script> <delay_control_block>
//
// Where the to_delay_script is listed above, and the delay_control_block
// computed as:
//
// delay_control_block = (output_key_y_parity | 0xc0) || taproot_nums_key
//
// The revocation path is simply:
//
// <local_delayedpubkey> OP_DROP
// <revocationkey> OP_CHECKSIG
//
// The revocation path can be spent with a control block similar to the above
// (but contains the hash of the other script), and with the following witness:
//
// <revocation_sig>
//
// We use a noop data push to ensure that the local public key is also revealed
// on chain, which enables the anchor output to be swept.
func TaprootCommitScriptToSelf(csvTimeout uint32,
selfKey, revokeKey *btcec.PublicKey) (*btcec.PublicKey, error) {
commitScriptTree, err := NewLocalCommitScriptTree(
csvTimeout, selfKey, revokeKey, NoneTapLeaf(),
)
if err != nil {
return nil, err
}
return commitScriptTree.TaprootKey, nil
}
// MakeTaprootCtrlBlock takes a leaf script, the internal key (usually the
// revoke key), and a script tree and creates a valid control block for a spend
// of the leaf.
func MakeTaprootCtrlBlock(leafScript []byte, internalKey *btcec.PublicKey,
scriptTree *txscript.IndexedTapScriptTree) txscript.ControlBlock {
tapLeafHash := txscript.NewBaseTapLeaf(leafScript).TapHash()
scriptIdx := scriptTree.LeafProofIndex[tapLeafHash]
settleMerkleProof := scriptTree.LeafMerkleProofs[scriptIdx]
return settleMerkleProof.ToControlBlock(internalKey)
}
// TaprootCommitSpendSuccess constructs a valid witness allowing a node to
// sweep the settled taproot output after the delay has passed for a force
// close.
func TaprootCommitSpendSuccess(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx,
scriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) {
// First, we'll need to construct a valid control block to execute the
// leaf script for sweep settlement.
//
// TODO(roasbeef); make into closure instead? only need reovke key and
// scriptTree to make the ctrl block -- then default version that would
// take froms ign desc?
var ctrlBlockBytes []byte
if signDesc.ControlBlock == nil {
settleControlBlock := MakeTaprootCtrlBlock(
signDesc.WitnessScript, &TaprootNUMSKey, scriptTree,
)
ctrlBytes, err := settleControlBlock.ToBytes()
if err != nil {
return nil, err
}
ctrlBlockBytes = ctrlBytes
} else {
ctrlBlockBytes = signDesc.ControlBlock
}
// With the control block created, we'll now generate the signature we
// need to authorize the spend.
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// The final witness stack will be:
//
// <sweep sig> <sweep script> <control block>
witnessStack := make(wire.TxWitness, 3)
witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType)
witnessStack[1] = signDesc.WitnessScript
witnessStack[2] = ctrlBlockBytes
return witnessStack, nil
}
// TaprootCommitSpendRevoke constructs a valid witness allowing a node to sweep
// the revoked taproot output of a malicious peer.
func TaprootCommitSpendRevoke(signer Signer, signDesc *SignDescriptor,
revokeTx *wire.MsgTx,
scriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) {
// First, we'll need to construct a valid control block to execute the
// leaf script for revocation path.
var ctrlBlockBytes []byte
if signDesc.ControlBlock == nil {
revokeCtrlBlock := MakeTaprootCtrlBlock(
signDesc.WitnessScript, &TaprootNUMSKey, scriptTree,
)
revokeBytes, err := revokeCtrlBlock.ToBytes()
if err != nil {
return nil, err
}
ctrlBlockBytes = revokeBytes
} else {
ctrlBlockBytes = signDesc.ControlBlock
}
// With the control block created, we'll now generate the signature we
// need to authorize the spend.
revokeSig, err := signer.SignOutputRaw(revokeTx, signDesc)
if err != nil {
return nil, err
}
// The final witness stack will be:
//
// <revoke sig sig> <revoke script> <control block>
witnessStack := make(wire.TxWitness, 3)
witnessStack[0] = maybeAppendSighash(revokeSig, signDesc.HashType)
witnessStack[1] = signDesc.WitnessScript
witnessStack[2] = ctrlBlockBytes
return witnessStack, nil
}
// LeaseCommitScriptToSelf constructs the public key script for the output on the
// commitment transaction paying to the "owner" of said commitment transaction.
// If the other party learns of the preimage to the revocation hash, then they
// can claim all the settled funds in the channel, plus the unsettled funds.
//
// Possible Input Scripts:
//
// REVOKE: <sig> 1
// SENDRSWEEP: <sig> <emptyvector>
//
// Output Script:
//
// OP_IF
// <revokeKey>
// OP_ELSE
// <absoluteLeaseExpiry> OP_CHECKLOCKTIMEVERIFY OP_DROP
// <numRelativeBlocks> OP_CHECKSEQUENCEVERIFY OP_DROP
// <selfKey>
// OP_ENDIF
// OP_CHECKSIG
func LeaseCommitScriptToSelf(selfKey, revokeKey *btcec.PublicKey,
csvTimeout, leaseExpiry uint32) ([]byte, error) {
// This script is spendable under two conditions: either the
// 'csvTimeout' has passed and we can redeem our funds, or they can
// produce a valid signature with the revocation public key. The
// revocation public key will *only* be known to the other party if we
// have divulged the revocation hash, allowing them to homomorphically
// derive the proper private key which corresponds to the revoke public
// key.
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
ToLocalScriptSize + LeaseWitnessScriptSizeOverhead,
))
builder.AddOp(txscript.OP_IF)
// If a valid signature using the revocation key is presented, then
// allow an immediate spend provided the proper signature.
builder.AddData(revokeKey.SerializeCompressed())
builder.AddOp(txscript.OP_ELSE)
// Otherwise, we can re-claim our funds after once the CLTV lease
// maturity has been met, along with the CSV delay of 'csvTimeout'
// timeout blocks, and a valid signature.
builder.AddInt64(int64(leaseExpiry))
builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY)
builder.AddOp(txscript.OP_DROP)
builder.AddInt64(int64(csvTimeout))
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)
builder.AddData(selfKey.SerializeCompressed())
builder.AddOp(txscript.OP_ENDIF)
// Finally, we'll validate the signature against the public key that's
// left on the top of the stack.
builder.AddOp(txscript.OP_CHECKSIG)
return builder.Script()
}
// CommitSpendTimeout constructs a valid witness allowing the owner of a
// particular commitment transaction to spend the output returning settled
// funds back to themselves after a relative block timeout. In order to
// properly spend the transaction, the target input's sequence number should be
// set accordingly based off of the target relative block timeout within the
// redeem script. Additionally, OP_CSV requires that the version of the
// transaction spending a pkscript with OP_CSV within it *must* be >= 2.
func CommitSpendTimeout(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx) (wire.TxWitness, error) {
// Ensure the transaction version supports the validation of sequence
// locks and CSV semantics.
if sweepTx.Version < 2 {
return nil, fmt.Errorf("version of passed transaction MUST "+
"be >= 2, not %v", sweepTx.Version)
}
// With the sequence number in place, we're now able to properly sign
// off on the sweep transaction.
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// Place an empty byte as the first item in the evaluated witness stack
// to force script execution to the timeout spend clause. We need to
// place an empty byte in order to ensure our script is still valid
// from the PoV of nodes that are enforcing minimal OP_IF/OP_NOTIF.
witnessStack := wire.TxWitness(make([][]byte, 3))
witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType))
witnessStack[1] = nil
witnessStack[2] = signDesc.WitnessScript
return witnessStack, nil
}
// CommitSpendRevoke constructs a valid witness allowing a node to sweep the
// settled output of a malicious counterparty who broadcasts a revoked
// commitment transaction.
//
// NOTE: The passed SignDescriptor should include the raw (untweaked)
// revocation base public key of the receiver and also the proper double tweak
// value based on the commitment secret of the revoked commitment.
func CommitSpendRevoke(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx) (wire.TxWitness, error) {
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// Place a 1 as the first item in the evaluated witness stack to
// force script execution to the revocation clause.
witnessStack := wire.TxWitness(make([][]byte, 3))
witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType))
witnessStack[1] = []byte{1}
witnessStack[2] = signDesc.WitnessScript
return witnessStack, nil
}
// CommitSpendNoDelay constructs a valid witness allowing a node to spend their
// settled no-delay output on the counterparty's commitment transaction. If the
// tweakless field is true, then we'll omit the set where we tweak the pubkey
// with a random set of bytes, and use it directly in the witness stack.
//
// NOTE: The passed SignDescriptor should include the raw (untweaked) public
// key of the receiver and also the proper single tweak value based on the
// current commitment point.
func CommitSpendNoDelay(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx, tweakless bool) (wire.TxWitness, error) {
if signDesc.KeyDesc.PubKey == nil {
return nil, fmt.Errorf("cannot generate witness with nil " +
"KeyDesc pubkey")
}
// This is just a regular p2wkh spend which looks something like:
// * witness: <sig> <pubkey>
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// Finally, we'll manually craft the witness. The witness here is the
// exact same as a regular p2wkh witness, depending on the value of the
// tweakless bool.
witness := make([][]byte, 2)
witness[0] = append(sweepSig.Serialize(), byte(signDesc.HashType))
switch tweakless {
// If we're tweaking the key, then we use the tweaked public key as the
// last item in the witness stack which was originally used to created
// the pkScript we're spending.
case false:
witness[1] = TweakPubKeyWithTweak(
signDesc.KeyDesc.PubKey, signDesc.SingleTweak,
).SerializeCompressed()
// Otherwise, we can just use the raw pubkey, since there's no random
// value to be combined.
case true:
witness[1] = signDesc.KeyDesc.PubKey.SerializeCompressed()
}
return witness, nil
}
// CommitScriptUnencumbered constructs the public key script on the commitment
// transaction paying to the "other" party. The constructed output is a normal
// p2wkh output spendable immediately, requiring no contestation period.
func CommitScriptUnencumbered(key *btcec.PublicKey) ([]byte, error) {
// This script goes to the "other" party, and is spendable immediately.
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
P2WPKHSize,
))
builder.AddOp(txscript.OP_0)
builder.AddData(btcutil.Hash160(key.SerializeCompressed()))
return builder.Script()
}
// CommitScriptToRemoteConfirmed constructs the script for the output on the
// commitment transaction paying to the remote party of said commitment
// transaction. The money can only be spend after one confirmation.
//
// Possible Input Scripts:
//
// SWEEP: <sig>
//
// Output Script:
//
// <key> OP_CHECKSIGVERIFY
// 1 OP_CHECKSEQUENCEVERIFY
func CommitScriptToRemoteConfirmed(key *btcec.PublicKey) ([]byte, error) {
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
ToRemoteConfirmedScriptSize,
))
// Only the given key can spend the output.
builder.AddData(key.SerializeCompressed())
builder.AddOp(txscript.OP_CHECKSIGVERIFY)
// Check that the it has one confirmation.
builder.AddOp(txscript.OP_1)
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
return builder.Script()
}
// NewRemoteCommitScriptTree constructs a new script tree for the remote party
// to sweep their funds after a hard coded 1 block delay.
func NewRemoteCommitScriptTree(remoteKey *btcec.PublicKey,
auxLeaf AuxTapLeaf) (*CommitScriptTree, error) {
// First, construct the remote party's tapscript they'll use to sweep
// their outputs.
builder := txscript.NewScriptBuilder()
builder.AddData(schnorr.SerializePubKey(remoteKey))
builder.AddOp(txscript.OP_CHECKSIG)
builder.AddOp(txscript.OP_1)
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)
remoteScript, err := builder.Script()
if err != nil {
return nil, err
}
tapLeaf := txscript.NewBaseTapLeaf(remoteScript)
tapLeaves := []txscript.TapLeaf{tapLeaf}
auxLeaf.WhenSome(func(l txscript.TapLeaf) {
tapLeaves = append(tapLeaves, l)
})
// With this script constructed, we'll map that into a tapLeaf, then
// make a new tapscript root from that.
tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaves...)
tapScriptRoot := tapScriptTree.RootNode.TapHash()
// Now that we have our root, we can arrive at the final output script
// by tweaking the internal key with this root.
toRemoteOutputKey := txscript.ComputeTaprootOutputKey(
&TaprootNUMSKey, tapScriptRoot[:],
)
return &CommitScriptTree{
ScriptTree: ScriptTree{
TaprootKey: toRemoteOutputKey,
TapscriptTree: tapScriptTree,
TapscriptRoot: tapScriptRoot[:],
InternalKey: &TaprootNUMSKey,
},
SettleLeaf: tapLeaf,
AuxLeaf: auxLeaf,
}, nil
}
// TaprootCommitScriptToRemote constructs a taproot witness program for the
// output on the commitment transaction for the remote party. For the top level
// key spend, we'll use a NUMs key to ensure that only the script path can be
// taken. Using a set NUMs key here also means that recovery solutions can scan
// the chain given knowledge of the public key for the remote party. We then
// commit to a single tapscript leaf that holds the normal CSV 1 delay
// script.
//
// Our single tapleaf will use the following script:
//
// <remotepubkey> OP_CHECKSIG
// 1 OP_CHECKSEQUENCEVERIFY OP_DROP
func TaprootCommitScriptToRemote(remoteKey *btcec.PublicKey,
auxLeaf AuxTapLeaf) (*btcec.PublicKey, error) {
commitScriptTree, err := NewRemoteCommitScriptTree(remoteKey, auxLeaf)
if err != nil {
return nil, err
}
return commitScriptTree.TaprootKey, nil
}
// TaprootCommitRemoteSpend allows the remote party to sweep their output into
// their wallet after an enforced 1 block delay.
func TaprootCommitRemoteSpend(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx,
scriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) {
// First, we'll need to construct a valid control block to execute the
// leaf script for sweep settlement.
var ctrlBlockBytes []byte
if signDesc.ControlBlock == nil {
settleControlBlock := MakeTaprootCtrlBlock(
signDesc.WitnessScript, &TaprootNUMSKey, scriptTree,
)
ctrlBytes, err := settleControlBlock.ToBytes()
if err != nil {
return nil, err
}
ctrlBlockBytes = ctrlBytes
} else {
ctrlBlockBytes = signDesc.ControlBlock
}
// With the control block created, we'll now generate the signature we
// need to authorize the spend.
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// The final witness stack will be:
//
// <sweep sig> <sweep script> <control block>
witnessStack := make(wire.TxWitness, 3)
witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType)
witnessStack[1] = signDesc.WitnessScript
witnessStack[2] = ctrlBlockBytes
return witnessStack, nil
}
// LeaseCommitScriptToRemoteConfirmed constructs the script for the output on
// the commitment transaction paying to the remote party of said commitment
// transaction. The money can only be spend after one confirmation.
//
// Possible Input Scripts:
//
// SWEEP: <sig>
//
// Output Script:
//
// <key> OP_CHECKSIGVERIFY
// <lease maturity in blocks> OP_CHECKLOCKTIMEVERIFY OP_DROP
// 1 OP_CHECKSEQUENCEVERIFY
func LeaseCommitScriptToRemoteConfirmed(key *btcec.PublicKey,
leaseExpiry uint32) ([]byte, error) {
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(45))
// Only the given key can spend the output.
builder.AddData(key.SerializeCompressed())
builder.AddOp(txscript.OP_CHECKSIGVERIFY)
// The channel initiator always has the additional channel lease
// expiration constraint for outputs that pay to them which must be
// satisfied.
builder.AddInt64(int64(leaseExpiry))
builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY)
builder.AddOp(txscript.OP_DROP)
// Check that it has one confirmation.
builder.AddOp(txscript.OP_1)
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
return builder.Script()
}
// CommitSpendToRemoteConfirmed constructs a valid witness allowing a node to
// spend their settled output on the counterparty's commitment transaction when
// it has one confirmetion. This is used for the anchor channel type. The
// spending key will always be non-tweaked for this output type.
func CommitSpendToRemoteConfirmed(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx) (wire.TxWitness, error) {
if signDesc.KeyDesc.PubKey == nil {
return nil, fmt.Errorf("cannot generate witness with nil " +
"KeyDesc pubkey")
}
// Similar to non delayed output, only a signature is needed.
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// Finally, we'll manually craft the witness. The witness here is the
// signature and the redeem script.
witnessStack := make([][]byte, 2)
witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType))
witnessStack[1] = signDesc.WitnessScript
return witnessStack, nil
}
// CommitScriptAnchor constructs the script for the anchor output spendable by
// the given key immediately, or by anyone after 16 confirmations.
//
// Possible Input Scripts:
//
// By owner: <sig>
// By anyone (after 16 conf): <emptyvector>
//
// Output Script:
//
// <funding_pubkey> OP_CHECKSIG OP_IFDUP
// OP_NOTIF
// OP_16 OP_CSV
// OP_ENDIF
func CommitScriptAnchor(key *btcec.PublicKey) ([]byte, error) {
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
AnchorScriptSize,
))
// Spend immediately with key.
builder.AddData(key.SerializeCompressed())
builder.AddOp(txscript.OP_CHECKSIG)
// Duplicate the value if true, since it will be consumed by the NOTIF.
builder.AddOp(txscript.OP_IFDUP)
// Otherwise spendable by anyone after 16 confirmations.
builder.AddOp(txscript.OP_NOTIF)
builder.AddOp(txscript.OP_16)
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_ENDIF)
return builder.Script()
}
// AnchorScriptTree holds all the contents needed to sweep a taproot anchor
// output on chain.
type AnchorScriptTree struct {
ScriptTree
// SweepLeaf is the leaf used to settle the output after the delay.
SweepLeaf txscript.TapLeaf
}
// NewAnchorScriptTree makes a new script tree for an anchor output with the
// passed anchor key.
func NewAnchorScriptTree(
anchorKey *btcec.PublicKey) (*AnchorScriptTree, error) {
// The main script used is just a OP_16 CSV (anyone can sweep after 16
// blocks).
builder := txscript.NewScriptBuilder()
builder.AddOp(txscript.OP_16)
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
anchorScript, err := builder.Script()
if err != nil {
return nil, err
}
// With the script, we can make our sole leaf, then derive the root
// from that.
tapLeaf := txscript.NewBaseTapLeaf(anchorScript)
tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf)
tapScriptRoot := tapScriptTree.RootNode.TapHash()
// Now that we have our root, we can arrive at the final output script
// by tweaking the internal key with this root.
anchorOutputKey := txscript.ComputeTaprootOutputKey(
anchorKey, tapScriptRoot[:],
)
return &AnchorScriptTree{
ScriptTree: ScriptTree{
TaprootKey: anchorOutputKey,
TapscriptTree: tapScriptTree,
TapscriptRoot: tapScriptRoot[:],
InternalKey: anchorKey,
},
SweepLeaf: tapLeaf,
}, nil
}
// WitnessScriptToSign returns the witness script that we'll use when signing
// for the remote party, and also verifying signatures on our transactions. As
// an example, when we create an outgoing HTLC for the remote party, we want to
// sign their success path.
func (a *AnchorScriptTree) WitnessScriptToSign() []byte {
return a.SweepLeaf.Script
}
// WitnessScriptForPath returns the witness script for the given spending path.
// An error is returned if the path is unknown.
func (a *AnchorScriptTree) WitnessScriptForPath(
path ScriptPath) ([]byte, error) {
switch path {
case ScriptPathDelay:
fallthrough
case ScriptPathSuccess:
return a.SweepLeaf.Script, nil
default:
return nil, fmt.Errorf("unknown script path: %v", path)
}
}
// CtrlBlockForPath returns the control block for the given spending path. For
// script types that don't have a control block, nil is returned.
func (a *AnchorScriptTree) CtrlBlockForPath(
path ScriptPath) (*txscript.ControlBlock, error) {
switch path {
case ScriptPathDelay:
fallthrough
case ScriptPathSuccess:
return lnutils.Ptr(MakeTaprootCtrlBlock(
a.SweepLeaf.Script, a.InternalKey,
a.TapscriptTree,
)), nil
default:
return nil, fmt.Errorf("unknown script path: %v", path)
}
}
// Tree returns the underlying ScriptTree of the AnchorScriptTree.
func (a *AnchorScriptTree) Tree() ScriptTree {
return a.ScriptTree
}
// A compile time check to ensure AnchorScriptTree implements the
// TapscriptDescriptor interface.
var _ TapscriptDescriptor = (*AnchorScriptTree)(nil)
// TaprootOutputKeyAnchor returns the segwit v1 (taproot) witness program that
// encodes the anchor output spending conditions: the passed key can be used
// for keyspend, with the OP_CSV 16 clause living within an internal tapscript
// leaf.
//
// Spend paths:
// - Key spend: <key_signature>
// - Script spend: OP_16 CSV <control_block>
func TaprootOutputKeyAnchor(key *btcec.PublicKey) (*btcec.PublicKey, error) {
anchorScriptTree, err := NewAnchorScriptTree(key)
if err != nil {
return nil, err
}
return anchorScriptTree.TaprootKey, nil
}
// TaprootAnchorSpend constructs a valid witness allowing a node to sweep their
// anchor output.
func TaprootAnchorSpend(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx) (wire.TxWitness, error) {
// For this spend type, we only need a single signature which'll be a
// keyspend using the anchor private key.
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// The witness stack in this case is pretty simple: we only need to
// specify the signature generated.
witnessStack := make(wire.TxWitness, 1)
witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType)
return witnessStack, nil
}
// TaprootAnchorSpendAny constructs a valid witness allowing anyone to sweep
// the anchor output after 16 blocks.
func TaprootAnchorSpendAny(anchorKey *btcec.PublicKey) (wire.TxWitness, error) {
anchorScriptTree, err := NewAnchorScriptTree(anchorKey)
if err != nil {
return nil, err
}
// For this spend, the only thing we need to do is create a valid
// control block. Other than that, there're no restrictions to how the
// output can be spent.
scriptTree := anchorScriptTree.TapscriptTree
sweepLeaf := anchorScriptTree.SweepLeaf
sweepIdx := scriptTree.LeafProofIndex[sweepLeaf.TapHash()]
sweepMerkleProof := scriptTree.LeafMerkleProofs[sweepIdx]
sweepControlBlock := sweepMerkleProof.ToControlBlock(anchorKey)
// The final witness stack will be:
//
// <sweep script> <control block>
witnessStack := make(wire.TxWitness, 2)
witnessStack[0] = sweepLeaf.Script
witnessStack[1], err = sweepControlBlock.ToBytes()
if err != nil {
return nil, err
}
return witnessStack, nil
}
// CommitSpendAnchor constructs a valid witness allowing a node to spend their
// anchor output on the commitment transaction using their funding key. This is
// used for the anchor channel type.
func CommitSpendAnchor(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx) (wire.TxWitness, error) {
if signDesc.KeyDesc.PubKey == nil {
return nil, fmt.Errorf("cannot generate witness with nil " +
"KeyDesc pubkey")
}
// Create a signature.
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// The witness here is just a signature and the redeem script.
witnessStack := make([][]byte, 2)
witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType))
witnessStack[1] = signDesc.WitnessScript
return witnessStack, nil
}
// CommitSpendAnchorAnyone constructs a witness allowing anyone to spend the
// anchor output after it has gotten 16 confirmations. Since no signing is
// required, only knowledge of the redeem script is necessary to spend it.
func CommitSpendAnchorAnyone(script []byte) (wire.TxWitness, error) {
// The witness here is just the redeem script.
witnessStack := make([][]byte, 2)
witnessStack[0] = nil
witnessStack[1] = script
return witnessStack, nil
}
// SingleTweakBytes computes set of bytes we call the single tweak. The purpose
// of the single tweak is to randomize all regular delay and payment base
// points. To do this, we generate a hash that binds the commitment point to
// the pay/delay base point. The end result is that the basePoint is
// tweaked as follows:
//
// - key = basePoint + sha256(commitPoint || basePoint)*G
func SingleTweakBytes(commitPoint, basePoint *btcec.PublicKey) []byte {
h := sha256.New()
h.Write(commitPoint.SerializeCompressed())
h.Write(basePoint.SerializeCompressed())
return h.Sum(nil)
}
// TweakPubKey tweaks a public base point given a per commitment point. The per
// commitment point is a unique point on our target curve for each commitment
// transaction. When tweaking a local base point for use in a remote commitment
// transaction, the remote party's current per commitment point is to be used.
// The opposite applies for when tweaking remote keys. Precisely, the following
// operation is used to "tweak" public keys:
//
// tweakPub := basePoint + sha256(commitPoint || basePoint) * G
// := G*k + sha256(commitPoint || basePoint)*G
// := G*(k + sha256(commitPoint || basePoint))
//
// Therefore, if a party possess the value k, the private key of the base
// point, then they are able to derive the proper private key for the
// revokeKey by computing:
//
// revokePriv := k + sha256(commitPoint || basePoint) mod N
//
// Where N is the order of the sub-group.
//
// The rationale for tweaking all public keys used within the commitment
// contracts is to ensure that all keys are properly delinearized to avoid any
// funny business when jointly collaborating to compute public and private
// keys. Additionally, the use of the per commitment point ensures that each
// commitment state houses a unique set of keys which is useful when creating
// blinded channel outsourcing protocols.
//
// TODO(roasbeef): should be using double-scalar mult here
func TweakPubKey(basePoint, commitPoint *btcec.PublicKey) *btcec.PublicKey {
tweakBytes := SingleTweakBytes(commitPoint, basePoint)
return TweakPubKeyWithTweak(basePoint, tweakBytes)
}
// TweakPubKeyWithTweak is the exact same as the TweakPubKey function, however
// it accepts the raw tweak bytes directly rather than the commitment point.
func TweakPubKeyWithTweak(pubKey *btcec.PublicKey,
tweakBytes []byte) *btcec.PublicKey {
var (
pubKeyJacobian btcec.JacobianPoint
tweakJacobian btcec.JacobianPoint
resultJacobian btcec.JacobianPoint
)
tweakKey, _ := btcec.PrivKeyFromBytes(tweakBytes)
btcec.ScalarBaseMultNonConst(&tweakKey.Key, &tweakJacobian)
pubKey.AsJacobian(&pubKeyJacobian)
btcec.AddNonConst(&pubKeyJacobian, &tweakJacobian, &resultJacobian)
resultJacobian.ToAffine()
return btcec.NewPublicKey(&resultJacobian.X, &resultJacobian.Y)
}
// TweakPrivKey tweaks the private key of a public base point given a per
// commitment point. The per commitment secret is the revealed revocation
// secret for the commitment state in question. This private key will only need
// to be generated in the case that a channel counter party broadcasts a
// revoked state. Precisely, the following operation is used to derive a
// tweaked private key:
//
// - tweakPriv := basePriv + sha256(commitment || basePub) mod N
//
// Where N is the order of the sub-group.
func TweakPrivKey(basePriv *btcec.PrivateKey,
commitTweak []byte) *btcec.PrivateKey {
// tweakInt := sha256(commitPoint || basePub)
tweakScalar := new(btcec.ModNScalar)
tweakScalar.SetByteSlice(commitTweak)
tweakScalar.Add(&basePriv.Key)
return &btcec.PrivateKey{Key: *tweakScalar}
}
// DeriveRevocationPubkey derives the revocation public key given the
// counterparty's commitment key, and revocation preimage derived via a
// pseudo-random-function. In the event that we (for some reason) broadcast a
// revoked commitment transaction, then if the other party knows the revocation
// preimage, then they'll be able to derive the corresponding private key to
// this private key by exploiting the homomorphism in the elliptic curve group:
// - https://en.wikipedia.org/wiki/Group_homomorphism#Homomorphisms_of_abelian_groups
//
// The derivation is performed as follows:
//
// revokeKey := revokeBase * sha256(revocationBase || commitPoint) +
// commitPoint * sha256(commitPoint || revocationBase)
//
// := G*(revokeBasePriv * sha256(revocationBase || commitPoint)) +
// G*(commitSecret * sha256(commitPoint || revocationBase))
//
// := G*(revokeBasePriv * sha256(revocationBase || commitPoint) +
// commitSecret * sha256(commitPoint || revocationBase))
//
// Therefore, once we divulge the revocation secret, the remote peer is able to
// compute the proper private key for the revokeKey by computing:
//
// revokePriv := (revokeBasePriv * sha256(revocationBase || commitPoint)) +
// (commitSecret * sha256(commitPoint || revocationBase)) mod N
//
// Where N is the order of the sub-group.
func DeriveRevocationPubkey(revokeBase,
commitPoint *btcec.PublicKey) *btcec.PublicKey {
// R = revokeBase * sha256(revocationBase || commitPoint)
revokeTweakBytes := SingleTweakBytes(revokeBase, commitPoint)
revokeTweakScalar := new(btcec.ModNScalar)
revokeTweakScalar.SetByteSlice(revokeTweakBytes)
var (
revokeBaseJacobian btcec.JacobianPoint
rJacobian btcec.JacobianPoint
)
revokeBase.AsJacobian(&revokeBaseJacobian)
btcec.ScalarMultNonConst(
revokeTweakScalar, &revokeBaseJacobian, &rJacobian,
)
// C = commitPoint * sha256(commitPoint || revocationBase)
commitTweakBytes := SingleTweakBytes(commitPoint, revokeBase)
commitTweakScalar := new(btcec.ModNScalar)
commitTweakScalar.SetByteSlice(commitTweakBytes)
var (
commitPointJacobian btcec.JacobianPoint
cJacobian btcec.JacobianPoint
)
commitPoint.AsJacobian(&commitPointJacobian)
btcec.ScalarMultNonConst(
commitTweakScalar, &commitPointJacobian, &cJacobian,
)
// Now that we have the revocation point, we add this to their commitment
// public key in order to obtain the revocation public key.
//
// P = R + C
var resultJacobian btcec.JacobianPoint
btcec.AddNonConst(&rJacobian, &cJacobian, &resultJacobian)
resultJacobian.ToAffine()
return btcec.NewPublicKey(&resultJacobian.X, &resultJacobian.Y)
}
// DeriveRevocationPrivKey derives the revocation private key given a node's
// commitment private key, and the preimage to a previously seen revocation
// hash. Using this derived private key, a node is able to claim the output
// within the commitment transaction of a node in the case that they broadcast
// a previously revoked commitment transaction.
//
// The private key is derived as follows:
//
// revokePriv := (revokeBasePriv * sha256(revocationBase || commitPoint)) +
// (commitSecret * sha256(commitPoint || revocationBase)) mod N
//
// Where N is the order of the sub-group.
func DeriveRevocationPrivKey(revokeBasePriv *btcec.PrivateKey,
commitSecret *btcec.PrivateKey) *btcec.PrivateKey {
// r = sha256(revokeBasePub || commitPoint)
revokeTweakBytes := SingleTweakBytes(
revokeBasePriv.PubKey(), commitSecret.PubKey(),
)
revokeTweakScalar := new(btcec.ModNScalar)
revokeTweakScalar.SetByteSlice(revokeTweakBytes)
// c = sha256(commitPoint || revokeBasePub)
commitTweakBytes := SingleTweakBytes(
commitSecret.PubKey(), revokeBasePriv.PubKey(),
)
commitTweakScalar := new(btcec.ModNScalar)
commitTweakScalar.SetByteSlice(commitTweakBytes)
// Finally to derive the revocation secret key we'll perform the
// following operation:
//
// k = (revocationPriv * r) + (commitSecret * c) mod N
//
// This works since:
// P = (G*a)*b + (G*c)*d
// P = G*(a*b) + G*(c*d)
// P = G*(a*b + c*d)
revokeHalfPriv := revokeTweakScalar.Mul(&revokeBasePriv.Key)
commitHalfPriv := commitTweakScalar.Mul(&commitSecret.Key)
revocationPriv := revokeHalfPriv.Add(commitHalfPriv)
return &btcec.PrivateKey{Key: *revocationPriv}
}
// ComputeCommitmentPoint generates a commitment point given a commitment
// secret. The commitment point for each state is used to randomize each key in
// the key-ring and also to used as a tweak to derive new public+private keys
// for the state.
func ComputeCommitmentPoint(commitSecret []byte) *btcec.PublicKey {
_, pubKey := btcec.PrivKeyFromBytes(commitSecret)
return pubKey
}
// ScriptIsOpReturn returns true if the passed script is an OP_RETURN script.
//
// Lifted from the txscript package:
// https://github.com/btcsuite/btcd/blob/cc26860b40265e1332cca8748c5dbaf3c81cc094/txscript/standard.go#L493-L526.
//
//nolint:ll
func ScriptIsOpReturn(script []byte) bool {
// A null script is of the form:
// OP_RETURN <optional data>
//
// Thus, it can either be a single OP_RETURN or an OP_RETURN followed by
// a data push up to MaxDataCarrierSize bytes.
// The script can't possibly be a null data script if it doesn't start
// with OP_RETURN. Fail fast to avoid more work below.
if len(script) < 1 || script[0] != txscript.OP_RETURN {
return false
}
// Single OP_RETURN.
if len(script) == 1 {
return true
}
// OP_RETURN followed by data push up to MaxDataCarrierSize bytes.
tokenizer := txscript.MakeScriptTokenizer(0, script[1:])
return tokenizer.Next() && tokenizer.Done() &&
(txscript.IsSmallInt(tokenizer.Opcode()) ||
tokenizer.Opcode() <= txscript.OP_PUSHDATA4) &&
len(tokenizer.Data()) <= txscript.MaxDataCarrierSize
}
package input
import (
"encoding/binary"
"errors"
"fmt"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/keychain"
)
var (
// ErrTweakOverdose signals a SignDescriptor is invalid because both of its
// SingleTweak and DoubleTweak are non-nil.
ErrTweakOverdose = errors.New("sign descriptor should only have one tweak")
)
// SignDescriptor houses the necessary information required to successfully
// sign a given segwit output. This struct is used by the Signer interface in
// order to gain access to critical data needed to generate a valid signature.
type SignDescriptor struct {
// KeyDesc is a descriptor that precisely describes *which* key to use
// for signing. This may provide the raw public key directly, or
// require the Signer to re-derive the key according to the populated
// derivation path.
KeyDesc keychain.KeyDescriptor
// SingleTweak is a scalar value that will be added to the private key
// corresponding to the above public key to obtain the private key to
// be used to sign this input. This value is typically derived via the
// following computation:
//
// * derivedKey = privkey + sha256(perCommitmentPoint || pubKey) mod N
//
// NOTE: If this value is nil, then the input can be signed using only
// the above public key. Either a SingleTweak should be set or a
// DoubleTweak, not both.
SingleTweak []byte
// DoubleTweak is a private key that will be used in combination with
// its corresponding private key to derive the private key that is to
// be used to sign the target input. Within the Lightning protocol,
// this value is typically the commitment secret from a previously
// revoked commitment transaction. This value is in combination with
// two hash values, and the original private key to derive the private
// key to be used when signing.
//
// * k = (privKey*sha256(pubKey || tweakPub) +
// tweakPriv*sha256(tweakPub || pubKey)) mod N
//
// NOTE: If this value is nil, then the input can be signed using only
// the above public key. Either a SingleTweak should be set or a
// DoubleTweak, not both.
DoubleTweak *btcec.PrivateKey
// TapTweak is a 32-byte value that will be used to derive a taproot
// output public key (or the corresponding private key) from an
// internal key and this tweak. The transformation applied is:
// * outputKey = internalKey +
// tagged_hash("tapTweak", internalKey || tapTweak)
//
// When attempting to sign an output derived via BIP 86, then this
// field should be an empty byte array.
//
// When attempting to sign for the key spend path of an output key that
// commits to an actual script tree, the script root should be used.
TapTweak []byte
// WitnessScript is the full script required to properly redeem the
// output. This field should be set to the full script if a p2wsh
// output is being signed. For p2wkh it should be set to the hashed
// script (PkScript).
WitnessScript []byte
// SignMethod specifies how the input should be signed. Depending on the
// selected method, either the TapTweak, WitnessScript or both need to
// be specified.
SignMethod SignMethod
// Output is the target output which should be signed. The PkScript and
// Value fields within the output should be properly populated,
// otherwise an invalid signature may be generated.
Output *wire.TxOut
// HashType is the target sighash type that should be used when
// generating the final sighash, and signature.
HashType txscript.SigHashType
// SigHashes is the pre-computed sighash midstate to be used when
// generating the final sighash for signing.
SigHashes *txscript.TxSigHashes
// PrevOutputFetcher is an interface that can return the output
// information on all UTXOs that are being spent in this transaction.
// This MUST be set when spending Taproot outputs.
PrevOutputFetcher txscript.PrevOutputFetcher
// ControlBlock is a fully serialized control block that contains the
// merkle proof necessary to spend a taproot output. This may
// optionally be set if the SignMethod is
// input.TaprootScriptSpendSignMethod. In which case, this should be an
// inclusion proof for the WitnessScript.
ControlBlock []byte
// InputIndex is the target input within the transaction that should be
// signed.
InputIndex int
}
// SignMethod defines the different ways a signer can sign, given a specific
// input.
type SignMethod uint8
const (
// WitnessV0SignMethod denotes that a SegWit v0 (p2wkh, np2wkh, p2wsh)
// input script should be signed.
WitnessV0SignMethod SignMethod = 0
// TaprootKeySpendBIP0086SignMethod denotes that a SegWit v1 (p2tr)
// input should be signed by using the BIP0086 method (commit to
// internal key only).
TaprootKeySpendBIP0086SignMethod SignMethod = 1
// TaprootKeySpendSignMethod denotes that a SegWit v1 (p2tr)
// input should be signed by using a given taproot hash to commit to in
// addition to the internal key.
TaprootKeySpendSignMethod SignMethod = 2
// TaprootScriptSpendSignMethod denotes that a SegWit v1 (p2tr) input
// should be spent using the script path and that a specific leaf script
// should be signed for.
TaprootScriptSpendSignMethod SignMethod = 3
)
// String returns a human-readable representation of the signing method.
func (s SignMethod) String() string {
switch s {
case WitnessV0SignMethod:
return "witness_v0"
case TaprootKeySpendBIP0086SignMethod:
return "taproot_key_spend_bip86"
case TaprootKeySpendSignMethod:
return "taproot_key_spend"
case TaprootScriptSpendSignMethod:
return "taproot_script_spend"
default:
return fmt.Sprintf("unknown<%d>", s)
}
}
// PkScriptCompatible returns true if the given public key script is compatible
// with the sign method.
func (s SignMethod) PkScriptCompatible(pkScript []byte) bool {
switch s {
// SegWit v0 can be p2wkh, np2wkh, p2wsh.
case WitnessV0SignMethod:
return txscript.IsPayToWitnessPubKeyHash(pkScript) ||
txscript.IsPayToWitnessScriptHash(pkScript) ||
txscript.IsPayToScriptHash(pkScript)
case TaprootKeySpendBIP0086SignMethod, TaprootKeySpendSignMethod,
TaprootScriptSpendSignMethod:
return txscript.IsPayToTaproot(pkScript)
default:
return false
}
}
// WriteSignDescriptor serializes a SignDescriptor struct into the passed
// io.Writer stream.
//
// NOTE: We assume the SigHashes and InputIndex fields haven't been assigned
// yet, since that is usually done just before broadcast by the witness
// generator.
func WriteSignDescriptor(w io.Writer, sd *SignDescriptor) error {
err := binary.Write(w, binary.BigEndian, sd.KeyDesc.Family)
if err != nil {
return err
}
err = binary.Write(w, binary.BigEndian, sd.KeyDesc.Index)
if err != nil {
return err
}
err = binary.Write(w, binary.BigEndian, sd.KeyDesc.PubKey != nil)
if err != nil {
return err
}
if sd.KeyDesc.PubKey != nil {
serializedPubKey := sd.KeyDesc.PubKey.SerializeCompressed()
if err := wire.WriteVarBytes(w, 0, serializedPubKey); err != nil {
return err
}
}
if err := wire.WriteVarBytes(w, 0, sd.SingleTweak); err != nil {
return err
}
var doubleTweakBytes []byte
if sd.DoubleTweak != nil {
doubleTweakBytes = sd.DoubleTweak.Serialize()
}
if err := wire.WriteVarBytes(w, 0, doubleTweakBytes); err != nil {
return err
}
if err := wire.WriteVarBytes(w, 0, sd.WitnessScript); err != nil {
return err
}
if err := writeTxOut(w, sd.Output); err != nil {
return err
}
var scratch [4]byte
binary.BigEndian.PutUint32(scratch[:], uint32(sd.HashType))
if _, err := w.Write(scratch[:]); err != nil {
return err
}
return nil
}
// ReadSignDescriptor deserializes a SignDescriptor struct from the passed
// io.Reader stream.
func ReadSignDescriptor(r io.Reader, sd *SignDescriptor) error {
err := binary.Read(r, binary.BigEndian, &sd.KeyDesc.Family)
if err != nil {
return err
}
err = binary.Read(r, binary.BigEndian, &sd.KeyDesc.Index)
if err != nil {
return err
}
var hasKey bool
err = binary.Read(r, binary.BigEndian, &hasKey)
if err != nil {
return err
}
if hasKey {
pubKeyBytes, err := wire.ReadVarBytes(r, 0, 34, "pubkey")
if err != nil {
return err
}
sd.KeyDesc.PubKey, err = btcec.ParsePubKey(pubKeyBytes)
if err != nil {
return err
}
}
singleTweak, err := wire.ReadVarBytes(r, 0, 32, "singleTweak")
if err != nil {
return err
}
// Serializing a SignDescriptor with a nil-valued SingleTweak results
// in deserializing a zero-length slice. Since a nil-valued SingleTweak
// has special meaning and a zero-length slice for a SingleTweak is
// invalid, we can use the zero-length slice as the flag for a
// nil-valued SingleTweak.
if len(singleTweak) == 0 {
sd.SingleTweak = nil
} else {
sd.SingleTweak = singleTweak
}
doubleTweakBytes, err := wire.ReadVarBytes(r, 0, 32, "doubleTweak")
if err != nil {
return err
}
// Serializing a SignDescriptor with a nil-valued DoubleTweak results
// in deserializing a zero-length slice. Since a nil-valued DoubleTweak
// has special meaning and a zero-length slice for a DoubleTweak is
// invalid, we can use the zero-length slice as the flag for a
// nil-valued DoubleTweak.
if len(doubleTweakBytes) == 0 {
sd.DoubleTweak = nil
} else {
sd.DoubleTweak, _ = btcec.PrivKeyFromBytes(doubleTweakBytes)
}
// Only one tweak should ever be set, fail if both are present.
if sd.SingleTweak != nil && sd.DoubleTweak != nil {
return ErrTweakOverdose
}
witnessScript, err := wire.ReadVarBytes(r, 0, 500, "witnessScript")
if err != nil {
return err
}
sd.WitnessScript = witnessScript
txOut := &wire.TxOut{}
if err := readTxOut(r, txOut); err != nil {
return err
}
sd.Output = txOut
var hashType [4]byte
if _, err := io.ReadFull(r, hashType[:]); err != nil {
return err
}
sd.HashType = txscript.SigHashType(binary.BigEndian.Uint32(hashType[:]))
return nil
}
package input
import (
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/lightningnetwork/lnd/lntypes"
)
const (
// witnessScaleFactor determines the level of "discount" witness data
// receives compared to "base" data. A scale factor of 4, denotes that
// witness data is 1/4 as cheap as regular non-witness data. Value copied
// here for convenience.
witnessScaleFactor = blockchain.WitnessScaleFactor
// The weight(weight), which is different from the !size! (see BIP-141),
// is calculated as:
// Weight = 4 * BaseSize + WitnessSize (weight).
// BaseSize - size of the transaction without witness data (bytes).
// WitnessSize - witness size (bytes).
// Weight - the metric for determining the weight of the transaction.
// P2WPKHSize 22 bytes
// - OP_0: 1 byte
// - OP_DATA: 1 byte (PublicKeyHASH160 length)
// - PublicKeyHASH160: 20 bytes
P2WPKHSize = 1 + 1 + 20
// NestedP2WPKHSize 23 bytes
// - OP_DATA: 1 byte (P2WPKHSize)
// - P2WPKHWitnessProgram: 22 bytes
NestedP2WPKHSize = 1 + P2WPKHSize
// P2WSHSize 34 bytes
// - OP_0: 1 byte
// - OP_DATA: 1 byte (WitnessScriptSHA256 length)
// - WitnessScriptSHA256: 32 bytes
P2WSHSize = 1 + 1 + 32
// NestedP2WSHSize 35 bytes
// - OP_DATA: 1 byte (P2WSHSize)
// - P2WSHWitnessProgram: 34 bytes
NestedP2WSHSize = 1 + P2WSHSize
// UnknownWitnessSize 42 bytes
// - OP_x: 1 byte
// - OP_DATA: 1 byte (max-size length)
// - max-size: 40 bytes
UnknownWitnessSize = 1 + 1 + 40
// BaseOutputSize 9 bytes
// - value: 8 bytes
// - var_int: 1 byte (pkscript_length)
BaseOutputSize = 8 + 1
// P2PKHSize 25 bytes.
P2PKHSize = 25
// P2PKHOutputSize 34 bytes
// - value: 8 bytes
// - var_int: 1 byte (pkscript_length)
// - pkscript (p2pkh): 25 bytes
P2PKHOutputSize = BaseOutputSize + P2PKHSize
// P2WKHOutputSize 31 bytes
// - value: 8 bytes
// - var_int: 1 byte (pkscript_length)
// - pkscript (p2wpkh): 22 bytes
P2WKHOutputSize = BaseOutputSize + P2WPKHSize
// P2WSHOutputSize 43 bytes
// - value: 8 bytes
// - var_int: 1 byte (pkscript_length)
// - pkscript (p2wsh): 34 bytes
P2WSHOutputSize = BaseOutputSize + P2WSHSize
// P2SHSize 23 bytes.
P2SHSize = 23
// P2SHOutputSize 32 bytes
// - value: 8 bytes
// - var_int: 1 byte (pkscript_length)
// - pkscript (p2sh): 23 bytes
P2SHOutputSize = BaseOutputSize + P2SHSize
// P2TRSize 34 bytes
// - OP_0: 1 byte
// - OP_DATA: 1 byte (x-only public key length)
// - x-only public key length: 32 bytes
P2TRSize = 34
// P2TROutputSize 43 bytes
// - value: 8 bytes
// - var_int: 1 byte (pkscript_length)
// - pkscript (p2tr): 34 bytes
P2TROutputSize = BaseOutputSize + P2TRSize
// P2PKHScriptSigSize 108 bytes
// - OP_DATA: 1 byte (signature length)
// - signature
// - OP_DATA: 1 byte (pubkey length)
// - pubkey
P2PKHScriptSigSize = 1 + 73 + 1 + 33
// P2WKHWitnessSize 109 bytes
// - number_of_witness_elements: 1 byte
// - signature_length: 1 byte
// - signature
// - pubkey_length: 1 byte
// - pubkey
P2WKHWitnessSize = 1 + 1 + 73 + 1 + 33
// MultiSigSize 71 bytes
// - OP_2: 1 byte
// - OP_DATA: 1 byte (pubKeyAlice length)
// - pubKeyAlice: 33 bytes
// - OP_DATA: 1 byte (pubKeyBob length)
// - pubKeyBob: 33 bytes
// - OP_2: 1 byte
// - OP_CHECKMULTISIG: 1 byte
MultiSigSize = 1 + 1 + 33 + 1 + 33 + 1 + 1
// MultiSigWitnessSize 222 bytes
// - NumberOfWitnessElements: 1 byte
// - NilLength: 1 byte
// - sigAliceLength: 1 byte
// - sigAlice: 73 bytes
// - sigBobLength: 1 byte
// - sigBob: 73 bytes
// - WitnessScriptLength: 1 byte
// - WitnessScript (MultiSig)
MultiSigWitnessSize = 1 + 1 + 1 + 73 + 1 + 73 + 1 + MultiSigSize
// InputSize 41 bytes
// - PreviousOutPoint:
// - Hash: 32 bytes
// - Index: 4 bytes
// - OP_DATA: 1 byte (ScriptSigLength)
// - ScriptSig: 0 bytes
// - Witness <---- we use "Witness" instead of "ScriptSig" for
// transaction validation, but "Witness" is stored
// separately and weight for it size is smaller. So
// we separate the calculation of ordinary data
// from witness data.
// - Sequence: 4 bytes
InputSize = 32 + 4 + 1 + 4
// FundingInputSize 41 bytes
// FundingInputSize represents the size of an input to a funding
// transaction, and is equivalent to the size of a standard segwit input
// as calculated above.
FundingInputSize = InputSize
// CommitmentDelayOutput 43 bytes
// - Value: 8 bytes
// - VarInt: 1 byte (PkScript length)
// - PkScript (P2WSH)
CommitmentDelayOutput = 8 + 1 + P2WSHSize
// TaprootCommitmentOutput 43 bytes
// - Value: 8 bytes
// - VarInt: 1 byte (PkScript length)
// - PkScript (P2TR)
TaprootCommitmentOutput = 8 + 1 + P2TRSize
// CommitmentKeyHashOutput 31 bytes
// - Value: 8 bytes
// - VarInt: 1 byte (PkScript length)
// - PkScript (P2WPKH)
CommitmentKeyHashOutput = 8 + 1 + P2WPKHSize
// CommitmentAnchorOutput 43 bytes
// - Value: 8 bytes
// - VarInt: 1 byte (PkScript length)
// - PkScript (P2WSH)
CommitmentAnchorOutput = 8 + 1 + P2WSHSize
// TaprootCommitmentAnchorOutput 43 bytes
// - Value: 8 bytes
// - VarInt: 1 byte (PkScript length)
// - PkScript (P2TR)
TaprootCommitmentAnchorOutput = 8 + 1 + P2TRSize
// HTLCSize 43 bytes
// - Value: 8 bytes
// - VarInt: 1 byte (PkScript length)
// - PkScript (PW2SH)
HTLCSize = 8 + 1 + P2WSHSize
// WitnessHeaderSize 2 bytes
// - Flag: 1 byte
// - Marker: 1 byte
WitnessHeaderSize = 1 + 1
// BaseTxSize 8 bytes
// - Version: 4 bytes
// - LockTime: 4 bytes
BaseTxSize = 4 + 4
// BaseCommitmentTxSize 125 + 43 * num-htlc-outputs bytes
// - Version: 4 bytes
// - WitnessHeader <---- part of the witness data
// - CountTxIn: 1 byte
// - TxIn: 41 bytes
// FundingInput
// - CountTxOut: 1 byte
// - TxOut: 74 + 43 * num-htlc-outputs bytes
// OutputPayingToThem,
// OutputPayingToUs,
// ....HTLCOutputs...
// - LockTime: 4 bytes
BaseCommitmentTxSize = 4 + 1 + FundingInputSize + 1 +
CommitmentDelayOutput + CommitmentKeyHashOutput + 4
// BaseCommitmentTxWeight 500 weight
BaseCommitmentTxWeight = witnessScaleFactor * BaseCommitmentTxSize
// WitnessCommitmentTxWeight 224 weight
WitnessCommitmentTxWeight = WitnessHeaderSize + MultiSigWitnessSize
// BaseAnchorCommitmentTxSize 225 + 43 * num-htlc-outputs bytes
// - Version: 4 bytes
// - WitnessHeader <---- part of the witness data
// - CountTxIn: 1 byte
// - TxIn: 41 bytes
// FundingInput
// - CountTxOut: 3 byte
// - TxOut: 4*43 + 43 * num-htlc-outputs bytes
// OutputPayingToThem,
// OutputPayingToUs,
// AnchorPayingToThem,
// AnchorPayingToUs,
// ....HTLCOutputs...
// - LockTime: 4 bytes
BaseAnchorCommitmentTxSize = 4 + 1 + FundingInputSize + 3 +
2*CommitmentDelayOutput + 2*CommitmentAnchorOutput + 4
// BaseAnchorCommitmentTxWeight 900 weight.
BaseAnchorCommitmentTxWeight = witnessScaleFactor * BaseAnchorCommitmentTxSize
// BaseTaprootCommitmentTxWeight 225 + 43 * num-htlc-outputs bytes
// - Version: 4 bytes
// - WitnessHeader <---- part of the witness data
// - CountTxIn: 1 byte
// - TxIn: 41 bytes
// FundingInput
// - CountTxOut: 3 byte
// - TxOut: 172 + 43 * num-htlc-outputs bytes
// OutputPayingToThem,
// OutputPayingToUs,
// ....HTLCOutputs...
// - LockTime: 4 bytes
BaseTaprootCommitmentTxWeight = (4 + 1 + FundingInputSize + 3 +
2*TaprootCommitmentOutput + 2*TaprootCommitmentAnchorOutput +
4) * witnessScaleFactor
// CommitWeight 724 weight.
CommitWeight = BaseCommitmentTxWeight + WitnessCommitmentTxWeight
// AnchorCommitWeight 1124 weight.
AnchorCommitWeight = BaseAnchorCommitmentTxWeight + WitnessCommitmentTxWeight
// TaprootCommitWeight 968 weight.
TaprootCommitWeight = (BaseTaprootCommitmentTxWeight +
WitnessHeaderSize + TaprootKeyPathWitnessSize)
// HTLCWeight 172 weight.
HTLCWeight = witnessScaleFactor * HTLCSize
// HtlcTimeoutWeight 663 weight
// HtlcTimeoutWeight is the weight of the HTLC timeout transaction
// which will transition an outgoing HTLC to the delay-and-claim state.
HtlcTimeoutWeight = 663
// TaprootHtlcTimeoutWeight is the total weight of the taproot HTLC
// timeout transaction.
TaprootHtlcTimeoutWeight = 645
// HtlcSuccessWeight 703 weight
// HtlcSuccessWeight is the weight of the HTLC success transaction
// which will transition an incoming HTLC to the delay-and-claim state.
HtlcSuccessWeight = 703
// TaprootHtlcSuccessWeight is the total weight of the taproot HTLC
// success transaction.
TaprootHtlcSuccessWeight = 705
// HtlcConfirmedScriptOverhead 3 bytes
// HtlcConfirmedScriptOverhead is the extra length of an HTLC script
// that requires confirmation before it can be spent. These extra bytes
// is a result of the extra CSV check.
HtlcConfirmedScriptOverhead = 3
// HtlcTimeoutWeightConfirmed 666 weight
// HtlcTimeoutWeightConfirmed is the weight of the HTLC timeout
// transaction which will transition an outgoing HTLC to the
// delay-and-claim state, for the confirmed HTLC outputs. It is 3 bytes
// larger because of the additional CSV check in the input script.
HtlcTimeoutWeightConfirmed = HtlcTimeoutWeight + HtlcConfirmedScriptOverhead
// HtlcSuccessWeightConfirmed 706 weight
// HtlcSuccessWeightConfirmed is the weight of the HTLC success
// transaction which will transition an incoming HTLC to the
// delay-and-claim state, for the confirmed HTLC outputs. It is 3 bytes
// larger because of the cdditional CSV check in the input script.
HtlcSuccessWeightConfirmed = HtlcSuccessWeight + HtlcConfirmedScriptOverhead
// MaxHTLCNumber 966
// MaxHTLCNumber is the maximum number HTLCs which can be included in a
// commitment transaction. This limit was chosen such that, in the case
// of a contract breach, the punishment transaction is able to sweep
// all the HTLC's yet still remain below the widely used standard
// weight limits.
MaxHTLCNumber = 966
// ToLocalScriptSize 79 bytes
// - OP_IF: 1 byte
// - OP_DATA: 1 byte
// - revoke_key: 33 bytes
// - OP_ELSE: 1 byte
// - OP_DATA: 1 byte
// - csv_delay: 4 bytes
// - OP_CHECKSEQUENCEVERIFY: 1 byte
// - OP_DROP: 1 byte
// - OP_DATA: 1 byte
// - delay_key: 33 bytes
// - OP_ENDIF: 1 byte
// - OP_CHECKSIG: 1 byte
ToLocalScriptSize = 1 + 1 + 33 + 1 + 1 + 4 + 1 + 1 + 1 + 33 + 1 + 1
// LeaseWitnessScriptSizeOverhead represents the size overhead in bytes
// of the witness scripts used within script enforced lease commitments.
// This overhead results from the additional CLTV clause required to
// spend.
//
// - OP_DATA: 1 byte
// - lease_expiry: 4 bytes
// - OP_CHECKLOCKTIMEVERIFY: 1 byte
// - OP_DROP: 1 byte
LeaseWitnessScriptSizeOverhead = 1 + 4 + 1 + 1
// ToLocalTimeoutWitnessSize 156 bytes
// - number_of_witness_elements: 1 byte
// - local_delay_sig_length: 1 byte
// - local_delay_sig: 73 bytes
// - zero_length: 1 byte
// - witness_script_length: 1 byte
// - witness_script (to_local_script)
ToLocalTimeoutWitnessSize = 1 + 1 + 73 + 1 + 1 + ToLocalScriptSize
// ToLocalPenaltyWitnessSize 157 bytes
// - number_of_witness_elements: 1 byte
// - revocation_sig_length: 1 byte
// - revocation_sig: 73 bytes
// - OP_TRUE_length: 1 byte
// - OP_TRUE: 1 byte
// - witness_script_length: 1 byte
// - witness_script (to_local_script)
ToLocalPenaltyWitnessSize = 1 + 1 + 73 + 1 + 1 + 1 + ToLocalScriptSize
// ToRemoteConfirmedScriptSize 37 bytes
// - OP_DATA: 1 byte
// - to_remote_key: 33 bytes
// - OP_CHECKSIGVERIFY: 1 byte
// - OP_1: 1 byte
// - OP_CHECKSEQUENCEVERIFY: 1 byte
ToRemoteConfirmedScriptSize = 1 + 33 + 1 + 1 + 1
// ToRemoteConfirmedWitnessSize 113 bytes
// - number_of_witness_elements: 1 byte
// - sig_length: 1 byte
// - sig: 73 bytes
// - witness_script_length: 1 byte
// - witness_script (to_remote_delayed_script)
ToRemoteConfirmedWitnessSize = 1 + 1 + 73 + 1 + ToRemoteConfirmedScriptSize
// AcceptedHtlcScriptSize 140 bytes
// - OP_DUP: 1 byte
// - OP_HASH160: 1 byte
// - OP_DATA: 1 byte (RIPEMD160(SHA256(revocationkey)) length)
// - RIPEMD160(SHA256(revocationkey)): 20 bytes
// - OP_EQUAL: 1 byte
// - OP_IF: 1 byte
// - OP_CHECKSIG: 1 byte
// - OP_ELSE: 1 byte
// - OP_DATA: 1 byte (remotekey length)
// - remotekey: 33 bytes
// - OP_SWAP: 1 byte
// - OP_SIZE: 1 byte
// - OP_DATA: 1 byte (32 length)
// - 32: 1 byte
// - OP_EQUAL: 1 byte
// - OP_IF: 1 byte
// - OP_HASH160: 1 byte
// - OP_DATA: 1 byte (RIPEMD160(payment_hash) length)
// - RIPEMD160(payment_hash): 20 bytes
// - OP_EQUALVERIFY: 1 byte
// - 2: 1 byte
// - OP_SWAP: 1 byte
// - OP_DATA: 1 byte (localkey length)
// - localkey: 33 bytes
// - 2: 1 byte
// - OP_CHECKMULTISIG: 1 byte
// - OP_ELSE: 1 byte
// - OP_DROP: 1 byte
// - OP_DATA: 1 byte (cltv_expiry length)
// - cltv_expiry: 4 bytes
// - OP_CHECKLOCKTIMEVERIFY: 1 byte
// - OP_DROP: 1 byte
// - OP_CHECKSIG: 1 byte
// - OP_ENDIF: 1 byte
// - OP_1: 1 byte // These 3 extra bytes are only
// - OP_CSV: 1 byte // present for the confirmed
// - OP_DROP: 1 byte // HTLC script types.
// - OP_ENDIF: 1 byte
AcceptedHtlcScriptSize = 3*1 + 20 + 5*1 + 33 + 8*1 + 20 + 4*1 +
33 + 5*1 + 4 + 5*1
// AcceptedHtlcScriptSizeConfirmed 143 bytes.
AcceptedHtlcScriptSizeConfirmed = AcceptedHtlcScriptSize +
HtlcConfirmedScriptOverhead
// AcceptedHtlcTimeoutWitnessSize 217 bytes
// - number_of_witness_elements: 1 byte
// - sender_sig_length: 1 byte
// - sender_sig: 73 bytes
// - nil_length: 1 byte
// - witness_script_length: 1 byte
// - witness_script: (accepted_htlc_script)
AcceptedHtlcTimeoutWitnessSize = 1 + 1 + 73 + 1 + 1 + AcceptedHtlcScriptSize
// AcceptedHtlcTimeoutWitnessSizeConfirmed 220 bytes.
AcceptedHtlcTimeoutWitnessSizeConfirmed = 1 + 1 + 73 + 1 + 1 +
AcceptedHtlcScriptSizeConfirmed
// AcceptedHtlcPenaltyWitnessSize 250 bytes
// - number_of_witness_elements: 1 byte
// - revocation_sig_length: 1 byte
// - revocation_sig: 73 bytes
// - revocation_key_length: 1 byte
// - revocation_key: 33 bytes
// - witness_script_length: 1 byte
// - witness_script (accepted_htlc_script)
AcceptedHtlcPenaltyWitnessSize = 1 + 1 + 73 + 1 + 33 + 1 + AcceptedHtlcScriptSize
// AcceptedHtlcPenaltyWitnessSizeConfirmed 253 bytes.
AcceptedHtlcPenaltyWitnessSizeConfirmed = 1 + 1 + 73 + 1 + 33 + 1 +
AcceptedHtlcScriptSizeConfirmed
// AcceptedHtlcSuccessWitnessSize 324 bytes
// - number_of_witness_elements: 1 byte
// - nil_length: 1 byte
// - sig_alice_length: 1 byte
// - sig_alice: 73 bytes
// - sig_bob_length: 1 byte
// - sig_bob: 73 bytes
// - preimage_length: 1 byte
// - preimage: 32 bytes
// - witness_script_length: 1 byte
// - witness_script (accepted_htlc_script)
//
// Input to second level success tx, spending non-delayed HTLC output.
AcceptedHtlcSuccessWitnessSize = 1 + 1 + 1 + 73 + 1 + 73 + 1 + 32 + 1 +
AcceptedHtlcScriptSize
// AcceptedHtlcSuccessWitnessSizeConfirmed 327 bytes
//
// Input to second level success tx, spending 1 CSV delayed HTLC output.
AcceptedHtlcSuccessWitnessSizeConfirmed = 1 + 1 + 1 + 73 + 1 + 73 + 1 + 32 + 1 +
AcceptedHtlcScriptSizeConfirmed
// OfferedHtlcScriptSize 133 bytes
// - OP_DUP: 1 byte
// - OP_HASH160: 1 byte
// - OP_DATA: 1 byte (RIPEMD160(SHA256(revocationkey)) length)
// - RIPEMD160(SHA256(revocationkey)): 20 bytes
// - OP_EQUAL: 1 byte
// - OP_IF: 1 byte
// - OP_CHECKSIG: 1 byte
// - OP_ELSE: 1 byte
// - OP_DATA: 1 byte (remotekey length)
// - remotekey: 33 bytes
// - OP_SWAP: 1 byte
// - OP_SIZE: 1 byte
// - OP_DATA: 1 byte (32 length)
// - 32: 1 byte
// - OP_EQUAL: 1 byte
// - OP_NOTIF: 1 byte
// - OP_DROP: 1 byte
// - 2: 1 byte
// - OP_SWAP: 1 byte
// - OP_DATA: 1 byte (localkey length)
// - localkey: 33 bytes
// - 2: 1 byte
// - OP_CHECKMULTISIG: 1 byte
// - OP_ELSE: 1 byte
// - OP_HASH160: 1 byte
// - OP_DATA: 1 byte (RIPEMD160(payment_hash) length)
// - RIPEMD160(payment_hash): 20 bytes
// - OP_EQUALVERIFY: 1 byte
// - OP_CHECKSIG: 1 byte
// - OP_ENDIF: 1 byte
// - OP_1: 1 byte // These 3 extra bytes are only
// - OP_CSV: 1 byte // present for the confirmed
// - OP_DROP: 1 byte // HTLC script types.
// - OP_ENDIF: 1 byte
OfferedHtlcScriptSize = 3*1 + 20 + 5*1 + 33 + 10*1 + 33 + 5*1 + 20 + 4*1
// OfferedHtlcScriptSizeConfirmed 136 bytes.
OfferedHtlcScriptSizeConfirmed = OfferedHtlcScriptSize +
HtlcConfirmedScriptOverhead
// OfferedHtlcSuccessWitnessSize 242 bytes
// - number_of_witness_elements: 1 byte
// - receiver_sig_length: 1 byte
// - receiver_sig: 73 bytes
// - payment_preimage_length: 1 byte
// - payment_preimage: 32 bytes
// - witness_script_length: 1 byte
// - witness_script (offered_htlc_script)
OfferedHtlcSuccessWitnessSize = 1 + 1 + 73 + 1 + 32 + 1 + OfferedHtlcScriptSize
// OfferedHtlcSuccessWitnessSizeConfirmed 245 bytes.
OfferedHtlcSuccessWitnessSizeConfirmed = 1 + 1 + 73 + 1 + 32 + 1 +
OfferedHtlcScriptSizeConfirmed
// OfferedHtlcTimeoutWitnessSize 285 bytes
// - number_of_witness_elements: 1 byte
// - nil_length: 1 byte
// - sig_alice_length: 1 byte
// - sig_alice: 73 bytes
// - sig_bob_length: 1 byte
// - sig_bob: 73 bytes
// - nil_length: 1 byte
// - witness_script_length: 1 byte
// - witness_script (offered_htlc_script)
//
// Input to second level timeout tx, spending non-delayed HTLC output.
OfferedHtlcTimeoutWitnessSize = 1 + 1 + 1 + 73 + 1 + 73 + 1 + 1 +
OfferedHtlcScriptSize
// OfferedHtlcTimeoutWitnessSizeConfirmed 288 bytes
//
// Input to second level timeout tx, spending 1 CSV delayed HTLC output.
OfferedHtlcTimeoutWitnessSizeConfirmed = 1 + 1 + 1 + 73 + 1 + 73 + 1 + 1 +
OfferedHtlcScriptSizeConfirmed
// OfferedHtlcPenaltyWitnessSize 243 bytes
// - number_of_witness_elements: 1 byte
// - revocation_sig_length: 1 byte
// - revocation_sig: 73 bytes
// - revocation_key_length: 1 byte
// - revocation_key: 33 bytes
// - witness_script_length: 1 byte
// - witness_script (offered_htlc_script)
OfferedHtlcPenaltyWitnessSize = 1 + 1 + 73 + 1 + 33 + 1 + OfferedHtlcScriptSize
// OfferedHtlcPenaltyWitnessSizeConfirmed 246 bytes.
OfferedHtlcPenaltyWitnessSizeConfirmed = 1 + 1 + 73 + 1 + 33 + 1 +
OfferedHtlcScriptSizeConfirmed
// AnchorScriptSize 40 bytes
// - pubkey_length: 1 byte
// - pubkey: 33 bytes
// - OP_CHECKSIG: 1 byte
// - OP_IFDUP: 1 byte
// - OP_NOTIF: 1 byte
// - OP_16: 1 byte
// - OP_CSV 1 byte
// - OP_ENDIF: 1 byte
AnchorScriptSize = 1 + 33 + 6*1
// AnchorWitnessSize 116 bytes
// - number_of_witnes_elements: 1 byte
// - signature_length: 1 byte
// - signature: 73 bytes
// - witness_script_length: 1 byte
// - witness_script (anchor_script)
AnchorWitnessSize = 1 + 1 + 73 + 1 + AnchorScriptSize
// TaprootSignatureWitnessSize 65 bytes
// - sigLength: 1 byte
// - sig: 64 bytes
TaprootSignatureWitnessSize = 1 + 64
// TaprootKeyPathWitnessSize 66 bytes
// - NumberOfWitnessElements: 1 byte
// - sigLength: 1 byte
// - sig: 64 bytes
TaprootKeyPathWitnessSize = 1 + TaprootSignatureWitnessSize
// TaprootKeyPathCustomSighashWitnessSize 67 bytes
// - NumberOfWitnessElements: 1 byte
// - sigLength: 1 byte
// - sig: 64 bytes
// - sighashFlag: 1 byte
TaprootKeyPathCustomSighashWitnessSize = TaprootKeyPathWitnessSize + 1
// TaprootBaseControlBlockWitnessSize 33 bytes
// - leafVersionAndParity: 1 byte
// - schnorrPubKey: 32 byte
TaprootBaseControlBlockWitnessSize = 33
// TaprootToLocalScriptSize
// - OP_DATA: 1 byte (pub key len)
// - local_key: 32 bytes
// - OP_CHECKSIG: 1 byte
// - OP_DATA: 1 byte (csv delay)
// - csv_delay: 4 bytes (worst case estimate)
// - OP_CSV: 1 byte
// - OP_DROP: 1 byte
TaprootToLocalScriptSize = 41
// TaprootToLocalWitnessSize: 175 bytes
// - number_of_witness_elements: 1 byte
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
// - script_len: 1 byte
// - taproot_to_local_script_size: 41 bytes
// - ctrl_block_len: 1 byte
// - base_control_block_size: 33 bytes
// - sibling_merkle_hash: 32 bytes
TaprootToLocalWitnessSize = 1 + 1 + 65 + 1 + TaprootToLocalScriptSize +
1 + TaprootBaseControlBlockWitnessSize + 32
// TaprootToLocalRevokeScriptSize: 68 bytes
// - OP_DATA: 1 byte
// - local key: 32 bytes
// - OP_DROP: 1 byte
// - OP_DATA: 1 byte
// - revocation key: 32 bytes
// - OP_CHECKSIG: 1 byte
TaprootToLocalRevokeScriptSize = 1 + 32 + 1 + 1 + 32 + 1
// TaprootToLocalRevokeWitnessSize: 202 bytes
// - NumberOfWitnessElements: 1 byte
// - sigLength: 1 byte
// - sweep sig: 65 bytes
// - script len: 1 byte
// - revocation script size: 68 bytes
// - ctrl block size: 1 byte
// - base control block: 33 bytes
// - merkle proof: 32
TaprootToLocalRevokeWitnessSize = (1 + 1 + 65 + 1 +
TaprootToLocalRevokeScriptSize + 1 + 33 + 32)
// TaprootToRemoteScriptSize
// - OP_DATA: 1 byte
// - remote key: 32 bytes
// - OP_CHECKSIG: 1 byte
// - OP_1: 1 byte
// - OP_CHECKSEQUENCEVERIFY: 1 byte
// - OP_DROP: 1 byte
TaprootToRemoteScriptSize = 1 + 32 + 1 + 1 + 1 + 1
// TaprootToRemoteWitnessSize:
// - number_of_witness_elements: 1 byte
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
// - script_len: 1 byte
// - taproot_to_local_script_size: 36 bytes
// - ctrl_block_len: 1 byte
// - base_control_block_size: 33 bytes
TaprootToRemoteWitnessSize = (1 + 1 + 65 + 1 +
TaprootToRemoteScriptSize + 1 +
TaprootBaseControlBlockWitnessSize)
// TaprootAnchorWitnessSize: 67 bytes
//
// In this case, we use the custom sighash size to give the most
// pessemistic estimate.
TaprootAnchorWitnessSize = TaprootKeyPathCustomSighashWitnessSize
// TaprootSecondLevelHtlcScriptSize: 41 bytes
// - OP_DATA: 1 byte (pub key len)
// - local_key: 32 bytes
// - OP_CHECKSIG: 1 byte
// - OP_DATA: 1 byte (csv delay)
// - csv_delay: 4 bytes (worst case)
// - OP_CSV: 1 byte
// - OP_DROP: 1 byte
TaprootSecondLevelHtlcScriptSize = 1 + 32 + 1 + 1 + 4 + 1 + 1
// TaprootSecondLevelHtlcWitnessSize:
// - number_of_witness_elements: 1 byte
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
// - script_len: 1 byte
// - taproot_second_level_htlc_script_size: 40 bytes
// - ctrl_block_len: 1 byte
// - base_control_block_size: 33 bytes
TaprootSecondLevelHtlcWitnessSize = 1 + 1 + 65 + 1 +
TaprootSecondLevelHtlcScriptSize + 1 +
TaprootBaseControlBlockWitnessSize
// TaprootSecondLevelRevokeWitnessSize
// - number_of_witness_elements: 1 byte
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
//nolint:ll
TaprootSecondLevelRevokeWitnessSize = TaprootKeyPathCustomSighashWitnessSize
// TaprootAcceptedRevokeWitnessSize:
// - number_of_witness_elements: 1 byte
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
//nolint:ll
TaprootAcceptedRevokeWitnessSize = TaprootKeyPathCustomSighashWitnessSize
// TaprootOfferedRevokeWitnessSize:
// - number_of_witness_elements: 1 byte
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
TaprootOfferedRevokeWitnessSize = TaprootKeyPathCustomSighashWitnessSize
// TaprootHtlcOfferedRemoteTimeoutScriptSize: 42 bytes
// - OP_DATA: 1 byte (pub key len)
// - local_key: 32 bytes
// - OP_CHECKSIG: 1 byte
// - OP_1: 1 byte
// - OP_DROP: 1 byte
// - OP_CHECKSEQUENCEVERIFY: 1 byte
// - OP_DATA: 1 byte (cltv_expiry length)
// - cltv_expiry: 4 bytes
// - OP_CHECKLOCKTIMEVERIFY: 1 byte
// - OP_DROP: 1 byte
TaprootHtlcOfferedRemoteTimeoutScriptSize = (1 + 32 + 1 + 1 + 1 + 1 +
1 + 4 + 1 + 1)
// TaprootHtlcOfferedRemoteTimeoutwitSize: 176 bytes
// - number_of_witness_elements: 1 byte
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
// - script_len: 1 byte
// - taproot_offered_htlc_script_size: 42 bytes
// - ctrl_block_len: 1 byte
// - base_control_block_size: 33 bytes
// - sibilng_merkle_proof: 32 bytes
TaprootHtlcOfferedRemoteTimeoutWitnessSize = 1 + 1 + 65 + 1 +
TaprootHtlcOfferedRemoteTimeoutScriptSize + 1 +
TaprootBaseControlBlockWitnessSize + 32
// TaprootHtlcOfferedLocalTmeoutScriptSize:
// - OP_DATA: 1 byte (pub key len)
// - local_key: 32 bytes
// - OP_CHECKSIGVERIFY: 1 byte
// - OP_DATA: 1 byte (pub key len)
// - remote_key: 32 bytes
// - OP_CHECKSIG: 1 byte
TaprootHtlcOfferedLocalTimeoutScriptSize = 1 + 32 + 1 + 1 + 32 + 1
// TaprootOfferedLocalTimeoutWitnessSize
// - number_of_witness_elements: 1 byte
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
// - script_len: 1 byte
// - taproot_offered_htlc_script_timeout_size:
// - ctrl_block_len: 1 byte
// - base_control_block_size: 33 bytes
// - sibilng_merkle_proof: 32 bytes
TaprootOfferedLocalTimeoutWitnessSize = 1 + 1 + 65 + 1 + 65 + 1 +
TaprootHtlcOfferedLocalTimeoutScriptSize + 1 +
TaprootBaseControlBlockWitnessSize + 32
// TaprootHtlcAcceptedRemoteSuccessScriptSize:
// - OP_SIZE: 1 byte
// - OP_DATA: 1 byte
// - 32: 1 byte
// - OP_EQUALVERIFY: 1 byte
// - OP_HASH160: 1 byte
// - OP_DATA: 1 byte (RIPEMD160(payment_hash) length)
// - RIPEMD160(payment_hash): 20 bytes
// - OP_EQUALVERIFY: 1 byte
// - OP_DATA: 1 byte (pub key len)
// - remote_key: 32 bytes
// - OP_CHECKSIG: 1 byte
// - OP_1: 1 byte
// - OP_CSV: 1 byte
// - OP_DROP: 1 byte
TaprootHtlcAcceptedRemoteSuccessScriptSize = 1 + 1 + 1 + 1 + 1 + 1 +
1 + 20 + 1 + 32 + 1 + 1 + 1 + 1
// TaprootHtlcAcceptedRemoteSuccessScriptSize:
// - number_of_witness_elements: 1 byte
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
// - payment_preimage_length: 1 byte
// - payment_preimage: 32 bytes
// - script_len: 1 byte
// - taproot_offered_htlc_script_success_size:
// - ctrl_block_len: 1 byte
// - base_control_block_size: 33 bytes
// - sibilng_merkle_proof: 32 bytes
TaprootHtlcAcceptedRemoteSuccessWitnessSize = 1 + 1 + 65 + 1 + 32 + 1 +
TaprootHtlcAcceptedRemoteSuccessScriptSize + 1 +
TaprootBaseControlBlockWitnessSize + 32
// TaprootHtlcAcceptedLocalSuccessScriptSize:
// - OP_SIZE: 1 byte
// - OP_DATA: 1 byte
// - 32: 1 byte
// - OP_EQUALVERIFY: 1 byte
// - OP_HASH160: 1 byte
// - OP_DATA: 1 byte (RIPEMD160(payment_hash) length)
// - RIPEMD160(payment_hash): 20 bytes
// - OP_EQUALVERIFY: 1 byte
// - OP_DATA: 1 byte (pub key len)
// - local_key: 32 bytes
// - OP_CHECKSIGVERIFY: 1 byte
// - OP_DATA: 1 byte (pub key len)
// - remote_key: 32 bytes
// - OP_CHECKSIG: 1 byte
TaprootHtlcAcceptedLocalSuccessScriptSize = 1 + 1 + 1 + 1 + 1 + 1 +
20 + 1 + 1 + 32 + 1 + 1 + 32 + 1
// TaprootHtlcAcceptedLocalSuccessWitnessSize:
// - number_of_witness_elements: 1 byte
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
// - payment_preimage_length: 1 byte
// - payment_preimage: 32 bytes
// - script_len: 1 byte
// - taproot_accepted_htlc_script_success_size:
// - ctrl_block_len: 1 byte
// - base_control_block_size: 33 bytes
// - sibilng_merkle_proof: 32 bytes
TaprootHtlcAcceptedLocalSuccessWitnessSize = 1 + 1 + 65 + 1 + 65 + 1 +
32 + 1 + TaprootHtlcAcceptedLocalSuccessScriptSize + 1 +
TaprootBaseControlBlockWitnessSize + 32
)
// EstimateCommitTxWeight estimate commitment transaction weight depending on
// the precalculated weight of base transaction, witness data, which is needed
// for paying for funding tx, and htlc weight multiplied by their count.
func EstimateCommitTxWeight(count int, prediction bool) int64 {
// Make prediction about the size of commitment transaction with
// additional HTLC.
if prediction {
count++
}
htlcWeight := int64(count * HTLCWeight)
baseWeight := int64(BaseCommitmentTxWeight)
witnessWeight := int64(WitnessCommitmentTxWeight)
// TODO(roasbeef): need taproot modifier? also no anchor so wrong?
return htlcWeight + baseWeight + witnessWeight
}
// TxWeightEstimator is able to calculate weight estimates for transactions
// based on the input and output types. For purposes of estimation, all
// signatures are assumed to be of the maximum possible size, 73 bytes. Each
// method of the estimator returns an instance with the estimate applied. This
// allows callers to chain each of the methods
type TxWeightEstimator struct {
hasWitness bool
inputCount uint32
outputCount uint32
inputSize lntypes.VByte
inputWitnessSize lntypes.WeightUnit
outputSize lntypes.VByte
}
// AddP2PKHInput updates the weight estimate to account for an additional input
// spending a P2PKH output.
func (twe *TxWeightEstimator) AddP2PKHInput() *TxWeightEstimator {
twe.inputSize += InputSize + P2PKHScriptSigSize
twe.inputWitnessSize++
twe.inputCount++
return twe
}
// AddP2WKHInput updates the weight estimate to account for an additional input
// spending a native P2PWKH output.
func (twe *TxWeightEstimator) AddP2WKHInput() *TxWeightEstimator {
twe.AddWitnessInput(P2WKHWitnessSize)
return twe
}
// AddWitnessInput updates the weight estimate to account for an additional
// input spending a native pay-to-witness output. This accepts the total size
// of the witness as a parameter.
func (twe *TxWeightEstimator) AddWitnessInput(
witnessSize lntypes.WeightUnit) *TxWeightEstimator {
twe.inputSize += InputSize
twe.inputWitnessSize += witnessSize
twe.inputCount++
twe.hasWitness = true
return twe
}
// AddTapscriptInput updates the weight estimate to account for an additional
// input spending a segwit v1 pay-to-taproot output using the script path. This
// accepts the total size of the witness for the script leaf that is executed
// and adds the size of the control block to the total witness size.
//
// NOTE: The leaf witness size must be calculated without the byte that accounts
// for the number of witness elements, only the total size of all elements on
// the stack that are consumed by the revealed script should be counted.
func (twe *TxWeightEstimator) AddTapscriptInput(
leafWitnessSize lntypes.WeightUnit,
tapscript *waddrmgr.Tapscript) *TxWeightEstimator {
// We add 1 byte for the total number of witness elements.
controlBlockWitnessSize := 1 + TaprootBaseControlBlockWitnessSize +
// 1 byte for the length of the element plus the element itself.
1 + len(tapscript.RevealedScript) +
1 + len(tapscript.ControlBlock.InclusionProof)
twe.inputSize += InputSize
twe.inputWitnessSize += leafWitnessSize + lntypes.WeightUnit(
controlBlockWitnessSize,
)
twe.inputCount++
twe.hasWitness = true
return twe
}
// AddTaprootKeySpendInput updates the weight estimate to account for an
// additional input spending a segwit v1 pay-to-taproot output using the key
// spend path. This accepts the sighash type being used since that has an
// influence on the total size of the signature.
func (twe *TxWeightEstimator) AddTaprootKeySpendInput(
hashType txscript.SigHashType) *TxWeightEstimator {
twe.inputSize += InputSize
if hashType == txscript.SigHashDefault {
twe.inputWitnessSize += TaprootKeyPathWitnessSize
} else {
twe.inputWitnessSize += TaprootKeyPathCustomSighashWitnessSize
}
twe.inputCount++
twe.hasWitness = true
return twe
}
// AddNestedP2WKHInput updates the weight estimate to account for an additional
// input spending a P2SH output with a nested P2WKH redeem script.
func (twe *TxWeightEstimator) AddNestedP2WKHInput() *TxWeightEstimator {
twe.inputSize += InputSize + NestedP2WPKHSize
twe.inputWitnessSize += P2WKHWitnessSize
twe.inputCount++
twe.hasWitness = true
return twe
}
// AddNestedP2WSHInput updates the weight estimate to account for an additional
// input spending a P2SH output with a nested P2WSH redeem script.
func (twe *TxWeightEstimator) AddNestedP2WSHInput(
witnessSize lntypes.WeightUnit) *TxWeightEstimator {
twe.inputSize += InputSize + NestedP2WSHSize
twe.inputWitnessSize += witnessSize
twe.inputCount++
twe.hasWitness = true
return twe
}
// AddTxOutput adds a known TxOut to the weight estimator.
func (twe *TxWeightEstimator) AddTxOutput(txOut *wire.TxOut) *TxWeightEstimator {
twe.outputSize += lntypes.VByte(txOut.SerializeSize())
twe.outputCount++
return twe
}
// AddP2PKHOutput updates the weight estimate to account for an additional P2PKH
// output.
func (twe *TxWeightEstimator) AddP2PKHOutput() *TxWeightEstimator {
twe.outputSize += P2PKHOutputSize
twe.outputCount++
return twe
}
// AddP2WKHOutput updates the weight estimate to account for an additional
// native P2WKH output.
func (twe *TxWeightEstimator) AddP2WKHOutput() *TxWeightEstimator {
twe.outputSize += P2WKHOutputSize
twe.outputCount++
return twe
}
// AddP2WSHOutput updates the weight estimate to account for an additional
// native P2WSH output.
func (twe *TxWeightEstimator) AddP2WSHOutput() *TxWeightEstimator {
twe.outputSize += P2WSHOutputSize
twe.outputCount++
return twe
}
// AddP2TROutput updates the weight estimate to account for an additional native
// SegWit v1 P2TR output.
func (twe *TxWeightEstimator) AddP2TROutput() *TxWeightEstimator {
twe.outputSize += P2TROutputSize
twe.outputCount++
return twe
}
// AddP2SHOutput updates the weight estimate to account for an additional P2SH
// output.
func (twe *TxWeightEstimator) AddP2SHOutput() *TxWeightEstimator {
twe.outputSize += P2SHOutputSize
twe.outputCount++
return twe
}
// AddOutput estimates the weight of an output based on the pkScript.
func (twe *TxWeightEstimator) AddOutput(pkScript []byte) *TxWeightEstimator {
twe.outputSize += BaseOutputSize + lntypes.VByte(len(pkScript))
twe.outputCount++
return twe
}
// Weight gets the estimated weight of the transaction.
func (twe *TxWeightEstimator) Weight() lntypes.WeightUnit {
inputCount := wire.VarIntSerializeSize(uint64(twe.inputCount))
outputCount := wire.VarIntSerializeSize(uint64(twe.outputCount))
txSizeStripped := BaseTxSize + lntypes.VByte(inputCount) +
twe.inputSize + lntypes.VByte(outputCount) + twe.outputSize
weight := lntypes.WeightUnit(txSizeStripped * witnessScaleFactor)
if twe.hasWitness {
weight += WitnessHeaderSize + twe.inputWitnessSize
}
return weight
}
// VSize gets the estimated virtual size of the transactions, in vbytes.
func (twe *TxWeightEstimator) VSize() int {
// A tx's vsize is 1/4 of the weight, rounded up.
return int(twe.Weight().ToVB())
}
package input
import (
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/lightningnetwork/lnd/fn/v2"
)
const (
// PubKeyFormatCompressedOdd is the identifier prefix byte for a public
// key whose Y coordinate is odd when serialized in the compressed
// format per section 2.3.4 of
// [SEC1](https://secg.org/sec1-v2.pdf#subsubsection.2.3.4).
// This is copied from the github.com/decred/dcrd/dcrec/secp256k1/v4 to
// avoid needing to directly reference (and by accident pull in
// incompatible crypto primitives) the package.
PubKeyFormatCompressedOdd byte = 0x03
)
// AuxTapLeaf is a type alias for an optional tapscript leaf that may be added
// to the tapscript tree of HTLC and commitment outputs.
type AuxTapLeaf = fn.Option[txscript.TapLeaf]
// NoneTapLeaf returns an empty optional tapscript leaf.
func NoneTapLeaf() AuxTapLeaf {
return fn.None[txscript.TapLeaf]()
}
// HtlcIndex represents the monotonically increasing counter that is used to
// identify HTLCs created by a peer.
type HtlcIndex = uint64
// HtlcAuxLeaf is a type that represents an auxiliary leaf for an HTLC output.
// An HTLC may have up to two aux leaves: one for the output on the commitment
// transaction, and one for the second level HTLC.
type HtlcAuxLeaf struct {
AuxTapLeaf
// SecondLevelLeaf is the auxiliary leaf for the second level HTLC
// success or timeout transaction.
SecondLevelLeaf AuxTapLeaf
}
// HtlcAuxLeaves is a type alias for a map of optional tapscript leaves.
type HtlcAuxLeaves = map[HtlcIndex]HtlcAuxLeaf
// NewTxSigHashesV0Only returns a new txscript.TxSigHashes instance that will
// only calculate the sighash midstate values for segwit v0 inputs and can
// therefore never be used for transactions that want to spend segwit v1
// (taproot) inputs.
func NewTxSigHashesV0Only(tx *wire.MsgTx) *txscript.TxSigHashes {
// The canned output fetcher returns a wire.TxOut instance with the
// given pk script and amount. We can get away with nil since the first
// thing the TxSigHashes constructor checks is the length of the pk
// script and whether it matches taproot output script length. If the
// length doesn't match it assumes v0 inputs only.
nilFetcher := txscript.NewCannedPrevOutputFetcher(nil, 0)
return txscript.NewTxSigHashes(tx, nilFetcher)
}
// MultiPrevOutFetcher returns a txscript.MultiPrevOutFetcher for the given set
// of inputs.
func MultiPrevOutFetcher(inputs []Input) (*txscript.MultiPrevOutFetcher, error) {
fetcher := txscript.NewMultiPrevOutFetcher(nil)
for _, inp := range inputs {
op := inp.OutPoint()
desc := inp.SignDesc()
if op == EmptyOutPoint {
return nil, fmt.Errorf("missing input outpoint")
}
if desc == nil || desc.Output == nil {
return nil, fmt.Errorf("missing input utxo information")
}
fetcher.AddPrevOut(op, desc.Output)
}
return fetcher, nil
}
// TapscriptFullTree creates a waddrmgr.Tapscript for the given internal key and
// tree leaves.
func TapscriptFullTree(internalKey *btcec.PublicKey,
allTreeLeaves ...txscript.TapLeaf) *waddrmgr.Tapscript {
tree := txscript.AssembleTaprootScriptTree(allTreeLeaves...)
rootHash := tree.RootNode.TapHash()
tapKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash[:])
var outputKeyYIsOdd bool
if tapKey.SerializeCompressed()[0] == PubKeyFormatCompressedOdd {
outputKeyYIsOdd = true
}
return &waddrmgr.Tapscript{
Type: waddrmgr.TapscriptTypeFullTree,
ControlBlock: &txscript.ControlBlock{
InternalKey: internalKey,
OutputKeyYIsOdd: outputKeyYIsOdd,
LeafVersion: txscript.BaseLeafVersion,
},
Leaves: allTreeLeaves,
}
}
// TapscriptPartialReveal creates a waddrmgr.Tapscript for the given internal
// key and revealed script.
func TapscriptPartialReveal(internalKey *btcec.PublicKey,
revealedLeaf txscript.TapLeaf,
inclusionProof []byte) *waddrmgr.Tapscript {
controlBlock := &txscript.ControlBlock{
InternalKey: internalKey,
LeafVersion: txscript.BaseLeafVersion,
InclusionProof: inclusionProof,
}
rootHash := controlBlock.RootHash(revealedLeaf.Script)
tapKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash)
if tapKey.SerializeCompressed()[0] == PubKeyFormatCompressedOdd {
controlBlock.OutputKeyYIsOdd = true
}
return &waddrmgr.Tapscript{
Type: waddrmgr.TapscriptTypePartialReveal,
ControlBlock: controlBlock,
RevealedScript: revealedLeaf.Script,
}
}
// TapscriptRootHashOnly creates a waddrmgr.Tapscript for the given internal key
// and root hash.
func TapscriptRootHashOnly(internalKey *btcec.PublicKey,
rootHash []byte) *waddrmgr.Tapscript {
controlBlock := &txscript.ControlBlock{
InternalKey: internalKey,
}
tapKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash)
if tapKey.SerializeCompressed()[0] == PubKeyFormatCompressedOdd {
controlBlock.OutputKeyYIsOdd = true
}
return &waddrmgr.Tapscript{
Type: waddrmgr.TaprootKeySpendRootHash,
ControlBlock: controlBlock,
RootHash: rootHash,
}
}
// TapscriptFullKeyOnly creates a waddrmgr.Tapscript for the given full Taproot
// key.
func TapscriptFullKeyOnly(taprootKey *btcec.PublicKey) *waddrmgr.Tapscript {
return &waddrmgr.Tapscript{
Type: waddrmgr.TaprootFullKeyOnly,
FullOutputKey: taprootKey,
}
}
// PayToTaprootScript creates a new script to pay to a version 1 (taproot)
// witness program. The passed public key will be serialized as an x-only key
// to create the witness program.
func PayToTaprootScript(taprootKey *btcec.PublicKey) ([]byte, error) {
builder := txscript.NewScriptBuilder()
builder.AddOp(txscript.OP_1)
builder.AddData(schnorr.SerializePubKey(taprootKey))
return builder.Script()
}
package input
import (
"bytes"
"encoding/hex"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/keychain"
)
var (
// For simplicity a single priv key controls all of our test outputs.
testWalletPrivKey = []byte{
0x2b, 0xd8, 0x06, 0xc9, 0x7f, 0x0e, 0x00, 0xaf,
0x1a, 0x1f, 0xc3, 0x32, 0x8f, 0xa7, 0x63, 0xa9,
0x26, 0x97, 0x23, 0xc8, 0xdb, 0x8f, 0xac, 0x4f,
0x93, 0xaf, 0x71, 0xdb, 0x18, 0x6d, 0x6e, 0x90,
}
// We're alice :)
bobsPrivKey = []byte{
0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda,
0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17,
0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d,
0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9,
}
// Use a hard-coded HD seed.
testHdSeed = chainhash.Hash{
0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9,
0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
}
)
// MockSigner is a simple implementation of the Signer interface. Each one has
// a set of private keys in a slice and can sign messages using the appropriate
// one.
type MockSigner struct {
Privkeys []*btcec.PrivateKey
NetParams *chaincfg.Params
*MusigSessionManager
}
// NewMockSigner returns a new instance of the MockSigner given a set of
// backing private keys.
func NewMockSigner(privKeys []*btcec.PrivateKey,
netParams *chaincfg.Params) *MockSigner {
signer := &MockSigner{
Privkeys: privKeys,
NetParams: netParams,
}
keyFetcher := func(*keychain.KeyDescriptor) (*btcec.PrivateKey, error) {
return signer.Privkeys[0], nil
}
signer.MusigSessionManager = NewMusigSessionManager(keyFetcher)
return signer
}
// SignOutputRaw generates a signature for the passed transaction according to
// the data within the passed SignDescriptor.
func (m *MockSigner) SignOutputRaw(tx *wire.MsgTx,
signDesc *SignDescriptor) (Signature, error) {
pubkey := signDesc.KeyDesc.PubKey
switch {
case signDesc.SingleTweak != nil:
pubkey = TweakPubKeyWithTweak(pubkey, signDesc.SingleTweak)
case signDesc.DoubleTweak != nil:
pubkey = DeriveRevocationPubkey(pubkey, signDesc.DoubleTweak.PubKey())
}
hash160 := btcutil.Hash160(pubkey.SerializeCompressed())
privKey := m.findKey(hash160, signDesc.SingleTweak, signDesc.DoubleTweak)
if privKey == nil {
return nil, fmt.Errorf("mock signer does not have key")
}
// In case of a taproot output any signature is always a Schnorr
// signature, based on the new tapscript sighash algorithm.
if txscript.IsPayToTaproot(signDesc.Output.PkScript) {
sigHashes := txscript.NewTxSigHashes(
tx, signDesc.PrevOutputFetcher,
)
// Are we spending a script path or the key path? The API is
// slightly different, so we need to account for that to get
// the raw signature.
var (
rawSig []byte
err error
)
switch signDesc.SignMethod {
case TaprootKeySpendBIP0086SignMethod,
TaprootKeySpendSignMethod:
// This function tweaks the private key using the tap
// root key supplied as the tweak.
rawSig, err = txscript.RawTxInTaprootSignature(
tx, sigHashes, signDesc.InputIndex,
signDesc.Output.Value, signDesc.Output.PkScript,
signDesc.TapTweak, signDesc.HashType,
privKey,
)
if err != nil {
return nil, err
}
case TaprootScriptSpendSignMethod:
leaf := txscript.TapLeaf{
LeafVersion: txscript.BaseLeafVersion,
Script: signDesc.WitnessScript,
}
rawSig, err = txscript.RawTxInTapscriptSignature(
tx, sigHashes, signDesc.InputIndex,
signDesc.Output.Value, signDesc.Output.PkScript,
leaf, signDesc.HashType, privKey,
)
if err != nil {
return nil, err
}
}
// The signature returned above might have a sighash flag
// attached if a non-default type was used. We'll slice this
// off if it exists to ensure we can properly parse the raw
// signature.
sig, err := schnorr.ParseSignature(
rawSig[:schnorr.SignatureSize],
)
if err != nil {
return nil, err
}
return sig, nil
}
sig, err := txscript.RawTxInWitnessSignature(
tx, signDesc.SigHashes, signDesc.InputIndex,
signDesc.Output.Value, signDesc.WitnessScript,
signDesc.HashType, privKey,
)
if err != nil {
return nil, err
}
return ecdsa.ParseDERSignature(sig[:len(sig)-1])
}
// ComputeInputScript generates a complete InputIndex for the passed transaction
// with the signature as defined within the passed SignDescriptor. This method
// should be capable of generating the proper input script for both regular
// p2wkh output and p2wkh outputs nested within a regular p2sh output.
func (m *MockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*Script, error) {
scriptType, addresses, _, err := txscript.ExtractPkScriptAddrs(
signDesc.Output.PkScript, m.NetParams)
if err != nil {
return nil, err
}
switch scriptType {
case txscript.PubKeyHashTy:
privKey := m.findKey(addresses[0].ScriptAddress(), signDesc.SingleTweak,
signDesc.DoubleTweak)
if privKey == nil {
return nil, fmt.Errorf("mock signer does not have key for "+
"address %v", addresses[0])
}
sigScript, err := txscript.SignatureScript(
tx, signDesc.InputIndex, signDesc.Output.PkScript,
txscript.SigHashAll, privKey, true,
)
if err != nil {
return nil, err
}
return &Script{SigScript: sigScript}, nil
case txscript.WitnessV0PubKeyHashTy:
privKey := m.findKey(addresses[0].ScriptAddress(), signDesc.SingleTweak,
signDesc.DoubleTweak)
if privKey == nil {
return nil, fmt.Errorf("mock signer does not have key for "+
"address %v", addresses[0])
}
witnessScript, err := txscript.WitnessSignature(tx, signDesc.SigHashes,
signDesc.InputIndex, signDesc.Output.Value,
signDesc.Output.PkScript, txscript.SigHashAll, privKey, true)
if err != nil {
return nil, err
}
return &Script{Witness: witnessScript}, nil
default:
return nil, fmt.Errorf("unexpected script type: %v", scriptType)
}
}
// findKey searches through all stored private keys and returns one
// corresponding to the hashed pubkey if it can be found. The public key may
// either correspond directly to the private key or to the private key with a
// tweak applied.
func (m *MockSigner) findKey(needleHash160 []byte, singleTweak []byte,
doubleTweak *btcec.PrivateKey) *btcec.PrivateKey {
for _, privkey := range m.Privkeys {
// First check whether public key is directly derived from
// private key.
hash160 := btcutil.Hash160(privkey.PubKey().SerializeCompressed())
if bytes.Equal(hash160, needleHash160) {
return privkey
}
// Otherwise check if public key is derived from tweaked
// private key.
switch {
case singleTweak != nil:
privkey = TweakPrivKey(privkey, singleTweak)
case doubleTweak != nil:
privkey = DeriveRevocationPrivKey(privkey, doubleTweak)
default:
continue
}
hash160 = btcutil.Hash160(privkey.PubKey().SerializeCompressed())
if bytes.Equal(hash160, needleHash160) {
return privkey
}
}
return nil
}
// pubkeyFromHex parses a Bitcoin public key from a hex encoded string.
func pubkeyFromHex(keyHex string) (*btcec.PublicKey, error) {
bytes, err := hex.DecodeString(keyHex)
if err != nil {
return nil, err
}
return btcec.ParsePubKey(bytes)
}
// privkeyFromHex parses a Bitcoin private key from a hex encoded string.
func privkeyFromHex(keyHex string) (*btcec.PrivateKey, error) {
bytes, err := hex.DecodeString(keyHex)
if err != nil {
return nil, err
}
key, _ := btcec.PrivKeyFromBytes(bytes)
return key, nil
}
// pubkeyToHex serializes a Bitcoin public key to a hex encoded string.
func pubkeyToHex(key *btcec.PublicKey) string {
return hex.EncodeToString(key.SerializeCompressed())
}
// privkeyFromHex serializes a Bitcoin private key to a hex encoded string.
func privkeyToHex(key *btcec.PrivateKey) string {
return hex.EncodeToString(key.Serialize())
}
package input
import (
"encoding/binary"
"io"
"github.com/btcsuite/btcd/wire"
)
// writeTxOut serializes a wire.TxOut struct into the passed io.Writer stream.
func writeTxOut(w io.Writer, txo *wire.TxOut) error {
var scratch [8]byte
binary.BigEndian.PutUint64(scratch[:], uint64(txo.Value))
if _, err := w.Write(scratch[:]); err != nil {
return err
}
if err := wire.WriteVarBytes(w, 0, txo.PkScript); err != nil {
return err
}
return nil
}
// readTxOut deserializes a wire.TxOut struct from the passed io.Reader stream.
func readTxOut(r io.Reader, txo *wire.TxOut) error {
var scratch [8]byte
if _, err := io.ReadFull(r, scratch[:]); err != nil {
return err
}
value := int64(binary.BigEndian.Uint64(scratch[:]))
pkScript, err := wire.ReadVarBytes(r, 0, 80, "pkScript")
if err != nil {
return err
}
*txo = wire.TxOut{
Value: value,
PkScript: pkScript,
}
return nil
}
package input
import (
"fmt"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/lntypes"
)
// WitnessGenerator represents a function that is able to generate the final
// witness for a particular public key script. Additionally, if required, this
// function will also return the sigScript for spending nested P2SH witness
// outputs. This function acts as an abstraction layer, hiding the details of
// the underlying script.
type WitnessGenerator func(tx *wire.MsgTx, hc *txscript.TxSigHashes,
inputIndex int) (*Script, error)
// WitnessType determines how an output's witness will be generated. This
// interface can be implemented to be used for custom sweep scripts if the
// pre-defined StandardWitnessType list doesn't provide a suitable one.
type WitnessType interface {
// String returns a human readable version of the WitnessType.
String() string
// WitnessGenerator will return a WitnessGenerator function that an
// output uses to generate the witness and optionally the sigScript for
// a sweep transaction.
WitnessGenerator(signer Signer,
descriptor *SignDescriptor) WitnessGenerator
// SizeUpperBound returns the maximum length of the witness of this
// WitnessType if it would be included in a tx. It also returns if the
// output itself is a nested p2sh output, if so then we need to take
// into account the extra sigScript data size.
SizeUpperBound() (lntypes.WeightUnit, bool, error)
// AddWeightEstimation adds the estimated size of the witness in bytes
// to the given weight estimator.
AddWeightEstimation(e *TxWeightEstimator) error
}
// StandardWitnessType is a numeric representation of standard pre-defined types
// of witness configurations.
type StandardWitnessType uint16
// A compile time check to ensure StandardWitnessType implements the
// WitnessType interface.
var _ WitnessType = (StandardWitnessType)(0)
// NOTE: When adding a new `StandardWitnessType`, also update the `WitnessType`
// protobuf enum and the `allWitnessTypes` map in the `walletrpc` package.
const (
// CommitmentTimeLock is a witness that allows us to spend our output
// on our local commitment transaction after a relative lock-time
// lockout.
CommitmentTimeLock StandardWitnessType = 0
// CommitmentNoDelay is a witness that allows us to spend a settled
// no-delay output immediately on a counterparty's commitment
// transaction.
CommitmentNoDelay StandardWitnessType = 1
// CommitmentRevoke is a witness that allows us to sweep the settled
// output of a malicious counterparty's who broadcasts a revoked
// commitment transaction.
CommitmentRevoke StandardWitnessType = 2
// HtlcOfferedRevoke is a witness that allows us to sweep an HTLC which
// we offered to the remote party in the case that they broadcast a
// revoked commitment state.
HtlcOfferedRevoke StandardWitnessType = 3
// HtlcAcceptedRevoke is a witness that allows us to sweep an HTLC
// output sent to us in the case that the remote party broadcasts a
// revoked commitment state.
HtlcAcceptedRevoke StandardWitnessType = 4
// HtlcOfferedTimeoutSecondLevel is a witness that allows us to sweep
// an HTLC output that we extended to a party, but was never fulfilled.
// This HTLC output isn't directly on the commitment transaction, but
// is the result of a confirmed second-level HTLC transaction. As a
// result, we can only spend this after a CSV delay.
HtlcOfferedTimeoutSecondLevel StandardWitnessType = 5
// HtlcOfferedTimeoutSecondLevelInputConfirmed is a witness that allows
// us to sweep an HTLC output that we extended to a party, but was
// never fulfilled. This _is_ the HTLC output directly on our
// commitment transaction, and the input to the second-level HTLC
// timeout transaction. It can only be spent after CLTV expiry, and
// commitment confirmation.
HtlcOfferedTimeoutSecondLevelInputConfirmed StandardWitnessType = 15
// HtlcAcceptedSuccessSecondLevel is a witness that allows us to sweep
// an HTLC output that was offered to us, and for which we have a
// payment preimage. This HTLC output isn't directly on our commitment
// transaction, but is the result of confirmed second-level HTLC
// transaction. As a result, we can only spend this after a CSV delay.
HtlcAcceptedSuccessSecondLevel StandardWitnessType = 6
// HtlcAcceptedSuccessSecondLevelInputConfirmed is a witness that
// allows us to sweep an HTLC output that was offered to us, and for
// which we have a payment preimage. This _is_ the HTLC output directly
// on our commitment transaction, and the input to the second-level
// HTLC success transaction. It can only be spent after the commitment
// has confirmed.
HtlcAcceptedSuccessSecondLevelInputConfirmed StandardWitnessType = 16
// HtlcOfferedRemoteTimeout is a witness that allows us to sweep an
// HTLC that we offered to the remote party which lies in the
// commitment transaction of the remote party. We can spend this output
// after the absolute CLTV timeout of the HTLC as passed.
HtlcOfferedRemoteTimeout StandardWitnessType = 7
// HtlcAcceptedRemoteSuccess is a witness that allows us to sweep an
// HTLC that was offered to us by the remote party. We use this witness
// in the case that the remote party goes to chain, and we know the
// pre-image to the HTLC. We can sweep this without any additional
// timeout.
HtlcAcceptedRemoteSuccess StandardWitnessType = 8
// HtlcSecondLevelRevoke is a witness that allows us to sweep an HTLC
// from the remote party's commitment transaction in the case that the
// broadcast a revoked commitment, but then also immediately attempt to
// go to the second level to claim the HTLC.
HtlcSecondLevelRevoke StandardWitnessType = 9
// WitnessKeyHash is a witness type that allows us to spend a regular
// p2wkh output that's sent to an output which is under complete
// control of the backing wallet.
WitnessKeyHash StandardWitnessType = 10
// NestedWitnessKeyHash is a witness type that allows us to sweep an
// output that sends to a nested P2SH script that pays to a key solely
// under our control. The witness generated needs to include the
NestedWitnessKeyHash StandardWitnessType = 11
// CommitSpendNoDelayTweakless is similar to the CommitSpendNoDelay
// type, but it omits the tweak that randomizes the key we need to
// spend with a channel peer supplied set of randomness.
CommitSpendNoDelayTweakless StandardWitnessType = 12
// CommitmentToRemoteConfirmed is a witness that allows us to spend our
// output on the counterparty's commitment transaction after a
// confirmation.
CommitmentToRemoteConfirmed StandardWitnessType = 13
// CommitmentAnchor is a witness that allows us to spend our anchor on
// the commitment transaction.
CommitmentAnchor StandardWitnessType = 14
// LeaseCommitmentTimeLock is a witness that allows us to spend our
// output on our local commitment transaction after a relative and
// absolute lock-time lockout as part of the script enforced lease
// commitment type.
LeaseCommitmentTimeLock StandardWitnessType = 17
// LeaseCommitmentToRemoteConfirmed is a witness that allows us to spend
// our output on the counterparty's commitment transaction after a
// confirmation and absolute locktime as part of the script enforced
// lease commitment type.
LeaseCommitmentToRemoteConfirmed StandardWitnessType = 18
// LeaseHtlcOfferedTimeoutSecondLevel is a witness that allows us to
// sweep an HTLC output that we extended to a party, but was never
// fulfilled. This HTLC output isn't directly on the commitment
// transaction, but is the result of a confirmed second-level HTLC
// transaction. As a result, we can only spend this after a CSV delay
// and CLTV locktime as part of the script enforced lease commitment
// type.
LeaseHtlcOfferedTimeoutSecondLevel StandardWitnessType = 19
// LeaseHtlcAcceptedSuccessSecondLevel is a witness that allows us to
// sweep an HTLC output that was offered to us, and for which we have a
// payment preimage. This HTLC output isn't directly on our commitment
// transaction, but is the result of confirmed second-level HTLC
// transaction. As a result, we can only spend this after a CSV delay
// and CLTV locktime as part of the script enforced lease commitment
// type.
LeaseHtlcAcceptedSuccessSecondLevel StandardWitnessType = 20
// TaprootPubKeySpend is a witness type that allows us to spend a
// regular p2tr output that's sent to an output which is under complete
// control of the backing wallet.
TaprootPubKeySpend StandardWitnessType = 21
// TaprootLocalCommitSpend is a witness type that allows us to spend
// our settled local commitment after a CSV delay when we force close
// the channel.
TaprootLocalCommitSpend StandardWitnessType = 22
// TaprootRemoteCommitSpend is a witness type that allows us to spend
// our settled local commitment after a CSV delay when the remote party
// has force closed the channel.
TaprootRemoteCommitSpend StandardWitnessType = 23
// TaprootAnchorSweepSpend is the witness type we'll use for spending
// our own anchor output.
TaprootAnchorSweepSpend StandardWitnessType = 24
// TaprootHtlcOfferedTimeoutSecondLevel is a witness that allows us to
// timeout an HTLC we offered to the remote party on our commitment
// transaction. We use this when we need to go on chain to time out an
// HTLC.
TaprootHtlcOfferedTimeoutSecondLevel StandardWitnessType = 25
// TaprootHtlcAcceptedSuccessSecondLevel is a witness that allows us to
// sweep an HTLC we accepted on our commitment transaction after we go
// to the second level on chain.
TaprootHtlcAcceptedSuccessSecondLevel StandardWitnessType = 26
// TaprootHtlcSecondLevelRevoke is a witness that allows us to sweep an
// HTLC on the revoked transaction of the remote party that goes to the
// second level.
TaprootHtlcSecondLevelRevoke StandardWitnessType = 27
// TaprootHtlcAcceptedRevoke is a witness that allows us to sweep an
// HTLC sent to us by the remote party in the event that they broadcast
// a revoked state.
TaprootHtlcAcceptedRevoke StandardWitnessType = 28
// TaprootHtlcOfferedRevoke is a witness that allows us to sweep an
// HTLC we offered to the remote party if they broadcast a revoked
// commitment.
TaprootHtlcOfferedRevoke StandardWitnessType = 29
// TaprootHtlcOfferedRemoteTimeout is a witness that allows us to sweep
// an HTLC we offered to the remote party that lies on the commitment
// transaction for the remote party. We can spend this output after the
// absolute CLTV timeout of the HTLC as passed.
TaprootHtlcOfferedRemoteTimeout StandardWitnessType = 30
// TaprootHtlcLocalOfferedTimeout is a witness type that allows us to
// sign the second level HTLC timeout transaction when spending from an
// HTLC residing on our local commitment transaction.
//
// This is used by the sweeper to re-sign inputs if it needs to
// aggregate several second level HTLCs.
TaprootHtlcLocalOfferedTimeout StandardWitnessType = 31
// TaprootHtlcAcceptedRemoteSuccess is a witness that allows us to
// sweep an HTLC that was offered to us by the remote party for a
// taproot channels. We use this witness in the case that the remote
// party goes to chain, and we know the pre-image to the HTLC. We can
// sweep this without any additional timeout.
TaprootHtlcAcceptedRemoteSuccess StandardWitnessType = 32
// TaprootHtlcAcceptedLocalSuccess is a witness type that allows us to
// sweep the HTLC offered to us on our local commitment transaction.
// We'll use this when we need to go on chain to sweep the HTLC. In
// this case, this is the second level HTLC success transaction.
TaprootHtlcAcceptedLocalSuccess StandardWitnessType = 33
// TaprootCommitmentRevoke is a witness that allows us to sweep the
// settled output of a malicious counterparty's who broadcasts a
// revoked taproot commitment transaction.
TaprootCommitmentRevoke StandardWitnessType = 34
)
// String returns a human readable version of the target WitnessType.
//
// NOTE: This is part of the WitnessType interface.
func (wt StandardWitnessType) String() string {
switch wt {
case CommitmentTimeLock:
return "CommitmentTimeLock"
case CommitmentToRemoteConfirmed:
return "CommitmentToRemoteConfirmed"
case CommitmentAnchor:
return "CommitmentAnchor"
case CommitmentNoDelay:
return "CommitmentNoDelay"
case CommitSpendNoDelayTweakless:
return "CommitmentNoDelayTweakless"
case CommitmentRevoke:
return "CommitmentRevoke"
case HtlcOfferedRevoke:
return "HtlcOfferedRevoke"
case HtlcAcceptedRevoke:
return "HtlcAcceptedRevoke"
case HtlcOfferedTimeoutSecondLevel:
return "HtlcOfferedTimeoutSecondLevel"
case HtlcOfferedTimeoutSecondLevelInputConfirmed:
return "HtlcOfferedTimeoutSecondLevelInputConfirmed"
case HtlcAcceptedSuccessSecondLevel:
return "HtlcAcceptedSuccessSecondLevel"
case HtlcAcceptedSuccessSecondLevelInputConfirmed:
return "HtlcAcceptedSuccessSecondLevelInputConfirmed"
case HtlcOfferedRemoteTimeout:
return "HtlcOfferedRemoteTimeout"
case HtlcAcceptedRemoteSuccess:
return "HtlcAcceptedRemoteSuccess"
case HtlcSecondLevelRevoke:
return "HtlcSecondLevelRevoke"
case WitnessKeyHash:
return "WitnessKeyHash"
case NestedWitnessKeyHash:
return "NestedWitnessKeyHash"
case LeaseCommitmentTimeLock:
return "LeaseCommitmentTimeLock"
case LeaseCommitmentToRemoteConfirmed:
return "LeaseCommitmentToRemoteConfirmed"
case LeaseHtlcOfferedTimeoutSecondLevel:
return "LeaseHtlcOfferedTimeoutSecondLevel"
case LeaseHtlcAcceptedSuccessSecondLevel:
return "LeaseHtlcAcceptedSuccessSecondLevel"
case TaprootPubKeySpend:
return "TaprootPubKeySpend"
case TaprootLocalCommitSpend:
return "TaprootLocalCommitSpend"
case TaprootRemoteCommitSpend:
return "TaprootRemoteCommitSpend"
case TaprootAnchorSweepSpend:
return "TaprootAnchorSweepSpend"
case TaprootHtlcOfferedTimeoutSecondLevel:
return "TaprootHtlcOfferedTimeoutSecondLevel"
case TaprootHtlcAcceptedSuccessSecondLevel:
return "TaprootHtlcAcceptedSuccessSecondLevel"
case TaprootHtlcSecondLevelRevoke:
return "TaprootHtlcSecondLevelRevoke"
case TaprootHtlcAcceptedRevoke:
return "TaprootHtlcAcceptedRevoke"
case TaprootHtlcOfferedRevoke:
return "TaprootHtlcOfferedRevoke"
case TaprootHtlcOfferedRemoteTimeout:
return "TaprootHtlcOfferedRemoteTimeout"
case TaprootHtlcLocalOfferedTimeout:
return "TaprootHtlcLocalOfferedTimeout"
case TaprootHtlcAcceptedRemoteSuccess:
return "TaprootHtlcAcceptedRemoteSuccess"
case TaprootHtlcAcceptedLocalSuccess:
return "TaprootHtlcAcceptedLocalSuccess"
case TaprootCommitmentRevoke:
return "TaprootCommitmentRevoke"
default:
return fmt.Sprintf("Unknown WitnessType: %v", uint32(wt))
}
}
// WitnessGenerator will return a WitnessGenerator function that an output uses
// to generate the witness and optionally the sigScript for a sweep
// transaction. The sigScript will be generated if the witness type warrants
// one for spending, such as the NestedWitnessKeyHash witness type.
//
// NOTE: This is part of the WitnessType interface.
func (wt StandardWitnessType) WitnessGenerator(signer Signer,
descriptor *SignDescriptor) WitnessGenerator {
return func(tx *wire.MsgTx, hc *txscript.TxSigHashes,
inputIndex int) (*Script, error) {
// TODO(roasbeef): copy the desc?
desc := descriptor
desc.SigHashes = hc
desc.InputIndex = inputIndex
switch wt {
case CommitmentTimeLock, LeaseCommitmentTimeLock:
witness, err := CommitSpendTimeout(signer, desc, tx)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
case CommitmentToRemoteConfirmed, LeaseCommitmentToRemoteConfirmed:
witness, err := CommitSpendToRemoteConfirmed(
signer, desc, tx,
)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
case CommitmentAnchor:
witness, err := CommitSpendAnchor(signer, desc, tx)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
case CommitmentNoDelay:
witness, err := CommitSpendNoDelay(signer, desc, tx, false)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
case CommitSpendNoDelayTweakless:
witness, err := CommitSpendNoDelay(signer, desc, tx, true)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
case CommitmentRevoke:
witness, err := CommitSpendRevoke(signer, desc, tx)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
case HtlcOfferedRevoke:
witness, err := ReceiverHtlcSpendRevoke(signer, desc, tx)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
case HtlcAcceptedRevoke:
witness, err := SenderHtlcSpendRevoke(signer, desc, tx)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
case HtlcOfferedTimeoutSecondLevel,
LeaseHtlcOfferedTimeoutSecondLevel,
HtlcAcceptedSuccessSecondLevel,
LeaseHtlcAcceptedSuccessSecondLevel:
witness, err := HtlcSecondLevelSpend(signer, desc, tx)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
case HtlcOfferedRemoteTimeout:
// We pass in a value of -1 for the timeout, as we
// expect the caller to have already set the lock time
// value.
witness, err := ReceiverHtlcSpendTimeout(signer, desc, tx, -1)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
case HtlcSecondLevelRevoke:
witness, err := HtlcSpendRevoke(signer, desc, tx)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
case WitnessKeyHash:
fallthrough
case TaprootPubKeySpend:
fallthrough
case NestedWitnessKeyHash:
return signer.ComputeInputScript(tx, desc)
case TaprootLocalCommitSpend:
// Ensure that the sign desc has the proper sign method
// set, and a valid prev output fetcher.
desc.SignMethod = TaprootScriptSpendSignMethod
// The control block bytes must be set at this point.
if desc.ControlBlock == nil {
return nil, fmt.Errorf("control block must " +
"be set for taproot spend")
}
witness, err := TaprootCommitSpendSuccess(
signer, desc, tx, nil,
)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
case TaprootRemoteCommitSpend:
// Ensure that the sign desc has the proper sign method
// set, and a valid prev output fetcher.
desc.SignMethod = TaprootScriptSpendSignMethod
// The control block bytes must be set at this point.
if desc.ControlBlock == nil {
return nil, fmt.Errorf("control block must " +
"be set for taproot spend")
}
witness, err := TaprootCommitRemoteSpend(
signer, desc, tx, nil,
)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
case TaprootAnchorSweepSpend:
// Ensure that the sign desc has the proper sign method
// set, and a valid prev output fetcher.
desc.SignMethod = TaprootKeySpendSignMethod
// The tap tweak must be set at this point.
if desc.TapTweak == nil {
return nil, fmt.Errorf("tap tweak must be " +
"set for keyspend")
}
witness, err := TaprootAnchorSpend(
signer, desc, tx,
)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
case TaprootHtlcOfferedTimeoutSecondLevel,
TaprootHtlcAcceptedSuccessSecondLevel:
// Ensure that the sign desc has the proper sign method
// set, and a valid prev output fetcher.
desc.SignMethod = TaprootScriptSpendSignMethod
// The control block bytes must be set at this point.
if desc.ControlBlock == nil {
return nil, fmt.Errorf("control block must " +
"be set for taproot spend")
}
witness, err := TaprootHtlcSpendSuccess(
signer, desc, tx, nil, nil,
)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
case TaprootHtlcSecondLevelRevoke:
// Ensure that the sign desc has the proper sign method
// set, and a valid prev output fetcher.
desc.SignMethod = TaprootKeySpendSignMethod
// The tap tweak must be set at this point.
if desc.TapTweak == nil {
return nil, fmt.Errorf("tap tweak must be " +
"set for keyspend")
}
witness, err := TaprootHtlcSpendRevoke(
signer, desc, tx,
)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
case TaprootHtlcOfferedRevoke:
// Ensure that the sign desc has the proper sign method
// set, and a valid prev output fetcher.
desc.SignMethod = TaprootKeySpendSignMethod
// The tap tweak must be set at this point.
if desc.TapTweak == nil {
return nil, fmt.Errorf("tap tweak must be " +
"set for keyspend")
}
witness, err := SenderHTLCScriptTaprootRevoke(
signer, desc, tx,
)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
case TaprootHtlcAcceptedRevoke:
// Ensure that the sign desc has the proper sign method
// set, and a valid prev output fetcher.
desc.SignMethod = TaprootKeySpendSignMethod
// The tap tweak must be set at this point.
if desc.TapTweak == nil {
return nil, fmt.Errorf("tap tweak must be " +
"set for keyspend")
}
witness, err := ReceiverHTLCScriptTaprootRevoke(
signer, desc, tx,
)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
case TaprootHtlcOfferedRemoteTimeout:
// Ensure that the sign desc has the proper sign method
// set, and a valid prev output fetcher.
desc.SignMethod = TaprootScriptSpendSignMethod
// The control block bytes must be set at this point.
if desc.ControlBlock == nil {
return nil, fmt.Errorf("control block " +
"must be set for taproot spend")
}
witness, err := ReceiverHTLCScriptTaprootTimeout(
signer, desc, tx, -1, nil, nil,
)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
case TaprootCommitmentRevoke:
// Ensure that the sign desc has the proper sign method
// set, and a valid prev output fetcher.
desc.SignMethod = TaprootScriptSpendSignMethod
// The control block bytes must be set at this point.
if desc.ControlBlock == nil {
return nil, fmt.Errorf("control block " +
"must be set for taproot spend")
}
witness, err := TaprootCommitSpendRevoke(
signer, desc, tx, nil,
)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
default:
return nil, fmt.Errorf("unknown witness type: %v", wt)
}
}
}
// SizeUpperBound returns the maximum length of the witness of this witness
// type if it would be included in a tx. We also return if the output itself is
// a nested p2sh output, if so then we need to take into account the extra
// sigScript data size.
//
// NOTE: This is part of the WitnessType interface.
func (wt StandardWitnessType) SizeUpperBound() (lntypes.WeightUnit,
bool, error) {
switch wt {
// Outputs on a remote commitment transaction that pay directly to us.
case CommitSpendNoDelayTweakless:
fallthrough
case WitnessKeyHash:
fallthrough
case CommitmentNoDelay:
return P2WKHWitnessSize, false, nil
// Outputs on a past commitment transaction that pay directly
// to us.
case CommitmentTimeLock:
return ToLocalTimeoutWitnessSize, false, nil
case LeaseCommitmentTimeLock:
size := ToLocalTimeoutWitnessSize +
LeaseWitnessScriptSizeOverhead
return lntypes.WeightUnit(size), false, nil
// 1 CSV time locked output to us on remote commitment.
case CommitmentToRemoteConfirmed:
return ToRemoteConfirmedWitnessSize, false, nil
case LeaseCommitmentToRemoteConfirmed:
size := ToRemoteConfirmedWitnessSize +
LeaseWitnessScriptSizeOverhead
return lntypes.WeightUnit(size), false, nil
// Anchor output on the commitment transaction.
case CommitmentAnchor:
return AnchorWitnessSize, false, nil
// Outgoing second layer HTLC's that have confirmed within the
// chain, and the output they produced is now mature enough to
// sweep.
case HtlcOfferedTimeoutSecondLevel:
return ToLocalTimeoutWitnessSize, false, nil
case LeaseHtlcOfferedTimeoutSecondLevel:
size := ToLocalTimeoutWitnessSize +
LeaseWitnessScriptSizeOverhead
return lntypes.WeightUnit(size), false, nil
// Input to the outgoing HTLC second layer timeout transaction.
case HtlcOfferedTimeoutSecondLevelInputConfirmed:
return OfferedHtlcTimeoutWitnessSizeConfirmed, false, nil
// Incoming second layer HTLC's that have confirmed within the
// chain, and the output they produced is now mature enough to
// sweep.
case HtlcAcceptedSuccessSecondLevel:
return ToLocalTimeoutWitnessSize, false, nil
case LeaseHtlcAcceptedSuccessSecondLevel:
size := ToLocalTimeoutWitnessSize +
LeaseWitnessScriptSizeOverhead
return lntypes.WeightUnit(size), false, nil
// Input to the incoming second-layer HTLC success transaction.
case HtlcAcceptedSuccessSecondLevelInputConfirmed:
return AcceptedHtlcSuccessWitnessSizeConfirmed, false, nil
// An HTLC on the commitment transaction of the remote party,
// that has had its absolute timelock expire.
case HtlcOfferedRemoteTimeout:
return AcceptedHtlcTimeoutWitnessSize, false, nil
// An HTLC on the commitment transaction of the remote party,
// that can be swept with the preimage.
case HtlcAcceptedRemoteSuccess:
return OfferedHtlcSuccessWitnessSize, false, nil
// A nested P2SH input that has a p2wkh witness script. We'll mark this
// as nested P2SH so the caller can estimate the weight properly
// including the sigScript.
case NestedWitnessKeyHash:
return P2WKHWitnessSize, true, nil
// The revocation output on a revoked commitment transaction.
case CommitmentRevoke:
return ToLocalPenaltyWitnessSize, false, nil
// The revocation output on a revoked HTLC that we offered to the remote
// party.
case HtlcOfferedRevoke:
return OfferedHtlcPenaltyWitnessSize, false, nil
// The revocation output on a revoked HTLC that was sent to us.
case HtlcAcceptedRevoke:
return AcceptedHtlcPenaltyWitnessSize, false, nil
// The revocation output of a second level output of an HTLC.
case HtlcSecondLevelRevoke:
return ToLocalPenaltyWitnessSize, false, nil
case TaprootPubKeySpend:
return TaprootKeyPathCustomSighashWitnessSize, false, nil
// Sweeping a self output after a delay for taproot channels.
case TaprootLocalCommitSpend:
return TaprootToLocalWitnessSize, false, nil
// Sweeping a self output after the remote party fro ce closes. Must
// wait 1 CSV.
case TaprootRemoteCommitSpend:
return TaprootToRemoteWitnessSize, false, nil
// Sweeping our anchor output with a key spend witness.
case TaprootAnchorSweepSpend:
return TaprootAnchorWitnessSize, false, nil
case TaprootHtlcOfferedTimeoutSecondLevel,
TaprootHtlcAcceptedSuccessSecondLevel:
return TaprootSecondLevelHtlcWitnessSize, false, nil
case TaprootHtlcSecondLevelRevoke:
return TaprootSecondLevelRevokeWitnessSize, false, nil
case TaprootHtlcAcceptedRevoke:
return TaprootAcceptedRevokeWitnessSize, false, nil
case TaprootHtlcOfferedRevoke:
return TaprootOfferedRevokeWitnessSize, false, nil
case TaprootHtlcOfferedRemoteTimeout:
return TaprootHtlcOfferedRemoteTimeoutWitnessSize, false, nil
case TaprootHtlcLocalOfferedTimeout:
return TaprootOfferedLocalTimeoutWitnessSize, false, nil
case TaprootHtlcAcceptedRemoteSuccess:
return TaprootHtlcAcceptedRemoteSuccessWitnessSize, false, nil
case TaprootHtlcAcceptedLocalSuccess:
return TaprootHtlcAcceptedLocalSuccessWitnessSize, false, nil
case TaprootCommitmentRevoke:
return TaprootToLocalRevokeWitnessSize, false, nil
}
return 0, false, fmt.Errorf("unexpected witness type: %v", wt)
}
// AddWeightEstimation adds the estimated size of the witness in bytes to the
// given weight estimator.
//
// NOTE: This is part of the WitnessType interface.
func (wt StandardWitnessType) AddWeightEstimation(e *TxWeightEstimator) error {
// For fee estimation purposes, we'll now attempt to obtain an
// upper bound on the weight this input will add when fully
// populated.
size, isNestedP2SH, err := wt.SizeUpperBound()
if err != nil {
return err
}
// If this is a nested P2SH input, then we'll need to factor in
// the additional data push within the sigScript.
if isNestedP2SH {
e.AddNestedP2WSHInput(size)
} else {
e.AddWitnessInput(size)
}
return nil
}
package lnd
import (
"errors"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// ErrCannotResume is returned when an intercepted forward cannot be
// resumed. This is the case in the on-chain resolution flow.
ErrCannotResume = errors.New("cannot resume in the on-chain flow")
// ErrCannotFail is returned when an intercepted forward cannot be failed.
// This is the case in the on-chain resolution flow.
ErrCannotFail = errors.New("cannot fail in the on-chain flow")
// ErrPreimageMismatch is returned when the preimage that is specified to
// settle an htlc doesn't match the htlc hash.
ErrPreimageMismatch = errors.New("preimage does not match hash")
)
// interceptedForward implements the on-chain behavior for the resolution of
// a forwarded htlc.
type interceptedForward struct {
packet *htlcswitch.InterceptedPacket
beacon *preimageBeacon
}
func newInterceptedForward(
packet *htlcswitch.InterceptedPacket,
beacon *preimageBeacon) *interceptedForward {
return &interceptedForward{
beacon: beacon,
packet: packet,
}
}
// Packet returns the intercepted htlc packet.
func (f *interceptedForward) Packet() htlcswitch.InterceptedPacket {
return *f.packet
}
// Resume notifies the intention to resume an existing hold forward. This
// basically means the caller wants to resume with the default behavior for this
// htlc which usually means forward it.
func (f *interceptedForward) Resume() error {
return ErrCannotResume
}
// ResumeModified notifies the intention to resume an existing hold forward with
// a modified htlc.
func (f *interceptedForward) ResumeModified(_, _ fn.Option[lnwire.MilliSatoshi],
_ fn.Option[lnwire.CustomRecords]) error {
return ErrCannotResume
}
// Fail notifies the intention to fail an existing hold forward with an
// encrypted failure reason.
func (f *interceptedForward) Fail(_ []byte) error {
// We can't actively fail an htlc. The best we could do is abandon the
// resolver, but this wouldn't be a safe operation. There may be a race
// with the preimage beacon supplying a preimage. Therefore we don't
// attempt to fail and just return an error here.
return ErrCannotFail
}
// FailWithCode notifies the intention to fail an existing hold forward with the
// specified failure code.
func (f *interceptedForward) FailWithCode(_ lnwire.FailCode) error {
return ErrCannotFail
}
// Settle notifies the intention to settle an existing hold forward with a given
// preimage.
func (f *interceptedForward) Settle(preimage lntypes.Preimage) error {
if !preimage.Matches(f.packet.Hash) {
return ErrPreimageMismatch
}
// Add preimage to the preimage beacon. The onchain resolver will pick
// up the preimage from the beacon.
return f.beacon.AddPreimages(preimage)
}
// Copyright (c) 2013-2022 The btcsuite developers
package musig2v040
import (
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
)
var (
// ErrSignersNotSpecified is returned when a caller attempts to create
// a context without specifying either the total number of signers, or
// the complete set of singers.
ErrSignersNotSpecified = fmt.Errorf("total number of signers or all " +
"signers must be known")
// ErrSignerNotInKeySet is returned when a the private key for a signer
// isn't included in the set of signing public keys.
ErrSignerNotInKeySet = fmt.Errorf("signing key is not found in key" +
" set")
// ErrAlredyHaveAllNonces is called when RegisterPubNonce is called too
// many times for a given signing session.
ErrAlredyHaveAllNonces = fmt.Errorf("already have all nonces")
// ErrNotEnoughSigners is returned when a caller attempts to create a
// session from a context, but before all the required signers are
// known.
ErrNotEnoughSigners = fmt.Errorf("not enough signers")
// ErrAlredyHaveAllNonces is returned when a caller attempts to
// register a signer, once we already have the total set of known
// signers.
ErrAlreadyHaveAllSigners = fmt.Errorf("all signers registered")
// ErrAlredyHaveAllSigs is called when CombineSig is called too many
// times for a given signing session.
ErrAlredyHaveAllSigs = fmt.Errorf("already have all sigs")
// ErrSigningContextReuse is returned if a user attempts to sign using
// the same signing context more than once.
ErrSigningContextReuse = fmt.Errorf("nonce already used")
// ErrFinalSigInvalid is returned when the combined signature turns out
// to be invalid.
ErrFinalSigInvalid = fmt.Errorf("final signature is invalid")
// ErrCombinedNonceUnavailable is returned when a caller attempts to
// sign a partial signature, without first having collected all the
// required combined nonces.
ErrCombinedNonceUnavailable = fmt.Errorf("missing combined nonce")
// ErrTaprootInternalKeyUnavailable is returned when a user attempts to
// obtain the
ErrTaprootInternalKeyUnavailable = fmt.Errorf("taproot tweak not used")
// ErrNotEnoughSigners is returned if a caller attempts to obtain an
// early nonce when it wasn't specified
ErrNoEarlyNonce = fmt.Errorf("no early nonce available")
)
// Context is a managed signing context for musig2. It takes care of things
// like securely generating secret nonces, aggregating keys and nonces, etc.
type Context struct {
// signingKey is the key we'll use for signing.
signingKey *btcec.PrivateKey
// pubKey is our even-y coordinate public key.
pubKey *btcec.PublicKey
// combinedKey is the aggregated public key.
combinedKey *AggregateKey
// uniqueKeyIndex is the index of the second unique key in the keySet.
// This is used to speed up signing and verification computations.
uniqueKeyIndex int
// keysHash is the hash of all the keys as defined in musig2.
keysHash []byte
// opts is the set of options for the context.
opts *contextOptions
// shouldSort keeps track of if the public keys should be sorted before
// any operations.
shouldSort bool
// sessionNonce will be populated if the earlyNonce option is true.
// After the first session is created, this nonce will be blanked out.
sessionNonce *Nonces
}
// ContextOption is a functional option argument that allows callers to modify
// the musig2 signing is done within a context.
type ContextOption func(*contextOptions)
// contextOptions houses the set of functional options that can be used to
// musig2 signing protocol.
type contextOptions struct {
// tweaks is the set of optinoal tweaks to apply to the combined public
// key.
tweaks []KeyTweakDesc
// taprootTweak specifies the taproot tweak. If specified, then we'll
// use this as the script root for the BIP 341 taproot (x-only) tweak.
// Normally we'd just apply the raw 32 byte tweak, but for taproot, we
// first need to compute the aggregated key before tweaking, and then
// use it as the internal key. This is required as the taproot tweak
// also commits to the public key, which in this case is the aggregated
// key before the tweak.
taprootTweak []byte
// bip86Tweak if true, then the weak will just be
// h_tapTweak(internalKey) as there is no true script root.
bip86Tweak bool
// keySet is the complete set of signers for this context.
keySet []*btcec.PublicKey
// numSigners is the total number of signers that will eventually be a
// part of the context.
numSigners int
// earlyNonce determines if a nonce should be generated during context
// creation, to be automatically passed to the created session.
earlyNonce bool
}
// defaultContextOptions returns the default context options.
func defaultContextOptions() *contextOptions {
return &contextOptions{}
}
// WithTweakedContext specifies that within the context, the aggregated public
// key should be tweaked with the specified tweaks.
func WithTweakedContext(tweaks ...KeyTweakDesc) ContextOption {
return func(o *contextOptions) {
o.tweaks = tweaks
}
}
// WithTaprootTweakCtx specifies that within this context, the final key should
// use the taproot tweak as defined in BIP 341: outputKey = internalKey +
// h_tapTweak(internalKey || scriptRoot). In this case, the aggreaged key
// before the tweak will be used as the internal key.
func WithTaprootTweakCtx(scriptRoot []byte) ContextOption {
return func(o *contextOptions) {
o.taprootTweak = scriptRoot
}
}
// WithBip86TweakCtx specifies that within this context, the final key should
// use the taproot tweak as defined in BIP 341, with the BIP 86 modification:
// outputKey = internalKey + h_tapTweak(internalKey)*G. In this case, the
// aggreaged key before the tweak will be used as the internal key.
func WithBip86TweakCtx() ContextOption {
return func(o *contextOptions) {
o.bip86Tweak = true
}
}
// WithKnownSigners is an optional parameter that should be used if a session
// can be created as soon as all the singers are known.
func WithKnownSigners(signers []*btcec.PublicKey) ContextOption {
return func(o *contextOptions) {
o.keySet = signers
o.numSigners = len(signers)
}
}
// WithNumSigners is a functional option used to specify that a context should
// be created without knowing all the signers. Instead the total number of
// signers is specified to ensure that a session can only be created once all
// the signers are known.
//
// NOTE: Either WithKnownSigners or WithNumSigners MUST be specified.
func WithNumSigners(n int) ContextOption {
return func(o *contextOptions) {
o.numSigners = n
}
}
// WithEarlyNonceGen allow a caller to specify that a nonce should be generated
// early, before the session is created. This should be used in protocols that
// require some partial nonce exchange before all the signers are known.
//
// NOTE: This option must only be specified with the WithNumSigners option.
func WithEarlyNonceGen() ContextOption {
return func(o *contextOptions) {
o.earlyNonce = true
}
}
// NewContext creates a new signing context with the passed singing key and set
// of public keys for each of the other signers.
//
// NOTE: This struct should be used over the raw Sign API whenever possible.
func NewContext(signingKey *btcec.PrivateKey, shouldSort bool,
ctxOpts ...ContextOption) (*Context, error) {
// First, parse the set of optional context options.
opts := defaultContextOptions()
for _, option := range ctxOpts {
option(opts)
}
pubKey, err := schnorr.ParsePubKey(
schnorr.SerializePubKey(signingKey.PubKey()),
)
if err != nil {
return nil, err
}
ctx := &Context{
signingKey: signingKey,
pubKey: pubKey,
opts: opts,
shouldSort: shouldSort,
}
switch {
// We know all the signers, so we can compute the aggregated key, along
// with all the other intermediate state we need to do signing and
// verification.
case opts.keySet != nil:
if err := ctx.combineSignerKeys(); err != nil {
return nil, err
}
// The total signers are known, so we add ourselves, and skip key
// aggregation.
case opts.numSigners != 0:
// Otherwise, we'll add ourselves as the only known signer, and
// await further calls to RegisterSigner before a session can
// be created.
opts.keySet = make([]*btcec.PublicKey, 0, opts.numSigners)
opts.keySet = append(opts.keySet, pubKey)
// If early nonce generation is specified, then we'll generate
// the nonce now to pass in to the session once all the callers
// are known.
if opts.earlyNonce {
ctx.sessionNonce, err = GenNonces()
if err != nil {
return nil, err
}
}
default:
return nil, ErrSignersNotSpecified
}
return ctx, nil
}
// combineSignerKeys is used to compute the aggregated signer key once all the
// signers are known.
func (c *Context) combineSignerKeys() error {
// As a sanity check, make sure the signing key is actually
// amongst the sit of signers.
var keyFound bool
for _, key := range c.opts.keySet {
if key.IsEqual(c.pubKey) {
keyFound = true
break
}
}
if !keyFound {
return ErrSignerNotInKeySet
}
// Now that we know that we're actually a signer, we'll
// generate the key hash finger print and second unique key
// index so we can speed up signing later.
c.keysHash = keyHashFingerprint(c.opts.keySet, c.shouldSort)
c.uniqueKeyIndex = secondUniqueKeyIndex(
c.opts.keySet, c.shouldSort,
)
keyAggOpts := []KeyAggOption{
WithKeysHash(c.keysHash),
WithUniqueKeyIndex(c.uniqueKeyIndex),
}
switch {
case c.opts.bip86Tweak:
keyAggOpts = append(
keyAggOpts, WithBIP86KeyTweak(),
)
case c.opts.taprootTweak != nil:
keyAggOpts = append(
keyAggOpts, WithTaprootKeyTweak(c.opts.taprootTweak),
)
case len(c.opts.tweaks) != 0:
keyAggOpts = append(keyAggOpts, WithKeyTweaks(c.opts.tweaks...))
}
// Next, we'll use this information to compute the aggregated
// public key that'll be used for signing in practice.
var err error
c.combinedKey, _, _, err = AggregateKeys(
c.opts.keySet, c.shouldSort, keyAggOpts...,
)
if err != nil {
return err
}
return nil
}
// EarlySessionNonce returns the early session nonce, if available.
func (c *Context) EarlySessionNonce() (*Nonces, error) {
if c.sessionNonce == nil {
return nil, ErrNoEarlyNonce
}
return c.sessionNonce, nil
}
// RegisterSigner allows a caller to register a signer after the context has
// been created. This will be used in scenarios where the total number of
// signers is known, but nonce exchange needs to happen before all the signers
// are known.
//
// A bool is returned which indicates if all the signers have been registered.
//
// NOTE: If the set of keys are not to be sorted during signing, then the
// ordering each key is registered with MUST match the desired ordering.
func (c *Context) RegisterSigner(pub *btcec.PublicKey) (bool, error) {
haveAllSigners := len(c.opts.keySet) == c.opts.numSigners
if haveAllSigners {
return false, ErrAlreadyHaveAllSigners
}
c.opts.keySet = append(c.opts.keySet, pub)
// If we have the expected number of signers at this point, then we can
// generate the aggregated key and other necessary information.
haveAllSigners = len(c.opts.keySet) == c.opts.numSigners
if haveAllSigners {
if err := c.combineSignerKeys(); err != nil {
return false, err
}
}
return haveAllSigners, nil
}
// NumRegisteredSigners returns the total number of registered signers.
func (c *Context) NumRegisteredSigners() int {
return len(c.opts.keySet)
}
// CombinedKey returns the combined public key that will be used to generate
// multi-signatures against.
func (c *Context) CombinedKey() (*btcec.PublicKey, error) {
// If the caller hasn't registered all the signers at this point, then
// the combined key won't be available.
if c.combinedKey == nil {
return nil, ErrNotEnoughSigners
}
return c.combinedKey.FinalKey, nil
}
// PubKey returns the public key of the signer of this session.
func (c *Context) PubKey() btcec.PublicKey {
return *c.pubKey
}
// SigningKeys returns the set of keys used for signing.
func (c *Context) SigningKeys() []*btcec.PublicKey {
keys := make([]*btcec.PublicKey, len(c.opts.keySet))
copy(keys, c.opts.keySet)
return keys
}
// TaprootInternalKey returns the internal taproot key, which is the aggregated
// key _before_ the tweak is applied. If a taproot tweak was specified, then
// CombinedKey() will return the fully tweaked output key, with this method
// returning the internal key. If a taproot tweak wasn't specified, then this
// method will return an error.
func (c *Context) TaprootInternalKey() (*btcec.PublicKey, error) {
// If the caller hasn't registered all the signers at this point, then
// the combined key won't be available.
if c.combinedKey == nil {
return nil, ErrNotEnoughSigners
}
if c.opts.taprootTweak == nil && !c.opts.bip86Tweak {
return nil, ErrTaprootInternalKeyUnavailable
}
return c.combinedKey.PreTweakedKey, nil
}
// SessionOption is a functional option argument that allows callers to modify
// the musig2 signing is done within a session.
type SessionOption func(*sessionOptions)
// sessionOptions houses the set of functional options that can be used to
// modify the musig2 signing protocol.
type sessionOptions struct {
externalNonce *Nonces
}
// defaultSessionOptions returns the default session options.
func defaultSessionOptions() *sessionOptions {
return &sessionOptions{}
}
// WithPreGeneratedNonce allows a caller to start a session using a nonce
// they've generated themselves. This may be useful in protocols where all the
// signer keys may not be known before nonce exchange needs to occur.
func WithPreGeneratedNonce(nonce *Nonces) SessionOption {
return func(o *sessionOptions) {
o.externalNonce = nonce
}
}
// Session represents a musig2 signing session. A new instance should be
// created each time a multi-signature is needed. The session struct handles
// nonces management, incremental partial sig vitrifaction, as well as final
// signature combination. Errors are returned when unsafe behavior such as
// nonce re-use is attempted.
//
// NOTE: This struct should be used over the raw Sign API whenever possible.
type Session struct {
opts *sessionOptions
ctx *Context
localNonces *Nonces
pubNonces [][PubNonceSize]byte
combinedNonce *[PubNonceSize]byte
msg [32]byte
ourSig *PartialSignature
sigs []*PartialSignature
finalSig *schnorr.Signature
}
// NewSession creates a new musig2 signing session.
func (c *Context) NewSession(options ...SessionOption) (*Session, error) {
opts := defaultSessionOptions()
for _, opt := range options {
opt(opts)
}
// At this point we verify that we know of all the signers, as
// otherwise we can't proceed with the session. This check is intended
// to catch misuse of the API wherein a caller forgets to register the
// remaining signers if they're doing nonce generation ahead of time.
if len(c.opts.keySet) != c.opts.numSigners {
return nil, ErrNotEnoughSigners
}
// If an early nonce was specified, then we'll automatically add the
// corresponding session option for the caller.
var localNonces *Nonces
if c.sessionNonce != nil {
// Apply the early nonce to the session, and also blank out the
// session nonce on the context to ensure it isn't ever re-used
// for another session.
localNonces = c.sessionNonce
c.sessionNonce = nil
} else if opts.externalNonce != nil {
// Otherwise if there's a custom nonce passed in via the
// session options, then use that instead.
localNonces = opts.externalNonce
}
// Now that we know we have enough signers, we'll either use the caller
// specified nonce, or generate a fresh set.
var err error
if localNonces == nil {
// At this point we need to generate a fresh nonce. We'll pass
// in some auxiliary information to strengthen the nonce
// generated.
localNonces, err = GenNonces(
WithNonceSecretKeyAux(c.signingKey),
WithNonceCombinedKeyAux(c.combinedKey.FinalKey),
)
if err != nil {
return nil, err
}
}
s := &Session{
opts: opts,
ctx: c,
localNonces: localNonces,
pubNonces: make([][PubNonceSize]byte, 0, c.opts.numSigners),
sigs: make([]*PartialSignature, 0, c.opts.numSigners),
}
s.pubNonces = append(s.pubNonces, localNonces.PubNonce)
return s, nil
}
// PublicNonce returns the public nonce for a signer. This should be sent to
// other parties before signing begins, so they can compute the aggregated
// public nonce.
func (s *Session) PublicNonce() [PubNonceSize]byte {
return s.localNonces.PubNonce
}
// NumRegisteredNonces returns the total number of nonces that have been
// regsitered so far.
func (s *Session) NumRegisteredNonces() int {
return len(s.pubNonces)
}
// RegisterPubNonce should be called for each public nonce from the set of
// signers. This method returns true once all the public nonces have been
// accounted for.
func (s *Session) RegisterPubNonce(nonce [PubNonceSize]byte) (bool, error) {
// If we already have all the nonces, then this method was called too
// many times.
haveAllNonces := len(s.pubNonces) == s.ctx.opts.numSigners
if haveAllNonces {
return false, ErrAlredyHaveAllNonces
}
// Add this nonce and check again if we already have tall the nonces we
// need.
s.pubNonces = append(s.pubNonces, nonce)
haveAllNonces = len(s.pubNonces) == s.ctx.opts.numSigners
// If we have all the nonces, then we can go ahead and combine them
// now.
if haveAllNonces {
combinedNonce, err := AggregateNonces(s.pubNonces)
if err != nil {
return false, err
}
s.combinedNonce = &combinedNonce
}
return haveAllNonces, nil
}
// Sign generates a partial signature for the target message, using the target
// context. If this method is called more than once per context, then an error
// is returned, as that means a nonce was re-used.
func (s *Session) Sign(msg [32]byte,
signOpts ...SignOption) (*PartialSignature, error) {
switch {
// If no local nonce is present, then this means we already signed, so
// we'll return an error to prevent nonce re-use.
case s.localNonces == nil:
return nil, ErrSigningContextReuse
// We also need to make sure we have the combined nonce, otherwise this
// function was called too early.
case s.combinedNonce == nil:
return nil, ErrCombinedNonceUnavailable
}
switch {
case s.ctx.opts.bip86Tweak:
signOpts = append(
signOpts, WithBip86SignTweak(),
)
case s.ctx.opts.taprootTweak != nil:
signOpts = append(
signOpts, WithTaprootSignTweak(s.ctx.opts.taprootTweak),
)
case len(s.ctx.opts.tweaks) != 0:
signOpts = append(signOpts, WithTweaks(s.ctx.opts.tweaks...))
}
partialSig, err := Sign(
s.localNonces.SecNonce, s.ctx.signingKey, *s.combinedNonce,
s.ctx.opts.keySet, msg, signOpts...,
)
// Now that we've generated our signature, we'll make sure to blank out
// our signing nonce.
s.localNonces = nil
if err != nil {
return nil, err
}
s.msg = msg
s.ourSig = partialSig
s.sigs = append(s.sigs, partialSig)
return partialSig, nil
}
// CombineSig buffers a partial signature received from a signing party. The
// method returns true once all the signatures are available, and can be
// combined into the final signature.
func (s *Session) CombineSig(sig *PartialSignature) (bool, error) {
// First check if we already have all the signatures we need. We
// already accumulated our own signature when we generated the sig.
haveAllSigs := len(s.sigs) == len(s.ctx.opts.keySet)
if haveAllSigs {
return false, ErrAlredyHaveAllSigs
}
// TODO(roasbeef): incremental check for invalid sig, or just detect at
// the very end?
// Accumulate this sig, and check again if we have all the sigs we
// need.
s.sigs = append(s.sigs, sig)
haveAllSigs = len(s.sigs) == len(s.ctx.opts.keySet)
// If we have all the signatures, then we can combine them all into the
// final signature.
if haveAllSigs {
var combineOpts []CombineOption
switch {
case s.ctx.opts.bip86Tweak:
combineOpts = append(
combineOpts, WithBip86TweakedCombine(
s.msg, s.ctx.opts.keySet,
s.ctx.shouldSort,
),
)
case s.ctx.opts.taprootTweak != nil:
combineOpts = append(
combineOpts, WithTaprootTweakedCombine(
s.msg, s.ctx.opts.keySet,
s.ctx.opts.taprootTweak, s.ctx.shouldSort,
),
)
case len(s.ctx.opts.tweaks) != 0:
combineOpts = append(
combineOpts, WithTweakedCombine(
s.msg, s.ctx.opts.keySet,
s.ctx.opts.tweaks, s.ctx.shouldSort,
),
)
}
finalSig := CombineSigs(s.ourSig.R, s.sigs, combineOpts...)
// We'll also verify the signature at this point to ensure it's
// valid.
//
// TODO(roasbef): allow skipping?
if !finalSig.Verify(s.msg[:], s.ctx.combinedKey.FinalKey) {
return false, ErrFinalSigInvalid
}
s.finalSig = finalSig
}
return haveAllSigs, nil
}
// FinalSig returns the final combined multi-signature, if present.
func (s *Session) FinalSig() *schnorr.Signature {
return s.finalSig
}
// Copyright 2013-2022 The btcsuite developers
package musig2v040
import (
"bytes"
"fmt"
"sort"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/chaincfg/chainhash"
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
)
var (
// KeyAggTagList is the tagged hash tag used to compute the hash of the
// list of sorted public keys.
KeyAggTagList = []byte("KeyAgg list")
// KeyAggTagCoeff is the tagged hash tag used to compute the key
// aggregation coefficient for each key.
KeyAggTagCoeff = []byte("KeyAgg coefficient")
// ErrTweakedKeyIsInfinity is returned if while tweaking a key, we end
// up with the point at infinity.
ErrTweakedKeyIsInfinity = fmt.Errorf("tweaked key is infinity point")
// ErrTweakedKeyOverflows is returned if a tweaking key is larger than
// 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141.
ErrTweakedKeyOverflows = fmt.Errorf("tweaked key is to large")
)
// sortableKeys defines a type of slice of public keys that implements the sort
// interface for BIP 340 keys.
type sortableKeys []*btcec.PublicKey
// Less reports whether the element with index i must sort before the element
// with index j.
func (s sortableKeys) Less(i, j int) bool {
// TODO(roasbeef): more efficient way to compare...
keyIBytes := schnorr.SerializePubKey(s[i])
keyJBytes := schnorr.SerializePubKey(s[j])
return bytes.Compare(keyIBytes, keyJBytes) == -1
}
// Swap swaps the elements with indexes i and j.
func (s sortableKeys) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Len is the number of elements in the collection.
func (s sortableKeys) Len() int {
return len(s)
}
// sortKeys takes a set of schnorr public keys and returns a new slice that is
// a copy of the keys sorted in lexicographical order bytes on the x-only
// pubkey serialization.
func sortKeys(keys []*btcec.PublicKey) []*btcec.PublicKey {
keySet := sortableKeys(keys)
if sort.IsSorted(keySet) {
return keys
}
sort.Sort(keySet)
return keySet
}
// keyHashFingerprint computes the tagged hash of the series of (sorted) public
// keys passed as input. This is used to compute the aggregation coefficient
// for each key. The final computation is:
// - H(tag=KeyAgg list, pk1 || pk2..)
func keyHashFingerprint(keys []*btcec.PublicKey, sort bool) []byte {
if sort {
keys = sortKeys(keys)
}
// We'll create a single buffer and slice into that so the bytes buffer
// doesn't continually need to grow the underlying buffer.
keyAggBuf := make([]byte, 32*len(keys))
keyBytes := bytes.NewBuffer(keyAggBuf[0:0])
for _, key := range keys {
keyBytes.Write(schnorr.SerializePubKey(key))
}
h := chainhash.TaggedHash(KeyAggTagList, keyBytes.Bytes())
return h[:]
}
// keyBytesEqual returns true if two keys are the same from the PoV of BIP
// 340's 32-byte x-only public keys.
func keyBytesEqual(a, b *btcec.PublicKey) bool {
return bytes.Equal(
schnorr.SerializePubKey(a),
schnorr.SerializePubKey(b),
)
}
// aggregationCoefficient computes the key aggregation coefficient for the
// specified target key. The coefficient is computed as:
// - H(tag=KeyAgg coefficient, keyHashFingerprint(pks) || pk)
func aggregationCoefficient(keySet []*btcec.PublicKey,
targetKey *btcec.PublicKey, keysHash []byte,
secondKeyIdx int) *btcec.ModNScalar {
var mu btcec.ModNScalar
// If this is the second key, then this coefficient is just one.
if secondKeyIdx != -1 && keyBytesEqual(keySet[secondKeyIdx], targetKey) {
return mu.SetInt(1)
}
// Otherwise, we'll compute the full finger print hash for this given
// key and then use that to compute the coefficient tagged hash:
// * H(tag=KeyAgg coefficient, keyHashFingerprint(pks, pk) || pk)
var coefficientBytes [64]byte
copy(coefficientBytes[:], keysHash[:])
copy(coefficientBytes[32:], schnorr.SerializePubKey(targetKey))
muHash := chainhash.TaggedHash(KeyAggTagCoeff, coefficientBytes[:])
mu.SetByteSlice(muHash[:])
return &mu
}
// secondUniqueKeyIndex returns the index of the second unique key. If all keys
// are the same, then a value of -1 is returned.
func secondUniqueKeyIndex(keySet []*btcec.PublicKey, sort bool) int {
if sort {
keySet = sortKeys(keySet)
}
// Find the first key that isn't the same as the very first key (second
// unique key).
for i := range keySet {
if !keyBytesEqual(keySet[i], keySet[0]) {
return i
}
}
// A value of negative one is used to indicate that all the keys in the
// sign set are actually equal, which in practice actually makes musig2
// useless, but we need a value to distinguish this case.
return -1
}
// KeyTweakDesc describes a tweak to be applied to the aggregated public key
// generation and signing process. The IsXOnly specifies if the target key
// should be converted to an x-only public key before tweaking.
type KeyTweakDesc struct {
// Tweak is the 32-byte value that will modify the public key.
Tweak [32]byte
// IsXOnly if true, then the public key will be mapped to an x-only key
// before the tweaking operation is applied.
IsXOnly bool
}
// KeyAggOption is a functional option argument that allows callers to specify
// more or less information that has been pre-computed to the main routine.
type KeyAggOption func(*keyAggOption)
// keyAggOption houses the set of functional options that modify key
// aggregation.
type keyAggOption struct {
// keyHash is the output of keyHashFingerprint for a given set of keys.
keyHash []byte
// uniqueKeyIndex is the pre-computed index of the second unique key.
uniqueKeyIndex *int
// tweaks specifies a series of tweaks to be applied to the aggregated
// public key.
tweaks []KeyTweakDesc
// taprootTweak controls if the tweaks above should be applied in a BIP
// 340 style.
taprootTweak bool
// bip86Tweak specifies that the taproot tweak should be done in a BIP
// 86 style, where we don't expect an actual tweak and instead just
// commit to the public key itself.
bip86Tweak bool
}
// WithKeysHash allows key aggregation to be optimize, by allowing the caller
// to specify the hash of all the keys.
func WithKeysHash(keyHash []byte) KeyAggOption {
return func(o *keyAggOption) {
o.keyHash = keyHash
}
}
// WithUniqueKeyIndex allows the caller to specify the index of the second
// unique key.
func WithUniqueKeyIndex(idx int) KeyAggOption {
return func(o *keyAggOption) {
i := idx
o.uniqueKeyIndex = &i
}
}
// WithKeyTweaks allows a caller to specify a series of 32-byte tweaks that
// should be applied to the final aggregated public key.
func WithKeyTweaks(tweaks ...KeyTweakDesc) KeyAggOption {
return func(o *keyAggOption) {
o.tweaks = tweaks
}
}
// WithTaprootKeyTweak specifies that within this context, the final key should
// use the taproot tweak as defined in BIP 341: outputKey = internalKey +
// h_tapTweak(internalKey || scriptRoot). In this case, the aggregated key
// before the tweak will be used as the internal key.
//
// This option should be used instead of WithKeyTweaks when the aggregated key
// is intended to be used as a taproot output key that commits to a script
// root.
func WithTaprootKeyTweak(scriptRoot []byte) KeyAggOption {
return func(o *keyAggOption) {
var tweak [32]byte
copy(tweak[:], scriptRoot[:])
o.tweaks = []KeyTweakDesc{
{
Tweak: tweak,
IsXOnly: true,
},
}
o.taprootTweak = true
}
}
// WithBIP86KeyTweak specifies that then during key aggregation, the BIP 86
// tweak which just commits to the hash of the serialized public key should be
// used. This option should be used when signing with a key that was derived
// using BIP 86.
func WithBIP86KeyTweak() KeyAggOption {
return func(o *keyAggOption) {
o.tweaks = []KeyTweakDesc{
{
IsXOnly: true,
},
}
o.taprootTweak = true
o.bip86Tweak = true
}
}
// defaultKeyAggOptions returns the set of default arguments for key
// aggregation.
func defaultKeyAggOptions() *keyAggOption {
return &keyAggOption{}
}
// hasEvenY returns true if the affine representation of the passed jacobian
// point has an even y coordinate.
//
// TODO(roasbeef): double check, can just check the y coord even not jacobian?
func hasEvenY(pJ btcec.JacobianPoint) bool {
pJ.ToAffine()
p := btcec.NewPublicKey(&pJ.X, &pJ.Y)
keyBytes := p.SerializeCompressed()
return keyBytes[0] == secp.PubKeyFormatCompressedEven
}
// tweakKey applies a tweaks to the passed public key using the specified
// tweak. The parityAcc and tweakAcc are returned (in that order) which
// includes the accumulate ration of the parity factor and the tweak multiplied
// by the parity factor. The xOnly bool specifies if this is to be an x-only
// tweak or not.
func tweakKey(keyJ btcec.JacobianPoint, parityAcc btcec.ModNScalar, tweak [32]byte,
tweakAcc btcec.ModNScalar,
xOnly bool) (btcec.JacobianPoint, btcec.ModNScalar, btcec.ModNScalar, error) {
// First we'll compute the new parity factor for this key. If the key has
// an odd y coordinate (not even), then we'll need to negate it (multiply
// by -1 mod n, in this case).
var parityFactor btcec.ModNScalar
if xOnly && !hasEvenY(keyJ) {
parityFactor.SetInt(1).Negate()
} else {
parityFactor.SetInt(1)
}
// Next, map the tweak into a mod n integer so we can use it for
// manipulations below.
tweakInt := new(btcec.ModNScalar)
overflows := tweakInt.SetBytes(&tweak)
if overflows == 1 {
return keyJ, parityAcc, tweakAcc, ErrTweakedKeyOverflows
}
// Next, we'll compute: Q_i = g*Q + t*G, where g is our parityFactor and t
// is the tweakInt above. We'll space things out a bit to make it easier to
// follow.
//
// First compute t*G:
var tweakedGenerator btcec.JacobianPoint
btcec.ScalarBaseMultNonConst(tweakInt, &tweakedGenerator)
// Next compute g*Q:
btcec.ScalarMultNonConst(&parityFactor, &keyJ, &keyJ)
// Finally add both of them together to get our final
// tweaked point.
btcec.AddNonConst(&tweakedGenerator, &keyJ, &keyJ)
// As a sanity check, make sure that we didn't just end up with the
// point at infinity.
if keyJ == infinityPoint {
return keyJ, parityAcc, tweakAcc, ErrTweakedKeyIsInfinity
}
// As a final wrap up step, we'll accumulate the parity
// factor and also this tweak into the final set of accumulators.
parityAcc.Mul(&parityFactor)
tweakAcc.Mul(&parityFactor).Add(tweakInt)
return keyJ, parityAcc, tweakAcc, nil
}
// AggregateKey is a final aggregated key along with a possible version of the
// key without any tweaks applied.
type AggregateKey struct {
// FinalKey is the final aggregated key which may include one or more
// tweaks applied to it.
FinalKey *btcec.PublicKey
// PreTweakedKey is the aggregated *before* any tweaks have been
// applied. This should be used as the internal key in taproot
// contexts.
PreTweakedKey *btcec.PublicKey
}
// AggregateKeys takes a list of possibly unsorted keys and returns a single
// aggregated key as specified by the musig2 key aggregation algorithm. A nil
// value can be passed for keyHash, which causes this function to re-derive it.
// In addition to the combined public key, the parity accumulator and the tweak
// accumulator are returned as well.
func AggregateKeys(keys []*btcec.PublicKey, sort bool,
keyOpts ...KeyAggOption) (
*AggregateKey, *btcec.ModNScalar, *btcec.ModNScalar, error) {
// First, parse the set of optional signing options.
opts := defaultKeyAggOptions()
for _, option := range keyOpts {
option(opts)
}
// Sort the set of public key so we know we're working with them in
// sorted order for all the routines below.
if sort {
keys = sortKeys(keys)
}
// The caller may provide the hash of all the keys as an optimization
// during signing, as it already needs to be computed.
if opts.keyHash == nil {
opts.keyHash = keyHashFingerprint(keys, sort)
}
// A caller may also specify the unique key index themselves so we
// don't need to re-compute it.
if opts.uniqueKeyIndex == nil {
idx := secondUniqueKeyIndex(keys, sort)
opts.uniqueKeyIndex = &idx
}
// For each key, we'll compute the intermediate blinded key: a_i*P_i,
// where a_i is the aggregation coefficient for that key, and P_i is
// the key itself, then accumulate that (addition) into the main final
// key: P = P_1 + P_2 ... P_N.
var finalKeyJ btcec.JacobianPoint
for _, key := range keys {
// Port the key over to Jacobian coordinates as we need it in
// this format for the routines below.
var keyJ btcec.JacobianPoint
key.AsJacobian(&keyJ)
// Compute the aggregation coefficient for the key, then
// multiply it by the key itself: P_i' = a_i*P_i.
var tweakedKeyJ btcec.JacobianPoint
a := aggregationCoefficient(
keys, key, opts.keyHash, *opts.uniqueKeyIndex,
)
btcec.ScalarMultNonConst(a, &keyJ, &tweakedKeyJ)
// Finally accumulate this into the final key in an incremental
// fashion.
btcec.AddNonConst(&finalKeyJ, &tweakedKeyJ, &finalKeyJ)
}
// We'll copy over the key at this point, since this represents the
// aggregated key before any tweaks have been applied. This'll be used
// as the internal key for script path proofs.
finalKeyJ.ToAffine()
combinedKey := btcec.NewPublicKey(&finalKeyJ.X, &finalKeyJ.Y)
// At this point, if this is a taproot tweak, then we'll modify the
// base tweak value to use the BIP 341 tweak value.
if opts.taprootTweak {
// Emulate the same behavior as txscript.ComputeTaprootOutputKey
// which only operates on the x-only public key.
key, _ := schnorr.ParsePubKey(schnorr.SerializePubKey(
combinedKey,
))
// We only use the actual tweak bytes if we're not committing
// to a BIP-0086 key only spend output. Otherwise, we just
// commit to the internal key and an empty byte slice as the
// root hash.
tweakBytes := []byte{}
if !opts.bip86Tweak {
tweakBytes = opts.tweaks[0].Tweak[:]
}
// Compute the taproot key tagged hash of:
// h_tapTweak(internalKey || scriptRoot). We only do this for
// the first one, as you can only specify a single tweak when
// using the taproot mode with this API.
tapTweakHash := chainhash.TaggedHash(
chainhash.TagTapTweak, schnorr.SerializePubKey(key),
tweakBytes,
)
opts.tweaks[0].Tweak = *tapTweakHash
}
var (
err error
tweakAcc btcec.ModNScalar
parityAcc btcec.ModNScalar
)
parityAcc.SetInt(1)
// In this case we have a set of tweaks, so we'll incrementally apply
// each one, until we have our final tweaked key, and the related
// accumulators.
for i := 1; i <= len(opts.tweaks); i++ {
finalKeyJ, parityAcc, tweakAcc, err = tweakKey(
finalKeyJ, parityAcc, opts.tweaks[i-1].Tweak, tweakAcc,
opts.tweaks[i-1].IsXOnly,
)
if err != nil {
return nil, nil, nil, err
}
}
finalKeyJ.ToAffine()
finalKey := btcec.NewPublicKey(&finalKeyJ.X, &finalKeyJ.Y)
return &AggregateKey{
PreTweakedKey: combinedKey,
FinalKey: finalKey,
}, &parityAcc, &tweakAcc, nil
}
// Copyright 2013-2022 The btcsuite developers
package musig2v040
import (
"bytes"
"crypto/rand"
"encoding/binary"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
const (
// PubNonceSize is the size of the public nonces. Each public nonce is
// serialized the full compressed encoding, which uses 32 bytes for each
// nonce.
PubNonceSize = 66
// SecNonceSize is the size of the secret nonces for musig2. The secret
// nonces are the corresponding private keys to the public nonce points.
SecNonceSize = 64
)
var (
// NonceAuxTag is the tag used to optionally mix in the secret key with
// the set of aux randomness.
NonceAuxTag = []byte("MuSig/aux")
// NonceGenTag is used to generate the value (from a set of required an
// optional field) that will be used as the part of the secret nonce.
NonceGenTag = []byte("MuSig/nonce")
byteOrder = binary.BigEndian
)
// zeroSecNonce is a secret nonce that's all zeroes. This is used to check that
// we're not attempting to re-use a nonce, and also protect callers from it.
var zeroSecNonce [SecNonceSize]byte
// Nonces holds the public and secret nonces required for musig2.
//
// TODO(roasbeef): methods on this to help w/ parsing, etc?
type Nonces struct {
// PubNonce holds the two 33-byte compressed encoded points that serve
// as the public set of nonces.
PubNonce [PubNonceSize]byte
// SecNonce holds the two 32-byte scalar values that are the private
// keys to the two public nonces.
SecNonce [SecNonceSize]byte
}
// secNonceToPubNonce takes our two secrete nonces, and produces their two
// corresponding EC points, serialized in compressed format.
func secNonceToPubNonce(secNonce [SecNonceSize]byte) [PubNonceSize]byte {
var k1Mod, k2Mod btcec.ModNScalar
k1Mod.SetByteSlice(secNonce[:btcec.PrivKeyBytesLen])
k2Mod.SetByteSlice(secNonce[btcec.PrivKeyBytesLen:])
var r1, r2 btcec.JacobianPoint
btcec.ScalarBaseMultNonConst(&k1Mod, &r1)
btcec.ScalarBaseMultNonConst(&k2Mod, &r2)
// Next, we'll convert the key in jacobian format to a normal public
// key expressed in affine coordinates.
r1.ToAffine()
r2.ToAffine()
r1Pub := btcec.NewPublicKey(&r1.X, &r1.Y)
r2Pub := btcec.NewPublicKey(&r2.X, &r2.Y)
var pubNonce [PubNonceSize]byte
// The public nonces are serialized as: R1 || R2, where both keys are
// serialized in compressed format.
copy(pubNonce[:], r1Pub.SerializeCompressed())
copy(
pubNonce[btcec.PubKeyBytesLenCompressed:],
r2Pub.SerializeCompressed(),
)
return pubNonce
}
// NonceGenOption is a function option that allows callers to modify how nonce
// generation happens.
type NonceGenOption func(*nonceGenOpts)
// nonceGenOpts is the set of options that control how nonce generation
// happens.
type nonceGenOpts struct {
// randReader is what we'll use to generate a set of random bytes. If
// unspecified, then the normal crypto/rand rand.Read method will be
// used in place.
randReader io.Reader
// secretKey is an optional argument that's used to further augment the
// generated nonce by xor'ing it with this secret key.
secretKey []byte
// combinedKey is an optional argument that if specified, will be
// combined along with the nonce generation.
combinedKey []byte
// msg is an optional argument that will be mixed into the nonce
// derivation algorithm.
msg []byte
// auxInput is an optional argument that will be mixed into the nonce
// derivation algorithm.
auxInput []byte
}
// cryptoRandAdapter is an adapter struct that allows us to pass in the package
// level Read function from crypto/rand into a context that accepts an
// io.Reader.
type cryptoRandAdapter struct {
}
// Read implements the io.Reader interface for the crypto/rand package. By
// default, we always use the crypto/rand reader, but the caller is able to
// specify their own generation, which can be useful for deterministic tests.
func (c *cryptoRandAdapter) Read(p []byte) (n int, err error) {
return rand.Read(p)
}
// defaultNonceGenOpts returns the default set of nonce generation options.
func defaultNonceGenOpts() *nonceGenOpts {
return &nonceGenOpts{
randReader: &cryptoRandAdapter{},
}
}
// WithCustomRand allows a caller to use a custom random number generator in
// place for crypto/rand. This should only really be used to generate
// determinstic tests.
func WithCustomRand(r io.Reader) NonceGenOption {
return func(o *nonceGenOpts) {
o.randReader = r
}
}
// WithNonceSecretKeyAux allows a caller to optionally specify a secret key
// that should be used to augment the randomness used to generate the nonces.
func WithNonceSecretKeyAux(secKey *btcec.PrivateKey) NonceGenOption {
return func(o *nonceGenOpts) {
o.secretKey = secKey.Serialize()
}
}
// WithNonceCombinedKeyAux allows a caller to optionally specify the combined
// key used in this signing session to further augment the randomness used to
// generate nonces.
func WithNonceCombinedKeyAux(combinedKey *btcec.PublicKey) NonceGenOption {
return func(o *nonceGenOpts) {
o.combinedKey = schnorr.SerializePubKey(combinedKey)
}
}
// WithNonceMessageAux allows a caller to optionally specify a message to be
// mixed into the randomness generated to create the nonce.
func WithNonceMessageAux(msg [32]byte) NonceGenOption {
return func(o *nonceGenOpts) {
o.msg = msg[:]
}
}
// WithNonceAuxInput is a set of auxiliary randomness, similar to BIP 340 that
// can be used to further augment the nonce generation process.
func WithNonceAuxInput(aux []byte) NonceGenOption {
return func(o *nonceGenOpts) {
o.auxInput = aux
}
}
// withCustomOptions allows a caller to pass a complete set of custom
// nonceGenOpts, without needing to create custom and checked structs such as
// *btcec.PrivateKey. This is mainly used to match the testcases provided by
// the MuSig2 BIP.
func withCustomOptions(customOpts nonceGenOpts) NonceGenOption {
return func(o *nonceGenOpts) {
o.randReader = customOpts.randReader
o.secretKey = customOpts.secretKey
o.combinedKey = customOpts.combinedKey
o.msg = customOpts.msg
o.auxInput = customOpts.auxInput
}
}
// lengthWriter is a function closure that allows a caller to control how the
// length prefix of a byte slice is written.
type lengthWriter func(w io.Writer, b []byte) error
// uint8Writer is an implementation of lengthWriter that writes the length of
// the byte slice using 1 byte.
func uint8Writer(w io.Writer, b []byte) error {
return binary.Write(w, byteOrder, uint8(len(b)))
}
// uint32Writer is an implementation of lengthWriter that writes the length of
// the byte slice using 4 bytes.
func uint32Writer(w io.Writer, b []byte) error {
return binary.Write(w, byteOrder, uint32(len(b)))
}
// writeBytesPrefix is used to write out: len(b) || b, to the passed io.Writer.
// The lengthWriter function closure is used to allow the caller to specify the
// precise byte packing of the length.
func writeBytesPrefix(w io.Writer, b []byte, lenWriter lengthWriter) error {
// Write out the length of the byte first, followed by the set of bytes
// itself.
if err := lenWriter(w, b); err != nil {
return err
}
if _, err := w.Write(b); err != nil {
return err
}
return nil
}
// genNonceAuxBytes writes out the full byte string used to derive a secret
// nonce based on some initial randomness as well as the series of optional
// fields. The byte string used for derivation is:
// - tagged_hash("MuSig/nonce", rand || len(aggpk) || aggpk || len(m)
// || m || len(in) || in || i).
//
// where i is the ith secret nonce being generated.
func genNonceAuxBytes(rand []byte, i int,
opts *nonceGenOpts) (*chainhash.Hash, error) {
var w bytes.Buffer
// First, write out the randomness generated in the prior step.
if _, err := w.Write(rand); err != nil {
return nil, err
}
// Next, we'll write out: len(aggpk) || aggpk.
err := writeBytesPrefix(&w, opts.combinedKey, uint8Writer)
if err != nil {
return nil, err
}
// Next, we'll write out the length prefixed message.
err = writeBytesPrefix(&w, opts.msg, uint8Writer)
if err != nil {
return nil, err
}
// Finally we'll write out the auxiliary input.
err = writeBytesPrefix(&w, opts.auxInput, uint32Writer)
if err != nil {
return nil, err
}
// Next we'll write out the interaction/index number which will
// uniquely generate two nonces given the rest of the possibly static
// parameters.
if err := binary.Write(&w, byteOrder, uint8(i)); err != nil {
return nil, err
}
// With the message buffer complete, we'll now derive the tagged hash
// using our set of params.
return chainhash.TaggedHash(NonceGenTag, w.Bytes()), nil
}
// GenNonces generates the secret nonces, as well as the public nonces which
// correspond to an EC point generated using the secret nonce as a private key.
func GenNonces(options ...NonceGenOption) (*Nonces, error) {
opts := defaultNonceGenOpts()
for _, opt := range options {
opt(opts)
}
// First, we'll start out by generating 32 random bytes drawn from our
// CSPRNG.
var randBytes [32]byte
if _, err := opts.randReader.Read(randBytes[:]); err != nil {
return nil, err
}
// If the options contain a secret key, we XOR it with with the tagged
// random bytes.
if len(opts.secretKey) == 32 {
taggedHash := chainhash.TaggedHash(NonceAuxTag, randBytes[:])
for i := 0; i < chainhash.HashSize; i++ {
randBytes[i] = opts.secretKey[i] ^ taggedHash[i]
}
}
// Using our randomness and the set of optional params, generate our
// two secret nonces: k1 and k2.
k1, err := genNonceAuxBytes(randBytes[:], 0, opts)
if err != nil {
return nil, err
}
k2, err := genNonceAuxBytes(randBytes[:], 1, opts)
if err != nil {
return nil, err
}
var k1Mod, k2Mod btcec.ModNScalar
k1Mod.SetBytes((*[32]byte)(k1))
k2Mod.SetBytes((*[32]byte)(k2))
// The secret nonces are serialized as the concatenation of the two 32
// byte secret nonce values.
var nonces Nonces
k1Mod.PutBytesUnchecked(nonces.SecNonce[:])
k2Mod.PutBytesUnchecked(nonces.SecNonce[btcec.PrivKeyBytesLen:])
// Next, we'll generate R_1 = k_1*G and R_2 = k_2*G. Along the way we
// need to map our nonce values into mod n scalars so we can work with
// the btcec API.
nonces.PubNonce = secNonceToPubNonce(nonces.SecNonce)
return &nonces, nil
}
// AggregateNonces aggregates the set of a pair of public nonces for each party
// into a single aggregated nonces to be used for multi-signing.
func AggregateNonces(pubNonces [][PubNonceSize]byte) ([PubNonceSize]byte, error) {
// combineNonces is a helper function that aggregates (adds) up a
// series of nonces encoded in compressed format. It uses a slicing
// function to extra 33 bytes at a time from the packed 2x public
// nonces.
type nonceSlicer func([PubNonceSize]byte) []byte
combineNonces := func(slicer nonceSlicer) (btcec.JacobianPoint, error) {
// Convert the set of nonces into jacobian coordinates we can
// use to accumulate them all into each other.
pubNonceJs := make([]*btcec.JacobianPoint, len(pubNonces))
for i, pubNonceBytes := range pubNonces {
// Using the slicer, extract just the bytes we need to
// decode.
var nonceJ btcec.JacobianPoint
nonceJ, err := btcec.ParseJacobian(slicer(pubNonceBytes))
if err != nil {
return btcec.JacobianPoint{}, err
}
pubNonceJs[i] = &nonceJ
}
// Now that we have the set of complete nonces, we'll aggregate
// them: R = R_i + R_i+1 + ... + R_i+n.
var aggregateNonce btcec.JacobianPoint
for _, pubNonceJ := range pubNonceJs {
btcec.AddNonConst(
&aggregateNonce, pubNonceJ, &aggregateNonce,
)
}
aggregateNonce.ToAffine()
return aggregateNonce, nil
}
// The final nonce public nonce is actually two nonces, one that
// aggregate the first nonce of all the parties, and the other that
// aggregates the second nonce of all the parties.
var finalNonce [PubNonceSize]byte
combinedNonce1, err := combineNonces(func(n [PubNonceSize]byte) []byte {
return n[:btcec.PubKeyBytesLenCompressed]
})
if err != nil {
return finalNonce, err
}
combinedNonce2, err := combineNonces(func(n [PubNonceSize]byte) []byte {
return n[btcec.PubKeyBytesLenCompressed:]
})
if err != nil {
return finalNonce, err
}
copy(finalNonce[:], btcec.JacobianToByteSlice(combinedNonce1))
copy(
finalNonce[btcec.PubKeyBytesLenCompressed:],
btcec.JacobianToByteSlice(combinedNonce2),
)
return finalNonce, nil
}
// Copyright 2013-2022 The btcsuite developers
package musig2v040
import (
"bytes"
"fmt"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/chaincfg/chainhash"
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
)
var (
// NonceBlindTag is that tag used to construct the value b, which
// blinds the second public nonce of each party.
NonceBlindTag = []byte("MuSig/noncecoef")
// ChallengeHashTag is the tag used to construct the challenge hash
ChallengeHashTag = []byte("BIP0340/challenge")
// ErrNoncePointAtInfinity is returned if during signing, the fully
// combined public nonce is the point at infinity.
ErrNoncePointAtInfinity = fmt.Errorf("signing nonce is the infinity " +
"point")
// ErrPrivKeyZero is returned when the private key for signing is
// actually zero.
ErrPrivKeyZero = fmt.Errorf("priv key is zero")
// ErrPartialSigInvalid is returned when a partial is found to be
// invalid.
ErrPartialSigInvalid = fmt.Errorf("partial signature is invalid")
// ErrSecretNonceZero is returned when a secret nonce is passed in a
// zero.
ErrSecretNonceZero = fmt.Errorf("secret nonce is blank")
)
// infinityPoint is the jacobian representation of the point at infinity.
var infinityPoint btcec.JacobianPoint
// PartialSignature reprints a partial (s-only) musig2 multi-signature. This
// isn't a valid schnorr signature by itself, as it needs to be aggregated
// along with the other partial signatures to be completed.
type PartialSignature struct {
S *btcec.ModNScalar
R *btcec.PublicKey
}
// NewPartialSignature returns a new instances of the partial sig struct.
func NewPartialSignature(s *btcec.ModNScalar,
r *btcec.PublicKey) PartialSignature {
return PartialSignature{
S: s,
R: r,
}
}
// Encode writes a serialized version of the partial signature to the passed
// io.Writer
func (p *PartialSignature) Encode(w io.Writer) error {
var sBytes [32]byte
p.S.PutBytes(&sBytes)
if _, err := w.Write(sBytes[:]); err != nil {
return err
}
return nil
}
// Decode attempts to parse a serialized PartialSignature stored in the passed
// io reader.
func (p *PartialSignature) Decode(r io.Reader) error {
p.S = new(btcec.ModNScalar)
var sBytes [32]byte
if _, err := io.ReadFull(r, sBytes[:]); err != nil {
return nil
}
overflows := p.S.SetBytes(&sBytes)
if overflows == 1 {
return ErrPartialSigInvalid
}
return nil
}
// SignOption is a functional option argument that allows callers to modify the
// way we generate musig2 schnorr signatures.
type SignOption func(*signOptions)
// signOptions houses the set of functional options that can be used to modify
// the method used to generate the musig2 partial signature.
type signOptions struct {
// fastSign determines if we'll skip the check at the end of the
// routine where we attempt to verify the produced signature.
fastSign bool
// sortKeys determines if the set of keys should be sorted before doing
// key aggregation.
sortKeys bool
// tweaks specifies a series of tweaks to be applied to the aggregated
// public key, which also partially carries over into the signing
// process.
tweaks []KeyTweakDesc
// taprootTweak specifies a taproot specific tweak. of the tweaks
// specified above. Normally we'd just apply the raw 32 byte tweak, but
// for taproot, we first need to compute the aggregated key before
// tweaking, and then use it as the internal key. This is required as
// the taproot tweak also commits to the public key, which in this case
// is the aggregated key before the tweak.
taprootTweak []byte
// bip86Tweak specifies that the taproot tweak should be done in a BIP
// 86 style, where we don't expect an actual tweak and instead just
// commit to the public key itself.
bip86Tweak bool
}
// defaultSignOptions returns the default set of signing operations.
func defaultSignOptions() *signOptions {
return &signOptions{}
}
// WithFastSign forces signing to skip the extra verification step at the end.
// Performance sensitive applications may opt to use this option to speed up
// the signing operation.
func WithFastSign() SignOption {
return func(o *signOptions) {
o.fastSign = true
}
}
// WithSortedKeys determines if the set of signing public keys are to be sorted
// or not before doing key aggregation.
func WithSortedKeys() SignOption {
return func(o *signOptions) {
o.sortKeys = true
}
}
// WithTweaks determines if the aggregated public key used should apply a
// series of tweaks before key aggregation.
func WithTweaks(tweaks ...KeyTweakDesc) SignOption {
return func(o *signOptions) {
o.tweaks = tweaks
}
}
// WithTaprootSignTweak allows a caller to specify a tweak that should be used
// in a bip 340 manner when signing. This differs from WithTweaks as the tweak
// will be assumed to always be x-only and the intermediate aggregate key
// before tweaking will be used to generate part of the tweak (as the taproot
// tweak also commits to the internal key).
//
// This option should be used in the taproot context to create a valid
// signature for the keypath spend for taproot, when the output key is actually
// committing to a script path, or some other data.
func WithTaprootSignTweak(scriptRoot []byte) SignOption {
return func(o *signOptions) {
o.taprootTweak = scriptRoot
}
}
// WithBip86SignTweak allows a caller to specify a tweak that should be used in
// a bip 340 manner when signing, factoring in BIP 86 as well. This differs
// from WithTaprootSignTweak as no true script root will be committed to,
// instead we just commit to the internal key.
//
// This option should be used in the taproot context to create a valid
// signature for the keypath spend for taproot, when the output key was
// generated using BIP 86.
func WithBip86SignTweak() SignOption {
return func(o *signOptions) {
o.bip86Tweak = true
}
}
// Sign generates a musig2 partial signature given the passed key set, secret
// nonce, public nonce, and private keys. This method returns an error if the
// generated nonces are either too large, or end up mapping to the point at
// infinity.
func Sign(secNonce [SecNonceSize]byte, privKey *btcec.PrivateKey,
combinedNonce [PubNonceSize]byte, pubKeys []*btcec.PublicKey,
msg [32]byte, signOpts ...SignOption) (*PartialSignature, error) {
// First, parse the set of optional signing options.
opts := defaultSignOptions()
for _, option := range signOpts {
option(opts)
}
// Compute the hash of all the keys here as we'll need it do aggregate
// the keys and also at the final step of signing.
keysHash := keyHashFingerprint(pubKeys, opts.sortKeys)
uniqueKeyIndex := secondUniqueKeyIndex(pubKeys, opts.sortKeys)
keyAggOpts := []KeyAggOption{
WithKeysHash(keysHash), WithUniqueKeyIndex(uniqueKeyIndex),
}
switch {
case opts.bip86Tweak:
keyAggOpts = append(
keyAggOpts, WithBIP86KeyTweak(),
)
case opts.taprootTweak != nil:
keyAggOpts = append(
keyAggOpts, WithTaprootKeyTweak(opts.taprootTweak),
)
case len(opts.tweaks) != 0:
keyAggOpts = append(keyAggOpts, WithKeyTweaks(opts.tweaks...))
}
// Next we'll construct the aggregated public key based on the set of
// signers.
combinedKey, parityAcc, _, err := AggregateKeys(
pubKeys, opts.sortKeys, keyAggOpts...,
)
if err != nil {
return nil, err
}
// Next we'll compute the value b, that blinds our second public
// nonce:
// * b = h(tag=NonceBlindTag, combinedNonce || combinedKey || m).
var (
nonceMsgBuf bytes.Buffer
nonceBlinder btcec.ModNScalar
)
nonceMsgBuf.Write(combinedNonce[:])
nonceMsgBuf.Write(schnorr.SerializePubKey(combinedKey.FinalKey))
nonceMsgBuf.Write(msg[:])
nonceBlindHash := chainhash.TaggedHash(
NonceBlindTag, nonceMsgBuf.Bytes(),
)
nonceBlinder.SetByteSlice(nonceBlindHash[:])
// Next, we'll parse the public nonces into R1 and R2.
r1J, err := btcec.ParseJacobian(
combinedNonce[:btcec.PubKeyBytesLenCompressed],
)
if err != nil {
return nil, err
}
r2J, err := btcec.ParseJacobian(
combinedNonce[btcec.PubKeyBytesLenCompressed:],
)
if err != nil {
return nil, err
}
// With our nonce blinding value, we'll now combine both the public
// nonces, using the blinding factor to tweak the second nonce:
// * R = R_1 + b*R_2
var nonce btcec.JacobianPoint
btcec.ScalarMultNonConst(&nonceBlinder, &r2J, &r2J)
btcec.AddNonConst(&r1J, &r2J, &nonce)
// If the combined nonce it eh point at infinity, then we'll bail out.
if nonce == infinityPoint {
G := btcec.Generator()
G.AsJacobian(&nonce)
}
// Next we'll parse out our two secret nonces, which we'll be using in
// the core signing process below.
var k1, k2 btcec.ModNScalar
k1.SetByteSlice(secNonce[:btcec.PrivKeyBytesLen])
k2.SetByteSlice(secNonce[btcec.PrivKeyBytesLen:])
if k1.IsZero() || k2.IsZero() {
return nil, ErrSecretNonceZero
}
nonce.ToAffine()
nonceKey := btcec.NewPublicKey(&nonce.X, &nonce.Y)
// If the nonce R has an odd y coordinate, then we'll negate both our
// secret nonces.
if nonce.Y.IsOdd() {
k1.Negate()
k2.Negate()
}
privKeyScalar := privKey.Key
if privKeyScalar.IsZero() {
return nil, ErrPrivKeyZero
}
pubKey := privKey.PubKey()
pubKeyYIsOdd := func() bool {
pubKeyBytes := pubKey.SerializeCompressed()
return pubKeyBytes[0] == secp.PubKeyFormatCompressedOdd
}()
combinedKeyYIsOdd := func() bool {
combinedKeyBytes := combinedKey.FinalKey.SerializeCompressed()
return combinedKeyBytes[0] == secp.PubKeyFormatCompressedOdd
}()
// Next we'll compute our two parity factors for Q the combined public
// key, and P, the public key we're signing with. If the keys are odd,
// then we'll negate them.
parityCombinedKey := new(btcec.ModNScalar).SetInt(1)
paritySignKey := new(btcec.ModNScalar).SetInt(1)
if combinedKeyYIsOdd {
parityCombinedKey.Negate()
}
if pubKeyYIsOdd {
paritySignKey.Negate()
}
// Before we sign below, we'll multiply by our various parity factors
// to ensure that the signing key is properly negated (if necessary):
// * d = gv⋅gaccv⋅gp⋅d'
privKeyScalar.Mul(parityCombinedKey).Mul(paritySignKey).Mul(parityAcc)
// Next we'll create the challenge hash that commits to the combined
// nonce, combined public key and also the message:
// * e = H(tag=ChallengeHashTag, R || Q || m) mod n
var challengeMsg bytes.Buffer
challengeMsg.Write(schnorr.SerializePubKey(nonceKey))
challengeMsg.Write(schnorr.SerializePubKey(combinedKey.FinalKey))
challengeMsg.Write(msg[:])
challengeBytes := chainhash.TaggedHash(
ChallengeHashTag, challengeMsg.Bytes(),
)
var e btcec.ModNScalar
e.SetByteSlice(challengeBytes[:])
// Next, we'll compute a, our aggregation coefficient for the key that
// we're signing with.
a := aggregationCoefficient(pubKeys, pubKey, keysHash, uniqueKeyIndex)
// With mu constructed, we can finally generate our partial signature
// as: s = (k1_1 + b*k_2 + e*a*d) mod n.
s := new(btcec.ModNScalar)
s.Add(&k1).Add(k2.Mul(&nonceBlinder)).Add(e.Mul(a).Mul(&privKeyScalar))
sig := NewPartialSignature(s, nonceKey)
// If we're not in fast sign mode, then we'll also validate our partial
// signature.
if !opts.fastSign {
pubNonce := secNonceToPubNonce(secNonce)
sigValid := sig.Verify(
pubNonce, combinedNonce, pubKeys, pubKey, msg,
signOpts...,
)
if !sigValid {
return nil, fmt.Errorf("sig is invalid!")
}
}
return &sig, nil
}
// Verify implements partial signature verification given the public nonce for
// the signer, aggregate nonce, signer set and finally the message being
// signed.
func (p *PartialSignature) Verify(pubNonce [PubNonceSize]byte,
combinedNonce [PubNonceSize]byte, keySet []*btcec.PublicKey,
signingKey *btcec.PublicKey, msg [32]byte, signOpts ...SignOption) bool {
pubKey := schnorr.SerializePubKey(signingKey)
return verifyPartialSig(
p, pubNonce, combinedNonce, keySet, pubKey, msg, signOpts...,
) == nil
}
// verifyPartialSig attempts to verify a partial schnorr signature given the
// necessary parameters. This is the internal version of Verify that returns
// detailed errors. signed.
func verifyPartialSig(partialSig *PartialSignature, pubNonce [PubNonceSize]byte,
combinedNonce [PubNonceSize]byte, keySet []*btcec.PublicKey,
pubKey []byte, msg [32]byte, signOpts ...SignOption) error {
opts := defaultSignOptions()
for _, option := range signOpts {
option(opts)
}
// First we'll map the internal partial signature back into something
// we can manipulate.
s := partialSig.S
// Next we'll parse out the two public nonces into something we can
// use.
//
// Compute the hash of all the keys here as we'll need it do aggregate
// the keys and also at the final step of verification.
keysHash := keyHashFingerprint(keySet, opts.sortKeys)
uniqueKeyIndex := secondUniqueKeyIndex(keySet, opts.sortKeys)
keyAggOpts := []KeyAggOption{
WithKeysHash(keysHash), WithUniqueKeyIndex(uniqueKeyIndex),
}
switch {
case opts.bip86Tweak:
keyAggOpts = append(
keyAggOpts, WithBIP86KeyTweak(),
)
case opts.taprootTweak != nil:
keyAggOpts = append(
keyAggOpts, WithTaprootKeyTweak(opts.taprootTweak),
)
case len(opts.tweaks) != 0:
keyAggOpts = append(keyAggOpts, WithKeyTweaks(opts.tweaks...))
}
// Next we'll construct the aggregated public key based on the set of
// signers.
combinedKey, parityAcc, _, err := AggregateKeys(
keySet, opts.sortKeys, keyAggOpts...,
)
if err != nil {
return err
}
// Next we'll compute the value b, that blinds our second public
// nonce:
// * b = h(tag=NonceBlindTag, combinedNonce || combinedKey || m).
var (
nonceMsgBuf bytes.Buffer
nonceBlinder btcec.ModNScalar
)
nonceMsgBuf.Write(combinedNonce[:])
nonceMsgBuf.Write(schnorr.SerializePubKey(combinedKey.FinalKey))
nonceMsgBuf.Write(msg[:])
nonceBlindHash := chainhash.TaggedHash(NonceBlindTag, nonceMsgBuf.Bytes())
nonceBlinder.SetByteSlice(nonceBlindHash[:])
r1J, err := btcec.ParseJacobian(
combinedNonce[:btcec.PubKeyBytesLenCompressed],
)
if err != nil {
return err
}
r2J, err := btcec.ParseJacobian(
combinedNonce[btcec.PubKeyBytesLenCompressed:],
)
if err != nil {
return err
}
// With our nonce blinding value, we'll now combine both the public
// nonces, using the blinding factor to tweak the second nonce:
// * R = R_1 + b*R_2
var nonce btcec.JacobianPoint
btcec.ScalarMultNonConst(&nonceBlinder, &r2J, &r2J)
btcec.AddNonConst(&r1J, &r2J, &nonce)
// Next, we'll parse out the set of public nonces this signer used to
// generate the signature.
pubNonce1J, err := btcec.ParseJacobian(
pubNonce[:btcec.PubKeyBytesLenCompressed],
)
if err != nil {
return err
}
pubNonce2J, err := btcec.ParseJacobian(
pubNonce[btcec.PubKeyBytesLenCompressed:],
)
if err != nil {
return err
}
// If the nonce is the infinity point we set it to the Generator.
if nonce == infinityPoint {
btcec.GeneratorJacobian(&nonce)
} else {
nonce.ToAffine()
}
// We'll perform a similar aggregation and blinding operator as we did
// above for the combined nonces: R' = R_1' + b*R_2'.
var pubNonceJ btcec.JacobianPoint
btcec.ScalarMultNonConst(&nonceBlinder, &pubNonce2J, &pubNonce2J)
btcec.AddNonConst(&pubNonce1J, &pubNonce2J, &pubNonceJ)
pubNonceJ.ToAffine()
// If the combined nonce used in the challenge hash has an odd y
// coordinate, then we'll negate our final public nonce.
if nonce.Y.IsOdd() {
pubNonceJ.Y.Negate(1)
pubNonceJ.Y.Normalize()
}
// Next we'll create the challenge hash that commits to the combined
// nonce, combined public key and also the message:
// * e = H(tag=ChallengeHashTag, R || Q || m) mod n
var challengeMsg bytes.Buffer
challengeMsg.Write(schnorr.SerializePubKey(btcec.NewPublicKey(
&nonce.X, &nonce.Y,
)))
challengeMsg.Write(schnorr.SerializePubKey(combinedKey.FinalKey))
challengeMsg.Write(msg[:])
challengeBytes := chainhash.TaggedHash(
ChallengeHashTag, challengeMsg.Bytes(),
)
var e btcec.ModNScalar
e.SetByteSlice(challengeBytes[:])
signingKey, err := schnorr.ParsePubKey(pubKey)
if err != nil {
return err
}
// Next, we'll compute a, our aggregation coefficient for the key that
// we're signing with.
a := aggregationCoefficient(keySet, signingKey, keysHash, uniqueKeyIndex)
// If the combined key has an odd y coordinate, then we'll negate
// parity factor for the signing key.
paritySignKey := new(btcec.ModNScalar).SetInt(1)
combinedKeyBytes := combinedKey.FinalKey.SerializeCompressed()
if combinedKeyBytes[0] == secp.PubKeyFormatCompressedOdd {
paritySignKey.Negate()
}
// Next, we'll construct the final parity factor by multiplying the
// sign key parity factor with the accumulated parity factor for all
// the keys.
finalParityFactor := paritySignKey.Mul(parityAcc)
// Now we'll multiply the parity factor by our signing key, which'll
// take care of the amount of negation needed.
var signKeyJ btcec.JacobianPoint
signingKey.AsJacobian(&signKeyJ)
btcec.ScalarMultNonConst(finalParityFactor, &signKeyJ, &signKeyJ)
// In the final set, we'll check that: s*G == R' + e*a*P.
var sG, rP btcec.JacobianPoint
btcec.ScalarBaseMultNonConst(s, &sG)
btcec.ScalarMultNonConst(e.Mul(a), &signKeyJ, &rP)
btcec.AddNonConst(&rP, &pubNonceJ, &rP)
sG.ToAffine()
rP.ToAffine()
if sG != rP {
return ErrPartialSigInvalid
}
return nil
}
// CombineOption is a functional option argument that allows callers to modify the
// way we combine musig2 schnorr signatures.
type CombineOption func(*combineOptions)
// combineOptions houses the set of functional options that can be used to
// modify the method used to combine the musig2 partial signatures.
type combineOptions struct {
msg [32]byte
combinedKey *btcec.PublicKey
tweakAcc *btcec.ModNScalar
}
// defaultCombineOptions returns the default set of signing operations.
func defaultCombineOptions() *combineOptions {
return &combineOptions{}
}
// WithTweakedCombine is a functional option that allows callers to specify
// that the signature was produced using a tweaked aggregated public key. In
// order to properly aggregate the partial signatures, the caller must specify
// enough information to reconstruct the challenge, and also the final
// accumulated tweak value.
func WithTweakedCombine(msg [32]byte, keys []*btcec.PublicKey,
tweaks []KeyTweakDesc, sort bool) CombineOption {
return func(o *combineOptions) {
combinedKey, _, tweakAcc, _ := AggregateKeys(
keys, sort, WithKeyTweaks(tweaks...),
)
o.msg = msg
o.combinedKey = combinedKey.FinalKey
o.tweakAcc = tweakAcc
}
}
// WithTaprootTweakedCombine is similar to the WithTweakedCombine option, but
// assumes a BIP 341 context where the final tweaked key is to be used as the
// output key, where the internal key is the aggregated key pre-tweak.
//
// This option should be used over WithTweakedCombine when attempting to
// aggregate signatures for a top-level taproot keyspend, where the output key
// commits to a script root.
func WithTaprootTweakedCombine(msg [32]byte, keys []*btcec.PublicKey,
scriptRoot []byte, sort bool) CombineOption {
return func(o *combineOptions) {
combinedKey, _, tweakAcc, _ := AggregateKeys(
keys, sort, WithTaprootKeyTweak(scriptRoot),
)
o.msg = msg
o.combinedKey = combinedKey.FinalKey
o.tweakAcc = tweakAcc
}
}
// WithBip86TweakedCombine is similar to the WithTaprootTweakedCombine option,
// but assumes a BIP 341 + BIP 86 context where the final tweaked key is to be
// used as the output key, where the internal key is the aggregated key
// pre-tweak.
//
// This option should be used over WithTaprootTweakedCombine when attempting to
// aggregate signatures for a top-level taproot keyspend, where the output key
// was generated using BIP 86.
func WithBip86TweakedCombine(msg [32]byte, keys []*btcec.PublicKey,
sort bool) CombineOption {
return func(o *combineOptions) {
combinedKey, _, tweakAcc, _ := AggregateKeys(
keys, sort, WithBIP86KeyTweak(),
)
o.msg = msg
o.combinedKey = combinedKey.FinalKey
o.tweakAcc = tweakAcc
}
}
// CombineSigs combines the set of public keys given the final aggregated
// nonce, and the series of partial signatures for each nonce.
func CombineSigs(combinedNonce *btcec.PublicKey,
partialSigs []*PartialSignature,
combineOpts ...CombineOption) *schnorr.Signature {
// First, parse the set of optional combine options.
opts := defaultCombineOptions()
for _, option := range combineOpts {
option(opts)
}
// If signer keys and tweaks are specified, then we need to carry out
// some intermediate steps before we can combine the signature.
var tweakProduct *btcec.ModNScalar
if opts.combinedKey != nil && opts.tweakAcc != nil {
// Next, we'll construct the parity factor of the combined key,
// negating it if the combined key has an even y coordinate.
parityFactor := new(btcec.ModNScalar).SetInt(1)
combinedKeyBytes := opts.combinedKey.SerializeCompressed()
if combinedKeyBytes[0] == secp.PubKeyFormatCompressedOdd {
parityFactor.Negate()
}
// Next we'll reconstruct e the challenge has based on the
// nonce and combined public key.
// * e = H(tag=ChallengeHashTag, R || Q || m) mod n
var challengeMsg bytes.Buffer
challengeMsg.Write(schnorr.SerializePubKey(combinedNonce))
challengeMsg.Write(schnorr.SerializePubKey(opts.combinedKey))
challengeMsg.Write(opts.msg[:])
challengeBytes := chainhash.TaggedHash(
ChallengeHashTag, challengeMsg.Bytes(),
)
var e btcec.ModNScalar
e.SetByteSlice(challengeBytes[:])
tweakProduct = new(btcec.ModNScalar).Set(&e)
tweakProduct.Mul(opts.tweakAcc).Mul(parityFactor)
}
// Finally, the tweak factor also needs to be re-computed as well.
var combinedSig btcec.ModNScalar
for _, partialSig := range partialSigs {
combinedSig.Add(partialSig.S)
}
// If the tweak product was set above, then we'll need to add the value
// at the very end in order to produce a valid signature under the
// final tweaked key.
if tweakProduct != nil {
combinedSig.Add(tweakProduct)
}
// TODO(roasbeef): less verbose way to get the x coord...
var nonceJ btcec.JacobianPoint
combinedNonce.AsJacobian(&nonceJ)
nonceJ.ToAffine()
return schnorr.NewSignature(&nonceJ.X, &combinedSig)
}
package itest
import (
"time"
"github.com/lightningnetwork/lnd/lntest"
)
// flakePreimageSettlement documents a flake found when testing the preimage
// extraction logic in a force close. The scenario is,
// - Alice and Bob have a channel.
// - Alice sends an HTLC to Bob, and Bob won't settle it.
// - Alice goes offline.
// - Bob force closes the channel and claims the HTLC using the preimage using
// a sweeping tx.
//
// TODO(yy): Expose blockbeat to the link layer so the preimage extraction
// happens in the same block where it's spent.
func flakePreimageSettlement(ht *lntest.HarnessTest) {
// Mine a block to trigger the sweep. This is needed because the
// preimage extraction logic from the link is not managed by the
// blockbeat, which means the preimage may be sent to the contest
// resolver after it's launched, which means Bob would miss the block to
// launch the resolver.
ht.MineEmptyBlocks(1)
// Sleep for 2 seconds, which is needed to make sure the mempool has the
// correct tx. The above mined block can cause an RBF, if the preimage
// extraction has already been finished before the block is mined. This
// means Bob would have created the sweeping tx - mining another block
// would cause the sweeper to RBF it.
time.Sleep(2 * time.Second)
}
// flakeTxNotifierNeutrino documents a flake found when running force close
// tests using neutrino backend, which is a race between two notifications - one
// for the spending notification, the other for the block which contains the
// spending tx.
//
// TODO(yy): remove it once the issue is resolved.
func flakeTxNotifierNeutrino(ht *lntest.HarnessTest) {
// Mine an empty block the for neutrino backend. We need this step to
// trigger Bob's chain watcher to detect the force close tx. Deep down,
// this happens because the notification system for neutrino is very
// different from others. Specifically, when a block contains the force
// close tx is notified, these two calls,
// - RegisterBlockEpochNtfn, will notify the block first.
// - RegisterSpendNtfn, will wait for the neutrino notifier to sync to
// the block, then perform a GetUtxo, which, by the time the spend
// details are sent, the blockbeat is considered processed in Bob's
// chain watcher.
//
// TODO(yy): refactor txNotifier to fix the above issue.
if ht.IsNeutrinoBackend() {
ht.MineEmptyBlocks(1)
}
}
// flakeInconsistentHTLCView documents a flake found that the `ListChannels` RPC
// can give inaccurate HTLC states, which is found when we call
// `AssertHTLCNotActive` after a commitment dance is finished. Suppose Carol is
// settling an invoice with Bob, from Bob's PoV, a typical healthy settlement
// flow goes like this:
//
// [DBG] PEER brontide.go:2412: Peer([Carol]): Received UpdateFulfillHTLC
// [DBG] HSWC switch.go:1315: Closed completed SETTLE circuit for ...
// [INF] HSWC switch.go:3044: Forwarded HTLC...
// [DBG] PEER brontide.go:2412: Peer([Carol]): Received CommitSig
// [DBG] PEER brontide.go:2412: Peer([Carol]): Sending RevokeAndAck
// [DBG] PEER brontide.go:2412: Peer([Carol]): Sending CommitSig
// [DBG] PEER brontide.go:2412: Peer([Carol]): Received RevokeAndAck
// [DBG] HSWC link.go:3617: ChannelLink([ChanPoint: Bob=>Carol]): settle-fail-filter: count=1, filter=[0]
// [DBG] HSWC switch.go:3001: Circuit is closing for packet...
//
// Bob receives the preimage, closes the circuit, and exchanges commit sig and
// revoke msgs with Carol. Once Bob receives the `CommitSig` from Carol, the
// HTLC should be removed from his `LocalCommitment` via
// `RevokeCurrentCommitment`.
//
// However, in the test where `AssertHTLCNotActive` is called, although the
// above process is finished, the `ListChannels“ still finds the HTLC. Also note
// that the RPC makes direct call to the channeldb without any locks, which
// should be fine as the struct `OpenChannel.LocalCommitment` is passed by
// value, although we need to double check.
//
// TODO(yy): In order to fix it, we should make the RPC share the same view of
// our channel state machine. Instead of making DB queries, it should instead
// use `lnwallet.LightningChannel` instead to stay consistent.
//
//nolint:ll
func flakeInconsistentHTLCView() {
// Perform a sleep so the commiment dance can be finished before we call
// the ListChannels.
time.Sleep(2 * time.Second)
}
// flakePaymentStreamReturnEarly documents a flake found in the test which
// relies on a given payment to be settled before testing other state changes.
// The issue comes from the payment stream created from the RPC `SendPaymentV2`
// gives premature settled event for a given payment, which is found in,
// - if we force close the channel immediately, we may get an error because
// the commitment dance is not finished.
// - if we subscribe HTLC events immediately, we may get extra events, which
// is also related to the above unfinished commitment dance.
//
// TODO(yy): Make sure we only mark the payment being settled once the
// commitment dance is finished. In addition, we should also fix the exit hop
// logic in the invoice settlement flow to make sure the invoice is only marked
// as settled after the commitment dance is finished.
func flakePaymentStreamReturnEarly() {
// Sleep 2 seconds so the pending HTLCs will be removed from the
// commitment.
time.Sleep(2 * time.Second)
}
// flakeRaceInBitcoinClientNotifications documents a bug found that the
// `ListUnspent` gives inaccurate results. In specific,
// - an output is confirmed in block X, which is under the process of being
// credited to our wallet.
// - `ListUnspent` is called between the above process, returning an
// inaccurate result, causing the sweeper to think there's no wallet utxo.
// - the sweeping will fail at block X due to not enough inputs.
//
// Under the hood, the RPC client created for handling wallet txns and handling
// block notifications are independent. For the block notification, which is
// registered via `RegisterBlockEpochNtfn`, is managed by the `chainntnfs`,
// which is hooked to a bitcoind client created at startup. For the wallet, it
// uses another bitcoind client to receive online events. Although they share
// the same bitcoind RPC conn, these two clients are acting independently.
// With this setup, it means there's no coordination between the two system -
// `lnwallet` and `chainntnfs` can disagree on the latest onchain state for a
// short period, causing an inconsistent state which leads to the failed
// sweeping attempt.
//
// TODO(yy): We need to adhere to the SSOT principle, and make the effort to
// ensure the whole system only uses one bitcoind client.
func flakeRaceInBitcoinClientNotifications(ht *lntest.HarnessTest) {
ht.MineEmptyBlocks(1)
}
package itest
import (
"fmt"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/peersrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/stretchr/testify/require"
)
// testCustomFeatures tests advertisement of custom features in various bolt 9
// sets. For completeness, it also asserts that features aren't set in places
// where they aren't intended to be.
func testCustomFeatures(ht *lntest.HarnessTest) {
var (
// Odd custom features so that we don't need to worry about
// issues connecting to peers.
customInit uint32 = 101
customNodeAnn uint32 = 103
customInvoice uint32 = 105
)
// Restart Alice with custom feature bits configured.
extraArgs := []string{
fmt.Sprintf("--protocol.custom-init=%v", customInit),
fmt.Sprintf("--protocol.custom-nodeann=%v", customNodeAnn),
fmt.Sprintf("--protocol.custom-invoice=%v", customInvoice),
}
cfgs := [][]string{extraArgs, nil}
_, nodes := ht.CreateSimpleNetwork(
cfgs, lntest.OpenChannelParams{Amt: 1000000},
)
alice, bob := nodes[0], nodes[1]
// Check that Alice's custom feature bit was sent to Bob in her init
// message.
peers := bob.RPC.ListPeers()
require.Len(ht, peers.Peers, 1)
require.Equal(ht, peers.Peers[0].PubKey, alice.PubKeyStr)
_, customInitSet := peers.Peers[0].Features[customInit]
require.True(ht, customInitSet)
assertFeatureNotInSet(
ht, []uint32{customNodeAnn, customInvoice},
peers.Peers[0].Features,
)
// Assert that Alice's custom feature bit is contained in the node
// announcement sent to Bob.
updates := ht.AssertNumNodeAnns(bob, alice.PubKeyStr, 1)
features := updates[len(updates)-1].Features
_, customFeature := features[customNodeAnn]
require.True(ht, customFeature)
assertFeatureNotInSet(
ht, []uint32{customInit, customInvoice}, features,
)
// Assert that Alice's custom feature bit is included in invoices.
invoice := alice.RPC.AddInvoice(&lnrpc.Invoice{})
payReq := alice.RPC.DecodePayReq(invoice.PaymentRequest)
_, customInvoiceSet := payReq.Features[customInvoice]
require.True(ht, customInvoiceSet)
assertFeatureNotInSet(
ht, []uint32{customInit, customNodeAnn}, payReq.Features,
)
// Check that Alice can't unset configured features via the node
// announcement update API. This is only checked for node announcement
// because it is the only set that can be updated via the API.
nodeAnnReq := &peersrpc.NodeAnnouncementUpdateRequest{
FeatureUpdates: []*peersrpc.UpdateFeatureAction{
{
Action: peersrpc.UpdateAction_ADD,
FeatureBit: lnrpc.FeatureBit(customNodeAnn),
},
},
}
alice.RPC.UpdateNodeAnnouncementErr(nodeAnnReq)
}
// assertFeatureNotInSet checks that the features provided aren't contained in
// a feature map.
func assertFeatureNotInSet(ht *lntest.HarnessTest, features []uint32,
advertised map[uint32]*lnrpc.Feature) {
for _, feature := range features {
_, featureSet := advertised[feature]
require.False(ht, featureSet)
}
}
package itest
import (
"math"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/node"
"github.com/lightningnetwork/lnd/lntest/rpc"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
// testExperimentalEndorsement tests setting of positive and negative
// experimental endorsement signals.
func testExperimentalEndorsement(ht *lntest.HarnessTest) {
testEndorsement(ht, true)
testEndorsement(ht, false)
}
// testEndorsement sets up a 5 hop network and tests propagation of
// experimental endorsement signals.
func testEndorsement(ht *lntest.HarnessTest, aliceEndorse bool) {
cfg := node.CfgAnchor
carolCfg := append(
[]string{"--protocol.no-experimental-endorsement"}, cfg...,
)
cfgs := [][]string{cfg, cfg, carolCfg, cfg, cfg}
const chanAmt = btcutil.Amount(300000)
p := lntest.OpenChannelParams{Amt: chanAmt}
_, nodes := ht.CreateSimpleNetwork(cfgs, p)
alice, bob, carol, dave, eve := nodes[0], nodes[1], nodes[2], nodes[3],
nodes[4]
bobIntercept, cancelBob := bob.RPC.HtlcInterceptor()
defer cancelBob()
carolIntercept, cancelCarol := carol.RPC.HtlcInterceptor()
defer cancelCarol()
daveIntercept, cancelDave := dave.RPC.HtlcInterceptor()
defer cancelDave()
req := &lnrpc.Invoice{ValueMsat: 1000}
addResponse := eve.RPC.AddInvoice(req)
invoice := eve.RPC.LookupInvoice(addResponse.RHash)
sendReq := &routerrpc.SendPaymentRequest{
PaymentRequest: invoice.PaymentRequest,
TimeoutSeconds: int32(wait.PaymentTimeout.Seconds()),
FeeLimitMsat: math.MaxInt64,
}
expectedValue := []byte{lnwire.ExperimentalUnendorsed}
if aliceEndorse {
expectedValue = []byte{lnwire.ExperimentalEndorsed}
t := uint64(lnwire.ExperimentalEndorsementType)
sendReq.FirstHopCustomRecords = map[uint64][]byte{
t: expectedValue,
}
}
_ = alice.RPC.SendPayment(sendReq)
// Validate that our signal (positive or zero) propagates until carol
// and then is dropped because she has disabled the feature.
validateEndorsedAndResume(ht, bobIntercept, true, expectedValue)
validateEndorsedAndResume(ht, carolIntercept, true, expectedValue)
validateEndorsedAndResume(ht, daveIntercept, false, nil)
var preimage lntypes.Preimage
copy(preimage[:], invoice.RPreimage)
ht.AssertPaymentStatus(alice, preimage.Hash(), lnrpc.Payment_SUCCEEDED)
}
func validateEndorsedAndResume(ht *lntest.HarnessTest,
interceptor rpc.InterceptorClient, hasEndorsement bool,
expectedValue []byte) {
packet := ht.ReceiveHtlcInterceptor(interceptor)
var expectedRecords map[uint64][]byte
if hasEndorsement {
u64Type := uint64(lnwire.ExperimentalEndorsementType)
expectedRecords = map[uint64][]byte{
u64Type: expectedValue,
}
}
require.Equal(ht, expectedRecords, packet.InWireCustomRecords)
err := interceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{
IncomingCircuitKey: packet.IncomingCircuitKey,
Action: routerrpc.ResolveHoldForwardAction_RESUME,
})
require.NoError(ht, err)
}
package itest
import (
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/node"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/stretchr/testify/require"
)
// walletTestCases defines a set of tests aiming at asserting functionalities
// provided by the wallerpc.
var walletTestCases = []*lntest.TestCase{
{
Name: "listunspent P2WPKH",
TestFunc: func(ht *lntest.HarnessTest) {
runTestListUnspent(ht, ht.FundCoins)
},
},
{
Name: "listunspent NP2WPKH",
TestFunc: func(ht *lntest.HarnessTest) {
runTestListUnspent(ht, ht.FundCoinsNP2WKH)
},
},
{
Name: "listunspent P2TR",
TestFunc: func(ht *lntest.HarnessTest) {
runTestListUnspent(ht, ht.FundCoinsP2TR)
},
},
{
Name: "listunspent P2WPKH restart",
TestFunc: func(ht *lntest.HarnessTest) {
runTestListUnspentRestart(ht, ht.FundCoins)
},
},
{
Name: "listunspent NP2WPKH restart",
TestFunc: func(ht *lntest.HarnessTest) {
runTestListUnspentRestart(ht, ht.FundCoinsNP2WKH)
},
},
{
Name: "listunspent P2TR restart",
TestFunc: func(ht *lntest.HarnessTest) {
runTestListUnspentRestart(ht, ht.FundCoinsP2TR)
},
},
}
type fundMethod func(amt btcutil.Amount, hn *node.HarnessNode) *wire.MsgTx
// testListUnspent checks that once Alice sends all coins to Bob, her wallet
// balances are updated and the ListUnspent returns no UTXO.
func runTestListUnspent(ht *lntest.HarnessTest, fundCoins fundMethod) {
// Create two test nodes.
alice := ht.NewNode("Alice", nil)
bob := ht.NewNode("Bob", nil)
// Fund Alice one UTXO.
coin := btcutil.Amount(100_000)
fundCoins(coin, alice)
// Log Alice's wallet balance for debug.
balance := alice.RPC.WalletBalance()
ht.Logf("Alice has balance: %v", balance)
// Send all Alice's balance to Bob.
ht.SendAllCoins(alice, bob)
// Mine Alice's send coin tx.
ht.MineBlocksAndAssertNumTxes(1, 1)
// Alice wallet should be empty now, assert that all her balance fields
// are zero.
ht.AssertWalletAccountBalance(alice, lnwallet.DefaultAccountName, 0, 0)
ht.AssertWalletLockedBalance(alice, 0)
// Alice should have no UTXO.
ht.AssertNumUTXOs(alice, 0)
}
// testListUnspentRestart checks that once Alice sends all coins to Bob, then
// restarts, her wallet balances are updated and the ListUnspent returns no
// UTXO.
func runTestListUnspentRestart(ht *lntest.HarnessTest, fundCoins fundMethod) {
// Create two test nodes.
alice := ht.NewNode("Alice", nil)
bob := ht.NewNode("Bob", nil)
// Fund Alice one UTXO.
coin := btcutil.Amount(100_000)
fundCoins(coin, alice)
// Log Alice's wallet balance for debug.
balance := alice.RPC.WalletBalance()
ht.Logf("Alice has balance: %v", balance)
// Send all Alice's balance to Bob.
ht.SendAllCoins(alice, bob)
// Shutdown Alice.
restart := ht.SuspendNode(alice)
// Mine Alice's send coin tx.
ht.MineBlocksAndAssertNumTxes(1, 1)
// Restart Alice.
require.NoError(ht, restart())
// Alice wallet should be empty now, assert that all her balance fields
// are zero.
ht.AssertWalletAccountBalance(alice, lnwallet.DefaultAccountName, 0, 0)
ht.AssertWalletLockedBalance(alice, 0)
// Alice should have no UTXO.
ht.AssertNumUTXOs(alice, 0)
}
package keychain
import (
"crypto/sha256"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/walletdb"
)
const (
// CoinTypeBitcoin specifies the BIP44 coin type for Bitcoin key
// derivation.
CoinTypeBitcoin uint32 = 0
// CoinTypeTestnet specifies the BIP44 coin type for all testnet key
// derivation.
CoinTypeTestnet = 1
)
var (
// lightningAddrSchema is the scope addr schema for all keys that we
// derive. We'll treat them all as p2wkh addresses, as atm we must
// specify a particular type.
lightningAddrSchema = waddrmgr.ScopeAddrSchema{
ExternalAddrType: waddrmgr.WitnessPubKey,
InternalAddrType: waddrmgr.WitnessPubKey,
}
// waddrmgrNamespaceKey is the namespace key that the waddrmgr state is
// stored within the top-level waleltdb buckets of btcwallet.
waddrmgrNamespaceKey = []byte("waddrmgr")
)
// BtcWalletKeyRing is an implementation of both the KeyRing and SecretKeyRing
// interfaces backed by btcwallet's internal root waddrmgr. Internally, we'll
// be using a ScopedKeyManager to do all of our derivations, using the key
// scope and scope addr scehma defined above. Re-using the existing key scope
// construction means that all key derivation will be protected under the root
// seed of the wallet, making each derived key fully deterministic.
type BtcWalletKeyRing struct {
// wallet is a pointer to the active instance of the btcwallet core.
// This is required as we'll need to manually open database
// transactions in order to derive addresses and lookup relevant keys
wallet *wallet.Wallet
// chainKeyScope defines the purpose and coin type to be used when generating
// keys for this keyring.
chainKeyScope waddrmgr.KeyScope
// lightningScope is a pointer to the scope that we'll be using as a
// sub key manager to derive all the keys that we require.
lightningScope *waddrmgr.ScopedKeyManager
}
// NewBtcWalletKeyRing creates a new implementation of the
// keychain.SecretKeyRing interface backed by btcwallet.
//
// NOTE: The passed waddrmgr.Manager MUST be unlocked in order for the keychain
// to function.
func NewBtcWalletKeyRing(w *wallet.Wallet, coinType uint32) SecretKeyRing {
// Construct the key scope that will be used within the waddrmgr to
// create an HD chain for deriving all of our required keys. A different
// scope is used for each specific coin type.
chainKeyScope := waddrmgr.KeyScope{
Purpose: BIP0043Purpose,
Coin: coinType,
}
return &BtcWalletKeyRing{
wallet: w,
chainKeyScope: chainKeyScope,
}
}
// keyScope attempts to return the key scope that we'll use to derive all of
// our keys. If the scope has already been fetched from the database, then a
// cached version will be returned. Otherwise, we'll fetch it from the database
// and cache it for subsequent accesses.
func (b *BtcWalletKeyRing) keyScope() (*waddrmgr.ScopedKeyManager, error) {
// If the scope has already been populated, then we'll return it
// directly.
if b.lightningScope != nil {
return b.lightningScope, nil
}
// Otherwise, we'll first do a check to ensure that the root manager
// isn't locked, as otherwise we won't be able to *use* the scope.
if !b.wallet.Manager.WatchOnly() && b.wallet.Manager.IsLocked() {
return nil, fmt.Errorf("cannot create BtcWalletKeyRing with " +
"locked waddrmgr.Manager")
}
// If the manager is indeed unlocked, then we'll fetch the scope, cache
// it, and return to the caller.
lnScope, err := b.wallet.Manager.FetchScopedKeyManager(b.chainKeyScope)
if err != nil {
return nil, err
}
b.lightningScope = lnScope
return lnScope, nil
}
// createAccountIfNotExists will create the corresponding account for a key
// family if it doesn't already exist in the database.
func (b *BtcWalletKeyRing) createAccountIfNotExists(
addrmgrNs walletdb.ReadWriteBucket, keyFam KeyFamily,
scope *waddrmgr.ScopedKeyManager) error {
// If this is the multi-sig key family, then we can return early as
// this is the default account that's created.
if keyFam == KeyFamilyMultiSig {
return nil
}
// Otherwise, we'll check if the account already exists, if so, we can
// once again bail early.
_, err := scope.AccountName(addrmgrNs, uint32(keyFam))
if err == nil {
return nil
}
// If we reach this point, then the account hasn't yet been created, so
// we'll need to create it before we can proceed.
return scope.NewRawAccount(addrmgrNs, uint32(keyFam))
}
// DeriveNextKey attempts to derive the *next* key within the key family
// (account in BIP43) specified. This method should return the next external
// child within this branch.
//
// NOTE: This is part of the keychain.KeyRing interface.
func (b *BtcWalletKeyRing) DeriveNextKey(keyFam KeyFamily) (KeyDescriptor, error) {
var (
pubKey *btcec.PublicKey
keyLoc KeyLocator
)
db := b.wallet.Database()
err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
scope, err := b.keyScope()
if err != nil {
return err
}
// If the account doesn't exist, then we may need to create it
// for the first time in order to derive the keys that we
// require.
err = b.createAccountIfNotExists(addrmgrNs, keyFam, scope)
if err != nil {
return err
}
addrs, err := scope.NextExternalAddresses(
addrmgrNs, uint32(keyFam), 1,
)
if err != nil {
return err
}
// Extract the first address, ensuring that it is of the proper
// interface type, otherwise we can't manipulate it below.
addr, ok := addrs[0].(waddrmgr.ManagedPubKeyAddress)
if !ok {
return fmt.Errorf("address is not a managed pubkey " +
"addr")
}
pubKey = addr.PubKey()
_, pathInfo, _ := addr.DerivationInfo()
keyLoc = KeyLocator{
Family: keyFam,
Index: pathInfo.Index,
}
return nil
})
if err != nil {
return KeyDescriptor{}, err
}
return KeyDescriptor{
PubKey: pubKey,
KeyLocator: keyLoc,
}, nil
}
// DeriveKey attempts to derive an arbitrary key specified by the passed
// KeyLocator. This may be used in several recovery scenarios, or when manually
// rotating something like our current default node key.
//
// NOTE: This is part of the keychain.KeyRing interface.
func (b *BtcWalletKeyRing) DeriveKey(keyLoc KeyLocator) (KeyDescriptor, error) {
var keyDesc KeyDescriptor
db := b.wallet.Database()
err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
scope, err := b.keyScope()
if err != nil {
return err
}
// If the account doesn't exist, then we may need to create it
// for the first time in order to derive the keys that we
// require. We skip this if we're using a remote signer in which
// case we _need_ to create all accounts when creating the
// wallet, so it must exist now.
if !b.wallet.Manager.WatchOnly() {
err = b.createAccountIfNotExists(
addrmgrNs, keyLoc.Family, scope,
)
if err != nil {
return err
}
}
path := waddrmgr.DerivationPath{
InternalAccount: uint32(keyLoc.Family),
Branch: 0,
Index: keyLoc.Index,
}
addr, err := scope.DeriveFromKeyPath(addrmgrNs, path)
if err != nil {
return err
}
keyDesc.KeyLocator = keyLoc
keyDesc.PubKey = addr.(waddrmgr.ManagedPubKeyAddress).PubKey()
return nil
})
if err != nil {
return keyDesc, err
}
return keyDesc, nil
}
// DerivePrivKey attempts to derive the private key that corresponds to the
// passed key descriptor.
//
// NOTE: This is part of the keychain.SecretKeyRing interface.
func (b *BtcWalletKeyRing) DerivePrivKey(keyDesc KeyDescriptor) (
*btcec.PrivateKey, error) {
var key *btcec.PrivateKey
scope, err := b.keyScope()
if err != nil {
return nil, err
}
// First, attempt to see if we can read the key directly from
// btcwallet's internal cache, if we can then we can skip all the
// operations below (fast path).
if keyDesc.PubKey == nil {
keyPath := waddrmgr.DerivationPath{
InternalAccount: uint32(keyDesc.Family),
Account: uint32(keyDesc.Family),
Branch: 0,
Index: keyDesc.Index,
}
privKey, err := scope.DeriveFromKeyPathCache(keyPath)
if err == nil {
return privKey, nil
}
}
db := b.wallet.Database()
err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
// If the account doesn't exist, then we may need to create it
// for the first time in order to derive the keys that we
// require. We skip this if we're using a remote signer in which
// case we _need_ to create all accounts when creating the
// wallet, so it must exist now.
if !b.wallet.Manager.WatchOnly() {
err = b.createAccountIfNotExists(
addrmgrNs, keyDesc.Family, scope,
)
if err != nil {
return err
}
}
// If the public key isn't set or they have a non-zero index,
// then we know that the caller instead knows the derivation
// path for a key.
if keyDesc.PubKey == nil || keyDesc.Index > 0 {
// Now that we know the account exists, we can safely
// derive the full private key from the given path.
path := waddrmgr.DerivationPath{
InternalAccount: uint32(keyDesc.Family),
Branch: 0,
Index: keyDesc.Index,
}
addr, err := scope.DeriveFromKeyPath(addrmgrNs, path)
if err != nil {
return err
}
key, err = addr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
if err != nil {
return err
}
return nil
}
// If the public key isn't nil, then this indicates that we
// need to scan for the private key, assuming that we know the
// valid key family.
nextPath := waddrmgr.DerivationPath{
InternalAccount: uint32(keyDesc.Family),
Branch: 0,
Index: 0,
}
// We'll now iterate through our key range in an attempt to
// find the target public key.
//
// TODO(roasbeef): possibly move scanning into wallet to allow
// to be parallelized
for i := 0; i < MaxKeyRangeScan; i++ {
// Derive the next key in the range and fetch its
// managed address.
addr, err := scope.DeriveFromKeyPath(
addrmgrNs, nextPath,
)
if err != nil {
return err
}
managedAddr := addr.(waddrmgr.ManagedPubKeyAddress)
// If this is the target public key, then we'll return
// it directly back to the caller.
if managedAddr.PubKey().IsEqual(keyDesc.PubKey) {
key, err = managedAddr.PrivKey()
if err != nil {
return err
}
return nil
}
// This wasn't the target key, so roll forward and try
// the next one.
nextPath.Index++
}
// If we reach this point, then we we're unable to derive the
// private key, so return an error back to the user.
return ErrCannotDerivePrivKey
})
if err != nil {
return nil, err
}
return key, nil
}
// ECDH performs a scalar multiplication (ECDH-like operation) between the
// target key descriptor and remote public key. The output returned will be
// the sha256 of the resulting shared point serialized in compressed format. If
// k is our private key, and P is the public key, we perform the following
// operation:
//
// sx := k*P s := sha256(sx.SerializeCompressed())
//
// NOTE: This is part of the keychain.ECDHRing interface.
func (b *BtcWalletKeyRing) ECDH(keyDesc KeyDescriptor,
pub *btcec.PublicKey) ([32]byte, error) {
privKey, err := b.DerivePrivKey(keyDesc)
if err != nil {
return [32]byte{}, err
}
var (
pubJacobian btcec.JacobianPoint
s btcec.JacobianPoint
)
pub.AsJacobian(&pubJacobian)
btcec.ScalarMultNonConst(&privKey.Key, &pubJacobian, &s)
s.ToAffine()
sPubKey := btcec.NewPublicKey(&s.X, &s.Y)
h := sha256.Sum256(sPubKey.SerializeCompressed())
return h, nil
}
// SignMessage signs the given message, single or double SHA256 hashing it
// first, with the private key described in the key locator.
//
// NOTE: This is part of the keychain.MessageSignerRing interface.
func (b *BtcWalletKeyRing) SignMessage(keyLoc KeyLocator,
msg []byte, doubleHash bool) (*ecdsa.Signature, error) {
privKey, err := b.DerivePrivKey(KeyDescriptor{
KeyLocator: keyLoc,
})
if err != nil {
return nil, err
}
var digest []byte
if doubleHash {
digest = chainhash.DoubleHashB(msg)
} else {
digest = chainhash.HashB(msg)
}
return ecdsa.Sign(privKey, digest), nil
}
// SignMessageCompact signs the given message, single or double SHA256 hashing
// it first, with the private key described in the key locator and returns
// the signature in the compact, public key recoverable format.
//
// NOTE: This is part of the keychain.MessageSignerRing interface.
func (b *BtcWalletKeyRing) SignMessageCompact(keyLoc KeyLocator,
msg []byte, doubleHash bool) ([]byte, error) {
privKey, err := b.DerivePrivKey(KeyDescriptor{
KeyLocator: keyLoc,
})
if err != nil {
return nil, err
}
var digest []byte
if doubleHash {
digest = chainhash.DoubleHashB(msg)
} else {
digest = chainhash.HashB(msg)
}
return ecdsa.SignCompact(privKey, digest, true), nil
}
// SignMessageSchnorr uses the Schnorr signature algorithm to sign the given
// message, single or double SHA256 hashing it first, with the private key
// described in the key locator and the optional tweak applied to the private
// key.
//
// NOTE: This is part of the keychain.MessageSignerRing interface.
func (b *BtcWalletKeyRing) SignMessageSchnorr(keyLoc KeyLocator,
msg []byte, doubleHash bool, taprootTweak []byte,
tag []byte) (*schnorr.Signature, error) {
privKey, err := b.DerivePrivKey(KeyDescriptor{
KeyLocator: keyLoc,
})
if err != nil {
return nil, err
}
if len(taprootTweak) > 0 {
privKey = txscript.TweakTaprootPrivKey(*privKey, taprootTweak)
}
// If a tag was provided, we need to take the tagged hash of the input.
var digest []byte
switch {
case len(tag) > 0:
taggedHash := chainhash.TaggedHash(tag, msg)
digest = taggedHash[:]
case doubleHash:
digest = chainhash.DoubleHashB(msg)
default:
digest = chainhash.HashB(msg)
}
return schnorr.Sign(privKey, digest)
}
package keychain
import (
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
)
const (
// KeyDerivationVersionLegacy is the previous version of the key
// derivation schema defined below. We use a version as this means that
// we'll be able to accept new seed in the future and be able to discern
// if the software is compatible with the version of the seed.
KeyDerivationVersionLegacy = 0
// KeyDerivationVersionTaproot is the most recent version of the key
// derivation scheme that marks the introduction of the Taproot
// derivation with BIP0086 support.
KeyDerivationVersionTaproot = 1
// CurrentKeyDerivationVersion is the current default key derivation
// version that is used for new seeds.
CurrentKeyDerivationVersion = KeyDerivationVersionTaproot
// BIP0043Purpose is the "purpose" value that we'll use for the first
// version or our key derivation scheme. All keys are expected to be
// derived from this purpose, then the particular coin type of the
// chain where the keys are to be used. Slightly adhering to BIP0043
// allows us to not deviate too far from a widely used standard, and
// also fits into existing implementations of the BIP's template.
//
// NOTE: BRICK SQUUUUUAD.
BIP0043Purpose = 1017
)
// IsKnownVersion returns true if the given version is one of the known
// derivation scheme versions as defined by this package.
func IsKnownVersion(internalVersion uint8) bool {
return internalVersion == KeyDerivationVersionLegacy ||
internalVersion == KeyDerivationVersionTaproot
}
var (
// MaxKeyRangeScan is the maximum number of keys that we'll attempt to
// scan with if a caller knows the public key, but not the KeyLocator
// and wishes to derive a private key.
MaxKeyRangeScan = 100000
// ErrCannotDerivePrivKey is returned when DerivePrivKey is unable to
// derive a private key given only the public key and target key
// family.
ErrCannotDerivePrivKey = fmt.Errorf("unable to derive private key")
)
// KeyFamily represents a "family" of keys that will be used within various
// contracts created by lnd. These families are meant to be distinct branches
// within the HD key chain of the backing wallet. Usage of key families within
// the interface below are strict in order to promote integrability and the
// ability to restore all keys given a user master seed backup.
//
// The key derivation in this file follows the following hierarchy based on
// BIP43:
//
// - m/1017'/coinType'/keyFamily'/0/index
type KeyFamily uint32
const (
// KeyFamilyMultiSig are keys to be used within multi-sig scripts.
KeyFamilyMultiSig KeyFamily = 0
// KeyFamilyRevocationBase are keys that are used within channels to
// create revocation basepoints that the remote party will use to
// create revocation keys for us.
KeyFamilyRevocationBase KeyFamily = 1
// KeyFamilyHtlcBase are keys used within channels that will be
// combined with per-state randomness to produce public keys that will
// be used in HTLC scripts.
KeyFamilyHtlcBase KeyFamily = 2
// KeyFamilyPaymentBase are keys used within channels that will be
// combined with per-state randomness to produce public keys that will
// be used in scripts that pay directly to us without any delay.
KeyFamilyPaymentBase KeyFamily = 3
// KeyFamilyDelayBase are keys used within channels that will be
// combined with per-state randomness to produce public keys that will
// be used in scripts that pay to us, but require a CSV delay before we
// can sweep the funds.
KeyFamilyDelayBase KeyFamily = 4
// KeyFamilyRevocationRoot is a family of keys which will be used to
// derive the root of a revocation tree for a particular channel.
KeyFamilyRevocationRoot KeyFamily = 5
// KeyFamilyNodeKey is a family of keys that will be used to derive
// keys that will be advertised on the network to represent our current
// "identity" within the network. Peers will need our latest node key
// in order to establish a transport session with us on the Lightning
// p2p level (BOLT-0008).
KeyFamilyNodeKey KeyFamily = 6
// KeyFamilyBaseEncryption is the family of keys that will be used to
// derive keys that we use to encrypt and decrypt any general blob data
// like static channel backups and the TLS private key. Often used when
// encrypting files on disk.
KeyFamilyBaseEncryption KeyFamily = 7
// KeyFamilyTowerSession is the family of keys that will be used to
// derive session keys when negotiating sessions with watchtowers. The
// session keys are limited to the lifetime of the session and are used
// to increase privacy in the watchtower protocol.
KeyFamilyTowerSession KeyFamily = 8
// KeyFamilyTowerID is the family of keys used to derive the public key
// of a watchtower. This made distinct from the node key to offer a form
// of rudimentary whitelisting, i.e. via knowledge of the pubkey,
// preventing others from having full access to the tower just as a
// result of knowing the node key.
KeyFamilyTowerID KeyFamily = 9
)
// VersionZeroKeyFamilies is a slice of all the known key families for first
// version of the key derivation schema defined in this package.
var VersionZeroKeyFamilies = []KeyFamily{
KeyFamilyMultiSig,
KeyFamilyRevocationBase,
KeyFamilyHtlcBase,
KeyFamilyPaymentBase,
KeyFamilyDelayBase,
KeyFamilyRevocationRoot,
KeyFamilyNodeKey,
KeyFamilyBaseEncryption,
KeyFamilyTowerSession,
KeyFamilyTowerID,
}
// KeyLocator is a two-tuple that can be used to derive *any* key that has ever
// been used under the key derivation mechanisms described in this file.
// Version 0 of our key derivation schema uses the following BIP43-like
// derivation:
//
// - m/1017'/coinType'/keyFamily'/0/index
//
// Our purpose is 1017 (chosen arbitrary for now), and the coin type will vary
// based on which coin/chain the channels are being created on. The key family
// are actually just individual "accounts" in the nomenclature of BIP43. By
// default we assume a branch of 0 (external). Finally, the key index (which
// will vary per channel and use case) is the final element which allows us to
// deterministically derive keys.
type KeyLocator struct {
// TODO(roasbeef): add the key scope as well??
// Family is the family of key being identified.
Family KeyFamily
// Index is the precise index of the key being identified.
Index uint32
}
// IsEmpty returns true if a KeyLocator is "empty". This may be the case where
// we learn of a key from a remote party for a contract, but don't know the
// precise details of its derivation (as we don't know the private key!).
func (k KeyLocator) IsEmpty() bool {
return k.Family == 0 && k.Index == 0
}
// KeyDescriptor wraps a KeyLocator and also optionally includes a public key.
// Either the KeyLocator must be non-empty, or the public key pointer be
// non-nil. This will be used by the KeyRing interface to lookup arbitrary
// private keys, and also within the SignDescriptor struct to locate precisely
// which keys should be used for signing.
type KeyDescriptor struct {
// KeyLocator is the internal KeyLocator of the descriptor.
KeyLocator
// PubKey is an optional public key that fully describes a target key.
// If this is nil, the KeyLocator MUST NOT be empty.
PubKey *btcec.PublicKey
}
// KeyRing is the primary interface that will be used to perform public
// derivation of various keys used within the peer-to-peer network, and also
// within any created contracts. All derivation required by the KeyRing is
// based off of public derivation, so a system with only an extended public key
// (for the particular purpose+family) can derive this set of keys.
type KeyRing interface {
// DeriveNextKey attempts to derive the *next* key within the key
// family (account in BIP43) specified. This method should return the
// next external child within this branch.
DeriveNextKey(keyFam KeyFamily) (KeyDescriptor, error)
// DeriveKey attempts to derive an arbitrary key specified by the
// passed KeyLocator. This may be used in several recovery scenarios,
// or when manually rotating something like our current default node
// key.
DeriveKey(keyLoc KeyLocator) (KeyDescriptor, error)
}
// SecretKeyRing is a ring similar to the regular KeyRing interface, but it is
// also able to derive *private keys*. As this is a super-set of the regular
// KeyRing, we also expect the SecretKeyRing to implement the fully KeyRing
// interface. The methods in this struct may be used to extract the node key in
// order to accept inbound network connections, or to do manual signing for
// recovery purposes.
type SecretKeyRing interface {
KeyRing
ECDHRing
MessageSignerRing
// DerivePrivKey attempts to derive the private key that corresponds to
// the passed key descriptor. If the public key is set, then this
// method will perform an in-order scan over the key set, with a max of
// MaxKeyRangeScan keys. In order for this to work, the caller MUST set
// the KeyFamily within the partially populated KeyLocator.
DerivePrivKey(keyDesc KeyDescriptor) (*btcec.PrivateKey, error)
}
// MessageSignerRing is an interface that abstracts away basic low-level ECDSA
// signing on keys within a key ring.
type MessageSignerRing interface {
// SignMessage signs the given message, single or double SHA256 hashing
// it first, with the private key described in the key locator.
SignMessage(keyLoc KeyLocator, msg []byte,
doubleHash bool) (*ecdsa.Signature, error)
// SignMessageCompact signs the given message, single or double SHA256
// hashing it first, with the private key described in the key locator
// and returns the signature in the compact, public key recoverable
// format.
SignMessageCompact(keyLoc KeyLocator, msg []byte,
doubleHash bool) ([]byte, error)
// SignMessageSchnorr signs the given message, single or double SHA256
// hashing it first, with the private key described in the key locator
// and the optional Taproot tweak applied to the private key.
SignMessageSchnorr(keyLoc KeyLocator, msg []byte,
doubleHash bool, taprootTweak []byte,
tag []byte) (*schnorr.Signature, error)
}
// SingleKeyMessageSigner is an abstraction interface that hides the
// implementation of the low-level ECDSA signing operations by wrapping a
// single, specific private key.
type SingleKeyMessageSigner interface {
// PubKey returns the public key of the wrapped private key.
PubKey() *btcec.PublicKey
// KeyLocator returns the locator that describes the wrapped private
// key.
KeyLocator() KeyLocator
// SignMessage signs the given message, single or double SHA256 hashing
// it first, with the wrapped private key.
SignMessage(message []byte, doubleHash bool) (*ecdsa.Signature, error)
// SignMessageCompact signs the given message, single or double SHA256
// hashing it first, with the wrapped private key and returns the
// signature in the compact, public key recoverable format.
SignMessageCompact(message []byte, doubleHash bool) ([]byte, error)
}
// ECDHRing is an interface that abstracts away basic low-level ECDH shared key
// generation on keys within a key ring.
type ECDHRing interface {
// ECDH performs a scalar multiplication (ECDH-like operation) between
// the target key descriptor and remote public key. The output
// returned will be the sha256 of the resulting shared point serialized
// in compressed format. If k is our private key, and P is the public
// key, we perform the following operation:
//
// sx := k*P
// s := sha256(sx.SerializeCompressed())
ECDH(keyDesc KeyDescriptor, pubKey *btcec.PublicKey) ([32]byte, error)
}
// SingleKeyECDH is an abstraction interface that hides the implementation of an
// ECDH operation by wrapping a single, specific private key.
type SingleKeyECDH interface {
// PubKey returns the public key of the wrapped private key.
PubKey() *btcec.PublicKey
// ECDH performs a scalar multiplication (ECDH-like operation) between
// the wrapped private key and remote public key. The output returned
// will be the sha256 of the resulting shared point serialized in
// compressed format.
ECDH(pubKey *btcec.PublicKey) ([32]byte, error)
}
// TODO(roasbeef): extend to actually support scalar mult of key?
// * would allow to push in initial handshake auth into interface as well
package keychain
import (
"crypto/sha256"
"github.com/btcsuite/btcd/btcec/v2"
)
// NewPubKeyECDH wraps the given key of the key ring so it adheres to the
// SingleKeyECDH interface.
func NewPubKeyECDH(keyDesc KeyDescriptor, ecdh ECDHRing) *PubKeyECDH {
return &PubKeyECDH{
keyDesc: keyDesc,
ecdh: ecdh,
}
}
// PubKeyECDH is an implementation of the SingleKeyECDH interface. It wraps an
// ECDH key ring so it can perform ECDH shared key generation against a single
// abstracted away private key.
type PubKeyECDH struct {
keyDesc KeyDescriptor
ecdh ECDHRing
}
// PubKey returns the public key of the private key that is abstracted away by
// the interface.
//
// NOTE: This is part of the SingleKeyECDH interface.
func (p *PubKeyECDH) PubKey() *btcec.PublicKey {
return p.keyDesc.PubKey
}
// ECDH performs a scalar multiplication (ECDH-like operation) between the
// abstracted private key and a remote public key. The output returned will be
// the sha256 of the resulting shared point serialized in compressed format. If
// k is our private key, and P is the public key, we perform the following
// operation:
//
// sx := k*P
// s := sha256(sx.SerializeCompressed())
//
// NOTE: This is part of the SingleKeyECDH interface.
func (p *PubKeyECDH) ECDH(pubKey *btcec.PublicKey) ([32]byte, error) {
return p.ecdh.ECDH(p.keyDesc, pubKey)
}
// PrivKeyECDH is an implementation of the SingleKeyECDH in which we do have the
// full private key. This can be used to wrap a temporary key to conform to the
// SingleKeyECDH interface.
type PrivKeyECDH struct {
// PrivKey is the private key that is used for the ECDH operation.
PrivKey *btcec.PrivateKey
}
// PubKey returns the public key of the private key that is abstracted away by
// the interface.
//
// NOTE: This is part of the SingleKeyECDH interface.
func (p *PrivKeyECDH) PubKey() *btcec.PublicKey {
return p.PrivKey.PubKey()
}
// ECDH performs a scalar multiplication (ECDH-like operation) between the
// abstracted private key and a remote public key. The output returned will be
// the sha256 of the resulting shared point serialized in compressed format. If
// k is our private key, and P is the public key, we perform the following
// operation:
//
// sx := k*P
// s := sha256(sx.SerializeCompressed())
//
// NOTE: This is part of the SingleKeyECDH interface.
func (p *PrivKeyECDH) ECDH(pub *btcec.PublicKey) ([32]byte, error) {
var (
pubJacobian btcec.JacobianPoint
s btcec.JacobianPoint
)
pub.AsJacobian(&pubJacobian)
btcec.ScalarMultNonConst(&p.PrivKey.Key, &pubJacobian, &s)
s.ToAffine()
sPubKey := btcec.NewPublicKey(&s.X, &s.Y)
return sha256.Sum256(sPubKey.SerializeCompressed()), nil
}
var _ SingleKeyECDH = (*PubKeyECDH)(nil)
var _ SingleKeyECDH = (*PrivKeyECDH)(nil)
package keychain
import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
func NewPubKeyMessageSigner(pubKey *btcec.PublicKey, keyLoc KeyLocator,
signer MessageSignerRing) *PubKeyMessageSigner {
return &PubKeyMessageSigner{
pubKey: pubKey,
keyLoc: keyLoc,
digestSigner: signer,
}
}
type PubKeyMessageSigner struct {
pubKey *btcec.PublicKey
keyLoc KeyLocator
digestSigner MessageSignerRing
}
func (p *PubKeyMessageSigner) PubKey() *btcec.PublicKey {
return p.pubKey
}
func (p *PubKeyMessageSigner) KeyLocator() KeyLocator {
return p.keyLoc
}
func (p *PubKeyMessageSigner) SignMessage(message []byte,
doubleHash bool) (*ecdsa.Signature, error) {
return p.digestSigner.SignMessage(p.keyLoc, message, doubleHash)
}
func (p *PubKeyMessageSigner) SignMessageCompact(msg []byte,
doubleHash bool) ([]byte, error) {
return p.digestSigner.SignMessageCompact(p.keyLoc, msg, doubleHash)
}
func NewPrivKeyMessageSigner(privKey *btcec.PrivateKey,
keyLoc KeyLocator) *PrivKeyMessageSigner {
return &PrivKeyMessageSigner{
privKey: privKey,
keyLoc: keyLoc,
}
}
type PrivKeyMessageSigner struct {
keyLoc KeyLocator
privKey *btcec.PrivateKey
}
func (p *PrivKeyMessageSigner) PubKey() *btcec.PublicKey {
return p.privKey.PubKey()
}
func (p *PrivKeyMessageSigner) KeyLocator() KeyLocator {
return p.keyLoc
}
func (p *PrivKeyMessageSigner) SignMessage(msg []byte,
doubleHash bool) (*ecdsa.Signature, error) {
var digest []byte
if doubleHash {
digest = chainhash.DoubleHashB(msg)
} else {
digest = chainhash.HashB(msg)
}
return ecdsa.Sign(p.privKey, digest), nil
}
func (p *PrivKeyMessageSigner) SignMessageCompact(msg []byte,
doubleHash bool) ([]byte, error) {
var digest []byte
if doubleHash {
digest = chainhash.DoubleHashB(msg)
} else {
digest = chainhash.HashB(msg)
}
return ecdsa.SignCompact(p.privKey, digest, true), nil
}
var _ SingleKeyMessageSigner = (*PubKeyMessageSigner)(nil)
var _ SingleKeyMessageSigner = (*PrivKeyMessageSigner)(nil)
// Package labels contains labels used to label transactions broadcast by lnd.
// These labels are used across packages, so they are declared in a separate
// package to avoid dependency issues.
//
// Labels for transactions broadcast by lnd have two set fields followed by an
// optional set labelled data values, all separated by colons.
// - Label version: an integer that indicates the version lnd used
// - Label type: the type of transaction we are labelling
// - {field name}-{value}: a named field followed by its value, these items are
// optional, and there may be more than field present.
//
// For version 0 we have the following optional data fields defined:
// - shortchanid: the short channel ID that a transaction is associated with,
// with its value set to the uint64 short channel id.
package labels
import (
"fmt"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/lightningnetwork/lnd/lnwire"
)
// External labels a transaction as user initiated via the api. This
// label is only used when a custom user provided label is not given.
const External = "external"
// ValidateAPI returns the generic api label if the label provided is empty.
// This allows us to label all transactions published by the api, even if
// no label is provided. If a label is provided, it is validated against
// the known restrictions.
func ValidateAPI(label string) (string, error) {
if len(label) > wtxmgr.TxLabelLimit {
return "", fmt.Errorf("label length: %v exceeds "+
"limit of %v", len(label), wtxmgr.TxLabelLimit)
}
// If no label was provided by the user, add the generic user
// send label.
if len(label) == 0 {
return External, nil
}
return label, nil
}
// LabelVersion versions our labels so they can be easily update to contain
// new data while still easily string matched.
type LabelVersion uint8
// LabelVersionZero is the label version for labels that contain label type and
// channel ID (where available).
const LabelVersionZero LabelVersion = iota
// LabelType indicates the type of label we are creating. It is a string rather
// than an int for easy string matching and human-readability.
type LabelType string
const (
// LabelTypeChannelOpen is used to label channel opens.
LabelTypeChannelOpen LabelType = "openchannel"
// LabelTypeChannelClose is used to label channel closes.
LabelTypeChannelClose LabelType = "closechannel"
// LabelTypeJusticeTransaction is used to label justice transactions.
LabelTypeJusticeTransaction LabelType = "justicetx"
// LabelTypeSweepTransaction is used to label sweeps.
LabelTypeSweepTransaction LabelType = "sweep"
)
// LabelField is used to tag a value within a label.
type LabelField string
const (
// ShortChanID is used to tag short channel id values in our labels.
ShortChanID LabelField = "shortchanid"
)
// MakeLabel creates a label with the provided type and short channel id. If
// our short channel ID is not known, we simply return version:label_type. If
// we do have a short channel ID set, the label will also contain its value:
// shortchanid-{int64 chan ID}.
func MakeLabel(labelType LabelType, channelID *lnwire.ShortChannelID) string {
if channelID == nil {
return fmt.Sprintf("%v:%v", LabelVersionZero, labelType)
}
return fmt.Sprintf("%v:%v:%v-%v", LabelVersionZero, labelType,
ShortChanID, channelID.ToUint64())
}
package lncfg
import (
"context"
"crypto/tls"
"encoding/hex"
"fmt"
"net"
"strconv"
"strings"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tor"
)
// TCPResolver is a function signature that resolves an address on a given
// network.
type TCPResolver = func(network, addr string) (*net.TCPAddr, error)
// NormalizeAddresses returns a new slice with all the passed addresses
// normalized with the given default port and all duplicates removed.
func NormalizeAddresses(addrs []string, defaultPort string,
tcpResolver TCPResolver) ([]net.Addr, error) {
result := make([]net.Addr, 0, len(addrs))
seen := map[string]struct{}{}
for _, addr := range addrs {
parsedAddr, err := ParseAddressString(
addr, defaultPort, tcpResolver,
)
if err != nil {
return nil, fmt.Errorf("parse address %s failed: %w",
addr, err)
}
if _, ok := seen[parsedAddr.String()]; !ok {
result = append(result, parsedAddr)
seen[parsedAddr.String()] = struct{}{}
}
}
return result, nil
}
// EnforceSafeAuthentication enforces "safe" authentication taking into account
// the interfaces that the RPC servers are listening on, and if macaroons and
// TLS is activated or not. To protect users from using dangerous config
// combinations, we'll prevent disabling authentication if the server is
// listening on a public interface.
func EnforceSafeAuthentication(addrs []net.Addr, macaroonsActive,
tlsActive bool) error {
// We'll now examine all addresses that this RPC server is listening
// on. If it's a localhost address or a private address, we'll skip it,
// otherwise, we'll return an error if macaroons are inactive.
for _, addr := range addrs {
if IsLoopback(addr.String()) || IsUnix(addr) || IsPrivate(addr) {
continue
}
if !macaroonsActive {
return fmt.Errorf("detected RPC server listening on "+
"publicly reachable interface %v with "+
"authentication disabled! Refusing to start "+
"with --no-macaroons specified", addr)
}
if !tlsActive {
return fmt.Errorf("detected RPC server listening on "+
"publicly reachable interface %v with "+
"encryption disabled! Refusing to start "+
"with --no-rest-tls specified", addr)
}
}
return nil
}
// parseNetwork parses the network type of the given address.
func parseNetwork(addr net.Addr) string {
switch addr := addr.(type) {
// TCP addresses resolved through net.ResolveTCPAddr give a default
// network of "tcp", so we'll map back the correct network for the given
// address. This ensures that we can listen on the correct interface
// (IPv4 vs IPv6).
case *net.TCPAddr:
if addr.IP.To4() != nil {
return "tcp4"
}
return "tcp6"
default:
return addr.Network()
}
}
// ListenOnAddress creates a listener that listens on the given address.
func ListenOnAddress(addr net.Addr) (net.Listener, error) {
return net.Listen(parseNetwork(addr), addr.String())
}
// TLSListenOnAddress creates a TLS listener that listens on the given address.
func TLSListenOnAddress(addr net.Addr,
config *tls.Config) (net.Listener, error) {
return tls.Listen(parseNetwork(addr), addr.String(), config)
}
// IsLoopback returns true if an address describes a loopback interface.
func IsLoopback(host string) bool {
if strings.Contains(host, "localhost") {
return true
}
rawHost, _, _ := net.SplitHostPort(host)
addr := net.ParseIP(rawHost)
if addr == nil {
return false
}
return addr.IsLoopback()
}
// isIPv6Host returns true if the host is IPV6 and false otherwise.
func isIPv6Host(host string) bool {
v6Addr := net.ParseIP(host)
if v6Addr == nil {
return false
}
// The documentation states that if the IP address is an IPv6 address,
// then To4() will return nil.
return v6Addr.To4() == nil
}
// isUnspecifiedHost returns true if the host IP is considered unspecified.
func isUnspecifiedHost(host string) bool {
addr := net.ParseIP(host)
if addr == nil {
return false
}
return addr.IsUnspecified()
}
// IsUnix returns true if an address describes an Unix socket address.
func IsUnix(addr net.Addr) bool {
return strings.HasPrefix(addr.Network(), "unix")
}
// IsPrivate returns true if the address is private. The definitions are,
//
// https://en.wikipedia.org/wiki/Link-local_address
// https://en.wikipedia.org/wiki/Multicast_address
// Local IPv4 addresses, https://tools.ietf.org/html/rfc1918
// Local IPv6 addresses, https://tools.ietf.org/html/rfc4193
func IsPrivate(addr net.Addr) bool {
switch addr := addr.(type) {
case *net.TCPAddr:
// Check 169.254.0.0/16 and fe80::/10.
if addr.IP.IsLinkLocalUnicast() {
return true
}
// Check 224.0.0.0/4 and ff00::/8.
if addr.IP.IsLinkLocalMulticast() {
return true
}
// Check 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16.
if ip4 := addr.IP.To4(); ip4 != nil {
return ip4[0] == 10 ||
(ip4[0] == 172 && ip4[1]&0xf0 == 16) ||
(ip4[0] == 192 && ip4[1] == 168)
}
// Check fc00::/7.
return len(addr.IP) == net.IPv6len && addr.IP[0]&0xfe == 0xfc
default:
return false
}
}
// ParseAddressString converts an address in string format to a net.Addr that is
// compatible with lnd. UDP is not supported because lnd needs reliable
// connections. We accept a custom function to resolve any TCP addresses so
// that caller is able control exactly how resolution is performed.
func ParseAddressString(strAddress string, defaultPort string,
tcpResolver TCPResolver) (net.Addr, error) {
var parsedNetwork, parsedAddr string
// Addresses can either be in network://address:port format,
// network:address:port, address:port, or just port. We want to support
// all possible types.
if strings.Contains(strAddress, "://") {
parts := strings.Split(strAddress, "://")
parsedNetwork, parsedAddr = parts[0], parts[1]
} else if strings.Contains(strAddress, ":") {
parts := strings.Split(strAddress, ":")
parsedNetwork = parts[0]
parsedAddr = strings.Join(parts[1:], ":")
}
// Only TCP and Unix socket addresses are valid. We can't use IP or
// UDP only connections for anything we do in lnd.
switch parsedNetwork {
case "unix", "unixpacket":
return net.ResolveUnixAddr(parsedNetwork, parsedAddr)
case "tcp", "tcp4", "tcp6":
return tcpResolver(
parsedNetwork, verifyPort(parsedAddr, defaultPort),
)
case "ip", "ip4", "ip6", "udp", "udp4", "udp6", "unixgram":
return nil, fmt.Errorf("only TCP or unix socket "+
"addresses are supported: %s", parsedAddr)
default:
// We'll now possibly apply the default port, use the local
// host short circuit, or parse out an all interfaces listen.
addrWithPort := verifyPort(strAddress, defaultPort)
rawHost, rawPort, _ := net.SplitHostPort(addrWithPort)
// If we reach this point, then we'll check to see if we have
// an onion addresses, if so, we can directly pass the raw
// address and port to create the proper address.
if tor.IsOnionHost(rawHost) {
portNum, err := strconv.Atoi(rawPort)
if err != nil {
return nil, err
}
return &tor.OnionAddr{
OnionService: rawHost,
Port: portNum,
}, nil
}
// Otherwise, we'll attempt the resolve the host. The Tor
// resolver is unable to resolve local addresses,
// IPv6 addresses, or the all-interfaces address, so we'll use
// the system resolver instead for those.
if rawHost == "" || IsLoopback(rawHost) ||
isIPv6Host(rawHost) || isUnspecifiedHost(rawHost) {
return net.ResolveTCPAddr("tcp", addrWithPort)
}
// If we've reached this point, then it's possible that this
// resolve returns an error if it isn't able to resolve the
// host. For example, local entries in /etc/hosts will fail to
// be resolved by Tor. In order to handle this case, we'll fall
// back to the normal system resolver if we fail with an
// identifiable error.
addr, err := tcpResolver("tcp", addrWithPort)
if err != nil {
torErrStr := "tor host is unreachable"
if strings.Contains(err.Error(), torErrStr) {
return net.ResolveTCPAddr("tcp", addrWithPort)
}
return nil, err
}
return addr, nil
}
}
// ParseLNAddressString converts a string of the form <pubkey>@<addr> into an
// lnwire.NetAddress. The <pubkey> must be presented in hex, and result in a
// 33-byte, compressed public key that lies on the secp256k1 curve. The <addr>
// may be any address supported by ParseAddressString. If no port is specified,
// the defaultPort will be used. Any tcp addresses that need resolving will be
// resolved using the custom TCPResolver.
func ParseLNAddressString(strAddress string, defaultPort string,
tcpResolver TCPResolver) (*lnwire.NetAddress, error) {
pubKey, parsedAddr, err := ParseLNAddressPubkey(strAddress)
if err != nil {
return nil, err
}
// Finally, parse the address string using our generic address parser.
addr, err := ParseAddressString(parsedAddr, defaultPort, tcpResolver)
if err != nil {
return nil, fmt.Errorf("invalid lightning address address: %w",
err)
}
return &lnwire.NetAddress{
IdentityKey: pubKey,
Address: addr,
}, nil
}
// ParseLNAddressPubkey converts a string of the form <pubkey>@<addr> into two
// pieces: the pubkey bytes and an addr string. It validates that the pubkey
// is of a valid form.
func ParseLNAddressPubkey(strAddress string) (*btcec.PublicKey, string, error) {
// Split the address string around the @ sign.
parts := strings.Split(strAddress, "@")
// The string is malformed if there are not exactly two parts.
if len(parts) != 2 {
return nil, "", fmt.Errorf("invalid lightning address %s: "+
"must be of the form <pubkey-hex>@<addr>", strAddress)
}
// Now, take the first portion as the hex pubkey, and the latter as the
// address string.
parsedPubKey, parsedAddr := parts[0], parts[1]
// Decode the hex pubkey to get the raw compressed pubkey bytes.
pubKeyBytes, err := hex.DecodeString(parsedPubKey)
if err != nil {
return nil, "", fmt.Errorf("invalid lightning address "+
"pubkey: %w", err)
}
// The compressed pubkey should have a length of exactly 33 bytes.
if len(pubKeyBytes) != 33 {
return nil, "", fmt.Errorf("invalid lightning address pubkey: "+
"length must be 33 bytes, found %d", len(pubKeyBytes))
}
// Parse the pubkey bytes to verify that it corresponds to valid public
// key on the secp256k1 curve.
pubKey, err := btcec.ParsePubKey(pubKeyBytes)
if err != nil {
return nil, "", fmt.Errorf("invalid lightning address "+
"pubkey: %w", err)
}
return pubKey, parsedAddr, nil
}
// verifyPort makes sure that an address string has both a host and a port. If
// there is no port found, the default port is appended. If the address is just
// a port, then we'll assume that the user is using the short cut to specify a
// localhost:port address.
func verifyPort(address string, defaultPort string) string {
host, port, err := net.SplitHostPort(address)
if err != nil {
// If the address itself is just an integer, then we'll assume
// that we're mapping this directly to a localhost:port pair.
// This ensures we maintain the legacy behavior.
if _, err := strconv.Atoi(address); err == nil {
return net.JoinHostPort("localhost", address)
}
// Otherwise, we'll assume that the address just failed to
// attach its own port, so we'll use the default port. In the
// case of IPv6 addresses, if the host is already surrounded by
// brackets, then we'll avoid using the JoinHostPort function,
// since it will always add a pair of brackets.
if strings.HasPrefix(address, "[") {
return address + ":" + defaultPort
}
return net.JoinHostPort(address, defaultPort)
}
// In the case that both the host and port are empty, we'll use the
// default port.
if host == "" && port == "" {
return ":" + defaultPort
}
return address
}
// ClientAddressDialer creates a gRPC dialer that can also dial unix socket
// addresses instead of just TCP addresses.
func ClientAddressDialer(defaultPort string) func(context.Context,
string) (net.Conn, error) {
return func(ctx context.Context, addr string) (net.Conn, error) {
parsedAddr, err := ParseAddressString(
addr, defaultPort, net.ResolveTCPAddr,
)
if err != nil {
return nil, err
}
d := net.Dialer{}
return d.DialContext(
ctx, parsedAddr.Network(), parsedAddr.String(),
)
}
}
package lncfg
import (
"fmt"
"time"
)
const (
// MinRejectCacheSize is a floor on the maximum capacity allowed for
// channeldb's reject cache. This amounts to roughly 125 KB when full.
MinRejectCacheSize = 5000
// MinChannelCacheSize is a floor on the maximum capacity allowed for
// channeldb's channel cache. This amounts to roughly 2 MB when full.
MinChannelCacheSize = 1000
// DefaultRPCGraphCacheDuration is the default interval that the RPC
// response to DescribeGraph should be cached for.
DefaultRPCGraphCacheDuration = time.Minute
)
// Caches holds the configuration for various caches within lnd.
//
//nolint:ll
type Caches struct {
// RejectCacheSize is the maximum number of entries stored in lnd's
// reject cache, which is used for efficiently rejecting gossip updates.
// Memory usage is roughly 25b per entry.
RejectCacheSize int `long:"reject-cache-size" description:"Maximum number of entries contained in the reject cache, which is used to speed up filtering of new channel announcements and channel updates from peers. Each entry requires 25 bytes."`
// ChannelCacheSize is the maximum number of entries stored in lnd's
// channel cache, which is used reduce memory allocations in reply to
// peers querying for gossip traffic. Memory usage is roughly 2Kb per
// entry.
ChannelCacheSize int `long:"channel-cache-size" description:"Maximum number of entries contained in the channel cache, which is used to reduce memory allocations from gossip queries from peers. Each entry requires roughly 2Kb."`
// RPCGraphCacheDuration is used to control the flush interval of the
// channel graph cache.
RPCGraphCacheDuration time.Duration `long:"rpc-graph-cache-duration" description:"The period of time expressed as a duration (1s, 1m, 1h, etc) that the RPC response to DescribeGraph should be cached for."`
}
// Validate checks the Caches configuration for values that are too small to be
// sane.
func (c *Caches) Validate() error {
if c.RejectCacheSize < MinRejectCacheSize {
return fmt.Errorf("reject cache size %d is less than min: %d",
c.RejectCacheSize, MinRejectCacheSize)
}
if c.ChannelCacheSize < MinChannelCacheSize {
return fmt.Errorf("channel cache size %d is less than min: %d",
c.ChannelCacheSize, MinChannelCacheSize)
}
return nil
}
// Compile-time constraint to ensure Caches implements the Validator interface.
var _ Validator = (*Caches)(nil)
package lncfg
import (
"fmt"
"github.com/lightningnetwork/lnd/lnwire"
)
// Chain holds the configuration options for the daemon's chain settings.
//
//nolint:ll
type Chain struct {
Active bool `long:"active" description:"DEPRECATED: If the chain should be active or not. This field is now ignored since only the Bitcoin chain is supported" hidden:"true"`
ChainDir string `long:"chaindir" description:"The directory to store the chain's data within."`
Node string `long:"node" description:"The blockchain interface to use." choice:"btcd" choice:"bitcoind" choice:"neutrino" choice:"nochainbackend"`
MainNet bool `long:"mainnet" description:"Use the main network"`
TestNet3 bool `long:"testnet" description:"Use the test network"`
TestNet4 bool `long:"testnet4" description:"Use the testnet4 test network"`
SimNet bool `long:"simnet" description:"Use the simulation test network"`
RegTest bool `long:"regtest" description:"Use the regression test network"`
SigNet bool `long:"signet" description:"Use the signet test network"`
SigNetChallenge string `long:"signetchallenge" description:"Connect to a custom signet network defined by this challenge instead of using the global default signet test network -- Can be specified multiple times"`
SigNetSeedNode []string `long:"signetseednode" description:"Specify a seed node for the signet network instead of using the global default signet network seed nodes"`
DefaultNumChanConfs int `long:"defaultchanconfs" description:"The default number of confirmations a channel must have before it's considered open. If this is not set, we will scale the value according to the channel size."`
DefaultRemoteDelay int `long:"defaultremotedelay" description:"The default number of blocks we will require our channel counterparty to wait before accessing its funds in case of unilateral close. If this is not set, we will scale the value according to the channel size."`
MaxLocalDelay uint16 `long:"maxlocaldelay" description:"The maximum blocks we will allow our funds to be timelocked before accessing its funds in case of unilateral close. If a peer proposes a value greater than this, we will reject the channel."`
MinHTLCIn lnwire.MilliSatoshi `long:"minhtlc" description:"The smallest HTLC we are willing to accept on our channels, in millisatoshi"`
MinHTLCOut lnwire.MilliSatoshi `long:"minhtlcout" description:"The smallest HTLC we are willing to send out on our channels, in millisatoshi"`
BaseFee lnwire.MilliSatoshi `long:"basefee" description:"The base fee in millisatoshi we will charge for forwarding payments on our channels"`
FeeRate lnwire.MilliSatoshi `long:"feerate" description:"The fee rate used when forwarding payments on our channels. The total fee charged is basefee + (amount * feerate / 1000000), where amount is the forwarded amount."`
TimeLockDelta uint32 `long:"timelockdelta" description:"The CLTV delta we will subtract from a forwarded HTLC's timelock value"`
DNSSeeds []string `long:"dnsseed" description:"The seed DNS server(s) to use for initial peer discovery. Must be specified as a '<primary_dns>[,<soa_primary_dns>]' tuple where the SOA address is needed for DNS resolution through Tor but is optional for clearnet users. Multiple tuples can be specified, will overwrite the default seed servers."`
}
// Validate performs validation on our chain config.
func (c *Chain) Validate(minTimeLockDelta uint32, minDelay uint16) error {
if c.TimeLockDelta < minTimeLockDelta {
return fmt.Errorf("timelockdelta must be at least %v",
minTimeLockDelta)
}
// Check that our max local delay isn't set below some reasonable
// minimum value. We do this to prevent setting an unreasonably low
// delay, which would mean that the node would accept no channels.
if c.MaxLocalDelay < minDelay {
return fmt.Errorf("MaxLocalDelay must be at least: %v",
minDelay)
}
return nil
}
package lncfg
import (
"context"
"fmt"
"os"
"github.com/lightningnetwork/lnd/cluster"
)
const (
// DefaultEtcdElectionPrefix is used as election prefix if none is provided
// through the config.
DefaultEtcdElectionPrefix = "/leader/"
)
// Cluster holds configuration for clustered LND.
type Cluster struct {
EnableLeaderElection bool `long:"enable-leader-election" description:"Enables leader election if set."`
LeaderElector string `long:"leader-elector" choice:"etcd" description:"Leader elector to use. Valid values: \"etcd\"."`
EtcdElectionPrefix string `long:"etcd-election-prefix" description:"Election key prefix when using etcd leader elector."`
ID string `long:"id" description:"Identifier for this node inside the cluster (used in leader election). Defaults to the hostname."`
LeaderSessionTTL int `long:"leader-session-ttl" description:"The TTL in seconds to use for the leader election session."`
}
// DefaultCluster creates and returns a new default DB config.
func DefaultCluster() *Cluster {
hostname, _ := os.Hostname()
return &Cluster{
LeaderElector: cluster.EtcdLeaderElector,
EtcdElectionPrefix: DefaultEtcdElectionPrefix,
LeaderSessionTTL: 90,
ID: hostname,
}
}
// MakeLeaderElector is a helper method to construct the concrete leader elector
// based on the current configuration.
func (c *Cluster) MakeLeaderElector(electionCtx context.Context, db *DB) (
cluster.LeaderElector, error) {
if c.LeaderElector == cluster.EtcdLeaderElector {
return cluster.MakeLeaderElector(
electionCtx, c.LeaderElector, c.ID,
c.EtcdElectionPrefix, c.LeaderSessionTTL, db.Etcd,
)
}
return nil, fmt.Errorf("unsupported leader elector")
}
// Validate validates the Cluster config.
func (c *Cluster) Validate() error {
if !c.EnableLeaderElection {
return nil
}
switch c.LeaderElector {
case cluster.EtcdLeaderElector:
if c.EtcdElectionPrefix == "" {
return fmt.Errorf("etcd-election-prefix must be set")
}
return nil
default:
return fmt.Errorf("unknown leader elector, valid values are: "+
"\"%v\"", cluster.EtcdLeaderElector)
}
}
// Compile-time constraint to ensure Workers implements the Validator interface.
var _ Validator = (*Cluster)(nil)
package lncfg
import (
"encoding/hex"
"errors"
"image/color"
"regexp"
)
var (
// validColorRegexp is a regexp that lets you check if a particular
// color string matches the standard hex color format #RRGGBB.
validColorRegexp = regexp.MustCompile("^#[A-Fa-f0-9]{6}$")
)
// ParseHexColor takes a hex string representation of a color in the
// form "#RRGGBB", parses the hex color values, and returns a color.RGBA
// struct of the same color.
func ParseHexColor(colorStr string) (color.RGBA, error) {
// Check if the hex color string is a valid color representation.
if !validColorRegexp.MatchString(colorStr) {
return color.RGBA{}, errors.New("color must be specified " +
"using a hexadecimal value in the form #RRGGBB")
}
// Decode the hex color string to bytes.
// The resulting byte array is in the form [R, G, B].
colorBytes, err := hex.DecodeString(colorStr[1:])
if err != nil {
return color.RGBA{}, err
}
return color.RGBA{R: colorBytes[0], G: colorBytes[1], B: colorBytes[2]}, nil
}
package lncfg
import (
"os"
"os/user"
"path/filepath"
"strings"
"time"
)
const (
// DefaultConfigFilename is the default configuration file name lnd
// tries to load.
DefaultConfigFilename = "lnd.conf"
// DefaultMaxPendingChannels is the default maximum number of incoming
// pending channels permitted per peer.
DefaultMaxPendingChannels = 1
// DefaultIncomingBroadcastDelta defines the number of blocks before the
// expiry of an incoming htlc at which we force close the channel. We
// only go to chain if we also have the preimage to actually pull in the
// htlc. BOLT #2 suggests 7 blocks. We use a few more for extra safety.
// Within this window we need to get our sweep or 2nd level success tx
// confirmed, because after that the remote party is also able to claim
// the htlc using the timeout path.
DefaultIncomingBroadcastDelta = 10
// DefaultFinalCltvRejectDelta defines the number of blocks before the
// expiry of an incoming exit hop htlc at which we cancel it back
// immediately. It is an extra safety measure over the final cltv
// requirement as it is defined in the invoice. It ensures that we
// cancel back htlcs that, when held on to, may cause us to force close
// the channel because we enter the incoming broadcast window. Bolt #11
// suggests 9 blocks here. We use a few more for additional safety.
//
// There is still a small gap that remains between receiving the
// RevokeAndAck and canceling back. If a new block arrives within that
// window, we may still force close the channel. There is currently no
// way to reject an UpdateAddHtlc of which we already know that it will
// push us in the broadcast window.
DefaultFinalCltvRejectDelta = DefaultIncomingBroadcastDelta + 3
// DefaultCltvInterceptDelta defines the number of blocks before the
// expiry of the htlc where we don't intercept anymore. This value must
// be greater than CltvRejectDelta, because we don't want to offer htlcs
// to the interceptor client for which there is no time left to resolve
// them anymore.
DefaultCltvInterceptDelta = DefaultFinalCltvRejectDelta + 3
// DefaultOutgoingBroadcastDelta defines the number of blocks before the
// expiry of an outgoing htlc at which we force close the channel. We
// are not in a hurry to force close, because there is nothing to claim
// for us. We do need to time the htlc out, because there may be an
// incoming htlc that will time out too (albeit later). Bolt #2 suggests
// a value of -1 here, but we allow one block less to prevent potential
// confusion around the negative value. It means we force close the
// channel at exactly the htlc expiry height.
DefaultOutgoingBroadcastDelta = 0
// DefaultOutgoingCltvRejectDelta defines the number of blocks before
// the expiry of an outgoing htlc at which we don't want to offer it to
// the next peer anymore. If that happens, we cancel back the incoming
// htlc. This is to prevent the situation where we have an outstanding
// htlc that brings or will soon bring us inside the outgoing broadcast
// window and trigger us to force close the channel. Bolt #2 suggests a
// value of 0. We pad it a bit, to prevent a slow round trip to the next
// peer and a block arriving during that round trip to trigger force
// closure.
DefaultOutgoingCltvRejectDelta = DefaultOutgoingBroadcastDelta + 3
// DefaultZombieSweeperInterval is the default time interval at which
// unfinished (zombiestate) open channel flows are purged from memory.
DefaultZombieSweeperInterval = 1 * time.Minute
// DefaultMaxWaitNumBlocksFundingConf is the maximum number of blocks to
// wait for the funding transaction to confirm before forgetting
// channels that aren't initiated by us. 2016 blocks is ~2 weeks.
DefaultMaxWaitNumBlocksFundingConf = 2016
)
// CleanAndExpandPath expands environment variables and leading ~ in the
// passed path, cleans the result, and returns it.
// This function is taken from https://github.com/btcsuite/btcd
func CleanAndExpandPath(path string) string {
if path == "" {
return ""
}
// Expand initial ~ to OS specific home directory.
if strings.HasPrefix(path, "~") {
var homeDir string
u, err := user.Current()
if err == nil {
homeDir = u.HomeDir
} else {
homeDir = os.Getenv("HOME")
}
path = strings.Replace(path, "~", homeDir, 1)
}
// NOTE: The os.ExpandEnv doesn't work with Windows-style %VARIABLE%,
// but the variables can still be expanded via POSIX-style $VARIABLE.
return filepath.Clean(os.ExpandEnv(path))
}
// NormalizeNetwork returns the common name of a network type used to create
// file paths. This allows differently versioned networks to use the same path.
func NormalizeNetwork(network string) string {
// The 4th testnet isn't the "default" yet, so we'll want to explicitly
// point that to a "testnet4" directory.
if network == "testnet4" {
return network
}
// We want to collapse "testnet3" and "testnet" to the same "testnet"
// directory.
if strings.HasPrefix(network, "testnet") {
return "testnet"
}
return network
}
package lncfg
import (
"context"
"fmt"
"path"
"path/filepath"
"time"
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/kvdb/etcd"
"github.com/lightningnetwork/lnd/kvdb/postgres"
"github.com/lightningnetwork/lnd/kvdb/sqlbase"
"github.com/lightningnetwork/lnd/kvdb/sqlite"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"github.com/lightningnetwork/lnd/sqldb"
)
const (
ChannelDBName = "channel.db"
MacaroonDBName = "macaroons.db"
DecayedLogDbName = "sphinxreplay.db"
TowerClientDBName = "wtclient.db"
TowerServerDBName = "watchtower.db"
WalletDBName = "wallet.db"
NeutrinoDBName = "neutrino.db"
SqliteChannelDBName = "channel.sqlite"
SqliteChainDBName = "chain.sqlite"
SqliteNeutrinoDBName = "neutrino.sqlite"
SqliteTowerDBName = "watchtower.sqlite"
SqliteNativeDBName = "lnd.sqlite"
BoltBackend = "bolt"
EtcdBackend = "etcd"
PostgresBackend = "postgres"
SqliteBackend = "sqlite"
DefaultBatchCommitInterval = 500 * time.Millisecond
defaultPostgresMaxConnections = 50
defaultSqliteMaxConnections = 2
defaultSqliteBusyTimeout = 5 * time.Second
// NSChannelDB is the namespace name that we use for the combined graph
// and channel state DB.
NSChannelDB = "channeldb"
// NSMacaroonDB is the namespace name that we use for the macaroon DB.
NSMacaroonDB = "macaroondb"
// NSDecayedLogDB is the namespace name that we use for the sphinx
// replay a.k.a. decayed log DB.
NSDecayedLogDB = "decayedlogdb"
// NSTowerClientDB is the namespace name that we use for the watchtower
// client DB.
NSTowerClientDB = "towerclientdb"
// NSTowerServerDB is the namespace name that we use for the watchtower
// server DB.
NSTowerServerDB = "towerserverdb"
// NSWalletDB is the namespace name that we use for the wallet DB.
NSWalletDB = "walletdb"
// NSNeutrinoDB is the namespace name that we use for the neutrino DB.
NSNeutrinoDB = "neutrinodb"
)
// DB holds database configuration for LND.
//
//nolint:ll
type DB struct {
Backend string `long:"backend" description:"The selected database backend."`
BatchCommitInterval time.Duration `long:"batch-commit-interval" description:"The maximum duration the channel graph batch schedulers will wait before attempting to commit a batch of pending updates. This can be tradeoff database contenion for commit latency."`
Etcd *etcd.Config `group:"etcd" namespace:"etcd" description:"Etcd settings."`
Bolt *kvdb.BoltConfig `group:"bolt" namespace:"bolt" description:"Bolt settings."`
Postgres *sqldb.PostgresConfig `group:"postgres" namespace:"postgres" description:"Postgres settings."`
Sqlite *sqldb.SqliteConfig `group:"sqlite" namespace:"sqlite" description:"Sqlite settings."`
UseNativeSQL bool `long:"use-native-sql" description:"Use native SQL for tables that already support it."`
SkipNativeSQLMigration bool `long:"skip-native-sql-migration" description:"Do not run the KV to native SQL migration. It should only be used if errors are encountered normally."`
NoGraphCache bool `long:"no-graph-cache" description:"Don't use the in-memory graph cache for path finding. Much slower but uses less RAM. Can only be used with a bolt database backend."`
PruneRevocation bool `long:"prune-revocation" description:"Run the optional migration that prunes the revocation logs to save disk space."`
NoRevLogAmtData bool `long:"no-rev-log-amt-data" description:"If set, the to-local and to-remote output amounts of revoked commitment transactions will not be stored in the revocation log. Note that once this data is lost, a watchtower client will not be able to back up the revoked state."`
}
// DefaultDB creates and returns a new default DB config.
func DefaultDB() *DB {
return &DB{
Backend: BoltBackend,
BatchCommitInterval: DefaultBatchCommitInterval,
Bolt: &kvdb.BoltConfig{
NoFreelistSync: true,
AutoCompactMinAge: kvdb.DefaultBoltAutoCompactMinAge,
DBTimeout: kvdb.DefaultDBTimeout,
},
Etcd: &etcd.Config{
// Allow at most 32 MiB messages by default.
MaxMsgSize: 32768 * 1024,
},
Postgres: &sqldb.PostgresConfig{
MaxConnections: defaultPostgresMaxConnections,
},
Sqlite: &sqldb.SqliteConfig{
MaxConnections: defaultSqliteMaxConnections,
BusyTimeout: defaultSqliteBusyTimeout,
},
UseNativeSQL: false,
SkipNativeSQLMigration: false,
}
}
// Validate validates the DB config.
func (db *DB) Validate() error {
switch db.Backend {
case BoltBackend:
if db.UseNativeSQL {
return fmt.Errorf("cannot use native SQL with bolt " +
"backend")
}
case SqliteBackend:
case PostgresBackend:
if err := db.Postgres.Validate(); err != nil {
return err
}
case EtcdBackend:
if db.UseNativeSQL {
return fmt.Errorf("cannot use native SQL with etcd " +
"backend")
}
if !db.Etcd.Embedded && db.Etcd.Host == "" {
return fmt.Errorf("etcd host must be set")
}
default:
return fmt.Errorf("unknown backend, must be either '%v', "+
"'%v', '%v' or '%v'", BoltBackend, EtcdBackend,
PostgresBackend, SqliteBackend)
}
// The path finding uses a manual read transaction that's open for a
// potentially long time. That works fine with the locking model of
// bbolt but can lead to locks or rolled back transactions with etcd or
// postgres. And since we already have a smaller memory footprint for
// remote database setups (due to not needing to memory-map the bbolt DB
// files), we can keep the graph in memory instead. But for mobile
// devices the tradeoff between a smaller memory footprint and the
// longer time needed for path finding might be a desirable one.
if db.NoGraphCache && db.Backend != BoltBackend {
return fmt.Errorf("cannot use no-graph-cache with database "+
"backend '%v'", db.Backend)
}
return nil
}
// Init should be called upon start to pre-initialize database access dependent
// on configuration.
func (db *DB) Init(ctx context.Context, dbPath string) error {
// Start embedded etcd server if requested.
switch {
case db.Backend == EtcdBackend && db.Etcd.Embedded:
cfg, _, err := kvdb.StartEtcdTestBackend(
dbPath, db.Etcd.EmbeddedClientPort,
db.Etcd.EmbeddedPeerPort, db.Etcd.EmbeddedLogFile,
)
if err != nil {
return err
}
// Override the original config with the config for
// the embedded instance.
db.Etcd = cfg
case db.Backend == PostgresBackend:
sqlbase.Init(db.Postgres.MaxConnections)
case db.Backend == SqliteBackend:
sqlbase.Init(db.Sqlite.MaxConnections)
}
return nil
}
// DatabaseBackends is a two-tuple that holds the set of active database
// backends for the daemon. The two backends we expose are the graph database
// backend, and the channel state backend.
type DatabaseBackends struct {
// GraphDB points to the database backend that contains the less
// critical data that is accessed often, such as the channel graph and
// chain height hints.
GraphDB kvdb.Backend
// ChanStateDB points to a possibly networked replicated backend that
// contains the critical channel state related data.
ChanStateDB kvdb.Backend
// HeightHintDB points to a possibly networked replicated backend that
// contains the chain height hint related data.
HeightHintDB kvdb.Backend
// MacaroonDB points to a database backend that stores the macaroon root
// keys.
MacaroonDB kvdb.Backend
// DecayedLogDB points to a database backend that stores the decayed log
// data.
DecayedLogDB kvdb.Backend
// TowerClientDB points to a database backend that stores the watchtower
// client data. This might be nil if the watchtower client is disabled.
TowerClientDB kvdb.Backend
// TowerServerDB points to a database backend that stores the watchtower
// server data. This might be nil if the watchtower server is disabled.
TowerServerDB kvdb.Backend
// WalletDB is an option that instructs the wallet loader where to load
// the underlying wallet database from.
WalletDB btcwallet.LoaderOption
// NativeSQLStore holds a reference to the native SQL store that can
// be used for native SQL queries for tables that already support it.
// This may be nil if the use-native-sql flag was not set.
NativeSQLStore sqldb.DB
// Remote indicates whether the database backends are remote, possibly
// replicated instances or local bbolt or sqlite backed databases.
Remote bool
// CloseFuncs is a map of close functions for each of the initialized
// DB backends keyed by their namespace name.
CloseFuncs map[string]func() error
}
// GetPostgresConfigKVDB converts a sqldb.PostgresConfig to a kvdb
// postgres.Config.
func GetPostgresConfigKVDB(cfg *sqldb.PostgresConfig) *postgres.Config {
return &postgres.Config{
Dsn: cfg.Dsn,
Timeout: cfg.Timeout,
MaxConnections: cfg.MaxConnections,
}
}
// GetSqliteConfigKVDB converts a sqldb.SqliteConfig to a kvdb sqlite.Config.
func GetSqliteConfigKVDB(cfg *sqldb.SqliteConfig) *sqlite.Config {
return &sqlite.Config{
Timeout: cfg.Timeout,
BusyTimeout: cfg.BusyTimeout,
MaxConnections: cfg.MaxConnections,
PragmaOptions: cfg.PragmaOptions,
}
}
// GetBackends returns a set of kvdb.Backends as set in the DB config.
func (db *DB) GetBackends(ctx context.Context, chanDBPath,
walletDBPath, towerServerDBPath string, towerClientEnabled,
towerServerEnabled bool, logger btclog.Logger) (*DatabaseBackends,
error) {
// We keep track of all the kvdb backends we actually open and return a
// reference to their close function so they can be cleaned up properly
// on error or shutdown.
closeFuncs := make(map[string]func() error)
// If we need to return early because of an error, we invoke any close
// function that has been initialized so far.
returnEarly := true
defer func() {
if !returnEarly {
return
}
for _, closeFunc := range closeFuncs {
_ = closeFunc()
}
}()
switch db.Backend {
case EtcdBackend:
// As long as the graph data, channel state and height hint
// cache are all still in the channel.db file in bolt, we
// replicate the same behavior here and use the same etcd
// backend for those three sub DBs. But we namespace it properly
// to make such a split even easier in the future. This will
// break lnd for users that ran on etcd with 0.13.x since that
// code used the root namespace. We assume that nobody used etcd
// for mainnet just yet since that feature was clearly marked as
// experimental in 0.13.x.
etcdBackend, err := kvdb.Open(
kvdb.EtcdBackendName, ctx,
db.Etcd.CloneWithSubNamespace(NSChannelDB),
)
if err != nil {
return nil, fmt.Errorf("error opening etcd DB: %w", err)
}
closeFuncs[NSChannelDB] = etcdBackend.Close
etcdMacaroonBackend, err := kvdb.Open(
kvdb.EtcdBackendName, ctx,
db.Etcd.CloneWithSubNamespace(NSMacaroonDB),
)
if err != nil {
return nil, fmt.Errorf("error opening etcd macaroon "+
"DB: %v", err)
}
closeFuncs[NSMacaroonDB] = etcdMacaroonBackend.Close
etcdDecayedLogBackend, err := kvdb.Open(
kvdb.EtcdBackendName, ctx,
db.Etcd.CloneWithSubNamespace(NSDecayedLogDB),
)
if err != nil {
return nil, fmt.Errorf("error opening etcd decayed "+
"log DB: %v", err)
}
closeFuncs[NSDecayedLogDB] = etcdDecayedLogBackend.Close
etcdTowerClientBackend, err := kvdb.Open(
kvdb.EtcdBackendName, ctx,
db.Etcd.CloneWithSubNamespace(NSTowerClientDB),
)
if err != nil {
return nil, fmt.Errorf("error opening etcd tower "+
"client DB: %v", err)
}
closeFuncs[NSTowerClientDB] = etcdTowerClientBackend.Close
etcdTowerServerBackend, err := kvdb.Open(
kvdb.EtcdBackendName, ctx,
db.Etcd.CloneWithSubNamespace(NSTowerServerDB),
)
if err != nil {
return nil, fmt.Errorf("error opening etcd tower "+
"server DB: %v", err)
}
closeFuncs[NSTowerServerDB] = etcdTowerServerBackend.Close
etcdWalletBackend, err := kvdb.Open(
kvdb.EtcdBackendName, ctx,
db.Etcd.
CloneWithSubNamespace(NSWalletDB).
CloneWithSingleWriter(),
)
if err != nil {
return nil, fmt.Errorf("error opening etcd macaroon "+
"DB: %v", err)
}
closeFuncs[NSWalletDB] = etcdWalletBackend.Close
returnEarly = false
return &DatabaseBackends{
GraphDB: etcdBackend,
ChanStateDB: etcdBackend,
HeightHintDB: etcdBackend,
MacaroonDB: etcdMacaroonBackend,
DecayedLogDB: etcdDecayedLogBackend,
TowerClientDB: etcdTowerClientBackend,
TowerServerDB: etcdTowerServerBackend,
// The wallet loader will attempt to use/create the
// wallet in the replicated remote DB if we're running
// in a clustered environment. This will ensure that all
// members of the cluster have access to the same wallet
// state.
WalletDB: btcwallet.LoaderWithExternalWalletDB(
etcdWalletBackend,
),
Remote: true,
CloseFuncs: closeFuncs,
}, nil
case PostgresBackend:
// Convert the sqldb PostgresConfig to a kvdb postgres.Config.
// This is a temporary measure until we migrate all kvdb SQL
// users to native SQL.
postgresConfig := GetPostgresConfigKVDB(db.Postgres)
postgresBackend, err := kvdb.Open(
kvdb.PostgresBackendName, ctx,
postgresConfig, NSChannelDB,
)
if err != nil {
return nil, fmt.Errorf("error opening postgres graph "+
"DB: %v", err)
}
closeFuncs[NSChannelDB] = postgresBackend.Close
postgresMacaroonBackend, err := kvdb.Open(
kvdb.PostgresBackendName, ctx,
postgresConfig, NSMacaroonDB,
)
if err != nil {
return nil, fmt.Errorf("error opening postgres "+
"macaroon DB: %v", err)
}
closeFuncs[NSMacaroonDB] = postgresMacaroonBackend.Close
postgresDecayedLogBackend, err := kvdb.Open(
kvdb.PostgresBackendName, ctx,
postgresConfig, NSDecayedLogDB,
)
if err != nil {
return nil, fmt.Errorf("error opening postgres "+
"decayed log DB: %v", err)
}
closeFuncs[NSDecayedLogDB] = postgresDecayedLogBackend.Close
postgresTowerClientBackend, err := kvdb.Open(
kvdb.PostgresBackendName, ctx,
postgresConfig, NSTowerClientDB,
)
if err != nil {
return nil, fmt.Errorf("error opening postgres tower "+
"client DB: %v", err)
}
closeFuncs[NSTowerClientDB] = postgresTowerClientBackend.Close
postgresTowerServerBackend, err := kvdb.Open(
kvdb.PostgresBackendName, ctx,
postgresConfig, NSTowerServerDB,
)
if err != nil {
return nil, fmt.Errorf("error opening postgres tower "+
"server DB: %v", err)
}
closeFuncs[NSTowerServerDB] = postgresTowerServerBackend.Close
postgresWalletBackend, err := kvdb.Open(
kvdb.PostgresBackendName, ctx,
postgresConfig, NSWalletDB,
)
if err != nil {
return nil, fmt.Errorf("error opening postgres macaroon "+
"DB: %v", err)
}
closeFuncs[NSWalletDB] = postgresWalletBackend.Close
var nativeSQLStore sqldb.DB
if db.UseNativeSQL {
nativePostgresStore, err := sqldb.NewPostgresStore(
db.Postgres,
)
if err != nil {
return nil, fmt.Errorf("error opening "+
"native postgres store: %v", err)
}
nativeSQLStore = nativePostgresStore
closeFuncs[PostgresBackend] = nativePostgresStore.Close
}
// Warn if the user is trying to switch over to a Postgres DB
// while there is a wallet or channel bbolt DB still present.
warnExistingBoltDBs(
logger, "postgres", walletDBPath, WalletDBName,
)
warnExistingBoltDBs(
logger, "postgres", chanDBPath, ChannelDBName,
)
returnEarly = false
return &DatabaseBackends{
GraphDB: postgresBackend,
ChanStateDB: postgresBackend,
HeightHintDB: postgresBackend,
MacaroonDB: postgresMacaroonBackend,
DecayedLogDB: postgresDecayedLogBackend,
TowerClientDB: postgresTowerClientBackend,
TowerServerDB: postgresTowerServerBackend,
// The wallet loader will attempt to use/create the
// wallet in the replicated remote DB if we're running
// in a clustered environment. This will ensure that all
// members of the cluster have access to the same wallet
// state.
WalletDB: btcwallet.LoaderWithExternalWalletDB(
postgresWalletBackend,
),
NativeSQLStore: nativeSQLStore,
Remote: true,
CloseFuncs: closeFuncs,
}, nil
case SqliteBackend:
// Convert the sqldb SqliteConfig to a kvdb sqlite.Config.
// This is a temporary measure until we migrate all kvdb SQL
// users to native SQL.
sqliteConfig := GetSqliteConfigKVDB(db.Sqlite)
// Note that for sqlite, we put kv tables for the channel.db,
// wtclient.db and sphinxreplay.db all in the channel.sqlite db.
// The tables for wallet.db and macaroon.db are in the
// chain.sqlite db and watchtower.db tables are in the
// watchtower.sqlite db. The reason for the multiple sqlite dbs
// is twofold. The first reason is that it maintains the file
// structure that users are used to. The second reason is the
// fact that sqlite only supports one writer at a time which
// would cause deadlocks in the code due to the wallet db often
// being accessed during a write to another db.
sqliteBackend, err := kvdb.Open(
kvdb.SqliteBackendName, ctx, sqliteConfig, chanDBPath,
SqliteChannelDBName, NSChannelDB,
)
if err != nil {
return nil, fmt.Errorf("error opening sqlite graph "+
"DB: %v", err)
}
closeFuncs[NSChannelDB] = sqliteBackend.Close
sqliteMacaroonBackend, err := kvdb.Open(
kvdb.SqliteBackendName, ctx, sqliteConfig, walletDBPath,
SqliteChainDBName, NSMacaroonDB,
)
if err != nil {
return nil, fmt.Errorf("error opening sqlite "+
"macaroon DB: %v", err)
}
closeFuncs[NSMacaroonDB] = sqliteMacaroonBackend.Close
sqliteDecayedLogBackend, err := kvdb.Open(
kvdb.SqliteBackendName, ctx, sqliteConfig, chanDBPath,
SqliteChannelDBName, NSDecayedLogDB,
)
if err != nil {
return nil, fmt.Errorf("error opening sqlite decayed "+
"log DB: %v", err)
}
closeFuncs[NSDecayedLogDB] = sqliteDecayedLogBackend.Close
sqliteTowerClientBackend, err := kvdb.Open(
kvdb.SqliteBackendName, ctx, sqliteConfig, chanDBPath,
SqliteChannelDBName, NSTowerClientDB,
)
if err != nil {
return nil, fmt.Errorf("error opening sqlite tower "+
"client DB: %v", err)
}
closeFuncs[NSTowerClientDB] = sqliteTowerClientBackend.Close
sqliteTowerServerBackend, err := kvdb.Open(
kvdb.SqliteBackendName, ctx, sqliteConfig,
towerServerDBPath, SqliteTowerDBName, NSTowerServerDB,
)
if err != nil {
return nil, fmt.Errorf("error opening sqlite tower "+
"server DB: %v", err)
}
closeFuncs[NSTowerServerDB] = sqliteTowerServerBackend.Close
sqliteWalletBackend, err := kvdb.Open(
kvdb.SqliteBackendName, ctx, sqliteConfig, walletDBPath,
SqliteChainDBName, NSWalletDB,
)
if err != nil {
return nil, fmt.Errorf("error opening sqlite macaroon "+
"DB: %v", err)
}
closeFuncs[NSWalletDB] = sqliteWalletBackend.Close
var nativeSQLStore sqldb.DB
if db.UseNativeSQL {
nativeSQLiteStore, err := sqldb.NewSqliteStore(
db.Sqlite,
path.Join(chanDBPath, SqliteNativeDBName),
)
if err != nil {
return nil, fmt.Errorf("error opening "+
"native SQLite store: %v", err)
}
nativeSQLStore = nativeSQLiteStore
closeFuncs[SqliteBackend] = nativeSQLiteStore.Close
}
// Warn if the user is trying to switch over to a sqlite DB
// while there is a wallet or channel bbolt DB still present.
warnExistingBoltDBs(
logger, "sqlite", walletDBPath, WalletDBName,
)
warnExistingBoltDBs(
logger, "sqlite", chanDBPath, ChannelDBName,
)
returnEarly = false
return &DatabaseBackends{
GraphDB: sqliteBackend,
ChanStateDB: sqliteBackend,
HeightHintDB: sqliteBackend,
MacaroonDB: sqliteMacaroonBackend,
DecayedLogDB: sqliteDecayedLogBackend,
TowerClientDB: sqliteTowerClientBackend,
TowerServerDB: sqliteTowerServerBackend,
// The wallet loader will attempt to use/create the
// wallet in the replicated remote DB if we're running
// in a clustered environment. This will ensure that all
// members of the cluster have access to the same wallet
// state.
WalletDB: btcwallet.LoaderWithExternalWalletDB(
sqliteWalletBackend,
),
NativeSQLStore: nativeSQLStore,
CloseFuncs: closeFuncs,
}, nil
}
// We're using all bbolt based databases by default.
boltBackend, err := kvdb.GetBoltBackend(&kvdb.BoltBackendConfig{
DBPath: chanDBPath,
DBFileName: ChannelDBName,
DBTimeout: db.Bolt.DBTimeout,
NoFreelistSync: db.Bolt.NoFreelistSync,
AutoCompact: db.Bolt.AutoCompact,
AutoCompactMinAge: db.Bolt.AutoCompactMinAge,
})
if err != nil {
return nil, fmt.Errorf("error opening bolt DB: %w", err)
}
closeFuncs[NSChannelDB] = boltBackend.Close
macaroonBackend, err := kvdb.GetBoltBackend(&kvdb.BoltBackendConfig{
DBPath: walletDBPath,
DBFileName: MacaroonDBName,
DBTimeout: db.Bolt.DBTimeout,
NoFreelistSync: db.Bolt.NoFreelistSync,
AutoCompact: db.Bolt.AutoCompact,
AutoCompactMinAge: db.Bolt.AutoCompactMinAge,
})
if err != nil {
return nil, fmt.Errorf("error opening macaroon DB: %w", err)
}
closeFuncs[NSMacaroonDB] = macaroonBackend.Close
decayedLogBackend, err := kvdb.GetBoltBackend(&kvdb.BoltBackendConfig{
DBPath: chanDBPath,
DBFileName: DecayedLogDbName,
DBTimeout: db.Bolt.DBTimeout,
NoFreelistSync: db.Bolt.NoFreelistSync,
AutoCompact: db.Bolt.AutoCompact,
AutoCompactMinAge: db.Bolt.AutoCompactMinAge,
})
if err != nil {
return nil, fmt.Errorf("error opening decayed log DB: %w", err)
}
closeFuncs[NSDecayedLogDB] = decayedLogBackend.Close
// The tower client is optional and might not be enabled by the user. We
// handle it being nil properly in the main server.
var towerClientBackend kvdb.Backend
if towerClientEnabled {
towerClientBackend, err = kvdb.GetBoltBackend(
&kvdb.BoltBackendConfig{
DBPath: chanDBPath,
DBFileName: TowerClientDBName,
DBTimeout: db.Bolt.DBTimeout,
NoFreelistSync: db.Bolt.NoFreelistSync,
AutoCompact: db.Bolt.AutoCompact,
AutoCompactMinAge: db.Bolt.AutoCompactMinAge,
},
)
if err != nil {
return nil, fmt.Errorf("error opening tower client "+
"DB: %v", err)
}
closeFuncs[NSTowerClientDB] = towerClientBackend.Close
}
// The tower server is optional and might not be enabled by the user. We
// handle it being nil properly in the main server.
var towerServerBackend kvdb.Backend
if towerServerEnabled {
towerServerBackend, err = kvdb.GetBoltBackend(
&kvdb.BoltBackendConfig{
DBPath: towerServerDBPath,
DBFileName: TowerServerDBName,
DBTimeout: db.Bolt.DBTimeout,
NoFreelistSync: db.Bolt.NoFreelistSync,
AutoCompact: db.Bolt.AutoCompact,
AutoCompactMinAge: db.Bolt.AutoCompactMinAge,
},
)
if err != nil {
return nil, fmt.Errorf("error opening tower server "+
"DB: %v", err)
}
closeFuncs[NSTowerServerDB] = towerServerBackend.Close
}
returnEarly = false
return &DatabaseBackends{
GraphDB: boltBackend,
ChanStateDB: boltBackend,
HeightHintDB: boltBackend,
MacaroonDB: macaroonBackend,
DecayedLogDB: decayedLogBackend,
TowerClientDB: towerClientBackend,
TowerServerDB: towerServerBackend,
// When "running locally", LND will use the bbolt wallet.db to
// store the wallet located in the chain data dir, parametrized
// by the active network. The wallet loader has its own cleanup
// method so we don't need to add anything to our map (in fact
// nothing is opened just yet).
WalletDB: btcwallet.LoaderWithLocalWalletDB(
walletDBPath, db.Bolt.NoFreelistSync, db.Bolt.DBTimeout,
),
CloseFuncs: closeFuncs,
}, nil
}
// warnExistingBoltDBs checks if there is an existing bbolt database in the
// given location and logs a warning if so.
func warnExistingBoltDBs(log btclog.Logger, dbType, dir, fileName string) {
if lnrpc.FileExists(filepath.Join(dir, fileName)) {
log.Warnf("Found existing bbolt database file in %s/%s while "+
"using database type %s. Existing data will NOT be "+
"migrated to %s automatically!", dir, fileName, dbType,
dbType)
}
}
// Compile-time constraint to ensure Workers implements the Validator interface.
var _ Validator = (*DB)(nil)
//go:build !integration
package lncfg
import (
"time"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
)
// IsDevBuild returns a bool to indicate whether we are in a development
// environment.
//
// NOTE: always return false here.
func IsDevBuild() bool {
return false
}
// DevConfig specifies development configs used for production. This struct
// should always remain empty.
type DevConfig struct{}
// ChannelReadyWait returns the config value, which is always 0 for production
// build.
func (d *DevConfig) ChannelReadyWait() time.Duration {
return 0
}
// GetUnsafeDisconnect returns the config value, which is always true for
// production build.
//
// TODO(yy): this is a temporary solution to allow users to reconnect peers to
// trigger a reestablishiment for the active channels. Once a new dedicated RPC
// is added to realize that functionality, this function should return false to
// forbidden disconnecting peers while there are active channels.
func (d *DevConfig) GetUnsafeDisconnect() bool {
return true
}
// GetReservationTimeout returns the config value for `ReservationTimeout`.
func (d *DevConfig) GetReservationTimeout() time.Duration {
return chanfunding.DefaultReservationTimeout
}
// GetZombieSweeperInterval returns the config value for`ZombieSweeperInterval`.
func (d *DevConfig) GetZombieSweeperInterval() time.Duration {
return DefaultZombieSweeperInterval
}
// GetMaxWaitNumBlocksFundingConf returns the config value for
// `MaxWaitNumBlocksFundingConf`.
func (d *DevConfig) GetMaxWaitNumBlocksFundingConf() uint32 {
return DefaultMaxWaitNumBlocksFundingConf
}
package lncfg
import "fmt"
// UsageError is an error type that signals a problem with the supplied flags.
type UsageError struct {
Err error
}
// Error returns the error string.
//
// NOTE: This is part of the error interface.
func (u *UsageError) Error() string {
return u.Err.Error()
}
// Unwrap returns the underlying error.
func (u *UsageError) Unwrap() error {
return u.Err
}
// mkErr creates a new error from a string.
func mkErr(format string, args ...interface{}) error {
return fmt.Errorf(format, args...)
}
package lncfg
import (
"fmt"
"time"
"github.com/lightningnetwork/lnd/discovery"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
// minAnnouncementConf defines the minimal num of confs needed for the config
// AnnouncementConf. We choose 3 here as it's unlikely a reorg depth of 3 would
// happen.
//
// NOTE: The specs recommends setting this value to 6, which is the default
// value used for AnnouncementConf. However the receiver should be able to
// decide which channels to be included in its local graph, more details can be
// found:
// - https://github.com/lightning/bolts/pull/1215#issuecomment-2557337202
const minAnnouncementConf = 3
//nolint:ll
type Gossip struct {
PinnedSyncersRaw []string `long:"pinned-syncers" description:"A set of peers that should always remain in an active sync state, which can be used to closely synchronize the routing tables of two nodes. The value should be a hex-encoded pubkey, the flag can be specified multiple times to add multiple peers. Connected peers matching this pubkey will remain active for the duration of the connection and not count towards the NumActiveSyncer count."`
PinnedSyncers discovery.PinnedSyncers
MaxChannelUpdateBurst int `long:"max-channel-update-burst" description:"The maximum number of updates for a specific channel and direction that lnd will accept over the channel update interval."`
ChannelUpdateInterval time.Duration `long:"channel-update-interval" description:"The interval used to determine how often lnd should allow a burst of new updates for a specific channel and direction."`
SubBatchDelay time.Duration `long:"sub-batch-delay" description:"The duration to wait before sending the next announcement batch if there are multiple. Use a small value if there are a lot announcements and they need to be broadcast quickly."`
AnnouncementConf uint32 `long:"announcement-conf" description:"The number of confirmations required before processing channel announcements."`
MsgRateBytes uint64 `long:"msg-rate-bytes" description:"The maximum number of bytes of gossip messages that will be sent per second. This is a global limit that applies to all peers."`
MsgBurstBytes uint64 `long:"msg-burst-bytes" description:"The maximum number of bytes of gossip messages that will be sent in a burst. This is a global limit that applies to all peers. This value should be set to something greater than 130 KB"`
}
// Parse the pubkeys for the pinned syncers.
func (g *Gossip) Parse() error {
pinnedSyncers := make(discovery.PinnedSyncers)
for _, pubkeyStr := range g.PinnedSyncersRaw {
vertex, err := route.NewVertexFromStr(pubkeyStr)
if err != nil {
return err
}
pinnedSyncers[vertex] = struct{}{}
}
g.PinnedSyncers = pinnedSyncers
return nil
}
// Validate checks the Gossip configuration to ensure that the input values are
// sane.
func (g *Gossip) Validate() error {
if g.AnnouncementConf < minAnnouncementConf {
return fmt.Errorf("announcement-conf=%v must be no less than "+
"%v", g.AnnouncementConf, minAnnouncementConf)
}
if g.MsgBurstBytes < lnwire.MaxSliceLength {
return fmt.Errorf("msg-burst-bytes=%v must be at least %v",
g.MsgBurstBytes, lnwire.MaxSliceLength)
}
return nil
}
// Compile-time constraint to ensure Gossip implements the Validator interface.
var _ Validator = (*Gossip)(nil)
package lncfg
import (
"errors"
"fmt"
"time"
)
var (
// MinHealthCheckInterval is the minimum interval we allow between
// health checks.
MinHealthCheckInterval = time.Minute
// MinHealthCheckTimeout is the minimum timeout we allow for health
// check calls.
MinHealthCheckTimeout = time.Second
// MinHealthCheckBackoff is the minimum back off we allow between health
// check retries.
MinHealthCheckBackoff = time.Second
)
// HealthCheckConfig contains the configuration for the different health checks
// the lnd runs.
//
//nolint:ll
type HealthCheckConfig struct {
ChainCheck *CheckConfig `group:"chainbackend" namespace:"chainbackend"`
DiskCheck *DiskCheckConfig `group:"diskspace" namespace:"diskspace"`
TLSCheck *CheckConfig `group:"tls" namespace:"tls"`
TorConnection *CheckConfig `group:"torconnection" namespace:"torconnection"`
RemoteSigner *CheckConfig `group:"remotesigner" namespace:"remotesigner"`
LeaderCheck *CheckConfig `group:"leader" namespace:"leader"`
}
// Validate checks the values configured for our health checks.
func (h *HealthCheckConfig) Validate() error {
if err := h.ChainCheck.validate("chain backend"); err != nil {
return err
}
if err := h.DiskCheck.validate("disk space"); err != nil {
return err
}
if err := h.TLSCheck.validate("tls"); err != nil {
return err
}
if h.DiskCheck.RequiredRemaining < 0 ||
h.DiskCheck.RequiredRemaining >= 1 {
return errors.New("disk required ratio must be in [0:1)")
}
if err := h.TorConnection.validate("tor connection"); err != nil {
return err
}
return nil
}
type CheckConfig struct {
Interval time.Duration `long:"interval" description:"How often to run a health check."`
Attempts int `long:"attempts" description:"The number of calls we will make for the check before failing. Set this value to 0 to disable a check."`
Timeout time.Duration `long:"timeout" description:"The amount of time we allow the health check to take before failing due to timeout."`
Backoff time.Duration `long:"backoff" description:"The amount of time to back-off between failed health checks."`
}
// validate checks the values in a health check config entry if it is enabled.
func (c *CheckConfig) validate(name string) error {
if c.Attempts == 0 {
return nil
}
if c.Backoff < MinHealthCheckBackoff {
return fmt.Errorf("%v backoff: %v below minimum: %v", name,
c.Backoff, MinHealthCheckBackoff)
}
if c.Timeout < MinHealthCheckTimeout {
return fmt.Errorf("%v timeout: %v below minimum: %v", name,
c.Timeout, MinHealthCheckTimeout)
}
if c.Interval < MinHealthCheckInterval {
return fmt.Errorf("%v interval: %v below minimum: %v", name,
c.Interval, MinHealthCheckInterval)
}
return nil
}
// DiskCheckConfig contains configuration for ensuring that our node has
// sufficient disk space.
type DiskCheckConfig struct {
RequiredRemaining float64 `long:"diskrequired" description:"The minimum ratio of free disk space to total capacity that we allow before shutting lnd down safely."`
*CheckConfig
}
package lncfg
import (
"fmt"
"time"
)
var (
// MaxMailboxDeliveryTimeout specifies the max allowed timeout value.
// This value is derived from the itest `async_bidirectional_payments`,
// where both side send 483 payments at the same time to stress test
// lnd.
MaxMailboxDeliveryTimeout = 2 * time.Minute
)
//nolint:ll
type Htlcswitch struct {
MailboxDeliveryTimeout time.Duration `long:"mailboxdeliverytimeout" description:"The timeout value when delivering HTLCs to a channel link. Setting this value too small will result in local payment failures if large number of payments are sent over a short period."`
}
// Validate checks the values configured for htlcswitch.
func (h *Htlcswitch) Validate() error {
if h.MailboxDeliveryTimeout <= 0 {
return fmt.Errorf("mailboxdeliverytimeout must be positive")
}
if h.MailboxDeliveryTimeout > MaxMailboxDeliveryTimeout {
return fmt.Errorf("mailboxdeliverytimeout: %v exceeds "+
"maximum: %v", h.MailboxDeliveryTimeout,
MaxMailboxDeliveryTimeout)
}
return nil
}
package lncfg
// Validator is a generic interface for validating sub configurations.
type Validator interface {
// Validate returns an error if a particular configuration is invalid or
// insane.
Validate() error
}
// Validate accepts a variadic list of Validators and checks that each one
// passes its Validate method. An error is returned from the first Validator
// that fails.
func Validate(validators ...Validator) error {
for _, validator := range validators {
if err := validator.Validate(); err != nil {
return err
}
}
return nil
}
package lncfg
const (
// DefaultHoldInvoiceExpiryDelta defines the number of blocks before the
// expiry height of a hold invoice's htlc that lnd will automatically
// cancel the invoice to prevent the channel from force closing. This
// value *must* be greater than DefaultIncomingBroadcastDelta to prevent
// force closes.
DefaultHoldInvoiceExpiryDelta = DefaultIncomingBroadcastDelta + 2
// DefaultMinNumRealBlindedPathHops is the minimum number of _real_
// hops to include in a blinded payment path. This doesn't include our
// node (the destination node), so if the minimum is 1, then the path
// will contain at minimum our node along with an introduction node hop.
DefaultMinNumRealBlindedPathHops = 1
// DefaultNumBlindedPathHops is the number of hops to include in a
// blinded payment path. If paths shorter than this number are found,
// then dummy hops are used to pad the path to this length.
DefaultNumBlindedPathHops = 2
// DefaultMaxNumBlindedPaths is the maximum number of different blinded
// payment paths to include in an invoice.
DefaultMaxNumBlindedPaths = 3
// DefaultBlindedPathPolicyIncreaseMultiplier is the default multiplier
// used to increase certain blinded hop policy values in order to add
// a probing buffer.
DefaultBlindedPathPolicyIncreaseMultiplier = 1.1
// DefaultBlindedPathPolicyDecreaseMultiplier is the default multiplier
// used to decrease certain blinded hop policy values in order to add a
// probing buffer.
DefaultBlindedPathPolicyDecreaseMultiplier = 0.9
)
// Invoices holds the configuration options for invoices.
//
//nolint:ll
type Invoices struct {
HoldExpiryDelta uint32 `long:"holdexpirydelta" description:"The number of blocks before a hold invoice's htlc expires that the invoice should be canceled to prevent a force close. Force closes will not be prevented if this value is not greater than DefaultIncomingBroadcastDelta."`
}
// Validate checks that the various invoice config options are sane.
//
// NOTE: this is part of the Validator interface.
func (i *Invoices) Validate() error {
// Log a warning if our expiry delta is not greater than our incoming
// broadcast delta. We do not fail here because this value may be set
// to zero to intentionally keep lnd's behavior unchanged from when we
// didn't auto-cancel these invoices.
if i.HoldExpiryDelta <= DefaultIncomingBroadcastDelta {
log.Warnf("Invoice hold expiry delta: %v <= incoming "+
"delta: %v, accepted hold invoices will force close "+
"channels if they are not canceled manually",
i.HoldExpiryDelta, DefaultIncomingBroadcastDelta)
}
return nil
}
package lncfg
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// Subsystem defines the logging code for this subsystem.
const Subsystem = "CNFG"
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
//go:build !monitoring
// +build !monitoring
package lncfg
// Prometheus configures the Prometheus exporter when monitoring is enabled.
// Monitoring is currently disabled.
type Prometheus struct{}
// DefaultPrometheus is the default configuration for the Prometheus metrics
// exporter when monitoring is enabled. Monitoring is currently disabled.
func DefaultPrometheus() Prometheus {
return Prometheus{}
}
// Enabled returns whether or not Prometheus monitoring is enabled. Monitoring
// is currently disabled, so Enabled will always return false.
func (p *Prometheus) Enabled() bool {
return false
}
package lncfg
import (
"net"
"strconv"
)
// Pprof holds the configuration options for LND's built-in pprof server.
//
//nolint:ll
type Pprof struct {
CPUProfile string `long:"cpuprofile" description:"Write CPU profile to the specified file"`
Profile string `long:"profile" description:"Enable HTTP profiling on either a port or host:port"`
BlockingProfile int `long:"blockingprofile" description:"Used to enable a blocking profile to be served on the profiling port. This takes a value from 0 to 1, with 1 including every blocking event, and 0 including no events."`
MutexProfile int `long:"mutexprofile" description:"Used to Enable a mutex profile to be served on the profiling port. This takes a value from 0 to 1, with 1 including every mutex event, and 0 including no events."`
}
// Validate checks the values configured for the profiler.
func (p *Pprof) Validate() error {
if p.BlockingProfile > 0 {
log.Warn("Blocking profile enabled only useful for " +
"debugging because of significant performance impact")
}
if p.MutexProfile > 0 {
log.Warn("Mutex profile enabled only useful for " +
"debugging because of significant performance impact")
}
if p.CPUProfile != "" {
log.Warn("CPU profile enabled only useful for " +
"debugging because of significant performance impact")
}
if p.Profile != "" {
str := "%v: The profile port must be between 1024 and 65535"
// Try to parse Profile as a host:port.
_, hostPort, err := net.SplitHostPort(p.Profile)
if err == nil {
// Determine if the port is valid.
profilePort, err := strconv.Atoi(hostPort)
if err != nil || profilePort < 1024 ||
profilePort > 65535 {
return &UsageError{Err: mkErr(str, hostPort)}
}
} else {
// Try to parse Profile as a port.
profilePort, err := strconv.Atoi(p.Profile)
if err != nil || profilePort < 1024 ||
profilePort > 65535 {
return &UsageError{Err: mkErr(str, p.Profile)}
}
// Since the user just set a port, we will serve
// debugging information over localhost.
p.Profile = net.JoinHostPort("127.0.0.1", p.Profile)
}
}
return nil
}
//go:build !integration
package lncfg
import (
"github.com/lightningnetwork/lnd/feature"
"github.com/lightningnetwork/lnd/lnwire"
)
// ProtocolOptions is a struct that we use to be able to test backwards
// compatibility of protocol additions, while defaulting to the latest within
// lnd, or to enable experimental protocol changes.
//
//nolint:ll
type ProtocolOptions struct {
// LegacyProtocol is a sub-config that houses all the legacy protocol
// options. These are mostly used for integration tests as most modern
// nodes should always run with them on by default.
LegacyProtocol `group:"legacy" namespace:"legacy"`
// ExperimentalProtocol is a sub-config that houses any experimental
// protocol features that also require a build-tag to activate.
ExperimentalProtocol
// WumboChans should be set if we want to enable support for wumbo
// (channels larger than 0.16 BTC) channels, which is the opposite of
// mini.
WumboChans bool `long:"wumbo-channels" description:"if set, then lnd will create and accept requests for channels larger chan 0.16 BTC"`
// TaprootChans should be set if we want to enable support for the
// experimental simple taproot chans commitment type.
TaprootChans bool `long:"simple-taproot-chans" description:"if set, then lnd will create and accept requests for channels using the simple taproot commitment type"`
// TaprootOverlayChans should be set if we want to enable support for
// the experimental taproot overlay chan type.
TaprootOverlayChans bool `long:"simple-taproot-overlay-chans" description:"if set, then lnd will create and accept requests for channels using the taproot overlay commitment type"`
// RbfCoopClose should be set if we want to signal that we support for
// the new experimental RBF coop close feature.
RbfCoopClose bool `long:"rbf-coop-close" description:"if set, then lnd will signal that it supports the new RBF based coop close protocol, taproot channels are not supported"`
// NoAnchors should be set if we don't want to support opening or accepting
// channels having the anchor commitment type.
NoAnchors bool `long:"no-anchors" description:"disable support for anchor commitments"`
// NoScriptEnforcedLease should be set if we don't want to support
// opening or accepting channels having the script enforced commitment
// type for leased channel.
NoScriptEnforcedLease bool `long:"no-script-enforced-lease" description:"disable support for script enforced lease commitments"`
// OptionScidAlias should be set if we want to signal the
// option-scid-alias feature bit. This allows scid aliases and the
// option-scid-alias channel-type.
OptionScidAlias bool `long:"option-scid-alias" description:"enable support for option_scid_alias channels"`
// OptionZeroConf should be set if we want to signal the zero-conf
// feature bit.
OptionZeroConf bool `long:"zero-conf" description:"enable support for zero-conf channels, must have option-scid-alias set also"`
// NoOptionAnySegwit should be set to true if we don't want to use any
// Taproot (and beyond) addresses for co-op closing.
NoOptionAnySegwit bool `long:"no-any-segwit" description:"disallow using any segwit witness version as a co-op close address"`
// NoTimestampQueryOption should be set to true if we don't want our
// syncing peers to also send us the timestamps of announcement messages
// when we send them a channel range query. Setting this to true will
// also mean that we won't respond with timestamps if requested by our
// peers.
NoTimestampQueryOption bool `long:"no-timestamp-query-option" description:"do not query syncing peers for announcement timestamps and do not respond with timestamps if requested"`
// NoRouteBlindingOption disables forwarding of payments in blinded routes.
NoRouteBlindingOption bool `long:"no-route-blinding" description:"do not forward payments that are a part of a blinded route"`
// NoExperimentalEndorsementOption disables experimental endorsement.
NoExperimentalEndorsementOption bool `long:"no-experimental-endorsement" description:"do not forward experimental endorsement signals"`
// CustomMessage allows the custom message APIs to handle messages with
// the provided protocol numbers, which fall outside the custom message
// number range.
CustomMessage []uint16 `long:"custom-message" description:"allows the custom message apis to send and report messages with the protocol number provided that fall outside of the custom message number range."`
// CustomInit specifies feature bits to advertise in the node's init
// message.
CustomInit []uint16 `long:"custom-init" description:"custom feature bits — numbers defined in BOLT 9 — to advertise in the node's init message"`
// CustomNodeAnn specifies custom feature bits to advertise in the
// node's announcement message.
CustomNodeAnn []uint16 `long:"custom-nodeann" description:"custom feature bits — numbers defined in BOLT 9 — to advertise in the node's announcement message"`
// CustomInvoice specifies custom feature bits to advertise in the
// node's invoices.
CustomInvoice []uint16 `long:"custom-invoice" description:"custom feature bits — numbers defined in BOLT 9 — to advertise in the node's invoices"`
}
// Wumbo returns true if lnd should permit the creation and acceptance of wumbo
// channels.
func (l *ProtocolOptions) Wumbo() bool {
return l.WumboChans
}
// NoAnchorCommitments returns true if we have disabled support for the anchor
// commitment type.
func (l *ProtocolOptions) NoAnchorCommitments() bool {
return l.NoAnchors
}
// NoScriptEnforcementLease returns true if we have disabled support for the
// script enforcement commitment type for leased channels.
func (l *ProtocolOptions) NoScriptEnforcementLease() bool {
return l.NoScriptEnforcedLease
}
// ScidAlias returns true if we have enabled the option-scid-alias feature bit.
func (l *ProtocolOptions) ScidAlias() bool {
return l.OptionScidAlias
}
// ZeroConf returns true if we have enabled the zero-conf feature bit.
func (l *ProtocolOptions) ZeroConf() bool {
return l.OptionZeroConf
}
// NoAnySegwit returns true if we don't signal that we understand other newer
// segwit witness versions for co-op close addresses.
func (l *ProtocolOptions) NoAnySegwit() bool {
return l.NoOptionAnySegwit
}
// NoTimestampsQuery returns true if we should not ask our syncing peers to also
// send us the timestamps of announcement messages when we send them a channel
// range query, and it also means that we will not respond with timestamps if
// requested by our peer.
func (l *ProtocolOptions) NoTimestampsQuery() bool {
return l.NoTimestampQueryOption
}
// NoRouteBlinding returns true if forwarding of blinded payments is disabled.
func (l *ProtocolOptions) NoRouteBlinding() bool {
return l.NoRouteBlindingOption
}
// NoExperimentalEndorsement returns true if experimental endorsement should
// be disabled.
func (l *ProtocolOptions) NoExperimentalEndorsement() bool {
return l.NoExperimentalEndorsementOption
}
// NoQuiescence returns true if quiescence is disabled.
func (l *ProtocolOptions) NoQuiescence() bool {
return true
}
// CustomMessageOverrides returns the set of protocol messages that we override
// to allow custom handling.
func (p ProtocolOptions) CustomMessageOverrides() []uint16 {
return p.CustomMessage
}
// CustomFeatures returns a custom set of feature bits to advertise.
func (p ProtocolOptions) CustomFeatures() map[feature.Set][]lnwire.FeatureBit {
customFeatures := make(map[feature.Set][]lnwire.FeatureBit)
setFeatures := func(set feature.Set, bits []uint16) {
for _, customFeature := range bits {
customFeatures[set] = append(
customFeatures[set],
lnwire.FeatureBit(customFeature),
)
}
}
setFeatures(feature.SetInit, p.CustomInit)
setFeatures(feature.SetNodeAnn, p.CustomNodeAnn)
setFeatures(feature.SetInvoice, p.CustomInvoice)
return customFeatures
}
//go:build !dev
// +build !dev
package lncfg
// Legacy is a sub-config that houses all the legacy protocol options. These
// are mostly used for integration tests as most modern nodes should always run
// with them on by default.
type LegacyProtocol struct {
}
// LegacyOnion returns true if the old legacy onion format should be used when
// we're an intermediate or final hop. This controls if we set the
// TLVOnionPayloadOptional bit or not.
func (l *LegacyProtocol) LegacyOnion() bool {
return false
}
// NoStaticRemoteKey returns true if the old commitment format with a tweaked
// remote key should be used for new funded channels.
func (l *LegacyProtocol) NoStaticRemoteKey() bool {
return false
}
package lncfg
import (
"fmt"
"time"
)
const (
// DefaultRemoteSignerRPCTimeout is the default timeout that is used
// when forwarding a request to the remote signer through RPC.
DefaultRemoteSignerRPCTimeout = 5 * time.Second
)
// RemoteSigner holds the configuration options for a remote RPC signer.
//
//nolint:ll
type RemoteSigner struct {
Enable bool `long:"enable" description:"Use a remote signer for signing any on-chain related transactions or messages. Only recommended if local wallet is initialized as watch-only. Remote signer must use the same seed/root key as the local watch-only wallet but must have private keys."`
RPCHost string `long:"rpchost" description:"The remote signer's RPC host:port"`
MacaroonPath string `long:"macaroonpath" description:"The macaroon to use for authenticating with the remote signer"`
TLSCertPath string `long:"tlscertpath" description:"The TLS certificate to use for establishing the remote signer's identity"`
Timeout time.Duration `long:"timeout" description:"The timeout for connecting to and signing requests with the remote signer. Valid time units are {s, m, h}."`
MigrateWatchOnly bool `long:"migrate-wallet-to-watch-only" description:"If a wallet with private key material already exists, migrate it into a watch-only wallet on first startup. WARNING: This cannot be undone! Make sure you have backed up your seed before you use this flag! All private keys will be purged from the wallet after first unlock with this flag!"`
}
// Validate checks the values configured for our remote RPC signer.
func (r *RemoteSigner) Validate() error {
if !r.Enable {
return nil
}
if r.Timeout < time.Millisecond {
return fmt.Errorf("remote signer: timeout of %v is invalid, "+
"cannot be smaller than %v", r.Timeout,
time.Millisecond)
}
if r.MigrateWatchOnly && !r.Enable {
return fmt.Errorf("remote signer: cannot turn on wallet " +
"migration to watch-only if remote signing is not " +
"enabled")
}
return nil
}
package lncfg
import "fmt"
// Routing holds the configuration options for routing.
//
//nolint:ll
type Routing struct {
AssumeChannelValid bool `long:"assumechanvalid" description:"DEPRECATED: Skip checking channel spentness during graph validation. This speedup comes at the risk of using an unvalidated view of the network for routing. (default: false)" hidden:"true"`
StrictZombiePruning bool `long:"strictgraphpruning" description:"If true, then the graph will be pruned more aggressively for zombies. In practice this means that edges with a single stale edge will be considered a zombie."`
BlindedPaths BlindedPaths `group:"blinding" namespace:"blinding"`
}
// BlindedPaths holds the configuration options for blinded path construction.
//
//nolint:ll
type BlindedPaths struct {
MinNumRealHops uint8 `long:"min-num-real-hops" description:"The minimum number of real hops to include in a blinded path. This doesn't include our node, so if the minimum is 1, then the path will contain at minimum our node along with an introduction node hop. If it is zero then the shortest path will use our node as an introduction node."`
NumHops uint8 `long:"num-hops" description:"The number of hops to include in a blinded path. This doesn't include our node, so if it is 1, then the path will contain our node along with an introduction node or dummy node hop. If paths shorter than NumHops is found, then they will be padded using dummy hops."`
MaxNumPaths uint8 `long:"max-num-paths" description:"The maximum number of blinded paths to select and add to an invoice."`
PolicyIncreaseMultiplier float64 `long:"policy-increase-multiplier" description:"The amount by which to increase certain policy values of hops on a blinded path in order to add a probing buffer."`
PolicyDecreaseMultiplier float64 `long:"policy-decrease-multiplier" description:"The amount by which to decrease certain policy values of hops on a blinded path in order to add a probing buffer."`
}
// Validate checks that the various routing config options are sane.
//
// NOTE: this is part of the Validator interface.
func (r *Routing) Validate() error {
if r.BlindedPaths.MinNumRealHops > r.BlindedPaths.NumHops {
return fmt.Errorf("the minimum number of real hops in a " +
"blinded path must be smaller than or equal to the " +
"number of hops expected to be included in each path")
}
if r.BlindedPaths.PolicyIncreaseMultiplier < 1 {
return fmt.Errorf("the blinded route policy increase " +
"multiplier must be greater than or equal to 1")
}
if r.BlindedPaths.PolicyDecreaseMultiplier > 1 ||
r.BlindedPaths.PolicyDecreaseMultiplier < 0 {
return fmt.Errorf("the blinded route policy decrease " +
"multiplier must be in the range (0,1]")
}
return nil
}
package lncfg
import (
"fmt"
"time"
)
const (
// defaultRPCMiddlewareTimeout is the time after which a request sent to
// a gRPC interception middleware times out. This value is chosen very
// low since in a worst case scenario that time is added to a request's
// full duration twice (request and response interception) if a
// middleware is very slow.
defaultRPCMiddlewareTimeout = 2 * time.Second
)
// RPCMiddleware holds the configuration for RPC interception middleware.
//
//nolint:ll
type RPCMiddleware struct {
Enable bool `long:"enable" description:"Enable the RPC middleware interceptor functionality."`
InterceptTimeout time.Duration `long:"intercepttimeout" description:"Time after which a RPC middleware intercept request will time out and return an error if it hasn't yet received a response."`
Mandatory []string `long:"addmandatory" description:"Add the named middleware to the list of mandatory middlewares. All RPC requests are blocked/denied if any of the mandatory middlewares is not registered. Can be specified multiple times."`
}
// Validate checks the values configured for the RPC middleware.
func (r *RPCMiddleware) Validate() error {
if r.InterceptTimeout < 0 {
return fmt.Errorf("RPC middleware intercept timeout cannot " +
"be negative")
}
return nil
}
// DefaultRPCMiddleware returns the default values for the RPC interception
// middleware configuration.
func DefaultRPCMiddleware() *RPCMiddleware {
return &RPCMiddleware{
InterceptTimeout: defaultRPCMiddlewareTimeout,
}
}
package lncfg
import (
"fmt"
"time"
"github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/sweep"
)
const (
// MaxFeeRateFloor is the smallest config value allowed for the max fee
// rate in sat/vb.
MaxFeeRateFloor chainfee.SatPerVByte = 100
// MaxAllowedFeeRate is the largest fee rate in sat/vb that we allow
// when configuring the MaxFeeRate.
MaxAllowedFeeRate = 10_000
)
//nolint:ll
type Sweeper struct {
BatchWindowDuration time.Duration `long:"batchwindowduration" description:"Duration of the sweep batch window. The sweep is held back during the batch window to allow more inputs to be added and thereby lower the fee per input." hidden:"true"`
MaxFeeRate chainfee.SatPerVByte `long:"maxfeerate" description:"Maximum fee rate in sat/vb that the sweeper is allowed to use when sweeping funds, the fee rate derived from budgets are capped at this value. Setting this value too low can result in transactions not being confirmed in time, causing HTLCs to expire hence potentially losing funds."`
NoDeadlineConfTarget uint32 `long:"nodeadlineconftarget" description:"The conf target to use when sweeping non-time-sensitive outputs. This is useful for sweeping outputs that are not time-sensitive, and can be swept at a lower fee rate."`
Budget *contractcourt.BudgetConfig `group:"sweeper.budget" namespace:"budget" long:"budget" description:"An optional config group that's used for the automatic sweep fee estimation. The Budget config gives options to limits ones fee exposure when sweeping unilateral close outputs and the fee rate calculated from budgets is capped at sweeper.maxfeerate. Check the budget config options for more details."`
}
// Validate checks the values configured for the sweeper.
func (s *Sweeper) Validate() error {
if s.BatchWindowDuration < 0 {
return fmt.Errorf("batchwindowduration must be positive")
}
// We require the max fee rate to be at least 100 sat/vbyte.
if s.MaxFeeRate < MaxFeeRateFloor {
return fmt.Errorf("maxfeerate must be >= 100 sat/vb")
}
// We require the max fee rate to be no greater than 10_000 sat/vbyte.
if s.MaxFeeRate > MaxAllowedFeeRate {
return fmt.Errorf("maxfeerate must be <= 10000 sat/vb")
}
// Make sure the conf target is at least 144 blocks (1 day).
if s.NoDeadlineConfTarget < 144 {
return fmt.Errorf("nodeadlineconftarget must be at least 144")
}
// Validate the budget configuration.
if err := s.Budget.Validate(); err != nil {
return fmt.Errorf("invalid budget config: %w", err)
}
return nil
}
// DefaultSweeperConfig returns the default configuration for the sweeper.
func DefaultSweeperConfig() *Sweeper {
return &Sweeper{
MaxFeeRate: sweep.DefaultMaxFeeRate,
NoDeadlineConfTarget: uint32(sweep.DefaultDeadlineDelta),
Budget: contractcourt.DefaultBudgetConfig(),
}
}
package lncfg
import "github.com/lightningnetwork/lnd/watchtower"
// Watchtower holds the daemon specific configuration parameters for running a
// watchtower that shares resources with the daemon.
//
//nolint:ll
type Watchtower struct {
Active bool `long:"active" description:"If the watchtower should be active or not"`
TowerDir string `long:"towerdir" description:"Directory of the watchtower.db"`
watchtower.Conf
}
// DefaultWatchtowerCfg creates a Watchtower with some default values filled
// out.
func DefaultWatchtowerCfg(defaultTowerDir string) *Watchtower {
conf := watchtower.DefaultConf()
return &Watchtower{
TowerDir: defaultTowerDir,
Conf: *conf,
}
}
package lncfg
import "fmt"
const (
// DefaultReadWorkers is the default maximum number of concurrent
// workers used by the daemon's read pool.
DefaultReadWorkers = 100
// DefaultWriteWorkers is the default maximum number of concurrent
// workers used by the daemon's write pool.
DefaultWriteWorkers = 8
// DefaultSigWorkers is the default maximum number of concurrent workers
// used by the daemon's sig pool.
DefaultSigWorkers = 8
)
// Workers exposes CLI configuration for turning resources consumed by worker
// pools.
//
//nolint:ll
type Workers struct {
// Read is the maximum number of concurrent read pool workers.
Read int `long:"read" description:"Maximum number of concurrent read pool workers. This number should be proportional to the number of peers."`
// Write is the maximum number of concurrent write pool workers.
Write int `long:"write" description:"Maximum number of concurrent write pool workers. This number should be proportional to the number of CPUs on the host. "`
// Sig is the maximum number of concurrent sig pool workers.
Sig int `long:"sig" description:"Maximum number of concurrent sig pool workers. This number should be proportional to the number of CPUs on the host."`
}
// Validate checks the Workers configuration to ensure that the input values are
// sane.
func (w *Workers) Validate() error {
if w.Read <= 0 {
return fmt.Errorf("number of read workers (%d) must be "+
"positive", w.Read)
}
if w.Write <= 0 {
return fmt.Errorf("number of write workers (%d) must be "+
"positive", w.Write)
}
if w.Sig <= 0 {
return fmt.Errorf("number of sig workers (%d) must be "+
"positive", w.Sig)
}
return nil
}
// Compile-time constraint to ensure Workers implements the Validator interface.
var _ Validator = (*Workers)(nil)
package lncfg
import (
"fmt"
"github.com/lightningnetwork/lnd/watchtower/wtclient"
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
)
// WtClient holds the configuration options for the daemon's watchtower client.
//
//nolint:ll
type WtClient struct {
// Active determines whether a watchtower client should be created to
// back up channel states with registered watchtowers.
Active bool `long:"active" description:"Whether the daemon should use private watchtowers to back up revoked channel states."`
// SweepFeeRate specifies the fee rate in sat/byte to be used when
// constructing justice transactions sent to the tower.
SweepFeeRate uint64 `long:"sweep-fee-rate" description:"Specifies the fee rate in sat/byte to be used when constructing justice transactions sent to the watchtower."`
// SessionCloseRange is the range over which to choose a random number
// of blocks to wait after the last channel of a session is closed
// before sending the DeleteSession message to the tower server.
SessionCloseRange uint32 `long:"session-close-range" description:"The range over which to choose a random number of blocks to wait after the last channel of a session is closed before sending the DeleteSession message to the tower server. Set to 1 for no delay."`
// MaxTasksInMemQueue is the maximum number of back-up tasks that should
// be queued in memory before overflowing to disk.
MaxTasksInMemQueue uint64 `long:"max-tasks-in-mem-queue" description:"The maximum number of updates that should be queued in memory before overflowing to disk."`
// MaxUpdates is the maximum number of updates to be backed up in a
// single tower sessions.
MaxUpdates uint16 `long:"max-updates" description:"The maximum number of updates to be backed up in a single session."`
}
// DefaultWtClientCfg returns the WtClient config struct with some default
// values populated.
func DefaultWtClientCfg() *WtClient {
// The sweep fee rate used internally by the tower client is in sats/kw
// but the config exposed to the user is in sats/byte, so we convert the
// default here before exposing it to the user.
sweepSatsPerVB := wtpolicy.DefaultSweepFeeRate.FeePerVByte()
sweepFeeRate := uint64(sweepSatsPerVB)
return &WtClient{
SweepFeeRate: sweepFeeRate,
SessionCloseRange: wtclient.DefaultSessionCloseRange,
MaxTasksInMemQueue: wtclient.DefaultMaxTasksInMemQueue,
MaxUpdates: wtpolicy.DefaultMaxUpdates,
}
}
// Validate ensures the user has provided a valid configuration.
//
// NOTE: Part of the Validator interface.
func (c *WtClient) Validate() error {
if c.SweepFeeRate == 0 {
return fmt.Errorf("sweep-fee-rate must be non-zero")
}
if c.MaxUpdates == 0 {
return fmt.Errorf("max-updates must be non-zero")
}
if c.MaxTasksInMemQueue == 0 {
return fmt.Errorf("max-tasks-in-mem-queue must be non-zero")
}
if c.SessionCloseRange == 0 {
return fmt.Errorf("session-close-range must be non-zero")
}
return nil
}
// Compile-time constraint to ensure WtClient implements the Validator
// interface.
var _ Validator = (*WtClient)(nil)
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Copyright (C) 2015-2022 The Lightning Network Developers
package lnd
import (
"context"
"errors"
"fmt"
"log/slog"
"net"
"net/http"
"net/http/pprof"
"os"
"runtime"
runtimePprof "runtime/pprof"
"strings"
"sync"
"time"
"github.com/btcsuite/btcd/btcutil"
proxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/lightningnetwork/lnd/autopilot"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/chanacceptor"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/cluster"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/lightningnetwork/lnd/monitoring"
"github.com/lightningnetwork/lnd/rpcperms"
"github.com/lightningnetwork/lnd/signal"
"github.com/lightningnetwork/lnd/tor"
"github.com/lightningnetwork/lnd/walletunlocker"
"github.com/lightningnetwork/lnd/watchtower"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/keepalive"
"gopkg.in/macaroon-bakery.v2/bakery"
"gopkg.in/macaroon.v2"
)
const (
// adminMacaroonFilePermissions is the file permission that is used for
// creating the admin macaroon file.
//
// Why 640 is safe:
// Assuming a reasonably secure Linux system, it will have a
// separate group for each user. E.g. a new user lnd gets assigned group
// lnd which nothing else belongs to. A system that does not do this is
// inherently broken already.
//
// Since there is no other user in the group, no other user can read
// admin macaroon unless the administrator explicitly allowed it. Thus
// there's no harm allowing group read.
adminMacaroonFilePermissions = 0640
// leaderResignTimeout is the timeout used when resigning from the
// leader role. This is kept short so LND can shut down quickly in case
// of a system failure or network partition making the cluster
// unresponsive. The cluster itself should ensure that the leader is not
// elected again until the previous leader has resigned or the leader
// election timeout has passed.
leaderResignTimeout = 5 * time.Second
)
// AdminAuthOptions returns a list of DialOptions that can be used to
// authenticate with the RPC server with admin capabilities.
// skipMacaroons=true should be set if we don't want to include macaroons with
// the auth options. This is needed for instance for the WalletUnlocker
// service, which must be usable also before macaroons are created.
//
// NOTE: This should only be called after the RPCListener has signaled it is
// ready.
func AdminAuthOptions(cfg *Config, skipMacaroons bool) ([]grpc.DialOption,
error) {
creds, err := credentials.NewClientTLSFromFile(cfg.TLSCertPath, "")
if err != nil {
return nil, fmt.Errorf("unable to read TLS cert: %w", err)
}
// Create a dial options array.
opts := []grpc.DialOption{
grpc.WithTransportCredentials(creds),
}
// Get the admin macaroon if macaroons are active.
if !skipMacaroons && !cfg.NoMacaroons {
// Load the admin macaroon file.
macBytes, err := os.ReadFile(cfg.AdminMacPath)
if err != nil {
return nil, fmt.Errorf("unable to read macaroon "+
"path (check the network setting!): %v", err)
}
mac := &macaroon.Macaroon{}
if err = mac.UnmarshalBinary(macBytes); err != nil {
return nil, fmt.Errorf("unable to decode macaroon: %w",
err)
}
// Now we append the macaroon credentials to the dial options.
cred, err := macaroons.NewMacaroonCredential(mac)
if err != nil {
return nil, fmt.Errorf("error cloning mac: %w", err)
}
opts = append(opts, grpc.WithPerRPCCredentials(cred))
}
return opts, nil
}
// ListenerWithSignal is a net.Listener that has an additional Ready channel
// that will be closed when a server starts listening.
type ListenerWithSignal struct {
net.Listener
// Ready will be closed by the server listening on Listener.
Ready chan struct{}
// MacChan is an optional way to pass the admin macaroon to the program
// that started lnd. The channel should be buffered to avoid lnd being
// blocked on sending to the channel.
MacChan chan []byte
}
// ListenerCfg is a wrapper around custom listeners that can be passed to lnd
// when calling its main method.
type ListenerCfg struct {
// RPCListeners can be set to the listeners to use for the RPC server.
// If empty a regular network listener will be created.
RPCListeners []*ListenerWithSignal
}
var errStreamIsolationWithProxySkip = errors.New(
"while stream isolation is enabled, the TOR proxy may not be skipped",
)
// Main is the true entry point for lnd. It accepts a fully populated and
// validated main configuration struct and an optional listener config struct.
// This function starts all main system components then blocks until a signal
// is received on the shutdownChan at which point everything is shut down again.
func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg,
interceptor signal.Interceptor) error {
defer func() {
ltndLog.Info("Shutdown complete")
err := cfg.LogRotator.Close()
if err != nil {
ltndLog.Errorf("Could not close log rotator: %v", err)
}
}()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx, err := build.WithBuildInfo(ctx, cfg.LogConfig)
if err != nil {
return fmt.Errorf("unable to add build info to context: %w",
err)
}
mkErr := func(msg string, err error, attrs ...any) error {
ltndLog.ErrorS(ctx, "Shutting down due to error in main "+
"method", err, attrs...)
var (
params = []any{err}
fmtStr = msg + ": %w"
)
for _, attr := range attrs {
fmtStr += " %s"
params = append(params, attr)
}
return fmt.Errorf(fmtStr, params...)
}
// Show version at startup.
ltndLog.InfoS(ctx, "Version Info",
slog.String("version", build.Version()),
slog.String("commit", build.Commit),
slog.Any("debuglevel", build.Deployment),
slog.String("logging", cfg.DebugLevel))
var network string
switch {
case cfg.Bitcoin.TestNet3:
network = "testnet"
case cfg.Bitcoin.TestNet4:
network = "testnet4"
case cfg.Bitcoin.MainNet:
network = "mainnet"
case cfg.Bitcoin.SimNet:
network = "simnet"
case cfg.Bitcoin.RegTest:
network = "regtest"
case cfg.Bitcoin.SigNet:
network = "signet"
}
ltndLog.InfoS(ctx, "Network Info",
"active_chain", strings.Title(BitcoinChainName),
"network", network)
// Enable http profiling server if requested.
if cfg.Pprof.Profile != "" {
// Create the http handler.
pprofMux := http.NewServeMux()
pprofMux.HandleFunc("/debug/pprof/", pprof.Index)
pprofMux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
pprofMux.HandleFunc("/debug/pprof/profile", pprof.Profile)
pprofMux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
pprofMux.HandleFunc("/debug/pprof/trace", pprof.Trace)
if cfg.Pprof.BlockingProfile != 0 {
runtime.SetBlockProfileRate(cfg.Pprof.BlockingProfile)
}
if cfg.Pprof.MutexProfile != 0 {
runtime.SetMutexProfileFraction(cfg.Pprof.MutexProfile)
}
// Redirect all requests to the pprof handler, thus visiting
// `127.0.0.1:6060` will be redirected to
// `127.0.0.1:6060/debug/pprof`.
pprofMux.Handle("/", http.RedirectHandler(
"/debug/pprof/", http.StatusSeeOther,
))
ltndLog.InfoS(ctx, "Pprof listening", "addr", cfg.Pprof.Profile)
// Create the pprof server.
pprofServer := &http.Server{
Addr: cfg.Pprof.Profile,
Handler: pprofMux,
ReadHeaderTimeout: cfg.HTTPHeaderTimeout,
}
// Shut the server down when lnd is shutting down.
defer func() {
ltndLog.InfoS(ctx, "Stopping pprof server...")
err := pprofServer.Shutdown(ctx)
if err != nil {
ltndLog.ErrorS(ctx, "Stop pprof server", err)
}
}()
// Start the pprof server.
go func() {
err := pprofServer.ListenAndServe()
if err != nil && !errors.Is(err, http.ErrServerClosed) {
ltndLog.ErrorS(ctx, "Could not serve pprof "+
"server", err)
}
}()
}
// Write cpu profile if requested.
if cfg.Pprof.CPUProfile != "" {
f, err := os.Create(cfg.Pprof.CPUProfile)
if err != nil {
return mkErr("unable to create CPU profile", err)
}
_ = runtimePprof.StartCPUProfile(f)
defer func() {
_ = f.Close()
}()
defer runtimePprof.StopCPUProfile()
}
// Run configuration dependent DB pre-initialization. Note that this
// needs to be done early and once during the startup process, before
// any DB access.
if err := cfg.DB.Init(ctx, cfg.graphDatabaseDir()); err != nil {
return mkErr("error initializing DBs", err)
}
tlsManagerCfg := &TLSManagerCfg{
TLSCertPath: cfg.TLSCertPath,
TLSKeyPath: cfg.TLSKeyPath,
TLSEncryptKey: cfg.TLSEncryptKey,
TLSExtraIPs: cfg.TLSExtraIPs,
TLSExtraDomains: cfg.TLSExtraDomains,
TLSAutoRefresh: cfg.TLSAutoRefresh,
TLSDisableAutofill: cfg.TLSDisableAutofill,
TLSCertDuration: cfg.TLSCertDuration,
LetsEncryptDir: cfg.LetsEncryptDir,
LetsEncryptDomain: cfg.LetsEncryptDomain,
LetsEncryptListen: cfg.LetsEncryptListen,
DisableRestTLS: cfg.DisableRestTLS,
HTTPHeaderTimeout: cfg.HTTPHeaderTimeout,
}
tlsManager := NewTLSManager(tlsManagerCfg)
serverOpts, restDialOpts, restListen, cleanUp,
err := tlsManager.SetCertificateBeforeUnlock()
if err != nil {
return mkErr("error setting cert before unlock", err)
}
if cleanUp != nil {
defer cleanUp()
}
// If we have chosen to start with a dedicated listener for the
// rpc server, we set it directly.
grpcListeners := append([]*ListenerWithSignal{}, lisCfg.RPCListeners...)
if len(grpcListeners) == 0 {
// Otherwise we create listeners from the RPCListeners defined
// in the config.
for _, grpcEndpoint := range cfg.RPCListeners {
// Start a gRPC server listening for HTTP/2
// connections.
lis, err := lncfg.ListenOnAddress(grpcEndpoint)
if err != nil {
return mkErr("unable to listen on grpc "+
"endpoint", err,
slog.String(
"endpoint",
grpcEndpoint.String(),
))
}
defer lis.Close()
grpcListeners = append(
grpcListeners, &ListenerWithSignal{
Listener: lis,
Ready: make(chan struct{}),
},
)
}
}
// Create a new RPC interceptor that we'll add to the GRPC server. This
// will be used to log the API calls invoked on the GRPC server.
interceptorChain := rpcperms.NewInterceptorChain(
rpcsLog, cfg.NoMacaroons, cfg.RPCMiddleware.Mandatory,
)
if err := interceptorChain.Start(); err != nil {
return mkErr("error starting interceptor chain", err)
}
defer func() {
err := interceptorChain.Stop()
if err != nil {
ltndLog.Warnf("error stopping RPC interceptor "+
"chain: %v", err)
}
}()
// Allow the user to overwrite some defaults of the gRPC library related
// to connection keepalive (server side and client side pings).
serverKeepalive := keepalive.ServerParameters{
Time: cfg.GRPC.ServerPingTime,
Timeout: cfg.GRPC.ServerPingTimeout,
}
clientKeepalive := keepalive.EnforcementPolicy{
MinTime: cfg.GRPC.ClientPingMinWait,
PermitWithoutStream: cfg.GRPC.ClientAllowPingWithoutStream,
}
rpcServerOpts := interceptorChain.CreateServerOpts()
serverOpts = append(serverOpts, rpcServerOpts...)
serverOpts = append(
serverOpts, grpc.MaxRecvMsgSize(lnrpc.MaxGrpcMsgSize),
grpc.KeepaliveParams(serverKeepalive),
grpc.KeepaliveEnforcementPolicy(clientKeepalive),
)
grpcServer := grpc.NewServer(serverOpts...)
defer grpcServer.Stop()
// We'll also register the RPC interceptor chain as the StateServer, as
// it can be used to query for the current state of the wallet.
lnrpc.RegisterStateServer(grpcServer, interceptorChain)
// Initialize, and register our implementation of the gRPC interface
// exported by the rpcServer.
rpcServer := newRPCServer(cfg, interceptorChain, implCfg, interceptor)
err = rpcServer.RegisterWithGrpcServer(grpcServer)
if err != nil {
return mkErr("error registering gRPC server", err)
}
// Now that both the WalletUnlocker and LightningService have been
// registered with the GRPC server, we can start listening.
err = startGrpcListen(cfg, grpcServer, grpcListeners)
if err != nil {
return mkErr("error starting gRPC listener", err)
}
// Now start the REST proxy for our gRPC server above. We'll ensure
// we direct LND to connect to its loopback address rather than a
// wildcard to prevent certificate issues when accessing the proxy
// externally.
stopProxy, err := startRestProxy(
ctx, cfg, rpcServer, restDialOpts, restListen,
)
if err != nil {
return mkErr("error starting REST proxy", err)
}
defer stopProxy()
// Start leader election if we're running on etcd. Continuation will be
// blocked until this instance is elected as the current leader or
// shutting down.
elected := false
var leaderElector cluster.LeaderElector
if cfg.Cluster.EnableLeaderElection {
electionCtx, cancelElection := context.WithCancel(ctx)
go func() {
<-interceptor.ShutdownChannel()
cancelElection()
}()
ltndLog.InfoS(ctx, "Using leader elector",
"elector", cfg.Cluster.LeaderElector)
leaderElector, err = cfg.Cluster.MakeLeaderElector(
electionCtx, cfg.DB,
)
if err != nil {
return err
}
defer func() {
if !elected {
return
}
ltndLog.InfoS(ctx, "Attempting to resign from "+
"leader role", "cluster_id", cfg.Cluster.ID)
// Ensure that we don't block the shutdown process if
// the leader resigning process takes too long. The
// cluster will ensure that the leader is not elected
// again until the previous leader has resigned or the
// leader election timeout has passed.
timeoutCtx, cancel := context.WithTimeout(
ctx, leaderResignTimeout,
)
defer cancel()
if err := leaderElector.Resign(timeoutCtx); err != nil {
ltndLog.Errorf("Leader elector failed to "+
"resign: %v", err)
}
}()
ltndLog.InfoS(ctx, "Starting leadership campaign",
"cluster_id", cfg.Cluster.ID)
if err := leaderElector.Campaign(electionCtx); err != nil {
return mkErr("leadership campaign failed", err)
}
elected = true
ltndLog.InfoS(ctx, "Elected as leader",
"cluster_id", cfg.Cluster.ID)
}
dbs, cleanUp, err := implCfg.DatabaseBuilder.BuildDatabase(ctx)
switch {
case errors.Is(err, channeldb.ErrDryRunMigrationOK):
ltndLog.InfoS(ctx, "Exiting due to BuildDatabase error",
slog.Any("err", err))
return nil
case err != nil:
return mkErr("unable to open databases", err)
}
defer cleanUp()
partialChainControl, walletConfig, cleanUp, err := implCfg.BuildWalletConfig(
ctx, dbs, &implCfg.AuxComponents, interceptorChain,
grpcListeners,
)
if err != nil {
return mkErr("error creating wallet config", err)
}
defer cleanUp()
activeChainControl, cleanUp, err := implCfg.BuildChainControl(
partialChainControl, walletConfig,
)
if err != nil {
return mkErr("error loading chain control", err)
}
defer cleanUp()
// TODO(roasbeef): add rotation
idKeyDesc, err := activeChainControl.KeyRing.DeriveKey(
keychain.KeyLocator{
Family: keychain.KeyFamilyNodeKey,
Index: 0,
},
)
if err != nil {
return mkErr("error deriving node key", err)
}
if cfg.Tor.StreamIsolation && cfg.Tor.SkipProxyForClearNetTargets {
return errStreamIsolationWithProxySkip
}
if cfg.Tor.Active {
if cfg.Tor.SkipProxyForClearNetTargets {
srvrLog.InfoS(ctx, "Onion services are accessible "+
"via Tor! NOTE: Traffic to clearnet services "+
"is not routed via Tor.")
} else {
srvrLog.InfoS(ctx, "Proxying all network traffic "+
"via Tor! NOTE: Ensure the backend node is "+
"proxying over Tor as well",
"stream_isolation", cfg.Tor.StreamIsolation)
}
}
// If tor is active and either v2 or v3 onion services have been
// specified, make a tor controller and pass it into both the watchtower
// server and the regular lnd server.
var torController *tor.Controller
if cfg.Tor.Active && (cfg.Tor.V2 || cfg.Tor.V3) {
torController = tor.NewController(
cfg.Tor.Control, cfg.Tor.TargetIPAddress,
cfg.Tor.Password,
)
// Start the tor controller before giving it to any other
// subsystems.
if err := torController.Start(); err != nil {
return mkErr("unable to initialize tor controller",
err)
}
defer func() {
if err := torController.Stop(); err != nil {
ltndLog.ErrorS(ctx, "Error stopping tor "+
"controller", err)
}
}()
}
var tower *watchtower.Standalone
if cfg.Watchtower.Active {
towerKeyDesc, err := activeChainControl.KeyRing.DeriveKey(
keychain.KeyLocator{
Family: keychain.KeyFamilyTowerID,
Index: 0,
},
)
if err != nil {
return mkErr("error deriving tower key", err)
}
wtCfg := &watchtower.Config{
BlockFetcher: activeChainControl.ChainIO,
DB: dbs.TowerServerDB,
EpochRegistrar: activeChainControl.ChainNotifier,
Net: cfg.net,
NewAddress: func() (btcutil.Address, error) {
return activeChainControl.Wallet.NewAddress(
lnwallet.TaprootPubkey, false,
lnwallet.DefaultAccountName,
)
},
NodeKeyECDH: keychain.NewPubKeyECDH(
towerKeyDesc, activeChainControl.KeyRing,
),
PublishTx: activeChainControl.Wallet.PublishTransaction,
ChainHash: *cfg.ActiveNetParams.GenesisHash,
}
// If there is a tor controller (user wants auto hidden
// services), then store a pointer in the watchtower config.
if torController != nil {
wtCfg.TorController = torController
wtCfg.WatchtowerKeyPath = cfg.Tor.WatchtowerKeyPath
wtCfg.EncryptKey = cfg.Tor.EncryptKey
wtCfg.KeyRing = activeChainControl.KeyRing
switch {
case cfg.Tor.V2:
wtCfg.Type = tor.V2
case cfg.Tor.V3:
wtCfg.Type = tor.V3
}
}
wtConfig, err := cfg.Watchtower.Apply(
wtCfg, lncfg.NormalizeAddresses,
)
if err != nil {
return mkErr("unable to configure watchtower", err)
}
tower, err = watchtower.New(wtConfig)
if err != nil {
return mkErr("unable to create watchtower", err)
}
}
// Initialize the MultiplexAcceptor. If lnd was started with the
// zero-conf feature bit, then this will be a ZeroConfAcceptor.
// Otherwise, this will be a ChainedAcceptor.
var multiAcceptor chanacceptor.MultiplexAcceptor
if cfg.ProtocolOptions.ZeroConf() {
multiAcceptor = chanacceptor.NewZeroConfAcceptor()
} else {
multiAcceptor = chanacceptor.NewChainedAcceptor()
}
// Set up the core server which will listen for incoming peer
// connections.
server, err := newServer(
cfg, cfg.Listeners, dbs, activeChainControl, &idKeyDesc,
activeChainControl.Cfg.WalletUnlockParams.ChansToRestore,
multiAcceptor, torController, tlsManager, leaderElector,
implCfg,
)
if err != nil {
return mkErr("unable to create server", err)
}
// Set up an autopilot manager from the current config. This will be
// used to manage the underlying autopilot agent, starting and stopping
// it at will.
atplCfg, err := initAutoPilot(
server, cfg.Autopilot, activeChainControl.MinHtlcIn,
cfg.ActiveNetParams,
)
if err != nil {
return mkErr("unable to initialize autopilot", err)
}
atplManager, err := autopilot.NewManager(atplCfg)
if err != nil {
return mkErr("unable to create autopilot manager", err)
}
if err := atplManager.Start(); err != nil {
return mkErr("unable to start autopilot manager", err)
}
defer atplManager.Stop()
err = tlsManager.LoadPermanentCertificate(activeChainControl.KeyRing)
if err != nil {
return mkErr("unable to load permanent TLS certificate", err)
}
// Now we have created all dependencies necessary to populate and
// start the RPC server.
err = rpcServer.addDeps(
server, interceptorChain.MacaroonService(), cfg.SubRPCServers,
atplManager, server.invoices, tower, multiAcceptor,
server.invoiceHtlcModifier,
)
if err != nil {
return mkErr("unable to add deps to RPC server", err)
}
if err := rpcServer.Start(); err != nil {
return mkErr("unable to start RPC server", err)
}
defer rpcServer.Stop()
// We transition the RPC state to Active, as the RPC server is up.
interceptorChain.SetRPCActive()
if err := interceptor.Notifier.NotifyReady(true); err != nil {
return mkErr("error notifying ready", err)
}
// We'll wait until we're fully synced to continue the start up of the
// remainder of the daemon. This ensures that we don't accept any
// possibly invalid state transitions, or accept channels with spent
// funds.
_, bestHeight, err := activeChainControl.ChainIO.GetBestBlock()
if err != nil {
return mkErr("unable to determine chain tip", err)
}
ltndLog.InfoS(ctx, "Waiting for chain backend to finish sync",
slog.Int64("start_height", int64(bestHeight)))
type syncResult struct {
synced bool
bestBlockTime int64
err error
}
var syncedResChan = make(chan syncResult, 1)
for {
// We check if the wallet is synced in a separate goroutine as
// the call is blocking, and we want to be able to interrupt it
// if the daemon is shutting down.
go func() {
synced, bestBlockTime, err := activeChainControl.Wallet.
IsSynced()
syncedResChan <- syncResult{synced, bestBlockTime, err}
}()
select {
case <-interceptor.ShutdownChannel():
return nil
case res := <-syncedResChan:
if res.err != nil {
return mkErr("unable to determine if wallet "+
"is synced", res.err)
}
ltndLog.DebugS(ctx, "Syncing to block chain",
"best_block_time", time.Unix(res.bestBlockTime, 0),
"is_synced", res.synced)
if res.synced {
break
}
// If we're not yet synced, we'll wait for a second
// before checking again.
select {
case <-interceptor.ShutdownChannel():
return nil
case <-time.After(time.Second):
continue
}
}
break
}
_, bestHeight, err = activeChainControl.ChainIO.GetBestBlock()
if err != nil {
return mkErr("unable to determine chain tip", err)
}
ltndLog.InfoS(ctx, "Chain backend is fully synced!",
"end_height", bestHeight)
// With all the relevant chains initialized, we can finally start the
// server itself. We start the server in an asynchronous goroutine so
// that we are able to interrupt and shutdown the daemon gracefully in
// case the startup of the subservers do not behave as expected.
errChan := make(chan error)
go func() {
errChan <- server.Start()
}()
defer func() {
err := server.Stop()
if err != nil {
ltndLog.WarnS(ctx, "Stopping the server including all "+
"its subsystems failed with", err)
}
}()
select {
case err := <-errChan:
if err == nil {
break
}
return mkErr("unable to start server", err)
case <-interceptor.ShutdownChannel():
return nil
}
// We transition the server state to Active, as the server is up.
interceptorChain.SetServerActive()
// Now that the server has started, if the autopilot mode is currently
// active, then we'll start the autopilot agent immediately. It will be
// stopped together with the autopilot service.
if cfg.Autopilot.Active {
if err := atplManager.StartAgent(); err != nil {
return mkErr("unable to start autopilot agent", err)
}
}
if cfg.Watchtower.Active {
if err := tower.Start(); err != nil {
return mkErr("unable to start watchtower", err)
}
defer tower.Stop()
}
// Wait for shutdown signal from either a graceful server stop or from
// the interrupt handler.
<-interceptor.ShutdownChannel()
return nil
}
// bakeMacaroon creates a new macaroon with newest version and the given
// permissions then returns it binary serialized.
func bakeMacaroon(ctx context.Context, svc *macaroons.Service,
permissions []bakery.Op) ([]byte, error) {
mac, err := svc.NewMacaroon(
ctx, macaroons.DefaultRootKeyID, permissions...,
)
if err != nil {
return nil, err
}
return mac.M().MarshalBinary()
}
// saveMacaroon bakes a macaroon with the specified macaroon permissions and
// writes it to a file with the given filename and file permissions.
func saveMacaroon(ctx context.Context, svc *macaroons.Service, filename string,
macaroonPermissions []bakery.Op, filePermissions os.FileMode) error {
macaroonBytes, err := bakeMacaroon(ctx, svc, macaroonPermissions)
if err != nil {
return err
}
err = os.WriteFile(filename, macaroonBytes, filePermissions)
if err != nil {
_ = os.Remove(filename)
return err
}
return nil
}
// genDefaultMacaroons checks for three default macaroon files and generates
// them if they do not exist; one admin-level, one for invoice access and one
// read-only. Each macaroon is checked and created independently to ensure all
// three exist. The admin macaroon can also be used to generate more granular
// macaroons.
func genDefaultMacaroons(ctx context.Context, svc *macaroons.Service,
admFile, roFile, invoiceFile string) error {
// First, we'll generate a macaroon that only allows the caller to
// access invoice related calls. This is useful for merchants and other
// services to allow an isolated instance that can only query and
// modify invoices.
if !lnrpc.FileExists(invoiceFile) {
err := saveMacaroon(
ctx, svc, invoiceFile, invoicePermissions, 0644,
)
if err != nil {
return err
}
}
// Generate the read-only macaroon and write it to a file.
if !lnrpc.FileExists(roFile) {
err := saveMacaroon(
ctx, svc, roFile, readPermissions, 0644,
)
if err != nil {
return err
}
}
// Generate the admin macaroon and write it to a file.
if !lnrpc.FileExists(admFile) {
err := saveMacaroon(
ctx, svc, admFile, adminPermissions(),
adminMacaroonFilePermissions,
)
if err != nil {
return err
}
}
return nil
}
// adminPermissions returns a list of all permissions in a safe way that doesn't
// modify any of the source lists.
func adminPermissions() []bakery.Op {
admin := make([]bakery.Op, len(readPermissions)+len(writePermissions))
copy(admin[:len(readPermissions)], readPermissions)
copy(admin[len(readPermissions):], writePermissions)
return admin
}
// createWalletUnlockerService creates a WalletUnlockerService from the passed
// config.
func createWalletUnlockerService(cfg *Config) *walletunlocker.UnlockerService {
// The macaroonFiles are passed to the wallet unlocker so they can be
// deleted and recreated in case the root macaroon key is also changed
// during the change password operation.
macaroonFiles := []string{
cfg.AdminMacPath, cfg.ReadMacPath, cfg.InvoiceMacPath,
}
return walletunlocker.New(
cfg.ActiveNetParams.Params, macaroonFiles,
cfg.ResetWalletTransactions, nil,
)
}
// startGrpcListen starts the GRPC server on the passed listeners.
func startGrpcListen(cfg *Config, grpcServer *grpc.Server,
listeners []*ListenerWithSignal) error {
// Use a WaitGroup so we can be sure the instructions on how to input the
// password is the last thing to be printed to the console.
var wg sync.WaitGroup
for _, lis := range listeners {
wg.Add(1)
go func(lis *ListenerWithSignal) {
rpcsLog.Infof("RPC server listening on %s", lis.Addr())
// Close the ready chan to indicate we are listening.
close(lis.Ready)
wg.Done()
_ = grpcServer.Serve(lis)
}(lis)
}
// If Prometheus monitoring is enabled, start the Prometheus exporter.
if cfg.Prometheus.Enabled() {
err := monitoring.ExportPrometheusMetrics(
grpcServer, cfg.Prometheus,
)
if err != nil {
return err
}
}
// Wait for gRPC servers to be up running.
wg.Wait()
return nil
}
// startRestProxy starts the given REST proxy on the listeners found in the
// config.
func startRestProxy(ctx context.Context, cfg *Config, rpcServer *rpcServer,
restDialOpts []grpc.DialOption,
restListen func(net.Addr) (net.Listener, error)) (func(), error) {
// We use the first RPC listener as the destination for our REST proxy.
// If the listener is set to listen on all interfaces, we replace it
// with localhost, as we cannot dial it directly.
restProxyDest := cfg.RPCListeners[0].String()
switch {
case strings.Contains(restProxyDest, "0.0.0.0"):
restProxyDest = strings.Replace(
restProxyDest, "0.0.0.0", "127.0.0.1", 1,
)
case strings.Contains(restProxyDest, "[::]"):
restProxyDest = strings.Replace(
restProxyDest, "[::]", "[::1]", 1,
)
}
var shutdownFuncs []func()
shutdown := func() {
for _, shutdownFn := range shutdownFuncs {
shutdownFn()
}
}
// Start a REST proxy for our gRPC server.
ctx, cancel := context.WithCancel(ctx)
shutdownFuncs = append(shutdownFuncs, cancel)
// We'll set up a proxy that will forward REST calls to the GRPC
// server.
//
// The default JSON marshaler of the REST proxy only sets OrigName to
// true, which instructs it to use the same field names as specified in
// the proto file and not switch to camel case. What we also want is
// that the marshaler prints all values, even if they are falsey.
customMarshalerOption := proxy.WithMarshalerOption(
proxy.MIMEWildcard, &proxy.JSONPb{
MarshalOptions: *lnrpc.RESTJsonMarshalOpts,
UnmarshalOptions: *lnrpc.RESTJsonUnmarshalOpts,
},
)
mux := proxy.NewServeMux(
customMarshalerOption,
// Don't allow falling back to other HTTP methods, we want exact
// matches only. The actual method to be used can be overwritten
// by setting X-HTTP-Method-Override so there should be no
// reason for not specifying the correct method in the first
// place.
proxy.WithDisablePathLengthFallback(),
)
// Register our services with the REST proxy.
err := rpcServer.RegisterWithRestProxy(
ctx, mux, restDialOpts, restProxyDest,
)
if err != nil {
return nil, err
}
// Wrap the default grpc-gateway handler with the WebSocket handler.
restHandler := lnrpc.NewWebSocketProxy(
mux, rpcsLog, cfg.WSPingInterval, cfg.WSPongWait,
lnrpc.LndClientStreamingURIs,
)
// Use a WaitGroup so we can be sure the instructions on how to input the
// password is the last thing to be printed to the console.
var wg sync.WaitGroup
// Now spin up a network listener for each requested port and start a
// goroutine that serves REST with the created mux there.
for _, restEndpoint := range cfg.RESTListeners {
lis, err := restListen(restEndpoint)
if err != nil {
ltndLog.Errorf("gRPC proxy unable to listen on %s",
restEndpoint)
return nil, err
}
shutdownFuncs = append(shutdownFuncs, func() {
err := lis.Close()
if err != nil {
rpcsLog.Errorf("Error closing listener: %v",
err)
}
})
wg.Add(1)
go func() {
rpcsLog.Infof("gRPC proxy started at %s", lis.Addr())
// Create our proxy chain now. A request will pass
// through the following chain:
// req ---> CORS handler --> WS proxy --->
// REST proxy --> gRPC endpoint
corsHandler := allowCORS(restHandler, cfg.RestCORS)
wg.Done()
err := http.Serve(lis, corsHandler)
if err != nil && !lnrpc.IsClosedConnError(err) {
rpcsLog.Error(err)
}
}()
}
// Wait for REST servers to be up running.
wg.Wait()
return shutdown, nil
}
package lnencrypt
import (
"crypto/rand"
"crypto/sha256"
"fmt"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/keychain"
"golang.org/x/crypto/chacha20poly1305"
)
// baseEncryptionKeyLoc is the KeyLocator that we'll use to derive the base
// encryption key used for encrypting all payloads. We use this to then
// derive the actual key that we'll use for encryption. We do this
// rather than using the raw key, as we assume that we can't obtain the raw
// keys, and we don't want to require that the HSM know our target cipher for
// encryption.
//
// TODO(roasbeef): possibly unique encrypt?
var baseEncryptionKeyLoc = keychain.KeyLocator{
Family: keychain.KeyFamilyBaseEncryption,
Index: 0,
}
// EncrypterDecrypter is an interface representing an object that encrypts or
// decrypts data.
type EncrypterDecrypter interface {
// EncryptPayloadToWriter attempts to write the set of provided bytes
// into the passed io.Writer in an encrypted form.
EncryptPayloadToWriter([]byte, io.Writer) error
// DecryptPayloadFromReader attempts to decrypt the encrypted bytes
// within the passed io.Reader instance using the key derived from
// the passed keyRing.
DecryptPayloadFromReader(io.Reader) ([]byte, error)
}
// Encrypter is a struct responsible for encrypting and decrypting data.
type Encrypter struct {
encryptionKey []byte
}
// KeyRingEncrypter derives an encryption key to encrypt all our files that are
// written to disk and returns an Encrypter object holding the key.
//
// The key itself, is the sha2 of a base key that we get from the keyring. We
// derive the key this way as we don't force the HSM (or any future
// abstractions) to be able to derive and know of the cipher that we'll use
// within our protocol.
func KeyRingEncrypter(keyRing keychain.KeyRing) (*Encrypter, error) {
// key = SHA256(baseKey)
baseKey, err := keyRing.DeriveKey(
baseEncryptionKeyLoc,
)
if err != nil {
return nil, err
}
encryptionKey := sha256.Sum256(
baseKey.PubKey.SerializeCompressed(),
)
// TODO(roasbeef): throw back in ECDH?
return &Encrypter{
encryptionKey: encryptionKey[:],
}, nil
}
// ECDHEncrypter derives an encryption key by performing an ECDH operation on
// the passed keys. The resulting key is used to encrypt or decrypt files with
// sensitive content.
func ECDHEncrypter(localKey *btcec.PrivateKey,
remoteKey *btcec.PublicKey) (*Encrypter, error) {
ecdh := keychain.PrivKeyECDH{
PrivKey: localKey,
}
encryptionKey, err := ecdh.ECDH(remoteKey)
if err != nil {
return nil, fmt.Errorf("error deriving encryption key: %w", err)
}
return &Encrypter{
encryptionKey: encryptionKey[:],
}, nil
}
// EncryptPayloadToWriter attempts to write the set of provided bytes into the
// passed io.Writer in an encrypted form. We use a 24-byte chachapoly AEAD
// instance with a randomized nonce that's pre-pended to the final payload and
// used as associated data in the AEAD.
func (e Encrypter) EncryptPayloadToWriter(payload []byte,
w io.Writer) error {
// Before encryption, we'll initialize our cipher with the target
// encryption key, and also read out our random 24-byte nonce we use
// for encryption. Note that we use NewX, not New, as the latter
// version requires a 12-byte nonce, not a 24-byte nonce.
cipher, err := chacha20poly1305.NewX(e.encryptionKey)
if err != nil {
return err
}
var nonce [chacha20poly1305.NonceSizeX]byte
if _, err := rand.Read(nonce[:]); err != nil {
return err
}
// Finally, we encrypted the final payload, and write out our
// ciphertext with nonce pre-pended.
ciphertext := cipher.Seal(nil, nonce[:], payload, nonce[:])
if _, err := w.Write(nonce[:]); err != nil {
return err
}
if _, err := w.Write(ciphertext); err != nil {
return err
}
return nil
}
// DecryptPayloadFromReader attempts to decrypt the encrypted bytes within the
// passed io.Reader instance using the key derived from the passed keyRing. For
// further details regarding the key derivation protocol, see the
// KeyRingEncrypter function.
func (e Encrypter) DecryptPayloadFromReader(payload io.Reader) ([]byte,
error) {
// Next, we'll read out the entire blob as we need to isolate the nonce
// from the rest of the ciphertext.
packedPayload, err := io.ReadAll(payload)
if err != nil {
return nil, err
}
if len(packedPayload) < chacha20poly1305.NonceSizeX {
return nil, fmt.Errorf("payload size too small, must be at "+
"least %v bytes", chacha20poly1305.NonceSizeX)
}
nonce := packedPayload[:chacha20poly1305.NonceSizeX]
ciphertext := packedPayload[chacha20poly1305.NonceSizeX:]
// Now that we have the cipher text and the nonce separated, we can go
// ahead and decrypt the final blob so we can properly serialize.
cipher, err := chacha20poly1305.NewX(e.encryptionKey)
if err != nil {
return nil, err
}
plaintext, err := cipher.Open(nil, nonce, ciphertext, nonce)
if err != nil {
return nil, err
}
return plaintext, nil
}
package lnencrypt
import (
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/keychain"
)
var (
testWalletPrivKey = []byte{
0x2b, 0xd8, 0x06, 0xc9, 0x7f, 0x0e, 0x00, 0xaf,
0x1a, 0x1f, 0xc3, 0x32, 0x8f, 0xa7, 0x63, 0xa9,
0x26, 0x97, 0x23, 0xc8, 0xdb, 0x8f, 0xac, 0x4f,
0x93, 0xaf, 0x71, 0xdb, 0x18, 0x6d, 0x6e, 0x90,
}
)
type MockKeyRing struct {
Fail bool
}
func (m *MockKeyRing) DeriveNextKey(
keyFam keychain.KeyFamily) (keychain.KeyDescriptor, error) {
return keychain.KeyDescriptor{}, nil
}
func (m *MockKeyRing) DeriveKey(
keyLoc keychain.KeyLocator) (keychain.KeyDescriptor, error) {
if m.Fail {
return keychain.KeyDescriptor{}, fmt.Errorf("fail")
}
_, pub := btcec.PrivKeyFromBytes(testWalletPrivKey)
return keychain.KeyDescriptor{
PubKey: pub,
}, nil
}
package lnmock
import (
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/stretchr/testify/mock"
)
// MockChain is a mock implementation of the Chain interface.
type MockChain struct {
mock.Mock
}
// Compile-time constraint to ensure MockChain implements the Chain interface.
var _ chain.Interface = (*MockChain)(nil)
func (m *MockChain) Start() error {
args := m.Called()
return args.Error(0)
}
func (m *MockChain) Stop() {
m.Called()
}
func (m *MockChain) WaitForShutdown() {
m.Called()
}
func (m *MockChain) GetBestBlock() (*chainhash.Hash, int32, error) {
args := m.Called()
if args.Get(0) == nil {
return nil, args.Get(1).(int32), args.Error(2)
}
return args.Get(0).(*chainhash.Hash), args.Get(1).(int32), args.Error(2)
}
func (m *MockChain) GetBlock(hash *chainhash.Hash) (*wire.MsgBlock, error) {
args := m.Called(hash)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*wire.MsgBlock), args.Error(1)
}
func (m *MockChain) GetBlockHash(height int64) (*chainhash.Hash, error) {
args := m.Called(height)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*chainhash.Hash), args.Error(1)
}
func (m *MockChain) GetBlockHeader(hash *chainhash.Hash) (
*wire.BlockHeader, error) {
args := m.Called(hash)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*wire.BlockHeader), args.Error(1)
}
func (m *MockChain) GetUtxo(op *wire.OutPoint, pkScript []byte,
heightHint uint32, cancel <-chan struct{}) (*wire.TxOut, error) {
args := m.Called(op, pkScript, heightHint, cancel)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*wire.TxOut), args.Error(1)
}
func (m *MockChain) IsCurrent() bool {
args := m.Called()
return args.Bool(0)
}
func (m *MockChain) FilterBlocks(req *chain.FilterBlocksRequest) (
*chain.FilterBlocksResponse, error) {
args := m.Called(req)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*chain.FilterBlocksResponse), args.Error(1)
}
func (m *MockChain) BlockStamp() (*waddrmgr.BlockStamp, error) {
args := m.Called()
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*waddrmgr.BlockStamp), args.Error(1)
}
func (m *MockChain) SendRawTransaction(tx *wire.MsgTx, allowHighFees bool) (
*chainhash.Hash, error) {
args := m.Called(tx, allowHighFees)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*chainhash.Hash), args.Error(1)
}
func (m *MockChain) Rescan(startHash *chainhash.Hash, addrs []btcutil.Address,
outPoints map[wire.OutPoint]btcutil.Address) error {
args := m.Called(startHash, addrs, outPoints)
return args.Error(0)
}
func (m *MockChain) NotifyReceived(addrs []btcutil.Address) error {
args := m.Called(addrs)
return args.Error(0)
}
func (m *MockChain) NotifyBlocks() error {
args := m.Called()
return args.Error(0)
}
func (m *MockChain) Notifications() <-chan interface{} {
args := m.Called()
return args.Get(0).(<-chan interface{})
}
func (m *MockChain) BackEnd() string {
args := m.Called()
return args.String(0)
}
func (m *MockChain) TestMempoolAccept(txns []*wire.MsgTx, maxFeeRate float64) (
[]*btcjson.TestMempoolAcceptResult, error) {
args := m.Called(txns, maxFeeRate)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).([]*btcjson.TestMempoolAcceptResult), args.Error(1)
}
func (m *MockChain) MapRPCErr(err error) error {
args := m.Called(err)
return args.Error(0)
}
// NOTE: forcetypeassert is skipped for the mock because the test would fail if
// the returned value doesn't match the type.
package lnmock
import (
"time"
"github.com/lightningnetwork/lnd/clock"
"github.com/stretchr/testify/mock"
)
// MockClock implements the `clock.Clock` interface.
type MockClock struct {
mock.Mock
}
// Compile time assertion that MockClock implements clock.Clock.
var _ clock.Clock = (*MockClock)(nil)
func (m *MockClock) Now() time.Time {
args := m.Called()
return args.Get(0).(time.Time)
}
func (m *MockClock) TickAfter(d time.Duration) <-chan time.Time {
args := m.Called(d)
return args.Get(0).(chan time.Time)
}
package lnmock
import (
"bytes"
"github.com/lightningnetwork/lnd/lnwire"
)
// MockOnion returns a mock onion payload.
func MockOnion() [lnwire.OnionPacketSize]byte {
var onion [lnwire.OnionPacketSize]byte
onionBlob := bytes.Repeat([]byte{1}, lnwire.OnionPacketSize)
copy(onion[:], onionBlob)
return onion
}
package lnpeer
import (
"net"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/mock"
)
// MockPeer implements the `lnpeer.Peer` interface.
type MockPeer struct {
mock.Mock
}
// Compile time assertion that MockPeer implements lnpeer.Peer.
var _ Peer = (*MockPeer)(nil)
func (m *MockPeer) SendMessage(sync bool, msgs ...lnwire.Message) error {
args := m.Called(sync, msgs)
return args.Error(0)
}
func (m *MockPeer) SendMessageLazy(sync bool, msgs ...lnwire.Message) error {
args := m.Called(sync, msgs)
return args.Error(0)
}
func (m *MockPeer) AddNewChannel(channel *NewChannel,
cancel <-chan struct{}) error {
args := m.Called(channel, cancel)
return args.Error(0)
}
func (m *MockPeer) AddPendingChannel(cid lnwire.ChannelID,
cancel <-chan struct{}) error {
args := m.Called(cid, cancel)
return args.Error(0)
}
func (m *MockPeer) RemovePendingChannel(cid lnwire.ChannelID) error {
args := m.Called(cid)
return args.Error(0)
}
func (m *MockPeer) WipeChannel(op *wire.OutPoint) {
m.Called(op)
}
func (m *MockPeer) PubKey() [33]byte {
args := m.Called()
return args.Get(0).([33]byte)
}
func (m *MockPeer) IdentityKey() *btcec.PublicKey {
args := m.Called()
return args.Get(0).(*btcec.PublicKey)
}
func (m *MockPeer) Address() net.Addr {
args := m.Called()
return args.Get(0).(net.Addr)
}
func (m *MockPeer) QuitSignal() <-chan struct{} {
args := m.Called()
return args.Get(0).(<-chan struct{})
}
func (m *MockPeer) LocalFeatures() *lnwire.FeatureVector {
args := m.Called()
return args.Get(0).(*lnwire.FeatureVector)
}
func (m *MockPeer) RemoteFeatures() *lnwire.FeatureVector {
args := m.Called()
return args.Get(0).(*lnwire.FeatureVector)
}
func (m *MockPeer) Disconnect(err error) {}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v3.21.12
// source: autopilotrpc/autopilot.proto
package autopilotrpc
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type StatusRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *StatusRequest) Reset() {
*x = StatusRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_autopilotrpc_autopilot_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *StatusRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StatusRequest) ProtoMessage() {}
func (x *StatusRequest) ProtoReflect() protoreflect.Message {
mi := &file_autopilotrpc_autopilot_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StatusRequest.ProtoReflect.Descriptor instead.
func (*StatusRequest) Descriptor() ([]byte, []int) {
return file_autopilotrpc_autopilot_proto_rawDescGZIP(), []int{0}
}
type StatusResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Indicates whether the autopilot is active or not.
Active bool `protobuf:"varint,1,opt,name=active,proto3" json:"active,omitempty"`
}
func (x *StatusResponse) Reset() {
*x = StatusResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_autopilotrpc_autopilot_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *StatusResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StatusResponse) ProtoMessage() {}
func (x *StatusResponse) ProtoReflect() protoreflect.Message {
mi := &file_autopilotrpc_autopilot_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StatusResponse.ProtoReflect.Descriptor instead.
func (*StatusResponse) Descriptor() ([]byte, []int) {
return file_autopilotrpc_autopilot_proto_rawDescGZIP(), []int{1}
}
func (x *StatusResponse) GetActive() bool {
if x != nil {
return x.Active
}
return false
}
type ModifyStatusRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Whether the autopilot agent should be enabled or not.
Enable bool `protobuf:"varint,1,opt,name=enable,proto3" json:"enable,omitempty"`
}
func (x *ModifyStatusRequest) Reset() {
*x = ModifyStatusRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_autopilotrpc_autopilot_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ModifyStatusRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ModifyStatusRequest) ProtoMessage() {}
func (x *ModifyStatusRequest) ProtoReflect() protoreflect.Message {
mi := &file_autopilotrpc_autopilot_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ModifyStatusRequest.ProtoReflect.Descriptor instead.
func (*ModifyStatusRequest) Descriptor() ([]byte, []int) {
return file_autopilotrpc_autopilot_proto_rawDescGZIP(), []int{2}
}
func (x *ModifyStatusRequest) GetEnable() bool {
if x != nil {
return x.Enable
}
return false
}
type ModifyStatusResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ModifyStatusResponse) Reset() {
*x = ModifyStatusResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_autopilotrpc_autopilot_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ModifyStatusResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ModifyStatusResponse) ProtoMessage() {}
func (x *ModifyStatusResponse) ProtoReflect() protoreflect.Message {
mi := &file_autopilotrpc_autopilot_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ModifyStatusResponse.ProtoReflect.Descriptor instead.
func (*ModifyStatusResponse) Descriptor() ([]byte, []int) {
return file_autopilotrpc_autopilot_proto_rawDescGZIP(), []int{3}
}
type QueryScoresRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Pubkeys []string `protobuf:"bytes,1,rep,name=pubkeys,proto3" json:"pubkeys,omitempty"`
// If set, we will ignore the local channel state when calculating scores.
IgnoreLocalState bool `protobuf:"varint,2,opt,name=ignore_local_state,json=ignoreLocalState,proto3" json:"ignore_local_state,omitempty"`
}
func (x *QueryScoresRequest) Reset() {
*x = QueryScoresRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_autopilotrpc_autopilot_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *QueryScoresRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*QueryScoresRequest) ProtoMessage() {}
func (x *QueryScoresRequest) ProtoReflect() protoreflect.Message {
mi := &file_autopilotrpc_autopilot_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use QueryScoresRequest.ProtoReflect.Descriptor instead.
func (*QueryScoresRequest) Descriptor() ([]byte, []int) {
return file_autopilotrpc_autopilot_proto_rawDescGZIP(), []int{4}
}
func (x *QueryScoresRequest) GetPubkeys() []string {
if x != nil {
return x.Pubkeys
}
return nil
}
func (x *QueryScoresRequest) GetIgnoreLocalState() bool {
if x != nil {
return x.IgnoreLocalState
}
return false
}
type QueryScoresResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Results []*QueryScoresResponse_HeuristicResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"`
}
func (x *QueryScoresResponse) Reset() {
*x = QueryScoresResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_autopilotrpc_autopilot_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *QueryScoresResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*QueryScoresResponse) ProtoMessage() {}
func (x *QueryScoresResponse) ProtoReflect() protoreflect.Message {
mi := &file_autopilotrpc_autopilot_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use QueryScoresResponse.ProtoReflect.Descriptor instead.
func (*QueryScoresResponse) Descriptor() ([]byte, []int) {
return file_autopilotrpc_autopilot_proto_rawDescGZIP(), []int{5}
}
func (x *QueryScoresResponse) GetResults() []*QueryScoresResponse_HeuristicResult {
if x != nil {
return x.Results
}
return nil
}
type SetScoresRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The name of the heuristic to provide scores to.
Heuristic string `protobuf:"bytes,1,opt,name=heuristic,proto3" json:"heuristic,omitempty"`
// A map from hex-encoded public keys to scores. Scores must be in the range
// [0.0, 1.0].
Scores map[string]float64 `protobuf:"bytes,2,rep,name=scores,proto3" json:"scores,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"fixed64,2,opt,name=value,proto3"`
}
func (x *SetScoresRequest) Reset() {
*x = SetScoresRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_autopilotrpc_autopilot_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SetScoresRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SetScoresRequest) ProtoMessage() {}
func (x *SetScoresRequest) ProtoReflect() protoreflect.Message {
mi := &file_autopilotrpc_autopilot_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SetScoresRequest.ProtoReflect.Descriptor instead.
func (*SetScoresRequest) Descriptor() ([]byte, []int) {
return file_autopilotrpc_autopilot_proto_rawDescGZIP(), []int{6}
}
func (x *SetScoresRequest) GetHeuristic() string {
if x != nil {
return x.Heuristic
}
return ""
}
func (x *SetScoresRequest) GetScores() map[string]float64 {
if x != nil {
return x.Scores
}
return nil
}
type SetScoresResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *SetScoresResponse) Reset() {
*x = SetScoresResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_autopilotrpc_autopilot_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SetScoresResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SetScoresResponse) ProtoMessage() {}
func (x *SetScoresResponse) ProtoReflect() protoreflect.Message {
mi := &file_autopilotrpc_autopilot_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SetScoresResponse.ProtoReflect.Descriptor instead.
func (*SetScoresResponse) Descriptor() ([]byte, []int) {
return file_autopilotrpc_autopilot_proto_rawDescGZIP(), []int{7}
}
type QueryScoresResponse_HeuristicResult struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Heuristic string `protobuf:"bytes,1,opt,name=heuristic,proto3" json:"heuristic,omitempty"`
Scores map[string]float64 `protobuf:"bytes,2,rep,name=scores,proto3" json:"scores,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"fixed64,2,opt,name=value,proto3"`
}
func (x *QueryScoresResponse_HeuristicResult) Reset() {
*x = QueryScoresResponse_HeuristicResult{}
if protoimpl.UnsafeEnabled {
mi := &file_autopilotrpc_autopilot_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *QueryScoresResponse_HeuristicResult) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*QueryScoresResponse_HeuristicResult) ProtoMessage() {}
func (x *QueryScoresResponse_HeuristicResult) ProtoReflect() protoreflect.Message {
mi := &file_autopilotrpc_autopilot_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use QueryScoresResponse_HeuristicResult.ProtoReflect.Descriptor instead.
func (*QueryScoresResponse_HeuristicResult) Descriptor() ([]byte, []int) {
return file_autopilotrpc_autopilot_proto_rawDescGZIP(), []int{5, 0}
}
func (x *QueryScoresResponse_HeuristicResult) GetHeuristic() string {
if x != nil {
return x.Heuristic
}
return ""
}
func (x *QueryScoresResponse_HeuristicResult) GetScores() map[string]float64 {
if x != nil {
return x.Scores
}
return nil
}
var File_autopilotrpc_autopilot_proto protoreflect.FileDescriptor
var file_autopilotrpc_autopilot_proto_rawDesc = []byte{
0x0a, 0x1c, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x61,
0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c,
0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x72, 0x70, 0x63, 0x22, 0x0f, 0x0a, 0x0d,
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x28, 0x0a,
0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52,
0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x22, 0x2d, 0x0a, 0x13, 0x4d, 0x6f, 0x64, 0x69, 0x66,
0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16,
0x0a, 0x06, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06,
0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79,
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5c,
0x0a, 0x12, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x18,
0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x12, 0x2c,
0x0a, 0x12, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x73,
0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x69, 0x67, 0x6e, 0x6f,
0x72, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x22, 0xa6, 0x02, 0x0a,
0x13, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18,
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f,
0x74, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x48, 0x65, 0x75, 0x72, 0x69, 0x73, 0x74,
0x69, 0x63, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74,
0x73, 0x1a, 0xc1, 0x01, 0x0a, 0x0f, 0x48, 0x65, 0x75, 0x72, 0x69, 0x73, 0x74, 0x69, 0x63, 0x52,
0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x68, 0x65, 0x75, 0x72, 0x69, 0x73, 0x74,
0x69, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x68, 0x65, 0x75, 0x72, 0x69, 0x73,
0x74, 0x69, 0x63, 0x12, 0x55, 0x0a, 0x06, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x18, 0x02, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x72,
0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x48, 0x65, 0x75, 0x72, 0x69, 0x73, 0x74, 0x69, 0x63,
0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74,
0x72, 0x79, 0x52, 0x06, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x53, 0x63,
0x6f, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xaf, 0x01, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x53, 0x63, 0x6f,
0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x68, 0x65,
0x75, 0x72, 0x69, 0x73, 0x74, 0x69, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x68,
0x65, 0x75, 0x72, 0x69, 0x73, 0x74, 0x69, 0x63, 0x12, 0x42, 0x0a, 0x06, 0x73, 0x63, 0x6f, 0x72,
0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70,
0x69, 0x6c, 0x6f, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x53, 0x63, 0x6f, 0x72, 0x65,
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x1a, 0x39, 0x0a, 0x0b,
0x53, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x13, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x53, 0x63,
0x6f, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xc9, 0x02, 0x0a,
0x09, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x12, 0x43, 0x0a, 0x06, 0x53, 0x74,
0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74,
0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x1c, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x72, 0x70, 0x63,
0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x55, 0x0a, 0x0c, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
0x21, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4d,
0x6f, 0x64, 0x69, 0x66, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x22, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x72, 0x70,
0x63, 0x2e, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0b, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53,
0x63, 0x6f, 0x72, 0x65, 0x73, 0x12, 0x20, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f,
0x74, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69,
0x6c, 0x6f, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x63, 0x6f, 0x72,
0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x09, 0x53, 0x65,
0x74, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x12, 0x1e, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69,
0x6c, 0x6f, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69,
0x6c, 0x6f, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67,
0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2f, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_autopilotrpc_autopilot_proto_rawDescOnce sync.Once
file_autopilotrpc_autopilot_proto_rawDescData = file_autopilotrpc_autopilot_proto_rawDesc
)
func file_autopilotrpc_autopilot_proto_rawDescGZIP() []byte {
file_autopilotrpc_autopilot_proto_rawDescOnce.Do(func() {
file_autopilotrpc_autopilot_proto_rawDescData = protoimpl.X.CompressGZIP(file_autopilotrpc_autopilot_proto_rawDescData)
})
return file_autopilotrpc_autopilot_proto_rawDescData
}
var file_autopilotrpc_autopilot_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
var file_autopilotrpc_autopilot_proto_goTypes = []interface{}{
(*StatusRequest)(nil), // 0: autopilotrpc.StatusRequest
(*StatusResponse)(nil), // 1: autopilotrpc.StatusResponse
(*ModifyStatusRequest)(nil), // 2: autopilotrpc.ModifyStatusRequest
(*ModifyStatusResponse)(nil), // 3: autopilotrpc.ModifyStatusResponse
(*QueryScoresRequest)(nil), // 4: autopilotrpc.QueryScoresRequest
(*QueryScoresResponse)(nil), // 5: autopilotrpc.QueryScoresResponse
(*SetScoresRequest)(nil), // 6: autopilotrpc.SetScoresRequest
(*SetScoresResponse)(nil), // 7: autopilotrpc.SetScoresResponse
(*QueryScoresResponse_HeuristicResult)(nil), // 8: autopilotrpc.QueryScoresResponse.HeuristicResult
nil, // 9: autopilotrpc.QueryScoresResponse.HeuristicResult.ScoresEntry
nil, // 10: autopilotrpc.SetScoresRequest.ScoresEntry
}
var file_autopilotrpc_autopilot_proto_depIdxs = []int32{
8, // 0: autopilotrpc.QueryScoresResponse.results:type_name -> autopilotrpc.QueryScoresResponse.HeuristicResult
10, // 1: autopilotrpc.SetScoresRequest.scores:type_name -> autopilotrpc.SetScoresRequest.ScoresEntry
9, // 2: autopilotrpc.QueryScoresResponse.HeuristicResult.scores:type_name -> autopilotrpc.QueryScoresResponse.HeuristicResult.ScoresEntry
0, // 3: autopilotrpc.Autopilot.Status:input_type -> autopilotrpc.StatusRequest
2, // 4: autopilotrpc.Autopilot.ModifyStatus:input_type -> autopilotrpc.ModifyStatusRequest
4, // 5: autopilotrpc.Autopilot.QueryScores:input_type -> autopilotrpc.QueryScoresRequest
6, // 6: autopilotrpc.Autopilot.SetScores:input_type -> autopilotrpc.SetScoresRequest
1, // 7: autopilotrpc.Autopilot.Status:output_type -> autopilotrpc.StatusResponse
3, // 8: autopilotrpc.Autopilot.ModifyStatus:output_type -> autopilotrpc.ModifyStatusResponse
5, // 9: autopilotrpc.Autopilot.QueryScores:output_type -> autopilotrpc.QueryScoresResponse
7, // 10: autopilotrpc.Autopilot.SetScores:output_type -> autopilotrpc.SetScoresResponse
7, // [7:11] is the sub-list for method output_type
3, // [3:7] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
}
func init() { file_autopilotrpc_autopilot_proto_init() }
func file_autopilotrpc_autopilot_proto_init() {
if File_autopilotrpc_autopilot_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_autopilotrpc_autopilot_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StatusRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_autopilotrpc_autopilot_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StatusResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_autopilotrpc_autopilot_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ModifyStatusRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_autopilotrpc_autopilot_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ModifyStatusResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_autopilotrpc_autopilot_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*QueryScoresRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_autopilotrpc_autopilot_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*QueryScoresResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_autopilotrpc_autopilot_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SetScoresRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_autopilotrpc_autopilot_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SetScoresResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_autopilotrpc_autopilot_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*QueryScoresResponse_HeuristicResult); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_autopilotrpc_autopilot_proto_rawDesc,
NumEnums: 0,
NumMessages: 11,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_autopilotrpc_autopilot_proto_goTypes,
DependencyIndexes: file_autopilotrpc_autopilot_proto_depIdxs,
MessageInfos: file_autopilotrpc_autopilot_proto_msgTypes,
}.Build()
File_autopilotrpc_autopilot_proto = out.File
file_autopilotrpc_autopilot_proto_rawDesc = nil
file_autopilotrpc_autopilot_proto_goTypes = nil
file_autopilotrpc_autopilot_proto_depIdxs = nil
}
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: autopilotrpc/autopilot.proto
/*
Package autopilotrpc is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package autopilotrpc
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
func request_Autopilot_Status_0(ctx context.Context, marshaler runtime.Marshaler, client AutopilotClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq StatusRequest
var metadata runtime.ServerMetadata
msg, err := client.Status(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Autopilot_Status_0(ctx context.Context, marshaler runtime.Marshaler, server AutopilotServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq StatusRequest
var metadata runtime.ServerMetadata
msg, err := server.Status(ctx, &protoReq)
return msg, metadata, err
}
func request_Autopilot_ModifyStatus_0(ctx context.Context, marshaler runtime.Marshaler, client AutopilotClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ModifyStatusRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ModifyStatus(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Autopilot_ModifyStatus_0(ctx context.Context, marshaler runtime.Marshaler, server AutopilotServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ModifyStatusRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ModifyStatus(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Autopilot_QueryScores_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Autopilot_QueryScores_0(ctx context.Context, marshaler runtime.Marshaler, client AutopilotClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq QueryScoresRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Autopilot_QueryScores_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.QueryScores(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Autopilot_QueryScores_0(ctx context.Context, marshaler runtime.Marshaler, server AutopilotServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq QueryScoresRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Autopilot_QueryScores_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.QueryScores(ctx, &protoReq)
return msg, metadata, err
}
func request_Autopilot_SetScores_0(ctx context.Context, marshaler runtime.Marshaler, client AutopilotClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SetScoresRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.SetScores(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Autopilot_SetScores_0(ctx context.Context, marshaler runtime.Marshaler, server AutopilotServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SetScoresRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.SetScores(ctx, &protoReq)
return msg, metadata, err
}
// RegisterAutopilotHandlerServer registers the http handlers for service Autopilot to "mux".
// UnaryRPC :call AutopilotServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterAutopilotHandlerFromEndpoint instead.
func RegisterAutopilotHandlerServer(ctx context.Context, mux *runtime.ServeMux, server AutopilotServer) error {
mux.Handle("GET", pattern_Autopilot_Status_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/autopilotrpc.Autopilot/Status", runtime.WithHTTPPathPattern("/v2/autopilot/status"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Autopilot_Status_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Autopilot_Status_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Autopilot_ModifyStatus_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/autopilotrpc.Autopilot/ModifyStatus", runtime.WithHTTPPathPattern("/v2/autopilot/modify"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Autopilot_ModifyStatus_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Autopilot_ModifyStatus_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Autopilot_QueryScores_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/autopilotrpc.Autopilot/QueryScores", runtime.WithHTTPPathPattern("/v2/autopilot/scores"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Autopilot_QueryScores_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Autopilot_QueryScores_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Autopilot_SetScores_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/autopilotrpc.Autopilot/SetScores", runtime.WithHTTPPathPattern("/v2/autopilot/scores"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Autopilot_SetScores_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Autopilot_SetScores_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterAutopilotHandlerFromEndpoint is same as RegisterAutopilotHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterAutopilotHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterAutopilotHandler(ctx, mux, conn)
}
// RegisterAutopilotHandler registers the http handlers for service Autopilot to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterAutopilotHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterAutopilotHandlerClient(ctx, mux, NewAutopilotClient(conn))
}
// RegisterAutopilotHandlerClient registers the http handlers for service Autopilot
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "AutopilotClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "AutopilotClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "AutopilotClient" to call the correct interceptors.
func RegisterAutopilotHandlerClient(ctx context.Context, mux *runtime.ServeMux, client AutopilotClient) error {
mux.Handle("GET", pattern_Autopilot_Status_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/autopilotrpc.Autopilot/Status", runtime.WithHTTPPathPattern("/v2/autopilot/status"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Autopilot_Status_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Autopilot_Status_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Autopilot_ModifyStatus_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/autopilotrpc.Autopilot/ModifyStatus", runtime.WithHTTPPathPattern("/v2/autopilot/modify"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Autopilot_ModifyStatus_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Autopilot_ModifyStatus_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Autopilot_QueryScores_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/autopilotrpc.Autopilot/QueryScores", runtime.WithHTTPPathPattern("/v2/autopilot/scores"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Autopilot_QueryScores_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Autopilot_QueryScores_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Autopilot_SetScores_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/autopilotrpc.Autopilot/SetScores", runtime.WithHTTPPathPattern("/v2/autopilot/scores"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Autopilot_SetScores_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Autopilot_SetScores_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_Autopilot_Status_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "autopilot", "status"}, ""))
pattern_Autopilot_ModifyStatus_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "autopilot", "modify"}, ""))
pattern_Autopilot_QueryScores_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "autopilot", "scores"}, ""))
pattern_Autopilot_SetScores_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "autopilot", "scores"}, ""))
)
var (
forward_Autopilot_Status_0 = runtime.ForwardResponseMessage
forward_Autopilot_ModifyStatus_0 = runtime.ForwardResponseMessage
forward_Autopilot_QueryScores_0 = runtime.ForwardResponseMessage
forward_Autopilot_SetScores_0 = runtime.ForwardResponseMessage
)
// Code generated by falafel 0.9.2. DO NOT EDIT.
// source: autopilot.proto
package autopilotrpc
import (
"context"
gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protojson"
)
func RegisterAutopilotJSONCallbacks(registry map[string]func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error))) {
marshaler := &gateway.JSONPb{
MarshalOptions: protojson.MarshalOptions{
UseProtoNames: true,
EmitUnpopulated: true,
},
}
registry["autopilotrpc.Autopilot.Status"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &StatusRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewAutopilotClient(conn)
resp, err := client.Status(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["autopilotrpc.Autopilot.ModifyStatus"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ModifyStatusRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewAutopilotClient(conn)
resp, err := client.ModifyStatus(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["autopilotrpc.Autopilot.QueryScores"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &QueryScoresRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewAutopilotClient(conn)
resp, err := client.QueryScores(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["autopilotrpc.Autopilot.SetScores"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SetScoresRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewAutopilotClient(conn)
resp, err := client.SetScores(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package autopilotrpc
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// AutopilotClient is the client API for Autopilot service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type AutopilotClient interface {
// lncli: `autopilot status`
// Status returns whether the daemon's autopilot agent is active.
Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error)
// ModifyStatus is used to modify the status of the autopilot agent, like
// enabling or disabling it.
ModifyStatus(ctx context.Context, in *ModifyStatusRequest, opts ...grpc.CallOption) (*ModifyStatusResponse, error)
// lncli: `autopilot query`
// QueryScores queries all available autopilot heuristics, in addition to any
// active combination of these heruristics, for the scores they would give to
// the given nodes.
QueryScores(ctx context.Context, in *QueryScoresRequest, opts ...grpc.CallOption) (*QueryScoresResponse, error)
// SetScores attempts to set the scores used by the running autopilot agent,
// if the external scoring heuristic is enabled.
SetScores(ctx context.Context, in *SetScoresRequest, opts ...grpc.CallOption) (*SetScoresResponse, error)
}
type autopilotClient struct {
cc grpc.ClientConnInterface
}
func NewAutopilotClient(cc grpc.ClientConnInterface) AutopilotClient {
return &autopilotClient{cc}
}
func (c *autopilotClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) {
out := new(StatusResponse)
err := c.cc.Invoke(ctx, "/autopilotrpc.Autopilot/Status", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *autopilotClient) ModifyStatus(ctx context.Context, in *ModifyStatusRequest, opts ...grpc.CallOption) (*ModifyStatusResponse, error) {
out := new(ModifyStatusResponse)
err := c.cc.Invoke(ctx, "/autopilotrpc.Autopilot/ModifyStatus", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *autopilotClient) QueryScores(ctx context.Context, in *QueryScoresRequest, opts ...grpc.CallOption) (*QueryScoresResponse, error) {
out := new(QueryScoresResponse)
err := c.cc.Invoke(ctx, "/autopilotrpc.Autopilot/QueryScores", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *autopilotClient) SetScores(ctx context.Context, in *SetScoresRequest, opts ...grpc.CallOption) (*SetScoresResponse, error) {
out := new(SetScoresResponse)
err := c.cc.Invoke(ctx, "/autopilotrpc.Autopilot/SetScores", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// AutopilotServer is the server API for Autopilot service.
// All implementations must embed UnimplementedAutopilotServer
// for forward compatibility
type AutopilotServer interface {
// lncli: `autopilot status`
// Status returns whether the daemon's autopilot agent is active.
Status(context.Context, *StatusRequest) (*StatusResponse, error)
// ModifyStatus is used to modify the status of the autopilot agent, like
// enabling or disabling it.
ModifyStatus(context.Context, *ModifyStatusRequest) (*ModifyStatusResponse, error)
// lncli: `autopilot query`
// QueryScores queries all available autopilot heuristics, in addition to any
// active combination of these heruristics, for the scores they would give to
// the given nodes.
QueryScores(context.Context, *QueryScoresRequest) (*QueryScoresResponse, error)
// SetScores attempts to set the scores used by the running autopilot agent,
// if the external scoring heuristic is enabled.
SetScores(context.Context, *SetScoresRequest) (*SetScoresResponse, error)
mustEmbedUnimplementedAutopilotServer()
}
// UnimplementedAutopilotServer must be embedded to have forward compatible implementations.
type UnimplementedAutopilotServer struct {
}
func (UnimplementedAutopilotServer) Status(context.Context, *StatusRequest) (*StatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Status not implemented")
}
func (UnimplementedAutopilotServer) ModifyStatus(context.Context, *ModifyStatusRequest) (*ModifyStatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ModifyStatus not implemented")
}
func (UnimplementedAutopilotServer) QueryScores(context.Context, *QueryScoresRequest) (*QueryScoresResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method QueryScores not implemented")
}
func (UnimplementedAutopilotServer) SetScores(context.Context, *SetScoresRequest) (*SetScoresResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetScores not implemented")
}
func (UnimplementedAutopilotServer) mustEmbedUnimplementedAutopilotServer() {}
// UnsafeAutopilotServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to AutopilotServer will
// result in compilation errors.
type UnsafeAutopilotServer interface {
mustEmbedUnimplementedAutopilotServer()
}
func RegisterAutopilotServer(s grpc.ServiceRegistrar, srv AutopilotServer) {
s.RegisterService(&Autopilot_ServiceDesc, srv)
}
func _Autopilot_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StatusRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AutopilotServer).Status(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/autopilotrpc.Autopilot/Status",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AutopilotServer).Status(ctx, req.(*StatusRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Autopilot_ModifyStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ModifyStatusRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AutopilotServer).ModifyStatus(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/autopilotrpc.Autopilot/ModifyStatus",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AutopilotServer).ModifyStatus(ctx, req.(*ModifyStatusRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Autopilot_QueryScores_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(QueryScoresRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AutopilotServer).QueryScores(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/autopilotrpc.Autopilot/QueryScores",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AutopilotServer).QueryScores(ctx, req.(*QueryScoresRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Autopilot_SetScores_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SetScoresRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AutopilotServer).SetScores(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/autopilotrpc.Autopilot/SetScores",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AutopilotServer).SetScores(ctx, req.(*SetScoresRequest))
}
return interceptor(ctx, in, info, handler)
}
// Autopilot_ServiceDesc is the grpc.ServiceDesc for Autopilot service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Autopilot_ServiceDesc = grpc.ServiceDesc{
ServiceName: "autopilotrpc.Autopilot",
HandlerType: (*AutopilotServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Status",
Handler: _Autopilot_Status_Handler,
},
{
MethodName: "ModifyStatus",
Handler: _Autopilot_ModifyStatus_Handler,
},
{
MethodName: "QueryScores",
Handler: _Autopilot_QueryScores_Handler,
},
{
MethodName: "SetScores",
Handler: _Autopilot_SetScores_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "autopilotrpc/autopilot.proto",
}
package autopilotrpc
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This means the
// package will not perform any logging by default until the caller requests
// it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("ARPC", nil))
}
// DisableLog disables all library log output. Logging output is disabled by
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info. This
// should be used in preference to SetLogWriter if the caller is also using
// btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v3.21.12
// source: chainrpc/chainkit.proto
package chainrpc
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type GetBlockRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The hash of the requested block.
BlockHash []byte `protobuf:"bytes,1,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"`
}
func (x *GetBlockRequest) Reset() {
*x = GetBlockRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_chainrpc_chainkit_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetBlockRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetBlockRequest) ProtoMessage() {}
func (x *GetBlockRequest) ProtoReflect() protoreflect.Message {
mi := &file_chainrpc_chainkit_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetBlockRequest.ProtoReflect.Descriptor instead.
func (*GetBlockRequest) Descriptor() ([]byte, []int) {
return file_chainrpc_chainkit_proto_rawDescGZIP(), []int{0}
}
func (x *GetBlockRequest) GetBlockHash() []byte {
if x != nil {
return x.BlockHash
}
return nil
}
// TODO(ffranr): The neutrino GetBlock response includes many
// additional helpful fields. Consider adding them here also.
type GetBlockResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The raw bytes of the requested block.
RawBlock []byte `protobuf:"bytes,1,opt,name=raw_block,json=rawBlock,proto3" json:"raw_block,omitempty"`
}
func (x *GetBlockResponse) Reset() {
*x = GetBlockResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_chainrpc_chainkit_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetBlockResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetBlockResponse) ProtoMessage() {}
func (x *GetBlockResponse) ProtoReflect() protoreflect.Message {
mi := &file_chainrpc_chainkit_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetBlockResponse.ProtoReflect.Descriptor instead.
func (*GetBlockResponse) Descriptor() ([]byte, []int) {
return file_chainrpc_chainkit_proto_rawDescGZIP(), []int{1}
}
func (x *GetBlockResponse) GetRawBlock() []byte {
if x != nil {
return x.RawBlock
}
return nil
}
type GetBlockHeaderRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The hash of the block with the requested header.
BlockHash []byte `protobuf:"bytes,1,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"`
}
func (x *GetBlockHeaderRequest) Reset() {
*x = GetBlockHeaderRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_chainrpc_chainkit_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetBlockHeaderRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetBlockHeaderRequest) ProtoMessage() {}
func (x *GetBlockHeaderRequest) ProtoReflect() protoreflect.Message {
mi := &file_chainrpc_chainkit_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetBlockHeaderRequest.ProtoReflect.Descriptor instead.
func (*GetBlockHeaderRequest) Descriptor() ([]byte, []int) {
return file_chainrpc_chainkit_proto_rawDescGZIP(), []int{2}
}
func (x *GetBlockHeaderRequest) GetBlockHash() []byte {
if x != nil {
return x.BlockHash
}
return nil
}
type GetBlockHeaderResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The header of the block with the requested hash.
RawBlockHeader []byte `protobuf:"bytes,1,opt,name=raw_block_header,json=rawBlockHeader,proto3" json:"raw_block_header,omitempty"`
}
func (x *GetBlockHeaderResponse) Reset() {
*x = GetBlockHeaderResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_chainrpc_chainkit_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetBlockHeaderResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetBlockHeaderResponse) ProtoMessage() {}
func (x *GetBlockHeaderResponse) ProtoReflect() protoreflect.Message {
mi := &file_chainrpc_chainkit_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetBlockHeaderResponse.ProtoReflect.Descriptor instead.
func (*GetBlockHeaderResponse) Descriptor() ([]byte, []int) {
return file_chainrpc_chainkit_proto_rawDescGZIP(), []int{3}
}
func (x *GetBlockHeaderResponse) GetRawBlockHeader() []byte {
if x != nil {
return x.RawBlockHeader
}
return nil
}
type GetBestBlockRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *GetBestBlockRequest) Reset() {
*x = GetBestBlockRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_chainrpc_chainkit_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetBestBlockRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetBestBlockRequest) ProtoMessage() {}
func (x *GetBestBlockRequest) ProtoReflect() protoreflect.Message {
mi := &file_chainrpc_chainkit_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetBestBlockRequest.ProtoReflect.Descriptor instead.
func (*GetBestBlockRequest) Descriptor() ([]byte, []int) {
return file_chainrpc_chainkit_proto_rawDescGZIP(), []int{4}
}
type GetBestBlockResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The hash of the best block.
BlockHash []byte `protobuf:"bytes,1,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"`
// The height of the best block.
BlockHeight int32 `protobuf:"varint,2,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"`
}
func (x *GetBestBlockResponse) Reset() {
*x = GetBestBlockResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_chainrpc_chainkit_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetBestBlockResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetBestBlockResponse) ProtoMessage() {}
func (x *GetBestBlockResponse) ProtoReflect() protoreflect.Message {
mi := &file_chainrpc_chainkit_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetBestBlockResponse.ProtoReflect.Descriptor instead.
func (*GetBestBlockResponse) Descriptor() ([]byte, []int) {
return file_chainrpc_chainkit_proto_rawDescGZIP(), []int{5}
}
func (x *GetBestBlockResponse) GetBlockHash() []byte {
if x != nil {
return x.BlockHash
}
return nil
}
func (x *GetBestBlockResponse) GetBlockHeight() int32 {
if x != nil {
return x.BlockHeight
}
return 0
}
type GetBlockHashRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Block height of the target best chain block.
BlockHeight int64 `protobuf:"varint,1,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"`
}
func (x *GetBlockHashRequest) Reset() {
*x = GetBlockHashRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_chainrpc_chainkit_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetBlockHashRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetBlockHashRequest) ProtoMessage() {}
func (x *GetBlockHashRequest) ProtoReflect() protoreflect.Message {
mi := &file_chainrpc_chainkit_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetBlockHashRequest.ProtoReflect.Descriptor instead.
func (*GetBlockHashRequest) Descriptor() ([]byte, []int) {
return file_chainrpc_chainkit_proto_rawDescGZIP(), []int{6}
}
func (x *GetBlockHashRequest) GetBlockHeight() int64 {
if x != nil {
return x.BlockHeight
}
return 0
}
type GetBlockHashResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The hash of the best block at the specified height.
BlockHash []byte `protobuf:"bytes,1,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"`
}
func (x *GetBlockHashResponse) Reset() {
*x = GetBlockHashResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_chainrpc_chainkit_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetBlockHashResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetBlockHashResponse) ProtoMessage() {}
func (x *GetBlockHashResponse) ProtoReflect() protoreflect.Message {
mi := &file_chainrpc_chainkit_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetBlockHashResponse.ProtoReflect.Descriptor instead.
func (*GetBlockHashResponse) Descriptor() ([]byte, []int) {
return file_chainrpc_chainkit_proto_rawDescGZIP(), []int{7}
}
func (x *GetBlockHashResponse) GetBlockHash() []byte {
if x != nil {
return x.BlockHash
}
return nil
}
var File_chainrpc_chainkit_proto protoreflect.FileDescriptor
var file_chainrpc_chainkit_proto_rawDesc = []byte{
0x0a, 0x17, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e,
0x6b, 0x69, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e,
0x72, 0x70, 0x63, 0x22, 0x30, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f,
0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63,
0x6b, 0x48, 0x61, 0x73, 0x68, 0x22, 0x2f, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63,
0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x61, 0x77,
0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x72, 0x61,
0x77, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x36, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f,
0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x22, 0x42,
0x0a, 0x16, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x61, 0x77, 0x5f,
0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x0e, 0x72, 0x61, 0x77, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64,
0x65, 0x72, 0x22, 0x15, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x42, 0x65, 0x73, 0x74, 0x42, 0x6c, 0x6f,
0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x58, 0x0a, 0x14, 0x47, 0x65, 0x74,
0x42, 0x65, 0x73, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68,
0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74,
0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69,
0x67, 0x68, 0x74, 0x22, 0x38, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48,
0x61, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c,
0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03,
0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x35, 0x0a,
0x14, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68,
0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b,
0x48, 0x61, 0x73, 0x68, 0x32, 0xc0, 0x02, 0x0a, 0x08, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4b, 0x69,
0x74, 0x12, 0x41, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x19, 0x2e,
0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63,
0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b,
0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x1f, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65,
0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0c, 0x47, 0x65, 0x74,
0x42, 0x65, 0x73, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x1d, 0x2e, 0x63, 0x68, 0x61, 0x69,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x65, 0x73, 0x74, 0x42, 0x6c, 0x6f, 0x63,
0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x65, 0x73, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x42,
0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1d, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e,
0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x33,
}
var (
file_chainrpc_chainkit_proto_rawDescOnce sync.Once
file_chainrpc_chainkit_proto_rawDescData = file_chainrpc_chainkit_proto_rawDesc
)
func file_chainrpc_chainkit_proto_rawDescGZIP() []byte {
file_chainrpc_chainkit_proto_rawDescOnce.Do(func() {
file_chainrpc_chainkit_proto_rawDescData = protoimpl.X.CompressGZIP(file_chainrpc_chainkit_proto_rawDescData)
})
return file_chainrpc_chainkit_proto_rawDescData
}
var file_chainrpc_chainkit_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
var file_chainrpc_chainkit_proto_goTypes = []interface{}{
(*GetBlockRequest)(nil), // 0: chainrpc.GetBlockRequest
(*GetBlockResponse)(nil), // 1: chainrpc.GetBlockResponse
(*GetBlockHeaderRequest)(nil), // 2: chainrpc.GetBlockHeaderRequest
(*GetBlockHeaderResponse)(nil), // 3: chainrpc.GetBlockHeaderResponse
(*GetBestBlockRequest)(nil), // 4: chainrpc.GetBestBlockRequest
(*GetBestBlockResponse)(nil), // 5: chainrpc.GetBestBlockResponse
(*GetBlockHashRequest)(nil), // 6: chainrpc.GetBlockHashRequest
(*GetBlockHashResponse)(nil), // 7: chainrpc.GetBlockHashResponse
}
var file_chainrpc_chainkit_proto_depIdxs = []int32{
0, // 0: chainrpc.ChainKit.GetBlock:input_type -> chainrpc.GetBlockRequest
2, // 1: chainrpc.ChainKit.GetBlockHeader:input_type -> chainrpc.GetBlockHeaderRequest
4, // 2: chainrpc.ChainKit.GetBestBlock:input_type -> chainrpc.GetBestBlockRequest
6, // 3: chainrpc.ChainKit.GetBlockHash:input_type -> chainrpc.GetBlockHashRequest
1, // 4: chainrpc.ChainKit.GetBlock:output_type -> chainrpc.GetBlockResponse
3, // 5: chainrpc.ChainKit.GetBlockHeader:output_type -> chainrpc.GetBlockHeaderResponse
5, // 6: chainrpc.ChainKit.GetBestBlock:output_type -> chainrpc.GetBestBlockResponse
7, // 7: chainrpc.ChainKit.GetBlockHash:output_type -> chainrpc.GetBlockHashResponse
4, // [4:8] is the sub-list for method output_type
0, // [0:4] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_chainrpc_chainkit_proto_init() }
func file_chainrpc_chainkit_proto_init() {
if File_chainrpc_chainkit_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_chainrpc_chainkit_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetBlockRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_chainrpc_chainkit_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetBlockResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_chainrpc_chainkit_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetBlockHeaderRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_chainrpc_chainkit_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetBlockHeaderResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_chainrpc_chainkit_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetBestBlockRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_chainrpc_chainkit_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetBestBlockResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_chainrpc_chainkit_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetBlockHashRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_chainrpc_chainkit_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetBlockHashResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_chainrpc_chainkit_proto_rawDesc,
NumEnums: 0,
NumMessages: 8,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_chainrpc_chainkit_proto_goTypes,
DependencyIndexes: file_chainrpc_chainkit_proto_depIdxs,
MessageInfos: file_chainrpc_chainkit_proto_msgTypes,
}.Build()
File_chainrpc_chainkit_proto = out.File
file_chainrpc_chainkit_proto_rawDesc = nil
file_chainrpc_chainkit_proto_goTypes = nil
file_chainrpc_chainkit_proto_depIdxs = nil
}
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: chainrpc/chainkit.proto
/*
Package chainrpc is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package chainrpc
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
var (
filter_ChainKit_GetBlock_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_ChainKit_GetBlock_0(ctx context.Context, marshaler runtime.Marshaler, client ChainKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetBlockRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ChainKit_GetBlock_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.GetBlock(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ChainKit_GetBlock_0(ctx context.Context, marshaler runtime.Marshaler, server ChainKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetBlockRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ChainKit_GetBlock_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.GetBlock(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_ChainKit_GetBlockHeader_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_ChainKit_GetBlockHeader_0(ctx context.Context, marshaler runtime.Marshaler, client ChainKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetBlockHeaderRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ChainKit_GetBlockHeader_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.GetBlockHeader(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ChainKit_GetBlockHeader_0(ctx context.Context, marshaler runtime.Marshaler, server ChainKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetBlockHeaderRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ChainKit_GetBlockHeader_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.GetBlockHeader(ctx, &protoReq)
return msg, metadata, err
}
func request_ChainKit_GetBestBlock_0(ctx context.Context, marshaler runtime.Marshaler, client ChainKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetBestBlockRequest
var metadata runtime.ServerMetadata
msg, err := client.GetBestBlock(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ChainKit_GetBestBlock_0(ctx context.Context, marshaler runtime.Marshaler, server ChainKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetBestBlockRequest
var metadata runtime.ServerMetadata
msg, err := server.GetBestBlock(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_ChainKit_GetBlockHash_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_ChainKit_GetBlockHash_0(ctx context.Context, marshaler runtime.Marshaler, client ChainKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetBlockHashRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ChainKit_GetBlockHash_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.GetBlockHash(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ChainKit_GetBlockHash_0(ctx context.Context, marshaler runtime.Marshaler, server ChainKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetBlockHashRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ChainKit_GetBlockHash_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.GetBlockHash(ctx, &protoReq)
return msg, metadata, err
}
// RegisterChainKitHandlerServer registers the http handlers for service ChainKit to "mux".
// UnaryRPC :call ChainKitServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterChainKitHandlerFromEndpoint instead.
func RegisterChainKitHandlerServer(ctx context.Context, mux *runtime.ServeMux, server ChainKitServer) error {
mux.Handle("GET", pattern_ChainKit_GetBlock_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/chainrpc.ChainKit/GetBlock", runtime.WithHTTPPathPattern("/v2/chainkit/block"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ChainKit_GetBlock_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ChainKit_GetBlock_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_ChainKit_GetBlockHeader_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/chainrpc.ChainKit/GetBlockHeader", runtime.WithHTTPPathPattern("/v2/chainkit/blockheader"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ChainKit_GetBlockHeader_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ChainKit_GetBlockHeader_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_ChainKit_GetBestBlock_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/chainrpc.ChainKit/GetBestBlock", runtime.WithHTTPPathPattern("/v2/chainkit/bestblock"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ChainKit_GetBestBlock_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ChainKit_GetBestBlock_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_ChainKit_GetBlockHash_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/chainrpc.ChainKit/GetBlockHash", runtime.WithHTTPPathPattern("/v2/chainkit/blockhash"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ChainKit_GetBlockHash_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ChainKit_GetBlockHash_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterChainKitHandlerFromEndpoint is same as RegisterChainKitHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterChainKitHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterChainKitHandler(ctx, mux, conn)
}
// RegisterChainKitHandler registers the http handlers for service ChainKit to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterChainKitHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterChainKitHandlerClient(ctx, mux, NewChainKitClient(conn))
}
// RegisterChainKitHandlerClient registers the http handlers for service ChainKit
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "ChainKitClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "ChainKitClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "ChainKitClient" to call the correct interceptors.
func RegisterChainKitHandlerClient(ctx context.Context, mux *runtime.ServeMux, client ChainKitClient) error {
mux.Handle("GET", pattern_ChainKit_GetBlock_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/chainrpc.ChainKit/GetBlock", runtime.WithHTTPPathPattern("/v2/chainkit/block"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ChainKit_GetBlock_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ChainKit_GetBlock_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_ChainKit_GetBlockHeader_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/chainrpc.ChainKit/GetBlockHeader", runtime.WithHTTPPathPattern("/v2/chainkit/blockheader"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ChainKit_GetBlockHeader_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ChainKit_GetBlockHeader_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_ChainKit_GetBestBlock_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/chainrpc.ChainKit/GetBestBlock", runtime.WithHTTPPathPattern("/v2/chainkit/bestblock"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ChainKit_GetBestBlock_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ChainKit_GetBestBlock_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_ChainKit_GetBlockHash_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/chainrpc.ChainKit/GetBlockHash", runtime.WithHTTPPathPattern("/v2/chainkit/blockhash"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ChainKit_GetBlockHash_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ChainKit_GetBlockHash_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_ChainKit_GetBlock_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "chainkit", "block"}, ""))
pattern_ChainKit_GetBlockHeader_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "chainkit", "blockheader"}, ""))
pattern_ChainKit_GetBestBlock_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "chainkit", "bestblock"}, ""))
pattern_ChainKit_GetBlockHash_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "chainkit", "blockhash"}, ""))
)
var (
forward_ChainKit_GetBlock_0 = runtime.ForwardResponseMessage
forward_ChainKit_GetBlockHeader_0 = runtime.ForwardResponseMessage
forward_ChainKit_GetBestBlock_0 = runtime.ForwardResponseMessage
forward_ChainKit_GetBlockHash_0 = runtime.ForwardResponseMessage
)
// Code generated by falafel 0.9.2. DO NOT EDIT.
// source: chainkit.proto
package chainrpc
import (
"context"
gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protojson"
)
func RegisterChainKitJSONCallbacks(registry map[string]func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error))) {
marshaler := &gateway.JSONPb{
MarshalOptions: protojson.MarshalOptions{
UseProtoNames: true,
EmitUnpopulated: true,
},
}
registry["chainrpc.ChainKit.GetBlock"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &GetBlockRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewChainKitClient(conn)
resp, err := client.GetBlock(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["chainrpc.ChainKit.GetBlockHeader"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &GetBlockHeaderRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewChainKitClient(conn)
resp, err := client.GetBlockHeader(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["chainrpc.ChainKit.GetBestBlock"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &GetBestBlockRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewChainKitClient(conn)
resp, err := client.GetBestBlock(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["chainrpc.ChainKit.GetBlockHash"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &GetBlockHashRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewChainKitClient(conn)
resp, err := client.GetBlockHash(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package chainrpc
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// ChainKitClient is the client API for ChainKit service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type ChainKitClient interface {
// lncli: `chain getblock`
// GetBlock returns a block given the corresponding block hash.
GetBlock(ctx context.Context, in *GetBlockRequest, opts ...grpc.CallOption) (*GetBlockResponse, error)
// lncli: `chain getblockheader`
// GetBlockHeader returns a block header with a particular block hash.
GetBlockHeader(ctx context.Context, in *GetBlockHeaderRequest, opts ...grpc.CallOption) (*GetBlockHeaderResponse, error)
// lncli: `chain getbestblock`
// GetBestBlock returns the block hash and current height from the valid
// most-work chain.
GetBestBlock(ctx context.Context, in *GetBestBlockRequest, opts ...grpc.CallOption) (*GetBestBlockResponse, error)
// lncli: `chain getblockhash`
// GetBlockHash returns the hash of the block in the best blockchain
// at the given height.
GetBlockHash(ctx context.Context, in *GetBlockHashRequest, opts ...grpc.CallOption) (*GetBlockHashResponse, error)
}
type chainKitClient struct {
cc grpc.ClientConnInterface
}
func NewChainKitClient(cc grpc.ClientConnInterface) ChainKitClient {
return &chainKitClient{cc}
}
func (c *chainKitClient) GetBlock(ctx context.Context, in *GetBlockRequest, opts ...grpc.CallOption) (*GetBlockResponse, error) {
out := new(GetBlockResponse)
err := c.cc.Invoke(ctx, "/chainrpc.ChainKit/GetBlock", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *chainKitClient) GetBlockHeader(ctx context.Context, in *GetBlockHeaderRequest, opts ...grpc.CallOption) (*GetBlockHeaderResponse, error) {
out := new(GetBlockHeaderResponse)
err := c.cc.Invoke(ctx, "/chainrpc.ChainKit/GetBlockHeader", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *chainKitClient) GetBestBlock(ctx context.Context, in *GetBestBlockRequest, opts ...grpc.CallOption) (*GetBestBlockResponse, error) {
out := new(GetBestBlockResponse)
err := c.cc.Invoke(ctx, "/chainrpc.ChainKit/GetBestBlock", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *chainKitClient) GetBlockHash(ctx context.Context, in *GetBlockHashRequest, opts ...grpc.CallOption) (*GetBlockHashResponse, error) {
out := new(GetBlockHashResponse)
err := c.cc.Invoke(ctx, "/chainrpc.ChainKit/GetBlockHash", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// ChainKitServer is the server API for ChainKit service.
// All implementations must embed UnimplementedChainKitServer
// for forward compatibility
type ChainKitServer interface {
// lncli: `chain getblock`
// GetBlock returns a block given the corresponding block hash.
GetBlock(context.Context, *GetBlockRequest) (*GetBlockResponse, error)
// lncli: `chain getblockheader`
// GetBlockHeader returns a block header with a particular block hash.
GetBlockHeader(context.Context, *GetBlockHeaderRequest) (*GetBlockHeaderResponse, error)
// lncli: `chain getbestblock`
// GetBestBlock returns the block hash and current height from the valid
// most-work chain.
GetBestBlock(context.Context, *GetBestBlockRequest) (*GetBestBlockResponse, error)
// lncli: `chain getblockhash`
// GetBlockHash returns the hash of the block in the best blockchain
// at the given height.
GetBlockHash(context.Context, *GetBlockHashRequest) (*GetBlockHashResponse, error)
mustEmbedUnimplementedChainKitServer()
}
// UnimplementedChainKitServer must be embedded to have forward compatible implementations.
type UnimplementedChainKitServer struct {
}
func (UnimplementedChainKitServer) GetBlock(context.Context, *GetBlockRequest) (*GetBlockResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetBlock not implemented")
}
func (UnimplementedChainKitServer) GetBlockHeader(context.Context, *GetBlockHeaderRequest) (*GetBlockHeaderResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetBlockHeader not implemented")
}
func (UnimplementedChainKitServer) GetBestBlock(context.Context, *GetBestBlockRequest) (*GetBestBlockResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetBestBlock not implemented")
}
func (UnimplementedChainKitServer) GetBlockHash(context.Context, *GetBlockHashRequest) (*GetBlockHashResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetBlockHash not implemented")
}
func (UnimplementedChainKitServer) mustEmbedUnimplementedChainKitServer() {}
// UnsafeChainKitServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to ChainKitServer will
// result in compilation errors.
type UnsafeChainKitServer interface {
mustEmbedUnimplementedChainKitServer()
}
func RegisterChainKitServer(s grpc.ServiceRegistrar, srv ChainKitServer) {
s.RegisterService(&ChainKit_ServiceDesc, srv)
}
func _ChainKit_GetBlock_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetBlockRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ChainKitServer).GetBlock(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/chainrpc.ChainKit/GetBlock",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ChainKitServer).GetBlock(ctx, req.(*GetBlockRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ChainKit_GetBlockHeader_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetBlockHeaderRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ChainKitServer).GetBlockHeader(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/chainrpc.ChainKit/GetBlockHeader",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ChainKitServer).GetBlockHeader(ctx, req.(*GetBlockHeaderRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ChainKit_GetBestBlock_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetBestBlockRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ChainKitServer).GetBestBlock(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/chainrpc.ChainKit/GetBestBlock",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ChainKitServer).GetBestBlock(ctx, req.(*GetBestBlockRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ChainKit_GetBlockHash_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetBlockHashRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ChainKitServer).GetBlockHash(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/chainrpc.ChainKit/GetBlockHash",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ChainKitServer).GetBlockHash(ctx, req.(*GetBlockHashRequest))
}
return interceptor(ctx, in, info, handler)
}
// ChainKit_ServiceDesc is the grpc.ServiceDesc for ChainKit service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var ChainKit_ServiceDesc = grpc.ServiceDesc{
ServiceName: "chainrpc.ChainKit",
HandlerType: (*ChainKitServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetBlock",
Handler: _ChainKit_GetBlock_Handler,
},
{
MethodName: "GetBlockHeader",
Handler: _ChainKit_GetBlockHeader_Handler,
},
{
MethodName: "GetBestBlock",
Handler: _ChainKit_GetBestBlock_Handler,
},
{
MethodName: "GetBlockHash",
Handler: _ChainKit_GetBlockHash_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "chainrpc/chainkit.proto",
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v3.21.12
// source: chainrpc/chainnotifier.proto
package chainrpc
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type ConfRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The transaction hash for which we should request a confirmation notification
// for. If set to a hash of all zeros, then the confirmation notification will
// be requested for the script instead.
Txid []byte `protobuf:"bytes,1,opt,name=txid,proto3" json:"txid,omitempty"`
// An output script within a transaction with the hash above which will be used
// by light clients to match block filters. If the transaction hash is set to a
// hash of all zeros, then a confirmation notification will be requested for
// this script instead.
Script []byte `protobuf:"bytes,2,opt,name=script,proto3" json:"script,omitempty"`
// The number of desired confirmations the transaction/output script should
// reach before dispatching a confirmation notification.
NumConfs uint32 `protobuf:"varint,3,opt,name=num_confs,json=numConfs,proto3" json:"num_confs,omitempty"`
// The earliest height in the chain for which the transaction/output script
// could have been included in a block. This should in most cases be set to the
// broadcast height of the transaction/output script.
HeightHint uint32 `protobuf:"varint,4,opt,name=height_hint,json=heightHint,proto3" json:"height_hint,omitempty"`
// If true, then the block that mines the specified txid/script will be
// included in eventual the notification event.
IncludeBlock bool `protobuf:"varint,5,opt,name=include_block,json=includeBlock,proto3" json:"include_block,omitempty"`
}
func (x *ConfRequest) Reset() {
*x = ConfRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_chainrpc_chainnotifier_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ConfRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ConfRequest) ProtoMessage() {}
func (x *ConfRequest) ProtoReflect() protoreflect.Message {
mi := &file_chainrpc_chainnotifier_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ConfRequest.ProtoReflect.Descriptor instead.
func (*ConfRequest) Descriptor() ([]byte, []int) {
return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{0}
}
func (x *ConfRequest) GetTxid() []byte {
if x != nil {
return x.Txid
}
return nil
}
func (x *ConfRequest) GetScript() []byte {
if x != nil {
return x.Script
}
return nil
}
func (x *ConfRequest) GetNumConfs() uint32 {
if x != nil {
return x.NumConfs
}
return 0
}
func (x *ConfRequest) GetHeightHint() uint32 {
if x != nil {
return x.HeightHint
}
return 0
}
func (x *ConfRequest) GetIncludeBlock() bool {
if x != nil {
return x.IncludeBlock
}
return false
}
type ConfDetails struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The raw bytes of the confirmed transaction.
RawTx []byte `protobuf:"bytes,1,opt,name=raw_tx,json=rawTx,proto3" json:"raw_tx,omitempty"`
// The hash of the block in which the confirmed transaction was included in.
BlockHash []byte `protobuf:"bytes,2,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"`
// The height of the block in which the confirmed transaction was included
// in.
BlockHeight uint32 `protobuf:"varint,3,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"`
// The index of the confirmed transaction within the block.
TxIndex uint32 `protobuf:"varint,4,opt,name=tx_index,json=txIndex,proto3" json:"tx_index,omitempty"`
// The raw bytes of the block that mined the transaction. Only included if
// include_block was set in the request.
RawBlock []byte `protobuf:"bytes,5,opt,name=raw_block,json=rawBlock,proto3" json:"raw_block,omitempty"`
}
func (x *ConfDetails) Reset() {
*x = ConfDetails{}
if protoimpl.UnsafeEnabled {
mi := &file_chainrpc_chainnotifier_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ConfDetails) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ConfDetails) ProtoMessage() {}
func (x *ConfDetails) ProtoReflect() protoreflect.Message {
mi := &file_chainrpc_chainnotifier_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ConfDetails.ProtoReflect.Descriptor instead.
func (*ConfDetails) Descriptor() ([]byte, []int) {
return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{1}
}
func (x *ConfDetails) GetRawTx() []byte {
if x != nil {
return x.RawTx
}
return nil
}
func (x *ConfDetails) GetBlockHash() []byte {
if x != nil {
return x.BlockHash
}
return nil
}
func (x *ConfDetails) GetBlockHeight() uint32 {
if x != nil {
return x.BlockHeight
}
return 0
}
func (x *ConfDetails) GetTxIndex() uint32 {
if x != nil {
return x.TxIndex
}
return 0
}
func (x *ConfDetails) GetRawBlock() []byte {
if x != nil {
return x.RawBlock
}
return nil
}
type Reorg struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *Reorg) Reset() {
*x = Reorg{}
if protoimpl.UnsafeEnabled {
mi := &file_chainrpc_chainnotifier_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Reorg) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Reorg) ProtoMessage() {}
func (x *Reorg) ProtoReflect() protoreflect.Message {
mi := &file_chainrpc_chainnotifier_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Reorg.ProtoReflect.Descriptor instead.
func (*Reorg) Descriptor() ([]byte, []int) {
return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{2}
}
type ConfEvent struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Event:
//
// *ConfEvent_Conf
// *ConfEvent_Reorg
Event isConfEvent_Event `protobuf_oneof:"event"`
}
func (x *ConfEvent) Reset() {
*x = ConfEvent{}
if protoimpl.UnsafeEnabled {
mi := &file_chainrpc_chainnotifier_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ConfEvent) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ConfEvent) ProtoMessage() {}
func (x *ConfEvent) ProtoReflect() protoreflect.Message {
mi := &file_chainrpc_chainnotifier_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ConfEvent.ProtoReflect.Descriptor instead.
func (*ConfEvent) Descriptor() ([]byte, []int) {
return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{3}
}
func (m *ConfEvent) GetEvent() isConfEvent_Event {
if m != nil {
return m.Event
}
return nil
}
func (x *ConfEvent) GetConf() *ConfDetails {
if x, ok := x.GetEvent().(*ConfEvent_Conf); ok {
return x.Conf
}
return nil
}
func (x *ConfEvent) GetReorg() *Reorg {
if x, ok := x.GetEvent().(*ConfEvent_Reorg); ok {
return x.Reorg
}
return nil
}
type isConfEvent_Event interface {
isConfEvent_Event()
}
type ConfEvent_Conf struct {
// An event that includes the confirmation details of the request
// (txid/ouput script).
Conf *ConfDetails `protobuf:"bytes,1,opt,name=conf,proto3,oneof"`
}
type ConfEvent_Reorg struct {
// An event send when the transaction of the request is reorged out of the
// chain.
Reorg *Reorg `protobuf:"bytes,2,opt,name=reorg,proto3,oneof"`
}
func (*ConfEvent_Conf) isConfEvent_Event() {}
func (*ConfEvent_Reorg) isConfEvent_Event() {}
type Outpoint struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The hash of the transaction.
Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"`
// The index of the output within the transaction.
Index uint32 `protobuf:"varint,2,opt,name=index,proto3" json:"index,omitempty"`
}
func (x *Outpoint) Reset() {
*x = Outpoint{}
if protoimpl.UnsafeEnabled {
mi := &file_chainrpc_chainnotifier_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Outpoint) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Outpoint) ProtoMessage() {}
func (x *Outpoint) ProtoReflect() protoreflect.Message {
mi := &file_chainrpc_chainnotifier_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Outpoint.ProtoReflect.Descriptor instead.
func (*Outpoint) Descriptor() ([]byte, []int) {
return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{4}
}
func (x *Outpoint) GetHash() []byte {
if x != nil {
return x.Hash
}
return nil
}
func (x *Outpoint) GetIndex() uint32 {
if x != nil {
return x.Index
}
return 0
}
type SpendRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The outpoint for which we should request a spend notification for. If set to
// a zero outpoint, then the spend notification will be requested for the
// script instead. A zero or nil outpoint is not supported for Taproot spends
// because the output script cannot reliably be computed from the witness alone
// and the spent output script is not always available in the rescan context.
// So an outpoint must _always_ be specified when registering a spend
// notification for a Taproot output.
Outpoint *Outpoint `protobuf:"bytes,1,opt,name=outpoint,proto3" json:"outpoint,omitempty"`
// The output script for the outpoint above. This will be used by light clients
// to match block filters. If the outpoint is set to a zero outpoint, then a
// spend notification will be requested for this script instead.
Script []byte `protobuf:"bytes,2,opt,name=script,proto3" json:"script,omitempty"`
// The earliest height in the chain for which the outpoint/output script could
// have been spent. This should in most cases be set to the broadcast height of
// the outpoint/output script.
HeightHint uint32 `protobuf:"varint,3,opt,name=height_hint,json=heightHint,proto3" json:"height_hint,omitempty"`
}
func (x *SpendRequest) Reset() {
*x = SpendRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_chainrpc_chainnotifier_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SpendRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SpendRequest) ProtoMessage() {}
func (x *SpendRequest) ProtoReflect() protoreflect.Message {
mi := &file_chainrpc_chainnotifier_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SpendRequest.ProtoReflect.Descriptor instead.
func (*SpendRequest) Descriptor() ([]byte, []int) {
return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{5}
}
func (x *SpendRequest) GetOutpoint() *Outpoint {
if x != nil {
return x.Outpoint
}
return nil
}
func (x *SpendRequest) GetScript() []byte {
if x != nil {
return x.Script
}
return nil
}
func (x *SpendRequest) GetHeightHint() uint32 {
if x != nil {
return x.HeightHint
}
return 0
}
type SpendDetails struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The outpoint was that spent.
SpendingOutpoint *Outpoint `protobuf:"bytes,1,opt,name=spending_outpoint,json=spendingOutpoint,proto3" json:"spending_outpoint,omitempty"`
// The raw bytes of the spending transaction.
RawSpendingTx []byte `protobuf:"bytes,2,opt,name=raw_spending_tx,json=rawSpendingTx,proto3" json:"raw_spending_tx,omitempty"`
// The hash of the spending transaction.
SpendingTxHash []byte `protobuf:"bytes,3,opt,name=spending_tx_hash,json=spendingTxHash,proto3" json:"spending_tx_hash,omitempty"`
// The input of the spending transaction that fulfilled the spend request.
SpendingInputIndex uint32 `protobuf:"varint,4,opt,name=spending_input_index,json=spendingInputIndex,proto3" json:"spending_input_index,omitempty"`
// The height at which the spending transaction was included in a block.
SpendingHeight uint32 `protobuf:"varint,5,opt,name=spending_height,json=spendingHeight,proto3" json:"spending_height,omitempty"`
}
func (x *SpendDetails) Reset() {
*x = SpendDetails{}
if protoimpl.UnsafeEnabled {
mi := &file_chainrpc_chainnotifier_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SpendDetails) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SpendDetails) ProtoMessage() {}
func (x *SpendDetails) ProtoReflect() protoreflect.Message {
mi := &file_chainrpc_chainnotifier_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SpendDetails.ProtoReflect.Descriptor instead.
func (*SpendDetails) Descriptor() ([]byte, []int) {
return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{6}
}
func (x *SpendDetails) GetSpendingOutpoint() *Outpoint {
if x != nil {
return x.SpendingOutpoint
}
return nil
}
func (x *SpendDetails) GetRawSpendingTx() []byte {
if x != nil {
return x.RawSpendingTx
}
return nil
}
func (x *SpendDetails) GetSpendingTxHash() []byte {
if x != nil {
return x.SpendingTxHash
}
return nil
}
func (x *SpendDetails) GetSpendingInputIndex() uint32 {
if x != nil {
return x.SpendingInputIndex
}
return 0
}
func (x *SpendDetails) GetSpendingHeight() uint32 {
if x != nil {
return x.SpendingHeight
}
return 0
}
type SpendEvent struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Event:
//
// *SpendEvent_Spend
// *SpendEvent_Reorg
Event isSpendEvent_Event `protobuf_oneof:"event"`
}
func (x *SpendEvent) Reset() {
*x = SpendEvent{}
if protoimpl.UnsafeEnabled {
mi := &file_chainrpc_chainnotifier_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SpendEvent) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SpendEvent) ProtoMessage() {}
func (x *SpendEvent) ProtoReflect() protoreflect.Message {
mi := &file_chainrpc_chainnotifier_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SpendEvent.ProtoReflect.Descriptor instead.
func (*SpendEvent) Descriptor() ([]byte, []int) {
return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{7}
}
func (m *SpendEvent) GetEvent() isSpendEvent_Event {
if m != nil {
return m.Event
}
return nil
}
func (x *SpendEvent) GetSpend() *SpendDetails {
if x, ok := x.GetEvent().(*SpendEvent_Spend); ok {
return x.Spend
}
return nil
}
func (x *SpendEvent) GetReorg() *Reorg {
if x, ok := x.GetEvent().(*SpendEvent_Reorg); ok {
return x.Reorg
}
return nil
}
type isSpendEvent_Event interface {
isSpendEvent_Event()
}
type SpendEvent_Spend struct {
// An event that includes the details of the spending transaction of the
// request (outpoint/output script).
Spend *SpendDetails `protobuf:"bytes,1,opt,name=spend,proto3,oneof"`
}
type SpendEvent_Reorg struct {
// An event sent when the spending transaction of the request was
// reorged out of the chain.
Reorg *Reorg `protobuf:"bytes,2,opt,name=reorg,proto3,oneof"`
}
func (*SpendEvent_Spend) isSpendEvent_Event() {}
func (*SpendEvent_Reorg) isSpendEvent_Event() {}
type BlockEpoch struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The hash of the block.
Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"`
// The height of the block.
Height uint32 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"`
}
func (x *BlockEpoch) Reset() {
*x = BlockEpoch{}
if protoimpl.UnsafeEnabled {
mi := &file_chainrpc_chainnotifier_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *BlockEpoch) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BlockEpoch) ProtoMessage() {}
func (x *BlockEpoch) ProtoReflect() protoreflect.Message {
mi := &file_chainrpc_chainnotifier_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BlockEpoch.ProtoReflect.Descriptor instead.
func (*BlockEpoch) Descriptor() ([]byte, []int) {
return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{8}
}
func (x *BlockEpoch) GetHash() []byte {
if x != nil {
return x.Hash
}
return nil
}
func (x *BlockEpoch) GetHeight() uint32 {
if x != nil {
return x.Height
}
return 0
}
var File_chainrpc_chainnotifier_proto protoreflect.FileDescriptor
var file_chainrpc_chainnotifier_proto_rawDesc = []byte{
0x0a, 0x1c, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e,
0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08,
0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x22, 0x9c, 0x01, 0x0a, 0x0b, 0x43, 0x6f, 0x6e,
0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06,
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x63,
0x72, 0x69, 0x70, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x6f, 0x6e, 0x66,
0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6e, 0x75, 0x6d, 0x43, 0x6f, 0x6e, 0x66,
0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x68, 0x69, 0x6e, 0x74,
0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x48, 0x69,
0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x62, 0x6c,
0x6f, 0x63, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x6e, 0x63, 0x6c, 0x75,
0x64, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x9e, 0x01, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x66,
0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x61, 0x77, 0x5f, 0x74,
0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x61, 0x77, 0x54, 0x78, 0x12, 0x1d,
0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x21, 0x0a,
0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20,
0x01, 0x28, 0x0d, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74,
0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01,
0x28, 0x0d, 0x52, 0x07, 0x74, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1b, 0x0a, 0x09, 0x72,
0x61, 0x77, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08,
0x72, 0x61, 0x77, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x07, 0x0a, 0x05, 0x52, 0x65, 0x6f, 0x72,
0x67, 0x22, 0x6a, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x66, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2b,
0x0a, 0x04, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63,
0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x44, 0x65, 0x74, 0x61,
0x69, 0x6c, 0x73, 0x48, 0x00, 0x52, 0x04, 0x63, 0x6f, 0x6e, 0x66, 0x12, 0x27, 0x0a, 0x05, 0x72,
0x65, 0x6f, 0x72, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x68, 0x61,
0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6f, 0x72, 0x67, 0x48, 0x00, 0x52, 0x05, 0x72,
0x65, 0x6f, 0x72, 0x67, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x34, 0x0a,
0x08, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73,
0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a,
0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x69, 0x6e,
0x64, 0x65, 0x78, 0x22, 0x77, 0x0a, 0x0c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f,
0x69, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x68,
0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d,
0x52, 0x0a, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x48, 0x69, 0x6e, 0x74, 0x22, 0xfc, 0x01, 0x0a,
0x0c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x3f, 0x0a,
0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69,
0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x10, 0x73, 0x70,
0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x26,
0x0a, 0x0f, 0x72, 0x61, 0x77, 0x5f, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74,
0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x72, 0x61, 0x77, 0x53, 0x70, 0x65, 0x6e,
0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69,
0x6e, 0x67, 0x5f, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x0e, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x48, 0x61, 0x73, 0x68,
0x12, 0x30, 0x0a, 0x14, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x6e, 0x70,
0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12,
0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64,
0x65, 0x78, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68,
0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x73, 0x70, 0x65,
0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x6e, 0x0a, 0x0a, 0x53,
0x70, 0x65, 0x6e, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x05, 0x73, 0x70, 0x65,
0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73,
0x48, 0x00, 0x52, 0x05, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x12, 0x27, 0x0a, 0x05, 0x72, 0x65, 0x6f,
0x72, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6f, 0x72, 0x67, 0x48, 0x00, 0x52, 0x05, 0x72, 0x65, 0x6f,
0x72, 0x67, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x38, 0x0a, 0x0a, 0x42,
0x6c, 0x6f, 0x63, 0x6b, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73,
0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a,
0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x68,
0x65, 0x69, 0x67, 0x68, 0x74, 0x32, 0xe7, 0x01, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4e,
0x6f, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x49, 0x0a, 0x19, 0x52, 0x65, 0x67, 0x69, 0x73,
0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73,
0x4e, 0x74, 0x66, 0x6e, 0x12, 0x15, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x43, 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x63, 0x68,
0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x45, 0x76, 0x65, 0x6e, 0x74,
0x30, 0x01, 0x12, 0x43, 0x0a, 0x11, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, 0x70,
0x65, 0x6e, 0x64, 0x4e, 0x74, 0x66, 0x6e, 0x12, 0x16, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x14, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x70, 0x65, 0x6e, 0x64,
0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x46, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69, 0x73,
0x74, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x4e, 0x74, 0x66,
0x6e, 0x12, 0x14, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x6c, 0x6f,
0x63, 0x6b, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x1a, 0x14, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x30, 0x01, 0x42,
0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69,
0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c,
0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70,
0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_chainrpc_chainnotifier_proto_rawDescOnce sync.Once
file_chainrpc_chainnotifier_proto_rawDescData = file_chainrpc_chainnotifier_proto_rawDesc
)
func file_chainrpc_chainnotifier_proto_rawDescGZIP() []byte {
file_chainrpc_chainnotifier_proto_rawDescOnce.Do(func() {
file_chainrpc_chainnotifier_proto_rawDescData = protoimpl.X.CompressGZIP(file_chainrpc_chainnotifier_proto_rawDescData)
})
return file_chainrpc_chainnotifier_proto_rawDescData
}
var file_chainrpc_chainnotifier_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
var file_chainrpc_chainnotifier_proto_goTypes = []interface{}{
(*ConfRequest)(nil), // 0: chainrpc.ConfRequest
(*ConfDetails)(nil), // 1: chainrpc.ConfDetails
(*Reorg)(nil), // 2: chainrpc.Reorg
(*ConfEvent)(nil), // 3: chainrpc.ConfEvent
(*Outpoint)(nil), // 4: chainrpc.Outpoint
(*SpendRequest)(nil), // 5: chainrpc.SpendRequest
(*SpendDetails)(nil), // 6: chainrpc.SpendDetails
(*SpendEvent)(nil), // 7: chainrpc.SpendEvent
(*BlockEpoch)(nil), // 8: chainrpc.BlockEpoch
}
var file_chainrpc_chainnotifier_proto_depIdxs = []int32{
1, // 0: chainrpc.ConfEvent.conf:type_name -> chainrpc.ConfDetails
2, // 1: chainrpc.ConfEvent.reorg:type_name -> chainrpc.Reorg
4, // 2: chainrpc.SpendRequest.outpoint:type_name -> chainrpc.Outpoint
4, // 3: chainrpc.SpendDetails.spending_outpoint:type_name -> chainrpc.Outpoint
6, // 4: chainrpc.SpendEvent.spend:type_name -> chainrpc.SpendDetails
2, // 5: chainrpc.SpendEvent.reorg:type_name -> chainrpc.Reorg
0, // 6: chainrpc.ChainNotifier.RegisterConfirmationsNtfn:input_type -> chainrpc.ConfRequest
5, // 7: chainrpc.ChainNotifier.RegisterSpendNtfn:input_type -> chainrpc.SpendRequest
8, // 8: chainrpc.ChainNotifier.RegisterBlockEpochNtfn:input_type -> chainrpc.BlockEpoch
3, // 9: chainrpc.ChainNotifier.RegisterConfirmationsNtfn:output_type -> chainrpc.ConfEvent
7, // 10: chainrpc.ChainNotifier.RegisterSpendNtfn:output_type -> chainrpc.SpendEvent
8, // 11: chainrpc.ChainNotifier.RegisterBlockEpochNtfn:output_type -> chainrpc.BlockEpoch
9, // [9:12] is the sub-list for method output_type
6, // [6:9] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
}
func init() { file_chainrpc_chainnotifier_proto_init() }
func file_chainrpc_chainnotifier_proto_init() {
if File_chainrpc_chainnotifier_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_chainrpc_chainnotifier_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ConfRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_chainrpc_chainnotifier_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ConfDetails); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_chainrpc_chainnotifier_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Reorg); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_chainrpc_chainnotifier_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ConfEvent); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_chainrpc_chainnotifier_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Outpoint); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_chainrpc_chainnotifier_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SpendRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_chainrpc_chainnotifier_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SpendDetails); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_chainrpc_chainnotifier_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SpendEvent); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_chainrpc_chainnotifier_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BlockEpoch); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_chainrpc_chainnotifier_proto_msgTypes[3].OneofWrappers = []interface{}{
(*ConfEvent_Conf)(nil),
(*ConfEvent_Reorg)(nil),
}
file_chainrpc_chainnotifier_proto_msgTypes[7].OneofWrappers = []interface{}{
(*SpendEvent_Spend)(nil),
(*SpendEvent_Reorg)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_chainrpc_chainnotifier_proto_rawDesc,
NumEnums: 0,
NumMessages: 9,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_chainrpc_chainnotifier_proto_goTypes,
DependencyIndexes: file_chainrpc_chainnotifier_proto_depIdxs,
MessageInfos: file_chainrpc_chainnotifier_proto_msgTypes,
}.Build()
File_chainrpc_chainnotifier_proto = out.File
file_chainrpc_chainnotifier_proto_rawDesc = nil
file_chainrpc_chainnotifier_proto_goTypes = nil
file_chainrpc_chainnotifier_proto_depIdxs = nil
}
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: chainrpc/chainnotifier.proto
/*
Package chainrpc is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package chainrpc
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
func request_ChainNotifier_RegisterConfirmationsNtfn_0(ctx context.Context, marshaler runtime.Marshaler, client ChainNotifierClient, req *http.Request, pathParams map[string]string) (ChainNotifier_RegisterConfirmationsNtfnClient, runtime.ServerMetadata, error) {
var protoReq ConfRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
stream, err := client.RegisterConfirmationsNtfn(ctx, &protoReq)
if err != nil {
return nil, metadata, err
}
header, err := stream.Header()
if err != nil {
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
func request_ChainNotifier_RegisterSpendNtfn_0(ctx context.Context, marshaler runtime.Marshaler, client ChainNotifierClient, req *http.Request, pathParams map[string]string) (ChainNotifier_RegisterSpendNtfnClient, runtime.ServerMetadata, error) {
var protoReq SpendRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
stream, err := client.RegisterSpendNtfn(ctx, &protoReq)
if err != nil {
return nil, metadata, err
}
header, err := stream.Header()
if err != nil {
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
func request_ChainNotifier_RegisterBlockEpochNtfn_0(ctx context.Context, marshaler runtime.Marshaler, client ChainNotifierClient, req *http.Request, pathParams map[string]string) (ChainNotifier_RegisterBlockEpochNtfnClient, runtime.ServerMetadata, error) {
var protoReq BlockEpoch
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
stream, err := client.RegisterBlockEpochNtfn(ctx, &protoReq)
if err != nil {
return nil, metadata, err
}
header, err := stream.Header()
if err != nil {
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
// RegisterChainNotifierHandlerServer registers the http handlers for service ChainNotifier to "mux".
// UnaryRPC :call ChainNotifierServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterChainNotifierHandlerFromEndpoint instead.
func RegisterChainNotifierHandlerServer(ctx context.Context, mux *runtime.ServeMux, server ChainNotifierServer) error {
mux.Handle("POST", pattern_ChainNotifier_RegisterConfirmationsNtfn_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
mux.Handle("POST", pattern_ChainNotifier_RegisterSpendNtfn_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
mux.Handle("POST", pattern_ChainNotifier_RegisterBlockEpochNtfn_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
return nil
}
// RegisterChainNotifierHandlerFromEndpoint is same as RegisterChainNotifierHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterChainNotifierHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterChainNotifierHandler(ctx, mux, conn)
}
// RegisterChainNotifierHandler registers the http handlers for service ChainNotifier to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterChainNotifierHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterChainNotifierHandlerClient(ctx, mux, NewChainNotifierClient(conn))
}
// RegisterChainNotifierHandlerClient registers the http handlers for service ChainNotifier
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "ChainNotifierClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "ChainNotifierClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "ChainNotifierClient" to call the correct interceptors.
func RegisterChainNotifierHandlerClient(ctx context.Context, mux *runtime.ServeMux, client ChainNotifierClient) error {
mux.Handle("POST", pattern_ChainNotifier_RegisterConfirmationsNtfn_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/chainrpc.ChainNotifier/RegisterConfirmationsNtfn", runtime.WithHTTPPathPattern("/v2/chainnotifier/register/confirmations"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ChainNotifier_RegisterConfirmationsNtfn_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ChainNotifier_RegisterConfirmationsNtfn_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_ChainNotifier_RegisterSpendNtfn_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/chainrpc.ChainNotifier/RegisterSpendNtfn", runtime.WithHTTPPathPattern("/v2/chainnotifier/register/spends"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ChainNotifier_RegisterSpendNtfn_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ChainNotifier_RegisterSpendNtfn_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_ChainNotifier_RegisterBlockEpochNtfn_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/chainrpc.ChainNotifier/RegisterBlockEpochNtfn", runtime.WithHTTPPathPattern("/v2/chainnotifier/register/blocks"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ChainNotifier_RegisterBlockEpochNtfn_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ChainNotifier_RegisterBlockEpochNtfn_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_ChainNotifier_RegisterConfirmationsNtfn_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "chainnotifier", "register", "confirmations"}, ""))
pattern_ChainNotifier_RegisterSpendNtfn_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "chainnotifier", "register", "spends"}, ""))
pattern_ChainNotifier_RegisterBlockEpochNtfn_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "chainnotifier", "register", "blocks"}, ""))
)
var (
forward_ChainNotifier_RegisterConfirmationsNtfn_0 = runtime.ForwardResponseStream
forward_ChainNotifier_RegisterSpendNtfn_0 = runtime.ForwardResponseStream
forward_ChainNotifier_RegisterBlockEpochNtfn_0 = runtime.ForwardResponseStream
)
// Code generated by falafel 0.9.2. DO NOT EDIT.
// source: chainnotifier.proto
package chainrpc
import (
"context"
gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protojson"
)
func RegisterChainNotifierJSONCallbacks(registry map[string]func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error))) {
marshaler := &gateway.JSONPb{
MarshalOptions: protojson.MarshalOptions{
UseProtoNames: true,
EmitUnpopulated: true,
},
}
registry["chainrpc.ChainNotifier.RegisterConfirmationsNtfn"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ConfRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewChainNotifierClient(conn)
stream, err := client.RegisterConfirmationsNtfn(ctx, req)
if err != nil {
callback("", err)
return
}
go func() {
for {
select {
case <-stream.Context().Done():
callback("", stream.Context().Err())
return
default:
}
resp, err := stream.Recv()
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}()
}
registry["chainrpc.ChainNotifier.RegisterSpendNtfn"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SpendRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewChainNotifierClient(conn)
stream, err := client.RegisterSpendNtfn(ctx, req)
if err != nil {
callback("", err)
return
}
go func() {
for {
select {
case <-stream.Context().Done():
callback("", stream.Context().Err())
return
default:
}
resp, err := stream.Recv()
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}()
}
registry["chainrpc.ChainNotifier.RegisterBlockEpochNtfn"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &BlockEpoch{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewChainNotifierClient(conn)
stream, err := client.RegisterBlockEpochNtfn(ctx, req)
if err != nil {
callback("", err)
return
}
go func() {
for {
select {
case <-stream.Context().Done():
callback("", stream.Context().Err())
return
default:
}
resp, err := stream.Recv()
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}()
}
}
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package chainrpc
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// ChainNotifierClient is the client API for ChainNotifier service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type ChainNotifierClient interface {
// RegisterConfirmationsNtfn is a synchronous response-streaming RPC that
// registers an intent for a client to be notified once a confirmation request
// has reached its required number of confirmations on-chain.
//
// A confirmation request must have a valid output script. It is also possible
// to give a transaction ID. If the transaction ID is not set, a notification
// is sent once the output script confirms. If the transaction ID is also set,
// a notification is sent once the output script confirms in the given
// transaction.
RegisterConfirmationsNtfn(ctx context.Context, in *ConfRequest, opts ...grpc.CallOption) (ChainNotifier_RegisterConfirmationsNtfnClient, error)
// RegisterSpendNtfn is a synchronous response-streaming RPC that registers an
// intent for a client to be notification once a spend request has been spent
// by a transaction that has confirmed on-chain.
//
// A client can specify whether the spend request should be for a particular
// outpoint or for an output script by specifying a zero outpoint.
RegisterSpendNtfn(ctx context.Context, in *SpendRequest, opts ...grpc.CallOption) (ChainNotifier_RegisterSpendNtfnClient, error)
// RegisterBlockEpochNtfn is a synchronous response-streaming RPC that
// registers an intent for a client to be notified of blocks in the chain. The
// stream will return a hash and height tuple of a block for each new/stale
// block in the chain. It is the client's responsibility to determine whether
// the tuple returned is for a new or stale block in the chain.
//
// A client can also request a historical backlog of blocks from a particular
// point. This allows clients to be idempotent by ensuring that they do not
// missing processing a single block within the chain.
RegisterBlockEpochNtfn(ctx context.Context, in *BlockEpoch, opts ...grpc.CallOption) (ChainNotifier_RegisterBlockEpochNtfnClient, error)
}
type chainNotifierClient struct {
cc grpc.ClientConnInterface
}
func NewChainNotifierClient(cc grpc.ClientConnInterface) ChainNotifierClient {
return &chainNotifierClient{cc}
}
func (c *chainNotifierClient) RegisterConfirmationsNtfn(ctx context.Context, in *ConfRequest, opts ...grpc.CallOption) (ChainNotifier_RegisterConfirmationsNtfnClient, error) {
stream, err := c.cc.NewStream(ctx, &ChainNotifier_ServiceDesc.Streams[0], "/chainrpc.ChainNotifier/RegisterConfirmationsNtfn", opts...)
if err != nil {
return nil, err
}
x := &chainNotifierRegisterConfirmationsNtfnClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type ChainNotifier_RegisterConfirmationsNtfnClient interface {
Recv() (*ConfEvent, error)
grpc.ClientStream
}
type chainNotifierRegisterConfirmationsNtfnClient struct {
grpc.ClientStream
}
func (x *chainNotifierRegisterConfirmationsNtfnClient) Recv() (*ConfEvent, error) {
m := new(ConfEvent)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *chainNotifierClient) RegisterSpendNtfn(ctx context.Context, in *SpendRequest, opts ...grpc.CallOption) (ChainNotifier_RegisterSpendNtfnClient, error) {
stream, err := c.cc.NewStream(ctx, &ChainNotifier_ServiceDesc.Streams[1], "/chainrpc.ChainNotifier/RegisterSpendNtfn", opts...)
if err != nil {
return nil, err
}
x := &chainNotifierRegisterSpendNtfnClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type ChainNotifier_RegisterSpendNtfnClient interface {
Recv() (*SpendEvent, error)
grpc.ClientStream
}
type chainNotifierRegisterSpendNtfnClient struct {
grpc.ClientStream
}
func (x *chainNotifierRegisterSpendNtfnClient) Recv() (*SpendEvent, error) {
m := new(SpendEvent)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *chainNotifierClient) RegisterBlockEpochNtfn(ctx context.Context, in *BlockEpoch, opts ...grpc.CallOption) (ChainNotifier_RegisterBlockEpochNtfnClient, error) {
stream, err := c.cc.NewStream(ctx, &ChainNotifier_ServiceDesc.Streams[2], "/chainrpc.ChainNotifier/RegisterBlockEpochNtfn", opts...)
if err != nil {
return nil, err
}
x := &chainNotifierRegisterBlockEpochNtfnClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type ChainNotifier_RegisterBlockEpochNtfnClient interface {
Recv() (*BlockEpoch, error)
grpc.ClientStream
}
type chainNotifierRegisterBlockEpochNtfnClient struct {
grpc.ClientStream
}
func (x *chainNotifierRegisterBlockEpochNtfnClient) Recv() (*BlockEpoch, error) {
m := new(BlockEpoch)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// ChainNotifierServer is the server API for ChainNotifier service.
// All implementations must embed UnimplementedChainNotifierServer
// for forward compatibility
type ChainNotifierServer interface {
// RegisterConfirmationsNtfn is a synchronous response-streaming RPC that
// registers an intent for a client to be notified once a confirmation request
// has reached its required number of confirmations on-chain.
//
// A confirmation request must have a valid output script. It is also possible
// to give a transaction ID. If the transaction ID is not set, a notification
// is sent once the output script confirms. If the transaction ID is also set,
// a notification is sent once the output script confirms in the given
// transaction.
RegisterConfirmationsNtfn(*ConfRequest, ChainNotifier_RegisterConfirmationsNtfnServer) error
// RegisterSpendNtfn is a synchronous response-streaming RPC that registers an
// intent for a client to be notification once a spend request has been spent
// by a transaction that has confirmed on-chain.
//
// A client can specify whether the spend request should be for a particular
// outpoint or for an output script by specifying a zero outpoint.
RegisterSpendNtfn(*SpendRequest, ChainNotifier_RegisterSpendNtfnServer) error
// RegisterBlockEpochNtfn is a synchronous response-streaming RPC that
// registers an intent for a client to be notified of blocks in the chain. The
// stream will return a hash and height tuple of a block for each new/stale
// block in the chain. It is the client's responsibility to determine whether
// the tuple returned is for a new or stale block in the chain.
//
// A client can also request a historical backlog of blocks from a particular
// point. This allows clients to be idempotent by ensuring that they do not
// missing processing a single block within the chain.
RegisterBlockEpochNtfn(*BlockEpoch, ChainNotifier_RegisterBlockEpochNtfnServer) error
mustEmbedUnimplementedChainNotifierServer()
}
// UnimplementedChainNotifierServer must be embedded to have forward compatible implementations.
type UnimplementedChainNotifierServer struct {
}
func (UnimplementedChainNotifierServer) RegisterConfirmationsNtfn(*ConfRequest, ChainNotifier_RegisterConfirmationsNtfnServer) error {
return status.Errorf(codes.Unimplemented, "method RegisterConfirmationsNtfn not implemented")
}
func (UnimplementedChainNotifierServer) RegisterSpendNtfn(*SpendRequest, ChainNotifier_RegisterSpendNtfnServer) error {
return status.Errorf(codes.Unimplemented, "method RegisterSpendNtfn not implemented")
}
func (UnimplementedChainNotifierServer) RegisterBlockEpochNtfn(*BlockEpoch, ChainNotifier_RegisterBlockEpochNtfnServer) error {
return status.Errorf(codes.Unimplemented, "method RegisterBlockEpochNtfn not implemented")
}
func (UnimplementedChainNotifierServer) mustEmbedUnimplementedChainNotifierServer() {}
// UnsafeChainNotifierServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to ChainNotifierServer will
// result in compilation errors.
type UnsafeChainNotifierServer interface {
mustEmbedUnimplementedChainNotifierServer()
}
func RegisterChainNotifierServer(s grpc.ServiceRegistrar, srv ChainNotifierServer) {
s.RegisterService(&ChainNotifier_ServiceDesc, srv)
}
func _ChainNotifier_RegisterConfirmationsNtfn_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(ConfRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(ChainNotifierServer).RegisterConfirmationsNtfn(m, &chainNotifierRegisterConfirmationsNtfnServer{stream})
}
type ChainNotifier_RegisterConfirmationsNtfnServer interface {
Send(*ConfEvent) error
grpc.ServerStream
}
type chainNotifierRegisterConfirmationsNtfnServer struct {
grpc.ServerStream
}
func (x *chainNotifierRegisterConfirmationsNtfnServer) Send(m *ConfEvent) error {
return x.ServerStream.SendMsg(m)
}
func _ChainNotifier_RegisterSpendNtfn_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(SpendRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(ChainNotifierServer).RegisterSpendNtfn(m, &chainNotifierRegisterSpendNtfnServer{stream})
}
type ChainNotifier_RegisterSpendNtfnServer interface {
Send(*SpendEvent) error
grpc.ServerStream
}
type chainNotifierRegisterSpendNtfnServer struct {
grpc.ServerStream
}
func (x *chainNotifierRegisterSpendNtfnServer) Send(m *SpendEvent) error {
return x.ServerStream.SendMsg(m)
}
func _ChainNotifier_RegisterBlockEpochNtfn_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(BlockEpoch)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(ChainNotifierServer).RegisterBlockEpochNtfn(m, &chainNotifierRegisterBlockEpochNtfnServer{stream})
}
type ChainNotifier_RegisterBlockEpochNtfnServer interface {
Send(*BlockEpoch) error
grpc.ServerStream
}
type chainNotifierRegisterBlockEpochNtfnServer struct {
grpc.ServerStream
}
func (x *chainNotifierRegisterBlockEpochNtfnServer) Send(m *BlockEpoch) error {
return x.ServerStream.SendMsg(m)
}
// ChainNotifier_ServiceDesc is the grpc.ServiceDesc for ChainNotifier service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var ChainNotifier_ServiceDesc = grpc.ServiceDesc{
ServiceName: "chainrpc.ChainNotifier",
HandlerType: (*ChainNotifierServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "RegisterConfirmationsNtfn",
Handler: _ChainNotifier_RegisterConfirmationsNtfn_Handler,
ServerStreams: true,
},
{
StreamName: "RegisterSpendNtfn",
Handler: _ChainNotifier_RegisterSpendNtfn_Handler,
ServerStreams: true,
},
{
StreamName: "RegisterBlockEpochNtfn",
Handler: _ChainNotifier_RegisterBlockEpochNtfn_Handler,
ServerStreams: true,
},
},
Metadata: "chainrpc/chainnotifier.proto",
}
package chainrpc
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("NTFR", nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v3.21.12
// source: devrpc/dev.proto
package devrpc
import (
lnrpc "github.com/lightningnetwork/lnd/lnrpc"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type ImportGraphResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ImportGraphResponse) Reset() {
*x = ImportGraphResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_devrpc_dev_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ImportGraphResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ImportGraphResponse) ProtoMessage() {}
func (x *ImportGraphResponse) ProtoReflect() protoreflect.Message {
mi := &file_devrpc_dev_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ImportGraphResponse.ProtoReflect.Descriptor instead.
func (*ImportGraphResponse) Descriptor() ([]byte, []int) {
return file_devrpc_dev_proto_rawDescGZIP(), []int{0}
}
type QuiescenceRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The channel point of the channel we wish to quiesce
ChanId *lnrpc.ChannelPoint `protobuf:"bytes,1,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"`
}
func (x *QuiescenceRequest) Reset() {
*x = QuiescenceRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_devrpc_dev_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *QuiescenceRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*QuiescenceRequest) ProtoMessage() {}
func (x *QuiescenceRequest) ProtoReflect() protoreflect.Message {
mi := &file_devrpc_dev_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use QuiescenceRequest.ProtoReflect.Descriptor instead.
func (*QuiescenceRequest) Descriptor() ([]byte, []int) {
return file_devrpc_dev_proto_rawDescGZIP(), []int{1}
}
func (x *QuiescenceRequest) GetChanId() *lnrpc.ChannelPoint {
if x != nil {
return x.ChanId
}
return nil
}
type QuiescenceResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Indicates whether or not we hold the initiator role or not once the
// negotiation completes
Initiator bool `protobuf:"varint,1,opt,name=initiator,proto3" json:"initiator,omitempty"`
}
func (x *QuiescenceResponse) Reset() {
*x = QuiescenceResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_devrpc_dev_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *QuiescenceResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*QuiescenceResponse) ProtoMessage() {}
func (x *QuiescenceResponse) ProtoReflect() protoreflect.Message {
mi := &file_devrpc_dev_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use QuiescenceResponse.ProtoReflect.Descriptor instead.
func (*QuiescenceResponse) Descriptor() ([]byte, []int) {
return file_devrpc_dev_proto_rawDescGZIP(), []int{2}
}
func (x *QuiescenceResponse) GetInitiator() bool {
if x != nil {
return x.Initiator
}
return false
}
var File_devrpc_dev_proto protoreflect.FileDescriptor
var file_devrpc_dev_proto_rawDesc = []byte{
0x0a, 0x10, 0x64, 0x65, 0x76, 0x72, 0x70, 0x63, 0x2f, 0x64, 0x65, 0x76, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x12, 0x06, 0x64, 0x65, 0x76, 0x72, 0x70, 0x63, 0x1a, 0x0f, 0x6c, 0x69, 0x67, 0x68,
0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x15, 0x0a, 0x13, 0x49,
0x6d, 0x70, 0x6f, 0x72, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x41, 0x0a, 0x11, 0x51, 0x75, 0x69, 0x65, 0x73, 0x63, 0x65, 0x6e, 0x63, 0x65,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x63,
0x68, 0x61, 0x6e, 0x49, 0x64, 0x22, 0x32, 0x0a, 0x12, 0x51, 0x75, 0x69, 0x65, 0x73, 0x63, 0x65,
0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x69,
0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09,
0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x32, 0x88, 0x01, 0x0a, 0x03, 0x44, 0x65,
0x76, 0x12, 0x3f, 0x0a, 0x0b, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68,
0x12, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
0x47, 0x72, 0x61, 0x70, 0x68, 0x1a, 0x1b, 0x2e, 0x64, 0x65, 0x76, 0x72, 0x70, 0x63, 0x2e, 0x49,
0x6d, 0x70, 0x6f, 0x72, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x40, 0x0a, 0x07, 0x51, 0x75, 0x69, 0x65, 0x73, 0x63, 0x65, 0x12, 0x19, 0x2e,
0x64, 0x65, 0x76, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x69, 0x65, 0x73, 0x63, 0x65, 0x6e, 0x63,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x64, 0x65, 0x76, 0x72, 0x70,
0x63, 0x2e, 0x51, 0x75, 0x69, 0x65, 0x73, 0x63, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77,
0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x64, 0x65,
0x76, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_devrpc_dev_proto_rawDescOnce sync.Once
file_devrpc_dev_proto_rawDescData = file_devrpc_dev_proto_rawDesc
)
func file_devrpc_dev_proto_rawDescGZIP() []byte {
file_devrpc_dev_proto_rawDescOnce.Do(func() {
file_devrpc_dev_proto_rawDescData = protoimpl.X.CompressGZIP(file_devrpc_dev_proto_rawDescData)
})
return file_devrpc_dev_proto_rawDescData
}
var file_devrpc_dev_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_devrpc_dev_proto_goTypes = []interface{}{
(*ImportGraphResponse)(nil), // 0: devrpc.ImportGraphResponse
(*QuiescenceRequest)(nil), // 1: devrpc.QuiescenceRequest
(*QuiescenceResponse)(nil), // 2: devrpc.QuiescenceResponse
(*lnrpc.ChannelPoint)(nil), // 3: lnrpc.ChannelPoint
(*lnrpc.ChannelGraph)(nil), // 4: lnrpc.ChannelGraph
}
var file_devrpc_dev_proto_depIdxs = []int32{
3, // 0: devrpc.QuiescenceRequest.chan_id:type_name -> lnrpc.ChannelPoint
4, // 1: devrpc.Dev.ImportGraph:input_type -> lnrpc.ChannelGraph
1, // 2: devrpc.Dev.Quiesce:input_type -> devrpc.QuiescenceRequest
0, // 3: devrpc.Dev.ImportGraph:output_type -> devrpc.ImportGraphResponse
2, // 4: devrpc.Dev.Quiesce:output_type -> devrpc.QuiescenceResponse
3, // [3:5] is the sub-list for method output_type
1, // [1:3] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_devrpc_dev_proto_init() }
func file_devrpc_dev_proto_init() {
if File_devrpc_dev_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_devrpc_dev_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ImportGraphResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_devrpc_dev_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*QuiescenceRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_devrpc_dev_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*QuiescenceResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_devrpc_dev_proto_rawDesc,
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_devrpc_dev_proto_goTypes,
DependencyIndexes: file_devrpc_dev_proto_depIdxs,
MessageInfos: file_devrpc_dev_proto_msgTypes,
}.Build()
File_devrpc_dev_proto = out.File
file_devrpc_dev_proto_rawDesc = nil
file_devrpc_dev_proto_goTypes = nil
file_devrpc_dev_proto_depIdxs = nil
}
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: devrpc/dev.proto
/*
Package devrpc is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package devrpc
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"github.com/lightningnetwork/lnd/lnrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
func request_Dev_ImportGraph_0(ctx context.Context, marshaler runtime.Marshaler, client DevClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq lnrpc.ChannelGraph
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ImportGraph(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Dev_ImportGraph_0(ctx context.Context, marshaler runtime.Marshaler, server DevServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq lnrpc.ChannelGraph
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ImportGraph(ctx, &protoReq)
return msg, metadata, err
}
func request_Dev_Quiesce_0(ctx context.Context, marshaler runtime.Marshaler, client DevClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq QuiescenceRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.Quiesce(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Dev_Quiesce_0(ctx context.Context, marshaler runtime.Marshaler, server DevServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq QuiescenceRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.Quiesce(ctx, &protoReq)
return msg, metadata, err
}
// RegisterDevHandlerServer registers the http handlers for service Dev to "mux".
// UnaryRPC :call DevServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterDevHandlerFromEndpoint instead.
func RegisterDevHandlerServer(ctx context.Context, mux *runtime.ServeMux, server DevServer) error {
mux.Handle("POST", pattern_Dev_ImportGraph_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/devrpc.Dev/ImportGraph", runtime.WithHTTPPathPattern("/v2/dev/importgraph"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Dev_ImportGraph_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Dev_ImportGraph_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Dev_Quiesce_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/devrpc.Dev/Quiesce", runtime.WithHTTPPathPattern("/v2/dev/quiesce"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Dev_Quiesce_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Dev_Quiesce_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterDevHandlerFromEndpoint is same as RegisterDevHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterDevHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterDevHandler(ctx, mux, conn)
}
// RegisterDevHandler registers the http handlers for service Dev to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterDevHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterDevHandlerClient(ctx, mux, NewDevClient(conn))
}
// RegisterDevHandlerClient registers the http handlers for service Dev
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "DevClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "DevClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "DevClient" to call the correct interceptors.
func RegisterDevHandlerClient(ctx context.Context, mux *runtime.ServeMux, client DevClient) error {
mux.Handle("POST", pattern_Dev_ImportGraph_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/devrpc.Dev/ImportGraph", runtime.WithHTTPPathPattern("/v2/dev/importgraph"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Dev_ImportGraph_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Dev_ImportGraph_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Dev_Quiesce_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/devrpc.Dev/Quiesce", runtime.WithHTTPPathPattern("/v2/dev/quiesce"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Dev_Quiesce_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Dev_Quiesce_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_Dev_ImportGraph_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "dev", "importgraph"}, ""))
pattern_Dev_Quiesce_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "dev", "quiesce"}, ""))
)
var (
forward_Dev_ImportGraph_0 = runtime.ForwardResponseMessage
forward_Dev_Quiesce_0 = runtime.ForwardResponseMessage
)
// Code generated by falafel 0.9.2. DO NOT EDIT.
// source: dev.proto
package devrpc
import (
"context"
gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/lightningnetwork/lnd/lnrpc"
"google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protojson"
)
func RegisterDevJSONCallbacks(registry map[string]func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error))) {
marshaler := &gateway.JSONPb{
MarshalOptions: protojson.MarshalOptions{
UseProtoNames: true,
EmitUnpopulated: true,
},
}
registry["devrpc.Dev.ImportGraph"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &lnrpc.ChannelGraph{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewDevClient(conn)
resp, err := client.ImportGraph(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["devrpc.Dev.Quiesce"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &QuiescenceRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewDevClient(conn)
resp, err := client.Quiesce(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package devrpc
import (
context "context"
lnrpc "github.com/lightningnetwork/lnd/lnrpc"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// DevClient is the client API for Dev service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type DevClient interface {
// lncli: `importgraph`
// ImportGraph imports a ChannelGraph into the graph database. Should only be
// used for development.
ImportGraph(ctx context.Context, in *lnrpc.ChannelGraph, opts ...grpc.CallOption) (*ImportGraphResponse, error)
// Quiesce instructs a channel to initiate the quiescence (stfu) protocol. This
// RPC is for testing purposes only. The commit that adds it will be removed
// once interop is confirmed.
Quiesce(ctx context.Context, in *QuiescenceRequest, opts ...grpc.CallOption) (*QuiescenceResponse, error)
}
type devClient struct {
cc grpc.ClientConnInterface
}
func NewDevClient(cc grpc.ClientConnInterface) DevClient {
return &devClient{cc}
}
func (c *devClient) ImportGraph(ctx context.Context, in *lnrpc.ChannelGraph, opts ...grpc.CallOption) (*ImportGraphResponse, error) {
out := new(ImportGraphResponse)
err := c.cc.Invoke(ctx, "/devrpc.Dev/ImportGraph", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *devClient) Quiesce(ctx context.Context, in *QuiescenceRequest, opts ...grpc.CallOption) (*QuiescenceResponse, error) {
out := new(QuiescenceResponse)
err := c.cc.Invoke(ctx, "/devrpc.Dev/Quiesce", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// DevServer is the server API for Dev service.
// All implementations must embed UnimplementedDevServer
// for forward compatibility
type DevServer interface {
// lncli: `importgraph`
// ImportGraph imports a ChannelGraph into the graph database. Should only be
// used for development.
ImportGraph(context.Context, *lnrpc.ChannelGraph) (*ImportGraphResponse, error)
// Quiesce instructs a channel to initiate the quiescence (stfu) protocol. This
// RPC is for testing purposes only. The commit that adds it will be removed
// once interop is confirmed.
Quiesce(context.Context, *QuiescenceRequest) (*QuiescenceResponse, error)
mustEmbedUnimplementedDevServer()
}
// UnimplementedDevServer must be embedded to have forward compatible implementations.
type UnimplementedDevServer struct {
}
func (UnimplementedDevServer) ImportGraph(context.Context, *lnrpc.ChannelGraph) (*ImportGraphResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ImportGraph not implemented")
}
func (UnimplementedDevServer) Quiesce(context.Context, *QuiescenceRequest) (*QuiescenceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Quiesce not implemented")
}
func (UnimplementedDevServer) mustEmbedUnimplementedDevServer() {}
// UnsafeDevServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to DevServer will
// result in compilation errors.
type UnsafeDevServer interface {
mustEmbedUnimplementedDevServer()
}
func RegisterDevServer(s grpc.ServiceRegistrar, srv DevServer) {
s.RegisterService(&Dev_ServiceDesc, srv)
}
func _Dev_ImportGraph_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(lnrpc.ChannelGraph)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(DevServer).ImportGraph(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/devrpc.Dev/ImportGraph",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DevServer).ImportGraph(ctx, req.(*lnrpc.ChannelGraph))
}
return interceptor(ctx, in, info, handler)
}
func _Dev_Quiesce_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(QuiescenceRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(DevServer).Quiesce(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/devrpc.Dev/Quiesce",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DevServer).Quiesce(ctx, req.(*QuiescenceRequest))
}
return interceptor(ctx, in, info, handler)
}
// Dev_ServiceDesc is the grpc.ServiceDesc for Dev service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Dev_ServiceDesc = grpc.ServiceDesc{
ServiceName: "devrpc.Dev",
HandlerType: (*DevServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "ImportGraph",
Handler: _Dev_ImportGraph_Handler,
},
{
MethodName: "Quiesce",
Handler: _Dev_Quiesce_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "devrpc/dev.proto",
}
package devrpc
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This means the
// package will not perform any logging by default until the caller requests
// it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("DRPC", nil))
}
// DisableLog disables all library log output. Logging output is disabled by
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info. This
// should be used in preference to SetLogWriter if the caller is also using
// btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package lnrpc
import (
"os"
)
// FileExists reports whether the named file or directory exists.
func FileExists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
package invoicesrpc
import (
"bytes"
"context"
"crypto/rand"
"errors"
"fmt"
"math"
mathRand "math/rand"
"sort"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/routing/blindedpath"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/zpay32"
)
const (
// DefaultInvoiceExpiry is the default invoice expiry for new MPP
// invoices.
DefaultInvoiceExpiry = 24 * time.Hour
// DefaultAMPInvoiceExpiry is the default invoice expiry for new AMP
// invoices.
DefaultAMPInvoiceExpiry = 30 * 24 * time.Hour
// hopHintFactor is factor by which we scale the total amount of
// inbound capacity we want our hop hints to represent, allowing us to
// have some leeway if peers go offline.
hopHintFactor = 2
// maxHopHints is the maximum number of hint paths that will be included
// in an invoice.
maxHopHints = 20
)
// AddInvoiceConfig contains dependencies for invoice creation.
type AddInvoiceConfig struct {
// AddInvoice is called to add the invoice to the registry.
AddInvoice func(ctx context.Context, invoice *invoices.Invoice,
paymentHash lntypes.Hash) (uint64, error)
// IsChannelActive is used to generate valid hop hints.
IsChannelActive func(chanID lnwire.ChannelID) bool
// ChainParams are required to properly decode invoice payment requests
// that are marshalled over rpc.
ChainParams *chaincfg.Params
// NodeSigner is an implementation of the MessageSigner implementation
// that's backed by the identity private key of the running lnd node.
NodeSigner *netann.NodeSigner
// DefaultCLTVExpiry is the default invoice expiry if no values is
// specified.
DefaultCLTVExpiry uint32
// ChanDB is a global boltdb instance which is needed to access the
// channel graph.
ChanDB *channeldb.ChannelStateDB
// Graph gives the invoice server access to various graph related
// queries.
Graph GraphSource
// GenInvoiceFeatures returns a feature containing feature bits that
// should be advertised on freshly generated invoices.
GenInvoiceFeatures func() *lnwire.FeatureVector
// GenAmpInvoiceFeatures returns a feature containing feature bits that
// should be advertised on freshly generated AMP invoices.
GenAmpInvoiceFeatures func() *lnwire.FeatureVector
// GetAlias allows the peer's alias SCID to be retrieved for private
// option_scid_alias channels.
GetAlias func(lnwire.ChannelID) (lnwire.ShortChannelID, error)
// BestHeight returns the current best block height that this node is
// aware of.
BestHeight func() (uint32, error)
// QueryBlindedRoutes can be used to generate a few routes to this node
// that can then be used in the construction of a blinded payment path.
QueryBlindedRoutes func(lnwire.MilliSatoshi) ([]*route.Route, error)
}
// AddInvoiceData contains the required data to create a new invoice.
type AddInvoiceData struct {
// An optional memo to attach along with the invoice. Used for record
// keeping purposes for the invoice's creator, and will also be set in
// the description field of the encoded payment request if the
// description_hash field is not being used.
Memo string
// The preimage which will allow settling an incoming HTLC payable to
// this preimage. If Preimage is set, Hash should be nil. If both
// Preimage and Hash are nil, a random preimage is generated.
Preimage *lntypes.Preimage
// The hash of the preimage. If Hash is set, Preimage should be nil.
// This condition indicates that we have a 'hold invoice' for which the
// htlc will be accepted and held until the preimage becomes known.
Hash *lntypes.Hash
// The value of this invoice in millisatoshis.
Value lnwire.MilliSatoshi
// Hash (SHA-256) of a description of the payment. Used if the
// description of payment (memo) is too long to naturally fit within the
// description field of an encoded payment request.
DescriptionHash []byte
// Payment request expiry time in seconds. Default is 3600 (1 hour).
Expiry int64
// Fallback on-chain address.
FallbackAddr string
// Delta to use for the time-lock of the CLTV extended to the final hop.
CltvExpiry uint64
// Whether this invoice should include routing hints for private
// channels.
Private bool
// HodlInvoice signals that this invoice shouldn't be settled
// immediately upon receiving the payment.
HodlInvoice bool
// Amp signals whether or not to create an AMP invoice.
//
// NOTE: Preimage should always be set to nil when this value is true.
Amp bool
// BlindedPathCfg holds the config values to use when constructing
// blinded paths to add to the invoice. A non-nil BlindedPathCfg signals
// that this invoice should disguise the location of the recipient by
// adding blinded payment paths to the invoice instead of revealing the
// destination node's real pub key.
BlindedPathCfg *BlindedPathConfig
// RouteHints are optional route hints that can each be individually
// used to assist in reaching the invoice's destination.
RouteHints [][]zpay32.HopHint
}
// BlindedPathConfig holds the configuration values required for blinded path
// generation for invoices.
type BlindedPathConfig struct {
// RoutePolicyIncrMultiplier is the amount by which policy values for
// hops in a blinded route will be bumped to avoid easy probing. For
// example, a multiplier of 1.1 will bump all appropriate the values
// (base fee, fee rate, CLTV delta and min HLTC) by 10%.
RoutePolicyIncrMultiplier float64
// RoutePolicyDecrMultiplier is the amount by which appropriate policy
// values for hops in a blinded route will be decreased to avoid easy
// probing. For example, a multiplier of 0.9 will reduce appropriate
// values (like maximum HTLC) by 10%.
RoutePolicyDecrMultiplier float64
// MinNumPathHops is the minimum number of hops that a blinded path
// should be. Dummy hops will be used to pad any route with a length
// less than this.
MinNumPathHops uint8
// DefaultDummyHopPolicy holds the default policy values to use for
// dummy hops in a blinded path in the case where they cant be derived
// through other means.
DefaultDummyHopPolicy *blindedpath.BlindedHopPolicy
}
// paymentHashAndPreimage returns the payment hash and preimage for this invoice
// depending on the configuration.
//
// For AMP invoices (when Amp flag is true), this method always returns a nil
// preimage. The hash value can be set externally by the user using the Hash
// field, or one will be generated randomly. The payment hash here only serves
// as a unique identifier for insertion into the invoice index, as there is
// no universal preimage for an AMP payment.
//
// For MPP invoices (when Amp flag is false), this method may return nil
// preimage when create a hodl invoice, but otherwise will always return a
// non-nil preimage and the corresponding payment hash. The valid combinations
// are parsed as follows:
// - Preimage == nil && Hash == nil -> (random preimage, H(random preimage))
// - Preimage != nil && Hash == nil -> (Preimage, H(Preimage))
// - Preimage == nil && Hash != nil -> (nil, Hash)
func (d *AddInvoiceData) paymentHashAndPreimage() (
*lntypes.Preimage, lntypes.Hash, error) {
if d.Amp {
return d.ampPaymentHashAndPreimage()
}
return d.mppPaymentHashAndPreimage()
}
// ampPaymentHashAndPreimage returns the payment hash to use for an AMP invoice.
// The preimage will always be nil.
func (d *AddInvoiceData) ampPaymentHashAndPreimage() (*lntypes.Preimage,
lntypes.Hash, error) {
switch {
// Preimages cannot be set on AMP invoice.
case d.Preimage != nil:
return nil, lntypes.Hash{},
errors.New("preimage set on AMP invoice")
// If a specific hash was requested, use that.
case d.Hash != nil:
return nil, *d.Hash, nil
// Otherwise generate a random hash value, just needs to be unique to be
// added to the invoice index.
default:
var paymentHash lntypes.Hash
if _, err := rand.Read(paymentHash[:]); err != nil {
return nil, lntypes.Hash{}, err
}
return nil, paymentHash, nil
}
}
// mppPaymentHashAndPreimage returns the payment hash and preimage to use for an
// MPP invoice.
func (d *AddInvoiceData) mppPaymentHashAndPreimage() (*lntypes.Preimage,
lntypes.Hash, error) {
var (
paymentPreimage *lntypes.Preimage
paymentHash lntypes.Hash
)
switch {
// Only either preimage or hash can be set.
case d.Preimage != nil && d.Hash != nil:
return nil, lntypes.Hash{},
errors.New("preimage and hash both set")
// If no hash or preimage is given, generate a random preimage.
case d.Preimage == nil && d.Hash == nil:
paymentPreimage = &lntypes.Preimage{}
if _, err := rand.Read(paymentPreimage[:]); err != nil {
return nil, lntypes.Hash{}, err
}
paymentHash = paymentPreimage.Hash()
// If just a hash is given, we create a hold invoice by setting the
// preimage to unknown.
case d.Preimage == nil && d.Hash != nil:
paymentHash = *d.Hash
// A specific preimage was supplied. Use that for the invoice.
case d.Preimage != nil && d.Hash == nil:
preimage := *d.Preimage
paymentPreimage = &preimage
paymentHash = d.Preimage.Hash()
}
return paymentPreimage, paymentHash, nil
}
// AddInvoice attempts to add a new invoice to the invoice database. Any
// duplicated invoices are rejected, therefore all invoices *must* have a
// unique payment preimage.
func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
invoice *AddInvoiceData) (*lntypes.Hash, *invoices.Invoice, error) {
blind := invoice.BlindedPathCfg != nil
if invoice.Amp && blind {
return nil, nil, fmt.Errorf("AMP invoices with blinded paths " +
"are not yet supported")
}
paymentPreimage, paymentHash, err := invoice.paymentHashAndPreimage()
if err != nil {
return nil, nil, err
}
// The size of the memo, receipt and description hash attached must not
// exceed the maximum values for either of the fields.
if len(invoice.Memo) > invoices.MaxMemoSize {
return nil, nil, fmt.Errorf("memo too large: %v bytes "+
"(maxsize=%v)", len(invoice.Memo),
invoices.MaxMemoSize)
}
if len(invoice.DescriptionHash) > 0 &&
len(invoice.DescriptionHash) != 32 {
return nil, nil, fmt.Errorf("description hash is %v bytes, "+
"must be 32", len(invoice.DescriptionHash))
}
// We set the max invoice amount to 100k BTC, which itself is several
// multiples off the current block reward.
maxInvoiceAmt := btcutil.Amount(btcutil.SatoshiPerBitcoin * 100000)
switch {
// The value of the invoice must not be negative.
case int64(invoice.Value) < 0:
return nil, nil, fmt.Errorf("payments of negative value "+
"are not allowed, value is %v", int64(invoice.Value))
// Also ensure that the invoice is actually realistic, while preventing
// any issues due to underflow.
case invoice.Value.ToSatoshis() > maxInvoiceAmt:
return nil, nil, fmt.Errorf("invoice amount %v is "+
"too large, max is %v", invoice.Value.ToSatoshis(),
maxInvoiceAmt)
}
amtMSat := invoice.Value
// We also create an encoded payment request which allows the
// caller to compactly send the invoice to the payer. We'll create a
// list of options to be added to the encoded payment request. For now
// we only support the required fields description/description_hash,
// expiry, fallback address, and the amount field.
var options []func(*zpay32.Invoice)
// We only include the amount in the invoice if it is greater than 0.
// By not including the amount, we enable the creation of invoices that
// allow the payer to specify the amount of satoshis they wish to send.
if amtMSat > 0 {
options = append(options, zpay32.Amount(amtMSat))
}
// If specified, add a fallback address to the payment request.
if len(invoice.FallbackAddr) > 0 {
addr, err := btcutil.DecodeAddress(
invoice.FallbackAddr, cfg.ChainParams,
)
if err != nil {
return nil, nil, fmt.Errorf("invalid fallback "+
"address: %v", err)
}
if !addr.IsForNet(cfg.ChainParams) {
return nil, nil, fmt.Errorf("fallback address is not "+
"for %s", cfg.ChainParams.Name)
}
options = append(options, zpay32.FallbackAddr(addr))
}
var expiry time.Duration
switch {
// An invoice expiry has been provided by the caller.
case invoice.Expiry > 0:
// We'll ensure that the specified expiry is restricted to sane
// number of seconds. As a result, we'll reject an invoice with
// an expiry greater than 1 year.
maxExpiry := time.Hour * 24 * 365
expSeconds := invoice.Expiry
if float64(expSeconds) > maxExpiry.Seconds() {
return nil, nil, fmt.Errorf("expiry of %v seconds "+
"greater than max expiry of %v seconds",
float64(expSeconds), maxExpiry.Seconds())
}
expiry = time.Duration(invoice.Expiry) * time.Second
// If no custom expiry is provided, use the default MPP expiry.
case !invoice.Amp:
expiry = DefaultInvoiceExpiry
// Otherwise, use the default AMP expiry.
default:
expiry = DefaultAMPInvoiceExpiry
}
options = append(options, zpay32.Expiry(expiry))
// If the description hash is set, then we add it do the list of
// options. If not, use the memo field as the payment request
// description.
if len(invoice.DescriptionHash) > 0 {
var descHash [32]byte
copy(descHash[:], invoice.DescriptionHash[:])
options = append(options, zpay32.DescriptionHash(descHash))
} else {
// Use the memo field as the description. If this is not set
// this will just be an empty string.
options = append(options, zpay32.Description(invoice.Memo))
}
if invoice.CltvExpiry > routing.MaxCLTVDelta {
return nil, nil, fmt.Errorf("CLTV delta of %v is too large, "+
"max accepted is: %v", invoice.CltvExpiry,
math.MaxUint16)
}
// We'll use our current default CLTV value unless one was specified as
// an option on the command line when creating an invoice.
cltvExpiryDelta := uint64(cfg.DefaultCLTVExpiry)
if invoice.CltvExpiry != 0 {
// Disallow user-chosen final CLTV deltas below the required
// minimum.
if invoice.CltvExpiry < routing.MinCLTVDelta {
return nil, nil, fmt.Errorf("CLTV delta of %v must be "+
"greater than minimum of %v",
invoice.CltvExpiry, routing.MinCLTVDelta)
}
cltvExpiryDelta = invoice.CltvExpiry
}
// Only include a final CLTV expiry delta if this is not a blinded
// invoice. In a blinded invoice, this value will be added to the total
// blinded route CLTV delta value
if !blind {
options = append(options, zpay32.CLTVExpiry(cltvExpiryDelta))
}
// We make sure that the given invoice routing hints number is within
// the valid range
if len(invoice.RouteHints) > maxHopHints {
return nil, nil, fmt.Errorf("number of routing hints must "+
"not exceed maximum of %v", maxHopHints)
}
// Include route hints if needed.
if len(invoice.RouteHints) > 0 || invoice.Private {
if blind {
return nil, nil, fmt.Errorf("can't set both hop " +
"hints and add blinded payment paths")
}
// Validate provided hop hints.
for _, hint := range invoice.RouteHints {
if len(hint) == 0 {
return nil, nil, fmt.Errorf("number of hop " +
"hint within a route must be positive")
}
}
totalHopHints := len(invoice.RouteHints)
if invoice.Private {
totalHopHints = maxHopHints
}
hopHintsCfg := newSelectHopHintsCfg(cfg, totalHopHints)
hopHints, err := PopulateHopHints(
hopHintsCfg, amtMSat, invoice.RouteHints,
)
if err != nil {
return nil, nil, fmt.Errorf("unable to populate hop "+
"hints: %v", err)
}
// Convert our set of selected hop hints into route
// hints and add to our invoice options.
for _, hopHint := range hopHints {
routeHint := zpay32.RouteHint(hopHint)
options = append(
options, routeHint,
)
}
}
// Set our desired invoice features and add them to our list of options.
var invoiceFeatures *lnwire.FeatureVector
if invoice.Amp {
invoiceFeatures = cfg.GenAmpInvoiceFeatures()
} else {
invoiceFeatures = cfg.GenInvoiceFeatures()
}
options = append(options, zpay32.Features(invoiceFeatures))
// Generate and set a random payment address for this payment. If the
// sender understands payment addresses, this can be used to avoid
// intermediaries probing the receiver. If the invoice does not have
// blinded paths, then this will be encoded in the invoice itself.
// Otherwise, it will instead be embedded in the encrypted recipient
// data of blinded paths. In the blinded path case, this will be used
// for the PathID.
var paymentAddr [32]byte
if _, err := rand.Read(paymentAddr[:]); err != nil {
return nil, nil, err
}
if blind {
blindCfg := invoice.BlindedPathCfg
// Use the 10-min-per-block assumption to get a rough estimate
// of the number of blocks until the invoice expires. We want
// to make sure that the blinded path definitely does not expire
// before the invoice does, and so we add a healthy buffer.
invoiceExpiry := uint32(expiry.Minutes() / 10)
blindedPathExpiry := invoiceExpiry * 2
// Add BlockPadding to the finalCltvDelta so that the receiving
// node does not reject the HTLC if some blocks are mined while
// the payment is in-flight. Note that unlike vanilla invoices,
// with blinded paths, the recipient is responsible for adding
// this block padding instead of the sender.
finalCLTVDelta := uint32(cltvExpiryDelta)
finalCLTVDelta += uint32(routing.BlockPadding)
//nolint:ll
paths, err := blindedpath.BuildBlindedPaymentPaths(
&blindedpath.BuildBlindedPathCfg{
FindRoutes: cfg.QueryBlindedRoutes,
FetchChannelEdgesByID: cfg.Graph.FetchChannelEdgesByID,
FetchOurOpenChannels: cfg.ChanDB.FetchAllOpenChannels,
PathID: paymentAddr[:],
ValueMsat: invoice.Value,
BestHeight: cfg.BestHeight,
MinFinalCLTVExpiryDelta: finalCLTVDelta,
BlocksUntilExpiry: blindedPathExpiry,
AddPolicyBuffer: func(
p *blindedpath.BlindedHopPolicy) (
*blindedpath.BlindedHopPolicy, error) {
//nolint:ll
return blindedpath.AddPolicyBuffer(
p, blindCfg.RoutePolicyIncrMultiplier,
blindCfg.RoutePolicyDecrMultiplier,
)
},
MinNumHops: blindCfg.MinNumPathHops,
DefaultDummyHopPolicy: blindCfg.DefaultDummyHopPolicy,
},
)
if err != nil {
return nil, nil, err
}
for _, path := range paths {
options = append(options, zpay32.WithBlindedPaymentPath(
path,
))
}
} else {
options = append(options, zpay32.PaymentAddr(paymentAddr))
}
// Create and encode the payment request as a bech32 (zpay32) string.
creationDate := time.Now()
payReq, err := zpay32.NewInvoice(
cfg.ChainParams, paymentHash, creationDate, options...,
)
if err != nil {
return nil, nil, err
}
payReqString, err := payReq.Encode(zpay32.MessageSigner{
SignCompact: func(msg []byte) ([]byte, error) {
// For an invoice without a blinded path, the main node
// key is used to sign the invoice so that the sender
// can derive the true pub key of the recipient.
if !blind {
return cfg.NodeSigner.SignMessageCompact(
msg, false,
)
}
// For an invoice with a blinded path, we use an
// ephemeral key to sign the invoice since we don't want
// the sender to be able to know the real pub key of
// the recipient.
ephemKey, err := btcec.NewPrivateKey()
if err != nil {
return nil, err
}
return ecdsa.SignCompact(
ephemKey, chainhash.HashB(msg), true,
), nil
},
})
if err != nil {
return nil, nil, err
}
newInvoice := &invoices.Invoice{
CreationDate: creationDate,
Memo: []byte(invoice.Memo),
PaymentRequest: []byte(payReqString),
Terms: invoices.ContractTerm{
FinalCltvDelta: int32(payReq.MinFinalCLTVExpiry()),
Expiry: payReq.Expiry(),
Value: amtMSat,
PaymentPreimage: paymentPreimage,
PaymentAddr: paymentAddr,
Features: invoiceFeatures,
},
HodlInvoice: invoice.HodlInvoice,
}
log.Tracef("[addinvoice] adding new invoice %v",
lnutils.SpewLogClosure(newInvoice))
// With all sanity checks passed, write the invoice to the database.
_, err = cfg.AddInvoice(ctx, newInvoice, paymentHash)
if err != nil {
return nil, nil, err
}
return &paymentHash, newInvoice, nil
}
// chanCanBeHopHint returns true if the target channel is eligible to be a hop
// hint.
func chanCanBeHopHint(channel *HopHintInfo, cfg *SelectHopHintsCfg) (
*models.ChannelEdgePolicy, bool) {
// Since we're only interested in our private channels, we'll skip
// public ones.
if channel.IsPublic {
return nil, false
}
// Make sure the channel is active.
if !channel.IsActive {
log.Debugf("Skipping channel %v due to not "+
"being eligible to forward payments",
channel.ShortChannelID)
return nil, false
}
// To ensure we don't leak unadvertised nodes, we'll make sure our
// counterparty is publicly advertised within the network. Otherwise,
// we'll end up leaking information about nodes that intend to stay
// unadvertised, like in the case of a node only having private
// channels.
var remotePub [33]byte
copy(remotePub[:], channel.RemotePubkey.SerializeCompressed())
isRemoteNodePublic, err := cfg.IsPublicNode(remotePub)
if err != nil {
log.Errorf("Unable to determine if node %x "+
"is advertised: %v", remotePub, err)
return nil, false
}
if !isRemoteNodePublic {
log.Debugf("Skipping channel %v due to "+
"counterparty %x being unadvertised",
channel.ShortChannelID, remotePub)
return nil, false
}
// Fetch the policies for each end of the channel.
info, p1, p2, err := cfg.FetchChannelEdgesByID(channel.ShortChannelID)
if err != nil {
// In the case of zero-conf channels, it may be the case that
// the alias SCID was deleted from the graph, and replaced by
// the confirmed SCID. Check the Graph for the confirmed SCID.
confirmedScid := channel.ConfirmedScidZC
info, p1, p2, err = cfg.FetchChannelEdgesByID(confirmedScid)
if err != nil {
log.Errorf("Unable to fetch the routing policies for "+
"the edges of the channel %v: %v",
channel.ShortChannelID, err)
return nil, false
}
}
// Now, we'll need to determine which is the correct policy for HTLCs
// being sent from the remote node.
var remotePolicy *models.ChannelEdgePolicy
if bytes.Equal(remotePub[:], info.NodeKey1Bytes[:]) {
remotePolicy = p1
} else {
remotePolicy = p2
}
return remotePolicy, true
}
// HopHintInfo contains the channel information required to create a hop hint.
type HopHintInfo struct {
// IsPublic indicates whether a channel is advertised to the network.
IsPublic bool
// IsActive indicates whether the channel is online and available for
// use.
IsActive bool
// FundingOutpoint is the funding txid:index for the channel.
FundingOutpoint wire.OutPoint
// RemotePubkey is the public key of the remote party that this channel
// is in.
RemotePubkey *btcec.PublicKey
// RemoteBalance is the remote party's balance (our current incoming
// capacity).
RemoteBalance lnwire.MilliSatoshi
// ShortChannelID is the short channel ID of the channel.
ShortChannelID uint64
// ConfirmedScidZC is the confirmed SCID of a zero-conf channel. This
// may be used for looking up a channel in the graph.
ConfirmedScidZC uint64
// ScidAliasFeature denotes whether the channel has negotiated the
// option-scid-alias feature bit.
ScidAliasFeature bool
}
func newHopHintInfo(c *channeldb.OpenChannel, isActive bool) *HopHintInfo {
isPublic := c.ChannelFlags&lnwire.FFAnnounceChannel != 0
return &HopHintInfo{
IsPublic: isPublic,
IsActive: isActive,
FundingOutpoint: c.FundingOutpoint,
RemotePubkey: c.IdentityPub,
RemoteBalance: c.LocalCommitment.RemoteBalance,
ShortChannelID: c.ShortChannelID.ToUint64(),
ConfirmedScidZC: c.ZeroConfRealScid().ToUint64(),
ScidAliasFeature: c.ChanType.HasScidAliasFeature(),
}
}
// newHopHint returns a new hop hint using the relevant data from a hopHintInfo
// and a ChannelEdgePolicy.
func newHopHint(hopHintInfo *HopHintInfo,
chanPolicy *models.ChannelEdgePolicy) zpay32.HopHint {
return zpay32.HopHint{
NodeID: hopHintInfo.RemotePubkey,
ChannelID: hopHintInfo.ShortChannelID,
FeeBaseMSat: uint32(chanPolicy.FeeBaseMSat),
FeeProportionalMillionths: uint32(
chanPolicy.FeeProportionalMillionths,
),
CLTVExpiryDelta: chanPolicy.TimeLockDelta,
}
}
// SelectHopHintsCfg contains the dependencies required to obtain hop hints
// for an invoice.
type SelectHopHintsCfg struct {
// IsPublicNode is returns a bool indicating whether the node with the
// given public key is seen as a public node in the graph from the
// graph's source node's point of view.
IsPublicNode func(pubKey [33]byte) (bool, error)
// FetchChannelEdgesByID attempts to lookup the two directed edges for
// the channel identified by the channel ID.
FetchChannelEdgesByID func(chanID uint64) (*models.ChannelEdgeInfo,
*models.ChannelEdgePolicy, *models.ChannelEdgePolicy,
error)
// GetAlias allows the peer's alias SCID to be retrieved for private
// option_scid_alias channels.
GetAlias func(lnwire.ChannelID) (lnwire.ShortChannelID, error)
// FetchAllChannels retrieves all open channels currently stored
// within the database.
FetchAllChannels func() ([]*channeldb.OpenChannel, error)
// IsChannelActive checks whether the channel identified by the provided
// ChannelID is considered active.
IsChannelActive func(chanID lnwire.ChannelID) bool
// MaxHopHints is the maximum number of hop hints we are interested in.
MaxHopHints int
}
func newSelectHopHintsCfg(invoicesCfg *AddInvoiceConfig,
maxHopHints int) *SelectHopHintsCfg {
return &SelectHopHintsCfg{
FetchAllChannels: invoicesCfg.ChanDB.FetchAllChannels,
IsChannelActive: invoicesCfg.IsChannelActive,
IsPublicNode: invoicesCfg.Graph.IsPublicNode,
FetchChannelEdgesByID: invoicesCfg.Graph.FetchChannelEdgesByID,
GetAlias: invoicesCfg.GetAlias,
MaxHopHints: maxHopHints,
}
}
// sufficientHints checks whether we have sufficient hop hints, based on the
// any of the following criteria:
// - Hop hint count: the number of hints have reach our max target.
// - Total incoming capacity (for non-zero invoice amounts): the sum of the
// remote balance amount in the hints is bigger of equal than our target
// (currently twice the invoice amount)
//
// We limit our number of hop hints like this to keep our invoice size down,
// and to avoid leaking all our private channels when we don't need to.
func sufficientHints(nHintsLeft int, currentAmount,
targetAmount lnwire.MilliSatoshi) bool {
if nHintsLeft <= 0 {
log.Debugf("Reached targeted number of hop hints")
return true
}
if targetAmount != 0 && currentAmount >= targetAmount {
log.Debugf("Total hint amount: %v has reached target hint "+
"bandwidth: %v", currentAmount, targetAmount)
return true
}
return false
}
// getPotentialHints returns a slice of open channels that should be considered
// for the hopHint list in an invoice. The slice is sorted in descending order
// based on the remote balance.
func getPotentialHints(cfg *SelectHopHintsCfg) ([]*channeldb.OpenChannel,
error) {
// TODO(positiveblue): get the channels slice already filtered by
// private == true and sorted by RemoteBalance?
openChannels, err := cfg.FetchAllChannels()
if err != nil {
return nil, err
}
privateChannels := make([]*channeldb.OpenChannel, 0, len(openChannels))
for _, oc := range openChannels {
isPublic := oc.ChannelFlags&lnwire.FFAnnounceChannel != 0
if !isPublic {
privateChannels = append(privateChannels, oc)
}
}
// Sort the channels in descending remote balance.
compareRemoteBalance := func(i, j int) bool {
iBalance := privateChannels[i].LocalCommitment.RemoteBalance
jBalance := privateChannels[j].LocalCommitment.RemoteBalance
return iBalance > jBalance
}
sort.Slice(privateChannels, compareRemoteBalance)
return privateChannels, nil
}
// shouldIncludeChannel returns true if the channel passes all the checks to
// be a hopHint in a given invoice.
func shouldIncludeChannel(cfg *SelectHopHintsCfg,
channel *channeldb.OpenChannel,
alreadyIncluded map[uint64]bool) (zpay32.HopHint, lnwire.MilliSatoshi,
bool) {
if _, ok := alreadyIncluded[channel.ShortChannelID.ToUint64()]; ok {
return zpay32.HopHint{}, 0, false
}
chanID := lnwire.NewChanIDFromOutPoint(
channel.FundingOutpoint,
)
hopHintInfo := newHopHintInfo(channel, cfg.IsChannelActive(chanID))
// If this channel can't be a hop hint, then skip it.
edgePolicy, canBeHopHint := chanCanBeHopHint(hopHintInfo, cfg)
if edgePolicy == nil || !canBeHopHint {
return zpay32.HopHint{}, 0, false
}
if hopHintInfo.ScidAliasFeature {
alias, err := cfg.GetAlias(chanID)
if err != nil {
return zpay32.HopHint{}, 0, false
}
if alias.IsDefault() || alreadyIncluded[alias.ToUint64()] {
return zpay32.HopHint{}, 0, false
}
hopHintInfo.ShortChannelID = alias.ToUint64()
}
// Now that we know this channel use usable, add it as a hop hint and
// the indexes we'll use later.
hopHint := newHopHint(hopHintInfo, edgePolicy)
return hopHint, hopHintInfo.RemoteBalance, true
}
// selectHopHints iterates a list of potential hints selecting the valid hop
// hints until we have enough hints or run out of channels.
//
// NOTE: selectHopHints expects potentialHints to be already sorted in
// descending priority.
func selectHopHints(cfg *SelectHopHintsCfg, nHintsLeft int,
targetBandwidth lnwire.MilliSatoshi,
potentialHints []*channeldb.OpenChannel,
alreadyIncluded map[uint64]bool) [][]zpay32.HopHint {
currentBandwidth := lnwire.MilliSatoshi(0)
hopHints := make([][]zpay32.HopHint, 0, nHintsLeft)
for _, channel := range potentialHints {
enoughHopHints := sufficientHints(
nHintsLeft, currentBandwidth, targetBandwidth,
)
if enoughHopHints {
return hopHints
}
hopHint, remoteBalance, include := shouldIncludeChannel(
cfg, channel, alreadyIncluded,
)
if include {
// Now that we now this channel use usable, add it as a hop
// hint and the indexes we'll use later.
hopHints = append(hopHints, []zpay32.HopHint{hopHint})
currentBandwidth += remoteBalance
nHintsLeft--
}
}
// We do not want to leak information about how our remote balance is
// distributed in our private channels. We shuffle the selected ones
// here so they do not appear in order in the invoice.
mathRand.Shuffle(
len(hopHints), func(i, j int) {
hopHints[i], hopHints[j] = hopHints[j], hopHints[i]
},
)
return hopHints
}
// PopulateHopHints will select up to cfg.MaxHophints from the current open
// channels. The set of hop hints will be returned as a slice of functional
// options that'll append the route hint to the set of all route hints.
//
// TODO(roasbeef): do proper sub-set sum max hints usually << numChans.
func PopulateHopHints(cfg *SelectHopHintsCfg, amtMSat lnwire.MilliSatoshi,
forcedHints [][]zpay32.HopHint) ([][]zpay32.HopHint, error) {
hopHints := forcedHints
// If we already have enough hints we don't need to add any more.
nHintsLeft := cfg.MaxHopHints - len(hopHints)
if nHintsLeft <= 0 {
return hopHints, nil
}
alreadyIncluded := make(map[uint64]bool)
for _, hopHint := range hopHints {
alreadyIncluded[hopHint[0].ChannelID] = true
}
potentialHints, err := getPotentialHints(cfg)
if err != nil {
return nil, err
}
targetBandwidth := amtMSat * hopHintFactor
selectedHints := selectHopHints(
cfg, nHintsLeft, targetBandwidth, potentialHints,
alreadyIncluded,
)
hopHints = append(hopHints, selectedHints...)
return hopHints, nil
}
package invoicesrpc
import (
"fmt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/lnwire"
)
// htlcModifier is a helper struct that handles the lifecycle of an RPC invoice
// HTLC modifier server instance.
//
// This struct handles passing send and receive RPC messages between the client
// and the invoice service.
type htlcModifier struct {
// chainParams is required to properly marshall an invoice for RPC.
chainParams *chaincfg.Params
// serverStream is a bidirectional RPC server stream to send invoices to
// a client and receive accept responses from the client.
serverStream Invoices_HtlcModifierServer
}
// newHtlcModifier creates a new RPC invoice HTLC modifier handler.
func newHtlcModifier(params *chaincfg.Params,
serverStream Invoices_HtlcModifierServer) *htlcModifier {
return &htlcModifier{
chainParams: params,
serverStream: serverStream,
}
}
// onIntercept is called when an invoice HTLC is intercepted by the invoice HTLC
// modifier. This method sends the invoice and the current HTLC to the client.
func (r *htlcModifier) onIntercept(
req invoices.HtlcModifyRequest) (*invoices.HtlcModifyResponse, error) {
// Convert the circuit key to an RPC circuit key.
rpcCircuitKey := &CircuitKey{
ChanId: req.ExitHtlcCircuitKey.ChanID.ToUint64(),
HtlcId: req.ExitHtlcCircuitKey.HtlcID,
}
// Convert the invoice to an RPC invoice.
rpcInvoice, err := CreateRPCInvoice(&req.Invoice, r.chainParams)
if err != nil {
return nil, err
}
// Send the modification request to the client.
err = r.serverStream.Send(&HtlcModifyRequest{
Invoice: rpcInvoice,
ExitHtlcCircuitKey: rpcCircuitKey,
ExitHtlcAmt: uint64(req.ExitHtlcAmt),
ExitHtlcExpiry: req.ExitHtlcExpiry,
CurrentHeight: req.CurrentHeight,
ExitHtlcWireCustomRecords: req.WireCustomRecords,
})
if err != nil {
return nil, err
}
// Then wait for the client to respond.
resp, err := r.serverStream.Recv()
if err != nil {
return nil, err
}
if resp.CircuitKey == nil {
return nil, fmt.Errorf("missing circuit key")
}
log.Tracef("Resolving invoice HTLC modifier response %v", resp)
// Pass the resolution to the modifier.
var amtPaid lnwire.MilliSatoshi
if resp.AmtPaid != nil {
amtPaid = lnwire.MilliSatoshi(*resp.AmtPaid)
}
return &invoices.HtlcModifyResponse{
AmountPaid: amtPaid,
CancelSet: resp.CancelSet,
}, nil
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v3.21.12
// source: invoicesrpc/invoices.proto
package invoicesrpc
import (
lnrpc "github.com/lightningnetwork/lnd/lnrpc"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type LookupModifier int32
const (
// The default look up modifier, no look up behavior is changed.
LookupModifier_DEFAULT LookupModifier = 0
// Indicates that when a look up is done based on a set_id, then only that set
// of HTLCs related to that set ID should be returned.
LookupModifier_HTLC_SET_ONLY LookupModifier = 1
// Indicates that when a look up is done using a payment_addr, then no HTLCs
// related to the payment_addr should be returned. This is useful when one
// wants to be able to obtain the set of associated setIDs with a given
// invoice, then look up the sub-invoices "projected" by that set ID.
LookupModifier_HTLC_SET_BLANK LookupModifier = 2
)
// Enum value maps for LookupModifier.
var (
LookupModifier_name = map[int32]string{
0: "DEFAULT",
1: "HTLC_SET_ONLY",
2: "HTLC_SET_BLANK",
}
LookupModifier_value = map[string]int32{
"DEFAULT": 0,
"HTLC_SET_ONLY": 1,
"HTLC_SET_BLANK": 2,
}
)
func (x LookupModifier) Enum() *LookupModifier {
p := new(LookupModifier)
*p = x
return p
}
func (x LookupModifier) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (LookupModifier) Descriptor() protoreflect.EnumDescriptor {
return file_invoicesrpc_invoices_proto_enumTypes[0].Descriptor()
}
func (LookupModifier) Type() protoreflect.EnumType {
return &file_invoicesrpc_invoices_proto_enumTypes[0]
}
func (x LookupModifier) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use LookupModifier.Descriptor instead.
func (LookupModifier) EnumDescriptor() ([]byte, []int) {
return file_invoicesrpc_invoices_proto_rawDescGZIP(), []int{0}
}
type CancelInvoiceMsg struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Hash corresponding to the (hold) invoice to cancel. When using
// REST, this field must be encoded as base64.
PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"`
}
func (x *CancelInvoiceMsg) Reset() {
*x = CancelInvoiceMsg{}
if protoimpl.UnsafeEnabled {
mi := &file_invoicesrpc_invoices_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CancelInvoiceMsg) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CancelInvoiceMsg) ProtoMessage() {}
func (x *CancelInvoiceMsg) ProtoReflect() protoreflect.Message {
mi := &file_invoicesrpc_invoices_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CancelInvoiceMsg.ProtoReflect.Descriptor instead.
func (*CancelInvoiceMsg) Descriptor() ([]byte, []int) {
return file_invoicesrpc_invoices_proto_rawDescGZIP(), []int{0}
}
func (x *CancelInvoiceMsg) GetPaymentHash() []byte {
if x != nil {
return x.PaymentHash
}
return nil
}
type CancelInvoiceResp struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *CancelInvoiceResp) Reset() {
*x = CancelInvoiceResp{}
if protoimpl.UnsafeEnabled {
mi := &file_invoicesrpc_invoices_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CancelInvoiceResp) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CancelInvoiceResp) ProtoMessage() {}
func (x *CancelInvoiceResp) ProtoReflect() protoreflect.Message {
mi := &file_invoicesrpc_invoices_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CancelInvoiceResp.ProtoReflect.Descriptor instead.
func (*CancelInvoiceResp) Descriptor() ([]byte, []int) {
return file_invoicesrpc_invoices_proto_rawDescGZIP(), []int{1}
}
type AddHoldInvoiceRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// An optional memo to attach along with the invoice. Used for record keeping
// purposes for the invoice's creator, and will also be set in the description
// field of the encoded payment request if the description_hash field is not
// being used.
Memo string `protobuf:"bytes,1,opt,name=memo,proto3" json:"memo,omitempty"`
// The hash of the preimage
Hash []byte `protobuf:"bytes,2,opt,name=hash,proto3" json:"hash,omitempty"`
// The value of this invoice in satoshis
//
// The fields value and value_msat are mutually exclusive.
Value int64 `protobuf:"varint,3,opt,name=value,proto3" json:"value,omitempty"`
// The value of this invoice in millisatoshis
//
// The fields value and value_msat are mutually exclusive.
ValueMsat int64 `protobuf:"varint,10,opt,name=value_msat,json=valueMsat,proto3" json:"value_msat,omitempty"`
// Hash (SHA-256) of a description of the payment. Used if the description of
// payment (memo) is too long to naturally fit within the description field
// of an encoded payment request.
DescriptionHash []byte `protobuf:"bytes,4,opt,name=description_hash,json=descriptionHash,proto3" json:"description_hash,omitempty"`
// Payment request expiry time in seconds. Default is 86400 (24 hours).
Expiry int64 `protobuf:"varint,5,opt,name=expiry,proto3" json:"expiry,omitempty"`
// Fallback on-chain address.
FallbackAddr string `protobuf:"bytes,6,opt,name=fallback_addr,json=fallbackAddr,proto3" json:"fallback_addr,omitempty"`
// Delta to use for the time-lock of the CLTV extended to the final hop.
CltvExpiry uint64 `protobuf:"varint,7,opt,name=cltv_expiry,json=cltvExpiry,proto3" json:"cltv_expiry,omitempty"`
// Route hints that can each be individually used to assist in reaching the
// invoice's destination.
RouteHints []*lnrpc.RouteHint `protobuf:"bytes,8,rep,name=route_hints,json=routeHints,proto3" json:"route_hints,omitempty"`
// Whether this invoice should include routing hints for private channels.
Private bool `protobuf:"varint,9,opt,name=private,proto3" json:"private,omitempty"`
}
func (x *AddHoldInvoiceRequest) Reset() {
*x = AddHoldInvoiceRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_invoicesrpc_invoices_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AddHoldInvoiceRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AddHoldInvoiceRequest) ProtoMessage() {}
func (x *AddHoldInvoiceRequest) ProtoReflect() protoreflect.Message {
mi := &file_invoicesrpc_invoices_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AddHoldInvoiceRequest.ProtoReflect.Descriptor instead.
func (*AddHoldInvoiceRequest) Descriptor() ([]byte, []int) {
return file_invoicesrpc_invoices_proto_rawDescGZIP(), []int{2}
}
func (x *AddHoldInvoiceRequest) GetMemo() string {
if x != nil {
return x.Memo
}
return ""
}
func (x *AddHoldInvoiceRequest) GetHash() []byte {
if x != nil {
return x.Hash
}
return nil
}
func (x *AddHoldInvoiceRequest) GetValue() int64 {
if x != nil {
return x.Value
}
return 0
}
func (x *AddHoldInvoiceRequest) GetValueMsat() int64 {
if x != nil {
return x.ValueMsat
}
return 0
}
func (x *AddHoldInvoiceRequest) GetDescriptionHash() []byte {
if x != nil {
return x.DescriptionHash
}
return nil
}
func (x *AddHoldInvoiceRequest) GetExpiry() int64 {
if x != nil {
return x.Expiry
}
return 0
}
func (x *AddHoldInvoiceRequest) GetFallbackAddr() string {
if x != nil {
return x.FallbackAddr
}
return ""
}
func (x *AddHoldInvoiceRequest) GetCltvExpiry() uint64 {
if x != nil {
return x.CltvExpiry
}
return 0
}
func (x *AddHoldInvoiceRequest) GetRouteHints() []*lnrpc.RouteHint {
if x != nil {
return x.RouteHints
}
return nil
}
func (x *AddHoldInvoiceRequest) GetPrivate() bool {
if x != nil {
return x.Private
}
return false
}
type AddHoldInvoiceResp struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A bare-bones invoice for a payment within the Lightning Network. With the
// details of the invoice, the sender has all the data necessary to send a
// payment to the recipient.
PaymentRequest string `protobuf:"bytes,1,opt,name=payment_request,json=paymentRequest,proto3" json:"payment_request,omitempty"`
// The "add" index of this invoice. Each newly created invoice will increment
// this index making it monotonically increasing. Callers to the
// SubscribeInvoices call can use this to instantly get notified of all added
// invoices with an add_index greater than this one.
AddIndex uint64 `protobuf:"varint,2,opt,name=add_index,json=addIndex,proto3" json:"add_index,omitempty"`
// The payment address of the generated invoice. This is also called
// the payment secret in specifications (e.g. BOLT 11). This value should
// be used in all payments for this invoice as we require it for end to end
// security.
PaymentAddr []byte `protobuf:"bytes,3,opt,name=payment_addr,json=paymentAddr,proto3" json:"payment_addr,omitempty"`
}
func (x *AddHoldInvoiceResp) Reset() {
*x = AddHoldInvoiceResp{}
if protoimpl.UnsafeEnabled {
mi := &file_invoicesrpc_invoices_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AddHoldInvoiceResp) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AddHoldInvoiceResp) ProtoMessage() {}
func (x *AddHoldInvoiceResp) ProtoReflect() protoreflect.Message {
mi := &file_invoicesrpc_invoices_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AddHoldInvoiceResp.ProtoReflect.Descriptor instead.
func (*AddHoldInvoiceResp) Descriptor() ([]byte, []int) {
return file_invoicesrpc_invoices_proto_rawDescGZIP(), []int{3}
}
func (x *AddHoldInvoiceResp) GetPaymentRequest() string {
if x != nil {
return x.PaymentRequest
}
return ""
}
func (x *AddHoldInvoiceResp) GetAddIndex() uint64 {
if x != nil {
return x.AddIndex
}
return 0
}
func (x *AddHoldInvoiceResp) GetPaymentAddr() []byte {
if x != nil {
return x.PaymentAddr
}
return nil
}
type SettleInvoiceMsg struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Externally discovered pre-image that should be used to settle the hold
// invoice.
Preimage []byte `protobuf:"bytes,1,opt,name=preimage,proto3" json:"preimage,omitempty"`
}
func (x *SettleInvoiceMsg) Reset() {
*x = SettleInvoiceMsg{}
if protoimpl.UnsafeEnabled {
mi := &file_invoicesrpc_invoices_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SettleInvoiceMsg) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SettleInvoiceMsg) ProtoMessage() {}
func (x *SettleInvoiceMsg) ProtoReflect() protoreflect.Message {
mi := &file_invoicesrpc_invoices_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SettleInvoiceMsg.ProtoReflect.Descriptor instead.
func (*SettleInvoiceMsg) Descriptor() ([]byte, []int) {
return file_invoicesrpc_invoices_proto_rawDescGZIP(), []int{4}
}
func (x *SettleInvoiceMsg) GetPreimage() []byte {
if x != nil {
return x.Preimage
}
return nil
}
type SettleInvoiceResp struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *SettleInvoiceResp) Reset() {
*x = SettleInvoiceResp{}
if protoimpl.UnsafeEnabled {
mi := &file_invoicesrpc_invoices_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SettleInvoiceResp) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SettleInvoiceResp) ProtoMessage() {}
func (x *SettleInvoiceResp) ProtoReflect() protoreflect.Message {
mi := &file_invoicesrpc_invoices_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SettleInvoiceResp.ProtoReflect.Descriptor instead.
func (*SettleInvoiceResp) Descriptor() ([]byte, []int) {
return file_invoicesrpc_invoices_proto_rawDescGZIP(), []int{5}
}
type SubscribeSingleInvoiceRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Hash corresponding to the (hold) invoice to subscribe to. When using
// REST, this field must be encoded as base64url.
RHash []byte `protobuf:"bytes,2,opt,name=r_hash,json=rHash,proto3" json:"r_hash,omitempty"`
}
func (x *SubscribeSingleInvoiceRequest) Reset() {
*x = SubscribeSingleInvoiceRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_invoicesrpc_invoices_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SubscribeSingleInvoiceRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SubscribeSingleInvoiceRequest) ProtoMessage() {}
func (x *SubscribeSingleInvoiceRequest) ProtoReflect() protoreflect.Message {
mi := &file_invoicesrpc_invoices_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SubscribeSingleInvoiceRequest.ProtoReflect.Descriptor instead.
func (*SubscribeSingleInvoiceRequest) Descriptor() ([]byte, []int) {
return file_invoicesrpc_invoices_proto_rawDescGZIP(), []int{6}
}
func (x *SubscribeSingleInvoiceRequest) GetRHash() []byte {
if x != nil {
return x.RHash
}
return nil
}
type LookupInvoiceMsg struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to InvoiceRef:
//
// *LookupInvoiceMsg_PaymentHash
// *LookupInvoiceMsg_PaymentAddr
// *LookupInvoiceMsg_SetId
InvoiceRef isLookupInvoiceMsg_InvoiceRef `protobuf_oneof:"invoice_ref"`
LookupModifier LookupModifier `protobuf:"varint,4,opt,name=lookup_modifier,json=lookupModifier,proto3,enum=invoicesrpc.LookupModifier" json:"lookup_modifier,omitempty"`
}
func (x *LookupInvoiceMsg) Reset() {
*x = LookupInvoiceMsg{}
if protoimpl.UnsafeEnabled {
mi := &file_invoicesrpc_invoices_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *LookupInvoiceMsg) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LookupInvoiceMsg) ProtoMessage() {}
func (x *LookupInvoiceMsg) ProtoReflect() protoreflect.Message {
mi := &file_invoicesrpc_invoices_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LookupInvoiceMsg.ProtoReflect.Descriptor instead.
func (*LookupInvoiceMsg) Descriptor() ([]byte, []int) {
return file_invoicesrpc_invoices_proto_rawDescGZIP(), []int{7}
}
func (m *LookupInvoiceMsg) GetInvoiceRef() isLookupInvoiceMsg_InvoiceRef {
if m != nil {
return m.InvoiceRef
}
return nil
}
func (x *LookupInvoiceMsg) GetPaymentHash() []byte {
if x, ok := x.GetInvoiceRef().(*LookupInvoiceMsg_PaymentHash); ok {
return x.PaymentHash
}
return nil
}
func (x *LookupInvoiceMsg) GetPaymentAddr() []byte {
if x, ok := x.GetInvoiceRef().(*LookupInvoiceMsg_PaymentAddr); ok {
return x.PaymentAddr
}
return nil
}
func (x *LookupInvoiceMsg) GetSetId() []byte {
if x, ok := x.GetInvoiceRef().(*LookupInvoiceMsg_SetId); ok {
return x.SetId
}
return nil
}
func (x *LookupInvoiceMsg) GetLookupModifier() LookupModifier {
if x != nil {
return x.LookupModifier
}
return LookupModifier_DEFAULT
}
type isLookupInvoiceMsg_InvoiceRef interface {
isLookupInvoiceMsg_InvoiceRef()
}
type LookupInvoiceMsg_PaymentHash struct {
// When using REST, this field must be encoded as base64.
PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3,oneof"`
}
type LookupInvoiceMsg_PaymentAddr struct {
PaymentAddr []byte `protobuf:"bytes,2,opt,name=payment_addr,json=paymentAddr,proto3,oneof"`
}
type LookupInvoiceMsg_SetId struct {
SetId []byte `protobuf:"bytes,3,opt,name=set_id,json=setId,proto3,oneof"`
}
func (*LookupInvoiceMsg_PaymentHash) isLookupInvoiceMsg_InvoiceRef() {}
func (*LookupInvoiceMsg_PaymentAddr) isLookupInvoiceMsg_InvoiceRef() {}
func (*LookupInvoiceMsg_SetId) isLookupInvoiceMsg_InvoiceRef() {}
// CircuitKey is a unique identifier for an HTLC.
type CircuitKey struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The id of the channel that the is part of this circuit.
ChanId uint64 `protobuf:"varint,1,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"`
// The index of the incoming htlc in the incoming channel.
HtlcId uint64 `protobuf:"varint,2,opt,name=htlc_id,json=htlcId,proto3" json:"htlc_id,omitempty"`
}
func (x *CircuitKey) Reset() {
*x = CircuitKey{}
if protoimpl.UnsafeEnabled {
mi := &file_invoicesrpc_invoices_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CircuitKey) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CircuitKey) ProtoMessage() {}
func (x *CircuitKey) ProtoReflect() protoreflect.Message {
mi := &file_invoicesrpc_invoices_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CircuitKey.ProtoReflect.Descriptor instead.
func (*CircuitKey) Descriptor() ([]byte, []int) {
return file_invoicesrpc_invoices_proto_rawDescGZIP(), []int{8}
}
func (x *CircuitKey) GetChanId() uint64 {
if x != nil {
return x.ChanId
}
return 0
}
func (x *CircuitKey) GetHtlcId() uint64 {
if x != nil {
return x.HtlcId
}
return 0
}
type HtlcModifyRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The invoice the intercepted HTLC is attempting to settle. The HTLCs in
// the invoice are only HTLCs that have already been accepted or settled,
// not including the current intercepted HTLC.
Invoice *lnrpc.Invoice `protobuf:"bytes,1,opt,name=invoice,proto3" json:"invoice,omitempty"`
// The unique identifier of the HTLC of this intercepted HTLC.
ExitHtlcCircuitKey *CircuitKey `protobuf:"bytes,2,opt,name=exit_htlc_circuit_key,json=exitHtlcCircuitKey,proto3" json:"exit_htlc_circuit_key,omitempty"`
// The amount in milli-satoshi that the exit HTLC is attempting to pay.
ExitHtlcAmt uint64 `protobuf:"varint,3,opt,name=exit_htlc_amt,json=exitHtlcAmt,proto3" json:"exit_htlc_amt,omitempty"`
// The absolute expiry height of the exit HTLC.
ExitHtlcExpiry uint32 `protobuf:"varint,4,opt,name=exit_htlc_expiry,json=exitHtlcExpiry,proto3" json:"exit_htlc_expiry,omitempty"`
// The current block height.
CurrentHeight uint32 `protobuf:"varint,5,opt,name=current_height,json=currentHeight,proto3" json:"current_height,omitempty"`
// The wire message custom records of the exit HTLC.
ExitHtlcWireCustomRecords map[uint64][]byte `protobuf:"bytes,6,rep,name=exit_htlc_wire_custom_records,json=exitHtlcWireCustomRecords,proto3" json:"exit_htlc_wire_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *HtlcModifyRequest) Reset() {
*x = HtlcModifyRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_invoicesrpc_invoices_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HtlcModifyRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HtlcModifyRequest) ProtoMessage() {}
func (x *HtlcModifyRequest) ProtoReflect() protoreflect.Message {
mi := &file_invoicesrpc_invoices_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HtlcModifyRequest.ProtoReflect.Descriptor instead.
func (*HtlcModifyRequest) Descriptor() ([]byte, []int) {
return file_invoicesrpc_invoices_proto_rawDescGZIP(), []int{9}
}
func (x *HtlcModifyRequest) GetInvoice() *lnrpc.Invoice {
if x != nil {
return x.Invoice
}
return nil
}
func (x *HtlcModifyRequest) GetExitHtlcCircuitKey() *CircuitKey {
if x != nil {
return x.ExitHtlcCircuitKey
}
return nil
}
func (x *HtlcModifyRequest) GetExitHtlcAmt() uint64 {
if x != nil {
return x.ExitHtlcAmt
}
return 0
}
func (x *HtlcModifyRequest) GetExitHtlcExpiry() uint32 {
if x != nil {
return x.ExitHtlcExpiry
}
return 0
}
func (x *HtlcModifyRequest) GetCurrentHeight() uint32 {
if x != nil {
return x.CurrentHeight
}
return 0
}
func (x *HtlcModifyRequest) GetExitHtlcWireCustomRecords() map[uint64][]byte {
if x != nil {
return x.ExitHtlcWireCustomRecords
}
return nil
}
type HtlcModifyResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The circuit key of the HTLC that the client wants to modify.
CircuitKey *CircuitKey `protobuf:"bytes,1,opt,name=circuit_key,json=circuitKey,proto3" json:"circuit_key,omitempty"`
// The modified amount in milli-satoshi that the exit HTLC is paying. This
// value can be different from the actual on-chain HTLC amount, in case the
// HTLC carries other valuable items, as can be the case with custom channel
// types.
AmtPaid *uint64 `protobuf:"varint,2,opt,name=amt_paid,json=amtPaid,proto3,oneof" json:"amt_paid,omitempty"`
// This flag indicates whether the HTLCs associated with the invoices should
// be cancelled. The interceptor client may set this field if some
// unexpected behavior is encountered. Setting this will ignore the amt_paid
// field.
CancelSet bool `protobuf:"varint,3,opt,name=cancel_set,json=cancelSet,proto3" json:"cancel_set,omitempty"`
}
func (x *HtlcModifyResponse) Reset() {
*x = HtlcModifyResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_invoicesrpc_invoices_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HtlcModifyResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HtlcModifyResponse) ProtoMessage() {}
func (x *HtlcModifyResponse) ProtoReflect() protoreflect.Message {
mi := &file_invoicesrpc_invoices_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HtlcModifyResponse.ProtoReflect.Descriptor instead.
func (*HtlcModifyResponse) Descriptor() ([]byte, []int) {
return file_invoicesrpc_invoices_proto_rawDescGZIP(), []int{10}
}
func (x *HtlcModifyResponse) GetCircuitKey() *CircuitKey {
if x != nil {
return x.CircuitKey
}
return nil
}
func (x *HtlcModifyResponse) GetAmtPaid() uint64 {
if x != nil && x.AmtPaid != nil {
return *x.AmtPaid
}
return 0
}
func (x *HtlcModifyResponse) GetCancelSet() bool {
if x != nil {
return x.CancelSet
}
return false
}
var File_invoicesrpc_invoices_proto protoreflect.FileDescriptor
var file_invoicesrpc_invoices_proto_rawDesc = []byte{
0x0a, 0x1a, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2f, 0x69, 0x6e,
0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x69, 0x6e,
0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x1a, 0x0f, 0x6c, 0x69, 0x67, 0x68, 0x74,
0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x35, 0x0a, 0x10, 0x43, 0x61,
0x6e, 0x63, 0x65, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x12, 0x21,
0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73,
0x68, 0x22, 0x13, 0x0a, 0x11, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69,
0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x22, 0xca, 0x02, 0x0a, 0x15, 0x41, 0x64, 0x64, 0x48, 0x6f,
0x6c, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
0x6d, 0x65, 0x6d, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1d,
0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01,
0x28, 0x03, 0x52, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x29, 0x0a,
0x10, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x61, 0x73,
0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,
0x74, 0x69, 0x6f, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69,
0x72, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79,
0x12, 0x23, 0x0a, 0x0d, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x61, 0x64, 0x64,
0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63,
0x6b, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78,
0x70, 0x69, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x63, 0x6c, 0x74, 0x76,
0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x31, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f,
0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x72,
0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69,
0x76, 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76,
0x61, 0x74, 0x65, 0x22, 0x7d, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x48, 0x6f, 0x6c, 0x64, 0x49, 0x6e,
0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x79,
0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18,
0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x61, 0x64, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12,
0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18,
0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x64,
0x64, 0x72, 0x22, 0x2e, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f,
0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61,
0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61,
0x67, 0x65, 0x22, 0x13, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f,
0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x22, 0x3c, 0x0a, 0x1d, 0x53, 0x75, 0x62, 0x73, 0x63,
0x72, 0x69, 0x62, 0x65, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x5f, 0x68, 0x61,
0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x48, 0x61, 0x73, 0x68, 0x4a,
0x04, 0x08, 0x01, 0x10, 0x02, 0x22, 0xca, 0x01, 0x0a, 0x10, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70,
0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x12, 0x23, 0x0a, 0x0c, 0x70, 0x61,
0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
0x48, 0x00, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12,
0x23, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74,
0x41, 0x64, 0x64, 0x72, 0x12, 0x17, 0x0a, 0x06, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03,
0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x05, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x44, 0x0a,
0x0f, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x5f, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x72,
0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65,
0x73, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x4d, 0x6f, 0x64, 0x69, 0x66,
0x69, 0x65, 0x72, 0x52, 0x0e, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x4d, 0x6f, 0x64, 0x69, 0x66,
0x69, 0x65, 0x72, 0x42, 0x0d, 0x0a, 0x0b, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x72,
0x65, 0x66, 0x22, 0x3e, 0x0a, 0x0a, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79,
0x12, 0x17, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x04, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x74, 0x6c,
0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x74, 0x6c, 0x63,
0x49, 0x64, 0x22, 0xcd, 0x03, 0x0a, 0x11, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x6f, 0x64, 0x69, 0x66,
0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x07, 0x69, 0x6e, 0x76, 0x6f,
0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x07, 0x69, 0x6e, 0x76, 0x6f, 0x69,
0x63, 0x65, 0x12, 0x4a, 0x0a, 0x15, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f,
0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x17, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e,
0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x12, 0x65, 0x78, 0x69, 0x74,
0x48, 0x74, 0x6c, 0x63, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x22,
0x0a, 0x0d, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x61, 0x6d, 0x74, 0x18,
0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x65, 0x78, 0x69, 0x74, 0x48, 0x74, 0x6c, 0x63, 0x41,
0x6d, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f,
0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x65, 0x78,
0x69, 0x74, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x25, 0x0a, 0x0e,
0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x69,
0x67, 0x68, 0x74, 0x12, 0x7f, 0x0a, 0x1d, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x68, 0x74, 0x6c, 0x63,
0x5f, 0x77, 0x69, 0x72, 0x65, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63,
0x6f, 0x72, 0x64, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x69, 0x6e, 0x76,
0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x6f, 0x64,
0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x45, 0x78, 0x69, 0x74, 0x48,
0x74, 0x6c, 0x63, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63,
0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x19, 0x65, 0x78, 0x69, 0x74, 0x48,
0x74, 0x6c, 0x63, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63,
0x6f, 0x72, 0x64, 0x73, 0x1a, 0x4c, 0x0a, 0x1e, 0x45, 0x78, 0x69, 0x74, 0x48, 0x74, 0x6c, 0x63,
0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64,
0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
0x38, 0x01, 0x22, 0x9a, 0x01, 0x0a, 0x12, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x6f, 0x64, 0x69, 0x66,
0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x0b, 0x63, 0x69, 0x72,
0x63, 0x75, 0x69, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17,
0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x69, 0x72,
0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x0a, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74,
0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x70, 0x61, 0x69, 0x64, 0x18,
0x02, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x07, 0x61, 0x6d, 0x74, 0x50, 0x61, 0x69, 0x64,
0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x5f, 0x73, 0x65,
0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x53,
0x65, 0x74, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x70, 0x61, 0x69, 0x64, 0x2a,
0x44, 0x0a, 0x0e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65,
0x72, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x11,
0x0a, 0x0d, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x54, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10,
0x01, 0x12, 0x12, 0x0a, 0x0e, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x54, 0x5f, 0x42, 0x4c,
0x41, 0x4e, 0x4b, 0x10, 0x02, 0x32, 0xf0, 0x03, 0x0a, 0x08, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63,
0x65, 0x73, 0x12, 0x56, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x53,
0x69, 0x6e, 0x67, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x2a, 0x2e, 0x69,
0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63,
0x72, 0x69, 0x62, 0x65, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x30, 0x01, 0x12, 0x4e, 0x0a, 0x0d, 0x43, 0x61,
0x6e, 0x63, 0x65, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x1d, 0x2e, 0x69, 0x6e,
0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c,
0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x1a, 0x1e, 0x2e, 0x69, 0x6e, 0x76,
0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x49,
0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x55, 0x0a, 0x0e, 0x41, 0x64,
0x64, 0x48, 0x6f, 0x6c, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x22, 0x2e, 0x69,
0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x48, 0x6f,
0x6c, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x1f, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x41,
0x64, 0x64, 0x48, 0x6f, 0x6c, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73,
0x70, 0x12, 0x4e, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69,
0x63, 0x65, 0x12, 0x1d, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63,
0x2e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x4d, 0x73,
0x67, 0x1a, 0x1e, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e,
0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73,
0x70, 0x12, 0x40, 0x0a, 0x0f, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69,
0x63, 0x65, 0x56, 0x32, 0x12, 0x1d, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72,
0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65,
0x4d, 0x73, 0x67, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f,
0x69, 0x63, 0x65, 0x12, 0x53, 0x0a, 0x0c, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x6f, 0x64, 0x69, 0x66,
0x69, 0x65, 0x72, 0x12, 0x1f, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70,
0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x1e, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72,
0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67,
0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2f, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_invoicesrpc_invoices_proto_rawDescOnce sync.Once
file_invoicesrpc_invoices_proto_rawDescData = file_invoicesrpc_invoices_proto_rawDesc
)
func file_invoicesrpc_invoices_proto_rawDescGZIP() []byte {
file_invoicesrpc_invoices_proto_rawDescOnce.Do(func() {
file_invoicesrpc_invoices_proto_rawDescData = protoimpl.X.CompressGZIP(file_invoicesrpc_invoices_proto_rawDescData)
})
return file_invoicesrpc_invoices_proto_rawDescData
}
var file_invoicesrpc_invoices_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_invoicesrpc_invoices_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
var file_invoicesrpc_invoices_proto_goTypes = []interface{}{
(LookupModifier)(0), // 0: invoicesrpc.LookupModifier
(*CancelInvoiceMsg)(nil), // 1: invoicesrpc.CancelInvoiceMsg
(*CancelInvoiceResp)(nil), // 2: invoicesrpc.CancelInvoiceResp
(*AddHoldInvoiceRequest)(nil), // 3: invoicesrpc.AddHoldInvoiceRequest
(*AddHoldInvoiceResp)(nil), // 4: invoicesrpc.AddHoldInvoiceResp
(*SettleInvoiceMsg)(nil), // 5: invoicesrpc.SettleInvoiceMsg
(*SettleInvoiceResp)(nil), // 6: invoicesrpc.SettleInvoiceResp
(*SubscribeSingleInvoiceRequest)(nil), // 7: invoicesrpc.SubscribeSingleInvoiceRequest
(*LookupInvoiceMsg)(nil), // 8: invoicesrpc.LookupInvoiceMsg
(*CircuitKey)(nil), // 9: invoicesrpc.CircuitKey
(*HtlcModifyRequest)(nil), // 10: invoicesrpc.HtlcModifyRequest
(*HtlcModifyResponse)(nil), // 11: invoicesrpc.HtlcModifyResponse
nil, // 12: invoicesrpc.HtlcModifyRequest.ExitHtlcWireCustomRecordsEntry
(*lnrpc.RouteHint)(nil), // 13: lnrpc.RouteHint
(*lnrpc.Invoice)(nil), // 14: lnrpc.Invoice
}
var file_invoicesrpc_invoices_proto_depIdxs = []int32{
13, // 0: invoicesrpc.AddHoldInvoiceRequest.route_hints:type_name -> lnrpc.RouteHint
0, // 1: invoicesrpc.LookupInvoiceMsg.lookup_modifier:type_name -> invoicesrpc.LookupModifier
14, // 2: invoicesrpc.HtlcModifyRequest.invoice:type_name -> lnrpc.Invoice
9, // 3: invoicesrpc.HtlcModifyRequest.exit_htlc_circuit_key:type_name -> invoicesrpc.CircuitKey
12, // 4: invoicesrpc.HtlcModifyRequest.exit_htlc_wire_custom_records:type_name -> invoicesrpc.HtlcModifyRequest.ExitHtlcWireCustomRecordsEntry
9, // 5: invoicesrpc.HtlcModifyResponse.circuit_key:type_name -> invoicesrpc.CircuitKey
7, // 6: invoicesrpc.Invoices.SubscribeSingleInvoice:input_type -> invoicesrpc.SubscribeSingleInvoiceRequest
1, // 7: invoicesrpc.Invoices.CancelInvoice:input_type -> invoicesrpc.CancelInvoiceMsg
3, // 8: invoicesrpc.Invoices.AddHoldInvoice:input_type -> invoicesrpc.AddHoldInvoiceRequest
5, // 9: invoicesrpc.Invoices.SettleInvoice:input_type -> invoicesrpc.SettleInvoiceMsg
8, // 10: invoicesrpc.Invoices.LookupInvoiceV2:input_type -> invoicesrpc.LookupInvoiceMsg
11, // 11: invoicesrpc.Invoices.HtlcModifier:input_type -> invoicesrpc.HtlcModifyResponse
14, // 12: invoicesrpc.Invoices.SubscribeSingleInvoice:output_type -> lnrpc.Invoice
2, // 13: invoicesrpc.Invoices.CancelInvoice:output_type -> invoicesrpc.CancelInvoiceResp
4, // 14: invoicesrpc.Invoices.AddHoldInvoice:output_type -> invoicesrpc.AddHoldInvoiceResp
6, // 15: invoicesrpc.Invoices.SettleInvoice:output_type -> invoicesrpc.SettleInvoiceResp
14, // 16: invoicesrpc.Invoices.LookupInvoiceV2:output_type -> lnrpc.Invoice
10, // 17: invoicesrpc.Invoices.HtlcModifier:output_type -> invoicesrpc.HtlcModifyRequest
12, // [12:18] is the sub-list for method output_type
6, // [6:12] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
}
func init() { file_invoicesrpc_invoices_proto_init() }
func file_invoicesrpc_invoices_proto_init() {
if File_invoicesrpc_invoices_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_invoicesrpc_invoices_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CancelInvoiceMsg); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_invoicesrpc_invoices_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CancelInvoiceResp); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_invoicesrpc_invoices_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AddHoldInvoiceRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_invoicesrpc_invoices_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AddHoldInvoiceResp); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_invoicesrpc_invoices_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SettleInvoiceMsg); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_invoicesrpc_invoices_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SettleInvoiceResp); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_invoicesrpc_invoices_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SubscribeSingleInvoiceRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_invoicesrpc_invoices_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*LookupInvoiceMsg); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_invoicesrpc_invoices_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CircuitKey); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_invoicesrpc_invoices_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HtlcModifyRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_invoicesrpc_invoices_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HtlcModifyResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_invoicesrpc_invoices_proto_msgTypes[7].OneofWrappers = []interface{}{
(*LookupInvoiceMsg_PaymentHash)(nil),
(*LookupInvoiceMsg_PaymentAddr)(nil),
(*LookupInvoiceMsg_SetId)(nil),
}
file_invoicesrpc_invoices_proto_msgTypes[10].OneofWrappers = []interface{}{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_invoicesrpc_invoices_proto_rawDesc,
NumEnums: 1,
NumMessages: 12,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_invoicesrpc_invoices_proto_goTypes,
DependencyIndexes: file_invoicesrpc_invoices_proto_depIdxs,
EnumInfos: file_invoicesrpc_invoices_proto_enumTypes,
MessageInfos: file_invoicesrpc_invoices_proto_msgTypes,
}.Build()
File_invoicesrpc_invoices_proto = out.File
file_invoicesrpc_invoices_proto_rawDesc = nil
file_invoicesrpc_invoices_proto_goTypes = nil
file_invoicesrpc_invoices_proto_depIdxs = nil
}
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: invoicesrpc/invoices.proto
/*
Package invoicesrpc is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package invoicesrpc
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
func request_Invoices_SubscribeSingleInvoice_0(ctx context.Context, marshaler runtime.Marshaler, client InvoicesClient, req *http.Request, pathParams map[string]string) (Invoices_SubscribeSingleInvoiceClient, runtime.ServerMetadata, error) {
var protoReq SubscribeSingleInvoiceRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["r_hash"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "r_hash")
}
protoReq.RHash, err = runtime.Bytes(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "r_hash", err)
}
stream, err := client.SubscribeSingleInvoice(ctx, &protoReq)
if err != nil {
return nil, metadata, err
}
header, err := stream.Header()
if err != nil {
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
func request_Invoices_CancelInvoice_0(ctx context.Context, marshaler runtime.Marshaler, client InvoicesClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CancelInvoiceMsg
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.CancelInvoice(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Invoices_CancelInvoice_0(ctx context.Context, marshaler runtime.Marshaler, server InvoicesServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CancelInvoiceMsg
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.CancelInvoice(ctx, &protoReq)
return msg, metadata, err
}
func request_Invoices_AddHoldInvoice_0(ctx context.Context, marshaler runtime.Marshaler, client InvoicesClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq AddHoldInvoiceRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.AddHoldInvoice(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Invoices_AddHoldInvoice_0(ctx context.Context, marshaler runtime.Marshaler, server InvoicesServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq AddHoldInvoiceRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.AddHoldInvoice(ctx, &protoReq)
return msg, metadata, err
}
func request_Invoices_SettleInvoice_0(ctx context.Context, marshaler runtime.Marshaler, client InvoicesClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SettleInvoiceMsg
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.SettleInvoice(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Invoices_SettleInvoice_0(ctx context.Context, marshaler runtime.Marshaler, server InvoicesServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SettleInvoiceMsg
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.SettleInvoice(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Invoices_LookupInvoiceV2_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Invoices_LookupInvoiceV2_0(ctx context.Context, marshaler runtime.Marshaler, client InvoicesClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq LookupInvoiceMsg
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Invoices_LookupInvoiceV2_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.LookupInvoiceV2(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Invoices_LookupInvoiceV2_0(ctx context.Context, marshaler runtime.Marshaler, server InvoicesServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq LookupInvoiceMsg
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Invoices_LookupInvoiceV2_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.LookupInvoiceV2(ctx, &protoReq)
return msg, metadata, err
}
func request_Invoices_HtlcModifier_0(ctx context.Context, marshaler runtime.Marshaler, client InvoicesClient, req *http.Request, pathParams map[string]string) (Invoices_HtlcModifierClient, runtime.ServerMetadata, error) {
var metadata runtime.ServerMetadata
stream, err := client.HtlcModifier(ctx)
if err != nil {
grpclog.Infof("Failed to start streaming: %v", err)
return nil, metadata, err
}
dec := marshaler.NewDecoder(req.Body)
handleSend := func() error {
var protoReq HtlcModifyResponse
err := dec.Decode(&protoReq)
if err == io.EOF {
return err
}
if err != nil {
grpclog.Infof("Failed to decode request: %v", err)
return err
}
if err := stream.Send(&protoReq); err != nil {
grpclog.Infof("Failed to send request: %v", err)
return err
}
return nil
}
go func() {
for {
if err := handleSend(); err != nil {
break
}
}
if err := stream.CloseSend(); err != nil {
grpclog.Infof("Failed to terminate client stream: %v", err)
}
}()
header, err := stream.Header()
if err != nil {
grpclog.Infof("Failed to get header from client: %v", err)
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
// RegisterInvoicesHandlerServer registers the http handlers for service Invoices to "mux".
// UnaryRPC :call InvoicesServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterInvoicesHandlerFromEndpoint instead.
func RegisterInvoicesHandlerServer(ctx context.Context, mux *runtime.ServeMux, server InvoicesServer) error {
mux.Handle("GET", pattern_Invoices_SubscribeSingleInvoice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
mux.Handle("POST", pattern_Invoices_CancelInvoice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/invoicesrpc.Invoices/CancelInvoice", runtime.WithHTTPPathPattern("/v2/invoices/cancel"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Invoices_CancelInvoice_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Invoices_CancelInvoice_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Invoices_AddHoldInvoice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/invoicesrpc.Invoices/AddHoldInvoice", runtime.WithHTTPPathPattern("/v2/invoices/hodl"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Invoices_AddHoldInvoice_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Invoices_AddHoldInvoice_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Invoices_SettleInvoice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/invoicesrpc.Invoices/SettleInvoice", runtime.WithHTTPPathPattern("/v2/invoices/settle"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Invoices_SettleInvoice_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Invoices_SettleInvoice_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Invoices_LookupInvoiceV2_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/invoicesrpc.Invoices/LookupInvoiceV2", runtime.WithHTTPPathPattern("/v2/invoices/lookup"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Invoices_LookupInvoiceV2_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Invoices_LookupInvoiceV2_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Invoices_HtlcModifier_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
return nil
}
// RegisterInvoicesHandlerFromEndpoint is same as RegisterInvoicesHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterInvoicesHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterInvoicesHandler(ctx, mux, conn)
}
// RegisterInvoicesHandler registers the http handlers for service Invoices to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterInvoicesHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterInvoicesHandlerClient(ctx, mux, NewInvoicesClient(conn))
}
// RegisterInvoicesHandlerClient registers the http handlers for service Invoices
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "InvoicesClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "InvoicesClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "InvoicesClient" to call the correct interceptors.
func RegisterInvoicesHandlerClient(ctx context.Context, mux *runtime.ServeMux, client InvoicesClient) error {
mux.Handle("GET", pattern_Invoices_SubscribeSingleInvoice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/invoicesrpc.Invoices/SubscribeSingleInvoice", runtime.WithHTTPPathPattern("/v2/invoices/subscribe/{r_hash}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Invoices_SubscribeSingleInvoice_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Invoices_SubscribeSingleInvoice_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Invoices_CancelInvoice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/invoicesrpc.Invoices/CancelInvoice", runtime.WithHTTPPathPattern("/v2/invoices/cancel"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Invoices_CancelInvoice_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Invoices_CancelInvoice_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Invoices_AddHoldInvoice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/invoicesrpc.Invoices/AddHoldInvoice", runtime.WithHTTPPathPattern("/v2/invoices/hodl"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Invoices_AddHoldInvoice_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Invoices_AddHoldInvoice_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Invoices_SettleInvoice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/invoicesrpc.Invoices/SettleInvoice", runtime.WithHTTPPathPattern("/v2/invoices/settle"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Invoices_SettleInvoice_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Invoices_SettleInvoice_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Invoices_LookupInvoiceV2_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/invoicesrpc.Invoices/LookupInvoiceV2", runtime.WithHTTPPathPattern("/v2/invoices/lookup"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Invoices_LookupInvoiceV2_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Invoices_LookupInvoiceV2_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Invoices_HtlcModifier_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/invoicesrpc.Invoices/HtlcModifier", runtime.WithHTTPPathPattern("/v2/invoices/htlcmodifier"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Invoices_HtlcModifier_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Invoices_HtlcModifier_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_Invoices_SubscribeSingleInvoice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v2", "invoices", "subscribe", "r_hash"}, ""))
pattern_Invoices_CancelInvoice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "invoices", "cancel"}, ""))
pattern_Invoices_AddHoldInvoice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "invoices", "hodl"}, ""))
pattern_Invoices_SettleInvoice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "invoices", "settle"}, ""))
pattern_Invoices_LookupInvoiceV2_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "invoices", "lookup"}, ""))
pattern_Invoices_HtlcModifier_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "invoices", "htlcmodifier"}, ""))
)
var (
forward_Invoices_SubscribeSingleInvoice_0 = runtime.ForwardResponseStream
forward_Invoices_CancelInvoice_0 = runtime.ForwardResponseMessage
forward_Invoices_AddHoldInvoice_0 = runtime.ForwardResponseMessage
forward_Invoices_SettleInvoice_0 = runtime.ForwardResponseMessage
forward_Invoices_LookupInvoiceV2_0 = runtime.ForwardResponseMessage
forward_Invoices_HtlcModifier_0 = runtime.ForwardResponseStream
)
// Code generated by falafel 0.9.2. DO NOT EDIT.
// source: invoices.proto
package invoicesrpc
import (
"context"
gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protojson"
)
func RegisterInvoicesJSONCallbacks(registry map[string]func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error))) {
marshaler := &gateway.JSONPb{
MarshalOptions: protojson.MarshalOptions{
UseProtoNames: true,
EmitUnpopulated: true,
},
}
registry["invoicesrpc.Invoices.SubscribeSingleInvoice"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SubscribeSingleInvoiceRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewInvoicesClient(conn)
stream, err := client.SubscribeSingleInvoice(ctx, req)
if err != nil {
callback("", err)
return
}
go func() {
for {
select {
case <-stream.Context().Done():
callback("", stream.Context().Err())
return
default:
}
resp, err := stream.Recv()
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}()
}
registry["invoicesrpc.Invoices.CancelInvoice"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &CancelInvoiceMsg{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewInvoicesClient(conn)
resp, err := client.CancelInvoice(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["invoicesrpc.Invoices.AddHoldInvoice"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &AddHoldInvoiceRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewInvoicesClient(conn)
resp, err := client.AddHoldInvoice(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["invoicesrpc.Invoices.SettleInvoice"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SettleInvoiceMsg{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewInvoicesClient(conn)
resp, err := client.SettleInvoice(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["invoicesrpc.Invoices.LookupInvoiceV2"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &LookupInvoiceMsg{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewInvoicesClient(conn)
resp, err := client.LookupInvoiceV2(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package invoicesrpc
import (
context "context"
lnrpc "github.com/lightningnetwork/lnd/lnrpc"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// InvoicesClient is the client API for Invoices service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type InvoicesClient interface {
// SubscribeSingleInvoice returns a uni-directional stream (server -> client)
// to notify the client of state transitions of the specified invoice.
// Initially the current invoice state is always sent out.
SubscribeSingleInvoice(ctx context.Context, in *SubscribeSingleInvoiceRequest, opts ...grpc.CallOption) (Invoices_SubscribeSingleInvoiceClient, error)
// lncli: `cancelinvoice`
// CancelInvoice cancels a currently open invoice. If the invoice is already
// canceled, this call will succeed. If the invoice is already settled, it will
// fail.
CancelInvoice(ctx context.Context, in *CancelInvoiceMsg, opts ...grpc.CallOption) (*CancelInvoiceResp, error)
// lncli: `addholdinvoice`
// AddHoldInvoice creates a hold invoice. It ties the invoice to the hash
// supplied in the request.
AddHoldInvoice(ctx context.Context, in *AddHoldInvoiceRequest, opts ...grpc.CallOption) (*AddHoldInvoiceResp, error)
// lncli: `settleinvoice`
// SettleInvoice settles an accepted invoice. If the invoice is already
// settled, this call will succeed.
SettleInvoice(ctx context.Context, in *SettleInvoiceMsg, opts ...grpc.CallOption) (*SettleInvoiceResp, error)
// LookupInvoiceV2 attempts to look up at invoice. An invoice can be referenced
// using either its payment hash, payment address, or set ID.
LookupInvoiceV2(ctx context.Context, in *LookupInvoiceMsg, opts ...grpc.CallOption) (*lnrpc.Invoice, error)
// HtlcModifier is a bidirectional streaming RPC that allows a client to
// intercept and modify the HTLCs that attempt to settle the given invoice. The
// server will send HTLCs of invoices to the client and the client can modify
// some aspects of the HTLC in order to pass the invoice acceptance tests.
HtlcModifier(ctx context.Context, opts ...grpc.CallOption) (Invoices_HtlcModifierClient, error)
}
type invoicesClient struct {
cc grpc.ClientConnInterface
}
func NewInvoicesClient(cc grpc.ClientConnInterface) InvoicesClient {
return &invoicesClient{cc}
}
func (c *invoicesClient) SubscribeSingleInvoice(ctx context.Context, in *SubscribeSingleInvoiceRequest, opts ...grpc.CallOption) (Invoices_SubscribeSingleInvoiceClient, error) {
stream, err := c.cc.NewStream(ctx, &Invoices_ServiceDesc.Streams[0], "/invoicesrpc.Invoices/SubscribeSingleInvoice", opts...)
if err != nil {
return nil, err
}
x := &invoicesSubscribeSingleInvoiceClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Invoices_SubscribeSingleInvoiceClient interface {
Recv() (*lnrpc.Invoice, error)
grpc.ClientStream
}
type invoicesSubscribeSingleInvoiceClient struct {
grpc.ClientStream
}
func (x *invoicesSubscribeSingleInvoiceClient) Recv() (*lnrpc.Invoice, error) {
m := new(lnrpc.Invoice)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *invoicesClient) CancelInvoice(ctx context.Context, in *CancelInvoiceMsg, opts ...grpc.CallOption) (*CancelInvoiceResp, error) {
out := new(CancelInvoiceResp)
err := c.cc.Invoke(ctx, "/invoicesrpc.Invoices/CancelInvoice", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *invoicesClient) AddHoldInvoice(ctx context.Context, in *AddHoldInvoiceRequest, opts ...grpc.CallOption) (*AddHoldInvoiceResp, error) {
out := new(AddHoldInvoiceResp)
err := c.cc.Invoke(ctx, "/invoicesrpc.Invoices/AddHoldInvoice", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *invoicesClient) SettleInvoice(ctx context.Context, in *SettleInvoiceMsg, opts ...grpc.CallOption) (*SettleInvoiceResp, error) {
out := new(SettleInvoiceResp)
err := c.cc.Invoke(ctx, "/invoicesrpc.Invoices/SettleInvoice", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *invoicesClient) LookupInvoiceV2(ctx context.Context, in *LookupInvoiceMsg, opts ...grpc.CallOption) (*lnrpc.Invoice, error) {
out := new(lnrpc.Invoice)
err := c.cc.Invoke(ctx, "/invoicesrpc.Invoices/LookupInvoiceV2", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *invoicesClient) HtlcModifier(ctx context.Context, opts ...grpc.CallOption) (Invoices_HtlcModifierClient, error) {
stream, err := c.cc.NewStream(ctx, &Invoices_ServiceDesc.Streams[1], "/invoicesrpc.Invoices/HtlcModifier", opts...)
if err != nil {
return nil, err
}
x := &invoicesHtlcModifierClient{stream}
return x, nil
}
type Invoices_HtlcModifierClient interface {
Send(*HtlcModifyResponse) error
Recv() (*HtlcModifyRequest, error)
grpc.ClientStream
}
type invoicesHtlcModifierClient struct {
grpc.ClientStream
}
func (x *invoicesHtlcModifierClient) Send(m *HtlcModifyResponse) error {
return x.ClientStream.SendMsg(m)
}
func (x *invoicesHtlcModifierClient) Recv() (*HtlcModifyRequest, error) {
m := new(HtlcModifyRequest)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// InvoicesServer is the server API for Invoices service.
// All implementations must embed UnimplementedInvoicesServer
// for forward compatibility
type InvoicesServer interface {
// SubscribeSingleInvoice returns a uni-directional stream (server -> client)
// to notify the client of state transitions of the specified invoice.
// Initially the current invoice state is always sent out.
SubscribeSingleInvoice(*SubscribeSingleInvoiceRequest, Invoices_SubscribeSingleInvoiceServer) error
// lncli: `cancelinvoice`
// CancelInvoice cancels a currently open invoice. If the invoice is already
// canceled, this call will succeed. If the invoice is already settled, it will
// fail.
CancelInvoice(context.Context, *CancelInvoiceMsg) (*CancelInvoiceResp, error)
// lncli: `addholdinvoice`
// AddHoldInvoice creates a hold invoice. It ties the invoice to the hash
// supplied in the request.
AddHoldInvoice(context.Context, *AddHoldInvoiceRequest) (*AddHoldInvoiceResp, error)
// lncli: `settleinvoice`
// SettleInvoice settles an accepted invoice. If the invoice is already
// settled, this call will succeed.
SettleInvoice(context.Context, *SettleInvoiceMsg) (*SettleInvoiceResp, error)
// LookupInvoiceV2 attempts to look up at invoice. An invoice can be referenced
// using either its payment hash, payment address, or set ID.
LookupInvoiceV2(context.Context, *LookupInvoiceMsg) (*lnrpc.Invoice, error)
// HtlcModifier is a bidirectional streaming RPC that allows a client to
// intercept and modify the HTLCs that attempt to settle the given invoice. The
// server will send HTLCs of invoices to the client and the client can modify
// some aspects of the HTLC in order to pass the invoice acceptance tests.
HtlcModifier(Invoices_HtlcModifierServer) error
mustEmbedUnimplementedInvoicesServer()
}
// UnimplementedInvoicesServer must be embedded to have forward compatible implementations.
type UnimplementedInvoicesServer struct {
}
func (UnimplementedInvoicesServer) SubscribeSingleInvoice(*SubscribeSingleInvoiceRequest, Invoices_SubscribeSingleInvoiceServer) error {
return status.Errorf(codes.Unimplemented, "method SubscribeSingleInvoice not implemented")
}
func (UnimplementedInvoicesServer) CancelInvoice(context.Context, *CancelInvoiceMsg) (*CancelInvoiceResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method CancelInvoice not implemented")
}
func (UnimplementedInvoicesServer) AddHoldInvoice(context.Context, *AddHoldInvoiceRequest) (*AddHoldInvoiceResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method AddHoldInvoice not implemented")
}
func (UnimplementedInvoicesServer) SettleInvoice(context.Context, *SettleInvoiceMsg) (*SettleInvoiceResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method SettleInvoice not implemented")
}
func (UnimplementedInvoicesServer) LookupInvoiceV2(context.Context, *LookupInvoiceMsg) (*lnrpc.Invoice, error) {
return nil, status.Errorf(codes.Unimplemented, "method LookupInvoiceV2 not implemented")
}
func (UnimplementedInvoicesServer) HtlcModifier(Invoices_HtlcModifierServer) error {
return status.Errorf(codes.Unimplemented, "method HtlcModifier not implemented")
}
func (UnimplementedInvoicesServer) mustEmbedUnimplementedInvoicesServer() {}
// UnsafeInvoicesServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to InvoicesServer will
// result in compilation errors.
type UnsafeInvoicesServer interface {
mustEmbedUnimplementedInvoicesServer()
}
func RegisterInvoicesServer(s grpc.ServiceRegistrar, srv InvoicesServer) {
s.RegisterService(&Invoices_ServiceDesc, srv)
}
func _Invoices_SubscribeSingleInvoice_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(SubscribeSingleInvoiceRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(InvoicesServer).SubscribeSingleInvoice(m, &invoicesSubscribeSingleInvoiceServer{stream})
}
type Invoices_SubscribeSingleInvoiceServer interface {
Send(*lnrpc.Invoice) error
grpc.ServerStream
}
type invoicesSubscribeSingleInvoiceServer struct {
grpc.ServerStream
}
func (x *invoicesSubscribeSingleInvoiceServer) Send(m *lnrpc.Invoice) error {
return x.ServerStream.SendMsg(m)
}
func _Invoices_CancelInvoice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CancelInvoiceMsg)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InvoicesServer).CancelInvoice(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/invoicesrpc.Invoices/CancelInvoice",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InvoicesServer).CancelInvoice(ctx, req.(*CancelInvoiceMsg))
}
return interceptor(ctx, in, info, handler)
}
func _Invoices_AddHoldInvoice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AddHoldInvoiceRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InvoicesServer).AddHoldInvoice(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/invoicesrpc.Invoices/AddHoldInvoice",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InvoicesServer).AddHoldInvoice(ctx, req.(*AddHoldInvoiceRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Invoices_SettleInvoice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SettleInvoiceMsg)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InvoicesServer).SettleInvoice(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/invoicesrpc.Invoices/SettleInvoice",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InvoicesServer).SettleInvoice(ctx, req.(*SettleInvoiceMsg))
}
return interceptor(ctx, in, info, handler)
}
func _Invoices_LookupInvoiceV2_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LookupInvoiceMsg)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InvoicesServer).LookupInvoiceV2(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/invoicesrpc.Invoices/LookupInvoiceV2",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InvoicesServer).LookupInvoiceV2(ctx, req.(*LookupInvoiceMsg))
}
return interceptor(ctx, in, info, handler)
}
func _Invoices_HtlcModifier_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(InvoicesServer).HtlcModifier(&invoicesHtlcModifierServer{stream})
}
type Invoices_HtlcModifierServer interface {
Send(*HtlcModifyRequest) error
Recv() (*HtlcModifyResponse, error)
grpc.ServerStream
}
type invoicesHtlcModifierServer struct {
grpc.ServerStream
}
func (x *invoicesHtlcModifierServer) Send(m *HtlcModifyRequest) error {
return x.ServerStream.SendMsg(m)
}
func (x *invoicesHtlcModifierServer) Recv() (*HtlcModifyResponse, error) {
m := new(HtlcModifyResponse)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// Invoices_ServiceDesc is the grpc.ServiceDesc for Invoices service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Invoices_ServiceDesc = grpc.ServiceDesc{
ServiceName: "invoicesrpc.Invoices",
HandlerType: (*InvoicesServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "CancelInvoice",
Handler: _Invoices_CancelInvoice_Handler,
},
{
MethodName: "AddHoldInvoice",
Handler: _Invoices_AddHoldInvoice_Handler,
},
{
MethodName: "SettleInvoice",
Handler: _Invoices_SettleInvoice_Handler,
},
{
MethodName: "LookupInvoiceV2",
Handler: _Invoices_LookupInvoiceV2_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "SubscribeSingleInvoice",
Handler: _Invoices_SubscribeSingleInvoice_Handler,
ServerStreams: true,
},
{
StreamName: "HtlcModifier",
Handler: _Invoices_HtlcModifier_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "invoicesrpc/invoices.proto",
}
package invoicesrpc
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This means the
// package will not perform any logging by default until the caller requests
// it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("IRPC", nil))
}
// DisableLog disables all library log output. Logging output is disabled by
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info. This
// should be used in preference to SetLogWriter if the caller is also using
// btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package invoicesrpc
import (
"cmp"
"encoding/hex"
"fmt"
"slices"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/zpay32"
)
// decodePayReq decodes the invoice payment request if present. This is needed,
// because not all information is stored in dedicated invoice fields. If there
// is no payment request present, a dummy request will be returned. This can
// happen with just-in-time inserted keysend invoices.
func decodePayReq(invoice *invoices.Invoice,
activeNetParams *chaincfg.Params) (*zpay32.Invoice, error) {
paymentRequest := string(invoice.PaymentRequest)
if paymentRequest == "" {
preimage := invoice.Terms.PaymentPreimage
if preimage == nil {
return &zpay32.Invoice{}, nil
}
hash := [32]byte(preimage.Hash())
return &zpay32.Invoice{
PaymentHash: &hash,
}, nil
}
var err error
decoded, err := zpay32.Decode(paymentRequest, activeNetParams)
if err != nil {
return nil, fmt.Errorf("unable to decode payment "+
"request: %v", err)
}
return decoded, nil
}
// CreateRPCInvoice creates an *lnrpc.Invoice from the *invoices.Invoice.
func CreateRPCInvoice(invoice *invoices.Invoice,
activeNetParams *chaincfg.Params) (*lnrpc.Invoice, error) {
decoded, err := decodePayReq(invoice, activeNetParams)
if err != nil {
return nil, err
}
var rHash []byte
if decoded.PaymentHash != nil {
rHash = decoded.PaymentHash[:]
}
var descHash []byte
if decoded.DescriptionHash != nil {
descHash = decoded.DescriptionHash[:]
}
fallbackAddr := ""
if decoded.FallbackAddr != nil {
fallbackAddr = decoded.FallbackAddr.String()
}
settleDate := int64(0)
if !invoice.SettleDate.IsZero() {
settleDate = invoice.SettleDate.Unix()
}
// Convert between the `lnrpc` and `routing` types.
routeHints := CreateRPCRouteHints(decoded.RouteHints)
preimage := invoice.Terms.PaymentPreimage
satAmt := invoice.Terms.Value.ToSatoshis()
satAmtPaid := invoice.AmtPaid.ToSatoshis()
isSettled := invoice.State == invoices.ContractSettled
var state lnrpc.Invoice_InvoiceState
switch invoice.State {
case invoices.ContractOpen:
state = lnrpc.Invoice_OPEN
case invoices.ContractSettled:
state = lnrpc.Invoice_SETTLED
case invoices.ContractCanceled:
state = lnrpc.Invoice_CANCELED
case invoices.ContractAccepted:
state = lnrpc.Invoice_ACCEPTED
default:
return nil, fmt.Errorf("unknown invoice state %v",
invoice.State)
}
rpcHtlcs := make([]*lnrpc.InvoiceHTLC, 0, len(invoice.Htlcs))
for key, htlc := range invoice.Htlcs {
var state lnrpc.InvoiceHTLCState
switch htlc.State {
case invoices.HtlcStateAccepted:
state = lnrpc.InvoiceHTLCState_ACCEPTED
case invoices.HtlcStateSettled:
state = lnrpc.InvoiceHTLCState_SETTLED
case invoices.HtlcStateCanceled:
state = lnrpc.InvoiceHTLCState_CANCELED
default:
return nil, fmt.Errorf("unknown state %v", htlc.State)
}
rpcHtlc := lnrpc.InvoiceHTLC{
ChanId: key.ChanID.ToUint64(),
HtlcIndex: key.HtlcID,
AcceptHeight: int32(htlc.AcceptHeight),
AcceptTime: htlc.AcceptTime.Unix(),
ExpiryHeight: int32(htlc.Expiry),
AmtMsat: uint64(htlc.Amt),
State: state,
CustomRecords: htlc.CustomRecords,
MppTotalAmtMsat: uint64(htlc.MppTotalAmt),
}
// The custom channel data is currently just the raw bytes of
// the encoded custom records.
customData, err := lnwire.CustomRecords(
htlc.CustomRecords,
).Serialize()
if err != nil {
return nil, err
}
rpcHtlc.CustomChannelData = customData
// Populate any fields relevant to AMP payments.
if htlc.AMP != nil {
rootShare := htlc.AMP.Record.RootShare()
setID := htlc.AMP.Record.SetID()
var preimage []byte
if htlc.AMP.Preimage != nil {
preimage = htlc.AMP.Preimage[:]
}
rpcHtlc.Amp = &lnrpc.AMP{
RootShare: rootShare[:],
SetId: setID[:],
ChildIndex: htlc.AMP.Record.ChildIndex(),
Hash: htlc.AMP.Hash[:],
Preimage: preimage,
}
}
// Only report resolved times if htlc is resolved.
if htlc.State != invoices.HtlcStateAccepted {
rpcHtlc.ResolveTime = htlc.ResolveTime.Unix()
}
rpcHtlcs = append(rpcHtlcs, &rpcHtlc)
}
// Perform an inplace sort of the HTLCs to ensure they are ordered.
slices.SortFunc(rpcHtlcs, func(i, j *lnrpc.InvoiceHTLC) int {
return cmp.Compare(i.HtlcIndex, j.HtlcIndex)
})
rpcInvoice := &lnrpc.Invoice{
Memo: string(invoice.Memo),
RHash: rHash,
Value: int64(satAmt),
ValueMsat: int64(invoice.Terms.Value),
CreationDate: invoice.CreationDate.Unix(),
SettleDate: settleDate,
Settled: isSettled,
PaymentRequest: string(invoice.PaymentRequest),
DescriptionHash: descHash,
Expiry: int64(invoice.Terms.Expiry.Seconds()),
CltvExpiry: uint64(invoice.Terms.FinalCltvDelta),
FallbackAddr: fallbackAddr,
RouteHints: routeHints,
AddIndex: invoice.AddIndex,
Private: len(routeHints) > 0,
SettleIndex: invoice.SettleIndex,
AmtPaidSat: int64(satAmtPaid),
AmtPaidMsat: int64(invoice.AmtPaid),
AmtPaid: int64(invoice.AmtPaid),
// This will be set to SETTLED if at least one of the AMP Sets
// is settled (see below).
State: state,
Htlcs: rpcHtlcs,
Features: CreateRPCFeatures(invoice.Terms.Features),
IsKeysend: invoice.IsKeysend(),
PaymentAddr: invoice.Terms.PaymentAddr[:],
IsAmp: invoice.IsAMP(),
IsBlinded: invoice.IsBlinded(),
}
rpcInvoice.AmpInvoiceState = make(map[string]*lnrpc.AMPInvoiceState)
for setID, ampState := range invoice.AMPState {
setIDStr := hex.EncodeToString(setID[:])
var state lnrpc.InvoiceHTLCState
switch ampState.State {
case invoices.HtlcStateAccepted:
state = lnrpc.InvoiceHTLCState_ACCEPTED
case invoices.HtlcStateSettled:
state = lnrpc.InvoiceHTLCState_SETTLED
case invoices.HtlcStateCanceled:
state = lnrpc.InvoiceHTLCState_CANCELED
default:
return nil, fmt.Errorf("unknown state %v", ampState.State)
}
rpcInvoice.AmpInvoiceState[setIDStr] = &lnrpc.AMPInvoiceState{
State: state,
SettleIndex: ampState.SettleIndex,
SettleTime: ampState.SettleDate.Unix(),
AmtPaidMsat: int64(ampState.AmtPaid),
}
// If at least one of the present HTLC sets show up as being
// settled, then we'll mark the invoice itself as being
// settled.
if ampState.State == invoices.HtlcStateSettled {
rpcInvoice.Settled = true // nolint:staticcheck
rpcInvoice.State = lnrpc.Invoice_SETTLED
}
}
if preimage != nil {
rpcInvoice.RPreimage = preimage[:]
}
return rpcInvoice, nil
}
// CreateRPCFeatures maps a feature vector into a list of lnrpc.Features.
func CreateRPCFeatures(fv *lnwire.FeatureVector) map[uint32]*lnrpc.Feature {
if fv == nil {
return nil
}
features := fv.Features()
rpcFeatures := make(map[uint32]*lnrpc.Feature, len(features))
for bit := range features {
rpcFeatures[uint32(bit)] = &lnrpc.Feature{
Name: fv.Name(bit),
IsRequired: bit.IsRequired(),
IsKnown: fv.IsKnown(bit),
}
}
return rpcFeatures
}
// CreateRPCRouteHints takes in the decoded form of an invoice's route hints
// and converts them into the lnrpc type.
func CreateRPCRouteHints(routeHints [][]zpay32.HopHint) []*lnrpc.RouteHint {
var res []*lnrpc.RouteHint
for _, route := range routeHints {
hopHints := make([]*lnrpc.HopHint, 0, len(route))
for _, hop := range route {
pubKey := hex.EncodeToString(
hop.NodeID.SerializeCompressed(),
)
hint := &lnrpc.HopHint{
NodeId: pubKey,
ChanId: hop.ChannelID,
FeeBaseMsat: hop.FeeBaseMSat,
FeeProportionalMillionths: hop.FeeProportionalMillionths,
CltvExpiryDelta: uint32(hop.CLTVExpiryDelta),
}
hopHints = append(hopHints, hint)
}
routeHint := &lnrpc.RouteHint{HopHints: hopHints}
res = append(res, routeHint)
}
return res
}
// CreateRPCBlindedPayments takes a set of zpay32.BlindedPaymentPath and
// converts them into a set of lnrpc.BlindedPaymentPaths.
func CreateRPCBlindedPayments(blindedPaths []*zpay32.BlindedPaymentPath) (
[]*lnrpc.BlindedPaymentPath, error) {
var res []*lnrpc.BlindedPaymentPath
for _, path := range blindedPaths {
features := path.Features.Features()
var featuresSlice []lnrpc.FeatureBit
for feature := range features {
featuresSlice = append(
featuresSlice, lnrpc.FeatureBit(feature),
)
}
if len(path.Hops) == 0 {
return nil, fmt.Errorf("each blinded path must " +
"contain at least one hop")
}
var hops []*lnrpc.BlindedHop
for _, hop := range path.Hops {
blindedNodeID := hop.BlindedNodePub.
SerializeCompressed()
hops = append(hops, &lnrpc.BlindedHop{
BlindedNode: blindedNodeID,
EncryptedData: hop.CipherText,
})
}
introNode := path.Hops[0].BlindedNodePub
firstBlindingPoint := path.FirstEphemeralBlindingPoint
blindedPath := &lnrpc.BlindedPath{
IntroductionNode: introNode.SerializeCompressed(),
BlindingPoint: firstBlindingPoint.
SerializeCompressed(),
BlindedHops: hops,
}
res = append(res, &lnrpc.BlindedPaymentPath{
BlindedPath: blindedPath,
BaseFeeMsat: uint64(path.FeeBaseMsat),
ProportionalFeeRate: path.FeeRate,
TotalCltvDelta: uint32(path.CltvExpiryDelta),
HtlcMinMsat: path.HTLCMinMsat,
HtlcMaxMsat: path.HTLCMaxMsat,
Features: featuresSlice,
})
}
return res, nil
}
// CreateZpay32HopHints takes in the lnrpc form of route hints and converts them
// into an invoice decoded form.
func CreateZpay32HopHints(routeHints []*lnrpc.RouteHint) ([][]zpay32.HopHint, error) {
var res [][]zpay32.HopHint
for _, route := range routeHints {
hopHints := make([]zpay32.HopHint, 0, len(route.HopHints))
for _, hop := range route.HopHints {
pubKeyBytes, err := hex.DecodeString(hop.NodeId)
if err != nil {
return nil, err
}
p, err := btcec.ParsePubKey(pubKeyBytes)
if err != nil {
return nil, err
}
hopHints = append(hopHints, zpay32.HopHint{
NodeID: p,
ChannelID: hop.ChanId,
FeeBaseMSat: hop.FeeBaseMsat,
FeeProportionalMillionths: hop.FeeProportionalMillionths,
CLTVExpiryDelta: uint16(hop.CltvExpiryDelta),
})
}
res = append(res, hopHints)
}
return res, nil
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v3.21.12
// source: lightning.proto
package lnrpc
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type OutputScriptType int32
const (
OutputScriptType_SCRIPT_TYPE_PUBKEY_HASH OutputScriptType = 0
OutputScriptType_SCRIPT_TYPE_SCRIPT_HASH OutputScriptType = 1
OutputScriptType_SCRIPT_TYPE_WITNESS_V0_PUBKEY_HASH OutputScriptType = 2
OutputScriptType_SCRIPT_TYPE_WITNESS_V0_SCRIPT_HASH OutputScriptType = 3
OutputScriptType_SCRIPT_TYPE_PUBKEY OutputScriptType = 4
OutputScriptType_SCRIPT_TYPE_MULTISIG OutputScriptType = 5
OutputScriptType_SCRIPT_TYPE_NULLDATA OutputScriptType = 6
OutputScriptType_SCRIPT_TYPE_NON_STANDARD OutputScriptType = 7
OutputScriptType_SCRIPT_TYPE_WITNESS_UNKNOWN OutputScriptType = 8
OutputScriptType_SCRIPT_TYPE_WITNESS_V1_TAPROOT OutputScriptType = 9
)
// Enum value maps for OutputScriptType.
var (
OutputScriptType_name = map[int32]string{
0: "SCRIPT_TYPE_PUBKEY_HASH",
1: "SCRIPT_TYPE_SCRIPT_HASH",
2: "SCRIPT_TYPE_WITNESS_V0_PUBKEY_HASH",
3: "SCRIPT_TYPE_WITNESS_V0_SCRIPT_HASH",
4: "SCRIPT_TYPE_PUBKEY",
5: "SCRIPT_TYPE_MULTISIG",
6: "SCRIPT_TYPE_NULLDATA",
7: "SCRIPT_TYPE_NON_STANDARD",
8: "SCRIPT_TYPE_WITNESS_UNKNOWN",
9: "SCRIPT_TYPE_WITNESS_V1_TAPROOT",
}
OutputScriptType_value = map[string]int32{
"SCRIPT_TYPE_PUBKEY_HASH": 0,
"SCRIPT_TYPE_SCRIPT_HASH": 1,
"SCRIPT_TYPE_WITNESS_V0_PUBKEY_HASH": 2,
"SCRIPT_TYPE_WITNESS_V0_SCRIPT_HASH": 3,
"SCRIPT_TYPE_PUBKEY": 4,
"SCRIPT_TYPE_MULTISIG": 5,
"SCRIPT_TYPE_NULLDATA": 6,
"SCRIPT_TYPE_NON_STANDARD": 7,
"SCRIPT_TYPE_WITNESS_UNKNOWN": 8,
"SCRIPT_TYPE_WITNESS_V1_TAPROOT": 9,
}
)
func (x OutputScriptType) Enum() *OutputScriptType {
p := new(OutputScriptType)
*p = x
return p
}
func (x OutputScriptType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (OutputScriptType) Descriptor() protoreflect.EnumDescriptor {
return file_lightning_proto_enumTypes[0].Descriptor()
}
func (OutputScriptType) Type() protoreflect.EnumType {
return &file_lightning_proto_enumTypes[0]
}
func (x OutputScriptType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use OutputScriptType.Descriptor instead.
func (OutputScriptType) EnumDescriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{0}
}
type CoinSelectionStrategy int32
const (
// Use the coin selection strategy defined in the global configuration
// (lnd.conf).
CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG CoinSelectionStrategy = 0
// Select the largest available coins first during coin selection.
CoinSelectionStrategy_STRATEGY_LARGEST CoinSelectionStrategy = 1
// Randomly select the available coins during coin selection.
CoinSelectionStrategy_STRATEGY_RANDOM CoinSelectionStrategy = 2
)
// Enum value maps for CoinSelectionStrategy.
var (
CoinSelectionStrategy_name = map[int32]string{
0: "STRATEGY_USE_GLOBAL_CONFIG",
1: "STRATEGY_LARGEST",
2: "STRATEGY_RANDOM",
}
CoinSelectionStrategy_value = map[string]int32{
"STRATEGY_USE_GLOBAL_CONFIG": 0,
"STRATEGY_LARGEST": 1,
"STRATEGY_RANDOM": 2,
}
)
func (x CoinSelectionStrategy) Enum() *CoinSelectionStrategy {
p := new(CoinSelectionStrategy)
*p = x
return p
}
func (x CoinSelectionStrategy) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (CoinSelectionStrategy) Descriptor() protoreflect.EnumDescriptor {
return file_lightning_proto_enumTypes[1].Descriptor()
}
func (CoinSelectionStrategy) Type() protoreflect.EnumType {
return &file_lightning_proto_enumTypes[1]
}
func (x CoinSelectionStrategy) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use CoinSelectionStrategy.Descriptor instead.
func (CoinSelectionStrategy) EnumDescriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{1}
}
// `AddressType` has to be one of:
//
// - `p2wkh`: Pay to witness key hash (`WITNESS_PUBKEY_HASH` = 0)
// - `np2wkh`: Pay to nested witness key hash (`NESTED_PUBKEY_HASH` = 1)
// - `p2tr`: Pay to taproot pubkey (`TAPROOT_PUBKEY` = 4)
type AddressType int32
const (
AddressType_WITNESS_PUBKEY_HASH AddressType = 0
AddressType_NESTED_PUBKEY_HASH AddressType = 1
AddressType_UNUSED_WITNESS_PUBKEY_HASH AddressType = 2
AddressType_UNUSED_NESTED_PUBKEY_HASH AddressType = 3
AddressType_TAPROOT_PUBKEY AddressType = 4
AddressType_UNUSED_TAPROOT_PUBKEY AddressType = 5
)
// Enum value maps for AddressType.
var (
AddressType_name = map[int32]string{
0: "WITNESS_PUBKEY_HASH",
1: "NESTED_PUBKEY_HASH",
2: "UNUSED_WITNESS_PUBKEY_HASH",
3: "UNUSED_NESTED_PUBKEY_HASH",
4: "TAPROOT_PUBKEY",
5: "UNUSED_TAPROOT_PUBKEY",
}
AddressType_value = map[string]int32{
"WITNESS_PUBKEY_HASH": 0,
"NESTED_PUBKEY_HASH": 1,
"UNUSED_WITNESS_PUBKEY_HASH": 2,
"UNUSED_NESTED_PUBKEY_HASH": 3,
"TAPROOT_PUBKEY": 4,
"UNUSED_TAPROOT_PUBKEY": 5,
}
)
func (x AddressType) Enum() *AddressType {
p := new(AddressType)
*p = x
return p
}
func (x AddressType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (AddressType) Descriptor() protoreflect.EnumDescriptor {
return file_lightning_proto_enumTypes[2].Descriptor()
}
func (AddressType) Type() protoreflect.EnumType {
return &file_lightning_proto_enumTypes[2]
}
func (x AddressType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use AddressType.Descriptor instead.
func (AddressType) EnumDescriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{2}
}
type CommitmentType int32
const (
// Returned when the commitment type isn't known or unavailable.
CommitmentType_UNKNOWN_COMMITMENT_TYPE CommitmentType = 0
// A channel using the legacy commitment format having tweaked to_remote
// keys.
CommitmentType_LEGACY CommitmentType = 1
// A channel that uses the modern commitment format where the key in the
// output of the remote party does not change each state. This makes back
// up and recovery easier as when the channel is closed, the funds go
// directly to that key.
CommitmentType_STATIC_REMOTE_KEY CommitmentType = 2
// A channel that uses a commitment format that has anchor outputs on the
// commitments, allowing fee bumping after a force close transaction has
// been broadcast.
CommitmentType_ANCHORS CommitmentType = 3
// A channel that uses a commitment type that builds upon the anchors
// commitment format, but in addition requires a CLTV clause to spend outputs
// paying to the channel initiator. This is intended for use on leased channels
// to guarantee that the channel initiator has no incentives to close a leased
// channel before its maturity date.
CommitmentType_SCRIPT_ENFORCED_LEASE CommitmentType = 4
// A channel that uses musig2 for the funding output, and the new tapscript
// features where relevant.
CommitmentType_SIMPLE_TAPROOT CommitmentType = 5
// Identical to the SIMPLE_TAPROOT channel type, but with extra functionality.
// This channel type also commits to additional meta data in the tapscript
// leaves for the scripts in a channel.
CommitmentType_SIMPLE_TAPROOT_OVERLAY CommitmentType = 6
)
// Enum value maps for CommitmentType.
var (
CommitmentType_name = map[int32]string{
0: "UNKNOWN_COMMITMENT_TYPE",
1: "LEGACY",
2: "STATIC_REMOTE_KEY",
3: "ANCHORS",
4: "SCRIPT_ENFORCED_LEASE",
5: "SIMPLE_TAPROOT",
6: "SIMPLE_TAPROOT_OVERLAY",
}
CommitmentType_value = map[string]int32{
"UNKNOWN_COMMITMENT_TYPE": 0,
"LEGACY": 1,
"STATIC_REMOTE_KEY": 2,
"ANCHORS": 3,
"SCRIPT_ENFORCED_LEASE": 4,
"SIMPLE_TAPROOT": 5,
"SIMPLE_TAPROOT_OVERLAY": 6,
}
)
func (x CommitmentType) Enum() *CommitmentType {
p := new(CommitmentType)
*p = x
return p
}
func (x CommitmentType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (CommitmentType) Descriptor() protoreflect.EnumDescriptor {
return file_lightning_proto_enumTypes[3].Descriptor()
}
func (CommitmentType) Type() protoreflect.EnumType {
return &file_lightning_proto_enumTypes[3]
}
func (x CommitmentType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use CommitmentType.Descriptor instead.
func (CommitmentType) EnumDescriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{3}
}
type Initiator int32
const (
Initiator_INITIATOR_UNKNOWN Initiator = 0
Initiator_INITIATOR_LOCAL Initiator = 1
Initiator_INITIATOR_REMOTE Initiator = 2
Initiator_INITIATOR_BOTH Initiator = 3
)
// Enum value maps for Initiator.
var (
Initiator_name = map[int32]string{
0: "INITIATOR_UNKNOWN",
1: "INITIATOR_LOCAL",
2: "INITIATOR_REMOTE",
3: "INITIATOR_BOTH",
}
Initiator_value = map[string]int32{
"INITIATOR_UNKNOWN": 0,
"INITIATOR_LOCAL": 1,
"INITIATOR_REMOTE": 2,
"INITIATOR_BOTH": 3,
}
)
func (x Initiator) Enum() *Initiator {
p := new(Initiator)
*p = x
return p
}
func (x Initiator) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (Initiator) Descriptor() protoreflect.EnumDescriptor {
return file_lightning_proto_enumTypes[4].Descriptor()
}
func (Initiator) Type() protoreflect.EnumType {
return &file_lightning_proto_enumTypes[4]
}
func (x Initiator) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use Initiator.Descriptor instead.
func (Initiator) EnumDescriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{4}
}
type ResolutionType int32
const (
ResolutionType_TYPE_UNKNOWN ResolutionType = 0
// We resolved an anchor output.
ResolutionType_ANCHOR ResolutionType = 1
// We are resolving an incoming htlc on chain. This if this htlc is
// claimed, we swept the incoming htlc with the preimage. If it is timed
// out, our peer swept the timeout path.
ResolutionType_INCOMING_HTLC ResolutionType = 2
// We are resolving an outgoing htlc on chain. If this htlc is claimed,
// the remote party swept the htlc with the preimage. If it is timed out,
// we swept it with the timeout path.
ResolutionType_OUTGOING_HTLC ResolutionType = 3
// We force closed and need to sweep our time locked commitment output.
ResolutionType_COMMIT ResolutionType = 4
)
// Enum value maps for ResolutionType.
var (
ResolutionType_name = map[int32]string{
0: "TYPE_UNKNOWN",
1: "ANCHOR",
2: "INCOMING_HTLC",
3: "OUTGOING_HTLC",
4: "COMMIT",
}
ResolutionType_value = map[string]int32{
"TYPE_UNKNOWN": 0,
"ANCHOR": 1,
"INCOMING_HTLC": 2,
"OUTGOING_HTLC": 3,
"COMMIT": 4,
}
)
func (x ResolutionType) Enum() *ResolutionType {
p := new(ResolutionType)
*p = x
return p
}
func (x ResolutionType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (ResolutionType) Descriptor() protoreflect.EnumDescriptor {
return file_lightning_proto_enumTypes[5].Descriptor()
}
func (ResolutionType) Type() protoreflect.EnumType {
return &file_lightning_proto_enumTypes[5]
}
func (x ResolutionType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use ResolutionType.Descriptor instead.
func (ResolutionType) EnumDescriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{5}
}
type ResolutionOutcome int32
const (
// Outcome unknown.
ResolutionOutcome_OUTCOME_UNKNOWN ResolutionOutcome = 0
// An output was claimed on chain.
ResolutionOutcome_CLAIMED ResolutionOutcome = 1
// An output was left unclaimed on chain.
ResolutionOutcome_UNCLAIMED ResolutionOutcome = 2
// ResolverOutcomeAbandoned indicates that an output that we did not
// claim on chain, for example an anchor that we did not sweep and a
// third party claimed on chain, or a htlc that we could not decode
// so left unclaimed.
ResolutionOutcome_ABANDONED ResolutionOutcome = 3
// If we force closed our channel, our htlcs need to be claimed in two
// stages. This outcome represents the broadcast of a timeout or success
// transaction for this two stage htlc claim.
ResolutionOutcome_FIRST_STAGE ResolutionOutcome = 4
// A htlc was timed out on chain.
ResolutionOutcome_TIMEOUT ResolutionOutcome = 5
)
// Enum value maps for ResolutionOutcome.
var (
ResolutionOutcome_name = map[int32]string{
0: "OUTCOME_UNKNOWN",
1: "CLAIMED",
2: "UNCLAIMED",
3: "ABANDONED",
4: "FIRST_STAGE",
5: "TIMEOUT",
}
ResolutionOutcome_value = map[string]int32{
"OUTCOME_UNKNOWN": 0,
"CLAIMED": 1,
"UNCLAIMED": 2,
"ABANDONED": 3,
"FIRST_STAGE": 4,
"TIMEOUT": 5,
}
)
func (x ResolutionOutcome) Enum() *ResolutionOutcome {
p := new(ResolutionOutcome)
*p = x
return p
}
func (x ResolutionOutcome) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (ResolutionOutcome) Descriptor() protoreflect.EnumDescriptor {
return file_lightning_proto_enumTypes[6].Descriptor()
}
func (ResolutionOutcome) Type() protoreflect.EnumType {
return &file_lightning_proto_enumTypes[6]
}
func (x ResolutionOutcome) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use ResolutionOutcome.Descriptor instead.
func (ResolutionOutcome) EnumDescriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{6}
}
type NodeMetricType int32
const (
NodeMetricType_UNKNOWN NodeMetricType = 0
NodeMetricType_BETWEENNESS_CENTRALITY NodeMetricType = 1
)
// Enum value maps for NodeMetricType.
var (
NodeMetricType_name = map[int32]string{
0: "UNKNOWN",
1: "BETWEENNESS_CENTRALITY",
}
NodeMetricType_value = map[string]int32{
"UNKNOWN": 0,
"BETWEENNESS_CENTRALITY": 1,
}
)
func (x NodeMetricType) Enum() *NodeMetricType {
p := new(NodeMetricType)
*p = x
return p
}
func (x NodeMetricType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (NodeMetricType) Descriptor() protoreflect.EnumDescriptor {
return file_lightning_proto_enumTypes[7].Descriptor()
}
func (NodeMetricType) Type() protoreflect.EnumType {
return &file_lightning_proto_enumTypes[7]
}
func (x NodeMetricType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use NodeMetricType.Descriptor instead.
func (NodeMetricType) EnumDescriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{7}
}
type InvoiceHTLCState int32
const (
InvoiceHTLCState_ACCEPTED InvoiceHTLCState = 0
InvoiceHTLCState_SETTLED InvoiceHTLCState = 1
InvoiceHTLCState_CANCELED InvoiceHTLCState = 2
)
// Enum value maps for InvoiceHTLCState.
var (
InvoiceHTLCState_name = map[int32]string{
0: "ACCEPTED",
1: "SETTLED",
2: "CANCELED",
}
InvoiceHTLCState_value = map[string]int32{
"ACCEPTED": 0,
"SETTLED": 1,
"CANCELED": 2,
}
)
func (x InvoiceHTLCState) Enum() *InvoiceHTLCState {
p := new(InvoiceHTLCState)
*p = x
return p
}
func (x InvoiceHTLCState) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (InvoiceHTLCState) Descriptor() protoreflect.EnumDescriptor {
return file_lightning_proto_enumTypes[8].Descriptor()
}
func (InvoiceHTLCState) Type() protoreflect.EnumType {
return &file_lightning_proto_enumTypes[8]
}
func (x InvoiceHTLCState) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use InvoiceHTLCState.Descriptor instead.
func (InvoiceHTLCState) EnumDescriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{8}
}
type PaymentFailureReason int32
const (
// Payment isn't failed (yet).
PaymentFailureReason_FAILURE_REASON_NONE PaymentFailureReason = 0
// There are more routes to try, but the payment timeout was exceeded.
PaymentFailureReason_FAILURE_REASON_TIMEOUT PaymentFailureReason = 1
// All possible routes were tried and failed permanently. Or were no
// routes to the destination at all.
PaymentFailureReason_FAILURE_REASON_NO_ROUTE PaymentFailureReason = 2
// A non-recoverable error has occured.
PaymentFailureReason_FAILURE_REASON_ERROR PaymentFailureReason = 3
// Payment details incorrect (unknown hash, invalid amt or
// invalid final cltv delta)
PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS PaymentFailureReason = 4
// Insufficient local balance.
PaymentFailureReason_FAILURE_REASON_INSUFFICIENT_BALANCE PaymentFailureReason = 5
// The payment was canceled.
PaymentFailureReason_FAILURE_REASON_CANCELED PaymentFailureReason = 6
)
// Enum value maps for PaymentFailureReason.
var (
PaymentFailureReason_name = map[int32]string{
0: "FAILURE_REASON_NONE",
1: "FAILURE_REASON_TIMEOUT",
2: "FAILURE_REASON_NO_ROUTE",
3: "FAILURE_REASON_ERROR",
4: "FAILURE_REASON_INCORRECT_PAYMENT_DETAILS",
5: "FAILURE_REASON_INSUFFICIENT_BALANCE",
6: "FAILURE_REASON_CANCELED",
}
PaymentFailureReason_value = map[string]int32{
"FAILURE_REASON_NONE": 0,
"FAILURE_REASON_TIMEOUT": 1,
"FAILURE_REASON_NO_ROUTE": 2,
"FAILURE_REASON_ERROR": 3,
"FAILURE_REASON_INCORRECT_PAYMENT_DETAILS": 4,
"FAILURE_REASON_INSUFFICIENT_BALANCE": 5,
"FAILURE_REASON_CANCELED": 6,
}
)
func (x PaymentFailureReason) Enum() *PaymentFailureReason {
p := new(PaymentFailureReason)
*p = x
return p
}
func (x PaymentFailureReason) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (PaymentFailureReason) Descriptor() protoreflect.EnumDescriptor {
return file_lightning_proto_enumTypes[9].Descriptor()
}
func (PaymentFailureReason) Type() protoreflect.EnumType {
return &file_lightning_proto_enumTypes[9]
}
func (x PaymentFailureReason) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use PaymentFailureReason.Descriptor instead.
func (PaymentFailureReason) EnumDescriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{9}
}
type FeatureBit int32
const (
FeatureBit_DATALOSS_PROTECT_REQ FeatureBit = 0
FeatureBit_DATALOSS_PROTECT_OPT FeatureBit = 1
FeatureBit_INITIAL_ROUING_SYNC FeatureBit = 3
FeatureBit_UPFRONT_SHUTDOWN_SCRIPT_REQ FeatureBit = 4
FeatureBit_UPFRONT_SHUTDOWN_SCRIPT_OPT FeatureBit = 5
FeatureBit_GOSSIP_QUERIES_REQ FeatureBit = 6
FeatureBit_GOSSIP_QUERIES_OPT FeatureBit = 7
FeatureBit_TLV_ONION_REQ FeatureBit = 8
FeatureBit_TLV_ONION_OPT FeatureBit = 9
FeatureBit_EXT_GOSSIP_QUERIES_REQ FeatureBit = 10
FeatureBit_EXT_GOSSIP_QUERIES_OPT FeatureBit = 11
FeatureBit_STATIC_REMOTE_KEY_REQ FeatureBit = 12
FeatureBit_STATIC_REMOTE_KEY_OPT FeatureBit = 13
FeatureBit_PAYMENT_ADDR_REQ FeatureBit = 14
FeatureBit_PAYMENT_ADDR_OPT FeatureBit = 15
FeatureBit_MPP_REQ FeatureBit = 16
FeatureBit_MPP_OPT FeatureBit = 17
FeatureBit_WUMBO_CHANNELS_REQ FeatureBit = 18
FeatureBit_WUMBO_CHANNELS_OPT FeatureBit = 19
FeatureBit_ANCHORS_REQ FeatureBit = 20
FeatureBit_ANCHORS_OPT FeatureBit = 21
FeatureBit_ANCHORS_ZERO_FEE_HTLC_REQ FeatureBit = 22
FeatureBit_ANCHORS_ZERO_FEE_HTLC_OPT FeatureBit = 23
FeatureBit_ROUTE_BLINDING_REQUIRED FeatureBit = 24
FeatureBit_ROUTE_BLINDING_OPTIONAL FeatureBit = 25
FeatureBit_AMP_REQ FeatureBit = 30
FeatureBit_AMP_OPT FeatureBit = 31
)
// Enum value maps for FeatureBit.
var (
FeatureBit_name = map[int32]string{
0: "DATALOSS_PROTECT_REQ",
1: "DATALOSS_PROTECT_OPT",
3: "INITIAL_ROUING_SYNC",
4: "UPFRONT_SHUTDOWN_SCRIPT_REQ",
5: "UPFRONT_SHUTDOWN_SCRIPT_OPT",
6: "GOSSIP_QUERIES_REQ",
7: "GOSSIP_QUERIES_OPT",
8: "TLV_ONION_REQ",
9: "TLV_ONION_OPT",
10: "EXT_GOSSIP_QUERIES_REQ",
11: "EXT_GOSSIP_QUERIES_OPT",
12: "STATIC_REMOTE_KEY_REQ",
13: "STATIC_REMOTE_KEY_OPT",
14: "PAYMENT_ADDR_REQ",
15: "PAYMENT_ADDR_OPT",
16: "MPP_REQ",
17: "MPP_OPT",
18: "WUMBO_CHANNELS_REQ",
19: "WUMBO_CHANNELS_OPT",
20: "ANCHORS_REQ",
21: "ANCHORS_OPT",
22: "ANCHORS_ZERO_FEE_HTLC_REQ",
23: "ANCHORS_ZERO_FEE_HTLC_OPT",
24: "ROUTE_BLINDING_REQUIRED",
25: "ROUTE_BLINDING_OPTIONAL",
30: "AMP_REQ",
31: "AMP_OPT",
}
FeatureBit_value = map[string]int32{
"DATALOSS_PROTECT_REQ": 0,
"DATALOSS_PROTECT_OPT": 1,
"INITIAL_ROUING_SYNC": 3,
"UPFRONT_SHUTDOWN_SCRIPT_REQ": 4,
"UPFRONT_SHUTDOWN_SCRIPT_OPT": 5,
"GOSSIP_QUERIES_REQ": 6,
"GOSSIP_QUERIES_OPT": 7,
"TLV_ONION_REQ": 8,
"TLV_ONION_OPT": 9,
"EXT_GOSSIP_QUERIES_REQ": 10,
"EXT_GOSSIP_QUERIES_OPT": 11,
"STATIC_REMOTE_KEY_REQ": 12,
"STATIC_REMOTE_KEY_OPT": 13,
"PAYMENT_ADDR_REQ": 14,
"PAYMENT_ADDR_OPT": 15,
"MPP_REQ": 16,
"MPP_OPT": 17,
"WUMBO_CHANNELS_REQ": 18,
"WUMBO_CHANNELS_OPT": 19,
"ANCHORS_REQ": 20,
"ANCHORS_OPT": 21,
"ANCHORS_ZERO_FEE_HTLC_REQ": 22,
"ANCHORS_ZERO_FEE_HTLC_OPT": 23,
"ROUTE_BLINDING_REQUIRED": 24,
"ROUTE_BLINDING_OPTIONAL": 25,
"AMP_REQ": 30,
"AMP_OPT": 31,
}
)
func (x FeatureBit) Enum() *FeatureBit {
p := new(FeatureBit)
*p = x
return p
}
func (x FeatureBit) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (FeatureBit) Descriptor() protoreflect.EnumDescriptor {
return file_lightning_proto_enumTypes[10].Descriptor()
}
func (FeatureBit) Type() protoreflect.EnumType {
return &file_lightning_proto_enumTypes[10]
}
func (x FeatureBit) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use FeatureBit.Descriptor instead.
func (FeatureBit) EnumDescriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{10}
}
type UpdateFailure int32
const (
UpdateFailure_UPDATE_FAILURE_UNKNOWN UpdateFailure = 0
UpdateFailure_UPDATE_FAILURE_PENDING UpdateFailure = 1
UpdateFailure_UPDATE_FAILURE_NOT_FOUND UpdateFailure = 2
UpdateFailure_UPDATE_FAILURE_INTERNAL_ERR UpdateFailure = 3
UpdateFailure_UPDATE_FAILURE_INVALID_PARAMETER UpdateFailure = 4
)
// Enum value maps for UpdateFailure.
var (
UpdateFailure_name = map[int32]string{
0: "UPDATE_FAILURE_UNKNOWN",
1: "UPDATE_FAILURE_PENDING",
2: "UPDATE_FAILURE_NOT_FOUND",
3: "UPDATE_FAILURE_INTERNAL_ERR",
4: "UPDATE_FAILURE_INVALID_PARAMETER",
}
UpdateFailure_value = map[string]int32{
"UPDATE_FAILURE_UNKNOWN": 0,
"UPDATE_FAILURE_PENDING": 1,
"UPDATE_FAILURE_NOT_FOUND": 2,
"UPDATE_FAILURE_INTERNAL_ERR": 3,
"UPDATE_FAILURE_INVALID_PARAMETER": 4,
}
)
func (x UpdateFailure) Enum() *UpdateFailure {
p := new(UpdateFailure)
*p = x
return p
}
func (x UpdateFailure) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (UpdateFailure) Descriptor() protoreflect.EnumDescriptor {
return file_lightning_proto_enumTypes[11].Descriptor()
}
func (UpdateFailure) Type() protoreflect.EnumType {
return &file_lightning_proto_enumTypes[11]
}
func (x UpdateFailure) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use UpdateFailure.Descriptor instead.
func (UpdateFailure) EnumDescriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{11}
}
type ChannelCloseSummary_ClosureType int32
const (
ChannelCloseSummary_COOPERATIVE_CLOSE ChannelCloseSummary_ClosureType = 0
ChannelCloseSummary_LOCAL_FORCE_CLOSE ChannelCloseSummary_ClosureType = 1
ChannelCloseSummary_REMOTE_FORCE_CLOSE ChannelCloseSummary_ClosureType = 2
ChannelCloseSummary_BREACH_CLOSE ChannelCloseSummary_ClosureType = 3
ChannelCloseSummary_FUNDING_CANCELED ChannelCloseSummary_ClosureType = 4
ChannelCloseSummary_ABANDONED ChannelCloseSummary_ClosureType = 5
)
// Enum value maps for ChannelCloseSummary_ClosureType.
var (
ChannelCloseSummary_ClosureType_name = map[int32]string{
0: "COOPERATIVE_CLOSE",
1: "LOCAL_FORCE_CLOSE",
2: "REMOTE_FORCE_CLOSE",
3: "BREACH_CLOSE",
4: "FUNDING_CANCELED",
5: "ABANDONED",
}
ChannelCloseSummary_ClosureType_value = map[string]int32{
"COOPERATIVE_CLOSE": 0,
"LOCAL_FORCE_CLOSE": 1,
"REMOTE_FORCE_CLOSE": 2,
"BREACH_CLOSE": 3,
"FUNDING_CANCELED": 4,
"ABANDONED": 5,
}
)
func (x ChannelCloseSummary_ClosureType) Enum() *ChannelCloseSummary_ClosureType {
p := new(ChannelCloseSummary_ClosureType)
*p = x
return p
}
func (x ChannelCloseSummary_ClosureType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (ChannelCloseSummary_ClosureType) Descriptor() protoreflect.EnumDescriptor {
return file_lightning_proto_enumTypes[12].Descriptor()
}
func (ChannelCloseSummary_ClosureType) Type() protoreflect.EnumType {
return &file_lightning_proto_enumTypes[12]
}
func (x ChannelCloseSummary_ClosureType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use ChannelCloseSummary_ClosureType.Descriptor instead.
func (ChannelCloseSummary_ClosureType) EnumDescriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{47, 0}
}
type Peer_SyncType int32
const (
// Denotes that we cannot determine the peer's current sync type.
Peer_UNKNOWN_SYNC Peer_SyncType = 0
// Denotes that we are actively receiving new graph updates from the peer.
Peer_ACTIVE_SYNC Peer_SyncType = 1
// Denotes that we are not receiving new graph updates from the peer.
Peer_PASSIVE_SYNC Peer_SyncType = 2
// Denotes that this peer is pinned into an active sync.
Peer_PINNED_SYNC Peer_SyncType = 3
)
// Enum value maps for Peer_SyncType.
var (
Peer_SyncType_name = map[int32]string{
0: "UNKNOWN_SYNC",
1: "ACTIVE_SYNC",
2: "PASSIVE_SYNC",
3: "PINNED_SYNC",
}
Peer_SyncType_value = map[string]int32{
"UNKNOWN_SYNC": 0,
"ACTIVE_SYNC": 1,
"PASSIVE_SYNC": 2,
"PINNED_SYNC": 3,
}
)
func (x Peer_SyncType) Enum() *Peer_SyncType {
p := new(Peer_SyncType)
*p = x
return p
}
func (x Peer_SyncType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (Peer_SyncType) Descriptor() protoreflect.EnumDescriptor {
return file_lightning_proto_enumTypes[13].Descriptor()
}
func (Peer_SyncType) Type() protoreflect.EnumType {
return &file_lightning_proto_enumTypes[13]
}
func (x Peer_SyncType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use Peer_SyncType.Descriptor instead.
func (Peer_SyncType) EnumDescriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{51, 0}
}
type PeerEvent_EventType int32
const (
PeerEvent_PEER_ONLINE PeerEvent_EventType = 0
PeerEvent_PEER_OFFLINE PeerEvent_EventType = 1
)
// Enum value maps for PeerEvent_EventType.
var (
PeerEvent_EventType_name = map[int32]string{
0: "PEER_ONLINE",
1: "PEER_OFFLINE",
}
PeerEvent_EventType_value = map[string]int32{
"PEER_ONLINE": 0,
"PEER_OFFLINE": 1,
}
)
func (x PeerEvent_EventType) Enum() *PeerEvent_EventType {
p := new(PeerEvent_EventType)
*p = x
return p
}
func (x PeerEvent_EventType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (PeerEvent_EventType) Descriptor() protoreflect.EnumDescriptor {
return file_lightning_proto_enumTypes[14].Descriptor()
}
func (PeerEvent_EventType) Type() protoreflect.EnumType {
return &file_lightning_proto_enumTypes[14]
}
func (x PeerEvent_EventType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use PeerEvent_EventType.Descriptor instead.
func (PeerEvent_EventType) EnumDescriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{56, 0}
}
// There are three resolution states for the anchor:
// limbo, lost and recovered. Derive the current state
// from the limbo and recovered balances.
type PendingChannelsResponse_ForceClosedChannel_AnchorState int32
const (
// The recovered_balance is zero and limbo_balance is non-zero.
PendingChannelsResponse_ForceClosedChannel_LIMBO PendingChannelsResponse_ForceClosedChannel_AnchorState = 0
// The recovered_balance is non-zero.
PendingChannelsResponse_ForceClosedChannel_RECOVERED PendingChannelsResponse_ForceClosedChannel_AnchorState = 1
// A state that is neither LIMBO nor RECOVERED.
PendingChannelsResponse_ForceClosedChannel_LOST PendingChannelsResponse_ForceClosedChannel_AnchorState = 2
)
// Enum value maps for PendingChannelsResponse_ForceClosedChannel_AnchorState.
var (
PendingChannelsResponse_ForceClosedChannel_AnchorState_name = map[int32]string{
0: "LIMBO",
1: "RECOVERED",
2: "LOST",
}
PendingChannelsResponse_ForceClosedChannel_AnchorState_value = map[string]int32{
"LIMBO": 0,
"RECOVERED": 1,
"LOST": 2,
}
)
func (x PendingChannelsResponse_ForceClosedChannel_AnchorState) Enum() *PendingChannelsResponse_ForceClosedChannel_AnchorState {
p := new(PendingChannelsResponse_ForceClosedChannel_AnchorState)
*p = x
return p
}
func (x PendingChannelsResponse_ForceClosedChannel_AnchorState) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (PendingChannelsResponse_ForceClosedChannel_AnchorState) Descriptor() protoreflect.EnumDescriptor {
return file_lightning_proto_enumTypes[15].Descriptor()
}
func (PendingChannelsResponse_ForceClosedChannel_AnchorState) Type() protoreflect.EnumType {
return &file_lightning_proto_enumTypes[15]
}
func (x PendingChannelsResponse_ForceClosedChannel_AnchorState) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use PendingChannelsResponse_ForceClosedChannel_AnchorState.Descriptor instead.
func (PendingChannelsResponse_ForceClosedChannel_AnchorState) EnumDescriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{90, 5, 0}
}
type ChannelEventUpdate_UpdateType int32
const (
ChannelEventUpdate_OPEN_CHANNEL ChannelEventUpdate_UpdateType = 0
ChannelEventUpdate_CLOSED_CHANNEL ChannelEventUpdate_UpdateType = 1
ChannelEventUpdate_ACTIVE_CHANNEL ChannelEventUpdate_UpdateType = 2
ChannelEventUpdate_INACTIVE_CHANNEL ChannelEventUpdate_UpdateType = 3
ChannelEventUpdate_PENDING_OPEN_CHANNEL ChannelEventUpdate_UpdateType = 4
ChannelEventUpdate_FULLY_RESOLVED_CHANNEL ChannelEventUpdate_UpdateType = 5
)
// Enum value maps for ChannelEventUpdate_UpdateType.
var (
ChannelEventUpdate_UpdateType_name = map[int32]string{
0: "OPEN_CHANNEL",
1: "CLOSED_CHANNEL",
2: "ACTIVE_CHANNEL",
3: "INACTIVE_CHANNEL",
4: "PENDING_OPEN_CHANNEL",
5: "FULLY_RESOLVED_CHANNEL",
}
ChannelEventUpdate_UpdateType_value = map[string]int32{
"OPEN_CHANNEL": 0,
"CLOSED_CHANNEL": 1,
"ACTIVE_CHANNEL": 2,
"INACTIVE_CHANNEL": 3,
"PENDING_OPEN_CHANNEL": 4,
"FULLY_RESOLVED_CHANNEL": 5,
}
)
func (x ChannelEventUpdate_UpdateType) Enum() *ChannelEventUpdate_UpdateType {
p := new(ChannelEventUpdate_UpdateType)
*p = x
return p
}
func (x ChannelEventUpdate_UpdateType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (ChannelEventUpdate_UpdateType) Descriptor() protoreflect.EnumDescriptor {
return file_lightning_proto_enumTypes[16].Descriptor()
}
func (ChannelEventUpdate_UpdateType) Type() protoreflect.EnumType {
return &file_lightning_proto_enumTypes[16]
}
func (x ChannelEventUpdate_UpdateType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use ChannelEventUpdate_UpdateType.Descriptor instead.
func (ChannelEventUpdate_UpdateType) EnumDescriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{92, 0}
}
type Invoice_InvoiceState int32
const (
Invoice_OPEN Invoice_InvoiceState = 0
Invoice_SETTLED Invoice_InvoiceState = 1
Invoice_CANCELED Invoice_InvoiceState = 2
Invoice_ACCEPTED Invoice_InvoiceState = 3
)
// Enum value maps for Invoice_InvoiceState.
var (
Invoice_InvoiceState_name = map[int32]string{
0: "OPEN",
1: "SETTLED",
2: "CANCELED",
3: "ACCEPTED",
}
Invoice_InvoiceState_value = map[string]int32{
"OPEN": 0,
"SETTLED": 1,
"CANCELED": 2,
"ACCEPTED": 3,
}
)
func (x Invoice_InvoiceState) Enum() *Invoice_InvoiceState {
p := new(Invoice_InvoiceState)
*p = x
return p
}
func (x Invoice_InvoiceState) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (Invoice_InvoiceState) Descriptor() protoreflect.EnumDescriptor {
return file_lightning_proto_enumTypes[17].Descriptor()
}
func (Invoice_InvoiceState) Type() protoreflect.EnumType {
return &file_lightning_proto_enumTypes[17]
}
func (x Invoice_InvoiceState) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use Invoice_InvoiceState.Descriptor instead.
func (Invoice_InvoiceState) EnumDescriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{135, 0}
}
type Payment_PaymentStatus int32
const (
// Deprecated. This status will never be returned.
//
// Deprecated: Marked as deprecated in lightning.proto.
Payment_UNKNOWN Payment_PaymentStatus = 0
// Payment has inflight HTLCs.
Payment_IN_FLIGHT Payment_PaymentStatus = 1
// Payment is settled.
Payment_SUCCEEDED Payment_PaymentStatus = 2
// Payment is failed.
Payment_FAILED Payment_PaymentStatus = 3
// Payment is created and has not attempted any HTLCs.
Payment_INITIATED Payment_PaymentStatus = 4
)
// Enum value maps for Payment_PaymentStatus.
var (
Payment_PaymentStatus_name = map[int32]string{
0: "UNKNOWN",
1: "IN_FLIGHT",
2: "SUCCEEDED",
3: "FAILED",
4: "INITIATED",
}
Payment_PaymentStatus_value = map[string]int32{
"UNKNOWN": 0,
"IN_FLIGHT": 1,
"SUCCEEDED": 2,
"FAILED": 3,
"INITIATED": 4,
}
)
func (x Payment_PaymentStatus) Enum() *Payment_PaymentStatus {
p := new(Payment_PaymentStatus)
*p = x
return p
}
func (x Payment_PaymentStatus) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (Payment_PaymentStatus) Descriptor() protoreflect.EnumDescriptor {
return file_lightning_proto_enumTypes[18].Descriptor()
}
func (Payment_PaymentStatus) Type() protoreflect.EnumType {
return &file_lightning_proto_enumTypes[18]
}
func (x Payment_PaymentStatus) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use Payment_PaymentStatus.Descriptor instead.
func (Payment_PaymentStatus) EnumDescriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{144, 0}
}
type HTLCAttempt_HTLCStatus int32
const (
HTLCAttempt_IN_FLIGHT HTLCAttempt_HTLCStatus = 0
HTLCAttempt_SUCCEEDED HTLCAttempt_HTLCStatus = 1
HTLCAttempt_FAILED HTLCAttempt_HTLCStatus = 2
)
// Enum value maps for HTLCAttempt_HTLCStatus.
var (
HTLCAttempt_HTLCStatus_name = map[int32]string{
0: "IN_FLIGHT",
1: "SUCCEEDED",
2: "FAILED",
}
HTLCAttempt_HTLCStatus_value = map[string]int32{
"IN_FLIGHT": 0,
"SUCCEEDED": 1,
"FAILED": 2,
}
)
func (x HTLCAttempt_HTLCStatus) Enum() *HTLCAttempt_HTLCStatus {
p := new(HTLCAttempt_HTLCStatus)
*p = x
return p
}
func (x HTLCAttempt_HTLCStatus) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (HTLCAttempt_HTLCStatus) Descriptor() protoreflect.EnumDescriptor {
return file_lightning_proto_enumTypes[19].Descriptor()
}
func (HTLCAttempt_HTLCStatus) Type() protoreflect.EnumType {
return &file_lightning_proto_enumTypes[19]
}
func (x HTLCAttempt_HTLCStatus) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use HTLCAttempt_HTLCStatus.Descriptor instead.
func (HTLCAttempt_HTLCStatus) EnumDescriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{145, 0}
}
type Failure_FailureCode int32
const (
// The numbers assigned in this enumeration match the failure codes as
// defined in BOLT #4. Because protobuf 3 requires enums to start with 0,
// a RESERVED value is added.
Failure_RESERVED Failure_FailureCode = 0
Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS Failure_FailureCode = 1
Failure_INCORRECT_PAYMENT_AMOUNT Failure_FailureCode = 2
Failure_FINAL_INCORRECT_CLTV_EXPIRY Failure_FailureCode = 3
Failure_FINAL_INCORRECT_HTLC_AMOUNT Failure_FailureCode = 4
Failure_FINAL_EXPIRY_TOO_SOON Failure_FailureCode = 5
Failure_INVALID_REALM Failure_FailureCode = 6
Failure_EXPIRY_TOO_SOON Failure_FailureCode = 7
Failure_INVALID_ONION_VERSION Failure_FailureCode = 8
Failure_INVALID_ONION_HMAC Failure_FailureCode = 9
Failure_INVALID_ONION_KEY Failure_FailureCode = 10
Failure_AMOUNT_BELOW_MINIMUM Failure_FailureCode = 11
Failure_FEE_INSUFFICIENT Failure_FailureCode = 12
Failure_INCORRECT_CLTV_EXPIRY Failure_FailureCode = 13
Failure_CHANNEL_DISABLED Failure_FailureCode = 14
Failure_TEMPORARY_CHANNEL_FAILURE Failure_FailureCode = 15
Failure_REQUIRED_NODE_FEATURE_MISSING Failure_FailureCode = 16
Failure_REQUIRED_CHANNEL_FEATURE_MISSING Failure_FailureCode = 17
Failure_UNKNOWN_NEXT_PEER Failure_FailureCode = 18
Failure_TEMPORARY_NODE_FAILURE Failure_FailureCode = 19
Failure_PERMANENT_NODE_FAILURE Failure_FailureCode = 20
Failure_PERMANENT_CHANNEL_FAILURE Failure_FailureCode = 21
Failure_EXPIRY_TOO_FAR Failure_FailureCode = 22
Failure_MPP_TIMEOUT Failure_FailureCode = 23
Failure_INVALID_ONION_PAYLOAD Failure_FailureCode = 24
Failure_INVALID_ONION_BLINDING Failure_FailureCode = 25
// An internal error occurred.
Failure_INTERNAL_FAILURE Failure_FailureCode = 997
// The error source is known, but the failure itself couldn't be decoded.
Failure_UNKNOWN_FAILURE Failure_FailureCode = 998
// An unreadable failure result is returned if the received failure message
// cannot be decrypted. In that case the error source is unknown.
Failure_UNREADABLE_FAILURE Failure_FailureCode = 999
)
// Enum value maps for Failure_FailureCode.
var (
Failure_FailureCode_name = map[int32]string{
0: "RESERVED",
1: "INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS",
2: "INCORRECT_PAYMENT_AMOUNT",
3: "FINAL_INCORRECT_CLTV_EXPIRY",
4: "FINAL_INCORRECT_HTLC_AMOUNT",
5: "FINAL_EXPIRY_TOO_SOON",
6: "INVALID_REALM",
7: "EXPIRY_TOO_SOON",
8: "INVALID_ONION_VERSION",
9: "INVALID_ONION_HMAC",
10: "INVALID_ONION_KEY",
11: "AMOUNT_BELOW_MINIMUM",
12: "FEE_INSUFFICIENT",
13: "INCORRECT_CLTV_EXPIRY",
14: "CHANNEL_DISABLED",
15: "TEMPORARY_CHANNEL_FAILURE",
16: "REQUIRED_NODE_FEATURE_MISSING",
17: "REQUIRED_CHANNEL_FEATURE_MISSING",
18: "UNKNOWN_NEXT_PEER",
19: "TEMPORARY_NODE_FAILURE",
20: "PERMANENT_NODE_FAILURE",
21: "PERMANENT_CHANNEL_FAILURE",
22: "EXPIRY_TOO_FAR",
23: "MPP_TIMEOUT",
24: "INVALID_ONION_PAYLOAD",
25: "INVALID_ONION_BLINDING",
997: "INTERNAL_FAILURE",
998: "UNKNOWN_FAILURE",
999: "UNREADABLE_FAILURE",
}
Failure_FailureCode_value = map[string]int32{
"RESERVED": 0,
"INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS": 1,
"INCORRECT_PAYMENT_AMOUNT": 2,
"FINAL_INCORRECT_CLTV_EXPIRY": 3,
"FINAL_INCORRECT_HTLC_AMOUNT": 4,
"FINAL_EXPIRY_TOO_SOON": 5,
"INVALID_REALM": 6,
"EXPIRY_TOO_SOON": 7,
"INVALID_ONION_VERSION": 8,
"INVALID_ONION_HMAC": 9,
"INVALID_ONION_KEY": 10,
"AMOUNT_BELOW_MINIMUM": 11,
"FEE_INSUFFICIENT": 12,
"INCORRECT_CLTV_EXPIRY": 13,
"CHANNEL_DISABLED": 14,
"TEMPORARY_CHANNEL_FAILURE": 15,
"REQUIRED_NODE_FEATURE_MISSING": 16,
"REQUIRED_CHANNEL_FEATURE_MISSING": 17,
"UNKNOWN_NEXT_PEER": 18,
"TEMPORARY_NODE_FAILURE": 19,
"PERMANENT_NODE_FAILURE": 20,
"PERMANENT_CHANNEL_FAILURE": 21,
"EXPIRY_TOO_FAR": 22,
"MPP_TIMEOUT": 23,
"INVALID_ONION_PAYLOAD": 24,
"INVALID_ONION_BLINDING": 25,
"INTERNAL_FAILURE": 997,
"UNKNOWN_FAILURE": 998,
"UNREADABLE_FAILURE": 999,
}
)
func (x Failure_FailureCode) Enum() *Failure_FailureCode {
p := new(Failure_FailureCode)
*p = x
return p
}
func (x Failure_FailureCode) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (Failure_FailureCode) Descriptor() protoreflect.EnumDescriptor {
return file_lightning_proto_enumTypes[20].Descriptor()
}
func (Failure_FailureCode) Type() protoreflect.EnumType {
return &file_lightning_proto_enumTypes[20]
}
func (x Failure_FailureCode) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use Failure_FailureCode.Descriptor instead.
func (Failure_FailureCode) EnumDescriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{189, 0}
}
type LookupHtlcResolutionRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ChanId uint64 `protobuf:"varint,1,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"`
HtlcIndex uint64 `protobuf:"varint,2,opt,name=htlc_index,json=htlcIndex,proto3" json:"htlc_index,omitempty"`
}
func (x *LookupHtlcResolutionRequest) Reset() {
*x = LookupHtlcResolutionRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *LookupHtlcResolutionRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LookupHtlcResolutionRequest) ProtoMessage() {}
func (x *LookupHtlcResolutionRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LookupHtlcResolutionRequest.ProtoReflect.Descriptor instead.
func (*LookupHtlcResolutionRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{0}
}
func (x *LookupHtlcResolutionRequest) GetChanId() uint64 {
if x != nil {
return x.ChanId
}
return 0
}
func (x *LookupHtlcResolutionRequest) GetHtlcIndex() uint64 {
if x != nil {
return x.HtlcIndex
}
return 0
}
type LookupHtlcResolutionResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Settled is true is the htlc was settled. If false, the htlc was failed.
Settled bool `protobuf:"varint,1,opt,name=settled,proto3" json:"settled,omitempty"`
// Offchain indicates whether the htlc was resolved off-chain or on-chain.
Offchain bool `protobuf:"varint,2,opt,name=offchain,proto3" json:"offchain,omitempty"`
}
func (x *LookupHtlcResolutionResponse) Reset() {
*x = LookupHtlcResolutionResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *LookupHtlcResolutionResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LookupHtlcResolutionResponse) ProtoMessage() {}
func (x *LookupHtlcResolutionResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LookupHtlcResolutionResponse.ProtoReflect.Descriptor instead.
func (*LookupHtlcResolutionResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{1}
}
func (x *LookupHtlcResolutionResponse) GetSettled() bool {
if x != nil {
return x.Settled
}
return false
}
func (x *LookupHtlcResolutionResponse) GetOffchain() bool {
if x != nil {
return x.Offchain
}
return false
}
type SubscribeCustomMessagesRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *SubscribeCustomMessagesRequest) Reset() {
*x = SubscribeCustomMessagesRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SubscribeCustomMessagesRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SubscribeCustomMessagesRequest) ProtoMessage() {}
func (x *SubscribeCustomMessagesRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SubscribeCustomMessagesRequest.ProtoReflect.Descriptor instead.
func (*SubscribeCustomMessagesRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{2}
}
type CustomMessage struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Peer from which the message originates
Peer []byte `protobuf:"bytes,1,opt,name=peer,proto3" json:"peer,omitempty"`
// Message type. This value will be in the custom range (>= 32768).
Type uint32 `protobuf:"varint,2,opt,name=type,proto3" json:"type,omitempty"`
// Raw message data
Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"`
}
func (x *CustomMessage) Reset() {
*x = CustomMessage{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CustomMessage) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CustomMessage) ProtoMessage() {}
func (x *CustomMessage) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CustomMessage.ProtoReflect.Descriptor instead.
func (*CustomMessage) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{3}
}
func (x *CustomMessage) GetPeer() []byte {
if x != nil {
return x.Peer
}
return nil
}
func (x *CustomMessage) GetType() uint32 {
if x != nil {
return x.Type
}
return 0
}
func (x *CustomMessage) GetData() []byte {
if x != nil {
return x.Data
}
return nil
}
type SendCustomMessageRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Peer to send the message to
Peer []byte `protobuf:"bytes,1,opt,name=peer,proto3" json:"peer,omitempty"`
// Message type. This value needs to be in the custom range (>= 32768).
// To send a type < custom range, lnd needs to be compiled with the `dev`
// build tag, and the message type to override should be specified in lnd's
// experimental protocol configuration.
Type uint32 `protobuf:"varint,2,opt,name=type,proto3" json:"type,omitempty"`
// Raw message data.
Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"`
}
func (x *SendCustomMessageRequest) Reset() {
*x = SendCustomMessageRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SendCustomMessageRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendCustomMessageRequest) ProtoMessage() {}
func (x *SendCustomMessageRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendCustomMessageRequest.ProtoReflect.Descriptor instead.
func (*SendCustomMessageRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{4}
}
func (x *SendCustomMessageRequest) GetPeer() []byte {
if x != nil {
return x.Peer
}
return nil
}
func (x *SendCustomMessageRequest) GetType() uint32 {
if x != nil {
return x.Type
}
return 0
}
func (x *SendCustomMessageRequest) GetData() []byte {
if x != nil {
return x.Data
}
return nil
}
type SendCustomMessageResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The status of the send operation.
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
}
func (x *SendCustomMessageResponse) Reset() {
*x = SendCustomMessageResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SendCustomMessageResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendCustomMessageResponse) ProtoMessage() {}
func (x *SendCustomMessageResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendCustomMessageResponse.ProtoReflect.Descriptor instead.
func (*SendCustomMessageResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{5}
}
func (x *SendCustomMessageResponse) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
type Utxo struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The type of address
AddressType AddressType `protobuf:"varint,1,opt,name=address_type,json=addressType,proto3,enum=lnrpc.AddressType" json:"address_type,omitempty"`
// The address
Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"`
// The value of the unspent coin in satoshis
AmountSat int64 `protobuf:"varint,3,opt,name=amount_sat,json=amountSat,proto3" json:"amount_sat,omitempty"`
// The pkscript in hex
PkScript string `protobuf:"bytes,4,opt,name=pk_script,json=pkScript,proto3" json:"pk_script,omitempty"`
// The outpoint in format txid:n
Outpoint *OutPoint `protobuf:"bytes,5,opt,name=outpoint,proto3" json:"outpoint,omitempty"`
// The number of confirmations for the Utxo
Confirmations int64 `protobuf:"varint,6,opt,name=confirmations,proto3" json:"confirmations,omitempty"`
}
func (x *Utxo) Reset() {
*x = Utxo{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Utxo) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Utxo) ProtoMessage() {}
func (x *Utxo) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Utxo.ProtoReflect.Descriptor instead.
func (*Utxo) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{6}
}
func (x *Utxo) GetAddressType() AddressType {
if x != nil {
return x.AddressType
}
return AddressType_WITNESS_PUBKEY_HASH
}
func (x *Utxo) GetAddress() string {
if x != nil {
return x.Address
}
return ""
}
func (x *Utxo) GetAmountSat() int64 {
if x != nil {
return x.AmountSat
}
return 0
}
func (x *Utxo) GetPkScript() string {
if x != nil {
return x.PkScript
}
return ""
}
func (x *Utxo) GetOutpoint() *OutPoint {
if x != nil {
return x.Outpoint
}
return nil
}
func (x *Utxo) GetConfirmations() int64 {
if x != nil {
return x.Confirmations
}
return 0
}
type OutputDetail struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The type of the output
OutputType OutputScriptType `protobuf:"varint,1,opt,name=output_type,json=outputType,proto3,enum=lnrpc.OutputScriptType" json:"output_type,omitempty"`
// The address
Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"`
// The pkscript in hex
PkScript string `protobuf:"bytes,3,opt,name=pk_script,json=pkScript,proto3" json:"pk_script,omitempty"`
// The output index used in the raw transaction
OutputIndex int64 `protobuf:"varint,4,opt,name=output_index,json=outputIndex,proto3" json:"output_index,omitempty"`
// The value of the output coin in satoshis
Amount int64 `protobuf:"varint,5,opt,name=amount,proto3" json:"amount,omitempty"`
// Denotes if the output is controlled by the internal wallet
IsOurAddress bool `protobuf:"varint,6,opt,name=is_our_address,json=isOurAddress,proto3" json:"is_our_address,omitempty"`
}
func (x *OutputDetail) Reset() {
*x = OutputDetail{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *OutputDetail) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*OutputDetail) ProtoMessage() {}
func (x *OutputDetail) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use OutputDetail.ProtoReflect.Descriptor instead.
func (*OutputDetail) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{7}
}
func (x *OutputDetail) GetOutputType() OutputScriptType {
if x != nil {
return x.OutputType
}
return OutputScriptType_SCRIPT_TYPE_PUBKEY_HASH
}
func (x *OutputDetail) GetAddress() string {
if x != nil {
return x.Address
}
return ""
}
func (x *OutputDetail) GetPkScript() string {
if x != nil {
return x.PkScript
}
return ""
}
func (x *OutputDetail) GetOutputIndex() int64 {
if x != nil {
return x.OutputIndex
}
return 0
}
func (x *OutputDetail) GetAmount() int64 {
if x != nil {
return x.Amount
}
return 0
}
func (x *OutputDetail) GetIsOurAddress() bool {
if x != nil {
return x.IsOurAddress
}
return false
}
type Transaction struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The transaction hash
TxHash string `protobuf:"bytes,1,opt,name=tx_hash,json=txHash,proto3" json:"tx_hash,omitempty"`
// The transaction amount, denominated in satoshis
Amount int64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"`
// The number of confirmations
NumConfirmations int32 `protobuf:"varint,3,opt,name=num_confirmations,json=numConfirmations,proto3" json:"num_confirmations,omitempty"`
// The hash of the block this transaction was included in
BlockHash string `protobuf:"bytes,4,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"`
// The height of the block this transaction was included in
BlockHeight int32 `protobuf:"varint,5,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"`
// Timestamp of this transaction
TimeStamp int64 `protobuf:"varint,6,opt,name=time_stamp,json=timeStamp,proto3" json:"time_stamp,omitempty"`
// Fees paid for this transaction
TotalFees int64 `protobuf:"varint,7,opt,name=total_fees,json=totalFees,proto3" json:"total_fees,omitempty"`
// Addresses that received funds for this transaction. Deprecated as it is
// now incorporated in the output_details field.
//
// Deprecated: Marked as deprecated in lightning.proto.
DestAddresses []string `protobuf:"bytes,8,rep,name=dest_addresses,json=destAddresses,proto3" json:"dest_addresses,omitempty"`
// Outputs that received funds for this transaction
OutputDetails []*OutputDetail `protobuf:"bytes,11,rep,name=output_details,json=outputDetails,proto3" json:"output_details,omitempty"`
// The raw transaction hex.
RawTxHex string `protobuf:"bytes,9,opt,name=raw_tx_hex,json=rawTxHex,proto3" json:"raw_tx_hex,omitempty"`
// A label that was optionally set on transaction broadcast.
Label string `protobuf:"bytes,10,opt,name=label,proto3" json:"label,omitempty"`
// PreviousOutpoints/Inputs of this transaction.
PreviousOutpoints []*PreviousOutPoint `protobuf:"bytes,12,rep,name=previous_outpoints,json=previousOutpoints,proto3" json:"previous_outpoints,omitempty"`
}
func (x *Transaction) Reset() {
*x = Transaction{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Transaction) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Transaction) ProtoMessage() {}
func (x *Transaction) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Transaction.ProtoReflect.Descriptor instead.
func (*Transaction) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{8}
}
func (x *Transaction) GetTxHash() string {
if x != nil {
return x.TxHash
}
return ""
}
func (x *Transaction) GetAmount() int64 {
if x != nil {
return x.Amount
}
return 0
}
func (x *Transaction) GetNumConfirmations() int32 {
if x != nil {
return x.NumConfirmations
}
return 0
}
func (x *Transaction) GetBlockHash() string {
if x != nil {
return x.BlockHash
}
return ""
}
func (x *Transaction) GetBlockHeight() int32 {
if x != nil {
return x.BlockHeight
}
return 0
}
func (x *Transaction) GetTimeStamp() int64 {
if x != nil {
return x.TimeStamp
}
return 0
}
func (x *Transaction) GetTotalFees() int64 {
if x != nil {
return x.TotalFees
}
return 0
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *Transaction) GetDestAddresses() []string {
if x != nil {
return x.DestAddresses
}
return nil
}
func (x *Transaction) GetOutputDetails() []*OutputDetail {
if x != nil {
return x.OutputDetails
}
return nil
}
func (x *Transaction) GetRawTxHex() string {
if x != nil {
return x.RawTxHex
}
return ""
}
func (x *Transaction) GetLabel() string {
if x != nil {
return x.Label
}
return ""
}
func (x *Transaction) GetPreviousOutpoints() []*PreviousOutPoint {
if x != nil {
return x.PreviousOutpoints
}
return nil
}
type GetTransactionsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The height from which to list transactions, inclusive. If this value is
// greater than end_height, transactions will be read in reverse.
StartHeight int32 `protobuf:"varint,1,opt,name=start_height,json=startHeight,proto3" json:"start_height,omitempty"`
// The height until which to list transactions, inclusive. To include
// unconfirmed transactions, this value should be set to -1, which will
// return transactions from start_height until the current chain tip and
// unconfirmed transactions. If no end_height is provided, the call will
// default to this option.
EndHeight int32 `protobuf:"varint,2,opt,name=end_height,json=endHeight,proto3" json:"end_height,omitempty"`
// An optional filter to only include transactions relevant to an account.
Account string `protobuf:"bytes,3,opt,name=account,proto3" json:"account,omitempty"`
// The index of a transaction that will be used in a query to determine which
// transaction should be returned in the response.
IndexOffset uint32 `protobuf:"varint,4,opt,name=index_offset,json=indexOffset,proto3" json:"index_offset,omitempty"`
// The maximal number of transactions returned in the response to this query.
// This value should be set to 0 to return all transactions.
MaxTransactions uint32 `protobuf:"varint,5,opt,name=max_transactions,json=maxTransactions,proto3" json:"max_transactions,omitempty"`
}
func (x *GetTransactionsRequest) Reset() {
*x = GetTransactionsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetTransactionsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetTransactionsRequest) ProtoMessage() {}
func (x *GetTransactionsRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetTransactionsRequest.ProtoReflect.Descriptor instead.
func (*GetTransactionsRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{9}
}
func (x *GetTransactionsRequest) GetStartHeight() int32 {
if x != nil {
return x.StartHeight
}
return 0
}
func (x *GetTransactionsRequest) GetEndHeight() int32 {
if x != nil {
return x.EndHeight
}
return 0
}
func (x *GetTransactionsRequest) GetAccount() string {
if x != nil {
return x.Account
}
return ""
}
func (x *GetTransactionsRequest) GetIndexOffset() uint32 {
if x != nil {
return x.IndexOffset
}
return 0
}
func (x *GetTransactionsRequest) GetMaxTransactions() uint32 {
if x != nil {
return x.MaxTransactions
}
return 0
}
type TransactionDetails struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The list of transactions relevant to the wallet.
Transactions []*Transaction `protobuf:"bytes,1,rep,name=transactions,proto3" json:"transactions,omitempty"`
// The index of the last item in the set of returned transactions. This can be
// used to seek further, pagination style.
LastIndex uint64 `protobuf:"varint,2,opt,name=last_index,json=lastIndex,proto3" json:"last_index,omitempty"`
// The index of the last item in the set of returned transactions. This can be
// used to seek backwards, pagination style.
FirstIndex uint64 `protobuf:"varint,3,opt,name=first_index,json=firstIndex,proto3" json:"first_index,omitempty"`
}
func (x *TransactionDetails) Reset() {
*x = TransactionDetails{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TransactionDetails) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TransactionDetails) ProtoMessage() {}
func (x *TransactionDetails) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TransactionDetails.ProtoReflect.Descriptor instead.
func (*TransactionDetails) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{10}
}
func (x *TransactionDetails) GetTransactions() []*Transaction {
if x != nil {
return x.Transactions
}
return nil
}
func (x *TransactionDetails) GetLastIndex() uint64 {
if x != nil {
return x.LastIndex
}
return 0
}
func (x *TransactionDetails) GetFirstIndex() uint64 {
if x != nil {
return x.FirstIndex
}
return 0
}
type FeeLimit struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Limit:
//
// *FeeLimit_Fixed
// *FeeLimit_FixedMsat
// *FeeLimit_Percent
Limit isFeeLimit_Limit `protobuf_oneof:"limit"`
}
func (x *FeeLimit) Reset() {
*x = FeeLimit{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FeeLimit) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FeeLimit) ProtoMessage() {}
func (x *FeeLimit) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[11]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FeeLimit.ProtoReflect.Descriptor instead.
func (*FeeLimit) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{11}
}
func (m *FeeLimit) GetLimit() isFeeLimit_Limit {
if m != nil {
return m.Limit
}
return nil
}
func (x *FeeLimit) GetFixed() int64 {
if x, ok := x.GetLimit().(*FeeLimit_Fixed); ok {
return x.Fixed
}
return 0
}
func (x *FeeLimit) GetFixedMsat() int64 {
if x, ok := x.GetLimit().(*FeeLimit_FixedMsat); ok {
return x.FixedMsat
}
return 0
}
func (x *FeeLimit) GetPercent() int64 {
if x, ok := x.GetLimit().(*FeeLimit_Percent); ok {
return x.Percent
}
return 0
}
type isFeeLimit_Limit interface {
isFeeLimit_Limit()
}
type FeeLimit_Fixed struct {
// The fee limit expressed as a fixed amount of satoshis.
//
// The fields fixed and fixed_msat are mutually exclusive.
Fixed int64 `protobuf:"varint,1,opt,name=fixed,proto3,oneof"`
}
type FeeLimit_FixedMsat struct {
// The fee limit expressed as a fixed amount of millisatoshis.
//
// The fields fixed and fixed_msat are mutually exclusive.
FixedMsat int64 `protobuf:"varint,3,opt,name=fixed_msat,json=fixedMsat,proto3,oneof"`
}
type FeeLimit_Percent struct {
// The fee limit expressed as a percentage of the payment amount.
Percent int64 `protobuf:"varint,2,opt,name=percent,proto3,oneof"`
}
func (*FeeLimit_Fixed) isFeeLimit_Limit() {}
func (*FeeLimit_FixedMsat) isFeeLimit_Limit() {}
func (*FeeLimit_Percent) isFeeLimit_Limit() {}
type SendRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The identity pubkey of the payment recipient. When using REST, this field
// must be encoded as base64.
Dest []byte `protobuf:"bytes,1,opt,name=dest,proto3" json:"dest,omitempty"`
// The hex-encoded identity pubkey of the payment recipient. Deprecated now
// that the REST gateway supports base64 encoding of bytes fields.
//
// Deprecated: Marked as deprecated in lightning.proto.
DestString string `protobuf:"bytes,2,opt,name=dest_string,json=destString,proto3" json:"dest_string,omitempty"`
// The amount to send expressed in satoshis.
//
// The fields amt and amt_msat are mutually exclusive.
Amt int64 `protobuf:"varint,3,opt,name=amt,proto3" json:"amt,omitempty"`
// The amount to send expressed in millisatoshis.
//
// The fields amt and amt_msat are mutually exclusive.
AmtMsat int64 `protobuf:"varint,12,opt,name=amt_msat,json=amtMsat,proto3" json:"amt_msat,omitempty"`
// The hash to use within the payment's HTLC. When using REST, this field
// must be encoded as base64.
PaymentHash []byte `protobuf:"bytes,4,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"`
// The hex-encoded hash to use within the payment's HTLC. Deprecated now
// that the REST gateway supports base64 encoding of bytes fields.
//
// Deprecated: Marked as deprecated in lightning.proto.
PaymentHashString string `protobuf:"bytes,5,opt,name=payment_hash_string,json=paymentHashString,proto3" json:"payment_hash_string,omitempty"`
// A bare-bones invoice for a payment within the Lightning Network. With the
// details of the invoice, the sender has all the data necessary to send a
// payment to the recipient.
PaymentRequest string `protobuf:"bytes,6,opt,name=payment_request,json=paymentRequest,proto3" json:"payment_request,omitempty"`
// The CLTV delta from the current height that should be used to set the
// timelock for the final hop.
FinalCltvDelta int32 `protobuf:"varint,7,opt,name=final_cltv_delta,json=finalCltvDelta,proto3" json:"final_cltv_delta,omitempty"`
// The maximum number of satoshis that will be paid as a fee of the payment.
// This value can be represented either as a percentage of the amount being
// sent, or as a fixed amount of the maximum fee the user is willing the pay to
// send the payment. If not specified, lnd will use a default value of 100%
// fees for small amounts (<=1k sat) or 5% fees for larger amounts.
FeeLimit *FeeLimit `protobuf:"bytes,8,opt,name=fee_limit,json=feeLimit,proto3" json:"fee_limit,omitempty"`
// The channel id of the channel that must be taken to the first hop. If zero,
// any channel may be used.
OutgoingChanId uint64 `protobuf:"varint,9,opt,name=outgoing_chan_id,json=outgoingChanId,proto3" json:"outgoing_chan_id,omitempty"`
// The pubkey of the last hop of the route. If empty, any hop may be used.
LastHopPubkey []byte `protobuf:"bytes,13,opt,name=last_hop_pubkey,json=lastHopPubkey,proto3" json:"last_hop_pubkey,omitempty"`
// An optional maximum total time lock for the route. This should not exceed
// lnd's `--max-cltv-expiry` setting. If zero, then the value of
// `--max-cltv-expiry` is enforced.
CltvLimit uint32 `protobuf:"varint,10,opt,name=cltv_limit,json=cltvLimit,proto3" json:"cltv_limit,omitempty"`
// An optional field that can be used to pass an arbitrary set of TLV records
// to a peer which understands the new records. This can be used to pass
// application specific data during the payment attempt. Record types are
// required to be in the custom range >= 65536. When using REST, the values
// must be encoded as base64.
DestCustomRecords map[uint64][]byte `protobuf:"bytes,11,rep,name=dest_custom_records,json=destCustomRecords,proto3" json:"dest_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
// If set, circular payments to self are permitted.
AllowSelfPayment bool `protobuf:"varint,14,opt,name=allow_self_payment,json=allowSelfPayment,proto3" json:"allow_self_payment,omitempty"`
// Features assumed to be supported by the final node. All transitive feature
// dependencies must also be set properly. For a given feature bit pair, either
// optional or remote may be set, but not both. If this field is nil or empty,
// the router will try to load destination features from the graph as a
// fallback.
DestFeatures []FeatureBit `protobuf:"varint,15,rep,packed,name=dest_features,json=destFeatures,proto3,enum=lnrpc.FeatureBit" json:"dest_features,omitempty"`
// The payment address of the generated invoice. This is also called
// payment secret in specifications (e.g. BOLT 11).
PaymentAddr []byte `protobuf:"bytes,16,opt,name=payment_addr,json=paymentAddr,proto3" json:"payment_addr,omitempty"`
}
func (x *SendRequest) Reset() {
*x = SendRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SendRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendRequest) ProtoMessage() {}
func (x *SendRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[12]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendRequest.ProtoReflect.Descriptor instead.
func (*SendRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{12}
}
func (x *SendRequest) GetDest() []byte {
if x != nil {
return x.Dest
}
return nil
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *SendRequest) GetDestString() string {
if x != nil {
return x.DestString
}
return ""
}
func (x *SendRequest) GetAmt() int64 {
if x != nil {
return x.Amt
}
return 0
}
func (x *SendRequest) GetAmtMsat() int64 {
if x != nil {
return x.AmtMsat
}
return 0
}
func (x *SendRequest) GetPaymentHash() []byte {
if x != nil {
return x.PaymentHash
}
return nil
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *SendRequest) GetPaymentHashString() string {
if x != nil {
return x.PaymentHashString
}
return ""
}
func (x *SendRequest) GetPaymentRequest() string {
if x != nil {
return x.PaymentRequest
}
return ""
}
func (x *SendRequest) GetFinalCltvDelta() int32 {
if x != nil {
return x.FinalCltvDelta
}
return 0
}
func (x *SendRequest) GetFeeLimit() *FeeLimit {
if x != nil {
return x.FeeLimit
}
return nil
}
func (x *SendRequest) GetOutgoingChanId() uint64 {
if x != nil {
return x.OutgoingChanId
}
return 0
}
func (x *SendRequest) GetLastHopPubkey() []byte {
if x != nil {
return x.LastHopPubkey
}
return nil
}
func (x *SendRequest) GetCltvLimit() uint32 {
if x != nil {
return x.CltvLimit
}
return 0
}
func (x *SendRequest) GetDestCustomRecords() map[uint64][]byte {
if x != nil {
return x.DestCustomRecords
}
return nil
}
func (x *SendRequest) GetAllowSelfPayment() bool {
if x != nil {
return x.AllowSelfPayment
}
return false
}
func (x *SendRequest) GetDestFeatures() []FeatureBit {
if x != nil {
return x.DestFeatures
}
return nil
}
func (x *SendRequest) GetPaymentAddr() []byte {
if x != nil {
return x.PaymentAddr
}
return nil
}
type SendResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
PaymentError string `protobuf:"bytes,1,opt,name=payment_error,json=paymentError,proto3" json:"payment_error,omitempty"`
PaymentPreimage []byte `protobuf:"bytes,2,opt,name=payment_preimage,json=paymentPreimage,proto3" json:"payment_preimage,omitempty"`
PaymentRoute *Route `protobuf:"bytes,3,opt,name=payment_route,json=paymentRoute,proto3" json:"payment_route,omitempty"`
PaymentHash []byte `protobuf:"bytes,4,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"`
}
func (x *SendResponse) Reset() {
*x = SendResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SendResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendResponse) ProtoMessage() {}
func (x *SendResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[13]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendResponse.ProtoReflect.Descriptor instead.
func (*SendResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{13}
}
func (x *SendResponse) GetPaymentError() string {
if x != nil {
return x.PaymentError
}
return ""
}
func (x *SendResponse) GetPaymentPreimage() []byte {
if x != nil {
return x.PaymentPreimage
}
return nil
}
func (x *SendResponse) GetPaymentRoute() *Route {
if x != nil {
return x.PaymentRoute
}
return nil
}
func (x *SendResponse) GetPaymentHash() []byte {
if x != nil {
return x.PaymentHash
}
return nil
}
type SendToRouteRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The payment hash to use for the HTLC. When using REST, this field must be
// encoded as base64.
PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"`
// An optional hex-encoded payment hash to be used for the HTLC. Deprecated now
// that the REST gateway supports base64 encoding of bytes fields.
//
// Deprecated: Marked as deprecated in lightning.proto.
PaymentHashString string `protobuf:"bytes,2,opt,name=payment_hash_string,json=paymentHashString,proto3" json:"payment_hash_string,omitempty"`
// Route that should be used to attempt to complete the payment.
Route *Route `protobuf:"bytes,4,opt,name=route,proto3" json:"route,omitempty"`
}
func (x *SendToRouteRequest) Reset() {
*x = SendToRouteRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SendToRouteRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendToRouteRequest) ProtoMessage() {}
func (x *SendToRouteRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[14]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendToRouteRequest.ProtoReflect.Descriptor instead.
func (*SendToRouteRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{14}
}
func (x *SendToRouteRequest) GetPaymentHash() []byte {
if x != nil {
return x.PaymentHash
}
return nil
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *SendToRouteRequest) GetPaymentHashString() string {
if x != nil {
return x.PaymentHashString
}
return ""
}
func (x *SendToRouteRequest) GetRoute() *Route {
if x != nil {
return x.Route
}
return nil
}
type ChannelAcceptRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The pubkey of the node that wishes to open an inbound channel.
NodePubkey []byte `protobuf:"bytes,1,opt,name=node_pubkey,json=nodePubkey,proto3" json:"node_pubkey,omitempty"`
// The hash of the genesis block that the proposed channel resides in.
ChainHash []byte `protobuf:"bytes,2,opt,name=chain_hash,json=chainHash,proto3" json:"chain_hash,omitempty"`
// The pending channel id.
PendingChanId []byte `protobuf:"bytes,3,opt,name=pending_chan_id,json=pendingChanId,proto3" json:"pending_chan_id,omitempty"`
// The funding amount in satoshis that initiator wishes to use in the
// channel.
FundingAmt uint64 `protobuf:"varint,4,opt,name=funding_amt,json=fundingAmt,proto3" json:"funding_amt,omitempty"`
// The push amount of the proposed channel in millisatoshis.
PushAmt uint64 `protobuf:"varint,5,opt,name=push_amt,json=pushAmt,proto3" json:"push_amt,omitempty"`
// The dust limit of the initiator's commitment tx.
DustLimit uint64 `protobuf:"varint,6,opt,name=dust_limit,json=dustLimit,proto3" json:"dust_limit,omitempty"`
// The maximum amount of coins in millisatoshis that can be pending in this
// channel.
MaxValueInFlight uint64 `protobuf:"varint,7,opt,name=max_value_in_flight,json=maxValueInFlight,proto3" json:"max_value_in_flight,omitempty"`
// The minimum amount of satoshis the initiator requires us to have at all
// times.
ChannelReserve uint64 `protobuf:"varint,8,opt,name=channel_reserve,json=channelReserve,proto3" json:"channel_reserve,omitempty"`
// The smallest HTLC in millisatoshis that the initiator will accept.
MinHtlc uint64 `protobuf:"varint,9,opt,name=min_htlc,json=minHtlc,proto3" json:"min_htlc,omitempty"`
// The initial fee rate that the initiator suggests for both commitment
// transactions.
FeePerKw uint64 `protobuf:"varint,10,opt,name=fee_per_kw,json=feePerKw,proto3" json:"fee_per_kw,omitempty"`
// The number of blocks to use for the relative time lock in the pay-to-self
// output of both commitment transactions.
CsvDelay uint32 `protobuf:"varint,11,opt,name=csv_delay,json=csvDelay,proto3" json:"csv_delay,omitempty"`
// The total number of incoming HTLC's that the initiator will accept.
MaxAcceptedHtlcs uint32 `protobuf:"varint,12,opt,name=max_accepted_htlcs,json=maxAcceptedHtlcs,proto3" json:"max_accepted_htlcs,omitempty"`
// A bit-field which the initiator uses to specify proposed channel
// behavior.
ChannelFlags uint32 `protobuf:"varint,13,opt,name=channel_flags,json=channelFlags,proto3" json:"channel_flags,omitempty"`
// The commitment type the initiator wishes to use for the proposed channel.
CommitmentType CommitmentType `protobuf:"varint,14,opt,name=commitment_type,json=commitmentType,proto3,enum=lnrpc.CommitmentType" json:"commitment_type,omitempty"`
// Whether the initiator wants to open a zero-conf channel via the channel
// type.
WantsZeroConf bool `protobuf:"varint,15,opt,name=wants_zero_conf,json=wantsZeroConf,proto3" json:"wants_zero_conf,omitempty"`
// Whether the initiator wants to use the scid-alias channel type. This is
// separate from the feature bit.
WantsScidAlias bool `protobuf:"varint,16,opt,name=wants_scid_alias,json=wantsScidAlias,proto3" json:"wants_scid_alias,omitempty"`
}
func (x *ChannelAcceptRequest) Reset() {
*x = ChannelAcceptRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChannelAcceptRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelAcceptRequest) ProtoMessage() {}
func (x *ChannelAcceptRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[15]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelAcceptRequest.ProtoReflect.Descriptor instead.
func (*ChannelAcceptRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{15}
}
func (x *ChannelAcceptRequest) GetNodePubkey() []byte {
if x != nil {
return x.NodePubkey
}
return nil
}
func (x *ChannelAcceptRequest) GetChainHash() []byte {
if x != nil {
return x.ChainHash
}
return nil
}
func (x *ChannelAcceptRequest) GetPendingChanId() []byte {
if x != nil {
return x.PendingChanId
}
return nil
}
func (x *ChannelAcceptRequest) GetFundingAmt() uint64 {
if x != nil {
return x.FundingAmt
}
return 0
}
func (x *ChannelAcceptRequest) GetPushAmt() uint64 {
if x != nil {
return x.PushAmt
}
return 0
}
func (x *ChannelAcceptRequest) GetDustLimit() uint64 {
if x != nil {
return x.DustLimit
}
return 0
}
func (x *ChannelAcceptRequest) GetMaxValueInFlight() uint64 {
if x != nil {
return x.MaxValueInFlight
}
return 0
}
func (x *ChannelAcceptRequest) GetChannelReserve() uint64 {
if x != nil {
return x.ChannelReserve
}
return 0
}
func (x *ChannelAcceptRequest) GetMinHtlc() uint64 {
if x != nil {
return x.MinHtlc
}
return 0
}
func (x *ChannelAcceptRequest) GetFeePerKw() uint64 {
if x != nil {
return x.FeePerKw
}
return 0
}
func (x *ChannelAcceptRequest) GetCsvDelay() uint32 {
if x != nil {
return x.CsvDelay
}
return 0
}
func (x *ChannelAcceptRequest) GetMaxAcceptedHtlcs() uint32 {
if x != nil {
return x.MaxAcceptedHtlcs
}
return 0
}
func (x *ChannelAcceptRequest) GetChannelFlags() uint32 {
if x != nil {
return x.ChannelFlags
}
return 0
}
func (x *ChannelAcceptRequest) GetCommitmentType() CommitmentType {
if x != nil {
return x.CommitmentType
}
return CommitmentType_UNKNOWN_COMMITMENT_TYPE
}
func (x *ChannelAcceptRequest) GetWantsZeroConf() bool {
if x != nil {
return x.WantsZeroConf
}
return false
}
func (x *ChannelAcceptRequest) GetWantsScidAlias() bool {
if x != nil {
return x.WantsScidAlias
}
return false
}
type ChannelAcceptResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Whether or not the client accepts the channel.
Accept bool `protobuf:"varint,1,opt,name=accept,proto3" json:"accept,omitempty"`
// The pending channel id to which this response applies.
PendingChanId []byte `protobuf:"bytes,2,opt,name=pending_chan_id,json=pendingChanId,proto3" json:"pending_chan_id,omitempty"`
// An optional error to send the initiating party to indicate why the channel
// was rejected. This field *should not* contain sensitive information, it will
// be sent to the initiating party. This field should only be set if accept is
// false, the channel will be rejected if an error is set with accept=true
// because the meaning of this response is ambiguous. Limited to 500
// characters.
Error string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"`
// The upfront shutdown address to use if the initiating peer supports option
// upfront shutdown script (see ListPeers for the features supported). Note
// that the channel open will fail if this value is set for a peer that does
// not support this feature bit.
UpfrontShutdown string `protobuf:"bytes,4,opt,name=upfront_shutdown,json=upfrontShutdown,proto3" json:"upfront_shutdown,omitempty"`
// The csv delay (in blocks) that we require for the remote party.
CsvDelay uint32 `protobuf:"varint,5,opt,name=csv_delay,json=csvDelay,proto3" json:"csv_delay,omitempty"`
// The reserve amount in satoshis that we require the remote peer to adhere to.
// We require that the remote peer always have some reserve amount allocated to
// them so that there is always a disincentive to broadcast old state (if they
// hold 0 sats on their side of the channel, there is nothing to lose).
ReserveSat uint64 `protobuf:"varint,6,opt,name=reserve_sat,json=reserveSat,proto3" json:"reserve_sat,omitempty"`
// The maximum amount of funds in millisatoshis that we allow the remote peer
// to have in outstanding htlcs.
InFlightMaxMsat uint64 `protobuf:"varint,7,opt,name=in_flight_max_msat,json=inFlightMaxMsat,proto3" json:"in_flight_max_msat,omitempty"`
// The maximum number of htlcs that the remote peer can offer us.
MaxHtlcCount uint32 `protobuf:"varint,8,opt,name=max_htlc_count,json=maxHtlcCount,proto3" json:"max_htlc_count,omitempty"`
// The minimum value in millisatoshis for incoming htlcs on the channel.
MinHtlcIn uint64 `protobuf:"varint,9,opt,name=min_htlc_in,json=minHtlcIn,proto3" json:"min_htlc_in,omitempty"`
// The number of confirmations we require before we consider the channel open.
MinAcceptDepth uint32 `protobuf:"varint,10,opt,name=min_accept_depth,json=minAcceptDepth,proto3" json:"min_accept_depth,omitempty"`
// Whether the responder wants this to be a zero-conf channel. This will fail
// if either side does not have the scid-alias feature bit set. The minimum
// depth field must be zero if this is true.
ZeroConf bool `protobuf:"varint,11,opt,name=zero_conf,json=zeroConf,proto3" json:"zero_conf,omitempty"`
}
func (x *ChannelAcceptResponse) Reset() {
*x = ChannelAcceptResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChannelAcceptResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelAcceptResponse) ProtoMessage() {}
func (x *ChannelAcceptResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[16]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelAcceptResponse.ProtoReflect.Descriptor instead.
func (*ChannelAcceptResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{16}
}
func (x *ChannelAcceptResponse) GetAccept() bool {
if x != nil {
return x.Accept
}
return false
}
func (x *ChannelAcceptResponse) GetPendingChanId() []byte {
if x != nil {
return x.PendingChanId
}
return nil
}
func (x *ChannelAcceptResponse) GetError() string {
if x != nil {
return x.Error
}
return ""
}
func (x *ChannelAcceptResponse) GetUpfrontShutdown() string {
if x != nil {
return x.UpfrontShutdown
}
return ""
}
func (x *ChannelAcceptResponse) GetCsvDelay() uint32 {
if x != nil {
return x.CsvDelay
}
return 0
}
func (x *ChannelAcceptResponse) GetReserveSat() uint64 {
if x != nil {
return x.ReserveSat
}
return 0
}
func (x *ChannelAcceptResponse) GetInFlightMaxMsat() uint64 {
if x != nil {
return x.InFlightMaxMsat
}
return 0
}
func (x *ChannelAcceptResponse) GetMaxHtlcCount() uint32 {
if x != nil {
return x.MaxHtlcCount
}
return 0
}
func (x *ChannelAcceptResponse) GetMinHtlcIn() uint64 {
if x != nil {
return x.MinHtlcIn
}
return 0
}
func (x *ChannelAcceptResponse) GetMinAcceptDepth() uint32 {
if x != nil {
return x.MinAcceptDepth
}
return 0
}
func (x *ChannelAcceptResponse) GetZeroConf() bool {
if x != nil {
return x.ZeroConf
}
return false
}
type ChannelPoint struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to FundingTxid:
//
// *ChannelPoint_FundingTxidBytes
// *ChannelPoint_FundingTxidStr
FundingTxid isChannelPoint_FundingTxid `protobuf_oneof:"funding_txid"`
// The index of the output of the funding transaction
OutputIndex uint32 `protobuf:"varint,3,opt,name=output_index,json=outputIndex,proto3" json:"output_index,omitempty"`
}
func (x *ChannelPoint) Reset() {
*x = ChannelPoint{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChannelPoint) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelPoint) ProtoMessage() {}
func (x *ChannelPoint) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[17]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelPoint.ProtoReflect.Descriptor instead.
func (*ChannelPoint) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{17}
}
func (m *ChannelPoint) GetFundingTxid() isChannelPoint_FundingTxid {
if m != nil {
return m.FundingTxid
}
return nil
}
func (x *ChannelPoint) GetFundingTxidBytes() []byte {
if x, ok := x.GetFundingTxid().(*ChannelPoint_FundingTxidBytes); ok {
return x.FundingTxidBytes
}
return nil
}
func (x *ChannelPoint) GetFundingTxidStr() string {
if x, ok := x.GetFundingTxid().(*ChannelPoint_FundingTxidStr); ok {
return x.FundingTxidStr
}
return ""
}
func (x *ChannelPoint) GetOutputIndex() uint32 {
if x != nil {
return x.OutputIndex
}
return 0
}
type isChannelPoint_FundingTxid interface {
isChannelPoint_FundingTxid()
}
type ChannelPoint_FundingTxidBytes struct {
// Txid of the funding transaction. When using REST, this field must be
// encoded as base64.
FundingTxidBytes []byte `protobuf:"bytes,1,opt,name=funding_txid_bytes,json=fundingTxidBytes,proto3,oneof"`
}
type ChannelPoint_FundingTxidStr struct {
// Hex-encoded string representing the byte-reversed hash of the funding
// transaction.
FundingTxidStr string `protobuf:"bytes,2,opt,name=funding_txid_str,json=fundingTxidStr,proto3,oneof"`
}
func (*ChannelPoint_FundingTxidBytes) isChannelPoint_FundingTxid() {}
func (*ChannelPoint_FundingTxidStr) isChannelPoint_FundingTxid() {}
type OutPoint struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Raw bytes representing the transaction id.
TxidBytes []byte `protobuf:"bytes,1,opt,name=txid_bytes,json=txidBytes,proto3" json:"txid_bytes,omitempty"`
// Reversed, hex-encoded string representing the transaction id.
TxidStr string `protobuf:"bytes,2,opt,name=txid_str,json=txidStr,proto3" json:"txid_str,omitempty"`
// The index of the output on the transaction.
OutputIndex uint32 `protobuf:"varint,3,opt,name=output_index,json=outputIndex,proto3" json:"output_index,omitempty"`
}
func (x *OutPoint) Reset() {
*x = OutPoint{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *OutPoint) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*OutPoint) ProtoMessage() {}
func (x *OutPoint) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[18]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use OutPoint.ProtoReflect.Descriptor instead.
func (*OutPoint) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{18}
}
func (x *OutPoint) GetTxidBytes() []byte {
if x != nil {
return x.TxidBytes
}
return nil
}
func (x *OutPoint) GetTxidStr() string {
if x != nil {
return x.TxidStr
}
return ""
}
func (x *OutPoint) GetOutputIndex() uint32 {
if x != nil {
return x.OutputIndex
}
return 0
}
type PreviousOutPoint struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The outpoint in format txid:n.
Outpoint string `protobuf:"bytes,1,opt,name=outpoint,proto3" json:"outpoint,omitempty"`
// Denotes if the outpoint is controlled by the internal wallet.
// The flag will only detect p2wkh, np2wkh and p2tr inputs as its own.
IsOurOutput bool `protobuf:"varint,2,opt,name=is_our_output,json=isOurOutput,proto3" json:"is_our_output,omitempty"`
}
func (x *PreviousOutPoint) Reset() {
*x = PreviousOutPoint{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[19]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PreviousOutPoint) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PreviousOutPoint) ProtoMessage() {}
func (x *PreviousOutPoint) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[19]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PreviousOutPoint.ProtoReflect.Descriptor instead.
func (*PreviousOutPoint) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{19}
}
func (x *PreviousOutPoint) GetOutpoint() string {
if x != nil {
return x.Outpoint
}
return ""
}
func (x *PreviousOutPoint) GetIsOurOutput() bool {
if x != nil {
return x.IsOurOutput
}
return false
}
type LightningAddress struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The identity pubkey of the Lightning node.
Pubkey string `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"`
// The network location of the lightning node, e.g. `69.69.69.69:1337` or
// `localhost:10011`.
Host string `protobuf:"bytes,2,opt,name=host,proto3" json:"host,omitempty"`
}
func (x *LightningAddress) Reset() {
*x = LightningAddress{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[20]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *LightningAddress) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LightningAddress) ProtoMessage() {}
func (x *LightningAddress) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[20]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LightningAddress.ProtoReflect.Descriptor instead.
func (*LightningAddress) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{20}
}
func (x *LightningAddress) GetPubkey() string {
if x != nil {
return x.Pubkey
}
return ""
}
func (x *LightningAddress) GetHost() string {
if x != nil {
return x.Host
}
return ""
}
type EstimateFeeRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The map from addresses to amounts for the transaction.
AddrToAmount map[string]int64 `protobuf:"bytes,1,rep,name=AddrToAmount,proto3" json:"AddrToAmount,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
// The target number of blocks that this transaction should be confirmed
// by.
TargetConf int32 `protobuf:"varint,2,opt,name=target_conf,json=targetConf,proto3" json:"target_conf,omitempty"`
// The minimum number of confirmations each one of your outputs used for
// the transaction must satisfy.
MinConfs int32 `protobuf:"varint,3,opt,name=min_confs,json=minConfs,proto3" json:"min_confs,omitempty"`
// Whether unconfirmed outputs should be used as inputs for the transaction.
SpendUnconfirmed bool `protobuf:"varint,4,opt,name=spend_unconfirmed,json=spendUnconfirmed,proto3" json:"spend_unconfirmed,omitempty"`
// The strategy to use for selecting coins during fees estimation.
CoinSelectionStrategy CoinSelectionStrategy `protobuf:"varint,5,opt,name=coin_selection_strategy,json=coinSelectionStrategy,proto3,enum=lnrpc.CoinSelectionStrategy" json:"coin_selection_strategy,omitempty"`
}
func (x *EstimateFeeRequest) Reset() {
*x = EstimateFeeRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[21]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *EstimateFeeRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EstimateFeeRequest) ProtoMessage() {}
func (x *EstimateFeeRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[21]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EstimateFeeRequest.ProtoReflect.Descriptor instead.
func (*EstimateFeeRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{21}
}
func (x *EstimateFeeRequest) GetAddrToAmount() map[string]int64 {
if x != nil {
return x.AddrToAmount
}
return nil
}
func (x *EstimateFeeRequest) GetTargetConf() int32 {
if x != nil {
return x.TargetConf
}
return 0
}
func (x *EstimateFeeRequest) GetMinConfs() int32 {
if x != nil {
return x.MinConfs
}
return 0
}
func (x *EstimateFeeRequest) GetSpendUnconfirmed() bool {
if x != nil {
return x.SpendUnconfirmed
}
return false
}
func (x *EstimateFeeRequest) GetCoinSelectionStrategy() CoinSelectionStrategy {
if x != nil {
return x.CoinSelectionStrategy
}
return CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG
}
type EstimateFeeResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The total fee in satoshis.
FeeSat int64 `protobuf:"varint,1,opt,name=fee_sat,json=feeSat,proto3" json:"fee_sat,omitempty"`
// Deprecated, use sat_per_vbyte.
// The fee rate in satoshi/vbyte.
//
// Deprecated: Marked as deprecated in lightning.proto.
FeerateSatPerByte int64 `protobuf:"varint,2,opt,name=feerate_sat_per_byte,json=feerateSatPerByte,proto3" json:"feerate_sat_per_byte,omitempty"`
// The fee rate in satoshi/vbyte.
SatPerVbyte uint64 `protobuf:"varint,3,opt,name=sat_per_vbyte,json=satPerVbyte,proto3" json:"sat_per_vbyte,omitempty"`
}
func (x *EstimateFeeResponse) Reset() {
*x = EstimateFeeResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[22]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *EstimateFeeResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EstimateFeeResponse) ProtoMessage() {}
func (x *EstimateFeeResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[22]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EstimateFeeResponse.ProtoReflect.Descriptor instead.
func (*EstimateFeeResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{22}
}
func (x *EstimateFeeResponse) GetFeeSat() int64 {
if x != nil {
return x.FeeSat
}
return 0
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *EstimateFeeResponse) GetFeerateSatPerByte() int64 {
if x != nil {
return x.FeerateSatPerByte
}
return 0
}
func (x *EstimateFeeResponse) GetSatPerVbyte() uint64 {
if x != nil {
return x.SatPerVbyte
}
return 0
}
type SendManyRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The map from addresses to amounts
AddrToAmount map[string]int64 `protobuf:"bytes,1,rep,name=AddrToAmount,proto3" json:"AddrToAmount,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
// The target number of blocks that this transaction should be confirmed
// by.
TargetConf int32 `protobuf:"varint,3,opt,name=target_conf,json=targetConf,proto3" json:"target_conf,omitempty"`
// A manual fee rate set in sat/vbyte that should be used when crafting the
// transaction.
SatPerVbyte uint64 `protobuf:"varint,4,opt,name=sat_per_vbyte,json=satPerVbyte,proto3" json:"sat_per_vbyte,omitempty"`
// Deprecated, use sat_per_vbyte.
// A manual fee rate set in sat/vbyte that should be used when crafting the
// transaction.
//
// Deprecated: Marked as deprecated in lightning.proto.
SatPerByte int64 `protobuf:"varint,5,opt,name=sat_per_byte,json=satPerByte,proto3" json:"sat_per_byte,omitempty"`
// An optional label for the transaction, limited to 500 characters.
Label string `protobuf:"bytes,6,opt,name=label,proto3" json:"label,omitempty"`
// The minimum number of confirmations each one of your outputs used for
// the transaction must satisfy.
MinConfs int32 `protobuf:"varint,7,opt,name=min_confs,json=minConfs,proto3" json:"min_confs,omitempty"`
// Whether unconfirmed outputs should be used as inputs for the transaction.
SpendUnconfirmed bool `protobuf:"varint,8,opt,name=spend_unconfirmed,json=spendUnconfirmed,proto3" json:"spend_unconfirmed,omitempty"`
// The strategy to use for selecting coins during sending many requests.
CoinSelectionStrategy CoinSelectionStrategy `protobuf:"varint,9,opt,name=coin_selection_strategy,json=coinSelectionStrategy,proto3,enum=lnrpc.CoinSelectionStrategy" json:"coin_selection_strategy,omitempty"`
}
func (x *SendManyRequest) Reset() {
*x = SendManyRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[23]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SendManyRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendManyRequest) ProtoMessage() {}
func (x *SendManyRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[23]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendManyRequest.ProtoReflect.Descriptor instead.
func (*SendManyRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{23}
}
func (x *SendManyRequest) GetAddrToAmount() map[string]int64 {
if x != nil {
return x.AddrToAmount
}
return nil
}
func (x *SendManyRequest) GetTargetConf() int32 {
if x != nil {
return x.TargetConf
}
return 0
}
func (x *SendManyRequest) GetSatPerVbyte() uint64 {
if x != nil {
return x.SatPerVbyte
}
return 0
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *SendManyRequest) GetSatPerByte() int64 {
if x != nil {
return x.SatPerByte
}
return 0
}
func (x *SendManyRequest) GetLabel() string {
if x != nil {
return x.Label
}
return ""
}
func (x *SendManyRequest) GetMinConfs() int32 {
if x != nil {
return x.MinConfs
}
return 0
}
func (x *SendManyRequest) GetSpendUnconfirmed() bool {
if x != nil {
return x.SpendUnconfirmed
}
return false
}
func (x *SendManyRequest) GetCoinSelectionStrategy() CoinSelectionStrategy {
if x != nil {
return x.CoinSelectionStrategy
}
return CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG
}
type SendManyResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The id of the transaction
Txid string `protobuf:"bytes,1,opt,name=txid,proto3" json:"txid,omitempty"`
}
func (x *SendManyResponse) Reset() {
*x = SendManyResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[24]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SendManyResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendManyResponse) ProtoMessage() {}
func (x *SendManyResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[24]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendManyResponse.ProtoReflect.Descriptor instead.
func (*SendManyResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{24}
}
func (x *SendManyResponse) GetTxid() string {
if x != nil {
return x.Txid
}
return ""
}
type SendCoinsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The address to send coins to
Addr string `protobuf:"bytes,1,opt,name=addr,proto3" json:"addr,omitempty"`
// The amount in satoshis to send
Amount int64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"`
// The target number of blocks that this transaction should be confirmed
// by.
TargetConf int32 `protobuf:"varint,3,opt,name=target_conf,json=targetConf,proto3" json:"target_conf,omitempty"`
// A manual fee rate set in sat/vbyte that should be used when crafting the
// transaction.
SatPerVbyte uint64 `protobuf:"varint,4,opt,name=sat_per_vbyte,json=satPerVbyte,proto3" json:"sat_per_vbyte,omitempty"`
// Deprecated, use sat_per_vbyte.
// A manual fee rate set in sat/vbyte that should be used when crafting the
// transaction.
//
// Deprecated: Marked as deprecated in lightning.proto.
SatPerByte int64 `protobuf:"varint,5,opt,name=sat_per_byte,json=satPerByte,proto3" json:"sat_per_byte,omitempty"`
// If set, the amount field should be unset. It indicates lnd will send all
// wallet coins or all selected coins to the specified address.
SendAll bool `protobuf:"varint,6,opt,name=send_all,json=sendAll,proto3" json:"send_all,omitempty"`
// An optional label for the transaction, limited to 500 characters.
Label string `protobuf:"bytes,7,opt,name=label,proto3" json:"label,omitempty"`
// The minimum number of confirmations each one of your outputs used for
// the transaction must satisfy.
MinConfs int32 `protobuf:"varint,8,opt,name=min_confs,json=minConfs,proto3" json:"min_confs,omitempty"`
// Whether unconfirmed outputs should be used as inputs for the transaction.
SpendUnconfirmed bool `protobuf:"varint,9,opt,name=spend_unconfirmed,json=spendUnconfirmed,proto3" json:"spend_unconfirmed,omitempty"`
// The strategy to use for selecting coins.
CoinSelectionStrategy CoinSelectionStrategy `protobuf:"varint,10,opt,name=coin_selection_strategy,json=coinSelectionStrategy,proto3,enum=lnrpc.CoinSelectionStrategy" json:"coin_selection_strategy,omitempty"`
// A list of selected outpoints as inputs for the transaction.
Outpoints []*OutPoint `protobuf:"bytes,11,rep,name=outpoints,proto3" json:"outpoints,omitempty"`
}
func (x *SendCoinsRequest) Reset() {
*x = SendCoinsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[25]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SendCoinsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendCoinsRequest) ProtoMessage() {}
func (x *SendCoinsRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[25]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendCoinsRequest.ProtoReflect.Descriptor instead.
func (*SendCoinsRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{25}
}
func (x *SendCoinsRequest) GetAddr() string {
if x != nil {
return x.Addr
}
return ""
}
func (x *SendCoinsRequest) GetAmount() int64 {
if x != nil {
return x.Amount
}
return 0
}
func (x *SendCoinsRequest) GetTargetConf() int32 {
if x != nil {
return x.TargetConf
}
return 0
}
func (x *SendCoinsRequest) GetSatPerVbyte() uint64 {
if x != nil {
return x.SatPerVbyte
}
return 0
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *SendCoinsRequest) GetSatPerByte() int64 {
if x != nil {
return x.SatPerByte
}
return 0
}
func (x *SendCoinsRequest) GetSendAll() bool {
if x != nil {
return x.SendAll
}
return false
}
func (x *SendCoinsRequest) GetLabel() string {
if x != nil {
return x.Label
}
return ""
}
func (x *SendCoinsRequest) GetMinConfs() int32 {
if x != nil {
return x.MinConfs
}
return 0
}
func (x *SendCoinsRequest) GetSpendUnconfirmed() bool {
if x != nil {
return x.SpendUnconfirmed
}
return false
}
func (x *SendCoinsRequest) GetCoinSelectionStrategy() CoinSelectionStrategy {
if x != nil {
return x.CoinSelectionStrategy
}
return CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG
}
func (x *SendCoinsRequest) GetOutpoints() []*OutPoint {
if x != nil {
return x.Outpoints
}
return nil
}
type SendCoinsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The transaction ID of the transaction
Txid string `protobuf:"bytes,1,opt,name=txid,proto3" json:"txid,omitempty"`
}
func (x *SendCoinsResponse) Reset() {
*x = SendCoinsResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[26]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SendCoinsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendCoinsResponse) ProtoMessage() {}
func (x *SendCoinsResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[26]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendCoinsResponse.ProtoReflect.Descriptor instead.
func (*SendCoinsResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{26}
}
func (x *SendCoinsResponse) GetTxid() string {
if x != nil {
return x.Txid
}
return ""
}
type ListUnspentRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The minimum number of confirmations to be included.
MinConfs int32 `protobuf:"varint,1,opt,name=min_confs,json=minConfs,proto3" json:"min_confs,omitempty"`
// The maximum number of confirmations to be included.
MaxConfs int32 `protobuf:"varint,2,opt,name=max_confs,json=maxConfs,proto3" json:"max_confs,omitempty"`
// An optional filter to only include outputs belonging to an account.
Account string `protobuf:"bytes,3,opt,name=account,proto3" json:"account,omitempty"`
}
func (x *ListUnspentRequest) Reset() {
*x = ListUnspentRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[27]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListUnspentRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListUnspentRequest) ProtoMessage() {}
func (x *ListUnspentRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[27]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListUnspentRequest.ProtoReflect.Descriptor instead.
func (*ListUnspentRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{27}
}
func (x *ListUnspentRequest) GetMinConfs() int32 {
if x != nil {
return x.MinConfs
}
return 0
}
func (x *ListUnspentRequest) GetMaxConfs() int32 {
if x != nil {
return x.MaxConfs
}
return 0
}
func (x *ListUnspentRequest) GetAccount() string {
if x != nil {
return x.Account
}
return ""
}
type ListUnspentResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A list of utxos
Utxos []*Utxo `protobuf:"bytes,1,rep,name=utxos,proto3" json:"utxos,omitempty"`
}
func (x *ListUnspentResponse) Reset() {
*x = ListUnspentResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[28]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListUnspentResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListUnspentResponse) ProtoMessage() {}
func (x *ListUnspentResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[28]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListUnspentResponse.ProtoReflect.Descriptor instead.
func (*ListUnspentResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{28}
}
func (x *ListUnspentResponse) GetUtxos() []*Utxo {
if x != nil {
return x.Utxos
}
return nil
}
type NewAddressRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The type of address to generate.
Type AddressType `protobuf:"varint,1,opt,name=type,proto3,enum=lnrpc.AddressType" json:"type,omitempty"`
// The name of the account to generate a new address for. If empty, the
// default wallet account is used.
Account string `protobuf:"bytes,2,opt,name=account,proto3" json:"account,omitempty"`
}
func (x *NewAddressRequest) Reset() {
*x = NewAddressRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[29]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *NewAddressRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NewAddressRequest) ProtoMessage() {}
func (x *NewAddressRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[29]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NewAddressRequest.ProtoReflect.Descriptor instead.
func (*NewAddressRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{29}
}
func (x *NewAddressRequest) GetType() AddressType {
if x != nil {
return x.Type
}
return AddressType_WITNESS_PUBKEY_HASH
}
func (x *NewAddressRequest) GetAccount() string {
if x != nil {
return x.Account
}
return ""
}
type NewAddressResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The newly generated wallet address
Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
}
func (x *NewAddressResponse) Reset() {
*x = NewAddressResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[30]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *NewAddressResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NewAddressResponse) ProtoMessage() {}
func (x *NewAddressResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[30]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NewAddressResponse.ProtoReflect.Descriptor instead.
func (*NewAddressResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{30}
}
func (x *NewAddressResponse) GetAddress() string {
if x != nil {
return x.Address
}
return ""
}
type SignMessageRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The message to be signed. When using REST, this field must be encoded as
// base64.
Msg []byte `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"`
// Instead of the default double-SHA256 hashing of the message before signing,
// only use one round of hashing instead.
SingleHash bool `protobuf:"varint,2,opt,name=single_hash,json=singleHash,proto3" json:"single_hash,omitempty"`
}
func (x *SignMessageRequest) Reset() {
*x = SignMessageRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[31]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SignMessageRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SignMessageRequest) ProtoMessage() {}
func (x *SignMessageRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[31]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SignMessageRequest.ProtoReflect.Descriptor instead.
func (*SignMessageRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{31}
}
func (x *SignMessageRequest) GetMsg() []byte {
if x != nil {
return x.Msg
}
return nil
}
func (x *SignMessageRequest) GetSingleHash() bool {
if x != nil {
return x.SingleHash
}
return false
}
type SignMessageResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The signature for the given message
Signature string `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"`
}
func (x *SignMessageResponse) Reset() {
*x = SignMessageResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[32]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SignMessageResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SignMessageResponse) ProtoMessage() {}
func (x *SignMessageResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[32]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SignMessageResponse.ProtoReflect.Descriptor instead.
func (*SignMessageResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{32}
}
func (x *SignMessageResponse) GetSignature() string {
if x != nil {
return x.Signature
}
return ""
}
type VerifyMessageRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The message over which the signature is to be verified. When using REST,
// this field must be encoded as base64.
Msg []byte `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"`
// The signature to be verified over the given message
Signature string `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"`
}
func (x *VerifyMessageRequest) Reset() {
*x = VerifyMessageRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[33]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *VerifyMessageRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*VerifyMessageRequest) ProtoMessage() {}
func (x *VerifyMessageRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[33]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use VerifyMessageRequest.ProtoReflect.Descriptor instead.
func (*VerifyMessageRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{33}
}
func (x *VerifyMessageRequest) GetMsg() []byte {
if x != nil {
return x.Msg
}
return nil
}
func (x *VerifyMessageRequest) GetSignature() string {
if x != nil {
return x.Signature
}
return ""
}
type VerifyMessageResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Whether the signature was valid over the given message
Valid bool `protobuf:"varint,1,opt,name=valid,proto3" json:"valid,omitempty"`
// The pubkey recovered from the signature
Pubkey string `protobuf:"bytes,2,opt,name=pubkey,proto3" json:"pubkey,omitempty"`
}
func (x *VerifyMessageResponse) Reset() {
*x = VerifyMessageResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[34]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *VerifyMessageResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*VerifyMessageResponse) ProtoMessage() {}
func (x *VerifyMessageResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[34]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use VerifyMessageResponse.ProtoReflect.Descriptor instead.
func (*VerifyMessageResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{34}
}
func (x *VerifyMessageResponse) GetValid() bool {
if x != nil {
return x.Valid
}
return false
}
func (x *VerifyMessageResponse) GetPubkey() string {
if x != nil {
return x.Pubkey
}
return ""
}
type ConnectPeerRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Lightning address of the peer to connect to.
Addr *LightningAddress `protobuf:"bytes,1,opt,name=addr,proto3" json:"addr,omitempty"`
// If set, the daemon will attempt to persistently connect to the target
// peer. Otherwise, the call will be synchronous.
Perm bool `protobuf:"varint,2,opt,name=perm,proto3" json:"perm,omitempty"`
// The connection timeout value (in seconds) for this request. It won't affect
// other requests.
Timeout uint64 `protobuf:"varint,3,opt,name=timeout,proto3" json:"timeout,omitempty"`
}
func (x *ConnectPeerRequest) Reset() {
*x = ConnectPeerRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[35]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ConnectPeerRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ConnectPeerRequest) ProtoMessage() {}
func (x *ConnectPeerRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[35]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ConnectPeerRequest.ProtoReflect.Descriptor instead.
func (*ConnectPeerRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{35}
}
func (x *ConnectPeerRequest) GetAddr() *LightningAddress {
if x != nil {
return x.Addr
}
return nil
}
func (x *ConnectPeerRequest) GetPerm() bool {
if x != nil {
return x.Perm
}
return false
}
func (x *ConnectPeerRequest) GetTimeout() uint64 {
if x != nil {
return x.Timeout
}
return 0
}
type ConnectPeerResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The status of the connect operation.
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
}
func (x *ConnectPeerResponse) Reset() {
*x = ConnectPeerResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[36]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ConnectPeerResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ConnectPeerResponse) ProtoMessage() {}
func (x *ConnectPeerResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[36]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ConnectPeerResponse.ProtoReflect.Descriptor instead.
func (*ConnectPeerResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{36}
}
func (x *ConnectPeerResponse) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
type DisconnectPeerRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The pubkey of the node to disconnect from
PubKey string `protobuf:"bytes,1,opt,name=pub_key,json=pubKey,proto3" json:"pub_key,omitempty"`
}
func (x *DisconnectPeerRequest) Reset() {
*x = DisconnectPeerRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[37]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DisconnectPeerRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DisconnectPeerRequest) ProtoMessage() {}
func (x *DisconnectPeerRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[37]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DisconnectPeerRequest.ProtoReflect.Descriptor instead.
func (*DisconnectPeerRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{37}
}
func (x *DisconnectPeerRequest) GetPubKey() string {
if x != nil {
return x.PubKey
}
return ""
}
type DisconnectPeerResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The status of the disconnect operation.
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
}
func (x *DisconnectPeerResponse) Reset() {
*x = DisconnectPeerResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[38]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DisconnectPeerResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DisconnectPeerResponse) ProtoMessage() {}
func (x *DisconnectPeerResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[38]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DisconnectPeerResponse.ProtoReflect.Descriptor instead.
func (*DisconnectPeerResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{38}
}
func (x *DisconnectPeerResponse) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
type HTLC struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Incoming bool `protobuf:"varint,1,opt,name=incoming,proto3" json:"incoming,omitempty"`
Amount int64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"`
HashLock []byte `protobuf:"bytes,3,opt,name=hash_lock,json=hashLock,proto3" json:"hash_lock,omitempty"`
ExpirationHeight uint32 `protobuf:"varint,4,opt,name=expiration_height,json=expirationHeight,proto3" json:"expiration_height,omitempty"`
// Index identifying the htlc on the channel.
HtlcIndex uint64 `protobuf:"varint,5,opt,name=htlc_index,json=htlcIndex,proto3" json:"htlc_index,omitempty"`
// If this HTLC is involved in a forwarding operation, this field indicates
// the forwarding channel. For an outgoing htlc, it is the incoming channel.
// For an incoming htlc, it is the outgoing channel. When the htlc
// originates from this node or this node is the final destination,
// forwarding_channel will be zero. The forwarding channel will also be zero
// for htlcs that need to be forwarded but don't have a forwarding decision
// persisted yet.
ForwardingChannel uint64 `protobuf:"varint,6,opt,name=forwarding_channel,json=forwardingChannel,proto3" json:"forwarding_channel,omitempty"`
// Index identifying the htlc on the forwarding channel.
ForwardingHtlcIndex uint64 `protobuf:"varint,7,opt,name=forwarding_htlc_index,json=forwardingHtlcIndex,proto3" json:"forwarding_htlc_index,omitempty"`
// Whether the HTLC is locked in. An HTLC is considered locked in when the
// remote party has sent us the `revoke_and_ack` to irrevocably commit this
// HTLC.
LockedIn bool `protobuf:"varint,8,opt,name=locked_in,json=lockedIn,proto3" json:"locked_in,omitempty"`
}
func (x *HTLC) Reset() {
*x = HTLC{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[39]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HTLC) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HTLC) ProtoMessage() {}
func (x *HTLC) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[39]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HTLC.ProtoReflect.Descriptor instead.
func (*HTLC) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{39}
}
func (x *HTLC) GetIncoming() bool {
if x != nil {
return x.Incoming
}
return false
}
func (x *HTLC) GetAmount() int64 {
if x != nil {
return x.Amount
}
return 0
}
func (x *HTLC) GetHashLock() []byte {
if x != nil {
return x.HashLock
}
return nil
}
func (x *HTLC) GetExpirationHeight() uint32 {
if x != nil {
return x.ExpirationHeight
}
return 0
}
func (x *HTLC) GetHtlcIndex() uint64 {
if x != nil {
return x.HtlcIndex
}
return 0
}
func (x *HTLC) GetForwardingChannel() uint64 {
if x != nil {
return x.ForwardingChannel
}
return 0
}
func (x *HTLC) GetForwardingHtlcIndex() uint64 {
if x != nil {
return x.ForwardingHtlcIndex
}
return 0
}
func (x *HTLC) GetLockedIn() bool {
if x != nil {
return x.LockedIn
}
return false
}
type ChannelConstraints struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The CSV delay expressed in relative blocks. If the channel is force closed,
// we will need to wait for this many blocks before we can regain our funds.
CsvDelay uint32 `protobuf:"varint,1,opt,name=csv_delay,json=csvDelay,proto3" json:"csv_delay,omitempty"`
// The minimum satoshis this node is required to reserve in its balance.
ChanReserveSat uint64 `protobuf:"varint,2,opt,name=chan_reserve_sat,json=chanReserveSat,proto3" json:"chan_reserve_sat,omitempty"`
// The dust limit (in satoshis) of the initiator's commitment tx.
DustLimitSat uint64 `protobuf:"varint,3,opt,name=dust_limit_sat,json=dustLimitSat,proto3" json:"dust_limit_sat,omitempty"`
// The maximum amount of coins in millisatoshis that can be pending in this
// channel.
MaxPendingAmtMsat uint64 `protobuf:"varint,4,opt,name=max_pending_amt_msat,json=maxPendingAmtMsat,proto3" json:"max_pending_amt_msat,omitempty"`
// The smallest HTLC in millisatoshis that the initiator will accept.
MinHtlcMsat uint64 `protobuf:"varint,5,opt,name=min_htlc_msat,json=minHtlcMsat,proto3" json:"min_htlc_msat,omitempty"`
// The total number of incoming HTLC's that the initiator will accept.
MaxAcceptedHtlcs uint32 `protobuf:"varint,6,opt,name=max_accepted_htlcs,json=maxAcceptedHtlcs,proto3" json:"max_accepted_htlcs,omitempty"`
}
func (x *ChannelConstraints) Reset() {
*x = ChannelConstraints{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[40]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChannelConstraints) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelConstraints) ProtoMessage() {}
func (x *ChannelConstraints) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[40]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelConstraints.ProtoReflect.Descriptor instead.
func (*ChannelConstraints) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{40}
}
func (x *ChannelConstraints) GetCsvDelay() uint32 {
if x != nil {
return x.CsvDelay
}
return 0
}
func (x *ChannelConstraints) GetChanReserveSat() uint64 {
if x != nil {
return x.ChanReserveSat
}
return 0
}
func (x *ChannelConstraints) GetDustLimitSat() uint64 {
if x != nil {
return x.DustLimitSat
}
return 0
}
func (x *ChannelConstraints) GetMaxPendingAmtMsat() uint64 {
if x != nil {
return x.MaxPendingAmtMsat
}
return 0
}
func (x *ChannelConstraints) GetMinHtlcMsat() uint64 {
if x != nil {
return x.MinHtlcMsat
}
return 0
}
func (x *ChannelConstraints) GetMaxAcceptedHtlcs() uint32 {
if x != nil {
return x.MaxAcceptedHtlcs
}
return 0
}
type Channel struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Whether this channel is active or not
Active bool `protobuf:"varint,1,opt,name=active,proto3" json:"active,omitempty"`
// The identity pubkey of the remote node
RemotePubkey string `protobuf:"bytes,2,opt,name=remote_pubkey,json=remotePubkey,proto3" json:"remote_pubkey,omitempty"`
// The outpoint (txid:index) of the funding transaction. With this value, Bob
// will be able to generate a signature for Alice's version of the commitment
// transaction.
ChannelPoint string `protobuf:"bytes,3,opt,name=channel_point,json=channelPoint,proto3" json:"channel_point,omitempty"`
// The unique channel ID for the channel. The first 3 bytes are the block
// height, the next 3 the index within the block, and the last 2 bytes are the
// output index for the channel.
ChanId uint64 `protobuf:"varint,4,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"`
// The total amount of funds held in this channel
Capacity int64 `protobuf:"varint,5,opt,name=capacity,proto3" json:"capacity,omitempty"`
// This node's current balance in this channel
LocalBalance int64 `protobuf:"varint,6,opt,name=local_balance,json=localBalance,proto3" json:"local_balance,omitempty"`
// The counterparty's current balance in this channel
RemoteBalance int64 `protobuf:"varint,7,opt,name=remote_balance,json=remoteBalance,proto3" json:"remote_balance,omitempty"`
// The amount calculated to be paid in fees for the current set of commitment
// transactions. The fee amount is persisted with the channel in order to
// allow the fee amount to be removed and recalculated with each channel state
// update, including updates that happen after a system restart.
CommitFee int64 `protobuf:"varint,8,opt,name=commit_fee,json=commitFee,proto3" json:"commit_fee,omitempty"`
// The weight of the commitment transaction
CommitWeight int64 `protobuf:"varint,9,opt,name=commit_weight,json=commitWeight,proto3" json:"commit_weight,omitempty"`
// The required number of satoshis per kilo-weight that the requester will pay
// at all times, for both the funding transaction and commitment transaction.
// This value can later be updated once the channel is open.
FeePerKw int64 `protobuf:"varint,10,opt,name=fee_per_kw,json=feePerKw,proto3" json:"fee_per_kw,omitempty"`
// The unsettled balance in this channel
UnsettledBalance int64 `protobuf:"varint,11,opt,name=unsettled_balance,json=unsettledBalance,proto3" json:"unsettled_balance,omitempty"`
// The total number of satoshis we've sent within this channel.
TotalSatoshisSent int64 `protobuf:"varint,12,opt,name=total_satoshis_sent,json=totalSatoshisSent,proto3" json:"total_satoshis_sent,omitempty"`
// The total number of satoshis we've received within this channel.
TotalSatoshisReceived int64 `protobuf:"varint,13,opt,name=total_satoshis_received,json=totalSatoshisReceived,proto3" json:"total_satoshis_received,omitempty"`
// The total number of updates conducted within this channel.
NumUpdates uint64 `protobuf:"varint,14,opt,name=num_updates,json=numUpdates,proto3" json:"num_updates,omitempty"`
// The list of active, uncleared HTLCs currently pending within the channel.
PendingHtlcs []*HTLC `protobuf:"bytes,15,rep,name=pending_htlcs,json=pendingHtlcs,proto3" json:"pending_htlcs,omitempty"`
// Deprecated. The CSV delay expressed in relative blocks. If the channel is
// force closed, we will need to wait for this many blocks before we can regain
// our funds.
//
// Deprecated: Marked as deprecated in lightning.proto.
CsvDelay uint32 `protobuf:"varint,16,opt,name=csv_delay,json=csvDelay,proto3" json:"csv_delay,omitempty"`
// Whether this channel is advertised to the network or not.
Private bool `protobuf:"varint,17,opt,name=private,proto3" json:"private,omitempty"`
// True if we were the ones that created the channel.
Initiator bool `protobuf:"varint,18,opt,name=initiator,proto3" json:"initiator,omitempty"`
// A set of flags showing the current state of the channel.
ChanStatusFlags string `protobuf:"bytes,19,opt,name=chan_status_flags,json=chanStatusFlags,proto3" json:"chan_status_flags,omitempty"`
// Deprecated. The minimum satoshis this node is required to reserve in its
// balance.
//
// Deprecated: Marked as deprecated in lightning.proto.
LocalChanReserveSat int64 `protobuf:"varint,20,opt,name=local_chan_reserve_sat,json=localChanReserveSat,proto3" json:"local_chan_reserve_sat,omitempty"`
// Deprecated. The minimum satoshis the other node is required to reserve in
// its balance.
//
// Deprecated: Marked as deprecated in lightning.proto.
RemoteChanReserveSat int64 `protobuf:"varint,21,opt,name=remote_chan_reserve_sat,json=remoteChanReserveSat,proto3" json:"remote_chan_reserve_sat,omitempty"`
// Deprecated. Use commitment_type.
//
// Deprecated: Marked as deprecated in lightning.proto.
StaticRemoteKey bool `protobuf:"varint,22,opt,name=static_remote_key,json=staticRemoteKey,proto3" json:"static_remote_key,omitempty"`
// The commitment type used by this channel.
CommitmentType CommitmentType `protobuf:"varint,26,opt,name=commitment_type,json=commitmentType,proto3,enum=lnrpc.CommitmentType" json:"commitment_type,omitempty"`
// The number of seconds that the channel has been monitored by the channel
// scoring system. Scores are currently not persisted, so this value may be
// less than the lifetime of the channel [EXPERIMENTAL].
Lifetime int64 `protobuf:"varint,23,opt,name=lifetime,proto3" json:"lifetime,omitempty"`
// The number of seconds that the remote peer has been observed as being online
// by the channel scoring system over the lifetime of the channel
// [EXPERIMENTAL].
Uptime int64 `protobuf:"varint,24,opt,name=uptime,proto3" json:"uptime,omitempty"`
// Close address is the address that we will enforce payout to on cooperative
// close if the channel was opened utilizing option upfront shutdown. This
// value can be set on channel open by setting close_address in an open channel
// request. If this value is not set, you can still choose a payout address by
// cooperatively closing with the delivery_address field set.
CloseAddress string `protobuf:"bytes,25,opt,name=close_address,json=closeAddress,proto3" json:"close_address,omitempty"`
// The amount that the initiator of the channel optionally pushed to the remote
// party on channel open. This amount will be zero if the channel initiator did
// not push any funds to the remote peer. If the initiator field is true, we
// pushed this amount to our peer, if it is false, the remote peer pushed this
// amount to us.
PushAmountSat uint64 `protobuf:"varint,27,opt,name=push_amount_sat,json=pushAmountSat,proto3" json:"push_amount_sat,omitempty"`
// This uint32 indicates if this channel is to be considered 'frozen'. A
// frozen channel doest not allow a cooperative channel close by the
// initiator. The thaw_height is the height that this restriction stops
// applying to the channel. This field is optional, not setting it or using a
// value of zero will mean the channel has no additional restrictions. The
// height can be interpreted in two ways: as a relative height if the value is
// less than 500,000, or as an absolute height otherwise.
ThawHeight uint32 `protobuf:"varint,28,opt,name=thaw_height,json=thawHeight,proto3" json:"thaw_height,omitempty"`
// List constraints for the local node.
LocalConstraints *ChannelConstraints `protobuf:"bytes,29,opt,name=local_constraints,json=localConstraints,proto3" json:"local_constraints,omitempty"`
// List constraints for the remote node.
RemoteConstraints *ChannelConstraints `protobuf:"bytes,30,opt,name=remote_constraints,json=remoteConstraints,proto3" json:"remote_constraints,omitempty"`
// This lists out the set of alias short channel ids that exist for a channel.
// This may be empty.
AliasScids []uint64 `protobuf:"varint,31,rep,packed,name=alias_scids,json=aliasScids,proto3" json:"alias_scids,omitempty"`
// Whether or not this is a zero-conf channel.
ZeroConf bool `protobuf:"varint,32,opt,name=zero_conf,json=zeroConf,proto3" json:"zero_conf,omitempty"`
// This is the confirmed / on-chain zero-conf SCID.
ZeroConfConfirmedScid uint64 `protobuf:"varint,33,opt,name=zero_conf_confirmed_scid,json=zeroConfConfirmedScid,proto3" json:"zero_conf_confirmed_scid,omitempty"`
// The configured alias name of our peer.
PeerAlias string `protobuf:"bytes,34,opt,name=peer_alias,json=peerAlias,proto3" json:"peer_alias,omitempty"`
// This is the peer SCID alias.
PeerScidAlias uint64 `protobuf:"varint,35,opt,name=peer_scid_alias,json=peerScidAlias,proto3" json:"peer_scid_alias,omitempty"`
// An optional note-to-self to go along with the channel containing some
// useful information. This is only ever stored locally and in no way impacts
// the channel's operation.
Memo string `protobuf:"bytes,36,opt,name=memo,proto3" json:"memo,omitempty"`
// Custom channel data that might be populated in custom channels.
CustomChannelData []byte `protobuf:"bytes,37,opt,name=custom_channel_data,json=customChannelData,proto3" json:"custom_channel_data,omitempty"`
}
func (x *Channel) Reset() {
*x = Channel{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[41]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Channel) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Channel) ProtoMessage() {}
func (x *Channel) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[41]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Channel.ProtoReflect.Descriptor instead.
func (*Channel) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{41}
}
func (x *Channel) GetActive() bool {
if x != nil {
return x.Active
}
return false
}
func (x *Channel) GetRemotePubkey() string {
if x != nil {
return x.RemotePubkey
}
return ""
}
func (x *Channel) GetChannelPoint() string {
if x != nil {
return x.ChannelPoint
}
return ""
}
func (x *Channel) GetChanId() uint64 {
if x != nil {
return x.ChanId
}
return 0
}
func (x *Channel) GetCapacity() int64 {
if x != nil {
return x.Capacity
}
return 0
}
func (x *Channel) GetLocalBalance() int64 {
if x != nil {
return x.LocalBalance
}
return 0
}
func (x *Channel) GetRemoteBalance() int64 {
if x != nil {
return x.RemoteBalance
}
return 0
}
func (x *Channel) GetCommitFee() int64 {
if x != nil {
return x.CommitFee
}
return 0
}
func (x *Channel) GetCommitWeight() int64 {
if x != nil {
return x.CommitWeight
}
return 0
}
func (x *Channel) GetFeePerKw() int64 {
if x != nil {
return x.FeePerKw
}
return 0
}
func (x *Channel) GetUnsettledBalance() int64 {
if x != nil {
return x.UnsettledBalance
}
return 0
}
func (x *Channel) GetTotalSatoshisSent() int64 {
if x != nil {
return x.TotalSatoshisSent
}
return 0
}
func (x *Channel) GetTotalSatoshisReceived() int64 {
if x != nil {
return x.TotalSatoshisReceived
}
return 0
}
func (x *Channel) GetNumUpdates() uint64 {
if x != nil {
return x.NumUpdates
}
return 0
}
func (x *Channel) GetPendingHtlcs() []*HTLC {
if x != nil {
return x.PendingHtlcs
}
return nil
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *Channel) GetCsvDelay() uint32 {
if x != nil {
return x.CsvDelay
}
return 0
}
func (x *Channel) GetPrivate() bool {
if x != nil {
return x.Private
}
return false
}
func (x *Channel) GetInitiator() bool {
if x != nil {
return x.Initiator
}
return false
}
func (x *Channel) GetChanStatusFlags() string {
if x != nil {
return x.ChanStatusFlags
}
return ""
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *Channel) GetLocalChanReserveSat() int64 {
if x != nil {
return x.LocalChanReserveSat
}
return 0
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *Channel) GetRemoteChanReserveSat() int64 {
if x != nil {
return x.RemoteChanReserveSat
}
return 0
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *Channel) GetStaticRemoteKey() bool {
if x != nil {
return x.StaticRemoteKey
}
return false
}
func (x *Channel) GetCommitmentType() CommitmentType {
if x != nil {
return x.CommitmentType
}
return CommitmentType_UNKNOWN_COMMITMENT_TYPE
}
func (x *Channel) GetLifetime() int64 {
if x != nil {
return x.Lifetime
}
return 0
}
func (x *Channel) GetUptime() int64 {
if x != nil {
return x.Uptime
}
return 0
}
func (x *Channel) GetCloseAddress() string {
if x != nil {
return x.CloseAddress
}
return ""
}
func (x *Channel) GetPushAmountSat() uint64 {
if x != nil {
return x.PushAmountSat
}
return 0
}
func (x *Channel) GetThawHeight() uint32 {
if x != nil {
return x.ThawHeight
}
return 0
}
func (x *Channel) GetLocalConstraints() *ChannelConstraints {
if x != nil {
return x.LocalConstraints
}
return nil
}
func (x *Channel) GetRemoteConstraints() *ChannelConstraints {
if x != nil {
return x.RemoteConstraints
}
return nil
}
func (x *Channel) GetAliasScids() []uint64 {
if x != nil {
return x.AliasScids
}
return nil
}
func (x *Channel) GetZeroConf() bool {
if x != nil {
return x.ZeroConf
}
return false
}
func (x *Channel) GetZeroConfConfirmedScid() uint64 {
if x != nil {
return x.ZeroConfConfirmedScid
}
return 0
}
func (x *Channel) GetPeerAlias() string {
if x != nil {
return x.PeerAlias
}
return ""
}
func (x *Channel) GetPeerScidAlias() uint64 {
if x != nil {
return x.PeerScidAlias
}
return 0
}
func (x *Channel) GetMemo() string {
if x != nil {
return x.Memo
}
return ""
}
func (x *Channel) GetCustomChannelData() []byte {
if x != nil {
return x.CustomChannelData
}
return nil
}
type ListChannelsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ActiveOnly bool `protobuf:"varint,1,opt,name=active_only,json=activeOnly,proto3" json:"active_only,omitempty"`
InactiveOnly bool `protobuf:"varint,2,opt,name=inactive_only,json=inactiveOnly,proto3" json:"inactive_only,omitempty"`
PublicOnly bool `protobuf:"varint,3,opt,name=public_only,json=publicOnly,proto3" json:"public_only,omitempty"`
PrivateOnly bool `protobuf:"varint,4,opt,name=private_only,json=privateOnly,proto3" json:"private_only,omitempty"`
// Filters the response for channels with a target peer's pubkey. If peer is
// empty, all channels will be returned.
Peer []byte `protobuf:"bytes,5,opt,name=peer,proto3" json:"peer,omitempty"`
// Informs the server if the peer alias lookup per channel should be
// enabled. It is turned off by default in order to avoid degradation of
// performance for existing clients.
PeerAliasLookup bool `protobuf:"varint,6,opt,name=peer_alias_lookup,json=peerAliasLookup,proto3" json:"peer_alias_lookup,omitempty"`
}
func (x *ListChannelsRequest) Reset() {
*x = ListChannelsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[42]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListChannelsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListChannelsRequest) ProtoMessage() {}
func (x *ListChannelsRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[42]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListChannelsRequest.ProtoReflect.Descriptor instead.
func (*ListChannelsRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{42}
}
func (x *ListChannelsRequest) GetActiveOnly() bool {
if x != nil {
return x.ActiveOnly
}
return false
}
func (x *ListChannelsRequest) GetInactiveOnly() bool {
if x != nil {
return x.InactiveOnly
}
return false
}
func (x *ListChannelsRequest) GetPublicOnly() bool {
if x != nil {
return x.PublicOnly
}
return false
}
func (x *ListChannelsRequest) GetPrivateOnly() bool {
if x != nil {
return x.PrivateOnly
}
return false
}
func (x *ListChannelsRequest) GetPeer() []byte {
if x != nil {
return x.Peer
}
return nil
}
func (x *ListChannelsRequest) GetPeerAliasLookup() bool {
if x != nil {
return x.PeerAliasLookup
}
return false
}
type ListChannelsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The list of active channels
Channels []*Channel `protobuf:"bytes,11,rep,name=channels,proto3" json:"channels,omitempty"`
}
func (x *ListChannelsResponse) Reset() {
*x = ListChannelsResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[43]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListChannelsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListChannelsResponse) ProtoMessage() {}
func (x *ListChannelsResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[43]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListChannelsResponse.ProtoReflect.Descriptor instead.
func (*ListChannelsResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{43}
}
func (x *ListChannelsResponse) GetChannels() []*Channel {
if x != nil {
return x.Channels
}
return nil
}
type AliasMap struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// For non-zero-conf channels, this is the confirmed SCID. Otherwise, this is
// the first assigned "base" alias.
BaseScid uint64 `protobuf:"varint,1,opt,name=base_scid,json=baseScid,proto3" json:"base_scid,omitempty"`
// The set of all aliases stored for the base SCID.
Aliases []uint64 `protobuf:"varint,2,rep,packed,name=aliases,proto3" json:"aliases,omitempty"`
}
func (x *AliasMap) Reset() {
*x = AliasMap{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[44]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AliasMap) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AliasMap) ProtoMessage() {}
func (x *AliasMap) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[44]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AliasMap.ProtoReflect.Descriptor instead.
func (*AliasMap) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{44}
}
func (x *AliasMap) GetBaseScid() uint64 {
if x != nil {
return x.BaseScid
}
return 0
}
func (x *AliasMap) GetAliases() []uint64 {
if x != nil {
return x.Aliases
}
return nil
}
type ListAliasesRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ListAliasesRequest) Reset() {
*x = ListAliasesRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[45]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListAliasesRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListAliasesRequest) ProtoMessage() {}
func (x *ListAliasesRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[45]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListAliasesRequest.ProtoReflect.Descriptor instead.
func (*ListAliasesRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{45}
}
type ListAliasesResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
AliasMaps []*AliasMap `protobuf:"bytes,1,rep,name=alias_maps,json=aliasMaps,proto3" json:"alias_maps,omitempty"`
}
func (x *ListAliasesResponse) Reset() {
*x = ListAliasesResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[46]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListAliasesResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListAliasesResponse) ProtoMessage() {}
func (x *ListAliasesResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[46]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListAliasesResponse.ProtoReflect.Descriptor instead.
func (*ListAliasesResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{46}
}
func (x *ListAliasesResponse) GetAliasMaps() []*AliasMap {
if x != nil {
return x.AliasMaps
}
return nil
}
type ChannelCloseSummary struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The outpoint (txid:index) of the funding transaction.
ChannelPoint string `protobuf:"bytes,1,opt,name=channel_point,json=channelPoint,proto3" json:"channel_point,omitempty"`
// The unique channel ID for the channel.
ChanId uint64 `protobuf:"varint,2,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"`
// The hash of the genesis block that this channel resides within.
ChainHash string `protobuf:"bytes,3,opt,name=chain_hash,json=chainHash,proto3" json:"chain_hash,omitempty"`
// The txid of the transaction which ultimately closed this channel.
ClosingTxHash string `protobuf:"bytes,4,opt,name=closing_tx_hash,json=closingTxHash,proto3" json:"closing_tx_hash,omitempty"`
// Public key of the remote peer that we formerly had a channel with.
RemotePubkey string `protobuf:"bytes,5,opt,name=remote_pubkey,json=remotePubkey,proto3" json:"remote_pubkey,omitempty"`
// Total capacity of the channel.
Capacity int64 `protobuf:"varint,6,opt,name=capacity,proto3" json:"capacity,omitempty"`
// Height at which the funding transaction was spent.
CloseHeight uint32 `protobuf:"varint,7,opt,name=close_height,json=closeHeight,proto3" json:"close_height,omitempty"`
// Settled balance at the time of channel closure
SettledBalance int64 `protobuf:"varint,8,opt,name=settled_balance,json=settledBalance,proto3" json:"settled_balance,omitempty"`
// The sum of all the time-locked outputs at the time of channel closure
TimeLockedBalance int64 `protobuf:"varint,9,opt,name=time_locked_balance,json=timeLockedBalance,proto3" json:"time_locked_balance,omitempty"`
// Details on how the channel was closed.
CloseType ChannelCloseSummary_ClosureType `protobuf:"varint,10,opt,name=close_type,json=closeType,proto3,enum=lnrpc.ChannelCloseSummary_ClosureType" json:"close_type,omitempty"`
// Open initiator is the party that initiated opening the channel. Note that
// this value may be unknown if the channel was closed before we migrated to
// store open channel information after close.
OpenInitiator Initiator `protobuf:"varint,11,opt,name=open_initiator,json=openInitiator,proto3,enum=lnrpc.Initiator" json:"open_initiator,omitempty"`
// Close initiator indicates which party initiated the close. This value will
// be unknown for channels that were cooperatively closed before we started
// tracking cooperative close initiators. Note that this indicates which party
// initiated a close, and it is possible for both to initiate cooperative or
// force closes, although only one party's close will be confirmed on chain.
CloseInitiator Initiator `protobuf:"varint,12,opt,name=close_initiator,json=closeInitiator,proto3,enum=lnrpc.Initiator" json:"close_initiator,omitempty"`
Resolutions []*Resolution `protobuf:"bytes,13,rep,name=resolutions,proto3" json:"resolutions,omitempty"`
// This lists out the set of alias short channel ids that existed for the
// closed channel. This may be empty.
AliasScids []uint64 `protobuf:"varint,14,rep,packed,name=alias_scids,json=aliasScids,proto3" json:"alias_scids,omitempty"`
// The confirmed SCID for a zero-conf channel.
ZeroConfConfirmedScid uint64 `protobuf:"varint,15,opt,name=zero_conf_confirmed_scid,json=zeroConfConfirmedScid,proto3" json:"zero_conf_confirmed_scid,omitempty"`
// The TLV encoded custom channel data records for this output, which might
// be set for custom channels.
CustomChannelData []byte `protobuf:"bytes,16,opt,name=custom_channel_data,json=customChannelData,proto3" json:"custom_channel_data,omitempty"`
}
func (x *ChannelCloseSummary) Reset() {
*x = ChannelCloseSummary{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[47]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChannelCloseSummary) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelCloseSummary) ProtoMessage() {}
func (x *ChannelCloseSummary) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[47]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelCloseSummary.ProtoReflect.Descriptor instead.
func (*ChannelCloseSummary) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{47}
}
func (x *ChannelCloseSummary) GetChannelPoint() string {
if x != nil {
return x.ChannelPoint
}
return ""
}
func (x *ChannelCloseSummary) GetChanId() uint64 {
if x != nil {
return x.ChanId
}
return 0
}
func (x *ChannelCloseSummary) GetChainHash() string {
if x != nil {
return x.ChainHash
}
return ""
}
func (x *ChannelCloseSummary) GetClosingTxHash() string {
if x != nil {
return x.ClosingTxHash
}
return ""
}
func (x *ChannelCloseSummary) GetRemotePubkey() string {
if x != nil {
return x.RemotePubkey
}
return ""
}
func (x *ChannelCloseSummary) GetCapacity() int64 {
if x != nil {
return x.Capacity
}
return 0
}
func (x *ChannelCloseSummary) GetCloseHeight() uint32 {
if x != nil {
return x.CloseHeight
}
return 0
}
func (x *ChannelCloseSummary) GetSettledBalance() int64 {
if x != nil {
return x.SettledBalance
}
return 0
}
func (x *ChannelCloseSummary) GetTimeLockedBalance() int64 {
if x != nil {
return x.TimeLockedBalance
}
return 0
}
func (x *ChannelCloseSummary) GetCloseType() ChannelCloseSummary_ClosureType {
if x != nil {
return x.CloseType
}
return ChannelCloseSummary_COOPERATIVE_CLOSE
}
func (x *ChannelCloseSummary) GetOpenInitiator() Initiator {
if x != nil {
return x.OpenInitiator
}
return Initiator_INITIATOR_UNKNOWN
}
func (x *ChannelCloseSummary) GetCloseInitiator() Initiator {
if x != nil {
return x.CloseInitiator
}
return Initiator_INITIATOR_UNKNOWN
}
func (x *ChannelCloseSummary) GetResolutions() []*Resolution {
if x != nil {
return x.Resolutions
}
return nil
}
func (x *ChannelCloseSummary) GetAliasScids() []uint64 {
if x != nil {
return x.AliasScids
}
return nil
}
func (x *ChannelCloseSummary) GetZeroConfConfirmedScid() uint64 {
if x != nil {
return x.ZeroConfConfirmedScid
}
return 0
}
func (x *ChannelCloseSummary) GetCustomChannelData() []byte {
if x != nil {
return x.CustomChannelData
}
return nil
}
type Resolution struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The type of output we are resolving.
ResolutionType ResolutionType `protobuf:"varint,1,opt,name=resolution_type,json=resolutionType,proto3,enum=lnrpc.ResolutionType" json:"resolution_type,omitempty"`
// The outcome of our on chain action that resolved the outpoint.
Outcome ResolutionOutcome `protobuf:"varint,2,opt,name=outcome,proto3,enum=lnrpc.ResolutionOutcome" json:"outcome,omitempty"`
// The outpoint that was spent by the resolution.
Outpoint *OutPoint `protobuf:"bytes,3,opt,name=outpoint,proto3" json:"outpoint,omitempty"`
// The amount that was claimed by the resolution.
AmountSat uint64 `protobuf:"varint,4,opt,name=amount_sat,json=amountSat,proto3" json:"amount_sat,omitempty"`
// The hex-encoded transaction ID of the sweep transaction that spent the
// output.
SweepTxid string `protobuf:"bytes,5,opt,name=sweep_txid,json=sweepTxid,proto3" json:"sweep_txid,omitempty"`
}
func (x *Resolution) Reset() {
*x = Resolution{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[48]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Resolution) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Resolution) ProtoMessage() {}
func (x *Resolution) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[48]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Resolution.ProtoReflect.Descriptor instead.
func (*Resolution) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{48}
}
func (x *Resolution) GetResolutionType() ResolutionType {
if x != nil {
return x.ResolutionType
}
return ResolutionType_TYPE_UNKNOWN
}
func (x *Resolution) GetOutcome() ResolutionOutcome {
if x != nil {
return x.Outcome
}
return ResolutionOutcome_OUTCOME_UNKNOWN
}
func (x *Resolution) GetOutpoint() *OutPoint {
if x != nil {
return x.Outpoint
}
return nil
}
func (x *Resolution) GetAmountSat() uint64 {
if x != nil {
return x.AmountSat
}
return 0
}
func (x *Resolution) GetSweepTxid() string {
if x != nil {
return x.SweepTxid
}
return ""
}
type ClosedChannelsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Cooperative bool `protobuf:"varint,1,opt,name=cooperative,proto3" json:"cooperative,omitempty"`
LocalForce bool `protobuf:"varint,2,opt,name=local_force,json=localForce,proto3" json:"local_force,omitempty"`
RemoteForce bool `protobuf:"varint,3,opt,name=remote_force,json=remoteForce,proto3" json:"remote_force,omitempty"`
Breach bool `protobuf:"varint,4,opt,name=breach,proto3" json:"breach,omitempty"`
FundingCanceled bool `protobuf:"varint,5,opt,name=funding_canceled,json=fundingCanceled,proto3" json:"funding_canceled,omitempty"`
Abandoned bool `protobuf:"varint,6,opt,name=abandoned,proto3" json:"abandoned,omitempty"`
}
func (x *ClosedChannelsRequest) Reset() {
*x = ClosedChannelsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[49]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ClosedChannelsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ClosedChannelsRequest) ProtoMessage() {}
func (x *ClosedChannelsRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[49]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ClosedChannelsRequest.ProtoReflect.Descriptor instead.
func (*ClosedChannelsRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{49}
}
func (x *ClosedChannelsRequest) GetCooperative() bool {
if x != nil {
return x.Cooperative
}
return false
}
func (x *ClosedChannelsRequest) GetLocalForce() bool {
if x != nil {
return x.LocalForce
}
return false
}
func (x *ClosedChannelsRequest) GetRemoteForce() bool {
if x != nil {
return x.RemoteForce
}
return false
}
func (x *ClosedChannelsRequest) GetBreach() bool {
if x != nil {
return x.Breach
}
return false
}
func (x *ClosedChannelsRequest) GetFundingCanceled() bool {
if x != nil {
return x.FundingCanceled
}
return false
}
func (x *ClosedChannelsRequest) GetAbandoned() bool {
if x != nil {
return x.Abandoned
}
return false
}
type ClosedChannelsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Channels []*ChannelCloseSummary `protobuf:"bytes,1,rep,name=channels,proto3" json:"channels,omitempty"`
}
func (x *ClosedChannelsResponse) Reset() {
*x = ClosedChannelsResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[50]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ClosedChannelsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ClosedChannelsResponse) ProtoMessage() {}
func (x *ClosedChannelsResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[50]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ClosedChannelsResponse.ProtoReflect.Descriptor instead.
func (*ClosedChannelsResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{50}
}
func (x *ClosedChannelsResponse) GetChannels() []*ChannelCloseSummary {
if x != nil {
return x.Channels
}
return nil
}
type Peer struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The identity pubkey of the peer
PubKey string `protobuf:"bytes,1,opt,name=pub_key,json=pubKey,proto3" json:"pub_key,omitempty"`
// Network address of the peer; eg `127.0.0.1:10011`
Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"`
// Bytes of data transmitted to this peer
BytesSent uint64 `protobuf:"varint,4,opt,name=bytes_sent,json=bytesSent,proto3" json:"bytes_sent,omitempty"`
// Bytes of data transmitted from this peer
BytesRecv uint64 `protobuf:"varint,5,opt,name=bytes_recv,json=bytesRecv,proto3" json:"bytes_recv,omitempty"`
// Satoshis sent to this peer
SatSent int64 `protobuf:"varint,6,opt,name=sat_sent,json=satSent,proto3" json:"sat_sent,omitempty"`
// Satoshis received from this peer
SatRecv int64 `protobuf:"varint,7,opt,name=sat_recv,json=satRecv,proto3" json:"sat_recv,omitempty"`
// A channel is inbound if the counterparty initiated the channel
Inbound bool `protobuf:"varint,8,opt,name=inbound,proto3" json:"inbound,omitempty"`
// Ping time to this peer
PingTime int64 `protobuf:"varint,9,opt,name=ping_time,json=pingTime,proto3" json:"ping_time,omitempty"`
// The type of sync we are currently performing with this peer.
SyncType Peer_SyncType `protobuf:"varint,10,opt,name=sync_type,json=syncType,proto3,enum=lnrpc.Peer_SyncType" json:"sync_type,omitempty"`
// Features advertised by the remote peer in their init message.
Features map[uint32]*Feature `protobuf:"bytes,11,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
// The latest errors received from our peer with timestamps, limited to the 10
// most recent errors. These errors are tracked across peer connections, but
// are not persisted across lnd restarts. Note that these errors are only
// stored for peers that we have channels open with, to prevent peers from
// spamming us with errors at no cost.
Errors []*TimestampedError `protobuf:"bytes,12,rep,name=errors,proto3" json:"errors,omitempty"`
// The number of times we have recorded this peer going offline or coming
// online, recorded across restarts. Note that this value is decreased over
// time if the peer has not recently flapped, so that we can forgive peers
// with historically high flap counts.
FlapCount int32 `protobuf:"varint,13,opt,name=flap_count,json=flapCount,proto3" json:"flap_count,omitempty"`
// The timestamp of the last flap we observed for this peer. If this value is
// zero, we have not observed any flaps for this peer.
LastFlapNs int64 `protobuf:"varint,14,opt,name=last_flap_ns,json=lastFlapNs,proto3" json:"last_flap_ns,omitempty"`
// The last ping payload the peer has sent to us.
LastPingPayload []byte `protobuf:"bytes,15,opt,name=last_ping_payload,json=lastPingPayload,proto3" json:"last_ping_payload,omitempty"`
}
func (x *Peer) Reset() {
*x = Peer{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[51]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Peer) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Peer) ProtoMessage() {}
func (x *Peer) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[51]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Peer.ProtoReflect.Descriptor instead.
func (*Peer) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{51}
}
func (x *Peer) GetPubKey() string {
if x != nil {
return x.PubKey
}
return ""
}
func (x *Peer) GetAddress() string {
if x != nil {
return x.Address
}
return ""
}
func (x *Peer) GetBytesSent() uint64 {
if x != nil {
return x.BytesSent
}
return 0
}
func (x *Peer) GetBytesRecv() uint64 {
if x != nil {
return x.BytesRecv
}
return 0
}
func (x *Peer) GetSatSent() int64 {
if x != nil {
return x.SatSent
}
return 0
}
func (x *Peer) GetSatRecv() int64 {
if x != nil {
return x.SatRecv
}
return 0
}
func (x *Peer) GetInbound() bool {
if x != nil {
return x.Inbound
}
return false
}
func (x *Peer) GetPingTime() int64 {
if x != nil {
return x.PingTime
}
return 0
}
func (x *Peer) GetSyncType() Peer_SyncType {
if x != nil {
return x.SyncType
}
return Peer_UNKNOWN_SYNC
}
func (x *Peer) GetFeatures() map[uint32]*Feature {
if x != nil {
return x.Features
}
return nil
}
func (x *Peer) GetErrors() []*TimestampedError {
if x != nil {
return x.Errors
}
return nil
}
func (x *Peer) GetFlapCount() int32 {
if x != nil {
return x.FlapCount
}
return 0
}
func (x *Peer) GetLastFlapNs() int64 {
if x != nil {
return x.LastFlapNs
}
return 0
}
func (x *Peer) GetLastPingPayload() []byte {
if x != nil {
return x.LastPingPayload
}
return nil
}
type TimestampedError struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The unix timestamp in seconds when the error occurred.
Timestamp uint64 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
// The string representation of the error sent by our peer.
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"`
}
func (x *TimestampedError) Reset() {
*x = TimestampedError{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[52]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TimestampedError) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TimestampedError) ProtoMessage() {}
func (x *TimestampedError) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[52]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TimestampedError.ProtoReflect.Descriptor instead.
func (*TimestampedError) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{52}
}
func (x *TimestampedError) GetTimestamp() uint64 {
if x != nil {
return x.Timestamp
}
return 0
}
func (x *TimestampedError) GetError() string {
if x != nil {
return x.Error
}
return ""
}
type ListPeersRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// If true, only the last error that our peer sent us will be returned with
// the peer's information, rather than the full set of historic errors we have
// stored.
LatestError bool `protobuf:"varint,1,opt,name=latest_error,json=latestError,proto3" json:"latest_error,omitempty"`
}
func (x *ListPeersRequest) Reset() {
*x = ListPeersRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[53]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListPeersRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListPeersRequest) ProtoMessage() {}
func (x *ListPeersRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[53]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListPeersRequest.ProtoReflect.Descriptor instead.
func (*ListPeersRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{53}
}
func (x *ListPeersRequest) GetLatestError() bool {
if x != nil {
return x.LatestError
}
return false
}
type ListPeersResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The list of currently connected peers
Peers []*Peer `protobuf:"bytes,1,rep,name=peers,proto3" json:"peers,omitempty"`
}
func (x *ListPeersResponse) Reset() {
*x = ListPeersResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[54]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListPeersResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListPeersResponse) ProtoMessage() {}
func (x *ListPeersResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[54]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListPeersResponse.ProtoReflect.Descriptor instead.
func (*ListPeersResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{54}
}
func (x *ListPeersResponse) GetPeers() []*Peer {
if x != nil {
return x.Peers
}
return nil
}
type PeerEventSubscription struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *PeerEventSubscription) Reset() {
*x = PeerEventSubscription{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[55]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PeerEventSubscription) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PeerEventSubscription) ProtoMessage() {}
func (x *PeerEventSubscription) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[55]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PeerEventSubscription.ProtoReflect.Descriptor instead.
func (*PeerEventSubscription) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{55}
}
type PeerEvent struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The identity pubkey of the peer.
PubKey string `protobuf:"bytes,1,opt,name=pub_key,json=pubKey,proto3" json:"pub_key,omitempty"`
Type PeerEvent_EventType `protobuf:"varint,2,opt,name=type,proto3,enum=lnrpc.PeerEvent_EventType" json:"type,omitempty"`
}
func (x *PeerEvent) Reset() {
*x = PeerEvent{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[56]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PeerEvent) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PeerEvent) ProtoMessage() {}
func (x *PeerEvent) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[56]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PeerEvent.ProtoReflect.Descriptor instead.
func (*PeerEvent) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{56}
}
func (x *PeerEvent) GetPubKey() string {
if x != nil {
return x.PubKey
}
return ""
}
func (x *PeerEvent) GetType() PeerEvent_EventType {
if x != nil {
return x.Type
}
return PeerEvent_PEER_ONLINE
}
type GetInfoRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *GetInfoRequest) Reset() {
*x = GetInfoRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[57]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetInfoRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetInfoRequest) ProtoMessage() {}
func (x *GetInfoRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[57]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetInfoRequest.ProtoReflect.Descriptor instead.
func (*GetInfoRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{57}
}
type GetInfoResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The version of the LND software that the node is running.
Version string `protobuf:"bytes,14,opt,name=version,proto3" json:"version,omitempty"`
// The SHA1 commit hash that the daemon is compiled with.
CommitHash string `protobuf:"bytes,20,opt,name=commit_hash,json=commitHash,proto3" json:"commit_hash,omitempty"`
// The identity pubkey of the current node.
IdentityPubkey string `protobuf:"bytes,1,opt,name=identity_pubkey,json=identityPubkey,proto3" json:"identity_pubkey,omitempty"`
// If applicable, the alias of the current node, e.g. "bob"
Alias string `protobuf:"bytes,2,opt,name=alias,proto3" json:"alias,omitempty"`
// The color of the current node in hex code format
Color string `protobuf:"bytes,17,opt,name=color,proto3" json:"color,omitempty"`
// Number of pending channels
NumPendingChannels uint32 `protobuf:"varint,3,opt,name=num_pending_channels,json=numPendingChannels,proto3" json:"num_pending_channels,omitempty"`
// Number of active channels
NumActiveChannels uint32 `protobuf:"varint,4,opt,name=num_active_channels,json=numActiveChannels,proto3" json:"num_active_channels,omitempty"`
// Number of inactive channels
NumInactiveChannels uint32 `protobuf:"varint,15,opt,name=num_inactive_channels,json=numInactiveChannels,proto3" json:"num_inactive_channels,omitempty"`
// Number of peers
NumPeers uint32 `protobuf:"varint,5,opt,name=num_peers,json=numPeers,proto3" json:"num_peers,omitempty"`
// The node's current view of the height of the best block
BlockHeight uint32 `protobuf:"varint,6,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"`
// The node's current view of the hash of the best block
BlockHash string `protobuf:"bytes,8,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"`
// Timestamp of the block best known to the wallet
BestHeaderTimestamp int64 `protobuf:"varint,13,opt,name=best_header_timestamp,json=bestHeaderTimestamp,proto3" json:"best_header_timestamp,omitempty"`
// Whether the wallet's view is synced to the main chain
SyncedToChain bool `protobuf:"varint,9,opt,name=synced_to_chain,json=syncedToChain,proto3" json:"synced_to_chain,omitempty"`
// Whether we consider ourselves synced with the public channel graph.
SyncedToGraph bool `protobuf:"varint,18,opt,name=synced_to_graph,json=syncedToGraph,proto3" json:"synced_to_graph,omitempty"`
// Whether the current node is connected to testnet or testnet4. This field is
// deprecated and the network field should be used instead.
//
// Deprecated: Marked as deprecated in lightning.proto.
Testnet bool `protobuf:"varint,10,opt,name=testnet,proto3" json:"testnet,omitempty"`
// A list of active chains the node is connected to. This will only
// ever contain a single entry since LND will only ever have a single
// chain backend during its lifetime.
Chains []*Chain `protobuf:"bytes,16,rep,name=chains,proto3" json:"chains,omitempty"`
// The URIs of the current node.
Uris []string `protobuf:"bytes,12,rep,name=uris,proto3" json:"uris,omitempty"`
// Features that our node has advertised in our init message, node
// announcements and invoices.
Features map[uint32]*Feature `protobuf:"bytes,19,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
// Indicates whether the HTLC interceptor API is in always-on mode.
RequireHtlcInterceptor bool `protobuf:"varint,21,opt,name=require_htlc_interceptor,json=requireHtlcInterceptor,proto3" json:"require_htlc_interceptor,omitempty"`
// Indicates whether final htlc resolutions are stored on disk.
StoreFinalHtlcResolutions bool `protobuf:"varint,22,opt,name=store_final_htlc_resolutions,json=storeFinalHtlcResolutions,proto3" json:"store_final_htlc_resolutions,omitempty"`
}
func (x *GetInfoResponse) Reset() {
*x = GetInfoResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[58]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetInfoResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetInfoResponse) ProtoMessage() {}
func (x *GetInfoResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[58]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetInfoResponse.ProtoReflect.Descriptor instead.
func (*GetInfoResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{58}
}
func (x *GetInfoResponse) GetVersion() string {
if x != nil {
return x.Version
}
return ""
}
func (x *GetInfoResponse) GetCommitHash() string {
if x != nil {
return x.CommitHash
}
return ""
}
func (x *GetInfoResponse) GetIdentityPubkey() string {
if x != nil {
return x.IdentityPubkey
}
return ""
}
func (x *GetInfoResponse) GetAlias() string {
if x != nil {
return x.Alias
}
return ""
}
func (x *GetInfoResponse) GetColor() string {
if x != nil {
return x.Color
}
return ""
}
func (x *GetInfoResponse) GetNumPendingChannels() uint32 {
if x != nil {
return x.NumPendingChannels
}
return 0
}
func (x *GetInfoResponse) GetNumActiveChannels() uint32 {
if x != nil {
return x.NumActiveChannels
}
return 0
}
func (x *GetInfoResponse) GetNumInactiveChannels() uint32 {
if x != nil {
return x.NumInactiveChannels
}
return 0
}
func (x *GetInfoResponse) GetNumPeers() uint32 {
if x != nil {
return x.NumPeers
}
return 0
}
func (x *GetInfoResponse) GetBlockHeight() uint32 {
if x != nil {
return x.BlockHeight
}
return 0
}
func (x *GetInfoResponse) GetBlockHash() string {
if x != nil {
return x.BlockHash
}
return ""
}
func (x *GetInfoResponse) GetBestHeaderTimestamp() int64 {
if x != nil {
return x.BestHeaderTimestamp
}
return 0
}
func (x *GetInfoResponse) GetSyncedToChain() bool {
if x != nil {
return x.SyncedToChain
}
return false
}
func (x *GetInfoResponse) GetSyncedToGraph() bool {
if x != nil {
return x.SyncedToGraph
}
return false
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *GetInfoResponse) GetTestnet() bool {
if x != nil {
return x.Testnet
}
return false
}
func (x *GetInfoResponse) GetChains() []*Chain {
if x != nil {
return x.Chains
}
return nil
}
func (x *GetInfoResponse) GetUris() []string {
if x != nil {
return x.Uris
}
return nil
}
func (x *GetInfoResponse) GetFeatures() map[uint32]*Feature {
if x != nil {
return x.Features
}
return nil
}
func (x *GetInfoResponse) GetRequireHtlcInterceptor() bool {
if x != nil {
return x.RequireHtlcInterceptor
}
return false
}
func (x *GetInfoResponse) GetStoreFinalHtlcResolutions() bool {
if x != nil {
return x.StoreFinalHtlcResolutions
}
return false
}
type GetDebugInfoRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *GetDebugInfoRequest) Reset() {
*x = GetDebugInfoRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[59]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetDebugInfoRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetDebugInfoRequest) ProtoMessage() {}
func (x *GetDebugInfoRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[59]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetDebugInfoRequest.ProtoReflect.Descriptor instead.
func (*GetDebugInfoRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{59}
}
type GetDebugInfoResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Config map[string]string `protobuf:"bytes,1,rep,name=config,proto3" json:"config,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Log []string `protobuf:"bytes,2,rep,name=log,proto3" json:"log,omitempty"`
}
func (x *GetDebugInfoResponse) Reset() {
*x = GetDebugInfoResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[60]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetDebugInfoResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetDebugInfoResponse) ProtoMessage() {}
func (x *GetDebugInfoResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[60]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetDebugInfoResponse.ProtoReflect.Descriptor instead.
func (*GetDebugInfoResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{60}
}
func (x *GetDebugInfoResponse) GetConfig() map[string]string {
if x != nil {
return x.Config
}
return nil
}
func (x *GetDebugInfoResponse) GetLog() []string {
if x != nil {
return x.Log
}
return nil
}
type GetRecoveryInfoRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *GetRecoveryInfoRequest) Reset() {
*x = GetRecoveryInfoRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[61]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetRecoveryInfoRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetRecoveryInfoRequest) ProtoMessage() {}
func (x *GetRecoveryInfoRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[61]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetRecoveryInfoRequest.ProtoReflect.Descriptor instead.
func (*GetRecoveryInfoRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{61}
}
type GetRecoveryInfoResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Whether the wallet is in recovery mode
RecoveryMode bool `protobuf:"varint,1,opt,name=recovery_mode,json=recoveryMode,proto3" json:"recovery_mode,omitempty"`
// Whether the wallet recovery progress is finished
RecoveryFinished bool `protobuf:"varint,2,opt,name=recovery_finished,json=recoveryFinished,proto3" json:"recovery_finished,omitempty"`
// The recovery progress, ranging from 0 to 1.
Progress float64 `protobuf:"fixed64,3,opt,name=progress,proto3" json:"progress,omitempty"`
}
func (x *GetRecoveryInfoResponse) Reset() {
*x = GetRecoveryInfoResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[62]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetRecoveryInfoResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetRecoveryInfoResponse) ProtoMessage() {}
func (x *GetRecoveryInfoResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[62]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetRecoveryInfoResponse.ProtoReflect.Descriptor instead.
func (*GetRecoveryInfoResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{62}
}
func (x *GetRecoveryInfoResponse) GetRecoveryMode() bool {
if x != nil {
return x.RecoveryMode
}
return false
}
func (x *GetRecoveryInfoResponse) GetRecoveryFinished() bool {
if x != nil {
return x.RecoveryFinished
}
return false
}
func (x *GetRecoveryInfoResponse) GetProgress() float64 {
if x != nil {
return x.Progress
}
return 0
}
type Chain struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Deprecated. The chain is now always assumed to be bitcoin.
// The blockchain the node is on (must be bitcoin)
//
// Deprecated: Marked as deprecated in lightning.proto.
Chain string `protobuf:"bytes,1,opt,name=chain,proto3" json:"chain,omitempty"`
// The network the node is on (eg regtest, testnet, mainnet)
Network string `protobuf:"bytes,2,opt,name=network,proto3" json:"network,omitempty"`
}
func (x *Chain) Reset() {
*x = Chain{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[63]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Chain) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Chain) ProtoMessage() {}
func (x *Chain) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[63]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Chain.ProtoReflect.Descriptor instead.
func (*Chain) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{63}
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *Chain) GetChain() string {
if x != nil {
return x.Chain
}
return ""
}
func (x *Chain) GetNetwork() string {
if x != nil {
return x.Network
}
return ""
}
type ConfirmationUpdate struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
BlockSha []byte `protobuf:"bytes,1,opt,name=block_sha,json=blockSha,proto3" json:"block_sha,omitempty"`
BlockHeight int32 `protobuf:"varint,2,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"`
NumConfsLeft uint32 `protobuf:"varint,3,opt,name=num_confs_left,json=numConfsLeft,proto3" json:"num_confs_left,omitempty"`
}
func (x *ConfirmationUpdate) Reset() {
*x = ConfirmationUpdate{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[64]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ConfirmationUpdate) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ConfirmationUpdate) ProtoMessage() {}
func (x *ConfirmationUpdate) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[64]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ConfirmationUpdate.ProtoReflect.Descriptor instead.
func (*ConfirmationUpdate) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{64}
}
func (x *ConfirmationUpdate) GetBlockSha() []byte {
if x != nil {
return x.BlockSha
}
return nil
}
func (x *ConfirmationUpdate) GetBlockHeight() int32 {
if x != nil {
return x.BlockHeight
}
return 0
}
func (x *ConfirmationUpdate) GetNumConfsLeft() uint32 {
if x != nil {
return x.NumConfsLeft
}
return 0
}
type ChannelOpenUpdate struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ChannelPoint *ChannelPoint `protobuf:"bytes,1,opt,name=channel_point,json=channelPoint,proto3" json:"channel_point,omitempty"`
}
func (x *ChannelOpenUpdate) Reset() {
*x = ChannelOpenUpdate{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[65]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChannelOpenUpdate) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelOpenUpdate) ProtoMessage() {}
func (x *ChannelOpenUpdate) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[65]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelOpenUpdate.ProtoReflect.Descriptor instead.
func (*ChannelOpenUpdate) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{65}
}
func (x *ChannelOpenUpdate) GetChannelPoint() *ChannelPoint {
if x != nil {
return x.ChannelPoint
}
return nil
}
type CloseOutput struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The amount in satoshi of this close output. This amount is the final
// commitment balance of the channel and the actual amount paid out on chain
// might be smaller due to subtracted fees.
AmountSat int64 `protobuf:"varint,1,opt,name=amount_sat,json=amountSat,proto3" json:"amount_sat,omitempty"`
// The pkScript of the close output.
PkScript []byte `protobuf:"bytes,2,opt,name=pk_script,json=pkScript,proto3" json:"pk_script,omitempty"`
// Whether this output is for the local or remote node.
IsLocal bool `protobuf:"varint,3,opt,name=is_local,json=isLocal,proto3" json:"is_local,omitempty"`
// The TLV encoded custom channel data records for this output, which might
// be set for custom channels.
CustomChannelData []byte `protobuf:"bytes,4,opt,name=custom_channel_data,json=customChannelData,proto3" json:"custom_channel_data,omitempty"`
}
func (x *CloseOutput) Reset() {
*x = CloseOutput{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[66]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CloseOutput) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CloseOutput) ProtoMessage() {}
func (x *CloseOutput) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[66]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CloseOutput.ProtoReflect.Descriptor instead.
func (*CloseOutput) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{66}
}
func (x *CloseOutput) GetAmountSat() int64 {
if x != nil {
return x.AmountSat
}
return 0
}
func (x *CloseOutput) GetPkScript() []byte {
if x != nil {
return x.PkScript
}
return nil
}
func (x *CloseOutput) GetIsLocal() bool {
if x != nil {
return x.IsLocal
}
return false
}
func (x *CloseOutput) GetCustomChannelData() []byte {
if x != nil {
return x.CustomChannelData
}
return nil
}
type ChannelCloseUpdate struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ClosingTxid []byte `protobuf:"bytes,1,opt,name=closing_txid,json=closingTxid,proto3" json:"closing_txid,omitempty"`
Success bool `protobuf:"varint,2,opt,name=success,proto3" json:"success,omitempty"`
// The local channel close output. If the local channel balance was dust to
// begin with, this output will not be set.
LocalCloseOutput *CloseOutput `protobuf:"bytes,3,opt,name=local_close_output,json=localCloseOutput,proto3" json:"local_close_output,omitempty"`
// The remote channel close output. If the remote channel balance was dust
// to begin with, this output will not be set.
RemoteCloseOutput *CloseOutput `protobuf:"bytes,4,opt,name=remote_close_output,json=remoteCloseOutput,proto3" json:"remote_close_output,omitempty"`
// Any additional outputs that might be added for custom channel types.
AdditionalOutputs []*CloseOutput `protobuf:"bytes,5,rep,name=additional_outputs,json=additionalOutputs,proto3" json:"additional_outputs,omitempty"`
}
func (x *ChannelCloseUpdate) Reset() {
*x = ChannelCloseUpdate{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[67]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChannelCloseUpdate) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelCloseUpdate) ProtoMessage() {}
func (x *ChannelCloseUpdate) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[67]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelCloseUpdate.ProtoReflect.Descriptor instead.
func (*ChannelCloseUpdate) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{67}
}
func (x *ChannelCloseUpdate) GetClosingTxid() []byte {
if x != nil {
return x.ClosingTxid
}
return nil
}
func (x *ChannelCloseUpdate) GetSuccess() bool {
if x != nil {
return x.Success
}
return false
}
func (x *ChannelCloseUpdate) GetLocalCloseOutput() *CloseOutput {
if x != nil {
return x.LocalCloseOutput
}
return nil
}
func (x *ChannelCloseUpdate) GetRemoteCloseOutput() *CloseOutput {
if x != nil {
return x.RemoteCloseOutput
}
return nil
}
func (x *ChannelCloseUpdate) GetAdditionalOutputs() []*CloseOutput {
if x != nil {
return x.AdditionalOutputs
}
return nil
}
type CloseChannelRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The outpoint (txid:index) of the funding transaction. With this value, Bob
// will be able to generate a signature for Alice's version of the commitment
// transaction.
ChannelPoint *ChannelPoint `protobuf:"bytes,1,opt,name=channel_point,json=channelPoint,proto3" json:"channel_point,omitempty"`
// If true, then the channel will be closed forcibly. This means the
// current commitment transaction will be signed and broadcast.
Force bool `protobuf:"varint,2,opt,name=force,proto3" json:"force,omitempty"`
// The target number of blocks that the closure transaction should be
// confirmed by.
TargetConf int32 `protobuf:"varint,3,opt,name=target_conf,json=targetConf,proto3" json:"target_conf,omitempty"`
// Deprecated, use sat_per_vbyte.
// A manual fee rate set in sat/vbyte that should be used when crafting the
// closure transaction.
//
// Deprecated: Marked as deprecated in lightning.proto.
SatPerByte int64 `protobuf:"varint,4,opt,name=sat_per_byte,json=satPerByte,proto3" json:"sat_per_byte,omitempty"`
// An optional address to send funds to in the case of a cooperative close.
// If the channel was opened with an upfront shutdown script and this field
// is set, the request to close will fail because the channel must pay out
// to the upfront shutdown addresss.
DeliveryAddress string `protobuf:"bytes,5,opt,name=delivery_address,json=deliveryAddress,proto3" json:"delivery_address,omitempty"`
// A manual fee rate set in sat/vbyte that should be used when crafting the
// closure transaction.
SatPerVbyte uint64 `protobuf:"varint,6,opt,name=sat_per_vbyte,json=satPerVbyte,proto3" json:"sat_per_vbyte,omitempty"`
// The maximum fee rate the closer is willing to pay.
//
// NOTE: This field is only respected if we're the initiator of the channel.
MaxFeePerVbyte uint64 `protobuf:"varint,7,opt,name=max_fee_per_vbyte,json=maxFeePerVbyte,proto3" json:"max_fee_per_vbyte,omitempty"`
// If true, then the rpc call will not block while it awaits a closing txid
// to be broadcasted to the mempool. To obtain the closing tx one has to
// listen to the stream for the particular updates. Moreover if a coop close
// is specified and this flag is set to true the coop closing flow will be
// initiated even if HTLCs are active on the channel. The channel will wait
// until all HTLCs are resolved and then start the coop closing process. The
// channel will be disabled in the meantime and will disallow any new HTLCs.
NoWait bool `protobuf:"varint,8,opt,name=no_wait,json=noWait,proto3" json:"no_wait,omitempty"`
}
func (x *CloseChannelRequest) Reset() {
*x = CloseChannelRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[68]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CloseChannelRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CloseChannelRequest) ProtoMessage() {}
func (x *CloseChannelRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[68]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CloseChannelRequest.ProtoReflect.Descriptor instead.
func (*CloseChannelRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{68}
}
func (x *CloseChannelRequest) GetChannelPoint() *ChannelPoint {
if x != nil {
return x.ChannelPoint
}
return nil
}
func (x *CloseChannelRequest) GetForce() bool {
if x != nil {
return x.Force
}
return false
}
func (x *CloseChannelRequest) GetTargetConf() int32 {
if x != nil {
return x.TargetConf
}
return 0
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *CloseChannelRequest) GetSatPerByte() int64 {
if x != nil {
return x.SatPerByte
}
return 0
}
func (x *CloseChannelRequest) GetDeliveryAddress() string {
if x != nil {
return x.DeliveryAddress
}
return ""
}
func (x *CloseChannelRequest) GetSatPerVbyte() uint64 {
if x != nil {
return x.SatPerVbyte
}
return 0
}
func (x *CloseChannelRequest) GetMaxFeePerVbyte() uint64 {
if x != nil {
return x.MaxFeePerVbyte
}
return 0
}
func (x *CloseChannelRequest) GetNoWait() bool {
if x != nil {
return x.NoWait
}
return false
}
type CloseStatusUpdate struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Update:
//
// *CloseStatusUpdate_ClosePending
// *CloseStatusUpdate_ChanClose
// *CloseStatusUpdate_CloseInstant
Update isCloseStatusUpdate_Update `protobuf_oneof:"update"`
}
func (x *CloseStatusUpdate) Reset() {
*x = CloseStatusUpdate{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[69]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CloseStatusUpdate) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CloseStatusUpdate) ProtoMessage() {}
func (x *CloseStatusUpdate) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[69]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CloseStatusUpdate.ProtoReflect.Descriptor instead.
func (*CloseStatusUpdate) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{69}
}
func (m *CloseStatusUpdate) GetUpdate() isCloseStatusUpdate_Update {
if m != nil {
return m.Update
}
return nil
}
func (x *CloseStatusUpdate) GetClosePending() *PendingUpdate {
if x, ok := x.GetUpdate().(*CloseStatusUpdate_ClosePending); ok {
return x.ClosePending
}
return nil
}
func (x *CloseStatusUpdate) GetChanClose() *ChannelCloseUpdate {
if x, ok := x.GetUpdate().(*CloseStatusUpdate_ChanClose); ok {
return x.ChanClose
}
return nil
}
func (x *CloseStatusUpdate) GetCloseInstant() *InstantUpdate {
if x, ok := x.GetUpdate().(*CloseStatusUpdate_CloseInstant); ok {
return x.CloseInstant
}
return nil
}
type isCloseStatusUpdate_Update interface {
isCloseStatusUpdate_Update()
}
type CloseStatusUpdate_ClosePending struct {
ClosePending *PendingUpdate `protobuf:"bytes,1,opt,name=close_pending,json=closePending,proto3,oneof"`
}
type CloseStatusUpdate_ChanClose struct {
ChanClose *ChannelCloseUpdate `protobuf:"bytes,3,opt,name=chan_close,json=chanClose,proto3,oneof"`
}
type CloseStatusUpdate_CloseInstant struct {
CloseInstant *InstantUpdate `protobuf:"bytes,4,opt,name=close_instant,json=closeInstant,proto3,oneof"`
}
func (*CloseStatusUpdate_ClosePending) isCloseStatusUpdate_Update() {}
func (*CloseStatusUpdate_ChanClose) isCloseStatusUpdate_Update() {}
func (*CloseStatusUpdate_CloseInstant) isCloseStatusUpdate_Update() {}
type PendingUpdate struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Txid []byte `protobuf:"bytes,1,opt,name=txid,proto3" json:"txid,omitempty"`
OutputIndex uint32 `protobuf:"varint,2,opt,name=output_index,json=outputIndex,proto3" json:"output_index,omitempty"`
FeePerVbyte int64 `protobuf:"varint,3,opt,name=fee_per_vbyte,json=feePerVbyte,proto3" json:"fee_per_vbyte,omitempty"`
LocalCloseTx bool `protobuf:"varint,4,opt,name=local_close_tx,json=localCloseTx,proto3" json:"local_close_tx,omitempty"`
}
func (x *PendingUpdate) Reset() {
*x = PendingUpdate{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[70]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PendingUpdate) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PendingUpdate) ProtoMessage() {}
func (x *PendingUpdate) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[70]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PendingUpdate.ProtoReflect.Descriptor instead.
func (*PendingUpdate) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{70}
}
func (x *PendingUpdate) GetTxid() []byte {
if x != nil {
return x.Txid
}
return nil
}
func (x *PendingUpdate) GetOutputIndex() uint32 {
if x != nil {
return x.OutputIndex
}
return 0
}
func (x *PendingUpdate) GetFeePerVbyte() int64 {
if x != nil {
return x.FeePerVbyte
}
return 0
}
func (x *PendingUpdate) GetLocalCloseTx() bool {
if x != nil {
return x.LocalCloseTx
}
return false
}
type InstantUpdate struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The number of pending HTLCs that are currently active on the channel.
// These HTLCs need to be resolved before the channel can be closed
// cooperatively.
NumPendingHtlcs int32 `protobuf:"varint,1,opt,name=num_pending_htlcs,json=numPendingHtlcs,proto3" json:"num_pending_htlcs,omitempty"`
}
func (x *InstantUpdate) Reset() {
*x = InstantUpdate{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[71]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *InstantUpdate) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*InstantUpdate) ProtoMessage() {}
func (x *InstantUpdate) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[71]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use InstantUpdate.ProtoReflect.Descriptor instead.
func (*InstantUpdate) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{71}
}
func (x *InstantUpdate) GetNumPendingHtlcs() int32 {
if x != nil {
return x.NumPendingHtlcs
}
return 0
}
type ReadyForPsbtFunding struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The P2WSH address of the channel funding multisig address that the below
// specified amount in satoshis needs to be sent to.
FundingAddress string `protobuf:"bytes,1,opt,name=funding_address,json=fundingAddress,proto3" json:"funding_address,omitempty"`
// The exact amount in satoshis that needs to be sent to the above address to
// fund the pending channel.
FundingAmount int64 `protobuf:"varint,2,opt,name=funding_amount,json=fundingAmount,proto3" json:"funding_amount,omitempty"`
// A raw PSBT that contains the pending channel output. If a base PSBT was
// provided in the PsbtShim, this is the base PSBT with one additional output.
// If no base PSBT was specified, this is an otherwise empty PSBT with exactly
// one output.
Psbt []byte `protobuf:"bytes,3,opt,name=psbt,proto3" json:"psbt,omitempty"`
}
func (x *ReadyForPsbtFunding) Reset() {
*x = ReadyForPsbtFunding{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[72]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ReadyForPsbtFunding) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ReadyForPsbtFunding) ProtoMessage() {}
func (x *ReadyForPsbtFunding) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[72]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ReadyForPsbtFunding.ProtoReflect.Descriptor instead.
func (*ReadyForPsbtFunding) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{72}
}
func (x *ReadyForPsbtFunding) GetFundingAddress() string {
if x != nil {
return x.FundingAddress
}
return ""
}
func (x *ReadyForPsbtFunding) GetFundingAmount() int64 {
if x != nil {
return x.FundingAmount
}
return 0
}
func (x *ReadyForPsbtFunding) GetPsbt() []byte {
if x != nil {
return x.Psbt
}
return nil
}
type BatchOpenChannelRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The list of channels to open.
Channels []*BatchOpenChannel `protobuf:"bytes,1,rep,name=channels,proto3" json:"channels,omitempty"`
// The target number of blocks that the funding transaction should be
// confirmed by.
TargetConf int32 `protobuf:"varint,2,opt,name=target_conf,json=targetConf,proto3" json:"target_conf,omitempty"`
// A manual fee rate set in sat/vByte that should be used when crafting the
// funding transaction.
SatPerVbyte int64 `protobuf:"varint,3,opt,name=sat_per_vbyte,json=satPerVbyte,proto3" json:"sat_per_vbyte,omitempty"`
// The minimum number of confirmations each one of your outputs used for
// the funding transaction must satisfy.
MinConfs int32 `protobuf:"varint,4,opt,name=min_confs,json=minConfs,proto3" json:"min_confs,omitempty"`
// Whether unconfirmed outputs should be used as inputs for the funding
// transaction.
SpendUnconfirmed bool `protobuf:"varint,5,opt,name=spend_unconfirmed,json=spendUnconfirmed,proto3" json:"spend_unconfirmed,omitempty"`
// An optional label for the batch transaction, limited to 500 characters.
Label string `protobuf:"bytes,6,opt,name=label,proto3" json:"label,omitempty"`
// The strategy to use for selecting coins during batch opening channels.
CoinSelectionStrategy CoinSelectionStrategy `protobuf:"varint,7,opt,name=coin_selection_strategy,json=coinSelectionStrategy,proto3,enum=lnrpc.CoinSelectionStrategy" json:"coin_selection_strategy,omitempty"`
}
func (x *BatchOpenChannelRequest) Reset() {
*x = BatchOpenChannelRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[73]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *BatchOpenChannelRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BatchOpenChannelRequest) ProtoMessage() {}
func (x *BatchOpenChannelRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[73]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BatchOpenChannelRequest.ProtoReflect.Descriptor instead.
func (*BatchOpenChannelRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{73}
}
func (x *BatchOpenChannelRequest) GetChannels() []*BatchOpenChannel {
if x != nil {
return x.Channels
}
return nil
}
func (x *BatchOpenChannelRequest) GetTargetConf() int32 {
if x != nil {
return x.TargetConf
}
return 0
}
func (x *BatchOpenChannelRequest) GetSatPerVbyte() int64 {
if x != nil {
return x.SatPerVbyte
}
return 0
}
func (x *BatchOpenChannelRequest) GetMinConfs() int32 {
if x != nil {
return x.MinConfs
}
return 0
}
func (x *BatchOpenChannelRequest) GetSpendUnconfirmed() bool {
if x != nil {
return x.SpendUnconfirmed
}
return false
}
func (x *BatchOpenChannelRequest) GetLabel() string {
if x != nil {
return x.Label
}
return ""
}
func (x *BatchOpenChannelRequest) GetCoinSelectionStrategy() CoinSelectionStrategy {
if x != nil {
return x.CoinSelectionStrategy
}
return CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG
}
type BatchOpenChannel struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The pubkey of the node to open a channel with. When using REST, this
// field must be encoded as base64.
NodePubkey []byte `protobuf:"bytes,1,opt,name=node_pubkey,json=nodePubkey,proto3" json:"node_pubkey,omitempty"`
// The number of satoshis the wallet should commit to the channel.
LocalFundingAmount int64 `protobuf:"varint,2,opt,name=local_funding_amount,json=localFundingAmount,proto3" json:"local_funding_amount,omitempty"`
// The number of satoshis to push to the remote side as part of the initial
// commitment state.
PushSat int64 `protobuf:"varint,3,opt,name=push_sat,json=pushSat,proto3" json:"push_sat,omitempty"`
// Whether this channel should be private, not announced to the greater
// network.
Private bool `protobuf:"varint,4,opt,name=private,proto3" json:"private,omitempty"`
// The minimum value in millisatoshi we will require for incoming HTLCs on
// the channel.
MinHtlcMsat int64 `protobuf:"varint,5,opt,name=min_htlc_msat,json=minHtlcMsat,proto3" json:"min_htlc_msat,omitempty"`
// The delay we require on the remote's commitment transaction. If this is
// not set, it will be scaled automatically with the channel size.
RemoteCsvDelay uint32 `protobuf:"varint,6,opt,name=remote_csv_delay,json=remoteCsvDelay,proto3" json:"remote_csv_delay,omitempty"`
// Close address is an optional address which specifies the address to which
// funds should be paid out to upon cooperative close. This field may only be
// set if the peer supports the option upfront feature bit (call listpeers
// to check). The remote peer will only accept cooperative closes to this
// address if it is set.
//
// Note: If this value is set on channel creation, you will *not* be able to
// cooperatively close out to a different address.
CloseAddress string `protobuf:"bytes,7,opt,name=close_address,json=closeAddress,proto3" json:"close_address,omitempty"`
// An optional, unique identifier of 32 random bytes that will be used as the
// pending channel ID to identify the channel while it is in the pre-pending
// state.
PendingChanId []byte `protobuf:"bytes,8,opt,name=pending_chan_id,json=pendingChanId,proto3" json:"pending_chan_id,omitempty"`
// The explicit commitment type to use. Note this field will only be used if
// the remote peer supports explicit channel negotiation.
CommitmentType CommitmentType `protobuf:"varint,9,opt,name=commitment_type,json=commitmentType,proto3,enum=lnrpc.CommitmentType" json:"commitment_type,omitempty"`
// The maximum amount of coins in millisatoshi that can be pending within
// the channel. It only applies to the remote party.
RemoteMaxValueInFlightMsat uint64 `protobuf:"varint,10,opt,name=remote_max_value_in_flight_msat,json=remoteMaxValueInFlightMsat,proto3" json:"remote_max_value_in_flight_msat,omitempty"`
// The maximum number of concurrent HTLCs we will allow the remote party to add
// to the commitment transaction.
RemoteMaxHtlcs uint32 `protobuf:"varint,11,opt,name=remote_max_htlcs,json=remoteMaxHtlcs,proto3" json:"remote_max_htlcs,omitempty"`
// Max local csv is the maximum csv delay we will allow for our own commitment
// transaction.
MaxLocalCsv uint32 `protobuf:"varint,12,opt,name=max_local_csv,json=maxLocalCsv,proto3" json:"max_local_csv,omitempty"`
// If this is true, then a zero-conf channel open will be attempted.
ZeroConf bool `protobuf:"varint,13,opt,name=zero_conf,json=zeroConf,proto3" json:"zero_conf,omitempty"`
// If this is true, then an option-scid-alias channel-type open will be
// attempted.
ScidAlias bool `protobuf:"varint,14,opt,name=scid_alias,json=scidAlias,proto3" json:"scid_alias,omitempty"`
// The base fee charged regardless of the number of milli-satoshis sent.
BaseFee uint64 `protobuf:"varint,15,opt,name=base_fee,json=baseFee,proto3" json:"base_fee,omitempty"`
// The fee rate in ppm (parts per million) that will be charged in
// proportion of the value of each forwarded HTLC.
FeeRate uint64 `protobuf:"varint,16,opt,name=fee_rate,json=feeRate,proto3" json:"fee_rate,omitempty"`
// If use_base_fee is true the open channel announcement will update the
// channel base fee with the value specified in base_fee. In the case of
// a base_fee of 0 use_base_fee is needed downstream to distinguish whether
// to use the default base fee value specified in the config or 0.
UseBaseFee bool `protobuf:"varint,17,opt,name=use_base_fee,json=useBaseFee,proto3" json:"use_base_fee,omitempty"`
// If use_fee_rate is true the open channel announcement will update the
// channel fee rate with the value specified in fee_rate. In the case of
// a fee_rate of 0 use_fee_rate is needed downstream to distinguish whether
// to use the default fee rate value specified in the config or 0.
UseFeeRate bool `protobuf:"varint,18,opt,name=use_fee_rate,json=useFeeRate,proto3" json:"use_fee_rate,omitempty"`
// The number of satoshis we require the remote peer to reserve. This value,
// if specified, must be above the dust limit and below 20% of the channel
// capacity.
RemoteChanReserveSat uint64 `protobuf:"varint,19,opt,name=remote_chan_reserve_sat,json=remoteChanReserveSat,proto3" json:"remote_chan_reserve_sat,omitempty"`
// An optional note-to-self to go along with the channel containing some
// useful information. This is only ever stored locally and in no way impacts
// the channel's operation.
Memo string `protobuf:"bytes,20,opt,name=memo,proto3" json:"memo,omitempty"`
}
func (x *BatchOpenChannel) Reset() {
*x = BatchOpenChannel{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[74]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *BatchOpenChannel) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BatchOpenChannel) ProtoMessage() {}
func (x *BatchOpenChannel) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[74]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BatchOpenChannel.ProtoReflect.Descriptor instead.
func (*BatchOpenChannel) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{74}
}
func (x *BatchOpenChannel) GetNodePubkey() []byte {
if x != nil {
return x.NodePubkey
}
return nil
}
func (x *BatchOpenChannel) GetLocalFundingAmount() int64 {
if x != nil {
return x.LocalFundingAmount
}
return 0
}
func (x *BatchOpenChannel) GetPushSat() int64 {
if x != nil {
return x.PushSat
}
return 0
}
func (x *BatchOpenChannel) GetPrivate() bool {
if x != nil {
return x.Private
}
return false
}
func (x *BatchOpenChannel) GetMinHtlcMsat() int64 {
if x != nil {
return x.MinHtlcMsat
}
return 0
}
func (x *BatchOpenChannel) GetRemoteCsvDelay() uint32 {
if x != nil {
return x.RemoteCsvDelay
}
return 0
}
func (x *BatchOpenChannel) GetCloseAddress() string {
if x != nil {
return x.CloseAddress
}
return ""
}
func (x *BatchOpenChannel) GetPendingChanId() []byte {
if x != nil {
return x.PendingChanId
}
return nil
}
func (x *BatchOpenChannel) GetCommitmentType() CommitmentType {
if x != nil {
return x.CommitmentType
}
return CommitmentType_UNKNOWN_COMMITMENT_TYPE
}
func (x *BatchOpenChannel) GetRemoteMaxValueInFlightMsat() uint64 {
if x != nil {
return x.RemoteMaxValueInFlightMsat
}
return 0
}
func (x *BatchOpenChannel) GetRemoteMaxHtlcs() uint32 {
if x != nil {
return x.RemoteMaxHtlcs
}
return 0
}
func (x *BatchOpenChannel) GetMaxLocalCsv() uint32 {
if x != nil {
return x.MaxLocalCsv
}
return 0
}
func (x *BatchOpenChannel) GetZeroConf() bool {
if x != nil {
return x.ZeroConf
}
return false
}
func (x *BatchOpenChannel) GetScidAlias() bool {
if x != nil {
return x.ScidAlias
}
return false
}
func (x *BatchOpenChannel) GetBaseFee() uint64 {
if x != nil {
return x.BaseFee
}
return 0
}
func (x *BatchOpenChannel) GetFeeRate() uint64 {
if x != nil {
return x.FeeRate
}
return 0
}
func (x *BatchOpenChannel) GetUseBaseFee() bool {
if x != nil {
return x.UseBaseFee
}
return false
}
func (x *BatchOpenChannel) GetUseFeeRate() bool {
if x != nil {
return x.UseFeeRate
}
return false
}
func (x *BatchOpenChannel) GetRemoteChanReserveSat() uint64 {
if x != nil {
return x.RemoteChanReserveSat
}
return 0
}
func (x *BatchOpenChannel) GetMemo() string {
if x != nil {
return x.Memo
}
return ""
}
type BatchOpenChannelResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
PendingChannels []*PendingUpdate `protobuf:"bytes,1,rep,name=pending_channels,json=pendingChannels,proto3" json:"pending_channels,omitempty"`
}
func (x *BatchOpenChannelResponse) Reset() {
*x = BatchOpenChannelResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[75]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *BatchOpenChannelResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BatchOpenChannelResponse) ProtoMessage() {}
func (x *BatchOpenChannelResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[75]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BatchOpenChannelResponse.ProtoReflect.Descriptor instead.
func (*BatchOpenChannelResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{75}
}
func (x *BatchOpenChannelResponse) GetPendingChannels() []*PendingUpdate {
if x != nil {
return x.PendingChannels
}
return nil
}
type OpenChannelRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A manual fee rate set in sat/vbyte that should be used when crafting the
// funding transaction.
SatPerVbyte uint64 `protobuf:"varint,1,opt,name=sat_per_vbyte,json=satPerVbyte,proto3" json:"sat_per_vbyte,omitempty"`
// The pubkey of the node to open a channel with. When using REST, this field
// must be encoded as base64.
NodePubkey []byte `protobuf:"bytes,2,opt,name=node_pubkey,json=nodePubkey,proto3" json:"node_pubkey,omitempty"`
// The hex encoded pubkey of the node to open a channel with. Deprecated now
// that the REST gateway supports base64 encoding of bytes fields.
//
// Deprecated: Marked as deprecated in lightning.proto.
NodePubkeyString string `protobuf:"bytes,3,opt,name=node_pubkey_string,json=nodePubkeyString,proto3" json:"node_pubkey_string,omitempty"`
// The number of satoshis the wallet should commit to the channel
LocalFundingAmount int64 `protobuf:"varint,4,opt,name=local_funding_amount,json=localFundingAmount,proto3" json:"local_funding_amount,omitempty"`
// The number of satoshis to push to the remote side as part of the initial
// commitment state
PushSat int64 `protobuf:"varint,5,opt,name=push_sat,json=pushSat,proto3" json:"push_sat,omitempty"`
// The target number of blocks that the funding transaction should be
// confirmed by.
TargetConf int32 `protobuf:"varint,6,opt,name=target_conf,json=targetConf,proto3" json:"target_conf,omitempty"`
// Deprecated, use sat_per_vbyte.
// A manual fee rate set in sat/vbyte that should be used when crafting the
// funding transaction.
//
// Deprecated: Marked as deprecated in lightning.proto.
SatPerByte int64 `protobuf:"varint,7,opt,name=sat_per_byte,json=satPerByte,proto3" json:"sat_per_byte,omitempty"`
// Whether this channel should be private, not announced to the greater
// network.
Private bool `protobuf:"varint,8,opt,name=private,proto3" json:"private,omitempty"`
// The minimum value in millisatoshi we will require for incoming HTLCs on
// the channel.
MinHtlcMsat int64 `protobuf:"varint,9,opt,name=min_htlc_msat,json=minHtlcMsat,proto3" json:"min_htlc_msat,omitempty"`
// The delay we require on the remote's commitment transaction. If this is
// not set, it will be scaled automatically with the channel size.
RemoteCsvDelay uint32 `protobuf:"varint,10,opt,name=remote_csv_delay,json=remoteCsvDelay,proto3" json:"remote_csv_delay,omitempty"`
// The minimum number of confirmations each one of your outputs used for
// the funding transaction must satisfy.
MinConfs int32 `protobuf:"varint,11,opt,name=min_confs,json=minConfs,proto3" json:"min_confs,omitempty"`
// Whether unconfirmed outputs should be used as inputs for the funding
// transaction.
SpendUnconfirmed bool `protobuf:"varint,12,opt,name=spend_unconfirmed,json=spendUnconfirmed,proto3" json:"spend_unconfirmed,omitempty"`
// Close address is an optional address which specifies the address to which
// funds should be paid out to upon cooperative close. This field may only be
// set if the peer supports the option upfront feature bit (call listpeers
// to check). The remote peer will only accept cooperative closes to this
// address if it is set.
//
// Note: If this value is set on channel creation, you will *not* be able to
// cooperatively close out to a different address.
CloseAddress string `protobuf:"bytes,13,opt,name=close_address,json=closeAddress,proto3" json:"close_address,omitempty"`
// Funding shims are an optional argument that allow the caller to intercept
// certain funding functionality. For example, a shim can be provided to use a
// particular key for the commitment key (ideally cold) rather than use one
// that is generated by the wallet as normal, or signal that signing will be
// carried out in an interactive manner (PSBT based).
FundingShim *FundingShim `protobuf:"bytes,14,opt,name=funding_shim,json=fundingShim,proto3" json:"funding_shim,omitempty"`
// The maximum amount of coins in millisatoshi that can be pending within
// the channel. It only applies to the remote party.
RemoteMaxValueInFlightMsat uint64 `protobuf:"varint,15,opt,name=remote_max_value_in_flight_msat,json=remoteMaxValueInFlightMsat,proto3" json:"remote_max_value_in_flight_msat,omitempty"`
// The maximum number of concurrent HTLCs we will allow the remote party to add
// to the commitment transaction.
RemoteMaxHtlcs uint32 `protobuf:"varint,16,opt,name=remote_max_htlcs,json=remoteMaxHtlcs,proto3" json:"remote_max_htlcs,omitempty"`
// Max local csv is the maximum csv delay we will allow for our own commitment
// transaction.
MaxLocalCsv uint32 `protobuf:"varint,17,opt,name=max_local_csv,json=maxLocalCsv,proto3" json:"max_local_csv,omitempty"`
// The explicit commitment type to use. Note this field will only be used if
// the remote peer supports explicit channel negotiation.
CommitmentType CommitmentType `protobuf:"varint,18,opt,name=commitment_type,json=commitmentType,proto3,enum=lnrpc.CommitmentType" json:"commitment_type,omitempty"`
// If this is true, then a zero-conf channel open will be attempted.
ZeroConf bool `protobuf:"varint,19,opt,name=zero_conf,json=zeroConf,proto3" json:"zero_conf,omitempty"`
// If this is true, then an option-scid-alias channel-type open will be
// attempted.
ScidAlias bool `protobuf:"varint,20,opt,name=scid_alias,json=scidAlias,proto3" json:"scid_alias,omitempty"`
// The base fee charged regardless of the number of milli-satoshis sent.
BaseFee uint64 `protobuf:"varint,21,opt,name=base_fee,json=baseFee,proto3" json:"base_fee,omitempty"`
// The fee rate in ppm (parts per million) that will be charged in
// proportion of the value of each forwarded HTLC.
FeeRate uint64 `protobuf:"varint,22,opt,name=fee_rate,json=feeRate,proto3" json:"fee_rate,omitempty"`
// If use_base_fee is true the open channel announcement will update the
// channel base fee with the value specified in base_fee. In the case of
// a base_fee of 0 use_base_fee is needed downstream to distinguish whether
// to use the default base fee value specified in the config or 0.
UseBaseFee bool `protobuf:"varint,23,opt,name=use_base_fee,json=useBaseFee,proto3" json:"use_base_fee,omitempty"`
// If use_fee_rate is true the open channel announcement will update the
// channel fee rate with the value specified in fee_rate. In the case of
// a fee_rate of 0 use_fee_rate is needed downstream to distinguish whether
// to use the default fee rate value specified in the config or 0.
UseFeeRate bool `protobuf:"varint,24,opt,name=use_fee_rate,json=useFeeRate,proto3" json:"use_fee_rate,omitempty"`
// The number of satoshis we require the remote peer to reserve. This value,
// if specified, must be above the dust limit and below 20% of the channel
// capacity.
RemoteChanReserveSat uint64 `protobuf:"varint,25,opt,name=remote_chan_reserve_sat,json=remoteChanReserveSat,proto3" json:"remote_chan_reserve_sat,omitempty"`
// If set, then lnd will attempt to commit all the coins under control of the
// internal wallet to open the channel, and the LocalFundingAmount field must
// be zero and is ignored.
FundMax bool `protobuf:"varint,26,opt,name=fund_max,json=fundMax,proto3" json:"fund_max,omitempty"`
// An optional note-to-self to go along with the channel containing some
// useful information. This is only ever stored locally and in no way impacts
// the channel's operation.
Memo string `protobuf:"bytes,27,opt,name=memo,proto3" json:"memo,omitempty"`
// A list of selected outpoints that are allocated for channel funding.
Outpoints []*OutPoint `protobuf:"bytes,28,rep,name=outpoints,proto3" json:"outpoints,omitempty"`
}
func (x *OpenChannelRequest) Reset() {
*x = OpenChannelRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[76]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *OpenChannelRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*OpenChannelRequest) ProtoMessage() {}
func (x *OpenChannelRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[76]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use OpenChannelRequest.ProtoReflect.Descriptor instead.
func (*OpenChannelRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{76}
}
func (x *OpenChannelRequest) GetSatPerVbyte() uint64 {
if x != nil {
return x.SatPerVbyte
}
return 0
}
func (x *OpenChannelRequest) GetNodePubkey() []byte {
if x != nil {
return x.NodePubkey
}
return nil
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *OpenChannelRequest) GetNodePubkeyString() string {
if x != nil {
return x.NodePubkeyString
}
return ""
}
func (x *OpenChannelRequest) GetLocalFundingAmount() int64 {
if x != nil {
return x.LocalFundingAmount
}
return 0
}
func (x *OpenChannelRequest) GetPushSat() int64 {
if x != nil {
return x.PushSat
}
return 0
}
func (x *OpenChannelRequest) GetTargetConf() int32 {
if x != nil {
return x.TargetConf
}
return 0
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *OpenChannelRequest) GetSatPerByte() int64 {
if x != nil {
return x.SatPerByte
}
return 0
}
func (x *OpenChannelRequest) GetPrivate() bool {
if x != nil {
return x.Private
}
return false
}
func (x *OpenChannelRequest) GetMinHtlcMsat() int64 {
if x != nil {
return x.MinHtlcMsat
}
return 0
}
func (x *OpenChannelRequest) GetRemoteCsvDelay() uint32 {
if x != nil {
return x.RemoteCsvDelay
}
return 0
}
func (x *OpenChannelRequest) GetMinConfs() int32 {
if x != nil {
return x.MinConfs
}
return 0
}
func (x *OpenChannelRequest) GetSpendUnconfirmed() bool {
if x != nil {
return x.SpendUnconfirmed
}
return false
}
func (x *OpenChannelRequest) GetCloseAddress() string {
if x != nil {
return x.CloseAddress
}
return ""
}
func (x *OpenChannelRequest) GetFundingShim() *FundingShim {
if x != nil {
return x.FundingShim
}
return nil
}
func (x *OpenChannelRequest) GetRemoteMaxValueInFlightMsat() uint64 {
if x != nil {
return x.RemoteMaxValueInFlightMsat
}
return 0
}
func (x *OpenChannelRequest) GetRemoteMaxHtlcs() uint32 {
if x != nil {
return x.RemoteMaxHtlcs
}
return 0
}
func (x *OpenChannelRequest) GetMaxLocalCsv() uint32 {
if x != nil {
return x.MaxLocalCsv
}
return 0
}
func (x *OpenChannelRequest) GetCommitmentType() CommitmentType {
if x != nil {
return x.CommitmentType
}
return CommitmentType_UNKNOWN_COMMITMENT_TYPE
}
func (x *OpenChannelRequest) GetZeroConf() bool {
if x != nil {
return x.ZeroConf
}
return false
}
func (x *OpenChannelRequest) GetScidAlias() bool {
if x != nil {
return x.ScidAlias
}
return false
}
func (x *OpenChannelRequest) GetBaseFee() uint64 {
if x != nil {
return x.BaseFee
}
return 0
}
func (x *OpenChannelRequest) GetFeeRate() uint64 {
if x != nil {
return x.FeeRate
}
return 0
}
func (x *OpenChannelRequest) GetUseBaseFee() bool {
if x != nil {
return x.UseBaseFee
}
return false
}
func (x *OpenChannelRequest) GetUseFeeRate() bool {
if x != nil {
return x.UseFeeRate
}
return false
}
func (x *OpenChannelRequest) GetRemoteChanReserveSat() uint64 {
if x != nil {
return x.RemoteChanReserveSat
}
return 0
}
func (x *OpenChannelRequest) GetFundMax() bool {
if x != nil {
return x.FundMax
}
return false
}
func (x *OpenChannelRequest) GetMemo() string {
if x != nil {
return x.Memo
}
return ""
}
func (x *OpenChannelRequest) GetOutpoints() []*OutPoint {
if x != nil {
return x.Outpoints
}
return nil
}
type OpenStatusUpdate struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Update:
//
// *OpenStatusUpdate_ChanPending
// *OpenStatusUpdate_ChanOpen
// *OpenStatusUpdate_PsbtFund
Update isOpenStatusUpdate_Update `protobuf_oneof:"update"`
// The pending channel ID of the created channel. This value may be used to
// further the funding flow manually via the FundingStateStep method.
PendingChanId []byte `protobuf:"bytes,4,opt,name=pending_chan_id,json=pendingChanId,proto3" json:"pending_chan_id,omitempty"`
}
func (x *OpenStatusUpdate) Reset() {
*x = OpenStatusUpdate{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[77]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *OpenStatusUpdate) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*OpenStatusUpdate) ProtoMessage() {}
func (x *OpenStatusUpdate) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[77]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use OpenStatusUpdate.ProtoReflect.Descriptor instead.
func (*OpenStatusUpdate) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{77}
}
func (m *OpenStatusUpdate) GetUpdate() isOpenStatusUpdate_Update {
if m != nil {
return m.Update
}
return nil
}
func (x *OpenStatusUpdate) GetChanPending() *PendingUpdate {
if x, ok := x.GetUpdate().(*OpenStatusUpdate_ChanPending); ok {
return x.ChanPending
}
return nil
}
func (x *OpenStatusUpdate) GetChanOpen() *ChannelOpenUpdate {
if x, ok := x.GetUpdate().(*OpenStatusUpdate_ChanOpen); ok {
return x.ChanOpen
}
return nil
}
func (x *OpenStatusUpdate) GetPsbtFund() *ReadyForPsbtFunding {
if x, ok := x.GetUpdate().(*OpenStatusUpdate_PsbtFund); ok {
return x.PsbtFund
}
return nil
}
func (x *OpenStatusUpdate) GetPendingChanId() []byte {
if x != nil {
return x.PendingChanId
}
return nil
}
type isOpenStatusUpdate_Update interface {
isOpenStatusUpdate_Update()
}
type OpenStatusUpdate_ChanPending struct {
// Signals that the channel is now fully negotiated and the funding
// transaction published.
ChanPending *PendingUpdate `protobuf:"bytes,1,opt,name=chan_pending,json=chanPending,proto3,oneof"`
}
type OpenStatusUpdate_ChanOpen struct {
// Signals that the channel's funding transaction has now reached the
// required number of confirmations on chain and can be used.
ChanOpen *ChannelOpenUpdate `protobuf:"bytes,3,opt,name=chan_open,json=chanOpen,proto3,oneof"`
}
type OpenStatusUpdate_PsbtFund struct {
// Signals that the funding process has been suspended and the construction
// of a PSBT that funds the channel PK script is now required.
PsbtFund *ReadyForPsbtFunding `protobuf:"bytes,5,opt,name=psbt_fund,json=psbtFund,proto3,oneof"`
}
func (*OpenStatusUpdate_ChanPending) isOpenStatusUpdate_Update() {}
func (*OpenStatusUpdate_ChanOpen) isOpenStatusUpdate_Update() {}
func (*OpenStatusUpdate_PsbtFund) isOpenStatusUpdate_Update() {}
type KeyLocator struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The family of key being identified.
KeyFamily int32 `protobuf:"varint,1,opt,name=key_family,json=keyFamily,proto3" json:"key_family,omitempty"`
// The precise index of the key being identified.
KeyIndex int32 `protobuf:"varint,2,opt,name=key_index,json=keyIndex,proto3" json:"key_index,omitempty"`
}
func (x *KeyLocator) Reset() {
*x = KeyLocator{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[78]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *KeyLocator) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*KeyLocator) ProtoMessage() {}
func (x *KeyLocator) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[78]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use KeyLocator.ProtoReflect.Descriptor instead.
func (*KeyLocator) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{78}
}
func (x *KeyLocator) GetKeyFamily() int32 {
if x != nil {
return x.KeyFamily
}
return 0
}
func (x *KeyLocator) GetKeyIndex() int32 {
if x != nil {
return x.KeyIndex
}
return 0
}
type KeyDescriptor struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The raw bytes of the key being identified.
RawKeyBytes []byte `protobuf:"bytes,1,opt,name=raw_key_bytes,json=rawKeyBytes,proto3" json:"raw_key_bytes,omitempty"`
// The key locator that identifies which key to use for signing.
KeyLoc *KeyLocator `protobuf:"bytes,2,opt,name=key_loc,json=keyLoc,proto3" json:"key_loc,omitempty"`
}
func (x *KeyDescriptor) Reset() {
*x = KeyDescriptor{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[79]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *KeyDescriptor) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*KeyDescriptor) ProtoMessage() {}
func (x *KeyDescriptor) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[79]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use KeyDescriptor.ProtoReflect.Descriptor instead.
func (*KeyDescriptor) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{79}
}
func (x *KeyDescriptor) GetRawKeyBytes() []byte {
if x != nil {
return x.RawKeyBytes
}
return nil
}
func (x *KeyDescriptor) GetKeyLoc() *KeyLocator {
if x != nil {
return x.KeyLoc
}
return nil
}
type ChanPointShim struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The size of the pre-crafted output to be used as the channel point for this
// channel funding.
Amt int64 `protobuf:"varint,1,opt,name=amt,proto3" json:"amt,omitempty"`
// The target channel point to refrence in created commitment transactions.
ChanPoint *ChannelPoint `protobuf:"bytes,2,opt,name=chan_point,json=chanPoint,proto3" json:"chan_point,omitempty"`
// Our local key to use when creating the multi-sig output.
LocalKey *KeyDescriptor `protobuf:"bytes,3,opt,name=local_key,json=localKey,proto3" json:"local_key,omitempty"`
// The key of the remote party to use when creating the multi-sig output.
RemoteKey []byte `protobuf:"bytes,4,opt,name=remote_key,json=remoteKey,proto3" json:"remote_key,omitempty"`
// If non-zero, then this will be used as the pending channel ID on the wire
// protocol to initate the funding request. This is an optional field, and
// should only be set if the responder is already expecting a specific pending
// channel ID.
PendingChanId []byte `protobuf:"bytes,5,opt,name=pending_chan_id,json=pendingChanId,proto3" json:"pending_chan_id,omitempty"`
// This uint32 indicates if this channel is to be considered 'frozen'. A frozen
// channel does not allow a cooperative channel close by the initiator. The
// thaw_height is the height that this restriction stops applying to the
// channel. The height can be interpreted in two ways: as a relative height if
// the value is less than 500,000, or as an absolute height otherwise.
ThawHeight uint32 `protobuf:"varint,6,opt,name=thaw_height,json=thawHeight,proto3" json:"thaw_height,omitempty"`
// Indicates that the funding output is using a MuSig2 multi-sig output.
Musig2 bool `protobuf:"varint,7,opt,name=musig2,proto3" json:"musig2,omitempty"`
}
func (x *ChanPointShim) Reset() {
*x = ChanPointShim{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[80]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChanPointShim) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChanPointShim) ProtoMessage() {}
func (x *ChanPointShim) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[80]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChanPointShim.ProtoReflect.Descriptor instead.
func (*ChanPointShim) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{80}
}
func (x *ChanPointShim) GetAmt() int64 {
if x != nil {
return x.Amt
}
return 0
}
func (x *ChanPointShim) GetChanPoint() *ChannelPoint {
if x != nil {
return x.ChanPoint
}
return nil
}
func (x *ChanPointShim) GetLocalKey() *KeyDescriptor {
if x != nil {
return x.LocalKey
}
return nil
}
func (x *ChanPointShim) GetRemoteKey() []byte {
if x != nil {
return x.RemoteKey
}
return nil
}
func (x *ChanPointShim) GetPendingChanId() []byte {
if x != nil {
return x.PendingChanId
}
return nil
}
func (x *ChanPointShim) GetThawHeight() uint32 {
if x != nil {
return x.ThawHeight
}
return 0
}
func (x *ChanPointShim) GetMusig2() bool {
if x != nil {
return x.Musig2
}
return false
}
type PsbtShim struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A unique identifier of 32 random bytes that will be used as the pending
// channel ID to identify the PSBT state machine when interacting with it and
// on the wire protocol to initiate the funding request.
PendingChanId []byte `protobuf:"bytes,1,opt,name=pending_chan_id,json=pendingChanId,proto3" json:"pending_chan_id,omitempty"`
// An optional base PSBT the new channel output will be added to. If this is
// non-empty, it must be a binary serialized PSBT.
BasePsbt []byte `protobuf:"bytes,2,opt,name=base_psbt,json=basePsbt,proto3" json:"base_psbt,omitempty"`
// If a channel should be part of a batch (multiple channel openings in one
// transaction), it can be dangerous if the whole batch transaction is
// published too early before all channel opening negotiations are completed.
// This flag prevents this particular channel from broadcasting the transaction
// after the negotiation with the remote peer. In a batch of channel openings
// this flag should be set to true for every channel but the very last.
NoPublish bool `protobuf:"varint,3,opt,name=no_publish,json=noPublish,proto3" json:"no_publish,omitempty"`
}
func (x *PsbtShim) Reset() {
*x = PsbtShim{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[81]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PsbtShim) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PsbtShim) ProtoMessage() {}
func (x *PsbtShim) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[81]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PsbtShim.ProtoReflect.Descriptor instead.
func (*PsbtShim) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{81}
}
func (x *PsbtShim) GetPendingChanId() []byte {
if x != nil {
return x.PendingChanId
}
return nil
}
func (x *PsbtShim) GetBasePsbt() []byte {
if x != nil {
return x.BasePsbt
}
return nil
}
func (x *PsbtShim) GetNoPublish() bool {
if x != nil {
return x.NoPublish
}
return false
}
type FundingShim struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Shim:
//
// *FundingShim_ChanPointShim
// *FundingShim_PsbtShim
Shim isFundingShim_Shim `protobuf_oneof:"shim"`
}
func (x *FundingShim) Reset() {
*x = FundingShim{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[82]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FundingShim) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FundingShim) ProtoMessage() {}
func (x *FundingShim) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[82]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FundingShim.ProtoReflect.Descriptor instead.
func (*FundingShim) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{82}
}
func (m *FundingShim) GetShim() isFundingShim_Shim {
if m != nil {
return m.Shim
}
return nil
}
func (x *FundingShim) GetChanPointShim() *ChanPointShim {
if x, ok := x.GetShim().(*FundingShim_ChanPointShim); ok {
return x.ChanPointShim
}
return nil
}
func (x *FundingShim) GetPsbtShim() *PsbtShim {
if x, ok := x.GetShim().(*FundingShim_PsbtShim); ok {
return x.PsbtShim
}
return nil
}
type isFundingShim_Shim interface {
isFundingShim_Shim()
}
type FundingShim_ChanPointShim struct {
// A channel shim where the channel point was fully constructed outside
// of lnd's wallet and the transaction might already be published.
ChanPointShim *ChanPointShim `protobuf:"bytes,1,opt,name=chan_point_shim,json=chanPointShim,proto3,oneof"`
}
type FundingShim_PsbtShim struct {
// A channel shim that uses a PSBT to fund and sign the channel funding
// transaction.
PsbtShim *PsbtShim `protobuf:"bytes,2,opt,name=psbt_shim,json=psbtShim,proto3,oneof"`
}
func (*FundingShim_ChanPointShim) isFundingShim_Shim() {}
func (*FundingShim_PsbtShim) isFundingShim_Shim() {}
type FundingShimCancel struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The pending channel ID of the channel to cancel the funding shim for.
PendingChanId []byte `protobuf:"bytes,1,opt,name=pending_chan_id,json=pendingChanId,proto3" json:"pending_chan_id,omitempty"`
}
func (x *FundingShimCancel) Reset() {
*x = FundingShimCancel{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[83]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FundingShimCancel) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FundingShimCancel) ProtoMessage() {}
func (x *FundingShimCancel) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[83]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FundingShimCancel.ProtoReflect.Descriptor instead.
func (*FundingShimCancel) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{83}
}
func (x *FundingShimCancel) GetPendingChanId() []byte {
if x != nil {
return x.PendingChanId
}
return nil
}
type FundingPsbtVerify struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The funded but not yet signed PSBT that sends the exact channel capacity
// amount to the PK script returned in the open channel message in a previous
// step.
FundedPsbt []byte `protobuf:"bytes,1,opt,name=funded_psbt,json=fundedPsbt,proto3" json:"funded_psbt,omitempty"`
// The pending channel ID of the channel to get the PSBT for.
PendingChanId []byte `protobuf:"bytes,2,opt,name=pending_chan_id,json=pendingChanId,proto3" json:"pending_chan_id,omitempty"`
// Can only be used if the no_publish flag was set to true in the OpenChannel
// call meaning that the caller is solely responsible for publishing the final
// funding transaction. If skip_finalize is set to true then lnd will not wait
// for a FundingPsbtFinalize state step and instead assumes that a transaction
// with the same TXID as the passed in PSBT will eventually confirm.
// IT IS ABSOLUTELY IMPERATIVE that the TXID of the transaction that is
// eventually published does have the _same TXID_ as the verified PSBT. That
// means no inputs or outputs can change, only signatures can be added. If the
// TXID changes between this call and the publish step then the channel will
// never be created and the funds will be in limbo.
SkipFinalize bool `protobuf:"varint,3,opt,name=skip_finalize,json=skipFinalize,proto3" json:"skip_finalize,omitempty"`
}
func (x *FundingPsbtVerify) Reset() {
*x = FundingPsbtVerify{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[84]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FundingPsbtVerify) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FundingPsbtVerify) ProtoMessage() {}
func (x *FundingPsbtVerify) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[84]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FundingPsbtVerify.ProtoReflect.Descriptor instead.
func (*FundingPsbtVerify) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{84}
}
func (x *FundingPsbtVerify) GetFundedPsbt() []byte {
if x != nil {
return x.FundedPsbt
}
return nil
}
func (x *FundingPsbtVerify) GetPendingChanId() []byte {
if x != nil {
return x.PendingChanId
}
return nil
}
func (x *FundingPsbtVerify) GetSkipFinalize() bool {
if x != nil {
return x.SkipFinalize
}
return false
}
type FundingPsbtFinalize struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The funded PSBT that contains all witness data to send the exact channel
// capacity amount to the PK script returned in the open channel message in a
// previous step. Cannot be set at the same time as final_raw_tx.
SignedPsbt []byte `protobuf:"bytes,1,opt,name=signed_psbt,json=signedPsbt,proto3" json:"signed_psbt,omitempty"`
// The pending channel ID of the channel to get the PSBT for.
PendingChanId []byte `protobuf:"bytes,2,opt,name=pending_chan_id,json=pendingChanId,proto3" json:"pending_chan_id,omitempty"`
// As an alternative to the signed PSBT with all witness data, the final raw
// wire format transaction can also be specified directly. Cannot be set at the
// same time as signed_psbt.
FinalRawTx []byte `protobuf:"bytes,3,opt,name=final_raw_tx,json=finalRawTx,proto3" json:"final_raw_tx,omitempty"`
}
func (x *FundingPsbtFinalize) Reset() {
*x = FundingPsbtFinalize{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[85]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FundingPsbtFinalize) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FundingPsbtFinalize) ProtoMessage() {}
func (x *FundingPsbtFinalize) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[85]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FundingPsbtFinalize.ProtoReflect.Descriptor instead.
func (*FundingPsbtFinalize) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{85}
}
func (x *FundingPsbtFinalize) GetSignedPsbt() []byte {
if x != nil {
return x.SignedPsbt
}
return nil
}
func (x *FundingPsbtFinalize) GetPendingChanId() []byte {
if x != nil {
return x.PendingChanId
}
return nil
}
func (x *FundingPsbtFinalize) GetFinalRawTx() []byte {
if x != nil {
return x.FinalRawTx
}
return nil
}
type FundingTransitionMsg struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Trigger:
//
// *FundingTransitionMsg_ShimRegister
// *FundingTransitionMsg_ShimCancel
// *FundingTransitionMsg_PsbtVerify
// *FundingTransitionMsg_PsbtFinalize
Trigger isFundingTransitionMsg_Trigger `protobuf_oneof:"trigger"`
}
func (x *FundingTransitionMsg) Reset() {
*x = FundingTransitionMsg{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[86]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FundingTransitionMsg) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FundingTransitionMsg) ProtoMessage() {}
func (x *FundingTransitionMsg) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[86]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FundingTransitionMsg.ProtoReflect.Descriptor instead.
func (*FundingTransitionMsg) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{86}
}
func (m *FundingTransitionMsg) GetTrigger() isFundingTransitionMsg_Trigger {
if m != nil {
return m.Trigger
}
return nil
}
func (x *FundingTransitionMsg) GetShimRegister() *FundingShim {
if x, ok := x.GetTrigger().(*FundingTransitionMsg_ShimRegister); ok {
return x.ShimRegister
}
return nil
}
func (x *FundingTransitionMsg) GetShimCancel() *FundingShimCancel {
if x, ok := x.GetTrigger().(*FundingTransitionMsg_ShimCancel); ok {
return x.ShimCancel
}
return nil
}
func (x *FundingTransitionMsg) GetPsbtVerify() *FundingPsbtVerify {
if x, ok := x.GetTrigger().(*FundingTransitionMsg_PsbtVerify); ok {
return x.PsbtVerify
}
return nil
}
func (x *FundingTransitionMsg) GetPsbtFinalize() *FundingPsbtFinalize {
if x, ok := x.GetTrigger().(*FundingTransitionMsg_PsbtFinalize); ok {
return x.PsbtFinalize
}
return nil
}
type isFundingTransitionMsg_Trigger interface {
isFundingTransitionMsg_Trigger()
}
type FundingTransitionMsg_ShimRegister struct {
// The funding shim to register. This should be used before any
// channel funding has began by the remote party, as it is intended as a
// preparatory step for the full channel funding.
ShimRegister *FundingShim `protobuf:"bytes,1,opt,name=shim_register,json=shimRegister,proto3,oneof"`
}
type FundingTransitionMsg_ShimCancel struct {
// Used to cancel an existing registered funding shim.
ShimCancel *FundingShimCancel `protobuf:"bytes,2,opt,name=shim_cancel,json=shimCancel,proto3,oneof"`
}
type FundingTransitionMsg_PsbtVerify struct {
// Used to continue a funding flow that was initiated to be executed
// through a PSBT. This step verifies that the PSBT contains the correct
// outputs to fund the channel.
PsbtVerify *FundingPsbtVerify `protobuf:"bytes,3,opt,name=psbt_verify,json=psbtVerify,proto3,oneof"`
}
type FundingTransitionMsg_PsbtFinalize struct {
// Used to continue a funding flow that was initiated to be executed
// through a PSBT. This step finalizes the funded and signed PSBT, finishes
// negotiation with the peer and finally publishes the resulting funding
// transaction.
PsbtFinalize *FundingPsbtFinalize `protobuf:"bytes,4,opt,name=psbt_finalize,json=psbtFinalize,proto3,oneof"`
}
func (*FundingTransitionMsg_ShimRegister) isFundingTransitionMsg_Trigger() {}
func (*FundingTransitionMsg_ShimCancel) isFundingTransitionMsg_Trigger() {}
func (*FundingTransitionMsg_PsbtVerify) isFundingTransitionMsg_Trigger() {}
func (*FundingTransitionMsg_PsbtFinalize) isFundingTransitionMsg_Trigger() {}
type FundingStateStepResp struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *FundingStateStepResp) Reset() {
*x = FundingStateStepResp{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[87]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FundingStateStepResp) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FundingStateStepResp) ProtoMessage() {}
func (x *FundingStateStepResp) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[87]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FundingStateStepResp.ProtoReflect.Descriptor instead.
func (*FundingStateStepResp) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{87}
}
type PendingHTLC struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The direction within the channel that the htlc was sent
Incoming bool `protobuf:"varint,1,opt,name=incoming,proto3" json:"incoming,omitempty"`
// The total value of the htlc
Amount int64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"`
// The final output to be swept back to the user's wallet
Outpoint string `protobuf:"bytes,3,opt,name=outpoint,proto3" json:"outpoint,omitempty"`
// The next block height at which we can spend the current stage
MaturityHeight uint32 `protobuf:"varint,4,opt,name=maturity_height,json=maturityHeight,proto3" json:"maturity_height,omitempty"`
// The number of blocks remaining until the current stage can be swept.
// Negative values indicate how many blocks have passed since becoming
// mature.
BlocksTilMaturity int32 `protobuf:"varint,5,opt,name=blocks_til_maturity,json=blocksTilMaturity,proto3" json:"blocks_til_maturity,omitempty"`
// Indicates whether the htlc is in its first or second stage of recovery
Stage uint32 `protobuf:"varint,6,opt,name=stage,proto3" json:"stage,omitempty"`
}
func (x *PendingHTLC) Reset() {
*x = PendingHTLC{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[88]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PendingHTLC) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PendingHTLC) ProtoMessage() {}
func (x *PendingHTLC) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[88]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PendingHTLC.ProtoReflect.Descriptor instead.
func (*PendingHTLC) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{88}
}
func (x *PendingHTLC) GetIncoming() bool {
if x != nil {
return x.Incoming
}
return false
}
func (x *PendingHTLC) GetAmount() int64 {
if x != nil {
return x.Amount
}
return 0
}
func (x *PendingHTLC) GetOutpoint() string {
if x != nil {
return x.Outpoint
}
return ""
}
func (x *PendingHTLC) GetMaturityHeight() uint32 {
if x != nil {
return x.MaturityHeight
}
return 0
}
func (x *PendingHTLC) GetBlocksTilMaturity() int32 {
if x != nil {
return x.BlocksTilMaturity
}
return 0
}
func (x *PendingHTLC) GetStage() uint32 {
if x != nil {
return x.Stage
}
return 0
}
type PendingChannelsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Indicates whether to include the raw transaction hex for
// waiting_close_channels.
IncludeRawTx bool `protobuf:"varint,1,opt,name=include_raw_tx,json=includeRawTx,proto3" json:"include_raw_tx,omitempty"`
}
func (x *PendingChannelsRequest) Reset() {
*x = PendingChannelsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[89]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PendingChannelsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PendingChannelsRequest) ProtoMessage() {}
func (x *PendingChannelsRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[89]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PendingChannelsRequest.ProtoReflect.Descriptor instead.
func (*PendingChannelsRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{89}
}
func (x *PendingChannelsRequest) GetIncludeRawTx() bool {
if x != nil {
return x.IncludeRawTx
}
return false
}
type PendingChannelsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The balance in satoshis encumbered in pending channels
TotalLimboBalance int64 `protobuf:"varint,1,opt,name=total_limbo_balance,json=totalLimboBalance,proto3" json:"total_limbo_balance,omitempty"`
// Channels pending opening
PendingOpenChannels []*PendingChannelsResponse_PendingOpenChannel `protobuf:"bytes,2,rep,name=pending_open_channels,json=pendingOpenChannels,proto3" json:"pending_open_channels,omitempty"`
// Deprecated: Channels pending closing previously contained cooperatively
// closed channels with a single confirmation. These channels are now
// considered closed from the time we see them on chain.
//
// Deprecated: Marked as deprecated in lightning.proto.
PendingClosingChannels []*PendingChannelsResponse_ClosedChannel `protobuf:"bytes,3,rep,name=pending_closing_channels,json=pendingClosingChannels,proto3" json:"pending_closing_channels,omitempty"`
// Channels pending force closing
PendingForceClosingChannels []*PendingChannelsResponse_ForceClosedChannel `protobuf:"bytes,4,rep,name=pending_force_closing_channels,json=pendingForceClosingChannels,proto3" json:"pending_force_closing_channels,omitempty"`
// Channels waiting for closing tx to confirm
WaitingCloseChannels []*PendingChannelsResponse_WaitingCloseChannel `protobuf:"bytes,5,rep,name=waiting_close_channels,json=waitingCloseChannels,proto3" json:"waiting_close_channels,omitempty"`
}
func (x *PendingChannelsResponse) Reset() {
*x = PendingChannelsResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[90]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PendingChannelsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PendingChannelsResponse) ProtoMessage() {}
func (x *PendingChannelsResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[90]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PendingChannelsResponse.ProtoReflect.Descriptor instead.
func (*PendingChannelsResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{90}
}
func (x *PendingChannelsResponse) GetTotalLimboBalance() int64 {
if x != nil {
return x.TotalLimboBalance
}
return 0
}
func (x *PendingChannelsResponse) GetPendingOpenChannels() []*PendingChannelsResponse_PendingOpenChannel {
if x != nil {
return x.PendingOpenChannels
}
return nil
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *PendingChannelsResponse) GetPendingClosingChannels() []*PendingChannelsResponse_ClosedChannel {
if x != nil {
return x.PendingClosingChannels
}
return nil
}
func (x *PendingChannelsResponse) GetPendingForceClosingChannels() []*PendingChannelsResponse_ForceClosedChannel {
if x != nil {
return x.PendingForceClosingChannels
}
return nil
}
func (x *PendingChannelsResponse) GetWaitingCloseChannels() []*PendingChannelsResponse_WaitingCloseChannel {
if x != nil {
return x.WaitingCloseChannels
}
return nil
}
type ChannelEventSubscription struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ChannelEventSubscription) Reset() {
*x = ChannelEventSubscription{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[91]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChannelEventSubscription) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelEventSubscription) ProtoMessage() {}
func (x *ChannelEventSubscription) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[91]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelEventSubscription.ProtoReflect.Descriptor instead.
func (*ChannelEventSubscription) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{91}
}
type ChannelEventUpdate struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Channel:
//
// *ChannelEventUpdate_OpenChannel
// *ChannelEventUpdate_ClosedChannel
// *ChannelEventUpdate_ActiveChannel
// *ChannelEventUpdate_InactiveChannel
// *ChannelEventUpdate_PendingOpenChannel
// *ChannelEventUpdate_FullyResolvedChannel
Channel isChannelEventUpdate_Channel `protobuf_oneof:"channel"`
Type ChannelEventUpdate_UpdateType `protobuf:"varint,5,opt,name=type,proto3,enum=lnrpc.ChannelEventUpdate_UpdateType" json:"type,omitempty"`
}
func (x *ChannelEventUpdate) Reset() {
*x = ChannelEventUpdate{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[92]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChannelEventUpdate) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelEventUpdate) ProtoMessage() {}
func (x *ChannelEventUpdate) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[92]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelEventUpdate.ProtoReflect.Descriptor instead.
func (*ChannelEventUpdate) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{92}
}
func (m *ChannelEventUpdate) GetChannel() isChannelEventUpdate_Channel {
if m != nil {
return m.Channel
}
return nil
}
func (x *ChannelEventUpdate) GetOpenChannel() *Channel {
if x, ok := x.GetChannel().(*ChannelEventUpdate_OpenChannel); ok {
return x.OpenChannel
}
return nil
}
func (x *ChannelEventUpdate) GetClosedChannel() *ChannelCloseSummary {
if x, ok := x.GetChannel().(*ChannelEventUpdate_ClosedChannel); ok {
return x.ClosedChannel
}
return nil
}
func (x *ChannelEventUpdate) GetActiveChannel() *ChannelPoint {
if x, ok := x.GetChannel().(*ChannelEventUpdate_ActiveChannel); ok {
return x.ActiveChannel
}
return nil
}
func (x *ChannelEventUpdate) GetInactiveChannel() *ChannelPoint {
if x, ok := x.GetChannel().(*ChannelEventUpdate_InactiveChannel); ok {
return x.InactiveChannel
}
return nil
}
func (x *ChannelEventUpdate) GetPendingOpenChannel() *PendingUpdate {
if x, ok := x.GetChannel().(*ChannelEventUpdate_PendingOpenChannel); ok {
return x.PendingOpenChannel
}
return nil
}
func (x *ChannelEventUpdate) GetFullyResolvedChannel() *ChannelPoint {
if x, ok := x.GetChannel().(*ChannelEventUpdate_FullyResolvedChannel); ok {
return x.FullyResolvedChannel
}
return nil
}
func (x *ChannelEventUpdate) GetType() ChannelEventUpdate_UpdateType {
if x != nil {
return x.Type
}
return ChannelEventUpdate_OPEN_CHANNEL
}
type isChannelEventUpdate_Channel interface {
isChannelEventUpdate_Channel()
}
type ChannelEventUpdate_OpenChannel struct {
OpenChannel *Channel `protobuf:"bytes,1,opt,name=open_channel,json=openChannel,proto3,oneof"`
}
type ChannelEventUpdate_ClosedChannel struct {
ClosedChannel *ChannelCloseSummary `protobuf:"bytes,2,opt,name=closed_channel,json=closedChannel,proto3,oneof"`
}
type ChannelEventUpdate_ActiveChannel struct {
ActiveChannel *ChannelPoint `protobuf:"bytes,3,opt,name=active_channel,json=activeChannel,proto3,oneof"`
}
type ChannelEventUpdate_InactiveChannel struct {
InactiveChannel *ChannelPoint `protobuf:"bytes,4,opt,name=inactive_channel,json=inactiveChannel,proto3,oneof"`
}
type ChannelEventUpdate_PendingOpenChannel struct {
PendingOpenChannel *PendingUpdate `protobuf:"bytes,6,opt,name=pending_open_channel,json=pendingOpenChannel,proto3,oneof"`
}
type ChannelEventUpdate_FullyResolvedChannel struct {
FullyResolvedChannel *ChannelPoint `protobuf:"bytes,7,opt,name=fully_resolved_channel,json=fullyResolvedChannel,proto3,oneof"`
}
func (*ChannelEventUpdate_OpenChannel) isChannelEventUpdate_Channel() {}
func (*ChannelEventUpdate_ClosedChannel) isChannelEventUpdate_Channel() {}
func (*ChannelEventUpdate_ActiveChannel) isChannelEventUpdate_Channel() {}
func (*ChannelEventUpdate_InactiveChannel) isChannelEventUpdate_Channel() {}
func (*ChannelEventUpdate_PendingOpenChannel) isChannelEventUpdate_Channel() {}
func (*ChannelEventUpdate_FullyResolvedChannel) isChannelEventUpdate_Channel() {}
type WalletAccountBalance struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The confirmed balance of the account (with >= 1 confirmations).
ConfirmedBalance int64 `protobuf:"varint,1,opt,name=confirmed_balance,json=confirmedBalance,proto3" json:"confirmed_balance,omitempty"`
// The unconfirmed balance of the account (with 0 confirmations).
UnconfirmedBalance int64 `protobuf:"varint,2,opt,name=unconfirmed_balance,json=unconfirmedBalance,proto3" json:"unconfirmed_balance,omitempty"`
}
func (x *WalletAccountBalance) Reset() {
*x = WalletAccountBalance{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[93]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *WalletAccountBalance) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*WalletAccountBalance) ProtoMessage() {}
func (x *WalletAccountBalance) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[93]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use WalletAccountBalance.ProtoReflect.Descriptor instead.
func (*WalletAccountBalance) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{93}
}
func (x *WalletAccountBalance) GetConfirmedBalance() int64 {
if x != nil {
return x.ConfirmedBalance
}
return 0
}
func (x *WalletAccountBalance) GetUnconfirmedBalance() int64 {
if x != nil {
return x.UnconfirmedBalance
}
return 0
}
type WalletBalanceRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The wallet account the balance is shown for.
// If this is not specified, the balance of the "default" account is shown.
Account string `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"`
// The minimum number of confirmations each one of your outputs used for the
// funding transaction must satisfy. If this is not specified, the default
// value of 1 is used.
MinConfs int32 `protobuf:"varint,2,opt,name=min_confs,json=minConfs,proto3" json:"min_confs,omitempty"`
}
func (x *WalletBalanceRequest) Reset() {
*x = WalletBalanceRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[94]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *WalletBalanceRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*WalletBalanceRequest) ProtoMessage() {}
func (x *WalletBalanceRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[94]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use WalletBalanceRequest.ProtoReflect.Descriptor instead.
func (*WalletBalanceRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{94}
}
func (x *WalletBalanceRequest) GetAccount() string {
if x != nil {
return x.Account
}
return ""
}
func (x *WalletBalanceRequest) GetMinConfs() int32 {
if x != nil {
return x.MinConfs
}
return 0
}
type WalletBalanceResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The balance of the wallet
TotalBalance int64 `protobuf:"varint,1,opt,name=total_balance,json=totalBalance,proto3" json:"total_balance,omitempty"`
// The confirmed balance of a wallet(with >= 1 confirmations)
ConfirmedBalance int64 `protobuf:"varint,2,opt,name=confirmed_balance,json=confirmedBalance,proto3" json:"confirmed_balance,omitempty"`
// The unconfirmed balance of a wallet(with 0 confirmations)
UnconfirmedBalance int64 `protobuf:"varint,3,opt,name=unconfirmed_balance,json=unconfirmedBalance,proto3" json:"unconfirmed_balance,omitempty"`
// The total amount of wallet UTXOs held in outputs that are locked for
// other usage.
LockedBalance int64 `protobuf:"varint,5,opt,name=locked_balance,json=lockedBalance,proto3" json:"locked_balance,omitempty"`
// The amount of reserve required.
ReservedBalanceAnchorChan int64 `protobuf:"varint,6,opt,name=reserved_balance_anchor_chan,json=reservedBalanceAnchorChan,proto3" json:"reserved_balance_anchor_chan,omitempty"`
// A mapping of each wallet account's name to its balance.
AccountBalance map[string]*WalletAccountBalance `protobuf:"bytes,4,rep,name=account_balance,json=accountBalance,proto3" json:"account_balance,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *WalletBalanceResponse) Reset() {
*x = WalletBalanceResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[95]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *WalletBalanceResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*WalletBalanceResponse) ProtoMessage() {}
func (x *WalletBalanceResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[95]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use WalletBalanceResponse.ProtoReflect.Descriptor instead.
func (*WalletBalanceResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{95}
}
func (x *WalletBalanceResponse) GetTotalBalance() int64 {
if x != nil {
return x.TotalBalance
}
return 0
}
func (x *WalletBalanceResponse) GetConfirmedBalance() int64 {
if x != nil {
return x.ConfirmedBalance
}
return 0
}
func (x *WalletBalanceResponse) GetUnconfirmedBalance() int64 {
if x != nil {
return x.UnconfirmedBalance
}
return 0
}
func (x *WalletBalanceResponse) GetLockedBalance() int64 {
if x != nil {
return x.LockedBalance
}
return 0
}
func (x *WalletBalanceResponse) GetReservedBalanceAnchorChan() int64 {
if x != nil {
return x.ReservedBalanceAnchorChan
}
return 0
}
func (x *WalletBalanceResponse) GetAccountBalance() map[string]*WalletAccountBalance {
if x != nil {
return x.AccountBalance
}
return nil
}
type Amount struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Value denominated in satoshis.
Sat uint64 `protobuf:"varint,1,opt,name=sat,proto3" json:"sat,omitempty"`
// Value denominated in milli-satoshis.
Msat uint64 `protobuf:"varint,2,opt,name=msat,proto3" json:"msat,omitempty"`
}
func (x *Amount) Reset() {
*x = Amount{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[96]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Amount) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Amount) ProtoMessage() {}
func (x *Amount) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[96]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Amount.ProtoReflect.Descriptor instead.
func (*Amount) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{96}
}
func (x *Amount) GetSat() uint64 {
if x != nil {
return x.Sat
}
return 0
}
func (x *Amount) GetMsat() uint64 {
if x != nil {
return x.Msat
}
return 0
}
type ChannelBalanceRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ChannelBalanceRequest) Reset() {
*x = ChannelBalanceRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[97]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChannelBalanceRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelBalanceRequest) ProtoMessage() {}
func (x *ChannelBalanceRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[97]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelBalanceRequest.ProtoReflect.Descriptor instead.
func (*ChannelBalanceRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{97}
}
type ChannelBalanceResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Deprecated. Sum of channels balances denominated in satoshis
//
// Deprecated: Marked as deprecated in lightning.proto.
Balance int64 `protobuf:"varint,1,opt,name=balance,proto3" json:"balance,omitempty"`
// Deprecated. Sum of channels pending balances denominated in satoshis
//
// Deprecated: Marked as deprecated in lightning.proto.
PendingOpenBalance int64 `protobuf:"varint,2,opt,name=pending_open_balance,json=pendingOpenBalance,proto3" json:"pending_open_balance,omitempty"`
// Sum of channels local balances.
LocalBalance *Amount `protobuf:"bytes,3,opt,name=local_balance,json=localBalance,proto3" json:"local_balance,omitempty"`
// Sum of channels remote balances.
RemoteBalance *Amount `protobuf:"bytes,4,opt,name=remote_balance,json=remoteBalance,proto3" json:"remote_balance,omitempty"`
// Sum of channels local unsettled balances.
UnsettledLocalBalance *Amount `protobuf:"bytes,5,opt,name=unsettled_local_balance,json=unsettledLocalBalance,proto3" json:"unsettled_local_balance,omitempty"`
// Sum of channels remote unsettled balances.
UnsettledRemoteBalance *Amount `protobuf:"bytes,6,opt,name=unsettled_remote_balance,json=unsettledRemoteBalance,proto3" json:"unsettled_remote_balance,omitempty"`
// Sum of channels pending local balances.
PendingOpenLocalBalance *Amount `protobuf:"bytes,7,opt,name=pending_open_local_balance,json=pendingOpenLocalBalance,proto3" json:"pending_open_local_balance,omitempty"`
// Sum of channels pending remote balances.
PendingOpenRemoteBalance *Amount `protobuf:"bytes,8,opt,name=pending_open_remote_balance,json=pendingOpenRemoteBalance,proto3" json:"pending_open_remote_balance,omitempty"`
// Custom channel data that might be populated if there are custom channels
// present.
CustomChannelData []byte `protobuf:"bytes,9,opt,name=custom_channel_data,json=customChannelData,proto3" json:"custom_channel_data,omitempty"`
}
func (x *ChannelBalanceResponse) Reset() {
*x = ChannelBalanceResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[98]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChannelBalanceResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelBalanceResponse) ProtoMessage() {}
func (x *ChannelBalanceResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[98]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelBalanceResponse.ProtoReflect.Descriptor instead.
func (*ChannelBalanceResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{98}
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *ChannelBalanceResponse) GetBalance() int64 {
if x != nil {
return x.Balance
}
return 0
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *ChannelBalanceResponse) GetPendingOpenBalance() int64 {
if x != nil {
return x.PendingOpenBalance
}
return 0
}
func (x *ChannelBalanceResponse) GetLocalBalance() *Amount {
if x != nil {
return x.LocalBalance
}
return nil
}
func (x *ChannelBalanceResponse) GetRemoteBalance() *Amount {
if x != nil {
return x.RemoteBalance
}
return nil
}
func (x *ChannelBalanceResponse) GetUnsettledLocalBalance() *Amount {
if x != nil {
return x.UnsettledLocalBalance
}
return nil
}
func (x *ChannelBalanceResponse) GetUnsettledRemoteBalance() *Amount {
if x != nil {
return x.UnsettledRemoteBalance
}
return nil
}
func (x *ChannelBalanceResponse) GetPendingOpenLocalBalance() *Amount {
if x != nil {
return x.PendingOpenLocalBalance
}
return nil
}
func (x *ChannelBalanceResponse) GetPendingOpenRemoteBalance() *Amount {
if x != nil {
return x.PendingOpenRemoteBalance
}
return nil
}
func (x *ChannelBalanceResponse) GetCustomChannelData() []byte {
if x != nil {
return x.CustomChannelData
}
return nil
}
type QueryRoutesRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The 33-byte hex-encoded public key for the payment destination
PubKey string `protobuf:"bytes,1,opt,name=pub_key,json=pubKey,proto3" json:"pub_key,omitempty"`
// The amount to send expressed in satoshis.
//
// The fields amt and amt_msat are mutually exclusive.
Amt int64 `protobuf:"varint,2,opt,name=amt,proto3" json:"amt,omitempty"`
// The amount to send expressed in millisatoshis.
//
// The fields amt and amt_msat are mutually exclusive.
AmtMsat int64 `protobuf:"varint,12,opt,name=amt_msat,json=amtMsat,proto3" json:"amt_msat,omitempty"`
// An optional CLTV delta from the current height that should be used for the
// timelock of the final hop. Note that unlike SendPayment, QueryRoutes does
// not add any additional block padding on top of final_ctlv_delta. This
// padding of a few blocks needs to be added manually or otherwise failures may
// happen when a block comes in while the payment is in flight.
//
// Note: must not be set if making a payment to a blinded path (delta is
// set by the aggregate parameters provided by blinded_payment_paths)
FinalCltvDelta int32 `protobuf:"varint,4,opt,name=final_cltv_delta,json=finalCltvDelta,proto3" json:"final_cltv_delta,omitempty"`
// The maximum number of satoshis that will be paid as a fee of the payment.
// This value can be represented either as a percentage of the amount being
// sent, or as a fixed amount of the maximum fee the user is willing the pay to
// send the payment. If not specified, lnd will use a default value of 100%
// fees for small amounts (<=1k sat) or 5% fees for larger amounts.
FeeLimit *FeeLimit `protobuf:"bytes,5,opt,name=fee_limit,json=feeLimit,proto3" json:"fee_limit,omitempty"`
// A list of nodes to ignore during path finding. When using REST, these fields
// must be encoded as base64.
IgnoredNodes [][]byte `protobuf:"bytes,6,rep,name=ignored_nodes,json=ignoredNodes,proto3" json:"ignored_nodes,omitempty"`
// Deprecated. A list of edges to ignore during path finding.
//
// Deprecated: Marked as deprecated in lightning.proto.
IgnoredEdges []*EdgeLocator `protobuf:"bytes,7,rep,name=ignored_edges,json=ignoredEdges,proto3" json:"ignored_edges,omitempty"`
// The source node where the request route should originated from. If empty,
// self is assumed.
SourcePubKey string `protobuf:"bytes,8,opt,name=source_pub_key,json=sourcePubKey,proto3" json:"source_pub_key,omitempty"`
// If set to true, edge probabilities from mission control will be used to get
// the optimal route.
UseMissionControl bool `protobuf:"varint,9,opt,name=use_mission_control,json=useMissionControl,proto3" json:"use_mission_control,omitempty"`
// A list of directed node pairs that will be ignored during path finding.
IgnoredPairs []*NodePair `protobuf:"bytes,10,rep,name=ignored_pairs,json=ignoredPairs,proto3" json:"ignored_pairs,omitempty"`
// An optional maximum total time lock for the route. If the source is empty or
// ourselves, this should not exceed lnd's `--max-cltv-expiry` setting. If
// zero, then the value of `--max-cltv-expiry` is used as the limit.
CltvLimit uint32 `protobuf:"varint,11,opt,name=cltv_limit,json=cltvLimit,proto3" json:"cltv_limit,omitempty"`
// An optional field that can be used to pass an arbitrary set of TLV records
// to a peer which understands the new records. This can be used to pass
// application specific data during the payment attempt. If the destination
// does not support the specified records, an error will be returned.
// Record types are required to be in the custom range >= 65536. When using
// REST, the values must be encoded as base64.
DestCustomRecords map[uint64][]byte `protobuf:"bytes,13,rep,name=dest_custom_records,json=destCustomRecords,proto3" json:"dest_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
// The channel id of the channel that must be taken to the first hop. If zero,
// any channel may be used.
OutgoingChanId uint64 `protobuf:"varint,14,opt,name=outgoing_chan_id,json=outgoingChanId,proto3" json:"outgoing_chan_id,omitempty"`
// The pubkey of the last hop of the route. If empty, any hop may be used.
LastHopPubkey []byte `protobuf:"bytes,15,opt,name=last_hop_pubkey,json=lastHopPubkey,proto3" json:"last_hop_pubkey,omitempty"`
// Optional route hints to reach the destination through private channels.
RouteHints []*RouteHint `protobuf:"bytes,16,rep,name=route_hints,json=routeHints,proto3" json:"route_hints,omitempty"`
// An optional blinded path(s) to reach the destination. Note that the
// introduction node must be provided as the first hop in the route.
BlindedPaymentPaths []*BlindedPaymentPath `protobuf:"bytes,19,rep,name=blinded_payment_paths,json=blindedPaymentPaths,proto3" json:"blinded_payment_paths,omitempty"`
// Features assumed to be supported by the final node. All transitive feature
// dependencies must also be set properly. For a given feature bit pair, either
// optional or remote may be set, but not both. If this field is nil or empty,
// the router will try to load destination features from the graph as a
// fallback.
//
// Note: must not be set if making a payment to a blinded route (features
// are provided in blinded_payment_paths).
DestFeatures []FeatureBit `protobuf:"varint,17,rep,packed,name=dest_features,json=destFeatures,proto3,enum=lnrpc.FeatureBit" json:"dest_features,omitempty"`
// The time preference for this payment. Set to -1 to optimize for fees
// only, to 1 to optimize for reliability only or a value inbetween for a mix.
TimePref float64 `protobuf:"fixed64,18,opt,name=time_pref,json=timePref,proto3" json:"time_pref,omitempty"`
}
func (x *QueryRoutesRequest) Reset() {
*x = QueryRoutesRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[99]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *QueryRoutesRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*QueryRoutesRequest) ProtoMessage() {}
func (x *QueryRoutesRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[99]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use QueryRoutesRequest.ProtoReflect.Descriptor instead.
func (*QueryRoutesRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{99}
}
func (x *QueryRoutesRequest) GetPubKey() string {
if x != nil {
return x.PubKey
}
return ""
}
func (x *QueryRoutesRequest) GetAmt() int64 {
if x != nil {
return x.Amt
}
return 0
}
func (x *QueryRoutesRequest) GetAmtMsat() int64 {
if x != nil {
return x.AmtMsat
}
return 0
}
func (x *QueryRoutesRequest) GetFinalCltvDelta() int32 {
if x != nil {
return x.FinalCltvDelta
}
return 0
}
func (x *QueryRoutesRequest) GetFeeLimit() *FeeLimit {
if x != nil {
return x.FeeLimit
}
return nil
}
func (x *QueryRoutesRequest) GetIgnoredNodes() [][]byte {
if x != nil {
return x.IgnoredNodes
}
return nil
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *QueryRoutesRequest) GetIgnoredEdges() []*EdgeLocator {
if x != nil {
return x.IgnoredEdges
}
return nil
}
func (x *QueryRoutesRequest) GetSourcePubKey() string {
if x != nil {
return x.SourcePubKey
}
return ""
}
func (x *QueryRoutesRequest) GetUseMissionControl() bool {
if x != nil {
return x.UseMissionControl
}
return false
}
func (x *QueryRoutesRequest) GetIgnoredPairs() []*NodePair {
if x != nil {
return x.IgnoredPairs
}
return nil
}
func (x *QueryRoutesRequest) GetCltvLimit() uint32 {
if x != nil {
return x.CltvLimit
}
return 0
}
func (x *QueryRoutesRequest) GetDestCustomRecords() map[uint64][]byte {
if x != nil {
return x.DestCustomRecords
}
return nil
}
func (x *QueryRoutesRequest) GetOutgoingChanId() uint64 {
if x != nil {
return x.OutgoingChanId
}
return 0
}
func (x *QueryRoutesRequest) GetLastHopPubkey() []byte {
if x != nil {
return x.LastHopPubkey
}
return nil
}
func (x *QueryRoutesRequest) GetRouteHints() []*RouteHint {
if x != nil {
return x.RouteHints
}
return nil
}
func (x *QueryRoutesRequest) GetBlindedPaymentPaths() []*BlindedPaymentPath {
if x != nil {
return x.BlindedPaymentPaths
}
return nil
}
func (x *QueryRoutesRequest) GetDestFeatures() []FeatureBit {
if x != nil {
return x.DestFeatures
}
return nil
}
func (x *QueryRoutesRequest) GetTimePref() float64 {
if x != nil {
return x.TimePref
}
return 0
}
type NodePair struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The sending node of the pair. When using REST, this field must be encoded as
// base64.
From []byte `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"`
// The receiving node of the pair. When using REST, this field must be encoded
// as base64.
To []byte `protobuf:"bytes,2,opt,name=to,proto3" json:"to,omitempty"`
}
func (x *NodePair) Reset() {
*x = NodePair{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[100]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *NodePair) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NodePair) ProtoMessage() {}
func (x *NodePair) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[100]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NodePair.ProtoReflect.Descriptor instead.
func (*NodePair) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{100}
}
func (x *NodePair) GetFrom() []byte {
if x != nil {
return x.From
}
return nil
}
func (x *NodePair) GetTo() []byte {
if x != nil {
return x.To
}
return nil
}
type EdgeLocator struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The short channel id of this edge.
ChannelId uint64 `protobuf:"varint,1,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"`
// The direction of this edge. If direction_reverse is false, the direction
// of this edge is from the channel endpoint with the lexicographically smaller
// pub key to the endpoint with the larger pub key. If direction_reverse is
// is true, the edge goes the other way.
DirectionReverse bool `protobuf:"varint,2,opt,name=direction_reverse,json=directionReverse,proto3" json:"direction_reverse,omitempty"`
}
func (x *EdgeLocator) Reset() {
*x = EdgeLocator{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[101]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *EdgeLocator) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EdgeLocator) ProtoMessage() {}
func (x *EdgeLocator) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[101]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EdgeLocator.ProtoReflect.Descriptor instead.
func (*EdgeLocator) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{101}
}
func (x *EdgeLocator) GetChannelId() uint64 {
if x != nil {
return x.ChannelId
}
return 0
}
func (x *EdgeLocator) GetDirectionReverse() bool {
if x != nil {
return x.DirectionReverse
}
return false
}
type QueryRoutesResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The route that results from the path finding operation. This is still a
// repeated field to retain backwards compatibility.
Routes []*Route `protobuf:"bytes,1,rep,name=routes,proto3" json:"routes,omitempty"`
// The success probability of the returned route based on the current mission
// control state. [EXPERIMENTAL]
SuccessProb float64 `protobuf:"fixed64,2,opt,name=success_prob,json=successProb,proto3" json:"success_prob,omitempty"`
}
func (x *QueryRoutesResponse) Reset() {
*x = QueryRoutesResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[102]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *QueryRoutesResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*QueryRoutesResponse) ProtoMessage() {}
func (x *QueryRoutesResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[102]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use QueryRoutesResponse.ProtoReflect.Descriptor instead.
func (*QueryRoutesResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{102}
}
func (x *QueryRoutesResponse) GetRoutes() []*Route {
if x != nil {
return x.Routes
}
return nil
}
func (x *QueryRoutesResponse) GetSuccessProb() float64 {
if x != nil {
return x.SuccessProb
}
return 0
}
type Hop struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The unique channel ID for the channel. The first 3 bytes are the block
// height, the next 3 the index within the block, and the last 2 bytes are the
// output index for the channel.
ChanId uint64 `protobuf:"varint,1,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"`
// Deprecated: Marked as deprecated in lightning.proto.
ChanCapacity int64 `protobuf:"varint,2,opt,name=chan_capacity,json=chanCapacity,proto3" json:"chan_capacity,omitempty"`
// Deprecated: Marked as deprecated in lightning.proto.
AmtToForward int64 `protobuf:"varint,3,opt,name=amt_to_forward,json=amtToForward,proto3" json:"amt_to_forward,omitempty"`
// Deprecated: Marked as deprecated in lightning.proto.
Fee int64 `protobuf:"varint,4,opt,name=fee,proto3" json:"fee,omitempty"`
Expiry uint32 `protobuf:"varint,5,opt,name=expiry,proto3" json:"expiry,omitempty"`
AmtToForwardMsat int64 `protobuf:"varint,6,opt,name=amt_to_forward_msat,json=amtToForwardMsat,proto3" json:"amt_to_forward_msat,omitempty"`
FeeMsat int64 `protobuf:"varint,7,opt,name=fee_msat,json=feeMsat,proto3" json:"fee_msat,omitempty"`
// An optional public key of the hop. If the public key is given, the payment
// can be executed without relying on a copy of the channel graph.
PubKey string `protobuf:"bytes,8,opt,name=pub_key,json=pubKey,proto3" json:"pub_key,omitempty"`
// If set to true, then this hop will be encoded using the new variable length
// TLV format. Note that if any custom tlv_records below are specified, then
// this field MUST be set to true for them to be encoded properly.
//
// Deprecated: Marked as deprecated in lightning.proto.
TlvPayload bool `protobuf:"varint,9,opt,name=tlv_payload,json=tlvPayload,proto3" json:"tlv_payload,omitempty"`
// An optional TLV record that signals the use of an MPP payment. If present,
// the receiver will enforce that the same mpp_record is included in the final
// hop payload of all non-zero payments in the HTLC set. If empty, a regular
// single-shot payment is or was attempted.
MppRecord *MPPRecord `protobuf:"bytes,10,opt,name=mpp_record,json=mppRecord,proto3" json:"mpp_record,omitempty"`
// An optional TLV record that signals the use of an AMP payment. If present,
// the receiver will treat all received payments including the same
// (payment_addr, set_id) pair as being part of one logical payment. The
// payment will be settled by XORing the root_share's together and deriving the
// child hashes and preimages according to BOLT XX. Must be used in conjunction
// with mpp_record.
AmpRecord *AMPRecord `protobuf:"bytes,12,opt,name=amp_record,json=ampRecord,proto3" json:"amp_record,omitempty"`
// An optional set of key-value TLV records. This is useful within the context
// of the SendToRoute call as it allows callers to specify arbitrary K-V pairs
// to drop off at each hop within the onion.
CustomRecords map[uint64][]byte `protobuf:"bytes,11,rep,name=custom_records,json=customRecords,proto3" json:"custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
// The payment metadata to send along with the payment to the payee.
Metadata []byte `protobuf:"bytes,13,opt,name=metadata,proto3" json:"metadata,omitempty"`
// Blinding point is an optional blinding point included for introduction
// nodes in blinded paths. This field is mandatory for hops that represents
// the introduction point in a blinded path.
BlindingPoint []byte `protobuf:"bytes,14,opt,name=blinding_point,json=blindingPoint,proto3" json:"blinding_point,omitempty"`
// Encrypted data is a receiver-produced blob of data that provides hops
// in a blinded route with forwarding data. As this data is encrypted by
// the recipient, we will not be able to parse it - it is essentially an
// arbitrary blob of data from our node's perspective. This field is
// mandatory for all hops in a blinded path, including the introduction
// node.
EncryptedData []byte `protobuf:"bytes,15,opt,name=encrypted_data,json=encryptedData,proto3" json:"encrypted_data,omitempty"`
// The total amount that is sent to the recipient (possibly across multiple
// HTLCs), as specified by the sender when making a payment to a blinded path.
// This value is only set in the final hop payload of a blinded payment. This
// value is analogous to the MPPRecord that is used for regular (non-blinded)
// MPP payments.
TotalAmtMsat uint64 `protobuf:"varint,16,opt,name=total_amt_msat,json=totalAmtMsat,proto3" json:"total_amt_msat,omitempty"`
}
func (x *Hop) Reset() {
*x = Hop{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[103]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Hop) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Hop) ProtoMessage() {}
func (x *Hop) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[103]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Hop.ProtoReflect.Descriptor instead.
func (*Hop) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{103}
}
func (x *Hop) GetChanId() uint64 {
if x != nil {
return x.ChanId
}
return 0
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *Hop) GetChanCapacity() int64 {
if x != nil {
return x.ChanCapacity
}
return 0
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *Hop) GetAmtToForward() int64 {
if x != nil {
return x.AmtToForward
}
return 0
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *Hop) GetFee() int64 {
if x != nil {
return x.Fee
}
return 0
}
func (x *Hop) GetExpiry() uint32 {
if x != nil {
return x.Expiry
}
return 0
}
func (x *Hop) GetAmtToForwardMsat() int64 {
if x != nil {
return x.AmtToForwardMsat
}
return 0
}
func (x *Hop) GetFeeMsat() int64 {
if x != nil {
return x.FeeMsat
}
return 0
}
func (x *Hop) GetPubKey() string {
if x != nil {
return x.PubKey
}
return ""
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *Hop) GetTlvPayload() bool {
if x != nil {
return x.TlvPayload
}
return false
}
func (x *Hop) GetMppRecord() *MPPRecord {
if x != nil {
return x.MppRecord
}
return nil
}
func (x *Hop) GetAmpRecord() *AMPRecord {
if x != nil {
return x.AmpRecord
}
return nil
}
func (x *Hop) GetCustomRecords() map[uint64][]byte {
if x != nil {
return x.CustomRecords
}
return nil
}
func (x *Hop) GetMetadata() []byte {
if x != nil {
return x.Metadata
}
return nil
}
func (x *Hop) GetBlindingPoint() []byte {
if x != nil {
return x.BlindingPoint
}
return nil
}
func (x *Hop) GetEncryptedData() []byte {
if x != nil {
return x.EncryptedData
}
return nil
}
func (x *Hop) GetTotalAmtMsat() uint64 {
if x != nil {
return x.TotalAmtMsat
}
return 0
}
type MPPRecord struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A unique, random identifier used to authenticate the sender as the intended
// payer of a multi-path payment. The payment_addr must be the same for all
// subpayments, and match the payment_addr provided in the receiver's invoice.
// The same payment_addr must be used on all subpayments. This is also called
// payment secret in specifications (e.g. BOLT 11).
PaymentAddr []byte `protobuf:"bytes,11,opt,name=payment_addr,json=paymentAddr,proto3" json:"payment_addr,omitempty"`
// The total amount in milli-satoshis being sent as part of a larger multi-path
// payment. The caller is responsible for ensuring subpayments to the same node
// and payment_hash sum exactly to total_amt_msat. The same
// total_amt_msat must be used on all subpayments.
TotalAmtMsat int64 `protobuf:"varint,10,opt,name=total_amt_msat,json=totalAmtMsat,proto3" json:"total_amt_msat,omitempty"`
}
func (x *MPPRecord) Reset() {
*x = MPPRecord{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[104]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MPPRecord) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MPPRecord) ProtoMessage() {}
func (x *MPPRecord) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[104]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MPPRecord.ProtoReflect.Descriptor instead.
func (*MPPRecord) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{104}
}
func (x *MPPRecord) GetPaymentAddr() []byte {
if x != nil {
return x.PaymentAddr
}
return nil
}
func (x *MPPRecord) GetTotalAmtMsat() int64 {
if x != nil {
return x.TotalAmtMsat
}
return 0
}
type AMPRecord struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
RootShare []byte `protobuf:"bytes,1,opt,name=root_share,json=rootShare,proto3" json:"root_share,omitempty"`
SetId []byte `protobuf:"bytes,2,opt,name=set_id,json=setId,proto3" json:"set_id,omitempty"`
ChildIndex uint32 `protobuf:"varint,3,opt,name=child_index,json=childIndex,proto3" json:"child_index,omitempty"`
}
func (x *AMPRecord) Reset() {
*x = AMPRecord{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[105]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AMPRecord) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AMPRecord) ProtoMessage() {}
func (x *AMPRecord) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[105]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AMPRecord.ProtoReflect.Descriptor instead.
func (*AMPRecord) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{105}
}
func (x *AMPRecord) GetRootShare() []byte {
if x != nil {
return x.RootShare
}
return nil
}
func (x *AMPRecord) GetSetId() []byte {
if x != nil {
return x.SetId
}
return nil
}
func (x *AMPRecord) GetChildIndex() uint32 {
if x != nil {
return x.ChildIndex
}
return 0
}
// A path through the channel graph which runs over one or more channels in
// succession. This struct carries all the information required to craft the
// Sphinx onion packet, and send the payment along the first hop in the path. A
// route is only selected as valid if all the channels have sufficient capacity to
// carry the initial payment amount after fees are accounted for.
type Route struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The cumulative (final) time lock across the entire route. This is the CLTV
// value that should be extended to the first hop in the route. All other hops
// will decrement the time-lock as advertised, leaving enough time for all
// hops to wait for or present the payment preimage to complete the payment.
TotalTimeLock uint32 `protobuf:"varint,1,opt,name=total_time_lock,json=totalTimeLock,proto3" json:"total_time_lock,omitempty"`
// The sum of the fees paid at each hop within the final route. In the case
// of a one-hop payment, this value will be zero as we don't need to pay a fee
// to ourselves.
//
// Deprecated: Marked as deprecated in lightning.proto.
TotalFees int64 `protobuf:"varint,2,opt,name=total_fees,json=totalFees,proto3" json:"total_fees,omitempty"`
// The total amount of funds required to complete a payment over this route.
// This value includes the cumulative fees at each hop. As a result, the HTLC
// extended to the first-hop in the route will need to have at least this many
// satoshis, otherwise the route will fail at an intermediate node due to an
// insufficient amount of fees.
//
// Deprecated: Marked as deprecated in lightning.proto.
TotalAmt int64 `protobuf:"varint,3,opt,name=total_amt,json=totalAmt,proto3" json:"total_amt,omitempty"`
// Contains details concerning the specific forwarding details at each hop.
Hops []*Hop `protobuf:"bytes,4,rep,name=hops,proto3" json:"hops,omitempty"`
// The total fees in millisatoshis.
TotalFeesMsat int64 `protobuf:"varint,5,opt,name=total_fees_msat,json=totalFeesMsat,proto3" json:"total_fees_msat,omitempty"`
// The total amount in millisatoshis.
TotalAmtMsat int64 `protobuf:"varint,6,opt,name=total_amt_msat,json=totalAmtMsat,proto3" json:"total_amt_msat,omitempty"`
// The actual on-chain amount that was sent out to the first hop. This value is
// only different from the total_amt_msat field if this is a custom channel
// payment and the value transported in the HTLC is different from the BTC
// amount in the HTLC. If this value is zero, then this is an old payment that
// didn't have this value yet and can be ignored.
FirstHopAmountMsat int64 `protobuf:"varint,7,opt,name=first_hop_amount_msat,json=firstHopAmountMsat,proto3" json:"first_hop_amount_msat,omitempty"`
// Custom channel data that might be populated in custom channels.
CustomChannelData []byte `protobuf:"bytes,8,opt,name=custom_channel_data,json=customChannelData,proto3" json:"custom_channel_data,omitempty"`
}
func (x *Route) Reset() {
*x = Route{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[106]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Route) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Route) ProtoMessage() {}
func (x *Route) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[106]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Route.ProtoReflect.Descriptor instead.
func (*Route) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{106}
}
func (x *Route) GetTotalTimeLock() uint32 {
if x != nil {
return x.TotalTimeLock
}
return 0
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *Route) GetTotalFees() int64 {
if x != nil {
return x.TotalFees
}
return 0
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *Route) GetTotalAmt() int64 {
if x != nil {
return x.TotalAmt
}
return 0
}
func (x *Route) GetHops() []*Hop {
if x != nil {
return x.Hops
}
return nil
}
func (x *Route) GetTotalFeesMsat() int64 {
if x != nil {
return x.TotalFeesMsat
}
return 0
}
func (x *Route) GetTotalAmtMsat() int64 {
if x != nil {
return x.TotalAmtMsat
}
return 0
}
func (x *Route) GetFirstHopAmountMsat() int64 {
if x != nil {
return x.FirstHopAmountMsat
}
return 0
}
func (x *Route) GetCustomChannelData() []byte {
if x != nil {
return x.CustomChannelData
}
return nil
}
type NodeInfoRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The 33-byte hex-encoded compressed public of the target node
PubKey string `protobuf:"bytes,1,opt,name=pub_key,json=pubKey,proto3" json:"pub_key,omitempty"`
// If true, will include all known channels associated with the node.
IncludeChannels bool `protobuf:"varint,2,opt,name=include_channels,json=includeChannels,proto3" json:"include_channels,omitempty"`
}
func (x *NodeInfoRequest) Reset() {
*x = NodeInfoRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[107]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *NodeInfoRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NodeInfoRequest) ProtoMessage() {}
func (x *NodeInfoRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[107]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NodeInfoRequest.ProtoReflect.Descriptor instead.
func (*NodeInfoRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{107}
}
func (x *NodeInfoRequest) GetPubKey() string {
if x != nil {
return x.PubKey
}
return ""
}
func (x *NodeInfoRequest) GetIncludeChannels() bool {
if x != nil {
return x.IncludeChannels
}
return false
}
type NodeInfo struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// An individual vertex/node within the channel graph. A node is
// connected to other nodes by one or more channel edges emanating from it. As
// the graph is directed, a node will also have an incoming edge attached to
// it for each outgoing edge.
Node *LightningNode `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"`
// The total number of channels for the node.
NumChannels uint32 `protobuf:"varint,2,opt,name=num_channels,json=numChannels,proto3" json:"num_channels,omitempty"`
// The sum of all channels capacity for the node, denominated in satoshis.
TotalCapacity int64 `protobuf:"varint,3,opt,name=total_capacity,json=totalCapacity,proto3" json:"total_capacity,omitempty"`
// A list of all public channels for the node.
Channels []*ChannelEdge `protobuf:"bytes,4,rep,name=channels,proto3" json:"channels,omitempty"`
}
func (x *NodeInfo) Reset() {
*x = NodeInfo{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[108]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *NodeInfo) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NodeInfo) ProtoMessage() {}
func (x *NodeInfo) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[108]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NodeInfo.ProtoReflect.Descriptor instead.
func (*NodeInfo) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{108}
}
func (x *NodeInfo) GetNode() *LightningNode {
if x != nil {
return x.Node
}
return nil
}
func (x *NodeInfo) GetNumChannels() uint32 {
if x != nil {
return x.NumChannels
}
return 0
}
func (x *NodeInfo) GetTotalCapacity() int64 {
if x != nil {
return x.TotalCapacity
}
return 0
}
func (x *NodeInfo) GetChannels() []*ChannelEdge {
if x != nil {
return x.Channels
}
return nil
}
// An individual vertex/node within the channel graph. A node is
// connected to other nodes by one or more channel edges emanating from it. As the
// graph is directed, a node will also have an incoming edge attached to it for
// each outgoing edge.
type LightningNode struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
LastUpdate uint32 `protobuf:"varint,1,opt,name=last_update,json=lastUpdate,proto3" json:"last_update,omitempty"`
PubKey string `protobuf:"bytes,2,opt,name=pub_key,json=pubKey,proto3" json:"pub_key,omitempty"`
Alias string `protobuf:"bytes,3,opt,name=alias,proto3" json:"alias,omitempty"`
Addresses []*NodeAddress `protobuf:"bytes,4,rep,name=addresses,proto3" json:"addresses,omitempty"`
Color string `protobuf:"bytes,5,opt,name=color,proto3" json:"color,omitempty"`
Features map[uint32]*Feature `protobuf:"bytes,6,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
// Custom node announcement tlv records.
CustomRecords map[uint64][]byte `protobuf:"bytes,7,rep,name=custom_records,json=customRecords,proto3" json:"custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *LightningNode) Reset() {
*x = LightningNode{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[109]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *LightningNode) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LightningNode) ProtoMessage() {}
func (x *LightningNode) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[109]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LightningNode.ProtoReflect.Descriptor instead.
func (*LightningNode) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{109}
}
func (x *LightningNode) GetLastUpdate() uint32 {
if x != nil {
return x.LastUpdate
}
return 0
}
func (x *LightningNode) GetPubKey() string {
if x != nil {
return x.PubKey
}
return ""
}
func (x *LightningNode) GetAlias() string {
if x != nil {
return x.Alias
}
return ""
}
func (x *LightningNode) GetAddresses() []*NodeAddress {
if x != nil {
return x.Addresses
}
return nil
}
func (x *LightningNode) GetColor() string {
if x != nil {
return x.Color
}
return ""
}
func (x *LightningNode) GetFeatures() map[uint32]*Feature {
if x != nil {
return x.Features
}
return nil
}
func (x *LightningNode) GetCustomRecords() map[uint64][]byte {
if x != nil {
return x.CustomRecords
}
return nil
}
type NodeAddress struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Network string `protobuf:"bytes,1,opt,name=network,proto3" json:"network,omitempty"`
Addr string `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,omitempty"`
}
func (x *NodeAddress) Reset() {
*x = NodeAddress{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[110]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *NodeAddress) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NodeAddress) ProtoMessage() {}
func (x *NodeAddress) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[110]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NodeAddress.ProtoReflect.Descriptor instead.
func (*NodeAddress) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{110}
}
func (x *NodeAddress) GetNetwork() string {
if x != nil {
return x.Network
}
return ""
}
func (x *NodeAddress) GetAddr() string {
if x != nil {
return x.Addr
}
return ""
}
type RoutingPolicy struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
TimeLockDelta uint32 `protobuf:"varint,1,opt,name=time_lock_delta,json=timeLockDelta,proto3" json:"time_lock_delta,omitempty"`
MinHtlc int64 `protobuf:"varint,2,opt,name=min_htlc,json=minHtlc,proto3" json:"min_htlc,omitempty"`
FeeBaseMsat int64 `protobuf:"varint,3,opt,name=fee_base_msat,json=feeBaseMsat,proto3" json:"fee_base_msat,omitempty"`
FeeRateMilliMsat int64 `protobuf:"varint,4,opt,name=fee_rate_milli_msat,json=feeRateMilliMsat,proto3" json:"fee_rate_milli_msat,omitempty"`
Disabled bool `protobuf:"varint,5,opt,name=disabled,proto3" json:"disabled,omitempty"`
MaxHtlcMsat uint64 `protobuf:"varint,6,opt,name=max_htlc_msat,json=maxHtlcMsat,proto3" json:"max_htlc_msat,omitempty"`
LastUpdate uint32 `protobuf:"varint,7,opt,name=last_update,json=lastUpdate,proto3" json:"last_update,omitempty"`
// Custom channel update tlv records.
CustomRecords map[uint64][]byte `protobuf:"bytes,8,rep,name=custom_records,json=customRecords,proto3" json:"custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
InboundFeeBaseMsat int32 `protobuf:"varint,9,opt,name=inbound_fee_base_msat,json=inboundFeeBaseMsat,proto3" json:"inbound_fee_base_msat,omitempty"`
InboundFeeRateMilliMsat int32 `protobuf:"varint,10,opt,name=inbound_fee_rate_milli_msat,json=inboundFeeRateMilliMsat,proto3" json:"inbound_fee_rate_milli_msat,omitempty"`
}
func (x *RoutingPolicy) Reset() {
*x = RoutingPolicy{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[111]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RoutingPolicy) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RoutingPolicy) ProtoMessage() {}
func (x *RoutingPolicy) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[111]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RoutingPolicy.ProtoReflect.Descriptor instead.
func (*RoutingPolicy) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{111}
}
func (x *RoutingPolicy) GetTimeLockDelta() uint32 {
if x != nil {
return x.TimeLockDelta
}
return 0
}
func (x *RoutingPolicy) GetMinHtlc() int64 {
if x != nil {
return x.MinHtlc
}
return 0
}
func (x *RoutingPolicy) GetFeeBaseMsat() int64 {
if x != nil {
return x.FeeBaseMsat
}
return 0
}
func (x *RoutingPolicy) GetFeeRateMilliMsat() int64 {
if x != nil {
return x.FeeRateMilliMsat
}
return 0
}
func (x *RoutingPolicy) GetDisabled() bool {
if x != nil {
return x.Disabled
}
return false
}
func (x *RoutingPolicy) GetMaxHtlcMsat() uint64 {
if x != nil {
return x.MaxHtlcMsat
}
return 0
}
func (x *RoutingPolicy) GetLastUpdate() uint32 {
if x != nil {
return x.LastUpdate
}
return 0
}
func (x *RoutingPolicy) GetCustomRecords() map[uint64][]byte {
if x != nil {
return x.CustomRecords
}
return nil
}
func (x *RoutingPolicy) GetInboundFeeBaseMsat() int32 {
if x != nil {
return x.InboundFeeBaseMsat
}
return 0
}
func (x *RoutingPolicy) GetInboundFeeRateMilliMsat() int32 {
if x != nil {
return x.InboundFeeRateMilliMsat
}
return 0
}
// A fully authenticated channel along with all its unique attributes.
// Once an authenticated channel announcement has been processed on the network,
// then an instance of ChannelEdgeInfo encapsulating the channels attributes is
// stored. The other portions relevant to routing policy of a channel are stored
// within a ChannelEdgePolicy for each direction of the channel.
type ChannelEdge struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The unique channel ID for the channel. The first 3 bytes are the block
// height, the next 3 the index within the block, and the last 2 bytes are the
// output index for the channel.
ChannelId uint64 `protobuf:"varint,1,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"`
ChanPoint string `protobuf:"bytes,2,opt,name=chan_point,json=chanPoint,proto3" json:"chan_point,omitempty"`
// Deprecated: Marked as deprecated in lightning.proto.
LastUpdate uint32 `protobuf:"varint,3,opt,name=last_update,json=lastUpdate,proto3" json:"last_update,omitempty"`
Node1Pub string `protobuf:"bytes,4,opt,name=node1_pub,json=node1Pub,proto3" json:"node1_pub,omitempty"`
Node2Pub string `protobuf:"bytes,5,opt,name=node2_pub,json=node2Pub,proto3" json:"node2_pub,omitempty"`
Capacity int64 `protobuf:"varint,6,opt,name=capacity,proto3" json:"capacity,omitempty"`
Node1Policy *RoutingPolicy `protobuf:"bytes,7,opt,name=node1_policy,json=node1Policy,proto3" json:"node1_policy,omitempty"`
Node2Policy *RoutingPolicy `protobuf:"bytes,8,opt,name=node2_policy,json=node2Policy,proto3" json:"node2_policy,omitempty"`
// Custom channel announcement tlv records.
CustomRecords map[uint64][]byte `protobuf:"bytes,9,rep,name=custom_records,json=customRecords,proto3" json:"custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *ChannelEdge) Reset() {
*x = ChannelEdge{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[112]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChannelEdge) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelEdge) ProtoMessage() {}
func (x *ChannelEdge) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[112]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelEdge.ProtoReflect.Descriptor instead.
func (*ChannelEdge) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{112}
}
func (x *ChannelEdge) GetChannelId() uint64 {
if x != nil {
return x.ChannelId
}
return 0
}
func (x *ChannelEdge) GetChanPoint() string {
if x != nil {
return x.ChanPoint
}
return ""
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *ChannelEdge) GetLastUpdate() uint32 {
if x != nil {
return x.LastUpdate
}
return 0
}
func (x *ChannelEdge) GetNode1Pub() string {
if x != nil {
return x.Node1Pub
}
return ""
}
func (x *ChannelEdge) GetNode2Pub() string {
if x != nil {
return x.Node2Pub
}
return ""
}
func (x *ChannelEdge) GetCapacity() int64 {
if x != nil {
return x.Capacity
}
return 0
}
func (x *ChannelEdge) GetNode1Policy() *RoutingPolicy {
if x != nil {
return x.Node1Policy
}
return nil
}
func (x *ChannelEdge) GetNode2Policy() *RoutingPolicy {
if x != nil {
return x.Node2Policy
}
return nil
}
func (x *ChannelEdge) GetCustomRecords() map[uint64][]byte {
if x != nil {
return x.CustomRecords
}
return nil
}
type ChannelGraphRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Whether unannounced channels are included in the response or not. If set,
// unannounced channels are included. Unannounced channels are both private
// channels, and public channels that are not yet announced to the network.
IncludeUnannounced bool `protobuf:"varint,1,opt,name=include_unannounced,json=includeUnannounced,proto3" json:"include_unannounced,omitempty"`
}
func (x *ChannelGraphRequest) Reset() {
*x = ChannelGraphRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[113]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChannelGraphRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelGraphRequest) ProtoMessage() {}
func (x *ChannelGraphRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[113]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelGraphRequest.ProtoReflect.Descriptor instead.
func (*ChannelGraphRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{113}
}
func (x *ChannelGraphRequest) GetIncludeUnannounced() bool {
if x != nil {
return x.IncludeUnannounced
}
return false
}
// Returns a new instance of the directed channel graph.
type ChannelGraph struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The list of `LightningNode`s in this channel graph
Nodes []*LightningNode `protobuf:"bytes,1,rep,name=nodes,proto3" json:"nodes,omitempty"`
// The list of `ChannelEdge`s in this channel graph
Edges []*ChannelEdge `protobuf:"bytes,2,rep,name=edges,proto3" json:"edges,omitempty"`
}
func (x *ChannelGraph) Reset() {
*x = ChannelGraph{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[114]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChannelGraph) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelGraph) ProtoMessage() {}
func (x *ChannelGraph) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[114]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelGraph.ProtoReflect.Descriptor instead.
func (*ChannelGraph) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{114}
}
func (x *ChannelGraph) GetNodes() []*LightningNode {
if x != nil {
return x.Nodes
}
return nil
}
func (x *ChannelGraph) GetEdges() []*ChannelEdge {
if x != nil {
return x.Edges
}
return nil
}
type NodeMetricsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The requested node metrics.
Types []NodeMetricType `protobuf:"varint,1,rep,packed,name=types,proto3,enum=lnrpc.NodeMetricType" json:"types,omitempty"`
}
func (x *NodeMetricsRequest) Reset() {
*x = NodeMetricsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[115]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *NodeMetricsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NodeMetricsRequest) ProtoMessage() {}
func (x *NodeMetricsRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[115]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NodeMetricsRequest.ProtoReflect.Descriptor instead.
func (*NodeMetricsRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{115}
}
func (x *NodeMetricsRequest) GetTypes() []NodeMetricType {
if x != nil {
return x.Types
}
return nil
}
type NodeMetricsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Betweenness centrality is the sum of the ratio of shortest paths that pass
// through the node for each pair of nodes in the graph (not counting paths
// starting or ending at this node).
// Map of node pubkey to betweenness centrality of the node. Normalized
// values are in the [0,1] closed interval.
BetweennessCentrality map[string]*FloatMetric `protobuf:"bytes,1,rep,name=betweenness_centrality,json=betweennessCentrality,proto3" json:"betweenness_centrality,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *NodeMetricsResponse) Reset() {
*x = NodeMetricsResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[116]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *NodeMetricsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NodeMetricsResponse) ProtoMessage() {}
func (x *NodeMetricsResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[116]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NodeMetricsResponse.ProtoReflect.Descriptor instead.
func (*NodeMetricsResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{116}
}
func (x *NodeMetricsResponse) GetBetweennessCentrality() map[string]*FloatMetric {
if x != nil {
return x.BetweennessCentrality
}
return nil
}
type FloatMetric struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Arbitrary float value.
Value float64 `protobuf:"fixed64,1,opt,name=value,proto3" json:"value,omitempty"`
// The value normalized to [0,1] or [-1,1].
NormalizedValue float64 `protobuf:"fixed64,2,opt,name=normalized_value,json=normalizedValue,proto3" json:"normalized_value,omitempty"`
}
func (x *FloatMetric) Reset() {
*x = FloatMetric{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[117]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FloatMetric) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FloatMetric) ProtoMessage() {}
func (x *FloatMetric) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[117]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FloatMetric.ProtoReflect.Descriptor instead.
func (*FloatMetric) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{117}
}
func (x *FloatMetric) GetValue() float64 {
if x != nil {
return x.Value
}
return 0
}
func (x *FloatMetric) GetNormalizedValue() float64 {
if x != nil {
return x.NormalizedValue
}
return 0
}
type ChanInfoRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The unique channel ID for the channel. The first 3 bytes are the block
// height, the next 3 the index within the block, and the last 2 bytes are the
// output index for the channel.
ChanId uint64 `protobuf:"varint,1,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"`
// The channel point of the channel in format funding_txid:output_index. If
// chan_id is specified, this field is ignored.
ChanPoint string `protobuf:"bytes,2,opt,name=chan_point,json=chanPoint,proto3" json:"chan_point,omitempty"`
}
func (x *ChanInfoRequest) Reset() {
*x = ChanInfoRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[118]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChanInfoRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChanInfoRequest) ProtoMessage() {}
func (x *ChanInfoRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[118]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChanInfoRequest.ProtoReflect.Descriptor instead.
func (*ChanInfoRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{118}
}
func (x *ChanInfoRequest) GetChanId() uint64 {
if x != nil {
return x.ChanId
}
return 0
}
func (x *ChanInfoRequest) GetChanPoint() string {
if x != nil {
return x.ChanPoint
}
return ""
}
type NetworkInfoRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *NetworkInfoRequest) Reset() {
*x = NetworkInfoRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[119]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *NetworkInfoRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NetworkInfoRequest) ProtoMessage() {}
func (x *NetworkInfoRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[119]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NetworkInfoRequest.ProtoReflect.Descriptor instead.
func (*NetworkInfoRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{119}
}
type NetworkInfo struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
GraphDiameter uint32 `protobuf:"varint,1,opt,name=graph_diameter,json=graphDiameter,proto3" json:"graph_diameter,omitempty"`
AvgOutDegree float64 `protobuf:"fixed64,2,opt,name=avg_out_degree,json=avgOutDegree,proto3" json:"avg_out_degree,omitempty"`
MaxOutDegree uint32 `protobuf:"varint,3,opt,name=max_out_degree,json=maxOutDegree,proto3" json:"max_out_degree,omitempty"`
NumNodes uint32 `protobuf:"varint,4,opt,name=num_nodes,json=numNodes,proto3" json:"num_nodes,omitempty"`
NumChannels uint32 `protobuf:"varint,5,opt,name=num_channels,json=numChannels,proto3" json:"num_channels,omitempty"`
TotalNetworkCapacity int64 `protobuf:"varint,6,opt,name=total_network_capacity,json=totalNetworkCapacity,proto3" json:"total_network_capacity,omitempty"`
AvgChannelSize float64 `protobuf:"fixed64,7,opt,name=avg_channel_size,json=avgChannelSize,proto3" json:"avg_channel_size,omitempty"`
MinChannelSize int64 `protobuf:"varint,8,opt,name=min_channel_size,json=minChannelSize,proto3" json:"min_channel_size,omitempty"`
MaxChannelSize int64 `protobuf:"varint,9,opt,name=max_channel_size,json=maxChannelSize,proto3" json:"max_channel_size,omitempty"`
MedianChannelSizeSat int64 `protobuf:"varint,10,opt,name=median_channel_size_sat,json=medianChannelSizeSat,proto3" json:"median_channel_size_sat,omitempty"`
// The number of edges marked as zombies.
NumZombieChans uint64 `protobuf:"varint,11,opt,name=num_zombie_chans,json=numZombieChans,proto3" json:"num_zombie_chans,omitempty"`
}
func (x *NetworkInfo) Reset() {
*x = NetworkInfo{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[120]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *NetworkInfo) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NetworkInfo) ProtoMessage() {}
func (x *NetworkInfo) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[120]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NetworkInfo.ProtoReflect.Descriptor instead.
func (*NetworkInfo) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{120}
}
func (x *NetworkInfo) GetGraphDiameter() uint32 {
if x != nil {
return x.GraphDiameter
}
return 0
}
func (x *NetworkInfo) GetAvgOutDegree() float64 {
if x != nil {
return x.AvgOutDegree
}
return 0
}
func (x *NetworkInfo) GetMaxOutDegree() uint32 {
if x != nil {
return x.MaxOutDegree
}
return 0
}
func (x *NetworkInfo) GetNumNodes() uint32 {
if x != nil {
return x.NumNodes
}
return 0
}
func (x *NetworkInfo) GetNumChannels() uint32 {
if x != nil {
return x.NumChannels
}
return 0
}
func (x *NetworkInfo) GetTotalNetworkCapacity() int64 {
if x != nil {
return x.TotalNetworkCapacity
}
return 0
}
func (x *NetworkInfo) GetAvgChannelSize() float64 {
if x != nil {
return x.AvgChannelSize
}
return 0
}
func (x *NetworkInfo) GetMinChannelSize() int64 {
if x != nil {
return x.MinChannelSize
}
return 0
}
func (x *NetworkInfo) GetMaxChannelSize() int64 {
if x != nil {
return x.MaxChannelSize
}
return 0
}
func (x *NetworkInfo) GetMedianChannelSizeSat() int64 {
if x != nil {
return x.MedianChannelSizeSat
}
return 0
}
func (x *NetworkInfo) GetNumZombieChans() uint64 {
if x != nil {
return x.NumZombieChans
}
return 0
}
type StopRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *StopRequest) Reset() {
*x = StopRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[121]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *StopRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StopRequest) ProtoMessage() {}
func (x *StopRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[121]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StopRequest.ProtoReflect.Descriptor instead.
func (*StopRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{121}
}
type StopResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The status of the stop operation.
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
}
func (x *StopResponse) Reset() {
*x = StopResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[122]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *StopResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StopResponse) ProtoMessage() {}
func (x *StopResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[122]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StopResponse.ProtoReflect.Descriptor instead.
func (*StopResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{122}
}
func (x *StopResponse) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
type GraphTopologySubscription struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *GraphTopologySubscription) Reset() {
*x = GraphTopologySubscription{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[123]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GraphTopologySubscription) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GraphTopologySubscription) ProtoMessage() {}
func (x *GraphTopologySubscription) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[123]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GraphTopologySubscription.ProtoReflect.Descriptor instead.
func (*GraphTopologySubscription) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{123}
}
type GraphTopologyUpdate struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
NodeUpdates []*NodeUpdate `protobuf:"bytes,1,rep,name=node_updates,json=nodeUpdates,proto3" json:"node_updates,omitempty"`
ChannelUpdates []*ChannelEdgeUpdate `protobuf:"bytes,2,rep,name=channel_updates,json=channelUpdates,proto3" json:"channel_updates,omitempty"`
ClosedChans []*ClosedChannelUpdate `protobuf:"bytes,3,rep,name=closed_chans,json=closedChans,proto3" json:"closed_chans,omitempty"`
}
func (x *GraphTopologyUpdate) Reset() {
*x = GraphTopologyUpdate{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[124]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GraphTopologyUpdate) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GraphTopologyUpdate) ProtoMessage() {}
func (x *GraphTopologyUpdate) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[124]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GraphTopologyUpdate.ProtoReflect.Descriptor instead.
func (*GraphTopologyUpdate) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{124}
}
func (x *GraphTopologyUpdate) GetNodeUpdates() []*NodeUpdate {
if x != nil {
return x.NodeUpdates
}
return nil
}
func (x *GraphTopologyUpdate) GetChannelUpdates() []*ChannelEdgeUpdate {
if x != nil {
return x.ChannelUpdates
}
return nil
}
func (x *GraphTopologyUpdate) GetClosedChans() []*ClosedChannelUpdate {
if x != nil {
return x.ClosedChans
}
return nil
}
type NodeUpdate struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Deprecated, use node_addresses.
//
// Deprecated: Marked as deprecated in lightning.proto.
Addresses []string `protobuf:"bytes,1,rep,name=addresses,proto3" json:"addresses,omitempty"`
IdentityKey string `protobuf:"bytes,2,opt,name=identity_key,json=identityKey,proto3" json:"identity_key,omitempty"`
// Deprecated, use features.
//
// Deprecated: Marked as deprecated in lightning.proto.
GlobalFeatures []byte `protobuf:"bytes,3,opt,name=global_features,json=globalFeatures,proto3" json:"global_features,omitempty"`
Alias string `protobuf:"bytes,4,opt,name=alias,proto3" json:"alias,omitempty"`
Color string `protobuf:"bytes,5,opt,name=color,proto3" json:"color,omitempty"`
NodeAddresses []*NodeAddress `protobuf:"bytes,7,rep,name=node_addresses,json=nodeAddresses,proto3" json:"node_addresses,omitempty"`
// Features that the node has advertised in the init message, node
// announcements and invoices.
Features map[uint32]*Feature `protobuf:"bytes,6,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *NodeUpdate) Reset() {
*x = NodeUpdate{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[125]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *NodeUpdate) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NodeUpdate) ProtoMessage() {}
func (x *NodeUpdate) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[125]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NodeUpdate.ProtoReflect.Descriptor instead.
func (*NodeUpdate) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{125}
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *NodeUpdate) GetAddresses() []string {
if x != nil {
return x.Addresses
}
return nil
}
func (x *NodeUpdate) GetIdentityKey() string {
if x != nil {
return x.IdentityKey
}
return ""
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *NodeUpdate) GetGlobalFeatures() []byte {
if x != nil {
return x.GlobalFeatures
}
return nil
}
func (x *NodeUpdate) GetAlias() string {
if x != nil {
return x.Alias
}
return ""
}
func (x *NodeUpdate) GetColor() string {
if x != nil {
return x.Color
}
return ""
}
func (x *NodeUpdate) GetNodeAddresses() []*NodeAddress {
if x != nil {
return x.NodeAddresses
}
return nil
}
func (x *NodeUpdate) GetFeatures() map[uint32]*Feature {
if x != nil {
return x.Features
}
return nil
}
type ChannelEdgeUpdate struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The unique channel ID for the channel. The first 3 bytes are the block
// height, the next 3 the index within the block, and the last 2 bytes are the
// output index for the channel.
ChanId uint64 `protobuf:"varint,1,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"`
ChanPoint *ChannelPoint `protobuf:"bytes,2,opt,name=chan_point,json=chanPoint,proto3" json:"chan_point,omitempty"`
Capacity int64 `protobuf:"varint,3,opt,name=capacity,proto3" json:"capacity,omitempty"`
RoutingPolicy *RoutingPolicy `protobuf:"bytes,4,opt,name=routing_policy,json=routingPolicy,proto3" json:"routing_policy,omitempty"`
AdvertisingNode string `protobuf:"bytes,5,opt,name=advertising_node,json=advertisingNode,proto3" json:"advertising_node,omitempty"`
ConnectingNode string `protobuf:"bytes,6,opt,name=connecting_node,json=connectingNode,proto3" json:"connecting_node,omitempty"`
}
func (x *ChannelEdgeUpdate) Reset() {
*x = ChannelEdgeUpdate{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[126]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChannelEdgeUpdate) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelEdgeUpdate) ProtoMessage() {}
func (x *ChannelEdgeUpdate) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[126]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelEdgeUpdate.ProtoReflect.Descriptor instead.
func (*ChannelEdgeUpdate) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{126}
}
func (x *ChannelEdgeUpdate) GetChanId() uint64 {
if x != nil {
return x.ChanId
}
return 0
}
func (x *ChannelEdgeUpdate) GetChanPoint() *ChannelPoint {
if x != nil {
return x.ChanPoint
}
return nil
}
func (x *ChannelEdgeUpdate) GetCapacity() int64 {
if x != nil {
return x.Capacity
}
return 0
}
func (x *ChannelEdgeUpdate) GetRoutingPolicy() *RoutingPolicy {
if x != nil {
return x.RoutingPolicy
}
return nil
}
func (x *ChannelEdgeUpdate) GetAdvertisingNode() string {
if x != nil {
return x.AdvertisingNode
}
return ""
}
func (x *ChannelEdgeUpdate) GetConnectingNode() string {
if x != nil {
return x.ConnectingNode
}
return ""
}
type ClosedChannelUpdate struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The unique channel ID for the channel. The first 3 bytes are the block
// height, the next 3 the index within the block, and the last 2 bytes are the
// output index for the channel.
ChanId uint64 `protobuf:"varint,1,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"`
Capacity int64 `protobuf:"varint,2,opt,name=capacity,proto3" json:"capacity,omitempty"`
ClosedHeight uint32 `protobuf:"varint,3,opt,name=closed_height,json=closedHeight,proto3" json:"closed_height,omitempty"`
ChanPoint *ChannelPoint `protobuf:"bytes,4,opt,name=chan_point,json=chanPoint,proto3" json:"chan_point,omitempty"`
}
func (x *ClosedChannelUpdate) Reset() {
*x = ClosedChannelUpdate{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[127]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ClosedChannelUpdate) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ClosedChannelUpdate) ProtoMessage() {}
func (x *ClosedChannelUpdate) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[127]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ClosedChannelUpdate.ProtoReflect.Descriptor instead.
func (*ClosedChannelUpdate) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{127}
}
func (x *ClosedChannelUpdate) GetChanId() uint64 {
if x != nil {
return x.ChanId
}
return 0
}
func (x *ClosedChannelUpdate) GetCapacity() int64 {
if x != nil {
return x.Capacity
}
return 0
}
func (x *ClosedChannelUpdate) GetClosedHeight() uint32 {
if x != nil {
return x.ClosedHeight
}
return 0
}
func (x *ClosedChannelUpdate) GetChanPoint() *ChannelPoint {
if x != nil {
return x.ChanPoint
}
return nil
}
type HopHint struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The public key of the node at the start of the channel.
NodeId string `protobuf:"bytes,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"`
// The unique identifier of the channel.
ChanId uint64 `protobuf:"varint,2,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"`
// The base fee of the channel denominated in millisatoshis.
FeeBaseMsat uint32 `protobuf:"varint,3,opt,name=fee_base_msat,json=feeBaseMsat,proto3" json:"fee_base_msat,omitempty"`
// The fee rate of the channel for sending one satoshi across it denominated in
// millionths of a satoshi.
FeeProportionalMillionths uint32 `protobuf:"varint,4,opt,name=fee_proportional_millionths,json=feeProportionalMillionths,proto3" json:"fee_proportional_millionths,omitempty"`
// The time-lock delta of the channel.
CltvExpiryDelta uint32 `protobuf:"varint,5,opt,name=cltv_expiry_delta,json=cltvExpiryDelta,proto3" json:"cltv_expiry_delta,omitempty"`
}
func (x *HopHint) Reset() {
*x = HopHint{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[128]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HopHint) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HopHint) ProtoMessage() {}
func (x *HopHint) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[128]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HopHint.ProtoReflect.Descriptor instead.
func (*HopHint) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{128}
}
func (x *HopHint) GetNodeId() string {
if x != nil {
return x.NodeId
}
return ""
}
func (x *HopHint) GetChanId() uint64 {
if x != nil {
return x.ChanId
}
return 0
}
func (x *HopHint) GetFeeBaseMsat() uint32 {
if x != nil {
return x.FeeBaseMsat
}
return 0
}
func (x *HopHint) GetFeeProportionalMillionths() uint32 {
if x != nil {
return x.FeeProportionalMillionths
}
return 0
}
func (x *HopHint) GetCltvExpiryDelta() uint32 {
if x != nil {
return x.CltvExpiryDelta
}
return 0
}
type SetID struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
SetId []byte `protobuf:"bytes,1,opt,name=set_id,json=setId,proto3" json:"set_id,omitempty"`
}
func (x *SetID) Reset() {
*x = SetID{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[129]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SetID) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SetID) ProtoMessage() {}
func (x *SetID) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[129]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SetID.ProtoReflect.Descriptor instead.
func (*SetID) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{129}
}
func (x *SetID) GetSetId() []byte {
if x != nil {
return x.SetId
}
return nil
}
type RouteHint struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A list of hop hints that when chained together can assist in reaching a
// specific destination.
HopHints []*HopHint `protobuf:"bytes,1,rep,name=hop_hints,json=hopHints,proto3" json:"hop_hints,omitempty"`
}
func (x *RouteHint) Reset() {
*x = RouteHint{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[130]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RouteHint) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RouteHint) ProtoMessage() {}
func (x *RouteHint) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[130]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RouteHint.ProtoReflect.Descriptor instead.
func (*RouteHint) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{130}
}
func (x *RouteHint) GetHopHints() []*HopHint {
if x != nil {
return x.HopHints
}
return nil
}
type BlindedPaymentPath struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The blinded path to send the payment to.
BlindedPath *BlindedPath `protobuf:"bytes,1,opt,name=blinded_path,json=blindedPath,proto3" json:"blinded_path,omitempty"`
// The base fee for the blinded path provided, expressed in msat.
BaseFeeMsat uint64 `protobuf:"varint,2,opt,name=base_fee_msat,json=baseFeeMsat,proto3" json:"base_fee_msat,omitempty"`
// The proportional fee for the blinded path provided, expressed in parts
// per million.
ProportionalFeeRate uint32 `protobuf:"varint,3,opt,name=proportional_fee_rate,json=proportionalFeeRate,proto3" json:"proportional_fee_rate,omitempty"`
// The total CLTV delta for the blinded path provided, including the
// final CLTV delta for the receiving node.
TotalCltvDelta uint32 `protobuf:"varint,4,opt,name=total_cltv_delta,json=totalCltvDelta,proto3" json:"total_cltv_delta,omitempty"`
// The minimum hltc size that may be sent over the blinded path, expressed
// in msat.
HtlcMinMsat uint64 `protobuf:"varint,5,opt,name=htlc_min_msat,json=htlcMinMsat,proto3" json:"htlc_min_msat,omitempty"`
// The maximum htlc size that may be sent over the blinded path, expressed
// in msat.
HtlcMaxMsat uint64 `protobuf:"varint,6,opt,name=htlc_max_msat,json=htlcMaxMsat,proto3" json:"htlc_max_msat,omitempty"`
// The feature bits for the route.
Features []FeatureBit `protobuf:"varint,7,rep,packed,name=features,proto3,enum=lnrpc.FeatureBit" json:"features,omitempty"`
}
func (x *BlindedPaymentPath) Reset() {
*x = BlindedPaymentPath{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[131]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *BlindedPaymentPath) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BlindedPaymentPath) ProtoMessage() {}
func (x *BlindedPaymentPath) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[131]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BlindedPaymentPath.ProtoReflect.Descriptor instead.
func (*BlindedPaymentPath) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{131}
}
func (x *BlindedPaymentPath) GetBlindedPath() *BlindedPath {
if x != nil {
return x.BlindedPath
}
return nil
}
func (x *BlindedPaymentPath) GetBaseFeeMsat() uint64 {
if x != nil {
return x.BaseFeeMsat
}
return 0
}
func (x *BlindedPaymentPath) GetProportionalFeeRate() uint32 {
if x != nil {
return x.ProportionalFeeRate
}
return 0
}
func (x *BlindedPaymentPath) GetTotalCltvDelta() uint32 {
if x != nil {
return x.TotalCltvDelta
}
return 0
}
func (x *BlindedPaymentPath) GetHtlcMinMsat() uint64 {
if x != nil {
return x.HtlcMinMsat
}
return 0
}
func (x *BlindedPaymentPath) GetHtlcMaxMsat() uint64 {
if x != nil {
return x.HtlcMaxMsat
}
return 0
}
func (x *BlindedPaymentPath) GetFeatures() []FeatureBit {
if x != nil {
return x.Features
}
return nil
}
type BlindedPath struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The unblinded pubkey of the introduction node for the route.
IntroductionNode []byte `protobuf:"bytes,1,opt,name=introduction_node,json=introductionNode,proto3" json:"introduction_node,omitempty"`
// The ephemeral pubkey used by nodes in the blinded route.
BlindingPoint []byte `protobuf:"bytes,2,opt,name=blinding_point,json=blindingPoint,proto3" json:"blinding_point,omitempty"`
// A set of blinded node keys and data blobs for the blinded portion of the
// route. Note that the first hop is expected to be the introduction node,
// so the route is always expected to have at least one hop.
BlindedHops []*BlindedHop `protobuf:"bytes,3,rep,name=blinded_hops,json=blindedHops,proto3" json:"blinded_hops,omitempty"`
}
func (x *BlindedPath) Reset() {
*x = BlindedPath{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[132]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *BlindedPath) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BlindedPath) ProtoMessage() {}
func (x *BlindedPath) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[132]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BlindedPath.ProtoReflect.Descriptor instead.
func (*BlindedPath) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{132}
}
func (x *BlindedPath) GetIntroductionNode() []byte {
if x != nil {
return x.IntroductionNode
}
return nil
}
func (x *BlindedPath) GetBlindingPoint() []byte {
if x != nil {
return x.BlindingPoint
}
return nil
}
func (x *BlindedPath) GetBlindedHops() []*BlindedHop {
if x != nil {
return x.BlindedHops
}
return nil
}
type BlindedHop struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The blinded public key of the node.
BlindedNode []byte `protobuf:"bytes,1,opt,name=blinded_node,json=blindedNode,proto3" json:"blinded_node,omitempty"`
// An encrypted blob of data provided to the blinded node.
EncryptedData []byte `protobuf:"bytes,2,opt,name=encrypted_data,json=encryptedData,proto3" json:"encrypted_data,omitempty"`
}
func (x *BlindedHop) Reset() {
*x = BlindedHop{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[133]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *BlindedHop) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BlindedHop) ProtoMessage() {}
func (x *BlindedHop) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[133]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BlindedHop.ProtoReflect.Descriptor instead.
func (*BlindedHop) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{133}
}
func (x *BlindedHop) GetBlindedNode() []byte {
if x != nil {
return x.BlindedNode
}
return nil
}
func (x *BlindedHop) GetEncryptedData() []byte {
if x != nil {
return x.EncryptedData
}
return nil
}
type AMPInvoiceState struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The state the HTLCs associated with this setID are in.
State InvoiceHTLCState `protobuf:"varint,1,opt,name=state,proto3,enum=lnrpc.InvoiceHTLCState" json:"state,omitempty"`
// The settle index of this HTLC set, if the invoice state is settled.
SettleIndex uint64 `protobuf:"varint,2,opt,name=settle_index,json=settleIndex,proto3" json:"settle_index,omitempty"`
// The time this HTLC set was settled expressed in unix epoch.
SettleTime int64 `protobuf:"varint,3,opt,name=settle_time,json=settleTime,proto3" json:"settle_time,omitempty"`
// The total amount paid for the sub-invoice expressed in milli satoshis.
AmtPaidMsat int64 `protobuf:"varint,5,opt,name=amt_paid_msat,json=amtPaidMsat,proto3" json:"amt_paid_msat,omitempty"`
}
func (x *AMPInvoiceState) Reset() {
*x = AMPInvoiceState{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[134]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AMPInvoiceState) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AMPInvoiceState) ProtoMessage() {}
func (x *AMPInvoiceState) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[134]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AMPInvoiceState.ProtoReflect.Descriptor instead.
func (*AMPInvoiceState) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{134}
}
func (x *AMPInvoiceState) GetState() InvoiceHTLCState {
if x != nil {
return x.State
}
return InvoiceHTLCState_ACCEPTED
}
func (x *AMPInvoiceState) GetSettleIndex() uint64 {
if x != nil {
return x.SettleIndex
}
return 0
}
func (x *AMPInvoiceState) GetSettleTime() int64 {
if x != nil {
return x.SettleTime
}
return 0
}
func (x *AMPInvoiceState) GetAmtPaidMsat() int64 {
if x != nil {
return x.AmtPaidMsat
}
return 0
}
type Invoice struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// An optional memo to attach along with the invoice. Used for record keeping
// purposes for the invoice's creator, and will also be set in the description
// field of the encoded payment request if the description_hash field is not
// being used.
Memo string `protobuf:"bytes,1,opt,name=memo,proto3" json:"memo,omitempty"`
// The hex-encoded preimage (32 byte) which will allow settling an incoming
// HTLC payable to this preimage. When using REST, this field must be encoded
// as base64.
RPreimage []byte `protobuf:"bytes,3,opt,name=r_preimage,json=rPreimage,proto3" json:"r_preimage,omitempty"`
// The hash of the preimage. When using REST, this field must be encoded as
// base64.
// Note: Output only, don't specify for creating an invoice.
RHash []byte `protobuf:"bytes,4,opt,name=r_hash,json=rHash,proto3" json:"r_hash,omitempty"`
// The value of this invoice in satoshis
//
// The fields value and value_msat are mutually exclusive.
Value int64 `protobuf:"varint,5,opt,name=value,proto3" json:"value,omitempty"`
// The value of this invoice in millisatoshis
//
// The fields value and value_msat are mutually exclusive.
ValueMsat int64 `protobuf:"varint,23,opt,name=value_msat,json=valueMsat,proto3" json:"value_msat,omitempty"`
// Whether this invoice has been fulfilled.
//
// The field is deprecated. Use the state field instead (compare to SETTLED).
//
// Deprecated: Marked as deprecated in lightning.proto.
Settled bool `protobuf:"varint,6,opt,name=settled,proto3" json:"settled,omitempty"`
// When this invoice was created.
// Measured in seconds since the unix epoch.
// Note: Output only, don't specify for creating an invoice.
CreationDate int64 `protobuf:"varint,7,opt,name=creation_date,json=creationDate,proto3" json:"creation_date,omitempty"`
// When this invoice was settled.
// Measured in seconds since the unix epoch.
// Note: Output only, don't specify for creating an invoice.
SettleDate int64 `protobuf:"varint,8,opt,name=settle_date,json=settleDate,proto3" json:"settle_date,omitempty"`
// A bare-bones invoice for a payment within the Lightning Network. With the
// details of the invoice, the sender has all the data necessary to send a
// payment to the recipient.
// Note: Output only, don't specify for creating an invoice.
PaymentRequest string `protobuf:"bytes,9,opt,name=payment_request,json=paymentRequest,proto3" json:"payment_request,omitempty"`
// Hash (SHA-256) of a description of the payment. Used if the description of
// payment (memo) is too long to naturally fit within the description field
// of an encoded payment request. When using REST, this field must be encoded
// as base64.
DescriptionHash []byte `protobuf:"bytes,10,opt,name=description_hash,json=descriptionHash,proto3" json:"description_hash,omitempty"`
// Payment request expiry time in seconds. Default is 86400 (24 hours).
Expiry int64 `protobuf:"varint,11,opt,name=expiry,proto3" json:"expiry,omitempty"`
// Fallback on-chain address.
FallbackAddr string `protobuf:"bytes,12,opt,name=fallback_addr,json=fallbackAddr,proto3" json:"fallback_addr,omitempty"`
// Delta to use for the time-lock of the CLTV extended to the final hop.
CltvExpiry uint64 `protobuf:"varint,13,opt,name=cltv_expiry,json=cltvExpiry,proto3" json:"cltv_expiry,omitempty"`
// Route hints that can each be individually used to assist in reaching the
// invoice's destination.
RouteHints []*RouteHint `protobuf:"bytes,14,rep,name=route_hints,json=routeHints,proto3" json:"route_hints,omitempty"`
// Whether this invoice should include routing hints for private channels.
// Note: When enabled, if value and value_msat are zero, a large number of
// hints with these channels can be included, which might not be desirable.
Private bool `protobuf:"varint,15,opt,name=private,proto3" json:"private,omitempty"`
// The "add" index of this invoice. Each newly created invoice will increment
// this index making it monotonically increasing. Callers to the
// SubscribeInvoices call can use this to instantly get notified of all added
// invoices with an add_index greater than this one.
// Note: Output only, don't specify for creating an invoice.
AddIndex uint64 `protobuf:"varint,16,opt,name=add_index,json=addIndex,proto3" json:"add_index,omitempty"`
// The "settle" index of this invoice. Each newly settled invoice will
// increment this index making it monotonically increasing. Callers to the
// SubscribeInvoices call can use this to instantly get notified of all
// settled invoices with an settle_index greater than this one.
// Note: Output only, don't specify for creating an invoice.
SettleIndex uint64 `protobuf:"varint,17,opt,name=settle_index,json=settleIndex,proto3" json:"settle_index,omitempty"`
// Deprecated, use amt_paid_sat or amt_paid_msat.
//
// Deprecated: Marked as deprecated in lightning.proto.
AmtPaid int64 `protobuf:"varint,18,opt,name=amt_paid,json=amtPaid,proto3" json:"amt_paid,omitempty"`
// The amount that was accepted for this invoice, in satoshis. This will ONLY
// be set if this invoice has been settled or accepted. We provide this field
// as if the invoice was created with a zero value, then we need to record what
// amount was ultimately accepted. Additionally, it's possible that the sender
// paid MORE that was specified in the original invoice. So we'll record that
// here as well.
// Note: Output only, don't specify for creating an invoice.
AmtPaidSat int64 `protobuf:"varint,19,opt,name=amt_paid_sat,json=amtPaidSat,proto3" json:"amt_paid_sat,omitempty"`
// The amount that was accepted for this invoice, in millisatoshis. This will
// ONLY be set if this invoice has been settled or accepted. We provide this
// field as if the invoice was created with a zero value, then we need to
// record what amount was ultimately accepted. Additionally, it's possible that
// the sender paid MORE that was specified in the original invoice. So we'll
// record that here as well.
// Note: Output only, don't specify for creating an invoice.
AmtPaidMsat int64 `protobuf:"varint,20,opt,name=amt_paid_msat,json=amtPaidMsat,proto3" json:"amt_paid_msat,omitempty"`
// The state the invoice is in.
// Note: Output only, don't specify for creating an invoice.
State Invoice_InvoiceState `protobuf:"varint,21,opt,name=state,proto3,enum=lnrpc.Invoice_InvoiceState" json:"state,omitempty"`
// List of HTLCs paying to this invoice [EXPERIMENTAL].
// Note: Output only, don't specify for creating an invoice.
Htlcs []*InvoiceHTLC `protobuf:"bytes,22,rep,name=htlcs,proto3" json:"htlcs,omitempty"`
// List of features advertised on the invoice.
// Note: Output only, don't specify for creating an invoice.
Features map[uint32]*Feature `protobuf:"bytes,24,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
// Indicates if this invoice was a spontaneous payment that arrived via keysend
// [EXPERIMENTAL].
// Note: Output only, don't specify for creating an invoice.
IsKeysend bool `protobuf:"varint,25,opt,name=is_keysend,json=isKeysend,proto3" json:"is_keysend,omitempty"`
// The payment address of this invoice. This is also called payment secret in
// specifications (e.g. BOLT 11). This value will be used in MPP payments, and
// also for newer invoices that always require the MPP payload for added
// end-to-end security.
// Note: Output only, don't specify for creating an invoice.
PaymentAddr []byte `protobuf:"bytes,26,opt,name=payment_addr,json=paymentAddr,proto3" json:"payment_addr,omitempty"`
// Signals whether or not this is an AMP invoice.
IsAmp bool `protobuf:"varint,27,opt,name=is_amp,json=isAmp,proto3" json:"is_amp,omitempty"`
// [EXPERIMENTAL]:
//
// Maps a 32-byte hex-encoded set ID to the sub-invoice AMP state for the
// given set ID. This field is always populated for AMP invoices, and can be
// used along side LookupInvoice to obtain the HTLC information related to a
// given sub-invoice.
// Note: Output only, don't specify for creating an invoice.
AmpInvoiceState map[string]*AMPInvoiceState `protobuf:"bytes,28,rep,name=amp_invoice_state,json=ampInvoiceState,proto3" json:"amp_invoice_state,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
// Signals that the invoice should include blinded paths to hide the true
// identity of the recipient.
IsBlinded bool `protobuf:"varint,29,opt,name=is_blinded,json=isBlinded,proto3" json:"is_blinded,omitempty"`
// Config values to use when creating blinded paths for this invoice. These
// can be used to override the defaults config values provided in by the
// global config. This field is only used if is_blinded is true.
BlindedPathConfig *BlindedPathConfig `protobuf:"bytes,30,opt,name=blinded_path_config,json=blindedPathConfig,proto3" json:"blinded_path_config,omitempty"`
}
func (x *Invoice) Reset() {
*x = Invoice{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[135]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Invoice) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Invoice) ProtoMessage() {}
func (x *Invoice) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[135]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Invoice.ProtoReflect.Descriptor instead.
func (*Invoice) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{135}
}
func (x *Invoice) GetMemo() string {
if x != nil {
return x.Memo
}
return ""
}
func (x *Invoice) GetRPreimage() []byte {
if x != nil {
return x.RPreimage
}
return nil
}
func (x *Invoice) GetRHash() []byte {
if x != nil {
return x.RHash
}
return nil
}
func (x *Invoice) GetValue() int64 {
if x != nil {
return x.Value
}
return 0
}
func (x *Invoice) GetValueMsat() int64 {
if x != nil {
return x.ValueMsat
}
return 0
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *Invoice) GetSettled() bool {
if x != nil {
return x.Settled
}
return false
}
func (x *Invoice) GetCreationDate() int64 {
if x != nil {
return x.CreationDate
}
return 0
}
func (x *Invoice) GetSettleDate() int64 {
if x != nil {
return x.SettleDate
}
return 0
}
func (x *Invoice) GetPaymentRequest() string {
if x != nil {
return x.PaymentRequest
}
return ""
}
func (x *Invoice) GetDescriptionHash() []byte {
if x != nil {
return x.DescriptionHash
}
return nil
}
func (x *Invoice) GetExpiry() int64 {
if x != nil {
return x.Expiry
}
return 0
}
func (x *Invoice) GetFallbackAddr() string {
if x != nil {
return x.FallbackAddr
}
return ""
}
func (x *Invoice) GetCltvExpiry() uint64 {
if x != nil {
return x.CltvExpiry
}
return 0
}
func (x *Invoice) GetRouteHints() []*RouteHint {
if x != nil {
return x.RouteHints
}
return nil
}
func (x *Invoice) GetPrivate() bool {
if x != nil {
return x.Private
}
return false
}
func (x *Invoice) GetAddIndex() uint64 {
if x != nil {
return x.AddIndex
}
return 0
}
func (x *Invoice) GetSettleIndex() uint64 {
if x != nil {
return x.SettleIndex
}
return 0
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *Invoice) GetAmtPaid() int64 {
if x != nil {
return x.AmtPaid
}
return 0
}
func (x *Invoice) GetAmtPaidSat() int64 {
if x != nil {
return x.AmtPaidSat
}
return 0
}
func (x *Invoice) GetAmtPaidMsat() int64 {
if x != nil {
return x.AmtPaidMsat
}
return 0
}
func (x *Invoice) GetState() Invoice_InvoiceState {
if x != nil {
return x.State
}
return Invoice_OPEN
}
func (x *Invoice) GetHtlcs() []*InvoiceHTLC {
if x != nil {
return x.Htlcs
}
return nil
}
func (x *Invoice) GetFeatures() map[uint32]*Feature {
if x != nil {
return x.Features
}
return nil
}
func (x *Invoice) GetIsKeysend() bool {
if x != nil {
return x.IsKeysend
}
return false
}
func (x *Invoice) GetPaymentAddr() []byte {
if x != nil {
return x.PaymentAddr
}
return nil
}
func (x *Invoice) GetIsAmp() bool {
if x != nil {
return x.IsAmp
}
return false
}
func (x *Invoice) GetAmpInvoiceState() map[string]*AMPInvoiceState {
if x != nil {
return x.AmpInvoiceState
}
return nil
}
func (x *Invoice) GetIsBlinded() bool {
if x != nil {
return x.IsBlinded
}
return false
}
func (x *Invoice) GetBlindedPathConfig() *BlindedPathConfig {
if x != nil {
return x.BlindedPathConfig
}
return nil
}
type BlindedPathConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The minimum number of real hops to include in a blinded path. This doesn't
// include our node, so if the minimum is 1, then the path will contain at
// minimum our node along with an introduction node hop. If it is zero then
// the shortest path will use our node as an introduction node.
MinNumRealHops *uint32 `protobuf:"varint,1,opt,name=min_num_real_hops,json=minNumRealHops,proto3,oneof" json:"min_num_real_hops,omitempty"`
// The number of hops to include in a blinded path. This doesn't include our
// node, so if it is 1, then the path will contain our node along with an
// introduction node or dummy node hop. If paths shorter than NumHops is
// found, then they will be padded using dummy hops.
NumHops *uint32 `protobuf:"varint,2,opt,name=num_hops,json=numHops,proto3,oneof" json:"num_hops,omitempty"`
// The maximum number of blinded paths to select and add to an invoice.
MaxNumPaths *uint32 `protobuf:"varint,3,opt,name=max_num_paths,json=maxNumPaths,proto3,oneof" json:"max_num_paths,omitempty"`
// A list of node IDs of nodes that should not be used in any of our generated
// blinded paths.
NodeOmissionList [][]byte `protobuf:"bytes,4,rep,name=node_omission_list,json=nodeOmissionList,proto3" json:"node_omission_list,omitempty"`
}
func (x *BlindedPathConfig) Reset() {
*x = BlindedPathConfig{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[136]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *BlindedPathConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BlindedPathConfig) ProtoMessage() {}
func (x *BlindedPathConfig) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[136]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BlindedPathConfig.ProtoReflect.Descriptor instead.
func (*BlindedPathConfig) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{136}
}
func (x *BlindedPathConfig) GetMinNumRealHops() uint32 {
if x != nil && x.MinNumRealHops != nil {
return *x.MinNumRealHops
}
return 0
}
func (x *BlindedPathConfig) GetNumHops() uint32 {
if x != nil && x.NumHops != nil {
return *x.NumHops
}
return 0
}
func (x *BlindedPathConfig) GetMaxNumPaths() uint32 {
if x != nil && x.MaxNumPaths != nil {
return *x.MaxNumPaths
}
return 0
}
func (x *BlindedPathConfig) GetNodeOmissionList() [][]byte {
if x != nil {
return x.NodeOmissionList
}
return nil
}
// Details of an HTLC that paid to an invoice
type InvoiceHTLC struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Short channel id over which the htlc was received.
ChanId uint64 `protobuf:"varint,1,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"`
// Index identifying the htlc on the channel.
HtlcIndex uint64 `protobuf:"varint,2,opt,name=htlc_index,json=htlcIndex,proto3" json:"htlc_index,omitempty"`
// The amount of the htlc in msat.
AmtMsat uint64 `protobuf:"varint,3,opt,name=amt_msat,json=amtMsat,proto3" json:"amt_msat,omitempty"`
// Block height at which this htlc was accepted.
AcceptHeight int32 `protobuf:"varint,4,opt,name=accept_height,json=acceptHeight,proto3" json:"accept_height,omitempty"`
// Time at which this htlc was accepted.
AcceptTime int64 `protobuf:"varint,5,opt,name=accept_time,json=acceptTime,proto3" json:"accept_time,omitempty"`
// Time at which this htlc was settled or canceled.
ResolveTime int64 `protobuf:"varint,6,opt,name=resolve_time,json=resolveTime,proto3" json:"resolve_time,omitempty"`
// Block height at which this htlc expires.
ExpiryHeight int32 `protobuf:"varint,7,opt,name=expiry_height,json=expiryHeight,proto3" json:"expiry_height,omitempty"`
// Current state the htlc is in.
State InvoiceHTLCState `protobuf:"varint,8,opt,name=state,proto3,enum=lnrpc.InvoiceHTLCState" json:"state,omitempty"`
// Custom tlv records.
CustomRecords map[uint64][]byte `protobuf:"bytes,9,rep,name=custom_records,json=customRecords,proto3" json:"custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
// The total amount of the mpp payment in msat.
MppTotalAmtMsat uint64 `protobuf:"varint,10,opt,name=mpp_total_amt_msat,json=mppTotalAmtMsat,proto3" json:"mpp_total_amt_msat,omitempty"`
// Details relevant to AMP HTLCs, only populated if this is an AMP HTLC.
Amp *AMP `protobuf:"bytes,11,opt,name=amp,proto3" json:"amp,omitempty"`
// Custom channel data that might be populated in custom channels.
CustomChannelData []byte `protobuf:"bytes,12,opt,name=custom_channel_data,json=customChannelData,proto3" json:"custom_channel_data,omitempty"`
}
func (x *InvoiceHTLC) Reset() {
*x = InvoiceHTLC{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[137]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *InvoiceHTLC) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*InvoiceHTLC) ProtoMessage() {}
func (x *InvoiceHTLC) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[137]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use InvoiceHTLC.ProtoReflect.Descriptor instead.
func (*InvoiceHTLC) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{137}
}
func (x *InvoiceHTLC) GetChanId() uint64 {
if x != nil {
return x.ChanId
}
return 0
}
func (x *InvoiceHTLC) GetHtlcIndex() uint64 {
if x != nil {
return x.HtlcIndex
}
return 0
}
func (x *InvoiceHTLC) GetAmtMsat() uint64 {
if x != nil {
return x.AmtMsat
}
return 0
}
func (x *InvoiceHTLC) GetAcceptHeight() int32 {
if x != nil {
return x.AcceptHeight
}
return 0
}
func (x *InvoiceHTLC) GetAcceptTime() int64 {
if x != nil {
return x.AcceptTime
}
return 0
}
func (x *InvoiceHTLC) GetResolveTime() int64 {
if x != nil {
return x.ResolveTime
}
return 0
}
func (x *InvoiceHTLC) GetExpiryHeight() int32 {
if x != nil {
return x.ExpiryHeight
}
return 0
}
func (x *InvoiceHTLC) GetState() InvoiceHTLCState {
if x != nil {
return x.State
}
return InvoiceHTLCState_ACCEPTED
}
func (x *InvoiceHTLC) GetCustomRecords() map[uint64][]byte {
if x != nil {
return x.CustomRecords
}
return nil
}
func (x *InvoiceHTLC) GetMppTotalAmtMsat() uint64 {
if x != nil {
return x.MppTotalAmtMsat
}
return 0
}
func (x *InvoiceHTLC) GetAmp() *AMP {
if x != nil {
return x.Amp
}
return nil
}
func (x *InvoiceHTLC) GetCustomChannelData() []byte {
if x != nil {
return x.CustomChannelData
}
return nil
}
// Details specific to AMP HTLCs.
type AMP struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// An n-of-n secret share of the root seed from which child payment hashes
// and preimages are derived.
RootShare []byte `protobuf:"bytes,1,opt,name=root_share,json=rootShare,proto3" json:"root_share,omitempty"`
// An identifier for the HTLC set that this HTLC belongs to.
SetId []byte `protobuf:"bytes,2,opt,name=set_id,json=setId,proto3" json:"set_id,omitempty"`
// A nonce used to randomize the child preimage and child hash from a given
// root_share.
ChildIndex uint32 `protobuf:"varint,3,opt,name=child_index,json=childIndex,proto3" json:"child_index,omitempty"`
// The payment hash of the AMP HTLC.
Hash []byte `protobuf:"bytes,4,opt,name=hash,proto3" json:"hash,omitempty"`
// The preimage used to settle this AMP htlc. This field will only be
// populated if the invoice is in InvoiceState_ACCEPTED or
// InvoiceState_SETTLED.
Preimage []byte `protobuf:"bytes,5,opt,name=preimage,proto3" json:"preimage,omitempty"`
}
func (x *AMP) Reset() {
*x = AMP{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[138]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AMP) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AMP) ProtoMessage() {}
func (x *AMP) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[138]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AMP.ProtoReflect.Descriptor instead.
func (*AMP) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{138}
}
func (x *AMP) GetRootShare() []byte {
if x != nil {
return x.RootShare
}
return nil
}
func (x *AMP) GetSetId() []byte {
if x != nil {
return x.SetId
}
return nil
}
func (x *AMP) GetChildIndex() uint32 {
if x != nil {
return x.ChildIndex
}
return 0
}
func (x *AMP) GetHash() []byte {
if x != nil {
return x.Hash
}
return nil
}
func (x *AMP) GetPreimage() []byte {
if x != nil {
return x.Preimage
}
return nil
}
type AddInvoiceResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
RHash []byte `protobuf:"bytes,1,opt,name=r_hash,json=rHash,proto3" json:"r_hash,omitempty"`
// A bare-bones invoice for a payment within the Lightning Network. With the
// details of the invoice, the sender has all the data necessary to send a
// payment to the recipient.
PaymentRequest string `protobuf:"bytes,2,opt,name=payment_request,json=paymentRequest,proto3" json:"payment_request,omitempty"`
// The "add" index of this invoice. Each newly created invoice will increment
// this index making it monotonically increasing. Callers to the
// SubscribeInvoices call can use this to instantly get notified of all added
// invoices with an add_index greater than this one.
AddIndex uint64 `protobuf:"varint,16,opt,name=add_index,json=addIndex,proto3" json:"add_index,omitempty"`
// The payment address of the generated invoice. This is also called
// payment secret in specifications (e.g. BOLT 11). This value should be used
// in all payments for this invoice as we require it for end to end security.
PaymentAddr []byte `protobuf:"bytes,17,opt,name=payment_addr,json=paymentAddr,proto3" json:"payment_addr,omitempty"`
}
func (x *AddInvoiceResponse) Reset() {
*x = AddInvoiceResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[139]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AddInvoiceResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AddInvoiceResponse) ProtoMessage() {}
func (x *AddInvoiceResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[139]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AddInvoiceResponse.ProtoReflect.Descriptor instead.
func (*AddInvoiceResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{139}
}
func (x *AddInvoiceResponse) GetRHash() []byte {
if x != nil {
return x.RHash
}
return nil
}
func (x *AddInvoiceResponse) GetPaymentRequest() string {
if x != nil {
return x.PaymentRequest
}
return ""
}
func (x *AddInvoiceResponse) GetAddIndex() uint64 {
if x != nil {
return x.AddIndex
}
return 0
}
func (x *AddInvoiceResponse) GetPaymentAddr() []byte {
if x != nil {
return x.PaymentAddr
}
return nil
}
type PaymentHash struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The hex-encoded payment hash of the invoice to be looked up. The passed
// payment hash must be exactly 32 bytes, otherwise an error is returned.
// Deprecated now that the REST gateway supports base64 encoding of bytes
// fields.
//
// Deprecated: Marked as deprecated in lightning.proto.
RHashStr string `protobuf:"bytes,1,opt,name=r_hash_str,json=rHashStr,proto3" json:"r_hash_str,omitempty"`
// The payment hash of the invoice to be looked up. When using REST, this field
// must be encoded as base64.
RHash []byte `protobuf:"bytes,2,opt,name=r_hash,json=rHash,proto3" json:"r_hash,omitempty"`
}
func (x *PaymentHash) Reset() {
*x = PaymentHash{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[140]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PaymentHash) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PaymentHash) ProtoMessage() {}
func (x *PaymentHash) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[140]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PaymentHash.ProtoReflect.Descriptor instead.
func (*PaymentHash) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{140}
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *PaymentHash) GetRHashStr() string {
if x != nil {
return x.RHashStr
}
return ""
}
func (x *PaymentHash) GetRHash() []byte {
if x != nil {
return x.RHash
}
return nil
}
type ListInvoiceRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// If set, only invoices that are not settled and not canceled will be returned
// in the response.
PendingOnly bool `protobuf:"varint,1,opt,name=pending_only,json=pendingOnly,proto3" json:"pending_only,omitempty"`
// The index of an invoice that will be used as either the start or end of a
// query to determine which invoices should be returned in the response.
IndexOffset uint64 `protobuf:"varint,4,opt,name=index_offset,json=indexOffset,proto3" json:"index_offset,omitempty"`
// The max number of invoices to return in the response to this query.
NumMaxInvoices uint64 `protobuf:"varint,5,opt,name=num_max_invoices,json=numMaxInvoices,proto3" json:"num_max_invoices,omitempty"`
// If set, the invoices returned will result from seeking backwards from the
// specified index offset. This can be used to paginate backwards.
Reversed bool `protobuf:"varint,6,opt,name=reversed,proto3" json:"reversed,omitempty"`
// If set, returns all invoices with a creation date greater than or equal
// to it. Measured in seconds since the unix epoch.
CreationDateStart uint64 `protobuf:"varint,7,opt,name=creation_date_start,json=creationDateStart,proto3" json:"creation_date_start,omitempty"`
// If set, returns all invoices with a creation date less than or equal to
// it. Measured in seconds since the unix epoch.
CreationDateEnd uint64 `protobuf:"varint,8,opt,name=creation_date_end,json=creationDateEnd,proto3" json:"creation_date_end,omitempty"`
}
func (x *ListInvoiceRequest) Reset() {
*x = ListInvoiceRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[141]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListInvoiceRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListInvoiceRequest) ProtoMessage() {}
func (x *ListInvoiceRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[141]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListInvoiceRequest.ProtoReflect.Descriptor instead.
func (*ListInvoiceRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{141}
}
func (x *ListInvoiceRequest) GetPendingOnly() bool {
if x != nil {
return x.PendingOnly
}
return false
}
func (x *ListInvoiceRequest) GetIndexOffset() uint64 {
if x != nil {
return x.IndexOffset
}
return 0
}
func (x *ListInvoiceRequest) GetNumMaxInvoices() uint64 {
if x != nil {
return x.NumMaxInvoices
}
return 0
}
func (x *ListInvoiceRequest) GetReversed() bool {
if x != nil {
return x.Reversed
}
return false
}
func (x *ListInvoiceRequest) GetCreationDateStart() uint64 {
if x != nil {
return x.CreationDateStart
}
return 0
}
func (x *ListInvoiceRequest) GetCreationDateEnd() uint64 {
if x != nil {
return x.CreationDateEnd
}
return 0
}
type ListInvoiceResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A list of invoices from the time slice of the time series specified in the
// request.
Invoices []*Invoice `protobuf:"bytes,1,rep,name=invoices,proto3" json:"invoices,omitempty"`
// The index of the last item in the set of returned invoices. This can be used
// to seek further, pagination style.
LastIndexOffset uint64 `protobuf:"varint,2,opt,name=last_index_offset,json=lastIndexOffset,proto3" json:"last_index_offset,omitempty"`
// The index of the last item in the set of returned invoices. This can be used
// to seek backwards, pagination style.
FirstIndexOffset uint64 `protobuf:"varint,3,opt,name=first_index_offset,json=firstIndexOffset,proto3" json:"first_index_offset,omitempty"`
}
func (x *ListInvoiceResponse) Reset() {
*x = ListInvoiceResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[142]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListInvoiceResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListInvoiceResponse) ProtoMessage() {}
func (x *ListInvoiceResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[142]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListInvoiceResponse.ProtoReflect.Descriptor instead.
func (*ListInvoiceResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{142}
}
func (x *ListInvoiceResponse) GetInvoices() []*Invoice {
if x != nil {
return x.Invoices
}
return nil
}
func (x *ListInvoiceResponse) GetLastIndexOffset() uint64 {
if x != nil {
return x.LastIndexOffset
}
return 0
}
func (x *ListInvoiceResponse) GetFirstIndexOffset() uint64 {
if x != nil {
return x.FirstIndexOffset
}
return 0
}
type InvoiceSubscription struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// If specified (non-zero), then we'll first start by sending out
// notifications for all added indexes with an add_index greater than this
// value. This allows callers to catch up on any events they missed while they
// weren't connected to the streaming RPC.
AddIndex uint64 `protobuf:"varint,1,opt,name=add_index,json=addIndex,proto3" json:"add_index,omitempty"`
// If specified (non-zero), then we'll first start by sending out
// notifications for all settled indexes with an settle_index greater than
// this value. This allows callers to catch up on any events they missed while
// they weren't connected to the streaming RPC.
SettleIndex uint64 `protobuf:"varint,2,opt,name=settle_index,json=settleIndex,proto3" json:"settle_index,omitempty"`
}
func (x *InvoiceSubscription) Reset() {
*x = InvoiceSubscription{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[143]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *InvoiceSubscription) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*InvoiceSubscription) ProtoMessage() {}
func (x *InvoiceSubscription) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[143]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use InvoiceSubscription.ProtoReflect.Descriptor instead.
func (*InvoiceSubscription) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{143}
}
func (x *InvoiceSubscription) GetAddIndex() uint64 {
if x != nil {
return x.AddIndex
}
return 0
}
func (x *InvoiceSubscription) GetSettleIndex() uint64 {
if x != nil {
return x.SettleIndex
}
return 0
}
type Payment struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The payment hash
PaymentHash string `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"`
// Deprecated, use value_sat or value_msat.
//
// Deprecated: Marked as deprecated in lightning.proto.
Value int64 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"`
// Deprecated, use creation_time_ns
//
// Deprecated: Marked as deprecated in lightning.proto.
CreationDate int64 `protobuf:"varint,3,opt,name=creation_date,json=creationDate,proto3" json:"creation_date,omitempty"`
// Deprecated, use fee_sat or fee_msat.
//
// Deprecated: Marked as deprecated in lightning.proto.
Fee int64 `protobuf:"varint,5,opt,name=fee,proto3" json:"fee,omitempty"`
// The payment preimage
PaymentPreimage string `protobuf:"bytes,6,opt,name=payment_preimage,json=paymentPreimage,proto3" json:"payment_preimage,omitempty"`
// The value of the payment in satoshis
ValueSat int64 `protobuf:"varint,7,opt,name=value_sat,json=valueSat,proto3" json:"value_sat,omitempty"`
// The value of the payment in milli-satoshis
ValueMsat int64 `protobuf:"varint,8,opt,name=value_msat,json=valueMsat,proto3" json:"value_msat,omitempty"`
// The optional payment request being fulfilled.
PaymentRequest string `protobuf:"bytes,9,opt,name=payment_request,json=paymentRequest,proto3" json:"payment_request,omitempty"`
// The status of the payment.
Status Payment_PaymentStatus `protobuf:"varint,10,opt,name=status,proto3,enum=lnrpc.Payment_PaymentStatus" json:"status,omitempty"`
// The fee paid for this payment in satoshis
FeeSat int64 `protobuf:"varint,11,opt,name=fee_sat,json=feeSat,proto3" json:"fee_sat,omitempty"`
// The fee paid for this payment in milli-satoshis
FeeMsat int64 `protobuf:"varint,12,opt,name=fee_msat,json=feeMsat,proto3" json:"fee_msat,omitempty"`
// The time in UNIX nanoseconds at which the payment was created.
CreationTimeNs int64 `protobuf:"varint,13,opt,name=creation_time_ns,json=creationTimeNs,proto3" json:"creation_time_ns,omitempty"`
// The HTLCs made in attempt to settle the payment.
Htlcs []*HTLCAttempt `protobuf:"bytes,14,rep,name=htlcs,proto3" json:"htlcs,omitempty"`
// The creation index of this payment. Each payment can be uniquely identified
// by this index, which may not strictly increment by 1 for payments made in
// older versions of lnd.
PaymentIndex uint64 `protobuf:"varint,15,opt,name=payment_index,json=paymentIndex,proto3" json:"payment_index,omitempty"`
FailureReason PaymentFailureReason `protobuf:"varint,16,opt,name=failure_reason,json=failureReason,proto3,enum=lnrpc.PaymentFailureReason" json:"failure_reason,omitempty"`
// The custom TLV records that were sent to the first hop as part of the HTLC
// wire message for this payment.
FirstHopCustomRecords map[uint64][]byte `protobuf:"bytes,17,rep,name=first_hop_custom_records,json=firstHopCustomRecords,proto3" json:"first_hop_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *Payment) Reset() {
*x = Payment{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[144]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Payment) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Payment) ProtoMessage() {}
func (x *Payment) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[144]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Payment.ProtoReflect.Descriptor instead.
func (*Payment) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{144}
}
func (x *Payment) GetPaymentHash() string {
if x != nil {
return x.PaymentHash
}
return ""
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *Payment) GetValue() int64 {
if x != nil {
return x.Value
}
return 0
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *Payment) GetCreationDate() int64 {
if x != nil {
return x.CreationDate
}
return 0
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *Payment) GetFee() int64 {
if x != nil {
return x.Fee
}
return 0
}
func (x *Payment) GetPaymentPreimage() string {
if x != nil {
return x.PaymentPreimage
}
return ""
}
func (x *Payment) GetValueSat() int64 {
if x != nil {
return x.ValueSat
}
return 0
}
func (x *Payment) GetValueMsat() int64 {
if x != nil {
return x.ValueMsat
}
return 0
}
func (x *Payment) GetPaymentRequest() string {
if x != nil {
return x.PaymentRequest
}
return ""
}
func (x *Payment) GetStatus() Payment_PaymentStatus {
if x != nil {
return x.Status
}
return Payment_UNKNOWN
}
func (x *Payment) GetFeeSat() int64 {
if x != nil {
return x.FeeSat
}
return 0
}
func (x *Payment) GetFeeMsat() int64 {
if x != nil {
return x.FeeMsat
}
return 0
}
func (x *Payment) GetCreationTimeNs() int64 {
if x != nil {
return x.CreationTimeNs
}
return 0
}
func (x *Payment) GetHtlcs() []*HTLCAttempt {
if x != nil {
return x.Htlcs
}
return nil
}
func (x *Payment) GetPaymentIndex() uint64 {
if x != nil {
return x.PaymentIndex
}
return 0
}
func (x *Payment) GetFailureReason() PaymentFailureReason {
if x != nil {
return x.FailureReason
}
return PaymentFailureReason_FAILURE_REASON_NONE
}
func (x *Payment) GetFirstHopCustomRecords() map[uint64][]byte {
if x != nil {
return x.FirstHopCustomRecords
}
return nil
}
type HTLCAttempt struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The unique ID that is used for this attempt.
AttemptId uint64 `protobuf:"varint,7,opt,name=attempt_id,json=attemptId,proto3" json:"attempt_id,omitempty"`
// The status of the HTLC.
Status HTLCAttempt_HTLCStatus `protobuf:"varint,1,opt,name=status,proto3,enum=lnrpc.HTLCAttempt_HTLCStatus" json:"status,omitempty"`
// The route taken by this HTLC.
Route *Route `protobuf:"bytes,2,opt,name=route,proto3" json:"route,omitempty"`
// The time in UNIX nanoseconds at which this HTLC was sent.
AttemptTimeNs int64 `protobuf:"varint,3,opt,name=attempt_time_ns,json=attemptTimeNs,proto3" json:"attempt_time_ns,omitempty"`
// The time in UNIX nanoseconds at which this HTLC was settled or failed.
// This value will not be set if the HTLC is still IN_FLIGHT.
ResolveTimeNs int64 `protobuf:"varint,4,opt,name=resolve_time_ns,json=resolveTimeNs,proto3" json:"resolve_time_ns,omitempty"`
// Detailed htlc failure info.
Failure *Failure `protobuf:"bytes,5,opt,name=failure,proto3" json:"failure,omitempty"`
// The preimage that was used to settle the HTLC.
Preimage []byte `protobuf:"bytes,6,opt,name=preimage,proto3" json:"preimage,omitempty"`
}
func (x *HTLCAttempt) Reset() {
*x = HTLCAttempt{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[145]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HTLCAttempt) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HTLCAttempt) ProtoMessage() {}
func (x *HTLCAttempt) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[145]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HTLCAttempt.ProtoReflect.Descriptor instead.
func (*HTLCAttempt) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{145}
}
func (x *HTLCAttempt) GetAttemptId() uint64 {
if x != nil {
return x.AttemptId
}
return 0
}
func (x *HTLCAttempt) GetStatus() HTLCAttempt_HTLCStatus {
if x != nil {
return x.Status
}
return HTLCAttempt_IN_FLIGHT
}
func (x *HTLCAttempt) GetRoute() *Route {
if x != nil {
return x.Route
}
return nil
}
func (x *HTLCAttempt) GetAttemptTimeNs() int64 {
if x != nil {
return x.AttemptTimeNs
}
return 0
}
func (x *HTLCAttempt) GetResolveTimeNs() int64 {
if x != nil {
return x.ResolveTimeNs
}
return 0
}
func (x *HTLCAttempt) GetFailure() *Failure {
if x != nil {
return x.Failure
}
return nil
}
func (x *HTLCAttempt) GetPreimage() []byte {
if x != nil {
return x.Preimage
}
return nil
}
type ListPaymentsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// If true, then return payments that have not yet fully completed. This means
// that pending payments, as well as failed payments will show up if this
// field is set to true. This flag doesn't change the meaning of the indices,
// which are tied to individual payments.
IncludeIncomplete bool `protobuf:"varint,1,opt,name=include_incomplete,json=includeIncomplete,proto3" json:"include_incomplete,omitempty"`
// The index of a payment that will be used as either the start or end of a
// query to determine which payments should be returned in the response. The
// index_offset is exclusive. In the case of a zero index_offset, the query
// will start with the oldest payment when paginating forwards, or will end
// with the most recent payment when paginating backwards.
IndexOffset uint64 `protobuf:"varint,2,opt,name=index_offset,json=indexOffset,proto3" json:"index_offset,omitempty"`
// The maximal number of payments returned in the response to this query.
MaxPayments uint64 `protobuf:"varint,3,opt,name=max_payments,json=maxPayments,proto3" json:"max_payments,omitempty"`
// If set, the payments returned will result from seeking backwards from the
// specified index offset. This can be used to paginate backwards. The order
// of the returned payments is always oldest first (ascending index order).
Reversed bool `protobuf:"varint,4,opt,name=reversed,proto3" json:"reversed,omitempty"`
// If set, all payments (complete and incomplete, independent of the
// max_payments parameter) will be counted. Note that setting this to true will
// increase the run time of the call significantly on systems that have a lot
// of payments, as all of them have to be iterated through to be counted.
CountTotalPayments bool `protobuf:"varint,5,opt,name=count_total_payments,json=countTotalPayments,proto3" json:"count_total_payments,omitempty"`
// If set, returns all payments with a creation date greater than or equal
// to it. Measured in seconds since the unix epoch.
CreationDateStart uint64 `protobuf:"varint,6,opt,name=creation_date_start,json=creationDateStart,proto3" json:"creation_date_start,omitempty"`
// If set, returns all payments with a creation date less than or equal to
// it. Measured in seconds since the unix epoch.
CreationDateEnd uint64 `protobuf:"varint,7,opt,name=creation_date_end,json=creationDateEnd,proto3" json:"creation_date_end,omitempty"`
}
func (x *ListPaymentsRequest) Reset() {
*x = ListPaymentsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[146]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListPaymentsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListPaymentsRequest) ProtoMessage() {}
func (x *ListPaymentsRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[146]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListPaymentsRequest.ProtoReflect.Descriptor instead.
func (*ListPaymentsRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{146}
}
func (x *ListPaymentsRequest) GetIncludeIncomplete() bool {
if x != nil {
return x.IncludeIncomplete
}
return false
}
func (x *ListPaymentsRequest) GetIndexOffset() uint64 {
if x != nil {
return x.IndexOffset
}
return 0
}
func (x *ListPaymentsRequest) GetMaxPayments() uint64 {
if x != nil {
return x.MaxPayments
}
return 0
}
func (x *ListPaymentsRequest) GetReversed() bool {
if x != nil {
return x.Reversed
}
return false
}
func (x *ListPaymentsRequest) GetCountTotalPayments() bool {
if x != nil {
return x.CountTotalPayments
}
return false
}
func (x *ListPaymentsRequest) GetCreationDateStart() uint64 {
if x != nil {
return x.CreationDateStart
}
return 0
}
func (x *ListPaymentsRequest) GetCreationDateEnd() uint64 {
if x != nil {
return x.CreationDateEnd
}
return 0
}
type ListPaymentsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The list of payments
Payments []*Payment `protobuf:"bytes,1,rep,name=payments,proto3" json:"payments,omitempty"`
// The index of the first item in the set of returned payments. This can be
// used as the index_offset to continue seeking backwards in the next request.
FirstIndexOffset uint64 `protobuf:"varint,2,opt,name=first_index_offset,json=firstIndexOffset,proto3" json:"first_index_offset,omitempty"`
// The index of the last item in the set of returned payments. This can be used
// as the index_offset to continue seeking forwards in the next request.
LastIndexOffset uint64 `protobuf:"varint,3,opt,name=last_index_offset,json=lastIndexOffset,proto3" json:"last_index_offset,omitempty"`
// Will only be set if count_total_payments in the request was set. Represents
// the total number of payments (complete and incomplete, independent of the
// number of payments requested in the query) currently present in the payments
// database.
TotalNumPayments uint64 `protobuf:"varint,4,opt,name=total_num_payments,json=totalNumPayments,proto3" json:"total_num_payments,omitempty"`
}
func (x *ListPaymentsResponse) Reset() {
*x = ListPaymentsResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[147]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListPaymentsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListPaymentsResponse) ProtoMessage() {}
func (x *ListPaymentsResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[147]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListPaymentsResponse.ProtoReflect.Descriptor instead.
func (*ListPaymentsResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{147}
}
func (x *ListPaymentsResponse) GetPayments() []*Payment {
if x != nil {
return x.Payments
}
return nil
}
func (x *ListPaymentsResponse) GetFirstIndexOffset() uint64 {
if x != nil {
return x.FirstIndexOffset
}
return 0
}
func (x *ListPaymentsResponse) GetLastIndexOffset() uint64 {
if x != nil {
return x.LastIndexOffset
}
return 0
}
func (x *ListPaymentsResponse) GetTotalNumPayments() uint64 {
if x != nil {
return x.TotalNumPayments
}
return 0
}
type DeletePaymentRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Payment hash to delete.
PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"`
// Only delete failed HTLCs from the payment, not the payment itself.
FailedHtlcsOnly bool `protobuf:"varint,2,opt,name=failed_htlcs_only,json=failedHtlcsOnly,proto3" json:"failed_htlcs_only,omitempty"`
}
func (x *DeletePaymentRequest) Reset() {
*x = DeletePaymentRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[148]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DeletePaymentRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeletePaymentRequest) ProtoMessage() {}
func (x *DeletePaymentRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[148]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeletePaymentRequest.ProtoReflect.Descriptor instead.
func (*DeletePaymentRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{148}
}
func (x *DeletePaymentRequest) GetPaymentHash() []byte {
if x != nil {
return x.PaymentHash
}
return nil
}
func (x *DeletePaymentRequest) GetFailedHtlcsOnly() bool {
if x != nil {
return x.FailedHtlcsOnly
}
return false
}
type DeleteAllPaymentsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Only delete failed payments.
FailedPaymentsOnly bool `protobuf:"varint,1,opt,name=failed_payments_only,json=failedPaymentsOnly,proto3" json:"failed_payments_only,omitempty"`
// Only delete failed HTLCs from payments, not the payment itself.
FailedHtlcsOnly bool `protobuf:"varint,2,opt,name=failed_htlcs_only,json=failedHtlcsOnly,proto3" json:"failed_htlcs_only,omitempty"`
// Delete all payments. NOTE: Using this option requires careful
// consideration as it is a destructive operation.
AllPayments bool `protobuf:"varint,3,opt,name=all_payments,json=allPayments,proto3" json:"all_payments,omitempty"`
}
func (x *DeleteAllPaymentsRequest) Reset() {
*x = DeleteAllPaymentsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[149]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DeleteAllPaymentsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeleteAllPaymentsRequest) ProtoMessage() {}
func (x *DeleteAllPaymentsRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[149]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeleteAllPaymentsRequest.ProtoReflect.Descriptor instead.
func (*DeleteAllPaymentsRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{149}
}
func (x *DeleteAllPaymentsRequest) GetFailedPaymentsOnly() bool {
if x != nil {
return x.FailedPaymentsOnly
}
return false
}
func (x *DeleteAllPaymentsRequest) GetFailedHtlcsOnly() bool {
if x != nil {
return x.FailedHtlcsOnly
}
return false
}
func (x *DeleteAllPaymentsRequest) GetAllPayments() bool {
if x != nil {
return x.AllPayments
}
return false
}
type DeletePaymentResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The status of the delete operation.
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
}
func (x *DeletePaymentResponse) Reset() {
*x = DeletePaymentResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[150]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DeletePaymentResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeletePaymentResponse) ProtoMessage() {}
func (x *DeletePaymentResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[150]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeletePaymentResponse.ProtoReflect.Descriptor instead.
func (*DeletePaymentResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{150}
}
func (x *DeletePaymentResponse) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
type DeleteAllPaymentsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The status of the delete operation.
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
}
func (x *DeleteAllPaymentsResponse) Reset() {
*x = DeleteAllPaymentsResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[151]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DeleteAllPaymentsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeleteAllPaymentsResponse) ProtoMessage() {}
func (x *DeleteAllPaymentsResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[151]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeleteAllPaymentsResponse.ProtoReflect.Descriptor instead.
func (*DeleteAllPaymentsResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{151}
}
func (x *DeleteAllPaymentsResponse) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
type AbandonChannelRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ChannelPoint *ChannelPoint `protobuf:"bytes,1,opt,name=channel_point,json=channelPoint,proto3" json:"channel_point,omitempty"`
PendingFundingShimOnly bool `protobuf:"varint,2,opt,name=pending_funding_shim_only,json=pendingFundingShimOnly,proto3" json:"pending_funding_shim_only,omitempty"`
// Override the requirement for being in dev mode by setting this to true and
// confirming the user knows what they are doing and this is a potential foot
// gun to lose funds if used on active channels.
IKnowWhatIAmDoing bool `protobuf:"varint,3,opt,name=i_know_what_i_am_doing,json=iKnowWhatIAmDoing,proto3" json:"i_know_what_i_am_doing,omitempty"`
}
func (x *AbandonChannelRequest) Reset() {
*x = AbandonChannelRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[152]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AbandonChannelRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AbandonChannelRequest) ProtoMessage() {}
func (x *AbandonChannelRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[152]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AbandonChannelRequest.ProtoReflect.Descriptor instead.
func (*AbandonChannelRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{152}
}
func (x *AbandonChannelRequest) GetChannelPoint() *ChannelPoint {
if x != nil {
return x.ChannelPoint
}
return nil
}
func (x *AbandonChannelRequest) GetPendingFundingShimOnly() bool {
if x != nil {
return x.PendingFundingShimOnly
}
return false
}
func (x *AbandonChannelRequest) GetIKnowWhatIAmDoing() bool {
if x != nil {
return x.IKnowWhatIAmDoing
}
return false
}
type AbandonChannelResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The status of the abandon operation.
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
}
func (x *AbandonChannelResponse) Reset() {
*x = AbandonChannelResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[153]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AbandonChannelResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AbandonChannelResponse) ProtoMessage() {}
func (x *AbandonChannelResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[153]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AbandonChannelResponse.ProtoReflect.Descriptor instead.
func (*AbandonChannelResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{153}
}
func (x *AbandonChannelResponse) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
type DebugLevelRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Show bool `protobuf:"varint,1,opt,name=show,proto3" json:"show,omitempty"`
LevelSpec string `protobuf:"bytes,2,opt,name=level_spec,json=levelSpec,proto3" json:"level_spec,omitempty"`
}
func (x *DebugLevelRequest) Reset() {
*x = DebugLevelRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[154]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DebugLevelRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DebugLevelRequest) ProtoMessage() {}
func (x *DebugLevelRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[154]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DebugLevelRequest.ProtoReflect.Descriptor instead.
func (*DebugLevelRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{154}
}
func (x *DebugLevelRequest) GetShow() bool {
if x != nil {
return x.Show
}
return false
}
func (x *DebugLevelRequest) GetLevelSpec() string {
if x != nil {
return x.LevelSpec
}
return ""
}
type DebugLevelResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
SubSystems string `protobuf:"bytes,1,opt,name=sub_systems,json=subSystems,proto3" json:"sub_systems,omitempty"`
}
func (x *DebugLevelResponse) Reset() {
*x = DebugLevelResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[155]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DebugLevelResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DebugLevelResponse) ProtoMessage() {}
func (x *DebugLevelResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[155]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DebugLevelResponse.ProtoReflect.Descriptor instead.
func (*DebugLevelResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{155}
}
func (x *DebugLevelResponse) GetSubSystems() string {
if x != nil {
return x.SubSystems
}
return ""
}
type PayReqString struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The payment request string to be decoded
PayReq string `protobuf:"bytes,1,opt,name=pay_req,json=payReq,proto3" json:"pay_req,omitempty"`
}
func (x *PayReqString) Reset() {
*x = PayReqString{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[156]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PayReqString) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PayReqString) ProtoMessage() {}
func (x *PayReqString) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[156]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PayReqString.ProtoReflect.Descriptor instead.
func (*PayReqString) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{156}
}
func (x *PayReqString) GetPayReq() string {
if x != nil {
return x.PayReq
}
return ""
}
type PayReq struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Destination string `protobuf:"bytes,1,opt,name=destination,proto3" json:"destination,omitempty"`
PaymentHash string `protobuf:"bytes,2,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"`
NumSatoshis int64 `protobuf:"varint,3,opt,name=num_satoshis,json=numSatoshis,proto3" json:"num_satoshis,omitempty"`
Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
Expiry int64 `protobuf:"varint,5,opt,name=expiry,proto3" json:"expiry,omitempty"`
Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty"`
DescriptionHash string `protobuf:"bytes,7,opt,name=description_hash,json=descriptionHash,proto3" json:"description_hash,omitempty"`
FallbackAddr string `protobuf:"bytes,8,opt,name=fallback_addr,json=fallbackAddr,proto3" json:"fallback_addr,omitempty"`
CltvExpiry int64 `protobuf:"varint,9,opt,name=cltv_expiry,json=cltvExpiry,proto3" json:"cltv_expiry,omitempty"`
RouteHints []*RouteHint `protobuf:"bytes,10,rep,name=route_hints,json=routeHints,proto3" json:"route_hints,omitempty"`
PaymentAddr []byte `protobuf:"bytes,11,opt,name=payment_addr,json=paymentAddr,proto3" json:"payment_addr,omitempty"`
NumMsat int64 `protobuf:"varint,12,opt,name=num_msat,json=numMsat,proto3" json:"num_msat,omitempty"`
Features map[uint32]*Feature `protobuf:"bytes,13,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
BlindedPaths []*BlindedPaymentPath `protobuf:"bytes,14,rep,name=blinded_paths,json=blindedPaths,proto3" json:"blinded_paths,omitempty"`
}
func (x *PayReq) Reset() {
*x = PayReq{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[157]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PayReq) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PayReq) ProtoMessage() {}
func (x *PayReq) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[157]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PayReq.ProtoReflect.Descriptor instead.
func (*PayReq) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{157}
}
func (x *PayReq) GetDestination() string {
if x != nil {
return x.Destination
}
return ""
}
func (x *PayReq) GetPaymentHash() string {
if x != nil {
return x.PaymentHash
}
return ""
}
func (x *PayReq) GetNumSatoshis() int64 {
if x != nil {
return x.NumSatoshis
}
return 0
}
func (x *PayReq) GetTimestamp() int64 {
if x != nil {
return x.Timestamp
}
return 0
}
func (x *PayReq) GetExpiry() int64 {
if x != nil {
return x.Expiry
}
return 0
}
func (x *PayReq) GetDescription() string {
if x != nil {
return x.Description
}
return ""
}
func (x *PayReq) GetDescriptionHash() string {
if x != nil {
return x.DescriptionHash
}
return ""
}
func (x *PayReq) GetFallbackAddr() string {
if x != nil {
return x.FallbackAddr
}
return ""
}
func (x *PayReq) GetCltvExpiry() int64 {
if x != nil {
return x.CltvExpiry
}
return 0
}
func (x *PayReq) GetRouteHints() []*RouteHint {
if x != nil {
return x.RouteHints
}
return nil
}
func (x *PayReq) GetPaymentAddr() []byte {
if x != nil {
return x.PaymentAddr
}
return nil
}
func (x *PayReq) GetNumMsat() int64 {
if x != nil {
return x.NumMsat
}
return 0
}
func (x *PayReq) GetFeatures() map[uint32]*Feature {
if x != nil {
return x.Features
}
return nil
}
func (x *PayReq) GetBlindedPaths() []*BlindedPaymentPath {
if x != nil {
return x.BlindedPaths
}
return nil
}
type Feature struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
IsRequired bool `protobuf:"varint,3,opt,name=is_required,json=isRequired,proto3" json:"is_required,omitempty"`
IsKnown bool `protobuf:"varint,4,opt,name=is_known,json=isKnown,proto3" json:"is_known,omitempty"`
}
func (x *Feature) Reset() {
*x = Feature{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[158]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Feature) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Feature) ProtoMessage() {}
func (x *Feature) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[158]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Feature.ProtoReflect.Descriptor instead.
func (*Feature) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{158}
}
func (x *Feature) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Feature) GetIsRequired() bool {
if x != nil {
return x.IsRequired
}
return false
}
func (x *Feature) GetIsKnown() bool {
if x != nil {
return x.IsKnown
}
return false
}
type FeeReportRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *FeeReportRequest) Reset() {
*x = FeeReportRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[159]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FeeReportRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FeeReportRequest) ProtoMessage() {}
func (x *FeeReportRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[159]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FeeReportRequest.ProtoReflect.Descriptor instead.
func (*FeeReportRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{159}
}
type ChannelFeeReport struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The short channel id that this fee report belongs to.
ChanId uint64 `protobuf:"varint,5,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"`
// The channel that this fee report belongs to.
ChannelPoint string `protobuf:"bytes,1,opt,name=channel_point,json=channelPoint,proto3" json:"channel_point,omitempty"`
// The base fee charged regardless of the number of milli-satoshis sent.
BaseFeeMsat int64 `protobuf:"varint,2,opt,name=base_fee_msat,json=baseFeeMsat,proto3" json:"base_fee_msat,omitempty"`
// The amount charged per milli-satoshis transferred expressed in
// millionths of a satoshi.
FeePerMil int64 `protobuf:"varint,3,opt,name=fee_per_mil,json=feePerMil,proto3" json:"fee_per_mil,omitempty"`
// The effective fee rate in milli-satoshis. Computed by dividing the
// fee_per_mil value by 1 million.
FeeRate float64 `protobuf:"fixed64,4,opt,name=fee_rate,json=feeRate,proto3" json:"fee_rate,omitempty"`
// The base fee charged regardless of the number of milli-satoshis sent.
InboundBaseFeeMsat int32 `protobuf:"varint,6,opt,name=inbound_base_fee_msat,json=inboundBaseFeeMsat,proto3" json:"inbound_base_fee_msat,omitempty"`
// The amount charged per milli-satoshis transferred expressed in
// millionths of a satoshi.
InboundFeePerMil int32 `protobuf:"varint,7,opt,name=inbound_fee_per_mil,json=inboundFeePerMil,proto3" json:"inbound_fee_per_mil,omitempty"`
}
func (x *ChannelFeeReport) Reset() {
*x = ChannelFeeReport{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[160]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChannelFeeReport) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelFeeReport) ProtoMessage() {}
func (x *ChannelFeeReport) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[160]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelFeeReport.ProtoReflect.Descriptor instead.
func (*ChannelFeeReport) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{160}
}
func (x *ChannelFeeReport) GetChanId() uint64 {
if x != nil {
return x.ChanId
}
return 0
}
func (x *ChannelFeeReport) GetChannelPoint() string {
if x != nil {
return x.ChannelPoint
}
return ""
}
func (x *ChannelFeeReport) GetBaseFeeMsat() int64 {
if x != nil {
return x.BaseFeeMsat
}
return 0
}
func (x *ChannelFeeReport) GetFeePerMil() int64 {
if x != nil {
return x.FeePerMil
}
return 0
}
func (x *ChannelFeeReport) GetFeeRate() float64 {
if x != nil {
return x.FeeRate
}
return 0
}
func (x *ChannelFeeReport) GetInboundBaseFeeMsat() int32 {
if x != nil {
return x.InboundBaseFeeMsat
}
return 0
}
func (x *ChannelFeeReport) GetInboundFeePerMil() int32 {
if x != nil {
return x.InboundFeePerMil
}
return 0
}
type FeeReportResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// An array of channel fee reports which describes the current fee schedule
// for each channel.
ChannelFees []*ChannelFeeReport `protobuf:"bytes,1,rep,name=channel_fees,json=channelFees,proto3" json:"channel_fees,omitempty"`
// The total amount of fee revenue (in satoshis) the switch has collected
// over the past 24 hrs.
DayFeeSum uint64 `protobuf:"varint,2,opt,name=day_fee_sum,json=dayFeeSum,proto3" json:"day_fee_sum,omitempty"`
// The total amount of fee revenue (in satoshis) the switch has collected
// over the past 1 week.
WeekFeeSum uint64 `protobuf:"varint,3,opt,name=week_fee_sum,json=weekFeeSum,proto3" json:"week_fee_sum,omitempty"`
// The total amount of fee revenue (in satoshis) the switch has collected
// over the past 1 month.
MonthFeeSum uint64 `protobuf:"varint,4,opt,name=month_fee_sum,json=monthFeeSum,proto3" json:"month_fee_sum,omitempty"`
}
func (x *FeeReportResponse) Reset() {
*x = FeeReportResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[161]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FeeReportResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FeeReportResponse) ProtoMessage() {}
func (x *FeeReportResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[161]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FeeReportResponse.ProtoReflect.Descriptor instead.
func (*FeeReportResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{161}
}
func (x *FeeReportResponse) GetChannelFees() []*ChannelFeeReport {
if x != nil {
return x.ChannelFees
}
return nil
}
func (x *FeeReportResponse) GetDayFeeSum() uint64 {
if x != nil {
return x.DayFeeSum
}
return 0
}
func (x *FeeReportResponse) GetWeekFeeSum() uint64 {
if x != nil {
return x.WeekFeeSum
}
return 0
}
func (x *FeeReportResponse) GetMonthFeeSum() uint64 {
if x != nil {
return x.MonthFeeSum
}
return 0
}
type InboundFee struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The inbound base fee charged regardless of the number of milli-satoshis
// received in the channel. By default, only negative values are accepted.
BaseFeeMsat int32 `protobuf:"varint,1,opt,name=base_fee_msat,json=baseFeeMsat,proto3" json:"base_fee_msat,omitempty"`
// The effective inbound fee rate in micro-satoshis (parts per million).
// By default, only negative values are accepted.
FeeRatePpm int32 `protobuf:"varint,2,opt,name=fee_rate_ppm,json=feeRatePpm,proto3" json:"fee_rate_ppm,omitempty"`
}
func (x *InboundFee) Reset() {
*x = InboundFee{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[162]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *InboundFee) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*InboundFee) ProtoMessage() {}
func (x *InboundFee) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[162]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use InboundFee.ProtoReflect.Descriptor instead.
func (*InboundFee) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{162}
}
func (x *InboundFee) GetBaseFeeMsat() int32 {
if x != nil {
return x.BaseFeeMsat
}
return 0
}
func (x *InboundFee) GetFeeRatePpm() int32 {
if x != nil {
return x.FeeRatePpm
}
return 0
}
type PolicyUpdateRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Scope:
//
// *PolicyUpdateRequest_Global
// *PolicyUpdateRequest_ChanPoint
Scope isPolicyUpdateRequest_Scope `protobuf_oneof:"scope"`
// The base fee charged regardless of the number of milli-satoshis sent.
BaseFeeMsat int64 `protobuf:"varint,3,opt,name=base_fee_msat,json=baseFeeMsat,proto3" json:"base_fee_msat,omitempty"`
// The effective fee rate in milli-satoshis. The precision of this value
// goes up to 6 decimal places, so 1e-6.
FeeRate float64 `protobuf:"fixed64,4,opt,name=fee_rate,json=feeRate,proto3" json:"fee_rate,omitempty"`
// The effective fee rate in micro-satoshis (parts per million).
FeeRatePpm uint32 `protobuf:"varint,9,opt,name=fee_rate_ppm,json=feeRatePpm,proto3" json:"fee_rate_ppm,omitempty"`
// The required timelock delta for HTLCs forwarded over the channel.
TimeLockDelta uint32 `protobuf:"varint,5,opt,name=time_lock_delta,json=timeLockDelta,proto3" json:"time_lock_delta,omitempty"`
// If set, the maximum HTLC size in milli-satoshis. If unset, the maximum
// HTLC will be unchanged.
MaxHtlcMsat uint64 `protobuf:"varint,6,opt,name=max_htlc_msat,json=maxHtlcMsat,proto3" json:"max_htlc_msat,omitempty"`
// The minimum HTLC size in milli-satoshis. Only applied if
// min_htlc_msat_specified is true.
MinHtlcMsat uint64 `protobuf:"varint,7,opt,name=min_htlc_msat,json=minHtlcMsat,proto3" json:"min_htlc_msat,omitempty"`
// If true, min_htlc_msat is applied.
MinHtlcMsatSpecified bool `protobuf:"varint,8,opt,name=min_htlc_msat_specified,json=minHtlcMsatSpecified,proto3" json:"min_htlc_msat_specified,omitempty"`
// Optional inbound fee. If unset, the previously set value will be
// retained [EXPERIMENTAL].
InboundFee *InboundFee `protobuf:"bytes,10,opt,name=inbound_fee,json=inboundFee,proto3" json:"inbound_fee,omitempty"`
// Under unknown circumstances a channel can exist with a missing edge in
// the graph database. This can cause an 'edge not found' error when calling
// `getchaninfo` and/or cause the default channel policy to be used during
// forwards. Setting this flag will recreate the edge if not found, allowing
// updating this channel policy and fixing the missing edge problem for this
// channel permanently. For fields not set in this command, the default
// policy will be created.
CreateMissingEdge bool `protobuf:"varint,11,opt,name=create_missing_edge,json=createMissingEdge,proto3" json:"create_missing_edge,omitempty"`
}
func (x *PolicyUpdateRequest) Reset() {
*x = PolicyUpdateRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[163]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PolicyUpdateRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PolicyUpdateRequest) ProtoMessage() {}
func (x *PolicyUpdateRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[163]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PolicyUpdateRequest.ProtoReflect.Descriptor instead.
func (*PolicyUpdateRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{163}
}
func (m *PolicyUpdateRequest) GetScope() isPolicyUpdateRequest_Scope {
if m != nil {
return m.Scope
}
return nil
}
func (x *PolicyUpdateRequest) GetGlobal() bool {
if x, ok := x.GetScope().(*PolicyUpdateRequest_Global); ok {
return x.Global
}
return false
}
func (x *PolicyUpdateRequest) GetChanPoint() *ChannelPoint {
if x, ok := x.GetScope().(*PolicyUpdateRequest_ChanPoint); ok {
return x.ChanPoint
}
return nil
}
func (x *PolicyUpdateRequest) GetBaseFeeMsat() int64 {
if x != nil {
return x.BaseFeeMsat
}
return 0
}
func (x *PolicyUpdateRequest) GetFeeRate() float64 {
if x != nil {
return x.FeeRate
}
return 0
}
func (x *PolicyUpdateRequest) GetFeeRatePpm() uint32 {
if x != nil {
return x.FeeRatePpm
}
return 0
}
func (x *PolicyUpdateRequest) GetTimeLockDelta() uint32 {
if x != nil {
return x.TimeLockDelta
}
return 0
}
func (x *PolicyUpdateRequest) GetMaxHtlcMsat() uint64 {
if x != nil {
return x.MaxHtlcMsat
}
return 0
}
func (x *PolicyUpdateRequest) GetMinHtlcMsat() uint64 {
if x != nil {
return x.MinHtlcMsat
}
return 0
}
func (x *PolicyUpdateRequest) GetMinHtlcMsatSpecified() bool {
if x != nil {
return x.MinHtlcMsatSpecified
}
return false
}
func (x *PolicyUpdateRequest) GetInboundFee() *InboundFee {
if x != nil {
return x.InboundFee
}
return nil
}
func (x *PolicyUpdateRequest) GetCreateMissingEdge() bool {
if x != nil {
return x.CreateMissingEdge
}
return false
}
type isPolicyUpdateRequest_Scope interface {
isPolicyUpdateRequest_Scope()
}
type PolicyUpdateRequest_Global struct {
// If set, then this update applies to all currently active channels.
Global bool `protobuf:"varint,1,opt,name=global,proto3,oneof"`
}
type PolicyUpdateRequest_ChanPoint struct {
// If set, this update will target a specific channel.
ChanPoint *ChannelPoint `protobuf:"bytes,2,opt,name=chan_point,json=chanPoint,proto3,oneof"`
}
func (*PolicyUpdateRequest_Global) isPolicyUpdateRequest_Scope() {}
func (*PolicyUpdateRequest_ChanPoint) isPolicyUpdateRequest_Scope() {}
type FailedUpdate struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The outpoint in format txid:n
Outpoint *OutPoint `protobuf:"bytes,1,opt,name=outpoint,proto3" json:"outpoint,omitempty"`
// Reason for the policy update failure.
Reason UpdateFailure `protobuf:"varint,2,opt,name=reason,proto3,enum=lnrpc.UpdateFailure" json:"reason,omitempty"`
// A string representation of the policy update error.
UpdateError string `protobuf:"bytes,3,opt,name=update_error,json=updateError,proto3" json:"update_error,omitempty"`
}
func (x *FailedUpdate) Reset() {
*x = FailedUpdate{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[164]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FailedUpdate) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FailedUpdate) ProtoMessage() {}
func (x *FailedUpdate) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[164]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FailedUpdate.ProtoReflect.Descriptor instead.
func (*FailedUpdate) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{164}
}
func (x *FailedUpdate) GetOutpoint() *OutPoint {
if x != nil {
return x.Outpoint
}
return nil
}
func (x *FailedUpdate) GetReason() UpdateFailure {
if x != nil {
return x.Reason
}
return UpdateFailure_UPDATE_FAILURE_UNKNOWN
}
func (x *FailedUpdate) GetUpdateError() string {
if x != nil {
return x.UpdateError
}
return ""
}
type PolicyUpdateResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// List of failed policy updates.
FailedUpdates []*FailedUpdate `protobuf:"bytes,1,rep,name=failed_updates,json=failedUpdates,proto3" json:"failed_updates,omitempty"`
}
func (x *PolicyUpdateResponse) Reset() {
*x = PolicyUpdateResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[165]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PolicyUpdateResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PolicyUpdateResponse) ProtoMessage() {}
func (x *PolicyUpdateResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[165]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PolicyUpdateResponse.ProtoReflect.Descriptor instead.
func (*PolicyUpdateResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{165}
}
func (x *PolicyUpdateResponse) GetFailedUpdates() []*FailedUpdate {
if x != nil {
return x.FailedUpdates
}
return nil
}
type ForwardingHistoryRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Start time is the starting point of the forwarding history request. All
// records beyond this point will be included, respecting the end time, and
// the index offset.
StartTime uint64 `protobuf:"varint,1,opt,name=start_time,json=startTime,proto3" json:"start_time,omitempty"`
// End time is the end point of the forwarding history request. The
// response will carry at most 50k records between the start time and the
// end time. The index offset can be used to implement pagination.
EndTime uint64 `protobuf:"varint,2,opt,name=end_time,json=endTime,proto3" json:"end_time,omitempty"`
// Index offset is the offset in the time series to start at. As each
// response can only contain 50k records, callers can use this to skip
// around within a packed time series.
IndexOffset uint32 `protobuf:"varint,3,opt,name=index_offset,json=indexOffset,proto3" json:"index_offset,omitempty"`
// The max number of events to return in the response to this query.
NumMaxEvents uint32 `protobuf:"varint,4,opt,name=num_max_events,json=numMaxEvents,proto3" json:"num_max_events,omitempty"`
// Informs the server if the peer alias should be looked up for each
// forwarding event.
PeerAliasLookup bool `protobuf:"varint,5,opt,name=peer_alias_lookup,json=peerAliasLookup,proto3" json:"peer_alias_lookup,omitempty"`
}
func (x *ForwardingHistoryRequest) Reset() {
*x = ForwardingHistoryRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[166]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ForwardingHistoryRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ForwardingHistoryRequest) ProtoMessage() {}
func (x *ForwardingHistoryRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[166]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ForwardingHistoryRequest.ProtoReflect.Descriptor instead.
func (*ForwardingHistoryRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{166}
}
func (x *ForwardingHistoryRequest) GetStartTime() uint64 {
if x != nil {
return x.StartTime
}
return 0
}
func (x *ForwardingHistoryRequest) GetEndTime() uint64 {
if x != nil {
return x.EndTime
}
return 0
}
func (x *ForwardingHistoryRequest) GetIndexOffset() uint32 {
if x != nil {
return x.IndexOffset
}
return 0
}
func (x *ForwardingHistoryRequest) GetNumMaxEvents() uint32 {
if x != nil {
return x.NumMaxEvents
}
return 0
}
func (x *ForwardingHistoryRequest) GetPeerAliasLookup() bool {
if x != nil {
return x.PeerAliasLookup
}
return false
}
type ForwardingEvent struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Timestamp is the time (unix epoch offset) that this circuit was
// completed. Deprecated by timestamp_ns.
//
// Deprecated: Marked as deprecated in lightning.proto.
Timestamp uint64 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
// The incoming channel ID that carried the HTLC that created the circuit.
ChanIdIn uint64 `protobuf:"varint,2,opt,name=chan_id_in,json=chanIdIn,proto3" json:"chan_id_in,omitempty"`
// The outgoing channel ID that carried the preimage that completed the
// circuit.
ChanIdOut uint64 `protobuf:"varint,4,opt,name=chan_id_out,json=chanIdOut,proto3" json:"chan_id_out,omitempty"`
// The total amount (in satoshis) of the incoming HTLC that created half
// the circuit.
AmtIn uint64 `protobuf:"varint,5,opt,name=amt_in,json=amtIn,proto3" json:"amt_in,omitempty"`
// The total amount (in satoshis) of the outgoing HTLC that created the
// second half of the circuit.
AmtOut uint64 `protobuf:"varint,6,opt,name=amt_out,json=amtOut,proto3" json:"amt_out,omitempty"`
// The total fee (in satoshis) that this payment circuit carried.
Fee uint64 `protobuf:"varint,7,opt,name=fee,proto3" json:"fee,omitempty"`
// The total fee (in milli-satoshis) that this payment circuit carried.
FeeMsat uint64 `protobuf:"varint,8,opt,name=fee_msat,json=feeMsat,proto3" json:"fee_msat,omitempty"`
// The total amount (in milli-satoshis) of the incoming HTLC that created
// half the circuit.
AmtInMsat uint64 `protobuf:"varint,9,opt,name=amt_in_msat,json=amtInMsat,proto3" json:"amt_in_msat,omitempty"`
// The total amount (in milli-satoshis) of the outgoing HTLC that created
// the second half of the circuit.
AmtOutMsat uint64 `protobuf:"varint,10,opt,name=amt_out_msat,json=amtOutMsat,proto3" json:"amt_out_msat,omitempty"`
// The number of nanoseconds elapsed since January 1, 1970 UTC when this
// circuit was completed.
TimestampNs uint64 `protobuf:"varint,11,opt,name=timestamp_ns,json=timestampNs,proto3" json:"timestamp_ns,omitempty"`
// The peer alias of the incoming channel.
PeerAliasIn string `protobuf:"bytes,12,opt,name=peer_alias_in,json=peerAliasIn,proto3" json:"peer_alias_in,omitempty"`
// The peer alias of the outgoing channel.
PeerAliasOut string `protobuf:"bytes,13,opt,name=peer_alias_out,json=peerAliasOut,proto3" json:"peer_alias_out,omitempty"`
}
func (x *ForwardingEvent) Reset() {
*x = ForwardingEvent{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[167]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ForwardingEvent) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ForwardingEvent) ProtoMessage() {}
func (x *ForwardingEvent) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[167]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ForwardingEvent.ProtoReflect.Descriptor instead.
func (*ForwardingEvent) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{167}
}
// Deprecated: Marked as deprecated in lightning.proto.
func (x *ForwardingEvent) GetTimestamp() uint64 {
if x != nil {
return x.Timestamp
}
return 0
}
func (x *ForwardingEvent) GetChanIdIn() uint64 {
if x != nil {
return x.ChanIdIn
}
return 0
}
func (x *ForwardingEvent) GetChanIdOut() uint64 {
if x != nil {
return x.ChanIdOut
}
return 0
}
func (x *ForwardingEvent) GetAmtIn() uint64 {
if x != nil {
return x.AmtIn
}
return 0
}
func (x *ForwardingEvent) GetAmtOut() uint64 {
if x != nil {
return x.AmtOut
}
return 0
}
func (x *ForwardingEvent) GetFee() uint64 {
if x != nil {
return x.Fee
}
return 0
}
func (x *ForwardingEvent) GetFeeMsat() uint64 {
if x != nil {
return x.FeeMsat
}
return 0
}
func (x *ForwardingEvent) GetAmtInMsat() uint64 {
if x != nil {
return x.AmtInMsat
}
return 0
}
func (x *ForwardingEvent) GetAmtOutMsat() uint64 {
if x != nil {
return x.AmtOutMsat
}
return 0
}
func (x *ForwardingEvent) GetTimestampNs() uint64 {
if x != nil {
return x.TimestampNs
}
return 0
}
func (x *ForwardingEvent) GetPeerAliasIn() string {
if x != nil {
return x.PeerAliasIn
}
return ""
}
func (x *ForwardingEvent) GetPeerAliasOut() string {
if x != nil {
return x.PeerAliasOut
}
return ""
}
type ForwardingHistoryResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A list of forwarding events from the time slice of the time series
// specified in the request.
ForwardingEvents []*ForwardingEvent `protobuf:"bytes,1,rep,name=forwarding_events,json=forwardingEvents,proto3" json:"forwarding_events,omitempty"`
// The index of the last time in the set of returned forwarding events. Can
// be used to seek further, pagination style.
LastOffsetIndex uint32 `protobuf:"varint,2,opt,name=last_offset_index,json=lastOffsetIndex,proto3" json:"last_offset_index,omitempty"`
}
func (x *ForwardingHistoryResponse) Reset() {
*x = ForwardingHistoryResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[168]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ForwardingHistoryResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ForwardingHistoryResponse) ProtoMessage() {}
func (x *ForwardingHistoryResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[168]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ForwardingHistoryResponse.ProtoReflect.Descriptor instead.
func (*ForwardingHistoryResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{168}
}
func (x *ForwardingHistoryResponse) GetForwardingEvents() []*ForwardingEvent {
if x != nil {
return x.ForwardingEvents
}
return nil
}
func (x *ForwardingHistoryResponse) GetLastOffsetIndex() uint32 {
if x != nil {
return x.LastOffsetIndex
}
return 0
}
type ExportChannelBackupRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The target channel point to obtain a back up for.
ChanPoint *ChannelPoint `protobuf:"bytes,1,opt,name=chan_point,json=chanPoint,proto3" json:"chan_point,omitempty"`
}
func (x *ExportChannelBackupRequest) Reset() {
*x = ExportChannelBackupRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[169]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ExportChannelBackupRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ExportChannelBackupRequest) ProtoMessage() {}
func (x *ExportChannelBackupRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[169]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ExportChannelBackupRequest.ProtoReflect.Descriptor instead.
func (*ExportChannelBackupRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{169}
}
func (x *ExportChannelBackupRequest) GetChanPoint() *ChannelPoint {
if x != nil {
return x.ChanPoint
}
return nil
}
type ChannelBackup struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Identifies the channel that this backup belongs to.
ChanPoint *ChannelPoint `protobuf:"bytes,1,opt,name=chan_point,json=chanPoint,proto3" json:"chan_point,omitempty"`
// Is an encrypted single-chan backup. this can be passed to
// RestoreChannelBackups, or the WalletUnlocker Init and Unlock methods in
// order to trigger the recovery protocol. When using REST, this field must be
// encoded as base64.
ChanBackup []byte `protobuf:"bytes,2,opt,name=chan_backup,json=chanBackup,proto3" json:"chan_backup,omitempty"`
}
func (x *ChannelBackup) Reset() {
*x = ChannelBackup{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[170]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChannelBackup) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelBackup) ProtoMessage() {}
func (x *ChannelBackup) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[170]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelBackup.ProtoReflect.Descriptor instead.
func (*ChannelBackup) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{170}
}
func (x *ChannelBackup) GetChanPoint() *ChannelPoint {
if x != nil {
return x.ChanPoint
}
return nil
}
func (x *ChannelBackup) GetChanBackup() []byte {
if x != nil {
return x.ChanBackup
}
return nil
}
type MultiChanBackup struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Is the set of all channels that are included in this multi-channel backup.
ChanPoints []*ChannelPoint `protobuf:"bytes,1,rep,name=chan_points,json=chanPoints,proto3" json:"chan_points,omitempty"`
// A single encrypted blob containing all the static channel backups of the
// channel listed above. This can be stored as a single file or blob, and
// safely be replaced with any prior/future versions. When using REST, this
// field must be encoded as base64.
MultiChanBackup []byte `protobuf:"bytes,2,opt,name=multi_chan_backup,json=multiChanBackup,proto3" json:"multi_chan_backup,omitempty"`
}
func (x *MultiChanBackup) Reset() {
*x = MultiChanBackup{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[171]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MultiChanBackup) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MultiChanBackup) ProtoMessage() {}
func (x *MultiChanBackup) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[171]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MultiChanBackup.ProtoReflect.Descriptor instead.
func (*MultiChanBackup) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{171}
}
func (x *MultiChanBackup) GetChanPoints() []*ChannelPoint {
if x != nil {
return x.ChanPoints
}
return nil
}
func (x *MultiChanBackup) GetMultiChanBackup() []byte {
if x != nil {
return x.MultiChanBackup
}
return nil
}
type ChanBackupExportRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ChanBackupExportRequest) Reset() {
*x = ChanBackupExportRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[172]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChanBackupExportRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChanBackupExportRequest) ProtoMessage() {}
func (x *ChanBackupExportRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[172]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChanBackupExportRequest.ProtoReflect.Descriptor instead.
func (*ChanBackupExportRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{172}
}
type ChanBackupSnapshot struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The set of new channels that have been added since the last channel backup
// snapshot was requested.
SingleChanBackups *ChannelBackups `protobuf:"bytes,1,opt,name=single_chan_backups,json=singleChanBackups,proto3" json:"single_chan_backups,omitempty"`
// A multi-channel backup that covers all open channels currently known to
// lnd.
MultiChanBackup *MultiChanBackup `protobuf:"bytes,2,opt,name=multi_chan_backup,json=multiChanBackup,proto3" json:"multi_chan_backup,omitempty"`
}
func (x *ChanBackupSnapshot) Reset() {
*x = ChanBackupSnapshot{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[173]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChanBackupSnapshot) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChanBackupSnapshot) ProtoMessage() {}
func (x *ChanBackupSnapshot) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[173]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChanBackupSnapshot.ProtoReflect.Descriptor instead.
func (*ChanBackupSnapshot) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{173}
}
func (x *ChanBackupSnapshot) GetSingleChanBackups() *ChannelBackups {
if x != nil {
return x.SingleChanBackups
}
return nil
}
func (x *ChanBackupSnapshot) GetMultiChanBackup() *MultiChanBackup {
if x != nil {
return x.MultiChanBackup
}
return nil
}
type ChannelBackups struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A set of single-chan static channel backups.
ChanBackups []*ChannelBackup `protobuf:"bytes,1,rep,name=chan_backups,json=chanBackups,proto3" json:"chan_backups,omitempty"`
}
func (x *ChannelBackups) Reset() {
*x = ChannelBackups{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[174]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChannelBackups) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelBackups) ProtoMessage() {}
func (x *ChannelBackups) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[174]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelBackups.ProtoReflect.Descriptor instead.
func (*ChannelBackups) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{174}
}
func (x *ChannelBackups) GetChanBackups() []*ChannelBackup {
if x != nil {
return x.ChanBackups
}
return nil
}
type RestoreChanBackupRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Backup:
//
// *RestoreChanBackupRequest_ChanBackups
// *RestoreChanBackupRequest_MultiChanBackup
Backup isRestoreChanBackupRequest_Backup `protobuf_oneof:"backup"`
}
func (x *RestoreChanBackupRequest) Reset() {
*x = RestoreChanBackupRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[175]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RestoreChanBackupRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RestoreChanBackupRequest) ProtoMessage() {}
func (x *RestoreChanBackupRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[175]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RestoreChanBackupRequest.ProtoReflect.Descriptor instead.
func (*RestoreChanBackupRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{175}
}
func (m *RestoreChanBackupRequest) GetBackup() isRestoreChanBackupRequest_Backup {
if m != nil {
return m.Backup
}
return nil
}
func (x *RestoreChanBackupRequest) GetChanBackups() *ChannelBackups {
if x, ok := x.GetBackup().(*RestoreChanBackupRequest_ChanBackups); ok {
return x.ChanBackups
}
return nil
}
func (x *RestoreChanBackupRequest) GetMultiChanBackup() []byte {
if x, ok := x.GetBackup().(*RestoreChanBackupRequest_MultiChanBackup); ok {
return x.MultiChanBackup
}
return nil
}
type isRestoreChanBackupRequest_Backup interface {
isRestoreChanBackupRequest_Backup()
}
type RestoreChanBackupRequest_ChanBackups struct {
// The channels to restore as a list of channel/backup pairs.
ChanBackups *ChannelBackups `protobuf:"bytes,1,opt,name=chan_backups,json=chanBackups,proto3,oneof"`
}
type RestoreChanBackupRequest_MultiChanBackup struct {
// The channels to restore in the packed multi backup format. When using
// REST, this field must be encoded as base64.
MultiChanBackup []byte `protobuf:"bytes,2,opt,name=multi_chan_backup,json=multiChanBackup,proto3,oneof"`
}
func (*RestoreChanBackupRequest_ChanBackups) isRestoreChanBackupRequest_Backup() {}
func (*RestoreChanBackupRequest_MultiChanBackup) isRestoreChanBackupRequest_Backup() {}
type RestoreBackupResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The number of channels successfully restored.
NumRestored uint32 `protobuf:"varint,1,opt,name=num_restored,json=numRestored,proto3" json:"num_restored,omitempty"`
}
func (x *RestoreBackupResponse) Reset() {
*x = RestoreBackupResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[176]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RestoreBackupResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RestoreBackupResponse) ProtoMessage() {}
func (x *RestoreBackupResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[176]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RestoreBackupResponse.ProtoReflect.Descriptor instead.
func (*RestoreBackupResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{176}
}
func (x *RestoreBackupResponse) GetNumRestored() uint32 {
if x != nil {
return x.NumRestored
}
return 0
}
type ChannelBackupSubscription struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ChannelBackupSubscription) Reset() {
*x = ChannelBackupSubscription{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[177]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChannelBackupSubscription) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelBackupSubscription) ProtoMessage() {}
func (x *ChannelBackupSubscription) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[177]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelBackupSubscription.ProtoReflect.Descriptor instead.
func (*ChannelBackupSubscription) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{177}
}
type VerifyChanBackupResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ChanPoints []string `protobuf:"bytes,1,rep,name=chan_points,json=chanPoints,proto3" json:"chan_points,omitempty"`
}
func (x *VerifyChanBackupResponse) Reset() {
*x = VerifyChanBackupResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[178]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *VerifyChanBackupResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*VerifyChanBackupResponse) ProtoMessage() {}
func (x *VerifyChanBackupResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[178]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use VerifyChanBackupResponse.ProtoReflect.Descriptor instead.
func (*VerifyChanBackupResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{178}
}
func (x *VerifyChanBackupResponse) GetChanPoints() []string {
if x != nil {
return x.ChanPoints
}
return nil
}
type MacaroonPermission struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The entity a permission grants access to.
Entity string `protobuf:"bytes,1,opt,name=entity,proto3" json:"entity,omitempty"`
// The action that is granted.
Action string `protobuf:"bytes,2,opt,name=action,proto3" json:"action,omitempty"`
}
func (x *MacaroonPermission) Reset() {
*x = MacaroonPermission{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[179]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MacaroonPermission) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MacaroonPermission) ProtoMessage() {}
func (x *MacaroonPermission) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[179]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MacaroonPermission.ProtoReflect.Descriptor instead.
func (*MacaroonPermission) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{179}
}
func (x *MacaroonPermission) GetEntity() string {
if x != nil {
return x.Entity
}
return ""
}
func (x *MacaroonPermission) GetAction() string {
if x != nil {
return x.Action
}
return ""
}
type BakeMacaroonRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The list of permissions the new macaroon should grant.
Permissions []*MacaroonPermission `protobuf:"bytes,1,rep,name=permissions,proto3" json:"permissions,omitempty"`
// The root key ID used to create the macaroon, must be a positive integer.
RootKeyId uint64 `protobuf:"varint,2,opt,name=root_key_id,json=rootKeyId,proto3" json:"root_key_id,omitempty"`
// Informs the RPC on whether to allow external permissions that LND is not
// aware of.
AllowExternalPermissions bool `protobuf:"varint,3,opt,name=allow_external_permissions,json=allowExternalPermissions,proto3" json:"allow_external_permissions,omitempty"`
}
func (x *BakeMacaroonRequest) Reset() {
*x = BakeMacaroonRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[180]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *BakeMacaroonRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BakeMacaroonRequest) ProtoMessage() {}
func (x *BakeMacaroonRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[180]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BakeMacaroonRequest.ProtoReflect.Descriptor instead.
func (*BakeMacaroonRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{180}
}
func (x *BakeMacaroonRequest) GetPermissions() []*MacaroonPermission {
if x != nil {
return x.Permissions
}
return nil
}
func (x *BakeMacaroonRequest) GetRootKeyId() uint64 {
if x != nil {
return x.RootKeyId
}
return 0
}
func (x *BakeMacaroonRequest) GetAllowExternalPermissions() bool {
if x != nil {
return x.AllowExternalPermissions
}
return false
}
type BakeMacaroonResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The hex encoded macaroon, serialized in binary format.
Macaroon string `protobuf:"bytes,1,opt,name=macaroon,proto3" json:"macaroon,omitempty"`
}
func (x *BakeMacaroonResponse) Reset() {
*x = BakeMacaroonResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[181]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *BakeMacaroonResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BakeMacaroonResponse) ProtoMessage() {}
func (x *BakeMacaroonResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[181]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BakeMacaroonResponse.ProtoReflect.Descriptor instead.
func (*BakeMacaroonResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{181}
}
func (x *BakeMacaroonResponse) GetMacaroon() string {
if x != nil {
return x.Macaroon
}
return ""
}
type ListMacaroonIDsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ListMacaroonIDsRequest) Reset() {
*x = ListMacaroonIDsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[182]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListMacaroonIDsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListMacaroonIDsRequest) ProtoMessage() {}
func (x *ListMacaroonIDsRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[182]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListMacaroonIDsRequest.ProtoReflect.Descriptor instead.
func (*ListMacaroonIDsRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{182}
}
type ListMacaroonIDsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The list of root key IDs that are in use.
RootKeyIds []uint64 `protobuf:"varint,1,rep,packed,name=root_key_ids,json=rootKeyIds,proto3" json:"root_key_ids,omitempty"`
}
func (x *ListMacaroonIDsResponse) Reset() {
*x = ListMacaroonIDsResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[183]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListMacaroonIDsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListMacaroonIDsResponse) ProtoMessage() {}
func (x *ListMacaroonIDsResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[183]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListMacaroonIDsResponse.ProtoReflect.Descriptor instead.
func (*ListMacaroonIDsResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{183}
}
func (x *ListMacaroonIDsResponse) GetRootKeyIds() []uint64 {
if x != nil {
return x.RootKeyIds
}
return nil
}
type DeleteMacaroonIDRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The root key ID to be removed.
RootKeyId uint64 `protobuf:"varint,1,opt,name=root_key_id,json=rootKeyId,proto3" json:"root_key_id,omitempty"`
}
func (x *DeleteMacaroonIDRequest) Reset() {
*x = DeleteMacaroonIDRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[184]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DeleteMacaroonIDRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeleteMacaroonIDRequest) ProtoMessage() {}
func (x *DeleteMacaroonIDRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[184]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeleteMacaroonIDRequest.ProtoReflect.Descriptor instead.
func (*DeleteMacaroonIDRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{184}
}
func (x *DeleteMacaroonIDRequest) GetRootKeyId() uint64 {
if x != nil {
return x.RootKeyId
}
return 0
}
type DeleteMacaroonIDResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A boolean indicates that the deletion is successful.
Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"`
}
func (x *DeleteMacaroonIDResponse) Reset() {
*x = DeleteMacaroonIDResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[185]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DeleteMacaroonIDResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeleteMacaroonIDResponse) ProtoMessage() {}
func (x *DeleteMacaroonIDResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[185]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeleteMacaroonIDResponse.ProtoReflect.Descriptor instead.
func (*DeleteMacaroonIDResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{185}
}
func (x *DeleteMacaroonIDResponse) GetDeleted() bool {
if x != nil {
return x.Deleted
}
return false
}
type MacaroonPermissionList struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A list of macaroon permissions.
Permissions []*MacaroonPermission `protobuf:"bytes,1,rep,name=permissions,proto3" json:"permissions,omitempty"`
}
func (x *MacaroonPermissionList) Reset() {
*x = MacaroonPermissionList{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[186]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MacaroonPermissionList) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MacaroonPermissionList) ProtoMessage() {}
func (x *MacaroonPermissionList) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[186]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MacaroonPermissionList.ProtoReflect.Descriptor instead.
func (*MacaroonPermissionList) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{186}
}
func (x *MacaroonPermissionList) GetPermissions() []*MacaroonPermission {
if x != nil {
return x.Permissions
}
return nil
}
type ListPermissionsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ListPermissionsRequest) Reset() {
*x = ListPermissionsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[187]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListPermissionsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListPermissionsRequest) ProtoMessage() {}
func (x *ListPermissionsRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[187]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListPermissionsRequest.ProtoReflect.Descriptor instead.
func (*ListPermissionsRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{187}
}
type ListPermissionsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A map between all RPC method URIs and their required macaroon permissions to
// access them.
MethodPermissions map[string]*MacaroonPermissionList `protobuf:"bytes,1,rep,name=method_permissions,json=methodPermissions,proto3" json:"method_permissions,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *ListPermissionsResponse) Reset() {
*x = ListPermissionsResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[188]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListPermissionsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListPermissionsResponse) ProtoMessage() {}
func (x *ListPermissionsResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[188]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListPermissionsResponse.ProtoReflect.Descriptor instead.
func (*ListPermissionsResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{188}
}
func (x *ListPermissionsResponse) GetMethodPermissions() map[string]*MacaroonPermissionList {
if x != nil {
return x.MethodPermissions
}
return nil
}
type Failure struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Failure code as defined in the Lightning spec
Code Failure_FailureCode `protobuf:"varint,1,opt,name=code,proto3,enum=lnrpc.Failure_FailureCode" json:"code,omitempty"`
// An optional channel update message.
ChannelUpdate *ChannelUpdate `protobuf:"bytes,3,opt,name=channel_update,json=channelUpdate,proto3" json:"channel_update,omitempty"`
// A failure type-dependent htlc value.
HtlcMsat uint64 `protobuf:"varint,4,opt,name=htlc_msat,json=htlcMsat,proto3" json:"htlc_msat,omitempty"`
// The sha256 sum of the onion payload.
OnionSha_256 []byte `protobuf:"bytes,5,opt,name=onion_sha_256,json=onionSha256,proto3" json:"onion_sha_256,omitempty"`
// A failure type-dependent cltv expiry value.
CltvExpiry uint32 `protobuf:"varint,6,opt,name=cltv_expiry,json=cltvExpiry,proto3" json:"cltv_expiry,omitempty"`
// A failure type-dependent flags value.
Flags uint32 `protobuf:"varint,7,opt,name=flags,proto3" json:"flags,omitempty"`
// The position in the path of the intermediate or final node that generated
// the failure message. Position zero is the sender node.
FailureSourceIndex uint32 `protobuf:"varint,8,opt,name=failure_source_index,json=failureSourceIndex,proto3" json:"failure_source_index,omitempty"`
// A failure type-dependent block height.
Height uint32 `protobuf:"varint,9,opt,name=height,proto3" json:"height,omitempty"`
}
func (x *Failure) Reset() {
*x = Failure{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[189]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Failure) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Failure) ProtoMessage() {}
func (x *Failure) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[189]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Failure.ProtoReflect.Descriptor instead.
func (*Failure) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{189}
}
func (x *Failure) GetCode() Failure_FailureCode {
if x != nil {
return x.Code
}
return Failure_RESERVED
}
func (x *Failure) GetChannelUpdate() *ChannelUpdate {
if x != nil {
return x.ChannelUpdate
}
return nil
}
func (x *Failure) GetHtlcMsat() uint64 {
if x != nil {
return x.HtlcMsat
}
return 0
}
func (x *Failure) GetOnionSha_256() []byte {
if x != nil {
return x.OnionSha_256
}
return nil
}
func (x *Failure) GetCltvExpiry() uint32 {
if x != nil {
return x.CltvExpiry
}
return 0
}
func (x *Failure) GetFlags() uint32 {
if x != nil {
return x.Flags
}
return 0
}
func (x *Failure) GetFailureSourceIndex() uint32 {
if x != nil {
return x.FailureSourceIndex
}
return 0
}
func (x *Failure) GetHeight() uint32 {
if x != nil {
return x.Height
}
return 0
}
type ChannelUpdate struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The signature that validates the announced data and proves the ownership
// of node id.
Signature []byte `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"`
// The target chain that this channel was opened within. This value
// should be the genesis hash of the target chain. Along with the short
// channel ID, this uniquely identifies the channel globally in a
// blockchain.
ChainHash []byte `protobuf:"bytes,2,opt,name=chain_hash,json=chainHash,proto3" json:"chain_hash,omitempty"`
// The unique description of the funding transaction.
ChanId uint64 `protobuf:"varint,3,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"`
// A timestamp that allows ordering in the case of multiple announcements.
// We should ignore the message if timestamp is not greater than the
// last-received.
Timestamp uint32 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
// The bitfield that describes whether optional fields are present in this
// update. Currently, the least-significant bit must be set to 1 if the
// optional field MaxHtlc is present.
MessageFlags uint32 `protobuf:"varint,10,opt,name=message_flags,json=messageFlags,proto3" json:"message_flags,omitempty"`
// The bitfield that describes additional meta-data concerning how the
// update is to be interpreted. Currently, the least-significant bit must be
// set to 0 if the creating node corresponds to the first node in the
// previously sent channel announcement and 1 otherwise. If the second bit
// is set, then the channel is set to be disabled.
ChannelFlags uint32 `protobuf:"varint,5,opt,name=channel_flags,json=channelFlags,proto3" json:"channel_flags,omitempty"`
// The minimum number of blocks this node requires to be added to the expiry
// of HTLCs. This is a security parameter determined by the node operator.
// This value represents the required gap between the time locks of the
// incoming and outgoing HTLC's set to this node.
TimeLockDelta uint32 `protobuf:"varint,6,opt,name=time_lock_delta,json=timeLockDelta,proto3" json:"time_lock_delta,omitempty"`
// The minimum HTLC value which will be accepted.
HtlcMinimumMsat uint64 `protobuf:"varint,7,opt,name=htlc_minimum_msat,json=htlcMinimumMsat,proto3" json:"htlc_minimum_msat,omitempty"`
// The base fee that must be used for incoming HTLC's to this particular
// channel. This value will be tacked onto the required for a payment
// independent of the size of the payment.
BaseFee uint32 `protobuf:"varint,8,opt,name=base_fee,json=baseFee,proto3" json:"base_fee,omitempty"`
// The fee rate that will be charged per millionth of a satoshi.
FeeRate uint32 `protobuf:"varint,9,opt,name=fee_rate,json=feeRate,proto3" json:"fee_rate,omitempty"`
// The maximum HTLC value which will be accepted.
HtlcMaximumMsat uint64 `protobuf:"varint,11,opt,name=htlc_maximum_msat,json=htlcMaximumMsat,proto3" json:"htlc_maximum_msat,omitempty"`
// The set of data that was appended to this message, some of which we may
// not actually know how to iterate or parse. By holding onto this data, we
// ensure that we're able to properly validate the set of signatures that
// cover these new fields, and ensure we're able to make upgrades to the
// network in a forwards compatible manner.
ExtraOpaqueData []byte `protobuf:"bytes,12,opt,name=extra_opaque_data,json=extraOpaqueData,proto3" json:"extra_opaque_data,omitempty"`
}
func (x *ChannelUpdate) Reset() {
*x = ChannelUpdate{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[190]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChannelUpdate) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChannelUpdate) ProtoMessage() {}
func (x *ChannelUpdate) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[190]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChannelUpdate.ProtoReflect.Descriptor instead.
func (*ChannelUpdate) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{190}
}
func (x *ChannelUpdate) GetSignature() []byte {
if x != nil {
return x.Signature
}
return nil
}
func (x *ChannelUpdate) GetChainHash() []byte {
if x != nil {
return x.ChainHash
}
return nil
}
func (x *ChannelUpdate) GetChanId() uint64 {
if x != nil {
return x.ChanId
}
return 0
}
func (x *ChannelUpdate) GetTimestamp() uint32 {
if x != nil {
return x.Timestamp
}
return 0
}
func (x *ChannelUpdate) GetMessageFlags() uint32 {
if x != nil {
return x.MessageFlags
}
return 0
}
func (x *ChannelUpdate) GetChannelFlags() uint32 {
if x != nil {
return x.ChannelFlags
}
return 0
}
func (x *ChannelUpdate) GetTimeLockDelta() uint32 {
if x != nil {
return x.TimeLockDelta
}
return 0
}
func (x *ChannelUpdate) GetHtlcMinimumMsat() uint64 {
if x != nil {
return x.HtlcMinimumMsat
}
return 0
}
func (x *ChannelUpdate) GetBaseFee() uint32 {
if x != nil {
return x.BaseFee
}
return 0
}
func (x *ChannelUpdate) GetFeeRate() uint32 {
if x != nil {
return x.FeeRate
}
return 0
}
func (x *ChannelUpdate) GetHtlcMaximumMsat() uint64 {
if x != nil {
return x.HtlcMaximumMsat
}
return 0
}
func (x *ChannelUpdate) GetExtraOpaqueData() []byte {
if x != nil {
return x.ExtraOpaqueData
}
return nil
}
type MacaroonId struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Nonce []byte `protobuf:"bytes,1,opt,name=nonce,proto3" json:"nonce,omitempty"`
StorageId []byte `protobuf:"bytes,2,opt,name=storageId,proto3" json:"storageId,omitempty"`
Ops []*Op `protobuf:"bytes,3,rep,name=ops,proto3" json:"ops,omitempty"`
}
func (x *MacaroonId) Reset() {
*x = MacaroonId{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[191]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MacaroonId) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MacaroonId) ProtoMessage() {}
func (x *MacaroonId) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[191]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MacaroonId.ProtoReflect.Descriptor instead.
func (*MacaroonId) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{191}
}
func (x *MacaroonId) GetNonce() []byte {
if x != nil {
return x.Nonce
}
return nil
}
func (x *MacaroonId) GetStorageId() []byte {
if x != nil {
return x.StorageId
}
return nil
}
func (x *MacaroonId) GetOps() []*Op {
if x != nil {
return x.Ops
}
return nil
}
type Op struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Entity string `protobuf:"bytes,1,opt,name=entity,proto3" json:"entity,omitempty"`
Actions []string `protobuf:"bytes,2,rep,name=actions,proto3" json:"actions,omitempty"`
}
func (x *Op) Reset() {
*x = Op{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[192]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Op) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Op) ProtoMessage() {}
func (x *Op) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[192]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Op.ProtoReflect.Descriptor instead.
func (*Op) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{192}
}
func (x *Op) GetEntity() string {
if x != nil {
return x.Entity
}
return ""
}
func (x *Op) GetActions() []string {
if x != nil {
return x.Actions
}
return nil
}
type CheckMacPermRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Macaroon []byte `protobuf:"bytes,1,opt,name=macaroon,proto3" json:"macaroon,omitempty"`
Permissions []*MacaroonPermission `protobuf:"bytes,2,rep,name=permissions,proto3" json:"permissions,omitempty"`
FullMethod string `protobuf:"bytes,3,opt,name=fullMethod,proto3" json:"fullMethod,omitempty"`
}
func (x *CheckMacPermRequest) Reset() {
*x = CheckMacPermRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[193]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CheckMacPermRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CheckMacPermRequest) ProtoMessage() {}
func (x *CheckMacPermRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[193]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CheckMacPermRequest.ProtoReflect.Descriptor instead.
func (*CheckMacPermRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{193}
}
func (x *CheckMacPermRequest) GetMacaroon() []byte {
if x != nil {
return x.Macaroon
}
return nil
}
func (x *CheckMacPermRequest) GetPermissions() []*MacaroonPermission {
if x != nil {
return x.Permissions
}
return nil
}
func (x *CheckMacPermRequest) GetFullMethod() string {
if x != nil {
return x.FullMethod
}
return ""
}
type CheckMacPermResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Valid bool `protobuf:"varint,1,opt,name=valid,proto3" json:"valid,omitempty"`
}
func (x *CheckMacPermResponse) Reset() {
*x = CheckMacPermResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[194]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CheckMacPermResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CheckMacPermResponse) ProtoMessage() {}
func (x *CheckMacPermResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[194]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CheckMacPermResponse.ProtoReflect.Descriptor instead.
func (*CheckMacPermResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{194}
}
func (x *CheckMacPermResponse) GetValid() bool {
if x != nil {
return x.Valid
}
return false
}
type RPCMiddlewareRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The unique ID of the intercepted original gRPC request. Useful for mapping
// request to response when implementing full duplex message interception. For
// streaming requests, this will be the same ID for all incoming and outgoing
// middleware intercept messages of the _same_ stream.
RequestId uint64 `protobuf:"varint,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
// The raw bytes of the complete macaroon as sent by the gRPC client in the
// original request. This might be empty for a request that doesn't require
// macaroons such as the wallet unlocker RPCs.
RawMacaroon []byte `protobuf:"bytes,2,opt,name=raw_macaroon,json=rawMacaroon,proto3" json:"raw_macaroon,omitempty"`
// The parsed condition of the macaroon's custom caveat for convenient access.
// This field only contains the value of the custom caveat that the handling
// middleware has registered itself for. The condition _must_ be validated for
// messages of intercept_type stream_auth and request!
CustomCaveatCondition string `protobuf:"bytes,3,opt,name=custom_caveat_condition,json=customCaveatCondition,proto3" json:"custom_caveat_condition,omitempty"`
// There are three types of messages that will be sent to the middleware for
// inspection and approval: Stream authentication, request and response
// interception. The first two can only be accepted (=forward to main RPC
// server) or denied (=return error to client). Intercepted responses can also
// be replaced/overwritten.
//
// Types that are assignable to InterceptType:
//
// *RPCMiddlewareRequest_StreamAuth
// *RPCMiddlewareRequest_Request
// *RPCMiddlewareRequest_Response
// *RPCMiddlewareRequest_RegComplete
InterceptType isRPCMiddlewareRequest_InterceptType `protobuf_oneof:"intercept_type"`
// The unique message ID of this middleware intercept message. There can be
// multiple middleware intercept messages per single gRPC request (one for the
// incoming request and one for the outgoing response) or gRPC stream (one for
// each incoming message and one for each outgoing response). This message ID
// must be referenced when responding (accepting/rejecting/modifying) to an
// intercept message.
MsgId uint64 `protobuf:"varint,7,opt,name=msg_id,json=msgId,proto3" json:"msg_id,omitempty"`
}
func (x *RPCMiddlewareRequest) Reset() {
*x = RPCMiddlewareRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[195]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RPCMiddlewareRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RPCMiddlewareRequest) ProtoMessage() {}
func (x *RPCMiddlewareRequest) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[195]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RPCMiddlewareRequest.ProtoReflect.Descriptor instead.
func (*RPCMiddlewareRequest) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{195}
}
func (x *RPCMiddlewareRequest) GetRequestId() uint64 {
if x != nil {
return x.RequestId
}
return 0
}
func (x *RPCMiddlewareRequest) GetRawMacaroon() []byte {
if x != nil {
return x.RawMacaroon
}
return nil
}
func (x *RPCMiddlewareRequest) GetCustomCaveatCondition() string {
if x != nil {
return x.CustomCaveatCondition
}
return ""
}
func (m *RPCMiddlewareRequest) GetInterceptType() isRPCMiddlewareRequest_InterceptType {
if m != nil {
return m.InterceptType
}
return nil
}
func (x *RPCMiddlewareRequest) GetStreamAuth() *StreamAuth {
if x, ok := x.GetInterceptType().(*RPCMiddlewareRequest_StreamAuth); ok {
return x.StreamAuth
}
return nil
}
func (x *RPCMiddlewareRequest) GetRequest() *RPCMessage {
if x, ok := x.GetInterceptType().(*RPCMiddlewareRequest_Request); ok {
return x.Request
}
return nil
}
func (x *RPCMiddlewareRequest) GetResponse() *RPCMessage {
if x, ok := x.GetInterceptType().(*RPCMiddlewareRequest_Response); ok {
return x.Response
}
return nil
}
func (x *RPCMiddlewareRequest) GetRegComplete() bool {
if x, ok := x.GetInterceptType().(*RPCMiddlewareRequest_RegComplete); ok {
return x.RegComplete
}
return false
}
func (x *RPCMiddlewareRequest) GetMsgId() uint64 {
if x != nil {
return x.MsgId
}
return 0
}
type isRPCMiddlewareRequest_InterceptType interface {
isRPCMiddlewareRequest_InterceptType()
}
type RPCMiddlewareRequest_StreamAuth struct {
// Intercept stream authentication: each new streaming RPC call that is
// initiated against lnd and contains the middleware's custom macaroon
// caveat can be approved or denied based upon the macaroon in the stream
// header. This message will only be sent for streaming RPCs, unary RPCs
// must handle the macaroon authentication in the request interception to
// avoid an additional message round trip between lnd and the middleware.
StreamAuth *StreamAuth `protobuf:"bytes,4,opt,name=stream_auth,json=streamAuth,proto3,oneof"`
}
type RPCMiddlewareRequest_Request struct {
// Intercept incoming gRPC client request message: all incoming messages,
// both on streaming and unary RPCs, are forwarded to the middleware for
// inspection. For unary RPC messages the middleware is also expected to
// validate the custom macaroon caveat of the request.
Request *RPCMessage `protobuf:"bytes,5,opt,name=request,proto3,oneof"`
}
type RPCMiddlewareRequest_Response struct {
// Intercept outgoing gRPC response message: all outgoing messages, both on
// streaming and unary RPCs, are forwarded to the middleware for inspection
// and amendment. The response in this message is the original response as
// it was generated by the main RPC server. It can either be accepted
// (=forwarded to the client), replaced/overwritten with a new message of
// the same type, or replaced by an error message.
Response *RPCMessage `protobuf:"bytes,6,opt,name=response,proto3,oneof"`
}
type RPCMiddlewareRequest_RegComplete struct {
// This is used to indicate to the client that the server has successfully
// registered the interceptor. This is only used in the very first message
// that the server sends to the client after the client sends the server
// the middleware registration message.
RegComplete bool `protobuf:"varint,8,opt,name=reg_complete,json=regComplete,proto3,oneof"`
}
func (*RPCMiddlewareRequest_StreamAuth) isRPCMiddlewareRequest_InterceptType() {}
func (*RPCMiddlewareRequest_Request) isRPCMiddlewareRequest_InterceptType() {}
func (*RPCMiddlewareRequest_Response) isRPCMiddlewareRequest_InterceptType() {}
func (*RPCMiddlewareRequest_RegComplete) isRPCMiddlewareRequest_InterceptType() {}
type StreamAuth struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The full URI (in the format /<rpcpackage>.<ServiceName>/MethodName, for
// example /lnrpc.Lightning/GetInfo) of the streaming RPC method that was just
// established.
MethodFullUri string `protobuf:"bytes,1,opt,name=method_full_uri,json=methodFullUri,proto3" json:"method_full_uri,omitempty"`
}
func (x *StreamAuth) Reset() {
*x = StreamAuth{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[196]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *StreamAuth) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StreamAuth) ProtoMessage() {}
func (x *StreamAuth) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[196]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StreamAuth.ProtoReflect.Descriptor instead.
func (*StreamAuth) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{196}
}
func (x *StreamAuth) GetMethodFullUri() string {
if x != nil {
return x.MethodFullUri
}
return ""
}
type RPCMessage struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The full URI (in the format /<rpcpackage>.<ServiceName>/MethodName, for
// example /lnrpc.Lightning/GetInfo) of the RPC method the message was sent
// to/from.
MethodFullUri string `protobuf:"bytes,1,opt,name=method_full_uri,json=methodFullUri,proto3" json:"method_full_uri,omitempty"`
// Indicates whether the message was sent over a streaming RPC method or not.
StreamRpc bool `protobuf:"varint,2,opt,name=stream_rpc,json=streamRpc,proto3" json:"stream_rpc,omitempty"`
// The full canonical gRPC name of the message type (in the format
// <rpcpackage>.TypeName, for example lnrpc.GetInfoRequest). In case of an
// error being returned from lnd, this simply contains the string "error".
TypeName string `protobuf:"bytes,3,opt,name=type_name,json=typeName,proto3" json:"type_name,omitempty"`
// The full content of the gRPC message, serialized in the binary protobuf
// format.
Serialized []byte `protobuf:"bytes,4,opt,name=serialized,proto3" json:"serialized,omitempty"`
// Indicates that the response from lnd was an error, not a gRPC response. If
// this is set to true then the type_name contains the string "error" and
// serialized contains the error string.
IsError bool `protobuf:"varint,5,opt,name=is_error,json=isError,proto3" json:"is_error,omitempty"`
}
func (x *RPCMessage) Reset() {
*x = RPCMessage{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[197]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RPCMessage) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RPCMessage) ProtoMessage() {}
func (x *RPCMessage) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[197]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RPCMessage.ProtoReflect.Descriptor instead.
func (*RPCMessage) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{197}
}
func (x *RPCMessage) GetMethodFullUri() string {
if x != nil {
return x.MethodFullUri
}
return ""
}
func (x *RPCMessage) GetStreamRpc() bool {
if x != nil {
return x.StreamRpc
}
return false
}
func (x *RPCMessage) GetTypeName() string {
if x != nil {
return x.TypeName
}
return ""
}
func (x *RPCMessage) GetSerialized() []byte {
if x != nil {
return x.Serialized
}
return nil
}
func (x *RPCMessage) GetIsError() bool {
if x != nil {
return x.IsError
}
return false
}
type RPCMiddlewareResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The request message ID this response refers to. Must always be set when
// giving feedback to an intercept but is ignored for the initial registration
// message.
RefMsgId uint64 `protobuf:"varint,1,opt,name=ref_msg_id,json=refMsgId,proto3" json:"ref_msg_id,omitempty"`
// The middleware can only send two types of messages to lnd: The initial
// registration message that identifies the middleware and after that only
// feedback messages to requests sent to the middleware.
//
// Types that are assignable to MiddlewareMessage:
//
// *RPCMiddlewareResponse_Register
// *RPCMiddlewareResponse_Feedback
MiddlewareMessage isRPCMiddlewareResponse_MiddlewareMessage `protobuf_oneof:"middleware_message"`
}
func (x *RPCMiddlewareResponse) Reset() {
*x = RPCMiddlewareResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[198]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RPCMiddlewareResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RPCMiddlewareResponse) ProtoMessage() {}
func (x *RPCMiddlewareResponse) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[198]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RPCMiddlewareResponse.ProtoReflect.Descriptor instead.
func (*RPCMiddlewareResponse) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{198}
}
func (x *RPCMiddlewareResponse) GetRefMsgId() uint64 {
if x != nil {
return x.RefMsgId
}
return 0
}
func (m *RPCMiddlewareResponse) GetMiddlewareMessage() isRPCMiddlewareResponse_MiddlewareMessage {
if m != nil {
return m.MiddlewareMessage
}
return nil
}
func (x *RPCMiddlewareResponse) GetRegister() *MiddlewareRegistration {
if x, ok := x.GetMiddlewareMessage().(*RPCMiddlewareResponse_Register); ok {
return x.Register
}
return nil
}
func (x *RPCMiddlewareResponse) GetFeedback() *InterceptFeedback {
if x, ok := x.GetMiddlewareMessage().(*RPCMiddlewareResponse_Feedback); ok {
return x.Feedback
}
return nil
}
type isRPCMiddlewareResponse_MiddlewareMessage interface {
isRPCMiddlewareResponse_MiddlewareMessage()
}
type RPCMiddlewareResponse_Register struct {
// The registration message identifies the middleware that's being
// registered in lnd. The registration message must be sent immediately
// after initiating the RegisterRpcMiddleware stream, otherwise lnd will
// time out the attempt and terminate the request. NOTE: The middleware
// will only receive interception messages for requests that contain a
// macaroon with the custom caveat that the middleware declares it is
// responsible for handling in the registration message! As a security
// measure, _no_ middleware can intercept requests made with _unencumbered_
// macaroons!
Register *MiddlewareRegistration `protobuf:"bytes,2,opt,name=register,proto3,oneof"`
}
type RPCMiddlewareResponse_Feedback struct {
// The middleware received an interception request and gives feedback to
// it. The request_id indicates what message the feedback refers to.
Feedback *InterceptFeedback `protobuf:"bytes,3,opt,name=feedback,proto3,oneof"`
}
func (*RPCMiddlewareResponse_Register) isRPCMiddlewareResponse_MiddlewareMessage() {}
func (*RPCMiddlewareResponse_Feedback) isRPCMiddlewareResponse_MiddlewareMessage() {}
type MiddlewareRegistration struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The name of the middleware to register. The name should be as informative
// as possible and is logged on registration.
MiddlewareName string `protobuf:"bytes,1,opt,name=middleware_name,json=middlewareName,proto3" json:"middleware_name,omitempty"`
// The name of the custom macaroon caveat that this middleware is responsible
// for. Only requests/responses that contain a macaroon with the registered
// custom caveat are forwarded for interception to the middleware. The
// exception being the read-only mode: All requests/responses are forwarded to
// a middleware that requests read-only access but such a middleware won't be
// allowed to _alter_ responses. As a security measure, _no_ middleware can
// change responses to requests made with _unencumbered_ macaroons!
// NOTE: Cannot be used at the same time as read_only_mode.
CustomMacaroonCaveatName string `protobuf:"bytes,2,opt,name=custom_macaroon_caveat_name,json=customMacaroonCaveatName,proto3" json:"custom_macaroon_caveat_name,omitempty"`
// Instead of defining a custom macaroon caveat name a middleware can register
// itself for read-only access only. In that mode all requests/responses are
// forwarded to the middleware but the middleware isn't allowed to alter any of
// the responses.
// NOTE: Cannot be used at the same time as custom_macaroon_caveat_name.
ReadOnlyMode bool `protobuf:"varint,3,opt,name=read_only_mode,json=readOnlyMode,proto3" json:"read_only_mode,omitempty"`
}
func (x *MiddlewareRegistration) Reset() {
*x = MiddlewareRegistration{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[199]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MiddlewareRegistration) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MiddlewareRegistration) ProtoMessage() {}
func (x *MiddlewareRegistration) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[199]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MiddlewareRegistration.ProtoReflect.Descriptor instead.
func (*MiddlewareRegistration) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{199}
}
func (x *MiddlewareRegistration) GetMiddlewareName() string {
if x != nil {
return x.MiddlewareName
}
return ""
}
func (x *MiddlewareRegistration) GetCustomMacaroonCaveatName() string {
if x != nil {
return x.CustomMacaroonCaveatName
}
return ""
}
func (x *MiddlewareRegistration) GetReadOnlyMode() bool {
if x != nil {
return x.ReadOnlyMode
}
return false
}
type InterceptFeedback struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The error to return to the user. If this is non-empty, the incoming gRPC
// stream/request is aborted and the error is returned to the gRPC client. If
// this value is empty, it means the middleware accepts the stream/request/
// response and the processing of it can continue.
Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"`
// A boolean indicating that the gRPC message should be replaced/overwritten.
// This boolean is needed because in protobuf an empty message is serialized as
// a 0-length or nil byte slice and we wouldn't be able to distinguish between
// an empty replacement message and the "don't replace anything" case.
ReplaceResponse bool `protobuf:"varint,2,opt,name=replace_response,json=replaceResponse,proto3" json:"replace_response,omitempty"`
// If the replace_response field is set to true, this field must contain the
// binary serialized gRPC message in the protobuf format.
ReplacementSerialized []byte `protobuf:"bytes,3,opt,name=replacement_serialized,json=replacementSerialized,proto3" json:"replacement_serialized,omitempty"`
}
func (x *InterceptFeedback) Reset() {
*x = InterceptFeedback{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[200]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *InterceptFeedback) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*InterceptFeedback) ProtoMessage() {}
func (x *InterceptFeedback) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[200]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use InterceptFeedback.ProtoReflect.Descriptor instead.
func (*InterceptFeedback) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{200}
}
func (x *InterceptFeedback) GetError() string {
if x != nil {
return x.Error
}
return ""
}
func (x *InterceptFeedback) GetReplaceResponse() bool {
if x != nil {
return x.ReplaceResponse
}
return false
}
func (x *InterceptFeedback) GetReplacementSerialized() []byte {
if x != nil {
return x.ReplacementSerialized
}
return nil
}
type PendingChannelsResponse_PendingChannel struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
RemoteNodePub string `protobuf:"bytes,1,opt,name=remote_node_pub,json=remoteNodePub,proto3" json:"remote_node_pub,omitempty"`
ChannelPoint string `protobuf:"bytes,2,opt,name=channel_point,json=channelPoint,proto3" json:"channel_point,omitempty"`
Capacity int64 `protobuf:"varint,3,opt,name=capacity,proto3" json:"capacity,omitempty"`
LocalBalance int64 `protobuf:"varint,4,opt,name=local_balance,json=localBalance,proto3" json:"local_balance,omitempty"`
RemoteBalance int64 `protobuf:"varint,5,opt,name=remote_balance,json=remoteBalance,proto3" json:"remote_balance,omitempty"`
// The minimum satoshis this node is required to reserve in its
// balance.
LocalChanReserveSat int64 `protobuf:"varint,6,opt,name=local_chan_reserve_sat,json=localChanReserveSat,proto3" json:"local_chan_reserve_sat,omitempty"`
// The minimum satoshis the other node is required to reserve in its
// balance.
RemoteChanReserveSat int64 `protobuf:"varint,7,opt,name=remote_chan_reserve_sat,json=remoteChanReserveSat,proto3" json:"remote_chan_reserve_sat,omitempty"`
// The party that initiated opening the channel.
Initiator Initiator `protobuf:"varint,8,opt,name=initiator,proto3,enum=lnrpc.Initiator" json:"initiator,omitempty"`
// The commitment type used by this channel.
CommitmentType CommitmentType `protobuf:"varint,9,opt,name=commitment_type,json=commitmentType,proto3,enum=lnrpc.CommitmentType" json:"commitment_type,omitempty"`
// Total number of forwarding packages created in this channel.
NumForwardingPackages int64 `protobuf:"varint,10,opt,name=num_forwarding_packages,json=numForwardingPackages,proto3" json:"num_forwarding_packages,omitempty"`
// A set of flags showing the current state of the channel.
ChanStatusFlags string `protobuf:"bytes,11,opt,name=chan_status_flags,json=chanStatusFlags,proto3" json:"chan_status_flags,omitempty"`
// Whether this channel is advertised to the network or not.
Private bool `protobuf:"varint,12,opt,name=private,proto3" json:"private,omitempty"`
// An optional note-to-self to go along with the channel containing some
// useful information. This is only ever stored locally and in no way
// impacts the channel's operation.
Memo string `protobuf:"bytes,13,opt,name=memo,proto3" json:"memo,omitempty"`
// Custom channel data that might be populated in custom channels.
CustomChannelData []byte `protobuf:"bytes,34,opt,name=custom_channel_data,json=customChannelData,proto3" json:"custom_channel_data,omitempty"`
}
func (x *PendingChannelsResponse_PendingChannel) Reset() {
*x = PendingChannelsResponse_PendingChannel{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[207]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PendingChannelsResponse_PendingChannel) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PendingChannelsResponse_PendingChannel) ProtoMessage() {}
func (x *PendingChannelsResponse_PendingChannel) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[207]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PendingChannelsResponse_PendingChannel.ProtoReflect.Descriptor instead.
func (*PendingChannelsResponse_PendingChannel) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{90, 0}
}
func (x *PendingChannelsResponse_PendingChannel) GetRemoteNodePub() string {
if x != nil {
return x.RemoteNodePub
}
return ""
}
func (x *PendingChannelsResponse_PendingChannel) GetChannelPoint() string {
if x != nil {
return x.ChannelPoint
}
return ""
}
func (x *PendingChannelsResponse_PendingChannel) GetCapacity() int64 {
if x != nil {
return x.Capacity
}
return 0
}
func (x *PendingChannelsResponse_PendingChannel) GetLocalBalance() int64 {
if x != nil {
return x.LocalBalance
}
return 0
}
func (x *PendingChannelsResponse_PendingChannel) GetRemoteBalance() int64 {
if x != nil {
return x.RemoteBalance
}
return 0
}
func (x *PendingChannelsResponse_PendingChannel) GetLocalChanReserveSat() int64 {
if x != nil {
return x.LocalChanReserveSat
}
return 0
}
func (x *PendingChannelsResponse_PendingChannel) GetRemoteChanReserveSat() int64 {
if x != nil {
return x.RemoteChanReserveSat
}
return 0
}
func (x *PendingChannelsResponse_PendingChannel) GetInitiator() Initiator {
if x != nil {
return x.Initiator
}
return Initiator_INITIATOR_UNKNOWN
}
func (x *PendingChannelsResponse_PendingChannel) GetCommitmentType() CommitmentType {
if x != nil {
return x.CommitmentType
}
return CommitmentType_UNKNOWN_COMMITMENT_TYPE
}
func (x *PendingChannelsResponse_PendingChannel) GetNumForwardingPackages() int64 {
if x != nil {
return x.NumForwardingPackages
}
return 0
}
func (x *PendingChannelsResponse_PendingChannel) GetChanStatusFlags() string {
if x != nil {
return x.ChanStatusFlags
}
return ""
}
func (x *PendingChannelsResponse_PendingChannel) GetPrivate() bool {
if x != nil {
return x.Private
}
return false
}
func (x *PendingChannelsResponse_PendingChannel) GetMemo() string {
if x != nil {
return x.Memo
}
return ""
}
func (x *PendingChannelsResponse_PendingChannel) GetCustomChannelData() []byte {
if x != nil {
return x.CustomChannelData
}
return nil
}
type PendingChannelsResponse_PendingOpenChannel struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The pending channel
Channel *PendingChannelsResponse_PendingChannel `protobuf:"bytes,1,opt,name=channel,proto3" json:"channel,omitempty"`
// The amount calculated to be paid in fees for the current set of
// commitment transactions. The fee amount is persisted with the channel
// in order to allow the fee amount to be removed and recalculated with
// each channel state update, including updates that happen after a system
// restart.
CommitFee int64 `protobuf:"varint,4,opt,name=commit_fee,json=commitFee,proto3" json:"commit_fee,omitempty"`
// The weight of the commitment transaction
CommitWeight int64 `protobuf:"varint,5,opt,name=commit_weight,json=commitWeight,proto3" json:"commit_weight,omitempty"`
// The required number of satoshis per kilo-weight that the requester will
// pay at all times, for both the funding transaction and commitment
// transaction. This value can later be updated once the channel is open.
FeePerKw int64 `protobuf:"varint,6,opt,name=fee_per_kw,json=feePerKw,proto3" json:"fee_per_kw,omitempty"`
// The number of blocks until the funding transaction is considered
// expired. If this value gets close to zero, there is a risk that the
// channel funding will be canceled by the channel responder. The
// channel should be fee bumped using CPFP (see walletrpc.BumpFee) to
// ensure that the channel confirms in time. Otherwise a force-close
// will be necessary if the channel confirms after the funding
// transaction expires. A negative value means the channel responder has
// very likely canceled the funding and the channel will never become
// fully operational.
FundingExpiryBlocks int32 `protobuf:"varint,3,opt,name=funding_expiry_blocks,json=fundingExpiryBlocks,proto3" json:"funding_expiry_blocks,omitempty"`
}
func (x *PendingChannelsResponse_PendingOpenChannel) Reset() {
*x = PendingChannelsResponse_PendingOpenChannel{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[208]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PendingChannelsResponse_PendingOpenChannel) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PendingChannelsResponse_PendingOpenChannel) ProtoMessage() {}
func (x *PendingChannelsResponse_PendingOpenChannel) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[208]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PendingChannelsResponse_PendingOpenChannel.ProtoReflect.Descriptor instead.
func (*PendingChannelsResponse_PendingOpenChannel) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{90, 1}
}
func (x *PendingChannelsResponse_PendingOpenChannel) GetChannel() *PendingChannelsResponse_PendingChannel {
if x != nil {
return x.Channel
}
return nil
}
func (x *PendingChannelsResponse_PendingOpenChannel) GetCommitFee() int64 {
if x != nil {
return x.CommitFee
}
return 0
}
func (x *PendingChannelsResponse_PendingOpenChannel) GetCommitWeight() int64 {
if x != nil {
return x.CommitWeight
}
return 0
}
func (x *PendingChannelsResponse_PendingOpenChannel) GetFeePerKw() int64 {
if x != nil {
return x.FeePerKw
}
return 0
}
func (x *PendingChannelsResponse_PendingOpenChannel) GetFundingExpiryBlocks() int32 {
if x != nil {
return x.FundingExpiryBlocks
}
return 0
}
type PendingChannelsResponse_WaitingCloseChannel struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The pending channel waiting for closing tx to confirm
Channel *PendingChannelsResponse_PendingChannel `protobuf:"bytes,1,opt,name=channel,proto3" json:"channel,omitempty"`
// The balance in satoshis encumbered in this channel
LimboBalance int64 `protobuf:"varint,2,opt,name=limbo_balance,json=limboBalance,proto3" json:"limbo_balance,omitempty"`
// A list of valid commitment transactions. Any of these can confirm at
// this point.
Commitments *PendingChannelsResponse_Commitments `protobuf:"bytes,3,opt,name=commitments,proto3" json:"commitments,omitempty"`
// The transaction id of the closing transaction
ClosingTxid string `protobuf:"bytes,4,opt,name=closing_txid,json=closingTxid,proto3" json:"closing_txid,omitempty"`
// The raw hex encoded bytes of the closing transaction. Included if
// include_raw_tx in the request is true.
ClosingTxHex string `protobuf:"bytes,5,opt,name=closing_tx_hex,json=closingTxHex,proto3" json:"closing_tx_hex,omitempty"`
}
func (x *PendingChannelsResponse_WaitingCloseChannel) Reset() {
*x = PendingChannelsResponse_WaitingCloseChannel{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[209]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PendingChannelsResponse_WaitingCloseChannel) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PendingChannelsResponse_WaitingCloseChannel) ProtoMessage() {}
func (x *PendingChannelsResponse_WaitingCloseChannel) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[209]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PendingChannelsResponse_WaitingCloseChannel.ProtoReflect.Descriptor instead.
func (*PendingChannelsResponse_WaitingCloseChannel) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{90, 2}
}
func (x *PendingChannelsResponse_WaitingCloseChannel) GetChannel() *PendingChannelsResponse_PendingChannel {
if x != nil {
return x.Channel
}
return nil
}
func (x *PendingChannelsResponse_WaitingCloseChannel) GetLimboBalance() int64 {
if x != nil {
return x.LimboBalance
}
return 0
}
func (x *PendingChannelsResponse_WaitingCloseChannel) GetCommitments() *PendingChannelsResponse_Commitments {
if x != nil {
return x.Commitments
}
return nil
}
func (x *PendingChannelsResponse_WaitingCloseChannel) GetClosingTxid() string {
if x != nil {
return x.ClosingTxid
}
return ""
}
func (x *PendingChannelsResponse_WaitingCloseChannel) GetClosingTxHex() string {
if x != nil {
return x.ClosingTxHex
}
return ""
}
type PendingChannelsResponse_Commitments struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Hash of the local version of the commitment tx.
LocalTxid string `protobuf:"bytes,1,opt,name=local_txid,json=localTxid,proto3" json:"local_txid,omitempty"`
// Hash of the remote version of the commitment tx.
RemoteTxid string `protobuf:"bytes,2,opt,name=remote_txid,json=remoteTxid,proto3" json:"remote_txid,omitempty"`
// Hash of the remote pending version of the commitment tx.
RemotePendingTxid string `protobuf:"bytes,3,opt,name=remote_pending_txid,json=remotePendingTxid,proto3" json:"remote_pending_txid,omitempty"`
// The amount in satoshis calculated to be paid in fees for the local
// commitment.
LocalCommitFeeSat uint64 `protobuf:"varint,4,opt,name=local_commit_fee_sat,json=localCommitFeeSat,proto3" json:"local_commit_fee_sat,omitempty"`
// The amount in satoshis calculated to be paid in fees for the remote
// commitment.
RemoteCommitFeeSat uint64 `protobuf:"varint,5,opt,name=remote_commit_fee_sat,json=remoteCommitFeeSat,proto3" json:"remote_commit_fee_sat,omitempty"`
// The amount in satoshis calculated to be paid in fees for the remote
// pending commitment.
RemotePendingCommitFeeSat uint64 `protobuf:"varint,6,opt,name=remote_pending_commit_fee_sat,json=remotePendingCommitFeeSat,proto3" json:"remote_pending_commit_fee_sat,omitempty"`
}
func (x *PendingChannelsResponse_Commitments) Reset() {
*x = PendingChannelsResponse_Commitments{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[210]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PendingChannelsResponse_Commitments) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PendingChannelsResponse_Commitments) ProtoMessage() {}
func (x *PendingChannelsResponse_Commitments) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[210]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PendingChannelsResponse_Commitments.ProtoReflect.Descriptor instead.
func (*PendingChannelsResponse_Commitments) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{90, 3}
}
func (x *PendingChannelsResponse_Commitments) GetLocalTxid() string {
if x != nil {
return x.LocalTxid
}
return ""
}
func (x *PendingChannelsResponse_Commitments) GetRemoteTxid() string {
if x != nil {
return x.RemoteTxid
}
return ""
}
func (x *PendingChannelsResponse_Commitments) GetRemotePendingTxid() string {
if x != nil {
return x.RemotePendingTxid
}
return ""
}
func (x *PendingChannelsResponse_Commitments) GetLocalCommitFeeSat() uint64 {
if x != nil {
return x.LocalCommitFeeSat
}
return 0
}
func (x *PendingChannelsResponse_Commitments) GetRemoteCommitFeeSat() uint64 {
if x != nil {
return x.RemoteCommitFeeSat
}
return 0
}
func (x *PendingChannelsResponse_Commitments) GetRemotePendingCommitFeeSat() uint64 {
if x != nil {
return x.RemotePendingCommitFeeSat
}
return 0
}
type PendingChannelsResponse_ClosedChannel struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The pending channel to be closed
Channel *PendingChannelsResponse_PendingChannel `protobuf:"bytes,1,opt,name=channel,proto3" json:"channel,omitempty"`
// The transaction id of the closing transaction
ClosingTxid string `protobuf:"bytes,2,opt,name=closing_txid,json=closingTxid,proto3" json:"closing_txid,omitempty"`
}
func (x *PendingChannelsResponse_ClosedChannel) Reset() {
*x = PendingChannelsResponse_ClosedChannel{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[211]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PendingChannelsResponse_ClosedChannel) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PendingChannelsResponse_ClosedChannel) ProtoMessage() {}
func (x *PendingChannelsResponse_ClosedChannel) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[211]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PendingChannelsResponse_ClosedChannel.ProtoReflect.Descriptor instead.
func (*PendingChannelsResponse_ClosedChannel) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{90, 4}
}
func (x *PendingChannelsResponse_ClosedChannel) GetChannel() *PendingChannelsResponse_PendingChannel {
if x != nil {
return x.Channel
}
return nil
}
func (x *PendingChannelsResponse_ClosedChannel) GetClosingTxid() string {
if x != nil {
return x.ClosingTxid
}
return ""
}
type PendingChannelsResponse_ForceClosedChannel struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The pending channel to be force closed
Channel *PendingChannelsResponse_PendingChannel `protobuf:"bytes,1,opt,name=channel,proto3" json:"channel,omitempty"`
// The transaction id of the closing transaction
ClosingTxid string `protobuf:"bytes,2,opt,name=closing_txid,json=closingTxid,proto3" json:"closing_txid,omitempty"`
// The balance in satoshis encumbered in this pending channel
LimboBalance int64 `protobuf:"varint,3,opt,name=limbo_balance,json=limboBalance,proto3" json:"limbo_balance,omitempty"`
// The height at which funds can be swept into the wallet
MaturityHeight uint32 `protobuf:"varint,4,opt,name=maturity_height,json=maturityHeight,proto3" json:"maturity_height,omitempty"`
// Remaining # of blocks until the commitment output can be swept.
// Negative values indicate how many blocks have passed since becoming
// mature.
BlocksTilMaturity int32 `protobuf:"varint,5,opt,name=blocks_til_maturity,json=blocksTilMaturity,proto3" json:"blocks_til_maturity,omitempty"`
// The total value of funds successfully recovered from this channel
RecoveredBalance int64 `protobuf:"varint,6,opt,name=recovered_balance,json=recoveredBalance,proto3" json:"recovered_balance,omitempty"`
PendingHtlcs []*PendingHTLC `protobuf:"bytes,8,rep,name=pending_htlcs,json=pendingHtlcs,proto3" json:"pending_htlcs,omitempty"`
Anchor PendingChannelsResponse_ForceClosedChannel_AnchorState `protobuf:"varint,9,opt,name=anchor,proto3,enum=lnrpc.PendingChannelsResponse_ForceClosedChannel_AnchorState" json:"anchor,omitempty"`
}
func (x *PendingChannelsResponse_ForceClosedChannel) Reset() {
*x = PendingChannelsResponse_ForceClosedChannel{}
if protoimpl.UnsafeEnabled {
mi := &file_lightning_proto_msgTypes[212]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PendingChannelsResponse_ForceClosedChannel) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PendingChannelsResponse_ForceClosedChannel) ProtoMessage() {}
func (x *PendingChannelsResponse_ForceClosedChannel) ProtoReflect() protoreflect.Message {
mi := &file_lightning_proto_msgTypes[212]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PendingChannelsResponse_ForceClosedChannel.ProtoReflect.Descriptor instead.
func (*PendingChannelsResponse_ForceClosedChannel) Descriptor() ([]byte, []int) {
return file_lightning_proto_rawDescGZIP(), []int{90, 5}
}
func (x *PendingChannelsResponse_ForceClosedChannel) GetChannel() *PendingChannelsResponse_PendingChannel {
if x != nil {
return x.Channel
}
return nil
}
func (x *PendingChannelsResponse_ForceClosedChannel) GetClosingTxid() string {
if x != nil {
return x.ClosingTxid
}
return ""
}
func (x *PendingChannelsResponse_ForceClosedChannel) GetLimboBalance() int64 {
if x != nil {
return x.LimboBalance
}
return 0
}
func (x *PendingChannelsResponse_ForceClosedChannel) GetMaturityHeight() uint32 {
if x != nil {
return x.MaturityHeight
}
return 0
}
func (x *PendingChannelsResponse_ForceClosedChannel) GetBlocksTilMaturity() int32 {
if x != nil {
return x.BlocksTilMaturity
}
return 0
}
func (x *PendingChannelsResponse_ForceClosedChannel) GetRecoveredBalance() int64 {
if x != nil {
return x.RecoveredBalance
}
return 0
}
func (x *PendingChannelsResponse_ForceClosedChannel) GetPendingHtlcs() []*PendingHTLC {
if x != nil {
return x.PendingHtlcs
}
return nil
}
func (x *PendingChannelsResponse_ForceClosedChannel) GetAnchor() PendingChannelsResponse_ForceClosedChannel_AnchorState {
if x != nil {
return x.Anchor
}
return PendingChannelsResponse_ForceClosedChannel_LIMBO
}
var File_lightning_proto protoreflect.FileDescriptor
var file_lightning_proto_rawDesc = []byte{
0x0a, 0x0f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x12, 0x05, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x22, 0x55, 0x0a, 0x1b, 0x4c, 0x6f, 0x6f, 0x6b,
0x75, 0x70, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64,
0x12, 0x1d, 0x0a, 0x0a, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02,
0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x68, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22,
0x54, 0x0a, 0x1c, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73,
0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x18, 0x0a, 0x07, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,
0x52, 0x07, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x66, 0x66,
0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6f, 0x66, 0x66,
0x63, 0x68, 0x61, 0x69, 0x6e, 0x22, 0x20, 0x0a, 0x1e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
0x62, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4b, 0x0a, 0x0d, 0x43, 0x75, 0x73, 0x74, 0x6f,
0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x65, 0x65, 0x72,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x65, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04,
0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65,
0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04,
0x64, 0x61, 0x74, 0x61, 0x22, 0x56, 0x0a, 0x18, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74,
0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x12, 0x0a, 0x04, 0x70, 0x65, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04,
0x70, 0x65, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0d, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61,
0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x33, 0x0a, 0x19,
0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61,
0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
0x73, 0x22, 0xe6, 0x01, 0x0a, 0x04, 0x55, 0x74, 0x78, 0x6f, 0x12, 0x35, 0x0a, 0x0c, 0x61, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e,
0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70,
0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x61,
0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52,
0x09, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x61, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6b,
0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70,
0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f,
0x69, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70,
0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x63, 0x6f, 0x6e,
0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xe0, 0x01, 0x0a, 0x0c, 0x4f,
0x75, 0x74, 0x70, 0x75, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x38, 0x0a, 0x0b, 0x6f,
0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e,
0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x53,
0x63, 0x72, 0x69, 0x70, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x6f, 0x75, 0x74, 0x70, 0x75,
0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12,
0x1b, 0x0a, 0x09, 0x70, 0x6b, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x03, 0x20, 0x01,
0x28, 0x09, 0x52, 0x08, 0x70, 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x21, 0x0a, 0x0c,
0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01,
0x28, 0x03, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12,
0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52,
0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x69, 0x73, 0x5f, 0x6f, 0x75,
0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52,
0x0c, 0x69, 0x73, 0x4f, 0x75, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0xce, 0x03,
0x0a, 0x0b, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a,
0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74,
0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2b,
0x0a, 0x11, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x6e, 0x75, 0x6d, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x62,
0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c,
0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05,
0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d, 0x0a,
0x0a, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28,
0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1d, 0x0a, 0x0a,
0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03,
0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x46, 0x65, 0x65, 0x73, 0x12, 0x29, 0x0a, 0x0e, 0x64,
0x65, 0x73, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x08, 0x20,
0x03, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0d, 0x64, 0x65, 0x73, 0x74, 0x41, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0e, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74,
0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13,
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x44, 0x65, 0x74,
0x61, 0x69, 0x6c, 0x52, 0x0d, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69,
0x6c, 0x73, 0x12, 0x1c, 0x0a, 0x0a, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x5f, 0x68, 0x65, 0x78,
0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x61, 0x77, 0x54, 0x78, 0x48, 0x65, 0x78,
0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52,
0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x46, 0x0a, 0x12, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f,
0x75, 0x73, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x0c, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x72, 0x65, 0x76, 0x69,
0x6f, 0x75, 0x73, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x11, 0x70, 0x72, 0x65,
0x76, 0x69, 0x6f, 0x75, 0x73, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x22, 0xc2,
0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x74, 0x61,
0x72, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52,
0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d, 0x0a, 0x0a,
0x65, 0x6e, 0x64, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05,
0x52, 0x09, 0x65, 0x6e, 0x64, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61,
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f,
0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x69, 0x6e, 0x64,
0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x6d, 0x61, 0x78, 0x5f,
0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x01,
0x28, 0x0d, 0x52, 0x0f, 0x6d, 0x61, 0x78, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x73, 0x22, 0x8c, 0x01, 0x0a, 0x12, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x36, 0x0a, 0x0c, 0x74, 0x72,
0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78,
0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65,
0x78, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78,
0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x66, 0x69, 0x72, 0x73, 0x74, 0x49, 0x6e, 0x64,
0x65, 0x78, 0x22, 0x68, 0x0a, 0x08, 0x46, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16,
0x0a, 0x05, 0x66, 0x69, 0x78, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52,
0x05, 0x66, 0x69, 0x78, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0a, 0x66, 0x69, 0x78, 0x65, 0x64, 0x5f,
0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x09, 0x66, 0x69,
0x78, 0x65, 0x64, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1a, 0x0a, 0x07, 0x70, 0x65, 0x72, 0x63, 0x65,
0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x07, 0x70, 0x65, 0x72, 0x63,
0x65, 0x6e, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0xea, 0x05, 0x0a,
0x0b, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04,
0x64, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74,
0x12, 0x23, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x64, 0x65, 0x73, 0x74, 0x53,
0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6d, 0x74, 0x18, 0x03, 0x20, 0x01,
0x28, 0x03, 0x52, 0x03, 0x61, 0x6d, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x6d,
0x73, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x6d, 0x74, 0x4d, 0x73,
0x61, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61,
0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e,
0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x32, 0x0a, 0x13, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74,
0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x05, 0x20, 0x01,
0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x11, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48,
0x61, 0x73, 0x68, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x79,
0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x06, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x63, 0x6c, 0x74, 0x76,
0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x66, 0x69,
0x6e, 0x61, 0x6c, 0x43, 0x6c, 0x74, 0x76, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x2c, 0x0a, 0x09,
0x66, 0x65, 0x65, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74,
0x52, 0x08, 0x66, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2c, 0x0a, 0x10, 0x6f, 0x75,
0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x09,
0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69,
0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x6c, 0x61, 0x73, 0x74,
0x5f, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x0d, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79,
0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x0a,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x63, 0x6c, 0x74, 0x76, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12,
0x59, 0x0a, 0x13, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72,
0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x2e, 0x44, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72,
0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x64, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73,
0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x6c,
0x6c, 0x6f, 0x77, 0x5f, 0x73, 0x65, 0x6c, 0x66, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74,
0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x53, 0x65, 0x6c,
0x66, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x0d, 0x64, 0x65, 0x73, 0x74,
0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0e, 0x32,
0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42,
0x69, 0x74, 0x52, 0x0c, 0x64, 0x65, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73,
0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72,
0x18, 0x10, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41,
0x64, 0x64, 0x72, 0x1a, 0x44, 0x0a, 0x16, 0x44, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f,
0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a,
0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb4, 0x01, 0x0a, 0x0c, 0x53, 0x65,
0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x61,
0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12,
0x29, 0x0a, 0x10, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x65, 0x69, 0x6d,
0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65,
0x6e, 0x74, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x31, 0x0a, 0x0d, 0x70, 0x61,
0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52,
0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x21, 0x0a,
0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68,
0x22, 0x95, 0x01, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65,
0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70,
0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x32, 0x0a, 0x13, 0x70, 0x61,
0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e,
0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x11, 0x70, 0x61, 0x79,
0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x22,
0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x05, 0x72, 0x6f, 0x75,
0x74, 0x65, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0xec, 0x04, 0x0a, 0x14, 0x43, 0x68, 0x61,
0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x50, 0x75, 0x62, 0x6b,
0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x48, 0x61, 0x73,
0x68, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61,
0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64,
0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e,
0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a,
0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x75,
0x73, 0x68, 0x5f, 0x61, 0x6d, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x70, 0x75,
0x73, 0x68, 0x41, 0x6d, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x75, 0x73, 0x74, 0x5f, 0x6c, 0x69,
0x6d, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x64, 0x75, 0x73, 0x74, 0x4c,
0x69, 0x6d, 0x69, 0x74, 0x12, 0x2d, 0x0a, 0x13, 0x6d, 0x61, 0x78, 0x5f, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x5f, 0x69, 0x6e, 0x5f, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28,
0x04, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x49, 0x6e, 0x46, 0x6c, 0x69,
0x67, 0x68, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x72,
0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x63, 0x68,
0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x12, 0x19, 0x0a, 0x08,
0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07,
0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x12, 0x1c, 0x0a, 0x0a, 0x66, 0x65, 0x65, 0x5f, 0x70,
0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x66, 0x65, 0x65,
0x50, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x73, 0x76, 0x5f, 0x64, 0x65, 0x6c,
0x61, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x63, 0x73, 0x76, 0x44, 0x65, 0x6c,
0x61, 0x79, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74,
0x65, 0x64, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10,
0x6d, 0x61, 0x78, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x73,
0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x6c, 0x61, 0x67,
0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d,
0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15,
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e,
0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e,
0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x77, 0x61, 0x6e, 0x74, 0x73, 0x5f, 0x7a,
0x65, 0x72, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d,
0x77, 0x61, 0x6e, 0x74, 0x73, 0x5a, 0x65, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x28, 0x0a,
0x10, 0x77, 0x61, 0x6e, 0x74, 0x73, 0x5f, 0x73, 0x63, 0x69, 0x64, 0x5f, 0x61, 0x6c, 0x69, 0x61,
0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x77, 0x61, 0x6e, 0x74, 0x73, 0x53, 0x63,
0x69, 0x64, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x22, 0x90, 0x03, 0x0a, 0x15, 0x43, 0x68, 0x61, 0x6e,
0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
0x08, 0x52, 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e,
0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49,
0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x75, 0x70, 0x66, 0x72, 0x6f,
0x6e, 0x74, 0x5f, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0f, 0x75, 0x70, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f,
0x77, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x73, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18,
0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x63, 0x73, 0x76, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12,
0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x06,
0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74,
0x12, 0x2b, 0x0a, 0x12, 0x69, 0x6e, 0x5f, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x6d, 0x61,
0x78, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x69, 0x6e,
0x46, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x4d, 0x61, 0x78, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x24, 0x0a,
0x0e, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18,
0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, 0x43, 0x6f,
0x75, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f,
0x69, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c,
0x63, 0x49, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x69, 0x6e, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x70,
0x74, 0x5f, 0x64, 0x65, 0x70, 0x74, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x6d,
0x69, 0x6e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x44, 0x65, 0x70, 0x74, 0x68, 0x12, 0x1b, 0x0a,
0x09, 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08,
0x52, 0x08, 0x7a, 0x65, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x66, 0x22, 0x9d, 0x01, 0x0a, 0x0c, 0x43,
0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x12, 0x66,
0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65,
0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x10, 0x66, 0x75, 0x6e, 0x64, 0x69,
0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x66,
0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x5f, 0x73, 0x74, 0x72, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0e, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67,
0x54, 0x78, 0x69, 0x64, 0x53, 0x74, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75,
0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6f,
0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x0e, 0x0a, 0x0c, 0x66, 0x75,
0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x22, 0x67, 0x0a, 0x08, 0x4f, 0x75,
0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x78, 0x69, 0x64, 0x5f, 0x62,
0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x74, 0x78, 0x69, 0x64,
0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x69, 0x64, 0x5f, 0x73, 0x74,
0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x78, 0x69, 0x64, 0x53, 0x74, 0x72,
0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78,
0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e,
0x64, 0x65, 0x78, 0x22, 0x52, 0x0a, 0x10, 0x50, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x4f,
0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f,
0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f,
0x69, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x69, 0x73, 0x5f, 0x6f, 0x75, 0x72, 0x5f, 0x6f, 0x75,
0x74, 0x70, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, 0x4f, 0x75,
0x72, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x3e, 0x0a, 0x10, 0x4c, 0x69, 0x67, 0x68, 0x74,
0x6e, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x70,
0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62,
0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x22, 0xe7, 0x02, 0x0a, 0x12, 0x45, 0x73, 0x74, 0x69,
0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4f,
0x0a, 0x0c, 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74,
0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e,
0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72,
0x79, 0x52, 0x0c, 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12,
0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x02,
0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66,
0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x03, 0x20,
0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a,
0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d,
0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55,
0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x54, 0x0a, 0x17, 0x63, 0x6f,
0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72,
0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x6c, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x15, 0x63, 0x6f, 0x69, 0x6e, 0x53,
0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
0x1a, 0x3f, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74,
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
0x01, 0x22, 0x87, 0x01, 0x0a, 0x13, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65,
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x65, 0x65,
0x5f, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x66, 0x65, 0x65, 0x53,
0x61, 0x74, 0x12, 0x33, 0x0a, 0x14, 0x66, 0x65, 0x65, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x61,
0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03,
0x42, 0x02, 0x18, 0x01, 0x52, 0x11, 0x66, 0x65, 0x65, 0x72, 0x61, 0x74, 0x65, 0x53, 0x61, 0x74,
0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70,
0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b,
0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x22, 0xc1, 0x03, 0x0a, 0x0f,
0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x4c, 0x0a, 0x0c, 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18,
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65,
0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x64,
0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52,
0x0c, 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a,
0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x03, 0x20, 0x01,
0x28, 0x05, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x22,
0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18,
0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79,
0x74, 0x65, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79,
0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61,
0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65,
0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1b,
0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28,
0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73,
0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64,
0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63,
0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x54, 0x0a, 0x17, 0x63, 0x6f, 0x69, 0x6e,
0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74,
0x65, 0x67, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53,
0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x15, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c,
0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x1a, 0x3f,
0x0a, 0x11, 0x41, 0x64, 0x64, 0x72, 0x54, 0x6f, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6e,
0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22,
0x26, 0x0a, 0x10, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x22, 0xa9, 0x03, 0x0a, 0x10, 0x53, 0x65, 0x6e, 0x64,
0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04,
0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72,
0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03,
0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67,
0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74,
0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74,
0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04,
0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x24, 0x0a,
0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x05, 0x20,
0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42,
0x79, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x61, 0x6c, 0x6c, 0x18,
0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x65, 0x6e, 0x64, 0x41, 0x6c, 0x6c, 0x12, 0x14,
0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c,
0x61, 0x62, 0x65, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66,
0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66,
0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e,
0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70,
0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x54,
0x0a, 0x17, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65,
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x15, 0x63,
0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61,
0x74, 0x65, 0x67, 0x79, 0x12, 0x2d, 0x0a, 0x09, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74,
0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69,
0x6e, 0x74, 0x73, 0x22, 0x27, 0x0a, 0x11, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x22, 0x68, 0x0a, 0x12,
0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18,
0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12,
0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x02, 0x20, 0x01,
0x28, 0x05, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x18, 0x0a, 0x07,
0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61,
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x38, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e,
0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a,
0x05, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x52, 0x05, 0x75, 0x74, 0x78, 0x6f, 0x73,
0x22, 0x55, 0x0a, 0x11, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a,
0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x2e, 0x0a, 0x12, 0x4e, 0x65, 0x77, 0x41, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a,
0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x47, 0x0a, 0x12, 0x53, 0x69, 0x67, 0x6e, 0x4d,
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a,
0x03, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12,
0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02,
0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x48, 0x61, 0x73, 0x68,
0x22, 0x33, 0x0a, 0x13, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61,
0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e,
0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x46, 0x0a, 0x14, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d,
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a,
0x03, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12,
0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x45, 0x0a,
0x15, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06,
0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75,
0x62, 0x6b, 0x65, 0x79, 0x22, 0x6f, 0x0a, 0x12, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50,
0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x04, 0x61, 0x64,
0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x65, 0x72, 0x6d, 0x18,
0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x70, 0x65, 0x72, 0x6d, 0x12, 0x18, 0x0a, 0x07, 0x74,
0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x74, 0x69,
0x6d, 0x65, 0x6f, 0x75, 0x74, 0x22, 0x2d, 0x0a, 0x13, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74,
0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06,
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74,
0x61, 0x74, 0x75, 0x73, 0x22, 0x30, 0x0a, 0x15, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65,
0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a,
0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x22, 0x30, 0x0a, 0x16, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e,
0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xa3, 0x02, 0x0a, 0x04, 0x48, 0x54, 0x4c,
0x43, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20,
0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x16, 0x0a,
0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61,
0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x6c, 0x6f,
0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x68, 0x61, 0x73, 0x68, 0x4c, 0x6f,
0x63, 0x6b, 0x12, 0x2b, 0x0a, 0x11, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x65,
0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12,
0x1d, 0x0a, 0x0a, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x05, 0x20,
0x01, 0x28, 0x04, 0x52, 0x09, 0x68, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2d,
0x0a, 0x12, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61,
0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x66, 0x6f, 0x72, 0x77,
0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x32, 0x0a,
0x15, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x74, 0x6c, 0x63,
0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x13, 0x66, 0x6f,
0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x64, 0x65,
0x78, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x18, 0x08,
0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x49, 0x6e, 0x22, 0x84,
0x02, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72,
0x61, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x73, 0x76, 0x5f, 0x64, 0x65, 0x6c,
0x61, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x63, 0x73, 0x76, 0x44, 0x65, 0x6c,
0x61, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72,
0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x63, 0x68,
0x61, 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x24, 0x0a, 0x0e,
0x64, 0x75, 0x73, 0x74, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x03,
0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x64, 0x75, 0x73, 0x74, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x53,
0x61, 0x74, 0x12, 0x2f, 0x0a, 0x14, 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e,
0x67, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04,
0x52, 0x11, 0x6d, 0x61, 0x78, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x74, 0x4d,
0x73, 0x61, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f,
0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x48,
0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, 0x61,
0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x06, 0x20,
0x01, 0x28, 0x0d, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64,
0x48, 0x74, 0x6c, 0x63, 0x73, 0x22, 0xdd, 0x0b, 0x0a, 0x07, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
0x6c, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x08, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6d,
0x6f, 0x74, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x23,
0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18,
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f,
0x69, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04,
0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64,
0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01,
0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x23, 0x0a, 0x0d,
0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20,
0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63,
0x65, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x62, 0x61, 0x6c, 0x61,
0x6e, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x74,
0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d,
0x69, 0x74, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f,
0x6d, 0x6d, 0x69, 0x74, 0x46, 0x65, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x6d, 0x69,
0x74, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c,
0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1c, 0x0a, 0x0a,
0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03,
0x52, 0x08, 0x66, 0x65, 0x65, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x2b, 0x0a, 0x11, 0x75, 0x6e,
0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18,
0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x75, 0x6e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64,
0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c,
0x5f, 0x73, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x73, 0x5f, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x0c,
0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x53, 0x61, 0x74, 0x6f, 0x73,
0x68, 0x69, 0x73, 0x53, 0x65, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x17, 0x74, 0x6f, 0x74, 0x61, 0x6c,
0x5f, 0x73, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76,
0x65, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x53,
0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x73, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x12,
0x1f, 0x0a, 0x0b, 0x6e, 0x75, 0x6d, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x0e,
0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x6e, 0x75, 0x6d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73,
0x12, 0x30, 0x0a, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x74, 0x6c, 0x63,
0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x48, 0x54, 0x4c, 0x43, 0x52, 0x0c, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c,
0x63, 0x73, 0x12, 0x1f, 0x0a, 0x09, 0x63, 0x73, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18,
0x10, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x63, 0x73, 0x76, 0x44, 0x65,
0x6c, 0x61, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x11,
0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a,
0x09, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08,
0x52, 0x09, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x2a, 0x0a, 0x11, 0x63,
0x68, 0x61, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73,
0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74,
0x75, 0x73, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x37, 0x0a, 0x16, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61,
0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x13, 0x6c, 0x6f, 0x63,
0x61, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74,
0x12, 0x39, 0x0a, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f,
0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x15, 0x20, 0x01, 0x28,
0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x14, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61,
0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x2e, 0x0a, 0x11, 0x73,
0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79,
0x18, 0x16, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0f, 0x73, 0x74, 0x61, 0x74,
0x69, 0x63, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x3e, 0x0a, 0x0f, 0x63,
0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x1a,
0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d,
0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d,
0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6c,
0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x17, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6c,
0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d,
0x65, 0x18, 0x18, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x12,
0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x18, 0x19, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x75, 0x73, 0x68, 0x5f, 0x61, 0x6d, 0x6f,
0x75, 0x6e, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x70,
0x75, 0x73, 0x68, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x61, 0x74, 0x12, 0x1f, 0x0a, 0x0b,
0x74, 0x68, 0x61, 0x77, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x1c, 0x20, 0x01, 0x28,
0x0d, 0x52, 0x0a, 0x74, 0x68, 0x61, 0x77, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x46, 0x0a,
0x11, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e,
0x74, 0x73, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69,
0x6e, 0x74, 0x73, 0x52, 0x10, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72,
0x61, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x48, 0x0a, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f,
0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x1e, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
0x6c, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x52, 0x11, 0x72, 0x65,
0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x12,
0x1f, 0x0a, 0x0b, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x73, 0x63, 0x69, 0x64, 0x73, 0x18, 0x1f,
0x20, 0x03, 0x28, 0x04, 0x52, 0x0a, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x53, 0x63, 0x69, 0x64, 0x73,
0x12, 0x1b, 0x0a, 0x09, 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x20, 0x20,
0x01, 0x28, 0x08, 0x52, 0x08, 0x7a, 0x65, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x37, 0x0a,
0x18, 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69,
0x72, 0x6d, 0x65, 0x64, 0x5f, 0x73, 0x63, 0x69, 0x64, 0x18, 0x21, 0x20, 0x01, 0x28, 0x04, 0x52,
0x15, 0x7a, 0x65, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x66, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d,
0x65, 0x64, 0x53, 0x63, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x61,
0x6c, 0x69, 0x61, 0x73, 0x18, 0x22, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x65, 0x65, 0x72,
0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x2a, 0x0a, 0x0f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x73, 0x63,
0x69, 0x64, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x23, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02,
0x30, 0x01, 0x52, 0x0d, 0x70, 0x65, 0x65, 0x72, 0x53, 0x63, 0x69, 0x64, 0x41, 0x6c, 0x69, 0x61,
0x73, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x18, 0x24, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f,
0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x25, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
0x6c, 0x44, 0x61, 0x74, 0x61, 0x22, 0xdf, 0x01, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68,
0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a,
0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x08, 0x52, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x23,
0x0a, 0x0d, 0x69, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18,
0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4f,
0x6e, 0x6c, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6f, 0x6e,
0x6c, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63,
0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f,
0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x70, 0x72, 0x69, 0x76,
0x61, 0x74, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x65, 0x65, 0x72, 0x18,
0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x65, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x11, 0x70,
0x65, 0x65, 0x72, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70,
0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x70, 0x65, 0x65, 0x72, 0x41, 0x6c, 0x69, 0x61,
0x73, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x22, 0x42, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x43,
0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x2a, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
0x6c, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0x41, 0x0a, 0x08, 0x41,
0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x61, 0x73, 0x65, 0x5f,
0x73, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x62, 0x61, 0x73, 0x65,
0x53, 0x63, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x18,
0x02, 0x20, 0x03, 0x28, 0x04, 0x52, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x22, 0x14,
0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x22, 0x45, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, 0x61,
0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x0a, 0x61,
0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70,
0x52, 0x09, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x73, 0x22, 0xe6, 0x06, 0x0a, 0x13,
0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x75, 0x6d, 0x6d,
0x61, 0x72, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70,
0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e,
0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e,
0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63,
0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68,
0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e,
0x48, 0x61, 0x73, 0x68, 0x12, 0x26, 0x0a, 0x0f, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f,
0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63,
0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x23, 0x0a, 0x0d,
0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6b, 0x65,
0x79, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20,
0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x21, 0x0a,
0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x07, 0x20,
0x01, 0x28, 0x0d, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74,
0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61,
0x6e, 0x63, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x65, 0x74, 0x74, 0x6c,
0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x74, 0x69, 0x6d,
0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65,
0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b,
0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x0a, 0x63, 0x6c, 0x6f,
0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6c, 0x6f,
0x73, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x75, 0x72,
0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65,
0x12, 0x37, 0x0a, 0x0e, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74,
0x6f, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x0d, 0x6f, 0x70, 0x65, 0x6e,
0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x39, 0x0a, 0x0f, 0x63, 0x6c, 0x6f,
0x73, 0x65, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x0c, 0x20, 0x01,
0x28, 0x0e, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69,
0x61, 0x74, 0x6f, 0x72, 0x52, 0x0e, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x49, 0x6e, 0x69, 0x74, 0x69,
0x61, 0x74, 0x6f, 0x72, 0x12, 0x33, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69,
0x6f, 0x6e, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x72, 0x65,
0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x6c, 0x69,
0x61, 0x73, 0x5f, 0x73, 0x63, 0x69, 0x64, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x04, 0x52, 0x0a,
0x61, 0x6c, 0x69, 0x61, 0x73, 0x53, 0x63, 0x69, 0x64, 0x73, 0x12, 0x3b, 0x0a, 0x18, 0x7a, 0x65,
0x72, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65,
0x64, 0x5f, 0x73, 0x63, 0x69, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01,
0x52, 0x15, 0x7a, 0x65, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x66, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72,
0x6d, 0x65, 0x64, 0x53, 0x63, 0x69, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f,
0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x10,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x6e,
0x6e, 0x65, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x22, 0x8a, 0x01, 0x0a, 0x0b, 0x43, 0x6c, 0x6f, 0x73,
0x75, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4f, 0x50, 0x45,
0x52, 0x41, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x00, 0x12, 0x15,
0x0a, 0x11, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x5f, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x43, 0x4c,
0x4f, 0x53, 0x45, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f,
0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x02, 0x12, 0x10, 0x0a,
0x0c, 0x42, 0x52, 0x45, 0x41, 0x43, 0x48, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x03, 0x12,
0x14, 0x0a, 0x10, 0x46, 0x55, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45,
0x4c, 0x45, 0x44, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x41, 0x42, 0x41, 0x4e, 0x44, 0x4f, 0x4e,
0x45, 0x44, 0x10, 0x05, 0x22, 0xeb, 0x01, 0x0a, 0x0a, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74,
0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f,
0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x54,
0x79, 0x70, 0x65, 0x52, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x54,
0x79, 0x70, 0x65, 0x12, 0x32, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x63, 0x6f, 0x6d, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73,
0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x63, 0x6f, 0x6d, 0x65, 0x52, 0x07,
0x6f, 0x75, 0x74, 0x63, 0x6f, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f,
0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70,
0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73,
0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74,
0x53, 0x61, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x74, 0x78, 0x69,
0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x77, 0x65, 0x65, 0x70, 0x54, 0x78,
0x69, 0x64, 0x22, 0xde, 0x01, 0x0a, 0x15, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61,
0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b,
0x63, 0x6f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x08, 0x52, 0x0b, 0x63, 0x6f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x1f,
0x0a, 0x0b, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x08, 0x52, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x12,
0x21, 0x0a, 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18,
0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x46, 0x6f, 0x72,
0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x72, 0x65, 0x61, 0x63, 0x68, 0x18, 0x04, 0x20, 0x01,
0x28, 0x08, 0x52, 0x06, 0x62, 0x72, 0x65, 0x61, 0x63, 0x68, 0x12, 0x29, 0x0a, 0x10, 0x66, 0x75,
0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x18, 0x05,
0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x61, 0x6e,
0x63, 0x65, 0x6c, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e,
0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x62, 0x61, 0x6e, 0x64, 0x6f,
0x6e, 0x65, 0x64, 0x22, 0x50, 0x0a, 0x16, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61,
0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a,
0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43,
0x6c, 0x6f, 0x73, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x08, 0x63, 0x68, 0x61,
0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0x8b, 0x05, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x17,
0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x73, 0x65, 0x6e, 0x74, 0x18,
0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x62, 0x79, 0x74, 0x65, 0x73, 0x53, 0x65, 0x6e, 0x74,
0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x63, 0x76, 0x18, 0x05,
0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x62, 0x79, 0x74, 0x65, 0x73, 0x52, 0x65, 0x63, 0x76, 0x12,
0x19, 0x0a, 0x08, 0x73, 0x61, 0x74, 0x5f, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28,
0x03, 0x52, 0x07, 0x73, 0x61, 0x74, 0x53, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x61,
0x74, 0x5f, 0x72, 0x65, 0x63, 0x76, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x73, 0x61,
0x74, 0x52, 0x65, 0x63, 0x76, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64,
0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12,
0x1b, 0x0a, 0x09, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01,
0x28, 0x03, 0x52, 0x08, 0x70, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x31, 0x0a, 0x09,
0x73, 0x79, 0x6e, 0x63, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x2e, 0x53, 0x79, 0x6e,
0x63, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12,
0x35, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x2e, 0x46,
0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65,
0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73,
0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54,
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x65, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52,
0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x6c, 0x61, 0x70, 0x5f,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x66, 0x6c, 0x61,
0x70, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x66,
0x6c, 0x61, 0x70, 0x5f, 0x6e, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x6c, 0x61,
0x73, 0x74, 0x46, 0x6c, 0x61, 0x70, 0x4e, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74,
0x5f, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x0f, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x50, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x79,
0x6c, 0x6f, 0x61, 0x64, 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73,
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46,
0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
0x01, 0x22, 0x50, 0x0a, 0x08, 0x53, 0x79, 0x6e, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a,
0x0c, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x10, 0x00, 0x12,
0x0f, 0x0a, 0x0b, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x10, 0x01,
0x12, 0x10, 0x0a, 0x0c, 0x50, 0x41, 0x53, 0x53, 0x49, 0x56, 0x45, 0x5f, 0x53, 0x59, 0x4e, 0x43,
0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x49, 0x4e, 0x4e, 0x45, 0x44, 0x5f, 0x53, 0x59, 0x4e,
0x43, 0x10, 0x03, 0x22, 0x46, 0x0a, 0x10, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
0x65, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73,
0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65,
0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x35, 0x0a, 0x10, 0x4c,
0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x21, 0x0a, 0x0c, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18,
0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x45, 0x72, 0x72,
0x6f, 0x72, 0x22, 0x36, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73,
0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50,
0x65, 0x65, 0x72, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0x17, 0x0a, 0x15, 0x50, 0x65,
0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x69, 0x6f, 0x6e, 0x22, 0x84, 0x01, 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e,
0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x79,
0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74,
0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2e, 0x0a, 0x09, 0x45, 0x76,
0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x45, 0x45, 0x52, 0x5f,
0x4f, 0x4e, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x45, 0x45, 0x52,
0x5f, 0x4f, 0x46, 0x46, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x01, 0x22, 0x10, 0x0a, 0x0e, 0x47, 0x65,
0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x82, 0x07, 0x0a,
0x0f, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0e, 0x20, 0x01, 0x28,
0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f,
0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x27, 0x0a, 0x0f, 0x69,
0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x50, 0x75,
0x62, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f,
0x6c, 0x6f, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72,
0x12, 0x30, 0x0a, 0x14, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f,
0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12,
0x6e, 0x75, 0x6d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
0x6c, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x75, 0x6d, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65,
0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52,
0x11, 0x6e, 0x75, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
0x6c, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x6e, 0x75, 0x6d, 0x5f, 0x69, 0x6e, 0x61, 0x63, 0x74, 0x69,
0x76, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28,
0x0d, 0x52, 0x13, 0x6e, 0x75, 0x6d, 0x49, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x68,
0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x65,
0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6e, 0x75, 0x6d, 0x50, 0x65,
0x65, 0x72, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69,
0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b,
0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f,
0x68, 0x61, 0x73, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63,
0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x32, 0x0a, 0x15, 0x62, 0x65, 0x73, 0x74, 0x5f, 0x68, 0x65,
0x61, 0x64, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0d,
0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x62, 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72,
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x79, 0x6e,
0x63, 0x65, 0x64, 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x09, 0x20, 0x01,
0x28, 0x08, 0x52, 0x0d, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x54, 0x6f, 0x43, 0x68, 0x61, 0x69,
0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x5f, 0x74, 0x6f, 0x5f, 0x67,
0x72, 0x61, 0x70, 0x68, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x79, 0x6e, 0x63,
0x65, 0x64, 0x54, 0x6f, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x1c, 0x0a, 0x07, 0x74, 0x65, 0x73,
0x74, 0x6e, 0x65, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07,
0x74, 0x65, 0x73, 0x74, 0x6e, 0x65, 0x74, 0x12, 0x24, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x69, 0x6e,
0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x43, 0x68, 0x61, 0x69, 0x6e, 0x52, 0x06, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x12, 0x0a,
0x04, 0x75, 0x72, 0x69, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x75, 0x72, 0x69,
0x73, 0x12, 0x40, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x13, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49,
0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74,
0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75,
0x72, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x5f, 0x68,
0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x18,
0x15, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x48, 0x74,
0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x3f, 0x0a,
0x1c, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x68, 0x74, 0x6c,
0x63, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x16, 0x20,
0x01, 0x28, 0x08, 0x52, 0x19, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x48,
0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x4b,
0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12,
0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65,
0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65,
0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, 0x08, 0x0b, 0x10,
0x0c, 0x22, 0x15, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66,
0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa4, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74,
0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x3f, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x27, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x62,
0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66,
0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52,
0x03, 0x6c, 0x6f, 0x67, 0x1a, 0x39, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e,
0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22,
0x18, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e,
0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x87, 0x01, 0x0a, 0x17, 0x47, 0x65,
0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72,
0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x72, 0x65,
0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65,
0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18,
0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x46,
0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72,
0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72,
0x65, 0x73, 0x73, 0x22, 0x3b, 0x0a, 0x05, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x18, 0x0a, 0x05,
0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52,
0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72,
0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
0x22, 0x7a, 0x0a, 0x12, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f,
0x73, 0x68, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x62, 0x6c, 0x6f, 0x63, 0x6b,
0x53, 0x68, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69,
0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b,
0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x6f,
0x6e, 0x66, 0x73, 0x5f, 0x6c, 0x65, 0x66, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c,
0x6e, 0x75, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x4c, 0x65, 0x66, 0x74, 0x22, 0x4d, 0x0a, 0x11,
0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4f, 0x70, 0x65, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x12, 0x38, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69,
0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0c, 0x63,
0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x94, 0x01, 0x0a, 0x0b,
0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x61,
0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52,
0x09, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x61, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6b,
0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70,
0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x6c, 0x6f,
0x63, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x4c, 0x6f, 0x63,
0x61, 0x6c, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x68, 0x61,
0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x44, 0x61,
0x74, 0x61, 0x22, 0x9a, 0x02, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6c,
0x6f, 0x73, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f,
0x73, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07,
0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73,
0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x40, 0x0a, 0x12, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f,
0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65,
0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x10, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x6c, 0x6f,
0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x42, 0x0a, 0x13, 0x72, 0x65, 0x6d, 0x6f,
0x74, 0x65, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18,
0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c,
0x6f, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x11, 0x72, 0x65, 0x6d, 0x6f, 0x74,
0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x41, 0x0a, 0x12,
0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75,
0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x11, 0x61, 0x64,
0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x22,
0xbf, 0x02, 0x0a, 0x13, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e,
0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13,
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f,
0x69, 0x6e, 0x74, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e,
0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08,
0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65,
0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x61,
0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f,
0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02,
0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x29,
0x0a, 0x10, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65,
0x72, 0x79, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74,
0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04,
0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x29, 0x0a,
0x11, 0x6d, 0x61, 0x78, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79,
0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x46, 0x65, 0x65,
0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x5f, 0x77,
0x61, 0x69, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6e, 0x6f, 0x57, 0x61, 0x69,
0x74, 0x22, 0xd3, 0x01, 0x0a, 0x11, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75,
0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x3b, 0x0a, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65,
0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14,
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x55, 0x70,
0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x50, 0x65, 0x6e,
0x64, 0x69, 0x6e, 0x67, 0x12, 0x3a, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x63, 0x6c, 0x6f,
0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x55, 0x70, 0x64,
0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x43, 0x6c, 0x6f, 0x73, 0x65,
0x12, 0x3b, 0x0a, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e,
0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52,
0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x42, 0x08, 0x0a,
0x06, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x22, 0x90, 0x01, 0x0a, 0x0d, 0x50, 0x65, 0x6e, 0x64,
0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x21, 0x0a,
0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78,
0x12, 0x22, 0x0a, 0x0d, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74,
0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x66, 0x65, 0x65, 0x50, 0x65, 0x72, 0x56,
0x62, 0x79, 0x74, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x6c,
0x6f, 0x73, 0x65, 0x5f, 0x74, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6c, 0x6f,
0x63, 0x61, 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x54, 0x78, 0x22, 0x3b, 0x0a, 0x0d, 0x49, 0x6e,
0x73, 0x74, 0x61, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x6e,
0x75, 0x6d, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73,
0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x6e, 0x75, 0x6d, 0x50, 0x65, 0x6e, 0x64, 0x69,
0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x22, 0x79, 0x0a, 0x13, 0x52, 0x65, 0x61, 0x64, 0x79,
0x46, 0x6f, 0x72, 0x50, 0x73, 0x62, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x27,
0x0a, 0x0f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67,
0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x75, 0x6e, 0x64, 0x69,
0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52,
0x0d, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12,
0x0a, 0x04, 0x70, 0x73, 0x62, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x73,
0x62, 0x74, 0x22, 0xc9, 0x02, 0x0a, 0x17, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e,
0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33,
0x0a, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70,
0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e,
0x65, 0x6c, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f,
0x6e, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74,
0x43, 0x6f, 0x6e, 0x66, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f,
0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x73, 0x61, 0x74,
0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f,
0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e,
0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75,
0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08,
0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d,
0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28,
0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x54, 0x0a, 0x17, 0x63, 0x6f, 0x69, 0x6e,
0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74,
0x65, 0x67, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53,
0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x15, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c,
0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x22, 0x89,
0x06, 0x0a, 0x10, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e,
0x6e, 0x65, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6b,
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x50, 0x75,
0x62, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x66, 0x75,
0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01,
0x28, 0x03, 0x52, 0x12, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67,
0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x75, 0x73, 0x68, 0x5f, 0x73,
0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x70, 0x75, 0x73, 0x68, 0x53, 0x61,
0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01,
0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x6d,
0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01,
0x28, 0x03, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12,
0x28, 0x0a, 0x10, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x73, 0x76, 0x5f, 0x64, 0x65,
0x6c, 0x61, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74,
0x65, 0x43, 0x73, 0x76, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x6f,
0x73, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x26,
0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69,
0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67,
0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74,
0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65,
0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65,
0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x43, 0x0a, 0x1f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65,
0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x69, 0x6e, 0x5f, 0x66, 0x6c,
0x69, 0x67, 0x68, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52,
0x1a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d, 0x61, 0x78, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x49,
0x6e, 0x46, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x72,
0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18,
0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d, 0x61, 0x78,
0x48, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x6c, 0x6f, 0x63,
0x61, 0x6c, 0x5f, 0x63, 0x73, 0x76, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6d, 0x61,
0x78, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x73, 0x76, 0x12, 0x1b, 0x0a, 0x09, 0x7a, 0x65, 0x72,
0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x7a, 0x65,
0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x63, 0x69, 0x64, 0x5f, 0x61,
0x6c, 0x69, 0x61, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x63, 0x69, 0x64,
0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65,
0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x62, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65,
0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x10, 0x20, 0x01,
0x28, 0x04, 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x75,
0x73, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28,
0x08, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x42, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x12, 0x20, 0x0a,
0x0c, 0x75, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x12, 0x20,
0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x46, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12,
0x35, 0x0a, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x72,
0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x04,
0x52, 0x14, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x65,
0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x18, 0x14,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x22, 0x5b, 0x0a, 0x18, 0x42, 0x61,
0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x10, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e,
0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67,
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43,
0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0xcb, 0x08, 0x0a, 0x12, 0x4f, 0x70, 0x65, 0x6e,
0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22,
0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79,
0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65,
0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x50, 0x75, 0x62,
0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x12, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6b,
0x65, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42,
0x02, 0x18, 0x01, 0x52, 0x10, 0x6e, 0x6f, 0x64, 0x65, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x53,
0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x30, 0x0a, 0x14, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x66,
0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20,
0x01, 0x28, 0x03, 0x52, 0x12, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e,
0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x75, 0x73, 0x68, 0x5f,
0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x70, 0x75, 0x73, 0x68, 0x53,
0x61, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e,
0x66, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43,
0x6f, 0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62,
0x79, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73,
0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69,
0x76, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76,
0x61, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f,
0x6d, 0x73, 0x61, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x48,
0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x6d, 0x6f, 0x74,
0x65, 0x5f, 0x63, 0x73, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28,
0x0d, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x73, 0x76, 0x44, 0x65, 0x6c, 0x61,
0x79, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x0b,
0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b,
0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72,
0x6d, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64,
0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63,
0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x0d, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x12, 0x35, 0x0a, 0x0c, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x68, 0x69, 0x6d,
0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46,
0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, 0x52, 0x0b, 0x66, 0x75, 0x6e, 0x64,
0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, 0x12, 0x43, 0x0a, 0x1f, 0x72, 0x65, 0x6d, 0x6f, 0x74,
0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x69, 0x6e, 0x5f, 0x66,
0x6c, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x04,
0x52, 0x1a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d, 0x61, 0x78, 0x56, 0x61, 0x6c, 0x75, 0x65,
0x49, 0x6e, 0x46, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10,
0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73,
0x18, 0x10, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d, 0x61,
0x78, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x6c, 0x6f,
0x63, 0x61, 0x6c, 0x5f, 0x63, 0x73, 0x76, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6d,
0x61, 0x78, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x73, 0x76, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x6f,
0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x12, 0x20,
0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d,
0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d,
0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x7a, 0x65,
0x72, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x13, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x7a,
0x65, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x63, 0x69, 0x64, 0x5f,
0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x63, 0x69,
0x64, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66,
0x65, 0x65, 0x18, 0x15, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x62, 0x61, 0x73, 0x65, 0x46, 0x65,
0x65, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x16, 0x20,
0x01, 0x28, 0x04, 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x0c,
0x75, 0x73, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x17, 0x20, 0x01,
0x28, 0x08, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x42, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x12, 0x20,
0x0a, 0x0c, 0x75, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x18,
0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x46, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65,
0x12, 0x35, 0x0a, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f,
0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x19, 0x20, 0x01, 0x28,
0x04, 0x52, 0x14, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x52, 0x65, 0x73,
0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x75, 0x6e, 0x64, 0x5f,
0x6d, 0x61, 0x78, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x66, 0x75, 0x6e, 0x64, 0x4d,
0x61, 0x78, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x09,
0x52, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x12, 0x2d, 0x0a, 0x09, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69,
0x6e, 0x74, 0x73, 0x18, 0x1c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x6f, 0x75, 0x74, 0x70,
0x6f, 0x69, 0x6e, 0x74, 0x73, 0x22, 0xf3, 0x01, 0x0a, 0x10, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x74,
0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x39, 0x0a, 0x0c, 0x63, 0x68,
0x61, 0x6e, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67,
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x65,
0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x37, 0x0a, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x6f, 0x70,
0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4f, 0x70, 0x65, 0x6e, 0x55, 0x70, 0x64, 0x61,
0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x4f, 0x70, 0x65, 0x6e, 0x12, 0x39,
0x0a, 0x09, 0x70, 0x73, 0x62, 0x74, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x79, 0x46,
0x6f, 0x72, 0x50, 0x73, 0x62, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x00, 0x52,
0x08, 0x70, 0x73, 0x62, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e,
0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49,
0x64, 0x42, 0x08, 0x0a, 0x06, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x22, 0x48, 0x0a, 0x0a, 0x4b,
0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x6b, 0x65, 0x79,
0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6b,
0x65, 0x79, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x6b, 0x65, 0x79, 0x5f,
0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6b, 0x65, 0x79,
0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x5f, 0x0a, 0x0d, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63,
0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x61, 0x77, 0x5f, 0x6b, 0x65,
0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x72,
0x61, 0x77, 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x07, 0x6b, 0x65,
0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x06,
0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x22, 0x88, 0x02, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x6e, 0x50,
0x6f, 0x69, 0x6e, 0x74, 0x53, 0x68, 0x69, 0x6d, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6d, 0x74, 0x18,
0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x61, 0x6d, 0x74, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68,
0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13,
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f,
0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x31,
0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73,
0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x4b, 0x65,
0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18,
0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4b, 0x65, 0x79,
0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e,
0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69,
0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x68, 0x61, 0x77,
0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74,
0x68, 0x61, 0x77, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x75, 0x73,
0x69, 0x67, 0x32, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6d, 0x75, 0x73, 0x69, 0x67,
0x32, 0x22, 0x6e, 0x0a, 0x08, 0x50, 0x73, 0x62, 0x74, 0x53, 0x68, 0x69, 0x6d, 0x12, 0x26, 0x0a,
0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43,
0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x70, 0x73,
0x62, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x62, 0x61, 0x73, 0x65, 0x50, 0x73,
0x62, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x6f, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68,
0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6e, 0x6f, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73,
0x68, 0x22, 0x85, 0x01, 0x0a, 0x0b, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69,
0x6d, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f,
0x73, 0x68, 0x69, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x68, 0x69, 0x6d,
0x48, 0x00, 0x52, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x68, 0x69,
0x6d, 0x12, 0x2e, 0x0a, 0x09, 0x70, 0x73, 0x62, 0x74, 0x5f, 0x73, 0x68, 0x69, 0x6d, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x73, 0x62,
0x74, 0x53, 0x68, 0x69, 0x6d, 0x48, 0x00, 0x52, 0x08, 0x70, 0x73, 0x62, 0x74, 0x53, 0x68, 0x69,
0x6d, 0x42, 0x06, 0x0a, 0x04, 0x73, 0x68, 0x69, 0x6d, 0x22, 0x3b, 0x0a, 0x11, 0x46, 0x75, 0x6e,
0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x12, 0x26,
0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67,
0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x22, 0x81, 0x01, 0x0a, 0x11, 0x46, 0x75, 0x6e, 0x64, 0x69,
0x6e, 0x67, 0x50, 0x73, 0x62, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, 0x1f, 0x0a, 0x0b,
0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x26, 0x0a,
0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43,
0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x66, 0x69,
0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b,
0x69, 0x70, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x22, 0x80, 0x01, 0x0a, 0x13, 0x46,
0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x73, 0x62, 0x74, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69,
0x7a, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62,
0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50,
0x73, 0x62, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63,
0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65,
0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x66,
0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x0a, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x61, 0x77, 0x54, 0x78, 0x22, 0x99, 0x02,
0x0a, 0x14, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74,
0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x67, 0x12, 0x39, 0x0a, 0x0d, 0x73, 0x68, 0x69, 0x6d, 0x5f, 0x72,
0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69,
0x6d, 0x48, 0x00, 0x52, 0x0c, 0x73, 0x68, 0x69, 0x6d, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65,
0x72, 0x12, 0x3b, 0x0a, 0x0b, 0x73, 0x68, 0x69, 0x6d, 0x5f, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46,
0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c,
0x48, 0x00, 0x52, 0x0a, 0x73, 0x68, 0x69, 0x6d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x12, 0x3b,
0x0a, 0x0b, 0x70, 0x73, 0x62, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x18, 0x03, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64,
0x69, 0x6e, 0x67, 0x50, 0x73, 0x62, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x48, 0x00, 0x52,
0x0a, 0x70, 0x73, 0x62, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, 0x41, 0x0a, 0x0d, 0x70,
0x73, 0x62, 0x74, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69,
0x6e, 0x67, 0x50, 0x73, 0x62, 0x74, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x48, 0x00,
0x52, 0x0c, 0x70, 0x73, 0x62, 0x74, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x42, 0x09,
0x0a, 0x07, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x22, 0x16, 0x0a, 0x14, 0x46, 0x75, 0x6e,
0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x73,
0x70, 0x22, 0xcc, 0x01, 0x0a, 0x0b, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x54, 0x4c,
0x43, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20,
0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x16, 0x0a,
0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61,
0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e,
0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e,
0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x68, 0x65,
0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x6d, 0x61, 0x74, 0x75,
0x72, 0x69, 0x74, 0x79, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x62, 0x6c,
0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x74, 0x69, 0x6c, 0x5f, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74,
0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x54,
0x69, 0x6c, 0x4d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74,
0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65,
0x22, 0x3e, 0x0a, 0x16, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e,
0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x69, 0x6e,
0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01,
0x28, 0x08, 0x52, 0x0c, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x52, 0x61, 0x77, 0x54, 0x78,
0x22, 0x91, 0x14, 0x0a, 0x17, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e,
0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x13,
0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x6d, 0x62, 0x6f, 0x5f, 0x62, 0x61, 0x6c, 0x61,
0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c,
0x4c, 0x69, 0x6d, 0x62, 0x6f, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x65, 0x0a, 0x15,
0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x63, 0x68, 0x61,
0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x6c, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e,
0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x65, 0x6e, 0x64,
0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x13,
0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e,
0x65, 0x6c, 0x73, 0x12, 0x6a, 0x0a, 0x18, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63,
0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18,
0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65,
0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e,
0x6e, 0x65, 0x6c, 0x42, 0x02, 0x18, 0x01, 0x52, 0x16, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67,
0x43, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12,
0x76, 0x0a, 0x1e, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x6f, 0x72, 0x63, 0x65,
0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f,
0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x1b, 0x70, 0x65, 0x6e, 0x64,
0x69, 0x6e, 0x67, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x43,
0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x68, 0x0a, 0x16, 0x77, 0x61, 0x69, 0x74, 0x69,
0x6e, 0x67, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x43,
0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x14, 0x77, 0x61, 0x69,
0x74, 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
0x73, 0x1a, 0xe3, 0x04, 0x0a, 0x0e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61,
0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x26, 0x0a, 0x0f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6e,
0x6f, 0x64, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72,
0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x50, 0x75, 0x62, 0x12, 0x23, 0x0a, 0x0d,
0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e,
0x74, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20,
0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x23, 0x0a,
0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x04,
0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e,
0x63, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x62, 0x61, 0x6c,
0x61, 0x6e, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x72, 0x65, 0x6d, 0x6f,
0x74, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x16, 0x6c, 0x6f, 0x63,
0x61, 0x6c, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f,
0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
0x43, 0x68, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x35,
0x0a, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x72, 0x65,
0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52,
0x14, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72,
0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x2e, 0x0a, 0x09, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74,
0x6f, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x09, 0x69, 0x6e, 0x69, 0x74,
0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d,
0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15,
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e,
0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e,
0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x6e, 0x75, 0x6d, 0x5f, 0x66, 0x6f, 0x72,
0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73,
0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x6e, 0x75, 0x6d, 0x46, 0x6f, 0x72, 0x77, 0x61,
0x72, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x12, 0x2a, 0x0a,
0x11, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x66, 0x6c, 0x61,
0x67, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x53, 0x74,
0x61, 0x74, 0x75, 0x73, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69,
0x76, 0x61, 0x74, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76,
0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x18, 0x0d, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f,
0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x22,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x6e,
0x6e, 0x65, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x1a, 0xf9, 0x01, 0x0a, 0x12, 0x50, 0x65, 0x6e, 0x64,
0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x47,
0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x2d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43,
0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e,
0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x07,
0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x69,
0x74, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x6d,
0x6d, 0x69, 0x74, 0x46, 0x65, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74,
0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63,
0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1c, 0x0a, 0x0a, 0x66,
0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52,
0x08, 0x66, 0x65, 0x65, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x32, 0x0a, 0x15, 0x66, 0x75, 0x6e,
0x64, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x62, 0x6c, 0x6f, 0x63,
0x6b, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e,
0x67, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x4a, 0x04, 0x08,
0x02, 0x10, 0x03, 0x1a, 0x9a, 0x02, 0x0a, 0x13, 0x57, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x43,
0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x47, 0x0a, 0x07, 0x63,
0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e,
0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x65, 0x6e,
0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x07, 0x63, 0x68, 0x61,
0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x69, 0x6d, 0x62, 0x6f, 0x5f, 0x62, 0x61,
0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x69, 0x6d,
0x62, 0x6f, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x63, 0x6f, 0x6d,
0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a,
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68,
0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43,
0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x6d,
0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x69,
0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63,
0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x63, 0x6c,
0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x5f, 0x68, 0x65, 0x78, 0x18, 0x05, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x48, 0x65, 0x78,
0x1a, 0xa3, 0x02, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x73,
0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x54, 0x78, 0x69, 0x64, 0x12,
0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x54, 0x78, 0x69, 0x64,
0x12, 0x2e, 0x0a, 0x13, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69,
0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x72,
0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64,
0x12, 0x2f, 0x0a, 0x14, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74,
0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11,
0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x46, 0x65, 0x65, 0x53, 0x61,
0x74, 0x12, 0x31, 0x0a, 0x15, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x6d,
0x69, 0x74, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04,
0x52, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x46, 0x65,
0x65, 0x53, 0x61, 0x74, 0x12, 0x40, 0x0a, 0x1d, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x70,
0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x66, 0x65,
0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x19, 0x72, 0x65, 0x6d,
0x6f, 0x74, 0x65, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74,
0x46, 0x65, 0x65, 0x53, 0x61, 0x74, 0x1a, 0x7b, 0x0a, 0x0d, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64,
0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x47, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e,
0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67,
0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x54,
0x78, 0x69, 0x64, 0x1a, 0xee, 0x03, 0x0a, 0x12, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f,
0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x47, 0x0a, 0x07, 0x63, 0x68,
0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6c, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e,
0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x65, 0x6e, 0x64,
0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e,
0x6e, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x74,
0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x69,
0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x69, 0x6d, 0x62, 0x6f, 0x5f,
0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c,
0x69, 0x6d, 0x62, 0x6f, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x6d,
0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x48, 0x65,
0x69, 0x67, 0x68, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x74,
0x69, 0x6c, 0x5f, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28,
0x05, 0x52, 0x11, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x54, 0x69, 0x6c, 0x4d, 0x61, 0x74, 0x75,
0x72, 0x69, 0x74, 0x79, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x65,
0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52,
0x10, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63,
0x65, 0x12, 0x37, 0x0a, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x74, 0x6c,
0x63, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x54, 0x4c, 0x43, 0x52, 0x0c, 0x70, 0x65,
0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x55, 0x0a, 0x06, 0x61, 0x6e,
0x63, 0x68, 0x6f, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x3d, 0x2e, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x6f, 0x72, 0x63, 0x65,
0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x41, 0x6e,
0x63, 0x68, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x06, 0x61, 0x6e, 0x63, 0x68, 0x6f,
0x72, 0x22, 0x31, 0x0a, 0x0b, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65,
0x12, 0x09, 0x0a, 0x05, 0x4c, 0x49, 0x4d, 0x42, 0x4f, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x52,
0x45, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x45, 0x44, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x4c, 0x4f,
0x53, 0x54, 0x10, 0x02, 0x22, 0x1a, 0x0a, 0x18, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45,
0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
0x22, 0xff, 0x04, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e,
0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x33, 0x0a, 0x0c, 0x6f, 0x70, 0x65, 0x6e, 0x5f,
0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x48, 0x00, 0x52,
0x0b, 0x6f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x43, 0x0a, 0x0e,
0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61,
0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79,
0x48, 0x00, 0x52, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
0x6c, 0x12, 0x3c, 0x0a, 0x0e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e,
0x6e, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x00,
0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12,
0x40, 0x0a, 0x10, 0x69, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e,
0x6e, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x00,
0x52, 0x0f, 0x69, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
0x6c, 0x12, 0x48, 0x0a, 0x14, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x70, 0x65,
0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x55,
0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x12, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67,
0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x4b, 0x0a, 0x16, 0x66,
0x75, 0x6c, 0x6c, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x64, 0x5f, 0x63, 0x68,
0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74,
0x48, 0x00, 0x52, 0x14, 0x66, 0x75, 0x6c, 0x6c, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65,
0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x38, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65,
0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43,
0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79,
0x70, 0x65, 0x22, 0x92, 0x01, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70,
0x65, 0x12, 0x10, 0x0a, 0x0c, 0x4f, 0x50, 0x45, 0x4e, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45,
0x4c, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x44, 0x5f, 0x43, 0x48,
0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x41, 0x43, 0x54, 0x49, 0x56,
0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x02, 0x12, 0x14, 0x0a, 0x10, 0x49,
0x4e, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10,
0x03, 0x12, 0x18, 0x0a, 0x14, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x4f, 0x50, 0x45,
0x4e, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x04, 0x12, 0x1a, 0x0a, 0x16, 0x46,
0x55, 0x4c, 0x4c, 0x59, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x4c, 0x56, 0x45, 0x44, 0x5f, 0x43, 0x48,
0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x05, 0x42, 0x09, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e,
0x65, 0x6c, 0x22, 0x74, 0x0a, 0x14, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f,
0x75, 0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6f,
0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64,
0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x13, 0x75, 0x6e, 0x63, 0x6f, 0x6e,
0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65,
0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x4d, 0x0a, 0x14, 0x57, 0x61, 0x6c, 0x6c,
0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69,
0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d,
0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x22, 0xbd, 0x03, 0x0a, 0x15, 0x57, 0x61, 0x6c, 0x6c,
0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e,
0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x42,
0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72,
0x6d, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x03, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61,
0x6e, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x13, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d,
0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03,
0x52, 0x12, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x42, 0x61, 0x6c,
0x61, 0x6e, 0x63, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x62,
0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6c, 0x6f,
0x63, 0x6b, 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x3f, 0x0a, 0x1c, 0x72,
0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f,
0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28,
0x03, 0x52, 0x19, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e,
0x63, 0x65, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x12, 0x59, 0x0a, 0x0f,
0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18,
0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61,
0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e,
0x63, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x1a, 0x5e, 0x0a, 0x13, 0x41, 0x63, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10,
0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, 0x63,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x2e, 0x0a, 0x06, 0x41, 0x6d, 0x6f, 0x75, 0x6e,
0x74, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03,
0x73, 0x61, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
0x04, 0x52, 0x04, 0x6d, 0x73, 0x61, 0x74, 0x22, 0x17, 0x0a, 0x15, 0x43, 0x68, 0x61, 0x6e, 0x6e,
0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x22, 0xb0, 0x04, 0x0a, 0x16, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61,
0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x07, 0x62,
0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01,
0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x14, 0x70, 0x65, 0x6e,
0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x12, 0x70, 0x65, 0x6e,
0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12,
0x32, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65,
0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41,
0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x61, 0x6c, 0x61,
0x6e, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x62, 0x61,
0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x0d, 0x72, 0x65, 0x6d, 0x6f,
0x74, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x17, 0x75, 0x6e, 0x73,
0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c,
0x61, 0x6e, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x15, 0x75, 0x6e, 0x73, 0x65, 0x74,
0x74, 0x6c, 0x65, 0x64, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65,
0x12, 0x47, 0x0a, 0x18, 0x75, 0x6e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x72, 0x65,
0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e,
0x74, 0x52, 0x16, 0x75, 0x6e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x6d, 0x6f,
0x74, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x1a, 0x70, 0x65, 0x6e,
0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f,
0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x17, 0x70, 0x65,
0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x61,
0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x4c, 0x0a, 0x1b, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67,
0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x62, 0x61, 0x6c,
0x61, 0x6e, 0x63, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x18, 0x70, 0x65, 0x6e, 0x64, 0x69,
0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x6c, 0x61,
0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x68,
0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x44,
0x61, 0x74, 0x61, 0x22, 0x9a, 0x07, 0x0a, 0x12, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75,
0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75,
0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62,
0x4b, 0x65, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6d, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03,
0x52, 0x03, 0x61, 0x6d, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61,
0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74,
0x12, 0x28, 0x0a, 0x10, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x64,
0x65, 0x6c, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x66, 0x69, 0x6e, 0x61,
0x6c, 0x43, 0x6c, 0x74, 0x76, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x2c, 0x0a, 0x09, 0x66, 0x65,
0x65, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x52, 0x08,
0x66, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x6f,
0x72, 0x65, 0x64, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0c, 0x52,
0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x3b, 0x0a,
0x0d, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x65, 0x64, 0x67, 0x65, 0x73, 0x18, 0x07,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x64, 0x67,
0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x69, 0x67,
0x6e, 0x6f, 0x72, 0x65, 0x64, 0x45, 0x64, 0x67, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x08, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79,
0x12, 0x2e, 0x0a, 0x13, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f,
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x75,
0x73, 0x65, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x12, 0x34, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x69, 0x72,
0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x4e, 0x6f, 0x64, 0x65, 0x50, 0x61, 0x69, 0x72, 0x52, 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65,
0x64, 0x50, 0x61, 0x69, 0x72, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x6c,
0x69, 0x6d, 0x69, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x63, 0x6c, 0x74, 0x76,
0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x60, 0x0a, 0x13, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x75,
0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x0d, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x30, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79,
0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x65,
0x73, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x64, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d,
0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x2c, 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f,
0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28,
0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x43,
0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x68, 0x6f,
0x70, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d,
0x6c, 0x61, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a,
0x0b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x10, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65,
0x48, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x73,
0x12, 0x4d, 0x0a, 0x15, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x79, 0x6d,
0x65, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50,
0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x74, 0x68, 0x52, 0x13, 0x62, 0x6c, 0x69, 0x6e,
0x64, 0x65, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x74, 0x68, 0x73, 0x12,
0x36, 0x0a, 0x0d, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73,
0x18, 0x11, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46,
0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, 0x74, 0x52, 0x0c, 0x64, 0x65, 0x73, 0x74, 0x46,
0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x5f,
0x70, 0x72, 0x65, 0x66, 0x18, 0x12, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65,
0x50, 0x72, 0x65, 0x66, 0x1a, 0x44, 0x0a, 0x16, 0x44, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, 0x74,
0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10,
0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79,
0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04,
0x22, 0x2e, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x50, 0x61, 0x69, 0x72, 0x12, 0x12, 0x0a, 0x04,
0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d,
0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x74, 0x6f,
0x22, 0x5d, 0x0a, 0x0b, 0x45, 0x64, 0x67, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x12,
0x21, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
0x49, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x64,
0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x22,
0x5e, 0x0a, 0x13, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73,
0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52,
0x6f, 0x75, 0x74, 0x65, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c,
0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x62, 0x18, 0x02, 0x20, 0x01,
0x28, 0x01, 0x52, 0x0b, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x50, 0x72, 0x6f, 0x62, 0x22,
0xa5, 0x05, 0x0a, 0x03, 0x48, 0x6f, 0x70, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68,
0x61, 0x6e, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x63, 0x61, 0x70,
0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52,
0x0c, 0x63, 0x68, 0x61, 0x6e, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x28, 0x0a,
0x0e, 0x61, 0x6d, 0x74, 0x5f, 0x74, 0x6f, 0x5f, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x18,
0x03, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x61, 0x6d, 0x74, 0x54, 0x6f,
0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x12, 0x14, 0x0a, 0x03, 0x66, 0x65, 0x65, 0x18, 0x04,
0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x03, 0x66, 0x65, 0x65, 0x12, 0x16, 0x0a,
0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x65,
0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x2d, 0x0a, 0x13, 0x61, 0x6d, 0x74, 0x5f, 0x74, 0x6f, 0x5f,
0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01,
0x28, 0x03, 0x52, 0x10, 0x61, 0x6d, 0x74, 0x54, 0x6f, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64,
0x4d, 0x73, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74,
0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x66, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12,
0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09,
0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0b, 0x74, 0x6c, 0x76, 0x5f,
0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18,
0x01, 0x52, 0x0a, 0x74, 0x6c, 0x76, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x2f, 0x0a,
0x0a, 0x6d, 0x70, 0x70, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x50, 0x50, 0x52, 0x65, 0x63,
0x6f, 0x72, 0x64, 0x52, 0x09, 0x6d, 0x70, 0x70, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x2f,
0x0a, 0x0a, 0x61, 0x6d, 0x70, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x0c, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x4d, 0x50, 0x52, 0x65,
0x63, 0x6f, 0x72, 0x64, 0x52, 0x09, 0x61, 0x6d, 0x70, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12,
0x44, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64,
0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x48, 0x6f, 0x70, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64,
0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65,
0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
0x61, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
0x61, 0x12, 0x25, 0x0a, 0x0e, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x6f,
0x69, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x62, 0x6c, 0x69, 0x6e, 0x64,
0x69, 0x6e, 0x67, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x63, 0x72,
0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12,
0x24, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61,
0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d,
0x74, 0x4d, 0x73, 0x61, 0x74, 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52,
0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x54, 0x0a, 0x09, 0x4d, 0x50, 0x50, 0x52, 0x65,
0x63, 0x6f, 0x72, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f,
0x61, 0x64, 0x64, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d,
0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c,
0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52,
0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x22, 0x62, 0x0a,
0x09, 0x41, 0x4d, 0x50, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f,
0x6f, 0x74, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09,
0x72, 0x6f, 0x6f, 0x74, 0x53, 0x68, 0x61, 0x72, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x74,
0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x65, 0x74, 0x49, 0x64,
0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18,
0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x64, 0x65,
0x78, 0x22, 0xc4, 0x02, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x74,
0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x4c,
0x6f, 0x63, 0x6b, 0x12, 0x21, 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x65, 0x65,
0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x74, 0x6f, 0x74,
0x61, 0x6c, 0x46, 0x65, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f,
0x61, 0x6d, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x74,
0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x74, 0x12, 0x1e, 0x0a, 0x04, 0x68, 0x6f, 0x70, 0x73, 0x18,
0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x6f,
0x70, 0x52, 0x04, 0x68, 0x6f, 0x70, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x6f, 0x74, 0x61, 0x6c,
0x5f, 0x66, 0x65, 0x65, 0x73, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03,
0x52, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x46, 0x65, 0x65, 0x73, 0x4d, 0x73, 0x61, 0x74, 0x12,
0x24, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61,
0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d,
0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x31, 0x0a, 0x15, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x68,
0x6f, 0x70, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07,
0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x66, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x41, 0x6d,
0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x73, 0x74,
0x6f, 0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18,
0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x68, 0x61,
0x6e, 0x6e, 0x65, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x22, 0x55, 0x0a, 0x0f, 0x4e, 0x6f, 0x64, 0x65,
0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70,
0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75,
0x62, 0x4b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f,
0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f,
0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22,
0xae, 0x01, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x28, 0x0a, 0x04,
0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65,
0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68,
0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6e, 0x75,
0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x6f, 0x74,
0x61, 0x6c, 0x5f, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28,
0x03, 0x52, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79,
0x12, 0x2e, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e,
0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73,
0x22, 0xc6, 0x03, 0x0a, 0x0d, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f,
0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74,
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64,
0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05,
0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, 0x69,
0x61, 0x73, 0x12, 0x30, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18,
0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f,
0x64, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x05, 0x20,
0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x12, 0x3e, 0x0a, 0x08, 0x66, 0x65,
0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f,
0x64, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x4e, 0x0a, 0x0e, 0x63, 0x75,
0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x07, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x67, 0x68, 0x74,
0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52,
0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73,
0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65,
0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f,
0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a,
0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x3b, 0x0a, 0x0b, 0x4e, 0x6f, 0x64,
0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77,
0x6f, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f,
0x72, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x22, 0x89, 0x04, 0x0a, 0x0d, 0x52, 0x6f, 0x75, 0x74, 0x69,
0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65,
0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0d, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x44, 0x65, 0x6c, 0x74, 0x61,
0x12, 0x19, 0x0a, 0x08, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x18, 0x02, 0x20, 0x01,
0x28, 0x03, 0x52, 0x07, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x12, 0x22, 0x0a, 0x0d, 0x66,
0x65, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01,
0x28, 0x03, 0x52, 0x0b, 0x66, 0x65, 0x65, 0x42, 0x61, 0x73, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12,
0x2d, 0x0a, 0x13, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x69, 0x6c, 0x6c,
0x69, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x66, 0x65,
0x65, 0x52, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1a,
0x0a, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08,
0x52, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61,
0x78, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28,
0x04, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1f,
0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20,
0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12,
0x4e, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64,
0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x43, 0x75,
0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12,
0x31, 0x0a, 0x15, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x62,
0x61, 0x73, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12,
0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x65, 0x65, 0x42, 0x61, 0x73, 0x65, 0x4d, 0x73,
0x61, 0x74, 0x12, 0x3c, 0x0a, 0x1b, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x65,
0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x5f, 0x6d, 0x73, 0x61,
0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x17, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64,
0x46, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x4d, 0x73, 0x61, 0x74,
0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64,
0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
0x38, 0x01, 0x22, 0xcc, 0x03, 0x0a, 0x0b, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64,
0x67, 0x65, 0x12, 0x21, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e,
0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f,
0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50,
0x6f, 0x69, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64,
0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x6c,
0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64,
0x65, 0x31, 0x5f, 0x70, 0x75, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f,
0x64, 0x65, 0x31, 0x50, 0x75, 0x62, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x32, 0x5f,
0x70, 0x75, 0x62, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x32,
0x50, 0x75, 0x62, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18,
0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12,
0x37, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x31, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18,
0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f,
0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0b, 0x6e, 0x6f, 0x64,
0x65, 0x31, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x37, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65,
0x32, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14,
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f,
0x6c, 0x69, 0x63, 0x79, 0x52, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x32, 0x50, 0x6f, 0x6c, 0x69, 0x63,
0x79, 0x12, 0x4c, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f,
0x72, 0x64, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x2e, 0x43, 0x75,
0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a,
0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73,
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
0x01, 0x22, 0x46, 0x0a, 0x13, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70,
0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x13, 0x69, 0x6e, 0x63, 0x6c,
0x75, 0x64, 0x65, 0x5f, 0x75, 0x6e, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x55, 0x6e,
0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x64, 0x22, 0x64, 0x0a, 0x0c, 0x43, 0x68, 0x61,
0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x2a, 0x0a, 0x05, 0x6e, 0x6f, 0x64,
0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x05,
0x6e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x05, 0x65, 0x64, 0x67, 0x65, 0x73, 0x18, 0x02,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61,
0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x52, 0x05, 0x65, 0x64, 0x67, 0x65, 0x73, 0x22,
0x41, 0x0a, 0x12, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x01,
0x20, 0x03, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64,
0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x52, 0x05, 0x74, 0x79, 0x70,
0x65, 0x73, 0x22, 0xe1, 0x01, 0x0a, 0x13, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69,
0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x16, 0x62, 0x65,
0x74, 0x77, 0x65, 0x65, 0x6e, 0x6e, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x65, 0x6e, 0x74, 0x72, 0x61,
0x6c, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x42, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x6e, 0x65,
0x73, 0x73, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x72,
0x79, 0x52, 0x15, 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x6e, 0x65, 0x73, 0x73, 0x43, 0x65,
0x6e, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x1a, 0x5c, 0x0a, 0x1a, 0x42, 0x65, 0x74, 0x77,
0x65, 0x65, 0x6e, 0x6e, 0x65, 0x73, 0x73, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x74,
0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x46, 0x6c, 0x6f, 0x61, 0x74, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x4e, 0x0a, 0x0b, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x4d,
0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x6e,
0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18,
0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0f, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65,
0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x4d, 0x0a, 0x0f, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x6e,
0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61,
0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06,
0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70,
0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e,
0x50, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x14, 0x0a, 0x12, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xd5, 0x03, 0x0a, 0x0b,
0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x25, 0x0a, 0x0e, 0x67,
0x72, 0x61, 0x70, 0x68, 0x5f, 0x64, 0x69, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0d, 0x52, 0x0d, 0x67, 0x72, 0x61, 0x70, 0x68, 0x44, 0x69, 0x61, 0x6d, 0x65, 0x74,
0x65, 0x72, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x76, 0x67, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x64, 0x65,
0x67, 0x72, 0x65, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x61, 0x76, 0x67, 0x4f,
0x75, 0x74, 0x44, 0x65, 0x67, 0x72, 0x65, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x61, 0x78, 0x5f,
0x6f, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x67, 0x72, 0x65, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d,
0x52, 0x0c, 0x6d, 0x61, 0x78, 0x4f, 0x75, 0x74, 0x44, 0x65, 0x67, 0x72, 0x65, 0x65, 0x12, 0x1b,
0x0a, 0x09, 0x6e, 0x75, 0x6d, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28,
0x0d, 0x52, 0x08, 0x6e, 0x75, 0x6d, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6e,
0x75, 0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28,
0x0d, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x34,
0x0a, 0x16, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f,
0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14,
0x74, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x43, 0x61, 0x70, 0x61,
0x63, 0x69, 0x74, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x61, 0x76, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e,
0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0e,
0x61, 0x76, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x28,
0x0a, 0x10, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69,
0x7a, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6d, 0x69, 0x6e, 0x43, 0x68, 0x61,
0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x61, 0x78, 0x5f,
0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x09, 0x20, 0x01,
0x28, 0x03, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x69,
0x7a, 0x65, 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x5f, 0x63, 0x68, 0x61,
0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20,
0x01, 0x28, 0x03, 0x52, 0x14, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e,
0x65, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x53, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6e, 0x75, 0x6d,
0x5f, 0x7a, 0x6f, 0x6d, 0x62, 0x69, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x73, 0x18, 0x0b, 0x20,
0x01, 0x28, 0x04, 0x52, 0x0e, 0x6e, 0x75, 0x6d, 0x5a, 0x6f, 0x6d, 0x62, 0x69, 0x65, 0x43, 0x68,
0x61, 0x6e, 0x73, 0x22, 0x0d, 0x0a, 0x0b, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x22, 0x26, 0x0a, 0x0c, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x1b, 0x0a, 0x19, 0x47, 0x72,
0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x53, 0x75, 0x62, 0x73, 0x63,
0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xcd, 0x01, 0x0a, 0x13, 0x47, 0x72, 0x61, 0x70,
0x68, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12,
0x34, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18,
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f,
0x64, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x55, 0x70,
0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x41, 0x0a, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18,
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64,
0x67, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65,
0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73,
0x65, 0x64, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a,
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61,
0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73,
0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x73, 0x22, 0xef, 0x02, 0x0a, 0x0a, 0x4e, 0x6f, 0x64, 0x65,
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x61,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x64, 0x65, 0x6e,
0x74, 0x69, 0x74, 0x79, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x4b, 0x65, 0x79, 0x12, 0x2b, 0x0a, 0x0f, 0x67,
0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x03,
0x20, 0x01, 0x28, 0x0c, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c,
0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x61,
0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x14,
0x0a, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63,
0x6f, 0x6c, 0x6f, 0x72, 0x12, 0x39, 0x0a, 0x0e, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x61, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x52, 0x0d, 0x6e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12,
0x3b, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x55, 0x70,
0x64, 0x61, 0x74, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74,
0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x1a, 0x4b, 0x0a, 0x0d,
0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a,
0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e,
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x05,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x91, 0x02, 0x0a, 0x11, 0x43, 0x68,
0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12,
0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x0a,
0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74,
0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01,
0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x0e,
0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x04,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75,
0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0d, 0x72, 0x6f, 0x75, 0x74,
0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x61, 0x64, 0x76,
0x65, 0x72, 0x74, 0x69, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0f, 0x61, 0x64, 0x76, 0x65, 0x72, 0x74, 0x69, 0x73, 0x69, 0x6e, 0x67,
0x4e, 0x6f, 0x64, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69,
0x6e, 0x67, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63,
0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x22, 0xa7, 0x01,
0x0a, 0x13, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55,
0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e,
0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02,
0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x23,
0x0a, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18,
0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x48, 0x65, 0x69,
0x67, 0x68, 0x74, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e,
0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68,
0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0xcf, 0x01, 0x0a, 0x07, 0x48, 0x6f, 0x70, 0x48,
0x69, 0x6e, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x07,
0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30,
0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x66, 0x65, 0x65,
0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d,
0x52, 0x0b, 0x66, 0x65, 0x65, 0x42, 0x61, 0x73, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x3e, 0x0a,
0x1b, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x61,
0x6c, 0x5f, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x6f, 0x6e, 0x74, 0x68, 0x73, 0x18, 0x04, 0x20, 0x01,
0x28, 0x0d, 0x52, 0x19, 0x66, 0x65, 0x65, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f,
0x6e, 0x61, 0x6c, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x6f, 0x6e, 0x74, 0x68, 0x73, 0x12, 0x2a, 0x0a,
0x11, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x64, 0x65, 0x6c,
0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x63, 0x6c, 0x74, 0x76, 0x45, 0x78,
0x70, 0x69, 0x72, 0x79, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x22, 0x1e, 0x0a, 0x05, 0x53, 0x65, 0x74,
0x49, 0x44, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x05, 0x73, 0x65, 0x74, 0x49, 0x64, 0x22, 0x38, 0x0a, 0x09, 0x52, 0x6f, 0x75,
0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x12, 0x2b, 0x0a, 0x09, 0x68, 0x6f, 0x70, 0x5f, 0x68, 0x69,
0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x48, 0x6f, 0x70, 0x48, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x68, 0x6f, 0x70, 0x48, 0x69,
0x6e, 0x74, 0x73, 0x22, 0xc4, 0x02, 0x0a, 0x12, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50,
0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x74, 0x68, 0x12, 0x35, 0x0a, 0x0c, 0x62, 0x6c,
0x69, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64,
0x50, 0x61, 0x74, 0x68, 0x52, 0x0b, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x74,
0x68, 0x12, 0x22, 0x0a, 0x0d, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73,
0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x62, 0x61, 0x73, 0x65, 0x46, 0x65,
0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x72, 0x74,
0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x03,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e,
0x61, 0x6c, 0x46, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74,
0x61, 0x6c, 0x5f, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x04, 0x20,
0x01, 0x28, 0x0d, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6c, 0x74, 0x76, 0x44, 0x65,
0x6c, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x0d, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x69, 0x6e, 0x5f,
0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x68, 0x74, 0x6c, 0x63,
0x4d, 0x69, 0x6e, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x68, 0x74, 0x6c, 0x63, 0x5f,
0x6d, 0x61, 0x78, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b,
0x68, 0x74, 0x6c, 0x63, 0x4d, 0x61, 0x78, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2d, 0x0a, 0x08, 0x66,
0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x11, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, 0x74,
0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x22, 0x97, 0x01, 0x0a, 0x0b, 0x42,
0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x6e,
0x74, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x69, 0x6e, 0x74, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x62, 0x6c, 0x69, 0x6e, 0x64,
0x69, 0x6e, 0x67, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x0d, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x34,
0x0a, 0x0c, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x68, 0x6f, 0x70, 0x73, 0x18, 0x03,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x6c, 0x69,
0x6e, 0x64, 0x65, 0x64, 0x48, 0x6f, 0x70, 0x52, 0x0b, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64,
0x48, 0x6f, 0x70, 0x73, 0x22, 0x56, 0x0a, 0x0a, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x48,
0x6f, 0x70, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x6e, 0x6f,
0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65,
0x64, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74,
0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65,
0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x22, 0xa8, 0x01, 0x0a,
0x0f, 0x41, 0x4d, 0x50, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65,
0x12, 0x2d, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48,
0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12,
0x21, 0x0a, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18,
0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x64,
0x65, 0x78, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x74, 0x69, 0x6d,
0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x54,
0x69, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x6d, 0x74, 0x5f, 0x70, 0x61, 0x69, 0x64, 0x5f,
0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x6d, 0x74, 0x50,
0x61, 0x69, 0x64, 0x4d, 0x73, 0x61, 0x74, 0x22, 0xac, 0x0a, 0x0a, 0x07, 0x49, 0x6e, 0x76, 0x6f,
0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x5f, 0x70, 0x72, 0x65,
0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x50, 0x72,
0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68,
0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x48, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x6d, 0x73, 0x61,
0x74, 0x18, 0x17, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x4d, 0x73,
0x61, 0x74, 0x12, 0x1c, 0x0a, 0x07, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20,
0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64,
0x12, 0x23, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74,
0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x44, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f,
0x64, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x65, 0x74, 0x74,
0x6c, 0x65, 0x44, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e,
0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x29, 0x0a, 0x10, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68,
0x61, 0x73, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x64, 0x65, 0x73, 0x63, 0x72,
0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78,
0x70, 0x69, 0x72, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69,
0x72, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x61,
0x64, 0x64, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x61, 0x6c, 0x6c, 0x62,
0x61, 0x63, 0x6b, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x74, 0x76, 0x5f,
0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x63, 0x6c,
0x74, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x31, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74,
0x65, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x52,
0x0a, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70,
0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72,
0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x5f, 0x69, 0x6e, 0x64,
0x65, 0x78, 0x18, 0x10, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x61, 0x64, 0x64, 0x49, 0x6e, 0x64,
0x65, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x64,
0x65, 0x78, 0x18, 0x11, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65,
0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1d, 0x0a, 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x70, 0x61, 0x69,
0x64, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x61, 0x6d, 0x74,
0x50, 0x61, 0x69, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x61, 0x6d, 0x74, 0x5f, 0x70, 0x61, 0x69, 0x64,
0x5f, 0x73, 0x61, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x61, 0x6d, 0x74, 0x50,
0x61, 0x69, 0x64, 0x53, 0x61, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x6d, 0x74, 0x5f, 0x70, 0x61,
0x69, 0x64, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61,
0x6d, 0x74, 0x50, 0x61, 0x69, 0x64, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x31, 0x0a, 0x05, 0x73, 0x74,
0x61, 0x74, 0x65, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63,
0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x28, 0x0a,
0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x16, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43,
0x52, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x38, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75,
0x72, 0x65, 0x73, 0x18, 0x18, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72,
0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65,
0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x65, 0x6e, 0x64, 0x18,
0x19, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x4b, 0x65, 0x79, 0x73, 0x65, 0x6e, 0x64,
0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72,
0x18, 0x1a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41,
0x64, 0x64, 0x72, 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, 0x5f, 0x61, 0x6d, 0x70, 0x18, 0x1b, 0x20,
0x01, 0x28, 0x08, 0x52, 0x05, 0x69, 0x73, 0x41, 0x6d, 0x70, 0x12, 0x4f, 0x0a, 0x11, 0x61, 0x6d,
0x70, 0x5f, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18,
0x1c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e,
0x76, 0x6f, 0x69, 0x63, 0x65, 0x2e, 0x41, 0x6d, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65,
0x53, 0x74, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0f, 0x61, 0x6d, 0x70, 0x49,
0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x69,
0x73, 0x5f, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x08, 0x52,
0x09, 0x69, 0x73, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x12, 0x48, 0x0a, 0x13, 0x62, 0x6c,
0x69, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x52, 0x11, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73,
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46,
0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
0x01, 0x1a, 0x5a, 0x0a, 0x14, 0x41, 0x6d, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53,
0x74, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x41, 0x4d, 0x50, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61,
0x74, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x41, 0x0a,
0x0c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x08, 0x0a,
0x04, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45, 0x54, 0x54, 0x4c,
0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44,
0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x10, 0x03,
0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0xef, 0x01, 0x0a, 0x11, 0x42, 0x6c, 0x69, 0x6e, 0x64,
0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x0a, 0x11,
0x6d, 0x69, 0x6e, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, 0x72, 0x65, 0x61, 0x6c, 0x5f, 0x68, 0x6f, 0x70,
0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x00, 0x52, 0x0e, 0x6d, 0x69, 0x6e, 0x4e, 0x75,
0x6d, 0x52, 0x65, 0x61, 0x6c, 0x48, 0x6f, 0x70, 0x73, 0x88, 0x01, 0x01, 0x12, 0x1e, 0x0a, 0x08,
0x6e, 0x75, 0x6d, 0x5f, 0x68, 0x6f, 0x70, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x01,
0x52, 0x07, 0x6e, 0x75, 0x6d, 0x48, 0x6f, 0x70, 0x73, 0x88, 0x01, 0x01, 0x12, 0x27, 0x0a, 0x0d,
0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x03, 0x20,
0x01, 0x28, 0x0d, 0x48, 0x02, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x4e, 0x75, 0x6d, 0x50, 0x61, 0x74,
0x68, 0x73, 0x88, 0x01, 0x01, 0x12, 0x2c, 0x0a, 0x12, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6f, 0x6d,
0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x04, 0x20, 0x03, 0x28,
0x0c, 0x52, 0x10, 0x6e, 0x6f, 0x64, 0x65, 0x4f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c,
0x69, 0x73, 0x74, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x6e, 0x75, 0x6d, 0x5f,
0x72, 0x65, 0x61, 0x6c, 0x5f, 0x68, 0x6f, 0x70, 0x73, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6e, 0x75,
0x6d, 0x5f, 0x68, 0x6f, 0x70, 0x73, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6e,
0x75, 0x6d, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x22, 0xac, 0x04, 0x0a, 0x0b, 0x49, 0x6e, 0x76,
0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e,
0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63,
0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e,
0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x68, 0x74, 0x6c, 0x63, 0x49,
0x6e, 0x64, 0x65, 0x78, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74,
0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x61, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12,
0x23, 0x0a, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74,
0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x48, 0x65,
0x69, 0x67, 0x68, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x74,
0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x61, 0x63, 0x63, 0x65, 0x70,
0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65,
0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x65, 0x73,
0x6f, 0x6c, 0x76, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69,
0x72, 0x79, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52,
0x0c, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x2d, 0x0a,
0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43,
0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x4c, 0x0a, 0x0e,
0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x09,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76,
0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52,
0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73,
0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x2b, 0x0a, 0x12, 0x6d, 0x70,
0x70, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74,
0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x70, 0x70, 0x54, 0x6f, 0x74, 0x61, 0x6c,
0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1c, 0x0a, 0x03, 0x61, 0x6d, 0x70, 0x18, 0x0b,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x4d, 0x50,
0x52, 0x03, 0x61, 0x6d, 0x70, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f,
0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0c, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
0x6c, 0x44, 0x61, 0x74, 0x61, 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52,
0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x8c, 0x01, 0x0a, 0x03, 0x41, 0x4d, 0x50, 0x12,
0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x53, 0x68, 0x61, 0x72, 0x65, 0x12, 0x15,
0x0a, 0x06, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05,
0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x69,
0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x63, 0x68, 0x69, 0x6c,
0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72,
0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72,
0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x94, 0x01, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x49, 0x6e,
0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15, 0x0a,
0x06, 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72,
0x48, 0x61, 0x73, 0x68, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f,
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70,
0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a,
0x09, 0x61, 0x64, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x10, 0x20, 0x01, 0x28, 0x04,
0x52, 0x08, 0x61, 0x64, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61,
0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x22, 0x46, 0x0a,
0x0b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x20, 0x0a, 0x0a,
0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x73, 0x74, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x72, 0x48, 0x61, 0x73, 0x68, 0x53, 0x74, 0x72, 0x12, 0x15,
0x0a, 0x06, 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05,
0x72, 0x48, 0x61, 0x73, 0x68, 0x22, 0xfc, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e,
0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c,
0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x08, 0x52, 0x0b, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x6e, 0x6c, 0x79, 0x12,
0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18,
0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73,
0x65, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6e, 0x75, 0x6d, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x69, 0x6e,
0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x6e, 0x75,
0x6d, 0x4d, 0x61, 0x78, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08,
0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08,
0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x72, 0x65, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18,
0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44,
0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x72, 0x65, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x08, 0x20,
0x01, 0x28, 0x04, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74,
0x65, 0x45, 0x6e, 0x64, 0x22, 0x9b, 0x01, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76,
0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x08,
0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e,
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x08,
0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74,
0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20,
0x01, 0x28, 0x04, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66,
0x66, 0x73, 0x65, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x69, 0x6e,
0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04,
0x52, 0x10, 0x66, 0x69, 0x72, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73,
0x65, 0x74, 0x22, 0x55, 0x0a, 0x13, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62,
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x64,
0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x61, 0x64,
0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65,
0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x65,
0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0xcb, 0x06, 0x0a, 0x07, 0x50, 0x61,
0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74,
0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x79,
0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x18, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x12, 0x27, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64,
0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x63,
0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x03, 0x66,
0x65, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x03, 0x66, 0x65,
0x65, 0x12, 0x29, 0x0a, 0x10, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x65,
0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x61, 0x79,
0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52,
0x08, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x61, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x79, 0x6d,
0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x34, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28,
0x0e, 0x32, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e,
0x74, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,
0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x65, 0x65, 0x5f, 0x73,
0x61, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x66, 0x65, 0x65, 0x53, 0x61, 0x74,
0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01,
0x28, 0x03, 0x52, 0x07, 0x66, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x63,
0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6e, 0x73, 0x18,
0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54,
0x69, 0x6d, 0x65, 0x4e, 0x73, 0x12, 0x28, 0x0a, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x0e,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x54, 0x4c,
0x43, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x52, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x12,
0x23, 0x0a, 0x0d, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78,
0x18, 0x0f, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49,
0x6e, 0x64, 0x65, 0x78, 0x12, 0x42, 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f,
0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x46, 0x61, 0x69, 0x6c,
0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75,
0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x62, 0x0a, 0x18, 0x66, 0x69, 0x72, 0x73,
0x74, 0x5f, 0x68, 0x6f, 0x70, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63,
0x6f, 0x72, 0x64, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x73, 0x74,
0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73,
0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x15, 0x66, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43,
0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x48, 0x0a, 0x1a,
0x46, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65,
0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x59, 0x0a, 0x0d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e,
0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0f, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f,
0x57, 0x4e, 0x10, 0x00, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x5f, 0x46,
0x4c, 0x49, 0x47, 0x48, 0x54, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45,
0x45, 0x44, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44,
0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x45, 0x44, 0x10,
0x04, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, 0xd5, 0x02, 0x0a, 0x0b, 0x48, 0x54, 0x4c, 0x43,
0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x65, 0x6d,
0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x61, 0x74, 0x74,
0x65, 0x6d, 0x70, 0x74, 0x49, 0x64, 0x12, 0x35, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48,
0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x53,
0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a,
0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74,
0x65, 0x12, 0x26, 0x0a, 0x0f, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x5f, 0x74, 0x69, 0x6d,
0x65, 0x5f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x65,
0x6d, 0x70, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x4e, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x72, 0x65, 0x73,
0x6f, 0x6c, 0x76, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01,
0x28, 0x03, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x4e,
0x73, 0x12, 0x28, 0x0a, 0x07, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x18, 0x05, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75,
0x72, 0x65, 0x52, 0x07, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70,
0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70,
0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x36, 0x0a, 0x0a, 0x48, 0x54, 0x4c, 0x43, 0x53,
0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x5f, 0x46, 0x4c, 0x49, 0x47,
0x48, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45,
0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x22,
0xb4, 0x02, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2d, 0x0a, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75,
0x64, 0x65, 0x5f, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x08, 0x52, 0x11, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x49, 0x6e, 0x63, 0x6f,
0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f,
0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x69, 0x6e,
0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x78,
0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52,
0x0b, 0x6d, 0x61, 0x78, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08,
0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08,
0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x6f, 0x75, 0x6e,
0x74, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73,
0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x6f, 0x74,
0x61, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x72,
0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72,
0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x44, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x72,
0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x65, 0x6e, 0x64, 0x18,
0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44,
0x61, 0x74, 0x65, 0x45, 0x6e, 0x64, 0x22, 0xca, 0x01, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x50,
0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x2a, 0x0a, 0x08, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e,
0x74, 0x52, 0x08, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x66,
0x69, 0x72, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65,
0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x66, 0x69, 0x72, 0x73, 0x74, 0x49, 0x6e,
0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73,
0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03,
0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f,
0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x6e,
0x75, 0x6d, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28,
0x04, 0x52, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65,
0x6e, 0x74, 0x73, 0x22, 0x65, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79,
0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70,
0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x2a,
0x0a, 0x11, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x5f, 0x6f,
0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x65,
0x64, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x9b, 0x01, 0x0a, 0x18, 0x44,
0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x66, 0x61, 0x69, 0x6c, 0x65,
0x64, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18,
0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x50, 0x61, 0x79,
0x6d, 0x65, 0x6e, 0x74, 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x66, 0x61, 0x69,
0x6c, 0x65, 0x64, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02,
0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x48, 0x74, 0x6c, 0x63,
0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x6c, 0x6c, 0x5f, 0x70, 0x61, 0x79,
0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x61, 0x6c, 0x6c,
0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x2f, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65,
0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x33, 0x0a, 0x19, 0x44, 0x65, 0x6c,
0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xbf,
0x01, 0x0a, 0x15, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e,
0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50,
0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69,
0x6e, 0x74, 0x12, 0x39, 0x0a, 0x19, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x75,
0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x68, 0x69, 0x6d, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18,
0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x46, 0x75,
0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x31, 0x0a,
0x16, 0x69, 0x5f, 0x6b, 0x6e, 0x6f, 0x77, 0x5f, 0x77, 0x68, 0x61, 0x74, 0x5f, 0x69, 0x5f, 0x61,
0x6d, 0x5f, 0x64, 0x6f, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x69,
0x4b, 0x6e, 0x6f, 0x77, 0x57, 0x68, 0x61, 0x74, 0x49, 0x41, 0x6d, 0x44, 0x6f, 0x69, 0x6e, 0x67,
0x22, 0x30, 0x0a, 0x16, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e,
0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74,
0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74,
0x75, 0x73, 0x22, 0x46, 0x0a, 0x11, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x68, 0x6f, 0x77, 0x18,
0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x73, 0x68, 0x6f, 0x77, 0x12, 0x1d, 0x0a, 0x0a, 0x6c,
0x65, 0x76, 0x65, 0x6c, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x09, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x53, 0x70, 0x65, 0x63, 0x22, 0x35, 0x0a, 0x12, 0x44, 0x65,
0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x75, 0x62, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x75, 0x62, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d,
0x73, 0x22, 0x27, 0x0a, 0x0c, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x53, 0x74, 0x72, 0x69, 0x6e,
0x67, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x5f, 0x72, 0x65, 0x71, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x06, 0x70, 0x61, 0x79, 0x52, 0x65, 0x71, 0x22, 0xf0, 0x04, 0x0a, 0x06, 0x50,
0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74,
0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65,
0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70,
0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x75,
0x6d, 0x5f, 0x73, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03,
0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x53, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x73, 0x12, 0x1c, 0x0a,
0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03,
0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x65,
0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x65, 0x78, 0x70,
0x69, 0x72, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69,
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,
0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x61, 0x73, 0x68,
0x12, 0x23, 0x0a, 0x0d, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x61, 0x64, 0x64,
0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63,
0x6b, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78,
0x70, 0x69, 0x72, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6c, 0x74, 0x76,
0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x31, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f,
0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x72,
0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79,
0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x19, 0x0a, 0x08,
0x6e, 0x75, 0x6d, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07,
0x6e, 0x75, 0x6d, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75,
0x72, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65,
0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73,
0x12, 0x3e, 0x0a, 0x0d, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68,
0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x61,
0x74, 0x68, 0x52, 0x0c, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x73,
0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72,
0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03,
0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75,
0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x59, 0x0a,
0x07, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b,
0x69, 0x73, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
0x08, 0x52, 0x0a, 0x69, 0x73, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x19, 0x0a,
0x08, 0x69, 0x73, 0x5f, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52,
0x07, 0x69, 0x73, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x22, 0x12, 0x0a, 0x10, 0x46, 0x65, 0x65, 0x52,
0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x95, 0x02, 0x0a,
0x10, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72,
0x74, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01,
0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x23,
0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f,
0x69, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f,
0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x62, 0x61, 0x73, 0x65,
0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x66, 0x65, 0x65, 0x5f, 0x70,
0x65, 0x72, 0x5f, 0x6d, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x66, 0x65,
0x65, 0x50, 0x65, 0x72, 0x4d, 0x69, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72,
0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, 0x61,
0x74, 0x65, 0x12, 0x31, 0x0a, 0x15, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x62, 0x61,
0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28,
0x05, 0x52, 0x12, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x61, 0x73, 0x65, 0x46, 0x65,
0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2d, 0x0a, 0x13, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64,
0x5f, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6d, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x01,
0x28, 0x05, 0x52, 0x10, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x65, 0x65, 0x50, 0x65,
0x72, 0x4d, 0x69, 0x6c, 0x22, 0xb5, 0x01, 0x0a, 0x11, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f,
0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0c, 0x63, 0x68,
0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x6e,
0x65, 0x6c, 0x46, 0x65, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x64, 0x61, 0x79, 0x5f, 0x66, 0x65,
0x65, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x64, 0x61, 0x79,
0x46, 0x65, 0x65, 0x53, 0x75, 0x6d, 0x12, 0x20, 0x0a, 0x0c, 0x77, 0x65, 0x65, 0x6b, 0x5f, 0x66,
0x65, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x77, 0x65,
0x65, 0x6b, 0x46, 0x65, 0x65, 0x53, 0x75, 0x6d, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x6f, 0x6e, 0x74,
0x68, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52,
0x0b, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x46, 0x65, 0x65, 0x53, 0x75, 0x6d, 0x22, 0x52, 0x0a, 0x0a,
0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x65, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x62, 0x61,
0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
0x05, 0x52, 0x0b, 0x62, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x20,
0x0a, 0x0c, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x02,
0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x50, 0x70, 0x6d,
0x22, 0xda, 0x03, 0x0a, 0x13, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x06, 0x67, 0x6c, 0x6f, 0x62,
0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x06, 0x67, 0x6c, 0x6f, 0x62,
0x61, 0x6c, 0x12, 0x34, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43,
0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x09, 0x63,
0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x62, 0x61, 0x73, 0x65,
0x5f, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52,
0x0b, 0x62, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08,
0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07,
0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x66, 0x65, 0x65, 0x5f, 0x72,
0x61, 0x74, 0x65, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x66,
0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x50, 0x70, 0x6d, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x69, 0x6d,
0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01,
0x28, 0x0d, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x44, 0x65, 0x6c, 0x74,
0x61, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73,
0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x48, 0x74, 0x6c,
0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c,
0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x69,
0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x69, 0x6e,
0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x69,
0x66, 0x69, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x6d, 0x69, 0x6e, 0x48,
0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64,
0x12, 0x32, 0x0a, 0x0b, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x65, 0x65, 0x18,
0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e,
0x62, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x65, 0x65, 0x52, 0x0a, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e,
0x64, 0x46, 0x65, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x6d,
0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x64, 0x67, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28,
0x08, 0x52, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67,
0x45, 0x64, 0x67, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x22, 0x8c, 0x01,
0x0a, 0x0c, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x2b,
0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e,
0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x06, 0x72,
0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x6c, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72,
0x65, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x75, 0x70, 0x64,
0x61, 0x74, 0x65, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x52, 0x0a, 0x14,
0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x75,
0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73,
0x22, 0xc9, 0x01, 0x0a, 0x18, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48,
0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a,
0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x04, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08,
0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07,
0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x64, 0x65, 0x78,
0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x69,
0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x6e, 0x75,
0x6d, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01,
0x28, 0x0d, 0x52, 0x0c, 0x6e, 0x75, 0x6d, 0x4d, 0x61, 0x78, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73,
0x12, 0x2a, 0x0a, 0x11, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6c,
0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x70, 0x65, 0x65,
0x72, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x22, 0x85, 0x03, 0x0a,
0x0f, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74,
0x12, 0x20, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20,
0x01, 0x28, 0x04, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
0x6d, 0x70, 0x12, 0x20, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x5f, 0x69, 0x6e,
0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e,
0x49, 0x64, 0x49, 0x6e, 0x12, 0x22, 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x5f,
0x6f, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x09, 0x63,
0x68, 0x61, 0x6e, 0x49, 0x64, 0x4f, 0x75, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x61, 0x6d, 0x74, 0x5f,
0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x61, 0x6d, 0x74, 0x49, 0x6e, 0x12,
0x17, 0x0a, 0x07, 0x61, 0x6d, 0x74, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04,
0x52, 0x06, 0x61, 0x6d, 0x74, 0x4f, 0x75, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x65, 0x65, 0x18,
0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x66, 0x65, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65,
0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x66, 0x65,
0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x61, 0x6d, 0x74, 0x5f, 0x69, 0x6e, 0x5f,
0x6d, 0x73, 0x61, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x61, 0x6d, 0x74, 0x49,
0x6e, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x61, 0x6d, 0x74, 0x5f, 0x6f, 0x75, 0x74,
0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x61, 0x6d, 0x74,
0x4f, 0x75, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x73,
0x74, 0x61, 0x6d, 0x70, 0x5f, 0x6e, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x74,
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x4e, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x70, 0x65,
0x65, 0x72, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x69, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0b, 0x70, 0x65, 0x65, 0x72, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x49, 0x6e, 0x12, 0x24,
0x0a, 0x0e, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6f, 0x75, 0x74,
0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x41, 0x6c, 0x69, 0x61,
0x73, 0x4f, 0x75, 0x74, 0x22, 0x8c, 0x01, 0x0a, 0x19, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64,
0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x43, 0x0a, 0x11, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67,
0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67,
0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x10, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e,
0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f,
0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0d, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x49, 0x6e,
0x64, 0x65, 0x78, 0x22, 0x50, 0x0a, 0x1a, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61,
0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68,
0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e,
0x50, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x64, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70,
0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52,
0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x68,
0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x0a, 0x63, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, 0x73, 0x0a, 0x0f, 0x4d,
0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x34,
0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e,
0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f,
0x69, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x63, 0x68,
0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x0f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70,
0x22, 0x19, 0x0a, 0x17, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x45, 0x78,
0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x9f, 0x01, 0x0a, 0x12,
0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68,
0x6f, 0x74, 0x12, 0x45, 0x0a, 0x13, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x5f, 0x63, 0x68, 0x61,
0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42,
0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x52, 0x11, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x43, 0x68,
0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x42, 0x0a, 0x11, 0x6d, 0x75, 0x6c,
0x74, 0x69, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x6c,
0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x0f, 0x6d, 0x75,
0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, 0x49, 0x0a,
0x0e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12,
0x37, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18,
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68,
0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x0b, 0x63, 0x68, 0x61,
0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x22, 0x8e, 0x01, 0x0a, 0x18, 0x52, 0x65, 0x73,
0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61,
0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75,
0x70, 0x73, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70,
0x73, 0x12, 0x2c, 0x0a, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f,
0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0f,
0x6d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x42,
0x08, 0x0a, 0x06, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, 0x3a, 0x0a, 0x15, 0x52, 0x65, 0x73,
0x74, 0x6f, 0x72, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72,
0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x52, 0x65, 0x73,
0x74, 0x6f, 0x72, 0x65, 0x64, 0x22, 0x1b, 0x0a, 0x19, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
0x6f, 0x6e, 0x22, 0x3b, 0x0a, 0x18, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, 0x6e,
0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f,
0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20,
0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x22,
0x44, 0x0a, 0x12, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69,
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a,
0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61,
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xb0, 0x01, 0x0a, 0x13, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61,
0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a,
0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72,
0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70,
0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x6f,
0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52,
0x09, 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1a, 0x61, 0x6c,
0x6c, 0x6f, 0x77, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x65, 0x72,
0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18,
0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x50, 0x65, 0x72,
0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x32, 0x0a, 0x14, 0x42, 0x61, 0x6b, 0x65,
0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x22, 0x18, 0x0a, 0x16,
0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3b, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61,
0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64,
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x04, 0x52, 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79,
0x49, 0x64, 0x73, 0x22, 0x39, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63,
0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e,
0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x22, 0x34,
0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e,
0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65,
0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c,
0x65, 0x74, 0x65, 0x64, 0x22, 0x55, 0x0a, 0x16, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e,
0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x3b,
0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61,
0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b,
0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x18, 0x0a, 0x16, 0x4c,
0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xe4, 0x01, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65,
0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x64, 0x0a, 0x12, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x6d,
0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73,
0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65,
0x74, 0x68, 0x6f, 0x64, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x50, 0x65, 0x72, 0x6d,
0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x63, 0x0a, 0x16, 0x4d, 0x65, 0x74, 0x68, 0x6f,
0x64, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72,
0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
0x6b, 0x65, 0x79, 0x12, 0x33, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72,
0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73,
0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xcc, 0x08, 0x0a,
0x07, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x2e, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46,
0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f,
0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x3b, 0x0a, 0x0e, 0x63, 0x68, 0x61, 0x6e,
0x6e, 0x65, 0x6c, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55,
0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73,
0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x73,
0x61, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x68, 0x61, 0x5f,
0x32, 0x35, 0x36, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6f, 0x6e, 0x69, 0x6f, 0x6e,
0x53, 0x68, 0x61, 0x32, 0x35, 0x36, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65,
0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x63, 0x6c, 0x74,
0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73,
0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x30, 0x0a,
0x14, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f,
0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x66, 0x61, 0x69,
0x6c, 0x75, 0x72, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12,
0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52,
0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x8b, 0x06, 0x0a, 0x0b, 0x46, 0x61, 0x69, 0x6c,
0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x53, 0x45, 0x52,
0x56, 0x45, 0x44, 0x10, 0x00, 0x12, 0x28, 0x0a, 0x24, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45,
0x43, 0x54, 0x5f, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x50, 0x41,
0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53, 0x10, 0x01, 0x12,
0x1c, 0x0a, 0x18, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x50, 0x41, 0x59,
0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x02, 0x12, 0x1f, 0x0a,
0x1b, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54,
0x5f, 0x43, 0x4c, 0x54, 0x56, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x10, 0x03, 0x12, 0x1f,
0x0a, 0x1b, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43,
0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x04, 0x12,
0x19, 0x0a, 0x15, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f,
0x54, 0x4f, 0x4f, 0x5f, 0x53, 0x4f, 0x4f, 0x4e, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e,
0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x52, 0x45, 0x41, 0x4c, 0x4d, 0x10, 0x06, 0x12, 0x13, 0x0a,
0x0f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x53, 0x4f, 0x4f, 0x4e,
0x10, 0x07, 0x12, 0x19, 0x0a, 0x15, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e,
0x49, 0x4f, 0x4e, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x08, 0x12, 0x16, 0x0a,
0x12, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x48,
0x4d, 0x41, 0x43, 0x10, 0x09, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44,
0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x0a, 0x12, 0x18, 0x0a, 0x14,
0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x42, 0x45, 0x4c, 0x4f, 0x57, 0x5f, 0x4d, 0x49, 0x4e,
0x49, 0x4d, 0x55, 0x4d, 0x10, 0x0b, 0x12, 0x14, 0x0a, 0x10, 0x46, 0x45, 0x45, 0x5f, 0x49, 0x4e,
0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x10, 0x0c, 0x12, 0x19, 0x0a, 0x15,
0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x43, 0x4c, 0x54, 0x56, 0x5f, 0x45,
0x58, 0x50, 0x49, 0x52, 0x59, 0x10, 0x0d, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x48, 0x41, 0x4e, 0x4e,
0x45, 0x4c, 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x0e, 0x12, 0x1d, 0x0a,
0x19, 0x54, 0x45, 0x4d, 0x50, 0x4f, 0x52, 0x41, 0x52, 0x59, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e,
0x45, 0x4c, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x0f, 0x12, 0x21, 0x0a, 0x1d,
0x52, 0x45, 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x45,
0x41, 0x54, 0x55, 0x52, 0x45, 0x5f, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4e, 0x47, 0x10, 0x10, 0x12,
0x24, 0x0a, 0x20, 0x52, 0x45, 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, 0x5f, 0x43, 0x48, 0x41, 0x4e,
0x4e, 0x45, 0x4c, 0x5f, 0x46, 0x45, 0x41, 0x54, 0x55, 0x52, 0x45, 0x5f, 0x4d, 0x49, 0x53, 0x53,
0x49, 0x4e, 0x47, 0x10, 0x11, 0x12, 0x15, 0x0a, 0x11, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e,
0x5f, 0x4e, 0x45, 0x58, 0x54, 0x5f, 0x50, 0x45, 0x45, 0x52, 0x10, 0x12, 0x12, 0x1a, 0x0a, 0x16,
0x54, 0x45, 0x4d, 0x50, 0x4f, 0x52, 0x41, 0x52, 0x59, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x46,
0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x13, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x45, 0x52, 0x4d,
0x41, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55,
0x52, 0x45, 0x10, 0x14, 0x12, 0x1d, 0x0a, 0x19, 0x50, 0x45, 0x52, 0x4d, 0x41, 0x4e, 0x45, 0x4e,
0x54, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52,
0x45, 0x10, 0x15, 0x12, 0x12, 0x0a, 0x0e, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f,
0x4f, 0x5f, 0x46, 0x41, 0x52, 0x10, 0x16, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x50, 0x50, 0x5f, 0x54,
0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x17, 0x12, 0x19, 0x0a, 0x15, 0x49, 0x4e, 0x56, 0x41,
0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41,
0x44, 0x10, 0x18, 0x12, 0x1a, 0x0a, 0x16, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f,
0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x42, 0x4c, 0x49, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x19, 0x12,
0x15, 0x0a, 0x10, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x46, 0x41, 0x49, 0x4c,
0x55, 0x52, 0x45, 0x10, 0xe5, 0x07, 0x12, 0x14, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57,
0x4e, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0xe6, 0x07, 0x12, 0x17, 0x0a, 0x12,
0x55, 0x4e, 0x52, 0x45, 0x41, 0x44, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55,
0x52, 0x45, 0x10, 0xe7, 0x07, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0xb3, 0x03, 0x0a, 0x0d,
0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a,
0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63,
0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68,
0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52,
0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73,
0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65,
0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68,
0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28,
0x0d, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12,
0x26, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c,
0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f,
0x63, 0x6b, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x74, 0x6c, 0x63, 0x5f,
0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01,
0x28, 0x04, 0x52, 0x0f, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x4d,
0x73, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x18,
0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x62, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x12, 0x19,
0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d,
0x52, 0x07, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x74, 0x6c,
0x63, 0x5f, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0b,
0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x61, 0x78, 0x69, 0x6d, 0x75,
0x6d, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x6f,
0x70, 0x61, 0x71, 0x75, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x0f, 0x65, 0x78, 0x74, 0x72, 0x61, 0x4f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x44, 0x61, 0x74,
0x61, 0x22, 0x5d, 0x0a, 0x0a, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x64, 0x12,
0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05,
0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67,
0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x03, 0x6f, 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x09, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x52, 0x03, 0x6f, 0x70, 0x73,
0x22, 0x36, 0x0a, 0x02, 0x4f, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x18,
0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52,
0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x8e, 0x01, 0x0a, 0x13, 0x43, 0x68, 0x65,
0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x0b,
0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f,
0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65,
0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x66, 0x75, 0x6c,
0x6c, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x66,
0x75, 0x6c, 0x6c, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x22, 0x2c, 0x0a, 0x14, 0x43, 0x68, 0x65,
0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,
0x52, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x22, 0xf4, 0x02, 0x0a, 0x14, 0x52, 0x50, 0x43, 0x4d,
0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12,
0x21, 0x0a, 0x0c, 0x72, 0x61, 0x77, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x72, 0x61, 0x77, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f,
0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x17, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x61, 0x76,
0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20,
0x01, 0x28, 0x09, 0x52, 0x15, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x76, 0x65, 0x61,
0x74, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x0a, 0x0b, 0x73, 0x74,
0x72, 0x65, 0x61, 0x6d, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x41, 0x75,
0x74, 0x68, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x41, 0x75, 0x74, 0x68,
0x12, 0x2d, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x73,
0x73, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x2f, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x73,
0x73, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x23, 0x0a, 0x0c, 0x72, 0x65, 0x67, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65,
0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0b, 0x72, 0x65, 0x67, 0x43, 0x6f, 0x6d,
0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18,
0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6d, 0x73, 0x67, 0x49, 0x64, 0x42, 0x10, 0x0a, 0x0e,
0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0x34,
0x0a, 0x0a, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x41, 0x75, 0x74, 0x68, 0x12, 0x26, 0x0a, 0x0f,
0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x75, 0x72, 0x69, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x46, 0x75, 0x6c,
0x6c, 0x55, 0x72, 0x69, 0x22, 0xab, 0x01, 0x0a, 0x0a, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x73, 0x73,
0x61, 0x67, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x66, 0x75,
0x6c, 0x6c, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x65,
0x74, 0x68, 0x6f, 0x64, 0x46, 0x75, 0x6c, 0x6c, 0x55, 0x72, 0x69, 0x12, 0x1d, 0x0a, 0x0a, 0x73,
0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x72, 0x70, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52,
0x09, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x70, 0x63, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x79,
0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74,
0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x69, 0x61,
0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x65, 0x72,
0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x65, 0x72,
0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x45, 0x72, 0x72,
0x6f, 0x72, 0x22, 0xc0, 0x01, 0x0a, 0x15, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65,
0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x0a,
0x72, 0x65, 0x66, 0x5f, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
0x52, 0x08, 0x72, 0x65, 0x66, 0x4d, 0x73, 0x67, 0x49, 0x64, 0x12, 0x3b, 0x0a, 0x08, 0x72, 0x65,
0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52,
0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x08, 0x72,
0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x36, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x64, 0x62,
0x61, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x46, 0x65, 0x65, 0x64, 0x62,
0x61, 0x63, 0x6b, 0x48, 0x00, 0x52, 0x08, 0x66, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x42,
0x14, 0x0a, 0x12, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x6d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xa6, 0x01, 0x0a, 0x16, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65,
0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x6e,
0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x69, 0x64, 0x64, 0x6c,
0x65, 0x77, 0x61, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x1b, 0x63, 0x75, 0x73,
0x74, 0x6f, 0x6d, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x5f, 0x63, 0x61, 0x76,
0x65, 0x61, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x18,
0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x43, 0x61,
0x76, 0x65, 0x61, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x61, 0x64,
0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08,
0x52, 0x0c, 0x72, 0x65, 0x61, 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x22, 0x8b,
0x01, 0x0a, 0x11, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x46, 0x65, 0x65, 0x64,
0x62, 0x61, 0x63, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65,
0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x16, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65,
0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18,
0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65,
0x6e, 0x74, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x2a, 0xcb, 0x02, 0x0a,
0x10, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x54, 0x79, 0x70,
0x65, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45,
0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x00, 0x12, 0x1b,
0x0a, 0x17, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x43,
0x52, 0x49, 0x50, 0x54, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x26, 0x0a, 0x22, 0x53,
0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45,
0x53, 0x53, 0x5f, 0x56, 0x30, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53,
0x48, 0x10, 0x02, 0x12, 0x26, 0x0a, 0x22, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59,
0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x56, 0x30, 0x5f, 0x53, 0x43,
0x52, 0x49, 0x50, 0x54, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12, 0x53,
0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45,
0x59, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59,
0x50, 0x45, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x53, 0x49, 0x47, 0x10, 0x05, 0x12, 0x18, 0x0a,
0x14, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4e, 0x55, 0x4c,
0x4c, 0x44, 0x41, 0x54, 0x41, 0x10, 0x06, 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x43, 0x52, 0x49, 0x50,
0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4e, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x4e, 0x44,
0x41, 0x52, 0x44, 0x10, 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f,
0x54, 0x59, 0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x55, 0x4e, 0x4b,
0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x08, 0x12, 0x22, 0x0a, 0x1e, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54,
0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x56, 0x31,
0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x10, 0x09, 0x2a, 0x62, 0x0a, 0x15, 0x43, 0x6f,
0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74,
0x65, 0x67, 0x79, 0x12, 0x1e, 0x0a, 0x1a, 0x53, 0x54, 0x52, 0x41, 0x54, 0x45, 0x47, 0x59, 0x5f,
0x55, 0x53, 0x45, 0x5f, 0x47, 0x4c, 0x4f, 0x42, 0x41, 0x4c, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49,
0x47, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x54, 0x52, 0x41, 0x54, 0x45, 0x47, 0x59, 0x5f,
0x4c, 0x41, 0x52, 0x47, 0x45, 0x53, 0x54, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, 0x52,
0x41, 0x54, 0x45, 0x47, 0x59, 0x5f, 0x52, 0x41, 0x4e, 0x44, 0x4f, 0x4d, 0x10, 0x02, 0x2a, 0xac,
0x01, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17,
0x0a, 0x13, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59,
0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x4e, 0x45, 0x53, 0x54, 0x45,
0x44, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12,
0x1e, 0x0a, 0x1a, 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53,
0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x02, 0x12,
0x1d, 0x0a, 0x19, 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44, 0x5f, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44,
0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 0x12, 0x12,
0x0a, 0x0e, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59,
0x10, 0x04, 0x12, 0x19, 0x0a, 0x15, 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44, 0x5f, 0x54, 0x41, 0x50,
0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x10, 0x05, 0x2a, 0xa8, 0x01,
0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65,
0x12, 0x1b, 0x0a, 0x17, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x43, 0x4f, 0x4d, 0x4d,
0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x0a, 0x0a,
0x06, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41,
0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x02,
0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x10, 0x03, 0x12, 0x19, 0x0a,
0x15, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x45, 0x4e, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x44,
0x5f, 0x4c, 0x45, 0x41, 0x53, 0x45, 0x10, 0x04, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x49, 0x4d, 0x50,
0x4c, 0x45, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x10, 0x05, 0x12, 0x1a, 0x0a, 0x16,
0x53, 0x49, 0x4d, 0x50, 0x4c, 0x45, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4f,
0x56, 0x45, 0x52, 0x4c, 0x41, 0x59, 0x10, 0x06, 0x2a, 0x61, 0x0a, 0x09, 0x49, 0x6e, 0x69, 0x74,
0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54,
0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f,
0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x10,
0x01, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x52,
0x45, 0x4d, 0x4f, 0x54, 0x45, 0x10, 0x02, 0x12, 0x12, 0x0a, 0x0e, 0x49, 0x4e, 0x49, 0x54, 0x49,
0x41, 0x54, 0x4f, 0x52, 0x5f, 0x42, 0x4f, 0x54, 0x48, 0x10, 0x03, 0x2a, 0x60, 0x0a, 0x0e, 0x52,
0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a,
0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12,
0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x49,
0x4e, 0x43, 0x4f, 0x4d, 0x49, 0x4e, 0x47, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x10, 0x02, 0x12, 0x11,
0x0a, 0x0d, 0x4f, 0x55, 0x54, 0x47, 0x4f, 0x49, 0x4e, 0x47, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x10,
0x03, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, 0x04, 0x2a, 0x71, 0x0a,
0x11, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x63, 0x6f,
0x6d, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x55, 0x54, 0x43, 0x4f, 0x4d, 0x45, 0x5f, 0x55, 0x4e,
0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4c, 0x41, 0x49, 0x4d,
0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x45,
0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x41, 0x42, 0x41, 0x4e, 0x44, 0x4f, 0x4e, 0x45, 0x44,
0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x46, 0x49, 0x52, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x47,
0x45, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x05,
0x2a, 0x39, 0x0a, 0x0e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79,
0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12,
0x1a, 0x0a, 0x16, 0x42, 0x45, 0x54, 0x57, 0x45, 0x45, 0x4e, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x43,
0x45, 0x4e, 0x54, 0x52, 0x41, 0x4c, 0x49, 0x54, 0x59, 0x10, 0x01, 0x2a, 0x3b, 0x0a, 0x10, 0x49,
0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12,
0x0c, 0x0a, 0x08, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a,
0x07, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x41,
0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x2a, 0xf6, 0x01, 0x0a, 0x14, 0x50, 0x61, 0x79,
0x6d, 0x65, 0x6e, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f,
0x6e, 0x12, 0x17, 0x0a, 0x13, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41,
0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x46, 0x41,
0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x49, 0x4d,
0x45, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x1b, 0x0a, 0x17, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52,
0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x4f, 0x55, 0x54,
0x45, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52,
0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x2c, 0x0a,
0x28, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f,
0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e,
0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53, 0x10, 0x04, 0x12, 0x27, 0x0a, 0x23, 0x46,
0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e,
0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e,
0x43, 0x45, 0x10, 0x05, 0x12, 0x1b, 0x0a, 0x17, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f,
0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10,
0x06, 0x2a, 0x89, 0x05, 0x0a, 0x0a, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, 0x74,
0x12, 0x18, 0x0a, 0x14, 0x44, 0x41, 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x53, 0x5f, 0x50, 0x52, 0x4f,
0x54, 0x45, 0x43, 0x54, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x41,
0x54, 0x41, 0x4c, 0x4f, 0x53, 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x45, 0x43, 0x54, 0x5f, 0x4f,
0x50, 0x54, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x5f,
0x52, 0x4f, 0x55, 0x49, 0x4e, 0x47, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x10, 0x03, 0x12, 0x1f, 0x0a,
0x1b, 0x55, 0x50, 0x46, 0x52, 0x4f, 0x4e, 0x54, 0x5f, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57,
0x4e, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x04, 0x12, 0x1f,
0x0a, 0x1b, 0x55, 0x50, 0x46, 0x52, 0x4f, 0x4e, 0x54, 0x5f, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f,
0x57, 0x4e, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x05, 0x12,
0x16, 0x0a, 0x12, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45,
0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x47, 0x4f, 0x53, 0x53, 0x49,
0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x07, 0x12,
0x11, 0x0a, 0x0d, 0x54, 0x4c, 0x56, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x51,
0x10, 0x08, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x4c, 0x56, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f,
0x4f, 0x50, 0x54, 0x10, 0x09, 0x12, 0x1a, 0x0a, 0x16, 0x45, 0x58, 0x54, 0x5f, 0x47, 0x4f, 0x53,
0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10,
0x0a, 0x12, 0x1a, 0x0a, 0x16, 0x45, 0x58, 0x54, 0x5f, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f,
0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0b, 0x12, 0x19, 0x0a,
0x15, 0x53, 0x54, 0x41, 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b,
0x45, 0x59, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x0c, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x54, 0x41, 0x54,
0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x4f, 0x50,
0x54, 0x10, 0x0d, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41,
0x44, 0x44, 0x52, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x0e, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x41, 0x59,
0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0f, 0x12,
0x0b, 0x0a, 0x07, 0x4d, 0x50, 0x50, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x10, 0x12, 0x0b, 0x0a, 0x07,
0x4d, 0x50, 0x50, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x11, 0x12, 0x16, 0x0a, 0x12, 0x57, 0x55, 0x4d,
0x42, 0x4f, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10,
0x12, 0x12, 0x16, 0x0a, 0x12, 0x57, 0x55, 0x4d, 0x42, 0x4f, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e,
0x45, 0x4c, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x13, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x4e, 0x43,
0x48, 0x4f, 0x52, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x14, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x4e,
0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x15, 0x12, 0x1d, 0x0a, 0x19, 0x41,
0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x5a, 0x45, 0x52, 0x4f, 0x5f, 0x46, 0x45, 0x45, 0x5f,
0x48, 0x54, 0x4c, 0x43, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x16, 0x12, 0x1d, 0x0a, 0x19, 0x41, 0x4e,
0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x5a, 0x45, 0x52, 0x4f, 0x5f, 0x46, 0x45, 0x45, 0x5f, 0x48,
0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x17, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x4f, 0x55,
0x54, 0x45, 0x5f, 0x42, 0x4c, 0x49, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x52, 0x45, 0x51, 0x55,
0x49, 0x52, 0x45, 0x44, 0x10, 0x18, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x5f,
0x42, 0x4c, 0x49, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x41,
0x4c, 0x10, 0x19, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4d, 0x50, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x1e,
0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4d, 0x50, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x1f, 0x2a, 0xac, 0x01,
0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12,
0x1a, 0x0a, 0x16, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52,
0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x55,
0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x50, 0x45,
0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x55, 0x50, 0x44, 0x41, 0x54,
0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f,
0x55, 0x4e, 0x44, 0x10, 0x02, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f,
0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c,
0x5f, 0x45, 0x52, 0x52, 0x10, 0x03, 0x12, 0x24, 0x0a, 0x20, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45,
0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44,
0x5f, 0x50, 0x41, 0x52, 0x41, 0x4d, 0x45, 0x54, 0x45, 0x52, 0x10, 0x04, 0x32, 0xc3, 0x27, 0x0a,
0x09, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x4a, 0x0a, 0x0d, 0x57, 0x61,
0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43,
0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e,
0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69,
0x6c, 0x73, 0x12, 0x44, 0x0a, 0x0b, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65,
0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61,
0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x53, 0x65, 0x6e, 0x64,
0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65,
0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18,
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74,
0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55,
0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c,
0x0a, 0x15, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73,
0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54,
0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x01, 0x12, 0x3b, 0x0a, 0x08,
0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e,
0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0a, 0x4e, 0x65, 0x77,
0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b,
0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53,
0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73,
0x61, 0x67, 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69,
0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d,
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44,
0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x19, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65,
0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65,
0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44,
0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73,
0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73,
0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65,
0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65,
0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73,
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x38, 0x0a, 0x07,
0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16,
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x44, 0x65, 0x62,
0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47,
0x65, 0x74, 0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65,
0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x50, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e,
0x66, 0x6f, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65,
0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63,
0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x50, 0x0a, 0x0f, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e,
0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e,
0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64,
0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e,
0x65, 0x6c, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74,
0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e,
0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x16,
0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43,
0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63,
0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61,
0x74, 0x65, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x0e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68,
0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43,
0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f,
0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0f, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e,
0x65, 0x6c, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f,
0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x43, 0x0a, 0x0b, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68,
0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70,
0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x74, 0x61,
0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x53, 0x0a, 0x10, 0x42,
0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12,
0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65,
0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65,
0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x4c, 0x0a, 0x10, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65,
0x53, 0x74, 0x65, 0x70, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e,
0x64, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73,
0x67, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e,
0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x73, 0x70, 0x12, 0x50,
0x0a, 0x0f, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x6f,
0x72, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a,
0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41,
0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01,
0x12, 0x46, 0x0a, 0x0c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68,
0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x0e, 0x41, 0x62, 0x61, 0x6e,
0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50,
0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53,
0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, 0x01, 0x12, 0x3f, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64,
0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x12, 0x2e, 0x6c, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x46, 0x0a, 0x0b, 0x53, 0x65, 0x6e,
0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30,
0x01, 0x12, 0x46, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65,
0x53, 0x79, 0x6e, 0x63, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e,
0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x37, 0x0a, 0x0a, 0x41, 0x64, 0x64,
0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x45, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63,
0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49,
0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63,
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x0d, 0x4c, 0x6f, 0x6f,
0x6b, 0x75, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x1a, 0x0e,
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x41,
0x0a, 0x11, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69,
0x63, 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f,
0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a,
0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x30,
0x01, 0x12, 0x32, 0x0a, 0x0c, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x50, 0x61, 0x79, 0x52, 0x65,
0x71, 0x12, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71,
0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x1a, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50,
0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79,
0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69,
0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61,
0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a,
0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12,
0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61,
0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65,
0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x11, 0x44, 0x65,
0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12,
0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c,
0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41,
0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x40, 0x0a, 0x0d, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x47, 0x72,
0x61, 0x70, 0x68, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e,
0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47,
0x72, 0x61, 0x70, 0x68, 0x12, 0x47, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x4d,
0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e,
0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65,
0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a,
0x0b, 0x47, 0x65, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61,
0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x12, 0x36, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4e,
0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f,
0x12, 0x44, 0x0a, 0x0b, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12,
0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75,
0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4e, 0x65, 0x74,
0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x74, 0x77,
0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x35, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x70, 0x44,
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74,
0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57,
0x0a, 0x15, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e,
0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x47, 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x53, 0x75, 0x62,
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x47, 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x55,
0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x41, 0x0a, 0x0a, 0x44, 0x65, 0x62, 0x75, 0x67,
0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65,
0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76,
0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x46, 0x65,
0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f,
0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x13, 0x55, 0x70,
0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63,
0x79, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79,
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61,
0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x11, 0x46, 0x6f,
0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12,
0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69,
0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64,
0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x4e, 0x0a, 0x13, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e,
0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x21, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42,
0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b,
0x75, 0x70, 0x12, 0x54, 0x0a, 0x17, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x6c, 0x6c, 0x43,
0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x1e, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70,
0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70,
0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x4e, 0x0a, 0x10, 0x56, 0x65, 0x72, 0x69,
0x66, 0x79, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x19, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53,
0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x74,
0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70,
0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72,
0x65, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f,
0x72, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x58, 0x0a, 0x17, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61,
0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x20, 0x2e, 0x6c, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75,
0x70, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x19, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70,
0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x30, 0x01, 0x12, 0x47, 0x0a, 0x0c, 0x42, 0x61,
0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42,
0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72,
0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c,
0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69,
0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d,
0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e,
0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e,
0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x4c, 0x69,
0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73,
0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73,
0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x18,
0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72,
0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x65,
0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x56, 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x50, 0x43,
0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x56, 0x0a, 0x11, 0x53, 0x65, 0x6e,
0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f,
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f,
0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74,
0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x58, 0x0a, 0x17, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x75,
0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x12, 0x25, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x75,
0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x73, 0x74,
0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x30, 0x01, 0x12, 0x44, 0x0a, 0x0b, 0x4c,
0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69,
0x73, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x5f, 0x0a, 0x14, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x48, 0x74, 0x6c, 0x63, 0x52,
0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f,
0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x48, 0x74, 0x6c, 0x63,
0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72,
0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
}
var (
file_lightning_proto_rawDescOnce sync.Once
file_lightning_proto_rawDescData = file_lightning_proto_rawDesc
)
func file_lightning_proto_rawDescGZIP() []byte {
file_lightning_proto_rawDescOnce.Do(func() {
file_lightning_proto_rawDescData = protoimpl.X.CompressGZIP(file_lightning_proto_rawDescData)
})
return file_lightning_proto_rawDescData
}
var file_lightning_proto_enumTypes = make([]protoimpl.EnumInfo, 21)
var file_lightning_proto_msgTypes = make([]protoimpl.MessageInfo, 228)
var file_lightning_proto_goTypes = []interface{}{
(OutputScriptType)(0), // 0: lnrpc.OutputScriptType
(CoinSelectionStrategy)(0), // 1: lnrpc.CoinSelectionStrategy
(AddressType)(0), // 2: lnrpc.AddressType
(CommitmentType)(0), // 3: lnrpc.CommitmentType
(Initiator)(0), // 4: lnrpc.Initiator
(ResolutionType)(0), // 5: lnrpc.ResolutionType
(ResolutionOutcome)(0), // 6: lnrpc.ResolutionOutcome
(NodeMetricType)(0), // 7: lnrpc.NodeMetricType
(InvoiceHTLCState)(0), // 8: lnrpc.InvoiceHTLCState
(PaymentFailureReason)(0), // 9: lnrpc.PaymentFailureReason
(FeatureBit)(0), // 10: lnrpc.FeatureBit
(UpdateFailure)(0), // 11: lnrpc.UpdateFailure
(ChannelCloseSummary_ClosureType)(0), // 12: lnrpc.ChannelCloseSummary.ClosureType
(Peer_SyncType)(0), // 13: lnrpc.Peer.SyncType
(PeerEvent_EventType)(0), // 14: lnrpc.PeerEvent.EventType
(PendingChannelsResponse_ForceClosedChannel_AnchorState)(0), // 15: lnrpc.PendingChannelsResponse.ForceClosedChannel.AnchorState
(ChannelEventUpdate_UpdateType)(0), // 16: lnrpc.ChannelEventUpdate.UpdateType
(Invoice_InvoiceState)(0), // 17: lnrpc.Invoice.InvoiceState
(Payment_PaymentStatus)(0), // 18: lnrpc.Payment.PaymentStatus
(HTLCAttempt_HTLCStatus)(0), // 19: lnrpc.HTLCAttempt.HTLCStatus
(Failure_FailureCode)(0), // 20: lnrpc.Failure.FailureCode
(*LookupHtlcResolutionRequest)(nil), // 21: lnrpc.LookupHtlcResolutionRequest
(*LookupHtlcResolutionResponse)(nil), // 22: lnrpc.LookupHtlcResolutionResponse
(*SubscribeCustomMessagesRequest)(nil), // 23: lnrpc.SubscribeCustomMessagesRequest
(*CustomMessage)(nil), // 24: lnrpc.CustomMessage
(*SendCustomMessageRequest)(nil), // 25: lnrpc.SendCustomMessageRequest
(*SendCustomMessageResponse)(nil), // 26: lnrpc.SendCustomMessageResponse
(*Utxo)(nil), // 27: lnrpc.Utxo
(*OutputDetail)(nil), // 28: lnrpc.OutputDetail
(*Transaction)(nil), // 29: lnrpc.Transaction
(*GetTransactionsRequest)(nil), // 30: lnrpc.GetTransactionsRequest
(*TransactionDetails)(nil), // 31: lnrpc.TransactionDetails
(*FeeLimit)(nil), // 32: lnrpc.FeeLimit
(*SendRequest)(nil), // 33: lnrpc.SendRequest
(*SendResponse)(nil), // 34: lnrpc.SendResponse
(*SendToRouteRequest)(nil), // 35: lnrpc.SendToRouteRequest
(*ChannelAcceptRequest)(nil), // 36: lnrpc.ChannelAcceptRequest
(*ChannelAcceptResponse)(nil), // 37: lnrpc.ChannelAcceptResponse
(*ChannelPoint)(nil), // 38: lnrpc.ChannelPoint
(*OutPoint)(nil), // 39: lnrpc.OutPoint
(*PreviousOutPoint)(nil), // 40: lnrpc.PreviousOutPoint
(*LightningAddress)(nil), // 41: lnrpc.LightningAddress
(*EstimateFeeRequest)(nil), // 42: lnrpc.EstimateFeeRequest
(*EstimateFeeResponse)(nil), // 43: lnrpc.EstimateFeeResponse
(*SendManyRequest)(nil), // 44: lnrpc.SendManyRequest
(*SendManyResponse)(nil), // 45: lnrpc.SendManyResponse
(*SendCoinsRequest)(nil), // 46: lnrpc.SendCoinsRequest
(*SendCoinsResponse)(nil), // 47: lnrpc.SendCoinsResponse
(*ListUnspentRequest)(nil), // 48: lnrpc.ListUnspentRequest
(*ListUnspentResponse)(nil), // 49: lnrpc.ListUnspentResponse
(*NewAddressRequest)(nil), // 50: lnrpc.NewAddressRequest
(*NewAddressResponse)(nil), // 51: lnrpc.NewAddressResponse
(*SignMessageRequest)(nil), // 52: lnrpc.SignMessageRequest
(*SignMessageResponse)(nil), // 53: lnrpc.SignMessageResponse
(*VerifyMessageRequest)(nil), // 54: lnrpc.VerifyMessageRequest
(*VerifyMessageResponse)(nil), // 55: lnrpc.VerifyMessageResponse
(*ConnectPeerRequest)(nil), // 56: lnrpc.ConnectPeerRequest
(*ConnectPeerResponse)(nil), // 57: lnrpc.ConnectPeerResponse
(*DisconnectPeerRequest)(nil), // 58: lnrpc.DisconnectPeerRequest
(*DisconnectPeerResponse)(nil), // 59: lnrpc.DisconnectPeerResponse
(*HTLC)(nil), // 60: lnrpc.HTLC
(*ChannelConstraints)(nil), // 61: lnrpc.ChannelConstraints
(*Channel)(nil), // 62: lnrpc.Channel
(*ListChannelsRequest)(nil), // 63: lnrpc.ListChannelsRequest
(*ListChannelsResponse)(nil), // 64: lnrpc.ListChannelsResponse
(*AliasMap)(nil), // 65: lnrpc.AliasMap
(*ListAliasesRequest)(nil), // 66: lnrpc.ListAliasesRequest
(*ListAliasesResponse)(nil), // 67: lnrpc.ListAliasesResponse
(*ChannelCloseSummary)(nil), // 68: lnrpc.ChannelCloseSummary
(*Resolution)(nil), // 69: lnrpc.Resolution
(*ClosedChannelsRequest)(nil), // 70: lnrpc.ClosedChannelsRequest
(*ClosedChannelsResponse)(nil), // 71: lnrpc.ClosedChannelsResponse
(*Peer)(nil), // 72: lnrpc.Peer
(*TimestampedError)(nil), // 73: lnrpc.TimestampedError
(*ListPeersRequest)(nil), // 74: lnrpc.ListPeersRequest
(*ListPeersResponse)(nil), // 75: lnrpc.ListPeersResponse
(*PeerEventSubscription)(nil), // 76: lnrpc.PeerEventSubscription
(*PeerEvent)(nil), // 77: lnrpc.PeerEvent
(*GetInfoRequest)(nil), // 78: lnrpc.GetInfoRequest
(*GetInfoResponse)(nil), // 79: lnrpc.GetInfoResponse
(*GetDebugInfoRequest)(nil), // 80: lnrpc.GetDebugInfoRequest
(*GetDebugInfoResponse)(nil), // 81: lnrpc.GetDebugInfoResponse
(*GetRecoveryInfoRequest)(nil), // 82: lnrpc.GetRecoveryInfoRequest
(*GetRecoveryInfoResponse)(nil), // 83: lnrpc.GetRecoveryInfoResponse
(*Chain)(nil), // 84: lnrpc.Chain
(*ConfirmationUpdate)(nil), // 85: lnrpc.ConfirmationUpdate
(*ChannelOpenUpdate)(nil), // 86: lnrpc.ChannelOpenUpdate
(*CloseOutput)(nil), // 87: lnrpc.CloseOutput
(*ChannelCloseUpdate)(nil), // 88: lnrpc.ChannelCloseUpdate
(*CloseChannelRequest)(nil), // 89: lnrpc.CloseChannelRequest
(*CloseStatusUpdate)(nil), // 90: lnrpc.CloseStatusUpdate
(*PendingUpdate)(nil), // 91: lnrpc.PendingUpdate
(*InstantUpdate)(nil), // 92: lnrpc.InstantUpdate
(*ReadyForPsbtFunding)(nil), // 93: lnrpc.ReadyForPsbtFunding
(*BatchOpenChannelRequest)(nil), // 94: lnrpc.BatchOpenChannelRequest
(*BatchOpenChannel)(nil), // 95: lnrpc.BatchOpenChannel
(*BatchOpenChannelResponse)(nil), // 96: lnrpc.BatchOpenChannelResponse
(*OpenChannelRequest)(nil), // 97: lnrpc.OpenChannelRequest
(*OpenStatusUpdate)(nil), // 98: lnrpc.OpenStatusUpdate
(*KeyLocator)(nil), // 99: lnrpc.KeyLocator
(*KeyDescriptor)(nil), // 100: lnrpc.KeyDescriptor
(*ChanPointShim)(nil), // 101: lnrpc.ChanPointShim
(*PsbtShim)(nil), // 102: lnrpc.PsbtShim
(*FundingShim)(nil), // 103: lnrpc.FundingShim
(*FundingShimCancel)(nil), // 104: lnrpc.FundingShimCancel
(*FundingPsbtVerify)(nil), // 105: lnrpc.FundingPsbtVerify
(*FundingPsbtFinalize)(nil), // 106: lnrpc.FundingPsbtFinalize
(*FundingTransitionMsg)(nil), // 107: lnrpc.FundingTransitionMsg
(*FundingStateStepResp)(nil), // 108: lnrpc.FundingStateStepResp
(*PendingHTLC)(nil), // 109: lnrpc.PendingHTLC
(*PendingChannelsRequest)(nil), // 110: lnrpc.PendingChannelsRequest
(*PendingChannelsResponse)(nil), // 111: lnrpc.PendingChannelsResponse
(*ChannelEventSubscription)(nil), // 112: lnrpc.ChannelEventSubscription
(*ChannelEventUpdate)(nil), // 113: lnrpc.ChannelEventUpdate
(*WalletAccountBalance)(nil), // 114: lnrpc.WalletAccountBalance
(*WalletBalanceRequest)(nil), // 115: lnrpc.WalletBalanceRequest
(*WalletBalanceResponse)(nil), // 116: lnrpc.WalletBalanceResponse
(*Amount)(nil), // 117: lnrpc.Amount
(*ChannelBalanceRequest)(nil), // 118: lnrpc.ChannelBalanceRequest
(*ChannelBalanceResponse)(nil), // 119: lnrpc.ChannelBalanceResponse
(*QueryRoutesRequest)(nil), // 120: lnrpc.QueryRoutesRequest
(*NodePair)(nil), // 121: lnrpc.NodePair
(*EdgeLocator)(nil), // 122: lnrpc.EdgeLocator
(*QueryRoutesResponse)(nil), // 123: lnrpc.QueryRoutesResponse
(*Hop)(nil), // 124: lnrpc.Hop
(*MPPRecord)(nil), // 125: lnrpc.MPPRecord
(*AMPRecord)(nil), // 126: lnrpc.AMPRecord
(*Route)(nil), // 127: lnrpc.Route
(*NodeInfoRequest)(nil), // 128: lnrpc.NodeInfoRequest
(*NodeInfo)(nil), // 129: lnrpc.NodeInfo
(*LightningNode)(nil), // 130: lnrpc.LightningNode
(*NodeAddress)(nil), // 131: lnrpc.NodeAddress
(*RoutingPolicy)(nil), // 132: lnrpc.RoutingPolicy
(*ChannelEdge)(nil), // 133: lnrpc.ChannelEdge
(*ChannelGraphRequest)(nil), // 134: lnrpc.ChannelGraphRequest
(*ChannelGraph)(nil), // 135: lnrpc.ChannelGraph
(*NodeMetricsRequest)(nil), // 136: lnrpc.NodeMetricsRequest
(*NodeMetricsResponse)(nil), // 137: lnrpc.NodeMetricsResponse
(*FloatMetric)(nil), // 138: lnrpc.FloatMetric
(*ChanInfoRequest)(nil), // 139: lnrpc.ChanInfoRequest
(*NetworkInfoRequest)(nil), // 140: lnrpc.NetworkInfoRequest
(*NetworkInfo)(nil), // 141: lnrpc.NetworkInfo
(*StopRequest)(nil), // 142: lnrpc.StopRequest
(*StopResponse)(nil), // 143: lnrpc.StopResponse
(*GraphTopologySubscription)(nil), // 144: lnrpc.GraphTopologySubscription
(*GraphTopologyUpdate)(nil), // 145: lnrpc.GraphTopologyUpdate
(*NodeUpdate)(nil), // 146: lnrpc.NodeUpdate
(*ChannelEdgeUpdate)(nil), // 147: lnrpc.ChannelEdgeUpdate
(*ClosedChannelUpdate)(nil), // 148: lnrpc.ClosedChannelUpdate
(*HopHint)(nil), // 149: lnrpc.HopHint
(*SetID)(nil), // 150: lnrpc.SetID
(*RouteHint)(nil), // 151: lnrpc.RouteHint
(*BlindedPaymentPath)(nil), // 152: lnrpc.BlindedPaymentPath
(*BlindedPath)(nil), // 153: lnrpc.BlindedPath
(*BlindedHop)(nil), // 154: lnrpc.BlindedHop
(*AMPInvoiceState)(nil), // 155: lnrpc.AMPInvoiceState
(*Invoice)(nil), // 156: lnrpc.Invoice
(*BlindedPathConfig)(nil), // 157: lnrpc.BlindedPathConfig
(*InvoiceHTLC)(nil), // 158: lnrpc.InvoiceHTLC
(*AMP)(nil), // 159: lnrpc.AMP
(*AddInvoiceResponse)(nil), // 160: lnrpc.AddInvoiceResponse
(*PaymentHash)(nil), // 161: lnrpc.PaymentHash
(*ListInvoiceRequest)(nil), // 162: lnrpc.ListInvoiceRequest
(*ListInvoiceResponse)(nil), // 163: lnrpc.ListInvoiceResponse
(*InvoiceSubscription)(nil), // 164: lnrpc.InvoiceSubscription
(*Payment)(nil), // 165: lnrpc.Payment
(*HTLCAttempt)(nil), // 166: lnrpc.HTLCAttempt
(*ListPaymentsRequest)(nil), // 167: lnrpc.ListPaymentsRequest
(*ListPaymentsResponse)(nil), // 168: lnrpc.ListPaymentsResponse
(*DeletePaymentRequest)(nil), // 169: lnrpc.DeletePaymentRequest
(*DeleteAllPaymentsRequest)(nil), // 170: lnrpc.DeleteAllPaymentsRequest
(*DeletePaymentResponse)(nil), // 171: lnrpc.DeletePaymentResponse
(*DeleteAllPaymentsResponse)(nil), // 172: lnrpc.DeleteAllPaymentsResponse
(*AbandonChannelRequest)(nil), // 173: lnrpc.AbandonChannelRequest
(*AbandonChannelResponse)(nil), // 174: lnrpc.AbandonChannelResponse
(*DebugLevelRequest)(nil), // 175: lnrpc.DebugLevelRequest
(*DebugLevelResponse)(nil), // 176: lnrpc.DebugLevelResponse
(*PayReqString)(nil), // 177: lnrpc.PayReqString
(*PayReq)(nil), // 178: lnrpc.PayReq
(*Feature)(nil), // 179: lnrpc.Feature
(*FeeReportRequest)(nil), // 180: lnrpc.FeeReportRequest
(*ChannelFeeReport)(nil), // 181: lnrpc.ChannelFeeReport
(*FeeReportResponse)(nil), // 182: lnrpc.FeeReportResponse
(*InboundFee)(nil), // 183: lnrpc.InboundFee
(*PolicyUpdateRequest)(nil), // 184: lnrpc.PolicyUpdateRequest
(*FailedUpdate)(nil), // 185: lnrpc.FailedUpdate
(*PolicyUpdateResponse)(nil), // 186: lnrpc.PolicyUpdateResponse
(*ForwardingHistoryRequest)(nil), // 187: lnrpc.ForwardingHistoryRequest
(*ForwardingEvent)(nil), // 188: lnrpc.ForwardingEvent
(*ForwardingHistoryResponse)(nil), // 189: lnrpc.ForwardingHistoryResponse
(*ExportChannelBackupRequest)(nil), // 190: lnrpc.ExportChannelBackupRequest
(*ChannelBackup)(nil), // 191: lnrpc.ChannelBackup
(*MultiChanBackup)(nil), // 192: lnrpc.MultiChanBackup
(*ChanBackupExportRequest)(nil), // 193: lnrpc.ChanBackupExportRequest
(*ChanBackupSnapshot)(nil), // 194: lnrpc.ChanBackupSnapshot
(*ChannelBackups)(nil), // 195: lnrpc.ChannelBackups
(*RestoreChanBackupRequest)(nil), // 196: lnrpc.RestoreChanBackupRequest
(*RestoreBackupResponse)(nil), // 197: lnrpc.RestoreBackupResponse
(*ChannelBackupSubscription)(nil), // 198: lnrpc.ChannelBackupSubscription
(*VerifyChanBackupResponse)(nil), // 199: lnrpc.VerifyChanBackupResponse
(*MacaroonPermission)(nil), // 200: lnrpc.MacaroonPermission
(*BakeMacaroonRequest)(nil), // 201: lnrpc.BakeMacaroonRequest
(*BakeMacaroonResponse)(nil), // 202: lnrpc.BakeMacaroonResponse
(*ListMacaroonIDsRequest)(nil), // 203: lnrpc.ListMacaroonIDsRequest
(*ListMacaroonIDsResponse)(nil), // 204: lnrpc.ListMacaroonIDsResponse
(*DeleteMacaroonIDRequest)(nil), // 205: lnrpc.DeleteMacaroonIDRequest
(*DeleteMacaroonIDResponse)(nil), // 206: lnrpc.DeleteMacaroonIDResponse
(*MacaroonPermissionList)(nil), // 207: lnrpc.MacaroonPermissionList
(*ListPermissionsRequest)(nil), // 208: lnrpc.ListPermissionsRequest
(*ListPermissionsResponse)(nil), // 209: lnrpc.ListPermissionsResponse
(*Failure)(nil), // 210: lnrpc.Failure
(*ChannelUpdate)(nil), // 211: lnrpc.ChannelUpdate
(*MacaroonId)(nil), // 212: lnrpc.MacaroonId
(*Op)(nil), // 213: lnrpc.Op
(*CheckMacPermRequest)(nil), // 214: lnrpc.CheckMacPermRequest
(*CheckMacPermResponse)(nil), // 215: lnrpc.CheckMacPermResponse
(*RPCMiddlewareRequest)(nil), // 216: lnrpc.RPCMiddlewareRequest
(*StreamAuth)(nil), // 217: lnrpc.StreamAuth
(*RPCMessage)(nil), // 218: lnrpc.RPCMessage
(*RPCMiddlewareResponse)(nil), // 219: lnrpc.RPCMiddlewareResponse
(*MiddlewareRegistration)(nil), // 220: lnrpc.MiddlewareRegistration
(*InterceptFeedback)(nil), // 221: lnrpc.InterceptFeedback
nil, // 222: lnrpc.SendRequest.DestCustomRecordsEntry
nil, // 223: lnrpc.EstimateFeeRequest.AddrToAmountEntry
nil, // 224: lnrpc.SendManyRequest.AddrToAmountEntry
nil, // 225: lnrpc.Peer.FeaturesEntry
nil, // 226: lnrpc.GetInfoResponse.FeaturesEntry
nil, // 227: lnrpc.GetDebugInfoResponse.ConfigEntry
(*PendingChannelsResponse_PendingChannel)(nil), // 228: lnrpc.PendingChannelsResponse.PendingChannel
(*PendingChannelsResponse_PendingOpenChannel)(nil), // 229: lnrpc.PendingChannelsResponse.PendingOpenChannel
(*PendingChannelsResponse_WaitingCloseChannel)(nil), // 230: lnrpc.PendingChannelsResponse.WaitingCloseChannel
(*PendingChannelsResponse_Commitments)(nil), // 231: lnrpc.PendingChannelsResponse.Commitments
(*PendingChannelsResponse_ClosedChannel)(nil), // 232: lnrpc.PendingChannelsResponse.ClosedChannel
(*PendingChannelsResponse_ForceClosedChannel)(nil), // 233: lnrpc.PendingChannelsResponse.ForceClosedChannel
nil, // 234: lnrpc.WalletBalanceResponse.AccountBalanceEntry
nil, // 235: lnrpc.QueryRoutesRequest.DestCustomRecordsEntry
nil, // 236: lnrpc.Hop.CustomRecordsEntry
nil, // 237: lnrpc.LightningNode.FeaturesEntry
nil, // 238: lnrpc.LightningNode.CustomRecordsEntry
nil, // 239: lnrpc.RoutingPolicy.CustomRecordsEntry
nil, // 240: lnrpc.ChannelEdge.CustomRecordsEntry
nil, // 241: lnrpc.NodeMetricsResponse.BetweennessCentralityEntry
nil, // 242: lnrpc.NodeUpdate.FeaturesEntry
nil, // 243: lnrpc.Invoice.FeaturesEntry
nil, // 244: lnrpc.Invoice.AmpInvoiceStateEntry
nil, // 245: lnrpc.InvoiceHTLC.CustomRecordsEntry
nil, // 246: lnrpc.Payment.FirstHopCustomRecordsEntry
nil, // 247: lnrpc.PayReq.FeaturesEntry
nil, // 248: lnrpc.ListPermissionsResponse.MethodPermissionsEntry
}
var file_lightning_proto_depIdxs = []int32{
2, // 0: lnrpc.Utxo.address_type:type_name -> lnrpc.AddressType
39, // 1: lnrpc.Utxo.outpoint:type_name -> lnrpc.OutPoint
0, // 2: lnrpc.OutputDetail.output_type:type_name -> lnrpc.OutputScriptType
28, // 3: lnrpc.Transaction.output_details:type_name -> lnrpc.OutputDetail
40, // 4: lnrpc.Transaction.previous_outpoints:type_name -> lnrpc.PreviousOutPoint
29, // 5: lnrpc.TransactionDetails.transactions:type_name -> lnrpc.Transaction
32, // 6: lnrpc.SendRequest.fee_limit:type_name -> lnrpc.FeeLimit
222, // 7: lnrpc.SendRequest.dest_custom_records:type_name -> lnrpc.SendRequest.DestCustomRecordsEntry
10, // 8: lnrpc.SendRequest.dest_features:type_name -> lnrpc.FeatureBit
127, // 9: lnrpc.SendResponse.payment_route:type_name -> lnrpc.Route
127, // 10: lnrpc.SendToRouteRequest.route:type_name -> lnrpc.Route
3, // 11: lnrpc.ChannelAcceptRequest.commitment_type:type_name -> lnrpc.CommitmentType
223, // 12: lnrpc.EstimateFeeRequest.AddrToAmount:type_name -> lnrpc.EstimateFeeRequest.AddrToAmountEntry
1, // 13: lnrpc.EstimateFeeRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy
224, // 14: lnrpc.SendManyRequest.AddrToAmount:type_name -> lnrpc.SendManyRequest.AddrToAmountEntry
1, // 15: lnrpc.SendManyRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy
1, // 16: lnrpc.SendCoinsRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy
39, // 17: lnrpc.SendCoinsRequest.outpoints:type_name -> lnrpc.OutPoint
27, // 18: lnrpc.ListUnspentResponse.utxos:type_name -> lnrpc.Utxo
2, // 19: lnrpc.NewAddressRequest.type:type_name -> lnrpc.AddressType
41, // 20: lnrpc.ConnectPeerRequest.addr:type_name -> lnrpc.LightningAddress
60, // 21: lnrpc.Channel.pending_htlcs:type_name -> lnrpc.HTLC
3, // 22: lnrpc.Channel.commitment_type:type_name -> lnrpc.CommitmentType
61, // 23: lnrpc.Channel.local_constraints:type_name -> lnrpc.ChannelConstraints
61, // 24: lnrpc.Channel.remote_constraints:type_name -> lnrpc.ChannelConstraints
62, // 25: lnrpc.ListChannelsResponse.channels:type_name -> lnrpc.Channel
65, // 26: lnrpc.ListAliasesResponse.alias_maps:type_name -> lnrpc.AliasMap
12, // 27: lnrpc.ChannelCloseSummary.close_type:type_name -> lnrpc.ChannelCloseSummary.ClosureType
4, // 28: lnrpc.ChannelCloseSummary.open_initiator:type_name -> lnrpc.Initiator
4, // 29: lnrpc.ChannelCloseSummary.close_initiator:type_name -> lnrpc.Initiator
69, // 30: lnrpc.ChannelCloseSummary.resolutions:type_name -> lnrpc.Resolution
5, // 31: lnrpc.Resolution.resolution_type:type_name -> lnrpc.ResolutionType
6, // 32: lnrpc.Resolution.outcome:type_name -> lnrpc.ResolutionOutcome
39, // 33: lnrpc.Resolution.outpoint:type_name -> lnrpc.OutPoint
68, // 34: lnrpc.ClosedChannelsResponse.channels:type_name -> lnrpc.ChannelCloseSummary
13, // 35: lnrpc.Peer.sync_type:type_name -> lnrpc.Peer.SyncType
225, // 36: lnrpc.Peer.features:type_name -> lnrpc.Peer.FeaturesEntry
73, // 37: lnrpc.Peer.errors:type_name -> lnrpc.TimestampedError
72, // 38: lnrpc.ListPeersResponse.peers:type_name -> lnrpc.Peer
14, // 39: lnrpc.PeerEvent.type:type_name -> lnrpc.PeerEvent.EventType
84, // 40: lnrpc.GetInfoResponse.chains:type_name -> lnrpc.Chain
226, // 41: lnrpc.GetInfoResponse.features:type_name -> lnrpc.GetInfoResponse.FeaturesEntry
227, // 42: lnrpc.GetDebugInfoResponse.config:type_name -> lnrpc.GetDebugInfoResponse.ConfigEntry
38, // 43: lnrpc.ChannelOpenUpdate.channel_point:type_name -> lnrpc.ChannelPoint
87, // 44: lnrpc.ChannelCloseUpdate.local_close_output:type_name -> lnrpc.CloseOutput
87, // 45: lnrpc.ChannelCloseUpdate.remote_close_output:type_name -> lnrpc.CloseOutput
87, // 46: lnrpc.ChannelCloseUpdate.additional_outputs:type_name -> lnrpc.CloseOutput
38, // 47: lnrpc.CloseChannelRequest.channel_point:type_name -> lnrpc.ChannelPoint
91, // 48: lnrpc.CloseStatusUpdate.close_pending:type_name -> lnrpc.PendingUpdate
88, // 49: lnrpc.CloseStatusUpdate.chan_close:type_name -> lnrpc.ChannelCloseUpdate
92, // 50: lnrpc.CloseStatusUpdate.close_instant:type_name -> lnrpc.InstantUpdate
95, // 51: lnrpc.BatchOpenChannelRequest.channels:type_name -> lnrpc.BatchOpenChannel
1, // 52: lnrpc.BatchOpenChannelRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy
3, // 53: lnrpc.BatchOpenChannel.commitment_type:type_name -> lnrpc.CommitmentType
91, // 54: lnrpc.BatchOpenChannelResponse.pending_channels:type_name -> lnrpc.PendingUpdate
103, // 55: lnrpc.OpenChannelRequest.funding_shim:type_name -> lnrpc.FundingShim
3, // 56: lnrpc.OpenChannelRequest.commitment_type:type_name -> lnrpc.CommitmentType
39, // 57: lnrpc.OpenChannelRequest.outpoints:type_name -> lnrpc.OutPoint
91, // 58: lnrpc.OpenStatusUpdate.chan_pending:type_name -> lnrpc.PendingUpdate
86, // 59: lnrpc.OpenStatusUpdate.chan_open:type_name -> lnrpc.ChannelOpenUpdate
93, // 60: lnrpc.OpenStatusUpdate.psbt_fund:type_name -> lnrpc.ReadyForPsbtFunding
99, // 61: lnrpc.KeyDescriptor.key_loc:type_name -> lnrpc.KeyLocator
38, // 62: lnrpc.ChanPointShim.chan_point:type_name -> lnrpc.ChannelPoint
100, // 63: lnrpc.ChanPointShim.local_key:type_name -> lnrpc.KeyDescriptor
101, // 64: lnrpc.FundingShim.chan_point_shim:type_name -> lnrpc.ChanPointShim
102, // 65: lnrpc.FundingShim.psbt_shim:type_name -> lnrpc.PsbtShim
103, // 66: lnrpc.FundingTransitionMsg.shim_register:type_name -> lnrpc.FundingShim
104, // 67: lnrpc.FundingTransitionMsg.shim_cancel:type_name -> lnrpc.FundingShimCancel
105, // 68: lnrpc.FundingTransitionMsg.psbt_verify:type_name -> lnrpc.FundingPsbtVerify
106, // 69: lnrpc.FundingTransitionMsg.psbt_finalize:type_name -> lnrpc.FundingPsbtFinalize
229, // 70: lnrpc.PendingChannelsResponse.pending_open_channels:type_name -> lnrpc.PendingChannelsResponse.PendingOpenChannel
232, // 71: lnrpc.PendingChannelsResponse.pending_closing_channels:type_name -> lnrpc.PendingChannelsResponse.ClosedChannel
233, // 72: lnrpc.PendingChannelsResponse.pending_force_closing_channels:type_name -> lnrpc.PendingChannelsResponse.ForceClosedChannel
230, // 73: lnrpc.PendingChannelsResponse.waiting_close_channels:type_name -> lnrpc.PendingChannelsResponse.WaitingCloseChannel
62, // 74: lnrpc.ChannelEventUpdate.open_channel:type_name -> lnrpc.Channel
68, // 75: lnrpc.ChannelEventUpdate.closed_channel:type_name -> lnrpc.ChannelCloseSummary
38, // 76: lnrpc.ChannelEventUpdate.active_channel:type_name -> lnrpc.ChannelPoint
38, // 77: lnrpc.ChannelEventUpdate.inactive_channel:type_name -> lnrpc.ChannelPoint
91, // 78: lnrpc.ChannelEventUpdate.pending_open_channel:type_name -> lnrpc.PendingUpdate
38, // 79: lnrpc.ChannelEventUpdate.fully_resolved_channel:type_name -> lnrpc.ChannelPoint
16, // 80: lnrpc.ChannelEventUpdate.type:type_name -> lnrpc.ChannelEventUpdate.UpdateType
234, // 81: lnrpc.WalletBalanceResponse.account_balance:type_name -> lnrpc.WalletBalanceResponse.AccountBalanceEntry
117, // 82: lnrpc.ChannelBalanceResponse.local_balance:type_name -> lnrpc.Amount
117, // 83: lnrpc.ChannelBalanceResponse.remote_balance:type_name -> lnrpc.Amount
117, // 84: lnrpc.ChannelBalanceResponse.unsettled_local_balance:type_name -> lnrpc.Amount
117, // 85: lnrpc.ChannelBalanceResponse.unsettled_remote_balance:type_name -> lnrpc.Amount
117, // 86: lnrpc.ChannelBalanceResponse.pending_open_local_balance:type_name -> lnrpc.Amount
117, // 87: lnrpc.ChannelBalanceResponse.pending_open_remote_balance:type_name -> lnrpc.Amount
32, // 88: lnrpc.QueryRoutesRequest.fee_limit:type_name -> lnrpc.FeeLimit
122, // 89: lnrpc.QueryRoutesRequest.ignored_edges:type_name -> lnrpc.EdgeLocator
121, // 90: lnrpc.QueryRoutesRequest.ignored_pairs:type_name -> lnrpc.NodePair
235, // 91: lnrpc.QueryRoutesRequest.dest_custom_records:type_name -> lnrpc.QueryRoutesRequest.DestCustomRecordsEntry
151, // 92: lnrpc.QueryRoutesRequest.route_hints:type_name -> lnrpc.RouteHint
152, // 93: lnrpc.QueryRoutesRequest.blinded_payment_paths:type_name -> lnrpc.BlindedPaymentPath
10, // 94: lnrpc.QueryRoutesRequest.dest_features:type_name -> lnrpc.FeatureBit
127, // 95: lnrpc.QueryRoutesResponse.routes:type_name -> lnrpc.Route
125, // 96: lnrpc.Hop.mpp_record:type_name -> lnrpc.MPPRecord
126, // 97: lnrpc.Hop.amp_record:type_name -> lnrpc.AMPRecord
236, // 98: lnrpc.Hop.custom_records:type_name -> lnrpc.Hop.CustomRecordsEntry
124, // 99: lnrpc.Route.hops:type_name -> lnrpc.Hop
130, // 100: lnrpc.NodeInfo.node:type_name -> lnrpc.LightningNode
133, // 101: lnrpc.NodeInfo.channels:type_name -> lnrpc.ChannelEdge
131, // 102: lnrpc.LightningNode.addresses:type_name -> lnrpc.NodeAddress
237, // 103: lnrpc.LightningNode.features:type_name -> lnrpc.LightningNode.FeaturesEntry
238, // 104: lnrpc.LightningNode.custom_records:type_name -> lnrpc.LightningNode.CustomRecordsEntry
239, // 105: lnrpc.RoutingPolicy.custom_records:type_name -> lnrpc.RoutingPolicy.CustomRecordsEntry
132, // 106: lnrpc.ChannelEdge.node1_policy:type_name -> lnrpc.RoutingPolicy
132, // 107: lnrpc.ChannelEdge.node2_policy:type_name -> lnrpc.RoutingPolicy
240, // 108: lnrpc.ChannelEdge.custom_records:type_name -> lnrpc.ChannelEdge.CustomRecordsEntry
130, // 109: lnrpc.ChannelGraph.nodes:type_name -> lnrpc.LightningNode
133, // 110: lnrpc.ChannelGraph.edges:type_name -> lnrpc.ChannelEdge
7, // 111: lnrpc.NodeMetricsRequest.types:type_name -> lnrpc.NodeMetricType
241, // 112: lnrpc.NodeMetricsResponse.betweenness_centrality:type_name -> lnrpc.NodeMetricsResponse.BetweennessCentralityEntry
146, // 113: lnrpc.GraphTopologyUpdate.node_updates:type_name -> lnrpc.NodeUpdate
147, // 114: lnrpc.GraphTopologyUpdate.channel_updates:type_name -> lnrpc.ChannelEdgeUpdate
148, // 115: lnrpc.GraphTopologyUpdate.closed_chans:type_name -> lnrpc.ClosedChannelUpdate
131, // 116: lnrpc.NodeUpdate.node_addresses:type_name -> lnrpc.NodeAddress
242, // 117: lnrpc.NodeUpdate.features:type_name -> lnrpc.NodeUpdate.FeaturesEntry
38, // 118: lnrpc.ChannelEdgeUpdate.chan_point:type_name -> lnrpc.ChannelPoint
132, // 119: lnrpc.ChannelEdgeUpdate.routing_policy:type_name -> lnrpc.RoutingPolicy
38, // 120: lnrpc.ClosedChannelUpdate.chan_point:type_name -> lnrpc.ChannelPoint
149, // 121: lnrpc.RouteHint.hop_hints:type_name -> lnrpc.HopHint
153, // 122: lnrpc.BlindedPaymentPath.blinded_path:type_name -> lnrpc.BlindedPath
10, // 123: lnrpc.BlindedPaymentPath.features:type_name -> lnrpc.FeatureBit
154, // 124: lnrpc.BlindedPath.blinded_hops:type_name -> lnrpc.BlindedHop
8, // 125: lnrpc.AMPInvoiceState.state:type_name -> lnrpc.InvoiceHTLCState
151, // 126: lnrpc.Invoice.route_hints:type_name -> lnrpc.RouteHint
17, // 127: lnrpc.Invoice.state:type_name -> lnrpc.Invoice.InvoiceState
158, // 128: lnrpc.Invoice.htlcs:type_name -> lnrpc.InvoiceHTLC
243, // 129: lnrpc.Invoice.features:type_name -> lnrpc.Invoice.FeaturesEntry
244, // 130: lnrpc.Invoice.amp_invoice_state:type_name -> lnrpc.Invoice.AmpInvoiceStateEntry
157, // 131: lnrpc.Invoice.blinded_path_config:type_name -> lnrpc.BlindedPathConfig
8, // 132: lnrpc.InvoiceHTLC.state:type_name -> lnrpc.InvoiceHTLCState
245, // 133: lnrpc.InvoiceHTLC.custom_records:type_name -> lnrpc.InvoiceHTLC.CustomRecordsEntry
159, // 134: lnrpc.InvoiceHTLC.amp:type_name -> lnrpc.AMP
156, // 135: lnrpc.ListInvoiceResponse.invoices:type_name -> lnrpc.Invoice
18, // 136: lnrpc.Payment.status:type_name -> lnrpc.Payment.PaymentStatus
166, // 137: lnrpc.Payment.htlcs:type_name -> lnrpc.HTLCAttempt
9, // 138: lnrpc.Payment.failure_reason:type_name -> lnrpc.PaymentFailureReason
246, // 139: lnrpc.Payment.first_hop_custom_records:type_name -> lnrpc.Payment.FirstHopCustomRecordsEntry
19, // 140: lnrpc.HTLCAttempt.status:type_name -> lnrpc.HTLCAttempt.HTLCStatus
127, // 141: lnrpc.HTLCAttempt.route:type_name -> lnrpc.Route
210, // 142: lnrpc.HTLCAttempt.failure:type_name -> lnrpc.Failure
165, // 143: lnrpc.ListPaymentsResponse.payments:type_name -> lnrpc.Payment
38, // 144: lnrpc.AbandonChannelRequest.channel_point:type_name -> lnrpc.ChannelPoint
151, // 145: lnrpc.PayReq.route_hints:type_name -> lnrpc.RouteHint
247, // 146: lnrpc.PayReq.features:type_name -> lnrpc.PayReq.FeaturesEntry
152, // 147: lnrpc.PayReq.blinded_paths:type_name -> lnrpc.BlindedPaymentPath
181, // 148: lnrpc.FeeReportResponse.channel_fees:type_name -> lnrpc.ChannelFeeReport
38, // 149: lnrpc.PolicyUpdateRequest.chan_point:type_name -> lnrpc.ChannelPoint
183, // 150: lnrpc.PolicyUpdateRequest.inbound_fee:type_name -> lnrpc.InboundFee
39, // 151: lnrpc.FailedUpdate.outpoint:type_name -> lnrpc.OutPoint
11, // 152: lnrpc.FailedUpdate.reason:type_name -> lnrpc.UpdateFailure
185, // 153: lnrpc.PolicyUpdateResponse.failed_updates:type_name -> lnrpc.FailedUpdate
188, // 154: lnrpc.ForwardingHistoryResponse.forwarding_events:type_name -> lnrpc.ForwardingEvent
38, // 155: lnrpc.ExportChannelBackupRequest.chan_point:type_name -> lnrpc.ChannelPoint
38, // 156: lnrpc.ChannelBackup.chan_point:type_name -> lnrpc.ChannelPoint
38, // 157: lnrpc.MultiChanBackup.chan_points:type_name -> lnrpc.ChannelPoint
195, // 158: lnrpc.ChanBackupSnapshot.single_chan_backups:type_name -> lnrpc.ChannelBackups
192, // 159: lnrpc.ChanBackupSnapshot.multi_chan_backup:type_name -> lnrpc.MultiChanBackup
191, // 160: lnrpc.ChannelBackups.chan_backups:type_name -> lnrpc.ChannelBackup
195, // 161: lnrpc.RestoreChanBackupRequest.chan_backups:type_name -> lnrpc.ChannelBackups
200, // 162: lnrpc.BakeMacaroonRequest.permissions:type_name -> lnrpc.MacaroonPermission
200, // 163: lnrpc.MacaroonPermissionList.permissions:type_name -> lnrpc.MacaroonPermission
248, // 164: lnrpc.ListPermissionsResponse.method_permissions:type_name -> lnrpc.ListPermissionsResponse.MethodPermissionsEntry
20, // 165: lnrpc.Failure.code:type_name -> lnrpc.Failure.FailureCode
211, // 166: lnrpc.Failure.channel_update:type_name -> lnrpc.ChannelUpdate
213, // 167: lnrpc.MacaroonId.ops:type_name -> lnrpc.Op
200, // 168: lnrpc.CheckMacPermRequest.permissions:type_name -> lnrpc.MacaroonPermission
217, // 169: lnrpc.RPCMiddlewareRequest.stream_auth:type_name -> lnrpc.StreamAuth
218, // 170: lnrpc.RPCMiddlewareRequest.request:type_name -> lnrpc.RPCMessage
218, // 171: lnrpc.RPCMiddlewareRequest.response:type_name -> lnrpc.RPCMessage
220, // 172: lnrpc.RPCMiddlewareResponse.register:type_name -> lnrpc.MiddlewareRegistration
221, // 173: lnrpc.RPCMiddlewareResponse.feedback:type_name -> lnrpc.InterceptFeedback
179, // 174: lnrpc.Peer.FeaturesEntry.value:type_name -> lnrpc.Feature
179, // 175: lnrpc.GetInfoResponse.FeaturesEntry.value:type_name -> lnrpc.Feature
4, // 176: lnrpc.PendingChannelsResponse.PendingChannel.initiator:type_name -> lnrpc.Initiator
3, // 177: lnrpc.PendingChannelsResponse.PendingChannel.commitment_type:type_name -> lnrpc.CommitmentType
228, // 178: lnrpc.PendingChannelsResponse.PendingOpenChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel
228, // 179: lnrpc.PendingChannelsResponse.WaitingCloseChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel
231, // 180: lnrpc.PendingChannelsResponse.WaitingCloseChannel.commitments:type_name -> lnrpc.PendingChannelsResponse.Commitments
228, // 181: lnrpc.PendingChannelsResponse.ClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel
228, // 182: lnrpc.PendingChannelsResponse.ForceClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel
109, // 183: lnrpc.PendingChannelsResponse.ForceClosedChannel.pending_htlcs:type_name -> lnrpc.PendingHTLC
15, // 184: lnrpc.PendingChannelsResponse.ForceClosedChannel.anchor:type_name -> lnrpc.PendingChannelsResponse.ForceClosedChannel.AnchorState
114, // 185: lnrpc.WalletBalanceResponse.AccountBalanceEntry.value:type_name -> lnrpc.WalletAccountBalance
179, // 186: lnrpc.LightningNode.FeaturesEntry.value:type_name -> lnrpc.Feature
138, // 187: lnrpc.NodeMetricsResponse.BetweennessCentralityEntry.value:type_name -> lnrpc.FloatMetric
179, // 188: lnrpc.NodeUpdate.FeaturesEntry.value:type_name -> lnrpc.Feature
179, // 189: lnrpc.Invoice.FeaturesEntry.value:type_name -> lnrpc.Feature
155, // 190: lnrpc.Invoice.AmpInvoiceStateEntry.value:type_name -> lnrpc.AMPInvoiceState
179, // 191: lnrpc.PayReq.FeaturesEntry.value:type_name -> lnrpc.Feature
207, // 192: lnrpc.ListPermissionsResponse.MethodPermissionsEntry.value:type_name -> lnrpc.MacaroonPermissionList
115, // 193: lnrpc.Lightning.WalletBalance:input_type -> lnrpc.WalletBalanceRequest
118, // 194: lnrpc.Lightning.ChannelBalance:input_type -> lnrpc.ChannelBalanceRequest
30, // 195: lnrpc.Lightning.GetTransactions:input_type -> lnrpc.GetTransactionsRequest
42, // 196: lnrpc.Lightning.EstimateFee:input_type -> lnrpc.EstimateFeeRequest
46, // 197: lnrpc.Lightning.SendCoins:input_type -> lnrpc.SendCoinsRequest
48, // 198: lnrpc.Lightning.ListUnspent:input_type -> lnrpc.ListUnspentRequest
30, // 199: lnrpc.Lightning.SubscribeTransactions:input_type -> lnrpc.GetTransactionsRequest
44, // 200: lnrpc.Lightning.SendMany:input_type -> lnrpc.SendManyRequest
50, // 201: lnrpc.Lightning.NewAddress:input_type -> lnrpc.NewAddressRequest
52, // 202: lnrpc.Lightning.SignMessage:input_type -> lnrpc.SignMessageRequest
54, // 203: lnrpc.Lightning.VerifyMessage:input_type -> lnrpc.VerifyMessageRequest
56, // 204: lnrpc.Lightning.ConnectPeer:input_type -> lnrpc.ConnectPeerRequest
58, // 205: lnrpc.Lightning.DisconnectPeer:input_type -> lnrpc.DisconnectPeerRequest
74, // 206: lnrpc.Lightning.ListPeers:input_type -> lnrpc.ListPeersRequest
76, // 207: lnrpc.Lightning.SubscribePeerEvents:input_type -> lnrpc.PeerEventSubscription
78, // 208: lnrpc.Lightning.GetInfo:input_type -> lnrpc.GetInfoRequest
80, // 209: lnrpc.Lightning.GetDebugInfo:input_type -> lnrpc.GetDebugInfoRequest
82, // 210: lnrpc.Lightning.GetRecoveryInfo:input_type -> lnrpc.GetRecoveryInfoRequest
110, // 211: lnrpc.Lightning.PendingChannels:input_type -> lnrpc.PendingChannelsRequest
63, // 212: lnrpc.Lightning.ListChannels:input_type -> lnrpc.ListChannelsRequest
112, // 213: lnrpc.Lightning.SubscribeChannelEvents:input_type -> lnrpc.ChannelEventSubscription
70, // 214: lnrpc.Lightning.ClosedChannels:input_type -> lnrpc.ClosedChannelsRequest
97, // 215: lnrpc.Lightning.OpenChannelSync:input_type -> lnrpc.OpenChannelRequest
97, // 216: lnrpc.Lightning.OpenChannel:input_type -> lnrpc.OpenChannelRequest
94, // 217: lnrpc.Lightning.BatchOpenChannel:input_type -> lnrpc.BatchOpenChannelRequest
107, // 218: lnrpc.Lightning.FundingStateStep:input_type -> lnrpc.FundingTransitionMsg
37, // 219: lnrpc.Lightning.ChannelAcceptor:input_type -> lnrpc.ChannelAcceptResponse
89, // 220: lnrpc.Lightning.CloseChannel:input_type -> lnrpc.CloseChannelRequest
173, // 221: lnrpc.Lightning.AbandonChannel:input_type -> lnrpc.AbandonChannelRequest
33, // 222: lnrpc.Lightning.SendPayment:input_type -> lnrpc.SendRequest
33, // 223: lnrpc.Lightning.SendPaymentSync:input_type -> lnrpc.SendRequest
35, // 224: lnrpc.Lightning.SendToRoute:input_type -> lnrpc.SendToRouteRequest
35, // 225: lnrpc.Lightning.SendToRouteSync:input_type -> lnrpc.SendToRouteRequest
156, // 226: lnrpc.Lightning.AddInvoice:input_type -> lnrpc.Invoice
162, // 227: lnrpc.Lightning.ListInvoices:input_type -> lnrpc.ListInvoiceRequest
161, // 228: lnrpc.Lightning.LookupInvoice:input_type -> lnrpc.PaymentHash
164, // 229: lnrpc.Lightning.SubscribeInvoices:input_type -> lnrpc.InvoiceSubscription
177, // 230: lnrpc.Lightning.DecodePayReq:input_type -> lnrpc.PayReqString
167, // 231: lnrpc.Lightning.ListPayments:input_type -> lnrpc.ListPaymentsRequest
169, // 232: lnrpc.Lightning.DeletePayment:input_type -> lnrpc.DeletePaymentRequest
170, // 233: lnrpc.Lightning.DeleteAllPayments:input_type -> lnrpc.DeleteAllPaymentsRequest
134, // 234: lnrpc.Lightning.DescribeGraph:input_type -> lnrpc.ChannelGraphRequest
136, // 235: lnrpc.Lightning.GetNodeMetrics:input_type -> lnrpc.NodeMetricsRequest
139, // 236: lnrpc.Lightning.GetChanInfo:input_type -> lnrpc.ChanInfoRequest
128, // 237: lnrpc.Lightning.GetNodeInfo:input_type -> lnrpc.NodeInfoRequest
120, // 238: lnrpc.Lightning.QueryRoutes:input_type -> lnrpc.QueryRoutesRequest
140, // 239: lnrpc.Lightning.GetNetworkInfo:input_type -> lnrpc.NetworkInfoRequest
142, // 240: lnrpc.Lightning.StopDaemon:input_type -> lnrpc.StopRequest
144, // 241: lnrpc.Lightning.SubscribeChannelGraph:input_type -> lnrpc.GraphTopologySubscription
175, // 242: lnrpc.Lightning.DebugLevel:input_type -> lnrpc.DebugLevelRequest
180, // 243: lnrpc.Lightning.FeeReport:input_type -> lnrpc.FeeReportRequest
184, // 244: lnrpc.Lightning.UpdateChannelPolicy:input_type -> lnrpc.PolicyUpdateRequest
187, // 245: lnrpc.Lightning.ForwardingHistory:input_type -> lnrpc.ForwardingHistoryRequest
190, // 246: lnrpc.Lightning.ExportChannelBackup:input_type -> lnrpc.ExportChannelBackupRequest
193, // 247: lnrpc.Lightning.ExportAllChannelBackups:input_type -> lnrpc.ChanBackupExportRequest
194, // 248: lnrpc.Lightning.VerifyChanBackup:input_type -> lnrpc.ChanBackupSnapshot
196, // 249: lnrpc.Lightning.RestoreChannelBackups:input_type -> lnrpc.RestoreChanBackupRequest
198, // 250: lnrpc.Lightning.SubscribeChannelBackups:input_type -> lnrpc.ChannelBackupSubscription
201, // 251: lnrpc.Lightning.BakeMacaroon:input_type -> lnrpc.BakeMacaroonRequest
203, // 252: lnrpc.Lightning.ListMacaroonIDs:input_type -> lnrpc.ListMacaroonIDsRequest
205, // 253: lnrpc.Lightning.DeleteMacaroonID:input_type -> lnrpc.DeleteMacaroonIDRequest
208, // 254: lnrpc.Lightning.ListPermissions:input_type -> lnrpc.ListPermissionsRequest
214, // 255: lnrpc.Lightning.CheckMacaroonPermissions:input_type -> lnrpc.CheckMacPermRequest
219, // 256: lnrpc.Lightning.RegisterRPCMiddleware:input_type -> lnrpc.RPCMiddlewareResponse
25, // 257: lnrpc.Lightning.SendCustomMessage:input_type -> lnrpc.SendCustomMessageRequest
23, // 258: lnrpc.Lightning.SubscribeCustomMessages:input_type -> lnrpc.SubscribeCustomMessagesRequest
66, // 259: lnrpc.Lightning.ListAliases:input_type -> lnrpc.ListAliasesRequest
21, // 260: lnrpc.Lightning.LookupHtlcResolution:input_type -> lnrpc.LookupHtlcResolutionRequest
116, // 261: lnrpc.Lightning.WalletBalance:output_type -> lnrpc.WalletBalanceResponse
119, // 262: lnrpc.Lightning.ChannelBalance:output_type -> lnrpc.ChannelBalanceResponse
31, // 263: lnrpc.Lightning.GetTransactions:output_type -> lnrpc.TransactionDetails
43, // 264: lnrpc.Lightning.EstimateFee:output_type -> lnrpc.EstimateFeeResponse
47, // 265: lnrpc.Lightning.SendCoins:output_type -> lnrpc.SendCoinsResponse
49, // 266: lnrpc.Lightning.ListUnspent:output_type -> lnrpc.ListUnspentResponse
29, // 267: lnrpc.Lightning.SubscribeTransactions:output_type -> lnrpc.Transaction
45, // 268: lnrpc.Lightning.SendMany:output_type -> lnrpc.SendManyResponse
51, // 269: lnrpc.Lightning.NewAddress:output_type -> lnrpc.NewAddressResponse
53, // 270: lnrpc.Lightning.SignMessage:output_type -> lnrpc.SignMessageResponse
55, // 271: lnrpc.Lightning.VerifyMessage:output_type -> lnrpc.VerifyMessageResponse
57, // 272: lnrpc.Lightning.ConnectPeer:output_type -> lnrpc.ConnectPeerResponse
59, // 273: lnrpc.Lightning.DisconnectPeer:output_type -> lnrpc.DisconnectPeerResponse
75, // 274: lnrpc.Lightning.ListPeers:output_type -> lnrpc.ListPeersResponse
77, // 275: lnrpc.Lightning.SubscribePeerEvents:output_type -> lnrpc.PeerEvent
79, // 276: lnrpc.Lightning.GetInfo:output_type -> lnrpc.GetInfoResponse
81, // 277: lnrpc.Lightning.GetDebugInfo:output_type -> lnrpc.GetDebugInfoResponse
83, // 278: lnrpc.Lightning.GetRecoveryInfo:output_type -> lnrpc.GetRecoveryInfoResponse
111, // 279: lnrpc.Lightning.PendingChannels:output_type -> lnrpc.PendingChannelsResponse
64, // 280: lnrpc.Lightning.ListChannels:output_type -> lnrpc.ListChannelsResponse
113, // 281: lnrpc.Lightning.SubscribeChannelEvents:output_type -> lnrpc.ChannelEventUpdate
71, // 282: lnrpc.Lightning.ClosedChannels:output_type -> lnrpc.ClosedChannelsResponse
38, // 283: lnrpc.Lightning.OpenChannelSync:output_type -> lnrpc.ChannelPoint
98, // 284: lnrpc.Lightning.OpenChannel:output_type -> lnrpc.OpenStatusUpdate
96, // 285: lnrpc.Lightning.BatchOpenChannel:output_type -> lnrpc.BatchOpenChannelResponse
108, // 286: lnrpc.Lightning.FundingStateStep:output_type -> lnrpc.FundingStateStepResp
36, // 287: lnrpc.Lightning.ChannelAcceptor:output_type -> lnrpc.ChannelAcceptRequest
90, // 288: lnrpc.Lightning.CloseChannel:output_type -> lnrpc.CloseStatusUpdate
174, // 289: lnrpc.Lightning.AbandonChannel:output_type -> lnrpc.AbandonChannelResponse
34, // 290: lnrpc.Lightning.SendPayment:output_type -> lnrpc.SendResponse
34, // 291: lnrpc.Lightning.SendPaymentSync:output_type -> lnrpc.SendResponse
34, // 292: lnrpc.Lightning.SendToRoute:output_type -> lnrpc.SendResponse
34, // 293: lnrpc.Lightning.SendToRouteSync:output_type -> lnrpc.SendResponse
160, // 294: lnrpc.Lightning.AddInvoice:output_type -> lnrpc.AddInvoiceResponse
163, // 295: lnrpc.Lightning.ListInvoices:output_type -> lnrpc.ListInvoiceResponse
156, // 296: lnrpc.Lightning.LookupInvoice:output_type -> lnrpc.Invoice
156, // 297: lnrpc.Lightning.SubscribeInvoices:output_type -> lnrpc.Invoice
178, // 298: lnrpc.Lightning.DecodePayReq:output_type -> lnrpc.PayReq
168, // 299: lnrpc.Lightning.ListPayments:output_type -> lnrpc.ListPaymentsResponse
171, // 300: lnrpc.Lightning.DeletePayment:output_type -> lnrpc.DeletePaymentResponse
172, // 301: lnrpc.Lightning.DeleteAllPayments:output_type -> lnrpc.DeleteAllPaymentsResponse
135, // 302: lnrpc.Lightning.DescribeGraph:output_type -> lnrpc.ChannelGraph
137, // 303: lnrpc.Lightning.GetNodeMetrics:output_type -> lnrpc.NodeMetricsResponse
133, // 304: lnrpc.Lightning.GetChanInfo:output_type -> lnrpc.ChannelEdge
129, // 305: lnrpc.Lightning.GetNodeInfo:output_type -> lnrpc.NodeInfo
123, // 306: lnrpc.Lightning.QueryRoutes:output_type -> lnrpc.QueryRoutesResponse
141, // 307: lnrpc.Lightning.GetNetworkInfo:output_type -> lnrpc.NetworkInfo
143, // 308: lnrpc.Lightning.StopDaemon:output_type -> lnrpc.StopResponse
145, // 309: lnrpc.Lightning.SubscribeChannelGraph:output_type -> lnrpc.GraphTopologyUpdate
176, // 310: lnrpc.Lightning.DebugLevel:output_type -> lnrpc.DebugLevelResponse
182, // 311: lnrpc.Lightning.FeeReport:output_type -> lnrpc.FeeReportResponse
186, // 312: lnrpc.Lightning.UpdateChannelPolicy:output_type -> lnrpc.PolicyUpdateResponse
189, // 313: lnrpc.Lightning.ForwardingHistory:output_type -> lnrpc.ForwardingHistoryResponse
191, // 314: lnrpc.Lightning.ExportChannelBackup:output_type -> lnrpc.ChannelBackup
194, // 315: lnrpc.Lightning.ExportAllChannelBackups:output_type -> lnrpc.ChanBackupSnapshot
199, // 316: lnrpc.Lightning.VerifyChanBackup:output_type -> lnrpc.VerifyChanBackupResponse
197, // 317: lnrpc.Lightning.RestoreChannelBackups:output_type -> lnrpc.RestoreBackupResponse
194, // 318: lnrpc.Lightning.SubscribeChannelBackups:output_type -> lnrpc.ChanBackupSnapshot
202, // 319: lnrpc.Lightning.BakeMacaroon:output_type -> lnrpc.BakeMacaroonResponse
204, // 320: lnrpc.Lightning.ListMacaroonIDs:output_type -> lnrpc.ListMacaroonIDsResponse
206, // 321: lnrpc.Lightning.DeleteMacaroonID:output_type -> lnrpc.DeleteMacaroonIDResponse
209, // 322: lnrpc.Lightning.ListPermissions:output_type -> lnrpc.ListPermissionsResponse
215, // 323: lnrpc.Lightning.CheckMacaroonPermissions:output_type -> lnrpc.CheckMacPermResponse
216, // 324: lnrpc.Lightning.RegisterRPCMiddleware:output_type -> lnrpc.RPCMiddlewareRequest
26, // 325: lnrpc.Lightning.SendCustomMessage:output_type -> lnrpc.SendCustomMessageResponse
24, // 326: lnrpc.Lightning.SubscribeCustomMessages:output_type -> lnrpc.CustomMessage
67, // 327: lnrpc.Lightning.ListAliases:output_type -> lnrpc.ListAliasesResponse
22, // 328: lnrpc.Lightning.LookupHtlcResolution:output_type -> lnrpc.LookupHtlcResolutionResponse
261, // [261:329] is the sub-list for method output_type
193, // [193:261] is the sub-list for method input_type
193, // [193:193] is the sub-list for extension type_name
193, // [193:193] is the sub-list for extension extendee
0, // [0:193] is the sub-list for field type_name
}
func init() { file_lightning_proto_init() }
func file_lightning_proto_init() {
if File_lightning_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_lightning_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*LookupHtlcResolutionRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*LookupHtlcResolutionResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SubscribeCustomMessagesRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CustomMessage); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SendCustomMessageRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SendCustomMessageResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Utxo); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*OutputDetail); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Transaction); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetTransactionsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TransactionDetails); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FeeLimit); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SendRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SendResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SendToRouteRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChannelAcceptRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChannelAcceptResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChannelPoint); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*OutPoint); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PreviousOutPoint); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*LightningAddress); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*EstimateFeeRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*EstimateFeeResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SendManyRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SendManyResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SendCoinsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SendCoinsResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListUnspentRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListUnspentResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*NewAddressRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*NewAddressResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SignMessageRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SignMessageResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*VerifyMessageRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*VerifyMessageResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ConnectPeerRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ConnectPeerResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DisconnectPeerRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DisconnectPeerResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HTLC); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChannelConstraints); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Channel); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListChannelsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListChannelsResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AliasMap); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListAliasesRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[46].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListAliasesResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[47].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChannelCloseSummary); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[48].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Resolution); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[49].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ClosedChannelsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[50].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ClosedChannelsResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[51].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Peer); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[52].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TimestampedError); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[53].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListPeersRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[54].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListPeersResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[55].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PeerEventSubscription); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[56].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PeerEvent); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[57].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetInfoRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[58].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetInfoResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[59].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetDebugInfoRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[60].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetDebugInfoResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[61].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetRecoveryInfoRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[62].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetRecoveryInfoResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[63].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Chain); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[64].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ConfirmationUpdate); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[65].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChannelOpenUpdate); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[66].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CloseOutput); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[67].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChannelCloseUpdate); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[68].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CloseChannelRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[69].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CloseStatusUpdate); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[70].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PendingUpdate); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[71].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*InstantUpdate); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[72].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ReadyForPsbtFunding); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[73].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BatchOpenChannelRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[74].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BatchOpenChannel); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[75].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BatchOpenChannelResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[76].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*OpenChannelRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[77].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*OpenStatusUpdate); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[78].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*KeyLocator); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[79].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*KeyDescriptor); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[80].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChanPointShim); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[81].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PsbtShim); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[82].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FundingShim); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[83].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FundingShimCancel); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[84].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FundingPsbtVerify); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[85].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FundingPsbtFinalize); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[86].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FundingTransitionMsg); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[87].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FundingStateStepResp); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[88].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PendingHTLC); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[89].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PendingChannelsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[90].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PendingChannelsResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[91].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChannelEventSubscription); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[92].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChannelEventUpdate); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[93].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*WalletAccountBalance); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[94].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*WalletBalanceRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[95].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*WalletBalanceResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[96].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Amount); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[97].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChannelBalanceRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[98].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChannelBalanceResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[99].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*QueryRoutesRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[100].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*NodePair); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[101].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*EdgeLocator); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[102].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*QueryRoutesResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[103].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Hop); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[104].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MPPRecord); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[105].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AMPRecord); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[106].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Route); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[107].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*NodeInfoRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[108].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*NodeInfo); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[109].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*LightningNode); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[110].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*NodeAddress); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[111].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RoutingPolicy); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[112].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChannelEdge); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[113].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChannelGraphRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[114].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChannelGraph); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[115].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*NodeMetricsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[116].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*NodeMetricsResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[117].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FloatMetric); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[118].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChanInfoRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[119].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*NetworkInfoRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[120].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*NetworkInfo); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[121].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StopRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[122].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StopResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[123].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GraphTopologySubscription); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[124].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GraphTopologyUpdate); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[125].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*NodeUpdate); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[126].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChannelEdgeUpdate); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[127].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ClosedChannelUpdate); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[128].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HopHint); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[129].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SetID); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[130].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RouteHint); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[131].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BlindedPaymentPath); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[132].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BlindedPath); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[133].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BlindedHop); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[134].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AMPInvoiceState); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[135].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Invoice); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[136].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BlindedPathConfig); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[137].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*InvoiceHTLC); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[138].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AMP); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[139].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AddInvoiceResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[140].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PaymentHash); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[141].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListInvoiceRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[142].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListInvoiceResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[143].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*InvoiceSubscription); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[144].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Payment); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[145].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HTLCAttempt); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[146].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListPaymentsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[147].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListPaymentsResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[148].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DeletePaymentRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[149].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DeleteAllPaymentsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[150].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DeletePaymentResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[151].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DeleteAllPaymentsResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[152].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AbandonChannelRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[153].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AbandonChannelResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[154].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DebugLevelRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[155].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DebugLevelResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[156].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PayReqString); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[157].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PayReq); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[158].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Feature); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[159].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FeeReportRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[160].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChannelFeeReport); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[161].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FeeReportResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[162].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*InboundFee); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[163].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PolicyUpdateRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[164].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FailedUpdate); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[165].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PolicyUpdateResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[166].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ForwardingHistoryRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[167].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ForwardingEvent); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[168].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ForwardingHistoryResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[169].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ExportChannelBackupRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[170].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChannelBackup); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[171].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MultiChanBackup); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[172].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChanBackupExportRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[173].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChanBackupSnapshot); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[174].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChannelBackups); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[175].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RestoreChanBackupRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[176].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RestoreBackupResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[177].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChannelBackupSubscription); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[178].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*VerifyChanBackupResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[179].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MacaroonPermission); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[180].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BakeMacaroonRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[181].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BakeMacaroonResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[182].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListMacaroonIDsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[183].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListMacaroonIDsResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[184].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DeleteMacaroonIDRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[185].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DeleteMacaroonIDResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[186].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MacaroonPermissionList); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[187].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListPermissionsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[188].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListPermissionsResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[189].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Failure); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[190].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChannelUpdate); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[191].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MacaroonId); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[192].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Op); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[193].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CheckMacPermRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[194].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CheckMacPermResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[195].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RPCMiddlewareRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[196].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StreamAuth); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[197].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RPCMessage); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[198].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RPCMiddlewareResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[199].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MiddlewareRegistration); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[200].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*InterceptFeedback); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[207].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PendingChannelsResponse_PendingChannel); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[208].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PendingChannelsResponse_PendingOpenChannel); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[209].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PendingChannelsResponse_WaitingCloseChannel); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[210].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PendingChannelsResponse_Commitments); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[211].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PendingChannelsResponse_ClosedChannel); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lightning_proto_msgTypes[212].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PendingChannelsResponse_ForceClosedChannel); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_lightning_proto_msgTypes[11].OneofWrappers = []interface{}{
(*FeeLimit_Fixed)(nil),
(*FeeLimit_FixedMsat)(nil),
(*FeeLimit_Percent)(nil),
}
file_lightning_proto_msgTypes[17].OneofWrappers = []interface{}{
(*ChannelPoint_FundingTxidBytes)(nil),
(*ChannelPoint_FundingTxidStr)(nil),
}
file_lightning_proto_msgTypes[69].OneofWrappers = []interface{}{
(*CloseStatusUpdate_ClosePending)(nil),
(*CloseStatusUpdate_ChanClose)(nil),
(*CloseStatusUpdate_CloseInstant)(nil),
}
file_lightning_proto_msgTypes[77].OneofWrappers = []interface{}{
(*OpenStatusUpdate_ChanPending)(nil),
(*OpenStatusUpdate_ChanOpen)(nil),
(*OpenStatusUpdate_PsbtFund)(nil),
}
file_lightning_proto_msgTypes[82].OneofWrappers = []interface{}{
(*FundingShim_ChanPointShim)(nil),
(*FundingShim_PsbtShim)(nil),
}
file_lightning_proto_msgTypes[86].OneofWrappers = []interface{}{
(*FundingTransitionMsg_ShimRegister)(nil),
(*FundingTransitionMsg_ShimCancel)(nil),
(*FundingTransitionMsg_PsbtVerify)(nil),
(*FundingTransitionMsg_PsbtFinalize)(nil),
}
file_lightning_proto_msgTypes[92].OneofWrappers = []interface{}{
(*ChannelEventUpdate_OpenChannel)(nil),
(*ChannelEventUpdate_ClosedChannel)(nil),
(*ChannelEventUpdate_ActiveChannel)(nil),
(*ChannelEventUpdate_InactiveChannel)(nil),
(*ChannelEventUpdate_PendingOpenChannel)(nil),
(*ChannelEventUpdate_FullyResolvedChannel)(nil),
}
file_lightning_proto_msgTypes[136].OneofWrappers = []interface{}{}
file_lightning_proto_msgTypes[163].OneofWrappers = []interface{}{
(*PolicyUpdateRequest_Global)(nil),
(*PolicyUpdateRequest_ChanPoint)(nil),
}
file_lightning_proto_msgTypes[175].OneofWrappers = []interface{}{
(*RestoreChanBackupRequest_ChanBackups)(nil),
(*RestoreChanBackupRequest_MultiChanBackup)(nil),
}
file_lightning_proto_msgTypes[195].OneofWrappers = []interface{}{
(*RPCMiddlewareRequest_StreamAuth)(nil),
(*RPCMiddlewareRequest_Request)(nil),
(*RPCMiddlewareRequest_Response)(nil),
(*RPCMiddlewareRequest_RegComplete)(nil),
}
file_lightning_proto_msgTypes[198].OneofWrappers = []interface{}{
(*RPCMiddlewareResponse_Register)(nil),
(*RPCMiddlewareResponse_Feedback)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_lightning_proto_rawDesc,
NumEnums: 21,
NumMessages: 228,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_lightning_proto_goTypes,
DependencyIndexes: file_lightning_proto_depIdxs,
EnumInfos: file_lightning_proto_enumTypes,
MessageInfos: file_lightning_proto_msgTypes,
}.Build()
File_lightning_proto = out.File
file_lightning_proto_rawDesc = nil
file_lightning_proto_goTypes = nil
file_lightning_proto_depIdxs = nil
}
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: lightning.proto
/*
Package lnrpc is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package lnrpc
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
var (
filter_Lightning_WalletBalance_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Lightning_WalletBalance_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq WalletBalanceRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_WalletBalance_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.WalletBalance(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_WalletBalance_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq WalletBalanceRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_WalletBalance_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.WalletBalance(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_ChannelBalance_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ChannelBalanceRequest
var metadata runtime.ServerMetadata
msg, err := client.ChannelBalance(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_ChannelBalance_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ChannelBalanceRequest
var metadata runtime.ServerMetadata
msg, err := server.ChannelBalance(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Lightning_GetTransactions_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Lightning_GetTransactions_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetTransactionsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_GetTransactions_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.GetTransactions(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_GetTransactions_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetTransactionsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_GetTransactions_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.GetTransactions(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Lightning_EstimateFee_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Lightning_EstimateFee_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq EstimateFeeRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_EstimateFee_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.EstimateFee(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_EstimateFee_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq EstimateFeeRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_EstimateFee_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.EstimateFee(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_SendCoins_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SendCoinsRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.SendCoins(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_SendCoins_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SendCoinsRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.SendCoins(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Lightning_ListUnspent_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Lightning_ListUnspent_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListUnspentRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_ListUnspent_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ListUnspent(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_ListUnspent_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListUnspentRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_ListUnspent_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ListUnspent(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Lightning_SubscribeTransactions_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Lightning_SubscribeTransactions_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (Lightning_SubscribeTransactionsClient, runtime.ServerMetadata, error) {
var protoReq GetTransactionsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_SubscribeTransactions_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
stream, err := client.SubscribeTransactions(ctx, &protoReq)
if err != nil {
return nil, metadata, err
}
header, err := stream.Header()
if err != nil {
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
func request_Lightning_SendMany_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SendManyRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.SendMany(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_SendMany_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SendManyRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.SendMany(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Lightning_NewAddress_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Lightning_NewAddress_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq NewAddressRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_NewAddress_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.NewAddress(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_NewAddress_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq NewAddressRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_NewAddress_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.NewAddress(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_SignMessage_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SignMessageRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.SignMessage(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_SignMessage_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SignMessageRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.SignMessage(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_VerifyMessage_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq VerifyMessageRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.VerifyMessage(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_VerifyMessage_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq VerifyMessageRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.VerifyMessage(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_ConnectPeer_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ConnectPeerRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ConnectPeer(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_ConnectPeer_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ConnectPeerRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ConnectPeer(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_DisconnectPeer_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DisconnectPeerRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["pub_key"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pub_key")
}
protoReq.PubKey, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pub_key", err)
}
msg, err := client.DisconnectPeer(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_DisconnectPeer_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DisconnectPeerRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["pub_key"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pub_key")
}
protoReq.PubKey, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pub_key", err)
}
msg, err := server.DisconnectPeer(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Lightning_ListPeers_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Lightning_ListPeers_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListPeersRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_ListPeers_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ListPeers(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_ListPeers_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListPeersRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_ListPeers_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ListPeers(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_SubscribePeerEvents_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (Lightning_SubscribePeerEventsClient, runtime.ServerMetadata, error) {
var protoReq PeerEventSubscription
var metadata runtime.ServerMetadata
stream, err := client.SubscribePeerEvents(ctx, &protoReq)
if err != nil {
return nil, metadata, err
}
header, err := stream.Header()
if err != nil {
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
func request_Lightning_GetInfo_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetInfoRequest
var metadata runtime.ServerMetadata
msg, err := client.GetInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_GetInfo_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetInfoRequest
var metadata runtime.ServerMetadata
msg, err := server.GetInfo(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_GetDebugInfo_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetDebugInfoRequest
var metadata runtime.ServerMetadata
msg, err := client.GetDebugInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_GetDebugInfo_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetDebugInfoRequest
var metadata runtime.ServerMetadata
msg, err := server.GetDebugInfo(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_GetRecoveryInfo_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetRecoveryInfoRequest
var metadata runtime.ServerMetadata
msg, err := client.GetRecoveryInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_GetRecoveryInfo_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetRecoveryInfoRequest
var metadata runtime.ServerMetadata
msg, err := server.GetRecoveryInfo(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Lightning_PendingChannels_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Lightning_PendingChannels_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq PendingChannelsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_PendingChannels_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.PendingChannels(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_PendingChannels_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq PendingChannelsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_PendingChannels_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.PendingChannels(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Lightning_ListChannels_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Lightning_ListChannels_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListChannelsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_ListChannels_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ListChannels(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_ListChannels_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListChannelsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_ListChannels_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ListChannels(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_SubscribeChannelEvents_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (Lightning_SubscribeChannelEventsClient, runtime.ServerMetadata, error) {
var protoReq ChannelEventSubscription
var metadata runtime.ServerMetadata
stream, err := client.SubscribeChannelEvents(ctx, &protoReq)
if err != nil {
return nil, metadata, err
}
header, err := stream.Header()
if err != nil {
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
var (
filter_Lightning_ClosedChannels_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Lightning_ClosedChannels_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ClosedChannelsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_ClosedChannels_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ClosedChannels(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_ClosedChannels_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ClosedChannelsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_ClosedChannels_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ClosedChannels(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_OpenChannelSync_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq OpenChannelRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.OpenChannelSync(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_OpenChannelSync_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq OpenChannelRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.OpenChannelSync(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_OpenChannel_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (Lightning_OpenChannelClient, runtime.ServerMetadata, error) {
var protoReq OpenChannelRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
stream, err := client.OpenChannel(ctx, &protoReq)
if err != nil {
return nil, metadata, err
}
header, err := stream.Header()
if err != nil {
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
func request_Lightning_BatchOpenChannel_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq BatchOpenChannelRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.BatchOpenChannel(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_BatchOpenChannel_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq BatchOpenChannelRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.BatchOpenChannel(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_FundingStateStep_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq FundingTransitionMsg
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.FundingStateStep(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_FundingStateStep_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq FundingTransitionMsg
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.FundingStateStep(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_ChannelAcceptor_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (Lightning_ChannelAcceptorClient, runtime.ServerMetadata, error) {
var metadata runtime.ServerMetadata
stream, err := client.ChannelAcceptor(ctx)
if err != nil {
grpclog.Infof("Failed to start streaming: %v", err)
return nil, metadata, err
}
dec := marshaler.NewDecoder(req.Body)
handleSend := func() error {
var protoReq ChannelAcceptResponse
err := dec.Decode(&protoReq)
if err == io.EOF {
return err
}
if err != nil {
grpclog.Infof("Failed to decode request: %v", err)
return err
}
if err := stream.Send(&protoReq); err != nil {
grpclog.Infof("Failed to send request: %v", err)
return err
}
return nil
}
go func() {
for {
if err := handleSend(); err != nil {
break
}
}
if err := stream.CloseSend(); err != nil {
grpclog.Infof("Failed to terminate client stream: %v", err)
}
}()
header, err := stream.Header()
if err != nil {
grpclog.Infof("Failed to get header from client: %v", err)
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
var (
filter_Lightning_CloseChannel_0 = &utilities.DoubleArray{Encoding: map[string]int{"channel_point": 0, "funding_txid_str": 1, "fundingTxidStr": 2, "output_index": 3, "outputIndex": 4}, Base: []int{1, 1, 1, 3, 2, 4, 0, 0, 0, 0}, Check: []int{0, 1, 2, 1, 2, 1, 3, 5, 4, 6}}
)
func request_Lightning_CloseChannel_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (Lightning_CloseChannelClient, runtime.ServerMetadata, error) {
var protoReq CloseChannelRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["channel_point.funding_txid_str"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_point.funding_txid_str")
}
err = runtime.PopulateFieldFromPath(&protoReq, "channel_point.funding_txid_str", val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_point.funding_txid_str", err)
}
val, ok = pathParams["channel_point.output_index"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_point.output_index")
}
err = runtime.PopulateFieldFromPath(&protoReq, "channel_point.output_index", val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_point.output_index", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_CloseChannel_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
stream, err := client.CloseChannel(ctx, &protoReq)
if err != nil {
return nil, metadata, err
}
header, err := stream.Header()
if err != nil {
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
var (
filter_Lightning_AbandonChannel_0 = &utilities.DoubleArray{Encoding: map[string]int{"channel_point": 0, "funding_txid_str": 1, "fundingTxidStr": 2, "output_index": 3, "outputIndex": 4}, Base: []int{1, 1, 1, 3, 2, 4, 0, 0, 0, 0}, Check: []int{0, 1, 2, 1, 2, 1, 3, 5, 4, 6}}
)
func request_Lightning_AbandonChannel_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq AbandonChannelRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["channel_point.funding_txid_str"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_point.funding_txid_str")
}
err = runtime.PopulateFieldFromPath(&protoReq, "channel_point.funding_txid_str", val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_point.funding_txid_str", err)
}
val, ok = pathParams["channel_point.output_index"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_point.output_index")
}
err = runtime.PopulateFieldFromPath(&protoReq, "channel_point.output_index", val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_point.output_index", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_AbandonChannel_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.AbandonChannel(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_AbandonChannel_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq AbandonChannelRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["channel_point.funding_txid_str"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_point.funding_txid_str")
}
err = runtime.PopulateFieldFromPath(&protoReq, "channel_point.funding_txid_str", val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_point.funding_txid_str", err)
}
val, ok = pathParams["channel_point.output_index"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_point.output_index")
}
err = runtime.PopulateFieldFromPath(&protoReq, "channel_point.output_index", val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_point.output_index", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_AbandonChannel_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.AbandonChannel(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_SendPayment_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (Lightning_SendPaymentClient, runtime.ServerMetadata, error) {
var metadata runtime.ServerMetadata
stream, err := client.SendPayment(ctx)
if err != nil {
grpclog.Infof("Failed to start streaming: %v", err)
return nil, metadata, err
}
dec := marshaler.NewDecoder(req.Body)
handleSend := func() error {
var protoReq SendRequest
err := dec.Decode(&protoReq)
if err == io.EOF {
return err
}
if err != nil {
grpclog.Infof("Failed to decode request: %v", err)
return err
}
if err := stream.Send(&protoReq); err != nil {
grpclog.Infof("Failed to send request: %v", err)
return err
}
return nil
}
go func() {
for {
if err := handleSend(); err != nil {
break
}
}
if err := stream.CloseSend(); err != nil {
grpclog.Infof("Failed to terminate client stream: %v", err)
}
}()
header, err := stream.Header()
if err != nil {
grpclog.Infof("Failed to get header from client: %v", err)
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
func request_Lightning_SendPaymentSync_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SendRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.SendPaymentSync(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_SendPaymentSync_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SendRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.SendPaymentSync(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_SendToRouteSync_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SendToRouteRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.SendToRouteSync(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_SendToRouteSync_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SendToRouteRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.SendToRouteSync(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_AddInvoice_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq Invoice
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.AddInvoice(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_AddInvoice_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq Invoice
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.AddInvoice(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Lightning_ListInvoices_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Lightning_ListInvoices_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListInvoiceRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_ListInvoices_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ListInvoices(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_ListInvoices_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListInvoiceRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_ListInvoices_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ListInvoices(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Lightning_LookupInvoice_0 = &utilities.DoubleArray{Encoding: map[string]int{"r_hash_str": 0, "rHashStr": 1}, Base: []int{1, 1, 2, 0, 0}, Check: []int{0, 1, 1, 2, 3}}
)
func request_Lightning_LookupInvoice_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq PaymentHash
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["r_hash_str"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "r_hash_str")
}
protoReq.RHashStr, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "r_hash_str", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_LookupInvoice_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.LookupInvoice(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_LookupInvoice_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq PaymentHash
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["r_hash_str"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "r_hash_str")
}
protoReq.RHashStr, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "r_hash_str", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_LookupInvoice_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.LookupInvoice(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Lightning_SubscribeInvoices_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Lightning_SubscribeInvoices_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (Lightning_SubscribeInvoicesClient, runtime.ServerMetadata, error) {
var protoReq InvoiceSubscription
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_SubscribeInvoices_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
stream, err := client.SubscribeInvoices(ctx, &protoReq)
if err != nil {
return nil, metadata, err
}
header, err := stream.Header()
if err != nil {
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
func request_Lightning_DecodePayReq_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq PayReqString
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["pay_req"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pay_req")
}
protoReq.PayReq, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pay_req", err)
}
msg, err := client.DecodePayReq(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_DecodePayReq_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq PayReqString
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["pay_req"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pay_req")
}
protoReq.PayReq, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pay_req", err)
}
msg, err := server.DecodePayReq(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Lightning_ListPayments_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Lightning_ListPayments_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListPaymentsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_ListPayments_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ListPayments(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_ListPayments_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListPaymentsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_ListPayments_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ListPayments(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Lightning_DeletePayment_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Lightning_DeletePayment_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeletePaymentRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_DeletePayment_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.DeletePayment(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_DeletePayment_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeletePaymentRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_DeletePayment_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.DeletePayment(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Lightning_DeleteAllPayments_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Lightning_DeleteAllPayments_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeleteAllPaymentsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_DeleteAllPayments_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.DeleteAllPayments(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_DeleteAllPayments_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeleteAllPaymentsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_DeleteAllPayments_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.DeleteAllPayments(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Lightning_DescribeGraph_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Lightning_DescribeGraph_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ChannelGraphRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_DescribeGraph_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.DescribeGraph(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_DescribeGraph_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ChannelGraphRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_DescribeGraph_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.DescribeGraph(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Lightning_GetNodeMetrics_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Lightning_GetNodeMetrics_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq NodeMetricsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_GetNodeMetrics_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.GetNodeMetrics(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_GetNodeMetrics_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq NodeMetricsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_GetNodeMetrics_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.GetNodeMetrics(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Lightning_GetChanInfo_0 = &utilities.DoubleArray{Encoding: map[string]int{"chan_id": 0, "chanId": 1}, Base: []int{1, 1, 2, 0, 0}, Check: []int{0, 1, 1, 2, 3}}
)
func request_Lightning_GetChanInfo_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ChanInfoRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["chan_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "chan_id")
}
protoReq.ChanId, err = runtime.Uint64(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "chan_id", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_GetChanInfo_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.GetChanInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_GetChanInfo_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ChanInfoRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["chan_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "chan_id")
}
protoReq.ChanId, err = runtime.Uint64(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "chan_id", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_GetChanInfo_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.GetChanInfo(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Lightning_GetNodeInfo_0 = &utilities.DoubleArray{Encoding: map[string]int{"pub_key": 0, "pubKey": 1}, Base: []int{1, 1, 2, 0, 0}, Check: []int{0, 1, 1, 2, 3}}
)
func request_Lightning_GetNodeInfo_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq NodeInfoRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["pub_key"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pub_key")
}
protoReq.PubKey, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pub_key", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_GetNodeInfo_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.GetNodeInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_GetNodeInfo_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq NodeInfoRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["pub_key"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pub_key")
}
protoReq.PubKey, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pub_key", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_GetNodeInfo_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.GetNodeInfo(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Lightning_QueryRoutes_0 = &utilities.DoubleArray{Encoding: map[string]int{"pub_key": 0, "pubKey": 1, "amt": 2}, Base: []int{1, 1, 2, 4, 0, 0, 0, 0}, Check: []int{0, 1, 1, 1, 2, 3, 4, 4}}
)
func request_Lightning_QueryRoutes_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq QueryRoutesRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["pub_key"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pub_key")
}
protoReq.PubKey, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pub_key", err)
}
val, ok = pathParams["amt"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "amt")
}
protoReq.Amt, err = runtime.Int64(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "amt", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_QueryRoutes_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.QueryRoutes(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_QueryRoutes_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq QueryRoutesRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["pub_key"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pub_key")
}
protoReq.PubKey, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pub_key", err)
}
val, ok = pathParams["amt"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "amt")
}
protoReq.Amt, err = runtime.Int64(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "amt", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_QueryRoutes_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.QueryRoutes(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_QueryRoutes_1(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq QueryRoutesRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["pub_key"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pub_key")
}
protoReq.PubKey, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pub_key", err)
}
val, ok = pathParams["amt"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "amt")
}
protoReq.Amt, err = runtime.Int64(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "amt", err)
}
msg, err := client.QueryRoutes(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_QueryRoutes_1(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq QueryRoutesRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["pub_key"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pub_key")
}
protoReq.PubKey, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pub_key", err)
}
val, ok = pathParams["amt"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "amt")
}
protoReq.Amt, err = runtime.Int64(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "amt", err)
}
msg, err := server.QueryRoutes(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_GetNetworkInfo_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq NetworkInfoRequest
var metadata runtime.ServerMetadata
msg, err := client.GetNetworkInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_GetNetworkInfo_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq NetworkInfoRequest
var metadata runtime.ServerMetadata
msg, err := server.GetNetworkInfo(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_StopDaemon_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq StopRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.StopDaemon(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_StopDaemon_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq StopRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.StopDaemon(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_SubscribeChannelGraph_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (Lightning_SubscribeChannelGraphClient, runtime.ServerMetadata, error) {
var protoReq GraphTopologySubscription
var metadata runtime.ServerMetadata
stream, err := client.SubscribeChannelGraph(ctx, &protoReq)
if err != nil {
return nil, metadata, err
}
header, err := stream.Header()
if err != nil {
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
func request_Lightning_DebugLevel_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DebugLevelRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.DebugLevel(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_DebugLevel_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DebugLevelRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.DebugLevel(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_FeeReport_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq FeeReportRequest
var metadata runtime.ServerMetadata
msg, err := client.FeeReport(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_FeeReport_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq FeeReportRequest
var metadata runtime.ServerMetadata
msg, err := server.FeeReport(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_UpdateChannelPolicy_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq PolicyUpdateRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.UpdateChannelPolicy(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_UpdateChannelPolicy_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq PolicyUpdateRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.UpdateChannelPolicy(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_ForwardingHistory_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ForwardingHistoryRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ForwardingHistory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_ForwardingHistory_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ForwardingHistoryRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ForwardingHistory(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Lightning_ExportChannelBackup_0 = &utilities.DoubleArray{Encoding: map[string]int{"chan_point": 0, "funding_txid_str": 1, "fundingTxidStr": 2, "output_index": 3, "outputIndex": 4}, Base: []int{1, 1, 1, 3, 2, 4, 0, 0, 0, 0}, Check: []int{0, 1, 2, 1, 2, 1, 3, 5, 4, 6}}
)
func request_Lightning_ExportChannelBackup_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ExportChannelBackupRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["chan_point.funding_txid_str"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "chan_point.funding_txid_str")
}
err = runtime.PopulateFieldFromPath(&protoReq, "chan_point.funding_txid_str", val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "chan_point.funding_txid_str", err)
}
val, ok = pathParams["chan_point.output_index"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "chan_point.output_index")
}
err = runtime.PopulateFieldFromPath(&protoReq, "chan_point.output_index", val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "chan_point.output_index", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_ExportChannelBackup_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ExportChannelBackup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_ExportChannelBackup_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ExportChannelBackupRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["chan_point.funding_txid_str"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "chan_point.funding_txid_str")
}
err = runtime.PopulateFieldFromPath(&protoReq, "chan_point.funding_txid_str", val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "chan_point.funding_txid_str", err)
}
val, ok = pathParams["chan_point.output_index"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "chan_point.output_index")
}
err = runtime.PopulateFieldFromPath(&protoReq, "chan_point.output_index", val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "chan_point.output_index", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Lightning_ExportChannelBackup_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ExportChannelBackup(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_ExportAllChannelBackups_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ChanBackupExportRequest
var metadata runtime.ServerMetadata
msg, err := client.ExportAllChannelBackups(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_ExportAllChannelBackups_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ChanBackupExportRequest
var metadata runtime.ServerMetadata
msg, err := server.ExportAllChannelBackups(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_VerifyChanBackup_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ChanBackupSnapshot
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.VerifyChanBackup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_VerifyChanBackup_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ChanBackupSnapshot
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.VerifyChanBackup(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_RestoreChannelBackups_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq RestoreChanBackupRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.RestoreChannelBackups(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_RestoreChannelBackups_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq RestoreChanBackupRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.RestoreChannelBackups(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_SubscribeChannelBackups_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (Lightning_SubscribeChannelBackupsClient, runtime.ServerMetadata, error) {
var protoReq ChannelBackupSubscription
var metadata runtime.ServerMetadata
stream, err := client.SubscribeChannelBackups(ctx, &protoReq)
if err != nil {
return nil, metadata, err
}
header, err := stream.Header()
if err != nil {
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
func request_Lightning_BakeMacaroon_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq BakeMacaroonRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.BakeMacaroon(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_BakeMacaroon_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq BakeMacaroonRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.BakeMacaroon(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_ListMacaroonIDs_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListMacaroonIDsRequest
var metadata runtime.ServerMetadata
msg, err := client.ListMacaroonIDs(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_ListMacaroonIDs_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListMacaroonIDsRequest
var metadata runtime.ServerMetadata
msg, err := server.ListMacaroonIDs(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_DeleteMacaroonID_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeleteMacaroonIDRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["root_key_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "root_key_id")
}
protoReq.RootKeyId, err = runtime.Uint64(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "root_key_id", err)
}
msg, err := client.DeleteMacaroonID(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_DeleteMacaroonID_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeleteMacaroonIDRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["root_key_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "root_key_id")
}
protoReq.RootKeyId, err = runtime.Uint64(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "root_key_id", err)
}
msg, err := server.DeleteMacaroonID(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_ListPermissions_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListPermissionsRequest
var metadata runtime.ServerMetadata
msg, err := client.ListPermissions(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_ListPermissions_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListPermissionsRequest
var metadata runtime.ServerMetadata
msg, err := server.ListPermissions(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_CheckMacaroonPermissions_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CheckMacPermRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.CheckMacaroonPermissions(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_CheckMacaroonPermissions_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CheckMacPermRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.CheckMacaroonPermissions(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_RegisterRPCMiddleware_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (Lightning_RegisterRPCMiddlewareClient, runtime.ServerMetadata, error) {
var metadata runtime.ServerMetadata
stream, err := client.RegisterRPCMiddleware(ctx)
if err != nil {
grpclog.Infof("Failed to start streaming: %v", err)
return nil, metadata, err
}
dec := marshaler.NewDecoder(req.Body)
handleSend := func() error {
var protoReq RPCMiddlewareResponse
err := dec.Decode(&protoReq)
if err == io.EOF {
return err
}
if err != nil {
grpclog.Infof("Failed to decode request: %v", err)
return err
}
if err := stream.Send(&protoReq); err != nil {
grpclog.Infof("Failed to send request: %v", err)
return err
}
return nil
}
go func() {
for {
if err := handleSend(); err != nil {
break
}
}
if err := stream.CloseSend(); err != nil {
grpclog.Infof("Failed to terminate client stream: %v", err)
}
}()
header, err := stream.Header()
if err != nil {
grpclog.Infof("Failed to get header from client: %v", err)
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
func request_Lightning_SendCustomMessage_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SendCustomMessageRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.SendCustomMessage(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_SendCustomMessage_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SendCustomMessageRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.SendCustomMessage(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_SubscribeCustomMessages_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (Lightning_SubscribeCustomMessagesClient, runtime.ServerMetadata, error) {
var protoReq SubscribeCustomMessagesRequest
var metadata runtime.ServerMetadata
stream, err := client.SubscribeCustomMessages(ctx, &protoReq)
if err != nil {
return nil, metadata, err
}
header, err := stream.Header()
if err != nil {
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
func request_Lightning_ListAliases_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListAliasesRequest
var metadata runtime.ServerMetadata
msg, err := client.ListAliases(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_ListAliases_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListAliasesRequest
var metadata runtime.ServerMetadata
msg, err := server.ListAliases(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_LookupHtlcResolution_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq LookupHtlcResolutionRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["chan_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "chan_id")
}
protoReq.ChanId, err = runtime.Uint64(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "chan_id", err)
}
val, ok = pathParams["htlc_index"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "htlc_index")
}
protoReq.HtlcIndex, err = runtime.Uint64(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "htlc_index", err)
}
msg, err := client.LookupHtlcResolution(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_LookupHtlcResolution_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq LookupHtlcResolutionRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["chan_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "chan_id")
}
protoReq.ChanId, err = runtime.Uint64(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "chan_id", err)
}
val, ok = pathParams["htlc_index"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "htlc_index")
}
protoReq.HtlcIndex, err = runtime.Uint64(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "htlc_index", err)
}
msg, err := server.LookupHtlcResolution(ctx, &protoReq)
return msg, metadata, err
}
// RegisterLightningHandlerServer registers the http handlers for service Lightning to "mux".
// UnaryRPC :call LightningServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterLightningHandlerFromEndpoint instead.
func RegisterLightningHandlerServer(ctx context.Context, mux *runtime.ServeMux, server LightningServer) error {
mux.Handle("GET", pattern_Lightning_WalletBalance_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/WalletBalance", runtime.WithHTTPPathPattern("/v1/balance/blockchain"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_WalletBalance_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_WalletBalance_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_ChannelBalance_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/ChannelBalance", runtime.WithHTTPPathPattern("/v1/balance/channels"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_ChannelBalance_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ChannelBalance_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_GetTransactions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/GetTransactions", runtime.WithHTTPPathPattern("/v1/transactions"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_GetTransactions_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_GetTransactions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_EstimateFee_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/EstimateFee", runtime.WithHTTPPathPattern("/v1/transactions/fee"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_EstimateFee_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_EstimateFee_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_SendCoins_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/SendCoins", runtime.WithHTTPPathPattern("/v1/transactions"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_SendCoins_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_SendCoins_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_ListUnspent_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/ListUnspent", runtime.WithHTTPPathPattern("/v1/utxos"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_ListUnspent_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ListUnspent_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_SubscribeTransactions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
mux.Handle("POST", pattern_Lightning_SendMany_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/SendMany", runtime.WithHTTPPathPattern("/v1/transactions/many"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_SendMany_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_SendMany_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_NewAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/NewAddress", runtime.WithHTTPPathPattern("/v1/newaddress"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_NewAddress_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_NewAddress_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_SignMessage_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/SignMessage", runtime.WithHTTPPathPattern("/v1/signmessage"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_SignMessage_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_SignMessage_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_VerifyMessage_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/VerifyMessage", runtime.WithHTTPPathPattern("/v1/verifymessage"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_VerifyMessage_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_VerifyMessage_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_ConnectPeer_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/ConnectPeer", runtime.WithHTTPPathPattern("/v1/peers"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_ConnectPeer_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ConnectPeer_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_Lightning_DisconnectPeer_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/DisconnectPeer", runtime.WithHTTPPathPattern("/v1/peers/{pub_key}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_DisconnectPeer_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_DisconnectPeer_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_ListPeers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/ListPeers", runtime.WithHTTPPathPattern("/v1/peers"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_ListPeers_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ListPeers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_SubscribePeerEvents_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
mux.Handle("GET", pattern_Lightning_GetInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/GetInfo", runtime.WithHTTPPathPattern("/v1/getinfo"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_GetInfo_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_GetInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_GetDebugInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/GetDebugInfo", runtime.WithHTTPPathPattern("/v1/getdebuginfo"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_GetDebugInfo_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_GetDebugInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_GetRecoveryInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/GetRecoveryInfo", runtime.WithHTTPPathPattern("/v1/getrecoveryinfo"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_GetRecoveryInfo_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_GetRecoveryInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_PendingChannels_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/PendingChannels", runtime.WithHTTPPathPattern("/v1/channels/pending"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_PendingChannels_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_PendingChannels_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_ListChannels_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/ListChannels", runtime.WithHTTPPathPattern("/v1/channels"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_ListChannels_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ListChannels_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_SubscribeChannelEvents_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
mux.Handle("GET", pattern_Lightning_ClosedChannels_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/ClosedChannels", runtime.WithHTTPPathPattern("/v1/channels/closed"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_ClosedChannels_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ClosedChannels_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_OpenChannelSync_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/OpenChannelSync", runtime.WithHTTPPathPattern("/v1/channels"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_OpenChannelSync_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_OpenChannelSync_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_OpenChannel_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
mux.Handle("POST", pattern_Lightning_BatchOpenChannel_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/BatchOpenChannel", runtime.WithHTTPPathPattern("/v1/channels/batch"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_BatchOpenChannel_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_BatchOpenChannel_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_FundingStateStep_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/FundingStateStep", runtime.WithHTTPPathPattern("/v1/funding/step"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_FundingStateStep_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_FundingStateStep_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_ChannelAcceptor_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
mux.Handle("DELETE", pattern_Lightning_CloseChannel_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
mux.Handle("DELETE", pattern_Lightning_AbandonChannel_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/AbandonChannel", runtime.WithHTTPPathPattern("/v1/channels/abandon/{channel_point.funding_txid_str}/{channel_point.output_index}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_AbandonChannel_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_AbandonChannel_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_SendPayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
mux.Handle("POST", pattern_Lightning_SendPaymentSync_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/SendPaymentSync", runtime.WithHTTPPathPattern("/v1/channels/transactions"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_SendPaymentSync_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_SendPaymentSync_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_SendToRouteSync_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/SendToRouteSync", runtime.WithHTTPPathPattern("/v1/channels/transactions/route"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_SendToRouteSync_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_SendToRouteSync_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_AddInvoice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/AddInvoice", runtime.WithHTTPPathPattern("/v1/invoices"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_AddInvoice_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_AddInvoice_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_ListInvoices_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/ListInvoices", runtime.WithHTTPPathPattern("/v1/invoices"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_ListInvoices_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ListInvoices_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_LookupInvoice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/LookupInvoice", runtime.WithHTTPPathPattern("/v1/invoice/{r_hash_str}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_LookupInvoice_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_LookupInvoice_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_SubscribeInvoices_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
mux.Handle("GET", pattern_Lightning_DecodePayReq_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/DecodePayReq", runtime.WithHTTPPathPattern("/v1/payreq/{pay_req}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_DecodePayReq_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_DecodePayReq_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_ListPayments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/ListPayments", runtime.WithHTTPPathPattern("/v1/payments"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_ListPayments_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ListPayments_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_Lightning_DeletePayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/DeletePayment", runtime.WithHTTPPathPattern("/v1/payment"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_DeletePayment_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_DeletePayment_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_Lightning_DeleteAllPayments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/DeleteAllPayments", runtime.WithHTTPPathPattern("/v1/payments"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_DeleteAllPayments_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_DeleteAllPayments_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_DescribeGraph_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/DescribeGraph", runtime.WithHTTPPathPattern("/v1/graph"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_DescribeGraph_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_DescribeGraph_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_GetNodeMetrics_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/GetNodeMetrics", runtime.WithHTTPPathPattern("/v1/graph/nodemetrics"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_GetNodeMetrics_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_GetNodeMetrics_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_GetChanInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/GetChanInfo", runtime.WithHTTPPathPattern("/v1/graph/edge/{chan_id}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_GetChanInfo_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_GetChanInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_GetNodeInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/GetNodeInfo", runtime.WithHTTPPathPattern("/v1/graph/node/{pub_key}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_GetNodeInfo_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_GetNodeInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_QueryRoutes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/QueryRoutes", runtime.WithHTTPPathPattern("/v1/graph/routes/{pub_key}/{amt}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_QueryRoutes_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_QueryRoutes_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_QueryRoutes_1, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/QueryRoutes", runtime.WithHTTPPathPattern("/v1/graph/routes/{pub_key}/{amt}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_QueryRoutes_1(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_QueryRoutes_1(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_GetNetworkInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/GetNetworkInfo", runtime.WithHTTPPathPattern("/v1/graph/info"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_GetNetworkInfo_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_GetNetworkInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_StopDaemon_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/StopDaemon", runtime.WithHTTPPathPattern("/v1/stop"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_StopDaemon_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_StopDaemon_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_SubscribeChannelGraph_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
mux.Handle("POST", pattern_Lightning_DebugLevel_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/DebugLevel", runtime.WithHTTPPathPattern("/v1/debuglevel"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_DebugLevel_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_DebugLevel_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_FeeReport_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/FeeReport", runtime.WithHTTPPathPattern("/v1/fees"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_FeeReport_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_FeeReport_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_UpdateChannelPolicy_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/UpdateChannelPolicy", runtime.WithHTTPPathPattern("/v1/chanpolicy"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_UpdateChannelPolicy_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_UpdateChannelPolicy_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_ForwardingHistory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/ForwardingHistory", runtime.WithHTTPPathPattern("/v1/switch"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_ForwardingHistory_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ForwardingHistory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_ExportChannelBackup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/ExportChannelBackup", runtime.WithHTTPPathPattern("/v1/channels/backup/{chan_point.funding_txid_str}/{chan_point.output_index}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_ExportChannelBackup_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ExportChannelBackup_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_ExportAllChannelBackups_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/ExportAllChannelBackups", runtime.WithHTTPPathPattern("/v1/channels/backup"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_ExportAllChannelBackups_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ExportAllChannelBackups_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_VerifyChanBackup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/VerifyChanBackup", runtime.WithHTTPPathPattern("/v1/channels/backup/verify"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_VerifyChanBackup_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_VerifyChanBackup_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_RestoreChannelBackups_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/RestoreChannelBackups", runtime.WithHTTPPathPattern("/v1/channels/backup/restore"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_RestoreChannelBackups_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_RestoreChannelBackups_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_SubscribeChannelBackups_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
mux.Handle("POST", pattern_Lightning_BakeMacaroon_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/BakeMacaroon", runtime.WithHTTPPathPattern("/v1/macaroon"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_BakeMacaroon_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_BakeMacaroon_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_ListMacaroonIDs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/ListMacaroonIDs", runtime.WithHTTPPathPattern("/v1/macaroon/ids"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_ListMacaroonIDs_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ListMacaroonIDs_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_Lightning_DeleteMacaroonID_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/DeleteMacaroonID", runtime.WithHTTPPathPattern("/v1/macaroon/{root_key_id}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_DeleteMacaroonID_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_DeleteMacaroonID_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_ListPermissions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/ListPermissions", runtime.WithHTTPPathPattern("/v1/macaroon/permissions"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_ListPermissions_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ListPermissions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_CheckMacaroonPermissions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/CheckMacaroonPermissions", runtime.WithHTTPPathPattern("/v1/macaroon/checkpermissions"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_CheckMacaroonPermissions_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_CheckMacaroonPermissions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_RegisterRPCMiddleware_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
mux.Handle("POST", pattern_Lightning_SendCustomMessage_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/SendCustomMessage", runtime.WithHTTPPathPattern("/v1/custommessage"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_SendCustomMessage_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_SendCustomMessage_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_SubscribeCustomMessages_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
mux.Handle("GET", pattern_Lightning_ListAliases_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/ListAliases", runtime.WithHTTPPathPattern("/v1/aliases/list"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_ListAliases_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ListAliases_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_LookupHtlcResolution_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.Lightning/LookupHtlcResolution", runtime.WithHTTPPathPattern("/v1/htlc-resolution/{chan_id}/{htlc_index}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_LookupHtlcResolution_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_LookupHtlcResolution_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterLightningHandlerFromEndpoint is same as RegisterLightningHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterLightningHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterLightningHandler(ctx, mux, conn)
}
// RegisterLightningHandler registers the http handlers for service Lightning to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterLightningHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterLightningHandlerClient(ctx, mux, NewLightningClient(conn))
}
// RegisterLightningHandlerClient registers the http handlers for service Lightning
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "LightningClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "LightningClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "LightningClient" to call the correct interceptors.
func RegisterLightningHandlerClient(ctx context.Context, mux *runtime.ServeMux, client LightningClient) error {
mux.Handle("GET", pattern_Lightning_WalletBalance_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/WalletBalance", runtime.WithHTTPPathPattern("/v1/balance/blockchain"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_WalletBalance_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_WalletBalance_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_ChannelBalance_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/ChannelBalance", runtime.WithHTTPPathPattern("/v1/balance/channels"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_ChannelBalance_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ChannelBalance_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_GetTransactions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/GetTransactions", runtime.WithHTTPPathPattern("/v1/transactions"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_GetTransactions_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_GetTransactions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_EstimateFee_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/EstimateFee", runtime.WithHTTPPathPattern("/v1/transactions/fee"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_EstimateFee_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_EstimateFee_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_SendCoins_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/SendCoins", runtime.WithHTTPPathPattern("/v1/transactions"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_SendCoins_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_SendCoins_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_ListUnspent_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/ListUnspent", runtime.WithHTTPPathPattern("/v1/utxos"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_ListUnspent_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ListUnspent_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_SubscribeTransactions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/SubscribeTransactions", runtime.WithHTTPPathPattern("/v1/transactions/subscribe"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_SubscribeTransactions_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_SubscribeTransactions_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_SendMany_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/SendMany", runtime.WithHTTPPathPattern("/v1/transactions/many"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_SendMany_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_SendMany_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_NewAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/NewAddress", runtime.WithHTTPPathPattern("/v1/newaddress"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_NewAddress_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_NewAddress_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_SignMessage_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/SignMessage", runtime.WithHTTPPathPattern("/v1/signmessage"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_SignMessage_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_SignMessage_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_VerifyMessage_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/VerifyMessage", runtime.WithHTTPPathPattern("/v1/verifymessage"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_VerifyMessage_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_VerifyMessage_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_ConnectPeer_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/ConnectPeer", runtime.WithHTTPPathPattern("/v1/peers"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_ConnectPeer_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ConnectPeer_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_Lightning_DisconnectPeer_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/DisconnectPeer", runtime.WithHTTPPathPattern("/v1/peers/{pub_key}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_DisconnectPeer_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_DisconnectPeer_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_ListPeers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/ListPeers", runtime.WithHTTPPathPattern("/v1/peers"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_ListPeers_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ListPeers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_SubscribePeerEvents_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/SubscribePeerEvents", runtime.WithHTTPPathPattern("/v1/peers/subscribe"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_SubscribePeerEvents_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_SubscribePeerEvents_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_GetInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/GetInfo", runtime.WithHTTPPathPattern("/v1/getinfo"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_GetInfo_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_GetInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_GetDebugInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/GetDebugInfo", runtime.WithHTTPPathPattern("/v1/getdebuginfo"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_GetDebugInfo_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_GetDebugInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_GetRecoveryInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/GetRecoveryInfo", runtime.WithHTTPPathPattern("/v1/getrecoveryinfo"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_GetRecoveryInfo_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_GetRecoveryInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_PendingChannels_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/PendingChannels", runtime.WithHTTPPathPattern("/v1/channels/pending"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_PendingChannels_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_PendingChannels_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_ListChannels_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/ListChannels", runtime.WithHTTPPathPattern("/v1/channels"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_ListChannels_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ListChannels_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_SubscribeChannelEvents_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/SubscribeChannelEvents", runtime.WithHTTPPathPattern("/v1/channels/subscribe"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_SubscribeChannelEvents_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_SubscribeChannelEvents_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_ClosedChannels_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/ClosedChannels", runtime.WithHTTPPathPattern("/v1/channels/closed"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_ClosedChannels_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ClosedChannels_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_OpenChannelSync_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/OpenChannelSync", runtime.WithHTTPPathPattern("/v1/channels"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_OpenChannelSync_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_OpenChannelSync_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_OpenChannel_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/OpenChannel", runtime.WithHTTPPathPattern("/v1/channels/stream"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_OpenChannel_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_OpenChannel_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_BatchOpenChannel_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/BatchOpenChannel", runtime.WithHTTPPathPattern("/v1/channels/batch"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_BatchOpenChannel_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_BatchOpenChannel_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_FundingStateStep_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/FundingStateStep", runtime.WithHTTPPathPattern("/v1/funding/step"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_FundingStateStep_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_FundingStateStep_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_ChannelAcceptor_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/ChannelAcceptor", runtime.WithHTTPPathPattern("/v1/channels/acceptor"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_ChannelAcceptor_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ChannelAcceptor_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_Lightning_CloseChannel_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/CloseChannel", runtime.WithHTTPPathPattern("/v1/channels/{channel_point.funding_txid_str}/{channel_point.output_index}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_CloseChannel_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_CloseChannel_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_Lightning_AbandonChannel_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/AbandonChannel", runtime.WithHTTPPathPattern("/v1/channels/abandon/{channel_point.funding_txid_str}/{channel_point.output_index}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_AbandonChannel_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_AbandonChannel_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_SendPayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/SendPayment", runtime.WithHTTPPathPattern("/v1/channels/transaction-stream"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_SendPayment_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_SendPayment_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_SendPaymentSync_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/SendPaymentSync", runtime.WithHTTPPathPattern("/v1/channels/transactions"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_SendPaymentSync_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_SendPaymentSync_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_SendToRouteSync_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/SendToRouteSync", runtime.WithHTTPPathPattern("/v1/channels/transactions/route"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_SendToRouteSync_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_SendToRouteSync_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_AddInvoice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/AddInvoice", runtime.WithHTTPPathPattern("/v1/invoices"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_AddInvoice_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_AddInvoice_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_ListInvoices_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/ListInvoices", runtime.WithHTTPPathPattern("/v1/invoices"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_ListInvoices_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ListInvoices_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_LookupInvoice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/LookupInvoice", runtime.WithHTTPPathPattern("/v1/invoice/{r_hash_str}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_LookupInvoice_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_LookupInvoice_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_SubscribeInvoices_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/SubscribeInvoices", runtime.WithHTTPPathPattern("/v1/invoices/subscribe"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_SubscribeInvoices_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_SubscribeInvoices_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_DecodePayReq_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/DecodePayReq", runtime.WithHTTPPathPattern("/v1/payreq/{pay_req}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_DecodePayReq_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_DecodePayReq_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_ListPayments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/ListPayments", runtime.WithHTTPPathPattern("/v1/payments"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_ListPayments_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ListPayments_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_Lightning_DeletePayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/DeletePayment", runtime.WithHTTPPathPattern("/v1/payment"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_DeletePayment_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_DeletePayment_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_Lightning_DeleteAllPayments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/DeleteAllPayments", runtime.WithHTTPPathPattern("/v1/payments"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_DeleteAllPayments_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_DeleteAllPayments_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_DescribeGraph_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/DescribeGraph", runtime.WithHTTPPathPattern("/v1/graph"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_DescribeGraph_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_DescribeGraph_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_GetNodeMetrics_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/GetNodeMetrics", runtime.WithHTTPPathPattern("/v1/graph/nodemetrics"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_GetNodeMetrics_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_GetNodeMetrics_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_GetChanInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/GetChanInfo", runtime.WithHTTPPathPattern("/v1/graph/edge/{chan_id}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_GetChanInfo_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_GetChanInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_GetNodeInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/GetNodeInfo", runtime.WithHTTPPathPattern("/v1/graph/node/{pub_key}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_GetNodeInfo_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_GetNodeInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_QueryRoutes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/QueryRoutes", runtime.WithHTTPPathPattern("/v1/graph/routes/{pub_key}/{amt}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_QueryRoutes_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_QueryRoutes_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_QueryRoutes_1, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/QueryRoutes", runtime.WithHTTPPathPattern("/v1/graph/routes/{pub_key}/{amt}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_QueryRoutes_1(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_QueryRoutes_1(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_GetNetworkInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/GetNetworkInfo", runtime.WithHTTPPathPattern("/v1/graph/info"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_GetNetworkInfo_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_GetNetworkInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_StopDaemon_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/StopDaemon", runtime.WithHTTPPathPattern("/v1/stop"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_StopDaemon_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_StopDaemon_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_SubscribeChannelGraph_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/SubscribeChannelGraph", runtime.WithHTTPPathPattern("/v1/graph/subscribe"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_SubscribeChannelGraph_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_SubscribeChannelGraph_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_DebugLevel_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/DebugLevel", runtime.WithHTTPPathPattern("/v1/debuglevel"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_DebugLevel_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_DebugLevel_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_FeeReport_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/FeeReport", runtime.WithHTTPPathPattern("/v1/fees"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_FeeReport_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_FeeReport_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_UpdateChannelPolicy_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/UpdateChannelPolicy", runtime.WithHTTPPathPattern("/v1/chanpolicy"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_UpdateChannelPolicy_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_UpdateChannelPolicy_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_ForwardingHistory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/ForwardingHistory", runtime.WithHTTPPathPattern("/v1/switch"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_ForwardingHistory_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ForwardingHistory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_ExportChannelBackup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/ExportChannelBackup", runtime.WithHTTPPathPattern("/v1/channels/backup/{chan_point.funding_txid_str}/{chan_point.output_index}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_ExportChannelBackup_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ExportChannelBackup_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_ExportAllChannelBackups_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/ExportAllChannelBackups", runtime.WithHTTPPathPattern("/v1/channels/backup"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_ExportAllChannelBackups_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ExportAllChannelBackups_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_VerifyChanBackup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/VerifyChanBackup", runtime.WithHTTPPathPattern("/v1/channels/backup/verify"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_VerifyChanBackup_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_VerifyChanBackup_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_RestoreChannelBackups_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/RestoreChannelBackups", runtime.WithHTTPPathPattern("/v1/channels/backup/restore"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_RestoreChannelBackups_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_RestoreChannelBackups_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_SubscribeChannelBackups_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/SubscribeChannelBackups", runtime.WithHTTPPathPattern("/v1/channels/backup/subscribe"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_SubscribeChannelBackups_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_SubscribeChannelBackups_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_BakeMacaroon_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/BakeMacaroon", runtime.WithHTTPPathPattern("/v1/macaroon"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_BakeMacaroon_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_BakeMacaroon_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_ListMacaroonIDs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/ListMacaroonIDs", runtime.WithHTTPPathPattern("/v1/macaroon/ids"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_ListMacaroonIDs_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ListMacaroonIDs_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_Lightning_DeleteMacaroonID_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/DeleteMacaroonID", runtime.WithHTTPPathPattern("/v1/macaroon/{root_key_id}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_DeleteMacaroonID_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_DeleteMacaroonID_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_ListPermissions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/ListPermissions", runtime.WithHTTPPathPattern("/v1/macaroon/permissions"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_ListPermissions_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ListPermissions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_CheckMacaroonPermissions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/CheckMacaroonPermissions", runtime.WithHTTPPathPattern("/v1/macaroon/checkpermissions"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_CheckMacaroonPermissions_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_CheckMacaroonPermissions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_RegisterRPCMiddleware_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/RegisterRPCMiddleware", runtime.WithHTTPPathPattern("/v1/middleware"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_RegisterRPCMiddleware_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_RegisterRPCMiddleware_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Lightning_SendCustomMessage_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/SendCustomMessage", runtime.WithHTTPPathPattern("/v1/custommessage"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_SendCustomMessage_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_SendCustomMessage_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_SubscribeCustomMessages_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/SubscribeCustomMessages", runtime.WithHTTPPathPattern("/v1/custommessage/subscribe"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_SubscribeCustomMessages_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_SubscribeCustomMessages_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_ListAliases_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/ListAliases", runtime.WithHTTPPathPattern("/v1/aliases/list"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_ListAliases_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_ListAliases_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_LookupHtlcResolution_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.Lightning/LookupHtlcResolution", runtime.WithHTTPPathPattern("/v1/htlc-resolution/{chan_id}/{htlc_index}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_LookupHtlcResolution_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_LookupHtlcResolution_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_Lightning_WalletBalance_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "balance", "blockchain"}, ""))
pattern_Lightning_ChannelBalance_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "balance", "channels"}, ""))
pattern_Lightning_GetTransactions_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "transactions"}, ""))
pattern_Lightning_EstimateFee_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "transactions", "fee"}, ""))
pattern_Lightning_SendCoins_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "transactions"}, ""))
pattern_Lightning_ListUnspent_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "utxos"}, ""))
pattern_Lightning_SubscribeTransactions_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "transactions", "subscribe"}, ""))
pattern_Lightning_SendMany_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "transactions", "many"}, ""))
pattern_Lightning_NewAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "newaddress"}, ""))
pattern_Lightning_SignMessage_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "signmessage"}, ""))
pattern_Lightning_VerifyMessage_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "verifymessage"}, ""))
pattern_Lightning_ConnectPeer_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "peers"}, ""))
pattern_Lightning_DisconnectPeer_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "peers", "pub_key"}, ""))
pattern_Lightning_ListPeers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "peers"}, ""))
pattern_Lightning_SubscribePeerEvents_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "peers", "subscribe"}, ""))
pattern_Lightning_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getinfo"}, ""))
pattern_Lightning_GetDebugInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getdebuginfo"}, ""))
pattern_Lightning_GetRecoveryInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getrecoveryinfo"}, ""))
pattern_Lightning_PendingChannels_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "channels", "pending"}, ""))
pattern_Lightning_ListChannels_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "channels"}, ""))
pattern_Lightning_SubscribeChannelEvents_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "channels", "subscribe"}, ""))
pattern_Lightning_ClosedChannels_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "channels", "closed"}, ""))
pattern_Lightning_OpenChannelSync_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "channels"}, ""))
pattern_Lightning_OpenChannel_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "channels", "stream"}, ""))
pattern_Lightning_BatchOpenChannel_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "channels", "batch"}, ""))
pattern_Lightning_FundingStateStep_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "funding", "step"}, ""))
pattern_Lightning_ChannelAcceptor_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "channels", "acceptor"}, ""))
pattern_Lightning_CloseChannel_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 1, 0, 4, 1, 5, 3}, []string{"v1", "channels", "channel_point.funding_txid_str", "channel_point.output_index"}, ""))
pattern_Lightning_AbandonChannel_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 1, 0, 4, 1, 5, 4}, []string{"v1", "channels", "abandon", "channel_point.funding_txid_str", "channel_point.output_index"}, ""))
pattern_Lightning_SendPayment_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "channels", "transaction-stream"}, ""))
pattern_Lightning_SendPaymentSync_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "channels", "transactions"}, ""))
pattern_Lightning_SendToRouteSync_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "channels", "transactions", "route"}, ""))
pattern_Lightning_AddInvoice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "invoices"}, ""))
pattern_Lightning_ListInvoices_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "invoices"}, ""))
pattern_Lightning_LookupInvoice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "invoice", "r_hash_str"}, ""))
pattern_Lightning_SubscribeInvoices_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "invoices", "subscribe"}, ""))
pattern_Lightning_DecodePayReq_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "payreq", "pay_req"}, ""))
pattern_Lightning_ListPayments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "payments"}, ""))
pattern_Lightning_DeletePayment_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "payment"}, ""))
pattern_Lightning_DeleteAllPayments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "payments"}, ""))
pattern_Lightning_DescribeGraph_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "graph"}, ""))
pattern_Lightning_GetNodeMetrics_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "graph", "nodemetrics"}, ""))
pattern_Lightning_GetChanInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v1", "graph", "edge", "chan_id"}, ""))
pattern_Lightning_GetNodeInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v1", "graph", "node", "pub_key"}, ""))
pattern_Lightning_QueryRoutes_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 1, 0, 4, 1, 5, 4}, []string{"v1", "graph", "routes", "pub_key", "amt"}, ""))
pattern_Lightning_QueryRoutes_1 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 1, 0, 4, 1, 5, 4}, []string{"v1", "graph", "routes", "pub_key", "amt"}, ""))
pattern_Lightning_GetNetworkInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "graph", "info"}, ""))
pattern_Lightning_StopDaemon_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "stop"}, ""))
pattern_Lightning_SubscribeChannelGraph_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "graph", "subscribe"}, ""))
pattern_Lightning_DebugLevel_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "debuglevel"}, ""))
pattern_Lightning_FeeReport_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "fees"}, ""))
pattern_Lightning_UpdateChannelPolicy_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "chanpolicy"}, ""))
pattern_Lightning_ForwardingHistory_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "switch"}, ""))
pattern_Lightning_ExportChannelBackup_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 1, 0, 4, 1, 5, 4}, []string{"v1", "channels", "backup", "chan_point.funding_txid_str", "chan_point.output_index"}, ""))
pattern_Lightning_ExportAllChannelBackups_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "channels", "backup"}, ""))
pattern_Lightning_VerifyChanBackup_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "channels", "backup", "verify"}, ""))
pattern_Lightning_RestoreChannelBackups_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "channels", "backup", "restore"}, ""))
pattern_Lightning_SubscribeChannelBackups_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "channels", "backup", "subscribe"}, ""))
pattern_Lightning_BakeMacaroon_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "macaroon"}, ""))
pattern_Lightning_ListMacaroonIDs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "macaroon", "ids"}, ""))
pattern_Lightning_DeleteMacaroonID_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "macaroon", "root_key_id"}, ""))
pattern_Lightning_ListPermissions_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "macaroon", "permissions"}, ""))
pattern_Lightning_CheckMacaroonPermissions_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "macaroon", "checkpermissions"}, ""))
pattern_Lightning_RegisterRPCMiddleware_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "middleware"}, ""))
pattern_Lightning_SendCustomMessage_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "custommessage"}, ""))
pattern_Lightning_SubscribeCustomMessages_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "custommessage", "subscribe"}, ""))
pattern_Lightning_ListAliases_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "aliases", "list"}, ""))
pattern_Lightning_LookupHtlcResolution_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 1, 0, 4, 1, 5, 3}, []string{"v1", "htlc-resolution", "chan_id", "htlc_index"}, ""))
)
var (
forward_Lightning_WalletBalance_0 = runtime.ForwardResponseMessage
forward_Lightning_ChannelBalance_0 = runtime.ForwardResponseMessage
forward_Lightning_GetTransactions_0 = runtime.ForwardResponseMessage
forward_Lightning_EstimateFee_0 = runtime.ForwardResponseMessage
forward_Lightning_SendCoins_0 = runtime.ForwardResponseMessage
forward_Lightning_ListUnspent_0 = runtime.ForwardResponseMessage
forward_Lightning_SubscribeTransactions_0 = runtime.ForwardResponseStream
forward_Lightning_SendMany_0 = runtime.ForwardResponseMessage
forward_Lightning_NewAddress_0 = runtime.ForwardResponseMessage
forward_Lightning_SignMessage_0 = runtime.ForwardResponseMessage
forward_Lightning_VerifyMessage_0 = runtime.ForwardResponseMessage
forward_Lightning_ConnectPeer_0 = runtime.ForwardResponseMessage
forward_Lightning_DisconnectPeer_0 = runtime.ForwardResponseMessage
forward_Lightning_ListPeers_0 = runtime.ForwardResponseMessage
forward_Lightning_SubscribePeerEvents_0 = runtime.ForwardResponseStream
forward_Lightning_GetInfo_0 = runtime.ForwardResponseMessage
forward_Lightning_GetDebugInfo_0 = runtime.ForwardResponseMessage
forward_Lightning_GetRecoveryInfo_0 = runtime.ForwardResponseMessage
forward_Lightning_PendingChannels_0 = runtime.ForwardResponseMessage
forward_Lightning_ListChannels_0 = runtime.ForwardResponseMessage
forward_Lightning_SubscribeChannelEvents_0 = runtime.ForwardResponseStream
forward_Lightning_ClosedChannels_0 = runtime.ForwardResponseMessage
forward_Lightning_OpenChannelSync_0 = runtime.ForwardResponseMessage
forward_Lightning_OpenChannel_0 = runtime.ForwardResponseStream
forward_Lightning_BatchOpenChannel_0 = runtime.ForwardResponseMessage
forward_Lightning_FundingStateStep_0 = runtime.ForwardResponseMessage
forward_Lightning_ChannelAcceptor_0 = runtime.ForwardResponseStream
forward_Lightning_CloseChannel_0 = runtime.ForwardResponseStream
forward_Lightning_AbandonChannel_0 = runtime.ForwardResponseMessage
forward_Lightning_SendPayment_0 = runtime.ForwardResponseStream
forward_Lightning_SendPaymentSync_0 = runtime.ForwardResponseMessage
forward_Lightning_SendToRouteSync_0 = runtime.ForwardResponseMessage
forward_Lightning_AddInvoice_0 = runtime.ForwardResponseMessage
forward_Lightning_ListInvoices_0 = runtime.ForwardResponseMessage
forward_Lightning_LookupInvoice_0 = runtime.ForwardResponseMessage
forward_Lightning_SubscribeInvoices_0 = runtime.ForwardResponseStream
forward_Lightning_DecodePayReq_0 = runtime.ForwardResponseMessage
forward_Lightning_ListPayments_0 = runtime.ForwardResponseMessage
forward_Lightning_DeletePayment_0 = runtime.ForwardResponseMessage
forward_Lightning_DeleteAllPayments_0 = runtime.ForwardResponseMessage
forward_Lightning_DescribeGraph_0 = runtime.ForwardResponseMessage
forward_Lightning_GetNodeMetrics_0 = runtime.ForwardResponseMessage
forward_Lightning_GetChanInfo_0 = runtime.ForwardResponseMessage
forward_Lightning_GetNodeInfo_0 = runtime.ForwardResponseMessage
forward_Lightning_QueryRoutes_0 = runtime.ForwardResponseMessage
forward_Lightning_QueryRoutes_1 = runtime.ForwardResponseMessage
forward_Lightning_GetNetworkInfo_0 = runtime.ForwardResponseMessage
forward_Lightning_StopDaemon_0 = runtime.ForwardResponseMessage
forward_Lightning_SubscribeChannelGraph_0 = runtime.ForwardResponseStream
forward_Lightning_DebugLevel_0 = runtime.ForwardResponseMessage
forward_Lightning_FeeReport_0 = runtime.ForwardResponseMessage
forward_Lightning_UpdateChannelPolicy_0 = runtime.ForwardResponseMessage
forward_Lightning_ForwardingHistory_0 = runtime.ForwardResponseMessage
forward_Lightning_ExportChannelBackup_0 = runtime.ForwardResponseMessage
forward_Lightning_ExportAllChannelBackups_0 = runtime.ForwardResponseMessage
forward_Lightning_VerifyChanBackup_0 = runtime.ForwardResponseMessage
forward_Lightning_RestoreChannelBackups_0 = runtime.ForwardResponseMessage
forward_Lightning_SubscribeChannelBackups_0 = runtime.ForwardResponseStream
forward_Lightning_BakeMacaroon_0 = runtime.ForwardResponseMessage
forward_Lightning_ListMacaroonIDs_0 = runtime.ForwardResponseMessage
forward_Lightning_DeleteMacaroonID_0 = runtime.ForwardResponseMessage
forward_Lightning_ListPermissions_0 = runtime.ForwardResponseMessage
forward_Lightning_CheckMacaroonPermissions_0 = runtime.ForwardResponseMessage
forward_Lightning_RegisterRPCMiddleware_0 = runtime.ForwardResponseStream
forward_Lightning_SendCustomMessage_0 = runtime.ForwardResponseMessage
forward_Lightning_SubscribeCustomMessages_0 = runtime.ForwardResponseStream
forward_Lightning_ListAliases_0 = runtime.ForwardResponseMessage
forward_Lightning_LookupHtlcResolution_0 = runtime.ForwardResponseMessage
)
// Code generated by falafel 0.9.2. DO NOT EDIT.
// source: lightning.proto
package lnrpc
import (
"context"
gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protojson"
)
func RegisterLightningJSONCallbacks(registry map[string]func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error))) {
marshaler := &gateway.JSONPb{
MarshalOptions: protojson.MarshalOptions{
UseProtoNames: true,
EmitUnpopulated: true,
},
}
registry["lnrpc.Lightning.WalletBalance"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &WalletBalanceRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.WalletBalance(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.ChannelBalance"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ChannelBalanceRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.ChannelBalance(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.GetTransactions"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &GetTransactionsRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.GetTransactions(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.EstimateFee"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &EstimateFeeRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.EstimateFee(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.SendCoins"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SendCoinsRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.SendCoins(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.ListUnspent"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ListUnspentRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.ListUnspent(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.SubscribeTransactions"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &GetTransactionsRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
stream, err := client.SubscribeTransactions(ctx, req)
if err != nil {
callback("", err)
return
}
go func() {
for {
select {
case <-stream.Context().Done():
callback("", stream.Context().Err())
return
default:
}
resp, err := stream.Recv()
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}()
}
registry["lnrpc.Lightning.SendMany"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SendManyRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.SendMany(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.NewAddress"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &NewAddressRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.NewAddress(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.SignMessage"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SignMessageRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.SignMessage(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.VerifyMessage"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &VerifyMessageRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.VerifyMessage(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.ConnectPeer"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ConnectPeerRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.ConnectPeer(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.DisconnectPeer"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &DisconnectPeerRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.DisconnectPeer(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.ListPeers"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ListPeersRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.ListPeers(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.SubscribePeerEvents"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &PeerEventSubscription{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
stream, err := client.SubscribePeerEvents(ctx, req)
if err != nil {
callback("", err)
return
}
go func() {
for {
select {
case <-stream.Context().Done():
callback("", stream.Context().Err())
return
default:
}
resp, err := stream.Recv()
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}()
}
registry["lnrpc.Lightning.GetInfo"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &GetInfoRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.GetInfo(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.GetDebugInfo"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &GetDebugInfoRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.GetDebugInfo(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.GetRecoveryInfo"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &GetRecoveryInfoRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.GetRecoveryInfo(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.PendingChannels"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &PendingChannelsRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.PendingChannels(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.ListChannels"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ListChannelsRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.ListChannels(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.SubscribeChannelEvents"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ChannelEventSubscription{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
stream, err := client.SubscribeChannelEvents(ctx, req)
if err != nil {
callback("", err)
return
}
go func() {
for {
select {
case <-stream.Context().Done():
callback("", stream.Context().Err())
return
default:
}
resp, err := stream.Recv()
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}()
}
registry["lnrpc.Lightning.ClosedChannels"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ClosedChannelsRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.ClosedChannels(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.OpenChannelSync"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &OpenChannelRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.OpenChannelSync(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.OpenChannel"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &OpenChannelRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
stream, err := client.OpenChannel(ctx, req)
if err != nil {
callback("", err)
return
}
go func() {
for {
select {
case <-stream.Context().Done():
callback("", stream.Context().Err())
return
default:
}
resp, err := stream.Recv()
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}()
}
registry["lnrpc.Lightning.BatchOpenChannel"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &BatchOpenChannelRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.BatchOpenChannel(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.FundingStateStep"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &FundingTransitionMsg{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.FundingStateStep(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.CloseChannel"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &CloseChannelRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
stream, err := client.CloseChannel(ctx, req)
if err != nil {
callback("", err)
return
}
go func() {
for {
select {
case <-stream.Context().Done():
callback("", stream.Context().Err())
return
default:
}
resp, err := stream.Recv()
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}()
}
registry["lnrpc.Lightning.AbandonChannel"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &AbandonChannelRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.AbandonChannel(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.SendPaymentSync"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SendRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.SendPaymentSync(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.SendToRouteSync"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SendToRouteRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.SendToRouteSync(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.AddInvoice"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &Invoice{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.AddInvoice(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.ListInvoices"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ListInvoiceRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.ListInvoices(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.LookupInvoice"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &PaymentHash{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.LookupInvoice(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.SubscribeInvoices"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &InvoiceSubscription{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
stream, err := client.SubscribeInvoices(ctx, req)
if err != nil {
callback("", err)
return
}
go func() {
for {
select {
case <-stream.Context().Done():
callback("", stream.Context().Err())
return
default:
}
resp, err := stream.Recv()
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}()
}
registry["lnrpc.Lightning.DecodePayReq"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &PayReqString{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.DecodePayReq(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.ListPayments"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ListPaymentsRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.ListPayments(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.DeletePayment"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &DeletePaymentRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.DeletePayment(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.DeleteAllPayments"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &DeleteAllPaymentsRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.DeleteAllPayments(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.DescribeGraph"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ChannelGraphRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.DescribeGraph(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.GetNodeMetrics"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &NodeMetricsRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.GetNodeMetrics(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.GetChanInfo"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ChanInfoRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.GetChanInfo(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.GetNodeInfo"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &NodeInfoRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.GetNodeInfo(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.QueryRoutes"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &QueryRoutesRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.QueryRoutes(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.GetNetworkInfo"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &NetworkInfoRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.GetNetworkInfo(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.StopDaemon"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &StopRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.StopDaemon(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.SubscribeChannelGraph"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &GraphTopologySubscription{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
stream, err := client.SubscribeChannelGraph(ctx, req)
if err != nil {
callback("", err)
return
}
go func() {
for {
select {
case <-stream.Context().Done():
callback("", stream.Context().Err())
return
default:
}
resp, err := stream.Recv()
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}()
}
registry["lnrpc.Lightning.DebugLevel"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &DebugLevelRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.DebugLevel(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.FeeReport"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &FeeReportRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.FeeReport(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.UpdateChannelPolicy"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &PolicyUpdateRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.UpdateChannelPolicy(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.ForwardingHistory"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ForwardingHistoryRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.ForwardingHistory(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.ExportChannelBackup"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ExportChannelBackupRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.ExportChannelBackup(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.ExportAllChannelBackups"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ChanBackupExportRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.ExportAllChannelBackups(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.VerifyChanBackup"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ChanBackupSnapshot{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.VerifyChanBackup(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.RestoreChannelBackups"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &RestoreChanBackupRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.RestoreChannelBackups(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.SubscribeChannelBackups"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ChannelBackupSubscription{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
stream, err := client.SubscribeChannelBackups(ctx, req)
if err != nil {
callback("", err)
return
}
go func() {
for {
select {
case <-stream.Context().Done():
callback("", stream.Context().Err())
return
default:
}
resp, err := stream.Recv()
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}()
}
registry["lnrpc.Lightning.BakeMacaroon"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &BakeMacaroonRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.BakeMacaroon(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.ListMacaroonIDs"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ListMacaroonIDsRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.ListMacaroonIDs(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.DeleteMacaroonID"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &DeleteMacaroonIDRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.DeleteMacaroonID(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.ListPermissions"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ListPermissionsRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.ListPermissions(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.CheckMacaroonPermissions"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &CheckMacPermRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.CheckMacaroonPermissions(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.SendCustomMessage"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SendCustomMessageRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.SendCustomMessage(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.SubscribeCustomMessages"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SubscribeCustomMessagesRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
stream, err := client.SubscribeCustomMessages(ctx, req)
if err != nil {
callback("", err)
return
}
go func() {
for {
select {
case <-stream.Context().Done():
callback("", stream.Context().Err())
return
default:
}
resp, err := stream.Recv()
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}()
}
registry["lnrpc.Lightning.ListAliases"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ListAliasesRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.ListAliases(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.LookupHtlcResolution"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &LookupHtlcResolutionRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.LookupHtlcResolution(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package lnrpc
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// LightningClient is the client API for Lightning service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type LightningClient interface {
// lncli: `walletbalance`
// WalletBalance returns total unspent outputs(confirmed and unconfirmed), all
// confirmed unspent outputs and all unconfirmed unspent outputs under control
// of the wallet.
WalletBalance(ctx context.Context, in *WalletBalanceRequest, opts ...grpc.CallOption) (*WalletBalanceResponse, error)
// lncli: `channelbalance`
// ChannelBalance returns a report on the total funds across all open channels,
// categorized in local/remote, pending local/remote and unsettled local/remote
// balances.
ChannelBalance(ctx context.Context, in *ChannelBalanceRequest, opts ...grpc.CallOption) (*ChannelBalanceResponse, error)
// lncli: `listchaintxns`
// GetTransactions returns a list describing all the known transactions
// relevant to the wallet.
GetTransactions(ctx context.Context, in *GetTransactionsRequest, opts ...grpc.CallOption) (*TransactionDetails, error)
// lncli: `estimatefee`
// EstimateFee asks the chain backend to estimate the fee rate and total fees
// for a transaction that pays to multiple specified outputs.
//
// When using REST, the `AddrToAmount` map type can be set by appending
// `&AddrToAmount[<address>]=<amount_to_send>` to the URL. Unfortunately this
// map type doesn't appear in the REST API documentation because of a bug in
// the grpc-gateway library.
EstimateFee(ctx context.Context, in *EstimateFeeRequest, opts ...grpc.CallOption) (*EstimateFeeResponse, error)
// lncli: `sendcoins`
// SendCoins executes a request to send coins to a particular address. Unlike
// SendMany, this RPC call only allows creating a single output at a time. If
// neither target_conf, or sat_per_vbyte are set, then the internal wallet will
// consult its fee model to determine a fee for the default confirmation
// target.
SendCoins(ctx context.Context, in *SendCoinsRequest, opts ...grpc.CallOption) (*SendCoinsResponse, error)
// lncli: `listunspent`
// Deprecated, use walletrpc.ListUnspent instead.
//
// ListUnspent returns a list of all utxos spendable by the wallet with a
// number of confirmations between the specified minimum and maximum.
ListUnspent(ctx context.Context, in *ListUnspentRequest, opts ...grpc.CallOption) (*ListUnspentResponse, error)
// SubscribeTransactions creates a uni-directional stream from the server to
// the client in which any newly discovered transactions relevant to the
// wallet are sent over.
SubscribeTransactions(ctx context.Context, in *GetTransactionsRequest, opts ...grpc.CallOption) (Lightning_SubscribeTransactionsClient, error)
// lncli: `sendmany`
// SendMany handles a request for a transaction that creates multiple specified
// outputs in parallel. If neither target_conf, or sat_per_vbyte are set, then
// the internal wallet will consult its fee model to determine a fee for the
// default confirmation target.
SendMany(ctx context.Context, in *SendManyRequest, opts ...grpc.CallOption) (*SendManyResponse, error)
// lncli: `newaddress`
// NewAddress creates a new address under control of the local wallet.
NewAddress(ctx context.Context, in *NewAddressRequest, opts ...grpc.CallOption) (*NewAddressResponse, error)
// lncli: `signmessage`
// SignMessage signs a message with this node's private key. The returned
// signature string is `zbase32` encoded and pubkey recoverable, meaning that
// only the message digest and signature are needed for verification.
SignMessage(ctx context.Context, in *SignMessageRequest, opts ...grpc.CallOption) (*SignMessageResponse, error)
// lncli: `verifymessage`
// VerifyMessage verifies a signature over a message and recovers the signer's
// public key. The signature is only deemed valid if the recovered public key
// corresponds to a node key in the public Lightning network. The signature
// must be zbase32 encoded and signed by an active node in the resident node's
// channel database. In addition to returning the validity of the signature,
// VerifyMessage also returns the recovered pubkey from the signature.
VerifyMessage(ctx context.Context, in *VerifyMessageRequest, opts ...grpc.CallOption) (*VerifyMessageResponse, error)
// lncli: `connect`
// ConnectPeer attempts to establish a connection to a remote peer. This is at
// the networking level, and is used for communication between nodes. This is
// distinct from establishing a channel with a peer.
ConnectPeer(ctx context.Context, in *ConnectPeerRequest, opts ...grpc.CallOption) (*ConnectPeerResponse, error)
// lncli: `disconnect`
// DisconnectPeer attempts to disconnect one peer from another identified by a
// given pubKey. In the case that we currently have a pending or active channel
// with the target peer, then this action will be not be allowed.
DisconnectPeer(ctx context.Context, in *DisconnectPeerRequest, opts ...grpc.CallOption) (*DisconnectPeerResponse, error)
// lncli: `listpeers`
// ListPeers returns a verbose listing of all currently active peers.
ListPeers(ctx context.Context, in *ListPeersRequest, opts ...grpc.CallOption) (*ListPeersResponse, error)
// SubscribePeerEvents creates a uni-directional stream from the server to
// the client in which any events relevant to the state of peers are sent
// over. Events include peers going online and offline.
SubscribePeerEvents(ctx context.Context, in *PeerEventSubscription, opts ...grpc.CallOption) (Lightning_SubscribePeerEventsClient, error)
// lncli: `getinfo`
// GetInfo returns general information concerning the lightning node including
// it's identity pubkey, alias, the chains it is connected to, and information
// concerning the number of open+pending channels.
GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error)
// lncli: 'getdebuginfo'
// GetDebugInfo returns debug information concerning the state of the daemon
// and its subsystems. This includes the full configuration and the latest log
// entries from the log file.
GetDebugInfo(ctx context.Context, in *GetDebugInfoRequest, opts ...grpc.CallOption) (*GetDebugInfoResponse, error)
// * lncli: `getrecoveryinfo`
// GetRecoveryInfo returns information concerning the recovery mode including
// whether it's in a recovery mode, whether the recovery is finished, and the
// progress made so far.
GetRecoveryInfo(ctx context.Context, in *GetRecoveryInfoRequest, opts ...grpc.CallOption) (*GetRecoveryInfoResponse, error)
// lncli: `pendingchannels`
// PendingChannels returns a list of all the channels that are currently
// considered "pending". A channel is pending if it has finished the funding
// workflow and is waiting for confirmations for the funding txn, or is in the
// process of closure, either initiated cooperatively or non-cooperatively.
PendingChannels(ctx context.Context, in *PendingChannelsRequest, opts ...grpc.CallOption) (*PendingChannelsResponse, error)
// lncli: `listchannels`
// ListChannels returns a description of all the open channels that this node
// is a participant in.
ListChannels(ctx context.Context, in *ListChannelsRequest, opts ...grpc.CallOption) (*ListChannelsResponse, error)
// SubscribeChannelEvents creates a uni-directional stream from the server to
// the client in which any updates relevant to the state of the channels are
// sent over. Events include new active channels, inactive channels, and closed
// channels.
SubscribeChannelEvents(ctx context.Context, in *ChannelEventSubscription, opts ...grpc.CallOption) (Lightning_SubscribeChannelEventsClient, error)
// lncli: `closedchannels`
// ClosedChannels returns a description of all the closed channels that
// this node was a participant in.
ClosedChannels(ctx context.Context, in *ClosedChannelsRequest, opts ...grpc.CallOption) (*ClosedChannelsResponse, error)
// OpenChannelSync is a synchronous version of the OpenChannel RPC call. This
// call is meant to be consumed by clients to the REST proxy. As with all
// other sync calls, all byte slices are intended to be populated as hex
// encoded strings.
OpenChannelSync(ctx context.Context, in *OpenChannelRequest, opts ...grpc.CallOption) (*ChannelPoint, error)
// lncli: `openchannel`
// OpenChannel attempts to open a singly funded channel specified in the
// request to a remote peer. Users are able to specify a target number of
// blocks that the funding transaction should be confirmed in, or a manual fee
// rate to us for the funding transaction. If neither are specified, then a
// lax block confirmation target is used. Each OpenStatusUpdate will return
// the pending channel ID of the in-progress channel. Depending on the
// arguments specified in the OpenChannelRequest, this pending channel ID can
// then be used to manually progress the channel funding flow.
OpenChannel(ctx context.Context, in *OpenChannelRequest, opts ...grpc.CallOption) (Lightning_OpenChannelClient, error)
// lncli: `batchopenchannel`
// BatchOpenChannel attempts to open multiple single-funded channels in a
// single transaction in an atomic way. This means either all channel open
// requests succeed at once or all attempts are aborted if any of them fail.
// This is the safer variant of using PSBTs to manually fund a batch of
// channels through the OpenChannel RPC.
BatchOpenChannel(ctx context.Context, in *BatchOpenChannelRequest, opts ...grpc.CallOption) (*BatchOpenChannelResponse, error)
// FundingStateStep is an advanced funding related call that allows the caller
// to either execute some preparatory steps for a funding workflow, or
// manually progress a funding workflow. The primary way a funding flow is
// identified is via its pending channel ID. As an example, this method can be
// used to specify that we're expecting a funding flow for a particular
// pending channel ID, for which we need to use specific parameters.
// Alternatively, this can be used to interactively drive PSBT signing for
// funding for partially complete funding transactions.
FundingStateStep(ctx context.Context, in *FundingTransitionMsg, opts ...grpc.CallOption) (*FundingStateStepResp, error)
// ChannelAcceptor dispatches a bi-directional streaming RPC in which
// OpenChannel requests are sent to the client and the client responds with
// a boolean that tells LND whether or not to accept the channel. This allows
// node operators to specify their own criteria for accepting inbound channels
// through a single persistent connection.
ChannelAcceptor(ctx context.Context, opts ...grpc.CallOption) (Lightning_ChannelAcceptorClient, error)
// lncli: `closechannel`
// CloseChannel attempts to close an active channel identified by its channel
// outpoint (ChannelPoint). The actions of this method can additionally be
// augmented to attempt a force close after a timeout period in the case of an
// inactive peer. If a non-force close (cooperative closure) is requested,
// then the user can specify either a target number of blocks until the
// closure transaction is confirmed, or a manual fee rate. If neither are
// specified, then a default lax, block confirmation target is used.
CloseChannel(ctx context.Context, in *CloseChannelRequest, opts ...grpc.CallOption) (Lightning_CloseChannelClient, error)
// lncli: `abandonchannel`
// AbandonChannel removes all channel state from the database except for a
// close summary. This method can be used to get rid of permanently unusable
// channels due to bugs fixed in newer versions of lnd. This method can also be
// used to remove externally funded channels where the funding transaction was
// never broadcast. Only available for non-externally funded channels in dev
// build.
AbandonChannel(ctx context.Context, in *AbandonChannelRequest, opts ...grpc.CallOption) (*AbandonChannelResponse, error)
// Deprecated: Do not use.
// lncli: `sendpayment`
// Deprecated, use routerrpc.SendPaymentV2. SendPayment dispatches a
// bi-directional streaming RPC for sending payments through the Lightning
// Network. A single RPC invocation creates a persistent bi-directional
// stream allowing clients to rapidly send payments through the Lightning
// Network with a single persistent connection.
SendPayment(ctx context.Context, opts ...grpc.CallOption) (Lightning_SendPaymentClient, error)
// Deprecated: Do not use.
//
// Deprecated, use routerrpc.SendPaymentV2. SendPaymentSync is the synchronous
// non-streaming version of SendPayment. This RPC is intended to be consumed by
// clients of the REST proxy. Additionally, this RPC expects the destination's
// public key and the payment hash (if any) to be encoded as hex strings.
SendPaymentSync(ctx context.Context, in *SendRequest, opts ...grpc.CallOption) (*SendResponse, error)
// Deprecated: Do not use.
// lncli: `sendtoroute`
// Deprecated, use routerrpc.SendToRouteV2. SendToRoute is a bi-directional
// streaming RPC for sending payment through the Lightning Network. This
// method differs from SendPayment in that it allows users to specify a full
// route manually. This can be used for things like rebalancing, and atomic
// swaps.
SendToRoute(ctx context.Context, opts ...grpc.CallOption) (Lightning_SendToRouteClient, error)
// Deprecated: Do not use.
//
// Deprecated, use routerrpc.SendToRouteV2. SendToRouteSync is a synchronous
// version of SendToRoute. It Will block until the payment either fails or
// succeeds.
SendToRouteSync(ctx context.Context, in *SendToRouteRequest, opts ...grpc.CallOption) (*SendResponse, error)
// lncli: `addinvoice`
// AddInvoice attempts to add a new invoice to the invoice database. Any
// duplicated invoices are rejected, therefore all invoices *must* have a
// unique payment preimage.
AddInvoice(ctx context.Context, in *Invoice, opts ...grpc.CallOption) (*AddInvoiceResponse, error)
// lncli: `listinvoices`
// ListInvoices returns a list of all the invoices currently stored within the
// database. Any active debug invoices are ignored. It has full support for
// paginated responses, allowing users to query for specific invoices through
// their add_index. This can be done by using either the first_index_offset or
// last_index_offset fields included in the response as the index_offset of the
// next request. By default, the first 100 invoices created will be returned.
// Backwards pagination is also supported through the Reversed flag.
ListInvoices(ctx context.Context, in *ListInvoiceRequest, opts ...grpc.CallOption) (*ListInvoiceResponse, error)
// lncli: `lookupinvoice`
// LookupInvoice attempts to look up an invoice according to its payment hash.
// The passed payment hash *must* be exactly 32 bytes, if not, an error is
// returned.
LookupInvoice(ctx context.Context, in *PaymentHash, opts ...grpc.CallOption) (*Invoice, error)
// SubscribeInvoices returns a uni-directional stream (server -> client) for
// notifying the client of newly added/settled invoices. The caller can
// optionally specify the add_index and/or the settle_index. If the add_index
// is specified, then we'll first start by sending add invoice events for all
// invoices with an add_index greater than the specified value. If the
// settle_index is specified, then next, we'll send out all settle events for
// invoices with a settle_index greater than the specified value. One or both
// of these fields can be set. If no fields are set, then we'll only send out
// the latest add/settle events.
SubscribeInvoices(ctx context.Context, in *InvoiceSubscription, opts ...grpc.CallOption) (Lightning_SubscribeInvoicesClient, error)
// lncli: `decodepayreq`
// DecodePayReq takes an encoded payment request string and attempts to decode
// it, returning a full description of the conditions encoded within the
// payment request.
DecodePayReq(ctx context.Context, in *PayReqString, opts ...grpc.CallOption) (*PayReq, error)
// lncli: `listpayments`
// ListPayments returns a list of all outgoing payments.
ListPayments(ctx context.Context, in *ListPaymentsRequest, opts ...grpc.CallOption) (*ListPaymentsResponse, error)
// lncli: `deletepayments`
// DeletePayment deletes an outgoing payment from DB. Note that it will not
// attempt to delete an In-Flight payment, since that would be unsafe.
DeletePayment(ctx context.Context, in *DeletePaymentRequest, opts ...grpc.CallOption) (*DeletePaymentResponse, error)
// lncli: `deletepayments --all`
// DeleteAllPayments deletes all outgoing payments from DB. Note that it will
// not attempt to delete In-Flight payments, since that would be unsafe.
DeleteAllPayments(ctx context.Context, in *DeleteAllPaymentsRequest, opts ...grpc.CallOption) (*DeleteAllPaymentsResponse, error)
// lncli: `describegraph`
// DescribeGraph returns a description of the latest graph state from the
// point of view of the node. The graph information is partitioned into two
// components: all the nodes/vertexes, and all the edges that connect the
// vertexes themselves. As this is a directed graph, the edges also contain
// the node directional specific routing policy which includes: the time lock
// delta, fee information, etc.
DescribeGraph(ctx context.Context, in *ChannelGraphRequest, opts ...grpc.CallOption) (*ChannelGraph, error)
// lncli: `getnodemetrics`
// GetNodeMetrics returns node metrics calculated from the graph. Currently
// the only supported metric is betweenness centrality of individual nodes.
GetNodeMetrics(ctx context.Context, in *NodeMetricsRequest, opts ...grpc.CallOption) (*NodeMetricsResponse, error)
// lncli: `getchaninfo`
// GetChanInfo returns the latest authenticated network announcement for the
// given channel identified by its channel ID: an 8-byte integer which
// uniquely identifies the location of transaction's funding output within the
// blockchain.
GetChanInfo(ctx context.Context, in *ChanInfoRequest, opts ...grpc.CallOption) (*ChannelEdge, error)
// lncli: `getnodeinfo`
// GetNodeInfo returns the latest advertised, aggregated, and authenticated
// channel information for the specified node identified by its public key.
GetNodeInfo(ctx context.Context, in *NodeInfoRequest, opts ...grpc.CallOption) (*NodeInfo, error)
// lncli: `queryroutes`
// QueryRoutes attempts to query the daemon's Channel Router for a possible
// route to a target destination capable of carrying a specific amount of
// satoshis. The returned route contains the full details required to craft and
// send an HTLC, also including the necessary information that should be
// present within the Sphinx packet encapsulated within the HTLC.
//
// When using REST, the `dest_custom_records` map type can be set by appending
// `&dest_custom_records[<record_number>]=<record_data_base64_url_encoded>`
// to the URL. Unfortunately this map type doesn't appear in the REST API
// documentation because of a bug in the grpc-gateway library.
QueryRoutes(ctx context.Context, in *QueryRoutesRequest, opts ...grpc.CallOption) (*QueryRoutesResponse, error)
// lncli: `getnetworkinfo`
// GetNetworkInfo returns some basic stats about the known channel graph from
// the point of view of the node.
GetNetworkInfo(ctx context.Context, in *NetworkInfoRequest, opts ...grpc.CallOption) (*NetworkInfo, error)
// lncli: `stop`
// StopDaemon will send a shutdown request to the interrupt handler, triggering
// a graceful shutdown of the daemon.
StopDaemon(ctx context.Context, in *StopRequest, opts ...grpc.CallOption) (*StopResponse, error)
// SubscribeChannelGraph launches a streaming RPC that allows the caller to
// receive notifications upon any changes to the channel graph topology from
// the point of view of the responding node. Events notified include: new
// nodes coming online, nodes updating their authenticated attributes, new
// channels being advertised, updates in the routing policy for a directional
// channel edge, and when channels are closed on-chain.
SubscribeChannelGraph(ctx context.Context, in *GraphTopologySubscription, opts ...grpc.CallOption) (Lightning_SubscribeChannelGraphClient, error)
// lncli: `debuglevel`
// DebugLevel allows a caller to programmatically set the logging verbosity of
// lnd. The logging can be targeted according to a coarse daemon-wide logging
// level, or in a granular fashion to specify the logging for a target
// sub-system.
DebugLevel(ctx context.Context, in *DebugLevelRequest, opts ...grpc.CallOption) (*DebugLevelResponse, error)
// lncli: `feereport`
// FeeReport allows the caller to obtain a report detailing the current fee
// schedule enforced by the node globally for each channel.
FeeReport(ctx context.Context, in *FeeReportRequest, opts ...grpc.CallOption) (*FeeReportResponse, error)
// lncli: `updatechanpolicy`
// UpdateChannelPolicy allows the caller to update the fee schedule and
// channel policies for all channels globally, or a particular channel.
UpdateChannelPolicy(ctx context.Context, in *PolicyUpdateRequest, opts ...grpc.CallOption) (*PolicyUpdateResponse, error)
// lncli: `fwdinghistory`
// ForwardingHistory allows the caller to query the htlcswitch for a record of
// all HTLCs forwarded within the target time range, and integer offset
// within that time range, for a maximum number of events. If no maximum number
// of events is specified, up to 100 events will be returned. If no time-range
// is specified, then events will be returned in the order that they occured.
//
// A list of forwarding events are returned. The size of each forwarding event
// is 40 bytes, and the max message size able to be returned in gRPC is 4 MiB.
// As a result each message can only contain 50k entries. Each response has
// the index offset of the last entry. The index offset can be provided to the
// request to allow the caller to skip a series of records.
ForwardingHistory(ctx context.Context, in *ForwardingHistoryRequest, opts ...grpc.CallOption) (*ForwardingHistoryResponse, error)
// lncli: `exportchanbackup`
// ExportChannelBackup attempts to return an encrypted static channel backup
// for the target channel identified by it channel point. The backup is
// encrypted with a key generated from the aezeed seed of the user. The
// returned backup can either be restored using the RestoreChannelBackup
// method once lnd is running, or via the InitWallet and UnlockWallet methods
// from the WalletUnlocker service.
ExportChannelBackup(ctx context.Context, in *ExportChannelBackupRequest, opts ...grpc.CallOption) (*ChannelBackup, error)
// ExportAllChannelBackups returns static channel backups for all existing
// channels known to lnd. A set of regular singular static channel backups for
// each channel are returned. Additionally, a multi-channel backup is returned
// as well, which contains a single encrypted blob containing the backups of
// each channel.
ExportAllChannelBackups(ctx context.Context, in *ChanBackupExportRequest, opts ...grpc.CallOption) (*ChanBackupSnapshot, error)
// lncli: `verifychanbackup`
// VerifyChanBackup allows a caller to verify the integrity of a channel backup
// snapshot. This method will accept either a packed Single or a packed Multi.
// Specifying both will result in an error.
VerifyChanBackup(ctx context.Context, in *ChanBackupSnapshot, opts ...grpc.CallOption) (*VerifyChanBackupResponse, error)
// lncli: `restorechanbackup`
// RestoreChannelBackups accepts a set of singular channel backups, or a
// single encrypted multi-chan backup and attempts to recover any funds
// remaining within the channel. If we are able to unpack the backup, then the
// new channel will be shown under listchannels, as well as pending channels.
RestoreChannelBackups(ctx context.Context, in *RestoreChanBackupRequest, opts ...grpc.CallOption) (*RestoreBackupResponse, error)
// SubscribeChannelBackups allows a client to sub-subscribe to the most up to
// date information concerning the state of all channel backups. Each time a
// new channel is added, we return the new set of channels, along with a
// multi-chan backup containing the backup info for all channels. Each time a
// channel is closed, we send a new update, which contains new new chan back
// ups, but the updated set of encrypted multi-chan backups with the closed
// channel(s) removed.
SubscribeChannelBackups(ctx context.Context, in *ChannelBackupSubscription, opts ...grpc.CallOption) (Lightning_SubscribeChannelBackupsClient, error)
// lncli: `bakemacaroon`
// BakeMacaroon allows the creation of a new macaroon with custom read and
// write permissions. No first-party caveats are added since this can be done
// offline.
BakeMacaroon(ctx context.Context, in *BakeMacaroonRequest, opts ...grpc.CallOption) (*BakeMacaroonResponse, error)
// lncli: `listmacaroonids`
// ListMacaroonIDs returns all root key IDs that are in use.
ListMacaroonIDs(ctx context.Context, in *ListMacaroonIDsRequest, opts ...grpc.CallOption) (*ListMacaroonIDsResponse, error)
// lncli: `deletemacaroonid`
// DeleteMacaroonID deletes the specified macaroon ID and invalidates all
// macaroons derived from that ID.
DeleteMacaroonID(ctx context.Context, in *DeleteMacaroonIDRequest, opts ...grpc.CallOption) (*DeleteMacaroonIDResponse, error)
// lncli: `listpermissions`
// ListPermissions lists all RPC method URIs and their required macaroon
// permissions to access them.
ListPermissions(ctx context.Context, in *ListPermissionsRequest, opts ...grpc.CallOption) (*ListPermissionsResponse, error)
// CheckMacaroonPermissions checks whether a request follows the constraints
// imposed on the macaroon and that the macaroon is authorized to follow the
// provided permissions.
CheckMacaroonPermissions(ctx context.Context, in *CheckMacPermRequest, opts ...grpc.CallOption) (*CheckMacPermResponse, error)
// RegisterRPCMiddleware adds a new gRPC middleware to the interceptor chain. A
// gRPC middleware is software component external to lnd that aims to add
// additional business logic to lnd by observing/intercepting/validating
// incoming gRPC client requests and (if needed) replacing/overwriting outgoing
// messages before they're sent to the client. When registering the middleware
// must identify itself and indicate what custom macaroon caveats it wants to
// be responsible for. Only requests that contain a macaroon with that specific
// custom caveat are then sent to the middleware for inspection. The other
// option is to register for the read-only mode in which all requests/responses
// are forwarded for interception to the middleware but the middleware is not
// allowed to modify any responses. As a security measure, _no_ middleware can
// modify responses for requests made with _unencumbered_ macaroons!
RegisterRPCMiddleware(ctx context.Context, opts ...grpc.CallOption) (Lightning_RegisterRPCMiddlewareClient, error)
// lncli: `sendcustom`
// SendCustomMessage sends a custom peer message.
SendCustomMessage(ctx context.Context, in *SendCustomMessageRequest, opts ...grpc.CallOption) (*SendCustomMessageResponse, error)
// lncli: `subscribecustom`
// SubscribeCustomMessages subscribes to a stream of incoming custom peer
// messages.
//
// To include messages with type outside of the custom range (>= 32768) lnd
// needs to be compiled with the `dev` build tag, and the message type to
// override should be specified in lnd's experimental protocol configuration.
SubscribeCustomMessages(ctx context.Context, in *SubscribeCustomMessagesRequest, opts ...grpc.CallOption) (Lightning_SubscribeCustomMessagesClient, error)
// lncli: `listaliases`
// ListAliases returns the set of all aliases that have ever existed with
// their confirmed SCID (if it exists) and/or the base SCID (in the case of
// zero conf).
ListAliases(ctx context.Context, in *ListAliasesRequest, opts ...grpc.CallOption) (*ListAliasesResponse, error)
// LookupHtlcResolution retrieves a final htlc resolution from the database.
// If the htlc has no final resolution yet, a NotFound grpc status code is
// returned.
LookupHtlcResolution(ctx context.Context, in *LookupHtlcResolutionRequest, opts ...grpc.CallOption) (*LookupHtlcResolutionResponse, error)
}
type lightningClient struct {
cc grpc.ClientConnInterface
}
func NewLightningClient(cc grpc.ClientConnInterface) LightningClient {
return &lightningClient{cc}
}
func (c *lightningClient) WalletBalance(ctx context.Context, in *WalletBalanceRequest, opts ...grpc.CallOption) (*WalletBalanceResponse, error) {
out := new(WalletBalanceResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/WalletBalance", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) ChannelBalance(ctx context.Context, in *ChannelBalanceRequest, opts ...grpc.CallOption) (*ChannelBalanceResponse, error) {
out := new(ChannelBalanceResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/ChannelBalance", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) GetTransactions(ctx context.Context, in *GetTransactionsRequest, opts ...grpc.CallOption) (*TransactionDetails, error) {
out := new(TransactionDetails)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/GetTransactions", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) EstimateFee(ctx context.Context, in *EstimateFeeRequest, opts ...grpc.CallOption) (*EstimateFeeResponse, error) {
out := new(EstimateFeeResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/EstimateFee", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) SendCoins(ctx context.Context, in *SendCoinsRequest, opts ...grpc.CallOption) (*SendCoinsResponse, error) {
out := new(SendCoinsResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/SendCoins", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) ListUnspent(ctx context.Context, in *ListUnspentRequest, opts ...grpc.CallOption) (*ListUnspentResponse, error) {
out := new(ListUnspentResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/ListUnspent", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) SubscribeTransactions(ctx context.Context, in *GetTransactionsRequest, opts ...grpc.CallOption) (Lightning_SubscribeTransactionsClient, error) {
stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[0], "/lnrpc.Lightning/SubscribeTransactions", opts...)
if err != nil {
return nil, err
}
x := &lightningSubscribeTransactionsClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Lightning_SubscribeTransactionsClient interface {
Recv() (*Transaction, error)
grpc.ClientStream
}
type lightningSubscribeTransactionsClient struct {
grpc.ClientStream
}
func (x *lightningSubscribeTransactionsClient) Recv() (*Transaction, error) {
m := new(Transaction)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *lightningClient) SendMany(ctx context.Context, in *SendManyRequest, opts ...grpc.CallOption) (*SendManyResponse, error) {
out := new(SendManyResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/SendMany", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) NewAddress(ctx context.Context, in *NewAddressRequest, opts ...grpc.CallOption) (*NewAddressResponse, error) {
out := new(NewAddressResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/NewAddress", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) SignMessage(ctx context.Context, in *SignMessageRequest, opts ...grpc.CallOption) (*SignMessageResponse, error) {
out := new(SignMessageResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/SignMessage", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) VerifyMessage(ctx context.Context, in *VerifyMessageRequest, opts ...grpc.CallOption) (*VerifyMessageResponse, error) {
out := new(VerifyMessageResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/VerifyMessage", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) ConnectPeer(ctx context.Context, in *ConnectPeerRequest, opts ...grpc.CallOption) (*ConnectPeerResponse, error) {
out := new(ConnectPeerResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/ConnectPeer", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) DisconnectPeer(ctx context.Context, in *DisconnectPeerRequest, opts ...grpc.CallOption) (*DisconnectPeerResponse, error) {
out := new(DisconnectPeerResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/DisconnectPeer", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) ListPeers(ctx context.Context, in *ListPeersRequest, opts ...grpc.CallOption) (*ListPeersResponse, error) {
out := new(ListPeersResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/ListPeers", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) SubscribePeerEvents(ctx context.Context, in *PeerEventSubscription, opts ...grpc.CallOption) (Lightning_SubscribePeerEventsClient, error) {
stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[1], "/lnrpc.Lightning/SubscribePeerEvents", opts...)
if err != nil {
return nil, err
}
x := &lightningSubscribePeerEventsClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Lightning_SubscribePeerEventsClient interface {
Recv() (*PeerEvent, error)
grpc.ClientStream
}
type lightningSubscribePeerEventsClient struct {
grpc.ClientStream
}
func (x *lightningSubscribePeerEventsClient) Recv() (*PeerEvent, error) {
m := new(PeerEvent)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *lightningClient) GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error) {
out := new(GetInfoResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/GetInfo", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) GetDebugInfo(ctx context.Context, in *GetDebugInfoRequest, opts ...grpc.CallOption) (*GetDebugInfoResponse, error) {
out := new(GetDebugInfoResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/GetDebugInfo", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) GetRecoveryInfo(ctx context.Context, in *GetRecoveryInfoRequest, opts ...grpc.CallOption) (*GetRecoveryInfoResponse, error) {
out := new(GetRecoveryInfoResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/GetRecoveryInfo", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) PendingChannels(ctx context.Context, in *PendingChannelsRequest, opts ...grpc.CallOption) (*PendingChannelsResponse, error) {
out := new(PendingChannelsResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/PendingChannels", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) ListChannels(ctx context.Context, in *ListChannelsRequest, opts ...grpc.CallOption) (*ListChannelsResponse, error) {
out := new(ListChannelsResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/ListChannels", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) SubscribeChannelEvents(ctx context.Context, in *ChannelEventSubscription, opts ...grpc.CallOption) (Lightning_SubscribeChannelEventsClient, error) {
stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[2], "/lnrpc.Lightning/SubscribeChannelEvents", opts...)
if err != nil {
return nil, err
}
x := &lightningSubscribeChannelEventsClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Lightning_SubscribeChannelEventsClient interface {
Recv() (*ChannelEventUpdate, error)
grpc.ClientStream
}
type lightningSubscribeChannelEventsClient struct {
grpc.ClientStream
}
func (x *lightningSubscribeChannelEventsClient) Recv() (*ChannelEventUpdate, error) {
m := new(ChannelEventUpdate)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *lightningClient) ClosedChannels(ctx context.Context, in *ClosedChannelsRequest, opts ...grpc.CallOption) (*ClosedChannelsResponse, error) {
out := new(ClosedChannelsResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/ClosedChannels", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) OpenChannelSync(ctx context.Context, in *OpenChannelRequest, opts ...grpc.CallOption) (*ChannelPoint, error) {
out := new(ChannelPoint)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/OpenChannelSync", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) OpenChannel(ctx context.Context, in *OpenChannelRequest, opts ...grpc.CallOption) (Lightning_OpenChannelClient, error) {
stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[3], "/lnrpc.Lightning/OpenChannel", opts...)
if err != nil {
return nil, err
}
x := &lightningOpenChannelClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Lightning_OpenChannelClient interface {
Recv() (*OpenStatusUpdate, error)
grpc.ClientStream
}
type lightningOpenChannelClient struct {
grpc.ClientStream
}
func (x *lightningOpenChannelClient) Recv() (*OpenStatusUpdate, error) {
m := new(OpenStatusUpdate)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *lightningClient) BatchOpenChannel(ctx context.Context, in *BatchOpenChannelRequest, opts ...grpc.CallOption) (*BatchOpenChannelResponse, error) {
out := new(BatchOpenChannelResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/BatchOpenChannel", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) FundingStateStep(ctx context.Context, in *FundingTransitionMsg, opts ...grpc.CallOption) (*FundingStateStepResp, error) {
out := new(FundingStateStepResp)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/FundingStateStep", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) ChannelAcceptor(ctx context.Context, opts ...grpc.CallOption) (Lightning_ChannelAcceptorClient, error) {
stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[4], "/lnrpc.Lightning/ChannelAcceptor", opts...)
if err != nil {
return nil, err
}
x := &lightningChannelAcceptorClient{stream}
return x, nil
}
type Lightning_ChannelAcceptorClient interface {
Send(*ChannelAcceptResponse) error
Recv() (*ChannelAcceptRequest, error)
grpc.ClientStream
}
type lightningChannelAcceptorClient struct {
grpc.ClientStream
}
func (x *lightningChannelAcceptorClient) Send(m *ChannelAcceptResponse) error {
return x.ClientStream.SendMsg(m)
}
func (x *lightningChannelAcceptorClient) Recv() (*ChannelAcceptRequest, error) {
m := new(ChannelAcceptRequest)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *lightningClient) CloseChannel(ctx context.Context, in *CloseChannelRequest, opts ...grpc.CallOption) (Lightning_CloseChannelClient, error) {
stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[5], "/lnrpc.Lightning/CloseChannel", opts...)
if err != nil {
return nil, err
}
x := &lightningCloseChannelClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Lightning_CloseChannelClient interface {
Recv() (*CloseStatusUpdate, error)
grpc.ClientStream
}
type lightningCloseChannelClient struct {
grpc.ClientStream
}
func (x *lightningCloseChannelClient) Recv() (*CloseStatusUpdate, error) {
m := new(CloseStatusUpdate)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *lightningClient) AbandonChannel(ctx context.Context, in *AbandonChannelRequest, opts ...grpc.CallOption) (*AbandonChannelResponse, error) {
out := new(AbandonChannelResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/AbandonChannel", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Deprecated: Do not use.
func (c *lightningClient) SendPayment(ctx context.Context, opts ...grpc.CallOption) (Lightning_SendPaymentClient, error) {
stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[6], "/lnrpc.Lightning/SendPayment", opts...)
if err != nil {
return nil, err
}
x := &lightningSendPaymentClient{stream}
return x, nil
}
type Lightning_SendPaymentClient interface {
Send(*SendRequest) error
Recv() (*SendResponse, error)
grpc.ClientStream
}
type lightningSendPaymentClient struct {
grpc.ClientStream
}
func (x *lightningSendPaymentClient) Send(m *SendRequest) error {
return x.ClientStream.SendMsg(m)
}
func (x *lightningSendPaymentClient) Recv() (*SendResponse, error) {
m := new(SendResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// Deprecated: Do not use.
func (c *lightningClient) SendPaymentSync(ctx context.Context, in *SendRequest, opts ...grpc.CallOption) (*SendResponse, error) {
out := new(SendResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/SendPaymentSync", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Deprecated: Do not use.
func (c *lightningClient) SendToRoute(ctx context.Context, opts ...grpc.CallOption) (Lightning_SendToRouteClient, error) {
stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[7], "/lnrpc.Lightning/SendToRoute", opts...)
if err != nil {
return nil, err
}
x := &lightningSendToRouteClient{stream}
return x, nil
}
type Lightning_SendToRouteClient interface {
Send(*SendToRouteRequest) error
Recv() (*SendResponse, error)
grpc.ClientStream
}
type lightningSendToRouteClient struct {
grpc.ClientStream
}
func (x *lightningSendToRouteClient) Send(m *SendToRouteRequest) error {
return x.ClientStream.SendMsg(m)
}
func (x *lightningSendToRouteClient) Recv() (*SendResponse, error) {
m := new(SendResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// Deprecated: Do not use.
func (c *lightningClient) SendToRouteSync(ctx context.Context, in *SendToRouteRequest, opts ...grpc.CallOption) (*SendResponse, error) {
out := new(SendResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/SendToRouteSync", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) AddInvoice(ctx context.Context, in *Invoice, opts ...grpc.CallOption) (*AddInvoiceResponse, error) {
out := new(AddInvoiceResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/AddInvoice", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) ListInvoices(ctx context.Context, in *ListInvoiceRequest, opts ...grpc.CallOption) (*ListInvoiceResponse, error) {
out := new(ListInvoiceResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/ListInvoices", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) LookupInvoice(ctx context.Context, in *PaymentHash, opts ...grpc.CallOption) (*Invoice, error) {
out := new(Invoice)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/LookupInvoice", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) SubscribeInvoices(ctx context.Context, in *InvoiceSubscription, opts ...grpc.CallOption) (Lightning_SubscribeInvoicesClient, error) {
stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[8], "/lnrpc.Lightning/SubscribeInvoices", opts...)
if err != nil {
return nil, err
}
x := &lightningSubscribeInvoicesClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Lightning_SubscribeInvoicesClient interface {
Recv() (*Invoice, error)
grpc.ClientStream
}
type lightningSubscribeInvoicesClient struct {
grpc.ClientStream
}
func (x *lightningSubscribeInvoicesClient) Recv() (*Invoice, error) {
m := new(Invoice)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *lightningClient) DecodePayReq(ctx context.Context, in *PayReqString, opts ...grpc.CallOption) (*PayReq, error) {
out := new(PayReq)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/DecodePayReq", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) ListPayments(ctx context.Context, in *ListPaymentsRequest, opts ...grpc.CallOption) (*ListPaymentsResponse, error) {
out := new(ListPaymentsResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/ListPayments", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) DeletePayment(ctx context.Context, in *DeletePaymentRequest, opts ...grpc.CallOption) (*DeletePaymentResponse, error) {
out := new(DeletePaymentResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/DeletePayment", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) DeleteAllPayments(ctx context.Context, in *DeleteAllPaymentsRequest, opts ...grpc.CallOption) (*DeleteAllPaymentsResponse, error) {
out := new(DeleteAllPaymentsResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/DeleteAllPayments", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) DescribeGraph(ctx context.Context, in *ChannelGraphRequest, opts ...grpc.CallOption) (*ChannelGraph, error) {
out := new(ChannelGraph)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/DescribeGraph", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) GetNodeMetrics(ctx context.Context, in *NodeMetricsRequest, opts ...grpc.CallOption) (*NodeMetricsResponse, error) {
out := new(NodeMetricsResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/GetNodeMetrics", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) GetChanInfo(ctx context.Context, in *ChanInfoRequest, opts ...grpc.CallOption) (*ChannelEdge, error) {
out := new(ChannelEdge)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/GetChanInfo", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) GetNodeInfo(ctx context.Context, in *NodeInfoRequest, opts ...grpc.CallOption) (*NodeInfo, error) {
out := new(NodeInfo)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/GetNodeInfo", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) QueryRoutes(ctx context.Context, in *QueryRoutesRequest, opts ...grpc.CallOption) (*QueryRoutesResponse, error) {
out := new(QueryRoutesResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/QueryRoutes", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) GetNetworkInfo(ctx context.Context, in *NetworkInfoRequest, opts ...grpc.CallOption) (*NetworkInfo, error) {
out := new(NetworkInfo)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/GetNetworkInfo", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) StopDaemon(ctx context.Context, in *StopRequest, opts ...grpc.CallOption) (*StopResponse, error) {
out := new(StopResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/StopDaemon", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) SubscribeChannelGraph(ctx context.Context, in *GraphTopologySubscription, opts ...grpc.CallOption) (Lightning_SubscribeChannelGraphClient, error) {
stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[9], "/lnrpc.Lightning/SubscribeChannelGraph", opts...)
if err != nil {
return nil, err
}
x := &lightningSubscribeChannelGraphClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Lightning_SubscribeChannelGraphClient interface {
Recv() (*GraphTopologyUpdate, error)
grpc.ClientStream
}
type lightningSubscribeChannelGraphClient struct {
grpc.ClientStream
}
func (x *lightningSubscribeChannelGraphClient) Recv() (*GraphTopologyUpdate, error) {
m := new(GraphTopologyUpdate)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *lightningClient) DebugLevel(ctx context.Context, in *DebugLevelRequest, opts ...grpc.CallOption) (*DebugLevelResponse, error) {
out := new(DebugLevelResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/DebugLevel", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) FeeReport(ctx context.Context, in *FeeReportRequest, opts ...grpc.CallOption) (*FeeReportResponse, error) {
out := new(FeeReportResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/FeeReport", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) UpdateChannelPolicy(ctx context.Context, in *PolicyUpdateRequest, opts ...grpc.CallOption) (*PolicyUpdateResponse, error) {
out := new(PolicyUpdateResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/UpdateChannelPolicy", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) ForwardingHistory(ctx context.Context, in *ForwardingHistoryRequest, opts ...grpc.CallOption) (*ForwardingHistoryResponse, error) {
out := new(ForwardingHistoryResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/ForwardingHistory", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) ExportChannelBackup(ctx context.Context, in *ExportChannelBackupRequest, opts ...grpc.CallOption) (*ChannelBackup, error) {
out := new(ChannelBackup)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/ExportChannelBackup", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) ExportAllChannelBackups(ctx context.Context, in *ChanBackupExportRequest, opts ...grpc.CallOption) (*ChanBackupSnapshot, error) {
out := new(ChanBackupSnapshot)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/ExportAllChannelBackups", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) VerifyChanBackup(ctx context.Context, in *ChanBackupSnapshot, opts ...grpc.CallOption) (*VerifyChanBackupResponse, error) {
out := new(VerifyChanBackupResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/VerifyChanBackup", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) RestoreChannelBackups(ctx context.Context, in *RestoreChanBackupRequest, opts ...grpc.CallOption) (*RestoreBackupResponse, error) {
out := new(RestoreBackupResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/RestoreChannelBackups", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) SubscribeChannelBackups(ctx context.Context, in *ChannelBackupSubscription, opts ...grpc.CallOption) (Lightning_SubscribeChannelBackupsClient, error) {
stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[10], "/lnrpc.Lightning/SubscribeChannelBackups", opts...)
if err != nil {
return nil, err
}
x := &lightningSubscribeChannelBackupsClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Lightning_SubscribeChannelBackupsClient interface {
Recv() (*ChanBackupSnapshot, error)
grpc.ClientStream
}
type lightningSubscribeChannelBackupsClient struct {
grpc.ClientStream
}
func (x *lightningSubscribeChannelBackupsClient) Recv() (*ChanBackupSnapshot, error) {
m := new(ChanBackupSnapshot)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *lightningClient) BakeMacaroon(ctx context.Context, in *BakeMacaroonRequest, opts ...grpc.CallOption) (*BakeMacaroonResponse, error) {
out := new(BakeMacaroonResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/BakeMacaroon", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) ListMacaroonIDs(ctx context.Context, in *ListMacaroonIDsRequest, opts ...grpc.CallOption) (*ListMacaroonIDsResponse, error) {
out := new(ListMacaroonIDsResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/ListMacaroonIDs", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) DeleteMacaroonID(ctx context.Context, in *DeleteMacaroonIDRequest, opts ...grpc.CallOption) (*DeleteMacaroonIDResponse, error) {
out := new(DeleteMacaroonIDResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/DeleteMacaroonID", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) ListPermissions(ctx context.Context, in *ListPermissionsRequest, opts ...grpc.CallOption) (*ListPermissionsResponse, error) {
out := new(ListPermissionsResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/ListPermissions", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) CheckMacaroonPermissions(ctx context.Context, in *CheckMacPermRequest, opts ...grpc.CallOption) (*CheckMacPermResponse, error) {
out := new(CheckMacPermResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/CheckMacaroonPermissions", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) RegisterRPCMiddleware(ctx context.Context, opts ...grpc.CallOption) (Lightning_RegisterRPCMiddlewareClient, error) {
stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[11], "/lnrpc.Lightning/RegisterRPCMiddleware", opts...)
if err != nil {
return nil, err
}
x := &lightningRegisterRPCMiddlewareClient{stream}
return x, nil
}
type Lightning_RegisterRPCMiddlewareClient interface {
Send(*RPCMiddlewareResponse) error
Recv() (*RPCMiddlewareRequest, error)
grpc.ClientStream
}
type lightningRegisterRPCMiddlewareClient struct {
grpc.ClientStream
}
func (x *lightningRegisterRPCMiddlewareClient) Send(m *RPCMiddlewareResponse) error {
return x.ClientStream.SendMsg(m)
}
func (x *lightningRegisterRPCMiddlewareClient) Recv() (*RPCMiddlewareRequest, error) {
m := new(RPCMiddlewareRequest)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *lightningClient) SendCustomMessage(ctx context.Context, in *SendCustomMessageRequest, opts ...grpc.CallOption) (*SendCustomMessageResponse, error) {
out := new(SendCustomMessageResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/SendCustomMessage", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) SubscribeCustomMessages(ctx context.Context, in *SubscribeCustomMessagesRequest, opts ...grpc.CallOption) (Lightning_SubscribeCustomMessagesClient, error) {
stream, err := c.cc.NewStream(ctx, &Lightning_ServiceDesc.Streams[12], "/lnrpc.Lightning/SubscribeCustomMessages", opts...)
if err != nil {
return nil, err
}
x := &lightningSubscribeCustomMessagesClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Lightning_SubscribeCustomMessagesClient interface {
Recv() (*CustomMessage, error)
grpc.ClientStream
}
type lightningSubscribeCustomMessagesClient struct {
grpc.ClientStream
}
func (x *lightningSubscribeCustomMessagesClient) Recv() (*CustomMessage, error) {
m := new(CustomMessage)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *lightningClient) ListAliases(ctx context.Context, in *ListAliasesRequest, opts ...grpc.CallOption) (*ListAliasesResponse, error) {
out := new(ListAliasesResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/ListAliases", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *lightningClient) LookupHtlcResolution(ctx context.Context, in *LookupHtlcResolutionRequest, opts ...grpc.CallOption) (*LookupHtlcResolutionResponse, error) {
out := new(LookupHtlcResolutionResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/LookupHtlcResolution", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// LightningServer is the server API for Lightning service.
// All implementations must embed UnimplementedLightningServer
// for forward compatibility
type LightningServer interface {
// lncli: `walletbalance`
// WalletBalance returns total unspent outputs(confirmed and unconfirmed), all
// confirmed unspent outputs and all unconfirmed unspent outputs under control
// of the wallet.
WalletBalance(context.Context, *WalletBalanceRequest) (*WalletBalanceResponse, error)
// lncli: `channelbalance`
// ChannelBalance returns a report on the total funds across all open channels,
// categorized in local/remote, pending local/remote and unsettled local/remote
// balances.
ChannelBalance(context.Context, *ChannelBalanceRequest) (*ChannelBalanceResponse, error)
// lncli: `listchaintxns`
// GetTransactions returns a list describing all the known transactions
// relevant to the wallet.
GetTransactions(context.Context, *GetTransactionsRequest) (*TransactionDetails, error)
// lncli: `estimatefee`
// EstimateFee asks the chain backend to estimate the fee rate and total fees
// for a transaction that pays to multiple specified outputs.
//
// When using REST, the `AddrToAmount` map type can be set by appending
// `&AddrToAmount[<address>]=<amount_to_send>` to the URL. Unfortunately this
// map type doesn't appear in the REST API documentation because of a bug in
// the grpc-gateway library.
EstimateFee(context.Context, *EstimateFeeRequest) (*EstimateFeeResponse, error)
// lncli: `sendcoins`
// SendCoins executes a request to send coins to a particular address. Unlike
// SendMany, this RPC call only allows creating a single output at a time. If
// neither target_conf, or sat_per_vbyte are set, then the internal wallet will
// consult its fee model to determine a fee for the default confirmation
// target.
SendCoins(context.Context, *SendCoinsRequest) (*SendCoinsResponse, error)
// lncli: `listunspent`
// Deprecated, use walletrpc.ListUnspent instead.
//
// ListUnspent returns a list of all utxos spendable by the wallet with a
// number of confirmations between the specified minimum and maximum.
ListUnspent(context.Context, *ListUnspentRequest) (*ListUnspentResponse, error)
// SubscribeTransactions creates a uni-directional stream from the server to
// the client in which any newly discovered transactions relevant to the
// wallet are sent over.
SubscribeTransactions(*GetTransactionsRequest, Lightning_SubscribeTransactionsServer) error
// lncli: `sendmany`
// SendMany handles a request for a transaction that creates multiple specified
// outputs in parallel. If neither target_conf, or sat_per_vbyte are set, then
// the internal wallet will consult its fee model to determine a fee for the
// default confirmation target.
SendMany(context.Context, *SendManyRequest) (*SendManyResponse, error)
// lncli: `newaddress`
// NewAddress creates a new address under control of the local wallet.
NewAddress(context.Context, *NewAddressRequest) (*NewAddressResponse, error)
// lncli: `signmessage`
// SignMessage signs a message with this node's private key. The returned
// signature string is `zbase32` encoded and pubkey recoverable, meaning that
// only the message digest and signature are needed for verification.
SignMessage(context.Context, *SignMessageRequest) (*SignMessageResponse, error)
// lncli: `verifymessage`
// VerifyMessage verifies a signature over a message and recovers the signer's
// public key. The signature is only deemed valid if the recovered public key
// corresponds to a node key in the public Lightning network. The signature
// must be zbase32 encoded and signed by an active node in the resident node's
// channel database. In addition to returning the validity of the signature,
// VerifyMessage also returns the recovered pubkey from the signature.
VerifyMessage(context.Context, *VerifyMessageRequest) (*VerifyMessageResponse, error)
// lncli: `connect`
// ConnectPeer attempts to establish a connection to a remote peer. This is at
// the networking level, and is used for communication between nodes. This is
// distinct from establishing a channel with a peer.
ConnectPeer(context.Context, *ConnectPeerRequest) (*ConnectPeerResponse, error)
// lncli: `disconnect`
// DisconnectPeer attempts to disconnect one peer from another identified by a
// given pubKey. In the case that we currently have a pending or active channel
// with the target peer, then this action will be not be allowed.
DisconnectPeer(context.Context, *DisconnectPeerRequest) (*DisconnectPeerResponse, error)
// lncli: `listpeers`
// ListPeers returns a verbose listing of all currently active peers.
ListPeers(context.Context, *ListPeersRequest) (*ListPeersResponse, error)
// SubscribePeerEvents creates a uni-directional stream from the server to
// the client in which any events relevant to the state of peers are sent
// over. Events include peers going online and offline.
SubscribePeerEvents(*PeerEventSubscription, Lightning_SubscribePeerEventsServer) error
// lncli: `getinfo`
// GetInfo returns general information concerning the lightning node including
// it's identity pubkey, alias, the chains it is connected to, and information
// concerning the number of open+pending channels.
GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error)
// lncli: 'getdebuginfo'
// GetDebugInfo returns debug information concerning the state of the daemon
// and its subsystems. This includes the full configuration and the latest log
// entries from the log file.
GetDebugInfo(context.Context, *GetDebugInfoRequest) (*GetDebugInfoResponse, error)
// * lncli: `getrecoveryinfo`
// GetRecoveryInfo returns information concerning the recovery mode including
// whether it's in a recovery mode, whether the recovery is finished, and the
// progress made so far.
GetRecoveryInfo(context.Context, *GetRecoveryInfoRequest) (*GetRecoveryInfoResponse, error)
// lncli: `pendingchannels`
// PendingChannels returns a list of all the channels that are currently
// considered "pending". A channel is pending if it has finished the funding
// workflow and is waiting for confirmations for the funding txn, or is in the
// process of closure, either initiated cooperatively or non-cooperatively.
PendingChannels(context.Context, *PendingChannelsRequest) (*PendingChannelsResponse, error)
// lncli: `listchannels`
// ListChannels returns a description of all the open channels that this node
// is a participant in.
ListChannels(context.Context, *ListChannelsRequest) (*ListChannelsResponse, error)
// SubscribeChannelEvents creates a uni-directional stream from the server to
// the client in which any updates relevant to the state of the channels are
// sent over. Events include new active channels, inactive channels, and closed
// channels.
SubscribeChannelEvents(*ChannelEventSubscription, Lightning_SubscribeChannelEventsServer) error
// lncli: `closedchannels`
// ClosedChannels returns a description of all the closed channels that
// this node was a participant in.
ClosedChannels(context.Context, *ClosedChannelsRequest) (*ClosedChannelsResponse, error)
// OpenChannelSync is a synchronous version of the OpenChannel RPC call. This
// call is meant to be consumed by clients to the REST proxy. As with all
// other sync calls, all byte slices are intended to be populated as hex
// encoded strings.
OpenChannelSync(context.Context, *OpenChannelRequest) (*ChannelPoint, error)
// lncli: `openchannel`
// OpenChannel attempts to open a singly funded channel specified in the
// request to a remote peer. Users are able to specify a target number of
// blocks that the funding transaction should be confirmed in, or a manual fee
// rate to us for the funding transaction. If neither are specified, then a
// lax block confirmation target is used. Each OpenStatusUpdate will return
// the pending channel ID of the in-progress channel. Depending on the
// arguments specified in the OpenChannelRequest, this pending channel ID can
// then be used to manually progress the channel funding flow.
OpenChannel(*OpenChannelRequest, Lightning_OpenChannelServer) error
// lncli: `batchopenchannel`
// BatchOpenChannel attempts to open multiple single-funded channels in a
// single transaction in an atomic way. This means either all channel open
// requests succeed at once or all attempts are aborted if any of them fail.
// This is the safer variant of using PSBTs to manually fund a batch of
// channels through the OpenChannel RPC.
BatchOpenChannel(context.Context, *BatchOpenChannelRequest) (*BatchOpenChannelResponse, error)
// FundingStateStep is an advanced funding related call that allows the caller
// to either execute some preparatory steps for a funding workflow, or
// manually progress a funding workflow. The primary way a funding flow is
// identified is via its pending channel ID. As an example, this method can be
// used to specify that we're expecting a funding flow for a particular
// pending channel ID, for which we need to use specific parameters.
// Alternatively, this can be used to interactively drive PSBT signing for
// funding for partially complete funding transactions.
FundingStateStep(context.Context, *FundingTransitionMsg) (*FundingStateStepResp, error)
// ChannelAcceptor dispatches a bi-directional streaming RPC in which
// OpenChannel requests are sent to the client and the client responds with
// a boolean that tells LND whether or not to accept the channel. This allows
// node operators to specify their own criteria for accepting inbound channels
// through a single persistent connection.
ChannelAcceptor(Lightning_ChannelAcceptorServer) error
// lncli: `closechannel`
// CloseChannel attempts to close an active channel identified by its channel
// outpoint (ChannelPoint). The actions of this method can additionally be
// augmented to attempt a force close after a timeout period in the case of an
// inactive peer. If a non-force close (cooperative closure) is requested,
// then the user can specify either a target number of blocks until the
// closure transaction is confirmed, or a manual fee rate. If neither are
// specified, then a default lax, block confirmation target is used.
CloseChannel(*CloseChannelRequest, Lightning_CloseChannelServer) error
// lncli: `abandonchannel`
// AbandonChannel removes all channel state from the database except for a
// close summary. This method can be used to get rid of permanently unusable
// channels due to bugs fixed in newer versions of lnd. This method can also be
// used to remove externally funded channels where the funding transaction was
// never broadcast. Only available for non-externally funded channels in dev
// build.
AbandonChannel(context.Context, *AbandonChannelRequest) (*AbandonChannelResponse, error)
// Deprecated: Do not use.
// lncli: `sendpayment`
// Deprecated, use routerrpc.SendPaymentV2. SendPayment dispatches a
// bi-directional streaming RPC for sending payments through the Lightning
// Network. A single RPC invocation creates a persistent bi-directional
// stream allowing clients to rapidly send payments through the Lightning
// Network with a single persistent connection.
SendPayment(Lightning_SendPaymentServer) error
// Deprecated: Do not use.
//
// Deprecated, use routerrpc.SendPaymentV2. SendPaymentSync is the synchronous
// non-streaming version of SendPayment. This RPC is intended to be consumed by
// clients of the REST proxy. Additionally, this RPC expects the destination's
// public key and the payment hash (if any) to be encoded as hex strings.
SendPaymentSync(context.Context, *SendRequest) (*SendResponse, error)
// Deprecated: Do not use.
// lncli: `sendtoroute`
// Deprecated, use routerrpc.SendToRouteV2. SendToRoute is a bi-directional
// streaming RPC for sending payment through the Lightning Network. This
// method differs from SendPayment in that it allows users to specify a full
// route manually. This can be used for things like rebalancing, and atomic
// swaps.
SendToRoute(Lightning_SendToRouteServer) error
// Deprecated: Do not use.
//
// Deprecated, use routerrpc.SendToRouteV2. SendToRouteSync is a synchronous
// version of SendToRoute. It Will block until the payment either fails or
// succeeds.
SendToRouteSync(context.Context, *SendToRouteRequest) (*SendResponse, error)
// lncli: `addinvoice`
// AddInvoice attempts to add a new invoice to the invoice database. Any
// duplicated invoices are rejected, therefore all invoices *must* have a
// unique payment preimage.
AddInvoice(context.Context, *Invoice) (*AddInvoiceResponse, error)
// lncli: `listinvoices`
// ListInvoices returns a list of all the invoices currently stored within the
// database. Any active debug invoices are ignored. It has full support for
// paginated responses, allowing users to query for specific invoices through
// their add_index. This can be done by using either the first_index_offset or
// last_index_offset fields included in the response as the index_offset of the
// next request. By default, the first 100 invoices created will be returned.
// Backwards pagination is also supported through the Reversed flag.
ListInvoices(context.Context, *ListInvoiceRequest) (*ListInvoiceResponse, error)
// lncli: `lookupinvoice`
// LookupInvoice attempts to look up an invoice according to its payment hash.
// The passed payment hash *must* be exactly 32 bytes, if not, an error is
// returned.
LookupInvoice(context.Context, *PaymentHash) (*Invoice, error)
// SubscribeInvoices returns a uni-directional stream (server -> client) for
// notifying the client of newly added/settled invoices. The caller can
// optionally specify the add_index and/or the settle_index. If the add_index
// is specified, then we'll first start by sending add invoice events for all
// invoices with an add_index greater than the specified value. If the
// settle_index is specified, then next, we'll send out all settle events for
// invoices with a settle_index greater than the specified value. One or both
// of these fields can be set. If no fields are set, then we'll only send out
// the latest add/settle events.
SubscribeInvoices(*InvoiceSubscription, Lightning_SubscribeInvoicesServer) error
// lncli: `decodepayreq`
// DecodePayReq takes an encoded payment request string and attempts to decode
// it, returning a full description of the conditions encoded within the
// payment request.
DecodePayReq(context.Context, *PayReqString) (*PayReq, error)
// lncli: `listpayments`
// ListPayments returns a list of all outgoing payments.
ListPayments(context.Context, *ListPaymentsRequest) (*ListPaymentsResponse, error)
// lncli: `deletepayments`
// DeletePayment deletes an outgoing payment from DB. Note that it will not
// attempt to delete an In-Flight payment, since that would be unsafe.
DeletePayment(context.Context, *DeletePaymentRequest) (*DeletePaymentResponse, error)
// lncli: `deletepayments --all`
// DeleteAllPayments deletes all outgoing payments from DB. Note that it will
// not attempt to delete In-Flight payments, since that would be unsafe.
DeleteAllPayments(context.Context, *DeleteAllPaymentsRequest) (*DeleteAllPaymentsResponse, error)
// lncli: `describegraph`
// DescribeGraph returns a description of the latest graph state from the
// point of view of the node. The graph information is partitioned into two
// components: all the nodes/vertexes, and all the edges that connect the
// vertexes themselves. As this is a directed graph, the edges also contain
// the node directional specific routing policy which includes: the time lock
// delta, fee information, etc.
DescribeGraph(context.Context, *ChannelGraphRequest) (*ChannelGraph, error)
// lncli: `getnodemetrics`
// GetNodeMetrics returns node metrics calculated from the graph. Currently
// the only supported metric is betweenness centrality of individual nodes.
GetNodeMetrics(context.Context, *NodeMetricsRequest) (*NodeMetricsResponse, error)
// lncli: `getchaninfo`
// GetChanInfo returns the latest authenticated network announcement for the
// given channel identified by its channel ID: an 8-byte integer which
// uniquely identifies the location of transaction's funding output within the
// blockchain.
GetChanInfo(context.Context, *ChanInfoRequest) (*ChannelEdge, error)
// lncli: `getnodeinfo`
// GetNodeInfo returns the latest advertised, aggregated, and authenticated
// channel information for the specified node identified by its public key.
GetNodeInfo(context.Context, *NodeInfoRequest) (*NodeInfo, error)
// lncli: `queryroutes`
// QueryRoutes attempts to query the daemon's Channel Router for a possible
// route to a target destination capable of carrying a specific amount of
// satoshis. The returned route contains the full details required to craft and
// send an HTLC, also including the necessary information that should be
// present within the Sphinx packet encapsulated within the HTLC.
//
// When using REST, the `dest_custom_records` map type can be set by appending
// `&dest_custom_records[<record_number>]=<record_data_base64_url_encoded>`
// to the URL. Unfortunately this map type doesn't appear in the REST API
// documentation because of a bug in the grpc-gateway library.
QueryRoutes(context.Context, *QueryRoutesRequest) (*QueryRoutesResponse, error)
// lncli: `getnetworkinfo`
// GetNetworkInfo returns some basic stats about the known channel graph from
// the point of view of the node.
GetNetworkInfo(context.Context, *NetworkInfoRequest) (*NetworkInfo, error)
// lncli: `stop`
// StopDaemon will send a shutdown request to the interrupt handler, triggering
// a graceful shutdown of the daemon.
StopDaemon(context.Context, *StopRequest) (*StopResponse, error)
// SubscribeChannelGraph launches a streaming RPC that allows the caller to
// receive notifications upon any changes to the channel graph topology from
// the point of view of the responding node. Events notified include: new
// nodes coming online, nodes updating their authenticated attributes, new
// channels being advertised, updates in the routing policy for a directional
// channel edge, and when channels are closed on-chain.
SubscribeChannelGraph(*GraphTopologySubscription, Lightning_SubscribeChannelGraphServer) error
// lncli: `debuglevel`
// DebugLevel allows a caller to programmatically set the logging verbosity of
// lnd. The logging can be targeted according to a coarse daemon-wide logging
// level, or in a granular fashion to specify the logging for a target
// sub-system.
DebugLevel(context.Context, *DebugLevelRequest) (*DebugLevelResponse, error)
// lncli: `feereport`
// FeeReport allows the caller to obtain a report detailing the current fee
// schedule enforced by the node globally for each channel.
FeeReport(context.Context, *FeeReportRequest) (*FeeReportResponse, error)
// lncli: `updatechanpolicy`
// UpdateChannelPolicy allows the caller to update the fee schedule and
// channel policies for all channels globally, or a particular channel.
UpdateChannelPolicy(context.Context, *PolicyUpdateRequest) (*PolicyUpdateResponse, error)
// lncli: `fwdinghistory`
// ForwardingHistory allows the caller to query the htlcswitch for a record of
// all HTLCs forwarded within the target time range, and integer offset
// within that time range, for a maximum number of events. If no maximum number
// of events is specified, up to 100 events will be returned. If no time-range
// is specified, then events will be returned in the order that they occured.
//
// A list of forwarding events are returned. The size of each forwarding event
// is 40 bytes, and the max message size able to be returned in gRPC is 4 MiB.
// As a result each message can only contain 50k entries. Each response has
// the index offset of the last entry. The index offset can be provided to the
// request to allow the caller to skip a series of records.
ForwardingHistory(context.Context, *ForwardingHistoryRequest) (*ForwardingHistoryResponse, error)
// lncli: `exportchanbackup`
// ExportChannelBackup attempts to return an encrypted static channel backup
// for the target channel identified by it channel point. The backup is
// encrypted with a key generated from the aezeed seed of the user. The
// returned backup can either be restored using the RestoreChannelBackup
// method once lnd is running, or via the InitWallet and UnlockWallet methods
// from the WalletUnlocker service.
ExportChannelBackup(context.Context, *ExportChannelBackupRequest) (*ChannelBackup, error)
// ExportAllChannelBackups returns static channel backups for all existing
// channels known to lnd. A set of regular singular static channel backups for
// each channel are returned. Additionally, a multi-channel backup is returned
// as well, which contains a single encrypted blob containing the backups of
// each channel.
ExportAllChannelBackups(context.Context, *ChanBackupExportRequest) (*ChanBackupSnapshot, error)
// lncli: `verifychanbackup`
// VerifyChanBackup allows a caller to verify the integrity of a channel backup
// snapshot. This method will accept either a packed Single or a packed Multi.
// Specifying both will result in an error.
VerifyChanBackup(context.Context, *ChanBackupSnapshot) (*VerifyChanBackupResponse, error)
// lncli: `restorechanbackup`
// RestoreChannelBackups accepts a set of singular channel backups, or a
// single encrypted multi-chan backup and attempts to recover any funds
// remaining within the channel. If we are able to unpack the backup, then the
// new channel will be shown under listchannels, as well as pending channels.
RestoreChannelBackups(context.Context, *RestoreChanBackupRequest) (*RestoreBackupResponse, error)
// SubscribeChannelBackups allows a client to sub-subscribe to the most up to
// date information concerning the state of all channel backups. Each time a
// new channel is added, we return the new set of channels, along with a
// multi-chan backup containing the backup info for all channels. Each time a
// channel is closed, we send a new update, which contains new new chan back
// ups, but the updated set of encrypted multi-chan backups with the closed
// channel(s) removed.
SubscribeChannelBackups(*ChannelBackupSubscription, Lightning_SubscribeChannelBackupsServer) error
// lncli: `bakemacaroon`
// BakeMacaroon allows the creation of a new macaroon with custom read and
// write permissions. No first-party caveats are added since this can be done
// offline.
BakeMacaroon(context.Context, *BakeMacaroonRequest) (*BakeMacaroonResponse, error)
// lncli: `listmacaroonids`
// ListMacaroonIDs returns all root key IDs that are in use.
ListMacaroonIDs(context.Context, *ListMacaroonIDsRequest) (*ListMacaroonIDsResponse, error)
// lncli: `deletemacaroonid`
// DeleteMacaroonID deletes the specified macaroon ID and invalidates all
// macaroons derived from that ID.
DeleteMacaroonID(context.Context, *DeleteMacaroonIDRequest) (*DeleteMacaroonIDResponse, error)
// lncli: `listpermissions`
// ListPermissions lists all RPC method URIs and their required macaroon
// permissions to access them.
ListPermissions(context.Context, *ListPermissionsRequest) (*ListPermissionsResponse, error)
// CheckMacaroonPermissions checks whether a request follows the constraints
// imposed on the macaroon and that the macaroon is authorized to follow the
// provided permissions.
CheckMacaroonPermissions(context.Context, *CheckMacPermRequest) (*CheckMacPermResponse, error)
// RegisterRPCMiddleware adds a new gRPC middleware to the interceptor chain. A
// gRPC middleware is software component external to lnd that aims to add
// additional business logic to lnd by observing/intercepting/validating
// incoming gRPC client requests and (if needed) replacing/overwriting outgoing
// messages before they're sent to the client. When registering the middleware
// must identify itself and indicate what custom macaroon caveats it wants to
// be responsible for. Only requests that contain a macaroon with that specific
// custom caveat are then sent to the middleware for inspection. The other
// option is to register for the read-only mode in which all requests/responses
// are forwarded for interception to the middleware but the middleware is not
// allowed to modify any responses. As a security measure, _no_ middleware can
// modify responses for requests made with _unencumbered_ macaroons!
RegisterRPCMiddleware(Lightning_RegisterRPCMiddlewareServer) error
// lncli: `sendcustom`
// SendCustomMessage sends a custom peer message.
SendCustomMessage(context.Context, *SendCustomMessageRequest) (*SendCustomMessageResponse, error)
// lncli: `subscribecustom`
// SubscribeCustomMessages subscribes to a stream of incoming custom peer
// messages.
//
// To include messages with type outside of the custom range (>= 32768) lnd
// needs to be compiled with the `dev` build tag, and the message type to
// override should be specified in lnd's experimental protocol configuration.
SubscribeCustomMessages(*SubscribeCustomMessagesRequest, Lightning_SubscribeCustomMessagesServer) error
// lncli: `listaliases`
// ListAliases returns the set of all aliases that have ever existed with
// their confirmed SCID (if it exists) and/or the base SCID (in the case of
// zero conf).
ListAliases(context.Context, *ListAliasesRequest) (*ListAliasesResponse, error)
// LookupHtlcResolution retrieves a final htlc resolution from the database.
// If the htlc has no final resolution yet, a NotFound grpc status code is
// returned.
LookupHtlcResolution(context.Context, *LookupHtlcResolutionRequest) (*LookupHtlcResolutionResponse, error)
mustEmbedUnimplementedLightningServer()
}
// UnimplementedLightningServer must be embedded to have forward compatible implementations.
type UnimplementedLightningServer struct {
}
func (UnimplementedLightningServer) WalletBalance(context.Context, *WalletBalanceRequest) (*WalletBalanceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method WalletBalance not implemented")
}
func (UnimplementedLightningServer) ChannelBalance(context.Context, *ChannelBalanceRequest) (*ChannelBalanceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ChannelBalance not implemented")
}
func (UnimplementedLightningServer) GetTransactions(context.Context, *GetTransactionsRequest) (*TransactionDetails, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetTransactions not implemented")
}
func (UnimplementedLightningServer) EstimateFee(context.Context, *EstimateFeeRequest) (*EstimateFeeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method EstimateFee not implemented")
}
func (UnimplementedLightningServer) SendCoins(context.Context, *SendCoinsRequest) (*SendCoinsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SendCoins not implemented")
}
func (UnimplementedLightningServer) ListUnspent(context.Context, *ListUnspentRequest) (*ListUnspentResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListUnspent not implemented")
}
func (UnimplementedLightningServer) SubscribeTransactions(*GetTransactionsRequest, Lightning_SubscribeTransactionsServer) error {
return status.Errorf(codes.Unimplemented, "method SubscribeTransactions not implemented")
}
func (UnimplementedLightningServer) SendMany(context.Context, *SendManyRequest) (*SendManyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SendMany not implemented")
}
func (UnimplementedLightningServer) NewAddress(context.Context, *NewAddressRequest) (*NewAddressResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method NewAddress not implemented")
}
func (UnimplementedLightningServer) SignMessage(context.Context, *SignMessageRequest) (*SignMessageResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SignMessage not implemented")
}
func (UnimplementedLightningServer) VerifyMessage(context.Context, *VerifyMessageRequest) (*VerifyMessageResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method VerifyMessage not implemented")
}
func (UnimplementedLightningServer) ConnectPeer(context.Context, *ConnectPeerRequest) (*ConnectPeerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ConnectPeer not implemented")
}
func (UnimplementedLightningServer) DisconnectPeer(context.Context, *DisconnectPeerRequest) (*DisconnectPeerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DisconnectPeer not implemented")
}
func (UnimplementedLightningServer) ListPeers(context.Context, *ListPeersRequest) (*ListPeersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListPeers not implemented")
}
func (UnimplementedLightningServer) SubscribePeerEvents(*PeerEventSubscription, Lightning_SubscribePeerEventsServer) error {
return status.Errorf(codes.Unimplemented, "method SubscribePeerEvents not implemented")
}
func (UnimplementedLightningServer) GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetInfo not implemented")
}
func (UnimplementedLightningServer) GetDebugInfo(context.Context, *GetDebugInfoRequest) (*GetDebugInfoResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetDebugInfo not implemented")
}
func (UnimplementedLightningServer) GetRecoveryInfo(context.Context, *GetRecoveryInfoRequest) (*GetRecoveryInfoResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRecoveryInfo not implemented")
}
func (UnimplementedLightningServer) PendingChannels(context.Context, *PendingChannelsRequest) (*PendingChannelsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method PendingChannels not implemented")
}
func (UnimplementedLightningServer) ListChannels(context.Context, *ListChannelsRequest) (*ListChannelsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListChannels not implemented")
}
func (UnimplementedLightningServer) SubscribeChannelEvents(*ChannelEventSubscription, Lightning_SubscribeChannelEventsServer) error {
return status.Errorf(codes.Unimplemented, "method SubscribeChannelEvents not implemented")
}
func (UnimplementedLightningServer) ClosedChannels(context.Context, *ClosedChannelsRequest) (*ClosedChannelsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ClosedChannels not implemented")
}
func (UnimplementedLightningServer) OpenChannelSync(context.Context, *OpenChannelRequest) (*ChannelPoint, error) {
return nil, status.Errorf(codes.Unimplemented, "method OpenChannelSync not implemented")
}
func (UnimplementedLightningServer) OpenChannel(*OpenChannelRequest, Lightning_OpenChannelServer) error {
return status.Errorf(codes.Unimplemented, "method OpenChannel not implemented")
}
func (UnimplementedLightningServer) BatchOpenChannel(context.Context, *BatchOpenChannelRequest) (*BatchOpenChannelResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BatchOpenChannel not implemented")
}
func (UnimplementedLightningServer) FundingStateStep(context.Context, *FundingTransitionMsg) (*FundingStateStepResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method FundingStateStep not implemented")
}
func (UnimplementedLightningServer) ChannelAcceptor(Lightning_ChannelAcceptorServer) error {
return status.Errorf(codes.Unimplemented, "method ChannelAcceptor not implemented")
}
func (UnimplementedLightningServer) CloseChannel(*CloseChannelRequest, Lightning_CloseChannelServer) error {
return status.Errorf(codes.Unimplemented, "method CloseChannel not implemented")
}
func (UnimplementedLightningServer) AbandonChannel(context.Context, *AbandonChannelRequest) (*AbandonChannelResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AbandonChannel not implemented")
}
func (UnimplementedLightningServer) SendPayment(Lightning_SendPaymentServer) error {
return status.Errorf(codes.Unimplemented, "method SendPayment not implemented")
}
func (UnimplementedLightningServer) SendPaymentSync(context.Context, *SendRequest) (*SendResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SendPaymentSync not implemented")
}
func (UnimplementedLightningServer) SendToRoute(Lightning_SendToRouteServer) error {
return status.Errorf(codes.Unimplemented, "method SendToRoute not implemented")
}
func (UnimplementedLightningServer) SendToRouteSync(context.Context, *SendToRouteRequest) (*SendResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SendToRouteSync not implemented")
}
func (UnimplementedLightningServer) AddInvoice(context.Context, *Invoice) (*AddInvoiceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AddInvoice not implemented")
}
func (UnimplementedLightningServer) ListInvoices(context.Context, *ListInvoiceRequest) (*ListInvoiceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListInvoices not implemented")
}
func (UnimplementedLightningServer) LookupInvoice(context.Context, *PaymentHash) (*Invoice, error) {
return nil, status.Errorf(codes.Unimplemented, "method LookupInvoice not implemented")
}
func (UnimplementedLightningServer) SubscribeInvoices(*InvoiceSubscription, Lightning_SubscribeInvoicesServer) error {
return status.Errorf(codes.Unimplemented, "method SubscribeInvoices not implemented")
}
func (UnimplementedLightningServer) DecodePayReq(context.Context, *PayReqString) (*PayReq, error) {
return nil, status.Errorf(codes.Unimplemented, "method DecodePayReq not implemented")
}
func (UnimplementedLightningServer) ListPayments(context.Context, *ListPaymentsRequest) (*ListPaymentsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListPayments not implemented")
}
func (UnimplementedLightningServer) DeletePayment(context.Context, *DeletePaymentRequest) (*DeletePaymentResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeletePayment not implemented")
}
func (UnimplementedLightningServer) DeleteAllPayments(context.Context, *DeleteAllPaymentsRequest) (*DeleteAllPaymentsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteAllPayments not implemented")
}
func (UnimplementedLightningServer) DescribeGraph(context.Context, *ChannelGraphRequest) (*ChannelGraph, error) {
return nil, status.Errorf(codes.Unimplemented, "method DescribeGraph not implemented")
}
func (UnimplementedLightningServer) GetNodeMetrics(context.Context, *NodeMetricsRequest) (*NodeMetricsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetNodeMetrics not implemented")
}
func (UnimplementedLightningServer) GetChanInfo(context.Context, *ChanInfoRequest) (*ChannelEdge, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetChanInfo not implemented")
}
func (UnimplementedLightningServer) GetNodeInfo(context.Context, *NodeInfoRequest) (*NodeInfo, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetNodeInfo not implemented")
}
func (UnimplementedLightningServer) QueryRoutes(context.Context, *QueryRoutesRequest) (*QueryRoutesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method QueryRoutes not implemented")
}
func (UnimplementedLightningServer) GetNetworkInfo(context.Context, *NetworkInfoRequest) (*NetworkInfo, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetNetworkInfo not implemented")
}
func (UnimplementedLightningServer) StopDaemon(context.Context, *StopRequest) (*StopResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method StopDaemon not implemented")
}
func (UnimplementedLightningServer) SubscribeChannelGraph(*GraphTopologySubscription, Lightning_SubscribeChannelGraphServer) error {
return status.Errorf(codes.Unimplemented, "method SubscribeChannelGraph not implemented")
}
func (UnimplementedLightningServer) DebugLevel(context.Context, *DebugLevelRequest) (*DebugLevelResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DebugLevel not implemented")
}
func (UnimplementedLightningServer) FeeReport(context.Context, *FeeReportRequest) (*FeeReportResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method FeeReport not implemented")
}
func (UnimplementedLightningServer) UpdateChannelPolicy(context.Context, *PolicyUpdateRequest) (*PolicyUpdateResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateChannelPolicy not implemented")
}
func (UnimplementedLightningServer) ForwardingHistory(context.Context, *ForwardingHistoryRequest) (*ForwardingHistoryResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ForwardingHistory not implemented")
}
func (UnimplementedLightningServer) ExportChannelBackup(context.Context, *ExportChannelBackupRequest) (*ChannelBackup, error) {
return nil, status.Errorf(codes.Unimplemented, "method ExportChannelBackup not implemented")
}
func (UnimplementedLightningServer) ExportAllChannelBackups(context.Context, *ChanBackupExportRequest) (*ChanBackupSnapshot, error) {
return nil, status.Errorf(codes.Unimplemented, "method ExportAllChannelBackups not implemented")
}
func (UnimplementedLightningServer) VerifyChanBackup(context.Context, *ChanBackupSnapshot) (*VerifyChanBackupResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method VerifyChanBackup not implemented")
}
func (UnimplementedLightningServer) RestoreChannelBackups(context.Context, *RestoreChanBackupRequest) (*RestoreBackupResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RestoreChannelBackups not implemented")
}
func (UnimplementedLightningServer) SubscribeChannelBackups(*ChannelBackupSubscription, Lightning_SubscribeChannelBackupsServer) error {
return status.Errorf(codes.Unimplemented, "method SubscribeChannelBackups not implemented")
}
func (UnimplementedLightningServer) BakeMacaroon(context.Context, *BakeMacaroonRequest) (*BakeMacaroonResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BakeMacaroon not implemented")
}
func (UnimplementedLightningServer) ListMacaroonIDs(context.Context, *ListMacaroonIDsRequest) (*ListMacaroonIDsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListMacaroonIDs not implemented")
}
func (UnimplementedLightningServer) DeleteMacaroonID(context.Context, *DeleteMacaroonIDRequest) (*DeleteMacaroonIDResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteMacaroonID not implemented")
}
func (UnimplementedLightningServer) ListPermissions(context.Context, *ListPermissionsRequest) (*ListPermissionsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListPermissions not implemented")
}
func (UnimplementedLightningServer) CheckMacaroonPermissions(context.Context, *CheckMacPermRequest) (*CheckMacPermResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CheckMacaroonPermissions not implemented")
}
func (UnimplementedLightningServer) RegisterRPCMiddleware(Lightning_RegisterRPCMiddlewareServer) error {
return status.Errorf(codes.Unimplemented, "method RegisterRPCMiddleware not implemented")
}
func (UnimplementedLightningServer) SendCustomMessage(context.Context, *SendCustomMessageRequest) (*SendCustomMessageResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SendCustomMessage not implemented")
}
func (UnimplementedLightningServer) SubscribeCustomMessages(*SubscribeCustomMessagesRequest, Lightning_SubscribeCustomMessagesServer) error {
return status.Errorf(codes.Unimplemented, "method SubscribeCustomMessages not implemented")
}
func (UnimplementedLightningServer) ListAliases(context.Context, *ListAliasesRequest) (*ListAliasesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListAliases not implemented")
}
func (UnimplementedLightningServer) LookupHtlcResolution(context.Context, *LookupHtlcResolutionRequest) (*LookupHtlcResolutionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method LookupHtlcResolution not implemented")
}
func (UnimplementedLightningServer) mustEmbedUnimplementedLightningServer() {}
// UnsafeLightningServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to LightningServer will
// result in compilation errors.
type UnsafeLightningServer interface {
mustEmbedUnimplementedLightningServer()
}
func RegisterLightningServer(s grpc.ServiceRegistrar, srv LightningServer) {
s.RegisterService(&Lightning_ServiceDesc, srv)
}
func _Lightning_WalletBalance_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(WalletBalanceRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).WalletBalance(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/WalletBalance",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).WalletBalance(ctx, req.(*WalletBalanceRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_ChannelBalance_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ChannelBalanceRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).ChannelBalance(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/ChannelBalance",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).ChannelBalance(ctx, req.(*ChannelBalanceRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_GetTransactions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetTransactionsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).GetTransactions(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/GetTransactions",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).GetTransactions(ctx, req.(*GetTransactionsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_EstimateFee_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(EstimateFeeRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).EstimateFee(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/EstimateFee",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).EstimateFee(ctx, req.(*EstimateFeeRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_SendCoins_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SendCoinsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).SendCoins(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/SendCoins",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).SendCoins(ctx, req.(*SendCoinsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_ListUnspent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListUnspentRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).ListUnspent(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/ListUnspent",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).ListUnspent(ctx, req.(*ListUnspentRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_SubscribeTransactions_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(GetTransactionsRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(LightningServer).SubscribeTransactions(m, &lightningSubscribeTransactionsServer{stream})
}
type Lightning_SubscribeTransactionsServer interface {
Send(*Transaction) error
grpc.ServerStream
}
type lightningSubscribeTransactionsServer struct {
grpc.ServerStream
}
func (x *lightningSubscribeTransactionsServer) Send(m *Transaction) error {
return x.ServerStream.SendMsg(m)
}
func _Lightning_SendMany_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SendManyRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).SendMany(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/SendMany",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).SendMany(ctx, req.(*SendManyRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_NewAddress_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(NewAddressRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).NewAddress(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/NewAddress",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).NewAddress(ctx, req.(*NewAddressRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_SignMessage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SignMessageRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).SignMessage(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/SignMessage",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).SignMessage(ctx, req.(*SignMessageRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_VerifyMessage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(VerifyMessageRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).VerifyMessage(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/VerifyMessage",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).VerifyMessage(ctx, req.(*VerifyMessageRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_ConnectPeer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ConnectPeerRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).ConnectPeer(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/ConnectPeer",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).ConnectPeer(ctx, req.(*ConnectPeerRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_DisconnectPeer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DisconnectPeerRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).DisconnectPeer(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/DisconnectPeer",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).DisconnectPeer(ctx, req.(*DisconnectPeerRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_ListPeers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListPeersRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).ListPeers(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/ListPeers",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).ListPeers(ctx, req.(*ListPeersRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_SubscribePeerEvents_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(PeerEventSubscription)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(LightningServer).SubscribePeerEvents(m, &lightningSubscribePeerEventsServer{stream})
}
type Lightning_SubscribePeerEventsServer interface {
Send(*PeerEvent) error
grpc.ServerStream
}
type lightningSubscribePeerEventsServer struct {
grpc.ServerStream
}
func (x *lightningSubscribePeerEventsServer) Send(m *PeerEvent) error {
return x.ServerStream.SendMsg(m)
}
func _Lightning_GetInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetInfoRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).GetInfo(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/GetInfo",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).GetInfo(ctx, req.(*GetInfoRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_GetDebugInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetDebugInfoRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).GetDebugInfo(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/GetDebugInfo",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).GetDebugInfo(ctx, req.(*GetDebugInfoRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_GetRecoveryInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetRecoveryInfoRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).GetRecoveryInfo(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/GetRecoveryInfo",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).GetRecoveryInfo(ctx, req.(*GetRecoveryInfoRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_PendingChannels_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PendingChannelsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).PendingChannels(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/PendingChannels",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).PendingChannels(ctx, req.(*PendingChannelsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_ListChannels_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListChannelsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).ListChannels(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/ListChannels",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).ListChannels(ctx, req.(*ListChannelsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_SubscribeChannelEvents_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(ChannelEventSubscription)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(LightningServer).SubscribeChannelEvents(m, &lightningSubscribeChannelEventsServer{stream})
}
type Lightning_SubscribeChannelEventsServer interface {
Send(*ChannelEventUpdate) error
grpc.ServerStream
}
type lightningSubscribeChannelEventsServer struct {
grpc.ServerStream
}
func (x *lightningSubscribeChannelEventsServer) Send(m *ChannelEventUpdate) error {
return x.ServerStream.SendMsg(m)
}
func _Lightning_ClosedChannels_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ClosedChannelsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).ClosedChannels(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/ClosedChannels",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).ClosedChannels(ctx, req.(*ClosedChannelsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_OpenChannelSync_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(OpenChannelRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).OpenChannelSync(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/OpenChannelSync",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).OpenChannelSync(ctx, req.(*OpenChannelRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_OpenChannel_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(OpenChannelRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(LightningServer).OpenChannel(m, &lightningOpenChannelServer{stream})
}
type Lightning_OpenChannelServer interface {
Send(*OpenStatusUpdate) error
grpc.ServerStream
}
type lightningOpenChannelServer struct {
grpc.ServerStream
}
func (x *lightningOpenChannelServer) Send(m *OpenStatusUpdate) error {
return x.ServerStream.SendMsg(m)
}
func _Lightning_BatchOpenChannel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(BatchOpenChannelRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).BatchOpenChannel(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/BatchOpenChannel",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).BatchOpenChannel(ctx, req.(*BatchOpenChannelRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_FundingStateStep_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(FundingTransitionMsg)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).FundingStateStep(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/FundingStateStep",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).FundingStateStep(ctx, req.(*FundingTransitionMsg))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_ChannelAcceptor_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(LightningServer).ChannelAcceptor(&lightningChannelAcceptorServer{stream})
}
type Lightning_ChannelAcceptorServer interface {
Send(*ChannelAcceptRequest) error
Recv() (*ChannelAcceptResponse, error)
grpc.ServerStream
}
type lightningChannelAcceptorServer struct {
grpc.ServerStream
}
func (x *lightningChannelAcceptorServer) Send(m *ChannelAcceptRequest) error {
return x.ServerStream.SendMsg(m)
}
func (x *lightningChannelAcceptorServer) Recv() (*ChannelAcceptResponse, error) {
m := new(ChannelAcceptResponse)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func _Lightning_CloseChannel_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(CloseChannelRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(LightningServer).CloseChannel(m, &lightningCloseChannelServer{stream})
}
type Lightning_CloseChannelServer interface {
Send(*CloseStatusUpdate) error
grpc.ServerStream
}
type lightningCloseChannelServer struct {
grpc.ServerStream
}
func (x *lightningCloseChannelServer) Send(m *CloseStatusUpdate) error {
return x.ServerStream.SendMsg(m)
}
func _Lightning_AbandonChannel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AbandonChannelRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).AbandonChannel(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/AbandonChannel",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).AbandonChannel(ctx, req.(*AbandonChannelRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_SendPayment_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(LightningServer).SendPayment(&lightningSendPaymentServer{stream})
}
type Lightning_SendPaymentServer interface {
Send(*SendResponse) error
Recv() (*SendRequest, error)
grpc.ServerStream
}
type lightningSendPaymentServer struct {
grpc.ServerStream
}
func (x *lightningSendPaymentServer) Send(m *SendResponse) error {
return x.ServerStream.SendMsg(m)
}
func (x *lightningSendPaymentServer) Recv() (*SendRequest, error) {
m := new(SendRequest)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func _Lightning_SendPaymentSync_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SendRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).SendPaymentSync(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/SendPaymentSync",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).SendPaymentSync(ctx, req.(*SendRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_SendToRoute_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(LightningServer).SendToRoute(&lightningSendToRouteServer{stream})
}
type Lightning_SendToRouteServer interface {
Send(*SendResponse) error
Recv() (*SendToRouteRequest, error)
grpc.ServerStream
}
type lightningSendToRouteServer struct {
grpc.ServerStream
}
func (x *lightningSendToRouteServer) Send(m *SendResponse) error {
return x.ServerStream.SendMsg(m)
}
func (x *lightningSendToRouteServer) Recv() (*SendToRouteRequest, error) {
m := new(SendToRouteRequest)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func _Lightning_SendToRouteSync_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SendToRouteRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).SendToRouteSync(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/SendToRouteSync",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).SendToRouteSync(ctx, req.(*SendToRouteRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_AddInvoice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Invoice)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).AddInvoice(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/AddInvoice",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).AddInvoice(ctx, req.(*Invoice))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_ListInvoices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListInvoiceRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).ListInvoices(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/ListInvoices",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).ListInvoices(ctx, req.(*ListInvoiceRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_LookupInvoice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PaymentHash)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).LookupInvoice(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/LookupInvoice",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).LookupInvoice(ctx, req.(*PaymentHash))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_SubscribeInvoices_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(InvoiceSubscription)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(LightningServer).SubscribeInvoices(m, &lightningSubscribeInvoicesServer{stream})
}
type Lightning_SubscribeInvoicesServer interface {
Send(*Invoice) error
grpc.ServerStream
}
type lightningSubscribeInvoicesServer struct {
grpc.ServerStream
}
func (x *lightningSubscribeInvoicesServer) Send(m *Invoice) error {
return x.ServerStream.SendMsg(m)
}
func _Lightning_DecodePayReq_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PayReqString)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).DecodePayReq(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/DecodePayReq",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).DecodePayReq(ctx, req.(*PayReqString))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_ListPayments_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListPaymentsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).ListPayments(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/ListPayments",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).ListPayments(ctx, req.(*ListPaymentsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_DeletePayment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeletePaymentRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).DeletePayment(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/DeletePayment",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).DeletePayment(ctx, req.(*DeletePaymentRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_DeleteAllPayments_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeleteAllPaymentsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).DeleteAllPayments(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/DeleteAllPayments",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).DeleteAllPayments(ctx, req.(*DeleteAllPaymentsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_DescribeGraph_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ChannelGraphRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).DescribeGraph(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/DescribeGraph",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).DescribeGraph(ctx, req.(*ChannelGraphRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_GetNodeMetrics_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(NodeMetricsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).GetNodeMetrics(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/GetNodeMetrics",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).GetNodeMetrics(ctx, req.(*NodeMetricsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_GetChanInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ChanInfoRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).GetChanInfo(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/GetChanInfo",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).GetChanInfo(ctx, req.(*ChanInfoRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_GetNodeInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(NodeInfoRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).GetNodeInfo(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/GetNodeInfo",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).GetNodeInfo(ctx, req.(*NodeInfoRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_QueryRoutes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(QueryRoutesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).QueryRoutes(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/QueryRoutes",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).QueryRoutes(ctx, req.(*QueryRoutesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_GetNetworkInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(NetworkInfoRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).GetNetworkInfo(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/GetNetworkInfo",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).GetNetworkInfo(ctx, req.(*NetworkInfoRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_StopDaemon_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StopRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).StopDaemon(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/StopDaemon",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).StopDaemon(ctx, req.(*StopRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_SubscribeChannelGraph_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(GraphTopologySubscription)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(LightningServer).SubscribeChannelGraph(m, &lightningSubscribeChannelGraphServer{stream})
}
type Lightning_SubscribeChannelGraphServer interface {
Send(*GraphTopologyUpdate) error
grpc.ServerStream
}
type lightningSubscribeChannelGraphServer struct {
grpc.ServerStream
}
func (x *lightningSubscribeChannelGraphServer) Send(m *GraphTopologyUpdate) error {
return x.ServerStream.SendMsg(m)
}
func _Lightning_DebugLevel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DebugLevelRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).DebugLevel(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/DebugLevel",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).DebugLevel(ctx, req.(*DebugLevelRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_FeeReport_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(FeeReportRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).FeeReport(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/FeeReport",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).FeeReport(ctx, req.(*FeeReportRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_UpdateChannelPolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PolicyUpdateRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).UpdateChannelPolicy(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/UpdateChannelPolicy",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).UpdateChannelPolicy(ctx, req.(*PolicyUpdateRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_ForwardingHistory_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ForwardingHistoryRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).ForwardingHistory(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/ForwardingHistory",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).ForwardingHistory(ctx, req.(*ForwardingHistoryRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_ExportChannelBackup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ExportChannelBackupRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).ExportChannelBackup(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/ExportChannelBackup",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).ExportChannelBackup(ctx, req.(*ExportChannelBackupRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_ExportAllChannelBackups_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ChanBackupExportRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).ExportAllChannelBackups(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/ExportAllChannelBackups",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).ExportAllChannelBackups(ctx, req.(*ChanBackupExportRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_VerifyChanBackup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ChanBackupSnapshot)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).VerifyChanBackup(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/VerifyChanBackup",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).VerifyChanBackup(ctx, req.(*ChanBackupSnapshot))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_RestoreChannelBackups_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RestoreChanBackupRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).RestoreChannelBackups(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/RestoreChannelBackups",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).RestoreChannelBackups(ctx, req.(*RestoreChanBackupRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_SubscribeChannelBackups_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(ChannelBackupSubscription)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(LightningServer).SubscribeChannelBackups(m, &lightningSubscribeChannelBackupsServer{stream})
}
type Lightning_SubscribeChannelBackupsServer interface {
Send(*ChanBackupSnapshot) error
grpc.ServerStream
}
type lightningSubscribeChannelBackupsServer struct {
grpc.ServerStream
}
func (x *lightningSubscribeChannelBackupsServer) Send(m *ChanBackupSnapshot) error {
return x.ServerStream.SendMsg(m)
}
func _Lightning_BakeMacaroon_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(BakeMacaroonRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).BakeMacaroon(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/BakeMacaroon",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).BakeMacaroon(ctx, req.(*BakeMacaroonRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_ListMacaroonIDs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListMacaroonIDsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).ListMacaroonIDs(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/ListMacaroonIDs",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).ListMacaroonIDs(ctx, req.(*ListMacaroonIDsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_DeleteMacaroonID_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeleteMacaroonIDRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).DeleteMacaroonID(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/DeleteMacaroonID",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).DeleteMacaroonID(ctx, req.(*DeleteMacaroonIDRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_ListPermissions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListPermissionsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).ListPermissions(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/ListPermissions",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).ListPermissions(ctx, req.(*ListPermissionsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_CheckMacaroonPermissions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CheckMacPermRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).CheckMacaroonPermissions(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/CheckMacaroonPermissions",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).CheckMacaroonPermissions(ctx, req.(*CheckMacPermRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_RegisterRPCMiddleware_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(LightningServer).RegisterRPCMiddleware(&lightningRegisterRPCMiddlewareServer{stream})
}
type Lightning_RegisterRPCMiddlewareServer interface {
Send(*RPCMiddlewareRequest) error
Recv() (*RPCMiddlewareResponse, error)
grpc.ServerStream
}
type lightningRegisterRPCMiddlewareServer struct {
grpc.ServerStream
}
func (x *lightningRegisterRPCMiddlewareServer) Send(m *RPCMiddlewareRequest) error {
return x.ServerStream.SendMsg(m)
}
func (x *lightningRegisterRPCMiddlewareServer) Recv() (*RPCMiddlewareResponse, error) {
m := new(RPCMiddlewareResponse)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func _Lightning_SendCustomMessage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SendCustomMessageRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).SendCustomMessage(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/SendCustomMessage",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).SendCustomMessage(ctx, req.(*SendCustomMessageRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_SubscribeCustomMessages_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(SubscribeCustomMessagesRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(LightningServer).SubscribeCustomMessages(m, &lightningSubscribeCustomMessagesServer{stream})
}
type Lightning_SubscribeCustomMessagesServer interface {
Send(*CustomMessage) error
grpc.ServerStream
}
type lightningSubscribeCustomMessagesServer struct {
grpc.ServerStream
}
func (x *lightningSubscribeCustomMessagesServer) Send(m *CustomMessage) error {
return x.ServerStream.SendMsg(m)
}
func _Lightning_ListAliases_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListAliasesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).ListAliases(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/ListAliases",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).ListAliases(ctx, req.(*ListAliasesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Lightning_LookupHtlcResolution_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LookupHtlcResolutionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).LookupHtlcResolution(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/LookupHtlcResolution",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).LookupHtlcResolution(ctx, req.(*LookupHtlcResolutionRequest))
}
return interceptor(ctx, in, info, handler)
}
// Lightning_ServiceDesc is the grpc.ServiceDesc for Lightning service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Lightning_ServiceDesc = grpc.ServiceDesc{
ServiceName: "lnrpc.Lightning",
HandlerType: (*LightningServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "WalletBalance",
Handler: _Lightning_WalletBalance_Handler,
},
{
MethodName: "ChannelBalance",
Handler: _Lightning_ChannelBalance_Handler,
},
{
MethodName: "GetTransactions",
Handler: _Lightning_GetTransactions_Handler,
},
{
MethodName: "EstimateFee",
Handler: _Lightning_EstimateFee_Handler,
},
{
MethodName: "SendCoins",
Handler: _Lightning_SendCoins_Handler,
},
{
MethodName: "ListUnspent",
Handler: _Lightning_ListUnspent_Handler,
},
{
MethodName: "SendMany",
Handler: _Lightning_SendMany_Handler,
},
{
MethodName: "NewAddress",
Handler: _Lightning_NewAddress_Handler,
},
{
MethodName: "SignMessage",
Handler: _Lightning_SignMessage_Handler,
},
{
MethodName: "VerifyMessage",
Handler: _Lightning_VerifyMessage_Handler,
},
{
MethodName: "ConnectPeer",
Handler: _Lightning_ConnectPeer_Handler,
},
{
MethodName: "DisconnectPeer",
Handler: _Lightning_DisconnectPeer_Handler,
},
{
MethodName: "ListPeers",
Handler: _Lightning_ListPeers_Handler,
},
{
MethodName: "GetInfo",
Handler: _Lightning_GetInfo_Handler,
},
{
MethodName: "GetDebugInfo",
Handler: _Lightning_GetDebugInfo_Handler,
},
{
MethodName: "GetRecoveryInfo",
Handler: _Lightning_GetRecoveryInfo_Handler,
},
{
MethodName: "PendingChannels",
Handler: _Lightning_PendingChannels_Handler,
},
{
MethodName: "ListChannels",
Handler: _Lightning_ListChannels_Handler,
},
{
MethodName: "ClosedChannels",
Handler: _Lightning_ClosedChannels_Handler,
},
{
MethodName: "OpenChannelSync",
Handler: _Lightning_OpenChannelSync_Handler,
},
{
MethodName: "BatchOpenChannel",
Handler: _Lightning_BatchOpenChannel_Handler,
},
{
MethodName: "FundingStateStep",
Handler: _Lightning_FundingStateStep_Handler,
},
{
MethodName: "AbandonChannel",
Handler: _Lightning_AbandonChannel_Handler,
},
{
MethodName: "SendPaymentSync",
Handler: _Lightning_SendPaymentSync_Handler,
},
{
MethodName: "SendToRouteSync",
Handler: _Lightning_SendToRouteSync_Handler,
},
{
MethodName: "AddInvoice",
Handler: _Lightning_AddInvoice_Handler,
},
{
MethodName: "ListInvoices",
Handler: _Lightning_ListInvoices_Handler,
},
{
MethodName: "LookupInvoice",
Handler: _Lightning_LookupInvoice_Handler,
},
{
MethodName: "DecodePayReq",
Handler: _Lightning_DecodePayReq_Handler,
},
{
MethodName: "ListPayments",
Handler: _Lightning_ListPayments_Handler,
},
{
MethodName: "DeletePayment",
Handler: _Lightning_DeletePayment_Handler,
},
{
MethodName: "DeleteAllPayments",
Handler: _Lightning_DeleteAllPayments_Handler,
},
{
MethodName: "DescribeGraph",
Handler: _Lightning_DescribeGraph_Handler,
},
{
MethodName: "GetNodeMetrics",
Handler: _Lightning_GetNodeMetrics_Handler,
},
{
MethodName: "GetChanInfo",
Handler: _Lightning_GetChanInfo_Handler,
},
{
MethodName: "GetNodeInfo",
Handler: _Lightning_GetNodeInfo_Handler,
},
{
MethodName: "QueryRoutes",
Handler: _Lightning_QueryRoutes_Handler,
},
{
MethodName: "GetNetworkInfo",
Handler: _Lightning_GetNetworkInfo_Handler,
},
{
MethodName: "StopDaemon",
Handler: _Lightning_StopDaemon_Handler,
},
{
MethodName: "DebugLevel",
Handler: _Lightning_DebugLevel_Handler,
},
{
MethodName: "FeeReport",
Handler: _Lightning_FeeReport_Handler,
},
{
MethodName: "UpdateChannelPolicy",
Handler: _Lightning_UpdateChannelPolicy_Handler,
},
{
MethodName: "ForwardingHistory",
Handler: _Lightning_ForwardingHistory_Handler,
},
{
MethodName: "ExportChannelBackup",
Handler: _Lightning_ExportChannelBackup_Handler,
},
{
MethodName: "ExportAllChannelBackups",
Handler: _Lightning_ExportAllChannelBackups_Handler,
},
{
MethodName: "VerifyChanBackup",
Handler: _Lightning_VerifyChanBackup_Handler,
},
{
MethodName: "RestoreChannelBackups",
Handler: _Lightning_RestoreChannelBackups_Handler,
},
{
MethodName: "BakeMacaroon",
Handler: _Lightning_BakeMacaroon_Handler,
},
{
MethodName: "ListMacaroonIDs",
Handler: _Lightning_ListMacaroonIDs_Handler,
},
{
MethodName: "DeleteMacaroonID",
Handler: _Lightning_DeleteMacaroonID_Handler,
},
{
MethodName: "ListPermissions",
Handler: _Lightning_ListPermissions_Handler,
},
{
MethodName: "CheckMacaroonPermissions",
Handler: _Lightning_CheckMacaroonPermissions_Handler,
},
{
MethodName: "SendCustomMessage",
Handler: _Lightning_SendCustomMessage_Handler,
},
{
MethodName: "ListAliases",
Handler: _Lightning_ListAliases_Handler,
},
{
MethodName: "LookupHtlcResolution",
Handler: _Lightning_LookupHtlcResolution_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "SubscribeTransactions",
Handler: _Lightning_SubscribeTransactions_Handler,
ServerStreams: true,
},
{
StreamName: "SubscribePeerEvents",
Handler: _Lightning_SubscribePeerEvents_Handler,
ServerStreams: true,
},
{
StreamName: "SubscribeChannelEvents",
Handler: _Lightning_SubscribeChannelEvents_Handler,
ServerStreams: true,
},
{
StreamName: "OpenChannel",
Handler: _Lightning_OpenChannel_Handler,
ServerStreams: true,
},
{
StreamName: "ChannelAcceptor",
Handler: _Lightning_ChannelAcceptor_Handler,
ServerStreams: true,
ClientStreams: true,
},
{
StreamName: "CloseChannel",
Handler: _Lightning_CloseChannel_Handler,
ServerStreams: true,
},
{
StreamName: "SendPayment",
Handler: _Lightning_SendPayment_Handler,
ServerStreams: true,
ClientStreams: true,
},
{
StreamName: "SendToRoute",
Handler: _Lightning_SendToRoute_Handler,
ServerStreams: true,
ClientStreams: true,
},
{
StreamName: "SubscribeInvoices",
Handler: _Lightning_SubscribeInvoices_Handler,
ServerStreams: true,
},
{
StreamName: "SubscribeChannelGraph",
Handler: _Lightning_SubscribeChannelGraph_Handler,
ServerStreams: true,
},
{
StreamName: "SubscribeChannelBackups",
Handler: _Lightning_SubscribeChannelBackups_Handler,
ServerStreams: true,
},
{
StreamName: "RegisterRPCMiddleware",
Handler: _Lightning_RegisterRPCMiddleware_Handler,
ServerStreams: true,
ClientStreams: true,
},
{
StreamName: "SubscribeCustomMessages",
Handler: _Lightning_SubscribeCustomMessages_Handler,
ServerStreams: true,
},
},
Metadata: "lightning.proto",
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v3.21.12
// source: lnclipb/lncli.proto
package lnclipb
import (
verrpc "github.com/lightningnetwork/lnd/lnrpc/verrpc"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type VersionResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The version information for lncli.
Lncli *verrpc.Version `protobuf:"bytes,1,opt,name=lncli,proto3" json:"lncli,omitempty"`
// The version information for lnd.
Lnd *verrpc.Version `protobuf:"bytes,2,opt,name=lnd,proto3" json:"lnd,omitempty"`
}
func (x *VersionResponse) Reset() {
*x = VersionResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lnclipb_lncli_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *VersionResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*VersionResponse) ProtoMessage() {}
func (x *VersionResponse) ProtoReflect() protoreflect.Message {
mi := &file_lnclipb_lncli_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use VersionResponse.ProtoReflect.Descriptor instead.
func (*VersionResponse) Descriptor() ([]byte, []int) {
return file_lnclipb_lncli_proto_rawDescGZIP(), []int{0}
}
func (x *VersionResponse) GetLncli() *verrpc.Version {
if x != nil {
return x.Lncli
}
return nil
}
func (x *VersionResponse) GetLnd() *verrpc.Version {
if x != nil {
return x.Lnd
}
return nil
}
var File_lnclipb_lncli_proto protoreflect.FileDescriptor
var file_lnclipb_lncli_proto_rawDesc = []byte{
0x0a, 0x13, 0x6c, 0x6e, 0x63, 0x6c, 0x69, 0x70, 0x62, 0x2f, 0x6c, 0x6e, 0x63, 0x6c, 0x69, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x6c, 0x6e, 0x63, 0x6c, 0x69, 0x70, 0x62, 0x1a, 0x13,
0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2f, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x22, 0x5b, 0x0a, 0x0f, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x05, 0x6c, 0x6e, 0x63, 0x6c, 0x69, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x56,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x6c, 0x6e, 0x63, 0x6c, 0x69, 0x12, 0x21, 0x0a,
0x03, 0x6c, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x65, 0x72,
0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x6c, 0x6e, 0x64,
0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c,
0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f,
0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x6c, 0x6e, 0x63, 0x6c, 0x69, 0x70,
0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_lnclipb_lncli_proto_rawDescOnce sync.Once
file_lnclipb_lncli_proto_rawDescData = file_lnclipb_lncli_proto_rawDesc
)
func file_lnclipb_lncli_proto_rawDescGZIP() []byte {
file_lnclipb_lncli_proto_rawDescOnce.Do(func() {
file_lnclipb_lncli_proto_rawDescData = protoimpl.X.CompressGZIP(file_lnclipb_lncli_proto_rawDescData)
})
return file_lnclipb_lncli_proto_rawDescData
}
var file_lnclipb_lncli_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_lnclipb_lncli_proto_goTypes = []interface{}{
(*VersionResponse)(nil), // 0: lnclipb.VersionResponse
(*verrpc.Version)(nil), // 1: verrpc.Version
}
var file_lnclipb_lncli_proto_depIdxs = []int32{
1, // 0: lnclipb.VersionResponse.lncli:type_name -> verrpc.Version
1, // 1: lnclipb.VersionResponse.lnd:type_name -> verrpc.Version
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_lnclipb_lncli_proto_init() }
func file_lnclipb_lncli_proto_init() {
if File_lnclipb_lncli_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_lnclipb_lncli_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*VersionResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_lnclipb_lncli_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_lnclipb_lncli_proto_goTypes,
DependencyIndexes: file_lnclipb_lncli_proto_depIdxs,
MessageInfos: file_lnclipb_lncli_proto_msgTypes,
}.Build()
File_lnclipb_lncli_proto = out.File
file_lnclipb_lncli_proto_rawDesc = nil
file_lnclipb_lncli_proto_goTypes = nil
file_lnclipb_lncli_proto_depIdxs = nil
}
package lnrpc
import (
"encoding/hex"
"errors"
"fmt"
"maps"
"slices"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/wallet"
"github.com/lightningnetwork/lnd/aliasmgr"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// ErrSatMsatMutualExclusive is returned when both a sat and an msat
// amount are set.
ErrSatMsatMutualExclusive = errors.New(
"sat and msat arguments are mutually exclusive",
)
// ErrNegativeAmt is returned when a negative amount is specified.
ErrNegativeAmt = errors.New("amount cannot be negative")
)
// CalculateFeeLimit returns the fee limit in millisatoshis. If a percentage
// based fee limit has been requested, we'll factor in the ratio provided with
// the amount of the payment.
func CalculateFeeLimit(feeLimit *FeeLimit,
amount lnwire.MilliSatoshi) lnwire.MilliSatoshi {
switch feeLimit.GetLimit().(type) {
case *FeeLimit_Fixed:
return lnwire.NewMSatFromSatoshis(
btcutil.Amount(feeLimit.GetFixed()),
)
case *FeeLimit_FixedMsat:
return lnwire.MilliSatoshi(feeLimit.GetFixedMsat())
case *FeeLimit_Percent:
return amount * lnwire.MilliSatoshi(feeLimit.GetPercent()) / 100
default:
// Fall back to a sane default value that is based on the amount
// itself.
return lnwallet.DefaultRoutingFeeLimitForAmount(amount)
}
}
// UnmarshallAmt returns a strong msat type for a sat/msat pair of rpc fields.
func UnmarshallAmt(amtSat, amtMsat int64) (lnwire.MilliSatoshi, error) {
if amtSat != 0 && amtMsat != 0 {
return 0, ErrSatMsatMutualExclusive
}
if amtSat < 0 || amtMsat < 0 {
return 0, ErrNegativeAmt
}
if amtSat != 0 {
return lnwire.NewMSatFromSatoshis(btcutil.Amount(amtSat)), nil
}
return lnwire.MilliSatoshi(amtMsat), nil
}
// ParseConfs validates the minimum and maximum confirmation arguments of a
// ListUnspent request.
func ParseConfs(min, max int32) (int32, int32, error) {
switch {
// Ensure that the user didn't attempt to specify a negative number of
// confirmations, as that isn't possible.
case min < 0:
return 0, 0, fmt.Errorf("min confirmations must be >= 0")
// We'll also ensure that the min number of confs is strictly less than
// or equal to the max number of confs for sanity.
case min > max:
return 0, 0, fmt.Errorf("max confirmations must be >= min " +
"confirmations")
default:
return min, max, nil
}
}
// MarshalUtxos translates a []*lnwallet.Utxo into a []*lnrpc.Utxo.
func MarshalUtxos(utxos []*lnwallet.Utxo, activeNetParams *chaincfg.Params) (
[]*Utxo, error) {
res := make([]*Utxo, 0, len(utxos))
for _, utxo := range utxos {
// Translate lnwallet address type to the proper gRPC proto
// address type.
var addrType AddressType
switch utxo.AddressType {
case lnwallet.WitnessPubKey:
addrType = AddressType_WITNESS_PUBKEY_HASH
case lnwallet.NestedWitnessPubKey:
addrType = AddressType_NESTED_PUBKEY_HASH
case lnwallet.TaprootPubkey:
addrType = AddressType_TAPROOT_PUBKEY
case lnwallet.UnknownAddressType:
continue
default:
return nil, fmt.Errorf("invalid utxo address type")
}
// Now that we know we have a proper mapping to an address,
// we'll convert the regular outpoint to an lnrpc variant.
outpoint := MarshalOutPoint(&utxo.OutPoint)
utxoResp := Utxo{
AddressType: addrType,
AmountSat: int64(utxo.Value),
PkScript: hex.EncodeToString(utxo.PkScript),
Outpoint: outpoint,
Confirmations: utxo.Confirmations,
}
// Finally, we'll attempt to extract the raw address from the
// script so we can display a human friendly address to the end
// user.
_, outAddresses, _, err := txscript.ExtractPkScriptAddrs(
utxo.PkScript, activeNetParams,
)
if err != nil {
return nil, err
}
// If we can't properly locate a single address, then this was
// an error in our mapping, and we'll return an error back to
// the user.
if len(outAddresses) != 1 {
return nil, fmt.Errorf("an output was unexpectedly " +
"multisig")
}
utxoResp.Address = outAddresses[0].String()
res = append(res, &utxoResp)
}
return res, nil
}
// MarshallOutputType translates a txscript.ScriptClass into a
// lnrpc.OutputScriptType.
func MarshallOutputType(o txscript.ScriptClass) OutputScriptType {
// Translate txscript ScriptClass type to the proper gRPC proto
// output script type.
switch o {
case txscript.ScriptHashTy:
return OutputScriptType_SCRIPT_TYPE_SCRIPT_HASH
case txscript.WitnessV0PubKeyHashTy:
return OutputScriptType_SCRIPT_TYPE_WITNESS_V0_PUBKEY_HASH
case txscript.WitnessV0ScriptHashTy:
return OutputScriptType_SCRIPT_TYPE_WITNESS_V0_SCRIPT_HASH
case txscript.PubKeyTy:
return OutputScriptType_SCRIPT_TYPE_PUBKEY
case txscript.MultiSigTy:
return OutputScriptType_SCRIPT_TYPE_MULTISIG
case txscript.NullDataTy:
return OutputScriptType_SCRIPT_TYPE_NULLDATA
case txscript.NonStandardTy:
return OutputScriptType_SCRIPT_TYPE_NON_STANDARD
case txscript.WitnessUnknownTy:
return OutputScriptType_SCRIPT_TYPE_WITNESS_UNKNOWN
case txscript.WitnessV1TaprootTy:
return OutputScriptType_SCRIPT_TYPE_WITNESS_V1_TAPROOT
default:
return OutputScriptType_SCRIPT_TYPE_PUBKEY_HASH
}
}
// MarshalOutPoint converts a wire.OutPoint to its proto counterpart.
func MarshalOutPoint(op *wire.OutPoint) *OutPoint {
return &OutPoint{
TxidBytes: op.Hash[:],
TxidStr: op.Hash.String(),
OutputIndex: op.Index,
}
}
// UnmarshallCoinSelectionStrategy converts a lnrpc.CoinSelectionStrategy proto
// type to its wallet.CoinSelectionStrategy counterpart type, considering
// a global default strategy if necessary.
//
// The globalStrategy parameter specifies the default coin selection strategy
// to use if the strategy is set to STRATEGY_USE_GLOBAL_CONFIG. This allows
// flexibility in defining a default strategy at a global level.
func UnmarshallCoinSelectionStrategy(strategy CoinSelectionStrategy,
globalStrategy wallet.CoinSelectionStrategy) (
wallet.CoinSelectionStrategy, error) {
switch strategy {
case CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG:
return globalStrategy, nil
case CoinSelectionStrategy_STRATEGY_LARGEST:
return wallet.CoinSelectionLargest, nil
case CoinSelectionStrategy_STRATEGY_RANDOM:
return wallet.CoinSelectionRandom, nil
default:
return nil, fmt.Errorf("unknown coin selection strategy "+
"%v", strategy)
}
}
// MarshalAliasMap converts a ScidAliasMap to its proto counterpart. This is
// used in various RPCs that handle scid alias mappings.
func MarshalAliasMap(scidMap aliasmgr.ScidAliasMap) []*AliasMap {
return fn.Map(
slices.Collect(maps.Keys(scidMap)),
func(base lnwire.ShortChannelID) *AliasMap {
return &AliasMap{
BaseScid: base.ToUint64(),
Aliases: fn.Map(
scidMap[base],
func(a lnwire.ShortChannelID) uint64 {
return a.ToUint64()
},
),
}
},
)
}
package neutrinorpc
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// Subsystem defines the logging code for this subsystem.
const Subsystem = "NRPC"
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v3.21.12
// source: neutrinorpc/neutrino.proto
package neutrinorpc
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type StatusRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *StatusRequest) Reset() {
*x = StatusRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *StatusRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StatusRequest) ProtoMessage() {}
func (x *StatusRequest) ProtoReflect() protoreflect.Message {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StatusRequest.ProtoReflect.Descriptor instead.
func (*StatusRequest) Descriptor() ([]byte, []int) {
return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{0}
}
type StatusResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Indicates whether the neutrino backend is active or not.
Active bool `protobuf:"varint,1,opt,name=active,proto3" json:"active,omitempty"`
// Is fully synced.
Synced bool `protobuf:"varint,2,opt,name=synced,proto3" json:"synced,omitempty"`
// Best block height.
BlockHeight int32 `protobuf:"varint,3,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"`
// Best block hash.
BlockHash string `protobuf:"bytes,4,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"`
// Connected peers.
Peers []string `protobuf:"bytes,5,rep,name=peers,proto3" json:"peers,omitempty"`
}
func (x *StatusResponse) Reset() {
*x = StatusResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *StatusResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StatusResponse) ProtoMessage() {}
func (x *StatusResponse) ProtoReflect() protoreflect.Message {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StatusResponse.ProtoReflect.Descriptor instead.
func (*StatusResponse) Descriptor() ([]byte, []int) {
return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{1}
}
func (x *StatusResponse) GetActive() bool {
if x != nil {
return x.Active
}
return false
}
func (x *StatusResponse) GetSynced() bool {
if x != nil {
return x.Synced
}
return false
}
func (x *StatusResponse) GetBlockHeight() int32 {
if x != nil {
return x.BlockHeight
}
return 0
}
func (x *StatusResponse) GetBlockHash() string {
if x != nil {
return x.BlockHash
}
return ""
}
func (x *StatusResponse) GetPeers() []string {
if x != nil {
return x.Peers
}
return nil
}
type AddPeerRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Peer to add.
PeerAddrs string `protobuf:"bytes,1,opt,name=peer_addrs,json=peerAddrs,proto3" json:"peer_addrs,omitempty"`
}
func (x *AddPeerRequest) Reset() {
*x = AddPeerRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AddPeerRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AddPeerRequest) ProtoMessage() {}
func (x *AddPeerRequest) ProtoReflect() protoreflect.Message {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AddPeerRequest.ProtoReflect.Descriptor instead.
func (*AddPeerRequest) Descriptor() ([]byte, []int) {
return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{2}
}
func (x *AddPeerRequest) GetPeerAddrs() string {
if x != nil {
return x.PeerAddrs
}
return ""
}
type AddPeerResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *AddPeerResponse) Reset() {
*x = AddPeerResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AddPeerResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AddPeerResponse) ProtoMessage() {}
func (x *AddPeerResponse) ProtoReflect() protoreflect.Message {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AddPeerResponse.ProtoReflect.Descriptor instead.
func (*AddPeerResponse) Descriptor() ([]byte, []int) {
return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{3}
}
type DisconnectPeerRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Peer to disconnect.
PeerAddrs string `protobuf:"bytes,1,opt,name=peer_addrs,json=peerAddrs,proto3" json:"peer_addrs,omitempty"`
}
func (x *DisconnectPeerRequest) Reset() {
*x = DisconnectPeerRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DisconnectPeerRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DisconnectPeerRequest) ProtoMessage() {}
func (x *DisconnectPeerRequest) ProtoReflect() protoreflect.Message {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DisconnectPeerRequest.ProtoReflect.Descriptor instead.
func (*DisconnectPeerRequest) Descriptor() ([]byte, []int) {
return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{4}
}
func (x *DisconnectPeerRequest) GetPeerAddrs() string {
if x != nil {
return x.PeerAddrs
}
return ""
}
type DisconnectPeerResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *DisconnectPeerResponse) Reset() {
*x = DisconnectPeerResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DisconnectPeerResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DisconnectPeerResponse) ProtoMessage() {}
func (x *DisconnectPeerResponse) ProtoReflect() protoreflect.Message {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DisconnectPeerResponse.ProtoReflect.Descriptor instead.
func (*DisconnectPeerResponse) Descriptor() ([]byte, []int) {
return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{5}
}
type IsBannedRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Peer to lookup.
PeerAddrs string `protobuf:"bytes,1,opt,name=peer_addrs,json=peerAddrs,proto3" json:"peer_addrs,omitempty"`
}
func (x *IsBannedRequest) Reset() {
*x = IsBannedRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *IsBannedRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*IsBannedRequest) ProtoMessage() {}
func (x *IsBannedRequest) ProtoReflect() protoreflect.Message {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use IsBannedRequest.ProtoReflect.Descriptor instead.
func (*IsBannedRequest) Descriptor() ([]byte, []int) {
return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{6}
}
func (x *IsBannedRequest) GetPeerAddrs() string {
if x != nil {
return x.PeerAddrs
}
return ""
}
type IsBannedResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Banned bool `protobuf:"varint,1,opt,name=banned,proto3" json:"banned,omitempty"`
}
func (x *IsBannedResponse) Reset() {
*x = IsBannedResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *IsBannedResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*IsBannedResponse) ProtoMessage() {}
func (x *IsBannedResponse) ProtoReflect() protoreflect.Message {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use IsBannedResponse.ProtoReflect.Descriptor instead.
func (*IsBannedResponse) Descriptor() ([]byte, []int) {
return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{7}
}
func (x *IsBannedResponse) GetBanned() bool {
if x != nil {
return x.Banned
}
return false
}
type GetBlockHeaderRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Block hash in hex notation.
Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"`
}
func (x *GetBlockHeaderRequest) Reset() {
*x = GetBlockHeaderRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetBlockHeaderRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetBlockHeaderRequest) ProtoMessage() {}
func (x *GetBlockHeaderRequest) ProtoReflect() protoreflect.Message {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetBlockHeaderRequest.ProtoReflect.Descriptor instead.
func (*GetBlockHeaderRequest) Descriptor() ([]byte, []int) {
return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{8}
}
func (x *GetBlockHeaderRequest) GetHash() string {
if x != nil {
return x.Hash
}
return ""
}
type GetBlockHeaderResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The block hash (same as provided).
Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"`
// The number of confirmations.
Confirmations int64 `protobuf:"varint,2,opt,name=confirmations,proto3" json:"confirmations,omitempty"`
// The block size excluding witness data.
StrippedSize int64 `protobuf:"varint,3,opt,name=stripped_size,json=strippedSize,proto3" json:"stripped_size,omitempty"`
// The block size (bytes).
Size int64 `protobuf:"varint,4,opt,name=size,proto3" json:"size,omitempty"`
// The block weight as defined in BIP 141.
Weight int64 `protobuf:"varint,5,opt,name=weight,proto3" json:"weight,omitempty"`
// The block height or index.
Height int32 `protobuf:"varint,6,opt,name=height,proto3" json:"height,omitempty"`
// The block version.
Version int32 `protobuf:"varint,7,opt,name=version,proto3" json:"version,omitempty"`
// The block version.
VersionHex string `protobuf:"bytes,8,opt,name=version_hex,json=versionHex,proto3" json:"version_hex,omitempty"`
// The merkle root.
Merkleroot string `protobuf:"bytes,9,opt,name=merkleroot,proto3" json:"merkleroot,omitempty"`
// The block time in seconds since epoch (Jan 1 1970 GMT).
Time int64 `protobuf:"varint,10,opt,name=time,proto3" json:"time,omitempty"`
// The nonce.
Nonce uint32 `protobuf:"varint,11,opt,name=nonce,proto3" json:"nonce,omitempty"`
// The bits in hex notation.
Bits string `protobuf:"bytes,12,opt,name=bits,proto3" json:"bits,omitempty"`
// The number of transactions in the block.
Ntx int32 `protobuf:"varint,13,opt,name=ntx,proto3" json:"ntx,omitempty"`
// The hash of the previous block.
PreviousBlockHash string `protobuf:"bytes,14,opt,name=previous_block_hash,json=previousBlockHash,proto3" json:"previous_block_hash,omitempty"`
// The raw hex of the block.
RawHex []byte `protobuf:"bytes,15,opt,name=raw_hex,json=rawHex,proto3" json:"raw_hex,omitempty"`
}
func (x *GetBlockHeaderResponse) Reset() {
*x = GetBlockHeaderResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetBlockHeaderResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetBlockHeaderResponse) ProtoMessage() {}
func (x *GetBlockHeaderResponse) ProtoReflect() protoreflect.Message {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetBlockHeaderResponse.ProtoReflect.Descriptor instead.
func (*GetBlockHeaderResponse) Descriptor() ([]byte, []int) {
return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{9}
}
func (x *GetBlockHeaderResponse) GetHash() string {
if x != nil {
return x.Hash
}
return ""
}
func (x *GetBlockHeaderResponse) GetConfirmations() int64 {
if x != nil {
return x.Confirmations
}
return 0
}
func (x *GetBlockHeaderResponse) GetStrippedSize() int64 {
if x != nil {
return x.StrippedSize
}
return 0
}
func (x *GetBlockHeaderResponse) GetSize() int64 {
if x != nil {
return x.Size
}
return 0
}
func (x *GetBlockHeaderResponse) GetWeight() int64 {
if x != nil {
return x.Weight
}
return 0
}
func (x *GetBlockHeaderResponse) GetHeight() int32 {
if x != nil {
return x.Height
}
return 0
}
func (x *GetBlockHeaderResponse) GetVersion() int32 {
if x != nil {
return x.Version
}
return 0
}
func (x *GetBlockHeaderResponse) GetVersionHex() string {
if x != nil {
return x.VersionHex
}
return ""
}
func (x *GetBlockHeaderResponse) GetMerkleroot() string {
if x != nil {
return x.Merkleroot
}
return ""
}
func (x *GetBlockHeaderResponse) GetTime() int64 {
if x != nil {
return x.Time
}
return 0
}
func (x *GetBlockHeaderResponse) GetNonce() uint32 {
if x != nil {
return x.Nonce
}
return 0
}
func (x *GetBlockHeaderResponse) GetBits() string {
if x != nil {
return x.Bits
}
return ""
}
func (x *GetBlockHeaderResponse) GetNtx() int32 {
if x != nil {
return x.Ntx
}
return 0
}
func (x *GetBlockHeaderResponse) GetPreviousBlockHash() string {
if x != nil {
return x.PreviousBlockHash
}
return ""
}
func (x *GetBlockHeaderResponse) GetRawHex() []byte {
if x != nil {
return x.RawHex
}
return nil
}
type GetBlockRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Block hash in hex notation.
Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"`
}
func (x *GetBlockRequest) Reset() {
*x = GetBlockRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetBlockRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetBlockRequest) ProtoMessage() {}
func (x *GetBlockRequest) ProtoReflect() protoreflect.Message {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetBlockRequest.ProtoReflect.Descriptor instead.
func (*GetBlockRequest) Descriptor() ([]byte, []int) {
return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{10}
}
func (x *GetBlockRequest) GetHash() string {
if x != nil {
return x.Hash
}
return ""
}
type GetBlockResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The block hash (same as provided).
Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"`
// The number of confirmations.
Confirmations int64 `protobuf:"varint,2,opt,name=confirmations,proto3" json:"confirmations,omitempty"`
// The block size excluding witness data.
StrippedSize int64 `protobuf:"varint,3,opt,name=stripped_size,json=strippedSize,proto3" json:"stripped_size,omitempty"`
// The block size (bytes).
Size int64 `protobuf:"varint,4,opt,name=size,proto3" json:"size,omitempty"`
// The block weight as defined in BIP 141.
Weight int64 `protobuf:"varint,5,opt,name=weight,proto3" json:"weight,omitempty"`
// The block height or index.
Height int32 `protobuf:"varint,6,opt,name=height,proto3" json:"height,omitempty"`
// The block version.
Version int32 `protobuf:"varint,7,opt,name=version,proto3" json:"version,omitempty"`
// The block version.
VersionHex string `protobuf:"bytes,8,opt,name=version_hex,json=versionHex,proto3" json:"version_hex,omitempty"`
// The merkle root.
Merkleroot string `protobuf:"bytes,9,opt,name=merkleroot,proto3" json:"merkleroot,omitempty"`
// List of transaction ids.
Tx []string `protobuf:"bytes,10,rep,name=tx,proto3" json:"tx,omitempty"`
// The block time in seconds since epoch (Jan 1 1970 GMT).
Time int64 `protobuf:"varint,11,opt,name=time,proto3" json:"time,omitempty"`
// The nonce.
Nonce uint32 `protobuf:"varint,12,opt,name=nonce,proto3" json:"nonce,omitempty"`
// The bits in hex notation.
Bits string `protobuf:"bytes,13,opt,name=bits,proto3" json:"bits,omitempty"`
// The number of transactions in the block.
Ntx int32 `protobuf:"varint,14,opt,name=ntx,proto3" json:"ntx,omitempty"`
// The hash of the previous block.
PreviousBlockHash string `protobuf:"bytes,15,opt,name=previous_block_hash,json=previousBlockHash,proto3" json:"previous_block_hash,omitempty"`
// The raw hex of the block.
RawHex []byte `protobuf:"bytes,16,opt,name=raw_hex,json=rawHex,proto3" json:"raw_hex,omitempty"`
}
func (x *GetBlockResponse) Reset() {
*x = GetBlockResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetBlockResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetBlockResponse) ProtoMessage() {}
func (x *GetBlockResponse) ProtoReflect() protoreflect.Message {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[11]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetBlockResponse.ProtoReflect.Descriptor instead.
func (*GetBlockResponse) Descriptor() ([]byte, []int) {
return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{11}
}
func (x *GetBlockResponse) GetHash() string {
if x != nil {
return x.Hash
}
return ""
}
func (x *GetBlockResponse) GetConfirmations() int64 {
if x != nil {
return x.Confirmations
}
return 0
}
func (x *GetBlockResponse) GetStrippedSize() int64 {
if x != nil {
return x.StrippedSize
}
return 0
}
func (x *GetBlockResponse) GetSize() int64 {
if x != nil {
return x.Size
}
return 0
}
func (x *GetBlockResponse) GetWeight() int64 {
if x != nil {
return x.Weight
}
return 0
}
func (x *GetBlockResponse) GetHeight() int32 {
if x != nil {
return x.Height
}
return 0
}
func (x *GetBlockResponse) GetVersion() int32 {
if x != nil {
return x.Version
}
return 0
}
func (x *GetBlockResponse) GetVersionHex() string {
if x != nil {
return x.VersionHex
}
return ""
}
func (x *GetBlockResponse) GetMerkleroot() string {
if x != nil {
return x.Merkleroot
}
return ""
}
func (x *GetBlockResponse) GetTx() []string {
if x != nil {
return x.Tx
}
return nil
}
func (x *GetBlockResponse) GetTime() int64 {
if x != nil {
return x.Time
}
return 0
}
func (x *GetBlockResponse) GetNonce() uint32 {
if x != nil {
return x.Nonce
}
return 0
}
func (x *GetBlockResponse) GetBits() string {
if x != nil {
return x.Bits
}
return ""
}
func (x *GetBlockResponse) GetNtx() int32 {
if x != nil {
return x.Ntx
}
return 0
}
func (x *GetBlockResponse) GetPreviousBlockHash() string {
if x != nil {
return x.PreviousBlockHash
}
return ""
}
func (x *GetBlockResponse) GetRawHex() []byte {
if x != nil {
return x.RawHex
}
return nil
}
type GetCFilterRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Block hash in hex notation.
Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"`
}
func (x *GetCFilterRequest) Reset() {
*x = GetCFilterRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetCFilterRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetCFilterRequest) ProtoMessage() {}
func (x *GetCFilterRequest) ProtoReflect() protoreflect.Message {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[12]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetCFilterRequest.ProtoReflect.Descriptor instead.
func (*GetCFilterRequest) Descriptor() ([]byte, []int) {
return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{12}
}
func (x *GetCFilterRequest) GetHash() string {
if x != nil {
return x.Hash
}
return ""
}
type GetCFilterResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// GCS filter.
Filter []byte `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"`
}
func (x *GetCFilterResponse) Reset() {
*x = GetCFilterResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetCFilterResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetCFilterResponse) ProtoMessage() {}
func (x *GetCFilterResponse) ProtoReflect() protoreflect.Message {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[13]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetCFilterResponse.ProtoReflect.Descriptor instead.
func (*GetCFilterResponse) Descriptor() ([]byte, []int) {
return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{13}
}
func (x *GetCFilterResponse) GetFilter() []byte {
if x != nil {
return x.Filter
}
return nil
}
type GetBlockHashRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The block height or index.
Height int32 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"`
}
func (x *GetBlockHashRequest) Reset() {
*x = GetBlockHashRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetBlockHashRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetBlockHashRequest) ProtoMessage() {}
func (x *GetBlockHashRequest) ProtoReflect() protoreflect.Message {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[14]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetBlockHashRequest.ProtoReflect.Descriptor instead.
func (*GetBlockHashRequest) Descriptor() ([]byte, []int) {
return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{14}
}
func (x *GetBlockHashRequest) GetHeight() int32 {
if x != nil {
return x.Height
}
return 0
}
type GetBlockHashResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The block hash.
Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"`
}
func (x *GetBlockHashResponse) Reset() {
*x = GetBlockHashResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetBlockHashResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetBlockHashResponse) ProtoMessage() {}
func (x *GetBlockHashResponse) ProtoReflect() protoreflect.Message {
mi := &file_neutrinorpc_neutrino_proto_msgTypes[15]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetBlockHashResponse.ProtoReflect.Descriptor instead.
func (*GetBlockHashResponse) Descriptor() ([]byte, []int) {
return file_neutrinorpc_neutrino_proto_rawDescGZIP(), []int{15}
}
func (x *GetBlockHashResponse) GetHash() string {
if x != nil {
return x.Hash
}
return ""
}
var File_neutrinorpc_neutrino_proto protoreflect.FileDescriptor
var file_neutrinorpc_neutrino_proto_rawDesc = []byte{
0x0a, 0x1a, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2f, 0x6e, 0x65,
0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x6e, 0x65,
0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x22, 0x0f, 0x0a, 0x0d, 0x53, 0x74, 0x61,
0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x98, 0x01, 0x0a, 0x0e, 0x53,
0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a,
0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x61,
0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x18,
0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x73, 0x79, 0x6e, 0x63, 0x65, 0x64, 0x12, 0x21, 0x0a,
0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20,
0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74,
0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04,
0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12,
0x14, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05,
0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0x2f, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x50, 0x65, 0x65, 0x72,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x5f,
0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x65, 0x65,
0x72, 0x41, 0x64, 0x64, 0x72, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x41, 0x64, 0x64, 0x50, 0x65, 0x65,
0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x0a, 0x15, 0x44, 0x69, 0x73,
0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x65, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72,
0x73, 0x22, 0x18, 0x0a, 0x16, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50,
0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x30, 0x0a, 0x0f, 0x49,
0x73, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d,
0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x09, 0x70, 0x65, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x73, 0x22, 0x2a, 0x0a,
0x10, 0x49, 0x73, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x08, 0x52, 0x06, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x22, 0x2b, 0x0a, 0x15, 0x47, 0x65, 0x74,
0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, 0xaf, 0x03, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x42, 0x6c,
0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x63, 0x6f,
0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x73,
0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01,
0x28, 0x03, 0x52, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x64, 0x53, 0x69, 0x7a, 0x65,
0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04,
0x73, 0x69, 0x7a, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05,
0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, 0x06,
0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x68, 0x65,
0x69, 0x67, 0x68, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f,
0x0a, 0x0b, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x65, 0x78, 0x18, 0x08, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0a, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x78, 0x12,
0x1e, 0x0a, 0x0a, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x09, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x72, 0x6f, 0x6f, 0x74, 0x12,
0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x74,
0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01,
0x28, 0x0d, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x69, 0x74,
0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x69, 0x74, 0x73, 0x12, 0x10, 0x0a,
0x03, 0x6e, 0x74, 0x78, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6e, 0x74, 0x78, 0x12,
0x2e, 0x0a, 0x13, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x62, 0x6c, 0x6f, 0x63,
0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x70, 0x72,
0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12,
0x17, 0x0a, 0x07, 0x72, 0x61, 0x77, 0x5f, 0x68, 0x65, 0x78, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x06, 0x72, 0x61, 0x77, 0x48, 0x65, 0x78, 0x22, 0x25, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x42,
0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68,
0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22,
0xb9, 0x03, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66,
0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52,
0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x23,
0x0a, 0x0d, 0x73, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18,
0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x64, 0x53,
0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68,
0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12,
0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52,
0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69,
0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x65, 0x78,
0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48,
0x65, 0x78, 0x12, 0x1e, 0x0a, 0x0a, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x72, 0x6f, 0x6f, 0x74,
0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x72, 0x6f,
0x6f, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x78, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x02,
0x74, 0x78, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03,
0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18,
0x0c, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04,
0x62, 0x69, 0x74, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x69, 0x74, 0x73,
0x12, 0x10, 0x0a, 0x03, 0x6e, 0x74, 0x78, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6e,
0x74, 0x78, 0x12, 0x2e, 0x0a, 0x13, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x62,
0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52,
0x11, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61,
0x73, 0x68, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x61, 0x77, 0x5f, 0x68, 0x65, 0x78, 0x18, 0x10, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x61, 0x77, 0x48, 0x65, 0x78, 0x22, 0x27, 0x0a, 0x11, 0x47,
0x65, 0x74, 0x43, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
0x68, 0x61, 0x73, 0x68, 0x22, 0x2c, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x43, 0x46, 0x69, 0x6c, 0x74,
0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69,
0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74,
0x65, 0x72, 0x22, 0x2d, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61,
0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69,
0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68,
0x74, 0x22, 0x2a, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73,
0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73,
0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x32, 0x87, 0x05,
0x0a, 0x0b, 0x4e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x4b, 0x69, 0x74, 0x12, 0x41, 0x0a,
0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69,
0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70,
0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x44, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x50, 0x65, 0x65, 0x72, 0x12, 0x1b, 0x2e, 0x6e, 0x65,
0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x65, 0x65,
0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72,
0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x0e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e,
0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x22, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72,
0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63,
0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6e,
0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f,
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x47, 0x0a, 0x08, 0x49, 0x73, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x12, 0x1c, 0x2e,
0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x73, 0x42, 0x61,
0x6e, 0x6e, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6e, 0x65,
0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x73, 0x42, 0x61, 0x6e, 0x6e,
0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x0e, 0x47, 0x65,
0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x22, 0x2e, 0x6e,
0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c,
0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x23, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x47,
0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63,
0x6b, 0x12, 0x1c, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e,
0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x1d, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65,
0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d,
0x0a, 0x0a, 0x47, 0x65, 0x74, 0x43, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x6e,
0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x46,
0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6e,
0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x46,
0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a,
0x0c, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x20, 0x2e,
0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x42,
0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x21, 0x2e, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65,
0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e,
0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2f, 0x6e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
}
var (
file_neutrinorpc_neutrino_proto_rawDescOnce sync.Once
file_neutrinorpc_neutrino_proto_rawDescData = file_neutrinorpc_neutrino_proto_rawDesc
)
func file_neutrinorpc_neutrino_proto_rawDescGZIP() []byte {
file_neutrinorpc_neutrino_proto_rawDescOnce.Do(func() {
file_neutrinorpc_neutrino_proto_rawDescData = protoimpl.X.CompressGZIP(file_neutrinorpc_neutrino_proto_rawDescData)
})
return file_neutrinorpc_neutrino_proto_rawDescData
}
var file_neutrinorpc_neutrino_proto_msgTypes = make([]protoimpl.MessageInfo, 16)
var file_neutrinorpc_neutrino_proto_goTypes = []interface{}{
(*StatusRequest)(nil), // 0: neutrinorpc.StatusRequest
(*StatusResponse)(nil), // 1: neutrinorpc.StatusResponse
(*AddPeerRequest)(nil), // 2: neutrinorpc.AddPeerRequest
(*AddPeerResponse)(nil), // 3: neutrinorpc.AddPeerResponse
(*DisconnectPeerRequest)(nil), // 4: neutrinorpc.DisconnectPeerRequest
(*DisconnectPeerResponse)(nil), // 5: neutrinorpc.DisconnectPeerResponse
(*IsBannedRequest)(nil), // 6: neutrinorpc.IsBannedRequest
(*IsBannedResponse)(nil), // 7: neutrinorpc.IsBannedResponse
(*GetBlockHeaderRequest)(nil), // 8: neutrinorpc.GetBlockHeaderRequest
(*GetBlockHeaderResponse)(nil), // 9: neutrinorpc.GetBlockHeaderResponse
(*GetBlockRequest)(nil), // 10: neutrinorpc.GetBlockRequest
(*GetBlockResponse)(nil), // 11: neutrinorpc.GetBlockResponse
(*GetCFilterRequest)(nil), // 12: neutrinorpc.GetCFilterRequest
(*GetCFilterResponse)(nil), // 13: neutrinorpc.GetCFilterResponse
(*GetBlockHashRequest)(nil), // 14: neutrinorpc.GetBlockHashRequest
(*GetBlockHashResponse)(nil), // 15: neutrinorpc.GetBlockHashResponse
}
var file_neutrinorpc_neutrino_proto_depIdxs = []int32{
0, // 0: neutrinorpc.NeutrinoKit.Status:input_type -> neutrinorpc.StatusRequest
2, // 1: neutrinorpc.NeutrinoKit.AddPeer:input_type -> neutrinorpc.AddPeerRequest
4, // 2: neutrinorpc.NeutrinoKit.DisconnectPeer:input_type -> neutrinorpc.DisconnectPeerRequest
6, // 3: neutrinorpc.NeutrinoKit.IsBanned:input_type -> neutrinorpc.IsBannedRequest
8, // 4: neutrinorpc.NeutrinoKit.GetBlockHeader:input_type -> neutrinorpc.GetBlockHeaderRequest
10, // 5: neutrinorpc.NeutrinoKit.GetBlock:input_type -> neutrinorpc.GetBlockRequest
12, // 6: neutrinorpc.NeutrinoKit.GetCFilter:input_type -> neutrinorpc.GetCFilterRequest
14, // 7: neutrinorpc.NeutrinoKit.GetBlockHash:input_type -> neutrinorpc.GetBlockHashRequest
1, // 8: neutrinorpc.NeutrinoKit.Status:output_type -> neutrinorpc.StatusResponse
3, // 9: neutrinorpc.NeutrinoKit.AddPeer:output_type -> neutrinorpc.AddPeerResponse
5, // 10: neutrinorpc.NeutrinoKit.DisconnectPeer:output_type -> neutrinorpc.DisconnectPeerResponse
7, // 11: neutrinorpc.NeutrinoKit.IsBanned:output_type -> neutrinorpc.IsBannedResponse
9, // 12: neutrinorpc.NeutrinoKit.GetBlockHeader:output_type -> neutrinorpc.GetBlockHeaderResponse
11, // 13: neutrinorpc.NeutrinoKit.GetBlock:output_type -> neutrinorpc.GetBlockResponse
13, // 14: neutrinorpc.NeutrinoKit.GetCFilter:output_type -> neutrinorpc.GetCFilterResponse
15, // 15: neutrinorpc.NeutrinoKit.GetBlockHash:output_type -> neutrinorpc.GetBlockHashResponse
8, // [8:16] is the sub-list for method output_type
0, // [0:8] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_neutrinorpc_neutrino_proto_init() }
func file_neutrinorpc_neutrino_proto_init() {
if File_neutrinorpc_neutrino_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_neutrinorpc_neutrino_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StatusRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_neutrinorpc_neutrino_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StatusResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_neutrinorpc_neutrino_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AddPeerRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_neutrinorpc_neutrino_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AddPeerResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_neutrinorpc_neutrino_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DisconnectPeerRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_neutrinorpc_neutrino_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DisconnectPeerResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_neutrinorpc_neutrino_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*IsBannedRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_neutrinorpc_neutrino_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*IsBannedResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_neutrinorpc_neutrino_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetBlockHeaderRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_neutrinorpc_neutrino_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetBlockHeaderResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_neutrinorpc_neutrino_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetBlockRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_neutrinorpc_neutrino_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetBlockResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_neutrinorpc_neutrino_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetCFilterRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_neutrinorpc_neutrino_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetCFilterResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_neutrinorpc_neutrino_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetBlockHashRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_neutrinorpc_neutrino_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetBlockHashResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_neutrinorpc_neutrino_proto_rawDesc,
NumEnums: 0,
NumMessages: 16,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_neutrinorpc_neutrino_proto_goTypes,
DependencyIndexes: file_neutrinorpc_neutrino_proto_depIdxs,
MessageInfos: file_neutrinorpc_neutrino_proto_msgTypes,
}.Build()
File_neutrinorpc_neutrino_proto = out.File
file_neutrinorpc_neutrino_proto_rawDesc = nil
file_neutrinorpc_neutrino_proto_goTypes = nil
file_neutrinorpc_neutrino_proto_depIdxs = nil
}
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: neutrinorpc/neutrino.proto
/*
Package neutrinorpc is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package neutrinorpc
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
func request_NeutrinoKit_Status_0(ctx context.Context, marshaler runtime.Marshaler, client NeutrinoKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq StatusRequest
var metadata runtime.ServerMetadata
msg, err := client.Status(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_NeutrinoKit_Status_0(ctx context.Context, marshaler runtime.Marshaler, server NeutrinoKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq StatusRequest
var metadata runtime.ServerMetadata
msg, err := server.Status(ctx, &protoReq)
return msg, metadata, err
}
func request_NeutrinoKit_AddPeer_0(ctx context.Context, marshaler runtime.Marshaler, client NeutrinoKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq AddPeerRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.AddPeer(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_NeutrinoKit_AddPeer_0(ctx context.Context, marshaler runtime.Marshaler, server NeutrinoKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq AddPeerRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.AddPeer(ctx, &protoReq)
return msg, metadata, err
}
func request_NeutrinoKit_DisconnectPeer_0(ctx context.Context, marshaler runtime.Marshaler, client NeutrinoKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DisconnectPeerRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.DisconnectPeer(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_NeutrinoKit_DisconnectPeer_0(ctx context.Context, marshaler runtime.Marshaler, server NeutrinoKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DisconnectPeerRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.DisconnectPeer(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_NeutrinoKit_IsBanned_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_NeutrinoKit_IsBanned_0(ctx context.Context, marshaler runtime.Marshaler, client NeutrinoKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq IsBannedRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NeutrinoKit_IsBanned_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.IsBanned(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_NeutrinoKit_IsBanned_0(ctx context.Context, marshaler runtime.Marshaler, server NeutrinoKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq IsBannedRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_NeutrinoKit_IsBanned_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.IsBanned(ctx, &protoReq)
return msg, metadata, err
}
func request_NeutrinoKit_GetBlockHeader_0(ctx context.Context, marshaler runtime.Marshaler, client NeutrinoKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetBlockHeaderRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["hash"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "hash")
}
protoReq.Hash, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "hash", err)
}
msg, err := client.GetBlockHeader(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_NeutrinoKit_GetBlockHeader_0(ctx context.Context, marshaler runtime.Marshaler, server NeutrinoKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetBlockHeaderRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["hash"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "hash")
}
protoReq.Hash, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "hash", err)
}
msg, err := server.GetBlockHeader(ctx, &protoReq)
return msg, metadata, err
}
func request_NeutrinoKit_GetBlock_0(ctx context.Context, marshaler runtime.Marshaler, client NeutrinoKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetBlockRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["hash"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "hash")
}
protoReq.Hash, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "hash", err)
}
msg, err := client.GetBlock(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_NeutrinoKit_GetBlock_0(ctx context.Context, marshaler runtime.Marshaler, server NeutrinoKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetBlockRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["hash"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "hash")
}
protoReq.Hash, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "hash", err)
}
msg, err := server.GetBlock(ctx, &protoReq)
return msg, metadata, err
}
func request_NeutrinoKit_GetCFilter_0(ctx context.Context, marshaler runtime.Marshaler, client NeutrinoKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetCFilterRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["hash"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "hash")
}
protoReq.Hash, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "hash", err)
}
msg, err := client.GetCFilter(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_NeutrinoKit_GetCFilter_0(ctx context.Context, marshaler runtime.Marshaler, server NeutrinoKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetCFilterRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["hash"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "hash")
}
protoReq.Hash, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "hash", err)
}
msg, err := server.GetCFilter(ctx, &protoReq)
return msg, metadata, err
}
func request_NeutrinoKit_GetBlockHash_0(ctx context.Context, marshaler runtime.Marshaler, client NeutrinoKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetBlockHashRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["height"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "height")
}
protoReq.Height, err = runtime.Int32(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "height", err)
}
msg, err := client.GetBlockHash(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_NeutrinoKit_GetBlockHash_0(ctx context.Context, marshaler runtime.Marshaler, server NeutrinoKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetBlockHashRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["height"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "height")
}
protoReq.Height, err = runtime.Int32(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "height", err)
}
msg, err := server.GetBlockHash(ctx, &protoReq)
return msg, metadata, err
}
// RegisterNeutrinoKitHandlerServer registers the http handlers for service NeutrinoKit to "mux".
// UnaryRPC :call NeutrinoKitServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterNeutrinoKitHandlerFromEndpoint instead.
func RegisterNeutrinoKitHandlerServer(ctx context.Context, mux *runtime.ServeMux, server NeutrinoKitServer) error {
mux.Handle("GET", pattern_NeutrinoKit_Status_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/Status", runtime.WithHTTPPathPattern("/v2/neutrino/status"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_NeutrinoKit_Status_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_NeutrinoKit_Status_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_NeutrinoKit_AddPeer_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/AddPeer", runtime.WithHTTPPathPattern("/v2/neutrino/addpeer"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_NeutrinoKit_AddPeer_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_NeutrinoKit_AddPeer_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_NeutrinoKit_DisconnectPeer_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/DisconnectPeer", runtime.WithHTTPPathPattern("/v2/neutrino/disconnect"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_NeutrinoKit_DisconnectPeer_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_NeutrinoKit_DisconnectPeer_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_NeutrinoKit_IsBanned_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/IsBanned", runtime.WithHTTPPathPattern("/v2/neutrino/isbanned"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_NeutrinoKit_IsBanned_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_NeutrinoKit_IsBanned_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_NeutrinoKit_GetBlockHeader_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/GetBlockHeader", runtime.WithHTTPPathPattern("/v2/neutrino/blockheader/{hash}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_NeutrinoKit_GetBlockHeader_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_NeutrinoKit_GetBlockHeader_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_NeutrinoKit_GetBlock_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/GetBlock", runtime.WithHTTPPathPattern("/v2/neutrino/block/{hash}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_NeutrinoKit_GetBlock_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_NeutrinoKit_GetBlock_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_NeutrinoKit_GetCFilter_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/GetCFilter", runtime.WithHTTPPathPattern("/v2/neutrino/cfilter/{hash}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_NeutrinoKit_GetCFilter_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_NeutrinoKit_GetCFilter_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_NeutrinoKit_GetBlockHash_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/GetBlockHash", runtime.WithHTTPPathPattern("/v2/neutrino/blockhash/{height}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_NeutrinoKit_GetBlockHash_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_NeutrinoKit_GetBlockHash_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterNeutrinoKitHandlerFromEndpoint is same as RegisterNeutrinoKitHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterNeutrinoKitHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterNeutrinoKitHandler(ctx, mux, conn)
}
// RegisterNeutrinoKitHandler registers the http handlers for service NeutrinoKit to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterNeutrinoKitHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterNeutrinoKitHandlerClient(ctx, mux, NewNeutrinoKitClient(conn))
}
// RegisterNeutrinoKitHandlerClient registers the http handlers for service NeutrinoKit
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "NeutrinoKitClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "NeutrinoKitClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "NeutrinoKitClient" to call the correct interceptors.
func RegisterNeutrinoKitHandlerClient(ctx context.Context, mux *runtime.ServeMux, client NeutrinoKitClient) error {
mux.Handle("GET", pattern_NeutrinoKit_Status_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/Status", runtime.WithHTTPPathPattern("/v2/neutrino/status"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_NeutrinoKit_Status_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_NeutrinoKit_Status_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_NeutrinoKit_AddPeer_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/AddPeer", runtime.WithHTTPPathPattern("/v2/neutrino/addpeer"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_NeutrinoKit_AddPeer_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_NeutrinoKit_AddPeer_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_NeutrinoKit_DisconnectPeer_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/DisconnectPeer", runtime.WithHTTPPathPattern("/v2/neutrino/disconnect"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_NeutrinoKit_DisconnectPeer_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_NeutrinoKit_DisconnectPeer_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_NeutrinoKit_IsBanned_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/IsBanned", runtime.WithHTTPPathPattern("/v2/neutrino/isbanned"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_NeutrinoKit_IsBanned_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_NeutrinoKit_IsBanned_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_NeutrinoKit_GetBlockHeader_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/GetBlockHeader", runtime.WithHTTPPathPattern("/v2/neutrino/blockheader/{hash}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_NeutrinoKit_GetBlockHeader_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_NeutrinoKit_GetBlockHeader_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_NeutrinoKit_GetBlock_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/GetBlock", runtime.WithHTTPPathPattern("/v2/neutrino/block/{hash}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_NeutrinoKit_GetBlock_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_NeutrinoKit_GetBlock_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_NeutrinoKit_GetCFilter_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/GetCFilter", runtime.WithHTTPPathPattern("/v2/neutrino/cfilter/{hash}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_NeutrinoKit_GetCFilter_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_NeutrinoKit_GetCFilter_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_NeutrinoKit_GetBlockHash_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/neutrinorpc.NeutrinoKit/GetBlockHash", runtime.WithHTTPPathPattern("/v2/neutrino/blockhash/{height}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_NeutrinoKit_GetBlockHash_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_NeutrinoKit_GetBlockHash_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_NeutrinoKit_Status_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "neutrino", "status"}, ""))
pattern_NeutrinoKit_AddPeer_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "neutrino", "addpeer"}, ""))
pattern_NeutrinoKit_DisconnectPeer_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "neutrino", "disconnect"}, ""))
pattern_NeutrinoKit_IsBanned_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "neutrino", "isbanned"}, ""))
pattern_NeutrinoKit_GetBlockHeader_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v2", "neutrino", "blockheader", "hash"}, ""))
pattern_NeutrinoKit_GetBlock_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v2", "neutrino", "block", "hash"}, ""))
pattern_NeutrinoKit_GetCFilter_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v2", "neutrino", "cfilter", "hash"}, ""))
pattern_NeutrinoKit_GetBlockHash_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v2", "neutrino", "blockhash", "height"}, ""))
)
var (
forward_NeutrinoKit_Status_0 = runtime.ForwardResponseMessage
forward_NeutrinoKit_AddPeer_0 = runtime.ForwardResponseMessage
forward_NeutrinoKit_DisconnectPeer_0 = runtime.ForwardResponseMessage
forward_NeutrinoKit_IsBanned_0 = runtime.ForwardResponseMessage
forward_NeutrinoKit_GetBlockHeader_0 = runtime.ForwardResponseMessage
forward_NeutrinoKit_GetBlock_0 = runtime.ForwardResponseMessage
forward_NeutrinoKit_GetCFilter_0 = runtime.ForwardResponseMessage
forward_NeutrinoKit_GetBlockHash_0 = runtime.ForwardResponseMessage
)
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package neutrinorpc
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// NeutrinoKitClient is the client API for NeutrinoKit service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type NeutrinoKitClient interface {
// lncli: `neutrino status`
// Status returns the status of the light client neutrino instance,
// along with height and hash of the best block, and a list of connected
// peers.
Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error)
// lncli: `neutrino addpeer`
// AddPeer adds a new peer that has already been connected to the server.
AddPeer(ctx context.Context, in *AddPeerRequest, opts ...grpc.CallOption) (*AddPeerResponse, error)
// lncli: `neutrino disconnectpeer`
// DisconnectPeer disconnects a peer by target address. Both outbound and
// inbound nodes will be searched for the target node. An error message will
// be returned if the peer was not found.
DisconnectPeer(ctx context.Context, in *DisconnectPeerRequest, opts ...grpc.CallOption) (*DisconnectPeerResponse, error)
// lncli: `neutrino isbanned`
// IsBanned returns true if the peer is banned, otherwise false.
IsBanned(ctx context.Context, in *IsBannedRequest, opts ...grpc.CallOption) (*IsBannedResponse, error)
// lncli: `neutrino getblockheader`
// GetBlockHeader returns a block header with a particular block hash.
GetBlockHeader(ctx context.Context, in *GetBlockHeaderRequest, opts ...grpc.CallOption) (*GetBlockHeaderResponse, error)
// GetBlock returns a block with a particular block hash.
GetBlock(ctx context.Context, in *GetBlockRequest, opts ...grpc.CallOption) (*GetBlockResponse, error)
// lncli: `neutrino getcfilter`
// GetCFilter returns a compact filter from a block.
GetCFilter(ctx context.Context, in *GetCFilterRequest, opts ...grpc.CallOption) (*GetCFilterResponse, error)
// Deprecated: Do not use.
//
// Deprecated, use chainrpc.GetBlockHash instead.
// GetBlockHash returns the header hash of a block at a given height.
GetBlockHash(ctx context.Context, in *GetBlockHashRequest, opts ...grpc.CallOption) (*GetBlockHashResponse, error)
}
type neutrinoKitClient struct {
cc grpc.ClientConnInterface
}
func NewNeutrinoKitClient(cc grpc.ClientConnInterface) NeutrinoKitClient {
return &neutrinoKitClient{cc}
}
func (c *neutrinoKitClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) {
out := new(StatusResponse)
err := c.cc.Invoke(ctx, "/neutrinorpc.NeutrinoKit/Status", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *neutrinoKitClient) AddPeer(ctx context.Context, in *AddPeerRequest, opts ...grpc.CallOption) (*AddPeerResponse, error) {
out := new(AddPeerResponse)
err := c.cc.Invoke(ctx, "/neutrinorpc.NeutrinoKit/AddPeer", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *neutrinoKitClient) DisconnectPeer(ctx context.Context, in *DisconnectPeerRequest, opts ...grpc.CallOption) (*DisconnectPeerResponse, error) {
out := new(DisconnectPeerResponse)
err := c.cc.Invoke(ctx, "/neutrinorpc.NeutrinoKit/DisconnectPeer", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *neutrinoKitClient) IsBanned(ctx context.Context, in *IsBannedRequest, opts ...grpc.CallOption) (*IsBannedResponse, error) {
out := new(IsBannedResponse)
err := c.cc.Invoke(ctx, "/neutrinorpc.NeutrinoKit/IsBanned", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *neutrinoKitClient) GetBlockHeader(ctx context.Context, in *GetBlockHeaderRequest, opts ...grpc.CallOption) (*GetBlockHeaderResponse, error) {
out := new(GetBlockHeaderResponse)
err := c.cc.Invoke(ctx, "/neutrinorpc.NeutrinoKit/GetBlockHeader", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *neutrinoKitClient) GetBlock(ctx context.Context, in *GetBlockRequest, opts ...grpc.CallOption) (*GetBlockResponse, error) {
out := new(GetBlockResponse)
err := c.cc.Invoke(ctx, "/neutrinorpc.NeutrinoKit/GetBlock", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *neutrinoKitClient) GetCFilter(ctx context.Context, in *GetCFilterRequest, opts ...grpc.CallOption) (*GetCFilterResponse, error) {
out := new(GetCFilterResponse)
err := c.cc.Invoke(ctx, "/neutrinorpc.NeutrinoKit/GetCFilter", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Deprecated: Do not use.
func (c *neutrinoKitClient) GetBlockHash(ctx context.Context, in *GetBlockHashRequest, opts ...grpc.CallOption) (*GetBlockHashResponse, error) {
out := new(GetBlockHashResponse)
err := c.cc.Invoke(ctx, "/neutrinorpc.NeutrinoKit/GetBlockHash", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// NeutrinoKitServer is the server API for NeutrinoKit service.
// All implementations must embed UnimplementedNeutrinoKitServer
// for forward compatibility
type NeutrinoKitServer interface {
// lncli: `neutrino status`
// Status returns the status of the light client neutrino instance,
// along with height and hash of the best block, and a list of connected
// peers.
Status(context.Context, *StatusRequest) (*StatusResponse, error)
// lncli: `neutrino addpeer`
// AddPeer adds a new peer that has already been connected to the server.
AddPeer(context.Context, *AddPeerRequest) (*AddPeerResponse, error)
// lncli: `neutrino disconnectpeer`
// DisconnectPeer disconnects a peer by target address. Both outbound and
// inbound nodes will be searched for the target node. An error message will
// be returned if the peer was not found.
DisconnectPeer(context.Context, *DisconnectPeerRequest) (*DisconnectPeerResponse, error)
// lncli: `neutrino isbanned`
// IsBanned returns true if the peer is banned, otherwise false.
IsBanned(context.Context, *IsBannedRequest) (*IsBannedResponse, error)
// lncli: `neutrino getblockheader`
// GetBlockHeader returns a block header with a particular block hash.
GetBlockHeader(context.Context, *GetBlockHeaderRequest) (*GetBlockHeaderResponse, error)
// GetBlock returns a block with a particular block hash.
GetBlock(context.Context, *GetBlockRequest) (*GetBlockResponse, error)
// lncli: `neutrino getcfilter`
// GetCFilter returns a compact filter from a block.
GetCFilter(context.Context, *GetCFilterRequest) (*GetCFilterResponse, error)
// Deprecated: Do not use.
//
// Deprecated, use chainrpc.GetBlockHash instead.
// GetBlockHash returns the header hash of a block at a given height.
GetBlockHash(context.Context, *GetBlockHashRequest) (*GetBlockHashResponse, error)
mustEmbedUnimplementedNeutrinoKitServer()
}
// UnimplementedNeutrinoKitServer must be embedded to have forward compatible implementations.
type UnimplementedNeutrinoKitServer struct {
}
func (UnimplementedNeutrinoKitServer) Status(context.Context, *StatusRequest) (*StatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Status not implemented")
}
func (UnimplementedNeutrinoKitServer) AddPeer(context.Context, *AddPeerRequest) (*AddPeerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AddPeer not implemented")
}
func (UnimplementedNeutrinoKitServer) DisconnectPeer(context.Context, *DisconnectPeerRequest) (*DisconnectPeerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DisconnectPeer not implemented")
}
func (UnimplementedNeutrinoKitServer) IsBanned(context.Context, *IsBannedRequest) (*IsBannedResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method IsBanned not implemented")
}
func (UnimplementedNeutrinoKitServer) GetBlockHeader(context.Context, *GetBlockHeaderRequest) (*GetBlockHeaderResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetBlockHeader not implemented")
}
func (UnimplementedNeutrinoKitServer) GetBlock(context.Context, *GetBlockRequest) (*GetBlockResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetBlock not implemented")
}
func (UnimplementedNeutrinoKitServer) GetCFilter(context.Context, *GetCFilterRequest) (*GetCFilterResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetCFilter not implemented")
}
func (UnimplementedNeutrinoKitServer) GetBlockHash(context.Context, *GetBlockHashRequest) (*GetBlockHashResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetBlockHash not implemented")
}
func (UnimplementedNeutrinoKitServer) mustEmbedUnimplementedNeutrinoKitServer() {}
// UnsafeNeutrinoKitServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to NeutrinoKitServer will
// result in compilation errors.
type UnsafeNeutrinoKitServer interface {
mustEmbedUnimplementedNeutrinoKitServer()
}
func RegisterNeutrinoKitServer(s grpc.ServiceRegistrar, srv NeutrinoKitServer) {
s.RegisterService(&NeutrinoKit_ServiceDesc, srv)
}
func _NeutrinoKit_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StatusRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(NeutrinoKitServer).Status(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/neutrinorpc.NeutrinoKit/Status",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(NeutrinoKitServer).Status(ctx, req.(*StatusRequest))
}
return interceptor(ctx, in, info, handler)
}
func _NeutrinoKit_AddPeer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AddPeerRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(NeutrinoKitServer).AddPeer(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/neutrinorpc.NeutrinoKit/AddPeer",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(NeutrinoKitServer).AddPeer(ctx, req.(*AddPeerRequest))
}
return interceptor(ctx, in, info, handler)
}
func _NeutrinoKit_DisconnectPeer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DisconnectPeerRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(NeutrinoKitServer).DisconnectPeer(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/neutrinorpc.NeutrinoKit/DisconnectPeer",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(NeutrinoKitServer).DisconnectPeer(ctx, req.(*DisconnectPeerRequest))
}
return interceptor(ctx, in, info, handler)
}
func _NeutrinoKit_IsBanned_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(IsBannedRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(NeutrinoKitServer).IsBanned(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/neutrinorpc.NeutrinoKit/IsBanned",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(NeutrinoKitServer).IsBanned(ctx, req.(*IsBannedRequest))
}
return interceptor(ctx, in, info, handler)
}
func _NeutrinoKit_GetBlockHeader_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetBlockHeaderRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(NeutrinoKitServer).GetBlockHeader(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/neutrinorpc.NeutrinoKit/GetBlockHeader",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(NeutrinoKitServer).GetBlockHeader(ctx, req.(*GetBlockHeaderRequest))
}
return interceptor(ctx, in, info, handler)
}
func _NeutrinoKit_GetBlock_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetBlockRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(NeutrinoKitServer).GetBlock(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/neutrinorpc.NeutrinoKit/GetBlock",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(NeutrinoKitServer).GetBlock(ctx, req.(*GetBlockRequest))
}
return interceptor(ctx, in, info, handler)
}
func _NeutrinoKit_GetCFilter_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetCFilterRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(NeutrinoKitServer).GetCFilter(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/neutrinorpc.NeutrinoKit/GetCFilter",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(NeutrinoKitServer).GetCFilter(ctx, req.(*GetCFilterRequest))
}
return interceptor(ctx, in, info, handler)
}
func _NeutrinoKit_GetBlockHash_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetBlockHashRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(NeutrinoKitServer).GetBlockHash(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/neutrinorpc.NeutrinoKit/GetBlockHash",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(NeutrinoKitServer).GetBlockHash(ctx, req.(*GetBlockHashRequest))
}
return interceptor(ctx, in, info, handler)
}
// NeutrinoKit_ServiceDesc is the grpc.ServiceDesc for NeutrinoKit service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var NeutrinoKit_ServiceDesc = grpc.ServiceDesc{
ServiceName: "neutrinorpc.NeutrinoKit",
HandlerType: (*NeutrinoKitServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Status",
Handler: _NeutrinoKit_Status_Handler,
},
{
MethodName: "AddPeer",
Handler: _NeutrinoKit_AddPeer_Handler,
},
{
MethodName: "DisconnectPeer",
Handler: _NeutrinoKit_DisconnectPeer_Handler,
},
{
MethodName: "IsBanned",
Handler: _NeutrinoKit_IsBanned_Handler,
},
{
MethodName: "GetBlockHeader",
Handler: _NeutrinoKit_GetBlockHeader_Handler,
},
{
MethodName: "GetBlock",
Handler: _NeutrinoKit_GetBlock_Handler,
},
{
MethodName: "GetCFilter",
Handler: _NeutrinoKit_GetCFilter_Handler,
},
{
MethodName: "GetBlockHash",
Handler: _NeutrinoKit_GetBlockHash_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "neutrinorpc/neutrino.proto",
}
// Code generated by falafel 0.9.2. DO NOT EDIT.
// source: neutrino.proto
package neutrinorpc
import (
"context"
gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protojson"
)
func RegisterNeutrinoKitJSONCallbacks(registry map[string]func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error))) {
marshaler := &gateway.JSONPb{
MarshalOptions: protojson.MarshalOptions{
UseProtoNames: true,
EmitUnpopulated: true,
},
}
registry["neutrinorpc.NeutrinoKit.Status"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &StatusRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewNeutrinoKitClient(conn)
resp, err := client.Status(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["neutrinorpc.NeutrinoKit.AddPeer"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &AddPeerRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewNeutrinoKitClient(conn)
resp, err := client.AddPeer(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["neutrinorpc.NeutrinoKit.DisconnectPeer"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &DisconnectPeerRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewNeutrinoKitClient(conn)
resp, err := client.DisconnectPeer(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["neutrinorpc.NeutrinoKit.IsBanned"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &IsBannedRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewNeutrinoKitClient(conn)
resp, err := client.IsBanned(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["neutrinorpc.NeutrinoKit.GetBlockHeader"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &GetBlockHeaderRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewNeutrinoKitClient(conn)
resp, err := client.GetBlockHeader(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["neutrinorpc.NeutrinoKit.GetBlock"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &GetBlockRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewNeutrinoKitClient(conn)
resp, err := client.GetBlock(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["neutrinorpc.NeutrinoKit.GetCFilter"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &GetCFilterRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewNeutrinoKitClient(conn)
resp, err := client.GetCFilter(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["neutrinorpc.NeutrinoKit.GetBlockHash"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &GetBlockHashRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewNeutrinoKitClient(conn)
resp, err := client.GetBlockHash(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}
package peersrpc
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This means the
// package will not perform any logging by default until the caller requests
// it.
var log btclog.Logger
// Subsystem defines the logging code for this subsystem.
const Subsystem = "PRPC"
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled by
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info. This
// should be used in preference to SetLogWriter if the caller is also using
// btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v3.21.12
// source: peersrpc/peers.proto
package peersrpc
import (
lnrpc "github.com/lightningnetwork/lnd/lnrpc"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// UpdateAction is used to determine the kind of action we are referring to.
type UpdateAction int32
const (
// ADD indicates this is an "insertion" kind of action.
UpdateAction_ADD UpdateAction = 0
// REMOVE indicates this is a "deletion" kind of action.
UpdateAction_REMOVE UpdateAction = 1
)
// Enum value maps for UpdateAction.
var (
UpdateAction_name = map[int32]string{
0: "ADD",
1: "REMOVE",
}
UpdateAction_value = map[string]int32{
"ADD": 0,
"REMOVE": 1,
}
)
func (x UpdateAction) Enum() *UpdateAction {
p := new(UpdateAction)
*p = x
return p
}
func (x UpdateAction) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (UpdateAction) Descriptor() protoreflect.EnumDescriptor {
return file_peersrpc_peers_proto_enumTypes[0].Descriptor()
}
func (UpdateAction) Type() protoreflect.EnumType {
return &file_peersrpc_peers_proto_enumTypes[0]
}
func (x UpdateAction) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use UpdateAction.Descriptor instead.
func (UpdateAction) EnumDescriptor() ([]byte, []int) {
return file_peersrpc_peers_proto_rawDescGZIP(), []int{0}
}
type FeatureSet int32
const (
// SET_INIT identifies features that should be sent in an Init message to
// a remote peer.
FeatureSet_SET_INIT FeatureSet = 0
// SET_LEGACY_GLOBAL identifies features that should be set in the legacy
// GlobalFeatures field of an Init message, which maintains backwards
// compatibility with nodes that haven't implemented flat features.
FeatureSet_SET_LEGACY_GLOBAL FeatureSet = 1
// SET_NODE_ANN identifies features that should be advertised on node
// announcements.
FeatureSet_SET_NODE_ANN FeatureSet = 2
// SET_INVOICE identifies features that should be advertised on invoices
// generated by the daemon.
FeatureSet_SET_INVOICE FeatureSet = 3
// SET_INVOICE_AMP identifies the features that should be advertised on
// AMP invoices generated by the daemon.
FeatureSet_SET_INVOICE_AMP FeatureSet = 4
)
// Enum value maps for FeatureSet.
var (
FeatureSet_name = map[int32]string{
0: "SET_INIT",
1: "SET_LEGACY_GLOBAL",
2: "SET_NODE_ANN",
3: "SET_INVOICE",
4: "SET_INVOICE_AMP",
}
FeatureSet_value = map[string]int32{
"SET_INIT": 0,
"SET_LEGACY_GLOBAL": 1,
"SET_NODE_ANN": 2,
"SET_INVOICE": 3,
"SET_INVOICE_AMP": 4,
}
)
func (x FeatureSet) Enum() *FeatureSet {
p := new(FeatureSet)
*p = x
return p
}
func (x FeatureSet) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (FeatureSet) Descriptor() protoreflect.EnumDescriptor {
return file_peersrpc_peers_proto_enumTypes[1].Descriptor()
}
func (FeatureSet) Type() protoreflect.EnumType {
return &file_peersrpc_peers_proto_enumTypes[1]
}
func (x FeatureSet) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use FeatureSet.Descriptor instead.
func (FeatureSet) EnumDescriptor() ([]byte, []int) {
return file_peersrpc_peers_proto_rawDescGZIP(), []int{1}
}
type UpdateAddressAction struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Determines the kind of action.
Action UpdateAction `protobuf:"varint,1,opt,name=action,proto3,enum=peersrpc.UpdateAction" json:"action,omitempty"`
// The address used to apply the update action.
Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"`
}
func (x *UpdateAddressAction) Reset() {
*x = UpdateAddressAction{}
if protoimpl.UnsafeEnabled {
mi := &file_peersrpc_peers_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *UpdateAddressAction) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UpdateAddressAction) ProtoMessage() {}
func (x *UpdateAddressAction) ProtoReflect() protoreflect.Message {
mi := &file_peersrpc_peers_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UpdateAddressAction.ProtoReflect.Descriptor instead.
func (*UpdateAddressAction) Descriptor() ([]byte, []int) {
return file_peersrpc_peers_proto_rawDescGZIP(), []int{0}
}
func (x *UpdateAddressAction) GetAction() UpdateAction {
if x != nil {
return x.Action
}
return UpdateAction_ADD
}
func (x *UpdateAddressAction) GetAddress() string {
if x != nil {
return x.Address
}
return ""
}
type UpdateFeatureAction struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Determines the kind of action.
Action UpdateAction `protobuf:"varint,1,opt,name=action,proto3,enum=peersrpc.UpdateAction" json:"action,omitempty"`
// The feature bit used to apply the update action.
FeatureBit lnrpc.FeatureBit `protobuf:"varint,2,opt,name=feature_bit,json=featureBit,proto3,enum=lnrpc.FeatureBit" json:"feature_bit,omitempty"`
}
func (x *UpdateFeatureAction) Reset() {
*x = UpdateFeatureAction{}
if protoimpl.UnsafeEnabled {
mi := &file_peersrpc_peers_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *UpdateFeatureAction) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UpdateFeatureAction) ProtoMessage() {}
func (x *UpdateFeatureAction) ProtoReflect() protoreflect.Message {
mi := &file_peersrpc_peers_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UpdateFeatureAction.ProtoReflect.Descriptor instead.
func (*UpdateFeatureAction) Descriptor() ([]byte, []int) {
return file_peersrpc_peers_proto_rawDescGZIP(), []int{1}
}
func (x *UpdateFeatureAction) GetAction() UpdateAction {
if x != nil {
return x.Action
}
return UpdateAction_ADD
}
func (x *UpdateFeatureAction) GetFeatureBit() lnrpc.FeatureBit {
if x != nil {
return x.FeatureBit
}
return lnrpc.FeatureBit(0)
}
type NodeAnnouncementUpdateRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Set of changes for the features that the node supports.
FeatureUpdates []*UpdateFeatureAction `protobuf:"bytes,1,rep,name=feature_updates,json=featureUpdates,proto3" json:"feature_updates,omitempty"`
// Color is the node's color in hex code format.
Color string `protobuf:"bytes,2,opt,name=color,proto3" json:"color,omitempty"`
// Alias or nick name of the node.
Alias string `protobuf:"bytes,3,opt,name=alias,proto3" json:"alias,omitempty"`
// Set of changes for the node's known addresses.
AddressUpdates []*UpdateAddressAction `protobuf:"bytes,4,rep,name=address_updates,json=addressUpdates,proto3" json:"address_updates,omitempty"`
}
func (x *NodeAnnouncementUpdateRequest) Reset() {
*x = NodeAnnouncementUpdateRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_peersrpc_peers_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *NodeAnnouncementUpdateRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NodeAnnouncementUpdateRequest) ProtoMessage() {}
func (x *NodeAnnouncementUpdateRequest) ProtoReflect() protoreflect.Message {
mi := &file_peersrpc_peers_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NodeAnnouncementUpdateRequest.ProtoReflect.Descriptor instead.
func (*NodeAnnouncementUpdateRequest) Descriptor() ([]byte, []int) {
return file_peersrpc_peers_proto_rawDescGZIP(), []int{2}
}
func (x *NodeAnnouncementUpdateRequest) GetFeatureUpdates() []*UpdateFeatureAction {
if x != nil {
return x.FeatureUpdates
}
return nil
}
func (x *NodeAnnouncementUpdateRequest) GetColor() string {
if x != nil {
return x.Color
}
return ""
}
func (x *NodeAnnouncementUpdateRequest) GetAlias() string {
if x != nil {
return x.Alias
}
return ""
}
func (x *NodeAnnouncementUpdateRequest) GetAddressUpdates() []*UpdateAddressAction {
if x != nil {
return x.AddressUpdates
}
return nil
}
type NodeAnnouncementUpdateResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Ops []*lnrpc.Op `protobuf:"bytes,1,rep,name=ops,proto3" json:"ops,omitempty"`
}
func (x *NodeAnnouncementUpdateResponse) Reset() {
*x = NodeAnnouncementUpdateResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_peersrpc_peers_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *NodeAnnouncementUpdateResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NodeAnnouncementUpdateResponse) ProtoMessage() {}
func (x *NodeAnnouncementUpdateResponse) ProtoReflect() protoreflect.Message {
mi := &file_peersrpc_peers_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NodeAnnouncementUpdateResponse.ProtoReflect.Descriptor instead.
func (*NodeAnnouncementUpdateResponse) Descriptor() ([]byte, []int) {
return file_peersrpc_peers_proto_rawDescGZIP(), []int{3}
}
func (x *NodeAnnouncementUpdateResponse) GetOps() []*lnrpc.Op {
if x != nil {
return x.Ops
}
return nil
}
var File_peersrpc_peers_proto protoreflect.FileDescriptor
var file_peersrpc_peers_proto_rawDesc = []byte{
0x0a, 0x14, 0x70, 0x65, 0x65, 0x72, 0x73, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x65, 0x65, 0x72, 0x73,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x70, 0x65, 0x65, 0x72, 0x73, 0x72, 0x70, 0x63,
0x1a, 0x0f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x22, 0x5f, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x73,
0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x22, 0x79, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x65, 0x61, 0x74,
0x75, 0x72, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x06, 0x61, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x70, 0x65, 0x65, 0x72,
0x73, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x0b, 0x66, 0x65, 0x61,
0x74, 0x75, 0x72, 0x65, 0x5f, 0x62, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11,
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69,
0x74, 0x52, 0x0a, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, 0x74, 0x22, 0xdb, 0x01,
0x0a, 0x1d, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65,
0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x46, 0x0a, 0x0f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74,
0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x73,
0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72,
0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65,
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x12, 0x14, 0x0a,
0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c,
0x69, 0x61, 0x73, 0x12, 0x46, 0x0a, 0x0f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x75,
0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70,
0x65, 0x65, 0x72, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x61, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0x3d, 0x0a, 0x1e, 0x4e,
0x6f, 0x64, 0x65, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55,
0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a,
0x03, 0x6f, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x4f, 0x70, 0x52, 0x03, 0x6f, 0x70, 0x73, 0x2a, 0x23, 0x0a, 0x0c, 0x55, 0x70,
0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x44,
0x44, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x45, 0x4d, 0x4f, 0x56, 0x45, 0x10, 0x01, 0x2a,
0x69, 0x0a, 0x0a, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x74, 0x12, 0x0c, 0x0a,
0x08, 0x53, 0x45, 0x54, 0x5f, 0x49, 0x4e, 0x49, 0x54, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x53,
0x45, 0x54, 0x5f, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x5f, 0x47, 0x4c, 0x4f, 0x42, 0x41, 0x4c,
0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x45, 0x54, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x41,
0x4e, 0x4e, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x45, 0x54, 0x5f, 0x49, 0x4e, 0x56, 0x4f,
0x49, 0x43, 0x45, 0x10, 0x03, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x45, 0x54, 0x5f, 0x49, 0x4e, 0x56,
0x4f, 0x49, 0x43, 0x45, 0x5f, 0x41, 0x4d, 0x50, 0x10, 0x04, 0x32, 0x74, 0x0a, 0x05, 0x50, 0x65,
0x65, 0x72, 0x73, 0x12, 0x6b, 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x64,
0x65, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x27, 0x2e,
0x70, 0x65, 0x65, 0x72, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x6e, 0x6e,
0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x73, 0x72, 0x70,
0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65,
0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c,
0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f,
0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x72,
0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_peersrpc_peers_proto_rawDescOnce sync.Once
file_peersrpc_peers_proto_rawDescData = file_peersrpc_peers_proto_rawDesc
)
func file_peersrpc_peers_proto_rawDescGZIP() []byte {
file_peersrpc_peers_proto_rawDescOnce.Do(func() {
file_peersrpc_peers_proto_rawDescData = protoimpl.X.CompressGZIP(file_peersrpc_peers_proto_rawDescData)
})
return file_peersrpc_peers_proto_rawDescData
}
var file_peersrpc_peers_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_peersrpc_peers_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_peersrpc_peers_proto_goTypes = []interface{}{
(UpdateAction)(0), // 0: peersrpc.UpdateAction
(FeatureSet)(0), // 1: peersrpc.FeatureSet
(*UpdateAddressAction)(nil), // 2: peersrpc.UpdateAddressAction
(*UpdateFeatureAction)(nil), // 3: peersrpc.UpdateFeatureAction
(*NodeAnnouncementUpdateRequest)(nil), // 4: peersrpc.NodeAnnouncementUpdateRequest
(*NodeAnnouncementUpdateResponse)(nil), // 5: peersrpc.NodeAnnouncementUpdateResponse
(lnrpc.FeatureBit)(0), // 6: lnrpc.FeatureBit
(*lnrpc.Op)(nil), // 7: lnrpc.Op
}
var file_peersrpc_peers_proto_depIdxs = []int32{
0, // 0: peersrpc.UpdateAddressAction.action:type_name -> peersrpc.UpdateAction
0, // 1: peersrpc.UpdateFeatureAction.action:type_name -> peersrpc.UpdateAction
6, // 2: peersrpc.UpdateFeatureAction.feature_bit:type_name -> lnrpc.FeatureBit
3, // 3: peersrpc.NodeAnnouncementUpdateRequest.feature_updates:type_name -> peersrpc.UpdateFeatureAction
2, // 4: peersrpc.NodeAnnouncementUpdateRequest.address_updates:type_name -> peersrpc.UpdateAddressAction
7, // 5: peersrpc.NodeAnnouncementUpdateResponse.ops:type_name -> lnrpc.Op
4, // 6: peersrpc.Peers.UpdateNodeAnnouncement:input_type -> peersrpc.NodeAnnouncementUpdateRequest
5, // 7: peersrpc.Peers.UpdateNodeAnnouncement:output_type -> peersrpc.NodeAnnouncementUpdateResponse
7, // [7:8] is the sub-list for method output_type
6, // [6:7] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
}
func init() { file_peersrpc_peers_proto_init() }
func file_peersrpc_peers_proto_init() {
if File_peersrpc_peers_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_peersrpc_peers_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UpdateAddressAction); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_peersrpc_peers_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UpdateFeatureAction); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_peersrpc_peers_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*NodeAnnouncementUpdateRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_peersrpc_peers_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*NodeAnnouncementUpdateResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_peersrpc_peers_proto_rawDesc,
NumEnums: 2,
NumMessages: 4,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_peersrpc_peers_proto_goTypes,
DependencyIndexes: file_peersrpc_peers_proto_depIdxs,
EnumInfos: file_peersrpc_peers_proto_enumTypes,
MessageInfos: file_peersrpc_peers_proto_msgTypes,
}.Build()
File_peersrpc_peers_proto = out.File
file_peersrpc_peers_proto_rawDesc = nil
file_peersrpc_peers_proto_goTypes = nil
file_peersrpc_peers_proto_depIdxs = nil
}
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: peersrpc/peers.proto
/*
Package peersrpc is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package peersrpc
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
func request_Peers_UpdateNodeAnnouncement_0(ctx context.Context, marshaler runtime.Marshaler, client PeersClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq NodeAnnouncementUpdateRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.UpdateNodeAnnouncement(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Peers_UpdateNodeAnnouncement_0(ctx context.Context, marshaler runtime.Marshaler, server PeersServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq NodeAnnouncementUpdateRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.UpdateNodeAnnouncement(ctx, &protoReq)
return msg, metadata, err
}
// RegisterPeersHandlerServer registers the http handlers for service Peers to "mux".
// UnaryRPC :call PeersServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterPeersHandlerFromEndpoint instead.
func RegisterPeersHandlerServer(ctx context.Context, mux *runtime.ServeMux, server PeersServer) error {
mux.Handle("POST", pattern_Peers_UpdateNodeAnnouncement_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/peersrpc.Peers/UpdateNodeAnnouncement", runtime.WithHTTPPathPattern("/v2/peers/nodeannouncement"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Peers_UpdateNodeAnnouncement_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Peers_UpdateNodeAnnouncement_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterPeersHandlerFromEndpoint is same as RegisterPeersHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterPeersHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterPeersHandler(ctx, mux, conn)
}
// RegisterPeersHandler registers the http handlers for service Peers to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterPeersHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterPeersHandlerClient(ctx, mux, NewPeersClient(conn))
}
// RegisterPeersHandlerClient registers the http handlers for service Peers
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "PeersClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "PeersClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "PeersClient" to call the correct interceptors.
func RegisterPeersHandlerClient(ctx context.Context, mux *runtime.ServeMux, client PeersClient) error {
mux.Handle("POST", pattern_Peers_UpdateNodeAnnouncement_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/peersrpc.Peers/UpdateNodeAnnouncement", runtime.WithHTTPPathPattern("/v2/peers/nodeannouncement"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Peers_UpdateNodeAnnouncement_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Peers_UpdateNodeAnnouncement_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_Peers_UpdateNodeAnnouncement_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "peers", "nodeannouncement"}, ""))
)
var (
forward_Peers_UpdateNodeAnnouncement_0 = runtime.ForwardResponseMessage
)
// Code generated by falafel 0.9.2. DO NOT EDIT.
// source: peers.proto
package peersrpc
import (
"context"
gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protojson"
)
func RegisterPeersJSONCallbacks(registry map[string]func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error))) {
marshaler := &gateway.JSONPb{
MarshalOptions: protojson.MarshalOptions{
UseProtoNames: true,
EmitUnpopulated: true,
},
}
registry["peersrpc.Peers.UpdateNodeAnnouncement"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &NodeAnnouncementUpdateRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewPeersClient(conn)
resp, err := client.UpdateNodeAnnouncement(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package peersrpc
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// PeersClient is the client API for Peers service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type PeersClient interface {
// lncli: peers updatenodeannouncement
// UpdateNodeAnnouncement allows the caller to update the node parameters
// and broadcasts a new version of the node announcement to its peers.
UpdateNodeAnnouncement(ctx context.Context, in *NodeAnnouncementUpdateRequest, opts ...grpc.CallOption) (*NodeAnnouncementUpdateResponse, error)
}
type peersClient struct {
cc grpc.ClientConnInterface
}
func NewPeersClient(cc grpc.ClientConnInterface) PeersClient {
return &peersClient{cc}
}
func (c *peersClient) UpdateNodeAnnouncement(ctx context.Context, in *NodeAnnouncementUpdateRequest, opts ...grpc.CallOption) (*NodeAnnouncementUpdateResponse, error) {
out := new(NodeAnnouncementUpdateResponse)
err := c.cc.Invoke(ctx, "/peersrpc.Peers/UpdateNodeAnnouncement", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// PeersServer is the server API for Peers service.
// All implementations must embed UnimplementedPeersServer
// for forward compatibility
type PeersServer interface {
// lncli: peers updatenodeannouncement
// UpdateNodeAnnouncement allows the caller to update the node parameters
// and broadcasts a new version of the node announcement to its peers.
UpdateNodeAnnouncement(context.Context, *NodeAnnouncementUpdateRequest) (*NodeAnnouncementUpdateResponse, error)
mustEmbedUnimplementedPeersServer()
}
// UnimplementedPeersServer must be embedded to have forward compatible implementations.
type UnimplementedPeersServer struct {
}
func (UnimplementedPeersServer) UpdateNodeAnnouncement(context.Context, *NodeAnnouncementUpdateRequest) (*NodeAnnouncementUpdateResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateNodeAnnouncement not implemented")
}
func (UnimplementedPeersServer) mustEmbedUnimplementedPeersServer() {}
// UnsafePeersServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to PeersServer will
// result in compilation errors.
type UnsafePeersServer interface {
mustEmbedUnimplementedPeersServer()
}
func RegisterPeersServer(s grpc.ServiceRegistrar, srv PeersServer) {
s.RegisterService(&Peers_ServiceDesc, srv)
}
func _Peers_UpdateNodeAnnouncement_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(NodeAnnouncementUpdateRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PeersServer).UpdateNodeAnnouncement(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/peersrpc.Peers/UpdateNodeAnnouncement",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PeersServer).UpdateNodeAnnouncement(ctx, req.(*NodeAnnouncementUpdateRequest))
}
return interceptor(ctx, in, info, handler)
}
// Peers_ServiceDesc is the grpc.ServiceDesc for Peers service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Peers_ServiceDesc = grpc.ServiceDesc{
ServiceName: "peersrpc.Peers",
HandlerType: (*PeersServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "UpdateNodeAnnouncement",
Handler: _Peers_UpdateNodeAnnouncement_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "peersrpc/peers.proto",
}
package routerrpc
import (
"github.com/lightningnetwork/lnd/aliasmgr"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/lightningnetwork/lnd/routing"
)
// Config is the main configuration file for the router RPC server. It contains
// all the items required for the router RPC server to carry out its duties.
// The fields with struct tags are meant to be parsed as normal configuration
// options, while if able to be populated, the latter fields MUST also be
// specified.
//
//nolint:ll
type Config struct {
RoutingConfig
// UseStatusInitiated is a boolean that indicates whether the router
// should use the new status code `Payment_INITIATED`.
//
// TODO(yy): remove this config after the new status code is fully
// deployed to the network(v0.20.0).
UseStatusInitiated bool `long:"usestatusinitiated" description:"If true, the router will send Payment_INITIATED for new payments, otherwise Payment_In_FLIGHT will be sent for compatibility concerns."`
// RouterMacPath is the path for the router macaroon. If unspecified
// then we assume that the macaroon will be found under the network
// directory, named DefaultRouterMacFilename.
RouterMacPath string `long:"routermacaroonpath" description:"Path to the router macaroon"`
// NetworkDir is the main network directory wherein the router rpc
// server will find the macaroon named DefaultRouterMacFilename.
NetworkDir string
// MacService is the main macaroon service that we'll use to handle
// authentication for the Router rpc server.
MacService *macaroons.Service
// Router is the main channel router instance that backs this RPC
// server.
//
// TODO(roasbeef): make into pkg lvl interface?
//
// TODO(roasbeef): assumes router handles saving payment state
Router *routing.ChannelRouter
// RouterBackend contains shared logic between this sub server and the
// main rpc server.
RouterBackend *RouterBackend
// AliasMgr is the alias manager instance that is used to handle all the
// SCID alias related information for channels.
AliasMgr *aliasmgr.Manager
}
// DefaultConfig defines the config defaults.
func DefaultConfig() *Config {
defaultRoutingConfig := RoutingConfig{
ProbabilityEstimatorType: routing.DefaultEstimator,
MinRouteProbability: routing.DefaultMinRouteProbability,
AttemptCost: routing.DefaultAttemptCost.ToSatoshis(),
AttemptCostPPM: routing.DefaultAttemptCostPPM,
MaxMcHistory: routing.DefaultMaxMcHistory,
McFlushInterval: routing.DefaultMcFlushInterval,
AprioriConfig: &AprioriConfig{
HopProbability: routing.DefaultAprioriHopProbability,
Weight: routing.DefaultAprioriWeight,
PenaltyHalfLife: routing.DefaultPenaltyHalfLife,
CapacityFraction: routing.DefaultCapacityFraction,
},
BimodalConfig: &BimodalConfig{
Scale: int64(routing.DefaultBimodalScaleMsat),
NodeWeight: routing.DefaultBimodalNodeWeight,
DecayTime: routing.DefaultBimodalDecayTime,
},
FeeEstimationTimeout: routing.DefaultFeeEstimationTimeout,
}
return &Config{
RoutingConfig: defaultRoutingConfig,
}
}
// GetRoutingConfig returns the routing config based on this sub server config.
func GetRoutingConfig(cfg *Config) *RoutingConfig {
return &RoutingConfig{
ProbabilityEstimatorType: cfg.ProbabilityEstimatorType,
MinRouteProbability: cfg.MinRouteProbability,
AttemptCost: cfg.AttemptCost,
AttemptCostPPM: cfg.AttemptCostPPM,
MaxMcHistory: cfg.MaxMcHistory,
McFlushInterval: cfg.McFlushInterval,
AprioriConfig: &AprioriConfig{
HopProbability: cfg.AprioriConfig.HopProbability,
Weight: cfg.AprioriConfig.Weight,
PenaltyHalfLife: cfg.AprioriConfig.PenaltyHalfLife,
CapacityFraction: cfg.AprioriConfig.CapacityFraction,
},
BimodalConfig: &BimodalConfig{
Scale: cfg.BimodalConfig.Scale,
NodeWeight: cfg.BimodalConfig.NodeWeight,
DecayTime: cfg.BimodalConfig.DecayTime,
},
FeeEstimationTimeout: cfg.FeeEstimationTimeout,
}
}
package routerrpc
import (
"fmt"
"github.com/lightningnetwork/lnd/lnrpc"
)
// createNewSubServer is a helper method that will create the new router sub
// server given the main config dispatcher method. If we're unable to find the
// config that is meant for us in the config dispatcher, then we'll exit with
// an error.
func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) (
*Server, lnrpc.MacaroonPerms, error) {
// We'll attempt to look up the config that we expect, according to our
// subServerName name. If we can't find this, then we'll exit with an
// error, as we're unable to properly initialize ourselves without this
// config.
routeServerConf, ok := configRegistry.FetchConfig(subServerName)
if !ok {
return nil, nil, fmt.Errorf("unable to find config for "+
"subserver type %s", subServerName)
}
// Now that we've found an object mapping to our service name, we'll
// ensure that it's the type we need.
config, ok := routeServerConf.(*Config)
if !ok {
return nil, nil, fmt.Errorf("wrong type of config for "+
"subserver %s, expected %T got %T", subServerName,
&Config{}, routeServerConf)
}
// Before we try to make the new router service instance, we'll perform
// some sanity checks on the arguments to ensure that they're usable.
switch {
case config.Router == nil:
return nil, nil, fmt.Errorf("Router must be set to create " +
"Routerpc")
}
return New(config)
}
func init() {
subServer := &lnrpc.SubServerDriver{
SubServerName: subServerName,
NewGrpcHandler: func() lnrpc.GrpcHandler {
return &ServerShell{}
},
}
// If the build tag is active, then we'll register ourselves as a
// sub-RPC server within the global lnrpc package namespace.
if err := lnrpc.RegisterSubServer(subServer); err != nil {
panic(fmt.Sprintf("failed to register sub server driver '%s': %v",
subServerName, err))
}
}
package routerrpc
import (
"errors"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwire"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
var (
// ErrFwdNotExists is an error returned when the caller tries to resolve
// a forward that doesn't exist anymore.
ErrFwdNotExists = errors.New("forward does not exist")
// ErrMissingPreimage is an error returned when the caller tries to settle
// a forward and doesn't provide a preimage.
ErrMissingPreimage = errors.New("missing preimage")
)
// forwardInterceptor is a helper struct that handles the lifecycle of an RPC
// interceptor streaming session.
// It is created when the stream opens and disconnects when the stream closes.
type forwardInterceptor struct {
// stream is the bidirectional RPC stream
stream Router_HtlcInterceptorServer
htlcSwitch htlcswitch.InterceptableHtlcForwarder
}
// newForwardInterceptor creates a new forwardInterceptor.
func newForwardInterceptor(htlcSwitch htlcswitch.InterceptableHtlcForwarder,
stream Router_HtlcInterceptorServer) *forwardInterceptor {
return &forwardInterceptor{
htlcSwitch: htlcSwitch,
stream: stream,
}
}
// run sends the intercepted packets to the client and receives the
// corresponding responses. On one hand it registered itself as an interceptor
// that receives the switch packets and on the other hand launches a go routine
// to read from the client stream.
// To coordinate all this and make sure it is safe for concurrent access all
// packets are sent to the main where they are handled.
func (r *forwardInterceptor) run() error {
// Register our interceptor so we receive all forwarded packets.
r.htlcSwitch.SetInterceptor(r.onIntercept)
defer r.htlcSwitch.SetInterceptor(nil)
for {
resp, err := r.stream.Recv()
if err != nil {
return err
}
log.Tracef("Received packet from stream: %v",
lnutils.SpewLogClosure(resp))
if err := r.resolveFromClient(resp); err != nil {
return err
}
}
}
// onIntercept is the function that is called by the switch for every forwarded
// packet. Our interceptor makes sure we hold the packet and then signal to the
// main loop to handle the packet. We only return true if we were able
// to deliver the packet to the main loop.
func (r *forwardInterceptor) onIntercept(
htlc htlcswitch.InterceptedPacket) error {
log.Tracef("Sending intercepted packet to client %v",
lnutils.SpewLogClosure(htlc))
inKey := htlc.IncomingCircuit
// First hold the forward, then send to client.
interceptionRequest := &ForwardHtlcInterceptRequest{
IncomingCircuitKey: &CircuitKey{
ChanId: inKey.ChanID.ToUint64(),
HtlcId: inKey.HtlcID,
},
OutgoingRequestedChanId: htlc.OutgoingChanID.ToUint64(),
PaymentHash: htlc.Hash[:],
OutgoingAmountMsat: uint64(htlc.OutgoingAmount),
OutgoingExpiry: htlc.OutgoingExpiry,
IncomingAmountMsat: uint64(htlc.IncomingAmount),
IncomingExpiry: htlc.IncomingExpiry,
CustomRecords: htlc.InOnionCustomRecords,
OnionBlob: htlc.OnionBlob[:],
AutoFailHeight: htlc.AutoFailHeight,
InWireCustomRecords: htlc.InWireCustomRecords,
}
return r.stream.Send(interceptionRequest)
}
// resolveFromClient handles a resolution arrived from the client.
func (r *forwardInterceptor) resolveFromClient(
in *ForwardHtlcInterceptResponse) error {
if in.IncomingCircuitKey == nil {
return status.Errorf(codes.InvalidArgument,
"CircuitKey missing from ForwardHtlcInterceptResponse")
}
log.Tracef("Resolving intercepted packet %v", in)
circuitKey := models.CircuitKey{
ChanID: lnwire.NewShortChanIDFromInt(
in.IncomingCircuitKey.ChanId,
),
HtlcID: in.IncomingCircuitKey.HtlcId,
}
switch in.Action {
case ResolveHoldForwardAction_RESUME:
return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{
Key: circuitKey,
Action: htlcswitch.FwdActionResume,
})
case ResolveHoldForwardAction_RESUME_MODIFIED:
// Modify HTLC and resume forward.
inAmtMsat := fn.None[lnwire.MilliSatoshi]()
if in.InAmountMsat > 0 {
inAmtMsat = fn.Some(lnwire.MilliSatoshi(
in.InAmountMsat,
))
}
outAmtMsat := fn.None[lnwire.MilliSatoshi]()
if in.OutAmountMsat > 0 {
outAmtMsat = fn.Some(lnwire.MilliSatoshi(
in.OutAmountMsat,
))
}
outWireCustomRecords := fn.None[lnwire.CustomRecords]()
if len(in.OutWireCustomRecords) > 0 {
// Validate custom records.
cr := lnwire.CustomRecords(in.OutWireCustomRecords)
if err := cr.Validate(); err != nil {
return status.Errorf(
codes.InvalidArgument,
"failed to validate custom records: %v",
err,
)
}
outWireCustomRecords = fn.Some[lnwire.CustomRecords](cr)
}
//nolint:ll
return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{
Key: circuitKey,
Action: htlcswitch.FwdActionResumeModified,
InAmountMsat: inAmtMsat,
OutAmountMsat: outAmtMsat,
OutWireCustomRecords: outWireCustomRecords,
})
case ResolveHoldForwardAction_FAIL:
// Fail with an encrypted reason.
if in.FailureMessage != nil {
if in.FailureCode != 0 {
return status.Errorf(
codes.InvalidArgument,
"failure message and failure code "+
"are mutually exclusive",
)
}
// Verify that the size is equal to the fixed failure
// message size + hmac + two uint16 lengths. See BOLT
// #4.
if len(in.FailureMessage) !=
lnwire.FailureMessageLength+32+2+2 {
return status.Errorf(
codes.InvalidArgument,
"failure message length invalid",
)
}
return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{
Key: circuitKey,
Action: htlcswitch.FwdActionFail,
FailureMessage: in.FailureMessage,
})
}
var code lnwire.FailCode
switch in.FailureCode {
case lnrpc.Failure_INVALID_ONION_HMAC:
code = lnwire.CodeInvalidOnionHmac
case lnrpc.Failure_INVALID_ONION_KEY:
code = lnwire.CodeInvalidOnionKey
case lnrpc.Failure_INVALID_ONION_VERSION:
code = lnwire.CodeInvalidOnionVersion
// Default to TemporaryChannelFailure.
case 0, lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE:
code = lnwire.CodeTemporaryChannelFailure
default:
return status.Errorf(
codes.InvalidArgument,
"unsupported failure code: %v", in.FailureCode,
)
}
return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{
Key: circuitKey,
Action: htlcswitch.FwdActionFail,
FailureCode: code,
})
case ResolveHoldForwardAction_SETTLE:
if in.Preimage == nil {
return ErrMissingPreimage
}
preimage, err := lntypes.MakePreimage(in.Preimage)
if err != nil {
return err
}
return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{
Key: circuitKey,
Action: htlcswitch.FwdActionSettle,
Preimage: preimage,
})
default:
return status.Errorf(
codes.InvalidArgument,
"unrecognized resolve action %v", in.Action,
)
}
}
package routerrpc
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// Subsystem defines the logging code for this subsystem.
const Subsystem = "RRPC"
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v3.21.12
// source: routerrpc/router.proto
package routerrpc
import (
lnrpc "github.com/lightningnetwork/lnd/lnrpc"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type FailureDetail int32
const (
FailureDetail_UNKNOWN FailureDetail = 0
FailureDetail_NO_DETAIL FailureDetail = 1
FailureDetail_ONION_DECODE FailureDetail = 2
FailureDetail_LINK_NOT_ELIGIBLE FailureDetail = 3
FailureDetail_ON_CHAIN_TIMEOUT FailureDetail = 4
FailureDetail_HTLC_EXCEEDS_MAX FailureDetail = 5
FailureDetail_INSUFFICIENT_BALANCE FailureDetail = 6
FailureDetail_INCOMPLETE_FORWARD FailureDetail = 7
FailureDetail_HTLC_ADD_FAILED FailureDetail = 8
FailureDetail_FORWARDS_DISABLED FailureDetail = 9
FailureDetail_INVOICE_CANCELED FailureDetail = 10
FailureDetail_INVOICE_UNDERPAID FailureDetail = 11
FailureDetail_INVOICE_EXPIRY_TOO_SOON FailureDetail = 12
FailureDetail_INVOICE_NOT_OPEN FailureDetail = 13
FailureDetail_MPP_INVOICE_TIMEOUT FailureDetail = 14
FailureDetail_ADDRESS_MISMATCH FailureDetail = 15
FailureDetail_SET_TOTAL_MISMATCH FailureDetail = 16
FailureDetail_SET_TOTAL_TOO_LOW FailureDetail = 17
FailureDetail_SET_OVERPAID FailureDetail = 18
FailureDetail_UNKNOWN_INVOICE FailureDetail = 19
FailureDetail_INVALID_KEYSEND FailureDetail = 20
FailureDetail_MPP_IN_PROGRESS FailureDetail = 21
FailureDetail_CIRCULAR_ROUTE FailureDetail = 22
)
// Enum value maps for FailureDetail.
var (
FailureDetail_name = map[int32]string{
0: "UNKNOWN",
1: "NO_DETAIL",
2: "ONION_DECODE",
3: "LINK_NOT_ELIGIBLE",
4: "ON_CHAIN_TIMEOUT",
5: "HTLC_EXCEEDS_MAX",
6: "INSUFFICIENT_BALANCE",
7: "INCOMPLETE_FORWARD",
8: "HTLC_ADD_FAILED",
9: "FORWARDS_DISABLED",
10: "INVOICE_CANCELED",
11: "INVOICE_UNDERPAID",
12: "INVOICE_EXPIRY_TOO_SOON",
13: "INVOICE_NOT_OPEN",
14: "MPP_INVOICE_TIMEOUT",
15: "ADDRESS_MISMATCH",
16: "SET_TOTAL_MISMATCH",
17: "SET_TOTAL_TOO_LOW",
18: "SET_OVERPAID",
19: "UNKNOWN_INVOICE",
20: "INVALID_KEYSEND",
21: "MPP_IN_PROGRESS",
22: "CIRCULAR_ROUTE",
}
FailureDetail_value = map[string]int32{
"UNKNOWN": 0,
"NO_DETAIL": 1,
"ONION_DECODE": 2,
"LINK_NOT_ELIGIBLE": 3,
"ON_CHAIN_TIMEOUT": 4,
"HTLC_EXCEEDS_MAX": 5,
"INSUFFICIENT_BALANCE": 6,
"INCOMPLETE_FORWARD": 7,
"HTLC_ADD_FAILED": 8,
"FORWARDS_DISABLED": 9,
"INVOICE_CANCELED": 10,
"INVOICE_UNDERPAID": 11,
"INVOICE_EXPIRY_TOO_SOON": 12,
"INVOICE_NOT_OPEN": 13,
"MPP_INVOICE_TIMEOUT": 14,
"ADDRESS_MISMATCH": 15,
"SET_TOTAL_MISMATCH": 16,
"SET_TOTAL_TOO_LOW": 17,
"SET_OVERPAID": 18,
"UNKNOWN_INVOICE": 19,
"INVALID_KEYSEND": 20,
"MPP_IN_PROGRESS": 21,
"CIRCULAR_ROUTE": 22,
}
)
func (x FailureDetail) Enum() *FailureDetail {
p := new(FailureDetail)
*p = x
return p
}
func (x FailureDetail) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (FailureDetail) Descriptor() protoreflect.EnumDescriptor {
return file_routerrpc_router_proto_enumTypes[0].Descriptor()
}
func (FailureDetail) Type() protoreflect.EnumType {
return &file_routerrpc_router_proto_enumTypes[0]
}
func (x FailureDetail) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use FailureDetail.Descriptor instead.
func (FailureDetail) EnumDescriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{0}
}
type PaymentState int32
const (
// Payment is still in flight.
PaymentState_IN_FLIGHT PaymentState = 0
// Payment completed successfully.
PaymentState_SUCCEEDED PaymentState = 1
// There are more routes to try, but the payment timeout was exceeded.
PaymentState_FAILED_TIMEOUT PaymentState = 2
// All possible routes were tried and failed permanently. Or were no
// routes to the destination at all.
PaymentState_FAILED_NO_ROUTE PaymentState = 3
// A non-recoverable error has occurred.
PaymentState_FAILED_ERROR PaymentState = 4
// Payment details incorrect (unknown hash, invalid amt or
// invalid final cltv delta)
PaymentState_FAILED_INCORRECT_PAYMENT_DETAILS PaymentState = 5
// Insufficient local balance.
PaymentState_FAILED_INSUFFICIENT_BALANCE PaymentState = 6
)
// Enum value maps for PaymentState.
var (
PaymentState_name = map[int32]string{
0: "IN_FLIGHT",
1: "SUCCEEDED",
2: "FAILED_TIMEOUT",
3: "FAILED_NO_ROUTE",
4: "FAILED_ERROR",
5: "FAILED_INCORRECT_PAYMENT_DETAILS",
6: "FAILED_INSUFFICIENT_BALANCE",
}
PaymentState_value = map[string]int32{
"IN_FLIGHT": 0,
"SUCCEEDED": 1,
"FAILED_TIMEOUT": 2,
"FAILED_NO_ROUTE": 3,
"FAILED_ERROR": 4,
"FAILED_INCORRECT_PAYMENT_DETAILS": 5,
"FAILED_INSUFFICIENT_BALANCE": 6,
}
)
func (x PaymentState) Enum() *PaymentState {
p := new(PaymentState)
*p = x
return p
}
func (x PaymentState) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (PaymentState) Descriptor() protoreflect.EnumDescriptor {
return file_routerrpc_router_proto_enumTypes[1].Descriptor()
}
func (PaymentState) Type() protoreflect.EnumType {
return &file_routerrpc_router_proto_enumTypes[1]
}
func (x PaymentState) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use PaymentState.Descriptor instead.
func (PaymentState) EnumDescriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{1}
}
type ResolveHoldForwardAction int32
const (
// SETTLE is an action that is used to settle an HTLC instead of forwarding
// it.
ResolveHoldForwardAction_SETTLE ResolveHoldForwardAction = 0
// FAIL is an action that is used to fail an HTLC backwards.
ResolveHoldForwardAction_FAIL ResolveHoldForwardAction = 1
// RESUME is an action that is used to resume a forward HTLC.
ResolveHoldForwardAction_RESUME ResolveHoldForwardAction = 2
// RESUME_MODIFIED is an action that is used to resume a hold forward HTLC
// with modifications specified during interception.
ResolveHoldForwardAction_RESUME_MODIFIED ResolveHoldForwardAction = 3
)
// Enum value maps for ResolveHoldForwardAction.
var (
ResolveHoldForwardAction_name = map[int32]string{
0: "SETTLE",
1: "FAIL",
2: "RESUME",
3: "RESUME_MODIFIED",
}
ResolveHoldForwardAction_value = map[string]int32{
"SETTLE": 0,
"FAIL": 1,
"RESUME": 2,
"RESUME_MODIFIED": 3,
}
)
func (x ResolveHoldForwardAction) Enum() *ResolveHoldForwardAction {
p := new(ResolveHoldForwardAction)
*p = x
return p
}
func (x ResolveHoldForwardAction) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (ResolveHoldForwardAction) Descriptor() protoreflect.EnumDescriptor {
return file_routerrpc_router_proto_enumTypes[2].Descriptor()
}
func (ResolveHoldForwardAction) Type() protoreflect.EnumType {
return &file_routerrpc_router_proto_enumTypes[2]
}
func (x ResolveHoldForwardAction) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use ResolveHoldForwardAction.Descriptor instead.
func (ResolveHoldForwardAction) EnumDescriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{2}
}
type ChanStatusAction int32
const (
ChanStatusAction_ENABLE ChanStatusAction = 0
ChanStatusAction_DISABLE ChanStatusAction = 1
ChanStatusAction_AUTO ChanStatusAction = 2
)
// Enum value maps for ChanStatusAction.
var (
ChanStatusAction_name = map[int32]string{
0: "ENABLE",
1: "DISABLE",
2: "AUTO",
}
ChanStatusAction_value = map[string]int32{
"ENABLE": 0,
"DISABLE": 1,
"AUTO": 2,
}
)
func (x ChanStatusAction) Enum() *ChanStatusAction {
p := new(ChanStatusAction)
*p = x
return p
}
func (x ChanStatusAction) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (ChanStatusAction) Descriptor() protoreflect.EnumDescriptor {
return file_routerrpc_router_proto_enumTypes[3].Descriptor()
}
func (ChanStatusAction) Type() protoreflect.EnumType {
return &file_routerrpc_router_proto_enumTypes[3]
}
func (x ChanStatusAction) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use ChanStatusAction.Descriptor instead.
func (ChanStatusAction) EnumDescriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{3}
}
type MissionControlConfig_ProbabilityModel int32
const (
MissionControlConfig_APRIORI MissionControlConfig_ProbabilityModel = 0
MissionControlConfig_BIMODAL MissionControlConfig_ProbabilityModel = 1
)
// Enum value maps for MissionControlConfig_ProbabilityModel.
var (
MissionControlConfig_ProbabilityModel_name = map[int32]string{
0: "APRIORI",
1: "BIMODAL",
}
MissionControlConfig_ProbabilityModel_value = map[string]int32{
"APRIORI": 0,
"BIMODAL": 1,
}
)
func (x MissionControlConfig_ProbabilityModel) Enum() *MissionControlConfig_ProbabilityModel {
p := new(MissionControlConfig_ProbabilityModel)
*p = x
return p
}
func (x MissionControlConfig_ProbabilityModel) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (MissionControlConfig_ProbabilityModel) Descriptor() protoreflect.EnumDescriptor {
return file_routerrpc_router_proto_enumTypes[4].Descriptor()
}
func (MissionControlConfig_ProbabilityModel) Type() protoreflect.EnumType {
return &file_routerrpc_router_proto_enumTypes[4]
}
func (x MissionControlConfig_ProbabilityModel) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use MissionControlConfig_ProbabilityModel.Descriptor instead.
func (MissionControlConfig_ProbabilityModel) EnumDescriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{19, 0}
}
type HtlcEvent_EventType int32
const (
HtlcEvent_UNKNOWN HtlcEvent_EventType = 0
HtlcEvent_SEND HtlcEvent_EventType = 1
HtlcEvent_RECEIVE HtlcEvent_EventType = 2
HtlcEvent_FORWARD HtlcEvent_EventType = 3
)
// Enum value maps for HtlcEvent_EventType.
var (
HtlcEvent_EventType_name = map[int32]string{
0: "UNKNOWN",
1: "SEND",
2: "RECEIVE",
3: "FORWARD",
}
HtlcEvent_EventType_value = map[string]int32{
"UNKNOWN": 0,
"SEND": 1,
"RECEIVE": 2,
"FORWARD": 3,
}
)
func (x HtlcEvent_EventType) Enum() *HtlcEvent_EventType {
p := new(HtlcEvent_EventType)
*p = x
return p
}
func (x HtlcEvent_EventType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (HtlcEvent_EventType) Descriptor() protoreflect.EnumDescriptor {
return file_routerrpc_router_proto_enumTypes[5].Descriptor()
}
func (HtlcEvent_EventType) Type() protoreflect.EnumType {
return &file_routerrpc_router_proto_enumTypes[5]
}
func (x HtlcEvent_EventType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use HtlcEvent_EventType.Descriptor instead.
func (HtlcEvent_EventType) EnumDescriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{27, 0}
}
type SendPaymentRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The identity pubkey of the payment recipient
Dest []byte `protobuf:"bytes,1,opt,name=dest,proto3" json:"dest,omitempty"`
// Number of satoshis to send.
//
// The fields amt and amt_msat are mutually exclusive.
Amt int64 `protobuf:"varint,2,opt,name=amt,proto3" json:"amt,omitempty"`
// The hash to use within the payment's HTLC
PaymentHash []byte `protobuf:"bytes,3,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"`
// The CLTV delta from the current height that should be used to set the
// timelock for the final hop.
FinalCltvDelta int32 `protobuf:"varint,4,opt,name=final_cltv_delta,json=finalCltvDelta,proto3" json:"final_cltv_delta,omitempty"`
// A bare-bones invoice for a payment within the Lightning Network. With the
// details of the invoice, the sender has all the data necessary to send a
// payment to the recipient. The amount in the payment request may be zero. In
// that case it is required to set the amt field as well. If no payment request
// is specified, the following fields are required: dest, amt and payment_hash.
PaymentRequest string `protobuf:"bytes,5,opt,name=payment_request,json=paymentRequest,proto3" json:"payment_request,omitempty"`
// An optional limit, expressed in seconds, on the time to wait before
// attempting the first HTLC. Once HTLCs are in flight, the payment will
// not be aborted until the HTLCs are either settled or failed. If the field
// is not set or is explicitly set to zero, the default value of 60 seconds
// will be applied.
TimeoutSeconds int32 `protobuf:"varint,6,opt,name=timeout_seconds,json=timeoutSeconds,proto3" json:"timeout_seconds,omitempty"`
// The maximum number of satoshis that will be paid as a fee of the payment.
// If this field is left to the default value of 0, only zero-fee routes will
// be considered. This usually means single hop routes connecting directly to
// the destination. To send the payment without a fee limit, use max int here.
//
// The fields fee_limit_sat and fee_limit_msat are mutually exclusive.
FeeLimitSat int64 `protobuf:"varint,7,opt,name=fee_limit_sat,json=feeLimitSat,proto3" json:"fee_limit_sat,omitempty"`
// Deprecated, use outgoing_chan_ids. The channel id of the channel that must
// be taken to the first hop. If zero, any channel may be used (unless
// outgoing_chan_ids are set).
//
// Deprecated: Marked as deprecated in routerrpc/router.proto.
OutgoingChanId uint64 `protobuf:"varint,8,opt,name=outgoing_chan_id,json=outgoingChanId,proto3" json:"outgoing_chan_id,omitempty"`
// An optional maximum total time lock for the route. This should not
// exceed lnd's `--max-cltv-expiry` setting. If zero, then the value of
// `--max-cltv-expiry` is enforced.
CltvLimit int32 `protobuf:"varint,9,opt,name=cltv_limit,json=cltvLimit,proto3" json:"cltv_limit,omitempty"`
// Optional route hints to reach the destination through private channels.
RouteHints []*lnrpc.RouteHint `protobuf:"bytes,10,rep,name=route_hints,json=routeHints,proto3" json:"route_hints,omitempty"`
// An optional field that can be used to pass an arbitrary set of TLV records
// to a peer which understands the new records. This can be used to pass
// application specific data during the payment attempt. Record types are
// required to be in the custom range >= 65536. When using REST, the values
// must be encoded as base64.
DestCustomRecords map[uint64][]byte `protobuf:"bytes,11,rep,name=dest_custom_records,json=destCustomRecords,proto3" json:"dest_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
// Number of millisatoshis to send.
//
// The fields amt and amt_msat are mutually exclusive.
AmtMsat int64 `protobuf:"varint,12,opt,name=amt_msat,json=amtMsat,proto3" json:"amt_msat,omitempty"`
// The maximum number of millisatoshis that will be paid as a fee of the
// payment. If this field is left to the default value of 0, only zero-fee
// routes will be considered. This usually means single hop routes connecting
// directly to the destination. To send the payment without a fee limit, use
// max int here.
//
// The fields fee_limit_sat and fee_limit_msat are mutually exclusive.
FeeLimitMsat int64 `protobuf:"varint,13,opt,name=fee_limit_msat,json=feeLimitMsat,proto3" json:"fee_limit_msat,omitempty"`
// The pubkey of the last hop of the route. If empty, any hop may be used.
LastHopPubkey []byte `protobuf:"bytes,14,opt,name=last_hop_pubkey,json=lastHopPubkey,proto3" json:"last_hop_pubkey,omitempty"`
// If set, circular payments to self are permitted.
AllowSelfPayment bool `protobuf:"varint,15,opt,name=allow_self_payment,json=allowSelfPayment,proto3" json:"allow_self_payment,omitempty"`
// Features assumed to be supported by the final node. All transitive feature
// dependencies must also be set properly. For a given feature bit pair, either
// optional or remote may be set, but not both. If this field is nil or empty,
// the router will try to load destination features from the graph as a
// fallback.
DestFeatures []lnrpc.FeatureBit `protobuf:"varint,16,rep,packed,name=dest_features,json=destFeatures,proto3,enum=lnrpc.FeatureBit" json:"dest_features,omitempty"`
// The maximum number of partial payments that may be use to complete the full
// amount.
MaxParts uint32 `protobuf:"varint,17,opt,name=max_parts,json=maxParts,proto3" json:"max_parts,omitempty"`
// If set, only the final payment update is streamed back. Intermediate updates
// that show which htlcs are still in flight are suppressed.
NoInflightUpdates bool `protobuf:"varint,18,opt,name=no_inflight_updates,json=noInflightUpdates,proto3" json:"no_inflight_updates,omitempty"`
// The channel ids of the channels are allowed for the first hop. If empty,
// any channel may be used.
OutgoingChanIds []uint64 `protobuf:"varint,19,rep,packed,name=outgoing_chan_ids,json=outgoingChanIds,proto3" json:"outgoing_chan_ids,omitempty"`
// An optional payment addr to be included within the last hop of the route.
// This is also called payment secret in specifications (e.g. BOLT 11).
PaymentAddr []byte `protobuf:"bytes,20,opt,name=payment_addr,json=paymentAddr,proto3" json:"payment_addr,omitempty"`
// The largest payment split that should be attempted when making a payment if
// splitting is necessary. Setting this value will effectively cause lnd to
// split more aggressively, vs only when it thinks it needs to. Note that this
// value is in milli-satoshis.
MaxShardSizeMsat uint64 `protobuf:"varint,21,opt,name=max_shard_size_msat,json=maxShardSizeMsat,proto3" json:"max_shard_size_msat,omitempty"`
// If set, an AMP-payment will be attempted.
Amp bool `protobuf:"varint,22,opt,name=amp,proto3" json:"amp,omitempty"`
// The time preference for this payment. Set to -1 to optimize for fees
// only, to 1 to optimize for reliability only or a value inbetween for a mix.
TimePref float64 `protobuf:"fixed64,23,opt,name=time_pref,json=timePref,proto3" json:"time_pref,omitempty"`
// If set, the payment loop can be interrupted by manually canceling the
// payment context, even before the payment timeout is reached. Note that the
// payment may still succeed after cancellation, as in-flight attempts can
// still settle afterwards. Canceling will only prevent further attempts from
// being sent.
Cancelable bool `protobuf:"varint,24,opt,name=cancelable,proto3" json:"cancelable,omitempty"`
// An optional field that can be used to pass an arbitrary set of TLV records
// to the first hop peer of this payment. This can be used to pass application
// specific data during the payment attempt. Record types are required to be in
// the custom range >= 65536. When using REST, the values must be encoded as
// base64.
FirstHopCustomRecords map[uint64][]byte `protobuf:"bytes,25,rep,name=first_hop_custom_records,json=firstHopCustomRecords,proto3" json:"first_hop_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *SendPaymentRequest) Reset() {
*x = SendPaymentRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SendPaymentRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendPaymentRequest) ProtoMessage() {}
func (x *SendPaymentRequest) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendPaymentRequest.ProtoReflect.Descriptor instead.
func (*SendPaymentRequest) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{0}
}
func (x *SendPaymentRequest) GetDest() []byte {
if x != nil {
return x.Dest
}
return nil
}
func (x *SendPaymentRequest) GetAmt() int64 {
if x != nil {
return x.Amt
}
return 0
}
func (x *SendPaymentRequest) GetPaymentHash() []byte {
if x != nil {
return x.PaymentHash
}
return nil
}
func (x *SendPaymentRequest) GetFinalCltvDelta() int32 {
if x != nil {
return x.FinalCltvDelta
}
return 0
}
func (x *SendPaymentRequest) GetPaymentRequest() string {
if x != nil {
return x.PaymentRequest
}
return ""
}
func (x *SendPaymentRequest) GetTimeoutSeconds() int32 {
if x != nil {
return x.TimeoutSeconds
}
return 0
}
func (x *SendPaymentRequest) GetFeeLimitSat() int64 {
if x != nil {
return x.FeeLimitSat
}
return 0
}
// Deprecated: Marked as deprecated in routerrpc/router.proto.
func (x *SendPaymentRequest) GetOutgoingChanId() uint64 {
if x != nil {
return x.OutgoingChanId
}
return 0
}
func (x *SendPaymentRequest) GetCltvLimit() int32 {
if x != nil {
return x.CltvLimit
}
return 0
}
func (x *SendPaymentRequest) GetRouteHints() []*lnrpc.RouteHint {
if x != nil {
return x.RouteHints
}
return nil
}
func (x *SendPaymentRequest) GetDestCustomRecords() map[uint64][]byte {
if x != nil {
return x.DestCustomRecords
}
return nil
}
func (x *SendPaymentRequest) GetAmtMsat() int64 {
if x != nil {
return x.AmtMsat
}
return 0
}
func (x *SendPaymentRequest) GetFeeLimitMsat() int64 {
if x != nil {
return x.FeeLimitMsat
}
return 0
}
func (x *SendPaymentRequest) GetLastHopPubkey() []byte {
if x != nil {
return x.LastHopPubkey
}
return nil
}
func (x *SendPaymentRequest) GetAllowSelfPayment() bool {
if x != nil {
return x.AllowSelfPayment
}
return false
}
func (x *SendPaymentRequest) GetDestFeatures() []lnrpc.FeatureBit {
if x != nil {
return x.DestFeatures
}
return nil
}
func (x *SendPaymentRequest) GetMaxParts() uint32 {
if x != nil {
return x.MaxParts
}
return 0
}
func (x *SendPaymentRequest) GetNoInflightUpdates() bool {
if x != nil {
return x.NoInflightUpdates
}
return false
}
func (x *SendPaymentRequest) GetOutgoingChanIds() []uint64 {
if x != nil {
return x.OutgoingChanIds
}
return nil
}
func (x *SendPaymentRequest) GetPaymentAddr() []byte {
if x != nil {
return x.PaymentAddr
}
return nil
}
func (x *SendPaymentRequest) GetMaxShardSizeMsat() uint64 {
if x != nil {
return x.MaxShardSizeMsat
}
return 0
}
func (x *SendPaymentRequest) GetAmp() bool {
if x != nil {
return x.Amp
}
return false
}
func (x *SendPaymentRequest) GetTimePref() float64 {
if x != nil {
return x.TimePref
}
return 0
}
func (x *SendPaymentRequest) GetCancelable() bool {
if x != nil {
return x.Cancelable
}
return false
}
func (x *SendPaymentRequest) GetFirstHopCustomRecords() map[uint64][]byte {
if x != nil {
return x.FirstHopCustomRecords
}
return nil
}
type TrackPaymentRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The hash of the payment to look up.
PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"`
// If set, only the final payment update is streamed back. Intermediate updates
// that show which htlcs are still in flight are suppressed.
NoInflightUpdates bool `protobuf:"varint,2,opt,name=no_inflight_updates,json=noInflightUpdates,proto3" json:"no_inflight_updates,omitempty"`
}
func (x *TrackPaymentRequest) Reset() {
*x = TrackPaymentRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TrackPaymentRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TrackPaymentRequest) ProtoMessage() {}
func (x *TrackPaymentRequest) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TrackPaymentRequest.ProtoReflect.Descriptor instead.
func (*TrackPaymentRequest) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{1}
}
func (x *TrackPaymentRequest) GetPaymentHash() []byte {
if x != nil {
return x.PaymentHash
}
return nil
}
func (x *TrackPaymentRequest) GetNoInflightUpdates() bool {
if x != nil {
return x.NoInflightUpdates
}
return false
}
type TrackPaymentsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// If set, only the final payment updates are streamed back. Intermediate
// updates that show which htlcs are still in flight are suppressed.
NoInflightUpdates bool `protobuf:"varint,1,opt,name=no_inflight_updates,json=noInflightUpdates,proto3" json:"no_inflight_updates,omitempty"`
}
func (x *TrackPaymentsRequest) Reset() {
*x = TrackPaymentsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TrackPaymentsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TrackPaymentsRequest) ProtoMessage() {}
func (x *TrackPaymentsRequest) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TrackPaymentsRequest.ProtoReflect.Descriptor instead.
func (*TrackPaymentsRequest) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{2}
}
func (x *TrackPaymentsRequest) GetNoInflightUpdates() bool {
if x != nil {
return x.NoInflightUpdates
}
return false
}
type RouteFeeRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The destination one wishes to obtain a routing fee quote to. If set, this
// parameter requires the amt_sat parameter also to be set. This parameter
// combination triggers a graph based routing fee estimation as opposed to a
// payment probe based estimate in case a payment request is provided. The
// graph based estimation is an algorithm that is executed on the in memory
// graph. Hence its runtime is significantly shorter than a payment probe
// estimation that sends out actual payments to the network.
Dest []byte `protobuf:"bytes,1,opt,name=dest,proto3" json:"dest,omitempty"`
// The amount one wishes to send to the target destination. It is only to be
// used in combination with the dest parameter.
AmtSat int64 `protobuf:"varint,2,opt,name=amt_sat,json=amtSat,proto3" json:"amt_sat,omitempty"`
// A payment request of the target node that the route fee request is intended
// for. Its parameters are input to probe payments that estimate routing fees.
// The timeout parameter can be specified to set a maximum time on the probing
// attempt. Cannot be used in combination with dest and amt_sat.
PaymentRequest string `protobuf:"bytes,3,opt,name=payment_request,json=paymentRequest,proto3" json:"payment_request,omitempty"`
// A user preference of how long a probe payment should maximally be allowed to
// take, denoted in seconds. The probing payment loop is aborted if this
// timeout is reached. Note that the probing process itself can take longer
// than the timeout if the HTLC becomes delayed or stuck. Canceling the context
// of this call will not cancel the payment loop, the duration is only
// controlled by the timeout parameter.
Timeout uint32 `protobuf:"varint,4,opt,name=timeout,proto3" json:"timeout,omitempty"`
}
func (x *RouteFeeRequest) Reset() {
*x = RouteFeeRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RouteFeeRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RouteFeeRequest) ProtoMessage() {}
func (x *RouteFeeRequest) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RouteFeeRequest.ProtoReflect.Descriptor instead.
func (*RouteFeeRequest) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{3}
}
func (x *RouteFeeRequest) GetDest() []byte {
if x != nil {
return x.Dest
}
return nil
}
func (x *RouteFeeRequest) GetAmtSat() int64 {
if x != nil {
return x.AmtSat
}
return 0
}
func (x *RouteFeeRequest) GetPaymentRequest() string {
if x != nil {
return x.PaymentRequest
}
return ""
}
func (x *RouteFeeRequest) GetTimeout() uint32 {
if x != nil {
return x.Timeout
}
return 0
}
type RouteFeeResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A lower bound of the estimated fee to the target destination within the
// network, expressed in milli-satoshis.
RoutingFeeMsat int64 `protobuf:"varint,1,opt,name=routing_fee_msat,json=routingFeeMsat,proto3" json:"routing_fee_msat,omitempty"`
// An estimate of the worst case time delay that can occur. Note that callers
// will still need to factor in the final CLTV delta of the last hop into this
// value.
TimeLockDelay int64 `protobuf:"varint,2,opt,name=time_lock_delay,json=timeLockDelay,proto3" json:"time_lock_delay,omitempty"`
// An indication whether a probing payment succeeded or whether and why it
// failed. FAILURE_REASON_NONE indicates success.
FailureReason lnrpc.PaymentFailureReason `protobuf:"varint,5,opt,name=failure_reason,json=failureReason,proto3,enum=lnrpc.PaymentFailureReason" json:"failure_reason,omitempty"`
}
func (x *RouteFeeResponse) Reset() {
*x = RouteFeeResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RouteFeeResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RouteFeeResponse) ProtoMessage() {}
func (x *RouteFeeResponse) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RouteFeeResponse.ProtoReflect.Descriptor instead.
func (*RouteFeeResponse) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{4}
}
func (x *RouteFeeResponse) GetRoutingFeeMsat() int64 {
if x != nil {
return x.RoutingFeeMsat
}
return 0
}
func (x *RouteFeeResponse) GetTimeLockDelay() int64 {
if x != nil {
return x.TimeLockDelay
}
return 0
}
func (x *RouteFeeResponse) GetFailureReason() lnrpc.PaymentFailureReason {
if x != nil {
return x.FailureReason
}
return lnrpc.PaymentFailureReason(0)
}
type SendToRouteRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The payment hash to use for the HTLC.
PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"`
// Route that should be used to attempt to complete the payment.
Route *lnrpc.Route `protobuf:"bytes,2,opt,name=route,proto3" json:"route,omitempty"`
// Whether the payment should be marked as failed when a temporary error is
// returned from the given route. Set it to true so the payment won't be
// failed unless a terminal error is occurred, such as payment timeout, no
// routes, incorrect payment details, or insufficient funds.
SkipTempErr bool `protobuf:"varint,3,opt,name=skip_temp_err,json=skipTempErr,proto3" json:"skip_temp_err,omitempty"`
// An optional field that can be used to pass an arbitrary set of TLV records
// to the first hop peer of this payment. This can be used to pass application
// specific data during the payment attempt. Record types are required to be in
// the custom range >= 65536. When using REST, the values must be encoded as
// base64.
FirstHopCustomRecords map[uint64][]byte `protobuf:"bytes,4,rep,name=first_hop_custom_records,json=firstHopCustomRecords,proto3" json:"first_hop_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *SendToRouteRequest) Reset() {
*x = SendToRouteRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SendToRouteRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendToRouteRequest) ProtoMessage() {}
func (x *SendToRouteRequest) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendToRouteRequest.ProtoReflect.Descriptor instead.
func (*SendToRouteRequest) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{5}
}
func (x *SendToRouteRequest) GetPaymentHash() []byte {
if x != nil {
return x.PaymentHash
}
return nil
}
func (x *SendToRouteRequest) GetRoute() *lnrpc.Route {
if x != nil {
return x.Route
}
return nil
}
func (x *SendToRouteRequest) GetSkipTempErr() bool {
if x != nil {
return x.SkipTempErr
}
return false
}
func (x *SendToRouteRequest) GetFirstHopCustomRecords() map[uint64][]byte {
if x != nil {
return x.FirstHopCustomRecords
}
return nil
}
type SendToRouteResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The preimage obtained by making the payment.
Preimage []byte `protobuf:"bytes,1,opt,name=preimage,proto3" json:"preimage,omitempty"`
// The failure message in case the payment failed.
Failure *lnrpc.Failure `protobuf:"bytes,2,opt,name=failure,proto3" json:"failure,omitempty"`
}
func (x *SendToRouteResponse) Reset() {
*x = SendToRouteResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SendToRouteResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendToRouteResponse) ProtoMessage() {}
func (x *SendToRouteResponse) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendToRouteResponse.ProtoReflect.Descriptor instead.
func (*SendToRouteResponse) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{6}
}
func (x *SendToRouteResponse) GetPreimage() []byte {
if x != nil {
return x.Preimage
}
return nil
}
func (x *SendToRouteResponse) GetFailure() *lnrpc.Failure {
if x != nil {
return x.Failure
}
return nil
}
type ResetMissionControlRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ResetMissionControlRequest) Reset() {
*x = ResetMissionControlRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ResetMissionControlRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResetMissionControlRequest) ProtoMessage() {}
func (x *ResetMissionControlRequest) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ResetMissionControlRequest.ProtoReflect.Descriptor instead.
func (*ResetMissionControlRequest) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{7}
}
type ResetMissionControlResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ResetMissionControlResponse) Reset() {
*x = ResetMissionControlResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ResetMissionControlResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResetMissionControlResponse) ProtoMessage() {}
func (x *ResetMissionControlResponse) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ResetMissionControlResponse.ProtoReflect.Descriptor instead.
func (*ResetMissionControlResponse) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{8}
}
type QueryMissionControlRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *QueryMissionControlRequest) Reset() {
*x = QueryMissionControlRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *QueryMissionControlRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*QueryMissionControlRequest) ProtoMessage() {}
func (x *QueryMissionControlRequest) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use QueryMissionControlRequest.ProtoReflect.Descriptor instead.
func (*QueryMissionControlRequest) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{9}
}
// QueryMissionControlResponse contains mission control state.
type QueryMissionControlResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Node pair-level mission control state.
Pairs []*PairHistory `protobuf:"bytes,2,rep,name=pairs,proto3" json:"pairs,omitempty"`
}
func (x *QueryMissionControlResponse) Reset() {
*x = QueryMissionControlResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *QueryMissionControlResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*QueryMissionControlResponse) ProtoMessage() {}
func (x *QueryMissionControlResponse) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use QueryMissionControlResponse.ProtoReflect.Descriptor instead.
func (*QueryMissionControlResponse) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{10}
}
func (x *QueryMissionControlResponse) GetPairs() []*PairHistory {
if x != nil {
return x.Pairs
}
return nil
}
type XImportMissionControlRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Node pair-level mission control state to be imported.
Pairs []*PairHistory `protobuf:"bytes,1,rep,name=pairs,proto3" json:"pairs,omitempty"`
// Whether to force override MC pair history. Note that even with force
// override the failure pair is imported before the success pair and both
// still clamp existing failure/success amounts.
Force bool `protobuf:"varint,2,opt,name=force,proto3" json:"force,omitempty"`
}
func (x *XImportMissionControlRequest) Reset() {
*x = XImportMissionControlRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *XImportMissionControlRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*XImportMissionControlRequest) ProtoMessage() {}
func (x *XImportMissionControlRequest) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[11]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use XImportMissionControlRequest.ProtoReflect.Descriptor instead.
func (*XImportMissionControlRequest) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{11}
}
func (x *XImportMissionControlRequest) GetPairs() []*PairHistory {
if x != nil {
return x.Pairs
}
return nil
}
func (x *XImportMissionControlRequest) GetForce() bool {
if x != nil {
return x.Force
}
return false
}
type XImportMissionControlResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *XImportMissionControlResponse) Reset() {
*x = XImportMissionControlResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *XImportMissionControlResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*XImportMissionControlResponse) ProtoMessage() {}
func (x *XImportMissionControlResponse) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[12]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use XImportMissionControlResponse.ProtoReflect.Descriptor instead.
func (*XImportMissionControlResponse) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{12}
}
// PairHistory contains the mission control state for a particular node pair.
type PairHistory struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The source node pubkey of the pair.
NodeFrom []byte `protobuf:"bytes,1,opt,name=node_from,json=nodeFrom,proto3" json:"node_from,omitempty"`
// The destination node pubkey of the pair.
NodeTo []byte `protobuf:"bytes,2,opt,name=node_to,json=nodeTo,proto3" json:"node_to,omitempty"`
History *PairData `protobuf:"bytes,7,opt,name=history,proto3" json:"history,omitempty"`
}
func (x *PairHistory) Reset() {
*x = PairHistory{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PairHistory) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PairHistory) ProtoMessage() {}
func (x *PairHistory) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[13]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PairHistory.ProtoReflect.Descriptor instead.
func (*PairHistory) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{13}
}
func (x *PairHistory) GetNodeFrom() []byte {
if x != nil {
return x.NodeFrom
}
return nil
}
func (x *PairHistory) GetNodeTo() []byte {
if x != nil {
return x.NodeTo
}
return nil
}
func (x *PairHistory) GetHistory() *PairData {
if x != nil {
return x.History
}
return nil
}
type PairData struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Time of last failure.
FailTime int64 `protobuf:"varint,1,opt,name=fail_time,json=failTime,proto3" json:"fail_time,omitempty"`
// Lowest amount that failed to forward rounded to whole sats. This may be
// set to zero if the failure is independent of amount.
FailAmtSat int64 `protobuf:"varint,2,opt,name=fail_amt_sat,json=failAmtSat,proto3" json:"fail_amt_sat,omitempty"`
// Lowest amount that failed to forward in millisats. This may be
// set to zero if the failure is independent of amount.
FailAmtMsat int64 `protobuf:"varint,4,opt,name=fail_amt_msat,json=failAmtMsat,proto3" json:"fail_amt_msat,omitempty"`
// Time of last success.
SuccessTime int64 `protobuf:"varint,5,opt,name=success_time,json=successTime,proto3" json:"success_time,omitempty"`
// Highest amount that we could successfully forward rounded to whole sats.
SuccessAmtSat int64 `protobuf:"varint,6,opt,name=success_amt_sat,json=successAmtSat,proto3" json:"success_amt_sat,omitempty"`
// Highest amount that we could successfully forward in millisats.
SuccessAmtMsat int64 `protobuf:"varint,7,opt,name=success_amt_msat,json=successAmtMsat,proto3" json:"success_amt_msat,omitempty"`
}
func (x *PairData) Reset() {
*x = PairData{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PairData) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PairData) ProtoMessage() {}
func (x *PairData) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[14]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PairData.ProtoReflect.Descriptor instead.
func (*PairData) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{14}
}
func (x *PairData) GetFailTime() int64 {
if x != nil {
return x.FailTime
}
return 0
}
func (x *PairData) GetFailAmtSat() int64 {
if x != nil {
return x.FailAmtSat
}
return 0
}
func (x *PairData) GetFailAmtMsat() int64 {
if x != nil {
return x.FailAmtMsat
}
return 0
}
func (x *PairData) GetSuccessTime() int64 {
if x != nil {
return x.SuccessTime
}
return 0
}
func (x *PairData) GetSuccessAmtSat() int64 {
if x != nil {
return x.SuccessAmtSat
}
return 0
}
func (x *PairData) GetSuccessAmtMsat() int64 {
if x != nil {
return x.SuccessAmtMsat
}
return 0
}
type GetMissionControlConfigRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *GetMissionControlConfigRequest) Reset() {
*x = GetMissionControlConfigRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetMissionControlConfigRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetMissionControlConfigRequest) ProtoMessage() {}
func (x *GetMissionControlConfigRequest) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[15]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetMissionControlConfigRequest.ProtoReflect.Descriptor instead.
func (*GetMissionControlConfigRequest) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{15}
}
type GetMissionControlConfigResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Mission control's currently active config.
Config *MissionControlConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
}
func (x *GetMissionControlConfigResponse) Reset() {
*x = GetMissionControlConfigResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetMissionControlConfigResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetMissionControlConfigResponse) ProtoMessage() {}
func (x *GetMissionControlConfigResponse) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[16]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetMissionControlConfigResponse.ProtoReflect.Descriptor instead.
func (*GetMissionControlConfigResponse) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{16}
}
func (x *GetMissionControlConfigResponse) GetConfig() *MissionControlConfig {
if x != nil {
return x.Config
}
return nil
}
type SetMissionControlConfigRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The config to set for mission control. Note that all values *must* be set,
// because the full config will be applied.
Config *MissionControlConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
}
func (x *SetMissionControlConfigRequest) Reset() {
*x = SetMissionControlConfigRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SetMissionControlConfigRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SetMissionControlConfigRequest) ProtoMessage() {}
func (x *SetMissionControlConfigRequest) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[17]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SetMissionControlConfigRequest.ProtoReflect.Descriptor instead.
func (*SetMissionControlConfigRequest) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{17}
}
func (x *SetMissionControlConfigRequest) GetConfig() *MissionControlConfig {
if x != nil {
return x.Config
}
return nil
}
type SetMissionControlConfigResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *SetMissionControlConfigResponse) Reset() {
*x = SetMissionControlConfigResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SetMissionControlConfigResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SetMissionControlConfigResponse) ProtoMessage() {}
func (x *SetMissionControlConfigResponse) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[18]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SetMissionControlConfigResponse.ProtoReflect.Descriptor instead.
func (*SetMissionControlConfigResponse) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{18}
}
type MissionControlConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Deprecated, use AprioriParameters. The amount of time mission control will
// take to restore a penalized node or channel back to 50% success probability,
// expressed in seconds. Setting this value to a higher value will penalize
// failures for longer, making mission control less likely to route through
// nodes and channels that we have previously recorded failures for.
//
// Deprecated: Marked as deprecated in routerrpc/router.proto.
HalfLifeSeconds uint64 `protobuf:"varint,1,opt,name=half_life_seconds,json=halfLifeSeconds,proto3" json:"half_life_seconds,omitempty"`
// Deprecated, use AprioriParameters. The probability of success mission
// control should assign to hop in a route where it has no other information
// available. Higher values will make mission control more willing to try hops
// that we have no information about, lower values will discourage trying these
// hops.
//
// Deprecated: Marked as deprecated in routerrpc/router.proto.
HopProbability float32 `protobuf:"fixed32,2,opt,name=hop_probability,json=hopProbability,proto3" json:"hop_probability,omitempty"`
// Deprecated, use AprioriParameters. The importance that mission control
// should place on historical results, expressed as a value in [0;1]. Setting
// this value to 1 will ignore all historical payments and just use the hop
// probability to assess the probability of success for each hop. A zero value
// ignores hop probability completely and relies entirely on historical
// results, unless none are available.
//
// Deprecated: Marked as deprecated in routerrpc/router.proto.
Weight float32 `protobuf:"fixed32,3,opt,name=weight,proto3" json:"weight,omitempty"`
// The maximum number of payment results that mission control will store.
MaximumPaymentResults uint32 `protobuf:"varint,4,opt,name=maximum_payment_results,json=maximumPaymentResults,proto3" json:"maximum_payment_results,omitempty"`
// The minimum time that must have passed since the previously recorded failure
// before we raise the failure amount.
MinimumFailureRelaxInterval uint64 `protobuf:"varint,5,opt,name=minimum_failure_relax_interval,json=minimumFailureRelaxInterval,proto3" json:"minimum_failure_relax_interval,omitempty"`
// ProbabilityModel defines which probability estimator should be used in
// pathfinding. Note that the bimodal estimator is experimental.
Model MissionControlConfig_ProbabilityModel `protobuf:"varint,6,opt,name=model,proto3,enum=routerrpc.MissionControlConfig_ProbabilityModel" json:"model,omitempty"`
// EstimatorConfig is populated dependent on the estimator type.
//
// Types that are assignable to EstimatorConfig:
//
// *MissionControlConfig_Apriori
// *MissionControlConfig_Bimodal
EstimatorConfig isMissionControlConfig_EstimatorConfig `protobuf_oneof:"EstimatorConfig"`
}
func (x *MissionControlConfig) Reset() {
*x = MissionControlConfig{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[19]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MissionControlConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MissionControlConfig) ProtoMessage() {}
func (x *MissionControlConfig) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[19]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MissionControlConfig.ProtoReflect.Descriptor instead.
func (*MissionControlConfig) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{19}
}
// Deprecated: Marked as deprecated in routerrpc/router.proto.
func (x *MissionControlConfig) GetHalfLifeSeconds() uint64 {
if x != nil {
return x.HalfLifeSeconds
}
return 0
}
// Deprecated: Marked as deprecated in routerrpc/router.proto.
func (x *MissionControlConfig) GetHopProbability() float32 {
if x != nil {
return x.HopProbability
}
return 0
}
// Deprecated: Marked as deprecated in routerrpc/router.proto.
func (x *MissionControlConfig) GetWeight() float32 {
if x != nil {
return x.Weight
}
return 0
}
func (x *MissionControlConfig) GetMaximumPaymentResults() uint32 {
if x != nil {
return x.MaximumPaymentResults
}
return 0
}
func (x *MissionControlConfig) GetMinimumFailureRelaxInterval() uint64 {
if x != nil {
return x.MinimumFailureRelaxInterval
}
return 0
}
func (x *MissionControlConfig) GetModel() MissionControlConfig_ProbabilityModel {
if x != nil {
return x.Model
}
return MissionControlConfig_APRIORI
}
func (m *MissionControlConfig) GetEstimatorConfig() isMissionControlConfig_EstimatorConfig {
if m != nil {
return m.EstimatorConfig
}
return nil
}
func (x *MissionControlConfig) GetApriori() *AprioriParameters {
if x, ok := x.GetEstimatorConfig().(*MissionControlConfig_Apriori); ok {
return x.Apriori
}
return nil
}
func (x *MissionControlConfig) GetBimodal() *BimodalParameters {
if x, ok := x.GetEstimatorConfig().(*MissionControlConfig_Bimodal); ok {
return x.Bimodal
}
return nil
}
type isMissionControlConfig_EstimatorConfig interface {
isMissionControlConfig_EstimatorConfig()
}
type MissionControlConfig_Apriori struct {
Apriori *AprioriParameters `protobuf:"bytes,7,opt,name=apriori,proto3,oneof"`
}
type MissionControlConfig_Bimodal struct {
Bimodal *BimodalParameters `protobuf:"bytes,8,opt,name=bimodal,proto3,oneof"`
}
func (*MissionControlConfig_Apriori) isMissionControlConfig_EstimatorConfig() {}
func (*MissionControlConfig_Bimodal) isMissionControlConfig_EstimatorConfig() {}
type BimodalParameters struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// NodeWeight defines how strongly other previous forwardings on channels of a
// router should be taken into account when computing a channel's probability
// to route. The allowed values are in the range [0, 1], where a value of 0
// means that only direct information about a channel is taken into account.
NodeWeight float64 `protobuf:"fixed64,1,opt,name=node_weight,json=nodeWeight,proto3" json:"node_weight,omitempty"`
// ScaleMsat describes the scale over which channels statistically have some
// liquidity left. The value determines how quickly the bimodal distribution
// drops off from the edges of a channel. A larger value (compared to typical
// channel capacities) means that the drop off is slow and that channel
// balances are distributed more uniformly. A small value leads to the
// assumption of very unbalanced channels.
ScaleMsat uint64 `protobuf:"varint,2,opt,name=scale_msat,json=scaleMsat,proto3" json:"scale_msat,omitempty"`
// DecayTime describes the information decay of knowledge about previous
// successes and failures in channels. The smaller the decay time, the quicker
// we forget about past forwardings.
DecayTime uint64 `protobuf:"varint,3,opt,name=decay_time,json=decayTime,proto3" json:"decay_time,omitempty"`
}
func (x *BimodalParameters) Reset() {
*x = BimodalParameters{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[20]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *BimodalParameters) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BimodalParameters) ProtoMessage() {}
func (x *BimodalParameters) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[20]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BimodalParameters.ProtoReflect.Descriptor instead.
func (*BimodalParameters) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{20}
}
func (x *BimodalParameters) GetNodeWeight() float64 {
if x != nil {
return x.NodeWeight
}
return 0
}
func (x *BimodalParameters) GetScaleMsat() uint64 {
if x != nil {
return x.ScaleMsat
}
return 0
}
func (x *BimodalParameters) GetDecayTime() uint64 {
if x != nil {
return x.DecayTime
}
return 0
}
type AprioriParameters struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The amount of time mission control will take to restore a penalized node
// or channel back to 50% success probability, expressed in seconds. Setting
// this value to a higher value will penalize failures for longer, making
// mission control less likely to route through nodes and channels that we
// have previously recorded failures for.
HalfLifeSeconds uint64 `protobuf:"varint,1,opt,name=half_life_seconds,json=halfLifeSeconds,proto3" json:"half_life_seconds,omitempty"`
// The probability of success mission control should assign to hop in a route
// where it has no other information available. Higher values will make mission
// control more willing to try hops that we have no information about, lower
// values will discourage trying these hops.
HopProbability float64 `protobuf:"fixed64,2,opt,name=hop_probability,json=hopProbability,proto3" json:"hop_probability,omitempty"`
// The importance that mission control should place on historical results,
// expressed as a value in [0;1]. Setting this value to 1 will ignore all
// historical payments and just use the hop probability to assess the
// probability of success for each hop. A zero value ignores hop probability
// completely and relies entirely on historical results, unless none are
// available.
Weight float64 `protobuf:"fixed64,3,opt,name=weight,proto3" json:"weight,omitempty"`
// The fraction of a channel's capacity that we consider to have liquidity. For
// amounts that come close to or exceed the fraction, an additional penalty is
// applied. A value of 1.0 disables the capacity factor. Allowed values are in
// [0.75, 1.0].
CapacityFraction float64 `protobuf:"fixed64,4,opt,name=capacity_fraction,json=capacityFraction,proto3" json:"capacity_fraction,omitempty"`
}
func (x *AprioriParameters) Reset() {
*x = AprioriParameters{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[21]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AprioriParameters) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AprioriParameters) ProtoMessage() {}
func (x *AprioriParameters) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[21]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AprioriParameters.ProtoReflect.Descriptor instead.
func (*AprioriParameters) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{21}
}
func (x *AprioriParameters) GetHalfLifeSeconds() uint64 {
if x != nil {
return x.HalfLifeSeconds
}
return 0
}
func (x *AprioriParameters) GetHopProbability() float64 {
if x != nil {
return x.HopProbability
}
return 0
}
func (x *AprioriParameters) GetWeight() float64 {
if x != nil {
return x.Weight
}
return 0
}
func (x *AprioriParameters) GetCapacityFraction() float64 {
if x != nil {
return x.CapacityFraction
}
return 0
}
type QueryProbabilityRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The source node pubkey of the pair.
FromNode []byte `protobuf:"bytes,1,opt,name=from_node,json=fromNode,proto3" json:"from_node,omitempty"`
// The destination node pubkey of the pair.
ToNode []byte `protobuf:"bytes,2,opt,name=to_node,json=toNode,proto3" json:"to_node,omitempty"`
// The amount for which to calculate a probability.
AmtMsat int64 `protobuf:"varint,3,opt,name=amt_msat,json=amtMsat,proto3" json:"amt_msat,omitempty"`
}
func (x *QueryProbabilityRequest) Reset() {
*x = QueryProbabilityRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[22]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *QueryProbabilityRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*QueryProbabilityRequest) ProtoMessage() {}
func (x *QueryProbabilityRequest) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[22]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use QueryProbabilityRequest.ProtoReflect.Descriptor instead.
func (*QueryProbabilityRequest) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{22}
}
func (x *QueryProbabilityRequest) GetFromNode() []byte {
if x != nil {
return x.FromNode
}
return nil
}
func (x *QueryProbabilityRequest) GetToNode() []byte {
if x != nil {
return x.ToNode
}
return nil
}
func (x *QueryProbabilityRequest) GetAmtMsat() int64 {
if x != nil {
return x.AmtMsat
}
return 0
}
type QueryProbabilityResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The success probability for the requested pair.
Probability float64 `protobuf:"fixed64,1,opt,name=probability,proto3" json:"probability,omitempty"`
// The historical data for the requested pair.
History *PairData `protobuf:"bytes,2,opt,name=history,proto3" json:"history,omitempty"`
}
func (x *QueryProbabilityResponse) Reset() {
*x = QueryProbabilityResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[23]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *QueryProbabilityResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*QueryProbabilityResponse) ProtoMessage() {}
func (x *QueryProbabilityResponse) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[23]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use QueryProbabilityResponse.ProtoReflect.Descriptor instead.
func (*QueryProbabilityResponse) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{23}
}
func (x *QueryProbabilityResponse) GetProbability() float64 {
if x != nil {
return x.Probability
}
return 0
}
func (x *QueryProbabilityResponse) GetHistory() *PairData {
if x != nil {
return x.History
}
return nil
}
type BuildRouteRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The amount to send expressed in msat. If set to zero, the minimum routable
// amount is used.
AmtMsat int64 `protobuf:"varint,1,opt,name=amt_msat,json=amtMsat,proto3" json:"amt_msat,omitempty"`
// CLTV delta from the current height that should be used for the timelock
// of the final hop
FinalCltvDelta int32 `protobuf:"varint,2,opt,name=final_cltv_delta,json=finalCltvDelta,proto3" json:"final_cltv_delta,omitempty"`
// The channel id of the channel that must be taken to the first hop. If zero,
// any channel may be used.
OutgoingChanId uint64 `protobuf:"varint,3,opt,name=outgoing_chan_id,json=outgoingChanId,proto3" json:"outgoing_chan_id,omitempty"`
// A list of hops that defines the route. This does not include the source hop
// pubkey.
HopPubkeys [][]byte `protobuf:"bytes,4,rep,name=hop_pubkeys,json=hopPubkeys,proto3" json:"hop_pubkeys,omitempty"`
// An optional payment addr to be included within the last hop of the route.
// This is also called payment secret in specifications (e.g. BOLT 11).
PaymentAddr []byte `protobuf:"bytes,5,opt,name=payment_addr,json=paymentAddr,proto3" json:"payment_addr,omitempty"`
// An optional field that can be used to pass an arbitrary set of TLV records
// to the first hop peer of this payment. This can be used to pass application
// specific data during the payment attempt. Record types are required to be in
// the custom range >= 65536. When using REST, the values must be encoded as
// base64.
FirstHopCustomRecords map[uint64][]byte `protobuf:"bytes,6,rep,name=first_hop_custom_records,json=firstHopCustomRecords,proto3" json:"first_hop_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *BuildRouteRequest) Reset() {
*x = BuildRouteRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[24]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *BuildRouteRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BuildRouteRequest) ProtoMessage() {}
func (x *BuildRouteRequest) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[24]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BuildRouteRequest.ProtoReflect.Descriptor instead.
func (*BuildRouteRequest) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{24}
}
func (x *BuildRouteRequest) GetAmtMsat() int64 {
if x != nil {
return x.AmtMsat
}
return 0
}
func (x *BuildRouteRequest) GetFinalCltvDelta() int32 {
if x != nil {
return x.FinalCltvDelta
}
return 0
}
func (x *BuildRouteRequest) GetOutgoingChanId() uint64 {
if x != nil {
return x.OutgoingChanId
}
return 0
}
func (x *BuildRouteRequest) GetHopPubkeys() [][]byte {
if x != nil {
return x.HopPubkeys
}
return nil
}
func (x *BuildRouteRequest) GetPaymentAddr() []byte {
if x != nil {
return x.PaymentAddr
}
return nil
}
func (x *BuildRouteRequest) GetFirstHopCustomRecords() map[uint64][]byte {
if x != nil {
return x.FirstHopCustomRecords
}
return nil
}
type BuildRouteResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Fully specified route that can be used to execute the payment.
Route *lnrpc.Route `protobuf:"bytes,1,opt,name=route,proto3" json:"route,omitempty"`
}
func (x *BuildRouteResponse) Reset() {
*x = BuildRouteResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[25]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *BuildRouteResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BuildRouteResponse) ProtoMessage() {}
func (x *BuildRouteResponse) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[25]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BuildRouteResponse.ProtoReflect.Descriptor instead.
func (*BuildRouteResponse) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{25}
}
func (x *BuildRouteResponse) GetRoute() *lnrpc.Route {
if x != nil {
return x.Route
}
return nil
}
type SubscribeHtlcEventsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *SubscribeHtlcEventsRequest) Reset() {
*x = SubscribeHtlcEventsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[26]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SubscribeHtlcEventsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SubscribeHtlcEventsRequest) ProtoMessage() {}
func (x *SubscribeHtlcEventsRequest) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[26]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SubscribeHtlcEventsRequest.ProtoReflect.Descriptor instead.
func (*SubscribeHtlcEventsRequest) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{26}
}
// HtlcEvent contains the htlc event that was processed. These are served on a
// best-effort basis; events are not persisted, delivery is not guaranteed
// (in the event of a crash in the switch, forward events may be lost) and
// some events may be replayed upon restart. Events consumed from this package
// should be de-duplicated by the htlc's unique combination of incoming and
// outgoing channel id and htlc id. [EXPERIMENTAL]
type HtlcEvent struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The short channel id that the incoming htlc arrived at our node on. This
// value is zero for sends.
IncomingChannelId uint64 `protobuf:"varint,1,opt,name=incoming_channel_id,json=incomingChannelId,proto3" json:"incoming_channel_id,omitempty"`
// The short channel id that the outgoing htlc left our node on. This value
// is zero for receives.
OutgoingChannelId uint64 `protobuf:"varint,2,opt,name=outgoing_channel_id,json=outgoingChannelId,proto3" json:"outgoing_channel_id,omitempty"`
// Incoming id is the index of the incoming htlc in the incoming channel.
// This value is zero for sends.
IncomingHtlcId uint64 `protobuf:"varint,3,opt,name=incoming_htlc_id,json=incomingHtlcId,proto3" json:"incoming_htlc_id,omitempty"`
// Outgoing id is the index of the outgoing htlc in the outgoing channel.
// This value is zero for receives.
OutgoingHtlcId uint64 `protobuf:"varint,4,opt,name=outgoing_htlc_id,json=outgoingHtlcId,proto3" json:"outgoing_htlc_id,omitempty"`
// The time in unix nanoseconds that the event occurred.
TimestampNs uint64 `protobuf:"varint,5,opt,name=timestamp_ns,json=timestampNs,proto3" json:"timestamp_ns,omitempty"`
// The event type indicates whether the htlc was part of a send, receive or
// forward.
EventType HtlcEvent_EventType `protobuf:"varint,6,opt,name=event_type,json=eventType,proto3,enum=routerrpc.HtlcEvent_EventType" json:"event_type,omitempty"`
// Types that are assignable to Event:
//
// *HtlcEvent_ForwardEvent
// *HtlcEvent_ForwardFailEvent
// *HtlcEvent_SettleEvent
// *HtlcEvent_LinkFailEvent
// *HtlcEvent_SubscribedEvent
// *HtlcEvent_FinalHtlcEvent
Event isHtlcEvent_Event `protobuf_oneof:"event"`
}
func (x *HtlcEvent) Reset() {
*x = HtlcEvent{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[27]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HtlcEvent) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HtlcEvent) ProtoMessage() {}
func (x *HtlcEvent) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[27]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HtlcEvent.ProtoReflect.Descriptor instead.
func (*HtlcEvent) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{27}
}
func (x *HtlcEvent) GetIncomingChannelId() uint64 {
if x != nil {
return x.IncomingChannelId
}
return 0
}
func (x *HtlcEvent) GetOutgoingChannelId() uint64 {
if x != nil {
return x.OutgoingChannelId
}
return 0
}
func (x *HtlcEvent) GetIncomingHtlcId() uint64 {
if x != nil {
return x.IncomingHtlcId
}
return 0
}
func (x *HtlcEvent) GetOutgoingHtlcId() uint64 {
if x != nil {
return x.OutgoingHtlcId
}
return 0
}
func (x *HtlcEvent) GetTimestampNs() uint64 {
if x != nil {
return x.TimestampNs
}
return 0
}
func (x *HtlcEvent) GetEventType() HtlcEvent_EventType {
if x != nil {
return x.EventType
}
return HtlcEvent_UNKNOWN
}
func (m *HtlcEvent) GetEvent() isHtlcEvent_Event {
if m != nil {
return m.Event
}
return nil
}
func (x *HtlcEvent) GetForwardEvent() *ForwardEvent {
if x, ok := x.GetEvent().(*HtlcEvent_ForwardEvent); ok {
return x.ForwardEvent
}
return nil
}
func (x *HtlcEvent) GetForwardFailEvent() *ForwardFailEvent {
if x, ok := x.GetEvent().(*HtlcEvent_ForwardFailEvent); ok {
return x.ForwardFailEvent
}
return nil
}
func (x *HtlcEvent) GetSettleEvent() *SettleEvent {
if x, ok := x.GetEvent().(*HtlcEvent_SettleEvent); ok {
return x.SettleEvent
}
return nil
}
func (x *HtlcEvent) GetLinkFailEvent() *LinkFailEvent {
if x, ok := x.GetEvent().(*HtlcEvent_LinkFailEvent); ok {
return x.LinkFailEvent
}
return nil
}
func (x *HtlcEvent) GetSubscribedEvent() *SubscribedEvent {
if x, ok := x.GetEvent().(*HtlcEvent_SubscribedEvent); ok {
return x.SubscribedEvent
}
return nil
}
func (x *HtlcEvent) GetFinalHtlcEvent() *FinalHtlcEvent {
if x, ok := x.GetEvent().(*HtlcEvent_FinalHtlcEvent); ok {
return x.FinalHtlcEvent
}
return nil
}
type isHtlcEvent_Event interface {
isHtlcEvent_Event()
}
type HtlcEvent_ForwardEvent struct {
ForwardEvent *ForwardEvent `protobuf:"bytes,7,opt,name=forward_event,json=forwardEvent,proto3,oneof"`
}
type HtlcEvent_ForwardFailEvent struct {
ForwardFailEvent *ForwardFailEvent `protobuf:"bytes,8,opt,name=forward_fail_event,json=forwardFailEvent,proto3,oneof"`
}
type HtlcEvent_SettleEvent struct {
SettleEvent *SettleEvent `protobuf:"bytes,9,opt,name=settle_event,json=settleEvent,proto3,oneof"`
}
type HtlcEvent_LinkFailEvent struct {
LinkFailEvent *LinkFailEvent `protobuf:"bytes,10,opt,name=link_fail_event,json=linkFailEvent,proto3,oneof"`
}
type HtlcEvent_SubscribedEvent struct {
SubscribedEvent *SubscribedEvent `protobuf:"bytes,11,opt,name=subscribed_event,json=subscribedEvent,proto3,oneof"`
}
type HtlcEvent_FinalHtlcEvent struct {
FinalHtlcEvent *FinalHtlcEvent `protobuf:"bytes,12,opt,name=final_htlc_event,json=finalHtlcEvent,proto3,oneof"`
}
func (*HtlcEvent_ForwardEvent) isHtlcEvent_Event() {}
func (*HtlcEvent_ForwardFailEvent) isHtlcEvent_Event() {}
func (*HtlcEvent_SettleEvent) isHtlcEvent_Event() {}
func (*HtlcEvent_LinkFailEvent) isHtlcEvent_Event() {}
func (*HtlcEvent_SubscribedEvent) isHtlcEvent_Event() {}
func (*HtlcEvent_FinalHtlcEvent) isHtlcEvent_Event() {}
type HtlcInfo struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The timelock on the incoming htlc.
IncomingTimelock uint32 `protobuf:"varint,1,opt,name=incoming_timelock,json=incomingTimelock,proto3" json:"incoming_timelock,omitempty"`
// The timelock on the outgoing htlc.
OutgoingTimelock uint32 `protobuf:"varint,2,opt,name=outgoing_timelock,json=outgoingTimelock,proto3" json:"outgoing_timelock,omitempty"`
// The amount of the incoming htlc.
IncomingAmtMsat uint64 `protobuf:"varint,3,opt,name=incoming_amt_msat,json=incomingAmtMsat,proto3" json:"incoming_amt_msat,omitempty"`
// The amount of the outgoing htlc.
OutgoingAmtMsat uint64 `protobuf:"varint,4,opt,name=outgoing_amt_msat,json=outgoingAmtMsat,proto3" json:"outgoing_amt_msat,omitempty"`
}
func (x *HtlcInfo) Reset() {
*x = HtlcInfo{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[28]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HtlcInfo) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HtlcInfo) ProtoMessage() {}
func (x *HtlcInfo) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[28]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HtlcInfo.ProtoReflect.Descriptor instead.
func (*HtlcInfo) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{28}
}
func (x *HtlcInfo) GetIncomingTimelock() uint32 {
if x != nil {
return x.IncomingTimelock
}
return 0
}
func (x *HtlcInfo) GetOutgoingTimelock() uint32 {
if x != nil {
return x.OutgoingTimelock
}
return 0
}
func (x *HtlcInfo) GetIncomingAmtMsat() uint64 {
if x != nil {
return x.IncomingAmtMsat
}
return 0
}
func (x *HtlcInfo) GetOutgoingAmtMsat() uint64 {
if x != nil {
return x.OutgoingAmtMsat
}
return 0
}
type ForwardEvent struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Info contains details about the htlc that was forwarded.
Info *HtlcInfo `protobuf:"bytes,1,opt,name=info,proto3" json:"info,omitempty"`
}
func (x *ForwardEvent) Reset() {
*x = ForwardEvent{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[29]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ForwardEvent) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ForwardEvent) ProtoMessage() {}
func (x *ForwardEvent) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[29]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ForwardEvent.ProtoReflect.Descriptor instead.
func (*ForwardEvent) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{29}
}
func (x *ForwardEvent) GetInfo() *HtlcInfo {
if x != nil {
return x.Info
}
return nil
}
type ForwardFailEvent struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ForwardFailEvent) Reset() {
*x = ForwardFailEvent{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[30]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ForwardFailEvent) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ForwardFailEvent) ProtoMessage() {}
func (x *ForwardFailEvent) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[30]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ForwardFailEvent.ProtoReflect.Descriptor instead.
func (*ForwardFailEvent) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{30}
}
type SettleEvent struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The revealed preimage.
Preimage []byte `protobuf:"bytes,1,opt,name=preimage,proto3" json:"preimage,omitempty"`
}
func (x *SettleEvent) Reset() {
*x = SettleEvent{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[31]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SettleEvent) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SettleEvent) ProtoMessage() {}
func (x *SettleEvent) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[31]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SettleEvent.ProtoReflect.Descriptor instead.
func (*SettleEvent) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{31}
}
func (x *SettleEvent) GetPreimage() []byte {
if x != nil {
return x.Preimage
}
return nil
}
type FinalHtlcEvent struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Settled bool `protobuf:"varint,1,opt,name=settled,proto3" json:"settled,omitempty"`
Offchain bool `protobuf:"varint,2,opt,name=offchain,proto3" json:"offchain,omitempty"`
}
func (x *FinalHtlcEvent) Reset() {
*x = FinalHtlcEvent{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[32]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FinalHtlcEvent) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FinalHtlcEvent) ProtoMessage() {}
func (x *FinalHtlcEvent) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[32]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FinalHtlcEvent.ProtoReflect.Descriptor instead.
func (*FinalHtlcEvent) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{32}
}
func (x *FinalHtlcEvent) GetSettled() bool {
if x != nil {
return x.Settled
}
return false
}
func (x *FinalHtlcEvent) GetOffchain() bool {
if x != nil {
return x.Offchain
}
return false
}
type SubscribedEvent struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *SubscribedEvent) Reset() {
*x = SubscribedEvent{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[33]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SubscribedEvent) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SubscribedEvent) ProtoMessage() {}
func (x *SubscribedEvent) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[33]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SubscribedEvent.ProtoReflect.Descriptor instead.
func (*SubscribedEvent) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{33}
}
type LinkFailEvent struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Info contains details about the htlc that we failed.
Info *HtlcInfo `protobuf:"bytes,1,opt,name=info,proto3" json:"info,omitempty"`
// FailureCode is the BOLT error code for the failure.
WireFailure lnrpc.Failure_FailureCode `protobuf:"varint,2,opt,name=wire_failure,json=wireFailure,proto3,enum=lnrpc.Failure_FailureCode" json:"wire_failure,omitempty"`
// FailureDetail provides additional information about the reason for the
// failure. This detail enriches the information provided by the wire message
// and may be 'no detail' if the wire message requires no additional metadata.
FailureDetail FailureDetail `protobuf:"varint,3,opt,name=failure_detail,json=failureDetail,proto3,enum=routerrpc.FailureDetail" json:"failure_detail,omitempty"`
// A string representation of the link failure.
FailureString string `protobuf:"bytes,4,opt,name=failure_string,json=failureString,proto3" json:"failure_string,omitempty"`
}
func (x *LinkFailEvent) Reset() {
*x = LinkFailEvent{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[34]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *LinkFailEvent) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LinkFailEvent) ProtoMessage() {}
func (x *LinkFailEvent) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[34]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LinkFailEvent.ProtoReflect.Descriptor instead.
func (*LinkFailEvent) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{34}
}
func (x *LinkFailEvent) GetInfo() *HtlcInfo {
if x != nil {
return x.Info
}
return nil
}
func (x *LinkFailEvent) GetWireFailure() lnrpc.Failure_FailureCode {
if x != nil {
return x.WireFailure
}
return lnrpc.Failure_FailureCode(0)
}
func (x *LinkFailEvent) GetFailureDetail() FailureDetail {
if x != nil {
return x.FailureDetail
}
return FailureDetail_UNKNOWN
}
func (x *LinkFailEvent) GetFailureString() string {
if x != nil {
return x.FailureString
}
return ""
}
type PaymentStatus struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Current state the payment is in.
State PaymentState `protobuf:"varint,1,opt,name=state,proto3,enum=routerrpc.PaymentState" json:"state,omitempty"`
// The pre-image of the payment when state is SUCCEEDED.
Preimage []byte `protobuf:"bytes,2,opt,name=preimage,proto3" json:"preimage,omitempty"`
// The HTLCs made in attempt to settle the payment [EXPERIMENTAL].
Htlcs []*lnrpc.HTLCAttempt `protobuf:"bytes,4,rep,name=htlcs,proto3" json:"htlcs,omitempty"`
}
func (x *PaymentStatus) Reset() {
*x = PaymentStatus{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[35]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PaymentStatus) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PaymentStatus) ProtoMessage() {}
func (x *PaymentStatus) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[35]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PaymentStatus.ProtoReflect.Descriptor instead.
func (*PaymentStatus) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{35}
}
func (x *PaymentStatus) GetState() PaymentState {
if x != nil {
return x.State
}
return PaymentState_IN_FLIGHT
}
func (x *PaymentStatus) GetPreimage() []byte {
if x != nil {
return x.Preimage
}
return nil
}
func (x *PaymentStatus) GetHtlcs() []*lnrpc.HTLCAttempt {
if x != nil {
return x.Htlcs
}
return nil
}
type CircuitKey struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// / The id of the channel that the is part of this circuit.
ChanId uint64 `protobuf:"varint,1,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"`
// / The index of the incoming htlc in the incoming channel.
HtlcId uint64 `protobuf:"varint,2,opt,name=htlc_id,json=htlcId,proto3" json:"htlc_id,omitempty"`
}
func (x *CircuitKey) Reset() {
*x = CircuitKey{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[36]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CircuitKey) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CircuitKey) ProtoMessage() {}
func (x *CircuitKey) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[36]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CircuitKey.ProtoReflect.Descriptor instead.
func (*CircuitKey) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{36}
}
func (x *CircuitKey) GetChanId() uint64 {
if x != nil {
return x.ChanId
}
return 0
}
func (x *CircuitKey) GetHtlcId() uint64 {
if x != nil {
return x.HtlcId
}
return 0
}
type ForwardHtlcInterceptRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The key of this forwarded htlc. It defines the incoming channel id and
// the index in this channel.
IncomingCircuitKey *CircuitKey `protobuf:"bytes,1,opt,name=incoming_circuit_key,json=incomingCircuitKey,proto3" json:"incoming_circuit_key,omitempty"`
// The incoming htlc amount.
IncomingAmountMsat uint64 `protobuf:"varint,5,opt,name=incoming_amount_msat,json=incomingAmountMsat,proto3" json:"incoming_amount_msat,omitempty"`
// The incoming htlc expiry.
IncomingExpiry uint32 `protobuf:"varint,6,opt,name=incoming_expiry,json=incomingExpiry,proto3" json:"incoming_expiry,omitempty"`
// The htlc payment hash. This value is not guaranteed to be unique per
// request.
PaymentHash []byte `protobuf:"bytes,2,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"`
// The requested outgoing channel id for this forwarded htlc. Because of
// non-strict forwarding, this isn't necessarily the channel over which the
// packet will be forwarded eventually. A different channel to the same peer
// may be selected as well.
OutgoingRequestedChanId uint64 `protobuf:"varint,7,opt,name=outgoing_requested_chan_id,json=outgoingRequestedChanId,proto3" json:"outgoing_requested_chan_id,omitempty"`
// The outgoing htlc amount.
OutgoingAmountMsat uint64 `protobuf:"varint,3,opt,name=outgoing_amount_msat,json=outgoingAmountMsat,proto3" json:"outgoing_amount_msat,omitempty"`
// The outgoing htlc expiry.
OutgoingExpiry uint32 `protobuf:"varint,4,opt,name=outgoing_expiry,json=outgoingExpiry,proto3" json:"outgoing_expiry,omitempty"`
// Any custom records that were present in the payload.
CustomRecords map[uint64][]byte `protobuf:"bytes,8,rep,name=custom_records,json=customRecords,proto3" json:"custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
// The onion blob for the next hop
OnionBlob []byte `protobuf:"bytes,9,opt,name=onion_blob,json=onionBlob,proto3" json:"onion_blob,omitempty"`
// The block height at which this htlc will be auto-failed to prevent the
// channel from force-closing.
AutoFailHeight int32 `protobuf:"varint,10,opt,name=auto_fail_height,json=autoFailHeight,proto3" json:"auto_fail_height,omitempty"`
// The custom records of the peer's incoming p2p wire message.
InWireCustomRecords map[uint64][]byte `protobuf:"bytes,11,rep,name=in_wire_custom_records,json=inWireCustomRecords,proto3" json:"in_wire_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *ForwardHtlcInterceptRequest) Reset() {
*x = ForwardHtlcInterceptRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[37]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ForwardHtlcInterceptRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ForwardHtlcInterceptRequest) ProtoMessage() {}
func (x *ForwardHtlcInterceptRequest) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[37]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ForwardHtlcInterceptRequest.ProtoReflect.Descriptor instead.
func (*ForwardHtlcInterceptRequest) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{37}
}
func (x *ForwardHtlcInterceptRequest) GetIncomingCircuitKey() *CircuitKey {
if x != nil {
return x.IncomingCircuitKey
}
return nil
}
func (x *ForwardHtlcInterceptRequest) GetIncomingAmountMsat() uint64 {
if x != nil {
return x.IncomingAmountMsat
}
return 0
}
func (x *ForwardHtlcInterceptRequest) GetIncomingExpiry() uint32 {
if x != nil {
return x.IncomingExpiry
}
return 0
}
func (x *ForwardHtlcInterceptRequest) GetPaymentHash() []byte {
if x != nil {
return x.PaymentHash
}
return nil
}
func (x *ForwardHtlcInterceptRequest) GetOutgoingRequestedChanId() uint64 {
if x != nil {
return x.OutgoingRequestedChanId
}
return 0
}
func (x *ForwardHtlcInterceptRequest) GetOutgoingAmountMsat() uint64 {
if x != nil {
return x.OutgoingAmountMsat
}
return 0
}
func (x *ForwardHtlcInterceptRequest) GetOutgoingExpiry() uint32 {
if x != nil {
return x.OutgoingExpiry
}
return 0
}
func (x *ForwardHtlcInterceptRequest) GetCustomRecords() map[uint64][]byte {
if x != nil {
return x.CustomRecords
}
return nil
}
func (x *ForwardHtlcInterceptRequest) GetOnionBlob() []byte {
if x != nil {
return x.OnionBlob
}
return nil
}
func (x *ForwardHtlcInterceptRequest) GetAutoFailHeight() int32 {
if x != nil {
return x.AutoFailHeight
}
return 0
}
func (x *ForwardHtlcInterceptRequest) GetInWireCustomRecords() map[uint64][]byte {
if x != nil {
return x.InWireCustomRecords
}
return nil
}
// *
// ForwardHtlcInterceptResponse enables the caller to resolve a previously hold
// forward. The caller can choose either to:
// - `Resume`: Execute the default behavior (usually forward).
// - `ResumeModified`: Execute the default behavior (usually forward) with HTLC
// field modifications.
// - `Reject`: Fail the htlc backwards.
// - `Settle`: Settle this htlc with a given preimage.
type ForwardHtlcInterceptResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// *
// The key of this forwarded htlc. It defines the incoming channel id and
// the index in this channel.
IncomingCircuitKey *CircuitKey `protobuf:"bytes,1,opt,name=incoming_circuit_key,json=incomingCircuitKey,proto3" json:"incoming_circuit_key,omitempty"`
// The resolve action for this intercepted htlc.
Action ResolveHoldForwardAction `protobuf:"varint,2,opt,name=action,proto3,enum=routerrpc.ResolveHoldForwardAction" json:"action,omitempty"`
// The preimage in case the resolve action is Settle.
Preimage []byte `protobuf:"bytes,3,opt,name=preimage,proto3" json:"preimage,omitempty"`
// Encrypted failure message in case the resolve action is Fail.
//
// If failure_message is specified, the failure_code field must be set
// to zero.
FailureMessage []byte `protobuf:"bytes,4,opt,name=failure_message,json=failureMessage,proto3" json:"failure_message,omitempty"`
// Return the specified failure code in case the resolve action is Fail. The
// message data fields are populated automatically.
//
// If a non-zero failure_code is specified, failure_message must not be set.
//
// For backwards-compatibility reasons, TEMPORARY_CHANNEL_FAILURE is the
// default value for this field.
FailureCode lnrpc.Failure_FailureCode `protobuf:"varint,5,opt,name=failure_code,json=failureCode,proto3,enum=lnrpc.Failure_FailureCode" json:"failure_code,omitempty"`
// The amount that was set on the p2p wire message of the incoming HTLC.
// This field is ignored if the action is not RESUME_MODIFIED or the amount
// is zero.
InAmountMsat uint64 `protobuf:"varint,6,opt,name=in_amount_msat,json=inAmountMsat,proto3" json:"in_amount_msat,omitempty"`
// The amount to set on the p2p wire message of the resumed HTLC. This field
// is ignored if the action is not RESUME_MODIFIED or the amount is zero.
OutAmountMsat uint64 `protobuf:"varint,7,opt,name=out_amount_msat,json=outAmountMsat,proto3" json:"out_amount_msat,omitempty"`
// Any custom records that should be set on the p2p wire message message of
// the resumed HTLC. This field is ignored if the action is not
// RESUME_MODIFIED.
//
// This map will merge with the existing set of custom records (if any),
// replacing any conflicting types. Note that there currently is no support
// for deleting existing custom records (they can only be replaced).
OutWireCustomRecords map[uint64][]byte `protobuf:"bytes,8,rep,name=out_wire_custom_records,json=outWireCustomRecords,proto3" json:"out_wire_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *ForwardHtlcInterceptResponse) Reset() {
*x = ForwardHtlcInterceptResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[38]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ForwardHtlcInterceptResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ForwardHtlcInterceptResponse) ProtoMessage() {}
func (x *ForwardHtlcInterceptResponse) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[38]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ForwardHtlcInterceptResponse.ProtoReflect.Descriptor instead.
func (*ForwardHtlcInterceptResponse) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{38}
}
func (x *ForwardHtlcInterceptResponse) GetIncomingCircuitKey() *CircuitKey {
if x != nil {
return x.IncomingCircuitKey
}
return nil
}
func (x *ForwardHtlcInterceptResponse) GetAction() ResolveHoldForwardAction {
if x != nil {
return x.Action
}
return ResolveHoldForwardAction_SETTLE
}
func (x *ForwardHtlcInterceptResponse) GetPreimage() []byte {
if x != nil {
return x.Preimage
}
return nil
}
func (x *ForwardHtlcInterceptResponse) GetFailureMessage() []byte {
if x != nil {
return x.FailureMessage
}
return nil
}
func (x *ForwardHtlcInterceptResponse) GetFailureCode() lnrpc.Failure_FailureCode {
if x != nil {
return x.FailureCode
}
return lnrpc.Failure_FailureCode(0)
}
func (x *ForwardHtlcInterceptResponse) GetInAmountMsat() uint64 {
if x != nil {
return x.InAmountMsat
}
return 0
}
func (x *ForwardHtlcInterceptResponse) GetOutAmountMsat() uint64 {
if x != nil {
return x.OutAmountMsat
}
return 0
}
func (x *ForwardHtlcInterceptResponse) GetOutWireCustomRecords() map[uint64][]byte {
if x != nil {
return x.OutWireCustomRecords
}
return nil
}
type UpdateChanStatusRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ChanPoint *lnrpc.ChannelPoint `protobuf:"bytes,1,opt,name=chan_point,json=chanPoint,proto3" json:"chan_point,omitempty"`
Action ChanStatusAction `protobuf:"varint,2,opt,name=action,proto3,enum=routerrpc.ChanStatusAction" json:"action,omitempty"`
}
func (x *UpdateChanStatusRequest) Reset() {
*x = UpdateChanStatusRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[39]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *UpdateChanStatusRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UpdateChanStatusRequest) ProtoMessage() {}
func (x *UpdateChanStatusRequest) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[39]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UpdateChanStatusRequest.ProtoReflect.Descriptor instead.
func (*UpdateChanStatusRequest) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{39}
}
func (x *UpdateChanStatusRequest) GetChanPoint() *lnrpc.ChannelPoint {
if x != nil {
return x.ChanPoint
}
return nil
}
func (x *UpdateChanStatusRequest) GetAction() ChanStatusAction {
if x != nil {
return x.Action
}
return ChanStatusAction_ENABLE
}
type UpdateChanStatusResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *UpdateChanStatusResponse) Reset() {
*x = UpdateChanStatusResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[40]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *UpdateChanStatusResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UpdateChanStatusResponse) ProtoMessage() {}
func (x *UpdateChanStatusResponse) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[40]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UpdateChanStatusResponse.ProtoReflect.Descriptor instead.
func (*UpdateChanStatusResponse) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{40}
}
type AddAliasesRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
AliasMaps []*lnrpc.AliasMap `protobuf:"bytes,1,rep,name=alias_maps,json=aliasMaps,proto3" json:"alias_maps,omitempty"`
}
func (x *AddAliasesRequest) Reset() {
*x = AddAliasesRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[41]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AddAliasesRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AddAliasesRequest) ProtoMessage() {}
func (x *AddAliasesRequest) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[41]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AddAliasesRequest.ProtoReflect.Descriptor instead.
func (*AddAliasesRequest) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{41}
}
func (x *AddAliasesRequest) GetAliasMaps() []*lnrpc.AliasMap {
if x != nil {
return x.AliasMaps
}
return nil
}
type AddAliasesResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
AliasMaps []*lnrpc.AliasMap `protobuf:"bytes,1,rep,name=alias_maps,json=aliasMaps,proto3" json:"alias_maps,omitempty"`
}
func (x *AddAliasesResponse) Reset() {
*x = AddAliasesResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[42]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AddAliasesResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AddAliasesResponse) ProtoMessage() {}
func (x *AddAliasesResponse) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[42]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AddAliasesResponse.ProtoReflect.Descriptor instead.
func (*AddAliasesResponse) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{42}
}
func (x *AddAliasesResponse) GetAliasMaps() []*lnrpc.AliasMap {
if x != nil {
return x.AliasMaps
}
return nil
}
type DeleteAliasesRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
AliasMaps []*lnrpc.AliasMap `protobuf:"bytes,1,rep,name=alias_maps,json=aliasMaps,proto3" json:"alias_maps,omitempty"`
}
func (x *DeleteAliasesRequest) Reset() {
*x = DeleteAliasesRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[43]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DeleteAliasesRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeleteAliasesRequest) ProtoMessage() {}
func (x *DeleteAliasesRequest) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[43]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeleteAliasesRequest.ProtoReflect.Descriptor instead.
func (*DeleteAliasesRequest) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{43}
}
func (x *DeleteAliasesRequest) GetAliasMaps() []*lnrpc.AliasMap {
if x != nil {
return x.AliasMaps
}
return nil
}
type DeleteAliasesResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
AliasMaps []*lnrpc.AliasMap `protobuf:"bytes,1,rep,name=alias_maps,json=aliasMaps,proto3" json:"alias_maps,omitempty"`
}
func (x *DeleteAliasesResponse) Reset() {
*x = DeleteAliasesResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_routerrpc_router_proto_msgTypes[44]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DeleteAliasesResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeleteAliasesResponse) ProtoMessage() {}
func (x *DeleteAliasesResponse) ProtoReflect() protoreflect.Message {
mi := &file_routerrpc_router_proto_msgTypes[44]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeleteAliasesResponse.ProtoReflect.Descriptor instead.
func (*DeleteAliasesResponse) Descriptor() ([]byte, []int) {
return file_routerrpc_router_proto_rawDescGZIP(), []int{44}
}
func (x *DeleteAliasesResponse) GetAliasMaps() []*lnrpc.AliasMap {
if x != nil {
return x.AliasMaps
}
return nil
}
var File_routerrpc_router_proto protoreflect.FileDescriptor
var file_routerrpc_router_proto_rawDesc = []byte{
0x0a, 0x16, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2f, 0x72, 0x6f, 0x75, 0x74,
0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72,
0x72, 0x70, 0x63, 0x1a, 0x0f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd1, 0x09, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79,
0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64,
0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12,
0x10, 0x0a, 0x03, 0x61, 0x6d, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x61, 0x6d,
0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73,
0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74,
0x48, 0x61, 0x73, 0x68, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x63, 0x6c,
0x74, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e,
0x66, 0x69, 0x6e, 0x61, 0x6c, 0x43, 0x6c, 0x74, 0x76, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x27,
0x0a, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x6f,
0x75, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05,
0x52, 0x0e, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73,
0x12, 0x22, 0x0a, 0x0d, 0x66, 0x65, 0x65, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x73, 0x61,
0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x66, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69,
0x74, 0x53, 0x61, 0x74, 0x12, 0x2e, 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67,
0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x42, 0x04,
0x18, 0x01, 0x30, 0x01, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x43, 0x68,
0x61, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x6c, 0x69, 0x6d,
0x69, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x63, 0x6c, 0x74, 0x76, 0x4c, 0x69,
0x6d, 0x69, 0x74, 0x12, 0x31, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x68, 0x69, 0x6e,
0x74, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x72, 0x6f, 0x75, 0x74,
0x65, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x64, 0x0a, 0x13, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x63,
0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x0b, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e,
0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63,
0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x64, 0x65, 0x73, 0x74, 0x43,
0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x19, 0x0a, 0x08,
0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07,
0x61, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x65, 0x65, 0x5f, 0x6c,
0x69, 0x6d, 0x69, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52,
0x0c, 0x66, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x26, 0x0a,
0x0f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79,
0x18, 0x0e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x50,
0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x73,
0x65, 0x6c, 0x66, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28,
0x08, 0x52, 0x10, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x53, 0x65, 0x6c, 0x66, 0x50, 0x61, 0x79, 0x6d,
0x65, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x0d, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x66, 0x65, 0x61, 0x74,
0x75, 0x72, 0x65, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, 0x74, 0x52, 0x0c, 0x64,
0x65, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6d,
0x61, 0x78, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08,
0x6d, 0x61, 0x78, 0x50, 0x61, 0x72, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x6f, 0x5f, 0x69,
0x6e, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18,
0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x6e, 0x6f, 0x49, 0x6e, 0x66, 0x6c, 0x69, 0x67, 0x68,
0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x67,
0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x13, 0x20,
0x03, 0x28, 0x04, 0x52, 0x0f, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61,
0x6e, 0x49, 0x64, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f,
0x61, 0x64, 0x64, 0x72, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d,
0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x2d, 0x0a, 0x13, 0x6d, 0x61, 0x78, 0x5f, 0x73,
0x68, 0x61, 0x72, 0x64, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x15,
0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x53, 0x68, 0x61, 0x72, 0x64, 0x53, 0x69,
0x7a, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6d, 0x70, 0x18, 0x16, 0x20,
0x01, 0x28, 0x08, 0x52, 0x03, 0x61, 0x6d, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65,
0x5f, 0x70, 0x72, 0x65, 0x66, 0x18, 0x17, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x74, 0x69, 0x6d,
0x65, 0x50, 0x72, 0x65, 0x66, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x61,
0x62, 0x6c, 0x65, 0x18, 0x18, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x63, 0x61, 0x6e, 0x63, 0x65,
0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x71, 0x0a, 0x18, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x68,
0x6f, 0x70, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64,
0x73, 0x18, 0x19, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72,
0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43,
0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72,
0x79, 0x52, 0x15, 0x66, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f,
0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x44, 0x0a, 0x16, 0x44, 0x65, 0x73, 0x74,
0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74,
0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52,
0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x48,
0x0a, 0x1a, 0x46, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d,
0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03,
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14,
0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x68, 0x0a, 0x13, 0x54, 0x72, 0x61, 0x63,
0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61,
0x73, 0x68, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x6f, 0x5f, 0x69, 0x6e, 0x66, 0x6c, 0x69, 0x67, 0x68,
0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52,
0x11, 0x6e, 0x6f, 0x49, 0x6e, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x73, 0x22, 0x46, 0x0a, 0x14, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65,
0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x6f,
0x5f, 0x69, 0x6e, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65,
0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x6e, 0x6f, 0x49, 0x6e, 0x66, 0x6c, 0x69,
0x67, 0x68, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0x81, 0x01, 0x0a, 0x0f, 0x52,
0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12,
0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x65,
0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x61, 0x6d, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20,
0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x74, 0x53, 0x61, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x70,
0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x03,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18,
0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x22, 0xa8,
0x01, 0x0a, 0x10, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x66,
0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72,
0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x26, 0x0a,
0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79,
0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b,
0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x42, 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65,
0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x46, 0x61, 0x69,
0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c,
0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xbc, 0x02, 0x0a, 0x12, 0x53, 0x65,
0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48,
0x61, 0x73, 0x68, 0x12, 0x22, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65,
0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x5f,
0x74, 0x65, 0x6d, 0x70, 0x5f, 0x65, 0x72, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b,
0x73, 0x6b, 0x69, 0x70, 0x54, 0x65, 0x6d, 0x70, 0x45, 0x72, 0x72, 0x12, 0x71, 0x0a, 0x18, 0x66,
0x69, 0x72, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x70, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f,
0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e,
0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f,
0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x72,
0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72,
0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x15, 0x66, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f,
0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x48,
0x0a, 0x1a, 0x46, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d,
0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03,
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14,
0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x5b, 0x0a, 0x13, 0x53, 0x65, 0x6e, 0x64,
0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x66,
0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x07, 0x66, 0x61,
0x69, 0x6c, 0x75, 0x72, 0x65, 0x22, 0x1c, 0x0a, 0x1a, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69,
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x22, 0x1d, 0x0a, 0x1b, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73,
0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x1c, 0x0a, 0x1a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69,
0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x22, 0x51, 0x0a, 0x1b, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e,
0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x2c, 0x0a, 0x05, 0x70, 0x61, 0x69, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16,
0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x48,
0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x05, 0x70, 0x61, 0x69, 0x72, 0x73, 0x4a, 0x04, 0x08,
0x01, 0x10, 0x02, 0x22, 0x62, 0x0a, 0x1c, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69,
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x05, 0x70, 0x61, 0x69, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50,
0x61, 0x69, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x05, 0x70, 0x61, 0x69, 0x72,
0x73, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08,
0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x22, 0x1f, 0x0a, 0x1d, 0x58, 0x49, 0x6d, 0x70, 0x6f,
0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x8a, 0x01, 0x0a, 0x0b, 0x50, 0x61, 0x69,
0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65,
0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6e, 0x6f, 0x64,
0x65, 0x46, 0x72, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x74, 0x6f,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x54, 0x6f, 0x12, 0x2d,
0x0a, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x13, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x69, 0x72,
0x44, 0x61, 0x74, 0x61, 0x52, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x04, 0x08,
0x03, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x4a,
0x04, 0x08, 0x06, 0x10, 0x07, 0x22, 0xe8, 0x01, 0x0a, 0x08, 0x50, 0x61, 0x69, 0x72, 0x44, 0x61,
0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x66, 0x61, 0x69, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12,
0x20, 0x0a, 0x0c, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18,
0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, 0x61, 0x69, 0x6c, 0x41, 0x6d, 0x74, 0x53, 0x61,
0x74, 0x12, 0x22, 0x0a, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73,
0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x66, 0x61, 0x69, 0x6c, 0x41, 0x6d,
0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73,
0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x73, 0x75, 0x63,
0x63, 0x65, 0x73, 0x73, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x75, 0x63, 0x63,
0x65, 0x73, 0x73, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28,
0x03, 0x52, 0x0d, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x41, 0x6d, 0x74, 0x53, 0x61, 0x74,
0x12, 0x28, 0x0a, 0x10, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x61, 0x6d, 0x74, 0x5f,
0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x75, 0x63, 0x63,
0x65, 0x73, 0x73, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04,
0x22, 0x20, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f,
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x22, 0x5a, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e,
0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70,
0x63, 0x2e, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x59,
0x0a, 0x1e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74,
0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x37, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x1f, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x69, 0x73,
0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x21, 0x0a, 0x1f, 0x53, 0x65, 0x74,
0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x89, 0x04, 0x0a,
0x14, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x0a, 0x11, 0x68, 0x61, 0x6c, 0x66, 0x5f, 0x6c, 0x69,
0x66, 0x65, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
0x42, 0x02, 0x18, 0x01, 0x52, 0x0f, 0x68, 0x61, 0x6c, 0x66, 0x4c, 0x69, 0x66, 0x65, 0x53, 0x65,
0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x2b, 0x0a, 0x0f, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x72, 0x6f,
0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x42, 0x02,
0x18, 0x01, 0x52, 0x0e, 0x68, 0x6f, 0x70, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69,
0x74, 0x79, 0x12, 0x1a, 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01,
0x28, 0x02, 0x42, 0x02, 0x18, 0x01, 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x36,
0x0a, 0x17, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e,
0x74, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52,
0x15, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52,
0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x43, 0x0a, 0x1e, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75,
0x6d, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x78, 0x5f,
0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x1b,
0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65,
0x6c, 0x61, 0x78, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x46, 0x0a, 0x05, 0x6d,
0x6f, 0x64, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x72, 0x6f, 0x75,
0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f,
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x72, 0x6f, 0x62,
0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x52, 0x05, 0x6d, 0x6f,
0x64, 0x65, 0x6c, 0x12, 0x38, 0x0a, 0x07, 0x61, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x18, 0x07,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63,
0x2e, 0x41, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65,
0x72, 0x73, 0x48, 0x00, 0x52, 0x07, 0x61, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x12, 0x38, 0x0a,
0x07, 0x62, 0x69, 0x6d, 0x6f, 0x64, 0x61, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c,
0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x69, 0x6d, 0x6f, 0x64,
0x61, 0x6c, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x48, 0x00, 0x52, 0x07,
0x62, 0x69, 0x6d, 0x6f, 0x64, 0x61, 0x6c, 0x22, 0x2c, 0x0a, 0x10, 0x50, 0x72, 0x6f, 0x62, 0x61,
0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x41,
0x50, 0x52, 0x49, 0x4f, 0x52, 0x49, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x42, 0x49, 0x4d, 0x4f,
0x44, 0x41, 0x4c, 0x10, 0x01, 0x42, 0x11, 0x0a, 0x0f, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74,
0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x72, 0x0a, 0x11, 0x42, 0x69, 0x6d, 0x6f,
0x64, 0x61, 0x6c, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x1f, 0x0a,
0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01,
0x28, 0x01, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d,
0x0a, 0x0a, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01,
0x28, 0x04, 0x52, 0x09, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1d, 0x0a,
0x0a, 0x64, 0x65, 0x63, 0x61, 0x79, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
0x04, 0x52, 0x09, 0x64, 0x65, 0x63, 0x61, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x22, 0xad, 0x01, 0x0a,
0x11, 0x41, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65,
0x72, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x61, 0x6c, 0x66, 0x5f, 0x6c, 0x69, 0x66, 0x65, 0x5f,
0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x68,
0x61, 0x6c, 0x66, 0x4c, 0x69, 0x66, 0x65, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x27,
0x0a, 0x0f, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74,
0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0e, 0x68, 0x6f, 0x70, 0x50, 0x72, 0x6f, 0x62,
0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68,
0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12,
0x2b, 0x0a, 0x11, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x5f, 0x66, 0x72, 0x61, 0x63,
0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x63, 0x61, 0x70, 0x61,
0x63, 0x69, 0x74, 0x79, 0x46, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x6a, 0x0a, 0x17,
0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x72, 0x6f, 0x6d, 0x5f,
0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x66, 0x72, 0x6f, 0x6d,
0x4e, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6f, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x74, 0x6f, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a,
0x08, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52,
0x07, 0x61, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x22, 0x6b, 0x0a, 0x18, 0x51, 0x75, 0x65, 0x72,
0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c,
0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x62, 0x61,
0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x2d, 0x0a, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72,
0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72,
0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x44, 0x61, 0x74, 0x61, 0x52, 0x07, 0x68, 0x69,
0x73, 0x74, 0x6f, 0x72, 0x79, 0x22, 0x86, 0x03, 0x0a, 0x11, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52,
0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61,
0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61,
0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f,
0x63, 0x6c, 0x74, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05,
0x52, 0x0e, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x43, 0x6c, 0x74, 0x76, 0x44, 0x65, 0x6c, 0x74, 0x61,
0x12, 0x2c, 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61,
0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0e,
0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f,
0x0a, 0x0b, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x04, 0x20,
0x03, 0x28, 0x0c, 0x52, 0x0a, 0x68, 0x6f, 0x70, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x12,
0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18,
0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x64,
0x64, 0x72, 0x12, 0x70, 0x0a, 0x18, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x70, 0x5f,
0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x06,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63,
0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f,
0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x15, 0x66,
0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63,
0x6f, 0x72, 0x64, 0x73, 0x1a, 0x48, 0x0a, 0x1a, 0x46, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70,
0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74,
0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52,
0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x38,
0x0a, 0x12, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74,
0x65, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x22, 0x1c, 0x0a, 0x1a, 0x53, 0x75, 0x62, 0x73,
0x63, 0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x86, 0x06, 0x0a, 0x09, 0x48, 0x74, 0x6c, 0x63, 0x45,
0x76, 0x65, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67,
0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x04, 0x52, 0x11, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e,
0x65, 0x6c, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67,
0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
0x04, 0x52, 0x11, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e,
0x65, 0x6c, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67,
0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e,
0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x64, 0x12, 0x28,
0x0a, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f,
0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69,
0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65,
0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b,
0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x4e, 0x73, 0x12, 0x3d, 0x0a, 0x0a, 0x65,
0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63,
0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52,
0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x3e, 0x0a, 0x0d, 0x66, 0x6f,
0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x17, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f,
0x72, 0x77, 0x61, 0x72, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x66, 0x6f,
0x72, 0x77, 0x61, 0x72, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x4b, 0x0a, 0x12, 0x66, 0x6f,
0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74,
0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72,
0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76,
0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x10, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x46, 0x61,
0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3b, 0x0a, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c,
0x65, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e,
0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65,
0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x45,
0x76, 0x65, 0x6e, 0x74, 0x12, 0x42, 0x0a, 0x0f, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x66, 0x61, 0x69,
0x6c, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e,
0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x46, 0x61,
0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x6c, 0x69, 0x6e, 0x6b, 0x46,
0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x73,
0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53,
0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00,
0x52, 0x0f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e,
0x74, 0x12, 0x45, 0x0a, 0x10, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f,
0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x72, 0x6f,
0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x48, 0x74, 0x6c,
0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x48,
0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x3c, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e,
0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e,
0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07,
0x52, 0x45, 0x43, 0x45, 0x49, 0x56, 0x45, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x4f, 0x52,
0x57, 0x41, 0x52, 0x44, 0x10, 0x03, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22,
0xbc, 0x01, 0x0a, 0x08, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2b, 0x0a, 0x11,
0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x6f, 0x63,
0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e,
0x67, 0x54, 0x69, 0x6d, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75, 0x74,
0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x54, 0x69,
0x6d, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x2a, 0x0a, 0x11, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69,
0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28,
0x04, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x74, 0x4d, 0x73,
0x61, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x61,
0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6f,
0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x22, 0x37,
0x0a, 0x0c, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x27,
0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x72,
0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x66,
0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x12, 0x0a, 0x10, 0x46, 0x6f, 0x72, 0x77, 0x61,
0x72, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x29, 0x0a, 0x0b, 0x53,
0x65, 0x74, 0x74, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72,
0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72,
0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x46, 0x0a, 0x0e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x48,
0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x74, 0x74,
0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x65, 0x74, 0x74, 0x6c,
0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x02,
0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x22, 0x11,
0x0a, 0x0f, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e,
0x74, 0x22, 0xdf, 0x01, 0x0a, 0x0d, 0x4c, 0x69, 0x6e, 0x6b, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76,
0x65, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x13, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74,
0x6c, 0x63, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x3d, 0x0a, 0x0c,
0x77, 0x69, 0x72, 0x65, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75,
0x72, 0x65, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x0b,
0x77, 0x69, 0x72, 0x65, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x3f, 0x0a, 0x0e, 0x66,
0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20,
0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e,
0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x52, 0x0d, 0x66,
0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x25, 0x0a, 0x0e,
0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x04,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x53, 0x74, 0x72,
0x69, 0x6e, 0x67, 0x22, 0x8a, 0x01, 0x0a, 0x0d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53,
0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2d, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63,
0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73,
0x74, 0x61, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65,
0x12, 0x28, 0x0a, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65,
0x6d, 0x70, 0x74, 0x52, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04,
0x22, 0x3e, 0x0a, 0x0a, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x17,
0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52,
0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x74, 0x6c, 0x63, 0x5f,
0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x74, 0x6c, 0x63, 0x49, 0x64,
0x22, 0xa7, 0x06, 0x0a, 0x1b, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63,
0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x47, 0x0a, 0x14, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x69, 0x72,
0x63, 0x75, 0x69, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15,
0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x69, 0x72, 0x63, 0x75,
0x69, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43,
0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x69, 0x6e, 0x63,
0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61,
0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e,
0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x69,
0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x06,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x45, 0x78,
0x70, 0x69, 0x72, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f,
0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d,
0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x3b, 0x0a, 0x1a, 0x6f, 0x75, 0x74, 0x67, 0x6f,
0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x68,
0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x17, 0x6f, 0x75, 0x74,
0x67, 0x6f, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x68,
0x61, 0x6e, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67,
0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01,
0x28, 0x04, 0x52, 0x12, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75,
0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69,
0x6e, 0x67, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52,
0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12,
0x60, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64,
0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72,
0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49,
0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e,
0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74,
0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64,
0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x18,
0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x62,
0x12, 0x28, 0x0a, 0x10, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x68, 0x65,
0x69, 0x67, 0x68, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x6f,
0x46, 0x61, 0x69, 0x6c, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x74, 0x0a, 0x16, 0x69, 0x6e,
0x5f, 0x77, 0x69, 0x72, 0x65, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63,
0x6f, 0x72, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x72, 0x6f, 0x75,
0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74,
0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x2e, 0x49, 0x6e, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52,
0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x13, 0x69, 0x6e, 0x57,
0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73,
0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64,
0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
0x38, 0x01, 0x1a, 0x46, 0x0a, 0x18, 0x49, 0x6e, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74,
0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10,
0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79,
0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb9, 0x04, 0x0a, 0x1c, 0x46,
0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63,
0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x14, 0x69,
0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x5f,
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x6f, 0x75, 0x74,
0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79,
0x52, 0x12, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69,
0x74, 0x4b, 0x65, 0x79, 0x12, 0x3b, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63,
0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x48, 0x6f, 0x6c, 0x64, 0x46, 0x6f, 0x72, 0x77,
0x61, 0x72, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x27, 0x0a,
0x0f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x4d,
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x3d, 0x0a, 0x0c, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72,
0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x46, 0x61, 0x69,
0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x0b, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72,
0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75,
0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x69,
0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6f,
0x75, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07,
0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x6f, 0x75, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d,
0x73, 0x61, 0x74, 0x12, 0x78, 0x0a, 0x17, 0x6f, 0x75, 0x74, 0x5f, 0x77, 0x69, 0x72, 0x65, 0x5f,
0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x08,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63,
0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65,
0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4f, 0x75,
0x74, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72,
0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x14, 0x6f, 0x75, 0x74, 0x57, 0x69, 0x72, 0x65,
0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x47, 0x0a,
0x19, 0x4f, 0x75, 0x74, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65,
0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x82, 0x01, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43,
0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61,
0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72,
0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x41, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x1a, 0x0a, 0x18, 0x55,
0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x43, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x41, 0x6c,
0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x0a,
0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61,
0x70, 0x52, 0x09, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x73, 0x22, 0x44, 0x0a, 0x12,
0x41, 0x64, 0x64, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x2e, 0x0a, 0x0a, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x73,
0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41,
0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x52, 0x09, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61,
0x70, 0x73, 0x22, 0x46, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x69, 0x61,
0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x0a, 0x61, 0x6c,
0x69, 0x61, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f,
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x52,
0x09, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x73, 0x22, 0x47, 0x0a, 0x15, 0x44, 0x65,
0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x0a, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6d, 0x61, 0x70,
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x41, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x52, 0x09, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x4d,
0x61, 0x70, 0x73, 0x2a, 0x81, 0x04, 0x0a, 0x0d, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x44,
0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e,
0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x10,
0x01, 0x12, 0x10, 0x0a, 0x0c, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x43, 0x4f, 0x44,
0x45, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x4e, 0x4f, 0x54, 0x5f,
0x45, 0x4c, 0x49, 0x47, 0x49, 0x42, 0x4c, 0x45, 0x10, 0x03, 0x12, 0x14, 0x0a, 0x10, 0x4f, 0x4e,
0x5f, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x04,
0x12, 0x14, 0x0a, 0x10, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x45, 0x58, 0x43, 0x45, 0x45, 0x44, 0x53,
0x5f, 0x4d, 0x41, 0x58, 0x10, 0x05, 0x12, 0x18, 0x0a, 0x14, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46,
0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x06,
0x12, 0x16, 0x0a, 0x12, 0x49, 0x4e, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x46,
0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x10, 0x07, 0x12, 0x13, 0x0a, 0x0f, 0x48, 0x54, 0x4c, 0x43,
0x5f, 0x41, 0x44, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x08, 0x12, 0x15, 0x0a,
0x11, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x53, 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c,
0x45, 0x44, 0x10, 0x09, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f,
0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x0a, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e,
0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x44, 0x45, 0x52, 0x50, 0x41, 0x49, 0x44, 0x10,
0x0b, 0x12, 0x1b, 0x0a, 0x17, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x45, 0x58, 0x50,
0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x53, 0x4f, 0x4f, 0x4e, 0x10, 0x0c, 0x12, 0x14,
0x0a, 0x10, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x4f, 0x50,
0x45, 0x4e, 0x10, 0x0d, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x50, 0x50, 0x5f, 0x49, 0x4e, 0x56, 0x4f,
0x49, 0x43, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x0e, 0x12, 0x14, 0x0a,
0x10, 0x41, 0x44, 0x44, 0x52, 0x45, 0x53, 0x53, 0x5f, 0x4d, 0x49, 0x53, 0x4d, 0x41, 0x54, 0x43,
0x48, 0x10, 0x0f, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c,
0x5f, 0x4d, 0x49, 0x53, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x10, 0x10, 0x12, 0x15, 0x0a, 0x11, 0x53,
0x45, 0x54, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x4c, 0x4f, 0x57,
0x10, 0x11, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x45, 0x54, 0x5f, 0x4f, 0x56, 0x45, 0x52, 0x50, 0x41,
0x49, 0x44, 0x10, 0x12, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f,
0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x10, 0x13, 0x12, 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x56,
0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4b, 0x45, 0x59, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x14, 0x12, 0x13,
0x0a, 0x0f, 0x4d, 0x50, 0x50, 0x5f, 0x49, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53,
0x53, 0x10, 0x15, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x49, 0x52, 0x43, 0x55, 0x4c, 0x41, 0x52, 0x5f,
0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x16, 0x2a, 0xae, 0x01, 0x0a, 0x0c, 0x50, 0x61, 0x79, 0x6d,
0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x5f, 0x46,
0x4c, 0x49, 0x47, 0x48, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45,
0x45, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44,
0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x46, 0x41,
0x49, 0x4c, 0x45, 0x44, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x03, 0x12,
0x10, 0x0a, 0x0c, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10,
0x04, 0x12, 0x24, 0x0a, 0x20, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x49, 0x4e, 0x43, 0x4f,
0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45,
0x54, 0x41, 0x49, 0x4c, 0x53, 0x10, 0x05, 0x12, 0x1f, 0x0a, 0x1b, 0x46, 0x41, 0x49, 0x4c, 0x45,
0x44, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42,
0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x06, 0x2a, 0x51, 0x0a, 0x18, 0x52, 0x65, 0x73, 0x6f,
0x6c, 0x76, 0x65, 0x48, 0x6f, 0x6c, 0x64, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x41, 0x63,
0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x10, 0x00,
0x12, 0x08, 0x0a, 0x04, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x45,
0x53, 0x55, 0x4d, 0x45, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x52, 0x45, 0x53, 0x55, 0x4d, 0x45,
0x5f, 0x4d, 0x4f, 0x44, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x03, 0x2a, 0x35, 0x0a, 0x10, 0x43,
0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12,
0x0a, 0x0a, 0x06, 0x45, 0x4e, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x44,
0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x55, 0x54, 0x4f,
0x10, 0x02, 0x32, 0xe8, 0x0d, 0x0a, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x12, 0x40, 0x0a,
0x0d, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x32, 0x12, 0x1d,
0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50,
0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12,
0x42, 0x0a, 0x0e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x56,
0x32, 0x12, 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72,
0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e,
0x74, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0d, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d,
0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63,
0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61,
0x79, 0x6d, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x4b, 0x0a, 0x10, 0x45, 0x73, 0x74, 0x69, 0x6d,
0x61, 0x74, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, 0x1a, 0x2e, 0x72, 0x6f,
0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72,
0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f,
0x75, 0x74, 0x65, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e,
0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53,
0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x42, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x54,
0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x56, 0x32, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65,
0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x12, 0x64, 0x0a, 0x13, 0x52,
0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72,
0x6f, 0x6c, 0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52,
0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72,
0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x72, 0x6f, 0x75, 0x74,
0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69,
0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x64, 0x0a, 0x13, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f,
0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65,
0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f,
0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x26, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72,
0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x15, 0x58, 0x49, 0x6d, 0x70, 0x6f,
0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x12, 0x27, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x58, 0x49, 0x6d,
0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72,
0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x72, 0x6f, 0x75, 0x74,
0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, 0x73,
0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f,
0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x29,
0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x69,
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66,
0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x6f, 0x75, 0x74,
0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e,
0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73,
0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
0x12, 0x29, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74,
0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x6f,
0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69,
0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x10, 0x51, 0x75, 0x65, 0x72, 0x79,
0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x22, 0x2e, 0x72, 0x6f,
0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f,
0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x23, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72,
0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75,
0x74, 0x65, 0x12, 0x1c, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x42,
0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x69,
0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x54, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63,
0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72,
0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63,
0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e,
0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76,
0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79,
0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63,
0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e,
0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x03, 0x88,
0x02, 0x01, 0x30, 0x01, 0x12, 0x4f, 0x0a, 0x0c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79,
0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63,
0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63,
0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x03,
0x88, 0x02, 0x01, 0x30, 0x01, 0x12, 0x66, 0x0a, 0x0f, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74,
0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x27, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65,
0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63,
0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x1a, 0x26, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f,
0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65,
0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x5b, 0x0a,
0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75,
0x73, 0x12, 0x22, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70,
0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70,
0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74,
0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x14, 0x58, 0x41,
0x64, 0x64, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x41, 0x6c, 0x69, 0x61, 0x73,
0x65, 0x73, 0x12, 0x1c, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x41,
0x64, 0x64, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64,
0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x5c, 0x0a, 0x17, 0x58, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43,
0x68, 0x61, 0x6e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x1f, 0x2e, 0x72, 0x6f, 0x75,
0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x69,
0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x72, 0x6f,
0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c,
0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x31, 0x5a,
0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68,
0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64,
0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_routerrpc_router_proto_rawDescOnce sync.Once
file_routerrpc_router_proto_rawDescData = file_routerrpc_router_proto_rawDesc
)
func file_routerrpc_router_proto_rawDescGZIP() []byte {
file_routerrpc_router_proto_rawDescOnce.Do(func() {
file_routerrpc_router_proto_rawDescData = protoimpl.X.CompressGZIP(file_routerrpc_router_proto_rawDescData)
})
return file_routerrpc_router_proto_rawDescData
}
var file_routerrpc_router_proto_enumTypes = make([]protoimpl.EnumInfo, 6)
var file_routerrpc_router_proto_msgTypes = make([]protoimpl.MessageInfo, 52)
var file_routerrpc_router_proto_goTypes = []interface{}{
(FailureDetail)(0), // 0: routerrpc.FailureDetail
(PaymentState)(0), // 1: routerrpc.PaymentState
(ResolveHoldForwardAction)(0), // 2: routerrpc.ResolveHoldForwardAction
(ChanStatusAction)(0), // 3: routerrpc.ChanStatusAction
(MissionControlConfig_ProbabilityModel)(0), // 4: routerrpc.MissionControlConfig.ProbabilityModel
(HtlcEvent_EventType)(0), // 5: routerrpc.HtlcEvent.EventType
(*SendPaymentRequest)(nil), // 6: routerrpc.SendPaymentRequest
(*TrackPaymentRequest)(nil), // 7: routerrpc.TrackPaymentRequest
(*TrackPaymentsRequest)(nil), // 8: routerrpc.TrackPaymentsRequest
(*RouteFeeRequest)(nil), // 9: routerrpc.RouteFeeRequest
(*RouteFeeResponse)(nil), // 10: routerrpc.RouteFeeResponse
(*SendToRouteRequest)(nil), // 11: routerrpc.SendToRouteRequest
(*SendToRouteResponse)(nil), // 12: routerrpc.SendToRouteResponse
(*ResetMissionControlRequest)(nil), // 13: routerrpc.ResetMissionControlRequest
(*ResetMissionControlResponse)(nil), // 14: routerrpc.ResetMissionControlResponse
(*QueryMissionControlRequest)(nil), // 15: routerrpc.QueryMissionControlRequest
(*QueryMissionControlResponse)(nil), // 16: routerrpc.QueryMissionControlResponse
(*XImportMissionControlRequest)(nil), // 17: routerrpc.XImportMissionControlRequest
(*XImportMissionControlResponse)(nil), // 18: routerrpc.XImportMissionControlResponse
(*PairHistory)(nil), // 19: routerrpc.PairHistory
(*PairData)(nil), // 20: routerrpc.PairData
(*GetMissionControlConfigRequest)(nil), // 21: routerrpc.GetMissionControlConfigRequest
(*GetMissionControlConfigResponse)(nil), // 22: routerrpc.GetMissionControlConfigResponse
(*SetMissionControlConfigRequest)(nil), // 23: routerrpc.SetMissionControlConfigRequest
(*SetMissionControlConfigResponse)(nil), // 24: routerrpc.SetMissionControlConfigResponse
(*MissionControlConfig)(nil), // 25: routerrpc.MissionControlConfig
(*BimodalParameters)(nil), // 26: routerrpc.BimodalParameters
(*AprioriParameters)(nil), // 27: routerrpc.AprioriParameters
(*QueryProbabilityRequest)(nil), // 28: routerrpc.QueryProbabilityRequest
(*QueryProbabilityResponse)(nil), // 29: routerrpc.QueryProbabilityResponse
(*BuildRouteRequest)(nil), // 30: routerrpc.BuildRouteRequest
(*BuildRouteResponse)(nil), // 31: routerrpc.BuildRouteResponse
(*SubscribeHtlcEventsRequest)(nil), // 32: routerrpc.SubscribeHtlcEventsRequest
(*HtlcEvent)(nil), // 33: routerrpc.HtlcEvent
(*HtlcInfo)(nil), // 34: routerrpc.HtlcInfo
(*ForwardEvent)(nil), // 35: routerrpc.ForwardEvent
(*ForwardFailEvent)(nil), // 36: routerrpc.ForwardFailEvent
(*SettleEvent)(nil), // 37: routerrpc.SettleEvent
(*FinalHtlcEvent)(nil), // 38: routerrpc.FinalHtlcEvent
(*SubscribedEvent)(nil), // 39: routerrpc.SubscribedEvent
(*LinkFailEvent)(nil), // 40: routerrpc.LinkFailEvent
(*PaymentStatus)(nil), // 41: routerrpc.PaymentStatus
(*CircuitKey)(nil), // 42: routerrpc.CircuitKey
(*ForwardHtlcInterceptRequest)(nil), // 43: routerrpc.ForwardHtlcInterceptRequest
(*ForwardHtlcInterceptResponse)(nil), // 44: routerrpc.ForwardHtlcInterceptResponse
(*UpdateChanStatusRequest)(nil), // 45: routerrpc.UpdateChanStatusRequest
(*UpdateChanStatusResponse)(nil), // 46: routerrpc.UpdateChanStatusResponse
(*AddAliasesRequest)(nil), // 47: routerrpc.AddAliasesRequest
(*AddAliasesResponse)(nil), // 48: routerrpc.AddAliasesResponse
(*DeleteAliasesRequest)(nil), // 49: routerrpc.DeleteAliasesRequest
(*DeleteAliasesResponse)(nil), // 50: routerrpc.DeleteAliasesResponse
nil, // 51: routerrpc.SendPaymentRequest.DestCustomRecordsEntry
nil, // 52: routerrpc.SendPaymentRequest.FirstHopCustomRecordsEntry
nil, // 53: routerrpc.SendToRouteRequest.FirstHopCustomRecordsEntry
nil, // 54: routerrpc.BuildRouteRequest.FirstHopCustomRecordsEntry
nil, // 55: routerrpc.ForwardHtlcInterceptRequest.CustomRecordsEntry
nil, // 56: routerrpc.ForwardHtlcInterceptRequest.InWireCustomRecordsEntry
nil, // 57: routerrpc.ForwardHtlcInterceptResponse.OutWireCustomRecordsEntry
(*lnrpc.RouteHint)(nil), // 58: lnrpc.RouteHint
(lnrpc.FeatureBit)(0), // 59: lnrpc.FeatureBit
(lnrpc.PaymentFailureReason)(0), // 60: lnrpc.PaymentFailureReason
(*lnrpc.Route)(nil), // 61: lnrpc.Route
(*lnrpc.Failure)(nil), // 62: lnrpc.Failure
(lnrpc.Failure_FailureCode)(0), // 63: lnrpc.Failure.FailureCode
(*lnrpc.HTLCAttempt)(nil), // 64: lnrpc.HTLCAttempt
(*lnrpc.ChannelPoint)(nil), // 65: lnrpc.ChannelPoint
(*lnrpc.AliasMap)(nil), // 66: lnrpc.AliasMap
(*lnrpc.Payment)(nil), // 67: lnrpc.Payment
}
var file_routerrpc_router_proto_depIdxs = []int32{
58, // 0: routerrpc.SendPaymentRequest.route_hints:type_name -> lnrpc.RouteHint
51, // 1: routerrpc.SendPaymentRequest.dest_custom_records:type_name -> routerrpc.SendPaymentRequest.DestCustomRecordsEntry
59, // 2: routerrpc.SendPaymentRequest.dest_features:type_name -> lnrpc.FeatureBit
52, // 3: routerrpc.SendPaymentRequest.first_hop_custom_records:type_name -> routerrpc.SendPaymentRequest.FirstHopCustomRecordsEntry
60, // 4: routerrpc.RouteFeeResponse.failure_reason:type_name -> lnrpc.PaymentFailureReason
61, // 5: routerrpc.SendToRouteRequest.route:type_name -> lnrpc.Route
53, // 6: routerrpc.SendToRouteRequest.first_hop_custom_records:type_name -> routerrpc.SendToRouteRequest.FirstHopCustomRecordsEntry
62, // 7: routerrpc.SendToRouteResponse.failure:type_name -> lnrpc.Failure
19, // 8: routerrpc.QueryMissionControlResponse.pairs:type_name -> routerrpc.PairHistory
19, // 9: routerrpc.XImportMissionControlRequest.pairs:type_name -> routerrpc.PairHistory
20, // 10: routerrpc.PairHistory.history:type_name -> routerrpc.PairData
25, // 11: routerrpc.GetMissionControlConfigResponse.config:type_name -> routerrpc.MissionControlConfig
25, // 12: routerrpc.SetMissionControlConfigRequest.config:type_name -> routerrpc.MissionControlConfig
4, // 13: routerrpc.MissionControlConfig.model:type_name -> routerrpc.MissionControlConfig.ProbabilityModel
27, // 14: routerrpc.MissionControlConfig.apriori:type_name -> routerrpc.AprioriParameters
26, // 15: routerrpc.MissionControlConfig.bimodal:type_name -> routerrpc.BimodalParameters
20, // 16: routerrpc.QueryProbabilityResponse.history:type_name -> routerrpc.PairData
54, // 17: routerrpc.BuildRouteRequest.first_hop_custom_records:type_name -> routerrpc.BuildRouteRequest.FirstHopCustomRecordsEntry
61, // 18: routerrpc.BuildRouteResponse.route:type_name -> lnrpc.Route
5, // 19: routerrpc.HtlcEvent.event_type:type_name -> routerrpc.HtlcEvent.EventType
35, // 20: routerrpc.HtlcEvent.forward_event:type_name -> routerrpc.ForwardEvent
36, // 21: routerrpc.HtlcEvent.forward_fail_event:type_name -> routerrpc.ForwardFailEvent
37, // 22: routerrpc.HtlcEvent.settle_event:type_name -> routerrpc.SettleEvent
40, // 23: routerrpc.HtlcEvent.link_fail_event:type_name -> routerrpc.LinkFailEvent
39, // 24: routerrpc.HtlcEvent.subscribed_event:type_name -> routerrpc.SubscribedEvent
38, // 25: routerrpc.HtlcEvent.final_htlc_event:type_name -> routerrpc.FinalHtlcEvent
34, // 26: routerrpc.ForwardEvent.info:type_name -> routerrpc.HtlcInfo
34, // 27: routerrpc.LinkFailEvent.info:type_name -> routerrpc.HtlcInfo
63, // 28: routerrpc.LinkFailEvent.wire_failure:type_name -> lnrpc.Failure.FailureCode
0, // 29: routerrpc.LinkFailEvent.failure_detail:type_name -> routerrpc.FailureDetail
1, // 30: routerrpc.PaymentStatus.state:type_name -> routerrpc.PaymentState
64, // 31: routerrpc.PaymentStatus.htlcs:type_name -> lnrpc.HTLCAttempt
42, // 32: routerrpc.ForwardHtlcInterceptRequest.incoming_circuit_key:type_name -> routerrpc.CircuitKey
55, // 33: routerrpc.ForwardHtlcInterceptRequest.custom_records:type_name -> routerrpc.ForwardHtlcInterceptRequest.CustomRecordsEntry
56, // 34: routerrpc.ForwardHtlcInterceptRequest.in_wire_custom_records:type_name -> routerrpc.ForwardHtlcInterceptRequest.InWireCustomRecordsEntry
42, // 35: routerrpc.ForwardHtlcInterceptResponse.incoming_circuit_key:type_name -> routerrpc.CircuitKey
2, // 36: routerrpc.ForwardHtlcInterceptResponse.action:type_name -> routerrpc.ResolveHoldForwardAction
63, // 37: routerrpc.ForwardHtlcInterceptResponse.failure_code:type_name -> lnrpc.Failure.FailureCode
57, // 38: routerrpc.ForwardHtlcInterceptResponse.out_wire_custom_records:type_name -> routerrpc.ForwardHtlcInterceptResponse.OutWireCustomRecordsEntry
65, // 39: routerrpc.UpdateChanStatusRequest.chan_point:type_name -> lnrpc.ChannelPoint
3, // 40: routerrpc.UpdateChanStatusRequest.action:type_name -> routerrpc.ChanStatusAction
66, // 41: routerrpc.AddAliasesRequest.alias_maps:type_name -> lnrpc.AliasMap
66, // 42: routerrpc.AddAliasesResponse.alias_maps:type_name -> lnrpc.AliasMap
66, // 43: routerrpc.DeleteAliasesRequest.alias_maps:type_name -> lnrpc.AliasMap
66, // 44: routerrpc.DeleteAliasesResponse.alias_maps:type_name -> lnrpc.AliasMap
6, // 45: routerrpc.Router.SendPaymentV2:input_type -> routerrpc.SendPaymentRequest
7, // 46: routerrpc.Router.TrackPaymentV2:input_type -> routerrpc.TrackPaymentRequest
8, // 47: routerrpc.Router.TrackPayments:input_type -> routerrpc.TrackPaymentsRequest
9, // 48: routerrpc.Router.EstimateRouteFee:input_type -> routerrpc.RouteFeeRequest
11, // 49: routerrpc.Router.SendToRoute:input_type -> routerrpc.SendToRouteRequest
11, // 50: routerrpc.Router.SendToRouteV2:input_type -> routerrpc.SendToRouteRequest
13, // 51: routerrpc.Router.ResetMissionControl:input_type -> routerrpc.ResetMissionControlRequest
15, // 52: routerrpc.Router.QueryMissionControl:input_type -> routerrpc.QueryMissionControlRequest
17, // 53: routerrpc.Router.XImportMissionControl:input_type -> routerrpc.XImportMissionControlRequest
21, // 54: routerrpc.Router.GetMissionControlConfig:input_type -> routerrpc.GetMissionControlConfigRequest
23, // 55: routerrpc.Router.SetMissionControlConfig:input_type -> routerrpc.SetMissionControlConfigRequest
28, // 56: routerrpc.Router.QueryProbability:input_type -> routerrpc.QueryProbabilityRequest
30, // 57: routerrpc.Router.BuildRoute:input_type -> routerrpc.BuildRouteRequest
32, // 58: routerrpc.Router.SubscribeHtlcEvents:input_type -> routerrpc.SubscribeHtlcEventsRequest
6, // 59: routerrpc.Router.SendPayment:input_type -> routerrpc.SendPaymentRequest
7, // 60: routerrpc.Router.TrackPayment:input_type -> routerrpc.TrackPaymentRequest
44, // 61: routerrpc.Router.HtlcInterceptor:input_type -> routerrpc.ForwardHtlcInterceptResponse
45, // 62: routerrpc.Router.UpdateChanStatus:input_type -> routerrpc.UpdateChanStatusRequest
47, // 63: routerrpc.Router.XAddLocalChanAliases:input_type -> routerrpc.AddAliasesRequest
49, // 64: routerrpc.Router.XDeleteLocalChanAliases:input_type -> routerrpc.DeleteAliasesRequest
67, // 65: routerrpc.Router.SendPaymentV2:output_type -> lnrpc.Payment
67, // 66: routerrpc.Router.TrackPaymentV2:output_type -> lnrpc.Payment
67, // 67: routerrpc.Router.TrackPayments:output_type -> lnrpc.Payment
10, // 68: routerrpc.Router.EstimateRouteFee:output_type -> routerrpc.RouteFeeResponse
12, // 69: routerrpc.Router.SendToRoute:output_type -> routerrpc.SendToRouteResponse
64, // 70: routerrpc.Router.SendToRouteV2:output_type -> lnrpc.HTLCAttempt
14, // 71: routerrpc.Router.ResetMissionControl:output_type -> routerrpc.ResetMissionControlResponse
16, // 72: routerrpc.Router.QueryMissionControl:output_type -> routerrpc.QueryMissionControlResponse
18, // 73: routerrpc.Router.XImportMissionControl:output_type -> routerrpc.XImportMissionControlResponse
22, // 74: routerrpc.Router.GetMissionControlConfig:output_type -> routerrpc.GetMissionControlConfigResponse
24, // 75: routerrpc.Router.SetMissionControlConfig:output_type -> routerrpc.SetMissionControlConfigResponse
29, // 76: routerrpc.Router.QueryProbability:output_type -> routerrpc.QueryProbabilityResponse
31, // 77: routerrpc.Router.BuildRoute:output_type -> routerrpc.BuildRouteResponse
33, // 78: routerrpc.Router.SubscribeHtlcEvents:output_type -> routerrpc.HtlcEvent
41, // 79: routerrpc.Router.SendPayment:output_type -> routerrpc.PaymentStatus
41, // 80: routerrpc.Router.TrackPayment:output_type -> routerrpc.PaymentStatus
43, // 81: routerrpc.Router.HtlcInterceptor:output_type -> routerrpc.ForwardHtlcInterceptRequest
46, // 82: routerrpc.Router.UpdateChanStatus:output_type -> routerrpc.UpdateChanStatusResponse
48, // 83: routerrpc.Router.XAddLocalChanAliases:output_type -> routerrpc.AddAliasesResponse
50, // 84: routerrpc.Router.XDeleteLocalChanAliases:output_type -> routerrpc.DeleteAliasesResponse
65, // [65:85] is the sub-list for method output_type
45, // [45:65] is the sub-list for method input_type
45, // [45:45] is the sub-list for extension type_name
45, // [45:45] is the sub-list for extension extendee
0, // [0:45] is the sub-list for field type_name
}
func init() { file_routerrpc_router_proto_init() }
func file_routerrpc_router_proto_init() {
if File_routerrpc_router_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_routerrpc_router_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SendPaymentRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TrackPaymentRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TrackPaymentsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RouteFeeRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RouteFeeResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SendToRouteRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SendToRouteResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ResetMissionControlRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ResetMissionControlResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*QueryMissionControlRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*QueryMissionControlResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*XImportMissionControlRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*XImportMissionControlResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PairHistory); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PairData); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetMissionControlConfigRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetMissionControlConfigResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SetMissionControlConfigRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SetMissionControlConfigResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MissionControlConfig); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BimodalParameters); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AprioriParameters); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*QueryProbabilityRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*QueryProbabilityResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BuildRouteRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BuildRouteResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SubscribeHtlcEventsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HtlcEvent); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HtlcInfo); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ForwardEvent); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ForwardFailEvent); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SettleEvent); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FinalHtlcEvent); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SubscribedEvent); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*LinkFailEvent); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PaymentStatus); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CircuitKey); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ForwardHtlcInterceptRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ForwardHtlcInterceptResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UpdateChanStatusRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UpdateChanStatusResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AddAliasesRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AddAliasesResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DeleteAliasesRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_routerrpc_router_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DeleteAliasesResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_routerrpc_router_proto_msgTypes[19].OneofWrappers = []interface{}{
(*MissionControlConfig_Apriori)(nil),
(*MissionControlConfig_Bimodal)(nil),
}
file_routerrpc_router_proto_msgTypes[27].OneofWrappers = []interface{}{
(*HtlcEvent_ForwardEvent)(nil),
(*HtlcEvent_ForwardFailEvent)(nil),
(*HtlcEvent_SettleEvent)(nil),
(*HtlcEvent_LinkFailEvent)(nil),
(*HtlcEvent_SubscribedEvent)(nil),
(*HtlcEvent_FinalHtlcEvent)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_routerrpc_router_proto_rawDesc,
NumEnums: 6,
NumMessages: 52,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_routerrpc_router_proto_goTypes,
DependencyIndexes: file_routerrpc_router_proto_depIdxs,
EnumInfos: file_routerrpc_router_proto_enumTypes,
MessageInfos: file_routerrpc_router_proto_msgTypes,
}.Build()
File_routerrpc_router_proto = out.File
file_routerrpc_router_proto_rawDesc = nil
file_routerrpc_router_proto_goTypes = nil
file_routerrpc_router_proto_depIdxs = nil
}
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: routerrpc/router.proto
/*
Package routerrpc is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package routerrpc
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
func request_Router_SendPaymentV2_0(ctx context.Context, marshaler runtime.Marshaler, client RouterClient, req *http.Request, pathParams map[string]string) (Router_SendPaymentV2Client, runtime.ServerMetadata, error) {
var protoReq SendPaymentRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
stream, err := client.SendPaymentV2(ctx, &protoReq)
if err != nil {
return nil, metadata, err
}
header, err := stream.Header()
if err != nil {
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
var (
filter_Router_TrackPaymentV2_0 = &utilities.DoubleArray{Encoding: map[string]int{"payment_hash": 0, "paymentHash": 1}, Base: []int{1, 1, 2, 0, 0}, Check: []int{0, 1, 1, 2, 3}}
)
func request_Router_TrackPaymentV2_0(ctx context.Context, marshaler runtime.Marshaler, client RouterClient, req *http.Request, pathParams map[string]string) (Router_TrackPaymentV2Client, runtime.ServerMetadata, error) {
var protoReq TrackPaymentRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["payment_hash"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "payment_hash")
}
protoReq.PaymentHash, err = runtime.Bytes(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "payment_hash", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Router_TrackPaymentV2_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
stream, err := client.TrackPaymentV2(ctx, &protoReq)
if err != nil {
return nil, metadata, err
}
header, err := stream.Header()
if err != nil {
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
var (
filter_Router_TrackPayments_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Router_TrackPayments_0(ctx context.Context, marshaler runtime.Marshaler, client RouterClient, req *http.Request, pathParams map[string]string) (Router_TrackPaymentsClient, runtime.ServerMetadata, error) {
var protoReq TrackPaymentsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Router_TrackPayments_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
stream, err := client.TrackPayments(ctx, &protoReq)
if err != nil {
return nil, metadata, err
}
header, err := stream.Header()
if err != nil {
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
func request_Router_EstimateRouteFee_0(ctx context.Context, marshaler runtime.Marshaler, client RouterClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq RouteFeeRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.EstimateRouteFee(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Router_EstimateRouteFee_0(ctx context.Context, marshaler runtime.Marshaler, server RouterServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq RouteFeeRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.EstimateRouteFee(ctx, &protoReq)
return msg, metadata, err
}
func request_Router_SendToRouteV2_0(ctx context.Context, marshaler runtime.Marshaler, client RouterClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SendToRouteRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.SendToRouteV2(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Router_SendToRouteV2_0(ctx context.Context, marshaler runtime.Marshaler, server RouterServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SendToRouteRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.SendToRouteV2(ctx, &protoReq)
return msg, metadata, err
}
func request_Router_ResetMissionControl_0(ctx context.Context, marshaler runtime.Marshaler, client RouterClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ResetMissionControlRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ResetMissionControl(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Router_ResetMissionControl_0(ctx context.Context, marshaler runtime.Marshaler, server RouterServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ResetMissionControlRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ResetMissionControl(ctx, &protoReq)
return msg, metadata, err
}
func request_Router_QueryMissionControl_0(ctx context.Context, marshaler runtime.Marshaler, client RouterClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq QueryMissionControlRequest
var metadata runtime.ServerMetadata
msg, err := client.QueryMissionControl(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Router_QueryMissionControl_0(ctx context.Context, marshaler runtime.Marshaler, server RouterServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq QueryMissionControlRequest
var metadata runtime.ServerMetadata
msg, err := server.QueryMissionControl(ctx, &protoReq)
return msg, metadata, err
}
func request_Router_XImportMissionControl_0(ctx context.Context, marshaler runtime.Marshaler, client RouterClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq XImportMissionControlRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.XImportMissionControl(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Router_XImportMissionControl_0(ctx context.Context, marshaler runtime.Marshaler, server RouterServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq XImportMissionControlRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.XImportMissionControl(ctx, &protoReq)
return msg, metadata, err
}
func request_Router_GetMissionControlConfig_0(ctx context.Context, marshaler runtime.Marshaler, client RouterClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetMissionControlConfigRequest
var metadata runtime.ServerMetadata
msg, err := client.GetMissionControlConfig(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Router_GetMissionControlConfig_0(ctx context.Context, marshaler runtime.Marshaler, server RouterServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetMissionControlConfigRequest
var metadata runtime.ServerMetadata
msg, err := server.GetMissionControlConfig(ctx, &protoReq)
return msg, metadata, err
}
func request_Router_SetMissionControlConfig_0(ctx context.Context, marshaler runtime.Marshaler, client RouterClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SetMissionControlConfigRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.SetMissionControlConfig(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Router_SetMissionControlConfig_0(ctx context.Context, marshaler runtime.Marshaler, server RouterServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SetMissionControlConfigRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.SetMissionControlConfig(ctx, &protoReq)
return msg, metadata, err
}
func request_Router_QueryProbability_0(ctx context.Context, marshaler runtime.Marshaler, client RouterClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq QueryProbabilityRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["from_node"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "from_node")
}
protoReq.FromNode, err = runtime.Bytes(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "from_node", err)
}
val, ok = pathParams["to_node"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "to_node")
}
protoReq.ToNode, err = runtime.Bytes(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "to_node", err)
}
val, ok = pathParams["amt_msat"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "amt_msat")
}
protoReq.AmtMsat, err = runtime.Int64(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "amt_msat", err)
}
msg, err := client.QueryProbability(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Router_QueryProbability_0(ctx context.Context, marshaler runtime.Marshaler, server RouterServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq QueryProbabilityRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["from_node"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "from_node")
}
protoReq.FromNode, err = runtime.Bytes(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "from_node", err)
}
val, ok = pathParams["to_node"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "to_node")
}
protoReq.ToNode, err = runtime.Bytes(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "to_node", err)
}
val, ok = pathParams["amt_msat"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "amt_msat")
}
protoReq.AmtMsat, err = runtime.Int64(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "amt_msat", err)
}
msg, err := server.QueryProbability(ctx, &protoReq)
return msg, metadata, err
}
func request_Router_BuildRoute_0(ctx context.Context, marshaler runtime.Marshaler, client RouterClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq BuildRouteRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.BuildRoute(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Router_BuildRoute_0(ctx context.Context, marshaler runtime.Marshaler, server RouterServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq BuildRouteRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.BuildRoute(ctx, &protoReq)
return msg, metadata, err
}
func request_Router_SubscribeHtlcEvents_0(ctx context.Context, marshaler runtime.Marshaler, client RouterClient, req *http.Request, pathParams map[string]string) (Router_SubscribeHtlcEventsClient, runtime.ServerMetadata, error) {
var protoReq SubscribeHtlcEventsRequest
var metadata runtime.ServerMetadata
stream, err := client.SubscribeHtlcEvents(ctx, &protoReq)
if err != nil {
return nil, metadata, err
}
header, err := stream.Header()
if err != nil {
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
func request_Router_HtlcInterceptor_0(ctx context.Context, marshaler runtime.Marshaler, client RouterClient, req *http.Request, pathParams map[string]string) (Router_HtlcInterceptorClient, runtime.ServerMetadata, error) {
var metadata runtime.ServerMetadata
stream, err := client.HtlcInterceptor(ctx)
if err != nil {
grpclog.Infof("Failed to start streaming: %v", err)
return nil, metadata, err
}
dec := marshaler.NewDecoder(req.Body)
handleSend := func() error {
var protoReq ForwardHtlcInterceptResponse
err := dec.Decode(&protoReq)
if err == io.EOF {
return err
}
if err != nil {
grpclog.Infof("Failed to decode request: %v", err)
return err
}
if err := stream.Send(&protoReq); err != nil {
grpclog.Infof("Failed to send request: %v", err)
return err
}
return nil
}
go func() {
for {
if err := handleSend(); err != nil {
break
}
}
if err := stream.CloseSend(); err != nil {
grpclog.Infof("Failed to terminate client stream: %v", err)
}
}()
header, err := stream.Header()
if err != nil {
grpclog.Infof("Failed to get header from client: %v", err)
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
func request_Router_UpdateChanStatus_0(ctx context.Context, marshaler runtime.Marshaler, client RouterClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq UpdateChanStatusRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.UpdateChanStatus(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Router_UpdateChanStatus_0(ctx context.Context, marshaler runtime.Marshaler, server RouterServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq UpdateChanStatusRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.UpdateChanStatus(ctx, &protoReq)
return msg, metadata, err
}
func request_Router_XAddLocalChanAliases_0(ctx context.Context, marshaler runtime.Marshaler, client RouterClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq AddAliasesRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.XAddLocalChanAliases(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Router_XAddLocalChanAliases_0(ctx context.Context, marshaler runtime.Marshaler, server RouterServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq AddAliasesRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.XAddLocalChanAliases(ctx, &protoReq)
return msg, metadata, err
}
func request_Router_XDeleteLocalChanAliases_0(ctx context.Context, marshaler runtime.Marshaler, client RouterClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeleteAliasesRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.XDeleteLocalChanAliases(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Router_XDeleteLocalChanAliases_0(ctx context.Context, marshaler runtime.Marshaler, server RouterServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeleteAliasesRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.XDeleteLocalChanAliases(ctx, &protoReq)
return msg, metadata, err
}
// RegisterRouterHandlerServer registers the http handlers for service Router to "mux".
// UnaryRPC :call RouterServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterRouterHandlerFromEndpoint instead.
func RegisterRouterHandlerServer(ctx context.Context, mux *runtime.ServeMux, server RouterServer) error {
mux.Handle("POST", pattern_Router_SendPaymentV2_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
mux.Handle("GET", pattern_Router_TrackPaymentV2_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
mux.Handle("GET", pattern_Router_TrackPayments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
mux.Handle("POST", pattern_Router_EstimateRouteFee_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/routerrpc.Router/EstimateRouteFee", runtime.WithHTTPPathPattern("/v2/router/route/estimatefee"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Router_EstimateRouteFee_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_EstimateRouteFee_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Router_SendToRouteV2_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/routerrpc.Router/SendToRouteV2", runtime.WithHTTPPathPattern("/v2/router/route/send"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Router_SendToRouteV2_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_SendToRouteV2_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Router_ResetMissionControl_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/routerrpc.Router/ResetMissionControl", runtime.WithHTTPPathPattern("/v2/router/mc/reset"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Router_ResetMissionControl_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_ResetMissionControl_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Router_QueryMissionControl_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/routerrpc.Router/QueryMissionControl", runtime.WithHTTPPathPattern("/v2/router/mc"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Router_QueryMissionControl_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_QueryMissionControl_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Router_XImportMissionControl_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/routerrpc.Router/XImportMissionControl", runtime.WithHTTPPathPattern("/v2/router/x/importhistory"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Router_XImportMissionControl_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_XImportMissionControl_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Router_GetMissionControlConfig_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/routerrpc.Router/GetMissionControlConfig", runtime.WithHTTPPathPattern("/v2/router/mccfg"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Router_GetMissionControlConfig_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_GetMissionControlConfig_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Router_SetMissionControlConfig_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/routerrpc.Router/SetMissionControlConfig", runtime.WithHTTPPathPattern("/v2/router/mccfg"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Router_SetMissionControlConfig_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_SetMissionControlConfig_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Router_QueryProbability_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/routerrpc.Router/QueryProbability", runtime.WithHTTPPathPattern("/v2/router/mc/probability/{from_node}/{to_node}/{amt_msat}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Router_QueryProbability_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_QueryProbability_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Router_BuildRoute_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/routerrpc.Router/BuildRoute", runtime.WithHTTPPathPattern("/v2/router/route"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Router_BuildRoute_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_BuildRoute_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Router_SubscribeHtlcEvents_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
mux.Handle("POST", pattern_Router_HtlcInterceptor_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
mux.Handle("POST", pattern_Router_UpdateChanStatus_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/routerrpc.Router/UpdateChanStatus", runtime.WithHTTPPathPattern("/v2/router/updatechanstatus"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Router_UpdateChanStatus_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_UpdateChanStatus_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Router_XAddLocalChanAliases_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/routerrpc.Router/XAddLocalChanAliases", runtime.WithHTTPPathPattern("/v2/router/x/addaliases"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Router_XAddLocalChanAliases_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_XAddLocalChanAliases_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Router_XDeleteLocalChanAliases_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/routerrpc.Router/XDeleteLocalChanAliases", runtime.WithHTTPPathPattern("/v2/router/x/deletealiases"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Router_XDeleteLocalChanAliases_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_XDeleteLocalChanAliases_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterRouterHandlerFromEndpoint is same as RegisterRouterHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterRouterHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterRouterHandler(ctx, mux, conn)
}
// RegisterRouterHandler registers the http handlers for service Router to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterRouterHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterRouterHandlerClient(ctx, mux, NewRouterClient(conn))
}
// RegisterRouterHandlerClient registers the http handlers for service Router
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "RouterClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "RouterClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "RouterClient" to call the correct interceptors.
func RegisterRouterHandlerClient(ctx context.Context, mux *runtime.ServeMux, client RouterClient) error {
mux.Handle("POST", pattern_Router_SendPaymentV2_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/routerrpc.Router/SendPaymentV2", runtime.WithHTTPPathPattern("/v2/router/send"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Router_SendPaymentV2_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_SendPaymentV2_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Router_TrackPaymentV2_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/routerrpc.Router/TrackPaymentV2", runtime.WithHTTPPathPattern("/v2/router/track/{payment_hash}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Router_TrackPaymentV2_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_TrackPaymentV2_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Router_TrackPayments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/routerrpc.Router/TrackPayments", runtime.WithHTTPPathPattern("/v2/router/payments"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Router_TrackPayments_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_TrackPayments_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Router_EstimateRouteFee_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/routerrpc.Router/EstimateRouteFee", runtime.WithHTTPPathPattern("/v2/router/route/estimatefee"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Router_EstimateRouteFee_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_EstimateRouteFee_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Router_SendToRouteV2_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/routerrpc.Router/SendToRouteV2", runtime.WithHTTPPathPattern("/v2/router/route/send"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Router_SendToRouteV2_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_SendToRouteV2_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Router_ResetMissionControl_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/routerrpc.Router/ResetMissionControl", runtime.WithHTTPPathPattern("/v2/router/mc/reset"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Router_ResetMissionControl_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_ResetMissionControl_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Router_QueryMissionControl_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/routerrpc.Router/QueryMissionControl", runtime.WithHTTPPathPattern("/v2/router/mc"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Router_QueryMissionControl_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_QueryMissionControl_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Router_XImportMissionControl_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/routerrpc.Router/XImportMissionControl", runtime.WithHTTPPathPattern("/v2/router/x/importhistory"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Router_XImportMissionControl_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_XImportMissionControl_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Router_GetMissionControlConfig_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/routerrpc.Router/GetMissionControlConfig", runtime.WithHTTPPathPattern("/v2/router/mccfg"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Router_GetMissionControlConfig_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_GetMissionControlConfig_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Router_SetMissionControlConfig_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/routerrpc.Router/SetMissionControlConfig", runtime.WithHTTPPathPattern("/v2/router/mccfg"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Router_SetMissionControlConfig_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_SetMissionControlConfig_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Router_QueryProbability_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/routerrpc.Router/QueryProbability", runtime.WithHTTPPathPattern("/v2/router/mc/probability/{from_node}/{to_node}/{amt_msat}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Router_QueryProbability_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_QueryProbability_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Router_BuildRoute_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/routerrpc.Router/BuildRoute", runtime.WithHTTPPathPattern("/v2/router/route"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Router_BuildRoute_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_BuildRoute_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Router_SubscribeHtlcEvents_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/routerrpc.Router/SubscribeHtlcEvents", runtime.WithHTTPPathPattern("/v2/router/htlcevents"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Router_SubscribeHtlcEvents_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_SubscribeHtlcEvents_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Router_HtlcInterceptor_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/routerrpc.Router/HtlcInterceptor", runtime.WithHTTPPathPattern("/v2/router/htlcinterceptor"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Router_HtlcInterceptor_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_HtlcInterceptor_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Router_UpdateChanStatus_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/routerrpc.Router/UpdateChanStatus", runtime.WithHTTPPathPattern("/v2/router/updatechanstatus"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Router_UpdateChanStatus_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_UpdateChanStatus_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Router_XAddLocalChanAliases_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/routerrpc.Router/XAddLocalChanAliases", runtime.WithHTTPPathPattern("/v2/router/x/addaliases"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Router_XAddLocalChanAliases_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_XAddLocalChanAliases_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Router_XDeleteLocalChanAliases_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/routerrpc.Router/XDeleteLocalChanAliases", runtime.WithHTTPPathPattern("/v2/router/x/deletealiases"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Router_XDeleteLocalChanAliases_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Router_XDeleteLocalChanAliases_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_Router_SendPaymentV2_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "router", "send"}, ""))
pattern_Router_TrackPaymentV2_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v2", "router", "track", "payment_hash"}, ""))
pattern_Router_TrackPayments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "router", "payments"}, ""))
pattern_Router_EstimateRouteFee_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "router", "route", "estimatefee"}, ""))
pattern_Router_SendToRouteV2_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "router", "route", "send"}, ""))
pattern_Router_ResetMissionControl_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "router", "mc", "reset"}, ""))
pattern_Router_QueryMissionControl_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "router", "mc"}, ""))
pattern_Router_XImportMissionControl_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "router", "x", "importhistory"}, ""))
pattern_Router_GetMissionControlConfig_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "router", "mccfg"}, ""))
pattern_Router_SetMissionControlConfig_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "router", "mccfg"}, ""))
pattern_Router_QueryProbability_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 1, 0, 4, 1, 5, 5, 1, 0, 4, 1, 5, 6}, []string{"v2", "router", "mc", "probability", "from_node", "to_node", "amt_msat"}, ""))
pattern_Router_BuildRoute_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "router", "route"}, ""))
pattern_Router_SubscribeHtlcEvents_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "router", "htlcevents"}, ""))
pattern_Router_HtlcInterceptor_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "router", "htlcinterceptor"}, ""))
pattern_Router_UpdateChanStatus_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "router", "updatechanstatus"}, ""))
pattern_Router_XAddLocalChanAliases_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "router", "x", "addaliases"}, ""))
pattern_Router_XDeleteLocalChanAliases_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "router", "x", "deletealiases"}, ""))
)
var (
forward_Router_SendPaymentV2_0 = runtime.ForwardResponseStream
forward_Router_TrackPaymentV2_0 = runtime.ForwardResponseStream
forward_Router_TrackPayments_0 = runtime.ForwardResponseStream
forward_Router_EstimateRouteFee_0 = runtime.ForwardResponseMessage
forward_Router_SendToRouteV2_0 = runtime.ForwardResponseMessage
forward_Router_ResetMissionControl_0 = runtime.ForwardResponseMessage
forward_Router_QueryMissionControl_0 = runtime.ForwardResponseMessage
forward_Router_XImportMissionControl_0 = runtime.ForwardResponseMessage
forward_Router_GetMissionControlConfig_0 = runtime.ForwardResponseMessage
forward_Router_SetMissionControlConfig_0 = runtime.ForwardResponseMessage
forward_Router_QueryProbability_0 = runtime.ForwardResponseMessage
forward_Router_BuildRoute_0 = runtime.ForwardResponseMessage
forward_Router_SubscribeHtlcEvents_0 = runtime.ForwardResponseStream
forward_Router_HtlcInterceptor_0 = runtime.ForwardResponseStream
forward_Router_UpdateChanStatus_0 = runtime.ForwardResponseMessage
forward_Router_XAddLocalChanAliases_0 = runtime.ForwardResponseMessage
forward_Router_XDeleteLocalChanAliases_0 = runtime.ForwardResponseMessage
)
// Code generated by falafel 0.9.2. DO NOT EDIT.
// source: router.proto
package routerrpc
import (
"context"
gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protojson"
)
func RegisterRouterJSONCallbacks(registry map[string]func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error))) {
marshaler := &gateway.JSONPb{
MarshalOptions: protojson.MarshalOptions{
UseProtoNames: true,
EmitUnpopulated: true,
},
}
registry["routerrpc.Router.SendPaymentV2"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SendPaymentRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewRouterClient(conn)
stream, err := client.SendPaymentV2(ctx, req)
if err != nil {
callback("", err)
return
}
go func() {
for {
select {
case <-stream.Context().Done():
callback("", stream.Context().Err())
return
default:
}
resp, err := stream.Recv()
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}()
}
registry["routerrpc.Router.TrackPaymentV2"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &TrackPaymentRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewRouterClient(conn)
stream, err := client.TrackPaymentV2(ctx, req)
if err != nil {
callback("", err)
return
}
go func() {
for {
select {
case <-stream.Context().Done():
callback("", stream.Context().Err())
return
default:
}
resp, err := stream.Recv()
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}()
}
registry["routerrpc.Router.TrackPayments"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &TrackPaymentsRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewRouterClient(conn)
stream, err := client.TrackPayments(ctx, req)
if err != nil {
callback("", err)
return
}
go func() {
for {
select {
case <-stream.Context().Done():
callback("", stream.Context().Err())
return
default:
}
resp, err := stream.Recv()
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}()
}
registry["routerrpc.Router.EstimateRouteFee"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &RouteFeeRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewRouterClient(conn)
resp, err := client.EstimateRouteFee(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["routerrpc.Router.SendToRoute"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SendToRouteRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewRouterClient(conn)
resp, err := client.SendToRoute(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["routerrpc.Router.SendToRouteV2"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SendToRouteRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewRouterClient(conn)
resp, err := client.SendToRouteV2(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["routerrpc.Router.ResetMissionControl"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ResetMissionControlRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewRouterClient(conn)
resp, err := client.ResetMissionControl(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["routerrpc.Router.QueryMissionControl"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &QueryMissionControlRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewRouterClient(conn)
resp, err := client.QueryMissionControl(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["routerrpc.Router.XImportMissionControl"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &XImportMissionControlRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewRouterClient(conn)
resp, err := client.XImportMissionControl(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["routerrpc.Router.GetMissionControlConfig"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &GetMissionControlConfigRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewRouterClient(conn)
resp, err := client.GetMissionControlConfig(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["routerrpc.Router.SetMissionControlConfig"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SetMissionControlConfigRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewRouterClient(conn)
resp, err := client.SetMissionControlConfig(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["routerrpc.Router.QueryProbability"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &QueryProbabilityRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewRouterClient(conn)
resp, err := client.QueryProbability(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["routerrpc.Router.BuildRoute"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &BuildRouteRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewRouterClient(conn)
resp, err := client.BuildRoute(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["routerrpc.Router.SubscribeHtlcEvents"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SubscribeHtlcEventsRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewRouterClient(conn)
stream, err := client.SubscribeHtlcEvents(ctx, req)
if err != nil {
callback("", err)
return
}
go func() {
for {
select {
case <-stream.Context().Done():
callback("", stream.Context().Err())
return
default:
}
resp, err := stream.Recv()
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}()
}
registry["routerrpc.Router.SendPayment"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SendPaymentRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewRouterClient(conn)
stream, err := client.SendPayment(ctx, req)
if err != nil {
callback("", err)
return
}
go func() {
for {
select {
case <-stream.Context().Done():
callback("", stream.Context().Err())
return
default:
}
resp, err := stream.Recv()
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}()
}
registry["routerrpc.Router.TrackPayment"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &TrackPaymentRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewRouterClient(conn)
stream, err := client.TrackPayment(ctx, req)
if err != nil {
callback("", err)
return
}
go func() {
for {
select {
case <-stream.Context().Done():
callback("", stream.Context().Err())
return
default:
}
resp, err := stream.Recv()
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}()
}
registry["routerrpc.Router.UpdateChanStatus"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &UpdateChanStatusRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewRouterClient(conn)
resp, err := client.UpdateChanStatus(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["routerrpc.Router.XAddLocalChanAliases"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &AddAliasesRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewRouterClient(conn)
resp, err := client.XAddLocalChanAliases(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["routerrpc.Router.XDeleteLocalChanAliases"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &DeleteAliasesRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewRouterClient(conn)
resp, err := client.XDeleteLocalChanAliases(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}
package routerrpc
import (
"context"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
math "math"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/wire"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/feature"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/subscribe"
"github.com/lightningnetwork/lnd/zpay32"
"google.golang.org/protobuf/proto"
)
const (
// DefaultMaxParts is the default number of splits we'll possibly use
// for MPP when the user is attempting to send a payment.
//
// TODO(roasbeef): make this value dynamic based on expected number of
// attempts for given amount.
DefaultMaxParts = 16
// MaxPartsUpperLimit defines the maximum allowable number of splits
// for MPP/AMP when the user is attempting to send a payment.
MaxPartsUpperLimit = 1000
)
// RouterBackend contains the backend implementation of the router rpc sub
// server calls.
type RouterBackend struct {
// SelfNode is the vertex of the node sending the payment.
SelfNode route.Vertex
// FetchChannelCapacity is a closure that we'll use the fetch the total
// capacity of a channel to populate in responses.
FetchChannelCapacity func(chanID uint64) (btcutil.Amount, error)
// FetchAmountPairCapacity determines the maximal channel capacity
// between two nodes given a certain amount.
FetchAmountPairCapacity func(nodeFrom, nodeTo route.Vertex,
amount lnwire.MilliSatoshi) (btcutil.Amount, error)
// FetchChannelEndpoints returns the pubkeys of both endpoints of the
// given channel id.
FetchChannelEndpoints func(chanID uint64) (route.Vertex,
route.Vertex, error)
// FindRoute is a closure that abstracts away how we locate/query for
// routes.
FindRoute func(*routing.RouteRequest) (*route.Route, float64, error)
MissionControl MissionControl
// ActiveNetParams are the network parameters of the primary network
// that the route is operating on. This is necessary so we can ensure
// that we receive payment requests that send to destinations on our
// network.
ActiveNetParams *chaincfg.Params
// Tower is the ControlTower instance that is used to track pending
// payments.
Tower routing.ControlTower
// MaxTotalTimelock is the maximum total time lock a route is allowed to
// have.
MaxTotalTimelock uint32
// DefaultFinalCltvDelta is the default value used as final cltv delta
// when an RPC caller doesn't specify a value.
DefaultFinalCltvDelta uint16
// SubscribeHtlcEvents returns a subscription client for the node's
// htlc events.
SubscribeHtlcEvents func() (*subscribe.Client, error)
// InterceptableForwarder exposes the ability to intercept forward events
// by letting the router register a ForwardInterceptor.
InterceptableForwarder htlcswitch.InterceptableHtlcForwarder
// SetChannelEnabled exposes the ability to manually enable a channel.
SetChannelEnabled func(wire.OutPoint) error
// SetChannelDisabled exposes the ability to manually disable a channel
SetChannelDisabled func(wire.OutPoint) error
// SetChannelAuto exposes the ability to restore automatic channel state
// management after manually setting channel status.
SetChannelAuto func(wire.OutPoint) error
// UseStatusInitiated is a boolean that indicates whether the router
// should use the new status code `Payment_INITIATED`.
//
// TODO(yy): remove this config after the new status code is fully
// deployed to the network(v0.20.0).
UseStatusInitiated bool
// ParseCustomChannelData is a function that can be used to parse custom
// channel data from the first hop of a route.
ParseCustomChannelData func(message proto.Message) error
// ShouldSetExpEndorsement returns a boolean indicating whether the
// experimental endorsement bit should be set.
ShouldSetExpEndorsement func() bool
}
// MissionControl defines the mission control dependencies of routerrpc.
type MissionControl interface {
// GetProbability is expected to return the success probability of a
// payment from fromNode to toNode.
GetProbability(fromNode, toNode route.Vertex,
amt lnwire.MilliSatoshi, capacity btcutil.Amount) float64
// ResetHistory resets the history of MissionControl returning it to a
// state as if no payment attempts have been made.
ResetHistory() error
// GetHistorySnapshot takes a snapshot from the current mission control
// state and actual probability estimates.
GetHistorySnapshot() *routing.MissionControlSnapshot
// ImportHistory imports the mission control snapshot to our internal
// state. This import will only be applied in-memory, and will not be
// persisted across restarts.
ImportHistory(snapshot *routing.MissionControlSnapshot, force bool) error
// GetPairHistorySnapshot returns the stored history for a given node
// pair.
GetPairHistorySnapshot(fromNode,
toNode route.Vertex) routing.TimedPairResult
// GetConfig gets mission control's current config.
GetConfig() *routing.MissionControlConfig
// SetConfig sets mission control's config to the values provided, if
// they are valid.
SetConfig(cfg *routing.MissionControlConfig) error
}
// QueryRoutes attempts to query the daemons' Channel Router for a possible
// route to a target destination capable of carrying a specific amount of
// satoshis within the route's flow. The returned route contains the full
// details required to craft and send an HTLC, also including the necessary
// information that should be present within the Sphinx packet encapsulated
// within the HTLC.
//
// TODO(roasbeef): should return a slice of routes in reality * create separate
// PR to send based on well formatted route
func (r *RouterBackend) QueryRoutes(ctx context.Context,
in *lnrpc.QueryRoutesRequest) (*lnrpc.QueryRoutesResponse, error) {
routeReq, err := r.parseQueryRoutesRequest(in)
if err != nil {
return nil, err
}
// Query the channel router for a possible path to the destination that
// can carry `in.Amt` satoshis _including_ the total fee required on
// the route
route, successProb, err := r.FindRoute(routeReq)
if err != nil {
return nil, err
}
// For each valid route, we'll convert the result into the format
// required by the RPC system.
rpcRoute, err := r.MarshallRoute(route)
if err != nil {
return nil, err
}
routeResp := &lnrpc.QueryRoutesResponse{
Routes: []*lnrpc.Route{rpcRoute},
SuccessProb: successProb,
}
return routeResp, nil
}
func parsePubKey(key string) (route.Vertex, error) {
pubKeyBytes, err := hex.DecodeString(key)
if err != nil {
return route.Vertex{}, err
}
return route.NewVertexFromBytes(pubKeyBytes)
}
func (r *RouterBackend) parseIgnored(in *lnrpc.QueryRoutesRequest) (
map[route.Vertex]struct{}, map[routing.DirectedNodePair]struct{},
error) {
ignoredNodes := make(map[route.Vertex]struct{})
for _, ignorePubKey := range in.IgnoredNodes {
ignoreVertex, err := route.NewVertexFromBytes(ignorePubKey)
if err != nil {
return nil, nil, err
}
ignoredNodes[ignoreVertex] = struct{}{}
}
ignoredPairs := make(map[routing.DirectedNodePair]struct{})
// Convert deprecated ignoredEdges to pairs.
for _, ignoredEdge := range in.IgnoredEdges {
pair, err := r.rpcEdgeToPair(ignoredEdge)
if err != nil {
log.Warnf("Ignore channel %v skipped: %v",
ignoredEdge.ChannelId, err)
continue
}
ignoredPairs[pair] = struct{}{}
}
// Add ignored pairs to set.
for _, ignorePair := range in.IgnoredPairs {
from, err := route.NewVertexFromBytes(ignorePair.From)
if err != nil {
return nil, nil, err
}
to, err := route.NewVertexFromBytes(ignorePair.To)
if err != nil {
return nil, nil, err
}
pair := routing.NewDirectedNodePair(from, to)
ignoredPairs[pair] = struct{}{}
}
return ignoredNodes, ignoredPairs, nil
}
func (r *RouterBackend) parseQueryRoutesRequest(in *lnrpc.QueryRoutesRequest) (
*routing.RouteRequest, error) {
// Parse the hex-encoded source public key into a full public key that
// we can properly manipulate.
var sourcePubKey route.Vertex
if in.SourcePubKey != "" {
var err error
sourcePubKey, err = parsePubKey(in.SourcePubKey)
if err != nil {
return nil, err
}
} else {
// If no source is specified, use self.
sourcePubKey = r.SelfNode
}
// Currently, within the bootstrap phase of the network, we limit the
// largest payment size allotted to (2^32) - 1 mSAT or 4.29 million
// satoshis.
amt, err := lnrpc.UnmarshallAmt(in.Amt, in.AmtMsat)
if err != nil {
return nil, err
}
// Unmarshall restrictions from request.
feeLimit := lnrpc.CalculateFeeLimit(in.FeeLimit, amt)
// Since QueryRoutes allows having a different source other than
// ourselves, we'll only apply our max time lock if we are the source.
maxTotalTimelock := r.MaxTotalTimelock
if sourcePubKey != r.SelfNode {
maxTotalTimelock = math.MaxUint32
}
cltvLimit, err := ValidateCLTVLimit(in.CltvLimit, maxTotalTimelock)
if err != nil {
return nil, err
}
// If we have a blinded path set, we'll get a few of our fields from
// inside of the path rather than the request's fields.
var (
targetPubKey *route.Vertex
routeHintEdges map[route.Vertex][]routing.AdditionalEdge
blindedPathSet *routing.BlindedPaymentPathSet
// finalCLTVDelta varies depending on whether we're sending to
// a blinded route or an unblinded node. For blinded paths,
// our final cltv is already baked into the path so we restrict
// this value to zero on the API. Bolt11 invoices have a
// default, so we'll fill that in for the non-blinded case.
finalCLTVDelta uint16
// destinationFeatures is the set of features for the
// destination node.
destinationFeatures *lnwire.FeatureVector
)
// Validate that the fields provided in the request are sane depending
// on whether it is using a blinded path or not.
if len(in.BlindedPaymentPaths) > 0 {
blindedPathSet, err = parseBlindedPaymentPaths(in)
if err != nil {
return nil, err
}
pathFeatures := blindedPathSet.Features()
if pathFeatures != nil {
destinationFeatures = pathFeatures.Clone()
}
} else {
// If we do not have a blinded path, a target pubkey must be
// set.
pk, err := parsePubKey(in.PubKey)
if err != nil {
return nil, err
}
targetPubKey = &pk
// Convert route hints to an edge map.
routeHints, err := unmarshallRouteHints(in.RouteHints)
if err != nil {
return nil, err
}
routeHintEdges, err = routing.RouteHintsToEdges(
routeHints, *targetPubKey,
)
if err != nil {
return nil, err
}
// Set a non-zero final CLTV delta for payments that are not
// to blinded paths, as bolt11 has a default final cltv delta
// value that is used in the absence of a value.
finalCLTVDelta = r.DefaultFinalCltvDelta
if in.FinalCltvDelta != 0 {
finalCLTVDelta = uint16(in.FinalCltvDelta)
}
// Do bounds checking without block padding so we don't give
// routes that will leave the router in a zombie payment state.
err = routing.ValidateCLTVLimit(
cltvLimit, finalCLTVDelta, false,
)
if err != nil {
return nil, err
}
// Parse destination feature bits.
destinationFeatures, err = UnmarshalFeatures(in.DestFeatures)
if err != nil {
return nil, err
}
}
// We need to subtract the final delta before passing it into path
// finding. The optimal path is independent of the final cltv delta and
// the path finding algorithm is unaware of this value.
cltvLimit -= uint32(finalCLTVDelta)
ignoredNodes, ignoredPairs, err := r.parseIgnored(in)
if err != nil {
return nil, err
}
restrictions := &routing.RestrictParams{
FeeLimit: feeLimit,
ProbabilitySource: func(fromNode, toNode route.Vertex,
amt lnwire.MilliSatoshi,
capacity btcutil.Amount) float64 {
if _, ok := ignoredNodes[fromNode]; ok {
return 0
}
pair := routing.DirectedNodePair{
From: fromNode,
To: toNode,
}
if _, ok := ignoredPairs[pair]; ok {
return 0
}
if !in.UseMissionControl {
return 1
}
return r.MissionControl.GetProbability(
fromNode, toNode, amt, capacity,
)
},
DestCustomRecords: record.CustomSet(in.DestCustomRecords),
CltvLimit: cltvLimit,
DestFeatures: destinationFeatures,
BlindedPaymentPathSet: blindedPathSet,
}
// Pass along an outgoing channel restriction if specified.
if in.OutgoingChanId != 0 {
restrictions.OutgoingChannelIDs = []uint64{in.OutgoingChanId}
}
// Pass along a last hop restriction if specified.
if len(in.LastHopPubkey) > 0 {
lastHop, err := route.NewVertexFromBytes(
in.LastHopPubkey,
)
if err != nil {
return nil, err
}
restrictions.LastHop = &lastHop
}
// If we have any TLV records destined for the final hop, then we'll
// attempt to decode them now into a form that the router can more
// easily manipulate.
customRecords := record.CustomSet(in.DestCustomRecords)
if err := customRecords.Validate(); err != nil {
return nil, err
}
return routing.NewRouteRequest(
sourcePubKey, targetPubKey, amt, in.TimePref, restrictions,
customRecords, routeHintEdges, blindedPathSet,
finalCLTVDelta,
)
}
func parseBlindedPaymentPaths(in *lnrpc.QueryRoutesRequest) (
*routing.BlindedPaymentPathSet, error) {
if len(in.PubKey) != 0 {
return nil, fmt.Errorf("target pubkey: %x should not be set "+
"when blinded path is provided", in.PubKey)
}
if len(in.RouteHints) > 0 {
return nil, errors.New("route hints and blinded path can't " +
"both be set")
}
if in.FinalCltvDelta != 0 {
return nil, errors.New("final cltv delta should be " +
"zero for blinded paths")
}
// For blinded paths, we get one set of features for the relaying
// intermediate nodes and the final destination. We don't allow the
// destination feature bit field for regular payments to be set, as
// this could lead to ambiguity.
if len(in.DestFeatures) > 0 {
return nil, errors.New("destination features should " +
"be populated in blinded path")
}
paths := make([]*routing.BlindedPayment, len(in.BlindedPaymentPaths))
for i, paymentPath := range in.BlindedPaymentPaths {
blindedPmt, err := unmarshalBlindedPayment(paymentPath)
if err != nil {
return nil, fmt.Errorf("parse blinded payment: %w", err)
}
if err := blindedPmt.Validate(); err != nil {
return nil, fmt.Errorf("invalid blinded path: %w", err)
}
paths[i] = blindedPmt
}
return routing.NewBlindedPaymentPathSet(paths)
}
func unmarshalBlindedPayment(rpcPayment *lnrpc.BlindedPaymentPath) (
*routing.BlindedPayment, error) {
if rpcPayment == nil {
return nil, errors.New("nil blinded payment")
}
path, err := unmarshalBlindedPaymentPaths(rpcPayment.BlindedPath)
if err != nil {
return nil, err
}
features, err := UnmarshalFeatures(rpcPayment.Features)
if err != nil {
return nil, err
}
return &routing.BlindedPayment{
BlindedPath: path,
CltvExpiryDelta: uint16(rpcPayment.TotalCltvDelta),
BaseFee: uint32(rpcPayment.BaseFeeMsat),
ProportionalFeeRate: rpcPayment.ProportionalFeeRate,
HtlcMinimum: rpcPayment.HtlcMinMsat,
HtlcMaximum: rpcPayment.HtlcMaxMsat,
Features: features,
}, nil
}
func unmarshalBlindedPaymentPaths(rpcPath *lnrpc.BlindedPath) (
*sphinx.BlindedPath, error) {
if rpcPath == nil {
return nil, errors.New("blinded path required when blinded " +
"route is provided")
}
introduction, err := btcec.ParsePubKey(rpcPath.IntroductionNode)
if err != nil {
return nil, err
}
blinding, err := btcec.ParsePubKey(rpcPath.BlindingPoint)
if err != nil {
return nil, err
}
if len(rpcPath.BlindedHops) < 1 {
return nil, errors.New("at least 1 blinded hops required")
}
path := &sphinx.BlindedPath{
IntroductionPoint: introduction,
BlindingPoint: blinding,
BlindedHops: make(
[]*sphinx.BlindedHopInfo, len(rpcPath.BlindedHops),
),
}
for i, hop := range rpcPath.BlindedHops {
path.BlindedHops[i], err = unmarshalBlindedHop(hop)
if err != nil {
return nil, err
}
}
return path, nil
}
func unmarshalBlindedHop(rpcHop *lnrpc.BlindedHop) (*sphinx.BlindedHopInfo,
error) {
pubkey, err := btcec.ParsePubKey(rpcHop.BlindedNode)
if err != nil {
return nil, err
}
if len(rpcHop.EncryptedData) == 0 {
return nil, errors.New("empty encrypted data not allowed")
}
return &sphinx.BlindedHopInfo{
BlindedNodePub: pubkey,
CipherText: rpcHop.EncryptedData,
}, nil
}
// rpcEdgeToPair looks up the provided channel and returns the channel endpoints
// as a directed pair.
func (r *RouterBackend) rpcEdgeToPair(e *lnrpc.EdgeLocator) (
routing.DirectedNodePair, error) {
a, b, err := r.FetchChannelEndpoints(e.ChannelId)
if err != nil {
return routing.DirectedNodePair{}, err
}
var pair routing.DirectedNodePair
if e.DirectionReverse {
pair.From, pair.To = b, a
} else {
pair.From, pair.To = a, b
}
return pair, nil
}
// MarshallRoute marshalls an internal route to an rpc route struct.
func (r *RouterBackend) MarshallRoute(route *route.Route) (*lnrpc.Route, error) {
resp := &lnrpc.Route{
TotalTimeLock: route.TotalTimeLock,
TotalFees: int64(route.TotalFees().ToSatoshis()),
TotalFeesMsat: int64(route.TotalFees()),
TotalAmt: int64(route.TotalAmount.ToSatoshis()),
TotalAmtMsat: int64(route.TotalAmount),
Hops: make([]*lnrpc.Hop, len(route.Hops)),
FirstHopAmountMsat: int64(route.FirstHopAmount.Val.Int()),
}
// Encode the route's custom channel data (if available).
if len(route.FirstHopWireCustomRecords) > 0 {
customData, err := route.FirstHopWireCustomRecords.Serialize()
if err != nil {
return nil, err
}
resp.CustomChannelData = customData
// Allow the aux data parser to parse the custom records into
// a human-readable JSON (if available).
if r.ParseCustomChannelData != nil {
err := r.ParseCustomChannelData(resp)
if err != nil {
return nil, err
}
}
}
incomingAmt := route.TotalAmount
for i, hop := range route.Hops {
fee := route.HopFee(i)
// Channel capacity is not a defining property of a route. For
// backwards RPC compatibility, we retrieve it here from the
// graph.
chanCapacity, err := r.FetchChannelCapacity(hop.ChannelID)
if err != nil {
// If capacity cannot be retrieved, this may be a
// not-yet-received or private channel. Then report
// amount that is sent through the channel as capacity.
chanCapacity = incomingAmt.ToSatoshis()
}
// Extract the MPP fields if present on this hop.
var mpp *lnrpc.MPPRecord
if hop.MPP != nil {
addr := hop.MPP.PaymentAddr()
mpp = &lnrpc.MPPRecord{
PaymentAddr: addr[:],
TotalAmtMsat: int64(hop.MPP.TotalMsat()),
}
}
var amp *lnrpc.AMPRecord
if hop.AMP != nil {
rootShare := hop.AMP.RootShare()
setID := hop.AMP.SetID()
amp = &lnrpc.AMPRecord{
RootShare: rootShare[:],
SetId: setID[:],
ChildIndex: hop.AMP.ChildIndex(),
}
}
resp.Hops[i] = &lnrpc.Hop{
ChanId: hop.ChannelID,
ChanCapacity: int64(chanCapacity),
AmtToForward: int64(hop.AmtToForward.ToSatoshis()),
AmtToForwardMsat: int64(hop.AmtToForward),
Fee: int64(fee.ToSatoshis()),
FeeMsat: int64(fee),
Expiry: uint32(hop.OutgoingTimeLock),
PubKey: hex.EncodeToString(
hop.PubKeyBytes[:],
),
CustomRecords: hop.CustomRecords,
TlvPayload: !hop.LegacyPayload,
MppRecord: mpp,
AmpRecord: amp,
Metadata: hop.Metadata,
EncryptedData: hop.EncryptedData,
TotalAmtMsat: uint64(hop.TotalAmtMsat),
}
if hop.BlindingPoint != nil {
blinding := hop.BlindingPoint.SerializeCompressed()
resp.Hops[i].BlindingPoint = blinding
}
incomingAmt = hop.AmtToForward
}
return resp, nil
}
// UnmarshallHopWithPubkey unmarshalls an rpc hop for which the pubkey has
// already been extracted.
func UnmarshallHopWithPubkey(rpcHop *lnrpc.Hop, pubkey route.Vertex) (*route.Hop,
error) {
customRecords := record.CustomSet(rpcHop.CustomRecords)
if err := customRecords.Validate(); err != nil {
return nil, err
}
mpp, err := UnmarshalMPP(rpcHop.MppRecord)
if err != nil {
return nil, err
}
amp, err := UnmarshalAMP(rpcHop.AmpRecord)
if err != nil {
return nil, err
}
hop := &route.Hop{
OutgoingTimeLock: rpcHop.Expiry,
AmtToForward: lnwire.MilliSatoshi(rpcHop.AmtToForwardMsat),
PubKeyBytes: pubkey,
ChannelID: rpcHop.ChanId,
CustomRecords: customRecords,
LegacyPayload: false,
MPP: mpp,
AMP: amp,
EncryptedData: rpcHop.EncryptedData,
TotalAmtMsat: lnwire.MilliSatoshi(rpcHop.TotalAmtMsat),
}
haveBlindingPoint := len(rpcHop.BlindingPoint) != 0
if haveBlindingPoint {
hop.BlindingPoint, err = btcec.ParsePubKey(
rpcHop.BlindingPoint,
)
if err != nil {
return nil, fmt.Errorf("blinding point: %w", err)
}
}
if haveBlindingPoint && len(rpcHop.EncryptedData) == 0 {
return nil, errors.New("encrypted data should be present if " +
"blinding point is provided")
}
return hop, nil
}
// UnmarshallHop unmarshalls an rpc hop that may or may not contain a node
// pubkey.
func (r *RouterBackend) UnmarshallHop(rpcHop *lnrpc.Hop,
prevNodePubKey [33]byte) (*route.Hop, error) {
var pubKeyBytes [33]byte
if rpcHop.PubKey != "" {
// Unmarshall the provided hop pubkey.
pubKey, err := hex.DecodeString(rpcHop.PubKey)
if err != nil {
return nil, fmt.Errorf("cannot decode pubkey %s",
rpcHop.PubKey)
}
copy(pubKeyBytes[:], pubKey)
} else {
// If no pub key is given of the hop, the local channel graph
// needs to be queried to complete the information necessary for
// routing. Discard edge policies, because they may be nil.
node1, node2, err := r.FetchChannelEndpoints(rpcHop.ChanId)
if err != nil {
return nil, err
}
switch {
case prevNodePubKey == node1:
pubKeyBytes = node2
case prevNodePubKey == node2:
pubKeyBytes = node1
default:
return nil, fmt.Errorf("channel edge does not match " +
"expected node")
}
}
return UnmarshallHopWithPubkey(rpcHop, pubKeyBytes)
}
// UnmarshallRoute unmarshalls an rpc route. For hops that don't specify a
// pubkey, the channel graph is queried.
func (r *RouterBackend) UnmarshallRoute(rpcroute *lnrpc.Route) (
*route.Route, error) {
prevNodePubKey := r.SelfNode
hops := make([]*route.Hop, len(rpcroute.Hops))
for i, hop := range rpcroute.Hops {
routeHop, err := r.UnmarshallHop(hop, prevNodePubKey)
if err != nil {
return nil, err
}
hops[i] = routeHop
prevNodePubKey = routeHop.PubKeyBytes
}
route, err := route.NewRouteFromHops(
lnwire.MilliSatoshi(rpcroute.TotalAmtMsat),
rpcroute.TotalTimeLock,
r.SelfNode,
hops,
)
if err != nil {
return nil, err
}
return route, nil
}
// extractIntentFromSendRequest attempts to parse the SendRequest details
// required to dispatch a client from the information presented by an RPC
// client.
func (r *RouterBackend) extractIntentFromSendRequest(
rpcPayReq *SendPaymentRequest) (*routing.LightningPayment, error) {
payIntent := &routing.LightningPayment{}
// Pass along time preference.
if rpcPayReq.TimePref < -1 || rpcPayReq.TimePref > 1 {
return nil, errors.New("time preference out of range")
}
payIntent.TimePref = rpcPayReq.TimePref
// Pass along restrictions on the outgoing channels that may be used.
payIntent.OutgoingChannelIDs = rpcPayReq.OutgoingChanIds
// Add the deprecated single outgoing channel restriction if present.
if rpcPayReq.OutgoingChanId != 0 {
if payIntent.OutgoingChannelIDs != nil {
return nil, errors.New("outgoing_chan_id and " +
"outgoing_chan_ids are mutually exclusive")
}
payIntent.OutgoingChannelIDs = append(
payIntent.OutgoingChannelIDs, rpcPayReq.OutgoingChanId,
)
}
// Pass along a last hop restriction if specified.
if len(rpcPayReq.LastHopPubkey) > 0 {
lastHop, err := route.NewVertexFromBytes(
rpcPayReq.LastHopPubkey,
)
if err != nil {
return nil, err
}
payIntent.LastHop = &lastHop
}
// Take the CLTV limit from the request if set, otherwise use the max.
cltvLimit, err := ValidateCLTVLimit(
uint32(rpcPayReq.CltvLimit), r.MaxTotalTimelock,
)
if err != nil {
return nil, err
}
payIntent.CltvLimit = cltvLimit
// Attempt to parse the max parts value set by the user, if this value
// isn't set, then we'll use the current default value for this
// setting.
maxParts := rpcPayReq.MaxParts
if maxParts == 0 {
maxParts = DefaultMaxParts
}
payIntent.MaxParts = maxParts
// If this payment had a max shard amount specified, then we'll apply
// that now, which'll force us to always make payment splits smaller
// than this.
if rpcPayReq.MaxShardSizeMsat > 0 {
shardAmtMsat := lnwire.MilliSatoshi(rpcPayReq.MaxShardSizeMsat)
payIntent.MaxShardAmt = &shardAmtMsat
// If the requested max_parts exceeds the allowed limit, then we
// cannot send the payment amount.
if payIntent.MaxParts > MaxPartsUpperLimit {
return nil, fmt.Errorf("requested max_parts (%v) "+
"exceeds the allowed upper limit of %v; cannot"+
" send payment amount with max_shard_size_msat"+
"=%v", payIntent.MaxParts, MaxPartsUpperLimit,
*payIntent.MaxShardAmt)
}
}
// Take fee limit from request.
payIntent.FeeLimit, err = lnrpc.UnmarshallAmt(
rpcPayReq.FeeLimitSat, rpcPayReq.FeeLimitMsat,
)
if err != nil {
return nil, err
}
customRecords := record.CustomSet(rpcPayReq.DestCustomRecords)
if err := customRecords.Validate(); err != nil {
return nil, err
}
payIntent.DestCustomRecords = customRecords
// Keysend payments do not support MPP payments.
//
// NOTE: There is no need to validate the `MaxParts` value here because
// it is set to 1 somewhere else in case it's a keysend payment.
if customRecords.IsKeysend() {
if payIntent.MaxShardAmt != nil {
return nil, errors.New("keysend payments cannot " +
"specify a max shard amount - MPP not " +
"supported with keysend payments")
}
}
firstHopRecords := lnwire.CustomRecords(rpcPayReq.FirstHopCustomRecords)
if err := firstHopRecords.Validate(); err != nil {
return nil, err
}
payIntent.FirstHopCustomRecords = firstHopRecords
// If the experimental endorsement signal is not already set, propagate
// a zero value field if configured to set this signal.
if r.ShouldSetExpEndorsement() {
if payIntent.FirstHopCustomRecords == nil {
payIntent.FirstHopCustomRecords = make(
map[uint64][]byte,
)
}
t := uint64(lnwire.ExperimentalEndorsementType)
if _, set := payIntent.FirstHopCustomRecords[t]; !set {
payIntent.FirstHopCustomRecords[t] = []byte{
lnwire.ExperimentalUnendorsed,
}
}
}
payIntent.PayAttemptTimeout = time.Second *
time.Duration(rpcPayReq.TimeoutSeconds)
// Route hints.
routeHints, err := unmarshallRouteHints(
rpcPayReq.RouteHints,
)
if err != nil {
return nil, err
}
payIntent.RouteHints = routeHints
// Unmarshall either sat or msat amount from request.
reqAmt, err := lnrpc.UnmarshallAmt(
rpcPayReq.Amt, rpcPayReq.AmtMsat,
)
if err != nil {
return nil, err
}
// If the payment request field isn't blank, then the details of the
// invoice are encoded entirely within the encoded payReq. So we'll
// attempt to decode it, populating the payment accordingly.
if rpcPayReq.PaymentRequest != "" {
switch {
case len(rpcPayReq.Dest) > 0:
return nil, errors.New("dest and payment_request " +
"cannot appear together")
case len(rpcPayReq.PaymentHash) > 0:
return nil, errors.New("payment_hash and payment_request " +
"cannot appear together")
case rpcPayReq.FinalCltvDelta != 0:
return nil, errors.New("final_cltv_delta and payment_request " +
"cannot appear together")
}
payReq, err := zpay32.Decode(
rpcPayReq.PaymentRequest, r.ActiveNetParams,
)
if err != nil {
return nil, err
}
// Next, we'll ensure that this payreq hasn't already expired.
err = ValidatePayReqExpiry(payReq)
if err != nil {
return nil, err
}
// If the amount was not included in the invoice, then we let
// the payer specify the amount of satoshis they wish to send.
// We override the amount to pay with the amount provided from
// the payment request.
if payReq.MilliSat == nil {
if reqAmt == 0 {
return nil, errors.New("amount must be " +
"specified when paying a zero amount " +
"invoice")
}
payIntent.Amount = reqAmt
} else {
if reqAmt != 0 {
return nil, errors.New("amount must not be " +
"specified when paying a non-zero " +
" amount invoice")
}
payIntent.Amount = *payReq.MilliSat
}
if !payReq.Features.HasFeature(lnwire.MPPOptional) &&
!payReq.Features.HasFeature(lnwire.AMPOptional) {
payIntent.MaxParts = 1
}
payAddr := payReq.PaymentAddr
if payReq.Features.HasFeature(lnwire.AMPOptional) {
// The opt-in AMP flag is required to pay an AMP
// invoice.
if !rpcPayReq.Amp {
return nil, fmt.Errorf("the AMP flag (--amp " +
"or SendPaymentRequest.Amp) must be " +
"set to pay an AMP invoice")
}
// Generate random SetID and root share.
var setID [32]byte
_, err = rand.Read(setID[:])
if err != nil {
return nil, err
}
var rootShare [32]byte
_, err = rand.Read(rootShare[:])
if err != nil {
return nil, err
}
err := payIntent.SetAMP(&routing.AMPOptions{
SetID: setID,
RootShare: rootShare,
})
if err != nil {
return nil, err
}
// For AMP invoices, we'll allow users to override the
// included payment addr to allow the invoice to be
// pseudo-reusable, e.g. the invoice parameters are
// reused (amt, cltv, hop hints, etc) even though the
// payments will share different payment hashes.
//
// NOTE: This will only work when the peer has
// spontaneous AMP payments enabled.
if len(rpcPayReq.PaymentAddr) > 0 {
var addr [32]byte
copy(addr[:], rpcPayReq.PaymentAddr)
payAddr = fn.Some(addr)
}
} else {
err = payIntent.SetPaymentHash(*payReq.PaymentHash)
if err != nil {
return nil, err
}
}
destKey := payReq.Destination.SerializeCompressed()
copy(payIntent.Target[:], destKey)
payIntent.FinalCLTVDelta = uint16(payReq.MinFinalCLTVExpiry())
payIntent.RouteHints = append(
payIntent.RouteHints, payReq.RouteHints...,
)
payIntent.DestFeatures = payReq.Features
payIntent.PaymentAddr = payAddr
payIntent.PaymentRequest = []byte(rpcPayReq.PaymentRequest)
payIntent.Metadata = payReq.Metadata
if len(payReq.BlindedPaymentPaths) > 0 {
pathSet, err := BuildBlindedPathSet(
payReq.BlindedPaymentPaths,
)
if err != nil {
return nil, err
}
payIntent.BlindedPathSet = pathSet
// Replace the target node with the target public key
// of the blinded path set.
copy(
payIntent.Target[:],
pathSet.TargetPubKey().SerializeCompressed(),
)
pathFeatures := pathSet.Features()
if !pathFeatures.IsEmpty() {
payIntent.DestFeatures = pathFeatures.Clone()
}
}
} else {
// Otherwise, If the payment request field was not specified
// (and a custom route wasn't specified), construct the payment
// from the other fields.
// Payment destination.
target, err := route.NewVertexFromBytes(rpcPayReq.Dest)
if err != nil {
return nil, err
}
payIntent.Target = target
// Final payment CLTV delta.
if rpcPayReq.FinalCltvDelta != 0 {
payIntent.FinalCLTVDelta =
uint16(rpcPayReq.FinalCltvDelta)
} else {
payIntent.FinalCLTVDelta = r.DefaultFinalCltvDelta
}
// Amount.
if reqAmt == 0 {
return nil, errors.New("amount must be specified")
}
payIntent.Amount = reqAmt
// Parse destination feature bits.
features, err := UnmarshalFeatures(rpcPayReq.DestFeatures)
if err != nil {
return nil, err
}
// Validate the features if any was specified.
if features != nil {
err = feature.ValidateDeps(features)
if err != nil {
return nil, err
}
}
// If this is an AMP payment, we must generate the initial
// randomness.
if rpcPayReq.Amp {
// If no destination features were specified, we set
// those necessary for AMP payments.
if features == nil {
ampFeatures := []lnrpc.FeatureBit{
lnrpc.FeatureBit_TLV_ONION_OPT,
lnrpc.FeatureBit_PAYMENT_ADDR_OPT,
lnrpc.FeatureBit_AMP_OPT,
}
features, err = UnmarshalFeatures(ampFeatures)
if err != nil {
return nil, err
}
}
// First make sure the destination supports AMP.
if !features.HasFeature(lnwire.AMPOptional) {
return nil, fmt.Errorf("destination doesn't " +
"support AMP payments")
}
// If no payment address is set, generate a random one.
var payAddr [32]byte
if len(rpcPayReq.PaymentAddr) == 0 {
_, err = rand.Read(payAddr[:])
if err != nil {
return nil, err
}
} else {
copy(payAddr[:], rpcPayReq.PaymentAddr)
}
payIntent.PaymentAddr = fn.Some(payAddr)
// Generate random SetID and root share.
var setID [32]byte
_, err = rand.Read(setID[:])
if err != nil {
return nil, err
}
var rootShare [32]byte
_, err = rand.Read(rootShare[:])
if err != nil {
return nil, err
}
err := payIntent.SetAMP(&routing.AMPOptions{
SetID: setID,
RootShare: rootShare,
})
if err != nil {
return nil, err
}
} else {
// Payment hash.
paymentHash, err := lntypes.MakeHash(rpcPayReq.PaymentHash)
if err != nil {
return nil, err
}
err = payIntent.SetPaymentHash(paymentHash)
if err != nil {
return nil, err
}
// If the payment addresses is specified, then we'll
// also populate that now as well.
if len(rpcPayReq.PaymentAddr) != 0 {
var payAddr [32]byte
copy(payAddr[:], rpcPayReq.PaymentAddr)
payIntent.PaymentAddr = fn.Some(payAddr)
}
}
payIntent.DestFeatures = features
}
// Validate that the MPP parameters are compatible with the
// payment amount. In other words, the parameters are invalid if
// they do not permit sending the full payment amount.
if payIntent.MaxShardAmt != nil {
maxPossibleAmount := (*payIntent.MaxShardAmt) *
lnwire.MilliSatoshi(payIntent.MaxParts)
if payIntent.Amount > maxPossibleAmount {
return nil, fmt.Errorf("payment amount %v exceeds "+
"maximum possible amount %v with max_parts=%v "+
"and max_shard_size_msat=%v", payIntent.Amount,
maxPossibleAmount, payIntent.MaxParts,
*payIntent.MaxShardAmt,
)
}
}
// Do bounds checking with the block padding so the router isn't
// left with a zombie payment in case the user messes up.
err = routing.ValidateCLTVLimit(
payIntent.CltvLimit, payIntent.FinalCLTVDelta, true,
)
if err != nil {
return nil, err
}
// Check for disallowed payments to self.
if !rpcPayReq.AllowSelfPayment && payIntent.Target == r.SelfNode {
return nil, errors.New("self-payments not allowed")
}
return payIntent, nil
}
// BuildBlindedPathSet marshals a set of zpay32.BlindedPaymentPath and uses
// the result to build a new routing.BlindedPaymentPathSet.
func BuildBlindedPathSet(paths []*zpay32.BlindedPaymentPath) (
*routing.BlindedPaymentPathSet, error) {
marshalledPaths := make([]*routing.BlindedPayment, len(paths))
for i, path := range paths {
paymentPath := marshalBlindedPayment(path)
err := paymentPath.Validate()
if err != nil {
return nil, err
}
marshalledPaths[i] = paymentPath
}
return routing.NewBlindedPaymentPathSet(marshalledPaths)
}
// marshalBlindedPayment marshals a zpay32.BLindedPaymentPath into a
// routing.BlindedPayment.
func marshalBlindedPayment(
path *zpay32.BlindedPaymentPath) *routing.BlindedPayment {
return &routing.BlindedPayment{
BlindedPath: &sphinx.BlindedPath{
IntroductionPoint: path.Hops[0].BlindedNodePub,
BlindingPoint: path.FirstEphemeralBlindingPoint,
BlindedHops: path.Hops,
},
BaseFee: path.FeeBaseMsat,
ProportionalFeeRate: path.FeeRate,
CltvExpiryDelta: path.CltvExpiryDelta,
HtlcMinimum: path.HTLCMinMsat,
HtlcMaximum: path.HTLCMaxMsat,
Features: path.Features,
}
}
// unmarshallRouteHints unmarshalls a list of route hints.
func unmarshallRouteHints(rpcRouteHints []*lnrpc.RouteHint) (
[][]zpay32.HopHint, error) {
routeHints := make([][]zpay32.HopHint, 0, len(rpcRouteHints))
for _, rpcRouteHint := range rpcRouteHints {
routeHint := make(
[]zpay32.HopHint, 0, len(rpcRouteHint.HopHints),
)
for _, rpcHint := range rpcRouteHint.HopHints {
hint, err := unmarshallHopHint(rpcHint)
if err != nil {
return nil, err
}
routeHint = append(routeHint, hint)
}
routeHints = append(routeHints, routeHint)
}
return routeHints, nil
}
// unmarshallHopHint unmarshalls a single hop hint.
func unmarshallHopHint(rpcHint *lnrpc.HopHint) (zpay32.HopHint, error) {
pubBytes, err := hex.DecodeString(rpcHint.NodeId)
if err != nil {
return zpay32.HopHint{}, err
}
pubkey, err := btcec.ParsePubKey(pubBytes)
if err != nil {
return zpay32.HopHint{}, err
}
return zpay32.HopHint{
NodeID: pubkey,
ChannelID: rpcHint.ChanId,
FeeBaseMSat: rpcHint.FeeBaseMsat,
FeeProportionalMillionths: rpcHint.FeeProportionalMillionths,
CLTVExpiryDelta: uint16(rpcHint.CltvExpiryDelta),
}, nil
}
// MarshalFeatures converts a feature vector into a list of uint32's.
func MarshalFeatures(feats *lnwire.FeatureVector) []lnrpc.FeatureBit {
var featureBits []lnrpc.FeatureBit
for feature := range feats.Features() {
featureBits = append(featureBits, lnrpc.FeatureBit(feature))
}
return featureBits
}
// UnmarshalFeatures converts a list of uint32's into a valid feature vector.
// This method checks that feature bit pairs aren't assigned together, and
// validates transitive dependencies.
func UnmarshalFeatures(
rpcFeatures []lnrpc.FeatureBit) (*lnwire.FeatureVector, error) {
// If no destination features are specified we'll return nil to signal
// that the router should try to use the graph as a fallback.
if rpcFeatures == nil {
return nil, nil
}
raw := lnwire.NewRawFeatureVector()
for _, bit := range rpcFeatures {
err := raw.SafeSet(lnwire.FeatureBit(bit))
if err != nil {
return nil, err
}
}
return lnwire.NewFeatureVector(raw, lnwire.Features), nil
}
// ValidatePayReqExpiry checks if the passed payment request has expired. In
// the case it has expired, an error will be returned.
func ValidatePayReqExpiry(payReq *zpay32.Invoice) error {
expiry := payReq.Expiry()
validUntil := payReq.Timestamp.Add(expiry)
if time.Now().After(validUntil) {
return fmt.Errorf("invoice expired. Valid until %v", validUntil)
}
return nil
}
// ValidateCLTVLimit returns a valid CLTV limit given a value and a maximum. If
// the value exceeds the maximum, then an error is returned. If the value is 0,
// then the maximum is used.
func ValidateCLTVLimit(val, max uint32) (uint32, error) {
switch {
case val == 0:
return max, nil
case val > max:
return 0, fmt.Errorf("total time lock of %v exceeds max "+
"allowed %v", val, max)
default:
return val, nil
}
}
// UnmarshalMPP accepts the mpp_total_amt_msat and mpp_payment_addr fields from
// an RPC request and converts into an record.MPP object. An error is returned
// if the payment address is not 0 or 32 bytes. If the total amount and payment
// address are zero-value, the return value will be nil signaling there is no
// MPP record to attach to this hop. Otherwise, a non-nil reocrd will be
// contained combining the provided values.
func UnmarshalMPP(reqMPP *lnrpc.MPPRecord) (*record.MPP, error) {
// If no MPP record was submitted, assume the user wants to send a
// regular payment.
if reqMPP == nil {
return nil, nil
}
reqTotal := reqMPP.TotalAmtMsat
reqAddr := reqMPP.PaymentAddr
switch {
// No MPP fields were provided.
case reqTotal == 0 && len(reqAddr) == 0:
return nil, fmt.Errorf("missing total_msat and payment_addr")
// Total is present, but payment address is missing.
case reqTotal > 0 && len(reqAddr) == 0:
return nil, fmt.Errorf("missing payment_addr")
// Payment address is present, but total is missing.
case reqTotal == 0 && len(reqAddr) > 0:
return nil, fmt.Errorf("missing total_msat")
}
addr, err := lntypes.MakeHash(reqAddr)
if err != nil {
return nil, fmt.Errorf("unable to parse "+
"payment_addr: %v", err)
}
total := lnwire.MilliSatoshi(reqTotal)
return record.NewMPP(total, addr), nil
}
func UnmarshalAMP(reqAMP *lnrpc.AMPRecord) (*record.AMP, error) {
if reqAMP == nil {
return nil, nil
}
reqRootShare := reqAMP.RootShare
reqSetID := reqAMP.SetId
switch {
case len(reqRootShare) != 32:
return nil, errors.New("AMP root_share must be 32 bytes")
case len(reqSetID) != 32:
return nil, errors.New("AMP set_id must be 32 bytes")
}
var (
rootShare [32]byte
setID [32]byte
)
copy(rootShare[:], reqRootShare)
copy(setID[:], reqSetID)
return record.NewAMP(rootShare, setID, reqAMP.ChildIndex), nil
}
// MarshalHTLCAttempt constructs an RPC HTLCAttempt from the db representation.
func (r *RouterBackend) MarshalHTLCAttempt(
htlc channeldb.HTLCAttempt) (*lnrpc.HTLCAttempt, error) {
route, err := r.MarshallRoute(&htlc.Route)
if err != nil {
return nil, err
}
rpcAttempt := &lnrpc.HTLCAttempt{
AttemptId: htlc.AttemptID,
AttemptTimeNs: MarshalTimeNano(htlc.AttemptTime),
Route: route,
}
switch {
case htlc.Settle != nil:
rpcAttempt.Status = lnrpc.HTLCAttempt_SUCCEEDED
rpcAttempt.ResolveTimeNs = MarshalTimeNano(
htlc.Settle.SettleTime,
)
rpcAttempt.Preimage = htlc.Settle.Preimage[:]
case htlc.Failure != nil:
rpcAttempt.Status = lnrpc.HTLCAttempt_FAILED
rpcAttempt.ResolveTimeNs = MarshalTimeNano(
htlc.Failure.FailTime,
)
var err error
rpcAttempt.Failure, err = marshallHtlcFailure(htlc.Failure)
if err != nil {
return nil, err
}
default:
rpcAttempt.Status = lnrpc.HTLCAttempt_IN_FLIGHT
}
return rpcAttempt, nil
}
// marshallHtlcFailure marshalls htlc fail info from the database to its rpc
// representation.
func marshallHtlcFailure(failure *channeldb.HTLCFailInfo) (*lnrpc.Failure,
error) {
rpcFailure := &lnrpc.Failure{
FailureSourceIndex: failure.FailureSourceIndex,
}
switch failure.Reason {
case channeldb.HTLCFailUnknown:
rpcFailure.Code = lnrpc.Failure_UNKNOWN_FAILURE
case channeldb.HTLCFailUnreadable:
rpcFailure.Code = lnrpc.Failure_UNREADABLE_FAILURE
case channeldb.HTLCFailInternal:
rpcFailure.Code = lnrpc.Failure_INTERNAL_FAILURE
case channeldb.HTLCFailMessage:
err := marshallWireError(failure.Message, rpcFailure)
if err != nil {
return nil, err
}
default:
return nil, errors.New("unknown htlc failure reason")
}
return rpcFailure, nil
}
// MarshalTimeNano converts a time.Time into its nanosecond representation. If
// the time is zero, this method simply returns 0, since calling UnixNano() on a
// zero-valued time is undefined.
func MarshalTimeNano(t time.Time) int64 {
if t.IsZero() {
return 0
}
return t.UnixNano()
}
// marshallError marshall an error as received from the switch to rpc structs
// suitable for returning to the caller of an rpc method.
//
// Because of difficulties with using protobuf oneof constructs in some
// languages, the decision was made here to use a single message format for all
// failure messages with some fields left empty depending on the failure type.
func marshallError(sendError error) (*lnrpc.Failure, error) {
response := &lnrpc.Failure{}
if sendError == htlcswitch.ErrUnreadableFailureMessage {
response.Code = lnrpc.Failure_UNREADABLE_FAILURE
return response, nil
}
rtErr, ok := sendError.(htlcswitch.ClearTextError)
if !ok {
return nil, sendError
}
err := marshallWireError(rtErr.WireMessage(), response)
if err != nil {
return nil, err
}
// If the ClearTextError received is a ForwardingError, the error
// originated from a node along the route, not locally on our outgoing
// link. We set failureSourceIdx to the index of the node where the
// failure occurred. If the error is not a ForwardingError, the failure
// occurred at our node, so we leave the index as 0 to indicate that
// we failed locally.
fErr, ok := rtErr.(*htlcswitch.ForwardingError)
if ok {
response.FailureSourceIndex = uint32(fErr.FailureSourceIdx)
}
return response, nil
}
// marshallError marshall an error as received from the switch to rpc structs
// suitable for returning to the caller of an rpc method.
//
// Because of difficulties with using protobuf oneof constructs in some
// languages, the decision was made here to use a single message format for all
// failure messages with some fields left empty depending on the failure type.
func marshallWireError(msg lnwire.FailureMessage,
response *lnrpc.Failure) error {
switch onionErr := msg.(type) {
case *lnwire.FailIncorrectDetails:
response.Code = lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS
response.Height = onionErr.Height()
case *lnwire.FailIncorrectPaymentAmount:
response.Code = lnrpc.Failure_INCORRECT_PAYMENT_AMOUNT
case *lnwire.FailFinalIncorrectCltvExpiry:
response.Code = lnrpc.Failure_FINAL_INCORRECT_CLTV_EXPIRY
response.CltvExpiry = onionErr.CltvExpiry
case *lnwire.FailFinalIncorrectHtlcAmount:
response.Code = lnrpc.Failure_FINAL_INCORRECT_HTLC_AMOUNT
response.HtlcMsat = uint64(onionErr.IncomingHTLCAmount)
case *lnwire.FailFinalExpiryTooSoon:
response.Code = lnrpc.Failure_FINAL_EXPIRY_TOO_SOON
case *lnwire.FailInvalidRealm:
response.Code = lnrpc.Failure_INVALID_REALM
case *lnwire.FailExpiryTooSoon:
response.Code = lnrpc.Failure_EXPIRY_TOO_SOON
response.ChannelUpdate = marshallChannelUpdate(&onionErr.Update)
case *lnwire.FailExpiryTooFar:
response.Code = lnrpc.Failure_EXPIRY_TOO_FAR
case *lnwire.FailInvalidOnionVersion:
response.Code = lnrpc.Failure_INVALID_ONION_VERSION
response.OnionSha_256 = onionErr.OnionSHA256[:]
case *lnwire.FailInvalidOnionHmac:
response.Code = lnrpc.Failure_INVALID_ONION_HMAC
response.OnionSha_256 = onionErr.OnionSHA256[:]
case *lnwire.FailInvalidOnionKey:
response.Code = lnrpc.Failure_INVALID_ONION_KEY
response.OnionSha_256 = onionErr.OnionSHA256[:]
case *lnwire.FailAmountBelowMinimum:
response.Code = lnrpc.Failure_AMOUNT_BELOW_MINIMUM
response.ChannelUpdate = marshallChannelUpdate(&onionErr.Update)
response.HtlcMsat = uint64(onionErr.HtlcMsat)
case *lnwire.FailFeeInsufficient:
response.Code = lnrpc.Failure_FEE_INSUFFICIENT
response.ChannelUpdate = marshallChannelUpdate(&onionErr.Update)
response.HtlcMsat = uint64(onionErr.HtlcMsat)
case *lnwire.FailIncorrectCltvExpiry:
response.Code = lnrpc.Failure_INCORRECT_CLTV_EXPIRY
response.ChannelUpdate = marshallChannelUpdate(&onionErr.Update)
response.CltvExpiry = onionErr.CltvExpiry
case *lnwire.FailChannelDisabled:
response.Code = lnrpc.Failure_CHANNEL_DISABLED
response.ChannelUpdate = marshallChannelUpdate(&onionErr.Update)
response.Flags = uint32(onionErr.Flags)
case *lnwire.FailTemporaryChannelFailure:
response.Code = lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE
response.ChannelUpdate = marshallChannelUpdate(onionErr.Update)
case *lnwire.FailRequiredNodeFeatureMissing:
response.Code = lnrpc.Failure_REQUIRED_NODE_FEATURE_MISSING
case *lnwire.FailRequiredChannelFeatureMissing:
response.Code = lnrpc.Failure_REQUIRED_CHANNEL_FEATURE_MISSING
case *lnwire.FailUnknownNextPeer:
response.Code = lnrpc.Failure_UNKNOWN_NEXT_PEER
case *lnwire.FailTemporaryNodeFailure:
response.Code = lnrpc.Failure_TEMPORARY_NODE_FAILURE
case *lnwire.FailPermanentNodeFailure:
response.Code = lnrpc.Failure_PERMANENT_NODE_FAILURE
case *lnwire.FailPermanentChannelFailure:
response.Code = lnrpc.Failure_PERMANENT_CHANNEL_FAILURE
case *lnwire.FailMPPTimeout:
response.Code = lnrpc.Failure_MPP_TIMEOUT
case *lnwire.InvalidOnionPayload:
response.Code = lnrpc.Failure_INVALID_ONION_PAYLOAD
case *lnwire.FailInvalidBlinding:
response.Code = lnrpc.Failure_INVALID_ONION_BLINDING
response.OnionSha_256 = onionErr.OnionSHA256[:]
case nil:
response.Code = lnrpc.Failure_UNKNOWN_FAILURE
default:
return fmt.Errorf("cannot marshall failure %T", onionErr)
}
return nil
}
// marshallChannelUpdate marshalls a channel update as received over the wire to
// the router rpc format.
func marshallChannelUpdate(update *lnwire.ChannelUpdate1) *lnrpc.ChannelUpdate {
if update == nil {
return nil
}
return &lnrpc.ChannelUpdate{
Signature: update.Signature.RawBytes(),
ChainHash: update.ChainHash[:],
ChanId: update.ShortChannelID.ToUint64(),
Timestamp: update.Timestamp,
MessageFlags: uint32(update.MessageFlags),
ChannelFlags: uint32(update.ChannelFlags),
TimeLockDelta: uint32(update.TimeLockDelta),
HtlcMinimumMsat: uint64(update.HtlcMinimumMsat),
BaseFee: update.BaseFee,
FeeRate: update.FeeRate,
HtlcMaximumMsat: uint64(update.HtlcMaximumMsat),
ExtraOpaqueData: update.ExtraOpaqueData,
}
}
// MarshallPayment marshall a payment to its rpc representation.
func (r *RouterBackend) MarshallPayment(payment *channeldb.MPPayment) (
*lnrpc.Payment, error) {
// Fetch the payment's preimage and the total paid in fees.
var (
fee lnwire.MilliSatoshi
preimage lntypes.Preimage
)
for _, htlc := range payment.HTLCs {
// If any of the htlcs have settled, extract a valid
// preimage.
if htlc.Settle != nil {
preimage = htlc.Settle.Preimage
fee += htlc.Route.TotalFees()
}
}
msatValue := int64(payment.Info.Value)
satValue := int64(payment.Info.Value.ToSatoshis())
status, err := convertPaymentStatus(
payment.Status, r.UseStatusInitiated,
)
if err != nil {
return nil, err
}
htlcs := make([]*lnrpc.HTLCAttempt, 0, len(payment.HTLCs))
for _, dbHTLC := range payment.HTLCs {
htlc, err := r.MarshalHTLCAttempt(dbHTLC)
if err != nil {
return nil, err
}
htlcs = append(htlcs, htlc)
}
paymentID := payment.Info.PaymentIdentifier
creationTimeNS := MarshalTimeNano(payment.Info.CreationTime)
failureReason, err := marshallPaymentFailureReason(
payment.FailureReason,
)
if err != nil {
return nil, err
}
return &lnrpc.Payment{
// TODO: set this to setID for AMP-payments?
PaymentHash: hex.EncodeToString(paymentID[:]),
Value: satValue,
ValueMsat: msatValue,
ValueSat: satValue,
CreationDate: payment.Info.CreationTime.Unix(),
CreationTimeNs: creationTimeNS,
Fee: int64(fee.ToSatoshis()),
FeeSat: int64(fee.ToSatoshis()),
FeeMsat: int64(fee),
PaymentPreimage: hex.EncodeToString(preimage[:]),
PaymentRequest: string(payment.Info.PaymentRequest),
Status: status,
Htlcs: htlcs,
PaymentIndex: payment.SequenceNum,
FailureReason: failureReason,
FirstHopCustomRecords: payment.Info.FirstHopCustomRecords,
}, nil
}
// convertPaymentStatus converts a channeldb.PaymentStatus to the type expected
// by the RPC.
func convertPaymentStatus(dbStatus channeldb.PaymentStatus, useInit bool) (
lnrpc.Payment_PaymentStatus, error) {
switch dbStatus {
case channeldb.StatusInitiated:
// If the client understands the new status, return it.
if useInit {
return lnrpc.Payment_INITIATED, nil
}
// Otherwise remain the old behavior.
return lnrpc.Payment_IN_FLIGHT, nil
case channeldb.StatusInFlight:
return lnrpc.Payment_IN_FLIGHT, nil
case channeldb.StatusSucceeded:
return lnrpc.Payment_SUCCEEDED, nil
case channeldb.StatusFailed:
return lnrpc.Payment_FAILED, nil
default:
return 0, fmt.Errorf("unhandled payment status %v", dbStatus)
}
}
// marshallPaymentFailureReason marshalls the failure reason to the corresponding rpc
// type.
func marshallPaymentFailureReason(reason *channeldb.FailureReason) (
lnrpc.PaymentFailureReason, error) {
if reason == nil {
return lnrpc.PaymentFailureReason_FAILURE_REASON_NONE, nil
}
switch *reason {
case channeldb.FailureReasonTimeout:
return lnrpc.PaymentFailureReason_FAILURE_REASON_TIMEOUT, nil
case channeldb.FailureReasonNoRoute:
return lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE, nil
case channeldb.FailureReasonError:
return lnrpc.PaymentFailureReason_FAILURE_REASON_ERROR, nil
case channeldb.FailureReasonPaymentDetails:
return lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS, nil
case channeldb.FailureReasonInsufficientBalance:
return lnrpc.PaymentFailureReason_FAILURE_REASON_INSUFFICIENT_BALANCE, nil
case channeldb.FailureReasonCanceled:
return lnrpc.PaymentFailureReason_FAILURE_REASON_CANCELED, nil
}
return 0, errors.New("unknown failure reason")
}
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package routerrpc
import (
context "context"
lnrpc "github.com/lightningnetwork/lnd/lnrpc"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// RouterClient is the client API for Router service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type RouterClient interface {
// SendPaymentV2 attempts to route a payment described by the passed
// PaymentRequest to the final destination. The call returns a stream of
// payment updates. When using this RPC, make sure to set a fee limit, as the
// default routing fee limit is 0 sats. Without a non-zero fee limit only
// routes without fees will be attempted which often fails with
// FAILURE_REASON_NO_ROUTE.
SendPaymentV2(ctx context.Context, in *SendPaymentRequest, opts ...grpc.CallOption) (Router_SendPaymentV2Client, error)
// lncli: `trackpayment`
// TrackPaymentV2 returns an update stream for the payment identified by the
// payment hash.
TrackPaymentV2(ctx context.Context, in *TrackPaymentRequest, opts ...grpc.CallOption) (Router_TrackPaymentV2Client, error)
// TrackPayments returns an update stream for every payment that is not in a
// terminal state. Note that if payments are in-flight while starting a new
// subscription, the start of the payment stream could produce out-of-order
// and/or duplicate events. In order to get updates for every in-flight
// payment attempt make sure to subscribe to this method before initiating any
// payments.
TrackPayments(ctx context.Context, in *TrackPaymentsRequest, opts ...grpc.CallOption) (Router_TrackPaymentsClient, error)
// EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it
// may cost to send an HTLC to the target end destination.
EstimateRouteFee(ctx context.Context, in *RouteFeeRequest, opts ...grpc.CallOption) (*RouteFeeResponse, error)
// Deprecated: Do not use.
//
// Deprecated, use SendToRouteV2. SendToRoute attempts to make a payment via
// the specified route. This method differs from SendPayment in that it
// allows users to specify a full route manually. This can be used for
// things like rebalancing, and atomic swaps. It differs from the newer
// SendToRouteV2 in that it doesn't return the full HTLC information.
SendToRoute(ctx context.Context, in *SendToRouteRequest, opts ...grpc.CallOption) (*SendToRouteResponse, error)
// SendToRouteV2 attempts to make a payment via the specified route. This
// method differs from SendPayment in that it allows users to specify a full
// route manually. This can be used for things like rebalancing, and atomic
// swaps.
SendToRouteV2(ctx context.Context, in *SendToRouteRequest, opts ...grpc.CallOption) (*lnrpc.HTLCAttempt, error)
// lncli: `resetmc`
// ResetMissionControl clears all mission control state and starts with a clean
// slate.
ResetMissionControl(ctx context.Context, in *ResetMissionControlRequest, opts ...grpc.CallOption) (*ResetMissionControlResponse, error)
// lncli: `querymc`
// QueryMissionControl exposes the internal mission control state to callers.
// It is a development feature.
QueryMissionControl(ctx context.Context, in *QueryMissionControlRequest, opts ...grpc.CallOption) (*QueryMissionControlResponse, error)
// lncli: `importmc`
// XImportMissionControl is an experimental API that imports the state provided
// to the internal mission control's state, using all results which are more
// recent than our existing values. These values will only be imported
// in-memory, and will not be persisted across restarts.
XImportMissionControl(ctx context.Context, in *XImportMissionControlRequest, opts ...grpc.CallOption) (*XImportMissionControlResponse, error)
// lncli: `getmccfg`
// GetMissionControlConfig returns mission control's current config.
GetMissionControlConfig(ctx context.Context, in *GetMissionControlConfigRequest, opts ...grpc.CallOption) (*GetMissionControlConfigResponse, error)
// lncli: `setmccfg`
// SetMissionControlConfig will set mission control's config, if the config
// provided is valid.
SetMissionControlConfig(ctx context.Context, in *SetMissionControlConfigRequest, opts ...grpc.CallOption) (*SetMissionControlConfigResponse, error)
// lncli: `queryprob`
// Deprecated. QueryProbability returns the current success probability
// estimate for a given node pair and amount. The call returns a zero success
// probability if no channel is available or if the amount violates min/max
// HTLC constraints.
QueryProbability(ctx context.Context, in *QueryProbabilityRequest, opts ...grpc.CallOption) (*QueryProbabilityResponse, error)
// lncli: `buildroute`
// BuildRoute builds a fully specified route based on a list of hop public
// keys. It retrieves the relevant channel policies from the graph in order to
// calculate the correct fees and time locks.
// Note that LND will use its default final_cltv_delta if no value is supplied.
// Make sure to add the correct final_cltv_delta depending on the invoice
// restriction. Moreover the caller has to make sure to provide the
// payment_addr if the route is paying an invoice which signaled it.
BuildRoute(ctx context.Context, in *BuildRouteRequest, opts ...grpc.CallOption) (*BuildRouteResponse, error)
// SubscribeHtlcEvents creates a uni-directional stream from the server to
// the client which delivers a stream of htlc events.
SubscribeHtlcEvents(ctx context.Context, in *SubscribeHtlcEventsRequest, opts ...grpc.CallOption) (Router_SubscribeHtlcEventsClient, error)
// Deprecated: Do not use.
//
// Deprecated, use SendPaymentV2. SendPayment attempts to route a payment
// described by the passed PaymentRequest to the final destination. The call
// returns a stream of payment status updates.
SendPayment(ctx context.Context, in *SendPaymentRequest, opts ...grpc.CallOption) (Router_SendPaymentClient, error)
// Deprecated: Do not use.
//
// Deprecated, use TrackPaymentV2. TrackPayment returns an update stream for
// the payment identified by the payment hash.
TrackPayment(ctx context.Context, in *TrackPaymentRequest, opts ...grpc.CallOption) (Router_TrackPaymentClient, error)
// *
// HtlcInterceptor dispatches a bi-directional streaming RPC in which
// Forwarded HTLC requests are sent to the client and the client responds with
// a boolean that tells LND if this htlc should be intercepted.
// In case of interception, the htlc can be either settled, cancelled or
// resumed later by using the ResolveHoldForward endpoint.
HtlcInterceptor(ctx context.Context, opts ...grpc.CallOption) (Router_HtlcInterceptorClient, error)
// lncli: `updatechanstatus`
// UpdateChanStatus attempts to manually set the state of a channel
// (enabled, disabled, or auto). A manual "disable" request will cause the
// channel to stay disabled until a subsequent manual request of either
// "enable" or "auto".
UpdateChanStatus(ctx context.Context, in *UpdateChanStatusRequest, opts ...grpc.CallOption) (*UpdateChanStatusResponse, error)
// XAddLocalChanAliases is an experimental API that creates a set of new
// channel SCID alias mappings. The final total set of aliases in the manager
// after the add operation is returned. This is only a locally stored alias,
// and will not be communicated to the channel peer via any message. Therefore,
// routing over such an alias will only work if the peer also calls this same
// RPC on their end. If an alias already exists, an error is returned
XAddLocalChanAliases(ctx context.Context, in *AddAliasesRequest, opts ...grpc.CallOption) (*AddAliasesResponse, error)
// XDeleteLocalChanAliases is an experimental API that deletes a set of alias
// mappings. The final total set of aliases in the manager after the delete
// operation is returned. The deletion will not be communicated to the channel
// peer via any message.
XDeleteLocalChanAliases(ctx context.Context, in *DeleteAliasesRequest, opts ...grpc.CallOption) (*DeleteAliasesResponse, error)
}
type routerClient struct {
cc grpc.ClientConnInterface
}
func NewRouterClient(cc grpc.ClientConnInterface) RouterClient {
return &routerClient{cc}
}
func (c *routerClient) SendPaymentV2(ctx context.Context, in *SendPaymentRequest, opts ...grpc.CallOption) (Router_SendPaymentV2Client, error) {
stream, err := c.cc.NewStream(ctx, &Router_ServiceDesc.Streams[0], "/routerrpc.Router/SendPaymentV2", opts...)
if err != nil {
return nil, err
}
x := &routerSendPaymentV2Client{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Router_SendPaymentV2Client interface {
Recv() (*lnrpc.Payment, error)
grpc.ClientStream
}
type routerSendPaymentV2Client struct {
grpc.ClientStream
}
func (x *routerSendPaymentV2Client) Recv() (*lnrpc.Payment, error) {
m := new(lnrpc.Payment)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *routerClient) TrackPaymentV2(ctx context.Context, in *TrackPaymentRequest, opts ...grpc.CallOption) (Router_TrackPaymentV2Client, error) {
stream, err := c.cc.NewStream(ctx, &Router_ServiceDesc.Streams[1], "/routerrpc.Router/TrackPaymentV2", opts...)
if err != nil {
return nil, err
}
x := &routerTrackPaymentV2Client{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Router_TrackPaymentV2Client interface {
Recv() (*lnrpc.Payment, error)
grpc.ClientStream
}
type routerTrackPaymentV2Client struct {
grpc.ClientStream
}
func (x *routerTrackPaymentV2Client) Recv() (*lnrpc.Payment, error) {
m := new(lnrpc.Payment)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *routerClient) TrackPayments(ctx context.Context, in *TrackPaymentsRequest, opts ...grpc.CallOption) (Router_TrackPaymentsClient, error) {
stream, err := c.cc.NewStream(ctx, &Router_ServiceDesc.Streams[2], "/routerrpc.Router/TrackPayments", opts...)
if err != nil {
return nil, err
}
x := &routerTrackPaymentsClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Router_TrackPaymentsClient interface {
Recv() (*lnrpc.Payment, error)
grpc.ClientStream
}
type routerTrackPaymentsClient struct {
grpc.ClientStream
}
func (x *routerTrackPaymentsClient) Recv() (*lnrpc.Payment, error) {
m := new(lnrpc.Payment)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *routerClient) EstimateRouteFee(ctx context.Context, in *RouteFeeRequest, opts ...grpc.CallOption) (*RouteFeeResponse, error) {
out := new(RouteFeeResponse)
err := c.cc.Invoke(ctx, "/routerrpc.Router/EstimateRouteFee", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Deprecated: Do not use.
func (c *routerClient) SendToRoute(ctx context.Context, in *SendToRouteRequest, opts ...grpc.CallOption) (*SendToRouteResponse, error) {
out := new(SendToRouteResponse)
err := c.cc.Invoke(ctx, "/routerrpc.Router/SendToRoute", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *routerClient) SendToRouteV2(ctx context.Context, in *SendToRouteRequest, opts ...grpc.CallOption) (*lnrpc.HTLCAttempt, error) {
out := new(lnrpc.HTLCAttempt)
err := c.cc.Invoke(ctx, "/routerrpc.Router/SendToRouteV2", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *routerClient) ResetMissionControl(ctx context.Context, in *ResetMissionControlRequest, opts ...grpc.CallOption) (*ResetMissionControlResponse, error) {
out := new(ResetMissionControlResponse)
err := c.cc.Invoke(ctx, "/routerrpc.Router/ResetMissionControl", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *routerClient) QueryMissionControl(ctx context.Context, in *QueryMissionControlRequest, opts ...grpc.CallOption) (*QueryMissionControlResponse, error) {
out := new(QueryMissionControlResponse)
err := c.cc.Invoke(ctx, "/routerrpc.Router/QueryMissionControl", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *routerClient) XImportMissionControl(ctx context.Context, in *XImportMissionControlRequest, opts ...grpc.CallOption) (*XImportMissionControlResponse, error) {
out := new(XImportMissionControlResponse)
err := c.cc.Invoke(ctx, "/routerrpc.Router/XImportMissionControl", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *routerClient) GetMissionControlConfig(ctx context.Context, in *GetMissionControlConfigRequest, opts ...grpc.CallOption) (*GetMissionControlConfigResponse, error) {
out := new(GetMissionControlConfigResponse)
err := c.cc.Invoke(ctx, "/routerrpc.Router/GetMissionControlConfig", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *routerClient) SetMissionControlConfig(ctx context.Context, in *SetMissionControlConfigRequest, opts ...grpc.CallOption) (*SetMissionControlConfigResponse, error) {
out := new(SetMissionControlConfigResponse)
err := c.cc.Invoke(ctx, "/routerrpc.Router/SetMissionControlConfig", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *routerClient) QueryProbability(ctx context.Context, in *QueryProbabilityRequest, opts ...grpc.CallOption) (*QueryProbabilityResponse, error) {
out := new(QueryProbabilityResponse)
err := c.cc.Invoke(ctx, "/routerrpc.Router/QueryProbability", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *routerClient) BuildRoute(ctx context.Context, in *BuildRouteRequest, opts ...grpc.CallOption) (*BuildRouteResponse, error) {
out := new(BuildRouteResponse)
err := c.cc.Invoke(ctx, "/routerrpc.Router/BuildRoute", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *routerClient) SubscribeHtlcEvents(ctx context.Context, in *SubscribeHtlcEventsRequest, opts ...grpc.CallOption) (Router_SubscribeHtlcEventsClient, error) {
stream, err := c.cc.NewStream(ctx, &Router_ServiceDesc.Streams[3], "/routerrpc.Router/SubscribeHtlcEvents", opts...)
if err != nil {
return nil, err
}
x := &routerSubscribeHtlcEventsClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Router_SubscribeHtlcEventsClient interface {
Recv() (*HtlcEvent, error)
grpc.ClientStream
}
type routerSubscribeHtlcEventsClient struct {
grpc.ClientStream
}
func (x *routerSubscribeHtlcEventsClient) Recv() (*HtlcEvent, error) {
m := new(HtlcEvent)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// Deprecated: Do not use.
func (c *routerClient) SendPayment(ctx context.Context, in *SendPaymentRequest, opts ...grpc.CallOption) (Router_SendPaymentClient, error) {
stream, err := c.cc.NewStream(ctx, &Router_ServiceDesc.Streams[4], "/routerrpc.Router/SendPayment", opts...)
if err != nil {
return nil, err
}
x := &routerSendPaymentClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Router_SendPaymentClient interface {
Recv() (*PaymentStatus, error)
grpc.ClientStream
}
type routerSendPaymentClient struct {
grpc.ClientStream
}
func (x *routerSendPaymentClient) Recv() (*PaymentStatus, error) {
m := new(PaymentStatus)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// Deprecated: Do not use.
func (c *routerClient) TrackPayment(ctx context.Context, in *TrackPaymentRequest, opts ...grpc.CallOption) (Router_TrackPaymentClient, error) {
stream, err := c.cc.NewStream(ctx, &Router_ServiceDesc.Streams[5], "/routerrpc.Router/TrackPayment", opts...)
if err != nil {
return nil, err
}
x := &routerTrackPaymentClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Router_TrackPaymentClient interface {
Recv() (*PaymentStatus, error)
grpc.ClientStream
}
type routerTrackPaymentClient struct {
grpc.ClientStream
}
func (x *routerTrackPaymentClient) Recv() (*PaymentStatus, error) {
m := new(PaymentStatus)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *routerClient) HtlcInterceptor(ctx context.Context, opts ...grpc.CallOption) (Router_HtlcInterceptorClient, error) {
stream, err := c.cc.NewStream(ctx, &Router_ServiceDesc.Streams[6], "/routerrpc.Router/HtlcInterceptor", opts...)
if err != nil {
return nil, err
}
x := &routerHtlcInterceptorClient{stream}
return x, nil
}
type Router_HtlcInterceptorClient interface {
Send(*ForwardHtlcInterceptResponse) error
Recv() (*ForwardHtlcInterceptRequest, error)
grpc.ClientStream
}
type routerHtlcInterceptorClient struct {
grpc.ClientStream
}
func (x *routerHtlcInterceptorClient) Send(m *ForwardHtlcInterceptResponse) error {
return x.ClientStream.SendMsg(m)
}
func (x *routerHtlcInterceptorClient) Recv() (*ForwardHtlcInterceptRequest, error) {
m := new(ForwardHtlcInterceptRequest)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *routerClient) UpdateChanStatus(ctx context.Context, in *UpdateChanStatusRequest, opts ...grpc.CallOption) (*UpdateChanStatusResponse, error) {
out := new(UpdateChanStatusResponse)
err := c.cc.Invoke(ctx, "/routerrpc.Router/UpdateChanStatus", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *routerClient) XAddLocalChanAliases(ctx context.Context, in *AddAliasesRequest, opts ...grpc.CallOption) (*AddAliasesResponse, error) {
out := new(AddAliasesResponse)
err := c.cc.Invoke(ctx, "/routerrpc.Router/XAddLocalChanAliases", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *routerClient) XDeleteLocalChanAliases(ctx context.Context, in *DeleteAliasesRequest, opts ...grpc.CallOption) (*DeleteAliasesResponse, error) {
out := new(DeleteAliasesResponse)
err := c.cc.Invoke(ctx, "/routerrpc.Router/XDeleteLocalChanAliases", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// RouterServer is the server API for Router service.
// All implementations must embed UnimplementedRouterServer
// for forward compatibility
type RouterServer interface {
// SendPaymentV2 attempts to route a payment described by the passed
// PaymentRequest to the final destination. The call returns a stream of
// payment updates. When using this RPC, make sure to set a fee limit, as the
// default routing fee limit is 0 sats. Without a non-zero fee limit only
// routes without fees will be attempted which often fails with
// FAILURE_REASON_NO_ROUTE.
SendPaymentV2(*SendPaymentRequest, Router_SendPaymentV2Server) error
// lncli: `trackpayment`
// TrackPaymentV2 returns an update stream for the payment identified by the
// payment hash.
TrackPaymentV2(*TrackPaymentRequest, Router_TrackPaymentV2Server) error
// TrackPayments returns an update stream for every payment that is not in a
// terminal state. Note that if payments are in-flight while starting a new
// subscription, the start of the payment stream could produce out-of-order
// and/or duplicate events. In order to get updates for every in-flight
// payment attempt make sure to subscribe to this method before initiating any
// payments.
TrackPayments(*TrackPaymentsRequest, Router_TrackPaymentsServer) error
// EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it
// may cost to send an HTLC to the target end destination.
EstimateRouteFee(context.Context, *RouteFeeRequest) (*RouteFeeResponse, error)
// Deprecated: Do not use.
//
// Deprecated, use SendToRouteV2. SendToRoute attempts to make a payment via
// the specified route. This method differs from SendPayment in that it
// allows users to specify a full route manually. This can be used for
// things like rebalancing, and atomic swaps. It differs from the newer
// SendToRouteV2 in that it doesn't return the full HTLC information.
SendToRoute(context.Context, *SendToRouteRequest) (*SendToRouteResponse, error)
// SendToRouteV2 attempts to make a payment via the specified route. This
// method differs from SendPayment in that it allows users to specify a full
// route manually. This can be used for things like rebalancing, and atomic
// swaps.
SendToRouteV2(context.Context, *SendToRouteRequest) (*lnrpc.HTLCAttempt, error)
// lncli: `resetmc`
// ResetMissionControl clears all mission control state and starts with a clean
// slate.
ResetMissionControl(context.Context, *ResetMissionControlRequest) (*ResetMissionControlResponse, error)
// lncli: `querymc`
// QueryMissionControl exposes the internal mission control state to callers.
// It is a development feature.
QueryMissionControl(context.Context, *QueryMissionControlRequest) (*QueryMissionControlResponse, error)
// lncli: `importmc`
// XImportMissionControl is an experimental API that imports the state provided
// to the internal mission control's state, using all results which are more
// recent than our existing values. These values will only be imported
// in-memory, and will not be persisted across restarts.
XImportMissionControl(context.Context, *XImportMissionControlRequest) (*XImportMissionControlResponse, error)
// lncli: `getmccfg`
// GetMissionControlConfig returns mission control's current config.
GetMissionControlConfig(context.Context, *GetMissionControlConfigRequest) (*GetMissionControlConfigResponse, error)
// lncli: `setmccfg`
// SetMissionControlConfig will set mission control's config, if the config
// provided is valid.
SetMissionControlConfig(context.Context, *SetMissionControlConfigRequest) (*SetMissionControlConfigResponse, error)
// lncli: `queryprob`
// Deprecated. QueryProbability returns the current success probability
// estimate for a given node pair and amount. The call returns a zero success
// probability if no channel is available or if the amount violates min/max
// HTLC constraints.
QueryProbability(context.Context, *QueryProbabilityRequest) (*QueryProbabilityResponse, error)
// lncli: `buildroute`
// BuildRoute builds a fully specified route based on a list of hop public
// keys. It retrieves the relevant channel policies from the graph in order to
// calculate the correct fees and time locks.
// Note that LND will use its default final_cltv_delta if no value is supplied.
// Make sure to add the correct final_cltv_delta depending on the invoice
// restriction. Moreover the caller has to make sure to provide the
// payment_addr if the route is paying an invoice which signaled it.
BuildRoute(context.Context, *BuildRouteRequest) (*BuildRouteResponse, error)
// SubscribeHtlcEvents creates a uni-directional stream from the server to
// the client which delivers a stream of htlc events.
SubscribeHtlcEvents(*SubscribeHtlcEventsRequest, Router_SubscribeHtlcEventsServer) error
// Deprecated: Do not use.
//
// Deprecated, use SendPaymentV2. SendPayment attempts to route a payment
// described by the passed PaymentRequest to the final destination. The call
// returns a stream of payment status updates.
SendPayment(*SendPaymentRequest, Router_SendPaymentServer) error
// Deprecated: Do not use.
//
// Deprecated, use TrackPaymentV2. TrackPayment returns an update stream for
// the payment identified by the payment hash.
TrackPayment(*TrackPaymentRequest, Router_TrackPaymentServer) error
// *
// HtlcInterceptor dispatches a bi-directional streaming RPC in which
// Forwarded HTLC requests are sent to the client and the client responds with
// a boolean that tells LND if this htlc should be intercepted.
// In case of interception, the htlc can be either settled, cancelled or
// resumed later by using the ResolveHoldForward endpoint.
HtlcInterceptor(Router_HtlcInterceptorServer) error
// lncli: `updatechanstatus`
// UpdateChanStatus attempts to manually set the state of a channel
// (enabled, disabled, or auto). A manual "disable" request will cause the
// channel to stay disabled until a subsequent manual request of either
// "enable" or "auto".
UpdateChanStatus(context.Context, *UpdateChanStatusRequest) (*UpdateChanStatusResponse, error)
// XAddLocalChanAliases is an experimental API that creates a set of new
// channel SCID alias mappings. The final total set of aliases in the manager
// after the add operation is returned. This is only a locally stored alias,
// and will not be communicated to the channel peer via any message. Therefore,
// routing over such an alias will only work if the peer also calls this same
// RPC on their end. If an alias already exists, an error is returned
XAddLocalChanAliases(context.Context, *AddAliasesRequest) (*AddAliasesResponse, error)
// XDeleteLocalChanAliases is an experimental API that deletes a set of alias
// mappings. The final total set of aliases in the manager after the delete
// operation is returned. The deletion will not be communicated to the channel
// peer via any message.
XDeleteLocalChanAliases(context.Context, *DeleteAliasesRequest) (*DeleteAliasesResponse, error)
mustEmbedUnimplementedRouterServer()
}
// UnimplementedRouterServer must be embedded to have forward compatible implementations.
type UnimplementedRouterServer struct {
}
func (UnimplementedRouterServer) SendPaymentV2(*SendPaymentRequest, Router_SendPaymentV2Server) error {
return status.Errorf(codes.Unimplemented, "method SendPaymentV2 not implemented")
}
func (UnimplementedRouterServer) TrackPaymentV2(*TrackPaymentRequest, Router_TrackPaymentV2Server) error {
return status.Errorf(codes.Unimplemented, "method TrackPaymentV2 not implemented")
}
func (UnimplementedRouterServer) TrackPayments(*TrackPaymentsRequest, Router_TrackPaymentsServer) error {
return status.Errorf(codes.Unimplemented, "method TrackPayments not implemented")
}
func (UnimplementedRouterServer) EstimateRouteFee(context.Context, *RouteFeeRequest) (*RouteFeeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method EstimateRouteFee not implemented")
}
func (UnimplementedRouterServer) SendToRoute(context.Context, *SendToRouteRequest) (*SendToRouteResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SendToRoute not implemented")
}
func (UnimplementedRouterServer) SendToRouteV2(context.Context, *SendToRouteRequest) (*lnrpc.HTLCAttempt, error) {
return nil, status.Errorf(codes.Unimplemented, "method SendToRouteV2 not implemented")
}
func (UnimplementedRouterServer) ResetMissionControl(context.Context, *ResetMissionControlRequest) (*ResetMissionControlResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ResetMissionControl not implemented")
}
func (UnimplementedRouterServer) QueryMissionControl(context.Context, *QueryMissionControlRequest) (*QueryMissionControlResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method QueryMissionControl not implemented")
}
func (UnimplementedRouterServer) XImportMissionControl(context.Context, *XImportMissionControlRequest) (*XImportMissionControlResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method XImportMissionControl not implemented")
}
func (UnimplementedRouterServer) GetMissionControlConfig(context.Context, *GetMissionControlConfigRequest) (*GetMissionControlConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetMissionControlConfig not implemented")
}
func (UnimplementedRouterServer) SetMissionControlConfig(context.Context, *SetMissionControlConfigRequest) (*SetMissionControlConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetMissionControlConfig not implemented")
}
func (UnimplementedRouterServer) QueryProbability(context.Context, *QueryProbabilityRequest) (*QueryProbabilityResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method QueryProbability not implemented")
}
func (UnimplementedRouterServer) BuildRoute(context.Context, *BuildRouteRequest) (*BuildRouteResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BuildRoute not implemented")
}
func (UnimplementedRouterServer) SubscribeHtlcEvents(*SubscribeHtlcEventsRequest, Router_SubscribeHtlcEventsServer) error {
return status.Errorf(codes.Unimplemented, "method SubscribeHtlcEvents not implemented")
}
func (UnimplementedRouterServer) SendPayment(*SendPaymentRequest, Router_SendPaymentServer) error {
return status.Errorf(codes.Unimplemented, "method SendPayment not implemented")
}
func (UnimplementedRouterServer) TrackPayment(*TrackPaymentRequest, Router_TrackPaymentServer) error {
return status.Errorf(codes.Unimplemented, "method TrackPayment not implemented")
}
func (UnimplementedRouterServer) HtlcInterceptor(Router_HtlcInterceptorServer) error {
return status.Errorf(codes.Unimplemented, "method HtlcInterceptor not implemented")
}
func (UnimplementedRouterServer) UpdateChanStatus(context.Context, *UpdateChanStatusRequest) (*UpdateChanStatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateChanStatus not implemented")
}
func (UnimplementedRouterServer) XAddLocalChanAliases(context.Context, *AddAliasesRequest) (*AddAliasesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method XAddLocalChanAliases not implemented")
}
func (UnimplementedRouterServer) XDeleteLocalChanAliases(context.Context, *DeleteAliasesRequest) (*DeleteAliasesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method XDeleteLocalChanAliases not implemented")
}
func (UnimplementedRouterServer) mustEmbedUnimplementedRouterServer() {}
// UnsafeRouterServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to RouterServer will
// result in compilation errors.
type UnsafeRouterServer interface {
mustEmbedUnimplementedRouterServer()
}
func RegisterRouterServer(s grpc.ServiceRegistrar, srv RouterServer) {
s.RegisterService(&Router_ServiceDesc, srv)
}
func _Router_SendPaymentV2_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(SendPaymentRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(RouterServer).SendPaymentV2(m, &routerSendPaymentV2Server{stream})
}
type Router_SendPaymentV2Server interface {
Send(*lnrpc.Payment) error
grpc.ServerStream
}
type routerSendPaymentV2Server struct {
grpc.ServerStream
}
func (x *routerSendPaymentV2Server) Send(m *lnrpc.Payment) error {
return x.ServerStream.SendMsg(m)
}
func _Router_TrackPaymentV2_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(TrackPaymentRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(RouterServer).TrackPaymentV2(m, &routerTrackPaymentV2Server{stream})
}
type Router_TrackPaymentV2Server interface {
Send(*lnrpc.Payment) error
grpc.ServerStream
}
type routerTrackPaymentV2Server struct {
grpc.ServerStream
}
func (x *routerTrackPaymentV2Server) Send(m *lnrpc.Payment) error {
return x.ServerStream.SendMsg(m)
}
func _Router_TrackPayments_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(TrackPaymentsRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(RouterServer).TrackPayments(m, &routerTrackPaymentsServer{stream})
}
type Router_TrackPaymentsServer interface {
Send(*lnrpc.Payment) error
grpc.ServerStream
}
type routerTrackPaymentsServer struct {
grpc.ServerStream
}
func (x *routerTrackPaymentsServer) Send(m *lnrpc.Payment) error {
return x.ServerStream.SendMsg(m)
}
func _Router_EstimateRouteFee_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RouteFeeRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RouterServer).EstimateRouteFee(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/routerrpc.Router/EstimateRouteFee",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RouterServer).EstimateRouteFee(ctx, req.(*RouteFeeRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Router_SendToRoute_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SendToRouteRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RouterServer).SendToRoute(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/routerrpc.Router/SendToRoute",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RouterServer).SendToRoute(ctx, req.(*SendToRouteRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Router_SendToRouteV2_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SendToRouteRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RouterServer).SendToRouteV2(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/routerrpc.Router/SendToRouteV2",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RouterServer).SendToRouteV2(ctx, req.(*SendToRouteRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Router_ResetMissionControl_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ResetMissionControlRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RouterServer).ResetMissionControl(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/routerrpc.Router/ResetMissionControl",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RouterServer).ResetMissionControl(ctx, req.(*ResetMissionControlRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Router_QueryMissionControl_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(QueryMissionControlRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RouterServer).QueryMissionControl(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/routerrpc.Router/QueryMissionControl",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RouterServer).QueryMissionControl(ctx, req.(*QueryMissionControlRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Router_XImportMissionControl_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(XImportMissionControlRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RouterServer).XImportMissionControl(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/routerrpc.Router/XImportMissionControl",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RouterServer).XImportMissionControl(ctx, req.(*XImportMissionControlRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Router_GetMissionControlConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetMissionControlConfigRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RouterServer).GetMissionControlConfig(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/routerrpc.Router/GetMissionControlConfig",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RouterServer).GetMissionControlConfig(ctx, req.(*GetMissionControlConfigRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Router_SetMissionControlConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SetMissionControlConfigRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RouterServer).SetMissionControlConfig(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/routerrpc.Router/SetMissionControlConfig",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RouterServer).SetMissionControlConfig(ctx, req.(*SetMissionControlConfigRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Router_QueryProbability_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(QueryProbabilityRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RouterServer).QueryProbability(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/routerrpc.Router/QueryProbability",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RouterServer).QueryProbability(ctx, req.(*QueryProbabilityRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Router_BuildRoute_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(BuildRouteRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RouterServer).BuildRoute(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/routerrpc.Router/BuildRoute",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RouterServer).BuildRoute(ctx, req.(*BuildRouteRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Router_SubscribeHtlcEvents_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(SubscribeHtlcEventsRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(RouterServer).SubscribeHtlcEvents(m, &routerSubscribeHtlcEventsServer{stream})
}
type Router_SubscribeHtlcEventsServer interface {
Send(*HtlcEvent) error
grpc.ServerStream
}
type routerSubscribeHtlcEventsServer struct {
grpc.ServerStream
}
func (x *routerSubscribeHtlcEventsServer) Send(m *HtlcEvent) error {
return x.ServerStream.SendMsg(m)
}
func _Router_SendPayment_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(SendPaymentRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(RouterServer).SendPayment(m, &routerSendPaymentServer{stream})
}
type Router_SendPaymentServer interface {
Send(*PaymentStatus) error
grpc.ServerStream
}
type routerSendPaymentServer struct {
grpc.ServerStream
}
func (x *routerSendPaymentServer) Send(m *PaymentStatus) error {
return x.ServerStream.SendMsg(m)
}
func _Router_TrackPayment_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(TrackPaymentRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(RouterServer).TrackPayment(m, &routerTrackPaymentServer{stream})
}
type Router_TrackPaymentServer interface {
Send(*PaymentStatus) error
grpc.ServerStream
}
type routerTrackPaymentServer struct {
grpc.ServerStream
}
func (x *routerTrackPaymentServer) Send(m *PaymentStatus) error {
return x.ServerStream.SendMsg(m)
}
func _Router_HtlcInterceptor_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(RouterServer).HtlcInterceptor(&routerHtlcInterceptorServer{stream})
}
type Router_HtlcInterceptorServer interface {
Send(*ForwardHtlcInterceptRequest) error
Recv() (*ForwardHtlcInterceptResponse, error)
grpc.ServerStream
}
type routerHtlcInterceptorServer struct {
grpc.ServerStream
}
func (x *routerHtlcInterceptorServer) Send(m *ForwardHtlcInterceptRequest) error {
return x.ServerStream.SendMsg(m)
}
func (x *routerHtlcInterceptorServer) Recv() (*ForwardHtlcInterceptResponse, error) {
m := new(ForwardHtlcInterceptResponse)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func _Router_UpdateChanStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateChanStatusRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RouterServer).UpdateChanStatus(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/routerrpc.Router/UpdateChanStatus",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RouterServer).UpdateChanStatus(ctx, req.(*UpdateChanStatusRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Router_XAddLocalChanAliases_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AddAliasesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RouterServer).XAddLocalChanAliases(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/routerrpc.Router/XAddLocalChanAliases",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RouterServer).XAddLocalChanAliases(ctx, req.(*AddAliasesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Router_XDeleteLocalChanAliases_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeleteAliasesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RouterServer).XDeleteLocalChanAliases(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/routerrpc.Router/XDeleteLocalChanAliases",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RouterServer).XDeleteLocalChanAliases(ctx, req.(*DeleteAliasesRequest))
}
return interceptor(ctx, in, info, handler)
}
// Router_ServiceDesc is the grpc.ServiceDesc for Router service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Router_ServiceDesc = grpc.ServiceDesc{
ServiceName: "routerrpc.Router",
HandlerType: (*RouterServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "EstimateRouteFee",
Handler: _Router_EstimateRouteFee_Handler,
},
{
MethodName: "SendToRoute",
Handler: _Router_SendToRoute_Handler,
},
{
MethodName: "SendToRouteV2",
Handler: _Router_SendToRouteV2_Handler,
},
{
MethodName: "ResetMissionControl",
Handler: _Router_ResetMissionControl_Handler,
},
{
MethodName: "QueryMissionControl",
Handler: _Router_QueryMissionControl_Handler,
},
{
MethodName: "XImportMissionControl",
Handler: _Router_XImportMissionControl_Handler,
},
{
MethodName: "GetMissionControlConfig",
Handler: _Router_GetMissionControlConfig_Handler,
},
{
MethodName: "SetMissionControlConfig",
Handler: _Router_SetMissionControlConfig_Handler,
},
{
MethodName: "QueryProbability",
Handler: _Router_QueryProbability_Handler,
},
{
MethodName: "BuildRoute",
Handler: _Router_BuildRoute_Handler,
},
{
MethodName: "UpdateChanStatus",
Handler: _Router_UpdateChanStatus_Handler,
},
{
MethodName: "XAddLocalChanAliases",
Handler: _Router_XAddLocalChanAliases_Handler,
},
{
MethodName: "XDeleteLocalChanAliases",
Handler: _Router_XDeleteLocalChanAliases_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "SendPaymentV2",
Handler: _Router_SendPaymentV2_Handler,
ServerStreams: true,
},
{
StreamName: "TrackPaymentV2",
Handler: _Router_TrackPaymentV2_Handler,
ServerStreams: true,
},
{
StreamName: "TrackPayments",
Handler: _Router_TrackPayments_Handler,
ServerStreams: true,
},
{
StreamName: "SubscribeHtlcEvents",
Handler: _Router_SubscribeHtlcEvents_Handler,
ServerStreams: true,
},
{
StreamName: "SendPayment",
Handler: _Router_SendPayment_Handler,
ServerStreams: true,
},
{
StreamName: "TrackPayment",
Handler: _Router_TrackPayment_Handler,
ServerStreams: true,
},
{
StreamName: "HtlcInterceptor",
Handler: _Router_HtlcInterceptor_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "routerrpc/router.proto",
}
package routerrpc
import (
"bytes"
"context"
crand "crypto/rand"
"errors"
"fmt"
"os"
"path/filepath"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/lightningnetwork/lnd/aliasmgr"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/zpay32"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"gopkg.in/macaroon-bakery.v2/bakery"
)
const (
// subServerName is the name of the sub rpc server. We'll use this name
// to register ourselves, and we also require that the main
// SubServerConfigDispatcher instance recognize as the name of our
subServerName = "RouterRPC"
// routeFeeLimitSat is the maximum routing fee that we allow to occur
// when estimating a routing fee.
routeFeeLimitSat = 100_000_000
// DefaultPaymentTimeout is the default value of time we should spend
// when attempting to fulfill the payment.
DefaultPaymentTimeout int32 = 60
)
var (
errServerShuttingDown = errors.New("routerrpc server shutting down")
// ErrInterceptorAlreadyExists is an error returned when a new stream is
// opened and there is already one active interceptor. The user must
// disconnect prior to open another stream.
ErrInterceptorAlreadyExists = errors.New("interceptor already exists")
errMissingPaymentAttempt = errors.New("missing payment attempt")
errMissingRoute = errors.New("missing route")
errUnexpectedFailureSource = errors.New("unexpected failure source")
// ErrAliasAlreadyExists is returned if a new SCID alias is attempted
// to be added that already exists.
ErrAliasAlreadyExists = errors.New("alias already exists")
// ErrNoValidAlias is returned if an alias is not in the valid range for
// allowed SCID aliases.
ErrNoValidAlias = errors.New("not a valid alias")
// macaroonOps are the set of capabilities that our minted macaroon (if
// it doesn't already exist) will have.
macaroonOps = []bakery.Op{
{
Entity: "offchain",
Action: "read",
},
{
Entity: "offchain",
Action: "write",
},
}
// macPermissions maps RPC calls to the permissions they require.
macPermissions = map[string][]bakery.Op{
"/routerrpc.Router/SendPaymentV2": {{
Entity: "offchain",
Action: "write",
}},
"/routerrpc.Router/SendToRouteV2": {{
Entity: "offchain",
Action: "write",
}},
"/routerrpc.Router/SendToRoute": {{
Entity: "offchain",
Action: "write",
}},
"/routerrpc.Router/TrackPaymentV2": {{
Entity: "offchain",
Action: "read",
}},
"/routerrpc.Router/TrackPayments": {{
Entity: "offchain",
Action: "read",
}},
"/routerrpc.Router/EstimateRouteFee": {{
Entity: "offchain",
Action: "read",
}},
"/routerrpc.Router/QueryMissionControl": {{
Entity: "offchain",
Action: "read",
}},
"/routerrpc.Router/XImportMissionControl": {{
Entity: "offchain",
Action: "write",
}},
"/routerrpc.Router/GetMissionControlConfig": {{
Entity: "offchain",
Action: "read",
}},
"/routerrpc.Router/SetMissionControlConfig": {{
Entity: "offchain",
Action: "write",
}},
"/routerrpc.Router/QueryProbability": {{
Entity: "offchain",
Action: "read",
}},
"/routerrpc.Router/ResetMissionControl": {{
Entity: "offchain",
Action: "write",
}},
"/routerrpc.Router/BuildRoute": {{
Entity: "offchain",
Action: "read",
}},
"/routerrpc.Router/SubscribeHtlcEvents": {{
Entity: "offchain",
Action: "read",
}},
"/routerrpc.Router/SendPayment": {{
Entity: "offchain",
Action: "write",
}},
"/routerrpc.Router/TrackPayment": {{
Entity: "offchain",
Action: "read",
}},
"/routerrpc.Router/HtlcInterceptor": {{
Entity: "offchain",
Action: "write",
}},
"/routerrpc.Router/UpdateChanStatus": {{
Entity: "offchain",
Action: "write",
}},
"/routerrpc.Router/XAddLocalChanAliases": {{
Entity: "offchain",
Action: "write",
}},
"/routerrpc.Router/XDeleteLocalChanAliases": {{
Entity: "offchain",
Action: "write",
}},
}
// DefaultRouterMacFilename is the default name of the router macaroon
// that we expect to find via a file handle within the main
// configuration file in this package.
DefaultRouterMacFilename = "router.macaroon"
)
// FetchChannelEndpoints returns the pubkeys of both endpoints of the
// given channel id if it exists in the graph.
type FetchChannelEndpoints func(chanID uint64) (route.Vertex, route.Vertex,
error)
// ServerShell is a shell struct holding a reference to the actual sub-server.
// It is used to register the gRPC sub-server with the root server before we
// have the necessary dependencies to populate the actual sub-server.
type ServerShell struct {
RouterServer
}
// Server is a stand-alone sub RPC server which exposes functionality that
// allows clients to route arbitrary payment through the Lightning Network.
type Server struct {
started int32 // To be used atomically.
shutdown int32 // To be used atomically.
forwardInterceptorActive int32 // To be used atomically.
// Required by the grpc-gateway/v2 library for forward compatibility.
// Must be after the atomically used variables to not break struct
// alignment.
UnimplementedRouterServer
cfg *Config
quit chan struct{}
}
// A compile time check to ensure that Server fully implements the RouterServer
// gRPC service.
var _ RouterServer = (*Server)(nil)
// New creates a new instance of the RouterServer given a configuration struct
// that contains all external dependencies. If the target macaroon exists, and
// we're unable to create it, then an error will be returned. We also return
// the set of permissions that we require as a server. At the time of writing
// of this documentation, this is the same macaroon as the admin macaroon.
func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) {
// If the path of the router macaroon wasn't generated, then we'll
// assume that it's found at the default network directory.
if cfg.RouterMacPath == "" {
cfg.RouterMacPath = filepath.Join(
cfg.NetworkDir, DefaultRouterMacFilename,
)
}
// Now that we know the full path of the router macaroon, we can check
// to see if we need to create it or not. If stateless_init is set
// then we don't write the macaroons.
macFilePath := cfg.RouterMacPath
if cfg.MacService != nil && !cfg.MacService.StatelessInit &&
!lnrpc.FileExists(macFilePath) {
log.Infof("Making macaroons for Router RPC Server at: %v",
macFilePath)
// At this point, we know that the router macaroon doesn't yet,
// exist, so we need to create it with the help of the main
// macaroon service.
routerMac, err := cfg.MacService.NewMacaroon(
context.Background(), macaroons.DefaultRootKeyID,
macaroonOps...,
)
if err != nil {
return nil, nil, err
}
routerMacBytes, err := routerMac.M().MarshalBinary()
if err != nil {
return nil, nil, err
}
err = os.WriteFile(macFilePath, routerMacBytes, 0644)
if err != nil {
_ = os.Remove(macFilePath)
return nil, nil, err
}
}
routerServer := &Server{
cfg: cfg,
quit: make(chan struct{}),
}
return routerServer, macPermissions, nil
}
// Start launches any helper goroutines required for the rpcServer to function.
//
// NOTE: This is part of the lnrpc.SubServer interface.
func (s *Server) Start() error {
if atomic.AddInt32(&s.started, 1) != 1 {
return nil
}
return nil
}
// Stop signals any active goroutines for a graceful closure.
//
// NOTE: This is part of the lnrpc.SubServer interface.
func (s *Server) Stop() error {
if atomic.AddInt32(&s.shutdown, 1) != 1 {
return nil
}
close(s.quit)
return nil
}
// Name returns a unique string representation of the sub-server. This can be
// used to identify the sub-server and also de-duplicate them.
//
// NOTE: This is part of the lnrpc.SubServer interface.
func (s *Server) Name() string {
return subServerName
}
// RegisterWithRootServer will be called by the root gRPC server to direct a
// sub RPC server to register itself with the main gRPC root server. Until this
// is called, each sub-server won't be able to have requests routed towards it.
//
// NOTE: This is part of the lnrpc.GrpcHandler interface.
func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error {
// We make sure that we register it with the main gRPC server to ensure
// all our methods are routed properly.
RegisterRouterServer(grpcServer, r)
log.Debugf("Router RPC server successfully registered with root gRPC " +
"server")
return nil
}
// RegisterWithRestServer will be called by the root REST mux to direct a sub
// RPC server to register itself with the main REST mux server. Until this is
// called, each sub-server won't be able to have requests routed towards it.
//
// NOTE: This is part of the lnrpc.GrpcHandler interface.
func (r *ServerShell) RegisterWithRestServer(ctx context.Context,
mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error {
// We make sure that we register it with the main REST server to ensure
// all our methods are routed properly.
err := RegisterRouterHandlerFromEndpoint(ctx, mux, dest, opts)
if err != nil {
log.Errorf("Could not register Router REST server "+
"with root REST server: %v", err)
return err
}
log.Debugf("Router REST server successfully registered with " +
"root REST server")
return nil
}
// CreateSubServer populates the subserver's dependencies using the passed
// SubServerConfigDispatcher. This method should fully initialize the
// sub-server instance, making it ready for action. It returns the macaroon
// permissions that the sub-server wishes to pass on to the root server for all
// methods routed towards it.
//
// NOTE: This is part of the lnrpc.GrpcHandler interface.
func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) (
lnrpc.SubServer, lnrpc.MacaroonPerms, error) {
subServer, macPermissions, err := createNewSubServer(configRegistry)
if err != nil {
return nil, nil, err
}
r.RouterServer = subServer
return subServer, macPermissions, nil
}
// SendPaymentV2 attempts to route a payment described by the passed
// PaymentRequest to the final destination. If we are unable to route the
// payment, or cannot find a route that satisfies the constraints in the
// PaymentRequest, then an error will be returned. Otherwise, the payment
// pre-image, along with the final route will be returned.
func (s *Server) SendPaymentV2(req *SendPaymentRequest,
stream Router_SendPaymentV2Server) error {
// Set payment request attempt timeout.
if req.TimeoutSeconds == 0 {
req.TimeoutSeconds = DefaultPaymentTimeout
}
payment, err := s.cfg.RouterBackend.extractIntentFromSendRequest(req)
if err != nil {
return err
}
// Get the payment hash.
payHash := payment.Identifier()
// Init the payment in db.
paySession, shardTracker, err := s.cfg.Router.PreparePayment(payment)
if err != nil {
log.Errorf("SendPayment async error for payment %x: %v",
payment.Identifier(), err)
// Transform user errors to grpc code.
if errors.Is(err, channeldb.ErrPaymentExists) ||
errors.Is(err, channeldb.ErrPaymentInFlight) ||
errors.Is(err, channeldb.ErrAlreadyPaid) {
return status.Error(
codes.AlreadyExists, err.Error(),
)
}
return err
}
// Subscribe to the payment before sending it to make sure we won't
// miss events.
sub, err := s.subscribePayment(payHash)
if err != nil {
return err
}
// The payment context is influenced by two user-provided parameters,
// the cancelable flag and the payment attempt timeout.
// If the payment is cancelable, we will use the stream context as the
// payment context. That way, if the user ends the stream, the payment
// loop will be canceled.
// The second context parameter is the timeout. If the user provides a
// timeout, we will additionally wrap the context in a deadline. If the
// user provided 'cancelable' and ends the stream before the timeout is
// reached the payment will be canceled.
ctx := context.Background()
if req.Cancelable {
ctx = stream.Context()
}
// Send the payment asynchronously.
s.cfg.Router.SendPaymentAsync(ctx, payment, paySession, shardTracker)
// Track the payment and return.
return s.trackPayment(sub, payHash, stream, req.NoInflightUpdates)
}
// EstimateRouteFee allows callers to obtain an expected value w.r.t how much it
// may cost to send an HTLC to the target end destination. This method sends
// probe payments to the target node, based on target invoice parameters and a
// random payment hash that makes it impossible for the target to settle the
// htlc. The probing stops if a user-provided timeout is reached. If provided
// with a destination key and amount, this method will perform a local graph
// based fee estimation.
func (s *Server) EstimateRouteFee(ctx context.Context,
req *RouteFeeRequest) (*RouteFeeResponse, error) {
isProbeDestination := len(req.Dest) > 0
isProbeInvoice := len(req.PaymentRequest) > 0
switch {
case isProbeDestination == isProbeInvoice:
return nil, errors.New("specify either a destination or an " +
"invoice")
case isProbeDestination:
switch {
case len(req.Dest) != 33:
return nil, errors.New("invalid length destination key")
case req.AmtSat <= 0:
return nil, errors.New("amount must be greater than 0")
default:
return s.probeDestination(req.Dest, req.AmtSat)
}
case isProbeInvoice:
return s.probePaymentRequest(
ctx, req.PaymentRequest, req.Timeout,
)
}
return &RouteFeeResponse{}, nil
}
// probeDestination estimates fees along a route to a destination based on the
// contents of the local graph.
func (s *Server) probeDestination(dest []byte, amtSat int64) (*RouteFeeResponse,
error) {
destNode, err := route.NewVertexFromBytes(dest)
if err != nil {
return nil, err
}
// Next, we'll convert the amount in satoshis to mSAT, which are the
// native unit of LN.
amtMsat := lnwire.NewMSatFromSatoshis(btcutil.Amount(amtSat))
// Finally, we'll query for a route to the destination that can carry
// that target amount, we'll only request a single route. Set a
// restriction for the default CLTV limit, otherwise we can find a route
// that exceeds it and is useless to us.
mc := s.cfg.RouterBackend.MissionControl
routeReq, err := routing.NewRouteRequest(
s.cfg.RouterBackend.SelfNode, &destNode, amtMsat, 0,
&routing.RestrictParams{
FeeLimit: routeFeeLimitSat,
CltvLimit: s.cfg.RouterBackend.MaxTotalTimelock,
ProbabilitySource: mc.GetProbability,
}, nil, nil, nil, s.cfg.RouterBackend.DefaultFinalCltvDelta,
)
if err != nil {
return nil, err
}
route, _, err := s.cfg.Router.FindRoute(routeReq)
if err != nil {
return nil, err
}
// We are adding a block padding to the total time lock to account for
// the safety buffer that the payment session will add to the last hop's
// cltv delta. This is to prevent the htlc from failing if blocks are
// mined while it is in flight.
timeLockDelay := route.TotalTimeLock + uint32(routing.BlockPadding)
return &RouteFeeResponse{
RoutingFeeMsat: int64(route.TotalFees()),
TimeLockDelay: int64(timeLockDelay),
FailureReason: lnrpc.PaymentFailureReason_FAILURE_REASON_NONE,
}, nil
}
// probePaymentRequest estimates fees along a route to a destination that is
// specified in an invoice. The estimation duration is limited by a timeout. In
// case that route hints are provided, this method applies a heuristic to
// identify LSPs which might block probe payments. In that case, fees are
// manually calculated and added to the probed fee estimation up until the LSP
// node. If the route hints don't indicate an LSP, they are passed as arguments
// to the SendPayment_V2 method, which enable it to send probe payments to the
// payment request destination.
func (s *Server) probePaymentRequest(ctx context.Context, paymentRequest string,
timeout uint32) (*RouteFeeResponse, error) {
payReq, err := zpay32.Decode(
paymentRequest, s.cfg.RouterBackend.ActiveNetParams,
)
if err != nil {
return nil, err
}
if payReq.MilliSat == nil || *payReq.MilliSat <= 0 {
return nil, errors.New("payment request amount must be " +
"greater than 0")
}
// Generate random payment hash, so we can be sure that the target of
// the probe payment doesn't have the preimage to settle the htlc.
var paymentHash lntypes.Hash
_, err = crand.Read(paymentHash[:])
if err != nil {
return nil, fmt.Errorf("cannot generate random probe "+
"preimage: %w", err)
}
amtMsat := int64(*payReq.MilliSat)
probeRequest := &SendPaymentRequest{
TimeoutSeconds: int32(timeout),
Dest: payReq.Destination.SerializeCompressed(),
MaxParts: 1,
AllowSelfPayment: false,
AmtMsat: amtMsat,
PaymentHash: paymentHash[:],
FeeLimitSat: routeFeeLimitSat,
FinalCltvDelta: int32(payReq.MinFinalCLTVExpiry()),
DestFeatures: MarshalFeatures(payReq.Features),
}
// If the payment addresses is specified, then we'll also populate that
// now as well.
payReq.PaymentAddr.WhenSome(func(addr [32]byte) {
copy(probeRequest.PaymentAddr, addr[:])
})
hints := payReq.RouteHints
// If the hints don't indicate an LSP then chances are that our probe
// payment won't be blocked along the route to the destination. We send
// a probe payment with unmodified route hints.
if !isLSP(hints, s.cfg.RouterBackend.FetchChannelEndpoints) {
probeRequest.RouteHints = invoicesrpc.CreateRPCRouteHints(hints)
return s.sendProbePayment(ctx, probeRequest)
}
// If the heuristic indicates an LSP we modify the route hints to allow
// probing the LSP.
lspAdjustedRouteHints, lspHint, err := prepareLspRouteHints(
hints, *payReq.MilliSat,
)
if err != nil {
return nil, err
}
// The adjusted route hints serve the payment probe to find the last
// public hop to the LSP on the route.
probeRequest.Dest = lspHint.NodeID.SerializeCompressed()
if len(lspAdjustedRouteHints) > 0 {
probeRequest.RouteHints = invoicesrpc.CreateRPCRouteHints(
lspAdjustedRouteHints,
)
}
// The payment probe will be able to calculate the fee up until the LSP
// node. The fee of the last hop has to be calculated manually. Since
// the last hop's fee amount has to be sent across the payment path we
// have to add it to the original payment amount. Only then will the
// payment probe be able to determine the correct fee to the last hop
// prior to the private destination. For example, if the user wants to
// send 1000 sats to a private destination and the last hop's fee is 10
// sats, then 1010 sats will have to arrive at the last hop. This means
// that the probe has to be dispatched with 1010 sats to correctly
// calculate the routing fee.
//
// Calculate the hop fee for the last hop manually.
hopFee := lspHint.HopFee(*payReq.MilliSat)
if err != nil {
return nil, err
}
// Add the last hop's fee to the requested payment amount that we want
// to get an estimate for.
probeRequest.AmtMsat += int64(hopFee)
// Use the hop hint's cltv delta as the payment request's final cltv
// delta. The actual final cltv delta of the invoice will be added to
// the payment probe's cltv delta.
probeRequest.FinalCltvDelta = int32(lspHint.CLTVExpiryDelta)
// Dispatch the payment probe with adjusted fee amount.
resp, err := s.sendProbePayment(ctx, probeRequest)
if err != nil {
return nil, err
}
// If the payment probe failed we only return the failure reason and
// leave the probe result params unaltered.
if resp.FailureReason != lnrpc.PaymentFailureReason_FAILURE_REASON_NONE { //nolint:ll
return resp, nil
}
// The probe succeeded, so we can add the last hop's fee to fee the
// payment probe returned.
resp.RoutingFeeMsat += int64(hopFee)
// Add the final cltv delta of the invoice to the payment probe's total
// cltv delta. This is the cltv delta for the hop behind the LSP.
resp.TimeLockDelay += int64(payReq.MinFinalCLTVExpiry())
return resp, nil
}
// isLSP checks if the route hints indicate an LSP. An LSP is indicated with
// true if the destination hop hint in each route hint has the same node id,
// false otherwise. If the destination hop hint of any route hint contains a
// public channel, the function returns false because we can directly send a
// probe to the final destination.
func isLSP(routeHints [][]zpay32.HopHint,
fetchChannelEndpoints FetchChannelEndpoints) bool {
if len(routeHints) == 0 || len(routeHints[0]) == 0 {
return false
}
destHopHint := routeHints[0][len(routeHints[0])-1]
// If the destination hop hint of the first route hint contains a public
// channel we can send a probe to it directly, hence we don't signal an
// LSP.
_, _, err := fetchChannelEndpoints(destHopHint.ChannelID)
if err == nil {
return false
}
for i := 1; i < len(routeHints); i++ {
// Skip empty route hints.
if len(routeHints[i]) == 0 {
continue
}
lastHop := routeHints[i][len(routeHints[i])-1]
// If the last hop hint of any route hint contains a public
// channel we can send a probe to it directly, hence we don't
// signal an LSP.
_, _, err = fetchChannelEndpoints(lastHop.ChannelID)
if err == nil {
return false
}
idMatchesRefNode := bytes.Equal(
lastHop.NodeID.SerializeCompressed(),
destHopHint.NodeID.SerializeCompressed(),
)
if !idMatchesRefNode {
return false
}
}
// We ensured that the destination hop hint doesn't contain a public
// channel, and that all destination hop hints of all route hints match,
// so we signal an LSP.
return true
}
// prepareLspRouteHints assumes that the isLsp heuristic returned true for the
// route hints passed in here. It constructs a modified list of route hints that
// allows the caller to probe the LSP, which itself is returned as a separate
// hop hint.
func prepareLspRouteHints(routeHints [][]zpay32.HopHint,
amt lnwire.MilliSatoshi) ([][]zpay32.HopHint, *zpay32.HopHint, error) {
if len(routeHints) == 0 {
return nil, nil, fmt.Errorf("no route hints provided")
}
// Create the LSP hop hint. We are probing for the worst case fee and
// cltv delta. So we look for the max values amongst all LSP hop hints.
refHint := routeHints[0][len(routeHints[0])-1]
refHint.CLTVExpiryDelta = maxLspCltvDelta(routeHints)
refHint.FeeBaseMSat, refHint.FeeProportionalMillionths = maxLspFee(
routeHints, amt,
)
// We construct a modified list of route hints that allows the caller to
// probe the LSP.
adjustedHints := make([][]zpay32.HopHint, 0, len(routeHints))
// Strip off the LSP hop hint from all route hints.
for i := 0; i < len(routeHints); i++ {
hint := routeHints[i]
if len(hint) > 1 {
adjustedHints = append(
adjustedHints, hint[:len(hint)-1],
)
}
}
return adjustedHints, &refHint, nil
}
// maxLspFee returns base fee and fee rate amongst all LSP route hints that
// results in the overall highest fee for the given amount.
func maxLspFee(routeHints [][]zpay32.HopHint, amt lnwire.MilliSatoshi) (uint32,
uint32) {
var maxFeePpm uint32
var maxBaseFee uint32
var maxTotalFee lnwire.MilliSatoshi
for _, rh := range routeHints {
lastHop := rh[len(rh)-1]
lastHopFee := lastHop.HopFee(amt)
if lastHopFee > maxTotalFee {
maxTotalFee = lastHopFee
maxBaseFee = lastHop.FeeBaseMSat
maxFeePpm = lastHop.FeeProportionalMillionths
}
}
return maxBaseFee, maxFeePpm
}
// maxLspCltvDelta returns the maximum cltv delta amongst all LSP route hints.
func maxLspCltvDelta(routeHints [][]zpay32.HopHint) uint16 {
var maxCltvDelta uint16
for _, rh := range routeHints {
rhLastHop := rh[len(rh)-1]
if rhLastHop.CLTVExpiryDelta > maxCltvDelta {
maxCltvDelta = rhLastHop.CLTVExpiryDelta
}
}
return maxCltvDelta
}
// probePaymentStream is a custom implementation of the grpc.ServerStream
// interface. It is used to send payment status updates to the caller on the
// stream channel.
type probePaymentStream struct {
Router_SendPaymentV2Server
stream chan *lnrpc.Payment
ctx context.Context //nolint:containedctx
}
// Send sends a payment status update to a payment stream that the caller can
// evaluate.
func (p *probePaymentStream) Send(response *lnrpc.Payment) error {
select {
case p.stream <- response:
case <-p.ctx.Done():
return p.ctx.Err()
}
return nil
}
// Context returns the context of the stream.
func (p *probePaymentStream) Context() context.Context {
return p.ctx
}
// sendProbePayment sends a payment to a target node in order to obtain
// potential routing fees for it. The payment request has to contain a payment
// hash that is guaranteed to be unknown to the target node, so it cannot settle
// the payment. This method invokes a payment request loop in a goroutine and
// awaits payment status updates.
func (s *Server) sendProbePayment(ctx context.Context,
req *SendPaymentRequest) (*RouteFeeResponse, error) {
// We'll launch a goroutine to send the payment probes.
errChan := make(chan error, 1)
defer close(errChan)
paymentStream := &probePaymentStream{
stream: make(chan *lnrpc.Payment),
ctx: ctx,
}
go func() {
err := s.SendPaymentV2(req, paymentStream)
if err != nil {
select {
case errChan <- err:
case <-paymentStream.ctx.Done():
return
}
}
}()
for {
select {
case payment := <-paymentStream.stream:
switch payment.Status {
case lnrpc.Payment_INITIATED:
case lnrpc.Payment_IN_FLIGHT:
case lnrpc.Payment_SUCCEEDED:
return nil, errors.New("warning, the fee " +
"estimation payment probe " +
"unexpectedly succeeded. Please reach" +
"out to the probe destination to " +
"negotiate a refund. Otherwise the " +
"payment probe amount is lost forever")
case lnrpc.Payment_FAILED:
// Incorrect payment details point to a
// successful probe.
//nolint:ll
if payment.FailureReason == lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS {
return paymentDetails(payment)
}
return &RouteFeeResponse{
RoutingFeeMsat: 0,
TimeLockDelay: 0,
FailureReason: payment.FailureReason,
}, nil
default:
return nil, errors.New("unexpected payment " +
"status")
}
case err := <-errChan:
return nil, err
case <-s.quit:
return nil, errServerShuttingDown
}
}
}
func paymentDetails(payment *lnrpc.Payment) (*RouteFeeResponse, error) {
fee, timeLock, err := timelockAndFee(payment)
if errors.Is(err, errUnexpectedFailureSource) {
return nil, err
}
return &RouteFeeResponse{
RoutingFeeMsat: fee,
TimeLockDelay: timeLock,
FailureReason: lnrpc.PaymentFailureReason_FAILURE_REASON_NONE,
}, nil
}
// timelockAndFee returns the fee and total time lock of the last payment
// attempt.
func timelockAndFee(p *lnrpc.Payment) (int64, int64, error) {
if len(p.Htlcs) == 0 {
return 0, 0, nil
}
lastAttempt := p.Htlcs[len(p.Htlcs)-1]
if lastAttempt == nil {
return 0, 0, errMissingPaymentAttempt
}
lastRoute := lastAttempt.Route
if lastRoute == nil {
return 0, 0, errMissingRoute
}
hopFailureIndex := lastAttempt.Failure.FailureSourceIndex
finalHopIndex := uint32(len(lastRoute.Hops))
if hopFailureIndex != finalHopIndex {
return 0, 0, errUnexpectedFailureSource
}
return lastRoute.TotalFeesMsat, int64(lastRoute.TotalTimeLock), nil
}
// SendToRouteV2 sends a payment through a predefined route. The response of
// this call contains structured error information.
func (s *Server) SendToRouteV2(ctx context.Context,
req *SendToRouteRequest) (*lnrpc.HTLCAttempt, error) {
if req.Route == nil {
return nil, fmt.Errorf("unable to send, no routes provided")
}
route, err := s.cfg.RouterBackend.UnmarshallRoute(req.Route)
if err != nil {
return nil, err
}
hash, err := lntypes.MakeHash(req.PaymentHash)
if err != nil {
return nil, err
}
firstHopRecords := lnwire.CustomRecords(req.FirstHopCustomRecords)
if err := firstHopRecords.Validate(); err != nil {
return nil, err
}
var attempt *channeldb.HTLCAttempt
// Pass route to the router. This call returns the full htlc attempt
// information as it is stored in the database. It is possible that both
// the attempt return value and err are non-nil. This can happen when
// the attempt was already initiated before the error happened. In that
// case, we give precedence to the attempt information as stored in the
// db.
if req.SkipTempErr {
attempt, err = s.cfg.Router.SendToRouteSkipTempErr(
hash, route, firstHopRecords,
)
} else {
attempt, err = s.cfg.Router.SendToRoute(
hash, route, firstHopRecords,
)
}
if attempt != nil {
rpcAttempt, err := s.cfg.RouterBackend.MarshalHTLCAttempt(
*attempt,
)
if err != nil {
return nil, err
}
return rpcAttempt, nil
}
// Transform user errors to grpc code.
switch {
case errors.Is(err, channeldb.ErrPaymentExists):
fallthrough
case errors.Is(err, channeldb.ErrPaymentInFlight):
fallthrough
case errors.Is(err, channeldb.ErrAlreadyPaid):
return nil, status.Error(
codes.AlreadyExists, err.Error(),
)
}
return nil, err
}
// ResetMissionControl clears all mission control state and starts with a clean
// slate.
func (s *Server) ResetMissionControl(ctx context.Context,
req *ResetMissionControlRequest) (*ResetMissionControlResponse, error) {
err := s.cfg.RouterBackend.MissionControl.ResetHistory()
if err != nil {
return nil, err
}
return &ResetMissionControlResponse{}, nil
}
// GetMissionControlConfig returns our current mission control config.
func (s *Server) GetMissionControlConfig(ctx context.Context,
req *GetMissionControlConfigRequest) (*GetMissionControlConfigResponse,
error) {
// Query the current mission control config.
cfg := s.cfg.RouterBackend.MissionControl.GetConfig()
resp := &GetMissionControlConfigResponse{
Config: &MissionControlConfig{
MaximumPaymentResults: uint32(cfg.MaxMcHistory),
MinimumFailureRelaxInterval: uint64(
cfg.MinFailureRelaxInterval.Seconds(),
),
},
}
// We only populate fields based on the current estimator.
switch v := cfg.Estimator.Config().(type) {
case routing.AprioriConfig:
resp.Config.Model = MissionControlConfig_APRIORI
aCfg := AprioriParameters{
HalfLifeSeconds: uint64(v.PenaltyHalfLife.Seconds()),
HopProbability: v.AprioriHopProbability,
Weight: v.AprioriWeight,
CapacityFraction: v.CapacityFraction,
}
// Populate deprecated fields.
resp.Config.HalfLifeSeconds = uint64(
v.PenaltyHalfLife.Seconds(),
)
resp.Config.HopProbability = float32(v.AprioriHopProbability)
resp.Config.Weight = float32(v.AprioriWeight)
resp.Config.EstimatorConfig = &MissionControlConfig_Apriori{
Apriori: &aCfg,
}
case routing.BimodalConfig:
resp.Config.Model = MissionControlConfig_BIMODAL
bCfg := BimodalParameters{
NodeWeight: v.BimodalNodeWeight,
ScaleMsat: uint64(v.BimodalScaleMsat),
DecayTime: uint64(v.BimodalDecayTime.Seconds()),
}
resp.Config.EstimatorConfig = &MissionControlConfig_Bimodal{
Bimodal: &bCfg,
}
default:
return nil, fmt.Errorf("unknown estimator config type %T", v)
}
return resp, nil
}
// SetMissionControlConfig sets parameters in the mission control config.
func (s *Server) SetMissionControlConfig(ctx context.Context,
req *SetMissionControlConfigRequest) (*SetMissionControlConfigResponse,
error) {
mcCfg := &routing.MissionControlConfig{
MaxMcHistory: int(req.Config.MaximumPaymentResults),
MinFailureRelaxInterval: time.Duration(
req.Config.MinimumFailureRelaxInterval,
) * time.Second,
}
switch req.Config.Model {
case MissionControlConfig_APRIORI:
var aprioriConfig routing.AprioriConfig
// Determine the apriori config with backward compatibility
// should the api use deprecated fields.
switch v := req.Config.EstimatorConfig.(type) {
case *MissionControlConfig_Bimodal:
return nil, fmt.Errorf("bimodal config " +
"provided, but apriori model requested")
case *MissionControlConfig_Apriori:
aprioriConfig = routing.AprioriConfig{
PenaltyHalfLife: time.Duration(
v.Apriori.HalfLifeSeconds,
) * time.Second,
AprioriHopProbability: v.Apriori.HopProbability,
AprioriWeight: v.Apriori.Weight,
CapacityFraction: v.Apriori.
CapacityFraction,
}
default:
aprioriConfig = routing.AprioriConfig{
PenaltyHalfLife: time.Duration(
int64(req.Config.HalfLifeSeconds),
) * time.Second,
AprioriHopProbability: float64(
req.Config.HopProbability,
),
AprioriWeight: float64(req.Config.Weight),
CapacityFraction: routing.DefaultCapacityFraction, //nolint:ll
}
}
estimator, err := routing.NewAprioriEstimator(aprioriConfig)
if err != nil {
return nil, err
}
mcCfg.Estimator = estimator
case MissionControlConfig_BIMODAL:
cfg, ok := req.Config.
EstimatorConfig.(*MissionControlConfig_Bimodal)
if !ok {
return nil, fmt.Errorf("bimodal estimator requested " +
"but corresponding config not set")
}
bCfg := cfg.Bimodal
bimodalConfig := routing.BimodalConfig{
BimodalDecayTime: time.Duration(
bCfg.DecayTime,
) * time.Second,
BimodalScaleMsat: lnwire.MilliSatoshi(bCfg.ScaleMsat),
BimodalNodeWeight: bCfg.NodeWeight,
}
estimator, err := routing.NewBimodalEstimator(bimodalConfig)
if err != nil {
return nil, err
}
mcCfg.Estimator = estimator
default:
return nil, fmt.Errorf("unknown estimator type %v",
req.Config.Model)
}
return &SetMissionControlConfigResponse{},
s.cfg.RouterBackend.MissionControl.SetConfig(mcCfg)
}
// QueryMissionControl exposes the internal mission control state to callers. It
// is a development feature.
func (s *Server) QueryMissionControl(_ context.Context,
_ *QueryMissionControlRequest) (*QueryMissionControlResponse, error) {
snapshot := s.cfg.RouterBackend.MissionControl.GetHistorySnapshot()
rpcPairs := make([]*PairHistory, 0, len(snapshot.Pairs))
for _, p := range snapshot.Pairs {
// Prevent binding to loop variable.
pair := p
rpcPair := PairHistory{
NodeFrom: pair.Pair.From[:],
NodeTo: pair.Pair.To[:],
History: toRPCPairData(&pair.TimedPairResult),
}
rpcPairs = append(rpcPairs, &rpcPair)
}
response := QueryMissionControlResponse{
Pairs: rpcPairs,
}
return &response, nil
}
// toRPCPairData marshalls mission control pair data to the rpc struct.
func toRPCPairData(data *routing.TimedPairResult) *PairData {
rpcData := PairData{
FailAmtSat: int64(data.FailAmt.ToSatoshis()),
FailAmtMsat: int64(data.FailAmt),
SuccessAmtSat: int64(data.SuccessAmt.ToSatoshis()),
SuccessAmtMsat: int64(data.SuccessAmt),
}
if !data.FailTime.IsZero() {
rpcData.FailTime = data.FailTime.Unix()
}
if !data.SuccessTime.IsZero() {
rpcData.SuccessTime = data.SuccessTime.Unix()
}
return &rpcData
}
// XImportMissionControl imports the state provided to our internal mission
// control. Only entries that are fresher than our existing state will be used.
func (s *Server) XImportMissionControl(_ context.Context,
req *XImportMissionControlRequest) (*XImportMissionControlResponse,
error) {
if len(req.Pairs) == 0 {
return nil, errors.New("at least one pair required for import")
}
snapshot := &routing.MissionControlSnapshot{
Pairs: make(
[]routing.MissionControlPairSnapshot, len(req.Pairs),
),
}
for i, pairResult := range req.Pairs {
pairSnapshot, err := toPairSnapshot(pairResult)
if err != nil {
return nil, err
}
snapshot.Pairs[i] = *pairSnapshot
}
err := s.cfg.RouterBackend.MissionControl.ImportHistory(
snapshot, req.Force,
)
if err != nil {
return nil, err
}
return &XImportMissionControlResponse{}, nil
}
func toPairSnapshot(pairResult *PairHistory) (*routing.MissionControlPairSnapshot,
error) {
from, err := route.NewVertexFromBytes(pairResult.NodeFrom)
if err != nil {
return nil, err
}
to, err := route.NewVertexFromBytes(pairResult.NodeTo)
if err != nil {
return nil, err
}
pairPrefix := fmt.Sprintf("pair: %v -> %v:", from, to)
if from == to {
return nil, fmt.Errorf("%v source and destination node must "+
"differ", pairPrefix)
}
failAmt, failTime, err := getPair(
lnwire.MilliSatoshi(pairResult.History.FailAmtMsat),
btcutil.Amount(pairResult.History.FailAmtSat),
pairResult.History.FailTime,
true,
)
if err != nil {
return nil, fmt.Errorf("%v invalid failure: %w", pairPrefix,
err)
}
successAmt, successTime, err := getPair(
lnwire.MilliSatoshi(pairResult.History.SuccessAmtMsat),
btcutil.Amount(pairResult.History.SuccessAmtSat),
pairResult.History.SuccessTime,
false,
)
if err != nil {
return nil, fmt.Errorf("%v invalid success: %w", pairPrefix,
err)
}
if successAmt == 0 && failAmt == 0 {
return nil, fmt.Errorf("%v: either success or failure result "+
"required", pairPrefix)
}
pair := routing.NewDirectedNodePair(from, to)
result := &routing.TimedPairResult{
FailAmt: failAmt,
FailTime: failTime,
SuccessAmt: successAmt,
SuccessTime: successTime,
}
return &routing.MissionControlPairSnapshot{
Pair: pair,
TimedPairResult: *result,
}, nil
}
// getPair validates the values provided for a mission control result and
// returns the msat amount and timestamp for it. `isFailure` can be used to
// default values to 0 instead of returning an error.
func getPair(amtMsat lnwire.MilliSatoshi, amtSat btcutil.Amount,
timestamp int64, isFailure bool) (lnwire.MilliSatoshi, time.Time,
error) {
amt, err := getMsatPairValue(amtMsat, amtSat)
if err != nil {
return 0, time.Time{}, err
}
var (
timeSet = timestamp != 0
amountSet = amt != 0
)
switch {
// If a timestamp and amount if provided, return those values.
case timeSet && amountSet:
return amt, time.Unix(timestamp, 0), nil
// Return an error if it does have a timestamp without an amount, and
// it's not expected to be a failure.
case !isFailure && timeSet && !amountSet:
return 0, time.Time{}, errors.New("non-zero timestamp " +
"requires non-zero amount for success pairs")
// Return an error if it does have an amount without a timestamp, and
// it's not expected to be a failure.
case !isFailure && !timeSet && amountSet:
return 0, time.Time{}, errors.New("non-zero amount for " +
"success pairs requires non-zero timestamp")
default:
return 0, time.Time{}, nil
}
}
// getMsatPairValue checks the msat and sat values set for a pair and ensures
// that the values provided are either the same, or only a single value is set.
func getMsatPairValue(msatValue lnwire.MilliSatoshi,
satValue btcutil.Amount) (lnwire.MilliSatoshi, error) {
// If our msat value converted to sats equals our sat value, we just
// return the msat value, since the values are the same.
if msatValue.ToSatoshis() == satValue {
return msatValue, nil
}
// If we have no msatValue, we can just return our state value even if
// it is zero, because it's impossible that we have mismatched values.
if msatValue == 0 {
return lnwire.MilliSatoshi(satValue * 1000), nil
}
// Likewise, we can just use msat value if we have no sat value set.
if satValue == 0 {
return msatValue, nil
}
// If our values are non-zero but not equal, we have invalid amounts
// set, so we fail.
return 0, fmt.Errorf("msat: %v and sat: %v values not equal", msatValue,
satValue)
}
// TrackPaymentV2 returns a stream of payment state updates. The stream is
// closed when the payment completes.
func (s *Server) TrackPaymentV2(request *TrackPaymentRequest,
stream Router_TrackPaymentV2Server) error {
payHash, err := lntypes.MakeHash(request.PaymentHash)
if err != nil {
return err
}
log.Debugf("TrackPayment called for payment %v", payHash)
// Make the subscription.
sub, err := s.subscribePayment(payHash)
if err != nil {
return err
}
return s.trackPayment(sub, payHash, stream, request.NoInflightUpdates)
}
// subscribePayment subscribes to the payment updates for the given payment
// hash.
func (s *Server) subscribePayment(identifier lntypes.Hash) (
routing.ControlTowerSubscriber, error) {
// Make the subscription.
router := s.cfg.RouterBackend
sub, err := router.Tower.SubscribePayment(identifier)
switch {
case errors.Is(err, channeldb.ErrPaymentNotInitiated):
return nil, status.Error(codes.NotFound, err.Error())
case err != nil:
return nil, err
}
return sub, nil
}
// trackPayment writes payment status updates to the provided stream.
func (s *Server) trackPayment(subscription routing.ControlTowerSubscriber,
identifier lntypes.Hash, stream Router_TrackPaymentV2Server,
noInflightUpdates bool) error {
err := s.trackPaymentStream(
stream.Context(), subscription, noInflightUpdates, stream.Send,
)
switch {
case err == nil:
return nil
// If the context is canceled, we don't return an error.
case errors.Is(err, context.Canceled):
log.Infof("Payment stream %v canceled", identifier)
return nil
default:
}
// Otherwise, we will log and return the error as the stream has
// received an error from the payment lifecycle.
log.Errorf("TrackPayment got error for payment %v: %v", identifier, err)
return err
}
// TrackPayments returns a stream of payment state updates.
func (s *Server) TrackPayments(request *TrackPaymentsRequest,
stream Router_TrackPaymentsServer) error {
log.Debug("TrackPayments called")
router := s.cfg.RouterBackend
// Subscribe to payments.
subscription, err := router.Tower.SubscribeAllPayments()
if err != nil {
return err
}
// Stream updates to the client.
err = s.trackPaymentStream(
stream.Context(), subscription, request.NoInflightUpdates,
stream.Send,
)
if errors.Is(err, context.Canceled) {
log.Debugf("TrackPayments payment stream canceled.")
}
return err
}
// trackPaymentStream streams payment updates to the client.
func (s *Server) trackPaymentStream(context context.Context,
subscription routing.ControlTowerSubscriber, noInflightUpdates bool,
send func(*lnrpc.Payment) error) error {
defer subscription.Close()
// Stream updates back to the client.
for {
select {
case item, ok := <-subscription.Updates():
if !ok {
// No more payment updates.
return nil
}
result := item.(*channeldb.MPPayment)
log.Tracef("Payment %v updated to state %v",
result.Info.PaymentIdentifier, result.Status)
// Skip in-flight updates unless requested.
if noInflightUpdates {
if result.Status == channeldb.StatusInitiated {
continue
}
if result.Status == channeldb.StatusInFlight {
continue
}
}
rpcPayment, err := s.cfg.RouterBackend.MarshallPayment(
result,
)
if err != nil {
return err
}
// Send event to the client.
err = send(rpcPayment)
if err != nil {
return err
}
case <-s.quit:
return errServerShuttingDown
case <-context.Done():
return context.Err()
}
}
}
// BuildRoute builds a route from a list of hop addresses.
func (s *Server) BuildRoute(_ context.Context,
req *BuildRouteRequest) (*BuildRouteResponse, error) {
if len(req.HopPubkeys) == 0 {
return nil, errors.New("no hops specified")
}
// Unmarshall hop list.
hops := make([]route.Vertex, len(req.HopPubkeys))
for i, pubkeyBytes := range req.HopPubkeys {
pubkey, err := route.NewVertexFromBytes(pubkeyBytes)
if err != nil {
return nil, err
}
hops[i] = pubkey
}
// Prepare BuildRoute call parameters from rpc request.
var amt fn.Option[lnwire.MilliSatoshi]
if req.AmtMsat != 0 {
rpcAmt := lnwire.MilliSatoshi(req.AmtMsat)
amt = fn.Some(rpcAmt)
}
var outgoingChan *uint64
if req.OutgoingChanId != 0 {
outgoingChan = &req.OutgoingChanId
}
var payAddr fn.Option[[32]byte]
if len(req.PaymentAddr) != 0 {
var backingPayAddr [32]byte
copy(backingPayAddr[:], req.PaymentAddr)
payAddr = fn.Some(backingPayAddr)
}
if req.FinalCltvDelta == 0 {
req.FinalCltvDelta = int32(
s.cfg.RouterBackend.DefaultFinalCltvDelta,
)
}
var firstHopBlob fn.Option[[]byte]
if len(req.FirstHopCustomRecords) > 0 {
firstHopRecords := lnwire.CustomRecords(
req.FirstHopCustomRecords,
)
if err := firstHopRecords.Validate(); err != nil {
return nil, err
}
firstHopData, err := firstHopRecords.Serialize()
if err != nil {
return nil, err
}
firstHopBlob = fn.Some(firstHopData)
}
// Build the route and return it to the caller.
route, err := s.cfg.Router.BuildRoute(
amt, hops, outgoingChan, req.FinalCltvDelta, payAddr,
firstHopBlob,
)
if err != nil {
return nil, err
}
rpcRoute, err := s.cfg.RouterBackend.MarshallRoute(route)
if err != nil {
return nil, err
}
routeResp := &BuildRouteResponse{
Route: rpcRoute,
}
return routeResp, nil
}
// SubscribeHtlcEvents creates a uni-directional stream from the server to
// the client which delivers a stream of htlc events.
func (s *Server) SubscribeHtlcEvents(_ *SubscribeHtlcEventsRequest,
stream Router_SubscribeHtlcEventsServer) error {
htlcClient, err := s.cfg.RouterBackend.SubscribeHtlcEvents()
if err != nil {
return err
}
defer htlcClient.Cancel()
// Send out an initial subscribed event so that the caller knows the
// point from which new events will be transmitted.
if err := stream.Send(&HtlcEvent{
Event: &HtlcEvent_SubscribedEvent{
SubscribedEvent: &SubscribedEvent{},
},
}); err != nil {
return err
}
for {
select {
case event := <-htlcClient.Updates():
rpcEvent, err := rpcHtlcEvent(event)
if err != nil {
return err
}
if err := stream.Send(rpcEvent); err != nil {
return err
}
// If the stream's context is cancelled, return an error.
case <-stream.Context().Done():
log.Debugf("htlc event stream cancelled")
return stream.Context().Err()
// If the subscribe client terminates, exit with an error.
case <-htlcClient.Quit():
return errors.New("htlc event subscription terminated")
// If the server has been signalled to shut down, exit.
case <-s.quit:
return errServerShuttingDown
}
}
}
// HtlcInterceptor is a bidirectional stream for streaming interception
// requests to the caller.
// Upon connection, it does the following:
// 1. Check if there is already a live stream, if yes it rejects the request.
// 2. Registered a ForwardInterceptor
// 3. Delivers to the caller every √√ and detect his answer.
// It uses a local implementation of holdForwardsStore to keep all the hold
// forwards and find them when manual resolution is later needed.
func (s *Server) HtlcInterceptor(stream Router_HtlcInterceptorServer) error {
// We ensure there is only one interceptor at a time.
if !atomic.CompareAndSwapInt32(&s.forwardInterceptorActive, 0, 1) {
return ErrInterceptorAlreadyExists
}
defer atomic.CompareAndSwapInt32(&s.forwardInterceptorActive, 1, 0)
// Run the forward interceptor.
return newForwardInterceptor(
s.cfg.RouterBackend.InterceptableForwarder, stream,
).run()
}
// XAddLocalChanAliases is an experimental API that creates a set of new
// channel SCID alias mappings. The final total set of aliases in the manager
// after the add operation is returned. This is only a locally stored alias, and
// will not be communicated to the channel peer via any message. Therefore,
// routing over such an alias will only work if the peer also calls this same
// RPC on their end. If an alias already exists, an error is returned.
func (s *Server) XAddLocalChanAliases(_ context.Context,
in *AddAliasesRequest) (*AddAliasesResponse, error) {
existingAliases := s.cfg.AliasMgr.ListAliases()
// aliasExists checks if the new alias already exists in the alias map.
aliasExists := func(newAlias uint64,
baseScid lnwire.ShortChannelID) (bool, error) {
// First check that we actually have a channel for the given
// base scid. This should succeed for any channel where the
// option-scid-alias feature bit was negotiated.
if _, ok := existingAliases[baseScid]; !ok {
return false, fmt.Errorf("base scid %v not found",
baseScid)
}
for base, aliases := range existingAliases {
for _, alias := range aliases {
exists := alias.ToUint64() == newAlias
// Trying to add an alias that we already have
// for another channel is wrong.
if exists && base != baseScid {
return true, fmt.Errorf("%w: alias %v "+
"already exists for base scid "+
"%v", ErrAliasAlreadyExists,
alias, base)
}
if exists {
return true, nil
}
}
}
return false, nil
}
for _, v := range in.AliasMaps {
baseScid := lnwire.NewShortChanIDFromInt(v.BaseScid)
for _, rpcAlias := range v.Aliases {
// If not, let's add it to the alias manager now.
aliasScid := lnwire.NewShortChanIDFromInt(rpcAlias)
// But we only add it, if it's a valid alias, as defined
// by the BOLT spec.
if !aliasmgr.IsAlias(aliasScid) {
return nil, fmt.Errorf("%w: SCID alias %v is "+
"not a valid alias", ErrNoValidAlias,
aliasScid)
}
exists, err := aliasExists(rpcAlias, baseScid)
if err != nil {
return nil, err
}
// If the alias already exists, we see that as an error.
// This is to avoid "silent" collisions.
if exists {
return nil, fmt.Errorf("%w: SCID alias %v "+
"already exists", ErrAliasAlreadyExists,
rpcAlias)
}
err = s.cfg.AliasMgr.AddLocalAlias(
aliasScid, baseScid, false, true,
)
if err != nil {
return nil, fmt.Errorf("error adding scid "+
"alias, base_scid=%v, alias_scid=%v: "+
"%w", baseScid, aliasScid, err)
}
}
}
return &AddAliasesResponse{
AliasMaps: lnrpc.MarshalAliasMap(s.cfg.AliasMgr.ListAliases()),
}, nil
}
// XDeleteLocalChanAliases is an experimental API that deletes a set of alias
// mappings. The final total set of aliases in the manager after the delete
// operation is returned. The deletion will not be communicated to the channel
// peer via any message.
func (s *Server) XDeleteLocalChanAliases(_ context.Context,
in *DeleteAliasesRequest) (*DeleteAliasesResponse,
error) {
for _, v := range in.AliasMaps {
baseScid := lnwire.NewShortChanIDFromInt(v.BaseScid)
for _, alias := range v.Aliases {
aliasScid := lnwire.NewShortChanIDFromInt(alias)
err := s.cfg.AliasMgr.DeleteLocalAlias(
aliasScid, baseScid,
)
if err != nil {
return nil, fmt.Errorf("error deleting scid "+
"alias, base_scid=%v, alias_scid=%v: "+
"%w", baseScid, aliasScid, err)
}
}
}
return &DeleteAliasesResponse{
AliasMaps: lnrpc.MarshalAliasMap(s.cfg.AliasMgr.ListAliases()),
}, nil
}
func extractOutPoint(req *UpdateChanStatusRequest) (*wire.OutPoint, error) {
chanPoint := req.GetChanPoint()
txid, err := lnrpc.GetChanPointFundingTxid(chanPoint)
if err != nil {
return nil, err
}
index := chanPoint.OutputIndex
return wire.NewOutPoint(txid, index), nil
}
// UpdateChanStatus allows channel state to be set manually.
func (s *Server) UpdateChanStatus(_ context.Context,
req *UpdateChanStatusRequest) (*UpdateChanStatusResponse, error) {
outPoint, err := extractOutPoint(req)
if err != nil {
return nil, err
}
action := req.GetAction()
log.Debugf("UpdateChanStatus called for channel(%v) with "+
"action %v", outPoint, action)
switch action {
case ChanStatusAction_ENABLE:
err = s.cfg.RouterBackend.SetChannelEnabled(*outPoint)
case ChanStatusAction_DISABLE:
err = s.cfg.RouterBackend.SetChannelDisabled(*outPoint)
case ChanStatusAction_AUTO:
err = s.cfg.RouterBackend.SetChannelAuto(*outPoint)
default:
err = fmt.Errorf("unrecognized ChannelStatusAction %v", action)
}
if err != nil {
return nil, err
}
return &UpdateChanStatusResponse{}, nil
}
package routerrpc
import (
"context"
"encoding/hex"
"errors"
"fmt"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
// legacyTrackPaymentServer is a wrapper struct that transforms a stream of main
// rpc payment structs into the legacy PaymentStatus format.
type legacyTrackPaymentServer struct {
Router_TrackPaymentServer
}
// Send converts a Payment object and sends it as a PaymentStatus object on the
// embedded stream.
func (i *legacyTrackPaymentServer) Send(p *lnrpc.Payment) error {
var state PaymentState
switch p.Status {
case lnrpc.Payment_IN_FLIGHT:
state = PaymentState_IN_FLIGHT
case lnrpc.Payment_SUCCEEDED:
state = PaymentState_SUCCEEDED
case lnrpc.Payment_FAILED:
switch p.FailureReason {
case lnrpc.PaymentFailureReason_FAILURE_REASON_NONE:
return fmt.Errorf("expected fail reason")
case lnrpc.PaymentFailureReason_FAILURE_REASON_TIMEOUT:
state = PaymentState_FAILED_TIMEOUT
case lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE:
state = PaymentState_FAILED_NO_ROUTE
case lnrpc.PaymentFailureReason_FAILURE_REASON_ERROR:
state = PaymentState_FAILED_ERROR
case lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS:
state = PaymentState_FAILED_INCORRECT_PAYMENT_DETAILS
case lnrpc.PaymentFailureReason_FAILURE_REASON_INSUFFICIENT_BALANCE:
state = PaymentState_FAILED_INSUFFICIENT_BALANCE
default:
return fmt.Errorf("unknown failure reason %v",
p.FailureReason)
}
default:
return fmt.Errorf("unknown state %v", p.Status)
}
preimage, err := hex.DecodeString(p.PaymentPreimage)
if err != nil {
return err
}
legacyState := PaymentStatus{
State: state,
Preimage: preimage,
Htlcs: p.Htlcs,
}
return i.Router_TrackPaymentServer.Send(&legacyState)
}
// TrackPayment returns a stream of payment state updates. The stream is
// closed when the payment completes.
func (s *Server) TrackPayment(request *TrackPaymentRequest,
stream Router_TrackPaymentServer) error {
legacyStream := legacyTrackPaymentServer{
Router_TrackPaymentServer: stream,
}
return s.TrackPaymentV2(request, &legacyStream)
}
// SendPayment attempts to route a payment described by the passed
// PaymentRequest to the final destination. If we are unable to route the
// payment, or cannot find a route that satisfies the constraints in the
// PaymentRequest, then an error will be returned. Otherwise, the payment
// pre-image, along with the final route will be returned.
func (s *Server) SendPayment(request *SendPaymentRequest,
stream Router_SendPaymentServer) error {
if request.MaxParts > 1 {
return errors.New("for multi-part payments, use SendPaymentV2")
}
legacyStream := legacyTrackPaymentServer{
Router_TrackPaymentServer: stream,
}
return s.SendPaymentV2(request, &legacyStream)
}
// SendToRoute sends a payment through a predefined route. The response of this
// call contains structured error information.
func (s *Server) SendToRoute(ctx context.Context,
req *SendToRouteRequest) (*SendToRouteResponse, error) {
resp, err := s.SendToRouteV2(ctx, req)
if err != nil {
return nil, err
}
if resp == nil {
return nil, nil
}
// Need to convert to legacy response message because proto identifiers
// don't line up.
legacyResp := &SendToRouteResponse{
Preimage: resp.Preimage,
Failure: resp.Failure,
}
return legacyResp, nil
}
// QueryProbability returns the current success probability estimate for a
// given node pair and amount.
func (s *Server) QueryProbability(_ context.Context,
req *QueryProbabilityRequest) (*QueryProbabilityResponse, error) {
fromNode, err := route.NewVertexFromBytes(req.FromNode)
if err != nil {
return nil, err
}
toNode, err := route.NewVertexFromBytes(req.ToNode)
if err != nil {
return nil, err
}
amt := lnwire.MilliSatoshi(req.AmtMsat)
// Compute the probability.
var prob float64
mc := s.cfg.RouterBackend.MissionControl
capacity, err := s.cfg.RouterBackend.FetchAmountPairCapacity(
fromNode, toNode, amt,
)
// If we cannot query the capacity this means that either we don't have
// information available or that the channel fails min/maxHtlc
// constraints, so we return a zero probability.
if err != nil {
log.Errorf("Cannot fetch capacity: %v", err)
} else {
prob = mc.GetProbability(fromNode, toNode, amt, capacity)
}
history := mc.GetPairHistorySnapshot(fromNode, toNode)
return &QueryProbabilityResponse{
Probability: prob,
History: toRPCPairData(&history),
}, nil
}
package routerrpc
import (
"fmt"
"time"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/lnrpc"
)
// rpcHtlcEvent returns a rpc htlc event from a htlcswitch event.
func rpcHtlcEvent(htlcEvent interface{}) (*HtlcEvent, error) {
var (
key htlcswitch.HtlcKey
timestamp time.Time
eventType *htlcswitch.HtlcEventType
event isHtlcEvent_Event
)
switch e := htlcEvent.(type) {
case *htlcswitch.ForwardingEvent:
event = &HtlcEvent_ForwardEvent{
ForwardEvent: &ForwardEvent{
Info: rpcInfo(e.HtlcInfo),
},
}
key = e.HtlcKey
eventType = &e.HtlcEventType
timestamp = e.Timestamp
case *htlcswitch.ForwardingFailEvent:
event = &HtlcEvent_ForwardFailEvent{
ForwardFailEvent: &ForwardFailEvent{},
}
key = e.HtlcKey
eventType = &e.HtlcEventType
timestamp = e.Timestamp
case *htlcswitch.LinkFailEvent:
failureCode, failReason, err := rpcFailReason(
e.LinkError,
)
if err != nil {
return nil, err
}
event = &HtlcEvent_LinkFailEvent{
LinkFailEvent: &LinkFailEvent{
Info: rpcInfo(e.HtlcInfo),
WireFailure: failureCode,
FailureDetail: failReason,
FailureString: e.LinkError.Error(),
},
}
key = e.HtlcKey
eventType = &e.HtlcEventType
timestamp = e.Timestamp
case *htlcswitch.SettleEvent:
event = &HtlcEvent_SettleEvent{
SettleEvent: &SettleEvent{
Preimage: e.Preimage[:],
},
}
key = e.HtlcKey
eventType = &e.HtlcEventType
timestamp = e.Timestamp
case *htlcswitch.FinalHtlcEvent:
event = &HtlcEvent_FinalHtlcEvent{
FinalHtlcEvent: &FinalHtlcEvent{
Settled: e.Settled,
Offchain: e.Offchain,
},
}
key = htlcswitch.HtlcKey{
IncomingCircuit: e.CircuitKey,
}
timestamp = e.Timestamp
default:
return nil, fmt.Errorf("unknown event type: %T", e)
}
rpcEvent := &HtlcEvent{
IncomingChannelId: key.IncomingCircuit.ChanID.ToUint64(),
OutgoingChannelId: key.OutgoingCircuit.ChanID.ToUint64(),
IncomingHtlcId: key.IncomingCircuit.HtlcID,
OutgoingHtlcId: key.OutgoingCircuit.HtlcID,
TimestampNs: uint64(timestamp.UnixNano()),
Event: event,
}
// Convert the htlc event type to a rpc event.
if eventType != nil {
switch *eventType {
case htlcswitch.HtlcEventTypeSend:
rpcEvent.EventType = HtlcEvent_SEND
case htlcswitch.HtlcEventTypeReceive:
rpcEvent.EventType = HtlcEvent_RECEIVE
case htlcswitch.HtlcEventTypeForward:
rpcEvent.EventType = HtlcEvent_FORWARD
default:
return nil, fmt.Errorf("unknown event type: %v",
eventType)
}
}
return rpcEvent, nil
}
// rpcInfo returns a rpc struct containing the htlc information from the
// switch's htlc info struct.
func rpcInfo(info htlcswitch.HtlcInfo) *HtlcInfo {
return &HtlcInfo{
IncomingTimelock: info.IncomingTimeLock,
OutgoingTimelock: info.OutgoingTimeLock,
IncomingAmtMsat: uint64(info.IncomingAmt),
OutgoingAmtMsat: uint64(info.OutgoingAmt),
}
}
// rpcFailReason maps a lnwire failure message and failure detail to a rpc
// failure code and detail.
func rpcFailReason(linkErr *htlcswitch.LinkError) (lnrpc.Failure_FailureCode,
FailureDetail, error) {
wireErr, err := marshallError(linkErr)
if err != nil {
return 0, 0, err
}
wireCode := wireErr.GetCode()
// If the link has no failure detail, return with failure detail none.
if linkErr.FailureDetail == nil {
return wireCode, FailureDetail_NO_DETAIL, nil
}
switch failureDetail := linkErr.FailureDetail.(type) {
case invoices.FailResolutionResult:
fd, err := rpcFailureResolution(failureDetail)
return wireCode, fd, err
case htlcswitch.OutgoingFailure:
fd, err := rpcOutgoingFailure(failureDetail)
return wireCode, fd, err
default:
return 0, 0, fmt.Errorf("unknown failure "+
"detail type: %T", linkErr.FailureDetail)
}
}
// rpcFailureResolution maps an invoice failure resolution to a rpc failure
// detail. Invoice failures have no zero resolution results (every failure
// is accompanied with a result), so we error if we fail to match the result
// type.
func rpcFailureResolution(invoiceFailure invoices.FailResolutionResult) (
FailureDetail, error) {
switch invoiceFailure {
case invoices.ResultReplayToCanceled:
return FailureDetail_INVOICE_CANCELED, nil
case invoices.ResultInvoiceAlreadyCanceled:
return FailureDetail_INVOICE_CANCELED, nil
case invoices.ResultAmountTooLow:
return FailureDetail_INVOICE_UNDERPAID, nil
case invoices.ResultExpiryTooSoon:
return FailureDetail_INVOICE_EXPIRY_TOO_SOON, nil
case invoices.ResultCanceled:
return FailureDetail_INVOICE_CANCELED, nil
case invoices.ResultInvoiceNotOpen:
return FailureDetail_INVOICE_NOT_OPEN, nil
case invoices.ResultMppTimeout:
return FailureDetail_MPP_INVOICE_TIMEOUT, nil
case invoices.ResultAddressMismatch:
return FailureDetail_ADDRESS_MISMATCH, nil
case invoices.ResultHtlcSetTotalMismatch:
return FailureDetail_SET_TOTAL_MISMATCH, nil
case invoices.ResultHtlcSetTotalTooLow:
return FailureDetail_SET_TOTAL_TOO_LOW, nil
case invoices.ResultHtlcSetOverpayment:
return FailureDetail_SET_OVERPAID, nil
case invoices.ResultInvoiceNotFound:
return FailureDetail_UNKNOWN_INVOICE, nil
case invoices.ResultKeySendError:
return FailureDetail_INVALID_KEYSEND, nil
case invoices.ResultMppInProgress:
return FailureDetail_MPP_IN_PROGRESS, nil
default:
return 0, fmt.Errorf("unknown fail resolution: %v",
invoiceFailure.FailureString())
}
}
// rpcOutgoingFailure maps an outgoing failure to a rpc FailureDetail. If the
// failure detail is FailureDetailNone, which indicates that the failure was
// a wire message which required no further failure detail, we return a no
// detail failure detail to indicate that there was no additional information.
func rpcOutgoingFailure(failureDetail htlcswitch.OutgoingFailure) (
FailureDetail, error) {
switch failureDetail {
case htlcswitch.OutgoingFailureNone:
return FailureDetail_NO_DETAIL, nil
case htlcswitch.OutgoingFailureDecodeError:
return FailureDetail_ONION_DECODE, nil
case htlcswitch.OutgoingFailureLinkNotEligible:
return FailureDetail_LINK_NOT_ELIGIBLE, nil
case htlcswitch.OutgoingFailureOnChainTimeout:
return FailureDetail_ON_CHAIN_TIMEOUT, nil
case htlcswitch.OutgoingFailureHTLCExceedsMax:
return FailureDetail_HTLC_EXCEEDS_MAX, nil
case htlcswitch.OutgoingFailureInsufficientBalance:
return FailureDetail_INSUFFICIENT_BALANCE, nil
case htlcswitch.OutgoingFailureCircularRoute:
return FailureDetail_CIRCULAR_ROUTE, nil
case htlcswitch.OutgoingFailureIncompleteForward:
return FailureDetail_INCOMPLETE_FORWARD, nil
case htlcswitch.OutgoingFailureDownstreamHtlcAdd:
return FailureDetail_HTLC_ADD_FAILED, nil
case htlcswitch.OutgoingFailureForwardsDisabled:
return FailureDetail_FORWARDS_DISABLED, nil
default:
return 0, fmt.Errorf("unknown outgoing failure "+
"detail: %v", failureDetail.FailureString())
}
}
package lnrpc
import (
"encoding/hex"
"errors"
"fmt"
"sort"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/sweep"
"google.golang.org/protobuf/encoding/protojson"
)
const (
// RegisterRPCMiddlewareURI is the full RPC method URI for the
// middleware registration call. This is declared here rather than where
// it's mainly used to avoid circular package dependencies.
RegisterRPCMiddlewareURI = "/lnrpc.Lightning/RegisterRPCMiddleware"
)
var (
// ProtoJSONMarshalOpts is a struct that holds the default marshal
// options for marshaling protobuf messages into JSON in a
// human-readable way. This should only be used in the CLI and in
// integration tests.
ProtoJSONMarshalOpts = &protojson.MarshalOptions{
EmitUnpopulated: true,
UseProtoNames: true,
Indent: " ",
UseHexForBytes: true,
}
// ProtoJSONUnmarshalOpts is a struct that holds the default unmarshal
// options for un-marshaling lncli JSON into protobuf messages. This
// should only be used in the CLI and in integration tests.
ProtoJSONUnmarshalOpts = &protojson.UnmarshalOptions{
AllowPartial: false,
UseHexForBytes: true,
}
// RESTJsonMarshalOpts is a struct that holds the default marshal
// options for marshaling protobuf messages into REST JSON in a
// human-readable way. This should be used when interacting with the
// REST proxy only.
RESTJsonMarshalOpts = &protojson.MarshalOptions{
EmitUnpopulated: true,
UseProtoNames: true,
}
// RESTJsonUnmarshalOpts is a struct that holds the default unmarshal
// options for un-marshaling REST JSON into protobuf messages. This
// should be used when interacting with the REST proxy only.
RESTJsonUnmarshalOpts = &protojson.UnmarshalOptions{
AllowPartial: false,
}
)
// RPCTransaction returns a rpc transaction.
func RPCTransaction(tx *lnwallet.TransactionDetail) *Transaction {
var destAddresses []string
// Re-package destination output information.
var outputDetails []*OutputDetail
for _, o := range tx.OutputDetails {
// Note: DestAddresses is deprecated but we keep
// populating it with addresses for backwards
// compatibility.
for _, a := range o.Addresses {
destAddresses = append(destAddresses,
a.EncodeAddress())
}
var address string
if len(o.Addresses) == 1 {
address = o.Addresses[0].EncodeAddress()
}
outputDetails = append(outputDetails, &OutputDetail{
OutputType: MarshallOutputType(o.OutputType),
Address: address,
PkScript: hex.EncodeToString(o.PkScript),
OutputIndex: int64(o.OutputIndex),
Amount: int64(o.Value),
IsOurAddress: o.IsOurAddress,
})
}
previousOutpoints := make([]*PreviousOutPoint, len(tx.PreviousOutpoints))
for idx, previousOutPoint := range tx.PreviousOutpoints {
previousOutpoints[idx] = &PreviousOutPoint{
Outpoint: previousOutPoint.OutPoint,
IsOurOutput: previousOutPoint.IsOurOutput,
}
}
// We also get unconfirmed transactions, so BlockHash can be nil.
blockHash := ""
if tx.BlockHash != nil {
blockHash = tx.BlockHash.String()
}
return &Transaction{
TxHash: tx.Hash.String(),
Amount: int64(tx.Value),
NumConfirmations: tx.NumConfirmations,
BlockHash: blockHash,
BlockHeight: tx.BlockHeight,
TimeStamp: tx.Timestamp,
TotalFees: tx.TotalFees,
DestAddresses: destAddresses,
OutputDetails: outputDetails,
RawTxHex: hex.EncodeToString(tx.RawTx),
Label: tx.Label,
PreviousOutpoints: previousOutpoints,
}
}
// RPCTransactionDetails returns a set of rpc transaction details.
func RPCTransactionDetails(txns []*lnwallet.TransactionDetail, firstIdx,
lastIdx uint64) *TransactionDetails {
txDetails := &TransactionDetails{
Transactions: make([]*Transaction, len(txns)),
FirstIndex: firstIdx,
LastIndex: lastIdx,
}
for i, tx := range txns {
txDetails.Transactions[i] = RPCTransaction(tx)
}
// Sort transactions by number of confirmations rather than height so
// that unconfirmed transactions (height =0; confirmations =-1) will
// follow the most recently set of confirmed transactions. If we sort
// by height, unconfirmed transactions will follow our oldest
// transactions, because they have lower block heights.
sort.Slice(txDetails.Transactions, func(i, j int) bool {
return txDetails.Transactions[i].NumConfirmations <
txDetails.Transactions[j].NumConfirmations
})
return txDetails
}
// ExtractMinConfs extracts the minimum number of confirmations that each
// output used to fund a transaction should satisfy.
func ExtractMinConfs(minConfs int32, spendUnconfirmed bool) (int32, error) {
switch {
// Ensure that the MinConfs parameter is non-negative.
case minConfs < 0:
return 0, errors.New("minimum number of confirmations must " +
"be a non-negative number")
// The transaction should not be funded with unconfirmed outputs
// unless explicitly specified by SpendUnconfirmed. We do this to
// provide sane defaults to the OpenChannel RPC, as otherwise, if the
// MinConfs field isn't explicitly set by the caller, we'll use
// unconfirmed outputs without the caller being aware.
case minConfs == 0 && !spendUnconfirmed:
return 1, nil
// In the event that the caller set MinConfs > 0 and SpendUnconfirmed to
// true, we'll return an error to indicate the conflict.
case minConfs > 0 && spendUnconfirmed:
return 0, errors.New("SpendUnconfirmed set to true with " +
"MinConfs > 0")
// The funding transaction of the new channel to be created can be
// funded with unconfirmed outputs.
case spendUnconfirmed:
return 0, nil
// If none of the above cases matched, we'll return the value set
// explicitly by the caller.
default:
return minConfs, nil
}
}
// GetChanPointFundingTxid returns the given channel point's funding txid in
// raw bytes.
func GetChanPointFundingTxid(chanPoint *ChannelPoint) (*chainhash.Hash, error) {
var txid []byte
// A channel point's funding txid can be get/set as a byte slice or a
// string. In the case it is a string, decode it.
switch chanPoint.GetFundingTxid().(type) {
case *ChannelPoint_FundingTxidBytes:
txid = chanPoint.GetFundingTxidBytes()
case *ChannelPoint_FundingTxidStr:
s := chanPoint.GetFundingTxidStr()
h, err := chainhash.NewHashFromStr(s)
if err != nil {
return nil, err
}
txid = h[:]
}
return chainhash.NewHash(txid)
}
// GetChannelOutPoint returns the outpoint of the related channel point.
func GetChannelOutPoint(chanPoint *ChannelPoint) (*OutPoint, error) {
var txid []byte
// A channel point's funding txid can be get/set as a byte slice or a
// string. In the case it is a string, decode it.
switch chanPoint.GetFundingTxid().(type) {
case *ChannelPoint_FundingTxidBytes:
txid = chanPoint.GetFundingTxidBytes()
case *ChannelPoint_FundingTxidStr:
s := chanPoint.GetFundingTxidStr()
h, err := chainhash.NewHashFromStr(s)
if err != nil {
return nil, err
}
txid = h[:]
}
return &OutPoint{
TxidBytes: txid,
OutputIndex: chanPoint.OutputIndex,
}, nil
}
// CalculateFeeRate uses either satPerByte or satPerVByte, but not both, from a
// request to calculate the fee rate. It provides compatibility for the
// deprecated field, satPerByte. Once the field is safe to be removed, the
// check can then be deleted.
func CalculateFeeRate(satPerByte, satPerVByte uint64, targetConf uint32,
estimator chainfee.Estimator) (chainfee.SatPerKWeight, error) {
var feeRate chainfee.SatPerKWeight
// We only allow using either the deprecated field or the new field.
if satPerByte != 0 && satPerVByte != 0 {
return feeRate, fmt.Errorf("either SatPerByte or " +
"SatPerVByte should be set, but not both")
}
// Default to satPerVByte, and overwrite it if satPerByte is set.
satPerKw := chainfee.SatPerKVByte(satPerVByte * 1000).FeePerKWeight()
if satPerByte != 0 {
satPerKw = chainfee.SatPerKVByte(
satPerByte * 1000,
).FeePerKWeight()
}
// Based on the passed fee related parameters, we'll determine an
// appropriate fee rate for this transaction.
feePref := sweep.FeeEstimateInfo{
ConfTarget: targetConf,
FeeRate: satPerKw,
}
// TODO(yy): need to pass the configured max fee here.
feeRate, err := feePref.Estimate(estimator, 0)
if err != nil {
return feeRate, err
}
return feeRate, nil
}
package signrpc
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("SGNR", nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v3.21.12
// source: signrpc/signer.proto
package signrpc
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type SignMethod int32
const (
// Specifies that a SegWit v0 (p2wkh, np2wkh, p2wsh) input script should be
// signed.
SignMethod_SIGN_METHOD_WITNESS_V0 SignMethod = 0
// Specifies that a SegWit v1 (p2tr) input should be signed by using the
// BIP0086 method (commit to internal key only).
SignMethod_SIGN_METHOD_TAPROOT_KEY_SPEND_BIP0086 SignMethod = 1
// Specifies that a SegWit v1 (p2tr) input should be signed by using a given
// taproot hash to commit to in addition to the internal key.
SignMethod_SIGN_METHOD_TAPROOT_KEY_SPEND SignMethod = 2
// Specifies that a SegWit v1 (p2tr) input should be spent using the script
// path and that a specific leaf script should be signed for.
SignMethod_SIGN_METHOD_TAPROOT_SCRIPT_SPEND SignMethod = 3
)
// Enum value maps for SignMethod.
var (
SignMethod_name = map[int32]string{
0: "SIGN_METHOD_WITNESS_V0",
1: "SIGN_METHOD_TAPROOT_KEY_SPEND_BIP0086",
2: "SIGN_METHOD_TAPROOT_KEY_SPEND",
3: "SIGN_METHOD_TAPROOT_SCRIPT_SPEND",
}
SignMethod_value = map[string]int32{
"SIGN_METHOD_WITNESS_V0": 0,
"SIGN_METHOD_TAPROOT_KEY_SPEND_BIP0086": 1,
"SIGN_METHOD_TAPROOT_KEY_SPEND": 2,
"SIGN_METHOD_TAPROOT_SCRIPT_SPEND": 3,
}
)
func (x SignMethod) Enum() *SignMethod {
p := new(SignMethod)
*p = x
return p
}
func (x SignMethod) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (SignMethod) Descriptor() protoreflect.EnumDescriptor {
return file_signrpc_signer_proto_enumTypes[0].Descriptor()
}
func (SignMethod) Type() protoreflect.EnumType {
return &file_signrpc_signer_proto_enumTypes[0]
}
func (x SignMethod) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use SignMethod.Descriptor instead.
func (SignMethod) EnumDescriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{0}
}
type MuSig2Version int32
const (
// The default value on the RPC is zero for enums so we need to represent an
// invalid/undefined version by default to make sure clients upgrade their
// software to set the version explicitly.
MuSig2Version_MUSIG2_VERSION_UNDEFINED MuSig2Version = 0
// The version of MuSig2 that lnd 0.15.x shipped with, which corresponds to the
// version v0.4.0 of the MuSig2 BIP draft.
MuSig2Version_MUSIG2_VERSION_V040 MuSig2Version = 1
// The current version of MuSig2 which corresponds to the version v1.0.0rc2 of
// the MuSig2 BIP draft.
MuSig2Version_MUSIG2_VERSION_V100RC2 MuSig2Version = 2
)
// Enum value maps for MuSig2Version.
var (
MuSig2Version_name = map[int32]string{
0: "MUSIG2_VERSION_UNDEFINED",
1: "MUSIG2_VERSION_V040",
2: "MUSIG2_VERSION_V100RC2",
}
MuSig2Version_value = map[string]int32{
"MUSIG2_VERSION_UNDEFINED": 0,
"MUSIG2_VERSION_V040": 1,
"MUSIG2_VERSION_V100RC2": 2,
}
)
func (x MuSig2Version) Enum() *MuSig2Version {
p := new(MuSig2Version)
*p = x
return p
}
func (x MuSig2Version) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (MuSig2Version) Descriptor() protoreflect.EnumDescriptor {
return file_signrpc_signer_proto_enumTypes[1].Descriptor()
}
func (MuSig2Version) Type() protoreflect.EnumType {
return &file_signrpc_signer_proto_enumTypes[1]
}
func (x MuSig2Version) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use MuSig2Version.Descriptor instead.
func (MuSig2Version) EnumDescriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{1}
}
type KeyLocator struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The family of key being identified.
KeyFamily int32 `protobuf:"varint,1,opt,name=key_family,json=keyFamily,proto3" json:"key_family,omitempty"`
// The precise index of the key being identified.
KeyIndex int32 `protobuf:"varint,2,opt,name=key_index,json=keyIndex,proto3" json:"key_index,omitempty"`
}
func (x *KeyLocator) Reset() {
*x = KeyLocator{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *KeyLocator) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*KeyLocator) ProtoMessage() {}
func (x *KeyLocator) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use KeyLocator.ProtoReflect.Descriptor instead.
func (*KeyLocator) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{0}
}
func (x *KeyLocator) GetKeyFamily() int32 {
if x != nil {
return x.KeyFamily
}
return 0
}
func (x *KeyLocator) GetKeyIndex() int32 {
if x != nil {
return x.KeyIndex
}
return 0
}
type KeyDescriptor struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The raw bytes of the public key in the key pair being identified. Either
// this or the KeyLocator must be specified.
RawKeyBytes []byte `protobuf:"bytes,1,opt,name=raw_key_bytes,json=rawKeyBytes,proto3" json:"raw_key_bytes,omitempty"`
// The key locator that identifies which private key to use for signing.
// Either this or the raw bytes of the target public key must be specified.
KeyLoc *KeyLocator `protobuf:"bytes,2,opt,name=key_loc,json=keyLoc,proto3" json:"key_loc,omitempty"`
}
func (x *KeyDescriptor) Reset() {
*x = KeyDescriptor{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *KeyDescriptor) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*KeyDescriptor) ProtoMessage() {}
func (x *KeyDescriptor) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use KeyDescriptor.ProtoReflect.Descriptor instead.
func (*KeyDescriptor) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{1}
}
func (x *KeyDescriptor) GetRawKeyBytes() []byte {
if x != nil {
return x.RawKeyBytes
}
return nil
}
func (x *KeyDescriptor) GetKeyLoc() *KeyLocator {
if x != nil {
return x.KeyLoc
}
return nil
}
type TxOut struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The value of the output being spent.
Value int64 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
// The script of the output being spent.
PkScript []byte `protobuf:"bytes,2,opt,name=pk_script,json=pkScript,proto3" json:"pk_script,omitempty"`
}
func (x *TxOut) Reset() {
*x = TxOut{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TxOut) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TxOut) ProtoMessage() {}
func (x *TxOut) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TxOut.ProtoReflect.Descriptor instead.
func (*TxOut) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{2}
}
func (x *TxOut) GetValue() int64 {
if x != nil {
return x.Value
}
return 0
}
func (x *TxOut) GetPkScript() []byte {
if x != nil {
return x.PkScript
}
return nil
}
type SignDescriptor struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A descriptor that precisely describes *which* key to use for signing. This
// may provide the raw public key directly, or require the Signer to re-derive
// the key according to the populated derivation path.
//
// Note that if the key descriptor was obtained through walletrpc.DeriveKey,
// then the key locator MUST always be provided, since the derived keys are not
// persisted unlike with DeriveNextKey.
KeyDesc *KeyDescriptor `protobuf:"bytes,1,opt,name=key_desc,json=keyDesc,proto3" json:"key_desc,omitempty"`
// A scalar value that will be added to the private key corresponding to the
// above public key to obtain the private key to be used to sign this input.
// This value is typically derived via the following computation:
//
// derivedKey = privkey + sha256(perCommitmentPoint || pubKey) mod N
SingleTweak []byte `protobuf:"bytes,2,opt,name=single_tweak,json=singleTweak,proto3" json:"single_tweak,omitempty"`
// A private key that will be used in combination with its corresponding
// private key to derive the private key that is to be used to sign the target
// input. Within the Lightning protocol, this value is typically the
// commitment secret from a previously revoked commitment transaction. This
// value is in combination with two hash values, and the original private key
// to derive the private key to be used when signing.
//
// k = (privKey*sha256(pubKey || tweakPub) +
// tweakPriv*sha256(tweakPub || pubKey)) mod N
DoubleTweak []byte `protobuf:"bytes,3,opt,name=double_tweak,json=doubleTweak,proto3" json:"double_tweak,omitempty"`
// The 32 byte input to the taproot tweak derivation that is used to derive
// the output key from an internal key: outputKey = internalKey +
// tagged_hash("tapTweak", internalKey || tapTweak).
//
// When doing a BIP 86 spend, this field can be an empty byte slice.
//
// When doing a normal key path spend, with the output key committing to an
// actual script root, then this field should be: the tapscript root hash.
TapTweak []byte `protobuf:"bytes,10,opt,name=tap_tweak,json=tapTweak,proto3" json:"tap_tweak,omitempty"`
// The full script required to properly redeem the output. This field will
// only be populated if a p2tr, p2wsh or a p2sh output is being signed. If a
// taproot script path spend is being attempted, then this should be the raw
// leaf script.
WitnessScript []byte `protobuf:"bytes,4,opt,name=witness_script,json=witnessScript,proto3" json:"witness_script,omitempty"`
// A description of the output being spent. The value and script MUST be
// provided.
Output *TxOut `protobuf:"bytes,5,opt,name=output,proto3" json:"output,omitempty"`
// The target sighash type that should be used when generating the final
// sighash, and signature.
Sighash uint32 `protobuf:"varint,7,opt,name=sighash,proto3" json:"sighash,omitempty"`
// The target input within the transaction that should be signed.
InputIndex int32 `protobuf:"varint,8,opt,name=input_index,json=inputIndex,proto3" json:"input_index,omitempty"`
// The sign method specifies how the input should be signed. Depending on the
// method, either the tap_tweak, witness_script or both need to be specified.
// Defaults to SegWit v0 signing to be backward compatible with older RPC
// clients.
SignMethod SignMethod `protobuf:"varint,9,opt,name=sign_method,json=signMethod,proto3,enum=signrpc.SignMethod" json:"sign_method,omitempty"`
}
func (x *SignDescriptor) Reset() {
*x = SignDescriptor{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SignDescriptor) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SignDescriptor) ProtoMessage() {}
func (x *SignDescriptor) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SignDescriptor.ProtoReflect.Descriptor instead.
func (*SignDescriptor) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{3}
}
func (x *SignDescriptor) GetKeyDesc() *KeyDescriptor {
if x != nil {
return x.KeyDesc
}
return nil
}
func (x *SignDescriptor) GetSingleTweak() []byte {
if x != nil {
return x.SingleTweak
}
return nil
}
func (x *SignDescriptor) GetDoubleTweak() []byte {
if x != nil {
return x.DoubleTweak
}
return nil
}
func (x *SignDescriptor) GetTapTweak() []byte {
if x != nil {
return x.TapTweak
}
return nil
}
func (x *SignDescriptor) GetWitnessScript() []byte {
if x != nil {
return x.WitnessScript
}
return nil
}
func (x *SignDescriptor) GetOutput() *TxOut {
if x != nil {
return x.Output
}
return nil
}
func (x *SignDescriptor) GetSighash() uint32 {
if x != nil {
return x.Sighash
}
return 0
}
func (x *SignDescriptor) GetInputIndex() int32 {
if x != nil {
return x.InputIndex
}
return 0
}
func (x *SignDescriptor) GetSignMethod() SignMethod {
if x != nil {
return x.SignMethod
}
return SignMethod_SIGN_METHOD_WITNESS_V0
}
type SignReq struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The raw bytes of the transaction to be signed.
RawTxBytes []byte `protobuf:"bytes,1,opt,name=raw_tx_bytes,json=rawTxBytes,proto3" json:"raw_tx_bytes,omitempty"`
// A set of sign descriptors, for each input to be signed.
SignDescs []*SignDescriptor `protobuf:"bytes,2,rep,name=sign_descs,json=signDescs,proto3" json:"sign_descs,omitempty"`
// The full list of UTXO information for each of the inputs being spent. This
// is required when spending one or more taproot (SegWit v1) outputs.
PrevOutputs []*TxOut `protobuf:"bytes,3,rep,name=prev_outputs,json=prevOutputs,proto3" json:"prev_outputs,omitempty"`
}
func (x *SignReq) Reset() {
*x = SignReq{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SignReq) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SignReq) ProtoMessage() {}
func (x *SignReq) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SignReq.ProtoReflect.Descriptor instead.
func (*SignReq) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{4}
}
func (x *SignReq) GetRawTxBytes() []byte {
if x != nil {
return x.RawTxBytes
}
return nil
}
func (x *SignReq) GetSignDescs() []*SignDescriptor {
if x != nil {
return x.SignDescs
}
return nil
}
func (x *SignReq) GetPrevOutputs() []*TxOut {
if x != nil {
return x.PrevOutputs
}
return nil
}
type SignResp struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A set of signatures realized in a fixed 64-byte format ordered in ascending
// input order.
RawSigs [][]byte `protobuf:"bytes,1,rep,name=raw_sigs,json=rawSigs,proto3" json:"raw_sigs,omitempty"`
}
func (x *SignResp) Reset() {
*x = SignResp{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SignResp) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SignResp) ProtoMessage() {}
func (x *SignResp) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SignResp.ProtoReflect.Descriptor instead.
func (*SignResp) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{5}
}
func (x *SignResp) GetRawSigs() [][]byte {
if x != nil {
return x.RawSigs
}
return nil
}
type InputScript struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The serializes witness stack for the specified input.
Witness [][]byte `protobuf:"bytes,1,rep,name=witness,proto3" json:"witness,omitempty"`
// The optional sig script for the specified witness that will only be set if
// the input specified is a nested p2sh witness program.
SigScript []byte `protobuf:"bytes,2,opt,name=sig_script,json=sigScript,proto3" json:"sig_script,omitempty"`
}
func (x *InputScript) Reset() {
*x = InputScript{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *InputScript) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*InputScript) ProtoMessage() {}
func (x *InputScript) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use InputScript.ProtoReflect.Descriptor instead.
func (*InputScript) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{6}
}
func (x *InputScript) GetWitness() [][]byte {
if x != nil {
return x.Witness
}
return nil
}
func (x *InputScript) GetSigScript() []byte {
if x != nil {
return x.SigScript
}
return nil
}
type InputScriptResp struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The set of fully valid input scripts requested.
InputScripts []*InputScript `protobuf:"bytes,1,rep,name=input_scripts,json=inputScripts,proto3" json:"input_scripts,omitempty"`
}
func (x *InputScriptResp) Reset() {
*x = InputScriptResp{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *InputScriptResp) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*InputScriptResp) ProtoMessage() {}
func (x *InputScriptResp) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use InputScriptResp.ProtoReflect.Descriptor instead.
func (*InputScriptResp) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{7}
}
func (x *InputScriptResp) GetInputScripts() []*InputScript {
if x != nil {
return x.InputScripts
}
return nil
}
type SignMessageReq struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The message to be signed. When using REST, this field must be encoded as
// base64.
Msg []byte `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"`
// The key locator that identifies which key to use for signing.
KeyLoc *KeyLocator `protobuf:"bytes,2,opt,name=key_loc,json=keyLoc,proto3" json:"key_loc,omitempty"`
// Double-SHA256 hash instead of just the default single round.
DoubleHash bool `protobuf:"varint,3,opt,name=double_hash,json=doubleHash,proto3" json:"double_hash,omitempty"`
// Use the compact (pubkey recoverable) format instead of the raw lnwire
// format. This option cannot be used with Schnorr signatures.
CompactSig bool `protobuf:"varint,4,opt,name=compact_sig,json=compactSig,proto3" json:"compact_sig,omitempty"`
// Use Schnorr signature. This option cannot be used with compact format.
SchnorrSig bool `protobuf:"varint,5,opt,name=schnorr_sig,json=schnorrSig,proto3" json:"schnorr_sig,omitempty"`
// The optional Taproot tweak bytes to apply to the private key before creating
// a Schnorr signature. The private key is tweaked as described in BIP-341:
// privKey + h_tapTweak(internalKey || tapTweak)
SchnorrSigTapTweak []byte `protobuf:"bytes,6,opt,name=schnorr_sig_tap_tweak,json=schnorrSigTapTweak,proto3" json:"schnorr_sig_tap_tweak,omitempty"`
// An optional tag that can be provided when taking a tagged hash of a
// message. This option can only be used when schnorr_sig is true.
Tag []byte `protobuf:"bytes,7,opt,name=tag,proto3" json:"tag,omitempty"`
}
func (x *SignMessageReq) Reset() {
*x = SignMessageReq{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SignMessageReq) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SignMessageReq) ProtoMessage() {}
func (x *SignMessageReq) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SignMessageReq.ProtoReflect.Descriptor instead.
func (*SignMessageReq) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{8}
}
func (x *SignMessageReq) GetMsg() []byte {
if x != nil {
return x.Msg
}
return nil
}
func (x *SignMessageReq) GetKeyLoc() *KeyLocator {
if x != nil {
return x.KeyLoc
}
return nil
}
func (x *SignMessageReq) GetDoubleHash() bool {
if x != nil {
return x.DoubleHash
}
return false
}
func (x *SignMessageReq) GetCompactSig() bool {
if x != nil {
return x.CompactSig
}
return false
}
func (x *SignMessageReq) GetSchnorrSig() bool {
if x != nil {
return x.SchnorrSig
}
return false
}
func (x *SignMessageReq) GetSchnorrSigTapTweak() []byte {
if x != nil {
return x.SchnorrSigTapTweak
}
return nil
}
func (x *SignMessageReq) GetTag() []byte {
if x != nil {
return x.Tag
}
return nil
}
type SignMessageResp struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The signature for the given message in the fixed-size LN wire format.
Signature []byte `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"`
}
func (x *SignMessageResp) Reset() {
*x = SignMessageResp{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SignMessageResp) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SignMessageResp) ProtoMessage() {}
func (x *SignMessageResp) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SignMessageResp.ProtoReflect.Descriptor instead.
func (*SignMessageResp) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{9}
}
func (x *SignMessageResp) GetSignature() []byte {
if x != nil {
return x.Signature
}
return nil
}
type VerifyMessageReq struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The message over which the signature is to be verified. When using
// REST, this field must be encoded as base64.
Msg []byte `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"`
// The fixed-size LN wire encoded signature to be verified over the given
// message. When using REST, this field must be encoded as base64.
Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"`
// The public key the signature has to be valid for. When using REST, this
// field must be encoded as base64. If the is_schnorr_sig option is true, then
// the public key is expected to be in the 32-byte x-only serialization
// according to BIP-340.
Pubkey []byte `protobuf:"bytes,3,opt,name=pubkey,proto3" json:"pubkey,omitempty"`
// Specifies if the signature is a Schnorr signature.
IsSchnorrSig bool `protobuf:"varint,4,opt,name=is_schnorr_sig,json=isSchnorrSig,proto3" json:"is_schnorr_sig,omitempty"`
// An optional tag that can be provided when taking a tagged hash of a
// message. This option can only be used when is_schnorr_sig is true.
Tag []byte `protobuf:"bytes,5,opt,name=tag,proto3" json:"tag,omitempty"`
}
func (x *VerifyMessageReq) Reset() {
*x = VerifyMessageReq{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *VerifyMessageReq) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*VerifyMessageReq) ProtoMessage() {}
func (x *VerifyMessageReq) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use VerifyMessageReq.ProtoReflect.Descriptor instead.
func (*VerifyMessageReq) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{10}
}
func (x *VerifyMessageReq) GetMsg() []byte {
if x != nil {
return x.Msg
}
return nil
}
func (x *VerifyMessageReq) GetSignature() []byte {
if x != nil {
return x.Signature
}
return nil
}
func (x *VerifyMessageReq) GetPubkey() []byte {
if x != nil {
return x.Pubkey
}
return nil
}
func (x *VerifyMessageReq) GetIsSchnorrSig() bool {
if x != nil {
return x.IsSchnorrSig
}
return false
}
func (x *VerifyMessageReq) GetTag() []byte {
if x != nil {
return x.Tag
}
return nil
}
type VerifyMessageResp struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Whether the signature was valid over the given message.
Valid bool `protobuf:"varint,1,opt,name=valid,proto3" json:"valid,omitempty"`
}
func (x *VerifyMessageResp) Reset() {
*x = VerifyMessageResp{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *VerifyMessageResp) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*VerifyMessageResp) ProtoMessage() {}
func (x *VerifyMessageResp) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[11]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use VerifyMessageResp.ProtoReflect.Descriptor instead.
func (*VerifyMessageResp) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{11}
}
func (x *VerifyMessageResp) GetValid() bool {
if x != nil {
return x.Valid
}
return false
}
type SharedKeyRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The ephemeral public key to use for the DH key derivation.
EphemeralPubkey []byte `protobuf:"bytes,1,opt,name=ephemeral_pubkey,json=ephemeralPubkey,proto3" json:"ephemeral_pubkey,omitempty"`
// Deprecated. The optional key locator of the local key that should be used.
// If this parameter is not set then the node's identity private key will be
// used.
//
// Deprecated: Marked as deprecated in signrpc/signer.proto.
KeyLoc *KeyLocator `protobuf:"bytes,2,opt,name=key_loc,json=keyLoc,proto3" json:"key_loc,omitempty"`
// A key descriptor describes the key used for performing ECDH. Either a key
// locator or a raw public key is expected, if neither is supplied, defaults to
// the node's identity private key.
KeyDesc *KeyDescriptor `protobuf:"bytes,3,opt,name=key_desc,json=keyDesc,proto3" json:"key_desc,omitempty"`
}
func (x *SharedKeyRequest) Reset() {
*x = SharedKeyRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SharedKeyRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SharedKeyRequest) ProtoMessage() {}
func (x *SharedKeyRequest) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[12]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SharedKeyRequest.ProtoReflect.Descriptor instead.
func (*SharedKeyRequest) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{12}
}
func (x *SharedKeyRequest) GetEphemeralPubkey() []byte {
if x != nil {
return x.EphemeralPubkey
}
return nil
}
// Deprecated: Marked as deprecated in signrpc/signer.proto.
func (x *SharedKeyRequest) GetKeyLoc() *KeyLocator {
if x != nil {
return x.KeyLoc
}
return nil
}
func (x *SharedKeyRequest) GetKeyDesc() *KeyDescriptor {
if x != nil {
return x.KeyDesc
}
return nil
}
type SharedKeyResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The shared public key, hashed with sha256.
SharedKey []byte `protobuf:"bytes,1,opt,name=shared_key,json=sharedKey,proto3" json:"shared_key,omitempty"`
}
func (x *SharedKeyResponse) Reset() {
*x = SharedKeyResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SharedKeyResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SharedKeyResponse) ProtoMessage() {}
func (x *SharedKeyResponse) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[13]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SharedKeyResponse.ProtoReflect.Descriptor instead.
func (*SharedKeyResponse) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{13}
}
func (x *SharedKeyResponse) GetSharedKey() []byte {
if x != nil {
return x.SharedKey
}
return nil
}
type TweakDesc struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Tweak is the 32-byte value that will modify the public key.
Tweak []byte `protobuf:"bytes,1,opt,name=tweak,proto3" json:"tweak,omitempty"`
// Specifies if the target key should be converted to an x-only public key
// before tweaking. If true, then the public key will be mapped to an x-only
// key before the tweaking operation is applied.
IsXOnly bool `protobuf:"varint,2,opt,name=is_x_only,json=isXOnly,proto3" json:"is_x_only,omitempty"`
}
func (x *TweakDesc) Reset() {
*x = TweakDesc{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TweakDesc) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TweakDesc) ProtoMessage() {}
func (x *TweakDesc) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[14]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TweakDesc.ProtoReflect.Descriptor instead.
func (*TweakDesc) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{14}
}
func (x *TweakDesc) GetTweak() []byte {
if x != nil {
return x.Tweak
}
return nil
}
func (x *TweakDesc) GetIsXOnly() bool {
if x != nil {
return x.IsXOnly
}
return false
}
type TaprootTweakDesc struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The root hash of the tapscript tree if a script path is committed to. If
// the MuSig2 key put on chain doesn't also commit to a script path (BIP-0086
// key spend only), then this needs to be empty and the key_spend_only field
// below must be set to true. This is required because gRPC cannot
// differentiate between a zero-size byte slice and a nil byte slice (both
// would be serialized the same way). So the extra boolean is required.
ScriptRoot []byte `protobuf:"bytes,1,opt,name=script_root,json=scriptRoot,proto3" json:"script_root,omitempty"`
// Indicates that the above script_root is expected to be empty because this
// is a BIP-0086 key spend only commitment where only the internal key is
// committed to instead of also including a script root hash.
KeySpendOnly bool `protobuf:"varint,2,opt,name=key_spend_only,json=keySpendOnly,proto3" json:"key_spend_only,omitempty"`
}
func (x *TaprootTweakDesc) Reset() {
*x = TaprootTweakDesc{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TaprootTweakDesc) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TaprootTweakDesc) ProtoMessage() {}
func (x *TaprootTweakDesc) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[15]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TaprootTweakDesc.ProtoReflect.Descriptor instead.
func (*TaprootTweakDesc) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{15}
}
func (x *TaprootTweakDesc) GetScriptRoot() []byte {
if x != nil {
return x.ScriptRoot
}
return nil
}
func (x *TaprootTweakDesc) GetKeySpendOnly() bool {
if x != nil {
return x.KeySpendOnly
}
return false
}
type MuSig2CombineKeysRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A list of all public keys (serialized in 32-byte x-only format for v0.4.0
// and 33-byte compressed format for v1.0.0rc2!) participating in the signing
// session. The list will always be sorted lexicographically internally. This
// must include the local key which is described by the above key_loc.
AllSignerPubkeys [][]byte `protobuf:"bytes,1,rep,name=all_signer_pubkeys,json=allSignerPubkeys,proto3" json:"all_signer_pubkeys,omitempty"`
// A series of optional generic tweaks to be applied to the aggregated
// public key.
Tweaks []*TweakDesc `protobuf:"bytes,2,rep,name=tweaks,proto3" json:"tweaks,omitempty"`
// An optional taproot specific tweak that must be specified if the MuSig2
// combined key will be used as the main taproot key of a taproot output
// on-chain.
TaprootTweak *TaprootTweakDesc `protobuf:"bytes,3,opt,name=taproot_tweak,json=taprootTweak,proto3" json:"taproot_tweak,omitempty"`
// The mandatory version of the MuSig2 BIP draft to use. This is necessary to
// differentiate between the changes that were made to the BIP while this
// experimental RPC was already released. Some of those changes affect how the
// combined key and nonces are created.
Version MuSig2Version `protobuf:"varint,4,opt,name=version,proto3,enum=signrpc.MuSig2Version" json:"version,omitempty"`
}
func (x *MuSig2CombineKeysRequest) Reset() {
*x = MuSig2CombineKeysRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MuSig2CombineKeysRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MuSig2CombineKeysRequest) ProtoMessage() {}
func (x *MuSig2CombineKeysRequest) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[16]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MuSig2CombineKeysRequest.ProtoReflect.Descriptor instead.
func (*MuSig2CombineKeysRequest) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{16}
}
func (x *MuSig2CombineKeysRequest) GetAllSignerPubkeys() [][]byte {
if x != nil {
return x.AllSignerPubkeys
}
return nil
}
func (x *MuSig2CombineKeysRequest) GetTweaks() []*TweakDesc {
if x != nil {
return x.Tweaks
}
return nil
}
func (x *MuSig2CombineKeysRequest) GetTaprootTweak() *TaprootTweakDesc {
if x != nil {
return x.TaprootTweak
}
return nil
}
func (x *MuSig2CombineKeysRequest) GetVersion() MuSig2Version {
if x != nil {
return x.Version
}
return MuSig2Version_MUSIG2_VERSION_UNDEFINED
}
type MuSig2CombineKeysResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The combined public key (in the 32-byte x-only format) with all tweaks
// applied to it. If a taproot tweak is specified, this corresponds to the
// taproot key that can be put into the on-chain output.
CombinedKey []byte `protobuf:"bytes,1,opt,name=combined_key,json=combinedKey,proto3" json:"combined_key,omitempty"`
// The raw combined public key (in the 32-byte x-only format) before any tweaks
// are applied to it. If a taproot tweak is specified, this corresponds to the
// internal key that needs to be put into the witness if the script spend path
// is used.
TaprootInternalKey []byte `protobuf:"bytes,2,opt,name=taproot_internal_key,json=taprootInternalKey,proto3" json:"taproot_internal_key,omitempty"`
// The version of the MuSig2 BIP that was used to combine the keys.
Version MuSig2Version `protobuf:"varint,4,opt,name=version,proto3,enum=signrpc.MuSig2Version" json:"version,omitempty"`
}
func (x *MuSig2CombineKeysResponse) Reset() {
*x = MuSig2CombineKeysResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MuSig2CombineKeysResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MuSig2CombineKeysResponse) ProtoMessage() {}
func (x *MuSig2CombineKeysResponse) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[17]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MuSig2CombineKeysResponse.ProtoReflect.Descriptor instead.
func (*MuSig2CombineKeysResponse) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{17}
}
func (x *MuSig2CombineKeysResponse) GetCombinedKey() []byte {
if x != nil {
return x.CombinedKey
}
return nil
}
func (x *MuSig2CombineKeysResponse) GetTaprootInternalKey() []byte {
if x != nil {
return x.TaprootInternalKey
}
return nil
}
func (x *MuSig2CombineKeysResponse) GetVersion() MuSig2Version {
if x != nil {
return x.Version
}
return MuSig2Version_MUSIG2_VERSION_UNDEFINED
}
type MuSig2SessionRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The key locator that identifies which key to use for signing.
KeyLoc *KeyLocator `protobuf:"bytes,1,opt,name=key_loc,json=keyLoc,proto3" json:"key_loc,omitempty"`
// A list of all public keys (serialized in 32-byte x-only format for v0.4.0
// and 33-byte compressed format for v1.0.0rc2!) participating in the signing
// session. The list will always be sorted lexicographically internally. This
// must include the local key which is described by the above key_loc.
AllSignerPubkeys [][]byte `protobuf:"bytes,2,rep,name=all_signer_pubkeys,json=allSignerPubkeys,proto3" json:"all_signer_pubkeys,omitempty"`
// An optional list of all public nonces of other signing participants that
// might already be known.
OtherSignerPublicNonces [][]byte `protobuf:"bytes,3,rep,name=other_signer_public_nonces,json=otherSignerPublicNonces,proto3" json:"other_signer_public_nonces,omitempty"`
// A series of optional generic tweaks to be applied to the aggregated
// public key.
Tweaks []*TweakDesc `protobuf:"bytes,4,rep,name=tweaks,proto3" json:"tweaks,omitempty"`
// An optional taproot specific tweak that must be specified if the MuSig2
// combined key will be used as the main taproot key of a taproot output
// on-chain.
TaprootTweak *TaprootTweakDesc `protobuf:"bytes,5,opt,name=taproot_tweak,json=taprootTweak,proto3" json:"taproot_tweak,omitempty"`
// The mandatory version of the MuSig2 BIP draft to use. This is necessary to
// differentiate between the changes that were made to the BIP while this
// experimental RPC was already released. Some of those changes affect how the
// combined key and nonces are created.
Version MuSig2Version `protobuf:"varint,6,opt,name=version,proto3,enum=signrpc.MuSig2Version" json:"version,omitempty"`
// A set of pre generated secret local nonces to use in the musig2 session.
// This field is optional. This can be useful for protocols that need to send
// nonces ahead of time before the set of signer keys are known. This value
// MUST be 97 bytes and be the concatenation of two CSPRNG generated 32 byte
// values and local public key used for signing as specified in the key_loc
// field.
PregeneratedLocalNonce []byte `protobuf:"bytes,7,opt,name=pregenerated_local_nonce,json=pregeneratedLocalNonce,proto3" json:"pregenerated_local_nonce,omitempty"`
}
func (x *MuSig2SessionRequest) Reset() {
*x = MuSig2SessionRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MuSig2SessionRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MuSig2SessionRequest) ProtoMessage() {}
func (x *MuSig2SessionRequest) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[18]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MuSig2SessionRequest.ProtoReflect.Descriptor instead.
func (*MuSig2SessionRequest) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{18}
}
func (x *MuSig2SessionRequest) GetKeyLoc() *KeyLocator {
if x != nil {
return x.KeyLoc
}
return nil
}
func (x *MuSig2SessionRequest) GetAllSignerPubkeys() [][]byte {
if x != nil {
return x.AllSignerPubkeys
}
return nil
}
func (x *MuSig2SessionRequest) GetOtherSignerPublicNonces() [][]byte {
if x != nil {
return x.OtherSignerPublicNonces
}
return nil
}
func (x *MuSig2SessionRequest) GetTweaks() []*TweakDesc {
if x != nil {
return x.Tweaks
}
return nil
}
func (x *MuSig2SessionRequest) GetTaprootTweak() *TaprootTweakDesc {
if x != nil {
return x.TaprootTweak
}
return nil
}
func (x *MuSig2SessionRequest) GetVersion() MuSig2Version {
if x != nil {
return x.Version
}
return MuSig2Version_MUSIG2_VERSION_UNDEFINED
}
func (x *MuSig2SessionRequest) GetPregeneratedLocalNonce() []byte {
if x != nil {
return x.PregeneratedLocalNonce
}
return nil
}
type MuSig2SessionResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The unique ID that represents this signing session. A session can be used
// for producing a signature a single time. If the signing fails for any
// reason, a new session with the same participants needs to be created.
SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
// The combined public key (in the 32-byte x-only format) with all tweaks
// applied to it. If a taproot tweak is specified, this corresponds to the
// taproot key that can be put into the on-chain output.
CombinedKey []byte `protobuf:"bytes,2,opt,name=combined_key,json=combinedKey,proto3" json:"combined_key,omitempty"`
// The raw combined public key (in the 32-byte x-only format) before any tweaks
// are applied to it. If a taproot tweak is specified, this corresponds to the
// internal key that needs to be put into the witness if the script spend path
// is used.
TaprootInternalKey []byte `protobuf:"bytes,3,opt,name=taproot_internal_key,json=taprootInternalKey,proto3" json:"taproot_internal_key,omitempty"`
// The two public nonces the local signer uses, combined into a single value
// of 66 bytes. Can be split into the two 33-byte points to get the individual
// nonces.
LocalPublicNonces []byte `protobuf:"bytes,4,opt,name=local_public_nonces,json=localPublicNonces,proto3" json:"local_public_nonces,omitempty"`
// Indicates whether all nonces required to start the signing process are known
// now.
HaveAllNonces bool `protobuf:"varint,5,opt,name=have_all_nonces,json=haveAllNonces,proto3" json:"have_all_nonces,omitempty"`
// The version of the MuSig2 BIP that was used to create the session.
Version MuSig2Version `protobuf:"varint,6,opt,name=version,proto3,enum=signrpc.MuSig2Version" json:"version,omitempty"`
}
func (x *MuSig2SessionResponse) Reset() {
*x = MuSig2SessionResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[19]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MuSig2SessionResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MuSig2SessionResponse) ProtoMessage() {}
func (x *MuSig2SessionResponse) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[19]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MuSig2SessionResponse.ProtoReflect.Descriptor instead.
func (*MuSig2SessionResponse) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{19}
}
func (x *MuSig2SessionResponse) GetSessionId() []byte {
if x != nil {
return x.SessionId
}
return nil
}
func (x *MuSig2SessionResponse) GetCombinedKey() []byte {
if x != nil {
return x.CombinedKey
}
return nil
}
func (x *MuSig2SessionResponse) GetTaprootInternalKey() []byte {
if x != nil {
return x.TaprootInternalKey
}
return nil
}
func (x *MuSig2SessionResponse) GetLocalPublicNonces() []byte {
if x != nil {
return x.LocalPublicNonces
}
return nil
}
func (x *MuSig2SessionResponse) GetHaveAllNonces() bool {
if x != nil {
return x.HaveAllNonces
}
return false
}
func (x *MuSig2SessionResponse) GetVersion() MuSig2Version {
if x != nil {
return x.Version
}
return MuSig2Version_MUSIG2_VERSION_UNDEFINED
}
type MuSig2RegisterNoncesRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The unique ID of the signing session those nonces should be registered with.
SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
// A list of all public nonces of other signing participants that should be
// registered.
OtherSignerPublicNonces [][]byte `protobuf:"bytes,3,rep,name=other_signer_public_nonces,json=otherSignerPublicNonces,proto3" json:"other_signer_public_nonces,omitempty"`
}
func (x *MuSig2RegisterNoncesRequest) Reset() {
*x = MuSig2RegisterNoncesRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[20]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MuSig2RegisterNoncesRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MuSig2RegisterNoncesRequest) ProtoMessage() {}
func (x *MuSig2RegisterNoncesRequest) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[20]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MuSig2RegisterNoncesRequest.ProtoReflect.Descriptor instead.
func (*MuSig2RegisterNoncesRequest) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{20}
}
func (x *MuSig2RegisterNoncesRequest) GetSessionId() []byte {
if x != nil {
return x.SessionId
}
return nil
}
func (x *MuSig2RegisterNoncesRequest) GetOtherSignerPublicNonces() [][]byte {
if x != nil {
return x.OtherSignerPublicNonces
}
return nil
}
type MuSig2RegisterNoncesResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Indicates whether all nonces required to start the signing process are known
// now.
HaveAllNonces bool `protobuf:"varint,1,opt,name=have_all_nonces,json=haveAllNonces,proto3" json:"have_all_nonces,omitempty"`
}
func (x *MuSig2RegisterNoncesResponse) Reset() {
*x = MuSig2RegisterNoncesResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[21]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MuSig2RegisterNoncesResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MuSig2RegisterNoncesResponse) ProtoMessage() {}
func (x *MuSig2RegisterNoncesResponse) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[21]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MuSig2RegisterNoncesResponse.ProtoReflect.Descriptor instead.
func (*MuSig2RegisterNoncesResponse) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{21}
}
func (x *MuSig2RegisterNoncesResponse) GetHaveAllNonces() bool {
if x != nil {
return x.HaveAllNonces
}
return false
}
type MuSig2SignRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The unique ID of the signing session to use for signing.
SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
// The 32-byte SHA256 digest of the message to sign.
MessageDigest []byte `protobuf:"bytes,2,opt,name=message_digest,json=messageDigest,proto3" json:"message_digest,omitempty"`
// Cleanup indicates that after signing, the session state can be cleaned up,
// since another participant is going to be responsible for combining the
// partial signatures.
Cleanup bool `protobuf:"varint,3,opt,name=cleanup,proto3" json:"cleanup,omitempty"`
}
func (x *MuSig2SignRequest) Reset() {
*x = MuSig2SignRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[22]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MuSig2SignRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MuSig2SignRequest) ProtoMessage() {}
func (x *MuSig2SignRequest) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[22]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MuSig2SignRequest.ProtoReflect.Descriptor instead.
func (*MuSig2SignRequest) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{22}
}
func (x *MuSig2SignRequest) GetSessionId() []byte {
if x != nil {
return x.SessionId
}
return nil
}
func (x *MuSig2SignRequest) GetMessageDigest() []byte {
if x != nil {
return x.MessageDigest
}
return nil
}
func (x *MuSig2SignRequest) GetCleanup() bool {
if x != nil {
return x.Cleanup
}
return false
}
type MuSig2SignResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The partial signature created by the local signer.
LocalPartialSignature []byte `protobuf:"bytes,1,opt,name=local_partial_signature,json=localPartialSignature,proto3" json:"local_partial_signature,omitempty"`
}
func (x *MuSig2SignResponse) Reset() {
*x = MuSig2SignResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[23]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MuSig2SignResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MuSig2SignResponse) ProtoMessage() {}
func (x *MuSig2SignResponse) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[23]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MuSig2SignResponse.ProtoReflect.Descriptor instead.
func (*MuSig2SignResponse) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{23}
}
func (x *MuSig2SignResponse) GetLocalPartialSignature() []byte {
if x != nil {
return x.LocalPartialSignature
}
return nil
}
type MuSig2CombineSigRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The unique ID of the signing session to combine the signatures for.
SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
// The list of all other participants' partial signatures to add to the current
// session.
OtherPartialSignatures [][]byte `protobuf:"bytes,2,rep,name=other_partial_signatures,json=otherPartialSignatures,proto3" json:"other_partial_signatures,omitempty"`
}
func (x *MuSig2CombineSigRequest) Reset() {
*x = MuSig2CombineSigRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[24]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MuSig2CombineSigRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MuSig2CombineSigRequest) ProtoMessage() {}
func (x *MuSig2CombineSigRequest) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[24]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MuSig2CombineSigRequest.ProtoReflect.Descriptor instead.
func (*MuSig2CombineSigRequest) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{24}
}
func (x *MuSig2CombineSigRequest) GetSessionId() []byte {
if x != nil {
return x.SessionId
}
return nil
}
func (x *MuSig2CombineSigRequest) GetOtherPartialSignatures() [][]byte {
if x != nil {
return x.OtherPartialSignatures
}
return nil
}
type MuSig2CombineSigResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Indicates whether all partial signatures required to create a final, full
// signature are known yet. If this is true, then the final_signature field is
// set, otherwise it is empty.
HaveAllSignatures bool `protobuf:"varint,1,opt,name=have_all_signatures,json=haveAllSignatures,proto3" json:"have_all_signatures,omitempty"`
// The final, full signature that is valid for the combined public key.
FinalSignature []byte `protobuf:"bytes,2,opt,name=final_signature,json=finalSignature,proto3" json:"final_signature,omitempty"`
}
func (x *MuSig2CombineSigResponse) Reset() {
*x = MuSig2CombineSigResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[25]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MuSig2CombineSigResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MuSig2CombineSigResponse) ProtoMessage() {}
func (x *MuSig2CombineSigResponse) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[25]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MuSig2CombineSigResponse.ProtoReflect.Descriptor instead.
func (*MuSig2CombineSigResponse) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{25}
}
func (x *MuSig2CombineSigResponse) GetHaveAllSignatures() bool {
if x != nil {
return x.HaveAllSignatures
}
return false
}
func (x *MuSig2CombineSigResponse) GetFinalSignature() []byte {
if x != nil {
return x.FinalSignature
}
return nil
}
type MuSig2CleanupRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The unique ID of the signing session that should be removed/cleaned up.
SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
}
func (x *MuSig2CleanupRequest) Reset() {
*x = MuSig2CleanupRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[26]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MuSig2CleanupRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MuSig2CleanupRequest) ProtoMessage() {}
func (x *MuSig2CleanupRequest) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[26]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MuSig2CleanupRequest.ProtoReflect.Descriptor instead.
func (*MuSig2CleanupRequest) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{26}
}
func (x *MuSig2CleanupRequest) GetSessionId() []byte {
if x != nil {
return x.SessionId
}
return nil
}
type MuSig2CleanupResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *MuSig2CleanupResponse) Reset() {
*x = MuSig2CleanupResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[27]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MuSig2CleanupResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MuSig2CleanupResponse) ProtoMessage() {}
func (x *MuSig2CleanupResponse) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[27]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MuSig2CleanupResponse.ProtoReflect.Descriptor instead.
func (*MuSig2CleanupResponse) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{27}
}
var File_signrpc_signer_proto protoreflect.FileDescriptor
var file_signrpc_signer_proto_rawDesc = []byte{
0x0a, 0x14, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x22,
0x48, 0x0a, 0x0a, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1d, 0x0a,
0x0a, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,
0x05, 0x52, 0x09, 0x6b, 0x65, 0x79, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x12, 0x1b, 0x0a, 0x09,
0x6b, 0x65, 0x79, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52,
0x08, 0x6b, 0x65, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x61, 0x0a, 0x0d, 0x4b, 0x65, 0x79,
0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x61,
0x77, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x0b, 0x72, 0x61, 0x77, 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x2c,
0x0a, 0x07, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63,
0x61, 0x74, 0x6f, 0x72, 0x52, 0x06, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x22, 0x3a, 0x0a, 0x05,
0x54, 0x78, 0x4f, 0x75, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70,
0x6b, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08,
0x70, 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0xe6, 0x02, 0x0a, 0x0e, 0x53, 0x69, 0x67,
0x6e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x31, 0x0a, 0x08, 0x6b,
0x65, 0x79, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e,
0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72,
0x69, 0x70, 0x74, 0x6f, 0x72, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x12, 0x21,
0x0a, 0x0c, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x5f, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x54, 0x77, 0x65, 0x61,
0x6b, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x77, 0x65, 0x61,
0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x54,
0x77, 0x65, 0x61, 0x6b, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x61, 0x70, 0x5f, 0x74, 0x77, 0x65, 0x61,
0x6b, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x74, 0x61, 0x70, 0x54, 0x77, 0x65, 0x61,
0x6b, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x5f, 0x73, 0x63, 0x72,
0x69, 0x70, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x77, 0x69, 0x74, 0x6e, 0x65,
0x73, 0x73, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x26, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70,
0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x54, 0x78, 0x4f, 0x75, 0x74, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74,
0x12, 0x18, 0x0a, 0x07, 0x73, 0x69, 0x67, 0x68, 0x61, 0x73, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28,
0x0d, 0x52, 0x07, 0x73, 0x69, 0x67, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e,
0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52,
0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x34, 0x0a, 0x0b, 0x73,
0x69, 0x67, 0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e,
0x32, 0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d,
0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f,
0x64, 0x22, 0x96, 0x01, 0x0a, 0x07, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x12, 0x20, 0x0a,
0x0c, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x61, 0x77, 0x54, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12,
0x36, 0x0a, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x73, 0x18, 0x02, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69,
0x67, 0x6e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x52, 0x09, 0x73, 0x69,
0x67, 0x6e, 0x44, 0x65, 0x73, 0x63, 0x73, 0x12, 0x31, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x76, 0x5f,
0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e,
0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x4f, 0x75, 0x74, 0x52, 0x0b, 0x70,
0x72, 0x65, 0x76, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x22, 0x25, 0x0a, 0x08, 0x53, 0x69,
0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x61, 0x77, 0x5f, 0x73, 0x69,
0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x07, 0x72, 0x61, 0x77, 0x53, 0x69, 0x67,
0x73, 0x22, 0x46, 0x0a, 0x0b, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74,
0x12, 0x18, 0x0a, 0x07, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
0x0c, 0x52, 0x07, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x69,
0x67, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09,
0x73, 0x69, 0x67, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x4c, 0x0a, 0x0f, 0x49, 0x6e, 0x70,
0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x39, 0x0a, 0x0d,
0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x18, 0x01, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e,
0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74,
0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x22, 0xf8, 0x01, 0x0a, 0x0e, 0x53, 0x69, 0x67, 0x6e,
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73,
0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x2c, 0x0a, 0x07,
0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e,
0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74,
0x6f, 0x72, 0x52, 0x06, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x6f,
0x75, 0x62, 0x6c, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52,
0x0a, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x63,
0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x5f, 0x73, 0x69, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08,
0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x53, 0x69, 0x67, 0x12, 0x1f, 0x0a, 0x0b,
0x73, 0x63, 0x68, 0x6e, 0x6f, 0x72, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28,
0x08, 0x52, 0x0a, 0x73, 0x63, 0x68, 0x6e, 0x6f, 0x72, 0x72, 0x53, 0x69, 0x67, 0x12, 0x31, 0x0a,
0x15, 0x73, 0x63, 0x68, 0x6e, 0x6f, 0x72, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x5f, 0x74, 0x61, 0x70,
0x5f, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x73, 0x63,
0x68, 0x6e, 0x6f, 0x72, 0x72, 0x53, 0x69, 0x67, 0x54, 0x61, 0x70, 0x54, 0x77, 0x65, 0x61, 0x6b,
0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x74,
0x61, 0x67, 0x22, 0x2f, 0x0a, 0x0f, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75,
0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74,
0x75, 0x72, 0x65, 0x22, 0x92, 0x01, 0x0a, 0x10, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69,
0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73,
0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b,
0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79,
0x12, 0x24, 0x0a, 0x0e, 0x69, 0x73, 0x5f, 0x73, 0x63, 0x68, 0x6e, 0x6f, 0x72, 0x72, 0x5f, 0x73,
0x69, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x73, 0x53, 0x63, 0x68, 0x6e,
0x6f, 0x72, 0x72, 0x53, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x05, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x03, 0x74, 0x61, 0x67, 0x22, 0x29, 0x0a, 0x11, 0x56, 0x65, 0x72, 0x69,
0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x14, 0x0a,
0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61,
0x6c, 0x69, 0x64, 0x22, 0xa2, 0x01, 0x0a, 0x10, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65,
0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x65, 0x70, 0x68, 0x65,
0x6d, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x0f, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x50, 0x75, 0x62,
0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b,
0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x42, 0x02, 0x18, 0x01, 0x52, 0x06, 0x6b,
0x65, 0x79, 0x4c, 0x6f, 0x63, 0x12, 0x31, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x64, 0x65, 0x73,
0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x52,
0x07, 0x6b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x22, 0x32, 0x0a, 0x11, 0x53, 0x68, 0x61, 0x72,
0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a,
0x0a, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x09, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x22, 0x3d, 0x0a, 0x09,
0x54, 0x77, 0x65, 0x61, 0x6b, 0x44, 0x65, 0x73, 0x63, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x77, 0x65,
0x61, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x12,
0x1a, 0x0a, 0x09, 0x69, 0x73, 0x5f, 0x78, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01,
0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x58, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x59, 0x0a, 0x10, 0x54,
0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x54, 0x77, 0x65, 0x61, 0x6b, 0x44, 0x65, 0x73, 0x63, 0x12,
0x1f, 0x0a, 0x0b, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x6f, 0x6f, 0x74,
0x12, 0x24, 0x0a, 0x0e, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x6f, 0x6e,
0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6b, 0x65, 0x79, 0x53, 0x70, 0x65,
0x6e, 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0xe6, 0x01, 0x0a, 0x18, 0x4d, 0x75, 0x53, 0x69, 0x67,
0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x65,
0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52,
0x10, 0x61, 0x6c, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79,
0x73, 0x12, 0x2a, 0x0a, 0x06, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x12, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x77, 0x65, 0x61,
0x6b, 0x44, 0x65, 0x73, 0x63, 0x52, 0x06, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x73, 0x12, 0x3e, 0x0a,
0x0d, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x18, 0x03,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54,
0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x54, 0x77, 0x65, 0x61, 0x6b, 0x44, 0x65, 0x73, 0x63, 0x52,
0x0c, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x54, 0x77, 0x65, 0x61, 0x6b, 0x12, 0x30, 0x0a,
0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16,
0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x56,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22,
0xa2, 0x01, 0x0a, 0x19, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e,
0x65, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a,
0x0c, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x64, 0x4b, 0x65, 0x79,
0x12, 0x30, 0x0a, 0x14, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65,
0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12,
0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b,
0x65, 0x79, 0x12, 0x30, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20,
0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75,
0x53, 0x69, 0x67, 0x32, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x76, 0x65, 0x72,
0x73, 0x69, 0x6f, 0x6e, 0x22, 0x87, 0x03, 0x0a, 0x14, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53,
0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a,
0x07, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13,
0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61,
0x74, 0x6f, 0x72, 0x52, 0x06, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x12, 0x2c, 0x0a, 0x12, 0x61,
0x6c, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79,
0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x10, 0x61, 0x6c, 0x6c, 0x53, 0x69, 0x67, 0x6e,
0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x12, 0x3b, 0x0a, 0x1a, 0x6f, 0x74, 0x68,
0x65, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63,
0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x17, 0x6f,
0x74, 0x68, 0x65, 0x72, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63,
0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x06, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x73,
0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x54, 0x77, 0x65, 0x61, 0x6b, 0x44, 0x65, 0x73, 0x63, 0x52, 0x06, 0x74, 0x77, 0x65, 0x61,
0x6b, 0x73, 0x12, 0x3e, 0x0a, 0x0d, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x74, 0x77,
0x65, 0x61, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x69, 0x67, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x54, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x54, 0x77, 0x65, 0x61, 0x6b,
0x44, 0x65, 0x73, 0x63, 0x52, 0x0c, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x54, 0x77, 0x65,
0x61, 0x6b, 0x12, 0x30, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20,
0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75,
0x53, 0x69, 0x67, 0x32, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x76, 0x65, 0x72,
0x73, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x18, 0x70, 0x72, 0x65, 0x67, 0x65, 0x6e, 0x65, 0x72,
0x61, 0x74, 0x65, 0x64, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65,
0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x16, 0x70, 0x72, 0x65, 0x67, 0x65, 0x6e, 0x65, 0x72,
0x61, 0x74, 0x65, 0x64, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x22, 0x95,
0x02, 0x0a, 0x15, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73,
0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65,
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x62, 0x69,
0x6e, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63,
0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x74, 0x61,
0x70, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b,
0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f,
0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x13,
0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6e, 0x6f, 0x6e,
0x63, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f,
0x68, 0x61, 0x76, 0x65, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18,
0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x68, 0x61, 0x76, 0x65, 0x41, 0x6c, 0x6c, 0x4e, 0x6f,
0x6e, 0x63, 0x65, 0x73, 0x12, 0x30, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x76,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x79, 0x0a, 0x1b, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32,
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69,
0x6f, 0x6e, 0x49, 0x64, 0x12, 0x3b, 0x0a, 0x1a, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x73, 0x69,
0x67, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6e, 0x6f, 0x6e, 0x63,
0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x17, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x53,
0x69, 0x67, 0x6e, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4e, 0x6f, 0x6e, 0x63, 0x65,
0x73, 0x22, 0x46, 0x0a, 0x1c, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x52, 0x65, 0x67, 0x69, 0x73,
0x74, 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x26, 0x0a, 0x0f, 0x68, 0x61, 0x76, 0x65, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x6e, 0x6f,
0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x68, 0x61, 0x76, 0x65,
0x41, 0x6c, 0x6c, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x22, 0x73, 0x0a, 0x11, 0x4d, 0x75, 0x53,
0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d,
0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x25, 0x0a,
0x0e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x44, 0x69,
0x67, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x18,
0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x22, 0x4c,
0x0a, 0x12, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x70, 0x61,
0x72, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x61, 0x72, 0x74,
0x69, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x72, 0x0a, 0x17,
0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53, 0x69, 0x67,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69,
0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73,
0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x38, 0x0a, 0x18, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f,
0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72,
0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x16, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x50,
0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73,
0x22, 0x73, 0x0a, 0x18, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e,
0x65, 0x53, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x13,
0x68, 0x61, 0x76, 0x65, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75,
0x72, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x68, 0x61, 0x76, 0x65, 0x41,
0x6c, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x27, 0x0a, 0x0f,
0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e,
0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x35, 0x0a, 0x14, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43,
0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a,
0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x17, 0x0a, 0x15,
0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x9c, 0x01, 0x0a, 0x0a, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65,
0x74, 0x68, 0x6f, 0x64, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x49, 0x47, 0x4e, 0x5f, 0x4d, 0x45, 0x54,
0x48, 0x4f, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x56, 0x30, 0x10, 0x00,
0x12, 0x29, 0x0a, 0x25, 0x53, 0x49, 0x47, 0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f,
0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x53, 0x50, 0x45, 0x4e,
0x44, 0x5f, 0x42, 0x49, 0x50, 0x30, 0x30, 0x38, 0x36, 0x10, 0x01, 0x12, 0x21, 0x0a, 0x1d, 0x53,
0x49, 0x47, 0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f,
0x4f, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x53, 0x50, 0x45, 0x4e, 0x44, 0x10, 0x02, 0x12, 0x24,
0x0a, 0x20, 0x53, 0x49, 0x47, 0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x54, 0x41,
0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x53, 0x50, 0x45,
0x4e, 0x44, 0x10, 0x03, 0x2a, 0x62, 0x0a, 0x0d, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x56, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x18, 0x4d, 0x55, 0x53, 0x49, 0x47, 0x32, 0x5f,
0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x44, 0x45, 0x46, 0x49, 0x4e, 0x45,
0x44, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x55, 0x53, 0x49, 0x47, 0x32, 0x5f, 0x56, 0x45,
0x52, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x56, 0x30, 0x34, 0x30, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16,
0x4d, 0x55, 0x53, 0x49, 0x47, 0x32, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x56,
0x31, 0x30, 0x30, 0x52, 0x43, 0x32, 0x10, 0x02, 0x32, 0xdb, 0x06, 0x0a, 0x06, 0x53, 0x69, 0x67,
0x6e, 0x65, 0x72, 0x12, 0x34, 0x0a, 0x0d, 0x53, 0x69, 0x67, 0x6e, 0x4f, 0x75, 0x74, 0x70, 0x75,
0x74, 0x52, 0x61, 0x77, 0x12, 0x10, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53,
0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x12, 0x40, 0x0a, 0x12, 0x43, 0x6f, 0x6d,
0x70, 0x75, 0x74, 0x65, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12,
0x10, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65,
0x71, 0x1a, 0x18, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x70, 0x75,
0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x40, 0x0a, 0x0b, 0x53,
0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x17, 0x2e, 0x73, 0x69, 0x67,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69,
0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x46, 0x0a,
0x0d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19,
0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d,
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x73, 0x69, 0x67, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x48, 0x0a, 0x0f, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x53,
0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68,
0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x5a, 0x0a, 0x11, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65,
0x4b, 0x65, 0x79, 0x73, 0x12, 0x21, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d,
0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x4b, 0x65, 0x79, 0x73,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x4b,
0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x13, 0x4d,
0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69,
0x6f, 0x6e, 0x12, 0x1d, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53,
0x69, 0x67, 0x32, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x1e, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69,
0x67, 0x32, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x63, 0x0a, 0x14, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x52, 0x65, 0x67, 0x69, 0x73,
0x74, 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x24, 0x2e, 0x73, 0x69, 0x67, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74,
0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x25, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32,
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x0a, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32,
0x53, 0x69, 0x67, 0x6e, 0x12, 0x1a, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d,
0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x1b, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67,
0x32, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a,
0x10, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53, 0x69,
0x67, 0x12, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69,
0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75,
0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53, 0x69, 0x67, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0d, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32,
0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x12, 0x1d, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65,
0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f,
0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_signrpc_signer_proto_rawDescOnce sync.Once
file_signrpc_signer_proto_rawDescData = file_signrpc_signer_proto_rawDesc
)
func file_signrpc_signer_proto_rawDescGZIP() []byte {
file_signrpc_signer_proto_rawDescOnce.Do(func() {
file_signrpc_signer_proto_rawDescData = protoimpl.X.CompressGZIP(file_signrpc_signer_proto_rawDescData)
})
return file_signrpc_signer_proto_rawDescData
}
var file_signrpc_signer_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_signrpc_signer_proto_msgTypes = make([]protoimpl.MessageInfo, 28)
var file_signrpc_signer_proto_goTypes = []interface{}{
(SignMethod)(0), // 0: signrpc.SignMethod
(MuSig2Version)(0), // 1: signrpc.MuSig2Version
(*KeyLocator)(nil), // 2: signrpc.KeyLocator
(*KeyDescriptor)(nil), // 3: signrpc.KeyDescriptor
(*TxOut)(nil), // 4: signrpc.TxOut
(*SignDescriptor)(nil), // 5: signrpc.SignDescriptor
(*SignReq)(nil), // 6: signrpc.SignReq
(*SignResp)(nil), // 7: signrpc.SignResp
(*InputScript)(nil), // 8: signrpc.InputScript
(*InputScriptResp)(nil), // 9: signrpc.InputScriptResp
(*SignMessageReq)(nil), // 10: signrpc.SignMessageReq
(*SignMessageResp)(nil), // 11: signrpc.SignMessageResp
(*VerifyMessageReq)(nil), // 12: signrpc.VerifyMessageReq
(*VerifyMessageResp)(nil), // 13: signrpc.VerifyMessageResp
(*SharedKeyRequest)(nil), // 14: signrpc.SharedKeyRequest
(*SharedKeyResponse)(nil), // 15: signrpc.SharedKeyResponse
(*TweakDesc)(nil), // 16: signrpc.TweakDesc
(*TaprootTweakDesc)(nil), // 17: signrpc.TaprootTweakDesc
(*MuSig2CombineKeysRequest)(nil), // 18: signrpc.MuSig2CombineKeysRequest
(*MuSig2CombineKeysResponse)(nil), // 19: signrpc.MuSig2CombineKeysResponse
(*MuSig2SessionRequest)(nil), // 20: signrpc.MuSig2SessionRequest
(*MuSig2SessionResponse)(nil), // 21: signrpc.MuSig2SessionResponse
(*MuSig2RegisterNoncesRequest)(nil), // 22: signrpc.MuSig2RegisterNoncesRequest
(*MuSig2RegisterNoncesResponse)(nil), // 23: signrpc.MuSig2RegisterNoncesResponse
(*MuSig2SignRequest)(nil), // 24: signrpc.MuSig2SignRequest
(*MuSig2SignResponse)(nil), // 25: signrpc.MuSig2SignResponse
(*MuSig2CombineSigRequest)(nil), // 26: signrpc.MuSig2CombineSigRequest
(*MuSig2CombineSigResponse)(nil), // 27: signrpc.MuSig2CombineSigResponse
(*MuSig2CleanupRequest)(nil), // 28: signrpc.MuSig2CleanupRequest
(*MuSig2CleanupResponse)(nil), // 29: signrpc.MuSig2CleanupResponse
}
var file_signrpc_signer_proto_depIdxs = []int32{
2, // 0: signrpc.KeyDescriptor.key_loc:type_name -> signrpc.KeyLocator
3, // 1: signrpc.SignDescriptor.key_desc:type_name -> signrpc.KeyDescriptor
4, // 2: signrpc.SignDescriptor.output:type_name -> signrpc.TxOut
0, // 3: signrpc.SignDescriptor.sign_method:type_name -> signrpc.SignMethod
5, // 4: signrpc.SignReq.sign_descs:type_name -> signrpc.SignDescriptor
4, // 5: signrpc.SignReq.prev_outputs:type_name -> signrpc.TxOut
8, // 6: signrpc.InputScriptResp.input_scripts:type_name -> signrpc.InputScript
2, // 7: signrpc.SignMessageReq.key_loc:type_name -> signrpc.KeyLocator
2, // 8: signrpc.SharedKeyRequest.key_loc:type_name -> signrpc.KeyLocator
3, // 9: signrpc.SharedKeyRequest.key_desc:type_name -> signrpc.KeyDescriptor
16, // 10: signrpc.MuSig2CombineKeysRequest.tweaks:type_name -> signrpc.TweakDesc
17, // 11: signrpc.MuSig2CombineKeysRequest.taproot_tweak:type_name -> signrpc.TaprootTweakDesc
1, // 12: signrpc.MuSig2CombineKeysRequest.version:type_name -> signrpc.MuSig2Version
1, // 13: signrpc.MuSig2CombineKeysResponse.version:type_name -> signrpc.MuSig2Version
2, // 14: signrpc.MuSig2SessionRequest.key_loc:type_name -> signrpc.KeyLocator
16, // 15: signrpc.MuSig2SessionRequest.tweaks:type_name -> signrpc.TweakDesc
17, // 16: signrpc.MuSig2SessionRequest.taproot_tweak:type_name -> signrpc.TaprootTweakDesc
1, // 17: signrpc.MuSig2SessionRequest.version:type_name -> signrpc.MuSig2Version
1, // 18: signrpc.MuSig2SessionResponse.version:type_name -> signrpc.MuSig2Version
6, // 19: signrpc.Signer.SignOutputRaw:input_type -> signrpc.SignReq
6, // 20: signrpc.Signer.ComputeInputScript:input_type -> signrpc.SignReq
10, // 21: signrpc.Signer.SignMessage:input_type -> signrpc.SignMessageReq
12, // 22: signrpc.Signer.VerifyMessage:input_type -> signrpc.VerifyMessageReq
14, // 23: signrpc.Signer.DeriveSharedKey:input_type -> signrpc.SharedKeyRequest
18, // 24: signrpc.Signer.MuSig2CombineKeys:input_type -> signrpc.MuSig2CombineKeysRequest
20, // 25: signrpc.Signer.MuSig2CreateSession:input_type -> signrpc.MuSig2SessionRequest
22, // 26: signrpc.Signer.MuSig2RegisterNonces:input_type -> signrpc.MuSig2RegisterNoncesRequest
24, // 27: signrpc.Signer.MuSig2Sign:input_type -> signrpc.MuSig2SignRequest
26, // 28: signrpc.Signer.MuSig2CombineSig:input_type -> signrpc.MuSig2CombineSigRequest
28, // 29: signrpc.Signer.MuSig2Cleanup:input_type -> signrpc.MuSig2CleanupRequest
7, // 30: signrpc.Signer.SignOutputRaw:output_type -> signrpc.SignResp
9, // 31: signrpc.Signer.ComputeInputScript:output_type -> signrpc.InputScriptResp
11, // 32: signrpc.Signer.SignMessage:output_type -> signrpc.SignMessageResp
13, // 33: signrpc.Signer.VerifyMessage:output_type -> signrpc.VerifyMessageResp
15, // 34: signrpc.Signer.DeriveSharedKey:output_type -> signrpc.SharedKeyResponse
19, // 35: signrpc.Signer.MuSig2CombineKeys:output_type -> signrpc.MuSig2CombineKeysResponse
21, // 36: signrpc.Signer.MuSig2CreateSession:output_type -> signrpc.MuSig2SessionResponse
23, // 37: signrpc.Signer.MuSig2RegisterNonces:output_type -> signrpc.MuSig2RegisterNoncesResponse
25, // 38: signrpc.Signer.MuSig2Sign:output_type -> signrpc.MuSig2SignResponse
27, // 39: signrpc.Signer.MuSig2CombineSig:output_type -> signrpc.MuSig2CombineSigResponse
29, // 40: signrpc.Signer.MuSig2Cleanup:output_type -> signrpc.MuSig2CleanupResponse
30, // [30:41] is the sub-list for method output_type
19, // [19:30] is the sub-list for method input_type
19, // [19:19] is the sub-list for extension type_name
19, // [19:19] is the sub-list for extension extendee
0, // [0:19] is the sub-list for field type_name
}
func init() { file_signrpc_signer_proto_init() }
func file_signrpc_signer_proto_init() {
if File_signrpc_signer_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_signrpc_signer_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*KeyLocator); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*KeyDescriptor); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TxOut); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SignDescriptor); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SignReq); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SignResp); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*InputScript); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*InputScriptResp); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SignMessageReq); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SignMessageResp); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*VerifyMessageReq); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*VerifyMessageResp); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SharedKeyRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SharedKeyResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TweakDesc); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TaprootTweakDesc); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MuSig2CombineKeysRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MuSig2CombineKeysResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MuSig2SessionRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MuSig2SessionResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MuSig2RegisterNoncesRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MuSig2RegisterNoncesResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MuSig2SignRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MuSig2SignResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MuSig2CombineSigRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MuSig2CombineSigResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MuSig2CleanupRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MuSig2CleanupResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_signrpc_signer_proto_rawDesc,
NumEnums: 2,
NumMessages: 28,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_signrpc_signer_proto_goTypes,
DependencyIndexes: file_signrpc_signer_proto_depIdxs,
EnumInfos: file_signrpc_signer_proto_enumTypes,
MessageInfos: file_signrpc_signer_proto_msgTypes,
}.Build()
File_signrpc_signer_proto = out.File
file_signrpc_signer_proto_rawDesc = nil
file_signrpc_signer_proto_goTypes = nil
file_signrpc_signer_proto_depIdxs = nil
}
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: signrpc/signer.proto
/*
Package signrpc is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package signrpc
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
func request_Signer_SignOutputRaw_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SignReq
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.SignOutputRaw(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Signer_SignOutputRaw_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SignReq
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.SignOutputRaw(ctx, &protoReq)
return msg, metadata, err
}
func request_Signer_ComputeInputScript_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SignReq
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ComputeInputScript(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Signer_ComputeInputScript_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SignReq
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ComputeInputScript(ctx, &protoReq)
return msg, metadata, err
}
func request_Signer_SignMessage_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SignMessageReq
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.SignMessage(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Signer_SignMessage_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SignMessageReq
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.SignMessage(ctx, &protoReq)
return msg, metadata, err
}
func request_Signer_VerifyMessage_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq VerifyMessageReq
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.VerifyMessage(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Signer_VerifyMessage_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq VerifyMessageReq
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.VerifyMessage(ctx, &protoReq)
return msg, metadata, err
}
func request_Signer_DeriveSharedKey_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SharedKeyRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.DeriveSharedKey(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Signer_DeriveSharedKey_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SharedKeyRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.DeriveSharedKey(ctx, &protoReq)
return msg, metadata, err
}
func request_Signer_MuSig2CombineKeys_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq MuSig2CombineKeysRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.MuSig2CombineKeys(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Signer_MuSig2CombineKeys_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq MuSig2CombineKeysRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.MuSig2CombineKeys(ctx, &protoReq)
return msg, metadata, err
}
func request_Signer_MuSig2CreateSession_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq MuSig2SessionRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.MuSig2CreateSession(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Signer_MuSig2CreateSession_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq MuSig2SessionRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.MuSig2CreateSession(ctx, &protoReq)
return msg, metadata, err
}
func request_Signer_MuSig2RegisterNonces_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq MuSig2RegisterNoncesRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.MuSig2RegisterNonces(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Signer_MuSig2RegisterNonces_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq MuSig2RegisterNoncesRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.MuSig2RegisterNonces(ctx, &protoReq)
return msg, metadata, err
}
func request_Signer_MuSig2Sign_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq MuSig2SignRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.MuSig2Sign(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Signer_MuSig2Sign_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq MuSig2SignRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.MuSig2Sign(ctx, &protoReq)
return msg, metadata, err
}
func request_Signer_MuSig2CombineSig_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq MuSig2CombineSigRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.MuSig2CombineSig(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Signer_MuSig2CombineSig_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq MuSig2CombineSigRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.MuSig2CombineSig(ctx, &protoReq)
return msg, metadata, err
}
func request_Signer_MuSig2Cleanup_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq MuSig2CleanupRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.MuSig2Cleanup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Signer_MuSig2Cleanup_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq MuSig2CleanupRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.MuSig2Cleanup(ctx, &protoReq)
return msg, metadata, err
}
// RegisterSignerHandlerServer registers the http handlers for service Signer to "mux".
// UnaryRPC :call SignerServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterSignerHandlerFromEndpoint instead.
func RegisterSignerHandlerServer(ctx context.Context, mux *runtime.ServeMux, server SignerServer) error {
mux.Handle("POST", pattern_Signer_SignOutputRaw_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/SignOutputRaw", runtime.WithHTTPPathPattern("/v2/signer/signraw"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Signer_SignOutputRaw_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_SignOutputRaw_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Signer_ComputeInputScript_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/ComputeInputScript", runtime.WithHTTPPathPattern("/v2/signer/inputscript"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Signer_ComputeInputScript_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_ComputeInputScript_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Signer_SignMessage_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/SignMessage", runtime.WithHTTPPathPattern("/v2/signer/signmessage"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Signer_SignMessage_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_SignMessage_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Signer_VerifyMessage_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/VerifyMessage", runtime.WithHTTPPathPattern("/v2/signer/verifymessage"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Signer_VerifyMessage_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_VerifyMessage_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Signer_DeriveSharedKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/DeriveSharedKey", runtime.WithHTTPPathPattern("/v2/signer/sharedkey"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Signer_DeriveSharedKey_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_DeriveSharedKey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Signer_MuSig2CombineKeys_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/MuSig2CombineKeys", runtime.WithHTTPPathPattern("/v2/signer/musig2/combinekeys"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Signer_MuSig2CombineKeys_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_MuSig2CombineKeys_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Signer_MuSig2CreateSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/MuSig2CreateSession", runtime.WithHTTPPathPattern("/v2/signer/musig2/createsession"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Signer_MuSig2CreateSession_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_MuSig2CreateSession_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Signer_MuSig2RegisterNonces_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/MuSig2RegisterNonces", runtime.WithHTTPPathPattern("/v2/signer/musig2/registernonces"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Signer_MuSig2RegisterNonces_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_MuSig2RegisterNonces_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Signer_MuSig2Sign_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/MuSig2Sign", runtime.WithHTTPPathPattern("/v2/signer/musig2/sign"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Signer_MuSig2Sign_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_MuSig2Sign_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Signer_MuSig2CombineSig_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/MuSig2CombineSig", runtime.WithHTTPPathPattern("/v2/signer/musig2/combinesig"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Signer_MuSig2CombineSig_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_MuSig2CombineSig_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Signer_MuSig2Cleanup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/MuSig2Cleanup", runtime.WithHTTPPathPattern("/v2/signer/musig2/cleanup"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Signer_MuSig2Cleanup_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_MuSig2Cleanup_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterSignerHandlerFromEndpoint is same as RegisterSignerHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterSignerHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterSignerHandler(ctx, mux, conn)
}
// RegisterSignerHandler registers the http handlers for service Signer to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterSignerHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterSignerHandlerClient(ctx, mux, NewSignerClient(conn))
}
// RegisterSignerHandlerClient registers the http handlers for service Signer
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "SignerClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "SignerClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "SignerClient" to call the correct interceptors.
func RegisterSignerHandlerClient(ctx context.Context, mux *runtime.ServeMux, client SignerClient) error {
mux.Handle("POST", pattern_Signer_SignOutputRaw_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/SignOutputRaw", runtime.WithHTTPPathPattern("/v2/signer/signraw"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Signer_SignOutputRaw_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_SignOutputRaw_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Signer_ComputeInputScript_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/ComputeInputScript", runtime.WithHTTPPathPattern("/v2/signer/inputscript"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Signer_ComputeInputScript_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_ComputeInputScript_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Signer_SignMessage_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/SignMessage", runtime.WithHTTPPathPattern("/v2/signer/signmessage"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Signer_SignMessage_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_SignMessage_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Signer_VerifyMessage_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/VerifyMessage", runtime.WithHTTPPathPattern("/v2/signer/verifymessage"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Signer_VerifyMessage_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_VerifyMessage_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Signer_DeriveSharedKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/DeriveSharedKey", runtime.WithHTTPPathPattern("/v2/signer/sharedkey"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Signer_DeriveSharedKey_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_DeriveSharedKey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Signer_MuSig2CombineKeys_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/MuSig2CombineKeys", runtime.WithHTTPPathPattern("/v2/signer/musig2/combinekeys"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Signer_MuSig2CombineKeys_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_MuSig2CombineKeys_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Signer_MuSig2CreateSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/MuSig2CreateSession", runtime.WithHTTPPathPattern("/v2/signer/musig2/createsession"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Signer_MuSig2CreateSession_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_MuSig2CreateSession_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Signer_MuSig2RegisterNonces_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/MuSig2RegisterNonces", runtime.WithHTTPPathPattern("/v2/signer/musig2/registernonces"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Signer_MuSig2RegisterNonces_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_MuSig2RegisterNonces_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Signer_MuSig2Sign_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/MuSig2Sign", runtime.WithHTTPPathPattern("/v2/signer/musig2/sign"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Signer_MuSig2Sign_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_MuSig2Sign_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Signer_MuSig2CombineSig_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/MuSig2CombineSig", runtime.WithHTTPPathPattern("/v2/signer/musig2/combinesig"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Signer_MuSig2CombineSig_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_MuSig2CombineSig_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_Signer_MuSig2Cleanup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/MuSig2Cleanup", runtime.WithHTTPPathPattern("/v2/signer/musig2/cleanup"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Signer_MuSig2Cleanup_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_MuSig2Cleanup_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_Signer_SignOutputRaw_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "signer", "signraw"}, ""))
pattern_Signer_ComputeInputScript_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "signer", "inputscript"}, ""))
pattern_Signer_SignMessage_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "signer", "signmessage"}, ""))
pattern_Signer_VerifyMessage_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "signer", "verifymessage"}, ""))
pattern_Signer_DeriveSharedKey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "signer", "sharedkey"}, ""))
pattern_Signer_MuSig2CombineKeys_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "combinekeys"}, ""))
pattern_Signer_MuSig2CreateSession_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "createsession"}, ""))
pattern_Signer_MuSig2RegisterNonces_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "registernonces"}, ""))
pattern_Signer_MuSig2Sign_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "sign"}, ""))
pattern_Signer_MuSig2CombineSig_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "combinesig"}, ""))
pattern_Signer_MuSig2Cleanup_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "cleanup"}, ""))
)
var (
forward_Signer_SignOutputRaw_0 = runtime.ForwardResponseMessage
forward_Signer_ComputeInputScript_0 = runtime.ForwardResponseMessage
forward_Signer_SignMessage_0 = runtime.ForwardResponseMessage
forward_Signer_VerifyMessage_0 = runtime.ForwardResponseMessage
forward_Signer_DeriveSharedKey_0 = runtime.ForwardResponseMessage
forward_Signer_MuSig2CombineKeys_0 = runtime.ForwardResponseMessage
forward_Signer_MuSig2CreateSession_0 = runtime.ForwardResponseMessage
forward_Signer_MuSig2RegisterNonces_0 = runtime.ForwardResponseMessage
forward_Signer_MuSig2Sign_0 = runtime.ForwardResponseMessage
forward_Signer_MuSig2CombineSig_0 = runtime.ForwardResponseMessage
forward_Signer_MuSig2Cleanup_0 = runtime.ForwardResponseMessage
)
// Code generated by falafel 0.9.2. DO NOT EDIT.
// source: signer.proto
package signrpc
import (
"context"
gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protojson"
)
func RegisterSignerJSONCallbacks(registry map[string]func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error))) {
marshaler := &gateway.JSONPb{
MarshalOptions: protojson.MarshalOptions{
UseProtoNames: true,
EmitUnpopulated: true,
},
}
registry["signrpc.Signer.SignOutputRaw"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SignReq{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewSignerClient(conn)
resp, err := client.SignOutputRaw(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["signrpc.Signer.ComputeInputScript"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SignReq{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewSignerClient(conn)
resp, err := client.ComputeInputScript(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["signrpc.Signer.SignMessage"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SignMessageReq{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewSignerClient(conn)
resp, err := client.SignMessage(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["signrpc.Signer.VerifyMessage"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &VerifyMessageReq{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewSignerClient(conn)
resp, err := client.VerifyMessage(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["signrpc.Signer.DeriveSharedKey"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SharedKeyRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewSignerClient(conn)
resp, err := client.DeriveSharedKey(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["signrpc.Signer.MuSig2CombineKeys"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &MuSig2CombineKeysRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewSignerClient(conn)
resp, err := client.MuSig2CombineKeys(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["signrpc.Signer.MuSig2CreateSession"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &MuSig2SessionRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewSignerClient(conn)
resp, err := client.MuSig2CreateSession(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["signrpc.Signer.MuSig2RegisterNonces"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &MuSig2RegisterNoncesRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewSignerClient(conn)
resp, err := client.MuSig2RegisterNonces(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["signrpc.Signer.MuSig2Sign"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &MuSig2SignRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewSignerClient(conn)
resp, err := client.MuSig2Sign(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["signrpc.Signer.MuSig2CombineSig"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &MuSig2CombineSigRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewSignerClient(conn)
resp, err := client.MuSig2CombineSig(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["signrpc.Signer.MuSig2Cleanup"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &MuSig2CleanupRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewSignerClient(conn)
resp, err := client.MuSig2Cleanup(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package signrpc
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// SignerClient is the client API for Signer service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type SignerClient interface {
// SignOutputRaw is a method that can be used to generated a signature for a
// set of inputs/outputs to a transaction. Each request specifies details
// concerning how the outputs should be signed, which keys they should be
// signed with, and also any optional tweaks. The return value is a fixed
// 64-byte signature (the same format as we use on the wire in Lightning).
//
// If we are unable to sign using the specified keys, then an error will be
// returned.
SignOutputRaw(ctx context.Context, in *SignReq, opts ...grpc.CallOption) (*SignResp, error)
// ComputeInputScript generates a complete InputIndex for the passed
// transaction with the signature as defined within the passed SignDescriptor.
// This method should be capable of generating the proper input script for both
// regular p2wkh/p2tr outputs and p2wkh outputs nested within a regular p2sh
// output.
//
// Note that when using this method to sign inputs belonging to the wallet,
// the only items of the SignDescriptor that need to be populated are pkScript
// in the TxOut field, the value in that same field, and finally the input
// index.
ComputeInputScript(ctx context.Context, in *SignReq, opts ...grpc.CallOption) (*InputScriptResp, error)
// SignMessage signs a message with the key specified in the key locator. The
// returned signature is fixed-size LN wire format encoded.
//
// The main difference to SignMessage in the main RPC is that a specific key is
// used to sign the message instead of the node identity private key.
SignMessage(ctx context.Context, in *SignMessageReq, opts ...grpc.CallOption) (*SignMessageResp, error)
// VerifyMessage verifies a signature over a message using the public key
// provided. The signature must be fixed-size LN wire format encoded.
//
// The main difference to VerifyMessage in the main RPC is that the public key
// used to sign the message does not have to be a node known to the network.
VerifyMessage(ctx context.Context, in *VerifyMessageReq, opts ...grpc.CallOption) (*VerifyMessageResp, error)
// DeriveSharedKey returns a shared secret key by performing Diffie-Hellman key
// derivation between the ephemeral public key in the request and the node's
// key specified in the key_desc parameter. Either a key locator or a raw
// public key is expected in the key_desc, if neither is supplied, defaults to
// the node's identity private key:
// P_shared = privKeyNode * ephemeralPubkey
// The resulting shared public key is serialized in the compressed format and
// hashed with sha256, resulting in the final key length of 256bit.
DeriveSharedKey(ctx context.Context, in *SharedKeyRequest, opts ...grpc.CallOption) (*SharedKeyResponse, error)
// MuSig2CombineKeys (experimental!) is a stateless helper RPC that can be used
// to calculate the combined MuSig2 public key from a list of all participating
// signers' public keys. This RPC is completely stateless and deterministic and
// does not create any signing session. It can be used to determine the Taproot
// public key that should be put in an on-chain output once all public keys are
// known. A signing session is only needed later when that output should be
// _spent_ again.
//
// NOTE: The MuSig2 BIP is not final yet and therefore this API must be
// considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
// releases. Backward compatibility is not guaranteed!
MuSig2CombineKeys(ctx context.Context, in *MuSig2CombineKeysRequest, opts ...grpc.CallOption) (*MuSig2CombineKeysResponse, error)
// MuSig2CreateSession (experimental!) creates a new MuSig2 signing session
// using the local key identified by the key locator. The complete list of all
// public keys of all signing parties must be provided, including the public
// key of the local signing key. If nonces of other parties are already known,
// they can be submitted as well to reduce the number of RPC calls necessary
// later on.
//
// NOTE: The MuSig2 BIP is not final yet and therefore this API must be
// considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
// releases. Backward compatibility is not guaranteed!
MuSig2CreateSession(ctx context.Context, in *MuSig2SessionRequest, opts ...grpc.CallOption) (*MuSig2SessionResponse, error)
// MuSig2RegisterNonces (experimental!) registers one or more public nonces of
// other signing participants for a session identified by its ID. This RPC can
// be called multiple times until all nonces are registered.
//
// NOTE: The MuSig2 BIP is not final yet and therefore this API must be
// considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
// releases. Backward compatibility is not guaranteed!
MuSig2RegisterNonces(ctx context.Context, in *MuSig2RegisterNoncesRequest, opts ...grpc.CallOption) (*MuSig2RegisterNoncesResponse, error)
// MuSig2Sign (experimental!) creates a partial signature using the local
// signing key that was specified when the session was created. This can only
// be called when all public nonces of all participants are known and have been
// registered with the session. If this node isn't responsible for combining
// all the partial signatures, then the cleanup flag should be set, indicating
// that the session can be removed from memory once the signature was produced.
//
// NOTE: The MuSig2 BIP is not final yet and therefore this API must be
// considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
// releases. Backward compatibility is not guaranteed!
MuSig2Sign(ctx context.Context, in *MuSig2SignRequest, opts ...grpc.CallOption) (*MuSig2SignResponse, error)
// MuSig2CombineSig (experimental!) combines the given partial signature(s)
// with the local one, if it already exists. Once a partial signature of all
// participants is registered, the final signature will be combined and
// returned.
//
// NOTE: The MuSig2 BIP is not final yet and therefore this API must be
// considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
// releases. Backward compatibility is not guaranteed!
MuSig2CombineSig(ctx context.Context, in *MuSig2CombineSigRequest, opts ...grpc.CallOption) (*MuSig2CombineSigResponse, error)
// MuSig2Cleanup (experimental!) allows a caller to clean up a session early in
// cases where it's obvious that the signing session won't succeed and the
// resources can be released.
//
// NOTE: The MuSig2 BIP is not final yet and therefore this API must be
// considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
// releases. Backward compatibility is not guaranteed!
MuSig2Cleanup(ctx context.Context, in *MuSig2CleanupRequest, opts ...grpc.CallOption) (*MuSig2CleanupResponse, error)
}
type signerClient struct {
cc grpc.ClientConnInterface
}
func NewSignerClient(cc grpc.ClientConnInterface) SignerClient {
return &signerClient{cc}
}
func (c *signerClient) SignOutputRaw(ctx context.Context, in *SignReq, opts ...grpc.CallOption) (*SignResp, error) {
out := new(SignResp)
err := c.cc.Invoke(ctx, "/signrpc.Signer/SignOutputRaw", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *signerClient) ComputeInputScript(ctx context.Context, in *SignReq, opts ...grpc.CallOption) (*InputScriptResp, error) {
out := new(InputScriptResp)
err := c.cc.Invoke(ctx, "/signrpc.Signer/ComputeInputScript", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *signerClient) SignMessage(ctx context.Context, in *SignMessageReq, opts ...grpc.CallOption) (*SignMessageResp, error) {
out := new(SignMessageResp)
err := c.cc.Invoke(ctx, "/signrpc.Signer/SignMessage", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *signerClient) VerifyMessage(ctx context.Context, in *VerifyMessageReq, opts ...grpc.CallOption) (*VerifyMessageResp, error) {
out := new(VerifyMessageResp)
err := c.cc.Invoke(ctx, "/signrpc.Signer/VerifyMessage", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *signerClient) DeriveSharedKey(ctx context.Context, in *SharedKeyRequest, opts ...grpc.CallOption) (*SharedKeyResponse, error) {
out := new(SharedKeyResponse)
err := c.cc.Invoke(ctx, "/signrpc.Signer/DeriveSharedKey", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *signerClient) MuSig2CombineKeys(ctx context.Context, in *MuSig2CombineKeysRequest, opts ...grpc.CallOption) (*MuSig2CombineKeysResponse, error) {
out := new(MuSig2CombineKeysResponse)
err := c.cc.Invoke(ctx, "/signrpc.Signer/MuSig2CombineKeys", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *signerClient) MuSig2CreateSession(ctx context.Context, in *MuSig2SessionRequest, opts ...grpc.CallOption) (*MuSig2SessionResponse, error) {
out := new(MuSig2SessionResponse)
err := c.cc.Invoke(ctx, "/signrpc.Signer/MuSig2CreateSession", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *signerClient) MuSig2RegisterNonces(ctx context.Context, in *MuSig2RegisterNoncesRequest, opts ...grpc.CallOption) (*MuSig2RegisterNoncesResponse, error) {
out := new(MuSig2RegisterNoncesResponse)
err := c.cc.Invoke(ctx, "/signrpc.Signer/MuSig2RegisterNonces", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *signerClient) MuSig2Sign(ctx context.Context, in *MuSig2SignRequest, opts ...grpc.CallOption) (*MuSig2SignResponse, error) {
out := new(MuSig2SignResponse)
err := c.cc.Invoke(ctx, "/signrpc.Signer/MuSig2Sign", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *signerClient) MuSig2CombineSig(ctx context.Context, in *MuSig2CombineSigRequest, opts ...grpc.CallOption) (*MuSig2CombineSigResponse, error) {
out := new(MuSig2CombineSigResponse)
err := c.cc.Invoke(ctx, "/signrpc.Signer/MuSig2CombineSig", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *signerClient) MuSig2Cleanup(ctx context.Context, in *MuSig2CleanupRequest, opts ...grpc.CallOption) (*MuSig2CleanupResponse, error) {
out := new(MuSig2CleanupResponse)
err := c.cc.Invoke(ctx, "/signrpc.Signer/MuSig2Cleanup", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// SignerServer is the server API for Signer service.
// All implementations must embed UnimplementedSignerServer
// for forward compatibility
type SignerServer interface {
// SignOutputRaw is a method that can be used to generated a signature for a
// set of inputs/outputs to a transaction. Each request specifies details
// concerning how the outputs should be signed, which keys they should be
// signed with, and also any optional tweaks. The return value is a fixed
// 64-byte signature (the same format as we use on the wire in Lightning).
//
// If we are unable to sign using the specified keys, then an error will be
// returned.
SignOutputRaw(context.Context, *SignReq) (*SignResp, error)
// ComputeInputScript generates a complete InputIndex for the passed
// transaction with the signature as defined within the passed SignDescriptor.
// This method should be capable of generating the proper input script for both
// regular p2wkh/p2tr outputs and p2wkh outputs nested within a regular p2sh
// output.
//
// Note that when using this method to sign inputs belonging to the wallet,
// the only items of the SignDescriptor that need to be populated are pkScript
// in the TxOut field, the value in that same field, and finally the input
// index.
ComputeInputScript(context.Context, *SignReq) (*InputScriptResp, error)
// SignMessage signs a message with the key specified in the key locator. The
// returned signature is fixed-size LN wire format encoded.
//
// The main difference to SignMessage in the main RPC is that a specific key is
// used to sign the message instead of the node identity private key.
SignMessage(context.Context, *SignMessageReq) (*SignMessageResp, error)
// VerifyMessage verifies a signature over a message using the public key
// provided. The signature must be fixed-size LN wire format encoded.
//
// The main difference to VerifyMessage in the main RPC is that the public key
// used to sign the message does not have to be a node known to the network.
VerifyMessage(context.Context, *VerifyMessageReq) (*VerifyMessageResp, error)
// DeriveSharedKey returns a shared secret key by performing Diffie-Hellman key
// derivation between the ephemeral public key in the request and the node's
// key specified in the key_desc parameter. Either a key locator or a raw
// public key is expected in the key_desc, if neither is supplied, defaults to
// the node's identity private key:
// P_shared = privKeyNode * ephemeralPubkey
// The resulting shared public key is serialized in the compressed format and
// hashed with sha256, resulting in the final key length of 256bit.
DeriveSharedKey(context.Context, *SharedKeyRequest) (*SharedKeyResponse, error)
// MuSig2CombineKeys (experimental!) is a stateless helper RPC that can be used
// to calculate the combined MuSig2 public key from a list of all participating
// signers' public keys. This RPC is completely stateless and deterministic and
// does not create any signing session. It can be used to determine the Taproot
// public key that should be put in an on-chain output once all public keys are
// known. A signing session is only needed later when that output should be
// _spent_ again.
//
// NOTE: The MuSig2 BIP is not final yet and therefore this API must be
// considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
// releases. Backward compatibility is not guaranteed!
MuSig2CombineKeys(context.Context, *MuSig2CombineKeysRequest) (*MuSig2CombineKeysResponse, error)
// MuSig2CreateSession (experimental!) creates a new MuSig2 signing session
// using the local key identified by the key locator. The complete list of all
// public keys of all signing parties must be provided, including the public
// key of the local signing key. If nonces of other parties are already known,
// they can be submitted as well to reduce the number of RPC calls necessary
// later on.
//
// NOTE: The MuSig2 BIP is not final yet and therefore this API must be
// considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
// releases. Backward compatibility is not guaranteed!
MuSig2CreateSession(context.Context, *MuSig2SessionRequest) (*MuSig2SessionResponse, error)
// MuSig2RegisterNonces (experimental!) registers one or more public nonces of
// other signing participants for a session identified by its ID. This RPC can
// be called multiple times until all nonces are registered.
//
// NOTE: The MuSig2 BIP is not final yet and therefore this API must be
// considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
// releases. Backward compatibility is not guaranteed!
MuSig2RegisterNonces(context.Context, *MuSig2RegisterNoncesRequest) (*MuSig2RegisterNoncesResponse, error)
// MuSig2Sign (experimental!) creates a partial signature using the local
// signing key that was specified when the session was created. This can only
// be called when all public nonces of all participants are known and have been
// registered with the session. If this node isn't responsible for combining
// all the partial signatures, then the cleanup flag should be set, indicating
// that the session can be removed from memory once the signature was produced.
//
// NOTE: The MuSig2 BIP is not final yet and therefore this API must be
// considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
// releases. Backward compatibility is not guaranteed!
MuSig2Sign(context.Context, *MuSig2SignRequest) (*MuSig2SignResponse, error)
// MuSig2CombineSig (experimental!) combines the given partial signature(s)
// with the local one, if it already exists. Once a partial signature of all
// participants is registered, the final signature will be combined and
// returned.
//
// NOTE: The MuSig2 BIP is not final yet and therefore this API must be
// considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
// releases. Backward compatibility is not guaranteed!
MuSig2CombineSig(context.Context, *MuSig2CombineSigRequest) (*MuSig2CombineSigResponse, error)
// MuSig2Cleanup (experimental!) allows a caller to clean up a session early in
// cases where it's obvious that the signing session won't succeed and the
// resources can be released.
//
// NOTE: The MuSig2 BIP is not final yet and therefore this API must be
// considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
// releases. Backward compatibility is not guaranteed!
MuSig2Cleanup(context.Context, *MuSig2CleanupRequest) (*MuSig2CleanupResponse, error)
mustEmbedUnimplementedSignerServer()
}
// UnimplementedSignerServer must be embedded to have forward compatible implementations.
type UnimplementedSignerServer struct {
}
func (UnimplementedSignerServer) SignOutputRaw(context.Context, *SignReq) (*SignResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method SignOutputRaw not implemented")
}
func (UnimplementedSignerServer) ComputeInputScript(context.Context, *SignReq) (*InputScriptResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ComputeInputScript not implemented")
}
func (UnimplementedSignerServer) SignMessage(context.Context, *SignMessageReq) (*SignMessageResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method SignMessage not implemented")
}
func (UnimplementedSignerServer) VerifyMessage(context.Context, *VerifyMessageReq) (*VerifyMessageResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method VerifyMessage not implemented")
}
func (UnimplementedSignerServer) DeriveSharedKey(context.Context, *SharedKeyRequest) (*SharedKeyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeriveSharedKey not implemented")
}
func (UnimplementedSignerServer) MuSig2CombineKeys(context.Context, *MuSig2CombineKeysRequest) (*MuSig2CombineKeysResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MuSig2CombineKeys not implemented")
}
func (UnimplementedSignerServer) MuSig2CreateSession(context.Context, *MuSig2SessionRequest) (*MuSig2SessionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MuSig2CreateSession not implemented")
}
func (UnimplementedSignerServer) MuSig2RegisterNonces(context.Context, *MuSig2RegisterNoncesRequest) (*MuSig2RegisterNoncesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MuSig2RegisterNonces not implemented")
}
func (UnimplementedSignerServer) MuSig2Sign(context.Context, *MuSig2SignRequest) (*MuSig2SignResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MuSig2Sign not implemented")
}
func (UnimplementedSignerServer) MuSig2CombineSig(context.Context, *MuSig2CombineSigRequest) (*MuSig2CombineSigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MuSig2CombineSig not implemented")
}
func (UnimplementedSignerServer) MuSig2Cleanup(context.Context, *MuSig2CleanupRequest) (*MuSig2CleanupResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MuSig2Cleanup not implemented")
}
func (UnimplementedSignerServer) mustEmbedUnimplementedSignerServer() {}
// UnsafeSignerServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to SignerServer will
// result in compilation errors.
type UnsafeSignerServer interface {
mustEmbedUnimplementedSignerServer()
}
func RegisterSignerServer(s grpc.ServiceRegistrar, srv SignerServer) {
s.RegisterService(&Signer_ServiceDesc, srv)
}
func _Signer_SignOutputRaw_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SignReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SignerServer).SignOutputRaw(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/signrpc.Signer/SignOutputRaw",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SignerServer).SignOutputRaw(ctx, req.(*SignReq))
}
return interceptor(ctx, in, info, handler)
}
func _Signer_ComputeInputScript_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SignReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SignerServer).ComputeInputScript(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/signrpc.Signer/ComputeInputScript",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SignerServer).ComputeInputScript(ctx, req.(*SignReq))
}
return interceptor(ctx, in, info, handler)
}
func _Signer_SignMessage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SignMessageReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SignerServer).SignMessage(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/signrpc.Signer/SignMessage",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SignerServer).SignMessage(ctx, req.(*SignMessageReq))
}
return interceptor(ctx, in, info, handler)
}
func _Signer_VerifyMessage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(VerifyMessageReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SignerServer).VerifyMessage(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/signrpc.Signer/VerifyMessage",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SignerServer).VerifyMessage(ctx, req.(*VerifyMessageReq))
}
return interceptor(ctx, in, info, handler)
}
func _Signer_DeriveSharedKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SharedKeyRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SignerServer).DeriveSharedKey(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/signrpc.Signer/DeriveSharedKey",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SignerServer).DeriveSharedKey(ctx, req.(*SharedKeyRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Signer_MuSig2CombineKeys_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(MuSig2CombineKeysRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SignerServer).MuSig2CombineKeys(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/signrpc.Signer/MuSig2CombineKeys",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SignerServer).MuSig2CombineKeys(ctx, req.(*MuSig2CombineKeysRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Signer_MuSig2CreateSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(MuSig2SessionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SignerServer).MuSig2CreateSession(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/signrpc.Signer/MuSig2CreateSession",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SignerServer).MuSig2CreateSession(ctx, req.(*MuSig2SessionRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Signer_MuSig2RegisterNonces_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(MuSig2RegisterNoncesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SignerServer).MuSig2RegisterNonces(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/signrpc.Signer/MuSig2RegisterNonces",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SignerServer).MuSig2RegisterNonces(ctx, req.(*MuSig2RegisterNoncesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Signer_MuSig2Sign_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(MuSig2SignRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SignerServer).MuSig2Sign(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/signrpc.Signer/MuSig2Sign",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SignerServer).MuSig2Sign(ctx, req.(*MuSig2SignRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Signer_MuSig2CombineSig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(MuSig2CombineSigRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SignerServer).MuSig2CombineSig(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/signrpc.Signer/MuSig2CombineSig",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SignerServer).MuSig2CombineSig(ctx, req.(*MuSig2CombineSigRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Signer_MuSig2Cleanup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(MuSig2CleanupRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SignerServer).MuSig2Cleanup(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/signrpc.Signer/MuSig2Cleanup",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SignerServer).MuSig2Cleanup(ctx, req.(*MuSig2CleanupRequest))
}
return interceptor(ctx, in, info, handler)
}
// Signer_ServiceDesc is the grpc.ServiceDesc for Signer service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Signer_ServiceDesc = grpc.ServiceDesc{
ServiceName: "signrpc.Signer",
HandlerType: (*SignerServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "SignOutputRaw",
Handler: _Signer_SignOutputRaw_Handler,
},
{
MethodName: "ComputeInputScript",
Handler: _Signer_ComputeInputScript_Handler,
},
{
MethodName: "SignMessage",
Handler: _Signer_SignMessage_Handler,
},
{
MethodName: "VerifyMessage",
Handler: _Signer_VerifyMessage_Handler,
},
{
MethodName: "DeriveSharedKey",
Handler: _Signer_DeriveSharedKey_Handler,
},
{
MethodName: "MuSig2CombineKeys",
Handler: _Signer_MuSig2CombineKeys_Handler,
},
{
MethodName: "MuSig2CreateSession",
Handler: _Signer_MuSig2CreateSession_Handler,
},
{
MethodName: "MuSig2RegisterNonces",
Handler: _Signer_MuSig2RegisterNonces_Handler,
},
{
MethodName: "MuSig2Sign",
Handler: _Signer_MuSig2Sign_Handler,
},
{
MethodName: "MuSig2CombineSig",
Handler: _Signer_MuSig2CombineSig_Handler,
},
{
MethodName: "MuSig2Cleanup",
Handler: _Signer_MuSig2Cleanup_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "signrpc/signer.proto",
}
package signrpc
import (
"fmt"
"github.com/lightningnetwork/lnd/input"
)
// UnmarshalMuSig2Version parses the RPC MuSig2 version into the native
// counterpart.
func UnmarshalMuSig2Version(rpcVersion MuSig2Version) (input.MuSig2Version,
error) {
switch rpcVersion {
case MuSig2Version_MUSIG2_VERSION_V040:
return input.MuSig2Version040, nil
case MuSig2Version_MUSIG2_VERSION_V100RC2:
return input.MuSig2Version100RC2, nil
default:
return 0, fmt.Errorf("unknown MuSig2 version <%v>, make sure "+
"your client software is up to date, the version "+
"field is mandatory for this release of lnd",
rpcVersion.String())
}
}
// MarshalMuSig2Version turns the native MuSig2 version into its RPC
// counterpart.
func MarshalMuSig2Version(version input.MuSig2Version) (MuSig2Version, error) {
switch version {
case input.MuSig2Version040:
return MuSig2Version_MUSIG2_VERSION_V040, nil
case input.MuSig2Version100RC2:
return MuSig2Version_MUSIG2_VERSION_V100RC2, nil
default:
return 0, fmt.Errorf("unknown MuSig2 version <%d>", version)
}
}
// Code generated by falafel 0.9.2. DO NOT EDIT.
// source: stateservice.proto
package lnrpc
import (
"context"
gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protojson"
)
func RegisterStateJSONCallbacks(registry map[string]func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error))) {
marshaler := &gateway.JSONPb{
MarshalOptions: protojson.MarshalOptions{
UseProtoNames: true,
EmitUnpopulated: true,
},
}
registry["lnrpc.State.SubscribeState"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SubscribeStateRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewStateClient(conn)
stream, err := client.SubscribeState(ctx, req)
if err != nil {
callback("", err)
return
}
go func() {
for {
select {
case <-stream.Context().Done():
callback("", stream.Context().Err())
return
default:
}
resp, err := stream.Recv()
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}()
}
registry["lnrpc.State.GetState"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &GetStateRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewStateClient(conn)
resp, err := client.GetState(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v3.21.12
// source: stateservice.proto
package lnrpc
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type WalletState int32
const (
// NON_EXISTING means that the wallet has not yet been initialized.
WalletState_NON_EXISTING WalletState = 0
// LOCKED means that the wallet is locked and requires a password to unlock.
WalletState_LOCKED WalletState = 1
// UNLOCKED means that the wallet was unlocked successfully, but RPC server
// isn't ready.
WalletState_UNLOCKED WalletState = 2
// RPC_ACTIVE means that the lnd server is active but not fully ready for
// calls.
WalletState_RPC_ACTIVE WalletState = 3
// SERVER_ACTIVE means that the lnd server is ready to accept calls.
WalletState_SERVER_ACTIVE WalletState = 4
// WAITING_TO_START means that node is waiting to become the leader in a
// cluster and is not started yet.
WalletState_WAITING_TO_START WalletState = 255
)
// Enum value maps for WalletState.
var (
WalletState_name = map[int32]string{
0: "NON_EXISTING",
1: "LOCKED",
2: "UNLOCKED",
3: "RPC_ACTIVE",
4: "SERVER_ACTIVE",
255: "WAITING_TO_START",
}
WalletState_value = map[string]int32{
"NON_EXISTING": 0,
"LOCKED": 1,
"UNLOCKED": 2,
"RPC_ACTIVE": 3,
"SERVER_ACTIVE": 4,
"WAITING_TO_START": 255,
}
)
func (x WalletState) Enum() *WalletState {
p := new(WalletState)
*p = x
return p
}
func (x WalletState) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (WalletState) Descriptor() protoreflect.EnumDescriptor {
return file_stateservice_proto_enumTypes[0].Descriptor()
}
func (WalletState) Type() protoreflect.EnumType {
return &file_stateservice_proto_enumTypes[0]
}
func (x WalletState) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use WalletState.Descriptor instead.
func (WalletState) EnumDescriptor() ([]byte, []int) {
return file_stateservice_proto_rawDescGZIP(), []int{0}
}
type SubscribeStateRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *SubscribeStateRequest) Reset() {
*x = SubscribeStateRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_stateservice_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SubscribeStateRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SubscribeStateRequest) ProtoMessage() {}
func (x *SubscribeStateRequest) ProtoReflect() protoreflect.Message {
mi := &file_stateservice_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SubscribeStateRequest.ProtoReflect.Descriptor instead.
func (*SubscribeStateRequest) Descriptor() ([]byte, []int) {
return file_stateservice_proto_rawDescGZIP(), []int{0}
}
type SubscribeStateResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
State WalletState `protobuf:"varint,1,opt,name=state,proto3,enum=lnrpc.WalletState" json:"state,omitempty"`
}
func (x *SubscribeStateResponse) Reset() {
*x = SubscribeStateResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_stateservice_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SubscribeStateResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SubscribeStateResponse) ProtoMessage() {}
func (x *SubscribeStateResponse) ProtoReflect() protoreflect.Message {
mi := &file_stateservice_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SubscribeStateResponse.ProtoReflect.Descriptor instead.
func (*SubscribeStateResponse) Descriptor() ([]byte, []int) {
return file_stateservice_proto_rawDescGZIP(), []int{1}
}
func (x *SubscribeStateResponse) GetState() WalletState {
if x != nil {
return x.State
}
return WalletState_NON_EXISTING
}
type GetStateRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *GetStateRequest) Reset() {
*x = GetStateRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_stateservice_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetStateRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetStateRequest) ProtoMessage() {}
func (x *GetStateRequest) ProtoReflect() protoreflect.Message {
mi := &file_stateservice_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetStateRequest.ProtoReflect.Descriptor instead.
func (*GetStateRequest) Descriptor() ([]byte, []int) {
return file_stateservice_proto_rawDescGZIP(), []int{2}
}
type GetStateResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
State WalletState `protobuf:"varint,1,opt,name=state,proto3,enum=lnrpc.WalletState" json:"state,omitempty"`
}
func (x *GetStateResponse) Reset() {
*x = GetStateResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_stateservice_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetStateResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetStateResponse) ProtoMessage() {}
func (x *GetStateResponse) ProtoReflect() protoreflect.Message {
mi := &file_stateservice_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetStateResponse.ProtoReflect.Descriptor instead.
func (*GetStateResponse) Descriptor() ([]byte, []int) {
return file_stateservice_proto_rawDescGZIP(), []int{3}
}
func (x *GetStateResponse) GetState() WalletState {
if x != nil {
return x.State
}
return WalletState_NON_EXISTING
}
var File_stateservice_proto protoreflect.FileDescriptor
var file_stateservice_proto_rawDesc = []byte{
0x0a, 0x12, 0x73, 0x74, 0x61, 0x74, 0x65, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x22, 0x17, 0x0a, 0x15, 0x53,
0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x22, 0x42, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62,
0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28,
0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74,
0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x11, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x53,
0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3c, 0x0a, 0x10, 0x47,
0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x28, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12,
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x53, 0x74, 0x61,
0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2a, 0x73, 0x0a, 0x0b, 0x57, 0x61, 0x6c,
0x6c, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x4e, 0x4f, 0x4e, 0x5f,
0x45, 0x58, 0x49, 0x53, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x4f,
0x43, 0x4b, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x4e, 0x4c, 0x4f, 0x43, 0x4b,
0x45, 0x44, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x50, 0x43, 0x5f, 0x41, 0x43, 0x54, 0x49,
0x56, 0x45, 0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x41,
0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x04, 0x12, 0x15, 0x0a, 0x10, 0x57, 0x41, 0x49, 0x54, 0x49,
0x4e, 0x47, 0x5f, 0x54, 0x4f, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0xff, 0x01, 0x32, 0x95,
0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x4f, 0x0a, 0x0e, 0x53, 0x75, 0x62, 0x73,
0x63, 0x72, 0x69, 0x62, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, 0x74, 0x61, 0x74,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x3b, 0x0a, 0x08, 0x47, 0x65, 0x74,
0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65,
0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65,
0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_stateservice_proto_rawDescOnce sync.Once
file_stateservice_proto_rawDescData = file_stateservice_proto_rawDesc
)
func file_stateservice_proto_rawDescGZIP() []byte {
file_stateservice_proto_rawDescOnce.Do(func() {
file_stateservice_proto_rawDescData = protoimpl.X.CompressGZIP(file_stateservice_proto_rawDescData)
})
return file_stateservice_proto_rawDescData
}
var file_stateservice_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_stateservice_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_stateservice_proto_goTypes = []interface{}{
(WalletState)(0), // 0: lnrpc.WalletState
(*SubscribeStateRequest)(nil), // 1: lnrpc.SubscribeStateRequest
(*SubscribeStateResponse)(nil), // 2: lnrpc.SubscribeStateResponse
(*GetStateRequest)(nil), // 3: lnrpc.GetStateRequest
(*GetStateResponse)(nil), // 4: lnrpc.GetStateResponse
}
var file_stateservice_proto_depIdxs = []int32{
0, // 0: lnrpc.SubscribeStateResponse.state:type_name -> lnrpc.WalletState
0, // 1: lnrpc.GetStateResponse.state:type_name -> lnrpc.WalletState
1, // 2: lnrpc.State.SubscribeState:input_type -> lnrpc.SubscribeStateRequest
3, // 3: lnrpc.State.GetState:input_type -> lnrpc.GetStateRequest
2, // 4: lnrpc.State.SubscribeState:output_type -> lnrpc.SubscribeStateResponse
4, // 5: lnrpc.State.GetState:output_type -> lnrpc.GetStateResponse
4, // [4:6] is the sub-list for method output_type
2, // [2:4] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_stateservice_proto_init() }
func file_stateservice_proto_init() {
if File_stateservice_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_stateservice_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SubscribeStateRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_stateservice_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SubscribeStateResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_stateservice_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetStateRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_stateservice_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetStateResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_stateservice_proto_rawDesc,
NumEnums: 1,
NumMessages: 4,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_stateservice_proto_goTypes,
DependencyIndexes: file_stateservice_proto_depIdxs,
EnumInfos: file_stateservice_proto_enumTypes,
MessageInfos: file_stateservice_proto_msgTypes,
}.Build()
File_stateservice_proto = out.File
file_stateservice_proto_rawDesc = nil
file_stateservice_proto_goTypes = nil
file_stateservice_proto_depIdxs = nil
}
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: stateservice.proto
/*
Package lnrpc is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package lnrpc
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
func request_State_SubscribeState_0(ctx context.Context, marshaler runtime.Marshaler, client StateClient, req *http.Request, pathParams map[string]string) (State_SubscribeStateClient, runtime.ServerMetadata, error) {
var protoReq SubscribeStateRequest
var metadata runtime.ServerMetadata
stream, err := client.SubscribeState(ctx, &protoReq)
if err != nil {
return nil, metadata, err
}
header, err := stream.Header()
if err != nil {
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
func request_State_GetState_0(ctx context.Context, marshaler runtime.Marshaler, client StateClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetStateRequest
var metadata runtime.ServerMetadata
msg, err := client.GetState(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_State_GetState_0(ctx context.Context, marshaler runtime.Marshaler, server StateServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetStateRequest
var metadata runtime.ServerMetadata
msg, err := server.GetState(ctx, &protoReq)
return msg, metadata, err
}
// RegisterStateHandlerServer registers the http handlers for service State to "mux".
// UnaryRPC :call StateServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterStateHandlerFromEndpoint instead.
func RegisterStateHandlerServer(ctx context.Context, mux *runtime.ServeMux, server StateServer) error {
mux.Handle("GET", pattern_State_SubscribeState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
mux.Handle("GET", pattern_State_GetState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.State/GetState", runtime.WithHTTPPathPattern("/v1/state"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_State_GetState_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_State_GetState_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterStateHandlerFromEndpoint is same as RegisterStateHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterStateHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterStateHandler(ctx, mux, conn)
}
// RegisterStateHandler registers the http handlers for service State to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterStateHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterStateHandlerClient(ctx, mux, NewStateClient(conn))
}
// RegisterStateHandlerClient registers the http handlers for service State
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "StateClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "StateClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "StateClient" to call the correct interceptors.
func RegisterStateHandlerClient(ctx context.Context, mux *runtime.ServeMux, client StateClient) error {
mux.Handle("GET", pattern_State_SubscribeState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.State/SubscribeState", runtime.WithHTTPPathPattern("/v1/state/subscribe"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_State_SubscribeState_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_State_SubscribeState_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_State_GetState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.State/GetState", runtime.WithHTTPPathPattern("/v1/state"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_State_GetState_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_State_GetState_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_State_SubscribeState_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "state", "subscribe"}, ""))
pattern_State_GetState_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "state"}, ""))
)
var (
forward_State_SubscribeState_0 = runtime.ForwardResponseStream
forward_State_GetState_0 = runtime.ForwardResponseMessage
)
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package lnrpc
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// StateClient is the client API for State service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type StateClient interface {
// SubscribeState subscribes to the state of the wallet. The current wallet
// state will always be delivered immediately.
SubscribeState(ctx context.Context, in *SubscribeStateRequest, opts ...grpc.CallOption) (State_SubscribeStateClient, error)
// GetState returns the current wallet state without streaming further
// changes.
GetState(ctx context.Context, in *GetStateRequest, opts ...grpc.CallOption) (*GetStateResponse, error)
}
type stateClient struct {
cc grpc.ClientConnInterface
}
func NewStateClient(cc grpc.ClientConnInterface) StateClient {
return &stateClient{cc}
}
func (c *stateClient) SubscribeState(ctx context.Context, in *SubscribeStateRequest, opts ...grpc.CallOption) (State_SubscribeStateClient, error) {
stream, err := c.cc.NewStream(ctx, &State_ServiceDesc.Streams[0], "/lnrpc.State/SubscribeState", opts...)
if err != nil {
return nil, err
}
x := &stateSubscribeStateClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type State_SubscribeStateClient interface {
Recv() (*SubscribeStateResponse, error)
grpc.ClientStream
}
type stateSubscribeStateClient struct {
grpc.ClientStream
}
func (x *stateSubscribeStateClient) Recv() (*SubscribeStateResponse, error) {
m := new(SubscribeStateResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *stateClient) GetState(ctx context.Context, in *GetStateRequest, opts ...grpc.CallOption) (*GetStateResponse, error) {
out := new(GetStateResponse)
err := c.cc.Invoke(ctx, "/lnrpc.State/GetState", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// StateServer is the server API for State service.
// All implementations must embed UnimplementedStateServer
// for forward compatibility
type StateServer interface {
// SubscribeState subscribes to the state of the wallet. The current wallet
// state will always be delivered immediately.
SubscribeState(*SubscribeStateRequest, State_SubscribeStateServer) error
// GetState returns the current wallet state without streaming further
// changes.
GetState(context.Context, *GetStateRequest) (*GetStateResponse, error)
mustEmbedUnimplementedStateServer()
}
// UnimplementedStateServer must be embedded to have forward compatible implementations.
type UnimplementedStateServer struct {
}
func (UnimplementedStateServer) SubscribeState(*SubscribeStateRequest, State_SubscribeStateServer) error {
return status.Errorf(codes.Unimplemented, "method SubscribeState not implemented")
}
func (UnimplementedStateServer) GetState(context.Context, *GetStateRequest) (*GetStateResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetState not implemented")
}
func (UnimplementedStateServer) mustEmbedUnimplementedStateServer() {}
// UnsafeStateServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to StateServer will
// result in compilation errors.
type UnsafeStateServer interface {
mustEmbedUnimplementedStateServer()
}
func RegisterStateServer(s grpc.ServiceRegistrar, srv StateServer) {
s.RegisterService(&State_ServiceDesc, srv)
}
func _State_SubscribeState_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(SubscribeStateRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(StateServer).SubscribeState(m, &stateSubscribeStateServer{stream})
}
type State_SubscribeStateServer interface {
Send(*SubscribeStateResponse) error
grpc.ServerStream
}
type stateSubscribeStateServer struct {
grpc.ServerStream
}
func (x *stateSubscribeStateServer) Send(m *SubscribeStateResponse) error {
return x.ServerStream.SendMsg(m)
}
func _State_GetState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetStateRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StateServer).GetState(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.State/GetState",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StateServer).GetState(ctx, req.(*GetStateRequest))
}
return interceptor(ctx, in, info, handler)
}
// State_ServiceDesc is the grpc.ServiceDesc for State service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var State_ServiceDesc = grpc.ServiceDesc{
ServiceName: "lnrpc.State",
HandlerType: (*StateServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetState",
Handler: _State_GetState_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "SubscribeState",
Handler: _State_SubscribeState_Handler,
ServerStreams: true,
},
},
Metadata: "stateservice.proto",
}
package lnrpc
import (
"context"
"fmt"
"sync"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"gopkg.in/macaroon-bakery.v2/bakery"
)
// MacaroonPerms is a map from the FullMethod of an invoked gRPC command. It
// maps the set of operations that the macaroon presented with the command MUST
// satisfy. With this map, all sub-servers are able to communicate to the
// primary macaroon service what type of macaroon must be passed with each
// method present on the service of the sub-server.
type MacaroonPerms map[string][]bakery.Op
// SubServer is a child server of the main lnrpc gRPC server. Sub-servers allow
// lnd to expose discrete services that can be used with or independent of the
// main RPC server. The main rpcserver will create, start, stop, and manage
// each sub-server in a generalized manner.
type SubServer interface {
// Start starts the sub-server and all goroutines it needs to operate.
Start() error
// Stop signals that the sub-server should wrap up any lingering
// requests, and being a graceful shutdown.
Stop() error
// Name returns a unique string representation of the sub-server. This
// can be used to identify the sub-server and also de-duplicate them.
Name() string
}
// GrpcHandler is the interface that should be registered with the root gRPC
// server, and is the interface that implements the subserver's defined RPC.
// Before the actual sub server has been created, this will be an empty shell
// allowing us to start the gRPC server before we have all the dependencies
// needed to create the subserver itself.
type GrpcHandler interface {
// RegisterWithRootServer will be called by the root gRPC server to
// direct a sub RPC server to register itself with the main gRPC root
// server. Until this is called, each sub-server won't be able to have
// requests routed towards it.
RegisterWithRootServer(*grpc.Server) error
// RegisterWithRestServer will be called by the root REST mux to direct
// a sub RPC server to register itself with the main REST mux server.
// Until this is called, each sub-server won't be able to have requests
// routed towards it.
RegisterWithRestServer(context.Context, *runtime.ServeMux, string,
[]grpc.DialOption) error
// CreateSubServer populates the subserver's dependencies using the
// passed SubServerConfigDispatcher. This method should fully
// initialize the sub-server instance, making it ready for action. It
// returns the macaroon permissions that the sub-server wishes to pass
// on to the root server for all methods routed towards it.
CreateSubServer(subCfgs SubServerConfigDispatcher) (SubServer,
MacaroonPerms, error)
}
// SubServerConfigDispatcher is an interface that all sub-servers will use to
// dynamically locate their configuration files. This abstraction will allow
// the primary RPC sever to initialize all sub-servers in a generic manner
// without knowing of each individual sub server.
type SubServerConfigDispatcher interface {
// FetchConfig attempts to locate an existing configuration file mapped
// to the target sub-server. If we're unable to find a config file
// matching the subServerName name, then false will be returned for the
// second parameter.
FetchConfig(subServerName string) (interface{}, bool)
}
// SubServerDriver is a template struct that allows the root server to create a
// sub-server gRPC handler with minimal knowledge.
type SubServerDriver struct {
// SubServerName is the full name of a sub-sever.
//
// NOTE: This MUST be unique.
SubServerName string
// NewGrpcHandler creates a a new sub-server gRPC interface that can be
// registered with the root gRPC server. It is not expected that the
// SubServer is ready for operation before its CreateSubServer and
// Start methods have been called.
NewGrpcHandler func() GrpcHandler
}
var (
// subServers is a package level global variable that houses all the
// registered sub-servers.
subServers = make(map[string]*SubServerDriver)
// registerMtx is a mutex that protects access to the above subServer
// map.
registerMtx sync.Mutex
)
// RegisteredSubServers returns all registered sub-servers.
//
// NOTE: This function is safe for concurrent access.
func RegisteredSubServers() []*SubServerDriver {
registerMtx.Lock()
defer registerMtx.Unlock()
drivers := make([]*SubServerDriver, 0, len(subServers))
for _, driver := range subServers {
drivers = append(drivers, driver)
}
return drivers
}
// RegisterSubServer should be called by a sub-server within its package's
// init() method to register its existence with the main sub-server map. Each
// sub-server, if active, is meant to register via this method in their init()
// method. This allows callers to easily initialize and register all
// sub-servers without knowing any details beyond that the fact that they
// satisfy the necessary interfaces.
//
// NOTE: This function is safe for concurrent access.
func RegisterSubServer(driver *SubServerDriver) error {
registerMtx.Lock()
defer registerMtx.Unlock()
if _, ok := subServers[driver.SubServerName]; ok {
return fmt.Errorf("subserver already registered")
}
subServers[driver.SubServerName] = driver
return nil
}
// SupportedServers returns slice of the names of all registered sub-servers.
//
// NOTE: This function is safe for concurrent access.
func SupportedServers() []string {
registerMtx.Lock()
defer registerMtx.Unlock()
supportedSubServers := make([]string, 0, len(subServers))
for driverName := range subServers {
supportedSubServers = append(supportedSubServers, driverName)
}
return supportedSubServers
}
package verrpc
import (
"fmt"
"github.com/lightningnetwork/lnd/lnrpc"
)
func init() {
subServer := &lnrpc.SubServerDriver{
SubServerName: subServerName,
NewGrpcHandler: func() lnrpc.GrpcHandler {
return &ServerShell{}
},
}
// We'll register ourselves as a sub-RPC server within the global lnrpc
// package namespace.
if err := lnrpc.RegisterSubServer(subServer); err != nil {
panic(fmt.Sprintf("failed to register sub server driver '%s': %v",
subServerName, err))
}
}
package verrpc
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// Subsystem defines the logging code for this subsystem.
const Subsystem = "VRPC"
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package verrpc
import (
"context"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/lnrpc"
"google.golang.org/grpc"
"gopkg.in/macaroon-bakery.v2/bakery"
)
const subServerName = "VersionRPC"
var macPermissions = map[string][]bakery.Op{
"/verrpc.Versioner/GetVersion": {{
Entity: "info",
Action: "read",
}},
}
// ServerShell is a shell struct holding a reference to the actual sub-server.
// It is used to register the gRPC sub-server with the root server before we
// have the necessary dependencies to populate the actual sub-server.
type ServerShell struct {
VersionerServer
}
// Server is an rpc server that supports querying for information about the
// running binary.
type Server struct {
// Required by the grpc-gateway/v2 library for forward compatibility.
UnimplementedVersionerServer
}
// Start launches any helper goroutines required for the rpcServer to function.
//
// NOTE: This is part of the lnrpc.SubServer interface.
func (s *Server) Start() error {
return nil
}
// Stop signals any active goroutines for a graceful closure.
//
// NOTE: This is part of the lnrpc.SubServer interface.
func (s *Server) Stop() error {
return nil
}
// Name returns a unique string representation of the sub-server. This can be
// used to identify the sub-server and also de-duplicate them.
//
// NOTE: This is part of the lnrpc.SubServer interface.
func (s *Server) Name() string {
return subServerName
}
// RegisterWithRootServer will be called by the root gRPC server to direct a
// sub RPC server to register itself with the main gRPC root server. Until this
// is called, each sub-server won't be able to have requests routed towards it.
//
// NOTE: This is part of the lnrpc.GrpcHandler interface.
func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error {
RegisterVersionerServer(grpcServer, r)
log.Debugf("Versioner RPC server successfully registered with root " +
"gRPC server")
return nil
}
// RegisterWithRestServer will be called by the root REST mux to direct a sub
// RPC server to register itself with the main REST mux server. Until this is
// called, each sub-server won't be able to have requests routed towards it.
//
// NOTE: This is part of the lnrpc.GrpcHandler interface.
func (r *ServerShell) RegisterWithRestServer(ctx context.Context,
mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error {
// We make sure that we register it with the main REST server to ensure
// all our methods are routed properly.
err := RegisterVersionerHandlerFromEndpoint(ctx, mux, dest, opts)
if err != nil {
log.Errorf("Could not register Versioner REST server "+
"with root REST server: %v", err)
return err
}
log.Debugf("Versioner REST server successfully registered with " +
"root REST server")
return nil
}
// CreateSubServer populates the subserver's dependencies using the passed
// SubServerConfigDispatcher. This method should fully initialize the
// sub-server instance, making it ready for action. It returns the macaroon
// permissions that the sub-server wishes to pass on to the root server for all
// methods routed towards it.
//
// NOTE: This is part of the lnrpc.GrpcHandler interface.
func (r *ServerShell) CreateSubServer(_ lnrpc.SubServerConfigDispatcher) (
lnrpc.SubServer, lnrpc.MacaroonPerms, error) {
subServer := &Server{}
r.VersionerServer = subServer
return subServer, macPermissions, nil
}
// GetVersion returns information about the compiled binary.
func (s *Server) GetVersion(_ context.Context,
_ *VersionRequest) (*Version, error) {
return &Version{
Commit: build.Commit,
CommitHash: build.CommitHash,
Version: build.Version(),
AppMajor: uint32(build.AppMajor),
AppMinor: uint32(build.AppMinor),
AppPatch: uint32(build.AppPatch),
AppPreRelease: build.AppPreRelease,
BuildTags: build.Tags(),
GoVersion: build.GoVersion,
}, nil
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v3.21.12
// source: verrpc/verrpc.proto
package verrpc
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type VersionRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *VersionRequest) Reset() {
*x = VersionRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_verrpc_verrpc_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *VersionRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*VersionRequest) ProtoMessage() {}
func (x *VersionRequest) ProtoReflect() protoreflect.Message {
mi := &file_verrpc_verrpc_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use VersionRequest.ProtoReflect.Descriptor instead.
func (*VersionRequest) Descriptor() ([]byte, []int) {
return file_verrpc_verrpc_proto_rawDescGZIP(), []int{0}
}
type Version struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A verbose description of the daemon's commit.
Commit string `protobuf:"bytes,1,opt,name=commit,proto3" json:"commit,omitempty"`
// The SHA1 commit hash that the daemon is compiled with.
CommitHash string `protobuf:"bytes,2,opt,name=commit_hash,json=commitHash,proto3" json:"commit_hash,omitempty"`
// The semantic version.
Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"`
// The major application version.
AppMajor uint32 `protobuf:"varint,4,opt,name=app_major,json=appMajor,proto3" json:"app_major,omitempty"`
// The minor application version.
AppMinor uint32 `protobuf:"varint,5,opt,name=app_minor,json=appMinor,proto3" json:"app_minor,omitempty"`
// The application patch number.
AppPatch uint32 `protobuf:"varint,6,opt,name=app_patch,json=appPatch,proto3" json:"app_patch,omitempty"`
// The application pre-release modifier, possibly empty.
AppPreRelease string `protobuf:"bytes,7,opt,name=app_pre_release,json=appPreRelease,proto3" json:"app_pre_release,omitempty"`
// The list of build tags that were supplied during compilation.
BuildTags []string `protobuf:"bytes,8,rep,name=build_tags,json=buildTags,proto3" json:"build_tags,omitempty"`
// The version of go that compiled the executable.
GoVersion string `protobuf:"bytes,9,opt,name=go_version,json=goVersion,proto3" json:"go_version,omitempty"`
}
func (x *Version) Reset() {
*x = Version{}
if protoimpl.UnsafeEnabled {
mi := &file_verrpc_verrpc_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Version) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Version) ProtoMessage() {}
func (x *Version) ProtoReflect() protoreflect.Message {
mi := &file_verrpc_verrpc_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Version.ProtoReflect.Descriptor instead.
func (*Version) Descriptor() ([]byte, []int) {
return file_verrpc_verrpc_proto_rawDescGZIP(), []int{1}
}
func (x *Version) GetCommit() string {
if x != nil {
return x.Commit
}
return ""
}
func (x *Version) GetCommitHash() string {
if x != nil {
return x.CommitHash
}
return ""
}
func (x *Version) GetVersion() string {
if x != nil {
return x.Version
}
return ""
}
func (x *Version) GetAppMajor() uint32 {
if x != nil {
return x.AppMajor
}
return 0
}
func (x *Version) GetAppMinor() uint32 {
if x != nil {
return x.AppMinor
}
return 0
}
func (x *Version) GetAppPatch() uint32 {
if x != nil {
return x.AppPatch
}
return 0
}
func (x *Version) GetAppPreRelease() string {
if x != nil {
return x.AppPreRelease
}
return ""
}
func (x *Version) GetBuildTags() []string {
if x != nil {
return x.BuildTags
}
return nil
}
func (x *Version) GetGoVersion() string {
if x != nil {
return x.GoVersion
}
return ""
}
var File_verrpc_verrpc_proto protoreflect.FileDescriptor
var file_verrpc_verrpc_proto_rawDesc = []byte{
0x0a, 0x13, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2f, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x22, 0x10, 0x0a,
0x0e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22,
0x99, 0x02, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x63,
0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f, 0x6d,
0x6d, 0x69, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x68, 0x61,
0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74,
0x48, 0x61, 0x73, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1b,
0x0a, 0x09, 0x61, 0x70, 0x70, 0x5f, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28,
0x0d, 0x52, 0x08, 0x61, 0x70, 0x70, 0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x61,
0x70, 0x70, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08,
0x61, 0x70, 0x70, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x70, 0x70, 0x5f,
0x70, 0x61, 0x74, 0x63, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x61, 0x70, 0x70,
0x50, 0x61, 0x74, 0x63, 0x68, 0x12, 0x26, 0x0a, 0x0f, 0x61, 0x70, 0x70, 0x5f, 0x70, 0x72, 0x65,
0x5f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d,
0x61, 0x70, 0x70, 0x50, 0x72, 0x65, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x12, 0x1d, 0x0a,
0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28,
0x09, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x54, 0x61, 0x67, 0x73, 0x12, 0x1d, 0x0a, 0x0a,
0x67, 0x6f, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09,
0x52, 0x09, 0x67, 0x6f, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x32, 0x42, 0x0a, 0x09, 0x56,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x35, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x56,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e,
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f,
0x2e, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42,
0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69,
0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c,
0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_verrpc_verrpc_proto_rawDescOnce sync.Once
file_verrpc_verrpc_proto_rawDescData = file_verrpc_verrpc_proto_rawDesc
)
func file_verrpc_verrpc_proto_rawDescGZIP() []byte {
file_verrpc_verrpc_proto_rawDescOnce.Do(func() {
file_verrpc_verrpc_proto_rawDescData = protoimpl.X.CompressGZIP(file_verrpc_verrpc_proto_rawDescData)
})
return file_verrpc_verrpc_proto_rawDescData
}
var file_verrpc_verrpc_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_verrpc_verrpc_proto_goTypes = []interface{}{
(*VersionRequest)(nil), // 0: verrpc.VersionRequest
(*Version)(nil), // 1: verrpc.Version
}
var file_verrpc_verrpc_proto_depIdxs = []int32{
0, // 0: verrpc.Versioner.GetVersion:input_type -> verrpc.VersionRequest
1, // 1: verrpc.Versioner.GetVersion:output_type -> verrpc.Version
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_verrpc_verrpc_proto_init() }
func file_verrpc_verrpc_proto_init() {
if File_verrpc_verrpc_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_verrpc_verrpc_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*VersionRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_verrpc_verrpc_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Version); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_verrpc_verrpc_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_verrpc_verrpc_proto_goTypes,
DependencyIndexes: file_verrpc_verrpc_proto_depIdxs,
MessageInfos: file_verrpc_verrpc_proto_msgTypes,
}.Build()
File_verrpc_verrpc_proto = out.File
file_verrpc_verrpc_proto_rawDesc = nil
file_verrpc_verrpc_proto_goTypes = nil
file_verrpc_verrpc_proto_depIdxs = nil
}
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: verrpc/verrpc.proto
/*
Package verrpc is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package verrpc
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
func request_Versioner_GetVersion_0(ctx context.Context, marshaler runtime.Marshaler, client VersionerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq VersionRequest
var metadata runtime.ServerMetadata
msg, err := client.GetVersion(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Versioner_GetVersion_0(ctx context.Context, marshaler runtime.Marshaler, server VersionerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq VersionRequest
var metadata runtime.ServerMetadata
msg, err := server.GetVersion(ctx, &protoReq)
return msg, metadata, err
}
// RegisterVersionerHandlerServer registers the http handlers for service Versioner to "mux".
// UnaryRPC :call VersionerServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterVersionerHandlerFromEndpoint instead.
func RegisterVersionerHandlerServer(ctx context.Context, mux *runtime.ServeMux, server VersionerServer) error {
mux.Handle("GET", pattern_Versioner_GetVersion_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/verrpc.Versioner/GetVersion", runtime.WithHTTPPathPattern("/v2/versioner/version"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Versioner_GetVersion_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Versioner_GetVersion_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterVersionerHandlerFromEndpoint is same as RegisterVersionerHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterVersionerHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterVersionerHandler(ctx, mux, conn)
}
// RegisterVersionerHandler registers the http handlers for service Versioner to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterVersionerHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterVersionerHandlerClient(ctx, mux, NewVersionerClient(conn))
}
// RegisterVersionerHandlerClient registers the http handlers for service Versioner
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "VersionerClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "VersionerClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "VersionerClient" to call the correct interceptors.
func RegisterVersionerHandlerClient(ctx context.Context, mux *runtime.ServeMux, client VersionerClient) error {
mux.Handle("GET", pattern_Versioner_GetVersion_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/verrpc.Versioner/GetVersion", runtime.WithHTTPPathPattern("/v2/versioner/version"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Versioner_GetVersion_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Versioner_GetVersion_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_Versioner_GetVersion_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "versioner", "version"}, ""))
)
var (
forward_Versioner_GetVersion_0 = runtime.ForwardResponseMessage
)
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package verrpc
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// VersionerClient is the client API for Versioner service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type VersionerClient interface {
// lncli: `version`
// GetVersion returns the current version and build information of the running
// daemon.
GetVersion(ctx context.Context, in *VersionRequest, opts ...grpc.CallOption) (*Version, error)
}
type versionerClient struct {
cc grpc.ClientConnInterface
}
func NewVersionerClient(cc grpc.ClientConnInterface) VersionerClient {
return &versionerClient{cc}
}
func (c *versionerClient) GetVersion(ctx context.Context, in *VersionRequest, opts ...grpc.CallOption) (*Version, error) {
out := new(Version)
err := c.cc.Invoke(ctx, "/verrpc.Versioner/GetVersion", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// VersionerServer is the server API for Versioner service.
// All implementations must embed UnimplementedVersionerServer
// for forward compatibility
type VersionerServer interface {
// lncli: `version`
// GetVersion returns the current version and build information of the running
// daemon.
GetVersion(context.Context, *VersionRequest) (*Version, error)
mustEmbedUnimplementedVersionerServer()
}
// UnimplementedVersionerServer must be embedded to have forward compatible implementations.
type UnimplementedVersionerServer struct {
}
func (UnimplementedVersionerServer) GetVersion(context.Context, *VersionRequest) (*Version, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetVersion not implemented")
}
func (UnimplementedVersionerServer) mustEmbedUnimplementedVersionerServer() {}
// UnsafeVersionerServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to VersionerServer will
// result in compilation errors.
type UnsafeVersionerServer interface {
mustEmbedUnimplementedVersionerServer()
}
func RegisterVersionerServer(s grpc.ServiceRegistrar, srv VersionerServer) {
s.RegisterService(&Versioner_ServiceDesc, srv)
}
func _Versioner_GetVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(VersionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(VersionerServer).GetVersion(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/verrpc.Versioner/GetVersion",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(VersionerServer).GetVersion(ctx, req.(*VersionRequest))
}
return interceptor(ctx, in, info, handler)
}
// Versioner_ServiceDesc is the grpc.ServiceDesc for Versioner service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Versioner_ServiceDesc = grpc.ServiceDesc{
ServiceName: "verrpc.Versioner",
HandlerType: (*VersionerServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetVersion",
Handler: _Versioner_GetVersion_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "verrpc/verrpc.proto",
}
// Code generated by falafel 0.9.2. DO NOT EDIT.
// source: verrpc.proto
package verrpc
import (
"context"
gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protojson"
)
func RegisterVersionerJSONCallbacks(registry map[string]func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error))) {
marshaler := &gateway.JSONPb{
MarshalOptions: protojson.MarshalOptions{
UseProtoNames: true,
EmitUnpopulated: true,
},
}
registry["verrpc.Versioner.GetVersion"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &VersionRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewVersionerClient(conn)
resp, err := client.GetVersion(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}
package walletrpc
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This means the
// package will not perform any logging by default until the caller requests
// it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("WLKT", nil))
}
// DisableLog disables all library log output. Logging output is disabled by
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info. This
// should be used in preference to SetLogWriter if the caller is also using
// btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v3.21.12
// source: walletrpc/walletkit.proto
package walletrpc
import (
lnrpc "github.com/lightningnetwork/lnd/lnrpc"
signrpc "github.com/lightningnetwork/lnd/lnrpc/signrpc"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type AddressType int32
const (
AddressType_UNKNOWN AddressType = 0
AddressType_WITNESS_PUBKEY_HASH AddressType = 1
AddressType_NESTED_WITNESS_PUBKEY_HASH AddressType = 2
AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH AddressType = 3
AddressType_TAPROOT_PUBKEY AddressType = 4
)
// Enum value maps for AddressType.
var (
AddressType_name = map[int32]string{
0: "UNKNOWN",
1: "WITNESS_PUBKEY_HASH",
2: "NESTED_WITNESS_PUBKEY_HASH",
3: "HYBRID_NESTED_WITNESS_PUBKEY_HASH",
4: "TAPROOT_PUBKEY",
}
AddressType_value = map[string]int32{
"UNKNOWN": 0,
"WITNESS_PUBKEY_HASH": 1,
"NESTED_WITNESS_PUBKEY_HASH": 2,
"HYBRID_NESTED_WITNESS_PUBKEY_HASH": 3,
"TAPROOT_PUBKEY": 4,
}
)
func (x AddressType) Enum() *AddressType {
p := new(AddressType)
*p = x
return p
}
func (x AddressType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (AddressType) Descriptor() protoreflect.EnumDescriptor {
return file_walletrpc_walletkit_proto_enumTypes[0].Descriptor()
}
func (AddressType) Type() protoreflect.EnumType {
return &file_walletrpc_walletkit_proto_enumTypes[0]
}
func (x AddressType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use AddressType.Descriptor instead.
func (AddressType) EnumDescriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{0}
}
type WitnessType int32
const (
WitnessType_UNKNOWN_WITNESS WitnessType = 0
// A witness that allows us to spend the output of a commitment transaction
// after a relative lock-time lockout.
WitnessType_COMMITMENT_TIME_LOCK WitnessType = 1
// A witness that allows us to spend a settled no-delay output immediately on a
// counterparty's commitment transaction.
WitnessType_COMMITMENT_NO_DELAY WitnessType = 2
// A witness that allows us to sweep the settled output of a malicious
// counterparty's who broadcasts a revoked commitment transaction.
WitnessType_COMMITMENT_REVOKE WitnessType = 3
// A witness that allows us to sweep an HTLC which we offered to the remote
// party in the case that they broadcast a revoked commitment state.
WitnessType_HTLC_OFFERED_REVOKE WitnessType = 4
// A witness that allows us to sweep an HTLC output sent to us in the case that
// the remote party broadcasts a revoked commitment state.
WitnessType_HTLC_ACCEPTED_REVOKE WitnessType = 5
// A witness that allows us to sweep an HTLC output that we extended to a
// party, but was never fulfilled. This HTLC output isn't directly on the
// commitment transaction, but is the result of a confirmed second-level HTLC
// transaction. As a result, we can only spend this after a CSV delay.
WitnessType_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL WitnessType = 6
// A witness that allows us to sweep an HTLC output that was offered to us, and
// for which we have a payment preimage. This HTLC output isn't directly on our
// commitment transaction, but is the result of confirmed second-level HTLC
// transaction. As a result, we can only spend this after a CSV delay.
WitnessType_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL WitnessType = 7
// A witness that allows us to sweep an HTLC that we offered to the remote
// party which lies in the commitment transaction of the remote party. We can
// spend this output after the absolute CLTV timeout of the HTLC as passed.
WitnessType_HTLC_OFFERED_REMOTE_TIMEOUT WitnessType = 8
// A witness that allows us to sweep an HTLC that was offered to us by the
// remote party. We use this witness in the case that the remote party goes to
// chain, and we know the pre-image to the HTLC. We can sweep this without any
// additional timeout.
WitnessType_HTLC_ACCEPTED_REMOTE_SUCCESS WitnessType = 9
// A witness that allows us to sweep an HTLC from the remote party's commitment
// transaction in the case that the broadcast a revoked commitment, but then
// also immediately attempt to go to the second level to claim the HTLC.
WitnessType_HTLC_SECOND_LEVEL_REVOKE WitnessType = 10
// A witness type that allows us to spend a regular p2wkh output that's sent to
// an output which is under complete control of the backing wallet.
WitnessType_WITNESS_KEY_HASH WitnessType = 11
// A witness type that allows us to sweep an output that sends to a nested P2SH
// script that pays to a key solely under our control.
WitnessType_NESTED_WITNESS_KEY_HASH WitnessType = 12
// A witness type that allows us to spend our anchor on the commitment
// transaction.
WitnessType_COMMITMENT_ANCHOR WitnessType = 13
// A witness type that is similar to the COMMITMENT_NO_DELAY type,
// but it omits the tweak that randomizes the key we need to
// spend with a channel peer supplied set of randomness.
WitnessType_COMMITMENT_NO_DELAY_TWEAKLESS WitnessType = 14
// A witness type that allows us to spend our output on the counterparty's
// commitment transaction after a confirmation.
WitnessType_COMMITMENT_TO_REMOTE_CONFIRMED WitnessType = 15
// A witness type that allows us to sweep an HTLC output that we extended
// to a party, but was never fulfilled. This _is_ the HTLC output directly
// on our commitment transaction, and the input to the second-level HTLC
// timeout transaction. It can only be spent after CLTV expiry, and
// commitment confirmation.
WitnessType_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL_INPUT_CONFIRMED WitnessType = 16
// A witness type that allows us to sweep an HTLC output that was offered
// to us, and for which we have a payment preimage. This _is_ the HTLC
// output directly on our commitment transaction, and the input to the
// second-level HTLC success transaction. It can only be spent after the
// commitment has confirmed.
WitnessType_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL_INPUT_CONFIRMED WitnessType = 17
// A witness type that allows us to spend our output on our local
// commitment transaction after a relative and absolute lock-time lockout as
// part of the script enforced lease commitment type.
WitnessType_LEASE_COMMITMENT_TIME_LOCK WitnessType = 18
// A witness type that allows us to spend our output on the counterparty's
// commitment transaction after a confirmation and absolute locktime as part
// of the script enforced lease commitment type.
WitnessType_LEASE_COMMITMENT_TO_REMOTE_CONFIRMED WitnessType = 19
// A witness type that allows us to sweep an HTLC output that we extended
// to a party, but was never fulfilled. This HTLC output isn't directly on
// the commitment transaction, but is the result of a confirmed second-level
// HTLC transaction. As a result, we can only spend this after a CSV delay
// and CLTV locktime as part of the script enforced lease commitment type.
WitnessType_LEASE_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL WitnessType = 20
// A witness type that allows us to sweep an HTLC output that was offered
// to us, and for which we have a payment preimage. This HTLC output isn't
// directly on our commitment transaction, but is the result of confirmed
// second-level HTLC transaction. As a result, we can only spend this after
// a CSV delay and CLTV locktime as part of the script enforced lease
// commitment type.
WitnessType_LEASE_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL WitnessType = 21
// A witness type that allows us to spend a regular p2tr output that's sent
// to an output which is under complete control of the backing wallet.
WitnessType_TAPROOT_PUB_KEY_SPEND WitnessType = 22
// A witness type that allows us to spend our settled local commitment after a
// CSV delay when we force close the channel.
WitnessType_TAPROOT_LOCAL_COMMIT_SPEND WitnessType = 23
// A witness type that allows us to spend our settled local commitment after
// a CSV delay when the remote party has force closed the channel.
WitnessType_TAPROOT_REMOTE_COMMIT_SPEND WitnessType = 24
// A witness type that we'll use for spending our own anchor output.
WitnessType_TAPROOT_ANCHOR_SWEEP_SPEND WitnessType = 25
// A witness that allows us to timeout an HTLC we offered to the remote party
// on our commitment transaction. We use this when we need to go on chain to
// time out an HTLC.
WitnessType_TAPROOT_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL WitnessType = 26
// A witness type that allows us to sweep an HTLC we accepted on our commitment
// transaction after we go to the second level on chain.
WitnessType_TAPROOT_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL WitnessType = 27
// A witness that allows us to sweep an HTLC on the revoked transaction of the
// remote party that goes to the second level.
WitnessType_TAPROOT_HTLC_SECOND_LEVEL_REVOKE WitnessType = 28
// A witness that allows us to sweep an HTLC sent to us by the remote party
// in the event that they broadcast a revoked state.
WitnessType_TAPROOT_HTLC_ACCEPTED_REVOKE WitnessType = 29
// A witness that allows us to sweep an HTLC we offered to the remote party if
// they broadcast a revoked commitment.
WitnessType_TAPROOT_HTLC_OFFERED_REVOKE WitnessType = 30
// A witness that allows us to sweep an HTLC we offered to the remote party
// that lies on the commitment transaction for the remote party. We can spend
// this output after the absolute CLTV timeout of the HTLC as passed.
WitnessType_TAPROOT_HTLC_OFFERED_REMOTE_TIMEOUT WitnessType = 31
// A witness type that allows us to sign the second level HTLC timeout
// transaction when spending from an HTLC residing on our local commitment
// transaction.
// This is used by the sweeper to re-sign inputs if it needs to aggregate
// several second level HTLCs.
WitnessType_TAPROOT_HTLC_LOCAL_OFFERED_TIMEOUT WitnessType = 32
// A witness that allows us to sweep an HTLC that was offered to us by the
// remote party for a taproot channels. We use this witness in the case that
// the remote party goes to chain, and we know the pre-image to the HTLC. We
// can sweep this without any additional timeout.
WitnessType_TAPROOT_HTLC_ACCEPTED_REMOTE_SUCCESS WitnessType = 33
// A witness type that allows us to sweep the HTLC offered to us on our local
// commitment transaction. We'll use this when we need to go on chain to sweep
// the HTLC. In this case, this is the second level HTLC success transaction.
WitnessType_TAPROOT_HTLC_ACCEPTED_LOCAL_SUCCESS WitnessType = 34
// A witness that allows us to sweep the settled output of a malicious
// counterparty's who broadcasts a revoked taproot commitment transaction.
WitnessType_TAPROOT_COMMITMENT_REVOKE WitnessType = 35
)
// Enum value maps for WitnessType.
var (
WitnessType_name = map[int32]string{
0: "UNKNOWN_WITNESS",
1: "COMMITMENT_TIME_LOCK",
2: "COMMITMENT_NO_DELAY",
3: "COMMITMENT_REVOKE",
4: "HTLC_OFFERED_REVOKE",
5: "HTLC_ACCEPTED_REVOKE",
6: "HTLC_OFFERED_TIMEOUT_SECOND_LEVEL",
7: "HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL",
8: "HTLC_OFFERED_REMOTE_TIMEOUT",
9: "HTLC_ACCEPTED_REMOTE_SUCCESS",
10: "HTLC_SECOND_LEVEL_REVOKE",
11: "WITNESS_KEY_HASH",
12: "NESTED_WITNESS_KEY_HASH",
13: "COMMITMENT_ANCHOR",
14: "COMMITMENT_NO_DELAY_TWEAKLESS",
15: "COMMITMENT_TO_REMOTE_CONFIRMED",
16: "HTLC_OFFERED_TIMEOUT_SECOND_LEVEL_INPUT_CONFIRMED",
17: "HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL_INPUT_CONFIRMED",
18: "LEASE_COMMITMENT_TIME_LOCK",
19: "LEASE_COMMITMENT_TO_REMOTE_CONFIRMED",
20: "LEASE_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL",
21: "LEASE_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL",
22: "TAPROOT_PUB_KEY_SPEND",
23: "TAPROOT_LOCAL_COMMIT_SPEND",
24: "TAPROOT_REMOTE_COMMIT_SPEND",
25: "TAPROOT_ANCHOR_SWEEP_SPEND",
26: "TAPROOT_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL",
27: "TAPROOT_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL",
28: "TAPROOT_HTLC_SECOND_LEVEL_REVOKE",
29: "TAPROOT_HTLC_ACCEPTED_REVOKE",
30: "TAPROOT_HTLC_OFFERED_REVOKE",
31: "TAPROOT_HTLC_OFFERED_REMOTE_TIMEOUT",
32: "TAPROOT_HTLC_LOCAL_OFFERED_TIMEOUT",
33: "TAPROOT_HTLC_ACCEPTED_REMOTE_SUCCESS",
34: "TAPROOT_HTLC_ACCEPTED_LOCAL_SUCCESS",
35: "TAPROOT_COMMITMENT_REVOKE",
}
WitnessType_value = map[string]int32{
"UNKNOWN_WITNESS": 0,
"COMMITMENT_TIME_LOCK": 1,
"COMMITMENT_NO_DELAY": 2,
"COMMITMENT_REVOKE": 3,
"HTLC_OFFERED_REVOKE": 4,
"HTLC_ACCEPTED_REVOKE": 5,
"HTLC_OFFERED_TIMEOUT_SECOND_LEVEL": 6,
"HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL": 7,
"HTLC_OFFERED_REMOTE_TIMEOUT": 8,
"HTLC_ACCEPTED_REMOTE_SUCCESS": 9,
"HTLC_SECOND_LEVEL_REVOKE": 10,
"WITNESS_KEY_HASH": 11,
"NESTED_WITNESS_KEY_HASH": 12,
"COMMITMENT_ANCHOR": 13,
"COMMITMENT_NO_DELAY_TWEAKLESS": 14,
"COMMITMENT_TO_REMOTE_CONFIRMED": 15,
"HTLC_OFFERED_TIMEOUT_SECOND_LEVEL_INPUT_CONFIRMED": 16,
"HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL_INPUT_CONFIRMED": 17,
"LEASE_COMMITMENT_TIME_LOCK": 18,
"LEASE_COMMITMENT_TO_REMOTE_CONFIRMED": 19,
"LEASE_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL": 20,
"LEASE_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL": 21,
"TAPROOT_PUB_KEY_SPEND": 22,
"TAPROOT_LOCAL_COMMIT_SPEND": 23,
"TAPROOT_REMOTE_COMMIT_SPEND": 24,
"TAPROOT_ANCHOR_SWEEP_SPEND": 25,
"TAPROOT_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL": 26,
"TAPROOT_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL": 27,
"TAPROOT_HTLC_SECOND_LEVEL_REVOKE": 28,
"TAPROOT_HTLC_ACCEPTED_REVOKE": 29,
"TAPROOT_HTLC_OFFERED_REVOKE": 30,
"TAPROOT_HTLC_OFFERED_REMOTE_TIMEOUT": 31,
"TAPROOT_HTLC_LOCAL_OFFERED_TIMEOUT": 32,
"TAPROOT_HTLC_ACCEPTED_REMOTE_SUCCESS": 33,
"TAPROOT_HTLC_ACCEPTED_LOCAL_SUCCESS": 34,
"TAPROOT_COMMITMENT_REVOKE": 35,
}
)
func (x WitnessType) Enum() *WitnessType {
p := new(WitnessType)
*p = x
return p
}
func (x WitnessType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (WitnessType) Descriptor() protoreflect.EnumDescriptor {
return file_walletrpc_walletkit_proto_enumTypes[1].Descriptor()
}
func (WitnessType) Type() protoreflect.EnumType {
return &file_walletrpc_walletkit_proto_enumTypes[1]
}
func (x WitnessType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use WitnessType.Descriptor instead.
func (WitnessType) EnumDescriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{1}
}
// The possible change address types for default accounts and single imported
// public keys. By default, P2WPKH will be used. We don't provide the
// possibility to choose P2PKH as it is a legacy key scope, nor NP2WPKH as
// no key scope permits to do so. For custom accounts, no change type should
// be provided as the coin selection key scope will always be used to generate
// the change address.
type ChangeAddressType int32
const (
// CHANGE_ADDRESS_TYPE_UNSPECIFIED indicates that no change address type is
// provided. We will then use P2WPKH address type for change (BIP0084 key
// scope).
ChangeAddressType_CHANGE_ADDRESS_TYPE_UNSPECIFIED ChangeAddressType = 0
// CHANGE_ADDRESS_TYPE_P2TR indicates to use P2TR address for change output
// (BIP0086 key scope).
ChangeAddressType_CHANGE_ADDRESS_TYPE_P2TR ChangeAddressType = 1
)
// Enum value maps for ChangeAddressType.
var (
ChangeAddressType_name = map[int32]string{
0: "CHANGE_ADDRESS_TYPE_UNSPECIFIED",
1: "CHANGE_ADDRESS_TYPE_P2TR",
}
ChangeAddressType_value = map[string]int32{
"CHANGE_ADDRESS_TYPE_UNSPECIFIED": 0,
"CHANGE_ADDRESS_TYPE_P2TR": 1,
}
)
func (x ChangeAddressType) Enum() *ChangeAddressType {
p := new(ChangeAddressType)
*p = x
return p
}
func (x ChangeAddressType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (ChangeAddressType) Descriptor() protoreflect.EnumDescriptor {
return file_walletrpc_walletkit_proto_enumTypes[2].Descriptor()
}
func (ChangeAddressType) Type() protoreflect.EnumType {
return &file_walletrpc_walletkit_proto_enumTypes[2]
}
func (x ChangeAddressType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use ChangeAddressType.Descriptor instead.
func (ChangeAddressType) EnumDescriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{2}
}
type ListUnspentRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The minimum number of confirmations to be included.
MinConfs int32 `protobuf:"varint,1,opt,name=min_confs,json=minConfs,proto3" json:"min_confs,omitempty"`
// The maximum number of confirmations to be included.
MaxConfs int32 `protobuf:"varint,2,opt,name=max_confs,json=maxConfs,proto3" json:"max_confs,omitempty"`
// An optional filter to only include outputs belonging to an account.
Account string `protobuf:"bytes,3,opt,name=account,proto3" json:"account,omitempty"`
// When min_confs and max_confs are zero, setting false implicitly
// overrides max_confs to be MaxInt32, otherwise max_confs remains
// zero. An error is returned if the value is true and both min_confs
// and max_confs are non-zero. (default: false)
UnconfirmedOnly bool `protobuf:"varint,4,opt,name=unconfirmed_only,json=unconfirmedOnly,proto3" json:"unconfirmed_only,omitempty"`
}
func (x *ListUnspentRequest) Reset() {
*x = ListUnspentRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListUnspentRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListUnspentRequest) ProtoMessage() {}
func (x *ListUnspentRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListUnspentRequest.ProtoReflect.Descriptor instead.
func (*ListUnspentRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{0}
}
func (x *ListUnspentRequest) GetMinConfs() int32 {
if x != nil {
return x.MinConfs
}
return 0
}
func (x *ListUnspentRequest) GetMaxConfs() int32 {
if x != nil {
return x.MaxConfs
}
return 0
}
func (x *ListUnspentRequest) GetAccount() string {
if x != nil {
return x.Account
}
return ""
}
func (x *ListUnspentRequest) GetUnconfirmedOnly() bool {
if x != nil {
return x.UnconfirmedOnly
}
return false
}
type ListUnspentResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A list of utxos satisfying the specified number of confirmations.
Utxos []*lnrpc.Utxo `protobuf:"bytes,1,rep,name=utxos,proto3" json:"utxos,omitempty"`
}
func (x *ListUnspentResponse) Reset() {
*x = ListUnspentResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListUnspentResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListUnspentResponse) ProtoMessage() {}
func (x *ListUnspentResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListUnspentResponse.ProtoReflect.Descriptor instead.
func (*ListUnspentResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{1}
}
func (x *ListUnspentResponse) GetUtxos() []*lnrpc.Utxo {
if x != nil {
return x.Utxos
}
return nil
}
type LeaseOutputRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// An ID of 32 random bytes that must be unique for each distinct application
// using this RPC which will be used to bound the output lease to.
Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
// The identifying outpoint of the output being leased.
Outpoint *lnrpc.OutPoint `protobuf:"bytes,2,opt,name=outpoint,proto3" json:"outpoint,omitempty"`
// The time in seconds before the lock expires. If set to zero, the default
// lock duration is used.
ExpirationSeconds uint64 `protobuf:"varint,3,opt,name=expiration_seconds,json=expirationSeconds,proto3" json:"expiration_seconds,omitempty"`
}
func (x *LeaseOutputRequest) Reset() {
*x = LeaseOutputRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *LeaseOutputRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LeaseOutputRequest) ProtoMessage() {}
func (x *LeaseOutputRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LeaseOutputRequest.ProtoReflect.Descriptor instead.
func (*LeaseOutputRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{2}
}
func (x *LeaseOutputRequest) GetId() []byte {
if x != nil {
return x.Id
}
return nil
}
func (x *LeaseOutputRequest) GetOutpoint() *lnrpc.OutPoint {
if x != nil {
return x.Outpoint
}
return nil
}
func (x *LeaseOutputRequest) GetExpirationSeconds() uint64 {
if x != nil {
return x.ExpirationSeconds
}
return 0
}
type LeaseOutputResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The absolute expiration of the output lease represented as a unix timestamp.
Expiration uint64 `protobuf:"varint,1,opt,name=expiration,proto3" json:"expiration,omitempty"`
}
func (x *LeaseOutputResponse) Reset() {
*x = LeaseOutputResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *LeaseOutputResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LeaseOutputResponse) ProtoMessage() {}
func (x *LeaseOutputResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LeaseOutputResponse.ProtoReflect.Descriptor instead.
func (*LeaseOutputResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{3}
}
func (x *LeaseOutputResponse) GetExpiration() uint64 {
if x != nil {
return x.Expiration
}
return 0
}
type ReleaseOutputRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The unique ID that was used to lock the output.
Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
// The identifying outpoint of the output being released.
Outpoint *lnrpc.OutPoint `protobuf:"bytes,2,opt,name=outpoint,proto3" json:"outpoint,omitempty"`
}
func (x *ReleaseOutputRequest) Reset() {
*x = ReleaseOutputRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ReleaseOutputRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ReleaseOutputRequest) ProtoMessage() {}
func (x *ReleaseOutputRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ReleaseOutputRequest.ProtoReflect.Descriptor instead.
func (*ReleaseOutputRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{4}
}
func (x *ReleaseOutputRequest) GetId() []byte {
if x != nil {
return x.Id
}
return nil
}
func (x *ReleaseOutputRequest) GetOutpoint() *lnrpc.OutPoint {
if x != nil {
return x.Outpoint
}
return nil
}
type ReleaseOutputResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The status of the release operation.
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
}
func (x *ReleaseOutputResponse) Reset() {
*x = ReleaseOutputResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ReleaseOutputResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ReleaseOutputResponse) ProtoMessage() {}
func (x *ReleaseOutputResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ReleaseOutputResponse.ProtoReflect.Descriptor instead.
func (*ReleaseOutputResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{5}
}
func (x *ReleaseOutputResponse) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
type KeyReq struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Is the key finger print of the root pubkey that this request is targeting.
// This allows the WalletKit to possibly serve out keys for multiple HD chains
// via public derivation.
KeyFingerPrint int32 `protobuf:"varint,1,opt,name=key_finger_print,json=keyFingerPrint,proto3" json:"key_finger_print,omitempty"`
// The target key family to derive a key from. In other contexts, this is
// known as the "account".
KeyFamily int32 `protobuf:"varint,2,opt,name=key_family,json=keyFamily,proto3" json:"key_family,omitempty"`
}
func (x *KeyReq) Reset() {
*x = KeyReq{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *KeyReq) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*KeyReq) ProtoMessage() {}
func (x *KeyReq) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use KeyReq.ProtoReflect.Descriptor instead.
func (*KeyReq) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{6}
}
func (x *KeyReq) GetKeyFingerPrint() int32 {
if x != nil {
return x.KeyFingerPrint
}
return 0
}
func (x *KeyReq) GetKeyFamily() int32 {
if x != nil {
return x.KeyFamily
}
return 0
}
type AddrRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The name of the account to retrieve the next address of. If empty, the
// default wallet account is used.
Account string `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"`
// The type of address to derive.
Type AddressType `protobuf:"varint,2,opt,name=type,proto3,enum=walletrpc.AddressType" json:"type,omitempty"`
// Whether a change address should be derived.
Change bool `protobuf:"varint,3,opt,name=change,proto3" json:"change,omitempty"`
}
func (x *AddrRequest) Reset() {
*x = AddrRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AddrRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AddrRequest) ProtoMessage() {}
func (x *AddrRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AddrRequest.ProtoReflect.Descriptor instead.
func (*AddrRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{7}
}
func (x *AddrRequest) GetAccount() string {
if x != nil {
return x.Account
}
return ""
}
func (x *AddrRequest) GetType() AddressType {
if x != nil {
return x.Type
}
return AddressType_UNKNOWN
}
func (x *AddrRequest) GetChange() bool {
if x != nil {
return x.Change
}
return false
}
type AddrResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The address encoded using a bech32 format.
Addr string `protobuf:"bytes,1,opt,name=addr,proto3" json:"addr,omitempty"`
}
func (x *AddrResponse) Reset() {
*x = AddrResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AddrResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AddrResponse) ProtoMessage() {}
func (x *AddrResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AddrResponse.ProtoReflect.Descriptor instead.
func (*AddrResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{8}
}
func (x *AddrResponse) GetAddr() string {
if x != nil {
return x.Addr
}
return ""
}
type Account struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The name used to identify the account.
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
// The type of addresses the account supports.
AddressType AddressType `protobuf:"varint,2,opt,name=address_type,json=addressType,proto3,enum=walletrpc.AddressType" json:"address_type,omitempty"`
// The public key backing the account that all keys are derived from
// represented as an extended key. This will always be empty for the default
// imported account in which single public keys are imported into.
ExtendedPublicKey string `protobuf:"bytes,3,opt,name=extended_public_key,json=extendedPublicKey,proto3" json:"extended_public_key,omitempty"`
// The fingerprint of the root key from which the account public key was
// derived from. This will always be zero for the default imported account in
// which single public keys are imported into. The bytes are in big-endian
// order.
MasterKeyFingerprint []byte `protobuf:"bytes,4,opt,name=master_key_fingerprint,json=masterKeyFingerprint,proto3" json:"master_key_fingerprint,omitempty"`
// The derivation path corresponding to the account public key. This will
// always be empty for the default imported account in which single public keys
// are imported into.
DerivationPath string `protobuf:"bytes,5,opt,name=derivation_path,json=derivationPath,proto3" json:"derivation_path,omitempty"`
// The number of keys derived from the external branch of the account public
// key. This will always be zero for the default imported account in which
// single public keys are imported into.
ExternalKeyCount uint32 `protobuf:"varint,6,opt,name=external_key_count,json=externalKeyCount,proto3" json:"external_key_count,omitempty"`
// The number of keys derived from the internal branch of the account public
// key. This will always be zero for the default imported account in which
// single public keys are imported into.
InternalKeyCount uint32 `protobuf:"varint,7,opt,name=internal_key_count,json=internalKeyCount,proto3" json:"internal_key_count,omitempty"`
// Whether the wallet stores private keys for the account.
WatchOnly bool `protobuf:"varint,8,opt,name=watch_only,json=watchOnly,proto3" json:"watch_only,omitempty"`
}
func (x *Account) Reset() {
*x = Account{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Account) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Account) ProtoMessage() {}
func (x *Account) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Account.ProtoReflect.Descriptor instead.
func (*Account) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{9}
}
func (x *Account) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Account) GetAddressType() AddressType {
if x != nil {
return x.AddressType
}
return AddressType_UNKNOWN
}
func (x *Account) GetExtendedPublicKey() string {
if x != nil {
return x.ExtendedPublicKey
}
return ""
}
func (x *Account) GetMasterKeyFingerprint() []byte {
if x != nil {
return x.MasterKeyFingerprint
}
return nil
}
func (x *Account) GetDerivationPath() string {
if x != nil {
return x.DerivationPath
}
return ""
}
func (x *Account) GetExternalKeyCount() uint32 {
if x != nil {
return x.ExternalKeyCount
}
return 0
}
func (x *Account) GetInternalKeyCount() uint32 {
if x != nil {
return x.InternalKeyCount
}
return 0
}
func (x *Account) GetWatchOnly() bool {
if x != nil {
return x.WatchOnly
}
return false
}
type AddressProperty struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The address encoded using the appropriate format depending on the
// address type (base58, bech32, bech32m).
//
// Note that lnd's internal/custom keys for channels and other
// functionality are derived from the same scope. Since they
// aren't really used as addresses and will never have an
// on-chain balance, we'll show the public key instead (only if
// the show_custom_accounts flag is provided).
Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
// Denotes if the address is a change address.
IsInternal bool `protobuf:"varint,2,opt,name=is_internal,json=isInternal,proto3" json:"is_internal,omitempty"`
// The balance of the address.
Balance int64 `protobuf:"varint,3,opt,name=balance,proto3" json:"balance,omitempty"`
// The full derivation path of the address. This will be empty for imported
// addresses.
DerivationPath string `protobuf:"bytes,4,opt,name=derivation_path,json=derivationPath,proto3" json:"derivation_path,omitempty"`
// The public key of the address. This will be empty for imported addresses.
PublicKey []byte `protobuf:"bytes,5,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
}
func (x *AddressProperty) Reset() {
*x = AddressProperty{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AddressProperty) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AddressProperty) ProtoMessage() {}
func (x *AddressProperty) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AddressProperty.ProtoReflect.Descriptor instead.
func (*AddressProperty) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{10}
}
func (x *AddressProperty) GetAddress() string {
if x != nil {
return x.Address
}
return ""
}
func (x *AddressProperty) GetIsInternal() bool {
if x != nil {
return x.IsInternal
}
return false
}
func (x *AddressProperty) GetBalance() int64 {
if x != nil {
return x.Balance
}
return 0
}
func (x *AddressProperty) GetDerivationPath() string {
if x != nil {
return x.DerivationPath
}
return ""
}
func (x *AddressProperty) GetPublicKey() []byte {
if x != nil {
return x.PublicKey
}
return nil
}
type AccountWithAddresses struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The name used to identify the account.
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
// The type of addresses the account supports.
AddressType AddressType `protobuf:"varint,2,opt,name=address_type,json=addressType,proto3,enum=walletrpc.AddressType" json:"address_type,omitempty"`
// The derivation path corresponding to the account public key. This will
// always be empty for the default imported account in which single public keys
// are imported into.
DerivationPath string `protobuf:"bytes,3,opt,name=derivation_path,json=derivationPath,proto3" json:"derivation_path,omitempty"`
// List of address, its type internal/external & balance.
// Note that the order of addresses will be random and not according to the
// derivation index, since that information is not stored by the underlying
// wallet.
Addresses []*AddressProperty `protobuf:"bytes,4,rep,name=addresses,proto3" json:"addresses,omitempty"`
}
func (x *AccountWithAddresses) Reset() {
*x = AccountWithAddresses{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AccountWithAddresses) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AccountWithAddresses) ProtoMessage() {}
func (x *AccountWithAddresses) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[11]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AccountWithAddresses.ProtoReflect.Descriptor instead.
func (*AccountWithAddresses) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{11}
}
func (x *AccountWithAddresses) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *AccountWithAddresses) GetAddressType() AddressType {
if x != nil {
return x.AddressType
}
return AddressType_UNKNOWN
}
func (x *AccountWithAddresses) GetDerivationPath() string {
if x != nil {
return x.DerivationPath
}
return ""
}
func (x *AccountWithAddresses) GetAddresses() []*AddressProperty {
if x != nil {
return x.Addresses
}
return nil
}
type ListAccountsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// An optional filter to only return accounts matching this name.
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
// An optional filter to only return accounts matching this address type.
AddressType AddressType `protobuf:"varint,2,opt,name=address_type,json=addressType,proto3,enum=walletrpc.AddressType" json:"address_type,omitempty"`
}
func (x *ListAccountsRequest) Reset() {
*x = ListAccountsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListAccountsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListAccountsRequest) ProtoMessage() {}
func (x *ListAccountsRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[12]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListAccountsRequest.ProtoReflect.Descriptor instead.
func (*ListAccountsRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{12}
}
func (x *ListAccountsRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *ListAccountsRequest) GetAddressType() AddressType {
if x != nil {
return x.AddressType
}
return AddressType_UNKNOWN
}
type ListAccountsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Accounts []*Account `protobuf:"bytes,1,rep,name=accounts,proto3" json:"accounts,omitempty"`
}
func (x *ListAccountsResponse) Reset() {
*x = ListAccountsResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListAccountsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListAccountsResponse) ProtoMessage() {}
func (x *ListAccountsResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[13]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListAccountsResponse.ProtoReflect.Descriptor instead.
func (*ListAccountsResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{13}
}
func (x *ListAccountsResponse) GetAccounts() []*Account {
if x != nil {
return x.Accounts
}
return nil
}
type RequiredReserveRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The number of additional channels the user would like to open.
AdditionalPublicChannels uint32 `protobuf:"varint,1,opt,name=additional_public_channels,json=additionalPublicChannels,proto3" json:"additional_public_channels,omitempty"`
}
func (x *RequiredReserveRequest) Reset() {
*x = RequiredReserveRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RequiredReserveRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RequiredReserveRequest) ProtoMessage() {}
func (x *RequiredReserveRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[14]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RequiredReserveRequest.ProtoReflect.Descriptor instead.
func (*RequiredReserveRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{14}
}
func (x *RequiredReserveRequest) GetAdditionalPublicChannels() uint32 {
if x != nil {
return x.AdditionalPublicChannels
}
return 0
}
type RequiredReserveResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The amount of reserve required.
RequiredReserve int64 `protobuf:"varint,1,opt,name=required_reserve,json=requiredReserve,proto3" json:"required_reserve,omitempty"`
}
func (x *RequiredReserveResponse) Reset() {
*x = RequiredReserveResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RequiredReserveResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RequiredReserveResponse) ProtoMessage() {}
func (x *RequiredReserveResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[15]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RequiredReserveResponse.ProtoReflect.Descriptor instead.
func (*RequiredReserveResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{15}
}
func (x *RequiredReserveResponse) GetRequiredReserve() int64 {
if x != nil {
return x.RequiredReserve
}
return 0
}
type ListAddressesRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// An optional filter to only return addresses matching this account.
AccountName string `protobuf:"bytes,1,opt,name=account_name,json=accountName,proto3" json:"account_name,omitempty"`
// An optional flag to return LND's custom accounts (Purpose=1017)
// public key along with other addresses.
ShowCustomAccounts bool `protobuf:"varint,2,opt,name=show_custom_accounts,json=showCustomAccounts,proto3" json:"show_custom_accounts,omitempty"`
}
func (x *ListAddressesRequest) Reset() {
*x = ListAddressesRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListAddressesRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListAddressesRequest) ProtoMessage() {}
func (x *ListAddressesRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[16]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListAddressesRequest.ProtoReflect.Descriptor instead.
func (*ListAddressesRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{16}
}
func (x *ListAddressesRequest) GetAccountName() string {
if x != nil {
return x.AccountName
}
return ""
}
func (x *ListAddressesRequest) GetShowCustomAccounts() bool {
if x != nil {
return x.ShowCustomAccounts
}
return false
}
type ListAddressesResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A list of all the accounts and their addresses.
AccountWithAddresses []*AccountWithAddresses `protobuf:"bytes,1,rep,name=account_with_addresses,json=accountWithAddresses,proto3" json:"account_with_addresses,omitempty"`
}
func (x *ListAddressesResponse) Reset() {
*x = ListAddressesResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListAddressesResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListAddressesResponse) ProtoMessage() {}
func (x *ListAddressesResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[17]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListAddressesResponse.ProtoReflect.Descriptor instead.
func (*ListAddressesResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{17}
}
func (x *ListAddressesResponse) GetAccountWithAddresses() []*AccountWithAddresses {
if x != nil {
return x.AccountWithAddresses
}
return nil
}
type GetTransactionRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The txid of the transaction.
Txid string `protobuf:"bytes,1,opt,name=txid,proto3" json:"txid,omitempty"`
}
func (x *GetTransactionRequest) Reset() {
*x = GetTransactionRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetTransactionRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetTransactionRequest) ProtoMessage() {}
func (x *GetTransactionRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[18]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetTransactionRequest.ProtoReflect.Descriptor instead.
func (*GetTransactionRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{18}
}
func (x *GetTransactionRequest) GetTxid() string {
if x != nil {
return x.Txid
}
return ""
}
type SignMessageWithAddrRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The message to be signed. When using REST, this field must be encoded as
// base64.
Msg []byte `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"`
// The address which will be used to look up the private key and sign the
// corresponding message.
Addr string `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,omitempty"`
}
func (x *SignMessageWithAddrRequest) Reset() {
*x = SignMessageWithAddrRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[19]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SignMessageWithAddrRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SignMessageWithAddrRequest) ProtoMessage() {}
func (x *SignMessageWithAddrRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[19]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SignMessageWithAddrRequest.ProtoReflect.Descriptor instead.
func (*SignMessageWithAddrRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{19}
}
func (x *SignMessageWithAddrRequest) GetMsg() []byte {
if x != nil {
return x.Msg
}
return nil
}
func (x *SignMessageWithAddrRequest) GetAddr() string {
if x != nil {
return x.Addr
}
return ""
}
type SignMessageWithAddrResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The compact ECDSA signature for the given message encoded in base64.
Signature string `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"`
}
func (x *SignMessageWithAddrResponse) Reset() {
*x = SignMessageWithAddrResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[20]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SignMessageWithAddrResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SignMessageWithAddrResponse) ProtoMessage() {}
func (x *SignMessageWithAddrResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[20]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SignMessageWithAddrResponse.ProtoReflect.Descriptor instead.
func (*SignMessageWithAddrResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{20}
}
func (x *SignMessageWithAddrResponse) GetSignature() string {
if x != nil {
return x.Signature
}
return ""
}
type VerifyMessageWithAddrRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The message to be signed. When using REST, this field must be encoded as
// base64.
Msg []byte `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"`
// The compact ECDSA signature to be verified over the given message
// ecoded in base64.
Signature string `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"`
// The address which will be used to look up the public key and verify the
// the signature.
Addr string `protobuf:"bytes,3,opt,name=addr,proto3" json:"addr,omitempty"`
}
func (x *VerifyMessageWithAddrRequest) Reset() {
*x = VerifyMessageWithAddrRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[21]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *VerifyMessageWithAddrRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*VerifyMessageWithAddrRequest) ProtoMessage() {}
func (x *VerifyMessageWithAddrRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[21]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use VerifyMessageWithAddrRequest.ProtoReflect.Descriptor instead.
func (*VerifyMessageWithAddrRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{21}
}
func (x *VerifyMessageWithAddrRequest) GetMsg() []byte {
if x != nil {
return x.Msg
}
return nil
}
func (x *VerifyMessageWithAddrRequest) GetSignature() string {
if x != nil {
return x.Signature
}
return ""
}
func (x *VerifyMessageWithAddrRequest) GetAddr() string {
if x != nil {
return x.Addr
}
return ""
}
type VerifyMessageWithAddrResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Whether the signature was valid over the given message.
Valid bool `protobuf:"varint,1,opt,name=valid,proto3" json:"valid,omitempty"`
// The pubkey recovered from the signature.
Pubkey []byte `protobuf:"bytes,2,opt,name=pubkey,proto3" json:"pubkey,omitempty"`
}
func (x *VerifyMessageWithAddrResponse) Reset() {
*x = VerifyMessageWithAddrResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[22]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *VerifyMessageWithAddrResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*VerifyMessageWithAddrResponse) ProtoMessage() {}
func (x *VerifyMessageWithAddrResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[22]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use VerifyMessageWithAddrResponse.ProtoReflect.Descriptor instead.
func (*VerifyMessageWithAddrResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{22}
}
func (x *VerifyMessageWithAddrResponse) GetValid() bool {
if x != nil {
return x.Valid
}
return false
}
func (x *VerifyMessageWithAddrResponse) GetPubkey() []byte {
if x != nil {
return x.Pubkey
}
return nil
}
type ImportAccountRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A name to identify the account with.
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
// A public key that corresponds to a wallet account represented as an extended
// key. It must conform to a derivation path of the form
// m/purpose'/coin_type'/account'.
ExtendedPublicKey string `protobuf:"bytes,2,opt,name=extended_public_key,json=extendedPublicKey,proto3" json:"extended_public_key,omitempty"`
// The fingerprint of the root key (also known as the key with derivation path
// m/) from which the account public key was derived from. This may be required
// by some hardware wallets for proper identification and signing. The bytes
// must be in big-endian order.
MasterKeyFingerprint []byte `protobuf:"bytes,3,opt,name=master_key_fingerprint,json=masterKeyFingerprint,proto3" json:"master_key_fingerprint,omitempty"`
// An address type is only required when the extended account public key has a
// legacy version (xpub, tpub, etc.), such that the wallet cannot detect what
// address scheme it belongs to.
AddressType AddressType `protobuf:"varint,4,opt,name=address_type,json=addressType,proto3,enum=walletrpc.AddressType" json:"address_type,omitempty"`
// Whether a dry run should be attempted when importing the account. This
// serves as a way to confirm whether the account is being imported correctly
// by returning the first N addresses for the external and internal branches of
// the account. If these addresses match as expected, then it should be safe to
// import the account as is.
DryRun bool `protobuf:"varint,5,opt,name=dry_run,json=dryRun,proto3" json:"dry_run,omitempty"`
}
func (x *ImportAccountRequest) Reset() {
*x = ImportAccountRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[23]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ImportAccountRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ImportAccountRequest) ProtoMessage() {}
func (x *ImportAccountRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[23]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ImportAccountRequest.ProtoReflect.Descriptor instead.
func (*ImportAccountRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{23}
}
func (x *ImportAccountRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *ImportAccountRequest) GetExtendedPublicKey() string {
if x != nil {
return x.ExtendedPublicKey
}
return ""
}
func (x *ImportAccountRequest) GetMasterKeyFingerprint() []byte {
if x != nil {
return x.MasterKeyFingerprint
}
return nil
}
func (x *ImportAccountRequest) GetAddressType() AddressType {
if x != nil {
return x.AddressType
}
return AddressType_UNKNOWN
}
func (x *ImportAccountRequest) GetDryRun() bool {
if x != nil {
return x.DryRun
}
return false
}
type ImportAccountResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The details of the imported account.
Account *Account `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"`
// The first N addresses that belong to the external branch of the account.
// The external branch is typically used for external non-change addresses.
// These are only returned if a dry run was specified within the request.
DryRunExternalAddrs []string `protobuf:"bytes,2,rep,name=dry_run_external_addrs,json=dryRunExternalAddrs,proto3" json:"dry_run_external_addrs,omitempty"`
// The first N addresses that belong to the internal branch of the account.
// The internal branch is typically used for change addresses. These are only
// returned if a dry run was specified within the request.
DryRunInternalAddrs []string `protobuf:"bytes,3,rep,name=dry_run_internal_addrs,json=dryRunInternalAddrs,proto3" json:"dry_run_internal_addrs,omitempty"`
}
func (x *ImportAccountResponse) Reset() {
*x = ImportAccountResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[24]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ImportAccountResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ImportAccountResponse) ProtoMessage() {}
func (x *ImportAccountResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[24]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ImportAccountResponse.ProtoReflect.Descriptor instead.
func (*ImportAccountResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{24}
}
func (x *ImportAccountResponse) GetAccount() *Account {
if x != nil {
return x.Account
}
return nil
}
func (x *ImportAccountResponse) GetDryRunExternalAddrs() []string {
if x != nil {
return x.DryRunExternalAddrs
}
return nil
}
func (x *ImportAccountResponse) GetDryRunInternalAddrs() []string {
if x != nil {
return x.DryRunInternalAddrs
}
return nil
}
type ImportPublicKeyRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A compressed public key represented as raw bytes.
PublicKey []byte `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
// The type of address that will be generated from the public key.
AddressType AddressType `protobuf:"varint,2,opt,name=address_type,json=addressType,proto3,enum=walletrpc.AddressType" json:"address_type,omitempty"`
}
func (x *ImportPublicKeyRequest) Reset() {
*x = ImportPublicKeyRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[25]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ImportPublicKeyRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ImportPublicKeyRequest) ProtoMessage() {}
func (x *ImportPublicKeyRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[25]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ImportPublicKeyRequest.ProtoReflect.Descriptor instead.
func (*ImportPublicKeyRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{25}
}
func (x *ImportPublicKeyRequest) GetPublicKey() []byte {
if x != nil {
return x.PublicKey
}
return nil
}
func (x *ImportPublicKeyRequest) GetAddressType() AddressType {
if x != nil {
return x.AddressType
}
return AddressType_UNKNOWN
}
type ImportPublicKeyResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The status of the import operation.
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
}
func (x *ImportPublicKeyResponse) Reset() {
*x = ImportPublicKeyResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[26]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ImportPublicKeyResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ImportPublicKeyResponse) ProtoMessage() {}
func (x *ImportPublicKeyResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[26]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ImportPublicKeyResponse.ProtoReflect.Descriptor instead.
func (*ImportPublicKeyResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{26}
}
func (x *ImportPublicKeyResponse) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
type ImportTapscriptRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The internal public key, serialized as 32-byte x-only public key.
InternalPublicKey []byte `protobuf:"bytes,1,opt,name=internal_public_key,json=internalPublicKey,proto3" json:"internal_public_key,omitempty"`
// Types that are assignable to Script:
//
// *ImportTapscriptRequest_FullTree
// *ImportTapscriptRequest_PartialReveal
// *ImportTapscriptRequest_RootHashOnly
// *ImportTapscriptRequest_FullKeyOnly
Script isImportTapscriptRequest_Script `protobuf_oneof:"script"`
}
func (x *ImportTapscriptRequest) Reset() {
*x = ImportTapscriptRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[27]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ImportTapscriptRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ImportTapscriptRequest) ProtoMessage() {}
func (x *ImportTapscriptRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[27]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ImportTapscriptRequest.ProtoReflect.Descriptor instead.
func (*ImportTapscriptRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{27}
}
func (x *ImportTapscriptRequest) GetInternalPublicKey() []byte {
if x != nil {
return x.InternalPublicKey
}
return nil
}
func (m *ImportTapscriptRequest) GetScript() isImportTapscriptRequest_Script {
if m != nil {
return m.Script
}
return nil
}
func (x *ImportTapscriptRequest) GetFullTree() *TapscriptFullTree {
if x, ok := x.GetScript().(*ImportTapscriptRequest_FullTree); ok {
return x.FullTree
}
return nil
}
func (x *ImportTapscriptRequest) GetPartialReveal() *TapscriptPartialReveal {
if x, ok := x.GetScript().(*ImportTapscriptRequest_PartialReveal); ok {
return x.PartialReveal
}
return nil
}
func (x *ImportTapscriptRequest) GetRootHashOnly() []byte {
if x, ok := x.GetScript().(*ImportTapscriptRequest_RootHashOnly); ok {
return x.RootHashOnly
}
return nil
}
func (x *ImportTapscriptRequest) GetFullKeyOnly() bool {
if x, ok := x.GetScript().(*ImportTapscriptRequest_FullKeyOnly); ok {
return x.FullKeyOnly
}
return false
}
type isImportTapscriptRequest_Script interface {
isImportTapscriptRequest_Script()
}
type ImportTapscriptRequest_FullTree struct {
// The full script tree with all individual leaves is known and the root
// hash can be constructed from the full tree directly.
FullTree *TapscriptFullTree `protobuf:"bytes,2,opt,name=full_tree,json=fullTree,proto3,oneof"`
}
type ImportTapscriptRequest_PartialReveal struct {
// Only a single script leaf is known. To construct the root hash, the full
// inclusion proof must also be provided.
PartialReveal *TapscriptPartialReveal `protobuf:"bytes,3,opt,name=partial_reveal,json=partialReveal,proto3,oneof"`
}
type ImportTapscriptRequest_RootHashOnly struct {
// Only the root hash of the Taproot script tree (or other form of Taproot
// commitment) is known.
RootHashOnly []byte `protobuf:"bytes,4,opt,name=root_hash_only,json=rootHashOnly,proto3,oneof"`
}
type ImportTapscriptRequest_FullKeyOnly struct {
// Only the final, tweaked Taproot key is known and no additional
// information about the internal key or type of tweak that was used to
// derive it. When this is set, the wallet treats the key in
// internal_public_key as the Taproot key directly. This can be useful for
// tracking arbitrary Taproot outputs without the goal of ever being able
// to spend from them through the internal wallet.
FullKeyOnly bool `protobuf:"varint,5,opt,name=full_key_only,json=fullKeyOnly,proto3,oneof"`
}
func (*ImportTapscriptRequest_FullTree) isImportTapscriptRequest_Script() {}
func (*ImportTapscriptRequest_PartialReveal) isImportTapscriptRequest_Script() {}
func (*ImportTapscriptRequest_RootHashOnly) isImportTapscriptRequest_Script() {}
func (*ImportTapscriptRequest_FullKeyOnly) isImportTapscriptRequest_Script() {}
type TapscriptFullTree struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The complete, ordered list of all tap leaves of the tree.
AllLeaves []*TapLeaf `protobuf:"bytes,1,rep,name=all_leaves,json=allLeaves,proto3" json:"all_leaves,omitempty"`
}
func (x *TapscriptFullTree) Reset() {
*x = TapscriptFullTree{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[28]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TapscriptFullTree) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TapscriptFullTree) ProtoMessage() {}
func (x *TapscriptFullTree) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[28]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TapscriptFullTree.ProtoReflect.Descriptor instead.
func (*TapscriptFullTree) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{28}
}
func (x *TapscriptFullTree) GetAllLeaves() []*TapLeaf {
if x != nil {
return x.AllLeaves
}
return nil
}
type TapLeaf struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The leaf version. Should be 0xc0 (192) in case of a SegWit v1 script.
LeafVersion uint32 `protobuf:"varint,1,opt,name=leaf_version,json=leafVersion,proto3" json:"leaf_version,omitempty"`
// The script of the tap leaf.
Script []byte `protobuf:"bytes,2,opt,name=script,proto3" json:"script,omitempty"`
}
func (x *TapLeaf) Reset() {
*x = TapLeaf{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[29]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TapLeaf) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TapLeaf) ProtoMessage() {}
func (x *TapLeaf) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[29]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TapLeaf.ProtoReflect.Descriptor instead.
func (*TapLeaf) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{29}
}
func (x *TapLeaf) GetLeafVersion() uint32 {
if x != nil {
return x.LeafVersion
}
return 0
}
func (x *TapLeaf) GetScript() []byte {
if x != nil {
return x.Script
}
return nil
}
type TapscriptPartialReveal struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The tap leaf that is known and will be revealed.
RevealedLeaf *TapLeaf `protobuf:"bytes,1,opt,name=revealed_leaf,json=revealedLeaf,proto3" json:"revealed_leaf,omitempty"`
// The BIP-0341 serialized inclusion proof that is required to prove that
// the revealed leaf is part of the tree. This contains 0..n blocks of 32
// bytes. If the tree only contained a single leaf (which is the revealed
// leaf), this can be empty.
FullInclusionProof []byte `protobuf:"bytes,2,opt,name=full_inclusion_proof,json=fullInclusionProof,proto3" json:"full_inclusion_proof,omitempty"`
}
func (x *TapscriptPartialReveal) Reset() {
*x = TapscriptPartialReveal{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[30]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TapscriptPartialReveal) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TapscriptPartialReveal) ProtoMessage() {}
func (x *TapscriptPartialReveal) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[30]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TapscriptPartialReveal.ProtoReflect.Descriptor instead.
func (*TapscriptPartialReveal) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{30}
}
func (x *TapscriptPartialReveal) GetRevealedLeaf() *TapLeaf {
if x != nil {
return x.RevealedLeaf
}
return nil
}
func (x *TapscriptPartialReveal) GetFullInclusionProof() []byte {
if x != nil {
return x.FullInclusionProof
}
return nil
}
type ImportTapscriptResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The resulting pay-to-Taproot address that represents the imported internal
// key with the script committed to it.
P2TrAddress string `protobuf:"bytes,1,opt,name=p2tr_address,json=p2trAddress,proto3" json:"p2tr_address,omitempty"`
}
func (x *ImportTapscriptResponse) Reset() {
*x = ImportTapscriptResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[31]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ImportTapscriptResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ImportTapscriptResponse) ProtoMessage() {}
func (x *ImportTapscriptResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[31]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ImportTapscriptResponse.ProtoReflect.Descriptor instead.
func (*ImportTapscriptResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{31}
}
func (x *ImportTapscriptResponse) GetP2TrAddress() string {
if x != nil {
return x.P2TrAddress
}
return ""
}
type Transaction struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The raw serialized transaction. Despite the field name, this does need to be
// specified in raw bytes (or base64 encoded when using REST) and not in hex.
// To not break existing software, the field can't simply be renamed.
TxHex []byte `protobuf:"bytes,1,opt,name=tx_hex,json=txHex,proto3" json:"tx_hex,omitempty"`
// An optional label to save with the transaction. Limited to 500 characters.
Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"`
}
func (x *Transaction) Reset() {
*x = Transaction{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[32]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Transaction) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Transaction) ProtoMessage() {}
func (x *Transaction) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[32]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Transaction.ProtoReflect.Descriptor instead.
func (*Transaction) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{32}
}
func (x *Transaction) GetTxHex() []byte {
if x != nil {
return x.TxHex
}
return nil
}
func (x *Transaction) GetLabel() string {
if x != nil {
return x.Label
}
return ""
}
type PublishResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// If blank, then no error occurred and the transaction was successfully
// published. If not the empty string, then a string representation of the
// broadcast error.
//
// TODO(roasbeef): map to a proper enum type
PublishError string `protobuf:"bytes,1,opt,name=publish_error,json=publishError,proto3" json:"publish_error,omitempty"`
}
func (x *PublishResponse) Reset() {
*x = PublishResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[33]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PublishResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PublishResponse) ProtoMessage() {}
func (x *PublishResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[33]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PublishResponse.ProtoReflect.Descriptor instead.
func (*PublishResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{33}
}
func (x *PublishResponse) GetPublishError() string {
if x != nil {
return x.PublishError
}
return ""
}
type RemoveTransactionResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The status of the remove transaction operation.
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
}
func (x *RemoveTransactionResponse) Reset() {
*x = RemoveTransactionResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[34]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RemoveTransactionResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RemoveTransactionResponse) ProtoMessage() {}
func (x *RemoveTransactionResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[34]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RemoveTransactionResponse.ProtoReflect.Descriptor instead.
func (*RemoveTransactionResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{34}
}
func (x *RemoveTransactionResponse) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
type SendOutputsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The number of satoshis per kilo weight that should be used when crafting
// this transaction.
SatPerKw int64 `protobuf:"varint,1,opt,name=sat_per_kw,json=satPerKw,proto3" json:"sat_per_kw,omitempty"`
// A slice of the outputs that should be created in the transaction produced.
Outputs []*signrpc.TxOut `protobuf:"bytes,2,rep,name=outputs,proto3" json:"outputs,omitempty"`
// An optional label for the transaction, limited to 500 characters.
Label string `protobuf:"bytes,3,opt,name=label,proto3" json:"label,omitempty"`
// The minimum number of confirmations each one of your outputs used for
// the transaction must satisfy.
MinConfs int32 `protobuf:"varint,4,opt,name=min_confs,json=minConfs,proto3" json:"min_confs,omitempty"`
// Whether unconfirmed outputs should be used as inputs for the transaction.
SpendUnconfirmed bool `protobuf:"varint,5,opt,name=spend_unconfirmed,json=spendUnconfirmed,proto3" json:"spend_unconfirmed,omitempty"`
// The strategy to use for selecting coins during sending the outputs.
CoinSelectionStrategy lnrpc.CoinSelectionStrategy `protobuf:"varint,6,opt,name=coin_selection_strategy,json=coinSelectionStrategy,proto3,enum=lnrpc.CoinSelectionStrategy" json:"coin_selection_strategy,omitempty"`
}
func (x *SendOutputsRequest) Reset() {
*x = SendOutputsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[35]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SendOutputsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendOutputsRequest) ProtoMessage() {}
func (x *SendOutputsRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[35]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendOutputsRequest.ProtoReflect.Descriptor instead.
func (*SendOutputsRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{35}
}
func (x *SendOutputsRequest) GetSatPerKw() int64 {
if x != nil {
return x.SatPerKw
}
return 0
}
func (x *SendOutputsRequest) GetOutputs() []*signrpc.TxOut {
if x != nil {
return x.Outputs
}
return nil
}
func (x *SendOutputsRequest) GetLabel() string {
if x != nil {
return x.Label
}
return ""
}
func (x *SendOutputsRequest) GetMinConfs() int32 {
if x != nil {
return x.MinConfs
}
return 0
}
func (x *SendOutputsRequest) GetSpendUnconfirmed() bool {
if x != nil {
return x.SpendUnconfirmed
}
return false
}
func (x *SendOutputsRequest) GetCoinSelectionStrategy() lnrpc.CoinSelectionStrategy {
if x != nil {
return x.CoinSelectionStrategy
}
return lnrpc.CoinSelectionStrategy(0)
}
type SendOutputsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The serialized transaction sent out on the network.
RawTx []byte `protobuf:"bytes,1,opt,name=raw_tx,json=rawTx,proto3" json:"raw_tx,omitempty"`
}
func (x *SendOutputsResponse) Reset() {
*x = SendOutputsResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[36]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SendOutputsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SendOutputsResponse) ProtoMessage() {}
func (x *SendOutputsResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[36]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SendOutputsResponse.ProtoReflect.Descriptor instead.
func (*SendOutputsResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{36}
}
func (x *SendOutputsResponse) GetRawTx() []byte {
if x != nil {
return x.RawTx
}
return nil
}
type EstimateFeeRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The number of confirmations to shoot for when estimating the fee.
ConfTarget int32 `protobuf:"varint,1,opt,name=conf_target,json=confTarget,proto3" json:"conf_target,omitempty"`
}
func (x *EstimateFeeRequest) Reset() {
*x = EstimateFeeRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[37]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *EstimateFeeRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EstimateFeeRequest) ProtoMessage() {}
func (x *EstimateFeeRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[37]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EstimateFeeRequest.ProtoReflect.Descriptor instead.
func (*EstimateFeeRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{37}
}
func (x *EstimateFeeRequest) GetConfTarget() int32 {
if x != nil {
return x.ConfTarget
}
return 0
}
type EstimateFeeResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The amount of satoshis per kw that should be used in order to reach the
// confirmation target in the request.
SatPerKw int64 `protobuf:"varint,1,opt,name=sat_per_kw,json=satPerKw,proto3" json:"sat_per_kw,omitempty"`
// The current minimum relay fee based on our chain backend in sat/kw.
MinRelayFeeSatPerKw int64 `protobuf:"varint,2,opt,name=min_relay_fee_sat_per_kw,json=minRelayFeeSatPerKw,proto3" json:"min_relay_fee_sat_per_kw,omitempty"`
}
func (x *EstimateFeeResponse) Reset() {
*x = EstimateFeeResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[38]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *EstimateFeeResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EstimateFeeResponse) ProtoMessage() {}
func (x *EstimateFeeResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[38]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EstimateFeeResponse.ProtoReflect.Descriptor instead.
func (*EstimateFeeResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{38}
}
func (x *EstimateFeeResponse) GetSatPerKw() int64 {
if x != nil {
return x.SatPerKw
}
return 0
}
func (x *EstimateFeeResponse) GetMinRelayFeeSatPerKw() int64 {
if x != nil {
return x.MinRelayFeeSatPerKw
}
return 0
}
type PendingSweep struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The outpoint of the output we're attempting to sweep.
Outpoint *lnrpc.OutPoint `protobuf:"bytes,1,opt,name=outpoint,proto3" json:"outpoint,omitempty"`
// The witness type of the output we're attempting to sweep.
WitnessType WitnessType `protobuf:"varint,2,opt,name=witness_type,json=witnessType,proto3,enum=walletrpc.WitnessType" json:"witness_type,omitempty"`
// The value of the output we're attempting to sweep.
AmountSat uint32 `protobuf:"varint,3,opt,name=amount_sat,json=amountSat,proto3" json:"amount_sat,omitempty"`
// Deprecated, use sat_per_vbyte.
// The fee rate we'll use to sweep the output, expressed in sat/vbyte. The fee
// rate is only determined once a sweeping transaction for the output is
// created, so it's possible for this to be 0 before this.
//
// Deprecated: Marked as deprecated in walletrpc/walletkit.proto.
SatPerByte uint32 `protobuf:"varint,4,opt,name=sat_per_byte,json=satPerByte,proto3" json:"sat_per_byte,omitempty"`
// The number of broadcast attempts we've made to sweep the output.
BroadcastAttempts uint32 `protobuf:"varint,5,opt,name=broadcast_attempts,json=broadcastAttempts,proto3" json:"broadcast_attempts,omitempty"`
// Deprecated.
// The next height of the chain at which we'll attempt to broadcast the
// sweep transaction of the output.
//
// Deprecated: Marked as deprecated in walletrpc/walletkit.proto.
NextBroadcastHeight uint32 `protobuf:"varint,6,opt,name=next_broadcast_height,json=nextBroadcastHeight,proto3" json:"next_broadcast_height,omitempty"`
// Deprecated, use immediate.
// Whether this input must be force-swept. This means that it is swept
// immediately.
//
// Deprecated: Marked as deprecated in walletrpc/walletkit.proto.
Force bool `protobuf:"varint,7,opt,name=force,proto3" json:"force,omitempty"`
// Deprecated, use deadline.
// The requested confirmation target for this output, which is the deadline
// used by the sweeper.
//
// Deprecated: Marked as deprecated in walletrpc/walletkit.proto.
RequestedConfTarget uint32 `protobuf:"varint,8,opt,name=requested_conf_target,json=requestedConfTarget,proto3" json:"requested_conf_target,omitempty"`
// Deprecated, use requested_sat_per_vbyte.
// The requested fee rate, expressed in sat/vbyte, for this output.
//
// Deprecated: Marked as deprecated in walletrpc/walletkit.proto.
RequestedSatPerByte uint32 `protobuf:"varint,9,opt,name=requested_sat_per_byte,json=requestedSatPerByte,proto3" json:"requested_sat_per_byte,omitempty"`
// The current fee rate we'll use to sweep the output, expressed in sat/vbyte.
// The fee rate is only determined once a sweeping transaction for the output
// is created, so it's possible for this to be 0 before this.
SatPerVbyte uint64 `protobuf:"varint,10,opt,name=sat_per_vbyte,json=satPerVbyte,proto3" json:"sat_per_vbyte,omitempty"`
// The requested starting fee rate, expressed in sat/vbyte, for this
// output. When not requested, this field will be 0.
RequestedSatPerVbyte uint64 `protobuf:"varint,11,opt,name=requested_sat_per_vbyte,json=requestedSatPerVbyte,proto3" json:"requested_sat_per_vbyte,omitempty"`
// Whether this input will be swept immediately.
Immediate bool `protobuf:"varint,12,opt,name=immediate,proto3" json:"immediate,omitempty"`
// The budget for this sweep, expressed in satoshis. This is the maximum amount
// that can be spent as fees to sweep this output.
Budget uint64 `protobuf:"varint,13,opt,name=budget,proto3" json:"budget,omitempty"`
// The deadline height used for this output when perform fee bumping.
DeadlineHeight uint32 `protobuf:"varint,14,opt,name=deadline_height,json=deadlineHeight,proto3" json:"deadline_height,omitempty"`
}
func (x *PendingSweep) Reset() {
*x = PendingSweep{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[39]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PendingSweep) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PendingSweep) ProtoMessage() {}
func (x *PendingSweep) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[39]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PendingSweep.ProtoReflect.Descriptor instead.
func (*PendingSweep) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{39}
}
func (x *PendingSweep) GetOutpoint() *lnrpc.OutPoint {
if x != nil {
return x.Outpoint
}
return nil
}
func (x *PendingSweep) GetWitnessType() WitnessType {
if x != nil {
return x.WitnessType
}
return WitnessType_UNKNOWN_WITNESS
}
func (x *PendingSweep) GetAmountSat() uint32 {
if x != nil {
return x.AmountSat
}
return 0
}
// Deprecated: Marked as deprecated in walletrpc/walletkit.proto.
func (x *PendingSweep) GetSatPerByte() uint32 {
if x != nil {
return x.SatPerByte
}
return 0
}
func (x *PendingSweep) GetBroadcastAttempts() uint32 {
if x != nil {
return x.BroadcastAttempts
}
return 0
}
// Deprecated: Marked as deprecated in walletrpc/walletkit.proto.
func (x *PendingSweep) GetNextBroadcastHeight() uint32 {
if x != nil {
return x.NextBroadcastHeight
}
return 0
}
// Deprecated: Marked as deprecated in walletrpc/walletkit.proto.
func (x *PendingSweep) GetForce() bool {
if x != nil {
return x.Force
}
return false
}
// Deprecated: Marked as deprecated in walletrpc/walletkit.proto.
func (x *PendingSweep) GetRequestedConfTarget() uint32 {
if x != nil {
return x.RequestedConfTarget
}
return 0
}
// Deprecated: Marked as deprecated in walletrpc/walletkit.proto.
func (x *PendingSweep) GetRequestedSatPerByte() uint32 {
if x != nil {
return x.RequestedSatPerByte
}
return 0
}
func (x *PendingSweep) GetSatPerVbyte() uint64 {
if x != nil {
return x.SatPerVbyte
}
return 0
}
func (x *PendingSweep) GetRequestedSatPerVbyte() uint64 {
if x != nil {
return x.RequestedSatPerVbyte
}
return 0
}
func (x *PendingSweep) GetImmediate() bool {
if x != nil {
return x.Immediate
}
return false
}
func (x *PendingSweep) GetBudget() uint64 {
if x != nil {
return x.Budget
}
return 0
}
func (x *PendingSweep) GetDeadlineHeight() uint32 {
if x != nil {
return x.DeadlineHeight
}
return 0
}
type PendingSweepsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *PendingSweepsRequest) Reset() {
*x = PendingSweepsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[40]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PendingSweepsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PendingSweepsRequest) ProtoMessage() {}
func (x *PendingSweepsRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[40]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PendingSweepsRequest.ProtoReflect.Descriptor instead.
func (*PendingSweepsRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{40}
}
type PendingSweepsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The set of outputs currently being swept by lnd's central batching engine.
PendingSweeps []*PendingSweep `protobuf:"bytes,1,rep,name=pending_sweeps,json=pendingSweeps,proto3" json:"pending_sweeps,omitempty"`
}
func (x *PendingSweepsResponse) Reset() {
*x = PendingSweepsResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[41]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PendingSweepsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PendingSweepsResponse) ProtoMessage() {}
func (x *PendingSweepsResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[41]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PendingSweepsResponse.ProtoReflect.Descriptor instead.
func (*PendingSweepsResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{41}
}
func (x *PendingSweepsResponse) GetPendingSweeps() []*PendingSweep {
if x != nil {
return x.PendingSweeps
}
return nil
}
type BumpFeeRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The input we're attempting to bump the fee of.
Outpoint *lnrpc.OutPoint `protobuf:"bytes,1,opt,name=outpoint,proto3" json:"outpoint,omitempty"`
// Optional. The conf target the underlying fee estimator will use to
// estimate the starting fee rate for the fee function.
TargetConf uint32 `protobuf:"varint,2,opt,name=target_conf,json=targetConf,proto3" json:"target_conf,omitempty"`
// Deprecated, use sat_per_vbyte.
// The fee rate, expressed in sat/vbyte, that should be used to spend the input
// with.
//
// Deprecated: Marked as deprecated in walletrpc/walletkit.proto.
SatPerByte uint32 `protobuf:"varint,3,opt,name=sat_per_byte,json=satPerByte,proto3" json:"sat_per_byte,omitempty"`
// Deprecated, use immediate.
// Whether this input must be force-swept. This means that it is swept
// immediately.
//
// Deprecated: Marked as deprecated in walletrpc/walletkit.proto.
Force bool `protobuf:"varint,4,opt,name=force,proto3" json:"force,omitempty"`
// Optional. The starting fee rate, expressed in sat/vbyte, that will be used
// to spend the input with initially. This value will be used by the sweeper's
// fee function as its starting fee rate. When not set, the sweeper will use
// the estimated fee rate using the `target_conf` as the starting fee rate.
SatPerVbyte uint64 `protobuf:"varint,5,opt,name=sat_per_vbyte,json=satPerVbyte,proto3" json:"sat_per_vbyte,omitempty"`
// Optional. Whether this input will be swept immediately. When set to true,
// the sweeper will sweep this input without waiting for the next block.
Immediate bool `protobuf:"varint,6,opt,name=immediate,proto3" json:"immediate,omitempty"`
// Optional. The max amount in sats that can be used as the fees. Setting this
// value greater than the input's value may result in CPFP - one or more wallet
// utxos will be used to pay the fees specified by the budget. If not set, for
// new inputs, by default 50% of the input's value will be treated as the
// budget for fee bumping; for existing inputs, their current budgets will be
// retained.
Budget uint64 `protobuf:"varint,7,opt,name=budget,proto3" json:"budget,omitempty"`
// Optional. The deadline delta in number of blocks that the output
// should be spent within. This translates internally to the width of the
// fee function that the sweeper will use to bump the fee rate. When the
// deadline is reached, ALL the budget will be spent as fees.
DeadlineDelta uint32 `protobuf:"varint,8,opt,name=deadline_delta,json=deadlineDelta,proto3" json:"deadline_delta,omitempty"`
}
func (x *BumpFeeRequest) Reset() {
*x = BumpFeeRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[42]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *BumpFeeRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BumpFeeRequest) ProtoMessage() {}
func (x *BumpFeeRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[42]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BumpFeeRequest.ProtoReflect.Descriptor instead.
func (*BumpFeeRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{42}
}
func (x *BumpFeeRequest) GetOutpoint() *lnrpc.OutPoint {
if x != nil {
return x.Outpoint
}
return nil
}
func (x *BumpFeeRequest) GetTargetConf() uint32 {
if x != nil {
return x.TargetConf
}
return 0
}
// Deprecated: Marked as deprecated in walletrpc/walletkit.proto.
func (x *BumpFeeRequest) GetSatPerByte() uint32 {
if x != nil {
return x.SatPerByte
}
return 0
}
// Deprecated: Marked as deprecated in walletrpc/walletkit.proto.
func (x *BumpFeeRequest) GetForce() bool {
if x != nil {
return x.Force
}
return false
}
func (x *BumpFeeRequest) GetSatPerVbyte() uint64 {
if x != nil {
return x.SatPerVbyte
}
return 0
}
func (x *BumpFeeRequest) GetImmediate() bool {
if x != nil {
return x.Immediate
}
return false
}
func (x *BumpFeeRequest) GetBudget() uint64 {
if x != nil {
return x.Budget
}
return 0
}
func (x *BumpFeeRequest) GetDeadlineDelta() uint32 {
if x != nil {
return x.DeadlineDelta
}
return 0
}
type BumpFeeResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The status of the bump fee operation.
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
}
func (x *BumpFeeResponse) Reset() {
*x = BumpFeeResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[43]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *BumpFeeResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BumpFeeResponse) ProtoMessage() {}
func (x *BumpFeeResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[43]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BumpFeeResponse.ProtoReflect.Descriptor instead.
func (*BumpFeeResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{43}
}
func (x *BumpFeeResponse) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
type BumpForceCloseFeeRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The channel point which force close transaction we are attempting to
// bump the fee rate for.
ChanPoint *lnrpc.ChannelPoint `protobuf:"bytes,1,opt,name=chan_point,json=chanPoint,proto3" json:"chan_point,omitempty"`
// Optional. The deadline delta in number of blocks that the anchor output
// should be spent within to bump the closing transaction. When the
// deadline is reached, ALL the budget will be spent as fees
DeadlineDelta uint32 `protobuf:"varint,2,opt,name=deadline_delta,json=deadlineDelta,proto3" json:"deadline_delta,omitempty"`
// Optional. The starting fee rate, expressed in sat/vbyte. This value will be
// used by the sweeper's fee function as its starting fee rate. When not set,
// the sweeper will use the estimated fee rate using the target_conf as the
// starting fee rate.
StartingFeerate uint64 `protobuf:"varint,3,opt,name=starting_feerate,json=startingFeerate,proto3" json:"starting_feerate,omitempty"`
// Optional. Whether this cpfp transaction will be triggered immediately. When
// set to true, the sweeper will consider all currently registered sweeps and
// trigger new batch transactions including the sweeping of the anchor output
// related to the selected force close transaction.
Immediate bool `protobuf:"varint,4,opt,name=immediate,proto3" json:"immediate,omitempty"`
// Optional. The max amount in sats that can be used as the fees. For already
// registered anchor outputs if not set explicitly the old value will be used.
// For channel force closes which have no HTLCs in their commitment transaction
// this value has to be set to an appropriate amount to pay for the cpfp
// transaction of the force closed channel otherwise the fee bumping will fail.
Budget uint64 `protobuf:"varint,5,opt,name=budget,proto3" json:"budget,omitempty"`
// Optional. The conf target the underlying fee estimator will use to
// estimate the starting fee rate for the fee function.
TargetConf uint32 `protobuf:"varint,6,opt,name=target_conf,json=targetConf,proto3" json:"target_conf,omitempty"`
}
func (x *BumpForceCloseFeeRequest) Reset() {
*x = BumpForceCloseFeeRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[44]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *BumpForceCloseFeeRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BumpForceCloseFeeRequest) ProtoMessage() {}
func (x *BumpForceCloseFeeRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[44]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BumpForceCloseFeeRequest.ProtoReflect.Descriptor instead.
func (*BumpForceCloseFeeRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{44}
}
func (x *BumpForceCloseFeeRequest) GetChanPoint() *lnrpc.ChannelPoint {
if x != nil {
return x.ChanPoint
}
return nil
}
func (x *BumpForceCloseFeeRequest) GetDeadlineDelta() uint32 {
if x != nil {
return x.DeadlineDelta
}
return 0
}
func (x *BumpForceCloseFeeRequest) GetStartingFeerate() uint64 {
if x != nil {
return x.StartingFeerate
}
return 0
}
func (x *BumpForceCloseFeeRequest) GetImmediate() bool {
if x != nil {
return x.Immediate
}
return false
}
func (x *BumpForceCloseFeeRequest) GetBudget() uint64 {
if x != nil {
return x.Budget
}
return 0
}
func (x *BumpForceCloseFeeRequest) GetTargetConf() uint32 {
if x != nil {
return x.TargetConf
}
return 0
}
type BumpForceCloseFeeResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The status of the force close fee bump operation.
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
}
func (x *BumpForceCloseFeeResponse) Reset() {
*x = BumpForceCloseFeeResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[45]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *BumpForceCloseFeeResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BumpForceCloseFeeResponse) ProtoMessage() {}
func (x *BumpForceCloseFeeResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[45]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BumpForceCloseFeeResponse.ProtoReflect.Descriptor instead.
func (*BumpForceCloseFeeResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{45}
}
func (x *BumpForceCloseFeeResponse) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
type ListSweepsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Retrieve the full sweep transaction details. If false, only the sweep txids
// will be returned. Note that some sweeps that LND publishes will have been
// replaced-by-fee, so will not be included in this output.
Verbose bool `protobuf:"varint,1,opt,name=verbose,proto3" json:"verbose,omitempty"`
// The start height to use when fetching sweeps. If not specified (0), the
// result will start from the earliest sweep. If set to -1 the result will
// only include unconfirmed sweeps (at the time of the call).
StartHeight int32 `protobuf:"varint,2,opt,name=start_height,json=startHeight,proto3" json:"start_height,omitempty"`
}
func (x *ListSweepsRequest) Reset() {
*x = ListSweepsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[46]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListSweepsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListSweepsRequest) ProtoMessage() {}
func (x *ListSweepsRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[46]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListSweepsRequest.ProtoReflect.Descriptor instead.
func (*ListSweepsRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{46}
}
func (x *ListSweepsRequest) GetVerbose() bool {
if x != nil {
return x.Verbose
}
return false
}
func (x *ListSweepsRequest) GetStartHeight() int32 {
if x != nil {
return x.StartHeight
}
return 0
}
type ListSweepsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Sweeps:
//
// *ListSweepsResponse_TransactionDetails
// *ListSweepsResponse_TransactionIds
Sweeps isListSweepsResponse_Sweeps `protobuf_oneof:"sweeps"`
}
func (x *ListSweepsResponse) Reset() {
*x = ListSweepsResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[47]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListSweepsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListSweepsResponse) ProtoMessage() {}
func (x *ListSweepsResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[47]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListSweepsResponse.ProtoReflect.Descriptor instead.
func (*ListSweepsResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{47}
}
func (m *ListSweepsResponse) GetSweeps() isListSweepsResponse_Sweeps {
if m != nil {
return m.Sweeps
}
return nil
}
func (x *ListSweepsResponse) GetTransactionDetails() *lnrpc.TransactionDetails {
if x, ok := x.GetSweeps().(*ListSweepsResponse_TransactionDetails); ok {
return x.TransactionDetails
}
return nil
}
func (x *ListSweepsResponse) GetTransactionIds() *ListSweepsResponse_TransactionIDs {
if x, ok := x.GetSweeps().(*ListSweepsResponse_TransactionIds); ok {
return x.TransactionIds
}
return nil
}
type isListSweepsResponse_Sweeps interface {
isListSweepsResponse_Sweeps()
}
type ListSweepsResponse_TransactionDetails struct {
TransactionDetails *lnrpc.TransactionDetails `protobuf:"bytes,1,opt,name=transaction_details,json=transactionDetails,proto3,oneof"`
}
type ListSweepsResponse_TransactionIds struct {
TransactionIds *ListSweepsResponse_TransactionIDs `protobuf:"bytes,2,opt,name=transaction_ids,json=transactionIds,proto3,oneof"`
}
func (*ListSweepsResponse_TransactionDetails) isListSweepsResponse_Sweeps() {}
func (*ListSweepsResponse_TransactionIds) isListSweepsResponse_Sweeps() {}
type LabelTransactionRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The txid of the transaction to label. Note: When using gRPC, the bytes
// must be in little-endian (reverse) order.
Txid []byte `protobuf:"bytes,1,opt,name=txid,proto3" json:"txid,omitempty"`
// The label to add to the transaction, limited to 500 characters.
Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"`
// Whether to overwrite the existing label, if it is present.
Overwrite bool `protobuf:"varint,3,opt,name=overwrite,proto3" json:"overwrite,omitempty"`
}
func (x *LabelTransactionRequest) Reset() {
*x = LabelTransactionRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[48]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *LabelTransactionRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LabelTransactionRequest) ProtoMessage() {}
func (x *LabelTransactionRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[48]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LabelTransactionRequest.ProtoReflect.Descriptor instead.
func (*LabelTransactionRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{48}
}
func (x *LabelTransactionRequest) GetTxid() []byte {
if x != nil {
return x.Txid
}
return nil
}
func (x *LabelTransactionRequest) GetLabel() string {
if x != nil {
return x.Label
}
return ""
}
func (x *LabelTransactionRequest) GetOverwrite() bool {
if x != nil {
return x.Overwrite
}
return false
}
type LabelTransactionResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The status of the label operation.
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
}
func (x *LabelTransactionResponse) Reset() {
*x = LabelTransactionResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[49]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *LabelTransactionResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LabelTransactionResponse) ProtoMessage() {}
func (x *LabelTransactionResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[49]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LabelTransactionResponse.ProtoReflect.Descriptor instead.
func (*LabelTransactionResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{49}
}
func (x *LabelTransactionResponse) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
type FundPsbtRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Template:
//
// *FundPsbtRequest_Psbt
// *FundPsbtRequest_Raw
// *FundPsbtRequest_CoinSelect
Template isFundPsbtRequest_Template `protobuf_oneof:"template"`
// Types that are assignable to Fees:
//
// *FundPsbtRequest_TargetConf
// *FundPsbtRequest_SatPerVbyte
// *FundPsbtRequest_SatPerKw
Fees isFundPsbtRequest_Fees `protobuf_oneof:"fees"`
// The name of the account to fund the PSBT with. If empty, the default wallet
// account is used.
Account string `protobuf:"bytes,5,opt,name=account,proto3" json:"account,omitempty"`
// The minimum number of confirmations each one of your outputs used for
// the transaction must satisfy.
MinConfs int32 `protobuf:"varint,6,opt,name=min_confs,json=minConfs,proto3" json:"min_confs,omitempty"`
// Whether unconfirmed outputs should be used as inputs for the transaction.
SpendUnconfirmed bool `protobuf:"varint,7,opt,name=spend_unconfirmed,json=spendUnconfirmed,proto3" json:"spend_unconfirmed,omitempty"`
// The address type for the change. If empty, P2WPKH addresses will be used
// for default accounts and single imported public keys. For custom
// accounts, no change type should be provided as the coin selection key
// scope will always be used to generate the change address.
ChangeType ChangeAddressType `protobuf:"varint,8,opt,name=change_type,json=changeType,proto3,enum=walletrpc.ChangeAddressType" json:"change_type,omitempty"`
// The strategy to use for selecting coins during funding the PSBT.
CoinSelectionStrategy lnrpc.CoinSelectionStrategy `protobuf:"varint,10,opt,name=coin_selection_strategy,json=coinSelectionStrategy,proto3,enum=lnrpc.CoinSelectionStrategy" json:"coin_selection_strategy,omitempty"`
// The max fee to total output amount ratio that this psbt should adhere to.
MaxFeeRatio float64 `protobuf:"fixed64,12,opt,name=max_fee_ratio,json=maxFeeRatio,proto3" json:"max_fee_ratio,omitempty"`
// The custom lock ID to use for the inputs in the funded PSBT. The value
// if set must be exactly 32 bytes long. If empty, the default lock ID will
// be used.
CustomLockId []byte `protobuf:"bytes,13,opt,name=custom_lock_id,json=customLockId,proto3" json:"custom_lock_id,omitempty"`
// If set, then the inputs in the funded PSBT will be locked for the
// specified duration. The lock duration is specified in seconds. If not
// set, the default lock duration will be used.
LockExpirationSeconds uint64 `protobuf:"varint,14,opt,name=lock_expiration_seconds,json=lockExpirationSeconds,proto3" json:"lock_expiration_seconds,omitempty"`
}
func (x *FundPsbtRequest) Reset() {
*x = FundPsbtRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[50]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FundPsbtRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FundPsbtRequest) ProtoMessage() {}
func (x *FundPsbtRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[50]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FundPsbtRequest.ProtoReflect.Descriptor instead.
func (*FundPsbtRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{50}
}
func (m *FundPsbtRequest) GetTemplate() isFundPsbtRequest_Template {
if m != nil {
return m.Template
}
return nil
}
func (x *FundPsbtRequest) GetPsbt() []byte {
if x, ok := x.GetTemplate().(*FundPsbtRequest_Psbt); ok {
return x.Psbt
}
return nil
}
func (x *FundPsbtRequest) GetRaw() *TxTemplate {
if x, ok := x.GetTemplate().(*FundPsbtRequest_Raw); ok {
return x.Raw
}
return nil
}
func (x *FundPsbtRequest) GetCoinSelect() *PsbtCoinSelect {
if x, ok := x.GetTemplate().(*FundPsbtRequest_CoinSelect); ok {
return x.CoinSelect
}
return nil
}
func (m *FundPsbtRequest) GetFees() isFundPsbtRequest_Fees {
if m != nil {
return m.Fees
}
return nil
}
func (x *FundPsbtRequest) GetTargetConf() uint32 {
if x, ok := x.GetFees().(*FundPsbtRequest_TargetConf); ok {
return x.TargetConf
}
return 0
}
func (x *FundPsbtRequest) GetSatPerVbyte() uint64 {
if x, ok := x.GetFees().(*FundPsbtRequest_SatPerVbyte); ok {
return x.SatPerVbyte
}
return 0
}
func (x *FundPsbtRequest) GetSatPerKw() uint64 {
if x, ok := x.GetFees().(*FundPsbtRequest_SatPerKw); ok {
return x.SatPerKw
}
return 0
}
func (x *FundPsbtRequest) GetAccount() string {
if x != nil {
return x.Account
}
return ""
}
func (x *FundPsbtRequest) GetMinConfs() int32 {
if x != nil {
return x.MinConfs
}
return 0
}
func (x *FundPsbtRequest) GetSpendUnconfirmed() bool {
if x != nil {
return x.SpendUnconfirmed
}
return false
}
func (x *FundPsbtRequest) GetChangeType() ChangeAddressType {
if x != nil {
return x.ChangeType
}
return ChangeAddressType_CHANGE_ADDRESS_TYPE_UNSPECIFIED
}
func (x *FundPsbtRequest) GetCoinSelectionStrategy() lnrpc.CoinSelectionStrategy {
if x != nil {
return x.CoinSelectionStrategy
}
return lnrpc.CoinSelectionStrategy(0)
}
func (x *FundPsbtRequest) GetMaxFeeRatio() float64 {
if x != nil {
return x.MaxFeeRatio
}
return 0
}
func (x *FundPsbtRequest) GetCustomLockId() []byte {
if x != nil {
return x.CustomLockId
}
return nil
}
func (x *FundPsbtRequest) GetLockExpirationSeconds() uint64 {
if x != nil {
return x.LockExpirationSeconds
}
return 0
}
type isFundPsbtRequest_Template interface {
isFundPsbtRequest_Template()
}
type FundPsbtRequest_Psbt struct {
// Use an existing PSBT packet as the template for the funded PSBT.
//
// The packet must contain at least one non-dust output. If one or more
// inputs are specified, no coin selection is performed. In that case every
// input must be an UTXO known to the wallet that has not been locked
// before. The sum of all inputs must be sufficiently greater than the sum
// of all outputs to pay a miner fee with the specified fee rate. A change
// output is added to the PSBT if necessary.
Psbt []byte `protobuf:"bytes,1,opt,name=psbt,proto3,oneof"`
}
type FundPsbtRequest_Raw struct {
// Use the outputs and optional inputs from this raw template.
Raw *TxTemplate `protobuf:"bytes,2,opt,name=raw,proto3,oneof"`
}
type FundPsbtRequest_CoinSelect struct {
// Use an existing PSBT packet as the template for the funded PSBT.
//
// The difference to the pure PSBT template above is that coin selection is
// performed even if inputs are specified. The output amounts are summed up
// and used as the target amount for coin selection. A change output must
// either already exist in the PSBT and be marked as such, otherwise a new
// change output of the specified output type will be added. Any inputs
// already specified in the PSBT must already be locked (if they belong to
// this node), only newly added inputs will be locked by this RPC.
//
// In case the sum of the already provided inputs exceeds the required
// output amount, no new coins are selected. Instead only the fee and
// change amount calculation is performed (e.g. a change output is added if
// requested or the change is added to the specified existing change
// output, given there is any non-dust change). This can be identified by
// the returned locked UTXOs being empty.
CoinSelect *PsbtCoinSelect `protobuf:"bytes,9,opt,name=coin_select,json=coinSelect,proto3,oneof"`
}
func (*FundPsbtRequest_Psbt) isFundPsbtRequest_Template() {}
func (*FundPsbtRequest_Raw) isFundPsbtRequest_Template() {}
func (*FundPsbtRequest_CoinSelect) isFundPsbtRequest_Template() {}
type isFundPsbtRequest_Fees interface {
isFundPsbtRequest_Fees()
}
type FundPsbtRequest_TargetConf struct {
// The target number of blocks that the transaction should be confirmed in.
TargetConf uint32 `protobuf:"varint,3,opt,name=target_conf,json=targetConf,proto3,oneof"`
}
type FundPsbtRequest_SatPerVbyte struct {
// The fee rate, expressed in sat/vbyte, that should be used to spend the
// input with.
SatPerVbyte uint64 `protobuf:"varint,4,opt,name=sat_per_vbyte,json=satPerVbyte,proto3,oneof"`
}
type FundPsbtRequest_SatPerKw struct {
// The fee rate, expressed in sat/kWU, that should be used to spend the
// input with.
SatPerKw uint64 `protobuf:"varint,11,opt,name=sat_per_kw,json=satPerKw,proto3,oneof"`
}
func (*FundPsbtRequest_TargetConf) isFundPsbtRequest_Fees() {}
func (*FundPsbtRequest_SatPerVbyte) isFundPsbtRequest_Fees() {}
func (*FundPsbtRequest_SatPerKw) isFundPsbtRequest_Fees() {}
type FundPsbtResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The funded but not yet signed PSBT packet.
FundedPsbt []byte `protobuf:"bytes,1,opt,name=funded_psbt,json=fundedPsbt,proto3" json:"funded_psbt,omitempty"`
// The index of the added change output or -1 if no change was left over.
ChangeOutputIndex int32 `protobuf:"varint,2,opt,name=change_output_index,json=changeOutputIndex,proto3" json:"change_output_index,omitempty"`
// The list of lock leases that were acquired for the inputs in the funded PSBT
// packet. Only inputs added to the PSBT by this RPC are locked, inputs that
// were already present in the PSBT are not locked.
LockedUtxos []*UtxoLease `protobuf:"bytes,3,rep,name=locked_utxos,json=lockedUtxos,proto3" json:"locked_utxos,omitempty"`
}
func (x *FundPsbtResponse) Reset() {
*x = FundPsbtResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[51]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FundPsbtResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FundPsbtResponse) ProtoMessage() {}
func (x *FundPsbtResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[51]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FundPsbtResponse.ProtoReflect.Descriptor instead.
func (*FundPsbtResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{51}
}
func (x *FundPsbtResponse) GetFundedPsbt() []byte {
if x != nil {
return x.FundedPsbt
}
return nil
}
func (x *FundPsbtResponse) GetChangeOutputIndex() int32 {
if x != nil {
return x.ChangeOutputIndex
}
return 0
}
func (x *FundPsbtResponse) GetLockedUtxos() []*UtxoLease {
if x != nil {
return x.LockedUtxos
}
return nil
}
type TxTemplate struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// An optional list of inputs to use. Every input must be an UTXO known to the
// wallet that has not been locked before. The sum of all inputs must be
// sufficiently greater than the sum of all outputs to pay a miner fee with the
// fee rate specified in the parent message.
//
// If no inputs are specified, coin selection will be performed instead and
// inputs of sufficient value will be added to the resulting PSBT.
Inputs []*lnrpc.OutPoint `protobuf:"bytes,1,rep,name=inputs,proto3" json:"inputs,omitempty"`
// A map of all addresses and the amounts to send to in the funded PSBT.
Outputs map[string]uint64 `protobuf:"bytes,2,rep,name=outputs,proto3" json:"outputs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
}
func (x *TxTemplate) Reset() {
*x = TxTemplate{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[52]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TxTemplate) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TxTemplate) ProtoMessage() {}
func (x *TxTemplate) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[52]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TxTemplate.ProtoReflect.Descriptor instead.
func (*TxTemplate) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{52}
}
func (x *TxTemplate) GetInputs() []*lnrpc.OutPoint {
if x != nil {
return x.Inputs
}
return nil
}
func (x *TxTemplate) GetOutputs() map[string]uint64 {
if x != nil {
return x.Outputs
}
return nil
}
type PsbtCoinSelect struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The template to use for the funded PSBT. The template must contain at least
// one non-dust output. The amount to be funded is calculated by summing up the
// amounts of all outputs in the template, subtracting all the input values of
// the already specified inputs. The change value is added to the output that
// is marked as such (or a new change output is added if none is marked). For
// the input amount calculation to be correct, the template must have the
// WitnessUtxo field set for all inputs. Any inputs already specified in the
// PSBT must already be locked (if they belong to this node), only newly added
// inputs will be locked by this RPC.
Psbt []byte `protobuf:"bytes,1,opt,name=psbt,proto3" json:"psbt,omitempty"`
// Types that are assignable to ChangeOutput:
//
// *PsbtCoinSelect_ExistingOutputIndex
// *PsbtCoinSelect_Add
ChangeOutput isPsbtCoinSelect_ChangeOutput `protobuf_oneof:"change_output"`
}
func (x *PsbtCoinSelect) Reset() {
*x = PsbtCoinSelect{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[53]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PsbtCoinSelect) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PsbtCoinSelect) ProtoMessage() {}
func (x *PsbtCoinSelect) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[53]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PsbtCoinSelect.ProtoReflect.Descriptor instead.
func (*PsbtCoinSelect) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{53}
}
func (x *PsbtCoinSelect) GetPsbt() []byte {
if x != nil {
return x.Psbt
}
return nil
}
func (m *PsbtCoinSelect) GetChangeOutput() isPsbtCoinSelect_ChangeOutput {
if m != nil {
return m.ChangeOutput
}
return nil
}
func (x *PsbtCoinSelect) GetExistingOutputIndex() int32 {
if x, ok := x.GetChangeOutput().(*PsbtCoinSelect_ExistingOutputIndex); ok {
return x.ExistingOutputIndex
}
return 0
}
func (x *PsbtCoinSelect) GetAdd() bool {
if x, ok := x.GetChangeOutput().(*PsbtCoinSelect_Add); ok {
return x.Add
}
return false
}
type isPsbtCoinSelect_ChangeOutput interface {
isPsbtCoinSelect_ChangeOutput()
}
type PsbtCoinSelect_ExistingOutputIndex struct {
// Use the existing output within the template PSBT with the specified
// index as the change output. Any leftover change will be added to the
// already specified amount of that output. To add a new change output to
// the PSBT, set the "add" field below instead. The type of change output
// added is defined by change_type in the parent message.
ExistingOutputIndex int32 `protobuf:"varint,2,opt,name=existing_output_index,json=existingOutputIndex,proto3,oneof"`
}
type PsbtCoinSelect_Add struct {
// Add a new change output to the PSBT using the change_type specified in
// the parent message.
Add bool `protobuf:"varint,3,opt,name=add,proto3,oneof"`
}
func (*PsbtCoinSelect_ExistingOutputIndex) isPsbtCoinSelect_ChangeOutput() {}
func (*PsbtCoinSelect_Add) isPsbtCoinSelect_ChangeOutput() {}
type UtxoLease struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A 32 byte random ID that identifies the lease.
Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
// The identifying outpoint of the output being leased.
Outpoint *lnrpc.OutPoint `protobuf:"bytes,2,opt,name=outpoint,proto3" json:"outpoint,omitempty"`
// The absolute expiration of the output lease represented as a unix timestamp.
Expiration uint64 `protobuf:"varint,3,opt,name=expiration,proto3" json:"expiration,omitempty"`
// The public key script of the leased output.
PkScript []byte `protobuf:"bytes,4,opt,name=pk_script,json=pkScript,proto3" json:"pk_script,omitempty"`
// The value of the leased output in satoshis.
Value uint64 `protobuf:"varint,5,opt,name=value,proto3" json:"value,omitempty"`
}
func (x *UtxoLease) Reset() {
*x = UtxoLease{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[54]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *UtxoLease) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UtxoLease) ProtoMessage() {}
func (x *UtxoLease) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[54]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UtxoLease.ProtoReflect.Descriptor instead.
func (*UtxoLease) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{54}
}
func (x *UtxoLease) GetId() []byte {
if x != nil {
return x.Id
}
return nil
}
func (x *UtxoLease) GetOutpoint() *lnrpc.OutPoint {
if x != nil {
return x.Outpoint
}
return nil
}
func (x *UtxoLease) GetExpiration() uint64 {
if x != nil {
return x.Expiration
}
return 0
}
func (x *UtxoLease) GetPkScript() []byte {
if x != nil {
return x.PkScript
}
return nil
}
func (x *UtxoLease) GetValue() uint64 {
if x != nil {
return x.Value
}
return 0
}
type SignPsbtRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The PSBT that should be signed. The PSBT must contain all required inputs,
// outputs, UTXO data and custom fields required to identify the signing key.
FundedPsbt []byte `protobuf:"bytes,1,opt,name=funded_psbt,json=fundedPsbt,proto3" json:"funded_psbt,omitempty"`
}
func (x *SignPsbtRequest) Reset() {
*x = SignPsbtRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[55]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SignPsbtRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SignPsbtRequest) ProtoMessage() {}
func (x *SignPsbtRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[55]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SignPsbtRequest.ProtoReflect.Descriptor instead.
func (*SignPsbtRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{55}
}
func (x *SignPsbtRequest) GetFundedPsbt() []byte {
if x != nil {
return x.FundedPsbt
}
return nil
}
type SignPsbtResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The signed transaction in PSBT format.
SignedPsbt []byte `protobuf:"bytes,1,opt,name=signed_psbt,json=signedPsbt,proto3" json:"signed_psbt,omitempty"`
// The indices of signed inputs.
SignedInputs []uint32 `protobuf:"varint,2,rep,packed,name=signed_inputs,json=signedInputs,proto3" json:"signed_inputs,omitempty"`
}
func (x *SignPsbtResponse) Reset() {
*x = SignPsbtResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[56]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SignPsbtResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SignPsbtResponse) ProtoMessage() {}
func (x *SignPsbtResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[56]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SignPsbtResponse.ProtoReflect.Descriptor instead.
func (*SignPsbtResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{56}
}
func (x *SignPsbtResponse) GetSignedPsbt() []byte {
if x != nil {
return x.SignedPsbt
}
return nil
}
func (x *SignPsbtResponse) GetSignedInputs() []uint32 {
if x != nil {
return x.SignedInputs
}
return nil
}
type FinalizePsbtRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A PSBT that should be signed and finalized. The PSBT must contain all
// required inputs, outputs, UTXO data and partial signatures of all other
// signers.
FundedPsbt []byte `protobuf:"bytes,1,opt,name=funded_psbt,json=fundedPsbt,proto3" json:"funded_psbt,omitempty"`
// The name of the account to finalize the PSBT with. If empty, the default
// wallet account is used.
Account string `protobuf:"bytes,5,opt,name=account,proto3" json:"account,omitempty"`
}
func (x *FinalizePsbtRequest) Reset() {
*x = FinalizePsbtRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[57]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FinalizePsbtRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FinalizePsbtRequest) ProtoMessage() {}
func (x *FinalizePsbtRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[57]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FinalizePsbtRequest.ProtoReflect.Descriptor instead.
func (*FinalizePsbtRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{57}
}
func (x *FinalizePsbtRequest) GetFundedPsbt() []byte {
if x != nil {
return x.FundedPsbt
}
return nil
}
func (x *FinalizePsbtRequest) GetAccount() string {
if x != nil {
return x.Account
}
return ""
}
type FinalizePsbtResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The fully signed and finalized transaction in PSBT format.
SignedPsbt []byte `protobuf:"bytes,1,opt,name=signed_psbt,json=signedPsbt,proto3" json:"signed_psbt,omitempty"`
// The fully signed and finalized transaction in the raw wire format.
RawFinalTx []byte `protobuf:"bytes,2,opt,name=raw_final_tx,json=rawFinalTx,proto3" json:"raw_final_tx,omitempty"`
}
func (x *FinalizePsbtResponse) Reset() {
*x = FinalizePsbtResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[58]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FinalizePsbtResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FinalizePsbtResponse) ProtoMessage() {}
func (x *FinalizePsbtResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[58]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FinalizePsbtResponse.ProtoReflect.Descriptor instead.
func (*FinalizePsbtResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{58}
}
func (x *FinalizePsbtResponse) GetSignedPsbt() []byte {
if x != nil {
return x.SignedPsbt
}
return nil
}
func (x *FinalizePsbtResponse) GetRawFinalTx() []byte {
if x != nil {
return x.RawFinalTx
}
return nil
}
type ListLeasesRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ListLeasesRequest) Reset() {
*x = ListLeasesRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[59]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListLeasesRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListLeasesRequest) ProtoMessage() {}
func (x *ListLeasesRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[59]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListLeasesRequest.ProtoReflect.Descriptor instead.
func (*ListLeasesRequest) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{59}
}
type ListLeasesResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The list of currently leased utxos.
LockedUtxos []*UtxoLease `protobuf:"bytes,1,rep,name=locked_utxos,json=lockedUtxos,proto3" json:"locked_utxos,omitempty"`
}
func (x *ListLeasesResponse) Reset() {
*x = ListLeasesResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[60]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListLeasesResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListLeasesResponse) ProtoMessage() {}
func (x *ListLeasesResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[60]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListLeasesResponse.ProtoReflect.Descriptor instead.
func (*ListLeasesResponse) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{60}
}
func (x *ListLeasesResponse) GetLockedUtxos() []*UtxoLease {
if x != nil {
return x.LockedUtxos
}
return nil
}
type ListSweepsResponse_TransactionIDs struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Reversed, hex-encoded string representing the transaction ids of the
// sweeps that our node has broadcast. Note that these transactions may
// not have confirmed yet, we record sweeps on broadcast, not confirmation.
TransactionIds []string `protobuf:"bytes,1,rep,name=transaction_ids,json=transactionIds,proto3" json:"transaction_ids,omitempty"`
}
func (x *ListSweepsResponse_TransactionIDs) Reset() {
*x = ListSweepsResponse_TransactionIDs{}
if protoimpl.UnsafeEnabled {
mi := &file_walletrpc_walletkit_proto_msgTypes[61]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListSweepsResponse_TransactionIDs) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListSweepsResponse_TransactionIDs) ProtoMessage() {}
func (x *ListSweepsResponse_TransactionIDs) ProtoReflect() protoreflect.Message {
mi := &file_walletrpc_walletkit_proto_msgTypes[61]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListSweepsResponse_TransactionIDs.ProtoReflect.Descriptor instead.
func (*ListSweepsResponse_TransactionIDs) Descriptor() ([]byte, []int) {
return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{47, 0}
}
func (x *ListSweepsResponse_TransactionIDs) GetTransactionIds() []string {
if x != nil {
return x.TransactionIds
}
return nil
}
var File_walletrpc_walletkit_proto protoreflect.FileDescriptor
var file_walletrpc_walletkit_proto_rawDesc = []byte{
0x0a, 0x19, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x77, 0x61, 0x6c, 0x6c,
0x65, 0x74, 0x6b, 0x69, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x77, 0x61, 0x6c,
0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x1a, 0x0f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e,
0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63,
0x2f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x93, 0x01,
0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66,
0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66,
0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x02,
0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x18,
0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x75, 0x6e, 0x63, 0x6f,
0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01,
0x28, 0x08, 0x52, 0x0f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4f,
0x6e, 0x6c, 0x79, 0x22, 0x38, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65,
0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x05, 0x75, 0x74,
0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x52, 0x05, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x22, 0x80, 0x01,
0x0a, 0x12, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x02, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f,
0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e,
0x74, 0x12, 0x2d, 0x0a, 0x12, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x65,
0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73,
0x22, 0x35, 0x0a, 0x13, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x65, 0x78, 0x70,
0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x53, 0x0a, 0x14, 0x52, 0x65, 0x6c, 0x65, 0x61,
0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12,
0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69,
0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x2f, 0x0a, 0x15,
0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x51, 0x0a,
0x06, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x12, 0x28, 0x0a, 0x10, 0x6b, 0x65, 0x79, 0x5f, 0x66,
0x69, 0x6e, 0x67, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
0x05, 0x52, 0x0e, 0x6b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x50, 0x72, 0x69, 0x6e,
0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18,
0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6b, 0x65, 0x79, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79,
0x22, 0x6b, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x04, 0x74, 0x79, 0x70,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52,
0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18,
0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x22, 0x0a,
0x0c, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a,
0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64,
0x72, 0x22, 0xe2, 0x02, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a,
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,
0x65, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52,
0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2e, 0x0a, 0x13,
0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f,
0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, 0x6e,
0x64, 0x65, 0x64, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x34, 0x0a, 0x16,
0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65,
0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x6d, 0x61,
0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69,
0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x72,
0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2c, 0x0a, 0x12, 0x65,
0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e,
0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61,
0x6c, 0x4b, 0x65, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x69, 0x6e, 0x74,
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18,
0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b,
0x65, 0x79, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x77, 0x61, 0x74, 0x63, 0x68,
0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x77, 0x61, 0x74,
0x63, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0xae, 0x01, 0x0a, 0x0f, 0x41, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72,
0x6e, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x49, 0x6e, 0x74,
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65,
0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12,
0x27, 0x0a, 0x0f, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61,
0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c,
0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75,
0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0xc8, 0x01, 0x0a, 0x14, 0x41, 0x63, 0x63, 0x6f,
0x75, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73,
0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f,
0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c,
0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79,
0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12,
0x27, 0x0a, 0x0f, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61,
0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x72, 0x69, 0x76, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x12, 0x38, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x77, 0x61,
0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x50,
0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x65, 0x73, 0x22, 0x64, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e,
0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a,
0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e,
0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x22, 0x46, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74,
0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x2e, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41,
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73,
0x22, 0x56, 0x0a, 0x16, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x65,
0x72, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x1a, 0x61, 0x64,
0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f,
0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x18,
0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63,
0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0x44, 0x0a, 0x17, 0x52, 0x65, 0x71, 0x75,
0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f,
0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72,
0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x22, 0x6b,
0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e,
0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x68, 0x6f,
0x77, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x73, 0x68, 0x6f, 0x77, 0x43, 0x75, 0x73,
0x74, 0x6f, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x6e, 0x0a, 0x15, 0x4c,
0x69, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x16, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f,
0x77, 0x69, 0x74, 0x68, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63,
0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x14, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x57, 0x69,
0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x2b, 0x0a, 0x15, 0x47,
0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x22, 0x42, 0x0a, 0x1a, 0x53, 0x69, 0x67, 0x6e,
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x22, 0x3b, 0x0a, 0x1b,
0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41,
0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73,
0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x62, 0x0a, 0x1c, 0x56, 0x65, 0x72,
0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64,
0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x73,
0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64,
0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x22, 0x4d, 0x0a,
0x1d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69,
0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14,
0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76,
0x61, 0x6c, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0xe4, 0x01, 0x0a,
0x14, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x65, 0x78, 0x74,
0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64,
0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x34, 0x0a, 0x16, 0x6d, 0x61, 0x73,
0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72,
0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x6d, 0x61, 0x73, 0x74, 0x65,
0x72, 0x4b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12,
0x39, 0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18,
0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70,
0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72,
0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x72, 0x79,
0x52, 0x75, 0x6e, 0x22, 0xaf, 0x01, 0x0a, 0x15, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a,
0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12,
0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x16, 0x64,
0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f,
0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x64, 0x72, 0x79,
0x52, 0x75, 0x6e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x73,
0x12, 0x33, 0x0a, 0x16, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x65,
0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09,
0x52, 0x13, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
0x41, 0x64, 0x64, 0x72, 0x73, 0x22, 0x72, 0x0a, 0x16, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50,
0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x39,
0x0a, 0x0c, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63,
0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x61, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x22, 0x31, 0x0a, 0x17, 0x49, 0x6d, 0x70,
0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xa9, 0x02, 0x0a,
0x16, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x69, 0x6e, 0x74, 0x65, 0x72,
0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x50, 0x75,
0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x3b, 0x0a, 0x09, 0x66, 0x75, 0x6c, 0x6c, 0x5f,
0x74, 0x72, 0x65, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x77, 0x61, 0x6c,
0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x46, 0x75, 0x6c, 0x6c, 0x54, 0x72, 0x65, 0x65, 0x48, 0x00, 0x52, 0x08, 0x66, 0x75, 0x6c, 0x6c,
0x54, 0x72, 0x65, 0x65, 0x12, 0x4a, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x5f,
0x72, 0x65, 0x76, 0x65, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x77,
0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69,
0x70, 0x74, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x76, 0x65, 0x61, 0x6c, 0x48,
0x00, 0x52, 0x0d, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x76, 0x65, 0x61, 0x6c,
0x12, 0x26, 0x0a, 0x0e, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x6f, 0x6e,
0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0c, 0x72, 0x6f, 0x6f, 0x74,
0x48, 0x61, 0x73, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x66, 0x75, 0x6c, 0x6c,
0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x48,
0x00, 0x52, 0x0b, 0x66, 0x75, 0x6c, 0x6c, 0x4b, 0x65, 0x79, 0x4f, 0x6e, 0x6c, 0x79, 0x42, 0x08,
0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x46, 0x0a, 0x11, 0x54, 0x61, 0x70, 0x73,
0x63, 0x72, 0x69, 0x70, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x54, 0x72, 0x65, 0x65, 0x12, 0x31, 0x0a,
0x0a, 0x61, 0x6c, 0x6c, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x12, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x61,
0x70, 0x4c, 0x65, 0x61, 0x66, 0x52, 0x09, 0x61, 0x6c, 0x6c, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x73,
0x22, 0x44, 0x0a, 0x07, 0x54, 0x61, 0x70, 0x4c, 0x65, 0x61, 0x66, 0x12, 0x21, 0x0a, 0x0c, 0x6c,
0x65, 0x61, 0x66, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0d, 0x52, 0x0b, 0x6c, 0x65, 0x61, 0x66, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16,
0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06,
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x83, 0x01, 0x0a, 0x16, 0x54, 0x61, 0x70, 0x73, 0x63,
0x72, 0x69, 0x70, 0x74, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x76, 0x65, 0x61,
0x6c, 0x12, 0x37, 0x0a, 0x0d, 0x72, 0x65, 0x76, 0x65, 0x61, 0x6c, 0x65, 0x64, 0x5f, 0x6c, 0x65,
0x61, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65,
0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x61, 0x70, 0x4c, 0x65, 0x61, 0x66, 0x52, 0x0c, 0x72, 0x65,
0x76, 0x65, 0x61, 0x6c, 0x65, 0x64, 0x4c, 0x65, 0x61, 0x66, 0x12, 0x30, 0x0a, 0x14, 0x66, 0x75,
0x6c, 0x6c, 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f,
0x6f, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x66, 0x75, 0x6c, 0x6c, 0x49, 0x6e,
0x63, 0x6c, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x22, 0x3c, 0x0a, 0x17,
0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x32, 0x74, 0x72, 0x5f,
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70,
0x32, 0x74, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3a, 0x0a, 0x0b, 0x54, 0x72,
0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x0a, 0x06, 0x74, 0x78, 0x5f,
0x68, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x74, 0x78, 0x48, 0x65, 0x78,
0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x36, 0x0a, 0x0f, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73,
0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x75, 0x62,
0x6c, 0x69, 0x73, 0x68, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x33,
0x0a, 0x19, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73,
0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61,
0x74, 0x75, 0x73, 0x22, 0x92, 0x02, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70,
0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x0a, 0x73, 0x61,
0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08,
0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x28, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70,
0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x73, 0x69, 0x67, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x4f, 0x75, 0x74, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75,
0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28,
0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f,
0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e,
0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75,
0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08,
0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d,
0x65, 0x64, 0x12, 0x54, 0x0a, 0x17, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63,
0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x06, 0x20,
0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x69, 0x6e,
0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67,
0x79, 0x52, 0x15, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x22, 0x2c, 0x0a, 0x13, 0x53, 0x65, 0x6e, 0x64,
0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x15, 0x0a, 0x06, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x05, 0x72, 0x61, 0x77, 0x54, 0x78, 0x22, 0x35, 0x0a, 0x12, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61,
0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b,
0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
0x05, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0x6a, 0x0a,
0x13, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x0a, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f,
0x6b, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72,
0x4b, 0x77, 0x12, 0x35, 0x0a, 0x18, 0x6d, 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f,
0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x02,
0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x46, 0x65,
0x65, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x22, 0xe7, 0x04, 0x0a, 0x0c, 0x50, 0x65,
0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75,
0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f,
0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x0c, 0x77, 0x69, 0x74, 0x6e, 0x65,
0x73, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e,
0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x6e, 0x65, 0x73,
0x73, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x77, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x54, 0x79,
0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x73, 0x61, 0x74,
0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x61,
0x74, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74,
0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74,
0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x62, 0x72, 0x6f, 0x61, 0x64,
0x63, 0x61, 0x73, 0x74, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x18, 0x05, 0x20,
0x01, 0x28, 0x0d, 0x52, 0x11, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x41, 0x74,
0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x12, 0x36, 0x0a, 0x15, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x62,
0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18,
0x06, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x13, 0x6e, 0x65, 0x78, 0x74, 0x42,
0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x18,
0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18,
0x01, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x15, 0x72, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65,
0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x13, 0x72, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74,
0x12, 0x37, 0x0a, 0x16, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x61,
0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d,
0x42, 0x02, 0x18, 0x01, 0x52, 0x13, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x53,
0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74,
0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04,
0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x35, 0x0a,
0x17, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70,
0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14,
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56,
0x62, 0x79, 0x74, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74,
0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61,
0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x18, 0x0d, 0x20, 0x01,
0x28, 0x04, 0x52, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x65,
0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x0e, 0x20,
0x01, 0x28, 0x0d, 0x52, 0x0e, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x48, 0x65, 0x69,
0x67, 0x68, 0x74, 0x22, 0x16, 0x0a, 0x14, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77,
0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x57, 0x0a, 0x15, 0x50,
0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f,
0x73, 0x77, 0x65, 0x65, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x77,
0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67,
0x53, 0x77, 0x65, 0x65, 0x70, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77,
0x65, 0x65, 0x70, 0x73, 0x22, 0x9f, 0x02, 0x0a, 0x0e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f,
0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70,
0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63,
0x6f, 0x6e, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65,
0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72,
0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52,
0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x05, 0x66,
0x6f, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x05,
0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72,
0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61,
0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6d, 0x6d,
0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x6d,
0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65,
0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x12,
0x25, 0x0a, 0x0e, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74,
0x61, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e,
0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x22, 0x29, 0x0a, 0x0f, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65,
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61,
0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
0x73, 0x22, 0xf7, 0x01, 0x0a, 0x18, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43,
0x6c, 0x6f, 0x73, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32,
0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e,
0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69,
0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x64,
0x65, 0x6c, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x64, 0x65, 0x61, 0x64,
0x6c, 0x69, 0x6e, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x74, 0x61,
0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x65, 0x65, 0x72, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20,
0x01, 0x28, 0x04, 0x52, 0x0f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x46, 0x65, 0x65,
0x72, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74,
0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61,
0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01,
0x28, 0x04, 0x52, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61,
0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52,
0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x22, 0x33, 0x0a, 0x19, 0x42,
0x75, 0x6d, 0x70, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x46, 0x65, 0x65,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74,
0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
0x22, 0x50, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x12,
0x21, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18,
0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x48, 0x65, 0x69, 0x67,
0x68, 0x74, 0x22, 0x80, 0x02, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70,
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x13, 0x74, 0x72, 0x61,
0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54,
0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c,
0x73, 0x48, 0x00, 0x52, 0x12, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x57, 0x0a, 0x0f, 0x74, 0x72, 0x61, 0x6e, 0x73,
0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x2c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73,
0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e,
0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x48, 0x00,
0x52, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73,
0x1a, 0x39, 0x0a, 0x0e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49,
0x44, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x72, 0x61,
0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x42, 0x08, 0x0a, 0x06, 0x73,
0x77, 0x65, 0x65, 0x70, 0x73, 0x22, 0x61, 0x0a, 0x17, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72,
0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04,
0x74, 0x78, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x6f, 0x76,
0x65, 0x72, 0x77, 0x72, 0x69, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6f,
0x76, 0x65, 0x72, 0x77, 0x72, 0x69, 0x74, 0x65, 0x22, 0x32, 0x0a, 0x18, 0x4c, 0x61, 0x62, 0x65,
0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x88, 0x05, 0x0a,
0x0f, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x14, 0x0a, 0x04, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00,
0x52, 0x04, 0x70, 0x73, 0x62, 0x74, 0x12, 0x29, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e,
0x54, 0x78, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x03, 0x72, 0x61,
0x77, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74,
0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72,
0x70, 0x63, 0x2e, 0x50, 0x73, 0x62, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63,
0x74, 0x48, 0x00, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x12,
0x21, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x03,
0x20, 0x01, 0x28, 0x0d, 0x48, 0x01, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f,
0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62,
0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, 0x0b, 0x73, 0x61, 0x74,
0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x61, 0x74, 0x5f,
0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, 0x08,
0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f,
0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18,
0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12,
0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69,
0x72, 0x6d, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e,
0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x3d, 0x0a, 0x0b,
0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28,
0x0e, 0x32, 0x1c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68,
0x61, 0x6e, 0x67, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x52,
0x0a, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x54, 0x0a, 0x17, 0x63,
0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74,
0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x15, 0x63, 0x6f, 0x69, 0x6e,
0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67,
0x79, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74,
0x69, 0x6f, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x46, 0x65, 0x65,
0x52, 0x61, 0x74, 0x69, 0x6f, 0x12, 0x24, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f,
0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x63,
0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4c, 0x6f, 0x63, 0x6b, 0x49, 0x64, 0x12, 0x36, 0x0a, 0x17, 0x6c,
0x6f, 0x63, 0x6b, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73,
0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x6c, 0x6f,
0x63, 0x6b, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x63, 0x6f,
0x6e, 0x64, 0x73, 0x42, 0x0a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x42,
0x06, 0x0a, 0x04, 0x66, 0x65, 0x65, 0x73, 0x22, 0x9c, 0x01, 0x0a, 0x10, 0x46, 0x75, 0x6e, 0x64,
0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b,
0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x2e, 0x0a,
0x13, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69,
0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x63, 0x68, 0x61, 0x6e,
0x67, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x37, 0x0a,
0x0c, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x03, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e,
0x55, 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x52, 0x0b, 0x6c, 0x6f, 0x63, 0x6b, 0x65,
0x64, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x22, 0xaf, 0x01, 0x0a, 0x0a, 0x54, 0x78, 0x54, 0x65, 0x6d,
0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18,
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75,
0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x3c,
0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x78, 0x54, 0x65,
0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x45, 0x6e,
0x74, 0x72, 0x79, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x1a, 0x3a, 0x0a, 0x0c,
0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03,
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14,
0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x7f, 0x0a, 0x0e, 0x50, 0x73, 0x62, 0x74,
0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x73,
0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x73, 0x62, 0x74, 0x12, 0x34,
0x0a, 0x15, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75,
0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52,
0x13, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49,
0x6e, 0x64, 0x65, 0x78, 0x12, 0x12, 0x0a, 0x03, 0x61, 0x64, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
0x08, 0x48, 0x00, 0x52, 0x03, 0x61, 0x64, 0x64, 0x42, 0x0f, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e,
0x67, 0x65, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x9b, 0x01, 0x0a, 0x09, 0x55, 0x74,
0x78, 0x6f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f,
0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70,
0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6b, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70,
0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70,
0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04,
0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x32, 0x0a, 0x0f, 0x53, 0x69, 0x67, 0x6e, 0x50,
0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75,
0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x22, 0x58, 0x0a, 0x10, 0x53,
0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74,
0x12, 0x23, 0x0a, 0x0d, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74,
0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0c, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x49,
0x6e, 0x70, 0x75, 0x74, 0x73, 0x22, 0x50, 0x0a, 0x13, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a,
0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b,
0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x18, 0x0a,
0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x59, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c,
0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74,
0x12, 0x20, 0x0a, 0x0c, 0x72, 0x61, 0x77, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x74, 0x78,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x61, 0x77, 0x46, 0x69, 0x6e, 0x61, 0x6c,
0x54, 0x78, 0x22, 0x13, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4d, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x4c,
0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a,
0x0c, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e,
0x55, 0x74, 0x78, 0x6f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x52, 0x0b, 0x6c, 0x6f, 0x63, 0x6b, 0x65,
0x64, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x2a, 0x8e, 0x01, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57,
0x4e, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50,
0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a,
0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50,
0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x02, 0x12, 0x25, 0x0a, 0x21,
0x48, 0x59, 0x42, 0x52, 0x49, 0x44, 0x5f, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49,
0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53,
0x48, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50,
0x55, 0x42, 0x4b, 0x45, 0x59, 0x10, 0x04, 0x2a, 0xfb, 0x09, 0x0a, 0x0b, 0x57, 0x69, 0x74, 0x6e,
0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f,
0x57, 0x4e, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14,
0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x5f,
0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54,
0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, 0x5f, 0x44, 0x45, 0x4c, 0x41, 0x59, 0x10, 0x02, 0x12,
0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x52, 0x45,
0x56, 0x4f, 0x4b, 0x45, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f,
0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x04, 0x12,
0x18, 0x0a, 0x14, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44,
0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x05, 0x12, 0x25, 0x0a, 0x21, 0x48, 0x54, 0x4c,
0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55,
0x54, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x06,
0x12, 0x26, 0x0a, 0x22, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45,
0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44,
0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x48, 0x54, 0x4c, 0x43,
0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f,
0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x08, 0x12, 0x20, 0x0a, 0x1c, 0x48, 0x54, 0x4c,
0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54,
0x45, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x09, 0x12, 0x1c, 0x0a, 0x18, 0x48,
0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c,
0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x0a, 0x12, 0x14, 0x0a, 0x10, 0x57, 0x49, 0x54,
0x4e, 0x45, 0x53, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x0b, 0x12,
0x1b, 0x0a, 0x17, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53,
0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x0c, 0x12, 0x15, 0x0a, 0x11,
0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x4e, 0x43, 0x48, 0x4f,
0x52, 0x10, 0x0d, 0x12, 0x21, 0x0a, 0x1d, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e,
0x54, 0x5f, 0x4e, 0x4f, 0x5f, 0x44, 0x45, 0x4c, 0x41, 0x59, 0x5f, 0x54, 0x57, 0x45, 0x41, 0x4b,
0x4c, 0x45, 0x53, 0x53, 0x10, 0x0e, 0x12, 0x22, 0x0a, 0x1e, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54,
0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x43,
0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x10, 0x0f, 0x12, 0x35, 0x0a, 0x31, 0x48, 0x54,
0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f,
0x55, 0x54, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f,
0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x10,
0x10, 0x12, 0x36, 0x0a, 0x32, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54,
0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e,
0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x43, 0x4f,
0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x10, 0x11, 0x12, 0x1e, 0x0a, 0x1a, 0x4c, 0x45, 0x41,
0x53, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x49,
0x4d, 0x45, 0x5f, 0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x12, 0x12, 0x28, 0x0a, 0x24, 0x4c, 0x45, 0x41,
0x53, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x4f,
0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45,
0x44, 0x10, 0x13, 0x12, 0x2b, 0x0a, 0x27, 0x4c, 0x45, 0x41, 0x53, 0x45, 0x5f, 0x48, 0x54, 0x4c,
0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55,
0x54, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x14,
0x12, 0x2c, 0x0a, 0x28, 0x4c, 0x45, 0x41, 0x53, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41,
0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f,
0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x15, 0x12, 0x19,
0x0a, 0x15, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x5f, 0x4b, 0x45,
0x59, 0x5f, 0x53, 0x50, 0x45, 0x4e, 0x44, 0x10, 0x16, 0x12, 0x1e, 0x0a, 0x1a, 0x54, 0x41, 0x50,
0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49,
0x54, 0x5f, 0x53, 0x50, 0x45, 0x4e, 0x44, 0x10, 0x17, 0x12, 0x1f, 0x0a, 0x1b, 0x54, 0x41, 0x50,
0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x4d,
0x49, 0x54, 0x5f, 0x53, 0x50, 0x45, 0x4e, 0x44, 0x10, 0x18, 0x12, 0x1e, 0x0a, 0x1a, 0x54, 0x41,
0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x5f, 0x53, 0x57, 0x45,
0x45, 0x50, 0x5f, 0x53, 0x50, 0x45, 0x4e, 0x44, 0x10, 0x19, 0x12, 0x2d, 0x0a, 0x29, 0x54, 0x41,
0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52,
0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e,
0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x1a, 0x12, 0x2e, 0x0a, 0x2a, 0x54, 0x41, 0x50,
0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54,
0x45, 0x44, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e,
0x44, 0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x10, 0x1b, 0x12, 0x24, 0x0a, 0x20, 0x54, 0x41, 0x50,
0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x43, 0x4f, 0x4e, 0x44,
0x5f, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10, 0x1c, 0x12,
0x20, 0x0a, 0x1c, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f,
0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x10,
0x1d, 0x12, 0x1f, 0x0a, 0x1b, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, 0x4c,
0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45,
0x10, 0x1e, 0x12, 0x27, 0x0a, 0x23, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54,
0x4c, 0x43, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54,
0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x1f, 0x12, 0x26, 0x0a, 0x22, 0x54,
0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4c, 0x4f, 0x43, 0x41,
0x4c, 0x5f, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55,
0x54, 0x10, 0x20, 0x12, 0x28, 0x0a, 0x24, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48,
0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x4d,
0x4f, 0x54, 0x45, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x21, 0x12, 0x27, 0x0a,
0x23, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x43,
0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x5f, 0x53, 0x55, 0x43,
0x43, 0x45, 0x53, 0x53, 0x10, 0x22, 0x12, 0x1d, 0x0a, 0x19, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f,
0x54, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x52, 0x45, 0x56,
0x4f, 0x4b, 0x45, 0x10, 0x23, 0x2a, 0x56, 0x0a, 0x11, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x1f, 0x43, 0x48,
0x41, 0x4e, 0x47, 0x45, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x45, 0x53, 0x53, 0x5f, 0x54, 0x59, 0x50,
0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12,
0x1c, 0x0a, 0x18, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x45, 0x53,
0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x32, 0x54, 0x52, 0x10, 0x01, 0x32, 0xd6, 0x11,
0x0a, 0x09, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x4b, 0x69, 0x74, 0x12, 0x4c, 0x0a, 0x0b, 0x4c,
0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c,
0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65,
0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c,
0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e,
0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x4c, 0x65, 0x61,
0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65,
0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x52, 0x65, 0x6c, 0x65, 0x61,
0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65,
0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70,
0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c,
0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4f, 0x75, 0x74,
0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x4c,
0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x77, 0x61, 0x6c, 0x6c,
0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0d, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65,
0x4e, 0x65, 0x78, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x73, 0x69, 0x67,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x6f, 0x72, 0x12, 0x38, 0x0a, 0x09, 0x44, 0x65, 0x72, 0x69, 0x76, 0x65, 0x4b, 0x65, 0x79, 0x12,
0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63,
0x61, 0x74, 0x6f, 0x72, 0x1a, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b,
0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x3b, 0x0a, 0x08,
0x4e, 0x65, 0x78, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65,
0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x17, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64,
0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0e, 0x47, 0x65, 0x74,
0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x77, 0x61,
0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73,
0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x12, 0x4f, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
0x73, 0x12, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69,
0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69,
0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65,
0x73, 0x65, 0x72, 0x76, 0x65, 0x12, 0x21, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70,
0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65,
0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73,
0x65, 0x72, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d,
0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x1f, 0x2e,
0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20,
0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x64, 0x0a, 0x13, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57,
0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x12, 0x25, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57,
0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26,
0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d,
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x15, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79,
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x12,
0x27, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69,
0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64,
0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65,
0x74, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x41, 0x64, 0x64, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f,
0x75, 0x6e, 0x74, 0x12, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e,
0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63,
0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74,
0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x2e, 0x77, 0x61, 0x6c, 0x6c,
0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x75, 0x62, 0x6c,
0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x77,
0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50,
0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x58, 0x0a, 0x0f, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72,
0x69, 0x70, 0x74, 0x12, 0x21, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e,
0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72,
0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x70, 0x73, 0x63, 0x72, 0x69,
0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x12, 0x50, 0x75,
0x62, 0x6c, 0x69, 0x73, 0x68, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x12, 0x16, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61,
0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65,
0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x11, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x72,
0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c,
0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x77, 0x61,
0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x72,
0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73,
0x12, 0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e,
0x64, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64,
0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x4c, 0x0a, 0x0b, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, 0x1d,
0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d,
0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e,
0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61,
0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a,
0x0d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x12, 0x1f,
0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69,
0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x20, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64,
0x69, 0x6e, 0x67, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x40, 0x0a, 0x07, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x12, 0x19, 0x2e, 0x77,
0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x5e, 0x0a, 0x11, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x6f, 0x72, 0x63, 0x65,
0x43, 0x6c, 0x6f, 0x73, 0x65, 0x46, 0x65, 0x65, 0x12, 0x23, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65,
0x74, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c,
0x6f, 0x73, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e,
0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x6d, 0x70, 0x46, 0x6f,
0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70,
0x73, 0x12, 0x1c, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69,
0x73, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x1d, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74,
0x53, 0x77, 0x65, 0x65, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b,
0x0a, 0x10, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x12, 0x22, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c,
0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72,
0x70, 0x63, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x08, 0x46,
0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1a, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e,
0x46, 0x75, 0x6e, 0x64, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x43, 0x0a, 0x08, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1a, 0x2e, 0x77,
0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62,
0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65,
0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0c, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a,
0x65, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1e, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70,
0x63, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70,
0x63, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x73, 0x62, 0x74, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65,
0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f,
0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x33,
}
var (
file_walletrpc_walletkit_proto_rawDescOnce sync.Once
file_walletrpc_walletkit_proto_rawDescData = file_walletrpc_walletkit_proto_rawDesc
)
func file_walletrpc_walletkit_proto_rawDescGZIP() []byte {
file_walletrpc_walletkit_proto_rawDescOnce.Do(func() {
file_walletrpc_walletkit_proto_rawDescData = protoimpl.X.CompressGZIP(file_walletrpc_walletkit_proto_rawDescData)
})
return file_walletrpc_walletkit_proto_rawDescData
}
var file_walletrpc_walletkit_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
var file_walletrpc_walletkit_proto_msgTypes = make([]protoimpl.MessageInfo, 63)
var file_walletrpc_walletkit_proto_goTypes = []interface{}{
(AddressType)(0), // 0: walletrpc.AddressType
(WitnessType)(0), // 1: walletrpc.WitnessType
(ChangeAddressType)(0), // 2: walletrpc.ChangeAddressType
(*ListUnspentRequest)(nil), // 3: walletrpc.ListUnspentRequest
(*ListUnspentResponse)(nil), // 4: walletrpc.ListUnspentResponse
(*LeaseOutputRequest)(nil), // 5: walletrpc.LeaseOutputRequest
(*LeaseOutputResponse)(nil), // 6: walletrpc.LeaseOutputResponse
(*ReleaseOutputRequest)(nil), // 7: walletrpc.ReleaseOutputRequest
(*ReleaseOutputResponse)(nil), // 8: walletrpc.ReleaseOutputResponse
(*KeyReq)(nil), // 9: walletrpc.KeyReq
(*AddrRequest)(nil), // 10: walletrpc.AddrRequest
(*AddrResponse)(nil), // 11: walletrpc.AddrResponse
(*Account)(nil), // 12: walletrpc.Account
(*AddressProperty)(nil), // 13: walletrpc.AddressProperty
(*AccountWithAddresses)(nil), // 14: walletrpc.AccountWithAddresses
(*ListAccountsRequest)(nil), // 15: walletrpc.ListAccountsRequest
(*ListAccountsResponse)(nil), // 16: walletrpc.ListAccountsResponse
(*RequiredReserveRequest)(nil), // 17: walletrpc.RequiredReserveRequest
(*RequiredReserveResponse)(nil), // 18: walletrpc.RequiredReserveResponse
(*ListAddressesRequest)(nil), // 19: walletrpc.ListAddressesRequest
(*ListAddressesResponse)(nil), // 20: walletrpc.ListAddressesResponse
(*GetTransactionRequest)(nil), // 21: walletrpc.GetTransactionRequest
(*SignMessageWithAddrRequest)(nil), // 22: walletrpc.SignMessageWithAddrRequest
(*SignMessageWithAddrResponse)(nil), // 23: walletrpc.SignMessageWithAddrResponse
(*VerifyMessageWithAddrRequest)(nil), // 24: walletrpc.VerifyMessageWithAddrRequest
(*VerifyMessageWithAddrResponse)(nil), // 25: walletrpc.VerifyMessageWithAddrResponse
(*ImportAccountRequest)(nil), // 26: walletrpc.ImportAccountRequest
(*ImportAccountResponse)(nil), // 27: walletrpc.ImportAccountResponse
(*ImportPublicKeyRequest)(nil), // 28: walletrpc.ImportPublicKeyRequest
(*ImportPublicKeyResponse)(nil), // 29: walletrpc.ImportPublicKeyResponse
(*ImportTapscriptRequest)(nil), // 30: walletrpc.ImportTapscriptRequest
(*TapscriptFullTree)(nil), // 31: walletrpc.TapscriptFullTree
(*TapLeaf)(nil), // 32: walletrpc.TapLeaf
(*TapscriptPartialReveal)(nil), // 33: walletrpc.TapscriptPartialReveal
(*ImportTapscriptResponse)(nil), // 34: walletrpc.ImportTapscriptResponse
(*Transaction)(nil), // 35: walletrpc.Transaction
(*PublishResponse)(nil), // 36: walletrpc.PublishResponse
(*RemoveTransactionResponse)(nil), // 37: walletrpc.RemoveTransactionResponse
(*SendOutputsRequest)(nil), // 38: walletrpc.SendOutputsRequest
(*SendOutputsResponse)(nil), // 39: walletrpc.SendOutputsResponse
(*EstimateFeeRequest)(nil), // 40: walletrpc.EstimateFeeRequest
(*EstimateFeeResponse)(nil), // 41: walletrpc.EstimateFeeResponse
(*PendingSweep)(nil), // 42: walletrpc.PendingSweep
(*PendingSweepsRequest)(nil), // 43: walletrpc.PendingSweepsRequest
(*PendingSweepsResponse)(nil), // 44: walletrpc.PendingSweepsResponse
(*BumpFeeRequest)(nil), // 45: walletrpc.BumpFeeRequest
(*BumpFeeResponse)(nil), // 46: walletrpc.BumpFeeResponse
(*BumpForceCloseFeeRequest)(nil), // 47: walletrpc.BumpForceCloseFeeRequest
(*BumpForceCloseFeeResponse)(nil), // 48: walletrpc.BumpForceCloseFeeResponse
(*ListSweepsRequest)(nil), // 49: walletrpc.ListSweepsRequest
(*ListSweepsResponse)(nil), // 50: walletrpc.ListSweepsResponse
(*LabelTransactionRequest)(nil), // 51: walletrpc.LabelTransactionRequest
(*LabelTransactionResponse)(nil), // 52: walletrpc.LabelTransactionResponse
(*FundPsbtRequest)(nil), // 53: walletrpc.FundPsbtRequest
(*FundPsbtResponse)(nil), // 54: walletrpc.FundPsbtResponse
(*TxTemplate)(nil), // 55: walletrpc.TxTemplate
(*PsbtCoinSelect)(nil), // 56: walletrpc.PsbtCoinSelect
(*UtxoLease)(nil), // 57: walletrpc.UtxoLease
(*SignPsbtRequest)(nil), // 58: walletrpc.SignPsbtRequest
(*SignPsbtResponse)(nil), // 59: walletrpc.SignPsbtResponse
(*FinalizePsbtRequest)(nil), // 60: walletrpc.FinalizePsbtRequest
(*FinalizePsbtResponse)(nil), // 61: walletrpc.FinalizePsbtResponse
(*ListLeasesRequest)(nil), // 62: walletrpc.ListLeasesRequest
(*ListLeasesResponse)(nil), // 63: walletrpc.ListLeasesResponse
(*ListSweepsResponse_TransactionIDs)(nil), // 64: walletrpc.ListSweepsResponse.TransactionIDs
nil, // 65: walletrpc.TxTemplate.OutputsEntry
(*lnrpc.Utxo)(nil), // 66: lnrpc.Utxo
(*lnrpc.OutPoint)(nil), // 67: lnrpc.OutPoint
(*signrpc.TxOut)(nil), // 68: signrpc.TxOut
(lnrpc.CoinSelectionStrategy)(0), // 69: lnrpc.CoinSelectionStrategy
(*lnrpc.ChannelPoint)(nil), // 70: lnrpc.ChannelPoint
(*lnrpc.TransactionDetails)(nil), // 71: lnrpc.TransactionDetails
(*signrpc.KeyLocator)(nil), // 72: signrpc.KeyLocator
(*signrpc.KeyDescriptor)(nil), // 73: signrpc.KeyDescriptor
(*lnrpc.Transaction)(nil), // 74: lnrpc.Transaction
}
var file_walletrpc_walletkit_proto_depIdxs = []int32{
66, // 0: walletrpc.ListUnspentResponse.utxos:type_name -> lnrpc.Utxo
67, // 1: walletrpc.LeaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint
67, // 2: walletrpc.ReleaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint
0, // 3: walletrpc.AddrRequest.type:type_name -> walletrpc.AddressType
0, // 4: walletrpc.Account.address_type:type_name -> walletrpc.AddressType
0, // 5: walletrpc.AccountWithAddresses.address_type:type_name -> walletrpc.AddressType
13, // 6: walletrpc.AccountWithAddresses.addresses:type_name -> walletrpc.AddressProperty
0, // 7: walletrpc.ListAccountsRequest.address_type:type_name -> walletrpc.AddressType
12, // 8: walletrpc.ListAccountsResponse.accounts:type_name -> walletrpc.Account
14, // 9: walletrpc.ListAddressesResponse.account_with_addresses:type_name -> walletrpc.AccountWithAddresses
0, // 10: walletrpc.ImportAccountRequest.address_type:type_name -> walletrpc.AddressType
12, // 11: walletrpc.ImportAccountResponse.account:type_name -> walletrpc.Account
0, // 12: walletrpc.ImportPublicKeyRequest.address_type:type_name -> walletrpc.AddressType
31, // 13: walletrpc.ImportTapscriptRequest.full_tree:type_name -> walletrpc.TapscriptFullTree
33, // 14: walletrpc.ImportTapscriptRequest.partial_reveal:type_name -> walletrpc.TapscriptPartialReveal
32, // 15: walletrpc.TapscriptFullTree.all_leaves:type_name -> walletrpc.TapLeaf
32, // 16: walletrpc.TapscriptPartialReveal.revealed_leaf:type_name -> walletrpc.TapLeaf
68, // 17: walletrpc.SendOutputsRequest.outputs:type_name -> signrpc.TxOut
69, // 18: walletrpc.SendOutputsRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy
67, // 19: walletrpc.PendingSweep.outpoint:type_name -> lnrpc.OutPoint
1, // 20: walletrpc.PendingSweep.witness_type:type_name -> walletrpc.WitnessType
42, // 21: walletrpc.PendingSweepsResponse.pending_sweeps:type_name -> walletrpc.PendingSweep
67, // 22: walletrpc.BumpFeeRequest.outpoint:type_name -> lnrpc.OutPoint
70, // 23: walletrpc.BumpForceCloseFeeRequest.chan_point:type_name -> lnrpc.ChannelPoint
71, // 24: walletrpc.ListSweepsResponse.transaction_details:type_name -> lnrpc.TransactionDetails
64, // 25: walletrpc.ListSweepsResponse.transaction_ids:type_name -> walletrpc.ListSweepsResponse.TransactionIDs
55, // 26: walletrpc.FundPsbtRequest.raw:type_name -> walletrpc.TxTemplate
56, // 27: walletrpc.FundPsbtRequest.coin_select:type_name -> walletrpc.PsbtCoinSelect
2, // 28: walletrpc.FundPsbtRequest.change_type:type_name -> walletrpc.ChangeAddressType
69, // 29: walletrpc.FundPsbtRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy
57, // 30: walletrpc.FundPsbtResponse.locked_utxos:type_name -> walletrpc.UtxoLease
67, // 31: walletrpc.TxTemplate.inputs:type_name -> lnrpc.OutPoint
65, // 32: walletrpc.TxTemplate.outputs:type_name -> walletrpc.TxTemplate.OutputsEntry
67, // 33: walletrpc.UtxoLease.outpoint:type_name -> lnrpc.OutPoint
57, // 34: walletrpc.ListLeasesResponse.locked_utxos:type_name -> walletrpc.UtxoLease
3, // 35: walletrpc.WalletKit.ListUnspent:input_type -> walletrpc.ListUnspentRequest
5, // 36: walletrpc.WalletKit.LeaseOutput:input_type -> walletrpc.LeaseOutputRequest
7, // 37: walletrpc.WalletKit.ReleaseOutput:input_type -> walletrpc.ReleaseOutputRequest
62, // 38: walletrpc.WalletKit.ListLeases:input_type -> walletrpc.ListLeasesRequest
9, // 39: walletrpc.WalletKit.DeriveNextKey:input_type -> walletrpc.KeyReq
72, // 40: walletrpc.WalletKit.DeriveKey:input_type -> signrpc.KeyLocator
10, // 41: walletrpc.WalletKit.NextAddr:input_type -> walletrpc.AddrRequest
21, // 42: walletrpc.WalletKit.GetTransaction:input_type -> walletrpc.GetTransactionRequest
15, // 43: walletrpc.WalletKit.ListAccounts:input_type -> walletrpc.ListAccountsRequest
17, // 44: walletrpc.WalletKit.RequiredReserve:input_type -> walletrpc.RequiredReserveRequest
19, // 45: walletrpc.WalletKit.ListAddresses:input_type -> walletrpc.ListAddressesRequest
22, // 46: walletrpc.WalletKit.SignMessageWithAddr:input_type -> walletrpc.SignMessageWithAddrRequest
24, // 47: walletrpc.WalletKit.VerifyMessageWithAddr:input_type -> walletrpc.VerifyMessageWithAddrRequest
26, // 48: walletrpc.WalletKit.ImportAccount:input_type -> walletrpc.ImportAccountRequest
28, // 49: walletrpc.WalletKit.ImportPublicKey:input_type -> walletrpc.ImportPublicKeyRequest
30, // 50: walletrpc.WalletKit.ImportTapscript:input_type -> walletrpc.ImportTapscriptRequest
35, // 51: walletrpc.WalletKit.PublishTransaction:input_type -> walletrpc.Transaction
21, // 52: walletrpc.WalletKit.RemoveTransaction:input_type -> walletrpc.GetTransactionRequest
38, // 53: walletrpc.WalletKit.SendOutputs:input_type -> walletrpc.SendOutputsRequest
40, // 54: walletrpc.WalletKit.EstimateFee:input_type -> walletrpc.EstimateFeeRequest
43, // 55: walletrpc.WalletKit.PendingSweeps:input_type -> walletrpc.PendingSweepsRequest
45, // 56: walletrpc.WalletKit.BumpFee:input_type -> walletrpc.BumpFeeRequest
47, // 57: walletrpc.WalletKit.BumpForceCloseFee:input_type -> walletrpc.BumpForceCloseFeeRequest
49, // 58: walletrpc.WalletKit.ListSweeps:input_type -> walletrpc.ListSweepsRequest
51, // 59: walletrpc.WalletKit.LabelTransaction:input_type -> walletrpc.LabelTransactionRequest
53, // 60: walletrpc.WalletKit.FundPsbt:input_type -> walletrpc.FundPsbtRequest
58, // 61: walletrpc.WalletKit.SignPsbt:input_type -> walletrpc.SignPsbtRequest
60, // 62: walletrpc.WalletKit.FinalizePsbt:input_type -> walletrpc.FinalizePsbtRequest
4, // 63: walletrpc.WalletKit.ListUnspent:output_type -> walletrpc.ListUnspentResponse
6, // 64: walletrpc.WalletKit.LeaseOutput:output_type -> walletrpc.LeaseOutputResponse
8, // 65: walletrpc.WalletKit.ReleaseOutput:output_type -> walletrpc.ReleaseOutputResponse
63, // 66: walletrpc.WalletKit.ListLeases:output_type -> walletrpc.ListLeasesResponse
73, // 67: walletrpc.WalletKit.DeriveNextKey:output_type -> signrpc.KeyDescriptor
73, // 68: walletrpc.WalletKit.DeriveKey:output_type -> signrpc.KeyDescriptor
11, // 69: walletrpc.WalletKit.NextAddr:output_type -> walletrpc.AddrResponse
74, // 70: walletrpc.WalletKit.GetTransaction:output_type -> lnrpc.Transaction
16, // 71: walletrpc.WalletKit.ListAccounts:output_type -> walletrpc.ListAccountsResponse
18, // 72: walletrpc.WalletKit.RequiredReserve:output_type -> walletrpc.RequiredReserveResponse
20, // 73: walletrpc.WalletKit.ListAddresses:output_type -> walletrpc.ListAddressesResponse
23, // 74: walletrpc.WalletKit.SignMessageWithAddr:output_type -> walletrpc.SignMessageWithAddrResponse
25, // 75: walletrpc.WalletKit.VerifyMessageWithAddr:output_type -> walletrpc.VerifyMessageWithAddrResponse
27, // 76: walletrpc.WalletKit.ImportAccount:output_type -> walletrpc.ImportAccountResponse
29, // 77: walletrpc.WalletKit.ImportPublicKey:output_type -> walletrpc.ImportPublicKeyResponse
34, // 78: walletrpc.WalletKit.ImportTapscript:output_type -> walletrpc.ImportTapscriptResponse
36, // 79: walletrpc.WalletKit.PublishTransaction:output_type -> walletrpc.PublishResponse
37, // 80: walletrpc.WalletKit.RemoveTransaction:output_type -> walletrpc.RemoveTransactionResponse
39, // 81: walletrpc.WalletKit.SendOutputs:output_type -> walletrpc.SendOutputsResponse
41, // 82: walletrpc.WalletKit.EstimateFee:output_type -> walletrpc.EstimateFeeResponse
44, // 83: walletrpc.WalletKit.PendingSweeps:output_type -> walletrpc.PendingSweepsResponse
46, // 84: walletrpc.WalletKit.BumpFee:output_type -> walletrpc.BumpFeeResponse
48, // 85: walletrpc.WalletKit.BumpForceCloseFee:output_type -> walletrpc.BumpForceCloseFeeResponse
50, // 86: walletrpc.WalletKit.ListSweeps:output_type -> walletrpc.ListSweepsResponse
52, // 87: walletrpc.WalletKit.LabelTransaction:output_type -> walletrpc.LabelTransactionResponse
54, // 88: walletrpc.WalletKit.FundPsbt:output_type -> walletrpc.FundPsbtResponse
59, // 89: walletrpc.WalletKit.SignPsbt:output_type -> walletrpc.SignPsbtResponse
61, // 90: walletrpc.WalletKit.FinalizePsbt:output_type -> walletrpc.FinalizePsbtResponse
63, // [63:91] is the sub-list for method output_type
35, // [35:63] is the sub-list for method input_type
35, // [35:35] is the sub-list for extension type_name
35, // [35:35] is the sub-list for extension extendee
0, // [0:35] is the sub-list for field type_name
}
func init() { file_walletrpc_walletkit_proto_init() }
func file_walletrpc_walletkit_proto_init() {
if File_walletrpc_walletkit_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_walletrpc_walletkit_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListUnspentRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListUnspentResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*LeaseOutputRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*LeaseOutputResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ReleaseOutputRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ReleaseOutputResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*KeyReq); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AddrRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AddrResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Account); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AddressProperty); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AccountWithAddresses); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListAccountsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListAccountsResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RequiredReserveRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RequiredReserveResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListAddressesRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListAddressesResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetTransactionRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SignMessageWithAddrRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SignMessageWithAddrResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*VerifyMessageWithAddrRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*VerifyMessageWithAddrResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ImportAccountRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ImportAccountResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ImportPublicKeyRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ImportPublicKeyResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ImportTapscriptRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TapscriptFullTree); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TapLeaf); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TapscriptPartialReveal); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ImportTapscriptResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Transaction); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PublishResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RemoveTransactionResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SendOutputsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SendOutputsResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*EstimateFeeRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*EstimateFeeResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PendingSweep); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PendingSweepsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PendingSweepsResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BumpFeeRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BumpFeeResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BumpForceCloseFeeRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BumpForceCloseFeeResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[46].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListSweepsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[47].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListSweepsResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[48].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*LabelTransactionRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[49].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*LabelTransactionResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[50].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FundPsbtRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[51].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FundPsbtResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[52].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TxTemplate); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[53].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PsbtCoinSelect); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[54].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UtxoLease); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[55].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SignPsbtRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[56].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SignPsbtResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[57].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FinalizePsbtRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[58].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FinalizePsbtResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[59].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListLeasesRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[60].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListLeasesResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletrpc_walletkit_proto_msgTypes[61].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListSweepsResponse_TransactionIDs); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_walletrpc_walletkit_proto_msgTypes[27].OneofWrappers = []interface{}{
(*ImportTapscriptRequest_FullTree)(nil),
(*ImportTapscriptRequest_PartialReveal)(nil),
(*ImportTapscriptRequest_RootHashOnly)(nil),
(*ImportTapscriptRequest_FullKeyOnly)(nil),
}
file_walletrpc_walletkit_proto_msgTypes[47].OneofWrappers = []interface{}{
(*ListSweepsResponse_TransactionDetails)(nil),
(*ListSweepsResponse_TransactionIds)(nil),
}
file_walletrpc_walletkit_proto_msgTypes[50].OneofWrappers = []interface{}{
(*FundPsbtRequest_Psbt)(nil),
(*FundPsbtRequest_Raw)(nil),
(*FundPsbtRequest_CoinSelect)(nil),
(*FundPsbtRequest_TargetConf)(nil),
(*FundPsbtRequest_SatPerVbyte)(nil),
(*FundPsbtRequest_SatPerKw)(nil),
}
file_walletrpc_walletkit_proto_msgTypes[53].OneofWrappers = []interface{}{
(*PsbtCoinSelect_ExistingOutputIndex)(nil),
(*PsbtCoinSelect_Add)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_walletrpc_walletkit_proto_rawDesc,
NumEnums: 3,
NumMessages: 63,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_walletrpc_walletkit_proto_goTypes,
DependencyIndexes: file_walletrpc_walletkit_proto_depIdxs,
EnumInfos: file_walletrpc_walletkit_proto_enumTypes,
MessageInfos: file_walletrpc_walletkit_proto_msgTypes,
}.Build()
File_walletrpc_walletkit_proto = out.File
file_walletrpc_walletkit_proto_rawDesc = nil
file_walletrpc_walletkit_proto_goTypes = nil
file_walletrpc_walletkit_proto_depIdxs = nil
}
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: walletrpc/walletkit.proto
/*
Package walletrpc is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package walletrpc
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
func request_WalletKit_ListUnspent_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListUnspentRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ListUnspent(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_ListUnspent_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListUnspentRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ListUnspent(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_LeaseOutput_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq LeaseOutputRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.LeaseOutput(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_LeaseOutput_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq LeaseOutputRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.LeaseOutput(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_ReleaseOutput_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ReleaseOutputRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ReleaseOutput(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_ReleaseOutput_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ReleaseOutputRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ReleaseOutput(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_ListLeases_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListLeasesRequest
var metadata runtime.ServerMetadata
msg, err := client.ListLeases(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_ListLeases_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListLeasesRequest
var metadata runtime.ServerMetadata
msg, err := server.ListLeases(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_DeriveNextKey_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq KeyReq
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.DeriveNextKey(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_DeriveNextKey_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq KeyReq
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.DeriveNextKey(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_DeriveKey_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq signrpc.KeyLocator
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.DeriveKey(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_DeriveKey_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq signrpc.KeyLocator
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.DeriveKey(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_NextAddr_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq AddrRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.NextAddr(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_NextAddr_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq AddrRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.NextAddr(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_WalletKit_GetTransaction_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_WalletKit_GetTransaction_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetTransactionRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WalletKit_GetTransaction_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.GetTransaction(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_GetTransaction_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetTransactionRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WalletKit_GetTransaction_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.GetTransaction(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_WalletKit_ListAccounts_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_WalletKit_ListAccounts_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListAccountsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WalletKit_ListAccounts_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ListAccounts(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_ListAccounts_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListAccountsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WalletKit_ListAccounts_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ListAccounts(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_WalletKit_RequiredReserve_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_WalletKit_RequiredReserve_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq RequiredReserveRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WalletKit_RequiredReserve_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.RequiredReserve(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_RequiredReserve_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq RequiredReserveRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WalletKit_RequiredReserve_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.RequiredReserve(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_WalletKit_ListAddresses_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_WalletKit_ListAddresses_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListAddressesRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WalletKit_ListAddresses_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ListAddresses(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_ListAddresses_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListAddressesRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WalletKit_ListAddresses_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ListAddresses(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_SignMessageWithAddr_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SignMessageWithAddrRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.SignMessageWithAddr(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_SignMessageWithAddr_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SignMessageWithAddrRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.SignMessageWithAddr(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_VerifyMessageWithAddr_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq VerifyMessageWithAddrRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.VerifyMessageWithAddr(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_VerifyMessageWithAddr_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq VerifyMessageWithAddrRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.VerifyMessageWithAddr(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_ImportAccount_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ImportAccountRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ImportAccount(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_ImportAccount_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ImportAccountRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ImportAccount(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_ImportPublicKey_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ImportPublicKeyRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ImportPublicKey(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_ImportPublicKey_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ImportPublicKeyRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ImportPublicKey(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_ImportTapscript_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ImportTapscriptRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ImportTapscript(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_ImportTapscript_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ImportTapscriptRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ImportTapscript(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_PublishTransaction_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq Transaction
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.PublishTransaction(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_PublishTransaction_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq Transaction
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.PublishTransaction(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_RemoveTransaction_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetTransactionRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.RemoveTransaction(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_RemoveTransaction_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetTransactionRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.RemoveTransaction(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_SendOutputs_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SendOutputsRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.SendOutputs(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_SendOutputs_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SendOutputsRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.SendOutputs(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_EstimateFee_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq EstimateFeeRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["conf_target"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "conf_target")
}
protoReq.ConfTarget, err = runtime.Int32(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "conf_target", err)
}
msg, err := client.EstimateFee(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_EstimateFee_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq EstimateFeeRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["conf_target"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "conf_target")
}
protoReq.ConfTarget, err = runtime.Int32(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "conf_target", err)
}
msg, err := server.EstimateFee(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_PendingSweeps_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq PendingSweepsRequest
var metadata runtime.ServerMetadata
msg, err := client.PendingSweeps(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_PendingSweeps_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq PendingSweepsRequest
var metadata runtime.ServerMetadata
msg, err := server.PendingSweeps(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_BumpFee_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq BumpFeeRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.BumpFee(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_BumpFee_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq BumpFeeRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.BumpFee(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_BumpForceCloseFee_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq BumpForceCloseFeeRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.BumpForceCloseFee(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_BumpForceCloseFee_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq BumpForceCloseFeeRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.BumpForceCloseFee(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_WalletKit_ListSweeps_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_WalletKit_ListSweeps_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListSweepsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WalletKit_ListSweeps_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ListSweeps(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_ListSweeps_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListSweepsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WalletKit_ListSweeps_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ListSweeps(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_LabelTransaction_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq LabelTransactionRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.LabelTransaction(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_LabelTransaction_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq LabelTransactionRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.LabelTransaction(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_FundPsbt_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq FundPsbtRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.FundPsbt(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_FundPsbt_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq FundPsbtRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.FundPsbt(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_SignPsbt_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SignPsbtRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.SignPsbt(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_SignPsbt_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SignPsbtRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.SignPsbt(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_FinalizePsbt_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq FinalizePsbtRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.FinalizePsbt(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_FinalizePsbt_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq FinalizePsbtRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.FinalizePsbt(ctx, &protoReq)
return msg, metadata, err
}
// RegisterWalletKitHandlerServer registers the http handlers for service WalletKit to "mux".
// UnaryRPC :call WalletKitServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterWalletKitHandlerFromEndpoint instead.
func RegisterWalletKitHandlerServer(ctx context.Context, mux *runtime.ServeMux, server WalletKitServer) error {
mux.Handle("POST", pattern_WalletKit_ListUnspent_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/ListUnspent", runtime.WithHTTPPathPattern("/v2/wallet/utxos"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_ListUnspent_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_ListUnspent_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_LeaseOutput_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/LeaseOutput", runtime.WithHTTPPathPattern("/v2/wallet/utxos/lease"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_LeaseOutput_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_LeaseOutput_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_ReleaseOutput_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/ReleaseOutput", runtime.WithHTTPPathPattern("/v2/wallet/utxos/release"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_ReleaseOutput_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_ReleaseOutput_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_ListLeases_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/ListLeases", runtime.WithHTTPPathPattern("/v2/wallet/utxos/leases"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_ListLeases_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_ListLeases_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_DeriveNextKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/DeriveNextKey", runtime.WithHTTPPathPattern("/v2/wallet/key/next"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_DeriveNextKey_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_DeriveNextKey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_DeriveKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/DeriveKey", runtime.WithHTTPPathPattern("/v2/wallet/key"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_DeriveKey_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_DeriveKey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_NextAddr_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/NextAddr", runtime.WithHTTPPathPattern("/v2/wallet/address/next"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_NextAddr_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_NextAddr_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WalletKit_GetTransaction_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/GetTransaction", runtime.WithHTTPPathPattern("/v2/wallet/tx"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_GetTransaction_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_GetTransaction_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WalletKit_ListAccounts_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/ListAccounts", runtime.WithHTTPPathPattern("/v2/wallet/accounts"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_ListAccounts_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_ListAccounts_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WalletKit_RequiredReserve_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/RequiredReserve", runtime.WithHTTPPathPattern("/v2/wallet/reserve"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_RequiredReserve_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_RequiredReserve_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WalletKit_ListAddresses_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/ListAddresses", runtime.WithHTTPPathPattern("/v2/wallet/addresses"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_ListAddresses_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_ListAddresses_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_SignMessageWithAddr_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/SignMessageWithAddr", runtime.WithHTTPPathPattern("/v2/wallet/address/signmessage"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_SignMessageWithAddr_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_SignMessageWithAddr_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_VerifyMessageWithAddr_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/VerifyMessageWithAddr", runtime.WithHTTPPathPattern("/v2/wallet/address/verifymessage"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_VerifyMessageWithAddr_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_VerifyMessageWithAddr_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_ImportAccount_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/ImportAccount", runtime.WithHTTPPathPattern("/v2/wallet/accounts/import"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_ImportAccount_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_ImportAccount_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_ImportPublicKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/ImportPublicKey", runtime.WithHTTPPathPattern("/v2/wallet/key/import"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_ImportPublicKey_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_ImportPublicKey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_ImportTapscript_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/ImportTapscript", runtime.WithHTTPPathPattern("/v2/wallet/tapscript/import"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_ImportTapscript_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_ImportTapscript_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_PublishTransaction_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/PublishTransaction", runtime.WithHTTPPathPattern("/v2/wallet/tx"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_PublishTransaction_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_PublishTransaction_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_RemoveTransaction_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/RemoveTransaction", runtime.WithHTTPPathPattern("/v2/wallet/removetx"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_RemoveTransaction_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_RemoveTransaction_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_SendOutputs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/SendOutputs", runtime.WithHTTPPathPattern("/v2/wallet/send"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_SendOutputs_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_SendOutputs_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WalletKit_EstimateFee_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/EstimateFee", runtime.WithHTTPPathPattern("/v2/wallet/estimatefee/{conf_target}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_EstimateFee_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_EstimateFee_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WalletKit_PendingSweeps_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/PendingSweeps", runtime.WithHTTPPathPattern("/v2/wallet/sweeps/pending"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_PendingSweeps_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_PendingSweeps_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_BumpFee_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/BumpFee", runtime.WithHTTPPathPattern("/v2/wallet/bumpfee"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_BumpFee_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_BumpFee_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_BumpForceCloseFee_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/BumpForceCloseFee", runtime.WithHTTPPathPattern("/v2/wallet/BumpForceCloseFee"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_BumpForceCloseFee_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_BumpForceCloseFee_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WalletKit_ListSweeps_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/ListSweeps", runtime.WithHTTPPathPattern("/v2/wallet/sweeps"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_ListSweeps_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_ListSweeps_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_LabelTransaction_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/LabelTransaction", runtime.WithHTTPPathPattern("/v2/wallet/tx/label"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_LabelTransaction_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_LabelTransaction_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_FundPsbt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/FundPsbt", runtime.WithHTTPPathPattern("/v2/wallet/psbt/fund"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_FundPsbt_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_FundPsbt_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_SignPsbt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/SignPsbt", runtime.WithHTTPPathPattern("/v2/wallet/psbt/sign"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_SignPsbt_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_SignPsbt_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_FinalizePsbt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/FinalizePsbt", runtime.WithHTTPPathPattern("/v2/wallet/psbt/finalize"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_FinalizePsbt_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_FinalizePsbt_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterWalletKitHandlerFromEndpoint is same as RegisterWalletKitHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterWalletKitHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterWalletKitHandler(ctx, mux, conn)
}
// RegisterWalletKitHandler registers the http handlers for service WalletKit to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterWalletKitHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterWalletKitHandlerClient(ctx, mux, NewWalletKitClient(conn))
}
// RegisterWalletKitHandlerClient registers the http handlers for service WalletKit
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "WalletKitClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "WalletKitClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "WalletKitClient" to call the correct interceptors.
func RegisterWalletKitHandlerClient(ctx context.Context, mux *runtime.ServeMux, client WalletKitClient) error {
mux.Handle("POST", pattern_WalletKit_ListUnspent_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/ListUnspent", runtime.WithHTTPPathPattern("/v2/wallet/utxos"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_ListUnspent_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_ListUnspent_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_LeaseOutput_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/LeaseOutput", runtime.WithHTTPPathPattern("/v2/wallet/utxos/lease"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_LeaseOutput_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_LeaseOutput_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_ReleaseOutput_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/ReleaseOutput", runtime.WithHTTPPathPattern("/v2/wallet/utxos/release"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_ReleaseOutput_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_ReleaseOutput_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_ListLeases_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/ListLeases", runtime.WithHTTPPathPattern("/v2/wallet/utxos/leases"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_ListLeases_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_ListLeases_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_DeriveNextKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/DeriveNextKey", runtime.WithHTTPPathPattern("/v2/wallet/key/next"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_DeriveNextKey_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_DeriveNextKey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_DeriveKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/DeriveKey", runtime.WithHTTPPathPattern("/v2/wallet/key"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_DeriveKey_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_DeriveKey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_NextAddr_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/NextAddr", runtime.WithHTTPPathPattern("/v2/wallet/address/next"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_NextAddr_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_NextAddr_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WalletKit_GetTransaction_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/GetTransaction", runtime.WithHTTPPathPattern("/v2/wallet/tx"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_GetTransaction_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_GetTransaction_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WalletKit_ListAccounts_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/ListAccounts", runtime.WithHTTPPathPattern("/v2/wallet/accounts"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_ListAccounts_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_ListAccounts_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WalletKit_RequiredReserve_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/RequiredReserve", runtime.WithHTTPPathPattern("/v2/wallet/reserve"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_RequiredReserve_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_RequiredReserve_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WalletKit_ListAddresses_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/ListAddresses", runtime.WithHTTPPathPattern("/v2/wallet/addresses"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_ListAddresses_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_ListAddresses_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_SignMessageWithAddr_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/SignMessageWithAddr", runtime.WithHTTPPathPattern("/v2/wallet/address/signmessage"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_SignMessageWithAddr_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_SignMessageWithAddr_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_VerifyMessageWithAddr_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/VerifyMessageWithAddr", runtime.WithHTTPPathPattern("/v2/wallet/address/verifymessage"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_VerifyMessageWithAddr_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_VerifyMessageWithAddr_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_ImportAccount_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/ImportAccount", runtime.WithHTTPPathPattern("/v2/wallet/accounts/import"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_ImportAccount_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_ImportAccount_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_ImportPublicKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/ImportPublicKey", runtime.WithHTTPPathPattern("/v2/wallet/key/import"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_ImportPublicKey_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_ImportPublicKey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_ImportTapscript_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/ImportTapscript", runtime.WithHTTPPathPattern("/v2/wallet/tapscript/import"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_ImportTapscript_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_ImportTapscript_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_PublishTransaction_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/PublishTransaction", runtime.WithHTTPPathPattern("/v2/wallet/tx"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_PublishTransaction_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_PublishTransaction_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_RemoveTransaction_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/RemoveTransaction", runtime.WithHTTPPathPattern("/v2/wallet/removetx"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_RemoveTransaction_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_RemoveTransaction_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_SendOutputs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/SendOutputs", runtime.WithHTTPPathPattern("/v2/wallet/send"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_SendOutputs_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_SendOutputs_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WalletKit_EstimateFee_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/EstimateFee", runtime.WithHTTPPathPattern("/v2/wallet/estimatefee/{conf_target}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_EstimateFee_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_EstimateFee_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WalletKit_PendingSweeps_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/PendingSweeps", runtime.WithHTTPPathPattern("/v2/wallet/sweeps/pending"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_PendingSweeps_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_PendingSweeps_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_BumpFee_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/BumpFee", runtime.WithHTTPPathPattern("/v2/wallet/bumpfee"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_BumpFee_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_BumpFee_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_BumpForceCloseFee_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/BumpForceCloseFee", runtime.WithHTTPPathPattern("/v2/wallet/BumpForceCloseFee"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_BumpForceCloseFee_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_BumpForceCloseFee_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WalletKit_ListSweeps_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/ListSweeps", runtime.WithHTTPPathPattern("/v2/wallet/sweeps"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_ListSweeps_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_ListSweeps_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_LabelTransaction_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/LabelTransaction", runtime.WithHTTPPathPattern("/v2/wallet/tx/label"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_LabelTransaction_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_LabelTransaction_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_FundPsbt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/FundPsbt", runtime.WithHTTPPathPattern("/v2/wallet/psbt/fund"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_FundPsbt_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_FundPsbt_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_SignPsbt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/SignPsbt", runtime.WithHTTPPathPattern("/v2/wallet/psbt/sign"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_SignPsbt_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_SignPsbt_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_FinalizePsbt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/FinalizePsbt", runtime.WithHTTPPathPattern("/v2/wallet/psbt/finalize"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_FinalizePsbt_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_FinalizePsbt_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_WalletKit_ListUnspent_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "utxos"}, ""))
pattern_WalletKit_LeaseOutput_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "utxos", "lease"}, ""))
pattern_WalletKit_ReleaseOutput_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "utxos", "release"}, ""))
pattern_WalletKit_ListLeases_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "utxos", "leases"}, ""))
pattern_WalletKit_DeriveNextKey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "key", "next"}, ""))
pattern_WalletKit_DeriveKey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "key"}, ""))
pattern_WalletKit_NextAddr_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "address", "next"}, ""))
pattern_WalletKit_GetTransaction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "tx"}, ""))
pattern_WalletKit_ListAccounts_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "accounts"}, ""))
pattern_WalletKit_RequiredReserve_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "reserve"}, ""))
pattern_WalletKit_ListAddresses_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "addresses"}, ""))
pattern_WalletKit_SignMessageWithAddr_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "address", "signmessage"}, ""))
pattern_WalletKit_VerifyMessageWithAddr_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "address", "verifymessage"}, ""))
pattern_WalletKit_ImportAccount_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "accounts", "import"}, ""))
pattern_WalletKit_ImportPublicKey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "key", "import"}, ""))
pattern_WalletKit_ImportTapscript_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "tapscript", "import"}, ""))
pattern_WalletKit_PublishTransaction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "tx"}, ""))
pattern_WalletKit_RemoveTransaction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "removetx"}, ""))
pattern_WalletKit_SendOutputs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "send"}, ""))
pattern_WalletKit_EstimateFee_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v2", "wallet", "estimatefee", "conf_target"}, ""))
pattern_WalletKit_PendingSweeps_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "sweeps", "pending"}, ""))
pattern_WalletKit_BumpFee_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "bumpfee"}, ""))
pattern_WalletKit_BumpForceCloseFee_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "BumpForceCloseFee"}, ""))
pattern_WalletKit_ListSweeps_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "sweeps"}, ""))
pattern_WalletKit_LabelTransaction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "tx", "label"}, ""))
pattern_WalletKit_FundPsbt_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "psbt", "fund"}, ""))
pattern_WalletKit_SignPsbt_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "psbt", "sign"}, ""))
pattern_WalletKit_FinalizePsbt_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "psbt", "finalize"}, ""))
)
var (
forward_WalletKit_ListUnspent_0 = runtime.ForwardResponseMessage
forward_WalletKit_LeaseOutput_0 = runtime.ForwardResponseMessage
forward_WalletKit_ReleaseOutput_0 = runtime.ForwardResponseMessage
forward_WalletKit_ListLeases_0 = runtime.ForwardResponseMessage
forward_WalletKit_DeriveNextKey_0 = runtime.ForwardResponseMessage
forward_WalletKit_DeriveKey_0 = runtime.ForwardResponseMessage
forward_WalletKit_NextAddr_0 = runtime.ForwardResponseMessage
forward_WalletKit_GetTransaction_0 = runtime.ForwardResponseMessage
forward_WalletKit_ListAccounts_0 = runtime.ForwardResponseMessage
forward_WalletKit_RequiredReserve_0 = runtime.ForwardResponseMessage
forward_WalletKit_ListAddresses_0 = runtime.ForwardResponseMessage
forward_WalletKit_SignMessageWithAddr_0 = runtime.ForwardResponseMessage
forward_WalletKit_VerifyMessageWithAddr_0 = runtime.ForwardResponseMessage
forward_WalletKit_ImportAccount_0 = runtime.ForwardResponseMessage
forward_WalletKit_ImportPublicKey_0 = runtime.ForwardResponseMessage
forward_WalletKit_ImportTapscript_0 = runtime.ForwardResponseMessage
forward_WalletKit_PublishTransaction_0 = runtime.ForwardResponseMessage
forward_WalletKit_RemoveTransaction_0 = runtime.ForwardResponseMessage
forward_WalletKit_SendOutputs_0 = runtime.ForwardResponseMessage
forward_WalletKit_EstimateFee_0 = runtime.ForwardResponseMessage
forward_WalletKit_PendingSweeps_0 = runtime.ForwardResponseMessage
forward_WalletKit_BumpFee_0 = runtime.ForwardResponseMessage
forward_WalletKit_BumpForceCloseFee_0 = runtime.ForwardResponseMessage
forward_WalletKit_ListSweeps_0 = runtime.ForwardResponseMessage
forward_WalletKit_LabelTransaction_0 = runtime.ForwardResponseMessage
forward_WalletKit_FundPsbt_0 = runtime.ForwardResponseMessage
forward_WalletKit_SignPsbt_0 = runtime.ForwardResponseMessage
forward_WalletKit_FinalizePsbt_0 = runtime.ForwardResponseMessage
)
// Code generated by falafel 0.9.2. DO NOT EDIT.
// source: walletkit.proto
package walletrpc
import (
"context"
gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protojson"
)
func RegisterWalletKitJSONCallbacks(registry map[string]func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error))) {
marshaler := &gateway.JSONPb{
MarshalOptions: protojson.MarshalOptions{
UseProtoNames: true,
EmitUnpopulated: true,
},
}
registry["walletrpc.WalletKit.ListUnspent"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ListUnspentRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.ListUnspent(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.LeaseOutput"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &LeaseOutputRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.LeaseOutput(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.ReleaseOutput"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ReleaseOutputRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.ReleaseOutput(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.ListLeases"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ListLeasesRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.ListLeases(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.DeriveNextKey"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &KeyReq{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.DeriveNextKey(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.DeriveKey"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &signrpc.KeyLocator{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.DeriveKey(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.NextAddr"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &AddrRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.NextAddr(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.GetTransaction"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &GetTransactionRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.GetTransaction(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.ListAccounts"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ListAccountsRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.ListAccounts(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.RequiredReserve"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &RequiredReserveRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.RequiredReserve(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.ListAddresses"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ListAddressesRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.ListAddresses(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.SignMessageWithAddr"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SignMessageWithAddrRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.SignMessageWithAddr(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.VerifyMessageWithAddr"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &VerifyMessageWithAddrRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.VerifyMessageWithAddr(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.ImportAccount"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ImportAccountRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.ImportAccount(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.ImportPublicKey"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ImportPublicKeyRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.ImportPublicKey(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.ImportTapscript"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ImportTapscriptRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.ImportTapscript(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.PublishTransaction"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &Transaction{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.PublishTransaction(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.RemoveTransaction"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &GetTransactionRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.RemoveTransaction(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.SendOutputs"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SendOutputsRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.SendOutputs(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.EstimateFee"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &EstimateFeeRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.EstimateFee(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.PendingSweeps"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &PendingSweepsRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.PendingSweeps(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.BumpFee"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &BumpFeeRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.BumpFee(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.BumpForceCloseFee"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &BumpForceCloseFeeRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.BumpForceCloseFee(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.ListSweeps"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ListSweepsRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.ListSweeps(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.LabelTransaction"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &LabelTransactionRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.LabelTransaction(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.FundPsbt"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &FundPsbtRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.FundPsbt(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.SignPsbt"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &SignPsbtRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.SignPsbt(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.FinalizePsbt"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &FinalizePsbtRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.FinalizePsbt(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package walletrpc
import (
context "context"
lnrpc "github.com/lightningnetwork/lnd/lnrpc"
signrpc "github.com/lightningnetwork/lnd/lnrpc/signrpc"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// WalletKitClient is the client API for WalletKit service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type WalletKitClient interface {
// ListUnspent returns a list of all utxos spendable by the wallet with a
// number of confirmations between the specified minimum and maximum. By
// default, all utxos are listed. To list only the unconfirmed utxos, set
// the unconfirmed_only to true.
ListUnspent(ctx context.Context, in *ListUnspentRequest, opts ...grpc.CallOption) (*ListUnspentResponse, error)
// lncli: `wallet leaseoutput`
// LeaseOutput locks an output to the given ID, preventing it from being
// available for any future coin selection attempts. The absolute time of the
// lock's expiration is returned. The expiration of the lock can be extended by
// successive invocations of this RPC. Outputs can be unlocked before their
// expiration through `ReleaseOutput`.
LeaseOutput(ctx context.Context, in *LeaseOutputRequest, opts ...grpc.CallOption) (*LeaseOutputResponse, error)
// lncli: `wallet releaseoutput`
// ReleaseOutput unlocks an output, allowing it to be available for coin
// selection if it remains unspent. The ID should match the one used to
// originally lock the output.
ReleaseOutput(ctx context.Context, in *ReleaseOutputRequest, opts ...grpc.CallOption) (*ReleaseOutputResponse, error)
// lncli: `wallet listleases`
// ListLeases lists all currently locked utxos.
ListLeases(ctx context.Context, in *ListLeasesRequest, opts ...grpc.CallOption) (*ListLeasesResponse, error)
// DeriveNextKey attempts to derive the *next* key within the key family
// (account in BIP43) specified. This method should return the next external
// child within this branch.
DeriveNextKey(ctx context.Context, in *KeyReq, opts ...grpc.CallOption) (*signrpc.KeyDescriptor, error)
// DeriveKey attempts to derive an arbitrary key specified by the passed
// KeyLocator.
DeriveKey(ctx context.Context, in *signrpc.KeyLocator, opts ...grpc.CallOption) (*signrpc.KeyDescriptor, error)
// NextAddr returns the next unused address within the wallet.
NextAddr(ctx context.Context, in *AddrRequest, opts ...grpc.CallOption) (*AddrResponse, error)
// lncli: `wallet gettx`
// GetTransaction returns details for a transaction found in the wallet.
GetTransaction(ctx context.Context, in *GetTransactionRequest, opts ...grpc.CallOption) (*lnrpc.Transaction, error)
// lncli: `wallet accounts list`
// ListAccounts retrieves all accounts belonging to the wallet by default. A
// name and key scope filter can be provided to filter through all of the
// wallet accounts and return only those matching.
ListAccounts(ctx context.Context, in *ListAccountsRequest, opts ...grpc.CallOption) (*ListAccountsResponse, error)
// lncli: `wallet requiredreserve`
// RequiredReserve returns the minimum amount of satoshis that should be kept
// in the wallet in order to fee bump anchor channels if necessary. The value
// scales with the number of public anchor channels but is capped at a maximum.
RequiredReserve(ctx context.Context, in *RequiredReserveRequest, opts ...grpc.CallOption) (*RequiredReserveResponse, error)
// lncli: `wallet addresses list`
// ListAddresses retrieves all the addresses along with their balance. An
// account name filter can be provided to filter through all of the
// wallet accounts and return the addresses of only those matching.
ListAddresses(ctx context.Context, in *ListAddressesRequest, opts ...grpc.CallOption) (*ListAddressesResponse, error)
// lncli: `wallet addresses signmessage`
// SignMessageWithAddr returns the compact signature (base64 encoded) created
// with the private key of the provided address. This requires the address
// to be solely based on a public key lock (no scripts). Obviously the internal
// lnd wallet has to possess the private key of the address otherwise
// an error is returned.
//
// This method aims to provide full compatibility with the bitcoin-core and
// btcd implementation. Bitcoin-core's algorithm is not specified in a
// BIP and only applicable for legacy addresses. This method enhances the
// signing for additional address types: P2WKH, NP2WKH, P2TR.
// For P2TR addresses this represents a special case. ECDSA is used to create
// a compact signature which makes the public key of the signature recoverable.
SignMessageWithAddr(ctx context.Context, in *SignMessageWithAddrRequest, opts ...grpc.CallOption) (*SignMessageWithAddrResponse, error)
// lncli: `wallet addresses verifymessage`
// VerifyMessageWithAddr returns the validity and the recovered public key of
// the provided compact signature (base64 encoded). The verification is
// twofold. First the validity of the signature itself is checked and then
// it is verified that the recovered public key of the signature equals
// the public key of the provided address. There is no dependence on the
// private key of the address therefore also external addresses are allowed
// to verify signatures.
// Supported address types are P2PKH, P2WKH, NP2WKH, P2TR.
//
// This method is the counterpart of the related signing method
// (SignMessageWithAddr) and aims to provide full compatibility to
// bitcoin-core's implementation. Although bitcoin-core/btcd only provide
// this functionality for legacy addresses this function enhances it to
// the address types: P2PKH, P2WKH, NP2WKH, P2TR.
//
// The verification for P2TR addresses is a special case and requires the
// ECDSA compact signature to compare the reovered public key to the internal
// taproot key. The compact ECDSA signature format was used because there
// are still no known compact signature schemes for schnorr signatures.
VerifyMessageWithAddr(ctx context.Context, in *VerifyMessageWithAddrRequest, opts ...grpc.CallOption) (*VerifyMessageWithAddrResponse, error)
// lncli: `wallet accounts import`
// ImportAccount imports an account backed by an account extended public key.
// The master key fingerprint denotes the fingerprint of the root key
// corresponding to the account public key (also known as the key with
// derivation path m/). This may be required by some hardware wallets for
// proper identification and signing.
//
// The address type can usually be inferred from the key's version, but may be
// required for certain keys to map them into the proper scope.
//
// For BIP-0044 keys, an address type must be specified as we intend to not
// support importing BIP-0044 keys into the wallet using the legacy
// pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will force
// the standard BIP-0049 derivation scheme, while a witness address type will
// force the standard BIP-0084 derivation scheme.
//
// For BIP-0049 keys, an address type must also be specified to make a
// distinction between the standard BIP-0049 address schema (nested witness
// pubkeys everywhere) and our own BIP-0049Plus address schema (nested pubkeys
// externally, witness pubkeys internally).
//
// NOTE: Events (deposits/spends) for keys derived from an account will only be
// detected by lnd if they happen after the import. Rescans to detect past
// events will be supported later on.
ImportAccount(ctx context.Context, in *ImportAccountRequest, opts ...grpc.CallOption) (*ImportAccountResponse, error)
// lncli: `wallet accounts import-pubkey`
// ImportPublicKey imports a public key as watch-only into the wallet. The
// public key is converted into a simple address of the given type and that
// address script is watched on chain. For Taproot keys, this will only watch
// the BIP-0086 style output script. Use ImportTapscript for more advanced key
// spend or script spend outputs.
//
// NOTE: Events (deposits/spends) for a key will only be detected by lnd if
// they happen after the import. Rescans to detect past events will be
// supported later on.
ImportPublicKey(ctx context.Context, in *ImportPublicKeyRequest, opts ...grpc.CallOption) (*ImportPublicKeyResponse, error)
// ImportTapscript imports a Taproot script and internal key and adds the
// resulting Taproot output key as a watch-only output script into the wallet.
// For BIP-0086 style Taproot keys (no root hash commitment and no script spend
// path) use ImportPublicKey.
//
// NOTE: Events (deposits/spends) for a key will only be detected by lnd if
// they happen after the import. Rescans to detect past events will be
// supported later on.
//
// NOTE: Taproot keys imported through this RPC currently _cannot_ be used for
// funding PSBTs. Only tracking the balance and UTXOs is currently supported.
ImportTapscript(ctx context.Context, in *ImportTapscriptRequest, opts ...grpc.CallOption) (*ImportTapscriptResponse, error)
// lncli: `wallet publishtx`
// PublishTransaction attempts to publish the passed transaction to the
// network. Once this returns without an error, the wallet will continually
// attempt to re-broadcast the transaction on start up, until it enters the
// chain.
PublishTransaction(ctx context.Context, in *Transaction, opts ...grpc.CallOption) (*PublishResponse, error)
// lncli: `wallet removetx`
// RemoveTransaction attempts to remove the provided transaction from the
// internal transaction store of the wallet.
RemoveTransaction(ctx context.Context, in *GetTransactionRequest, opts ...grpc.CallOption) (*RemoveTransactionResponse, error)
// SendOutputs is similar to the existing sendmany call in Bitcoind, and
// allows the caller to create a transaction that sends to several outputs at
// once. This is ideal when wanting to batch create a set of transactions.
SendOutputs(ctx context.Context, in *SendOutputsRequest, opts ...grpc.CallOption) (*SendOutputsResponse, error)
// lncli: `wallet estimatefeerate`
// EstimateFee attempts to query the internal fee estimator of the wallet to
// determine the fee (in sat/kw) to attach to a transaction in order to
// achieve the confirmation target.
EstimateFee(ctx context.Context, in *EstimateFeeRequest, opts ...grpc.CallOption) (*EstimateFeeResponse, error)
// lncli: `wallet pendingsweeps`
// PendingSweeps returns lists of on-chain outputs that lnd is currently
// attempting to sweep within its central batching engine. Outputs with similar
// fee rates are batched together in order to sweep them within a single
// transaction.
//
// NOTE: Some of the fields within PendingSweepsRequest are not guaranteed to
// remain supported. This is an advanced API that depends on the internals of
// the UtxoSweeper, so things may change.
PendingSweeps(ctx context.Context, in *PendingSweepsRequest, opts ...grpc.CallOption) (*PendingSweepsResponse, error)
// lncli: `wallet bumpfee`
// BumpFee is an endpoint that allows users to interact with lnd's sweeper
// directly. It takes an outpoint from an unconfirmed transaction and sends it
// to the sweeper for potential fee bumping. Depending on whether the outpoint
// has been registered in the sweeper (an existing input, e.g., an anchor
// output) or not (a new input, e.g., an unconfirmed wallet utxo), this will
// either be an RBF or CPFP attempt.
//
// When receiving an input, lnd’s sweeper needs to understand its time
// sensitivity to make economical fee bumps - internally a fee function is
// created using the deadline and budget to guide the process. When the
// deadline is approaching, the fee function will increase the fee rate and
// perform an RBF.
//
// When a force close happens, all the outputs from the force closing
// transaction will be registered in the sweeper. The sweeper will then handle
// the creation, publish, and fee bumping of the sweeping transactions.
// Everytime a new block comes in, unless the sweeping transaction is
// confirmed, an RBF is attempted. To interfere with this automatic process,
// users can use BumpFee to specify customized fee rate, budget, deadline, and
// whether the sweep should happen immediately. It's recommended to call
// `ListSweeps` to understand the shape of the existing sweeping transaction
// first - depending on the number of inputs in this transaction, the RBF
// requirements can be quite different.
//
// This RPC also serves useful when wanting to perform a Child-Pays-For-Parent
// (CPFP), where the child transaction pays for its parent's fee. This can be
// done by specifying an outpoint within the low fee transaction that is under
// the control of the wallet.
BumpFee(ctx context.Context, in *BumpFeeRequest, opts ...grpc.CallOption) (*BumpFeeResponse, error)
// lncli: `wallet bumpforceclosefee`
// BumpForceCloseFee is an endpoint that allows users to bump the fee of a
// channel force close. This only works for channels with option_anchors.
BumpForceCloseFee(ctx context.Context, in *BumpForceCloseFeeRequest, opts ...grpc.CallOption) (*BumpForceCloseFeeResponse, error)
// lncli: `wallet listsweeps`
// ListSweeps returns a list of the sweep transactions our node has produced.
// Note that these sweeps may not be confirmed yet, as we record sweeps on
// broadcast, not confirmation.
ListSweeps(ctx context.Context, in *ListSweepsRequest, opts ...grpc.CallOption) (*ListSweepsResponse, error)
// lncli: `wallet labeltx`
// LabelTransaction adds a label to a transaction. If the transaction already
// has a label the call will fail unless the overwrite bool is set. This will
// overwrite the existing transaction label. Labels must not be empty, and
// cannot exceed 500 characters.
LabelTransaction(ctx context.Context, in *LabelTransactionRequest, opts ...grpc.CallOption) (*LabelTransactionResponse, error)
// lncli: `wallet psbt fund`
// FundPsbt creates a fully populated PSBT that contains enough inputs to fund
// the outputs specified in the template. There are three ways a user can
// specify what we call the template (a list of inputs and outputs to use in
// the PSBT): Either as a PSBT packet directly with no coin selection (using
// the legacy "psbt" field), a PSBT with advanced coin selection support (using
// the new "coin_select" field) or as a raw RPC message (using the "raw"
// field).
// The legacy "psbt" and "raw" modes, the following restrictions apply:
// 1. If there are no inputs specified in the template, coin selection is
// performed automatically.
// 2. If the template does contain any inputs, it is assumed that full
// coin selection happened externally and no additional inputs are added. If
// the specified inputs aren't enough to fund the outputs with the given fee
// rate, an error is returned.
//
// The new "coin_select" mode does not have these restrictions and allows the
// user to specify a PSBT with inputs and outputs and still perform coin
// selection on top of that.
// For all modes this RPC requires any inputs that are specified to be locked
// by the user (if they belong to this node in the first place).
//
// After either selecting or verifying the inputs, all input UTXOs are locked
// with an internal app ID.
//
// NOTE: If this method returns without an error, it is the caller's
// responsibility to either spend the locked UTXOs (by finalizing and then
// publishing the transaction) or to unlock/release the locked UTXOs in case of
// an error on the caller's side.
FundPsbt(ctx context.Context, in *FundPsbtRequest, opts ...grpc.CallOption) (*FundPsbtResponse, error)
// SignPsbt expects a partial transaction with all inputs and outputs fully
// declared and tries to sign all unsigned inputs that have all required fields
// (UTXO information, BIP32 derivation information, witness or sig scripts)
// set.
// If no error is returned, the PSBT is ready to be given to the next signer or
// to be finalized if lnd was the last signer.
//
// NOTE: This RPC only signs inputs (and only those it can sign), it does not
// perform any other tasks (such as coin selection, UTXO locking or
// input/output/fee value validation, PSBT finalization). Any input that is
// incomplete will be skipped.
SignPsbt(ctx context.Context, in *SignPsbtRequest, opts ...grpc.CallOption) (*SignPsbtResponse, error)
// lncli: `wallet psbt finalize`
// FinalizePsbt expects a partial transaction with all inputs and outputs fully
// declared and tries to sign all inputs that belong to the wallet. Lnd must be
// the last signer of the transaction. That means, if there are any unsigned
// non-witness inputs or inputs without UTXO information attached or inputs
// without witness data that do not belong to lnd's wallet, this method will
// fail. If no error is returned, the PSBT is ready to be extracted and the
// final TX within to be broadcast.
//
// NOTE: This method does NOT publish the transaction once finalized. It is the
// caller's responsibility to either publish the transaction on success or
// unlock/release any locked UTXOs in case of an error in this method.
FinalizePsbt(ctx context.Context, in *FinalizePsbtRequest, opts ...grpc.CallOption) (*FinalizePsbtResponse, error)
}
type walletKitClient struct {
cc grpc.ClientConnInterface
}
func NewWalletKitClient(cc grpc.ClientConnInterface) WalletKitClient {
return &walletKitClient{cc}
}
func (c *walletKitClient) ListUnspent(ctx context.Context, in *ListUnspentRequest, opts ...grpc.CallOption) (*ListUnspentResponse, error) {
out := new(ListUnspentResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/ListUnspent", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) LeaseOutput(ctx context.Context, in *LeaseOutputRequest, opts ...grpc.CallOption) (*LeaseOutputResponse, error) {
out := new(LeaseOutputResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/LeaseOutput", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) ReleaseOutput(ctx context.Context, in *ReleaseOutputRequest, opts ...grpc.CallOption) (*ReleaseOutputResponse, error) {
out := new(ReleaseOutputResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/ReleaseOutput", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) ListLeases(ctx context.Context, in *ListLeasesRequest, opts ...grpc.CallOption) (*ListLeasesResponse, error) {
out := new(ListLeasesResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/ListLeases", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) DeriveNextKey(ctx context.Context, in *KeyReq, opts ...grpc.CallOption) (*signrpc.KeyDescriptor, error) {
out := new(signrpc.KeyDescriptor)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/DeriveNextKey", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) DeriveKey(ctx context.Context, in *signrpc.KeyLocator, opts ...grpc.CallOption) (*signrpc.KeyDescriptor, error) {
out := new(signrpc.KeyDescriptor)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/DeriveKey", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) NextAddr(ctx context.Context, in *AddrRequest, opts ...grpc.CallOption) (*AddrResponse, error) {
out := new(AddrResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/NextAddr", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) GetTransaction(ctx context.Context, in *GetTransactionRequest, opts ...grpc.CallOption) (*lnrpc.Transaction, error) {
out := new(lnrpc.Transaction)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/GetTransaction", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) ListAccounts(ctx context.Context, in *ListAccountsRequest, opts ...grpc.CallOption) (*ListAccountsResponse, error) {
out := new(ListAccountsResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/ListAccounts", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) RequiredReserve(ctx context.Context, in *RequiredReserveRequest, opts ...grpc.CallOption) (*RequiredReserveResponse, error) {
out := new(RequiredReserveResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/RequiredReserve", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) ListAddresses(ctx context.Context, in *ListAddressesRequest, opts ...grpc.CallOption) (*ListAddressesResponse, error) {
out := new(ListAddressesResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/ListAddresses", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) SignMessageWithAddr(ctx context.Context, in *SignMessageWithAddrRequest, opts ...grpc.CallOption) (*SignMessageWithAddrResponse, error) {
out := new(SignMessageWithAddrResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/SignMessageWithAddr", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) VerifyMessageWithAddr(ctx context.Context, in *VerifyMessageWithAddrRequest, opts ...grpc.CallOption) (*VerifyMessageWithAddrResponse, error) {
out := new(VerifyMessageWithAddrResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/VerifyMessageWithAddr", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) ImportAccount(ctx context.Context, in *ImportAccountRequest, opts ...grpc.CallOption) (*ImportAccountResponse, error) {
out := new(ImportAccountResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/ImportAccount", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) ImportPublicKey(ctx context.Context, in *ImportPublicKeyRequest, opts ...grpc.CallOption) (*ImportPublicKeyResponse, error) {
out := new(ImportPublicKeyResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/ImportPublicKey", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) ImportTapscript(ctx context.Context, in *ImportTapscriptRequest, opts ...grpc.CallOption) (*ImportTapscriptResponse, error) {
out := new(ImportTapscriptResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/ImportTapscript", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) PublishTransaction(ctx context.Context, in *Transaction, opts ...grpc.CallOption) (*PublishResponse, error) {
out := new(PublishResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/PublishTransaction", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) RemoveTransaction(ctx context.Context, in *GetTransactionRequest, opts ...grpc.CallOption) (*RemoveTransactionResponse, error) {
out := new(RemoveTransactionResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/RemoveTransaction", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) SendOutputs(ctx context.Context, in *SendOutputsRequest, opts ...grpc.CallOption) (*SendOutputsResponse, error) {
out := new(SendOutputsResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/SendOutputs", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) EstimateFee(ctx context.Context, in *EstimateFeeRequest, opts ...grpc.CallOption) (*EstimateFeeResponse, error) {
out := new(EstimateFeeResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/EstimateFee", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) PendingSweeps(ctx context.Context, in *PendingSweepsRequest, opts ...grpc.CallOption) (*PendingSweepsResponse, error) {
out := new(PendingSweepsResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/PendingSweeps", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) BumpFee(ctx context.Context, in *BumpFeeRequest, opts ...grpc.CallOption) (*BumpFeeResponse, error) {
out := new(BumpFeeResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/BumpFee", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) BumpForceCloseFee(ctx context.Context, in *BumpForceCloseFeeRequest, opts ...grpc.CallOption) (*BumpForceCloseFeeResponse, error) {
out := new(BumpForceCloseFeeResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/BumpForceCloseFee", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) ListSweeps(ctx context.Context, in *ListSweepsRequest, opts ...grpc.CallOption) (*ListSweepsResponse, error) {
out := new(ListSweepsResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/ListSweeps", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) LabelTransaction(ctx context.Context, in *LabelTransactionRequest, opts ...grpc.CallOption) (*LabelTransactionResponse, error) {
out := new(LabelTransactionResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/LabelTransaction", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) FundPsbt(ctx context.Context, in *FundPsbtRequest, opts ...grpc.CallOption) (*FundPsbtResponse, error) {
out := new(FundPsbtResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/FundPsbt", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) SignPsbt(ctx context.Context, in *SignPsbtRequest, opts ...grpc.CallOption) (*SignPsbtResponse, error) {
out := new(SignPsbtResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/SignPsbt", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) FinalizePsbt(ctx context.Context, in *FinalizePsbtRequest, opts ...grpc.CallOption) (*FinalizePsbtResponse, error) {
out := new(FinalizePsbtResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/FinalizePsbt", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// WalletKitServer is the server API for WalletKit service.
// All implementations must embed UnimplementedWalletKitServer
// for forward compatibility
type WalletKitServer interface {
// ListUnspent returns a list of all utxos spendable by the wallet with a
// number of confirmations between the specified minimum and maximum. By
// default, all utxos are listed. To list only the unconfirmed utxos, set
// the unconfirmed_only to true.
ListUnspent(context.Context, *ListUnspentRequest) (*ListUnspentResponse, error)
// lncli: `wallet leaseoutput`
// LeaseOutput locks an output to the given ID, preventing it from being
// available for any future coin selection attempts. The absolute time of the
// lock's expiration is returned. The expiration of the lock can be extended by
// successive invocations of this RPC. Outputs can be unlocked before their
// expiration through `ReleaseOutput`.
LeaseOutput(context.Context, *LeaseOutputRequest) (*LeaseOutputResponse, error)
// lncli: `wallet releaseoutput`
// ReleaseOutput unlocks an output, allowing it to be available for coin
// selection if it remains unspent. The ID should match the one used to
// originally lock the output.
ReleaseOutput(context.Context, *ReleaseOutputRequest) (*ReleaseOutputResponse, error)
// lncli: `wallet listleases`
// ListLeases lists all currently locked utxos.
ListLeases(context.Context, *ListLeasesRequest) (*ListLeasesResponse, error)
// DeriveNextKey attempts to derive the *next* key within the key family
// (account in BIP43) specified. This method should return the next external
// child within this branch.
DeriveNextKey(context.Context, *KeyReq) (*signrpc.KeyDescriptor, error)
// DeriveKey attempts to derive an arbitrary key specified by the passed
// KeyLocator.
DeriveKey(context.Context, *signrpc.KeyLocator) (*signrpc.KeyDescriptor, error)
// NextAddr returns the next unused address within the wallet.
NextAddr(context.Context, *AddrRequest) (*AddrResponse, error)
// lncli: `wallet gettx`
// GetTransaction returns details for a transaction found in the wallet.
GetTransaction(context.Context, *GetTransactionRequest) (*lnrpc.Transaction, error)
// lncli: `wallet accounts list`
// ListAccounts retrieves all accounts belonging to the wallet by default. A
// name and key scope filter can be provided to filter through all of the
// wallet accounts and return only those matching.
ListAccounts(context.Context, *ListAccountsRequest) (*ListAccountsResponse, error)
// lncli: `wallet requiredreserve`
// RequiredReserve returns the minimum amount of satoshis that should be kept
// in the wallet in order to fee bump anchor channels if necessary. The value
// scales with the number of public anchor channels but is capped at a maximum.
RequiredReserve(context.Context, *RequiredReserveRequest) (*RequiredReserveResponse, error)
// lncli: `wallet addresses list`
// ListAddresses retrieves all the addresses along with their balance. An
// account name filter can be provided to filter through all of the
// wallet accounts and return the addresses of only those matching.
ListAddresses(context.Context, *ListAddressesRequest) (*ListAddressesResponse, error)
// lncli: `wallet addresses signmessage`
// SignMessageWithAddr returns the compact signature (base64 encoded) created
// with the private key of the provided address. This requires the address
// to be solely based on a public key lock (no scripts). Obviously the internal
// lnd wallet has to possess the private key of the address otherwise
// an error is returned.
//
// This method aims to provide full compatibility with the bitcoin-core and
// btcd implementation. Bitcoin-core's algorithm is not specified in a
// BIP and only applicable for legacy addresses. This method enhances the
// signing for additional address types: P2WKH, NP2WKH, P2TR.
// For P2TR addresses this represents a special case. ECDSA is used to create
// a compact signature which makes the public key of the signature recoverable.
SignMessageWithAddr(context.Context, *SignMessageWithAddrRequest) (*SignMessageWithAddrResponse, error)
// lncli: `wallet addresses verifymessage`
// VerifyMessageWithAddr returns the validity and the recovered public key of
// the provided compact signature (base64 encoded). The verification is
// twofold. First the validity of the signature itself is checked and then
// it is verified that the recovered public key of the signature equals
// the public key of the provided address. There is no dependence on the
// private key of the address therefore also external addresses are allowed
// to verify signatures.
// Supported address types are P2PKH, P2WKH, NP2WKH, P2TR.
//
// This method is the counterpart of the related signing method
// (SignMessageWithAddr) and aims to provide full compatibility to
// bitcoin-core's implementation. Although bitcoin-core/btcd only provide
// this functionality for legacy addresses this function enhances it to
// the address types: P2PKH, P2WKH, NP2WKH, P2TR.
//
// The verification for P2TR addresses is a special case and requires the
// ECDSA compact signature to compare the reovered public key to the internal
// taproot key. The compact ECDSA signature format was used because there
// are still no known compact signature schemes for schnorr signatures.
VerifyMessageWithAddr(context.Context, *VerifyMessageWithAddrRequest) (*VerifyMessageWithAddrResponse, error)
// lncli: `wallet accounts import`
// ImportAccount imports an account backed by an account extended public key.
// The master key fingerprint denotes the fingerprint of the root key
// corresponding to the account public key (also known as the key with
// derivation path m/). This may be required by some hardware wallets for
// proper identification and signing.
//
// The address type can usually be inferred from the key's version, but may be
// required for certain keys to map them into the proper scope.
//
// For BIP-0044 keys, an address type must be specified as we intend to not
// support importing BIP-0044 keys into the wallet using the legacy
// pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will force
// the standard BIP-0049 derivation scheme, while a witness address type will
// force the standard BIP-0084 derivation scheme.
//
// For BIP-0049 keys, an address type must also be specified to make a
// distinction between the standard BIP-0049 address schema (nested witness
// pubkeys everywhere) and our own BIP-0049Plus address schema (nested pubkeys
// externally, witness pubkeys internally).
//
// NOTE: Events (deposits/spends) for keys derived from an account will only be
// detected by lnd if they happen after the import. Rescans to detect past
// events will be supported later on.
ImportAccount(context.Context, *ImportAccountRequest) (*ImportAccountResponse, error)
// lncli: `wallet accounts import-pubkey`
// ImportPublicKey imports a public key as watch-only into the wallet. The
// public key is converted into a simple address of the given type and that
// address script is watched on chain. For Taproot keys, this will only watch
// the BIP-0086 style output script. Use ImportTapscript for more advanced key
// spend or script spend outputs.
//
// NOTE: Events (deposits/spends) for a key will only be detected by lnd if
// they happen after the import. Rescans to detect past events will be
// supported later on.
ImportPublicKey(context.Context, *ImportPublicKeyRequest) (*ImportPublicKeyResponse, error)
// ImportTapscript imports a Taproot script and internal key and adds the
// resulting Taproot output key as a watch-only output script into the wallet.
// For BIP-0086 style Taproot keys (no root hash commitment and no script spend
// path) use ImportPublicKey.
//
// NOTE: Events (deposits/spends) for a key will only be detected by lnd if
// they happen after the import. Rescans to detect past events will be
// supported later on.
//
// NOTE: Taproot keys imported through this RPC currently _cannot_ be used for
// funding PSBTs. Only tracking the balance and UTXOs is currently supported.
ImportTapscript(context.Context, *ImportTapscriptRequest) (*ImportTapscriptResponse, error)
// lncli: `wallet publishtx`
// PublishTransaction attempts to publish the passed transaction to the
// network. Once this returns without an error, the wallet will continually
// attempt to re-broadcast the transaction on start up, until it enters the
// chain.
PublishTransaction(context.Context, *Transaction) (*PublishResponse, error)
// lncli: `wallet removetx`
// RemoveTransaction attempts to remove the provided transaction from the
// internal transaction store of the wallet.
RemoveTransaction(context.Context, *GetTransactionRequest) (*RemoveTransactionResponse, error)
// SendOutputs is similar to the existing sendmany call in Bitcoind, and
// allows the caller to create a transaction that sends to several outputs at
// once. This is ideal when wanting to batch create a set of transactions.
SendOutputs(context.Context, *SendOutputsRequest) (*SendOutputsResponse, error)
// lncli: `wallet estimatefeerate`
// EstimateFee attempts to query the internal fee estimator of the wallet to
// determine the fee (in sat/kw) to attach to a transaction in order to
// achieve the confirmation target.
EstimateFee(context.Context, *EstimateFeeRequest) (*EstimateFeeResponse, error)
// lncli: `wallet pendingsweeps`
// PendingSweeps returns lists of on-chain outputs that lnd is currently
// attempting to sweep within its central batching engine. Outputs with similar
// fee rates are batched together in order to sweep them within a single
// transaction.
//
// NOTE: Some of the fields within PendingSweepsRequest are not guaranteed to
// remain supported. This is an advanced API that depends on the internals of
// the UtxoSweeper, so things may change.
PendingSweeps(context.Context, *PendingSweepsRequest) (*PendingSweepsResponse, error)
// lncli: `wallet bumpfee`
// BumpFee is an endpoint that allows users to interact with lnd's sweeper
// directly. It takes an outpoint from an unconfirmed transaction and sends it
// to the sweeper for potential fee bumping. Depending on whether the outpoint
// has been registered in the sweeper (an existing input, e.g., an anchor
// output) or not (a new input, e.g., an unconfirmed wallet utxo), this will
// either be an RBF or CPFP attempt.
//
// When receiving an input, lnd’s sweeper needs to understand its time
// sensitivity to make economical fee bumps - internally a fee function is
// created using the deadline and budget to guide the process. When the
// deadline is approaching, the fee function will increase the fee rate and
// perform an RBF.
//
// When a force close happens, all the outputs from the force closing
// transaction will be registered in the sweeper. The sweeper will then handle
// the creation, publish, and fee bumping of the sweeping transactions.
// Everytime a new block comes in, unless the sweeping transaction is
// confirmed, an RBF is attempted. To interfere with this automatic process,
// users can use BumpFee to specify customized fee rate, budget, deadline, and
// whether the sweep should happen immediately. It's recommended to call
// `ListSweeps` to understand the shape of the existing sweeping transaction
// first - depending on the number of inputs in this transaction, the RBF
// requirements can be quite different.
//
// This RPC also serves useful when wanting to perform a Child-Pays-For-Parent
// (CPFP), where the child transaction pays for its parent's fee. This can be
// done by specifying an outpoint within the low fee transaction that is under
// the control of the wallet.
BumpFee(context.Context, *BumpFeeRequest) (*BumpFeeResponse, error)
// lncli: `wallet bumpforceclosefee`
// BumpForceCloseFee is an endpoint that allows users to bump the fee of a
// channel force close. This only works for channels with option_anchors.
BumpForceCloseFee(context.Context, *BumpForceCloseFeeRequest) (*BumpForceCloseFeeResponse, error)
// lncli: `wallet listsweeps`
// ListSweeps returns a list of the sweep transactions our node has produced.
// Note that these sweeps may not be confirmed yet, as we record sweeps on
// broadcast, not confirmation.
ListSweeps(context.Context, *ListSweepsRequest) (*ListSweepsResponse, error)
// lncli: `wallet labeltx`
// LabelTransaction adds a label to a transaction. If the transaction already
// has a label the call will fail unless the overwrite bool is set. This will
// overwrite the existing transaction label. Labels must not be empty, and
// cannot exceed 500 characters.
LabelTransaction(context.Context, *LabelTransactionRequest) (*LabelTransactionResponse, error)
// lncli: `wallet psbt fund`
// FundPsbt creates a fully populated PSBT that contains enough inputs to fund
// the outputs specified in the template. There are three ways a user can
// specify what we call the template (a list of inputs and outputs to use in
// the PSBT): Either as a PSBT packet directly with no coin selection (using
// the legacy "psbt" field), a PSBT with advanced coin selection support (using
// the new "coin_select" field) or as a raw RPC message (using the "raw"
// field).
// The legacy "psbt" and "raw" modes, the following restrictions apply:
// 1. If there are no inputs specified in the template, coin selection is
// performed automatically.
// 2. If the template does contain any inputs, it is assumed that full
// coin selection happened externally and no additional inputs are added. If
// the specified inputs aren't enough to fund the outputs with the given fee
// rate, an error is returned.
//
// The new "coin_select" mode does not have these restrictions and allows the
// user to specify a PSBT with inputs and outputs and still perform coin
// selection on top of that.
// For all modes this RPC requires any inputs that are specified to be locked
// by the user (if they belong to this node in the first place).
//
// After either selecting or verifying the inputs, all input UTXOs are locked
// with an internal app ID.
//
// NOTE: If this method returns without an error, it is the caller's
// responsibility to either spend the locked UTXOs (by finalizing and then
// publishing the transaction) or to unlock/release the locked UTXOs in case of
// an error on the caller's side.
FundPsbt(context.Context, *FundPsbtRequest) (*FundPsbtResponse, error)
// SignPsbt expects a partial transaction with all inputs and outputs fully
// declared and tries to sign all unsigned inputs that have all required fields
// (UTXO information, BIP32 derivation information, witness or sig scripts)
// set.
// If no error is returned, the PSBT is ready to be given to the next signer or
// to be finalized if lnd was the last signer.
//
// NOTE: This RPC only signs inputs (and only those it can sign), it does not
// perform any other tasks (such as coin selection, UTXO locking or
// input/output/fee value validation, PSBT finalization). Any input that is
// incomplete will be skipped.
SignPsbt(context.Context, *SignPsbtRequest) (*SignPsbtResponse, error)
// lncli: `wallet psbt finalize`
// FinalizePsbt expects a partial transaction with all inputs and outputs fully
// declared and tries to sign all inputs that belong to the wallet. Lnd must be
// the last signer of the transaction. That means, if there are any unsigned
// non-witness inputs or inputs without UTXO information attached or inputs
// without witness data that do not belong to lnd's wallet, this method will
// fail. If no error is returned, the PSBT is ready to be extracted and the
// final TX within to be broadcast.
//
// NOTE: This method does NOT publish the transaction once finalized. It is the
// caller's responsibility to either publish the transaction on success or
// unlock/release any locked UTXOs in case of an error in this method.
FinalizePsbt(context.Context, *FinalizePsbtRequest) (*FinalizePsbtResponse, error)
mustEmbedUnimplementedWalletKitServer()
}
// UnimplementedWalletKitServer must be embedded to have forward compatible implementations.
type UnimplementedWalletKitServer struct {
}
func (UnimplementedWalletKitServer) ListUnspent(context.Context, *ListUnspentRequest) (*ListUnspentResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListUnspent not implemented")
}
func (UnimplementedWalletKitServer) LeaseOutput(context.Context, *LeaseOutputRequest) (*LeaseOutputResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method LeaseOutput not implemented")
}
func (UnimplementedWalletKitServer) ReleaseOutput(context.Context, *ReleaseOutputRequest) (*ReleaseOutputResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ReleaseOutput not implemented")
}
func (UnimplementedWalletKitServer) ListLeases(context.Context, *ListLeasesRequest) (*ListLeasesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListLeases not implemented")
}
func (UnimplementedWalletKitServer) DeriveNextKey(context.Context, *KeyReq) (*signrpc.KeyDescriptor, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeriveNextKey not implemented")
}
func (UnimplementedWalletKitServer) DeriveKey(context.Context, *signrpc.KeyLocator) (*signrpc.KeyDescriptor, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeriveKey not implemented")
}
func (UnimplementedWalletKitServer) NextAddr(context.Context, *AddrRequest) (*AddrResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method NextAddr not implemented")
}
func (UnimplementedWalletKitServer) GetTransaction(context.Context, *GetTransactionRequest) (*lnrpc.Transaction, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetTransaction not implemented")
}
func (UnimplementedWalletKitServer) ListAccounts(context.Context, *ListAccountsRequest) (*ListAccountsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListAccounts not implemented")
}
func (UnimplementedWalletKitServer) RequiredReserve(context.Context, *RequiredReserveRequest) (*RequiredReserveResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RequiredReserve not implemented")
}
func (UnimplementedWalletKitServer) ListAddresses(context.Context, *ListAddressesRequest) (*ListAddressesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListAddresses not implemented")
}
func (UnimplementedWalletKitServer) SignMessageWithAddr(context.Context, *SignMessageWithAddrRequest) (*SignMessageWithAddrResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SignMessageWithAddr not implemented")
}
func (UnimplementedWalletKitServer) VerifyMessageWithAddr(context.Context, *VerifyMessageWithAddrRequest) (*VerifyMessageWithAddrResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method VerifyMessageWithAddr not implemented")
}
func (UnimplementedWalletKitServer) ImportAccount(context.Context, *ImportAccountRequest) (*ImportAccountResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ImportAccount not implemented")
}
func (UnimplementedWalletKitServer) ImportPublicKey(context.Context, *ImportPublicKeyRequest) (*ImportPublicKeyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ImportPublicKey not implemented")
}
func (UnimplementedWalletKitServer) ImportTapscript(context.Context, *ImportTapscriptRequest) (*ImportTapscriptResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ImportTapscript not implemented")
}
func (UnimplementedWalletKitServer) PublishTransaction(context.Context, *Transaction) (*PublishResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method PublishTransaction not implemented")
}
func (UnimplementedWalletKitServer) RemoveTransaction(context.Context, *GetTransactionRequest) (*RemoveTransactionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RemoveTransaction not implemented")
}
func (UnimplementedWalletKitServer) SendOutputs(context.Context, *SendOutputsRequest) (*SendOutputsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SendOutputs not implemented")
}
func (UnimplementedWalletKitServer) EstimateFee(context.Context, *EstimateFeeRequest) (*EstimateFeeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method EstimateFee not implemented")
}
func (UnimplementedWalletKitServer) PendingSweeps(context.Context, *PendingSweepsRequest) (*PendingSweepsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method PendingSweeps not implemented")
}
func (UnimplementedWalletKitServer) BumpFee(context.Context, *BumpFeeRequest) (*BumpFeeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BumpFee not implemented")
}
func (UnimplementedWalletKitServer) BumpForceCloseFee(context.Context, *BumpForceCloseFeeRequest) (*BumpForceCloseFeeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BumpForceCloseFee not implemented")
}
func (UnimplementedWalletKitServer) ListSweeps(context.Context, *ListSweepsRequest) (*ListSweepsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListSweeps not implemented")
}
func (UnimplementedWalletKitServer) LabelTransaction(context.Context, *LabelTransactionRequest) (*LabelTransactionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method LabelTransaction not implemented")
}
func (UnimplementedWalletKitServer) FundPsbt(context.Context, *FundPsbtRequest) (*FundPsbtResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method FundPsbt not implemented")
}
func (UnimplementedWalletKitServer) SignPsbt(context.Context, *SignPsbtRequest) (*SignPsbtResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SignPsbt not implemented")
}
func (UnimplementedWalletKitServer) FinalizePsbt(context.Context, *FinalizePsbtRequest) (*FinalizePsbtResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method FinalizePsbt not implemented")
}
func (UnimplementedWalletKitServer) mustEmbedUnimplementedWalletKitServer() {}
// UnsafeWalletKitServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to WalletKitServer will
// result in compilation errors.
type UnsafeWalletKitServer interface {
mustEmbedUnimplementedWalletKitServer()
}
func RegisterWalletKitServer(s grpc.ServiceRegistrar, srv WalletKitServer) {
s.RegisterService(&WalletKit_ServiceDesc, srv)
}
func _WalletKit_ListUnspent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListUnspentRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).ListUnspent(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/ListUnspent",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).ListUnspent(ctx, req.(*ListUnspentRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_LeaseOutput_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LeaseOutputRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).LeaseOutput(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/LeaseOutput",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).LeaseOutput(ctx, req.(*LeaseOutputRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_ReleaseOutput_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ReleaseOutputRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).ReleaseOutput(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/ReleaseOutput",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).ReleaseOutput(ctx, req.(*ReleaseOutputRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_ListLeases_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListLeasesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).ListLeases(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/ListLeases",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).ListLeases(ctx, req.(*ListLeasesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_DeriveNextKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(KeyReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).DeriveNextKey(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/DeriveNextKey",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).DeriveNextKey(ctx, req.(*KeyReq))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_DeriveKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(signrpc.KeyLocator)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).DeriveKey(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/DeriveKey",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).DeriveKey(ctx, req.(*signrpc.KeyLocator))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_NextAddr_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AddrRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).NextAddr(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/NextAddr",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).NextAddr(ctx, req.(*AddrRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_GetTransaction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetTransactionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).GetTransaction(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/GetTransaction",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).GetTransaction(ctx, req.(*GetTransactionRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_ListAccounts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListAccountsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).ListAccounts(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/ListAccounts",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).ListAccounts(ctx, req.(*ListAccountsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_RequiredReserve_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RequiredReserveRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).RequiredReserve(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/RequiredReserve",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).RequiredReserve(ctx, req.(*RequiredReserveRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_ListAddresses_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListAddressesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).ListAddresses(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/ListAddresses",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).ListAddresses(ctx, req.(*ListAddressesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_SignMessageWithAddr_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SignMessageWithAddrRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).SignMessageWithAddr(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/SignMessageWithAddr",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).SignMessageWithAddr(ctx, req.(*SignMessageWithAddrRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_VerifyMessageWithAddr_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(VerifyMessageWithAddrRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).VerifyMessageWithAddr(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/VerifyMessageWithAddr",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).VerifyMessageWithAddr(ctx, req.(*VerifyMessageWithAddrRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_ImportAccount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ImportAccountRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).ImportAccount(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/ImportAccount",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).ImportAccount(ctx, req.(*ImportAccountRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_ImportPublicKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ImportPublicKeyRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).ImportPublicKey(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/ImportPublicKey",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).ImportPublicKey(ctx, req.(*ImportPublicKeyRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_ImportTapscript_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ImportTapscriptRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).ImportTapscript(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/ImportTapscript",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).ImportTapscript(ctx, req.(*ImportTapscriptRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_PublishTransaction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Transaction)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).PublishTransaction(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/PublishTransaction",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).PublishTransaction(ctx, req.(*Transaction))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_RemoveTransaction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetTransactionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).RemoveTransaction(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/RemoveTransaction",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).RemoveTransaction(ctx, req.(*GetTransactionRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_SendOutputs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SendOutputsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).SendOutputs(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/SendOutputs",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).SendOutputs(ctx, req.(*SendOutputsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_EstimateFee_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(EstimateFeeRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).EstimateFee(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/EstimateFee",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).EstimateFee(ctx, req.(*EstimateFeeRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_PendingSweeps_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PendingSweepsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).PendingSweeps(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/PendingSweeps",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).PendingSweeps(ctx, req.(*PendingSweepsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_BumpFee_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(BumpFeeRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).BumpFee(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/BumpFee",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).BumpFee(ctx, req.(*BumpFeeRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_BumpForceCloseFee_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(BumpForceCloseFeeRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).BumpForceCloseFee(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/BumpForceCloseFee",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).BumpForceCloseFee(ctx, req.(*BumpForceCloseFeeRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_ListSweeps_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListSweepsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).ListSweeps(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/ListSweeps",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).ListSweeps(ctx, req.(*ListSweepsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_LabelTransaction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LabelTransactionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).LabelTransaction(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/LabelTransaction",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).LabelTransaction(ctx, req.(*LabelTransactionRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_FundPsbt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(FundPsbtRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).FundPsbt(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/FundPsbt",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).FundPsbt(ctx, req.(*FundPsbtRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_SignPsbt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SignPsbtRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).SignPsbt(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/SignPsbt",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).SignPsbt(ctx, req.(*SignPsbtRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_FinalizePsbt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(FinalizePsbtRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).FinalizePsbt(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/FinalizePsbt",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).FinalizePsbt(ctx, req.(*FinalizePsbtRequest))
}
return interceptor(ctx, in, info, handler)
}
// WalletKit_ServiceDesc is the grpc.ServiceDesc for WalletKit service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var WalletKit_ServiceDesc = grpc.ServiceDesc{
ServiceName: "walletrpc.WalletKit",
HandlerType: (*WalletKitServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "ListUnspent",
Handler: _WalletKit_ListUnspent_Handler,
},
{
MethodName: "LeaseOutput",
Handler: _WalletKit_LeaseOutput_Handler,
},
{
MethodName: "ReleaseOutput",
Handler: _WalletKit_ReleaseOutput_Handler,
},
{
MethodName: "ListLeases",
Handler: _WalletKit_ListLeases_Handler,
},
{
MethodName: "DeriveNextKey",
Handler: _WalletKit_DeriveNextKey_Handler,
},
{
MethodName: "DeriveKey",
Handler: _WalletKit_DeriveKey_Handler,
},
{
MethodName: "NextAddr",
Handler: _WalletKit_NextAddr_Handler,
},
{
MethodName: "GetTransaction",
Handler: _WalletKit_GetTransaction_Handler,
},
{
MethodName: "ListAccounts",
Handler: _WalletKit_ListAccounts_Handler,
},
{
MethodName: "RequiredReserve",
Handler: _WalletKit_RequiredReserve_Handler,
},
{
MethodName: "ListAddresses",
Handler: _WalletKit_ListAddresses_Handler,
},
{
MethodName: "SignMessageWithAddr",
Handler: _WalletKit_SignMessageWithAddr_Handler,
},
{
MethodName: "VerifyMessageWithAddr",
Handler: _WalletKit_VerifyMessageWithAddr_Handler,
},
{
MethodName: "ImportAccount",
Handler: _WalletKit_ImportAccount_Handler,
},
{
MethodName: "ImportPublicKey",
Handler: _WalletKit_ImportPublicKey_Handler,
},
{
MethodName: "ImportTapscript",
Handler: _WalletKit_ImportTapscript_Handler,
},
{
MethodName: "PublishTransaction",
Handler: _WalletKit_PublishTransaction_Handler,
},
{
MethodName: "RemoveTransaction",
Handler: _WalletKit_RemoveTransaction_Handler,
},
{
MethodName: "SendOutputs",
Handler: _WalletKit_SendOutputs_Handler,
},
{
MethodName: "EstimateFee",
Handler: _WalletKit_EstimateFee_Handler,
},
{
MethodName: "PendingSweeps",
Handler: _WalletKit_PendingSweeps_Handler,
},
{
MethodName: "BumpFee",
Handler: _WalletKit_BumpFee_Handler,
},
{
MethodName: "BumpForceCloseFee",
Handler: _WalletKit_BumpForceCloseFee_Handler,
},
{
MethodName: "ListSweeps",
Handler: _WalletKit_ListSweeps_Handler,
},
{
MethodName: "LabelTransaction",
Handler: _WalletKit_LabelTransaction_Handler,
},
{
MethodName: "FundPsbt",
Handler: _WalletKit_FundPsbt_Handler,
},
{
MethodName: "SignPsbt",
Handler: _WalletKit_SignPsbt_Handler,
},
{
MethodName: "FinalizePsbt",
Handler: _WalletKit_FinalizePsbt_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "walletrpc/walletkit.proto",
}
package walletrpc
import (
"bytes"
"fmt"
"strconv"
"strings"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/lnrpc"
)
// AccountsToWatchOnly converts the accounts returned by the walletkit's
// ListAccounts RPC into a struct that can be used to create a watch-only
// wallet.
func AccountsToWatchOnly(exported []*Account) ([]*lnrpc.WatchOnlyAccount,
error) {
result := make([]*lnrpc.WatchOnlyAccount, len(exported))
for idx, acct := range exported {
parsedPath, err := parseDerivationPath(acct.DerivationPath)
if err != nil {
return nil, fmt.Errorf("error parsing derivation path "+
"of account %d: %v", idx, err)
}
if len(parsedPath) < 3 {
return nil, fmt.Errorf("derivation path of account %d "+
"has invalid derivation path, need at least "+
"path of depth 3, instead has depth %d", idx,
len(parsedPath))
}
result[idx] = &lnrpc.WatchOnlyAccount{
Purpose: parsedPath[0],
CoinType: parsedPath[1],
Account: parsedPath[2],
Xpub: acct.ExtendedPublicKey,
}
}
return result, nil
}
// parseDerivationPath parses a path in the form of m/x'/y'/z'/a/b into a slice
// of [x, y, z, a, b], meaning that the apostrophe is ignored and 2^31 is _not_
// added to the numbers.
func parseDerivationPath(path string) ([]uint32, error) {
path = strings.TrimSpace(path)
if len(path) == 0 {
return nil, fmt.Errorf("path cannot be empty")
}
if !strings.HasPrefix(path, "m/") {
return nil, fmt.Errorf("path must start with m/")
}
// Just the root key, no path was provided. This is valid but not useful
// in most cases.
rest := strings.ReplaceAll(path, "m/", "")
if rest == "" {
return []uint32{}, nil
}
parts := strings.Split(rest, "/")
indices := make([]uint32, len(parts))
for i := 0; i < len(parts); i++ {
part := parts[i]
if strings.Contains(parts[i], "'") {
part = strings.TrimRight(parts[i], "'")
}
parsed, err := strconv.ParseInt(part, 10, 32)
if err != nil {
return nil, fmt.Errorf("could not parse part \"%s\": "+
"%v", part, err)
}
indices[i] = uint32(parsed)
}
return indices, nil
}
// doubleHashMessage creates the double hash (sha256) of a message
// prepended with a specified prefix.
func doubleHashMessage(prefix string, msg string) ([]byte, error) {
var buf bytes.Buffer
err := wire.WriteVarString(&buf, 0, prefix)
if err != nil {
return nil, err
}
err = wire.WriteVarString(&buf, 0, msg)
if err != nil {
return nil, err
}
digest := chainhash.DoubleHashB(buf.Bytes())
return digest, nil
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v3.21.12
// source: walletunlocker.proto
package lnrpc
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type GenSeedRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// aezeed_passphrase is an optional user provided passphrase that will be used
// to encrypt the generated aezeed cipher seed. When using REST, this field
// must be encoded as base64.
AezeedPassphrase []byte `protobuf:"bytes,1,opt,name=aezeed_passphrase,json=aezeedPassphrase,proto3" json:"aezeed_passphrase,omitempty"`
// seed_entropy is an optional 16-bytes generated via CSPRNG. If not
// specified, then a fresh set of randomness will be used to create the seed.
// When using REST, this field must be encoded as base64.
SeedEntropy []byte `protobuf:"bytes,2,opt,name=seed_entropy,json=seedEntropy,proto3" json:"seed_entropy,omitempty"`
}
func (x *GenSeedRequest) Reset() {
*x = GenSeedRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletunlocker_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GenSeedRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GenSeedRequest) ProtoMessage() {}
func (x *GenSeedRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletunlocker_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GenSeedRequest.ProtoReflect.Descriptor instead.
func (*GenSeedRequest) Descriptor() ([]byte, []int) {
return file_walletunlocker_proto_rawDescGZIP(), []int{0}
}
func (x *GenSeedRequest) GetAezeedPassphrase() []byte {
if x != nil {
return x.AezeedPassphrase
}
return nil
}
func (x *GenSeedRequest) GetSeedEntropy() []byte {
if x != nil {
return x.SeedEntropy
}
return nil
}
type GenSeedResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed
// cipher seed obtained by the user. This field is optional, as if not
// provided, then the daemon will generate a new cipher seed for the user.
// Otherwise, then the daemon will attempt to recover the wallet state linked
// to this cipher seed.
CipherSeedMnemonic []string `protobuf:"bytes,1,rep,name=cipher_seed_mnemonic,json=cipherSeedMnemonic,proto3" json:"cipher_seed_mnemonic,omitempty"`
// enciphered_seed are the raw aezeed cipher seed bytes. This is the raw
// cipher text before run through our mnemonic encoding scheme.
EncipheredSeed []byte `protobuf:"bytes,2,opt,name=enciphered_seed,json=encipheredSeed,proto3" json:"enciphered_seed,omitempty"`
}
func (x *GenSeedResponse) Reset() {
*x = GenSeedResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletunlocker_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GenSeedResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GenSeedResponse) ProtoMessage() {}
func (x *GenSeedResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletunlocker_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GenSeedResponse.ProtoReflect.Descriptor instead.
func (*GenSeedResponse) Descriptor() ([]byte, []int) {
return file_walletunlocker_proto_rawDescGZIP(), []int{1}
}
func (x *GenSeedResponse) GetCipherSeedMnemonic() []string {
if x != nil {
return x.CipherSeedMnemonic
}
return nil
}
func (x *GenSeedResponse) GetEncipheredSeed() []byte {
if x != nil {
return x.EncipheredSeed
}
return nil
}
type InitWalletRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// wallet_password is the passphrase that should be used to encrypt the
// wallet. This MUST be at least 8 chars in length. After creation, this
// password is required to unlock the daemon. When using REST, this field
// must be encoded as base64.
WalletPassword []byte `protobuf:"bytes,1,opt,name=wallet_password,json=walletPassword,proto3" json:"wallet_password,omitempty"`
// cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed
// cipher seed obtained by the user. This may have been generated by the
// GenSeed method, or be an existing seed.
CipherSeedMnemonic []string `protobuf:"bytes,2,rep,name=cipher_seed_mnemonic,json=cipherSeedMnemonic,proto3" json:"cipher_seed_mnemonic,omitempty"`
// aezeed_passphrase is an optional user provided passphrase that will be used
// to encrypt the generated aezeed cipher seed. When using REST, this field
// must be encoded as base64.
AezeedPassphrase []byte `protobuf:"bytes,3,opt,name=aezeed_passphrase,json=aezeedPassphrase,proto3" json:"aezeed_passphrase,omitempty"`
// recovery_window is an optional argument specifying the address lookahead
// when restoring a wallet seed. The recovery window applies to each
// individual branch of the BIP44 derivation paths. Supplying a recovery
// window of zero indicates that no addresses should be recovered, such after
// the first initialization of the wallet.
RecoveryWindow int32 `protobuf:"varint,4,opt,name=recovery_window,json=recoveryWindow,proto3" json:"recovery_window,omitempty"`
// channel_backups is an optional argument that allows clients to recover the
// settled funds within a set of channels. This should be populated if the
// user was unable to close out all channels and sweep funds before partial or
// total data loss occurred. If specified, then after on-chain recovery of
// funds, lnd begin to carry out the data loss recovery protocol in order to
// recover the funds in each channel from a remote force closed transaction.
ChannelBackups *ChanBackupSnapshot `protobuf:"bytes,5,opt,name=channel_backups,json=channelBackups,proto3" json:"channel_backups,omitempty"`
// stateless_init is an optional argument instructing the daemon NOT to create
// any *.macaroon files in its filesystem. If this parameter is set, then the
// admin macaroon returned in the response MUST be stored by the caller of the
// RPC as otherwise all access to the daemon will be lost!
StatelessInit bool `protobuf:"varint,6,opt,name=stateless_init,json=statelessInit,proto3" json:"stateless_init,omitempty"`
// extended_master_key is an alternative to specifying cipher_seed_mnemonic and
// aezeed_passphrase. Instead of deriving the master root key from the entropy
// of an aezeed cipher seed, the given extended master root key is used
// directly as the wallet's master key. This allows users to import/use a
// master key from another wallet. When doing so, lnd still uses its default
// SegWit only (BIP49/84) derivation paths and funds from custom/non-default
// derivation paths will not automatically appear in the on-chain wallet. Using
// an 'xprv' instead of an aezeed also has the disadvantage that the wallet's
// birthday is not known as that is an information that's only encoded in the
// aezeed, not the xprv. Therefore a birthday needs to be specified in
// extended_master_key_birthday_timestamp or a "safe" default value will be
// used.
ExtendedMasterKey string `protobuf:"bytes,7,opt,name=extended_master_key,json=extendedMasterKey,proto3" json:"extended_master_key,omitempty"`
// extended_master_key_birthday_timestamp is the optional unix timestamp in
// seconds to use as the wallet's birthday when using an extended master key
// to restore the wallet. lnd will only start scanning for funds in blocks that
// are after the birthday which can speed up the process significantly. If the
// birthday is not known, this should be left at its default value of 0 in
// which case lnd will start scanning from the first SegWit block (481824 on
// mainnet).
ExtendedMasterKeyBirthdayTimestamp uint64 `protobuf:"varint,8,opt,name=extended_master_key_birthday_timestamp,json=extendedMasterKeyBirthdayTimestamp,proto3" json:"extended_master_key_birthday_timestamp,omitempty"`
// watch_only is the third option of initializing a wallet: by importing
// account xpubs only and therefore creating a watch-only wallet that does not
// contain any private keys. That means the wallet won't be able to sign for
// any of the keys and _needs_ to be run with a remote signer that has the
// corresponding private keys and can serve signing RPC requests.
WatchOnly *WatchOnly `protobuf:"bytes,9,opt,name=watch_only,json=watchOnly,proto3" json:"watch_only,omitempty"`
// macaroon_root_key is an optional 32 byte macaroon root key that can be
// provided when initializing the wallet rather than letting lnd generate one
// on its own.
MacaroonRootKey []byte `protobuf:"bytes,10,opt,name=macaroon_root_key,json=macaroonRootKey,proto3" json:"macaroon_root_key,omitempty"`
}
func (x *InitWalletRequest) Reset() {
*x = InitWalletRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletunlocker_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *InitWalletRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*InitWalletRequest) ProtoMessage() {}
func (x *InitWalletRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletunlocker_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use InitWalletRequest.ProtoReflect.Descriptor instead.
func (*InitWalletRequest) Descriptor() ([]byte, []int) {
return file_walletunlocker_proto_rawDescGZIP(), []int{2}
}
func (x *InitWalletRequest) GetWalletPassword() []byte {
if x != nil {
return x.WalletPassword
}
return nil
}
func (x *InitWalletRequest) GetCipherSeedMnemonic() []string {
if x != nil {
return x.CipherSeedMnemonic
}
return nil
}
func (x *InitWalletRequest) GetAezeedPassphrase() []byte {
if x != nil {
return x.AezeedPassphrase
}
return nil
}
func (x *InitWalletRequest) GetRecoveryWindow() int32 {
if x != nil {
return x.RecoveryWindow
}
return 0
}
func (x *InitWalletRequest) GetChannelBackups() *ChanBackupSnapshot {
if x != nil {
return x.ChannelBackups
}
return nil
}
func (x *InitWalletRequest) GetStatelessInit() bool {
if x != nil {
return x.StatelessInit
}
return false
}
func (x *InitWalletRequest) GetExtendedMasterKey() string {
if x != nil {
return x.ExtendedMasterKey
}
return ""
}
func (x *InitWalletRequest) GetExtendedMasterKeyBirthdayTimestamp() uint64 {
if x != nil {
return x.ExtendedMasterKeyBirthdayTimestamp
}
return 0
}
func (x *InitWalletRequest) GetWatchOnly() *WatchOnly {
if x != nil {
return x.WatchOnly
}
return nil
}
func (x *InitWalletRequest) GetMacaroonRootKey() []byte {
if x != nil {
return x.MacaroonRootKey
}
return nil
}
type InitWalletResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The binary serialized admin macaroon that can be used to access the daemon
// after creating the wallet. If the stateless_init parameter was set to true,
// this is the ONLY copy of the macaroon and MUST be stored safely by the
// caller. Otherwise a copy of this macaroon is also persisted on disk by the
// daemon, together with other macaroon files.
AdminMacaroon []byte `protobuf:"bytes,1,opt,name=admin_macaroon,json=adminMacaroon,proto3" json:"admin_macaroon,omitempty"`
}
func (x *InitWalletResponse) Reset() {
*x = InitWalletResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletunlocker_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *InitWalletResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*InitWalletResponse) ProtoMessage() {}
func (x *InitWalletResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletunlocker_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use InitWalletResponse.ProtoReflect.Descriptor instead.
func (*InitWalletResponse) Descriptor() ([]byte, []int) {
return file_walletunlocker_proto_rawDescGZIP(), []int{3}
}
func (x *InitWalletResponse) GetAdminMacaroon() []byte {
if x != nil {
return x.AdminMacaroon
}
return nil
}
type WatchOnly struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The unix timestamp in seconds of when the master key was created. lnd will
// only start scanning for funds in blocks that are after the birthday which
// can speed up the process significantly. If the birthday is not known, this
// should be left at its default value of 0 in which case lnd will start
// scanning from the first SegWit block (481824 on mainnet).
MasterKeyBirthdayTimestamp uint64 `protobuf:"varint,1,opt,name=master_key_birthday_timestamp,json=masterKeyBirthdayTimestamp,proto3" json:"master_key_birthday_timestamp,omitempty"`
// The fingerprint of the root key (also known as the key with derivation path
// m/) from which the account public keys were derived from. This may be
// required by some hardware wallets for proper identification and signing. The
// bytes must be in big-endian order.
MasterKeyFingerprint []byte `protobuf:"bytes,2,opt,name=master_key_fingerprint,json=masterKeyFingerprint,proto3" json:"master_key_fingerprint,omitempty"`
// The list of accounts to import. There _must_ be an account for all of lnd's
// main key scopes: BIP49/BIP84 (m/49'/0'/0', m/84'/0'/0', note that the
// coin type is always 0, even for testnet/regtest) and lnd's internal key
// scope (m/1017'/<coin_type>'/<account>'), where account is the key family as
// defined in `keychain/derivation.go` (currently indices 0 to 9).
Accounts []*WatchOnlyAccount `protobuf:"bytes,3,rep,name=accounts,proto3" json:"accounts,omitempty"`
}
func (x *WatchOnly) Reset() {
*x = WatchOnly{}
if protoimpl.UnsafeEnabled {
mi := &file_walletunlocker_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *WatchOnly) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*WatchOnly) ProtoMessage() {}
func (x *WatchOnly) ProtoReflect() protoreflect.Message {
mi := &file_walletunlocker_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use WatchOnly.ProtoReflect.Descriptor instead.
func (*WatchOnly) Descriptor() ([]byte, []int) {
return file_walletunlocker_proto_rawDescGZIP(), []int{4}
}
func (x *WatchOnly) GetMasterKeyBirthdayTimestamp() uint64 {
if x != nil {
return x.MasterKeyBirthdayTimestamp
}
return 0
}
func (x *WatchOnly) GetMasterKeyFingerprint() []byte {
if x != nil {
return x.MasterKeyFingerprint
}
return nil
}
func (x *WatchOnly) GetAccounts() []*WatchOnlyAccount {
if x != nil {
return x.Accounts
}
return nil
}
type WatchOnlyAccount struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Purpose is the first number in the derivation path, must be either 49, 84
// or 1017.
Purpose uint32 `protobuf:"varint,1,opt,name=purpose,proto3" json:"purpose,omitempty"`
// Coin type is the second number in the derivation path, this is _always_ 0
// for purposes 49 and 84. It only needs to be set to 1 for purpose 1017 on
// testnet or regtest.
CoinType uint32 `protobuf:"varint,2,opt,name=coin_type,json=coinType,proto3" json:"coin_type,omitempty"`
// Account is the third number in the derivation path. For purposes 49 and 84
// at least the default account (index 0) needs to be created but optional
// additional accounts are allowed. For purpose 1017 there needs to be exactly
// one account for each of the key families defined in `keychain/derivation.go`
// (currently indices 0 to 9)
Account uint32 `protobuf:"varint,3,opt,name=account,proto3" json:"account,omitempty"`
// The extended public key at depth 3 for the given account.
Xpub string `protobuf:"bytes,4,opt,name=xpub,proto3" json:"xpub,omitempty"`
}
func (x *WatchOnlyAccount) Reset() {
*x = WatchOnlyAccount{}
if protoimpl.UnsafeEnabled {
mi := &file_walletunlocker_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *WatchOnlyAccount) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*WatchOnlyAccount) ProtoMessage() {}
func (x *WatchOnlyAccount) ProtoReflect() protoreflect.Message {
mi := &file_walletunlocker_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use WatchOnlyAccount.ProtoReflect.Descriptor instead.
func (*WatchOnlyAccount) Descriptor() ([]byte, []int) {
return file_walletunlocker_proto_rawDescGZIP(), []int{5}
}
func (x *WatchOnlyAccount) GetPurpose() uint32 {
if x != nil {
return x.Purpose
}
return 0
}
func (x *WatchOnlyAccount) GetCoinType() uint32 {
if x != nil {
return x.CoinType
}
return 0
}
func (x *WatchOnlyAccount) GetAccount() uint32 {
if x != nil {
return x.Account
}
return 0
}
func (x *WatchOnlyAccount) GetXpub() string {
if x != nil {
return x.Xpub
}
return ""
}
type UnlockWalletRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// wallet_password should be the current valid passphrase for the daemon. This
// will be required to decrypt on-disk material that the daemon requires to
// function properly. When using REST, this field must be encoded as base64.
WalletPassword []byte `protobuf:"bytes,1,opt,name=wallet_password,json=walletPassword,proto3" json:"wallet_password,omitempty"`
// recovery_window is an optional argument specifying the address lookahead
// when restoring a wallet seed. The recovery window applies to each
// individual branch of the BIP44 derivation paths. Supplying a recovery
// window of zero indicates that no addresses should be recovered, such after
// the first initialization of the wallet.
RecoveryWindow int32 `protobuf:"varint,2,opt,name=recovery_window,json=recoveryWindow,proto3" json:"recovery_window,omitempty"`
// channel_backups is an optional argument that allows clients to recover the
// settled funds within a set of channels. This should be populated if the
// user was unable to close out all channels and sweep funds before partial or
// total data loss occurred. If specified, then after on-chain recovery of
// funds, lnd begin to carry out the data loss recovery protocol in order to
// recover the funds in each channel from a remote force closed transaction.
ChannelBackups *ChanBackupSnapshot `protobuf:"bytes,3,opt,name=channel_backups,json=channelBackups,proto3" json:"channel_backups,omitempty"`
// stateless_init is an optional argument instructing the daemon NOT to create
// any *.macaroon files in its file system.
StatelessInit bool `protobuf:"varint,4,opt,name=stateless_init,json=statelessInit,proto3" json:"stateless_init,omitempty"`
}
func (x *UnlockWalletRequest) Reset() {
*x = UnlockWalletRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletunlocker_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *UnlockWalletRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UnlockWalletRequest) ProtoMessage() {}
func (x *UnlockWalletRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletunlocker_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UnlockWalletRequest.ProtoReflect.Descriptor instead.
func (*UnlockWalletRequest) Descriptor() ([]byte, []int) {
return file_walletunlocker_proto_rawDescGZIP(), []int{6}
}
func (x *UnlockWalletRequest) GetWalletPassword() []byte {
if x != nil {
return x.WalletPassword
}
return nil
}
func (x *UnlockWalletRequest) GetRecoveryWindow() int32 {
if x != nil {
return x.RecoveryWindow
}
return 0
}
func (x *UnlockWalletRequest) GetChannelBackups() *ChanBackupSnapshot {
if x != nil {
return x.ChannelBackups
}
return nil
}
func (x *UnlockWalletRequest) GetStatelessInit() bool {
if x != nil {
return x.StatelessInit
}
return false
}
type UnlockWalletResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *UnlockWalletResponse) Reset() {
*x = UnlockWalletResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletunlocker_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *UnlockWalletResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UnlockWalletResponse) ProtoMessage() {}
func (x *UnlockWalletResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletunlocker_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UnlockWalletResponse.ProtoReflect.Descriptor instead.
func (*UnlockWalletResponse) Descriptor() ([]byte, []int) {
return file_walletunlocker_proto_rawDescGZIP(), []int{7}
}
type ChangePasswordRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// current_password should be the current valid passphrase used to unlock the
// daemon. When using REST, this field must be encoded as base64.
CurrentPassword []byte `protobuf:"bytes,1,opt,name=current_password,json=currentPassword,proto3" json:"current_password,omitempty"`
// new_password should be the new passphrase that will be needed to unlock the
// daemon. When using REST, this field must be encoded as base64.
NewPassword []byte `protobuf:"bytes,2,opt,name=new_password,json=newPassword,proto3" json:"new_password,omitempty"`
// stateless_init is an optional argument instructing the daemon NOT to create
// any *.macaroon files in its filesystem. If this parameter is set, then the
// admin macaroon returned in the response MUST be stored by the caller of the
// RPC as otherwise all access to the daemon will be lost!
StatelessInit bool `protobuf:"varint,3,opt,name=stateless_init,json=statelessInit,proto3" json:"stateless_init,omitempty"`
// new_macaroon_root_key is an optional argument instructing the daemon to
// rotate the macaroon root key when set to true. This will invalidate all
// previously generated macaroons.
NewMacaroonRootKey bool `protobuf:"varint,4,opt,name=new_macaroon_root_key,json=newMacaroonRootKey,proto3" json:"new_macaroon_root_key,omitempty"`
}
func (x *ChangePasswordRequest) Reset() {
*x = ChangePasswordRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_walletunlocker_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChangePasswordRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChangePasswordRequest) ProtoMessage() {}
func (x *ChangePasswordRequest) ProtoReflect() protoreflect.Message {
mi := &file_walletunlocker_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChangePasswordRequest.ProtoReflect.Descriptor instead.
func (*ChangePasswordRequest) Descriptor() ([]byte, []int) {
return file_walletunlocker_proto_rawDescGZIP(), []int{8}
}
func (x *ChangePasswordRequest) GetCurrentPassword() []byte {
if x != nil {
return x.CurrentPassword
}
return nil
}
func (x *ChangePasswordRequest) GetNewPassword() []byte {
if x != nil {
return x.NewPassword
}
return nil
}
func (x *ChangePasswordRequest) GetStatelessInit() bool {
if x != nil {
return x.StatelessInit
}
return false
}
func (x *ChangePasswordRequest) GetNewMacaroonRootKey() bool {
if x != nil {
return x.NewMacaroonRootKey
}
return false
}
type ChangePasswordResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The binary serialized admin macaroon that can be used to access the daemon
// after rotating the macaroon root key. If both the stateless_init and
// new_macaroon_root_key parameter were set to true, this is the ONLY copy of
// the macaroon that was created from the new root key and MUST be stored
// safely by the caller. Otherwise a copy of this macaroon is also persisted on
// disk by the daemon, together with other macaroon files.
AdminMacaroon []byte `protobuf:"bytes,1,opt,name=admin_macaroon,json=adminMacaroon,proto3" json:"admin_macaroon,omitempty"`
}
func (x *ChangePasswordResponse) Reset() {
*x = ChangePasswordResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_walletunlocker_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ChangePasswordResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ChangePasswordResponse) ProtoMessage() {}
func (x *ChangePasswordResponse) ProtoReflect() protoreflect.Message {
mi := &file_walletunlocker_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ChangePasswordResponse.ProtoReflect.Descriptor instead.
func (*ChangePasswordResponse) Descriptor() ([]byte, []int) {
return file_walletunlocker_proto_rawDescGZIP(), []int{9}
}
func (x *ChangePasswordResponse) GetAdminMacaroon() []byte {
if x != nil {
return x.AdminMacaroon
}
return nil
}
var File_walletunlocker_proto protoreflect.FileDescriptor
var file_walletunlocker_proto_rawDesc = []byte{
0x0a, 0x14, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x75, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x72,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x1a, 0x0f, 0x6c,
0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x60,
0x0a, 0x0e, 0x47, 0x65, 0x6e, 0x53, 0x65, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x2b, 0x0a, 0x11, 0x61, 0x65, 0x7a, 0x65, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x70,
0x68, 0x72, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x61, 0x65, 0x7a,
0x65, 0x65, 0x64, 0x50, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x12, 0x21, 0x0a,
0x0c, 0x73, 0x65, 0x65, 0x64, 0x5f, 0x65, 0x6e, 0x74, 0x72, 0x6f, 0x70, 0x79, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x0b, 0x73, 0x65, 0x65, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x6f, 0x70, 0x79,
0x22, 0x6c, 0x0a, 0x0f, 0x47, 0x65, 0x6e, 0x53, 0x65, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x5f, 0x73, 0x65,
0x65, 0x64, 0x5f, 0x6d, 0x6e, 0x65, 0x6d, 0x6f, 0x6e, 0x69, 0x63, 0x18, 0x01, 0x20, 0x03, 0x28,
0x09, 0x52, 0x12, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x65, 0x65, 0x64, 0x4d, 0x6e, 0x65,
0x6d, 0x6f, 0x6e, 0x69, 0x63, 0x12, 0x27, 0x0a, 0x0f, 0x65, 0x6e, 0x63, 0x69, 0x70, 0x68, 0x65,
0x72, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e,
0x65, 0x6e, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x65, 0x64, 0x53, 0x65, 0x65, 0x64, 0x22, 0x90,
0x04, 0x0a, 0x11, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x70,
0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x77,
0x61, 0x6c, 0x6c, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x30, 0x0a,
0x14, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x65, 0x64, 0x5f, 0x6d, 0x6e, 0x65,
0x6d, 0x6f, 0x6e, 0x69, 0x63, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x63, 0x69, 0x70,
0x68, 0x65, 0x72, 0x53, 0x65, 0x65, 0x64, 0x4d, 0x6e, 0x65, 0x6d, 0x6f, 0x6e, 0x69, 0x63, 0x12,
0x2b, 0x0a, 0x11, 0x61, 0x65, 0x7a, 0x65, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68,
0x72, 0x61, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x61, 0x65, 0x7a, 0x65,
0x65, 0x64, 0x50, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f,
0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x18,
0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x57,
0x69, 0x6e, 0x64, 0x6f, 0x77, 0x12, 0x42, 0x0a, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19,
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75,
0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x6e,
0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61,
0x74, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28,
0x08, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x69, 0x74,
0x12, 0x2e, 0x0a, 0x13, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x6d, 0x61, 0x73,
0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65,
0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79,
0x12, 0x52, 0x0a, 0x26, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x6d, 0x61, 0x73,
0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x79,
0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04,
0x52, 0x22, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72,
0x4b, 0x65, 0x79, 0x42, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x73,
0x74, 0x61, 0x6d, 0x70, 0x12, 0x2f, 0x0a, 0x0a, 0x77, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x6f, 0x6e,
0x6c, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x52, 0x09, 0x77, 0x61, 0x74, 0x63,
0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f,
0x6e, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x0f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x6f, 0x6f, 0x74, 0x4b, 0x65,
0x79, 0x22, 0x3b, 0x0a, 0x12, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x64, 0x6d, 0x69, 0x6e,
0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x22, 0xb9,
0x01, 0x0a, 0x09, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x41, 0x0a, 0x1d,
0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x69, 0x72, 0x74, 0x68,
0x64, 0x61, 0x79, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20,
0x01, 0x28, 0x04, 0x52, 0x1a, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x42, 0x69,
0x72, 0x74, 0x68, 0x64, 0x61, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12,
0x34, 0x0a, 0x16, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69,
0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x14, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72,
0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e,
0x57, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x77, 0x0a, 0x10, 0x57, 0x61,
0x74, 0x63, 0x68, 0x4f, 0x6e, 0x6c, 0x79, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x18,
0x0a, 0x07, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52,
0x07, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x69, 0x6e,
0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x63, 0x6f, 0x69,
0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12,
0x12, 0x0a, 0x04, 0x78, 0x70, 0x75, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x78,
0x70, 0x75, 0x62, 0x22, 0xd2, 0x01, 0x0a, 0x13, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x57, 0x61,
0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x77,
0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73,
0x77, 0x6f, 0x72, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79,
0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72,
0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x12, 0x42, 0x0a,
0x0f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73,
0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43,
0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f,
0x74, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70,
0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x5f, 0x69,
0x6e, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x65,
0x6c, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x69, 0x74, 0x22, 0x16, 0x0a, 0x14, 0x55, 0x6e, 0x6c, 0x6f,
0x63, 0x6b, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0xbf, 0x01, 0x0a, 0x15, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77,
0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x75,
0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x73,
0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x65, 0x77, 0x5f, 0x70, 0x61, 0x73,
0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6e, 0x65, 0x77,
0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74,
0x65, 0x6c, 0x65, 0x73, 0x73, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08,
0x52, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x69, 0x74, 0x12,
0x31, 0x0a, 0x15, 0x6e, 0x65, 0x77, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x5f,
0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12,
0x6e, 0x65, 0x77, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x6f, 0x6f, 0x74, 0x4b,
0x65, 0x79, 0x22, 0x3f, 0x0a, 0x16, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73,
0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e,
0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4d, 0x61, 0x63, 0x61, 0x72,
0x6f, 0x6f, 0x6e, 0x32, 0xa5, 0x02, 0x0a, 0x0e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x55, 0x6e,
0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x12, 0x38, 0x0a, 0x07, 0x47, 0x65, 0x6e, 0x53, 0x65, 0x65,
0x64, 0x12, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x53, 0x65, 0x65,
0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x47, 0x65, 0x6e, 0x53, 0x65, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x41, 0x0a, 0x0a, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x12, 0x18,
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65,
0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x57, 0x61, 0x6c,
0x6c, 0x65, 0x74, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x6e, 0x6c, 0x6f,
0x63, 0x6b, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x57, 0x61,
0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0e,
0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1c,
0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73,
0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77,
0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e,
0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_walletunlocker_proto_rawDescOnce sync.Once
file_walletunlocker_proto_rawDescData = file_walletunlocker_proto_rawDesc
)
func file_walletunlocker_proto_rawDescGZIP() []byte {
file_walletunlocker_proto_rawDescOnce.Do(func() {
file_walletunlocker_proto_rawDescData = protoimpl.X.CompressGZIP(file_walletunlocker_proto_rawDescData)
})
return file_walletunlocker_proto_rawDescData
}
var file_walletunlocker_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_walletunlocker_proto_goTypes = []interface{}{
(*GenSeedRequest)(nil), // 0: lnrpc.GenSeedRequest
(*GenSeedResponse)(nil), // 1: lnrpc.GenSeedResponse
(*InitWalletRequest)(nil), // 2: lnrpc.InitWalletRequest
(*InitWalletResponse)(nil), // 3: lnrpc.InitWalletResponse
(*WatchOnly)(nil), // 4: lnrpc.WatchOnly
(*WatchOnlyAccount)(nil), // 5: lnrpc.WatchOnlyAccount
(*UnlockWalletRequest)(nil), // 6: lnrpc.UnlockWalletRequest
(*UnlockWalletResponse)(nil), // 7: lnrpc.UnlockWalletResponse
(*ChangePasswordRequest)(nil), // 8: lnrpc.ChangePasswordRequest
(*ChangePasswordResponse)(nil), // 9: lnrpc.ChangePasswordResponse
(*ChanBackupSnapshot)(nil), // 10: lnrpc.ChanBackupSnapshot
}
var file_walletunlocker_proto_depIdxs = []int32{
10, // 0: lnrpc.InitWalletRequest.channel_backups:type_name -> lnrpc.ChanBackupSnapshot
4, // 1: lnrpc.InitWalletRequest.watch_only:type_name -> lnrpc.WatchOnly
5, // 2: lnrpc.WatchOnly.accounts:type_name -> lnrpc.WatchOnlyAccount
10, // 3: lnrpc.UnlockWalletRequest.channel_backups:type_name -> lnrpc.ChanBackupSnapshot
0, // 4: lnrpc.WalletUnlocker.GenSeed:input_type -> lnrpc.GenSeedRequest
2, // 5: lnrpc.WalletUnlocker.InitWallet:input_type -> lnrpc.InitWalletRequest
6, // 6: lnrpc.WalletUnlocker.UnlockWallet:input_type -> lnrpc.UnlockWalletRequest
8, // 7: lnrpc.WalletUnlocker.ChangePassword:input_type -> lnrpc.ChangePasswordRequest
1, // 8: lnrpc.WalletUnlocker.GenSeed:output_type -> lnrpc.GenSeedResponse
3, // 9: lnrpc.WalletUnlocker.InitWallet:output_type -> lnrpc.InitWalletResponse
7, // 10: lnrpc.WalletUnlocker.UnlockWallet:output_type -> lnrpc.UnlockWalletResponse
9, // 11: lnrpc.WalletUnlocker.ChangePassword:output_type -> lnrpc.ChangePasswordResponse
8, // [8:12] is the sub-list for method output_type
4, // [4:8] is the sub-list for method input_type
4, // [4:4] is the sub-list for extension type_name
4, // [4:4] is the sub-list for extension extendee
0, // [0:4] is the sub-list for field type_name
}
func init() { file_walletunlocker_proto_init() }
func file_walletunlocker_proto_init() {
if File_walletunlocker_proto != nil {
return
}
file_lightning_proto_init()
if !protoimpl.UnsafeEnabled {
file_walletunlocker_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GenSeedRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletunlocker_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GenSeedResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletunlocker_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*InitWalletRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletunlocker_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*InitWalletResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletunlocker_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*WatchOnly); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletunlocker_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*WatchOnlyAccount); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletunlocker_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UnlockWalletRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletunlocker_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UnlockWalletResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletunlocker_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChangePasswordRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_walletunlocker_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ChangePasswordResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_walletunlocker_proto_rawDesc,
NumEnums: 0,
NumMessages: 10,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_walletunlocker_proto_goTypes,
DependencyIndexes: file_walletunlocker_proto_depIdxs,
MessageInfos: file_walletunlocker_proto_msgTypes,
}.Build()
File_walletunlocker_proto = out.File
file_walletunlocker_proto_rawDesc = nil
file_walletunlocker_proto_goTypes = nil
file_walletunlocker_proto_depIdxs = nil
}
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: walletunlocker.proto
/*
Package lnrpc is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package lnrpc
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
var (
filter_WalletUnlocker_GenSeed_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_WalletUnlocker_GenSeed_0(ctx context.Context, marshaler runtime.Marshaler, client WalletUnlockerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GenSeedRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WalletUnlocker_GenSeed_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.GenSeed(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletUnlocker_GenSeed_0(ctx context.Context, marshaler runtime.Marshaler, server WalletUnlockerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GenSeedRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WalletUnlocker_GenSeed_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.GenSeed(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletUnlocker_InitWallet_0(ctx context.Context, marshaler runtime.Marshaler, client WalletUnlockerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq InitWalletRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.InitWallet(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletUnlocker_InitWallet_0(ctx context.Context, marshaler runtime.Marshaler, server WalletUnlockerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq InitWalletRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.InitWallet(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletUnlocker_UnlockWallet_0(ctx context.Context, marshaler runtime.Marshaler, client WalletUnlockerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq UnlockWalletRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.UnlockWallet(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletUnlocker_UnlockWallet_0(ctx context.Context, marshaler runtime.Marshaler, server WalletUnlockerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq UnlockWalletRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.UnlockWallet(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletUnlocker_ChangePassword_0(ctx context.Context, marshaler runtime.Marshaler, client WalletUnlockerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ChangePasswordRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ChangePassword(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletUnlocker_ChangePassword_0(ctx context.Context, marshaler runtime.Marshaler, server WalletUnlockerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ChangePasswordRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ChangePassword(ctx, &protoReq)
return msg, metadata, err
}
// RegisterWalletUnlockerHandlerServer registers the http handlers for service WalletUnlocker to "mux".
// UnaryRPC :call WalletUnlockerServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterWalletUnlockerHandlerFromEndpoint instead.
func RegisterWalletUnlockerHandlerServer(ctx context.Context, mux *runtime.ServeMux, server WalletUnlockerServer) error {
mux.Handle("GET", pattern_WalletUnlocker_GenSeed_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.WalletUnlocker/GenSeed", runtime.WithHTTPPathPattern("/v1/genseed"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletUnlocker_GenSeed_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletUnlocker_GenSeed_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletUnlocker_InitWallet_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.WalletUnlocker/InitWallet", runtime.WithHTTPPathPattern("/v1/initwallet"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletUnlocker_InitWallet_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletUnlocker_InitWallet_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletUnlocker_UnlockWallet_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.WalletUnlocker/UnlockWallet", runtime.WithHTTPPathPattern("/v1/unlockwallet"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletUnlocker_UnlockWallet_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletUnlocker_UnlockWallet_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletUnlocker_ChangePassword_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/lnrpc.WalletUnlocker/ChangePassword", runtime.WithHTTPPathPattern("/v1/changepassword"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletUnlocker_ChangePassword_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletUnlocker_ChangePassword_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterWalletUnlockerHandlerFromEndpoint is same as RegisterWalletUnlockerHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterWalletUnlockerHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterWalletUnlockerHandler(ctx, mux, conn)
}
// RegisterWalletUnlockerHandler registers the http handlers for service WalletUnlocker to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterWalletUnlockerHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterWalletUnlockerHandlerClient(ctx, mux, NewWalletUnlockerClient(conn))
}
// RegisterWalletUnlockerHandlerClient registers the http handlers for service WalletUnlocker
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "WalletUnlockerClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "WalletUnlockerClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "WalletUnlockerClient" to call the correct interceptors.
func RegisterWalletUnlockerHandlerClient(ctx context.Context, mux *runtime.ServeMux, client WalletUnlockerClient) error {
mux.Handle("GET", pattern_WalletUnlocker_GenSeed_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.WalletUnlocker/GenSeed", runtime.WithHTTPPathPattern("/v1/genseed"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletUnlocker_GenSeed_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletUnlocker_GenSeed_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletUnlocker_InitWallet_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.WalletUnlocker/InitWallet", runtime.WithHTTPPathPattern("/v1/initwallet"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletUnlocker_InitWallet_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletUnlocker_InitWallet_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletUnlocker_UnlockWallet_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.WalletUnlocker/UnlockWallet", runtime.WithHTTPPathPattern("/v1/unlockwallet"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletUnlocker_UnlockWallet_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletUnlocker_UnlockWallet_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletUnlocker_ChangePassword_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/lnrpc.WalletUnlocker/ChangePassword", runtime.WithHTTPPathPattern("/v1/changepassword"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletUnlocker_ChangePassword_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletUnlocker_ChangePassword_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_WalletUnlocker_GenSeed_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "genseed"}, ""))
pattern_WalletUnlocker_InitWallet_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "initwallet"}, ""))
pattern_WalletUnlocker_UnlockWallet_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "unlockwallet"}, ""))
pattern_WalletUnlocker_ChangePassword_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "changepassword"}, ""))
)
var (
forward_WalletUnlocker_GenSeed_0 = runtime.ForwardResponseMessage
forward_WalletUnlocker_InitWallet_0 = runtime.ForwardResponseMessage
forward_WalletUnlocker_UnlockWallet_0 = runtime.ForwardResponseMessage
forward_WalletUnlocker_ChangePassword_0 = runtime.ForwardResponseMessage
)
// Code generated by falafel 0.9.2. DO NOT EDIT.
// source: walletunlocker.proto
package lnrpc
import (
"context"
gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protojson"
)
func RegisterWalletUnlockerJSONCallbacks(registry map[string]func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error))) {
marshaler := &gateway.JSONPb{
MarshalOptions: protojson.MarshalOptions{
UseProtoNames: true,
EmitUnpopulated: true,
},
}
registry["lnrpc.WalletUnlocker.GenSeed"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &GenSeedRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletUnlockerClient(conn)
resp, err := client.GenSeed(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.WalletUnlocker.InitWallet"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &InitWalletRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletUnlockerClient(conn)
resp, err := client.InitWallet(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.WalletUnlocker.UnlockWallet"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &UnlockWalletRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletUnlockerClient(conn)
resp, err := client.UnlockWallet(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["lnrpc.WalletUnlocker.ChangePassword"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ChangePasswordRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletUnlockerClient(conn)
resp, err := client.ChangePassword(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package lnrpc
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// WalletUnlockerClient is the client API for WalletUnlocker service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type WalletUnlockerClient interface {
// GenSeed is the first method that should be used to instantiate a new lnd
// instance. This method allows a caller to generate a new aezeed cipher seed
// given an optional passphrase. If provided, the passphrase will be necessary
// to decrypt the cipherseed to expose the internal wallet seed.
//
// Once the cipherseed is obtained and verified by the user, the InitWallet
// method should be used to commit the newly generated seed, and create the
// wallet.
GenSeed(ctx context.Context, in *GenSeedRequest, opts ...grpc.CallOption) (*GenSeedResponse, error)
// InitWallet is used when lnd is starting up for the first time to fully
// initialize the daemon and its internal wallet. At the very least a wallet
// password must be provided. This will be used to encrypt sensitive material
// on disk.
//
// In the case of a recovery scenario, the user can also specify their aezeed
// mnemonic and passphrase. If set, then the daemon will use this prior state
// to initialize its internal wallet.
//
// Alternatively, this can be used along with the GenSeed RPC to obtain a
// seed, then present it to the user. Once it has been verified by the user,
// the seed can be fed into this RPC in order to commit the new wallet.
InitWallet(ctx context.Context, in *InitWalletRequest, opts ...grpc.CallOption) (*InitWalletResponse, error)
// lncli: `unlock`
// UnlockWallet is used at startup of lnd to provide a password to unlock
// the wallet database.
UnlockWallet(ctx context.Context, in *UnlockWalletRequest, opts ...grpc.CallOption) (*UnlockWalletResponse, error)
// lncli: `changepassword`
// ChangePassword changes the password of the encrypted wallet. This will
// automatically unlock the wallet database if successful.
ChangePassword(ctx context.Context, in *ChangePasswordRequest, opts ...grpc.CallOption) (*ChangePasswordResponse, error)
}
type walletUnlockerClient struct {
cc grpc.ClientConnInterface
}
func NewWalletUnlockerClient(cc grpc.ClientConnInterface) WalletUnlockerClient {
return &walletUnlockerClient{cc}
}
func (c *walletUnlockerClient) GenSeed(ctx context.Context, in *GenSeedRequest, opts ...grpc.CallOption) (*GenSeedResponse, error) {
out := new(GenSeedResponse)
err := c.cc.Invoke(ctx, "/lnrpc.WalletUnlocker/GenSeed", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletUnlockerClient) InitWallet(ctx context.Context, in *InitWalletRequest, opts ...grpc.CallOption) (*InitWalletResponse, error) {
out := new(InitWalletResponse)
err := c.cc.Invoke(ctx, "/lnrpc.WalletUnlocker/InitWallet", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletUnlockerClient) UnlockWallet(ctx context.Context, in *UnlockWalletRequest, opts ...grpc.CallOption) (*UnlockWalletResponse, error) {
out := new(UnlockWalletResponse)
err := c.cc.Invoke(ctx, "/lnrpc.WalletUnlocker/UnlockWallet", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletUnlockerClient) ChangePassword(ctx context.Context, in *ChangePasswordRequest, opts ...grpc.CallOption) (*ChangePasswordResponse, error) {
out := new(ChangePasswordResponse)
err := c.cc.Invoke(ctx, "/lnrpc.WalletUnlocker/ChangePassword", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// WalletUnlockerServer is the server API for WalletUnlocker service.
// All implementations must embed UnimplementedWalletUnlockerServer
// for forward compatibility
type WalletUnlockerServer interface {
// GenSeed is the first method that should be used to instantiate a new lnd
// instance. This method allows a caller to generate a new aezeed cipher seed
// given an optional passphrase. If provided, the passphrase will be necessary
// to decrypt the cipherseed to expose the internal wallet seed.
//
// Once the cipherseed is obtained and verified by the user, the InitWallet
// method should be used to commit the newly generated seed, and create the
// wallet.
GenSeed(context.Context, *GenSeedRequest) (*GenSeedResponse, error)
// InitWallet is used when lnd is starting up for the first time to fully
// initialize the daemon and its internal wallet. At the very least a wallet
// password must be provided. This will be used to encrypt sensitive material
// on disk.
//
// In the case of a recovery scenario, the user can also specify their aezeed
// mnemonic and passphrase. If set, then the daemon will use this prior state
// to initialize its internal wallet.
//
// Alternatively, this can be used along with the GenSeed RPC to obtain a
// seed, then present it to the user. Once it has been verified by the user,
// the seed can be fed into this RPC in order to commit the new wallet.
InitWallet(context.Context, *InitWalletRequest) (*InitWalletResponse, error)
// lncli: `unlock`
// UnlockWallet is used at startup of lnd to provide a password to unlock
// the wallet database.
UnlockWallet(context.Context, *UnlockWalletRequest) (*UnlockWalletResponse, error)
// lncli: `changepassword`
// ChangePassword changes the password of the encrypted wallet. This will
// automatically unlock the wallet database if successful.
ChangePassword(context.Context, *ChangePasswordRequest) (*ChangePasswordResponse, error)
mustEmbedUnimplementedWalletUnlockerServer()
}
// UnimplementedWalletUnlockerServer must be embedded to have forward compatible implementations.
type UnimplementedWalletUnlockerServer struct {
}
func (UnimplementedWalletUnlockerServer) GenSeed(context.Context, *GenSeedRequest) (*GenSeedResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GenSeed not implemented")
}
func (UnimplementedWalletUnlockerServer) InitWallet(context.Context, *InitWalletRequest) (*InitWalletResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method InitWallet not implemented")
}
func (UnimplementedWalletUnlockerServer) UnlockWallet(context.Context, *UnlockWalletRequest) (*UnlockWalletResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UnlockWallet not implemented")
}
func (UnimplementedWalletUnlockerServer) ChangePassword(context.Context, *ChangePasswordRequest) (*ChangePasswordResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ChangePassword not implemented")
}
func (UnimplementedWalletUnlockerServer) mustEmbedUnimplementedWalletUnlockerServer() {}
// UnsafeWalletUnlockerServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to WalletUnlockerServer will
// result in compilation errors.
type UnsafeWalletUnlockerServer interface {
mustEmbedUnimplementedWalletUnlockerServer()
}
func RegisterWalletUnlockerServer(s grpc.ServiceRegistrar, srv WalletUnlockerServer) {
s.RegisterService(&WalletUnlocker_ServiceDesc, srv)
}
func _WalletUnlocker_GenSeed_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GenSeedRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletUnlockerServer).GenSeed(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.WalletUnlocker/GenSeed",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletUnlockerServer).GenSeed(ctx, req.(*GenSeedRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletUnlocker_InitWallet_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(InitWalletRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletUnlockerServer).InitWallet(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.WalletUnlocker/InitWallet",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletUnlockerServer).InitWallet(ctx, req.(*InitWalletRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletUnlocker_UnlockWallet_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UnlockWalletRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletUnlockerServer).UnlockWallet(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.WalletUnlocker/UnlockWallet",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletUnlockerServer).UnlockWallet(ctx, req.(*UnlockWalletRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletUnlocker_ChangePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ChangePasswordRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletUnlockerServer).ChangePassword(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.WalletUnlocker/ChangePassword",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletUnlockerServer).ChangePassword(ctx, req.(*ChangePasswordRequest))
}
return interceptor(ctx, in, info, handler)
}
// WalletUnlocker_ServiceDesc is the grpc.ServiceDesc for WalletUnlocker service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var WalletUnlocker_ServiceDesc = grpc.ServiceDesc{
ServiceName: "lnrpc.WalletUnlocker",
HandlerType: (*WalletUnlockerServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GenSeed",
Handler: _WalletUnlocker_GenSeed_Handler,
},
{
MethodName: "InitWallet",
Handler: _WalletUnlocker_InitWallet_Handler,
},
{
MethodName: "UnlockWallet",
Handler: _WalletUnlocker_UnlockWallet_Handler,
},
{
MethodName: "ChangePassword",
Handler: _WalletUnlocker_ChangePassword_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "walletunlocker.proto",
}
package watchtowerrpc
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// Subsystem defines the logging code for this subsystem.
const Subsystem = "WRPC"
// log is a logger that is initialized with no output filters. This means the
// package will not perform any logging by default until the caller requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled by by
// default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info. This
// should be used in preference to SetLogWriter if the caller is also using
// btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v3.21.12
// source: watchtowerrpc/watchtower.proto
package watchtowerrpc
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type GetInfoRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *GetInfoRequest) Reset() {
*x = GetInfoRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_watchtowerrpc_watchtower_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetInfoRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetInfoRequest) ProtoMessage() {}
func (x *GetInfoRequest) ProtoReflect() protoreflect.Message {
mi := &file_watchtowerrpc_watchtower_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetInfoRequest.ProtoReflect.Descriptor instead.
func (*GetInfoRequest) Descriptor() ([]byte, []int) {
return file_watchtowerrpc_watchtower_proto_rawDescGZIP(), []int{0}
}
type GetInfoResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The public key of the watchtower.
Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"`
// The listening addresses of the watchtower.
Listeners []string `protobuf:"bytes,2,rep,name=listeners,proto3" json:"listeners,omitempty"`
// The URIs of the watchtower.
Uris []string `protobuf:"bytes,3,rep,name=uris,proto3" json:"uris,omitempty"`
}
func (x *GetInfoResponse) Reset() {
*x = GetInfoResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_watchtowerrpc_watchtower_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetInfoResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetInfoResponse) ProtoMessage() {}
func (x *GetInfoResponse) ProtoReflect() protoreflect.Message {
mi := &file_watchtowerrpc_watchtower_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetInfoResponse.ProtoReflect.Descriptor instead.
func (*GetInfoResponse) Descriptor() ([]byte, []int) {
return file_watchtowerrpc_watchtower_proto_rawDescGZIP(), []int{1}
}
func (x *GetInfoResponse) GetPubkey() []byte {
if x != nil {
return x.Pubkey
}
return nil
}
func (x *GetInfoResponse) GetListeners() []string {
if x != nil {
return x.Listeners
}
return nil
}
func (x *GetInfoResponse) GetUris() []string {
if x != nil {
return x.Uris
}
return nil
}
var File_watchtowerrpc_watchtower_proto protoreflect.FileDescriptor
var file_watchtowerrpc_watchtower_proto_rawDesc = []byte{
0x0a, 0x1e, 0x77, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2f,
0x77, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x12, 0x0d, 0x77, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x72, 0x70, 0x63, 0x22,
0x10, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x22, 0x5b, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09,
0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52,
0x09, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x72,
0x69, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x75, 0x72, 0x69, 0x73, 0x32, 0x56,
0x0a, 0x0a, 0x57, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x48, 0x0a, 0x07,
0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x2e, 0x77, 0x61, 0x74, 0x63, 0x68, 0x74,
0x6f, 0x77, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x77, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f,
0x77, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x35, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65,
0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f,
0x77, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_watchtowerrpc_watchtower_proto_rawDescOnce sync.Once
file_watchtowerrpc_watchtower_proto_rawDescData = file_watchtowerrpc_watchtower_proto_rawDesc
)
func file_watchtowerrpc_watchtower_proto_rawDescGZIP() []byte {
file_watchtowerrpc_watchtower_proto_rawDescOnce.Do(func() {
file_watchtowerrpc_watchtower_proto_rawDescData = protoimpl.X.CompressGZIP(file_watchtowerrpc_watchtower_proto_rawDescData)
})
return file_watchtowerrpc_watchtower_proto_rawDescData
}
var file_watchtowerrpc_watchtower_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_watchtowerrpc_watchtower_proto_goTypes = []interface{}{
(*GetInfoRequest)(nil), // 0: watchtowerrpc.GetInfoRequest
(*GetInfoResponse)(nil), // 1: watchtowerrpc.GetInfoResponse
}
var file_watchtowerrpc_watchtower_proto_depIdxs = []int32{
0, // 0: watchtowerrpc.Watchtower.GetInfo:input_type -> watchtowerrpc.GetInfoRequest
1, // 1: watchtowerrpc.Watchtower.GetInfo:output_type -> watchtowerrpc.GetInfoResponse
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_watchtowerrpc_watchtower_proto_init() }
func file_watchtowerrpc_watchtower_proto_init() {
if File_watchtowerrpc_watchtower_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_watchtowerrpc_watchtower_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetInfoRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_watchtowerrpc_watchtower_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetInfoResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_watchtowerrpc_watchtower_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_watchtowerrpc_watchtower_proto_goTypes,
DependencyIndexes: file_watchtowerrpc_watchtower_proto_depIdxs,
MessageInfos: file_watchtowerrpc_watchtower_proto_msgTypes,
}.Build()
File_watchtowerrpc_watchtower_proto = out.File
file_watchtowerrpc_watchtower_proto_rawDesc = nil
file_watchtowerrpc_watchtower_proto_goTypes = nil
file_watchtowerrpc_watchtower_proto_depIdxs = nil
}
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: watchtowerrpc/watchtower.proto
/*
Package watchtowerrpc is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package watchtowerrpc
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
func request_Watchtower_GetInfo_0(ctx context.Context, marshaler runtime.Marshaler, client WatchtowerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetInfoRequest
var metadata runtime.ServerMetadata
msg, err := client.GetInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Watchtower_GetInfo_0(ctx context.Context, marshaler runtime.Marshaler, server WatchtowerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetInfoRequest
var metadata runtime.ServerMetadata
msg, err := server.GetInfo(ctx, &protoReq)
return msg, metadata, err
}
// RegisterWatchtowerHandlerServer registers the http handlers for service Watchtower to "mux".
// UnaryRPC :call WatchtowerServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterWatchtowerHandlerFromEndpoint instead.
func RegisterWatchtowerHandlerServer(ctx context.Context, mux *runtime.ServeMux, server WatchtowerServer) error {
mux.Handle("GET", pattern_Watchtower_GetInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/watchtowerrpc.Watchtower/GetInfo", runtime.WithHTTPPathPattern("/v2/watchtower/server"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Watchtower_GetInfo_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Watchtower_GetInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterWatchtowerHandlerFromEndpoint is same as RegisterWatchtowerHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterWatchtowerHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterWatchtowerHandler(ctx, mux, conn)
}
// RegisterWatchtowerHandler registers the http handlers for service Watchtower to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterWatchtowerHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterWatchtowerHandlerClient(ctx, mux, NewWatchtowerClient(conn))
}
// RegisterWatchtowerHandlerClient registers the http handlers for service Watchtower
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "WatchtowerClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "WatchtowerClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "WatchtowerClient" to call the correct interceptors.
func RegisterWatchtowerHandlerClient(ctx context.Context, mux *runtime.ServeMux, client WatchtowerClient) error {
mux.Handle("GET", pattern_Watchtower_GetInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/watchtowerrpc.Watchtower/GetInfo", runtime.WithHTTPPathPattern("/v2/watchtower/server"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Watchtower_GetInfo_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Watchtower_GetInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_Watchtower_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "watchtower", "server"}, ""))
)
var (
forward_Watchtower_GetInfo_0 = runtime.ForwardResponseMessage
)
// Code generated by falafel 0.9.2. DO NOT EDIT.
// source: watchtower.proto
package watchtowerrpc
import (
"context"
gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protojson"
)
func RegisterWatchtowerJSONCallbacks(registry map[string]func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error))) {
marshaler := &gateway.JSONPb{
MarshalOptions: protojson.MarshalOptions{
UseProtoNames: true,
EmitUnpopulated: true,
},
}
registry["watchtowerrpc.Watchtower.GetInfo"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &GetInfoRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWatchtowerClient(conn)
resp, err := client.GetInfo(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package watchtowerrpc
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// WatchtowerClient is the client API for Watchtower service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type WatchtowerClient interface {
// lncli: `tower info`
// GetInfo returns general information concerning the companion watchtower
// including its public key and URIs where the server is currently
// listening for clients.
GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error)
}
type watchtowerClient struct {
cc grpc.ClientConnInterface
}
func NewWatchtowerClient(cc grpc.ClientConnInterface) WatchtowerClient {
return &watchtowerClient{cc}
}
func (c *watchtowerClient) GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error) {
out := new(GetInfoResponse)
err := c.cc.Invoke(ctx, "/watchtowerrpc.Watchtower/GetInfo", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// WatchtowerServer is the server API for Watchtower service.
// All implementations must embed UnimplementedWatchtowerServer
// for forward compatibility
type WatchtowerServer interface {
// lncli: `tower info`
// GetInfo returns general information concerning the companion watchtower
// including its public key and URIs where the server is currently
// listening for clients.
GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error)
mustEmbedUnimplementedWatchtowerServer()
}
// UnimplementedWatchtowerServer must be embedded to have forward compatible implementations.
type UnimplementedWatchtowerServer struct {
}
func (UnimplementedWatchtowerServer) GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetInfo not implemented")
}
func (UnimplementedWatchtowerServer) mustEmbedUnimplementedWatchtowerServer() {}
// UnsafeWatchtowerServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to WatchtowerServer will
// result in compilation errors.
type UnsafeWatchtowerServer interface {
mustEmbedUnimplementedWatchtowerServer()
}
func RegisterWatchtowerServer(s grpc.ServiceRegistrar, srv WatchtowerServer) {
s.RegisterService(&Watchtower_ServiceDesc, srv)
}
func _Watchtower_GetInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetInfoRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WatchtowerServer).GetInfo(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/watchtowerrpc.Watchtower/GetInfo",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WatchtowerServer).GetInfo(ctx, req.(*GetInfoRequest))
}
return interceptor(ctx, in, info, handler)
}
// Watchtower_ServiceDesc is the grpc.ServiceDesc for Watchtower service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Watchtower_ServiceDesc = grpc.ServiceDesc{
ServiceName: "watchtowerrpc.Watchtower",
HandlerType: (*WatchtowerServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetInfo",
Handler: _Watchtower_GetInfo_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "watchtowerrpc/watchtower.proto",
}
// The code in this file is a heavily modified version of
// https://github.com/tmc/grpc-websocket-proxy/
package lnrpc
import (
"bufio"
"io"
"net/http"
"net/textproto"
"regexp"
"strings"
"time"
"github.com/btcsuite/btclog/v2"
"github.com/gorilla/websocket"
"golang.org/x/net/context"
)
const (
// MethodOverrideParam is the GET query parameter that specifies what
// HTTP request method should be used for the forwarded REST request.
// This is necessary because the WebSocket API specifies that a
// handshake request must always be done through a GET request.
MethodOverrideParam = "method"
// HeaderWebSocketProtocol is the name of the WebSocket protocol
// exchange header field that we use to transport additional header
// fields.
HeaderWebSocketProtocol = "Sec-Websocket-Protocol"
// WebSocketProtocolDelimiter is the delimiter we use between the
// additional header field and its value. We use the plus symbol because
// the default delimiters aren't allowed in the protocol names.
WebSocketProtocolDelimiter = "+"
// PingContent is the content of the ping message we send out. This is
// an arbitrary non-empty message that has no deeper meaning but should
// be sent back by the client in the pong message.
PingContent = "are you there?"
// MaxWsMsgSize is the largest websockets message we'll attempt to
// decode in the gRPC <-> WS proxy. gRPC has a similar setting used
// elsewhere.
MaxWsMsgSize = 4 * 1024 * 1024
)
var (
// defaultHeadersToForward is a map of all HTTP header fields that are
// forwarded by default. The keys must be in the canonical MIME header
// format.
defaultHeadersToForward = map[string]bool{
"Origin": true,
"Referer": true,
"Grpc-Metadata-Macaroon": true,
}
// defaultProtocolsToAllow are additional header fields that we allow
// to be transported inside of the Sec-Websocket-Protocol field to be
// forwarded to the backend.
defaultProtocolsToAllow = map[string]bool{
"Grpc-Metadata-Macaroon": true,
}
// DefaultPingInterval is the default number of seconds to wait between
// sending ping requests.
DefaultPingInterval = time.Second * 30
// DefaultPongWait is the maximum duration we wait for a pong response
// to a ping we sent before we assume the connection died.
DefaultPongWait = time.Second * 5
)
// NewWebSocketProxy attempts to expose the underlying handler as a response-
// streaming WebSocket stream with newline-delimited JSON as the content
// encoding. If pingInterval is a non-zero duration, a ping message will be
// sent out periodically and a pong response message is expected from the
// client. The clientStreamingURIs parameter can hold a list of all patterns
// for URIs that are mapped to client-streaming RPC methods. We need to keep
// track of those to make sure we initialize the request body correctly for the
// underlying grpc-gateway library.
func NewWebSocketProxy(h http.Handler, logger btclog.Logger,
pingInterval, pongWait time.Duration,
clientStreamingURIs []*regexp.Regexp) http.Handler {
p := &WebsocketProxy{
backend: h,
logger: logger,
upgrader: &websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
},
clientStreamingURIs: clientStreamingURIs,
}
if pingInterval > 0 && pongWait > 0 {
p.pingInterval = pingInterval
p.pongWait = pongWait
}
return p
}
// WebsocketProxy provides websocket transport upgrade to compatible endpoints.
type WebsocketProxy struct {
backend http.Handler
logger btclog.Logger
upgrader *websocket.Upgrader
// clientStreamingURIs holds a list of all patterns for URIs that are
// mapped to client-streaming RPC methods. We need to keep track of
// those to make sure we initialize the request body correctly for the
// underlying grpc-gateway library.
clientStreamingURIs []*regexp.Regexp
pingInterval time.Duration
pongWait time.Duration
}
// pingPongEnabled returns true if a ping interval is set to enable sending and
// expecting regular ping/pong messages.
func (p *WebsocketProxy) pingPongEnabled() bool {
return p.pingInterval > 0 && p.pongWait > 0
}
// ServeHTTP handles the incoming HTTP request. If the request is an
// "upgradeable" WebSocket request (identified by header fields), then the
// WS proxy handles the request. Otherwise the request is passed directly to the
// underlying REST proxy.
func (p *WebsocketProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !websocket.IsWebSocketUpgrade(r) {
p.backend.ServeHTTP(w, r)
return
}
p.upgradeToWebSocketProxy(w, r)
}
// upgradeToWebSocketProxy upgrades the incoming request to a WebSocket, reads
// one incoming message then streams all responses until either the client or
// server quit the connection.
func (p *WebsocketProxy) upgradeToWebSocketProxy(w http.ResponseWriter,
r *http.Request) {
conn, err := p.upgrader.Upgrade(w, r, nil)
if err != nil {
p.logger.Errorf("error upgrading websocket:", err)
return
}
defer func() {
err := conn.Close()
if err != nil && !IsClosedConnError(err) {
p.logger.Errorf("WS: error closing upgraded conn: %v",
err)
}
}()
ctx, cancelFn := context.WithCancel(r.Context())
defer cancelFn()
requestForwarder := newRequestForwardingReader()
request, err := http.NewRequestWithContext(
ctx, r.Method, r.URL.String(), requestForwarder,
)
if err != nil {
p.logger.Errorf("WS: error preparing request:", err)
return
}
// Allow certain headers to be forwarded, either from source headers
// or the special Sec-Websocket-Protocol header field.
forwardHeaders(r.Header, request.Header)
// Also allow the target request method to be overwritten, as all
// WebSocket establishment calls MUST be GET requests.
if m := r.URL.Query().Get(MethodOverrideParam); m != "" {
request.Method = m
}
// Is this a call to a client-streaming RPC method?
clientStreaming := false
for _, pattern := range p.clientStreamingURIs {
if pattern.MatchString(r.URL.Path) {
clientStreaming = true
}
}
responseForwarder := newResponseForwardingWriter()
go func() {
<-ctx.Done()
responseForwarder.Close()
requestForwarder.CloseWriter()
}()
go func() {
defer cancelFn()
p.backend.ServeHTTP(responseForwarder, request)
}()
// Read loop: Take messages from websocket and write them to the payload
// channel. This needs to be its own goroutine because for non-client
// streaming RPCs, the requestForwarder.Write() in the second goroutine
// will block until the request has fully completed. But for the ping/
// pong handler to work, we need to have an active call to
// conn.ReadMessage() going on. So we make sure we have such an active
// call by starting a second read as soon as the first one has
// completed.
payloadChannel := make(chan []byte, 1)
go func() {
defer cancelFn()
defer close(payloadChannel)
for {
select {
case <-ctx.Done():
return
default:
}
_, payload, err := conn.ReadMessage()
if err != nil {
if IsClosedConnError(err) {
p.logger.Tracef("WS: socket "+
"closed: %v", err)
return
}
p.logger.Errorf("error reading message: %v",
err)
return
}
select {
case payloadChannel <- payload:
case <-ctx.Done():
return
}
}
}()
// Forward loop: Take messages from the incoming payload channel and
// write them to the http request.
go func() {
defer cancelFn()
for {
var payload []byte
select {
case <-ctx.Done():
return
case newPayload, more := <-payloadChannel:
if !more {
p.logger.Infof("WS: incoming payload " +
"chan closed")
return
}
payload = newPayload
}
_, err := requestForwarder.Write(payload)
if err != nil {
p.logger.Errorf("WS: error writing message "+
"to upstream http server: %v", err)
return
}
_, _ = requestForwarder.Write([]byte{'\n'})
// The grpc-gateway library uses a different request
// reader depending on whether it is a client streaming
// RPC or not. For a non-streaming request we need to
// close with EOF to signal the request was completed.
if !clientStreaming {
requestForwarder.CloseWriter()
}
}
}()
// Ping write loop: Send a ping message regularly if ping/pong is
// enabled.
if p.pingPongEnabled() {
// We'll send out our first ping in pingInterval. So the initial
// deadline is that interval plus the time we allow for a
// response to be sent.
initialDeadline := time.Now().Add(p.pingInterval + p.pongWait)
_ = conn.SetReadDeadline(initialDeadline)
// Whenever a pong message comes in, we extend the deadline
// until the next read is expected by the interval plus pong
// wait time. Since we can never _reach_ any of the deadlines,
// we also have to advance the deadline for the next expected
// write to happen, in case the next thing we actually write is
// the next ping.
conn.SetPongHandler(func(appData string) error {
nextDeadline := time.Now().Add(
p.pingInterval + p.pongWait,
)
_ = conn.SetReadDeadline(nextDeadline)
_ = conn.SetWriteDeadline(nextDeadline)
return nil
})
go func() {
ticker := time.NewTicker(p.pingInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
p.logger.Debug("WS: ping loop done")
return
case <-ticker.C:
// Writing the ping shouldn't take any
// longer than we'll wait for a response
// in the first place.
writeDeadline := time.Now().Add(
p.pongWait,
)
err := conn.WriteControl(
websocket.PingMessage,
[]byte(PingContent),
writeDeadline,
)
if err != nil {
p.logger.Warnf("WS: could not "+
"send ping message: %v",
err)
return
}
}
}
}()
}
// Write loop: Take messages from the response forwarder and write them
// to the WebSocket.
for responseForwarder.Scan() {
if len(responseForwarder.Bytes()) == 0 {
p.logger.Errorf("WS: empty scan: %v",
responseForwarder.Err())
continue
}
err := conn.WriteMessage(
websocket.TextMessage, responseForwarder.Bytes(),
)
if err != nil {
p.logger.Errorf("WS: error writing message: %v", err)
return
}
}
if err := responseForwarder.Err(); err != nil && !IsClosedConnError(err) {
p.logger.Errorf("WS: scanner err: %v", err)
}
}
// forwardHeaders forwards certain allowed header fields from the source request
// to the target request. Because browsers are limited in what header fields
// they can send on the WebSocket setup call, we also allow additional fields to
// be transported in the special Sec-Websocket-Protocol field.
func forwardHeaders(source, target http.Header) {
// Forward allowed header fields directly.
for header := range source {
headerName := textproto.CanonicalMIMEHeaderKey(header)
forward, ok := defaultHeadersToForward[headerName]
if ok && forward {
target.Set(headerName, source.Get(header))
}
}
// Browser aren't allowed to set custom header fields on WebSocket
// requests. We need to allow them to submit the macaroon as a WS
// protocol, which is the only allowed header. Set any "protocols" we
// declare valid as header fields on the forwarded request.
protocol := source.Get(HeaderWebSocketProtocol)
for key := range defaultProtocolsToAllow {
if strings.HasPrefix(protocol, key) {
// The format is "<protocol name>+<value>". We know the
// protocol string starts with the name so we only need
// to set the value.
values := strings.Split(
protocol, WebSocketProtocolDelimiter,
)
target.Set(key, values[1])
}
}
}
// newRequestForwardingReader creates a new request forwarding pipe.
func newRequestForwardingReader() *requestForwardingReader {
r, w := io.Pipe()
return &requestForwardingReader{
Reader: r,
Writer: w,
pipeR: r,
pipeW: w,
}
}
// requestForwardingReader is a wrapper around io.Pipe that embeds both the
// io.Reader and io.Writer interface and can be closed.
type requestForwardingReader struct {
io.Reader
io.Writer
pipeR *io.PipeReader
pipeW *io.PipeWriter
}
// CloseWriter closes the underlying pipe writer.
func (r *requestForwardingReader) CloseWriter() {
_ = r.pipeW.CloseWithError(io.EOF)
}
// newResponseForwardingWriter creates a new http.ResponseWriter that intercepts
// what's written to it and presents it through a bufio.Scanner interface.
func newResponseForwardingWriter() *responseForwardingWriter {
r, w := io.Pipe()
scanner := bufio.NewScanner(r)
// We pass in a custom buffer for the bufio scanner to use. We'll keep
// with a normal 64KB buffer, but allow a larger max message size,
// which may cause buffer expansion when needed.
buf := make([]byte, 0, bufio.MaxScanTokenSize)
scanner.Buffer(buf, MaxWsMsgSize)
return &responseForwardingWriter{
Writer: w,
Scanner: scanner,
pipeR: r,
pipeW: w,
header: http.Header{},
closed: make(chan bool, 1),
}
}
// responseForwardingWriter is a type that implements the http.ResponseWriter
// interface but internally forwards what's written to the writer through a pipe
// so it can easily be read again through the bufio.Scanner interface.
type responseForwardingWriter struct {
io.Writer
*bufio.Scanner
pipeR *io.PipeReader
pipeW *io.PipeWriter
header http.Header
code int
closed chan bool
}
// Write writes the given bytes to the internal pipe.
//
// NOTE: This is part of the http.ResponseWriter interface.
func (w *responseForwardingWriter) Write(b []byte) (int, error) {
return w.Writer.Write(b)
}
// Header returns the HTTP header fields intercepted so far.
//
// NOTE: This is part of the http.ResponseWriter interface.
func (w *responseForwardingWriter) Header() http.Header {
return w.header
}
// WriteHeader indicates that the header part of the response is now finished
// and sets the response code.
//
// NOTE: This is part of the http.ResponseWriter interface.
func (w *responseForwardingWriter) WriteHeader(code int) {
w.code = code
}
// CloseNotify returns a channel that indicates if a connection was closed.
//
// NOTE: This is part of the http.CloseNotifier interface.
func (w *responseForwardingWriter) CloseNotify() <-chan bool {
return w.closed
}
// Flush empties all buffers. We implement this to indicate to our backend that
// we support flushing our content. There is no actual implementation because
// all writes happen immediately, there is no internal buffering.
//
// NOTE: This is part of the http.Flusher interface.
func (w *responseForwardingWriter) Flush() {}
func (w *responseForwardingWriter) Close() {
_ = w.pipeR.CloseWithError(io.EOF)
_ = w.pipeW.CloseWithError(io.EOF)
w.closed <- true
}
// IsClosedConnError is a helper function that returns true if the given error
// is an error indicating we are using a closed connection.
func IsClosedConnError(err error) bool {
if err == nil {
return false
}
if err == http.ErrServerClosed {
return true
}
str := err.Error()
if strings.Contains(str, "use of closed network connection") {
return true
}
if strings.Contains(str, "closed pipe") {
return true
}
if strings.Contains(str, "broken pipe") {
return true
}
if strings.Contains(str, "connection reset by peer") {
return true
}
return websocket.IsCloseError(
err, websocket.CloseNormalClosure, websocket.CloseGoingAway,
)
}
package wtclientrpc
import (
"errors"
"fmt"
"github.com/lightningnetwork/lnd/lnrpc"
)
// createNewSubServer is a helper method that will create the new sub server
// given the main config dispatcher method. If we're unable to find the config
// that is meant for us in the config dispatcher, then we'll exit with an
// error.
func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) (
*WatchtowerClient, lnrpc.MacaroonPerms, error) {
// We'll attempt to look up the config that we expect, according to our
// subServerName name. If we can't find this, then we'll exit with an
// error, as we're unable to properly initialize ourselves without this
// config.
subServerConf, ok := configRegistry.FetchConfig(subServerName)
if !ok {
return nil, nil, fmt.Errorf("unable to find config for "+
"subserver type %s", subServerName)
}
// Now that we've found an object mapping to our service name, we'll
// ensure that it's the type we need.
config, ok := subServerConf.(*Config)
if !ok {
return nil, nil, fmt.Errorf("wrong type of config for "+
"subserver %s, expected %T got %T", subServerName,
&Config{}, subServerConf)
}
// Before we try to make the new service instance, we'll perform
// some sanity checks on the arguments to ensure that they're usable.
switch {
case config.Resolver == nil:
return nil, nil, errors.New("a lncfg.TCPResolver is required")
}
return New(config)
}
func init() {
subServer := &lnrpc.SubServerDriver{
SubServerName: subServerName,
NewGrpcHandler: func() lnrpc.GrpcHandler {
return &ServerShell{}
},
}
// If the build tag is active, then we'll register ourselves as a
// sub-RPC server within the global lnrpc package namespace.
if err := lnrpc.RegisterSubServer(subServer); err != nil {
panic(fmt.Sprintf("failed to register sub server driver "+
"'%s': %v", subServerName, err))
}
}
// Code generated by falafel 0.9.2. DO NOT EDIT.
// source: wtclient.proto
package wtclientrpc
import (
"context"
gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protojson"
)
func RegisterWatchtowerClientJSONCallbacks(registry map[string]func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error))) {
marshaler := &gateway.JSONPb{
MarshalOptions: protojson.MarshalOptions{
UseProtoNames: true,
EmitUnpopulated: true,
},
}
registry["wtclientrpc.WatchtowerClient.AddTower"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &AddTowerRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWatchtowerClientClient(conn)
resp, err := client.AddTower(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["wtclientrpc.WatchtowerClient.RemoveTower"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &RemoveTowerRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWatchtowerClientClient(conn)
resp, err := client.RemoveTower(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["wtclientrpc.WatchtowerClient.DeactivateTower"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &DeactivateTowerRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWatchtowerClientClient(conn)
resp, err := client.DeactivateTower(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["wtclientrpc.WatchtowerClient.TerminateSession"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &TerminateSessionRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWatchtowerClientClient(conn)
resp, err := client.TerminateSession(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["wtclientrpc.WatchtowerClient.ListTowers"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ListTowersRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWatchtowerClientClient(conn)
resp, err := client.ListTowers(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["wtclientrpc.WatchtowerClient.GetTowerInfo"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &GetTowerInfoRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWatchtowerClientClient(conn)
resp, err := client.GetTowerInfo(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["wtclientrpc.WatchtowerClient.Stats"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &StatsRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWatchtowerClientClient(conn)
resp, err := client.Stats(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["wtclientrpc.WatchtowerClient.Policy"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &PolicyRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWatchtowerClientClient(conn)
resp, err := client.Policy(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}
package wtclientrpc
import (
"bytes"
"context"
"encoding/binary"
"errors"
"fmt"
"net"
"sort"
"strconv"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/watchtower"
"github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/wtclient"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
"google.golang.org/grpc"
"gopkg.in/macaroon-bakery.v2/bakery"
)
const (
// subServerName is the name of the sub rpc server. We'll use this name
// to register ourselves, and we also require that the main
// SubServerConfigDispatcher instance recognizes it as the name of our
// RPC service.
subServerName = "WatchtowerClientRPC"
)
var (
// macPermissions maps RPC calls to the permissions they require.
//
// TODO(wilmer): create tower macaroon?
macPermissions = map[string][]bakery.Op{
"/wtclientrpc.WatchtowerClient/AddTower": {{
Entity: "offchain",
Action: "write",
}},
"/wtclientrpc.WatchtowerClient/RemoveTower": {{
Entity: "offchain",
Action: "write",
}},
"/wtclientrpc.WatchtowerClient/DeactivateTower": {{
Entity: "offchain",
Action: "write",
}},
"/wtclientrpc.WatchtowerClient/TerminateSession": {{
Entity: "offchain",
Action: "write",
}},
"/wtclientrpc.WatchtowerClient/ListTowers": {{
Entity: "offchain",
Action: "read",
}},
"/wtclientrpc.WatchtowerClient/GetTowerInfo": {{
Entity: "offchain",
Action: "read",
}},
"/wtclientrpc.WatchtowerClient/Stats": {{
Entity: "offchain",
Action: "read",
}},
"/wtclientrpc.WatchtowerClient/Policy": {{
Entity: "offchain",
Action: "read",
}},
}
// ErrWtclientNotActive signals that RPC calls cannot be processed
// because the watchtower client is not active.
ErrWtclientNotActive = errors.New("watchtower client not active")
)
// ServerShell is a shell struct holding a reference to the actual sub-server.
// It is used to register the gRPC sub-server with the root server before we
// have the necessary dependencies to populate the actual sub-server.
type ServerShell struct {
WatchtowerClientServer
}
// WatchtowerClient is the RPC server we'll use to interact with the backing
// active watchtower client.
//
// TODO(wilmer): better name?
type WatchtowerClient struct {
// Required by the grpc-gateway/v2 library for forward compatibility.
UnimplementedWatchtowerClientServer
cfg Config
}
// A compile time check to ensure that WatchtowerClient fully implements the
// WatchtowerClientWatchtowerClient gRPC service.
var _ WatchtowerClientServer = (*WatchtowerClient)(nil)
// New returns a new instance of the wtclientrpc WatchtowerClient sub-server.
// We also return the set of permissions for the macaroons that we may create
// within this method. If the macaroons we need aren't found in the filepath,
// then we'll create them on start up. If we're unable to locate, or create the
// macaroons we need, then we'll return with an error.
func New(cfg *Config) (*WatchtowerClient, lnrpc.MacaroonPerms, error) {
return &WatchtowerClient{cfg: *cfg}, macPermissions, nil
}
// Start launches any helper goroutines required for the WatchtowerClient to
// function.
//
// NOTE: This is part of the lnrpc.SubWatchtowerClient interface.
func (c *WatchtowerClient) Start() error {
return nil
}
// Stop signals any active goroutines for a graceful closure.
//
// NOTE: This is part of the lnrpc.SubServer interface.
func (c *WatchtowerClient) Stop() error {
return nil
}
// Name returns a unique string representation of the sub-server. This can be
// used to identify the sub-server and also de-duplicate them.
//
// NOTE: This is part of the lnrpc.SubServer interface.
func (c *WatchtowerClient) Name() string {
return subServerName
}
// RegisterWithRootServer will be called by the root gRPC server to direct a sub
// RPC server to register itself with the main gRPC root server. Until this is
// called, each sub-server won't be able to have requests routed towards it.
//
// NOTE: This is part of the lnrpc.GrpcHandler interface.
func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error {
// We make sure that we register it with the main gRPC server to ensure
// all our methods are routed properly.
RegisterWatchtowerClientServer(grpcServer, r)
return nil
}
// RegisterWithRestServer will be called by the root REST mux to direct a sub
// RPC server to register itself with the main REST mux server. Until this is
// called, each sub-server won't be able to have requests routed towards it.
//
// NOTE: This is part of the lnrpc.GrpcHandler interface.
func (r *ServerShell) RegisterWithRestServer(ctx context.Context,
mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error {
// We make sure that we register it with the main REST server to ensure
// all our methods are routed properly.
err := RegisterWatchtowerClientHandlerFromEndpoint(ctx, mux, dest, opts)
if err != nil {
return err
}
return nil
}
// CreateSubServer populates the subserver's dependencies using the passed
// SubServerConfigDispatcher. This method should fully initialize the
// sub-server instance, making it ready for action. It returns the macaroon
// permissions that the sub-server wishes to pass on to the root server for all
// methods routed towards it.
//
// NOTE: This is part of the lnrpc.GrpcHandler interface.
func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) (
lnrpc.SubServer, lnrpc.MacaroonPerms, error) {
subServer, macPermissions, err := createNewSubServer(configRegistry)
if err != nil {
return nil, nil, err
}
r.WatchtowerClientServer = subServer
return subServer, macPermissions, nil
}
// isActive returns nil if the watchtower client is initialized so that we can
// process RPC requests.
func (c *WatchtowerClient) isActive() error {
if c.cfg.Active {
return nil
}
return ErrWtclientNotActive
}
// AddTower adds a new watchtower reachable at the given address and considers
// it for new sessions. If the watchtower already exists, then any new addresses
// included will be considered when dialing it for session negotiations and
// backups.
func (c *WatchtowerClient) AddTower(ctx context.Context,
req *AddTowerRequest) (*AddTowerResponse, error) {
if err := c.isActive(); err != nil {
return nil, err
}
pubKey, err := btcec.ParsePubKey(req.Pubkey)
if err != nil {
return nil, err
}
addr, err := lncfg.ParseAddressString(
req.Address, strconv.Itoa(watchtower.DefaultPeerPort),
c.cfg.Resolver,
)
if err != nil {
return nil, fmt.Errorf("invalid address %v: %w", req.Address,
err)
}
towerAddr := &lnwire.NetAddress{
IdentityKey: pubKey,
Address: addr,
}
if err := c.cfg.ClientMgr.AddTower(towerAddr); err != nil {
return nil, err
}
return &AddTowerResponse{}, nil
}
// RemoveTower removes a watchtower from being considered for future session
// negotiations and from being used for any subsequent backups until it's added
// again. If an address is provided, then this RPC only serves as a way of
// removing the address from the watchtower instead.
func (c *WatchtowerClient) RemoveTower(ctx context.Context,
req *RemoveTowerRequest) (*RemoveTowerResponse, error) {
if err := c.isActive(); err != nil {
return nil, err
}
pubKey, err := btcec.ParsePubKey(req.Pubkey)
if err != nil {
return nil, err
}
var addr net.Addr
if req.Address != "" {
addr, err = lncfg.ParseAddressString(
req.Address, strconv.Itoa(watchtower.DefaultPeerPort),
c.cfg.Resolver,
)
if err != nil {
return nil, fmt.Errorf("unable to parse tower "+
"address %v: %v", req.Address, err)
}
}
err = c.cfg.ClientMgr.RemoveTower(pubKey, addr)
if err != nil {
return nil, err
}
return &RemoveTowerResponse{}, nil
}
// DeactivateTower sets the given tower's status to inactive so that it is not
// considered for session negotiation. Its sessions will also not be used while
// the tower is inactive.
func (c *WatchtowerClient) DeactivateTower(_ context.Context,
req *DeactivateTowerRequest) (*DeactivateTowerResponse, error) {
if err := c.isActive(); err != nil {
return nil, err
}
pubKey, err := btcec.ParsePubKey(req.Pubkey)
if err != nil {
return nil, err
}
err = c.cfg.ClientMgr.DeactivateTower(pubKey)
if err != nil {
return nil, err
}
return &DeactivateTowerResponse{
Status: fmt.Sprintf("Successful deactivation of tower: %x",
req.Pubkey),
}, nil
}
// TerminateSession terminates the given session and marks it as terminal so
// that it is never used again.
func (c *WatchtowerClient) TerminateSession(_ context.Context,
req *TerminateSessionRequest) (*TerminateSessionResponse, error) {
if err := c.isActive(); err != nil {
return nil, err
}
pubKey, err := btcec.ParsePubKey(req.SessionId)
if err != nil {
return nil, err
}
sessionID := wtdb.NewSessionIDFromPubKey(pubKey)
err = c.cfg.ClientMgr.TerminateSession(sessionID)
if err != nil {
return nil, err
}
return &TerminateSessionResponse{
Status: fmt.Sprintf("Successful termination of session: %s",
sessionID),
}, nil
}
// ListTowers returns the list of watchtowers registered with the client.
func (c *WatchtowerClient) ListTowers(ctx context.Context,
req *ListTowersRequest) (*ListTowersResponse, error) {
if err := c.isActive(); err != nil {
return nil, err
}
opts, ackCounts, committedUpdateCounts := constructFunctionalOptions(
req.IncludeSessions, req.ExcludeExhaustedSessions,
)
towersPerBlobType, err := c.cfg.ClientMgr.RegisteredTowers(opts...)
if err != nil {
return nil, err
}
// Collect all the legacy client towers. If it has any of the same
// towers that the anchors client has, then just add the session info
// for the legacy client to the existing tower.
rpcTowers := make(map[wtdb.TowerID]*Tower)
for blobType, towers := range towersPerBlobType {
policyType, err := blobTypeToPolicyType(blobType)
if err != nil {
return nil, err
}
for _, tower := range towers {
rpcTower := marshallTower(
tower, policyType, req.IncludeSessions,
ackCounts, committedUpdateCounts,
)
t, ok := rpcTowers[tower.ID]
if !ok {
rpcTowers[tower.ID] = rpcTower
continue
}
t.SessionInfo = append(
t.SessionInfo, rpcTower.SessionInfo...,
)
t.Sessions = append(
t.Sessions, rpcTower.Sessions...,
)
}
}
towers := make([]*Tower, 0, len(rpcTowers))
for _, tower := range rpcTowers {
towers = append(towers, tower)
}
return &ListTowersResponse{Towers: towers}, nil
}
// GetTowerInfo retrieves information for a registered watchtower.
func (c *WatchtowerClient) GetTowerInfo(ctx context.Context,
req *GetTowerInfoRequest) (*Tower, error) {
if err := c.isActive(); err != nil {
return nil, err
}
pubKey, err := btcec.ParsePubKey(req.Pubkey)
if err != nil {
return nil, err
}
opts, ackCounts, committedUpdateCounts := constructFunctionalOptions(
req.IncludeSessions, req.ExcludeExhaustedSessions,
)
towersPerBlobType, err := c.cfg.ClientMgr.LookupTower(pubKey, opts...)
if err != nil {
return nil, err
}
var resTower *Tower
for blobType, tower := range towersPerBlobType {
policyType, err := blobTypeToPolicyType(blobType)
if err != nil {
return nil, err
}
rpcTower := marshallTower(
tower, policyType, req.IncludeSessions,
ackCounts, committedUpdateCounts,
)
if resTower == nil {
resTower = rpcTower
continue
}
if !bytes.Equal(rpcTower.Pubkey, resTower.Pubkey) {
return nil, fmt.Errorf("tower clients returned " +
"inconsistent results for the given tower")
}
resTower.SessionInfo = append(
resTower.SessionInfo, rpcTower.SessionInfo...,
)
resTower.Sessions = append(
resTower.Sessions, rpcTower.Sessions...,
)
}
return resTower, nil
}
// constructFunctionalOptions is a helper function that constructs a list of
// functional options to be used when fetching a tower from the DB. It also
// returns a map of acked-update counts and one for un-acked-update counts that
// will be populated once the db call has been made.
func constructFunctionalOptions(includeSessions,
excludeExhaustedSessions bool) ([]wtdb.ClientSessionListOption,
map[wtdb.SessionID]uint16, map[wtdb.SessionID]uint16) {
var (
opts []wtdb.ClientSessionListOption
committedUpdateCounts = make(map[wtdb.SessionID]uint16)
ackCounts = make(map[wtdb.SessionID]uint16)
)
if !includeSessions {
return opts, ackCounts, committedUpdateCounts
}
perNumRogueUpdates := func(s *wtdb.ClientSession, numUpdates uint16) {
ackCounts[s.ID] += numUpdates
}
perNumAckedUpdates := func(s *wtdb.ClientSession, id lnwire.ChannelID,
numUpdates uint16) {
ackCounts[s.ID] += numUpdates
}
perCommittedUpdate := func(s *wtdb.ClientSession,
u *wtdb.CommittedUpdate) {
committedUpdateCounts[s.ID]++
}
opts = []wtdb.ClientSessionListOption{
wtdb.WithPerNumAckedUpdates(perNumAckedUpdates),
wtdb.WithPerCommittedUpdate(perCommittedUpdate),
wtdb.WithPerRogueUpdateCount(perNumRogueUpdates),
}
if excludeExhaustedSessions {
opts = append(opts, wtdb.WithPostEvalFilterFn(
wtclient.ExhaustedSessionFilter(),
))
}
return opts, ackCounts, committedUpdateCounts
}
// Stats returns the in-memory statistics of the client since startup.
func (c *WatchtowerClient) Stats(_ context.Context,
_ *StatsRequest) (*StatsResponse, error) {
if err := c.isActive(); err != nil {
return nil, err
}
stats := c.cfg.ClientMgr.Stats()
return &StatsResponse{
NumBackups: uint32(stats.NumTasksAccepted),
NumFailedBackups: uint32(stats.NumTasksIneligible),
NumPendingBackups: uint32(stats.NumTasksPending),
NumSessionsAcquired: uint32(stats.NumSessionsAcquired),
NumSessionsExhausted: uint32(stats.NumSessionsExhausted),
}, nil
}
// Policy returns the active watchtower client policy configuration.
func (c *WatchtowerClient) Policy(ctx context.Context,
req *PolicyRequest) (*PolicyResponse, error) {
if err := c.isActive(); err != nil {
return nil, err
}
blobType, err := policyTypeToBlobType(req.PolicyType)
if err != nil {
return nil, err
}
policy, err := c.cfg.ClientMgr.Policy(blobType)
if err != nil {
return nil, err
}
return &PolicyResponse{
MaxUpdates: uint32(policy.MaxUpdates),
SweepSatPerVbyte: uint32(policy.SweepFeeRate.FeePerVByte()),
// Deprecated field.
SweepSatPerByte: uint32(policy.SweepFeeRate.FeePerVByte()),
}, nil
}
// marshallTower converts a client registered watchtower into its corresponding
// RPC type.
func marshallTower(tower *wtclient.RegisteredTower, policyType PolicyType,
includeSessions bool, ackCounts map[wtdb.SessionID]uint16,
pendingCounts map[wtdb.SessionID]uint16) *Tower {
rpcAddrs := make([]string, 0, len(tower.Addresses))
for _, addr := range tower.Addresses {
rpcAddrs = append(rpcAddrs, addr.String())
}
var rpcSessions []*TowerSession
if includeSessions {
// To ensure that the output order is deterministic for a given
// set of sessions, we put the sessions into a slice and order
// them based on session ID.
sessions := make([]*wtdb.ClientSession, 0, len(tower.Sessions))
for _, session := range tower.Sessions {
sessions = append(sessions, session)
}
sort.Slice(sessions, func(i, j int) bool {
id1 := sessions[i].ID
id2 := sessions[j].ID
return binary.BigEndian.Uint64(id1[:]) <
binary.BigEndian.Uint64(id2[:])
})
rpcSessions = make([]*TowerSession, 0, len(tower.Sessions))
for _, session := range sessions {
satPerVByte := session.Policy.SweepFeeRate.FeePerVByte()
rpcSessions = append(rpcSessions, &TowerSession{
Id: session.ID[:],
NumBackups: uint32(ackCounts[session.ID]),
NumPendingBackups: uint32(pendingCounts[session.ID]),
MaxBackups: uint32(session.Policy.MaxUpdates),
SweepSatPerVbyte: uint32(satPerVByte),
// Deprecated field.
SweepSatPerByte: uint32(satPerVByte),
})
}
}
rpcTower := &Tower{
Pubkey: tower.IdentityKey.SerializeCompressed(),
Addresses: rpcAddrs,
SessionInfo: []*TowerSessionInfo{{
PolicyType: policyType,
ActiveSessionCandidate: tower.ActiveSessionCandidate,
NumSessions: uint32(len(tower.Sessions)),
Sessions: rpcSessions,
}},
// The below fields are populated for backwards compatibility
// but will be removed in a future commit when the proto fields
// are removed.
ActiveSessionCandidate: tower.ActiveSessionCandidate,
NumSessions: uint32(len(tower.Sessions)),
Sessions: rpcSessions,
}
return rpcTower
}
func blobTypeToPolicyType(t blob.Type) (PolicyType, error) {
switch t {
case blob.TypeAltruistTaprootCommit:
return PolicyType_TAPROOT, nil
case blob.TypeAltruistAnchorCommit:
return PolicyType_ANCHOR, nil
case blob.TypeAltruistCommit:
return PolicyType_LEGACY, nil
default:
return 0, fmt.Errorf("unknown blob type: %s", t)
}
}
func policyTypeToBlobType(t PolicyType) (blob.Type, error) {
switch t {
case PolicyType_TAPROOT:
return blob.TypeAltruistTaprootCommit, nil
case PolicyType_ANCHOR:
return blob.TypeAltruistAnchorCommit, nil
case PolicyType_LEGACY:
return blob.TypeAltruistCommit, nil
default:
return 0, fmt.Errorf("unknown policy type: %s", t)
}
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v3.21.12
// source: wtclientrpc/wtclient.proto
package wtclientrpc
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type PolicyType int32
const (
// Selects the policy from the legacy tower client.
PolicyType_LEGACY PolicyType = 0
// Selects the policy from the anchor tower client.
PolicyType_ANCHOR PolicyType = 1
// Selects the policy from the taproot tower client.
PolicyType_TAPROOT PolicyType = 2
)
// Enum value maps for PolicyType.
var (
PolicyType_name = map[int32]string{
0: "LEGACY",
1: "ANCHOR",
2: "TAPROOT",
}
PolicyType_value = map[string]int32{
"LEGACY": 0,
"ANCHOR": 1,
"TAPROOT": 2,
}
)
func (x PolicyType) Enum() *PolicyType {
p := new(PolicyType)
*p = x
return p
}
func (x PolicyType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (PolicyType) Descriptor() protoreflect.EnumDescriptor {
return file_wtclientrpc_wtclient_proto_enumTypes[0].Descriptor()
}
func (PolicyType) Type() protoreflect.EnumType {
return &file_wtclientrpc_wtclient_proto_enumTypes[0]
}
func (x PolicyType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use PolicyType.Descriptor instead.
func (PolicyType) EnumDescriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{0}
}
type AddTowerRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The identifying public key of the watchtower to add.
Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"`
// A network address the watchtower is reachable over.
Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"`
}
func (x *AddTowerRequest) Reset() {
*x = AddTowerRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AddTowerRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AddTowerRequest) ProtoMessage() {}
func (x *AddTowerRequest) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AddTowerRequest.ProtoReflect.Descriptor instead.
func (*AddTowerRequest) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{0}
}
func (x *AddTowerRequest) GetPubkey() []byte {
if x != nil {
return x.Pubkey
}
return nil
}
func (x *AddTowerRequest) GetAddress() string {
if x != nil {
return x.Address
}
return ""
}
type AddTowerResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *AddTowerResponse) Reset() {
*x = AddTowerResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AddTowerResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AddTowerResponse) ProtoMessage() {}
func (x *AddTowerResponse) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AddTowerResponse.ProtoReflect.Descriptor instead.
func (*AddTowerResponse) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{1}
}
type RemoveTowerRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The identifying public key of the watchtower to remove.
Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"`
// If set, then the record for this address will be removed, indicating that is
// is stale. Otherwise, the watchtower will no longer be used for future
// session negotiations and backups.
Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"`
}
func (x *RemoveTowerRequest) Reset() {
*x = RemoveTowerRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RemoveTowerRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RemoveTowerRequest) ProtoMessage() {}
func (x *RemoveTowerRequest) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RemoveTowerRequest.ProtoReflect.Descriptor instead.
func (*RemoveTowerRequest) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{2}
}
func (x *RemoveTowerRequest) GetPubkey() []byte {
if x != nil {
return x.Pubkey
}
return nil
}
func (x *RemoveTowerRequest) GetAddress() string {
if x != nil {
return x.Address
}
return ""
}
type RemoveTowerResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *RemoveTowerResponse) Reset() {
*x = RemoveTowerResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RemoveTowerResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RemoveTowerResponse) ProtoMessage() {}
func (x *RemoveTowerResponse) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RemoveTowerResponse.ProtoReflect.Descriptor instead.
func (*RemoveTowerResponse) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{3}
}
type DeactivateTowerRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The identifying public key of the watchtower to deactivate.
Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"`
}
func (x *DeactivateTowerRequest) Reset() {
*x = DeactivateTowerRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DeactivateTowerRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeactivateTowerRequest) ProtoMessage() {}
func (x *DeactivateTowerRequest) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeactivateTowerRequest.ProtoReflect.Descriptor instead.
func (*DeactivateTowerRequest) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{4}
}
func (x *DeactivateTowerRequest) GetPubkey() []byte {
if x != nil {
return x.Pubkey
}
return nil
}
type DeactivateTowerResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A string describing the action that took place.
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
}
func (x *DeactivateTowerResponse) Reset() {
*x = DeactivateTowerResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DeactivateTowerResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeactivateTowerResponse) ProtoMessage() {}
func (x *DeactivateTowerResponse) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeactivateTowerResponse.ProtoReflect.Descriptor instead.
func (*DeactivateTowerResponse) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{5}
}
func (x *DeactivateTowerResponse) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
type TerminateSessionRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The ID of the session that should be terminated.
SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
}
func (x *TerminateSessionRequest) Reset() {
*x = TerminateSessionRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TerminateSessionRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TerminateSessionRequest) ProtoMessage() {}
func (x *TerminateSessionRequest) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TerminateSessionRequest.ProtoReflect.Descriptor instead.
func (*TerminateSessionRequest) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{6}
}
func (x *TerminateSessionRequest) GetSessionId() []byte {
if x != nil {
return x.SessionId
}
return nil
}
type TerminateSessionResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// A string describing the action that took place.
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
}
func (x *TerminateSessionResponse) Reset() {
*x = TerminateSessionResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TerminateSessionResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TerminateSessionResponse) ProtoMessage() {}
func (x *TerminateSessionResponse) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TerminateSessionResponse.ProtoReflect.Descriptor instead.
func (*TerminateSessionResponse) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{7}
}
func (x *TerminateSessionResponse) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
type GetTowerInfoRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The identifying public key of the watchtower to retrieve information for.
Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"`
// Whether we should include sessions with the watchtower in the response.
IncludeSessions bool `protobuf:"varint,2,opt,name=include_sessions,json=includeSessions,proto3" json:"include_sessions,omitempty"`
// Whether to exclude exhausted sessions in the response info. This option
// is only meaningful if include_sessions is true.
ExcludeExhaustedSessions bool `protobuf:"varint,3,opt,name=exclude_exhausted_sessions,json=excludeExhaustedSessions,proto3" json:"exclude_exhausted_sessions,omitempty"`
}
func (x *GetTowerInfoRequest) Reset() {
*x = GetTowerInfoRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetTowerInfoRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetTowerInfoRequest) ProtoMessage() {}
func (x *GetTowerInfoRequest) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetTowerInfoRequest.ProtoReflect.Descriptor instead.
func (*GetTowerInfoRequest) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{8}
}
func (x *GetTowerInfoRequest) GetPubkey() []byte {
if x != nil {
return x.Pubkey
}
return nil
}
func (x *GetTowerInfoRequest) GetIncludeSessions() bool {
if x != nil {
return x.IncludeSessions
}
return false
}
func (x *GetTowerInfoRequest) GetExcludeExhaustedSessions() bool {
if x != nil {
return x.ExcludeExhaustedSessions
}
return false
}
type TowerSession struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The total number of successful backups that have been made to the
// watchtower session.
NumBackups uint32 `protobuf:"varint,1,opt,name=num_backups,json=numBackups,proto3" json:"num_backups,omitempty"`
// The total number of backups in the session that are currently pending to be
// acknowledged by the watchtower.
NumPendingBackups uint32 `protobuf:"varint,2,opt,name=num_pending_backups,json=numPendingBackups,proto3" json:"num_pending_backups,omitempty"`
// The maximum number of backups allowed by the watchtower session.
MaxBackups uint32 `protobuf:"varint,3,opt,name=max_backups,json=maxBackups,proto3" json:"max_backups,omitempty"`
// Deprecated, use sweep_sat_per_vbyte.
// The fee rate, in satoshis per vbyte, that will be used by the watchtower for
// the justice transaction in the event of a channel breach.
//
// Deprecated: Marked as deprecated in wtclientrpc/wtclient.proto.
SweepSatPerByte uint32 `protobuf:"varint,4,opt,name=sweep_sat_per_byte,json=sweepSatPerByte,proto3" json:"sweep_sat_per_byte,omitempty"`
// The fee rate, in satoshis per vbyte, that will be used by the watchtower for
// the justice transaction in the event of a channel breach.
SweepSatPerVbyte uint32 `protobuf:"varint,5,opt,name=sweep_sat_per_vbyte,json=sweepSatPerVbyte,proto3" json:"sweep_sat_per_vbyte,omitempty"`
// The ID of the session.
Id []byte `protobuf:"bytes,6,opt,name=id,proto3" json:"id,omitempty"`
}
func (x *TowerSession) Reset() {
*x = TowerSession{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TowerSession) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TowerSession) ProtoMessage() {}
func (x *TowerSession) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TowerSession.ProtoReflect.Descriptor instead.
func (*TowerSession) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{9}
}
func (x *TowerSession) GetNumBackups() uint32 {
if x != nil {
return x.NumBackups
}
return 0
}
func (x *TowerSession) GetNumPendingBackups() uint32 {
if x != nil {
return x.NumPendingBackups
}
return 0
}
func (x *TowerSession) GetMaxBackups() uint32 {
if x != nil {
return x.MaxBackups
}
return 0
}
// Deprecated: Marked as deprecated in wtclientrpc/wtclient.proto.
func (x *TowerSession) GetSweepSatPerByte() uint32 {
if x != nil {
return x.SweepSatPerByte
}
return 0
}
func (x *TowerSession) GetSweepSatPerVbyte() uint32 {
if x != nil {
return x.SweepSatPerVbyte
}
return 0
}
func (x *TowerSession) GetId() []byte {
if x != nil {
return x.Id
}
return nil
}
type Tower struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The identifying public key of the watchtower.
Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"`
// The list of addresses the watchtower is reachable over.
Addresses []string `protobuf:"bytes,2,rep,name=addresses,proto3" json:"addresses,omitempty"`
// Deprecated, use the active_session_candidate field under the
// correct identifier in the client_type map.
// Whether the watchtower is currently a candidate for new sessions.
//
// Deprecated: Marked as deprecated in wtclientrpc/wtclient.proto.
ActiveSessionCandidate bool `protobuf:"varint,3,opt,name=active_session_candidate,json=activeSessionCandidate,proto3" json:"active_session_candidate,omitempty"`
// Deprecated, use the num_sessions field under the correct identifier
// in the client_type map.
// The number of sessions that have been negotiated with the watchtower.
//
// Deprecated: Marked as deprecated in wtclientrpc/wtclient.proto.
NumSessions uint32 `protobuf:"varint,4,opt,name=num_sessions,json=numSessions,proto3" json:"num_sessions,omitempty"`
// Deprecated, use the sessions field under the correct identifier in the
// client_type map.
// The list of sessions that have been negotiated with the watchtower.
//
// Deprecated: Marked as deprecated in wtclientrpc/wtclient.proto.
Sessions []*TowerSession `protobuf:"bytes,5,rep,name=sessions,proto3" json:"sessions,omitempty"`
// A list sessions held with the tower.
SessionInfo []*TowerSessionInfo `protobuf:"bytes,6,rep,name=session_info,json=sessionInfo,proto3" json:"session_info,omitempty"`
}
func (x *Tower) Reset() {
*x = Tower{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Tower) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Tower) ProtoMessage() {}
func (x *Tower) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Tower.ProtoReflect.Descriptor instead.
func (*Tower) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{10}
}
func (x *Tower) GetPubkey() []byte {
if x != nil {
return x.Pubkey
}
return nil
}
func (x *Tower) GetAddresses() []string {
if x != nil {
return x.Addresses
}
return nil
}
// Deprecated: Marked as deprecated in wtclientrpc/wtclient.proto.
func (x *Tower) GetActiveSessionCandidate() bool {
if x != nil {
return x.ActiveSessionCandidate
}
return false
}
// Deprecated: Marked as deprecated in wtclientrpc/wtclient.proto.
func (x *Tower) GetNumSessions() uint32 {
if x != nil {
return x.NumSessions
}
return 0
}
// Deprecated: Marked as deprecated in wtclientrpc/wtclient.proto.
func (x *Tower) GetSessions() []*TowerSession {
if x != nil {
return x.Sessions
}
return nil
}
func (x *Tower) GetSessionInfo() []*TowerSessionInfo {
if x != nil {
return x.SessionInfo
}
return nil
}
type TowerSessionInfo struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Whether the watchtower is currently a candidate for new sessions.
ActiveSessionCandidate bool `protobuf:"varint,1,opt,name=active_session_candidate,json=activeSessionCandidate,proto3" json:"active_session_candidate,omitempty"`
// The number of sessions that have been negotiated with the watchtower.
NumSessions uint32 `protobuf:"varint,2,opt,name=num_sessions,json=numSessions,proto3" json:"num_sessions,omitempty"`
// The list of sessions that have been negotiated with the watchtower.
Sessions []*TowerSession `protobuf:"bytes,3,rep,name=sessions,proto3" json:"sessions,omitempty"`
// The session's policy type.
PolicyType PolicyType `protobuf:"varint,4,opt,name=policy_type,json=policyType,proto3,enum=wtclientrpc.PolicyType" json:"policy_type,omitempty"`
}
func (x *TowerSessionInfo) Reset() {
*x = TowerSessionInfo{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TowerSessionInfo) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TowerSessionInfo) ProtoMessage() {}
func (x *TowerSessionInfo) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[11]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TowerSessionInfo.ProtoReflect.Descriptor instead.
func (*TowerSessionInfo) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{11}
}
func (x *TowerSessionInfo) GetActiveSessionCandidate() bool {
if x != nil {
return x.ActiveSessionCandidate
}
return false
}
func (x *TowerSessionInfo) GetNumSessions() uint32 {
if x != nil {
return x.NumSessions
}
return 0
}
func (x *TowerSessionInfo) GetSessions() []*TowerSession {
if x != nil {
return x.Sessions
}
return nil
}
func (x *TowerSessionInfo) GetPolicyType() PolicyType {
if x != nil {
return x.PolicyType
}
return PolicyType_LEGACY
}
type ListTowersRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Whether we should include sessions with the watchtower in the response.
IncludeSessions bool `protobuf:"varint,1,opt,name=include_sessions,json=includeSessions,proto3" json:"include_sessions,omitempty"`
// Whether to exclude exhausted sessions in the response info. This option
// is only meaningful if include_sessions is true.
ExcludeExhaustedSessions bool `protobuf:"varint,2,opt,name=exclude_exhausted_sessions,json=excludeExhaustedSessions,proto3" json:"exclude_exhausted_sessions,omitempty"`
}
func (x *ListTowersRequest) Reset() {
*x = ListTowersRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListTowersRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListTowersRequest) ProtoMessage() {}
func (x *ListTowersRequest) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[12]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListTowersRequest.ProtoReflect.Descriptor instead.
func (*ListTowersRequest) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{12}
}
func (x *ListTowersRequest) GetIncludeSessions() bool {
if x != nil {
return x.IncludeSessions
}
return false
}
func (x *ListTowersRequest) GetExcludeExhaustedSessions() bool {
if x != nil {
return x.ExcludeExhaustedSessions
}
return false
}
type ListTowersResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The list of watchtowers available for new backups.
Towers []*Tower `protobuf:"bytes,1,rep,name=towers,proto3" json:"towers,omitempty"`
}
func (x *ListTowersResponse) Reset() {
*x = ListTowersResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListTowersResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListTowersResponse) ProtoMessage() {}
func (x *ListTowersResponse) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[13]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListTowersResponse.ProtoReflect.Descriptor instead.
func (*ListTowersResponse) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{13}
}
func (x *ListTowersResponse) GetTowers() []*Tower {
if x != nil {
return x.Towers
}
return nil
}
type StatsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *StatsRequest) Reset() {
*x = StatsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *StatsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StatsRequest) ProtoMessage() {}
func (x *StatsRequest) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[14]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StatsRequest.ProtoReflect.Descriptor instead.
func (*StatsRequest) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{14}
}
type StatsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The total number of backups made to all active and exhausted watchtower
// sessions.
NumBackups uint32 `protobuf:"varint,1,opt,name=num_backups,json=numBackups,proto3" json:"num_backups,omitempty"`
// The total number of backups that are pending to be acknowledged by all
// active and exhausted watchtower sessions.
NumPendingBackups uint32 `protobuf:"varint,2,opt,name=num_pending_backups,json=numPendingBackups,proto3" json:"num_pending_backups,omitempty"`
// The total number of backups that all active and exhausted watchtower
// sessions have failed to acknowledge.
NumFailedBackups uint32 `protobuf:"varint,3,opt,name=num_failed_backups,json=numFailedBackups,proto3" json:"num_failed_backups,omitempty"`
// The total number of new sessions made to watchtowers.
NumSessionsAcquired uint32 `protobuf:"varint,4,opt,name=num_sessions_acquired,json=numSessionsAcquired,proto3" json:"num_sessions_acquired,omitempty"`
// The total number of watchtower sessions that have been exhausted.
NumSessionsExhausted uint32 `protobuf:"varint,5,opt,name=num_sessions_exhausted,json=numSessionsExhausted,proto3" json:"num_sessions_exhausted,omitempty"`
}
func (x *StatsResponse) Reset() {
*x = StatsResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *StatsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StatsResponse) ProtoMessage() {}
func (x *StatsResponse) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[15]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StatsResponse.ProtoReflect.Descriptor instead.
func (*StatsResponse) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{15}
}
func (x *StatsResponse) GetNumBackups() uint32 {
if x != nil {
return x.NumBackups
}
return 0
}
func (x *StatsResponse) GetNumPendingBackups() uint32 {
if x != nil {
return x.NumPendingBackups
}
return 0
}
func (x *StatsResponse) GetNumFailedBackups() uint32 {
if x != nil {
return x.NumFailedBackups
}
return 0
}
func (x *StatsResponse) GetNumSessionsAcquired() uint32 {
if x != nil {
return x.NumSessionsAcquired
}
return 0
}
func (x *StatsResponse) GetNumSessionsExhausted() uint32 {
if x != nil {
return x.NumSessionsExhausted
}
return 0
}
type PolicyRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The client type from which to retrieve the active offering policy.
PolicyType PolicyType `protobuf:"varint,1,opt,name=policy_type,json=policyType,proto3,enum=wtclientrpc.PolicyType" json:"policy_type,omitempty"`
}
func (x *PolicyRequest) Reset() {
*x = PolicyRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PolicyRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PolicyRequest) ProtoMessage() {}
func (x *PolicyRequest) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[16]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PolicyRequest.ProtoReflect.Descriptor instead.
func (*PolicyRequest) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{16}
}
func (x *PolicyRequest) GetPolicyType() PolicyType {
if x != nil {
return x.PolicyType
}
return PolicyType_LEGACY
}
type PolicyResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The maximum number of updates each session we negotiate with watchtowers
// should allow.
MaxUpdates uint32 `protobuf:"varint,1,opt,name=max_updates,json=maxUpdates,proto3" json:"max_updates,omitempty"`
// Deprecated, use sweep_sat_per_vbyte.
// The fee rate, in satoshis per vbyte, that will be used by watchtowers for
// justice transactions in response to channel breaches.
//
// Deprecated: Marked as deprecated in wtclientrpc/wtclient.proto.
SweepSatPerByte uint32 `protobuf:"varint,2,opt,name=sweep_sat_per_byte,json=sweepSatPerByte,proto3" json:"sweep_sat_per_byte,omitempty"`
// The fee rate, in satoshis per vbyte, that will be used by watchtowers for
// justice transactions in response to channel breaches.
SweepSatPerVbyte uint32 `protobuf:"varint,3,opt,name=sweep_sat_per_vbyte,json=sweepSatPerVbyte,proto3" json:"sweep_sat_per_vbyte,omitempty"`
}
func (x *PolicyResponse) Reset() {
*x = PolicyResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PolicyResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PolicyResponse) ProtoMessage() {}
func (x *PolicyResponse) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[17]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PolicyResponse.ProtoReflect.Descriptor instead.
func (*PolicyResponse) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{17}
}
func (x *PolicyResponse) GetMaxUpdates() uint32 {
if x != nil {
return x.MaxUpdates
}
return 0
}
// Deprecated: Marked as deprecated in wtclientrpc/wtclient.proto.
func (x *PolicyResponse) GetSweepSatPerByte() uint32 {
if x != nil {
return x.SweepSatPerByte
}
return 0
}
func (x *PolicyResponse) GetSweepSatPerVbyte() uint32 {
if x != nil {
return x.SweepSatPerVbyte
}
return 0
}
var File_wtclientrpc_wtclient_proto protoreflect.FileDescriptor
var file_wtclientrpc_wtclient_proto_rawDesc = []byte{
0x0a, 0x1a, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x77, 0x74,
0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x77, 0x74,
0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x22, 0x43, 0x0a, 0x0f, 0x41, 0x64, 0x64,
0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06,
0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75,
0x62, 0x6b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x12,
0x0a, 0x10, 0x41, 0x64, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x46, 0x0a, 0x12, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65,
0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b,
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79,
0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x15, 0x0a, 0x13, 0x52, 0x65,
0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0x30, 0x0a, 0x16, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x54,
0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70,
0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62,
0x6b, 0x65, 0x79, 0x22, 0x31, 0x0a, 0x17, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74,
0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16,
0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x38, 0x0a, 0x17, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e,
0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64,
0x22, 0x32, 0x0a, 0x18, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73,
0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06,
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74,
0x61, 0x74, 0x75, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x77, 0x65,
0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06,
0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75,
0x62, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f,
0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f,
0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12,
0x3c, 0x0a, 0x1a, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x65, 0x78, 0x68, 0x61, 0x75,
0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20,
0x01, 0x28, 0x08, 0x52, 0x18, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x45, 0x78, 0x68, 0x61,
0x75, 0x73, 0x74, 0x65, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xf0, 0x01,
0x0a, 0x0c, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f,
0x0a, 0x0b, 0x6e, 0x75, 0x6d, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6e, 0x75, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12,
0x2e, 0x0a, 0x13, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x62,
0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6e, 0x75,
0x6d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12,
0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x78, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x03,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73,
0x12, 0x2f, 0x0a, 0x12, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65,
0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01,
0x52, 0x0f, 0x73, 0x77, 0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74,
0x65, 0x12, 0x2d, 0x0a, 0x13, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70,
0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10,
0x73, 0x77, 0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65,
0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64,
0x22, 0x9f, 0x02, 0x0a, 0x05, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75,
0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b,
0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18,
0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73,
0x12, 0x3c, 0x0a, 0x18, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69,
0x6f, 0x6e, 0x5f, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01,
0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x16, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x53, 0x65,
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x12, 0x25,
0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04,
0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73,
0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x39, 0x0a, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65,
0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69,
0x6f, 0x6e, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73,
0x12, 0x40, 0x0a, 0x0c, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f,
0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e,
0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f,
0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0b, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x6e,
0x66, 0x6f, 0x22, 0xe0, 0x01, 0x0a, 0x10, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73,
0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x38, 0x0a, 0x18, 0x61, 0x63, 0x74, 0x69, 0x76,
0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64,
0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x61, 0x63, 0x74, 0x69, 0x76,
0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74,
0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, 0x73,
0x69, 0x6f, 0x6e, 0x73, 0x12, 0x35, 0x0a, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73,
0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e,
0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f,
0x6e, 0x52, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x38, 0x0a, 0x0b, 0x70,
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e,
0x32, 0x17, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50,
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x70, 0x6f, 0x6c, 0x69, 0x63,
0x79, 0x54, 0x79, 0x70, 0x65, 0x22, 0x7c, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77,
0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e,
0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01,
0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x53, 0x65, 0x73,
0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3c, 0x0a, 0x1a, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65,
0x5f, 0x65, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69,
0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x65, 0x78, 0x63, 0x6c, 0x75,
0x64, 0x65, 0x45, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69,
0x6f, 0x6e, 0x73, 0x22, 0x40, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72,
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x74, 0x6f, 0x77,
0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x74, 0x63, 0x6c,
0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x06, 0x74,
0x6f, 0x77, 0x65, 0x72, 0x73, 0x22, 0x0e, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xf8, 0x01, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x75, 0x6d, 0x5f, 0x62,
0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6e, 0x75,
0x6d, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x75, 0x6d, 0x5f,
0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6e, 0x75, 0x6d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e,
0x67, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x6e, 0x75, 0x6d, 0x5f,
0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x03,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x6e, 0x75, 0x6d, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x42,
0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x65,
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x61, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18,
0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f,
0x6e, 0x73, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x16, 0x6e, 0x75,
0x6d, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x65, 0x78, 0x68, 0x61, 0x75,
0x73, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x6e, 0x75, 0x6d, 0x53,
0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x64,
0x22, 0x49, 0x0a, 0x0d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x38, 0x0a, 0x0b, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e,
0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52,
0x0a, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x22, 0x91, 0x01, 0x0a, 0x0e,
0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f,
0x0a, 0x0b, 0x6d, 0x61, 0x78, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12,
0x2f, 0x0a, 0x12, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72,
0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52,
0x0f, 0x73, 0x77, 0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65,
0x12, 0x2d, 0x0a, 0x13, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65,
0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x73,
0x77, 0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x2a,
0x31, 0x0a, 0x0a, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a,
0x06, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x43,
0x48, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54,
0x10, 0x02, 0x32, 0x84, 0x05, 0x0a, 0x10, 0x57, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65,
0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x08, 0x41, 0x64, 0x64, 0x54, 0x6f,
0x77, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70,
0x63, 0x2e, 0x41, 0x64, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x1d, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e,
0x41, 0x64, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x50, 0x0a, 0x0b, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12,
0x1f, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65,
0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x20, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52,
0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x5c, 0x0a, 0x0f, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65,
0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x23, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x54, 0x6f,
0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x77, 0x74, 0x63,
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76,
0x61, 0x74, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x5f, 0x0a, 0x10, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73,
0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72,
0x70, 0x63, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73,
0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x77, 0x74, 0x63,
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61,
0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x4d, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x12,
0x1e, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69,
0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x1f, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69,
0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x44, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f,
0x12, 0x20, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47,
0x65, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x12, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63,
0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12,
0x19, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74,
0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x77, 0x74, 0x63,
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79,
0x12, 0x1a, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50,
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x77,
0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63,
0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e,
0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2f, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_wtclientrpc_wtclient_proto_rawDescOnce sync.Once
file_wtclientrpc_wtclient_proto_rawDescData = file_wtclientrpc_wtclient_proto_rawDesc
)
func file_wtclientrpc_wtclient_proto_rawDescGZIP() []byte {
file_wtclientrpc_wtclient_proto_rawDescOnce.Do(func() {
file_wtclientrpc_wtclient_proto_rawDescData = protoimpl.X.CompressGZIP(file_wtclientrpc_wtclient_proto_rawDescData)
})
return file_wtclientrpc_wtclient_proto_rawDescData
}
var file_wtclientrpc_wtclient_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_wtclientrpc_wtclient_proto_msgTypes = make([]protoimpl.MessageInfo, 18)
var file_wtclientrpc_wtclient_proto_goTypes = []interface{}{
(PolicyType)(0), // 0: wtclientrpc.PolicyType
(*AddTowerRequest)(nil), // 1: wtclientrpc.AddTowerRequest
(*AddTowerResponse)(nil), // 2: wtclientrpc.AddTowerResponse
(*RemoveTowerRequest)(nil), // 3: wtclientrpc.RemoveTowerRequest
(*RemoveTowerResponse)(nil), // 4: wtclientrpc.RemoveTowerResponse
(*DeactivateTowerRequest)(nil), // 5: wtclientrpc.DeactivateTowerRequest
(*DeactivateTowerResponse)(nil), // 6: wtclientrpc.DeactivateTowerResponse
(*TerminateSessionRequest)(nil), // 7: wtclientrpc.TerminateSessionRequest
(*TerminateSessionResponse)(nil), // 8: wtclientrpc.TerminateSessionResponse
(*GetTowerInfoRequest)(nil), // 9: wtclientrpc.GetTowerInfoRequest
(*TowerSession)(nil), // 10: wtclientrpc.TowerSession
(*Tower)(nil), // 11: wtclientrpc.Tower
(*TowerSessionInfo)(nil), // 12: wtclientrpc.TowerSessionInfo
(*ListTowersRequest)(nil), // 13: wtclientrpc.ListTowersRequest
(*ListTowersResponse)(nil), // 14: wtclientrpc.ListTowersResponse
(*StatsRequest)(nil), // 15: wtclientrpc.StatsRequest
(*StatsResponse)(nil), // 16: wtclientrpc.StatsResponse
(*PolicyRequest)(nil), // 17: wtclientrpc.PolicyRequest
(*PolicyResponse)(nil), // 18: wtclientrpc.PolicyResponse
}
var file_wtclientrpc_wtclient_proto_depIdxs = []int32{
10, // 0: wtclientrpc.Tower.sessions:type_name -> wtclientrpc.TowerSession
12, // 1: wtclientrpc.Tower.session_info:type_name -> wtclientrpc.TowerSessionInfo
10, // 2: wtclientrpc.TowerSessionInfo.sessions:type_name -> wtclientrpc.TowerSession
0, // 3: wtclientrpc.TowerSessionInfo.policy_type:type_name -> wtclientrpc.PolicyType
11, // 4: wtclientrpc.ListTowersResponse.towers:type_name -> wtclientrpc.Tower
0, // 5: wtclientrpc.PolicyRequest.policy_type:type_name -> wtclientrpc.PolicyType
1, // 6: wtclientrpc.WatchtowerClient.AddTower:input_type -> wtclientrpc.AddTowerRequest
3, // 7: wtclientrpc.WatchtowerClient.RemoveTower:input_type -> wtclientrpc.RemoveTowerRequest
5, // 8: wtclientrpc.WatchtowerClient.DeactivateTower:input_type -> wtclientrpc.DeactivateTowerRequest
7, // 9: wtclientrpc.WatchtowerClient.TerminateSession:input_type -> wtclientrpc.TerminateSessionRequest
13, // 10: wtclientrpc.WatchtowerClient.ListTowers:input_type -> wtclientrpc.ListTowersRequest
9, // 11: wtclientrpc.WatchtowerClient.GetTowerInfo:input_type -> wtclientrpc.GetTowerInfoRequest
15, // 12: wtclientrpc.WatchtowerClient.Stats:input_type -> wtclientrpc.StatsRequest
17, // 13: wtclientrpc.WatchtowerClient.Policy:input_type -> wtclientrpc.PolicyRequest
2, // 14: wtclientrpc.WatchtowerClient.AddTower:output_type -> wtclientrpc.AddTowerResponse
4, // 15: wtclientrpc.WatchtowerClient.RemoveTower:output_type -> wtclientrpc.RemoveTowerResponse
6, // 16: wtclientrpc.WatchtowerClient.DeactivateTower:output_type -> wtclientrpc.DeactivateTowerResponse
8, // 17: wtclientrpc.WatchtowerClient.TerminateSession:output_type -> wtclientrpc.TerminateSessionResponse
14, // 18: wtclientrpc.WatchtowerClient.ListTowers:output_type -> wtclientrpc.ListTowersResponse
11, // 19: wtclientrpc.WatchtowerClient.GetTowerInfo:output_type -> wtclientrpc.Tower
16, // 20: wtclientrpc.WatchtowerClient.Stats:output_type -> wtclientrpc.StatsResponse
18, // 21: wtclientrpc.WatchtowerClient.Policy:output_type -> wtclientrpc.PolicyResponse
14, // [14:22] is the sub-list for method output_type
6, // [6:14] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
}
func init() { file_wtclientrpc_wtclient_proto_init() }
func file_wtclientrpc_wtclient_proto_init() {
if File_wtclientrpc_wtclient_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_wtclientrpc_wtclient_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AddTowerRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wtclientrpc_wtclient_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AddTowerResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wtclientrpc_wtclient_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RemoveTowerRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wtclientrpc_wtclient_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RemoveTowerResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wtclientrpc_wtclient_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DeactivateTowerRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wtclientrpc_wtclient_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DeactivateTowerResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wtclientrpc_wtclient_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TerminateSessionRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wtclientrpc_wtclient_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TerminateSessionResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wtclientrpc_wtclient_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetTowerInfoRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wtclientrpc_wtclient_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TowerSession); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wtclientrpc_wtclient_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Tower); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wtclientrpc_wtclient_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TowerSessionInfo); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wtclientrpc_wtclient_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListTowersRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wtclientrpc_wtclient_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListTowersResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wtclientrpc_wtclient_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StatsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wtclientrpc_wtclient_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StatsResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wtclientrpc_wtclient_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PolicyRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wtclientrpc_wtclient_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PolicyResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_wtclientrpc_wtclient_proto_rawDesc,
NumEnums: 1,
NumMessages: 18,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_wtclientrpc_wtclient_proto_goTypes,
DependencyIndexes: file_wtclientrpc_wtclient_proto_depIdxs,
EnumInfos: file_wtclientrpc_wtclient_proto_enumTypes,
MessageInfos: file_wtclientrpc_wtclient_proto_msgTypes,
}.Build()
File_wtclientrpc_wtclient_proto = out.File
file_wtclientrpc_wtclient_proto_rawDesc = nil
file_wtclientrpc_wtclient_proto_goTypes = nil
file_wtclientrpc_wtclient_proto_depIdxs = nil
}
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: wtclientrpc/wtclient.proto
/*
Package wtclientrpc is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package wtclientrpc
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
func request_WatchtowerClient_AddTower_0(ctx context.Context, marshaler runtime.Marshaler, client WatchtowerClientClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq AddTowerRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.AddTower(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WatchtowerClient_AddTower_0(ctx context.Context, marshaler runtime.Marshaler, server WatchtowerClientServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq AddTowerRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.AddTower(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_WatchtowerClient_RemoveTower_0 = &utilities.DoubleArray{Encoding: map[string]int{"pubkey": 0}, Base: []int{1, 2, 0, 0}, Check: []int{0, 1, 2, 2}}
)
func request_WatchtowerClient_RemoveTower_0(ctx context.Context, marshaler runtime.Marshaler, client WatchtowerClientClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq RemoveTowerRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["pubkey"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pubkey")
}
protoReq.Pubkey, err = runtime.Bytes(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pubkey", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WatchtowerClient_RemoveTower_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.RemoveTower(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WatchtowerClient_RemoveTower_0(ctx context.Context, marshaler runtime.Marshaler, server WatchtowerClientServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq RemoveTowerRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["pubkey"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pubkey")
}
protoReq.Pubkey, err = runtime.Bytes(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pubkey", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WatchtowerClient_RemoveTower_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.RemoveTower(ctx, &protoReq)
return msg, metadata, err
}
func request_WatchtowerClient_DeactivateTower_0(ctx context.Context, marshaler runtime.Marshaler, client WatchtowerClientClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeactivateTowerRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["pubkey"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pubkey")
}
protoReq.Pubkey, err = runtime.Bytes(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pubkey", err)
}
msg, err := client.DeactivateTower(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WatchtowerClient_DeactivateTower_0(ctx context.Context, marshaler runtime.Marshaler, server WatchtowerClientServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeactivateTowerRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["pubkey"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pubkey")
}
protoReq.Pubkey, err = runtime.Bytes(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pubkey", err)
}
msg, err := server.DeactivateTower(ctx, &protoReq)
return msg, metadata, err
}
func request_WatchtowerClient_TerminateSession_0(ctx context.Context, marshaler runtime.Marshaler, client WatchtowerClientClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq TerminateSessionRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["session_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "session_id")
}
protoReq.SessionId, err = runtime.Bytes(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "session_id", err)
}
msg, err := client.TerminateSession(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WatchtowerClient_TerminateSession_0(ctx context.Context, marshaler runtime.Marshaler, server WatchtowerClientServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq TerminateSessionRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["session_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "session_id")
}
protoReq.SessionId, err = runtime.Bytes(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "session_id", err)
}
msg, err := server.TerminateSession(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_WatchtowerClient_ListTowers_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_WatchtowerClient_ListTowers_0(ctx context.Context, marshaler runtime.Marshaler, client WatchtowerClientClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListTowersRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WatchtowerClient_ListTowers_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ListTowers(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WatchtowerClient_ListTowers_0(ctx context.Context, marshaler runtime.Marshaler, server WatchtowerClientServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListTowersRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WatchtowerClient_ListTowers_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ListTowers(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_WatchtowerClient_GetTowerInfo_0 = &utilities.DoubleArray{Encoding: map[string]int{"pubkey": 0}, Base: []int{1, 2, 0, 0}, Check: []int{0, 1, 2, 2}}
)
func request_WatchtowerClient_GetTowerInfo_0(ctx context.Context, marshaler runtime.Marshaler, client WatchtowerClientClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetTowerInfoRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["pubkey"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pubkey")
}
protoReq.Pubkey, err = runtime.Bytes(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pubkey", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WatchtowerClient_GetTowerInfo_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.GetTowerInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WatchtowerClient_GetTowerInfo_0(ctx context.Context, marshaler runtime.Marshaler, server WatchtowerClientServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetTowerInfoRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["pubkey"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pubkey")
}
protoReq.Pubkey, err = runtime.Bytes(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pubkey", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WatchtowerClient_GetTowerInfo_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.GetTowerInfo(ctx, &protoReq)
return msg, metadata, err
}
func request_WatchtowerClient_Stats_0(ctx context.Context, marshaler runtime.Marshaler, client WatchtowerClientClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq StatsRequest
var metadata runtime.ServerMetadata
msg, err := client.Stats(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WatchtowerClient_Stats_0(ctx context.Context, marshaler runtime.Marshaler, server WatchtowerClientServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq StatsRequest
var metadata runtime.ServerMetadata
msg, err := server.Stats(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_WatchtowerClient_Policy_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_WatchtowerClient_Policy_0(ctx context.Context, marshaler runtime.Marshaler, client WatchtowerClientClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq PolicyRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WatchtowerClient_Policy_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.Policy(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WatchtowerClient_Policy_0(ctx context.Context, marshaler runtime.Marshaler, server WatchtowerClientServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq PolicyRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WatchtowerClient_Policy_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.Policy(ctx, &protoReq)
return msg, metadata, err
}
// RegisterWatchtowerClientHandlerServer registers the http handlers for service WatchtowerClient to "mux".
// UnaryRPC :call WatchtowerClientServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterWatchtowerClientHandlerFromEndpoint instead.
func RegisterWatchtowerClientHandlerServer(ctx context.Context, mux *runtime.ServeMux, server WatchtowerClientServer) error {
mux.Handle("POST", pattern_WatchtowerClient_AddTower_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/wtclientrpc.WatchtowerClient/AddTower", runtime.WithHTTPPathPattern("/v2/watchtower/client"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WatchtowerClient_AddTower_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WatchtowerClient_AddTower_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_WatchtowerClient_RemoveTower_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/wtclientrpc.WatchtowerClient/RemoveTower", runtime.WithHTTPPathPattern("/v2/watchtower/client/{pubkey}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WatchtowerClient_RemoveTower_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WatchtowerClient_RemoveTower_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WatchtowerClient_DeactivateTower_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/wtclientrpc.WatchtowerClient/DeactivateTower", runtime.WithHTTPPathPattern("/v2/watchtower/client/tower/deactivate/{pubkey}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WatchtowerClient_DeactivateTower_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WatchtowerClient_DeactivateTower_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WatchtowerClient_TerminateSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/wtclientrpc.WatchtowerClient/TerminateSession", runtime.WithHTTPPathPattern("/v2/watchtower/client/sessions/terminate/{session_id}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WatchtowerClient_TerminateSession_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WatchtowerClient_TerminateSession_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WatchtowerClient_ListTowers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/wtclientrpc.WatchtowerClient/ListTowers", runtime.WithHTTPPathPattern("/v2/watchtower/client"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WatchtowerClient_ListTowers_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WatchtowerClient_ListTowers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WatchtowerClient_GetTowerInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/wtclientrpc.WatchtowerClient/GetTowerInfo", runtime.WithHTTPPathPattern("/v2/watchtower/client/info/{pubkey}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WatchtowerClient_GetTowerInfo_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WatchtowerClient_GetTowerInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WatchtowerClient_Stats_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/wtclientrpc.WatchtowerClient/Stats", runtime.WithHTTPPathPattern("/v2/watchtower/client/stats"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WatchtowerClient_Stats_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WatchtowerClient_Stats_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WatchtowerClient_Policy_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/wtclientrpc.WatchtowerClient/Policy", runtime.WithHTTPPathPattern("/v2/watchtower/client/policy"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WatchtowerClient_Policy_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WatchtowerClient_Policy_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterWatchtowerClientHandlerFromEndpoint is same as RegisterWatchtowerClientHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterWatchtowerClientHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterWatchtowerClientHandler(ctx, mux, conn)
}
// RegisterWatchtowerClientHandler registers the http handlers for service WatchtowerClient to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterWatchtowerClientHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterWatchtowerClientHandlerClient(ctx, mux, NewWatchtowerClientClient(conn))
}
// RegisterWatchtowerClientHandlerClient registers the http handlers for service WatchtowerClient
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "WatchtowerClientClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "WatchtowerClientClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "WatchtowerClientClient" to call the correct interceptors.
func RegisterWatchtowerClientHandlerClient(ctx context.Context, mux *runtime.ServeMux, client WatchtowerClientClient) error {
mux.Handle("POST", pattern_WatchtowerClient_AddTower_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/wtclientrpc.WatchtowerClient/AddTower", runtime.WithHTTPPathPattern("/v2/watchtower/client"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WatchtowerClient_AddTower_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WatchtowerClient_AddTower_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_WatchtowerClient_RemoveTower_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/wtclientrpc.WatchtowerClient/RemoveTower", runtime.WithHTTPPathPattern("/v2/watchtower/client/{pubkey}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WatchtowerClient_RemoveTower_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WatchtowerClient_RemoveTower_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WatchtowerClient_DeactivateTower_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/wtclientrpc.WatchtowerClient/DeactivateTower", runtime.WithHTTPPathPattern("/v2/watchtower/client/tower/deactivate/{pubkey}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WatchtowerClient_DeactivateTower_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WatchtowerClient_DeactivateTower_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WatchtowerClient_TerminateSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/wtclientrpc.WatchtowerClient/TerminateSession", runtime.WithHTTPPathPattern("/v2/watchtower/client/sessions/terminate/{session_id}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WatchtowerClient_TerminateSession_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WatchtowerClient_TerminateSession_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WatchtowerClient_ListTowers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/wtclientrpc.WatchtowerClient/ListTowers", runtime.WithHTTPPathPattern("/v2/watchtower/client"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WatchtowerClient_ListTowers_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WatchtowerClient_ListTowers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WatchtowerClient_GetTowerInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/wtclientrpc.WatchtowerClient/GetTowerInfo", runtime.WithHTTPPathPattern("/v2/watchtower/client/info/{pubkey}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WatchtowerClient_GetTowerInfo_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WatchtowerClient_GetTowerInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WatchtowerClient_Stats_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/wtclientrpc.WatchtowerClient/Stats", runtime.WithHTTPPathPattern("/v2/watchtower/client/stats"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WatchtowerClient_Stats_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WatchtowerClient_Stats_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WatchtowerClient_Policy_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/wtclientrpc.WatchtowerClient/Policy", runtime.WithHTTPPathPattern("/v2/watchtower/client/policy"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WatchtowerClient_Policy_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WatchtowerClient_Policy_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_WatchtowerClient_AddTower_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "watchtower", "client"}, ""))
pattern_WatchtowerClient_RemoveTower_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v2", "watchtower", "client", "pubkey"}, ""))
pattern_WatchtowerClient_DeactivateTower_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"v2", "watchtower", "client", "tower", "deactivate", "pubkey"}, ""))
pattern_WatchtowerClient_TerminateSession_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"v2", "watchtower", "client", "sessions", "terminate", "session_id"}, ""))
pattern_WatchtowerClient_ListTowers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "watchtower", "client"}, ""))
pattern_WatchtowerClient_GetTowerInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"v2", "watchtower", "client", "info", "pubkey"}, ""))
pattern_WatchtowerClient_Stats_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "watchtower", "client", "stats"}, ""))
pattern_WatchtowerClient_Policy_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "watchtower", "client", "policy"}, ""))
)
var (
forward_WatchtowerClient_AddTower_0 = runtime.ForwardResponseMessage
forward_WatchtowerClient_RemoveTower_0 = runtime.ForwardResponseMessage
forward_WatchtowerClient_DeactivateTower_0 = runtime.ForwardResponseMessage
forward_WatchtowerClient_TerminateSession_0 = runtime.ForwardResponseMessage
forward_WatchtowerClient_ListTowers_0 = runtime.ForwardResponseMessage
forward_WatchtowerClient_GetTowerInfo_0 = runtime.ForwardResponseMessage
forward_WatchtowerClient_Stats_0 = runtime.ForwardResponseMessage
forward_WatchtowerClient_Policy_0 = runtime.ForwardResponseMessage
)
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package wtclientrpc
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// WatchtowerClientClient is the client API for WatchtowerClient service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type WatchtowerClientClient interface {
// lncli: `wtclient add`
// AddTower adds a new watchtower reachable at the given address and
// considers it for new sessions. If the watchtower already exists, then
// any new addresses included will be considered when dialing it for
// session negotiations and backups.
AddTower(ctx context.Context, in *AddTowerRequest, opts ...grpc.CallOption) (*AddTowerResponse, error)
// lncli: `wtclient remove`
// RemoveTower removes a watchtower from being considered for future session
// negotiations and from being used for any subsequent backups until it's added
// again. If an address is provided, then this RPC only serves as a way of
// removing the address from the watchtower instead.
RemoveTower(ctx context.Context, in *RemoveTowerRequest, opts ...grpc.CallOption) (*RemoveTowerResponse, error)
// lncli: `wtclient deactivate`
// DeactivateTower sets the given tower's status to inactive so that it
// is not considered for session negotiation. Its sessions will also not
// be used while the tower is inactive.
DeactivateTower(ctx context.Context, in *DeactivateTowerRequest, opts ...grpc.CallOption) (*DeactivateTowerResponse, error)
// lncli: `wtclient session terminate`
// Terminate terminates the given session and marks it as terminal so that
// it is not used for backups anymore.
TerminateSession(ctx context.Context, in *TerminateSessionRequest, opts ...grpc.CallOption) (*TerminateSessionResponse, error)
// lncli: `wtclient towers`
// ListTowers returns the list of watchtowers registered with the client.
ListTowers(ctx context.Context, in *ListTowersRequest, opts ...grpc.CallOption) (*ListTowersResponse, error)
// lncli: `wtclient tower`
// GetTowerInfo retrieves information for a registered watchtower.
GetTowerInfo(ctx context.Context, in *GetTowerInfoRequest, opts ...grpc.CallOption) (*Tower, error)
// lncli: `wtclient stats`
// Stats returns the in-memory statistics of the client since startup.
Stats(ctx context.Context, in *StatsRequest, opts ...grpc.CallOption) (*StatsResponse, error)
// lncli: `wtclient policy`
// Policy returns the active watchtower client policy configuration.
Policy(ctx context.Context, in *PolicyRequest, opts ...grpc.CallOption) (*PolicyResponse, error)
}
type watchtowerClientClient struct {
cc grpc.ClientConnInterface
}
func NewWatchtowerClientClient(cc grpc.ClientConnInterface) WatchtowerClientClient {
return &watchtowerClientClient{cc}
}
func (c *watchtowerClientClient) AddTower(ctx context.Context, in *AddTowerRequest, opts ...grpc.CallOption) (*AddTowerResponse, error) {
out := new(AddTowerResponse)
err := c.cc.Invoke(ctx, "/wtclientrpc.WatchtowerClient/AddTower", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *watchtowerClientClient) RemoveTower(ctx context.Context, in *RemoveTowerRequest, opts ...grpc.CallOption) (*RemoveTowerResponse, error) {
out := new(RemoveTowerResponse)
err := c.cc.Invoke(ctx, "/wtclientrpc.WatchtowerClient/RemoveTower", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *watchtowerClientClient) DeactivateTower(ctx context.Context, in *DeactivateTowerRequest, opts ...grpc.CallOption) (*DeactivateTowerResponse, error) {
out := new(DeactivateTowerResponse)
err := c.cc.Invoke(ctx, "/wtclientrpc.WatchtowerClient/DeactivateTower", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *watchtowerClientClient) TerminateSession(ctx context.Context, in *TerminateSessionRequest, opts ...grpc.CallOption) (*TerminateSessionResponse, error) {
out := new(TerminateSessionResponse)
err := c.cc.Invoke(ctx, "/wtclientrpc.WatchtowerClient/TerminateSession", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *watchtowerClientClient) ListTowers(ctx context.Context, in *ListTowersRequest, opts ...grpc.CallOption) (*ListTowersResponse, error) {
out := new(ListTowersResponse)
err := c.cc.Invoke(ctx, "/wtclientrpc.WatchtowerClient/ListTowers", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *watchtowerClientClient) GetTowerInfo(ctx context.Context, in *GetTowerInfoRequest, opts ...grpc.CallOption) (*Tower, error) {
out := new(Tower)
err := c.cc.Invoke(ctx, "/wtclientrpc.WatchtowerClient/GetTowerInfo", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *watchtowerClientClient) Stats(ctx context.Context, in *StatsRequest, opts ...grpc.CallOption) (*StatsResponse, error) {
out := new(StatsResponse)
err := c.cc.Invoke(ctx, "/wtclientrpc.WatchtowerClient/Stats", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *watchtowerClientClient) Policy(ctx context.Context, in *PolicyRequest, opts ...grpc.CallOption) (*PolicyResponse, error) {
out := new(PolicyResponse)
err := c.cc.Invoke(ctx, "/wtclientrpc.WatchtowerClient/Policy", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// WatchtowerClientServer is the server API for WatchtowerClient service.
// All implementations must embed UnimplementedWatchtowerClientServer
// for forward compatibility
type WatchtowerClientServer interface {
// lncli: `wtclient add`
// AddTower adds a new watchtower reachable at the given address and
// considers it for new sessions. If the watchtower already exists, then
// any new addresses included will be considered when dialing it for
// session negotiations and backups.
AddTower(context.Context, *AddTowerRequest) (*AddTowerResponse, error)
// lncli: `wtclient remove`
// RemoveTower removes a watchtower from being considered for future session
// negotiations and from being used for any subsequent backups until it's added
// again. If an address is provided, then this RPC only serves as a way of
// removing the address from the watchtower instead.
RemoveTower(context.Context, *RemoveTowerRequest) (*RemoveTowerResponse, error)
// lncli: `wtclient deactivate`
// DeactivateTower sets the given tower's status to inactive so that it
// is not considered for session negotiation. Its sessions will also not
// be used while the tower is inactive.
DeactivateTower(context.Context, *DeactivateTowerRequest) (*DeactivateTowerResponse, error)
// lncli: `wtclient session terminate`
// Terminate terminates the given session and marks it as terminal so that
// it is not used for backups anymore.
TerminateSession(context.Context, *TerminateSessionRequest) (*TerminateSessionResponse, error)
// lncli: `wtclient towers`
// ListTowers returns the list of watchtowers registered with the client.
ListTowers(context.Context, *ListTowersRequest) (*ListTowersResponse, error)
// lncli: `wtclient tower`
// GetTowerInfo retrieves information for a registered watchtower.
GetTowerInfo(context.Context, *GetTowerInfoRequest) (*Tower, error)
// lncli: `wtclient stats`
// Stats returns the in-memory statistics of the client since startup.
Stats(context.Context, *StatsRequest) (*StatsResponse, error)
// lncli: `wtclient policy`
// Policy returns the active watchtower client policy configuration.
Policy(context.Context, *PolicyRequest) (*PolicyResponse, error)
mustEmbedUnimplementedWatchtowerClientServer()
}
// UnimplementedWatchtowerClientServer must be embedded to have forward compatible implementations.
type UnimplementedWatchtowerClientServer struct {
}
func (UnimplementedWatchtowerClientServer) AddTower(context.Context, *AddTowerRequest) (*AddTowerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AddTower not implemented")
}
func (UnimplementedWatchtowerClientServer) RemoveTower(context.Context, *RemoveTowerRequest) (*RemoveTowerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RemoveTower not implemented")
}
func (UnimplementedWatchtowerClientServer) DeactivateTower(context.Context, *DeactivateTowerRequest) (*DeactivateTowerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeactivateTower not implemented")
}
func (UnimplementedWatchtowerClientServer) TerminateSession(context.Context, *TerminateSessionRequest) (*TerminateSessionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method TerminateSession not implemented")
}
func (UnimplementedWatchtowerClientServer) ListTowers(context.Context, *ListTowersRequest) (*ListTowersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListTowers not implemented")
}
func (UnimplementedWatchtowerClientServer) GetTowerInfo(context.Context, *GetTowerInfoRequest) (*Tower, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetTowerInfo not implemented")
}
func (UnimplementedWatchtowerClientServer) Stats(context.Context, *StatsRequest) (*StatsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Stats not implemented")
}
func (UnimplementedWatchtowerClientServer) Policy(context.Context, *PolicyRequest) (*PolicyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Policy not implemented")
}
func (UnimplementedWatchtowerClientServer) mustEmbedUnimplementedWatchtowerClientServer() {}
// UnsafeWatchtowerClientServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to WatchtowerClientServer will
// result in compilation errors.
type UnsafeWatchtowerClientServer interface {
mustEmbedUnimplementedWatchtowerClientServer()
}
func RegisterWatchtowerClientServer(s grpc.ServiceRegistrar, srv WatchtowerClientServer) {
s.RegisterService(&WatchtowerClient_ServiceDesc, srv)
}
func _WatchtowerClient_AddTower_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AddTowerRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WatchtowerClientServer).AddTower(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/wtclientrpc.WatchtowerClient/AddTower",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WatchtowerClientServer).AddTower(ctx, req.(*AddTowerRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WatchtowerClient_RemoveTower_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RemoveTowerRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WatchtowerClientServer).RemoveTower(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/wtclientrpc.WatchtowerClient/RemoveTower",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WatchtowerClientServer).RemoveTower(ctx, req.(*RemoveTowerRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WatchtowerClient_DeactivateTower_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeactivateTowerRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WatchtowerClientServer).DeactivateTower(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/wtclientrpc.WatchtowerClient/DeactivateTower",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WatchtowerClientServer).DeactivateTower(ctx, req.(*DeactivateTowerRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WatchtowerClient_TerminateSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(TerminateSessionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WatchtowerClientServer).TerminateSession(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/wtclientrpc.WatchtowerClient/TerminateSession",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WatchtowerClientServer).TerminateSession(ctx, req.(*TerminateSessionRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WatchtowerClient_ListTowers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListTowersRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WatchtowerClientServer).ListTowers(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/wtclientrpc.WatchtowerClient/ListTowers",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WatchtowerClientServer).ListTowers(ctx, req.(*ListTowersRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WatchtowerClient_GetTowerInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetTowerInfoRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WatchtowerClientServer).GetTowerInfo(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/wtclientrpc.WatchtowerClient/GetTowerInfo",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WatchtowerClientServer).GetTowerInfo(ctx, req.(*GetTowerInfoRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WatchtowerClient_Stats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StatsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WatchtowerClientServer).Stats(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/wtclientrpc.WatchtowerClient/Stats",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WatchtowerClientServer).Stats(ctx, req.(*StatsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WatchtowerClient_Policy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PolicyRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WatchtowerClientServer).Policy(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/wtclientrpc.WatchtowerClient/Policy",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WatchtowerClientServer).Policy(ctx, req.(*PolicyRequest))
}
return interceptor(ctx, in, info, handler)
}
// WatchtowerClient_ServiceDesc is the grpc.ServiceDesc for WatchtowerClient service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var WatchtowerClient_ServiceDesc = grpc.ServiceDesc{
ServiceName: "wtclientrpc.WatchtowerClient",
HandlerType: (*WatchtowerClientServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "AddTower",
Handler: _WatchtowerClient_AddTower_Handler,
},
{
MethodName: "RemoveTower",
Handler: _WatchtowerClient_RemoveTower_Handler,
},
{
MethodName: "DeactivateTower",
Handler: _WatchtowerClient_DeactivateTower_Handler,
},
{
MethodName: "TerminateSession",
Handler: _WatchtowerClient_TerminateSession_Handler,
},
{
MethodName: "ListTowers",
Handler: _WatchtowerClient_ListTowers_Handler,
},
{
MethodName: "GetTowerInfo",
Handler: _WatchtowerClient_GetTowerInfo_Handler,
},
{
MethodName: "Stats",
Handler: _WatchtowerClient_Stats_Handler,
},
{
MethodName: "Policy",
Handler: _WatchtowerClient_Policy_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "wtclientrpc/wtclient.proto",
}
//go:build !bitcoind && !neutrino
// +build !bitcoind,!neutrino
package lntest
import (
"encoding/hex"
"errors"
"fmt"
"os"
"strings"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/integration/rpctest"
"github.com/btcsuite/btcd/rpcclient"
"github.com/lightningnetwork/lnd/lntest/miner"
"github.com/lightningnetwork/lnd/lntest/node"
)
// logDirPattern is the pattern of the name of the temporary log directory.
const logDirPattern = "%s/.backendlogs"
// BtcdBackendConfig is an implementation of the BackendConfig interface
// backed by a btcd node.
type BtcdBackendConfig struct {
// rpcConfig houses the connection config to the backing btcd instance.
rpcConfig rpcclient.ConnConfig
// harness is the backing btcd instance.
harness *rpctest.Harness
// minerAddr is the p2p address of the miner to connect to.
minerAddr string
}
// A compile time assertion to ensure BtcdBackendConfig meets the BackendConfig
// interface.
var _ node.BackendConfig = (*BtcdBackendConfig)(nil)
// GenArgs returns the arguments needed to be passed to LND at startup for
// using this node as a chain backend.
func (b BtcdBackendConfig) GenArgs() []string {
var args []string
encodedCert := hex.EncodeToString(b.rpcConfig.Certificates)
args = append(args, "--bitcoin.node=btcd")
args = append(args, fmt.Sprintf("--btcd.rpchost=%v", b.rpcConfig.Host))
args = append(args, fmt.Sprintf("--btcd.rpcuser=%v", b.rpcConfig.User))
args = append(args, fmt.Sprintf("--btcd.rpcpass=%v", b.rpcConfig.Pass))
args = append(args, fmt.Sprintf("--btcd.rawrpccert=%v", encodedCert))
return args
}
// ConnectMiner is called to establish a connection to the test miner.
func (b BtcdBackendConfig) ConnectMiner() error {
return b.harness.Client.Node(btcjson.NConnect, b.minerAddr, &miner.Temp)
}
// DisconnectMiner is called to disconnect the miner.
func (b BtcdBackendConfig) DisconnectMiner() error {
return b.harness.Client.Node(
btcjson.NDisconnect, b.minerAddr, &miner.Temp,
)
}
// Credentials returns the rpc username, password and host for the backend.
func (b BtcdBackendConfig) Credentials() (string, string, string, error) {
return b.rpcConfig.User, b.rpcConfig.Pass, b.rpcConfig.Host, nil
}
// Name returns the name of the backend type.
func (b BtcdBackendConfig) Name() string {
return "btcd"
}
// NewBackend starts a new rpctest.Harness and returns a BtcdBackendConfig for
// that node. miner should be set to the P2P address of the miner to connect
// to.
func NewBackend(miner string, netParams *chaincfg.Params) (
*BtcdBackendConfig, func() error, error) {
baseLogDir := fmt.Sprintf(logDirPattern, node.GetLogDir())
args := []string{
"--rejectnonstd",
"--txindex",
"--trickleinterval=100ms",
"--debuglevel=debug",
"--logdir=" + baseLogDir,
"--nowinservice",
// The miner will get banned and disconnected from the node if
// its requested data are not found. We add a nobanning flag to
// make sure they stay connected if it happens.
"--nobanning",
// Don't disconnect if a reply takes too long.
"--nostalldetect",
// The default max num of websockets is 25, but the closed
// connections are not cleaned up immediately so we double the
// size.
//
// TODO(yy): fix this in `btcd` to clean up the stale
// connections.
"--rpcmaxwebsockets=50",
}
chainBackend, err := rpctest.New(
netParams, nil, args, node.GetBtcdBinary(),
)
if err != nil {
return nil, nil, fmt.Errorf("unable to create btcd node: %w",
err)
}
// We want to overwrite some of the connection settings to make the
// tests more robust. We might need to restart the backend while there
// are already blocks present, which will take a bit longer than the
// 1 second the default settings amount to. Doubling both values will
// give us retries up to 4 seconds.
const (
maxConnRetries = rpctest.DefaultMaxConnectionRetries * 2
connRetryTimeout = rpctest.DefaultConnectionRetryTimeout * 2
)
chainBackend.MaxConnRetries = maxConnRetries
chainBackend.ConnectionRetryTimeout = connRetryTimeout
if err := chainBackend.SetUp(false, 0); err != nil {
return nil, nil, fmt.Errorf("unable to set up btcd backend: %w",
err)
}
bd := &BtcdBackendConfig{
rpcConfig: chainBackend.RPCConfig(),
harness: chainBackend,
minerAddr: miner,
}
cleanUp := func() error {
var errStr string
if err := chainBackend.TearDown(); err != nil {
errStr += err.Error() + "\n"
}
// After shutting down the chain backend, we'll make a copy of
// the log files, including any compressed log files from
// logrorate, before deleting the temporary log dir.
logDir := fmt.Sprintf("%s/%s", baseLogDir, netParams.Name)
files, err := os.ReadDir(logDir)
if err != nil {
errStr += fmt.Sprintf(
"unable to read log directory: %v\n", err,
)
}
for _, file := range files {
logFile := fmt.Sprintf("%s/%s", logDir, file.Name())
newFilename := strings.Replace(
file.Name(), "btcd.log",
"output_btcd_chainbackend.log", 1,
)
logDestination := fmt.Sprintf(
"%s/%s", node.GetLogDir(), newFilename,
)
err := node.CopyFile(logDestination, logFile)
if err != nil {
errStr += fmt.Sprintf("unable to copy file: "+
"%v\n", err)
}
}
if err = os.RemoveAll(baseLogDir); err != nil {
errStr += fmt.Sprintf(
"cannot remove dir %s: %v\n", baseLogDir, err,
)
}
if errStr != "" {
return errors.New(errStr)
}
return nil
}
return bd, cleanUp, nil
}
package lntest
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"sync"
"testing"
"github.com/lightningnetwork/lnd"
"github.com/lightningnetwork/lnd/lntest/port"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/stretchr/testify/require"
)
// WebFeeService defines an interface that's used to provide fee estimation
// service used in the integration tests. It must provide an URL so that a lnd
// node can be started with the flag `--fee.url` and uses the customized fee
// estimator.
type WebFeeService interface {
// Start starts the service.
Start() error
// Stop stops the service.
Stop() error
// URL returns the service's endpoint.
URL() string
// SetFeeRate sets the estimated fee rate for a given confirmation
// target.
SetFeeRate(feeRate chainfee.SatPerKWeight, conf uint32)
// SetMinRelayFeerate sets a min relay feerate.
SetMinRelayFeerate(fee chainfee.SatPerKVByte)
// Reset resets the fee rate map to the default value.
Reset()
}
const (
// feeServiceTarget is the confirmation target for which a fee estimate
// is returned. Requests for higher confirmation targets will fall back
// to this.
feeServiceTarget = 1
// DefaultFeeRateSatPerKw specifies the default fee rate used in the
// tests.
DefaultFeeRateSatPerKw = 12500
)
// FeeService runs a web service that provides fee estimation information.
type FeeService struct {
*testing.T
feeRateMap map[uint32]uint32
minRelayFeerate chainfee.SatPerKVByte
url string
srv *http.Server
wg sync.WaitGroup
lock sync.Mutex
}
// Compile-time check for the WebFeeService interface.
var _ WebFeeService = (*FeeService)(nil)
// NewFeeService spins up a go-routine to serve fee estimates.
func NewFeeService(t *testing.T) *FeeService {
t.Helper()
port := port.NextAvailablePort()
f := FeeService{
T: t,
url: fmt.Sprintf(
"http://localhost:%v/fee-estimates.json", port,
),
}
// Initialize default fee estimate.
f.feeRateMap = map[uint32]uint32{
feeServiceTarget: DefaultFeeRateSatPerKw,
}
f.minRelayFeerate = chainfee.FeePerKwFloor.FeePerKVByte()
listenAddr := fmt.Sprintf(":%v", port)
mux := http.NewServeMux()
mux.HandleFunc("/fee-estimates.json", f.handleRequest)
f.srv = &http.Server{
Addr: listenAddr,
Handler: mux,
ReadHeaderTimeout: lnd.DefaultHTTPHeaderTimeout,
}
return &f
}
// Start starts the web server.
func (f *FeeService) Start() error {
f.wg.Add(1)
go func() {
defer f.wg.Done()
if err := f.srv.ListenAndServe(); err != http.ErrServerClosed {
require.NoErrorf(f, err, "cannot start fee api")
}
}()
return nil
}
// handleRequest handles a client request for fee estimates.
func (f *FeeService) handleRequest(w http.ResponseWriter, _ *http.Request) {
f.lock.Lock()
defer f.lock.Unlock()
bytes, err := json.Marshal(
chainfee.WebAPIResponse{
FeeByBlockTarget: f.feeRateMap,
MinRelayFeerate: f.minRelayFeerate,
},
)
require.NoErrorf(f, err, "cannot serialize estimates")
_, err = io.WriteString(w, string(bytes))
require.NoError(f, err, "cannot send estimates")
}
// Stop stops the web server.
func (f *FeeService) Stop() error {
err := f.srv.Shutdown(context.Background())
require.NoError(f, err, "cannot stop fee api")
f.wg.Wait()
return nil
}
// SetFeeRate sets a fee for the given confirmation target.
func (f *FeeService) SetFeeRate(fee chainfee.SatPerKWeight, conf uint32) {
f.lock.Lock()
defer f.lock.Unlock()
f.feeRateMap[conf] = uint32(fee.FeePerKVByte())
}
// SetMinRelayFeerate sets a min relay feerate.
func (f *FeeService) SetMinRelayFeerate(fee chainfee.SatPerKVByte) {
f.lock.Lock()
defer f.lock.Unlock()
f.minRelayFeerate = fee
}
// Reset resets the fee rate map to the default value.
func (f *FeeService) Reset() {
f.lock.Lock()
f.feeRateMap = make(map[uint32]uint32)
f.minRelayFeerate = chainfee.FeePerKwFloor.FeePerKVByte()
f.lock.Unlock()
// Initialize default fee estimate.
f.SetFeeRate(DefaultFeeRateSatPerKw, 1)
}
// URL returns the service endpoint.
func (f *FeeService) URL() string {
return f.url
}
package lntest
import (
"context"
"fmt"
"strings"
"testing"
"time"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/kvdb/etcd"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lntest/miner"
"github.com/lightningnetwork/lnd/lntest/node"
"github.com/lightningnetwork/lnd/lntest/rpc"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing"
"github.com/stretchr/testify/require"
)
const (
// defaultMinerFeeRate specifies the fee rate in sats when sending
// outputs from the miner.
defaultMinerFeeRate = 7500
// numBlocksSendOutput specifies the number of blocks to mine after
// sending outputs from the miner.
numBlocksSendOutput = 2
// numBlocksOpenChannel specifies the number of blocks mined when
// opening a channel.
numBlocksOpenChannel = 6
// lndErrorChanSize specifies the buffer size used to receive errors
// from lnd process.
lndErrorChanSize = 10
// maxBlocksAllowed specifies the max allowed value to be used when
// mining blocks.
maxBlocksAllowed = 100
finalCltvDelta = routing.MinCLTVDelta // 18.
thawHeightDelta = finalCltvDelta * 2 // 36.
)
var (
// MaxBlocksMinedPerTest is the maximum number of blocks that we allow
// a test to mine. This is an exported global variable so it can be
// overwritten by other projects that don't have the same constraints.
MaxBlocksMinedPerTest = 50
)
// TestCase defines a test case that's been used in the integration test.
type TestCase struct {
// Name specifies the test name.
Name string
// TestFunc is the test case wrapped in a function.
TestFunc func(t *HarnessTest)
}
// HarnessTest builds on top of a testing.T with enhanced error detection. It
// is responsible for managing the interactions among different nodes, and
// providing easy-to-use assertions.
type HarnessTest struct {
*testing.T
// miner is a reference to a running full node that can be used to
// create new blocks on the network.
miner *miner.HarnessMiner
// manager handles the start and stop of a given node.
manager *nodeManager
// feeService is a web service that provides external fee estimates to
// lnd.
feeService WebFeeService
// Channel for transmitting stderr output from failed lightning node
// to main process.
lndErrorChan chan error
// runCtx is a context with cancel method. It's used to signal when the
// node needs to quit, and used as the parent context when spawning
// children contexts for RPC requests.
runCtx context.Context //nolint:containedctx
cancel context.CancelFunc
// stopChainBackend points to the cleanup function returned by the
// chainBackend.
stopChainBackend func()
// cleaned specifies whether the cleanup has been applied for the
// current HarnessTest.
cleaned bool
// currentHeight is the current height of the chain backend.
currentHeight uint32
}
// harnessOpts contains functional option to modify the behavior of the various
// harness calls.
type harnessOpts struct {
useAMP bool
}
// defaultHarnessOpts returns a new instance of the harnessOpts with default
// values specified.
func defaultHarnessOpts() harnessOpts {
return harnessOpts{
useAMP: false,
}
}
// HarnessOpt is a functional option that can be used to modify the behavior of
// harness functionality.
type HarnessOpt func(*harnessOpts)
// WithAMP is a functional option that can be used to enable the AMP feature
// for sending payments.
func WithAMP() HarnessOpt {
return func(h *harnessOpts) {
h.useAMP = true
}
}
// NewHarnessTest creates a new instance of a harnessTest from a regular
// testing.T instance.
func NewHarnessTest(t *testing.T, lndBinary string, feeService WebFeeService,
dbBackend node.DatabaseBackend, nativeSQL bool) *HarnessTest {
t.Helper()
// Create the run context.
ctxt, cancel := context.WithCancel(context.Background())
manager := newNodeManager(lndBinary, dbBackend, nativeSQL)
return &HarnessTest{
T: t,
manager: manager,
feeService: feeService,
runCtx: ctxt,
cancel: cancel,
// We need to use buffered channel here as we don't want to
// block sending errors.
lndErrorChan: make(chan error, lndErrorChanSize),
}
}
// Start will assemble the chain backend and the miner for the HarnessTest. It
// also starts the fee service and watches lnd process error.
func (h *HarnessTest) Start(chain node.BackendConfig,
miner *miner.HarnessMiner) {
// Spawn a new goroutine to watch for any fatal errors that any of the
// running lnd processes encounter. If an error occurs, then the test
// case should naturally as a result and we log the server error here
// to help debug.
go func() {
select {
case err, more := <-h.lndErrorChan:
if !more {
return
}
h.Logf("lnd finished with error (stderr):\n%v", err)
case <-h.runCtx.Done():
return
}
}()
// Start the fee service.
err := h.feeService.Start()
require.NoError(h, err, "failed to start fee service")
// Assemble the node manager with chainBackend and feeServiceURL.
h.manager.chainBackend = chain
h.manager.feeServiceURL = h.feeService.URL()
// Assemble the miner.
h.miner = miner
// Update block height.
h.updateCurrentHeight()
}
// ChainBackendName returns the chain backend name used in the test.
func (h *HarnessTest) ChainBackendName() string {
return h.manager.chainBackend.Name()
}
// Context returns the run context used in this test. Usaually it should be
// managed by the test itself otherwise undefined behaviors will occur. It can
// be used, however, when a test needs to have its own context being managed
// differently. In that case, instead of using a background context, the run
// context should be used such that the test context scope can be fully
// controlled.
func (h *HarnessTest) Context() context.Context {
return h.runCtx
}
// setupWatchOnlyNode initializes a node with the watch-only accounts of an
// associated remote signing instance.
func (h *HarnessTest) setupWatchOnlyNode(name string,
signerNode *node.HarnessNode, password []byte) *node.HarnessNode {
// Prepare arguments for watch-only node connected to the remote signer.
remoteSignerArgs := []string{
"--remotesigner.enable",
fmt.Sprintf("--remotesigner.rpchost=localhost:%d",
signerNode.Cfg.RPCPort),
fmt.Sprintf("--remotesigner.tlscertpath=%s",
signerNode.Cfg.TLSCertPath),
fmt.Sprintf("--remotesigner.macaroonpath=%s",
signerNode.Cfg.AdminMacPath),
}
// Fetch watch-only accounts from the signer node.
resp := signerNode.RPC.ListAccounts(&walletrpc.ListAccountsRequest{})
watchOnlyAccounts, err := walletrpc.AccountsToWatchOnly(resp.Accounts)
require.NoErrorf(h, err, "unable to find watch only accounts for %s",
name)
// Create a new watch-only node with remote signer configuration.
return h.NewNodeRemoteSigner(
name, remoteSignerArgs, password,
&lnrpc.WatchOnly{
MasterKeyBirthdayTimestamp: 0,
MasterKeyFingerprint: nil,
Accounts: watchOnlyAccounts,
},
)
}
// createAndSendOutput send amt satoshis from the internal mining node to the
// targeted lightning node using a P2WKH address. No blocks are mined so
// transactions will sit unconfirmed in mempool.
func (h *HarnessTest) createAndSendOutput(target *node.HarnessNode,
amt btcutil.Amount, addrType lnrpc.AddressType) {
req := &lnrpc.NewAddressRequest{Type: addrType}
resp := target.RPC.NewAddress(req)
addr := h.DecodeAddress(resp.Address)
addrScript := h.PayToAddrScript(addr)
output := &wire.TxOut{
PkScript: addrScript,
Value: int64(amt),
}
h.miner.SendOutput(output, defaultMinerFeeRate)
}
// Stop stops the test harness.
func (h *HarnessTest) Stop() {
// Do nothing if it's not started.
if h.runCtx == nil {
h.Log("HarnessTest is not started")
return
}
h.shutdownAllNodes()
close(h.lndErrorChan)
// Stop the fee service.
err := h.feeService.Stop()
require.NoError(h, err, "failed to stop fee service")
// Stop the chainBackend.
h.stopChainBackend()
// Stop the miner.
h.miner.Stop()
}
// RunTestCase executes a harness test case. Any errors or panics will be
// represented as fatal.
func (h *HarnessTest) RunTestCase(testCase *TestCase) {
defer func() {
if err := recover(); err != nil {
description := errors.Wrap(err, 2).ErrorStack()
h.Fatalf("Failed: (%v) panic with: \n%v",
testCase.Name, description)
}
}()
testCase.TestFunc(h)
}
// Subtest creates a child HarnessTest, which inherits the harness net and
// stand by nodes created by the parent test. It will return a cleanup function
// which resets all the standby nodes' configs back to its original state and
// create snapshots of each nodes' internal state.
func (h *HarnessTest) Subtest(t *testing.T) *HarnessTest {
t.Helper()
st := &HarnessTest{
T: t,
manager: h.manager,
miner: h.miner,
feeService: h.feeService,
lndErrorChan: make(chan error, lndErrorChanSize),
}
// Inherit context from the main test.
st.runCtx, st.cancel = context.WithCancel(h.runCtx)
// Inherit the subtest for the miner.
st.miner.T = st.T
// Reset fee estimator.
st.feeService.Reset()
// Record block height.
h.updateCurrentHeight()
startHeight := int32(h.CurrentHeight())
st.Cleanup(func() {
// Make sure the test is not consuming too many blocks.
st.checkAndLimitBlocksMined(startHeight)
// Don't bother run the cleanups if the test is failed.
if st.Failed() {
st.Log("test failed, skipped cleanup")
st.shutdownNodesNoAssert()
return
}
// Don't run cleanup if it's already done. This can happen if
// we have multiple level inheritance of the parent harness
// test. For instance, a `Subtest(st)`.
if st.cleaned {
st.Log("test already cleaned, skipped cleanup")
return
}
// If found running nodes, shut them down.
st.shutdownAllNodes()
// We require the mempool to be cleaned from the test.
require.Empty(st, st.miner.GetRawMempool(), "mempool not "+
"cleaned, please mine blocks to clean them all.")
// Finally, cancel the run context. We have to do it here
// because we need to keep the context alive for the above
// assertions used in cleanup.
st.cancel()
// We now want to mark the parent harness as cleaned to avoid
// running cleanup again since its internal state has been
// cleaned up by its child harness tests.
h.cleaned = true
})
return st
}
// checkAndLimitBlocksMined asserts that the blocks mined in a single test
// doesn't exceed 50, which implicitly discourage table-drive tests, which are
// hard to maintain and take a long time to run.
func (h *HarnessTest) checkAndLimitBlocksMined(startHeight int32) {
_, endHeight := h.GetBestBlock()
blocksMined := endHeight - startHeight
h.Logf("finished test: %s, start height=%d, end height=%d, mined "+
"blocks=%d", h.manager.currentTestCase, startHeight, endHeight,
blocksMined)
// If the number of blocks is less than 40, we consider the test
// healthy.
if blocksMined < 40 {
return
}
// Otherwise log a warning if it's mining more than 40 blocks.
desc := "!============================================!\n"
desc += fmt.Sprintf("Too many blocks (%v) mined in one test! Tips:\n",
blocksMined)
desc += "1. break test into smaller individual tests, especially if " +
"this is a table-drive test.\n" +
"2. use smaller CSV via `--bitcoin.defaultremotedelay=1.`\n" +
"3. use smaller CLTV via `--bitcoin.timelockdelta=18.`\n" +
"4. remove unnecessary CloseChannel when test ends.\n" +
"5. use `CreateSimpleNetwork` for efficient channel creation.\n"
h.Log(desc)
// We enforce that the test should not mine more than
// MaxBlocksMinedPerTest (50 by default) blocks, which is more than
// enough to test a multi hop force close scenario.
require.LessOrEqualf(
h, int(blocksMined), MaxBlocksMinedPerTest,
"cannot mine more than %d blocks in one test",
MaxBlocksMinedPerTest,
)
}
// shutdownNodesNoAssert will shutdown all running nodes without assertions.
// This is used when the test has already failed, we don't want to log more
// errors but focusing on the original error.
func (h *HarnessTest) shutdownNodesNoAssert() {
for _, node := range h.manager.activeNodes {
_ = h.manager.shutdownNode(node)
}
}
// shutdownAllNodes will shutdown all running nodes.
func (h *HarnessTest) shutdownAllNodes() {
var err error
for _, node := range h.manager.activeNodes {
err = h.manager.shutdownNode(node)
if err == nil {
continue
}
// Instead of returning the error, we will log it instead. This
// is needed so other nodes can continue their shutdown
// processes.
h.Logf("unable to shutdown %s, got err: %v", node.Name(), err)
}
require.NoError(h, err, "failed to shutdown all nodes")
}
// cleanupStandbyNode is a function should be called with defer whenever a
// subtest is created. It will reset the standby nodes configs, snapshot the
// states, and validate the node has a clean state.
func (h *HarnessTest) cleanupStandbyNode(hn *node.HarnessNode) {
// Remove connections made from this test.
h.removeConnectionns(hn)
// Delete all payments made from this test.
hn.RPC.DeleteAllPayments()
// Check the node's current state with timeout.
//
// NOTE: we need to do this in a `wait` because it takes some time for
// the node to update its internal state. Once the RPCs are synced we
// can then remove this wait.
err := wait.NoError(func() error {
// Update the node's internal state.
hn.UpdateState()
// Check the node is in a clean state for the following tests.
return h.validateNodeState(hn)
}, wait.DefaultTimeout)
require.NoError(h, err, "timeout checking node's state")
}
// removeConnectionns will remove all connections made on the standby nodes
// expect the connections between Alice and Bob.
func (h *HarnessTest) removeConnectionns(hn *node.HarnessNode) {
resp := hn.RPC.ListPeers()
for _, peer := range resp.Peers {
hn.RPC.DisconnectPeer(peer.PubKey)
}
}
// SetTestName set the test case name.
func (h *HarnessTest) SetTestName(name string) {
cleanTestCaseName := strings.ReplaceAll(name, " ", "_")
h.manager.currentTestCase = cleanTestCaseName
}
// NewNode creates a new node and asserts its creation. The node is guaranteed
// to have finished its initialization and all its subservers are started.
func (h *HarnessTest) NewNode(name string,
extraArgs []string) *node.HarnessNode {
node, err := h.manager.newNode(h.T, name, extraArgs, nil, false)
require.NoErrorf(h, err, "unable to create new node for %s", name)
// Start the node.
err = node.Start(h.runCtx)
require.NoError(h, err, "failed to start node %s", node.Name())
// Get the miner's best block hash.
bestBlock, err := h.miner.Client.GetBestBlockHash()
require.NoError(h, err, "unable to get best block hash")
// Wait until the node's chain backend is synced to the miner's best
// block.
h.WaitForBlockchainSyncTo(node, *bestBlock)
return node
}
// NewNodeWithCoins creates a new node and asserts its creation. The node is
// guaranteed to have finished its initialization and all its subservers are
// started. In addition, 5 UTXO of 1 BTC each are sent to the node.
func (h *HarnessTest) NewNodeWithCoins(name string,
extraArgs []string) *node.HarnessNode {
node := h.NewNode(name, extraArgs)
// Load up the wallets of the node with 5 outputs of 1 BTC each.
const (
numOutputs = 5
fundAmount = 1 * btcutil.SatoshiPerBitcoin
totalAmount = fundAmount * numOutputs
)
for i := 0; i < numOutputs; i++ {
h.createAndSendOutput(
node, fundAmount,
lnrpc.AddressType_WITNESS_PUBKEY_HASH,
)
}
// Mine a block to confirm the transactions.
h.MineBlocksAndAssertNumTxes(1, numOutputs)
// Now block until the wallet have fully synced up.
h.WaitForBalanceConfirmed(node, totalAmount)
return node
}
// Shutdown shuts down the given node and asserts that no errors occur.
func (h *HarnessTest) Shutdown(node *node.HarnessNode) {
err := h.manager.shutdownNode(node)
require.NoErrorf(h, err, "unable to shutdown %v in %v", node.Name(),
h.manager.currentTestCase)
}
// SuspendNode stops the given node and returns a callback that can be used to
// start it again.
func (h *HarnessTest) SuspendNode(node *node.HarnessNode) func() error {
err := node.Stop()
require.NoErrorf(h, err, "failed to stop %s", node.Name())
// Remove the node from active nodes.
delete(h.manager.activeNodes, node.Cfg.NodeID)
return func() error {
h.manager.registerNode(node)
if err := node.Start(h.runCtx); err != nil {
return err
}
h.WaitForBlockchainSync(node)
return nil
}
}
// RestartNode restarts a given node, unlocks it and asserts it's successfully
// started.
func (h *HarnessTest) RestartNode(hn *node.HarnessNode) {
err := h.manager.restartNode(h.runCtx, hn, nil)
require.NoErrorf(h, err, "failed to restart node %s", hn.Name())
err = h.manager.unlockNode(hn)
require.NoErrorf(h, err, "failed to unlock node %s", hn.Name())
if !hn.Cfg.SkipUnlock {
// Give the node some time to catch up with the chain before we
// continue with the tests.
h.WaitForBlockchainSync(hn)
}
}
// RestartNodeNoUnlock restarts a given node without unlocking its wallet.
func (h *HarnessTest) RestartNodeNoUnlock(hn *node.HarnessNode) {
err := h.manager.restartNode(h.runCtx, hn, nil)
require.NoErrorf(h, err, "failed to restart node %s", hn.Name())
}
// RestartNodeWithChanBackups restarts a given node with the specified channel
// backups.
func (h *HarnessTest) RestartNodeWithChanBackups(hn *node.HarnessNode,
chanBackups ...*lnrpc.ChanBackupSnapshot) {
err := h.manager.restartNode(h.runCtx, hn, nil)
require.NoErrorf(h, err, "failed to restart node %s", hn.Name())
err = h.manager.unlockNode(hn, chanBackups...)
require.NoErrorf(h, err, "failed to unlock node %s", hn.Name())
// Give the node some time to catch up with the chain before we
// continue with the tests.
h.WaitForBlockchainSync(hn)
}
// RestartNodeWithExtraArgs updates the node's config and restarts it.
func (h *HarnessTest) RestartNodeWithExtraArgs(hn *node.HarnessNode,
extraArgs []string) {
hn.SetExtraArgs(extraArgs)
h.RestartNode(hn)
}
// NewNodeWithSeed fully initializes a new HarnessNode after creating a fresh
// aezeed. The provided password is used as both the aezeed password and the
// wallet password. The generated mnemonic is returned along with the
// initialized harness node.
func (h *HarnessTest) NewNodeWithSeed(name string,
extraArgs []string, password []byte,
statelessInit bool) (*node.HarnessNode, []string, []byte) {
// Create a request to generate a new aezeed. The new seed will have
// the same password as the internal wallet.
req := &lnrpc.GenSeedRequest{
AezeedPassphrase: password,
SeedEntropy: nil,
}
return h.newNodeWithSeed(name, extraArgs, req, statelessInit)
}
// newNodeWithSeed creates and initializes a new HarnessNode such that it'll be
// ready to accept RPC calls. A `GenSeedRequest` is needed to generate the
// seed.
func (h *HarnessTest) newNodeWithSeed(name string,
extraArgs []string, req *lnrpc.GenSeedRequest,
statelessInit bool) (*node.HarnessNode, []string, []byte) {
node, err := h.manager.newNode(
h.T, name, extraArgs, req.AezeedPassphrase, true,
)
require.NoErrorf(h, err, "unable to create new node for %s", name)
// Start the node with seed only, which will only create the `State`
// and `WalletUnlocker` clients.
err = node.StartWithNoAuth(h.runCtx)
require.NoErrorf(h, err, "failed to start node %s", node.Name())
// Generate a new seed.
genSeedResp := node.RPC.GenSeed(req)
// With the seed created, construct the init request to the node,
// including the newly generated seed.
initReq := &lnrpc.InitWalletRequest{
WalletPassword: req.AezeedPassphrase,
CipherSeedMnemonic: genSeedResp.CipherSeedMnemonic,
AezeedPassphrase: req.AezeedPassphrase,
StatelessInit: statelessInit,
}
// Pass the init request via rpc to finish unlocking the node. This
// will also initialize the macaroon-authenticated LightningClient.
adminMac, err := h.manager.initWalletAndNode(node, initReq)
require.NoErrorf(h, err, "failed to unlock and init node %s",
node.Name())
// In stateless initialization mode we get a macaroon back that we have
// to return to the test, otherwise gRPC calls won't be possible since
// there are no macaroon files created in that mode.
// In stateful init the admin macaroon will just be nil.
return node, genSeedResp.CipherSeedMnemonic, adminMac
}
// RestoreNodeWithSeed fully initializes a HarnessNode using a chosen mnemonic,
// password, recovery window, and optionally a set of static channel backups.
// After providing the initialization request to unlock the node, this method
// will finish initializing the LightningClient such that the HarnessNode can
// be used for regular rpc operations.
func (h *HarnessTest) RestoreNodeWithSeed(name string, extraArgs []string,
password []byte, mnemonic []string, rootKey string,
recoveryWindow int32,
chanBackups *lnrpc.ChanBackupSnapshot) *node.HarnessNode {
n, err := h.manager.newNode(h.T, name, extraArgs, password, true)
require.NoErrorf(h, err, "unable to create new node for %s", name)
// Start the node with seed only, which will only create the `State`
// and `WalletUnlocker` clients.
err = n.StartWithNoAuth(h.runCtx)
require.NoErrorf(h, err, "failed to start node %s", n.Name())
// Create the wallet.
initReq := &lnrpc.InitWalletRequest{
WalletPassword: password,
CipherSeedMnemonic: mnemonic,
AezeedPassphrase: password,
ExtendedMasterKey: rootKey,
RecoveryWindow: recoveryWindow,
ChannelBackups: chanBackups,
}
_, err = h.manager.initWalletAndNode(n, initReq)
require.NoErrorf(h, err, "failed to unlock and init node %s",
n.Name())
return n
}
// NewNodeEtcd starts a new node with seed that'll use an external etcd
// database as its storage. The passed cluster flag indicates that we'd like
// the node to join the cluster leader election. We won't wait until RPC is
// available (this is useful when the node is not expected to become the leader
// right away).
func (h *HarnessTest) NewNodeEtcd(name string, etcdCfg *etcd.Config,
password []byte, cluster bool,
leaderSessionTTL int) *node.HarnessNode {
// We don't want to use the embedded etcd instance.
h.manager.dbBackend = node.BackendBbolt
extraArgs := node.ExtraArgsEtcd(
etcdCfg, name, cluster, leaderSessionTTL,
)
node, err := h.manager.newNode(h.T, name, extraArgs, password, true)
require.NoError(h, err, "failed to create new node with etcd")
// Start the node daemon only.
err = node.StartLndCmd(h.runCtx)
require.NoError(h, err, "failed to start node %s", node.Name())
return node
}
// NewNodeWithSeedEtcd starts a new node with seed that'll use an external etcd
// database as its storage. The passed cluster flag indicates that we'd like
// the node to join the cluster leader election.
func (h *HarnessTest) NewNodeWithSeedEtcd(name string, etcdCfg *etcd.Config,
password []byte, statelessInit, cluster bool,
leaderSessionTTL int) (*node.HarnessNode, []string, []byte) {
// We don't want to use the embedded etcd instance.
h.manager.dbBackend = node.BackendBbolt
// Create a request to generate a new aezeed. The new seed will have
// the same password as the internal wallet.
req := &lnrpc.GenSeedRequest{
AezeedPassphrase: password,
SeedEntropy: nil,
}
extraArgs := node.ExtraArgsEtcd(
etcdCfg, name, cluster, leaderSessionTTL,
)
return h.newNodeWithSeed(name, extraArgs, req, statelessInit)
}
// NewNodeRemoteSigner creates a new remote signer node and asserts its
// creation.
func (h *HarnessTest) NewNodeRemoteSigner(name string, extraArgs []string,
password []byte, watchOnly *lnrpc.WatchOnly) *node.HarnessNode {
hn, err := h.manager.newNode(h.T, name, extraArgs, password, true)
require.NoErrorf(h, err, "unable to create new node for %s", name)
err = hn.StartWithNoAuth(h.runCtx)
require.NoError(h, err, "failed to start node %s", name)
// With the seed created, construct the init request to the node,
// including the newly generated seed.
initReq := &lnrpc.InitWalletRequest{
WalletPassword: password,
WatchOnly: watchOnly,
}
// Pass the init request via rpc to finish unlocking the node. This
// will also initialize the macaroon-authenticated LightningClient.
_, err = h.manager.initWalletAndNode(hn, initReq)
require.NoErrorf(h, err, "failed to init node %s", name)
return hn
}
// KillNode kills the node and waits for the node process to stop.
func (h *HarnessTest) KillNode(hn *node.HarnessNode) {
delete(h.manager.activeNodes, hn.Cfg.NodeID)
h.Logf("Manually killing the node %s", hn.Name())
require.NoErrorf(h, hn.KillAndWait(), "%s: kill got error", hn.Name())
}
// SetFeeEstimate sets a fee rate to be returned from fee estimator.
//
// NOTE: this method will set the fee rate for a conf target of 1, which is the
// fallback fee rate for a `WebAPIEstimator` if a higher conf target's fee rate
// is not set. This means if the fee rate for conf target 6 is set, the fee
// estimator will use that value instead.
func (h *HarnessTest) SetFeeEstimate(fee chainfee.SatPerKWeight) {
h.feeService.SetFeeRate(fee, 1)
}
// SetFeeEstimateWithConf sets a fee rate of a specified conf target to be
// returned from fee estimator.
func (h *HarnessTest) SetFeeEstimateWithConf(
fee chainfee.SatPerKWeight, conf uint32) {
h.feeService.SetFeeRate(fee, conf)
}
// SetMinRelayFeerate sets a min relay fee rate to be returned from fee
// estimator.
func (h *HarnessTest) SetMinRelayFeerate(fee chainfee.SatPerKVByte) {
h.feeService.SetMinRelayFeerate(fee)
}
// validateNodeState checks that the node doesn't have any uncleaned states
// which will affect its following tests.
func (h *HarnessTest) validateNodeState(hn *node.HarnessNode) error {
errStr := func(subject string) error {
return fmt.Errorf("%s: found %s channels, please close "+
"them properly", hn.Name(), subject)
}
// If the node still has open channels, it's most likely that the
// current test didn't close it properly.
if hn.State.OpenChannel.Active != 0 {
return errStr("active")
}
if hn.State.OpenChannel.Public != 0 {
return errStr("public")
}
if hn.State.OpenChannel.Private != 0 {
return errStr("private")
}
if hn.State.OpenChannel.Pending != 0 {
return errStr("pending open")
}
// The number of pending force close channels should be zero.
if hn.State.CloseChannel.PendingForceClose != 0 {
return errStr("pending force")
}
// The number of waiting close channels should be zero.
if hn.State.CloseChannel.WaitingClose != 0 {
return errStr("waiting close")
}
// Ths number of payments should be zero.
if hn.State.Payment.Total != 0 {
return fmt.Errorf("%s: found uncleaned payments, please "+
"delete all of them properly", hn.Name())
}
// The number of public edges should be zero.
if hn.State.Edge.Public != 0 {
return fmt.Errorf("%s: found active public egdes, please "+
"clean them properly", hn.Name())
}
// The number of edges should be zero.
if hn.State.Edge.Total != 0 {
return fmt.Errorf("%s: found active edges, please "+
"clean them properly", hn.Name())
}
return nil
}
// GetChanPointFundingTxid takes a channel point and converts it into a chain
// hash.
func (h *HarnessTest) GetChanPointFundingTxid(
cp *lnrpc.ChannelPoint) chainhash.Hash {
txid, err := lnrpc.GetChanPointFundingTxid(cp)
require.NoError(h, err, "unable to get txid")
return *txid
}
// OutPointFromChannelPoint creates an outpoint from a given channel point.
func (h *HarnessTest) OutPointFromChannelPoint(
cp *lnrpc.ChannelPoint) wire.OutPoint {
txid := h.GetChanPointFundingTxid(cp)
return wire.OutPoint{
Hash: txid,
Index: cp.OutputIndex,
}
}
// OpenChannelParams houses the params to specify when opening a new channel.
type OpenChannelParams struct {
// Amt is the local amount being put into the channel.
Amt btcutil.Amount
// PushAmt is the amount that should be pushed to the remote when the
// channel is opened.
PushAmt btcutil.Amount
// Private is a boolan indicating whether the opened channel should be
// private.
Private bool
// SpendUnconfirmed is a boolean indicating whether we can utilize
// unconfirmed outputs to fund the channel.
SpendUnconfirmed bool
// MinHtlc is the htlc_minimum_msat value set when opening the channel.
MinHtlc lnwire.MilliSatoshi
// RemoteMaxHtlcs is the remote_max_htlcs value set when opening the
// channel, restricting the number of concurrent HTLCs the remote party
// can add to a commitment.
RemoteMaxHtlcs uint16
// FundingShim is an optional funding shim that the caller can specify
// in order to modify the channel funding workflow.
FundingShim *lnrpc.FundingShim
// SatPerVByte is the amount of satoshis to spend in chain fees per
// virtual byte of the transaction.
SatPerVByte btcutil.Amount
// ConfTarget is the number of blocks that the funding transaction
// should be confirmed in.
ConfTarget fn.Option[int32]
// CommitmentType is the commitment type that should be used for the
// channel to be opened.
CommitmentType lnrpc.CommitmentType
// ZeroConf is used to determine if the channel will be a zero-conf
// channel. This only works if the explicit negotiation is used with
// anchors or script enforced leases.
ZeroConf bool
// ScidAlias denotes whether the channel will be an option-scid-alias
// channel type negotiation.
ScidAlias bool
// BaseFee is the channel base fee applied during the channel
// announcement phase.
BaseFee uint64
// FeeRate is the channel fee rate in ppm applied during the channel
// announcement phase.
FeeRate uint64
// UseBaseFee, if set, instructs the downstream logic to apply the
// user-specified channel base fee to the channel update announcement.
// If set to false it avoids applying a base fee of 0 and instead
// activates the default configured base fee.
UseBaseFee bool
// UseFeeRate, if set, instructs the downstream logic to apply the
// user-specified channel fee rate to the channel update announcement.
// If set to false it avoids applying a fee rate of 0 and instead
// activates the default configured fee rate.
UseFeeRate bool
// FundMax is a boolean indicating whether the channel should be funded
// with the maximum possible amount from the wallet.
FundMax bool
// An optional note-to-self containing some useful information about the
// channel. This is stored locally only, and is purely for reference. It
// has no bearing on the channel's operation. Max allowed length is 500
// characters.
Memo string
// Outpoints is a list of client-selected outpoints that should be used
// for funding a channel. If Amt is specified then this amount is
// allocated from the sum of outpoints towards funding. If the
// FundMax flag is specified the entirety of selected funds is
// allocated towards channel funding.
Outpoints []*lnrpc.OutPoint
// CloseAddress sets the upfront_shutdown_script parameter during
// channel open. It is expected to be encoded as a bitcoin address.
CloseAddress string
}
// prepareOpenChannel waits for both nodes to be synced to chain and returns an
// OpenChannelRequest.
func (h *HarnessTest) prepareOpenChannel(srcNode, destNode *node.HarnessNode,
p OpenChannelParams) *lnrpc.OpenChannelRequest {
// Wait until srcNode and destNode have the latest chain synced.
// Otherwise, we may run into a check within the funding manager that
// prevents any funding workflows from being kicked off if the chain
// isn't yet synced.
h.WaitForBlockchainSync(srcNode)
h.WaitForBlockchainSync(destNode)
// Specify the minimal confirmations of the UTXOs used for channel
// funding.
minConfs := int32(1)
if p.SpendUnconfirmed {
minConfs = 0
}
// Get the requested conf target. If not set, default to 6.
confTarget := p.ConfTarget.UnwrapOr(6)
// If there's fee rate set, unset the conf target.
if p.SatPerVByte != 0 {
confTarget = 0
}
// Prepare the request.
return &lnrpc.OpenChannelRequest{
NodePubkey: destNode.PubKey[:],
LocalFundingAmount: int64(p.Amt),
PushSat: int64(p.PushAmt),
Private: p.Private,
TargetConf: confTarget,
MinConfs: minConfs,
SpendUnconfirmed: p.SpendUnconfirmed,
MinHtlcMsat: int64(p.MinHtlc),
RemoteMaxHtlcs: uint32(p.RemoteMaxHtlcs),
FundingShim: p.FundingShim,
SatPerVbyte: uint64(p.SatPerVByte),
CommitmentType: p.CommitmentType,
ZeroConf: p.ZeroConf,
ScidAlias: p.ScidAlias,
BaseFee: p.BaseFee,
FeeRate: p.FeeRate,
UseBaseFee: p.UseBaseFee,
UseFeeRate: p.UseFeeRate,
FundMax: p.FundMax,
Memo: p.Memo,
Outpoints: p.Outpoints,
CloseAddress: p.CloseAddress,
}
}
// OpenChannelAssertPending attempts to open a channel between srcNode and
// destNode with the passed channel funding parameters. Once the `OpenChannel`
// is called, it will consume the first event it receives from the open channel
// client and asserts it's a channel pending event.
func (h *HarnessTest) openChannelAssertPending(srcNode,
destNode *node.HarnessNode,
p OpenChannelParams) (*lnrpc.PendingUpdate, rpc.OpenChanClient) {
// Prepare the request and open the channel.
openReq := h.prepareOpenChannel(srcNode, destNode, p)
respStream := srcNode.RPC.OpenChannel(openReq)
// Consume the "channel pending" update. This waits until the node
// notifies us that the final message in the channel funding workflow
// has been sent to the remote node.
resp := h.ReceiveOpenChannelUpdate(respStream)
// Check that the update is channel pending.
update, ok := resp.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
require.Truef(h, ok, "expected channel pending: update, instead got %v",
resp)
return update.ChanPending, respStream
}
// OpenChannelAssertPending attempts to open a channel between srcNode and
// destNode with the passed channel funding parameters. Once the `OpenChannel`
// is called, it will consume the first event it receives from the open channel
// client and asserts it's a channel pending event. It returns the
// `PendingUpdate`.
func (h *HarnessTest) OpenChannelAssertPending(srcNode,
destNode *node.HarnessNode, p OpenChannelParams) *lnrpc.PendingUpdate {
resp, _ := h.openChannelAssertPending(srcNode, destNode, p)
return resp
}
// OpenChannelAssertStream attempts to open a channel between srcNode and
// destNode with the passed channel funding parameters. Once the `OpenChannel`
// is called, it will consume the first event it receives from the open channel
// client and asserts it's a channel pending event. It returns the open channel
// stream.
func (h *HarnessTest) OpenChannelAssertStream(srcNode,
destNode *node.HarnessNode, p OpenChannelParams) rpc.OpenChanClient {
_, stream := h.openChannelAssertPending(srcNode, destNode, p)
return stream
}
// OpenChannel attempts to open a channel with the specified parameters
// extended from Alice to Bob. Additionally, for public channels, it will mine
// extra blocks so they are announced to the network. In specific, the
// following items are asserted,
// - for non-zero conf channel, 1 blocks will be mined to confirm the funding
// tx.
// - both nodes should see the channel edge update in their network graph.
// - both nodes can report the status of the new channel from ListChannels.
// - extra blocks are mined if it's a public channel.
func (h *HarnessTest) OpenChannel(alice, bob *node.HarnessNode,
p OpenChannelParams) *lnrpc.ChannelPoint {
// First, open the channel without announcing it.
cp := h.OpenChannelNoAnnounce(alice, bob, p)
// If this is a private channel, there's no need to mine extra blocks
// since it will never be announced to the network.
if p.Private {
return cp
}
// Mine extra blocks to announce the channel.
if p.ZeroConf {
// For a zero-conf channel, no blocks have been mined so we
// need to mine 6 blocks.
//
// Mine 1 block to confirm the funding transaction.
h.MineBlocksAndAssertNumTxes(numBlocksOpenChannel, 1)
} else {
// For a regular channel, 1 block has already been mined to
// confirm the funding transaction, so we mine 5 blocks.
h.MineBlocks(numBlocksOpenChannel - 1)
}
return cp
}
// OpenChannelNoAnnounce attempts to open a channel with the specified
// parameters extended from Alice to Bob without mining the necessary blocks to
// announce the channel. Additionally, the following items are asserted,
// - for non-zero conf channel, 1 blocks will be mined to confirm the funding
// tx.
// - both nodes should see the channel edge update in their network graph.
// - both nodes can report the status of the new channel from ListChannels.
func (h *HarnessTest) OpenChannelNoAnnounce(alice, bob *node.HarnessNode,
p OpenChannelParams) *lnrpc.ChannelPoint {
chanOpenUpdate := h.OpenChannelAssertStream(alice, bob, p)
// Open a zero conf channel.
if p.ZeroConf {
return h.openChannelZeroConf(alice, bob, chanOpenUpdate)
}
// Open a non-zero conf channel.
return h.openChannel(alice, bob, chanOpenUpdate)
}
// openChannel attempts to open a channel with the specified parameters
// extended from Alice to Bob. Additionally, the following items are asserted,
// - 1 block is mined and the funding transaction should be found in it.
// - both nodes should see the channel edge update in their network graph.
// - both nodes can report the status of the new channel from ListChannels.
func (h *HarnessTest) openChannel(alice, bob *node.HarnessNode,
stream rpc.OpenChanClient) *lnrpc.ChannelPoint {
// Mine 1 block to confirm the funding transaction.
block := h.MineBlocksAndAssertNumTxes(1, 1)[0]
// Wait for the channel open event.
fundingChanPoint := h.WaitForChannelOpenEvent(stream)
// Check that the funding tx is found in the first block.
fundingTxID := h.GetChanPointFundingTxid(fundingChanPoint)
h.AssertTxInBlock(block, fundingTxID)
// Check that both alice and bob have seen the channel from their
// network topology.
h.AssertChannelInGraph(alice, fundingChanPoint)
h.AssertChannelInGraph(bob, fundingChanPoint)
// Check that the channel can be seen in their ListChannels.
h.AssertChannelExists(alice, fundingChanPoint)
h.AssertChannelExists(bob, fundingChanPoint)
return fundingChanPoint
}
// openChannelZeroConf attempts to open a channel with the specified parameters
// extended from Alice to Bob. Additionally, the following items are asserted,
// - both nodes should see the channel edge update in their network graph.
// - both nodes can report the status of the new channel from ListChannels.
func (h *HarnessTest) openChannelZeroConf(alice, bob *node.HarnessNode,
stream rpc.OpenChanClient) *lnrpc.ChannelPoint {
// Wait for the channel open event.
fundingChanPoint := h.WaitForChannelOpenEvent(stream)
// Check that both alice and bob have seen the channel from their
// network topology.
h.AssertChannelInGraph(alice, fundingChanPoint)
h.AssertChannelInGraph(bob, fundingChanPoint)
// Finally, check that the channel can be seen in their ListChannels.
h.AssertChannelExists(alice, fundingChanPoint)
h.AssertChannelExists(bob, fundingChanPoint)
return fundingChanPoint
}
// OpenChannelAssertErr opens a channel between node srcNode and destNode,
// asserts that the expected error is returned from the channel opening.
func (h *HarnessTest) OpenChannelAssertErr(srcNode, destNode *node.HarnessNode,
p OpenChannelParams, expectedErr error) {
// Prepare the request and open the channel.
openReq := h.prepareOpenChannel(srcNode, destNode, p)
respStream := srcNode.RPC.OpenChannel(openReq)
// Receive an error to be sent from the stream.
_, err := h.receiveOpenChannelUpdate(respStream)
require.NotNil(h, err, "expected channel opening to fail")
// Use string comparison here as we haven't codified all the RPC errors
// yet.
require.Containsf(h, err.Error(), expectedErr.Error(), "unexpected "+
"error returned, want %v, got %v", expectedErr, err)
}
// closeChannelOpts holds the options for closing a channel.
type closeChannelOpts struct {
feeRate fn.Option[chainfee.SatPerVByte]
// localTxOnly is a boolean indicating if we should only attempt to
// consume close pending notifications for the local transaction.
localTxOnly bool
// skipMempoolCheck is a boolean indicating if we should skip the normal
// mempool check after a coop close.
skipMempoolCheck bool
// errString is an expected error. If this is non-blank, then we'll
// assert that the coop close wasn't possible, and returns an error that
// contains this err string.
errString string
}
// CloseChanOpt is a functional option to modify the way we close a channel.
type CloseChanOpt func(*closeChannelOpts)
// WithCoopCloseFeeRate is a functional option to set the fee rate for a coop
// close attempt.
func WithCoopCloseFeeRate(rate chainfee.SatPerVByte) CloseChanOpt {
return func(o *closeChannelOpts) {
o.feeRate = fn.Some(rate)
}
}
// WithLocalTxNotify is a functional option to indicate that we should only
// notify for the local txn. This is useful for the RBF coop close type, as
// it'll notify for both local and remote txns.
func WithLocalTxNotify() CloseChanOpt {
return func(o *closeChannelOpts) {
o.localTxOnly = true
}
}
// WithSkipMempoolCheck is a functional option to indicate that we should skip
// the mempool check. This can be used when a coop close iteration may not
// result in a newly broadcast transaction.
func WithSkipMempoolCheck() CloseChanOpt {
return func(o *closeChannelOpts) {
o.skipMempoolCheck = true
}
}
// WithExpectedErrString is a functional option that can be used to assert that
// an error occurs during the coop close process.
func WithExpectedErrString(errString string) CloseChanOpt {
return func(o *closeChannelOpts) {
o.errString = errString
}
}
// defaultCloseOpts returns the set of default close options.
func defaultCloseOpts() *closeChannelOpts {
return &closeChannelOpts{}
}
// CloseChannelAssertPending attempts to close the channel indicated by the
// passed channel point, initiated by the passed node. Once the CloseChannel
// rpc is called, it will consume one event and assert it's a close pending
// event. In addition, it will check that the closing tx can be found in the
// mempool.
func (h *HarnessTest) CloseChannelAssertPending(hn *node.HarnessNode,
cp *lnrpc.ChannelPoint, force bool,
opts ...CloseChanOpt) (rpc.CloseChanClient, *lnrpc.CloseStatusUpdate) {
closeOpts := defaultCloseOpts()
for _, optFunc := range opts {
optFunc(closeOpts)
}
// Calls the rpc to close the channel.
closeReq := &lnrpc.CloseChannelRequest{
ChannelPoint: cp,
Force: force,
NoWait: true,
}
closeOpts.feeRate.WhenSome(func(feeRate chainfee.SatPerVByte) {
closeReq.SatPerVbyte = uint64(feeRate)
})
var (
stream rpc.CloseChanClient
event *lnrpc.CloseStatusUpdate
err error
)
// Consume the "channel close" update in order to wait for the closing
// transaction to be broadcast, then wait for the closing tx to be seen
// within the network.
stream = hn.RPC.CloseChannel(closeReq)
_, err = h.ReceiveCloseChannelUpdate(stream)
require.NoError(h, err, "close channel update got error: %v", err)
var closeTxid *chainhash.Hash
for {
event, err = h.ReceiveCloseChannelUpdate(stream)
if err != nil {
h.Logf("Test: %s, close channel got error: %v",
h.manager.currentTestCase, err)
}
if err != nil && closeOpts.errString == "" {
require.NoError(h, err, "retry closing channel failed")
} else if err != nil && closeOpts.errString != "" {
require.ErrorContains(h, err, closeOpts.errString)
return nil, nil
}
pendingClose, ok := event.Update.(*lnrpc.CloseStatusUpdate_ClosePending) //nolint:ll
require.Truef(h, ok, "expected channel close "+
"update, instead got %v", pendingClose)
if !pendingClose.ClosePending.LocalCloseTx &&
closeOpts.localTxOnly {
continue
}
notifyRate := pendingClose.ClosePending.FeePerVbyte
if closeOpts.localTxOnly &&
notifyRate != int64(closeReq.SatPerVbyte) {
continue
}
closeTxid, err = chainhash.NewHash(
pendingClose.ClosePending.Txid,
)
require.NoErrorf(h, err, "unable to decode closeTxid: %v",
pendingClose.ClosePending.Txid)
break
}
if !closeOpts.skipMempoolCheck {
// Assert the closing tx is in the mempool.
h.miner.AssertTxInMempool(*closeTxid)
}
return stream, event
}
// CloseChannel attempts to coop close a non-anchored channel identified by the
// passed channel point owned by the passed harness node. The following items
// are asserted,
// 1. a close pending event is sent from the close channel client.
// 2. the closing tx is found in the mempool.
// 3. the node reports the channel being waiting to close.
// 4. a block is mined and the closing tx should be found in it.
// 5. the node reports zero waiting close channels.
// 6. the node receives a topology update regarding the channel close.
func (h *HarnessTest) CloseChannel(hn *node.HarnessNode,
cp *lnrpc.ChannelPoint) chainhash.Hash {
stream, _ := h.CloseChannelAssertPending(hn, cp, false)
return h.AssertStreamChannelCoopClosed(hn, cp, false, stream)
}
// ForceCloseChannel attempts to force close a non-anchored channel identified
// by the passed channel point owned by the passed harness node. The following
// items are asserted,
// 1. a close pending event is sent from the close channel client.
// 2. the closing tx is found in the mempool.
// 3. the node reports the channel being waiting to close.
// 4. a block is mined and the closing tx should be found in it.
// 5. the node reports zero waiting close channels.
// 6. the node receives a topology update regarding the channel close.
// 7. mine DefaultCSV-1 blocks.
// 8. the node reports zero pending force close channels.
func (h *HarnessTest) ForceCloseChannel(hn *node.HarnessNode,
cp *lnrpc.ChannelPoint) chainhash.Hash {
stream, _ := h.CloseChannelAssertPending(hn, cp, true)
closingTxid := h.AssertStreamChannelForceClosed(hn, cp, false, stream)
// Cleanup the force close.
h.CleanupForceClose(hn)
return closingTxid
}
// CloseChannelAssertErr closes the given channel and asserts an error
// returned.
func (h *HarnessTest) CloseChannelAssertErr(hn *node.HarnessNode,
req *lnrpc.CloseChannelRequest) error {
// Calls the rpc to close the channel.
stream := hn.RPC.CloseChannel(req)
// Consume the "channel close" update in order to wait for the closing
// transaction to be broadcast, then wait for the closing tx to be seen
// within the network.
_, err := h.ReceiveCloseChannelUpdate(stream)
require.Errorf(h, err, "%s: expect close channel to return an error",
hn.Name())
return err
}
// IsNeutrinoBackend returns a bool indicating whether the node is using a
// neutrino as its backend. This is useful when we want to skip certain tests
// which cannot be done with a neutrino backend.
func (h *HarnessTest) IsNeutrinoBackend() bool {
return h.manager.chainBackend.Name() == NeutrinoBackendName
}
// fundCoins attempts to send amt satoshis from the internal mining node to the
// targeted lightning node. The confirmed boolean indicates whether the
// transaction that pays to the target should confirm. For neutrino backend,
// the `confirmed` param is ignored.
func (h *HarnessTest) fundCoins(amt btcutil.Amount, target *node.HarnessNode,
addrType lnrpc.AddressType, confirmed bool) *wire.MsgTx {
initialBalance := target.RPC.WalletBalance()
// First, obtain an address from the target lightning node, preferring
// to receive a p2wkh address s.t the output can immediately be used as
// an input to a funding transaction.
req := &lnrpc.NewAddressRequest{Type: addrType}
resp := target.RPC.NewAddress(req)
addr := h.DecodeAddress(resp.Address)
addrScript := h.PayToAddrScript(addr)
// Generate a transaction which creates an output to the target
// pkScript of the desired amount.
output := &wire.TxOut{
PkScript: addrScript,
Value: int64(amt),
}
txid := h.miner.SendOutput(output, defaultMinerFeeRate)
// Get the funding tx.
tx := h.GetRawTransaction(*txid)
msgTx := tx.MsgTx()
// Since neutrino doesn't support unconfirmed outputs, skip this check.
if !h.IsNeutrinoBackend() {
expectedBalance := btcutil.Amount(
initialBalance.UnconfirmedBalance,
) + amt
h.WaitForBalanceUnconfirmed(target, expectedBalance)
}
// If the transaction should remain unconfirmed, then we'll wait until
// the target node's unconfirmed balance reflects the expected balance
// and exit.
if !confirmed {
return msgTx
}
// Otherwise, we'll generate 1 new blocks to ensure the output gains a
// sufficient number of confirmations and wait for the balance to
// reflect what's expected.
h.MineBlockWithTx(msgTx)
expectedBalance := btcutil.Amount(initialBalance.ConfirmedBalance) + amt
h.WaitForBalanceConfirmed(target, expectedBalance)
return msgTx
}
// FundCoins attempts to send amt satoshis from the internal mining node to the
// targeted lightning node using a P2WKH address. 1 blocks are mined after in
// order to confirm the transaction.
func (h *HarnessTest) FundCoins(amt btcutil.Amount,
hn *node.HarnessNode) *wire.MsgTx {
return h.fundCoins(amt, hn, lnrpc.AddressType_WITNESS_PUBKEY_HASH, true)
}
// FundCoinsUnconfirmed attempts to send amt satoshis from the internal mining
// node to the targeted lightning node using a P2WKH address. No blocks are
// mined after and the UTXOs are unconfirmed.
func (h *HarnessTest) FundCoinsUnconfirmed(amt btcutil.Amount,
hn *node.HarnessNode) *wire.MsgTx {
return h.fundCoins(
amt, hn, lnrpc.AddressType_WITNESS_PUBKEY_HASH, false,
)
}
// FundCoinsNP2WKH attempts to send amt satoshis from the internal mining node
// to the targeted lightning node using a NP2WKH address.
func (h *HarnessTest) FundCoinsNP2WKH(amt btcutil.Amount,
target *node.HarnessNode) *wire.MsgTx {
return h.fundCoins(
amt, target, lnrpc.AddressType_NESTED_PUBKEY_HASH, true,
)
}
// FundCoinsP2TR attempts to send amt satoshis from the internal mining node to
// the targeted lightning node using a P2TR address.
func (h *HarnessTest) FundCoinsP2TR(amt btcutil.Amount,
target *node.HarnessNode) *wire.MsgTx {
return h.fundCoins(amt, target, lnrpc.AddressType_TAPROOT_PUBKEY, true)
}
// FundNumCoins attempts to send the given number of UTXOs from the internal
// mining node to the targeted lightning node using a P2WKH address. Each UTXO
// has an amount of 1 BTC. 1 blocks are mined to confirm the tx.
func (h *HarnessTest) FundNumCoins(hn *node.HarnessNode, num int) {
// Get the initial balance first.
resp := hn.RPC.WalletBalance()
initialBalance := btcutil.Amount(resp.ConfirmedBalance)
const fundAmount = 1 * btcutil.SatoshiPerBitcoin
// Send out the outputs from the miner.
for i := 0; i < num; i++ {
h.createAndSendOutput(
hn, fundAmount, lnrpc.AddressType_WITNESS_PUBKEY_HASH,
)
}
// Wait for ListUnspent to show the correct number of unconfirmed
// UTXOs.
//
// Since neutrino doesn't support unconfirmed outputs, skip this check.
if !h.IsNeutrinoBackend() {
h.AssertNumUTXOsUnconfirmed(hn, num)
}
// Mine a block to confirm the transactions.
h.MineBlocksAndAssertNumTxes(1, num)
// Now block until the wallet have fully synced up.
totalAmount := btcutil.Amount(fundAmount * num)
expectedBalance := initialBalance + totalAmount
h.WaitForBalanceConfirmed(hn, expectedBalance)
}
// completePaymentRequestsAssertStatus sends payments from a node to complete
// all payment requests. This function does not return until all payments
// have reached the specified status.
func (h *HarnessTest) completePaymentRequestsAssertStatus(hn *node.HarnessNode,
paymentRequests []string, status lnrpc.Payment_PaymentStatus,
opts ...HarnessOpt) {
payOpts := defaultHarnessOpts()
for _, opt := range opts {
opt(&payOpts)
}
// Create a buffered chan to signal the results.
results := make(chan rpc.PaymentClient, len(paymentRequests))
// send sends a payment and asserts if it doesn't succeeded.
send := func(payReq string) {
req := &routerrpc.SendPaymentRequest{
PaymentRequest: payReq,
TimeoutSeconds: int32(wait.PaymentTimeout.Seconds()),
FeeLimitMsat: noFeeLimitMsat,
Amp: payOpts.useAMP,
}
stream := hn.RPC.SendPayment(req)
// Signal sent succeeded.
results <- stream
}
// Launch all payments simultaneously.
for _, payReq := range paymentRequests {
payReqCopy := payReq
go send(payReqCopy)
}
// Wait for all payments to report the expected status.
timer := time.After(wait.PaymentTimeout)
select {
case stream := <-results:
h.AssertPaymentStatusFromStream(stream, status)
case <-timer:
require.Fail(h, "timeout", "waiting payment results timeout")
}
}
// CompletePaymentRequests sends payments from a node to complete all payment
// requests. This function does not return until all payments successfully
// complete without errors.
func (h *HarnessTest) CompletePaymentRequests(hn *node.HarnessNode,
paymentRequests []string, opts ...HarnessOpt) {
h.completePaymentRequestsAssertStatus(
hn, paymentRequests, lnrpc.Payment_SUCCEEDED, opts...,
)
}
// CompletePaymentRequestsNoWait sends payments from a node to complete all
// payment requests without waiting for the results. Instead, it checks the
// number of updates in the specified channel has increased.
func (h *HarnessTest) CompletePaymentRequestsNoWait(hn *node.HarnessNode,
paymentRequests []string, chanPoint *lnrpc.ChannelPoint) {
// We start by getting the current state of the client's channels. This
// is needed to ensure the payments actually have been committed before
// we return.
oldResp := h.GetChannelByChanPoint(hn, chanPoint)
// Send payments and assert they are in-flight.
h.completePaymentRequestsAssertStatus(
hn, paymentRequests, lnrpc.Payment_IN_FLIGHT,
)
// We are not waiting for feedback in the form of a response, but we
// should still wait long enough for the server to receive and handle
// the send before cancelling the request. We wait for the number of
// updates to one of our channels has increased before we return.
err := wait.NoError(func() error {
newResp := h.GetChannelByChanPoint(hn, chanPoint)
// If this channel has an increased number of updates, we
// assume the payments are committed, and we can return.
if newResp.NumUpdates > oldResp.NumUpdates {
return nil
}
// Otherwise return an error as the NumUpdates are not
// increased.
return fmt.Errorf("%s: channel:%v not updated after sending "+
"payments, old updates: %v, new updates: %v", hn.Name(),
chanPoint, oldResp.NumUpdates, newResp.NumUpdates)
}, DefaultTimeout)
require.NoError(h, err, "timeout while checking for channel updates")
}
// OpenChannelPsbt attempts to open a channel between srcNode and destNode with
// the passed channel funding parameters. It will assert if the expected step
// of funding the PSBT is not received from the source node.
func (h *HarnessTest) OpenChannelPsbt(srcNode, destNode *node.HarnessNode,
p OpenChannelParams) (rpc.OpenChanClient, []byte) {
// Wait until srcNode and destNode have the latest chain synced.
// Otherwise, we may run into a check within the funding manager that
// prevents any funding workflows from being kicked off if the chain
// isn't yet synced.
h.WaitForBlockchainSync(srcNode)
h.WaitForBlockchainSync(destNode)
// Send the request to open a channel to the source node now. This will
// open a long-lived stream where we'll receive status updates about
// the progress of the channel.
// respStream := h.OpenChannelStreamAndAssert(srcNode, destNode, p)
req := &lnrpc.OpenChannelRequest{
NodePubkey: destNode.PubKey[:],
LocalFundingAmount: int64(p.Amt),
PushSat: int64(p.PushAmt),
Private: p.Private,
SpendUnconfirmed: p.SpendUnconfirmed,
MinHtlcMsat: int64(p.MinHtlc),
FundingShim: p.FundingShim,
CommitmentType: p.CommitmentType,
}
respStream := srcNode.RPC.OpenChannel(req)
// Consume the "PSBT funding ready" update. This waits until the node
// notifies us that the PSBT can now be funded.
resp := h.ReceiveOpenChannelUpdate(respStream)
upd, ok := resp.Update.(*lnrpc.OpenStatusUpdate_PsbtFund)
require.Truef(h, ok, "expected PSBT funding update, got %v", resp)
// Make sure the channel funding address has the correct type for the
// given commitment type.
fundingAddr, err := btcutil.DecodeAddress(
upd.PsbtFund.FundingAddress, miner.HarnessNetParams,
)
require.NoError(h, err)
switch p.CommitmentType {
case lnrpc.CommitmentType_SIMPLE_TAPROOT:
require.IsType(h, &btcutil.AddressTaproot{}, fundingAddr)
default:
require.IsType(
h, &btcutil.AddressWitnessScriptHash{}, fundingAddr,
)
}
return respStream, upd.PsbtFund.Psbt
}
// CleanupForceClose mines blocks to clean up the force close process. This is
// used for tests that are not asserting the expected behavior is found during
// the force close process, e.g., num of sweeps, etc. Instead, it provides a
// shortcut to move the test forward with a clean mempool.
func (h *HarnessTest) CleanupForceClose(hn *node.HarnessNode) {
// Wait for the channel to be marked pending force close.
h.AssertNumPendingForceClose(hn, 1)
// Mine enough blocks for the node to sweep its funds from the force
// closed channel. The commit sweep resolver is offers the input to the
// sweeper when it's force closed, and broadcast the sweep tx at
// defaulCSV-1.
//
// NOTE: we might empty blocks here as we don't know the exact number
// of blocks to mine. This may end up mining more blocks than needed.
h.MineEmptyBlocks(node.DefaultCSV - 1)
// Assert there is one pending sweep.
h.AssertNumPendingSweeps(hn, 1)
// The node should now sweep the funds, clean up by mining the sweeping
// tx.
h.MineBlocksAndAssertNumTxes(1, 1)
// Mine blocks to get any second level HTLC resolved. If there are no
// HTLCs, this will behave like h.AssertNumPendingCloseChannels.
h.mineTillForceCloseResolved(hn)
}
// CreatePayReqs is a helper method that will create a slice of payment
// requests for the given node.
func (h *HarnessTest) CreatePayReqs(hn *node.HarnessNode,
paymentAmt btcutil.Amount, numInvoices int,
routeHints ...*lnrpc.RouteHint) ([]string, [][]byte, []*lnrpc.Invoice) {
payReqs := make([]string, numInvoices)
rHashes := make([][]byte, numInvoices)
invoices := make([]*lnrpc.Invoice, numInvoices)
for i := 0; i < numInvoices; i++ {
preimage := h.Random32Bytes()
invoice := &lnrpc.Invoice{
Memo: "testing",
RPreimage: preimage,
Value: int64(paymentAmt),
RouteHints: routeHints,
}
resp := hn.RPC.AddInvoice(invoice)
// Set the payment address in the invoice so the caller can
// properly use it.
invoice.PaymentAddr = resp.PaymentAddr
payReqs[i] = resp.PaymentRequest
rHashes[i] = resp.RHash
invoices[i] = invoice
}
return payReqs, rHashes, invoices
}
// BackupDB creates a backup of the current database. It will stop the node
// first, copy the database files, and restart the node.
func (h *HarnessTest) BackupDB(hn *node.HarnessNode) {
restart := h.SuspendNode(hn)
err := hn.BackupDB()
require.NoErrorf(h, err, "%s: failed to backup db", hn.Name())
err = restart()
require.NoErrorf(h, err, "%s: failed to restart", hn.Name())
}
// RestartNodeAndRestoreDB restarts a given node with a callback to restore the
// db.
func (h *HarnessTest) RestartNodeAndRestoreDB(hn *node.HarnessNode) {
cb := func() error { return hn.RestoreDB() }
err := h.manager.restartNode(h.runCtx, hn, cb)
require.NoErrorf(h, err, "failed to restart node %s", hn.Name())
err = h.manager.unlockNode(hn)
require.NoErrorf(h, err, "failed to unlock node %s", hn.Name())
// Give the node some time to catch up with the chain before we
// continue with the tests.
h.WaitForBlockchainSync(hn)
}
// CleanShutDown is used to quickly end a test by shutting down all non-standby
// nodes and mining blocks to empty the mempool.
//
// NOTE: this method provides a faster exit for a test that involves force
// closures as the caller doesn't need to mine all the blocks to make sure the
// mempool is empty.
func (h *HarnessTest) CleanShutDown() {
// First, shutdown all nodes to prevent new transactions being created
// and fed into the mempool.
h.shutdownAllNodes()
// Now mine blocks till the mempool is empty.
h.cleanMempool()
}
// QueryChannelByChanPoint tries to find a channel matching the channel point
// and asserts. It returns the channel found.
func (h *HarnessTest) QueryChannelByChanPoint(hn *node.HarnessNode,
chanPoint *lnrpc.ChannelPoint,
opts ...ListChannelOption) *lnrpc.Channel {
channel, err := h.findChannel(hn, chanPoint, opts...)
require.NoError(h, err, "failed to query channel")
return channel
}
// SendPaymentAndAssertStatus sends a payment from the passed node and asserts
// the desired status is reached.
func (h *HarnessTest) SendPaymentAndAssertStatus(hn *node.HarnessNode,
req *routerrpc.SendPaymentRequest,
status lnrpc.Payment_PaymentStatus) *lnrpc.Payment {
stream := hn.RPC.SendPayment(req)
return h.AssertPaymentStatusFromStream(stream, status)
}
// SendPaymentAssertFail sends a payment from the passed node and asserts the
// payment is failed with the specified failure reason .
func (h *HarnessTest) SendPaymentAssertFail(hn *node.HarnessNode,
req *routerrpc.SendPaymentRequest,
reason lnrpc.PaymentFailureReason) *lnrpc.Payment {
payment := h.SendPaymentAndAssertStatus(hn, req, lnrpc.Payment_FAILED)
require.Equal(h, reason, payment.FailureReason,
"payment failureReason not matched")
return payment
}
// SendPaymentAssertSettled sends a payment from the passed node and asserts the
// payment is settled.
func (h *HarnessTest) SendPaymentAssertSettled(hn *node.HarnessNode,
req *routerrpc.SendPaymentRequest) *lnrpc.Payment {
return h.SendPaymentAndAssertStatus(hn, req, lnrpc.Payment_SUCCEEDED)
}
// SendPaymentAssertInflight sends a payment from the passed node and asserts
// the payment is inflight.
func (h *HarnessTest) SendPaymentAssertInflight(hn *node.HarnessNode,
req *routerrpc.SendPaymentRequest) *lnrpc.Payment {
return h.SendPaymentAndAssertStatus(hn, req, lnrpc.Payment_IN_FLIGHT)
}
// OpenChannelRequest is used to open a channel using the method
// OpenMultiChannelsAsync.
type OpenChannelRequest struct {
// Local is the funding node.
Local *node.HarnessNode
// Remote is the receiving node.
Remote *node.HarnessNode
// Param is the open channel params.
Param OpenChannelParams
// stream is the client created after calling OpenChannel RPC.
stream rpc.OpenChanClient
// result is a channel used to send the channel point once the funding
// has succeeded.
result chan *lnrpc.ChannelPoint
}
// OpenMultiChannelsAsync takes a list of OpenChannelRequest and opens them in
// batch. The channel points are returned in same the order of the requests
// once all of the channel open succeeded.
//
// NOTE: compared to open multiple channel sequentially, this method will be
// faster as it doesn't need to mine 6 blocks for each channel open. However,
// it does make debugging the logs more difficult as messages are intertwined.
func (h *HarnessTest) OpenMultiChannelsAsync(
reqs []*OpenChannelRequest) []*lnrpc.ChannelPoint {
// openChannel opens a channel based on the request.
openChannel := func(req *OpenChannelRequest) {
stream := h.OpenChannelAssertStream(
req.Local, req.Remote, req.Param,
)
req.stream = stream
}
// assertChannelOpen is a helper closure that asserts a channel is
// open.
assertChannelOpen := func(req *OpenChannelRequest) {
// Wait for the channel open event from the stream.
cp := h.WaitForChannelOpenEvent(req.stream)
if !req.Param.Private {
// Check that both alice and bob have seen the channel
// from their channel watch request.
h.AssertChannelInGraph(req.Local, cp)
h.AssertChannelInGraph(req.Remote, cp)
}
// Finally, check that the channel can be seen in their
// ListChannels.
h.AssertChannelExists(req.Local, cp)
h.AssertChannelExists(req.Remote, cp)
req.result <- cp
}
// Go through the requests and make the OpenChannel RPC call.
for _, r := range reqs {
openChannel(r)
}
// Mine one block to confirm all the funding transactions.
h.MineBlocksAndAssertNumTxes(1, len(reqs))
// Mine 5 more blocks so all the public channels are announced to the
// network.
h.MineBlocks(numBlocksOpenChannel - 1)
// Once the blocks are mined, we fire goroutines for each of the
// request to watch for the channel openning.
for _, r := range reqs {
r.result = make(chan *lnrpc.ChannelPoint, 1)
go assertChannelOpen(r)
}
// Finally, collect the results.
channelPoints := make([]*lnrpc.ChannelPoint, 0)
for _, r := range reqs {
select {
case cp := <-r.result:
channelPoints = append(channelPoints, cp)
case <-time.After(wait.ChannelOpenTimeout):
require.Failf(h, "timeout", "wait channel point "+
"timeout for channel %s=>%s", r.Local.Name(),
r.Remote.Name())
}
}
// Assert that we have the expected num of channel points.
require.Len(h, channelPoints, len(reqs),
"returned channel points not match")
return channelPoints
}
// ReceiveInvoiceUpdate waits until a message is received on the subscribe
// invoice stream or the timeout is reached.
func (h *HarnessTest) ReceiveInvoiceUpdate(
stream rpc.InvoiceUpdateClient) *lnrpc.Invoice {
chanMsg := make(chan *lnrpc.Invoice)
errChan := make(chan error)
go func() {
// Consume one message. This will block until the message is
// received.
resp, err := stream.Recv()
if err != nil {
errChan <- err
return
}
chanMsg <- resp
}()
select {
case <-time.After(DefaultTimeout):
require.Fail(h, "timeout", "timeout receiving invoice update")
case err := <-errChan:
require.Failf(h, "err from stream",
"received err from stream: %v", err)
case updateMsg := <-chanMsg:
return updateMsg
}
return nil
}
// CalculateTxFee retrieves parent transactions and reconstructs the fee paid.
func (h *HarnessTest) CalculateTxFee(tx *wire.MsgTx) btcutil.Amount {
var balance btcutil.Amount
for _, in := range tx.TxIn {
parentHash := in.PreviousOutPoint.Hash
rawTx := h.miner.GetRawTransaction(parentHash)
parent := rawTx.MsgTx()
value := parent.TxOut[in.PreviousOutPoint.Index].Value
balance += btcutil.Amount(value)
}
for _, out := range tx.TxOut {
balance -= btcutil.Amount(out.Value)
}
return balance
}
// CalculateTxWeight calculates the weight for a given tx.
//
// TODO(yy): use weight estimator to get more accurate result.
func (h *HarnessTest) CalculateTxWeight(tx *wire.MsgTx) lntypes.WeightUnit {
utx := btcutil.NewTx(tx)
return lntypes.WeightUnit(blockchain.GetTransactionWeight(utx))
}
// CalculateTxFeeRate calculates the fee rate for a given tx.
func (h *HarnessTest) CalculateTxFeeRate(
tx *wire.MsgTx) chainfee.SatPerKWeight {
w := h.CalculateTxWeight(tx)
fee := h.CalculateTxFee(tx)
return chainfee.NewSatPerKWeight(fee, w)
}
// CalculateTxesFeeRate takes a list of transactions and estimates the fee rate
// used to sweep them.
//
// NOTE: only used in current test file.
func (h *HarnessTest) CalculateTxesFeeRate(txns []*wire.MsgTx) int64 {
const scale = 1000
var totalWeight, totalFee int64
for _, tx := range txns {
utx := btcutil.NewTx(tx)
totalWeight += blockchain.GetTransactionWeight(utx)
fee := h.CalculateTxFee(tx)
totalFee += int64(fee)
}
feeRate := totalFee * scale / totalWeight
return feeRate
}
// AssertSweepFound looks up a sweep in a nodes list of broadcast sweeps and
// asserts it's found.
//
// NOTE: Does not account for node's internal state.
func (h *HarnessTest) AssertSweepFound(hn *node.HarnessNode,
sweep string, verbose bool, startHeight int32) {
err := wait.NoError(func() error {
// List all sweeps that alice's node had broadcast.
sweepResp := hn.RPC.ListSweeps(verbose, startHeight)
var found bool
if verbose {
found = findSweepInDetails(h, sweep, sweepResp)
} else {
found = findSweepInTxids(h, sweep, sweepResp)
}
if found {
return nil
}
return fmt.Errorf("sweep tx %v not found in resp %v", sweep,
sweepResp)
}, wait.DefaultTimeout)
require.NoError(h, err, "%s: timeout checking sweep tx", hn.Name())
}
func findSweepInTxids(ht *HarnessTest, sweepTxid string,
sweepResp *walletrpc.ListSweepsResponse) bool {
sweepTxIDs := sweepResp.GetTransactionIds()
require.NotNil(ht, sweepTxIDs, "expected transaction ids")
require.Nil(ht, sweepResp.GetTransactionDetails())
// Check that the sweep tx we have just produced is present.
for _, tx := range sweepTxIDs.TransactionIds {
if tx == sweepTxid {
return true
}
}
return false
}
func findSweepInDetails(ht *HarnessTest, sweepTxid string,
sweepResp *walletrpc.ListSweepsResponse) bool {
sweepDetails := sweepResp.GetTransactionDetails()
require.NotNil(ht, sweepDetails, "expected transaction details")
require.Nil(ht, sweepResp.GetTransactionIds())
for _, tx := range sweepDetails.Transactions {
if tx.TxHash == sweepTxid {
return true
}
}
return false
}
// QueryRoutesAndRetry attempts to keep querying a route until timeout is
// reached.
//
// NOTE: when a channel is opened, we may need to query multiple times to get
// it in our QueryRoutes RPC. This happens even after we check the channel is
// heard by the node using ht.AssertChannelOpen. Deep down, this is because our
// GraphTopologySubscription and QueryRoutes give different results regarding a
// specific channel, with the formal reporting it being open while the latter
// not, resulting GraphTopologySubscription acting "faster" than QueryRoutes.
// TODO(yy): make sure related subsystems share the same view on a given
// channel.
func (h *HarnessTest) QueryRoutesAndRetry(hn *node.HarnessNode,
req *lnrpc.QueryRoutesRequest) *lnrpc.QueryRoutesResponse {
var routes *lnrpc.QueryRoutesResponse
err := wait.NoError(func() error {
ctxt, cancel := context.WithCancel(h.runCtx)
defer cancel()
resp, err := hn.RPC.LN.QueryRoutes(ctxt, req)
if err != nil {
return fmt.Errorf("%s: failed to query route: %w",
hn.Name(), err)
}
routes = resp
return nil
}, DefaultTimeout)
require.NoError(h, err, "timeout querying routes")
return routes
}
// ReceiveHtlcInterceptor waits until a message is received on the htlc
// interceptor stream or the timeout is reached.
func (h *HarnessTest) ReceiveHtlcInterceptor(
stream rpc.InterceptorClient) *routerrpc.ForwardHtlcInterceptRequest {
chanMsg := make(chan *routerrpc.ForwardHtlcInterceptRequest)
errChan := make(chan error)
go func() {
// Consume one message. This will block until the message is
// received.
resp, err := stream.Recv()
if err != nil {
errChan <- err
return
}
chanMsg <- resp
}()
select {
case <-time.After(DefaultTimeout):
require.Fail(h, "timeout", "timeout intercepting htlc")
case err := <-errChan:
require.Failf(h, "err from HTLC interceptor stream",
"received err from HTLC interceptor stream: %v", err)
case updateMsg := <-chanMsg:
return updateMsg
}
return nil
}
// ReceiveInvoiceHtlcModification waits until a message is received on the
// invoice HTLC modifier stream or the timeout is reached.
func (h *HarnessTest) ReceiveInvoiceHtlcModification(
stream rpc.InvoiceHtlcModifierClient) *invoicesrpc.HtlcModifyRequest {
chanMsg := make(chan *invoicesrpc.HtlcModifyRequest)
errChan := make(chan error)
go func() {
// Consume one message. This will block until the message is
// received.
resp, err := stream.Recv()
if err != nil {
errChan <- err
return
}
chanMsg <- resp
}()
select {
case <-time.After(DefaultTimeout):
require.Fail(h, "timeout", "timeout invoice HTLC modifier")
case err := <-errChan:
require.Failf(h, "err from invoice HTLC modifier stream",
"received err from invoice HTLC modifier stream: %v",
err)
case updateMsg := <-chanMsg:
return updateMsg
}
return nil
}
// ReceiveChannelEvent waits until a message is received from the
// ChannelEventsClient stream or the timeout is reached.
func (h *HarnessTest) ReceiveChannelEvent(
stream rpc.ChannelEventsClient) *lnrpc.ChannelEventUpdate {
chanMsg := make(chan *lnrpc.ChannelEventUpdate)
errChan := make(chan error)
go func() {
// Consume one message. This will block until the message is
// received.
resp, err := stream.Recv()
if err != nil {
errChan <- err
return
}
chanMsg <- resp
}()
select {
case <-time.After(DefaultTimeout):
require.Fail(h, "timeout", "timeout intercepting htlc")
case err := <-errChan:
require.Failf(h, "err from stream",
"received err from stream: %v", err)
case updateMsg := <-chanMsg:
return updateMsg
}
return nil
}
// GetOutputIndex returns the output index of the given address in the given
// transaction.
func (h *HarnessTest) GetOutputIndex(txid chainhash.Hash, addr string) int {
// We'll then extract the raw transaction from the mempool in order to
// determine the index of the p2tr output.
tx := h.miner.GetRawTransaction(txid)
p2trOutputIndex := -1
for i, txOut := range tx.MsgTx().TxOut {
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
txOut.PkScript, h.miner.ActiveNet,
)
require.NoError(h, err)
if addrs[0].String() == addr {
p2trOutputIndex = i
}
}
require.Greater(h, p2trOutputIndex, -1)
return p2trOutputIndex
}
// SendCoins sends a coin from node A to node B with the given amount, returns
// the sending tx.
func (h *HarnessTest) SendCoins(a, b *node.HarnessNode,
amt btcutil.Amount) *wire.MsgTx {
// Create an address for Bob receive the coins.
req := &lnrpc.NewAddressRequest{
Type: lnrpc.AddressType_TAPROOT_PUBKEY,
}
resp := b.RPC.NewAddress(req)
// Send the coins from Alice to Bob. We should expect a tx to be
// broadcast and seen in the mempool.
sendReq := &lnrpc.SendCoinsRequest{
Addr: resp.Address,
Amount: int64(amt),
TargetConf: 6,
}
a.RPC.SendCoins(sendReq)
tx := h.GetNumTxsFromMempool(1)[0]
return tx
}
// SendCoins sends all coins from node A to node B, returns the sending tx.
func (h *HarnessTest) SendAllCoins(a, b *node.HarnessNode) *wire.MsgTx {
// Create an address for Bob receive the coins.
req := &lnrpc.NewAddressRequest{
Type: lnrpc.AddressType_TAPROOT_PUBKEY,
}
resp := b.RPC.NewAddress(req)
// Send the coins from Alice to Bob. We should expect a tx to be
// broadcast and seen in the mempool.
sendReq := &lnrpc.SendCoinsRequest{
Addr: resp.Address,
TargetConf: 6,
SendAll: true,
SpendUnconfirmed: true,
}
a.RPC.SendCoins(sendReq)
tx := h.GetNumTxsFromMempool(1)[0]
return tx
}
// CreateSimpleNetwork creates the number of nodes specified by the number of
// configs and makes a topology of `node1 -> node2 -> node3...`. Each node is
// created using the specified config, the neighbors are connected, and the
// channels are opened. Each node will be funded with a single UTXO of 1 BTC
// except the last one.
//
// For instance, to create a network with 2 nodes that share the same node
// config,
//
// cfg := []string{"--protocol.anchors"}
// cfgs := [][]string{cfg, cfg}
// params := OpenChannelParams{...}
// chanPoints, nodes := ht.CreateSimpleNetwork(cfgs, params)
//
// This will create two nodes and open an anchor channel between them.
func (h *HarnessTest) CreateSimpleNetwork(nodeCfgs [][]string,
p OpenChannelParams) ([]*lnrpc.ChannelPoint, []*node.HarnessNode) {
// Create new nodes.
nodes := h.createNodes(nodeCfgs)
var resp []*lnrpc.ChannelPoint
// Open zero-conf channels if specified.
if p.ZeroConf {
resp = h.openZeroConfChannelsForNodes(nodes, p)
} else {
// Open channels between the nodes.
resp = h.openChannelsForNodes(nodes, p)
}
return resp, nodes
}
// acceptChannel is used to accept a single channel that comes across. This
// should be run in a goroutine and is used to test nodes with the zero-conf
// feature bit.
func acceptChannel(t *testing.T, zeroConf bool, stream rpc.AcceptorClient) {
req, err := stream.Recv()
require.NoError(t, err)
resp := &lnrpc.ChannelAcceptResponse{
Accept: true,
PendingChanId: req.PendingChanId,
ZeroConf: zeroConf,
}
err = stream.Send(resp)
require.NoError(t, err)
}
// nodeNames defines a slice of human-reable names for the nodes created in the
// `createNodes` method. 8 nodes are defined here as by default we can only
// create this many nodes in one test.
var nodeNames = []string{
"Alice", "Bob", "Carol", "Dave", "Eve", "Frank", "Grace", "Heidi",
}
// createNodes creates the number of nodes specified by the number of configs.
// Each node is created using the specified config, the neighbors are
// connected.
func (h *HarnessTest) createNodes(nodeCfgs [][]string) []*node.HarnessNode {
// Get the number of nodes.
numNodes := len(nodeCfgs)
// Make sure we are creating a reasonable number of nodes.
require.LessOrEqual(h, numNodes, len(nodeNames), "too many nodes")
// Make a slice of nodes.
nodes := make([]*node.HarnessNode, numNodes)
// Create new nodes.
for i, nodeCfg := range nodeCfgs {
nodeName := nodeNames[i]
n := h.NewNode(nodeName, nodeCfg)
nodes[i] = n
}
// Connect the nodes in a chain.
for i := 1; i < len(nodes); i++ {
nodeA := nodes[i-1]
nodeB := nodes[i]
h.EnsureConnected(nodeA, nodeB)
}
// Fund all the nodes expect the last one.
for i := 0; i < len(nodes)-1; i++ {
node := nodes[i]
h.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, node)
}
// Mine 1 block to get the above coins confirmed.
h.MineBlocksAndAssertNumTxes(1, numNodes-1)
return nodes
}
// openChannelsForNodes takes a list of nodes and makes a topology of `node1 ->
// node2 -> node3...`.
func (h *HarnessTest) openChannelsForNodes(nodes []*node.HarnessNode,
p OpenChannelParams) []*lnrpc.ChannelPoint {
// Sanity check the params.
require.Greater(h, len(nodes), 1, "need at least 2 nodes")
// attachFundingShim is a helper closure that optionally attaches a
// funding shim to the open channel params and returns it.
attachFundingShim := func(
nodeA, nodeB *node.HarnessNode) OpenChannelParams {
// If this channel is not a script enforced lease channel,
// we'll do nothing and return the params.
leasedType := lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE
if p.CommitmentType != leasedType {
return p
}
// Otherwise derive the funding shim, attach it to the original
// open channel params and return it.
minerHeight := h.CurrentHeight()
thawHeight := minerHeight + thawHeightDelta
fundingShim, _ := h.DeriveFundingShim(
nodeA, nodeB, p.Amt, thawHeight, true, leasedType,
)
p.FundingShim = fundingShim
return p
}
// Open channels in batch to save blocks mined.
reqs := make([]*OpenChannelRequest, 0, len(nodes)-1)
for i := 0; i < len(nodes)-1; i++ {
nodeA := nodes[i]
nodeB := nodes[i+1]
// Optionally attach a funding shim to the open channel params.
p = attachFundingShim(nodeA, nodeB)
req := &OpenChannelRequest{
Local: nodeA,
Remote: nodeB,
Param: p,
}
reqs = append(reqs, req)
}
resp := h.OpenMultiChannelsAsync(reqs)
// If the channels are private, make sure the channel participants know
// the relevant channels.
if p.Private {
for i, chanPoint := range resp {
// Get the channel participants - for n channels we
// would have n+1 nodes.
nodeA, nodeB := nodes[i], nodes[i+1]
h.AssertChannelInGraph(nodeA, chanPoint)
h.AssertChannelInGraph(nodeB, chanPoint)
}
} else {
// Make sure the all nodes know all the channels if they are
// public.
for _, node := range nodes {
for _, chanPoint := range resp {
h.AssertChannelInGraph(node, chanPoint)
}
// Make sure every node has updated its cached graph
// about the edges as indicated in `DescribeGraph`.
h.AssertNumEdges(node, len(resp), false)
}
}
return resp
}
// openZeroConfChannelsForNodes takes a list of nodes and makes a topology of
// `node1 -> node2 -> node3...` with zero-conf channels.
func (h *HarnessTest) openZeroConfChannelsForNodes(nodes []*node.HarnessNode,
p OpenChannelParams) []*lnrpc.ChannelPoint {
// Sanity check the params.
require.True(h, p.ZeroConf, "zero-conf channels must be enabled")
require.Greater(h, len(nodes), 1, "need at least 2 nodes")
// We are opening numNodes-1 channels.
cancels := make([]context.CancelFunc, 0, len(nodes)-1)
// Create the channel acceptors.
for _, node := range nodes[1:] {
acceptor, cancel := node.RPC.ChannelAcceptor()
go acceptChannel(h.T, true, acceptor)
cancels = append(cancels, cancel)
}
// Open channels between the nodes.
resp := h.openChannelsForNodes(nodes, p)
for _, cancel := range cancels {
cancel()
}
return resp
}
// DeriveFundingShim creates a channel funding shim by deriving the necessary
// keys on both sides.
func (h *HarnessTest) DeriveFundingShim(alice, bob *node.HarnessNode,
chanSize btcutil.Amount, thawHeight uint32, publish bool,
commitType lnrpc.CommitmentType) (*lnrpc.FundingShim,
*lnrpc.ChannelPoint) {
keyLoc := &signrpc.KeyLocator{KeyFamily: 9999}
carolFundingKey := alice.RPC.DeriveKey(keyLoc)
daveFundingKey := bob.RPC.DeriveKey(keyLoc)
// Now that we have the multi-sig keys for each party, we can manually
// construct the funding transaction. We'll instruct the backend to
// immediately create and broadcast a transaction paying out an exact
// amount. Normally this would reside in the mempool, but we just
// confirm it now for simplicity.
var (
fundingOutput *wire.TxOut
musig2 bool
err error
)
if commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT ||
commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY {
var carolKey, daveKey *btcec.PublicKey
carolKey, err = btcec.ParsePubKey(carolFundingKey.RawKeyBytes)
require.NoError(h, err)
daveKey, err = btcec.ParsePubKey(daveFundingKey.RawKeyBytes)
require.NoError(h, err)
_, fundingOutput, err = input.GenTaprootFundingScript(
carolKey, daveKey, int64(chanSize),
fn.None[chainhash.Hash](),
)
require.NoError(h, err)
musig2 = true
} else {
_, fundingOutput, err = input.GenFundingPkScript(
carolFundingKey.RawKeyBytes, daveFundingKey.RawKeyBytes,
int64(chanSize),
)
require.NoError(h, err)
}
var txid *chainhash.Hash
targetOutputs := []*wire.TxOut{fundingOutput}
if publish {
txid = h.SendOutputsWithoutChange(targetOutputs, 5)
} else {
tx := h.CreateTransaction(targetOutputs, 5)
txHash := tx.TxHash()
txid = &txHash
}
// At this point, we can being our external channel funding workflow.
// We'll start by generating a pending channel ID externally that will
// be used to track this new funding type.
pendingChanID := h.Random32Bytes()
// Now that we have the pending channel ID, Dave (our responder) will
// register the intent to receive a new channel funding workflow using
// the pending channel ID.
chanPoint := &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: txid[:],
},
}
chanPointShim := &lnrpc.ChanPointShim{
Amt: int64(chanSize),
ChanPoint: chanPoint,
LocalKey: &lnrpc.KeyDescriptor{
RawKeyBytes: daveFundingKey.RawKeyBytes,
KeyLoc: &lnrpc.KeyLocator{
KeyFamily: daveFundingKey.KeyLoc.KeyFamily,
KeyIndex: daveFundingKey.KeyLoc.KeyIndex,
},
},
RemoteKey: carolFundingKey.RawKeyBytes,
PendingChanId: pendingChanID,
ThawHeight: thawHeight,
Musig2: musig2,
}
fundingShim := &lnrpc.FundingShim{
Shim: &lnrpc.FundingShim_ChanPointShim{
ChanPointShim: chanPointShim,
},
}
bob.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{
Trigger: &lnrpc.FundingTransitionMsg_ShimRegister{
ShimRegister: fundingShim,
},
})
// If we attempt to register the same shim (has the same pending chan
// ID), then we should get an error.
bob.RPC.FundingStateStepAssertErr(&lnrpc.FundingTransitionMsg{
Trigger: &lnrpc.FundingTransitionMsg_ShimRegister{
ShimRegister: fundingShim,
},
})
// We'll take the chan point shim we just registered for Dave (the
// responder), and swap the local/remote keys before we feed it in as
// Carol's funding shim as the initiator.
fundingShim.GetChanPointShim().LocalKey = &lnrpc.KeyDescriptor{
RawKeyBytes: carolFundingKey.RawKeyBytes,
KeyLoc: &lnrpc.KeyLocator{
KeyFamily: carolFundingKey.KeyLoc.KeyFamily,
KeyIndex: carolFundingKey.KeyLoc.KeyIndex,
},
}
fundingShim.GetChanPointShim().RemoteKey = daveFundingKey.RawKeyBytes
return fundingShim, chanPoint
}
package lntest
import (
"bytes"
"context"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"math"
"sort"
"strings"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lntest/miner"
"github.com/lightningnetwork/lnd/lntest/node"
"github.com/lightningnetwork/lnd/lntest/rpc"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// FindChannelOption is a functional type for an option that modifies a
// ListChannelsRequest.
type ListChannelOption func(r *lnrpc.ListChannelsRequest)
// WithPeerAliasLookup is an option for setting the peer alias lookup flag on a
// ListChannelsRequest.
func WithPeerAliasLookup() ListChannelOption {
return func(r *lnrpc.ListChannelsRequest) {
r.PeerAliasLookup = true
}
}
// WaitForBlockchainSync waits until the node is synced to chain.
func (h *HarnessTest) WaitForBlockchainSync(hn *node.HarnessNode) {
err := wait.NoError(func() error {
resp := hn.RPC.GetInfo()
if resp.SyncedToChain {
return nil
}
return fmt.Errorf("%s is not synced to chain", hn.Name())
}, DefaultTimeout)
require.NoError(h, err, "timeout waiting for blockchain sync")
}
// WaitForBlockchainSyncTo waits until the node is synced to bestBlock.
func (h *HarnessTest) WaitForBlockchainSyncTo(hn *node.HarnessNode,
bestBlock chainhash.Hash) {
bestBlockHash := bestBlock.String()
err := wait.NoError(func() error {
resp := hn.RPC.GetInfo()
if resp.SyncedToChain {
if resp.BlockHash == bestBlockHash {
return nil
}
return fmt.Errorf("%s's backend is synced to the "+
"wrong block (expected=%s, actual=%s)",
hn.Name(), bestBlockHash, resp.BlockHash)
}
return fmt.Errorf("%s is not synced to chain", hn.Name())
}, DefaultTimeout)
require.NoError(h, err, "timeout waiting for blockchain sync")
}
// AssertPeerConnected asserts that the given node b is connected to a.
func (h *HarnessTest) AssertPeerConnected(a, b *node.HarnessNode) {
err := wait.NoError(func() error {
// We require the RPC call to be succeeded and won't wait for
// it as it's an unexpected behavior.
resp := a.RPC.ListPeers()
// If node B is seen in the ListPeers response from node A,
// then we can return true as the connection has been fully
// established.
for _, peer := range resp.Peers {
if peer.PubKey == b.PubKeyStr {
return nil
}
}
return fmt.Errorf("%s not found in %s's ListPeers",
b.Name(), a.Name())
}, DefaultTimeout)
require.NoError(h, err, "unable to connect %s to %s, got error: "+
"peers not connected within %v seconds",
a.Name(), b.Name(), DefaultTimeout)
}
// ConnectNodes creates a connection between the two nodes and asserts the
// connection is succeeded.
func (h *HarnessTest) ConnectNodes(a, b *node.HarnessNode) {
bobInfo := b.RPC.GetInfo()
req := &lnrpc.ConnectPeerRequest{
Addr: &lnrpc.LightningAddress{
Pubkey: bobInfo.IdentityPubkey,
Host: b.Cfg.P2PAddr(),
},
}
a.RPC.ConnectPeer(req)
h.AssertPeerConnected(a, b)
}
// ConnectNodesPerm creates a persistent connection between the two nodes and
// asserts the connection is succeeded.
func (h *HarnessTest) ConnectNodesPerm(a, b *node.HarnessNode) {
bobInfo := b.RPC.GetInfo()
req := &lnrpc.ConnectPeerRequest{
Addr: &lnrpc.LightningAddress{
Pubkey: bobInfo.IdentityPubkey,
Host: b.Cfg.P2PAddr(),
},
Perm: true,
}
a.RPC.ConnectPeer(req)
h.AssertPeerConnected(a, b)
}
// DisconnectNodes disconnects the given two nodes and asserts the
// disconnection is succeeded. The request is made from node a and sent to node
// b.
func (h *HarnessTest) DisconnectNodes(a, b *node.HarnessNode) {
bobInfo := b.RPC.GetInfo()
a.RPC.DisconnectPeer(bobInfo.IdentityPubkey)
// Assert disconnected.
h.AssertPeerNotConnected(a, b)
}
// EnsureConnected will try to connect to two nodes, returning no error if they
// are already connected. If the nodes were not connected previously, this will
// behave the same as ConnectNodes. If a pending connection request has already
// been made, the method will block until the two nodes appear in each other's
// peers list, or until the DefaultTimeout expires.
func (h *HarnessTest) EnsureConnected(a, b *node.HarnessNode) {
// errConnectionRequested is used to signal that a connection was
// requested successfully, which is distinct from already being
// connected to the peer.
errConnectionRequested := "connection request in progress"
// windowsErr is an error we've seen from windows build where
// connecting to an already connected node gives such error from the
// receiver side.
windowsErr := "An established connection was aborted by the software " +
"in your host machine."
tryConnect := func(a, b *node.HarnessNode) error {
bInfo := b.RPC.GetInfo()
req := &lnrpc.ConnectPeerRequest{
Addr: &lnrpc.LightningAddress{
Pubkey: bInfo.IdentityPubkey,
Host: b.Cfg.P2PAddr(),
},
}
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := a.RPC.LN.ConnectPeer(ctxt, req)
// Request was successful.
if err == nil {
return nil
}
// If the two are already connected, we return early with no
// error.
if strings.Contains(err.Error(), "already connected to peer") {
return nil
}
// Otherwise we log the error to console.
h.Logf("EnsureConnected %s=>%s got err: %v", a.Name(),
b.Name(), err)
// If the connection is in process, we return no error.
if strings.Contains(err.Error(), errConnectionRequested) {
return nil
}
// We may get connection refused error if we happens to be in
// the middle of a previous node disconnection, e.g., a restart
// from one of the nodes.
if strings.Contains(err.Error(), "connection refused") {
return nil
}
// Check for windows error. If Alice connects to Bob, Alice
// will throw "i/o timeout" and Bob will give windowsErr.
if strings.Contains(err.Error(), windowsErr) {
return nil
}
if strings.Contains(err.Error(), "i/o timeout") {
return nil
}
return err
}
// Return any critical errors returned by either alice or bob.
require.NoError(h, tryConnect(a, b), "connection failed between %s "+
"and %s", a.Cfg.Name, b.Cfg.Name)
// When Alice and Bob each makes a connection to the other side at the
// same time, it's likely neither connections could succeed. Bob's
// connection will be canceled by Alice since she has an outbound
// connection to Bob already, and same happens to Alice's. Thus the two
// connections cancel each other out.
// TODO(yy): move this back when the above issue is fixed.
// require.NoError(h, tryConnect(b, a), "connection failed between %s "+
// "and %s", a.Cfg.Name, b.Cfg.Name)
// Otherwise one or both requested a connection, so we wait for the
// peers lists to reflect the connection.
h.AssertPeerConnected(a, b)
h.AssertPeerConnected(b, a)
}
// AssertNumEdges checks that an expected number of edges can be found in the
// node specified.
func (h *HarnessTest) AssertNumEdges(hn *node.HarnessNode,
expected int, includeUnannounced bool) []*lnrpc.ChannelEdge {
var edges []*lnrpc.ChannelEdge
old := hn.State.Edge.Public
if includeUnannounced {
old = hn.State.Edge.Total
}
err := wait.NoError(func() error {
req := &lnrpc.ChannelGraphRequest{
IncludeUnannounced: includeUnannounced,
}
resp := hn.RPC.DescribeGraph(req)
total := len(resp.Edges)
if total-old == expected {
if expected != 0 {
// NOTE: assume edges come in ascending order
// that the old edges are at the front of the
// slice.
edges = resp.Edges[old:]
}
return nil
}
return errNumNotMatched(hn.Name(), "num of channel edges",
expected, total-old, total, old)
}, DefaultTimeout)
require.NoError(h, err, "timeout while checking for edges")
return edges
}
// ReceiveOpenChannelUpdate waits until a message is received on the stream or
// the timeout is reached.
func (h *HarnessTest) ReceiveOpenChannelUpdate(
stream rpc.OpenChanClient) *lnrpc.OpenStatusUpdate {
update, err := h.receiveOpenChannelUpdate(stream)
require.NoError(h, err, "received err from open channel stream")
return update
}
// ReceiveOpenChannelError waits for the expected error during the open channel
// flow from the peer or times out.
func (h *HarnessTest) ReceiveOpenChannelError(
stream rpc.OpenChanClient, expectedErr error) {
_, err := h.receiveOpenChannelUpdate(stream)
require.Contains(h, err.Error(), expectedErr.Error(),
"error not matched")
}
// receiveOpenChannelUpdate waits until a message or an error is received on
// the stream or the timeout is reached.
//
// TODO(yy): use generics to unify all receiving stream update once go@1.18 is
// used.
func (h *HarnessTest) receiveOpenChannelUpdate(
stream rpc.OpenChanClient) (*lnrpc.OpenStatusUpdate, error) {
chanMsg := make(chan *lnrpc.OpenStatusUpdate)
errChan := make(chan error)
go func() {
// Consume one message. This will block until the message is
// received.
resp, err := stream.Recv()
if err != nil {
errChan <- err
return
}
chanMsg <- resp
}()
select {
case <-time.After(DefaultTimeout):
require.Fail(h, "timeout", "timeout waiting for open channel "+
"update sent")
return nil, nil
case err := <-errChan:
return nil, err
case updateMsg := <-chanMsg:
return updateMsg, nil
}
}
// WaitForChannelOpenEvent waits for a notification that a channel is open by
// consuming a message from the passed open channel stream.
func (h HarnessTest) WaitForChannelOpenEvent(
stream rpc.OpenChanClient) *lnrpc.ChannelPoint {
// Consume one event.
event := h.ReceiveOpenChannelUpdate(stream)
resp, ok := event.Update.(*lnrpc.OpenStatusUpdate_ChanOpen)
require.Truef(h, ok, "expected channel open update, instead got %v",
resp)
return resp.ChanOpen.ChannelPoint
}
// AssertChannelExists asserts that an active channel identified by the
// specified channel point exists from the point-of-view of the node.
func (h *HarnessTest) AssertChannelExists(hn *node.HarnessNode,
cp *lnrpc.ChannelPoint) *lnrpc.Channel {
return h.assertChannelStatus(hn, cp, true)
}
// AssertChannelActive checks if a channel identified by the specified channel
// point is active.
func (h *HarnessTest) AssertChannelActive(hn *node.HarnessNode,
cp *lnrpc.ChannelPoint) *lnrpc.Channel {
return h.assertChannelStatus(hn, cp, true)
}
// AssertChannelInactive checks if a channel identified by the specified channel
// point is inactive.
func (h *HarnessTest) AssertChannelInactive(hn *node.HarnessNode,
cp *lnrpc.ChannelPoint) *lnrpc.Channel {
return h.assertChannelStatus(hn, cp, false)
}
// assertChannelStatus asserts that a channel identified by the specified
// channel point exists from the point-of-view of the node and that it is either
// active or inactive depending on the value of the active parameter.
func (h *HarnessTest) assertChannelStatus(hn *node.HarnessNode,
cp *lnrpc.ChannelPoint, active bool) *lnrpc.Channel {
var (
channel *lnrpc.Channel
err error
)
err = wait.NoError(func() error {
channel, err = h.findChannel(hn, cp)
if err != nil {
return err
}
// Check whether the channel is active, exit early if it is.
if channel.Active == active {
return nil
}
return fmt.Errorf("expected channel_active=%v, got %v",
active, channel.Active)
}, DefaultTimeout)
require.NoErrorf(h, err, "%s: timeout checking for channel point: %v",
hn.Name(), h.OutPointFromChannelPoint(cp))
return channel
}
// AssertOutputScriptClass checks that the specified transaction output has the
// expected script class.
func (h *HarnessTest) AssertOutputScriptClass(tx *btcutil.Tx,
outputIndex uint32, scriptClass txscript.ScriptClass) {
require.Greater(h, len(tx.MsgTx().TxOut), int(outputIndex))
txOut := tx.MsgTx().TxOut[outputIndex]
pkScript, err := txscript.ParsePkScript(txOut.PkScript)
require.NoError(h, err)
require.Equal(h, scriptClass, pkScript.Class())
}
// findChannel tries to find a target channel in the node using the given
// channel point.
func (h *HarnessTest) findChannel(hn *node.HarnessNode,
chanPoint *lnrpc.ChannelPoint,
opts ...ListChannelOption) (*lnrpc.Channel, error) {
// Get the funding point.
fp := h.OutPointFromChannelPoint(chanPoint)
req := &lnrpc.ListChannelsRequest{}
for _, opt := range opts {
opt(req)
}
channelInfo := hn.RPC.ListChannels(req)
// Find the target channel.
for _, channel := range channelInfo.Channels {
if channel.ChannelPoint == fp.String() {
return channel, nil
}
}
return nil, fmt.Errorf("%s: channel not found using %s", hn.Name(),
fp.String())
}
// ReceiveCloseChannelUpdate waits until a message or an error is received on
// the subscribe channel close stream or the timeout is reached.
func (h *HarnessTest) ReceiveCloseChannelUpdate(
stream rpc.CloseChanClient) (*lnrpc.CloseStatusUpdate, error) {
chanMsg := make(chan *lnrpc.CloseStatusUpdate)
errChan := make(chan error)
go func() {
// Consume one message. This will block until the message is
// received.
resp, err := stream.Recv()
if err != nil {
errChan <- err
return
}
chanMsg <- resp
}()
select {
case <-time.After(DefaultTimeout):
require.Fail(h, "timeout", "timeout waiting for close channel "+
"update sent")
return nil, nil
case err := <-errChan:
return nil, fmt.Errorf("received err from close channel "+
"stream: %v", err)
case updateMsg := <-chanMsg:
return updateMsg, nil
}
}
type WaitingCloseChannel *lnrpc.PendingChannelsResponse_WaitingCloseChannel
// AssertChannelWaitingClose asserts that the given channel found in the node
// is waiting close. Returns the WaitingCloseChannel if found.
func (h *HarnessTest) AssertChannelWaitingClose(hn *node.HarnessNode,
chanPoint *lnrpc.ChannelPoint) WaitingCloseChannel {
var target WaitingCloseChannel
op := h.OutPointFromChannelPoint(chanPoint)
err := wait.NoError(func() error {
resp := hn.RPC.PendingChannels()
for _, waitingClose := range resp.WaitingCloseChannels {
if waitingClose.Channel.ChannelPoint == op.String() {
target = waitingClose
return nil
}
}
return fmt.Errorf("%v: channel %s not found in waiting close",
hn.Name(), op)
}, DefaultTimeout)
require.NoError(h, err, "assert channel waiting close timed out")
return target
}
// AssertTopologyChannelClosed asserts a given channel is closed by checking
// the graph topology subscription of the specified node. Returns the closed
// channel update if found.
func (h *HarnessTest) AssertTopologyChannelClosed(hn *node.HarnessNode,
chanPoint *lnrpc.ChannelPoint) *lnrpc.ClosedChannelUpdate {
closedChan, err := hn.Watcher.WaitForChannelClose(chanPoint)
require.NoError(h, err, "failed to wait for channel close")
return closedChan
}
// WaitForChannelCloseEvent waits for a notification that a channel is closed
// by consuming a message from the passed close channel stream. Returns the
// closing txid if found.
func (h HarnessTest) WaitForChannelCloseEvent(
stream rpc.CloseChanClient) chainhash.Hash {
// Consume one event.
event, err := h.ReceiveCloseChannelUpdate(stream)
require.NoError(h, err)
resp, ok := event.Update.(*lnrpc.CloseStatusUpdate_ChanClose)
require.Truef(h, ok, "expected channel close update, instead got %v",
event.Update)
txid, err := chainhash.NewHash(resp.ChanClose.ClosingTxid)
require.NoErrorf(h, err, "wrong format found in closing txid: %v",
resp.ChanClose.ClosingTxid)
return *txid
}
// AssertNumWaitingClose checks that a PendingChannels response from the node
// reports the expected number of waiting close channels.
func (h *HarnessTest) AssertNumWaitingClose(hn *node.HarnessNode,
num int) []*lnrpc.PendingChannelsResponse_WaitingCloseChannel {
var channels []*lnrpc.PendingChannelsResponse_WaitingCloseChannel
oldWaiting := hn.State.CloseChannel.WaitingClose
err := wait.NoError(func() error {
resp := hn.RPC.PendingChannels()
channels = resp.WaitingCloseChannels
total := len(channels)
got := total - oldWaiting
if got == num {
return nil
}
return errNumNotMatched(hn.Name(), "waiting close channels",
num, got, total, oldWaiting)
}, DefaultTimeout)
require.NoErrorf(h, err, "%s: assert waiting close timeout",
hn.Name())
return channels
}
// AssertNumPendingForceClose checks that a PendingChannels response from the
// node reports the expected number of pending force close channels.
func (h *HarnessTest) AssertNumPendingForceClose(hn *node.HarnessNode,
num int) []*lnrpc.PendingChannelsResponse_ForceClosedChannel {
var channels []*lnrpc.PendingChannelsResponse_ForceClosedChannel
oldForce := hn.State.CloseChannel.PendingForceClose
err := wait.NoError(func() error {
// TODO(yy): we should be able to use `hn.RPC.PendingChannels`
// here to avoid checking the RPC error. However, we may get a
// `unable to find arbitrator` error from the rpc point, due to
// a timing issue in rpcserver,
// 1. `r.server.chanStateDB.FetchClosedChannels` fetches
// the pending force close channel.
// 2. `r.arbitratorPopulateForceCloseResp` relies on the
// channel arbitrator to get the report, and,
// 3. the arbitrator may be deleted due to the force close
// channel being resolved.
// Somewhere along the line is missing a lock to keep the data
// consistent.
req := &lnrpc.PendingChannelsRequest{}
resp, err := hn.RPC.LN.PendingChannels(h.runCtx, req)
if err != nil {
return fmt.Errorf("PendingChannels got: %w", err)
}
channels = resp.PendingForceClosingChannels
total := len(channels)
got := total - oldForce
if got == num {
return nil
}
return errNumNotMatched(hn.Name(), "pending force close "+
"channels", num, got, total, oldForce)
}, DefaultTimeout)
require.NoErrorf(h, err, "%s: assert pending force close timeout",
hn.Name())
return channels
}
// AssertStreamChannelCoopClosed reads an update from the close channel client
// stream and asserts that the mempool state and node's topology match a coop
// close. In specific,
// - assert the channel is waiting close and has the expected ChanStatusFlags.
// - assert the mempool has the closing txes and anchor sweeps.
// - mine a block and assert the closing txid is mined.
// - assert the node has zero waiting close channels.
// - assert the node has seen the channel close update.
func (h *HarnessTest) AssertStreamChannelCoopClosed(hn *node.HarnessNode,
cp *lnrpc.ChannelPoint, anchors bool,
stream rpc.CloseChanClient) chainhash.Hash {
// Assert the channel is waiting close.
resp := h.AssertChannelWaitingClose(hn, cp)
// Assert that the channel is in coop broadcasted.
require.Contains(h, resp.Channel.ChanStatusFlags,
channeldb.ChanStatusCoopBroadcasted.String(),
"channel not coop broadcasted")
// We'll now, generate a single block, wait for the final close status
// update, then ensure that the closing transaction was included in the
// block. If there are anchors, we also expect an anchor sweep.
expectedTxes := 1
if anchors {
expectedTxes = 2
}
block := h.MineBlocksAndAssertNumTxes(1, expectedTxes)[0]
// Consume one close event and assert the closing txid can be found in
// the block.
closingTxid := h.WaitForChannelCloseEvent(stream)
h.AssertTxInBlock(block, closingTxid)
// We should see zero waiting close channels now.
h.AssertNumWaitingClose(hn, 0)
// Finally, check that the node's topology graph has seen this channel
// closed if it's a public channel.
if !resp.Channel.Private {
h.AssertTopologyChannelClosed(hn, cp)
}
return closingTxid
}
// AssertStreamChannelForceClosed reads an update from the close channel client
// stream and asserts that the mempool state and node's topology match a local
// force close. In specific,
// - assert the channel is waiting close and has the expected ChanStatusFlags.
// - assert the mempool has the closing txes.
// - mine a block and assert the closing txid is mined.
// - assert the channel is pending force close.
// - assert the node has seen the channel close update.
// - assert there's a pending anchor sweep request once the force close tx is
// confirmed.
func (h *HarnessTest) AssertStreamChannelForceClosed(hn *node.HarnessNode,
cp *lnrpc.ChannelPoint, anchorSweep bool,
stream rpc.CloseChanClient) chainhash.Hash {
// Assert the channel is waiting close.
resp := h.AssertChannelWaitingClose(hn, cp)
// Assert that the channel is in local force broadcasted.
require.Contains(h, resp.Channel.ChanStatusFlags,
channeldb.ChanStatusLocalCloseInitiator.String(),
"channel not coop broadcasted")
// Get the closing txid.
closeTxid, err := chainhash.NewHashFromStr(resp.ClosingTxid)
require.NoError(h, err)
// We'll now, generate a single block, wait for the final close status
// update, then ensure that the closing transaction was included in the
// block.
closeTx := h.AssertTxInMempool(*closeTxid)
h.MineBlockWithTx(closeTx)
// Consume one close event and assert the closing txid can be found in
// the block.
closingTxid := h.WaitForChannelCloseEvent(stream)
// We should see zero waiting close channels and 1 pending force close
// channels now.
h.AssertNumWaitingClose(hn, 0)
h.AssertNumPendingForceClose(hn, 1)
// Finally, check that the node's topology graph has seen this channel
// closed if it's a public channel.
if !resp.Channel.Private {
h.AssertTopologyChannelClosed(hn, cp)
}
// Assert there's a pending anchor sweep.
if anchorSweep {
h.AssertNumPendingSweeps(hn, 1)
}
return closingTxid
}
// AssertChannelPolicyUpdate checks that the required policy update has
// happened on the given node.
func (h *HarnessTest) AssertChannelPolicyUpdate(hn *node.HarnessNode,
advertisingNode *node.HarnessNode, policy *lnrpc.RoutingPolicy,
chanPoint *lnrpc.ChannelPoint, includeUnannounced bool) {
require.NoError(
h, hn.Watcher.WaitForChannelPolicyUpdate(
advertisingNode, policy,
chanPoint, includeUnannounced,
), "%s: error while waiting for channel update", hn.Name(),
)
}
// WaitForGraphSync waits until the node is synced to graph or times out.
func (h *HarnessTest) WaitForGraphSync(hn *node.HarnessNode) {
err := wait.NoError(func() error {
resp := hn.RPC.GetInfo()
if resp.SyncedToGraph {
return nil
}
return fmt.Errorf("node not synced to graph")
}, DefaultTimeout)
require.NoError(h, err, "%s: timeout while sync to graph", hn.Name())
}
// AssertNumUTXOsWithConf waits for the given number of UTXOs with the
// specified confirmations range to be available or fails if that isn't the
// case before the default timeout.
func (h *HarnessTest) AssertNumUTXOsWithConf(hn *node.HarnessNode,
expectedUtxos int, max, min int32) []*lnrpc.Utxo {
var unconfirmed bool
if max == 0 {
unconfirmed = true
}
var utxos []*lnrpc.Utxo
err := wait.NoError(func() error {
req := &walletrpc.ListUnspentRequest{
Account: "",
MaxConfs: max,
MinConfs: min,
UnconfirmedOnly: unconfirmed,
}
resp := hn.RPC.ListUnspent(req)
total := len(resp.Utxos)
if total == expectedUtxos {
utxos = resp.Utxos
return nil
}
desc := "has UTXOs:\n"
for _, utxo := range resp.Utxos {
desc += fmt.Sprintf("%v\n", utxo)
}
return fmt.Errorf("%s: assert num of UTXOs failed: want %d, "+
"got: %d, %s", hn.Name(), expectedUtxos, total, desc)
}, DefaultTimeout)
require.NoError(h, err, "timeout waiting for UTXOs")
return utxos
}
// AssertNumUTXOsUnconfirmed asserts the expected num of unconfirmed utxos are
// seen.
func (h *HarnessTest) AssertNumUTXOsUnconfirmed(hn *node.HarnessNode,
num int) []*lnrpc.Utxo {
return h.AssertNumUTXOsWithConf(hn, num, 0, 0)
}
// AssertNumUTXOsConfirmed asserts the expected num of confirmed utxos are
// seen, which means the returned utxos have at least one confirmation.
func (h *HarnessTest) AssertNumUTXOsConfirmed(hn *node.HarnessNode,
num int) []*lnrpc.Utxo {
return h.AssertNumUTXOsWithConf(hn, num, math.MaxInt32, 1)
}
// AssertNumUTXOs asserts the expected num of utxos are seen, including
// confirmed and unconfirmed outputs.
func (h *HarnessTest) AssertNumUTXOs(hn *node.HarnessNode,
num int) []*lnrpc.Utxo {
return h.AssertNumUTXOsWithConf(hn, num, math.MaxInt32, 0)
}
// getUTXOs gets the number of newly created UTOXs within the current test
// scope.
func (h *HarnessTest) getUTXOs(hn *node.HarnessNode, account string,
max, min int32) []*lnrpc.Utxo {
var unconfirmed bool
if max == 0 {
unconfirmed = true
}
req := &walletrpc.ListUnspentRequest{
Account: account,
MaxConfs: max,
MinConfs: min,
UnconfirmedOnly: unconfirmed,
}
resp := hn.RPC.ListUnspent(req)
return resp.Utxos
}
// GetUTXOs returns all the UTXOs for the given node's account, including
// confirmed and unconfirmed.
func (h *HarnessTest) GetUTXOs(hn *node.HarnessNode,
account string) []*lnrpc.Utxo {
return h.getUTXOs(hn, account, math.MaxInt32, 0)
}
// GetUTXOsConfirmed returns the confirmed UTXOs for the given node's account.
func (h *HarnessTest) GetUTXOsConfirmed(hn *node.HarnessNode,
account string) []*lnrpc.Utxo {
return h.getUTXOs(hn, account, math.MaxInt32, 1)
}
// GetUTXOsUnconfirmed returns the unconfirmed UTXOs for the given node's
// account.
func (h *HarnessTest) GetUTXOsUnconfirmed(hn *node.HarnessNode,
account string) []*lnrpc.Utxo {
return h.getUTXOs(hn, account, 0, 0)
}
// WaitForBalanceConfirmed waits until the node sees the expected confirmed
// balance in its wallet.
func (h *HarnessTest) WaitForBalanceConfirmed(hn *node.HarnessNode,
expected btcutil.Amount) {
var lastBalance btcutil.Amount
err := wait.NoError(func() error {
resp := hn.RPC.WalletBalance()
lastBalance = btcutil.Amount(resp.ConfirmedBalance)
if lastBalance == expected {
return nil
}
return fmt.Errorf("expected %v, only have %v", expected,
lastBalance)
}, DefaultTimeout)
require.NoError(h, err, "timeout waiting for confirmed balances")
}
// WaitForBalanceUnconfirmed waits until the node sees the expected unconfirmed
// balance in its wallet.
func (h *HarnessTest) WaitForBalanceUnconfirmed(hn *node.HarnessNode,
expected btcutil.Amount) {
var lastBalance btcutil.Amount
err := wait.NoError(func() error {
resp := hn.RPC.WalletBalance()
lastBalance = btcutil.Amount(resp.UnconfirmedBalance)
if lastBalance == expected {
return nil
}
return fmt.Errorf("expected %v, only have %v", expected,
lastBalance)
}, DefaultTimeout)
require.NoError(h, err, "timeout waiting for unconfirmed balances")
}
// Random32Bytes generates a random 32 bytes which can be used as a pay hash,
// preimage, etc.
func (h *HarnessTest) Random32Bytes() []byte {
randBuf := make([]byte, lntypes.HashSize)
_, err := rand.Read(randBuf)
require.NoErrorf(h, err, "internal error, cannot generate random bytes")
return randBuf
}
// RandomPreimage generates a random preimage which can be used as a payment
// preimage.
func (h *HarnessTest) RandomPreimage() lntypes.Preimage {
var preimage lntypes.Preimage
copy(preimage[:], h.Random32Bytes())
return preimage
}
// DecodeAddress decodes a given address and asserts there's no error.
func (h *HarnessTest) DecodeAddress(addr string) btcutil.Address {
resp, err := btcutil.DecodeAddress(addr, miner.HarnessNetParams)
require.NoError(h, err, "DecodeAddress failed")
return resp
}
// PayToAddrScript creates a new script from the given address and asserts
// there's no error.
func (h *HarnessTest) PayToAddrScript(addr btcutil.Address) []byte {
addrScript, err := txscript.PayToAddrScript(addr)
require.NoError(h, err, "PayToAddrScript failed")
return addrScript
}
// AssertChannelBalanceResp makes a ChannelBalance request and checks the
// returned response matches the expected.
func (h *HarnessTest) AssertChannelBalanceResp(hn *node.HarnessNode,
expected *lnrpc.ChannelBalanceResponse) {
resp := hn.RPC.ChannelBalance()
// Ignore custom channel data of both expected and actual responses.
expected.CustomChannelData = nil
resp.CustomChannelData = nil
require.True(h, proto.Equal(expected, resp), "balance is incorrect "+
"got: %v, want: %v", resp, expected)
}
// GetChannelByChanPoint tries to find a channel matching the channel point and
// asserts. It returns the channel found.
func (h *HarnessTest) GetChannelByChanPoint(hn *node.HarnessNode,
chanPoint *lnrpc.ChannelPoint) *lnrpc.Channel {
channel, err := h.findChannel(hn, chanPoint)
require.NoErrorf(h, err, "channel not found using %v", chanPoint)
return channel
}
// GetChannelCommitType retrieves the active channel commitment type for the
// given chan point.
func (h *HarnessTest) GetChannelCommitType(hn *node.HarnessNode,
chanPoint *lnrpc.ChannelPoint) lnrpc.CommitmentType {
c := h.GetChannelByChanPoint(hn, chanPoint)
return c.CommitmentType
}
// AssertNumPendingOpenChannels asserts that a given node have the expected
// number of pending open channels.
func (h *HarnessTest) AssertNumPendingOpenChannels(hn *node.HarnessNode,
expected int) []*lnrpc.PendingChannelsResponse_PendingOpenChannel {
var channels []*lnrpc.PendingChannelsResponse_PendingOpenChannel
oldNum := hn.State.OpenChannel.Pending
err := wait.NoError(func() error {
resp := hn.RPC.PendingChannels()
channels = resp.PendingOpenChannels
total := len(channels)
numChans := total - oldNum
if numChans != expected {
return errNumNotMatched(hn.Name(),
"pending open channels", expected,
numChans, total, oldNum)
}
return nil
}, DefaultTimeout)
require.NoError(h, err, "num of pending open channels not match")
return channels
}
// AssertNodesNumPendingOpenChannels asserts that both of the nodes have the
// expected number of pending open channels.
func (h *HarnessTest) AssertNodesNumPendingOpenChannels(a, b *node.HarnessNode,
expected int) {
h.AssertNumPendingOpenChannels(a, expected)
h.AssertNumPendingOpenChannels(b, expected)
}
// AssertPaymentStatusFromStream takes a client stream and asserts the payment
// is in desired status before default timeout. The payment found is returned
// once succeeded.
func (h *HarnessTest) AssertPaymentStatusFromStream(stream rpc.PaymentClient,
status lnrpc.Payment_PaymentStatus) *lnrpc.Payment {
return h.assertPaymentStatusWithTimeout(
stream, status, wait.PaymentTimeout,
)
}
// AssertPaymentSucceedWithTimeout asserts that a payment is succeeded within
// the specified timeout.
func (h *HarnessTest) AssertPaymentSucceedWithTimeout(stream rpc.PaymentClient,
timeout time.Duration) *lnrpc.Payment {
return h.assertPaymentStatusWithTimeout(
stream, lnrpc.Payment_SUCCEEDED, timeout,
)
}
// assertPaymentStatusWithTimeout takes a client stream and asserts the payment
// is in desired status before the specified timeout. The payment found is
// returned once succeeded.
func (h *HarnessTest) assertPaymentStatusWithTimeout(stream rpc.PaymentClient,
status lnrpc.Payment_PaymentStatus,
timeout time.Duration) *lnrpc.Payment {
var target *lnrpc.Payment
err := wait.NoError(func() error {
// Consume one message. This will raise an error if the message
// is not received within DefaultTimeout.
payment, err := h.receivePaymentUpdateWithTimeout(
stream, timeout,
)
if err != nil {
return fmt.Errorf("received error from payment "+
"stream: %s", err)
}
// Return if the desired payment state is reached.
if payment.Status == status {
target = payment
return nil
}
// Return the err so that it can be used for debugging when
// timeout is reached.
return fmt.Errorf("payment %v status, got %v, want %v",
payment.PaymentHash, payment.Status, status)
}, timeout)
require.NoError(h, err, "timeout while waiting payment")
return target
}
// ReceivePaymentUpdate waits until a message is received on the payment client
// stream or the timeout is reached.
func (h *HarnessTest) ReceivePaymentUpdate(
stream rpc.PaymentClient) (*lnrpc.Payment, error) {
return h.receivePaymentUpdateWithTimeout(stream, DefaultTimeout)
}
// receivePaymentUpdateWithTimeout waits until a message is received on the
// payment client stream or the timeout is reached.
func (h *HarnessTest) receivePaymentUpdateWithTimeout(stream rpc.PaymentClient,
timeout time.Duration) (*lnrpc.Payment, error) {
chanMsg := make(chan *lnrpc.Payment, 1)
errChan := make(chan error, 1)
go func() {
// Consume one message. This will block until the message is
// received.
resp, err := stream.Recv()
if err != nil {
errChan <- err
return
}
chanMsg <- resp
}()
select {
case <-time.After(timeout):
require.Fail(h, "timeout", "timeout waiting for payment update")
return nil, nil
case err := <-errChan:
return nil, err
case updateMsg := <-chanMsg:
return updateMsg, nil
}
}
// AssertInvoiceSettled asserts a given invoice specified by its payment
// address is settled.
func (h *HarnessTest) AssertInvoiceSettled(hn *node.HarnessNode, addr []byte) {
msg := &invoicesrpc.LookupInvoiceMsg{
InvoiceRef: &invoicesrpc.LookupInvoiceMsg_PaymentAddr{
PaymentAddr: addr,
},
}
err := wait.NoError(func() error {
invoice := hn.RPC.LookupInvoiceV2(msg)
if invoice.State == lnrpc.Invoice_SETTLED {
return nil
}
return fmt.Errorf("%s: invoice with payment address %x not "+
"settled", hn.Name(), addr)
}, DefaultTimeout)
require.NoError(h, err, "timeout waiting for invoice settled state")
}
// AssertNodeNumChannels polls the provided node's list channels rpc until it
// reaches the desired number of total channels.
func (h *HarnessTest) AssertNodeNumChannels(hn *node.HarnessNode,
numChannels int) {
// Get the total number of channels.
old := hn.State.OpenChannel.Active + hn.State.OpenChannel.Inactive
err := wait.NoError(func() error {
// We require the RPC call to be succeeded and won't wait for
// it as it's an unexpected behavior.
chanInfo := hn.RPC.ListChannels(&lnrpc.ListChannelsRequest{})
// Return true if the query returned the expected number of
// channels.
num := len(chanInfo.Channels) - old
if num != numChannels {
return fmt.Errorf("expected %v channels, got %v",
numChannels, num)
}
return nil
}, DefaultTimeout)
require.NoError(h, err, "timeout checking node's num of channels")
}
// AssertChannelLocalBalance checks the local balance of the given channel is
// expected. The channel found using the specified channel point is returned.
func (h *HarnessTest) AssertChannelLocalBalance(hn *node.HarnessNode,
cp *lnrpc.ChannelPoint, balance int64) *lnrpc.Channel {
var result *lnrpc.Channel
// Get the funding point.
err := wait.NoError(func() error {
// Find the target channel first.
target, err := h.findChannel(hn, cp)
// Exit early if the channel is not found.
if err != nil {
return fmt.Errorf("check balance failed: %w", err)
}
result = target
// Check local balance.
if target.LocalBalance == balance {
return nil
}
return fmt.Errorf("balance is incorrect, got %v, expected %v",
target.LocalBalance, balance)
}, DefaultTimeout)
require.NoError(h, err, "timeout while checking for balance")
return result
}
// AssertChannelNumUpdates checks the num of updates is expected from the given
// channel.
func (h *HarnessTest) AssertChannelNumUpdates(hn *node.HarnessNode,
num uint64, cp *lnrpc.ChannelPoint) {
old := int(hn.State.OpenChannel.NumUpdates)
// Find the target channel first.
target, err := h.findChannel(hn, cp)
require.NoError(h, err, "unable to find channel")
err = wait.NoError(func() error {
total := int(target.NumUpdates)
if total-old == int(num) {
return nil
}
return errNumNotMatched(hn.Name(), "channel updates",
int(num), total-old, total, old)
}, DefaultTimeout)
require.NoError(h, err, "timeout while checking for num of updates")
}
// AssertNumActiveHtlcs asserts that a given number of HTLCs are seen in the
// node's channels.
func (h *HarnessTest) AssertNumActiveHtlcs(hn *node.HarnessNode, num int) {
old := hn.State.HTLC
err := wait.NoError(func() error {
// pendingHTLCs is used to print unacked HTLCs, if found.
var pendingHTLCs []string
// We require the RPC call to be succeeded and won't wait for
// it as it's an unexpected behavior.
req := &lnrpc.ListChannelsRequest{}
nodeChans := hn.RPC.ListChannels(req)
total := 0
for _, channel := range nodeChans.Channels {
for _, htlc := range channel.PendingHtlcs {
if htlc.LockedIn {
total++
}
rHash := fmt.Sprintf("%x", htlc.HashLock)
pendingHTLCs = append(pendingHTLCs, rHash)
}
}
if total-old != num {
desc := fmt.Sprintf("active HTLCs: unacked HTLCs: %v",
pendingHTLCs)
return errNumNotMatched(hn.Name(), desc,
num, total-old, total, old)
}
return nil
}, DefaultTimeout)
require.NoErrorf(h, err, "%s timeout checking num active htlcs",
hn.Name())
}
// AssertIncomingHTLCActive asserts the node has a pending incoming HTLC in the
// given channel. Returns the HTLC if found and active.
func (h *HarnessTest) AssertIncomingHTLCActive(hn *node.HarnessNode,
cp *lnrpc.ChannelPoint, payHash []byte) *lnrpc.HTLC {
return h.assertHTLCActive(hn, cp, payHash, true)
}
// AssertOutgoingHTLCActive asserts the node has a pending outgoing HTLC in the
// given channel. Returns the HTLC if found and active.
func (h *HarnessTest) AssertOutgoingHTLCActive(hn *node.HarnessNode,
cp *lnrpc.ChannelPoint, payHash []byte) *lnrpc.HTLC {
return h.assertHTLCActive(hn, cp, payHash, false)
}
// assertHLTCActive asserts the node has a pending HTLC in the given channel.
// Returns the HTLC if found and active.
func (h *HarnessTest) assertHTLCActive(hn *node.HarnessNode,
cp *lnrpc.ChannelPoint, payHash []byte, incoming bool) *lnrpc.HTLC {
var result *lnrpc.HTLC
target := hex.EncodeToString(payHash)
err := wait.NoError(func() error {
// We require the RPC call to be succeeded and won't wait for
// it as it's an unexpected behavior.
ch := h.GetChannelByChanPoint(hn, cp)
// Check all payment hashes active for this channel.
for _, htlc := range ch.PendingHtlcs {
rHash := hex.EncodeToString(htlc.HashLock)
if rHash != target {
continue
}
// If the payment hash is found, check the incoming
// field.
if htlc.Incoming == incoming {
// Return the result if it's locked in.
if htlc.LockedIn {
result = htlc
return nil
}
return fmt.Errorf("htlc(%x) not locked in",
payHash)
}
// Otherwise we do have the HTLC but its direction is
// not right.
have, want := "outgoing", "incoming"
if htlc.Incoming {
have, want = "incoming", "outgoing"
}
return fmt.Errorf("htlc(%x) has wrong direction - "+
"want: %s, have: %s", payHash, want, have)
}
return fmt.Errorf("htlc not found using payHash %x", payHash)
}, DefaultTimeout)
require.NoError(h, err, "%s: timeout checking pending HTLC", hn.Name())
return result
}
// AssertHLTCNotActive asserts the node doesn't have a pending HTLC in the
// given channel, which mean either the HTLC never exists, or it was pending
// and now settled. Returns the HTLC if found and active.
//
// NOTE: to check a pending HTLC becoming settled, first use AssertHLTCActive
// then follow this check.
func (h *HarnessTest) AssertHTLCNotActive(hn *node.HarnessNode,
cp *lnrpc.ChannelPoint, payHash []byte) *lnrpc.HTLC {
var result *lnrpc.HTLC
target := hex.EncodeToString(payHash)
err := wait.NoError(func() error {
// We require the RPC call to be succeeded and won't wait for
// it as it's an unexpected behavior.
ch := h.GetChannelByChanPoint(hn, cp)
// Check all payment hashes active for this channel.
for _, htlc := range ch.PendingHtlcs {
h := hex.EncodeToString(htlc.HashLock)
// Break if found the htlc.
if h == target {
result = htlc
break
}
}
// If we've found nothing, we're done.
if result == nil {
return nil
}
// Otherwise return an error.
return fmt.Errorf("node [%s:%x] still has: the payHash %x",
hn.Name(), hn.PubKey[:], payHash)
}, DefaultTimeout)
require.NoError(h, err, "timeout checking pending HTLC")
return result
}
// ReceiveSingleInvoice waits until a message is received on the subscribe
// single invoice stream or the timeout is reached.
func (h *HarnessTest) ReceiveSingleInvoice(
stream rpc.SingleInvoiceClient) *lnrpc.Invoice {
chanMsg := make(chan *lnrpc.Invoice, 1)
errChan := make(chan error, 1)
go func() {
// Consume one message. This will block until the message is
// received.
resp, err := stream.Recv()
if err != nil {
errChan <- err
return
}
chanMsg <- resp
}()
select {
case <-time.After(DefaultTimeout):
require.Fail(h, "timeout", "timeout receiving single invoice")
case err := <-errChan:
require.Failf(h, "err from stream",
"received err from stream: %v", err)
case updateMsg := <-chanMsg:
return updateMsg
}
return nil
}
// AssertInvoiceState takes a single invoice subscription stream and asserts
// that a given invoice has became the desired state before timeout and returns
// the invoice found.
func (h *HarnessTest) AssertInvoiceState(stream rpc.SingleInvoiceClient,
state lnrpc.Invoice_InvoiceState) *lnrpc.Invoice {
var invoice *lnrpc.Invoice
err := wait.NoError(func() error {
invoice = h.ReceiveSingleInvoice(stream)
if invoice.State == state {
return nil
}
return fmt.Errorf("mismatched invoice state, want %v, got %v",
state, invoice.State)
}, DefaultTimeout)
require.NoError(h, err, "timeout waiting for invoice state: %v", state)
return invoice
}
// assertAllTxesSpendFrom asserts that all txes in the list spend from the
// given tx.
func (h *HarnessTest) AssertAllTxesSpendFrom(txes []*wire.MsgTx,
prevTxid chainhash.Hash) {
for _, tx := range txes {
if tx.TxIn[0].PreviousOutPoint.Hash != prevTxid {
require.Failf(h, "", "tx %v did not spend from %v",
tx.TxHash(), prevTxid)
}
}
}
// AssertTxSpendFrom asserts that a given tx is spent from a previous tx.
func (h *HarnessTest) AssertTxSpendFrom(tx *wire.MsgTx,
prevTxid chainhash.Hash) {
if tx.TxIn[0].PreviousOutPoint.Hash != prevTxid {
require.Failf(h, "", "tx %v did not spend from %v",
tx.TxHash(), prevTxid)
}
}
type PendingForceClose *lnrpc.PendingChannelsResponse_ForceClosedChannel
// AssertChannelPendingForceClose asserts that the given channel found in the
// node is pending force close. Returns the PendingForceClose if found.
func (h *HarnessTest) AssertChannelPendingForceClose(hn *node.HarnessNode,
chanPoint *lnrpc.ChannelPoint) PendingForceClose {
var target PendingForceClose
op := h.OutPointFromChannelPoint(chanPoint)
err := wait.NoError(func() error {
resp := hn.RPC.PendingChannels()
forceCloseChans := resp.PendingForceClosingChannels
for _, ch := range forceCloseChans {
if ch.Channel.ChannelPoint == op.String() {
target = ch
return nil
}
}
return fmt.Errorf("%v: channel %s not found in pending "+
"force close", hn.Name(), chanPoint)
}, DefaultTimeout)
require.NoError(h, err, "assert pending force close timed out")
return target
}
// AssertNumHTLCsAndStage takes a pending force close channel's channel point
// and asserts the expected number of pending HTLCs and HTLC stage are matched.
func (h *HarnessTest) AssertNumHTLCsAndStage(hn *node.HarnessNode,
chanPoint *lnrpc.ChannelPoint, num int, stage uint32) {
// Get the channel output point.
cp := h.OutPointFromChannelPoint(chanPoint)
var target PendingForceClose
checkStage := func() error {
resp := hn.RPC.PendingChannels()
if len(resp.PendingForceClosingChannels) == 0 {
return fmt.Errorf("zero pending force closing channels")
}
for _, ch := range resp.PendingForceClosingChannels {
if ch.Channel.ChannelPoint == cp.String() {
target = ch
break
}
}
if target == nil {
return fmt.Errorf("cannot find pending force closing "+
"channel using %v", cp)
}
if target.LimboBalance == 0 {
return fmt.Errorf("zero limbo balance")
}
if len(target.PendingHtlcs) != num {
return fmt.Errorf("got %d pending htlcs, want %d, %s",
len(target.PendingHtlcs), num,
lnutils.SpewLogClosure(target.PendingHtlcs)())
}
for _, htlc := range target.PendingHtlcs {
if htlc.Stage == stage {
continue
}
return fmt.Errorf("HTLC %s got stage: %v, "+
"want stage: %v", htlc.Outpoint, htlc.Stage,
stage)
}
return nil
}
require.NoErrorf(h, wait.NoError(checkStage, DefaultTimeout),
"timeout waiting for htlc stage")
}
// findPayment queries the payment from the node's ListPayments which matches
// the specified preimage hash.
func (h *HarnessTest) findPayment(hn *node.HarnessNode,
paymentHash string) (*lnrpc.Payment, error) {
req := &lnrpc.ListPaymentsRequest{IncludeIncomplete: true}
paymentsResp := hn.RPC.ListPayments(req)
for _, p := range paymentsResp.Payments {
if p.PaymentHash == paymentHash {
return p, nil
}
}
return nil, fmt.Errorf("payment %v cannot be found", paymentHash)
}
// PaymentCheck is a function that checks a payment for a specific condition.
type PaymentCheck func(*lnrpc.Payment) error
// AssertPaymentStatus asserts that the given node list a payment with the given
// payment hash has the expected status. It also checks that the payment has the
// expected preimage, which is empty when it's not settled and matches the given
// preimage when it's succeeded.
func (h *HarnessTest) AssertPaymentStatus(hn *node.HarnessNode,
payHash lntypes.Hash, status lnrpc.Payment_PaymentStatus,
checks ...PaymentCheck) *lnrpc.Payment {
var target *lnrpc.Payment
err := wait.NoError(func() error {
p, err := h.findPayment(hn, payHash.String())
if err != nil {
return err
}
if status == p.Status {
target = p
return nil
}
return fmt.Errorf("payment: %v status not match, want %s "+
"got %s", payHash, status, p.Status)
}, DefaultTimeout)
require.NoError(h, err, "timeout checking payment status")
switch status {
// If this expected status is SUCCEEDED, we expect the final
// preimage.
case lnrpc.Payment_SUCCEEDED:
preimage, err := lntypes.MakePreimageFromStr(
target.PaymentPreimage,
)
require.NoError(h, err, "fail to make preimage")
require.Equal(h, payHash, preimage.Hash(), "preimage not match")
// Otherwise we expect an all-zero preimage.
default:
require.Equal(h, (lntypes.Preimage{}).String(),
target.PaymentPreimage, "expected zero preimage")
}
// Perform any additional checks on the payment.
for _, check := range checks {
require.NoError(h, check(target))
}
return target
}
// AssertPaymentFailureReason asserts that the given node lists a payment with
// the given preimage which has the expected failure reason.
func (h *HarnessTest) AssertPaymentFailureReason(
hn *node.HarnessNode, preimage lntypes.Preimage,
reason lnrpc.PaymentFailureReason) *lnrpc.Payment {
var payment *lnrpc.Payment
payHash := preimage.Hash()
err := wait.NoError(func() error {
p, err := h.findPayment(hn, payHash.String())
if err != nil {
return err
}
payment = p
if reason == p.FailureReason {
return nil
}
return fmt.Errorf("payment: %v failure reason not match, "+
"want %s(%d) got %s(%d)", payHash, reason, reason,
p.FailureReason, p.FailureReason)
}, DefaultTimeout)
require.NoError(h, err, "timeout checking payment failure reason")
return payment
}
// AssertActiveNodesSynced asserts all active nodes have synced to the chain.
func (h *HarnessTest) AssertActiveNodesSynced() {
for _, node := range h.manager.activeNodes {
h.WaitForBlockchainSync(node)
}
}
// AssertActiveNodesSyncedTo asserts all active nodes have synced to the
// provided bestBlock.
func (h *HarnessTest) AssertActiveNodesSyncedTo(bestBlock chainhash.Hash) {
for _, node := range h.manager.activeNodes {
h.WaitForBlockchainSyncTo(node, bestBlock)
}
}
// AssertPeerNotConnected asserts that the given node b is not connected to a.
func (h *HarnessTest) AssertPeerNotConnected(a, b *node.HarnessNode) {
err := wait.NoError(func() error {
// We require the RPC call to be succeeded and won't wait for
// it as it's an unexpected behavior.
resp := a.RPC.ListPeers()
// If node B is seen in the ListPeers response from node A,
// then we return false as the connection has been fully
// established.
for _, peer := range resp.Peers {
if peer.PubKey == b.PubKeyStr {
return fmt.Errorf("peers %s and %s still "+
"connected", a.Name(), b.Name())
}
}
return nil
}, DefaultTimeout)
require.NoError(h, err, "timeout checking peers not connected")
}
// AssertNotConnected asserts that two peers are not connected.
func (h *HarnessTest) AssertNotConnected(a, b *node.HarnessNode) {
h.AssertPeerNotConnected(a, b)
h.AssertPeerNotConnected(b, a)
}
// AssertConnected asserts that two peers are connected.
func (h *HarnessTest) AssertConnected(a, b *node.HarnessNode) {
h.AssertPeerConnected(a, b)
h.AssertPeerConnected(b, a)
}
// AssertAmountPaid checks that the ListChannels command of the provided
// node list the total amount sent and received as expected for the
// provided channel.
func (h *HarnessTest) AssertAmountPaid(channelName string, hn *node.HarnessNode,
chanPoint *lnrpc.ChannelPoint, amountSent, amountReceived int64) {
checkAmountPaid := func() error {
// Find the targeted channel.
channel, err := h.findChannel(hn, chanPoint)
if err != nil {
return fmt.Errorf("assert amount failed: %w", err)
}
if channel.TotalSatoshisSent != amountSent {
return fmt.Errorf("%v: incorrect amount"+
" sent: %v != %v", channelName,
channel.TotalSatoshisSent,
amountSent)
}
if channel.TotalSatoshisReceived !=
amountReceived {
return fmt.Errorf("%v: incorrect amount"+
" received: %v != %v",
channelName,
channel.TotalSatoshisReceived,
amountReceived)
}
return nil
}
// As far as HTLC inclusion in commitment transaction might be
// postponed we will try to check the balance couple of times,
// and then if after some period of time we receive wrong
// balance return the error.
err := wait.NoError(checkAmountPaid, DefaultTimeout)
require.NoError(h, err, "timeout while checking amount paid")
}
// AssertLastHTLCError checks that the last sent HTLC of the last payment sent
// by the given node failed with the expected failure code.
func (h *HarnessTest) AssertLastHTLCError(hn *node.HarnessNode,
code lnrpc.Failure_FailureCode) {
// Use -1 to specify the last HTLC.
h.assertHTLCError(hn, code, -1)
}
// AssertFirstHTLCError checks that the first HTLC of the last payment sent
// by the given node failed with the expected failure code.
func (h *HarnessTest) AssertFirstHTLCError(hn *node.HarnessNode,
code lnrpc.Failure_FailureCode) {
// Use 0 to specify the first HTLC.
h.assertHTLCError(hn, code, 0)
}
// assertLastHTLCError checks that the HTLC at the specified index of the last
// payment sent by the given node failed with the expected failure code.
func (h *HarnessTest) assertHTLCError(hn *node.HarnessNode,
code lnrpc.Failure_FailureCode, index int) {
req := &lnrpc.ListPaymentsRequest{
IncludeIncomplete: true,
}
err := wait.NoError(func() error {
paymentsResp := hn.RPC.ListPayments(req)
payments := paymentsResp.Payments
if len(payments) == 0 {
return fmt.Errorf("no payments found")
}
payment := payments[len(payments)-1]
htlcs := payment.Htlcs
if len(htlcs) == 0 {
return fmt.Errorf("no htlcs found")
}
// If the index is greater than 0, check we have enough htlcs.
if index > 0 && len(htlcs) <= index {
return fmt.Errorf("not enough htlcs")
}
// If index is less than or equal to 0, we will read the last
// htlc.
if index <= 0 {
index = len(htlcs) - 1
}
htlc := htlcs[index]
// The htlc must have a status of failed.
if htlc.Status != lnrpc.HTLCAttempt_FAILED {
return fmt.Errorf("htlc should be failed")
}
// The failure field must not be empty.
if htlc.Failure == nil {
return fmt.Errorf("expected htlc failure")
}
// Exit if the expected code is found.
if htlc.Failure.Code == code {
return nil
}
return fmt.Errorf("unexpected failure code")
}, DefaultTimeout)
require.NoError(h, err, "timeout checking HTLC error")
}
// AssertZombieChannel asserts that a given channel found using the chanID is
// marked as zombie.
func (h *HarnessTest) AssertZombieChannel(hn *node.HarnessNode, chanID uint64) {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
err := wait.NoError(func() error {
_, err := hn.RPC.LN.GetChanInfo(
ctxt, &lnrpc.ChanInfoRequest{ChanId: chanID},
)
if err == nil {
return fmt.Errorf("expected error but got nil")
}
if !strings.Contains(err.Error(), "marked as zombie") {
return fmt.Errorf("expected error to contain '%s' but "+
"was '%v'", "marked as zombie", err)
}
return nil
}, DefaultTimeout)
require.NoError(h, err, "timeout while checking zombie channel")
}
// AssertNotInGraph asserts that a given channel is either not found at all in
// the graph or that it has been marked as a zombie.
func (h *HarnessTest) AssertNotInGraph(hn *node.HarnessNode, chanID uint64) {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
err := wait.NoError(func() error {
_, err := hn.RPC.LN.GetChanInfo(
ctxt, &lnrpc.ChanInfoRequest{ChanId: chanID},
)
if err == nil {
return fmt.Errorf("expected error but got nil")
}
switch {
case strings.Contains(err.Error(), "marked as zombie"):
return nil
case strings.Contains(err.Error(), "edge not found"):
return nil
default:
return fmt.Errorf("expected error to contain either "+
"'%s' or '%s' but was: '%v'", "marked as i"+
"zombie", "edge not found", err)
}
}, DefaultTimeout)
require.NoError(h, err, "timeout while checking that channel is not "+
"found in graph")
}
// AssertChannelInGraphDB asserts that a given channel is found in the graph db.
func (h *HarnessTest) AssertChannelInGraphDB(hn *node.HarnessNode,
chanPoint *lnrpc.ChannelPoint) *lnrpc.ChannelEdge {
ctxt, cancel := context.WithCancel(h.runCtx)
defer cancel()
var edge *lnrpc.ChannelEdge
op := h.OutPointFromChannelPoint(chanPoint)
err := wait.NoError(func() error {
resp, err := hn.RPC.LN.GetChanInfo(
ctxt, &lnrpc.ChanInfoRequest{
ChanPoint: op.String(),
},
)
if err != nil {
return fmt.Errorf("channel %s not found in graph: %w",
op, err)
}
// Make sure the policies are populated, otherwise this edge
// cannot be used for routing.
if resp.Node1Policy == nil {
return fmt.Errorf("channel %s has no policy1", op)
}
if resp.Node2Policy == nil {
return fmt.Errorf("channel %s has no policy2", op)
}
edge = resp
return nil
}, DefaultTimeout)
require.NoError(h, err, "%s: timeout finding channel in graph",
hn.Name())
return edge
}
// AssertChannelInGraphCache asserts a given channel is found in the graph
// cache.
func (h *HarnessTest) AssertChannelInGraphCache(hn *node.HarnessNode,
chanPoint *lnrpc.ChannelPoint) *lnrpc.ChannelEdge {
var edge *lnrpc.ChannelEdge
req := &lnrpc.ChannelGraphRequest{IncludeUnannounced: true}
cpStr := channelPointStr(chanPoint)
err := wait.NoError(func() error {
chanGraph := hn.RPC.DescribeGraph(req)
// Iterate all the known edges, and make sure the edge policies
// are populated when a matched edge is found.
for _, e := range chanGraph.Edges {
if e.ChanPoint != cpStr {
continue
}
if e.Node1Policy == nil {
return fmt.Errorf("no policy for node1 %v",
e.Node1Pub)
}
if e.Node2Policy == nil {
return fmt.Errorf("no policy for node2 %v",
e.Node1Pub)
}
edge = e
return nil
}
// If we've iterated over all the known edges and we weren't
// able to find this specific one, then we'll fail.
return fmt.Errorf("no edge found for channel point: %s", cpStr)
}, DefaultTimeout)
require.NoError(h, err, "%s: timeout finding channel %v in graph cache",
cpStr, hn.Name())
return edge
}
// AssertChannelInGraphDB asserts that a given channel is found both in the
// graph db (GetChanInfo) and the graph cache (DescribeGraph).
func (h *HarnessTest) AssertChannelInGraph(hn *node.HarnessNode,
chanPoint *lnrpc.ChannelPoint) *lnrpc.ChannelEdge {
// Make sure the channel is found in the db first.
h.AssertChannelInGraphDB(hn, chanPoint)
// Assert the channel is also found in the graph cache, which refreshes
// every `--caches.rpc-graph-cache-duration`.
return h.AssertChannelInGraphCache(hn, chanPoint)
}
// AssertTxAtHeight gets all of the transactions that a node's wallet has a
// record of at the target height, and finds and returns the tx with the target
// txid, failing if it is not found.
func (h *HarnessTest) AssertTxAtHeight(hn *node.HarnessNode, height int32,
txid *chainhash.Hash) *lnrpc.Transaction {
req := &lnrpc.GetTransactionsRequest{
StartHeight: height,
EndHeight: height,
}
txns := hn.RPC.GetTransactions(req)
for _, tx := range txns.Transactions {
if tx.TxHash == txid.String() {
return tx
}
}
require.Failf(h, "fail to find tx", "tx:%v not found at height:%v",
txid, height)
return nil
}
// getChannelPolicies queries the channel graph and retrieves the current edge
// policies for the provided channel point.
func (h *HarnessTest) getChannelPolicies(hn *node.HarnessNode,
advertisingNode string,
cp *lnrpc.ChannelPoint) (*lnrpc.RoutingPolicy, error) {
req := &lnrpc.ChannelGraphRequest{IncludeUnannounced: true}
chanGraph := hn.RPC.DescribeGraph(req)
cpStr := channelPointStr(cp)
for _, e := range chanGraph.Edges {
if e.ChanPoint != cpStr {
continue
}
if e.Node1Pub == advertisingNode {
return e.Node1Policy, nil
}
return e.Node2Policy, nil
}
// If we've iterated over all the known edges and we weren't
// able to find this specific one, then we'll fail.
return nil, fmt.Errorf("did not find edge with advertisingNode: %s"+
", channel point: %s", advertisingNode, cpStr)
}
// AssertChannelPolicy asserts that the passed node's known channel policy for
// the passed chanPoint is consistent with the expected policy values.
func (h *HarnessTest) AssertChannelPolicy(hn *node.HarnessNode,
advertisingNode string, expectedPolicy *lnrpc.RoutingPolicy,
chanPoint *lnrpc.ChannelPoint) {
policy, err := h.getChannelPolicies(hn, advertisingNode, chanPoint)
require.NoErrorf(h, err, "%s: failed to find policy", hn.Name())
err = node.CheckChannelPolicy(policy, expectedPolicy)
require.NoErrorf(h, err, "%s: check policy failed", hn.Name())
}
// AssertNumPolicyUpdates asserts that a given number of channel policy updates
// has been seen in the specified node.
func (h *HarnessTest) AssertNumPolicyUpdates(hn *node.HarnessNode,
chanPoint *lnrpc.ChannelPoint,
advertisingNode *node.HarnessNode, num int) {
op := h.OutPointFromChannelPoint(chanPoint)
var policies []*node.PolicyUpdateInfo
err := wait.NoError(func() error {
policyMap := hn.Watcher.GetPolicyUpdates(op)
nodePolicy, ok := policyMap[advertisingNode.PubKeyStr]
if ok {
policies = nodePolicy
}
if len(policies) == num {
return nil
}
p, err := json.MarshalIndent(policies, "", "\t")
require.NoError(h, err, "encode policy err")
return fmt.Errorf("expected to find %d policy updates, "+
"instead got: %d, chanPoint: %v, "+
"advertisingNode: %s:%s, policy: %s", num,
len(policies), op, advertisingNode.Name(),
advertisingNode.PubKeyStr, p)
}, DefaultTimeout)
require.NoError(h, err, "%s: timeout waiting for num of policy updates",
hn.Name())
}
// AssertNumPayments asserts that the number of payments made within the test
// scope is as expected, including the incomplete ones.
func (h *HarnessTest) AssertNumPayments(hn *node.HarnessNode,
num int) []*lnrpc.Payment {
// Get the number of payments we already have from the previous test.
have := hn.State.Payment.Total
req := &lnrpc.ListPaymentsRequest{
IncludeIncomplete: true,
IndexOffset: hn.State.Payment.LastIndexOffset,
}
var payments []*lnrpc.Payment
err := wait.NoError(func() error {
resp := hn.RPC.ListPayments(req)
payments = resp.Payments
if len(payments) == num {
return nil
}
return errNumNotMatched(hn.Name(), "num of payments",
num, len(payments), have+len(payments), have)
}, DefaultTimeout)
require.NoError(h, err, "%s: timeout checking num of payments",
hn.Name())
return payments
}
// AssertNumNodeAnns asserts that a given number of node announcements has been
// seen in the specified node.
func (h *HarnessTest) AssertNumNodeAnns(hn *node.HarnessNode,
pubkey string, num int) []*lnrpc.NodeUpdate {
// We will get the current number of channel updates first and add it
// to our expected number of newly created channel updates.
anns, err := hn.Watcher.WaitForNumNodeUpdates(pubkey, num)
require.NoError(h, err, "%s: failed to assert num of node anns",
hn.Name())
return anns
}
// AssertNumChannelUpdates asserts that a given number of channel updates has
// been seen in the specified node's network topology.
func (h *HarnessTest) AssertNumChannelUpdates(hn *node.HarnessNode,
chanPoint *lnrpc.ChannelPoint, num int) {
op := h.OutPointFromChannelPoint(chanPoint)
err := hn.Watcher.WaitForNumChannelUpdates(op, num)
require.NoError(h, err, "%s: failed to assert num of channel updates",
hn.Name())
}
// CreateBurnAddr creates a random burn address of the given type.
func (h *HarnessTest) CreateBurnAddr(addrType lnrpc.AddressType) ([]byte,
btcutil.Address) {
randomPrivKey, err := btcec.NewPrivateKey()
require.NoError(h, err)
randomKeyBytes := randomPrivKey.PubKey().SerializeCompressed()
harnessNetParams := miner.HarnessNetParams
var addr btcutil.Address
switch addrType {
case lnrpc.AddressType_WITNESS_PUBKEY_HASH:
addr, err = btcutil.NewAddressWitnessPubKeyHash(
btcutil.Hash160(randomKeyBytes), harnessNetParams,
)
case lnrpc.AddressType_TAPROOT_PUBKEY:
taprootKey := txscript.ComputeTaprootKeyNoScript(
randomPrivKey.PubKey(),
)
addr, err = btcutil.NewAddressPubKey(
schnorr.SerializePubKey(taprootKey), harnessNetParams,
)
case lnrpc.AddressType_NESTED_PUBKEY_HASH:
var witnessAddr btcutil.Address
witnessAddr, err = btcutil.NewAddressWitnessPubKeyHash(
btcutil.Hash160(randomKeyBytes), harnessNetParams,
)
require.NoError(h, err)
addr, err = btcutil.NewAddressScriptHash(
h.PayToAddrScript(witnessAddr), harnessNetParams,
)
default:
h.Fatalf("Unsupported burn address type: %v", addrType)
}
require.NoError(h, err)
return h.PayToAddrScript(addr), addr
}
// ReceiveTrackPayment waits until a message is received on the track payment
// stream or the timeout is reached.
func (h *HarnessTest) ReceiveTrackPayment(
stream rpc.TrackPaymentClient) *lnrpc.Payment {
chanMsg := make(chan *lnrpc.Payment)
errChan := make(chan error)
go func() {
// Consume one message. This will block until the message is
// received.
resp, err := stream.Recv()
if err != nil {
errChan <- err
return
}
chanMsg <- resp
}()
select {
case <-time.After(DefaultTimeout):
require.Fail(h, "timeout", "timeout trakcing payment")
case err := <-errChan:
require.Failf(h, "err from stream",
"received err from stream: %v", err)
case updateMsg := <-chanMsg:
return updateMsg
}
return nil
}
// ReceiveHtlcEvent waits until a message is received on the subscribe
// htlc event stream or the timeout is reached.
func (h *HarnessTest) ReceiveHtlcEvent(
stream rpc.HtlcEventsClient) *routerrpc.HtlcEvent {
chanMsg := make(chan *routerrpc.HtlcEvent)
errChan := make(chan error)
go func() {
// Consume one message. This will block until the message is
// received.
resp, err := stream.Recv()
if err != nil {
errChan <- err
return
}
chanMsg <- resp
}()
select {
case <-time.After(DefaultTimeout):
require.Fail(h, "timeout", "timeout receiving htlc "+
"event update")
case err := <-errChan:
require.Failf(h, "err from stream",
"received err from stream: %v", err)
case updateMsg := <-chanMsg:
return updateMsg
}
return nil
}
// AssertHtlcEventType consumes one event from a client and asserts the event
// type is matched.
func (h *HarnessTest) AssertHtlcEventType(client rpc.HtlcEventsClient,
userType routerrpc.HtlcEvent_EventType) *routerrpc.HtlcEvent {
event := h.ReceiveHtlcEvent(client)
require.Equalf(h, userType, event.EventType, "wrong event type, "+
"want %v got %v", userType, event.EventType)
return event
}
// HtlcEvent maps the series of event types used in `*routerrpc.HtlcEvent_*`.
type HtlcEvent int
const (
HtlcEventForward HtlcEvent = iota
HtlcEventForwardFail
HtlcEventSettle
HtlcEventLinkFail
HtlcEventFinal
)
// AssertHtlcEventType consumes one event from a client and asserts both the
// user event type the event.Event type is matched.
func (h *HarnessTest) AssertHtlcEventTypes(client rpc.HtlcEventsClient,
userType routerrpc.HtlcEvent_EventType,
eventType HtlcEvent) *routerrpc.HtlcEvent {
event := h.ReceiveHtlcEvent(client)
require.Equalf(h, userType, event.EventType, "wrong event type, "+
"want %v got %v", userType, event.EventType)
var ok bool
switch eventType {
case HtlcEventForward:
_, ok = event.Event.(*routerrpc.HtlcEvent_ForwardEvent)
case HtlcEventForwardFail:
_, ok = event.Event.(*routerrpc.HtlcEvent_ForwardFailEvent)
case HtlcEventSettle:
_, ok = event.Event.(*routerrpc.HtlcEvent_SettleEvent)
case HtlcEventLinkFail:
_, ok = event.Event.(*routerrpc.HtlcEvent_LinkFailEvent)
case HtlcEventFinal:
_, ok = event.Event.(*routerrpc.HtlcEvent_FinalHtlcEvent)
}
require.Truef(h, ok, "wrong event type: %T, want %T", event.Event,
eventType)
return event
}
// AssertFeeReport checks that the fee report from the given node has the
// desired day, week, and month sum values.
func (h *HarnessTest) AssertFeeReport(hn *node.HarnessNode,
day, week, month int) {
err := wait.NoError(func() error {
feeReport, err := hn.RPC.LN.FeeReport(
h.runCtx, &lnrpc.FeeReportRequest{},
)
require.NoError(h, err, "unable to query for fee report")
if uint64(day) != feeReport.DayFeeSum {
return fmt.Errorf("day fee mismatch, want %d, got %d",
day, feeReport.DayFeeSum)
}
if uint64(week) != feeReport.WeekFeeSum {
return fmt.Errorf("week fee mismatch, want %d, got %d",
week, feeReport.WeekFeeSum)
}
if uint64(month) != feeReport.MonthFeeSum {
return fmt.Errorf("month fee mismatch, want %d, got %d",
month, feeReport.MonthFeeSum)
}
return nil
}, wait.DefaultTimeout)
require.NoErrorf(h, err, "%s: time out checking fee report", hn.Name())
}
// AssertHtlcEvents consumes events from a client and ensures that they are of
// the expected type and contain the expected number of forwards, forward
// failures and settles.
//
// TODO(yy): needs refactor to reduce its complexity.
func (h *HarnessTest) AssertHtlcEvents(client rpc.HtlcEventsClient,
fwdCount, fwdFailCount, settleCount, linkFailCount int,
userType routerrpc.HtlcEvent_EventType) []*routerrpc.HtlcEvent {
var forwards, forwardFails, settles, linkFails int
numEvents := fwdCount + fwdFailCount + settleCount + linkFailCount
events := make([]*routerrpc.HtlcEvent, 0)
// It's either the userType or the unknown type.
//
// TODO(yy): maybe the FinalHtlcEvent shouldn't be in UNKNOWN type?
eventTypes := []routerrpc.HtlcEvent_EventType{
userType, routerrpc.HtlcEvent_UNKNOWN,
}
for i := 0; i < numEvents; i++ {
event := h.ReceiveHtlcEvent(client)
require.Containsf(h, eventTypes, event.EventType,
"wrong event type, want %v, got %v", userType,
event.EventType)
events = append(events, event)
switch e := event.Event.(type) {
case *routerrpc.HtlcEvent_ForwardEvent:
forwards++
case *routerrpc.HtlcEvent_ForwardFailEvent:
forwardFails++
case *routerrpc.HtlcEvent_SettleEvent:
settles++
case *routerrpc.HtlcEvent_FinalHtlcEvent:
if e.FinalHtlcEvent.Settled {
settles++
}
case *routerrpc.HtlcEvent_LinkFailEvent:
linkFails++
default:
require.Fail(h, "assert event fail",
"unexpected event: %T", event.Event)
}
}
require.Equal(h, fwdCount, forwards, "num of forwards mismatch")
require.Equal(h, fwdFailCount, forwardFails,
"num of forward fails mismatch")
require.Equal(h, settleCount, settles, "num of settles mismatch")
require.Equal(h, linkFailCount, linkFails, "num of link fails mismatch")
return events
}
// AssertTransactionInWallet asserts a given txid can be found in the node's
// wallet.
func (h *HarnessTest) AssertTransactionInWallet(hn *node.HarnessNode,
txid chainhash.Hash) {
req := &lnrpc.GetTransactionsRequest{}
err := wait.NoError(func() error {
txResp := hn.RPC.GetTransactions(req)
for _, txn := range txResp.Transactions {
if txn.TxHash == txid.String() {
return nil
}
}
return fmt.Errorf("%s: expected txid=%v not found in wallet",
hn.Name(), txid)
}, DefaultTimeout)
require.NoError(h, err, "failed to find tx")
}
// AssertTransactionNotInWallet asserts a given txid can NOT be found in the
// node's wallet.
func (h *HarnessTest) AssertTransactionNotInWallet(hn *node.HarnessNode,
txid chainhash.Hash) {
req := &lnrpc.GetTransactionsRequest{}
err := wait.NoError(func() error {
txResp := hn.RPC.GetTransactions(req)
for _, txn := range txResp.Transactions {
if txn.TxHash == txid.String() {
return fmt.Errorf("expected txid=%v to be "+
"not found", txid)
}
}
return nil
}, DefaultTimeout)
require.NoErrorf(h, err, "%s: failed to assert tx not found", hn.Name())
}
// WaitForNodeBlockHeight queries the node for its current block height until
// it reaches the passed height.
func (h *HarnessTest) WaitForNodeBlockHeight(hn *node.HarnessNode,
height int32) {
err := wait.NoError(func() error {
info := hn.RPC.GetInfo()
if int32(info.BlockHeight) != height {
return fmt.Errorf("expected block height to "+
"be %v, was %v", height, info.BlockHeight)
}
return nil
}, DefaultTimeout)
require.NoErrorf(h, err, "%s: timeout while waiting for height",
hn.Name())
}
// AssertChannelCommitHeight asserts the given channel for the node has the
// expected commit height(`NumUpdates`).
func (h *HarnessTest) AssertChannelCommitHeight(hn *node.HarnessNode,
cp *lnrpc.ChannelPoint, height int) {
err := wait.NoError(func() error {
c, err := h.findChannel(hn, cp)
if err != nil {
return err
}
if int(c.NumUpdates) == height {
return nil
}
return fmt.Errorf("expected commit height to be %v, was %v",
height, c.NumUpdates)
}, DefaultTimeout)
require.NoError(h, err, "timeout while waiting for commit height")
}
// AssertNumInvoices asserts that the number of invoices made within the test
// scope is as expected.
func (h *HarnessTest) AssertNumInvoices(hn *node.HarnessNode,
num int) []*lnrpc.Invoice {
have := hn.State.Invoice.Total
req := &lnrpc.ListInvoiceRequest{
NumMaxInvoices: math.MaxUint64,
IndexOffset: hn.State.Invoice.LastIndexOffset,
}
var invoices []*lnrpc.Invoice
err := wait.NoError(func() error {
resp := hn.RPC.ListInvoices(req)
invoices = resp.Invoices
if len(invoices) == num {
return nil
}
return errNumNotMatched(hn.Name(), "num of invoices",
num, len(invoices), have+len(invoices), have)
}, DefaultTimeout)
require.NoError(h, err, "timeout checking num of invoices")
return invoices
}
// ReceiveSendToRouteUpdate waits until a message is received on the
// SendToRoute client stream or the timeout is reached.
func (h *HarnessTest) ReceiveSendToRouteUpdate(
stream rpc.SendToRouteClient) (*lnrpc.SendResponse, error) {
chanMsg := make(chan *lnrpc.SendResponse, 1)
errChan := make(chan error, 1)
go func() {
// Consume one message. This will block until the message is
// received.
resp, err := stream.Recv()
if err != nil {
errChan <- err
return
}
chanMsg <- resp
}()
select {
case <-time.After(DefaultTimeout):
require.Fail(h, "timeout", "timeout waiting for send resp")
return nil, nil
case err := <-errChan:
return nil, err
case updateMsg := <-chanMsg:
return updateMsg, nil
}
}
// AssertInvoiceEqual asserts that two lnrpc.Invoices are equivalent. A custom
// comparison function is defined for these tests, since proto message returned
// from unary and streaming RPCs (as of protobuf 1.23.0 and grpc 1.29.1) aren't
// consistent with the private fields set on the messages. As a result, we
// avoid using require.Equal and test only the actual data members.
func (h *HarnessTest) AssertInvoiceEqual(a, b *lnrpc.Invoice) {
// Ensure the HTLCs are sorted properly before attempting to compare.
sort.Slice(a.Htlcs, func(i, j int) bool {
return a.Htlcs[i].ChanId < a.Htlcs[j].ChanId
})
sort.Slice(b.Htlcs, func(i, j int) bool {
return b.Htlcs[i].ChanId < b.Htlcs[j].ChanId
})
require.Equal(h, a.Memo, b.Memo)
require.Equal(h, a.RPreimage, b.RPreimage)
require.Equal(h, a.RHash, b.RHash)
require.Equal(h, a.Value, b.Value)
require.Equal(h, a.ValueMsat, b.ValueMsat)
require.Equal(h, a.CreationDate, b.CreationDate)
require.Equal(h, a.SettleDate, b.SettleDate)
require.Equal(h, a.PaymentRequest, b.PaymentRequest)
require.Equal(h, a.DescriptionHash, b.DescriptionHash)
require.Equal(h, a.Expiry, b.Expiry)
require.Equal(h, a.FallbackAddr, b.FallbackAddr)
require.Equal(h, a.CltvExpiry, b.CltvExpiry)
require.Equal(h, a.RouteHints, b.RouteHints)
require.Equal(h, a.Private, b.Private)
require.Equal(h, a.AddIndex, b.AddIndex)
require.Equal(h, a.SettleIndex, b.SettleIndex)
require.Equal(h, a.AmtPaidSat, b.AmtPaidSat)
require.Equal(h, a.AmtPaidMsat, b.AmtPaidMsat)
require.Equal(h, a.State, b.State)
require.Equal(h, a.Features, b.Features)
require.Equal(h, a.IsKeysend, b.IsKeysend)
require.Equal(h, a.PaymentAddr, b.PaymentAddr)
require.Equal(h, a.IsAmp, b.IsAmp)
require.Equal(h, len(a.Htlcs), len(b.Htlcs))
for i := range a.Htlcs {
htlcA, htlcB := a.Htlcs[i], b.Htlcs[i]
require.Equal(h, htlcA.ChanId, htlcB.ChanId)
require.Equal(h, htlcA.HtlcIndex, htlcB.HtlcIndex)
require.Equal(h, htlcA.AmtMsat, htlcB.AmtMsat)
require.Equal(h, htlcA.AcceptHeight, htlcB.AcceptHeight)
require.Equal(h, htlcA.AcceptTime, htlcB.AcceptTime)
require.Equal(h, htlcA.ResolveTime, htlcB.ResolveTime)
require.Equal(h, htlcA.ExpiryHeight, htlcB.ExpiryHeight)
require.Equal(h, htlcA.State, htlcB.State)
require.Equal(h, htlcA.CustomRecords, htlcB.CustomRecords)
require.Equal(h, htlcA.MppTotalAmtMsat, htlcB.MppTotalAmtMsat)
require.Equal(h, htlcA.Amp, htlcB.Amp)
}
}
// AssertUTXOInWallet asserts that a given UTXO can be found in the node's
// wallet.
func (h *HarnessTest) AssertUTXOInWallet(hn *node.HarnessNode,
op *lnrpc.OutPoint, account string) {
err := wait.NoError(func() error {
utxos := h.GetUTXOs(hn, account)
err := fmt.Errorf("tx with hash %x not found", op.TxidBytes)
for _, utxo := range utxos {
if !bytes.Equal(utxo.Outpoint.TxidBytes, op.TxidBytes) {
continue
}
err = fmt.Errorf("tx with output index %v not found",
op.OutputIndex)
if utxo.Outpoint.OutputIndex != op.OutputIndex {
continue
}
return nil
}
return err
}, DefaultTimeout)
require.NoErrorf(h, err, "outpoint %v not found in %s's wallet",
op, hn.Name())
}
// AssertWalletAccountBalance asserts that the unconfirmed and confirmed
// balance for the given account is satisfied by the WalletBalance and
// ListUnspent RPCs. The unconfirmed balance is not checked for neutrino nodes.
func (h *HarnessTest) AssertWalletAccountBalance(hn *node.HarnessNode,
account string, confirmedBalance, unconfirmedBalance int64) {
err := wait.NoError(func() error {
balanceResp := hn.RPC.WalletBalance()
require.Contains(h, balanceResp.AccountBalance, account)
accountBalance := balanceResp.AccountBalance[account]
// Check confirmed balance.
if accountBalance.ConfirmedBalance != confirmedBalance {
return fmt.Errorf("expected confirmed balance %v, "+
"got %v", confirmedBalance,
accountBalance.ConfirmedBalance)
}
utxos := h.GetUTXOsConfirmed(hn, account)
var totalConfirmedVal int64
for _, utxo := range utxos {
totalConfirmedVal += utxo.AmountSat
}
if totalConfirmedVal != confirmedBalance {
return fmt.Errorf("expected total confirmed utxo "+
"balance %v, got %v", confirmedBalance,
totalConfirmedVal)
}
// Skip unconfirmed balance checks for neutrino nodes.
if h.IsNeutrinoBackend() {
return nil
}
// Check unconfirmed balance.
if accountBalance.UnconfirmedBalance != unconfirmedBalance {
return fmt.Errorf("expected unconfirmed balance %v, "+
"got %v", unconfirmedBalance,
accountBalance.UnconfirmedBalance)
}
utxos = h.GetUTXOsUnconfirmed(hn, account)
var totalUnconfirmedVal int64
for _, utxo := range utxos {
totalUnconfirmedVal += utxo.AmountSat
}
if totalUnconfirmedVal != unconfirmedBalance {
return fmt.Errorf("expected total unconfirmed utxo "+
"balance %v, got %v", unconfirmedBalance,
totalUnconfirmedVal)
}
return nil
}, DefaultTimeout)
require.NoError(h, err, "timeout checking wallet account balance")
}
// AssertClosingTxInMempool assert that the closing transaction of the given
// channel point can be found in the mempool. If the channel has anchors, it
// will assert the anchor sweep tx is also in the mempool.
func (h *HarnessTest) AssertClosingTxInMempool(cp *lnrpc.ChannelPoint,
c lnrpc.CommitmentType) *wire.MsgTx {
// Get expected number of txes to be found in the mempool.
expectedTxes := 1
hasAnchors := CommitTypeHasAnchors(c)
if hasAnchors {
expectedTxes = 2
}
// Wait for the expected txes to be found in the mempool.
h.AssertNumTxsInMempool(expectedTxes)
// Get the closing tx from the mempool.
op := h.OutPointFromChannelPoint(cp)
closeTx := h.AssertOutpointInMempool(op)
return closeTx
}
// AssertClosingTxInMempool assert that the closing transaction of the given
// channel point can be found in the mempool. If the channel has anchors, it
// will assert the anchor sweep tx is also in the mempool.
func (h *HarnessTest) MineClosingTx(cp *lnrpc.ChannelPoint) *wire.MsgTx {
// Wait for the expected txes to be found in the mempool.
h.AssertNumTxsInMempool(1)
// Get the closing tx from the mempool.
op := h.OutPointFromChannelPoint(cp)
closeTx := h.AssertOutpointInMempool(op)
// Mine a block to confirm the closing transaction and potential anchor
// sweep.
h.MineBlocksAndAssertNumTxes(1, 1)
return closeTx
}
// AssertWalletLockedBalance asserts the expected amount has been marked as
// locked in the node's WalletBalance response.
func (h *HarnessTest) AssertWalletLockedBalance(hn *node.HarnessNode,
balance int64) {
err := wait.NoError(func() error {
balanceResp := hn.RPC.WalletBalance()
got := balanceResp.LockedBalance
if got != balance {
return fmt.Errorf("want %d, got %d", balance, got)
}
return nil
}, wait.DefaultTimeout)
require.NoError(h, err, "%s: timeout checking locked balance",
hn.Name())
}
// AssertNumPendingSweeps asserts the number of pending sweeps for the given
// node.
func (h *HarnessTest) AssertNumPendingSweeps(hn *node.HarnessNode,
n int) []*walletrpc.PendingSweep {
results := make([]*walletrpc.PendingSweep, 0, n)
err := wait.NoError(func() error {
resp := hn.RPC.PendingSweeps()
num := len(resp.PendingSweeps)
numDesc := "\n"
for _, s := range resp.PendingSweeps {
desc := fmt.Sprintf("op=%v:%v, amt=%v, type=%v, "+
"deadline=%v\n", s.Outpoint.TxidStr,
s.Outpoint.OutputIndex, s.AmountSat,
s.WitnessType, s.DeadlineHeight)
numDesc += desc
// The deadline height must be set, otherwise the
// pending input response is not update-to-date.
if s.DeadlineHeight == 0 {
return fmt.Errorf("input not updated: %s", desc)
}
}
if num == n {
results = resp.PendingSweeps
return nil
}
return fmt.Errorf("want %d , got %d, sweeps: %s", n, num,
numDesc)
}, DefaultTimeout)
require.NoErrorf(h, err, "%s: check pending sweeps timeout", hn.Name())
return results
}
// FindSweepingTxns asserts the expected number of sweeping txns are found in
// the txns specified and return them.
func (h *HarnessTest) FindSweepingTxns(txns []*wire.MsgTx,
expectedNumSweeps int, closeTxid chainhash.Hash) []*wire.MsgTx {
var sweepTxns []*wire.MsgTx
for _, tx := range txns {
if tx.TxIn[0].PreviousOutPoint.Hash == closeTxid {
sweepTxns = append(sweepTxns, tx)
}
}
require.Len(h, sweepTxns, expectedNumSweeps, "unexpected num of sweeps")
return sweepTxns
}
// AssertForceCloseAndAnchorTxnsInMempool asserts that the force close and
// anchor sweep txns are found in the mempool and returns the force close tx
// and the anchor sweep tx.
func (h *HarnessTest) AssertForceCloseAndAnchorTxnsInMempool() (*wire.MsgTx,
*wire.MsgTx) {
// Assert there are two txns in the mempool.
txns := h.GetNumTxsFromMempool(2)
// isParentAndChild checks whether there is an input used in the
// assumed child tx by checking every input's previous outpoint against
// the assumed parentTxid.
isParentAndChild := func(parent, child *wire.MsgTx) bool {
parentTxid := parent.TxHash()
for _, inp := range child.TxIn {
if inp.PreviousOutPoint.Hash == parentTxid {
// Found a match, this is indeed the anchor
// sweeping tx so we return it here.
return true
}
}
return false
}
switch {
// Assume the first one is the closing tx and the second one is the
// anchor sweeping tx.
case isParentAndChild(txns[0], txns[1]):
return txns[0], txns[1]
// Assume the first one is the anchor sweeping tx and the second one is
// the closing tx.
case isParentAndChild(txns[1], txns[0]):
return txns[1], txns[0]
// Unrelated txns found, fail the test.
default:
h.Fatalf("the two txns not related: %v", txns)
return nil, nil
}
}
package lntest
import (
"fmt"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/lntest/miner"
"github.com/lightningnetwork/lnd/lntest/node"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require"
)
// Miner returns the miner instance.
//
// NOTE: Caller should keep in mind that when using this private instance,
// certain states won't be managed by the HarnessTest anymore. For instance,
// when mining directly, the nodes managed by the HarnessTest can be out of
// sync, and the `HarnessTest.CurrentHeight()` won't be accurate.
func (h *HarnessTest) Miner() *miner.HarnessMiner {
return h.miner
}
// MineBlocks mines blocks and asserts all active nodes have synced to the
// chain. It assumes no txns are expected in the blocks.
//
// NOTE: Use `MineBlocksAndAssertNumTxes` if you expect txns in the blocks. Use
// `MineEmptyBlocks` if you want to make sure that txns stay unconfirmed.
func (h *HarnessTest) MineBlocks(num int) {
require.Less(h, num, maxBlocksAllowed, "too many blocks to mine")
// Update the harness's current height.
defer h.updateCurrentHeight()
// Mine num of blocks.
for i := 0; i < num; i++ {
block := h.miner.MineBlocks(1)[0]
// Check the block doesn't have any txns except the coinbase.
if len(block.Transactions) <= 1 {
// Make sure all the active nodes are synced.
h.AssertActiveNodesSyncedTo(block.BlockHash())
// Mine the next block.
continue
}
// Create a detailed description.
desc := fmt.Sprintf("block %v has %d txns:\n",
block.BlockHash(), len(block.Transactions)-1)
// Print all the txns except the coinbase.
for _, tx := range block.Transactions {
if blockchain.IsCoinBaseTx(tx) {
continue
}
desc += fmt.Sprintf("%v\n", tx.TxHash())
}
desc += "Consider using `MineBlocksAndAssertNumTxes` if you " +
"expect txns, or `MineEmptyBlocks` if you want to " +
"keep the txns unconfirmed."
// Raise an error if the block has txns.
require.Fail(h, "MineBlocks", desc)
}
}
// MineEmptyBlocks mines a given number of empty blocks.
//
// NOTE: this differs from miner's `MineEmptyBlocks` as it requires the nodes
// to be synced.
func (h *HarnessTest) MineEmptyBlocks(num int) []*wire.MsgBlock {
require.Less(h, num, maxBlocksAllowed, "too many blocks to mine")
// Update the harness's current height.
defer h.updateCurrentHeight()
blocks := h.miner.MineEmptyBlocks(num)
// Finally, make sure all the active nodes are synced.
h.AssertActiveNodesSynced()
return blocks
}
// MineBlocksAndAssertNumTxes mines blocks and asserts the number of
// transactions are found in the first block. It also asserts all active nodes
// have synced to the chain.
//
// NOTE: this differs from miner's `MineBlocks` as it requires the nodes to be
// synced.
func (h *HarnessTest) MineBlocksAndAssertNumTxes(num uint32,
numTxs int) []*wire.MsgBlock {
// Update the harness's current height.
defer h.updateCurrentHeight()
// If we expect transactions to be included in the blocks we'll mine,
// we wait here until they are seen in the miner's mempool.
txids := h.AssertNumTxsInMempool(numTxs)
// Mine blocks.
blocks := h.miner.MineBlocks(num)
// Assert that all the transactions were included in the first block.
for _, txid := range txids {
h.miner.AssertTxInBlock(blocks[0], txid)
}
// Make sure the mempool has been updated.
h.miner.AssertTxnsNotInMempool(txids)
// Finally, make sure all the active nodes are synced.
bestBlock := blocks[len(blocks)-1]
h.AssertActiveNodesSyncedTo(bestBlock.BlockHash())
return blocks
}
// ConnectMiner connects the miner with the chain backend in the network.
func (h *HarnessTest) ConnectMiner() {
err := h.manager.chainBackend.ConnectMiner()
require.NoError(h, err, "failed to connect miner")
}
// DisconnectMiner removes the connection between the miner and the chain
// backend in the network.
func (h *HarnessTest) DisconnectMiner() {
err := h.manager.chainBackend.DisconnectMiner()
require.NoError(h, err, "failed to disconnect miner")
}
// cleanMempool mines blocks till the mempool is empty and asserts all active
// nodes have synced to the chain.
func (h *HarnessTest) cleanMempool() {
_, startHeight := h.GetBestBlock()
// Mining the blocks slow to give `lnd` more time to sync.
var bestBlock *wire.MsgBlock
err := wait.NoError(func() error {
// If mempool is empty, exit.
mem := h.miner.GetRawMempool()
if len(mem) == 0 {
_, height := h.GetBestBlock()
h.Logf("Mined %d blocks when cleanup the mempool",
height-startHeight)
return nil
}
// Otherwise mine a block.
blocks := h.miner.MineBlocksSlow(1)
bestBlock = blocks[len(blocks)-1]
// Make sure all the active nodes are synced.
h.AssertActiveNodesSyncedTo(bestBlock.BlockHash())
return fmt.Errorf("still have %d txes in mempool", len(mem))
}, wait.MinerMempoolTimeout)
require.NoError(h, err, "timeout cleaning up mempool")
}
// mineTillForceCloseResolved asserts that the number of pending close channels
// are zero. Each time it checks, an empty block is mined, followed by a
// mempool check to see if there are any sweeping txns. If found, these txns
// are then mined to clean up the mempool.
func (h *HarnessTest) mineTillForceCloseResolved(hn *node.HarnessNode) {
_, startHeight := h.GetBestBlock()
err := wait.NoError(func() error {
resp := hn.RPC.PendingChannels()
total := len(resp.PendingForceClosingChannels)
if total != 0 {
// Mine an empty block first.
h.MineEmptyBlocks(1)
// If there are new sweeping txns, mine a block to
// confirm it.
mem := h.GetRawMempool()
if len(mem) != 0 {
h.MineBlocksAndAssertNumTxes(1, len(mem))
}
return fmt.Errorf("expected num of pending force " +
"close channel to be zero")
}
_, height := h.GetBestBlock()
h.Logf("Mined %d blocks while waiting for force closed "+
"channel to be resolved", height-startHeight)
return nil
}, DefaultTimeout)
require.NoErrorf(h, err, "%s: assert force close resolved timeout",
hn.Name())
}
// AssertTxInMempool asserts a given transaction can be found in the mempool.
func (h *HarnessTest) AssertTxInMempool(txid chainhash.Hash) *wire.MsgTx {
return h.miner.AssertTxInMempool(txid)
}
// AssertTxNotInMempool asserts a given transaction cannot be found in the
// mempool. It assumes the mempool is not empty.
//
// NOTE: this should be used after `AssertTxInMempool` to ensure the tx has
// entered the mempool before. Otherwise it might give false positive and the
// tx may enter the mempool after the check.
func (h *HarnessTest) AssertTxNotInMempool(txid chainhash.Hash) {
h.miner.AssertTxNotInMempool(txid)
}
// AssertNumTxsInMempool polls until finding the desired number of transactions
// in the provided miner's mempool. It will assert if this number is not met
// after the given timeout.
func (h *HarnessTest) AssertNumTxsInMempool(n int) []chainhash.Hash {
return h.miner.AssertNumTxsInMempool(n)
}
// AssertOutpointInMempool asserts a given outpoint can be found in the mempool.
func (h *HarnessTest) AssertOutpointInMempool(op wire.OutPoint) *wire.MsgTx {
return h.miner.AssertOutpointInMempool(op)
}
// AssertTxInBlock asserts that a given txid can be found in the passed block.
func (h *HarnessTest) AssertTxInBlock(block *wire.MsgBlock,
txid chainhash.Hash) {
h.miner.AssertTxInBlock(block, txid)
}
// GetNumTxsFromMempool polls until finding the desired number of transactions
// in the miner's mempool and returns the full transactions to the caller.
func (h *HarnessTest) GetNumTxsFromMempool(n int) []*wire.MsgTx {
return h.miner.GetNumTxsFromMempool(n)
}
// GetBestBlock makes a RPC request to miner and asserts.
func (h *HarnessTest) GetBestBlock() (*chainhash.Hash, int32) {
return h.miner.GetBestBlock()
}
// MineBlockWithTx mines a single block to include the specifies tx only.
func (h *HarnessTest) MineBlockWithTx(tx *wire.MsgTx) *wire.MsgBlock {
// Update the harness's current height.
defer h.updateCurrentHeight()
block := h.miner.MineBlockWithTx(tx)
// Finally, make sure all the active nodes are synced.
h.AssertActiveNodesSyncedTo(block.BlockHash())
return block
}
// ConnectToMiner connects the miner to a temp miner.
func (h *HarnessTest) ConnectToMiner(tempMiner *miner.HarnessMiner) {
h.miner.ConnectMiner(tempMiner)
}
// DisconnectFromMiner disconnects the miner from the temp miner.
func (h *HarnessTest) DisconnectFromMiner(tempMiner *miner.HarnessMiner) {
h.miner.DisconnectMiner(tempMiner)
}
// GetRawMempool makes a RPC call to the miner's GetRawMempool and
// asserts.
func (h *HarnessTest) GetRawMempool() []chainhash.Hash {
return h.miner.GetRawMempool()
}
// GetRawTransaction makes a RPC call to the miner's GetRawTransaction and
// asserts.
func (h *HarnessTest) GetRawTransaction(txid chainhash.Hash) *btcutil.Tx {
return h.miner.GetRawTransaction(txid)
}
// NewMinerAddress creates a new address for the miner and asserts.
func (h *HarnessTest) NewMinerAddress() btcutil.Address {
return h.miner.NewMinerAddress()
}
// SpawnTempMiner creates a temp miner and syncs it with the current miner.
// Once miners are synced, the temp miner is disconnected from the original
// miner and returned.
func (h *HarnessTest) SpawnTempMiner() *miner.HarnessMiner {
return h.miner.SpawnTempMiner()
}
// CreateTransaction uses the miner to create a transaction using the given
// outputs using the specified fee rate and returns the transaction.
func (h *HarnessTest) CreateTransaction(outputs []*wire.TxOut,
feeRate btcutil.Amount) *wire.MsgTx {
return h.miner.CreateTransaction(outputs, feeRate)
}
// SendOutputsWithoutChange uses the miner to send the given outputs using the
// specified fee rate and returns the txid.
func (h *HarnessTest) SendOutputsWithoutChange(outputs []*wire.TxOut,
feeRate btcutil.Amount) *chainhash.Hash {
return h.miner.SendOutputsWithoutChange(outputs, feeRate)
}
// AssertMinerBlockHeightDelta ensures that tempMiner is 'delta' blocks ahead
// of miner.
func (h *HarnessTest) AssertMinerBlockHeightDelta(
tempMiner *miner.HarnessMiner, delta int32) {
h.miner.AssertMinerBlockHeightDelta(tempMiner, delta)
}
// SendRawTransaction submits the encoded transaction to the server which will
// then relay it to the network.
func (h *HarnessTest) SendRawTransaction(tx *wire.MsgTx,
allowHighFees bool) (chainhash.Hash, error) {
txid, err := h.miner.Client.SendRawTransaction(tx, allowHighFees)
require.NoError(h, err)
return *txid, nil
}
// CurrentHeight returns the current block height.
func (h *HarnessTest) CurrentHeight() uint32 {
return h.currentHeight
}
// updateCurrentHeight set the harness's current height to the best known
// height.
func (h *HarnessTest) updateCurrentHeight() {
_, height := h.GetBestBlock()
h.currentHeight = uint32(height)
}
package lntest
import (
"context"
"fmt"
"sync"
"sync/atomic"
"testing"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntest/miner"
"github.com/lightningnetwork/lnd/lntest/node"
"github.com/lightningnetwork/lnd/lntest/wait"
)
// nodeManager is responsible for handling the start and stop of a given node.
// It also keeps track of the running nodes.
type nodeManager struct {
sync.Mutex
// chainBackend houses the information necessary to use a node as LND
// chain backend, such as rpc configuration, P2P information etc.
chainBackend node.BackendConfig
// currentTestCase holds the name for the currently run test case.
currentTestCase string
// lndBinary is the full path to the lnd binary that was specifically
// compiled with all required itest flags.
lndBinary string
// dbBackend sets the database backend to use.
dbBackend node.DatabaseBackend
// nativeSQL sets the database backend to use native SQL when
// applicable.
nativeSQL bool
// activeNodes is a map of all running nodes, format:
// {pubkey: *HarnessNode}.
activeNodes map[uint32]*node.HarnessNode
// nodeCounter is a monotonically increasing counter that's used as the
// node's unique ID.
nodeCounter atomic.Uint32
// feeServiceURL is the url of the fee service.
feeServiceURL string
}
// newNodeManager creates a new node manager instance.
func newNodeManager(lndBinary string, dbBackend node.DatabaseBackend,
nativeSQL bool) *nodeManager {
return &nodeManager{
lndBinary: lndBinary,
dbBackend: dbBackend,
nativeSQL: nativeSQL,
activeNodes: make(map[uint32]*node.HarnessNode),
}
}
// nextNodeID generates a unique sequence to be used as the node's ID.
func (nm *nodeManager) nextNodeID() uint32 {
nodeID := nm.nodeCounter.Add(1)
return nodeID
}
// newNode initializes a new HarnessNode, supporting the ability to initialize
// a wallet with or without a seed. If useSeed is false, the returned harness
// node can be used immediately. Otherwise, the node will require an additional
// initialization phase where the wallet is either created or restored.
func (nm *nodeManager) newNode(t *testing.T, name string, extraArgs []string,
password []byte, noAuth bool) (*node.HarnessNode, error) {
cfg := &node.BaseNodeConfig{
Name: name,
LogFilenamePrefix: nm.currentTestCase,
Password: password,
BackendCfg: nm.chainBackend,
ExtraArgs: extraArgs,
FeeURL: nm.feeServiceURL,
DBBackend: nm.dbBackend,
NativeSQL: nm.nativeSQL,
NodeID: nm.nextNodeID(),
LndBinary: nm.lndBinary,
NetParams: miner.HarnessNetParams,
SkipUnlock: noAuth,
}
node, err := node.NewHarnessNode(t, cfg)
if err != nil {
return nil, err
}
// Put node in activeNodes to ensure Shutdown is called even if start
// returns an error.
nm.registerNode(node)
return node, nil
}
// RegisterNode records a new HarnessNode in the NetworkHarnesses map of known
// nodes. This method should only be called with nodes that have successfully
// retrieved their public keys via FetchNodeInfo.
func (nm *nodeManager) registerNode(node *node.HarnessNode) {
nm.Lock()
nm.activeNodes[node.Cfg.NodeID] = node
nm.Unlock()
}
// ShutdownNode stops an active lnd process and returns when the process has
// exited and any temporary directories have been cleaned up.
func (nm *nodeManager) shutdownNode(node *node.HarnessNode) error {
// Remove the node from the active nodes map even if the shutdown
// fails as the shutdown cannot be retried in that case.
delete(nm.activeNodes, node.Cfg.NodeID)
if err := node.Shutdown(); err != nil {
return err
}
return nil
}
// restartNode attempts to restart a lightning node by shutting it down
// cleanly, then restarting the process. This function is fully blocking. Upon
// restart, the RPC connection to the node will be re-attempted, continuing iff
// the connection attempt is successful. If the callback parameter is non-nil,
// then the function will be executed after the node shuts down, but *before*
// the process has been started up again.
func (nm *nodeManager) restartNode(ctxt context.Context,
hn *node.HarnessNode, callback func() error) error {
// Stop the node.
if err := hn.Stop(); err != nil {
return fmt.Errorf("restart node got error: %w", err)
}
if callback != nil {
if err := callback(); err != nil {
return err
}
}
// Start the node without unlocking the wallet.
if hn.Cfg.SkipUnlock {
return hn.StartWithNoAuth(ctxt)
}
return hn.Start(ctxt)
}
// unlockNode unlocks the node's wallet if the password is configured.
// Additionally, each time the node is unlocked, the caller can pass a set of
// SCBs to pass in via the Unlock method allowing them to restore channels
// during restart.
func (nm *nodeManager) unlockNode(hn *node.HarnessNode,
chanBackups ...*lnrpc.ChanBackupSnapshot) error {
// If the node doesn't have a password set, then we can exit here as we
// don't need to unlock it.
if len(hn.Cfg.Password) == 0 {
return nil
}
// Otherwise, we'll unlock the wallet, then complete the final steps
// for the node initialization process.
unlockReq := &lnrpc.UnlockWalletRequest{
WalletPassword: hn.Cfg.Password,
}
if len(chanBackups) != 0 {
unlockReq.ChannelBackups = chanBackups[0]
unlockReq.RecoveryWindow = 100
}
err := wait.NoError(func() error {
return hn.Unlock(unlockReq)
}, DefaultTimeout)
if err != nil {
return fmt.Errorf("%s: failed to unlock: %w", hn.Name(), err)
}
return nil
}
// initWalletAndNode will unlock the node's wallet and finish setting up the
// node so it's ready to take RPC requests.
func (nm *nodeManager) initWalletAndNode(hn *node.HarnessNode,
req *lnrpc.InitWalletRequest) ([]byte, error) {
// Pass the init request via rpc to finish unlocking the node.
resp := hn.RPC.InitWallet(req)
// Now that the wallet is unlocked, before creating an authed
// connection we will close the old unauthed connection.
if err := hn.CloseConn(); err != nil {
return nil, fmt.Errorf("close unauthed conn failed")
}
// Init the node, which will create the authed grpc conn and all its
// rpc clients.
err := hn.InitNode(resp.AdminMacaroon)
// In stateless initialization mode we get a macaroon back that we have
// to return to the test, otherwise gRPC calls won't be possible since
// there are no macaroon files created in that mode.
// In stateful init the admin macaroon will just be nil.
return resp.AdminMacaroon, err
}
package lntest
import (
"context"
"os"
"testing"
"github.com/btcsuite/btcd/integration/rpctest"
"github.com/lightningnetwork/lnd/lntest/miner"
"github.com/lightningnetwork/lnd/lntest/node"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require"
)
// SetupHarness creates a new HarnessTest with a series of setups such that the
// instance is ready for usage. The setups are,
// 1. create the directories to hold lnd files.
// 2. start a btcd miner.
// 3. start a chain backend(btcd, bitcoind, or neutrino).
// 4. connect the miner and the chain backend.
// 5. start the HarnessTest.
func SetupHarness(t *testing.T, binaryPath, dbBackendName string,
nativeSQL bool, feeService WebFeeService) *HarnessTest {
t.Log("Setting up HarnessTest...")
// Parse testing flags that influence our test execution.
logDir := node.GetLogDir()
require.NoError(t, os.MkdirAll(logDir, 0700), "create log dir failed")
// Parse database backend
dbBackend := prepareDBBackend(t, dbBackendName)
// Create a new HarnessTest.
ht := NewHarnessTest(t, binaryPath, feeService, dbBackend, nativeSQL)
// Init the miner.
t.Log("Prepare the miner and mine blocks to activate segwit...")
miner := prepareMiner(ht.runCtx, ht.T)
// Start a chain backend.
chainBackend, cleanUp := prepareChainBackend(t, miner.P2PAddress())
ht.stopChainBackend = cleanUp
// Connect our chainBackend to our miner.
t.Logf("Connecting the miner at %v with the chain backend...",
miner.P2PAddress())
// Give the chain backend some time to fully start up, re-trying if any
// errors in connecting to the miner are encountered.
err := wait.NoError(func() error {
return chainBackend.ConnectMiner()
}, DefaultTimeout)
require.NoError(t, err, "connect miner")
// Start the HarnessTest with the chainBackend and miner.
ht.Start(chainBackend, miner)
return ht
}
// prepareMiner creates an instance of the btcd's rpctest.Harness that will act
// as the miner for all tests. This will be used to fund the wallets of the
// nodes within the test network and to drive blockchain related events within
// the network. Revert the default setting of accepting non-standard
// transactions on simnet to reject them. Transactions on the lightning network
// should always be standard to get better guarantees of getting included in to
// blocks.
func prepareMiner(ctxt context.Context, t *testing.T) *miner.HarnessMiner {
m := miner.NewMiner(ctxt, t)
// Before we start anything, we want to overwrite some of the
// connection settings to make the tests more robust. We might need to
// restart the miner while there are already blocks present, which will
// take a bit longer than the 1 second the default settings amount to.
// Doubling both values will give us retries up to 4 seconds.
m.MaxConnRetries = rpctest.DefaultMaxConnectionRetries * 2
m.ConnectionRetryTimeout = rpctest.DefaultConnectionRetryTimeout * 2
// Set up miner and connect chain backend to it.
require.NoError(t, m.SetUp(true, 50))
require.NoError(t, m.Client.NotifyNewTransactions(false))
// Next mine enough blocks in order for segwit and the CSV package
// soft-fork to activate on SimNet.
numBlocks := miner.HarnessNetParams.MinerConfirmationWindow * 2
m.GenerateBlocks(numBlocks)
return m
}
// prepareChainBackend creates a new chain backend.
func prepareChainBackend(t *testing.T,
minerAddr string) (node.BackendConfig, func()) {
chainBackend, cleanUp, err := NewBackend(
minerAddr, miner.HarnessNetParams,
)
require.NoError(t, err, "new backend")
return chainBackend, func() {
require.NoError(t, cleanUp(), "cleanup")
}
}
// prepareDBBackend parses a DatabaseBackend based on the name given.
func prepareDBBackend(t *testing.T,
dbBackendName string) node.DatabaseBackend {
var dbBackend node.DatabaseBackend
switch dbBackendName {
case "bbolt":
dbBackend = node.BackendBbolt
case "etcd":
dbBackend = node.BackendEtcd
case "postgres":
dbBackend = node.BackendPostgres
case "sqlite":
dbBackend = node.BackendSqlite
default:
require.Fail(t, "unknown db backend")
}
return dbBackend
}
package miner
import (
"bytes"
"context"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/integration/rpctest"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lntest/node"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require"
)
const (
// minerLogFilename is the default log filename for the miner node.
minerLogFilename = "output_btcd_miner.log"
// minerLogDir is the default log dir for the miner node.
minerLogDir = ".minerlogs"
// slowMineDelay defines a wait period between mining new blocks.
slowMineDelay = 100 * time.Millisecond
)
var (
HarnessNetParams = &chaincfg.RegressionNetParams
// temp is used to signal we want to establish a temporary connection
// using the btcd Node API.
//
// NOTE: Cannot be const, since the node API expects a reference.
Temp = "temp"
)
type HarnessMiner struct {
*testing.T
*rpctest.Harness
// runCtx is a context with cancel method. It's used to signal when the
// node needs to quit, and used as the parent context when spawning
// children contexts for RPC requests.
runCtx context.Context //nolint:containedctx
cancel context.CancelFunc
// logPath is the directory path of the miner's logs.
logPath string
// logFilename is the saved log filename of the miner node.
logFilename string
}
// NewMiner creates a new miner using btcd backend with the default log file
// dir and name.
func NewMiner(ctxt context.Context, t *testing.T) *HarnessMiner {
t.Helper()
return newMiner(ctxt, t, minerLogDir, minerLogFilename)
}
// NewTempMiner creates a new miner using btcd backend with the specified log
// file dir and name.
func NewTempMiner(ctxt context.Context, t *testing.T,
tempDir, tempLogFilename string) *HarnessMiner {
t.Helper()
return newMiner(ctxt, t, tempDir, tempLogFilename)
}
// newMiner creates a new miner using btcd's rpctest.
func newMiner(ctxb context.Context, t *testing.T, minerDirName,
logFilename string) *HarnessMiner {
t.Helper()
handler := &rpcclient.NotificationHandlers{}
btcdBinary := node.GetBtcdBinary()
baseLogPath := fmt.Sprintf("%s/%s", node.GetLogDir(), minerDirName)
args := []string{
"--rejectnonstd",
"--txindex",
"--nowinservice",
"--nobanning",
"--debuglevel=debug",
"--logdir=" + baseLogPath,
"--trickleinterval=100ms",
// Don't disconnect if a reply takes too long.
"--nostalldetect",
}
miner, err := rpctest.New(HarnessNetParams, handler, args, btcdBinary)
require.NoError(t, err, "unable to create mining node")
ctxt, cancel := context.WithCancel(ctxb)
return &HarnessMiner{
T: t,
Harness: miner,
runCtx: ctxt,
cancel: cancel,
logPath: baseLogPath,
logFilename: logFilename,
}
}
// saveLogs copies the node logs and save it to the file specified by
// h.logFilename.
func (h *HarnessMiner) saveLogs() {
// After shutting down the miner, we'll make a copy of the log files
// before deleting the temporary log dir.
path := fmt.Sprintf("%s/%s", h.logPath, HarnessNetParams.Name)
files, err := os.ReadDir(path)
require.NoError(h, err, "unable to read log directory")
for _, file := range files {
newFilename := strings.Replace(
file.Name(), "btcd.log", h.logFilename, 1,
)
copyPath := fmt.Sprintf("%s/../%s", h.logPath, newFilename)
logFile := fmt.Sprintf("%s/%s", path, file.Name())
err := node.CopyFile(filepath.Clean(copyPath), logFile)
require.NoError(h, err, "unable to copy file")
}
err = os.RemoveAll(h.logPath)
require.NoErrorf(h, err, "cannot remove dir %s", h.logPath)
}
// Stop shuts down the miner and saves its logs.
func (h *HarnessMiner) Stop() {
h.cancel()
require.NoError(h, h.TearDown(), "tear down miner got error")
h.saveLogs()
}
// GetBestBlock makes a RPC request to miner and asserts.
func (h *HarnessMiner) GetBestBlock() (*chainhash.Hash, int32) {
blockHash, height, err := h.Client.GetBestBlock()
require.NoError(h, err, "failed to GetBestBlock")
return blockHash, height
}
// GetRawMempool makes a RPC call to the miner's GetRawMempool and
// asserts.
func (h *HarnessMiner) GetRawMempool() []chainhash.Hash {
mempool, err := h.Client.GetRawMempool()
require.NoError(h, err, "unable to get mempool")
txns := make([]chainhash.Hash, 0, len(mempool))
for _, txid := range mempool {
txns = append(txns, *txid)
}
return txns
}
// GenerateBlocks mine 'num' of blocks and returns them.
func (h *HarnessMiner) GenerateBlocks(num uint32) []*chainhash.Hash {
blockHashes, err := h.Client.Generate(num)
require.NoError(h, err, "unable to generate blocks")
require.Len(h, blockHashes, int(num), "wrong num of blocks generated")
return blockHashes
}
// GetBlock gets a block using its block hash.
func (h *HarnessMiner) GetBlock(blockHash *chainhash.Hash) *wire.MsgBlock {
block, err := h.Client.GetBlock(blockHash)
require.NoError(h, err, "unable to get block")
return block
}
// MineBlocks mine 'num' of blocks and check that blocks are present in
// node blockchain.
func (h *HarnessMiner) MineBlocks(num uint32) []*wire.MsgBlock {
blocks := make([]*wire.MsgBlock, num)
blockHashes := h.GenerateBlocks(num)
for i, blockHash := range blockHashes {
block := h.GetBlock(blockHash)
blocks[i] = block
}
return blocks
}
// AssertNumTxsInMempool polls until finding the desired number of transactions
// in the provided miner's mempool. It will assert if this number is not met
// after the given timeout.
func (h *HarnessMiner) AssertNumTxsInMempool(n int) []chainhash.Hash {
var (
mem []chainhash.Hash
err error
)
err = wait.NoError(func() error {
// We require the RPC call to be succeeded and won't wait for
// it as it's an unexpected behavior.
mem = h.GetRawMempool()
if len(mem) == n {
return nil
}
return fmt.Errorf("want %v, got %v in mempool: %v",
n, len(mem), mem)
}, wait.MinerMempoolTimeout)
require.NoError(h, err, "assert tx in mempool timeout")
return mem
}
// AssertTxInBlock asserts that a given txid can be found in the passed block.
func (h *HarnessMiner) AssertTxInBlock(block *wire.MsgBlock,
txid chainhash.Hash) {
blockTxes := make([]chainhash.Hash, 0)
for _, tx := range block.Transactions {
sha := tx.TxHash()
blockTxes = append(blockTxes, sha)
if bytes.Equal(txid[:], sha[:]) {
return
}
}
require.Failf(h, "tx was not included in block", "tx:%v, block has:%v",
txid, blockTxes)
}
// MineBlocksAndAssertNumTxes mine 'num' of blocks and check that blocks are
// present in node blockchain. numTxs should be set to the number of
// transactions (excluding the coinbase) we expect to be included in the first
// mined block.
func (h *HarnessMiner) MineBlocksAndAssertNumTxes(num uint32,
numTxs int) []*wire.MsgBlock {
// If we expect transactions to be included in the blocks we'll mine,
// we wait here until they are seen in the miner's mempool.
txids := h.AssertNumTxsInMempool(numTxs)
// Mine blocks.
blocks := h.MineBlocks(num)
// Finally, assert that all the transactions were included in the first
// block.
for _, txid := range txids {
h.AssertTxInBlock(blocks[0], txid)
}
return blocks
}
// GetRawTransaction makes a RPC call to the miner's GetRawTransaction and
// asserts.
func (h *HarnessMiner) GetRawTransaction(txid chainhash.Hash) *btcutil.Tx {
tx, err := h.Client.GetRawTransaction(&txid)
require.NoErrorf(h, err, "failed to get raw tx: %v", txid)
return tx
}
// GetRawTransactionVerbose makes a RPC call to the miner's
// GetRawTransactionVerbose and asserts.
func (h *HarnessMiner) GetRawTransactionVerbose(
txid chainhash.Hash) *btcjson.TxRawResult {
tx, err := h.Client.GetRawTransactionVerbose(&txid)
require.NoErrorf(h, err, "failed to get raw tx verbose: %v", txid)
return tx
}
// AssertTxInMempool asserts a given transaction can be found in the mempool.
func (h *HarnessMiner) AssertTxInMempool(txid chainhash.Hash) *wire.MsgTx {
err := wait.NoError(func() error {
// We require the RPC call to be succeeded and won't wait for
// it as it's an unexpected behavior.
mempool := h.GetRawMempool()
if len(mempool) == 0 {
return fmt.Errorf("empty mempool")
}
result := fn.Find(mempool, fn.Eq(txid))
if result.IsNone() {
return fmt.Errorf("txid %v not found in "+
"mempool: %v", txid, mempool)
}
return nil
}, wait.MinerMempoolTimeout)
require.NoError(h, err, "timeout checking mempool")
return h.GetRawTransaction(txid).MsgTx()
}
// AssertTxnsNotInMempool asserts the given txns are not found in the mempool.
// It assumes the mempool is not empty.
func (h *HarnessMiner) AssertTxnsNotInMempool(txids []chainhash.Hash) {
err := wait.NoError(func() error {
// We require the RPC call to be succeeded and won't wait for
// it as it's an unexpected behavior.
mempool := h.GetRawMempool()
// Turn the mempool into a txn set for faster lookups.
mempoolTxns := fn.NewSet(mempool...)
// Check if any of the txids are in the mempool.
for _, txid := range txids {
// Skip if the tx is not in the mempool.
if !mempoolTxns.Contains(txid) {
continue
}
return fmt.Errorf("expect txid %v to be NOT found in "+
"mempool", txid)
}
return nil
}, wait.MinerMempoolTimeout)
require.NoError(h, err, "timeout checking txns not in mempool")
}
// AssertTxNotInMempool asserts a given transaction cannot be found in the
// mempool. It assumes the mempool is not empty.
//
// NOTE: this should be used after `AssertTxInMempool` to ensure the tx has
// entered the mempool before. Otherwise it might give false positive and the
// tx may enter the mempool after the check.
func (h *HarnessMiner) AssertTxNotInMempool(txid chainhash.Hash) {
err := wait.NoError(func() error {
// We require the RPC call to be succeeded and won't wait for
// it as it's an unexpected behavior.
mempool := h.GetRawMempool()
for _, memTx := range mempool {
// Check the values are equal.
if txid == memTx {
return fmt.Errorf("expect txid %v to be NOT "+
"found in mempool", txid)
}
}
return nil
}, wait.MinerMempoolTimeout)
require.NoError(h, err, "timeout checking tx not in mempool")
}
// SendOutputsWithoutChange uses the miner to send the given outputs using the
// specified fee rate and returns the txid.
func (h *HarnessMiner) SendOutputsWithoutChange(outputs []*wire.TxOut,
feeRate btcutil.Amount) *chainhash.Hash {
txid, err := h.Harness.SendOutputsWithoutChange(
outputs, feeRate,
)
require.NoErrorf(h, err, "failed to send output")
return txid
}
// CreateTransaction uses the miner to create a transaction using the given
// outputs using the specified fee rate and returns the transaction.
func (h *HarnessMiner) CreateTransaction(outputs []*wire.TxOut,
feeRate btcutil.Amount) *wire.MsgTx {
tx, err := h.Harness.CreateTransaction(outputs, feeRate, false)
require.NoErrorf(h, err, "failed to create transaction")
return tx
}
// SendOutput creates, signs, and finally broadcasts a transaction spending
// the harness' available mature coinbase outputs to create the new output.
func (h *HarnessMiner) SendOutput(newOutput *wire.TxOut,
feeRate btcutil.Amount) *chainhash.Hash {
hash, err := h.Harness.SendOutputs([]*wire.TxOut{newOutput}, feeRate)
require.NoErrorf(h, err, "failed to send outputs")
return hash
}
// MineBlocksSlow mines 'num' of blocks. Between each mined block an artificial
// delay is introduced to give all network participants time to catch up.
func (h *HarnessMiner) MineBlocksSlow(num uint32) []*wire.MsgBlock {
blocks := make([]*wire.MsgBlock, num)
blockHashes := make([]*chainhash.Hash, 0, num)
for i := uint32(0); i < num; i++ {
generatedHashes := h.GenerateBlocks(1)
blockHashes = append(blockHashes, generatedHashes...)
time.Sleep(slowMineDelay)
}
for i, blockHash := range blockHashes {
block, err := h.Client.GetBlock(blockHash)
require.NoError(h, err, "get blocks")
blocks[i] = block
}
return blocks
}
// AssertOutpointInMempool asserts a given outpoint can be found in the mempool.
func (h *HarnessMiner) AssertOutpointInMempool(op wire.OutPoint) *wire.MsgTx {
var msgTx *wire.MsgTx
err := wait.NoError(func() error {
// We require the RPC call to be succeeded and won't wait for
// it as it's an unexpected behavior.
mempool := h.GetRawMempool()
if len(mempool) == 0 {
return fmt.Errorf("empty mempool")
}
for _, txid := range mempool {
// We don't use `ht.GetRawTransaction` which
// asserts a txid must be found. While iterating here,
// the actual mempool state might have been changed,
// causing a given txid being removed and cannot be
// found. For instance, the aggregation logic used in
// sweeping HTLC outputs will update the mempool by
// replacing the HTLC spending txes with a single one.
tx, err := h.Client.GetRawTransaction(&txid)
if err != nil {
return err
}
msgTx = tx.MsgTx()
for _, txIn := range msgTx.TxIn {
if txIn.PreviousOutPoint == op {
return nil
}
}
}
return fmt.Errorf("outpoint %v not found in mempool", op)
}, wait.MinerMempoolTimeout)
require.NoError(h, err, "timeout checking mempool")
return msgTx
}
// GetNumTxsFromMempool polls until finding the desired number of transactions
// in the miner's mempool and returns the full transactions to the caller.
func (h *HarnessMiner) GetNumTxsFromMempool(n int) []*wire.MsgTx {
txids := h.AssertNumTxsInMempool(n)
var txes []*wire.MsgTx
for _, txid := range txids {
tx := h.GetRawTransaction(txid)
txes = append(txes, tx.MsgTx())
}
return txes
}
// NewMinerAddress creates a new address for the miner and asserts.
func (h *HarnessMiner) NewMinerAddress() btcutil.Address {
addr, err := h.NewAddress()
require.NoError(h, err, "failed to create new miner address")
return addr
}
// MineBlocksWithTxes mines a single block to include the specifies
// transactions only.
func (h *HarnessMiner) MineBlockWithTxes(txes []*btcutil.Tx) *wire.MsgBlock {
var emptyTime time.Time
// Generate a block.
b, err := h.GenerateAndSubmitBlock(txes, -1, emptyTime)
require.NoError(h, err, "unable to mine block")
block, err := h.Client.GetBlock(b.Hash())
require.NoError(h, err, "unable to get block")
// Make sure the mempool has been updated.
for _, tx := range txes {
h.AssertTxNotInMempool(*tx.Hash())
}
return block
}
// MineBlocksWithTx mines a single block to include the specifies tx only.
func (h *HarnessMiner) MineBlockWithTx(tx *wire.MsgTx) *wire.MsgBlock {
var emptyTime time.Time
txes := []*btcutil.Tx{btcutil.NewTx(tx)}
// Generate a block.
b, err := h.GenerateAndSubmitBlock(txes, -1, emptyTime)
require.NoError(h, err, "unable to mine block")
block, err := h.Client.GetBlock(b.Hash())
require.NoError(h, err, "unable to get block")
// Make sure the mempool has been updated.
h.AssertTxNotInMempool(tx.TxHash())
return block
}
// MineEmptyBlocks mines a given number of empty blocks.
func (h *HarnessMiner) MineEmptyBlocks(num int) []*wire.MsgBlock {
var emptyTime time.Time
blocks := make([]*wire.MsgBlock, num)
for i := 0; i < num; i++ {
// Generate an empty block.
b, err := h.GenerateAndSubmitBlock(nil, -1, emptyTime)
require.NoError(h, err, "unable to mine empty block")
block := h.GetBlock(b.Hash())
blocks[i] = block
}
return blocks
}
// SpawnTempMiner creates a temp miner and syncs it with the current miner.
// Once miners are synced, the temp miner is disconnected from the original
// miner and returned.
func (h *HarnessMiner) SpawnTempMiner() *HarnessMiner {
require := require.New(h.T)
// Setup a temp miner.
tempLogDir := ".tempminerlogs"
logFilename := "output-temp_miner.log"
tempMiner := NewTempMiner(h.runCtx, h.T, tempLogDir, logFilename)
// Make sure to clean the miner when the test ends.
h.T.Cleanup(tempMiner.Stop)
// Setup the miner.
require.NoError(tempMiner.SetUp(false, 0), "unable to setup miner")
// Connect the temp miner to the original miner.
err := h.Client.Node(btcjson.NConnect, tempMiner.P2PAddress(), &Temp)
require.NoError(err, "unable to connect node")
// Sync the blocks.
nodeSlice := []*rpctest.Harness{h.Harness, tempMiner.Harness}
err = rpctest.JoinNodes(nodeSlice, rpctest.Blocks)
require.NoError(err, "unable to join node on blocks")
// The two miners should be on the same block height.
h.AssertMinerBlockHeightDelta(tempMiner, 0)
// Once synced, we now disconnect the temp miner so it'll be
// independent from the original miner.
err = h.Client.Node(btcjson.NDisconnect, tempMiner.P2PAddress(), &Temp)
require.NoError(err, "unable to disconnect miners")
return tempMiner
}
// ConnectMiner connects the miner to a temp miner.
func (h *HarnessMiner) ConnectMiner(tempMiner *HarnessMiner) {
require := require.New(h.T)
// Connect the current miner to the temporary miner.
err := h.Client.Node(btcjson.NConnect, tempMiner.P2PAddress(), &Temp)
require.NoError(err, "unable to connect temp miner")
nodes := []*rpctest.Harness{tempMiner.Harness, h.Harness}
err = rpctest.JoinNodes(nodes, rpctest.Blocks)
require.NoError(err, "unable to join node on blocks")
}
// DisconnectMiner disconnects the miner from the temp miner.
func (h *HarnessMiner) DisconnectMiner(tempMiner *HarnessMiner) {
err := h.Client.Node(btcjson.NDisconnect, tempMiner.P2PAddress(), &Temp)
require.NoError(h.T, err, "unable to disconnect temp miner")
}
// AssertMinerBlockHeightDelta ensures that tempMiner is 'delta' blocks ahead
// of miner.
func (h *HarnessMiner) AssertMinerBlockHeightDelta(tempMiner *HarnessMiner,
delta int32) {
// Ensure the chain lengths are what we expect.
err := wait.NoError(func() error {
_, tempMinerHeight, err := tempMiner.Client.GetBestBlock()
if err != nil {
return fmt.Errorf("unable to get current "+
"blockheight %v", err)
}
_, minerHeight, err := h.Client.GetBestBlock()
if err != nil {
return fmt.Errorf("unable to get current "+
"blockheight %v", err)
}
if tempMinerHeight != minerHeight+delta {
return fmt.Errorf("expected new miner(%d) to be %d "+
"blocks ahead of original miner(%d)",
tempMinerHeight, delta, minerHeight)
}
return nil
}, wait.DefaultTimeout)
require.NoError(h.T, err, "failed to assert block height delta")
}
package mock
import (
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
)
// ChainIO is a mock implementation of the BlockChainIO interface.
type ChainIO struct {
BestHeight int32
}
// GetBestBlock currently returns dummy values.
func (c *ChainIO) GetBestBlock() (*chainhash.Hash, int32, error) {
return chaincfg.TestNet3Params.GenesisHash, c.BestHeight, nil
}
// GetUtxo currently returns dummy values.
func (c *ChainIO) GetUtxo(op *wire.OutPoint, _ []byte,
heightHint uint32, _ <-chan struct{}) (*wire.TxOut, error) {
return nil, nil
}
// GetBlockHash currently returns dummy values.
func (c *ChainIO) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) {
return nil, nil
}
// GetBlock currently returns dummy values.
func (c *ChainIO) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) {
return nil, nil
}
// GetBlockHeader currently returns dummy values.
func (c *ChainIO) GetBlockHeader(blockHash *chainhash.Hash) (*wire.BlockHeader,
error) {
return nil, nil
}
package mock
import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs"
)
// ChainNotifier is a mock implementation of the ChainNotifier interface.
type ChainNotifier struct {
SpendChan chan *chainntnfs.SpendDetail
EpochChan chan *chainntnfs.BlockEpoch
ConfChan chan *chainntnfs.TxConfirmation
}
// RegisterConfirmationsNtfn returns a ConfirmationEvent that contains a channel
// that the tx confirmation will go over.
func (c *ChainNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
pkScript []byte, numConfs, heightHint uint32,
opts ...chainntnfs.NotifierOption) (*chainntnfs.ConfirmationEvent, error) {
return &chainntnfs.ConfirmationEvent{
Confirmed: c.ConfChan,
Cancel: func() {},
}, nil
}
// RegisterSpendNtfn returns a SpendEvent that contains a channel that the spend
// details will go over.
func (c *ChainNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) {
return &chainntnfs.SpendEvent{
Spend: c.SpendChan,
Cancel: func() {},
}, nil
}
// RegisterBlockEpochNtfn returns a BlockEpochEvent that contains a channel that
// block epochs will go over.
func (c *ChainNotifier) RegisterBlockEpochNtfn(blockEpoch *chainntnfs.BlockEpoch) (
*chainntnfs.BlockEpochEvent, error) {
return &chainntnfs.BlockEpochEvent{
Epochs: c.EpochChan,
Cancel: func() {},
}, nil
}
// Start currently returns a dummy value.
func (c *ChainNotifier) Start() error {
return nil
}
// Started currently returns a dummy value.
func (c *ChainNotifier) Started() bool {
return true
}
// Stop currently returns a dummy value.
func (c *ChainNotifier) Stop() error {
return nil
}
package mock
import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/lightningnetwork/lnd/keychain"
)
// SecretKeyRing is a mock implementation of the SecretKeyRing interface.
type SecretKeyRing struct {
RootKey *btcec.PrivateKey
}
// DeriveNextKey currently returns dummy values.
func (s *SecretKeyRing) DeriveNextKey(
_ keychain.KeyFamily) (keychain.KeyDescriptor, error) {
return keychain.KeyDescriptor{
PubKey: s.RootKey.PubKey(),
}, nil
}
// DeriveKey currently returns dummy values.
func (s *SecretKeyRing) DeriveKey(
_ keychain.KeyLocator) (keychain.KeyDescriptor, error) {
return keychain.KeyDescriptor{
PubKey: s.RootKey.PubKey(),
}, nil
}
// DerivePrivKey currently returns dummy values.
func (s *SecretKeyRing) DerivePrivKey(
_ keychain.KeyDescriptor) (*btcec.PrivateKey, error) {
return s.RootKey, nil
}
// ECDH currently returns dummy values.
func (s *SecretKeyRing) ECDH(_ keychain.KeyDescriptor,
_ *btcec.PublicKey) ([32]byte, error) {
return [32]byte{}, nil
}
// SignMessage signs the passed message and ignores the KeyDescriptor.
func (s *SecretKeyRing) SignMessage(_ keychain.KeyLocator,
msg []byte, doubleHash bool) (*ecdsa.Signature, error) {
var digest []byte
if doubleHash {
digest = chainhash.DoubleHashB(msg)
} else {
digest = chainhash.HashB(msg)
}
return ecdsa.Sign(s.RootKey, digest), nil
}
// SignMessageCompact signs the passed message.
func (s *SecretKeyRing) SignMessageCompact(_ keychain.KeyLocator,
msg []byte, doubleHash bool) ([]byte, error) {
var digest []byte
if doubleHash {
digest = chainhash.DoubleHashB(msg)
} else {
digest = chainhash.HashB(msg)
}
return ecdsa.SignCompact(s.RootKey, digest, true), nil
}
// SignMessageSchnorr signs the passed message and ignores the KeyDescriptor.
func (s *SecretKeyRing) SignMessageSchnorr(_ keychain.KeyLocator,
msg []byte, doubleHash bool, taprootTweak []byte,
tag []byte) (*schnorr.Signature, error) {
var digest []byte
switch {
case len(tag) > 0:
taggedHash := chainhash.TaggedHash(tag, msg)
digest = taggedHash[:]
case doubleHash:
digest = chainhash.DoubleHashB(msg)
default:
digest = chainhash.HashB(msg)
}
privKey := s.RootKey
if len(taprootTweak) > 0 {
privKey = txscript.TweakTaprootPrivKey(*privKey, taprootTweak)
}
return schnorr.Sign(privKey, digest)
}
package mock
import (
"crypto/sha256"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
)
var (
idKeyLoc = keychain.KeyLocator{Family: keychain.KeyFamilyNodeKey}
)
// DummySignature is a dummy Signature implementation.
type DummySignature struct{}
// Serialize returns an empty byte slice.
func (d *DummySignature) Serialize() []byte {
return []byte{}
}
// Verify always returns true.
func (d *DummySignature) Verify(_ []byte, _ *btcec.PublicKey) bool {
return true
}
// DummySigner is an implementation of the Signer interface that returns
// dummy values when called.
type DummySigner struct{}
// SignOutputRaw returns a dummy signature.
func (d *DummySigner) SignOutputRaw(tx *wire.MsgTx,
signDesc *input.SignDescriptor) (input.Signature, error) {
return &DummySignature{}, nil
}
// ComputeInputScript returns nil for both values.
func (d *DummySigner) ComputeInputScript(tx *wire.MsgTx,
signDesc *input.SignDescriptor) (*input.Script, error) {
return &input.Script{}, nil
}
// MuSig2CreateSession creates a new MuSig2 signing session using the local
// key identified by the key locator. The complete list of all public keys of
// all signing parties must be provided, including the public key of the local
// signing key. If nonces of other parties are already known, they can be
// submitted as well to reduce the number of method calls necessary later on.
func (d *DummySigner) MuSig2CreateSession(input.MuSig2Version,
keychain.KeyLocator, []*btcec.PublicKey, *input.MuSig2Tweaks,
[][musig2.PubNonceSize]byte, *musig2.Nonces,
) (*input.MuSig2SessionInfo, error) {
return nil, nil
}
// MuSig2RegisterNonces registers one or more public nonces of other signing
// participants for a session identified by its ID. This method returns true
// once we have all nonces for all other signing participants.
func (d *DummySigner) MuSig2RegisterNonces(input.MuSig2SessionID,
[][musig2.PubNonceSize]byte) (bool, error) {
return false, nil
}
// MuSig2Sign creates a partial signature using the local signing key
// that was specified when the session was created. This can only be
// called when all public nonces of all participants are known and have
// been registered with the session. If this node isn't responsible for
// combining all the partial signatures, then the cleanup parameter
// should be set, indicating that the session can be removed from memory
// once the signature was produced.
func (d *DummySigner) MuSig2Sign(input.MuSig2SessionID,
[sha256.Size]byte, bool) (*musig2.PartialSignature, error) {
return nil, nil
}
// MuSig2CombineSig combines the given partial signature(s) with the
// local one, if it already exists. Once a partial signature of all
// participants is registered, the final signature will be combined and
// returned.
func (d *DummySigner) MuSig2CombineSig(input.MuSig2SessionID,
[]*musig2.PartialSignature) (*schnorr.Signature, bool, error) {
return nil, false, nil
}
// MuSig2Cleanup removes a session from memory to free up resources.
func (d *DummySigner) MuSig2Cleanup(input.MuSig2SessionID) error {
return nil
}
// SingleSigner is an implementation of the Signer interface that signs
// everything with a single private key.
type SingleSigner struct {
Privkey *btcec.PrivateKey
KeyLoc keychain.KeyLocator
*input.MusigSessionManager
}
func NewSingleSigner(privkey *btcec.PrivateKey) *SingleSigner {
signer := &SingleSigner{
Privkey: privkey,
KeyLoc: idKeyLoc,
}
keyFetcher := func(*keychain.KeyDescriptor) (*btcec.PrivateKey, error) {
return signer.Privkey, nil
}
signer.MusigSessionManager = input.NewMusigSessionManager(keyFetcher)
return signer
}
// SignOutputRaw generates a signature for the passed transaction using the
// stored private key.
func (s *SingleSigner) SignOutputRaw(tx *wire.MsgTx,
signDesc *input.SignDescriptor) (input.Signature, error) {
amt := signDesc.Output.Value
witnessScript := signDesc.WitnessScript
privKey := s.Privkey
if !privKey.PubKey().IsEqual(signDesc.KeyDesc.PubKey) {
return nil, fmt.Errorf("incorrect key passed")
}
switch {
case signDesc.SingleTweak != nil:
privKey = input.TweakPrivKey(privKey,
signDesc.SingleTweak)
case signDesc.DoubleTweak != nil:
privKey = input.DeriveRevocationPrivKey(privKey,
signDesc.DoubleTweak)
}
sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes,
signDesc.InputIndex, amt, witnessScript, signDesc.HashType,
privKey)
if err != nil {
return nil, err
}
return ecdsa.ParseDERSignature(sig[:len(sig)-1])
}
// ComputeInputScript computes an input script with the stored private key
// given a transaction and a SignDescriptor.
func (s *SingleSigner) ComputeInputScript(tx *wire.MsgTx,
signDesc *input.SignDescriptor) (*input.Script, error) {
privKey := s.Privkey
switch {
case signDesc.SingleTweak != nil:
privKey = input.TweakPrivKey(privKey,
signDesc.SingleTweak)
case signDesc.DoubleTweak != nil:
privKey = input.DeriveRevocationPrivKey(privKey,
signDesc.DoubleTweak)
}
witnessScript, err := txscript.WitnessSignature(tx, signDesc.SigHashes,
signDesc.InputIndex, signDesc.Output.Value, signDesc.Output.PkScript,
signDesc.HashType, privKey, true)
if err != nil {
return nil, err
}
return &input.Script{
Witness: witnessScript,
}, nil
}
// SignMessage takes a public key and a message and only signs the message
// with the stored private key if the public key matches the private key.
func (s *SingleSigner) SignMessage(keyLoc keychain.KeyLocator,
msg []byte, doubleHash bool) (*ecdsa.Signature, error) {
mockKeyLoc := s.KeyLoc
if s.KeyLoc.IsEmpty() {
mockKeyLoc = idKeyLoc
}
if keyLoc != mockKeyLoc {
return nil, fmt.Errorf("unknown public key")
}
var digest []byte
if doubleHash {
digest = chainhash.DoubleHashB(msg)
} else {
digest = chainhash.HashB(msg)
}
return ecdsa.Sign(s.Privkey, digest), nil
}
package mock
import (
"sync"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs"
)
// SpendNotifier extends the mock.ChainNotifier so that spend
// notifications can be triggered and delivered to subscribers.
type SpendNotifier struct {
*ChainNotifier
spendMap map[wire.OutPoint][]chan *chainntnfs.SpendDetail
spends map[wire.OutPoint]*chainntnfs.SpendDetail
mtx sync.Mutex
}
// MakeMockSpendNotifier creates a SpendNotifier.
func MakeMockSpendNotifier() *SpendNotifier {
return &SpendNotifier{
ChainNotifier: &ChainNotifier{
SpendChan: make(chan *chainntnfs.SpendDetail),
EpochChan: make(chan *chainntnfs.BlockEpoch),
ConfChan: make(chan *chainntnfs.TxConfirmation),
},
spendMap: make(map[wire.OutPoint][]chan *chainntnfs.SpendDetail),
spends: make(map[wire.OutPoint]*chainntnfs.SpendDetail),
}
}
// RegisterSpendNtfn registers a spend notification for a specified outpoint.
func (s *SpendNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
_ []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
spendChan := make(chan *chainntnfs.SpendDetail, 1)
if detail, ok := s.spends[*outpoint]; ok {
// Deliver spend immediately if details are already known.
spendChan <- &chainntnfs.SpendDetail{
SpentOutPoint: detail.SpentOutPoint,
SpendingHeight: detail.SpendingHeight,
SpendingTx: detail.SpendingTx,
SpenderTxHash: detail.SpenderTxHash,
SpenderInputIndex: detail.SpenderInputIndex,
}
} else {
// Otherwise, queue the notification for delivery if the spend
// is ever received.
s.spendMap[*outpoint] = append(s.spendMap[*outpoint], spendChan)
}
return &chainntnfs.SpendEvent{
Spend: spendChan,
Cancel: func() {},
}, nil
}
// Spend dispatches SpendDetails to all subscribers of the outpoint. The details
// will includethe transaction and height provided by the caller.
func (s *SpendNotifier) Spend(outpoint *wire.OutPoint, height int32,
txn *wire.MsgTx) {
s.mtx.Lock()
defer s.mtx.Unlock()
var inputIndex uint32
for i, in := range txn.TxIn {
if in.PreviousOutPoint == *outpoint {
inputIndex = uint32(i)
}
}
txnHash := txn.TxHash()
details := &chainntnfs.SpendDetail{
SpentOutPoint: outpoint,
SpendingHeight: height,
SpendingTx: txn,
SpenderTxHash: &txnHash,
SpenderInputIndex: inputIndex,
}
// Cache details in case of late registration.
if _, ok := s.spends[*outpoint]; !ok {
s.spends[*outpoint] = details
}
// Deliver any backlogged spend notifications.
if spendChans, ok := s.spendMap[*outpoint]; ok {
delete(s.spendMap, *outpoint)
for _, spendChan := range spendChans {
spendChan <- &chainntnfs.SpendDetail{
SpentOutPoint: details.SpentOutPoint,
SpendingHeight: details.SpendingHeight,
SpendingTx: details.SpendingTx,
SpenderTxHash: details.SpenderTxHash,
SpenderInputIndex: details.SpenderInputIndex,
}
}
}
}
package mock
import (
"encoding/hex"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/waddrmgr"
base "github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/wallet/txauthor"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)
var (
CoinPkScript, _ = hex.DecodeString("001431df1bde03c074d0cf21ea2529427e1499b8f1de")
)
// WalletController is a mock implementation of the WalletController
// interface. It let's us mock the interaction with the bitcoin network.
type WalletController struct {
RootKey *btcec.PrivateKey
PublishedTransactions chan *wire.MsgTx
index uint32
Utxos []*lnwallet.Utxo
}
// A compile time check to ensure this mocked WalletController implements the
// WalletController.
var _ lnwallet.WalletController = (*WalletController)(nil)
// BackEnd returns "mock" to signify a mock wallet controller.
func (w *WalletController) BackEnd() string {
return "mock"
}
// FetchOutpointInfo will be called to get info about the inputs to the funding
// transaction.
func (w *WalletController) FetchOutpointInfo(
prevOut *wire.OutPoint) (*lnwallet.Utxo, error) {
utxo := &lnwallet.Utxo{
AddressType: lnwallet.WitnessPubKey,
Value: 10 * btcutil.SatoshiPerBitcoin,
PkScript: []byte("dummy"),
Confirmations: 1,
OutPoint: *prevOut,
}
return utxo, nil
}
// ScriptForOutput returns the address, witness program and redeem script for a
// given UTXO. An error is returned if the UTXO does not belong to our wallet or
// it is not a managed pubKey address.
func (w *WalletController) ScriptForOutput(*wire.TxOut) (
waddrmgr.ManagedPubKeyAddress, []byte, []byte, error) {
return nil, nil, nil, nil
}
// ConfirmedBalance currently returns dummy values.
func (w *WalletController) ConfirmedBalance(int32, string) (btcutil.Amount,
error) {
return 0, nil
}
// NewAddress is called to get new addresses for delivery, change etc.
func (w *WalletController) NewAddress(lnwallet.AddressType, bool,
string) (btcutil.Address, error) {
pkh := btcutil.Hash160(w.RootKey.PubKey().SerializeCompressed())
addr, _ := btcutil.NewAddressPubKeyHash(pkh, &chaincfg.MainNetParams)
return addr, nil
}
// LastUnusedAddress currently returns dummy values.
func (w *WalletController) LastUnusedAddress(lnwallet.AddressType,
string) (btcutil.Address, error) {
return nil, nil
}
// IsOurAddress currently returns a dummy value.
func (w *WalletController) IsOurAddress(btcutil.Address) bool {
return false
}
// AddressInfo currently returns a dummy value.
func (w *WalletController) AddressInfo(
btcutil.Address) (waddrmgr.ManagedAddress, error) {
return nil, nil
}
// ListAccounts currently returns a dummy value.
func (w *WalletController) ListAccounts(string,
*waddrmgr.KeyScope) ([]*waddrmgr.AccountProperties, error) {
return nil, nil
}
// RequiredReserve currently returns a dummy value.
func (w *WalletController) RequiredReserve(uint32) btcutil.Amount {
return 0
}
// ListAddresses currently returns a dummy value.
func (w *WalletController) ListAddresses(string,
bool) (lnwallet.AccountAddressMap, error) {
return nil, nil
}
// ImportAccount currently returns a dummy value.
func (w *WalletController) ImportAccount(string, *hdkeychain.ExtendedKey,
uint32, *waddrmgr.AddressType, bool) (*waddrmgr.AccountProperties,
[]btcutil.Address, []btcutil.Address, error) {
return nil, nil, nil, nil
}
// ImportPublicKey currently returns a dummy value.
func (w *WalletController) ImportPublicKey(*btcec.PublicKey,
waddrmgr.AddressType) error {
return nil
}
// ImportTaprootScript currently returns a dummy value.
func (w *WalletController) ImportTaprootScript(waddrmgr.KeyScope,
*waddrmgr.Tapscript) (waddrmgr.ManagedAddress, error) {
return nil, nil
}
// SendOutputs currently returns dummy values.
func (w *WalletController) SendOutputs(fn.Set[wire.OutPoint], []*wire.TxOut,
chainfee.SatPerKWeight, int32, string, base.CoinSelectionStrategy) (
*wire.MsgTx, error) {
return nil, nil
}
// CreateSimpleTx currently returns dummy values.
func (w *WalletController) CreateSimpleTx(fn.Set[wire.OutPoint], []*wire.TxOut,
chainfee.SatPerKWeight, int32, base.CoinSelectionStrategy,
bool) (*txauthor.AuthoredTx, error) {
return nil, nil
}
// ListUnspentWitness is called by the wallet when doing coin selection. We just
// need one unspent for the funding transaction.
func (w *WalletController) ListUnspentWitness(int32, int32,
string) ([]*lnwallet.Utxo, error) {
// If the mock already has a list of utxos, return it.
if w.Utxos != nil {
return w.Utxos, nil
}
// Otherwise create one to return.
utxo := &lnwallet.Utxo{
AddressType: lnwallet.WitnessPubKey,
Value: btcutil.Amount(10 * btcutil.SatoshiPerBitcoin),
PkScript: CoinPkScript,
OutPoint: wire.OutPoint{
Hash: chainhash.Hash{},
Index: w.index,
},
}
atomic.AddUint32(&w.index, 1)
var ret []*lnwallet.Utxo
ret = append(ret, utxo)
return ret, nil
}
// ListTransactionDetails currently returns dummy values.
func (w *WalletController) ListTransactionDetails(int32, int32,
string, uint32, uint32) ([]*lnwallet.TransactionDetail,
uint64, uint64, error) {
return nil, 0, 0, nil
}
// LeaseOutput returns the current time and a nil error.
func (w *WalletController) LeaseOutput(wtxmgr.LockID, wire.OutPoint,
time.Duration) (time.Time, error) {
return time.Now(), nil
}
// ReleaseOutput currently does nothing.
func (w *WalletController) ReleaseOutput(wtxmgr.LockID, wire.OutPoint) error {
return nil
}
func (w *WalletController) ListLeasedOutputs() ([]*base.ListLeasedOutputResult,
error) {
return nil, nil
}
// FundPsbt currently does nothing.
func (w *WalletController) FundPsbt(*psbt.Packet, int32, chainfee.SatPerKWeight,
string, *waddrmgr.KeyScope, base.CoinSelectionStrategy,
func(utxo wtxmgr.Credit) bool) (int32, error) {
return 0, nil
}
// SignPsbt currently does nothing.
func (w *WalletController) SignPsbt(*psbt.Packet) ([]uint32, error) {
return nil, nil
}
// FinalizePsbt currently does nothing.
func (w *WalletController) FinalizePsbt(_ *psbt.Packet, _ string) error {
return nil
}
// DecorateInputs currently does nothing.
func (w *WalletController) DecorateInputs(*psbt.Packet, bool) error {
return nil
}
// PublishTransaction sends a transaction to the PublishedTransactions chan.
func (w *WalletController) PublishTransaction(tx *wire.MsgTx, _ string) error {
w.PublishedTransactions <- tx
return nil
}
// GetTransactionDetails currently does nothing.
func (w *WalletController) GetTransactionDetails(
txHash *chainhash.Hash) (*lnwallet.TransactionDetail, error) {
return nil, nil
}
// LabelTransaction currently does nothing.
func (w *WalletController) LabelTransaction(chainhash.Hash, string,
bool) error {
return nil
}
// SubscribeTransactions currently does nothing.
func (w *WalletController) SubscribeTransactions() (lnwallet.TransactionSubscription,
error) {
return nil, nil
}
// IsSynced currently returns dummy values.
func (w *WalletController) IsSynced() (bool, int64, error) {
return true, int64(0), nil
}
// GetRecoveryInfo currently returns dummy values.
func (w *WalletController) GetRecoveryInfo() (bool, float64, error) {
return true, float64(1), nil
}
// Start currently does nothing.
func (w *WalletController) Start() error {
return nil
}
// Stop currently does nothing.
func (w *WalletController) Stop() error {
return nil
}
func (w *WalletController) FetchTx(chainhash.Hash) (*wire.MsgTx, error) {
return nil, nil
}
func (w *WalletController) RemoveDescendants(*wire.MsgTx) error {
return nil
}
func (w *WalletController) CheckMempoolAcceptance(tx *wire.MsgTx) error {
return nil
}
// FetchDerivationInfo queries for the wallet's knowledge of the passed
// pkScript and constructs the derivation info and returns it.
func (w *WalletController) FetchDerivationInfo(
pkScript []byte) (*psbt.Bip32Derivation, error) {
return nil, nil
}
package node
import (
"flag"
"fmt"
"io"
"os"
"path"
"path/filepath"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/integration/rpctest"
"github.com/lightningnetwork/lnd"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/kvdb/etcd"
"github.com/lightningnetwork/lnd/lntest/port"
"github.com/lightningnetwork/lnd/lntest/wait"
)
const (
// ListenerFormat is the format string that is used to generate local
// listener addresses.
ListenerFormat = "127.0.0.1:%d"
// DefaultCSV is the CSV delay (remotedelay) we will start our test
// nodes with.
DefaultCSV = 4
)
var (
// logOutput is a flag that can be set to append the output from the
// seed nodes to log files.
logOutput = flag.Bool("logoutput", false,
"log output from node n to file output-n.log")
// logSubDir is the default directory where the logs are written to if
// logOutput is true.
logSubDir = flag.String("logdir", ".", "default dir to write logs to")
// btcdExecutable is the full path to the btcd binary.
btcdExecutable = flag.String(
"btcdexec", "", "full path to btcd binary",
)
// CfgLegacy specifies the config used to create a node that uses the
// legacy channel format.
CfgLegacy = []string{"--protocol.legacy.committweak"}
// CfgStaticRemoteKey specifies the config used to create a node that
// uses the static remote key feature.
CfgStaticRemoteKey = []string{}
// CfgAnchor specifies the config used to create a node that uses the
// anchor output feature.
CfgAnchor = []string{"--protocol.anchors"}
// CfgLeased specifies the config used to create a node that uses the
// leased channel feature.
CfgLeased = []string{
"--protocol.anchors",
"--protocol.script-enforced-lease",
}
// CfgSimpleTaproot specifies the config used to create a node that
// uses the simple taproot feature.
CfgSimpleTaproot = []string{
"--protocol.anchors",
"--protocol.simple-taproot-chans",
}
// CfgRbfCoopClose specifies the config used to create a node that
// supports the new RBF close protocol.
CfgRbfClose = []string{
"--protocol.rbf-coop-close",
}
// CfgZeroConf specifies the config used to create a node that uses the
// zero-conf channel feature.
CfgZeroConf = []string{
"--protocol.anchors",
"--protocol.option-scid-alias",
"--protocol.zero-conf",
}
)
type DatabaseBackend int
const (
BackendBbolt DatabaseBackend = iota
BackendEtcd
BackendPostgres
BackendSqlite
)
// Option is a function for updating a node's configuration.
type Option func(*BaseNodeConfig)
// BackendConfig is an interface that abstracts away the specific chain backend
// node implementation.
type BackendConfig interface {
// GenArgs returns the arguments needed to be passed to LND at startup
// for using this node as a chain backend.
GenArgs() []string
// ConnectMiner is called to establish a connection to the test miner.
ConnectMiner() error
// DisconnectMiner is called to disconnect the miner.
DisconnectMiner() error
// Name returns the name of the backend type.
Name() string
// Credentials returns the rpc username, password and host for the
// backend.
Credentials() (string, string, string, error)
}
// BaseNodeConfig is the base node configuration.
type BaseNodeConfig struct {
Name string
// LogFilenamePrefix is used to prefix node log files. Can be used to
// store the current test case for simpler postmortem debugging.
LogFilenamePrefix string
NetParams *chaincfg.Params
BackendCfg BackendConfig
BaseDir string
ExtraArgs []string
OriginalExtraArgs []string
DataDir string
LogDir string
TLSCertPath string
TLSKeyPath string
AdminMacPath string
ReadMacPath string
InvoiceMacPath string
SkipUnlock bool
Password []byte
P2PPort int
RPCPort int
RESTPort int
ProfilePort int
FeeURL string
DBBackend DatabaseBackend
PostgresDsn string
NativeSQL bool
// NodeID is a unique ID used to identify the node.
NodeID uint32
// LndBinary is the full path to the lnd binary that was specifically
// compiled with all required itest flags.
LndBinary string
// backupDBDir is the path where a database backup is stored, if any.
backupDBDir string
// postgresDBName is the name of the postgres database where lnd data
// is stored in.
postgresDBName string
}
func (cfg BaseNodeConfig) P2PAddr() string {
return fmt.Sprintf(ListenerFormat, cfg.P2PPort)
}
func (cfg BaseNodeConfig) RPCAddr() string {
return fmt.Sprintf(ListenerFormat, cfg.RPCPort)
}
func (cfg BaseNodeConfig) RESTAddr() string {
return fmt.Sprintf(ListenerFormat, cfg.RESTPort)
}
// DBDir returns the holding directory path of the graph database.
func (cfg BaseNodeConfig) DBDir() string {
return filepath.Join(cfg.DataDir, "graph", cfg.NetParams.Name)
}
func (cfg BaseNodeConfig) DBPath() string {
return filepath.Join(cfg.DBDir(), "channel.db")
}
func (cfg BaseNodeConfig) ChanBackupPath() string {
return filepath.Join(
cfg.DataDir, "chain", lnd.BitcoinChainName,
fmt.Sprintf(
"%v/%v", cfg.NetParams.Name,
chanbackup.DefaultBackupFileName,
),
)
}
// GenerateListeningPorts generates the ports to listen on designated for the
// current lightning network test.
func (cfg *BaseNodeConfig) GenerateListeningPorts() {
if cfg.P2PPort == 0 {
cfg.P2PPort = port.NextAvailablePort()
}
if cfg.RPCPort == 0 {
cfg.RPCPort = port.NextAvailablePort()
}
if cfg.RESTPort == 0 {
cfg.RESTPort = port.NextAvailablePort()
}
if cfg.ProfilePort == 0 {
cfg.ProfilePort = port.NextAvailablePort()
}
}
// BaseConfig returns the base node configuration struct.
func (cfg *BaseNodeConfig) BaseConfig() *BaseNodeConfig {
return cfg
}
// GenArgs generates a slice of command line arguments from the lightning node
// config struct.
func (cfg *BaseNodeConfig) GenArgs() []string {
var args []string
switch cfg.NetParams {
case &chaincfg.TestNet3Params:
args = append(args, "--bitcoin.testnet")
case &chaincfg.TestNet4Params:
args = append(args, "--bitcoin.testnet4")
case &chaincfg.SimNetParams:
args = append(args, "--bitcoin.simnet")
case &chaincfg.RegressionNetParams:
args = append(args, "--bitcoin.regtest")
}
backendArgs := cfg.BackendCfg.GenArgs()
args = append(args, backendArgs...)
nodeArgs := []string{
"--nobootstrap",
"--debuglevel=debug",
"--bitcoin.defaultchanconfs=1",
"--accept-keysend",
"--keep-failed-payment-attempts",
"--logging.no-commit-hash",
fmt.Sprintf("--db.batch-commit-interval=%v", commitInterval),
fmt.Sprintf("--bitcoin.defaultremotedelay=%v", DefaultCSV),
fmt.Sprintf("--rpclisten=%v", cfg.RPCAddr()),
fmt.Sprintf("--restlisten=%v", cfg.RESTAddr()),
fmt.Sprintf("--restcors=https://%v", cfg.RESTAddr()),
fmt.Sprintf("--listen=%v", cfg.P2PAddr()),
fmt.Sprintf("--externalip=%v", cfg.P2PAddr()),
fmt.Sprintf("--lnddir=%v", cfg.BaseDir),
fmt.Sprintf("--adminmacaroonpath=%v", cfg.AdminMacPath),
fmt.Sprintf("--readonlymacaroonpath=%v", cfg.ReadMacPath),
fmt.Sprintf("--invoicemacaroonpath=%v", cfg.InvoiceMacPath),
fmt.Sprintf("--trickledelay=%v", trickleDelay),
// Use a small batch delay so we can broadcast the
// announcements quickly in the tests.
"--gossip.sub-batch-delay=5ms",
// Use a small cache duration so the `DescribeGraph` can be
// updated quicker.
"--caches.rpc-graph-cache-duration=100ms",
// Speed up the tests for bitcoind backend.
"--bitcoind.blockpollinginterval=100ms",
"--bitcoind.txpollinginterval=100ms",
// Allow unsafe disconnect in itest.
"--dev.unsafedisconnect",
}
args = append(args, nodeArgs...)
if cfg.Password == nil {
args = append(args, "--noseedbackup")
}
switch cfg.DBBackend {
case BackendEtcd:
args = append(args, "--db.backend=etcd")
args = append(args, "--db.etcd.embedded")
args = append(
args, fmt.Sprintf(
"--db.etcd.embedded_client_port=%v",
port.NextAvailablePort(),
),
)
args = append(
args, fmt.Sprintf(
"--db.etcd.embedded_peer_port=%v",
port.NextAvailablePort(),
),
)
args = append(
args, fmt.Sprintf(
"--db.etcd.embedded_log_file=%v",
path.Join(cfg.LogDir, "etcd.log"),
),
)
case BackendPostgres:
args = append(args, "--db.backend=postgres")
args = append(args, "--db.postgres.dsn="+cfg.PostgresDsn)
if cfg.NativeSQL {
args = append(args, "--db.use-native-sql")
}
case BackendSqlite:
args = append(args, "--db.backend=sqlite")
args = append(args, fmt.Sprintf("--db.sqlite.busytimeout=%v",
wait.SqliteBusyTimeout))
if cfg.NativeSQL {
args = append(args, "--db.use-native-sql")
}
}
if cfg.FeeURL != "" {
args = append(args, "--fee.url="+cfg.FeeURL)
}
// Put extra args in the end so the args can be overwritten.
if cfg.ExtraArgs != nil {
args = append(args, cfg.ExtraArgs...)
}
return args
}
// ExtraArgsEtcd returns extra args for configuring LND to use an external etcd
// database (for remote channel DB and wallet DB).
func ExtraArgsEtcd(etcdCfg *etcd.Config, name string, cluster bool,
leaderSessionTTL int) []string {
extraArgs := []string{
"--db.backend=etcd",
fmt.Sprintf("--db.etcd.host=%v", etcdCfg.Host),
fmt.Sprintf("--db.etcd.user=%v", etcdCfg.User),
fmt.Sprintf("--db.etcd.pass=%v", etcdCfg.Pass),
fmt.Sprintf("--db.etcd.namespace=%v", etcdCfg.Namespace),
}
if etcdCfg.InsecureSkipVerify {
extraArgs = append(extraArgs, "--db.etcd.insecure_skip_verify")
}
if cluster {
clusterArgs := []string{
"--cluster.enable-leader-election",
fmt.Sprintf("--cluster.id=%v", name),
fmt.Sprintf("--cluster.leader-session-ttl=%v",
leaderSessionTTL),
}
extraArgs = append(extraArgs, clusterArgs...)
extraArgs = append(
extraArgs, "--healthcheck.leader.interval=10s",
)
}
return extraArgs
}
// GetLogDir returns the passed --logdir flag or the default value if it wasn't
// set.
func GetLogDir() string {
if logSubDir != nil && *logSubDir != "" {
return *logSubDir
}
return "."
}
// CopyFile copies the file src to dest.
func CopyFile(dest, src string) error {
s, err := os.Open(src)
if err != nil {
return err
}
defer s.Close()
d, err := os.Create(dest)
if err != nil {
return err
}
if _, err := io.Copy(d, s); err != nil {
d.Close()
return err
}
return d.Close()
}
// GetBtcdBinary returns the full path to the binary of the custom built btcd
// executable or an empty string if none is set.
func GetBtcdBinary() string {
if btcdExecutable != nil {
return *btcdExecutable
}
return ""
}
func init() {
// Before we start any node, we need to make sure that any btcd or
// bitcoind node that is started through the RPC harness uses a unique
// port as well to avoid any port collisions.
rpctest.ListenAddressGenerator =
port.GenerateSystemUniqueListenerAddresses
}
package node
import (
"bytes"
"context"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"github.com/jackc/pgx/v4/pgxpool"
"github.com/lightningnetwork/lnd"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntest/rpc"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/macaroons"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/status"
"gopkg.in/macaroon.v2"
)
const (
// logPubKeyBytes is the number of bytes of the node's PubKey that will
// be appended to the log file name. The whole PubKey is too long and
// not really necessary to quickly identify what node produced which
// log file.
logPubKeyBytes = 4
// trickleDelay is the amount of time in milliseconds between each
// release of announcements by AuthenticatedGossiper to the network.
trickleDelay = 50
postgresDsn = "postgres://postgres:postgres@localhost:" +
"6432/%s?sslmode=disable"
// commitInterval specifies the maximum interval the graph database
// will wait between attempting to flush a batch of modifications to
// disk(db.batch-commit-interval).
commitInterval = 10 * time.Millisecond
)
// HarnessNode represents an instance of lnd running within our test network
// harness. It's responsible for managing the lnd process, grpc connection, and
// wallet auth. A HarnessNode is built upon its rpc clients, represented in
// `HarnessRPC`. It also has a `State` which holds its internal state, and a
// `Watcher` that keeps track of its topology updates.
type HarnessNode struct {
*testing.T
// Cfg holds the config values for the node.
Cfg *BaseNodeConfig
// RPC holds a list of RPC clients.
RPC *rpc.HarnessRPC
// State records the current state of the node.
State *State
// Watcher watches the node's topology updates.
Watcher *nodeWatcher
// PubKey is the serialized compressed identity public key of the node.
// This field will only be populated once the node itself has been
// started via the start() method.
PubKey [33]byte
PubKeyStr string
// conn is the underlying connection to the grpc endpoint of the node.
conn *grpc.ClientConn
// runCtx is a context with cancel method. It's used to signal when the
// node needs to quit, and used as the parent context when spawning
// children contexts for RPC requests.
runCtx context.Context //nolint:containedctx
cancel context.CancelFunc
// filename is the log file's name.
filename string
cmd *exec.Cmd
logFile *os.File
}
// NewHarnessNode creates a new test lightning node instance from the passed
// config.
func NewHarnessNode(t *testing.T, cfg *BaseNodeConfig) (*HarnessNode, error) {
if cfg.BaseDir == "" {
var err error
// Create a temporary directory for the node's data and logs.
// Use dash suffix as a separator between base name and random
// suffix.
dirBaseName := fmt.Sprintf("lndtest-node-%s-", cfg.Name)
cfg.BaseDir, err = os.MkdirTemp("", dirBaseName)
if err != nil {
return nil, err
}
}
cfg.DataDir = filepath.Join(cfg.BaseDir, "data")
cfg.LogDir = filepath.Join(cfg.BaseDir, "logs")
cfg.TLSCertPath = filepath.Join(cfg.BaseDir, "tls.cert")
cfg.TLSKeyPath = filepath.Join(cfg.BaseDir, "tls.key")
networkDir := filepath.Join(
cfg.DataDir, "chain", lnd.BitcoinChainName, cfg.NetParams.Name,
)
cfg.AdminMacPath = filepath.Join(networkDir, "admin.macaroon")
cfg.ReadMacPath = filepath.Join(networkDir, "readonly.macaroon")
cfg.InvoiceMacPath = filepath.Join(networkDir, "invoice.macaroon")
cfg.GenerateListeningPorts()
// Create temporary database.
var dbName string
if cfg.DBBackend == BackendPostgres {
var err error
dbName, err = createTempPgDB()
if err != nil {
return nil, err
}
cfg.PostgresDsn = postgresDatabaseDsn(dbName)
}
cfg.OriginalExtraArgs = cfg.ExtraArgs
cfg.postgresDBName = dbName
return &HarnessNode{
T: t,
Cfg: cfg,
}, nil
}
// Initialize creates a list of new RPC clients using the passed connection,
// initializes the node's internal state and creates a topology watcher.
func (hn *HarnessNode) Initialize(c *grpc.ClientConn) {
hn.conn = c
// Init all the rpc clients.
hn.RPC = rpc.NewHarnessRPC(hn.runCtx, hn.T, c, hn.Name())
// Init the node's state.
//
// If we already have a state, it means we are restarting the node and
// we will only reset its internal states. Otherwise we'll create a new
// state.
if hn.State != nil {
hn.State.resetEphermalStates(hn.RPC)
} else {
hn.State = newState(hn.RPC)
}
// Init the topology watcher.
hn.Watcher = newNodeWatcher(hn.RPC, hn.State)
}
// Name returns the name of this node set during initialization.
func (hn *HarnessNode) Name() string {
return hn.Cfg.Name
}
// UpdateState updates the node's internal state.
func (hn *HarnessNode) UpdateState() {
hn.State.updateState()
}
// String gives the internal state of the node which is useful for debugging.
func (hn *HarnessNode) String() string {
type nodeCfg struct {
LogFilenamePrefix string
ExtraArgs []string
SkipUnlock bool
Password []byte
P2PPort int
RPCPort int
RESTPort int
AcceptKeySend bool
FeeURL string
}
nodeState := struct {
NodeID uint32
Name string
PubKey string
State *State
NodeCfg nodeCfg
}{
NodeID: hn.Cfg.NodeID,
Name: hn.Cfg.Name,
PubKey: hn.PubKeyStr,
State: hn.State,
NodeCfg: nodeCfg{
SkipUnlock: hn.Cfg.SkipUnlock,
Password: hn.Cfg.Password,
LogFilenamePrefix: hn.Cfg.LogFilenamePrefix,
ExtraArgs: hn.Cfg.ExtraArgs,
P2PPort: hn.Cfg.P2PPort,
RPCPort: hn.Cfg.RPCPort,
RESTPort: hn.Cfg.RESTPort,
},
}
stateBytes, err := json.MarshalIndent(nodeState, "", "\t")
if err != nil {
return fmt.Sprintf("\n encode node state with err: %v", err)
}
return fmt.Sprintf("\nnode state: %s", stateBytes)
}
// WaitUntilStarted waits until the wallet state flips from "WAITING_TO_START".
func (hn *HarnessNode) WaitUntilStarted() error {
return hn.waitTillServerState(func(s lnrpc.WalletState) bool {
return s != lnrpc.WalletState_WAITING_TO_START
})
}
// WaitUntilServerActive waits until the lnd daemon is fully started.
func (hn *HarnessNode) WaitUntilServerActive() error {
return hn.waitTillServerState(func(s lnrpc.WalletState) bool {
return s == lnrpc.WalletState_SERVER_ACTIVE
})
}
// WaitUntilLeader attempts to finish the start procedure by initiating an RPC
// connection and setting up the wallet unlocker client. This is needed when
// a node that has recently been started was waiting to become the leader and
// we're at the point when we expect that it is the leader now (awaiting
// unlock).
func (hn *HarnessNode) WaitUntilLeader(timeout time.Duration) error {
var (
conn *grpc.ClientConn
connErr error
)
if err := wait.NoError(func() error {
conn, connErr = hn.ConnectRPCWithMacaroon(nil)
return connErr
}, timeout); err != nil {
return err
}
// Since the conn is not authed, only the `WalletUnlocker` and `State`
// clients can be inited from this conn.
hn.conn = conn
hn.RPC = rpc.NewHarnessRPC(hn.runCtx, hn.T, conn, hn.Name())
// Wait till the server is starting.
return hn.WaitUntilStarted()
}
// Unlock attempts to unlock the wallet of the target HarnessNode. This method
// should be called after the restart of a HarnessNode that was created with a
// seed+password. Once this method returns, the HarnessNode will be ready to
// accept normal gRPC requests and harness command.
func (hn *HarnessNode) Unlock(unlockReq *lnrpc.UnlockWalletRequest) error {
// Otherwise, we'll need to unlock the node before it's able to start
// up properly.
hn.RPC.UnlockWallet(unlockReq)
// Now that the wallet has been unlocked, we'll wait for the RPC client
// to be ready, then establish the normal gRPC connection.
return hn.InitNode(nil)
}
// AddToLogf adds a line of choice to the node's logfile. This is useful
// to interleave test output with output from the node.
func (hn *HarnessNode) AddToLogf(format string, a ...interface{}) {
// If this node was not set up with a log file, just return early.
if hn.logFile == nil {
return
}
desc := fmt.Sprintf("itest: %s\n", fmt.Sprintf(format, a...))
if _, err := hn.logFile.WriteString(desc); err != nil {
hn.printErrf("write to log err: %v", err)
}
}
// ReadMacaroon waits a given duration for the macaroon file to be created. If
// the file is readable within the timeout, its content is de-serialized as a
// macaroon and returned.
func (hn *HarnessNode) ReadMacaroon(macPath string, timeout time.Duration) (
*macaroon.Macaroon, error) {
// Wait until macaroon file is created and has valid content before
// using it.
var mac *macaroon.Macaroon
err := wait.NoError(func() error {
macBytes, err := os.ReadFile(macPath)
if err != nil {
return fmt.Errorf("error reading macaroon file: %w",
err)
}
newMac := &macaroon.Macaroon{}
if err = newMac.UnmarshalBinary(macBytes); err != nil {
return fmt.Errorf("error unmarshalling macaroon "+
"file: %w", err)
}
mac = newMac
return nil
}, timeout)
return mac, err
}
// ConnectRPCWithMacaroon uses the TLS certificate and given macaroon to
// create a gRPC client connection.
func (hn *HarnessNode) ConnectRPCWithMacaroon(mac *macaroon.Macaroon) (
*grpc.ClientConn, error) {
// Wait until TLS certificate is created and has valid content before
// using it, up to 30 sec.
var tlsCreds credentials.TransportCredentials
err := wait.NoError(func() error {
var err error
tlsCreds, err = credentials.NewClientTLSFromFile(
hn.Cfg.TLSCertPath, "",
)
return err
}, wait.DefaultTimeout)
if err != nil {
return nil, fmt.Errorf("error reading TLS cert: %w", err)
}
opts := []grpc.DialOption{
grpc.WithBlock(),
grpc.WithTransportCredentials(tlsCreds),
}
ctx, cancel := context.WithTimeout(hn.runCtx, wait.DefaultTimeout)
defer cancel()
if mac == nil {
return grpc.DialContext(ctx, hn.Cfg.RPCAddr(), opts...)
}
macCred, err := macaroons.NewMacaroonCredential(mac)
if err != nil {
return nil, fmt.Errorf("error cloning mac: %w", err)
}
opts = append(opts, grpc.WithPerRPCCredentials(macCred))
return grpc.DialContext(ctx, hn.Cfg.RPCAddr(), opts...)
}
// ConnectRPC uses the TLS certificate and admin macaroon files written by the
// lnd node to create a gRPC client connection.
func (hn *HarnessNode) ConnectRPC() (*grpc.ClientConn, error) {
// If we should use a macaroon, always take the admin macaroon as a
// default.
mac, err := hn.ReadMacaroon(hn.Cfg.AdminMacPath, wait.DefaultTimeout)
if err != nil {
return nil, err
}
return hn.ConnectRPCWithMacaroon(mac)
}
// SetExtraArgs assigns the ExtraArgs field for the node's configuration. The
// changes will take effect on restart.
func (hn *HarnessNode) SetExtraArgs(extraArgs []string) {
hn.Cfg.ExtraArgs = extraArgs
}
// StartLndCmd handles the startup of lnd, creating log files, and possibly
// kills the process when needed.
func (hn *HarnessNode) StartLndCmd(ctxb context.Context) error {
// Init the run context.
hn.runCtx, hn.cancel = context.WithCancel(ctxb)
args := hn.Cfg.GenArgs()
hn.cmd = exec.Command(hn.Cfg.LndBinary, args...)
// Redirect stderr output to buffer
var errb bytes.Buffer
hn.cmd.Stderr = &errb
// If the logoutput flag is passed, redirect output from the nodes to
// log files.
if *logOutput {
err := addLogFile(hn)
if err != nil {
return err
}
}
// Start the process.
if err := hn.cmd.Start(); err != nil {
return err
}
pid := hn.cmd.Process.Pid
hn.T.Logf("Starting node (name=%v) with PID=%v", hn.Cfg.Name, pid)
return nil
}
// StartWithNoAuth will start the lnd process, creates the grpc connection
// without macaroon auth, and waits until the server is reported as waiting to
// start.
//
// NOTE: caller needs to take extra step to create and unlock the wallet.
func (hn *HarnessNode) StartWithNoAuth(ctxt context.Context) error {
// Start lnd process and prepare logs.
if err := hn.StartLndCmd(ctxt); err != nil {
return fmt.Errorf("start lnd error: %w", err)
}
// Create an unauthed connection.
conn, err := hn.ConnectRPCWithMacaroon(nil)
if err != nil {
return fmt.Errorf("ConnectRPCWithMacaroon err: %w", err)
}
// Since the conn is not authed, only the `WalletUnlocker` and `State`
// clients can be inited from this conn.
hn.conn = conn
hn.RPC = rpc.NewHarnessRPC(hn.runCtx, hn.T, conn, hn.Name())
// Wait till the server is starting.
return hn.WaitUntilStarted()
}
// Start will start the lnd process, creates the grpc connection, and waits
// until the server is fully started.
func (hn *HarnessNode) Start(ctxt context.Context) error {
// Start lnd process and prepare logs.
if err := hn.StartLndCmd(ctxt); err != nil {
return fmt.Errorf("start lnd error: %w", err)
}
// Since Stop uses the LightningClient to stop the node, if we fail to
// get a connected client, we have to kill the process.
conn, err := hn.ConnectRPC()
if err != nil {
err = fmt.Errorf("ConnectRPC err: %w", err)
cmdErr := hn.Kill()
if cmdErr != nil {
err = fmt.Errorf("kill process got err: %w: %v",
cmdErr, err)
}
return err
}
// Init the node by creating the RPC clients, initializing node's
// internal state and watcher.
hn.Initialize(conn)
// Wait till the server is starting.
if err := hn.WaitUntilStarted(); err != nil {
return fmt.Errorf("waiting for start got: %w", err)
}
// Subscribe for topology updates.
return hn.initLightningClient()
}
// InitNode waits until the main gRPC server is detected as active, then
// complete the normal HarnessNode gRPC connection creation. A non-nil
// `macBytes` indicates the node is initialized stateless, otherwise it will
// use the admin macaroon.
func (hn *HarnessNode) InitNode(macBytes []byte) error {
var (
conn *grpc.ClientConn
err error
)
// If the node has been initialized stateless, we need to pass the
// macaroon to the client.
if macBytes != nil {
adminMac := &macaroon.Macaroon{}
err := adminMac.UnmarshalBinary(macBytes)
if err != nil {
return fmt.Errorf("unmarshal failed: %w", err)
}
conn, err = hn.ConnectRPCWithMacaroon(adminMac)
if err != nil {
return err
}
} else {
// Normal initialization, we expect a macaroon to be in the
// file system.
conn, err = hn.ConnectRPC()
if err != nil {
return err
}
}
// Init the node by creating the RPC clients, initializing node's
// internal state and watcher.
hn.Initialize(conn)
// Wait till the server is starting.
if err := hn.WaitUntilStarted(); err != nil {
return fmt.Errorf("waiting for start got: %w", err)
}
return hn.initLightningClient()
}
// InitChangePassword initializes a harness node by passing the change password
// request via RPC. After the request is submitted, this method will block until
// a macaroon-authenticated RPC connection can be established to the harness
// node. Once established, the new connection is used to initialize the
// RPC clients and subscribes the HarnessNode to topology changes.
func (hn *HarnessNode) ChangePasswordAndInit(
req *lnrpc.ChangePasswordRequest) (
*lnrpc.ChangePasswordResponse, error) {
response := hn.RPC.ChangePassword(req)
return response, hn.InitNode(response.AdminMacaroon)
}
// waitTillServerState makes a subscription to the server's state change and
// blocks until the server is in the targeted state.
func (hn *HarnessNode) waitTillServerState(
predicate func(state lnrpc.WalletState) bool) error {
client := hn.RPC.SubscribeState()
errChan := make(chan error, 1)
done := make(chan struct{})
go func() {
for {
resp, err := client.Recv()
if err != nil {
errChan <- err
return
}
if predicate(resp.State) {
close(done)
return
}
}
}()
for {
select {
case <-time.After(wait.NodeStartTimeout):
return fmt.Errorf("timeout waiting for server state")
case err := <-errChan:
return fmt.Errorf("receive server state err: %w", err)
case <-done:
return nil
}
}
}
// initLightningClient blocks until the lnd server is fully started and
// subscribes the harness node to graph topology updates. This method also
// spawns a lightning network watcher for this node, which watches for topology
// changes.
func (hn *HarnessNode) initLightningClient() error {
// Wait until the server is fully started.
if err := hn.WaitUntilServerActive(); err != nil {
return fmt.Errorf("waiting for server active: %w", err)
}
// Set the harness node's pubkey to what the node claims in GetInfo.
// The RPC must have been started at this point.
if err := hn.attachPubKey(); err != nil {
return err
}
// Launch the watcher that will hook into graph related topology change
// from the PoV of this node.
started := make(chan error, 1)
go hn.Watcher.topologyWatcher(hn.runCtx, started)
select {
// First time reading the channel indicates the topology client is
// started.
case err := <-started:
if err != nil {
return fmt.Errorf("create topology client stream "+
"got err: %v", err)
}
case <-time.After(wait.DefaultTimeout):
return fmt.Errorf("timeout creating topology client stream")
}
// Catch topology client stream error inside a goroutine.
go func() {
select {
case err := <-started:
hn.printErrf("topology client: %v", err)
case <-hn.runCtx.Done():
}
}()
return nil
}
// attachPubKey queries an unlocked node to retrieve its public key.
func (hn *HarnessNode) attachPubKey() error {
// Obtain the lnid of this node for quick identification purposes.
info := hn.RPC.GetInfo()
hn.PubKeyStr = info.IdentityPubkey
pubkey, err := hex.DecodeString(info.IdentityPubkey)
if err != nil {
return err
}
copy(hn.PubKey[:], pubkey)
return nil
}
// cleanup cleans up all the temporary files created by the node's process.
func (hn *HarnessNode) cleanup() error {
if hn.Cfg.backupDBDir != "" {
err := os.RemoveAll(hn.Cfg.backupDBDir)
if err != nil {
return fmt.Errorf("unable to remove backup dir: %w",
err)
}
}
return os.RemoveAll(hn.Cfg.BaseDir)
}
// waitForProcessExit Launch a new goroutine which that bubbles up any
// potential fatal process errors to the goroutine running the tests.
func (hn *HarnessNode) WaitForProcessExit() error {
var errReturned error
errChan := make(chan error, 1)
go func() {
errChan <- hn.cmd.Wait()
}()
select {
case err := <-errChan:
if err == nil {
break
}
// If the process has already been canceled, we can exit early
// as the logs have already been saved.
if strings.Contains(err.Error(), "Wait was already called") {
return nil
}
// The process may have already been killed in the test, in
// that case we will skip the error and continue processing
// the logs.
if strings.Contains(err.Error(), "signal: killed") {
break
}
// Otherwise, we print the error, break the select and save
// logs.
hn.printErrf("wait process exit got err: %v", err)
errReturned = err
case <-time.After(wait.DefaultTimeout):
hn.printErrf("timeout waiting for process to exit")
}
// If the node has an open log file handle, inspect the log file
// to verify that the node shut down correctly.
if hn.logFile != nil {
// Make sure log file is closed and renamed if necessary.
filename := finalizeLogfile(hn)
// Assert the node has shut down from the log file.
err1 := assertNodeShutdown(filename)
if err1 != nil {
return fmt.Errorf("[%s]: assert shutdown failed in "+
"log[%s]: %w", hn.Name(), filename, err1)
}
}
// Rename the etcd.log file if the node was running on embedded etcd.
finalizeEtcdLog(hn)
return errReturned
}
// Stop attempts to stop the active lnd process.
func (hn *HarnessNode) Stop() error {
// Do nothing if the process is not running.
if hn.runCtx == nil {
hn.printErrf("found nil run context")
return nil
}
// Stop the runCtx.
hn.cancel()
// If we ever reaches the state where `Watcher` is initialized, it
// means the node has an authed connection and all its RPC clients are
// ready for use. Thus we will try to stop it via the RPC.
if hn.Watcher != nil {
// Don't watch for error because sometimes the RPC connection
// gets closed before a response is returned.
req := lnrpc.StopRequest{}
ctxt, cancel := context.WithCancel(context.Background())
defer cancel()
err := wait.NoError(func() error {
_, err := hn.RPC.LN.StopDaemon(ctxt, &req)
if err == nil {
return nil
}
// If the connection is already closed, we can exit
// early as the node has already been shut down in the
// test, e.g., in etcd leader health check test.
if strings.Contains(err.Error(), "connection refused") {
return nil
}
return err
}, wait.DefaultTimeout)
if err != nil {
return fmt.Errorf("shutdown timeout: %w", err)
}
// Wait for goroutines to be finished.
done := make(chan struct{})
go func() {
hn.Watcher.wg.Wait()
close(done)
hn.Watcher = nil
}()
// If the goroutines fail to finish before timeout, we'll print
// the error to console and continue.
select {
case <-time.After(wait.DefaultTimeout):
hn.printErrf("timeout on wait group")
case <-done:
}
} else {
// If the rpc clients are not initiated, we'd kill the process
// manually.
hn.printErrf("found nil RPC clients")
if err := hn.Kill(); err != nil {
// Skip the error if the process is already dead.
if !strings.Contains(
err.Error(), "process already finished",
) {
return fmt.Errorf("killing process got: %w",
err)
}
}
}
// Close any attempts at further grpc connections.
if hn.conn != nil {
if err := hn.CloseConn(); err != nil {
return err
}
}
// Wait for lnd process to exit in the end.
return hn.WaitForProcessExit()
}
// CloseConn closes the grpc connection.
func (hn *HarnessNode) CloseConn() error {
err := status.Code(hn.conn.Close())
switch err {
case codes.OK:
return nil
// When the context is canceled above, we might get the
// following error as the context is no longer active.
case codes.Canceled:
return nil
case codes.Unknown:
return fmt.Errorf("unknown error attempting to stop "+
"grpc client: %v", err)
default:
return fmt.Errorf("error attempting to stop "+
"grpc client: %v", err)
}
}
// Shutdown stops the active lnd process and cleans up any temporary
// directories created along the way.
func (hn *HarnessNode) Shutdown() error {
if err := hn.Stop(); err != nil {
return err
}
if err := hn.cleanup(); err != nil {
return err
}
return nil
}
// Kill kills the lnd process.
func (hn *HarnessNode) Kill() error {
return hn.cmd.Process.Kill()
}
// KillAndWait kills the lnd process and waits for it to finish.
func (hn *HarnessNode) KillAndWait() error {
err := hn.cmd.Process.Kill()
if err != nil {
return err
}
_, err = hn.cmd.Process.Wait()
return err
}
// printErrf prints an error to the console.
func (hn *HarnessNode) printErrf(format string, a ...interface{}) {
fmt.Printf("%v: itest error from [%s:%s]: %s\n", //nolint:forbidigo
time.Now().UTC(), hn.Cfg.LogFilenamePrefix, hn.Cfg.Name,
fmt.Sprintf(format, a...))
}
// BackupDB creates a backup of the current database.
func (hn *HarnessNode) BackupDB() error {
if hn.Cfg.backupDBDir != "" {
return fmt.Errorf("backup already created")
}
if hn.Cfg.postgresDBName != "" {
// Backup database.
backupDBName := hn.Cfg.postgresDBName + "_backup"
err := executePgQuery(
"CREATE DATABASE " + backupDBName + " WITH TEMPLATE " +
hn.Cfg.postgresDBName,
)
if err != nil {
return err
}
} else {
// Backup files.
tempDir, err := os.MkdirTemp("", "past-state")
if err != nil {
return fmt.Errorf("unable to create temp db folder: %w",
err)
}
if err := copyAll(tempDir, hn.Cfg.DBDir()); err != nil {
return fmt.Errorf("unable to copy database files: %w",
err)
}
hn.Cfg.backupDBDir = tempDir
}
return nil
}
// RestoreDB restores a database backup.
func (hn *HarnessNode) RestoreDB() error {
if hn.Cfg.postgresDBName != "" {
// Restore database.
backupDBName := hn.Cfg.postgresDBName + "_backup"
err := executePgQuery(
"DROP DATABASE " + hn.Cfg.postgresDBName,
)
if err != nil {
return err
}
err = executePgQuery(
"ALTER DATABASE " + backupDBName + " RENAME TO " +
hn.Cfg.postgresDBName,
)
if err != nil {
return err
}
} else {
// Restore files.
if hn.Cfg.backupDBDir == "" {
return fmt.Errorf("no database backup created")
}
err := copyAll(hn.Cfg.DBDir(), hn.Cfg.backupDBDir)
if err != nil {
return fmt.Errorf("unable to copy database files: %w",
err)
}
if err := os.RemoveAll(hn.Cfg.backupDBDir); err != nil {
return fmt.Errorf("unable to remove backup dir: %w",
err)
}
hn.Cfg.backupDBDir = ""
}
return nil
}
// UpdateGlobalPolicy updates a node's global channel policy.
func (hn *HarnessNode) UpdateGlobalPolicy(policy *lnrpc.RoutingPolicy) {
updateFeeReq := &lnrpc.PolicyUpdateRequest{
BaseFeeMsat: policy.FeeBaseMsat,
FeeRate: float64(policy.FeeRateMilliMsat) /
float64(1_000_000),
TimeLockDelta: policy.TimeLockDelta,
Scope: &lnrpc.PolicyUpdateRequest_Global{Global: true},
MaxHtlcMsat: policy.MaxHtlcMsat,
}
hn.RPC.UpdateChannelPolicy(updateFeeReq)
}
func postgresDatabaseDsn(dbName string) string {
return fmt.Sprintf(postgresDsn, dbName)
}
// createTempPgDB creates a temp postgres database.
func createTempPgDB() (string, error) {
// Create random database name.
randBytes := make([]byte, 8)
_, err := rand.Read(randBytes)
if err != nil {
return "", err
}
dbName := "itest_" + hex.EncodeToString(randBytes)
// Create database.
err = executePgQuery("CREATE DATABASE " + dbName)
if err != nil {
return "", err
}
return dbName, nil
}
// executePgQuery executes a SQL statement in a postgres db.
func executePgQuery(query string) error {
pool, err := pgxpool.Connect(
context.Background(),
postgresDatabaseDsn("postgres"),
)
if err != nil {
return fmt.Errorf("unable to connect to database: %w", err)
}
defer pool.Close()
_, err = pool.Exec(context.Background(), query)
return err
}
// renameFile is a helper to rename (log) files created during integration
// tests.
func renameFile(fromFileName, toFileName string) {
err := os.Rename(fromFileName, toFileName)
if err != nil {
fmt.Printf("could not rename %s to %s: %v\n", // nolint:forbidigo
fromFileName, toFileName, err)
}
}
// getFinalizedLogFilePrefix returns the finalize log filename.
func getFinalizedLogFilePrefix(hn *HarnessNode) string {
pubKeyHex := hex.EncodeToString(
hn.PubKey[:logPubKeyBytes],
)
return fmt.Sprintf("%s/%d-%s-%s-%s", GetLogDir(), hn.Cfg.NodeID,
hn.Cfg.LogFilenamePrefix, hn.Cfg.Name, pubKeyHex)
}
// finalizeLogfile makes sure the log file cleanup function is initialized,
// even if no log file is created.
func finalizeLogfile(hn *HarnessNode) string {
// Exit early if there's no log file.
if hn.logFile == nil {
return ""
}
hn.logFile.Close()
// If logoutput flag is not set, return early.
if !*logOutput {
return ""
}
newFileName := fmt.Sprintf("%v.log", getFinalizedLogFilePrefix(hn))
renameFile(hn.filename, newFileName)
return newFileName
}
// assertNodeShutdown asserts that the node has shut down properly by checking
// the last lines of the log file for the shutdown message "Shutdown complete".
func assertNodeShutdown(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
// Read more than one line to make sure we get the last line.
// const linesSize = 200
//
// NOTE: Reading 200 bytes of lines should be more than enough to find
// the `Shutdown complete` message. However, this is only true if the
// message is printed the last, which means `lnd` will properly wait
// for all its subsystems to shut down before exiting. Unfortunately
// there is at least one bug in the shutdown process where we don't
// wait for the chain backend to fully quit first, which can be easily
// reproduced by turning on `RPCC=trace` and use a linesSize of 200.
//
// TODO(yy): fix the shutdown process and remove this workaround by
// refactoring the lnd to use only one rpcclient, which requires quite
// some work on the btcwallet front.
const linesSize = 1000
buf := make([]byte, linesSize)
stat, statErr := file.Stat()
if statErr != nil {
return err
}
start := stat.Size() - linesSize
_, err = file.ReadAt(buf, start)
if err != nil {
return err
}
// Exit early if the shutdown line is found.
if bytes.Contains(buf, []byte("Shutdown complete")) {
return nil
}
// For etcd tests, we need to check for the line where the node is
// blocked at wallet unlock since we are testing how such a behavior is
// handled by etcd.
if bytes.Contains(buf, []byte("wallet and unlock")) {
return nil
}
return fmt.Errorf("node did not shut down properly: found log "+
"lines: %s", buf)
}
// finalizeEtcdLog saves the etcd log files when test ends.
func finalizeEtcdLog(hn *HarnessNode) {
// Exit early if this is not etcd backend.
if hn.Cfg.DBBackend != BackendEtcd {
return
}
etcdLogFileName := fmt.Sprintf("%s/etcd.log", hn.Cfg.LogDir)
newEtcdLogFileName := fmt.Sprintf("%v-etcd.log",
getFinalizedLogFilePrefix(hn),
)
renameFile(etcdLogFileName, newEtcdLogFileName)
}
// addLogFile creates log files used by this node.
func addLogFile(hn *HarnessNode) error {
var fileName string
dir := GetLogDir()
fileName = fmt.Sprintf("%s/%d-%s-%s-%s.log", dir, hn.Cfg.NodeID,
hn.Cfg.LogFilenamePrefix, hn.Cfg.Name,
hex.EncodeToString(hn.PubKey[:logPubKeyBytes]))
// If the node's PubKey is not yet initialized, create a temporary file
// name. Later, after the PubKey has been initialized, the file can be
// moved to its final name with the PubKey included.
if bytes.Equal(hn.PubKey[:4], []byte{0, 0, 0, 0}) {
fileName = fmt.Sprintf("%s/%d-%s-%s-tmp__.log", dir,
hn.Cfg.NodeID, hn.Cfg.LogFilenamePrefix,
hn.Cfg.Name)
}
// Create file if not exists, otherwise append.
file, err := os.OpenFile(fileName,
os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
return err
}
// Pass node's stderr to both errb and the file.
w := io.MultiWriter(hn.cmd.Stderr, file)
hn.cmd.Stderr = w
// Pass the node's stdout only to the file.
hn.cmd.Stdout = file
// Let the node keep a reference to this file, such that we can add to
// it if necessary.
hn.logFile = file
hn.filename = fileName
return nil
}
// copyAll copies all files and directories from srcDir to dstDir recursively.
// Note that this function does not support links.
func copyAll(dstDir, srcDir string) error {
entries, err := os.ReadDir(srcDir)
if err != nil {
return err
}
for _, entry := range entries {
srcPath := filepath.Join(srcDir, entry.Name())
dstPath := filepath.Join(dstDir, entry.Name())
info, err := os.Stat(srcPath)
if err != nil {
return err
}
if info.IsDir() {
err := os.Mkdir(dstPath, info.Mode())
if err != nil && !os.IsExist(err) {
return err
}
err = copyAll(dstPath, srcPath)
if err != nil {
return err
}
} else if err := CopyFile(dstPath, srcPath); err != nil {
return err
}
}
return nil
}
package node
import (
"encoding/json"
"fmt"
"math"
"time"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lntest/rpc"
"github.com/lightningnetwork/lnd/lnutils"
)
type (
// PolicyUpdate defines a type to store channel policy updates for a
// given advertisingNode. It has the format,
// {"advertisingNode": [policy1, policy2, ...]}.
PolicyUpdate map[string][]*PolicyUpdateInfo
// policyUpdateMap defines a type to store channel policy updates. It
// has the format,
// {
// "chanPoint1": {
// "advertisingNode1": [
// policy1, policy2, ...
// ],
// "advertisingNode2": [
// policy1, policy2, ...
// ]
// },
// "chanPoint2": ...
// }.
policyUpdateMap map[string]map[string][]*lnrpc.RoutingPolicy
)
// PolicyUpdateInfo stores the RoutingPolicy plus the connecting node info.
type PolicyUpdateInfo struct {
*lnrpc.RoutingPolicy
// ConnectingNode specifies the node that is connected with the
// advertising node.
ConnectingNode string `json:"connecting_node"`
// Timestamp records the time the policy update is made.
Timestamp time.Time `json:"timestamp"`
}
// OpenChannelUpdate stores the open channel updates.
type OpenChannelUpdate struct {
// AdvertisingNode specifies the node that advertised this update.
AdvertisingNode string `json:"advertising_node"`
// ConnectingNode specifies the node that is connected with the
// advertising node.
ConnectingNode string `json:"connecting_node"`
// Timestamp records the time the policy update is made.
Timestamp time.Time `json:"timestamp"`
}
// openChannelCount stores the total number of channel related counts.
type openChannelCount struct {
Active int
Inactive int
Pending int
Public int
Private int
NumUpdates uint64
}
// closedChannelCount stores the total number of closed, waiting and pending
// force close channels.
type closedChannelCount struct {
PendingForceClose int
WaitingClose int
Closed int
}
// utxoCount counts the total confirmed and unconfirmed UTXOs.
type utxoCount struct {
Confirmed int
Unconfirmed int
}
// edgeCount counts the total and public edges.
type edgeCount struct {
Total int
Public int
}
// paymentCount counts the complete(settled/failed) and incomplete payments.
type paymentCount struct {
Total int
Completed int
LastIndexOffset uint64
}
// invoiceCount counts the complete(settled/failed) and incomplete invoices.
type invoiceCount struct {
Total int
Completed int
LastIndexOffset uint64
}
// walletBalance provides a summary over balances related the node's wallet.
type walletBalance struct {
TotalBalance int64
ConfirmedBalance int64
UnconfirmedBalance int64
AccountBalance map[string]*lnrpc.WalletAccountBalance
}
// State records the current state for a given node. It provides a simple count
// over the node so that the test can track its state. For a channel-specific
// state check, use dedicated function to query the channel as each channel is
// meant to be unique.
type State struct {
// rpc is the RPC clients used for the current node.
rpc *rpc.HarnessRPC
// OpenChannel gives the summary of open channel related counts.
OpenChannel openChannelCount
// CloseChannel gives the summary of close channel related counts.
CloseChannel closedChannelCount
// Wallet gives the summary of the wallet balance.
Wallet walletBalance
// HTLC counts the total active HTLCs.
HTLC int
// Edge counts the total private/public edges.
Edge edgeCount
// ChannelUpdate counts the total channel updates seen from the graph
// subscription.
ChannelUpdate int
// NodeUpdate counts the total node announcements seen from the graph
// subscription.
NodeUpdate int
// UTXO counts the total active UTXOs.
UTXO utxoCount
// Payment counts the total payment of the node.
Payment paymentCount
// Invoice counts the total invoices made by the node.
Invoice invoiceCount
// openChans records each opened channel and how many times it has
// heard the announcements from its graph subscription.
openChans *lnutils.SyncMap[wire.OutPoint, []*OpenChannelUpdate]
// closedChans records each closed channel and its close channel update
// message received from its graph subscription.
closedChans *lnutils.SyncMap[wire.OutPoint, *lnrpc.ClosedChannelUpdate]
// numChanUpdates records the number of channel updates seen by each
// channel.
numChanUpdates *lnutils.SyncMap[wire.OutPoint, int]
// nodeUpdates records the node announcements seen by each node.
nodeUpdates *lnutils.SyncMap[string, []*lnrpc.NodeUpdate]
// policyUpdates defines a type to store channel policy updates. It has
// the format,
// {
// "chanPoint1": {
// "advertisingNode1": [
// policy1, policy2, ...
// ],
// "advertisingNode2": [
// policy1, policy2, ...
// ]
// },
// "chanPoint2": ...
// }
policyUpdates *lnutils.SyncMap[wire.OutPoint, PolicyUpdate]
}
// newState initialize a new state with every field being set to its zero
// value.
func newState(rpc *rpc.HarnessRPC) *State {
return &State{
rpc: rpc,
openChans: &lnutils.SyncMap[
wire.OutPoint, []*OpenChannelUpdate,
]{},
closedChans: &lnutils.SyncMap[
wire.OutPoint, *lnrpc.ClosedChannelUpdate,
]{},
numChanUpdates: &lnutils.SyncMap[wire.OutPoint, int]{},
nodeUpdates: &lnutils.SyncMap[string, []*lnrpc.NodeUpdate]{},
policyUpdates: &lnutils.SyncMap[wire.OutPoint, PolicyUpdate]{},
}
}
// updateChannelStats gives the stats on open channel related fields.
func (s *State) updateChannelStats() {
req := &lnrpc.ListChannelsRequest{}
resp := s.rpc.ListChannels(req)
for _, channel := range resp.Channels {
if channel.Active {
s.OpenChannel.Active++
} else {
s.OpenChannel.Inactive++
}
if channel.Private {
s.OpenChannel.Private++
} else {
s.OpenChannel.Public++
}
s.OpenChannel.NumUpdates += channel.NumUpdates
s.HTLC += len(channel.PendingHtlcs)
}
}
// updateCloseChannelStats gives the stats on close channel related fields.
func (s *State) updateCloseChannelStats() {
resp := s.rpc.PendingChannels()
s.CloseChannel.PendingForceClose += len(
resp.PendingForceClosingChannels,
)
s.CloseChannel.WaitingClose += len(resp.WaitingCloseChannels)
closeReq := &lnrpc.ClosedChannelsRequest{}
closed := s.rpc.ClosedChannels(closeReq)
s.CloseChannel.Closed += len(closed.Channels)
s.OpenChannel.Pending += len(resp.PendingOpenChannels)
}
// updatePaymentStats counts the total payments made.
func (s *State) updatePaymentStats() {
req := &lnrpc.ListPaymentsRequest{
IndexOffset: s.Payment.LastIndexOffset,
}
resp := s.rpc.ListPayments(req)
// Exit early when the there's no payment.
//
// NOTE: we need to exit early here because when there's no invoice the
// `LastOffsetIndex` will be zero.
if len(resp.Payments) == 0 {
return
}
s.Payment.LastIndexOffset = resp.LastIndexOffset
for _, payment := range resp.Payments {
if payment.Status == lnrpc.Payment_FAILED ||
payment.Status == lnrpc.Payment_SUCCEEDED {
s.Payment.Completed++
}
}
s.Payment.Total += len(resp.Payments)
}
// updateInvoiceStats counts the total invoices made.
func (s *State) updateInvoiceStats() {
req := &lnrpc.ListInvoiceRequest{
NumMaxInvoices: math.MaxUint64,
IndexOffset: s.Invoice.LastIndexOffset,
}
resp := s.rpc.ListInvoices(req)
// Exit early when the there's no invoice.
//
// NOTE: we need to exit early here because when there's no invoice the
// `LastOffsetIndex` will be zero.
if len(resp.Invoices) == 0 {
return
}
s.Invoice.LastIndexOffset = resp.LastIndexOffset
for _, invoice := range resp.Invoices {
if invoice.State == lnrpc.Invoice_SETTLED ||
invoice.State == lnrpc.Invoice_CANCELED {
s.Invoice.Completed++
}
}
s.Invoice.Total += len(resp.Invoices)
}
// updateUTXOStats counts the total UTXOs made.
func (s *State) updateUTXOStats() {
req := &walletrpc.ListUnspentRequest{}
resp := s.rpc.ListUnspent(req)
for _, utxo := range resp.Utxos {
if utxo.Confirmations > 0 {
s.UTXO.Confirmed++
} else {
s.UTXO.Unconfirmed++
}
}
}
// updateEdgeStats counts the total edges.
func (s *State) updateEdgeStats() {
// filterDisabled is a helper closure that filters out disabled
// channels.
filterDisabled := func(edge *lnrpc.ChannelEdge) bool {
if edge.Node1Policy != nil && edge.Node1Policy.Disabled {
return false
}
if edge.Node2Policy != nil && edge.Node2Policy.Disabled {
return false
}
return true
}
req := &lnrpc.ChannelGraphRequest{IncludeUnannounced: true}
resp := s.rpc.DescribeGraph(req)
s.Edge.Total = len(fn.Filter(resp.Edges, filterDisabled))
req = &lnrpc.ChannelGraphRequest{IncludeUnannounced: false}
resp = s.rpc.DescribeGraph(req)
s.Edge.Public = len(fn.Filter(resp.Edges, filterDisabled))
}
// updateWalletBalance creates stats for the node's wallet balance.
func (s *State) updateWalletBalance() {
resp := s.rpc.WalletBalance()
s.Wallet.TotalBalance = resp.TotalBalance
s.Wallet.ConfirmedBalance = resp.ConfirmedBalance
s.Wallet.UnconfirmedBalance = resp.UnconfirmedBalance
s.Wallet.AccountBalance = resp.AccountBalance
}
// updateState updates the internal state of the node.
func (s *State) updateState() {
s.updateChannelStats()
s.updateCloseChannelStats()
s.updatePaymentStats()
s.updateInvoiceStats()
s.updateUTXOStats()
s.updateEdgeStats()
s.updateWalletBalance()
}
// String encodes the node's state for debugging.
func (s *State) String() string {
stateBytes, err := json.MarshalIndent(s, "", "\t")
if err != nil {
return fmt.Sprintf("\n encode node state with err: %v", err)
}
return fmt.Sprintf("\n%s", stateBytes)
}
// resetEphermalStates resets the current state with a new HarnessRPC and empty
// private fields which are used to track state only valid for the last test.
func (s *State) resetEphermalStates(rpc *rpc.HarnessRPC) {
s.rpc = rpc
// Reset ephermal states which are used to record info from finished
// tests.
s.openChans = &lnutils.SyncMap[wire.OutPoint, []*OpenChannelUpdate]{}
s.closedChans = &lnutils.SyncMap[
wire.OutPoint, *lnrpc.ClosedChannelUpdate,
]{}
s.numChanUpdates = &lnutils.SyncMap[wire.OutPoint, int]{}
s.nodeUpdates = &lnutils.SyncMap[string, []*lnrpc.NodeUpdate]{}
s.policyUpdates = &lnutils.SyncMap[wire.OutPoint, PolicyUpdate]{}
}
package node
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"sync"
"time"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntest/rpc"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lnutils"
)
type chanWatchType uint8
const (
// watchOpenChannel specifies that this is a request to watch an open
// channel event.
watchOpenChannel chanWatchType = iota
// watchCloseChannel specifies that this is a request to watch a close
// channel event.
watchCloseChannel
// watchPolicyUpdate specifies that this is a request to watch a policy
// update event.
watchPolicyUpdate
)
// chanWatchRequest is a request to the lightningNetworkWatcher to be notified
// once it's detected within the test Lightning Network, that a channel has
// either been added or closed.
type chanWatchRequest struct {
chanPoint wire.OutPoint
chanWatchType chanWatchType
eventChan chan struct{}
advertisingNode string
policy *lnrpc.RoutingPolicy
includeUnannounced bool
// handled is a channel that will be closed once the request has been
// handled by the topologyWatcher goroutine.
handled chan struct{}
}
// nodeWatcher is a topology watcher for a HarnessNode. It keeps track of all
// the topology updates seen in a given node, including NodeUpdate,
// ChannelEdgeUpdate, and ClosedChannelUpdate.
type nodeWatcher struct {
// rpc is the RPC clients used for the current node.
rpc *rpc.HarnessRPC
// state is the node's current state.
state *State
// chanWatchRequests receives a request for watching a particular event
// for a given channel.
chanWatchRequests chan *chanWatchRequest
// For each outpoint, we'll track an integer which denotes the number
// of edges seen for that channel within the network. When this number
// reaches 2, then it means that both edge advertisements has
// propagated through the network.
openChanWatchers *lnutils.SyncMap[wire.OutPoint, []chan struct{}]
closeChanWatchers *lnutils.SyncMap[wire.OutPoint, []chan struct{}]
wg sync.WaitGroup
}
func newNodeWatcher(rpc *rpc.HarnessRPC, state *State) *nodeWatcher {
return &nodeWatcher{
rpc: rpc,
state: state,
chanWatchRequests: make(chan *chanWatchRequest, 100),
openChanWatchers: &lnutils.SyncMap[
wire.OutPoint, []chan struct{},
]{},
closeChanWatchers: &lnutils.SyncMap[
wire.OutPoint, []chan struct{},
]{},
}
}
// GetNumChannelUpdates reads the num of channel updates inside a lock and
// returns the value.
func (nw *nodeWatcher) GetNumChannelUpdates(op wire.OutPoint) int {
result, _ := nw.state.numChanUpdates.Load(op)
return result
}
// GetPolicyUpdates returns the node's policyUpdates state.
func (nw *nodeWatcher) GetPolicyUpdates(op wire.OutPoint) PolicyUpdate {
result, _ := nw.state.policyUpdates.Load(op)
return result
}
// GetNodeUpdates reads the node updates inside a lock and returns the value.
func (nw *nodeWatcher) GetNodeUpdates(pubkey string) []*lnrpc.NodeUpdate {
result, _ := nw.state.nodeUpdates.Load(pubkey)
return result
}
// WaitForNumChannelUpdates will block until a given number of updates has been
// seen in the node's network topology.
func (nw *nodeWatcher) WaitForNumChannelUpdates(op wire.OutPoint,
expected int) error {
checkNumUpdates := func() error {
num := nw.GetNumChannelUpdates(op)
if num >= expected {
return nil
}
return fmt.Errorf("timeout waiting for num channel updates, "+
"want %d, got %d", expected, num)
}
return wait.NoError(checkNumUpdates, wait.DefaultTimeout)
}
// WaitForNumNodeUpdates will block until a given number of node updates has
// been seen in the node's network topology.
func (nw *nodeWatcher) WaitForNumNodeUpdates(pubkey string,
expected int) ([]*lnrpc.NodeUpdate, error) {
updates := make([]*lnrpc.NodeUpdate, 0)
checkNumUpdates := func() error {
updates = nw.GetNodeUpdates(pubkey)
num := len(updates)
if num >= expected {
return nil
}
return fmt.Errorf("timeout waiting for num node updates, "+
"want %d, got %d", expected, num)
}
err := wait.NoError(checkNumUpdates, wait.DefaultTimeout)
return updates, err
}
// WaitForChannelOpen will block until a channel with the target outpoint is
// seen as being fully advertised within the network. A channel is considered
// "fully advertised" once both of its directional edges has been advertised in
// the node's network topology.
func (nw *nodeWatcher) WaitForChannelOpen(chanPoint *lnrpc.ChannelPoint) error {
op := nw.rpc.MakeOutpoint(chanPoint)
eventChan := make(chan struct{})
nw.chanWatchRequests <- &chanWatchRequest{
chanPoint: op,
eventChan: eventChan,
chanWatchType: watchOpenChannel,
handled: make(chan struct{}),
}
timer := time.After(wait.DefaultTimeout)
select {
case <-eventChan:
return nil
case <-timer:
updates, err := syncMapToJSON(&nw.state.openChans.Map)
if err != nil {
return err
}
return fmt.Errorf("channel:%s not heard before timeout: "+
"node has heard: %s", op, updates)
}
}
// WaitForChannelClose will block until a channel with the target outpoint is
// seen as closed within the node's network topology. A channel is considered
// closed once a transaction spending the funding outpoint is seen within a
// confirmed block.
func (nw *nodeWatcher) WaitForChannelClose(
chanPoint *lnrpc.ChannelPoint) (*lnrpc.ClosedChannelUpdate, error) {
op := nw.rpc.MakeOutpoint(chanPoint)
eventChan := make(chan struct{})
nw.chanWatchRequests <- &chanWatchRequest{
chanPoint: op,
eventChan: eventChan,
chanWatchType: watchCloseChannel,
handled: make(chan struct{}),
}
timer := time.After(wait.DefaultTimeout)
select {
case <-eventChan:
closedChan, ok := nw.state.closedChans.Load(op)
if !ok {
return nil, fmt.Errorf("channel:%s expected to find "+
"a closed channel in node's state:%s", op,
nw.state)
}
return closedChan, nil
case <-timer:
return nil, fmt.Errorf("channel:%s not closed before timeout: "+
"%s", op, nw.state)
}
}
// WaitForChannelPolicyUpdate will block until a channel policy with the target
// outpoint and advertisingNode is seen within the network.
func (nw *nodeWatcher) WaitForChannelPolicyUpdate(
advertisingNode *HarnessNode, policy *lnrpc.RoutingPolicy,
chanPoint *lnrpc.ChannelPoint, includeUnannounced bool) error {
op := nw.rpc.MakeOutpoint(chanPoint)
ticker := time.NewTicker(wait.PollInterval)
timer := time.After(wait.DefaultTimeout)
defer ticker.Stop()
// onTimeout is a helper function that will be called in case the
// expected policy is not found before the timeout.
onTimeout := func() error {
expected, err := json.MarshalIndent(policy, "", "\t")
if err != nil {
return fmt.Errorf("encode policy err: %w", err)
}
policies, err := syncMapToJSON(&nw.state.policyUpdates.Map)
if err != nil {
return err
}
return fmt.Errorf("policy not updated before timeout:"+
"\nchannel: %v \nadvertisingNode: %s:%v"+
"\nwant policy:%s\nhave updates:%s", op,
advertisingNode.Name(), advertisingNode.PubKeyStr,
expected, policies)
}
var eventChan = make(chan struct{})
for {
select {
// Send a watch request every second.
case <-ticker.C:
// Did the event chan close in the meantime? We want to
// avoid a "close of closed channel" panic since we're
// re-using the same event chan for multiple requests.
select {
case <-eventChan:
return nil
default:
}
var handled = make(chan struct{})
nw.chanWatchRequests <- &chanWatchRequest{
chanPoint: op,
eventChan: eventChan,
chanWatchType: watchPolicyUpdate,
policy: policy,
advertisingNode: advertisingNode.PubKeyStr,
includeUnannounced: includeUnannounced,
handled: handled,
}
// We wait for the topologyWatcher to signal that
// it has completed the handling of the request so that
// we don't send a new request before the previous one
// has been processed as this could lead to a double
// closure of the eventChan channel.
select {
case <-handled:
case <-timer:
return onTimeout()
}
case <-eventChan:
return nil
case <-timer:
return onTimeout()
}
}
}
// syncMapToJSON is a helper function that creates json bytes from the sync.Map
// used in the node. Expect the sync.Map to have map[string]interface.
func syncMapToJSON(state *sync.Map) ([]byte, error) {
m := map[string]interface{}{}
state.Range(func(k, v interface{}) bool {
op := k.(wire.OutPoint)
m[op.String()] = v
return true
})
policies, err := json.MarshalIndent(m, "", "\t")
if err != nil {
return nil, fmt.Errorf("encode polices err: %w", err)
}
return policies, nil
}
// topologyWatcher is a goroutine which is able to dispatch notifications once
// it has been observed that a target channel has been closed or opened within
// the network. In order to dispatch these notifications, the
// GraphTopologySubscription client exposed as part of the gRPC interface is
// used.
//
// NOTE: must be run as a goroutine.
func (nw *nodeWatcher) topologyWatcher(ctxb context.Context,
started chan error) {
graphUpdates := make(chan *lnrpc.GraphTopologyUpdate)
client, err := nw.newTopologyClient(ctxb)
started <- err
// Exit if there's an error.
if err != nil {
return
}
// Start a goroutine to receive graph updates.
nw.wg.Add(1)
go func() {
defer nw.wg.Done()
// With the client being created, we now start receiving the
// updates.
err = nw.receiveTopologyClientStream(ctxb, client, graphUpdates)
if err != nil {
started <- fmt.Errorf("receiveTopologyClientStream "+
"got err: %v", err)
}
}()
for {
select {
// A new graph update has just been received, so we'll examine
// the current set of registered clients to see if we can
// dispatch any requests.
case graphUpdate := <-graphUpdates:
nw.handleChannelEdgeUpdates(graphUpdate.ChannelUpdates)
nw.handleClosedChannelUpdate(graphUpdate.ClosedChans)
nw.handleNodeUpdates(graphUpdate.NodeUpdates)
// A new watch request, has just arrived. We'll either be able
// to dispatch immediately, or need to add the client for
// processing later.
case watchRequest := <-nw.chanWatchRequests:
switch watchRequest.chanWatchType {
case watchOpenChannel:
// TODO(roasbeef): add update type also, checks
// for multiple of 2
nw.handleOpenChannelWatchRequest(watchRequest)
case watchCloseChannel:
nw.handleCloseChannelWatchRequest(watchRequest)
case watchPolicyUpdate:
nw.handlePolicyUpdateWatchRequest(watchRequest)
}
// Signal to the caller that the request has been
// handled.
close(watchRequest.handled)
case <-ctxb.Done():
return
}
}
}
func (nw *nodeWatcher) handleNodeUpdates(updates []*lnrpc.NodeUpdate) {
for _, nodeUpdate := range updates {
nw.updateNodeStateNodeUpdates(nodeUpdate)
}
}
// handleChannelEdgeUpdates takes a series of channel edge updates, extracts
// the outpoints, and saves them to harness node's internal state.
func (nw *nodeWatcher) handleChannelEdgeUpdates(
updates []*lnrpc.ChannelEdgeUpdate) {
// For each new channel, we'll increment the number of edges seen by
// one.
for _, newChan := range updates {
op := nw.rpc.MakeOutpoint(newChan.ChanPoint)
// Update the num of channel updates.
nw.updateNodeStateNumChanUpdates(op)
// Update the open channels.
nw.updateNodeStateOpenChannel(op, newChan)
// Check whether there's a routing policy update. If so, save
// it to the node state.
if newChan.RoutingPolicy != nil {
nw.updateNodeStatePolicy(op, newChan)
}
}
}
// updateNodeStateNumChanUpdates updates the internal state of the node
// regarding the num of channel update seen.
func (nw *nodeWatcher) updateNodeStateNumChanUpdates(op wire.OutPoint) {
oldNum, _ := nw.state.numChanUpdates.Load(op)
nw.state.numChanUpdates.Store(op, oldNum+1)
}
// updateNodeStateNodeUpdates updates the internal state of the node regarding
// the node updates seen.
func (nw *nodeWatcher) updateNodeStateNodeUpdates(update *lnrpc.NodeUpdate) {
oldUpdates, _ := nw.state.nodeUpdates.Load(update.IdentityKey)
nw.state.nodeUpdates.Store(
update.IdentityKey, append(oldUpdates, update),
)
}
// updateNodeStateOpenChannel updates the internal state of the node regarding
// the open channels.
func (nw *nodeWatcher) updateNodeStateOpenChannel(op wire.OutPoint,
newChan *lnrpc.ChannelEdgeUpdate) {
// Load the old updates the node has heard so far.
updates, _ := nw.state.openChans.Load(op)
// Create a new update based on this newChan.
newUpdate := &OpenChannelUpdate{
AdvertisingNode: newChan.AdvertisingNode,
ConnectingNode: newChan.ConnectingNode,
Timestamp: time.Now(),
}
// Update the node's state.
updates = append(updates, newUpdate)
nw.state.openChans.Store(op, updates)
// For this new channel, if the number of edges seen is less
// than two, then the channel hasn't been fully announced yet.
if len(updates) < 2 {
return
}
// Otherwise, we'll notify all the registered watchers and
// remove the dispatched watchers.
watcherResult, loaded := nw.openChanWatchers.LoadAndDelete(op)
if !loaded {
return
}
for _, eventChan := range watcherResult {
close(eventChan)
}
}
// updateNodeStatePolicy updates the internal state of the node regarding the
// policy updates.
func (nw *nodeWatcher) updateNodeStatePolicy(op wire.OutPoint,
newChan *lnrpc.ChannelEdgeUpdate) {
// Init an empty policy map and overwrite it if the channel point can
// be found in the node's policyUpdates.
policies, ok := nw.state.policyUpdates.Load(op)
if !ok {
policies = make(PolicyUpdate)
}
node := newChan.AdvertisingNode
// Append the policy to the slice and update the node's state.
newPolicy := PolicyUpdateInfo{
newChan.RoutingPolicy, newChan.ConnectingNode, time.Now(),
}
policies[node] = append(policies[node], &newPolicy)
nw.state.policyUpdates.Store(op, policies)
}
// handleOpenChannelWatchRequest processes a watch open channel request by
// checking the number of the edges seen for a given channel point. If the
// number is no less than 2 then the channel is considered open. Otherwise, we
// will attempt to find it in its channel graph. If neither can be found, the
// request is added to a watch request list than will be handled by
// handleChannelEdgeUpdates.
func (nw *nodeWatcher) handleOpenChannelWatchRequest(req *chanWatchRequest) {
targetChan := req.chanPoint
// If this is an open request, then it can be dispatched if the number
// of edges seen for the channel is at least two.
result, _ := nw.state.openChans.Load(targetChan)
if len(result) >= 2 {
close(req.eventChan)
return
}
// Otherwise, we'll add this to the list of open channel watchers for
// this out point.
watchers, _ := nw.openChanWatchers.Load(targetChan)
nw.openChanWatchers.Store(
targetChan, append(watchers, req.eventChan),
)
}
// handleClosedChannelUpdate takes a series of closed channel updates, extracts
// the outpoints, saves them to harness node's internal state, and notifies all
// registered clients.
func (nw *nodeWatcher) handleClosedChannelUpdate(
updates []*lnrpc.ClosedChannelUpdate) {
// For each channel closed, we'll mark that we've detected a channel
// closure while lnd was pruning the channel graph.
for _, closedChan := range updates {
op := nw.rpc.MakeOutpoint(closedChan.ChanPoint)
nw.state.closedChans.Store(op, closedChan)
// As the channel has been closed, we'll notify all register
// watchers.
watchers, loaded := nw.closeChanWatchers.LoadAndDelete(op)
if !loaded {
continue
}
for _, eventChan := range watchers {
close(eventChan)
}
}
}
// handleCloseChannelWatchRequest processes a watch close channel request by
// checking whether the given channel point can be found in the node's internal
// state. If not, the request is added to a watch request list than will be
// handled by handleCloseChannelWatchRequest.
func (nw *nodeWatcher) handleCloseChannelWatchRequest(req *chanWatchRequest) {
targetChan := req.chanPoint
// If this is a close request, then it can be immediately dispatched if
// we've already seen a channel closure for this channel.
if _, ok := nw.state.closedChans.Load(targetChan); ok {
close(req.eventChan)
return
}
// Otherwise, we'll add this to the list of close channel watchers for
// this out point.
oldWatchers, _ := nw.closeChanWatchers.Load(targetChan)
nw.closeChanWatchers.Store(
targetChan, append(oldWatchers, req.eventChan),
)
}
// handlePolicyUpdateWatchRequest checks that if the expected policy can be
// found either in the node's interval state or describe graph response. If
// found, it will signal the request by closing the event channel. Otherwise it
// does nothing but returns nil.
func (nw *nodeWatcher) handlePolicyUpdateWatchRequest(req *chanWatchRequest) {
op := req.chanPoint
var policies []*PolicyUpdateInfo
// Get a list of known policies for this chanPoint+advertisingNode
// combination. Start searching in the node state first.
policyMap, ok := nw.state.policyUpdates.Load(op)
if ok {
policies, ok = policyMap[req.advertisingNode]
if !ok {
return
}
} else {
// If it cannot be found in the node state, try searching it
// from the node's DescribeGraph.
policyMap := nw.getChannelPolicies(req.includeUnannounced)
result, ok := policyMap[op.String()][req.advertisingNode]
if !ok {
return
}
for _, policy := range result {
// Use empty from node to mark it being loaded from
// DescribeGraph.
policies = append(
policies, &PolicyUpdateInfo{
policy, "", time.Now(),
},
)
}
}
// Check if the latest policy is matched.
policy := policies[len(policies)-1]
if CheckChannelPolicy(policy.RoutingPolicy, req.policy) == nil {
close(req.eventChan)
return
}
}
type topologyClient lnrpc.Lightning_SubscribeChannelGraphClient
// newTopologyClient creates a topology client.
func (nw *nodeWatcher) newTopologyClient(
ctx context.Context) (topologyClient, error) {
req := &lnrpc.GraphTopologySubscription{}
client, err := nw.rpc.LN.SubscribeChannelGraph(ctx, req)
if err != nil {
return nil, fmt.Errorf("%s: unable to create topology client: "+
"%v (%s)", nw.rpc.Name, err, time.Now().String())
}
return client, nil
}
// receiveTopologyClientStream takes a topologyClient and receives graph
// updates.
//
// NOTE: must be run as a goroutine.
func (nw *nodeWatcher) receiveTopologyClientStream(ctxb context.Context,
client topologyClient,
receiver chan *lnrpc.GraphTopologyUpdate) error {
for {
update, err := client.Recv()
switch {
case err == nil:
// Good case. We will send the update to the receiver.
case strings.Contains(err.Error(), "EOF"):
// End of subscription stream. Do nothing and quit.
return nil
case strings.Contains(err.Error(), context.Canceled.Error()):
// End of subscription stream. Do nothing and quit.
return nil
default:
// An expected error is returned, return and leave it
// to be handled by the caller.
return fmt.Errorf("graph subscription err: %w", err)
}
// Send the update or quit.
select {
case receiver <- update:
case <-ctxb.Done():
return nil
}
}
}
// getChannelPolicies queries the channel graph and formats the policies into
// the format defined in type policyUpdateMap.
func (nw *nodeWatcher) getChannelPolicies(include bool) policyUpdateMap {
req := &lnrpc.ChannelGraphRequest{IncludeUnannounced: include}
graph := nw.rpc.DescribeGraph(req)
policyUpdates := policyUpdateMap{}
for _, e := range graph.Edges {
policies := policyUpdates[e.ChanPoint]
// If the map[op] is nil, we need to initialize the map first.
if policies == nil {
policies = make(map[string][]*lnrpc.RoutingPolicy)
}
if e.Node1Policy != nil {
policies[e.Node1Pub] = append(
policies[e.Node1Pub], e.Node1Policy,
)
}
if e.Node2Policy != nil {
policies[e.Node2Pub] = append(
policies[e.Node2Pub], e.Node2Policy,
)
}
policyUpdates[e.ChanPoint] = policies
}
return policyUpdates
}
// CheckChannelPolicy checks that the policy matches the expected one.
func CheckChannelPolicy(policy, expectedPolicy *lnrpc.RoutingPolicy) error {
if policy.FeeBaseMsat != expectedPolicy.FeeBaseMsat {
return fmt.Errorf("expected base fee %v, got %v",
expectedPolicy.FeeBaseMsat, policy.FeeBaseMsat)
}
if policy.FeeRateMilliMsat != expectedPolicy.FeeRateMilliMsat {
return fmt.Errorf("expected fee rate %v, got %v",
expectedPolicy.FeeRateMilliMsat,
policy.FeeRateMilliMsat)
}
if policy.TimeLockDelta != expectedPolicy.TimeLockDelta {
return fmt.Errorf("expected time lock delta %v, got %v",
expectedPolicy.TimeLockDelta,
policy.TimeLockDelta)
}
if policy.MinHtlc != expectedPolicy.MinHtlc {
return fmt.Errorf("expected min htlc %v, got %v",
expectedPolicy.MinHtlc, policy.MinHtlc)
}
if policy.MaxHtlcMsat != expectedPolicy.MaxHtlcMsat {
return fmt.Errorf("expected max htlc %v, got %v",
expectedPolicy.MaxHtlcMsat, policy.MaxHtlcMsat)
}
if policy.InboundFeeBaseMsat != expectedPolicy.InboundFeeBaseMsat {
return fmt.Errorf("expected inbound base fee %v, got %v",
expectedPolicy.InboundFeeBaseMsat,
policy.InboundFeeBaseMsat)
}
if policy.InboundFeeRateMilliMsat !=
expectedPolicy.InboundFeeRateMilliMsat {
return fmt.Errorf("expected inbound fee rate %v, got %v",
expectedPolicy.InboundFeeRateMilliMsat,
policy.InboundFeeRateMilliMsat)
}
if policy.Disabled != expectedPolicy.Disabled {
return errors.New("edge should be disabled but isn't")
}
return nil
}
package port
import (
"fmt"
"net"
"os"
"path/filepath"
"strconv"
"sync"
"time"
"github.com/lightningnetwork/lnd/lntest/wait"
)
const (
// ListenerFormat is the format string that is used to generate local
// listener addresses.
ListenerFormat = "127.0.0.1:%d"
// defaultNodePort is the start of the range for listening ports of
// harness nodes. Ports are monotonically increasing starting from this
// number and are determined by the results of NextAvailablePort().
defaultNodePort int = 10000
// uniquePortFile is the name of the file that is used to store the
// last port that was used by a node. This is used to make sure that
// the same port is not used by multiple nodes at the same time. The
// file is located in the temp directory of a system.
uniquePortFile = "rpctest-port"
)
var (
// portFileMutex is a mutex that is used to make sure that the port file
// is not accessed by multiple goroutines of the same process at the
// same time. This is used in conjunction with the lock file to make
// sure that the port file is not accessed by multiple processes at the
// same time either. So the lock file is to guard between processes and
// the mutex is to guard between goroutines of the same process.
portFileMutex sync.Mutex
)
// NextAvailablePort returns the first port that is available for listening by a
// new node, using a lock file to make sure concurrent access for parallel tasks
// on the same system don't re-use the same port.
func NextAvailablePort() int {
portFileMutex.Lock()
defer portFileMutex.Unlock()
lockFile := filepath.Join(os.TempDir(), uniquePortFile+".lock")
timeout := time.After(wait.DefaultTimeout)
var (
lockFileHandle *os.File
err error
)
for {
// Attempt to acquire the lock file. If it already exists, wait
// for a bit and retry.
lockFileHandle, err = os.OpenFile(
lockFile, os.O_CREATE|os.O_EXCL, 0600,
)
if err == nil {
// Lock acquired.
break
}
// Wait for a bit and retry.
select {
case <-timeout:
panic("timeout waiting for lock file")
case <-time.After(10 * time.Millisecond):
}
}
// Release the lock file when we're done.
defer func() {
// Always close file first, Windows won't allow us to remove it
// otherwise.
_ = lockFileHandle.Close()
err := os.Remove(lockFile)
if err != nil {
panic(fmt.Errorf("couldn't remove lock file: %w", err))
}
}()
portFile := filepath.Join(os.TempDir(), uniquePortFile)
port, err := os.ReadFile(portFile)
if err != nil {
if !os.IsNotExist(err) {
panic(fmt.Errorf("error reading port file: %w", err))
}
port = []byte(strconv.Itoa(defaultNodePort))
}
lastPort, err := strconv.Atoi(string(port))
if err != nil {
panic(fmt.Errorf("error parsing port: %w", err))
}
// We take the next one.
lastPort++
for lastPort < 65535 {
// If there are no errors while attempting to listen on this
// port, close the socket and return it as available. While it
// could be the case that some other process picks up this port
// between the time the socket is closed, and it's reopened in
// the harness node, in practice in CI servers this seems much
// less likely than simply some other process already being
// bound at the start of the tests.
addr := fmt.Sprintf(ListenerFormat, lastPort)
l, err := net.Listen("tcp4", addr)
if err == nil {
err := l.Close()
if err == nil {
err := os.WriteFile(
portFile,
[]byte(strconv.Itoa(lastPort)), 0600,
)
if err != nil {
panic(fmt.Errorf("error updating "+
"port file: %w", err))
}
return lastPort
}
}
lastPort++
// Start from the beginning if we reached the end of the port
// range. We need to do this because the lock file now is
// persistent across runs on the same machine during the same
// boot/uptime cycle. So in order to make this work on
// developer's machines, we need to reset the port to the
// default value when we reach the end of the range.
if lastPort == 65535 {
lastPort = defaultNodePort
}
}
// No ports available? Must be a mistake.
panic("no ports available for listening")
}
// GenerateSystemUniqueListenerAddresses is a function that returns two
// listener addresses with unique ports per system and should be used to
// overwrite rpctest's default generator which is prone to use colliding ports.
func GenerateSystemUniqueListenerAddresses() (string, string) {
port1 := NextAvailablePort()
port2 := NextAvailablePort()
return fmt.Sprintf(ListenerFormat, port1),
fmt.Sprintf(ListenerFormat, port2)
}
package rpc
import (
"context"
"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
)
// =====================
// ChainKitClient related RPCs.
// =====================
// GetBestBlock makes an RPC call to chain kit client's GetBestBlock and
// asserts.
func (h *HarnessRPC) GetBestBlock(
req *chainrpc.GetBestBlockRequest) *chainrpc.GetBestBlockResponse {
if req == nil {
req = &chainrpc.GetBestBlockRequest{}
}
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.ChainKit.GetBestBlock(ctxt, req)
h.NoError(err, "GetBestBlock")
return resp
}
// GetBlock makes an RPC call to chain kit client's GetBlock and asserts.
func (h *HarnessRPC) GetBlock(
req *chainrpc.GetBlockRequest) *chainrpc.GetBlockResponse {
if req == nil {
req = &chainrpc.GetBlockRequest{}
}
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.ChainKit.GetBlock(ctxt, req)
h.NoError(err, "GetBlock")
return resp
}
// GetBlockHeader makes an RPC call to chain kit client's GetBlockHeader and
// asserts.
func (h *HarnessRPC) GetBlockHeader(
req *chainrpc.GetBlockHeaderRequest) *chainrpc.GetBlockHeaderResponse {
if req == nil {
req = &chainrpc.GetBlockHeaderRequest{}
}
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.ChainKit.GetBlockHeader(ctxt, req)
h.NoError(err, "GetBlockHeader")
return resp
}
// GetBlockHash makes an RPC call to chain kit client's GetBlockHash and
// asserts.
func (h *HarnessRPC) GetBlockHash(
req *chainrpc.GetBlockHashRequest) *chainrpc.GetBlockHashResponse {
if req == nil {
req = &chainrpc.GetBlockHashRequest{}
}
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.ChainKit.GetBlockHash(ctxt, req)
h.NoError(err, "GetBlockHash")
return resp
}
package rpc
import (
"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
)
// =====================
// ChainClient related RPCs.
// =====================
type ConfNtfnClient chainrpc.ChainNotifier_RegisterConfirmationsNtfnClient
// RegisterConfirmationsNtfn creates a notification client to watch a given
// transaction being confirmed.
func (h *HarnessRPC) RegisterConfirmationsNtfn(
req *chainrpc.ConfRequest) ConfNtfnClient {
// RegisterConfirmationsNtfn needs to have the context alive for the
// entire test case as the returned client will be used for send and
// receive events stream. Thus we use runCtx here instead of a timeout
// context.
client, err := h.ChainClient.RegisterConfirmationsNtfn(
h.runCtx, req,
)
h.NoError(err, "RegisterConfirmationsNtfn")
return client
}
type SpendClient chainrpc.ChainNotifier_RegisterSpendNtfnClient
// RegisterSpendNtfn creates a notification client to watch a given
// transaction being spent.
func (h *HarnessRPC) RegisterSpendNtfn(req *chainrpc.SpendRequest) SpendClient {
// RegisterSpendNtfn needs to have the context alive for the entire
// test case as the returned client will be used for send and receive
// events stream. Thus we use runCtx here instead of a timeout context.
client, err := h.ChainClient.RegisterSpendNtfn(
h.runCtx, req,
)
h.NoError(err, "RegisterSpendNtfn")
return client
}
package rpc
import (
"context"
"testing"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
"github.com/lightningnetwork/lnd/lnrpc/devrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lnrpc/neutrinorpc"
"github.com/lightningnetwork/lnd/lnrpc/peersrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc"
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
)
const (
DefaultTimeout = wait.DefaultTimeout
)
// HarnessRPC wraps all lnd's RPC clients into a single struct for easier
// access.
type HarnessRPC struct {
*testing.T
LN lnrpc.LightningClient
WalletUnlocker lnrpc.WalletUnlockerClient
Invoice invoicesrpc.InvoicesClient
Signer signrpc.SignerClient
Router routerrpc.RouterClient
WalletKit walletrpc.WalletKitClient
Watchtower watchtowerrpc.WatchtowerClient
WatchtowerClient wtclientrpc.WatchtowerClientClient
State lnrpc.StateClient
ChainClient chainrpc.ChainNotifierClient
ChainKit chainrpc.ChainKitClient
NeutrinoKit neutrinorpc.NeutrinoKitClient
Peer peersrpc.PeersClient
Dev devrpc.DevClient
// Name is the HarnessNode's name.
Name string
// runCtx is a context with cancel method. It's used to signal when the
// node needs to quit, and used as the parent context when spawning
// children contexts for RPC requests.
runCtx context.Context //nolint:containedctx
cancel context.CancelFunc
}
// NewHarnessRPC creates a new HarnessRPC with its own context inherted from
// the pass context.
func NewHarnessRPC(ctxt context.Context, t *testing.T, c *grpc.ClientConn,
name string) *HarnessRPC {
h := &HarnessRPC{
T: t,
LN: lnrpc.NewLightningClient(c),
Invoice: invoicesrpc.NewInvoicesClient(c),
Router: routerrpc.NewRouterClient(c),
WalletKit: walletrpc.NewWalletKitClient(c),
WalletUnlocker: lnrpc.NewWalletUnlockerClient(c),
Watchtower: watchtowerrpc.NewWatchtowerClient(c),
WatchtowerClient: wtclientrpc.NewWatchtowerClientClient(c),
Signer: signrpc.NewSignerClient(c),
State: lnrpc.NewStateClient(c),
ChainClient: chainrpc.NewChainNotifierClient(c),
ChainKit: chainrpc.NewChainKitClient(c),
NeutrinoKit: neutrinorpc.NewNeutrinoKitClient(c),
Peer: peersrpc.NewPeersClient(c),
Dev: devrpc.NewDevClient(c),
Name: name,
}
// Inherit parent context.
h.runCtx, h.cancel = context.WithCancel(ctxt)
return h
}
// MakeOutpoint returns the outpoint of the channel's funding transaction.
func (h *HarnessRPC) MakeOutpoint(cp *lnrpc.ChannelPoint) wire.OutPoint {
fundingTxID, err := lnrpc.GetChanPointFundingTxid(cp)
require.NoErrorf(h, err, "failed to make chanPoint", h.Name)
return wire.OutPoint{
Hash: *fundingTxID,
Index: cp.OutputIndex,
}
}
// NoError is a helper method to format the error message used in calling RPCs.
func (h *HarnessRPC) NoError(err error, operation string) {
require.NoErrorf(h, err, "%s: failed to call %s", h.Name, operation)
}
package rpc
import (
"context"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
)
// =====================
// InvoiceClient related RPCs.
// =====================
// LookupInvoiceV2 queries the node's invoices using the invoice client's
// LookupInvoiceV2.
func (h *HarnessRPC) LookupInvoiceV2(
req *invoicesrpc.LookupInvoiceMsg) *lnrpc.Invoice {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.Invoice.LookupInvoiceV2(ctxt, req)
h.NoError(err, "LookupInvoiceV2")
return resp
}
// AddHoldInvoice adds a hold invoice for the given node and asserts.
func (h *HarnessRPC) AddHoldInvoice(
r *invoicesrpc.AddHoldInvoiceRequest) *invoicesrpc.AddHoldInvoiceResp {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
invoice, err := h.Invoice.AddHoldInvoice(ctxt, r)
h.NoError(err, "AddHoldInvoice")
return invoice
}
// SettleInvoice settles a given invoice and asserts.
func (h *HarnessRPC) SettleInvoice(
preimage []byte) *invoicesrpc.SettleInvoiceResp {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
req := &invoicesrpc.SettleInvoiceMsg{Preimage: preimage}
resp, err := h.Invoice.SettleInvoice(ctxt, req)
h.NoError(err, "SettleInvoice")
return resp
}
// CancelInvoice cancels a given invoice and asserts.
func (h *HarnessRPC) CancelInvoice(
payHash []byte) *invoicesrpc.CancelInvoiceResp {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
req := &invoicesrpc.CancelInvoiceMsg{PaymentHash: payHash}
resp, err := h.Invoice.CancelInvoice(ctxt, req)
h.NoError(err, "CancelInvoice")
return resp
}
type SingleInvoiceClient invoicesrpc.Invoices_SubscribeSingleInvoiceClient
// SubscribeSingleInvoice creates a subscription client for given invoice and
// asserts its creation.
func (h *HarnessRPC) SubscribeSingleInvoice(rHash []byte) SingleInvoiceClient {
req := &invoicesrpc.SubscribeSingleInvoiceRequest{RHash: rHash}
// SubscribeSingleInvoice needs to have the context alive for the
// entire test case as the returned client will be used for send and
// receive events stream. Thus we use runCtx here instead of a timeout
// context.
client, err := h.Invoice.SubscribeSingleInvoice(h.runCtx, req)
h.NoError(err, "SubscribeSingleInvoice")
return client
}
type InvoiceHtlcModifierClient invoicesrpc.Invoices_HtlcModifierClient
// InvoiceHtlcModifier makes an RPC call to the node's RouterClient and asserts.
func (h *HarnessRPC) InvoiceHtlcModifier() (InvoiceHtlcModifierClient,
context.CancelFunc) {
// InvoiceHtlcModifier needs to have the context alive for the entire
// test case as the returned client will be used for send and receive
// events stream. Therefore, we use cancel context here instead of a
// timeout context.
ctxt, cancel := context.WithCancel(h.runCtx)
resp, err := h.Invoice.HtlcModifier(ctxt)
h.NoError(err, "InvoiceHtlcModifier")
return resp, cancel
}
package rpc
import (
"context"
"strings"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/devrpc"
"github.com/stretchr/testify/require"
)
// =====================
// LightningClient related RPCs.
// =====================
// NewAddress makes a RPC call to NewAddress and asserts.
func (h *HarnessRPC) NewAddress(
req *lnrpc.NewAddressRequest) *lnrpc.NewAddressResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.LN.NewAddress(ctxt, req)
h.NoError(err, "NewAddress")
return resp
}
// WalletBalance makes a RPC call to WalletBalance and asserts.
func (h *HarnessRPC) WalletBalance() *lnrpc.WalletBalanceResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
req := &lnrpc.WalletBalanceRequest{}
resp, err := h.LN.WalletBalance(ctxt, req)
h.NoError(err, "WalletBalance")
return resp
}
// ListPeers makes a RPC call to the node's ListPeers and asserts.
func (h *HarnessRPC) ListPeers() *lnrpc.ListPeersResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.LN.ListPeers(ctxt, &lnrpc.ListPeersRequest{})
h.NoError(err, "ListPeers")
return resp
}
// DisconnectPeer calls the DisconnectPeer RPC on a given node with a specified
// public key string and asserts there's no error.
func (h *HarnessRPC) DisconnectPeer(
pubkey string) *lnrpc.DisconnectPeerResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
req := &lnrpc.DisconnectPeerRequest{PubKey: pubkey}
resp, err := h.LN.DisconnectPeer(ctxt, req)
h.NoError(err, "DisconnectPeer")
return resp
}
// DeleteAllPayments makes a RPC call to the node's DeleteAllPayments and
// asserts.
func (h *HarnessRPC) DeleteAllPayments() {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
req := &lnrpc.DeleteAllPaymentsRequest{AllPayments: true}
_, err := h.LN.DeleteAllPayments(ctxt, req)
h.NoError(err, "DeleteAllPayments")
}
// GetInfo calls the GetInfo RPC on a given node and asserts there's no error.
func (h *HarnessRPC) GetInfo() *lnrpc.GetInfoResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
info, err := h.LN.GetInfo(ctxt, &lnrpc.GetInfoRequest{})
h.NoError(err, "GetInfo")
return info
}
// ConnectPeer makes a RPC call to ConnectPeer and asserts there's no error.
func (h *HarnessRPC) ConnectPeer(
req *lnrpc.ConnectPeerRequest) *lnrpc.ConnectPeerResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.LN.ConnectPeer(ctxt, req)
h.NoError(err, "ConnectPeer")
return resp
}
// ConnectPeerAssertErr makes a RPC call to ConnectPeer and asserts an error
// returned.
func (h *HarnessRPC) ConnectPeerAssertErr(req *lnrpc.ConnectPeerRequest) error {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.LN.ConnectPeer(ctxt, req)
require.Error(h, err, "expected an error from ConnectPeer")
return err
}
// ListChannels list the channels for the given node and asserts it's
// successful.
func (h *HarnessRPC) ListChannels(
req *lnrpc.ListChannelsRequest) *lnrpc.ListChannelsResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.LN.ListChannels(ctxt, req)
h.NoError(err, "ListChannels")
return resp
}
// PendingChannels makes a RPC request to PendingChannels and asserts there's
// no error.
func (h *HarnessRPC) PendingChannels() *lnrpc.PendingChannelsResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
pendingChansRequest := &lnrpc.PendingChannelsRequest{
IncludeRawTx: true,
}
resp, err := h.LN.PendingChannels(ctxt, pendingChansRequest)
// TODO(yy): We may get a `unable to find arbitrator` error from the
// rpc point, due to a timing issue in rpcserver,
// 1. `r.server.chanStateDB.FetchClosedChannels` fetches
// the pending force close channel.
// 2. `r.arbitratorPopulateForceCloseResp` relies on the
// channel arbitrator to get the report, and,
// 3. the arbitrator may be deleted due to the force close
// channel being resolved.
// Somewhere along the line is missing a lock to keep the data
// consistent.
//
// Return if there's no error.
if err == nil {
return resp
}
// Otherwise, give it a second shot if it's the arbitrator error.
if strings.Contains(err.Error(), "unable to find arbitrator") {
resp, err = h.LN.PendingChannels(ctxt, pendingChansRequest)
}
// It's very unlikely we'd get the arbitrator not found error again.
h.NoError(err, "PendingChannels")
return resp
}
// ClosedChannels makes a RPC call to node's ClosedChannels and asserts.
func (h *HarnessRPC) ClosedChannels(
req *lnrpc.ClosedChannelsRequest) *lnrpc.ClosedChannelsResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.LN.ClosedChannels(ctxt, req)
h.NoError(err, "ClosedChannels")
return resp
}
// ListPayments lists the node's payments and asserts.
func (h *HarnessRPC) ListPayments(
req *lnrpc.ListPaymentsRequest) *lnrpc.ListPaymentsResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.LN.ListPayments(ctxt, req)
h.NoError(err, "ListPayments")
return resp
}
// ListInvoices list the node's invoice using the request and asserts.
func (h *HarnessRPC) ListInvoices(
req *lnrpc.ListInvoiceRequest) *lnrpc.ListInvoiceResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
if req == nil {
req = &lnrpc.ListInvoiceRequest{}
}
resp, err := h.LN.ListInvoices(ctxt, req)
h.NoError(err, "ListInvoice")
return resp
}
// DescribeGraph makes a RPC call to the node's DescribeGraph and asserts. It
// takes a bool to indicate whether we want to include private edges or not.
func (h *HarnessRPC) DescribeGraph(
req *lnrpc.ChannelGraphRequest) *lnrpc.ChannelGraph {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.LN.DescribeGraph(ctxt, req)
h.NoError(err, "DescribeGraph")
return resp
}
// ChannelBalance gets the channel balance and asserts.
func (h *HarnessRPC) ChannelBalance() *lnrpc.ChannelBalanceResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
req := &lnrpc.ChannelBalanceRequest{}
resp, err := h.LN.ChannelBalance(ctxt, req)
h.NoError(err, "ChannelBalance")
return resp
}
type OpenChanClient lnrpc.Lightning_OpenChannelClient
// OpenChannel makes a rpc call to LightningClient and returns the open channel
// client.
func (h *HarnessRPC) OpenChannel(req *lnrpc.OpenChannelRequest) OpenChanClient {
stream, err := h.LN.OpenChannel(h.runCtx, req)
h.NoError(err, "OpenChannel")
return stream
}
type CloseChanClient lnrpc.Lightning_CloseChannelClient
// CloseChannel makes a rpc call to LightningClient and returns the close
// channel client.
func (h *HarnessRPC) CloseChannel(
req *lnrpc.CloseChannelRequest) CloseChanClient {
// Use runCtx here instead of a timeout context to keep the client
// alive for the entire test case.
stream, err := h.LN.CloseChannel(h.runCtx, req)
h.NoError(err, "CloseChannel")
return stream
}
// FundingStateStep makes a RPC call to FundingStateStep and asserts.
func (h *HarnessRPC) FundingStateStep(
msg *lnrpc.FundingTransitionMsg) *lnrpc.FundingStateStepResp {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.LN.FundingStateStep(ctxt, msg)
h.NoError(err, "FundingStateStep")
return resp
}
// FundingStateStepAssertErr makes a RPC call to FundingStateStep and asserts
// there's an error.
func (h *HarnessRPC) FundingStateStepAssertErr(m *lnrpc.FundingTransitionMsg) {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.LN.FundingStateStep(ctxt, m)
require.Error(h, err, "expected an error from FundingStateStep")
}
// AddInvoice adds a invoice for the given node and asserts.
func (h *HarnessRPC) AddInvoice(req *lnrpc.Invoice) *lnrpc.AddInvoiceResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
invoice, err := h.LN.AddInvoice(ctxt, req)
h.NoError(err, "AddInvoice")
return invoice
}
// AbandonChannel makes a RPC call to AbandonChannel and asserts.
func (h *HarnessRPC) AbandonChannel(
req *lnrpc.AbandonChannelRequest) *lnrpc.AbandonChannelResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.LN.AbandonChannel(ctxt, req)
h.NoError(err, "AbandonChannel")
return resp
}
// ExportAllChanBackups makes a RPC call to the node's ExportAllChannelBackups
// and asserts.
func (h *HarnessRPC) ExportAllChanBackups() *lnrpc.ChanBackupSnapshot {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
req := &lnrpc.ChanBackupExportRequest{}
chanBackup, err := h.LN.ExportAllChannelBackups(ctxt, req)
h.NoError(err, "ExportAllChannelBackups")
return chanBackup
}
// ExportChanBackup makes a RPC call to the node's ExportChannelBackup
// and asserts.
func (h *HarnessRPC) ExportChanBackup(
chanPoint *lnrpc.ChannelPoint) *lnrpc.ChannelBackup {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
req := &lnrpc.ExportChannelBackupRequest{
ChanPoint: chanPoint,
}
chanBackup, err := h.LN.ExportChannelBackup(ctxt, req)
h.NoError(err, "ExportChannelBackup")
return chanBackup
}
// RestoreChanBackups makes a RPC call to the node's RestoreChannelBackups and
// asserts.
func (h *HarnessRPC) RestoreChanBackups(
req *lnrpc.RestoreChanBackupRequest) *lnrpc.RestoreBackupResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.LN.RestoreChannelBackups(ctxt, req)
h.NoError(err, "RestoreChannelBackups")
return resp
}
type AcceptorClient lnrpc.Lightning_ChannelAcceptorClient
// ChannelAcceptor makes a RPC call to the node's ChannelAcceptor and asserts.
func (h *HarnessRPC) ChannelAcceptor() (AcceptorClient, context.CancelFunc) {
// Use runCtx here instead of a timeout context to keep the client
// alive for the entire test case.
ctxt, cancel := context.WithCancel(h.runCtx)
resp, err := h.LN.ChannelAcceptor(ctxt)
h.NoError(err, "ChannelAcceptor")
return resp, cancel
}
// SendCoins sends a given amount of money to the specified address from the
// passed node.
func (h *HarnessRPC) SendCoins(
req *lnrpc.SendCoinsRequest) *lnrpc.SendCoinsResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.LN.SendCoins(ctxt, req)
h.NoError(err, "SendCoins")
return resp
}
// SendCoinsAssertErr sends a given amount of money to the specified address
// from the passed node and asserts an error has returned.
func (h *HarnessRPC) SendCoinsAssertErr(req *lnrpc.SendCoinsRequest) error {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.LN.SendCoins(ctxt, req)
require.Error(h, err, "node %s didn't not return an error", h.Name)
return err
}
// GetTransactions makes a RPC call to GetTransactions and asserts.
func (h *HarnessRPC) GetTransactions(
req *lnrpc.GetTransactionsRequest) *lnrpc.TransactionDetails {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
if req == nil {
req = &lnrpc.GetTransactionsRequest{}
}
resp, err := h.LN.GetTransactions(ctxt, req)
h.NoError(err, "GetTransactions")
return resp
}
// SignMessage makes a RPC call to node's SignMessage and asserts.
func (h *HarnessRPC) SignMessage(msg []byte) *lnrpc.SignMessageResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
req := &lnrpc.SignMessageRequest{Msg: msg}
resp, err := h.LN.SignMessage(ctxt, req)
h.NoError(err, "SignMessage")
return resp
}
// VerifyMessage makes a RPC call to node's VerifyMessage and asserts.
func (h *HarnessRPC) VerifyMessage(msg []byte,
sig string) *lnrpc.VerifyMessageResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
req := &lnrpc.VerifyMessageRequest{Msg: msg, Signature: sig}
resp, err := h.LN.VerifyMessage(ctxt, req)
h.NoError(err, "VerifyMessage")
return resp
}
// GetRecoveryInfo uses the specified node to make a RPC call to
// GetRecoveryInfo and asserts.
func (h *HarnessRPC) GetRecoveryInfo(
req *lnrpc.GetRecoveryInfoRequest) *lnrpc.GetRecoveryInfoResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
if req == nil {
req = &lnrpc.GetRecoveryInfoRequest{}
}
resp, err := h.LN.GetRecoveryInfo(ctxt, req)
h.NoError(err, "GetRecoveryInfo")
return resp
}
// BatchOpenChannel makes a RPC call to BatchOpenChannel and asserts.
func (h *HarnessRPC) BatchOpenChannel(
req *lnrpc.BatchOpenChannelRequest) *lnrpc.BatchOpenChannelResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.LN.BatchOpenChannel(ctxt, req)
h.NoError(err, "BatchOpenChannel")
return resp
}
// BatchOpenChannelAssertErr makes a RPC call to BatchOpenChannel and asserts
// there's an error returned.
func (h *HarnessRPC) BatchOpenChannelAssertErr(
req *lnrpc.BatchOpenChannelRequest) error {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.LN.BatchOpenChannel(ctxt, req)
require.Error(h, err, "expecte batch open channel fail")
return err
}
// QueryRoutes makes a RPC call to QueryRoutes and asserts.
func (h *HarnessRPC) QueryRoutes(
req *lnrpc.QueryRoutesRequest) *lnrpc.QueryRoutesResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
routes, err := h.LN.QueryRoutes(ctxt, req)
h.NoError(err, "QueryRoutes")
return routes
}
type SendToRouteClient lnrpc.Lightning_SendToRouteClient
// SendToRoute makes a RPC call to SendToRoute and asserts.
func (h *HarnessRPC) SendToRoute() SendToRouteClient {
// SendToRoute needs to have the context alive for the entire test case
// as the returned client will be used for send and receive payment
// stream. Thus we use runCtx here instead of a timeout context.
client, err := h.LN.SendToRoute(h.runCtx)
h.NoError(err, "SendToRoute")
return client
}
// SendToRouteSync makes a RPC call to SendToRouteSync and asserts.
func (h *HarnessRPC) SendToRouteSync(
req *lnrpc.SendToRouteRequest) *lnrpc.SendResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.LN.SendToRouteSync(ctxt, req)
h.NoError(err, "SendToRouteSync")
return resp
}
// UpdateChannelPolicy makes a RPC call to UpdateChannelPolicy and asserts.
func (h *HarnessRPC) UpdateChannelPolicy(
req *lnrpc.PolicyUpdateRequest) *lnrpc.PolicyUpdateResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.LN.UpdateChannelPolicy(ctxt, req)
h.NoError(err, "UpdateChannelPolicy")
return resp
}
type InvoiceUpdateClient lnrpc.Lightning_SubscribeInvoicesClient
// SubscribeInvoices creates a subscription client for invoice events and
// asserts its creation.
//
// NOTE: make sure to subscribe an invoice as early as possible as it takes
// some time for the lnd to create the subscription client. If an invoice is
// added right after the subscription, it may be missed. However, if AddIndex
// or SettleIndex is used in the request, it will be fine as a backlog will
// always be sent.
func (h *HarnessRPC) SubscribeInvoices(
req *lnrpc.InvoiceSubscription) InvoiceUpdateClient {
// SubscribeInvoices needs to have the context alive for the
// entire test case as the returned client will be used for send and
// receive events stream. Thus we use runCtx here instead of a timeout
// context.
client, err := h.LN.SubscribeInvoices(h.runCtx, req)
h.NoError(err, "SubscribeInvoices")
return client
}
type BackupSubscriber lnrpc.Lightning_SubscribeChannelBackupsClient
// SubscribeChannelBackups creates a client to listen to channel backup stream.
func (h *HarnessRPC) SubscribeChannelBackups() BackupSubscriber {
// Use runCtx here instead of timeout context to keep the stream client
// alive.
backupStream, err := h.LN.SubscribeChannelBackups(
h.runCtx, &lnrpc.ChannelBackupSubscription{},
)
h.NoError(err, "SubscribeChannelBackups")
return backupStream
}
// VerifyChanBackup makes a RPC call to node's VerifyChanBackup and asserts.
func (h *HarnessRPC) VerifyChanBackup(
ss *lnrpc.ChanBackupSnapshot) *lnrpc.VerifyChanBackupResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.LN.VerifyChanBackup(ctxt, ss)
h.NoError(err, "VerifyChanBackup")
return resp
}
// LookupInvoice queries the node's invoices using the specified rHash.
func (h *HarnessRPC) LookupInvoice(rHash []byte) *lnrpc.Invoice {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
payHash := &lnrpc.PaymentHash{RHash: rHash}
resp, err := h.LN.LookupInvoice(ctxt, payHash)
h.NoError(err, "LookupInvoice")
return resp
}
// DecodePayReq makes a RPC call to node's DecodePayReq and asserts.
func (h *HarnessRPC) DecodePayReq(req string) *lnrpc.PayReq {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
payReq := &lnrpc.PayReqString{PayReq: req}
resp, err := h.LN.DecodePayReq(ctxt, payReq)
h.NoError(err, "DecodePayReq")
return resp
}
// ForwardingHistory makes a RPC call to the node's ForwardingHistory and
// asserts.
func (h *HarnessRPC) ForwardingHistory(
req *lnrpc.ForwardingHistoryRequest) *lnrpc.ForwardingHistoryResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
if req == nil {
req = &lnrpc.ForwardingHistoryRequest{}
}
resp, err := h.LN.ForwardingHistory(ctxt, req)
h.NoError(err, "ForwardingHistory")
return resp
}
type MiddlewareClient lnrpc.Lightning_RegisterRPCMiddlewareClient
// RegisterRPCMiddleware makes a RPC call to the node's RegisterRPCMiddleware
// and asserts. It also returns a cancel context which can cancel the context
// used by the client.
func (h *HarnessRPC) RegisterRPCMiddleware() (MiddlewareClient,
context.CancelFunc) {
ctxt, cancel := context.WithCancel(h.runCtx)
stream, err := h.LN.RegisterRPCMiddleware(ctxt)
h.NoError(err, "RegisterRPCMiddleware")
return stream, cancel
}
type ChannelEventsClient lnrpc.Lightning_SubscribeChannelEventsClient
// SubscribeChannelEvents creates a subscription client for channel events and
// asserts its creation.
func (h *HarnessRPC) SubscribeChannelEvents() ChannelEventsClient {
req := &lnrpc.ChannelEventSubscription{}
// SubscribeChannelEvents needs to have the context alive for the
// entire test case as the returned client will be used for send and
// receive events stream. Thus we use runCtx here instead of a timeout
// context.
client, err := h.LN.SubscribeChannelEvents(h.runCtx, req)
h.NoError(err, "SubscribeChannelEvents")
return client
}
type CustomMessageClient lnrpc.Lightning_SubscribeCustomMessagesClient
// SubscribeCustomMessages creates a subscription client for custom messages.
func (h *HarnessRPC) SubscribeCustomMessages() (CustomMessageClient,
context.CancelFunc) {
ctxt, cancel := context.WithCancel(h.runCtx)
req := &lnrpc.SubscribeCustomMessagesRequest{}
// SubscribeCustomMessages needs to have the context alive for the
// entire test case as the returned client will be used for send and
// receive events stream. Thus we use runCtx here instead of a timeout
// context.
stream, err := h.LN.SubscribeCustomMessages(ctxt, req)
h.NoError(err, "SubscribeCustomMessages")
return stream, cancel
}
// SendCustomMessage makes a RPC call to the node's SendCustomMessage and
// returns the response.
func (h *HarnessRPC) SendCustomMessage(
req *lnrpc.SendCustomMessageRequest) *lnrpc.SendCustomMessageResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.LN.SendCustomMessage(ctxt, req)
h.NoError(err, "SendCustomMessage")
return resp
}
// GetChanInfo makes a RPC call to the node's GetChanInfo and returns the
// response.
func (h *HarnessRPC) GetChanInfo(
req *lnrpc.ChanInfoRequest) *lnrpc.ChannelEdge {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.LN.GetChanInfo(ctxt, req)
h.NoError(err, "GetChanInfo")
return resp
}
// LookupHtlcResolution makes a RPC call to the node's LookupHtlcResolution and
// returns the response.
//
//nolint:ll
func (h *HarnessRPC) LookupHtlcResolution(
req *lnrpc.LookupHtlcResolutionRequest) *lnrpc.LookupHtlcResolutionResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.LN.LookupHtlcResolution(ctxt, req)
h.NoError(err, "LookupHtlcResolution")
return resp
}
// LookupHtlcResolutionAssertErr makes a RPC call to the node's
// LookupHtlcResolution and asserts an RPC error is returned.
func (h *HarnessRPC) LookupHtlcResolutionAssertErr(
req *lnrpc.LookupHtlcResolutionRequest) error {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.LN.LookupHtlcResolution(ctxt, req)
require.Error(h, err, "expected an error")
return err
}
// Quiesce makes an RPC call to the node's Quiesce method and returns the
// response.
func (h *HarnessRPC) Quiesce(
req *devrpc.QuiescenceRequest) *devrpc.QuiescenceResponse {
ctx, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
res, err := h.Dev.Quiesce(ctx, req)
h.NoError(err, "Quiesce returned an error")
return res
}
package rpc
import (
"context"
"github.com/lightningnetwork/lnd/lnrpc/neutrinorpc"
)
// =====================
// NeutrinoKitClient related RPCs.
// =====================
// Status makes an RPC call to neutrino kit client's Status and asserts.
func (h *HarnessRPC) Status(
req *neutrinorpc.StatusRequest) *neutrinorpc.StatusResponse {
if req == nil {
req = &neutrinorpc.StatusRequest{}
}
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.NeutrinoKit.Status(ctxt, req)
h.NoError(err, "Status")
return resp
}
// GetCFilter makes an RPC call to neutrino kit client's GetCFilter and asserts.
func (h *HarnessRPC) GetCFilter(
req *neutrinorpc.GetCFilterRequest) *neutrinorpc.GetCFilterResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.NeutrinoKit.GetCFilter(ctxt, req)
h.NoError(err, "GetCFilter")
return resp
}
// AddPeer makes an RPC call to neutrino kit client's AddPeer and asserts.
func (h *HarnessRPC) AddPeer(
req *neutrinorpc.AddPeerRequest) *neutrinorpc.AddPeerResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.NeutrinoKit.AddPeer(ctxt, req)
h.NoError(err, "AddPeer")
return resp
}
package rpc
import (
"context"
"github.com/lightningnetwork/lnd/lnrpc/peersrpc"
"github.com/stretchr/testify/require"
)
// =====================
// PeerClient related RPCs.
// =====================
type (
AnnReq *peersrpc.NodeAnnouncementUpdateRequest
AnnResp *peersrpc.NodeAnnouncementUpdateResponse
)
// UpdateNodeAnnouncement makes an UpdateNodeAnnouncement RPC call the peersrpc
// client and asserts.
func (h *HarnessRPC) UpdateNodeAnnouncement(req AnnReq) AnnResp {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.Peer.UpdateNodeAnnouncement(ctxt, req)
h.NoError(err, "UpdateNodeAnnouncement")
return resp
}
// UpdateNodeAnnouncementErr makes an UpdateNodeAnnouncement RPC call the
// peersrpc client and asserts an error is returned.
func (h *HarnessRPC) UpdateNodeAnnouncementErr(req AnnReq) {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.Peer.UpdateNodeAnnouncement(ctxt, req)
require.Error(h, err, "expect an error from update announcement")
}
package rpc
import (
"context"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/stretchr/testify/require"
)
// =====================
// RouterClient related RPCs.
// =====================
// UpdateChanStatus makes a UpdateChanStatus RPC call to node's RouterClient
// and asserts.
//
//nolint:ll
func (h *HarnessRPC) UpdateChanStatus(
req *routerrpc.UpdateChanStatusRequest) *routerrpc.UpdateChanStatusResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.Router.UpdateChanStatus(ctxt, req)
h.NoError(err, "UpdateChanStatus")
return resp
}
type PaymentClient routerrpc.Router_SendPaymentV2Client
// SendPayment sends a payment using the given node and payment request. It
// also asserts the payment being sent successfully.
func (h *HarnessRPC) SendPayment(
req *routerrpc.SendPaymentRequest) PaymentClient {
// SendPayment needs to have the context alive for the entire test case
// as the router relies on the context to propagate HTLCs. Thus, we use
// runCtx here instead of a timeout context.
stream, err := h.Router.SendPaymentV2(h.runCtx, req)
h.NoError(err, "SendPaymentV2")
return stream
}
// SendPaymentWithContext sends a payment using the given node and payment
// request and does so with the passed in context.
func (h *HarnessRPC) SendPaymentWithContext(context context.Context,
req *routerrpc.SendPaymentRequest) PaymentClient {
require.NotNil(h.T, context, "context must not be nil")
// SendPayment needs to have the context alive for the entire test case
// as the router relies on the context to propagate HTLCs.
stream, err := h.Router.SendPaymentV2(context, req)
h.NoError(err, "SendPaymentV2")
return stream
}
type HtlcEventsClient routerrpc.Router_SubscribeHtlcEventsClient
// SubscribeHtlcEvents makes a subscription to the HTLC events and returns a
// htlc event client.
func (h *HarnessRPC) SubscribeHtlcEvents() HtlcEventsClient {
// Use runCtx here to keep the client alive for the scope of the test.
client, err := h.Router.SubscribeHtlcEvents(
h.runCtx, &routerrpc.SubscribeHtlcEventsRequest{},
)
h.NoError(err, "SubscribeHtlcEvents")
return client
}
// GetMissionControlConfig makes a RPC call to the node's
// GetMissionControlConfig and asserts.
//
//nolint:ll
func (h *HarnessRPC) GetMissionControlConfig() *routerrpc.GetMissionControlConfigResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
req := &routerrpc.GetMissionControlConfigRequest{}
resp, err := h.Router.GetMissionControlConfig(ctxt, req)
h.NoError(err, "GetMissionControlConfig")
return resp
}
// SetMissionControlConfig makes a RPC call to the node's
// SetMissionControlConfig and asserts.
func (h *HarnessRPC) SetMissionControlConfig(
config *routerrpc.MissionControlConfig) {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
req := &routerrpc.SetMissionControlConfigRequest{Config: config}
_, err := h.Router.SetMissionControlConfig(ctxt, req)
h.NoError(err, "SetMissionControlConfig")
}
// SetMissionControlConfigAssertErr makes a RPC call to the node's
// SetMissionControlConfig and asserts that we error.
func (h *HarnessRPC) SetMissionControlConfigAssertErr(
config *routerrpc.MissionControlConfig) {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
req := &routerrpc.SetMissionControlConfigRequest{Config: config}
_, err := h.Router.SetMissionControlConfig(ctxt, req)
require.Error(h, err, "expect an error from setting import mission "+
"control")
}
// ResetMissionControl makes a RPC call to the node's ResetMissionControl and
// asserts.
func (h *HarnessRPC) ResetMissionControl() {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
req := &routerrpc.ResetMissionControlRequest{}
_, err := h.Router.ResetMissionControl(ctxt, req)
h.NoError(err, "ResetMissionControl")
}
// SendToRouteV2 makes a RPC call to SendToRouteV2 and asserts.
func (h *HarnessRPC) SendToRouteV2(
req *routerrpc.SendToRouteRequest) *lnrpc.HTLCAttempt {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.Router.SendToRouteV2(ctxt, req)
h.NoError(err, "SendToRouteV2")
return resp
}
// QueryProbability makes a RPC call to the node's QueryProbability and
// asserts.
//
//nolint:ll
func (h *HarnessRPC) QueryProbability(
req *routerrpc.QueryProbabilityRequest) *routerrpc.QueryProbabilityResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.Router.QueryProbability(ctxt, req)
h.NoError(err, "QueryProbability")
return resp
}
// XImportMissionControl makes a RPC call to the node's XImportMissionControl
// and asserts.
func (h *HarnessRPC) XImportMissionControl(
req *routerrpc.XImportMissionControlRequest) {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.Router.XImportMissionControl(ctxt, req)
h.NoError(err, "XImportMissionControl")
}
// XImportMissionControlAssertErr makes a RPC call to the node's
// XImportMissionControl
// and asserts an error occurred.
func (h *HarnessRPC) XImportMissionControlAssertErr(
req *routerrpc.XImportMissionControlRequest) {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.Router.XImportMissionControl(ctxt, req)
require.Error(h, err, "expect an error from x import mission control")
}
// BuildRoute makes a RPC call to the node's RouterClient and asserts.
func (h *HarnessRPC) BuildRoute(
req *routerrpc.BuildRouteRequest) *routerrpc.BuildRouteResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.Router.BuildRoute(ctxt, req)
h.NoError(err, "BuildRoute")
return resp
}
type InterceptorClient routerrpc.Router_HtlcInterceptorClient
// HtlcInterceptor makes a RPC call to the node's RouterClient and asserts.
func (h *HarnessRPC) HtlcInterceptor() (InterceptorClient, context.CancelFunc) {
// HtlcInterceptor needs to have the context alive for the entire test
// case as the returned client will be used for send and receive events
// stream. Thus we use cancel context here instead of a timeout
// context.
ctxt, cancel := context.WithCancel(h.runCtx)
resp, err := h.Router.HtlcInterceptor(ctxt)
h.NoError(err, "HtlcInterceptor")
return resp, cancel
}
// XAddLocalChanAliases adds a list of aliases to the node's alias map.
func (h *HarnessRPC) XAddLocalChanAliases(req *routerrpc.AddAliasesRequest) {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.Router.XAddLocalChanAliases(ctxt, req)
h.NoError(err, "XAddLocalChanAliases")
}
// XAddLocalChanAliasesErr adds a list of aliases to the node's alias map and
// expects an error.
func (h *HarnessRPC) XAddLocalChanAliasesErr(
req *routerrpc.AddAliasesRequest) error {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.Router.XAddLocalChanAliases(ctxt, req)
require.Error(h, err)
return err
}
// XDeleteLocalChanAliases deleted a set of alias mappings.
func (h *HarnessRPC) XDeleteLocalChanAliases(
req *routerrpc.DeleteAliasesRequest) {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.Router.XDeleteLocalChanAliases(ctxt, req)
h.NoError(err, "XDeleteLocalChanAliases")
}
// XDeleteLocalChanAliasesErr deleted a set of alias mappings and expects an
// error.
func (h *HarnessRPC) XDeleteLocalChanAliasesErr(
req *routerrpc.DeleteAliasesRequest) error {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.Router.XDeleteLocalChanAliases(ctxt, req)
require.Error(h, err)
return err
}
type TrackPaymentsClient routerrpc.Router_TrackPaymentsClient
// TrackPayments makes a RPC call to the node's RouterClient and asserts.
func (h *HarnessRPC) TrackPayments(
req *routerrpc.TrackPaymentsRequest) TrackPaymentsClient {
resp, err := h.Router.TrackPayments(h.runCtx, req)
h.NoError(err, "TrackPayments")
return resp
}
type TrackPaymentClient routerrpc.Router_TrackPaymentV2Client
// TrackPaymentV2 creates a subscription client for given invoice and
// asserts its creation.
func (h *HarnessRPC) TrackPaymentV2(payHash []byte) TrackPaymentClient {
req := &routerrpc.TrackPaymentRequest{PaymentHash: payHash}
// TrackPaymentV2 needs to have the context alive for the entire test
// case as the returned client will be used for send and receive events
// stream. Thus we use runCtx here instead of a timeout context.
client, err := h.Router.TrackPaymentV2(h.runCtx, req)
h.NoError(err, "TrackPaymentV2")
return client
}
package rpc
import (
"context"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/stretchr/testify/require"
)
// =====================
// Signer related RPCs.
// =====================
// DeriveSharedKey makes a RPC call to the node's SignerClient and asserts.
func (h *HarnessRPC) DeriveSharedKey(
req *signrpc.SharedKeyRequest) *signrpc.SharedKeyResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.Signer.DeriveSharedKey(ctxt, req)
h.NoError(err, "DeriveSharedKey")
return resp
}
// DeriveSharedKeyErr makes a RPC call to the node's SignerClient and asserts
// there is an error.
func (h *HarnessRPC) DeriveSharedKeyErr(req *signrpc.SharedKeyRequest) error {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.Signer.DeriveSharedKey(ctxt, req)
require.Error(h, err, "expected error from calling DeriveSharedKey")
return err
}
// SignOutputRaw makes a RPC call to the node's SignerClient and asserts.
func (h *HarnessRPC) SignOutputRaw(req *signrpc.SignReq) *signrpc.SignResp {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.Signer.SignOutputRaw(ctxt, req)
h.NoError(err, "SignOutputRaw")
return resp
}
// SignOutputRawErr makes a RPC call to the node's SignerClient and asserts an
// error is returned.
func (h *HarnessRPC) SignOutputRawErr(req *signrpc.SignReq) error {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.Signer.SignOutputRaw(ctxt, req)
require.Error(h, err, "expect to fail to sign raw output")
return err
}
// MuSig2CreateSession makes a RPC call to the node's SignerClient and asserts.
func (h *HarnessRPC) MuSig2CreateSession(
req *signrpc.MuSig2SessionRequest) *signrpc.MuSig2SessionResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.Signer.MuSig2CreateSession(ctxt, req)
h.NoError(err, "MuSig2CreateSession")
return resp
}
// MuSig2CreateSessionErr makes an RPC call to the node's SignerClient and
// asserts an error is returned.
func (h *HarnessRPC) MuSig2CreateSessionErr(
req *signrpc.MuSig2SessionRequest) error {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.Signer.MuSig2CreateSession(ctxt, req)
require.Error(h, err, "expected error from calling MuSig2CreateSession")
return err
}
// MuSig2CombineKeys makes a RPC call to the node's SignerClient and asserts.
//
//nolint:ll
func (h *HarnessRPC) MuSig2CombineKeys(
req *signrpc.MuSig2CombineKeysRequest) *signrpc.MuSig2CombineKeysResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.Signer.MuSig2CombineKeys(ctxt, req)
h.NoError(err, "MuSig2CombineKeys")
return resp
}
// MuSig2CombineKeysErr makes an RPC call to the node's SignerClient and
// asserts an error is returned.
func (h *HarnessRPC) MuSig2CombineKeysErr(
req *signrpc.MuSig2CombineKeysRequest) error {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.Signer.MuSig2CombineKeys(ctxt, req)
require.Error(h, err, "expected error from calling MuSig2CombineKeys")
return err
}
// MuSig2RegisterNonces makes a RPC call to the node's SignerClient and asserts.
//
//nolint:ll
func (h *HarnessRPC) MuSig2RegisterNonces(
req *signrpc.MuSig2RegisterNoncesRequest) *signrpc.MuSig2RegisterNoncesResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.Signer.MuSig2RegisterNonces(ctxt, req)
h.NoError(err, "MuSig2RegisterNonces")
return resp
}
// MuSig2Sign makes a RPC call to the node's SignerClient and asserts.
func (h *HarnessRPC) MuSig2Sign(
req *signrpc.MuSig2SignRequest) *signrpc.MuSig2SignResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.Signer.MuSig2Sign(ctxt, req)
h.NoError(err, "MuSig2Sign")
return resp
}
// MuSig2SignErr makes a RPC call to the node's SignerClient and asserts an
// error is returned.
func (h *HarnessRPC) MuSig2SignErr(req *signrpc.MuSig2SignRequest) error {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.Signer.MuSig2Sign(ctxt, req)
require.Error(h, err, "expect an error")
return err
}
// MuSig2CombineSig makes a RPC call to the node's SignerClient and asserts.
func (h *HarnessRPC) MuSig2CombineSig(
r *signrpc.MuSig2CombineSigRequest) *signrpc.MuSig2CombineSigResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.Signer.MuSig2CombineSig(ctxt, r)
h.NoError(err, "MuSig2CombineSig")
return resp
}
// MuSig2Cleanup makes a RPC call to the node's SignerClient and asserts.
func (h *HarnessRPC) MuSig2Cleanup(
req *signrpc.MuSig2CleanupRequest) *signrpc.MuSig2CleanupResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.Signer.MuSig2Cleanup(ctxt, req)
h.NoError(err, "MuSig2Cleanup")
return resp
}
// SignMessageSigner makes a RPC call to the node's SignerClient and asserts.
//
// NOTE: there's already `SignMessage` in `h.LN`.
func (h *HarnessRPC) SignMessageSigner(
req *signrpc.SignMessageReq) *signrpc.SignMessageResp {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.Signer.SignMessage(ctxt, req)
h.NoError(err, "SignMessage")
return resp
}
// VerifyMessageSigner makes a RPC call to the node's SignerClient and asserts.
//
// NOTE: there's already `VerifyMessageSigner` in `h.LN`.
func (h *HarnessRPC) VerifyMessageSigner(
req *signrpc.VerifyMessageReq) *signrpc.VerifyMessageResp {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.Signer.VerifyMessage(ctxt, req)
h.NoError(err, "VerifyMessage")
return resp
}
// ComputeInputScript makes a RPC call to the node's SignerClient and asserts.
func (h *HarnessRPC) ComputeInputScript(
req *signrpc.SignReq) *signrpc.InputScriptResp {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.Signer.ComputeInputScript(ctxt, req)
h.NoError(err, "ComputeInputScript")
return resp
}
package rpc
import (
"github.com/lightningnetwork/lnd/lnrpc"
)
// =====================
// StateClient related RPCs.
// =====================
// SubscribeState makes a rpc call to StateClient and asserts there's no error.
func (h *HarnessRPC) SubscribeState() lnrpc.State_SubscribeStateClient {
client, err := h.State.SubscribeState(
h.runCtx, &lnrpc.SubscribeStateRequest{},
)
h.NoError(err, "SubscribeState")
return client
}
package rpc
import (
"context"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/stretchr/testify/require"
)
// =====================
// WalletKitClient related RPCs.
// =====================
type (
SignReq *walletrpc.SignMessageWithAddrResponse
VerifyResp *walletrpc.VerifyMessageWithAddrResponse
)
// FinalizePsbt makes a RPC call to node's ListUnspent and asserts.
func (h *HarnessRPC) ListUnspent(
req *walletrpc.ListUnspentRequest) *walletrpc.ListUnspentResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.WalletKit.ListUnspent(ctxt, req)
h.NoError(err, "ListUnspent")
return resp
}
// DeriveKey makes a RPC call to the DeriveKey and asserts.
func (h *HarnessRPC) DeriveKey(kl *signrpc.KeyLocator) *signrpc.KeyDescriptor {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
key, err := h.WalletKit.DeriveKey(ctxt, kl)
h.NoError(err, "DeriveKey")
return key
}
// SendOutputs makes a RPC call to the node's WalletKitClient and asserts.
func (h *HarnessRPC) SendOutputs(
req *walletrpc.SendOutputsRequest) *walletrpc.SendOutputsResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.WalletKit.SendOutputs(ctxt, req)
h.NoError(err, "SendOutputs")
return resp
}
// FundPsbt makes a RPC call to node's FundPsbt and asserts.
func (h *HarnessRPC) FundPsbt(
req *walletrpc.FundPsbtRequest) *walletrpc.FundPsbtResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.WalletKit.FundPsbt(ctxt, req)
h.NoError(err, "FundPsbt")
return resp
}
// FundPsbtAssertErr makes a RPC call to the node's FundPsbt and asserts an
// error is returned.
func (h *HarnessRPC) FundPsbtAssertErr(req *walletrpc.FundPsbtRequest) {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.WalletKit.FundPsbt(ctxt, req)
require.Error(h, err, "expected error returned")
}
// FinalizePsbt makes a RPC call to node's FinalizePsbt and asserts.
func (h *HarnessRPC) FinalizePsbt(
req *walletrpc.FinalizePsbtRequest) *walletrpc.FinalizePsbtResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.WalletKit.FinalizePsbt(ctxt, req)
h.NoError(err, "FinalizePsbt")
return resp
}
// LabelTransactionAssertErr makes a RPC call to the node's LabelTransaction
// and asserts an error is returned. It then returns the error.
func (h *HarnessRPC) LabelTransactionAssertErr(
req *walletrpc.LabelTransactionRequest) error {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.WalletKit.LabelTransaction(ctxt, req)
require.Error(h, err, "expected error returned")
return err
}
// LabelTransaction makes a RPC call to the node's LabelTransaction
// and asserts no error is returned.
func (h *HarnessRPC) LabelTransaction(req *walletrpc.LabelTransactionRequest) {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.WalletKit.LabelTransaction(ctxt, req)
h.NoError(err, "LabelTransaction")
}
// DeriveNextKey makes a RPC call to the DeriveNextKey and asserts.
func (h *HarnessRPC) DeriveNextKey(
req *walletrpc.KeyReq) *signrpc.KeyDescriptor {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
key, err := h.WalletKit.DeriveNextKey(ctxt, req)
h.NoError(err, "DeriveNextKey")
return key
}
// ListAddresses makes a RPC call to the ListAddresses and asserts.
func (h *HarnessRPC) ListAddresses(
req *walletrpc.ListAddressesRequest) *walletrpc.ListAddressesResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
key, err := h.WalletKit.ListAddresses(ctxt, req)
h.NoError(err, "ListAddresses")
return key
}
// SignMessageWithAddr makes a RPC call to the SignMessageWithAddr and asserts.
func (h *HarnessRPC) SignMessageWithAddr(
req *walletrpc.SignMessageWithAddrRequest) SignReq {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
key, err := h.WalletKit.SignMessageWithAddr(ctxt, req)
h.NoError(err, "SignMessageWithAddr")
return key
}
// VerifyMessageWithAddr makes a RPC call to
// the VerifyMessageWithAddr and asserts.
func (h *HarnessRPC) VerifyMessageWithAddr(
req *walletrpc.VerifyMessageWithAddrRequest) VerifyResp {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
key, err := h.WalletKit.VerifyMessageWithAddr(ctxt, req)
h.NoError(err, "VerifyMessageWithAddr")
return key
}
// ListSweeps makes a ListSweeps RPC call to the node's WalletKit client.
func (h *HarnessRPC) ListSweeps(verbose bool,
startHeight int32) *walletrpc.ListSweepsResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
req := &walletrpc.ListSweepsRequest{
Verbose: verbose,
StartHeight: startHeight,
}
resp, err := h.WalletKit.ListSweeps(ctxt, req)
h.NoError(err, "ListSweeps")
return resp
}
// PendingSweeps makes a RPC call to the node's WalletKitClient and asserts.
func (h *HarnessRPC) PendingSweeps() *walletrpc.PendingSweepsResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
req := &walletrpc.PendingSweepsRequest{}
resp, err := h.WalletKit.PendingSweeps(ctxt, req)
h.NoError(err, "PendingSweeps")
return resp
}
// PublishTransaction makes an RPC call to the node's WalletKitClient and
// asserts.
func (h *HarnessRPC) PublishTransaction(
req *walletrpc.Transaction) *walletrpc.PublishResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.WalletKit.PublishTransaction(ctxt, req)
h.NoError(err, "PublishTransaction")
return resp
}
// GetTransaction makes a RPC call to the node's WalletKitClient and asserts.
func (h *HarnessRPC) GetTransaction(
req *walletrpc.GetTransactionRequest) *lnrpc.Transaction {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.WalletKit.GetTransaction(ctxt, req)
h.NoError(err, "GetTransaction")
return resp
}
// RemoveTransaction makes an RPC call to the node's WalletKitClient and
// asserts.
//
//nolint:ll
func (h *HarnessRPC) RemoveTransaction(
req *walletrpc.GetTransactionRequest) *walletrpc.RemoveTransactionResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.WalletKit.RemoveTransaction(ctxt, req)
h.NoError(err, "RemoveTransaction")
return resp
}
// BumpFee makes a RPC call to the node's WalletKitClient and asserts.
func (h *HarnessRPC) BumpFee(
req *walletrpc.BumpFeeRequest) *walletrpc.BumpFeeResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.WalletKit.BumpFee(ctxt, req)
h.NoError(err, "BumpFee")
return resp
}
// BumpForceCloseFee makes a RPC call to the node's WalletKitClient and asserts.
//
//nolint:ll
func (h *HarnessRPC) BumpForceCloseFee(
req *walletrpc.BumpForceCloseFeeRequest) *walletrpc.BumpForceCloseFeeResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.WalletKit.BumpForceCloseFee(ctxt, req)
h.NoError(err, "BumpForceCloseFee")
return resp
}
// ListAccounts makes a RPC call to the node's WalletKitClient and asserts.
func (h *HarnessRPC) ListAccounts(
req *walletrpc.ListAccountsRequest) *walletrpc.ListAccountsResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.WalletKit.ListAccounts(ctxt, req)
h.NoError(err, "ListAccounts")
return resp
}
// ImportAccount makes a RPC call to the node's WalletKitClient and asserts.
func (h *HarnessRPC) ImportAccount(
req *walletrpc.ImportAccountRequest) *walletrpc.ImportAccountResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.WalletKit.ImportAccount(ctxt, req)
h.NoError(err, "ImportAccount")
return resp
}
// ImportAccountAssertErr makes the ImportAccount RPC call and asserts an error
// is returned. It then returns the error.
func (h *HarnessRPC) ImportAccountAssertErr(
req *walletrpc.ImportAccountRequest) error {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.WalletKit.ImportAccount(ctxt, req)
require.Error(h, err)
return err
}
// ImportPublicKey makes a RPC call to the node's WalletKitClient and asserts.
//
//nolint:ll
func (h *HarnessRPC) ImportPublicKey(
req *walletrpc.ImportPublicKeyRequest) *walletrpc.ImportPublicKeyResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.WalletKit.ImportPublicKey(ctxt, req)
h.NoError(err, "ImportPublicKey")
return resp
}
// SignPsbt makes a RPC call to the node's WalletKitClient and asserts.
func (h *HarnessRPC) SignPsbt(
req *walletrpc.SignPsbtRequest) *walletrpc.SignPsbtResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.WalletKit.SignPsbt(ctxt, req)
h.NoError(err, "SignPsbt")
return resp
}
// SignPsbtErr makes a RPC call to the node's WalletKitClient and asserts
// an error returned.
func (h *HarnessRPC) SignPsbtErr(req *walletrpc.SignPsbtRequest) error {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.WalletKit.SignPsbt(ctxt, req)
require.Errorf(h, err, "%s: expect sign psbt to return an error",
h.Name)
return err
}
// ImportTapscript makes a RPC call to the node's WalletKitClient and asserts.
//
//nolint:ll
func (h *HarnessRPC) ImportTapscript(
req *walletrpc.ImportTapscriptRequest) *walletrpc.ImportTapscriptResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.WalletKit.ImportTapscript(ctxt, req)
h.NoError(err, "ImportTapscript")
return resp
}
// RequiredReserve makes a RPC call to the node's WalletKitClient and asserts.
//
//nolint:ll
func (h *HarnessRPC) RequiredReserve(
req *walletrpc.RequiredReserveRequest) *walletrpc.RequiredReserveResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.WalletKit.RequiredReserve(ctxt, req)
h.NoError(err, "RequiredReserve")
return resp
}
// ListLeases makes a ListLeases RPC call to the node's WalletKit client.
func (h *HarnessRPC) ListLeases() *walletrpc.ListLeasesResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.WalletKit.ListLeases(
ctxt, &walletrpc.ListLeasesRequest{},
)
h.NoError(err, "ListLeases")
return resp
}
package rpc
import (
"context"
"github.com/lightningnetwork/lnd/lnrpc"
)
// =====================
// WalletUnlockerClient related RPCs.
// =====================
// UnlockWallet makes a RPC request to WalletUnlocker and asserts there's no
// error.
func (h *HarnessRPC) UnlockWallet(
req *lnrpc.UnlockWalletRequest) *lnrpc.UnlockWalletResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.WalletUnlocker.UnlockWallet(ctxt, req)
h.NoError(err, "UnlockWallet")
return resp
}
// InitWallet makes a RPC request to WalletUnlocker and asserts there's no
// error.
func (h *HarnessRPC) InitWallet(
req *lnrpc.InitWalletRequest) *lnrpc.InitWalletResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.WalletUnlocker.InitWallet(ctxt, req)
h.NoError(err, "InitWallet")
return resp
}
// GenSeed makes a RPC request to WalletUnlocker and asserts there's no error.
func (h *HarnessRPC) GenSeed(req *lnrpc.GenSeedRequest) *lnrpc.GenSeedResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.WalletUnlocker.GenSeed(ctxt, req)
h.NoError(err, "GenSeed")
return resp
}
// ChangePassword makes a RPC request to WalletUnlocker and asserts there's no
// error.
func (h *HarnessRPC) ChangePassword(
req *lnrpc.ChangePasswordRequest) *lnrpc.ChangePasswordResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.WalletUnlocker.ChangePassword(ctxt, req)
h.NoError(err, "ChangePassword")
return resp
}
package rpc
import (
"context"
"github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc"
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
)
// =====================
// WatchtowerClient and WatchtowerClientClient related RPCs.
// =====================
// GetInfoWatchtower makes a RPC call to the watchtower of the given node and
// asserts.
func (h *HarnessRPC) GetInfoWatchtower() *watchtowerrpc.GetInfoResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
req := &watchtowerrpc.GetInfoRequest{}
info, err := h.Watchtower.GetInfo(ctxt, req)
h.NoError(err, "GetInfo from Watchtower")
return info
}
// GetTowerInfo makes an RPC call to the watchtower client of the given node and
// asserts.
func (h *HarnessRPC) GetTowerInfo(
req *wtclientrpc.GetTowerInfoRequest) *wtclientrpc.Tower {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
info, err := h.WatchtowerClient.GetTowerInfo(ctxt, req)
h.NoError(err, "GetTowerInfo from WatchtowerClient")
return info
}
// AddTower makes a RPC call to the WatchtowerClient of the given node and
// asserts.
func (h *HarnessRPC) AddTower(
req *wtclientrpc.AddTowerRequest) *wtclientrpc.AddTowerResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.WatchtowerClient.AddTower(ctxt, req)
h.NoError(err, "AddTower")
return resp
}
// DeactivateTower makes an RPC call to the WatchtowerClient of the given node
// and asserts.
func (h *HarnessRPC) DeactivateTower(req *wtclientrpc.DeactivateTowerRequest) {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.WatchtowerClient.DeactivateTower(ctxt, req)
h.NoError(err, "DeactivateTower")
}
// TerminateSession makes an RPC call to the WatchtowerClient of the given node
// and asserts.
func (h *HarnessRPC) TerminateSession(
req *wtclientrpc.TerminateSessionRequest) {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.WatchtowerClient.TerminateSession(ctxt, req)
h.NoError(err, "TerminateSession")
}
// RemoveTower makes an RPC call to the WatchtowerClient of the given node
// and asserts.
func (h *HarnessRPC) RemoveTower(req *wtclientrpc.RemoveTowerRequest) {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.WatchtowerClient.RemoveTower(ctxt, req)
h.NoError(err, "RemoveTower")
}
// WatchtowerStats makes a RPC call to the WatchtowerClient of the given node
// and asserts.
func (h *HarnessRPC) WatchtowerStats() *wtclientrpc.StatsResponse {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
req := &wtclientrpc.StatsRequest{}
resp, err := h.WatchtowerClient.Stats(ctxt, req)
h.NoError(err, "Stats from Watchtower")
return resp
}
package unittest
import (
"fmt"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/integration/rpctest"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/lightninglabs/neutrino"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntest/port"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require"
)
var (
// TrickleInterval is the interval at which the miner should trickle
// transactions to its peers. We'll set it small to ensure the miner
// propagates transactions quickly in the tests.
TrickleInterval = 10 * time.Millisecond
)
var (
// NetParams are the default network parameters for the tests.
NetParams = &chaincfg.RegressionNetParams
)
// NewMiner spawns testing harness backed by a btcd node that can serve as a
// miner.
func NewMiner(t *testing.T, netParams *chaincfg.Params, extraArgs []string,
createChain bool, spendableOutputs uint32) *rpctest.Harness {
t.Helper()
args := []string{
"--nobanning",
"--debuglevel=debug",
fmt.Sprintf("--trickleinterval=%v", TrickleInterval),
// Don't disconnect if a reply takes too long.
"--nostalldetect",
}
extraArgs = append(extraArgs, args...)
node, err := rpctest.New(netParams, nil, extraArgs, "")
require.NoError(t, err, "unable to create backend node")
t.Cleanup(func() {
require.NoError(t, node.TearDown())
})
// We want to overwrite some of the connection settings to make the
// tests more robust. We might need to restart the backend while there
// are already blocks present, which will take a bit longer than the
// 1 second the default settings amount to. Doubling both values will
// give us retries up to 4 seconds.
node.MaxConnRetries = rpctest.DefaultMaxConnectionRetries * 2
node.ConnectionRetryTimeout = rpctest.DefaultConnectionRetryTimeout * 2
if err := node.SetUp(createChain, spendableOutputs); err != nil {
t.Fatalf("unable to set up backend node: %v", err)
}
// Next mine enough blocks in order for segwit and the CSV package
// soft-fork to activate.
numBlocks := netParams.MinerConfirmationWindow*2 + 17
_, err = node.Client.Generate(numBlocks)
require.NoError(t, err, "failed to generate blocks")
return node
}
// NewBitcoindBackend spawns a new bitcoind node that connects to a miner at the
// specified address. The txindex boolean can be set to determine whether the
// backend node should maintain a transaction index. The rpcpolling boolean
// can be set to determine whether bitcoind's RPC polling interface should be
// used for block and tx notifications or if its ZMQ interface should be used.
// A connection to the newly spawned bitcoind node is returned once the bitcoind
// is synced to the miner's best height.
func NewBitcoindBackend(t *testing.T, netParams *chaincfg.Params,
miner *rpctest.Harness, txindex, rpcpolling bool) *chain.BitcoindConn {
t.Helper()
tempBitcoindDir := t.TempDir()
rpcPort := port.NextAvailablePort()
torBindPort := port.NextAvailablePort()
zmqBlockPort := port.NextAvailablePort()
zmqTxPort := port.NextAvailablePort()
zmqBlockHost := fmt.Sprintf("tcp://127.0.0.1:%d", zmqBlockPort)
zmqTxHost := fmt.Sprintf("tcp://127.0.0.1:%d", zmqTxPort)
// TODO(yy): Make this configurable via `chain.BitcoindConfig` and
// replace the default P2P port when set.
p2pPort := port.NextAvailablePort()
netParams.DefaultPort = fmt.Sprintf("%d", p2pPort)
args := []string{
"-datadir=" + tempBitcoindDir,
"-regtest",
"-rpcauth=weks:469e9bb14ab2360f8e226efed5ca6fd$507c670e800a95" +
"284294edb5773b05544b220110063096c221be9933c82d38e1",
fmt.Sprintf("-rpcport=%d", rpcPort),
fmt.Sprintf("-bind=127.0.0.1:%d=onion", torBindPort),
fmt.Sprintf("-port=%d", p2pPort),
"-disablewallet",
"-zmqpubrawblock=" + zmqBlockHost,
"-zmqpubrawtx=" + zmqTxHost,
// whitelist localhost to speed up relay.
"-whitelist=127.0.0.1",
// Disable v2 transport as btcd doesn't support it yet.
//
// TODO(yy): Remove this line once v2 conn is supported in
// `btcd`.
"-v2transport=0",
}
if txindex {
args = append(args, "-txindex")
}
bitcoind := exec.Command("bitcoind", args...)
err := bitcoind.Start()
require.NoError(t, err, "unable to start bitcoind")
t.Cleanup(func() {
// Kill `bitcoind` and assert there's no error.
err = bitcoind.Process.Kill()
require.NoError(t, err)
err = bitcoind.Wait()
if strings.Contains(err.Error(), "signal: killed") {
return
}
require.NoError(t, err)
})
// Wait for the bitcoind instance to start up.
time.Sleep(time.Second)
host := fmt.Sprintf("127.0.0.1:%d", rpcPort)
cfg := &chain.BitcoindConfig{
ChainParams: netParams,
Host: host,
User: "weks",
Pass: "weks",
// Fields only required for pruned nodes, not needed for these
// tests.
Dialer: nil,
PrunedModeMaxPeers: 0,
}
if rpcpolling {
cfg.PollingConfig = &chain.PollingConfig{
BlockPollingInterval: time.Millisecond * 20,
TxPollingInterval: time.Millisecond * 20,
}
} else {
cfg.ZMQConfig = &chain.ZMQConfig{
ZMQBlockHost: zmqBlockHost,
ZMQTxHost: zmqTxHost,
ZMQReadDeadline: 5 * time.Second,
}
}
var conn *chain.BitcoindConn
err = wait.NoError(func() error {
var err error
conn, err = chain.NewBitcoindConn(cfg)
if err != nil {
return err
}
return conn.Start()
}, 10*time.Second)
if err != nil {
t.Fatalf("unable to establish connection to bitcoind at %v: "+
"%v", tempBitcoindDir, err)
}
t.Cleanup(conn.Stop)
// Assert that the connection with the miner is made.
//
// Create a new RPC client.
rpcCfg := rpcclient.ConnConfig{
Host: cfg.Host,
User: cfg.User,
Pass: cfg.Pass,
DisableConnectOnNew: true,
DisableAutoReconnect: false,
DisableTLS: true,
HTTPPostMode: true,
}
rpcClient, err := rpcclient.New(&rpcCfg, nil)
require.NoError(t, err, "failed to create RPC client")
// Connect to the miner node.
err = rpcClient.AddNode(miner.P2PAddress(), rpcclient.ANAdd)
require.NoError(t, err, "failed to connect to miner")
// Get the network info and assert the num of outbound connections is 1.
err = wait.NoError(func() error {
result, err := rpcClient.GetNetworkInfo()
require.NoError(t, err)
if int(result.Connections) != 1 {
return fmt.Errorf("want 1 conn, got %d",
result.Connections)
}
if int(result.ConnectionsOut) != 1 {
return fmt.Errorf("want 1 outbound conn, got %d",
result.Connections)
}
return nil
}, wait.DefaultTimeout)
require.NoError(t, err, "timeout connecting to the miner")
// Assert the chain backend is synced to the miner.
syncBitcoindWithMiner(t, rpcClient, miner, p2pPort)
// Tear down the rpc client.
rpcClient.Shutdown()
return conn
}
// syncBitcoindWithMiner waits until the bitcoind node is synced with the miner.
func syncBitcoindWithMiner(t *testing.T, notifier *rpcclient.Client,
miner *rpctest.Harness, p2pPort int) uint32 {
_, minerHeight, err := miner.Client.GetBestBlock()
require.NoError(t, err, "unable to retrieve miner's current height")
timeout := time.After(10 * time.Second)
for {
info, err := notifier.GetBlockChainInfo()
require.NoError(t, err)
bitcoindHeight := info.Blocks
t.Logf("miner height=%v, bitcoind height=%v", minerHeight,
bitcoindHeight)
if bitcoindHeight == minerHeight {
return uint32(bitcoindHeight)
}
select {
case <-time.After(100 * time.Millisecond):
case <-timeout:
t.Fatalf("timed out in syncNotifierWithMiner, got "+
"err=%v, minerHeight=%v, bitcoindHeight=%v",
err, minerHeight, bitcoindHeight)
}
// Get the num of connections the miner has. We expect it to
// have at least one connection with the chain backend.
count, err := miner.Client.GetConnectionCount()
require.NoError(t, err)
if count != 0 {
continue
}
// Reconnect the miner and the chain backend.
//
// NOTE: The connection should have been made before we perform
// the `syncNotifierWithMiner`. However, due unknown reason, the
// miner may refuse to process the inbound connection made by
// the bitcoind node, causing the connection to fail. It's
// possible there's a bug in the handshake between the two
// nodes.
//
// A normal flow is, bitcoind starts a v2 handshake flow, which
// btcd will fail and disconnect. Upon seeing this
// disconnection, bitcoind will try a v1 handshake and succeeds.
// The failed flow is, upon seeing the v2 handshake, btcd
// doesn't seem to perform the disconnect. Instead an EOF
// websocket error is found.
//
// TODO(yy): Fix the above bug in `btcd`. This can be reproduced
// using `make flakehunter-unit pkg=$pkg case=$case`, with,
// `case=TestHistoricalConfDetailsNoTxIndex/rpc_polling_enabled`
// `pkg=chainntnfs/bitcoindnotify`.
// Also need to modify the temp dir logic so we can save the
// debug logs.
// This bug is likely to be fixed when we implement the
// encrypted p2p conn, or when we properly fix the shutdown
// issues in all our RPC conns.
t.Log("Expected to the chain backend to have one conn with " +
"the miner, instead it's disconnected!")
// We now ask the miner to add the chain backend back.
host := fmt.Sprintf("127.0.0.1:%d", p2pPort)
// NOTE:AddNode must take a host that has the format
// `host:port`, otherwise the default port will be used. Check
// `normalizeAddress` in btcd for details.
err = miner.Client.AddNode(host, rpcclient.ANAdd)
require.NoError(t, err, "Failed to connect miner to the chain "+
"backend")
}
}
// NewNeutrinoBackend spawns a new neutrino node that connects to a miner at
// the specified address.
func NewNeutrinoBackend(t *testing.T, netParams *chaincfg.Params,
minerAddr string) *neutrino.ChainService {
t.Helper()
spvDir := t.TempDir()
dbName := filepath.Join(spvDir, "neutrino.db")
spvDatabase, err := walletdb.Create(
kvdb.BoltBackendName, dbName, true, kvdb.DefaultDBTimeout,
false,
)
if err != nil {
t.Fatalf("unable to create walletdb: %v", err)
}
t.Cleanup(func() {
spvDatabase.Close()
})
// Create an instance of neutrino connected to the running btcd
// instance.
spvConfig := neutrino.Config{
DataDir: spvDir,
Database: spvDatabase,
ChainParams: *netParams,
ConnectPeers: []string{minerAddr},
}
spvNode, err := neutrino.NewChainService(spvConfig)
if err != nil {
t.Fatalf("unable to create neutrino: %v", err)
}
// We'll also wait for the instance to sync up fully to the chain
// generated by the btcd instance.
_ = spvNode.Start()
for !spvNode.IsCurrent() {
time.Sleep(time.Millisecond * 100)
}
t.Cleanup(func() {
_ = spvNode.Stop()
})
return spvNode
}
func init() {
// Before we start any node, we need to make sure that any btcd or
// bitcoind node that is started through the RPC harness uses a unique
// port as well to avoid any port collisions.
rpctest.ListenAddressGenerator =
port.GenerateSystemUniqueListenerAddresses
}
package lntest
import (
"fmt"
"io"
"math"
"os"
"strconv"
"strings"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
)
const (
// NeutrinoBackendName is the name of the neutrino backend.
NeutrinoBackendName = "neutrino"
DefaultTimeout = wait.DefaultTimeout
// noFeeLimitMsat is used to specify we will put no requirements on fee
// charged when choosing a route path.
noFeeLimitMsat = math.MaxInt64
)
// CopyFile copies the file src to dest.
func CopyFile(dest, src string) error {
s, err := os.Open(src)
if err != nil {
return err
}
defer s.Close()
d, err := os.Create(dest)
if err != nil {
return err
}
if _, err := io.Copy(d, s); err != nil {
d.Close()
return err
}
return d.Close()
}
// errNumNotMatched is a helper method to return a nicely formatted error.
func errNumNotMatched(name string, subject string,
want, got, total, old int, desc ...any) error {
err := fmt.Errorf("%s: assert %s failed: want %d, got: %d, total: "+
"%d, previously had: %d", name, subject, want, got, total, old)
if len(desc) > 0 {
err = fmt.Errorf("%w, desc: %v", err, desc)
}
return err
}
// parseDerivationPath parses a path in the form of m/x'/y'/z'/a/b into a slice
// of [x, y, z, a, b], meaning that the apostrophe is ignored and 2^31 is _not_
// added to the numbers.
func ParseDerivationPath(path string) ([]uint32, error) {
path = strings.TrimSpace(path)
if len(path) == 0 {
return nil, fmt.Errorf("path cannot be empty")
}
if !strings.HasPrefix(path, "m/") {
return nil, fmt.Errorf("path must start with m/")
}
// Just the root key, no path was provided. This is valid but not useful
// in most cases.
rest := strings.ReplaceAll(path, "m/", "")
if rest == "" {
return []uint32{}, nil
}
parts := strings.Split(rest, "/")
indices := make([]uint32, len(parts))
for i := 0; i < len(parts); i++ {
part := parts[i]
if strings.Contains(parts[i], "'") {
part = strings.TrimRight(parts[i], "'")
}
parsed, err := strconv.ParseInt(part, 10, 32)
if err != nil {
return nil, fmt.Errorf("could not parse part \"%s\": "+
"%v", part, err)
}
indices[i] = uint32(parsed)
}
return indices, nil
}
// ChanPointFromPendingUpdate constructs a channel point from a lnrpc pending
// update.
func ChanPointFromPendingUpdate(pu *lnrpc.PendingUpdate) *lnrpc.ChannelPoint {
chanPoint := &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: pu.Txid,
},
OutputIndex: pu.OutputIndex,
}
return chanPoint
}
// channelPointStr returns the string representation of the channel's
// funding transaction.
func channelPointStr(chanPoint *lnrpc.ChannelPoint) string {
fundingTxID, err := lnrpc.GetChanPointFundingTxid(chanPoint)
if err != nil {
return ""
}
cp := wire.OutPoint{
Hash: *fundingTxID,
Index: chanPoint.OutputIndex,
}
return cp.String()
}
// CommitTypeHasTaproot returns whether commitType is a taproot commitment.
func CommitTypeHasTaproot(commitType lnrpc.CommitmentType) bool {
switch commitType {
case lnrpc.CommitmentType_SIMPLE_TAPROOT:
return true
default:
return false
}
}
// CommitTypeHasAnchors returns whether commitType uses anchor outputs.
func CommitTypeHasAnchors(commitType lnrpc.CommitmentType) bool {
switch commitType {
case lnrpc.CommitmentType_ANCHORS,
lnrpc.CommitmentType_SIMPLE_TAPROOT,
lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
return true
default:
return false
}
}
// NodeArgsForCommitType returns the command line flag to supply to enable this
// commitment type.
func NodeArgsForCommitType(commitType lnrpc.CommitmentType) []string {
switch commitType {
case lnrpc.CommitmentType_LEGACY:
return []string{"--protocol.legacy.committweak"}
case lnrpc.CommitmentType_STATIC_REMOTE_KEY:
return []string{}
case lnrpc.CommitmentType_ANCHORS:
return []string{"--protocol.anchors"}
case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
return []string{
"--protocol.anchors",
"--protocol.script-enforced-lease",
}
case lnrpc.CommitmentType_SIMPLE_TAPROOT:
return []string{
"--protocol.anchors",
"--protocol.simple-taproot-chans",
}
}
return nil
}
// CalcStaticFee calculates appropriate fees for commitment transactions. This
// function provides a simple way to allow test balance assertions to take fee
// calculations into account.
func CalcStaticFee(c lnrpc.CommitmentType, numHTLCs int) btcutil.Amount {
//nolint:ll
const (
htlcWeight = input.HTLCWeight
anchorSize = 330 * 2
defaultSatPerVByte = lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte
scale = 1000
)
var (
anchors = btcutil.Amount(0)
commitWeight = input.CommitWeight
feePerKw = chainfee.SatPerKWeight(DefaultFeeRateSatPerKw)
)
switch {
// The taproot commitment type has the extra anchor outputs, but also a
// smaller witness field (will just be a normal key spend), so we need
// to account for that here as well.
case CommitTypeHasTaproot(c):
feePerKw = chainfee.SatPerKVByte(
defaultSatPerVByte * scale,
).FeePerKWeight()
commitWeight = input.TaprootCommitWeight
anchors = anchorSize
// The anchor commitment type is slightly heavier, and we must also add
// the value of the two anchors to the resulting fee the initiator
// pays. In addition the fee rate is capped at 10 sat/vbyte for anchor
// channels.
case CommitTypeHasAnchors(c):
feePerKw = chainfee.SatPerKVByte(
defaultSatPerVByte * scale,
).FeePerKWeight()
commitWeight = input.AnchorCommitWeight
anchors = anchorSize
}
totalWeight := commitWeight + htlcWeight*numHTLCs
return feePerKw.FeeForWeight(lntypes.WeightUnit(totalWeight)) + anchors
}
// CalculateMaxHtlc re-implements the RequiredRemoteChannelReserve of the
// funding manager's config, which corresponds to the maximum MaxHTLC value we
// allow users to set when updating a channel policy.
func CalculateMaxHtlc(chanCap btcutil.Amount) uint64 {
const ratio = 100
reserve := lnwire.NewMSatFromSatoshis(chanCap / ratio)
max := lnwire.NewMSatFromSatoshis(chanCap) - reserve
return uint64(max)
}
// CalcStaticFeeBuffer calculates appropriate fee buffer which must be taken
// into account when sending htlcs.
func CalcStaticFeeBuffer(c lnrpc.CommitmentType, numHTLCs int) btcutil.Amount {
//nolint:ll
const (
htlcWeight = input.HTLCWeight
defaultSatPerVByte = lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte
scale = 1000
)
var (
commitWeight = input.CommitWeight
feePerKw = chainfee.SatPerKWeight(DefaultFeeRateSatPerKw)
)
switch {
// The taproot commitment type has the extra anchor outputs, but also a
// smaller witness field (will just be a normal key spend), so we need
// to account for that here as well.
case CommitTypeHasTaproot(c):
feePerKw = chainfee.SatPerKVByte(
defaultSatPerVByte * scale,
).FeePerKWeight()
commitWeight = input.TaprootCommitWeight
// The anchor commitment type is slightly heavier, and we must also add
// the value of the two anchors to the resulting fee the initiator
// pays. In addition the fee rate is capped at 10 sat/vbyte for anchor
// channels.
case CommitTypeHasAnchors(c):
feePerKw = chainfee.SatPerKVByte(
defaultSatPerVByte * scale,
).FeePerKWeight()
commitWeight = input.AnchorCommitWeight
}
// Account for the HTLC which will be required when sending an htlc.
numHTLCs++
totalWeight := commitWeight + numHTLCs*htlcWeight
feeBuffer := lnwallet.CalcFeeBuffer(
feePerKw, lntypes.WeightUnit(totalWeight),
)
return feeBuffer.ToSatoshis()
}
// CustomRecordsWithUnendorsed copies the map of custom records and adds an
// endorsed signal (replacing in the case of conflict) for assertion in tests.
func CustomRecordsWithUnendorsed(
originalRecords lnwire.CustomRecords) map[uint64][]byte {
return originalRecords.MergedCopy(map[uint64][]byte{
uint64(lnwire.ExperimentalEndorsementType): {
lnwire.ExperimentalUnendorsed,
}},
)
}
// LnrpcOutpointToStr returns a string representation of an lnrpc.OutPoint.
func LnrpcOutpointToStr(outpoint *lnrpc.OutPoint) string {
return fmt.Sprintf("%s:%d", outpoint.TxidStr, outpoint.OutputIndex)
}
package wait
import (
"fmt"
"time"
)
// PollInterval is a constant specifying a 200 ms interval.
const PollInterval = 200 * time.Millisecond
// Predicate is a helper test function that will wait for a timeout period of
// time until the passed predicate returns true. This function is helpful as
// timing doesn't always line up well when running integration tests with
// several running lnd nodes. This function gives callers a way to assert that
// some property is upheld within a particular time frame.
//
// TODO(yy): build a counter here so we know how many times we've tried the
// `pred`.
func Predicate(pred func() bool, timeout time.Duration) error {
exitTimer := time.After(timeout)
result := make(chan bool, 1)
for {
<-time.After(PollInterval)
go func() {
result <- pred()
}()
// Each time we call the pred(), we expect a result to be
// returned otherwise it will timeout.
select {
case <-exitTimer:
return fmt.Errorf("predicate not satisfied after " +
"time out")
case succeed := <-result:
if succeed {
return nil
}
}
}
}
// NoError is a wrapper around Predicate that waits for the passed method f to
// execute without error, and returns the last error encountered if this doesn't
// happen within the timeout.
func NoError(f func() error, timeout time.Duration) error {
var predErr error
pred := func() bool {
if err := f(); err != nil {
predErr = err
return false
}
return true
}
// If f() doesn't succeed within the timeout, return the last
// encountered error.
if err := Predicate(pred, timeout); err != nil {
// Handle the case where the passed in method, f, hangs for the
// full timeout
if predErr == nil {
return fmt.Errorf("method did not return within the " +
"timeout")
}
return predErr
}
return nil
}
// Invariant is a helper test function that will wait for a timeout period of
// time, verifying that a statement remains true for the entire duration. This
// function is helpful as timing doesn't always line up well when running
// integration tests with several running lnd nodes. This function gives callers
// a way to assert that some property is maintained over a particular time
// frame.
func Invariant(statement func() bool, timeout time.Duration) error {
const pollInterval = 20 * time.Millisecond
exitTimer := time.After(timeout)
for {
<-time.After(pollInterval)
// Fail if the invariant is broken while polling.
if !statement() {
return fmt.Errorf("invariant broken before time out")
}
select {
case <-exitTimer:
return nil
default:
}
}
}
// InvariantNoError is a wrapper around Invariant that waits out the duration
// specified by timeout. It fails if the predicate ever returns an error during
// that time.
func InvariantNoError(f func() error, timeout time.Duration) error {
var predErr error
pred := func() bool {
if err := f(); err != nil {
predErr = err
return false
}
return true
}
if err := Invariant(pred, timeout); err != nil {
return predErr
}
return nil
}
package lntypes
import "fmt"
// ChannelParty is a type used to have an unambiguous description of which node
// is being referred to. This eliminates the need to describe as "local" or
// "remote" using bool.
type ChannelParty uint8
const (
// Local is a ChannelParty constructor that is used to refer to the
// node that is running.
Local ChannelParty = iota
// Remote is a ChannelParty constructor that is used to refer to the
// node on the other end of the peer connection.
Remote
)
// String provides a string representation of ChannelParty (useful for logging).
func (p ChannelParty) String() string {
switch p {
case Local:
return "Local"
case Remote:
return "Remote"
default:
panic(fmt.Sprintf("invalid ChannelParty value: %d", p))
}
}
// CounterParty inverts the role of the ChannelParty.
func (p ChannelParty) CounterParty() ChannelParty {
switch p {
case Local:
return Remote
case Remote:
return Local
default:
panic(fmt.Sprintf("invalid ChannelParty value: %v", p))
}
}
// IsLocal returns true if the ChannelParty is Local.
func (p ChannelParty) IsLocal() bool {
return p == Local
}
// IsRemote returns true if the ChannelParty is Remote.
func (p ChannelParty) IsRemote() bool {
return p == Remote
}
// Dual represents a structure when we are tracking the same parameter for both
// the Local and Remote parties.
type Dual[A any] struct {
// Local is the value tracked for the Local ChannelParty.
Local A
// Remote is the value tracked for the Remote ChannelParty.
Remote A
}
// GetForParty gives Dual an access method that takes a ChannelParty as an
// argument. It is included for ergonomics in cases where the ChannelParty is
// in a variable and which party determines how we want to access the Dual.
func (d *Dual[A]) GetForParty(p ChannelParty) A {
switch p {
case Local:
return d.Local
case Remote:
return d.Remote
default:
panic(fmt.Sprintf(
"switch default triggered in ForParty: %v", p,
))
}
}
// SetForParty sets the value in the Dual for the given ChannelParty. This
// returns a copy of the original value.
func (d *Dual[A]) SetForParty(p ChannelParty, value A) {
switch p {
case Local:
d.Local = value
case Remote:
d.Remote = value
default:
panic(fmt.Sprintf(
"switch default triggered in ForParty: %v", p,
))
}
}
// ModifyForParty applies the function argument to the given ChannelParty field
// and returns a new copy of the Dual.
func (d *Dual[A]) ModifyForParty(p ChannelParty, f func(A) A) A {
switch p {
case Local:
d.Local = f(d.Local)
return d.Local
case Remote:
d.Remote = f(d.Remote)
return d.Remote
default:
panic(fmt.Sprintf(
"switch default triggered in ForParty: %v", p,
))
}
}
// MapDual applies the function argument to both the Local and Remote fields of
// the Dual[A] and returns a Dual[B] with that function applied.
func MapDual[A, B any](d Dual[A], f func(A) B) Dual[B] {
return Dual[B]{
Local: f(d.Local),
Remote: f(d.Remote),
}
}
var BothParties []ChannelParty = []ChannelParty{Local, Remote}
package lntypes
import (
"encoding/hex"
"fmt"
)
// HashSize of array used to store hashes.
const HashSize = 32
// ZeroHash is a predefined hash containing all zeroes.
var ZeroHash Hash
// Hash is used in several of the lightning messages and common structures. It
// typically represents a payment hash.
type Hash [HashSize]byte
// String returns the Hash as a hexadecimal string.
func (hash Hash) String() string {
return hex.EncodeToString(hash[:])
}
// MakeHash returns a new Hash from a byte slice. An error is returned if
// the number of bytes passed in is not HashSize.
func MakeHash(newHash []byte) (Hash, error) {
nhlen := len(newHash)
if nhlen != HashSize {
return Hash{}, fmt.Errorf("invalid hash length of %v, want %v",
nhlen, HashSize)
}
var hash Hash
copy(hash[:], newHash)
return hash, nil
}
// MakeHashFromStr creates a Hash from a hex hash string.
func MakeHashFromStr(newHash string) (Hash, error) {
// Return error if hash string is of incorrect length.
if len(newHash) != HashSize*2 {
return Hash{}, fmt.Errorf("invalid hash string length of %v, "+
"want %v", len(newHash), HashSize*2)
}
hash, err := hex.DecodeString(newHash)
if err != nil {
return Hash{}, err
}
return MakeHash(hash)
}
package lntypes
import (
"crypto/sha256"
"encoding/hex"
"fmt"
)
// PreimageSize of array used to store preimagees.
const PreimageSize = 32
// Preimage is used in several of the lightning messages and common structures.
// It represents a payment preimage.
type Preimage [PreimageSize]byte
// String returns the Preimage as a hexadecimal string.
func (p Preimage) String() string {
return hex.EncodeToString(p[:])
}
// MakePreimage returns a new Preimage from a bytes slice. An error is returned
// if the number of bytes passed in is not PreimageSize.
func MakePreimage(newPreimage []byte) (Preimage, error) {
nhlen := len(newPreimage)
if nhlen != PreimageSize {
return Preimage{}, fmt.Errorf("invalid preimage length of %v, "+
"want %v", nhlen, PreimageSize)
}
var preimage Preimage
copy(preimage[:], newPreimage)
return preimage, nil
}
// MakePreimageFromStr creates a Preimage from a hex preimage string.
func MakePreimageFromStr(newPreimage string) (Preimage, error) {
// Return error if preimage string is of incorrect length.
if len(newPreimage) != PreimageSize*2 {
return Preimage{}, fmt.Errorf("invalid preimage string length "+
"of %v, want %v", len(newPreimage), PreimageSize*2)
}
preimage, err := hex.DecodeString(newPreimage)
if err != nil {
return Preimage{}, err
}
return MakePreimage(preimage)
}
// Hash returns the sha256 hash of the preimage.
func (p *Preimage) Hash() Hash {
return Hash(sha256.Sum256(p[:]))
}
// Matches returns whether this preimage is the preimage of the given hash.
func (p *Preimage) Matches(h Hash) bool {
return h == p.Hash()
}
package lntypes
import (
"fmt"
"math"
)
// WeightUnit defines a unit to express the transaction size. One weight unit
// is 1/4_000_000 of the max block size. The tx weight is calculated using
// `Base tx size * 3 + Total tx size`.
// - Base tx size is size of the transaction serialized without the witness
// data.
// - Total tx size is the transaction size in bytes serialized according
// #BIP144.
type WeightUnit uint64
// ToVB converts a value expressed in weight units to virtual bytes.
func (wu WeightUnit) ToVB() VByte {
// According to BIP141: Virtual transaction size is defined as
// Transaction weight / 4 (rounded up to the next integer).
return VByte(math.Ceil(float64(wu) / 4))
}
// String returns the string representation of the weight unit.
func (wu WeightUnit) String() string {
return fmt.Sprintf("%d wu", wu)
}
// VByte defines a unit to express the transaction size. One virtual byte is
// 1/4th of a weight unit. The tx virtual bytes is calculated using `TxWeight /
// 4`.
type VByte uint64
// ToWU converts a value expressed in virtual bytes to weight units.
func (vb VByte) ToWU() WeightUnit {
return WeightUnit(vb * 4)
}
// String returns the string representation of the virtual byte.
func (vb VByte) String() string {
return fmt.Sprintf("%d vb", vb)
}
package lnutils
import (
"fmt"
"time"
)
// RecvOrTimeout attempts to recv over chan c, returning the value. If the
// timeout passes before the recv succeeds, an error is returned.
func RecvOrTimeout[T any](c <-chan T, timeout time.Duration) (*T, error) {
select {
case m := <-c:
return &m, nil
case <-time.After(timeout):
return nil, fmt.Errorf("timeout hit")
}
}
package lnutils
import "errors"
// ErrorAs behaves the same as `errors.As` except there's no need to declare
// the target error as a variable first.
// Instead of writing:
//
// var targetErr *TargetErr
// errors.As(err, &targetErr)
//
// We can write:
//
// lnutils.ErrorAs[*TargetErr](err)
//
// To save us from declaring the target error variable.
func ErrorAs[Target error](err error) bool {
var targetErr Target
return errors.As(err, &targetErr)
}
package lnutils
import (
"errors"
"fmt"
"os"
)
// CreateDir creates a directory if it doesn't exist and also handles
// symlink-related errors with user-friendly messages. It creates all necessary
// parent directories with the specified permissions.
func CreateDir(dir string, perm os.FileMode) error {
err := os.MkdirAll(dir, perm)
if err == nil {
return nil
}
// Show a nicer error message if it's because a symlink
// is linked to a directory that does not exist
// (probably because it's not mounted).
var pathErr *os.PathError
if errors.As(err, &pathErr) && os.IsExist(err) {
link, lerr := os.Readlink(pathErr.Path)
if lerr == nil {
return fmt.Errorf("is symlink %s -> %s "+
"mounted?", pathErr.Path, link)
}
}
return fmt.Errorf("failed to create directory '%s': %w", dir, err)
}
package lnutils
import (
"strings"
"github.com/davecgh/go-spew/spew"
)
// LogClosure is used to provide a closure over expensive logging operations so
// don't have to be performed when the logging level doesn't warrant it.
type LogClosure func() string
// String invokes the underlying function and returns the result.
func (c LogClosure) String() string {
return c()
}
// NewLogClosure returns a new closure over a function that returns a string
// which itself provides a Stringer interface so that it can be used with the
// logging system.
func NewLogClosure(c func() string) LogClosure {
return LogClosure(c)
}
// SpewLogClosure takes an interface and returns the string of it created from
// `spew.Sdump` in a LogClosure.
func SpewLogClosure(a any) LogClosure {
return func() string {
return spew.Sdump(a)
}
}
// NewSeparatorClosure returns a new closure that logs a separator line.
func NewSeparatorClosure() LogClosure {
return func() string {
return strings.Repeat("=", 80)
}
}
package lnutils
// Ptr returns the pointer of the given value. This is useful in instances
// where a function returns the value, but a pointer is wanted. Without this,
// then an intermediate variable is needed.
func Ptr[T any](v T) *T {
return &v
}
// ByteArray is a type constraint for type that reduces down to a fixed sized
// array.
type ByteArray interface {
~[32]byte
}
// ByteSlice takes a byte array, and returns a slice. This is useful when a
// function returns an array, but a slice is wanted. Without this, then an
// intermediate variable is needed.
func ByteSlice[T ByteArray](v T) []byte {
return v[:]
}
package lnutils
// Map takes an input slice, and applies the function f to each element,
// yielding a new slice.
func Map[T1, T2 any](s []T1, f func(T1) T2) []T2 {
r := make([]T2, len(s))
for i, v := range s {
r[i] = f(v)
}
return r
}
package lnutils
import "sync"
// SyncMap wraps a sync.Map with type parameters such that it's easier to
// access the items stored in the map since no type assertion is needed. It
// also requires explicit type definition when declaring and initiating the
// variables, which helps us understanding what's stored in a given map.
type SyncMap[K comparable, V any] struct {
sync.Map
}
// Store puts an item in the map.
func (m *SyncMap[K, V]) Store(key K, value V) {
m.Map.Store(key, value)
}
// Load queries an item from the map using the specified key. If the item
// cannot be found, an empty value and false will be returned. If the stored
// item fails the type assertion, a nil value and false will be returned.
func (m *SyncMap[K, V]) Load(key K) (V, bool) {
result, ok := m.Map.Load(key)
if !ok {
return *new(V), false // nolint: gocritic
}
item, ok := result.(V)
return item, ok
}
// Delete removes an item from the map specified by the key.
func (m *SyncMap[K, V]) Delete(key K) {
m.Map.Delete(key)
}
// LoadAndDelete queries an item and deletes it from the map using the
// specified key.
func (m *SyncMap[K, V]) LoadAndDelete(key K) (V, bool) {
result, loaded := m.Map.LoadAndDelete(key)
if !loaded {
return *new(V), loaded // nolint: gocritic
}
item, ok := result.(V)
return item, ok
}
// Range iterates the map and applies the `visitor` function. If the `visitor`
// returns false, the iteration will be stopped.
func (m *SyncMap[K, V]) Range(visitor func(K, V) bool) {
m.Map.Range(func(k any, v any) bool {
return visitor(k.(K), v.(V))
})
}
// ForEach iterates the map and applies the `visitor` function. Unlike the
// `Range` method, the `visitor` function will be applied to all the items
// unless there's an error.
func (m *SyncMap[K, V]) ForEach(visitor func(K, V) error) {
// rangeVisitor wraps the `visitor` function and returns false if
// there's an error returned from the `visitor` function.
rangeVisitor := func(k K, v V) bool {
if err := visitor(k, v); err != nil {
// Break the iteration if there's an error.
return false
}
return true
}
m.Range(rangeVisitor)
}
// Len returns the number of items in the map.
func (m *SyncMap[K, V]) Len() int {
var count int
m.Range(func(_ K, _ V) bool {
count++
return true
})
return count
}
// LoadOrStore queries an item from the map using the specified key. If the
// item cannot be found, the `value` will be stored in the map and returned.
// If the stored item fails the type assertion, a nil value and false will be
// returned.
func (m *SyncMap[K, V]) LoadOrStore(key K, value V) (V, bool) {
result, loaded := m.Map.LoadOrStore(key, value)
item, ok := result.(V)
if !ok {
return *new(V), false
}
return item, loaded
}
package lnwallet
import (
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tlv"
)
// CommitSortFunc is a function type alias for a function that sorts the
// commitment transaction outputs. The second parameter is a list of CLTV
// timeouts that must correspond to the number of transaction outputs, with the
// value of 0 for non-HTLC outputs. The HTLC indexes are needed to have a
// deterministic sort value for HTLCs that have the identical amount, CLTV
// timeout and payment hash (e.g. multiple MPP shards of the same payment, where
// the on-chain script would be identical).
type CommitSortFunc func(tx *wire.MsgTx, cltvs []uint32,
indexes []input.HtlcIndex) error
// DefaultCommitSort is the default commitment sort function that sorts the
// commitment transaction inputs and outputs according to BIP69. The second
// parameter is a list of CLTV timeouts that must correspond to the number of
// transaction outputs, with the value of 0 for non-HTLC outputs. The third
// parameter is unused for the default sort function.
func DefaultCommitSort(tx *wire.MsgTx, cltvs []uint32,
_ []input.HtlcIndex) error {
InPlaceCommitSort(tx, cltvs)
return nil
}
// CommitAuxLeaves stores two potential auxiliary leaves for the remote and
// local output that may be used to augment the final tapscript trees of the
// commitment transaction.
type CommitAuxLeaves struct {
// LocalAuxLeaf is the local party's auxiliary leaf.
LocalAuxLeaf input.AuxTapLeaf
// RemoteAuxLeaf is the remote party's auxiliary leaf.
RemoteAuxLeaf input.AuxTapLeaf
// OutgoingHTLCLeaves is the set of aux leaves for the outgoing HTLCs
// on this commitment transaction.
OutgoingHtlcLeaves input.HtlcAuxLeaves
// IncomingHTLCLeaves is the set of aux leaves for the incoming HTLCs
// on this commitment transaction.
IncomingHtlcLeaves input.HtlcAuxLeaves
}
// AuxChanState is a struct that holds certain fields of the
// channeldb.OpenChannel struct that are used by the aux components. The data
// is copied over to prevent accidental mutation of the original channel state.
type AuxChanState struct {
// ChanType denotes which type of channel this is.
ChanType channeldb.ChannelType
// FundingOutpoint is the outpoint of the final funding transaction.
// This value uniquely and globally identifies the channel within the
// target blockchain as specified by the chain hash parameter.
FundingOutpoint wire.OutPoint
// ShortChannelID encodes the exact location in the chain in which the
// channel was initially confirmed. This includes: the block height,
// transaction index, and the output within the target transaction.
//
// If IsZeroConf(), then this will the "base" (very first) ALIAS scid
// and the confirmed SCID will be stored in ConfirmedScid.
ShortChannelID lnwire.ShortChannelID
// IsInitiator is a bool which indicates if we were the original
// initiator for the channel. This value may affect how higher levels
// negotiate fees, or close the channel.
IsInitiator bool
// Capacity is the total capacity of this channel.
Capacity btcutil.Amount
// LocalChanCfg is the channel configuration for the local node.
LocalChanCfg channeldb.ChannelConfig
// RemoteChanCfg is the channel configuration for the remote node.
RemoteChanCfg channeldb.ChannelConfig
// ThawHeight is the height when a frozen channel once again becomes a
// normal channel. If this is zero, then there're no restrictions on
// this channel. If the value is lower than 500,000, then it's
// interpreted as a relative height, or an absolute height otherwise.
ThawHeight uint32
// TapscriptRoot is an optional tapscript root used to derive the MuSig2
// funding output.
TapscriptRoot fn.Option[chainhash.Hash]
// CustomBlob is an optional blob that can be used to store information
// specific to a custom channel type. This information is only created
// at channel funding time, and after wards is to be considered
// immutable.
CustomBlob fn.Option[tlv.Blob]
}
// NewAuxChanState creates a new AuxChanState from the given channel state.
func NewAuxChanState(chanState *channeldb.OpenChannel) AuxChanState {
return AuxChanState{
ChanType: chanState.ChanType,
FundingOutpoint: chanState.FundingOutpoint,
ShortChannelID: chanState.ShortChannelID,
IsInitiator: chanState.IsInitiator,
Capacity: chanState.Capacity,
LocalChanCfg: chanState.LocalChanCfg,
RemoteChanCfg: chanState.RemoteChanCfg,
ThawHeight: chanState.ThawHeight,
TapscriptRoot: chanState.TapscriptRoot,
CustomBlob: chanState.CustomBlob,
}
}
// CommitDiffAuxInput is the input required to compute the diff of the auxiliary
// leaves for a commitment transaction.
type CommitDiffAuxInput struct {
// ChannelState is the static channel information of the channel this
// commitment transaction relates to.
ChannelState AuxChanState
// PrevBlob is the blob of the previous commitment transaction.
PrevBlob tlv.Blob
// UnfilteredView is the unfiltered, original HTLC view of the channel.
// Unfiltered in this context means that the view contains all HTLCs,
// including the canceled ones.
UnfilteredView AuxHtlcView
// WhoseCommit denotes whose commitment transaction we are computing the
// diff for.
WhoseCommit lntypes.ChannelParty
// OurBalance is the balance of the local party.
OurBalance lnwire.MilliSatoshi
// TheirBalance is the balance of the remote party.
TheirBalance lnwire.MilliSatoshi
// KeyRing is the key ring that can be used to derive keys for the
// commitment transaction.
KeyRing CommitmentKeyRing
}
// CommitDiffAuxResult is the result of computing the diff of the auxiliary
// leaves for a commitment transaction.
type CommitDiffAuxResult struct {
// AuxLeaves are the auxiliary leaves for the new commitment
// transaction.
AuxLeaves fn.Option[CommitAuxLeaves]
// CommitSortFunc is an optional function that sorts the commitment
// transaction inputs and outputs.
CommitSortFunc fn.Option[CommitSortFunc]
}
// AuxLeafStore is used to optionally fetch auxiliary tapscript leaves for the
// commitment transaction given an opaque blob. This is also used to implement
// a state transition function for the blobs to allow them to be refreshed with
// each state.
type AuxLeafStore interface {
// FetchLeavesFromView attempts to fetch the auxiliary leaves that
// correspond to the passed aux blob, and pending original (unfiltered)
// HTLC view.
FetchLeavesFromView(
in CommitDiffAuxInput) fn.Result[CommitDiffAuxResult]
// FetchLeavesFromCommit attempts to fetch the auxiliary leaves that
// correspond to the passed aux blob, and an existing channel
// commitment.
FetchLeavesFromCommit(chanState AuxChanState,
commit channeldb.ChannelCommitment, keyRing CommitmentKeyRing,
whoseCommit lntypes.ChannelParty) fn.Result[CommitDiffAuxResult]
// FetchLeavesFromRevocation attempts to fetch the auxiliary leaves
// from a channel revocation that stores balance + blob information.
FetchLeavesFromRevocation(
r *channeldb.RevocationLog) fn.Result[CommitDiffAuxResult]
// ApplyHtlcView serves as the state transition function for the custom
// channel's blob. Given the old blob, and an HTLC view, then a new
// blob should be returned that reflects the pending updates.
ApplyHtlcView(in CommitDiffAuxInput) fn.Result[fn.Option[tlv.Blob]]
}
// auxLeavesFromView is used to derive the set of commit aux leaves (if any),
// that are needed to create a new commitment transaction using the original
// (unfiltered) htlc view.
func auxLeavesFromView(leafStore AuxLeafStore, chanState *channeldb.OpenChannel,
prevBlob fn.Option[tlv.Blob], originalView *HtlcView,
whoseCommit lntypes.ChannelParty, ourBalance,
theirBalance lnwire.MilliSatoshi,
keyRing CommitmentKeyRing) fn.Result[CommitDiffAuxResult] {
return fn.MapOptionZ(
prevBlob, func(blob tlv.Blob) fn.Result[CommitDiffAuxResult] {
return leafStore.FetchLeavesFromView(CommitDiffAuxInput{
ChannelState: NewAuxChanState(chanState),
PrevBlob: blob,
UnfilteredView: newAuxHtlcView(originalView),
WhoseCommit: whoseCommit,
OurBalance: ourBalance,
TheirBalance: theirBalance,
KeyRing: keyRing,
})
},
)
}
// updateAuxBlob is a helper function that attempts to update the aux blob
// given the prior and current state information.
func updateAuxBlob(leafStore AuxLeafStore, chanState *channeldb.OpenChannel,
prevBlob fn.Option[tlv.Blob], nextViewUnfiltered *HtlcView,
whoseCommit lntypes.ChannelParty, ourBalance,
theirBalance lnwire.MilliSatoshi,
keyRing CommitmentKeyRing) fn.Result[fn.Option[tlv.Blob]] {
return fn.MapOptionZ(
prevBlob, func(blob tlv.Blob) fn.Result[fn.Option[tlv.Blob]] {
return leafStore.ApplyHtlcView(CommitDiffAuxInput{
ChannelState: NewAuxChanState(chanState),
PrevBlob: blob,
UnfilteredView: newAuxHtlcView(
nextViewUnfiltered,
),
WhoseCommit: whoseCommit,
OurBalance: ourBalance,
TheirBalance: theirBalance,
KeyRing: keyRing,
})
},
)
}
package lnwallet
import (
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tlv"
)
// htlcCustomSigType is the TLV type that is used to encode the custom HTLC
// signatures within the custom data for an existing HTLC.
var htlcCustomSigType tlv.TlvType65543
// AuxHtlcView is a struct that contains a safe copy of an HTLC view that can
// be used by aux components.
type AuxHtlcView struct {
// NextHeight is the height of the commitment transaction that will be
// created using this view.
NextHeight uint64
// Updates is a Dual of the Local and Remote HTLCs.
Updates lntypes.Dual[[]AuxHtlcDescriptor]
// FeePerKw is the fee rate in sat/kw of the commitment transaction.
FeePerKw chainfee.SatPerKWeight
}
// newAuxHtlcView creates a new safe copy of the HTLC view that can be used by
// aux components.
//
// NOTE: This function should only be called while holding the channel's read
// lock, since the underlying local/remote payment descriptors are accessed
// directly.
func newAuxHtlcView(v *HtlcView) AuxHtlcView {
return AuxHtlcView{
NextHeight: v.NextHeight,
Updates: lntypes.Dual[[]AuxHtlcDescriptor]{
Local: fn.Map(v.Updates.Local, newAuxHtlcDescriptor),
Remote: fn.Map(v.Updates.Remote, newAuxHtlcDescriptor),
},
FeePerKw: v.FeePerKw,
}
}
// AuxHtlcDescriptor is a struct that contains the information needed to sign or
// verify an HTLC for custom channels.
type AuxHtlcDescriptor struct {
// ChanID is the ChannelID of the LightningChannel that this
// paymentDescriptor belongs to. We track this here so we can
// reconstruct the Messages that this paymentDescriptor is built from.
ChanID lnwire.ChannelID
// RHash is the payment hash for this HTLC. The HTLC can be settled iff
// the preimage to this hash is presented.
RHash PaymentHash
// Timeout is the absolute timeout in blocks, after which this HTLC
// expires.
Timeout uint32
// Amount is the HTLC amount in milli-satoshis.
Amount lnwire.MilliSatoshi
// HtlcIndex is the index within the main update log for this HTLC.
// Entries within the log of type Add will have this field populated,
// as other entries will point to the entry via this counter.
//
// NOTE: This field will only be populated if EntryType is Add.
HtlcIndex uint64
// ParentIndex is the HTLC index of the entry that this update settles
// or times out.
//
// NOTE: This field will only be populated if EntryType is Fail or
// Settle.
ParentIndex uint64
// EntryType denotes the exact type of the paymentDescriptor. In the
// case of a Timeout, or Settle type, then the Parent field will point
// into the log to the HTLC being modified.
EntryType updateType
// CustomRecords also stores the set of optional custom records that
// may have been attached to a sent HTLC.
CustomRecords lnwire.CustomRecords
// addCommitHeight[Remote|Local] encodes the height of the commitment
// which included this HTLC on either the remote or local commitment
// chain. This value is used to determine when an HTLC is fully
// "locked-in".
addCommitHeightRemote uint64
addCommitHeightLocal uint64
// removeCommitHeight[Remote|Local] encodes the height of the
// commitment which removed the parent pointer of this
// paymentDescriptor either due to a timeout or a settle. Once both
// these heights are below the tail of both chains, the log entries can
// safely be removed.
removeCommitHeightRemote uint64
removeCommitHeightLocal uint64
}
// AddHeight returns the height at which the HTLC was added to the commitment
// chain. The height is returned based on the chain the HTLC is being added to
// (local or remote chain).
func (a *AuxHtlcDescriptor) AddHeight(
whoseCommitChain lntypes.ChannelParty) uint64 {
if whoseCommitChain.IsRemote() {
return a.addCommitHeightRemote
}
return a.addCommitHeightLocal
}
// RemoveHeight returns the height at which the HTLC was removed from the
// commitment chain. The height is returned based on the chain the HTLC is being
// removed from (local or remote chain).
func (a *AuxHtlcDescriptor) RemoveHeight(
whoseCommitChain lntypes.ChannelParty) uint64 {
if whoseCommitChain.IsRemote() {
return a.removeCommitHeightRemote
}
return a.removeCommitHeightLocal
}
// newAuxHtlcDescriptor creates a new AuxHtlcDescriptor from a payment
// descriptor.
//
// NOTE: This function should only be called while holding the channel's read
// lock, since the underlying payment descriptors are accessed directly.
func newAuxHtlcDescriptor(p *paymentDescriptor) AuxHtlcDescriptor {
return AuxHtlcDescriptor{
ChanID: p.ChanID,
RHash: p.RHash,
Timeout: p.Timeout,
Amount: p.Amount,
HtlcIndex: p.HtlcIndex,
ParentIndex: p.ParentIndex,
EntryType: p.EntryType,
CustomRecords: p.CustomRecords.Copy(),
addCommitHeightRemote: p.addCommitHeights.Remote,
addCommitHeightLocal: p.addCommitHeights.Local,
removeCommitHeightRemote: p.removeCommitHeights.Remote,
removeCommitHeightLocal: p.removeCommitHeights.Local,
}
}
// BaseAuxJob is a struct that contains the common fields that are shared among
// the aux sign/verify jobs.
type BaseAuxJob struct {
// OutputIndex is the output index of the HTLC on the commitment
// transaction being signed.
//
// NOTE: If the output is dust from the PoV of the commitment chain,
// then this value will be -1.
OutputIndex int32
// KeyRing is the commitment key ring that contains the keys needed to
// generate the second level HTLC signatures.
KeyRing CommitmentKeyRing
// HTLC is the HTLC that is being signed or verified.
HTLC AuxHtlcDescriptor
// Incoming is a boolean that indicates if the HTLC is incoming or
// outgoing.
Incoming bool
// CommitBlob is the commitment transaction blob that contains the aux
// information for this channel.
CommitBlob fn.Option[tlv.Blob]
// HtlcLeaf is the aux tap leaf that corresponds to the HTLC being
// signed/verified.
HtlcLeaf input.AuxTapLeaf
}
// AuxSigJob is a struct that contains all the information needed to sign an
// HTLC for custom channels.
type AuxSigJob struct {
// SignDesc is the sign desc for this HTLC.
SignDesc input.SignDescriptor
BaseAuxJob
// Resp is a channel that will be used to send the result of the sign
// job. This channel MUST be buffered.
Resp chan AuxSigJobResp
// Cancel is a channel that is closed by the caller if they wish to
// abandon all pending sign jobs part of a single batch. This should
// never be closed by the validator.
Cancel <-chan struct{}
}
// NewAuxSigJob creates a new AuxSigJob.
func NewAuxSigJob(sigJob SignJob, keyRing CommitmentKeyRing, incoming bool,
htlc AuxHtlcDescriptor, commitBlob fn.Option[tlv.Blob],
htlcLeaf input.AuxTapLeaf, cancelChan <-chan struct{}) AuxSigJob {
return AuxSigJob{
SignDesc: sigJob.SignDesc,
BaseAuxJob: BaseAuxJob{
OutputIndex: sigJob.OutputIndex,
KeyRing: keyRing,
HTLC: htlc,
Incoming: incoming,
CommitBlob: commitBlob,
HtlcLeaf: htlcLeaf,
},
Resp: make(chan AuxSigJobResp, 1),
Cancel: cancelChan,
}
}
// AuxSigJobResp is a struct that contains the result of a sign job.
type AuxSigJobResp struct {
// SigBlob is the signature blob that was generated for the HTLC. This
// is an opaque TLV field that may contain the signature and other data.
SigBlob fn.Option[tlv.Blob]
// HtlcIndex is the index of the HTLC that was signed.
HtlcIndex uint64
// Err is the error that occurred when executing the specified
// signature job. In the case that no error occurred, this value will
// be nil.
Err error
}
// AuxVerifyJob is a struct that contains all the information needed to verify
// an HTLC for custom channels.
type AuxVerifyJob struct {
// SigBlob is the signature blob that was generated for the HTLC. This
// is an opaque TLV field that may contain the signature and other data.
SigBlob fn.Option[tlv.Blob]
BaseAuxJob
}
// NewAuxVerifyJob creates a new AuxVerifyJob.
func NewAuxVerifyJob(sig fn.Option[tlv.Blob], keyRing CommitmentKeyRing,
incoming bool, htlc AuxHtlcDescriptor, commitBlob fn.Option[tlv.Blob],
htlcLeaf input.AuxTapLeaf) AuxVerifyJob {
return AuxVerifyJob{
SigBlob: sig,
BaseAuxJob: BaseAuxJob{
KeyRing: keyRing,
HTLC: htlc,
Incoming: incoming,
CommitBlob: commitBlob,
HtlcLeaf: htlcLeaf,
},
}
}
// AuxSigner is an interface that is used to sign and verify HTLCs for custom
// channels. It is similar to the existing SigPool, but uses opaque blobs to
// shuffle around signature information and other metadata.
type AuxSigner interface {
// SubmitSecondLevelSigBatch takes a batch of aux sign jobs and
// processes them asynchronously.
SubmitSecondLevelSigBatch(chanState AuxChanState, commitTx *wire.MsgTx,
sigJob []AuxSigJob) error
// PackSigs takes a series of aux signatures and packs them into a
// single blob that can be sent alongside the CommitSig messages.
PackSigs([]fn.Option[tlv.Blob]) fn.Result[fn.Option[tlv.Blob]]
// UnpackSigs takes a packed blob of signatures and returns the
// original signatures for each HTLC, keyed by HTLC index.
UnpackSigs(fn.Option[tlv.Blob]) fn.Result[[]fn.Option[tlv.Blob]]
// VerifySecondLevelSigs attempts to synchronously verify a batch of aux
// sig jobs.
VerifySecondLevelSigs(chanState AuxChanState, commitTx *wire.MsgTx,
verifyJob []AuxVerifyJob) error
}
package btcwallet
import (
"encoding/hex"
"errors"
"fmt"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/chain"
"github.com/lightninglabs/neutrino"
"github.com/lightninglabs/neutrino/headerfs"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet"
)
var (
// ErrOutputSpent is returned by the GetUtxo method if the target output
// for lookup has already been spent.
ErrOutputSpent = errors.New("target output has been spent")
// ErrOutputNotFound signals that the desired output could not be
// located.
ErrOutputNotFound = errors.New("target output was not found")
)
// GetBestBlock returns the current height and hash of the best known block
// within the main chain.
//
// This method is a part of the lnwallet.BlockChainIO interface.
func (b *BtcWallet) GetBestBlock() (*chainhash.Hash, int32, error) {
return b.chain.GetBestBlock()
}
// GetUtxo returns the original output referenced by the passed outpoint that
// creates the target pkScript.
//
// This method is a part of the lnwallet.BlockChainIO interface.
func (b *BtcWallet) GetUtxo(op *wire.OutPoint, pkScript []byte,
heightHint uint32, cancel <-chan struct{}) (*wire.TxOut, error) {
switch backend := b.chain.(type) {
case *chain.NeutrinoClient:
spendReport, err := backend.CS.GetUtxo(
neutrino.WatchInputs(neutrino.InputWithScript{
OutPoint: *op,
PkScript: pkScript,
}),
neutrino.StartBlock(&headerfs.BlockStamp{
Height: int32(heightHint),
}),
neutrino.QuitChan(cancel),
)
if err != nil {
return nil, err
}
// If the spend report is nil, then the output was not found in
// the rescan.
if spendReport == nil {
return nil, ErrOutputNotFound
}
// If the spending transaction is populated in the spend report,
// this signals that the output has already been spent.
if spendReport.SpendingTx != nil {
return nil, ErrOutputSpent
}
// Otherwise, the output is assumed to be in the UTXO.
return spendReport.Output, nil
case *chain.RPCClient:
txout, err := backend.GetTxOut(&op.Hash, op.Index, false)
if err != nil {
return nil, err
} else if txout == nil {
return nil, ErrOutputSpent
}
pkScript, err := hex.DecodeString(txout.ScriptPubKey.Hex)
if err != nil {
return nil, err
}
// We'll ensure we properly convert the amount given in BTC to
// satoshis.
amt, err := btcutil.NewAmount(txout.Value)
if err != nil {
return nil, err
}
return &wire.TxOut{
Value: int64(amt),
PkScript: pkScript,
}, nil
case *chain.BitcoindClient:
txout, err := backend.GetTxOut(&op.Hash, op.Index, false)
if err != nil {
return nil, err
} else if txout == nil {
return nil, ErrOutputSpent
}
pkScript, err := hex.DecodeString(txout.ScriptPubKey.Hex)
if err != nil {
return nil, err
}
// Sadly, gettxout returns the output value in BTC instead of
// satoshis.
amt, err := btcutil.NewAmount(txout.Value)
if err != nil {
return nil, err
}
return &wire.TxOut{
Value: int64(amt),
PkScript: pkScript,
}, nil
default:
return nil, fmt.Errorf("unknown backend")
}
}
// GetBlock returns a raw block from the server given its hash. For the Neutrino
// implementation of the lnwallet.BlockChainIO interface, the Neutrino GetBlock
// method is called directly. For other implementations, the block cache is used
// to wrap the call to GetBlock.
//
// This method is a part of the lnwallet.BlockChainIO interface.
func (b *BtcWallet) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) {
_, ok := b.chain.(*chain.NeutrinoClient)
if !ok {
return b.blockCache.GetBlock(blockHash, b.chain.GetBlock)
}
// For the neutrino implementation of lnwallet.BlockChainIO the neutrino
// GetBlock function can be called directly since it uses the same block
// cache. However, it does not lock the block cache mutex for the given
// block hash and so that is done here.
b.blockCache.HashMutex.Lock(lntypes.Hash(*blockHash))
defer b.blockCache.HashMutex.Unlock(lntypes.Hash(*blockHash))
return b.chain.GetBlock(blockHash)
}
// GetBlockHeader returns a block header for the block with the given hash.
//
// This method is a part of the lnwallet.BlockChainIO interface.
func (b *BtcWallet) GetBlockHeader(
blockHash *chainhash.Hash) (*wire.BlockHeader, error) {
return b.chain.GetBlockHeader(blockHash)
}
// GetBlockHash returns the hash of the block in the best blockchain at the
// given height.
//
// This method is a part of the lnwallet.BlockChainIO interface.
func (b *BtcWallet) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) {
return b.chain.GetBlockHash(blockHeight)
}
// A compile time check to ensure that BtcWallet implements the BlockChainIO
// interface.
var _ lnwallet.WalletController = (*BtcWallet)(nil)
package btcwallet
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"math"
"sync"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet"
base "github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/wallet/txauthor"
"github.com/btcsuite/btcwallet/wallet/txrules"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/blockcache"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)
const (
defaultAccount = uint32(waddrmgr.DefaultAccountNum)
importedAccount = uint32(waddrmgr.ImportedAddrAccount)
// dryRunImportAccountNumAddrs represents the number of addresses we'll
// derive for an imported account's external and internal branch when a
// dry run is attempted.
dryRunImportAccountNumAddrs = 5
// UnconfirmedHeight is the special case end height that is used to
// obtain unconfirmed transactions from ListTransactionDetails.
UnconfirmedHeight int32 = -1
// walletMetaBucket is used to store wallet metadata.
walletMetaBucket = "lnwallet"
// walletReadyKey is used to indicate that the wallet has been
// initialized.
walletReadyKey = "ready"
)
var (
// lightningAddrSchema is the scope addr schema for all keys that we
// derive. We'll treat them all as p2wkh addresses, as atm we must
// specify a particular type.
lightningAddrSchema = waddrmgr.ScopeAddrSchema{
ExternalAddrType: waddrmgr.WitnessPubKey,
InternalAddrType: waddrmgr.WitnessPubKey,
}
// LndDefaultKeyScopes is the list of default key scopes that lnd adds
// to its wallet.
LndDefaultKeyScopes = []waddrmgr.KeyScope{
waddrmgr.KeyScopeBIP0049Plus,
waddrmgr.KeyScopeBIP0084,
waddrmgr.KeyScopeBIP0086,
}
// errNoImportedAddrGen is an error returned when a new address is
// requested for the default imported account within the wallet.
errNoImportedAddrGen = errors.New("addresses cannot be generated for " +
"the default imported account")
)
// BtcWallet is an implementation of the lnwallet.WalletController interface
// backed by an active instance of btcwallet. At the time of the writing of
// this documentation, this implementation requires a full btcd node to
// operate.
type BtcWallet struct {
// wallet is an active instance of btcwallet.
wallet *base.Wallet
chain chain.Interface
db walletdb.DB
cfg *Config
netParams *chaincfg.Params
chainKeyScope waddrmgr.KeyScope
blockCache *blockcache.BlockCache
*input.MusigSessionManager
}
// A compile time check to ensure that BtcWallet implements the
// WalletController and BlockChainIO interfaces.
var _ lnwallet.WalletController = (*BtcWallet)(nil)
var _ lnwallet.BlockChainIO = (*BtcWallet)(nil)
// New returns a new fully initialized instance of BtcWallet given a valid
// configuration struct.
func New(cfg Config, blockCache *blockcache.BlockCache) (*BtcWallet, error) {
// Create the key scope for the coin type being managed by this wallet.
chainKeyScope := waddrmgr.KeyScope{
Purpose: keychain.BIP0043Purpose,
Coin: cfg.CoinType,
}
// Maybe the wallet has already been opened and unlocked by the
// WalletUnlocker. So if we get a non-nil value from the config,
// we assume everything is in order.
var wallet = cfg.Wallet
if wallet == nil {
// No ready wallet was passed, so try to open an existing one.
var pubPass []byte
if cfg.PublicPass == nil {
pubPass = defaultPubPassphrase
} else {
pubPass = cfg.PublicPass
}
loader, err := NewWalletLoader(
cfg.NetParams, cfg.RecoveryWindow, cfg.LoaderOptions...,
)
if err != nil {
return nil, err
}
walletExists, err := loader.WalletExists()
if err != nil {
return nil, err
}
if !walletExists {
// Wallet has never been created, perform initial
// set up.
wallet, err = loader.CreateNewWallet(
pubPass, cfg.PrivatePass, cfg.HdSeed,
cfg.Birthday,
)
if err != nil {
return nil, err
}
} else {
// Wallet has been created and been initialized at
// this point, open it along with all the required DB
// namespaces, and the DB itself.
wallet, err = loader.OpenExistingWallet(pubPass, false)
if err != nil {
return nil, err
}
}
}
finalWallet := &BtcWallet{
cfg: &cfg,
wallet: wallet,
db: wallet.Database(),
chain: cfg.ChainSource,
netParams: cfg.NetParams,
chainKeyScope: chainKeyScope,
blockCache: blockCache,
}
finalWallet.MusigSessionManager = input.NewMusigSessionManager(
finalWallet.fetchPrivKey,
)
return finalWallet, nil
}
// loaderCfg holds optional wallet loader configuration.
type loaderCfg struct {
dbDirPath string
noFreelistSync bool
dbTimeout time.Duration
useLocalDB bool
externalDB kvdb.Backend
}
// LoaderOption is a functional option to update the optional loader config.
type LoaderOption func(*loaderCfg)
// LoaderWithLocalWalletDB configures the wallet loader to use the local db.
func LoaderWithLocalWalletDB(dbDirPath string, noFreelistSync bool,
dbTimeout time.Duration) LoaderOption {
return func(cfg *loaderCfg) {
cfg.dbDirPath = dbDirPath
cfg.noFreelistSync = noFreelistSync
cfg.dbTimeout = dbTimeout
cfg.useLocalDB = true
}
}
// LoaderWithExternalWalletDB configures the wallet loadr to use an external db.
func LoaderWithExternalWalletDB(db kvdb.Backend) LoaderOption {
return func(cfg *loaderCfg) {
cfg.externalDB = db
}
}
// NewWalletLoader constructs a wallet loader.
func NewWalletLoader(chainParams *chaincfg.Params, recoveryWindow uint32,
opts ...LoaderOption) (*base.Loader, error) {
cfg := &loaderCfg{}
// Apply all functional options.
for _, o := range opts {
o(cfg)
}
if cfg.externalDB != nil && cfg.useLocalDB {
return nil, fmt.Errorf("wallet can either be in the local or " +
"an external db")
}
if cfg.externalDB != nil {
loader, err := base.NewLoaderWithDB(
chainParams, recoveryWindow, cfg.externalDB,
func() (bool, error) {
return externalWalletExists(cfg.externalDB)
},
)
if err != nil {
return nil, err
}
// Decorate wallet db with out own key such that we
// can always check whether the wallet exists or not.
loader.OnWalletCreated(onWalletCreated)
return loader, nil
}
return base.NewLoader(
chainParams, cfg.dbDirPath, cfg.noFreelistSync,
cfg.dbTimeout, recoveryWindow,
), nil
}
// externalWalletExists is a helper function that we use to template btcwallet's
// Loader in order to be able check if the wallet database has been initialized
// in an external DB.
func externalWalletExists(db kvdb.Backend) (bool, error) {
exists := false
err := kvdb.View(db, func(tx kvdb.RTx) error {
metaBucket := tx.ReadBucket([]byte(walletMetaBucket))
if metaBucket != nil {
walletReady := metaBucket.Get([]byte(walletReadyKey))
exists = string(walletReady) == walletReadyKey
}
return nil
}, func() {})
return exists, err
}
// onWalletCreated is executed when btcwallet creates the wallet the first time.
func onWalletCreated(tx kvdb.RwTx) error {
metaBucket, err := tx.CreateTopLevelBucket([]byte(walletMetaBucket))
if err != nil {
return err
}
return metaBucket.Put([]byte(walletReadyKey), []byte(walletReadyKey))
}
// BackEnd returns the underlying ChainService's name as a string.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) BackEnd() string {
if b.chain != nil {
return b.chain.BackEnd()
}
return ""
}
// InternalWallet returns a pointer to the internal base wallet which is the
// core of btcwallet.
func (b *BtcWallet) InternalWallet() *base.Wallet {
return b.wallet
}
// Start initializes the underlying rpc connection, the wallet itself, and
// begins syncing to the current available blockchain state.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) Start() error {
// Is the wallet (according to its database) currently watch-only
// already? If it is, we won't need to convert it later.
walletIsWatchOnly := b.wallet.Manager.WatchOnly()
// If the wallet is watch-only, but we don't expect it to be, then we
// are in an unexpected state and cannot continue.
if walletIsWatchOnly && !b.cfg.WatchOnly {
return fmt.Errorf("wallet is watch-only but we expect it " +
"not to be; check if remote signing was disabled by " +
"accident")
}
// We'll start by unlocking the wallet and ensuring that the KeyScope:
// (1017, 1) exists within the internal waddrmgr. We'll need this in
// order to properly generate the keys required for signing various
// contracts. If this is a watch-only wallet, we don't have any private
// keys and therefore unlocking is not necessary.
if !walletIsWatchOnly {
if err := b.wallet.Unlock(b.cfg.PrivatePass, nil); err != nil {
return err
}
// If the wallet isn't about to be converted, we need to inform
// the user that this wallet still contains all private key
// material and that they need to migrate the existing wallet.
if b.cfg.WatchOnly && !b.cfg.MigrateWatchOnly {
log.Warnf("Wallet is expected to be in watch-only " +
"mode but hasn't been migrated to watch-only " +
"yet, it still contains private keys; " +
"consider turning on the watch-only wallet " +
"migration in remote signing mode")
}
}
// Because we might add new "default" key scopes over time, they are
// created correctly for new wallets. Existing wallets don't
// automatically add them, we need to do that manually now.
for _, scope := range LndDefaultKeyScopes {
_, err := b.wallet.Manager.FetchScopedKeyManager(scope)
if waddrmgr.IsError(err, waddrmgr.ErrScopeNotFound) {
// The default scope wasn't found, that probably means
// it was added recently and older wallets don't know it
// yet. Let's add it now.
addrSchema := waddrmgr.ScopeAddrMap[scope]
_, err := b.wallet.AddScopeManager(scope, addrSchema)
if err != nil {
return err
}
}
}
scope, err := b.wallet.Manager.FetchScopedKeyManager(b.chainKeyScope)
if err != nil {
// If the scope hasn't yet been created (it wouldn't been
// loaded by default if it was), then we'll manually create the
// scope for the first time ourselves.
manager, err := b.wallet.AddScopeManager(
b.chainKeyScope, lightningAddrSchema,
)
if err != nil {
return err
}
scope = manager
}
// If the wallet is not watch-only atm, and the user wants to migrate it
// to watch-only, we will set `convertToWatchOnly` to true so the wallet
// accounts are created and converted.
convertToWatchOnly := !walletIsWatchOnly && b.cfg.WatchOnly &&
b.cfg.MigrateWatchOnly
// Now that the wallet is unlocked, we'll go ahead and make sure we
// create accounts for all the key families we're going to use. This
// will make it possible to list all the account/family xpubs in the
// wallet list RPC.
err = b.wallet.InitAccounts(scope, convertToWatchOnly, 255)
if err != nil {
return err
}
// Establish an RPC connection in addition to starting the goroutines
// in the underlying wallet.
if err := b.chain.Start(); err != nil {
return err
}
// Start the underlying btcwallet core.
b.wallet.Start()
// Pass the rpc client into the wallet so it can sync up to the
// current main chain.
b.wallet.SynchronizeRPC(b.chain)
return nil
}
// Stop signals the wallet for shutdown. Shutdown may entail closing
// any active sockets, database handles, stopping goroutines, etc.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) Stop() error {
b.wallet.Stop()
b.wallet.WaitForShutdown()
b.chain.Stop()
return nil
}
// ConfirmedBalance returns the sum of all the wallet's unspent outputs that
// have at least confs confirmations. If confs is set to zero, then all unspent
// outputs, including those currently in the mempool will be included in the
// final sum. The account parameter serves as a filter to retrieve the balance
// for a specific account. When empty, the confirmed balance of all wallet
// accounts is returned.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) ConfirmedBalance(confs int32,
accountFilter string) (btcutil.Amount, error) {
var balance btcutil.Amount
witnessOutputs, err := b.ListUnspentWitness(
confs, math.MaxInt32, accountFilter,
)
if err != nil {
return 0, err
}
for _, witnessOutput := range witnessOutputs {
balance += witnessOutput.Value
}
return balance, nil
}
// keyScopeForAccountAddr determines the appropriate key scope of an account
// based on its name/address type.
func (b *BtcWallet) keyScopeForAccountAddr(accountName string,
addrType lnwallet.AddressType) (waddrmgr.KeyScope, uint32, error) {
// Map the requested address type to its key scope.
var addrKeyScope waddrmgr.KeyScope
switch addrType {
case lnwallet.WitnessPubKey:
addrKeyScope = waddrmgr.KeyScopeBIP0084
case lnwallet.NestedWitnessPubKey:
addrKeyScope = waddrmgr.KeyScopeBIP0049Plus
case lnwallet.TaprootPubkey:
addrKeyScope = waddrmgr.KeyScopeBIP0086
default:
return waddrmgr.KeyScope{}, 0,
fmt.Errorf("unknown address type")
}
// The default account spans across multiple key scopes, so the
// requested address type should already be valid for this account.
if accountName == lnwallet.DefaultAccountName {
return addrKeyScope, defaultAccount, nil
}
// Otherwise, look up the custom account and if it supports the given
// key scope.
accountNumber, err := b.wallet.AccountNumber(addrKeyScope, accountName)
if err != nil {
return waddrmgr.KeyScope{}, 0, err
}
return addrKeyScope, accountNumber, nil
}
// NewAddress returns the next external or internal address for the wallet
// dictated by the value of the `change` parameter. If change is true, then an
// internal address will be returned, otherwise an external address should be
// returned. The account parameter must be non-empty as it determines which
// account the address should be generated from.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) NewAddress(t lnwallet.AddressType, change bool,
accountName string) (btcutil.Address, error) {
// Addresses cannot be derived from the catch-all imported accounts.
if accountName == waddrmgr.ImportedAddrAccountName {
return nil, errNoImportedAddrGen
}
keyScope, account, err := b.keyScopeForAccountAddr(accountName, t)
if err != nil {
return nil, err
}
if change {
return b.wallet.NewChangeAddress(account, keyScope)
}
return b.wallet.NewAddress(account, keyScope)
}
// LastUnusedAddress returns the last *unused* address known by the wallet. An
// address is unused if it hasn't received any payments. This can be useful in
// UIs in order to continually show the "freshest" address without having to
// worry about "address inflation" caused by continual refreshing. Similar to
// NewAddress it can derive a specified address type, and also optionally a
// change address. The account parameter must be non-empty as it determines
// which account the address should be generated from.
func (b *BtcWallet) LastUnusedAddress(addrType lnwallet.AddressType,
accountName string) (btcutil.Address, error) {
// Addresses cannot be derived from the catch-all imported accounts.
if accountName == waddrmgr.ImportedAddrAccountName {
return nil, errNoImportedAddrGen
}
keyScope, account, err := b.keyScopeForAccountAddr(accountName, addrType)
if err != nil {
return nil, err
}
return b.wallet.CurrentAddress(account, keyScope)
}
// IsOurAddress checks if the passed address belongs to this wallet
//
// This is a part of the WalletController interface.
func (b *BtcWallet) IsOurAddress(a btcutil.Address) bool {
result, err := b.wallet.HaveAddress(a)
return result && (err == nil)
}
// AddressInfo returns the information about an address, if it's known to this
// wallet.
//
// NOTE: This is a part of the WalletController interface.
func (b *BtcWallet) AddressInfo(a btcutil.Address) (waddrmgr.ManagedAddress,
error) {
return b.wallet.AddressInfo(a)
}
// ListAccounts retrieves all accounts belonging to the wallet by default. A
// name and key scope filter can be provided to filter through all of the wallet
// accounts and return only those matching.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) ListAccounts(name string,
keyScope *waddrmgr.KeyScope) ([]*waddrmgr.AccountProperties, error) {
var res []*waddrmgr.AccountProperties
switch {
// If both the name and key scope filters were provided, we'll return
// the existing account matching those.
case name != "" && keyScope != nil:
account, err := b.wallet.AccountPropertiesByName(*keyScope, name)
if err != nil {
return nil, err
}
res = append(res, account)
// Only the name filter was provided.
case name != "" && keyScope == nil:
// If the name corresponds to the default or imported accounts,
// we'll return them for all our supported key scopes.
if name == lnwallet.DefaultAccountName ||
name == waddrmgr.ImportedAddrAccountName {
for _, defaultScope := range LndDefaultKeyScopes {
a, err := b.wallet.AccountPropertiesByName(
defaultScope, name,
)
if err != nil {
return nil, err
}
res = append(res, a)
}
break
}
// In theory, there should be only one custom account for the
// given name. However, due to a lack of check, users could
// create custom accounts with various key scopes. This
// behaviour has been fixed but, we return all potential custom
// accounts with the given name.
for _, scope := range waddrmgr.DefaultKeyScopes {
a, err := b.wallet.AccountPropertiesByName(
scope, name,
)
switch {
case waddrmgr.IsError(err, waddrmgr.ErrAccountNotFound):
continue
// In the specific case of a wallet initialized only by
// importing account xpubs (watch only wallets), it is
// possible that some keyscopes will be 'unknown' by the
// wallet (depending on the xpubs given to initialize
// it). If the keyscope is not found, just skip it.
case waddrmgr.IsError(err, waddrmgr.ErrScopeNotFound):
continue
case err != nil:
return nil, err
}
res = append(res, a)
}
if len(res) == 0 {
return nil, newAccountNotFoundError(name)
}
// Only the key scope filter was provided, so we'll return all accounts
// matching it.
case name == "" && keyScope != nil:
accounts, err := b.wallet.Accounts(*keyScope)
if err != nil {
return nil, err
}
for _, account := range accounts.Accounts {
account := account
res = append(res, &account.AccountProperties)
}
// Neither of the filters were provided, so return all accounts for our
// supported key scopes.
case name == "" && keyScope == nil:
for _, defaultScope := range LndDefaultKeyScopes {
accounts, err := b.wallet.Accounts(defaultScope)
if err != nil {
return nil, err
}
for _, account := range accounts.Accounts {
account := account
res = append(res, &account.AccountProperties)
}
}
accounts, err := b.wallet.Accounts(waddrmgr.KeyScope{
Purpose: keychain.BIP0043Purpose,
Coin: b.cfg.CoinType,
})
if err != nil {
return nil, err
}
for _, account := range accounts.Accounts {
account := account
res = append(res, &account.AccountProperties)
}
}
return res, nil
}
// newAccountNotFoundError returns an error indicating that the manager didn't
// find the specific account. This error is used to be compatible with the old
// 'LookupAccount' behaviour previously used.
func newAccountNotFoundError(name string) error {
str := fmt.Sprintf("account name '%s' not found", name)
return waddrmgr.ManagerError{
ErrorCode: waddrmgr.ErrAccountNotFound,
Description: str,
}
}
// RequiredReserve returns the minimum amount of satoshis that should be
// kept in the wallet in order to fee bump anchor channels if necessary.
// The value scales with the number of public anchor channels but is
// capped at a maximum.
func (b *BtcWallet) RequiredReserve(
numAnchorChans uint32) btcutil.Amount {
anchorChanReservedValue := lnwallet.AnchorChanReservedValue
reserved := btcutil.Amount(numAnchorChans) * anchorChanReservedValue
if reserved > lnwallet.MaxAnchorChanReservedValue {
reserved = lnwallet.MaxAnchorChanReservedValue
}
return reserved
}
// ListAddresses retrieves all the addresses along with their balance. An
// account name filter can be provided to filter through all of the
// wallet accounts and return the addresses of only those matching.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) ListAddresses(name string,
showCustomAccounts bool) (lnwallet.AccountAddressMap, error) {
accounts, err := b.ListAccounts(name, nil)
if err != nil {
return nil, err
}
addresses := make(lnwallet.AccountAddressMap)
addressBalance := make(map[string]btcutil.Amount)
// Retrieve all the unspent ouputs.
outputs, err := b.wallet.ListUnspent(0, math.MaxInt32, "")
if err != nil {
return nil, err
}
// Calculate the total balance of each address.
for _, output := range outputs {
amount, err := btcutil.NewAmount(output.Amount)
if err != nil {
return nil, err
}
addressBalance[output.Address] += amount
}
for _, accntDetails := range accounts {
accntScope := accntDetails.KeyScope
managedAddrs, err := b.wallet.AccountManagedAddresses(
accntDetails.KeyScope, accntDetails.AccountNumber,
)
if err != nil {
return nil, err
}
// Only consider those accounts which have addresses.
if len(managedAddrs) == 0 {
continue
}
// All the lnd internal/custom keys for channels and other
// functionality are derived from the same scope. Since they
// aren't really used as addresses and will never have an
// on-chain balance, we'll want to show the public key instead.
isLndCustom := accntScope.Purpose == keychain.BIP0043Purpose
addressProperties := make(
[]lnwallet.AddressProperty, len(managedAddrs),
)
for idx, managedAddr := range managedAddrs {
addr := managedAddr.Address()
addressString := addr.String()
// Hex-encode the compressed public key for custom lnd
// keys, addresses don't make a lot of sense.
var (
pubKey *btcec.PublicKey
derivationPath string
)
pka, ok := managedAddr.(waddrmgr.ManagedPubKeyAddress)
if ok {
pubKey = pka.PubKey()
// There can be an error in two cases: Either
// the address isn't a managed pubkey address,
// which we already checked above, or the
// address is imported in which case we don't
// know the derivation path, and it will just be
// empty anyway.
_, _, derivationPath, _ =
Bip32DerivationFromAddress(pka)
}
if pubKey != nil && isLndCustom {
addressString = hex.EncodeToString(
pubKey.SerializeCompressed(),
)
}
addressProperties[idx] = lnwallet.AddressProperty{
Address: addressString,
Internal: managedAddr.Internal(),
Balance: addressBalance[addressString],
PublicKey: pubKey,
DerivationPath: derivationPath,
}
}
if accntScope.Purpose != keychain.BIP0043Purpose ||
showCustomAccounts {
addresses[accntDetails] = addressProperties
}
}
return addresses, nil
}
// ImportAccount imports an account backed by an account extended public key.
// The master key fingerprint denotes the fingerprint of the root key
// corresponding to the account public key (also known as the key with
// derivation path m/). This may be required by some hardware wallets for proper
// identification and signing.
//
// The address type can usually be inferred from the key's version, but may be
// required for certain keys to map them into the proper scope.
//
// For custom accounts, we will first check if there is no account with the same
// name (even with a different key scope). No custom account should have various
// key scopes as it will result in non-deterministic behaviour.
//
// For BIP-0044 keys, an address type must be specified as we intend to not
// support importing BIP-0044 keys into the wallet using the legacy
// pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will force
// the standard BIP-0049 derivation scheme, while a witness address type will
// force the standard BIP-0084 derivation scheme.
//
// For BIP-0049 keys, an address type must also be specified to make a
// distinction between the standard BIP-0049 address schema (nested witness
// pubkeys everywhere) and our own BIP-0049Plus address schema (nested pubkeys
// externally, witness pubkeys internally).
//
// This is a part of the WalletController interface.
func (b *BtcWallet) ImportAccount(name string, accountPubKey *hdkeychain.ExtendedKey,
masterKeyFingerprint uint32, addrType *waddrmgr.AddressType,
dryRun bool) (*waddrmgr.AccountProperties, []btcutil.Address,
[]btcutil.Address, error) {
// For custom accounts, we first check if there is no existing account
// with the same name.
if name != lnwallet.DefaultAccountName &&
name != waddrmgr.ImportedAddrAccountName {
_, err := b.ListAccounts(name, nil)
if err == nil {
return nil, nil, nil,
fmt.Errorf("account '%s' already exists",
name)
}
if !waddrmgr.IsError(err, waddrmgr.ErrAccountNotFound) {
return nil, nil, nil, err
}
}
if !dryRun {
accountProps, err := b.wallet.ImportAccount(
name, accountPubKey, masterKeyFingerprint, addrType,
)
if err != nil {
return nil, nil, nil, err
}
return accountProps, nil, nil, nil
}
// Derive addresses from both the external and internal branches of the
// account. There's no risk of address inflation as this is only done
// for dry runs.
accountProps, extAddrs, intAddrs, err := b.wallet.ImportAccountDryRun(
name, accountPubKey, masterKeyFingerprint, addrType,
dryRunImportAccountNumAddrs,
)
if err != nil {
return nil, nil, nil, err
}
externalAddrs := make([]btcutil.Address, len(extAddrs))
for i := 0; i < len(extAddrs); i++ {
externalAddrs[i] = extAddrs[i].Address()
}
internalAddrs := make([]btcutil.Address, len(intAddrs))
for i := 0; i < len(intAddrs); i++ {
internalAddrs[i] = intAddrs[i].Address()
}
return accountProps, externalAddrs, internalAddrs, nil
}
// ImportPublicKey imports a single derived public key into the wallet. The
// address type can usually be inferred from the key's version, but in the case
// of legacy versions (xpub, tpub), an address type must be specified as we
// intend to not support importing BIP-44 keys into the wallet using the legacy
// pay-to-pubkey-hash (P2PKH) scheme.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) ImportPublicKey(pubKey *btcec.PublicKey,
addrType waddrmgr.AddressType) error {
return b.wallet.ImportPublicKey(pubKey, addrType)
}
// ImportTaprootScript imports a user-provided taproot script into the address
// manager. The imported script will act as a pay-to-taproot address.
func (b *BtcWallet) ImportTaprootScript(scope waddrmgr.KeyScope,
tapscript *waddrmgr.Tapscript) (waddrmgr.ManagedAddress, error) {
// We want to be able to import script addresses into a watch-only
// wallet, which is only possible if we don't encrypt the script with
// the private key encryption key. By specifying the script as being
// "not secret", we can also decrypt the script in a watch-only wallet.
const isSecretScript = false
// Currently, only v1 (Taproot) scripts are supported. We don't even
// know what a v2 witness version would look like at this point.
const witnessVersionTaproot byte = 1
return b.wallet.ImportTaprootScript(
scope, tapscript, nil, witnessVersionTaproot, isSecretScript,
)
}
// SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying out to
// the specified outputs. In the case the wallet has insufficient funds, or the
// outputs are non-standard, a non-nil error will be returned.
//
// NOTE: This method requires the global coin selection lock to be held.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) SendOutputs(inputs fn.Set[wire.OutPoint],
outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight,
minConfs int32, label string,
strategy base.CoinSelectionStrategy) (*wire.MsgTx, error) {
// Convert our fee rate from sat/kw to sat/kb since it's required by
// SendOutputs.
feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
// Sanity check outputs.
if len(outputs) < 1 {
return nil, lnwallet.ErrNoOutputs
}
// Sanity check minConfs.
if minConfs < 0 {
return nil, lnwallet.ErrInvalidMinconf
}
// Use selected UTXOs if specified, otherwise default selection.
if len(inputs) != 0 {
return b.wallet.SendOutputsWithInput(
outputs, nil, defaultAccount, minConfs, feeSatPerKB,
strategy, label, inputs.ToSlice(),
)
}
return b.wallet.SendOutputs(
outputs, nil, defaultAccount, minConfs, feeSatPerKB,
strategy, label,
)
}
// CreateSimpleTx creates a Bitcoin transaction paying to the specified
// outputs. The transaction is not broadcasted to the network, but a new change
// address might be created in the wallet database. In the case the wallet has
// insufficient funds, or the outputs are non-standard, an error should be
// returned. This method also takes the target fee expressed in sat/kw that
// should be used when crafting the transaction.
//
// NOTE: The dryRun argument can be set true to create a tx that doesn't alter
// the database. A tx created with this set to true SHOULD NOT be broadcasted.
//
// NOTE: This method requires the global coin selection lock to be held.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) CreateSimpleTx(inputs fn.Set[wire.OutPoint],
outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight, minConfs int32,
strategy base.CoinSelectionStrategy, dryRun bool) (
*txauthor.AuthoredTx, error) {
// The fee rate is passed in using units of sat/kw, so we'll convert
// this to sat/KB as the CreateSimpleTx method requires this unit.
feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
// Sanity check outputs.
if len(outputs) < 1 {
return nil, lnwallet.ErrNoOutputs
}
// Sanity check minConfs.
if minConfs < 0 {
return nil, lnwallet.ErrInvalidMinconf
}
for _, output := range outputs {
// When checking an output for things like dusty-ness, we'll
// use the default mempool relay fee rather than the target
// effective fee rate to ensure accuracy. Otherwise, we may
// mistakenly mark small-ish, but not quite dust output as
// dust.
err := txrules.CheckOutput(
output, txrules.DefaultRelayFeePerKb,
)
if err != nil {
return nil, err
}
}
// Add the optional inputs to the transaction.
optFunc := wallet.WithCustomSelectUtxos(inputs.ToSlice())
return b.wallet.CreateSimpleTx(
nil, defaultAccount, outputs, minConfs, feeSatPerKB,
strategy, dryRun, []wallet.TxCreateOption{optFunc}...,
)
}
// LeaseOutput locks an output to the given ID, preventing it from being
// available for any future coin selection attempts. The absolute time of the
// lock's expiration is returned. The expiration of the lock can be extended by
// successive invocations of this call. Outputs can be unlocked before their
// expiration through `ReleaseOutput`.
//
// If the output is not known, wtxmgr.ErrUnknownOutput is returned. If the
// output has already been locked to a different ID, then
// wtxmgr.ErrOutputAlreadyLocked is returned.
//
// NOTE: This method requires the global coin selection lock to be held.
func (b *BtcWallet) LeaseOutput(id wtxmgr.LockID, op wire.OutPoint,
duration time.Duration) (time.Time, error) {
// Make sure we don't attempt to double lock an output that's been
// locked by the in-memory implementation.
if b.wallet.LockedOutpoint(op) {
return time.Time{}, wtxmgr.ErrOutputAlreadyLocked
}
lockedUntil, err := b.wallet.LeaseOutput(id, op, duration)
if err != nil {
return time.Time{}, err
}
return lockedUntil, nil
}
// ListLeasedOutputs returns a list of all currently locked outputs.
func (b *BtcWallet) ListLeasedOutputs() ([]*base.ListLeasedOutputResult,
error) {
return b.wallet.ListLeasedOutputs()
}
// ReleaseOutput unlocks an output, allowing it to be available for coin
// selection if it remains unspent. The ID should match the one used to
// originally lock the output.
//
// NOTE: This method requires the global coin selection lock to be held.
func (b *BtcWallet) ReleaseOutput(id wtxmgr.LockID, op wire.OutPoint) error {
return b.wallet.ReleaseOutput(id, op)
}
// ListUnspentWitness returns all unspent outputs which are version 0 witness
// programs. The 'minConfs' and 'maxConfs' parameters indicate the minimum
// and maximum number of confirmations an output needs in order to be returned
// by this method. Passing -1 as 'minConfs' indicates that even unconfirmed
// outputs should be returned. Using MaxInt32 as 'maxConfs' implies returning
// all outputs with at least 'minConfs'. The account parameter serves as a
// filter to retrieve the unspent outputs for a specific account. When empty,
// the unspent outputs of all wallet accounts are returned.
//
// NOTE: This method requires the global coin selection lock to be held.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) ListUnspentWitness(minConfs, maxConfs int32,
accountFilter string) ([]*lnwallet.Utxo, error) {
// First, grab all the unfiltered currently unspent outputs.
unspentOutputs, err := b.wallet.ListUnspent(
minConfs, maxConfs, accountFilter,
)
if err != nil {
return nil, err
}
// Next, we'll run through all the regular outputs, only saving those
// which are p2wkh outputs or a p2wsh output nested within a p2sh output.
witnessOutputs := make([]*lnwallet.Utxo, 0, len(unspentOutputs))
for _, output := range unspentOutputs {
pkScript, err := hex.DecodeString(output.ScriptPubKey)
if err != nil {
return nil, err
}
addressType := lnwallet.UnknownAddressType
if txscript.IsPayToWitnessPubKeyHash(pkScript) {
addressType = lnwallet.WitnessPubKey
} else if txscript.IsPayToScriptHash(pkScript) {
// TODO(roasbeef): This assumes all p2sh outputs returned by the
// wallet are nested p2pkh. We can't check the redeem script because
// the btcwallet service does not include it.
addressType = lnwallet.NestedWitnessPubKey
} else if txscript.IsPayToTaproot(pkScript) {
addressType = lnwallet.TaprootPubkey
}
if addressType == lnwallet.WitnessPubKey ||
addressType == lnwallet.NestedWitnessPubKey ||
addressType == lnwallet.TaprootPubkey {
txid, err := chainhash.NewHashFromStr(output.TxID)
if err != nil {
return nil, err
}
// We'll ensure we properly convert the amount given in
// BTC to satoshis.
amt, err := btcutil.NewAmount(output.Amount)
if err != nil {
return nil, err
}
utxo := &lnwallet.Utxo{
AddressType: addressType,
Value: amt,
PkScript: pkScript,
OutPoint: wire.OutPoint{
Hash: *txid,
Index: output.Vout,
},
Confirmations: output.Confirmations,
}
witnessOutputs = append(witnessOutputs, utxo)
}
}
return witnessOutputs, nil
}
// mapRpcclientError maps an error from the `btcwallet/chain` package to
// defined error in this package.
//
// NOTE: we are mapping the errors returned from `sendrawtransaction` RPC or
// the reject reason from `testmempoolaccept` RPC.
func mapRpcclientError(err error) error {
// If we failed to publish the transaction, check whether we got an
// error of known type.
switch {
// If the wallet reports a double spend, convert it to our internal
// ErrDoubleSpend and return.
case errors.Is(err, chain.ErrMempoolConflict),
errors.Is(err, chain.ErrMissingInputs),
errors.Is(err, chain.ErrTxAlreadyKnown),
errors.Is(err, chain.ErrTxAlreadyConfirmed):
return lnwallet.ErrDoubleSpend
// If the wallet reports that fee requirements for accepting the tx
// into mempool are not met, convert it to our internal ErrMempoolFee
// and return.
case errors.Is(err, chain.ErrMempoolMinFeeNotMet):
return fmt.Errorf("%w: %v", lnwallet.ErrMempoolFee, err.Error())
}
return err
}
// PublishTransaction performs cursory validation (dust checks, etc), then
// finally broadcasts the passed transaction to the Bitcoin network. If
// publishing the transaction fails, an error describing the reason is returned
// and mapped to the wallet's internal error types. If the transaction is
// already published to the network (either in the mempool or chain) no error
// will be returned.
func (b *BtcWallet) PublishTransaction(tx *wire.MsgTx, label string) error {
// For neutrino backend there's no mempool, so we return early by
// publishing the transaction.
if b.chain.BackEnd() == "neutrino" {
err := b.wallet.PublishTransaction(tx, label)
return mapRpcclientError(err)
}
// For non-neutrino nodes, we will first check whether the transaction
// can be accepted by the mempool.
// Use a max feerate of 0 means the default value will be used when
// testing mempool acceptance. The default max feerate is 0.10 BTC/kvb,
// or 10,000 sat/vb.
results, err := b.chain.TestMempoolAccept([]*wire.MsgTx{tx}, 0)
if err != nil {
// If the chain backend doesn't support the mempool acceptance
// test RPC, we'll just attempt to publish the transaction.
if errors.Is(err, rpcclient.ErrBackendVersion) {
log.Warnf("TestMempoolAccept not supported by "+
"backend, consider upgrading %s to a newer "+
"version", b.chain.BackEnd())
err := b.wallet.PublishTransaction(tx, label)
return mapRpcclientError(err)
}
return err
}
// Sanity check that the expected single result is returned.
if len(results) != 1 {
return fmt.Errorf("expected 1 result from TestMempoolAccept, "+
"instead got %v", len(results))
}
result := results[0]
log.Debugf("TestMempoolAccept result: %s", spew.Sdump(result))
// Once mempool check passed, we can publish the transaction.
if result.Allowed {
err = b.wallet.PublishTransaction(tx, label)
return mapRpcclientError(err)
}
// If the check failed, there's no need to publish it. We'll handle the
// error and return.
log.Warnf("Transaction %v not accepted by mempool: %v",
tx.TxHash(), result.RejectReason)
// We need to use the string to create an error type and map it to a
// btcwallet error.
err = b.chain.MapRPCErr(errors.New(result.RejectReason))
//nolint:ll
// These two errors are ignored inside `PublishTransaction`:
// https://github.com/btcsuite/btcwallet/blob/master/wallet/wallet.go#L3763
// To keep our current behavior, we need to ignore the same errors
// returned from TestMempoolAccept.
//
// TODO(yy): since `LightningWallet.PublishTransaction` always publish
// the same tx twice, we'd always get ErrTxAlreadyInMempool. We should
// instead create a new rebroadcaster that monitors the mempool, and
// only rebroadcast when the tx is evicted. This way we don't need to
// broadcast twice, and can instead return these errors here.
switch {
// NOTE: In addition to ignoring these errors, we need to call
// `PublishTransaction` again because we need to mark the label in the
// wallet. We can remove this exception once we have the above TODO
// fixed.
case errors.Is(err, chain.ErrTxAlreadyInMempool),
errors.Is(err, chain.ErrTxAlreadyKnown),
errors.Is(err, chain.ErrTxAlreadyConfirmed):
err := b.wallet.PublishTransaction(tx, label)
return mapRpcclientError(err)
}
return mapRpcclientError(err)
}
// LabelTransaction adds a label to a transaction. If the tx already
// has a label, this call will fail unless the overwrite parameter
// is set. Labels must not be empty, and they are limited to 500 chars.
//
// Note: it is part of the WalletController interface.
func (b *BtcWallet) LabelTransaction(hash chainhash.Hash, label string,
overwrite bool) error {
return b.wallet.LabelTransaction(hash, label, overwrite)
}
// extractBalanceDelta extracts the net balance delta from the PoV of the
// wallet given a TransactionSummary.
func extractBalanceDelta(
txSummary base.TransactionSummary,
tx *wire.MsgTx,
) (btcutil.Amount, error) {
// For each input we debit the wallet's outflow for this transaction,
// and for each output we credit the wallet's inflow for this
// transaction.
var balanceDelta btcutil.Amount
for _, input := range txSummary.MyInputs {
balanceDelta -= input.PreviousAmount
}
for _, output := range txSummary.MyOutputs {
balanceDelta += btcutil.Amount(tx.TxOut[output.Index].Value)
}
return balanceDelta, nil
}
// getPreviousOutpoints is a helper function which gets the previous
// outpoints of a transaction.
func getPreviousOutpoints(wireTx *wire.MsgTx,
myInputs []base.TransactionSummaryInput) []lnwallet.PreviousOutPoint {
// isOurOutput is a map containing the output indices
// controlled by the wallet.
// Note: We make use of the information in `myInputs` provided
// by the `wallet.TransactionSummary` structure that holds
// information only if the input/previous_output is controlled by the wallet.
isOurOutput := make(map[uint32]bool, len(myInputs))
for _, myInput := range myInputs {
isOurOutput[myInput.Index] = true
}
previousOutpoints := make([]lnwallet.PreviousOutPoint, len(wireTx.TxIn))
for idx, txIn := range wireTx.TxIn {
previousOutpoints[idx] = lnwallet.PreviousOutPoint{
OutPoint: txIn.PreviousOutPoint.String(),
IsOurOutput: isOurOutput[uint32(idx)],
}
}
return previousOutpoints
}
// GetTransactionDetails returns details of a transaction given its
// transaction hash.
func (b *BtcWallet) GetTransactionDetails(
txHash *chainhash.Hash) (*lnwallet.TransactionDetail, error) {
// Grab the best block the wallet knows of, we'll use this to calculate
// # of confirmations shortly below.
bestBlock := b.wallet.Manager.SyncedTo()
currentHeight := bestBlock.Height
tx, err := b.wallet.GetTransaction(*txHash)
if err != nil {
return nil, err
}
// For both confirmed and unconfirmed transactions, create a
// TransactionDetail which re-packages the data returned by the base
// wallet.
if tx.Confirmations > 0 {
txDetails, err := minedTransactionsToDetails(
currentHeight,
base.Block{
Transactions: []base.TransactionSummary{
tx.Summary,
},
Hash: tx.BlockHash,
Height: tx.Height,
Timestamp: tx.Summary.Timestamp},
b.netParams,
)
if err != nil {
return nil, err
}
return txDetails[0], nil
}
return unminedTransactionsToDetail(tx.Summary, b.netParams)
}
// minedTransactionsToDetails is a helper function which converts a summary
// information about mined transactions to a TransactionDetail.
func minedTransactionsToDetails(
currentHeight int32,
block base.Block,
chainParams *chaincfg.Params,
) ([]*lnwallet.TransactionDetail, error) {
details := make([]*lnwallet.TransactionDetail, 0, len(block.Transactions))
for _, tx := range block.Transactions {
wireTx := &wire.MsgTx{}
txReader := bytes.NewReader(tx.Transaction)
if err := wireTx.Deserialize(txReader); err != nil {
return nil, err
}
// isOurAddress is a map containing the output indices
// controlled by the wallet.
// Note: We make use of the information in `MyOutputs` provided
// by the `wallet.TransactionSummary` structure that holds
// information only if the output is controlled by the wallet.
isOurAddress := make(map[int]bool, len(tx.MyOutputs))
for _, o := range tx.MyOutputs {
isOurAddress[int(o.Index)] = true
}
var outputDetails []lnwallet.OutputDetail
for i, txOut := range wireTx.TxOut {
var addresses []btcutil.Address
sc, outAddresses, _, err := txscript.ExtractPkScriptAddrs(
txOut.PkScript, chainParams,
)
if err == nil {
// Add supported addresses.
addresses = outAddresses
}
outputDetails = append(outputDetails, lnwallet.OutputDetail{
OutputType: sc,
Addresses: addresses,
PkScript: txOut.PkScript,
OutputIndex: i,
Value: btcutil.Amount(txOut.Value),
IsOurAddress: isOurAddress[i],
})
}
previousOutpoints := getPreviousOutpoints(wireTx, tx.MyInputs)
txDetail := &lnwallet.TransactionDetail{
Hash: *tx.Hash,
NumConfirmations: currentHeight - block.Height + 1,
BlockHash: block.Hash,
BlockHeight: block.Height,
Timestamp: block.Timestamp,
TotalFees: int64(tx.Fee),
OutputDetails: outputDetails,
RawTx: tx.Transaction,
Label: tx.Label,
PreviousOutpoints: previousOutpoints,
}
balanceDelta, err := extractBalanceDelta(tx, wireTx)
if err != nil {
return nil, err
}
txDetail.Value = balanceDelta
details = append(details, txDetail)
}
return details, nil
}
// unminedTransactionsToDetail is a helper function which converts a summary
// for an unconfirmed transaction to a transaction detail.
func unminedTransactionsToDetail(
summary base.TransactionSummary,
chainParams *chaincfg.Params,
) (*lnwallet.TransactionDetail, error) {
wireTx := &wire.MsgTx{}
txReader := bytes.NewReader(summary.Transaction)
if err := wireTx.Deserialize(txReader); err != nil {
return nil, err
}
// isOurAddress is a map containing the output indices controlled by
// the wallet.
// Note: We make use of the information in `MyOutputs` provided
// by the `wallet.TransactionSummary` structure that holds information
// only if the output is controlled by the wallet.
isOurAddress := make(map[int]bool, len(summary.MyOutputs))
for _, o := range summary.MyOutputs {
isOurAddress[int(o.Index)] = true
}
var outputDetails []lnwallet.OutputDetail
for i, txOut := range wireTx.TxOut {
var addresses []btcutil.Address
sc, outAddresses, _, err := txscript.ExtractPkScriptAddrs(
txOut.PkScript, chainParams,
)
if err == nil {
// Add supported addresses.
addresses = outAddresses
}
outputDetails = append(outputDetails, lnwallet.OutputDetail{
OutputType: sc,
Addresses: addresses,
PkScript: txOut.PkScript,
OutputIndex: i,
Value: btcutil.Amount(txOut.Value),
IsOurAddress: isOurAddress[i],
})
}
previousOutpoints := getPreviousOutpoints(wireTx, summary.MyInputs)
txDetail := &lnwallet.TransactionDetail{
Hash: *summary.Hash,
TotalFees: int64(summary.Fee),
Timestamp: summary.Timestamp,
OutputDetails: outputDetails,
RawTx: summary.Transaction,
Label: summary.Label,
PreviousOutpoints: previousOutpoints,
}
balanceDelta, err := extractBalanceDelta(summary, wireTx)
if err != nil {
return nil, err
}
txDetail.Value = balanceDelta
return txDetail, nil
}
// ListTransactionDetails returns a list of all transactions which are relevant
// to the wallet over [startHeight;endHeight]. If start height is greater than
// end height, the transactions will be retrieved in reverse order. To include
// unconfirmed transactions, endHeight should be set to the special value -1.
// This will return transactions from the tip of the chain until the start
// height (inclusive) and unconfirmed transactions. The account parameter serves
// as a filter to retrieve the transactions relevant to a specific account. When
// empty, transactions of all wallet accounts are returned.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) ListTransactionDetails(startHeight, endHeight int32,
accountFilter string, indexOffset uint32,
maxTransactions uint32) ([]*lnwallet.TransactionDetail, uint64, uint64,
error) {
// Grab the best block the wallet knows of, we'll use this to calculate
// # of confirmations shortly below.
bestBlock := b.wallet.Manager.SyncedTo()
currentHeight := bestBlock.Height
// We'll attempt to find all transactions from start to end height.
start := base.NewBlockIdentifierFromHeight(startHeight)
stop := base.NewBlockIdentifierFromHeight(endHeight)
txns, err := b.wallet.GetTransactions(start, stop, accountFilter, nil)
if err != nil {
return nil, 0, 0, err
}
txDetails := make([]*lnwallet.TransactionDetail, 0,
len(txns.MinedTransactions)+len(txns.UnminedTransactions))
// For both confirmed and unconfirmed transactions, create a
// TransactionDetail which re-packages the data returned by the base
// wallet.
for _, blockPackage := range txns.MinedTransactions {
details, err := minedTransactionsToDetails(
currentHeight, blockPackage, b.netParams,
)
if err != nil {
return nil, 0, 0, err
}
txDetails = append(txDetails, details...)
}
for _, tx := range txns.UnminedTransactions {
detail, err := unminedTransactionsToDetail(tx, b.netParams)
if err != nil {
return nil, 0, 0, err
}
txDetails = append(txDetails, detail)
}
// Return empty transaction list, if offset is more than all
// transactions.
if int(indexOffset) >= len(txDetails) {
txDetails = []*lnwallet.TransactionDetail{}
return txDetails, 0, 0, nil
}
end := indexOffset + maxTransactions
// If maxTransactions is set to 0, then we'll return all transactions
// starting from the offset.
if maxTransactions == 0 {
end = uint32(len(txDetails))
txDetails = txDetails[indexOffset:end]
return txDetails, uint64(indexOffset), uint64(end - 1), nil
}
if end > uint32(len(txDetails)) {
end = uint32(len(txDetails))
}
txDetails = txDetails[indexOffset:end]
return txDetails, uint64(indexOffset), uint64(end - 1), nil
}
// txSubscriptionClient encapsulates the transaction notification client from
// the base wallet. Notifications received from the client will be proxied over
// two distinct channels.
type txSubscriptionClient struct {
txClient base.TransactionNotificationsClient
confirmed chan *lnwallet.TransactionDetail
unconfirmed chan *lnwallet.TransactionDetail
w *base.Wallet
wg sync.WaitGroup
quit chan struct{}
}
// ConfirmedTransactions returns a channel which will be sent on as new
// relevant transactions are confirmed.
//
// This is part of the TransactionSubscription interface.
func (t *txSubscriptionClient) ConfirmedTransactions() chan *lnwallet.TransactionDetail {
return t.confirmed
}
// UnconfirmedTransactions returns a channel which will be sent on as
// new relevant transactions are seen within the network.
//
// This is part of the TransactionSubscription interface.
func (t *txSubscriptionClient) UnconfirmedTransactions() chan *lnwallet.TransactionDetail {
return t.unconfirmed
}
// Cancel finalizes the subscription, cleaning up any resources allocated.
//
// This is part of the TransactionSubscription interface.
func (t *txSubscriptionClient) Cancel() {
close(t.quit)
t.wg.Wait()
t.txClient.Done()
}
// notificationProxier proxies the notifications received by the underlying
// wallet's notification client to a higher-level TransactionSubscription
// client.
func (t *txSubscriptionClient) notificationProxier() {
defer t.wg.Done()
out:
for {
select {
case txNtfn := <-t.txClient.C:
// TODO(roasbeef): handle detached blocks
currentHeight := t.w.Manager.SyncedTo().Height
// Launch a goroutine to re-package and send
// notifications for any newly confirmed transactions.
//nolint:ll
go func(txNtfn *base.TransactionNotifications) {
for _, block := range txNtfn.AttachedBlocks {
details, err := minedTransactionsToDetails(
currentHeight, block,
t.w.ChainParams(),
)
if err != nil {
continue
}
for _, d := range details {
select {
case t.confirmed <- d:
case <-t.quit:
return
}
}
}
}(txNtfn)
// Launch a goroutine to re-package and send
// notifications for any newly unconfirmed transactions.
go func(txNtfn *base.TransactionNotifications) {
for _, tx := range txNtfn.UnminedTransactions {
detail, err := unminedTransactionsToDetail(
tx, t.w.ChainParams(),
)
if err != nil {
continue
}
select {
case t.unconfirmed <- detail:
case <-t.quit:
return
}
}
}(txNtfn)
case <-t.quit:
break out
}
}
}
// SubscribeTransactions returns a TransactionSubscription client which
// is capable of receiving async notifications as new transactions
// related to the wallet are seen within the network, or found in
// blocks.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) SubscribeTransactions() (lnwallet.TransactionSubscription, error) {
walletClient := b.wallet.NtfnServer.TransactionNotifications()
txClient := &txSubscriptionClient{
txClient: walletClient,
confirmed: make(chan *lnwallet.TransactionDetail),
unconfirmed: make(chan *lnwallet.TransactionDetail),
w: b.wallet,
quit: make(chan struct{}),
}
txClient.wg.Add(1)
go txClient.notificationProxier()
return txClient, nil
}
// IsSynced returns a boolean indicating if from the PoV of the wallet, it has
// fully synced to the current best block in the main chain.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) IsSynced() (bool, int64, error) {
// Grab the best chain state the wallet is currently aware of.
syncState := b.wallet.Manager.SyncedTo()
// We'll also extract the current best wallet timestamp so the caller
// can get an idea of where we are in the sync timeline.
bestTimestamp := syncState.Timestamp.Unix()
// Next, query the chain backend to grab the info about the tip of the
// main chain.
bestHash, bestHeight, err := b.cfg.ChainSource.GetBestBlock()
if err != nil {
return false, 0, err
}
// Make sure the backing chain has been considered synced first.
if !b.wallet.ChainSynced() {
bestHeader, err := b.cfg.ChainSource.GetBlockHeader(bestHash)
if err != nil {
return false, 0, err
}
bestTimestamp = bestHeader.Timestamp.Unix()
return false, bestTimestamp, nil
}
// If the wallet hasn't yet fully synced to the node's best chain tip,
// then we're not yet fully synced.
if syncState.Height < bestHeight {
return false, bestTimestamp, nil
}
// If the wallet is on par with the current best chain tip, then we
// still may not yet be synced as the chain backend may still be
// catching up to the main chain. So we'll grab the block header in
// order to make a guess based on the current time stamp.
blockHeader, err := b.cfg.ChainSource.GetBlockHeader(bestHash)
if err != nil {
return false, 0, err
}
// If the timestamp on the best header is more than 2 hours in the
// past, then we're not yet synced.
minus24Hours := time.Now().Add(-2 * time.Hour)
if blockHeader.Timestamp.Before(minus24Hours) {
return false, bestTimestamp, nil
}
return true, bestTimestamp, nil
}
// GetRecoveryInfo returns a boolean indicating whether the wallet is started
// in recovery mode. It also returns a float64, ranging from 0 to 1,
// representing the recovery progress made so far.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) GetRecoveryInfo() (bool, float64, error) {
isRecoveryMode := true
progress := float64(0)
// A zero value in RecoveryWindow indicates there is no trigger of
// recovery mode.
if b.cfg.RecoveryWindow == 0 {
isRecoveryMode = false
return isRecoveryMode, progress, nil
}
// Query the wallet's birthday block from db.
birthdayBlock, err := b.wallet.BirthdayBlock()
if err != nil {
// The wallet won't start until the backend is synced, thus the birthday
// block won't be set and this particular error will be returned. We'll
// catch this error and return a progress of 0 instead.
if waddrmgr.IsError(err, waddrmgr.ErrBirthdayBlockNotSet) {
return isRecoveryMode, progress, nil
}
return isRecoveryMode, progress, err
}
// Grab the best chain state the wallet is currently aware of.
syncState := b.wallet.Manager.SyncedTo()
// Next, query the chain backend to grab the info about the tip of the
// main chain.
//
// NOTE: The actual recovery process is handled by the btcsuite/btcwallet.
// The process purposefully doesn't update the best height. It might create
// a small difference between the height queried here and the height used
// in the recovery process, ie, the bestHeight used here might be greater,
// showing the recovery being unfinished while it's actually done. However,
// during a wallet rescan after the recovery, the wallet's synced height
// will catch up and this won't be an issue.
_, bestHeight, err := b.cfg.ChainSource.GetBestBlock()
if err != nil {
return isRecoveryMode, progress, err
}
// The birthday block height might be greater than the current synced height
// in a newly restored wallet, and might be greater than the chain tip if a
// rollback happens. In that case, we will return zero progress here.
if syncState.Height < birthdayBlock.Height ||
bestHeight < birthdayBlock.Height {
return isRecoveryMode, progress, nil
}
// progress is the ratio of the [number of blocks processed] over the [total
// number of blocks] needed in a recovery mode, ranging from 0 to 1, in
// which,
// - total number of blocks is the current chain's best height minus the
// wallet's birthday height plus 1.
// - number of blocks processed is the wallet's synced height minus its
// birthday height plus 1.
// - If the wallet is born very recently, the bestHeight can be equal to
// the birthdayBlock.Height, and it will recovery instantly.
progress = float64(syncState.Height-birthdayBlock.Height+1) /
float64(bestHeight-birthdayBlock.Height+1)
return isRecoveryMode, progress, nil
}
// FetchTx attempts to fetch a transaction in the wallet's database identified
// by the passed transaction hash. If the transaction can't be found, then a
// nil pointer is returned.
func (b *BtcWallet) FetchTx(txHash chainhash.Hash) (*wire.MsgTx, error) {
tx, err := b.wallet.GetTransaction(txHash)
if err != nil {
return nil, err
}
return tx.Summary.Tx, nil
}
// RemoveDescendants attempts to remove any transaction from the wallet's tx
// store (that may be unconfirmed) that spends outputs created by the passed
// transaction. This remove propagates recursively down the chain of descendent
// transactions.
func (b *BtcWallet) RemoveDescendants(tx *wire.MsgTx) error {
return b.wallet.RemoveDescendants(tx)
}
// CheckMempoolAcceptance is a wrapper around `TestMempoolAccept` which checks
// the mempool acceptance of a transaction.
func (b *BtcWallet) CheckMempoolAcceptance(tx *wire.MsgTx) error {
// Use a max feerate of 0 means the default value will be used when
// testing mempool acceptance. The default max feerate is 0.10 BTC/kvb,
// or 10,000 sat/vb.
results, err := b.chain.TestMempoolAccept([]*wire.MsgTx{tx}, 0)
if err != nil {
return err
}
// Sanity check that the expected single result is returned.
if len(results) != 1 {
return fmt.Errorf("expected 1 result from TestMempoolAccept, "+
"instead got %v", len(results))
}
result := results[0]
log.Debugf("TestMempoolAccept result: %s", spew.Sdump(result))
// Mempool check failed, we now map the reject reason to a proper RPC
// error and return it.
if !result.Allowed {
err := b.chain.MapRPCErr(errors.New(result.RejectReason))
return fmt.Errorf("mempool rejection: %w", err)
}
return nil
}
package btcwallet
import (
"path/filepath"
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/wallet"
)
var (
// defaultPubPassphrase is the default public wallet passphrase which is
// used when the user indicates they do not want additional protection
// provided by having all public data in the wallet encrypted by a
// passphrase only known to them.
defaultPubPassphrase = []byte("public")
)
// Config is a struct which houses configuration parameters which modify the
// instance of BtcWallet generated by the New() function.
type Config struct {
// LogDir is the name of the directory which should be used to store
// generated log files.
LogDir string
// PrivatePass is the private password to the underlying btcwallet
// instance. Without this, the wallet cannot be decrypted and operated.
PrivatePass []byte
// PublicPass is the optional public password to btcwallet. This is
// optionally used to encrypt public material such as public keys and
// scripts.
PublicPass []byte
// HdSeed is an optional seed to feed into the wallet. If this is
// unspecified, a new seed will be generated.
HdSeed []byte
// Birthday specifies the time at which this wallet was initially
// created. It is used to bound rescans for used addresses.
Birthday time.Time
// RecoveryWindow specifies the address look-ahead for which to scan
// when restoring a wallet. The recovery window will apply to all
// default BIP44 derivation paths.
RecoveryWindow uint32
// ChainSource is the primary chain interface. This is used to operate
// the wallet and do things such as rescanning, sending transactions,
// notifications for received funds, etc.
ChainSource chain.Interface
// NetParams is the net parameters for the target chain.
NetParams *chaincfg.Params
// CoinType specifies the BIP 44 coin type to be used for derivation.
CoinType uint32
// Wallet is an unlocked wallet instance that is set if the
// UnlockerService has already opened and unlocked the wallet. If this
// is nil, then a wallet might have just been created or is simply not
// encrypted at all, in which case it should be attempted to be loaded
// normally when creating the BtcWallet.
Wallet *wallet.Wallet
// LoaderOptions holds functional wallet db loader options.
LoaderOptions []LoaderOption
// CoinSelectionStrategy is the strategy that is used for selecting
// coins when funding a transaction.
CoinSelectionStrategy wallet.CoinSelectionStrategy
// WatchOnly indicates that the wallet was initialized with public key
// material only and does not contain any private keys.
WatchOnly bool
// MigrateWatchOnly indicates that if a wallet with private key material
// already exists, it should be attempted to be converted into a
// watch-only wallet on first startup. This flag has no effect if no
// wallet exists and a watch-only one is created directly, or, if the
// wallet was previously converted to a watch-only already.
MigrateWatchOnly bool
}
// NetworkDir returns the directory name of a network directory to hold wallet
// files.
func NetworkDir(dataDir string, chainParams *chaincfg.Params) string {
netname := chainParams.Name
// For now, we must always name the testnet data directory as "testnet"
// and not "testnet3" or any other version, as the chaincfg testnet3
// parameters will likely be switched to being named "testnet3" in the
// future. This is done to future proof that change, and an upgrade
// plan to move the testnet3 data directory can be worked out later.
if chainParams.Net == wire.TestNet3 {
netname = "testnet"
}
return filepath.Join(dataDir, netname)
}
package btcwallet
import (
"fmt"
"github.com/btcsuite/btcwallet/chain"
"github.com/lightningnetwork/lnd/blockcache"
"github.com/lightningnetwork/lnd/lnwallet"
)
const (
walletType = "btcwallet"
)
// createNewWallet creates a new instance of BtcWallet given the proper list of
// initialization parameters. This function is the factory function required to
// properly create an instance of the lnwallet.WalletDriver struct for
// BtcWallet.
func createNewWallet(args ...interface{}) (lnwallet.WalletController, error) {
if len(args) != 2 {
return nil, fmt.Errorf("incorrect number of arguments to .New(...), "+
"expected 2, instead passed %v", len(args))
}
config, ok := args[0].(*Config)
if !ok {
return nil, fmt.Errorf("first argument to btcdnotifier.New is " +
"incorrect, expected a *rpcclient.ConnConfig")
}
blockCache, ok := args[1].(*blockcache.BlockCache)
if !ok {
return nil, fmt.Errorf("second argument to btcdnotifier.New is " +
"incorrect, expected a *blockcache.BlockCache")
}
return New(*config, blockCache)
}
// init registers a driver for the BtcWallet concrete implementation of the
// lnwallet.WalletController interface.
func init() {
// Register the driver.
driver := &lnwallet.WalletDriver{
WalletType: walletType,
New: createNewWallet,
BackEnds: chain.BackEnds,
}
if err := lnwallet.RegisterWallet(driver); err != nil {
panic(fmt.Sprintf("failed to register wallet driver '%s': %v",
walletType, err))
}
}
package btcwallet
import (
"github.com/btcsuite/btclog/v2"
"github.com/btcsuite/btcwallet/chain"
btcwallet "github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/lightningnetwork/lnd/build"
)
// Subsystem defines the logging code for this subsystem.
const Subsystem = "BTWL"
// log is a logger that is initialized with no output filters. This means the
// package will not perform any logging by default until the caller requests
// it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled by
// default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info. This
// should be used in preference to SetLogWriter if the caller is also using
// btclog.
func UseLogger(logger btclog.Logger) {
log = logger
btcwallet.UseLogger(logger)
wtxmgr.UseLogger(logger)
chain.UseLogger(logger)
}
package btcwallet
import (
"bytes"
"crypto/sha256"
"errors"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)
var (
// PsbtKeyTypeInputSignatureTweakSingle is a custom/proprietary PSBT key
// for an input that specifies what single tweak should be applied to
// the key before signing the input. The value 51 is leet speak for
// "si", short for "single".
PsbtKeyTypeInputSignatureTweakSingle = []byte{0x51}
// PsbtKeyTypeInputSignatureTweakDouble is a custom/proprietary PSBT key
// for an input that specifies what double tweak should be applied to
// the key before signing the input. The value d0 is leet speak for
// "do", short for "double".
PsbtKeyTypeInputSignatureTweakDouble = []byte{0xd0}
// ErrInputMissingUTXOInfo is returned if a PSBT input is supplied that
// does not specify the witness UTXO info.
ErrInputMissingUTXOInfo = errors.New(
"input doesn't specify any UTXO info",
)
// ErrScriptSpendFeeEstimationUnsupported is returned if a PSBT input is
// of a script spend type.
ErrScriptSpendFeeEstimationUnsupported = errors.New(
"cannot estimate fee for script spend inputs",
)
// ErrUnsupportedScript is returned if a supplied pk script is not
// known or supported.
ErrUnsupportedScript = errors.New("unsupported or unknown pk script")
)
// FundPsbt creates a fully populated PSBT packet that contains enough inputs to
// fund the outputs specified in the passed in packet with the specified fee
// rate. If there is change left, a change output from the internal wallet is
// added and the index of the change output is returned. Otherwise no additional
// output is created and the index -1 is returned. If no custom change
// scope is specified, the BIP0084 will be used for default accounts and single
// imported public keys. For custom account, no key scope should be provided
// as the coin selection key scope will always be used to generate the change
// address.
// The function argument `allowUtxo` specifies a filter function for utxos
// during coin selection. It should return true for utxos that can be used and
// false for those that should be excluded.
//
// NOTE: If the packet doesn't contain any inputs, coin selection is performed
// automatically. The account parameter must be non-empty as it determines which
// set of coins are eligible for coin selection. If the packet does contain any
// inputs, it is assumed that full coin selection happened externally and no
// additional inputs are added. If the specified inputs aren't enough to fund
// the outputs with the given fee rate, an error is returned. No lock lease is
// acquired for any of the selected/validated inputs. It is in the caller's
// responsibility to lock the inputs before handing them out.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) FundPsbt(packet *psbt.Packet, minConfs int32,
feeRate chainfee.SatPerKWeight, accountName string,
changeScope *waddrmgr.KeyScope,
strategy wallet.CoinSelectionStrategy,
allowUtxo func(wtxmgr.Credit) bool) (int32, error) {
// The fee rate is passed in using units of sat/kw, so we'll convert
// this to sat/KB as the CreateSimpleTx method requires this unit.
feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
var (
keyScope *waddrmgr.KeyScope
accountNum uint32
)
switch accountName {
// For default accounts and single imported public keys, we'll provide a
// nil key scope to FundPsbt, allowing it to select inputs from all
// scopes (NP2WKH, P2WKH, P2TR). By default, the change key scope for
// these accounts will be P2WKH.
case lnwallet.DefaultAccountName:
if changeScope == nil {
changeScope = &waddrmgr.KeyScopeBIP0084
}
accountNum = defaultAccount
case waddrmgr.ImportedAddrAccountName:
if changeScope == nil {
changeScope = &waddrmgr.KeyScopeBIP0084
}
accountNum = importedAccount
// Otherwise, map the account name to its key scope and internal account
// number to only select inputs from said account. No change key scope
// should have been specified as a custom account should only have one
// key scope. Providing a change key scope would break this assumption
// and lead to non-deterministic behavior by using a different change
// key scope than the custom account key scope. The change key scope
// will always be the same as the coin selection.
default:
if changeScope != nil {
return 0, fmt.Errorf("couldn't select a " +
"custom change type for custom accounts")
}
scope, account, err := b.lookupFirstCustomAccount(accountName)
if err != nil {
return 0, err
}
keyScope = &scope
changeScope = keyScope
accountNum = account
}
var opts []wallet.TxCreateOption
if changeScope != nil {
opts = append(opts, wallet.WithCustomChangeScope(changeScope))
}
if allowUtxo != nil {
opts = append(opts, wallet.WithUtxoFilter(allowUtxo))
}
// Let the wallet handle coin selection and/or fee estimation based on
// the partial TX information in the packet.
return b.wallet.FundPsbt(
packet, keyScope, minConfs, accountNum, feeSatPerKB,
strategy, opts...,
)
}
// SignPsbt expects a partial transaction with all inputs and outputs fully
// declared and tries to sign all unsigned inputs that have all required fields
// (UTXO information, BIP32 derivation information, witness or sig scripts) set.
// If no error is returned, the PSBT is ready to be given to the next signer or
// to be finalized if lnd was the last signer.
//
// NOTE: This method only signs inputs (and only those it can sign), it does not
// perform any other tasks (such as coin selection, UTXO locking or
// input/output/fee value validation, PSBT finalization). Any input that is
// incomplete will be skipped.
func (b *BtcWallet) SignPsbt(packet *psbt.Packet) ([]uint32, error) {
// In signedInputs we return the indices of psbt inputs that were signed
// by our wallet. This way the caller can check if any inputs were signed.
var signedInputs []uint32
// Let's check that this is actually something we can and want to sign.
// We need at least one input and one output. In addition each
// input needs nonWitness Utxo or witness Utxo data specified.
err := psbt.InputsReadyToSign(packet)
if err != nil {
return nil, err
}
// Go through each input that doesn't have final witness data attached
// to it already and try to sign it. If there is nothing more to sign or
// there are inputs that we don't know how to sign, we won't return any
// error. So it's possible we're not the final signer.
tx := packet.UnsignedTx
prevOutputFetcher := wallet.PsbtPrevOutputFetcher(packet)
sigHashes := txscript.NewTxSigHashes(tx, prevOutputFetcher)
for idx := range tx.TxIn {
in := &packet.Inputs[idx]
// We can only sign if we have UTXO information available. Since
// we don't finalize, we just skip over any input that we know
// we can't do anything with. Since we only support signing
// witness inputs, we only look at the witness UTXO being set.
if in.WitnessUtxo == nil {
continue
}
// Skip this input if it's got final witness data attached.
if len(in.FinalScriptWitness) > 0 {
continue
}
// Skip this input if there is no BIP32 derivation info
// available.
if len(in.Bip32Derivation) == 0 {
continue
}
// TODO(guggero): For multisig, we'll need to find out what key
// to use and there should be multiple derivation paths in the
// BIP32 derivation field.
// Let's try and derive the key now. This method will decide if
// it's a BIP49/84 key for normal on-chain funds or a key of the
// custom purpose 1017 key scope.
derivationInfo := in.Bip32Derivation[0]
privKey, err := b.deriveKeyByBIP32Path(derivationInfo.Bip32Path)
if err != nil {
log.Warnf("SignPsbt: Skipping input %d, error "+
"deriving signing key: %v", idx, err)
continue
}
// We need to make sure we actually derived the key that was
// expected to be derived.
pubKeysEqual := bytes.Equal(
derivationInfo.PubKey,
privKey.PubKey().SerializeCompressed(),
)
if !pubKeysEqual {
log.Warnf("SignPsbt: Skipping input %d, derived "+
"public key %x does not match bip32 "+
"derivation info public key %x", idx,
privKey.PubKey().SerializeCompressed(),
derivationInfo.PubKey)
continue
}
// Do we need to tweak anything? Single or double tweaks are
// sent as custom/proprietary fields in the PSBT input section.
privKey = maybeTweakPrivKeyPsbt(in.Unknowns, privKey)
// What kind of signature is expected from us and do we have all
// information we need?
signMethod, err := validateSigningMethod(in)
if err != nil {
return nil, err
}
switch signMethod {
// For p2wkh, np2wkh and p2wsh.
case input.WitnessV0SignMethod:
err = signSegWitV0(in, tx, sigHashes, idx, privKey)
// For p2tr BIP0086 key spend only.
case input.TaprootKeySpendBIP0086SignMethod:
rootHash := make([]byte, 0)
err = signSegWitV1KeySpend(
in, tx, sigHashes, idx, privKey, rootHash,
)
// For p2tr with script commitment key spend path.
case input.TaprootKeySpendSignMethod:
rootHash := in.TaprootMerkleRoot
err = signSegWitV1KeySpend(
in, tx, sigHashes, idx, privKey, rootHash,
)
// For p2tr script spend path.
case input.TaprootScriptSpendSignMethod:
leafScript := in.TaprootLeafScript[0]
leaf := txscript.TapLeaf{
LeafVersion: leafScript.LeafVersion,
Script: leafScript.Script,
}
err = signSegWitV1ScriptSpend(
in, tx, sigHashes, idx, privKey, leaf,
)
default:
err = fmt.Errorf("unsupported signing method for "+
"PSBT signing: %v", signMethod)
}
if err != nil {
return nil, err
}
signedInputs = append(signedInputs, uint32(idx))
}
return signedInputs, nil
}
// validateSigningMethod attempts to detect the signing method that is required
// to sign for the given PSBT input and makes sure all information is available
// to do so.
func validateSigningMethod(in *psbt.PInput) (input.SignMethod, error) {
script, err := txscript.ParsePkScript(in.WitnessUtxo.PkScript)
if err != nil {
return 0, fmt.Errorf("error detecting signing method, "+
"couldn't parse pkScript: %v", err)
}
switch script.Class() {
case txscript.WitnessV0PubKeyHashTy, txscript.ScriptHashTy,
txscript.WitnessV0ScriptHashTy:
return input.WitnessV0SignMethod, nil
case txscript.WitnessV1TaprootTy:
if len(in.TaprootBip32Derivation) == 0 {
return 0, fmt.Errorf("cannot sign for taproot input " +
"without taproot BIP0032 derivation info")
}
// Currently, we only support creating one signature per input.
//
// TODO(guggero): Should we support signing multiple paths at
// the same time? What are the performance and security
// implications?
if len(in.TaprootBip32Derivation) > 1 {
return 0, fmt.Errorf("unsupported multiple taproot " +
"BIP0032 derivation info found, can only " +
"sign for one at a time")
}
derivation := in.TaprootBip32Derivation[0]
switch {
// No leaf hashes means this is the internal key we're signing
// with, so it's a key spend. And no merkle root means this is
// a BIP0086 output we're signing for.
case len(derivation.LeafHashes) == 0 &&
len(in.TaprootMerkleRoot) == 0:
return input.TaprootKeySpendBIP0086SignMethod, nil
// A non-empty merkle root means we committed to a taproot hash
// that we need to use in the tap tweak.
case len(derivation.LeafHashes) == 0:
// Getting here means the merkle root isn't empty, but
// is it exactly the length we need?
if len(in.TaprootMerkleRoot) != sha256.Size {
return 0, fmt.Errorf("invalid taproot merkle "+
"root length, got %d expected %d",
len(in.TaprootMerkleRoot), sha256.Size)
}
return input.TaprootKeySpendSignMethod, nil
// Currently, we only support signing for one leaf at a time.
//
// TODO(guggero): Should we support signing multiple paths at
// the same time? What are the performance and security
// implications?
case len(derivation.LeafHashes) == 1:
// If we're supposed to be signing for a leaf hash, we
// also expect the leaf script that hashes to that hash
// in the appropriate field.
if len(in.TaprootLeafScript) != 1 {
return 0, fmt.Errorf("specified leaf hash in " +
"taproot BIP0032 derivation but " +
"missing taproot leaf script")
}
leafScript := in.TaprootLeafScript[0]
leaf := txscript.TapLeaf{
LeafVersion: leafScript.LeafVersion,
Script: leafScript.Script,
}
leafHash := leaf.TapHash()
if !bytes.Equal(leafHash[:], derivation.LeafHashes[0]) {
return 0, fmt.Errorf("specified leaf hash in" +
"taproot BIP0032 derivation but " +
"corresponding taproot leaf script " +
"was not found")
}
return input.TaprootScriptSpendSignMethod, nil
default:
return 0, fmt.Errorf("unsupported number of leaf " +
"hashes in taproot BIP0032 derivation info, " +
"can only sign for one at a time")
}
default:
return 0, fmt.Errorf("unsupported script class for signing "+
"PSBT: %v", script.Class())
}
}
// EstimateInputWeight estimates the weight of a PSBT input and adds it to the
// passed in TxWeightEstimator. It returns an error if the input type is
// unknown or unsupported. Only inputs that have a known witness size are
// supported, which is P2WKH, NP2WKH and P2TR (key spend path).
func EstimateInputWeight(in *psbt.PInput, w *input.TxWeightEstimator) error {
if in.WitnessUtxo == nil {
return ErrInputMissingUTXOInfo
}
pkScript := in.WitnessUtxo.PkScript
switch {
case txscript.IsPayToScriptHash(pkScript):
w.AddNestedP2WKHInput()
case txscript.IsPayToWitnessPubKeyHash(pkScript):
w.AddP2WKHInput()
case txscript.IsPayToWitnessScriptHash(pkScript):
return fmt.Errorf("P2WSH inputs are not supported, cannot "+
"estimate witness size for script spend: %w",
ErrScriptSpendFeeEstimationUnsupported)
case txscript.IsPayToTaproot(pkScript):
signMethod, err := validateSigningMethod(in)
if err != nil {
return fmt.Errorf("error determining p2tr signing "+
"method: %w", err)
}
switch signMethod {
// For p2tr key spend paths.
case input.TaprootKeySpendBIP0086SignMethod,
input.TaprootKeySpendSignMethod:
w.AddTaprootKeySpendInput(in.SighashType)
// For p2tr script spend path.
case input.TaprootScriptSpendSignMethod:
return fmt.Errorf("P2TR inputs are not supported, "+
"cannot estimate witness size for script "+
"spend: %w",
ErrScriptSpendFeeEstimationUnsupported)
default:
return fmt.Errorf("unsupported signing method for "+
"PSBT signing: %v", signMethod)
}
default:
return fmt.Errorf("unknown input type for script %x: %w",
pkScript, ErrUnsupportedScript)
}
return nil
}
// SignSegWitV0 attempts to generate a signature for a SegWit version 0 input
// and stores it in the PartialSigs (and FinalScriptSig for np2wkh addresses)
// field.
func signSegWitV0(in *psbt.PInput, tx *wire.MsgTx,
sigHashes *txscript.TxSigHashes, idx int,
privKey *btcec.PrivateKey) error {
pubKeyBytes := privKey.PubKey().SerializeCompressed()
// Extract the correct witness and/or legacy scripts now, depending on
// the type of input we sign. The txscript package has the peculiar
// requirement that the PkScript of a P2PKH must be given as the witness
// script in order for it to arrive at the correct sighash. That's why
// we call it subScript here instead of witness script.
subScript := prepareScriptsV0(in)
// We have everything we need for signing the input now.
sig, err := txscript.RawTxInWitnessSignature(
tx, sigHashes, idx, in.WitnessUtxo.Value, subScript,
in.SighashType, privKey,
)
if err != nil {
return fmt.Errorf("error signing input %d: %w", idx, err)
}
in.PartialSigs = append(in.PartialSigs, &psbt.PartialSig{
PubKey: pubKeyBytes,
Signature: sig,
})
return nil
}
// signSegWitV1KeySpend attempts to generate a signature for a SegWit version 1
// (p2tr) input and stores it in the TaprootKeySpendSig field.
func signSegWitV1KeySpend(in *psbt.PInput, tx *wire.MsgTx,
sigHashes *txscript.TxSigHashes, idx int, privKey *btcec.PrivateKey,
tapscriptRootHash []byte) error {
rawSig, err := txscript.RawTxInTaprootSignature(
tx, sigHashes, idx, in.WitnessUtxo.Value,
in.WitnessUtxo.PkScript, tapscriptRootHash, in.SighashType,
privKey,
)
if err != nil {
return fmt.Errorf("error signing taproot input %d: %w", idx,
err)
}
in.TaprootKeySpendSig = rawSig
return nil
}
// signSegWitV1ScriptSpend attempts to generate a signature for a SegWit version
// 1 (p2tr) input and stores it in the TaprootScriptSpendSig field.
func signSegWitV1ScriptSpend(in *psbt.PInput, tx *wire.MsgTx,
sigHashes *txscript.TxSigHashes, idx int, privKey *btcec.PrivateKey,
leaf txscript.TapLeaf) error {
rawSig, err := txscript.RawTxInTapscriptSignature(
tx, sigHashes, idx, in.WitnessUtxo.Value,
in.WitnessUtxo.PkScript, leaf, in.SighashType, privKey,
)
if err != nil {
return fmt.Errorf("error signing taproot script input %d: %w",
idx, err)
}
leafHash := leaf.TapHash()
in.TaprootScriptSpendSig = append(
in.TaprootScriptSpendSig, &psbt.TaprootScriptSpendSig{
XOnlyPubKey: in.TaprootBip32Derivation[0].XOnlyPubKey,
LeafHash: leafHash[:],
// We snip off the sighash flag from the end (if it was
// specified in the first place.)
Signature: rawSig[:schnorr.SignatureSize],
SigHash: in.SighashType,
},
)
return nil
}
// prepareScriptsV0 returns the appropriate witness v0 and/or legacy scripts,
// depending on the type of input that should be signed.
func prepareScriptsV0(in *psbt.PInput) []byte {
switch {
// It's a NP2WKH input:
case len(in.RedeemScript) > 0:
return in.RedeemScript
// It's a P2WSH input:
case len(in.WitnessScript) > 0:
return in.WitnessScript
// It's a P2WKH input:
default:
return in.WitnessUtxo.PkScript
}
}
// maybeTweakPrivKeyPsbt examines if there are any tweak parameters given in the
// custom/proprietary PSBT fields and may perform a mapping on the passed
// private key in order to utilize the tweaks, if populated.
func maybeTweakPrivKeyPsbt(unknowns []*psbt.Unknown,
privKey *btcec.PrivateKey) *btcec.PrivateKey {
// There can be other custom/unknown keys in a PSBT that we just ignore.
// Key tweaking is optional and only one tweak (single _or_ double) can
// ever be applied (at least for any use cases described in the BOLT
// spec).
for _, u := range unknowns {
if bytes.Equal(u.Key, PsbtKeyTypeInputSignatureTweakSingle) {
return input.TweakPrivKey(privKey, u.Value)
}
if bytes.Equal(u.Key, PsbtKeyTypeInputSignatureTweakDouble) {
doubleTweakKey, _ := btcec.PrivKeyFromBytes(
u.Value,
)
return input.DeriveRevocationPrivKey(
privKey, doubleTweakKey,
)
}
}
return privKey
}
// FinalizePsbt expects a partial transaction with all inputs and outputs fully
// declared and tries to sign all inputs that belong to the specified account.
// Lnd must be the last signer of the transaction. That means, if there are any
// unsigned non-witness inputs or inputs without UTXO information attached or
// inputs without witness data that do not belong to lnd's wallet, this method
// will fail. If no error is returned, the PSBT is ready to be extracted and the
// final TX within to be broadcast.
//
// NOTE: This method does NOT publish the transaction after it's been
// finalized successfully.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) FinalizePsbt(packet *psbt.Packet, accountName string) error {
var (
keyScope *waddrmgr.KeyScope
accountNum uint32
)
switch accountName {
// If the default/imported account name was specified, we'll provide a
// nil key scope to FundPsbt, allowing it to sign inputs from both key
// scopes (NP2WKH, P2WKH).
case lnwallet.DefaultAccountName:
accountNum = defaultAccount
case waddrmgr.ImportedAddrAccountName:
accountNum = importedAccount
// Otherwise, map the account name to its key scope and internal account
// number to determine if the inputs belonging to this account should be
// signed.
default:
scope, account, err := b.lookupFirstCustomAccount(accountName)
if err != nil {
return err
}
keyScope = &scope
accountNum = account
}
return b.wallet.FinalizePsbt(keyScope, accountNum, packet)
}
// DecorateInputs fetches the UTXO information of all inputs it can identify and
// adds the required information to the package's inputs. The failOnUnknown
// boolean controls whether the method should return an error if it cannot
// identify an input or if it should just skip it.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) DecorateInputs(packet *psbt.Packet,
failOnUnknown bool) error {
return b.wallet.DecorateInputs(packet, failOnUnknown)
}
// lookupFirstCustomAccount returns the first custom account found. In theory,
// there should be only one custom account for the given name. However, due to a
// lack of check, users could have created custom accounts with various key
// scopes. This behaviour has been fixed but, we still need to handle this
// specific case to avoid non-deterministic behaviour implied by LookupAccount.
func (b *BtcWallet) lookupFirstCustomAccount(
name string) (waddrmgr.KeyScope, uint32, error) {
var (
account *waddrmgr.AccountProperties
keyScope waddrmgr.KeyScope
)
for _, scope := range waddrmgr.DefaultKeyScopes {
var err error
account, err = b.wallet.AccountPropertiesByName(scope, name)
if waddrmgr.IsError(err, waddrmgr.ErrAccountNotFound) {
continue
}
if err != nil {
return keyScope, 0, err
}
keyScope = scope
break
}
if account == nil {
return waddrmgr.KeyScope{}, 0, newAccountNotFoundError(name)
}
return keyScope, account.AccountNumber, nil
}
// Bip32DerivationFromKeyDesc returns the default and Taproot BIP-0032 key
// derivation information from the given key descriptor information.
func Bip32DerivationFromKeyDesc(keyDesc keychain.KeyDescriptor,
coinType uint32) (*psbt.Bip32Derivation, *psbt.TaprootBip32Derivation,
string) {
bip32Derivation := &psbt.Bip32Derivation{
PubKey: keyDesc.PubKey.SerializeCompressed(),
Bip32Path: []uint32{
keychain.BIP0043Purpose + hdkeychain.HardenedKeyStart,
coinType + hdkeychain.HardenedKeyStart,
uint32(keyDesc.Family) +
uint32(hdkeychain.HardenedKeyStart),
0,
keyDesc.Index,
},
}
derivationPath := fmt.Sprintf(
"m/%d'/%d'/%d'/%d/%d", keychain.BIP0043Purpose, coinType,
keyDesc.Family, 0, keyDesc.Index,
)
return bip32Derivation, &psbt.TaprootBip32Derivation{
XOnlyPubKey: bip32Derivation.PubKey[1:],
MasterKeyFingerprint: bip32Derivation.MasterKeyFingerprint,
Bip32Path: bip32Derivation.Bip32Path,
LeafHashes: make([][]byte, 0),
}, derivationPath
}
// Bip32DerivationFromAddress returns the default and Taproot BIP-0032 key
// derivation information from the given managed address.
func Bip32DerivationFromAddress(
addr waddrmgr.ManagedAddress) (*psbt.Bip32Derivation,
*psbt.TaprootBip32Derivation, string, error) {
pubKeyAddr, ok := addr.(waddrmgr.ManagedPubKeyAddress)
if !ok {
return nil, nil, "", fmt.Errorf("address is not a pubkey " +
"address")
}
scope, derivationInfo, haveInfo := pubKeyAddr.DerivationInfo()
if !haveInfo {
return nil, nil, "", fmt.Errorf("address is an imported " +
"public key, can't derive BIP32 path")
}
bip32Derivation := &psbt.Bip32Derivation{
PubKey: pubKeyAddr.PubKey().SerializeCompressed(),
Bip32Path: []uint32{
scope.Purpose + hdkeychain.HardenedKeyStart,
scope.Coin + hdkeychain.HardenedKeyStart,
derivationInfo.InternalAccount +
hdkeychain.HardenedKeyStart,
derivationInfo.Branch,
derivationInfo.Index,
},
}
derivationPath := fmt.Sprintf(
"m/%d'/%d'/%d'/%d/%d", scope.Purpose, scope.Coin,
derivationInfo.InternalAccount, derivationInfo.Branch,
derivationInfo.Index,
)
return bip32Derivation, &psbt.TaprootBip32Derivation{
XOnlyPubKey: bip32Derivation.PubKey[1:],
MasterKeyFingerprint: bip32Derivation.MasterKeyFingerprint,
Bip32Path: bip32Derivation.Bip32Path,
LeafHashes: make([][]byte, 0),
}, derivationPath, nil
}
package btcwallet
import (
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet"
)
// FetchOutpointInfo queries for the WalletController's knowledge of the passed
// outpoint. If the base wallet determines this output is under its control,
// then the original txout should be returned. Otherwise, a non-nil error value
// of ErrNotMine should be returned instead.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) FetchOutpointInfo(prevOut *wire.OutPoint) (*lnwallet.Utxo,
error) {
prevTx, txOut, confirmations, err := b.wallet.FetchOutpointInfo(prevOut)
if err != nil {
return nil, err
}
// Then, we'll populate all of the information required by the struct.
addressType := lnwallet.UnknownAddressType
switch {
case txscript.IsPayToWitnessPubKeyHash(txOut.PkScript):
addressType = lnwallet.WitnessPubKey
case txscript.IsPayToScriptHash(txOut.PkScript):
addressType = lnwallet.NestedWitnessPubKey
case txscript.IsPayToTaproot(txOut.PkScript):
addressType = lnwallet.TaprootPubkey
}
return &lnwallet.Utxo{
AddressType: addressType,
Value: btcutil.Amount(txOut.Value),
PkScript: txOut.PkScript,
Confirmations: confirmations,
OutPoint: *prevOut,
PrevTx: prevTx,
}, nil
}
// FetchDerivationInfo queries for the wallet's knowledge of the passed
// pkScript and constructs the derivation info and returns it.
func (b *BtcWallet) FetchDerivationInfo(
pkScript []byte) (*psbt.Bip32Derivation, error) {
return b.wallet.FetchDerivationInfo(pkScript)
}
// ScriptForOutput returns the address, witness program and redeem script for a
// given UTXO. An error is returned if the UTXO does not belong to our wallet or
// it is not a managed pubKey address.
func (b *BtcWallet) ScriptForOutput(output *wire.TxOut) (
waddrmgr.ManagedPubKeyAddress, []byte, []byte, error) {
return b.wallet.ScriptForOutput(output)
}
// deriveKeyByBIP32Path derives a key described by a BIP32 path. We expect the
// first three elements of the path to be hardened according to BIP44, so they
// must be a number >= 2^31.
func (b *BtcWallet) deriveKeyByBIP32Path(path []uint32) (*btcec.PrivateKey,
error) {
// Make sure we get a full path with exactly 5 elements. A path is
// either custom purpose one with 4 dynamic and one static elements:
// m/1017'/coinType'/keyFamily'/0/index
// Or a default BIP49/89 one with 5 elements:
// m/purpose'/coinType'/account'/change/index
const expectedDerivationPathDepth = 5
if len(path) != expectedDerivationPathDepth {
return nil, fmt.Errorf("invalid BIP32 derivation path, "+
"expected path length %d, instead was %d",
expectedDerivationPathDepth, len(path))
}
// Assert that the first three parts of the path are actually hardened
// to avoid under-flowing the uint32 type.
if err := assertHardened(path[0], path[1], path[2]); err != nil {
return nil, fmt.Errorf("invalid BIP32 derivation path, "+
"expected first three elements to be hardened: %w", err)
}
purpose := path[0] - hdkeychain.HardenedKeyStart
coinType := path[1] - hdkeychain.HardenedKeyStart
account := path[2] - hdkeychain.HardenedKeyStart
change, index := path[3], path[4]
// Is this a custom lnd internal purpose key?
switch purpose {
case keychain.BIP0043Purpose:
// Make sure it's for the same coin type as our wallet's
// keychain scope.
if coinType != b.chainKeyScope.Coin {
return nil, fmt.Errorf("invalid BIP32 derivation "+
"path, expected coin type %d, instead was %d",
b.chainKeyScope.Coin, coinType)
}
return b.deriveKeyByLocator(keychain.KeyLocator{
Family: keychain.KeyFamily(account),
Index: index,
})
// Is it a standard, BIP defined purpose that the wallet understands?
case waddrmgr.KeyScopeBIP0044.Purpose,
waddrmgr.KeyScopeBIP0049Plus.Purpose,
waddrmgr.KeyScopeBIP0084.Purpose,
waddrmgr.KeyScopeBIP0086.Purpose:
// We're going to continue below the switch statement to avoid
// unnecessary indentation for this default case.
// Currently, there is no way to import any other key scopes than the
// one custom purpose or three standard ones into lnd's wallet. So we
// shouldn't accept any other scopes to sign for.
default:
return nil, fmt.Errorf("invalid BIP32 derivation path, "+
"unknown purpose %d", purpose)
}
// Okay, we made sure it's a BIP49/84 key, so we need to derive it now.
// Interestingly, the btcwallet never actually uses a coin type other
// than 0 for those keys, so we need to make sure this behavior is
// replicated here.
if coinType != 0 {
return nil, fmt.Errorf("invalid BIP32 derivation path, coin " +
"type must be 0 for BIP49/84 btcwallet keys")
}
// We only expect to be asked to sign with key scopes that we know
// about. So if the scope doesn't exist, we don't create it.
scope := waddrmgr.KeyScope{
Purpose: purpose,
Coin: coinType,
}
keyPath := waddrmgr.DerivationPath{
InternalAccount: account,
Account: account,
Branch: change,
Index: index,
}
privKey, err := b.wallet.DeriveFromKeyPath(scope, keyPath)
if err != nil {
return nil, fmt.Errorf("error deriving key from path %#v: %w",
keyPath, err)
}
return privKey, nil
}
// assertHardened makes sure each given element is >= 2^31.
func assertHardened(elements ...uint32) error {
for idx, element := range elements {
if element < hdkeychain.HardenedKeyStart {
return fmt.Errorf("element at index %d is not hardened",
idx)
}
}
return nil
}
// deriveKeyByLocator attempts to derive a key stored in the wallet given a
// valid key locator.
func (b *BtcWallet) deriveKeyByLocator(
keyLoc keychain.KeyLocator) (*btcec.PrivateKey, error) {
// We'll assume the special lightning key scope in this case.
scope := b.chainKeyScope
path := waddrmgr.DerivationPath{
InternalAccount: uint32(keyLoc.Family),
Account: uint32(keyLoc.Family),
Branch: 0,
Index: keyLoc.Index,
}
key, err := b.wallet.DeriveFromKeyPathAddAccount(scope, path)
if err != nil {
return nil, err
}
return key, nil
}
// fetchPrivKey attempts to retrieve the raw private key corresponding to the
// passed public key if populated, or the key descriptor path (if non-empty).
func (b *BtcWallet) fetchPrivKey(
keyDesc *keychain.KeyDescriptor) (*btcec.PrivateKey, error) {
// If the key locator within the descriptor *isn't* empty, then we can
// directly derive the keys raw.
emptyLocator := keyDesc.KeyLocator.IsEmpty()
if !emptyLocator || keyDesc.PubKey == nil {
return b.deriveKeyByLocator(keyDesc.KeyLocator)
}
hash160 := btcutil.Hash160(keyDesc.PubKey.SerializeCompressed())
addr, err := btcutil.NewAddressWitnessPubKeyHash(hash160, b.netParams)
if err != nil {
return nil, err
}
// Otherwise, we'll attempt to derive the key based on the address.
// This will only work if we've already derived this address in the
// past, since the wallet relies on a mapping of addr -> key.
key, err := b.wallet.PrivKeyForAddress(addr)
switch {
// If we didn't find this key in the wallet, then there's a chance that
// this is actually an "empty" key locator. The legacy KeyLocator
// format failed to properly distinguish an empty key locator from the
// very first in the index (0, 0).IsEmpty() == true.
case waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) && emptyLocator:
return b.deriveKeyByLocator(keyDesc.KeyLocator)
case err != nil:
return nil, err
default:
return key, nil
}
}
// maybeTweakPrivKey examines the single and double tweak parameters on the
// passed sign descriptor and may perform a mapping on the passed private key
// in order to utilize the tweaks, if populated.
func maybeTweakPrivKey(signDesc *input.SignDescriptor,
privKey *btcec.PrivateKey) (*btcec.PrivateKey, error) {
var retPriv *btcec.PrivateKey
switch {
case signDesc.SingleTweak != nil:
retPriv = input.TweakPrivKey(privKey,
signDesc.SingleTweak)
case signDesc.DoubleTweak != nil:
retPriv = input.DeriveRevocationPrivKey(privKey,
signDesc.DoubleTweak)
default:
retPriv = privKey
}
return retPriv, nil
}
// SignOutputRaw generates a signature for the passed transaction according to
// the data within the passed SignDescriptor.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) SignOutputRaw(tx *wire.MsgTx,
signDesc *input.SignDescriptor) (input.Signature, error) {
witnessScript := signDesc.WitnessScript
// First attempt to fetch the private key which corresponds to the
// specified public key.
privKey, err := b.fetchPrivKey(&signDesc.KeyDesc)
if err != nil {
return nil, err
}
// If a tweak (single or double) is specified, then we'll need to use
// this tweak to derive the final private key to be used for signing
// this output.
privKey, err = maybeTweakPrivKey(signDesc, privKey)
if err != nil {
return nil, err
}
// In case of a taproot output any signature is always a Schnorr
// signature, based on the new tapscript sighash algorithm.
if txscript.IsPayToTaproot(signDesc.Output.PkScript) {
sigHashes := txscript.NewTxSigHashes(
tx, signDesc.PrevOutputFetcher,
)
// Are we spending a script path or the key path? The API is
// slightly different, so we need to account for that to get the
// raw signature.
var rawSig []byte
switch signDesc.SignMethod {
case input.TaprootKeySpendBIP0086SignMethod,
input.TaprootKeySpendSignMethod:
// This function tweaks the private key using the tap
// root key supplied as the tweak.
rawSig, err = txscript.RawTxInTaprootSignature(
tx, sigHashes, signDesc.InputIndex,
signDesc.Output.Value, signDesc.Output.PkScript,
signDesc.TapTweak, signDesc.HashType,
privKey,
)
if err != nil {
return nil, err
}
case input.TaprootScriptSpendSignMethod:
leaf := txscript.TapLeaf{
LeafVersion: txscript.BaseLeafVersion,
Script: witnessScript,
}
rawSig, err = txscript.RawTxInTapscriptSignature(
tx, sigHashes, signDesc.InputIndex,
signDesc.Output.Value, signDesc.Output.PkScript,
leaf, signDesc.HashType, privKey,
)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unknown sign method: %v",
signDesc.SignMethod)
}
// The signature returned above might have a sighash flag
// attached if a non-default type was used. We'll slice this
// off if it exists to ensure we can properly parse the raw
// signature.
sig, err := schnorr.ParseSignature(
rawSig[:schnorr.SignatureSize],
)
if err != nil {
return nil, err
}
return sig, nil
}
// TODO(roasbeef): generate sighash midstate if not present?
amt := signDesc.Output.Value
sig, err := txscript.RawTxInWitnessSignature(
tx, signDesc.SigHashes, signDesc.InputIndex, amt,
witnessScript, signDesc.HashType, privKey,
)
if err != nil {
return nil, err
}
// Chop off the sighash flag at the end of the signature.
return ecdsa.ParseDERSignature(sig[:len(sig)-1])
}
// ComputeInputScript generates a complete InputScript for the passed
// transaction with the signature as defined within the passed SignDescriptor.
// This method is capable of generating the proper input script for both
// regular p2wkh output and p2wkh outputs nested within a regular p2sh output.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) ComputeInputScript(tx *wire.MsgTx,
signDesc *input.SignDescriptor) (*input.Script, error) {
// If a tweak (single or double) is specified, then we'll need to use
// this tweak to derive the final private key to be used for signing
// this output.
privKeyTweaker := func(k *btcec.PrivateKey) (*btcec.PrivateKey, error) {
return maybeTweakPrivKey(signDesc, k)
}
// Let the wallet compute the input script now.
witness, sigScript, err := b.wallet.ComputeInputScript(
tx, signDesc.Output, signDesc.InputIndex, signDesc.SigHashes,
signDesc.HashType, privKeyTweaker,
)
if err != nil {
return nil, err
}
return &input.Script{
Witness: witness,
SigScript: sigScript,
}, nil
}
// A compile time check to ensure that BtcWallet implements the Signer
// interface.
var _ input.Signer = (*BtcWallet)(nil)
// SignMessage attempts to sign a target message with the private key that
// corresponds to the passed key locator. If the target private key is unable to
// be found, then an error will be returned. The actual digest signed is the
// double SHA-256 of the passed message.
//
// NOTE: This is a part of the MessageSigner interface.
func (b *BtcWallet) SignMessage(keyLoc keychain.KeyLocator,
msg []byte, doubleHash bool) (*ecdsa.Signature, error) {
// First attempt to fetch the private key which corresponds to the
// specified public key.
privKey, err := b.fetchPrivKey(&keychain.KeyDescriptor{
KeyLocator: keyLoc,
})
if err != nil {
return nil, err
}
// Double hash and sign the data.
var msgDigest []byte
if doubleHash {
msgDigest = chainhash.DoubleHashB(msg)
} else {
msgDigest = chainhash.HashB(msg)
}
return ecdsa.Sign(privKey, msgDigest), nil
}
// A compile time check to ensure that BtcWallet implements the MessageSigner
// interface.
var _ lnwallet.MessageSigner = (*BtcWallet)(nil)
package chainfee
import (
"encoding/json"
"errors"
"fmt"
"io"
"math"
prand "math/rand"
"net"
"net/http"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/rpcclient"
"github.com/lightningnetwork/lnd/lnutils"
)
const (
// MaxBlockTarget is the highest number of blocks confirmations that
// a WebAPIEstimator will cache fees for. This number is chosen
// because it's the highest number of confs bitcoind will return a fee
// estimate for.
MaxBlockTarget uint32 = 1008
// minBlockTarget is the lowest number of blocks confirmations that
// a WebAPIEstimator will cache fees for. Requesting an estimate for
// less than this will result in an error.
minBlockTarget uint32 = 1
// WebAPIConnectionTimeout specifies the timeout value for connecting
// to the api source.
WebAPIConnectionTimeout = 5 * time.Second
// WebAPIResponseTimeout specifies the timeout value for receiving a
// fee response from the api source.
WebAPIResponseTimeout = 10 * time.Second
// economicalFeeMode is a mode that bitcoind uses to serve
// non-conservative fee estimates. These fee estimates are less
// resistant to shocks.
economicalFeeMode = "ECONOMICAL"
// filterCapConfTarget is the conf target that will be used to cap our
// minimum feerate if we used the median of our peers' feefilter
// values.
filterCapConfTarget = uint32(1)
)
var (
// errNoFeeRateFound is used when a given conf target cannot be found
// from the fee estimator.
errNoFeeRateFound = errors.New("no fee estimation for block target")
// errEmptyCache is used when the fee rate cache is empty.
errEmptyCache = errors.New("fee rate cache is empty")
)
// Estimator provides the ability to estimate on-chain transaction fees for
// various combinations of transaction sizes and desired confirmation time
// (measured by number of blocks).
type Estimator interface {
// EstimateFeePerKW takes in a target for the number of blocks until an
// initial confirmation and returns the estimated fee expressed in
// sat/kw.
EstimateFeePerKW(numBlocks uint32) (SatPerKWeight, error)
// Start signals the Estimator to start any processes or goroutines
// it needs to perform its duty.
Start() error
// Stop stops any spawned goroutines and cleans up the resources used
// by the fee estimator.
Stop() error
// RelayFeePerKW returns the minimum fee rate required for transactions
// to be relayed. This is also the basis for calculation of the dust
// limit.
RelayFeePerKW() SatPerKWeight
}
// StaticEstimator will return a static value for all fee calculation requests.
// It is designed to be replaced by a proper fee calculation implementation.
// The fees are not accessible directly, because changing them would not be
// thread safe.
type StaticEstimator struct {
// feePerKW is the static fee rate in satoshis-per-vbyte that will be
// returned by this fee estimator.
feePerKW SatPerKWeight
// relayFee is the minimum fee rate required for transactions to be
// relayed.
relayFee SatPerKWeight
}
// NewStaticEstimator returns a new static fee estimator instance.
func NewStaticEstimator(feePerKW, relayFee SatPerKWeight) *StaticEstimator {
return &StaticEstimator{
feePerKW: feePerKW,
relayFee: relayFee,
}
}
// EstimateFeePerKW will return a static value for fee calculations.
//
// NOTE: This method is part of the Estimator interface.
func (e StaticEstimator) EstimateFeePerKW(numBlocks uint32) (SatPerKWeight, error) {
return e.feePerKW, nil
}
// RelayFeePerKW returns the minimum fee rate required for transactions to be
// relayed.
//
// NOTE: This method is part of the Estimator interface.
func (e StaticEstimator) RelayFeePerKW() SatPerKWeight {
return e.relayFee
}
// Start signals the Estimator to start any processes or goroutines
// it needs to perform its duty.
//
// NOTE: This method is part of the Estimator interface.
func (e StaticEstimator) Start() error {
return nil
}
// Stop stops any spawned goroutines and cleans up the resources used
// by the fee estimator.
//
// NOTE: This method is part of the Estimator interface.
func (e StaticEstimator) Stop() error {
return nil
}
// A compile-time assertion to ensure that StaticFeeEstimator implements the
// Estimator interface.
var _ Estimator = (*StaticEstimator)(nil)
// BtcdEstimator is an implementation of the Estimator interface backed
// by the RPC interface of an active btcd node. This implementation will proxy
// any fee estimation requests to btcd's RPC interface.
type BtcdEstimator struct {
// fallbackFeePerKW is the fall back fee rate in sat/kw that is returned
// if the fee estimator does not yet have enough data to actually
// produce fee estimates.
fallbackFeePerKW SatPerKWeight
// minFeeManager is used to query the current minimum fee, in sat/kw,
// that we should enforce. This will be used to determine fee rate for
// a transaction when the estimated fee rate is too low to allow the
// transaction to propagate through the network.
minFeeManager *minFeeManager
btcdConn *rpcclient.Client
// filterManager uses our peer's feefilter values to determine a
// suitable feerate to use that will allow successful transaction
// propagation.
filterManager *filterManager
}
// NewBtcdEstimator creates a new BtcdEstimator given a fully populated
// rpc config that is able to successfully connect and authenticate with the
// btcd node, and also a fall back fee rate. The fallback fee rate is used in
// the occasion that the estimator has insufficient data, or returns zero for a
// fee estimate.
func NewBtcdEstimator(rpcConfig rpcclient.ConnConfig,
fallBackFeeRate SatPerKWeight) (*BtcdEstimator, error) {
rpcConfig.DisableConnectOnNew = true
rpcConfig.DisableAutoReconnect = false
chainConn, err := rpcclient.New(&rpcConfig, nil)
if err != nil {
return nil, err
}
fetchCb := func() ([]SatPerKWeight, error) {
return fetchBtcdFilters(chainConn)
}
return &BtcdEstimator{
fallbackFeePerKW: fallBackFeeRate,
btcdConn: chainConn,
filterManager: newFilterManager(fetchCb),
}, nil
}
// Start signals the Estimator to start any processes or goroutines
// it needs to perform its duty.
//
// NOTE: This method is part of the Estimator interface.
func (b *BtcdEstimator) Start() error {
if err := b.btcdConn.Connect(20); err != nil {
return err
}
// Once the connection to the backend node has been established, we
// can initialise the minimum relay fee manager which queries the
// chain backend for the minimum relay fee on construction.
minRelayFeeManager, err := newMinFeeManager(
defaultUpdateInterval, b.fetchMinRelayFee,
)
if err != nil {
return err
}
b.minFeeManager = minRelayFeeManager
b.filterManager.Start()
return nil
}
// fetchMinRelayFee fetches and returns the minimum relay fee in sat/kb from
// the btcd backend.
func (b *BtcdEstimator) fetchMinRelayFee() (SatPerKWeight, error) {
info, err := b.btcdConn.GetInfo()
if err != nil {
return 0, err
}
relayFee, err := btcutil.NewAmount(info.RelayFee)
if err != nil {
return 0, err
}
// The fee rate is expressed in sat/kb, so we'll manually convert it to
// our desired sat/kw rate.
return SatPerKVByte(relayFee).FeePerKWeight(), nil
}
// Stop stops any spawned goroutines and cleans up the resources used
// by the fee estimator.
//
// NOTE: This method is part of the Estimator interface.
func (b *BtcdEstimator) Stop() error {
b.filterManager.Stop()
b.btcdConn.Shutdown()
b.btcdConn.WaitForShutdown()
return nil
}
// EstimateFeePerKW takes in a target for the number of blocks until an initial
// confirmation and returns the estimated fee expressed in sat/kw.
//
// NOTE: This method is part of the Estimator interface.
func (b *BtcdEstimator) EstimateFeePerKW(numBlocks uint32) (SatPerKWeight, error) {
feeEstimate, err := b.fetchEstimate(numBlocks)
switch {
// If the estimator doesn't have enough data, or returns an error, then
// to return a proper value, then we'll return the default fall back
// fee rate.
case err != nil:
log.Errorf("unable to query estimator: %v", err)
fallthrough
case feeEstimate == 0:
return b.fallbackFeePerKW, nil
}
return feeEstimate, nil
}
// RelayFeePerKW returns the minimum fee rate required for transactions to be
// relayed.
//
// NOTE: This method is part of the Estimator interface.
func (b *BtcdEstimator) RelayFeePerKW() SatPerKWeight {
// Get a suitable minimum feerate to use. This may optionally use the
// median of our peers' feefilter values.
feeCapClosure := func() (SatPerKWeight, error) {
return b.fetchEstimateInner(filterCapConfTarget)
}
return chooseMinFee(
b.minFeeManager.fetchMinFee, b.filterManager.FetchMedianFilter,
feeCapClosure,
)
}
// fetchEstimate returns a fee estimate for a transaction to be confirmed in
// confTarget blocks. The estimate is returned in sat/kw.
func (b *BtcdEstimator) fetchEstimate(confTarget uint32) (SatPerKWeight, error) {
satPerKw, err := b.fetchEstimateInner(confTarget)
if err != nil {
return 0, err
}
// Finally, we'll enforce our fee floor by choosing the higher of the
// minimum relay fee and the feerate returned by the filterManager.
absoluteMinFee := b.RelayFeePerKW()
if satPerKw < absoluteMinFee {
log.Debugf("Estimated fee rate of %v sat/kw is too low, "+
"using fee floor of %v sat/kw instead", satPerKw,
absoluteMinFee)
satPerKw = absoluteMinFee
}
log.Debugf("Returning %v sat/kw for conf target of %v",
int64(satPerKw), confTarget)
return satPerKw, nil
}
func (b *BtcdEstimator) fetchEstimateInner(confTarget uint32) (SatPerKWeight,
error) {
// First, we'll fetch the estimate for our confirmation target.
btcPerKB, err := b.btcdConn.EstimateFee(int64(confTarget))
if err != nil {
return 0, err
}
// Next, we'll convert the returned value to satoshis, as it's
// currently returned in BTC.
satPerKB, err := btcutil.NewAmount(btcPerKB)
if err != nil {
return 0, err
}
// Since we use fee rates in sat/kw internally, we'll convert the
// estimated fee rate from its sat/kb representation to sat/kw.
return SatPerKVByte(satPerKB).FeePerKWeight(), nil
}
// A compile-time assertion to ensure that BtcdEstimator implements the
// Estimator interface.
var _ Estimator = (*BtcdEstimator)(nil)
// BitcoindEstimator is an implementation of the Estimator interface backed by
// the RPC interface of an active bitcoind node. This implementation will proxy
// any fee estimation requests to bitcoind's RPC interface.
type BitcoindEstimator struct {
// fallbackFeePerKW is the fallback fee rate in sat/kw that is returned
// if the fee estimator does not yet have enough data to actually
// produce fee estimates.
fallbackFeePerKW SatPerKWeight
// minFeeManager is used to keep track of the minimum fee, in sat/kw,
// that we should enforce. This will be used as the default fee rate
// for a transaction when the estimated fee rate is too low to allow
// the transaction to propagate through the network.
minFeeManager *minFeeManager
// feeMode is the estimate_mode to use when calling "estimatesmartfee".
// It can be either "ECONOMICAL" or "CONSERVATIVE", and it's default
// to "CONSERVATIVE".
feeMode string
// TODO(ziggie): introduce an interface for the client to enhance
// testability of the estimator.
bitcoindConn *rpcclient.Client
// filterManager uses our peer's feefilter values to determine a
// suitable feerate to use that will allow successful transaction
// propagation.
filterManager *filterManager
}
// NewBitcoindEstimator creates a new BitcoindEstimator given a fully populated
// rpc config that is able to successfully connect and authenticate with the
// bitcoind node, and also a fall back fee rate. The fallback fee rate is used
// in the occasion that the estimator has insufficient data, or returns zero
// for a fee estimate.
func NewBitcoindEstimator(rpcConfig rpcclient.ConnConfig, feeMode string,
fallBackFeeRate SatPerKWeight) (*BitcoindEstimator, error) {
rpcConfig.DisableConnectOnNew = true
rpcConfig.DisableAutoReconnect = false
rpcConfig.DisableTLS = true
rpcConfig.HTTPPostMode = true
chainConn, err := rpcclient.New(&rpcConfig, nil)
if err != nil {
return nil, err
}
fetchCb := func() ([]SatPerKWeight, error) {
return fetchBitcoindFilters(chainConn)
}
return &BitcoindEstimator{
fallbackFeePerKW: fallBackFeeRate,
bitcoindConn: chainConn,
feeMode: feeMode,
filterManager: newFilterManager(fetchCb),
}, nil
}
// Start signals the Estimator to start any processes or goroutines
// it needs to perform its duty.
//
// NOTE: This method is part of the Estimator interface.
func (b *BitcoindEstimator) Start() error {
// Once the connection to the backend node has been established, we'll
// initialise the minimum relay fee manager which will query
// the backend node for its minimum mempool fee.
relayFeeManager, err := newMinFeeManager(
defaultUpdateInterval,
b.fetchMinMempoolFee,
)
if err != nil {
return err
}
b.minFeeManager = relayFeeManager
b.filterManager.Start()
return nil
}
// fetchMinMempoolFee is used to fetch the minimum fee that the backend node
// requires for a tx to enter its mempool. The returned fee will be the
// maximum of the minimum relay fee and the minimum mempool fee.
func (b *BitcoindEstimator) fetchMinMempoolFee() (SatPerKWeight, error) {
resp, err := b.bitcoindConn.RawRequest("getmempoolinfo", nil)
if err != nil {
return 0, err
}
// Parse the response to retrieve the min mempool fee in sat/KB.
// mempoolminfee is the maximum of minrelaytxfee and
// minimum mempool fee
info := struct {
MempoolMinFee float64 `json:"mempoolminfee"`
}{}
if err := json.Unmarshal(resp, &info); err != nil {
return 0, err
}
minMempoolFee, err := btcutil.NewAmount(info.MempoolMinFee)
if err != nil {
return 0, err
}
// The fee rate is expressed in sat/kb, so we'll manually convert it to
// our desired sat/kw rate.
return SatPerKVByte(minMempoolFee).FeePerKWeight(), nil
}
// Stop stops any spawned goroutines and cleans up the resources used
// by the fee estimator.
//
// NOTE: This method is part of the Estimator interface.
func (b *BitcoindEstimator) Stop() error {
b.filterManager.Stop()
return nil
}
// EstimateFeePerKW takes in a target for the number of blocks until an initial
// confirmation and returns the estimated fee expressed in sat/kw.
//
// NOTE: This method is part of the Estimator interface.
func (b *BitcoindEstimator) EstimateFeePerKW(
numBlocks uint32) (SatPerKWeight, error) {
if numBlocks > MaxBlockTarget {
log.Debugf("conf target %d exceeds the max value, "+
"use %d instead.", numBlocks, MaxBlockTarget,
)
numBlocks = MaxBlockTarget
}
feeEstimate, err := b.fetchEstimate(numBlocks, b.feeMode)
switch {
// If the estimator doesn't have enough data, or returns an error, then
// to return a proper value, then we'll return the default fall back
// fee rate.
case err != nil:
log.Errorf("unable to query estimator: %v", err)
fallthrough
case feeEstimate == 0:
return b.fallbackFeePerKW, nil
}
return feeEstimate, nil
}
// RelayFeePerKW returns the minimum fee rate required for transactions to be
// relayed.
//
// NOTE: This method is part of the Estimator interface.
func (b *BitcoindEstimator) RelayFeePerKW() SatPerKWeight {
// Get a suitable minimum feerate to use. This may optionally use the
// median of our peers' feefilter values.
feeCapClosure := func() (SatPerKWeight, error) {
return b.fetchEstimateInner(
filterCapConfTarget, economicalFeeMode,
)
}
return chooseMinFee(
b.minFeeManager.fetchMinFee, b.filterManager.FetchMedianFilter,
feeCapClosure,
)
}
// fetchEstimate returns a fee estimate for a transaction to be confirmed in
// confTarget blocks. The estimate is returned in sat/kw.
func (b *BitcoindEstimator) fetchEstimate(confTarget uint32, feeMode string) (
SatPerKWeight, error) {
satPerKw, err := b.fetchEstimateInner(confTarget, feeMode)
if err != nil {
return 0, err
}
// Finally, we'll enforce our fee floor by choosing the higher of the
// minimum relay fee and the feerate returned by the filterManager.
absoluteMinFee := b.RelayFeePerKW()
if satPerKw < absoluteMinFee {
log.Debugf("Estimated fee rate of %v sat/kw is too low, "+
"using fee floor of %v sat/kw instead", satPerKw,
absoluteMinFee)
satPerKw = absoluteMinFee
}
log.Debugf("Returning %v sat/kw for conf target of %v",
int64(satPerKw), confTarget)
return satPerKw, nil
}
func (b *BitcoindEstimator) fetchEstimateInner(confTarget uint32,
feeMode string) (SatPerKWeight, error) {
// First, we'll send an "estimatesmartfee" command as a raw request,
// since it isn't supported by btcd but is available in bitcoind.
target, err := json.Marshal(uint64(confTarget))
if err != nil {
return 0, err
}
// The mode must be either ECONOMICAL or CONSERVATIVE.
mode, err := json.Marshal(feeMode)
if err != nil {
return 0, err
}
resp, err := b.bitcoindConn.RawRequest(
"estimatesmartfee", []json.RawMessage{target, mode},
)
if err != nil {
return 0, err
}
// Next, we'll parse the response to get the BTC per KB.
feeEstimate := struct {
FeeRate float64 `json:"feerate"`
}{}
err = json.Unmarshal(resp, &feeEstimate)
if err != nil {
return 0, err
}
// Next, we'll convert the returned value to satoshis, as it's currently
// returned in BTC.
satPerKB, err := btcutil.NewAmount(feeEstimate.FeeRate)
if err != nil {
return 0, err
}
// Bitcoind will not report any fee estimation if it has not enough
// data available hence the fee will remain zero. We return an error
// here to make sure that we do not use the min relay fee instead.
if satPerKB == 0 {
return 0, fmt.Errorf("fee estimation data not available yet")
}
// Since we use fee rates in sat/kw internally, we'll convert the
// estimated fee rate from its sat/kb representation to sat/kw.
return SatPerKVByte(satPerKB).FeePerKWeight(), nil
}
// chooseMinFee takes the minimum relay fee and the median of our peers'
// feefilter values and takes the higher of the two. It then compares the value
// against a maximum fee and caps it if the value is higher than the maximum
// fee. This function is only called if we have data for our peers' feefilter.
// The returned value will be used as the fee floor for calls to
// RelayFeePerKW.
func chooseMinFee(minRelayFeeFunc func() SatPerKWeight,
medianFilterFunc func() (SatPerKWeight, error),
feeCapFunc func() (SatPerKWeight, error)) SatPerKWeight {
minRelayFee := minRelayFeeFunc()
medianFilter, err := medianFilterFunc()
if err != nil {
// If we don't have feefilter data, we fallback to using our
// minimum relay fee.
return minRelayFee
}
feeCap, err := feeCapFunc()
if err != nil {
// If we encountered an error, don't use the medianFilter and
// instead fallback to using our minimum relay fee.
return minRelayFee
}
// If the median feefilter is higher than our minimum relay fee, use it
// instead.
if medianFilter > minRelayFee {
// Only apply the cap if the median filter was used. This is
// to prevent an adversary from taking up the majority of our
// outbound peer slots and forcing us to use a high median
// filter value.
if medianFilter > feeCap {
return feeCap
}
return medianFilter
}
return minRelayFee
}
// A compile-time assertion to ensure that BitcoindEstimator implements the
// Estimator interface.
var _ Estimator = (*BitcoindEstimator)(nil)
// WebAPIFeeSource is an interface allows the WebAPIEstimator to query an
// arbitrary HTTP-based fee estimator. Each new set/network will gain an
// implementation of this interface in order to allow the WebAPIEstimator to
// be fully generic in its logic.
type WebAPIFeeSource interface {
// GetFeeInfo will query the web API, parse the response into a
// WebAPIResponse which contains a map of confirmation targets to
// sat/kw fees and min relay feerate.
GetFeeInfo() (WebAPIResponse, error)
}
// SparseConfFeeSource is an implementation of the WebAPIFeeSource that utilizes
// a user-specified fee estimation API for Bitcoin. It expects the response
// to be in the JSON format: `fee_by_block_target: { ... }` where the value maps
// block targets to fee estimates (in sat per kilovbyte).
type SparseConfFeeSource struct {
// URL is the fee estimation API specified by the user.
URL string
}
// WebAPIResponse is the response returned by the fee estimation API.
type WebAPIResponse struct {
// FeeByBlockTarget is a map of confirmation targets to sat/kvb fees.
FeeByBlockTarget map[uint32]uint32 `json:"fee_by_block_target"`
// MinRelayFeerate is the minimum relay fee in sat/kvb.
MinRelayFeerate SatPerKVByte `json:"min_relay_feerate"`
}
// parseResponse attempts to parse the body of the response generated by the
// above query URL. Typically this will be JSON, but the specifics are left to
// the WebAPIFeeSource implementation.
func (s SparseConfFeeSource) parseResponse(r io.Reader) (
WebAPIResponse, error) {
resp := WebAPIResponse{
FeeByBlockTarget: make(map[uint32]uint32),
MinRelayFeerate: 0,
}
jsonReader := json.NewDecoder(r)
if err := jsonReader.Decode(&resp); err != nil {
return WebAPIResponse{}, err
}
if resp.MinRelayFeerate == 0 {
log.Errorf("No min relay fee rate available, using default %v",
FeePerKwFloor)
resp.MinRelayFeerate = FeePerKwFloor.FeePerKVByte()
}
return resp, nil
}
// GetFeeInfo will query the web API, parse the response and return a map of
// confirmation targets to sat/kw fees and min relay feerate in a parsed
// response.
func (s SparseConfFeeSource) GetFeeInfo() (WebAPIResponse, error) {
// Rather than use the default http.Client, we'll make a custom one
// which will allow us to control how long we'll wait to read the
// response from the service. This way, if the service is down or
// overloaded, we can exit early and use our default fee.
netTransport := &http.Transport{
Dial: (&net.Dialer{
Timeout: WebAPIConnectionTimeout,
}).Dial,
TLSHandshakeTimeout: WebAPIConnectionTimeout,
}
netClient := &http.Client{
Timeout: WebAPIResponseTimeout,
Transport: netTransport,
}
// With the client created, we'll query the API source to fetch the URL
// that we should use to query for the fee estimation.
targetURL := s.URL
resp, err := netClient.Get(targetURL)
if err != nil {
log.Errorf("unable to query web api for fee response: %v",
err)
return WebAPIResponse{}, err
}
defer resp.Body.Close()
// Once we've obtained the response, we'll instruct the WebAPIFeeSource
// to parse out the body to obtain our final result.
parsedResp, err := s.parseResponse(resp.Body)
if err != nil {
log.Errorf("unable to parse fee api response: %v", err)
return WebAPIResponse{}, err
}
return parsedResp, nil
}
// A compile-time assertion to ensure that SparseConfFeeSource implements the
// WebAPIFeeSource interface.
var _ WebAPIFeeSource = (*SparseConfFeeSource)(nil)
// WebAPIEstimator is an implementation of the Estimator interface that
// queries an HTTP-based fee estimation from an existing web API.
type WebAPIEstimator struct {
started atomic.Bool
stopped atomic.Bool
// apiSource is the backing web API source we'll use for our queries.
apiSource WebAPIFeeSource
// updateFeeTicker is the ticker responsible for updating the Estimator's
// fee estimates every time it fires.
updateFeeTicker *time.Ticker
// feeByBlockTarget is our cache for fees pulled from the API. When a
// fee estimate request comes in, we pull the estimate from this array
// rather than re-querying the API, to prevent an inadvertent DoS attack.
feesMtx sync.Mutex
feeByBlockTarget map[uint32]uint32
minRelayFeerate SatPerKVByte
// noCache determines whether the web estimator should cache fee
// estimates.
noCache bool
// minFeeUpdateTimeout represents the minimum interval in which the
// web estimator will request fresh fees from its API.
minFeeUpdateTimeout time.Duration
// minFeeUpdateTimeout represents the maximum interval in which the
// web estimator will request fresh fees from its API.
maxFeeUpdateTimeout time.Duration
quit chan struct{}
wg sync.WaitGroup
}
// NewWebAPIEstimator creates a new WebAPIEstimator from a given URL and a
// fallback default fee. The fees are updated whenever a new block is mined.
func NewWebAPIEstimator(api WebAPIFeeSource, noCache bool,
minFeeUpdateTimeout time.Duration,
maxFeeUpdateTimeout time.Duration) (*WebAPIEstimator, error) {
if minFeeUpdateTimeout == 0 || maxFeeUpdateTimeout == 0 {
return nil, fmt.Errorf("minFeeUpdateTimeout and " +
"maxFeeUpdateTimeout must be greater than 0")
}
if minFeeUpdateTimeout >= maxFeeUpdateTimeout {
return nil, fmt.Errorf("minFeeUpdateTimeout target of %v "+
"cannot be greater than maxFeeUpdateTimeout of %v",
minFeeUpdateTimeout, maxFeeUpdateTimeout)
}
return &WebAPIEstimator{
apiSource: api,
feeByBlockTarget: make(map[uint32]uint32),
noCache: noCache,
quit: make(chan struct{}),
minFeeUpdateTimeout: minFeeUpdateTimeout,
maxFeeUpdateTimeout: maxFeeUpdateTimeout,
}, nil
}
// EstimateFeePerKW takes in a target for the number of blocks until an initial
// confirmation and returns the estimated fee expressed in sat/kw.
//
// NOTE: This method is part of the Estimator interface.
func (w *WebAPIEstimator) EstimateFeePerKW(numBlocks uint32) (
SatPerKWeight, error) {
// If the estimator hasn't been started yet, we'll return an error as
// we can't provide a fee estimate.
if !w.started.Load() {
return 0, fmt.Errorf("estimator not started")
}
if numBlocks > MaxBlockTarget {
numBlocks = MaxBlockTarget
} else if numBlocks < minBlockTarget {
return 0, fmt.Errorf("conf target of %v is too low, minimum "+
"accepted is %v", numBlocks, minBlockTarget)
}
// Get fee estimates now if we don't refresh periodically.
if w.noCache {
w.updateFeeEstimates()
}
feePerKb, err := w.getCachedFee(numBlocks)
// If the estimator returns an error, a zero value fee rate will be
// returned. We will log the error and return the fall back fee rate
// instead.
if err != nil {
log.Errorf("Unable to query estimator: %v", err)
}
// If the result is too low, then we'll clamp it to our current fee
// floor.
satPerKw := SatPerKVByte(feePerKb).FeePerKWeight()
if satPerKw < FeePerKwFloor {
satPerKw = FeePerKwFloor
}
log.Debugf("Web API returning %v sat/kw for conf target of %v",
int64(satPerKw), numBlocks)
return satPerKw, nil
}
// Start signals the Estimator to start any processes or goroutines it needs
// to perform its duty.
//
// NOTE: This method is part of the Estimator interface.
func (w *WebAPIEstimator) Start() error {
log.Infof("Starting Web API fee estimator...")
// Return an error if it's already been started.
if w.started.Load() {
return fmt.Errorf("web API fee estimator already started")
}
defer w.started.Store(true)
// During startup we'll query the API to initialize the fee map.
w.updateFeeEstimates()
// No update loop is needed when we don't cache.
if w.noCache {
return nil
}
feeUpdateTimeout := w.randomFeeUpdateTimeout()
log.Infof("Web API fee estimator using update timeout of %v",
feeUpdateTimeout)
w.updateFeeTicker = time.NewTicker(feeUpdateTimeout)
w.wg.Add(1)
go w.feeUpdateManager()
return nil
}
// Stop stops any spawned goroutines and cleans up the resources used by the
// fee estimator.
//
// NOTE: This method is part of the Estimator interface.
func (w *WebAPIEstimator) Stop() error {
log.Infof("Stopping web API fee estimator")
if w.stopped.Swap(true) {
return fmt.Errorf("web API fee estimator already stopped")
}
// Update loop is not running when we don't cache.
if w.noCache {
return nil
}
if w.updateFeeTicker != nil {
w.updateFeeTicker.Stop()
}
close(w.quit)
w.wg.Wait()
return nil
}
// RelayFeePerKW returns the minimum fee rate required for transactions to be
// relayed.
//
// NOTE: This method is part of the Estimator interface.
func (w *WebAPIEstimator) RelayFeePerKW() SatPerKWeight {
if !w.started.Load() {
log.Error("WebAPIEstimator not started")
}
// Get fee estimates now if we don't refresh periodically.
if w.noCache {
w.updateFeeEstimates()
}
log.Infof("Web API returning %v for min relay feerate",
w.minRelayFeerate)
return w.minRelayFeerate.FeePerKWeight()
}
// randomFeeUpdateTimeout returns a random timeout between minFeeUpdateTimeout
// and maxFeeUpdateTimeout that will be used to determine how often the Estimator
// should retrieve fresh fees from its API.
func (w *WebAPIEstimator) randomFeeUpdateTimeout() time.Duration {
lower := int64(w.minFeeUpdateTimeout)
upper := int64(w.maxFeeUpdateTimeout)
return time.Duration(
prand.Int63n(upper-lower) + lower, //nolint:gosec
).Round(time.Second)
}
// getCachedFee takes a conf target and returns the cached fee rate. When the
// fee rate cannot be found, it will search the cache by decrementing the conf
// target until a fee rate is found. If still not found, it will return the fee
// rate of the minimum conf target cached, in other words, the most expensive
// fee rate it knows of.
func (w *WebAPIEstimator) getCachedFee(numBlocks uint32) (uint32, error) {
w.feesMtx.Lock()
defer w.feesMtx.Unlock()
// If the cache is empty, return an error.
if len(w.feeByBlockTarget) == 0 {
return 0, fmt.Errorf("web API error: %w", errEmptyCache)
}
// Search the conf target from the cache. We expect a query to the web
// API has been made and the result has been cached at this point.
fee, ok := w.feeByBlockTarget[numBlocks]
// If the conf target can be found, exit early.
if ok {
return fee, nil
}
// The conf target cannot be found. We will first search the cache
// using a lower conf target. This is a conservative approach as the
// fee rate returned will be larger than what's requested.
for target := numBlocks; target >= minBlockTarget; target-- {
fee, ok := w.feeByBlockTarget[target]
if !ok {
continue
}
log.Warnf("Web API does not have a fee rate for target=%d, "+
"using the fee rate for target=%d instead",
numBlocks, target)
// Return the fee rate found, which will be more expensive than
// requested. We will not cache the fee rate here in the hope
// that the web API will later populate this value.
return fee, nil
}
// There are no lower conf targets cached, which is likely when the
// requested conf target is 1. We will search the cache using a higher
// conf target, which gives a fee rate that's cheaper than requested.
//
// NOTE: we can only get here iff the requested conf target is smaller
// than the minimum conf target cached, so we return the minimum conf
// target from the cache.
minTargetCached := uint32(math.MaxUint32)
for target := range w.feeByBlockTarget {
if target < minTargetCached {
minTargetCached = target
}
}
fee, ok = w.feeByBlockTarget[minTargetCached]
if !ok {
// We should never get here, just a vanity check.
return 0, fmt.Errorf("web API error: %w, conf target: %d",
errNoFeeRateFound, numBlocks)
}
// Log an error instead of a warning as a cheaper fee rate may delay
// the confirmation for some important transactions.
log.Errorf("Web API does not have a fee rate for target=%d, "+
"using the fee rate for target=%d instead",
numBlocks, minTargetCached)
return fee, nil
}
// updateFeeEstimates re-queries the API for fresh fees and caches them.
func (w *WebAPIEstimator) updateFeeEstimates() {
// Once we've obtained the response, we'll instruct the WebAPIFeeSource
// to parse out the body to obtain our final result.
resp, err := w.apiSource.GetFeeInfo()
if err != nil {
log.Errorf("unable to get fee response: %v", err)
return
}
log.Debugf("Received response from source: %s", lnutils.NewLogClosure(
func() string {
resp, _ := json.Marshal(resp)
return string(resp)
}))
w.feesMtx.Lock()
w.feeByBlockTarget = resp.FeeByBlockTarget
w.minRelayFeerate = resp.MinRelayFeerate
w.feesMtx.Unlock()
}
// feeUpdateManager updates the fee estimates whenever a new block comes in.
func (w *WebAPIEstimator) feeUpdateManager() {
defer w.wg.Done()
for {
select {
case <-w.updateFeeTicker.C:
w.updateFeeEstimates()
case <-w.quit:
return
}
}
}
// A compile-time assertion to ensure that WebAPIEstimator implements the
// Estimator interface.
var _ Estimator = (*WebAPIEstimator)(nil)
package chainfee
import (
"encoding/json"
"fmt"
"sort"
"sync"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/rpcclient"
"github.com/lightningnetwork/lnd/fn/v2"
)
const (
// fetchFilterInterval is the interval between successive fetches of
// our peers' feefilters.
fetchFilterInterval = time.Minute * 5
// minNumFilters is the minimum number of feefilters we need during a
// polling interval. If we have fewer than this, we won't consider the
// data.
minNumFilters = 6
)
var (
// errNoData is an error that's returned if fetchMedianFilter is
// called and there is no data available.
errNoData = fmt.Errorf("no data available")
)
// filterManager is responsible for determining an acceptable minimum fee to
// use based on our peers' feefilter values.
type filterManager struct {
// median stores the median of our outbound peer's feefilter values.
median fn.Option[SatPerKWeight]
medianMtx sync.RWMutex
fetchFunc func() ([]SatPerKWeight, error)
wg sync.WaitGroup
quit chan struct{}
}
// newFilterManager constructs a filterManager with a callback that fetches the
// set of peers' feefilters.
func newFilterManager(cb func() ([]SatPerKWeight, error)) *filterManager {
f := &filterManager{
quit: make(chan struct{}),
}
f.fetchFunc = cb
return f
}
// Start starts the filterManager.
func (f *filterManager) Start() {
f.wg.Add(1)
go f.fetchPeerFilters()
}
// Stop stops the filterManager.
func (f *filterManager) Stop() {
close(f.quit)
f.wg.Wait()
}
// fetchPeerFilters fetches our peers' feefilter values and calculates the
// median.
func (f *filterManager) fetchPeerFilters() {
defer f.wg.Done()
filterTicker := time.NewTicker(fetchFilterInterval)
defer filterTicker.Stop()
for {
select {
case <-filterTicker.C:
filters, err := f.fetchFunc()
if err != nil {
log.Errorf("Encountered err while fetching "+
"fee filters: %v", err)
return
}
f.updateMedian(filters)
case <-f.quit:
return
}
}
}
// fetchMedianFilter fetches the median feefilter value.
func (f *filterManager) FetchMedianFilter() (SatPerKWeight, error) {
f.medianMtx.RLock()
defer f.medianMtx.RUnlock()
// If there is no median, return errNoData so the caller knows to
// ignore the output and continue.
return f.median.UnwrapOrErr(errNoData)
}
type bitcoindPeerInfoResp struct {
Inbound bool `json:"inbound"`
MinFeeFilter float64 `json:"minfeefilter"`
}
func fetchBitcoindFilters(client *rpcclient.Client) ([]SatPerKWeight, error) {
resp, err := client.RawRequest("getpeerinfo", nil)
if err != nil {
return nil, err
}
var peerResps []bitcoindPeerInfoResp
err = json.Unmarshal(resp, &peerResps)
if err != nil {
return nil, err
}
// We filter for outbound peers since it is harder for an attacker to
// be our outbound peer and therefore harder for them to manipulate us
// into broadcasting high-fee or low-fee transactions.
var outboundPeerFilters []SatPerKWeight
for _, peerResp := range peerResps {
if peerResp.Inbound {
continue
}
// The value that bitcoind returns for the "minfeefilter" field
// is in fractions of a bitcoin that represents the satoshis
// per KvB. We need to convert this fraction to whole satoshis
// by multiplying with COIN. Then we need to convert the
// sats/KvB to sats/KW.
//
// Convert the sats/KvB from fractions of a bitcoin to whole
// satoshis.
filterKVByte := SatPerKVByte(
peerResp.MinFeeFilter * btcutil.SatoshiPerBitcoin,
)
if !isWithinBounds(filterKVByte) {
continue
}
// Convert KvB to KW and add it to outboundPeerFilters.
outboundPeerFilters = append(
outboundPeerFilters, filterKVByte.FeePerKWeight(),
)
}
// Check that we have enough data to use. We don't return an error as
// that would stop the querying goroutine.
if len(outboundPeerFilters) < minNumFilters {
return nil, nil
}
return outboundPeerFilters, nil
}
func fetchBtcdFilters(client *rpcclient.Client) ([]SatPerKWeight, error) {
resp, err := client.GetPeerInfo()
if err != nil {
return nil, err
}
var outboundPeerFilters []SatPerKWeight
for _, peerResp := range resp {
// We filter for outbound peers since it is harder for an
// attacker to be our outbound peer and therefore harder for
// them to manipulate us into broadcasting high-fee or low-fee
// transactions.
if peerResp.Inbound {
continue
}
// The feefilter is already in units of sat/KvB.
filter := SatPerKVByte(peerResp.FeeFilter)
if !isWithinBounds(filter) {
continue
}
outboundPeerFilters = append(
outboundPeerFilters, filter.FeePerKWeight(),
)
}
// Check that we have enough data to use. We don't return an error as
// that would stop the querying goroutine.
if len(outboundPeerFilters) < minNumFilters {
return nil, nil
}
return outboundPeerFilters, nil
}
// updateMedian takes a slice of feefilter values, computes the median, and
// updates our stored median value.
func (f *filterManager) updateMedian(feeFilters []SatPerKWeight) {
// If there are no elements, don't update.
numElements := len(feeFilters)
if numElements == 0 {
return
}
f.medianMtx.Lock()
defer f.medianMtx.Unlock()
// Log the new median.
median := med(feeFilters)
f.median = fn.Some(median)
log.Debugf("filterManager updated moving median to: %v",
median.FeePerKVByte())
}
// isWithinBounds returns false if the filter is unusable and true if it is.
func isWithinBounds(filter SatPerKVByte) bool {
// Ignore values of 0 and MaxSatoshi. A value of 0 likely means that
// the peer hasn't sent over a feefilter and a value of MaxSatoshi
// means the peer is using bitcoind and is in IBD.
switch filter {
case 0:
return false
case btcutil.MaxSatoshi:
return false
}
return true
}
// med calculates the median of a slice.
// NOTE: Passing in an empty slice will panic!
func med(f []SatPerKWeight) SatPerKWeight {
// Copy the original slice so that sorting doesn't modify the original.
fCopy := make([]SatPerKWeight, len(f))
copy(fCopy, f)
sort.Slice(fCopy, func(i, j int) bool {
return fCopy[i] < fCopy[j]
})
var median SatPerKWeight
numElements := len(fCopy)
switch numElements % 2 {
case 0:
// There's an even number of elements, so we need to average.
middle := numElements / 2
upper := fCopy[middle]
lower := fCopy[middle-1]
median = (upper + lower) / 2
case 1:
median = fCopy[numElements/2]
}
return median
}
package chainfee
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This means the
// package will not perform any logging by default until the caller requests
// it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("CFEE", nil))
}
// DisableLog disables all library log output. Logging output is disabled by
// default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info. This
// should be used in preference to SetLogWriter if the caller is also using
// btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package chainfee
import (
"sync"
"time"
)
const defaultUpdateInterval = 10 * time.Minute
// minFeeManager is used to store and update the minimum fee that is required
// by a transaction to be accepted to the mempool. The minFeeManager ensures
// that the backend used to fetch the fee is not queried too regularly.
type minFeeManager struct {
mu sync.Mutex
minFeePerKW SatPerKWeight
lastUpdatedTime time.Time
minUpdateInterval time.Duration
fetchFeeFunc fetchFee
}
// fetchFee represents a function that can be used to fetch a fee.
type fetchFee func() (SatPerKWeight, error)
// newMinFeeManager creates a new minFeeManager and uses the
// given fetchMinFee function to set the minFeePerKW of the minFeeManager.
// This function requires the fetchMinFee function to succeed.
func newMinFeeManager(minUpdateInterval time.Duration,
fetchMinFee fetchFee) (*minFeeManager, error) {
minFee, err := fetchMinFee()
if err != nil {
return nil, err
}
// Ensure that the minimum fee we use is always clamped by our fee
// floor.
if minFee < FeePerKwFloor {
minFee = FeePerKwFloor
}
return &minFeeManager{
minFeePerKW: minFee,
lastUpdatedTime: time.Now(),
minUpdateInterval: minUpdateInterval,
fetchFeeFunc: fetchMinFee,
}, nil
}
// fetchMinFee returns the stored minFeePerKW if it has been updated recently
// or if the call to the chain backend fails. Otherwise, it sets the stored
// minFeePerKW to the fee returned from the backend and floors it based on
// our fee floor.
func (m *minFeeManager) fetchMinFee() SatPerKWeight {
m.mu.Lock()
defer m.mu.Unlock()
if time.Since(m.lastUpdatedTime) < m.minUpdateInterval {
return m.minFeePerKW
}
newMinFee, err := m.fetchFeeFunc()
if err != nil {
log.Errorf("Unable to fetch updated min fee from chain "+
"backend. Using last known min fee instead: %v", err)
return m.minFeePerKW
}
// By default, we'll use the backend node's minimum fee as the
// minimum fee rate we'll propose for transactions. However, if this
// happens to be lower than our fee floor, we'll enforce that instead.
m.minFeePerKW = newMinFee
if m.minFeePerKW < FeePerKwFloor {
m.minFeePerKW = FeePerKwFloor
}
m.lastUpdatedTime = time.Now()
log.Debugf("Using minimum fee rate of %v sat/kw",
int64(m.minFeePerKW))
return m.minFeePerKW
}
package chainfee
import (
"github.com/stretchr/testify/mock"
)
type mockFeeSource struct {
mock.Mock
}
// A compile-time assertion to ensure that mockFeeSource implements the
// WebAPIFeeSource interface.
var _ WebAPIFeeSource = (*mockFeeSource)(nil)
func (m *mockFeeSource) GetFeeInfo() (WebAPIResponse, error) {
args := m.Called()
return args.Get(0).(WebAPIResponse), args.Error(1)
}
// MockEstimator implements the `Estimator` interface and is used by
// other packages for mock testing.
type MockEstimator struct {
mock.Mock
}
// Compile time assertion that MockEstimator implements Estimator.
var _ Estimator = (*MockEstimator)(nil)
// EstimateFeePerKW takes in a target for the number of blocks until an initial
// confirmation and returns the estimated fee expressed in sat/kw.
func (m *MockEstimator) EstimateFeePerKW(
numBlocks uint32) (SatPerKWeight, error) {
args := m.Called(numBlocks)
if args.Get(0) == nil {
return 0, args.Error(1)
}
return args.Get(0).(SatPerKWeight), args.Error(1)
}
// Start signals the Estimator to start any processes or goroutines it needs to
// perform its duty.
func (m *MockEstimator) Start() error {
args := m.Called()
return args.Error(0)
}
// Stop stops any spawned goroutines and cleans up the resources used by the
// fee estimator.
func (m *MockEstimator) Stop() error {
args := m.Called()
return args.Error(0)
}
// RelayFeePerKW returns the minimum fee rate required for transactions to be
// relayed. This is also the basis for calculation of the dust limit.
func (m *MockEstimator) RelayFeePerKW() SatPerKWeight {
args := m.Called()
return args.Get(0).(SatPerKWeight)
}
package chainfee
import (
"fmt"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/lntypes"
)
const (
// FeePerKwFloor is the lowest fee rate in sat/kw that we should use for
// estimating transaction fees before signing.
FeePerKwFloor SatPerKWeight = 253
// AbsoluteFeePerKwFloor is the lowest fee rate in sat/kw of a
// transaction that we should ever _create_. This is the equivalent
// of 1 sat/byte in sat/kw.
AbsoluteFeePerKwFloor SatPerKWeight = 250
)
// SatPerVByte represents a fee rate in sat/vbyte.
type SatPerVByte btcutil.Amount
// FeePerKWeight converts the current fee rate from sat/vb to sat/kw.
func (s SatPerVByte) FeePerKWeight() SatPerKWeight {
return SatPerKWeight(s * 1000 / blockchain.WitnessScaleFactor)
}
// FeePerKVByte converts the current fee rate from sat/vb to sat/kvb.
func (s SatPerVByte) FeePerKVByte() SatPerKVByte {
return SatPerKVByte(s * 1000)
}
// String returns a human-readable string of the fee rate.
func (s SatPerVByte) String() string {
return fmt.Sprintf("%v sat/vb", int64(s))
}
// SatPerKVByte represents a fee rate in sat/kb.
type SatPerKVByte btcutil.Amount
// FeeForVSize calculates the fee resulting from this fee rate and the given
// vsize in vbytes.
func (s SatPerKVByte) FeeForVSize(vbytes lntypes.VByte) btcutil.Amount {
return btcutil.Amount(s) * btcutil.Amount(vbytes) / 1000
}
// FeePerKWeight converts the current fee rate from sat/kb to sat/kw.
func (s SatPerKVByte) FeePerKWeight() SatPerKWeight {
return SatPerKWeight(s / blockchain.WitnessScaleFactor)
}
// String returns a human-readable string of the fee rate.
func (s SatPerKVByte) String() string {
return fmt.Sprintf("%v sat/kvb", int64(s))
}
// SatPerKWeight represents a fee rate in sat/kw.
type SatPerKWeight btcutil.Amount
// NewSatPerKWeight creates a new fee rate in sat/kw.
func NewSatPerKWeight(fee btcutil.Amount, wu lntypes.WeightUnit) SatPerKWeight {
return SatPerKWeight(fee.MulF64(1000 / float64(wu)))
}
// FeeForWeight calculates the fee resulting from this fee rate and the given
// weight in weight units (wu).
func (s SatPerKWeight) FeeForWeight(wu lntypes.WeightUnit) btcutil.Amount {
// The resulting fee is rounded down, as specified in BOLT#03.
return btcutil.Amount(s) * btcutil.Amount(wu) / 1000
}
// FeeForVByte calculates the fee resulting from this fee rate and the given
// size in vbytes (vb).
func (s SatPerKWeight) FeeForVByte(vb lntypes.VByte) btcutil.Amount {
return s.FeePerKVByte().FeeForVSize(vb)
}
// FeePerKVByte converts the current fee rate from sat/kw to sat/kb.
func (s SatPerKWeight) FeePerKVByte() SatPerKVByte {
return SatPerKVByte(s * blockchain.WitnessScaleFactor)
}
// FeePerVByte converts the current fee rate from sat/kw to sat/vb.
func (s SatPerKWeight) FeePerVByte() SatPerVByte {
return SatPerVByte(s * blockchain.WitnessScaleFactor / 1000)
}
// String returns a human-readable string of the fee rate.
func (s SatPerKWeight) String() string {
return fmt.Sprintf("%v sat/kw", int64(s))
}
package chancloser
import (
"bytes"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// ErrChanAlreadyClosing is returned when a channel shutdown is
// attempted more than once.
ErrChanAlreadyClosing = fmt.Errorf("channel shutdown already initiated")
// ErrChanCloseNotFinished is returned when a caller attempts to access
// a field or function that is contingent on the channel closure
// negotiation already being completed.
ErrChanCloseNotFinished = fmt.Errorf("close negotiation not finished")
// ErrInvalidState is returned when the closing state machine receives a
// message while it is in an unknown state.
ErrInvalidState = fmt.Errorf("invalid state")
// ErrUpfrontShutdownScriptMismatch is returned when a peer or end user
// provides a cooperative close script which does not match the upfront
// shutdown script previously set for that party.
ErrUpfrontShutdownScriptMismatch = fmt.Errorf("shutdown script does not " +
"match upfront shutdown script")
// ErrProposalExceedsMaxFee is returned when as the initiator, the
// latest fee proposal sent by the responder exceed our max fee.
// responder.
ErrProposalExceedsMaxFee = fmt.Errorf("latest fee proposal exceeds " +
"max fee")
// ErrInvalidShutdownScript is returned when we receive an address from
// a peer that isn't either a p2wsh or p2tr address.
ErrInvalidShutdownScript = fmt.Errorf("invalid shutdown script")
// errNoShutdownNonce is returned when a shutdown message is received
// w/o a nonce for a taproot channel.
errNoShutdownNonce = fmt.Errorf("shutdown nonce not populated")
)
// closeState represents all the possible states the channel closer state
// machine can be in. Each message will either advance to the next state, or
// remain at the current state. Once the state machine reaches a state of
// closeFinished, then negotiation is over.
type closeState uint8
const (
// closeIdle is the initial starting state. In this state, the state
// machine has been instantiated, but no state transitions have been
// attempted. If a state machine receives a message while in this state,
// then it is the responder to an initiated cooperative channel closure.
closeIdle closeState = iota
// closeShutdownInitiated is the state that's transitioned to once the
// initiator of a closing workflow sends the shutdown message. At this
// point, they're waiting for the remote party to respond with their own
// shutdown message. After which, they'll both enter the fee negotiation
// phase.
closeShutdownInitiated
// closeAwaitingFlush is the state that's transitioned to once both
// Shutdown messages have been exchanged but we are waiting for the
// HTLCs to clear out of the channel.
closeAwaitingFlush
// closeFeeNegotiation is the third, and most persistent state. Both
// parties enter this state after they've sent and received a shutdown
// message. During this phase, both sides will send monotonically
// increasing fee requests until one side accepts the last fee rate
// offered by the other party. In this case, the party will broadcast
// the closing transaction, and send the accepted fee to the remote
// party. This then causes a shift into the closeFinished state.
closeFeeNegotiation
// closeFinished is the final state of the state machine. In this state,
// a side has accepted a fee offer and has broadcast the valid closing
// transaction to the network. During this phase, the closing
// transaction becomes available for examination.
closeFinished
)
const (
// defaultMaxFeeMultiplier is a multiplier we'll apply to the ideal fee
// of the initiator, to decide when the negotiated fee is too high. By
// default, we want to bail out if we attempt to negotiate a fee that's
// 3x higher than our max fee.
defaultMaxFeeMultiplier = 3
)
// DeliveryAddrWithKey wraps a normal delivery addr, but also includes the
// internal key for the delivery addr if known.
type DeliveryAddrWithKey struct {
// DeliveryAddress is the raw, serialized pkScript of the delivery
// address.
lnwire.DeliveryAddress
// InternalKey is the Taproot internal key of the delivery address, if
// the address is a P2TR output.
InternalKey fn.Option[btcec.PublicKey]
}
// ChanCloseCfg holds all the items that a ChanCloser requires to carry out its
// duties.
type ChanCloseCfg struct {
// Channel is the channel that should be closed.
Channel Channel
// MusigSession is used to handle generating musig2 nonces, and also
// creating the proper set of closing options for taproot channels.
MusigSession MusigSession
// BroadcastTx broadcasts the passed transaction to the network.
BroadcastTx func(*wire.MsgTx, string) error
// DisableChannel disables a channel, resulting in it not being able to
// forward payments.
DisableChannel func(wire.OutPoint) error
// Disconnect will disconnect from the remote peer in this close.
Disconnect func() error
// MaxFee, is non-zero represents the highest fee that the initiator is
// willing to pay to close the channel.
MaxFee chainfee.SatPerKWeight
// ChainParams holds the parameters of the chain that we're active on.
ChainParams *chaincfg.Params
// Quit is a channel that should be sent upon in the occasion the state
// machine should cease all progress and shutdown.
Quit chan struct{}
// FeeEstimator is used to estimate the absolute starting co-op close
// fee.
FeeEstimator CoopFeeEstimator
// AuxCloser is an optional interface that can be used to modify the
// way the co-op close process proceeds.
AuxCloser fn.Option[AuxChanCloser]
}
// ChanCloser is a state machine that handles the cooperative channel closure
// procedure. This includes shutting down a channel, marking it ineligible for
// routing HTLC's, negotiating fees with the remote party, and finally
// broadcasting the fully signed closure transaction to the network.
type ChanCloser struct {
// state is the current state of the state machine.
state closeState
// cfg holds the configuration for this ChanCloser instance.
cfg ChanCloseCfg
// chanPoint is the full channel point of the target channel.
chanPoint wire.OutPoint
// cid is the full channel ID of the target channel.
cid lnwire.ChannelID
// negotiationHeight is the height that the fee negotiation begun at.
negotiationHeight uint32
// closingTx is the final, fully signed closing transaction. This will
// only be populated once the state machine shifts to the closeFinished
// state.
closingTx *wire.MsgTx
// idealFeeSat is the ideal fee that the state machine should initially
// offer when starting negotiation. This will be used as a baseline.
idealFeeSat btcutil.Amount
// maxFee is the highest fee the initiator is willing to pay to close
// out the channel. This is either a use specified value, or a default
// multiplier based of the initial starting ideal fee.
maxFee btcutil.Amount
// idealFeeRate is our ideal fee rate.
idealFeeRate chainfee.SatPerKWeight
// lastFeeProposal is the last fee that we proposed to the remote party.
// We'll use this as a pivot point to ratchet our next offer up, down,
// or simply accept the remote party's prior offer.
lastFeeProposal btcutil.Amount
// priorFeeOffers is a map that keeps track of all the proposed fees
// that we've offered during the fee negotiation. We use this map to cut
// the negotiation early if the remote party ever sends an offer that
// we've sent in the past. Once negotiation terminates, we can extract
// the prior signature of our accepted offer from this map.
//
// TODO(roasbeef): need to ensure if they broadcast w/ any of our prior
// sigs, we are aware of
priorFeeOffers map[btcutil.Amount]*lnwire.ClosingSigned
// closeReq is the initial closing request. This will only be populated
// if we're the initiator of this closing negotiation.
//
// TODO(roasbeef): abstract away
closeReq *htlcswitch.ChanClose
// localDeliveryScript is the script that we'll send our settled channel
// funds to.
localDeliveryScript []byte
// localInternalKey is the local delivery address Taproot internal key,
// if the local delivery script is a P2TR output.
localInternalKey fn.Option[btcec.PublicKey]
// remoteDeliveryScript is the script that we'll send the remote party's
// settled channel funds to.
remoteDeliveryScript []byte
// closer is ChannelParty who initiated the coop close
closer lntypes.ChannelParty
// cachedClosingSigned is a cached copy of a received ClosingSigned that
// we use to handle a specific race condition caused by the independent
// message processing queues.
cachedClosingSigned fn.Option[lnwire.ClosingSigned]
// localCloseOutput is the local output on the closing transaction that
// the local party should be paid to. This will only be populated if the
// local balance isn't dust.
localCloseOutput fn.Option[CloseOutput]
// remoteCloseOutput is the remote output on the closing transaction
// that the remote party should be paid to. This will only be populated
// if the remote balance isn't dust.
remoteCloseOutput fn.Option[CloseOutput]
// auxOutputs are the optional additional outputs that might be added to
// the closing transaction.
auxOutputs fn.Option[AuxCloseOutputs]
}
// calcCoopCloseFee computes an "ideal" absolute co-op close fee given the
// delivery scripts of both parties and our ideal fee rate.
func calcCoopCloseFee(chanType channeldb.ChannelType,
localOutput, remoteOutput *wire.TxOut,
idealFeeRate chainfee.SatPerKWeight) btcutil.Amount {
var weightEstimator input.TxWeightEstimator
if chanType.IsTaproot() {
weightEstimator.AddWitnessInput(
input.TaprootSignatureWitnessSize,
)
} else {
weightEstimator.AddWitnessInput(input.MultiSigWitnessSize)
}
// One of these outputs might be dust, so we'll skip adding it to our
// mock transaction, so the fees are more accurate.
if localOutput != nil {
weightEstimator.AddTxOutput(localOutput)
}
if remoteOutput != nil {
weightEstimator.AddTxOutput(remoteOutput)
}
totalWeight := weightEstimator.Weight()
return idealFeeRate.FeeForWeight(totalWeight)
}
// SimpleCoopFeeEstimator is the default co-op close fee estimator. It assumes
// a normal segwit v0 channel, and that no outputs on the closing transaction
// are dust.
type SimpleCoopFeeEstimator struct {
}
// EstimateFee estimates an _absolute_ fee for a co-op close transaction given
// the local+remote tx outs (for the co-op close transaction), channel type,
// and ideal fee rate.
func (d *SimpleCoopFeeEstimator) EstimateFee(chanType channeldb.ChannelType,
localTxOut, remoteTxOut *wire.TxOut,
idealFeeRate chainfee.SatPerKWeight) btcutil.Amount {
return calcCoopCloseFee(chanType, localTxOut, remoteTxOut, idealFeeRate)
}
// NewChanCloser creates a new instance of the channel closure given the passed
// configuration, and delivery+fee preference. The final argument should only
// be populated iff, we're the initiator of this closing request.
func NewChanCloser(cfg ChanCloseCfg, deliveryScript DeliveryAddrWithKey,
idealFeePerKw chainfee.SatPerKWeight, negotiationHeight uint32,
closeReq *htlcswitch.ChanClose,
closer lntypes.ChannelParty) *ChanCloser {
chanPoint := cfg.Channel.ChannelPoint()
cid := lnwire.NewChanIDFromOutPoint(chanPoint)
return &ChanCloser{
closeReq: closeReq,
state: closeIdle,
chanPoint: chanPoint,
cid: cid,
cfg: cfg,
negotiationHeight: negotiationHeight,
idealFeeRate: idealFeePerKw,
localInternalKey: deliveryScript.InternalKey,
localDeliveryScript: deliveryScript.DeliveryAddress,
priorFeeOffers: make(
map[btcutil.Amount]*lnwire.ClosingSigned,
),
closer: closer,
}
}
// initFeeBaseline computes our ideal fee rate, and also the largest fee we'll
// accept given information about the delivery script of the remote party.
func (c *ChanCloser) initFeeBaseline() {
// Depending on if a balance ends up being dust or not, we'll pass a
// nil TxOut into the EstimateFee call which can handle it.
var localTxOut, remoteTxOut *wire.TxOut
if isDust, _ := c.cfg.Channel.LocalBalanceDust(); !isDust {
localTxOut = &wire.TxOut{
PkScript: c.localDeliveryScript,
Value: 0,
}
}
if isDust, _ := c.cfg.Channel.RemoteBalanceDust(); !isDust {
remoteTxOut = &wire.TxOut{
PkScript: c.remoteDeliveryScript,
Value: 0,
}
}
// Given the target fee-per-kw, we'll compute what our ideal _total_
// fee will be starting at for this fee negotiation.
c.idealFeeSat = c.cfg.FeeEstimator.EstimateFee(
0, localTxOut, remoteTxOut, c.idealFeeRate,
)
// When we're the initiator, we'll want to also factor in the highest
// fee we want to pay. This'll either be 3x the ideal fee, or the
// specified explicit max fee.
c.maxFee = c.idealFeeSat * defaultMaxFeeMultiplier
if c.cfg.MaxFee > 0 {
c.maxFee = c.cfg.FeeEstimator.EstimateFee(
0, localTxOut, remoteTxOut, c.cfg.MaxFee,
)
}
// TODO(ziggie): Make sure the ideal fee is not higher than the max fee.
// Either error out or cap the ideal fee at the max fee.
chancloserLog.Infof("Ideal fee for closure of ChannelPoint(%v) "+
"is: %v sat (max_fee=%v sat)", c.cfg.Channel.ChannelPoint(),
int64(c.idealFeeSat), int64(c.maxFee))
}
// initChanShutdown begins the shutdown process by un-registering the channel,
// and creating a valid shutdown message to our target delivery address.
func (c *ChanCloser) initChanShutdown() (*lnwire.Shutdown, error) {
// With both items constructed we'll now send the shutdown message for
// this particular channel, advertising a shutdown request to our
// desired closing script.
shutdown := lnwire.NewShutdown(c.cid, c.localDeliveryScript)
// At this point, we'll check to see if we have any custom records to
// add to the shutdown message.
err := fn.MapOptionZ(c.cfg.AuxCloser, func(a AuxChanCloser) error {
shutdownCustomRecords, err := a.ShutdownBlob(AuxShutdownReq{
ChanPoint: c.chanPoint,
ShortChanID: c.cfg.Channel.ShortChanID(),
Initiator: c.cfg.Channel.IsInitiator(),
InternalKey: c.localInternalKey,
CommitBlob: c.cfg.Channel.LocalCommitmentBlob(),
FundingBlob: c.cfg.Channel.FundingBlob(),
})
if err != nil {
return err
}
shutdownCustomRecords.WhenSome(func(cr lnwire.CustomRecords) {
shutdown.CustomRecords = cr
})
return nil
})
if err != nil {
return nil, err
}
// If this is a taproot channel, then we'll need to also generate a
// nonce that'll be used sign the co-op close transaction offer.
if c.cfg.Channel.ChanType().IsTaproot() {
firstClosingNonce, err := c.cfg.MusigSession.ClosingNonce()
if err != nil {
return nil, err
}
shutdown.ShutdownNonce = lnwire.SomeShutdownNonce(
firstClosingNonce.PubNonce,
)
chancloserLog.Infof("Initiating shutdown w/ nonce: %v",
spew.Sdump(firstClosingNonce.PubNonce))
}
// Before closing, we'll attempt to send a disable update for the
// channel. We do so before closing the channel as otherwise the
// current edge policy won't be retrievable from the graph.
if err := c.cfg.DisableChannel(c.chanPoint); err != nil {
chancloserLog.Warnf("Unable to disable channel %v on close: %v",
c.chanPoint, err)
}
chancloserLog.Infof("ChannelPoint(%v): sending shutdown message",
c.chanPoint)
// At this point, we persist any relevant info regarding the Shutdown
// message we are about to send in order to ensure that if a
// re-establish occurs then we will re-send the same Shutdown message.
shutdownInfo := channeldb.NewShutdownInfo(
c.localDeliveryScript, c.closer.IsLocal(),
)
err = c.cfg.Channel.MarkShutdownSent(shutdownInfo)
if err != nil {
return nil, err
}
// We'll track our local close output, even if it's dust in BTC terms,
// it might still carry value in custom channel terms.
_, dustAmt := c.cfg.Channel.LocalBalanceDust()
localBalance, _ := c.cfg.Channel.CommitBalances()
c.localCloseOutput = fn.Some(CloseOutput{
Amt: localBalance,
DustLimit: dustAmt,
PkScript: c.localDeliveryScript,
ShutdownRecords: shutdown.CustomRecords,
})
return shutdown, nil
}
// ShutdownChan is the first method that's to be called by the initiator of the
// cooperative channel closure. This message returns the shutdown message to
// send to the remote party. Upon completion, we enter the
// closeShutdownInitiated phase as we await a response.
func (c *ChanCloser) ShutdownChan() (*lnwire.Shutdown, error) {
// If we attempt to shutdown the channel for the first time, and we're not
// in the closeIdle state, then the caller made an error.
if c.state != closeIdle {
return nil, ErrChanAlreadyClosing
}
chancloserLog.Infof("ChannelPoint(%v): initiating shutdown", c.chanPoint)
shutdownMsg, err := c.initChanShutdown()
if err != nil {
return nil, err
}
// With the opening steps complete, we'll transition into the
// closeShutdownInitiated state. In this state, we'll wait until the
// other party sends their version of the shutdown message.
c.state = closeShutdownInitiated
// Finally, we'll return the shutdown message to the caller so it can
// send it to the remote peer.
return shutdownMsg, nil
}
// ClosingTx returns the fully signed, final closing transaction.
//
// NOTE: This transaction is only available if the state machine is in the
// closeFinished state.
func (c *ChanCloser) ClosingTx() (*wire.MsgTx, error) {
// If the state machine hasn't finished closing the channel, then we'll
// return an error as we haven't yet computed the closing tx.
if c.state != closeFinished {
return nil, ErrChanCloseNotFinished
}
return c.closingTx, nil
}
// CloseRequest returns the original close request that prompted the creation
// of the state machine.
//
// NOTE: This will only return a non-nil pointer if we were the initiator of
// the cooperative closure workflow.
func (c *ChanCloser) CloseRequest() *htlcswitch.ChanClose {
return c.closeReq
}
// Channel returns the channel stored in the config as a
// *lnwallet.LightningChannel.
//
// NOTE: This method will PANIC if the underlying channel implementation isn't
// the desired type.
func (c *ChanCloser) Channel() *lnwallet.LightningChannel {
// TODO(roasbeef): remove this
return c.cfg.Channel.(*lnwallet.LightningChannel)
}
// NegotiationHeight returns the negotiation height.
func (c *ChanCloser) NegotiationHeight() uint32 {
return c.negotiationHeight
}
// LocalCloseOutput returns the local close output.
func (c *ChanCloser) LocalCloseOutput() fn.Option[CloseOutput] {
return c.localCloseOutput
}
// RemoteCloseOutput returns the remote close output.
func (c *ChanCloser) RemoteCloseOutput() fn.Option[CloseOutput] {
return c.remoteCloseOutput
}
// AuxOutputs returns optional extra outputs.
func (c *ChanCloser) AuxOutputs() fn.Option[AuxCloseOutputs] {
return c.auxOutputs
}
// validateShutdownScript attempts to match and validate the script provided in
// our peer's shutdown message with the upfront shutdown script we have on
// record. For any script specified, we also make sure it matches our
// requirements. If no upfront shutdown script was set, we do not need to
// enforce option upfront shutdown, so the function returns early. If an
// upfront script is set, we check whether it matches the script provided by
// our peer. If they do not match, we use the disconnect function provided to
// disconnect from the peer.
func validateShutdownScript(upfrontScript, peerScript lnwire.DeliveryAddress,
netParams *chaincfg.Params) error {
// Either way, we'll make sure that the script passed meets our
// standards. The upfrontScript should have already been checked at an
// earlier stage, but we'll repeat the check here for defense in depth.
if len(upfrontScript) != 0 {
if !lnwallet.ValidateUpfrontShutdown(upfrontScript, netParams) {
return ErrInvalidShutdownScript
}
}
if len(peerScript) != 0 {
if !lnwallet.ValidateUpfrontShutdown(peerScript, netParams) {
return ErrInvalidShutdownScript
}
}
// If no upfront shutdown script was set, return early because we do
// not need to enforce closure to a specific script.
if len(upfrontScript) == 0 {
return nil
}
// If an upfront shutdown script was provided, disconnect from the peer, as
// per BOLT 2, and return an error.
if !bytes.Equal(upfrontScript, peerScript) {
chancloserLog.Warnf("peer's script: %x does not match upfront "+
"shutdown script: %x", peerScript, upfrontScript)
return ErrUpfrontShutdownScriptMismatch
}
return nil
}
// ReceiveShutdown takes a raw Shutdown message and uses it to try and advance
// the ChanCloser state machine, failing if it is coming in at an invalid time.
// If appropriate, it will also generate a Shutdown message of its own to send
// out to the peer. It is possible for this method to return None when no error
// occurred.
func (c *ChanCloser) ReceiveShutdown(msg lnwire.Shutdown) (
fn.Option[lnwire.Shutdown], error) {
noShutdown := fn.None[lnwire.Shutdown]()
// We'll track their remote close output, even if it's dust in BTC
// terms, it might still carry value in custom channel terms.
_, dustAmt := c.cfg.Channel.RemoteBalanceDust()
_, remoteBalance := c.cfg.Channel.CommitBalances()
c.remoteCloseOutput = fn.Some(CloseOutput{
Amt: remoteBalance,
DustLimit: dustAmt,
PkScript: msg.Address,
ShutdownRecords: msg.CustomRecords,
})
switch c.state {
// If we're in the close idle state, and we're receiving a channel
// closure related message, then this indicates that we're on the
// receiving side of an initiated channel closure.
case closeIdle:
// As we're the responder to this shutdown (the other party
// wants to close), we'll check if this is a frozen channel or
// not. If the channel is frozen and we were not also the
// initiator of the channel opening, then we'll deny their close
// attempt.
chanInitiator := c.cfg.Channel.IsInitiator()
if !chanInitiator {
absoluteThawHeight, err :=
c.cfg.Channel.AbsoluteThawHeight()
if err != nil {
return noShutdown, err
}
if c.negotiationHeight < absoluteThawHeight {
return noShutdown, fmt.Errorf("initiator "+
"attempting to co-op close frozen "+
"ChannelPoint(%v) (current_height=%v, "+
"thaw_height=%v)", c.chanPoint,
c.negotiationHeight, absoluteThawHeight)
}
}
// If the remote node opened the channel with option upfront
// shutdown script, check that the script they provided matches.
if err := validateShutdownScript(
c.cfg.Channel.RemoteUpfrontShutdownScript(),
msg.Address, c.cfg.ChainParams,
); err != nil {
return noShutdown, err
}
// Once we have checked that the other party has not violated
// option upfront shutdown we set their preference for delivery
// address. We'll use this when we craft the closure
// transaction.
c.remoteDeliveryScript = msg.Address
// We'll generate a shutdown message of our own to send across
// the wire.
localShutdown, err := c.initChanShutdown()
if err != nil {
return noShutdown, err
}
// If this is a taproot channel, then we'll want to stash the
// remote nonces so we can properly create a new musig
// session for signing.
if c.cfg.Channel.ChanType().IsTaproot() {
shutdownNonce, err := msg.ShutdownNonce.UnwrapOrErrV(
errNoShutdownNonce,
)
if err != nil {
return noShutdown, err
}
c.cfg.MusigSession.InitRemoteNonce(&musig2.Nonces{
PubNonce: shutdownNonce,
})
}
chancloserLog.Infof("ChannelPoint(%v): responding to shutdown",
c.chanPoint)
// After the other party receives this message, we'll actually
// start the final stage of the closure process: fee
// negotiation. So we'll update our internal state to reflect
// this, so we can handle the next message sent.
c.state = closeAwaitingFlush
return fn.Some(*localShutdown), err
case closeShutdownInitiated:
// If the remote node opened the channel with option upfront
// shutdown script, check that the script they provided matches.
if err := validateShutdownScript(
c.cfg.Channel.RemoteUpfrontShutdownScript(),
msg.Address, c.cfg.ChainParams,
); err != nil {
return noShutdown, err
}
// Now that we know this is a valid shutdown message and
// address, we'll record their preferred delivery closing
// script.
c.remoteDeliveryScript = msg.Address
// At this point, we can now start the fee negotiation state, by
// constructing and sending our initial signature for what we
// think the closing transaction should look like.
c.state = closeAwaitingFlush
// If this is a taproot channel, then we'll want to stash the
// local+remote nonces so we can properly create a new musig
// session for signing.
if c.cfg.Channel.ChanType().IsTaproot() {
shutdownNonce, err := msg.ShutdownNonce.UnwrapOrErrV(
errNoShutdownNonce,
)
if err != nil {
return noShutdown, err
}
c.cfg.MusigSession.InitRemoteNonce(&musig2.Nonces{
PubNonce: shutdownNonce,
})
}
chancloserLog.Infof("ChannelPoint(%v): shutdown response "+
"received, entering fee negotiation", c.chanPoint)
return noShutdown, nil
default:
// Otherwise we are not in a state where we can accept this
// message.
return noShutdown, ErrInvalidState
}
}
// BeginNegotiation should be called when we have definitively reached a clean
// channel state and are ready to cooperatively arrive at a closing transaction.
// If it is our responsibility to kick off the negotiation, this method will
// generate a ClosingSigned message. If it is the remote's responsibility, then
// it will not. In either case it will transition the ChanCloser state machine
// to the negotiation phase wherein ClosingSigned messages are exchanged until
// a mutually agreeable result is achieved.
func (c *ChanCloser) BeginNegotiation() (fn.Option[lnwire.ClosingSigned],
error) {
noClosingSigned := fn.None[lnwire.ClosingSigned]()
switch c.state {
case closeAwaitingFlush:
// Now that we know their desired delivery script, we can
// compute what our max/ideal fee will be.
c.initFeeBaseline()
// Before continuing, mark the channel as cooperatively closed
// with a nil txn. Even though we haven't negotiated the final
// txn, this guarantees that our listchannels rpc will be
// externally consistent, and reflect that the channel is being
// shutdown by the time the closing request returns.
err := c.cfg.Channel.MarkCoopBroadcasted(
nil, c.closer,
)
if err != nil {
return noClosingSigned, err
}
// At this point, we can now start the fee negotiation state, by
// constructing and sending our initial signature for what we
// think the closing transaction should look like.
c.state = closeFeeNegotiation
if !c.cfg.Channel.IsInitiator() {
// By default this means we do nothing, but we do want
// to check if we have a cached remote offer to process.
// If we do, we'll process it here.
res := noClosingSigned
err = nil
c.cachedClosingSigned.WhenSome(
func(cs lnwire.ClosingSigned) {
res, err = c.ReceiveClosingSigned(cs)
},
)
return res, err
}
// We'll craft our initial close proposal in order to keep the
// negotiation moving, but only if we're the initiator.
closingSigned, err := c.proposeCloseSigned(c.idealFeeSat)
if err != nil {
return noClosingSigned,
fmt.Errorf("unable to sign new co op "+
"close offer: %w", err)
}
return fn.Some(*closingSigned), nil
default:
return noClosingSigned, ErrInvalidState
}
}
// ReceiveClosingSigned is a method that should be called whenever we receive a
// ClosingSigned message from the wire. It may or may not return a
// ClosingSigned of our own to send back to the remote.
func (c *ChanCloser) ReceiveClosingSigned( //nolint:funlen
msg lnwire.ClosingSigned) (fn.Option[lnwire.ClosingSigned], error) {
noClosing := fn.None[lnwire.ClosingSigned]()
switch c.state {
case closeAwaitingFlush:
// If we hit this case it either means there's a protocol
// violation or that our chanCloser received the remote offer
// before the link finished processing the channel flush.
c.cachedClosingSigned = fn.Some(msg)
return fn.None[lnwire.ClosingSigned](), nil
case closeFeeNegotiation:
// If this is a taproot channel, then it MUST have a partial
// signature set at this point.
isTaproot := c.cfg.Channel.ChanType().IsTaproot()
if isTaproot && msg.PartialSig.IsNone() {
return noClosing,
fmt.Errorf("partial sig not set " +
"for taproot chan")
}
isInitiator := c.cfg.Channel.IsInitiator()
// We'll compare the proposed total fee, to what we've proposed
// during the negotiations. If it doesn't match any of our
// prior offers, then we'll attempt to ratchet the fee closer
// to our ideal fee.
remoteProposedFee := msg.FeeSatoshis
_, feeMatchesOffer := c.priorFeeOffers[remoteProposedFee]
switch {
// For taproot channels, since nonces are involved, we can't do
// the existing co-op close negotiation process without going
// to a fully round based model. Rather than do this, we'll
// just accept the very first offer by the initiator.
case isTaproot && !isInitiator:
chancloserLog.Infof("ChannelPoint(%v) accepting "+
"initiator fee of %v", c.chanPoint,
remoteProposedFee)
// To auto-accept the initiators proposal, we'll just
// send back a signature w/ the same offer. We don't
// send the message here, as we can drop down and
// finalize the closure and broadcast, then echo back
// to Alice the final signature.
_, err := c.proposeCloseSigned(remoteProposedFee)
if err != nil {
return noClosing, fmt.Errorf("unable to sign "+
"new co op close offer: %w", err)
}
// Otherwise, if we are the initiator, and we just sent a
// signature for a taproot channel, then we'll ensure that the
// fee rate matches up exactly.
case isTaproot && isInitiator && !feeMatchesOffer:
return noClosing,
fmt.Errorf("fee rate for "+
"taproot channels was not accepted: "+
"sent %v, got %v",
c.idealFeeSat, remoteProposedFee)
// If we're the initiator of the taproot channel, and we had
// our fee echo'd back, then it's all good, and we can proceed
// with final broadcast.
case isTaproot && isInitiator && feeMatchesOffer:
break
// Otherwise, if this is a normal segwit v0 channel, and the
// fee doesn't match our offer, then we'll try to "negotiate" a
// new fee.
case !feeMatchesOffer:
// We'll now attempt to ratchet towards a fee deemed
// acceptable by both parties, factoring in our ideal
// fee rate, and the last proposed fee by both sides.
proposal := calcCompromiseFee(
c.chanPoint, c.idealFeeSat, c.lastFeeProposal,
remoteProposedFee,
)
if c.cfg.Channel.IsInitiator() && proposal > c.maxFee {
return noClosing, fmt.Errorf(
"%w: %v > %v",
ErrProposalExceedsMaxFee,
proposal, c.maxFee)
}
// With our new fee proposal calculated, we'll craft a
// new close signed signature to send to the other
// party so we can continue the fee negotiation
// process.
closeSigned, err := c.proposeCloseSigned(proposal)
if err != nil {
return noClosing, fmt.Errorf("unable to sign "+
"new co op close offer: %w", err)
}
// If the compromise fee doesn't match what the peer
// proposed, then we'll return this latest close signed
// message so we can continue negotiation.
if proposal != remoteProposedFee {
chancloserLog.Debugf("ChannelPoint(%v): close "+
"tx fee disagreement, continuing "+
"negotiation", c.chanPoint)
return fn.Some(*closeSigned), nil
}
}
chancloserLog.Infof("ChannelPoint(%v) fee of %v accepted, "+
"ending negotiation", c.chanPoint, remoteProposedFee)
// Otherwise, we've agreed on a fee for the closing
// transaction! We'll craft the final closing transaction so we
// can broadcast it to the network.
var (
localSig, remoteSig input.Signature
closeOpts []lnwallet.ChanCloseOpt
err error
)
matchingSig := c.priorFeeOffers[remoteProposedFee]
if c.cfg.Channel.ChanType().IsTaproot() {
localWireSig, err := matchingSig.PartialSig.UnwrapOrErrV( //nolint:ll
fmt.Errorf("none local sig"),
)
if err != nil {
return noClosing, err
}
remoteWireSig, err := msg.PartialSig.UnwrapOrErrV(
fmt.Errorf("none remote sig"),
)
if err != nil {
return noClosing, err
}
muSession := c.cfg.MusigSession
localSig, remoteSig, closeOpts, err = muSession.CombineClosingOpts( //nolint:ll
localWireSig, remoteWireSig,
)
if err != nil {
return noClosing, err
}
} else {
localSig, err = matchingSig.Signature.ToSignature()
if err != nil {
return noClosing, err
}
remoteSig, err = msg.Signature.ToSignature()
if err != nil {
return noClosing, err
}
}
// Before we complete the cooperative close, we'll see if we
// have any extra aux options.
c.auxOutputs, err = c.auxCloseOutputs(remoteProposedFee)
if err != nil {
return noClosing, err
}
c.auxOutputs.WhenSome(func(outs AuxCloseOutputs) {
closeOpts = append(
closeOpts, lnwallet.WithExtraCloseOutputs(
outs.ExtraCloseOutputs,
),
)
closeOpts = append(
closeOpts, lnwallet.WithCustomCoopSort(
outs.CustomSort,
),
)
})
closeTx, _, err := c.cfg.Channel.CompleteCooperativeClose(
localSig, remoteSig, c.localDeliveryScript,
c.remoteDeliveryScript, remoteProposedFee, closeOpts...,
)
if err != nil {
return noClosing, err
}
c.closingTx = closeTx
// If there's an aux chan closer, then we'll finalize with it
// before we write to disk.
err = fn.MapOptionZ(
c.cfg.AuxCloser, func(aux AuxChanCloser) error {
channel := c.cfg.Channel
//nolint:ll
req := AuxShutdownReq{
ChanPoint: c.chanPoint,
ShortChanID: c.cfg.Channel.ShortChanID(),
InternalKey: c.localInternalKey,
Initiator: channel.IsInitiator(),
CommitBlob: channel.LocalCommitmentBlob(),
FundingBlob: channel.FundingBlob(),
}
desc := AuxCloseDesc{
AuxShutdownReq: req,
LocalCloseOutput: c.localCloseOutput,
RemoteCloseOutput: c.remoteCloseOutput,
}
return aux.FinalizeClose(desc, closeTx)
},
)
if err != nil {
return noClosing, err
}
// Before publishing the closing tx, we persist it to the
// database, such that it can be republished if something goes
// wrong.
err = c.cfg.Channel.MarkCoopBroadcasted(
closeTx, c.closer,
)
if err != nil {
return noClosing, err
}
// With the closing transaction crafted, we'll now broadcast it
// to the network.
chancloserLog.Infof("Broadcasting cooperative close tx: %v",
lnutils.SpewLogClosure(closeTx))
// Create a close channel label.
chanID := c.cfg.Channel.ShortChanID()
closeLabel := labels.MakeLabel(
labels.LabelTypeChannelClose, &chanID,
)
if err := c.cfg.BroadcastTx(closeTx, closeLabel); err != nil {
return noClosing, err
}
// Finally, we'll transition to the closeFinished state, and
// also return the final close signed message we sent.
// Additionally, we return true for the second argument to
// indicate we're finished with the channel closing
// negotiation.
c.state = closeFinished
matchingOffer := c.priorFeeOffers[remoteProposedFee]
return fn.Some(*matchingOffer), nil
// If we received a message while in the closeFinished state, then this
// should only be the remote party echoing the last ClosingSigned
// message that we agreed on.
case closeFinished:
// There's no more to do as both sides should have already
// broadcast the closing transaction at this state.
return noClosing, nil
default:
return noClosing, ErrInvalidState
}
}
// auxCloseOutputs returns any additional outputs that should be used when
// closing the channel.
func (c *ChanCloser) auxCloseOutputs(
closeFee btcutil.Amount) (fn.Option[AuxCloseOutputs], error) {
var closeOuts fn.Option[AuxCloseOutputs]
err := fn.MapOptionZ(c.cfg.AuxCloser, func(aux AuxChanCloser) error {
req := AuxShutdownReq{
ChanPoint: c.chanPoint,
ShortChanID: c.cfg.Channel.ShortChanID(),
InternalKey: c.localInternalKey,
Initiator: c.cfg.Channel.IsInitiator(),
CommitBlob: c.cfg.Channel.LocalCommitmentBlob(),
FundingBlob: c.cfg.Channel.FundingBlob(),
}
outs, err := aux.AuxCloseOutputs(AuxCloseDesc{
AuxShutdownReq: req,
CloseFee: closeFee,
CommitFee: c.cfg.Channel.CommitFee(),
LocalCloseOutput: c.localCloseOutput,
RemoteCloseOutput: c.remoteCloseOutput,
})
if err != nil {
return err
}
closeOuts = outs
return nil
})
if err != nil {
return closeOuts, err
}
return closeOuts, nil
}
// proposeCloseSigned attempts to propose a new signature for the closing
// transaction for a channel based on the prior fee negotiations and our
// current compromise fee.
func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) (
*lnwire.ClosingSigned, error) {
var (
closeOpts []lnwallet.ChanCloseOpt
err error
)
// If this is a taproot channel, then we'll include the musig session
// generated for the next co-op close negotiation round.
if c.cfg.Channel.ChanType().IsTaproot() {
closeOpts, err = c.cfg.MusigSession.ProposalClosingOpts()
if err != nil {
return nil, err
}
}
// We'll also now see if the aux chan closer has any additional options
// for the closing purpose.
c.auxOutputs, err = c.auxCloseOutputs(fee)
if err != nil {
return nil, err
}
c.auxOutputs.WhenSome(func(outs AuxCloseOutputs) {
closeOpts = append(
closeOpts, lnwallet.WithExtraCloseOutputs(
outs.ExtraCloseOutputs,
),
)
closeOpts = append(
closeOpts, lnwallet.WithCustomCoopSort(
outs.CustomSort,
),
)
})
// With all our options added, we'll attempt to co-op close now.
rawSig, _, _, err := c.cfg.Channel.CreateCloseProposal(
fee, c.localDeliveryScript, c.remoteDeliveryScript,
closeOpts...,
)
if err != nil {
return nil, err
}
// We'll note our last signature and proposed fee so when the remote
// party responds we'll be able to decide if we've agreed on fees or
// not.
var (
parsedSig lnwire.Sig
partialSig *lnwire.PartialSigWithNonce
)
if c.cfg.Channel.ChanType().IsTaproot() {
musig, ok := rawSig.(*lnwallet.MusigPartialSig)
if !ok {
return nil, fmt.Errorf("expected MusigPartialSig, "+
"got %T", rawSig)
}
partialSig = musig.ToWireSig()
} else {
parsedSig, err = lnwire.NewSigFromSignature(rawSig)
if err != nil {
return nil, err
}
}
c.lastFeeProposal = fee
chancloserLog.Infof("ChannelPoint(%v): proposing fee of %v sat to "+
"close chan", c.chanPoint, int64(fee))
// We'll assemble a ClosingSigned message using this information and
// return it to the caller so we can kick off the final stage of the
// channel closure process.
closeSignedMsg := lnwire.NewClosingSigned(c.cid, fee, parsedSig)
// For musig2 channels, the main sig is blank, and instead we'll send
// over a partial signature which'll be combined once our offer is
// accepted.
if partialSig != nil {
closeSignedMsg.PartialSig = lnwire.SomePartialSig(
partialSig.PartialSig,
)
}
// We'll also save this close signed, in the case that the remote party
// accepts our offer. This way, we don't have to re-sign.
c.priorFeeOffers[fee] = closeSignedMsg
return closeSignedMsg, nil
}
// feeInAcceptableRange returns true if the passed remote fee is deemed to be
// in an "acceptable" range to our local fee. This is an attempt at a
// compromise and to ensure that the fee negotiation has a stopping point. We
// consider their fee acceptable if it's within 30% of our fee.
func feeInAcceptableRange(localFee, remoteFee btcutil.Amount) bool {
// If our offer is lower than theirs, then we'll accept their offer if
// it's no more than 30% *greater* than our current offer.
if localFee < remoteFee {
acceptableRange := localFee + ((localFee * 3) / 10)
return remoteFee <= acceptableRange
}
// If our offer is greater than theirs, then we'll accept their offer if
// it's no more than 30% *less* than our current offer.
acceptableRange := localFee - ((localFee * 3) / 10)
return remoteFee >= acceptableRange
}
// ratchetFee is our step function used to inch our fee closer to something
// that both sides can agree on. If up is true, then we'll attempt to increase
// our offered fee. Otherwise, if up is false, then we'll attempt to decrease
// our offered fee.
func ratchetFee(fee btcutil.Amount, up bool) btcutil.Amount {
// If we need to ratchet up, then we'll increase our fee by 10%.
if up {
return fee + ((fee * 1) / 10)
}
// Otherwise, we'll *decrease* our fee by 10%.
return fee - ((fee * 1) / 10)
}
// calcCompromiseFee performs the current fee negotiation algorithm, taking
// into consideration our ideal fee based on current fee environment, the fee
// we last proposed (if any), and the fee proposed by the peer.
func calcCompromiseFee(chanPoint wire.OutPoint, ourIdealFee, lastSentFee,
remoteFee btcutil.Amount) btcutil.Amount {
// TODO(roasbeef): take in number of rounds as well?
chancloserLog.Infof("ChannelPoint(%v): computing fee compromise, "+
"ideal=%v, last_sent=%v, remote_offer=%v", chanPoint,
int64(ourIdealFee), int64(lastSentFee), int64(remoteFee))
// Otherwise, we'll need to attempt to make a fee compromise if this is
// the second round, and neither side has agreed on fees.
switch {
// If their proposed fee is identical to our ideal fee, then we'll go
// with that as we can short circuit the fee negotiation. Similarly, if
// we haven't sent an offer yet, we'll default to our ideal fee.
case ourIdealFee == remoteFee || lastSentFee == 0:
return ourIdealFee
// If the last fee we sent, is equal to the fee the remote party is
// offering, then we can simply return this fee as the negotiation is
// over.
case remoteFee == lastSentFee:
return lastSentFee
// If the fee the remote party is offering is less than the last one we
// sent, then we'll need to ratchet down in order to move our offer
// closer to theirs.
case remoteFee < lastSentFee:
// If the fee is lower, but still acceptable, then we'll just
// return this fee and end the negotiation.
if feeInAcceptableRange(lastSentFee, remoteFee) {
chancloserLog.Infof("ChannelPoint(%v): proposed "+
"remote fee is close enough, capitulating",
chanPoint)
return remoteFee
}
// Otherwise, we'll ratchet the fee *down* using our current
// algorithm.
return ratchetFee(lastSentFee, false)
// If the fee the remote party is offering is greater than the last one
// we sent, then we'll ratchet up in order to ensure we terminate
// eventually.
case remoteFee > lastSentFee:
// If the fee is greater, but still acceptable, then we'll just
// return this fee in order to put an end to the negotiation.
if feeInAcceptableRange(lastSentFee, remoteFee) {
chancloserLog.Infof("ChannelPoint(%v): proposed "+
"remote fee is close enough, capitulating",
chanPoint)
return remoteFee
}
// Otherwise, we'll ratchet the fee up using our current
// algorithm.
return ratchetFee(lastSentFee, true)
default:
// TODO(roasbeef): fail if their fee isn't in expected range
return remoteFee
}
}
// ParseUpfrontShutdownAddress attempts to parse an upfront shutdown address.
// If the address is empty, it returns nil. If it successfully decoded the
// address, it returns a script that pays out to the address.
func ParseUpfrontShutdownAddress(address string,
params *chaincfg.Params) (lnwire.DeliveryAddress, error) {
if len(address) == 0 {
return nil, nil
}
addr, err := btcutil.DecodeAddress(
address, params,
)
if err != nil {
return nil, fmt.Errorf("invalid address: %w", err)
}
if !addr.IsForNet(params) {
return nil, fmt.Errorf("invalid address: %v is not a %s "+
"address", addr, params.Name)
}
return txscript.PayToAddrScript(addr)
}
package chancloser
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// chancloserLog is a logger that is initialized with the btclog.Disabled
// logger.
var chancloserLog btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("CHCL", nil))
}
// DisableLog disables all logging output.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
chancloserLog = logger
}
package chancloser
import (
"sync/atomic"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/mock"
)
type dummyAdapters struct {
mock.Mock
msgSent atomic.Bool
confChan chan *chainntnfs.TxConfirmation
spendChan chan *chainntnfs.SpendDetail
}
func newDaemonAdapters() *dummyAdapters {
return &dummyAdapters{
confChan: make(chan *chainntnfs.TxConfirmation, 1),
spendChan: make(chan *chainntnfs.SpendDetail, 1),
}
}
func (d *dummyAdapters) SendMessages(pub btcec.PublicKey,
msgs []lnwire.Message) error {
defer d.msgSent.Store(true)
args := d.Called(pub, msgs)
return args.Error(0)
}
func (d *dummyAdapters) BroadcastTransaction(tx *wire.MsgTx,
label string) error {
args := d.Called(tx, label)
return args.Error(0)
}
func (d *dummyAdapters) DisableChannel(op wire.OutPoint) error {
args := d.Called(op)
return args.Error(0)
}
func (d *dummyAdapters) RegisterConfirmationsNtfn(txid *chainhash.Hash,
pkScript []byte, numConfs, heightHint uint32,
opts ...chainntnfs.NotifierOption,
) (*chainntnfs.ConfirmationEvent, error) {
args := d.Called(txid, pkScript, numConfs)
err := args.Error(0)
return &chainntnfs.ConfirmationEvent{
Confirmed: d.confChan,
}, err
}
func (d *dummyAdapters) RegisterSpendNtfn(outpoint *wire.OutPoint,
pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) {
args := d.Called(outpoint, pkScript, heightHint)
err := args.Error(0)
return &chainntnfs.SpendEvent{
Spend: d.spendChan,
}, err
}
type mockFeeEstimator struct {
mock.Mock
}
func (m *mockFeeEstimator) EstimateFee(chanType channeldb.ChannelType,
localTxOut, remoteTxOut *wire.TxOut,
idealFeeRate chainfee.SatPerKWeight) btcutil.Amount {
args := m.Called(chanType, localTxOut, remoteTxOut, idealFeeRate)
return args.Get(0).(btcutil.Amount)
}
type mockChanObserver struct {
mock.Mock
}
func (m *mockChanObserver) NoDanglingUpdates() bool {
args := m.Called()
return args.Bool(0)
}
func (m *mockChanObserver) DisableIncomingAdds() error {
args := m.Called()
return args.Error(0)
}
func (m *mockChanObserver) DisableOutgoingAdds() error {
args := m.Called()
return args.Error(0)
}
func (m *mockChanObserver) MarkCoopBroadcasted(txn *wire.MsgTx,
local bool) error {
args := m.Called(txn, local)
return args.Error(0)
}
func (m *mockChanObserver) MarkShutdownSent(deliveryAddr []byte,
isInitiator bool) error {
args := m.Called(deliveryAddr, isInitiator)
return args.Error(0)
}
func (m *mockChanObserver) FinalBalances() fn.Option[ShutdownBalances] {
args := m.Called()
return args.Get(0).(fn.Option[ShutdownBalances])
}
func (m *mockChanObserver) DisableChannel() error {
args := m.Called()
return args.Error(0)
}
type mockErrorReporter struct {
mock.Mock
}
func (m *mockErrorReporter) ReportError(err error) {
m.Called(err)
}
type mockCloseSigner struct {
mock.Mock
}
func (m *mockCloseSigner) CreateCloseProposal(fee btcutil.Amount,
localScript []byte, remoteScript []byte,
closeOpt ...lnwallet.ChanCloseOpt) (
input.Signature, *wire.MsgTx, btcutil.Amount, error) {
args := m.Called(fee, localScript, remoteScript, closeOpt)
return args.Get(0).(input.Signature), args.Get(1).(*wire.MsgTx),
args.Get(2).(btcutil.Amount), args.Error(3)
}
func (m *mockCloseSigner) CompleteCooperativeClose(localSig,
remoteSig input.Signature,
localScript, remoteScript []byte,
fee btcutil.Amount, closeOpt ...lnwallet.ChanCloseOpt,
) (*wire.MsgTx, btcutil.Amount, error) {
args := m.Called(
localSig, remoteSig, localScript, remoteScript, fee, closeOpt,
)
return args.Get(0).(*wire.MsgTx), args.Get(1).(btcutil.Amount),
args.Error(2)
}
package chancloser
import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/msgmux"
)
// RbfMsgMapper is a struct that implements the MsgMapper interface for the
// rbf-coop close state machine. This enables the state machine to be used with
// protofsm.
type RbfMsgMapper struct {
// blockHeight is the height of the block when the co-op close request
// was initiated. This is used to validate conditions related to the
// thaw height.
blockHeight uint32
// chanID is the channel ID of the channel being closed.
chanID lnwire.ChannelID
// peerPub is the public key of the peer that the channel is being
// closed.
peerPub btcec.PublicKey
}
// NewRbfMsgMapper creates a new RbfMsgMapper instance given the current block
// height when the co-op close request was initiated.
func NewRbfMsgMapper(blockHeight uint32,
chanID lnwire.ChannelID, peerPub btcec.PublicKey) *RbfMsgMapper {
return &RbfMsgMapper{
blockHeight: blockHeight,
chanID: chanID,
peerPub: peerPub,
}
}
// someEvent returns the target type as a protocol event option.
func someEvent[T ProtocolEvent](m T) fn.Option[ProtocolEvent] {
return fn.Some(ProtocolEvent(m))
}
// isForUs returns true if the channel ID + pubkey of the message matches the
// bound instance.
func (r *RbfMsgMapper) isForUs(chanID lnwire.ChannelID,
fromPub btcec.PublicKey) bool {
return r.chanID == chanID && r.peerPub.IsEqual(&fromPub)
}
// MapMsg maps a wire message into a FSM event. If the message is not mappable,
// then an error is returned.
func (r *RbfMsgMapper) MapMsg(wireMsg msgmux.PeerMsg) fn.Option[ProtocolEvent] {
switch msg := wireMsg.Message.(type) {
case *lnwire.Shutdown:
if !r.isForUs(msg.ChannelID, wireMsg.PeerPub) {
return fn.None[ProtocolEvent]()
}
return someEvent(&ShutdownReceived{
BlockHeight: r.blockHeight,
ShutdownScript: msg.Address,
})
case *lnwire.ClosingComplete:
if !r.isForUs(msg.ChannelID, wireMsg.PeerPub) {
return fn.None[ProtocolEvent]()
}
return someEvent(&OfferReceivedEvent{
SigMsg: *msg,
})
case *lnwire.ClosingSig:
if !r.isForUs(msg.ChannelID, wireMsg.PeerPub) {
return fn.None[ProtocolEvent]()
}
return someEvent(&LocalSigReceived{
SigMsg: *msg,
})
}
return fn.None[ProtocolEvent]()
}
package chancloser
import (
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/protofsm"
)
var (
// ErrInvalidStateTransition is returned when we receive an unexpected
// event for a given state.
ErrInvalidStateTransition = fmt.Errorf("invalid state transition")
// ErrTooManySigs is returned when we receive too many sigs from the
// remote party in the ClosingSigs message.
ErrTooManySigs = fmt.Errorf("too many sigs received")
// ErrNoSig is returned when we receive no sig from the remote party.
ErrNoSig = fmt.Errorf("no sig received")
// ErrUnknownFinalBalance is returned if we're unable to determine the
// final channel balance after a flush.
ErrUnknownFinalBalance = fmt.Errorf("unknown final balance")
// ErrRemoteCannotPay is returned if the remote party cannot pay the
// pay for the fees when it sends a signature.
ErrRemoteCannotPay = fmt.Errorf("remote cannot pay fees")
// ErrNonFinalSequence is returned if we receive a non-final sequence
// from the remote party for their signature.
ErrNonFinalSequence = fmt.Errorf("received non-final sequence")
// ErrCloserNoClosee is returned if our balance is dust, but the remote
// party includes our output.
ErrCloserNoClosee = fmt.Errorf("expected CloserNoClosee sig")
// ErrCloserAndClosee is returned when we expect a sig covering both
// outputs, it isn't present.
ErrCloserAndClosee = fmt.Errorf("expected CloserAndClosee sig")
// ErrWrongLocalScript is returned when the remote party sends a
// ClosingComplete message that doesn't carry our last local script
// sent.
ErrWrongLocalScript = fmt.Errorf("wrong local script")
)
// ProtocolEvent is a special interface used to create the equivalent of a
// sum-type, but using a "sealed" interface. Protocol events can be used as
// input to trigger a state transition, and also as output to trigger a new set
// of events into the very same state machine.
type ProtocolEvent interface {
protocolSealed()
}
// ProtocolEvents is a special type constraint that enumerates all the possible
// protocol events. This is used mainly as type-level documentation, and may
// also be useful to constraint certain state transition functions.
type ProtocolEvents interface {
SendShutdown | ShutdownReceived | ShutdownComplete | ChannelFlushed |
SendOfferEvent | OfferReceivedEvent | LocalSigReceived |
SpendEvent
}
// SpendEvent indicates that a transaction spending the funding outpoint has
// been confirmed in the main chain.
type SpendEvent struct {
// Tx is the spending transaction that has been confirmed.
Tx *wire.MsgTx
// BlockHeight is the height of the block that confirmed the
// transaction.
BlockHeight uint32
}
// protocolSealed indicates that this struct is a ProtocolEvent instance.
func (s *SpendEvent) protocolSealed() {}
// SendShutdown indicates that the user wishes to co-op close the channel, so we
// should send a new shutdown message to the remote party.
//
// transition:
// - fromState: ChannelActive
// - toState: ChannelFlushing
type SendShutdown struct {
// DeliveryAddr is the address we'd like to receive the funds to. If
// None, then a new addr will be generated.
DeliveryAddr fn.Option[lnwire.DeliveryAddress]
// IdealFeeRate is the ideal fee rate we'd like to use for the closing
// attempt.
IdealFeeRate chainfee.SatPerVByte
}
// protocolSealed indicates that this struct is a ProtocolEvent instance.
func (s *SendShutdown) protocolSealed() {}
// ShutdownReceived indicates that we received a shutdown event so we need to
// enter the flushing state.
//
// transition:
// - fromState: ChannelActive
// - toState: ChannelFlushing
type ShutdownReceived struct {
// ShutdownScript is the script the remote party wants to use to
// shutdown.
ShutdownScript lnwire.DeliveryAddress
// BlockHeight is the height at which the shutdown message was
// received. This is used for channel leases to determine if a co-op
// close can occur.
BlockHeight uint32
}
// protocolSealed indicates that this struct is a ProtocolEvent instance.
func (s *ShutdownReceived) protocolSealed() {}
// ShutdownComplete is an event that indicates the channel has been fully
// shutdown. At this point, we'll go to the ChannelFlushing state so we can
// wait for all pending updates to be gone from the channel.
//
// transition:
// - fromState: ShutdownPending
// - toState: ChannelFlushing
type ShutdownComplete struct {
}
// protocolSealed indicates that this struct is a ProtocolEvent instance.
func (s *ShutdownComplete) protocolSealed() {}
// ShutdownBalances holds the local+remote balance once the channel has been
// fully flushed.
type ShutdownBalances struct {
// LocalBalance is the local balance of the channel.
LocalBalance lnwire.MilliSatoshi
// RemoteBalance is the remote balance of the channel.
RemoteBalance lnwire.MilliSatoshi
}
// unknownBalance is a special variable used to denote an unknown channel
// balance (channel not fully flushed yet).
var unknownBalance = ShutdownBalances{}
// ChannelFlushed is an event that indicates the channel has been fully flushed
// can we can now start closing negotiation.
//
// transition:
// - fromState: ChannelFlushing
// - toState: ClosingNegotiation
type ChannelFlushed struct {
// FreshFlush indicates if this is the first time the channel has been
// flushed, or if this is a flush as part of an RBF iteration.
FreshFlush bool
// ShutdownBalances is the balances of the channel once it has been
// flushed. We tie this to the ChannelFlushed state as this may not be
// the same as the starting value.
ShutdownBalances
}
// protocolSealed indicates that this struct is a ProtocolEvent instance.
func (c *ChannelFlushed) protocolSealed() {}
// SendOfferEvent is a self-triggered event that transitions us from the
// LocalCloseStart state to the LocalOfferSent state. This kicks off the new
// signing process for the co-op close process.
//
// transition:
// - fromState: LocalCloseStart
// - toState: LocalOfferSent
type SendOfferEvent struct {
// TargetFeeRate is the fee rate we'll use for the closing transaction.
TargetFeeRate chainfee.SatPerVByte
}
// protocolSealed indicates that this struct is a ProtocolEvent instance.
func (s *SendOfferEvent) protocolSealed() {}
// LocalSigReceived is an event that indicates we've received a signature from
// the remote party, which signs our the co-op close transaction at our
// specified fee rate.
//
// transition:
// - fromState: LocalOfferSent
// - toState: ClosePending
type LocalSigReceived struct {
// SigMsg is the sig message we received from the remote party.
SigMsg lnwire.ClosingSig
}
// protocolSealed indicates that this struct is a ProtocolEvent instance.
func (s *LocalSigReceived) protocolSealed() {}
// OfferReceivedEvent is an event that indicates we've received an offer from
// the remote party. This applies to the RemoteCloseStart state.
//
// transition:
// - fromState: RemoteCloseStart
// - toState: ClosePending
type OfferReceivedEvent struct {
// SigMsg is the signature message we received from the remote party.
SigMsg lnwire.ClosingComplete
}
// protocolSealed indicates that this struct is a ProtocolEvent instance.
func (s *OfferReceivedEvent) protocolSealed() {}
// CloseSigner is an interface that abstracts away the details of the signing
// new coop close transactions.
type CloseSigner interface {
// CreateCloseProposal creates a new co-op close proposal in the form
// of a valid signature, the chainhash of the final txid, and our final
// balance in the created state.
CreateCloseProposal(proposedFee btcutil.Amount,
localDeliveryScript []byte, remoteDeliveryScript []byte,
closeOpt ...lnwallet.ChanCloseOpt,
) (
input.Signature, *wire.MsgTx, btcutil.Amount, error)
// CompleteCooperativeClose persistently "completes" the cooperative
// close by producing a fully signed co-op close transaction.
CompleteCooperativeClose(localSig, remoteSig input.Signature,
localDeliveryScript, remoteDeliveryScript []byte,
proposedFee btcutil.Amount, closeOpt ...lnwallet.ChanCloseOpt,
) (*wire.MsgTx, btcutil.Amount, error)
}
// ChanStateObserver is an interface used to observe state changes that occur
// in a channel. This can be used to figure out if we're able to send a
// shutdown message or not.
type ChanStateObserver interface {
// NoDanglingUpdates returns true if there are no dangling updates in
// the channel. In other words, there are no active update messages
// that haven't already been covered by a commit sig.
NoDanglingUpdates() bool
// DisableIncomingAdds instructs the channel link to disable process new
// incoming add messages.
DisableIncomingAdds() error
// DisableOutgoingAdds instructs the channel link to disable process
// new outgoing add messages.
DisableOutgoingAdds() error
// DisableChannel attempts to disable a channel (marking it ineligible
// to forward), and also sends out a network update to disable the
// channel.
DisableChannel() error
// MarkCoopBroadcasted persistently marks that the channel close
// transaction has been broadcast.
MarkCoopBroadcasted(*wire.MsgTx, bool) error
// MarkShutdownSent persists the given ShutdownInfo. The existence of
// the ShutdownInfo represents the fact that the Shutdown message has
// been sent by us and so should be re-sent on re-establish.
MarkShutdownSent(deliveryAddr []byte, isInitiator bool) error
// FinalBalances is the balances of the channel once it has been
// flushed. If Some, then this indicates that the channel is now in a
// state where it's always flushed, so we can accelerate the state
// transitions.
FinalBalances() fn.Option[ShutdownBalances]
}
// Environment is a set of dependencies that a state machine may need to carry
// out the logic for a given state transition. All fields are to be considered
// immutable, and will be fixed for the lifetime of the state machine.
type Environment struct {
// ChainParams is the chain parameters for the channel.
ChainParams chaincfg.Params
// ChanPeer is the peer we're attempting to close the channel with.
ChanPeer btcec.PublicKey
// ChanPoint is the channel point of the active channel.
ChanPoint wire.OutPoint
// ChanID is the channel ID of the channel we're attempting to close.
ChanID lnwire.ChannelID
// ShortChanID is the short channel ID of the channel we're attempting
// to close.
Scid lnwire.ShortChannelID
// ChanType is the type of channel we're attempting to close.
ChanType channeldb.ChannelType
// BlockHeight is the current block height.
BlockHeight uint32
// DefaultFeeRate is the fee we'll use for the closing transaction if
// the user didn't specify an ideal fee rate. This may happen if the
// remote party is the one that initiates the co-op close.
DefaultFeeRate chainfee.SatPerVByte
// ThawHeight is the height at which the channel will be thawed. If
// this is None, then co-op close can occur at any moment.
ThawHeight fn.Option[uint32]
// RemoteUprontShutdown is the upfront shutdown addr of the remote
// party. We'll use this to validate if the remote peer is authorized to
// close the channel with the sent addr or not.
RemoteUpfrontShutdown fn.Option[lnwire.DeliveryAddress]
// LocalUprontShutdown is our upfront shutdown address. If Some, then
// we'll default to using this.
LocalUpfrontShutdown fn.Option[lnwire.DeliveryAddress]
// NewDeliveryScript is a function that returns a new delivery script.
// This is used if we don't have an upfront shutdown addr, and no addr
// was specified at closing time.
NewDeliveryScript func() (lnwire.DeliveryAddress, error)
// FeeEstimator is the fee estimator we'll use to determine the fee in
// satoshis we'll pay given a local and/or remote output.
FeeEstimator CoopFeeEstimator
// ChanObserver is an interface used to observe state changes to the
// channel. We'll use this to figure out when/if we can send certain
// messages.
ChanObserver ChanStateObserver
// CloseSigner is the signer we'll use to sign the close transaction.
// This is a part of the ChannelFlushed state, as the channel state
// we'll be signing can only be determined once the channel has been
// flushed.
CloseSigner CloseSigner
}
// Name returns the name of the environment. This is used to uniquely identify
// the environment of related state machines. For this state machine, the name
// is based on the channel ID.
func (e *Environment) Name() string {
return fmt.Sprintf("rbf_chan_closer(%v)", e.ChanPoint)
}
// CloseStateTransition is the StateTransition type specific to the coop close
// state machine.
//
//nolint:ll
type CloseStateTransition = protofsm.StateTransition[ProtocolEvent, *Environment]
// ProtocolState is our sum-type ish interface that represents the current
// protocol state.
type ProtocolState interface {
// protocolStateSealed is a special method that is used to seal the
// interface (only types in this package can implement it).
protocolStateSealed()
// IsTerminal returns true if the target state is a terminal state.
IsTerminal() bool
// ProcessEvent takes a protocol event, and implements a state
// transition for the state.
ProcessEvent(ProtocolEvent, *Environment) (*CloseStateTransition, error)
// String returns the name of the state.
String() string
}
// AsymmetricPeerState is an extension of the normal ProtocolState interface
// that gives a caller a hit on if the target state should process an incoming
// event or not.
type AsymmetricPeerState interface {
ProtocolState
// ShouldRouteTo returns true if the target state should process the
// target event.
ShouldRouteTo(ProtocolEvent) bool
}
// ProtocolStates is a special type constraint that enumerates all the possible
// protocol states.
type ProtocolStates interface {
ChannelActive | ShutdownPending | ChannelFlushing | ClosingNegotiation |
LocalCloseStart | LocalOfferSent | RemoteCloseStart |
ClosePending | CloseFin | CloseErr
}
// ChannelActive is the base state for the channel closer state machine. In
// this state, we haven't begun the shutdown process yet, so the channel is
// still active. Receiving the ShutdownSent or ShutdownReceived events will
// transition us to the ChannelFushing state.
//
// When we transition to this state, we emit a DaemonEvent to send the shutdown
// message if we received one ourselves. Alternatively, we may send out a new
// shutdown if we're initiating it for the very first time.
//
// transition:
// - fromState: None
// - toState: ChannelFlushing
//
// input events:
// - SendShutdown
// - ShutdownReceived
type ChannelActive struct {
}
// String returns the name of the state for ChannelActive.
func (c *ChannelActive) String() string {
return "ChannelActive"
}
// IsTerminal returns true if the target state is a terminal state.
func (c *ChannelActive) IsTerminal() bool {
return false
}
// protocolSealed indicates that this struct is a ProtocolEvent instance.
func (c *ChannelActive) protocolStateSealed() {}
// ShutdownScripts is a set of scripts that we'll use to co-op close the
// channel.
type ShutdownScripts struct {
// LocalDeliveryScript is the script that we'll send our settled
// channel funds to.
LocalDeliveryScript lnwire.DeliveryAddress
// RemoteDeliveryScript is the script that we'll send the remote
// party's settled channel funds to.
RemoteDeliveryScript lnwire.DeliveryAddress
}
// ShutdownPending is the state we enter into after we've sent or received the
// shutdown message. If we sent the shutdown, then we'll wait for the remote
// party to send a shutdown. Otherwise, if we received it, then we'll send our
// shutdown then go to the next state.
//
// transition:
// - fromState: ChannelActive
// - toState: ChannelFlushing
//
// input events:
// - SendShutdown
// - ShutdownReceived
type ShutdownPending struct {
// ShutdownScripts store the set of scripts we'll use to initiate a coop
// close.
ShutdownScripts
// IdealFeeRate is the ideal fee rate we'd like to use for the closing
// attempt.
IdealFeeRate fn.Option[chainfee.SatPerVByte]
// EarlyRemoteOffer is the offer we received from the remote party
// before we received their shutdown message. We'll stash it to process
// later.
EarlyRemoteOffer fn.Option[OfferReceivedEvent]
}
// String returns the name of the state for ShutdownPending.
func (s *ShutdownPending) String() string {
return "ShutdownPending"
}
// IsTerminal returns true if the target state is a terminal state.
func (s *ShutdownPending) IsTerminal() bool {
return false
}
// protocolStateSealed indicates that this struct is a ProtocolEvent instance.
func (s *ShutdownPending) protocolStateSealed() {}
// ChannelFlushing is the state we enter into after we've received or sent a
// shutdown message. In this state, we wait the ChannelFlushed event, after
// which we'll transition to the CloseReady state.
//
// transition:
// - fromState: ShutdownPending
// - toState: ClosingNegotiation
//
// input events:
// - ShutdownComplete
// - ShutdownReceived
type ChannelFlushing struct {
// EarlyRemoteOffer is the offer we received from the remote party
// before we obtained the local channel flushed event. We'll stash this
// to process later.
EarlyRemoteOffer fn.Option[OfferReceivedEvent]
// ShutdownScripts store the set of scripts we'll use to initiate a coop
// close.
ShutdownScripts
// IdealFeeRate is the ideal fee rate we'd like to use for the closing
// transaction. Once the channel has been flushed, we'll use this as
// our target fee rate.
IdealFeeRate fn.Option[chainfee.SatPerVByte]
}
// String returns the name of the state for ChannelFlushing.
func (c *ChannelFlushing) String() string {
return "ChannelFlushing"
}
// protocolStateSealed indicates that this struct is a ProtocolEvent instance.
func (c *ChannelFlushing) protocolStateSealed() {}
// IsTerminal returns true if the target state is a terminal state.
func (c *ChannelFlushing) IsTerminal() bool {
return false
}
// ClosingNegotiation is the state we transition to once the channel has been
// flushed. This is actually a composite state that contains one for each side
// of the channel, as the closing process is asymmetric. Once either of the
// peer states reaches the CloseFin state, then the channel is fully closed,
// and we'll transition to that terminal state.
//
// transition:
// - fromState: ChannelFlushing
// - toState: CloseFin
//
// input events:
// - ChannelFlushed
type ClosingNegotiation struct {
// PeerStates is a composite state that contains the state for both the
// local and remote parties. Our usage of Dual makes this a special
// state that allows us to treat two states as a single state. We'll use
// the ShouldRouteTo method to determine which state route incoming
// events to.
PeerState lntypes.Dual[AsymmetricPeerState]
// CloseChannelTerms is the terms we'll use to close the channel. We
// hold a value here which is pointed to by the various
// AsymmetricPeerState instances. This allows us to update this value if
// the remote peer sends a new address, with each of the state noting
// the new value via a pointer.
*CloseChannelTerms
}
// String returns the name of the state for ClosingNegotiation.
func (c *ClosingNegotiation) String() string {
localState := c.PeerState.GetForParty(lntypes.Local)
remoteState := c.PeerState.GetForParty(lntypes.Remote)
return fmt.Sprintf("ClosingNegotiation(local=%v, remote=%v)",
localState, remoteState)
}
// IsTerminal returns true if the target state is a terminal state.
func (c *ClosingNegotiation) IsTerminal() bool {
return false
}
// protocolSealed indicates that this struct is a ProtocolEvent instance.
func (c *ClosingNegotiation) protocolStateSealed() {}
// ErrState can be used to introspect into a benign error related to a state
// transition.
type ErrState interface {
sealed()
error
// Err returns an error for the ErrState.
Err() error
}
// ErrStateCantPayForFee is sent when the local party attempts a fee update
// that they can't actually party for.
type ErrStateCantPayForFee struct {
localBalance btcutil.Amount
attemptedFee btcutil.Amount
}
// NewErrStateCantPayForFee returns a new NewErrStateCantPayForFee error.
func NewErrStateCantPayForFee(localBalance, attemptedFee btcutil.Amount,
) *ErrStateCantPayForFee {
return &ErrStateCantPayForFee{
localBalance: localBalance,
attemptedFee: attemptedFee,
}
}
// sealed makes this a sealed interface.
func (e *ErrStateCantPayForFee) sealed() {
}
// Err returns an error for the ErrState.
func (e *ErrStateCantPayForFee) Err() error {
return fmt.Errorf("cannot pay for fee of %v, only have %v local "+
"balance", e.attemptedFee, e.localBalance)
}
// Error returns the error string for the ErrState.
func (e *ErrStateCantPayForFee) Error() string {
return e.Err().Error()
}
// String returns the string for the ErrStateCantPayForFee.
func (e *ErrStateCantPayForFee) String() string {
return fmt.Sprintf("ErrStateCantPayForFee(local_balance=%v, "+
"attempted_fee=%v)", e.localBalance, e.attemptedFee)
}
// CloseChannelTerms is a set of terms that we'll use to close the channel. This
// includes the balances of the channel, and the scripts we'll use to send each
// party's funds to.
type CloseChannelTerms struct {
ShutdownScripts
ShutdownBalances
}
// DeriveCloseTxOuts takes the close terms, and returns the local and remote tx
// out for the close transaction. If an output is dust, then it'll be nil.
func (c *CloseChannelTerms) DeriveCloseTxOuts() (*wire.TxOut, *wire.TxOut) {
//nolint:ll
deriveTxOut := func(balance btcutil.Amount, pkScript []byte) *wire.TxOut {
// We'll base the existence of the output on our normal dust
// check.
dustLimit := lnwallet.DustLimitForSize(len(pkScript))
if balance >= dustLimit {
return &wire.TxOut{
PkScript: pkScript,
Value: int64(balance),
}
}
return nil
}
localTxOut := deriveTxOut(
c.LocalBalance.ToSatoshis(),
c.LocalDeliveryScript,
)
remoteTxOut := deriveTxOut(
c.RemoteBalance.ToSatoshis(),
c.RemoteDeliveryScript,
)
return localTxOut, remoteTxOut
}
// RemoteAmtIsDust returns true if the remote output is dust.
func (c *CloseChannelTerms) RemoteAmtIsDust() bool {
return c.RemoteBalance.ToSatoshis() < lnwallet.DustLimitForSize(
len(c.RemoteDeliveryScript),
)
}
// LocalAmtIsDust returns true if the local output is dust.
func (c *CloseChannelTerms) LocalAmtIsDust() bool {
return c.LocalBalance.ToSatoshis() < lnwallet.DustLimitForSize(
len(c.LocalDeliveryScript),
)
}
// LocalCanPayFees returns true if the local party can pay the absolute fee
// from their local settled balance.
func (c *CloseChannelTerms) LocalCanPayFees(absoluteFee btcutil.Amount) bool {
return c.LocalBalance.ToSatoshis() >= absoluteFee
}
// RemoteCanPayFees returns true if the remote party can pay the absolute fee
// from their remote settled balance.
func (c *CloseChannelTerms) RemoteCanPayFees(absoluteFee btcutil.Amount) bool {
return c.RemoteBalance.ToSatoshis() >= absoluteFee
}
// LocalCloseStart is the state we enter into after we've received or sent
// shutdown, and the channel has been flushed. In this state, we'll emit a new
// event to send our offer to drive the rest of the process.
//
// transition:
// - fromState: ChannelFlushing
// - toState: LocalOfferSent
//
// input events:
// - SendOfferEvent
type LocalCloseStart struct {
*CloseChannelTerms
}
// String returns the name of the state for LocalCloseStart, including proposed
// fee details.
func (l *LocalCloseStart) String() string {
return "LocalCloseStart"
}
// ShouldRouteTo returns true if the target state should process the target
// event.
func (l *LocalCloseStart) ShouldRouteTo(event ProtocolEvent) bool {
switch event.(type) {
case *SendOfferEvent:
return true
default:
return false
}
}
// IsTerminal returns true if the target state is a terminal state.
func (l *LocalCloseStart) IsTerminal() bool {
return false
}
// protocolStateaSealed indicates that this struct is a ProtocolEvent instance.
func (l *LocalCloseStart) protocolStateSealed() {}
// LocalOfferSent is the state we transition to after we reveiver the
// SendOfferEvent in the LocalCloseStart state. With this state we send our
// offer to the remote party, then await a sig from them which concludes the
// local cooperative close process.
//
// transition:
// - fromState: LocalCloseStart
// - toState: ClosePending
//
// input events:
// - LocalSigReceived
type LocalOfferSent struct {
*CloseChannelTerms
// ProposedFee is the fee we proposed to the remote party.
ProposedFee btcutil.Amount
// ProposedFeeRate is the fee rate we proposed to the remote party.
ProposedFeeRate chainfee.SatPerVByte
// LocalSig is the signature we sent to the remote party.
LocalSig lnwire.Sig
}
// String returns the name of the state for LocalOfferSent, including proposed.
func (l *LocalOfferSent) String() string {
return fmt.Sprintf("LocalOfferSent(proposed_fee=%v)", l.ProposedFee)
}
// ShouldRouteTo returns true if the target state should process the target
// event.
func (l *LocalOfferSent) ShouldRouteTo(event ProtocolEvent) bool {
switch event.(type) {
case *LocalSigReceived:
return true
default:
return false
}
}
// protocolStateaSealed indicates that this struct is a ProtocolEvent instance.
func (l *LocalOfferSent) protocolStateSealed() {}
// IsTerminal returns true if the target state is a terminal state.
func (l *LocalOfferSent) IsTerminal() bool {
return false
}
// ClosePending is the state we enter after concluding the negotiation for the
// remote or local state. At this point, given a confirmation notification we
// can terminate the process. Otherwise, we can receive a fresh CoopCloseReq to
// go back to the very start.
//
// transition:
// - fromState: LocalOfferSent || RemoteCloseStart
// - toState: CloseFin
//
// input events:
// - LocalSigReceived
// - OfferReceivedEvent
type ClosePending struct {
// CloseTx is the pending close transaction.
CloseTx *wire.MsgTx
*CloseChannelTerms
// FeeRate is the fee rate of the closing transaction.
FeeRate chainfee.SatPerVByte
// Party indicates which party is at this state. This is used to
// implement the state transition properly, based on ShouldRouteTo.
Party lntypes.ChannelParty
}
// String returns the name of the state for ClosePending.
func (c *ClosePending) String() string {
return fmt.Sprintf("ClosePending(txid=%v, party=%v, fee_rate=%v)",
c.CloseTx.TxHash(), c.Party, c.FeeRate)
}
// isType returns true if the value is of type T.
func isType[T any](value any) bool {
_, ok := value.(T)
return ok
}
// ShouldRouteTo returns true if the target state should process the target
// event.
func (c *ClosePending) ShouldRouteTo(event ProtocolEvent) bool {
switch event.(type) {
case *SpendEvent:
return true
default:
switch {
case c.Party == lntypes.Local && isType[*SendOfferEvent](event):
return true
case c.Party == lntypes.Remote && isType[*OfferReceivedEvent](
event,
):
return true
}
return false
}
}
// protocolStateSealed indicates that this struct is a ProtocolEvent instance.
func (c *ClosePending) protocolStateSealed() {}
// IsTerminal returns true if the target state is a terminal state.
func (c *ClosePending) IsTerminal() bool {
return true
}
// CloseFin is the terminal state for the channel closer state machine. At this
// point, the close tx has been confirmed on chain.
type CloseFin struct {
// ConfirmedTx is the transaction that confirmed the channel close.
ConfirmedTx *wire.MsgTx
}
// String returns the name of the state for CloseFin.
func (c *CloseFin) String() string {
return "CloseFin"
}
// protocolStateSealed indicates that this struct is a ProtocolEvent instance.
func (c *CloseFin) protocolStateSealed() {}
// IsTerminal returns true if the target state is a terminal state.
func (c *CloseFin) IsTerminal() bool {
return true
}
// RemoteCloseStart is similar to the LocalCloseStart, but is used to drive the
// process of signing an offer for the remote party
//
// transition:
// - fromState: ChannelFlushing
// - toState: ClosePending
type RemoteCloseStart struct {
*CloseChannelTerms
}
// String returns the name of the state for RemoteCloseStart.
func (r *RemoteCloseStart) String() string {
return "RemoteCloseStart"
}
// ShouldRouteTo returns true if the target state should process the target
// event.
func (l *RemoteCloseStart) ShouldRouteTo(event ProtocolEvent) bool {
switch event.(type) {
case *OfferReceivedEvent:
return true
default:
return false
}
}
// protocolStateSealed indicates that this struct is a ProtocolEvent instance.
func (l *RemoteCloseStart) protocolStateSealed() {}
// IsTerminal returns true if the target state is a terminal state.
func (l *RemoteCloseStart) IsTerminal() bool {
return false
}
// CloseErr is an error state in the protocol. We enter this state when a
// protocol constraint is violated, or an upfront sanity check fails.
type CloseErr struct {
ErrState
*CloseChannelTerms
// Party indicates which party is at this state. This is used to
// implement the state transition properly, based on ShouldRouteTo.
Party lntypes.ChannelParty
}
// String returns the name of the state for CloseErr, including error and party
// details.
func (c *CloseErr) String() string {
return fmt.Sprintf("CloseErr(party=%v, err=%v)", c.Party, c.ErrState)
}
// ShouldRouteTo returns true if the target state should process the target
// event.
func (c *CloseErr) ShouldRouteTo(event ProtocolEvent) bool {
switch event.(type) {
case *SpendEvent:
return true
default:
switch {
case c.Party == lntypes.Local && isType[*SendOfferEvent](event):
return true
case c.Party == lntypes.Remote && isType[*OfferReceivedEvent](
event,
):
return true
}
return false
}
}
// protocolStateSealed indicates that this struct is a ProtocolEvent instance.
func (c *CloseErr) protocolStateSealed() {}
// IsTerminal returns true if the target state is a terminal state.
func (c *CloseErr) IsTerminal() bool {
return true
}
// RbfChanCloser is a state machine that handles the RBF-enabled cooperative
// channel close protocol.
type RbfChanCloser = protofsm.StateMachine[ProtocolEvent, *Environment]
// RbfChanCloserCfg is a configuration struct that is used to initialize a new
// RBF chan closer state machine.
type RbfChanCloserCfg = protofsm.StateMachineCfg[ProtocolEvent, *Environment]
// RbfSpendMapper is a type used to map the generic spend event to one specific
// to this package.
type RbfSpendMapper = protofsm.SpendMapper[ProtocolEvent]
func SpendMapper(spendEvent *chainntnfs.SpendDetail) ProtocolEvent {
return &SpendEvent{
Tx: spendEvent.SpendingTx,
BlockHeight: uint32(spendEvent.SpendingHeight),
}
}
// RbfMsgMapperT is a type used to map incoming wire messages to protocol
// events.
type RbfMsgMapperT = protofsm.MsgMapper[ProtocolEvent]
// RbfState is a type alias for the state of the RBF channel closer.
type RbfState = protofsm.State[ProtocolEvent, *Environment]
// RbfEvent is a type alias for the event type of the RBF channel closer.
type RbfEvent = protofsm.EmittedEvent[ProtocolEvent]
// RbfStateSub is a type alias for the state subscription type of the RBF chan
// closer.
type RbfStateSub = protofsm.StateSubscriber[ProtocolEvent, *Environment]
package chancloser
import (
"bytes"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/mempool"
"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/protofsm"
"github.com/lightningnetwork/lnd/tlv"
)
var (
// ErrInvalidStateTransition is returned if the remote party tries to
// close, but the thaw height hasn't been matched yet.
ErrThawHeightNotReached = fmt.Errorf("thaw height not reached")
)
// sendShutdownEvents is a helper function that returns a set of daemon events
// we need to emit when we decide that we should send a shutdown message. We'll
// also mark the channel as borked as well, as at this point, we no longer want
// to continue with normal operation.
func sendShutdownEvents(chanID lnwire.ChannelID, chanPoint wire.OutPoint,
deliveryAddr lnwire.DeliveryAddress, peerPub btcec.PublicKey,
postSendEvent fn.Option[ProtocolEvent],
chanState ChanStateObserver) (protofsm.DaemonEventSet, error) {
// We'll emit a daemon event that instructs the daemon to send out a
// new shutdown message to the remote peer.
msgsToSend := &protofsm.SendMsgEvent[ProtocolEvent]{
TargetPeer: peerPub,
Msgs: []lnwire.Message{&lnwire.Shutdown{
ChannelID: chanID,
Address: deliveryAddr,
}},
SendWhen: fn.Some(func() bool {
ok := chanState.NoDanglingUpdates()
if ok {
chancloserLog.Infof("ChannelPoint(%v): no "+
"dangling updates sending shutdown "+
"message", chanPoint)
}
return ok
}),
PostSendEvent: postSendEvent,
}
// If a close is already in process (we're in the RBF loop), then we
// can skip everything below, and just send out the shutdown message.
if chanState.FinalBalances().IsSome() {
return protofsm.DaemonEventSet{msgsToSend}, nil
}
// Before closing, we'll attempt to send a disable update for the
// channel. We do so before closing the channel as otherwise the
// current edge policy won't be retrievable from the graph.
if err := chanState.DisableChannel(); err != nil {
return nil, fmt.Errorf("unable to disable channel: %w", err)
}
// If we have a post-send event, then this means that we're the
// responder. We'll use this fact below to update state in the DB.
isInitiator := postSendEvent.IsNone()
chancloserLog.Infof("ChannelPoint(%v): disabling outgoing adds",
chanPoint)
// As we're about to send a shutdown, we'll disable adds in the
// outgoing direction.
if err := chanState.DisableOutgoingAdds(); err != nil {
return nil, fmt.Errorf("unable to disable outgoing "+
"adds: %w", err)
}
// To be able to survive a restart, we'll also write to disk
// information about the shutdown we're about to send out.
err := chanState.MarkShutdownSent(deliveryAddr, isInitiator)
if err != nil {
return nil, fmt.Errorf("unable to mark shutdown sent: %w", err)
}
chancloserLog.Debugf("ChannelPoint(%v): marking channel as borked",
chanPoint)
return protofsm.DaemonEventSet{msgsToSend}, nil
}
// validateShutdown is a helper function that validates that the shutdown has a
// proper delivery script, and can be sent based on the current thaw height of
// the channel.
func validateShutdown(chanThawHeight fn.Option[uint32],
upfrontAddr fn.Option[lnwire.DeliveryAddress],
msg *ShutdownReceived, chanPoint wire.OutPoint,
chainParams chaincfg.Params) error {
// If we've received a shutdown message, and we have a thaw height,
// then we need to make sure that the channel can now be co-op closed.
err := fn.MapOptionZ(chanThawHeight, func(thawHeight uint32) error {
// If the current height is below the thaw height, then we'll
// reject the shutdown message as we can't yet co-op close the
// channel.
if msg.BlockHeight < thawHeight {
return fmt.Errorf("%w: initiator attempting to "+
"co-op close frozen ChannelPoint(%v) "+
"(current_height=%v, thaw_height=%v)",
ErrThawHeightNotReached, chanPoint,
msg.BlockHeight, thawHeight)
}
return nil
})
if err != nil {
return err
}
// Next, we'll verify that the remote party is sending the expected
// shutdown script.
return fn.MapOption(func(addr lnwire.DeliveryAddress) error {
return validateShutdownScript(
addr, msg.ShutdownScript, &chainParams,
)
})(upfrontAddr).UnwrapOr(nil)
}
// ProcessEvent takes a protocol event, and implements a state transition for
// the state. From this state, we can receive two possible incoming events:
// SendShutdown and ShutdownReceived. Both of these will transition us to the
// ChannelFlushing state.
func (c *ChannelActive) ProcessEvent(event ProtocolEvent, env *Environment,
) (*CloseStateTransition, error) {
switch msg := event.(type) {
// If we get a confirmation, then a prior transaction we broadcasted
// has confirmed, so we can move to our terminal state early.
case *SpendEvent:
return &CloseStateTransition{
NextState: &CloseFin{
ConfirmedTx: msg.Tx,
},
}, nil
// If we receive the SendShutdown event, then we'll send our shutdown
// with a special SendPredicate, then go to the ShutdownPending where
// we'll wait for the remote to send their shutdown.
case *SendShutdown:
// If we have an upfront shutdown addr or a delivery addr then
// we'll use that. Otherwise, we'll generate a new delivery
// addr.
shutdownScript, err := env.LocalUpfrontShutdown.Alt(
msg.DeliveryAddr,
).UnwrapOrFuncErr(env.NewDeliveryScript)
if err != nil {
return nil, err
}
// We'll emit some daemon events to send the shutdown message
// and disable the channel on the network level. In this case,
// we don't need a post send event as receive their shutdown is
// what'll move us beyond the ShutdownPending state.
daemonEvents, err := sendShutdownEvents(
env.ChanID, env.ChanPoint, shutdownScript,
env.ChanPeer, fn.None[ProtocolEvent](),
env.ChanObserver,
)
if err != nil {
return nil, err
}
chancloserLog.Infof("ChannelPoint(%v): sending shutdown msg, "+
"delivery_script=%x", env.ChanPoint, shutdownScript)
// From here, we'll transition to the shutdown pending state. In
// this state we await their shutdown message (self loop), then
// also the flushing event.
return &CloseStateTransition{
NextState: &ShutdownPending{
IdealFeeRate: fn.Some(msg.IdealFeeRate),
ShutdownScripts: ShutdownScripts{
LocalDeliveryScript: shutdownScript,
},
},
NewEvents: fn.Some(RbfEvent{
ExternalEvents: daemonEvents,
}),
}, nil
// When we receive a shutdown from the remote party, we'll validate the
// shutdown message, then transition to the ShutdownPending state. We'll
// also emit similar events like the above to send out shutdown, and
// also disable the channel.
case *ShutdownReceived:
chancloserLog.Infof("ChannelPoint(%v): received shutdown msg",
env.ChanPoint)
// Validate that they can send the message now, and also that
// they haven't violated their commitment to a prior upfront
// shutdown addr.
err := validateShutdown(
env.ThawHeight, env.RemoteUpfrontShutdown, msg,
env.ChanPoint, env.ChainParams,
)
if err != nil {
chancloserLog.Errorf("ChannelPoint(%v): rejecting "+
"shutdown attempt: %v", env.ChanPoint, err)
return nil, err
}
// If we have an upfront shutdown addr we'll use that,
// otherwise, we'll generate a new delivery script.
shutdownAddr, err := env.LocalUpfrontShutdown.UnwrapOrFuncErr(
env.NewDeliveryScript,
)
if err != nil {
return nil, err
}
chancloserLog.Infof("ChannelPoint(%v): sending shutdown msg "+
"at next clean commit state", env.ChanPoint)
// Now that we know the shutdown message is valid, we'll obtain
// the set of daemon events we need to emit. We'll also specify
// that once the message has actually been sent, that we
// generate receive an input event of a ShutdownComplete.
daemonEvents, err := sendShutdownEvents(
env.ChanID, env.ChanPoint, shutdownAddr,
env.ChanPeer,
fn.Some[ProtocolEvent](&ShutdownComplete{}),
env.ChanObserver,
)
if err != nil {
return nil, err
}
chancloserLog.Infof("ChannelPoint(%v): disabling incoming adds",
env.ChanPoint)
// We just received a shutdown, so we'll disable the adds in
// the outgoing direction.
if err := env.ChanObserver.DisableIncomingAdds(); err != nil {
return nil, fmt.Errorf("unable to disable incoming "+
"adds: %w", err)
}
remoteAddr := msg.ShutdownScript
return &CloseStateTransition{
NextState: &ShutdownPending{
ShutdownScripts: ShutdownScripts{
LocalDeliveryScript: shutdownAddr,
RemoteDeliveryScript: remoteAddr,
},
},
NewEvents: fn.Some(protofsm.EmittedEvent[ProtocolEvent]{
ExternalEvents: daemonEvents,
}),
}, nil
// Any other messages in this state will result in an error, as this is
// an undefined state transition.
default:
return nil, fmt.Errorf("%w: received %T while in ChannelActive",
ErrInvalidStateTransition, msg)
}
}
// ProcessEvent takes a protocol event, and implements a state transition for
// the state. Our path to this state will determine the set of valid events. If
// we were the one that sent the shutdown, then we'll just wait on the
// ShutdownReceived event. Otherwise, we received the shutdown, and can move
// forward once we receive the ShutdownComplete event. Receiving
// ShutdownComplete means that we've sent our shutdown, as this was specified
// as a post send event.
func (s *ShutdownPending) ProcessEvent(event ProtocolEvent, env *Environment,
) (*CloseStateTransition, error) {
switch msg := event.(type) {
// If we get a confirmation, then a prior transaction we broadcasted
// has confirmed, so we can move to our terminal state early.
case *SpendEvent:
return &CloseStateTransition{
NextState: &CloseFin{
ConfirmedTx: msg.Tx,
},
}, nil
// The remote party sent an offer early. We'll go to the ChannelFlushing
// case, and then emit the offer as a internal event, which'll be
// handled as an early offer.
case *OfferReceivedEvent:
chancloserLog.Infof("ChannelPoint(%v): got an early offer "+
"in ShutdownPending, emitting as external event",
env.ChanPoint)
s.EarlyRemoteOffer = fn.Some(*msg)
// We'll perform a noop update so we can wait for the actual
// channel flushed event.
return &CloseStateTransition{
NextState: s,
}, nil
// When we receive a shutdown from the remote party, we'll validate the
// shutdown message, then transition to the ChannelFlushing state.
case *ShutdownReceived:
chancloserLog.Infof("ChannelPoint(%v): received shutdown msg",
env.ChanPoint)
// Validate that they can send the message now, and also that
// they haven't violated their commitment to a prior upfront
// shutdown addr.
err := validateShutdown(
env.ThawHeight, env.RemoteUpfrontShutdown, msg,
env.ChanPoint, env.ChainParams,
)
if err != nil {
chancloserLog.Errorf("ChannelPoint(%v): rejecting "+
"shutdown attempt: %v", env.ChanPoint, err)
return nil, err
}
// If the channel is *already* flushed, and the close is
// go straight into negotiation, as this is the RBF loop.
// already in progress, then we can skip the flushing state and
var eventsToEmit []ProtocolEvent
finalBalances := env.ChanObserver.FinalBalances().UnwrapOr(
unknownBalance,
)
if finalBalances != unknownBalance {
channelFlushed := ProtocolEvent(&ChannelFlushed{
ShutdownBalances: finalBalances,
})
eventsToEmit = append(eventsToEmit, channelFlushed)
}
chancloserLog.Infof("ChannelPoint(%v): disabling incoming adds",
env.ChanPoint)
// We just received a shutdown, so we'll disable the adds in
// the outgoing direction.
if err := env.ChanObserver.DisableIncomingAdds(); err != nil {
return nil, fmt.Errorf("unable to disable incoming "+
"adds: %w", err)
}
chancloserLog.Infof("ChannelPoint(%v): waiting for channel to "+
"be flushed...", env.ChanPoint)
// If we received a remote offer early from the remote party,
// then we'll add that to the set of internal events to emit.
s.EarlyRemoteOffer.WhenSome(func(offer OfferReceivedEvent) {
eventsToEmit = append(eventsToEmit, &offer)
})
var newEvents fn.Option[RbfEvent]
if len(eventsToEmit) > 0 {
newEvents = fn.Some(RbfEvent{
InternalEvent: eventsToEmit,
})
}
// We transition to the ChannelFlushing state, where we await
// the ChannelFlushed event.
return &CloseStateTransition{
NextState: &ChannelFlushing{
IdealFeeRate: s.IdealFeeRate,
ShutdownScripts: ShutdownScripts{
LocalDeliveryScript: s.LocalDeliveryScript, //nolint:ll
RemoteDeliveryScript: msg.ShutdownScript, //nolint:ll
},
},
NewEvents: newEvents,
}, nil
// If we get this message, then this means that we were finally able to
// send out shutdown after receiving it from the remote party. We'll
// now transition directly to the ChannelFlushing state.
case *ShutdownComplete:
chancloserLog.Infof("ChannelPoint(%v): waiting for channel to "+
"be flushed...", env.ChanPoint)
// If the channel is *already* flushed, and the close is
// already in progress, then we can skip the flushing state and
// go straight into negotiation, as this is the RBF loop.
var eventsToEmit []ProtocolEvent
finalBalances := env.ChanObserver.FinalBalances().UnwrapOr(
unknownBalance,
)
if finalBalances != unknownBalance {
channelFlushed := ProtocolEvent(&ChannelFlushed{
ShutdownBalances: finalBalances,
})
eventsToEmit = append(eventsToEmit, channelFlushed)
}
// If we received a remote offer early from the remote party,
// then we'll add that to the set of internal events to emit.
s.EarlyRemoteOffer.WhenSome(func(offer OfferReceivedEvent) {
eventsToEmit = append(eventsToEmit, &offer)
})
var newEvents fn.Option[RbfEvent]
if len(eventsToEmit) > 0 {
newEvents = fn.Some(RbfEvent{
InternalEvent: eventsToEmit,
})
}
// From here, we'll transition to the channel flushing state.
// We'll stay here until we receive the ChannelFlushed event.
return &CloseStateTransition{
NextState: &ChannelFlushing{
IdealFeeRate: s.IdealFeeRate,
ShutdownScripts: s.ShutdownScripts,
},
NewEvents: newEvents,
}, nil
// Any other messages in this state will result in an error, as this is
// an undefined state transition.
default:
return nil, fmt.Errorf("%w: received %T while in "+
"ShutdownPending", ErrInvalidStateTransition, msg)
}
}
// ProcessEvent takes a new protocol event, and figures out if we can
// transition to the next state, or just loop back upon ourself. If we receive
// a ShutdownReceived event, then we'll stay in the ChannelFlushing state, as
// we haven't yet fully cleared the channel. Otherwise, we can move to the
// CloseReady state which'll being the channel closing process.
func (c *ChannelFlushing) ProcessEvent(event ProtocolEvent, env *Environment,
) (*CloseStateTransition, error) {
switch msg := event.(type) {
// If we get a confirmation, then a prior transaction we broadcasted
// has confirmed, so we can move to our terminal state early.
case *SpendEvent:
return &CloseStateTransition{
NextState: &CloseFin{
ConfirmedTx: msg.Tx,
},
}, nil
// If we get an OfferReceived event, then the channel is flushed from
// the PoV of the remote party. However, due to propagation delay or
// concurrency, we may not have received the ChannelFlushed event yet.
// In this case, we'll stash the event and wait for the ChannelFlushed
// event.
case *OfferReceivedEvent:
chancloserLog.Infof("ChannelPoint(%v): received remote offer "+
"early, stashing...", env.ChanPoint)
c.EarlyRemoteOffer = fn.Some(*msg)
// We'll perform a noop update so we can wait for the actual
// channel flushed event.
return &CloseStateTransition{
NextState: c,
}, nil
// If we receive the ChannelFlushed event, then the coast is clear so
// we'll now morph into the dual peer state so we can handle any
// messages needed to drive forward the close process.
case *ChannelFlushed:
// Both the local and remote losing negotiation needs the terms
// we'll be using to close the channel, so we'll create them
// here.
closeTerms := CloseChannelTerms{
ShutdownScripts: c.ShutdownScripts,
ShutdownBalances: msg.ShutdownBalances,
}
chancloserLog.Infof("ChannelPoint(%v): channel flushed! "+
"proceeding with co-op close", env.ChanPoint)
// Now that the channel has been flushed, we'll mark on disk
// that we're approaching the point of no return where we'll
// send a new signature to the remote party.
//
// TODO(roasbeef): doesn't actually matter if initiator here?
if msg.FreshFlush {
err := env.ChanObserver.MarkCoopBroadcasted(nil, true)
if err != nil {
return nil, err
}
}
// If an ideal fee rate was specified, then we'll use that,
// otherwise we'll fall back to the default value given in the
// env.
idealFeeRate := c.IdealFeeRate.UnwrapOr(env.DefaultFeeRate)
// We'll then use that fee rate to determine the absolute fee
// we'd propose.
localTxOut, remoteTxOut := closeTerms.DeriveCloseTxOuts()
absoluteFee := env.FeeEstimator.EstimateFee(
env.ChanType, localTxOut, remoteTxOut,
idealFeeRate.FeePerKWeight(),
)
chancloserLog.Infof("ChannelPoint(%v): using ideal_fee=%v, "+
"absolute_fee=%v", env.ChanPoint, idealFeeRate,
absoluteFee)
var (
internalEvents []ProtocolEvent
newEvents fn.Option[RbfEvent]
)
// If we received a remote offer early from the remote party,
// then we'll add that to the set of internal events to emit.
c.EarlyRemoteOffer.WhenSome(func(offer OfferReceivedEvent) {
internalEvents = append(internalEvents, &offer)
})
// Only if we have enough funds to pay for the fees do we need
// to emit a localOfferSign event.
//
// TODO(roasbeef): also only proceed if was higher than fee in
// last round?
if closeTerms.LocalCanPayFees(absoluteFee) {
// Each time we go into this negotiation flow, we'll
// kick off our local state with a new close attempt.
// So we'll emit a internal event to drive forward that
// part of the state.
localOfferSign := ProtocolEvent(&SendOfferEvent{
TargetFeeRate: idealFeeRate,
})
internalEvents = append(internalEvents, localOfferSign)
} else {
chancloserLog.Infof("ChannelPoint(%v): unable to pay "+
"fees with local balance, skipping "+
"closing_complete", env.ChanPoint)
}
if len(internalEvents) > 0 {
newEvents = fn.Some(RbfEvent{
InternalEvent: internalEvents,
})
}
return &CloseStateTransition{
NextState: &ClosingNegotiation{
PeerState: lntypes.Dual[AsymmetricPeerState]{
Local: &LocalCloseStart{
CloseChannelTerms: &closeTerms,
},
Remote: &RemoteCloseStart{
CloseChannelTerms: &closeTerms,
},
},
CloseChannelTerms: &closeTerms,
},
NewEvents: newEvents,
}, nil
default:
return nil, fmt.Errorf("%w: received %T while in "+
"ChannelFlushing", ErrInvalidStateTransition, msg)
}
}
// processNegotiateEvent is a helper function that processes a new event to
// local channel state once we're in the ClosingNegotiation state.
func processNegotiateEvent(c *ClosingNegotiation, event ProtocolEvent,
env *Environment, chanPeer lntypes.ChannelParty,
) (*CloseStateTransition, error) {
targetPeerState := c.PeerState.GetForParty(chanPeer)
// Drive forward the remote state based on the next event.
transition, err := targetPeerState.ProcessEvent(
event, env,
)
if err != nil {
return nil, err
}
nextPeerState, ok := transition.NextState.(AsymmetricPeerState) //nolint:ll
if !ok {
return nil, fmt.Errorf("expected %T to be "+
"AsymmetricPeerState", transition.NextState)
}
// Make a copy of the input state, then update the peer state of the
// proper party.
newPeerState := *c
newPeerState.PeerState.SetForParty(chanPeer, nextPeerState)
return &CloseStateTransition{
NextState: &newPeerState,
NewEvents: transition.NewEvents,
}, nil
}
// updateAndValidateCloseTerms is a helper function that validates examines the
// incoming event, and decide if we need to update the remote party's address,
// or reject it if it doesn't include our latest address.
func (c *ClosingNegotiation) updateAndValidateCloseTerms(event ProtocolEvent,
) error {
assertLocalScriptMatches := func(localScriptInMsg []byte) error {
if !bytes.Equal(
c.LocalDeliveryScript, localScriptInMsg,
) {
return fmt.Errorf("%w: remote party sent wrong "+
"script, expected %x, got %x",
ErrWrongLocalScript, c.LocalDeliveryScript,
localScriptInMsg,
)
}
return nil
}
switch msg := event.(type) {
// The remote party is sending us a new request to counter sign their
// version of the commitment transaction.
case *OfferReceivedEvent:
// Make sure that they're sending our local script, and not
// something else.
err := assertLocalScriptMatches(msg.SigMsg.CloseeScript)
if err != nil {
return err
}
oldRemoteAddr := c.RemoteDeliveryScript
newRemoteAddr := msg.SigMsg.CloserScript
// If they're sending a new script, then we'll update to the new
// one.
if !bytes.Equal(oldRemoteAddr, newRemoteAddr) {
c.RemoteDeliveryScript = newRemoteAddr
}
// The remote party responded to our sig request with a signature for
// our version of the commitment transaction.
case *LocalSigReceived:
// Make sure that they're sending our local script, and not
// something else.
err := assertLocalScriptMatches(msg.SigMsg.CloserScript)
if err != nil {
return err
}
return nil
}
return nil
}
// ProcessEvent drives forward the composite states for the local and remote
// party in response to new events. From this state, we'll continue to drive
// forward the local and remote states until we arrive at the StateFin stage,
// or we loop back up to the ShutdownPending state.
func (c *ClosingNegotiation) ProcessEvent(event ProtocolEvent, env *Environment,
) (*CloseStateTransition, error) {
// There're two classes of events that can break us out of this state:
// we receive a confirmation event, or we receive a signal to restart
// the co-op close process.
switch msg := event.(type) {
// Ignore any potential duplicate channel flushed events.
case *ChannelFlushed:
return &CloseStateTransition{
NextState: c,
}, nil
// If we get a confirmation, then the spend request we issued when we
// were leaving the ChannelFlushing state has been confirmed. We'll
// now transition to the StateFin state.
case *SpendEvent:
return &CloseStateTransition{
NextState: &CloseFin{
ConfirmedTx: msg.Tx,
},
}, nil
}
// At this point, we know its a new signature message. We'll validate,
// and maybe update the set of close terms based on what we receive. We
// might update the remote party's address for example.
if err := c.updateAndValidateCloseTerms(event); err != nil {
return nil, fmt.Errorf("event violates close terms: %w", err)
}
shouldRouteTo := func(party lntypes.ChannelParty) bool {
state := c.PeerState.GetForParty(party)
if state == nil {
return false
}
return state.ShouldRouteTo(event)
}
// If we get to this point, then we have an event that'll drive forward
// the negotiation process. Based on the event, we'll figure out which
// state we'll be modifying.
switch {
case shouldRouteTo(lntypes.Local):
chancloserLog.Infof("ChannelPoint(%v): routing %T to local "+
"chan state", env.ChanPoint, event)
// Drive forward the local state based on the next event.
return processNegotiateEvent(c, event, env, lntypes.Local)
case shouldRouteTo(lntypes.Remote):
chancloserLog.Infof("ChannelPoint(%v): routing %T to remote "+
"chan state", env.ChanPoint, event)
// Drive forward the remote state based on the next event.
return processNegotiateEvent(c, event, env, lntypes.Remote)
}
return nil, fmt.Errorf("%w: received %T while in %v",
ErrInvalidStateTransition, event, c)
}
// newSigTlv is a helper function that returns a new optional TLV sig field for
// the parametrized tlv.TlvType value.
func newSigTlv[T tlv.TlvType](s lnwire.Sig) tlv.OptionalRecordT[T, lnwire.Sig] {
return tlv.SomeRecordT(tlv.NewRecordT[T](s))
}
// ProcessEvent implements the event processing to kick off the process of
// obtaining a new (possibly RBF'd) signature for our commitment transaction.
func (l *LocalCloseStart) ProcessEvent(event ProtocolEvent, env *Environment,
) (*CloseStateTransition, error) {
switch msg := event.(type) { //nolint:gocritic
// If we receive a SendOfferEvent, then we'll use the specified fee
// rate to generate for the closing transaction with our ideal fee
// rate.
case *SendOfferEvent:
// given the state of the local/remote outputs.
// First, we'll figure out the absolute fee rate we should pay
localTxOut, remoteTxOut := l.DeriveCloseTxOuts()
absoluteFee := env.FeeEstimator.EstimateFee(
env.ChanType, localTxOut, remoteTxOut,
msg.TargetFeeRate.FeePerKWeight(),
)
// If we can't actually pay for fees here, then we'll just do a
// noop back to the same state to await a new fee rate.
if !l.LocalCanPayFees(absoluteFee) {
chancloserLog.Infof("ChannelPoint(%v): unable to pay "+
"fee=%v with local balance %v, skipping "+
"closing_complete", env.ChanPoint, absoluteFee,
l.LocalBalance)
return &CloseStateTransition{
NextState: &CloseErr{
CloseChannelTerms: l.CloseChannelTerms,
Party: lntypes.Local,
ErrState: NewErrStateCantPayForFee(
l.LocalBalance.ToSatoshis(),
absoluteFee,
),
},
}, nil
}
// Now that we know what fee we want to pay, we'll create a new
// signature over our co-op close transaction. For our
// proposals, we'll just always use the known RBF sequence
// value.
localScript := l.LocalDeliveryScript
rawSig, closeTx, closeBalance, err := env.CloseSigner.CreateCloseProposal( //nolint:ll
absoluteFee, localScript, l.RemoteDeliveryScript,
lnwallet.WithCustomSequence(mempool.MaxRBFSequence),
lnwallet.WithCustomPayer(lntypes.Local),
)
if err != nil {
return nil, err
}
wireSig, err := lnwire.NewSigFromSignature(rawSig)
if err != nil {
return nil, err
}
chancloserLog.Infof("closing w/ local_addr=%x, "+
"remote_addr=%x, fee=%v", localScript[:],
l.RemoteDeliveryScript[:], absoluteFee)
chancloserLog.Infof("proposing closing_tx=%v",
spew.Sdump(closeTx))
// Now that we have our signature, we'll set the proper
// closingSigs field based on if the remote party's output is
// dust or not.
var closingSigs lnwire.ClosingSigs
switch {
// If the remote party's output is dust, then we'll set the
// CloserNoClosee field.
case remoteTxOut == nil:
closingSigs.CloserNoClosee = newSigTlv[tlv.TlvType1](
wireSig,
)
// If after paying for fees, our balance is below dust, then
// we'll set the NoCloserClosee field.
case closeBalance < lnwallet.DustLimitForSize(len(localScript)):
closingSigs.NoCloserClosee = newSigTlv[tlv.TlvType2](
wireSig,
)
// Otherwise, we'll set the CloserAndClosee field.
//
// TODO(roasbeef): should actually set both??
default:
closingSigs.CloserAndClosee = newSigTlv[tlv.TlvType3](
wireSig,
)
}
// Now that we have our sig, we'll emit a daemon event to send
// it to the remote party, then transition to the
// LocalOfferSent state.
//
// TODO(roasbeef): type alias for protocol event
sendEvent := protofsm.DaemonEventSet{&protofsm.SendMsgEvent[ProtocolEvent]{ //nolint:ll
TargetPeer: env.ChanPeer,
Msgs: []lnwire.Message{&lnwire.ClosingComplete{
ChannelID: env.ChanID,
CloserScript: l.LocalDeliveryScript,
CloseeScript: l.RemoteDeliveryScript,
FeeSatoshis: absoluteFee,
LockTime: env.BlockHeight,
ClosingSigs: closingSigs,
}},
}}
chancloserLog.Infof("ChannelPoint(%v): sending closing sig "+
"to remote party, fee_sats=%v", env.ChanPoint,
absoluteFee)
return &CloseStateTransition{
NextState: &LocalOfferSent{
ProposedFee: absoluteFee,
ProposedFeeRate: msg.TargetFeeRate,
LocalSig: wireSig,
CloseChannelTerms: l.CloseChannelTerms,
},
NewEvents: fn.Some(RbfEvent{
ExternalEvents: sendEvent,
}),
}, nil
}
return nil, fmt.Errorf("%w: received %T while in LocalCloseStart",
ErrInvalidStateTransition, event)
}
// extractSig extracts the expected signature from the closing sig message.
// Only one of them should actually be populated as the closing sig message is
// sent in response to a ClosingComplete message, it should only sign the same
// version of the co-op close tx as the sender did.
func extractSig(msg lnwire.ClosingSig) fn.Result[lnwire.Sig] {
// First, we'll validate that only one signature is included in their
// response to our initial offer. If not, then we'll exit here, and
// trigger a recycle of the connection.
sigInts := []bool{
msg.CloserNoClosee.IsSome(), msg.NoCloserClosee.IsSome(),
msg.CloserAndClosee.IsSome(),
}
numSigs := fn.Foldl(0, sigInts, func(acc int, sigInt bool) int {
if sigInt {
return acc + 1
}
return acc
})
if numSigs != 1 {
return fn.Errf[lnwire.Sig]("%w: only one sig should be set, "+
"got %v", ErrTooManySigs, numSigs)
}
// The final sig is the one that's actually set.
sig := msg.CloserAndClosee.ValOpt().Alt(
msg.NoCloserClosee.ValOpt(),
).Alt(
msg.CloserNoClosee.ValOpt(),
)
return fn.NewResult(sig.UnwrapOrErr(ErrNoSig))
}
// ProcessEvent implements the state transition function for the
// LocalOfferSent state. In this state, we'll wait for the remote party to
// send a close_signed message which gives us the ability to broadcast a new
// co-op close transaction.
func (l *LocalOfferSent) ProcessEvent(event ProtocolEvent, env *Environment,
) (*CloseStateTransition, error) {
switch msg := event.(type) { //nolint:gocritic
// If we receive a LocalSigReceived event, then we'll attempt to
// validate the signature from the remote party. If valid, then we can
// broadcast the transaction, and transition to the ClosePending state.
case *LocalSigReceived:
// Extract and validate that only one sig field is set.
sig, err := extractSig(msg.SigMsg).Unpack()
if err != nil {
return nil, err
}
remoteSig, err := sig.ToSignature()
if err != nil {
return nil, err
}
localSig, err := l.LocalSig.ToSignature()
if err != nil {
return nil, err
}
// Now that we have their signature, we'll attempt to validate
// it, then extract a valid closing signature from it.
closeTx, _, err := env.CloseSigner.CompleteCooperativeClose(
localSig, remoteSig, l.LocalDeliveryScript,
l.RemoteDeliveryScript, l.ProposedFee,
lnwallet.WithCustomSequence(mempool.MaxRBFSequence),
lnwallet.WithCustomPayer(lntypes.Local),
)
if err != nil {
return nil, err
}
// As we're about to broadcast a new version of the co-op close
// transaction, we'll mark again as broadcast, but with this
// variant of the co-op close tx.
err = env.ChanObserver.MarkCoopBroadcasted(closeTx, true)
if err != nil {
return nil, err
}
broadcastEvent := protofsm.DaemonEventSet{&protofsm.BroadcastTxn{ //nolint:ll
Tx: closeTx,
Label: labels.MakeLabel(
labels.LabelTypeChannelClose, &env.Scid,
),
}}
chancloserLog.Infof("ChannelPoint(%v): received sig from "+
"remote party, broadcasting: tx=%v", env.ChanPoint,
lnutils.SpewLogClosure(closeTx),
)
return &CloseStateTransition{
NextState: &ClosePending{
CloseTx: closeTx,
FeeRate: l.ProposedFeeRate,
CloseChannelTerms: l.CloseChannelTerms,
Party: lntypes.Local,
},
NewEvents: fn.Some(protofsm.EmittedEvent[ProtocolEvent]{
ExternalEvents: broadcastEvent,
}),
}, nil
}
return nil, fmt.Errorf("%w: received %T while in LocalOfferSent",
ErrInvalidStateTransition, event)
}
// ProcessEvent implements the state transition function for the
// RemoteCloseStart. In this state, we'll wait for the remote party to send a
// closing_complete message. Assuming they can pay for the fees, we'll sign it
// ourselves, then transition to the next state of ClosePending.
func (l *RemoteCloseStart) ProcessEvent(event ProtocolEvent, env *Environment,
) (*CloseStateTransition, error) {
switch msg := event.(type) { //nolint:gocritic
// If we receive a OfferReceived event, we'll make sure they can
// actually pay for the fee. If so, then we'll counter sign and
// transition to a terminal state.
case *OfferReceivedEvent:
// To start, we'll perform some basic validation of the sig
// message they've sent. We'll validate that the remote party
// actually has enough fees to pay the closing fees.
if !l.RemoteCanPayFees(msg.SigMsg.FeeSatoshis) {
return nil, fmt.Errorf("%w: %v vs %v",
ErrRemoteCannotPay,
msg.SigMsg.FeeSatoshis,
l.RemoteBalance.ToSatoshis())
}
// With the basic sanity checks out of the way, we'll now
// figure out which signature that we'll attempt to sign
// against.
var (
remoteSig input.Signature
noClosee bool
)
switch {
// If our balance is dust, then we expect the CloserNoClosee
// sig to be set.
case l.LocalAmtIsDust():
if msg.SigMsg.CloserNoClosee.IsNone() {
return nil, ErrCloserNoClosee
}
msg.SigMsg.CloserNoClosee.WhenSomeV(func(s lnwire.Sig) {
remoteSig, _ = s.ToSignature()
noClosee = true
})
// Otherwise, we'll assume that CloseAndClosee is set.
//
// TODO(roasbeef): NoCloserClosee, but makes no sense?
default:
if msg.SigMsg.CloserAndClosee.IsNone() {
return nil, ErrCloserAndClosee
}
msg.SigMsg.CloserAndClosee.WhenSomeV(func(s lnwire.Sig) { //nolint:ll
remoteSig, _ = s.ToSignature()
})
}
chanOpts := []lnwallet.ChanCloseOpt{
lnwallet.WithCustomSequence(mempool.MaxRBFSequence),
lnwallet.WithCustomLockTime(msg.SigMsg.LockTime),
lnwallet.WithCustomPayer(lntypes.Remote),
}
chancloserLog.Infof("responding to close w/ local_addr=%x, "+
"remote_addr=%x, fee=%v",
l.LocalDeliveryScript[:], l.RemoteDeliveryScript[:],
msg.SigMsg.FeeSatoshis)
// Now that we have the remote sig, we'll sign the version they
// signed, then attempt to complete the cooperative close
// process.
//
// TODO(roasbeef): need to be able to omit an output when
// signing based on the above, as closing opt
rawSig, _, _, err := env.CloseSigner.CreateCloseProposal(
msg.SigMsg.FeeSatoshis, l.LocalDeliveryScript,
l.RemoteDeliveryScript, chanOpts...,
)
if err != nil {
return nil, err
}
wireSig, err := lnwire.NewSigFromSignature(rawSig)
if err != nil {
return nil, err
}
localSig, err := wireSig.ToSignature()
if err != nil {
return nil, err
}
// With our signature created, we'll now attempt to finalize the
// close process.
closeTx, _, err := env.CloseSigner.CompleteCooperativeClose(
localSig, remoteSig, l.LocalDeliveryScript,
l.RemoteDeliveryScript, msg.SigMsg.FeeSatoshis,
chanOpts...,
)
if err != nil {
return nil, err
}
chancloserLog.Infof("ChannelPoint(%v): received sig (fee=%v "+
"sats) from remote party, signing new tx=%v",
env.ChanPoint, msg.SigMsg.FeeSatoshis,
lnutils.SpewLogClosure(closeTx),
)
var closingSigs lnwire.ClosingSigs
if noClosee {
closingSigs.CloserNoClosee = newSigTlv[tlv.TlvType1](
wireSig,
)
} else {
closingSigs.CloserAndClosee = newSigTlv[tlv.TlvType3](
wireSig,
)
}
// As we're about to broadcast a new version of the co-op close
// transaction, we'll mark again as broadcast, but with this
// variant of the co-op close tx.
//
// TODO(roasbeef): db will only store one instance, store both?
err = env.ChanObserver.MarkCoopBroadcasted(closeTx, false)
if err != nil {
return nil, err
}
// As we transition, we'll omit two events: one to broadcast
// the transaction, and the other to send our ClosingSig
// message to the remote party.
sendEvent := &protofsm.SendMsgEvent[ProtocolEvent]{
TargetPeer: env.ChanPeer,
Msgs: []lnwire.Message{&lnwire.ClosingSig{
ChannelID: env.ChanID,
CloserScript: l.RemoteDeliveryScript,
CloseeScript: l.LocalDeliveryScript,
FeeSatoshis: msg.SigMsg.FeeSatoshis,
LockTime: msg.SigMsg.LockTime,
ClosingSigs: closingSigs,
}},
}
broadcastEvent := &protofsm.BroadcastTxn{
Tx: closeTx,
Label: labels.MakeLabel(
labels.LabelTypeChannelClose, &env.Scid,
),
}
daemonEvents := protofsm.DaemonEventSet{
sendEvent, broadcastEvent,
}
// We'll also compute the final fee rate that the remote party
// paid based off the absolute fee and the size of the closing
// transaction.
vSize := mempool.GetTxVirtualSize(btcutil.NewTx(closeTx))
feeRate := chainfee.SatPerVByte(
int64(msg.SigMsg.FeeSatoshis) / vSize,
)
// Now that we've extracted the signature, we'll transition to
// the next state where we'll sign+broadcast the sig.
return &CloseStateTransition{
NextState: &ClosePending{
CloseTx: closeTx,
FeeRate: feeRate,
CloseChannelTerms: l.CloseChannelTerms,
Party: lntypes.Remote,
},
NewEvents: fn.Some(protofsm.EmittedEvent[ProtocolEvent]{
ExternalEvents: daemonEvents,
}),
}, nil
}
return nil, fmt.Errorf("%w: received %T while in RemoteCloseStart",
ErrInvalidStateTransition, event)
}
// ProcessEvent is a semi-terminal state in the rbf-coop close state machine.
// In this state, we're waiting for either a confirmation, or for either side
// to attempt to create a new RBF'd co-op close transaction.
func (c *ClosePending) ProcessEvent(event ProtocolEvent, env *Environment,
) (*CloseStateTransition, error) {
switch msg := event.(type) {
// If we can a spend while waiting for the close, then we'll go to our
// terminal state.
case *SpendEvent:
return &CloseStateTransition{
NextState: &CloseFin{
ConfirmedTx: msg.Tx,
},
}, nil
// If we get a send offer event in this state, then we're doing a state
// transition to the LocalCloseStart state, so we can sign a new closing
// tx.
case *SendOfferEvent:
return &CloseStateTransition{
NextState: &LocalCloseStart{
CloseChannelTerms: c.CloseChannelTerms,
},
NewEvents: fn.Some(protofsm.EmittedEvent[ProtocolEvent]{
InternalEvent: []ProtocolEvent{msg},
}),
}, nil
// If we get an offer received event, then we're doing a state
// transition to the RemoteCloseStart, as the remote peer wants to sign
// a new closing tx.
case *OfferReceivedEvent:
return &CloseStateTransition{
NextState: &RemoteCloseStart{
CloseChannelTerms: c.CloseChannelTerms,
},
NewEvents: fn.Some(protofsm.EmittedEvent[ProtocolEvent]{
InternalEvent: []ProtocolEvent{msg},
}),
}, nil
default:
return &CloseStateTransition{
NextState: c,
}, nil
}
}
// ProcessEvent is the event processing for out terminal state. In this state,
// we just keep looping back on ourselves.
func (c *CloseFin) ProcessEvent(event ProtocolEvent, env *Environment,
) (*CloseStateTransition, error) {
return &CloseStateTransition{
NextState: c,
}, nil
}
// ProcessEvent is a semi-terminal state in the rbf-coop close state machine.
// In this state, we hit a validation error in an earlier state, so we'll remain
// in this state for the user to examine. We may also process new requests to
// continue the state machine.
func (c *CloseErr) ProcessEvent(event ProtocolEvent, env *Environment,
) (*CloseStateTransition, error) {
switch msg := event.(type) {
// If we get a send offer event in this state, then we're doing a state
// transition to the LocalCloseStart state, so we can sign a new closing
// tx.
case *SendOfferEvent:
return &CloseStateTransition{
NextState: &LocalCloseStart{
CloseChannelTerms: c.CloseChannelTerms,
},
NewEvents: fn.Some(protofsm.EmittedEvent[ProtocolEvent]{
InternalEvent: []ProtocolEvent{msg},
}),
}, nil
// If we get an offer received event, then we're doing a state
// transition to the RemoteCloseStart, as the remote peer wants to sign
// a new closing tx.
case *OfferReceivedEvent:
return &CloseStateTransition{
NextState: &RemoteCloseStart{
CloseChannelTerms: c.CloseChannelTerms,
},
NewEvents: fn.Some(protofsm.EmittedEvent[ProtocolEvent]{
InternalEvent: []ProtocolEvent{msg},
}),
}, nil
default:
return &CloseStateTransition{
NextState: c,
}, nil
}
}
package chanfunding
import (
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
)
// NewShimIntent creates a new ShimIntent. This is only used for testing.
func NewShimIntent(localAmt, remoteAmt btcutil.Amount,
localKey *keychain.KeyDescriptor, remoteKey *btcec.PublicKey,
chanPoint *wire.OutPoint, thawHeight uint32, musig2 bool) *ShimIntent {
return &ShimIntent{
localFundingAmt: localAmt,
remoteFundingAmt: remoteAmt,
localKey: localKey,
remoteKey: remoteKey,
chanPoint: chanPoint,
thawHeight: thawHeight,
musig2: musig2,
}
}
// ShimIntent is an intent created by the CannedAssembler which represents a
// funding output to be created that was constructed outside the wallet. This
// might be used when a hardware wallet, or a channel factory is the entity
// crafting the funding transaction, and not lnd.
type ShimIntent struct {
// localFundingAmt is the final amount we put into the funding output.
localFundingAmt btcutil.Amount
// remoteFundingAmt is the final amount the remote party put into the
// funding output.
remoteFundingAmt btcutil.Amount
// localKey is our multi-sig key.
localKey *keychain.KeyDescriptor
// remoteKey is the remote party's multi-sig key.
remoteKey *btcec.PublicKey
// chanPoint is the final channel point for the to be created channel.
chanPoint *wire.OutPoint
// thawHeight, if non-zero is the height where this channel will become
// a normal channel. Until this height, it's considered frozen, so it
// can only be cooperatively closed by the responding party.
thawHeight uint32
// musig2 determines if the funding output should use musig2 to
// generate an aggregate key to use as the taproot-native multi-sig
// output.
musig2 bool
// tapscriptRoot is the root of the tapscript tree that will be used to
// create the funding output. This field will only be utilized if the
// MuSig2 flag above is set to true.
//
// TODO(roasbeef): fold above into new chan type? sum type like thing,
// includes the tapscript root, etc
tapscriptRoot fn.Option[chainhash.Hash]
}
// FundingOutput returns the witness script, and the output that creates the
// funding output.
//
// NOTE: This method satisfies the chanfunding.Intent interface.
func (s *ShimIntent) FundingOutput() ([]byte, *wire.TxOut, error) {
if s.localKey == nil || s.remoteKey == nil {
return nil, nil, fmt.Errorf("unable to create witness " +
"script, no funding keys")
}
totalAmt := s.localFundingAmt + s.remoteFundingAmt
// If musig2 is active, then we'll return a single aggregated key
// rather than using the "existing" funding script.
if s.musig2 {
// Similar to the existing p2wsh script, we'll always ensure
// the keys are sorted before use.
return input.GenTaprootFundingScript(
s.localKey.PubKey, s.remoteKey, int64(totalAmt),
s.tapscriptRoot,
)
}
return input.GenFundingPkScript(
s.localKey.PubKey.SerializeCompressed(),
s.remoteKey.SerializeCompressed(),
int64(totalAmt),
)
}
// TaprootInternalKey may return the internal key for a MuSig2 funding output,
// but only if this is actually a MuSig2 channel.
func (s *ShimIntent) TaprootInternalKey() fn.Option[*btcec.PublicKey] {
if !s.musig2 {
return fn.None[*btcec.PublicKey]()
}
// Similar to the existing p2wsh script, we'll always ensure the keys
// are sorted before use. Since we're only interested in the internal
// key, we don't need to take into account any tapscript root.
//
// We ignore the error here as this is only called after FundingOutput
// is called.
combinedKey, _, _, _ := musig2.AggregateKeys(
[]*btcec.PublicKey{s.localKey.PubKey, s.remoteKey}, true,
)
return fn.Some(combinedKey.PreTweakedKey)
}
// Cancel allows the caller to cancel a funding Intent at any time. This will
// return any resources such as coins back to the eligible pool to be used in
// order channel fundings.
//
// NOTE: This method satisfies the chanfunding.Intent interface.
func (s *ShimIntent) Cancel() {
}
// LocalFundingAmt is the amount we put into the channel. This may differ from
// the local amount requested, as depending on coin selection, we may bleed
// from of that LocalAmt into fees to minimize change.
//
// NOTE: This method satisfies the chanfunding.Intent interface.
func (s *ShimIntent) LocalFundingAmt() btcutil.Amount {
return s.localFundingAmt
}
// RemoteFundingAmt is the amount the remote party put into the channel.
//
// NOTE: This method satisfies the chanfunding.Intent interface.
func (s *ShimIntent) RemoteFundingAmt() btcutil.Amount {
return s.remoteFundingAmt
}
// ChanPoint returns the final outpoint that will create the funding output
// described above.
//
// NOTE: This method satisfies the chanfunding.Intent interface.
func (s *ShimIntent) ChanPoint() (*wire.OutPoint, error) {
if s.chanPoint == nil {
return nil, fmt.Errorf("chan point unknown, funding output " +
"not constructed")
}
return s.chanPoint, nil
}
// ThawHeight returns the height where this channel goes back to being a normal
// channel.
func (s *ShimIntent) ThawHeight() uint32 {
return s.thawHeight
}
// Inputs returns all inputs to the final funding transaction that we
// know about. For the ShimIntent this will always be none, since it is funded
// externally.
func (s *ShimIntent) Inputs() []wire.OutPoint {
return nil
}
// Outputs returns all outputs of the final funding transaction that we
// know about. Since this is an externally funded channel, the channel output
// is the only known one.
func (s *ShimIntent) Outputs() []*wire.TxOut {
_, txOut, err := s.FundingOutput()
if err != nil {
log.Warnf("Unable to find funding output for shim intent: %v",
err)
// Failed finding funding output, return empty list of known
// outputs.
return nil
}
return []*wire.TxOut{txOut}
}
// FundingKeys couples our multi-sig key along with the remote party's key.
type FundingKeys struct {
// LocalKey is our multi-sig key.
LocalKey *keychain.KeyDescriptor
// RemoteKey is the multi-sig key of the remote party.
RemoteKey *btcec.PublicKey
}
// MultiSigKeys returns the committed multi-sig keys, but only if they've been
// specified/provided.
func (s *ShimIntent) MultiSigKeys() (*FundingKeys, error) {
if s.localKey == nil || s.remoteKey == nil {
return nil, fmt.Errorf("unknown funding keys")
}
return &FundingKeys{
LocalKey: s.localKey,
RemoteKey: s.remoteKey,
}, nil
}
// A compile-time check to ensure ShimIntent adheres to the Intent interface.
var _ Intent = (*ShimIntent)(nil)
// CannedAssembler is a type of chanfunding.Assembler wherein the funding
// transaction is constructed outside of lnd, and may already exist. This
// Assembler serves as a shim which gives the funding flow the only thing it
// actually needs to proceed: the channel point.
type CannedAssembler struct {
// fundingAmt is the total amount of coins in the funding output.
fundingAmt btcutil.Amount
// localKey is our multi-sig key.
localKey *keychain.KeyDescriptor
// remoteKey is the remote party's multi-sig key.
remoteKey *btcec.PublicKey
// chanPoint is the final channel point for the to be created channel.
chanPoint wire.OutPoint
// initiator indicates if we're the initiator or the channel or not.
initiator bool
// thawHeight, if non-zero is the height where this channel will become
// a normal channel. Until this height, it's considered frozen, so it
// can only be cooperatively closed by the responding party.
thawHeight uint32
// musig2 determines if the funding output should use musig2 to
// generate an aggregate key to use as the taproot-native multi-sig
// output.
musig2 bool
}
// NewCannedAssembler creates a new CannedAssembler from the material required
// to construct a funding output and channel point.
//
// TODO(roasbeef): pass in chan type instead?
func NewCannedAssembler(thawHeight uint32, chanPoint wire.OutPoint,
fundingAmt btcutil.Amount, localKey *keychain.KeyDescriptor,
remoteKey *btcec.PublicKey, initiator, musig2 bool) *CannedAssembler {
return &CannedAssembler{
initiator: initiator,
localKey: localKey,
remoteKey: remoteKey,
fundingAmt: fundingAmt,
chanPoint: chanPoint,
thawHeight: thawHeight,
musig2: musig2,
}
}
// ProvisionChannel creates a new ShimIntent given the passed funding Request.
// The returned intent is immediately able to provide the channel point and
// funding output as they've already been created outside lnd.
//
// NOTE: This method satisfies the chanfunding.Assembler interface.
func (c *CannedAssembler) ProvisionChannel(req *Request) (Intent, error) {
// We'll exit out if SubtractFees is set as the funding transaction has
// already been assembled, so we don't influence coin selection.
if req.SubtractFees {
return nil, fmt.Errorf("SubtractFees ignored, funding " +
"transaction is frozen")
}
// We'll exit out if FundUpToMaxAmt or MinFundAmt is set as the funding
// transaction has already been assembled, so we don't influence coin
// selection.
if req.FundUpToMaxAmt != 0 || req.MinFundAmt != 0 {
return nil, fmt.Errorf("FundUpToMaxAmt and MinFundAmt " +
"ignored, funding transaction is frozen")
}
intent := &ShimIntent{
localKey: c.localKey,
remoteKey: c.remoteKey,
chanPoint: &c.chanPoint,
thawHeight: c.thawHeight,
musig2: c.musig2,
}
if c.initiator {
intent.localFundingAmt = c.fundingAmt
} else {
intent.remoteFundingAmt = c.fundingAmt
}
// A simple sanity check to ensure the provisioned request matches the
// re-made shim intent.
if req.LocalAmt+req.RemoteAmt != c.fundingAmt {
return nil, fmt.Errorf("intent doesn't match canned "+
"assembler: local_amt=%v, remote_amt=%v, funding_amt=%v",
req.LocalAmt, req.RemoteAmt, c.fundingAmt)
}
return intent, nil
}
// A compile-time assertion to ensure CannedAssembler meets the Assembler
// interface.
var _ Assembler = (*CannedAssembler)(nil)
package chanfunding
import (
"errors"
"fmt"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcwallet/wallet"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)
// ErrInsufficientFunds is a type matching the error interface which is
// returned when coin selection for a new funding transaction fails due to
// having an insufficient amount of confirmed funds.
type ErrInsufficientFunds struct {
amountAvailable btcutil.Amount
amountSelected btcutil.Amount
}
// Error returns a human-readable string describing the error.
func (e *ErrInsufficientFunds) Error() string {
return fmt.Sprintf("not enough witness outputs to create funding "+
"transaction, need %v only have %v available",
e.amountAvailable, e.amountSelected)
}
// errUnsupportedInput is a type matching the error interface, which is returned
// when trying to calculate the fee of a transaction that references an
// unsupported script in the outpoint of a transaction input.
type errUnsupportedInput struct {
PkScript []byte
}
// Error returns a human-readable string describing the error.
func (e *errUnsupportedInput) Error() string {
return fmt.Sprintf("unsupported address type: %x", e.PkScript)
}
// ChangeAddressType is an enum-like type that describes the type of change
// address that should be generated for a transaction.
type ChangeAddressType uint8
const (
// P2WKHChangeAddress indicates that the change output should be a
// P2WKH output.
P2WKHChangeAddress ChangeAddressType = 0
// P2TRChangeAddress indicates that the change output should be a
// P2TR output.
P2TRChangeAddress ChangeAddressType = 1
// ExistingChangeAddress indicates that the coin selection algorithm
// should assume an existing output will be used for any change, meaning
// that the change amount calculated will be added to an existing output
// and no weight for a new change output should be assumed. The caller
// must assert that the output value of the selected existing output
// already is above dust when using this change address type.
ExistingChangeAddress ChangeAddressType = 2
// DefaultMaxFeeRatio is the default fee to total amount of outputs
// ratio that is used to sanity check the fees of a transaction.
DefaultMaxFeeRatio float64 = 0.2
)
// selectInputs selects a slice of inputs necessary to meet the specified
// selection amount. If input selection is unable to succeed due to insufficient
// funds, a non-nil error is returned. Additionally, the total amount of the
// selected coins are returned in order for the caller to properly handle
// change+fees.
func selectInputs(amt btcutil.Amount, coins []wallet.Coin,
strategy wallet.CoinSelectionStrategy,
feeRate chainfee.SatPerKWeight) (btcutil.Amount, []wallet.Coin, error) {
// All coin selection code in the btcwallet library requires sat/KB.
feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
arrangedCoins, err := strategy.ArrangeCoins(coins, feeSatPerKB)
if err != nil {
return 0, nil, err
}
satSelected := btcutil.Amount(0)
for i, coin := range arrangedCoins {
satSelected += btcutil.Amount(coin.Value)
if satSelected >= amt {
return satSelected, arrangedCoins[:i+1], nil
}
}
return 0, nil, &ErrInsufficientFunds{amt, satSelected}
}
// calculateFees returns for the specified utxos and fee rate two fee
// estimates, one calculated using a change output and one without. The weight
// added to the estimator from a change output is for a P2WKH output.
func calculateFees(utxos []wallet.Coin, feeRate chainfee.SatPerKWeight,
existingWeight input.TxWeightEstimator,
changeType ChangeAddressType) (btcutil.Amount, btcutil.Amount, error) {
weightEstimate := existingWeight
for _, utxo := range utxos {
switch {
case txscript.IsPayToWitnessPubKeyHash(utxo.PkScript):
weightEstimate.AddP2WKHInput()
case txscript.IsPayToScriptHash(utxo.PkScript):
weightEstimate.AddNestedP2WKHInput()
case txscript.IsPayToTaproot(utxo.PkScript):
weightEstimate.AddTaprootKeySpendInput(
txscript.SigHashDefault,
)
default:
return 0, 0, &errUnsupportedInput{utxo.PkScript}
}
}
// Estimate the fee required for a transaction without a change
// output.
totalWeight := weightEstimate.Weight()
requiredFeeNoChange := feeRate.FeeForWeight(totalWeight)
// Estimate the fee required for a transaction with a change output.
switch changeType {
case P2WKHChangeAddress:
weightEstimate.AddP2WKHOutput()
case P2TRChangeAddress:
weightEstimate.AddP2TROutput()
case ExistingChangeAddress:
// Don't add an extra output.
default:
return 0, 0, fmt.Errorf("unknown change address type: %v",
changeType)
}
// Now that we have added the change output, redo the fee
// estimate.
totalWeight = weightEstimate.Weight()
requiredFeeWithChange := feeRate.FeeForWeight(totalWeight)
return requiredFeeNoChange, requiredFeeWithChange, nil
}
// sanityCheckFee checks if the specified fee amounts to what the provided ratio
// allows.
func sanityCheckFee(totalOut, fee btcutil.Amount, maxFeeRatio float64) error {
// Sanity check the maxFeeRatio itself.
if maxFeeRatio <= 0.00 || maxFeeRatio > 1.00 {
return fmt.Errorf("maxFeeRatio must be between 0.00 and 1.00 "+
"got %.2f", maxFeeRatio)
}
maxFee := btcutil.Amount(float64(totalOut) * maxFeeRatio)
// Check that the fees do not exceed the max allowed value.
if fee > maxFee {
return fmt.Errorf("fee %v exceeds max fee (%v) on total "+
"output value %v with max fee ratio of %.2f", fee,
maxFee, totalOut, maxFeeRatio)
}
// All checks passed, we return nil to signal that the fees are valid.
return nil
}
// CoinSelect attempts to select a sufficient amount of coins, including a
// change output to fund amt satoshis, adhering to the specified fee rate. The
// specified fee rate should be expressed in sat/kw for coin selection to
// function properly.
func CoinSelect(feeRate chainfee.SatPerKWeight, amt, dustLimit btcutil.Amount,
coins []wallet.Coin, strategy wallet.CoinSelectionStrategy,
existingWeight input.TxWeightEstimator,
changeType ChangeAddressType, maxFeeRatio float64) ([]wallet.Coin,
btcutil.Amount, error) {
amtNeeded := amt
for {
// First perform an initial round of coin selection to estimate
// the required fee.
totalSat, selectedUtxos, err := selectInputs(
amtNeeded, coins, strategy, feeRate,
)
if err != nil {
return nil, 0, err
}
// Obtain fee estimates both with and without using a change
// output.
requiredFeeNoChange, requiredFeeWithChange, err := calculateFees(
selectedUtxos, feeRate, existingWeight, changeType,
)
if err != nil {
return nil, 0, err
}
changeAmount, newAmtNeeded, err := CalculateChangeAmount(
totalSat, amt, requiredFeeNoChange,
requiredFeeWithChange, dustLimit, changeType,
maxFeeRatio,
)
if err != nil {
return nil, 0, err
}
// Need another round, the selected coins aren't enough to pay
// for the fees.
if newAmtNeeded != 0 {
amtNeeded = newAmtNeeded
continue
}
// Coin selection was successful.
return selectedUtxos, changeAmount, nil
}
}
// CalculateChangeAmount calculates the change amount being left over when the
// given total amount of sats is provided as inputs for the required output
// amount. The calculation takes into account that we might not want to add a
// change output if the change amount is below the dust limit. The first amount
// returned is the change amount. If that is non-zero, change is left over and
// should be dealt with. The second amount, if non-zero, indicates that the
// total input amount was just not enough to pay for the required amount and
// fees and that more coins need to be selected.
func CalculateChangeAmount(totalInputAmt, requiredAmt, requiredFeeNoChange,
requiredFeeWithChange, dustLimit btcutil.Amount,
changeType ChangeAddressType, maxFeeRatio float64) (btcutil.Amount,
btcutil.Amount, error) {
// This is just a sanity check to make sure the function is used
// correctly.
if changeType == ExistingChangeAddress &&
requiredFeeNoChange != requiredFeeWithChange {
return 0, 0, fmt.Errorf("when using existing change address, " +
"the fees for with or without change must be the same")
}
// The difference between the selected amount and the amount
// requested will be used to pay fees, and generate a change
// output with the remaining.
overShootAmt := totalInputAmt - requiredAmt
var changeAmt btcutil.Amount
switch {
// If the excess amount isn't enough to pay for fees based on
// fee rate and estimated size without using a change output,
// then increase the requested coin amount by the estimate
// required fee without using change, performing another round
// of coin selection.
case overShootAmt < requiredFeeNoChange:
return 0, requiredAmt + requiredFeeNoChange, nil
// If sufficient funds were selected to cover the fee required
// to include a change output, the remainder will be our change
// amount.
case overShootAmt > requiredFeeWithChange:
changeAmt = overShootAmt - requiredFeeWithChange
// Otherwise we have selected enough to pay for a tx without a
// change output.
default:
changeAmt = 0
}
// In case we would end up with a dust output if we created a
// change output, we instead just let the dust amount go to
// fees. Unless we want the change to go to an existing output,
// in that case we can increase that output value by any amount.
if changeAmt < dustLimit && changeType != ExistingChangeAddress {
changeAmt = 0
}
// Sanity check the resulting output values to make sure we
// don't burn a great part to fees.
totalOut := requiredAmt + changeAmt
err := sanityCheckFee(totalOut, totalInputAmt-totalOut, maxFeeRatio)
if err != nil {
return 0, 0, err
}
return changeAmt, 0, nil
}
// CoinSelectSubtractFees attempts to select coins such that we'll spend up to
// amt in total after fees, adhering to the specified fee rate. The selected
// coins, the final output and change values are returned.
func CoinSelectSubtractFees(feeRate chainfee.SatPerKWeight, amt,
dustLimit btcutil.Amount, coins []wallet.Coin,
strategy wallet.CoinSelectionStrategy,
existingWeight input.TxWeightEstimator, changeType ChangeAddressType,
maxFeeRatio float64) ([]wallet.Coin, btcutil.Amount, btcutil.Amount,
error) {
// First perform an initial round of coin selection to estimate
// the required fee.
totalSat, selectedUtxos, err := selectInputs(
amt, coins, strategy, feeRate,
)
if err != nil {
return nil, 0, 0, err
}
// Obtain fee estimates both with and without using a change
// output.
requiredFeeNoChange, requiredFeeWithChange, err := calculateFees(
selectedUtxos, feeRate, existingWeight, changeType,
)
if err != nil {
return nil, 0, 0, err
}
// For a transaction without a change output, we'll let everything go
// to our multi-sig output after subtracting fees.
outputAmt := totalSat - requiredFeeNoChange
changeAmt := btcutil.Amount(0)
// If the output is too small after subtracting the fee, the coin
// selection cannot be performed with an amount this small.
if outputAmt < dustLimit {
return nil, 0, 0, fmt.Errorf("output amount(%v) after "+
"subtracting fees(%v) below dust limit(%v)", outputAmt,
requiredFeeNoChange, dustLimit)
}
// For a transaction with a change output, everything we don't spend
// will go to change.
newOutput := amt - requiredFeeWithChange
newChange := totalSat - amt
// If adding a change output leads to both outputs being above
// the dust limit, we'll add the change output. Otherwise we'll
// go with the no change tx we originally found.
if newChange >= dustLimit && newOutput >= dustLimit {
outputAmt = newOutput
changeAmt = newChange
}
// Sanity check the resulting output values to make sure we
// don't burn a great part to fees.
totalOut := outputAmt + changeAmt
err = sanityCheckFee(totalOut, totalSat-totalOut, maxFeeRatio)
if err != nil {
return nil, 0, 0, err
}
return selectedUtxos, outputAmt, changeAmt, nil
}
// CoinSelectUpToAmount attempts to select coins such that we'll select up to
// maxAmount exclusive of fees and optional reserve if sufficient funds are
// available. If insufficient funds are available this method selects all
// available coins.
func CoinSelectUpToAmount(feeRate chainfee.SatPerKWeight, minAmount, maxAmount,
reserved, dustLimit btcutil.Amount, coins []wallet.Coin,
strategy wallet.CoinSelectionStrategy,
existingWeight input.TxWeightEstimator,
changeType ChangeAddressType, maxFeeRatio float64) ([]wallet.Coin,
btcutil.Amount, btcutil.Amount, error) {
var (
// selectSubtractFee is tracking if our coin selection was
// unsuccessful and whether we have to start a new round of
// selecting coins considering fees.
selectSubtractFee = false
outputAmount = maxAmount
)
// Get total balance from coins which we need for reserve considerations
// and fee sanity checks.
var totalBalance btcutil.Amount
for _, coin := range coins {
totalBalance += btcutil.Amount(coin.Value)
}
// First we try to select coins to create an output of the specified
// maxAmount with or without a change output that covers the miner fee.
selected, changeAmt, err := CoinSelect(
feeRate, maxAmount, dustLimit, coins, strategy, existingWeight,
changeType, maxFeeRatio,
)
var errInsufficientFunds *ErrInsufficientFunds
switch {
case err == nil:
// If the coin selection succeeds we check if our total balance
// covers the selected set of coins including fees plus an
// optional anchor reserve.
// First we sum up the value of all selected coins.
var sumSelected btcutil.Amount
for _, coin := range selected {
sumSelected += btcutil.Amount(coin.Value)
}
// We then subtract the change amount from the value of all
// selected coins to obtain the actual amount that is selected.
sumSelected -= changeAmt
// Next we check if our total balance can cover for the selected
// output plus the optional anchor reserve.
if totalBalance-sumSelected < reserved {
// If our local balance is insufficient to cover for the
// reserve we try to select an output amount that uses
// our total balance minus reserve and fees.
selectSubtractFee = true
}
case errors.As(err, &errInsufficientFunds):
// If the initial coin selection fails due to insufficient funds
// we select our total available balance minus fees.
selectSubtractFee = true
default:
return nil, 0, 0, err
}
// If we determined that our local balance is insufficient we check
// our total balance minus fees and optional reserve.
if selectSubtractFee {
selected, outputAmount, changeAmt, err = CoinSelectSubtractFees(
feeRate, totalBalance-reserved, dustLimit, coins,
strategy, existingWeight, changeType, maxFeeRatio,
)
if err != nil {
return nil, 0, 0, err
}
}
// Sanity check the resulting output values to make sure we don't burn a
// great part to fees.
totalOut := outputAmount + changeAmt
sum := func(coins []wallet.Coin) btcutil.Amount {
var sum btcutil.Amount
for _, coin := range coins {
sum += btcutil.Amount(coin.Value)
}
return sum
}
err = sanityCheckFee(totalOut, sum(selected)-totalOut, maxFeeRatio)
if err != nil {
return nil, 0, 0, err
}
// In case the selected amount is lower than minimum funding amount we
// must return an error. The minimum funding amount is determined
// upstream and denotes either the minimum viable channel size or an
// amount sufficient to cover for the initial remote balance.
if outputAmount < minAmount {
return nil, 0, 0, fmt.Errorf("available funds(%v) below the "+
"minimum amount(%v)", outputAmount, minAmount)
}
return selected, outputAmount, changeAmt, nil
}
package chanfunding
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("CHFD", nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package chanfunding
import (
"errors"
"fmt"
"sync"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
)
// PsbtState is a type for the state of the PSBT intent state machine.
type PsbtState uint8
const (
// PsbtShimRegistered denotes a channel funding process has started with
// a PSBT shim attached. This is the default state for a PsbtIntent. We
// don't use iota here because the values have to be in sync with the
// RPC constants.
PsbtShimRegistered PsbtState = 1
// PsbtOutputKnown denotes that the local and remote peer have
// negotiated the multisig keys to be used as the channel funding output
// and therefore the PSBT funding process can now start.
PsbtOutputKnown PsbtState = 2
// PsbtVerified denotes that a potential PSBT has been presented to the
// intent and passed all checks. The verified PSBT can be given to a/the
// signer(s).
PsbtVerified PsbtState = 3
// PsbtFinalized denotes that a fully signed PSBT has been given to the
// intent that looks identical to the previously verified transaction
// but has all witness data added and is therefore completely signed.
PsbtFinalized PsbtState = 4
// PsbtFundingTxCompiled denotes that the PSBT processed by this intent
// has been successfully converted into a protocol transaction. It is
// not yet completely certain that the resulting transaction will be
// published because the commitment transactions between the channel
// peers first need to be counter signed. But the job of the intent is
// hereby completed.
PsbtFundingTxCompiled PsbtState = 5
// PsbtInitiatorCanceled denotes that the user has canceled the intent.
PsbtInitiatorCanceled PsbtState = 6
// PsbtResponderCanceled denotes that the remote peer has canceled the
// funding, likely due to a timeout.
PsbtResponderCanceled PsbtState = 7
)
// String returns a string representation of the PsbtState.
func (s PsbtState) String() string {
switch s {
case PsbtShimRegistered:
return "shim_registered"
case PsbtOutputKnown:
return "output_known"
case PsbtVerified:
return "verified"
case PsbtFinalized:
return "finalized"
case PsbtFundingTxCompiled:
return "funding_tx_compiled"
case PsbtInitiatorCanceled:
return "user_canceled"
case PsbtResponderCanceled:
return "remote_canceled"
default:
return fmt.Sprintf("<unknown(%d)>", s)
}
}
var (
// ErrRemoteCanceled is the error that is returned to the user if the
// funding flow was canceled by the remote peer.
ErrRemoteCanceled = errors.New("remote canceled funding, possibly " +
"timed out")
// ErrUserCanceled is the error that is returned through the PsbtReady
// channel if the user canceled the funding flow.
ErrUserCanceled = errors.New("user canceled funding")
)
// PsbtIntent is an intent created by the PsbtAssembler which represents a
// funding output to be created by a PSBT. This might be used when a hardware
// wallet, or a channel factory is the entity crafting the funding transaction,
// and not lnd.
type PsbtIntent struct {
// ShimIntent is the wrapped basic intent that contains common fields
// we also use in the PSBT funding case.
ShimIntent
// State is the current state the intent state machine is in.
State PsbtState
// BasePsbt is the user-supplied base PSBT the channel output should be
// added to. If this is nil we will create a new, empty PSBT as the base
// for the funding transaction.
BasePsbt *psbt.Packet
// PendingPsbt is the parsed version of the current PSBT. This can be
// in two stages: If the user has not yet provided any PSBT, this is
// nil. Once the user sends us an unsigned funded PSBT, we verify that
// we have a valid transaction that sends to the channel output PK
// script and has an input large enough to pay for it. We keep this
// verified but not yet signed version around until the fully signed
// transaction is submitted by the user. At that point we make sure the
// inputs and outputs haven't changed to what was previously verified.
// Only witness data should be added after the verification process.
PendingPsbt *psbt.Packet
// FinalTX is the final, signed and ready to be published wire format
// transaction. This is only set after the PsbtFinalize step was
// completed successfully.
FinalTX *wire.MsgTx
// PsbtReady is an error channel the funding manager will listen for
// a signal about the PSBT being ready to continue the funding flow. In
// the normal, happy flow, this channel is only ever closed. If a
// non-nil error is sent through the channel, the funding flow will be
// canceled.
//
// NOTE: This channel must always be buffered.
PsbtReady chan error
// shouldPublish specifies if the intent assumes its assembler should
// publish the transaction once the channel funding has completed. If
// this is set to false then the finalize step can be skipped.
shouldPublish bool
// signalPsbtReady is a Once guard to make sure the PsbtReady channel is
// only closed exactly once.
signalPsbtReady sync.Once
// netParams are the network parameters used to encode the P2WSH funding
// address.
netParams *chaincfg.Params
}
// BindKeys sets both the remote and local node's keys that will be used for the
// channel funding multisig output.
func (i *PsbtIntent) BindKeys(localKey *keychain.KeyDescriptor,
remoteKey *btcec.PublicKey) {
i.localKey = localKey
i.remoteKey = remoteKey
i.State = PsbtOutputKnown
}
// BindTapscriptRoot takes an optional tapscript root and binds it to the
// underlying funding intent. This only applies to musig2 channels, and will be
// used to make the musig2 funding output.
func (i *PsbtIntent) BindTapscriptRoot(root fn.Option[chainhash.Hash]) {
i.tapscriptRoot = root
}
// FundingParams returns the parameters that are necessary to start funding the
// channel output this intent was created for. It returns the P2WSH funding
// address, the exact funding amount and a PSBT packet that contains exactly one
// output that encodes the previous two parameters.
func (i *PsbtIntent) FundingParams() (btcutil.Address, int64, *psbt.Packet,
error) {
if i.State != PsbtOutputKnown {
return nil, 0, nil, fmt.Errorf("invalid state, got %v "+
"expected %v", i.State, PsbtOutputKnown)
}
// The funding output needs to be known already at this point, which
// means we need to have the local and remote multisig keys bound
// already.
_, out, err := i.FundingOutput()
if err != nil {
return nil, 0, nil, fmt.Errorf("unable to create funding "+
"output: %v", err)
}
script, err := txscript.ParsePkScript(out.PkScript)
if err != nil {
return nil, 0, nil, fmt.Errorf("unable to parse funding "+
"output script: %w", err)
}
// Encode the address in the human-readable bech32 format.
addr, err := script.Address(i.netParams)
if err != nil {
return nil, 0, nil, fmt.Errorf("unable to encode address: %w",
err)
}
// We'll also encode the address/amount in a machine-readable raw PSBT
// format. If the user supplied a base PSBT, we'll add the output to
// that one, otherwise we'll create a new one.
packet := i.BasePsbt
if packet == nil {
packet, err = psbt.New(nil, nil, 2, 0, nil)
if err != nil {
return nil, 0, nil, fmt.Errorf("unable to create "+
"PSBT: %w", err)
}
}
packet.UnsignedTx.TxOut = append(packet.UnsignedTx.TxOut, out)
var pOut psbt.POutput
// If this is a MuSig2 channel, we also need to communicate the internal
// key to the caller. Otherwise, they cannot verify the construction of
// the P2TR output script.
pOut.TaprootInternalKey = fn.MapOptionZ(
i.TaprootInternalKey(), schnorr.SerializePubKey,
)
packet.Outputs = append(packet.Outputs, pOut)
return addr, out.Value, packet, nil
}
// Verify makes sure the PSBT that is given to the intent has an output that
// sends to the channel funding multisig address with the correct amount. A
// simple check that at least a single input has been specified is performed.
func (i *PsbtIntent) Verify(packet *psbt.Packet, skipFinalize bool) error {
if packet == nil {
return fmt.Errorf("PSBT is nil")
}
if i.State != PsbtOutputKnown {
return fmt.Errorf("invalid state. got %v expected %v", i.State,
PsbtOutputKnown)
}
// Try to locate the channel funding multisig output.
_, expectedOutput, err := i.FundingOutput()
if err != nil {
return fmt.Errorf("funding output cannot be created: %w", err)
}
outputFound := false
outputSum := int64(0)
for _, out := range packet.UnsignedTx.TxOut {
outputSum += out.Value
if psbt.TxOutsEqual(out, expectedOutput) {
outputFound = true
}
}
if !outputFound {
return fmt.Errorf("funding output not found in PSBT")
}
// At least one input needs to be specified and it must be large enough
// to pay for all outputs. We don't want to dive into fee estimation
// here so we just assume that if the input amount exceeds the output
// amount, the chosen fee is sufficient.
if len(packet.UnsignedTx.TxIn) == 0 {
return fmt.Errorf("PSBT has no inputs")
}
sum, err := psbt.SumUtxoInputValues(packet)
if err != nil {
return fmt.Errorf("error determining input sum: %w", err)
}
if sum <= outputSum {
return fmt.Errorf("input amount sum must be larger than " +
"output amount sum")
}
// To avoid possible malleability, all inputs to a funding transaction
// must be SegWit spends.
err = verifyAllInputsSegWit(packet.UnsignedTx.TxIn, packet.Inputs)
if err != nil {
return fmt.Errorf("cannot use TX for channel funding, "+
"not all inputs are SegWit spends, risk of "+
"malleability: %v", err)
}
// In case we aren't going to publish any transaction, we now have
// everything we need and can skip the Finalize step.
i.PendingPsbt = packet
if !i.shouldPublish && skipFinalize {
i.FinalTX = packet.UnsignedTx
i.State = PsbtFinalized
// Signal the funding manager that it can now continue with its
// funding flow as the PSBT is now complete .
i.signalPsbtReady.Do(func() {
close(i.PsbtReady)
})
return nil
}
i.State = PsbtVerified
return nil
}
// Finalize makes sure the final PSBT that is given to the intent is fully valid
// and signed but still contains the same UTXOs and outputs as the pending
// transaction we previously verified. If everything checks out, the funding
// manager is informed that the channel can now be opened and the funding
// transaction be broadcast.
func (i *PsbtIntent) Finalize(packet *psbt.Packet) error {
if packet == nil {
return fmt.Errorf("PSBT is nil")
}
if i.State != PsbtVerified {
return fmt.Errorf("invalid state. got %v expected %v", i.State,
PsbtVerified)
}
// Make sure the PSBT itself thinks it's finalized and ready to be
// broadcast.
err := psbt.MaybeFinalizeAll(packet)
if err != nil {
return fmt.Errorf("error finalizing PSBT: %w", err)
}
rawTx, err := psbt.Extract(packet)
if err != nil {
return fmt.Errorf("unable to extract funding TX: %w", err)
}
return i.FinalizeRawTX(rawTx)
}
// FinalizeRawTX makes sure the final raw transaction that is given to the
// intent is fully valid and signed but still contains the same UTXOs and
// outputs as the pending transaction we previously verified. If everything
// checks out, the funding manager is informed that the channel can now be
// opened and the funding transaction be broadcast.
func (i *PsbtIntent) FinalizeRawTX(rawTx *wire.MsgTx) error {
if rawTx == nil {
return fmt.Errorf("raw transaction is nil")
}
if i.State != PsbtVerified {
return fmt.Errorf("invalid state. got %v expected %v", i.State,
PsbtVerified)
}
// Do a basic check that this is still the same TX that we verified in
// the previous step. This is to protect the user from unwanted
// modifications. We only check the outputs and previous outpoints of
// the inputs of the wire transaction because the fields in the PSBT
// part are allowed to change.
if i.PendingPsbt == nil {
return fmt.Errorf("PSBT was not verified first")
}
err := psbt.VerifyOutputsEqual(
rawTx.TxOut, i.PendingPsbt.UnsignedTx.TxOut,
)
if err != nil {
return fmt.Errorf("outputs differ from verified PSBT: %w", err)
}
err = psbt.VerifyInputPrevOutpointsEqual(
rawTx.TxIn, i.PendingPsbt.UnsignedTx.TxIn,
)
if err != nil {
return fmt.Errorf("inputs differ from verified PSBT: %w", err)
}
// We also check that we have a signed TX. This is only necessary if the
// FinalizeRawTX is called directly with a wire format TX instead of
// extracting the TX from a PSBT.
err = verifyInputsSigned(rawTx.TxIn)
if err != nil {
return fmt.Errorf("inputs not signed: %w", err)
}
// As far as we can tell, this TX is ok to be used as a funding
// transaction.
i.State = PsbtFinalized
i.FinalTX = rawTx
// Signal the funding manager that it can now finally continue with its
// funding flow as the PSBT is now ready to be converted into a real
// transaction and be published.
i.signalPsbtReady.Do(func() {
close(i.PsbtReady)
})
return nil
}
// CompileFundingTx finalizes the previously verified PSBT and returns the
// extracted binary serialized transaction from it. It also prepares the channel
// point for which this funding intent was initiated for.
func (i *PsbtIntent) CompileFundingTx() (*wire.MsgTx, error) {
if i.State != PsbtFinalized {
return nil, fmt.Errorf("invalid state. got %v expected %v",
i.State, PsbtFinalized)
}
// Identify our funding outpoint now that we know everything's ready.
_, txOut, err := i.FundingOutput()
if err != nil {
return nil, fmt.Errorf("cannot get funding output: %w", err)
}
ok, idx := input.FindScriptOutputIndex(i.FinalTX, txOut.PkScript)
if !ok {
return nil, fmt.Errorf("funding output not found in PSBT")
}
i.chanPoint = &wire.OutPoint{
Hash: i.FinalTX.TxHash(),
Index: idx,
}
i.State = PsbtFundingTxCompiled
return i.FinalTX, nil
}
// RemoteCanceled informs the listener of the PSBT ready channel that the
// funding has been canceled by the remote peer and that we can no longer
// continue with it.
func (i *PsbtIntent) RemoteCanceled() {
log.Debugf("PSBT funding intent canceled by remote, state=%v", i.State)
i.signalPsbtReady.Do(func() {
i.PsbtReady <- ErrRemoteCanceled
i.State = PsbtResponderCanceled
})
i.ShimIntent.Cancel()
}
// Cancel allows the caller to cancel a funding Intent at any time. This will
// return make sure the channel funding flow with the remote peer is failed and
// any reservations are canceled.
//
// NOTE: Part of the chanfunding.Intent interface.
func (i *PsbtIntent) Cancel() {
log.Debugf("PSBT funding intent canceled, state=%v", i.State)
i.signalPsbtReady.Do(func() {
i.PsbtReady <- ErrUserCanceled
i.State = PsbtInitiatorCanceled
})
i.ShimIntent.Cancel()
}
// Inputs returns all inputs to the final funding transaction that we know
// about. These are only known after the PSBT has been verified.
func (i *PsbtIntent) Inputs() []wire.OutPoint {
var inputs []wire.OutPoint
switch i.State {
// We return the inputs to the pending psbt.
case PsbtVerified:
for _, in := range i.PendingPsbt.UnsignedTx.TxIn {
inputs = append(inputs, in.PreviousOutPoint)
}
// We return the inputs to the final funding tx.
case PsbtFinalized, PsbtFundingTxCompiled:
for _, in := range i.FinalTX.TxIn {
inputs = append(inputs, in.PreviousOutPoint)
}
// In all other states we cannot know the inputs to the funding tx, and
// return an empty list.
default:
}
return inputs
}
// Outputs returns all outputs of the final funding transaction that we
// know about. These are only known after the PSBT has been verified.
func (i *PsbtIntent) Outputs() []*wire.TxOut {
switch i.State {
// We return the outputs of the pending psbt.
case PsbtVerified:
return i.PendingPsbt.UnsignedTx.TxOut
// We return the outputs of the final funding tx.
case PsbtFinalized, PsbtFundingTxCompiled:
return i.FinalTX.TxOut
// In all other states we cannot know the final outputs, and return an
// empty list.
default:
return nil
}
}
// ShouldPublishFundingTX returns true if the intent assumes that its assembler
// should publish the funding TX once the funding negotiation is complete.
func (i *PsbtIntent) ShouldPublishFundingTX() bool {
return i.shouldPublish
}
// PsbtAssembler is a type of chanfunding.Assembler wherein the funding
// transaction is constructed outside of lnd by using partially signed bitcoin
// transactions (PSBT).
type PsbtAssembler struct {
// fundingAmt is the total amount of coins in the funding output.
fundingAmt btcutil.Amount
// basePsbt is the user-supplied base PSBT the channel output should be
// added to.
basePsbt *psbt.Packet
// netParams are the network parameters used to encode the P2WSH funding
// address.
netParams *chaincfg.Params
// shouldPublish specifies if the assembler should publish the
// transaction once the channel funding has completed.
shouldPublish bool
}
// NewPsbtAssembler creates a new CannedAssembler from the material required
// to construct a funding output and channel point. An optional base PSBT can
// be supplied which will be used to add the channel output to instead of
// creating a new one.
func NewPsbtAssembler(fundingAmt btcutil.Amount, basePsbt *psbt.Packet,
netParams *chaincfg.Params, shouldPublish bool) *PsbtAssembler {
return &PsbtAssembler{
fundingAmt: fundingAmt,
basePsbt: basePsbt,
netParams: netParams,
shouldPublish: shouldPublish,
}
}
// ProvisionChannel creates a new ShimIntent given the passed funding Request.
// The returned intent is immediately able to provide the channel point and
// funding output as they've already been created outside lnd.
//
// NOTE: This method satisfies the chanfunding.Assembler interface.
func (p *PsbtAssembler) ProvisionChannel(req *Request) (Intent, error) {
// We'll exit out if SubtractFees is set as the funding transaction will
// be assembled externally, so we don't influence coin selection.
if req.SubtractFees {
return nil, fmt.Errorf("SubtractFees not supported for PSBT")
}
// We'll exit out if FundUpToMaxAmt or MinFundAmt is set as the funding
// transaction will be assembled externally, so we don't influence coin
// selection.
if req.FundUpToMaxAmt != 0 || req.MinFundAmt != 0 {
return nil, fmt.Errorf("FundUpToMaxAmt and MinFundAmt not " +
"supported for PSBT")
}
intent := &PsbtIntent{
ShimIntent: ShimIntent{
localFundingAmt: p.fundingAmt,
musig2: req.Musig2,
tapscriptRoot: req.TapscriptRoot,
},
State: PsbtShimRegistered,
BasePsbt: p.basePsbt,
PsbtReady: make(chan error, 1),
shouldPublish: p.shouldPublish,
netParams: p.netParams,
}
// A simple sanity check to ensure the provisioned request matches the
// re-made shim intent.
if req.LocalAmt+req.RemoteAmt != p.fundingAmt {
return nil, fmt.Errorf("intent doesn't match PSBT "+
"assembler: local_amt=%v, remote_amt=%v, funding_amt=%v",
req.LocalAmt, req.RemoteAmt, p.fundingAmt)
}
return intent, nil
}
// ShouldPublishFundingTx is a method of the assembler that signals if the
// funding transaction should be published after the channel negotiations are
// completed with the remote peer.
//
// NOTE: This method is a part of the ConditionalPublishAssembler interface.
func (p *PsbtAssembler) ShouldPublishFundingTx() bool {
return p.shouldPublish
}
// A compile-time assertion to ensure PsbtAssembler meets the
// ConditionalPublishAssembler interface.
var _ ConditionalPublishAssembler = (*PsbtAssembler)(nil)
// verifyInputsSigned verifies that the given list of inputs is non-empty and
// that all the inputs either contain a script signature or a witness stack.
func verifyInputsSigned(ins []*wire.TxIn) error {
if len(ins) == 0 {
return fmt.Errorf("no inputs in transaction")
}
for idx, in := range ins {
if len(in.SignatureScript) == 0 && len(in.Witness) == 0 {
return fmt.Errorf("input %d has no signature data "+
"attached", idx)
}
}
return nil
}
// verifyAllInputsSegWit makes sure all inputs to a transaction are SegWit
// spends. This is a bit tricky because the PSBT spec doesn't require the
// WitnessUtxo field to be set. Therefore if only a NonWitnessUtxo is given, we
// need to look at it and make sure it's either a witness pkScript or a nested
// SegWit spend.
func verifyAllInputsSegWit(txIns []*wire.TxIn, ins []psbt.PInput) error {
for idx, in := range ins {
switch {
// The optimal case is that the witness UTXO is set explicitly.
case in.WitnessUtxo != nil:
// Only the non witness UTXO field is set, we need to inspect it
// to make sure it's not P2PKH or bare P2SH.
case in.NonWitnessUtxo != nil:
utxo := in.NonWitnessUtxo
txIn := txIns[idx]
txOut := utxo.TxOut[txIn.PreviousOutPoint.Index]
if !txscript.IsWitnessProgram(txOut.PkScript) &&
!txscript.IsWitnessProgram(in.RedeemScript) {
return fmt.Errorf("input %d is non-SegWit "+
"spend or missing redeem script", idx)
}
// This should've already been caught by a previous check but we
// keep it in for completeness' sake.
default:
return fmt.Errorf("input %d has no UTXO information",
idx)
}
}
return nil
}
package chanfunding
import (
"fmt"
"math"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/txsort"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
)
const (
// DefaultReservationTimeout is the default time we wait until we remove
// an unfinished (zombiestate) open channel flow from memory.
DefaultReservationTimeout = 10 * time.Minute
// DefaultLockDuration is the default duration used to lock outputs.
DefaultLockDuration = 10 * time.Minute
)
var (
// LndInternalLockID is the binary representation of the SHA256 hash of
// the string "lnd-internal-lock-id" and is used for UTXO lock leases to
// identify that we ourselves are locking an UTXO, for example when
// giving out a funded PSBT. The ID corresponds to the hex value of
// ede19a92ed321a4705f8a1cccc1d4f6182545d4bb4fae08bd5937831b7e38f98.
LndInternalLockID = wtxmgr.LockID{
0xed, 0xe1, 0x9a, 0x92, 0xed, 0x32, 0x1a, 0x47,
0x05, 0xf8, 0xa1, 0xcc, 0xcc, 0x1d, 0x4f, 0x61,
0x82, 0x54, 0x5d, 0x4b, 0xb4, 0xfa, 0xe0, 0x8b,
0xd5, 0x93, 0x78, 0x31, 0xb7, 0xe3, 0x8f, 0x98,
}
)
// FullIntent is an intent that is fully backed by the internal wallet. This
// intent differs from the ShimIntent, in that the funding transaction will be
// constructed internally, and will consist of only inputs we wholly control.
// This Intent implements a basic state machine that must be executed in order
// before CompileFundingTx can be called.
//
// Steps to final channel provisioning:
// 1. Call BindKeys to notify the intent which keys to use when constructing
// the multi-sig output.
// 2. Call CompileFundingTx afterwards to obtain the funding transaction.
//
// If either of these steps fail, then the Cancel method MUST be called.
type FullIntent struct {
ShimIntent
// InputCoins are the set of coins selected as inputs to this funding
// transaction.
InputCoins []wallet.Coin
// ChangeOutputs are the set of outputs that the Assembler will use as
// change from the main funding transaction.
ChangeOutputs []*wire.TxOut
// coinLeaser is the Assembler's instance of the OutputLeaser
// interface.
coinLeaser OutputLeaser
// coinSource is the Assembler's instance of the CoinSource interface.
coinSource CoinSource
// signer is the Assembler's instance of the Singer interface.
signer input.Signer
}
// BindKeys is a method unique to the FullIntent variant. This allows the
// caller to decide precisely which keys are used in the final funding
// transaction. This is kept out of the main Assembler as these may may not
// necessarily be under full control of the wallet. Only after this method has
// been executed will CompileFundingTx succeed.
func (f *FullIntent) BindKeys(localKey *keychain.KeyDescriptor,
remoteKey *btcec.PublicKey) {
f.localKey = localKey
f.remoteKey = remoteKey
}
// CompileFundingTx is to be called after BindKeys on the sub-intent has been
// called. This method will construct the final funding transaction, and fully
// sign all inputs that are known by the backing CoinSource. After this method
// returns, the Intent is assumed to be complete, as the output can be created
// at any point.
func (f *FullIntent) CompileFundingTx(extraInputs []*wire.TxIn,
extraOutputs []*wire.TxOut) (*wire.MsgTx, error) {
// Create a blank, fresh transaction. Soon to be a complete funding
// transaction which will allow opening a lightning channel.
fundingTx := wire.NewMsgTx(2)
// Add all multi-party inputs and outputs to the transaction.
for _, coin := range f.InputCoins {
fundingTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: coin.OutPoint,
})
}
for _, theirInput := range extraInputs {
fundingTx.AddTxIn(theirInput)
}
for _, ourChangeOutput := range f.ChangeOutputs {
fundingTx.AddTxOut(ourChangeOutput)
}
for _, theirChangeOutput := range extraOutputs {
fundingTx.AddTxOut(theirChangeOutput)
}
_, fundingOutput, err := f.FundingOutput()
if err != nil {
return nil, err
}
// Sort the transaction. Since both side agree to a canonical ordering,
// by sorting we no longer need to send the entire transaction. Only
// signatures will be exchanged.
fundingTx.AddTxOut(fundingOutput)
txsort.InPlaceSort(fundingTx)
// Now that the funding tx has been fully assembled, we'll locate the
// index of the funding output so we can create our final channel
// point.
_, multiSigIndex := input.FindScriptOutputIndex(
fundingTx, fundingOutput.PkScript,
)
// Next, sign all inputs that are ours, collecting the signatures in
// order of the inputs.
prevOutFetcher := NewSegWitV0DualFundingPrevOutputFetcher(
f.coinSource, extraInputs,
)
sigHashes := txscript.NewTxSigHashes(fundingTx, prevOutFetcher)
for i, txIn := range fundingTx.TxIn {
signDesc := input.SignDescriptor{
SigHashes: sigHashes,
PrevOutputFetcher: prevOutFetcher,
}
// We can only sign this input if it's ours, so we'll ask the
// coin source if it can map this outpoint into a coin we own.
// If not, then we'll continue as it isn't our input.
info, err := f.coinSource.CoinFromOutPoint(
txIn.PreviousOutPoint,
)
if err != nil {
continue
}
// Now that we know the input is ours, we'll populate the
// signDesc with the per input unique information.
signDesc.Output = &wire.TxOut{
Value: info.Value,
PkScript: info.PkScript,
}
signDesc.InputIndex = i
// We support spending a p2tr input ourselves. But not as part
// of their inputs.
signDesc.HashType = txscript.SigHashAll
if txscript.IsPayToTaproot(info.PkScript) {
signDesc.HashType = txscript.SigHashDefault
}
// Finally, we'll sign the input as is, and populate the input
// with the witness and sigScript (if needed).
inputScript, err := f.signer.ComputeInputScript(
fundingTx, &signDesc,
)
if err != nil {
return nil, err
}
txIn.SignatureScript = inputScript.SigScript
txIn.Witness = inputScript.Witness
}
// Finally, we'll populate the chanPoint now that we've fully
// constructed the funding transaction.
f.chanPoint = &wire.OutPoint{
Hash: fundingTx.TxHash(),
Index: multiSigIndex,
}
return fundingTx, nil
}
// Inputs returns all inputs to the final funding transaction that we
// know about. Since this funding transaction is created all from our wallet,
// it will be all inputs.
func (f *FullIntent) Inputs() []wire.OutPoint {
var ins []wire.OutPoint
for _, coin := range f.InputCoins {
ins = append(ins, coin.OutPoint)
}
return ins
}
// Outputs returns all outputs of the final funding transaction that we
// know about. This will be the funding output and the change outputs going
// back to our wallet.
func (f *FullIntent) Outputs() []*wire.TxOut {
outs := f.ShimIntent.Outputs()
outs = append(outs, f.ChangeOutputs...)
return outs
}
// Cancel allows the caller to cancel a funding Intent at any time. This will
// return any resources such as coins back to the eligible pool to be used in
// order channel fundings.
//
// NOTE: Part of the chanfunding.Intent interface.
func (f *FullIntent) Cancel() {
for _, coin := range f.InputCoins {
err := f.coinLeaser.ReleaseOutput(
LndInternalLockID, coin.OutPoint,
)
if err != nil {
log.Warnf("Failed to release UTXO %s (%v))",
coin.OutPoint, err)
}
}
f.ShimIntent.Cancel()
}
// A compile-time check to ensure FullIntent meets the Intent interface.
var _ Intent = (*FullIntent)(nil)
// WalletConfig is the main config of the WalletAssembler.
type WalletConfig struct {
// CoinSource is what the WalletAssembler uses to list/locate coins.
CoinSource CoinSource
// CoinSelectionStrategy is the strategy that is used for selecting
// coins when funding a transaction.
CoinSelectionStrategy wallet.CoinSelectionStrategy
// CoinSelectionLocker allows the WalletAssembler to gain exclusive
// access to the current set of coins returned by the CoinSource.
CoinSelectLocker CoinSelectionLocker
// CoinLeaser is what the WalletAssembler uses to lease coins that may
// be used as inputs for a new funding transaction.
CoinLeaser OutputLeaser
// Signer allows the WalletAssembler to sign inputs on any potential
// funding transactions.
Signer input.Signer
// DustLimit is the current dust limit. We'll use this to ensure that
// we don't make dust outputs on the funding transaction.
DustLimit btcutil.Amount
}
// WalletAssembler is an instance of the Assembler interface that is backed by
// a full wallet. This variant of the Assembler interface will produce the
// entirety of the funding transaction within the wallet. This implements the
// typical funding flow that is initiated either on the p2p level or using the
// CLi.
type WalletAssembler struct {
cfg WalletConfig
}
// NewWalletAssembler creates a new instance of the WalletAssembler from a
// fully populated wallet config.
func NewWalletAssembler(cfg WalletConfig) *WalletAssembler {
return &WalletAssembler{
cfg: cfg,
}
}
// ProvisionChannel is the main entry point to begin a funding workflow given a
// fully populated request. The internal WalletAssembler will perform coin
// selection in a goroutine safe manner, returning an Intent that will allow
// the caller to finalize the funding process.
//
// NOTE: To cancel the funding flow the Cancel() method on the returned Intent,
// MUST be called.
//
// NOTE: This is a part of the chanfunding.Assembler interface.
func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
var intent Intent
// We hold the coin select mutex while querying for outputs, and
// performing coin selection in order to avoid inadvertent double
// spends across funding transactions.
err := w.cfg.CoinSelectLocker.WithCoinSelectLock(func() error {
log.Infof("Performing funding tx coin selection using %v "+
"sat/kw as fee rate", int64(r.FeeRate))
var (
// allCoins refers to the entirety of coins in our
// wallet that are available for funding a channel.
allCoins []wallet.Coin
// manuallySelectedCoins refers to the client-side
// selected coins that should be considered available
// for funding a channel.
manuallySelectedCoins []wallet.Coin
err error
)
// Convert manually selected outpoints to coins.
manuallySelectedCoins, err = outpointsToCoins(
r.Outpoints, w.cfg.CoinSource.CoinFromOutPoint,
)
if err != nil {
return err
}
// Find all unlocked unspent witness outputs that satisfy the
// minimum number of confirmations required. Coin selection in
// this function currently ignores the configured coin selection
// strategy.
allCoins, err = w.cfg.CoinSource.ListCoins(
r.MinConfs, math.MaxInt32,
)
if err != nil {
return err
}
// Ensure that all manually selected coins remain unspent.
unspent := make(map[wire.OutPoint]struct{})
for _, coin := range allCoins {
unspent[coin.OutPoint] = struct{}{}
}
for _, coin := range manuallySelectedCoins {
if _, ok := unspent[coin.OutPoint]; !ok {
return fmt.Errorf("outpoint already spent or "+
"locked by another subsystem: %v",
coin.OutPoint)
}
}
// The coin selection algorithm requires to know what
// inputs/outputs are already present in the funding
// transaction and what a change output would look like. Since
// a channel funding is always either a P2WSH or P2TR output,
// we can use just P2WSH here (both of these output types have
// the same length). And we currently don't support specifying a
// change output type, so we always use P2TR.
var fundingOutputWeight input.TxWeightEstimator
fundingOutputWeight.AddP2WSHOutput()
changeType := P2TRChangeAddress
var (
coins []wallet.Coin
selectedCoins []wallet.Coin
localContributionAmt btcutil.Amount
changeAmt btcutil.Amount
)
// If outputs were specified manually then we'll take the
// corresponding coins as basis for coin selection. Otherwise,
// all available coins from our wallet are used.
coins = allCoins
if len(manuallySelectedCoins) > 0 {
coins = manuallySelectedCoins
}
// Perform coin selection over our available, unlocked unspent
// outputs in order to find enough coins to meet the funding
// amount requirements.
switch {
// If there's no funding amount at all (receiving an inbound
// single funder request), then we don't need to perform any
// coin selection at all.
case r.LocalAmt == 0 && r.FundUpToMaxAmt == 0:
break
// The local funding amount cannot be used in combination with
// the funding up to some maximum amount. If that is the case
// we return an error.
case r.LocalAmt != 0 && r.FundUpToMaxAmt != 0:
return fmt.Errorf("cannot use a local funding amount " +
"and fundmax parameters")
// We cannot use the subtract fees flag while using the funding
// up to some maximum amount. If that is the case we return an
// error.
case r.SubtractFees && r.FundUpToMaxAmt != 0:
return fmt.Errorf("cannot subtract fees from local " +
"amount while using fundmax parameters")
// In case this request uses funding up to some maximum amount,
// we will call the specialized coin selection function for
// that.
case r.FundUpToMaxAmt != 0 && r.MinFundAmt != 0:
// We need to ensure that manually selected coins, which
// are spent entirely on the channel funding, leave
// enough funds in the wallet to cover for a reserve.
reserve := r.WalletReserve
if len(manuallySelectedCoins) > 0 {
sumCoins := func(
coins []wallet.Coin) btcutil.Amount {
var sum btcutil.Amount
for _, coin := range coins {
sum += btcutil.Amount(
coin.Value,
)
}
return sum
}
sumManual := sumCoins(manuallySelectedCoins)
sumAll := sumCoins(allCoins)
// If sufficient reserve funds are available we
// don't have to provide for it during coin
// selection. The manually selected coins can be
// spent entirely on the channel funding. If
// the excess of coins cover the reserve
// partially then we have to provide for the
// rest during coin selection.
excess := sumAll - sumManual
if excess >= reserve {
reserve = 0
} else {
reserve -= excess
}
}
selectedCoins, localContributionAmt, changeAmt,
err = CoinSelectUpToAmount(
r.FeeRate, r.MinFundAmt, r.FundUpToMaxAmt,
reserve, w.cfg.DustLimit, coins,
w.cfg.CoinSelectionStrategy,
fundingOutputWeight, changeType,
DefaultMaxFeeRatio,
)
if err != nil {
return err
}
// Now where the actual channel capacity is determined
// we can check for local contribution constraints.
//
// Ensure that the remote channel reserve does not
// exceed 20% of the channel capacity.
if r.RemoteChanReserve >= localContributionAmt/5 {
return fmt.Errorf("remote channel reserve " +
"must be less than the %%20 of the " +
"channel capacity")
}
// Ensure that the initial remote balance does not
// exceed our local contribution as that would leave a
// negative balance on our side.
if r.PushAmt >= localContributionAmt {
return fmt.Errorf("amount pushed to remote " +
"peer for initial state must be " +
"below the local funding amount")
}
// In case this request want the fees subtracted from the local
// amount, we'll call the specialized method for that. This
// ensures that we won't deduct more that the specified balance
// from our wallet.
case r.SubtractFees:
dustLimit := w.cfg.DustLimit
selectedCoins, localContributionAmt, changeAmt,
err = CoinSelectSubtractFees(
r.FeeRate, r.LocalAmt, dustLimit, coins,
w.cfg.CoinSelectionStrategy,
fundingOutputWeight, changeType,
DefaultMaxFeeRatio,
)
if err != nil {
return err
}
// Otherwise do a normal coin selection where we target a given
// funding amount.
default:
dustLimit := w.cfg.DustLimit
localContributionAmt = r.LocalAmt
selectedCoins, changeAmt, err = CoinSelect(
r.FeeRate, r.LocalAmt, dustLimit, coins,
w.cfg.CoinSelectionStrategy,
fundingOutputWeight, changeType,
DefaultMaxFeeRatio,
)
if err != nil {
return err
}
}
// Sanity check: The addition of the outputs should not lead to the
// creation of dust.
if changeAmt != 0 && changeAmt < w.cfg.DustLimit {
return fmt.Errorf("change amount(%v) after coin "+
"select is below dust limit(%v)", changeAmt,
w.cfg.DustLimit)
}
// Record any change output(s) generated as a result of the
// coin selection.
var changeOutput *wire.TxOut
if changeAmt != 0 {
changeAddr, err := r.ChangeAddr()
if err != nil {
return err
}
changeScript, err := txscript.PayToAddrScript(changeAddr)
if err != nil {
return err
}
changeOutput = &wire.TxOut{
Value: int64(changeAmt),
PkScript: changeScript,
}
}
// Lock the selected coins. These coins are now "reserved",
// this prevents concurrent funding requests from referring to
// and this double-spending the same set of coins.
for _, coin := range selectedCoins {
outpoint := coin.OutPoint
_, err = w.cfg.CoinLeaser.LeaseOutput(
LndInternalLockID, outpoint,
DefaultReservationTimeout,
)
if err != nil {
return err
}
}
newIntent := &FullIntent{
ShimIntent: ShimIntent{
localFundingAmt: localContributionAmt,
remoteFundingAmt: r.RemoteAmt,
musig2: r.Musig2,
tapscriptRoot: r.TapscriptRoot,
},
InputCoins: selectedCoins,
coinLeaser: w.cfg.CoinLeaser,
coinSource: w.cfg.CoinSource,
signer: w.cfg.Signer,
}
if changeOutput != nil {
newIntent.ChangeOutputs = []*wire.TxOut{changeOutput}
}
intent = newIntent
return nil
})
if err != nil {
return nil, err
}
return intent, nil
}
// outpointsToCoins maps outpoints to coins in our wallet iff these coins are
// existent and returns an error otherwise.
func outpointsToCoins(outpoints []wire.OutPoint,
coinFromOutPoint func(wire.OutPoint) (*wallet.Coin, error)) (
[]wallet.Coin, error) {
var selectedCoins []wallet.Coin
for _, outpoint := range outpoints {
coin, err := coinFromOutPoint(
outpoint,
)
if err != nil {
return nil, err
}
selectedCoins = append(
selectedCoins, *coin,
)
}
return selectedCoins, nil
}
// FundingTxAvailable is an empty method that an assembler can implement to
// signal to callers that its able to provide the funding transaction for the
// channel via the intent it returns.
//
// NOTE: This method is a part of the FundingTxAssembler interface.
func (w *WalletAssembler) FundingTxAvailable() {}
// A compile-time assertion to ensure the WalletAssembler meets the
// FundingTxAssembler interface.
var _ FundingTxAssembler = (*WalletAssembler)(nil)
// SegWitV0DualFundingPrevOutputFetcher is a txscript.PrevOutputFetcher that
// knows about local and remote funding inputs.
//
// TODO(guggero): Support dual funding with p2tr inputs, currently only segwit
// v0 inputs are supported.
type SegWitV0DualFundingPrevOutputFetcher struct {
local CoinSource
remote *txscript.MultiPrevOutFetcher
}
var _ txscript.PrevOutputFetcher = (*SegWitV0DualFundingPrevOutputFetcher)(nil)
// NewSegWitV0DualFundingPrevOutputFetcher creates a new
// txscript.PrevOutputFetcher from the given local and remote inputs.
//
// NOTE: Since the actual pkScript and amounts aren't passed in, this will just
// make sure that nothing will panic when creating a SegWit v0 sighash. But this
// code will NOT WORK for transactions that spend any _remote_ Taproot inputs!
// So basically dual-funding won't work with Taproot inputs unless the UTXO info
// is exchanged between the peers.
func NewSegWitV0DualFundingPrevOutputFetcher(localSource CoinSource,
remoteInputs []*wire.TxIn) txscript.PrevOutputFetcher {
remote := txscript.NewMultiPrevOutFetcher(nil)
for _, inp := range remoteInputs {
// We add an empty output to prevent the sighash calculation
// from panicking. But this will always detect the inputs as
// SegWig v0!
remote.AddPrevOut(inp.PreviousOutPoint, &wire.TxOut{})
}
return &SegWitV0DualFundingPrevOutputFetcher{
local: localSource,
remote: remote,
}
}
// FetchPrevOutput attempts to fetch the previous output referenced by the
// passed outpoint.
//
// NOTE: This is a part of the txscript.PrevOutputFetcher interface.
func (d *SegWitV0DualFundingPrevOutputFetcher) FetchPrevOutput(
op wire.OutPoint) *wire.TxOut {
// Try the local source first. This will return nil if our internal
// wallet doesn't know the outpoint.
coin, err := d.local.CoinFromOutPoint(op)
if err == nil && coin != nil {
return &coin.TxOut
}
// Fall back to the remote
return d.remote.FetchPrevOutput(op)
}
package lnwallet
import (
"bytes"
"cmp"
"context"
"crypto/sha256"
"errors"
"fmt"
"slices"
"sync"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/txsort"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/mempool"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btclog/v2"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/shachain"
"github.com/lightningnetwork/lnd/tlv"
)
var (
// ErrChanClosing is returned when a caller attempts to close a channel
// that has already been closed or is in the process of being closed.
ErrChanClosing = fmt.Errorf("channel is being closed, operation disallowed")
// ErrNoWindow is returned when revocation window is exhausted.
ErrNoWindow = fmt.Errorf("unable to sign new commitment, the current" +
" revocation window is exhausted")
// ErrMaxWeightCost is returned when the cost/weight (see segwit)
// exceeds the widely used maximum allowed policy weight limit. In this
// case the commitment transaction can't be propagated through the
// network.
ErrMaxWeightCost = fmt.Errorf("commitment transaction exceed max " +
"available cost")
// ErrMaxHTLCNumber is returned when a proposed HTLC would exceed the
// maximum number of allowed HTLC's if committed in a state transition
ErrMaxHTLCNumber = fmt.Errorf("commitment transaction exceed max " +
"htlc number")
// ErrMaxPendingAmount is returned when a proposed HTLC would exceed
// the overall maximum pending value of all HTLCs if committed in a
// state transition.
ErrMaxPendingAmount = fmt.Errorf("commitment transaction exceed max" +
"overall pending htlc value")
// ErrBelowChanReserve is returned when a proposed HTLC would cause
// one of the peer's funds to dip below the channel reserve limit.
ErrBelowChanReserve = fmt.Errorf("commitment transaction dips peer " +
"below chan reserve")
// ErrBelowMinHTLC is returned when a proposed HTLC has a value that
// is below the minimum HTLC value constraint for either us or our
// peer depending on which flags are set.
ErrBelowMinHTLC = fmt.Errorf("proposed HTLC value is below minimum " +
"allowed HTLC value")
// ErrFeeBufferNotInitiator is returned when the FeeBuffer is enforced
// although the channel was not initiated (opened) locally.
ErrFeeBufferNotInitiator = fmt.Errorf("unable to enforce FeeBuffer, " +
"not initiator of the channel")
// ErrInvalidHTLCAmt signals that a proposed HTLC has a value that is
// not positive.
ErrInvalidHTLCAmt = fmt.Errorf("proposed HTLC value must be positive")
// ErrCannotSyncCommitChains is returned if, upon receiving a ChanSync
// message, the state machine deems that is unable to properly
// synchronize states with the remote peer. In this case we should fail
// the channel, but we won't automatically force close.
ErrCannotSyncCommitChains = fmt.Errorf("unable to sync commit chains")
// ErrInvalidLastCommitSecret is returned in the case that the
// commitment secret sent by the remote party in their
// ChannelReestablish message doesn't match the last secret we sent.
ErrInvalidLastCommitSecret = fmt.Errorf("commit secret is incorrect")
// ErrInvalidLocalUnrevokedCommitPoint is returned in the case that the
// commitment point sent by the remote party in their
// ChannelReestablish message doesn't match the last unrevoked commit
// point they sent us.
ErrInvalidLocalUnrevokedCommitPoint = fmt.Errorf("unrevoked commit " +
"point is invalid")
// ErrCommitSyncRemoteDataLoss is returned in the case that we receive
// a ChannelReestablish message from the remote that advertises a
// NextLocalCommitHeight that is lower than what they have already
// ACKed, or a RemoteCommitTailHeight that is lower than our revoked
// height. In this case we should force close the channel such that
// both parties can retrieve their funds.
ErrCommitSyncRemoteDataLoss = fmt.Errorf("possible remote commitment " +
"state data loss")
// ErrNoRevocationLogFound is returned when both the returned logs are
// nil from querying the revocation log bucket. In theory this should
// never happen as the query will return `ErrLogEntryNotFound`, yet
// we'd still perform a sanity check to make sure at least one of the
// logs is non-nil.
ErrNoRevocationLogFound = errors.New("no revocation log found")
// ErrOutputIndexOutOfRange is returned when an output index is greater
// than or equal to the length of a given transaction's outputs.
ErrOutputIndexOutOfRange = errors.New("output index is out of range")
// ErrRevLogDataMissing is returned when a certain wanted optional field
// in a revocation log entry is missing.
ErrRevLogDataMissing = errors.New("revocation log data missing")
// ErrForceCloseLocalDataLoss is returned in the case a user (or
// another sub-system) attempts to force close when we've detected that
// we've likely lost data ourselves.
ErrForceCloseLocalDataLoss = errors.New("cannot force close " +
"channel with local data loss")
// errNoNonce is returned when a nonce is required, but none is found.
errNoNonce = errors.New("no nonce found")
// errNoPartialSig is returned when a partial signature is required,
// but none is found.
errNoPartialSig = errors.New("no partial signature found")
// errQuit is returned when a quit signal was received, interrupting the
// current operation.
errQuit = errors.New("received quit signal")
)
// ErrCommitSyncLocalDataLoss is returned in the case that we receive a valid
// commit secret within the ChannelReestablish message from the remote node AND
// they advertise a RemoteCommitTailHeight higher than our current known
// height. This means we have lost some critical data, and must fail the
// channel and MUST NOT force close it. Instead we should wait for the remote
// to force close it, such that we can attempt to sweep our funds. The
// commitment point needed to sweep the remote's force close is encapsulated.
type ErrCommitSyncLocalDataLoss struct {
// ChannelPoint is the identifier for the channel that experienced data
// loss.
ChannelPoint wire.OutPoint
// CommitPoint is the last unrevoked commit point, sent to us by the
// remote when we determined we had lost state.
CommitPoint *btcec.PublicKey
}
// Error returns a string representation of the local data loss error.
func (e *ErrCommitSyncLocalDataLoss) Error() string {
return fmt.Sprintf("ChannelPoint(%v) with CommitPoint(%x) had "+
"possible local commitment state data loss", e.ChannelPoint,
e.CommitPoint.SerializeCompressed())
}
// PaymentHash represents the sha256 of a random value. This hash is used to
// uniquely track incoming/outgoing payments within this channel, as well as
// payments requested by the wallet/daemon.
type PaymentHash [32]byte
// commitment represents a commitment to a new state within an active channel.
// New commitments can be initiated by either side. Commitments are ordered
// into a commitment chain, with one existing for both parties. Each side can
// independently extend the other side's commitment chain, up to a certain
// "revocation window", which once reached, disallows new commitments until
// the local nodes receives the revocation for the remote node's chain tail.
type commitment struct {
// height represents the commitment height of this commitment, or the
// update number of this commitment.
height uint64
// whoseCommit indicates whether this is the local or remote node's
// version of the commitment.
whoseCommit lntypes.ChannelParty
// [our|their]MessageIndex are indexes into the HTLC log, up to which
// this commitment transaction includes. These indexes allow both sides
// to independently, and concurrent send create new commitments. Each
// new commitment sent to the remote party includes an index in the
// shared log which details which of their updates we're including in
// this new commitment.
messageIndices lntypes.Dual[uint64]
// [our|their]HtlcIndex are the current running counters for the HTLCs
// offered by either party. This value is incremented each time a party
// offers a new HTLC. The log update methods that consume HTLCs will
// reference these counters, rather than the running cumulative message
// counters.
ourHtlcIndex uint64
theirHtlcIndex uint64
// txn is the commitment transaction generated by including any HTLC
// updates whose index are below the two indexes listed above. If this
// commitment is being added to the remote chain, then this txn is
// their version of the commitment transactions. If the local commit
// chain is being modified, the opposite is true.
txn *wire.MsgTx
// sig is a signature for the above commitment transaction.
sig []byte
// [our|their]Balance represents the settled balances at this point
// within the commitment chain. This balance is computed by properly
// evaluating all the add/remove/settle log entries before the listed
// indexes.
//
// NOTE: This is the balance *after* subtracting any commitment fee,
// AND anchor output values.
ourBalance lnwire.MilliSatoshi
theirBalance lnwire.MilliSatoshi
// fee is the amount that will be paid as fees for this commitment
// transaction. The fee is recorded here so that it can be added back
// and recalculated for each new update to the channel state.
fee btcutil.Amount
// feePerKw is the fee per kw used to calculate this commitment
// transaction's fee.
feePerKw chainfee.SatPerKWeight
// dustLimit is the limit on the commitment transaction such that no
// output values should be below this amount.
dustLimit btcutil.Amount
// outgoingHTLCs is a slice of all the outgoing HTLC's (from our PoV)
// on this commitment transaction.
outgoingHTLCs []paymentDescriptor
// incomingHTLCs is a slice of all the incoming HTLC's (from our PoV)
// on this commitment transaction.
incomingHTLCs []paymentDescriptor
// customBlob stores opaque bytes that may be used by custom channels
// to store extra data for a given commitment state.
customBlob fn.Option[tlv.Blob]
// [outgoing|incoming]HTLCIndex is an index that maps an output index
// on the commitment transaction to the payment descriptor that
// represents the HTLC output.
//
// NOTE: that these fields are only populated if this commitment state
// belongs to the local node. These maps are used when validating any
// HTLC signatures which are part of the local commitment state. We use
// this map in order to locate the details needed to validate an HTLC
// signature while iterating of the outputs in the local commitment
// view.
outgoingHTLCIndex map[int32]*paymentDescriptor
incomingHTLCIndex map[int32]*paymentDescriptor
}
// locateOutputIndex is a small helper function to locate the output index of a
// particular HTLC within the current commitment transaction. The duplicate map
// passed in is to be retained for each output within the commitment
// transition. This ensures that we don't assign multiple HTLCs to the same
// index within the commitment transaction.
func locateOutputIndex(p *paymentDescriptor, tx *wire.MsgTx,
whoseCommit lntypes.ChannelParty, dups map[PaymentHash][]int32,
cltvs []uint32) (int32, error) {
// If this is their commitment transaction, we'll be trying to locate
// their pkScripts, otherwise we'll be looking for ours. This is
// required as the commitment states are asymmetric in order to ascribe
// blame in the case of a contract breach.
pkScript := p.theirPkScript
if whoseCommit.IsLocal() {
pkScript = p.ourPkScript
}
for i, txOut := range tx.TxOut {
cltv := cltvs[i]
if bytes.Equal(txOut.PkScript, pkScript) &&
txOut.Value == int64(p.Amount.ToSatoshis()) &&
cltv == p.Timeout {
// If this payment hash and index has already been
// found, then we'll continue in order to avoid any
// duplicate indexes.
if fn.Elem(int32(i), dups[p.RHash]) {
continue
}
idx := int32(i)
dups[p.RHash] = append(dups[p.RHash], idx)
return idx, nil
}
}
return 0, fmt.Errorf("unable to find htlc: script=%x, value=%v, "+
"cltv=%v", pkScript, p.Amount, p.Timeout)
}
// populateHtlcIndexes modifies the set of HTLCs locked-into the target view
// to have full indexing information populated. This information is required as
// we need to keep track of the indexes of each HTLC in order to properly write
// the current state to disk, and also to locate the paymentDescriptor
// corresponding to HTLC outputs in the commitment transaction.
func (c *commitment) populateHtlcIndexes(chanType channeldb.ChannelType,
cltvs []uint32) error {
// First, we'll set up some state to allow us to locate the output
// index of the all the HTLCs within the commitment transaction. We
// must keep this index so we can validate the HTLC signatures sent to
// us.
dups := make(map[PaymentHash][]int32)
c.outgoingHTLCIndex = make(map[int32]*paymentDescriptor)
c.incomingHTLCIndex = make(map[int32]*paymentDescriptor)
// populateIndex is a helper function that populates the necessary
// indexes within the commitment view for a particular HTLC.
populateIndex := func(htlc *paymentDescriptor, incoming bool) error {
isDust := HtlcIsDust(
chanType, incoming, c.whoseCommit, c.feePerKw,
htlc.Amount.ToSatoshis(), c.dustLimit,
)
var err error
switch {
// If this is our commitment transaction, and this is a dust
// output then we mark it as such using a -1 index.
case c.whoseCommit.IsLocal() && isDust:
htlc.localOutputIndex = -1
// If this is the commitment transaction of the remote party,
// and this is a dust output then we mark it as such using a -1
// index.
case c.whoseCommit.IsRemote() && isDust:
htlc.remoteOutputIndex = -1
// If this is our commitment transaction, then we'll need to
// locate the output and the index so we can verify an HTLC
// signatures.
case c.whoseCommit.IsLocal():
htlc.localOutputIndex, err = locateOutputIndex(
htlc, c.txn, c.whoseCommit, dups, cltvs,
)
if err != nil {
return err
}
// As this is our commitment transactions, we need to
// keep track of the locations of each output on the
// transaction so we can verify any HTLC signatures
// sent to us after we construct the HTLC view.
if incoming {
c.incomingHTLCIndex[htlc.localOutputIndex] = htlc
} else {
c.outgoingHTLCIndex[htlc.localOutputIndex] = htlc
}
// Otherwise, this is there remote party's commitment
// transaction and we only need to populate the remote output
// index within the HTLC index.
case c.whoseCommit.IsRemote():
htlc.remoteOutputIndex, err = locateOutputIndex(
htlc, c.txn, c.whoseCommit, dups, cltvs,
)
if err != nil {
return err
}
default:
return fmt.Errorf("invalid commitment configuration")
}
return nil
}
// Finally, we'll need to locate the index within the commitment
// transaction of all the HTLC outputs. This index will be required
// later when we write the commitment state to disk, and also when
// generating signatures for each of the HTLC transactions.
for i := 0; i < len(c.outgoingHTLCs); i++ {
htlc := &c.outgoingHTLCs[i]
if err := populateIndex(htlc, false); err != nil {
return err
}
}
for i := 0; i < len(c.incomingHTLCs); i++ {
htlc := &c.incomingHTLCs[i]
if err := populateIndex(htlc, true); err != nil {
return err
}
}
return nil
}
// toDiskCommit converts the target commitment into a format suitable to be
// written to disk after an accepted state transition.
func (c *commitment) toDiskCommit(
whoseCommit lntypes.ChannelParty) *channeldb.ChannelCommitment {
numHtlcs := len(c.outgoingHTLCs) + len(c.incomingHTLCs)
commit := &channeldb.ChannelCommitment{
CommitHeight: c.height,
LocalLogIndex: c.messageIndices.Local,
LocalHtlcIndex: c.ourHtlcIndex,
RemoteLogIndex: c.messageIndices.Remote,
RemoteHtlcIndex: c.theirHtlcIndex,
LocalBalance: c.ourBalance,
RemoteBalance: c.theirBalance,
CommitFee: c.fee,
FeePerKw: btcutil.Amount(c.feePerKw),
CommitTx: c.txn,
CommitSig: c.sig,
Htlcs: make([]channeldb.HTLC, 0, numHtlcs),
CustomBlob: c.customBlob,
}
for _, htlc := range c.outgoingHTLCs {
outputIndex := htlc.localOutputIndex
if whoseCommit.IsRemote() {
outputIndex = htlc.remoteOutputIndex
}
h := channeldb.HTLC{
RHash: htlc.RHash,
Amt: htlc.Amount,
RefundTimeout: htlc.Timeout,
OutputIndex: outputIndex,
HtlcIndex: htlc.HtlcIndex,
LogIndex: htlc.LogIndex,
Incoming: false,
OnionBlob: htlc.OnionBlob,
BlindingPoint: htlc.BlindingPoint,
CustomRecords: htlc.CustomRecords.Copy(),
}
if whoseCommit.IsLocal() && htlc.sig != nil {
h.Signature = htlc.sig.Serialize()
}
commit.Htlcs = append(commit.Htlcs, h)
}
for _, htlc := range c.incomingHTLCs {
outputIndex := htlc.localOutputIndex
if whoseCommit.IsRemote() {
outputIndex = htlc.remoteOutputIndex
}
h := channeldb.HTLC{
RHash: htlc.RHash,
Amt: htlc.Amount,
RefundTimeout: htlc.Timeout,
OutputIndex: outputIndex,
HtlcIndex: htlc.HtlcIndex,
LogIndex: htlc.LogIndex,
Incoming: true,
OnionBlob: htlc.OnionBlob,
BlindingPoint: htlc.BlindingPoint,
CustomRecords: htlc.CustomRecords.Copy(),
}
if whoseCommit.IsLocal() && htlc.sig != nil {
h.Signature = htlc.sig.Serialize()
}
commit.Htlcs = append(commit.Htlcs, h)
}
return commit
}
// diskHtlcToPayDesc converts an HTLC previously written to disk within a
// commitment state to the form required to manipulate in memory within the
// commitment struct and updateLog. This function is used when we need to
// restore commitment state written to disk back into memory once we need to
// restart a channel session.
func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight,
htlc *channeldb.HTLC, commitKeys lntypes.Dual[*CommitmentKeyRing],
whoseCommit lntypes.ChannelParty,
auxLeaf input.AuxTapLeaf) (paymentDescriptor, error) {
// The proper pkScripts for this paymentDescriptor must be
// generated so we can easily locate them within the commitment
// transaction in the future.
var (
ourP2WSH, theirP2WSH []byte
ourWitnessScript, theirWitnessScript []byte
pd paymentDescriptor
chanType = lc.channelState.ChanType
)
// If the either output is dust from the local or remote node's
// perspective, then we don't need to generate the scripts as we only
// generate them in order to locate the outputs within the commitment
// transaction. As we'll mark dust with a special output index in the
// on-disk state snapshot.
isDustLocal := HtlcIsDust(
chanType, htlc.Incoming, lntypes.Local, feeRate,
htlc.Amt.ToSatoshis(), lc.channelState.LocalChanCfg.DustLimit,
)
localCommitKeys := commitKeys.GetForParty(lntypes.Local)
if !isDustLocal && localCommitKeys != nil {
scriptInfo, err := genHtlcScript(
chanType, htlc.Incoming, lntypes.Local,
htlc.RefundTimeout, htlc.RHash, localCommitKeys,
auxLeaf,
)
if err != nil {
return pd, err
}
ourP2WSH = scriptInfo.PkScript()
ourWitnessScript = scriptInfo.WitnessScriptToSign()
}
isDustRemote := HtlcIsDust(
chanType, htlc.Incoming, lntypes.Remote, feeRate,
htlc.Amt.ToSatoshis(), lc.channelState.RemoteChanCfg.DustLimit,
)
remoteCommitKeys := commitKeys.GetForParty(lntypes.Remote)
if !isDustRemote && remoteCommitKeys != nil {
scriptInfo, err := genHtlcScript(
chanType, htlc.Incoming, lntypes.Remote,
htlc.RefundTimeout, htlc.RHash, remoteCommitKeys,
auxLeaf,
)
if err != nil {
return pd, err
}
theirP2WSH = scriptInfo.PkScript()
theirWitnessScript = scriptInfo.WitnessScriptToSign()
}
// Reconstruct the proper local/remote output indexes from the HTLC's
// persisted output index depending on whose commitment we are
// generating.
var (
localOutputIndex int32
remoteOutputIndex int32
)
if whoseCommit.IsLocal() {
localOutputIndex = htlc.OutputIndex
} else {
remoteOutputIndex = htlc.OutputIndex
}
// With the scripts reconstructed (depending on if this is our commit
// vs theirs or a pending commit for the remote party), we can now
// re-create the original payment descriptor.
return paymentDescriptor{
ChanID: lc.ChannelID(),
RHash: htlc.RHash,
Timeout: htlc.RefundTimeout,
Amount: htlc.Amt,
EntryType: Add,
HtlcIndex: htlc.HtlcIndex,
LogIndex: htlc.LogIndex,
OnionBlob: htlc.OnionBlob,
localOutputIndex: localOutputIndex,
remoteOutputIndex: remoteOutputIndex,
ourPkScript: ourP2WSH,
ourWitnessScript: ourWitnessScript,
theirPkScript: theirP2WSH,
theirWitnessScript: theirWitnessScript,
BlindingPoint: htlc.BlindingPoint,
CustomRecords: htlc.CustomRecords.Copy(),
}, nil
}
// extractPayDescs will convert all HTLC's present within a disk commit state
// to a set of incoming and outgoing payment descriptors. Once reconstructed,
// these payment descriptors can be re-inserted into the in-memory updateLog
// for each side.
func (lc *LightningChannel) extractPayDescs(feeRate chainfee.SatPerKWeight,
htlcs []channeldb.HTLC, commitKeys lntypes.Dual[*CommitmentKeyRing],
whoseCommit lntypes.ChannelParty,
auxLeaves fn.Option[CommitAuxLeaves]) ([]paymentDescriptor,
[]paymentDescriptor, error) {
var (
incomingHtlcs []paymentDescriptor
outgoingHtlcs []paymentDescriptor
)
// For each included HTLC within this commitment state, we'll convert
// the disk format into our in memory paymentDescriptor format,
// partitioning based on if we offered or received the HTLC.
for _, htlc := range htlcs {
// TODO(roasbeef): set isForwarded to false for all? need to
// persist state w.r.t to if forwarded or not, or can
// inadvertently trigger replays
htlc := htlc
auxLeaf := fn.FlatMapOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
leaves := l.OutgoingHtlcLeaves
if htlc.Incoming {
leaves = l.IncomingHtlcLeaves
}
return leaves[htlc.HtlcIndex].AuxTapLeaf
},
)(auxLeaves)
payDesc, err := lc.diskHtlcToPayDesc(
feeRate, &htlc, commitKeys, whoseCommit, auxLeaf,
)
if err != nil {
return incomingHtlcs, outgoingHtlcs, err
}
if htlc.Incoming {
incomingHtlcs = append(incomingHtlcs, payDesc)
} else {
outgoingHtlcs = append(outgoingHtlcs, payDesc)
}
}
return incomingHtlcs, outgoingHtlcs, nil
}
// diskCommitToMemCommit converts the on-disk commitment format to our
// in-memory commitment format which is needed in order to properly resume
// channel operations after a restart.
func (lc *LightningChannel) diskCommitToMemCommit(
whoseCommit lntypes.ChannelParty,
diskCommit *channeldb.ChannelCommitment, localCommitPoint,
remoteCommitPoint *btcec.PublicKey) (*commitment, error) {
// First, we'll need to re-derive the commitment key ring for each
// party used within this particular state. If this is a pending commit
// (we extended but weren't able to complete the commitment dance
// before shutdown), then the localCommitPoint won't be set as we
// haven't yet received a responding commitment from the remote party.
var commitKeys lntypes.Dual[*CommitmentKeyRing]
if localCommitPoint != nil {
commitKeys.SetForParty(lntypes.Local, DeriveCommitmentKeys(
localCommitPoint, lntypes.Local,
lc.channelState.ChanType,
&lc.channelState.LocalChanCfg,
&lc.channelState.RemoteChanCfg,
))
}
if remoteCommitPoint != nil {
commitKeys.SetForParty(lntypes.Remote, DeriveCommitmentKeys(
remoteCommitPoint, lntypes.Remote,
lc.channelState.ChanType,
&lc.channelState.LocalChanCfg,
&lc.channelState.RemoteChanCfg,
))
}
auxResult, err := fn.MapOptionZ(
lc.leafStore,
func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return s.FetchLeavesFromCommit(
NewAuxChanState(lc.channelState), *diskCommit,
*commitKeys.GetForParty(whoseCommit),
whoseCommit,
)
},
).Unpack()
if err != nil {
return nil, fmt.Errorf("unable to fetch aux leaves: %w", err)
}
// With the key rings re-created, we'll now convert all the on-disk
// HTLC"s into paymentDescriptor's so we can re-insert them into our
// update log.
incomingHtlcs, outgoingHtlcs, err := lc.extractPayDescs(
chainfee.SatPerKWeight(diskCommit.FeePerKw),
diskCommit.Htlcs, commitKeys, whoseCommit, auxResult.AuxLeaves,
)
if err != nil {
return nil, err
}
messageIndices := lntypes.Dual[uint64]{
Local: diskCommit.LocalLogIndex,
Remote: diskCommit.RemoteLogIndex,
}
// With the necessary items generated, we'll now re-construct the
// commitment state as it was originally present in memory.
commit := &commitment{
height: diskCommit.CommitHeight,
whoseCommit: whoseCommit,
ourBalance: diskCommit.LocalBalance,
theirBalance: diskCommit.RemoteBalance,
messageIndices: messageIndices,
ourHtlcIndex: diskCommit.LocalHtlcIndex,
theirHtlcIndex: diskCommit.RemoteHtlcIndex,
txn: diskCommit.CommitTx,
sig: diskCommit.CommitSig,
fee: diskCommit.CommitFee,
feePerKw: chainfee.SatPerKWeight(diskCommit.FeePerKw),
incomingHTLCs: incomingHtlcs,
outgoingHTLCs: outgoingHtlcs,
customBlob: diskCommit.CustomBlob,
}
if whoseCommit.IsLocal() {
commit.dustLimit = lc.channelState.LocalChanCfg.DustLimit
} else {
commit.dustLimit = lc.channelState.RemoteChanCfg.DustLimit
}
return commit, nil
}
// LightningChannel implements the state machine which corresponds to the
// current commitment protocol wire spec. The state machine implemented allows
// for asynchronous fully desynchronized, batched+pipelined updates to
// commitment transactions allowing for a high degree of non-blocking
// bi-directional payment throughput.
//
// In order to allow updates to be fully non-blocking, either side is able to
// create multiple new commitment states up to a pre-determined window size.
// This window size is encoded within InitialRevocationWindow. Before the start
// of a session, both side should send out revocation messages with nil
// preimages in order to populate their revocation window for the remote party.
//
// The state machine has for main methods:
// - .SignNextCommitment()
// - Called once when one wishes to sign the next commitment, either
// initiating a new state update, or responding to a received commitment.
// - .ReceiveNewCommitment()
// - Called upon receipt of a new commitment from the remote party. If the
// new commitment is valid, then a revocation should immediately be
// generated and sent.
// - .RevokeCurrentCommitment()
// - Revokes the current commitment. Should be called directly after
// receiving a new commitment.
// - .ReceiveRevocation()
// - Processes a revocation from the remote party. If successful creates a
// new defacto broadcastable state.
//
// See the individual comments within the above methods for further details.
type LightningChannel struct {
// Signer is the main signer instances that will be responsible for
// signing any HTLC and commitment transaction generated by the state
// machine.
Signer input.Signer
// leafStore is used to retrieve extra tapscript leaves for special
// custom channel types.
leafStore fn.Option[AuxLeafStore]
// signDesc is the primary sign descriptor that is capable of signing
// the commitment transaction that spends the multi-sig output.
signDesc *input.SignDescriptor
isClosed bool
// sigPool is a pool of workers that are capable of signing and
// validating signatures in parallel. This is utilized as an
// optimization to void serially signing or validating the HTLC
// signatures, of which there may be hundreds.
sigPool *SigPool
// auxSigner is a special signer used to obtain opaque signatures for
// custom channel variants.
auxSigner fn.Option[AuxSigner]
// auxResolver is an optional component that can be used to modify the
// way contracts are resolved.
auxResolver fn.Option[AuxContractResolver]
// Capacity is the total capacity of this channel.
Capacity btcutil.Amount
// currentHeight is the current height of our local commitment chain.
// This is also the same as the number of updates to the channel we've
// accepted.
currentHeight uint64
// commitChains is a Dual of the local and remote node's commitment
// chains. Any new commitments we initiate are added to Remote chain's
// tip. The Local portion of this field is our local commitment chain.
// Any new commitments received are added to the tip of this chain.
// The tail (or lowest height) in this chain is our current accepted
// state, which we are able to broadcast safely.
commitChains lntypes.Dual[*commitmentChain]
channelState *channeldb.OpenChannel
commitBuilder *CommitmentBuilder
// [local|remote]Log is a (mostly) append-only log storing all the HTLC
// updates to this channel. The log is walked backwards as HTLC updates
// are applied in order to re-construct a commitment transaction from a
// commitment. The log is compacted once a revocation is received.
updateLogs lntypes.Dual[*updateLog]
// log is a channel-specific logging instance.
log btclog.Logger
// taprootNonceProducer is used to generate a shachain tree for the
// purpose of generating verification nonces for taproot channels.
taprootNonceProducer shachain.Producer
// musigSessions holds the current musig2 pair session for the channel.
musigSessions *MusigPairSession
// pendingVerificationNonce is the initial verification nonce generated
// for musig2 channels when the state machine is intiated. Once we know
// the verification nonce of the remote party, then we can start to use
// the channel as normal.
pendingVerificationNonce *musig2.Nonces
// fundingOutput is the funding output (script+value).
fundingOutput wire.TxOut
// opts is the set of options that channel was initialized with.
opts *channelOpts
sync.RWMutex
}
// ChannelOpt is a functional option that lets callers modify how a new channel
// is created.
type ChannelOpt func(*channelOpts)
// channelOpts is the set of options used to create a new channel.
type channelOpts struct {
localNonce *musig2.Nonces
remoteNonce *musig2.Nonces
leafStore fn.Option[AuxLeafStore]
auxSigner fn.Option[AuxSigner]
auxResolver fn.Option[AuxContractResolver]
skipNonceInit bool
}
// WithLocalMusigNonces is used to bind an existing verification/local nonce to
// a new channel.
func WithLocalMusigNonces(nonce *musig2.Nonces) ChannelOpt {
return func(o *channelOpts) {
o.localNonce = nonce
}
}
// WithRemoteMusigNonces is used to bind the remote party's local/verification
// nonce to a new channel.
func WithRemoteMusigNonces(nonces *musig2.Nonces) ChannelOpt {
return func(o *channelOpts) {
o.remoteNonce = nonces
}
}
// WithSkipNonceInit is used to modify the way nonces are handled during
// channel initialization for taproot channels. If this option is specified,
// then when we receive the chan reest message from the remote party, we won't
// modify our nonce state. This is needed if we create a channel, get a channel
// ready message, then also get the chan reest message after that.
func WithSkipNonceInit() ChannelOpt {
return func(o *channelOpts) {
o.skipNonceInit = true
}
}
// WithLeafStore is used to specify a custom leaf store for the channel.
func WithLeafStore(store AuxLeafStore) ChannelOpt {
return func(o *channelOpts) {
o.leafStore = fn.Some[AuxLeafStore](store)
}
}
// WithAuxSigner is used to specify a custom aux signer for the channel.
func WithAuxSigner(signer AuxSigner) ChannelOpt {
return func(o *channelOpts) {
o.auxSigner = fn.Some[AuxSigner](signer)
}
}
// WithAuxResolver is used to specify a custom aux contract resolver for the
// channel.
func WithAuxResolver(resolver AuxContractResolver) ChannelOpt {
return func(o *channelOpts) {
o.auxResolver = fn.Some[AuxContractResolver](resolver)
}
}
// defaultChannelOpts returns the set of default options for a new channel.
func defaultChannelOpts() *channelOpts {
return &channelOpts{}
}
// NewLightningChannel creates a new, active payment channel given an
// implementation of the chain notifier, channel database, and the current
// settled channel state. Throughout state transitions, then channel will
// automatically persist pertinent state to the database in an efficient
// manner.
func NewLightningChannel(signer input.Signer,
state *channeldb.OpenChannel,
sigPool *SigPool, chanOpts ...ChannelOpt) (*LightningChannel, error) {
opts := defaultChannelOpts()
for _, optFunc := range chanOpts {
optFunc(opts)
}
localCommit := state.LocalCommitment
remoteCommit := state.RemoteCommitment
// First, initialize the update logs with their current counter values
// from the local and remote commitments.
localUpdateLog := newUpdateLog(
remoteCommit.LocalLogIndex, remoteCommit.LocalHtlcIndex,
)
remoteUpdateLog := newUpdateLog(
localCommit.RemoteLogIndex, localCommit.RemoteHtlcIndex,
)
updateLogs := lntypes.Dual[*updateLog]{
Local: localUpdateLog,
Remote: remoteUpdateLog,
}
logPrefix := fmt.Sprintf("ChannelPoint(%v):", state.FundingOutpoint)
taprootNonceProducer, err := channeldb.DeriveMusig2Shachain(
state.RevocationProducer,
)
if err != nil {
return nil, fmt.Errorf("unable to derive shachain: %w", err)
}
commitChains := lntypes.Dual[*commitmentChain]{
Local: newCommitmentChain(),
Remote: newCommitmentChain(),
}
lc := &LightningChannel{
Signer: signer,
leafStore: opts.leafStore,
auxSigner: opts.auxSigner,
auxResolver: opts.auxResolver,
sigPool: sigPool,
currentHeight: localCommit.CommitHeight,
commitChains: commitChains,
channelState: state,
commitBuilder: NewCommitmentBuilder(
state, opts.leafStore,
),
updateLogs: updateLogs,
Capacity: state.Capacity,
taprootNonceProducer: taprootNonceProducer,
log: walletLog.WithPrefix(logPrefix),
opts: opts,
}
switch {
// At this point, we may already have nonces that were passed in, so
// we'll check that now as this lets us skip some steps later.
case state.ChanType.IsTaproot() && opts.localNonce != nil:
lc.pendingVerificationNonce = opts.localNonce
// Otherwise, we'll generate the nonces here ourselves. This ensures
// we'll be ablve to process the chan syncmessag efrom the remote
// party.
case state.ChanType.IsTaproot() && opts.localNonce == nil:
_, err := lc.GenMusigNonces()
if err != nil {
return nil, err
}
}
if lc.pendingVerificationNonce != nil && opts.remoteNonce != nil {
err := lc.InitRemoteMusigNonces(opts.remoteNonce)
if err != nil {
return nil, err
}
}
// With the main channel struct reconstructed, we'll now restore the
// commitment state in memory and also the update logs themselves.
err = lc.restoreCommitState(&localCommit, &remoteCommit)
if err != nil {
return nil, err
}
// Create the sign descriptor which we'll be using very frequently to
// request a signature for the 2-of-2 multi-sig from the signer in
// order to complete channel state transitions.
if err := lc.createSignDesc(); err != nil {
return nil, err
}
return lc, nil
}
// createSignDesc derives the SignDescriptor for commitment transactions from
// other fields on the LightningChannel.
func (lc *LightningChannel) createSignDesc() error {
var (
fundingPkScript, multiSigScript []byte
err error
)
chanState := lc.channelState
localKey := chanState.LocalChanCfg.MultiSigKey.PubKey
remoteKey := chanState.RemoteChanCfg.MultiSigKey.PubKey
if chanState.ChanType.IsTaproot() {
fundingPkScript, _, err = input.GenTaprootFundingScript(
localKey, remoteKey, int64(lc.channelState.Capacity),
chanState.TapscriptRoot,
)
if err != nil {
return err
}
} else {
multiSigScript, err = input.GenMultiSigScript(
localKey.SerializeCompressed(),
remoteKey.SerializeCompressed(),
)
if err != nil {
return err
}
fundingPkScript, err = input.WitnessScriptHash(multiSigScript)
if err != nil {
return err
}
}
lc.fundingOutput = wire.TxOut{
PkScript: fundingPkScript,
Value: int64(lc.channelState.Capacity),
}
lc.signDesc = &input.SignDescriptor{
KeyDesc: lc.channelState.LocalChanCfg.MultiSigKey,
WitnessScript: multiSigScript,
Output: &lc.fundingOutput,
HashType: txscript.SigHashAll,
InputIndex: 0,
}
return nil
}
// ResetState resets the state of the channel back to the default state. This
// ensures that any active goroutines which need to act based on on-chain
// events do so properly.
func (lc *LightningChannel) ResetState() {
lc.Lock()
lc.isClosed = false
lc.Unlock()
}
// logUpdateToPayDesc converts a LogUpdate into a matching paymentDescriptor
// entry that can be re-inserted into the update log. This method is used when
// we extended a state to the remote party, but the connection was obstructed
// before we could finish the commitment dance. In this case, we need to
// re-insert the original entries back into the update log so we can resume as
// if nothing happened.
func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate,
remoteUpdateLog *updateLog, commitHeight uint64,
feeRate chainfee.SatPerKWeight, remoteCommitKeys *CommitmentKeyRing,
remoteDustLimit btcutil.Amount,
auxLeaves fn.Option[CommitAuxLeaves]) (*paymentDescriptor, error) {
// Depending on the type of update message we'll map that to a distinct
// paymentDescriptor instance.
var pd *paymentDescriptor
switch wireMsg := logUpdate.UpdateMsg.(type) {
// For offered HTLC's, we'll map that to a paymentDescriptor with the
// type Add, ensuring we restore the necessary fields. From the PoV of
// the commitment chain, this HTLC was included in the remote chain,
// but not the local chain.
case *lnwire.UpdateAddHTLC:
// First, we'll map all the relevant fields in the
// UpdateAddHTLC message to their corresponding fields in the
// paymentDescriptor struct. We also set addCommitHeightRemote
// as we've included this HTLC in our local commitment chain
// for the remote party.
pd = &paymentDescriptor{
ChanID: wireMsg.ChanID,
RHash: wireMsg.PaymentHash,
Timeout: wireMsg.Expiry,
Amount: wireMsg.Amount,
EntryType: Add,
HtlcIndex: wireMsg.ID,
LogIndex: logUpdate.LogIndex,
OnionBlob: wireMsg.OnionBlob,
BlindingPoint: wireMsg.BlindingPoint,
CustomRecords: wireMsg.CustomRecords.Copy(),
addCommitHeights: lntypes.Dual[uint64]{
Remote: commitHeight,
},
}
isDustRemote := HtlcIsDust(
lc.channelState.ChanType, false, lntypes.Remote,
feeRate, wireMsg.Amount.ToSatoshis(), remoteDustLimit,
)
if !isDustRemote {
auxLeaf := fn.FlatMapOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
leaves := l.OutgoingHtlcLeaves
return leaves[pd.HtlcIndex].AuxTapLeaf
},
)(auxLeaves)
scriptInfo, err := genHtlcScript(
lc.channelState.ChanType, false, lntypes.Remote,
wireMsg.Expiry, wireMsg.PaymentHash,
remoteCommitKeys, auxLeaf,
)
if err != nil {
return nil, err
}
pd.theirPkScript = scriptInfo.PkScript()
pd.theirWitnessScript = scriptInfo.WitnessScriptToSign()
}
// For HTLC's we're offered we'll fetch the original offered HTLC
// from the remote party's update log so we can retrieve the same
// paymentDescriptor that SettleHTLC would produce.
case *lnwire.UpdateFulfillHTLC:
ogHTLC := remoteUpdateLog.lookupHtlc(wireMsg.ID)
pd = &paymentDescriptor{
ChanID: wireMsg.ChanID,
Amount: ogHTLC.Amount,
RHash: ogHTLC.RHash,
RPreimage: wireMsg.PaymentPreimage,
LogIndex: logUpdate.LogIndex,
ParentIndex: ogHTLC.HtlcIndex,
EntryType: Settle,
removeCommitHeights: lntypes.Dual[uint64]{
Remote: commitHeight,
},
}
// If we sent a failure for a prior incoming HTLC, then we'll consult
// the update log of the remote party so we can retrieve the
// information of the original HTLC we're failing. We also set the
// removal height for the remote commitment.
case *lnwire.UpdateFailHTLC:
ogHTLC := remoteUpdateLog.lookupHtlc(wireMsg.ID)
pd = &paymentDescriptor{
ChanID: wireMsg.ChanID,
Amount: ogHTLC.Amount,
RHash: ogHTLC.RHash,
ParentIndex: ogHTLC.HtlcIndex,
LogIndex: logUpdate.LogIndex,
EntryType: Fail,
FailReason: wireMsg.Reason[:],
removeCommitHeights: lntypes.Dual[uint64]{
Remote: commitHeight,
},
}
// HTLC fails due to malformed onion blobs are treated the exact same
// way as regular HTLC fails.
case *lnwire.UpdateFailMalformedHTLC:
ogHTLC := remoteUpdateLog.lookupHtlc(wireMsg.ID)
// TODO(roasbeef): err if nil?
pd = &paymentDescriptor{
ChanID: wireMsg.ChanID,
Amount: ogHTLC.Amount,
RHash: ogHTLC.RHash,
ParentIndex: ogHTLC.HtlcIndex,
LogIndex: logUpdate.LogIndex,
EntryType: MalformedFail,
FailCode: wireMsg.FailureCode,
ShaOnionBlob: wireMsg.ShaOnionBlob,
removeCommitHeights: lntypes.Dual[uint64]{
Remote: commitHeight,
},
}
// For fee updates we'll create a FeeUpdate type to add to the log. We
// reuse the amount field to hold the fee rate. Since the amount field
// is denominated in msat we won't lose precision when storing the
// sat/kw denominated feerate. Note that we set both the add and remove
// height to the same value, as we consider the fee update locked in by
// adding and removing it at the same height.
case *lnwire.UpdateFee:
pd = &paymentDescriptor{
ChanID: wireMsg.ChanID,
LogIndex: logUpdate.LogIndex,
Amount: lnwire.NewMSatFromSatoshis(
btcutil.Amount(wireMsg.FeePerKw),
),
EntryType: FeeUpdate,
addCommitHeights: lntypes.Dual[uint64]{
Remote: commitHeight,
},
removeCommitHeights: lntypes.Dual[uint64]{
Remote: commitHeight,
},
}
}
return pd, nil
}
// localLogUpdateToPayDesc converts a LogUpdate into a matching
// paymentDescriptor entry that can be re-inserted into the local update log.
// This method is used when we sent an update+sig, receive a revocation, but
// drop right before the counterparty can sign for the update we just sent. In
// this case, we need to re-insert the original entries back into the update
// log so we'll be expecting the peer to sign them. The height of the remote
// commitment is expected to be provided and we restore all log update entries
// with this height, even though the real height may be lower. In the way these
// fields are used elsewhere, this doesn't change anything.
func (lc *LightningChannel) localLogUpdateToPayDesc(logUpdate *channeldb.LogUpdate,
remoteUpdateLog *updateLog, commitHeight uint64) (*paymentDescriptor,
error) {
// Since Add updates aren't saved to disk under this key, the update will
// never be an Add.
switch wireMsg := logUpdate.UpdateMsg.(type) {
// For HTLCs that we settled, we'll fetch the original offered HTLC from
// the remote update log so we can retrieve the same paymentDescriptor
// that ReceiveHTLCSettle would produce.
case *lnwire.UpdateFulfillHTLC:
ogHTLC := remoteUpdateLog.lookupHtlc(wireMsg.ID)
return &paymentDescriptor{
ChanID: wireMsg.ChanID,
Amount: ogHTLC.Amount,
RHash: ogHTLC.RHash,
RPreimage: wireMsg.PaymentPreimage,
LogIndex: logUpdate.LogIndex,
ParentIndex: ogHTLC.HtlcIndex,
EntryType: Settle,
removeCommitHeights: lntypes.Dual[uint64]{
Remote: commitHeight,
},
}, nil
// If we sent a failure for a prior incoming HTLC, then we'll consult the
// remote update log so we can retrieve the information of the original
// HTLC we're failing.
case *lnwire.UpdateFailHTLC:
ogHTLC := remoteUpdateLog.lookupHtlc(wireMsg.ID)
return &paymentDescriptor{
ChanID: wireMsg.ChanID,
Amount: ogHTLC.Amount,
RHash: ogHTLC.RHash,
ParentIndex: ogHTLC.HtlcIndex,
LogIndex: logUpdate.LogIndex,
EntryType: Fail,
FailReason: wireMsg.Reason[:],
removeCommitHeights: lntypes.Dual[uint64]{
Remote: commitHeight,
},
}, nil
// HTLC fails due to malformed onion blocks are treated the exact same
// way as regular HTLC fails.
case *lnwire.UpdateFailMalformedHTLC:
ogHTLC := remoteUpdateLog.lookupHtlc(wireMsg.ID)
return &paymentDescriptor{
ChanID: wireMsg.ChanID,
Amount: ogHTLC.Amount,
RHash: ogHTLC.RHash,
ParentIndex: ogHTLC.HtlcIndex,
LogIndex: logUpdate.LogIndex,
EntryType: MalformedFail,
FailCode: wireMsg.FailureCode,
ShaOnionBlob: wireMsg.ShaOnionBlob,
removeCommitHeights: lntypes.Dual[uint64]{
Remote: commitHeight,
},
}, nil
case *lnwire.UpdateFee:
return &paymentDescriptor{
ChanID: wireMsg.ChanID,
LogIndex: logUpdate.LogIndex,
Amount: lnwire.NewMSatFromSatoshis(
btcutil.Amount(wireMsg.FeePerKw),
),
EntryType: FeeUpdate,
addCommitHeights: lntypes.Dual[uint64]{
Remote: commitHeight,
},
removeCommitHeights: lntypes.Dual[uint64]{
Remote: commitHeight,
},
}, nil
default:
return nil, fmt.Errorf("unknown message type: %T", wireMsg)
}
}
// remoteLogUpdateToPayDesc converts a LogUpdate into a matching
// paymentDescriptor entry that can be re-inserted into the update log. This
// method is used when we revoked a local commitment, but the connection was
// obstructed before we could sign a remote commitment that contains these
// updates. In this case, we need to re-insert the original entries back into
// the update log so we can resume as if nothing happened. The height of the
// latest local commitment is also expected to be provided. We are restoring all
// log update entries with this height, even though the real commitment height
// may be lower. In the way these fields are used elsewhere, this doesn't change
// anything.
func (lc *LightningChannel) remoteLogUpdateToPayDesc(logUpdate *channeldb.LogUpdate,
localUpdateLog *updateLog, commitHeight uint64) (*paymentDescriptor,
error) {
switch wireMsg := logUpdate.UpdateMsg.(type) {
case *lnwire.UpdateAddHTLC:
pd := &paymentDescriptor{
ChanID: wireMsg.ChanID,
RHash: wireMsg.PaymentHash,
Timeout: wireMsg.Expiry,
Amount: wireMsg.Amount,
EntryType: Add,
HtlcIndex: wireMsg.ID,
LogIndex: logUpdate.LogIndex,
OnionBlob: wireMsg.OnionBlob,
BlindingPoint: wireMsg.BlindingPoint,
CustomRecords: wireMsg.CustomRecords.Copy(),
addCommitHeights: lntypes.Dual[uint64]{
Local: commitHeight,
},
}
// We don't need to generate an htlc script yet. This will be
// done once we sign our remote commitment.
return pd, nil
// For HTLCs that the remote party settled, we'll fetch the original
// offered HTLC from the local update log so we can retrieve the same
// paymentDescriptor that ReceiveHTLCSettle would produce.
case *lnwire.UpdateFulfillHTLC:
ogHTLC := localUpdateLog.lookupHtlc(wireMsg.ID)
return &paymentDescriptor{
ChanID: wireMsg.ChanID,
Amount: ogHTLC.Amount,
RHash: ogHTLC.RHash,
RPreimage: wireMsg.PaymentPreimage,
LogIndex: logUpdate.LogIndex,
ParentIndex: ogHTLC.HtlcIndex,
EntryType: Settle,
removeCommitHeights: lntypes.Dual[uint64]{
Local: commitHeight,
},
}, nil
// If we received a failure for a prior outgoing HTLC, then we'll
// consult the local update log so we can retrieve the information of
// the original HTLC we're failing.
case *lnwire.UpdateFailHTLC:
ogHTLC := localUpdateLog.lookupHtlc(wireMsg.ID)
return &paymentDescriptor{
ChanID: wireMsg.ChanID,
Amount: ogHTLC.Amount,
RHash: ogHTLC.RHash,
ParentIndex: ogHTLC.HtlcIndex,
LogIndex: logUpdate.LogIndex,
EntryType: Fail,
FailReason: wireMsg.Reason[:],
removeCommitHeights: lntypes.Dual[uint64]{
Local: commitHeight,
},
}, nil
// HTLC fails due to malformed onion blobs are treated the exact same
// way as regular HTLC fails.
case *lnwire.UpdateFailMalformedHTLC:
ogHTLC := localUpdateLog.lookupHtlc(wireMsg.ID)
return &paymentDescriptor{
ChanID: wireMsg.ChanID,
Amount: ogHTLC.Amount,
RHash: ogHTLC.RHash,
ParentIndex: ogHTLC.HtlcIndex,
LogIndex: logUpdate.LogIndex,
EntryType: MalformedFail,
FailCode: wireMsg.FailureCode,
ShaOnionBlob: wireMsg.ShaOnionBlob,
removeCommitHeights: lntypes.Dual[uint64]{
Local: commitHeight,
},
}, nil
// For fee updates we'll create a FeeUpdate type to add to the log. We
// reuse the amount field to hold the fee rate. Since the amount field
// is denominated in msat we won't lose precision when storing the
// sat/kw denominated feerate. Note that we set both the add and remove
// height to the same value, as we consider the fee update locked in by
// adding and removing it at the same height.
case *lnwire.UpdateFee:
return &paymentDescriptor{
ChanID: wireMsg.ChanID,
LogIndex: logUpdate.LogIndex,
Amount: lnwire.NewMSatFromSatoshis(
btcutil.Amount(wireMsg.FeePerKw),
),
EntryType: FeeUpdate,
addCommitHeights: lntypes.Dual[uint64]{
Local: commitHeight,
},
removeCommitHeights: lntypes.Dual[uint64]{
Local: commitHeight,
},
}, nil
default:
return nil, errors.New("unknown message type")
}
}
// restoreCommitState will restore the local commitment chain and updateLog
// state to a consistent in-memory representation of the passed disk commitment.
// This method is to be used upon reconnection to our channel counter party.
// Once the connection has been established, we'll prepare our in memory state
// to re-sync states with the remote party, and also verify/extend new proposed
// commitment states.
func (lc *LightningChannel) restoreCommitState(
localCommitState, remoteCommitState *channeldb.ChannelCommitment) error {
// In order to reconstruct the pkScripts on each of the pending HTLC
// outputs (if any) we'll need to regenerate the current revocation for
// this current un-revoked state as well as retrieve the current
// revocation for the remote party.
ourRevPreImage, err := lc.channelState.RevocationProducer.AtIndex(
lc.currentHeight,
)
if err != nil {
return err
}
localCommitPoint := input.ComputeCommitmentPoint(ourRevPreImage[:])
remoteCommitPoint := lc.channelState.RemoteCurrentRevocation
// With the revocation state reconstructed, we can now convert the disk
// commitment into our in-memory commitment format, inserting it into
// the local commitment chain.
localCommit, err := lc.diskCommitToMemCommit(
lntypes.Local, localCommitState, localCommitPoint,
remoteCommitPoint,
)
if err != nil {
return err
}
lc.commitChains.Local.addCommitment(localCommit)
lc.log.Tracef("starting local commitment: %v",
lnutils.SpewLogClosure(lc.commitChains.Local.tail()))
// We'll also do the same for the remote commitment chain.
remoteCommit, err := lc.diskCommitToMemCommit(
lntypes.Remote, remoteCommitState, localCommitPoint,
remoteCommitPoint,
)
if err != nil {
return err
}
lc.commitChains.Remote.addCommitment(remoteCommit)
lc.log.Tracef("starting remote commitment: %v",
lnutils.SpewLogClosure(lc.commitChains.Remote.tail()))
var (
pendingRemoteCommit *commitment
pendingRemoteCommitDiff *channeldb.CommitDiff
pendingRemoteKeyChain *CommitmentKeyRing
)
// Next, we'll check to see if we have an un-acked commitment state we
// extended to the remote party but which was never ACK'd.
pendingRemoteCommitDiff, err = lc.channelState.RemoteCommitChainTip()
if err != nil && err != channeldb.ErrNoPendingCommit {
return err
}
if pendingRemoteCommitDiff != nil {
// If we have a pending remote commitment, then we'll also
// reconstruct the original commitment for that state,
// inserting it into the remote party's commitment chain. We
// don't pass our commit point as we don't have the
// corresponding state for the local commitment chain.
pendingCommitPoint := lc.channelState.RemoteNextRevocation
pendingRemoteCommit, err = lc.diskCommitToMemCommit(
lntypes.Remote, &pendingRemoteCommitDiff.Commitment,
nil, pendingCommitPoint,
)
if err != nil {
return err
}
lc.commitChains.Remote.addCommitment(pendingRemoteCommit)
lc.log.Debugf("pending remote commitment: %v",
lnutils.SpewLogClosure(lc.commitChains.Remote.tip()))
// We'll also re-create the set of commitment keys needed to
// fully re-derive the state.
pendingRemoteKeyChain = DeriveCommitmentKeys(
pendingCommitPoint, lntypes.Remote,
lc.channelState.ChanType,
&lc.channelState.LocalChanCfg,
&lc.channelState.RemoteChanCfg,
)
}
// Fetch remote updates that we have acked but not yet signed for.
unsignedAckedUpdates, err := lc.channelState.UnsignedAckedUpdates()
if err != nil {
return err
}
// Fetch the local updates the peer still needs to sign for.
remoteUnsignedLocalUpdates, err := lc.channelState.RemoteUnsignedLocalUpdates()
if err != nil {
return err
}
// Finally, with the commitment states restored, we'll now restore the
// state logs based on the current local+remote commit, and any pending
// remote commit that exists.
err = lc.restoreStateLogs(
localCommit, remoteCommit, pendingRemoteCommit,
pendingRemoteCommitDiff, pendingRemoteKeyChain,
unsignedAckedUpdates, remoteUnsignedLocalUpdates,
)
if err != nil {
return err
}
return nil
}
// restoreStateLogs runs through the current locked-in HTLCs from the point of
// view of the channel and insert corresponding log entries (both local and
// remote) for each HTLC read from disk. This method is required to sync the
// in-memory state of the state machine with that read from persistent storage.
func (lc *LightningChannel) restoreStateLogs(
localCommitment, remoteCommitment, pendingRemoteCommit *commitment,
pendingRemoteCommitDiff *channeldb.CommitDiff,
pendingRemoteKeys *CommitmentKeyRing,
unsignedAckedUpdates,
remoteUnsignedLocalUpdates []channeldb.LogUpdate) error {
// We make a map of incoming HTLCs to the height of the remote
// commitment they were first added, and outgoing HTLCs to the height
// of the local commit they were first added. This will be used when we
// restore the update logs below.
incomingRemoteAddHeights := make(map[uint64]uint64)
outgoingLocalAddHeights := make(map[uint64]uint64)
// We start by setting the height of the incoming HTLCs on the pending
// remote commitment. We set these heights first since if there are
// duplicates, these will be overwritten by the lower height of the
// remoteCommitment below.
if pendingRemoteCommit != nil {
for _, r := range pendingRemoteCommit.incomingHTLCs {
incomingRemoteAddHeights[r.HtlcIndex] =
pendingRemoteCommit.height
}
}
// Now set the remote commit height of all incoming HTLCs found on the
// remote commitment.
for _, r := range remoteCommitment.incomingHTLCs {
incomingRemoteAddHeights[r.HtlcIndex] = remoteCommitment.height
}
// And finally we can do the same for the outgoing HTLCs.
for _, l := range localCommitment.outgoingHTLCs {
outgoingLocalAddHeights[l.HtlcIndex] = localCommitment.height
}
// If we have any unsigned acked updates to sign for, then the add is no
// longer on our local commitment, but is still on the remote's commitment.
// <---fail---
// <---sig----
// ----rev--->
// To ensure proper channel operation, we restore the add's addCommitHeightLocal
// field to the height of our local commitment.
for _, logUpdate := range unsignedAckedUpdates {
var htlcIdx uint64
switch wireMsg := logUpdate.UpdateMsg.(type) {
case *lnwire.UpdateFulfillHTLC:
htlcIdx = wireMsg.ID
case *lnwire.UpdateFailHTLC:
htlcIdx = wireMsg.ID
case *lnwire.UpdateFailMalformedHTLC:
htlcIdx = wireMsg.ID
default:
continue
}
// The htlcIdx is stored in the map with the local commitment
// height so the related add's addCommitHeightLocal field can be
// restored.
outgoingLocalAddHeights[htlcIdx] = localCommitment.height
}
// If there are local updates that the peer needs to sign for, then the
// corresponding add is no longer on the remote commitment, but is still on
// our local commitment.
// ----fail--->
// ----sig---->
// <---rev-----
// To ensure proper channel operation, we restore the add's addCommitHeightRemote
// field to the height of the remote commitment.
for _, logUpdate := range remoteUnsignedLocalUpdates {
var htlcIdx uint64
switch wireMsg := logUpdate.UpdateMsg.(type) {
case *lnwire.UpdateFulfillHTLC:
htlcIdx = wireMsg.ID
case *lnwire.UpdateFailHTLC:
htlcIdx = wireMsg.ID
case *lnwire.UpdateFailMalformedHTLC:
htlcIdx = wireMsg.ID
default:
continue
}
// The htlcIdx is stored in the map with the remote commitment
// height so the related add's addCommitHeightRemote field can be
// restored.
incomingRemoteAddHeights[htlcIdx] = remoteCommitment.height
}
// For each incoming HTLC within the local commitment, we add it to the
// remote update log. Since HTLCs are added first to the receiver's
// commitment, we don't have to restore outgoing HTLCs, as they will be
// restored from the remote commitment below.
for i := range localCommitment.incomingHTLCs {
htlc := localCommitment.incomingHTLCs[i]
// We'll need to set the add height of the HTLC. Since it is on
// this local commit, we can use its height as local add
// height. As remote add height we consult the incoming HTLC
// map we created earlier. Note that if this HTLC is not in
// incomingRemoteAddHeights, the remote add height will be set
// to zero, which indicates that it is not added yet.
htlc.addCommitHeights.Local = localCommitment.height
htlc.addCommitHeights.Remote =
incomingRemoteAddHeights[htlc.HtlcIndex]
// Restore the htlc back to the remote log.
lc.updateLogs.Remote.restoreHtlc(&htlc)
}
// Similarly, we'll do the same for the outgoing HTLCs within the
// remote commitment, adding them to the local update log.
for i := range remoteCommitment.outgoingHTLCs {
htlc := remoteCommitment.outgoingHTLCs[i]
// As for the incoming HTLCs, we'll use the current remote
// commit height as remote add height, and consult the map
// created above for the local add height.
htlc.addCommitHeights.Remote = remoteCommitment.height
htlc.addCommitHeights.Local =
outgoingLocalAddHeights[htlc.HtlcIndex]
// Restore the htlc back to the local log.
lc.updateLogs.Local.restoreHtlc(&htlc)
}
// If we have a dangling (un-acked) commit for the remote party, then we
// restore the updates leading up to this commit.
if pendingRemoteCommit != nil {
err := lc.restorePendingLocalUpdates(
pendingRemoteCommitDiff, pendingRemoteKeys,
)
if err != nil {
return err
}
}
// Restore unsigned acked remote log updates so that we can include them
// in our next signature.
err := lc.restorePendingRemoteUpdates(
unsignedAckedUpdates, localCommitment.height,
pendingRemoteCommit,
)
if err != nil {
return err
}
// Restore unsigned acked local log updates so we expect the peer to
// sign for them.
return lc.restorePeerLocalUpdates(
remoteUnsignedLocalUpdates, remoteCommitment.height,
)
}
// restorePendingRemoteUpdates restores the acked remote log updates that we
// haven't yet signed for.
func (lc *LightningChannel) restorePendingRemoteUpdates(
unsignedAckedUpdates []channeldb.LogUpdate,
localCommitmentHeight uint64,
pendingRemoteCommit *commitment) error {
lc.log.Debugf("Restoring %v dangling remote updates pending our sig",
len(unsignedAckedUpdates))
for _, logUpdate := range unsignedAckedUpdates {
logUpdate := logUpdate
payDesc, err := lc.remoteLogUpdateToPayDesc(
&logUpdate, lc.updateLogs.Local, localCommitmentHeight,
)
if err != nil {
return err
}
logIdx := payDesc.LogIndex
// Sanity check that we are not restoring a remote log update
// that we haven't received a sig for.
if logIdx >= lc.updateLogs.Remote.logIndex {
return fmt.Errorf("attempted to restore an "+
"unsigned remote update: log_index=%v",
logIdx)
}
// We previously restored Adds along with all the other updates,
// but this Add restoration was a no-op as every single one of
// these Adds was already restored since they're all incoming
// htlcs on the local commitment.
if payDesc.EntryType == Add {
continue
}
var (
height uint64
heightSet bool
)
// If we have a pending commitment for them, and this update
// is included in that commit, then we'll use this commitment
// height as this commitment will include these updates for
// their new remote commitment.
if pendingRemoteCommit != nil {
if logIdx < pendingRemoteCommit.messageIndices.Remote {
height = pendingRemoteCommit.height
heightSet = true
}
}
// Insert the update into the log. The log update index doesn't
// need to be incremented (hence the restore calls), because its
// final value was properly persisted with the last local
// commitment update.
switch payDesc.EntryType {
case FeeUpdate:
if heightSet {
payDesc.addCommitHeights.Remote = height
payDesc.removeCommitHeights.Remote = height
}
lc.updateLogs.Remote.restoreUpdate(payDesc)
default:
if heightSet {
payDesc.removeCommitHeights.Remote = height
}
lc.updateLogs.Remote.restoreUpdate(payDesc)
lc.updateLogs.Local.markHtlcModified(
payDesc.ParentIndex,
)
}
}
return nil
}
// restorePeerLocalUpdates restores the acked local log updates the peer still
// needs to sign for.
func (lc *LightningChannel) restorePeerLocalUpdates(updates []channeldb.LogUpdate,
remoteCommitmentHeight uint64) error {
lc.log.Debugf("Restoring %v local updates that the peer should sign",
len(updates))
for _, logUpdate := range updates {
logUpdate := logUpdate
payDesc, err := lc.localLogUpdateToPayDesc(
&logUpdate, lc.updateLogs.Remote,
remoteCommitmentHeight,
)
if err != nil {
return err
}
lc.updateLogs.Local.restoreUpdate(payDesc)
// Since Add updates are not stored and FeeUpdates don't have a
// corresponding entry in the remote update log, we only need to
// mark the htlc as modified if the update was Settle, Fail, or
// MalformedFail.
if payDesc.EntryType != FeeUpdate {
lc.updateLogs.Remote.markHtlcModified(
payDesc.ParentIndex,
)
}
}
return nil
}
// restorePendingLocalUpdates restores the local log updates leading up to the
// given pending remote commitment.
func (lc *LightningChannel) restorePendingLocalUpdates(
pendingRemoteCommitDiff *channeldb.CommitDiff,
pendingRemoteKeys *CommitmentKeyRing) error {
pendingCommit := pendingRemoteCommitDiff.Commitment
pendingHeight := pendingCommit.CommitHeight
lc.log.Debugf("Restoring pending remote commitment %v at commit "+
"height %v", pendingCommit.CommitTx.TxHash(), pendingHeight)
auxResult, err := fn.MapOptionZ(
lc.leafStore,
func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return s.FetchLeavesFromCommit(
NewAuxChanState(lc.channelState), pendingCommit,
*pendingRemoteKeys, lntypes.Remote,
)
},
).Unpack()
if err != nil {
return fmt.Errorf("unable to fetch aux leaves: %w", err)
}
// If we did have a dangling commit, then we'll examine which updates
// we included in that state and re-insert them into our update log.
for _, logUpdate := range pendingRemoteCommitDiff.LogUpdates {
logUpdate := logUpdate
payDesc, err := lc.logUpdateToPayDesc(
&logUpdate, lc.updateLogs.Remote, pendingHeight,
chainfee.SatPerKWeight(pendingCommit.FeePerKw),
pendingRemoteKeys,
lc.channelState.RemoteChanCfg.DustLimit,
auxResult.AuxLeaves,
)
if err != nil {
return err
}
// Earlier versions did not write the log index to disk for fee
// updates, so they will be unset. To account for this we set
// them to to current update log index.
if payDesc.EntryType == FeeUpdate && payDesc.LogIndex == 0 &&
lc.updateLogs.Local.logIndex > 0 {
payDesc.LogIndex = lc.updateLogs.Local.logIndex
lc.log.Debugf("Found FeeUpdate on "+
"pendingRemoteCommitDiff without logIndex, "+
"using %v", payDesc.LogIndex)
}
// At this point the restored update's logIndex must be equal
// to the update log, otherwise something is horribly wrong.
if payDesc.LogIndex != lc.updateLogs.Local.logIndex {
panic(fmt.Sprintf("log index mismatch: "+
"%v vs %v", payDesc.LogIndex,
lc.updateLogs.Local.logIndex))
}
switch payDesc.EntryType {
case Add:
// The HtlcIndex of the added HTLC _must_ be equal to
// the log's htlcCounter at this point. If it is not we
// panic to catch this.
// TODO(halseth): remove when cause of htlc entry bug
// is found.
if payDesc.HtlcIndex !=
lc.updateLogs.Local.htlcCounter {
panic(fmt.Sprintf("htlc index mismatch: "+
"%v vs %v", payDesc.HtlcIndex,
lc.updateLogs.Local.htlcCounter))
}
lc.updateLogs.Local.appendHtlc(payDesc)
case FeeUpdate:
lc.updateLogs.Local.appendUpdate(payDesc)
default:
lc.updateLogs.Local.appendUpdate(payDesc)
lc.updateLogs.Remote.markHtlcModified(
payDesc.ParentIndex,
)
}
}
return nil
}
// HtlcRetribution contains all the items necessary to seep a revoked HTLC
// transaction from a revoked commitment transaction broadcast by the remote
// party.
type HtlcRetribution struct {
// SignDesc is a design descriptor capable of generating the necessary
// signatures to satisfy the revocation clause of the HTLC's public key
// script.
SignDesc input.SignDescriptor
// OutPoint is the target outpoint of this HTLC pointing to the
// breached commitment transaction.
OutPoint wire.OutPoint
// SecondLevelWitnessScript is the witness script that will be created
// if the second level HTLC transaction for this output is
// broadcast/confirmed. We provide this as if the remote party attempts
// to go to the second level to claim the HTLC then we'll need to
// update the SignDesc above accordingly to sweep properly.
SecondLevelWitnessScript []byte
// SecondLevelTapTweak is the tap tweak value needed to spend the
// second level output in case the breaching party attempts to publish
// it.
SecondLevelTapTweak [32]byte
// IsIncoming is a boolean flag that indicates whether or not this
// HTLC was accepted from the counterparty. A false value indicates that
// this HTLC was offered by us. This flag is used determine the exact
// witness type should be used to sweep the output.
IsIncoming bool
// ResolutionBlob is a blob used for aux channels that permits a
// spender of this output to claim all funds.
ResolutionBlob fn.Option[tlv.Blob]
}
// BreachRetribution contains all the data necessary to bring a channel
// counterparty to justice claiming ALL lingering funds within the channel in
// the scenario that they broadcast a revoked commitment transaction. A
// BreachRetribution is created by the closeObserver if it detects an
// uncooperative close of the channel which uses a revoked commitment
// transaction. The BreachRetribution is then sent over the ContractBreach
// channel in order to allow the subscriber of the channel to dispatch justice.
type BreachRetribution struct {
// BreachTxHash is the transaction hash which breached the channel
// contract by spending from the funding multi-sig with a revoked
// commitment transaction.
BreachTxHash chainhash.Hash
// BreachHeight records the block height confirming the breach
// transaction, used as a height hint when registering for
// confirmations.
BreachHeight uint32
// ChainHash is the chain that the contract beach was identified
// within. This is also the resident chain of the contract (the chain
// the contract was created on).
ChainHash chainhash.Hash
// RevokedStateNum is the revoked state number which was broadcast.
RevokedStateNum uint64
// LocalOutputSignDesc is a SignDescriptor which is capable of
// generating the signature necessary to sweep the output within the
// breach transaction that pays directly us.
//
// NOTE: A nil value indicates that the local output is considered dust
// according to the remote party's dust limit.
LocalOutputSignDesc *input.SignDescriptor
// LocalOutpoint is the outpoint of the output paying to us (the local
// party) within the breach transaction.
LocalOutpoint wire.OutPoint
// LocalDelay is the CSV delay for the to_remote script on the breached
// commitment.
LocalDelay uint32
// RemoteOutputSignDesc is a SignDescriptor which is capable of
// generating the signature required to claim the funds as described
// within the revocation clause of the remote party's commitment
// output.
//
// NOTE: A nil value indicates that the local output is considered dust
// according to the remote party's dust limit.
RemoteOutputSignDesc *input.SignDescriptor
// RemoteOutpoint is the outpoint of the output paying to the remote
// party within the breach transaction.
RemoteOutpoint wire.OutPoint
// RemoteDelay specifies the CSV delay applied to to-local scripts on
// the breaching commitment transaction.
RemoteDelay uint32
// HtlcRetributions is a slice of HTLC retributions for each output
// active HTLC output within the breached commitment transaction.
HtlcRetributions []HtlcRetribution
// KeyRing contains the derived public keys used to construct the
// breaching commitment transaction. This allows downstream clients to
// have access to the public keys used in the scripts.
KeyRing *CommitmentKeyRing
// LocalResolutionBlob is a blob used for aux channels that permits an
// honest party to sweep the local commitment output.
LocalResolutionBlob fn.Option[tlv.Blob]
// RemoteResolutionBlob is a blob used for aux channels that permits an
// honest party to sweep the remote commitment output.
RemoteResolutionBlob fn.Option[tlv.Blob]
}
// NewBreachRetribution creates a new fully populated BreachRetribution for the
// passed channel, at a particular revoked state number. If the spend
// transaction that the breach retribution should target is known, then it can
// be provided via the spendTx parameter. Otherwise, if the spendTx parameter is
// nil, then the revocation log will be checked to see if it contains the info
// required to construct the BreachRetribution. If the revocation log is missing
// the required fields then ErrRevLogDataMissing will be returned.
func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
breachHeight uint32, spendTx *wire.MsgTx,
leafStore fn.Option[AuxLeafStore],
auxResolver fn.Option[AuxContractResolver]) (*BreachRetribution,
error) {
// Query the on-disk revocation log for the snapshot which was recorded
// at this particular state num. Based on whether a legacy revocation
// log is returned or not, we will process them differently.
revokedLog, revokedLogLegacy, err := chanState.FindPreviousState(
stateNum,
)
if err != nil {
return nil, err
}
// Sanity check that at least one of the logs is returned.
if revokedLog == nil && revokedLogLegacy == nil {
return nil, ErrNoRevocationLogFound
}
// With the state number broadcast known, we can now derive/restore the
// proper revocation preimage necessary to sweep the remote party's
// output.
revocationPreimage, err := chanState.RevocationStore.LookUp(stateNum)
if err != nil {
return nil, err
}
commitmentSecret, commitmentPoint := btcec.PrivKeyFromBytes(
revocationPreimage[:],
)
// With the commitment point generated, we can now generate the four
// keys we'll need to reconstruct the commitment state,
keyRing := DeriveCommitmentKeys(
commitmentPoint, lntypes.Remote, chanState.ChanType,
&chanState.LocalChanCfg, &chanState.RemoteChanCfg,
)
// Next, reconstruct the scripts as they were present at this state
// number so we can have the proper witness script to sign and include
// within the final witness.
var leaseExpiry uint32
if chanState.ChanType.HasLeaseExpiration() {
leaseExpiry = chanState.ThawHeight
}
auxResult, err := fn.MapOptionZ(
leafStore, func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return s.FetchLeavesFromRevocation(revokedLog)
},
).Unpack()
if err != nil {
return nil, fmt.Errorf("unable to fetch aux leaves: %w", err)
}
// Since it is the remote breach we are reconstructing, the output
// going to us will be a to-remote script with our local params.
remoteAuxLeaf := fn.FlatMapOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
return l.RemoteAuxLeaf
},
)(auxResult.AuxLeaves)
isRemoteInitiator := !chanState.IsInitiator
ourScript, ourDelay, err := CommitScriptToRemote(
chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey,
leaseExpiry, remoteAuxLeaf,
)
if err != nil {
return nil, err
}
localAuxLeaf := fn.FlatMapOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
return l.LocalAuxLeaf
},
)(auxResult.AuxLeaves)
theirDelay := uint32(chanState.RemoteChanCfg.CsvDelay)
theirScript, err := CommitScriptToSelf(
chanState.ChanType, isRemoteInitiator, keyRing.ToLocalKey,
keyRing.RevocationKey, theirDelay, leaseExpiry, localAuxLeaf,
)
if err != nil {
return nil, err
}
// Define an empty breach retribution that will be overwritten based on
// different version of the revocation log found.
var br *BreachRetribution
// Define our and their amounts, that will be overwritten below.
var ourAmt, theirAmt int64
// If the returned *RevocationLog is non-nil, use it to derive the info
// we need.
if revokedLog != nil {
br, ourAmt, theirAmt, err = createBreachRetribution(
revokedLog, spendTx, chanState, keyRing,
commitmentSecret, leaseExpiry, auxResult.AuxLeaves,
)
if err != nil {
return nil, err
}
} else {
// The returned revocation log is in legacy format, which is a
// *ChannelCommitment.
//
// NOTE: this branch is kept for compatibility such that for
// old nodes which refuse to migrate the legacy revocation log
// data can still function. This branch can be deleted once we
// are confident that no legacy format is in use.
br, ourAmt, theirAmt, err = createBreachRetributionLegacy(
revokedLogLegacy, chanState, keyRing, commitmentSecret,
ourScript, theirScript, leaseExpiry,
)
if err != nil {
return nil, err
}
}
// Conditionally instantiate a sign descriptor for each of the
// commitment outputs. If either is considered dust using the remote
// party's dust limit, the respective sign descriptor will be nil.
//
// If our balance exceeds the remote party's dust limit, instantiate
// the sign descriptor for our output.
if ourAmt >= int64(chanState.RemoteChanCfg.DustLimit) {
// As we're about to sweep our own output w/o a delay, we'll
// obtain the witness script for the success/delay path.
witnessScript, err := ourScript.WitnessScriptForPath(
input.ScriptPathDelay,
)
if err != nil {
return nil, err
}
br.LocalOutputSignDesc = &input.SignDescriptor{
SingleTweak: keyRing.LocalCommitKeyTweak,
KeyDesc: chanState.LocalChanCfg.PaymentBasePoint,
WitnessScript: witnessScript,
Output: &wire.TxOut{
PkScript: ourScript.PkScript(),
Value: ourAmt,
},
HashType: sweepSigHash(chanState.ChanType),
}
// For taproot channels, we'll make sure to set the script path
// spend (as our output on their revoked tx still needs the
// delay), and set the control block.
if scriptTree, ok := ourScript.(input.TapscriptDescriptor); ok {
//nolint:ll
br.LocalOutputSignDesc.SignMethod = input.TaprootScriptSpendSignMethod
ctrlBlock, err := scriptTree.CtrlBlockForPath(
input.ScriptPathDelay,
)
if err != nil {
return nil, err
}
//nolint:ll
br.LocalOutputSignDesc.ControlBlock, err = ctrlBlock.ToBytes()
if err != nil {
return nil, err
}
}
// At this point, we'll check to see if we need any extra
// resolution data for this output.
resolveReq := ResolutionReq{
ChanPoint: chanState.FundingOutpoint,
ChanType: chanState.ChanType,
ShortChanID: chanState.ShortChanID(),
Initiator: chanState.IsInitiator,
FundingBlob: chanState.CustomBlob,
Type: input.TaprootRemoteCommitSpend,
CloseType: Breach,
CommitTx: spendTx,
SignDesc: *br.LocalOutputSignDesc,
KeyRing: keyRing,
CsvDelay: ourDelay,
BreachCsvDelay: fn.Some(theirDelay),
CommitFee: chanState.RemoteCommitment.CommitFee,
}
if revokedLog != nil {
resolveReq.CommitBlob = revokedLog.CustomBlob.ValOpt()
}
resolveBlob := fn.MapOptionZ(
auxResolver,
func(a AuxContractResolver) fn.Result[tlv.Blob] {
return a.ResolveContract(resolveReq)
},
)
if err := resolveBlob.Err(); err != nil {
return nil, fmt.Errorf("unable to aux resolve: %w", err)
}
br.LocalResolutionBlob = resolveBlob.OkToSome()
}
// Similarly, if their balance exceeds the remote party's dust limit,
// assemble the sign descriptor for their output, which we can sweep.
if theirAmt >= int64(chanState.RemoteChanCfg.DustLimit) {
// As we're trying to defend the channel against a breach
// attempt from the remote party, we want to obain the
// revocation witness script here.
witnessScript, err := theirScript.WitnessScriptForPath(
input.ScriptPathRevocation,
)
if err != nil {
return nil, err
}
br.RemoteOutputSignDesc = &input.SignDescriptor{
KeyDesc: chanState.LocalChanCfg.
RevocationBasePoint,
DoubleTweak: commitmentSecret,
WitnessScript: witnessScript,
Output: &wire.TxOut{
PkScript: theirScript.PkScript(),
Value: theirAmt,
},
HashType: sweepSigHash(chanState.ChanType),
}
// For taproot channels, the remote output (the revoked output)
// is spent with a script path to ensure all information 3rd
// parties need to sweep anchors is revealed on chain.
scriptTree, ok := theirScript.(input.TapscriptDescriptor)
if ok {
//nolint:ll
br.RemoteOutputSignDesc.SignMethod = input.TaprootScriptSpendSignMethod
ctrlBlock, err := scriptTree.CtrlBlockForPath(
input.ScriptPathRevocation,
)
if err != nil {
return nil, err
}
//nolint:ll
br.RemoteOutputSignDesc.ControlBlock, err = ctrlBlock.ToBytes()
if err != nil {
return nil, err
}
}
// At this point, we'll check to see if we need any extra
// resolution data for this output.
resolveReq := ResolutionReq{
ChanPoint: chanState.FundingOutpoint,
ChanType: chanState.ChanType,
ShortChanID: chanState.ShortChanID(),
Initiator: chanState.IsInitiator,
FundingBlob: chanState.CustomBlob,
Type: input.TaprootCommitmentRevoke,
CloseType: Breach,
CommitTx: spendTx,
SignDesc: *br.RemoteOutputSignDesc,
KeyRing: keyRing,
CsvDelay: theirDelay,
BreachCsvDelay: fn.Some(theirDelay),
CommitFee: chanState.RemoteCommitment.CommitFee,
}
if revokedLog != nil {
resolveReq.CommitBlob = revokedLog.CustomBlob.ValOpt()
}
resolveBlob := fn.MapOptionZ(
auxResolver,
func(a AuxContractResolver) fn.Result[tlv.Blob] {
return a.ResolveContract(resolveReq)
},
)
if err := resolveBlob.Err(); err != nil {
return nil, fmt.Errorf("unable to aux resolve: %w", err)
}
br.RemoteResolutionBlob = resolveBlob.OkToSome()
}
// Finally, with all the necessary data constructed, we can pad the
// BreachRetribution struct which houses all the data necessary to
// swiftly bring justice to the cheating remote party.
br.BreachHeight = breachHeight
br.RevokedStateNum = stateNum
br.LocalDelay = ourDelay
br.RemoteDelay = theirDelay
return br, nil
}
// createHtlcRetribution is a helper function to construct an HtlcRetribution
// based on the passed params.
func createHtlcRetribution(chanState *channeldb.OpenChannel,
keyRing *CommitmentKeyRing, commitHash chainhash.Hash,
commitmentSecret *btcec.PrivateKey, leaseExpiry uint32,
htlc *channeldb.HTLCEntry,
auxLeaves fn.Option[CommitAuxLeaves]) (HtlcRetribution, error) {
var emptyRetribution HtlcRetribution
theirDelay := uint32(chanState.RemoteChanCfg.CsvDelay)
isRemoteInitiator := !chanState.IsInitiator
// We'll generate the original second level witness script now, as
// we'll need it if we're revoking an HTLC output on the remote
// commitment transaction, and *they* go to the second level.
secondLevelAuxLeaf := fn.FlatMapOption(
func(l CommitAuxLeaves) fn.Option[input.AuxTapLeaf] {
return fn.MapOption(func(val uint16) input.AuxTapLeaf {
idx := input.HtlcIndex(val)
if htlc.Incoming.Val {
leaves := l.IncomingHtlcLeaves[idx]
return leaves.SecondLevelLeaf
}
return l.OutgoingHtlcLeaves[idx].SecondLevelLeaf
})(htlc.HtlcIndex.ValOpt())
},
)(auxLeaves)
secondLevelScript, err := SecondLevelHtlcScript(
chanState.ChanType, isRemoteInitiator,
keyRing.RevocationKey, keyRing.ToLocalKey, theirDelay,
leaseExpiry, fn.FlattenOption(secondLevelAuxLeaf),
)
if err != nil {
return emptyRetribution, err
}
// If this is an incoming HTLC, then this means that they were the
// sender of the HTLC (relative to us). So we'll re-generate the sender
// HTLC script. Otherwise, is this was an outgoing HTLC that we sent,
// then from the PoV of the remote commitment state, they're the
// receiver of this HTLC.
htlcLeaf := fn.FlatMapOption(
func(l CommitAuxLeaves) fn.Option[input.AuxTapLeaf] {
return fn.MapOption(func(val uint16) input.AuxTapLeaf {
idx := input.HtlcIndex(val)
if htlc.Incoming.Val {
leaves := l.IncomingHtlcLeaves[idx]
return leaves.AuxTapLeaf
}
return l.OutgoingHtlcLeaves[idx].AuxTapLeaf
})(htlc.HtlcIndex.ValOpt())
},
)(auxLeaves)
scriptInfo, err := genHtlcScript(
chanState.ChanType, htlc.Incoming.Val, lntypes.Remote,
htlc.RefundTimeout.Val, htlc.RHash.Val, keyRing,
fn.FlattenOption(htlcLeaf),
)
if err != nil {
return emptyRetribution, err
}
signDesc := input.SignDescriptor{
KeyDesc: chanState.LocalChanCfg.
RevocationBasePoint,
DoubleTweak: commitmentSecret,
WitnessScript: scriptInfo.WitnessScriptToSign(),
Output: &wire.TxOut{
PkScript: scriptInfo.PkScript(),
Value: int64(htlc.Amt.Val.Int()),
},
HashType: sweepSigHash(chanState.ChanType),
}
// For taproot HTLC outputs, we need to set the sign method to key
// spend, and also set the tap tweak root needed to derive the proper
// private key.
if scriptTree, ok := scriptInfo.(input.TapscriptDescriptor); ok {
signDesc.SignMethod = input.TaprootKeySpendSignMethod
signDesc.TapTweak = scriptTree.TapTweak()
}
// The second level script we sign will always be the success path.
secondLevelWitnessScript, err := secondLevelScript.WitnessScriptForPath(
input.ScriptPathSuccess,
)
if err != nil {
return emptyRetribution, err
}
// If this is a taproot output, we'll also need to obtain the second
// level tap tweak as well.
var secondLevelTapTweak [32]byte
if scriptTree, ok := secondLevelScript.(input.TapscriptDescriptor); ok {
copy(secondLevelTapTweak[:], scriptTree.TapTweak())
}
return HtlcRetribution{
SignDesc: signDesc,
OutPoint: wire.OutPoint{
Hash: commitHash,
Index: uint32(htlc.OutputIndex.Val),
},
SecondLevelWitnessScript: secondLevelWitnessScript,
IsIncoming: htlc.Incoming.Val,
SecondLevelTapTweak: secondLevelTapTweak,
}, nil
}
// createBreachRetribution creates a partially initiated BreachRetribution
// using a RevocationLog. Returns the constructed retribution, our amount,
// their amount, and a possible non-nil error. If the spendTx parameter is
// non-nil, then it will be used to glean the breach transaction's to-local and
// to-remote output amounts. Otherwise, the RevocationLog will be checked to
// see if these fields are present there. If they are not, then
// ErrRevLogDataMissing is returned.
func createBreachRetribution(revokedLog *channeldb.RevocationLog,
spendTx *wire.MsgTx, chanState *channeldb.OpenChannel,
keyRing *CommitmentKeyRing, commitmentSecret *btcec.PrivateKey,
leaseExpiry uint32,
auxLeaves fn.Option[CommitAuxLeaves]) (*BreachRetribution, int64, int64,
error) {
commitHash := revokedLog.CommitTxHash
// Create the htlc retributions.
htlcRetributions := make([]HtlcRetribution, len(revokedLog.HTLCEntries))
for i, htlc := range revokedLog.HTLCEntries {
hr, err := createHtlcRetribution(
chanState, keyRing, commitHash.Val,
commitmentSecret, leaseExpiry, htlc, auxLeaves,
)
if err != nil {
return nil, 0, 0, err
}
htlcRetributions[i] = hr
}
var ourAmt, theirAmt int64
// Construct the our outpoint.
ourOutpoint := wire.OutPoint{
Hash: commitHash.Val,
}
if revokedLog.OurOutputIndex.Val != channeldb.OutputIndexEmpty {
ourOutpoint.Index = uint32(revokedLog.OurOutputIndex.Val)
// If the spend transaction is provided, then we use it to get
// the value of our output.
if spendTx != nil {
// Sanity check that OurOutputIndex is within range.
if int(ourOutpoint.Index) >= len(spendTx.TxOut) {
return nil, 0, 0, fmt.Errorf("%w: ours=%v, "+
"len(TxOut)=%v",
ErrOutputIndexOutOfRange,
ourOutpoint.Index, len(spendTx.TxOut),
)
}
// Read the amounts from the breach transaction.
//
// NOTE: ourAmt here includes commit fee and anchor
// amount (if enabled).
ourAmt = spendTx.TxOut[ourOutpoint.Index].Value
} else {
// Otherwise, we check to see if the revocation log
// contains our output amount. Due to a previous
// migration, this field may be empty in which case an
// error will be returned.
b, err := revokedLog.OurBalance.ValOpt().UnwrapOrErr(
ErrRevLogDataMissing,
)
if err != nil {
return nil, 0, 0, err
}
ourAmt = int64(b.Int().ToSatoshis())
}
}
// Construct the their outpoint.
theirOutpoint := wire.OutPoint{
Hash: commitHash.Val,
}
if revokedLog.TheirOutputIndex.Val != channeldb.OutputIndexEmpty {
theirOutpoint.Index = uint32(revokedLog.TheirOutputIndex.Val)
// If the spend transaction is provided, then we use it to get
// the value of the remote parties' output.
if spendTx != nil {
// Sanity check that TheirOutputIndex is within range.
if int(revokedLog.TheirOutputIndex.Val) >=
len(spendTx.TxOut) {
return nil, 0, 0, fmt.Errorf("%w: theirs=%v, "+
"len(TxOut)=%v",
ErrOutputIndexOutOfRange,
revokedLog.TheirOutputIndex,
len(spendTx.TxOut),
)
}
// Read the amounts from the breach transaction.
theirAmt = spendTx.TxOut[theirOutpoint.Index].Value
} else {
// Otherwise, we check to see if the revocation log
// contains remote parties' output amount. Due to a
// previous migration, this field may be empty in which
// case an error will be returned.
b, err := revokedLog.TheirBalance.ValOpt().UnwrapOrErr(
ErrRevLogDataMissing,
)
if err != nil {
return nil, 0, 0, err
}
theirAmt = int64(b.Int().ToSatoshis())
}
}
return &BreachRetribution{
BreachTxHash: commitHash.Val,
ChainHash: chanState.ChainHash,
LocalOutpoint: ourOutpoint,
RemoteOutpoint: theirOutpoint,
HtlcRetributions: htlcRetributions,
KeyRing: keyRing,
}, ourAmt, theirAmt, nil
}
// createBreachRetributionLegacy creates a partially initiated
// BreachRetribution using a ChannelCommitment. Returns the constructed
// retribution, our amount, their amount, and a possible non-nil error.
func createBreachRetributionLegacy(revokedLog *channeldb.ChannelCommitment,
chanState *channeldb.OpenChannel, keyRing *CommitmentKeyRing,
commitmentSecret *btcec.PrivateKey,
ourScript, theirScript input.ScriptDescriptor,
leaseExpiry uint32) (*BreachRetribution, int64, int64, error) {
commitHash := revokedLog.CommitTx.TxHash()
ourOutpoint := wire.OutPoint{
Hash: commitHash,
}
theirOutpoint := wire.OutPoint{
Hash: commitHash,
}
// In order to fully populate the breach retribution struct, we'll need
// to find the exact index of the commitment outputs.
for i, txOut := range revokedLog.CommitTx.TxOut {
switch {
case bytes.Equal(txOut.PkScript, ourScript.PkScript()):
ourOutpoint.Index = uint32(i)
case bytes.Equal(txOut.PkScript, theirScript.PkScript()):
theirOutpoint.Index = uint32(i)
}
}
// With the commitment outputs located, we'll now generate all the
// retribution structs for each of the HTLC transactions active on the
// remote commitment transaction.
htlcRetributions := make([]HtlcRetribution, len(revokedLog.Htlcs))
for i, htlc := range revokedLog.Htlcs {
// If the HTLC is dust, then we'll skip it as it doesn't have
// an output on the commitment transaction.
if HtlcIsDust(
chanState.ChanType, htlc.Incoming, lntypes.Remote,
chainfee.SatPerKWeight(revokedLog.FeePerKw),
htlc.Amt.ToSatoshis(),
chanState.RemoteChanCfg.DustLimit,
) {
continue
}
entry, err := channeldb.NewHTLCEntryFromHTLC(htlc)
if err != nil {
return nil, 0, 0, err
}
hr, err := createHtlcRetribution(
chanState, keyRing, commitHash,
commitmentSecret, leaseExpiry, entry,
fn.None[CommitAuxLeaves](),
)
if err != nil {
return nil, 0, 0, err
}
htlcRetributions[i] = hr
}
// Compute the balances in satoshis.
ourAmt := int64(revokedLog.LocalBalance.ToSatoshis())
theirAmt := int64(revokedLog.RemoteBalance.ToSatoshis())
return &BreachRetribution{
BreachTxHash: commitHash,
ChainHash: chanState.ChainHash,
LocalOutpoint: ourOutpoint,
RemoteOutpoint: theirOutpoint,
HtlcRetributions: htlcRetributions,
KeyRing: keyRing,
}, ourAmt, theirAmt, nil
}
// HtlcIsDust determines if an HTLC output is dust or not depending on two
// bits: if the HTLC is incoming and if the HTLC will be placed on our
// commitment transaction, or theirs. These two pieces of information are
// required as we currently used second-level HTLC transactions as off-chain
// covenants. Depending on the two bits, we'll either be using a timeout or
// success transaction which have different weights.
func HtlcIsDust(chanType channeldb.ChannelType,
incoming bool, whoseCommit lntypes.ChannelParty,
feePerKw chainfee.SatPerKWeight, htlcAmt, dustLimit btcutil.Amount,
) bool {
// First we'll determine the fee required for this HTLC based on if this is
// an incoming HTLC or not, and also on whose commitment transaction it
// will be placed on.
var htlcFee btcutil.Amount
switch {
// If this is an incoming HTLC on our commitment transaction, then the
// second-level transaction will be a success transaction.
case incoming && whoseCommit.IsLocal():
htlcFee = HtlcSuccessFee(chanType, feePerKw)
// If this is an incoming HTLC on their commitment transaction, then
// we'll be using a second-level timeout transaction as they've added
// this HTLC.
case incoming && whoseCommit.IsRemote():
htlcFee = HtlcTimeoutFee(chanType, feePerKw)
// If this is an outgoing HTLC on our commitment transaction, then
// we'll be using a timeout transaction as we're the sender of the
// HTLC.
case !incoming && whoseCommit.IsLocal():
htlcFee = HtlcTimeoutFee(chanType, feePerKw)
// If this is an outgoing HTLC on their commitment transaction, then
// we'll be using an HTLC success transaction as they're the receiver
// of this HTLC.
case !incoming && whoseCommit.IsRemote():
htlcFee = HtlcSuccessFee(chanType, feePerKw)
}
return (htlcAmt - htlcFee) < dustLimit
}
// HtlcView represents the "active" HTLCs at a particular point within the
// history of the HTLC update log.
type HtlcView struct {
// NextHeight is the height of the commitment transaction that will be
// created using this view.
NextHeight uint64
// Updates is a Dual of the Local and Remote HTLCs.
Updates lntypes.Dual[[]*paymentDescriptor]
// FeePerKw is the fee rate in sat/kw of the commitment transaction.
FeePerKw chainfee.SatPerKWeight
}
// AuxOurUpdates returns the outgoing HTLCs as a read-only copy of
// AuxHtlcDescriptors.
func (v *HtlcView) AuxOurUpdates() []AuxHtlcDescriptor {
return fn.Map(v.Updates.Local, newAuxHtlcDescriptor)
}
// AuxTheirUpdates returns the incoming HTLCs as a read-only copy of
// AuxHtlcDescriptors.
func (v *HtlcView) AuxTheirUpdates() []AuxHtlcDescriptor {
return fn.Map(v.Updates.Remote, newAuxHtlcDescriptor)
}
// FetchLatestAuxHTLCView returns the latest HTLC view of the lightning channel
// as a safe copy that can be used outside the wallet code in concurrent access.
func (lc *LightningChannel) FetchLatestAuxHTLCView() AuxHtlcView {
// This read lock is important, because we access both the local and
// remote log indexes as well as the underlying payment descriptors of
// the HTLCs when creating the view.
lc.RLock()
defer lc.RUnlock()
return newAuxHtlcView(lc.fetchHTLCView(
lc.updateLogs.Remote.logIndex, lc.updateLogs.Local.logIndex,
))
}
// fetchHTLCView returns all the candidate HTLC updates which should be
// considered for inclusion within a commitment based on the passed HTLC log
// indexes.
func (lc *LightningChannel) fetchHTLCView(theirLogIndex,
ourLogIndex uint64) *HtlcView {
var ourHTLCs []*paymentDescriptor
for e := lc.updateLogs.Local.Front(); e != nil; e = e.Next() {
htlc := e.Value
// This HTLC is active from this point-of-view iff the log
// index of the state update is below the specified index in
// our update log.
if htlc.LogIndex < ourLogIndex {
ourHTLCs = append(ourHTLCs, htlc)
}
}
var theirHTLCs []*paymentDescriptor
for e := lc.updateLogs.Remote.Front(); e != nil; e = e.Next() {
htlc := e.Value
// If this is an incoming HTLC, then it is only active from
// this point-of-view if the index of the HTLC addition in
// their log is below the specified view index.
if htlc.LogIndex < theirLogIndex {
theirHTLCs = append(theirHTLCs, htlc)
}
}
return &HtlcView{
Updates: lntypes.Dual[[]*paymentDescriptor]{
Local: ourHTLCs,
Remote: theirHTLCs,
},
}
}
// fetchCommitmentView returns a populated commitment which expresses the state
// of the channel from the point of view of a local or remote chain, evaluating
// the HTLC log up to the passed indexes. This function is used to construct
// both local and remote commitment transactions in order to sign or verify new
// commitment updates. A fully populated commitment is returned which reflects
// the proper balances for both sides at this point in the commitment chain.
func (lc *LightningChannel) fetchCommitmentView(
whoseCommitChain lntypes.ChannelParty,
ourLogIndex, ourHtlcIndex, theirLogIndex, theirHtlcIndex uint64,
keyRing *CommitmentKeyRing) (*commitment, error) {
commitChain := lc.commitChains.Local
dustLimit := lc.channelState.LocalChanCfg.DustLimit
if whoseCommitChain.IsRemote() {
commitChain = lc.commitChains.Remote
dustLimit = lc.channelState.RemoteChanCfg.DustLimit
}
nextHeight := commitChain.tip().height + 1
// Run through all the HTLCs that will be covered by this transaction
// in order to update their commitment addition height, and to adjust
// the balances on the commitment transaction accordingly. Note that
// these balances will be *before* taking a commitment fee from the
// initiator.
htlcView := lc.fetchHTLCView(theirLogIndex, ourLogIndex)
ourBalance, theirBalance, _, filteredHTLCView, err := lc.computeView(
htlcView, whoseCommitChain, true,
fn.None[chainfee.SatPerKWeight](),
)
if err != nil {
return nil, err
}
feePerKw := filteredHTLCView.FeePerKw
htlcView.NextHeight = nextHeight
filteredHTLCView.NextHeight = nextHeight
// Actually generate unsigned commitment transaction for this view.
commitTx, err := lc.commitBuilder.createUnsignedCommitmentTx(
ourBalance, theirBalance, whoseCommitChain, feePerKw,
nextHeight, htlcView, filteredHTLCView, keyRing,
commitChain.tip(),
)
if err != nil {
return nil, err
}
// We'll assert that there hasn't been a mistake during fee calculation
// leading to a fee too low.
var totalOut btcutil.Amount
for _, txOut := range commitTx.txn.TxOut {
totalOut += btcutil.Amount(txOut.Value)
}
fee := lc.channelState.Capacity - totalOut
var witnessWeight int64
if lc.channelState.ChanType.IsTaproot() {
witnessWeight = input.TaprootKeyPathWitnessSize
} else {
witnessWeight = input.WitnessCommitmentTxWeight
}
// Since the transaction is not signed yet, we use the witness weight
// used for weight calculation.
uTx := btcutil.NewTx(commitTx.txn)
weight := blockchain.GetTransactionWeight(uTx) + witnessWeight
effFeeRate := chainfee.SatPerKWeight(fee) * 1000 /
chainfee.SatPerKWeight(weight)
if effFeeRate < chainfee.AbsoluteFeePerKwFloor {
return nil, fmt.Errorf("height=%v, for ChannelPoint(%v) "+
"attempts to create commitment with feerate %v: %v",
nextHeight, lc.channelState.FundingOutpoint,
effFeeRate, spew.Sdump(commitTx))
}
// Given the custom blob of the past state, and this new HTLC view,
// we'll generate a new blob for the latest commitment.
newCommitBlob, err := fn.MapOptionZ(
lc.leafStore,
func(s AuxLeafStore) fn.Result[fn.Option[tlv.Blob]] {
return updateAuxBlob(
s, lc.channelState,
commitChain.tip().customBlob, htlcView,
whoseCommitChain, ourBalance, theirBalance,
*keyRing,
)
},
).Unpack()
if err != nil {
return nil, fmt.Errorf("unable to fetch aux leaves: %w", err)
}
messageIndices := lntypes.Dual[uint64]{
Local: ourLogIndex,
Remote: theirLogIndex,
}
// With the commitment view created, store the resulting balances and
// transaction with the other parameters for this height.
c := &commitment{
ourBalance: commitTx.ourBalance,
theirBalance: commitTx.theirBalance,
txn: commitTx.txn,
fee: commitTx.fee,
messageIndices: messageIndices,
ourHtlcIndex: ourHtlcIndex,
theirHtlcIndex: theirHtlcIndex,
height: nextHeight,
feePerKw: feePerKw,
dustLimit: dustLimit,
whoseCommit: whoseCommitChain,
customBlob: newCommitBlob,
}
// In order to ensure _none_ of the HTLC's associated with this new
// commitment are mutated, we'll manually copy over each HTLC to its
// respective slice.
c.outgoingHTLCs = make(
[]paymentDescriptor, len(filteredHTLCView.Updates.Local),
)
for i, htlc := range filteredHTLCView.Updates.Local {
c.outgoingHTLCs[i] = *htlc
}
c.incomingHTLCs = make(
[]paymentDescriptor, len(filteredHTLCView.Updates.Remote),
)
for i, htlc := range filteredHTLCView.Updates.Remote {
c.incomingHTLCs[i] = *htlc
}
// Finally, we'll populate all the HTLC indexes so we can track the
// locations of each HTLC in the commitment state. We pass in the sorted
// slice of CLTV deltas in order to properly locate HTLCs that otherwise
// have the same payment hash and amount.
err = c.populateHtlcIndexes(lc.channelState.ChanType, commitTx.cltvs)
if err != nil {
return nil, err
}
return c, nil
}
// fundingTxIn returns the funding output as a transaction input. The input
// returned by this function uses a max sequence number, so it isn't able to be
// used with RBF by default.
func fundingTxIn(chanState *channeldb.OpenChannel) wire.TxIn {
return *wire.NewTxIn(&chanState.FundingOutpoint, nil, nil)
}
// evaluateHTLCView processes all update entries in both HTLC update logs,
// producing a final view which is the result of properly applying all adds,
// settles, timeouts and fee updates found in both logs. The resulting view
// returned reflects the current state of HTLCs within the remote or local
// commitment chain, and the current commitment fee rate.
//
// The return values of this function are as follows:
// 1. The new htlcView reflecting the current channel state.
// 2. A Dual of the updates which have not yet been committed in
// 'whoseCommitChain's commitment chain.
func (lc *LightningChannel) evaluateHTLCView(view *HtlcView,
whoseCommitChain lntypes.ChannelParty, nextHeight uint64) (*HtlcView,
lntypes.Dual[[]*paymentDescriptor], lntypes.Dual[int64], error) {
// We initialize the view's fee rate to the fee rate of the unfiltered
// view. If any fee updates are found when evaluating the view, it will
// be updated.
newView := &HtlcView{
FeePerKw: view.FeePerKw,
NextHeight: nextHeight,
}
noUncommitted := lntypes.Dual[[]*paymentDescriptor]{}
// The fee rate of our view is always the last UpdateFee message from
// the channel's OpeningParty.
openerUpdates := view.Updates.GetForParty(lc.channelState.Initiator())
feeUpdates := fn.Filter(openerUpdates, func(u *paymentDescriptor) bool {
return u.EntryType == FeeUpdate
})
lastFeeUpdate := fn.Last(feeUpdates)
lastFeeUpdate.WhenSome(func(pd *paymentDescriptor) {
newView.FeePerKw = chainfee.SatPerKWeight(
pd.Amount.ToSatoshis(),
)
})
// We use two maps, one for the local log and one for the remote log to
// keep track of which entries we need to skip when creating the final
// htlc view. We skip an entry whenever we find a settle or a timeout
// modifying an entry.
skip := lntypes.Dual[fn.Set[uint64]]{
Local: fn.NewSet[uint64](),
Remote: fn.NewSet[uint64](),
}
balanceDeltas := lntypes.Dual[int64]{}
parties := [2]lntypes.ChannelParty{lntypes.Local, lntypes.Remote}
for _, party := range parties {
// First we run through non-add entries in both logs,
// populating the skip sets.
resolutions := fn.Filter(
view.Updates.GetForParty(party),
func(pd *paymentDescriptor) bool {
switch pd.EntryType {
case Settle, Fail, MalformedFail:
return true
default:
return false
}
},
)
for _, entry := range resolutions {
addEntry, err := lc.fetchParent(
entry, whoseCommitChain, party.CounterParty(),
)
if err != nil {
noDeltas := lntypes.Dual[int64]{}
return nil, noUncommitted, noDeltas, err
}
skipSet := skip.GetForParty(party.CounterParty())
skipSet.Add(addEntry.HtlcIndex)
rmvHeight := entry.removeCommitHeights.GetForParty(
whoseCommitChain,
)
if rmvHeight == 0 {
switch {
// If an incoming HTLC is being settled, then
// this means that the preimage has been
// received by the settling party Therefore, we
// increase the settling party's balance by the
// HTLC amount.
case entry.EntryType == Settle:
delta := int64(entry.Amount)
balanceDeltas.ModifyForParty(
party,
func(acc int64) int64 {
return acc + delta
},
)
// Otherwise, this HTLC is being failed out,
// therefore the value of the HTLC should
// return to the failing party's counterparty.
case entry.EntryType != Settle:
delta := int64(entry.Amount)
balanceDeltas.ModifyForParty(
party.CounterParty(),
func(acc int64) int64 {
return acc + delta
},
)
}
}
}
}
// Next we take a second pass through all the log entries, skipping any
// settled HTLCs, and debiting the chain state balance due to any newly
// added HTLCs.
for _, party := range parties {
liveAdds := fn.Filter(
view.Updates.GetForParty(party),
func(pd *paymentDescriptor) bool {
isAdd := pd.EntryType == Add
shouldSkip := skip.GetForParty(party).
Contains(pd.HtlcIndex)
return isAdd && !shouldSkip
},
)
for _, entry := range liveAdds {
// Skip the entries that have already had their add
// commit height set for this commit chain.
addHeight := entry.addCommitHeights.GetForParty(
whoseCommitChain,
)
if addHeight == 0 {
// If this is a new incoming (un-committed)
// HTLC, then we need to update their balance
// accordingly by subtracting the amount of
// the HTLC that are funds pending.
// Similarly, we need to debit our balance if
// this is an out going HTLC to reflect the
// pending balance.
balanceDeltas.ModifyForParty(
party,
func(acc int64) int64 {
return acc - int64(entry.Amount)
},
)
}
}
newView.Updates.SetForParty(party, liveAdds)
}
// Create a function that is capable of identifying whether or not the
// paymentDescriptor has been committed in the commitment chain
// corresponding to whoseCommitmentChain.
isUncommitted := func(update *paymentDescriptor) bool {
switch update.EntryType {
case Add:
return update.addCommitHeights.GetForParty(
whoseCommitChain,
) == 0
case FeeUpdate:
return update.addCommitHeights.GetForParty(
whoseCommitChain,
) == 0
case Settle, Fail, MalformedFail:
return update.removeCommitHeights.GetForParty(
whoseCommitChain,
) == 0
default:
panic("invalid paymentDescriptor EntryType")
}
}
// Collect all of the updates that haven't had their commit heights set
// for the commitment chain corresponding to whoseCommitmentChain.
uncommittedUpdates := lntypes.MapDual(
view.Updates,
func(us []*paymentDescriptor) []*paymentDescriptor {
return fn.Filter(us, isUncommitted)
},
)
return newView, uncommittedUpdates, balanceDeltas, nil
}
// fetchParent is a helper that looks up update log parent entries in the
// appropriate log.
func (lc *LightningChannel) fetchParent(entry *paymentDescriptor,
whoseCommitChain, whoseUpdateLog lntypes.ChannelParty,
) (*paymentDescriptor, error) {
var (
updateLog *updateLog
logName string
)
if whoseUpdateLog.IsRemote() {
updateLog = lc.updateLogs.Remote
logName = "remote"
} else {
updateLog = lc.updateLogs.Local
logName = "local"
}
addEntry := updateLog.lookupHtlc(entry.ParentIndex)
switch {
// We check if the parent entry is not found at this point.
// This could happen for old versions of lnd, and we return an
// error to gracefully shut down the state machine if such an
// entry is still in the logs.
case addEntry == nil:
return nil, fmt.Errorf("unable to find parent entry "+
"%d in %v update log: %v\nUpdatelog: %v",
entry.ParentIndex, logName,
lnutils.SpewLogClosure(entry),
lnutils.SpewLogClosure(updateLog))
// The parent add height should never be zero at this point. If
// that's the case we probably forgot to send a new commitment.
case addEntry.addCommitHeights.GetForParty(whoseCommitChain) == 0:
return nil, fmt.Errorf("parent entry %d for update %d "+
"had zero %v add height", entry.ParentIndex,
entry.LogIndex, whoseCommitChain)
}
return addEntry, nil
}
// generateRemoteHtlcSigJobs generates a series of HTLC signature jobs for the
// sig pool, along with a channel that if closed, will cancel any jobs after
// they have been submitted to the sigPool. This method is to be used when
// generating a new commitment for the remote party. The jobs generated by the
// signature can be submitted to the sigPool to generate all the signatures
// asynchronously and in parallel.
func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
chanState *channeldb.OpenChannel, leaseExpiry uint32,
remoteCommitView *commitment,
leafStore fn.Option[AuxLeafStore]) ([]SignJob, []AuxSigJob,
chan struct{}, error) {
var (
isRemoteInitiator = !chanState.IsInitiator
localChanCfg = chanState.LocalChanCfg
remoteChanCfg = chanState.RemoteChanCfg
chanType = chanState.ChanType
)
txHash := remoteCommitView.txn.TxHash()
dustLimit := remoteChanCfg.DustLimit
feePerKw := remoteCommitView.feePerKw
sigHashType := HtlcSigHashType(chanType)
// With the keys generated, we'll make a slice with enough capacity to
// hold potentially all the HTLCs. The actual slice may be a bit
// smaller (than its total capacity) and some HTLCs may be dust.
numSigs := len(remoteCommitView.incomingHTLCs) +
len(remoteCommitView.outgoingHTLCs)
sigBatch := make([]SignJob, 0, numSigs)
auxSigBatch := make([]AuxSigJob, 0, numSigs)
var err error
cancelChan := make(chan struct{})
diskCommit := remoteCommitView.toDiskCommit(lntypes.Remote)
auxResult, err := fn.MapOptionZ(
leafStore, func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return s.FetchLeavesFromCommit(
NewAuxChanState(chanState), *diskCommit,
*keyRing, lntypes.Remote,
)
},
).Unpack()
if err != nil {
return nil, nil, nil, fmt.Errorf("unable to fetch aux leaves: "+
"%w", err)
}
// For each outgoing and incoming HTLC, if the HTLC isn't considered a
// dust output after taking into account second-level HTLC fees, then a
// sigJob will be generated and appended to the current batch.
for _, htlc := range remoteCommitView.incomingHTLCs {
if HtlcIsDust(
chanType, true, lntypes.Remote, feePerKw,
htlc.Amount.ToSatoshis(), dustLimit,
) {
continue
}
// If the HTLC isn't dust, then we'll create an empty sign job
// to add to the batch momentarily.
var sigJob SignJob
sigJob.Cancel = cancelChan
sigJob.Resp = make(chan SignJobResp, 1)
// As this is an incoming HTLC and we're sinning the commitment
// transaction of the remote node, we'll need to generate an
// HTLC timeout transaction for them. The output of the timeout
// transaction needs to account for fees, so we'll compute the
// required fee and output now.
htlcFee := HtlcTimeoutFee(chanType, feePerKw)
outputAmt := htlc.Amount.ToSatoshis() - htlcFee
auxLeaf := fn.FlatMapOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
leaves := l.IncomingHtlcLeaves
return leaves[htlc.HtlcIndex].SecondLevelLeaf
},
)(auxResult.AuxLeaves)
// With the fee calculate, we can properly create the HTLC
// timeout transaction using the HTLC amount minus the fee.
op := wire.OutPoint{
Hash: txHash,
Index: uint32(htlc.remoteOutputIndex),
}
sigJob.Tx, err = CreateHtlcTimeoutTx(
chanType, isRemoteInitiator, op, outputAmt,
htlc.Timeout, uint32(remoteChanCfg.CsvDelay),
leaseExpiry, keyRing.RevocationKey, keyRing.ToLocalKey,
auxLeaf,
)
if err != nil {
return nil, nil, nil, err
}
// Construct a full hash cache as we may be signing a segwit v1
// sighash.
txOut := remoteCommitView.txn.TxOut[htlc.remoteOutputIndex]
prevFetcher := txscript.NewCannedPrevOutputFetcher(
txOut.PkScript, int64(htlc.Amount.ToSatoshis()),
)
hashCache := txscript.NewTxSigHashes(sigJob.Tx, prevFetcher)
// Finally, we'll generate a sign descriptor to generate a
// signature to give to the remote party for this commitment
// transaction. Note we use the raw HTLC amount.
sigJob.SignDesc = input.SignDescriptor{
KeyDesc: localChanCfg.HtlcBasePoint,
SingleTweak: keyRing.LocalHtlcKeyTweak,
WitnessScript: htlc.theirWitnessScript,
Output: txOut,
PrevOutputFetcher: prevFetcher,
HashType: sigHashType,
SigHashes: hashCache,
InputIndex: 0,
}
sigJob.OutputIndex = htlc.remoteOutputIndex
// If this is a taproot channel, then we'll need to set the
// method type to ensure we generate a valid signature.
if chanType.IsTaproot() {
//nolint:ll
sigJob.SignDesc.SignMethod = input.TaprootScriptSpendSignMethod
}
sigBatch = append(sigBatch, sigJob)
auxSigBatch = append(auxSigBatch, NewAuxSigJob(
sigJob, *keyRing, true, newAuxHtlcDescriptor(&htlc),
remoteCommitView.customBlob, auxLeaf, cancelChan,
))
}
for _, htlc := range remoteCommitView.outgoingHTLCs {
if HtlcIsDust(
chanType, false, lntypes.Remote, feePerKw,
htlc.Amount.ToSatoshis(), dustLimit,
) {
continue
}
sigJob := SignJob{}
sigJob.Cancel = cancelChan
sigJob.Resp = make(chan SignJobResp, 1)
// As this is an outgoing HTLC and we're signing the commitment
// transaction of the remote node, we'll need to generate an
// HTLC success transaction for them. The output of the timeout
// transaction needs to account for fees, so we'll compute the
// required fee and output now.
htlcFee := HtlcSuccessFee(chanType, feePerKw)
outputAmt := htlc.Amount.ToSatoshis() - htlcFee
auxLeaf := fn.FlatMapOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
leaves := l.OutgoingHtlcLeaves
return leaves[htlc.HtlcIndex].SecondLevelLeaf
},
)(auxResult.AuxLeaves)
// With the proper output amount calculated, we can now
// generate the success transaction using the remote party's
// CSV delay.
op := wire.OutPoint{
Hash: txHash,
Index: uint32(htlc.remoteOutputIndex),
}
sigJob.Tx, err = CreateHtlcSuccessTx(
chanType, isRemoteInitiator, op, outputAmt,
uint32(remoteChanCfg.CsvDelay), leaseExpiry,
keyRing.RevocationKey, keyRing.ToLocalKey,
auxLeaf,
)
if err != nil {
return nil, nil, nil, err
}
// Construct a full hash cache as we may be signing a segwit v1
// sighash.
txOut := remoteCommitView.txn.TxOut[htlc.remoteOutputIndex]
prevFetcher := txscript.NewCannedPrevOutputFetcher(
txOut.PkScript, int64(htlc.Amount.ToSatoshis()),
)
hashCache := txscript.NewTxSigHashes(sigJob.Tx, prevFetcher)
// Finally, we'll generate a sign descriptor to generate a
// signature to give to the remote party for this commitment
// transaction. Note we use the raw HTLC amount.
sigJob.SignDesc = input.SignDescriptor{
KeyDesc: localChanCfg.HtlcBasePoint,
SingleTweak: keyRing.LocalHtlcKeyTweak,
WitnessScript: htlc.theirWitnessScript,
Output: txOut,
PrevOutputFetcher: prevFetcher,
HashType: sigHashType,
SigHashes: hashCache,
InputIndex: 0,
}
sigJob.OutputIndex = htlc.remoteOutputIndex
// If this is a taproot channel, then we'll need to set the
// method type to ensure we generate a valid signature.
if chanType.IsTaproot() {
//nolint:ll
sigJob.SignDesc.SignMethod = input.TaprootScriptSpendSignMethod
}
sigBatch = append(sigBatch, sigJob)
auxSigBatch = append(auxSigBatch, NewAuxSigJob(
sigJob, *keyRing, false, newAuxHtlcDescriptor(&htlc),
remoteCommitView.customBlob, auxLeaf, cancelChan,
))
}
return sigBatch, auxSigBatch, cancelChan, nil
}
// createCommitDiff will create a commit diff given a new pending commitment
// for the remote party and the necessary signatures for the remote party to
// validate this new state. This function is called right before sending the
// new commitment to the remote party. The commit diff returned contains all
// information necessary for retransmission.
func (lc *LightningChannel) createCommitDiff(newCommit *commitment,
commitSig lnwire.Sig, htlcSigs []lnwire.Sig,
auxSigs []fn.Option[tlv.Blob]) (*channeldb.CommitDiff, error) {
var (
logUpdates []channeldb.LogUpdate
ackAddRefs []channeldb.AddRef
settleFailRefs []channeldb.SettleFailRef
openCircuitKeys []models.CircuitKey
closedCircuitKeys []models.CircuitKey
)
// We'll now run through our local update log to locate the items which
// were only just committed within this pending state. This will be the
// set of items we need to retransmit if we reconnect and find that
// they didn't process this new state fully.
for e := lc.updateLogs.Local.Front(); e != nil; e = e.Next() {
pd := e.Value
// If this entry wasn't committed at the exact height of this
// remote commitment, then we'll skip it as it was already
// lingering in the log.
if pd.addCommitHeights.Remote != newCommit.height &&
pd.removeCommitHeights.Remote != newCommit.height {
continue
}
// We'll map the type of the paymentDescriptor to one of the
// four messages that it corresponds to. With this set of
// messages obtained, we can simply read from disk and re-send
// them in the case of a needed channel sync.
switch pd.EntryType {
case Add:
// Gather any references for circuits opened by this Add
// HTLC.
if pd.OpenCircuitKey != nil {
openCircuitKeys = append(
openCircuitKeys, *pd.OpenCircuitKey,
)
}
case Settle, Fail, MalformedFail:
// Gather the fwd pkg references from any settle or fail
// packets, if they exist.
if pd.SourceRef != nil {
ackAddRefs = append(ackAddRefs, *pd.SourceRef)
}
if pd.DestRef != nil {
settleFailRefs = append(
settleFailRefs, *pd.DestRef,
)
}
if pd.ClosedCircuitKey != nil {
closedCircuitKeys = append(
closedCircuitKeys, *pd.ClosedCircuitKey,
)
}
case FeeUpdate:
// Nothing special to do.
}
logUpdates = append(logUpdates, pd.toLogUpdate())
}
// With the set of log updates mapped into wire messages, we'll now
// convert the in-memory commit into a format suitable for writing to
// disk.
diskCommit := newCommit.toDiskCommit(lntypes.Remote)
// We prepare the commit sig message to be sent to the remote party.
commitSigMsg := &lnwire.CommitSig{
ChanID: lnwire.NewChanIDFromOutPoint(
lc.channelState.FundingOutpoint,
),
CommitSig: commitSig,
HtlcSigs: htlcSigs,
}
// Encode and check the size of the custom records now.
auxCustomRecords, err := fn.MapOptionZ(
lc.auxSigner,
func(s AuxSigner) fn.Result[lnwire.CustomRecords] {
blobOption, err := s.PackSigs(auxSigs).Unpack()
if err != nil {
return fn.Err[lnwire.CustomRecords](err)
}
// We now serialize the commit sig message without the
// custom records to make sure we have space for them.
var buf bytes.Buffer
err = commitSigMsg.Encode(&buf, 0)
if err != nil {
return fn.Err[lnwire.CustomRecords](err)
}
// The number of available bytes is the max message size
// minus the size of the message without the custom
// records. We also subtract 8 bytes for encoding
// overhead of the custom records (just some safety
// padding).
available := lnwire.MaxMsgBody - buf.Len() - 8
blob := blobOption.UnwrapOr(nil)
if len(blob) > available {
err = fmt.Errorf("aux sigs size %d exceeds "+
"max allowed size of %d", len(blob),
available)
return fn.Err[lnwire.CustomRecords](err)
}
records, err := lnwire.ParseCustomRecords(blob)
if err != nil {
return fn.Err[lnwire.CustomRecords](err)
}
return fn.Ok(records)
},
).Unpack()
if err != nil {
return nil, fmt.Errorf("error packing aux sigs: %w", err)
}
commitSigMsg.CustomRecords = auxCustomRecords
return &channeldb.CommitDiff{
Commitment: *diskCommit,
CommitSig: commitSigMsg,
LogUpdates: logUpdates,
OpenedCircuitKeys: openCircuitKeys,
ClosedCircuitKeys: closedCircuitKeys,
AddAcks: ackAddRefs,
SettleFailAcks: settleFailRefs,
}, nil
}
// getUnsignedAckedUpdates returns all remote log updates that we haven't
// signed for yet ourselves.
func (lc *LightningChannel) getUnsignedAckedUpdates() []channeldb.LogUpdate {
// Fetch the last remote update that we have signed for.
lastRemoteCommitted :=
lc.commitChains.Remote.tail().messageIndices.Remote
// Fetch the last remote update that we have acked.
lastLocalCommitted :=
lc.commitChains.Local.tail().messageIndices.Remote
// We'll now run through the remote update log to locate the items that
// we haven't signed for yet. This will be the set of items we need to
// restore if we reconnect in order to produce the signature that the
// remote party expects.
var logUpdates []channeldb.LogUpdate
for e := lc.updateLogs.Remote.Front(); e != nil; e = e.Next() {
pd := e.Value
// Skip all remote updates that we have already included in our
// commit chain.
if pd.LogIndex < lastRemoteCommitted {
continue
}
// Skip all remote updates that we haven't acked yet. At the
// moment this function is called, there shouldn't be any, but
// we check it anyway to make this function more generally
// usable.
if pd.LogIndex >= lastLocalCommitted {
continue
}
logUpdates = append(logUpdates, pd.toLogUpdate())
}
return logUpdates
}
// CalcFeeBuffer calculates a FeeBuffer in accordance with the recommended
// amount specified in BOLT 02. It accounts for two times the current fee rate
// plus an additional htlc at this higher fee rate which allows our peer to add
// an htlc even if our channel is drained locally.
// See: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md
func CalcFeeBuffer(feePerKw chainfee.SatPerKWeight,
commitWeight lntypes.WeightUnit) lnwire.MilliSatoshi {
// Account for a 100% in fee rate increase.
bufferFeePerKw := 2 * feePerKw
feeBuffer := lnwire.NewMSatFromSatoshis(
// Account for an additional htlc at the higher fee level.
bufferFeePerKw.FeeForWeight(commitWeight + input.HTLCWeight),
)
return feeBuffer
}
// BufferType is used to determine what kind of additional buffer should be left
// when evaluating the usable balance of a channel.
type BufferType uint8
const (
// NoBuffer means no additional buffer is accounted for. This is
// important when verifying an already locked-in commitment state.
NoBuffer BufferType = iota
// FeeBuffer accounts for several edge cases. One of them is where
// a locally drained channel might become unusable due to the non-opener
// of the channel not being able to add a non-dust htlc to the channel
// state because we as a channel opener cannot pay the additional fees
// an htlc would require on the commitment tx.
// See: https://github.com/lightningnetwork/lightning-rfc/issues/728
//
// Moreover it mitigates the situation where htlcs are added
// simultaneously to the commitment transaction. This cannot be avoided
// until the feature __option_simplified_update__ is available in the
// protocol and deployed widely in the network.
// More information about the issue and the simplified commitment flow
// can be found here:
// https://github.com/lightningnetwork/lnd/issues/7657
// https://github.com/lightning/bolts/pull/867
//
// The last advantage is that we can react to fee spikes (up or down)
// by accounting for at least twice the size of the current fee rate
// (BOLT02). It also accounts for decreases in the fee rate because
// former dust htlcs might now become normal outputs so the overall
// fee might increase although the fee rate decreases (this is only true
// for non-anchor channels because htlcs have to account for their
// fee of the second-level covenant transactions).
FeeBuffer
// AdditionalHtlc just accounts for an additional htlc which is helpful
// when deciding about a fee update of the commitment transaction.
// Leaving always room for an additional htlc makes sure that even
// though we are the opener of a channel a new fee update will always
// allow an htlc from our peer to be added to the commitment tx.
AdditionalHtlc
)
// String returns a human readable name for the buffer type.
func (b BufferType) String() string {
switch b {
case NoBuffer:
return "nobuffer"
case FeeBuffer:
return "feebuffer"
case AdditionalHtlc:
return "additionalhtlc"
default:
return "unknown"
}
}
// applyCommitFee applies the commitFee including a buffer to the balance amount
// and verifies that it does not become negative. This function returns the new
// balance, the exact buffer amount (excluding the commitment fee) and the
// commitment fee.
func (lc *LightningChannel) applyCommitFee(
balance lnwire.MilliSatoshi, commitWeight lntypes.WeightUnit,
feePerKw chainfee.SatPerKWeight,
buffer BufferType) (lnwire.MilliSatoshi, lnwire.MilliSatoshi,
lnwire.MilliSatoshi, error) {
commitFee := feePerKw.FeeForWeight(commitWeight)
commitFeeMsat := lnwire.NewMSatFromSatoshis(commitFee)
var bufferAmt lnwire.MilliSatoshi
switch buffer {
// The FeeBuffer is subtracted from the balance. It is of predefined
// size add keeps room for an up to 2x increase in fees of the
// commitment tx and an additional htlc at this fee level reserved for
// the peer.
case FeeBuffer:
// Make sure that we are the initiator of the channel before we
// apply the FeeBuffer.
if !lc.channelState.IsInitiator {
return 0, 0, 0, ErrFeeBufferNotInitiator
}
// The FeeBuffer already includes the commitFee.
bufferAmt = CalcFeeBuffer(feePerKw, commitWeight)
if bufferAmt < balance {
newBalance := balance - bufferAmt
return newBalance, bufferAmt - commitFeeMsat,
commitFeeMsat, nil
}
// The AdditionalHtlc buffer type does NOT keep a FeeBuffer but solely
// keeps space for an additional htlc on the commitment tx which our
// peer can add.
case AdditionalHtlc:
additionalHtlcFee := lnwire.NewMSatFromSatoshis(
feePerKw.FeeForWeight(input.HTLCWeight),
)
bufferAmt = commitFeeMsat + additionalHtlcFee
newBalance := balance - bufferAmt
if bufferAmt < balance {
return newBalance, additionalHtlcFee, commitFeeMsat, nil
}
// The default case does not account for any buffer on the local balance
// but just subtracts the commit fee.
default:
if commitFeeMsat < balance {
newBalance := balance - commitFeeMsat
return newBalance, 0, commitFeeMsat, nil
}
}
// We still return the amount and bufferAmt here to log them at a later
// stage.
return balance, bufferAmt, commitFeeMsat, ErrBelowChanReserve
}
// validateCommitmentSanity is used to validate the current state of the
// commitment transaction in terms of the ChannelConstraints that we and our
// remote peer agreed upon during the funding workflow. The
// predict[Our|Their]Add should parameters should be set to a valid
// paymentDescriptor if we are validating in the state when adding a new HTLC,
// or nil otherwise.
func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter,
ourLogCounter uint64, whoseCommitChain lntypes.ChannelParty,
buffer BufferType, predictOurAdd, predictTheirAdd *paymentDescriptor,
) error {
// First fetch the initial balance before applying any updates.
commitChain := lc.commitChains.Local
if whoseCommitChain.IsRemote() {
commitChain = lc.commitChains.Remote
}
ourInitialBalance := commitChain.tip().ourBalance
theirInitialBalance := commitChain.tip().theirBalance
// Fetch all updates not committed.
view := lc.fetchHTLCView(theirLogCounter, ourLogCounter)
// If we are checking if we can add a new HTLC, we add this to the
// appropriate update log, in order to validate the sanity of the
// commitment resulting from _actually adding_ this HTLC to the state.
if predictOurAdd != nil {
view.Updates.Local = append(view.Updates.Local, predictOurAdd)
}
if predictTheirAdd != nil {
view.Updates.Remote = append(
view.Updates.Remote, predictTheirAdd,
)
}
ourBalance, theirBalance, commitWeight, filteredView, err := lc.computeView(
view, whoseCommitChain, false,
fn.None[chainfee.SatPerKWeight](),
)
if err != nil {
return err
}
feePerKw := filteredView.FeePerKw
// Ensure that the fee being applied is enough to be relayed across the
// network in a reasonable time frame.
if feePerKw < chainfee.FeePerKwFloor {
return fmt.Errorf("commitment fee per kw %v below fee floor %v",
feePerKw, chainfee.FeePerKwFloor)
}
// The channel opener has to account for the commitment fee. This
// includes also a buffer type. Depending on whether we are the opener
// of the channel we either want to enforce a buffer on the local
// amount.
var (
bufferAmt lnwire.MilliSatoshi
commitFee lnwire.MilliSatoshi
)
if lc.channelState.IsInitiator {
ourBalance, bufferAmt, commitFee, err = lc.applyCommitFee(
ourBalance, commitWeight, feePerKw, buffer,
)
if err != nil {
lc.log.Errorf("Cannot pay for the CommitmentFee of "+
"the ChannelState: ourBalance is negative "+
"after applying the fee: ourBalance=%v, "+
"commitFee=%v, feeBuffer=%v (type=%v) "+
"local_chan_initiator", ourBalance,
commitFee, bufferAmt, buffer)
return err
}
} else {
// No FeeBuffer is enforced when we are not the initiator of
// the channel. We cannot do this, because if our peer does not
// enforce the FeeBuffer (older LND software) the peer might
// bring his balance below the FeeBuffer making the channel
// stuck because locally we will never put another outgoing HTLC
// on the channel state. The FeeBuffer should ONLY be enforced
// if we locally pay for the commitment transaction.
theirBalance, bufferAmt, commitFee, err = lc.applyCommitFee(
theirBalance, commitWeight, feePerKw, NoBuffer,
)
if err != nil {
lc.log.Errorf("Cannot pay for the CommitmentFee "+
"of the ChannelState: theirBalance is "+
"negative after applying the fee: "+
"theirBalance=%v, commitFee=%v, feeBuffer=%v "+
"(type=%v) remote_chan_initiator",
theirBalance, commitFee, bufferAmt, buffer)
return err
}
}
// The commitment fee was accounted for successfully now make sure we
// still do have enough left to account for the channel reserve.
// If the added HTLCs will decrease the balance, make sure they won't
// dip the local and remote balances below the channel reserves.
ourReserve := lnwire.NewMSatFromSatoshis(
lc.channelState.LocalChanCfg.ChanReserve,
)
theirReserve := lnwire.NewMSatFromSatoshis(
lc.channelState.RemoteChanCfg.ChanReserve,
)
switch {
// TODO(ziggie): Allow the peer dip us below the channel reserve when
// our local balance would increase during this commitment dance or
// allow us to dip the peer below its reserve then their balance would
// increase during this commitment dance. This is needed for splicing
// when e.g. a new channel (bigger capacity) has a higher required
// reserve and the peer would need to add an additional htlc to push the
// missing amount to our side and viceversa.
// See: https://github.com/lightningnetwork/lnd/issues/8249
case ourBalance < ourInitialBalance && ourBalance < ourReserve:
lc.log.Debugf("Funds below chan reserve: ourBalance=%v, "+
"ourReserve=%v, commitFee=%v, feeBuffer=%v "+
"chan_initiator=%v", ourBalance, ourReserve,
commitFee, bufferAmt, lc.channelState.IsInitiator)
return fmt.Errorf("%w: our balance below chan reserve",
ErrBelowChanReserve)
case theirBalance < theirInitialBalance && theirBalance < theirReserve:
lc.log.Debugf("Funds below chan reserve: theirBalance=%v, "+
"theirReserve=%v", theirBalance, theirReserve)
return fmt.Errorf("%w: their balance below chan reserve",
ErrBelowChanReserve)
}
// validateUpdates take a set of updates, and validates them against
// the passed channel constraints.
validateUpdates := func(updates []*paymentDescriptor,
constraints *channeldb.ChannelConfig) error {
// We keep track of the number of HTLCs in flight for the
// commitment, and the amount in flight.
var numInFlight uint16
var amtInFlight lnwire.MilliSatoshi
// Go through all updates, checking that they don't violate the
// channel constraints.
for _, entry := range updates {
if entry.EntryType == Add {
// An HTLC is being added, this will add to the
// number and amount in flight.
amtInFlight += entry.Amount
numInFlight++
// Check that the HTLC amount is positive.
if entry.Amount == 0 {
return ErrInvalidHTLCAmt
}
// Check that the value of the HTLC they added
// is above our minimum.
if entry.Amount < constraints.MinHTLC {
return ErrBelowMinHTLC
}
}
}
// Now that we know the total value of added HTLCs, we check
// that this satisfy the MaxPendingAmont constraint.
if amtInFlight > constraints.MaxPendingAmount {
return ErrMaxPendingAmount
}
// In this step, we verify that the total number of active
// HTLCs does not exceed the constraint of the maximum number
// of HTLCs in flight.
if numInFlight > constraints.MaxAcceptedHtlcs {
return ErrMaxHTLCNumber
}
return nil
}
// First check that the remote updates won't violate it's channel
// constraints.
err = validateUpdates(
filteredView.Updates.Remote, &lc.channelState.RemoteChanCfg,
)
if err != nil {
return err
}
// Secondly check that our updates won't violate our channel
// constraints.
err = validateUpdates(
filteredView.Updates.Local, &lc.channelState.LocalChanCfg,
)
if err != nil {
return err
}
return nil
}
// CommitSigs holds the set of related signatures for a new commitment
// transaction state.
type CommitSigs struct {
// CommitSig is the normal commitment signature. This will only be a
// non-zero commitment signature for non-taproot channels.
CommitSig lnwire.Sig
// HtlcSigs is the set of signatures for all HTLCs in the commitment
// transaction. Depending on the channel type, these will either be
// ECDSA or Schnorr signatures.
HtlcSigs []lnwire.Sig
// PartialSig is the musig2 partial signature for taproot commitment
// transactions.
PartialSig lnwire.OptPartialSigWithNonceTLV
// AuxSigBlob is the blob containing all the auxiliary signatures for
// this new commitment state.
AuxSigBlob tlv.Blob
}
// NewCommitState wraps the various signatures needed to properly
// propose/accept a new commitment state. This includes the signer's nonce for
// musig2 channels.
type NewCommitState struct {
*CommitSigs
// PendingHTLCs is the set of new/pending HTLCs produced by this
// commitment state.
PendingHTLCs []channeldb.HTLC
}
// SignNextCommitment signs a new commitment which includes any previous
// unsettled HTLCs, any new HTLCs, and any modifications to prior HTLCs
// committed in previous commitment updates. Signing a new commitment
// decrements the available revocation window by 1. After a successful method
// call, the remote party's commitment chain is extended by a new commitment
// which includes all updates to the HTLC log prior to this method invocation.
// The first return parameter is the signature for the commitment transaction
// itself, while the second parameter is a slice of all HTLC signatures (if
// any). The HTLC signatures are sorted according to the BIP 69 order of the
// HTLC's on the commitment transaction. Finally, the new set of pending HTLCs
// for the remote party's commitment are also returned.
//
//nolint:funlen
func (lc *LightningChannel) SignNextCommitment(
ctx context.Context) (*NewCommitState, error) {
lc.Lock()
defer lc.Unlock()
// Check for empty commit sig. This should never happen, but we don't
// dare to fail hard here. We assume peers can deal with the empty sig
// and continue channel operation. We log an error so that the bug
// causing this can be tracked down.
if !lc.oweCommitment(lntypes.Local) {
lc.log.Errorf("sending empty commit sig")
}
var (
sig lnwire.Sig
partialSig *lnwire.PartialSigWithNonce
htlcSigs []lnwire.Sig
)
// If we're awaiting for an ACK to a commitment signature, or if we
// don't yet have the initial next revocation point of the remote
// party, then we're unable to create new states. Each time we create a
// new state, we consume a prior revocation point.
commitPoint := lc.channelState.RemoteNextRevocation
unacked := lc.commitChains.Remote.hasUnackedCommitment()
if unacked || commitPoint == nil {
lc.log.Tracef("waiting for remote ack=%v, nil "+
"RemoteNextRevocation: %v", unacked, commitPoint == nil)
return nil, ErrNoWindow
}
// Determine the last update on the remote log that has been locked in.
remoteACKedIndex := lc.commitChains.Local.tail().messageIndices.Remote
remoteHtlcIndex := lc.commitChains.Local.tail().theirHtlcIndex
// Before we extend this new commitment to the remote commitment chain,
// ensure that we aren't violating any of the constraints the remote
// party set up when we initially set up the channel. If we are, then
// we'll abort this state transition.
// We do not enforce the FeeBuffer here because when we reach this
// point all updates will have to get locked-in so we enforce the
// minimum requirement.
err := lc.validateCommitmentSanity(
remoteACKedIndex, lc.updateLogs.Local.logIndex, lntypes.Remote,
NoBuffer, nil, nil,
)
if err != nil {
return nil, err
}
// Grab the next commitment point for the remote party. This will be
// used within fetchCommitmentView to derive all the keys necessary to
// construct the commitment state.
keyRing := DeriveCommitmentKeys(
commitPoint, lntypes.Remote, lc.channelState.ChanType,
&lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg,
)
// Create a new commitment view which will calculate the evaluated
// state of the remote node's new commitment including our latest added
// HTLCs. The view includes the latest balances for both sides on the
// remote node's chain, and also update the addition height of any new
// HTLC log entries. When we creating a new remote view, we include
// _all_ of our changes (pending or committed) but only the remote
// node's changes up to the last change we've ACK'd.
newCommitView, err := lc.fetchCommitmentView(
lntypes.Remote, lc.updateLogs.Local.logIndex,
lc.updateLogs.Local.htlcCounter, remoteACKedIndex,
remoteHtlcIndex, keyRing,
)
if err != nil {
return nil, err
}
lc.log.Tracef("extending remote chain to height %v, "+
"local_log=%v, remote_log=%v",
newCommitView.height,
lc.updateLogs.Local.logIndex, remoteACKedIndex)
lc.log.Tracef("remote chain: our_balance=%v, "+
"their_balance=%v, commit_tx: %v",
newCommitView.ourBalance,
newCommitView.theirBalance,
lnutils.SpewLogClosure(newCommitView.txn))
// With the commitment view constructed, if there are any HTLC's, we'll
// need to generate signatures of each of them for the remote party's
// commitment state. We do so in two phases: first we generate and
// submit the set of signature jobs to the worker pool.
var leaseExpiry uint32
if lc.channelState.ChanType.HasLeaseExpiration() {
leaseExpiry = lc.channelState.ThawHeight
}
sigBatch, auxSigBatch, cancelChan, err := genRemoteHtlcSigJobs(
keyRing, lc.channelState, leaseExpiry, newCommitView,
lc.leafStore,
)
if err != nil {
return nil, err
}
// We'll need to send over the signatures to the remote party in the
// order as they appear on the commitment transaction after BIP 69
// sorting.
slices.SortFunc(sigBatch, func(i, j SignJob) int {
return cmp.Compare(i.OutputIndex, j.OutputIndex)
})
slices.SortFunc(auxSigBatch, func(i, j AuxSigJob) int {
return cmp.Compare(i.OutputIndex, j.OutputIndex)
})
lc.sigPool.SubmitSignBatch(sigBatch)
err = fn.MapOptionZ(lc.auxSigner, func(a AuxSigner) error {
return a.SubmitSecondLevelSigBatch(
NewAuxChanState(lc.channelState), newCommitView.txn,
auxSigBatch,
)
})
if err != nil {
return nil, fmt.Errorf("error submitting second level sig "+
"batch: %w", err)
}
// While the jobs are being carried out, we'll Sign their version of
// the new commitment transaction while we're waiting for the rest of
// the HTLC signatures to be processed.
//
// TODO(roasbeef): abstract into CommitSigner interface?
if lc.channelState.ChanType.IsTaproot() {
// In this case, we'll send out a partial signature as this is
// a musig2 channel. The encoded normal ECDSA signature will be
// just blank.
remoteSession := lc.musigSessions.RemoteSession
musig, err := remoteSession.SignCommit(
newCommitView.txn,
)
if err != nil {
close(cancelChan)
return nil, err
}
partialSig = musig.ToWireSig()
} else {
lc.signDesc.SigHashes = input.NewTxSigHashesV0Only(
newCommitView.txn,
)
rawSig, err := lc.Signer.SignOutputRaw(
newCommitView.txn, lc.signDesc,
)
if err != nil {
close(cancelChan)
return nil, err
}
sig, err = lnwire.NewSigFromSignature(rawSig)
if err != nil {
close(cancelChan)
return nil, err
}
}
// Iterate through all the responses to gather each of the signatures
// in the order they were submitted.
htlcSigs = make([]lnwire.Sig, 0, len(sigBatch))
auxSigs := make([]fn.Option[tlv.Blob], 0, len(auxSigBatch))
for i := range sigBatch {
htlcSigJob := sigBatch[i]
var jobResp SignJobResp
select {
case jobResp = <-htlcSigJob.Resp:
case <-ctx.Done():
return nil, errQuit
}
// If an error occurred, then we'll cancel any other active
// jobs.
if jobResp.Err != nil {
close(cancelChan)
return nil, jobResp.Err
}
htlcSigs = append(htlcSigs, jobResp.Sig)
if lc.auxSigner.IsNone() {
continue
}
auxHtlcSigJob := auxSigBatch[i]
var auxJobResp AuxSigJobResp
select {
case auxJobResp = <-auxHtlcSigJob.Resp:
case <-ctx.Done():
return nil, errQuit
}
// If an error occurred, then we'll cancel any other active
// jobs.
if auxJobResp.Err != nil {
close(cancelChan)
return nil, auxJobResp.Err
}
auxSigs = append(auxSigs, auxJobResp.SigBlob)
}
// As we're about to proposer a new commitment state for the remote
// party, we'll write this pending state to disk before we exit, so we
// can retransmit it if necessary.
commitDiff, err := lc.createCommitDiff(
newCommitView, sig, htlcSigs, auxSigs,
)
if err != nil {
return nil, err
}
err = lc.channelState.AppendRemoteCommitChain(commitDiff)
if err != nil {
return nil, err
}
// TODO(roasbeef): check that one eclair bug
// * need to retransmit on first state still?
// * after initial reconnect
// Extend the remote commitment chain by one with the addition of our
// latest commitment update.
lc.commitChains.Remote.addCommitment(newCommitView)
auxSigBlob, err := commitDiff.CommitSig.CustomRecords.Serialize()
if err != nil {
return nil, fmt.Errorf("unable to serialize aux sig blob: %w",
err)
}
return &NewCommitState{
CommitSigs: &CommitSigs{
CommitSig: sig,
HtlcSigs: htlcSigs,
PartialSig: lnwire.MaybePartialSigWithNonce(partialSig),
AuxSigBlob: auxSigBlob,
},
PendingHTLCs: commitDiff.Commitment.Htlcs,
}, nil
}
// resignMusigCommit is used to resign a commitment transaction for taproot
// channels when we need to retransmit a signature after a channel reestablish
// message. Taproot channels use musig2, which means we must use fresh nonces
// each time. After we receive the channel reestablish message, we learn the
// nonce we need to use for the remote party. As a result, we need to generate
// the partial signature again with the new nonce.
func (lc *LightningChannel) resignMusigCommit(
commitTx *wire.MsgTx) (lnwire.OptPartialSigWithNonceTLV, error) {
remoteSession := lc.musigSessions.RemoteSession
musig, err := remoteSession.SignCommit(commitTx)
if err != nil {
var none lnwire.OptPartialSigWithNonceTLV
return none, err
}
partialSig := lnwire.MaybePartialSigWithNonce(musig.ToWireSig())
return partialSig, nil
}
// ProcessChanSyncMsg processes a ChannelReestablish message sent by the remote
// connection upon re establishment of our connection with them. This method
// will return a single message if we are currently out of sync, otherwise a
// nil lnwire.Message will be returned. If it is decided that our level of
// de-synchronization is irreconcilable, then an error indicating the issue
// will be returned. In this case that an error is returned, the channel should
// be force closed, as we cannot continue updates.
//
// One of two message sets will be returned:
//
// - CommitSig+Updates: if we have a pending remote commit which they claim to
// have not received
// - RevokeAndAck: if we sent a revocation message that they claim to have
// not received
//
// If we detect a scenario where we need to send a CommitSig+Updates, this
// method also returns two sets models.CircuitKeys identifying the circuits
// that were opened and closed, respectively, as a result of signing the
// previous commitment txn. This allows the link to clear its mailbox of those
// circuits in case they are still in memory, and ensure the switch's circuit
// map has been updated by deleting the closed circuits.
//
//nolint:funlen
func (lc *LightningChannel) ProcessChanSyncMsg(ctx context.Context,
msg *lnwire.ChannelReestablish) ([]lnwire.Message, []models.CircuitKey,
[]models.CircuitKey, error) {
// Now we'll examine the state we have, vs what was contained in the
// chain sync message. If we're de-synchronized, then we'll send a
// batch of messages which when applied will kick start the chain
// resync.
var (
updates []lnwire.Message
openedCircuits []models.CircuitKey
closedCircuits []models.CircuitKey
)
// If the remote party included the optional fields, then we'll verify
// their correctness first, as it will influence our decisions below.
hasRecoveryOptions := msg.LocalUnrevokedCommitPoint != nil
if hasRecoveryOptions && msg.RemoteCommitTailHeight != 0 {
// We'll check that they've really sent a valid commit
// secret from our shachain for our prior height, but only if
// this isn't the first state.
heightSecret, err := lc.channelState.RevocationProducer.AtIndex(
msg.RemoteCommitTailHeight - 1,
)
if err != nil {
return nil, nil, nil, err
}
commitSecretCorrect := bytes.Equal(
heightSecret[:], msg.LastRemoteCommitSecret[:],
)
// If the commit secret they sent is incorrect then we'll fail
// the channel as the remote node has an inconsistent state.
if !commitSecretCorrect {
// In this case, we'll return an error to indicate the
// remote node sent us the wrong values. This will let
// the caller act accordingly.
lc.log.Errorf("sync failed: remote provided invalid " +
"commit secret!")
return nil, nil, nil, ErrInvalidLastCommitSecret
}
}
// If this is a taproot channel, then we expect that the remote party
// has sent the next verification nonce. If they haven't, then we'll
// bail out, otherwise we'll init our local session then continue as
// normal.
switch {
case lc.channelState.ChanType.IsTaproot() && msg.LocalNonce.IsNone():
return nil, nil, nil, fmt.Errorf("remote verification nonce " +
"not sent")
case lc.channelState.ChanType.IsTaproot() && msg.LocalNonce.IsSome():
if lc.opts.skipNonceInit {
// Don't call InitRemoteMusigNonces if we have already
// done so.
break
}
nextNonce, err := msg.LocalNonce.UnwrapOrErrV(errNoNonce)
if err != nil {
return nil, nil, nil, err
}
err = lc.InitRemoteMusigNonces(&musig2.Nonces{
PubNonce: nextNonce,
})
if err != nil {
return nil, nil, nil, fmt.Errorf("unable to init "+
"remote nonce: %w", err)
}
}
// If we detect that this is is a restored channel, then we can skip a
// portion of the verification, as we already know that we're unable to
// proceed with any updates.
isRestoredChan := lc.channelState.HasChanStatus(
channeldb.ChanStatusRestored,
)
// Take note of our current commit chain heights before we begin adding
// more to them.
var (
localTailHeight = lc.commitChains.Local.tail().height
remoteTailHeight = lc.commitChains.Remote.tail().height
remoteTipHeight = lc.commitChains.Remote.tip().height
)
// We'll now check that their view of our local chain is up-to-date.
// This means checking that what their view of our local chain tail
// height is what they believe. Note that the tail and tip height will
// always be the same for the local chain at this stage, as we won't
// store any received commitment to disk before it is ACKed.
switch {
// If their reported height for our local chain tail is ahead of our
// view, then we're behind!
case msg.RemoteCommitTailHeight > localTailHeight || isRestoredChan:
lc.log.Errorf("sync failed with local data loss: remote "+
"believes our tail height is %v, while we have %v!",
msg.RemoteCommitTailHeight, localTailHeight)
if isRestoredChan {
lc.log.Warnf("detected restored triggering DLP")
}
// We must check that we had recovery options to ensure the
// commitment secret matched up, and the remote is just not
// lying about its height.
if !hasRecoveryOptions {
// At this point we the remote is either lying about
// its height, or we are actually behind but the remote
// doesn't support data loss protection. In either case
// it is not safe for us to keep using the channel, so
// we mark it borked and fail the channel.
lc.log.Errorf("sync failed: local data loss, but no " +
"recovery option.")
return nil, nil, nil, ErrCannotSyncCommitChains
}
// In this case, we've likely lost data and shouldn't proceed
// with channel updates.
return nil, nil, nil, &ErrCommitSyncLocalDataLoss{
ChannelPoint: lc.channelState.FundingOutpoint,
CommitPoint: msg.LocalUnrevokedCommitPoint,
}
// If the height of our commitment chain reported by the remote party
// is behind our view of the chain, then they probably lost some state,
// and we'll force close the channel.
case msg.RemoteCommitTailHeight+1 < localTailHeight:
lc.log.Errorf("sync failed: remote believes our tail height is "+
"%v, while we have %v!",
msg.RemoteCommitTailHeight, localTailHeight)
return nil, nil, nil, ErrCommitSyncRemoteDataLoss
// Their view of our commit chain is consistent with our view.
case msg.RemoteCommitTailHeight == localTailHeight:
// In sync, don't have to do anything.
// We owe them a revocation if the tail of our current commitment chain
// is one greater than what they _think_ our commitment tail is. In
// this case we'll re-send the last revocation message that we sent.
// This will be the revocation message for our prior chain tail.
case msg.RemoteCommitTailHeight+1 == localTailHeight:
lc.log.Debugf("sync: remote believes our tail height is %v, "+
"while we have %v, we owe them a revocation",
msg.RemoteCommitTailHeight, localTailHeight)
heightToRetransmit := localTailHeight - 1
revocationMsg, err := lc.generateRevocation(heightToRetransmit)
if err != nil {
return nil, nil, nil, err
}
updates = append(updates, revocationMsg)
// Next, as a precaution, we'll check a special edge case. If
// they initiated a state transition, we sent the revocation,
// but died before the signature was sent. We re-transmit our
// revocation, but also initiate a state transition to re-sync
// them.
if lc.OweCommitment() {
newCommit, err := lc.SignNextCommitment(ctx)
switch {
// If we signed this state, then we'll accumulate
// another update to send over.
case err == nil:
customRecords, err := lnwire.ParseCustomRecords(
newCommit.AuxSigBlob,
)
if err != nil {
sErr := fmt.Errorf("error parsing aux "+
"sigs: %w", err)
return nil, nil, nil, sErr
}
commitSig := &lnwire.CommitSig{
ChanID: lnwire.NewChanIDFromOutPoint(
lc.channelState.FundingOutpoint,
),
CommitSig: newCommit.CommitSig,
HtlcSigs: newCommit.HtlcSigs,
PartialSig: newCommit.PartialSig,
CustomRecords: customRecords,
}
updates = append(updates, commitSig)
// If we get a failure due to not knowing their next
// point, then this is fine as they'll either send
// ChannelReady, or revoke their next state to allow
// us to continue forwards.
case err == ErrNoWindow:
// Otherwise, this is an error and we'll treat it as
// such.
default:
return nil, nil, nil, err
}
}
// There should be no other possible states.
default:
lc.log.Errorf("sync failed: remote believes our tail height is "+
"%v, while we have %v!",
msg.RemoteCommitTailHeight, localTailHeight)
return nil, nil, nil, ErrCannotSyncCommitChains
}
// Now check if our view of the remote chain is consistent with what
// they tell us.
switch {
// The remote's view of what their next commit height is 2+ states
// ahead of us, we most likely lost data, or the remote is trying to
// trick us. Since we have no way of verifying whether they are lying
// or not, we will fail the channel, but should not force close it
// automatically.
case msg.NextLocalCommitHeight > remoteTipHeight+1:
lc.log.Errorf("sync failed: remote's next commit height is %v, "+
"while we believe it is %v!",
msg.NextLocalCommitHeight, remoteTipHeight+1)
return nil, nil, nil, ErrCannotSyncCommitChains
// They are waiting for a state they have already ACKed.
case msg.NextLocalCommitHeight <= remoteTailHeight:
lc.log.Errorf("sync failed: remote's next commit height is %v, "+
"while we believe it is %v!",
msg.NextLocalCommitHeight, remoteTipHeight+1)
// They previously ACKed our current tail, and now they are
// waiting for it. They probably lost state.
return nil, nil, nil, ErrCommitSyncRemoteDataLoss
// They have received our latest commitment, life is good.
case msg.NextLocalCommitHeight == remoteTipHeight+1:
// We owe them a commitment if the tip of their chain (from our Pov) is
// equal to what they think their next commit height should be. We'll
// re-send all the updates necessary to recreate this state, along
// with the commit sig.
case msg.NextLocalCommitHeight == remoteTipHeight:
lc.log.Debugf("sync: remote's next commit height is %v, while "+
"we believe it is %v, we owe them a commitment",
msg.NextLocalCommitHeight, remoteTipHeight+1)
// Grab the current remote chain tip from the database. This
// commit diff contains all the information required to re-sync
// our states.
commitDiff, err := lc.channelState.RemoteCommitChainTip()
if err != nil {
return nil, nil, nil, err
}
var commitUpdates []lnwire.Message
// Next, we'll need to send over any updates we sent as part of
// this new proposed commitment state.
for _, logUpdate := range commitDiff.LogUpdates {
commitUpdates = append(
commitUpdates, logUpdate.UpdateMsg,
)
}
// If this is a taproot channel, then we need to regenerate the
// musig2 signature for the remote party, using their fresh
// nonce.
if lc.channelState.ChanType.IsTaproot() {
partialSig, err := lc.resignMusigCommit(
commitDiff.Commitment.CommitTx,
)
if err != nil {
return nil, nil, nil, err
}
commitDiff.CommitSig.PartialSig = partialSig
}
// With the batch of updates accumulated, we'll now re-send the
// original CommitSig message required to re-sync their remote
// commitment chain with our local version of their chain.
commitUpdates = append(commitUpdates, commitDiff.CommitSig)
// NOTE: If a revocation is not owed, then updates is empty.
if lc.channelState.LastWasRevoke {
// If lastWasRevoke is set to true, a revocation was last and we
// need to reorder the updates so that the revocation stored in
// updates comes after the LogUpdates+CommitSig.
//
// ---logupdates--->
// ---commitsig---->
// ---revocation--->
updates = append(commitUpdates, updates...)
} else {
// Otherwise, the revocation should come before LogUpdates
// + CommitSig.
//
// ---revocation--->
// ---logupdates--->
// ---commitsig---->
updates = append(updates, commitUpdates...)
}
openedCircuits = commitDiff.OpenedCircuitKeys
closedCircuits = commitDiff.ClosedCircuitKeys
// There should be no other possible states as long as the commit chain
// can have at most two elements. If that's the case, something is
// wrong.
default:
lc.log.Errorf("sync failed: remote's next commit height is %v, "+
"while we believe it is %v!",
msg.NextLocalCommitHeight, remoteTipHeight)
return nil, nil, nil, ErrCannotSyncCommitChains
}
// If we didn't have recovery options, then the final check cannot be
// performed, and we'll return early.
if !hasRecoveryOptions {
return updates, openedCircuits, closedCircuits, nil
}
// At this point we have determined that either the commit heights are
// in sync, or that we are in a state we can recover from. As a final
// check, we ensure that the commitment point sent to us by the remote
// is valid.
var commitPoint *btcec.PublicKey
switch {
// If their height is one beyond what we know their current height to
// be, then we need to compare their current unrevoked commitment point
// as that's what they should send.
case msg.NextLocalCommitHeight == remoteTailHeight+1:
commitPoint = lc.channelState.RemoteCurrentRevocation
// Alternatively, if their height is two beyond what we know their best
// height to be, then they're holding onto two commitments, and the
// highest unrevoked point is their next revocation.
//
// TODO(roasbeef): verify this in the spec...
case msg.NextLocalCommitHeight == remoteTailHeight+2:
commitPoint = lc.channelState.RemoteNextRevocation
}
// Only if this is a tweakless channel will we attempt to verify the
// commitment point, as otherwise it has no validity requirements.
tweakless := lc.channelState.ChanType.IsTweakless()
if !tweakless && commitPoint != nil &&
!commitPoint.IsEqual(msg.LocalUnrevokedCommitPoint) {
lc.log.Errorf("sync failed: remote sent invalid commit point "+
"for height %v!",
msg.NextLocalCommitHeight)
return nil, nil, nil, ErrInvalidLocalUnrevokedCommitPoint
}
return updates, openedCircuits, closedCircuits, nil
}
// computeView takes the given HtlcView, and calculates the balances, filtered
// view (settling unsettled HTLCs), commitment weight and feePerKw, after
// applying the HTLCs to the latest commitment. The returned balances are the
// balances *before* subtracting the commitment fee from the initiator's
// balance. It accepts a "dry run" feerate argument to calculate a potential
// commitment transaction fee.
//
// If the updateState boolean is set true, the add and remove heights of the
// HTLCs will be set to the next commitment height.
func (lc *LightningChannel) computeView(view *HtlcView,
whoseCommitChain lntypes.ChannelParty, updateState bool,
dryRunFee fn.Option[chainfee.SatPerKWeight]) (lnwire.MilliSatoshi,
lnwire.MilliSatoshi, lntypes.WeightUnit, *HtlcView, error) {
commitChain := lc.commitChains.Local
dustLimit := lc.channelState.LocalChanCfg.DustLimit
if whoseCommitChain.IsRemote() {
commitChain = lc.commitChains.Remote
dustLimit = lc.channelState.RemoteChanCfg.DustLimit
}
// Since the fetched htlc view will include all updates added after the
// last committed state, we start with the balances reflecting that
// state.
ourBalance := commitChain.tip().ourBalance
theirBalance := commitChain.tip().theirBalance
// Add the fee from the previous commitment state back to the
// initiator's balance, so that the fee can be recalculated and
// re-applied in case fee estimation parameters have changed or the
// number of outstanding HTLCs has changed.
if lc.channelState.IsInitiator {
ourBalance += lnwire.NewMSatFromSatoshis(
commitChain.tip().fee)
} else if !lc.channelState.IsInitiator {
theirBalance += lnwire.NewMSatFromSatoshis(
commitChain.tip().fee)
}
nextHeight := commitChain.tip().height + 1
// Initiate feePerKw to the last committed fee for this chain as we'll
// need this to determine which HTLCs are dust, and also the final fee
// rate.
view.FeePerKw = commitChain.tip().feePerKw
view.NextHeight = nextHeight
// We evaluate the view at this stage, meaning settled and failed HTLCs
// will remove their corresponding added HTLCs. The resulting filtered
// view will only have Add entries left, making it easy to compare the
// channel constraints to the final commitment state. If any fee
// updates are found in the logs, the commitment fee rate should be
// changed, so we'll also set the feePerKw to this new value.
filteredHTLCView, uncommitted, deltas, err := lc.evaluateHTLCView(
view, whoseCommitChain, nextHeight,
)
if err != nil {
return 0, 0, 0, nil, err
}
// Add the balance deltas to the balances we got from the commitment
// state.
if deltas.Local >= 0 {
ourBalance += lnwire.MilliSatoshi(deltas.Local)
} else {
ourBalance -= lnwire.MilliSatoshi(-1 * deltas.Local)
}
if deltas.Remote >= 0 {
theirBalance += lnwire.MilliSatoshi(deltas.Remote)
} else {
theirBalance -= lnwire.MilliSatoshi(-1 * deltas.Remote)
}
if updateState {
for _, party := range lntypes.BothParties {
for _, u := range uncommitted.GetForParty(party) {
u.setCommitHeight(whoseCommitChain, nextHeight)
if whoseCommitChain == lntypes.Local &&
u.EntryType == Settle {
lc.recordSettlement(party, u.Amount)
}
}
}
}
feePerKw := filteredHTLCView.FeePerKw
// Here we override the view's fee-rate if a dry-run fee-rate was
// passed in.
if !updateState {
feePerKw = dryRunFee.UnwrapOr(feePerKw)
}
// We need to first check ourBalance and theirBalance to be negative
// because MilliSathoshi is a unsigned type and can underflow in the
// code above. This should never happen for views which do not
// include new updates (remote or local).
if int64(ourBalance) < 0 {
err := fmt.Errorf("%w: our balance", ErrBelowChanReserve)
return 0, 0, 0, nil, err
}
if int64(theirBalance) < 0 {
err := fmt.Errorf("%w: their balance", ErrBelowChanReserve)
return 0, 0, 0, nil, err
}
// Now go through all HTLCs at this stage, to calculate the total
// weight, needed to calculate the transaction fee.
var totalHtlcWeight lntypes.WeightUnit
for _, htlc := range filteredHTLCView.Updates.Local {
if HtlcIsDust(
lc.channelState.ChanType, false, whoseCommitChain,
feePerKw, htlc.Amount.ToSatoshis(), dustLimit,
) {
continue
}
totalHtlcWeight += input.HTLCWeight
}
for _, htlc := range filteredHTLCView.Updates.Remote {
if HtlcIsDust(
lc.channelState.ChanType, true, whoseCommitChain,
feePerKw, htlc.Amount.ToSatoshis(), dustLimit,
) {
continue
}
totalHtlcWeight += input.HTLCWeight
}
totalCommitWeight := CommitWeight(lc.channelState.ChanType) +
totalHtlcWeight
return ourBalance, theirBalance, totalCommitWeight, filteredHTLCView, nil
}
// recordSettlement updates the lifetime payment flow values in persistent state
// of the LightningChannel, adding amt to the total received by the redeemer.
func (lc *LightningChannel) recordSettlement(
redeemer lntypes.ChannelParty, amt lnwire.MilliSatoshi) {
if redeemer == lntypes.Local {
lc.channelState.TotalMSatReceived += amt
} else {
lc.channelState.TotalMSatSent += amt
}
}
// genHtlcSigValidationJobs generates a series of signatures verification jobs
// meant to verify all the signatures for HTLC's attached to a newly created
// commitment state. The jobs generated are fully populated, and can be sent
// directly into the pool of workers.
//
//nolint:funlen
func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
localCommitmentView *commitment, keyRing *CommitmentKeyRing,
htlcSigs []lnwire.Sig, leaseExpiry uint32,
leafStore fn.Option[AuxLeafStore], auxSigner fn.Option[AuxSigner],
sigBlob fn.Option[tlv.Blob]) ([]VerifyJob, []AuxVerifyJob, error) {
var (
isLocalInitiator = chanState.IsInitiator
localChanCfg = chanState.LocalChanCfg
chanType = chanState.ChanType
)
txHash := localCommitmentView.txn.TxHash()
feePerKw := localCommitmentView.feePerKw
sigHashType := HtlcSigHashType(chanType)
// With the required state generated, we'll create a slice with large
// enough capacity to hold verification jobs for all HTLC's in this
// view. In the case that we have some dust outputs, then the actual
// length will be smaller than the total capacity.
numHtlcs := len(localCommitmentView.incomingHTLCs) +
len(localCommitmentView.outgoingHTLCs)
verifyJobs := make([]VerifyJob, 0, numHtlcs)
auxVerifyJobs := make([]AuxVerifyJob, 0, numHtlcs)
diskCommit := localCommitmentView.toDiskCommit(lntypes.Local)
auxResult, err := fn.MapOptionZ(
leafStore, func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return s.FetchLeavesFromCommit(
NewAuxChanState(chanState), *diskCommit,
*keyRing, lntypes.Local,
)
},
).Unpack()
if err != nil {
return nil, nil, fmt.Errorf("unable to fetch aux leaves: %w",
err)
}
// If we have a sig blob, then we'll attempt to map that to individual
// blobs for each HTLC we might need a signature for.
auxHtlcSigs, err := fn.MapOptionZ(
auxSigner, func(a AuxSigner) fn.Result[[]fn.Option[tlv.Blob]] {
return a.UnpackSigs(sigBlob)
},
).Unpack()
if err != nil {
return nil, nil, fmt.Errorf("error unpacking aux sigs: %w",
err)
}
// We'll iterate through each output in the commitment transaction,
// populating the sigHash closure function if it's detected to be an
// HLTC output. Given the sighash, and the signing key, we'll be able
// to validate each signature within the worker pool.
i := 0
for index := range localCommitmentView.txn.TxOut {
var (
htlcIndex uint64
sigHash func() ([]byte, error)
sig input.Signature
htlc *paymentDescriptor
incoming bool
auxLeaf input.AuxTapLeaf
err error
)
outputIndex := int32(index)
switch {
// If this output index is found within the incoming HTLC
// index, then this means that we need to generate an HTLC
// success transaction in order to validate the signature.
//nolint:ll
case localCommitmentView.incomingHTLCIndex[outputIndex] != nil:
htlc = localCommitmentView.incomingHTLCIndex[outputIndex]
htlcIndex = htlc.HtlcIndex
incoming = true
sigHash = func() ([]byte, error) {
op := wire.OutPoint{
Hash: txHash,
Index: uint32(htlc.localOutputIndex),
}
htlcFee := HtlcSuccessFee(chanType, feePerKw)
outputAmt := htlc.Amount.ToSatoshis() - htlcFee
auxLeaf := fn.FlatMapOption(func(
l CommitAuxLeaves) input.AuxTapLeaf {
leaves := l.IncomingHtlcLeaves
idx := htlc.HtlcIndex
return leaves[idx].SecondLevelLeaf
})(auxResult.AuxLeaves)
successTx, err := CreateHtlcSuccessTx(
chanType, isLocalInitiator, op,
outputAmt, uint32(localChanCfg.CsvDelay),
leaseExpiry, keyRing.RevocationKey,
keyRing.ToLocalKey, auxLeaf,
)
if err != nil {
return nil, err
}
htlcAmt := int64(htlc.Amount.ToSatoshis())
if chanType.IsTaproot() {
// TODO(roasbeef): add abstraction in
// front
prevFetcher := txscript.NewCannedPrevOutputFetcher( //nolint:ll
htlc.ourPkScript, htlcAmt,
)
hashCache := txscript.NewTxSigHashes(
successTx, prevFetcher,
)
tapLeaf := txscript.NewBaseTapLeaf(
htlc.ourWitnessScript,
)
return txscript.CalcTapscriptSignaturehash( //nolint:ll
hashCache, sigHashType,
successTx, 0, prevFetcher,
tapLeaf,
)
}
hashCache := input.NewTxSigHashesV0Only(successTx)
sigHash, err := txscript.CalcWitnessSigHash(
htlc.ourWitnessScript, hashCache,
sigHashType, successTx, 0,
htlcAmt,
)
if err != nil {
return nil, err
}
return sigHash, nil
}
// Make sure there are more signatures left.
if i >= len(htlcSigs) {
return nil, nil, fmt.Errorf("not enough HTLC " +
"signatures")
}
// If this is a taproot channel, then we'll convert it
// to a schnorr signature, so we can get correct type
// from ToSignature below.
if chanType.IsTaproot() {
htlcSigs[i].ForceSchnorr()
}
// With the sighash generated, we'll also store the
// signature so it can be written to disk if this state
// is valid.
sig, err = htlcSigs[i].ToSignature()
if err != nil {
return nil, nil, err
}
htlc.sig = sig
// Otherwise, if this is an outgoing HTLC, then we'll need to
// generate a timeout transaction so we can verify the
// signature presented.
//nolint:ll
case localCommitmentView.outgoingHTLCIndex[outputIndex] != nil:
htlc = localCommitmentView.outgoingHTLCIndex[outputIndex]
htlcIndex = htlc.HtlcIndex
sigHash = func() ([]byte, error) {
op := wire.OutPoint{
Hash: txHash,
Index: uint32(htlc.localOutputIndex),
}
htlcFee := HtlcTimeoutFee(chanType, feePerKw)
outputAmt := htlc.Amount.ToSatoshis() - htlcFee
auxLeaf := fn.FlatMapOption(func(
l CommitAuxLeaves) input.AuxTapLeaf {
leaves := l.OutgoingHtlcLeaves
idx := htlc.HtlcIndex
return leaves[idx].SecondLevelLeaf
})(auxResult.AuxLeaves)
timeoutTx, err := CreateHtlcTimeoutTx(
chanType, isLocalInitiator, op,
outputAmt, htlc.Timeout,
uint32(localChanCfg.CsvDelay),
leaseExpiry, keyRing.RevocationKey,
keyRing.ToLocalKey, auxLeaf,
)
if err != nil {
return nil, err
}
htlcAmt := int64(htlc.Amount.ToSatoshis())
if chanType.IsTaproot() {
// TODO(roasbeef): add abstraction in
// front
prevFetcher := txscript.NewCannedPrevOutputFetcher( //nolint:ll
htlc.ourPkScript, htlcAmt,
)
hashCache := txscript.NewTxSigHashes(
timeoutTx, prevFetcher,
)
tapLeaf := txscript.NewBaseTapLeaf(
htlc.ourWitnessScript,
)
return txscript.CalcTapscriptSignaturehash( //nolint:ll
hashCache, sigHashType,
timeoutTx, 0, prevFetcher,
tapLeaf,
)
}
hashCache := input.NewTxSigHashesV0Only(
timeoutTx,
)
sigHash, err := txscript.CalcWitnessSigHash(
htlc.ourWitnessScript, hashCache,
sigHashType, timeoutTx, 0,
htlcAmt,
)
if err != nil {
return nil, err
}
return sigHash, nil
}
// Make sure there are more signatures left.
if i >= len(htlcSigs) {
return nil, nil, fmt.Errorf("not enough HTLC " +
"signatures")
}
// If this is a taproot channel, then we'll convert it
// to a schnorr signature, so we can get correct type
// from ToSignature below.
if chanType.IsTaproot() {
htlcSigs[i].ForceSchnorr()
}
// With the sighash generated, we'll also store the
// signature so it can be written to disk if this state
// is valid.
sig, err = htlcSigs[i].ToSignature()
if err != nil {
return nil, nil, err
}
htlc.sig = sig
default:
continue
}
verifyJobs = append(verifyJobs, VerifyJob{
HtlcIndex: htlcIndex,
PubKey: keyRing.RemoteHtlcKey,
Sig: sig,
SigHash: sigHash,
})
if len(auxHtlcSigs) > i {
auxSig := auxHtlcSigs[i]
auxVerifyJob := NewAuxVerifyJob(
auxSig, *keyRing, incoming,
newAuxHtlcDescriptor(htlc),
localCommitmentView.customBlob, auxLeaf,
)
if htlc.CustomRecords == nil {
htlc.CustomRecords = make(lnwire.CustomRecords)
}
// As this HTLC has a custom signature associated with
// it, store it in the custom records map so we can
// write to disk later.
sigType := htlcCustomSigType.TypeVal()
htlc.CustomRecords[uint64(sigType)] = auxSig.UnwrapOr(
nil,
)
auxVerifyJobs = append(auxVerifyJobs, auxVerifyJob)
}
i++
}
// If we received a number of HTLC signatures that doesn't match our
// commitment, we'll return an error now.
if len(htlcSigs) != i {
return nil, nil, fmt.Errorf("number of htlc sig mismatch. "+
"Expected %v sigs, got %v", i, len(htlcSigs))
}
return verifyJobs, auxVerifyJobs, nil
}
// InvalidCommitSigError is a struct that implements the error interface to
// report a failure to validate a commitment signature for a remote peer.
// We'll use the items in this struct to generate a rich error message for the
// remote peer when we receive an invalid signature from it. Doing so can
// greatly aide in debugging cross implementation issues.
type InvalidCommitSigError struct {
commitHeight uint64
commitSig []byte
sigHash []byte
commitTx []byte
}
// Error returns a detailed error string including the exact transaction that
// caused an invalid commitment signature.
func (i *InvalidCommitSigError) Error() string {
return fmt.Sprintf("rejected commitment: commit_height=%v, "+
"invalid_commit_sig=%x, commit_tx=%x, sig_hash=%x", i.commitHeight,
i.commitSig[:], i.commitTx, i.sigHash[:])
}
// A compile time flag to ensure that InvalidCommitSigError implements the
// error interface.
var _ error = (*InvalidCommitSigError)(nil)
// InvalidPartialCommitSigError is used when we encounter an invalid musig2
// partial signature.
type InvalidPartialCommitSigError struct {
InvalidCommitSigError
*invalidPartialSigError
}
// Error returns a detailed error string including the exact transaction that
// caused an invalid partial commit sig signature.
func (i *InvalidPartialCommitSigError) Error() string {
return fmt.Sprintf("rejected commitment: commit_height=%v, "+
"commit_tx=%x -- %v", i.commitHeight, i.commitTx,
i.invalidPartialSigError)
}
// InvalidHtlcSigError is a struct that implements the error interface to
// report a failure to validate an htlc signature from a remote peer. We'll use
// the items in this struct to generate a rich error message for the remote
// peer when we receive an invalid signature from it. Doing so can greatly aide
// in debugging across implementation issues.
type InvalidHtlcSigError struct {
commitHeight uint64
htlcSig []byte
htlcIndex uint64
sigHash []byte
commitTx []byte
}
// Error returns a detailed error string including the exact transaction that
// caused an invalid htlc signature.
func (i *InvalidHtlcSigError) Error() string {
return fmt.Sprintf("rejected commitment: commit_height=%v, "+
"invalid_htlc_sig=%x, commit_tx=%x, sig_hash=%x", i.commitHeight,
i.htlcSig, i.commitTx, i.sigHash[:])
}
// A compile time flag to ensure that InvalidCommitSigError implements the
// error interface.
var _ error = (*InvalidCommitSigError)(nil)
// ReceiveNewCommitment process a signature for a new commitment state sent by
// the remote party. This method should be called in response to the
// remote party initiating a new change, or when the remote party sends a
// signature fully accepting a new state we've initiated. If we are able to
// successfully validate the signature, then the generated commitment is added
// to our local commitment chain. Once we send a revocation for our prior
// state, then this newly added commitment becomes our current accepted channel
// state.
//
//nolint:funlen
func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error {
lc.Lock()
defer lc.Unlock()
// Check for empty commit sig. Because of a previously existing bug, it
// is possible that we receive an empty commit sig from nodes running an
// older version. This is a relaxation of the spec, but it is still
// possible to handle it. To not break any channels with those older
// nodes, we just log the event. This check is also not totally
// reliable, because it could be that we've sent out a new sig, but the
// remote hasn't received it yet. We could then falsely assume that they
// should add our updates to their remote commitment tx.
if !lc.oweCommitment(lntypes.Remote) {
lc.log.Warnf("empty commit sig message received")
}
// Determine the last update on the local log that has been locked in.
localACKedIndex := lc.commitChains.Remote.tail().messageIndices.Local
localHtlcIndex := lc.commitChains.Remote.tail().ourHtlcIndex
// Ensure that this new local update from the remote node respects all
// the constraints we specified during initial channel setup. If not,
// then we'll abort the channel as they've violated our constraints.
//
// We do not enforce the FeeBuffer here because when we reach this
// point all updates will have to get locked-in (we already received
// the UpdateAddHTLC msg from our peer prior to receiving the
// commit-sig).
err := lc.validateCommitmentSanity(
lc.updateLogs.Remote.logIndex, localACKedIndex, lntypes.Local,
NoBuffer, nil, nil,
)
if err != nil {
return err
}
// We're receiving a new commitment which attempts to extend our local
// commitment chain height by one, so fetch the proper commitment point
// as this will be needed to derive the keys required to construct the
// commitment.
nextHeight := lc.currentHeight + 1
commitSecret, err := lc.channelState.RevocationProducer.AtIndex(nextHeight)
if err != nil {
return err
}
commitPoint := input.ComputeCommitmentPoint(commitSecret[:])
keyRing := DeriveCommitmentKeys(
commitPoint, lntypes.Local, lc.channelState.ChanType,
&lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg,
)
// With the current commitment point re-calculated, construct the new
// commitment view which includes all the entries (pending or committed)
// we know of in the remote node's HTLC log, but only our local changes
// up to the last change the remote node has ACK'd.
localCommitmentView, err := lc.fetchCommitmentView(
lntypes.Local, localACKedIndex, localHtlcIndex,
lc.updateLogs.Remote.logIndex, lc.updateLogs.Remote.htlcCounter,
keyRing,
)
if err != nil {
return err
}
lc.log.Tracef("extending local chain to height %v, "+
"local_log=%v, remote_log=%v",
localCommitmentView.height,
localACKedIndex, lc.updateLogs.Remote.logIndex)
lc.log.Tracef("local chain: our_balance=%v, "+
"their_balance=%v, commit_tx: %v",
localCommitmentView.ourBalance, localCommitmentView.theirBalance,
lnutils.SpewLogClosure(localCommitmentView.txn))
var auxSigBlob fn.Option[tlv.Blob]
if commitSigs.AuxSigBlob != nil {
auxSigBlob = fn.Some(commitSigs.AuxSigBlob)
}
// As an optimization, we'll generate a series of jobs for the worker
// pool to verify each of the HTLC signatures presented. Once
// generated, we'll submit these jobs to the worker pool.
var leaseExpiry uint32
if lc.channelState.ChanType.HasLeaseExpiration() {
leaseExpiry = lc.channelState.ThawHeight
}
verifyJobs, auxVerifyJobs, err := genHtlcSigValidationJobs(
lc.channelState, localCommitmentView, keyRing,
commitSigs.HtlcSigs, leaseExpiry, lc.leafStore, lc.auxSigner,
auxSigBlob,
)
if err != nil {
return err
}
cancelChan := make(chan struct{})
verifyResps := lc.sigPool.SubmitVerifyBatch(verifyJobs, cancelChan)
localCommitTx := localCommitmentView.txn
// While the HTLC verification jobs are proceeding asynchronously,
// we'll ensure that the newly constructed commitment state has a valid
// signature.
//
// To do that we'll, construct the sighash of the commitment
// transaction corresponding to this newly proposed state update. If
// this is a taproot channel, then in order to validate the sighash,
// we'll need to call into the relevant tapscript methods.
if lc.channelState.ChanType.IsTaproot() {
localSession := lc.musigSessions.LocalSession
partialSig, err := commitSigs.PartialSig.UnwrapOrErrV(
errNoPartialSig,
)
if err != nil {
return err
}
// As we want to ensure we never write nonces to disk, we'll
// use the shachain state to generate a nonce for our next
// local state. Similar to generateRevocation, we do height + 2
// (next height + 1) here, as this is for the _next_ local
// state, and we're about to accept height + 1.
localCtrNonce := WithLocalCounterNonce(
nextHeight+1, lc.taprootNonceProducer,
)
nextVerificationNonce, err := localSession.VerifyCommitSig(
localCommitTx, &partialSig, localCtrNonce,
)
if err != nil {
close(cancelChan)
var sigErr invalidPartialSigError
if errors.As(err, &sigErr) {
// If we fail to validate their commitment
// signature, we'll generate a special error to
// send over the protocol. We'll include the
// exact signature and commitment we failed to
// verify against in order to aide debugging.
var txBytes bytes.Buffer
_ = localCommitTx.Serialize(&txBytes)
return &InvalidPartialCommitSigError{
invalidPartialSigError: &sigErr,
InvalidCommitSigError: InvalidCommitSigError{ //nolint:ll
commitHeight: nextHeight,
commitTx: txBytes.Bytes(),
},
}
}
return err
}
// Now that we have the next verification nonce for our local
// session, we'll refresh it to yield a new session we'll use
// for the next incoming signature.
newLocalSession, err := lc.musigSessions.LocalSession.Refresh(
nextVerificationNonce,
)
if err != nil {
return err
}
lc.musigSessions.LocalSession = newLocalSession
} else {
multiSigScript := lc.signDesc.WitnessScript
prevFetcher := txscript.NewCannedPrevOutputFetcher(
multiSigScript, int64(lc.channelState.Capacity),
)
hashCache := txscript.NewTxSigHashes(localCommitTx, prevFetcher)
sigHash, err := txscript.CalcWitnessSigHash(
multiSigScript, hashCache, txscript.SigHashAll,
localCommitTx, 0, int64(lc.channelState.Capacity),
)
if err != nil {
// TODO(roasbeef): fetchview has already mutated the
// HTLCs... * need to either roll-back, or make pure
return err
}
verifyKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey
cSig, err := commitSigs.CommitSig.ToSignature()
if err != nil {
return err
}
if !cSig.Verify(sigHash, verifyKey) {
close(cancelChan)
// If we fail to validate their commitment signature,
// we'll generate a special error to send over the
// protocol. We'll include the exact signature and
// commitment we failed to verify against in order to
// aide debugging.
var txBytes bytes.Buffer
_ = localCommitTx.Serialize(&txBytes)
return &InvalidCommitSigError{
commitHeight: nextHeight,
commitSig: commitSigs.CommitSig.ToSignatureBytes(), //nolint:ll
sigHash: sigHash,
commitTx: txBytes.Bytes(),
}
}
}
// With the primary commitment transaction validated, we'll check each
// of the HTLC validation jobs.
for i := 0; i < len(verifyJobs); i++ {
// In the case that a single signature is invalid, we'll exit
// early and cancel all the outstanding verification jobs.
htlcErr := <-verifyResps
if htlcErr != nil {
close(cancelChan)
sig, err := lnwire.NewSigFromSignature(
htlcErr.Sig,
)
if err != nil {
return err
}
sigHash, err := htlcErr.SigHash()
if err != nil {
return err
}
var txBytes bytes.Buffer
err = localCommitTx.Serialize(&txBytes)
if err != nil {
return err
}
return &InvalidHtlcSigError{
commitHeight: nextHeight,
htlcSig: sig.ToSignatureBytes(),
htlcIndex: htlcErr.HtlcIndex,
sigHash: sigHash,
commitTx: txBytes.Bytes(),
}
}
}
// Now that we know all the normal sigs are valid, we'll also verify
// the aux jobs, if any exist.
err = fn.MapOptionZ(lc.auxSigner, func(a AuxSigner) error {
return a.VerifySecondLevelSigs(
NewAuxChanState(lc.channelState), localCommitTx,
auxVerifyJobs,
)
})
if err != nil {
return fmt.Errorf("unable to validate aux sigs: %w", err)
}
// The signature checks out, so we can now add the new commitment to
// our local commitment chain. For regular channels, we can just
// serialize the ECDSA sig. For taproot channels, we'll serialize the
// partial sig that includes the nonce that was used for signing.
if lc.channelState.ChanType.IsTaproot() {
partialSig, err := commitSigs.PartialSig.UnwrapOrErrV(
errNoPartialSig,
)
if err != nil {
return err
}
var sigBytes [lnwire.PartialSigWithNonceLen]byte
b := bytes.NewBuffer(sigBytes[0:0])
if err := partialSig.Encode(b); err != nil {
return err
}
localCommitmentView.sig = sigBytes[:]
} else {
localCommitmentView.sig = commitSigs.CommitSig.ToSignatureBytes() //nolint:ll
}
lc.commitChains.Local.addCommitment(localCommitmentView)
return nil
}
// IsChannelClean returns true if neither side has pending commitments, neither
// side has HTLC's, and all updates are locked in irrevocably. Internally, it
// utilizes the oweCommitment function by calling it for local and remote
// evaluation. We check if we have a pending commitment for our local state
// since this function may be called by sub-systems that are not the link (e.g.
// the rpcserver), and the ReceiveNewCommitment & RevokeCurrentCommitment calls
// are not atomic, even though link processing ensures no updates can happen in
// between.
func (lc *LightningChannel) IsChannelClean() bool {
lc.RLock()
defer lc.RUnlock()
// Check whether we have a pending commitment for our local state.
if lc.commitChains.Local.hasUnackedCommitment() {
return false
}
// Check whether our counterparty has a pending commitment for their
// state.
if lc.commitChains.Remote.hasUnackedCommitment() {
return false
}
// We call ActiveHtlcs to ensure there are no HTLCs on either
// commitment.
if len(lc.channelState.ActiveHtlcs()) != 0 {
return false
}
// Now check that both local and remote commitments are signing the
// same updates.
if lc.oweCommitment(lntypes.Local) {
return false
}
if lc.oweCommitment(lntypes.Remote) {
return false
}
// If we reached this point, the channel has no HTLCs and both
// commitments sign the same updates.
return true
}
// OweCommitment returns a boolean value reflecting whether we need to send
// out a commitment signature because there are outstanding local updates and/or
// updates in the local commit tx that aren't reflected in the remote commit tx
// yet.
func (lc *LightningChannel) OweCommitment() bool {
lc.RLock()
defer lc.RUnlock()
return lc.oweCommitment(lntypes.Local)
}
// NeedCommitment returns a boolean value reflecting whether we are waiting on
// a commitment signature because there are outstanding remote updates and/or
// updates in the remote commit tx that aren't reflected in the local commit tx
// yet.
func (lc *LightningChannel) NeedCommitment() bool {
lc.RLock()
defer lc.RUnlock()
return lc.oweCommitment(lntypes.Remote)
}
// oweCommitment is the internal version of OweCommitment. This function expects
// to be executed with a lock held.
func (lc *LightningChannel) oweCommitment(issuer lntypes.ChannelParty) bool {
var (
remoteUpdatesPending, localUpdatesPending bool
lastLocalCommit = lc.commitChains.Local.tip()
lastRemoteCommit = lc.commitChains.Remote.tip()
perspective string
)
if issuer.IsLocal() {
perspective = "local"
// There are local updates pending if our local update log is
// not in sync with our remote commitment tx.
localUpdatesPending = lc.updateLogs.Local.logIndex !=
lastRemoteCommit.messageIndices.Local
// There are remote updates pending if their remote commitment
// tx (our local commitment tx) contains updates that we don't
// have added to our remote commitment tx yet.
remoteUpdatesPending = lastLocalCommit.messageIndices.Remote !=
lastRemoteCommit.messageIndices.Remote
} else {
perspective = "remote"
// There are local updates pending (local updates from the
// perspective of the remote party) if the remote party has
// updates to their remote tx pending for which they haven't
// signed yet.
localUpdatesPending = lc.updateLogs.Remote.logIndex !=
lastLocalCommit.messageIndices.Remote
// There are remote updates pending (remote updates from the
// perspective of the remote party) if we have updates on our
// remote commitment tx that they haven't added to theirs yet.
remoteUpdatesPending = lastRemoteCommit.messageIndices.Local !=
lastLocalCommit.messageIndices.Local
}
// If any of the conditions above is true, we owe a commitment
// signature.
oweCommitment := localUpdatesPending || remoteUpdatesPending
lc.log.Tracef("%v owes commit: %v (local updates: %v, "+
"remote updates %v)", perspective, oweCommitment,
localUpdatesPending, remoteUpdatesPending)
return oweCommitment
}
// NumPendingUpdates returns the number of updates originated by whoseUpdates
// that have not been committed to the *tip* of whoseCommit's commitment chain.
func (lc *LightningChannel) NumPendingUpdates(whoseUpdates lntypes.ChannelParty,
whoseCommit lntypes.ChannelParty) uint64 {
lc.RLock()
defer lc.RUnlock()
lastCommit := lc.commitChains.GetForParty(whoseCommit).tip()
updateIndex := lc.updateLogs.GetForParty(whoseUpdates).logIndex
return updateIndex - lastCommit.messageIndices.GetForParty(whoseUpdates)
}
// RevokeCurrentCommitment revokes the next lowest unrevoked commitment
// transaction in the local commitment chain. As a result the edge of our
// revocation window is extended by one, and the tail of our local commitment
// chain is advanced by a single commitment. This now lowest unrevoked
// commitment becomes our currently accepted state within the channel. This
// method also returns the set of HTLC's currently active within the commitment
// transaction and the htlcs the were resolved. This return value allows callers
// to act once an HTLC has been locked into our commitment transaction.
func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.RevokeAndAck,
[]channeldb.HTLC, map[uint64]bool, error) {
lc.Lock()
defer lc.Unlock()
revocationMsg, err := lc.generateRevocation(lc.currentHeight)
if err != nil {
return nil, nil, nil, err
}
lc.log.Tracef("revoking height=%v, now at height=%v",
lc.commitChains.Local.tail().height,
lc.currentHeight+1)
// Advance our tail, as we've revoked our previous state.
lc.commitChains.Local.advanceTail()
lc.currentHeight++
// Additionally, generate a channel delta for this state transition for
// persistent storage.
chainTail := lc.commitChains.Local.tail()
newCommitment := chainTail.toDiskCommit(lntypes.Local)
// Get the unsigned acked remotes updates that are currently in memory.
// We need them after a restart to sync our remote commitment with what
// is committed locally.
unsignedAckedUpdates := lc.getUnsignedAckedUpdates()
finalHtlcs, err := lc.channelState.UpdateCommitment(
newCommitment, unsignedAckedUpdates,
)
if err != nil {
return nil, nil, nil, err
}
lc.log.Tracef("state transition accepted: "+
"our_balance=%v, their_balance=%v, unsigned_acked_updates=%v",
chainTail.ourBalance,
chainTail.theirBalance,
len(unsignedAckedUpdates))
revocationMsg.ChanID = lnwire.NewChanIDFromOutPoint(
lc.channelState.FundingOutpoint,
)
return revocationMsg, newCommitment.Htlcs, finalHtlcs, nil
}
// ReceiveRevocation processes a revocation sent by the remote party for the
// lowest unrevoked commitment within their commitment chain. We receive a
// revocation either during the initial session negotiation wherein revocation
// windows are extended, or in response to a state update that we initiate. If
// successful, then the remote commitment chain is advanced by a single
// commitment, and a log compaction is attempted.
//
// The returned values correspond to:
// 1. The forwarding package corresponding to the remote commitment height
// that was revoked.
// 2. The set of HTLCs present on the current valid commitment transaction
// for the remote party.
func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) (
*channeldb.FwdPkg, []channeldb.HTLC, error) {
lc.Lock()
defer lc.Unlock()
// Ensure that the new pre-image can be placed in preimage store.
store := lc.channelState.RevocationStore
revocation, err := chainhash.NewHash(revMsg.Revocation[:])
if err != nil {
return nil, nil, err
}
if err := store.AddNextEntry(revocation); err != nil {
return nil, nil, err
}
// Verify that if we use the commitment point computed based off of the
// revealed secret to derive a revocation key with our revocation base
// point, then it matches the current revocation of the remote party.
currentCommitPoint := lc.channelState.RemoteCurrentRevocation
derivedCommitPoint := input.ComputeCommitmentPoint(revMsg.Revocation[:])
if !derivedCommitPoint.IsEqual(currentCommitPoint) {
return nil, nil, fmt.Errorf("revocation key mismatch")
}
// Now that we've verified that the prior commitment has been properly
// revoked, we'll advance the revocation state we track for the remote
// party: the new current revocation is what was previously the next
// revocation, and the new next revocation is set to the key included
// in the message.
lc.channelState.RemoteCurrentRevocation = lc.channelState.RemoteNextRevocation
lc.channelState.RemoteNextRevocation = revMsg.NextRevocationKey
lc.log.Tracef("remote party accepted state transition, revoked height "+
"%v, now at %v",
lc.commitChains.Remote.tail().height,
lc.commitChains.Remote.tail().height+1)
// Add one to the remote tail since this will be height *after* we write
// the revocation to disk, the local height will remain unchanged.
remoteChainTail := lc.commitChains.Remote.tail().height + 1
localChainTail := lc.commitChains.Local.tail().height
source := lc.ShortChanID()
// Determine the set of htlcs that can be forwarded as a result of
// having received the revocation. We will simultaneously construct the
// log updates and payment descriptors, allowing us to persist the log
// updates to disk and optimistically buffer the forwarding package in
// memory.
var (
addUpdatesToForward []channeldb.LogUpdate
settleFailUpdatesToForward []channeldb.LogUpdate
)
var addIndex, settleFailIndex uint16
for e := lc.updateLogs.Remote.Front(); e != nil; e = e.Next() {
pd := e.Value
// Fee updates are local to this particular channel, and should
// never be forwarded.
if pd.EntryType == FeeUpdate {
continue
}
if pd.isForwarded {
continue
}
// For each type of HTLC, we will only consider forwarding it if
// both of the remote and local heights are non-zero. If either
// of these values is zero, it has yet to be committed in both
// the local and remote chains.
committedAdd := pd.addCommitHeights.Remote > 0 &&
pd.addCommitHeights.Local > 0
committedRmv := pd.removeCommitHeights.Remote > 0 &&
pd.removeCommitHeights.Local > 0
// Using the height of the remote and local commitments,
// preemptively compute whether or not to forward this HTLC for
// the case in which this in an Add HTLC, or if this is a
// Settle, Fail, or MalformedFail.
shouldFwdAdd := remoteChainTail == pd.addCommitHeights.Remote &&
localChainTail >= pd.addCommitHeights.Local
shouldFwdRmv := remoteChainTail ==
pd.removeCommitHeights.Remote &&
localChainTail >= pd.removeCommitHeights.Local
// We'll only forward any new HTLC additions iff, it's "freshly
// locked in". Meaning that the HTLC was only *just* considered
// locked-in at this new state. By doing this we ensure that we
// don't re-forward any already processed HTLC's after a
// restart.
switch {
case pd.EntryType == Add && committedAdd && shouldFwdAdd:
// Construct a reference specifying the location that
// this forwarded Add will be written in the forwarding
// package constructed at this remote height.
pd.SourceRef = &channeldb.AddRef{
Height: remoteChainTail,
Index: addIndex,
}
addIndex++
pd.isForwarded = true
// At this point we put the update into our list of
// updates that we will eventually put into the
// FwdPkg at this height.
addUpdatesToForward = append(
addUpdatesToForward, pd.toLogUpdate(),
)
case pd.EntryType != Add && committedRmv && shouldFwdRmv:
// Construct a reference specifying the location that
// this forwarded Settle/Fail will be written in the
// forwarding package constructed at this remote height.
pd.DestRef = &channeldb.SettleFailRef{
Source: source,
Height: remoteChainTail,
Index: settleFailIndex,
}
settleFailIndex++
pd.isForwarded = true
// At this point we put the update into our list of
// updates that we will eventually put into the
// FwdPkg at this height.
settleFailUpdatesToForward = append(
settleFailUpdatesToForward, pd.toLogUpdate(),
)
default:
// The update was not "freshly locked in" so we will
// ignore it as we construct the forwarding package.
continue
}
}
// We use the remote commitment chain's tip as it will soon become the tail
// once advanceTail is called.
remoteMessageIndex := lc.commitChains.Remote.tip().messageIndices.Local
localMessageIndex := lc.commitChains.Local.tail().messageIndices.Local
localPeerUpdates := lc.unsignedLocalUpdates(
remoteMessageIndex, localMessageIndex,
)
// Now that we have gathered the set of HTLCs to forward, separated by
// type, construct a forwarding package using the height that the remote
// commitment chain will be extended after persisting the revocation.
fwdPkg := channeldb.NewFwdPkg(
source, remoteChainTail, addUpdatesToForward,
settleFailUpdatesToForward,
)
// We will soon be saving the current remote commitment to revocation
// log bucket, which is `lc.channelState.RemoteCommitment`. After that,
// the `RemoteCommitment` will be replaced with a newer version found
// in `CommitDiff`. Thus we need to compute the output indexes here
// before the change since the indexes are meant for the current,
// revoked remote commitment.
ourOutputIndex, theirOutputIndex, err := findOutputIndexesFromRemote(
revocation, lc.channelState, lc.leafStore,
)
if err != nil {
return nil, nil, err
}
// Now that we have a new verification nonce from them, we can refresh
// our remote musig2 session which allows us to create another state.
if lc.channelState.ChanType.IsTaproot() {
localNonce, err := revMsg.LocalNonce.UnwrapOrErrV(errNoNonce)
if err != nil {
return nil, nil, err
}
session, err := lc.musigSessions.RemoteSession.Refresh(
&musig2.Nonces{
PubNonce: localNonce,
},
)
if err != nil {
return nil, nil, err
}
lc.musigSessions.RemoteSession = session
}
// At this point, the revocation has been accepted, and we've rotated
// the current revocation key+hash for the remote party. Therefore we
// sync now to ensure the revocation producer state is consistent with
// the current commitment height and also to advance the on-disk
// commitment chain.
err = lc.channelState.AdvanceCommitChainTail(
fwdPkg, localPeerUpdates,
ourOutputIndex, theirOutputIndex,
)
if err != nil {
return nil, nil, err
}
// Since they revoked the current lowest height in their commitment
// chain, we can advance their chain by a single commitment.
lc.commitChains.Remote.advanceTail()
// As we've just completed a new state transition, attempt to see if we
// can remove any entries from the update log which have been removed
// from the PoV of both commitment chains.
compactLogs(
lc.updateLogs.Local, lc.updateLogs.Remote, localChainTail,
remoteChainTail,
)
remoteHTLCs := lc.channelState.RemoteCommitment.Htlcs
return fwdPkg, remoteHTLCs, nil
}
// LoadFwdPkgs loads any pending log updates from disk and returns the payment
// descriptors to be processed by the link.
func (lc *LightningChannel) LoadFwdPkgs() ([]*channeldb.FwdPkg, error) {
return lc.channelState.LoadFwdPkgs()
}
// AckAddHtlcs sets a bit in the FwdFilter of a forwarding package belonging to
// this channel, that corresponds to the given AddRef. This method also succeeds
// if no forwarding package is found.
func (lc *LightningChannel) AckAddHtlcs(addRef channeldb.AddRef) error {
return lc.channelState.AckAddHtlcs(addRef)
}
// AckSettleFails sets a bit in the SettleFailFilter of a forwarding package
// belonging to this channel, that corresponds to the given SettleFailRef. This
// method also succeeds if no forwarding package is found.
func (lc *LightningChannel) AckSettleFails(
settleFailRefs ...channeldb.SettleFailRef) error {
return lc.channelState.AckSettleFails(settleFailRefs...)
}
// SetFwdFilter writes the forwarding decision for a given remote commitment
// height.
func (lc *LightningChannel) SetFwdFilter(height uint64,
fwdFilter *channeldb.PkgFilter) error {
return lc.channelState.SetFwdFilter(height, fwdFilter)
}
// RemoveFwdPkgs permanently deletes the forwarding package at the given heights.
func (lc *LightningChannel) RemoveFwdPkgs(heights ...uint64) error {
return lc.channelState.RemoveFwdPkgs(heights...)
}
// NextRevocationKey returns the commitment point for the _next_ commitment
// height. The pubkey returned by this function is required by the remote party
// along with their revocation base to extend our commitment chain with a
// new commitment.
func (lc *LightningChannel) NextRevocationKey() (*btcec.PublicKey, error) {
lc.RLock()
defer lc.RUnlock()
nextHeight := lc.currentHeight + 1
revocation, err := lc.channelState.RevocationProducer.AtIndex(nextHeight)
if err != nil {
return nil, err
}
return input.ComputeCommitmentPoint(revocation[:]), nil
}
// InitNextRevocation inserts the passed commitment point as the _next_
// revocation to be used when creating a new commitment state for the remote
// party. This function MUST be called before the channel can accept or propose
// any new states.
func (lc *LightningChannel) InitNextRevocation(revKey *btcec.PublicKey) error {
lc.Lock()
defer lc.Unlock()
return lc.channelState.InsertNextRevocation(revKey)
}
// AddHTLC is a wrapper of the `addHTLC` function which always enforces the
// FeeBuffer on the local balance if being the initiator of the channel. This
// method should be called when preparing to send an outgoing HTLC.
//
// The additional openKey argument corresponds to the incoming CircuitKey of the
// committed circuit for this HTLC. This value should never be nil.
//
// NOTE: It is okay for sourceRef to be nil when unit testing the wallet.
func (lc *LightningChannel) AddHTLC(htlc *lnwire.UpdateAddHTLC,
openKey *models.CircuitKey) (uint64, error) {
return lc.addHTLC(htlc, openKey, FeeBuffer)
}
// addHTLC adds an HTLC to the state machine's local update log. It provides
// the ability to enforce a buffer on the local balance when we are the
// initiator of the channel. This is useful when checking the edge cases of a
// channel state e.g. the BOLT 03 test vectors.
//
// The additional openKey argument corresponds to the incoming CircuitKey of the
// committed circuit for this HTLC. This value should never be nil.
//
// NOTE: It is okay for sourceRef to be nil when unit testing the wallet.
func (lc *LightningChannel) addHTLC(htlc *lnwire.UpdateAddHTLC,
openKey *models.CircuitKey, buffer BufferType) (uint64, error) {
lc.Lock()
defer lc.Unlock()
pd := lc.htlcAddDescriptor(htlc, openKey)
if err := lc.validateAddHtlc(pd, buffer); err != nil {
return 0, err
}
lc.updateLogs.Local.appendHtlc(pd)
return pd.HtlcIndex, nil
}
// GetDustSum takes in a boolean that determines which commitment to evaluate
// the dust sum on. The return value is the sum of dust on the desired
// commitment tx.
//
// NOTE: This over-estimates the dust exposure.
func (lc *LightningChannel) GetDustSum(whoseCommit lntypes.ChannelParty,
dryRunFee fn.Option[chainfee.SatPerKWeight]) lnwire.MilliSatoshi {
lc.RLock()
defer lc.RUnlock()
var dustSum lnwire.MilliSatoshi
dustLimit := lc.channelState.LocalChanCfg.DustLimit
commit := lc.channelState.LocalCommitment
if whoseCommit.IsRemote() {
// Calculate dust sum on the remote's commitment.
dustLimit = lc.channelState.RemoteChanCfg.DustLimit
commit = lc.channelState.RemoteCommitment
}
chanType := lc.channelState.ChanType
feeRate := chainfee.SatPerKWeight(commit.FeePerKw)
// Optionally use the dry-run fee-rate.
feeRate = dryRunFee.UnwrapOr(feeRate)
// Grab all of our HTLCs and evaluate against the dust limit.
for e := lc.updateLogs.Local.Front(); e != nil; e = e.Next() {
pd := e.Value
if pd.EntryType != Add {
continue
}
amt := pd.Amount.ToSatoshis()
// If the satoshi amount is under the dust limit, add the msat
// amount to the dust sum.
if HtlcIsDust(
chanType, false, whoseCommit, feeRate, amt, dustLimit,
) {
dustSum += pd.Amount
}
}
// Grab all of their HTLCs and evaluate against the dust limit.
for e := lc.updateLogs.Remote.Front(); e != nil; e = e.Next() {
pd := e.Value
if pd.EntryType != Add {
continue
}
amt := pd.Amount.ToSatoshis()
// If the satoshi amount is under the dust limit, add the msat
// amount to the dust sum.
if HtlcIsDust(
chanType, true, whoseCommit, feeRate,
amt, dustLimit,
) {
dustSum += pd.Amount
}
}
return dustSum
}
// MayAddOutgoingHtlc validates whether we can add an outgoing htlc to this
// channel. We don't have a circuit for this htlc, because we just want to test
// that we have slots for a potential htlc so we use a "mock" htlc to validate
// a potential commitment state with one more outgoing htlc. If a zero htlc
// amount is provided, we'll attempt to add the smallest possible htlc to the
// channel (either the minimum htlc, or 1 sat).
func (lc *LightningChannel) MayAddOutgoingHtlc(amt lnwire.MilliSatoshi) error {
lc.Lock()
defer lc.Unlock()
var mockHtlcAmt lnwire.MilliSatoshi
switch {
// If the caller specifically set an amount, we use it.
case amt != 0:
mockHtlcAmt = amt
// In absence of a specific amount, we want to use minimum htlc value
// for the channel. However certain implementations may set this value
// to zero, so we only use this value if it is non-zero.
case lc.channelState.LocalChanCfg.MinHTLC != 0:
mockHtlcAmt = lc.channelState.LocalChanCfg.MinHTLC
// As a last resort, we just add a non-zero amount.
default:
mockHtlcAmt++
}
// Create a "mock" outgoing htlc, using the smallest amount we can add
// to the commitment so that we validate commitment slots rather than
// available balance, since our actual htlc amount is unknown at this
// stage.
pd := lc.htlcAddDescriptor(
&lnwire.UpdateAddHTLC{
Amount: mockHtlcAmt,
},
&models.CircuitKey{},
)
// Enforce the FeeBuffer because we are evaluating whether we can add
// another htlc to the channel state.
if err := lc.validateAddHtlc(pd, FeeBuffer); err != nil {
lc.log.Debugf("May add outgoing htlc rejected: %v", err)
return err
}
return nil
}
// htlcAddDescriptor returns a payment descriptor for the htlc and open key
// provided to add to our local update log.
func (lc *LightningChannel) htlcAddDescriptor(htlc *lnwire.UpdateAddHTLC,
openKey *models.CircuitKey) *paymentDescriptor {
return &paymentDescriptor{
ChanID: htlc.ChanID,
EntryType: Add,
RHash: PaymentHash(htlc.PaymentHash),
Timeout: htlc.Expiry,
Amount: htlc.Amount,
LogIndex: lc.updateLogs.Local.logIndex,
HtlcIndex: lc.updateLogs.Local.htlcCounter,
OnionBlob: htlc.OnionBlob,
OpenCircuitKey: openKey,
BlindingPoint: htlc.BlindingPoint,
CustomRecords: htlc.CustomRecords.Copy(),
}
}
// validateAddHtlc validates the addition of an outgoing htlc to our local and
// remote commitments.
func (lc *LightningChannel) validateAddHtlc(pd *paymentDescriptor,
buffer BufferType) error {
// Make sure adding this HTLC won't violate any of the constraints we
// must keep on the commitment transactions.
remoteACKedIndex := lc.commitChains.Local.tail().messageIndices.Remote
// First we'll check whether this HTLC can be added to the remote
// commitment transaction without violation any of the constraints.
err := lc.validateCommitmentSanity(
remoteACKedIndex, lc.updateLogs.Local.logIndex, lntypes.Remote,
buffer, pd, nil,
)
if err != nil {
return err
}
// We must also check whether it can be added to our own commitment
// transaction, or the remote node will refuse to sign. This is not
// totally bullet proof, as the remote might be adding updates
// concurrently, but if we fail this check there is for sure not
// possible for us to add the HTLC.
err = lc.validateCommitmentSanity(
lc.updateLogs.Remote.logIndex, lc.updateLogs.Local.logIndex,
lntypes.Local, buffer, pd, nil,
)
if err != nil {
return err
}
return nil
}
// ReceiveHTLC adds an HTLC to the state machine's remote update log. This
// method should be called in response to receiving a new HTLC from the remote
// party.
func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.UpdateAddHTLC) (uint64,
error) {
lc.Lock()
defer lc.Unlock()
if htlc.ID != lc.updateLogs.Remote.htlcCounter {
return 0, fmt.Errorf("ID %d on HTLC add does not match "+
"expected next ID %d", htlc.ID,
lc.updateLogs.Remote.htlcCounter)
}
pd := &paymentDescriptor{
ChanID: htlc.ChanID,
EntryType: Add,
RHash: PaymentHash(htlc.PaymentHash),
Timeout: htlc.Expiry,
Amount: htlc.Amount,
LogIndex: lc.updateLogs.Remote.logIndex,
HtlcIndex: lc.updateLogs.Remote.htlcCounter,
OnionBlob: htlc.OnionBlob,
BlindingPoint: htlc.BlindingPoint,
CustomRecords: htlc.CustomRecords.Copy(),
}
localACKedIndex := lc.commitChains.Remote.tail().messageIndices.Local
// Clamp down on the number of HTLC's we can receive by checking the
// commitment sanity.
// We do not enforce the FeeBuffer here because one of the reasons it
// was introduced is to protect against asynchronous sending of htlcs so
// we use it here. The current lightning protocol does not allow to
// reject ADDs already sent by the peer.
err := lc.validateCommitmentSanity(
lc.updateLogs.Remote.logIndex, localACKedIndex, lntypes.Local,
NoBuffer, nil, pd,
)
if err != nil {
return 0, err
}
lc.updateLogs.Remote.appendHtlc(pd)
return pd.HtlcIndex, nil
}
// SettleHTLC attempts to settle an existing outstanding received HTLC. The
// remote log index of the HTLC settled is returned in order to facilitate
// creating the corresponding wire message. In the case the supplied preimage
// is invalid, an error is returned.
//
// The additional arguments correspond to:
//
// - sourceRef: specifies the location of the Add HTLC within a forwarding
// package that this HTLC is settling. Every Settle fails exactly one Add,
// so this should never be empty in practice.
//
// - destRef: specifies the location of the Settle HTLC within another
// channel's forwarding package. This value can be nil if the corresponding
// Add HTLC was never locked into an outgoing commitment txn, or this
// HTLC does not originate as a response from the peer on the outgoing
// link, e.g. on-chain resolutions.
//
// - closeKey: identifies the circuit that should be deleted after this Settle
// HTLC is included in a commitment txn. This value should only be nil if
// the HTLC was settled locally before committing a circuit to the circuit
// map.
//
// NOTE: It is okay for sourceRef, destRef, and closeKey to be nil when unit
// testing the wallet.
func (lc *LightningChannel) SettleHTLC(preimage [32]byte,
htlcIndex uint64, sourceRef *channeldb.AddRef,
destRef *channeldb.SettleFailRef, closeKey *models.CircuitKey) error {
lc.Lock()
defer lc.Unlock()
htlc := lc.updateLogs.Remote.lookupHtlc(htlcIndex)
if htlc == nil {
return ErrUnknownHtlcIndex{lc.ShortChanID(), htlcIndex}
}
// Now that we know the HTLC exists, before checking to see if the
// preimage matches, we'll ensure that we haven't already attempted to
// modify the HTLC.
if lc.updateLogs.Remote.htlcHasModification(htlcIndex) {
return ErrHtlcIndexAlreadySettled(htlcIndex)
}
if htlc.RHash != sha256.Sum256(preimage[:]) {
return ErrInvalidSettlePreimage{preimage[:], htlc.RHash[:]}
}
pd := &paymentDescriptor{
ChanID: lc.ChannelID(),
Amount: htlc.Amount,
RPreimage: preimage,
LogIndex: lc.updateLogs.Local.logIndex,
ParentIndex: htlcIndex,
EntryType: Settle,
SourceRef: sourceRef,
DestRef: destRef,
ClosedCircuitKey: closeKey,
}
lc.updateLogs.Local.appendUpdate(pd)
// With the settle added to our local log, we'll now mark the HTLC as
// modified to prevent ourselves from accidentally attempting a
// duplicate settle.
lc.updateLogs.Remote.markHtlcModified(htlcIndex)
return nil
}
// ReceiveHTLCSettle attempts to settle an existing outgoing HTLC indexed by an
// index into the local log. If the specified index doesn't exist within the
// log, and error is returned. Similarly if the preimage is invalid w.r.t to
// the referenced of then a distinct error is returned.
func (lc *LightningChannel) ReceiveHTLCSettle(preimage [32]byte, htlcIndex uint64) error {
lc.Lock()
defer lc.Unlock()
htlc := lc.updateLogs.Local.lookupHtlc(htlcIndex)
if htlc == nil {
return ErrUnknownHtlcIndex{lc.ShortChanID(), htlcIndex}
}
// Now that we know the HTLC exists, before checking to see if the
// preimage matches, we'll ensure that they haven't already attempted
// to modify the HTLC.
if lc.updateLogs.Local.htlcHasModification(htlcIndex) {
return ErrHtlcIndexAlreadySettled(htlcIndex)
}
if htlc.RHash != sha256.Sum256(preimage[:]) {
return ErrInvalidSettlePreimage{preimage[:], htlc.RHash[:]}
}
pd := &paymentDescriptor{
ChanID: lc.ChannelID(),
Amount: htlc.Amount,
RPreimage: preimage,
ParentIndex: htlc.HtlcIndex,
RHash: htlc.RHash,
LogIndex: lc.updateLogs.Remote.logIndex,
EntryType: Settle,
}
lc.updateLogs.Remote.appendUpdate(pd)
// With the settle added to the remote log, we'll now mark the HTLC as
// modified to prevent the remote party from accidentally attempting a
// duplicate settle.
lc.updateLogs.Local.markHtlcModified(htlcIndex)
return nil
}
// FailHTLC attempts to fail a targeted HTLC by its payment hash, inserting an
// entry which will remove the target log entry within the next commitment
// update. This method is intended to be called in order to cancel in
// _incoming_ HTLC.
//
// The additional arguments correspond to:
//
// - sourceRef: specifies the location of the Add HTLC within a forwarding
// package that this HTLC is failing. Every Fail fails exactly one Add, so
// this should never be empty in practice.
//
// - destRef: specifies the location of the Fail HTLC within another channel's
// forwarding package. This value can be nil if the corresponding Add HTLC
// was never locked into an outgoing commitment txn, or this HTLC does not
// originate as a response from the peer on the outgoing link, e.g.
// on-chain resolutions.
//
// - closeKey: identifies the circuit that should be deleted after this Fail
// HTLC is included in a commitment txn. This value should only be nil if
// the HTLC was failed locally before committing a circuit to the circuit
// map.
//
// NOTE: It is okay for sourceRef, destRef, and closeKey to be nil when unit
// testing the wallet.
func (lc *LightningChannel) FailHTLC(htlcIndex uint64, reason []byte,
sourceRef *channeldb.AddRef, destRef *channeldb.SettleFailRef,
closeKey *models.CircuitKey) error {
lc.Lock()
defer lc.Unlock()
htlc := lc.updateLogs.Remote.lookupHtlc(htlcIndex)
if htlc == nil {
return ErrUnknownHtlcIndex{lc.ShortChanID(), htlcIndex}
}
// Now that we know the HTLC exists, we'll ensure that we haven't
// already attempted to fail the HTLC.
if lc.updateLogs.Remote.htlcHasModification(htlcIndex) {
return ErrHtlcIndexAlreadyFailed(htlcIndex)
}
pd := &paymentDescriptor{
ChanID: lc.ChannelID(),
Amount: htlc.Amount,
RHash: htlc.RHash,
ParentIndex: htlcIndex,
LogIndex: lc.updateLogs.Local.logIndex,
EntryType: Fail,
FailReason: reason,
SourceRef: sourceRef,
DestRef: destRef,
ClosedCircuitKey: closeKey,
}
lc.updateLogs.Local.appendUpdate(pd)
// With the fail added to the remote log, we'll now mark the HTLC as
// modified to prevent ourselves from accidentally attempting a
// duplicate fail.
lc.updateLogs.Remote.markHtlcModified(htlcIndex)
return nil
}
// MalformedFailHTLC attempts to fail a targeted HTLC by its payment hash,
// inserting an entry which will remove the target log entry within the next
// commitment update. This method is intended to be called in order to cancel
// in _incoming_ HTLC.
//
// The additional sourceRef specifies the location of the Add HTLC within a
// forwarding package that this HTLC is failing. This value should never be
// empty.
//
// NOTE: It is okay for sourceRef to be nil when unit testing the wallet.
func (lc *LightningChannel) MalformedFailHTLC(htlcIndex uint64,
failCode lnwire.FailCode, shaOnionBlob [sha256.Size]byte,
sourceRef *channeldb.AddRef) error {
lc.Lock()
defer lc.Unlock()
htlc := lc.updateLogs.Remote.lookupHtlc(htlcIndex)
if htlc == nil {
return ErrUnknownHtlcIndex{lc.ShortChanID(), htlcIndex}
}
// Now that we know the HTLC exists, we'll ensure that we haven't
// already attempted to fail the HTLC.
if lc.updateLogs.Remote.htlcHasModification(htlcIndex) {
return ErrHtlcIndexAlreadyFailed(htlcIndex)
}
pd := &paymentDescriptor{
ChanID: lc.ChannelID(),
Amount: htlc.Amount,
RHash: htlc.RHash,
ParentIndex: htlcIndex,
LogIndex: lc.updateLogs.Local.logIndex,
EntryType: MalformedFail,
FailCode: failCode,
ShaOnionBlob: shaOnionBlob,
SourceRef: sourceRef,
}
lc.updateLogs.Local.appendUpdate(pd)
// With the fail added to the remote log, we'll now mark the HTLC as
// modified to prevent ourselves from accidentally attempting a
// duplicate fail.
lc.updateLogs.Remote.markHtlcModified(htlcIndex)
return nil
}
// ReceiveFailHTLC attempts to cancel a targeted HTLC by its log index,
// inserting an entry which will remove the target log entry within the next
// commitment update. This method should be called in response to the upstream
// party cancelling an outgoing HTLC.
func (lc *LightningChannel) ReceiveFailHTLC(htlcIndex uint64, reason []byte,
) error {
lc.Lock()
defer lc.Unlock()
htlc := lc.updateLogs.Local.lookupHtlc(htlcIndex)
if htlc == nil {
return ErrUnknownHtlcIndex{lc.ShortChanID(), htlcIndex}
}
// Now that we know the HTLC exists, we'll ensure that they haven't
// already attempted to fail the HTLC.
if lc.updateLogs.Local.htlcHasModification(htlcIndex) {
return ErrHtlcIndexAlreadyFailed(htlcIndex)
}
pd := &paymentDescriptor{
ChanID: lc.ChannelID(),
Amount: htlc.Amount,
RHash: htlc.RHash,
ParentIndex: htlc.HtlcIndex,
LogIndex: lc.updateLogs.Remote.logIndex,
EntryType: Fail,
FailReason: reason,
}
lc.updateLogs.Remote.appendUpdate(pd)
// With the fail added to the remote log, we'll now mark the HTLC as
// modified to prevent ourselves from accidentally attempting a
// duplicate fail.
lc.updateLogs.Local.markHtlcModified(htlcIndex)
return nil
}
// ChannelPoint returns the outpoint of the original funding transaction which
// created this active channel. This outpoint is used throughout various
// subsystems to uniquely identify an open channel.
func (lc *LightningChannel) ChannelPoint() wire.OutPoint {
return lc.channelState.FundingOutpoint
}
// ChannelID returns the ChannelID of this LightningChannel. This is the same
// ChannelID that is used in update messages for this channel.
func (lc *LightningChannel) ChannelID() lnwire.ChannelID {
return lnwire.NewChanIDFromOutPoint(lc.ChannelPoint())
}
// ShortChanID returns the short channel ID for the channel. The short channel
// ID encodes the exact location in the main chain that the original
// funding output can be found.
func (lc *LightningChannel) ShortChanID() lnwire.ShortChannelID {
return lc.channelState.ShortChanID()
}
// LocalUpfrontShutdownScript returns the local upfront shutdown script for the
// channel. If it was not set, an empty byte array is returned.
func (lc *LightningChannel) LocalUpfrontShutdownScript() lnwire.DeliveryAddress {
return lc.channelState.LocalShutdownScript
}
// RemoteUpfrontShutdownScript returns the remote upfront shutdown script for the
// channel. If it was not set, an empty byte array is returned.
func (lc *LightningChannel) RemoteUpfrontShutdownScript() lnwire.DeliveryAddress {
return lc.channelState.RemoteShutdownScript
}
// AbsoluteThawHeight determines a frozen channel's absolute thaw height. If
// the channel is not frozen, then 0 is returned.
//
// An error is returned if the channel is pending, or is an unconfirmed zero
// conf channel.
func (lc *LightningChannel) AbsoluteThawHeight() (uint32, error) {
return lc.channelState.AbsoluteThawHeight()
}
// SignedCommitTxInputs contains data needed to create a signed commit
// transaction using a signer. See GetSignedCommitTx.
type SignedCommitTxInputs struct {
// CommitTx is the latest version of the commitment state, broadcast
// able by us.
CommitTx *wire.MsgTx
// CommitSig is one half of the signature required to fully complete
// the script for the commitment transaction above. This is the
// signature signed by the remote party for our version of the
// commitment transactions.
CommitSig []byte
// OurKey is our key to be used within the 2-of-2 output script
// for the owner of this channel.
OurKey keychain.KeyDescriptor
// TheirKey is their key to be used within the 2-of-2 output script
// for the owner of this channel.
TheirKey keychain.KeyDescriptor
// SignDesc is the primary sign descriptor that is capable of signing
// the commitment transaction that spends the multi-sig output.
SignDesc *input.SignDescriptor
// Taproot holds fields needed in case of a taproot channel.
// Iff the channel is of taproot type, this field is filled.
Taproot fn.Option[TaprootSignedCommitTxInputs]
}
// TaprootSignedCommitTxInputs contains additional data needed to create a
// signed commit transaction using a signer, used in case of a taproot channel.
// See GetSignedCommitTx.
type TaprootSignedCommitTxInputs struct {
// CommitHeight is the update number that this channel state represents.
// It is the total number of commitment updates up to this point. This
// can be viewed as sort of a "commitment height" as this number is
// monotonically increasing. This number is used to make a signature
// for a taproot channel, since it is used by shachain nonce producer
// (TaprootNonceProducer).
CommitHeight uint64
// TaprootNonceProducer is used to generate a shachain tree for the
// purpose of generating verification nonces for taproot channels.
TaprootNonceProducer shachain.Producer
// TapscriptRoot is the root of the tapscript tree that will be used to
// create the funding output. This is an optional field that should
// only be set for taproot channels.
TapscriptRoot fn.Option[chainhash.Hash]
}
// GetSignedCommitTx creates the witness stack of a channel commitment
// transaction. It can handle all commitment types (taproot, legacy). It is
// exported to give outside tooling the possibility to recreate the witness.
// A key use case is generating the witness data for a commitment transaction
// from a Static Channel Backup (SCB).
func GetSignedCommitTx(inputs SignedCommitTxInputs,
signer input.Signer) (*wire.MsgTx, error) {
commitTx := inputs.CommitTx.Copy()
var witness wire.TxWitness
switch {
// If this is a taproot channel, then we'll need to re-derive the nonce
// we need to generate a new signature
case inputs.Taproot.IsSome():
// Extract Taproot from fn.Option. It is safe to call
// UnsafeFromSome because we just checked that it is some.
taproot := inputs.Taproot.UnsafeFromSome()
// First, we'll need to re-derive the local nonce we sent to
// the remote party to create this musig session. We pass in
// the same height here as we're generating the nonce needed
// for the _current_ state.
localNonce, err := channeldb.NewMusigVerificationNonce(
inputs.OurKey.PubKey, taproot.CommitHeight,
taproot.TaprootNonceProducer,
)
if err != nil {
return nil, fmt.Errorf("unable to re-derive "+
"verification nonce: %w", err)
}
tapscriptTweak := fn.MapOption(TapscriptRootToTweak)(
taproot.TapscriptRoot,
)
// Now that we have the local nonce, we'll re-create the musig
// session we had for this height.
musigSession := NewPartialMusigSession(
*localNonce, inputs.OurKey, inputs.TheirKey, signer,
inputs.SignDesc.Output, LocalMusigCommit,
tapscriptTweak,
)
var remoteSig lnwire.PartialSigWithNonce
err = remoteSig.Decode(
bytes.NewReader(inputs.CommitSig),
)
if err != nil {
return nil, fmt.Errorf("unable to decode remote "+
"partial sig: %w", err)
}
// Next, we'll manually finalize the session with the signing
// nonce we got from the remote party which is embedded in the
// signature we have.
err = musigSession.FinalizeSession(musig2.Nonces{
PubNonce: remoteSig.Nonce,
})
if err != nil {
return nil, fmt.Errorf("unable to finalize musig "+
"session: %w", err)
}
// Now that the session has been finalized, we can generate our
// half of the signature for the state. We don't capture the
// sig as it's stored within the session.
if _, err := musigSession.SignCommit(commitTx); err != nil {
return nil, fmt.Errorf("unable to sign musig2 "+
"commitment: %w", err)
}
// The final step is now to combine this signature we generated
// above, with the remote party's signature. We only need to
// pass the remote sig, as the local sig was already cached in
// the session.
var partialSig MusigPartialSig
partialSig.FromWireSig(&remoteSig)
finalSig, err := musigSession.CombineSigs(partialSig.sig)
if err != nil {
return nil, fmt.Errorf("unable to combine musig "+
"partial sigs: %w", err)
}
// The witness is the single keyspend schnorr sig.
witness = wire.TxWitness{
finalSig.Serialize(),
}
// Otherwise, the final witness we generate will be a normal p2wsh
// multi-sig spend.
default:
theirSig, err := ecdsa.ParseDERSignature(inputs.CommitSig)
if err != nil {
return nil, err
}
// With this, we then generate the full witness so the caller
// can broadcast a fully signed transaction.
inputs.SignDesc.SigHashes = input.NewTxSigHashesV0Only(commitTx)
ourSig, err := signer.SignOutputRaw(commitTx, inputs.SignDesc)
if err != nil {
return nil, err
}
// With the final signature generated, create the witness stack
// required to spend from the multi-sig output.
witness = input.SpendMultiSig(
inputs.SignDesc.WitnessScript,
inputs.OurKey.PubKey.SerializeCompressed(), ourSig,
inputs.TheirKey.PubKey.SerializeCompressed(), theirSig,
)
}
commitTx.TxIn[0].Witness = witness
return commitTx, nil
}
// getSignedCommitTx method takes the latest commitment transaction and
// populates it with witness data.
func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) {
// Fetch the current commitment transaction, along with their signature
// for the transaction.
localCommit := lc.channelState.LocalCommitment
inputs := SignedCommitTxInputs{
CommitTx: localCommit.CommitTx,
CommitSig: localCommit.CommitSig,
OurKey: lc.channelState.LocalChanCfg.MultiSigKey,
TheirKey: lc.channelState.RemoteChanCfg.MultiSigKey,
SignDesc: lc.signDesc,
}
if lc.channelState.ChanType.IsTaproot() {
inputs.Taproot = fn.Some(TaprootSignedCommitTxInputs{
CommitHeight: lc.currentHeight,
TaprootNonceProducer: lc.taprootNonceProducer,
TapscriptRoot: lc.channelState.TapscriptRoot,
})
}
return GetSignedCommitTx(inputs, lc.Signer)
}
// CommitOutputResolution carries the necessary information required to allow
// us to sweep our commitment output in the case that either party goes to
// chain.
type CommitOutputResolution struct {
// SelfOutPoint is the full outpoint that points to out pay-to-self
// output within the closing commitment transaction.
SelfOutPoint wire.OutPoint
// SelfOutputSignDesc is a fully populated sign descriptor capable of
// generating a valid signature to sweep the output paying to us.
SelfOutputSignDesc input.SignDescriptor
// MaturityDelay is the relative time-lock, in blocks for all outputs
// that pay to the local party within the broadcast commitment
// transaction.
MaturityDelay uint32
// ResolutionBlob is a blob used for aux channels that permits a
// spender of the output to properly resolve it in the case of a force
// close.
ResolutionBlob fn.Option[tlv.Blob]
}
// UnilateralCloseSummary describes the details of a detected unilateral
// channel closure. This includes the information about with which
// transactions, and block the channel was unilaterally closed, as well as
// summarization details concerning the _state_ of the channel at the point of
// channel closure. Additionally, if we had a commitment output above dust on
// the remote party's commitment transaction, the necessary a SignDescriptor
// with the material necessary to seep the output are returned. Finally, if we
// had any outgoing HTLC's within the commitment transaction, then an
// OutgoingHtlcResolution for each output will included.
type UnilateralCloseSummary struct {
// SpendDetail is a struct that describes how and when the funding
// output was spent.
*chainntnfs.SpendDetail
// ChannelCloseSummary is a struct describing the final state of the
// channel and in which state is was closed.
channeldb.ChannelCloseSummary
// CommitResolution contains all the data required to sweep the output
// to ourselves. If this is our commitment transaction, then we'll need
// to wait a time delay before we can sweep the output.
//
// NOTE: If our commitment delivery output is below the dust limit,
// then this will be nil.
CommitResolution *CommitOutputResolution
// HtlcResolutions contains a fully populated HtlcResolutions struct
// which contains all the data required to sweep any outgoing HTLC's,
// and also any incoming HTLC's that we know the pre-image to.
HtlcResolutions *HtlcResolutions
// RemoteCommit is the exact commitment state that the remote party
// broadcast.
RemoteCommit channeldb.ChannelCommitment
// AnchorResolution contains the data required to sweep our anchor
// output. If the channel type doesn't include anchors, the value of
// this field will be nil.
AnchorResolution *AnchorResolution
}
// NewUnilateralCloseSummary creates a new summary that provides the caller
// with all the information required to claim all funds on chain in the event
// that the remote party broadcasts their commitment. The commitPoint argument
// should be set to the per_commitment_point corresponding to the spending
// commitment.
//
// NOTE: The remoteCommit argument should be set to the stored commitment for
// this particular state. If we don't have the commitment stored (should only
// happen in case we have lost state) it should be set to an empty struct, in
// which case we will attempt to sweep the non-HTLC output using the passed
// commitPoint.
func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, //nolint:funlen
signer input.Signer, commitSpend *chainntnfs.SpendDetail,
remoteCommit channeldb.ChannelCommitment, commitPoint *btcec.PublicKey,
leafStore fn.Option[AuxLeafStore],
auxResolver fn.Option[AuxContractResolver]) (*UnilateralCloseSummary,
error) {
// First, we'll generate the commitment point and the revocation point
// so we can re-construct the HTLC state and also our payment key.
commitType := lntypes.Remote
keyRing := DeriveCommitmentKeys(
commitPoint, commitType, chanState.ChanType,
&chanState.LocalChanCfg, &chanState.RemoteChanCfg,
)
auxResult, err := fn.MapOptionZ(
leafStore, func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return s.FetchLeavesFromCommit(
NewAuxChanState(chanState), remoteCommit,
*keyRing, lntypes.Remote,
)
},
).Unpack()
if err != nil {
return nil, fmt.Errorf("unable to fetch aux leaves: %w", err)
}
// Next, we'll obtain HTLC resolutions for all the outgoing HTLC's we
// had on their commitment transaction.
var (
leaseExpiry uint32
selfPoint *wire.OutPoint
localBalance int64
isRemoteInitiator = !chanState.IsInitiator
commitTxBroadcast = commitSpend.SpendingTx
)
if chanState.ChanType.HasLeaseExpiration() {
leaseExpiry = chanState.ThawHeight
}
htlcResolutions, err := extractHtlcResolutions(
chainfee.SatPerKWeight(remoteCommit.FeePerKw), commitType,
signer, remoteCommit.Htlcs, keyRing, &chanState.LocalChanCfg,
&chanState.RemoteChanCfg, commitSpend.SpendingTx,
chanState.ChanType, isRemoteInitiator, leaseExpiry, chanState,
auxResult.AuxLeaves, auxResolver,
)
if err != nil {
return nil, fmt.Errorf("unable to create htlc resolutions: %w",
err)
}
// Before we can generate the proper sign descriptor, we'll need to
// locate the output index of our non-delayed output on the commitment
// transaction.
remoteAuxLeaf := fn.FlatMapOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
return l.RemoteAuxLeaf
},
)(auxResult.AuxLeaves)
selfScript, maturityDelay, err := CommitScriptToRemote(
chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey,
leaseExpiry, remoteAuxLeaf,
)
if err != nil {
return nil, fmt.Errorf("unable to create self commit "+
"script: %w", err)
}
for outputIndex, txOut := range commitTxBroadcast.TxOut {
if bytes.Equal(txOut.PkScript, selfScript.PkScript()) {
selfPoint = &wire.OutPoint{
Hash: *commitSpend.SpenderTxHash,
Index: uint32(outputIndex),
}
localBalance = txOut.Value
break
}
}
// With the HTLC's taken care of, we'll generate the sign descriptor
// necessary to sweep our commitment output, but only if we had a
// non-trimmed balance.
var commitResolution *CommitOutputResolution
if selfPoint != nil {
localPayBase := chanState.LocalChanCfg.PaymentBasePoint
// As the remote party has force closed, we just need the
// success witness script.
witnessScript, err := selfScript.WitnessScriptForPath(
input.ScriptPathSuccess,
)
if err != nil {
return nil, err
}
commitResolution = &CommitOutputResolution{
SelfOutPoint: *selfPoint,
SelfOutputSignDesc: input.SignDescriptor{
KeyDesc: localPayBase,
SingleTweak: keyRing.LocalCommitKeyTweak,
WitnessScript: witnessScript,
Output: &wire.TxOut{
Value: localBalance,
PkScript: selfScript.PkScript(),
},
HashType: sweepSigHash(chanState.ChanType),
},
MaturityDelay: maturityDelay,
}
// For taproot channels, we'll need to set some additional
// fields to ensure the output can be swept.
//
//nolint:ll
if scriptTree, ok := selfScript.(input.TapscriptDescriptor); ok {
commitResolution.SelfOutputSignDesc.SignMethod =
input.TaprootScriptSpendSignMethod
ctrlBlock, err := scriptTree.CtrlBlockForPath(
input.ScriptPathSuccess,
)
if err != nil {
return nil, err
}
//nolint:ll
commitResolution.SelfOutputSignDesc.ControlBlock, err = ctrlBlock.ToBytes()
if err != nil {
return nil, err
}
}
// At this point, we'll check to see if we need any extra
// resolution data for this output.
resolveReq := ResolutionReq{
ChanPoint: chanState.FundingOutpoint,
ChanType: chanState.ChanType,
ShortChanID: chanState.ShortChanID(),
Initiator: chanState.IsInitiator,
CommitBlob: chanState.RemoteCommitment.CustomBlob,
FundingBlob: chanState.CustomBlob,
Type: input.TaprootRemoteCommitSpend,
CloseType: RemoteForceClose,
CommitTx: commitTxBroadcast,
ContractPoint: *selfPoint,
SignDesc: commitResolution.SelfOutputSignDesc,
KeyRing: keyRing,
CsvDelay: maturityDelay,
CommitFee: chanState.RemoteCommitment.CommitFee,
}
resolveBlob := fn.MapOptionZ(
auxResolver,
func(a AuxContractResolver) fn.Result[tlv.Blob] {
return a.ResolveContract(resolveReq)
},
)
if err := resolveBlob.Err(); err != nil {
return nil, fmt.Errorf("unable to aux resolve: %w", err)
}
commitResolution.ResolutionBlob = resolveBlob.OkToSome()
}
closeSummary := channeldb.ChannelCloseSummary{
ChanPoint: chanState.FundingOutpoint,
ChainHash: chanState.ChainHash,
ClosingTXID: *commitSpend.SpenderTxHash,
CloseHeight: uint32(commitSpend.SpendingHeight),
RemotePub: chanState.IdentityPub,
Capacity: chanState.Capacity,
SettledBalance: btcutil.Amount(localBalance),
CloseType: channeldb.RemoteForceClose,
IsPending: true,
RemoteCurrentRevocation: chanState.RemoteCurrentRevocation,
RemoteNextRevocation: chanState.RemoteNextRevocation,
ShortChanID: chanState.ShortChanID(),
LocalChanConfig: chanState.LocalChanCfg,
}
// Attempt to add a channel sync message to the close summary.
chanSync, err := chanState.ChanSyncMsg()
if err != nil {
walletLog.Errorf("ChannelPoint(%v): unable to create channel sync "+
"message: %v", chanState.FundingOutpoint, err)
} else {
closeSummary.LastChanSyncMsg = chanSync
}
anchorResolution, err := NewAnchorResolution(
chanState, commitTxBroadcast, keyRing, lntypes.Remote,
)
if err != nil {
return nil, err
}
return &UnilateralCloseSummary{
SpendDetail: commitSpend,
ChannelCloseSummary: closeSummary,
CommitResolution: commitResolution,
HtlcResolutions: htlcResolutions,
RemoteCommit: remoteCommit,
AnchorResolution: anchorResolution,
}, nil
}
// IncomingHtlcResolution houses the information required to sweep any incoming
// HTLC's that we know the preimage to. We'll need to sweep an HTLC manually
// using this struct if we need to go on-chain for any reason, or if we detect
// that the remote party broadcasts their commitment transaction.
type IncomingHtlcResolution struct {
// Preimage is the preimage that will be used to satisfy the contract of
// the HTLC.
//
// NOTE: This field will only be populated in the incoming contest
// resolver.
Preimage [32]byte
// SignedSuccessTx is the fully signed HTLC success transaction. This
// transaction (if non-nil) can be broadcast immediately. After a csv
// delay (included below), then the output created by this transactions
// can be swept on-chain.
//
// NOTE: If this field is nil, then this indicates that we don't need
// to go to the second level to claim this HTLC. Instead, it can be
// claimed directly from the outpoint listed below.
SignedSuccessTx *wire.MsgTx
// SignDetails is non-nil if SignedSuccessTx is non-nil, and the
// channel is of the anchor type. As the above HTLC transaction will be
// signed by the channel peer using SINGLE|ANYONECANPAY for such
// channels, we can use the sign details to add the input-output pair
// of the HTLC transaction to another transaction, thereby aggregating
// multiple HTLC transactions together, and adding fees as needed.
SignDetails *input.SignDetails
// CsvDelay is the relative time lock (expressed in blocks) that must
// pass after the SignedSuccessTx is confirmed in the chain before the
// output can be swept.
//
// NOTE: If SignedTimeoutTx is nil, then this field denotes the CSV
// delay needed to spend from the commitment transaction.
CsvDelay uint32
// ClaimOutpoint is the final outpoint that needs to be spent in order
// to fully sweep the HTLC. The SignDescriptor below should be used to
// spend this outpoint. In the case of a second-level HTLC (non-nil
// SignedTimeoutTx), then we'll be spending a new transaction.
// Otherwise, it'll be an output in the commitment transaction.
ClaimOutpoint wire.OutPoint
// SweepSignDesc is a sign descriptor that has been populated with the
// necessary items required to spend the sole output of the above
// transaction.
SweepSignDesc input.SignDescriptor
// ResolutionBlob is a blob used for aux channels that permits a
// spender of the output to properly resolve it in the case of a force
// close.
ResolutionBlob fn.Option[tlv.Blob]
}
// OutgoingHtlcResolution houses the information necessary to sweep any
// outgoing HTLC's after their contract has expired. This struct will be needed
// in one of two cases: the local party force closes the commitment transaction
// or the remote party unilaterally closes with their version of the commitment
// transaction.
type OutgoingHtlcResolution struct {
// Expiry the absolute timeout of the HTLC. This value is expressed in
// block height, meaning after this height the HLTC can be swept.
Expiry uint32
// SignedTimeoutTx is the fully signed HTLC timeout transaction. This
// must be broadcast immediately after timeout has passed. Once this
// has been confirmed, the HTLC output will transition into the
// delay+claim state.
//
// NOTE: If this field is nil, then this indicates that we don't need
// to go to the second level to claim this HTLC. Instead, it can be
// claimed directly from the outpoint listed below.
SignedTimeoutTx *wire.MsgTx
// SignDetails is non-nil if SignedTimeoutTx is non-nil, and the
// channel is of the anchor type. As the above HTLC transaction will be
// signed by the channel peer using SINGLE|ANYONECANPAY for such
// channels, we can use the sign details to add the input-output pair
// of the HTLC transaction to another transaction, thereby aggregating
// multiple HTLC transactions together, and adding fees as needed.
SignDetails *input.SignDetails
// CsvDelay is the relative time lock (expressed in blocks) that must
// pass after the SignedTimeoutTx is confirmed in the chain before the
// output can be swept.
//
// NOTE: If SignedTimeoutTx is nil, then this field denotes the CSV
// delay needed to spend from the commitment transaction.
CsvDelay uint32
// ClaimOutpoint is the final outpoint that needs to be spent in order
// to fully sweep the HTLC. The SignDescriptor below should be used to
// spend this outpoint. In the case of a second-level HTLC (non-nil
// SignedTimeoutTx), then we'll be spending a new transaction.
// Otherwise, it'll be an output in the commitment transaction.
ClaimOutpoint wire.OutPoint
// SweepSignDesc is a sign descriptor that has been populated with the
// necessary items required to spend the sole output of the above
// transaction.
SweepSignDesc input.SignDescriptor
// ResolutionBlob is a blob used for aux channels that permits a
// spender of the output to properly resolve it in the case of a force
// close.
ResolutionBlob fn.Option[tlv.Blob]
}
// HtlcResolutions contains the items necessary to sweep HTLC's on chain
// directly from a commitment transaction. We'll use this in case either party
// goes broadcasts a commitment transaction with live HTLC's.
type HtlcResolutions struct {
// IncomingHTLCs contains a set of structs that can be used to sweep
// all the incoming HTL'C that we know the preimage to.
IncomingHTLCs []IncomingHtlcResolution
// OutgoingHTLCs contains a set of structs that contains all the info
// needed to sweep an outgoing HTLC we've sent to the remote party
// after an absolute delay has expired.
OutgoingHTLCs []OutgoingHtlcResolution
}
// newOutgoingHtlcResolution generates a new HTLC resolution capable of
// allowing the caller to sweep an outgoing HTLC present on either their, or
// the remote party's commitment transaction.
func newOutgoingHtlcResolution(signer input.Signer,
localChanCfg *channeldb.ChannelConfig, commitTx *wire.MsgTx,
htlc *channeldb.HTLC, keyRing *CommitmentKeyRing,
feePerKw chainfee.SatPerKWeight, csvDelay, leaseExpiry uint32,
whoseCommit lntypes.ChannelParty, isCommitFromInitiator bool,
chanType channeldb.ChannelType, chanState *channeldb.OpenChannel,
auxLeaves fn.Option[CommitAuxLeaves],
auxResolver fn.Option[AuxContractResolver],
) (*OutgoingHtlcResolution, error) {
op := wire.OutPoint{
Hash: commitTx.TxHash(),
Index: uint32(htlc.OutputIndex),
}
// First, we'll re-generate the script used to send the HTLC to the
// remote party within their commitment transaction.
auxLeaf := fn.FlatMapOption(func(l CommitAuxLeaves) input.AuxTapLeaf {
return l.OutgoingHtlcLeaves[htlc.HtlcIndex].AuxTapLeaf
})(auxLeaves)
htlcScriptInfo, err := genHtlcScript(
chanType, false, whoseCommit, htlc.RefundTimeout, htlc.RHash,
keyRing, auxLeaf,
)
if err != nil {
return nil, err
}
htlcPkScript := htlcScriptInfo.PkScript()
// As this is an outgoing HTLC, we just care about the timeout path
// here.
scriptPath := input.ScriptPathTimeout
htlcWitnessScript, err := htlcScriptInfo.WitnessScriptForPath(
scriptPath,
)
if err != nil {
return nil, err
}
htlcCsvDelay := HtlcSecondLevelInputSequence(chanType)
// If we're spending this HTLC output from the remote node's
// commitment, then we won't need to go to the second level as our
// outputs don't have a CSV delay.
if whoseCommit.IsRemote() {
// With the script generated, we can completely populated the
// SignDescriptor needed to sweep the output.
prevFetcher := txscript.NewCannedPrevOutputFetcher(
htlcPkScript, int64(htlc.Amt.ToSatoshis()),
)
signDesc := input.SignDescriptor{
KeyDesc: localChanCfg.HtlcBasePoint,
SingleTweak: keyRing.LocalHtlcKeyTweak,
WitnessScript: htlcWitnessScript,
Output: &wire.TxOut{
PkScript: htlcPkScript,
Value: int64(htlc.Amt.ToSatoshis()),
},
HashType: sweepSigHash(chanType),
PrevOutputFetcher: prevFetcher,
}
scriptTree, ok := htlcScriptInfo.(input.TapscriptDescriptor)
if ok {
signDesc.SignMethod = input.TaprootScriptSpendSignMethod
ctrlBlock, err := scriptTree.CtrlBlockForPath(
scriptPath,
)
if err != nil {
return nil, err
}
signDesc.ControlBlock, err = ctrlBlock.ToBytes()
if err != nil {
return nil, err
}
}
resReq := ResolutionReq{
ChanPoint: chanState.FundingOutpoint,
ChanType: chanType,
ShortChanID: chanState.ShortChanID(),
Initiator: chanState.IsInitiator,
CommitBlob: chanState.RemoteCommitment.CustomBlob,
FundingBlob: chanState.CustomBlob,
Type: input.TaprootHtlcOfferedRemoteTimeout,
CloseType: RemoteForceClose,
CommitTx: commitTx,
ContractPoint: op,
SignDesc: signDesc,
KeyRing: keyRing,
CsvDelay: htlcCsvDelay,
CltvDelay: fn.Some(htlc.RefundTimeout),
CommitFee: chanState.RemoteCommitment.CommitFee,
HtlcID: fn.Some(htlc.HtlcIndex),
PayHash: fn.Some(htlc.RHash),
}
resolveRes := fn.MapOptionZ(
auxResolver,
func(a AuxContractResolver) fn.Result[tlv.Blob] {
return a.ResolveContract(resReq)
},
)
if err := resolveRes.Err(); err != nil {
return nil, fmt.Errorf("unable to aux resolve: %w", err)
}
resolutionBlob := resolveRes.OkToSome()
return &OutgoingHtlcResolution{
Expiry: htlc.RefundTimeout,
ClaimOutpoint: op,
SweepSignDesc: signDesc,
CsvDelay: csvDelay,
ResolutionBlob: resolutionBlob,
}, nil
}
// Otherwise, we'll need to craft a second level HTLC transaction, as
// well as a sign desc to sweep after the CSV delay.
// In order to properly reconstruct the HTLC transaction, we'll need to
// re-calculate the fee required at this state, so we can add the
// correct output value amount to the transaction.
htlcFee := HtlcTimeoutFee(chanType, feePerKw)
secondLevelOutputAmt := htlc.Amt.ToSatoshis() - htlcFee
// With the fee calculated, re-construct the second level timeout
// transaction.
secondLevelAuxLeaf := fn.FlatMapOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
leaves := l.OutgoingHtlcLeaves
return leaves[htlc.HtlcIndex].SecondLevelLeaf
},
)(auxLeaves)
timeoutTx, err := CreateHtlcTimeoutTx(
chanType, isCommitFromInitiator, op, secondLevelOutputAmt,
htlc.RefundTimeout, csvDelay, leaseExpiry,
keyRing.RevocationKey, keyRing.ToLocalKey, secondLevelAuxLeaf,
)
if err != nil {
return nil, err
}
// With the transaction created, we can generate a sign descriptor
// that's capable of generating the signature required to spend the
// HTLC output using the timeout transaction.
txOut := commitTx.TxOut[htlc.OutputIndex]
prevFetcher := txscript.NewCannedPrevOutputFetcher(
txOut.PkScript, txOut.Value,
)
hashCache := txscript.NewTxSigHashes(timeoutTx, prevFetcher)
timeoutSignDesc := input.SignDescriptor{
KeyDesc: localChanCfg.HtlcBasePoint,
SingleTweak: keyRing.LocalHtlcKeyTweak,
WitnessScript: htlcWitnessScript,
Output: txOut,
HashType: sweepSigHash(chanType),
PrevOutputFetcher: prevFetcher,
SigHashes: hashCache,
InputIndex: 0,
}
htlcSig, err := input.ParseSignature(htlc.Signature)
if err != nil {
return nil, err
}
// With the sign desc created, we can now construct the full witness
// for the timeout transaction, and populate it as well.
sigHashType := HtlcSigHashType(chanType)
var timeoutWitness wire.TxWitness
if scriptTree, ok := htlcScriptInfo.(input.TapscriptDescriptor); ok {
timeoutSignDesc.SignMethod = input.TaprootScriptSpendSignMethod
timeoutWitness, err = input.SenderHTLCScriptTaprootTimeout(
htlcSig, sigHashType, signer, &timeoutSignDesc,
timeoutTx, keyRing.RevocationKey,
scriptTree.TapScriptTree(),
)
if err != nil {
return nil, err
}
// The control block is always the final element of the witness
// stack. We set this here as eventually the sweeper will need
// to re-sign, so it needs the isolated control block.
//
// TODO(roasbeef): move this into input.go?
ctlrBlkIdx := len(timeoutWitness) - 1
timeoutSignDesc.ControlBlock = timeoutWitness[ctlrBlkIdx]
} else {
timeoutWitness, err = input.SenderHtlcSpendTimeout(
htlcSig, sigHashType, signer, &timeoutSignDesc,
timeoutTx,
)
}
if err != nil {
return nil, err
}
timeoutTx.TxIn[0].Witness = timeoutWitness
// If this is an anchor type channel, the sign details will let us
// re-sign an aggregated tx later.
txSignDetails := HtlcSignDetails(
chanType, timeoutSignDesc, sigHashType, htlcSig,
)
// Finally, we'll generate the script output that the timeout
// transaction creates so we can generate the signDesc required to
// complete the claim process after a delay period.
var (
htlcSweepScript input.ScriptDescriptor
signMethod input.SignMethod
ctrlBlock []byte
)
if !chanType.IsTaproot() {
htlcSweepScript, err = SecondLevelHtlcScript(
chanType, isCommitFromInitiator, keyRing.RevocationKey,
keyRing.ToLocalKey, csvDelay, leaseExpiry,
secondLevelAuxLeaf,
)
if err != nil {
return nil, err
}
} else {
//nolint:ll
secondLevelScriptTree, err := input.TaprootSecondLevelScriptTree(
keyRing.RevocationKey, keyRing.ToLocalKey, csvDelay,
secondLevelAuxLeaf,
)
if err != nil {
return nil, err
}
signMethod = input.TaprootScriptSpendSignMethod
controlBlock, err := secondLevelScriptTree.CtrlBlockForPath(
input.ScriptPathSuccess,
)
if err != nil {
return nil, err
}
ctrlBlock, err = controlBlock.ToBytes()
if err != nil {
return nil, err
}
htlcSweepScript = secondLevelScriptTree
}
// In this case, the witness script that needs to be signed will always
// be that of the success path.
htlcSweepWitnessScript, err := htlcSweepScript.WitnessScriptForPath(
input.ScriptPathSuccess,
)
if err != nil {
return nil, err
}
localDelayTweak := input.SingleTweakBytes(
keyRing.CommitPoint, localChanCfg.DelayBasePoint.PubKey,
)
// In addition to the info in txSignDetails, we also need extra
// information to sweep the second level output after confirmation.
sweepSignDesc := input.SignDescriptor{
KeyDesc: localChanCfg.DelayBasePoint,
SingleTweak: localDelayTweak,
WitnessScript: htlcSweepWitnessScript,
Output: &wire.TxOut{
PkScript: htlcSweepScript.PkScript(),
Value: int64(secondLevelOutputAmt),
},
HashType: sweepSigHash(chanType),
PrevOutputFetcher: txscript.NewCannedPrevOutputFetcher(
htlcSweepScript.PkScript(),
int64(secondLevelOutputAmt),
),
SignMethod: signMethod,
ControlBlock: ctrlBlock,
}
// This might be an aux channel, so we'll go ahead and attempt to
// generate the resolution blob for the channel so we can pass along to
// the sweeping sub-system.
resolveRes := fn.MapOptionZ(
auxResolver, func(a AuxContractResolver) fn.Result[tlv.Blob] {
resReq := ResolutionReq{
ChanPoint: chanState.FundingOutpoint,
ChanType: chanType,
ShortChanID: chanState.ShortChanID(),
Initiator: chanState.IsInitiator,
CommitBlob: chanState.LocalCommitment.CustomBlob, //nolint:ll
FundingBlob: chanState.CustomBlob,
Type: input.TaprootHtlcLocalOfferedTimeout, //nolint:ll
CloseType: LocalForceClose,
CommitTx: commitTx,
ContractPoint: op,
SignDesc: sweepSignDesc,
KeyRing: keyRing,
CsvDelay: htlcCsvDelay,
HtlcAmt: btcutil.Amount(txOut.Value),
CommitCsvDelay: csvDelay,
CltvDelay: fn.Some(htlc.RefundTimeout),
CommitFee: chanState.LocalCommitment.CommitFee, //nolint:ll
HtlcID: fn.Some(htlc.HtlcIndex),
PayHash: fn.Some(htlc.RHash),
AuxSigDesc: fn.Some(AuxSigDesc{
SignDetails: *txSignDetails,
AuxSig: func() []byte {
tlvType := htlcCustomSigType.TypeVal() //nolint:ll
return htlc.CustomRecords[uint64(tlvType)] //nolint:ll
}(),
}),
}
return a.ResolveContract(resReq)
},
)
if err := resolveRes.Err(); err != nil {
return nil, fmt.Errorf("unable to aux resolve: %w", err)
}
resolutionBlob := resolveRes.OkToSome()
return &OutgoingHtlcResolution{
Expiry: htlc.RefundTimeout,
SignedTimeoutTx: timeoutTx,
SignDetails: txSignDetails,
CsvDelay: csvDelay,
ResolutionBlob: resolutionBlob,
ClaimOutpoint: wire.OutPoint{
Hash: timeoutTx.TxHash(),
Index: 0,
},
SweepSignDesc: sweepSignDesc,
}, nil
}
// newIncomingHtlcResolution creates a new HTLC resolution capable of allowing
// the caller to sweep an incoming HTLC. If the HTLC is on the caller's
// commitment transaction, then they'll need to broadcast a second-level
// transaction before sweeping the output (and incur a CSV delay). Otherwise,
// they can just sweep the output immediately with knowledge of the pre-image.
//
// TODO(roasbeef) consolidate code with above func
func newIncomingHtlcResolution(signer input.Signer,
localChanCfg *channeldb.ChannelConfig, commitTx *wire.MsgTx,
htlc *channeldb.HTLC, keyRing *CommitmentKeyRing,
feePerKw chainfee.SatPerKWeight, csvDelay, leaseExpiry uint32,
whoseCommit lntypes.ChannelParty, isCommitFromInitiator bool,
chanType channeldb.ChannelType, chanState *channeldb.OpenChannel,
auxLeaves fn.Option[CommitAuxLeaves],
auxResolver fn.Option[AuxContractResolver],
) (*IncomingHtlcResolution, error) {
op := wire.OutPoint{
Hash: commitTx.TxHash(),
Index: uint32(htlc.OutputIndex),
}
// First, we'll re-generate the script the remote party used to
// send the HTLC to us in their commitment transaction.
auxLeaf := fn.FlatMapOption(func(l CommitAuxLeaves) input.AuxTapLeaf {
return l.IncomingHtlcLeaves[htlc.HtlcIndex].AuxTapLeaf
})(auxLeaves)
scriptInfo, err := genHtlcScript(
chanType, true, whoseCommit, htlc.RefundTimeout, htlc.RHash,
keyRing, auxLeaf,
)
if err != nil {
return nil, err
}
htlcPkScript := scriptInfo.PkScript()
// As this is an incoming HTLC, we're attempting to sweep with the
// success path.
scriptPath := input.ScriptPathSuccess
htlcWitnessScript, err := scriptInfo.WitnessScriptForPath(
scriptPath,
)
if err != nil {
return nil, err
}
htlcCsvDelay := HtlcSecondLevelInputSequence(chanType)
// If we're spending this output from the remote node's commitment,
// then we can skip the second layer and spend the output directly.
if whoseCommit.IsRemote() {
// With the script generated, we can completely populated the
// SignDescriptor needed to sweep the output.
prevFetcher := txscript.NewCannedPrevOutputFetcher(
htlcPkScript, int64(htlc.Amt.ToSatoshis()),
)
signDesc := input.SignDescriptor{
KeyDesc: localChanCfg.HtlcBasePoint,
SingleTweak: keyRing.LocalHtlcKeyTweak,
WitnessScript: htlcWitnessScript,
Output: &wire.TxOut{
PkScript: htlcPkScript,
Value: int64(htlc.Amt.ToSatoshis()),
},
HashType: sweepSigHash(chanType),
PrevOutputFetcher: prevFetcher,
}
//nolint:ll
if scriptTree, ok := scriptInfo.(input.TapscriptDescriptor); ok {
signDesc.SignMethod = input.TaprootScriptSpendSignMethod
ctrlBlock, err := scriptTree.CtrlBlockForPath(
scriptPath,
)
if err != nil {
return nil, err
}
signDesc.ControlBlock, err = ctrlBlock.ToBytes()
if err != nil {
return nil, err
}
}
resReq := ResolutionReq{
ChanPoint: chanState.FundingOutpoint,
ChanType: chanType,
ShortChanID: chanState.ShortChanID(),
Initiator: chanState.IsInitiator,
CommitBlob: chanState.RemoteCommitment.CustomBlob,
Type: input.TaprootHtlcAcceptedRemoteSuccess,
FundingBlob: chanState.CustomBlob,
CloseType: RemoteForceClose,
CommitTx: commitTx,
ContractPoint: op,
SignDesc: signDesc,
KeyRing: keyRing,
HtlcID: fn.Some(htlc.HtlcIndex),
CsvDelay: htlcCsvDelay,
CltvDelay: fn.Some(htlc.RefundTimeout),
CommitFee: chanState.RemoteCommitment.CommitFee,
PayHash: fn.Some(htlc.RHash),
CommitCsvDelay: csvDelay,
HtlcAmt: htlc.Amt.ToSatoshis(),
}
resolveRes := fn.MapOptionZ(
auxResolver,
func(a AuxContractResolver) fn.Result[tlv.Blob] {
return a.ResolveContract(resReq)
},
)
if err := resolveRes.Err(); err != nil {
return nil, fmt.Errorf("unable to aux resolve: %w", err)
}
resolutionBlob := resolveRes.OkToSome()
return &IncomingHtlcResolution{
ClaimOutpoint: op,
SweepSignDesc: signDesc,
CsvDelay: htlcCsvDelay,
ResolutionBlob: resolutionBlob,
}, nil
}
secondLevelAuxLeaf := fn.FlatMapOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
leaves := l.IncomingHtlcLeaves
return leaves[htlc.HtlcIndex].SecondLevelLeaf
},
)(auxLeaves)
// Otherwise, we'll need to go to the second level to sweep this HTLC.
//
// First, we'll reconstruct the original HTLC success transaction,
// taking into account the fee rate used.
htlcFee := HtlcSuccessFee(chanType, feePerKw)
secondLevelOutputAmt := htlc.Amt.ToSatoshis() - htlcFee
successTx, err := CreateHtlcSuccessTx(
chanType, isCommitFromInitiator, op, secondLevelOutputAmt,
csvDelay, leaseExpiry, keyRing.RevocationKey,
keyRing.ToLocalKey, secondLevelAuxLeaf,
)
if err != nil {
return nil, err
}
// Once we've created the second-level transaction, we'll generate the
// SignDesc needed spend the HTLC output using the success transaction.
txOut := commitTx.TxOut[htlc.OutputIndex]
prevFetcher := txscript.NewCannedPrevOutputFetcher(
txOut.PkScript, txOut.Value,
)
hashCache := txscript.NewTxSigHashes(successTx, prevFetcher)
successSignDesc := input.SignDescriptor{
KeyDesc: localChanCfg.HtlcBasePoint,
SingleTweak: keyRing.LocalHtlcKeyTweak,
WitnessScript: htlcWitnessScript,
Output: txOut,
HashType: sweepSigHash(chanType),
PrevOutputFetcher: prevFetcher,
SigHashes: hashCache,
InputIndex: 0,
}
htlcSig, err := input.ParseSignature(htlc.Signature)
if err != nil {
return nil, err
}
// Next, we'll construct the full witness needed to satisfy the input of
// the success transaction. Don't specify the preimage yet. The preimage
// will be supplied by the contract resolver, either directly or when it
// becomes known.
var successWitness wire.TxWitness
sigHashType := HtlcSigHashType(chanType)
if scriptTree, ok := scriptInfo.(input.TapscriptDescriptor); ok {
successSignDesc.SignMethod = input.TaprootScriptSpendSignMethod
successWitness, err = input.ReceiverHTLCScriptTaprootRedeem(
htlcSig, sigHashType, nil, signer, &successSignDesc,
successTx, keyRing.RevocationKey,
scriptTree.TapScriptTree(),
)
if err != nil {
return nil, err
}
// The control block is always the final element of the witness
// stack. We set this here as eventually the sweeper will need
// to re-sign, so it needs the isolated control block.
//
// TODO(roasbeef): move this into input.go?
ctlrBlkIdx := len(successWitness) - 1
successSignDesc.ControlBlock = successWitness[ctlrBlkIdx]
} else {
successWitness, err = input.ReceiverHtlcSpendRedeem(
htlcSig, sigHashType, nil, signer, &successSignDesc,
successTx,
)
if err != nil {
return nil, err
}
}
successTx.TxIn[0].Witness = successWitness
// If this is an anchor type channel, the sign details will let us
// re-sign an aggregated tx later.
txSignDetails := HtlcSignDetails(
chanType, successSignDesc, sigHashType, htlcSig,
)
// Finally, we'll generate the script that the second-level transaction
// creates so we can generate the proper signDesc to sweep it after the
// CSV delay has passed.
var (
htlcSweepScript input.ScriptDescriptor
signMethod input.SignMethod
ctrlBlock []byte
)
if !chanType.IsTaproot() {
htlcSweepScript, err = SecondLevelHtlcScript(
chanType, isCommitFromInitiator, keyRing.RevocationKey,
keyRing.ToLocalKey, csvDelay, leaseExpiry,
secondLevelAuxLeaf,
)
if err != nil {
return nil, err
}
} else {
//nolint:ll
secondLevelScriptTree, err := input.TaprootSecondLevelScriptTree(
keyRing.RevocationKey, keyRing.ToLocalKey, csvDelay,
secondLevelAuxLeaf,
)
if err != nil {
return nil, err
}
signMethod = input.TaprootScriptSpendSignMethod
controlBlock, err := secondLevelScriptTree.CtrlBlockForPath(
input.ScriptPathSuccess,
)
if err != nil {
return nil, err
}
ctrlBlock, err = controlBlock.ToBytes()
if err != nil {
return nil, err
}
htlcSweepScript = secondLevelScriptTree
}
// In this case, the witness script that needs to be signed will always
// be that of the success path.
htlcSweepWitnessScript, err := htlcSweepScript.WitnessScriptForPath(
input.ScriptPathSuccess,
)
if err != nil {
return nil, err
}
localDelayTweak := input.SingleTweakBytes(
keyRing.CommitPoint, localChanCfg.DelayBasePoint.PubKey,
)
// In addition to the info in txSignDetails, we also need extra
// information to sweep the second level output after confirmation.
sweepSignDesc := input.SignDescriptor{
KeyDesc: localChanCfg.DelayBasePoint,
SingleTweak: localDelayTweak,
WitnessScript: htlcSweepWitnessScript,
Output: &wire.TxOut{
PkScript: htlcSweepScript.PkScript(),
Value: int64(secondLevelOutputAmt),
},
HashType: sweepSigHash(chanType),
PrevOutputFetcher: txscript.NewCannedPrevOutputFetcher(
htlcSweepScript.PkScript(),
int64(secondLevelOutputAmt),
),
SignMethod: signMethod,
ControlBlock: ctrlBlock,
}
resolveRes := fn.MapOptionZ(
auxResolver, func(a AuxContractResolver) fn.Result[tlv.Blob] {
resReq := ResolutionReq{
ChanPoint: chanState.FundingOutpoint,
ChanType: chanType,
ShortChanID: chanState.ShortChanID(),
Initiator: chanState.IsInitiator,
CommitBlob: chanState.LocalCommitment.CustomBlob, //nolint:ll
Type: input.TaprootHtlcAcceptedLocalSuccess, //nolint:ll
FundingBlob: chanState.CustomBlob,
CloseType: LocalForceClose,
CommitTx: commitTx,
ContractPoint: op,
SignDesc: sweepSignDesc,
KeyRing: keyRing,
HtlcID: fn.Some(htlc.HtlcIndex),
CsvDelay: htlcCsvDelay,
CommitFee: chanState.LocalCommitment.CommitFee, //nolint:ll
PayHash: fn.Some(htlc.RHash),
AuxSigDesc: fn.Some(AuxSigDesc{
SignDetails: *txSignDetails,
AuxSig: func() []byte {
tlvType := htlcCustomSigType.TypeVal() //nolint:ll
return htlc.CustomRecords[uint64(tlvType)] //nolint:ll
}(),
}),
CommitCsvDelay: csvDelay,
HtlcAmt: btcutil.Amount(txOut.Value),
CltvDelay: fn.Some(htlc.RefundTimeout),
}
return a.ResolveContract(resReq)
},
)
if err := resolveRes.Err(); err != nil {
return nil, fmt.Errorf("unable to aux resolve: %w", err)
}
resolutionBlob := resolveRes.OkToSome()
return &IncomingHtlcResolution{
SignedSuccessTx: successTx,
SignDetails: txSignDetails,
CsvDelay: csvDelay,
ResolutionBlob: resolutionBlob,
ClaimOutpoint: wire.OutPoint{
Hash: successTx.TxHash(),
Index: 0,
},
SweepSignDesc: sweepSignDesc,
}, nil
}
// HtlcPoint returns the htlc's outpoint on the commitment tx.
func (r *IncomingHtlcResolution) HtlcPoint() wire.OutPoint {
// If we have a success transaction, then the htlc's outpoint
// is the transaction's only input. Otherwise, it's the claim
// point.
if r.SignedSuccessTx != nil {
return r.SignedSuccessTx.TxIn[0].PreviousOutPoint
}
return r.ClaimOutpoint
}
// HtlcPoint returns the htlc's outpoint on the commitment tx.
func (r *OutgoingHtlcResolution) HtlcPoint() wire.OutPoint {
// If we have a timeout transaction, then the htlc's outpoint
// is the transaction's only input. Otherwise, it's the claim
// point.
if r.SignedTimeoutTx != nil {
return r.SignedTimeoutTx.TxIn[0].PreviousOutPoint
}
return r.ClaimOutpoint
}
// extractHtlcResolutions creates a series of outgoing HTLC resolutions, and
// the local key used when generating the HTLC scrips. This function is to be
// used in two cases: force close, or a unilateral close.
func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight,
whoseCommit lntypes.ChannelParty, signer input.Signer,
htlcs []channeldb.HTLC, keyRing *CommitmentKeyRing,
localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
commitTx *wire.MsgTx, chanType channeldb.ChannelType,
isCommitFromInitiator bool, leaseExpiry uint32,
chanState *channeldb.OpenChannel, auxLeaves fn.Option[CommitAuxLeaves],
auxResolver fn.Option[AuxContractResolver]) (*HtlcResolutions, error) {
// TODO(roasbeef): don't need to swap csv delay?
dustLimit := remoteChanCfg.DustLimit
csvDelay := remoteChanCfg.CsvDelay
if whoseCommit.IsLocal() {
dustLimit = localChanCfg.DustLimit
csvDelay = localChanCfg.CsvDelay
}
incomingResolutions := make([]IncomingHtlcResolution, 0, len(htlcs))
outgoingResolutions := make([]OutgoingHtlcResolution, 0, len(htlcs))
for _, htlc := range htlcs {
htlc := htlc
// We'll skip any HTLC's which were dust on the commitment
// transaction, as these don't have a corresponding output
// within the commitment transaction.
if HtlcIsDust(
chanType, htlc.Incoming, whoseCommit, feePerKw,
htlc.Amt.ToSatoshis(), dustLimit,
) {
continue
}
// If the HTLC is incoming, then we'll attempt to see if we
// know the pre-image to the HTLC.
if htlc.Incoming {
// Otherwise, we'll create an incoming HTLC resolution
// as we can satisfy the contract.
ihr, err := newIncomingHtlcResolution(
signer, localChanCfg, commitTx, &htlc,
keyRing, feePerKw, uint32(csvDelay),
leaseExpiry, whoseCommit, isCommitFromInitiator,
chanType, chanState, auxLeaves, auxResolver,
)
if err != nil {
return nil, fmt.Errorf("incoming resolution "+
"failed: %v", err)
}
incomingResolutions = append(incomingResolutions, *ihr)
continue
}
ohr, err := newOutgoingHtlcResolution(
signer, localChanCfg, commitTx, &htlc, keyRing,
feePerKw, uint32(csvDelay), leaseExpiry, whoseCommit,
isCommitFromInitiator, chanType, chanState, auxLeaves,
auxResolver,
)
if err != nil {
return nil, fmt.Errorf("outgoing resolution "+
"failed: %v", err)
}
outgoingResolutions = append(outgoingResolutions, *ohr)
}
return &HtlcResolutions{
IncomingHTLCs: incomingResolutions,
OutgoingHTLCs: outgoingResolutions,
}, nil
}
// AnchorResolution holds the information necessary to spend our commitment tx
// anchor.
type AnchorResolution struct {
// AnchorSignDescriptor is the sign descriptor for our anchor.
AnchorSignDescriptor input.SignDescriptor
// CommitAnchor is the anchor outpoint on the commit tx.
CommitAnchor wire.OutPoint
// CommitFee is the fee of the commit tx.
CommitFee btcutil.Amount
// CommitWeight is the weight of the commit tx.
CommitWeight lntypes.WeightUnit
}
// LocalForceCloseSummary describes the final commitment state before the
// channel is locked-down to initiate a force closure by broadcasting the
// latest state on-chain. If we intend to broadcast this this state, the
// channel should not be used after generating this close summary. The summary
// includes all the information required to claim all rightfully owned outputs
// when the commitment gets confirmed.
type LocalForceCloseSummary struct {
// ChanPoint is the outpoint that created the channel which has been
// force closed.
ChanPoint wire.OutPoint
// CloseTx is the transaction which can be used to close the channel
// on-chain. When we initiate a force close, this will be our latest
// commitment state.
CloseTx *wire.MsgTx
// ChanSnapshot is a snapshot of the final state of the channel at the
// time the summary was created.
ChanSnapshot channeldb.ChannelSnapshot
// ContractResolutions contains all the data required for resolving the
// different output types of a commitment transaction.
ContractResolutions fn.Option[ContractResolutions]
}
// ContractResolutions contains all the data required for resolving the
// different output types of a commitment transaction.
type ContractResolutions struct {
// CommitResolution contains all the data required to sweep the output
// to ourselves. Since this is our commitment transaction, we'll need
// to wait a time delay before we can sweep the output.
//
// NOTE: If our commitment delivery output is below the dust limit,
// then this will be nil.
CommitResolution *CommitOutputResolution
// AnchorResolution contains the data required to sweep the anchor
// output. If the channel type doesn't include anchors, the value of
// this field will be nil.
AnchorResolution *AnchorResolution
// HtlcResolutions contains all the data required to sweep any outgoing
// HTLC's and incoming HTLc's we know the preimage to. For each of these
// HTLC's, we'll need to go to the second level to sweep them fully.
HtlcResolutions *HtlcResolutions
}
// ForceCloseOpt is a functional option argument for the ForceClose method.
type ForceCloseOpt func(*forceCloseConfig)
// forceCloseConfig holds the configuration options for force closing a channel.
type forceCloseConfig struct {
// skipResolution if true will skip creating the contract resolutions
// when generating the force close summary.
skipResolution bool
}
// defaultForceCloseConfig returns the default force close configuration.
func defaultForceCloseConfig() *forceCloseConfig {
return &forceCloseConfig{}
}
// WithSkipContractResolutions creates an option to skip the contract
// resolutions from the returned summary.
func WithSkipContractResolutions() ForceCloseOpt {
return func(cfg *forceCloseConfig) {
cfg.skipResolution = true
}
}
// ForceClose executes a unilateral closure of the transaction at the current
// lowest commitment height of the channel. Following a force closure, all
// state transitions, or modifications to the state update logs will be
// rejected. Additionally, this function also returns a LocalForceCloseSummary
// which includes the necessary details required to sweep all the time-locked
// outputs within the commitment transaction.
//
// TODO(roasbeef): all methods need to abort if in dispute state
func (lc *LightningChannel) ForceClose(opts ...ForceCloseOpt) (
*LocalForceCloseSummary, error) {
lc.Lock()
defer lc.Unlock()
cfg := defaultForceCloseConfig()
for _, opt := range opts {
opt(cfg)
}
// If we've detected local data loss for this channel, then we won't
// allow a force close, as it may be the case that we have a dated
// version of the commitment, or this is actually a channel shell.
if lc.channelState.HasChanStatus(channeldb.ChanStatusLocalDataLoss) {
return nil, fmt.Errorf("%w: channel_state=%v",
ErrForceCloseLocalDataLoss,
lc.channelState.ChanStatus())
}
commitTx, err := lc.getSignedCommitTx()
if err != nil {
return nil, err
}
if cfg.skipResolution {
return &LocalForceCloseSummary{
ChanPoint: lc.channelState.FundingOutpoint,
ChanSnapshot: *lc.channelState.Snapshot(),
CloseTx: commitTx,
}, nil
}
localCommitment := lc.channelState.LocalCommitment
summary, err := NewLocalForceCloseSummary(
lc.channelState, lc.Signer, commitTx,
localCommitment.CommitHeight, lc.leafStore, lc.auxResolver,
)
if err != nil {
return nil, fmt.Errorf("unable to gen force close "+
"summary: %w", err)
}
// Mark the channel as closed to block future closure requests.
lc.isClosed = true
return summary, nil
}
// NewLocalForceCloseSummary generates a LocalForceCloseSummary from the given
// channel state. The passed commitTx must be a fully signed commitment
// transaction corresponding to localCommit.
func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel,
signer input.Signer, commitTx *wire.MsgTx, stateNum uint64,
leafStore fn.Option[AuxLeafStore],
auxResolver fn.Option[AuxContractResolver]) (*LocalForceCloseSummary,
error) {
// Re-derive the original pkScript for to-self output within the
// commitment transaction. We'll need this to find the corresponding
// output in the commitment transaction and potentially for creating
// the sign descriptor.
csvTimeout := uint32(chanState.LocalChanCfg.CsvDelay)
// We use the passed state num to derive our scripts, since in case
// this is after recovery, our latest channels state might not be up to
// date.
revocation, err := chanState.RevocationProducer.AtIndex(stateNum)
if err != nil {
return nil, err
}
commitPoint := input.ComputeCommitmentPoint(revocation[:])
keyRing := DeriveCommitmentKeys(
commitPoint, lntypes.Local, chanState.ChanType,
&chanState.LocalChanCfg, &chanState.RemoteChanCfg,
)
auxResult, err := fn.MapOptionZ(
leafStore, func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return s.FetchLeavesFromCommit(
NewAuxChanState(chanState),
chanState.LocalCommitment, *keyRing,
lntypes.Local,
)
},
).Unpack()
if err != nil {
return nil, fmt.Errorf("unable to fetch aux leaves: %w", err)
}
var leaseExpiry uint32
if chanState.ChanType.HasLeaseExpiration() {
leaseExpiry = chanState.ThawHeight
}
localAuxLeaf := fn.FlatMapOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
return l.LocalAuxLeaf
},
)(auxResult.AuxLeaves)
toLocalScript, err := CommitScriptToSelf(
chanState.ChanType, chanState.IsInitiator, keyRing.ToLocalKey,
keyRing.RevocationKey, csvTimeout, leaseExpiry, localAuxLeaf,
)
if err != nil {
return nil, err
}
// Locate the output index of the delayed commitment output back to us.
// We'll return the details of this output to the caller so they can
// sweep it once it's mature.
var (
delayIndex uint32
delayOut *wire.TxOut
)
for i, txOut := range commitTx.TxOut {
if !bytes.Equal(toLocalScript.PkScript(), txOut.PkScript) {
continue
}
delayIndex = uint32(i)
delayOut = txOut
break
}
// With the necessary information gathered above, create a new sign
// descriptor which is capable of generating the signature the caller
// needs to sweep this output. The hash cache, and input index are not
// set as the caller will decide these values once sweeping the output.
// If the output is non-existent (dust), have the sign descriptor be
// nil.
var commitResolution *CommitOutputResolution
if delayOut != nil {
// When attempting to sweep our own output, we only need the
// witness script for the delay path
scriptPath := input.ScriptPathDelay
witnessScript, err := toLocalScript.WitnessScriptForPath(
scriptPath,
)
if err != nil {
return nil, err
}
localBalance := delayOut.Value
commitResolution = &CommitOutputResolution{
SelfOutPoint: wire.OutPoint{
Hash: commitTx.TxHash(),
Index: delayIndex,
},
SelfOutputSignDesc: input.SignDescriptor{
KeyDesc: chanState.LocalChanCfg.DelayBasePoint,
SingleTweak: keyRing.LocalCommitKeyTweak,
WitnessScript: witnessScript,
Output: &wire.TxOut{
PkScript: delayOut.PkScript,
Value: localBalance,
},
HashType: sweepSigHash(chanState.ChanType),
},
MaturityDelay: csvTimeout,
}
// For taproot channels, we'll need to set some additional
// fields to ensure the output can be swept.
scriptTree, ok := toLocalScript.(input.TapscriptDescriptor)
if ok {
commitResolution.SelfOutputSignDesc.SignMethod =
input.TaprootScriptSpendSignMethod
ctrlBlock, err := scriptTree.CtrlBlockForPath(
scriptPath,
)
if err != nil {
return nil, err
}
//nolint:ll
commitResolution.SelfOutputSignDesc.ControlBlock, err = ctrlBlock.ToBytes()
if err != nil {
return nil, err
}
}
// At this point, we'll check to see if we need any extra
// resolution data for this output.
resolveBlob := fn.MapOptionZ(
auxResolver,
func(a AuxContractResolver) fn.Result[tlv.Blob] {
//nolint:ll
return a.ResolveContract(ResolutionReq{
ChanPoint: chanState.FundingOutpoint, //nolint:ll
ChanType: chanState.ChanType,
ShortChanID: chanState.ShortChanID(),
Initiator: chanState.IsInitiator,
CommitBlob: chanState.LocalCommitment.CustomBlob,
FundingBlob: chanState.CustomBlob,
Type: input.TaprootLocalCommitSpend,
CloseType: LocalForceClose,
CommitTx: commitTx,
ContractPoint: commitResolution.SelfOutPoint,
SignDesc: commitResolution.SelfOutputSignDesc,
KeyRing: keyRing,
CsvDelay: csvTimeout,
CommitFee: chanState.LocalCommitment.CommitFee,
})
},
)
if err := resolveBlob.Err(); err != nil {
return nil, fmt.Errorf("unable to aux resolve: %w", err)
}
commitResolution.ResolutionBlob = resolveBlob.OkToSome()
}
// Once the delay output has been found (if it exists), then we'll also
// need to create a series of sign descriptors for any lingering
// outgoing HTLC's that we'll need to claim as well. If this is after
// recovery there is not much we can do with HTLCs, so we'll always
// use what we have in our latest state when extracting resolutions.
localCommit := chanState.LocalCommitment
htlcResolutions, err := extractHtlcResolutions(
chainfee.SatPerKWeight(localCommit.FeePerKw), lntypes.Local,
signer, localCommit.Htlcs, keyRing, &chanState.LocalChanCfg,
&chanState.RemoteChanCfg, commitTx, chanState.ChanType,
chanState.IsInitiator, leaseExpiry, chanState,
auxResult.AuxLeaves, auxResolver,
)
if err != nil {
return nil, fmt.Errorf("unable to gen htlc resolution: %w", err)
}
anchorResolution, err := NewAnchorResolution(
chanState, commitTx, keyRing, lntypes.Local,
)
if err != nil {
return nil, fmt.Errorf("unable to gen anchor "+
"resolution: %w", err)
}
return &LocalForceCloseSummary{
ChanPoint: chanState.FundingOutpoint,
CloseTx: commitTx,
ChanSnapshot: *chanState.Snapshot(),
ContractResolutions: fn.Some(ContractResolutions{
CommitResolution: commitResolution,
HtlcResolutions: htlcResolutions,
AnchorResolution: anchorResolution,
}),
}, nil
}
// CloseOutput wraps a normal tx out with additional metadata that indicates if
// the output belongs to the initiator of the channel or not.
type CloseOutput struct {
wire.TxOut
// IsLocal indicates if the output belong to the local party.
IsLocal bool
}
// CloseSortFunc is a function type alias for a function that sorts the closing
// transaction.
type CloseSortFunc func(*wire.MsgTx) error
// chanCloseOpt is a functional option that can be used to modify the co-op
// close process.
type chanCloseOpt struct {
musigSession *MusigSession
extraCloseOutputs []CloseOutput
// customSort is a custom function that can be used to sort the
// transaction outputs. If this isn't set, then the default BIP-69
// sorting is used.
customSort CloseSortFunc
customSequence fn.Option[uint32]
customLockTime fn.Option[uint32]
customPayer fn.Option[lntypes.ChannelParty]
}
// ChanCloseOpt is a closure type that cen be used to modify the set of default
// options.
type ChanCloseOpt func(*chanCloseOpt)
// defaultCloseOpts is the default set of close options.
func defaultCloseOpts() *chanCloseOpt {
return &chanCloseOpt{}
}
// WithCoopCloseMusigSession can be used to apply an existing musig2 session to
// the cooperative close process. If specified, then a musig2 co-op close
// (single sig keyspend) will be used.
func WithCoopCloseMusigSession(session *MusigSession) ChanCloseOpt {
return func(opts *chanCloseOpt) {
opts.musigSession = session
}
}
// WithExtraCloseOutputs can be used to add extra outputs to the cooperative
// transaction.
func WithExtraCloseOutputs(extraOutputs []CloseOutput) ChanCloseOpt {
return func(opts *chanCloseOpt) {
opts.extraCloseOutputs = extraOutputs
}
}
// WithCustomCoopSort can be used to modify the way the co-op close transaction
// is sorted.
func WithCustomCoopSort(sorter CloseSortFunc) ChanCloseOpt {
return func(opts *chanCloseOpt) {
opts.customSort = sorter
}
}
// WithCustomSequence can be used to specify a custom sequence number for the
// co-op close process. Otherwise, a default non-final sequence will be used.
func WithCustomSequence(sequence uint32) ChanCloseOpt {
return func(opts *chanCloseOpt) {
opts.customSequence = fn.Some(sequence)
}
}
// WithCustomLockTime can be used to specify a custom lock time for the coop
// close transaction.
func WithCustomLockTime(lockTime uint32) ChanCloseOpt {
return func(opts *chanCloseOpt) {
opts.customLockTime = fn.Some(lockTime)
}
}
// WithCustomPayer can be used to specify a custom payer for the closing
// transaction. This overrides the default payer, which is the initiator of the
// channel.
func WithCustomPayer(payer lntypes.ChannelParty) ChanCloseOpt {
return func(opts *chanCloseOpt) {
opts.customPayer = fn.Some(payer)
}
}
// CreateCloseProposal is used by both parties in a cooperative channel close
// workflow to generate proposed close transactions and signatures. This method
// should only be executed once all pending HTLCs (if any) on the channel have
// been cleared/removed. Upon completion, the source channel will shift into
// the "closing" state, which indicates that all incoming/outgoing HTLC
// requests should be rejected. A signature for the closing transaction is
// returned.
func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount,
localDeliveryScript []byte, remoteDeliveryScript []byte,
closeOpts ...ChanCloseOpt) (input.Signature, *wire.MsgTx,
btcutil.Amount, error) {
lc.Lock()
defer lc.Unlock()
opts := defaultCloseOpts()
for _, optFunc := range closeOpts {
optFunc(opts)
}
// Unless there's a custom payer (sign of the RBF flow), if we're
// already closing the channel, then ignore this request.
if lc.isClosed && opts.customPayer.IsNone() {
return nil, nil, 0, ErrChanClosing
}
// Get the final balances after subtracting the proposed fee, taking
// care not to persist the adjusted balance, as the feeRate may change
// during the channel closing process.
ourBalance, theirBalance, err := CoopCloseBalance(
lc.channelState.ChanType, lc.channelState.IsInitiator,
proposedFee,
lc.channelState.LocalCommitment.LocalBalance.ToSatoshis(),
lc.channelState.LocalCommitment.RemoteBalance.ToSatoshis(),
lc.channelState.LocalCommitment.CommitFee,
opts.customPayer,
)
if err != nil {
return nil, nil, 0, err
}
var closeTxOpts []CloseTxOpt
// If this is a taproot channel, then we use an RBF'able funding input.
if lc.channelState.ChanType.IsTaproot() {
closeTxOpts = append(closeTxOpts, WithRBFCloseTx())
}
// If we have any extra outputs to pass along, then we'll map that to
// the co-op close option txn type.
if opts.extraCloseOutputs != nil {
closeTxOpts = append(closeTxOpts, WithExtraTxCloseOutputs(
opts.extraCloseOutputs,
))
}
if opts.customSort != nil {
closeTxOpts = append(
closeTxOpts, WithCustomTxSort(opts.customSort),
)
}
opts.customSequence.WhenSome(func(sequence uint32) {
closeTxOpts = append(closeTxOpts, WithCustomTxInSequence(
sequence,
))
})
opts.customLockTime.WhenSome(func(lockTime uint32) {
closeTxOpts = append(closeTxOpts, WithCustomTxLockTime(
lockTime,
))
})
closeTx, err := CreateCooperativeCloseTx(
fundingTxIn(lc.channelState), lc.channelState.LocalChanCfg.DustLimit,
lc.channelState.RemoteChanCfg.DustLimit, ourBalance, theirBalance,
localDeliveryScript, remoteDeliveryScript, closeTxOpts...,
)
if err != nil {
return nil, nil, 0, err
}
// Ensure that the transaction doesn't explicitly violate any
// consensus rules such as being too big, or having any value with a
// negative output.
tx := btcutil.NewTx(closeTx)
if err := blockchain.CheckTransactionSanity(tx); err != nil {
return nil, nil, 0, err
}
// If we have a co-op close musig session, then this is a taproot
// channel, so we'll generate a _partial_ signature.
var sig input.Signature
if opts.musigSession != nil {
sig, err = opts.musigSession.SignCommit(closeTx)
if err != nil {
return nil, nil, 0, err
}
} else {
// For regular channels we'll, sign the completed cooperative
// closure transaction. As the initiator we'll simply send our
// signature over to the remote party, using the generated txid
// to be notified once the closure transaction has been
// confirmed.
lc.signDesc.SigHashes = input.NewTxSigHashesV0Only(closeTx)
sig, err = lc.Signer.SignOutputRaw(closeTx, lc.signDesc)
if err != nil {
return nil, nil, 0, err
}
}
return sig, closeTx, ourBalance, nil
}
// CompleteCooperativeClose completes the cooperative closure of the target
// active lightning channel. A fully signed closure transaction as well as the
// signature itself are returned. Additionally, we also return our final
// settled balance, which reflects any fees we may have paid.
//
// NOTE: The passed local and remote sigs are expected to be fully complete
// signatures including the proper sighash byte.
func (lc *LightningChannel) CompleteCooperativeClose(
localSig, remoteSig input.Signature,
localDeliveryScript, remoteDeliveryScript []byte,
proposedFee btcutil.Amount,
closeOpts ...ChanCloseOpt) (*wire.MsgTx, btcutil.Amount, error) {
lc.Lock()
defer lc.Unlock()
opts := defaultCloseOpts()
for _, optFunc := range closeOpts {
optFunc(opts)
}
// Unless there's a custom payer (sign of the RBF flow), if we're
// already closing the channel, then ignore this request.
if lc.isClosed && opts.customPayer.IsNone() {
return nil, 0, ErrChanClosing
}
// Get the final balances after subtracting the proposed fee.
ourBalance, theirBalance, err := CoopCloseBalance(
lc.channelState.ChanType, lc.channelState.IsInitiator,
proposedFee,
lc.channelState.LocalCommitment.LocalBalance.ToSatoshis(),
lc.channelState.LocalCommitment.RemoteBalance.ToSatoshis(),
lc.channelState.LocalCommitment.CommitFee,
opts.customPayer,
)
if err != nil {
return nil, 0, err
}
var closeTxOpts []CloseTxOpt
// If this is a taproot channel, then we use an RBF'able funding input.
if lc.channelState.ChanType.IsTaproot() {
closeTxOpts = append(closeTxOpts, WithRBFCloseTx())
}
// If we have any extra outputs to pass along, then we'll map that to
// the co-op close option txn type.
if opts.extraCloseOutputs != nil {
closeTxOpts = append(closeTxOpts, WithExtraTxCloseOutputs(
opts.extraCloseOutputs,
))
}
if opts.customSort != nil {
closeTxOpts = append(
closeTxOpts, WithCustomTxSort(opts.customSort),
)
}
opts.customSequence.WhenSome(func(sequence uint32) {
closeTxOpts = append(closeTxOpts, WithCustomTxInSequence(
sequence,
))
})
opts.customLockTime.WhenSome(func(lockTime uint32) {
closeTxOpts = append(closeTxOpts, WithCustomTxLockTime(
lockTime,
))
})
// Create the transaction used to return the current settled balance
// on this active channel back to both parties. In this current model,
// the initiator pays full fees for the cooperative close transaction.
closeTx, err := CreateCooperativeCloseTx(
fundingTxIn(lc.channelState), lc.channelState.LocalChanCfg.DustLimit,
lc.channelState.RemoteChanCfg.DustLimit, ourBalance, theirBalance,
localDeliveryScript, remoteDeliveryScript, closeTxOpts...,
)
if err != nil {
return nil, 0, err
}
// Ensure that the transaction doesn't explicitly validate any
// consensus rules such as being too big, or having any value with a
// negative output.
tx := btcutil.NewTx(closeTx)
prevOut := lc.signDesc.Output
if err := blockchain.CheckTransactionSanity(tx); err != nil {
return nil, 0, err
}
prevOutputFetcher := txscript.NewCannedPrevOutputFetcher(
prevOut.PkScript, prevOut.Value,
)
hashCache := txscript.NewTxSigHashes(closeTx, prevOutputFetcher)
// Next, we'll complete the co-op close transaction. Depending on the
// set of options, we'll either do a regular p2wsh spend, or construct
// the final schnorr signature from a set of partial sigs.
if opts.musigSession != nil {
// For taproot channels, we'll use the attached session to
// combine the two partial signatures into a proper schnorr
// signature.
remotePartialSig, ok := remoteSig.(*MusigPartialSig)
if !ok {
return nil, 0, fmt.Errorf("expected MusigPartialSig, "+
"got %T", remoteSig)
}
finalSchnorrSig, err := opts.musigSession.CombineSigs(
remotePartialSig.sig,
)
if err != nil {
return nil, 0, fmt.Errorf("unable to combine "+
"final co-op close sig: %w", err)
}
// The witness for a keyspend is just the signature itself.
closeTx.TxIn[0].Witness = wire.TxWitness{
finalSchnorrSig.Serialize(),
}
} else {
// For regular channels, we'll need to , construct the witness
// stack minding the order of the pubkeys+sigs on the stack.
ourKey := lc.channelState.LocalChanCfg.MultiSigKey.PubKey.
SerializeCompressed()
theirKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey.
SerializeCompressed()
witness := input.SpendMultiSig(
lc.signDesc.WitnessScript, ourKey, localSig, theirKey,
remoteSig,
)
closeTx.TxIn[0].Witness = witness
}
// Validate the finalized transaction to ensure the output script is
// properly met, and that the remote peer supplied a valid signature.
vm, err := txscript.NewEngine(
prevOut.PkScript, closeTx, 0, txscript.StandardVerifyFlags, nil,
hashCache, prevOut.Value, prevOutputFetcher,
)
if err != nil {
return nil, 0, err
}
if err := vm.Execute(); err != nil {
return nil, 0, err
}
// As the transaction is sane, and the scripts are valid we'll mark the
// channel now as closed as the closure transaction should get into the
// chain in a timely manner and possibly be re-broadcast by the wallet.
lc.isClosed = true
return closeTx, ourBalance, nil
}
// AnchorResolutions is a set of anchor resolutions that's being used when
// sweeping anchors during local channel force close.
type AnchorResolutions struct {
// Local is the anchor resolution for the local commitment tx.
Local *AnchorResolution
// Remote is the anchor resolution for the remote commitment tx.
Remote *AnchorResolution
// RemotePending is the anchor resolution for the remote pending
// commitment tx. The value will be non-nil iff we've created a new
// commitment tx for the remote party which they haven't ACKed yet.
RemotePending *AnchorResolution
}
// NewAnchorResolutions returns a set of anchor resolutions wrapped in the
// struct AnchorResolutions. Because we have no view on the mempool, we can
// only blindly anchor all of these txes down. The caller needs to check the
// returned values against nil to decide whether there exists an anchor
// resolution for local/remote/pending remote commitment txes.
func (lc *LightningChannel) NewAnchorResolutions() (*AnchorResolutions,
error) {
lc.Lock()
defer lc.Unlock()
var resolutions AnchorResolutions
// Add anchor for local commitment tx, if any.
revocation, err := lc.channelState.RevocationProducer.AtIndex(
lc.currentHeight,
)
if err != nil {
return nil, err
}
localCommitPoint := input.ComputeCommitmentPoint(revocation[:])
localKeyRing := DeriveCommitmentKeys(
localCommitPoint, lntypes.Local, lc.channelState.ChanType,
&lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg,
)
localRes, err := NewAnchorResolution(
lc.channelState, lc.channelState.LocalCommitment.CommitTx,
localKeyRing, lntypes.Local,
)
if err != nil {
return nil, err
}
resolutions.Local = localRes
// Add anchor for remote commitment tx, if any.
remoteKeyRing := DeriveCommitmentKeys(
lc.channelState.RemoteCurrentRevocation, lntypes.Remote,
lc.channelState.ChanType, &lc.channelState.LocalChanCfg,
&lc.channelState.RemoteChanCfg,
)
remoteRes, err := NewAnchorResolution(
lc.channelState, lc.channelState.RemoteCommitment.CommitTx,
remoteKeyRing, lntypes.Remote,
)
if err != nil {
return nil, err
}
resolutions.Remote = remoteRes
// Add anchor for remote pending commitment tx, if any.
remotePendingCommit, err := lc.channelState.RemoteCommitChainTip()
if err != nil && err != channeldb.ErrNoPendingCommit {
return nil, err
}
if remotePendingCommit != nil {
pendingRemoteKeyRing := DeriveCommitmentKeys(
lc.channelState.RemoteNextRevocation, lntypes.Remote,
lc.channelState.ChanType, &lc.channelState.LocalChanCfg,
&lc.channelState.RemoteChanCfg,
)
remotePendingRes, err := NewAnchorResolution(
lc.channelState,
remotePendingCommit.Commitment.CommitTx,
pendingRemoteKeyRing, lntypes.Remote,
)
if err != nil {
return nil, err
}
resolutions.RemotePending = remotePendingRes
}
return &resolutions, nil
}
// NewAnchorResolution returns the information that is required to sweep the
// local anchor.
func NewAnchorResolution(chanState *channeldb.OpenChannel,
commitTx *wire.MsgTx, keyRing *CommitmentKeyRing,
whoseCommit lntypes.ChannelParty) (*AnchorResolution, error) {
// Return nil resolution if the channel has no anchors.
if !chanState.ChanType.HasAnchors() {
return nil, nil
}
// Derive our local anchor script. For taproot channels, rather than
// use the same multi-sig key for both commitments, the anchor script
// will differ depending on if this is our local or remote
// commitment.
localAnchor, remoteAnchor, err := CommitScriptAnchors(
chanState.ChanType, &chanState.LocalChanCfg,
&chanState.RemoteChanCfg, keyRing,
)
if err != nil {
return nil, err
}
if chanState.ChanType.IsTaproot() && whoseCommit.IsRemote() {
//nolint:ineffassign
localAnchor, remoteAnchor = remoteAnchor, localAnchor
}
// TODO(roasbeef): remote anchor not needed above
// Look up the script on the commitment transaction. It may not be
// present if there is no output paying to us.
found, index := input.FindScriptOutputIndex(
commitTx, localAnchor.PkScript(),
)
if !found {
return nil, nil
}
// For anchor outputs, we'll only ever care about the success path.
// script (sweep after 1 block csv delay).
anchorWitnessScript, err := localAnchor.WitnessScriptForPath(
input.ScriptPathSuccess,
)
if err != nil {
return nil, err
}
outPoint := &wire.OutPoint{
Hash: commitTx.TxHash(),
Index: index,
}
// Instantiate the sign descriptor that allows sweeping of the anchor.
signDesc := &input.SignDescriptor{
KeyDesc: chanState.LocalChanCfg.MultiSigKey,
WitnessScript: anchorWitnessScript,
Output: &wire.TxOut{
PkScript: localAnchor.PkScript(),
Value: int64(AnchorSize),
},
HashType: sweepSigHash(chanState.ChanType),
}
// For taproot outputs, we'll need to ensure that the proper sign
// method is used, and the tweak as well.
if scriptTree, ok := localAnchor.(input.TapscriptDescriptor); ok {
signDesc.SignMethod = input.TaprootKeySpendSignMethod
//nolint:ll
signDesc.PrevOutputFetcher = txscript.NewCannedPrevOutputFetcher(
localAnchor.PkScript(), int64(AnchorSize),
)
// For anchor outputs with taproot channels, the key desc is
// also different: we'll just re-use our local delay base point
// (which becomes our to local output).
if whoseCommit.IsLocal() {
// In addition to the sign method, we'll also need to
// ensure that the single tweak is set, as with the
// current formulation, we'll need to use two levels of
// tweaks: the normal LN tweak, and the tapscript
// tweak.
signDesc.SingleTweak = keyRing.LocalCommitKeyTweak
signDesc.KeyDesc = chanState.LocalChanCfg.DelayBasePoint
} else {
// When we're playing the force close of a remote
// commitment, as this is a "tweakless" channel type,
// we don't need a tweak value at all.
//
//nolint:ll
signDesc.KeyDesc = chanState.LocalChanCfg.PaymentBasePoint
}
// Finally, as this is a keyspend method, we'll need to also
// include the taptweak as well.
signDesc.TapTweak = scriptTree.TapTweak()
}
var witnessWeight int64
if chanState.ChanType.IsTaproot() {
witnessWeight = input.TaprootKeyPathWitnessSize
} else {
witnessWeight = input.WitnessCommitmentTxWeight
}
// Calculate commit tx weight. This commit tx doesn't yet include the
// witness spending the funding output, so we add the (worst case)
// weight for that too.
utx := btcutil.NewTx(commitTx)
weight := blockchain.GetTransactionWeight(utx) + witnessWeight
// Calculate commit tx fee.
fee := chanState.Capacity
for _, out := range commitTx.TxOut {
fee -= btcutil.Amount(out.Value)
}
return &AnchorResolution{
CommitAnchor: *outPoint,
AnchorSignDescriptor: *signDesc,
CommitWeight: lntypes.WeightUnit(weight),
CommitFee: fee,
}, nil
}
// AvailableBalance returns the current balance available for sending within
// the channel. By available balance, we mean that if at this very instance a
// new commitment were to be created which evals all the log entries, what
// would our available balance for adding an additional HTLC be. It takes into
// account the fee that must be paid for adding this HTLC, that we cannot spend
// from the channel reserve and moreover the FeeBuffer when we are the
// initiator of the channel. This method is useful when deciding if a given
// channel can accept an HTLC in the multi-hop forwarding scenario.
func (lc *LightningChannel) AvailableBalance() lnwire.MilliSatoshi {
lc.RLock()
defer lc.RUnlock()
bal, _ := lc.availableBalance(FeeBuffer)
return bal
}
// availableBalance is the private, non mutexed version of AvailableBalance.
// This method is provided so methods that already hold the lock can access
// this method. Additionally, the total weight of the next to be created
// commitment is returned for accounting purposes.
func (lc *LightningChannel) availableBalance(
buffer BufferType) (lnwire.MilliSatoshi, lntypes.WeightUnit) {
// We'll grab the current set of log updates that the remote has
// ACKed.
remoteACKedIndex := lc.commitChains.Local.tip().messageIndices.Remote
htlcView := lc.fetchHTLCView(remoteACKedIndex,
lc.updateLogs.Local.logIndex)
// Calculate our available balance from our local commitment.
// TODO(halseth): could reuse parts validateCommitmentSanity to do this
// balance calculation, as most of the logic is the same.
//
// NOTE: This is not always accurate, since the remote node can always
// add updates concurrently, causing our balance to go down if we're
// the initiator, but this is a problem on the protocol level.
ourLocalCommitBalance, commitWeight := lc.availableCommitmentBalance(
htlcView, lntypes.Local, buffer,
)
// Do the same calculation from the remote commitment point of view.
ourRemoteCommitBalance, _ := lc.availableCommitmentBalance(
htlcView, lntypes.Remote, buffer,
)
// Return which ever balance is lowest.
if ourRemoteCommitBalance < ourLocalCommitBalance {
return ourRemoteCommitBalance, commitWeight
}
return ourLocalCommitBalance, commitWeight
}
// availableCommitmentBalance attempts to calculate the balance we have
// available for HTLCs on the local/remote commitment given the HtlcView. To
// account for sending HTLCs of different sizes, it will report the balance
// available for sending non-dust HTLCs, which will be manifested on the
// commitment, increasing the commitment fee we must pay as an initiator,
// eating into our balance. It will make sure we won't violate the channel
// reserve constraints for this amount.
func (lc *LightningChannel) availableCommitmentBalance(view *HtlcView,
whoseCommitChain lntypes.ChannelParty, buffer BufferType) (
lnwire.MilliSatoshi, lntypes.WeightUnit) {
// Compute the current balances for this commitment. This will take
// into account HTLCs to determine the commit weight, which the
// initiator must pay the fee for.
ourBalance, theirBalance, commitWeight, filteredView, err := lc.computeView(
view, whoseCommitChain, false,
fn.None[chainfee.SatPerKWeight](),
)
if err != nil {
lc.log.Errorf("Unable to fetch available balance: %v", err)
return 0, 0
}
// We can never spend from the channel reserve, so we'll subtract it
// from our available balance.
ourReserve := lnwire.NewMSatFromSatoshis(
lc.channelState.LocalChanCfg.ChanReserve,
)
if ourReserve <= ourBalance {
ourBalance -= ourReserve
} else {
ourBalance = 0
}
// Calculate the commitment fee in the case where we would add another
// HTLC to the commitment, as only the balance remaining after this fee
// has been paid is actually available for sending.
feePerKw := filteredView.FeePerKw
additionalHtlcFee := lnwire.NewMSatFromSatoshis(
feePerKw.FeeForWeight(input.HTLCWeight),
)
commitFee := lnwire.NewMSatFromSatoshis(
feePerKw.FeeForWeight(commitWeight))
if lc.channelState.IsInitiator {
// When the buffer is of type `FeeBuffer` type we know we are
// going to send or forward an htlc over this channel therefore
// we account for an additional htlc output on the commitment
// tx.
futureCommitWeight := commitWeight
if buffer == FeeBuffer {
futureCommitWeight += input.HTLCWeight
}
// Make sure we do not overwrite `ourBalance` that's why we
// declare bufferAmt beforehand.
var bufferAmt lnwire.MilliSatoshi
ourBalance, bufferAmt, commitFee, err = lc.applyCommitFee(
ourBalance, futureCommitWeight, feePerKw, buffer,
)
if err != nil {
lc.log.Debugf("No available local balance after "+
"applying the CommitmentFee of the new "+
"CommitmentState(%v): ourBalance would drop "+
"below the reserve: "+
"ourBalance(w/o reserve)=%v, reserve=%v, "+
"current commitFee(w/o additional htlc)=%v, "+
"feeBuffer=%v (type=%v) "+
"local_chan_initiator=%v", whoseCommitChain,
ourBalance, ourReserve, commitFee, bufferAmt,
buffer, lc.channelState.IsInitiator)
return 0, commitWeight
}
return ourBalance, commitWeight
}
// If we're not the initiator, we must check whether the remote has
// enough balance to pay for the fee of our HTLC. We'll start by also
// subtracting our counterparty's reserve from their balance.
theirReserve := lnwire.NewMSatFromSatoshis(
lc.channelState.RemoteChanCfg.ChanReserve,
)
if theirReserve <= theirBalance {
theirBalance -= theirReserve
} else {
theirBalance = 0
}
// We'll use the dustlimit and htlcFee to find the largest HTLC value
// that will be considered dust on the commitment.
dustlimit := lnwire.NewMSatFromSatoshis(
lc.channelState.LocalChanCfg.DustLimit,
)
// For an extra HTLC fee to be paid on our commitment, the HTLC must be
// large enough to make a non-dust HTLC timeout transaction.
htlcFee := lnwire.NewMSatFromSatoshis(
HtlcTimeoutFee(lc.channelState.ChanType, feePerKw),
)
// If we are looking at the remote commitment, we must use the remote
// dust limit and the fee for adding an HTLC success transaction.
if whoseCommitChain.IsRemote() {
dustlimit = lnwire.NewMSatFromSatoshis(
lc.channelState.RemoteChanCfg.DustLimit,
)
htlcFee = lnwire.NewMSatFromSatoshis(
HtlcSuccessFee(lc.channelState.ChanType, feePerKw),
)
}
// The HTLC output will be manifested on the commitment if it
// is non-dust after paying the HTLC fee.
nonDustHtlcAmt := dustlimit + htlcFee
// commitFeeWithHtlc is the fee our peer has to pay in case we add
// another htlc to the commitment.
commitFeeWithHtlc := commitFee + additionalHtlcFee
// If they cannot pay the fee if we add another non-dust HTLC, we'll
// report our available balance just below the non-dust amount, to
// avoid attempting HTLCs larger than this size.
if theirBalance < commitFeeWithHtlc && ourBalance >= nonDustHtlcAmt {
// see https://github.com/lightning/bolts/issues/728
ourReportedBalance := nonDustHtlcAmt - 1
lc.log.Infof("Reducing local (reported) balance "+
"(from %v to %v): remote side does not have enough "+
"funds (%v < %v) to pay for non-dust HTLC in case of "+
"unilateral close.", ourBalance, ourReportedBalance,
theirBalance, commitFeeWithHtlc)
ourBalance = ourReportedBalance
}
return ourBalance, commitWeight
}
// StateSnapshot returns a snapshot of the current fully committed state within
// the channel.
func (lc *LightningChannel) StateSnapshot() *channeldb.ChannelSnapshot {
lc.RLock()
defer lc.RUnlock()
return lc.channelState.Snapshot()
}
// validateFeeRate ensures that if the passed fee is applied to the channel,
// and a new commitment is created (which evaluates this fee), then the
// initiator of the channel does not dip below their reserve.
func (lc *LightningChannel) validateFeeRate(feePerKw chainfee.SatPerKWeight) error {
// We'll ensure that we can accommodate this new fee change, yet still
// be above our reserve balance. Otherwise, we'll reject the fee
// update.
// We do not enforce the FeeBuffer here because it was exactly
// introduced to use this buffer for potential fee rate increases.
availableBalance, txWeight := lc.availableBalance(AdditionalHtlc)
oldFee := lnwire.NewMSatFromSatoshis(
lc.commitChains.Local.tip().feePerKw.FeeForWeight(txWeight),
)
// Our base balance is the total amount of satoshis we can commit
// towards fees before factoring in the channel reserve.
baseBalance := availableBalance + oldFee
// Using the weight of the commitment transaction if we were to create
// a commitment now, we'll compute our remaining balance if we apply
// this new fee update.
newFee := lnwire.NewMSatFromSatoshis(
feePerKw.FeeForWeight(txWeight),
)
// If the total fee exceeds our available balance (taking into account
// the fee from the last state), then we'll reject this update as it
// would mean we need to trim our entire output.
if newFee > baseBalance {
return fmt.Errorf("cannot apply fee_update=%v sat/kw, new fee "+
"of %v is greater than balance of %v", int64(feePerKw),
newFee, baseBalance)
}
// TODO(halseth): should fail if fee update is unreasonable,
// as specified in BOLT#2.
// * COMMENT(roasbeef): can cross-check with our ideal fee rate
return nil
}
// UpdateFee initiates a fee update for this channel. Must only be called by
// the channel initiator, and must be called before sending update_fee to
// the remote.
func (lc *LightningChannel) UpdateFee(feePerKw chainfee.SatPerKWeight) error {
lc.Lock()
defer lc.Unlock()
// Only initiator can send fee update, so trying to send one as
// non-initiator will fail.
if !lc.channelState.IsInitiator {
return fmt.Errorf("local fee update as non-initiator")
}
// Ensure that the passed fee rate meets our current requirements.
if err := lc.validateFeeRate(feePerKw); err != nil {
return err
}
pd := &paymentDescriptor{
ChanID: lc.ChannelID(),
LogIndex: lc.updateLogs.Local.logIndex,
Amount: lnwire.NewMSatFromSatoshis(btcutil.Amount(feePerKw)),
EntryType: FeeUpdate,
}
lc.updateLogs.Local.appendUpdate(pd)
return nil
}
// CommitFeeTotalAt applies a proposed feerate to the channel and returns the
// commitment fee with this new feerate. It does not modify the underlying
// LightningChannel.
func (lc *LightningChannel) CommitFeeTotalAt(
feePerKw chainfee.SatPerKWeight) (btcutil.Amount, btcutil.Amount,
error) {
lc.RLock()
defer lc.RUnlock()
dryRunFee := fn.Some[chainfee.SatPerKWeight](feePerKw)
// We want to grab every update in both update logs to calculate the
// commitment fees in the worst-case with this fee-rate.
localIdx := lc.updateLogs.Local.logIndex
remoteIdx := lc.updateLogs.Remote.logIndex
localHtlcView := lc.fetchHTLCView(remoteIdx, localIdx)
var localCommitFee, remoteCommitFee btcutil.Amount
// Compute the local commitment's weight.
_, _, localWeight, _, err := lc.computeView(
localHtlcView, lntypes.Local, false, dryRunFee,
)
if err != nil {
return 0, 0, err
}
localCommitFee = feePerKw.FeeForWeight(localWeight)
// Create another view in case for some reason the prior one was
// mutated.
remoteHtlcView := lc.fetchHTLCView(remoteIdx, localIdx)
// Compute the remote commitment's weight.
_, _, remoteWeight, _, err := lc.computeView(
remoteHtlcView, lntypes.Remote, false, dryRunFee,
)
if err != nil {
return 0, 0, err
}
remoteCommitFee = feePerKw.FeeForWeight(remoteWeight)
return localCommitFee, remoteCommitFee, err
}
// ReceiveUpdateFee handles an updated fee sent from remote. This method will
// return an error if called as channel initiator.
func (lc *LightningChannel) ReceiveUpdateFee(feePerKw chainfee.SatPerKWeight) error {
lc.Lock()
defer lc.Unlock()
// Only initiator can send fee update, and we must fail if we receive
// fee update as initiator
if lc.channelState.IsInitiator {
return fmt.Errorf("received fee update as initiator")
}
// TODO(roasbeef): or just modify to use the other balance?
pd := &paymentDescriptor{
ChanID: lc.ChannelID(),
LogIndex: lc.updateLogs.Remote.logIndex,
Amount: lnwire.NewMSatFromSatoshis(btcutil.Amount(feePerKw)),
EntryType: FeeUpdate,
}
lc.updateLogs.Remote.appendUpdate(pd)
return nil
}
// generateRevocation generates the revocation message for a given height.
func (lc *LightningChannel) generateRevocation(height uint64) (*lnwire.RevokeAndAck,
error) {
// Now that we've accept a new state transition, we send the remote
// party the revocation for our current commitment state.
revocationMsg := &lnwire.RevokeAndAck{}
commitSecret, err := lc.channelState.RevocationProducer.AtIndex(height)
if err != nil {
return nil, err
}
copy(revocationMsg.Revocation[:], commitSecret[:])
// Along with this revocation, we'll also send the _next_ commitment
// point that the remote party should use to create our next commitment
// transaction. We use a +2 here as we already gave them a look ahead
// of size one after the ChannelReady message was sent:
//
// 0: current revocation, 1: their "next" revocation, 2: this revocation
//
// We're revoking the current revocation. Once they receive this
// message they'll set the "current" revocation for us to their stored
// "next" revocation, and this revocation will become their new "next"
// revocation.
//
// Put simply in the window slides to the left by one.
revHeight := height + 2
nextCommitSecret, err := lc.channelState.RevocationProducer.AtIndex(
revHeight,
)
if err != nil {
return nil, err
}
revocationMsg.NextRevocationKey = input.ComputeCommitmentPoint(nextCommitSecret[:])
revocationMsg.ChanID = lnwire.NewChanIDFromOutPoint(
lc.channelState.FundingOutpoint,
)
// If this is a taproot channel, then we also need to generate the
// verification nonce for this target state.
if lc.channelState.ChanType.IsTaproot() {
nextVerificationNonce, err := channeldb.NewMusigVerificationNonce( //nolint:ll
lc.channelState.LocalChanCfg.MultiSigKey.PubKey,
revHeight, lc.taprootNonceProducer,
)
if err != nil {
return nil, err
}
revocationMsg.LocalNonce = lnwire.SomeMusig2Nonce(
nextVerificationNonce.PubNonce,
)
}
return revocationMsg, nil
}
// closeTxOpts houses the set of options that modify how the cooperative close
// tx is to be constructed.
type closeTxOpts struct {
// enableRBF indicates whether the cooperative close tx should signal
// RBF or not.
enableRBF bool
// extraCloseOutputs is a set of additional outputs that should be
// added the co-op close transaction.
extraCloseOutputs []CloseOutput
// customSort is a custom function that can be used to sort the
// transaction outputs. If this isn't set, then the default BIP-69
// sorting is used.
customSort CloseSortFunc
// customSequence is an optional custom sequence to set on the co-op
// close transaction. This gives slightly more control compared to the
// enableRBF option.
customSequence fn.Option[uint32]
customLockTime fn.Option[uint32]
}
// defaultCloseTxOpts returns a closeTxOpts struct with default values.
func defaultCloseTxOpts() closeTxOpts {
return closeTxOpts{
enableRBF: false,
}
}
// CloseTxOpt is a functional option that allows us to modify how the closing
// transaction is created.
type CloseTxOpt func(*closeTxOpts)
// WithRBFCloseTx signals that the cooperative close tx should signal RBF.
func WithRBFCloseTx() CloseTxOpt {
return func(o *closeTxOpts) {
o.enableRBF = true
}
}
// WithExtraTxCloseOutputs can be used to add extra outputs to the cooperative
// transaction.
func WithExtraTxCloseOutputs(extraOutputs []CloseOutput) CloseTxOpt {
return func(o *closeTxOpts) {
o.extraCloseOutputs = extraOutputs
}
}
// WithCustomTxSort can be used to modify the way the close transaction is
// sorted.
func WithCustomTxSort(sorter CloseSortFunc) CloseTxOpt {
return func(opts *closeTxOpts) {
opts.customSort = sorter
}
}
// WithCustomTxInSequence allows a caller to set a custom sequence on the sole
// input of the co-op close tx.
func WithCustomTxInSequence(sequence uint32) CloseTxOpt {
return func(o *closeTxOpts) {
o.customSequence = fn.Some(sequence)
}
}
func WithCustomTxLockTime(lockTime uint32) CloseTxOpt {
return func(o *closeTxOpts) {
o.customLockTime = fn.Some(lockTime)
}
}
// CreateCooperativeCloseTx creates a transaction which if signed by both
// parties, then broadcast cooperatively closes an active channel. The creation
// of the closure transaction is modified by a boolean indicating if the party
// constructing the channel is the initiator of the closure. Currently it is
// expected that the initiator pays the transaction fees for the closing
// transaction in full.
func CreateCooperativeCloseTx(fundingTxIn wire.TxIn,
localDust, remoteDust, ourBalance, theirBalance btcutil.Amount,
ourDeliveryScript, theirDeliveryScript []byte,
closeOpts ...CloseTxOpt) (*wire.MsgTx, error) {
opts := defaultCloseTxOpts()
for _, optFunc := range closeOpts {
optFunc(&opts)
}
// If RBF is signalled, then we'll modify the sequence to permit
// replacement.
if opts.enableRBF {
fundingTxIn.Sequence = mempool.MaxRBFSequence
}
// Otherwise, a custom sequence might be specified.
opts.customSequence.WhenSome(func(sequence uint32) {
fundingTxIn.Sequence = sequence
})
// Construct the transaction to perform a cooperative closure of the
// channel. In the event that one side doesn't have any settled funds
// within the channel then a refund output for that particular side can
// be omitted.
closeTx := wire.NewMsgTx(2)
closeTx.AddTxIn(&fundingTxIn)
opts.customLockTime.WhenSome(func(lockTime uint32) {
closeTx.LockTime = lockTime
})
// Create both cooperative closure outputs, properly respecting the dust
// limits of both parties.
var localOutputIdx fn.Option[int]
haveLocalOutput := ourBalance >= localDust
if haveLocalOutput {
// If our script is an OP_RETURN, then we set our balance to
// zero.
if opts.customSequence.IsSome() &&
input.ScriptIsOpReturn(ourDeliveryScript) {
ourBalance = 0
}
closeTx.AddTxOut(&wire.TxOut{
PkScript: ourDeliveryScript,
Value: int64(ourBalance),
})
localOutputIdx = fn.Some(len(closeTx.TxOut) - 1)
}
var remoteOutputIdx fn.Option[int]
haveRemoteOutput := theirBalance >= remoteDust
if haveRemoteOutput {
// If a party's script is an OP_RETURN, then we set their
// balance to zero.
if opts.customSequence.IsSome() &&
input.ScriptIsOpReturn(theirDeliveryScript) {
theirBalance = 0
}
closeTx.AddTxOut(&wire.TxOut{
PkScript: theirDeliveryScript,
Value: int64(theirBalance),
})
remoteOutputIdx = fn.Some(len(closeTx.TxOut) - 1)
}
// If we have extra outputs to add to the co-op close transaction, then
// we'll examine them now. We'll deduct the output's value from the
// owning party. In the case that a party can't pay for the output, then
// their normal output will be omitted.
for _, extraTxOut := range opts.extraCloseOutputs {
switch {
// For additional local outputs, add the output, then deduct
// the balance from our local balance.
case extraTxOut.IsLocal:
// The extraCloseOutputs in the options just indicate if
// an extra output should be added in general. But we
// only add one if we actually _need_ one, based on the
// balance. If we don't have enough local balance to
// cover the extra output, then localOutputIdx is None.
localOutputIdx.WhenSome(func(idx int) {
// The output that currently represents the
// local balance, which means:
// txOut.Value == ourBalance.
txOut := closeTx.TxOut[idx]
// The extra output (if one exists) is the more
// important one, as in custom channels it might
// carry some additional values. The normal
// output is just an address that sends the
// local balance back to our wallet. The extra
// one also goes to our wallet, but might also
// carry other values, so it has higher
// priority. Do we have enough balance to have
// both the extra output with the given value
// (which is subtracted from our balance) and
// still an above-dust normal output? If not, we
// skip the extra output and just overwrite the
// existing output script with the one from the
// extra output.
amtAfterOutput := btcutil.Amount(
txOut.Value - extraTxOut.Value,
)
if amtAfterOutput <= localDust {
txOut.PkScript = extraTxOut.PkScript
return
}
txOut.Value -= extraTxOut.Value
closeTx.AddTxOut(&extraTxOut.TxOut)
})
// For extra remote outputs, we'll do the opposite.
case !extraTxOut.IsLocal:
// The extraCloseOutputs in the options just indicate if
// an extra output should be added in general. But we
// only add one if we actually _need_ one, based on the
// balance. If we don't have enough remote balance to
// cover the extra output, then remoteOutputIdx is None.
remoteOutputIdx.WhenSome(func(idx int) {
// The output that currently represents the
// remote balance, which means:
// txOut.Value == theirBalance.
txOut := closeTx.TxOut[idx]
// The extra output (if one exists) is the more
// important one, as in custom channels it might
// carry some additional values. The normal
// output is just an address that sends the
// remote balance back to their wallet. The
// extra one also goes to their wallet, but
// might also carry other values, so it has
// higher priority. Do they have enough balance
// to have both the extra output with the given
// value (which is subtracted from their
// balance) and still an above-dust normal
// output? If not, we skip the extra output and
// just overwrite the existing output script
// with the one from the extra output.
amtAfterOutput := btcutil.Amount(
txOut.Value - extraTxOut.Value,
)
if amtAfterOutput <= remoteDust {
txOut.PkScript = extraTxOut.PkScript
return
}
txOut.Value -= extraTxOut.Value
closeTx.AddTxOut(&extraTxOut.TxOut)
})
}
}
if opts.customSort != nil {
if err := opts.customSort(closeTx); err != nil {
return nil, err
}
} else {
txsort.InPlaceSort(closeTx)
}
return closeTx, nil
}
// LocalBalanceDust returns true if when creating a co-op close transaction,
// the balance of the local party will be dust after accounting for any anchor
// outputs.
func (lc *LightningChannel) LocalBalanceDust() (bool, btcutil.Amount) {
lc.RLock()
defer lc.RUnlock()
chanState := lc.channelState
localBalance := chanState.LocalCommitment.LocalBalance.ToSatoshis()
// If this is an anchor channel, and we're the initiator, then we'll
// regain the stats allocated to the anchor outputs with the co-op
// close transaction.
if chanState.ChanType.HasAnchors() && chanState.IsInitiator {
localBalance += 2 * AnchorSize
}
localDust := chanState.LocalChanCfg.DustLimit
return localBalance <= localDust, localDust
}
// RemoteBalanceDust returns true if when creating a co-op close transaction,
// the balance of the remote party will be dust after accounting for any anchor
// outputs.
func (lc *LightningChannel) RemoteBalanceDust() (bool, btcutil.Amount) {
lc.RLock()
defer lc.RUnlock()
chanState := lc.channelState
remoteBalance := chanState.RemoteCommitment.RemoteBalance.ToSatoshis()
// If this is an anchor channel, and they're the initiator, then we'll
// regain the stats allocated to the anchor outputs with the co-op
// close transaction.
if chanState.ChanType.HasAnchors() && !chanState.IsInitiator {
remoteBalance += 2 * AnchorSize
}
remoteDust := chanState.RemoteChanCfg.DustLimit
return remoteBalance <= remoteDust, remoteDust
}
// CommitBalances returns the local and remote balances in the current
// commitment state.
func (lc *LightningChannel) CommitBalances() (btcutil.Amount, btcutil.Amount) {
lc.RLock()
defer lc.RUnlock()
chanState := lc.channelState
localCommit := lc.channelState.LocalCommitment
localBalance := localCommit.LocalBalance.ToSatoshis()
remoteBalance := localCommit.RemoteBalance.ToSatoshis()
if chanState.ChanType.HasAnchors() {
if chanState.IsInitiator {
localBalance += 2 * AnchorSize
} else {
remoteBalance += 2 * AnchorSize
}
}
return localBalance, remoteBalance
}
// CommitFee returns the commitment fee for the current commitment state.
func (lc *LightningChannel) CommitFee() btcutil.Amount {
lc.RLock()
defer lc.RUnlock()
return lc.channelState.LocalCommitment.CommitFee
}
// CalcFee returns the commitment fee to use for the given fee rate
// (fee-per-kw).
func (lc *LightningChannel) CalcFee(feeRate chainfee.SatPerKWeight) btcutil.Amount {
return feeRate.FeeForWeight(CommitWeight(lc.channelState.ChanType))
}
// MaxFeeRate returns the maximum fee rate given an allocation of the channel
// initiator's spendable balance along with the local reserve amount. This can
// be useful to determine when we should stop proposing fee updates that exceed
// our maximum allocation.
// Moreover it returns the share of the total balance in the range of [0,1]
// which can be allocated to fees. When our desired fee allocation would lead to
// a maximum fee rate below the current commitment fee rate we floor the maximum
// at the current fee rate which leads to different fee allocations than
// initially requested via `maxAllocation`.
//
// NOTE: This should only be used for channels in which the local commitment is
// the initiator.
func (lc *LightningChannel) MaxFeeRate(
maxAllocation float64) (chainfee.SatPerKWeight, float64) {
lc.RLock()
defer lc.RUnlock()
// The maximum fee depends on the available balance that can be
// committed towards fees. It takes into account our local reserve
// balance. We do not account for a FeeBuffer here because that is
// exactly why it was introduced to react for sharp fee changes.
availableBalance, weight := lc.availableBalance(AdditionalHtlc)
currentFee := lc.commitChains.Local.tip().feePerKw.FeeForWeight(weight)
// baseBalance is the maximum amount available for us to spend on fees.
baseBalance := availableBalance.ToSatoshis() + currentFee
// In case our local channel balance is drained, we make sure we do not
// decrease the fee rate below the current fee rate. This could lead to
// a scenario where we lower the commitment fee rate as low as the fee
// floor although current fee rates are way higher. The maximum fee
// we allow should not be smaller then the current fee. The decrease
// in fee rate should happen when the mempool reports lower fee levels
// rather than us decreasing in local balance. The max fee rate is
// always floored by the current fee rate of the channel.
idealMaxFee := float64(baseBalance) * maxAllocation
maxFee := max(float64(currentFee), idealMaxFee)
maxFeeAllocation := maxFee / float64(baseBalance)
maxFeeRate := chainfee.SatPerKWeight(maxFee / (float64(weight) / 1000))
return maxFeeRate, maxFeeAllocation
}
// IdealCommitFeeRate uses the current network fee, the minimum relay fee,
// maximum fee allocation and anchor channel commitment fee rate to determine
// the ideal fee to be used for the commitments of the channel.
func (lc *LightningChannel) IdealCommitFeeRate(netFeeRate, minRelayFeeRate,
maxAnchorCommitFeeRate chainfee.SatPerKWeight,
maxFeeAlloc float64) chainfee.SatPerKWeight {
// Get the maximum fee rate that we can use given our max fee allocation
// and given the local reserve balance that we must preserve.
maxFeeRate, _ := lc.MaxFeeRate(maxFeeAlloc)
var commitFeeRate chainfee.SatPerKWeight
// If the channel has anchor outputs then cap the fee rate at the
// max anchor fee rate if that maximum is less than our max fee rate.
// Otherwise, cap the fee rate at the max fee rate.
switch lc.channelState.ChanType.HasAnchors() &&
maxFeeRate > maxAnchorCommitFeeRate {
case true:
commitFeeRate = min(netFeeRate, maxAnchorCommitFeeRate)
case false:
commitFeeRate = min(netFeeRate, maxFeeRate)
}
if commitFeeRate >= minRelayFeeRate {
return commitFeeRate
}
// The commitment fee rate is below the minimum relay fee rate.
// If the min relay fee rate is still below the maximum fee, then use
// the minimum relay fee rate.
if minRelayFeeRate <= maxFeeRate {
return minRelayFeeRate
}
// The minimum relay fee rate is more than the ideal maximum fee rate.
// Check if it is smaller than the absolute maximum fee rate we can
// use. If it is, then we use the minimum relay fee rate and we log a
// warning to indicate that the max channel fee allocation option was
// ignored.
absoluteMaxFee, _ := lc.MaxFeeRate(1)
if minRelayFeeRate <= absoluteMaxFee {
lc.log.Warn("Ignoring max channel fee allocation to " +
"ensure that the commitment fee is above the " +
"minimum relay fee.")
return minRelayFeeRate
}
// The absolute maximum fee rate we can pay is below the minimum
// relay fee rate. The commitment tx will not be able to propagate.
// To give the transaction the best chance, we use the absolute
// maximum fee we have available and we log an error.
lc.log.Errorf("The commitment fee rate of %s is below the current "+
"minimum relay fee rate of %s. The max fee rate of %s will be "+
"used.", commitFeeRate, minRelayFeeRate, absoluteMaxFee)
return absoluteMaxFee
}
// RemoteNextRevocation returns the channelState's RemoteNextRevocation. For
// musig2 channels, until a nonce pair is processed by the remote party, a nil
// public key is returned.
//
// TODO(roasbeef): revisit, maybe just make a more general method instead?
func (lc *LightningChannel) RemoteNextRevocation() *btcec.PublicKey {
lc.RLock()
defer lc.RUnlock()
if !lc.channelState.ChanType.IsTaproot() {
return lc.channelState.RemoteNextRevocation
}
if lc.musigSessions == nil {
return nil
}
return lc.channelState.RemoteNextRevocation
}
// IsInitiator returns true if we were the ones that initiated the funding
// workflow which led to the creation of this channel. Otherwise, it returns
// false.
func (lc *LightningChannel) IsInitiator() bool {
lc.RLock()
defer lc.RUnlock()
return lc.channelState.IsInitiator
}
// CommitFeeRate returns the current fee rate of the commitment transaction in
// units of sat-per-kw.
func (lc *LightningChannel) CommitFeeRate() chainfee.SatPerKWeight {
lc.RLock()
defer lc.RUnlock()
return chainfee.SatPerKWeight(lc.channelState.LocalCommitment.FeePerKw)
}
// WorstCaseFeeRate returns the higher feerate from either the local commitment
// or the remote commitment.
func (lc *LightningChannel) WorstCaseFeeRate() chainfee.SatPerKWeight {
lc.RLock()
defer lc.RUnlock()
localFeeRate := lc.channelState.LocalCommitment.FeePerKw
remoteFeeRate := lc.channelState.RemoteCommitment.FeePerKw
if localFeeRate > remoteFeeRate {
return chainfee.SatPerKWeight(localFeeRate)
}
return chainfee.SatPerKWeight(remoteFeeRate)
}
// IsPending returns true if the channel's funding transaction has been fully
// confirmed, and false otherwise.
func (lc *LightningChannel) IsPending() bool {
lc.RLock()
defer lc.RUnlock()
return lc.channelState.IsPending
}
// State provides access to the channel's internal state.
func (lc *LightningChannel) State() *channeldb.OpenChannel {
return lc.channelState
}
// MarkBorked marks the event when the channel as reached an irreconcilable
// state, such as a channel breach or state desynchronization. Borked channels
// should never be added to the switch.
func (lc *LightningChannel) MarkBorked() error {
lc.Lock()
defer lc.Unlock()
return lc.channelState.MarkBorked()
}
// MarkCommitmentBroadcasted marks the channel as a commitment transaction has
// been broadcast, either our own or the remote, and we should watch the chain
// for it to confirm before taking any further action. It takes a boolean which
// indicates whether we initiated the close.
func (lc *LightningChannel) MarkCommitmentBroadcasted(tx *wire.MsgTx,
closer lntypes.ChannelParty) error {
lc.Lock()
defer lc.Unlock()
return lc.channelState.MarkCommitmentBroadcasted(tx, closer)
}
// MarkCoopBroadcasted marks the channel as a cooperative close transaction has
// been broadcast, and that we should watch the chain for it to confirm before
// taking any further action. It takes a locally initiated bool which is true
// if we initiated the cooperative close.
func (lc *LightningChannel) MarkCoopBroadcasted(tx *wire.MsgTx,
closer lntypes.ChannelParty) error {
lc.Lock()
defer lc.Unlock()
return lc.channelState.MarkCoopBroadcasted(tx, closer)
}
// MarkShutdownSent persists the given ShutdownInfo. The existence of the
// ShutdownInfo represents the fact that the Shutdown message has been sent by
// us and so should be re-sent on re-establish.
func (lc *LightningChannel) MarkShutdownSent(
info *channeldb.ShutdownInfo) error {
lc.Lock()
defer lc.Unlock()
return lc.channelState.MarkShutdownSent(info)
}
// MarkDataLoss marks sets the channel status to LocalDataLoss and stores the
// passed commitPoint for use to retrieve funds in case the remote force closes
// the channel.
func (lc *LightningChannel) MarkDataLoss(commitPoint *btcec.PublicKey) error {
lc.Lock()
defer lc.Unlock()
return lc.channelState.MarkDataLoss(commitPoint)
}
// ActiveHtlcs returns a slice of HTLC's which are currently active on *both*
// commitment transactions.
func (lc *LightningChannel) ActiveHtlcs() []channeldb.HTLC {
lc.RLock()
defer lc.RUnlock()
return lc.channelState.ActiveHtlcs()
}
// LocalChanReserve returns our local ChanReserve requirement for the remote party.
func (lc *LightningChannel) LocalChanReserve() btcutil.Amount {
return lc.channelState.LocalChanCfg.ChanReserve
}
// NextLocalHtlcIndex returns the next unallocated local htlc index. To ensure
// this always returns the next index that has been not been allocated, this
// will first try to examine any pending commitments, before falling back to the
// last locked-in local commitment.
func (lc *LightningChannel) NextLocalHtlcIndex() (uint64, error) {
lc.RLock()
defer lc.RUnlock()
return lc.channelState.NextLocalHtlcIndex()
}
// FwdMinHtlc returns the minimum HTLC value required by the remote node, i.e.
// the minimum value HTLC we can forward on this channel.
func (lc *LightningChannel) FwdMinHtlc() lnwire.MilliSatoshi {
return lc.channelState.LocalChanCfg.MinHTLC
}
// unsignedLocalUpdates retrieves the unsigned local updates that we should
// store upon receiving a revocation. This function is called from
// ReceiveRevocation. remoteMessageIndex is the height into the local update
// log that the remote commitment chain tip includes. localMessageIndex
// is the height into the local update log that the local commitment tail
// includes. Our local updates that are unsigned by the remote should
// have height greater than or equal to localMessageIndex (not on our commit),
// and height less than remoteMessageIndex (on the remote commit).
//
// NOTE: remoteMessageIndex is the height on the tip because this is called
// before the tail is advanced to the tip during ReceiveRevocation.
func (lc *LightningChannel) unsignedLocalUpdates(remoteMessageIndex,
localMessageIndex uint64) []channeldb.LogUpdate {
var localPeerUpdates []channeldb.LogUpdate
for e := lc.updateLogs.Local.Front(); e != nil; e = e.Next() {
pd := e.Value
// We don't save add updates as they are restored from the
// remote commitment in restoreStateLogs.
if pd.EntryType == Add {
continue
}
// This is a settle/fail that is on the remote commitment, but
// not on the local commitment. We expect this update to be
// covered in the next commitment signature that the remote
// sends.
if pd.LogIndex < remoteMessageIndex && pd.LogIndex >= localMessageIndex {
localPeerUpdates = append(
localPeerUpdates, pd.toLogUpdate(),
)
}
}
return localPeerUpdates
}
// GenMusigNonces generates the verification nonce to start off a new musig2
// channel session.
func (lc *LightningChannel) GenMusigNonces() (*musig2.Nonces, error) {
lc.Lock()
defer lc.Unlock()
var err error
// We pass in the current height+1 as this'll be the set of
// verification nonces we'll send to the party to create our _next_
// state.
lc.pendingVerificationNonce, err = channeldb.NewMusigVerificationNonce(
lc.channelState.LocalChanCfg.MultiSigKey.PubKey,
lc.currentHeight+1, lc.taprootNonceProducer,
)
if err != nil {
return nil, err
}
return lc.pendingVerificationNonce, nil
}
// HasRemoteNonces returns true if the channel has a remote nonce pair.
func (lc *LightningChannel) HasRemoteNonces() bool {
return lc.musigSessions != nil
}
// InitRemoteMusigNonces processes the remote musig nonces sent by the remote
// party. This should be called upon connection re-establishment, after we've
// generated our own nonces. Once this method returns a nil error, then the
// channel can be used to sign commitment states.
func (lc *LightningChannel) InitRemoteMusigNonces(remoteNonce *musig2.Nonces,
) error {
lc.Lock()
defer lc.Unlock()
if lc.pendingVerificationNonce == nil {
return fmt.Errorf("pending verification nonce is not set")
}
// Now that we have the set of local and remote nonces, we can generate
// a new pair of musig sessions for our local commitment and the
// commitment of the remote party.
localNonce := lc.pendingVerificationNonce
localChanCfg := lc.channelState.LocalChanCfg
remoteChanCfg := lc.channelState.RemoteChanCfg
// TODO(roasbeef): propagate rename of signing and verification nonces
sessionCfg := &MusigSessionCfg{
LocalKey: localChanCfg.MultiSigKey,
RemoteKey: remoteChanCfg.MultiSigKey,
LocalNonce: *localNonce,
RemoteNonce: *remoteNonce,
Signer: lc.Signer,
InputTxOut: &lc.fundingOutput,
TapscriptTweak: lc.channelState.TapscriptRoot,
}
lc.musigSessions = NewMusigPairSession(
sessionCfg,
)
lc.pendingVerificationNonce = nil
lc.opts.localNonce = nil
lc.opts.remoteNonce = nil
return nil
}
// ChanType returns the channel type.
func (lc *LightningChannel) ChanType() channeldb.ChannelType {
lc.RLock()
defer lc.RUnlock()
return lc.channelState.ChanType
}
// Initiator returns the ChannelParty that originally opened this channel.
func (lc *LightningChannel) Initiator() lntypes.ChannelParty {
lc.RLock()
defer lc.RUnlock()
return lc.channelState.Initiator()
}
// FundingTxOut returns the funding output of the channel.
func (lc *LightningChannel) FundingTxOut() *wire.TxOut {
lc.RLock()
defer lc.RUnlock()
return &lc.fundingOutput
}
// DeriveHeightHint derives the block height for the channel opening.
func (lc *LightningChannel) DeriveHeightHint() uint32 {
lc.RLock()
defer lc.RUnlock()
return lc.channelState.DeriveHeightHint()
}
// MultiSigKeys returns the set of multi-sig keys for an channel.
func (lc *LightningChannel) MultiSigKeys() (keychain.KeyDescriptor,
keychain.KeyDescriptor) {
lc.RLock()
defer lc.RUnlock()
return lc.channelState.LocalChanCfg.MultiSigKey,
lc.channelState.RemoteChanCfg.MultiSigKey
}
// LocalCommitmentBlob returns the custom blob of the local commitment.
func (lc *LightningChannel) LocalCommitmentBlob() fn.Option[tlv.Blob] {
lc.RLock()
defer lc.RUnlock()
chanState := lc.channelState
localBalance := chanState.LocalCommitment.CustomBlob
return fn.MapOption(func(b tlv.Blob) tlv.Blob {
newBlob := make([]byte, len(b))
copy(newBlob, b)
return newBlob
})(localBalance)
}
// FundingBlob returns the funding custom blob.
func (lc *LightningChannel) FundingBlob() fn.Option[tlv.Blob] {
lc.RLock()
defer lc.RUnlock()
return fn.MapOption(func(b tlv.Blob) tlv.Blob {
newBlob := make([]byte, len(b))
copy(newBlob, b)
return newBlob
})(lc.channelState.CustomBlob)
}
// ZeroConfRealScid returns an optional real scid for the channel. If this
// returns None, then this isn't a zero conf channel. Otherwise, the real scid
// value will be returned.
//
//nolint:ll
func (lc *LightningChannel) ZeroConfRealScid() fn.Option[lnwire.ShortChannelID] {
if lc.channelState.IsZeroConf() {
return fn.Some(lc.channelState.ZeroConfRealScid())
}
return fn.None[lnwire.ShortChannelID]()
}
package chanvalidate
import (
"bytes"
"fmt"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// ErrInvalidOutPoint is returned when the ChanLocator is unable to
// find the target outpoint.
ErrInvalidOutPoint = fmt.Errorf("output meant to create channel cannot " +
"be found")
// ErrWrongPkScript is returned when the alleged funding transaction is
// found to have an incorrect pkSript.
ErrWrongPkScript = fmt.Errorf("wrong pk script")
// ErrInvalidSize is returned when the alleged funding transaction
// output has the wrong size (channel capacity).
ErrInvalidSize = fmt.Errorf("channel has wrong size")
)
// ErrScriptValidateError is returned when Script VM validation fails for an
// alleged channel output.
type ErrScriptValidateError struct {
err error
}
// Error returns a human readable string describing the error.
func (e *ErrScriptValidateError) Error() string {
return fmt.Sprintf("script validation failed: %v", e.err)
}
// Unwrap returns the underlying wrapped VM execution failure error.
func (e *ErrScriptValidateError) Unwrap() error {
return e.err
}
// ChanLocator abstracts away obtaining the output that created the channel, as
// well as validating its existence given the funding transaction. We need
// this as there are several ways (outpoint, short chan ID) to identify the
// output of a channel given the funding transaction.
type ChanLocator interface {
// Locate attempts to locate the funding output within the funding
// transaction. It also returns the final out point of the channel
// which uniquely identifies the output which creates the channel. If
// the target output cannot be found, or cannot exist on the funding
// transaction, then an error is to be returned.
Locate(*wire.MsgTx) (*wire.TxOut, *wire.OutPoint, error)
}
// OutPointChanLocator is an implementation of the ChanLocator that can be used
// when one already knows the expected chan point.
type OutPointChanLocator struct {
// ChanPoint is the expected chan point.
ChanPoint wire.OutPoint
}
// Locate attempts to locate the funding output within the passed funding
// transaction.
//
// NOTE: Part of the ChanLocator interface.
func (o *OutPointChanLocator) Locate(fundingTx *wire.MsgTx) (
*wire.TxOut, *wire.OutPoint, error) {
// If the expected index is greater than the amount of output in the
// transaction, then we'll reject this channel as it's invalid.
if int(o.ChanPoint.Index) >= len(fundingTx.TxOut) {
return nil, nil, ErrInvalidOutPoint
}
// As an extra sanity check, we'll also ensure the txid hash matches.
fundingHash := fundingTx.TxHash()
if !bytes.Equal(fundingHash[:], o.ChanPoint.Hash[:]) {
return nil, nil, ErrInvalidOutPoint
}
return fundingTx.TxOut[o.ChanPoint.Index], &o.ChanPoint, nil
}
// ShortChanIDChanLocator is an implementation of the ChanLocator that can be
// used when one only knows the short channel ID of a channel. This should be
// used in contexts when one is verifying a 3rd party channel.
type ShortChanIDChanLocator struct {
// ID is the short channel ID of the target channel.
ID lnwire.ShortChannelID
}
// Locate attempts to locate the funding output within the passed funding
// transaction.
//
// NOTE: Part of the ChanLocator interface.
func (s *ShortChanIDChanLocator) Locate(fundingTx *wire.MsgTx) (
*wire.TxOut, *wire.OutPoint, error) {
// If the expected index is greater than the amount of output in the
// transaction, then we'll reject this channel as it's invalid.
outputIndex := s.ID.TxPosition
if int(outputIndex) >= len(fundingTx.TxOut) {
return nil, nil, ErrInvalidOutPoint
}
chanPoint := wire.OutPoint{
Hash: fundingTx.TxHash(),
Index: uint32(outputIndex),
}
return fundingTx.TxOut[outputIndex], &chanPoint, nil
}
// CommitmentContext is optional validation context that can be passed into the
// main Validate for self-owned channel. The information in this context allows
// us to fully verify out initial commitment spend based on the on-chain state
// of the funding output.
type CommitmentContext struct {
// Value is the known size of the channel.
Value btcutil.Amount
// FullySignedCommitTx is the fully signed commitment transaction. This
// should include a valid witness.
FullySignedCommitTx *wire.MsgTx
}
// Context is the main validation contxet. For a given channel, all fields but
// the optional CommitCtx should be populated based on existing
// known-to-be-valid parameters.
type Context struct {
// Locator is a concrete implementation of the ChanLocator interface.
Locator ChanLocator
// MultiSigPkScript is the fully serialized witness script of the
// multi-sig output. This is the final witness program that should be
// found in the funding output.
MultiSigPkScript []byte
// FundingTx is channel funding transaction as found confirmed in the
// chain.
FundingTx *wire.MsgTx
// CommitCtx is an optional additional set of validation context
// required to validate a self-owned channel. If present, then a full
// Script VM validation will be performed.
CommitCtx *CommitmentContext
}
// Validate given the specified context, this function validates that the
// alleged channel is well formed, and spendable (if the optional CommitCtx is
// specified). If this method returns an error, then the alleged channel is
// invalid and should be abandoned immediately.
func Validate(ctx *Context) (*wire.OutPoint, error) {
// First, we'll attempt to locate the target outpoint in the funding
// transaction. If this returns an error, then we know that the
// outpoint doesn't actually exist, so we'll exit early.
fundingOutput, chanPoint, err := ctx.Locator.Locate(
ctx.FundingTx,
)
if err != nil {
return nil, err
}
// The scripts should match up exactly, otherwise the channel is
// invalid.
fundingScript := fundingOutput.PkScript
if !bytes.Equal(ctx.MultiSigPkScript, fundingScript) {
return nil, ErrWrongPkScript
}
// If there's no commitment context, then we're done here as this is a
// 3rd party channel.
if ctx.CommitCtx == nil {
return chanPoint, nil
}
// Now that we know this is our channel, we'll verify the amount of the
// created output against our expected size of the channel.
fundingValue := fundingOutput.Value
if btcutil.Amount(fundingValue) != ctx.CommitCtx.Value {
return nil, ErrInvalidSize
}
// If we reach this point, then all other checks have succeeded, so
// we'll now attempt a full Script VM execution to ensure that we're
// able to close the channel using this initial state.
prevFetcher := txscript.NewCannedPrevOutputFetcher(
ctx.MultiSigPkScript, fundingValue,
)
commitTx := ctx.CommitCtx.FullySignedCommitTx
hashCache := txscript.NewTxSigHashes(commitTx, prevFetcher)
vm, err := txscript.NewEngine(
ctx.MultiSigPkScript, commitTx, 0, txscript.StandardVerifyFlags,
nil, hashCache, fundingValue, prevFetcher,
)
if err != nil {
return nil, err
}
// Finally, we'll attempt to verify our full spend, if this fails then
// the channel is definitely invalid.
err = vm.Execute()
if err != nil {
return nil, &ErrScriptValidateError{err: err}
}
return chanPoint, nil
}
package lnwallet
import (
"bytes"
"sort"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
)
// InPlaceCommitSort performs an in-place sort of a commitment transaction,
// given an unsorted transaction and a list of CLTV values for the HTLCs.
//
// The sort applied is a modified BIP69 sort, that uses the CLTV values of HTLCs
// as a tie breaker in case two HTLC outputs have an identical amount and
// pkscript. The pkscripts can be the same if they share the same payment hash,
// but since the CLTV is enforced via the nLockTime of the second-layer
// transactions, the script does not directly commit to them. Instead, the CLTVs
// must be supplied separately to act as a tie-breaker, otherwise we may produce
// invalid HTLC signatures if the receiver produces an alternative ordering
// during verification.
//
// NOTE: Commitment outputs should have a 0 CLTV corresponding to their index on
// the unsorted commitment transaction.
func InPlaceCommitSort(tx *wire.MsgTx, cltvs []uint32) {
if len(tx.TxOut) != len(cltvs) {
panic("output and cltv list size mismatch")
}
sort.Sort(sortableInputSlice(tx.TxIn))
sort.Sort(sortableCommitOutputSlice{tx.TxOut, cltvs})
}
// sortableInputSlice is a slice of transaction inputs that supports sorting via
// BIP69.
type sortableInputSlice []*wire.TxIn
// Len returns the length of the sortableInputSlice.
//
// NOTE: Part of the sort.Interface interface.
func (s sortableInputSlice) Len() int { return len(s) }
// Swap exchanges the position of inputs i and j.
//
// NOTE: Part of the sort.Interface interface.
func (s sortableInputSlice) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Less is the BIP69 input comparison function. The sort is first applied on
// input hash (reversed / rpc-style), then index. This logic is copied from
// btcutil/txsort.
//
// NOTE: Part of the sort.Interface interface.
func (s sortableInputSlice) Less(i, j int) bool {
// Input hashes are the same, so compare the index.
ihash := s[i].PreviousOutPoint.Hash
jhash := s[j].PreviousOutPoint.Hash
if ihash == jhash {
return s[i].PreviousOutPoint.Index < s[j].PreviousOutPoint.Index
}
// At this point, the hashes are not equal, so reverse them to
// big-endian and return the result of the comparison.
const hashSize = chainhash.HashSize
for b := 0; b < hashSize/2; b++ {
ihash[b], ihash[hashSize-1-b] = ihash[hashSize-1-b], ihash[b]
jhash[b], jhash[hashSize-1-b] = jhash[hashSize-1-b], jhash[b]
}
return bytes.Compare(ihash[:], jhash[:]) == -1
}
// sortableCommitOutputSlice is a slice of transaction outputs on a commitment
// transaction and the corresponding CLTV values of any HTLCs. Commitment
// outputs should have a CLTV of 0 and the same index in cltvs.
type sortableCommitOutputSlice struct {
txouts []*wire.TxOut
cltvs []uint32
}
// Len returns the length of the sortableCommitOutputSlice.
//
// NOTE: Part of the sort.Interface interface.
func (s sortableCommitOutputSlice) Len() int {
return len(s.txouts)
}
// Swap exchanges the position of outputs i and j, as well as their
// corresponding CLTV values.
//
// NOTE: Part of the sort.Interface interface.
func (s sortableCommitOutputSlice) Swap(i, j int) {
s.txouts[i], s.txouts[j] = s.txouts[j], s.txouts[i]
s.cltvs[i], s.cltvs[j] = s.cltvs[j], s.cltvs[i]
}
// Less is a modified BIP69 output comparison, that sorts based on value, then
// pkscript, then CLTV value.
//
// NOTE: Part of the sort.Interface interface.
func (s sortableCommitOutputSlice) Less(i, j int) bool {
outi, outj := s.txouts[i], s.txouts[j]
if outi.Value != outj.Value {
return outi.Value < outj.Value
}
pkScriptCmp := bytes.Compare(outi.PkScript, outj.PkScript)
if pkScriptCmp != 0 {
return pkScriptCmp < 0
}
return s.cltvs[i] < s.cltvs[j]
}
package lnwallet
import (
"bytes"
"fmt"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
)
// AnchorSize is the constant anchor output size.
const AnchorSize = btcutil.Amount(330)
// DefaultAnchorsCommitMaxFeeRateSatPerVByte is the default max fee rate in
// sat/vbyte the initiator will use for anchor channels. This should be enough
// to ensure propagation before anchoring down the commitment transaction.
const DefaultAnchorsCommitMaxFeeRateSatPerVByte = 10
// CommitmentKeyRing holds all derived keys needed to construct commitment and
// HTLC transactions. The keys are derived differently depending whether the
// commitment transaction is ours or the remote peer's. Private keys associated
// with each key may belong to the commitment owner or the "other party" which
// is referred to in the field comments, regardless of which is local and which
// is remote.
type CommitmentKeyRing struct {
// CommitPoint is the "per commitment point" used to derive the tweak
// for each base point.
CommitPoint *btcec.PublicKey
// LocalCommitKeyTweak is the tweak used to derive the local public key
// from the local payment base point or the local private key from the
// base point secret. This may be included in a SignDescriptor to
// generate signatures for the local payment key.
//
// NOTE: This will always refer to "our" local key, regardless of
// whether this is our commit or not.
LocalCommitKeyTweak []byte
// TODO(roasbeef): need delay tweak as well?
// LocalHtlcKeyTweak is the tweak used to derive the local HTLC key
// from the local HTLC base point. This value is needed in order to
// derive the final key used within the HTLC scripts in the commitment
// transaction.
//
// NOTE: This will always refer to "our" local HTLC key, regardless of
// whether this is our commit or not.
LocalHtlcKeyTweak []byte
// LocalHtlcKey is the key that will be used in any clause paying to
// our node of any HTLC scripts within the commitment transaction for
// this key ring set.
//
// NOTE: This will always refer to "our" local HTLC key, regardless of
// whether this is our commit or not.
LocalHtlcKey *btcec.PublicKey
// RemoteHtlcKey is the key that will be used in clauses within the
// HTLC script that send money to the remote party.
//
// NOTE: This will always refer to "their" remote HTLC key, regardless
// of whether this is our commit or not.
RemoteHtlcKey *btcec.PublicKey
// ToLocalKey is the commitment transaction owner's key which is
// included in HTLC success and timeout transaction scripts. This is
// the public key used for the to_local output of the commitment
// transaction.
//
// NOTE: Who's key this is depends on the current perspective. If this
// is our commitment this will be our key.
ToLocalKey *btcec.PublicKey
// ToRemoteKey is the non-owner's payment key in the commitment tx.
// This is the key used to generate the to_remote output within the
// commitment transaction.
//
// NOTE: Who's key this is depends on the current perspective. If this
// is our commitment this will be their key.
ToRemoteKey *btcec.PublicKey
// RevocationKey is the key that can be used by the other party to
// redeem outputs from a revoked commitment transaction if it were to
// be published.
//
// NOTE: Who can sign for this key depends on the current perspective.
// If this is our commitment, it means the remote node can sign for
// this key in case of a breach.
RevocationKey *btcec.PublicKey
}
// DeriveCommitmentKeys generates a new commitment key set using the base points
// and commitment point. The keys are derived differently depending on the type
// of channel, and whether the commitment transaction is ours or the remote
// peer's.
func DeriveCommitmentKeys(commitPoint *btcec.PublicKey,
whoseCommit lntypes.ChannelParty, chanType channeldb.ChannelType,
localChanCfg, remoteChanCfg *channeldb.ChannelConfig) *CommitmentKeyRing {
tweaklessCommit := chanType.IsTweakless()
// Depending on if this is our commit or not, we'll choose the correct
// base point.
localBasePoint := localChanCfg.PaymentBasePoint
if whoseCommit.IsLocal() {
localBasePoint = localChanCfg.DelayBasePoint
}
// First, we'll derive all the keys that don't depend on the context of
// whose commitment transaction this is.
keyRing := &CommitmentKeyRing{
CommitPoint: commitPoint,
LocalCommitKeyTweak: input.SingleTweakBytes(
commitPoint, localBasePoint.PubKey,
),
LocalHtlcKeyTweak: input.SingleTweakBytes(
commitPoint, localChanCfg.HtlcBasePoint.PubKey,
),
LocalHtlcKey: input.TweakPubKey(
localChanCfg.HtlcBasePoint.PubKey, commitPoint,
),
RemoteHtlcKey: input.TweakPubKey(
remoteChanCfg.HtlcBasePoint.PubKey, commitPoint,
),
}
// We'll now compute the to_local, to_remote, and revocation key based
// on the current commitment point. All keys are tweaked each state in
// order to ensure the keys from each state are unlinkable. To create
// the revocation key, we take the opposite party's revocation base
// point and combine that with the current commitment point.
var (
toLocalBasePoint *btcec.PublicKey
toRemoteBasePoint *btcec.PublicKey
revocationBasePoint *btcec.PublicKey
)
if whoseCommit.IsLocal() {
toLocalBasePoint = localChanCfg.DelayBasePoint.PubKey
toRemoteBasePoint = remoteChanCfg.PaymentBasePoint.PubKey
revocationBasePoint = remoteChanCfg.RevocationBasePoint.PubKey
} else {
toLocalBasePoint = remoteChanCfg.DelayBasePoint.PubKey
toRemoteBasePoint = localChanCfg.PaymentBasePoint.PubKey
revocationBasePoint = localChanCfg.RevocationBasePoint.PubKey
}
// With the base points assigned, we can now derive the actual keys
// using the base point, and the current commitment tweak.
keyRing.ToLocalKey = input.TweakPubKey(toLocalBasePoint, commitPoint)
keyRing.RevocationKey = input.DeriveRevocationPubkey(
revocationBasePoint, commitPoint,
)
// If this commitment should omit the tweak for the remote point, then
// we'll use that directly, and ignore the commitPoint tweak.
if tweaklessCommit {
keyRing.ToRemoteKey = toRemoteBasePoint
// If this is not our commitment, the above ToRemoteKey will be
// ours, and we blank out the local commitment tweak to
// indicate that the key should not be tweaked when signing.
if whoseCommit.IsRemote() {
keyRing.LocalCommitKeyTweak = nil
}
} else {
keyRing.ToRemoteKey = input.TweakPubKey(
toRemoteBasePoint, commitPoint,
)
}
return keyRing
}
// WitnessScriptDesc holds the output script and the witness script for p2wsh
// outputs.
type WitnessScriptDesc struct {
// OutputScript is the output's PkScript.
OutputScript []byte
// WitnessScript is the full script required to properly redeem the
// output. This field should be set to the full script if a p2wsh
// output is being signed. For p2wkh it should be set equal to the
// PkScript.
WitnessScript []byte
}
// PkScript is the public key script that commits to the final
// contract.
func (w *WitnessScriptDesc) PkScript() []byte {
return w.OutputScript
}
// WitnessScriptToSign returns the witness script that we'll use when signing
// for the remote party, and also verifying signatures on our transactions. As
// an example, when we create an outgoing HTLC for the remote party, we want to
// sign their success path.
func (w *WitnessScriptDesc) WitnessScriptToSign() []byte {
return w.WitnessScript
}
// WitnessScriptForPath returns the witness script for the given spending path.
// An error is returned if the path is unknown. This is useful as when
// constructing a control block for a given path, one also needs witness script
// being signed.
func (w *WitnessScriptDesc) WitnessScriptForPath(
_ input.ScriptPath) ([]byte, error) {
return w.WitnessScript, nil
}
// CommitScriptToSelf constructs the public key script for the output on the
// commitment transaction paying to the "owner" of said commitment transaction.
// The `initiator` argument should correspond to the owner of the commitment
// transaction which we are generating the to_local script for. If the other
// party learns of the preimage to the revocation hash, then they can claim all
// the settled funds in the channel, plus the unsettled funds.
func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool,
selfKey, revokeKey *btcec.PublicKey, csvDelay, leaseExpiry uint32,
auxLeaf input.AuxTapLeaf) (input.ScriptDescriptor, error) {
switch {
// For taproot scripts, we'll need to make a slightly modified script
// where a NUMS key is used to force a script path reveal of either the
// revocation or the CSV timeout.
//
// Our "redeem" script here is just the taproot witness program.
case chanType.IsTaproot():
return input.NewLocalCommitScriptTree(
csvDelay, selfKey, revokeKey, auxLeaf,
)
// If we are the initiator of a leased channel, then we have an
// additional CLTV requirement in addition to the usual CSV
// requirement.
case initiator && chanType.HasLeaseExpiration():
toLocalRedeemScript, err := input.LeaseCommitScriptToSelf(
selfKey, revokeKey, csvDelay, leaseExpiry,
)
if err != nil {
return nil, err
}
toLocalScriptHash, err := input.WitnessScriptHash(
toLocalRedeemScript,
)
if err != nil {
return nil, err
}
return &WitnessScriptDesc{
OutputScript: toLocalScriptHash,
WitnessScript: toLocalRedeemScript,
}, nil
default:
toLocalRedeemScript, err := input.CommitScriptToSelf(
csvDelay, selfKey, revokeKey,
)
if err != nil {
return nil, err
}
toLocalScriptHash, err := input.WitnessScriptHash(
toLocalRedeemScript,
)
if err != nil {
return nil, err
}
return &WitnessScriptDesc{
OutputScript: toLocalScriptHash,
WitnessScript: toLocalRedeemScript,
}, nil
}
}
// CommitScriptToRemote derives the appropriate to_remote script based on the
// channel's commitment type. The `initiator` argument should correspond to the
// owner of the commitment transaction which we are generating the to_remote
// script for. The second return value is the CSV delay of the output script,
// what must be satisfied in order to spend the output.
func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool,
remoteKey *btcec.PublicKey, leaseExpiry uint32,
auxLeaf input.AuxTapLeaf) (input.ScriptDescriptor, uint32, error) {
switch {
// If we are not the initiator of a leased channel, then the remote
// party has an additional CLTV requirement in addition to the 1 block
// CSV requirement.
case chanType.HasLeaseExpiration() && !initiator:
script, err := input.LeaseCommitScriptToRemoteConfirmed(
remoteKey, leaseExpiry,
)
if err != nil {
return nil, 0, err
}
p2wsh, err := input.WitnessScriptHash(script)
if err != nil {
return nil, 0, err
}
return &WitnessScriptDesc{
OutputScript: p2wsh,
WitnessScript: script,
}, 1, nil
// For taproot channels, we'll use a slightly different format, where
// we use a NUMS key to force the remote party to take a script path,
// with the sole tap leaf enforcing the 1 CSV delay.
case chanType.IsTaproot():
toRemoteScriptTree, err := input.NewRemoteCommitScriptTree(
remoteKey, auxLeaf,
)
if err != nil {
return nil, 0, err
}
return toRemoteScriptTree, 1, nil
// If this channel type has anchors, we derive the delayed to_remote
// script.
case chanType.HasAnchors():
script, err := input.CommitScriptToRemoteConfirmed(remoteKey)
if err != nil {
return nil, 0, err
}
p2wsh, err := input.WitnessScriptHash(script)
if err != nil {
return nil, 0, err
}
return &WitnessScriptDesc{
OutputScript: p2wsh,
WitnessScript: script,
}, 1, nil
default:
// Otherwise the to_remote will be a simple p2wkh.
p2wkh, err := input.CommitScriptUnencumbered(remoteKey)
if err != nil {
return nil, 0, err
}
// Since this is a regular P2WKH, the WitnessScipt and PkScript
// should both be set to the script hash.
return &WitnessScriptDesc{
OutputScript: p2wkh,
WitnessScript: p2wkh,
}, 0, nil
}
}
// HtlcSigHashType returns the sighash type to use for HTLC success and timeout
// transactions given the channel type.
func HtlcSigHashType(chanType channeldb.ChannelType) txscript.SigHashType {
if chanType.HasAnchors() {
return txscript.SigHashSingle | txscript.SigHashAnyOneCanPay
}
return txscript.SigHashAll
}
// HtlcSignDetails converts the passed parameters to a SignDetails valid for
// this channel type. For non-anchor channels this will return nil.
func HtlcSignDetails(chanType channeldb.ChannelType, signDesc input.SignDescriptor,
sigHash txscript.SigHashType, peerSig input.Signature) *input.SignDetails {
// Non-anchor channels don't need sign details, as the HTLC second
// level cannot be altered.
if !chanType.HasAnchors() {
return nil
}
return &input.SignDetails{
SignDesc: signDesc,
SigHashType: sigHash,
PeerSig: peerSig,
}
}
// HtlcSecondLevelInputSequence dictates the sequence number we must use on the
// input to a second level HTLC transaction.
func HtlcSecondLevelInputSequence(chanType channeldb.ChannelType) uint32 {
if chanType.HasAnchors() {
return 1
}
return 0
}
// sweepSigHash returns the sign descriptor to use when signing a sweep
// transaction. For taproot channels, we'll use this to always sweep with
// sighash default.
func sweepSigHash(chanType channeldb.ChannelType) txscript.SigHashType {
if chanType.IsTaproot() {
return txscript.SigHashDefault
}
return txscript.SigHashAll
}
// SecondLevelHtlcScript derives the appropriate second level HTLC script based
// on the channel's commitment type. It is the uniform script that's used as the
// output for the second-level HTLC transactions. The second level transaction
// act as a sort of covenant, ensuring that a 2-of-2 multi-sig output can only
// be spent in a particular way, and to a particular output. The `initiator`
// argument should correspond to the owner of the commitment transaction which
// we are generating the to_local script for.
func SecondLevelHtlcScript(chanType channeldb.ChannelType, initiator bool,
revocationKey, delayKey *btcec.PublicKey, csvDelay, leaseExpiry uint32,
auxLeaf input.AuxTapLeaf) (input.ScriptDescriptor, error) {
switch {
// For taproot channels, the pkScript is a segwit v1 p2tr output.
case chanType.IsTaproot():
return input.TaprootSecondLevelScriptTree(
revocationKey, delayKey, csvDelay, auxLeaf,
)
// If we are the initiator of a leased channel, then we have an
// additional CLTV requirement in addition to the usual CSV
// requirement.
case initiator && chanType.HasLeaseExpiration():
witnessScript, err := input.LeaseSecondLevelHtlcScript(
revocationKey, delayKey, csvDelay, leaseExpiry,
)
if err != nil {
return nil, err
}
pkScript, err := input.WitnessScriptHash(witnessScript)
if err != nil {
return nil, err
}
return &WitnessScriptDesc{
OutputScript: pkScript,
WitnessScript: witnessScript,
}, nil
default:
witnessScript, err := input.SecondLevelHtlcScript(
revocationKey, delayKey, csvDelay,
)
if err != nil {
return nil, err
}
pkScript, err := input.WitnessScriptHash(witnessScript)
if err != nil {
return nil, err
}
return &WitnessScriptDesc{
OutputScript: pkScript,
WitnessScript: witnessScript,
}, nil
}
}
// CommitWeight returns the base commitment weight before adding HTLCs.
func CommitWeight(chanType channeldb.ChannelType) lntypes.WeightUnit {
switch {
case chanType.IsTaproot():
return input.TaprootCommitWeight
// If this commitment has anchors, it will be slightly heavier.
case chanType.HasAnchors():
return input.AnchorCommitWeight
default:
return input.CommitWeight
}
}
// HtlcTimeoutFee returns the fee in satoshis required for an HTLC timeout
// transaction based on the current fee rate.
func HtlcTimeoutFee(chanType channeldb.ChannelType,
feePerKw chainfee.SatPerKWeight) btcutil.Amount {
switch {
// For zero-fee HTLC channels, this will always be zero, regardless of
// feerate.
case chanType.ZeroHtlcTxFee() || chanType.IsTaproot():
return 0
case chanType.HasAnchors():
return feePerKw.FeeForWeight(input.HtlcTimeoutWeightConfirmed)
default:
return feePerKw.FeeForWeight(input.HtlcTimeoutWeight)
}
}
// HtlcSuccessFee returns the fee in satoshis required for an HTLC success
// transaction based on the current fee rate.
func HtlcSuccessFee(chanType channeldb.ChannelType,
feePerKw chainfee.SatPerKWeight) btcutil.Amount {
switch {
// For zero-fee HTLC channels, this will always be zero, regardless of
// feerate.
case chanType.ZeroHtlcTxFee() || chanType.IsTaproot():
return 0
case chanType.HasAnchors():
return feePerKw.FeeForWeight(input.HtlcSuccessWeightConfirmed)
default:
return feePerKw.FeeForWeight(input.HtlcSuccessWeight)
}
}
// CommitScriptAnchors return the scripts to use for the local and remote
// anchor.
func CommitScriptAnchors(chanType channeldb.ChannelType,
localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
keyRing *CommitmentKeyRing) (
input.ScriptDescriptor, input.ScriptDescriptor, error) {
var (
anchorScript func(
key *btcec.PublicKey) (input.ScriptDescriptor, error)
keySelector func(*channeldb.ChannelConfig,
bool) *btcec.PublicKey
)
switch {
// For taproot channels, the anchor is slightly different: the top
// level key is now the (relative) local delay and remote public key,
// since these are fully revealed once the commitment hits the chain.
case chanType.IsTaproot():
anchorScript = func(
key *btcec.PublicKey) (input.ScriptDescriptor, error) {
return input.NewAnchorScriptTree(key)
}
keySelector = func(cfg *channeldb.ChannelConfig,
local bool) *btcec.PublicKey {
if local {
return keyRing.ToLocalKey
}
return keyRing.ToRemoteKey
}
// For normal channels we'll use the multi-sig keys since those are
// revealed when the channel closes
default:
// For normal channels, we'll create a p2wsh script based on
// the target key.
anchorScript = func(
key *btcec.PublicKey) (input.ScriptDescriptor, error) {
script, err := input.CommitScriptAnchor(key)
if err != nil {
return nil, err
}
scriptHash, err := input.WitnessScriptHash(script)
if err != nil {
return nil, err
}
return &WitnessScriptDesc{
OutputScript: scriptHash,
WitnessScript: script,
}, nil
}
// For the existing channels, we'll always select the multi-sig
// key from the party's channel config.
keySelector = func(cfg *channeldb.ChannelConfig,
_ bool) *btcec.PublicKey {
return cfg.MultiSigKey.PubKey
}
}
// Get the script used for the anchor output spendable by the local
// node.
localAnchor, err := anchorScript(keySelector(localChanCfg, true))
if err != nil {
return nil, nil, err
}
// And the anchor spendable by the remote node.
remoteAnchor, err := anchorScript(keySelector(remoteChanCfg, false))
if err != nil {
return nil, nil, err
}
return localAnchor, remoteAnchor, nil
}
// CommitmentBuilder is a type that wraps the type of channel we are dealing
// with, and abstracts the various ways of constructing commitment
// transactions.
type CommitmentBuilder struct {
// chanState is the underlying channel's state struct, used to
// determine the type of channel we are dealing with, and relevant
// parameters.
chanState *channeldb.OpenChannel
// obfuscator is a 48-bit state hint that's used to obfuscate the
// current state number on the commitment transactions.
obfuscator [StateHintSize]byte
// auxLeafStore is an interface that allows us to fetch auxiliary
// tapscript leaves for the commitment output.
auxLeafStore fn.Option[AuxLeafStore]
}
// NewCommitmentBuilder creates a new CommitmentBuilder from chanState.
func NewCommitmentBuilder(chanState *channeldb.OpenChannel,
leafStore fn.Option[AuxLeafStore]) *CommitmentBuilder {
// The anchor channel type MUST be tweakless.
if chanState.ChanType.HasAnchors() && !chanState.ChanType.IsTweakless() {
panic("invalid channel type combination")
}
return &CommitmentBuilder{
chanState: chanState,
obfuscator: createStateHintObfuscator(chanState),
auxLeafStore: leafStore,
}
}
// createStateHintObfuscator derives and assigns the state hint obfuscator for
// the channel, which is used to encode the commitment height in the sequence
// number of commitment transaction inputs.
func createStateHintObfuscator(state *channeldb.OpenChannel) [StateHintSize]byte {
if state.IsInitiator {
return DeriveStateHintObfuscator(
state.LocalChanCfg.PaymentBasePoint.PubKey,
state.RemoteChanCfg.PaymentBasePoint.PubKey,
)
}
return DeriveStateHintObfuscator(
state.RemoteChanCfg.PaymentBasePoint.PubKey,
state.LocalChanCfg.PaymentBasePoint.PubKey,
)
}
// unsignedCommitmentTx is the final commitment created from evaluating an HTLC
// view at a given height, along with some meta data.
type unsignedCommitmentTx struct {
// txn is the final, unsigned commitment transaction for this view.
txn *wire.MsgTx
// fee is the total fee of the commitment transaction.
fee btcutil.Amount
// ourBalance is our balance on this commitment *after* subtracting
// commitment fees and anchor outputs. This can be different than the
// balances before creating the commitment transaction as one party must
// pay the commitment fee.
ourBalance lnwire.MilliSatoshi
// theirBalance is their balance of this commitment *after* subtracting
// commitment fees and anchor outputs. This can be different than the
// balances before creating the commitment transaction as one party must
// pay the commitment fee.
theirBalance lnwire.MilliSatoshi
// cltvs is a sorted list of CLTV deltas for each HTLC on the commitment
// transaction. Any non-htlc outputs will have a CLTV delay of zero.
cltvs []uint32
}
// createUnsignedCommitmentTx generates the unsigned commitment transaction for
// a commitment view and returns it as part of the unsignedCommitmentTx. The
// passed in balances should be balances *before* subtracting any commitment
// fees, but after anchor outputs.
func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
theirBalance lnwire.MilliSatoshi, whoseCommit lntypes.ChannelParty,
feePerKw chainfee.SatPerKWeight, height uint64, originalHtlcView,
filteredHTLCView *HtlcView, keyRing *CommitmentKeyRing,
prevCommit *commitment) (*unsignedCommitmentTx, error) {
dustLimit := cb.chanState.LocalChanCfg.DustLimit
if whoseCommit.IsRemote() {
dustLimit = cb.chanState.RemoteChanCfg.DustLimit
}
numHTLCs := int64(0)
for _, htlc := range filteredHTLCView.Updates.Local {
if HtlcIsDust(
cb.chanState.ChanType, false, whoseCommit, feePerKw,
htlc.Amount.ToSatoshis(), dustLimit,
) {
continue
}
numHTLCs++
}
for _, htlc := range filteredHTLCView.Updates.Remote {
if HtlcIsDust(
cb.chanState.ChanType, true, whoseCommit, feePerKw,
htlc.Amount.ToSatoshis(), dustLimit,
) {
continue
}
numHTLCs++
}
// Next, we'll calculate the fee for the commitment transaction based
// on its total weight. Once we have the total weight, we'll multiply
// by the current fee-per-kw, then divide by 1000 to get the proper
// fee.
totalCommitWeight := CommitWeight(cb.chanState.ChanType) +
lntypes.WeightUnit(input.HTLCWeight*numHTLCs)
// With the weight known, we can now calculate the commitment fee,
// ensuring that we account for any dust outputs trimmed above.
commitFee := feePerKw.FeeForWeight(totalCommitWeight)
commitFeeMSat := lnwire.NewMSatFromSatoshis(commitFee)
// Currently, within the protocol, the initiator always pays the fees.
// So we'll subtract the fee amount from the balance of the current
// initiator. If the initiator is unable to pay the fee fully, then
// their entire output is consumed.
switch {
case cb.chanState.IsInitiator && commitFee > ourBalance.ToSatoshis():
ourBalance = 0
case cb.chanState.IsInitiator:
ourBalance -= commitFeeMSat
case !cb.chanState.IsInitiator && commitFee > theirBalance.ToSatoshis():
theirBalance = 0
case !cb.chanState.IsInitiator:
theirBalance -= commitFeeMSat
}
var commitTx *wire.MsgTx
// Before we create the commitment transaction below, we'll try to see
// if there're any aux leaves that need to be a part of the tapscript
// tree. We'll only do this if we have a custom blob defined though.
auxResult, err := fn.MapOptionZ(
cb.auxLeafStore,
func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return auxLeavesFromView(
s, cb.chanState, prevCommit.customBlob,
originalHtlcView, whoseCommit, ourBalance,
theirBalance, *keyRing,
)
},
).Unpack()
if err != nil {
return nil, fmt.Errorf("unable to fetch aux leaves: %w", err)
}
// Depending on whether the transaction is ours or not, we call
// CreateCommitTx with parameters matching the perspective, to generate
// a new commitment transaction with all the latest unsettled/un-timed
// out HTLCs.
var leaseExpiry uint32
if cb.chanState.ChanType.HasLeaseExpiration() {
leaseExpiry = cb.chanState.ThawHeight
}
if whoseCommit.IsLocal() {
commitTx, err = CreateCommitTx(
cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing,
&cb.chanState.LocalChanCfg, &cb.chanState.RemoteChanCfg,
ourBalance.ToSatoshis(), theirBalance.ToSatoshis(),
numHTLCs, cb.chanState.IsInitiator, leaseExpiry,
auxResult.AuxLeaves,
)
} else {
commitTx, err = CreateCommitTx(
cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing,
&cb.chanState.RemoteChanCfg, &cb.chanState.LocalChanCfg,
theirBalance.ToSatoshis(), ourBalance.ToSatoshis(),
numHTLCs, !cb.chanState.IsInitiator, leaseExpiry,
auxResult.AuxLeaves,
)
}
if err != nil {
return nil, err
}
// Similarly, we'll now attempt to extract the set of aux leaves for
// the set of incoming and outgoing HTLCs.
incomingAuxLeaves := fn.MapOption(
func(leaves CommitAuxLeaves) input.HtlcAuxLeaves {
return leaves.IncomingHtlcLeaves
},
)(auxResult.AuxLeaves)
outgoingAuxLeaves := fn.MapOption(
func(leaves CommitAuxLeaves) input.HtlcAuxLeaves {
return leaves.OutgoingHtlcLeaves
},
)(auxResult.AuxLeaves)
// We'll now add all the HTLC outputs to the commitment transaction.
// Each output includes an off-chain 2-of-2 covenant clause, so we'll
// need the objective local/remote keys for this particular commitment
// as well. For any non-dust HTLCs that are manifested on the commitment
// transaction, we'll also record its CLTV which is required to sort the
// commitment transaction below. The slice is initially sized to the
// number of existing outputs, since any outputs already added are
// commitment outputs and should correspond to zero values for the
// purposes of sorting.
cltvs := make([]uint32, len(commitTx.TxOut))
htlcIndexes := make([]input.HtlcIndex, len(commitTx.TxOut))
for _, htlc := range filteredHTLCView.Updates.Local {
if HtlcIsDust(
cb.chanState.ChanType, false, whoseCommit, feePerKw,
htlc.Amount.ToSatoshis(), dustLimit,
) {
continue
}
auxLeaf := fn.FlatMapOption(
func(leaves input.HtlcAuxLeaves) input.AuxTapLeaf {
return leaves[htlc.HtlcIndex].AuxTapLeaf
},
)(outgoingAuxLeaves)
err := addHTLC(
commitTx, whoseCommit, false, htlc, keyRing,
cb.chanState.ChanType, auxLeaf,
)
if err != nil {
return nil, err
}
// We want to add the CLTV and HTLC index to their respective
// slices, even if we already pre-allocated them.
cltvs = append(cltvs, htlc.Timeout) //nolint
htlcIndexes = append(htlcIndexes, htlc.HtlcIndex) //nolint
}
for _, htlc := range filteredHTLCView.Updates.Remote {
if HtlcIsDust(
cb.chanState.ChanType, true, whoseCommit, feePerKw,
htlc.Amount.ToSatoshis(), dustLimit,
) {
continue
}
auxLeaf := fn.FlatMapOption(
func(leaves input.HtlcAuxLeaves) input.AuxTapLeaf {
return leaves[htlc.HtlcIndex].AuxTapLeaf
},
)(incomingAuxLeaves)
err := addHTLC(
commitTx, whoseCommit, true, htlc, keyRing,
cb.chanState.ChanType, auxLeaf,
)
if err != nil {
return nil, err
}
// We want to add the CLTV and HTLC index to their respective
// slices, even if we already pre-allocated them.
cltvs = append(cltvs, htlc.Timeout) //nolint
htlcIndexes = append(htlcIndexes, htlc.HtlcIndex) //nolint
}
// Set the state hint of the commitment transaction to facilitate
// quickly recovering the necessary penalty state in the case of an
// uncooperative broadcast.
err = SetStateNumHint(commitTx, height, cb.obfuscator)
if err != nil {
return nil, err
}
// Sort the transactions according to the agreed upon canonical
// ordering (which might be customized for custom channel types, but
// deterministic and both parties will arrive at the same result). This
// lets us skip sending the entire transaction over, instead we'll just
// send signatures.
commitSort := auxResult.CommitSortFunc.UnwrapOr(DefaultCommitSort)
err = commitSort(commitTx, cltvs, htlcIndexes)
if err != nil {
return nil, fmt.Errorf("unable to sort commitment "+
"transaction: %w", err)
}
// Next, we'll ensure that we don't accidentally create a commitment
// transaction which would be invalid by consensus.
uTx := btcutil.NewTx(commitTx)
if err := blockchain.CheckTransactionSanity(uTx); err != nil {
return nil, err
}
// Finally, we'll assert that were not attempting to draw more out of
// the channel that was originally placed within it.
var totalOut btcutil.Amount
for _, txOut := range commitTx.TxOut {
totalOut += btcutil.Amount(txOut.Value)
}
if totalOut+commitFee > cb.chanState.Capacity {
return nil, fmt.Errorf("height=%v, for ChannelPoint(%v) "+
"attempts to consume %v while channel capacity is %v",
height, cb.chanState.FundingOutpoint,
totalOut+commitFee, cb.chanState.Capacity)
}
return &unsignedCommitmentTx{
txn: commitTx,
fee: commitFee,
ourBalance: ourBalance,
theirBalance: theirBalance,
cltvs: cltvs,
}, nil
}
// CreateCommitTx creates a commitment transaction, spending from specified
// funding output. The commitment transaction contains two outputs: one local
// output paying to the "owner" of the commitment transaction which can be
// spent after a relative block delay or revocation event, and a remote output
// paying the counterparty within the channel, which can be spent immediately
// or after a delay depending on the commitment type. The `initiator` argument
// should correspond to the owner of the commitment transaction we are creating.
func CreateCommitTx(chanType channeldb.ChannelType,
fundingOutput wire.TxIn, keyRing *CommitmentKeyRing,
localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
amountToLocal, amountToRemote btcutil.Amount,
numHTLCs int64, initiator bool, leaseExpiry uint32,
auxLeaves fn.Option[CommitAuxLeaves]) (*wire.MsgTx, error) {
// First, we create the script for the delayed "pay-to-self" output.
// This output has 2 main redemption clauses: either we can redeem the
// output after a relative block delay, or the remote node can claim
// the funds with the revocation key if we broadcast a revoked
// commitment transaction.
localAuxLeaf := fn.MapOption(func(l CommitAuxLeaves) input.AuxTapLeaf {
return l.LocalAuxLeaf
})(auxLeaves)
toLocalScript, err := CommitScriptToSelf(
chanType, initiator, keyRing.ToLocalKey, keyRing.RevocationKey,
uint32(localChanCfg.CsvDelay), leaseExpiry,
fn.FlattenOption(localAuxLeaf),
)
if err != nil {
return nil, err
}
// Next, we create the script paying to the remote.
remoteAuxLeaf := fn.MapOption(func(l CommitAuxLeaves) input.AuxTapLeaf {
return l.RemoteAuxLeaf
})(auxLeaves)
toRemoteScript, _, err := CommitScriptToRemote(
chanType, initiator, keyRing.ToRemoteKey, leaseExpiry,
fn.FlattenOption(remoteAuxLeaf),
)
if err != nil {
return nil, err
}
// Now that both output scripts have been created, we can finally create
// the transaction itself. We use a transaction version of 2 since CSV
// will fail unless the tx version is >= 2.
commitTx := wire.NewMsgTx(2)
commitTx.AddTxIn(&fundingOutput)
// Avoid creating dust outputs within the commitment transaction.
localOutput := amountToLocal >= localChanCfg.DustLimit
if localOutput {
commitTx.AddTxOut(&wire.TxOut{
PkScript: toLocalScript.PkScript(),
Value: int64(amountToLocal),
})
}
remoteOutput := amountToRemote >= localChanCfg.DustLimit
if remoteOutput {
commitTx.AddTxOut(&wire.TxOut{
PkScript: toRemoteScript.PkScript(),
Value: int64(amountToRemote),
})
}
// If this channel type has anchors, we'll also add those.
if chanType.HasAnchors() {
localAnchor, remoteAnchor, err := CommitScriptAnchors(
chanType, localChanCfg, remoteChanCfg, keyRing,
)
if err != nil {
return nil, err
}
// Add local anchor output only if we have a commitment output
// or there are HTLCs.
if localOutput || numHTLCs > 0 {
commitTx.AddTxOut(&wire.TxOut{
PkScript: localAnchor.PkScript(),
Value: int64(AnchorSize),
})
}
// Add anchor output to remote only if they have a commitment
// output or there are HTLCs.
if remoteOutput || numHTLCs > 0 {
commitTx.AddTxOut(&wire.TxOut{
PkScript: remoteAnchor.PkScript(),
Value: int64(AnchorSize),
})
}
}
return commitTx, nil
}
// CoopCloseBalance returns the final balances that should be used to create
// the cooperative close tx, given the channel type and transaction fee.
func CoopCloseBalance(chanType channeldb.ChannelType, isInitiator bool,
coopCloseFee, ourBalance, theirBalance, commitFee btcutil.Amount,
feePayer fn.Option[lntypes.ChannelParty],
) (btcutil.Amount, btcutil.Amount, error) {
// We'll make sure we account for the complete balance by adding the
// current dangling commitment fee to the balance of the initiator.
initiatorDelta := commitFee
// Since the initiator's balance also is stored after subtracting the
// anchor values, add that back in case this was an anchor commitment.
if chanType.HasAnchors() {
initiatorDelta += 2 * AnchorSize
}
// To start with, we'll add the anchor and/or commitment fee to the
// balance of the initiator.
if isInitiator {
ourBalance += initiatorDelta
} else {
theirBalance += initiatorDelta
}
// With the initiator's balance credited, we'll now subtract the closing
// fee from the closing party. By default, the initiator pays the full
// amount, but this can be overridden by the feePayer option.
defaultPayer := func() lntypes.ChannelParty {
if isInitiator {
return lntypes.Local
}
return lntypes.Remote
}()
payer := feePayer.UnwrapOr(defaultPayer)
// Based on the payer computed above, we'll subtract the closing fee.
switch payer {
case lntypes.Local:
ourBalance -= coopCloseFee
case lntypes.Remote:
theirBalance -= coopCloseFee
}
// During fee negotiation it should always be verified that the
// initiator can pay the proposed fee, but we do a sanity check just to
// be sure here.
if ourBalance < 0 || theirBalance < 0 {
return 0, 0, fmt.Errorf("initiator cannot afford proposed " +
"coop close fee")
}
return ourBalance, theirBalance, nil
}
// genSegwitV0HtlcScript generates the HTLC scripts for a normal segwit v0
// channel.
func genSegwitV0HtlcScript(chanType channeldb.ChannelType,
isIncoming bool, whoseCommit lntypes.ChannelParty, timeout uint32,
rHash [32]byte, keyRing *CommitmentKeyRing,
) (*WitnessScriptDesc, error) {
var (
witnessScript []byte
err error
)
// Choose scripts based on channel type.
confirmedHtlcSpends := false
if chanType.HasAnchors() {
confirmedHtlcSpends = true
}
// Generate the proper redeem scripts for the HTLC output modified by
// two-bits denoting if this is an incoming HTLC, and if the HTLC is
// being applied to their commitment transaction or ours.
switch {
// The HTLC is paying to us, and being applied to our commitment
// transaction. So we need to use the receiver's version of the HTLC
// script.
case isIncoming && whoseCommit.IsLocal():
witnessScript, err = input.ReceiverHTLCScript(
timeout, keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
keyRing.RevocationKey, rHash[:], confirmedHtlcSpends,
)
// We're being paid via an HTLC by the remote party, and the HTLC is
// being added to their commitment transaction, so we use the sender's
// version of the HTLC script.
case isIncoming && whoseCommit.IsRemote():
witnessScript, err = input.SenderHTLCScript(
keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
keyRing.RevocationKey, rHash[:], confirmedHtlcSpends,
)
// We're sending an HTLC which is being added to our commitment
// transaction. Therefore, we need to use the sender's version of the
// HTLC script.
case !isIncoming && whoseCommit.IsLocal():
witnessScript, err = input.SenderHTLCScript(
keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey,
keyRing.RevocationKey, rHash[:], confirmedHtlcSpends,
)
// Finally, we're paying the remote party via an HTLC, which is being
// added to their commitment transaction. Therefore, we use the
// receiver's version of the HTLC script.
case !isIncoming && whoseCommit.IsRemote():
witnessScript, err = input.ReceiverHTLCScript(
timeout, keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey,
keyRing.RevocationKey, rHash[:], confirmedHtlcSpends,
)
}
if err != nil {
return nil, err
}
// Now that we have the redeem scripts, create the P2WSH public key
// script for the output itself.
htlcP2WSH, err := input.WitnessScriptHash(witnessScript)
if err != nil {
return nil, err
}
return &WitnessScriptDesc{
OutputScript: htlcP2WSH,
WitnessScript: witnessScript,
}, nil
}
// GenTaprootHtlcScript generates the HTLC scripts for a taproot+musig2
// channel.
func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty,
timeout uint32, rHash [32]byte, keyRing *CommitmentKeyRing,
auxLeaf input.AuxTapLeaf) (*input.HtlcScriptTree, error) {
var (
htlcScriptTree *input.HtlcScriptTree
err error
)
// Generate the proper redeem scripts for the HTLC output modified by
// two-bits denoting if this is an incoming HTLC, and if the HTLC is
// being applied to their commitment transaction or ours.
switch {
// The HTLC is paying to us, and being applied to our commitment
// transaction. So we need to use the receiver's version of HTLC the
// script.
case isIncoming && whoseCommit.IsLocal():
htlcScriptTree, err = input.ReceiverHTLCScriptTaproot(
timeout, keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
keyRing.RevocationKey, rHash[:], whoseCommit, auxLeaf,
)
// We're being paid via an HTLC by the remote party, and the HTLC is
// being added to their commitment transaction, so we use the sender's
// version of the HTLC script.
case isIncoming && whoseCommit.IsRemote():
htlcScriptTree, err = input.SenderHTLCScriptTaproot(
keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
keyRing.RevocationKey, rHash[:], whoseCommit, auxLeaf,
)
// We're sending an HTLC which is being added to our commitment
// transaction. Therefore, we need to use the sender's version of the
// HTLC script.
case !isIncoming && whoseCommit.IsLocal():
htlcScriptTree, err = input.SenderHTLCScriptTaproot(
keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey,
keyRing.RevocationKey, rHash[:], whoseCommit, auxLeaf,
)
// Finally, we're paying the remote party via an HTLC, which is being
// added to their commitment transaction. Therefore, we use the
// receiver's version of the HTLC script.
case !isIncoming && whoseCommit.IsRemote():
htlcScriptTree, err = input.ReceiverHTLCScriptTaproot(
timeout, keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey,
keyRing.RevocationKey, rHash[:], whoseCommit, auxLeaf,
)
}
return htlcScriptTree, err
}
// genHtlcScript generates the proper P2WSH public key scripts for the HTLC
// output modified by two-bits denoting if this is an incoming HTLC, and if the
// HTLC is being applied to their commitment transaction or ours. A script
// multiplexer for the various spending paths is returned. The script path that
// we need to sign for the remote party (2nd level HTLCs) is also returned
// along side the multiplexer.
func genHtlcScript(chanType channeldb.ChannelType, isIncoming bool,
whoseCommit lntypes.ChannelParty, timeout uint32, rHash [32]byte,
keyRing *CommitmentKeyRing,
auxLeaf input.AuxTapLeaf) (input.ScriptDescriptor, error) {
if !chanType.IsTaproot() {
return genSegwitV0HtlcScript(
chanType, isIncoming, whoseCommit, timeout, rHash,
keyRing,
)
}
return GenTaprootHtlcScript(
isIncoming, whoseCommit, timeout, rHash, keyRing, auxLeaf,
)
}
// addHTLC adds a new HTLC to the passed commitment transaction. One of four
// full scripts will be generated for the HTLC output depending on if the HTLC
// is incoming and if it's being applied to our commitment transaction or that
// of the remote node's. Additionally, in order to be able to efficiently
// locate the added HTLC on the commitment transaction from the
// paymentDescriptor that generated it, the generated script is stored within
// the descriptor itself.
func addHTLC(commitTx *wire.MsgTx, whoseCommit lntypes.ChannelParty,
isIncoming bool, paymentDesc *paymentDescriptor,
keyRing *CommitmentKeyRing, chanType channeldb.ChannelType,
auxLeaf input.AuxTapLeaf) error {
timeout := paymentDesc.Timeout
rHash := paymentDesc.RHash
scriptInfo, err := genHtlcScript(
chanType, isIncoming, whoseCommit, timeout, rHash, keyRing,
auxLeaf,
)
if err != nil {
return err
}
pkScript := scriptInfo.PkScript()
// Add the new HTLC outputs to the respective commitment transactions.
amountPending := int64(paymentDesc.Amount.ToSatoshis())
commitTx.AddTxOut(wire.NewTxOut(amountPending, pkScript))
// Store the pkScript of this particular paymentDescriptor so we can
// quickly locate it within the commitment transaction later.
if whoseCommit.IsLocal() {
paymentDesc.ourPkScript = pkScript
paymentDesc.ourWitnessScript = scriptInfo.WitnessScriptToSign()
} else {
paymentDesc.theirPkScript = pkScript
//nolint:ll
paymentDesc.theirWitnessScript = scriptInfo.WitnessScriptToSign()
}
return nil
}
// findOutputIndexesFromRemote finds the index of our and their outputs from
// the remote commitment transaction. It derives the key ring to compute the
// output scripts and compares them against the outputs inside the commitment
// to find the match.
func findOutputIndexesFromRemote(revocationPreimage *chainhash.Hash,
chanState *channeldb.OpenChannel,
leafStore fn.Option[AuxLeafStore]) (uint32, uint32, error) {
// Init the output indexes as empty.
ourIndex := uint32(channeldb.OutputIndexEmpty)
theirIndex := uint32(channeldb.OutputIndexEmpty)
chanCommit := chanState.RemoteCommitment
_, commitmentPoint := btcec.PrivKeyFromBytes(revocationPreimage[:])
// With the commitment point generated, we can now derive the king ring
// which will be used to generate the output scripts.
keyRing := DeriveCommitmentKeys(
commitmentPoint, lntypes.Remote, chanState.ChanType,
&chanState.LocalChanCfg, &chanState.RemoteChanCfg,
)
// Since it's remote commitment chain, we'd used the mirrored values.
//
// We use the remote's channel config for the csv delay.
theirDelay := uint32(chanState.RemoteChanCfg.CsvDelay)
// If we are the initiator of this channel, then it's be false from the
// remote's PoV.
isRemoteInitiator := !chanState.IsInitiator
var leaseExpiry uint32
if chanState.ChanType.HasLeaseExpiration() {
leaseExpiry = chanState.ThawHeight
}
// If we have a custom blob, then we'll attempt to fetch the aux leaves
// for this state.
auxResult, err := fn.MapOptionZ(
leafStore, func(a AuxLeafStore) fn.Result[CommitDiffAuxResult] {
return a.FetchLeavesFromCommit(
NewAuxChanState(chanState), chanCommit,
*keyRing, lntypes.Remote,
)
},
).Unpack()
if err != nil {
return ourIndex, theirIndex, fmt.Errorf("unable to fetch aux "+
"leaves: %w", err)
}
// Map the scripts from our PoV. When facing a local commitment, the
// to_local output belongs to us and the to_remote output belongs to
// them. When facing a remote commitment, the to_local output belongs to
// them and the to_remote output belongs to us.
// Compute the to_local script. From our PoV, when facing a remote
// commitment, the to_local output belongs to them.
localAuxLeaf := fn.FlatMapOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
return l.LocalAuxLeaf
},
)(auxResult.AuxLeaves)
theirScript, err := CommitScriptToSelf(
chanState.ChanType, isRemoteInitiator, keyRing.ToLocalKey,
keyRing.RevocationKey, theirDelay, leaseExpiry, localAuxLeaf,
)
if err != nil {
return ourIndex, theirIndex, err
}
// Compute the to_remote script. From our PoV, when facing a remote
// commitment, the to_remote output belongs to us.
remoteAuxLeaf := fn.FlatMapOption(
func(l CommitAuxLeaves) input.AuxTapLeaf {
return l.RemoteAuxLeaf
},
)(auxResult.AuxLeaves)
ourScript, _, err := CommitScriptToRemote(
chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey,
leaseExpiry, remoteAuxLeaf,
)
if err != nil {
return ourIndex, theirIndex, err
}
// Now compare the scripts to find our/their output index.
for i, txOut := range chanCommit.CommitTx.TxOut {
switch {
case bytes.Equal(txOut.PkScript, ourScript.PkScript()):
ourIndex = uint32(i)
case bytes.Equal(txOut.PkScript, theirScript.PkScript()):
theirIndex = uint32(i)
}
}
return ourIndex, theirIndex, nil
}
package lnwallet
import (
"github.com/lightningnetwork/lnd/fn/v2"
)
// commitmentChain represents a chain of unrevoked commitments. The tail of the
// chain is the latest fully signed, yet unrevoked commitment. Two chains are
// tracked, one for the local node, and another for the remote node. New
// commitments we create locally extend the remote node's chain, and vice
// versa. Commitment chains are allowed to grow to a bounded length, after
// which the tail needs to be "dropped" before new commitments can be received.
// The tail is "dropped" when the owner of the chain sends a revocation for the
// previous tail.
type commitmentChain struct {
// commitments is a linked list of commitments to new states. New
// commitments are added to the end of the chain with increase height.
// Once a commitment transaction is revoked, the tail is incremented,
// freeing up the revocation window for new commitments.
commitments *fn.List[*commitment]
}
// newCommitmentChain creates a new commitment chain.
func newCommitmentChain() *commitmentChain {
return &commitmentChain{
commitments: fn.NewList[*commitment](),
}
}
// addCommitment extends the commitment chain by a single commitment. This
// added commitment represents a state update proposed by either party. Once
// the commitment prior to this commitment is revoked, the commitment becomes
// the new defacto state within the channel.
func (s *commitmentChain) addCommitment(c *commitment) {
s.commitments.PushBack(c)
}
// advanceTail reduces the length of the commitment chain by one. The tail of
// the chain should be advanced once a revocation for the lowest unrevoked
// commitment in the chain is received.
func (s *commitmentChain) advanceTail() {
s.commitments.Remove(s.commitments.Front())
}
// tip returns the latest commitment added to the chain.
func (s *commitmentChain) tip() *commitment {
return s.commitments.Back().Value
}
// tail returns the lowest unrevoked commitment transaction in the chain.
func (s *commitmentChain) tail() *commitment {
return s.commitments.Front().Value
}
// hasUnackedCommitment returns true if the commitment chain has more than one
// entry. The tail of the commitment chain has been ACKed by revoking all prior
// commitments, but any subsequent commitments have not yet been ACKed.
func (s *commitmentChain) hasUnackedCommitment() bool {
return s.commitments.Front() != s.commitments.Back()
}
package lnwallet
import (
"errors"
"fmt"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/lnwire"
)
// ReservationError wraps certain errors returned during channel reservation
// that can be sent across the wire to the remote peer. Errors not being
// ReservationErrors will not be sent to the remote in case of a failed channel
// reservation, as they may contain private information.
type ReservationError struct {
error
}
// A compile time check to ensure ReservationError implements the error
// interface.
var _ error = (*ReservationError)(nil)
// ErrZeroCapacity returns an error indicating the funder attempted to put zero
// funds into the channel.
func ErrZeroCapacity() ReservationError {
return ReservationError{
errors.New("zero channel funds"),
}
}
// ErrChainMismatch returns an error indicating that the initiator tried to
// open a channel for an unknown chain.
func ErrChainMismatch(knownChain,
unknownChain *chainhash.Hash) ReservationError {
return ReservationError{
fmt.Errorf("unknown chain=%v, supported chain=%v",
unknownChain, knownChain),
}
}
// ErrFunderBalanceDust returns an error indicating the initial balance of the
// funder is considered dust at the current commitment fee.
func ErrFunderBalanceDust(commitFee, funderBalance,
minBalance int64) ReservationError {
return ReservationError{
fmt.Errorf("funder balance too small (%v) with fee=%v sat, "+
"minimum=%v sat required", funderBalance,
commitFee, minBalance),
}
}
// ErrCsvDelayTooLarge returns an error indicating that the CSV delay was to
// large to be accepted, along with the current max.
func ErrCsvDelayTooLarge(remoteDelay, maxDelay uint16) ReservationError {
return ReservationError{
fmt.Errorf("CSV delay too large: %v, max is %v",
remoteDelay, maxDelay),
}
}
// ErrChanReserveTooSmall returns an error indicating that the channel reserve
// the remote is requiring is too small to be accepted.
func ErrChanReserveTooSmall(reserve, dustLimit btcutil.Amount) ReservationError {
return ReservationError{
fmt.Errorf("channel reserve of %v sat is too small, min is %v "+
"sat", int64(reserve), int64(dustLimit)),
}
}
// ErrChanReserveTooLarge returns an error indicating that the chan reserve the
// remote is requiring, is too large to be accepted.
func ErrChanReserveTooLarge(reserve,
maxReserve btcutil.Amount) ReservationError {
return ReservationError{
fmt.Errorf("channel reserve is too large: %v sat, max "+
"is %v sat", int64(reserve), int64(maxReserve)),
}
}
// ErrNonZeroPushAmount is returned by a remote peer that receives a
// FundingOpen request for a channel with non-zero push amount while
// they have 'rejectpush' enabled.
func ErrNonZeroPushAmount() ReservationError {
return ReservationError{errors.New("non-zero push amounts are disabled")}
}
// ErrMinHtlcTooLarge returns an error indicating that the MinHTLC value the
// remote required is too large to be accepted.
func ErrMinHtlcTooLarge(minHtlc,
maxMinHtlc lnwire.MilliSatoshi) ReservationError {
return ReservationError{
fmt.Errorf("minimum HTLC value is too large: %v, max is %v",
minHtlc, maxMinHtlc),
}
}
// ErrMaxHtlcNumTooLarge returns an error indicating that the 'max HTLCs in
// flight' value the remote required is too large to be accepted.
func ErrMaxHtlcNumTooLarge(maxHtlc, maxMaxHtlc uint16) ReservationError {
return ReservationError{
fmt.Errorf("maxHtlcs is too large: %d, max is %d",
maxHtlc, maxMaxHtlc),
}
}
// ErrMaxHtlcNumTooSmall returns an error indicating that the 'max HTLCs in
// flight' value the remote required is too small to be accepted.
func ErrMaxHtlcNumTooSmall(maxHtlc, minMaxHtlc uint16) ReservationError {
return ReservationError{
fmt.Errorf("maxHtlcs is too small: %d, min is %d",
maxHtlc, minMaxHtlc),
}
}
// ErrMaxValueInFlightTooSmall returns an error indicating that the 'max HTLC
// value in flight' the remote required is too small to be accepted.
func ErrMaxValueInFlightTooSmall(maxValInFlight,
minMaxValInFlight lnwire.MilliSatoshi) ReservationError {
return ReservationError{
fmt.Errorf("maxValueInFlight too small: %v, min is %v",
maxValInFlight, minMaxValInFlight),
}
}
// ErrNumConfsTooLarge returns an error indicating that the number of
// confirmations required for a channel is too large.
func ErrNumConfsTooLarge(numConfs, maxNumConfs uint32) error {
return ReservationError{
fmt.Errorf("minimum depth of %d is too large, max is %d",
numConfs, maxNumConfs),
}
}
// ErrChanTooSmall returns an error indicating that an incoming channel request
// was too small. We'll reject any incoming channels if they're below our
// configured value for the min channel size we'll accept.
func ErrChanTooSmall(chanSize, minChanSize btcutil.Amount) ReservationError {
return ReservationError{
fmt.Errorf("chan size of %v is below min chan size of %v",
chanSize, minChanSize),
}
}
// ErrChanTooLarge returns an error indicating that an incoming channel request
// was too large. We'll reject any incoming channels if they're above our
// configured value for the max channel size we'll accept.
func ErrChanTooLarge(chanSize, maxChanSize btcutil.Amount) ReservationError {
return ReservationError{
fmt.Errorf("chan size of %v exceeds maximum chan size of %v",
chanSize, maxChanSize),
}
}
// ErrInvalidDustLimit returns an error indicating that a proposed DustLimit
// was rejected.
func ErrInvalidDustLimit(dustLimit btcutil.Amount) ReservationError {
return ReservationError{
fmt.Errorf("dust limit %v is invalid", dustLimit),
}
}
// ErrHtlcIndexAlreadyFailed is returned when the HTLC index has already been
// failed, but has not been committed by our commitment state.
type ErrHtlcIndexAlreadyFailed uint64
// Error returns a message indicating the index that had already been failed.
func (e ErrHtlcIndexAlreadyFailed) Error() string {
return fmt.Sprintf("HTLC with ID %d has already been failed", e)
}
// ErrHtlcIndexAlreadySettled is returned when the HTLC index has already been
// settled, but has not been committed by our commitment state.
type ErrHtlcIndexAlreadySettled uint64
// Error returns a message indicating the index that had already been settled.
func (e ErrHtlcIndexAlreadySettled) Error() string {
return fmt.Sprintf("HTLC with ID %d has already been settled", e)
}
// ErrInvalidSettlePreimage is returned when trying to settle an HTLC, but the
// preimage does not correspond to the payment hash.
type ErrInvalidSettlePreimage struct {
preimage []byte
rhash []byte
}
// Error returns an error message with the offending preimage and intended
// payment hash.
func (e ErrInvalidSettlePreimage) Error() string {
return fmt.Sprintf("Invalid payment preimage %x for hash %x",
e.preimage, e.rhash)
}
// ErrUnknownHtlcIndex is returned when locally settling or failing an HTLC, but
// the HTLC index is not known to the channel. This typically indicates that the
// HTLC was already settled in a prior commitment.
type ErrUnknownHtlcIndex struct {
chanID lnwire.ShortChannelID
index uint64
}
// Error returns an error logging the channel and HTLC index that was unknown.
func (e ErrUnknownHtlcIndex) Error() string {
return fmt.Sprintf("No HTLC with ID %d in channel %v",
e.index, e.chanID)
}
package lnwallet
import (
"errors"
"fmt"
"sync"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/waddrmgr"
base "github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/wallet/txauthor"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/chanvalidate"
"github.com/lightningnetwork/lnd/lnwire"
)
const (
// DefaultAccountName is the name for the default account used to manage
// on-chain funds within the wallet.
DefaultAccountName = "default"
)
// AddressType is an enum-like type which denotes the possible address types
// WalletController supports.
type AddressType uint8
// AccountAddressMap maps the account properties to an array of
// address properties.
type AccountAddressMap map[*waddrmgr.AccountProperties][]AddressProperty
const (
// UnknownAddressType represents an output with an unknown or non-standard
// script.
UnknownAddressType AddressType = iota
// WitnessPubKey represents a p2wkh address.
WitnessPubKey
// NestedWitnessPubKey represents a p2sh output which is itself a
// nested p2wkh output.
NestedWitnessPubKey
// TaprootPubkey represents a p2tr key path spending address.
TaprootPubkey
)
var (
// DefaultPublicPassphrase is the default public passphrase used for the
// wallet.
DefaultPublicPassphrase = []byte("public")
// DefaultPrivatePassphrase is the default private passphrase used for
// the wallet.
DefaultPrivatePassphrase = []byte("hello")
// ErrDoubleSpend is returned from PublishTransaction in case the
// tx being published is spending an output spent by a conflicting
// transaction.
ErrDoubleSpend = errors.New("transaction rejected: output already spent")
// ErrNotMine is an error denoting that a WalletController instance is
// unable to spend a specified output.
ErrNotMine = errors.New("the passed output doesn't belong to the wallet")
// ErrMempoolFee is returned from PublishTransaction in case the tx
// being published is not accepted into mempool because the fee
// requirements of the mempool backend are not met.
ErrMempoolFee = errors.New("transaction rejected by the mempool " +
"because of low fees")
)
// ErrNoOutputs is returned if we try to create a transaction with no outputs
// or send coins to a set of outputs that is empty.
var ErrNoOutputs = errors.New("no outputs")
// ErrInvalidMinconf is returned if we try to create a transaction with
// invalid minConfs value.
var ErrInvalidMinconf = errors.New("minimum number of confirmations must " +
"be a non-negative number")
// AddressProperty contains wallet related information of an address.
type AddressProperty struct {
// Address is the address of an account.
Address string
// Internal denotes if the address is a change address.
Internal bool
// Balance returns the total balance of an address.
Balance btcutil.Amount
// DerivationPath is the derivation path of the address.
DerivationPath string
// PublicKey is the public key of the address.
PublicKey *btcec.PublicKey
}
// AccountIdentifier contains information to uniquely identify an account.
type AccountIdentifier struct {
// Name is the name of the account.
Name string
// AddressType is the type of addresses supported by the account.
AddressType AddressType
// DerivationPath is the derivation path corresponding to the account
// public key.
DerivationPath string
}
// Utxo is an unspent output denoted by its outpoint, and output value of the
// original output.
type Utxo struct {
AddressType AddressType
Value btcutil.Amount
Confirmations int64
PkScript []byte
wire.OutPoint
PrevTx *wire.MsgTx
}
// OutputDetail contains additional information on a destination address.
type OutputDetail struct {
OutputType txscript.ScriptClass
Addresses []btcutil.Address
PkScript []byte
OutputIndex int
Value btcutil.Amount
IsOurAddress bool
}
// PreviousOutPoint contains information about the previous outpoint.
type PreviousOutPoint struct {
// OutPoint is the transaction out point in the format txid:n.
OutPoint string
// IsOurOutput denotes if the previous output is controlled by the
// internal wallet. The flag will only detect p2wkh, np2wkh and p2tr
// inputs as its own.
IsOurOutput bool
}
// TransactionDetail describes a transaction with either inputs which belong to
// the wallet, or has outputs that pay to the wallet.
type TransactionDetail struct {
// Hash is the transaction hash of the transaction.
Hash chainhash.Hash
// Value is the net value of this transaction (in satoshis) from the
// PoV of the wallet. If this transaction purely spends from the
// wallet's funds, then this value will be negative. Similarly, if this
// transaction credits the wallet, then this value will be positive.
Value btcutil.Amount
// NumConfirmations is the number of confirmations this transaction
// has. If the transaction is unconfirmed, then this value will be
// zero.
NumConfirmations int32
// BlockHeight is the hash of the block which includes this
// transaction. Unconfirmed transactions will have a nil value for this
// field.
BlockHash *chainhash.Hash
// BlockHeight is the height of the block including this transaction.
// Unconfirmed transaction will show a height of zero.
BlockHeight int32
// Timestamp is the unix timestamp of the block including this
// transaction. If the transaction is unconfirmed, then this will be a
// timestamp of txn creation.
Timestamp int64
// TotalFees is the total fee in satoshis paid by this transaction.
TotalFees int64
// OutputDetails contains output data for each destination address, such
// as the output script and amount.
OutputDetails []OutputDetail
// RawTx returns the raw serialized transaction.
RawTx []byte
// Label is an optional transaction label.
Label string
// PreviousOutpoints are the inputs for a transaction.
PreviousOutpoints []PreviousOutPoint
}
// TransactionSubscription is an interface which describes an object capable of
// receiving notifications of new transaction related to the underlying wallet.
// TODO(roasbeef): add balance updates?
type TransactionSubscription interface {
// ConfirmedTransactions returns a channel which will be sent on as new
// relevant transactions are confirmed.
ConfirmedTransactions() chan *TransactionDetail
// UnconfirmedTransactions returns a channel which will be sent on as
// new relevant transactions are seen within the network.
UnconfirmedTransactions() chan *TransactionDetail
// Cancel finalizes the subscription, cleaning up any resources
// allocated.
Cancel()
}
// WalletController defines an abstract interface for controlling a local Pure
// Go wallet, a local or remote wallet via an RPC mechanism, or possibly even
// a daemon assisted hardware wallet. This interface serves the purpose of
// allowing LightningWallet to be seamlessly compatible with several wallets
// such as: uspv, btcwallet, Bitcoin Core, Electrum, etc. This interface then
// serves as a "base wallet", with Lightning Network awareness taking place at
// a "higher" level of abstraction. Essentially, an overlay wallet.
// Implementors of this interface must closely adhere to the documented
// behavior of all interface methods in order to ensure identical behavior
// across all concrete implementations.
type WalletController interface {
// FetchOutpointInfo queries for the WalletController's knowledge of
// the passed outpoint. If the base wallet determines this output is
// under its control, then the original txout should be returned.
// Otherwise, a non-nil error value of ErrNotMine should be returned
// instead.
FetchOutpointInfo(prevOut *wire.OutPoint) (*Utxo, error)
// FetchDerivationInfo queries for the wallet's knowledge of the passed
// pkScript and constructs the derivation info and returns it.
FetchDerivationInfo(pkScript []byte) (*psbt.Bip32Derivation, error)
// ScriptForOutput returns the address, witness program and redeem
// script for a given UTXO. An error is returned if the UTXO does not
// belong to our wallet or it is not a managed pubKey address.
ScriptForOutput(output *wire.TxOut) (waddrmgr.ManagedPubKeyAddress,
[]byte, []byte, error)
// ConfirmedBalance returns the sum of all the wallet's unspent outputs
// that have at least confs confirmations. If confs is set to zero,
// then all unspent outputs, including those currently in the mempool
// will be included in the final sum. The account parameter serves as a
// filter to retrieve the balance for a specific account. When empty,
// the confirmed balance of all wallet accounts is returned.
//
// NOTE: Only witness outputs should be included in the computation of
// the total spendable balance of the wallet. We require this as only
// witness inputs can be used for funding channels.
ConfirmedBalance(confs int32, accountFilter string) (btcutil.Amount,
error)
// NewAddress returns the next external or internal address for the
// wallet dictated by the value of the `change` parameter. If change is
// true, then an internal address should be used, otherwise an external
// address should be returned. The type of address returned is dictated
// by the wallet's capabilities, and may be of type: p2sh, p2wkh,
// p2wsh, etc. The account parameter must be non-empty as it determines
// which account the address should be generated from.
NewAddress(addrType AddressType, change bool,
account string) (btcutil.Address, error)
// LastUnusedAddress returns the last *unused* address known by the
// wallet. An address is unused if it hasn't received any payments.
// This can be useful in UIs in order to continually show the
// "freshest" address without having to worry about "address inflation"
// caused by continual refreshing. Similar to NewAddress it can derive
// a specified address type. By default, this is a non-change address.
// The account parameter must be non-empty as it determines which
// account the address should be generated from.
LastUnusedAddress(addrType AddressType,
account string) (btcutil.Address, error)
// IsOurAddress checks if the passed address belongs to this wallet
IsOurAddress(a btcutil.Address) bool
// AddressInfo returns the information about an address, if it's known
// to this wallet.
AddressInfo(a btcutil.Address) (waddrmgr.ManagedAddress, error)
// ListAccounts retrieves all accounts belonging to the wallet by
// default. A name and key scope filter can be provided to filter
// through all of the wallet accounts and return only those matching.
ListAccounts(string, *waddrmgr.KeyScope) ([]*waddrmgr.AccountProperties,
error)
// RequiredReserve returns the minimum amount of satoshis that should be
// kept in the wallet in order to fee bump anchor channels if necessary.
// The value scales with the number of public anchor channels but is
// capped at a maximum.
RequiredReserve(uint32) btcutil.Amount
// ListAddresses retrieves all the addresses along with their balance. An
// account name filter can be provided to filter through all of the
// wallet accounts and return the addresses of only those matching.
ListAddresses(string, bool) (AccountAddressMap, error)
// ImportAccount imports an account backed by an account extended public
// key. The master key fingerprint denotes the fingerprint of the root
// key corresponding to the account public key (also known as the key
// with derivation path m/). This may be required by some hardware
// wallets for proper identification and signing.
//
// The address type can usually be inferred from the key's version, but
// may be required for certain keys to map them into the proper scope.
//
// For BIP-0044 keys, an address type must be specified as we intend to
// not support importing BIP-0044 keys into the wallet using the legacy
// pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will
// force the standard BIP-0049 derivation scheme, while a witness
// address type will force the standard BIP-0084 derivation scheme.
//
// For BIP-0049 keys, an address type must also be specified to make a
// distinction between the standard BIP-0049 address schema (nested
// witness pubkeys everywhere) and our own BIP-0049Plus address schema
// (nested pubkeys externally, witness pubkeys internally).
ImportAccount(name string, accountPubKey *hdkeychain.ExtendedKey,
masterKeyFingerprint uint32, addrType *waddrmgr.AddressType,
dryRun bool) (*waddrmgr.AccountProperties, []btcutil.Address,
[]btcutil.Address, error)
// ImportPublicKey imports a single derived public key into the wallet.
// The address type can usually be inferred from the key's version, but
// in the case of legacy versions (xpub, tpub), an address type must be
// specified as we intend to not support importing BIP-44 keys into the
// wallet using the legacy pay-to-pubkey-hash (P2PKH) scheme.
ImportPublicKey(pubKey *btcec.PublicKey,
addrType waddrmgr.AddressType) error
// ImportTaprootScript imports a user-provided taproot script into the
// wallet. The imported script will act as a pay-to-taproot address.
//
// NOTE: Taproot keys imported through this RPC currently _cannot_ be
// used for funding PSBTs. Only tracking the balance and UTXOs is
// currently supported.
ImportTaprootScript(scope waddrmgr.KeyScope,
tapscript *waddrmgr.Tapscript) (waddrmgr.ManagedAddress, error)
// SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying
// out to the specified outputs. In the case the wallet has insufficient
// funds, or the outputs are non-standard, an error should be returned.
// This method also takes the target fee expressed in sat/kw that should
// be used when crafting the transaction.
//
// NOTE: This method requires the global coin selection lock to be held.
SendOutputs(inputs fn.Set[wire.OutPoint], outputs []*wire.TxOut,
feeRate chainfee.SatPerKWeight, minConfs int32, label string,
strategy base.CoinSelectionStrategy) (*wire.MsgTx, error)
// CreateSimpleTx creates a Bitcoin transaction paying to the specified
// outputs. The transaction is not broadcasted to the network. In the
// case the wallet has insufficient funds, or the outputs are
// non-standard, an error should be returned. This method also takes
// the target fee expressed in sat/kw that should be used when crafting
// the transaction.
//
// NOTE: The dryRun argument can be set true to create a tx that
// doesn't alter the database. A tx created with this set to true
// SHOULD NOT be broadcasted.
//
// NOTE: This method requires the global coin selection lock to be held.
CreateSimpleTx(inputs fn.Set[wire.OutPoint], outputs []*wire.TxOut,
feeRate chainfee.SatPerKWeight, minConfs int32,
strategy base.CoinSelectionStrategy, dryRun bool) (
*txauthor.AuthoredTx, error)
// GetTransactionDetails returns a detailed description of a transaction
// given its transaction hash.
GetTransactionDetails(txHash *chainhash.Hash) (
*TransactionDetail, error)
// ListUnspentWitness returns all unspent outputs which are version 0
// witness programs. The 'minConfs' and 'maxConfs' parameters
// indicate the minimum and maximum number of confirmations an output
// needs in order to be returned by this method. Passing -1 as
// 'minConfs' indicates that even unconfirmed outputs should be
// returned. Using MaxInt32 as 'maxConfs' implies returning all
// outputs with at least 'minConfs'. The account parameter serves as
// a filter to retrieve the unspent outputs for a specific account.
// When empty, the unspent outputs of all wallet accounts are returned.
//
// NOTE: This method requires the global coin selection lock to be held.
ListUnspentWitness(minConfs, maxConfs int32,
accountFilter string) ([]*Utxo, error)
// ListTransactionDetails returns a list of all transactions which are
// relevant to the wallet over [startHeight;endHeight]. If start height
// is greater than end height, the transactions will be retrieved in
// reverse order. To include unconfirmed transactions, endHeight should
// be set to the special value -1. This will return transactions from
// the tip of the chain until the start height (inclusive) and
// unconfirmed transactions. The account parameter serves as a filter to
// retrieve the transactions relevant to a specific account. When
// empty, transactions of all wallet accounts are returned.
ListTransactionDetails(startHeight, endHeight int32,
accountFilter string, indexOffset uint32,
maxTransactions uint32) ([]*TransactionDetail, uint64, uint64,
error)
// LeaseOutput locks an output to the given ID, preventing it from being
// available for any future coin selection attempts. The absolute time
// of the lock's expiration is returned. The expiration of the lock can
// be extended by successive invocations of this call. Outputs can be
// unlocked before their expiration through `ReleaseOutput`.
//
// If the output is not known, wtxmgr.ErrUnknownOutput is returned. If
// the output has already been locked to a different ID, then
// wtxmgr.ErrOutputAlreadyLocked is returned.
//
// NOTE: This method requires the global coin selection lock to be held.
LeaseOutput(id wtxmgr.LockID, op wire.OutPoint,
duration time.Duration) (time.Time, error)
// ReleaseOutput unlocks an output, allowing it to be available for coin
// selection if it remains unspent. The ID should match the one used to
// originally lock the output.
//
// NOTE: This method requires the global coin selection lock to be held.
ReleaseOutput(id wtxmgr.LockID, op wire.OutPoint) error
// ListLeasedOutputs returns a list of all currently locked outputs.
ListLeasedOutputs() ([]*base.ListLeasedOutputResult, error)
// PublishTransaction performs cursory validation (dust checks, etc),
// then finally broadcasts the passed transaction to the Bitcoin network.
// If the transaction is rejected because it is conflicting with an
// already known transaction, ErrDoubleSpend is returned. If the
// transaction is already known (published already), no error will be
// returned. Other error returned depends on the currently active chain
// backend. It takes an optional label which will save a label with the
// published transaction.
PublishTransaction(tx *wire.MsgTx, label string) error
// LabelTransaction adds a label to a transaction. If the tx already
// has a label, this call will fail unless the overwrite parameter
// is set. Labels must not be empty, and they are limited to 500 chars.
LabelTransaction(hash chainhash.Hash, label string, overwrite bool) error
// FetchTx attempts to fetch a transaction in the wallet's database
// identified by the passed transaction hash. If the transaction can't
// be found, then a nil pointer is returned.
FetchTx(chainhash.Hash) (*wire.MsgTx, error)
// RemoveDescendants attempts to remove any transaction from the
// wallet's tx store (that may be unconfirmed) that spends outputs
// created by the passed transaction. This remove propagates
// recursively down the chain of descendent transactions.
RemoveDescendants(*wire.MsgTx) error
// FundPsbt creates a fully populated PSBT packet that contains enough
// inputs to fund the outputs specified in the passed in packet with the
// specified fee rate. If there is change left, a change output from the
// internal wallet is added and the index of the change output is
// returned. Otherwise no additional output is created and the index -1
// is returned. If no custom change scope is specified, the BIP0084 will
// be used for default accounts and single imported public keys. For
// custom account, no key scope should be provided as the coin selection
// key scope will always be used to generate the change address.
//
// NOTE: If the packet doesn't contain any inputs, coin selection is
// performed automatically. The account parameter must be non-empty as
// it determines which set of coins are eligible for coin selection. If
// the packet does contain any inputs, it is assumed that full coin
// selection happened externally and no additional inputs are added. If
// the specified inputs aren't enough to fund the outputs with the given
// fee rate, an error is returned. No lock lease is acquired for any of
// the selected/validated inputs. It is in the caller's responsibility
// to lock the inputs before handing them out.
FundPsbt(packet *psbt.Packet, minConfs int32,
feeRate chainfee.SatPerKWeight, account string,
changeScope *waddrmgr.KeyScope,
strategy base.CoinSelectionStrategy,
allowUtxo func(wtxmgr.Credit) bool) (int32, error)
// SignPsbt expects a partial transaction with all inputs and outputs
// fully declared and tries to sign all unsigned inputs that have all
// required fields (UTXO information, BIP32 derivation information,
// witness or sig scripts) set.
// If no error is returned, the PSBT is ready to be given to the next
// signer or to be finalized if lnd was the last signer.
//
// NOTE: This method only signs inputs (and only those it can sign), it
// does not perform any other tasks (such as coin selection, UTXO
// locking or input/output/fee value validation, PSBT finalization). Any
// input that is incomplete will be skipped.
SignPsbt(packet *psbt.Packet) ([]uint32, error)
// FinalizePsbt expects a partial transaction with all inputs and
// outputs fully declared and tries to sign all inputs that belong to
// the specified account. Lnd must be the last signer of the
// transaction. That means, if there are any unsigned non-witness inputs
// or inputs without UTXO information attached or inputs without witness
// data that do not belong to lnd's wallet, this method will fail. If no
// error is returned, the PSBT is ready to be extracted and the final TX
// within to be broadcast.
//
// NOTE: This method does NOT publish the transaction after it's been
// finalized successfully.
FinalizePsbt(packet *psbt.Packet, account string) error
// DecorateInputs fetches the UTXO information of all inputs it can
// identify and adds the required information to the package's inputs.
// The failOnUnknown boolean controls whether the method should return
// an error if it cannot identify an input or if it should just skip it.
DecorateInputs(packet *psbt.Packet, failOnUnknown bool) error
// SubscribeTransactions returns a TransactionSubscription client which
// is capable of receiving async notifications as new transactions
// related to the wallet are seen within the network, or found in
// blocks.
//
// NOTE: a non-nil error should be returned if notifications aren't
// supported.
//
// TODO(roasbeef): make distinct interface?
SubscribeTransactions() (TransactionSubscription, error)
// IsSynced returns a boolean indicating if from the PoV of the wallet,
// it has fully synced to the current best block in the main chain.
// It also returns an int64 indicating the timestamp of the best block
// known to the wallet, expressed in Unix epoch time
IsSynced() (bool, int64, error)
// GetRecoveryInfo returns a boolean indicating whether the wallet is
// started in recovery mode. It also returns a float64 indicating the
// recovery progress made so far.
GetRecoveryInfo() (bool, float64, error)
// Start initializes the wallet, making any necessary connections,
// starting up required goroutines etc.
Start() error
// Stop signals the wallet for shutdown. Shutdown may entail closing
// any active sockets, database handles, stopping goroutines, etc.
Stop() error
// BackEnd returns a name for the wallet's backing chain service,
// which could be e.g. btcd, bitcoind, neutrino, or another consensus
// service.
BackEnd() string
// CheckMempoolAcceptance checks whether a transaction follows mempool
// policies and returns an error if it cannot be accepted into the
// mempool.
CheckMempoolAcceptance(tx *wire.MsgTx) error
}
// BlockChainIO is a dedicated source which will be used to obtain queries
// related to the current state of the blockchain. The data returned by each of
// the defined methods within this interface should always return the most up
// to date data possible.
//
// TODO(roasbeef): move to diff package perhaps?
// TODO(roasbeef): move publish txn here?
type BlockChainIO interface {
// GetBestBlock returns the current height and block hash of the valid
// most-work chain the implementation is aware of.
GetBestBlock() (*chainhash.Hash, int32, error)
// GetUtxo attempts to return the passed outpoint if it's still a
// member of the utxo set. The passed height hint should be the "birth
// height" of the passed outpoint. The script passed should be the
// script that the outpoint creates. In the case that the output is in
// the UTXO set, then the output corresponding to that output is
// returned. Otherwise, a non-nil error will be returned.
// As for some backends this call can initiate a rescan, the passed
// cancel channel can be closed to abort the call.
GetUtxo(op *wire.OutPoint, pkScript []byte, heightHint uint32,
cancel <-chan struct{}) (*wire.TxOut, error)
// GetBlockHash returns the hash of the block in the best blockchain
// at the given height.
GetBlockHash(blockHeight int64) (*chainhash.Hash, error)
// GetBlock returns the block in the main chain identified by the given
// hash.
GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error)
// GetBlockHeader returns the block header for the given block hash.
GetBlockHeader(blockHash *chainhash.Hash) (*wire.BlockHeader, error)
}
// MessageSigner represents an abstract object capable of signing arbitrary
// messages. The capabilities of this interface are used to sign announcements
// to the network, or just arbitrary messages that leverage the wallet's keys
// to attest to some message.
type MessageSigner interface {
// SignMessage attempts to sign a target message with the private key
// described in the key locator. If the target private key is unable to
// be found, then an error will be returned. The actual digest signed is
// the single or double SHA-256 of the passed message.
SignMessage(keyLoc keychain.KeyLocator, msg []byte,
doubleHash bool) (*ecdsa.Signature, error)
}
// AddrWithKey wraps a normal addr, but also includes the internal key for the
// delivery addr if known.
type AddrWithKey struct {
lnwire.DeliveryAddress
InternalKey fn.Option[keychain.KeyDescriptor]
// TODO(roasbeef): consolidate w/ instance in chan closer
}
// InternalKeyForAddr returns the internal key associated with a taproot
// address.
func InternalKeyForAddr(wallet WalletController, netParams *chaincfg.Params,
deliveryScript []byte) (fn.Option[keychain.KeyDescriptor], error) {
none := fn.None[keychain.KeyDescriptor]()
pkScript, err := txscript.ParsePkScript(deliveryScript)
if err != nil {
return none, err
}
addr, err := pkScript.Address(netParams)
if err != nil {
return none, err
}
// If it's not a taproot address, we don't require to know the internal
// key in the first place. So we don't return an error here, but also no
// internal key.
_, isTaproot := addr.(*btcutil.AddressTaproot)
if !isTaproot {
return none, nil
}
walletAddr, err := wallet.AddressInfo(addr)
if err != nil {
// If the error is that the address can't be found, it is not
// an error. This happens when any channel which is not a custom
// taproot channel is cooperatively closed to an external P2TR
// address. In this case there is no internal key associated
// with the address. Callers can use the .Option() method to get
// an option value.
var managerErr waddrmgr.ManagerError
if errors.As(err, &managerErr) &&
managerErr.ErrorCode == waddrmgr.ErrAddressNotFound {
return none, nil
}
return none, err
}
// No wallet addr. No error, but we'll return an nil error value here,
// as callers can use the .Option() method to get an option value.
if walletAddr == nil {
return none, nil
}
pubKeyAddr, ok := walletAddr.(waddrmgr.ManagedPubKeyAddress)
if !ok {
return none, fmt.Errorf("expected pubkey addr, got %T",
pubKeyAddr)
}
_, derivationPath, _ := pubKeyAddr.DerivationInfo()
return fn.Some[keychain.KeyDescriptor](keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamily(derivationPath.Account),
Index: derivationPath.Index,
},
PubKey: pubKeyAddr.PubKey(),
}), nil
}
// WalletDriver represents a "driver" for a particular concrete
// WalletController implementation. A driver is identified by a globally unique
// string identifier along with a 'New()' method which is responsible for
// initializing a particular WalletController concrete implementation.
type WalletDriver struct {
// WalletType is a string which uniquely identifies the
// WalletController that this driver, drives.
WalletType string
// New creates a new instance of a concrete WalletController
// implementation given a variadic set up arguments. The function takes
// a variadic number of interface parameters in order to provide
// initialization flexibility, thereby accommodating several potential
// WalletController implementations.
New func(args ...interface{}) (WalletController, error)
// BackEnds returns a list of available chain service drivers for the
// wallet driver. This could be e.g. bitcoind, btcd, neutrino, etc.
BackEnds func() []string
}
var (
wallets = make(map[string]*WalletDriver)
registerMtx sync.Mutex
)
// RegisteredWallets returns a slice of all currently registered notifiers.
//
// NOTE: This function is safe for concurrent access.
func RegisteredWallets() []*WalletDriver {
registerMtx.Lock()
defer registerMtx.Unlock()
registeredWallets := make([]*WalletDriver, 0, len(wallets))
for _, wallet := range wallets {
registeredWallets = append(registeredWallets, wallet)
}
return registeredWallets
}
// RegisterWallet registers a WalletDriver which is capable of driving a
// concrete WalletController interface. In the case that this driver has
// already been registered, an error is returned.
//
// NOTE: This function is safe for concurrent access.
func RegisterWallet(driver *WalletDriver) error {
registerMtx.Lock()
defer registerMtx.Unlock()
if _, ok := wallets[driver.WalletType]; ok {
return fmt.Errorf("wallet already registered")
}
wallets[driver.WalletType] = driver
return nil
}
// SupportedWallets returns a slice of strings that represents the wallet
// drivers that have been registered and are therefore supported.
//
// NOTE: This function is safe for concurrent access.
func SupportedWallets() []string {
registerMtx.Lock()
defer registerMtx.Unlock()
supportedWallets := make([]string, 0, len(wallets))
for walletName := range wallets {
supportedWallets = append(supportedWallets, walletName)
}
return supportedWallets
}
// FetchFundingTxWrapper is a wrapper around FetchFundingTx, except that it will
// exit when the supplied quit channel is closed.
func FetchFundingTxWrapper(chain BlockChainIO, chanID *lnwire.ShortChannelID,
quit chan struct{}) (*wire.MsgTx, error) {
txChan := make(chan *wire.MsgTx, 1)
errChan := make(chan error, 1)
go func() {
tx, err := FetchFundingTx(chain, chanID)
if err != nil {
errChan <- err
return
}
txChan <- tx
}()
select {
case tx := <-txChan:
return tx, nil
case err := <-errChan:
return nil, err
case <-quit:
return nil, fmt.Errorf("quit channel passed to " +
"lnwallet.FetchFundingTxWrapper has been closed")
}
}
// FetchFundingTx uses the given BlockChainIO to fetch and return the funding
// transaction identified by the passed short channel ID.
//
// TODO(roasbeef): replace with call to GetBlockTransaction? (would allow to
// later use getblocktxn).
func FetchFundingTx(chain BlockChainIO,
chanID *lnwire.ShortChannelID) (*wire.MsgTx, error) {
// First fetch the block hash by the block number encoded, then use
// that hash to fetch the block itself.
blockNum := int64(chanID.BlockHeight)
blockHash, err := chain.GetBlockHash(blockNum)
if err != nil {
return nil, err
}
fundingBlock, err := chain.GetBlock(blockHash)
if err != nil {
return nil, err
}
// As a sanity check, ensure that the advertised transaction index is
// within the bounds of the total number of transactions within a
// block.
numTxns := uint32(len(fundingBlock.Transactions))
if chanID.TxIndex > numTxns-1 {
return nil, fmt.Errorf("tx_index=#%v "+
"is out of range (max_index=%v), network_chan_id=%v",
chanID.TxIndex, numTxns-1, chanID)
}
return fundingBlock.Transactions[chanID.TxIndex].Copy(), nil
}
// FetchPKScriptWithQuit fetches the output script for the given SCID and exits
// early with an error if the provided quit channel is closed before
// completion.
func FetchPKScriptWithQuit(chain BlockChainIO, chanID *lnwire.ShortChannelID,
quit chan struct{}) ([]byte, error) {
tx, err := FetchFundingTxWrapper(chain, chanID, quit)
if err != nil {
return nil, err
}
outputLocator := chanvalidate.ShortChanIDChanLocator{
ID: *chanID,
}
output, _, err := outputLocator.Locate(tx)
if err != nil {
return nil, err
}
return output.PkScript, nil
}
package lnwallet
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)
// walletLog is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var walletLog btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("LNWL", nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
walletLog = logger
chainfee.UseLogger(logger)
}
package lnwallet
import (
"encoding/hex"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/waddrmgr"
base "github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/wallet/txauthor"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/tlv"
"github.com/stretchr/testify/mock"
)
var (
CoinPkScript, _ = hex.DecodeString(
"001431df1bde03c074d0cf21ea2529427e1499b8f1de",
)
)
// mockWalletController is a mock implementation of the WalletController
// interface. It let's us mock the interaction with the bitcoin network.
type mockWalletController struct {
RootKey *btcec.PrivateKey
PublishedTransactions chan *wire.MsgTx
index uint32
Utxos []*Utxo
}
// A compile time check to ensure that mockWalletController implements the
// WalletController.
var _ WalletController = (*mockWalletController)(nil)
// BackEnd returns "mock" to signify a mock wallet controller.
func (w *mockWalletController) BackEnd() string {
return "mock"
}
// FetchOutpointInfo will be called to get info about the inputs to the funding
// transaction.
func (w *mockWalletController) FetchOutpointInfo(
prevOut *wire.OutPoint) (*Utxo, error) {
utxo := &Utxo{
AddressType: WitnessPubKey,
Value: 10 * btcutil.SatoshiPerBitcoin,
PkScript: []byte("dummy"),
Confirmations: 1,
OutPoint: *prevOut,
}
return utxo, nil
}
// ScriptForOutput returns the address, witness program and redeem script for a
// given UTXO. An error is returned if the UTXO does not belong to our wallet or
// it is not a managed pubKey address.
func (w *mockWalletController) ScriptForOutput(*wire.TxOut) (
waddrmgr.ManagedPubKeyAddress, []byte, []byte, error) {
return nil, nil, nil, nil
}
// ConfirmedBalance currently returns dummy values.
func (w *mockWalletController) ConfirmedBalance(int32, string) (btcutil.Amount,
error) {
return 0, nil
}
// NewAddress is called to get new addresses for delivery, change etc.
func (w *mockWalletController) NewAddress(AddressType, bool,
string) (btcutil.Address, error) {
addr, _ := btcutil.NewAddressPubKey(
w.RootKey.PubKey().SerializeCompressed(),
&chaincfg.MainNetParams,
)
return addr, nil
}
// LastUnusedAddress currently returns dummy values.
func (w *mockWalletController) LastUnusedAddress(AddressType,
string) (btcutil.Address, error) {
return nil, nil
}
// IsOurAddress currently returns a dummy value.
func (w *mockWalletController) IsOurAddress(btcutil.Address) bool {
return false
}
// AddressInfo currently returns a dummy value.
func (w *mockWalletController) AddressInfo(
btcutil.Address) (waddrmgr.ManagedAddress, error) {
return nil, nil
}
// ListAccounts currently returns a dummy value.
func (w *mockWalletController) ListAccounts(string,
*waddrmgr.KeyScope) ([]*waddrmgr.AccountProperties, error) {
return nil, nil
}
// RequiredReserve currently returns a dummy value.
func (w *mockWalletController) RequiredReserve(uint32) btcutil.Amount {
return 0
}
// ListAddresses currently returns a dummy value.
func (w *mockWalletController) ListAddresses(string,
bool) (AccountAddressMap, error) {
return nil, nil
}
// ImportAccount currently returns a dummy value.
func (w *mockWalletController) ImportAccount(string, *hdkeychain.ExtendedKey,
uint32, *waddrmgr.AddressType, bool) (*waddrmgr.AccountProperties,
[]btcutil.Address, []btcutil.Address, error) {
return nil, nil, nil, nil
}
// ImportPublicKey currently returns a dummy value.
func (w *mockWalletController) ImportPublicKey(*btcec.PublicKey,
waddrmgr.AddressType) error {
return nil
}
// ImportTaprootScript currently returns a dummy value.
func (w *mockWalletController) ImportTaprootScript(waddrmgr.KeyScope,
*waddrmgr.Tapscript) (waddrmgr.ManagedAddress, error) {
return nil, nil
}
// SendOutputs currently returns dummy values.
func (w *mockWalletController) SendOutputs(fn.Set[wire.OutPoint], []*wire.TxOut,
chainfee.SatPerKWeight, int32, string,
base.CoinSelectionStrategy) (*wire.MsgTx, error) {
return nil, nil
}
// CreateSimpleTx currently returns dummy values.
func (w *mockWalletController) CreateSimpleTx(fn.Set[wire.OutPoint],
[]*wire.TxOut, chainfee.SatPerKWeight, int32,
base.CoinSelectionStrategy, bool) (*txauthor.AuthoredTx, error) {
return nil, nil
}
// ListUnspentWitness is called by the wallet when doing coin selection. We just
// need one unspent for the funding transaction.
func (w *mockWalletController) ListUnspentWitness(int32, int32,
string) ([]*Utxo, error) {
// If the mock already has a list of utxos, return it.
if w.Utxos != nil {
return w.Utxos, nil
}
// Otherwise create one to return.
utxo := &Utxo{
AddressType: WitnessPubKey,
Value: btcutil.Amount(10 * btcutil.SatoshiPerBitcoin),
PkScript: CoinPkScript,
OutPoint: wire.OutPoint{
Hash: chainhash.Hash{},
Index: w.index,
},
}
atomic.AddUint32(&w.index, 1)
var ret []*Utxo
ret = append(ret, utxo)
return ret, nil
}
// ListTransactionDetails currently returns dummy values.
func (w *mockWalletController) ListTransactionDetails(int32, int32,
string, uint32, uint32) ([]*TransactionDetail, uint64, uint64, error) {
return nil, 0, 0, nil
}
// LeaseOutput returns the current time and a nil error.
func (w *mockWalletController) LeaseOutput(wtxmgr.LockID, wire.OutPoint,
time.Duration) (time.Time, error) {
return time.Now(), nil
}
// ReleaseOutput currently does nothing.
func (w *mockWalletController) ReleaseOutput(wtxmgr.LockID,
wire.OutPoint) error {
return nil
}
func (w *mockWalletController) ListLeasedOutputs() (
[]*base.ListLeasedOutputResult, error) {
return nil, nil
}
// FundPsbt currently does nothing.
func (w *mockWalletController) FundPsbt(*psbt.Packet, int32,
chainfee.SatPerKWeight, string, *waddrmgr.KeyScope,
base.CoinSelectionStrategy, func(utxo wtxmgr.Credit) bool) (int32,
error) {
return 0, nil
}
// SignPsbt currently does nothing.
func (w *mockWalletController) SignPsbt(*psbt.Packet) ([]uint32, error) {
return nil, nil
}
// FinalizePsbt currently does nothing.
func (w *mockWalletController) FinalizePsbt(_ *psbt.Packet, _ string) error {
return nil
}
// DecorateInputs currently does nothing.
func (w *mockWalletController) DecorateInputs(*psbt.Packet, bool) error {
return nil
}
// PublishTransaction sends a transaction to the PublishedTransactions chan.
func (w *mockWalletController) PublishTransaction(tx *wire.MsgTx,
_ string) error {
w.PublishedTransactions <- tx
return nil
}
// GetTransactionDetails currently does nothing.
func (w *mockWalletController) GetTransactionDetails(*chainhash.Hash) (
*TransactionDetail, error) {
return nil, nil
}
// LabelTransaction currently does nothing.
func (w *mockWalletController) LabelTransaction(chainhash.Hash, string,
bool) error {
return nil
}
// SubscribeTransactions currently does nothing.
func (w *mockWalletController) SubscribeTransactions() (TransactionSubscription,
error) {
return nil, nil
}
// IsSynced currently returns dummy values.
func (w *mockWalletController) IsSynced() (bool, int64, error) {
return true, int64(0), nil
}
// GetRecoveryInfo currently returns dummy values.
func (w *mockWalletController) GetRecoveryInfo() (bool, float64, error) {
return true, float64(1), nil
}
// Start currently does nothing.
func (w *mockWalletController) Start() error {
return nil
}
// Stop currently does nothing.
func (w *mockWalletController) Stop() error {
return nil
}
func (w *mockWalletController) FetchTx(chainhash.Hash) (*wire.MsgTx, error) {
return nil, nil
}
func (w *mockWalletController) RemoveDescendants(*wire.MsgTx) error {
return nil
}
// FetchDerivationInfo queries for the wallet's knowledge of the passed
// pkScript and constructs the derivation info and returns it.
func (w *mockWalletController) FetchDerivationInfo(
pkScript []byte) (*psbt.Bip32Derivation, error) {
return nil, nil
}
func (w *mockWalletController) CheckMempoolAcceptance(tx *wire.MsgTx) error {
return nil
}
// mockChainNotifier is a mock implementation of the ChainNotifier interface.
type mockChainNotifier struct {
SpendChan chan *chainntnfs.SpendDetail
EpochChan chan *chainntnfs.BlockEpoch
ConfChan chan *chainntnfs.TxConfirmation
}
// RegisterConfirmationsNtfn returns a ConfirmationEvent that contains a channel
// that the tx confirmation will go over.
func (c *mockChainNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
pkScript []byte, numConfs, heightHint uint32,
opts ...chainntnfs.NotifierOption) (*chainntnfs.ConfirmationEvent,
error) {
return &chainntnfs.ConfirmationEvent{
Confirmed: c.ConfChan,
Cancel: func() {},
}, nil
}
// RegisterSpendNtfn returns a SpendEvent that contains a channel that the spend
// details will go over.
func (c *mockChainNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) {
return &chainntnfs.SpendEvent{
Spend: c.SpendChan,
Cancel: func() {},
}, nil
}
// RegisterBlockEpochNtfn returns a BlockEpochEvent that contains a channel that
// block epochs will go over.
func (c *mockChainNotifier) RegisterBlockEpochNtfn(
blockEpoch *chainntnfs.BlockEpoch) (*chainntnfs.BlockEpochEvent,
error) {
return &chainntnfs.BlockEpochEvent{
Epochs: c.EpochChan,
Cancel: func() {},
}, nil
}
// Start currently returns a dummy value.
func (c *mockChainNotifier) Start() error {
return nil
}
// Started currently returns a dummy value.
func (c *mockChainNotifier) Started() bool {
return true
}
// Stop currently returns a dummy value.
func (c *mockChainNotifier) Stop() error {
return nil
}
type mockChainIO struct{}
func (*mockChainIO) GetBestBlock() (*chainhash.Hash, int32, error) {
return nil, 0, nil
}
func (*mockChainIO) GetUtxo(op *wire.OutPoint, _ []byte,
heightHint uint32, _ <-chan struct{}) (*wire.TxOut, error) {
return nil, nil
}
func (*mockChainIO) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) {
return nil, nil
}
func (*mockChainIO) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock,
error) {
return nil, nil
}
func (*mockChainIO) GetBlockHeader(
blockHash *chainhash.Hash) (*wire.BlockHeader, error) {
return nil, nil
}
type MockAuxLeafStore struct{}
// A compile time check to ensure that MockAuxLeafStore implements the
// AuxLeafStore interface.
var _ AuxLeafStore = (*MockAuxLeafStore)(nil)
// FetchLeavesFromView attempts to fetch the auxiliary leaves that
// correspond to the passed aux blob, and pending original (unfiltered)
// HTLC view.
func (*MockAuxLeafStore) FetchLeavesFromView(
_ CommitDiffAuxInput) fn.Result[CommitDiffAuxResult] {
return fn.Ok(CommitDiffAuxResult{})
}
// FetchLeavesFromCommit attempts to fetch the auxiliary leaves that
// correspond to the passed aux blob, and an existing channel
// commitment.
func (*MockAuxLeafStore) FetchLeavesFromCommit(_ AuxChanState,
_ channeldb.ChannelCommitment, _ CommitmentKeyRing,
_ lntypes.ChannelParty) fn.Result[CommitDiffAuxResult] {
return fn.Ok(CommitDiffAuxResult{})
}
// FetchLeavesFromRevocation attempts to fetch the auxiliary leaves
// from a channel revocation that stores balance + blob information.
func (*MockAuxLeafStore) FetchLeavesFromRevocation(
_ *channeldb.RevocationLog) fn.Result[CommitDiffAuxResult] {
return fn.Ok(CommitDiffAuxResult{})
}
// ApplyHtlcView serves as the state transition function for the custom
// channel's blob. Given the old blob, and an HTLC view, then a new
// blob should be returned that reflects the pending updates.
func (*MockAuxLeafStore) ApplyHtlcView(
_ CommitDiffAuxInput) fn.Result[fn.Option[tlv.Blob]] {
return fn.Ok(fn.None[tlv.Blob]())
}
// EmptyMockJobHandler is a mock job handler that just sends an empty response
// to all jobs.
func EmptyMockJobHandler(jobs []AuxSigJob) {
for _, sigJob := range jobs {
sigJob.Resp <- AuxSigJobResp{}
}
}
// MockAuxSigner is a mock implementation of the AuxSigner interface.
type MockAuxSigner struct {
mock.Mock
jobHandlerFunc func([]AuxSigJob)
}
// NewAuxSignerMock creates a new mock aux signer with the given job handler.
func NewAuxSignerMock(jobHandler func([]AuxSigJob)) *MockAuxSigner {
return &MockAuxSigner{
jobHandlerFunc: jobHandler,
}
}
// SubmitSecondLevelSigBatch takes a batch of aux sign jobs and
// processes them asynchronously.
func (a *MockAuxSigner) SubmitSecondLevelSigBatch(chanState AuxChanState,
tx *wire.MsgTx, jobs []AuxSigJob) error {
args := a.Called(chanState, tx, jobs)
if a.jobHandlerFunc != nil {
a.jobHandlerFunc(jobs)
}
return args.Error(0)
}
// PackSigs takes a series of aux signatures and packs them into a
// single blob that can be sent alongside the CommitSig messages.
func (a *MockAuxSigner) PackSigs(
sigs []fn.Option[tlv.Blob]) fn.Result[fn.Option[tlv.Blob]] {
args := a.Called(sigs)
return args.Get(0).(fn.Result[fn.Option[tlv.Blob]])
}
// UnpackSigs takes a packed blob of signatures and returns the
// original signatures for each HTLC, keyed by HTLC index.
func (a *MockAuxSigner) UnpackSigs(
sigs fn.Option[tlv.Blob]) fn.Result[[]fn.Option[tlv.Blob]] {
args := a.Called(sigs)
return args.Get(0).(fn.Result[[]fn.Option[tlv.Blob]])
}
// VerifySecondLevelSigs attempts to synchronously verify a batch of aux
// sig jobs.
func (a *MockAuxSigner) VerifySecondLevelSigs(chanState AuxChanState,
tx *wire.MsgTx, jobs []AuxVerifyJob) error {
args := a.Called(chanState, tx, jobs)
return args.Error(0)
}
type MockAuxContractResolver struct{}
// ResolveContract is called to resolve a contract that needs
// additional information to resolve properly. If no extra information
// is required, a nil Result error is returned.
func (*MockAuxContractResolver) ResolveContract(
ResolutionReq) fn.Result[tlv.Blob] {
return fn.Ok[tlv.Blob](nil)
}
package lnwallet
import (
"bytes"
"fmt"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/shachain"
)
// MusigCommitType is an enum that denotes if this is the local or remote
// commitment.
type MusigCommitType uint8
const (
// LocalMusigCommit denotes that this a session for the local
// commitment.
LocalMusigCommit MusigCommitType = iota
// RemoteMusigCommit denotes that this is a session for the remote
// commitment.
RemoteMusigCommit
)
var (
// ErrSessionNotFinalized is returned when the SignCommit method is
// called for a local commitment, without the session being finalized
// (missing nonce).
ErrSessionNotFinalized = fmt.Errorf("musig2 session not finalized")
)
// tapscriptRootToSignOpt is a function that takes a tapscript root and returns
// a MuSig2 sign opt that'll apply the tweak when signing+verifying.
func tapscriptRootToSignOpt(root chainhash.Hash) musig2.SignOption {
return musig2.WithTaprootSignTweak(root[:])
}
// TapscriptRootToTweak is a helper function that converts a tapscript root
// into a tweak that can be used with the MuSig2 API.
func TapscriptRootToTweak(root chainhash.Hash) input.MuSig2Tweaks {
return input.MuSig2Tweaks{
TaprootTweak: root[:],
}
}
// MusigPartialSig is a wrapper around the base musig2.PartialSignature type
// that also includes information about the set of nonces used, and also the
// signer. This allows us to implement the input.Signature interface, as that
// requires the ability to perform abstract verification based on a public key.
type MusigPartialSig struct {
// sig is the actual musig2 partial signature.
sig *musig2.PartialSignature
// signerNonce is the nonce used by the signer to generate the partial
// signature.
signerNonce lnwire.Musig2Nonce
// combinedNonce is the combined nonce of all signers.
combinedNonce lnwire.Musig2Nonce
// signerKeys is the set of public keys of all signers.
signerKeys []*btcec.PublicKey
// tapscriptTweak is an optional tweak, that if specified, will be used
// instead of the normal BIP 86 tweak when validating the signature.
tapscriptTweak fn.Option[chainhash.Hash]
}
// NewMusigPartialSig creates a new MuSig2 partial signature.
func NewMusigPartialSig(sig *musig2.PartialSignature, signerNonce,
combinedNonce lnwire.Musig2Nonce, signerKeys []*btcec.PublicKey,
tapscriptTweak fn.Option[chainhash.Hash]) *MusigPartialSig {
return &MusigPartialSig{
sig: sig,
signerNonce: signerNonce,
combinedNonce: combinedNonce,
signerKeys: signerKeys,
tapscriptTweak: tapscriptTweak,
}
}
// FromWireSig maps a wire partial sig to this internal type that we'll use to
// perform signature validation.
func (p *MusigPartialSig) FromWireSig(
sig *lnwire.PartialSigWithNonce) *MusigPartialSig {
p.sig = &musig2.PartialSignature{
S: &sig.Sig,
}
p.signerNonce = sig.Nonce
return p
}
// ToWireSig maps the partial signature to something that we can use to write
// out for the wire protocol.
func (p *MusigPartialSig) ToWireSig() *lnwire.PartialSigWithNonce {
return &lnwire.PartialSigWithNonce{
PartialSig: lnwire.NewPartialSig(*p.sig.S),
Nonce: p.signerNonce,
}
}
// Serialize serializes the musig2 partial signature. The serializing includes
// the signer's public nonce _and_ the partial signature. The final signature
// is always 98 bytes in length.
func (p *MusigPartialSig) Serialize() []byte {
var b bytes.Buffer
_ = p.ToWireSig().Encode(&b)
return b.Bytes()
}
// ToSchnorrShell converts the musig partial signature to a regular schnorr.
// This schnorr signature uses a zero value for the 'r' field, so we're just
// only using the last 32-bytes of the signature. This is useful when we need
// to convert an HTLC schnorr signature into something we can send using the
// existing messages.
func (p *MusigPartialSig) ToSchnorrShell() *schnorr.Signature {
var zeroVal btcec.FieldVal
return schnorr.NewSignature(&zeroVal, p.sig.S)
}
// FromSchnorrShell takes a schnorr signature and parses out the last 32 bytes
// as a normal musig2 partial signature.
func (p *MusigPartialSig) FromSchnorrShell(sig *schnorr.Signature) {
var (
partialS btcec.ModNScalar
partialSBytes [32]byte
)
copy(partialSBytes[:], sig.Serialize()[32:])
partialS.SetBytes(&partialSBytes)
p.sig = &musig2.PartialSignature{
S: &partialS,
}
}
// Verify attempts to verify the partial musig2 signature using the passed
// message and signer public key.
//
// NOTE: This implements the input.Signature interface.
func (p *MusigPartialSig) Verify(msg []byte, pub *btcec.PublicKey) bool {
var m [32]byte
copy(m[:], msg)
// If we have a tapscript tweak, then we'll use that as a tweak
// otherwise, we'll fall back to the normal BIP 86 sign tweak.
signOpts := fn.MapOption(tapscriptRootToSignOpt)(
p.tapscriptTweak,
).UnwrapOr(musig2.WithBip86SignTweak())
return p.sig.Verify(
p.signerNonce, p.combinedNonce, p.signerKeys, pub, m,
musig2.WithSortedKeys(), signOpts,
)
}
// MusigNoncePair holds the two nonces needed to sign/verify a new commitment
// state. The signer nonce is the nonce used by the signer (remote nonce), and
// the verification nonce, the nonce used by the verifier (local nonce).
type MusigNoncePair struct {
// SigningNonce is the nonce used by the signer to sign the commitment.
SigningNonce musig2.Nonces
// VerificationNonce is the nonce used by the verifier to verify the
// commitment.
VerificationNonce musig2.Nonces
}
// String returns a string representation of the MusigNoncePair.
func (n *MusigNoncePair) String() string {
return fmt.Sprintf("NoncePair(verification_nonce=%x, "+
"signing_nonce=%x)", n.VerificationNonce.PubNonce[:],
n.SigningNonce.PubNonce[:])
}
// TapscriptRootToTweak is a function that takes a MuSig2 taproot tweak and
// returns the root hash of the tapscript tree.
func muSig2TweakToRoot(tweak input.MuSig2Tweaks) chainhash.Hash {
var root chainhash.Hash
copy(root[:], tweak.TaprootTweak)
return root
}
// MusigSession abstracts over the details of a logical musig session. A single
// session is used for each commitment transactions. The sessions use a JIT
// nonce style, wherein part of the session can be created using only the
// verifier nonce. Once a new state is signed, then the signer nonce is
// generated. Similarly, the verifier then uses the received signer nonce to
// complete the session and verify the incoming signature.
type MusigSession struct {
// session is the backing musig2 session. We'll use this to interact
// with the musig2 signer.
session *input.MuSig2SessionInfo
// combinedNonce is the combined nonce of all signers.
combinedNonce lnwire.Musig2Nonce
// nonces is the set of nonces that'll be used to generate/verify the
// next commitment.
nonces MusigNoncePair
// inputTxOut is the funding input.
inputTxOut *wire.TxOut
// signerKeys is the set of public keys of all signers.
signerKeys []*btcec.PublicKey
// remoteKey is the key desc of the remote key.
remoteKey keychain.KeyDescriptor
// localKey is the key desc of the local key.
localKey keychain.KeyDescriptor
// signer is the signer that'll be used to interact with the musig
// session.
signer input.MuSig2Signer
// commitType tracks if this is the session for the local or remote
// commitment.
commitType MusigCommitType
// tapscriptTweak is an optional tweak, that if specified, will be used
// instead of the normal BIP 86 tweak when creating the MuSig2
// aggregate key and session.
tapscriptTweak fn.Option[input.MuSig2Tweaks]
}
// NewPartialMusigSession creates a new musig2 session given only the
// verification nonce (local nonce), and the other information that has already
// been bound to the session.
func NewPartialMusigSession(verificationNonce musig2.Nonces,
localKey, remoteKey keychain.KeyDescriptor, signer input.MuSig2Signer,
inputTxOut *wire.TxOut, commitType MusigCommitType,
tapscriptTweak fn.Option[input.MuSig2Tweaks]) *MusigSession {
signerKeys := []*btcec.PublicKey{localKey.PubKey, remoteKey.PubKey}
nonces := MusigNoncePair{
VerificationNonce: verificationNonce,
}
return &MusigSession{
nonces: nonces,
remoteKey: remoteKey,
localKey: localKey,
inputTxOut: inputTxOut,
signerKeys: signerKeys,
signer: signer,
commitType: commitType,
tapscriptTweak: tapscriptTweak,
}
}
// FinalizeSession finalizes the session given the signer nonce. This is
// called before signing or verifying a new commitment.
func (m *MusigSession) FinalizeSession(signingNonce musig2.Nonces) error {
var (
localNonce, remoteNonce musig2.Nonces
err error
)
// First, we'll stash the freshly generated signing nonce. Depending on
// who's commitment we're handling, this'll either be our generated
// nonce, or the one we just got from the remote party.
m.nonces.SigningNonce = signingNonce
switch m.commitType {
// If we're making a session for the remote commitment, then the nonce
// we use to sign is actually will be the signing nonce for the
// session, and their nonce the verification nonce.
case RemoteMusigCommit:
localNonce = m.nonces.SigningNonce
remoteNonce = m.nonces.VerificationNonce
// Otherwise, we're generating/receiving a signature for our local
// commitment (to broadcast), so now our verification nonce is the one
// we've already generated, and we want to bind their new signing
// nonce.
case LocalMusigCommit:
localNonce = m.nonces.VerificationNonce
remoteNonce = m.nonces.SigningNonce
}
tweakDesc := m.tapscriptTweak.UnwrapOr(input.MuSig2Tweaks{
TaprootBIP0086Tweak: true,
})
m.session, err = m.signer.MuSig2CreateSession(
input.MuSig2Version100RC2, m.localKey.KeyLocator, m.signerKeys,
&tweakDesc, [][musig2.PubNonceSize]byte{remoteNonce.PubNonce},
&localNonce,
)
if err != nil {
return err
}
// We'll need the raw combined nonces later to be able to verify
// partial signatures, and also combine partial signatures, so we'll
// generate it now ourselves.
aggNonce, err := musig2.AggregateNonces([][musig2.PubNonceSize]byte{
m.nonces.SigningNonce.PubNonce,
m.nonces.VerificationNonce.PubNonce,
})
if err != nil {
return nil
}
m.combinedNonce = aggNonce
return nil
}
// taprootKeyspendSighash generates the sighash for a taproot key spend. As
// this is a musig2 channel output, the keyspend is the only path we can take.
func taprootKeyspendSighash(tx *wire.MsgTx, pkScript []byte,
value int64) ([]byte, error) {
prevOutputFetcher := txscript.NewCannedPrevOutputFetcher(
pkScript, value,
)
sigHashes := txscript.NewTxSigHashes(tx, prevOutputFetcher)
return txscript.CalcTaprootSignatureHash(
sigHashes, txscript.SigHashDefault, tx, 0, prevOutputFetcher,
)
}
// SignCommit signs the passed commitment w/ the current signing (relative
// remote) nonce. Given nonces should only ever be used once, once the method
// returns a new nonce is returned, w/ the existing nonce blanked out.
func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, error) {
switch {
// If we already have a session, then we don't need to finalize as this
// was done up front (symmetric nonce case, like for co-op close).
case m.session == nil && m.commitType == RemoteMusigCommit:
// Before we can sign a new commitment, we'll need to generate
// a fresh nonce that'll be sent along side our signature. With
// the nonce in hand, we can finalize the session.
txHash := tx.TxHash()
signingNonce, err := musig2.GenNonces(
musig2.WithPublicKey(m.localKey.PubKey),
musig2.WithNonceAuxInput(txHash[:]),
)
if err != nil {
return nil, err
}
if err := m.FinalizeSession(*signingNonce); err != nil {
return nil, err
}
// Otherwise, we're trying to make a new commitment transaction without
// an active session, so we'll error out.
case m.session == nil:
return nil, ErrSessionNotFinalized
}
// Next we can sign, we'll need to generate the sighash for their
// commitment transaction.
sigHash, err := taprootKeyspendSighash(
tx, m.inputTxOut.PkScript, m.inputTxOut.Value,
)
if err != nil {
return nil, err
}
// Now that we have our session created, we'll use it to generate the
// initial partial signature over our sighash.
var sigHashMsg [32]byte
copy(sigHashMsg[:], sigHash)
walletLog.Infof("Generating new musig2 sig for session=%x, nonces=%s",
m.session.SessionID[:], m.nonces.String())
sig, err := m.signer.MuSig2Sign(
m.session.SessionID, sigHashMsg, false,
)
if err != nil {
return nil, err
}
tapscriptRoot := fn.MapOption(muSig2TweakToRoot)(m.tapscriptTweak)
return NewMusigPartialSig(
sig, m.session.PublicNonce, m.combinedNonce, m.signerKeys,
tapscriptRoot,
), nil
}
// Refresh is called once we receive a new verification nonce from the remote
// party after sending a signature. This nonce will be coupled within the
// revoke-and-ack message of the remote party.
func (m *MusigSession) Refresh(verificationNonce *musig2.Nonces,
) (*MusigSession, error) {
return NewPartialMusigSession(
*verificationNonce, m.localKey, m.remoteKey, m.signer,
m.inputTxOut, m.commitType, m.tapscriptTweak,
), nil
}
// VerificationNonce returns the current verification nonce for the session.
func (m *MusigSession) VerificationNonce() *musig2.Nonces {
return &m.nonces.VerificationNonce
}
// musigSessionOpts is a set of options that can be used to modify calls to the
// musig session.
type musigSessionOpts struct {
// customRand is an optional custom random source that can be used to
// generate nonces via a counter scheme.
customRand io.Reader
}
// defaultMusigSessionOpts returns the default set of options for the musig
// session.
func defaultMusigSessionOpts() *musigSessionOpts {
return &musigSessionOpts{}
}
// MusigSessionOpt is a functional option that can be used to modify calls to
// the musig session.
type MusigSessionOpt func(*musigSessionOpts)
// WithLocalCounterNonce is used to generate local nonces based on the shachain
// producer and the current height. This allows us to not have to write secret
// nonce state to disk. Instead, we can use this to derive the nonce we need to
// sign and broadcast our own commitment transaction.
func WithLocalCounterNonce(targetHeight uint64,
shaGen shachain.Producer) MusigSessionOpt {
return func(opt *musigSessionOpts) {
nextPreimage, _ := shaGen.AtIndex(targetHeight)
opt.customRand = bytes.NewBuffer(nextPreimage[:])
}
}
// invalidPartialSigError is used to return additional debug information to a
// caller that encounters an invalid partial sig.
type invalidPartialSigError struct {
partialSig []byte
sigHash []byte
signingNonce [musig2.PubNonceSize]byte
verificationNonce [musig2.PubNonceSize]byte
}
// Error returns the error string for the partial sig error.
func (i invalidPartialSigError) Error() string {
return fmt.Sprintf("invalid partial sig: partial_sig=%x, "+
"sig_hash=%x, signing_nonce=%x, verification_nonce=%x",
i.partialSig, i.sigHash, i.signingNonce[:],
i.verificationNonce[:])
}
// VerifyCommitSig attempts to verify the passed partial signature against the
// passed commitment transaction. A keyspend sighash is assumed to generate the
// signed message. As we never re-use nonces, a new verification nonce (our
// relative local nonce) returned to transmit to the remote party, which allows
// them to generate another signature.
func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx,
sig *lnwire.PartialSigWithNonce,
musigOpts ...MusigSessionOpt) (*musig2.Nonces, error) {
opts := defaultMusigSessionOpts()
for _, optFunc := range musigOpts {
optFunc(opts)
}
if sig == nil {
return nil, fmt.Errorf("sig not provided")
}
// Before we can verify the signature, we'll need to finalize the
// session by binding the remote party's provided signing nonce.
if err := m.FinalizeSession(musig2.Nonces{
PubNonce: sig.Nonce,
}); err != nil {
return nil, err
}
// When we verify a commitment signature, we always assume that we're
// verifying a signature on our local commitment. Therefore, we'll use:
// their remote nonce, and also public key.
tapscriptRoot := fn.MapOption(muSig2TweakToRoot)(m.tapscriptTweak)
partialSig := NewMusigPartialSig(
&musig2.PartialSignature{S: &sig.Sig},
m.nonces.SigningNonce.PubNonce, m.combinedNonce, m.signerKeys,
tapscriptRoot,
)
// With the partial sig loaded with the proper context, we'll now
// generate the sighash that the remote party should have signed.
sigHash, err := taprootKeyspendSighash(
commitTx, m.inputTxOut.PkScript, m.inputTxOut.Value,
)
if err != nil {
return nil, err
}
walletLog.Infof("Verifying new musig2 sig for session=%x, nonce=%s",
m.session.SessionID[:], m.nonces.String())
if partialSig == nil {
return nil, fmt.Errorf("partial sig not set")
}
if !partialSig.Verify(sigHash, m.remoteKey.PubKey) {
return nil, &invalidPartialSigError{
partialSig: partialSig.Serialize(),
sigHash: sigHash,
verificationNonce: m.nonces.VerificationNonce.PubNonce,
signingNonce: m.nonces.SigningNonce.PubNonce,
}
}
nonceOpts := []musig2.NonceGenOption{
musig2.WithPublicKey(m.localKey.PubKey),
}
if opts.customRand != nil {
nonceOpts = append(
nonceOpts, musig2.WithCustomRand(opts.customRand),
)
}
// At this point, we know that their signature is valid, so we'll
// generate another verification nonce for them, so they can generate a
// new state transition.
nextVerificationNonce, err := musig2.GenNonces(nonceOpts...)
if err != nil {
return nil, fmt.Errorf("unable to gen new nonce: %w", err)
}
return nextVerificationNonce, nil
}
// CombineSigs combines the passed partial signatures into a valid schnorr
// signature.
func (m *MusigSession) CombineSigs(sigs ...*musig2.PartialSignature,
) (*schnorr.Signature, error) {
sig, _, err := m.signer.MuSig2CombineSig(
m.session.SessionID, sigs,
)
if err != nil {
return nil, err
}
return sig, nil
}
// MusigSessionCfg is used to create a new musig2 pair session. It contains the
// keys for both parties, as well as their initial verification nonces.
type MusigSessionCfg struct {
// LocalKey is a key desc for the local key.
LocalKey keychain.KeyDescriptor
// RemoteKey is a key desc for the remote key.
RemoteKey keychain.KeyDescriptor
// LocalNonce is the local party's initial verification nonce.
LocalNonce musig2.Nonces
// RemoteNonce is the remote party's initial verification nonce.
RemoteNonce musig2.Nonces
// Signer is the signer that will be used to generate the session.
Signer input.MuSig2Signer
// InputTxOut is the output that we're signing for. This will be the
// funding input.
InputTxOut *wire.TxOut
// TapscriptTweak is an optional tweak that can be used to modify the
// MuSig2 public key used in the session.
TapscriptTweak fn.Option[chainhash.Hash]
}
// MusigPairSession houses the two musig2 sessions needed to do funding and
// drive forward the state machine. The local session is used to verify
// incoming commitment states. The remote session is used to propose new
// commitment states to the remote party.
type MusigPairSession struct {
// LocalSession is the local party's musig2 session.
LocalSession *MusigSession
// RemoteSession is the remote party's musig2 session.
RemoteSession *MusigSession
// signer is the signer that will be used to drive the session.
signer input.MuSig2Signer
}
// NewMusigPairSession creates a new musig2 pair session.
func NewMusigPairSession(cfg *MusigSessionCfg) *MusigPairSession {
// Given the config passed in, we'll now create our two sessions: one
// for the local commit, and one for the remote commit.
//
// Both sessions will be created using only the verification nonce for
// the local+remote party.
tapscriptTweak := fn.MapOption(TapscriptRootToTweak)(cfg.TapscriptTweak)
localSession := NewPartialMusigSession(
cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer,
cfg.InputTxOut, LocalMusigCommit, tapscriptTweak,
)
remoteSession := NewPartialMusigSession(
cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer,
cfg.InputTxOut, RemoteMusigCommit, tapscriptTweak,
)
return &MusigPairSession{
LocalSession: localSession,
RemoteSession: remoteSession,
signer: cfg.Signer,
}
}
package lnwallet
import (
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/mempool"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// RoutingFee100PercentUpTo is the cut-off amount we allow 100% fees to
// be charged up to.
RoutingFee100PercentUpTo = lnwire.NewMSatFromSatoshis(1_000)
)
const (
// DefaultRoutingFeePercentage is the default off-chain routing fee we
// allow to be charged for a payment over the RoutingFee100PercentUpTo
// size.
DefaultRoutingFeePercentage lnwire.MilliSatoshi = 5
)
// DefaultRoutingFeeLimitForAmount returns the default off-chain routing fee
// limit lnd uses if the user does not specify a limit manually. The fee is
// amount dependent because of the base routing fee that is set on many
// channels. For example the default base fee is 1 satoshi. So sending a payment
// of one satoshi will cost 1 satoshi in fees over most channels, which comes to
// a fee of 100%. That's why for very small amounts we allow 100% fee.
func DefaultRoutingFeeLimitForAmount(a lnwire.MilliSatoshi) lnwire.MilliSatoshi {
// Allow 100% fees up to a certain amount to accommodate for base fees.
if a <= RoutingFee100PercentUpTo {
return a
}
// Everything larger than the cut-off amount will get a default fee
// percentage.
return a * DefaultRoutingFeePercentage / 100
}
// DustLimitForSize retrieves the dust limit for a given pkscript size. Given
// the size, it automatically determines whether the script is a witness script
// or not. It calls btcd's GetDustThreshold method under the hood. It must be
// called with a proper size parameter or else a panic occurs.
func DustLimitForSize(scriptSize int) btcutil.Amount {
var (
dustlimit btcutil.Amount
pkscript []byte
)
// With the size of the script, determine which type of pkscript to
// create. This will be used in the call to GetDustThreshold. We pass
// in an empty byte slice since the contents of the script itself don't
// matter.
switch scriptSize {
case input.P2WPKHSize:
pkscript, _ = input.WitnessPubKeyHash([]byte{})
case input.P2WSHSize:
pkscript, _ = input.WitnessScriptHash([]byte{})
case input.P2SHSize:
pkscript, _ = input.GenerateP2SH([]byte{})
case input.P2PKHSize:
pkscript, _ = input.GenerateP2PKH([]byte{})
case input.UnknownWitnessSize:
pkscript, _ = input.GenerateUnknownWitness()
default:
panic("invalid script size")
}
// Call GetDustThreshold with a TxOut containing the generated
// pkscript.
txout := &wire.TxOut{PkScript: pkscript}
dustlimit = btcutil.Amount(mempool.GetDustThreshold(txout))
return dustlimit
}
// DustLimitUnknownWitness returns the dust limit for an UnknownWitnessSize.
func DustLimitUnknownWitness() btcutil.Amount {
return DustLimitForSize(input.UnknownWitnessSize)
}
package lnwallet
import (
"crypto/sha256"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
)
// updateType is the exact type of an entry within the shared HTLC log.
type updateType uint8
const (
// Add is an update type that adds a new HTLC entry into the log.
// Either side can add a new pending HTLC by adding a new Add entry
// into their update log.
Add updateType = iota
// Fail is an update type which removes a prior HTLC entry from the
// log. Adding a Fail entry to one's log will modify the _remote_
// party's update log once a new commitment view has been evaluated
// which contains the Fail entry.
Fail
// MalformedFail is an update type which removes a prior HTLC entry
// from the log. Adding a MalformedFail entry to one's log will modify
// the _remote_ party's update log once a new commitment view has been
// evaluated which contains the MalformedFail entry. The difference
// from Fail type lie in the different data we have to store.
MalformedFail
// Settle is an update type which settles a prior HTLC crediting the
// balance of the receiving node. Adding a Settle entry to a log will
// result in the settle entry being removed on the log as well as the
// original add entry from the remote party's log after the next state
// transition.
Settle
// FeeUpdate is an update type sent by the channel initiator that
// updates the fee rate used when signing the commitment transaction.
FeeUpdate
)
// String returns a human readable string that uniquely identifies the target
// update type.
func (u updateType) String() string {
switch u {
case Add:
return "Add"
case Fail:
return "Fail"
case MalformedFail:
return "MalformedFail"
case Settle:
return "Settle"
case FeeUpdate:
return "FeeUpdate"
default:
return "<unknown type>"
}
}
// paymentDescriptor represents a commitment state update which either adds,
// settles, or removes an HTLC. paymentDescriptors encapsulate all necessary
// metadata w.r.t to an HTLC, and additional data pairing a settle message to
// the original added HTLC.
//
// TODO(roasbeef): LogEntry interface??
// - need to separate attrs for cancel/add/settle/feeupdate
type paymentDescriptor struct {
// ChanID is the ChannelID of the LightningChannel that this
// paymentDescriptor belongs to. We track this here so we can
// reconstruct the Messages that this paymentDescriptor is built from.
ChanID lnwire.ChannelID
// RHash is the payment hash for this HTLC. The HTLC can be settled iff
// the preimage to this hash is presented.
RHash PaymentHash
// RPreimage is the preimage that settles the HTLC pointed to within the
// log by the ParentIndex.
RPreimage PaymentHash
// Timeout is the absolute timeout in blocks, after which this HTLC
// expires.
Timeout uint32
// Amount is the HTLC amount in milli-satoshis.
Amount lnwire.MilliSatoshi
// LogIndex is the log entry number that his HTLC update has within the
// log. Depending on if IsIncoming is true, this is either an entry the
// remote party added, or one that we added locally.
LogIndex uint64
// HtlcIndex is the index within the main update log for this HTLC.
// Entries within the log of type Add will have this field populated,
// as other entries will point to the entry via this counter.
//
// NOTE: This field will only be populate if EntryType is Add.
HtlcIndex uint64
// ParentIndex is the HTLC index of the entry that this update settles
// or times out.
//
// NOTE: This field will only be populate if EntryType is Fail or
// Settle.
ParentIndex uint64
// SourceRef points to an Add update in a forwarding package owned by
// this channel.
//
// NOTE: This field will only be populated if EntryType is Fail or
// Settle.
SourceRef *channeldb.AddRef
// DestRef points to a Fail/Settle update in another link's forwarding
// package.
//
// NOTE: This field will only be populated if EntryType is Fail or
// Settle, and the forwarded Add successfully included in an outgoing
// link's commitment txn.
DestRef *channeldb.SettleFailRef
// OpenCircuitKey references the incoming Chan/HTLC ID of an Add HTLC
// packet delivered by the switch.
//
// NOTE: This field is only populated for payment descriptors in the
// *local* update log, and if the Add packet was delivered by the
// switch.
OpenCircuitKey *models.CircuitKey
// ClosedCircuitKey references the incoming Chan/HTLC ID of the Add HTLC
// that opened the circuit.
//
// NOTE: This field is only populated for payment descriptors in the
// *local* update log, and if settle/fails have a committed circuit in
// the circuit map.
ClosedCircuitKey *models.CircuitKey
// localOutputIndex is the output index of this HTLc output in the
// commitment transaction of the local node.
//
// NOTE: If the output is dust from the PoV of the local commitment
// chain, then this value will be -1.
localOutputIndex int32
// remoteOutputIndex is the output index of this HTLC output in the
// commitment transaction of the remote node.
//
// NOTE: If the output is dust from the PoV of the remote commitment
// chain, then this value will be -1.
remoteOutputIndex int32
// sig is the signature for the second-level HTLC transaction that
// spends the version of this HTLC on the commitment transaction of the
// local node. This signature is generated by the remote node and
// stored by the local node in the case that local node needs to
// broadcast their commitment transaction.
sig input.Signature
// addCommitHeight[Remote|Local] encodes the height of the commitment
// which included this HTLC on either the remote or local commitment
// chain. This value is used to determine when an HTLC is fully
// "locked-in".
addCommitHeights lntypes.Dual[uint64]
// removeCommitHeight[Remote|Local] encodes the height of the
// commitment which removed the parent pointer of this
// paymentDescriptor either due to a timeout or a settle. Once both
// these heights are below the tail of both chains, the log entries can
// safely be removed.
removeCommitHeights lntypes.Dual[uint64]
// OnionBlob is an opaque blob which is used to complete multi-hop
// routing.
//
// NOTE: Populated only on add payment descriptor entry types.
OnionBlob [lnwire.OnionPacketSize]byte
// ShaOnionBlob is a sha of the onion blob.
//
// NOTE: Populated only in payment descriptor with MalformedFail type.
ShaOnionBlob [sha256.Size]byte
// FailReason stores the reason why a particular payment was canceled.
//
// NOTE: Populate only in fail payment descriptor entry types.
FailReason []byte
// FailCode stores the code why a particular payment was canceled.
//
// NOTE: Populated only in payment descriptor with MalformedFail type.
FailCode lnwire.FailCode
// [our|their|]PkScript are the raw public key scripts that encodes the
// redemption rules for this particular HTLC. These fields will only be
// populated iff the EntryType of this paymentDescriptor is Add.
// ourPkScript is the ourPkScript from the context of our local
// commitment chain. theirPkScript is the latest pkScript from the
// context of the remote commitment chain.
//
// NOTE: These values may change within the logs themselves, however,
// they'll stay consistent within the commitment chain entries
// themselves.
ourPkScript []byte
ourWitnessScript []byte
theirPkScript []byte
theirWitnessScript []byte
// EntryType denotes the exact type of the paymentDescriptor. In the
// case of a Timeout, or Settle type, then the Parent field will point
// into the log to the HTLC being modified.
EntryType updateType
// isForwarded denotes if an incoming HTLC has been forwarded to any
// possible upstream peers in the route.
isForwarded bool
// BlindingPoint is an optional ephemeral key used in route blinding.
// This value is set for nodes that are relaying payments inside of a
// blinded route (ie, not the introduction node) from update_add_htlc's
// TLVs.
BlindingPoint lnwire.BlindingPointRecord
// CustomRecords also stores the set of optional custom records that
// may have been attached to a sent HTLC.
CustomRecords lnwire.CustomRecords
}
// toLogUpdate recovers the underlying LogUpdate from the paymentDescriptor.
// This operation is lossy and will forget some extra information tracked by the
// paymentDescriptor but the function is total in that all paymentDescriptors
// can be converted back to LogUpdates.
func (pd *paymentDescriptor) toLogUpdate() channeldb.LogUpdate {
var msg lnwire.Message
switch pd.EntryType {
case Add:
msg = &lnwire.UpdateAddHTLC{
ChanID: pd.ChanID,
ID: pd.HtlcIndex,
Amount: pd.Amount,
PaymentHash: pd.RHash,
Expiry: pd.Timeout,
OnionBlob: pd.OnionBlob,
BlindingPoint: pd.BlindingPoint,
CustomRecords: pd.CustomRecords.Copy(),
}
case Settle:
msg = &lnwire.UpdateFulfillHTLC{
ChanID: pd.ChanID,
ID: pd.ParentIndex,
PaymentPreimage: pd.RPreimage,
}
case Fail:
msg = &lnwire.UpdateFailHTLC{
ChanID: pd.ChanID,
ID: pd.ParentIndex,
Reason: pd.FailReason,
}
case MalformedFail:
msg = &lnwire.UpdateFailMalformedHTLC{
ChanID: pd.ChanID,
ID: pd.ParentIndex,
ShaOnionBlob: pd.ShaOnionBlob,
FailureCode: pd.FailCode,
}
case FeeUpdate:
// The Amount field holds the feerate denominated in
// msat. Since feerates are only denominated in sat/kw,
// we can convert it without loss of precision.
msg = &lnwire.UpdateFee{
ChanID: pd.ChanID,
FeePerKw: uint32(pd.Amount.ToSatoshis()),
}
}
return channeldb.LogUpdate{
LogIndex: pd.LogIndex,
UpdateMsg: msg,
}
}
// setCommitHeight updates the appropriate addCommitHeight and/or
// removeCommitHeight for whoseCommitChain and locks it in at nextHeight.
func (pd *paymentDescriptor) setCommitHeight(
whoseCommitChain lntypes.ChannelParty, nextHeight uint64) {
switch pd.EntryType {
case Add:
pd.addCommitHeights.SetForParty(
whoseCommitChain, nextHeight,
)
case Settle, Fail, MalformedFail:
pd.removeCommitHeights.SetForParty(
whoseCommitChain, nextHeight,
)
case FeeUpdate:
// Fee updates are applied for all commitments
// after they are sent/received, so we consider
// them being added and removed at the same
// height.
pd.addCommitHeights.SetForParty(
whoseCommitChain, nextHeight,
)
pd.removeCommitHeights.SetForParty(
whoseCommitChain, nextHeight,
)
}
}
package lnwallet
import (
"errors"
"fmt"
"net"
"sync"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
"github.com/lightningnetwork/lnd/lnwire"
)
// CommitmentType is an enum indicating the commitment type we should use for
// the channel we are opening.
type CommitmentType int
const (
// CommitmentTypeLegacy is the legacy commitment format with a tweaked
// to_remote key.
CommitmentTypeLegacy CommitmentType = iota
// CommitmentTypeTweakless is a newer commitment format where the
// to_remote key is static.
CommitmentTypeTweakless
// CommitmentTypeAnchorsZeroFeeHtlcTx is a commitment type that is an
// extension of the outdated CommitmentTypeAnchors, which in addition
// requires second-level HTLC transactions to be signed using a
// zero-fee.
CommitmentTypeAnchorsZeroFeeHtlcTx
// CommitmentTypeScriptEnforcedLease is a commitment type that builds
// upon CommitmentTypeTweakless and CommitmentTypeAnchorsZeroFeeHtlcTx,
// which in addition requires a CLTV clause to spend outputs paying to
// the channel initiator. This is intended for use on leased channels to
// guarantee that the channel initiator has no incentives to close a
// leased channel before its maturity date.
CommitmentTypeScriptEnforcedLease
// CommitmentTypeSimpleTaproot is the base commitment type for the
// channels that use a musig2 funding output and the tapscript tree
// where relevant for the commitment transaction pk scripts.
CommitmentTypeSimpleTaproot
// CommitmentTypeSimpleTaprootOverlay builds on the existing
// CommitmentTypeSimpleTaproot type but layers on a special overlay
// protocol.
CommitmentTypeSimpleTaprootOverlay
)
// HasStaticRemoteKey returns whether the commitment type supports remote
// outputs backed by static keys.
func (c CommitmentType) HasStaticRemoteKey() bool {
switch c {
case CommitmentTypeTweakless,
CommitmentTypeAnchorsZeroFeeHtlcTx,
CommitmentTypeScriptEnforcedLease,
CommitmentTypeSimpleTaproot,
CommitmentTypeSimpleTaprootOverlay:
return true
default:
return false
}
}
// HasAnchors returns whether the commitment type supports anchor outputs.
func (c CommitmentType) HasAnchors() bool {
switch c {
case CommitmentTypeAnchorsZeroFeeHtlcTx,
CommitmentTypeScriptEnforcedLease,
CommitmentTypeSimpleTaproot,
CommitmentTypeSimpleTaprootOverlay:
return true
default:
return false
}
}
// IsTaproot returns true if the channel type is a taproot channel.
func (c CommitmentType) IsTaproot() bool {
return c == CommitmentTypeSimpleTaproot ||
c == CommitmentTypeSimpleTaprootOverlay
}
// String returns the name of the CommitmentType.
func (c CommitmentType) String() string {
switch c {
case CommitmentTypeLegacy:
return "legacy"
case CommitmentTypeTweakless:
return "tweakless"
case CommitmentTypeAnchorsZeroFeeHtlcTx:
return "anchors-zero-fee-second-level"
case CommitmentTypeScriptEnforcedLease:
return "script-enforced-lease"
case CommitmentTypeSimpleTaproot:
return "simple-taproot"
case CommitmentTypeSimpleTaprootOverlay:
return "simple-taproot-overlay"
default:
return "invalid"
}
}
// ReservationState is a type that represents the current state of a channel
// reservation within the funding workflow.
type ReservationState int
const (
// WaitingToSend is the state either the funder/fundee is in after
// creating a reservation, but hasn't sent a message yet.
WaitingToSend ReservationState = iota
// SentOpenChannel is the state the funder is in after sending the
// OpenChannel message.
SentOpenChannel
// SentAcceptChannel is the state the fundee is in after sending the
// AcceptChannel message.
SentAcceptChannel
// SentFundingCreated is the state the funder is in after sending the
// FundingCreated message.
SentFundingCreated
)
// ChannelContribution is the primary constituent of the funding workflow
// within lnwallet. Each side first exchanges their respective contributions
// along with channel specific parameters like the min fee/KB. Once
// contributions have been exchanged, each side will then produce signatures
// for all their inputs to the funding transactions, and finally a signature
// for the other party's version of the commitment transaction.
type ChannelContribution struct {
// FundingOutpoint is the amount of funds contributed to the funding
// transaction.
FundingAmount btcutil.Amount
// Inputs to the funding transaction.
Inputs []*wire.TxIn
// ChangeOutputs are the Outputs to be used in the case that the total
// value of the funding inputs is greater than the total potential
// channel capacity.
ChangeOutputs []*wire.TxOut
// FirstCommitmentPoint is the first commitment point that will be used
// to create the revocation key in the first commitment transaction we
// send to the remote party.
FirstCommitmentPoint *btcec.PublicKey
// ChannelConfig is the concrete contribution that this node is
// offering to the channel. This includes all the various constraints
// such as the min HTLC, and also all the keys which will be used for
// the duration of the channel.
*channeldb.ChannelConfig
// UpfrontShutdown is an optional address to which the channel should be
// paid out to on cooperative close.
UpfrontShutdown lnwire.DeliveryAddress
// LocalNonce is populated if the channel type is a simple taproot
// channel. This stores the public (and secret) nonce that will be used
// to generate commitments for the local party.
LocalNonce *musig2.Nonces
}
// toChanConfig returns the raw channel configuration generated by a node's
// contribution to the channel.
func (c *ChannelContribution) toChanConfig() channeldb.ChannelConfig {
return *c.ChannelConfig
}
// ChannelReservation represents an intent to open a lightning payment channel
// with a counterparty. The funding processes from reservation to channel opening
// is a 3-step process. In order to allow for full concurrency during the
// reservation workflow, resources consumed by a contribution are "locked"
// themselves. This prevents a number of race conditions such as two funding
// transactions double-spending the same input. A reservation can also be
// canceled, which removes the resources from limbo, allowing another
// reservation to claim them.
//
// The reservation workflow consists of the following three steps:
// 1. lnwallet.InitChannelReservation
// * One requests the wallet to allocate the necessary resources for a
// channel reservation. These resources are put in limbo for the lifetime
// of a reservation.
// * Once completed the reservation will have the wallet's contribution
// accessible via the .OurContribution() method. This contribution
// contains the necessary items to allow the remote party to build both
// the funding, and commitment transactions.
// 2. ChannelReservation.ProcessContribution/ChannelReservation.ProcessSingleContribution
// * The counterparty presents their contribution to the payment channel.
// This allows us to build the funding, and commitment transactions
// ourselves.
// * We're now able to sign our inputs to the funding transactions, and
// the counterparty's version of the commitment transaction.
// * All signatures crafted by us, are now available via .OurSignatures().
// 3. ChannelReservation.CompleteReservation/ChannelReservation.CompleteReservationSingle
// * The final step in the workflow. The counterparty presents the
// signatures for all their inputs to the funding transaction, as well
// as a signature to our version of the commitment transaction.
// * We then verify the validity of all signatures before considering the
// channel "open".
type ChannelReservation struct {
// This mutex MUST be held when either reading or modifying any of the
// fields below.
sync.RWMutex
// fundingTx is the funding transaction for this pending channel.
fundingTx *wire.MsgTx
// In order of sorted inputs. Sorting is done in accordance
// to BIP-69: https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
ourFundingInputScripts []*input.Script
theirFundingInputScripts []*input.Script
// Our signature for their version of the commitment transaction.
ourCommitmentSig input.Signature
theirCommitmentSig input.Signature
ourContribution *ChannelContribution
theirContribution *ChannelContribution
partialState *channeldb.OpenChannel
nodeAddr net.Addr
// The ID of this reservation, used to uniquely track the reservation
// throughout its lifetime.
reservationID uint64
// pendingChanID is the pending channel ID for this channel as
// identified within the wire protocol.
pendingChanID [32]byte
// pushMSat the amount of milli-satoshis that should be pushed to the
// responder of a single funding channel as part of the initial
// commitment state.
pushMSat lnwire.MilliSatoshi
wallet *LightningWallet
chanFunder chanfunding.Assembler
fundingIntent chanfunding.Intent
// nextRevocationKeyLoc stores the key locator information for this
// channel.
nextRevocationKeyLoc keychain.KeyLocator
musigSessions *MusigPairSession
state ReservationState
}
// NewChannelReservation creates a new channel reservation. This function is
// used only internally by lnwallet. In order to concurrent safety, the
// creation of all channel reservations should be carried out via the
// lnwallet.InitChannelReservation interface.
func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
wallet *LightningWallet, id uint64, chainHash *chainhash.Hash,
thawHeight uint32, req *InitFundingReserveMsg) (*ChannelReservation,
error) {
var (
ourBalance lnwire.MilliSatoshi
theirBalance lnwire.MilliSatoshi
initiator bool
)
// Based on the channel type, we determine the initial commit weight
// and fee.
commitWeight := input.CommitWeight
if req.CommitType.IsTaproot() {
commitWeight = input.TaprootCommitWeight
} else if req.CommitType.HasAnchors() {
commitWeight = input.AnchorCommitWeight
}
commitFee := req.CommitFeePerKw.FeeForWeight(
lntypes.WeightUnit(commitWeight),
)
localFundingMSat := lnwire.NewMSatFromSatoshis(localFundingAmt)
// TODO(halseth): make method take remote funding amount directly
// instead of inferring it from capacity and local amt.
capacityMSat := lnwire.NewMSatFromSatoshis(capacity)
// The total fee paid by the initiator will be the commitment fee in
// addition to the two anchor outputs.
feeMSat := lnwire.NewMSatFromSatoshis(commitFee)
if req.CommitType.HasAnchors() {
feeMSat += 2 * lnwire.NewMSatFromSatoshis(AnchorSize)
}
// Used to cut down on verbosity.
defaultDust := DustLimitUnknownWitness()
// If we're the responder to a single-funder reservation, then we have
// no initial balance in the channel unless the remote party is pushing
// some funds to us within the first commitment state.
if localFundingAmt == 0 {
ourBalance = req.PushMSat
theirBalance = capacityMSat - feeMSat - req.PushMSat
initiator = false
// If the responder doesn't have enough funds to actually pay
// the fees, then we'll bail our early.
if int64(theirBalance) < 0 {
return nil, ErrFunderBalanceDust(
int64(commitFee), int64(theirBalance.ToSatoshis()),
int64(2*defaultDust),
)
}
} else {
// TODO(roasbeef): need to rework fee structure in general and
// also when we "unlock" dual funder within the daemon
if capacity == localFundingAmt {
// If we're initiating a single funder workflow, then
// we pay all the initial fees within the commitment
// transaction. We also deduct our balance by the
// amount pushed as part of the initial state.
ourBalance = capacityMSat - feeMSat - req.PushMSat
theirBalance = req.PushMSat
} else {
// Otherwise, this is a dual funder workflow where both
// slides split the amount funded and the commitment
// fee.
ourBalance = localFundingMSat - (feeMSat / 2)
theirBalance = capacityMSat - localFundingMSat - (feeMSat / 2) + req.PushMSat
}
initiator = true
// If we, the initiator don't have enough funds to actually pay
// the fees, then we'll exit with an error.
if int64(ourBalance) < 0 {
return nil, ErrFunderBalanceDust(
int64(commitFee), int64(ourBalance),
int64(2*defaultDust),
)
}
}
// If we're the initiator and our starting balance within the channel
// after we take account of fees is below 2x the dust limit, then we'll
// reject this channel creation request.
//
// TODO(roasbeef): reject if 30% goes to fees? dust channel
if initiator && ourBalance.ToSatoshis() <= 2*defaultDust {
return nil, ErrFunderBalanceDust(
int64(commitFee),
int64(ourBalance.ToSatoshis()),
int64(2*defaultDust),
)
}
// Similarly we ensure their balance is reasonable if we are not the
// initiator.
if !initiator && theirBalance.ToSatoshis() <= 2*defaultDust {
return nil, ErrFunderBalanceDust(
int64(commitFee),
int64(theirBalance.ToSatoshis()),
int64(2*defaultDust),
)
}
// Next we'll set the channel type based on what we can ascertain about
// the balances/push amount within the channel.
var chanType channeldb.ChannelType
// If either of the balances are zero at this point, or we have a
// non-zero push amt (there's no pushing for dual funder), then this is
// a single-funder channel.
if ourBalance == 0 || theirBalance == 0 || req.PushMSat != 0 {
// Both the tweakless type and the anchor type is tweakless,
// hence set the bit.
if req.CommitType.HasStaticRemoteKey() {
chanType |= channeldb.SingleFunderTweaklessBit
} else {
chanType |= channeldb.SingleFunderBit
}
switch a := req.ChanFunder.(type) {
// The first channels of a batch shouldn't publish the batch TX
// to avoid problems if some of the funding flows can't be
// completed. Only the last channel of a batch should publish.
case chanfunding.ConditionalPublishAssembler:
if !a.ShouldPublishFundingTx() {
chanType |= channeldb.NoFundingTxBit
}
// Normal funding flow, the assembler creates a TX from the
// internal wallet.
case chanfunding.FundingTxAssembler:
// Do nothing, a FundingTxAssembler has the transaction.
// If this intent isn't one that's able to provide us with a
// funding transaction, then we'll set the chanType bit to
// signal that we don't have access to one.
default:
chanType |= channeldb.NoFundingTxBit
}
} else {
// Otherwise, this is a dual funder channel, and no side is
// technically the "initiator"
initiator = false
chanType |= channeldb.DualFunderBit
}
// We are adding anchor outputs to our commitment. We only support this
// in combination with zero-fee second-levels HTLCs.
if req.CommitType.HasAnchors() {
chanType |= channeldb.AnchorOutputsBit
chanType |= channeldb.ZeroHtlcTxFeeBit
}
// Set the appropriate LeaseExpiration/Frozen bit based on the
// reservation parameters.
if req.CommitType == CommitmentTypeScriptEnforcedLease {
if thawHeight == 0 {
return nil, errors.New("missing absolute expiration " +
"for script enforced lease commitment type")
}
chanType |= channeldb.LeaseExpirationBit
} else if thawHeight > 0 {
chanType |= channeldb.FrozenBit
}
if req.CommitType.IsTaproot() {
chanType |= channeldb.SimpleTaprootFeatureBit
}
if req.ZeroConf {
chanType |= channeldb.ZeroConfBit
}
if req.OptionScidAlias {
chanType |= channeldb.ScidAliasChanBit
}
if req.ScidAliasFeature {
chanType |= channeldb.ScidAliasFeatureBit
}
taprootOverlay := req.CommitType == CommitmentTypeSimpleTaprootOverlay
switch {
case taprootOverlay && req.TapscriptRoot.IsNone():
fallthrough
case !taprootOverlay && req.TapscriptRoot.IsSome():
return nil, fmt.Errorf("taproot overlay chans must be set " +
"with tapscript root")
case taprootOverlay && req.TapscriptRoot.IsSome():
chanType |= channeldb.TapscriptRootBit
}
return &ChannelReservation{
ourContribution: &ChannelContribution{
FundingAmount: ourBalance.ToSatoshis(),
ChannelConfig: &channeldb.ChannelConfig{},
},
theirContribution: &ChannelContribution{
FundingAmount: theirBalance.ToSatoshis(),
ChannelConfig: &channeldb.ChannelConfig{},
},
partialState: &channeldb.OpenChannel{
ChanType: chanType,
ChainHash: *chainHash,
IsPending: true,
IsInitiator: initiator,
ChannelFlags: req.Flags,
Capacity: capacity,
LocalCommitment: channeldb.ChannelCommitment{
LocalBalance: ourBalance,
RemoteBalance: theirBalance,
FeePerKw: btcutil.Amount(req.CommitFeePerKw),
CommitFee: commitFee,
},
RemoteCommitment: channeldb.ChannelCommitment{
LocalBalance: ourBalance,
RemoteBalance: theirBalance,
FeePerKw: btcutil.Amount(req.CommitFeePerKw),
CommitFee: commitFee,
},
ThawHeight: thawHeight,
Db: wallet.Cfg.Database,
InitialLocalBalance: ourBalance,
InitialRemoteBalance: theirBalance,
Memo: req.Memo,
TapscriptRoot: req.TapscriptRoot,
},
pushMSat: req.PushMSat,
pendingChanID: req.PendingChanID,
reservationID: id,
wallet: wallet,
chanFunder: req.ChanFunder,
state: WaitingToSend,
}, nil
}
// AddAlias stores the first alias for zero-conf channels.
func (r *ChannelReservation) AddAlias(scid lnwire.ShortChannelID) {
r.Lock()
defer r.Unlock()
r.partialState.ShortChannelID = scid
}
// SetState sets the ReservationState.
func (r *ChannelReservation) SetState(state ReservationState) {
r.Lock()
defer r.Unlock()
r.state = state
}
// State returns the current ReservationState.
func (r *ChannelReservation) State() ReservationState {
r.RLock()
defer r.RUnlock()
return r.state
}
// SetNumConfsRequired sets the number of confirmations that are required for
// the ultimate funding transaction before the channel can be considered open.
// This is distinct from the main reservation workflow as it allows
// implementations a bit more flexibility w.r.t to if the responder of the
// initiator sets decides the number of confirmations needed.
func (r *ChannelReservation) SetNumConfsRequired(numConfs uint16) {
r.Lock()
defer r.Unlock()
r.partialState.NumConfsRequired = numConfs
}
// IsZeroConf returns if the reservation's underlying partial channel state is
// a zero-conf channel.
func (r *ChannelReservation) IsZeroConf() bool {
r.RLock()
defer r.RUnlock()
return r.partialState.IsZeroConf()
}
// IsTaproot returns if the reservation's underlying partial channel state is a
// taproot channel.
func (r *ChannelReservation) IsTaproot() bool {
r.RLock()
defer r.RUnlock()
return r.partialState.ChanType.IsTaproot()
}
// CommitConstraints takes the constraints that the remote party specifies for
// the type of commitments that we can generate for them. These constraints
// include several parameters that serve as flow control restricting the amount
// of satoshis that can be transferred in a single commitment. This function
// will also attempt to verify the constraints for sanity, returning an error
// if the parameters are seemed unsound.
func (r *ChannelReservation) CommitConstraints(
bounds *channeldb.ChannelStateBounds,
commitParams *channeldb.CommitmentParams,
maxLocalCSVDelay uint16,
responder bool) error {
r.Lock()
defer r.Unlock()
// First, verify the sanity of the channel constraints.
err := VerifyConstraints(
bounds, commitParams, maxLocalCSVDelay, r.partialState.Capacity,
)
if err != nil {
return err
}
// Our dust limit should always be less than or equal to our proposed
// channel reserve.
if responder && r.ourContribution.DustLimit > bounds.ChanReserve {
r.ourContribution.DustLimit = bounds.ChanReserve
}
r.ourContribution.ChanReserve = bounds.ChanReserve
r.ourContribution.MaxPendingAmount = bounds.MaxPendingAmount
r.ourContribution.MinHTLC = bounds.MinHTLC
r.ourContribution.MaxAcceptedHtlcs = bounds.MaxAcceptedHtlcs
r.ourContribution.CsvDelay = commitParams.CsvDelay
return nil
}
// validateReserveBounds checks that both ChannelReserve values are above both
// DustLimit values. This not only avoids stuck channels, but is also mandated
// by BOLT#02 even if it's not explicit. This returns true if the bounds are
// valid. This function should be called with the lock held.
func (r *ChannelReservation) validateReserveBounds() bool {
ourDustLimit := r.ourContribution.DustLimit
ourRequiredReserve := r.ourContribution.ChanReserve
theirDustLimit := r.theirContribution.DustLimit
theirRequiredReserve := r.theirContribution.ChanReserve
// We take the smaller of the two ChannelReserves and compare it
// against the larger of the two DustLimits.
minChanReserve := ourRequiredReserve
if minChanReserve > theirRequiredReserve {
minChanReserve = theirRequiredReserve
}
maxDustLimit := ourDustLimit
if maxDustLimit < theirDustLimit {
maxDustLimit = theirDustLimit
}
return minChanReserve >= maxDustLimit
}
// OurContribution returns the wallet's fully populated contribution to the
// pending payment channel. See 'ChannelContribution' for further details
// regarding the contents of a contribution.
//
// NOTE: This SHOULD NOT be modified.
// TODO(roasbeef): make copy?
func (r *ChannelReservation) OurContribution() *ChannelContribution {
r.RLock()
defer r.RUnlock()
return r.ourContribution
}
// ProcessContribution verifies the counterparty's contribution to the pending
// payment channel. As a result of this incoming message, lnwallet is able to
// build the funding transaction, and both commitment transactions. Once this
// message has been processed, all signatures to inputs to the funding
// transaction belonging to the wallet are available. Additionally, the wallet
// will generate a signature to the counterparty's version of the commitment
// transaction.
func (r *ChannelReservation) ProcessContribution(theirContribution *ChannelContribution) error {
errChan := make(chan error, 1)
r.wallet.msgChan <- &addContributionMsg{
pendingFundingID: r.reservationID,
contribution: theirContribution,
err: errChan,
}
return <-errChan
}
// IsPsbt returns true if there is a PSBT funding intent mapped to this
// reservation.
func (r *ChannelReservation) IsPsbt() bool {
_, ok := r.fundingIntent.(*chanfunding.PsbtIntent)
return ok
}
// IsCannedShim returns true if there is a canned shim funding intent mapped to
// this reservation.
func (r *ChannelReservation) IsCannedShim() bool {
_, ok := r.fundingIntent.(*chanfunding.ShimIntent)
return ok
}
// ProcessPsbt continues a previously paused funding flow that involves PSBT to
// construct the funding transaction. This method can be called once the PSBT
// is finalized and the signed transaction is available.
func (r *ChannelReservation) ProcessPsbt(
auxFundingDesc fn.Option[AuxFundingDesc]) error {
errChan := make(chan error, 1)
r.wallet.msgChan <- &continueContributionMsg{
auxFundingDesc: auxFundingDesc,
pendingFundingID: r.reservationID,
err: errChan,
}
return <-errChan
}
// RemoteCanceled informs the PSBT funding state machine that the remote peer
// has canceled the pending reservation, likely due to a timeout.
func (r *ChannelReservation) RemoteCanceled() {
psbtIntent, ok := r.fundingIntent.(*chanfunding.PsbtIntent)
if !ok {
return
}
psbtIntent.RemoteCanceled()
}
// ProcessSingleContribution verifies, and records the initiator's contribution
// to this pending single funder channel. Internally, no further action is
// taken other than recording the initiator's contribution to the single funder
// channel.
func (r *ChannelReservation) ProcessSingleContribution(theirContribution *ChannelContribution) error {
errChan := make(chan error, 1)
r.wallet.msgChan <- &addSingleContributionMsg{
pendingFundingID: r.reservationID,
contribution: theirContribution,
err: errChan,
}
return <-errChan
}
// TheirContribution returns the counterparty's pending contribution to the
// payment channel. See 'ChannelContribution' for further details regarding the
// contents of a contribution. This attribute will ONLY be available after a
// call to .ProcessContribution().
//
// NOTE: This SHOULD NOT be modified.
func (r *ChannelReservation) TheirContribution() *ChannelContribution {
r.RLock()
defer r.RUnlock()
return r.theirContribution
}
// OurSignatures retrieves the wallet's signatures to all inputs to the funding
// transaction belonging to itself, and also a signature for the counterparty's
// version of the commitment transaction. The signatures for the wallet's
// inputs to the funding transaction are returned in sorted order according to
// BIP-69: https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
//
// NOTE: These signatures will only be populated after a call to
// .ProcessContribution()
func (r *ChannelReservation) OurSignatures() ([]*input.Script,
input.Signature) {
r.RLock()
defer r.RUnlock()
return r.ourFundingInputScripts, r.ourCommitmentSig
}
// CompleteReservation finalizes the pending channel reservation, transitioning
// from a pending payment channel, to an open payment channel. All passed
// signatures to the counterparty's inputs to the funding transaction will be
// fully verified. Signatures are expected to be passed in sorted order
// according to BIP-69:
// https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
// Additionally, verification is performed in order to ensure that the
// counterparty supplied a valid signature to our version of the commitment
// transaction. Once this method returns, callers should broadcast the
// created funding transaction, then call .WaitForChannelOpen() which will
// block until the funding transaction obtains the configured number of
// confirmations. Once the method unblocks, a LightningChannel instance is
// returned, marking the channel available for updates.
func (r *ChannelReservation) CompleteReservation(fundingInputScripts []*input.Script,
commitmentSig input.Signature) (*channeldb.OpenChannel, error) {
// TODO(roasbeef): add flag for watch or not?
errChan := make(chan error, 1)
completeChan := make(chan *channeldb.OpenChannel, 1)
r.wallet.msgChan <- &addCounterPartySigsMsg{
pendingFundingID: r.reservationID,
theirFundingInputScripts: fundingInputScripts,
theirCommitmentSig: commitmentSig,
completeChan: completeChan,
err: errChan,
}
return <-completeChan, <-errChan
}
// CompleteReservationSingle finalizes the pending single funder channel
// reservation. Using the funding outpoint of the constructed funding
// transaction, and the initiator's signature for our version of the commitment
// transaction, we are able to verify the correctness of our commitment
// transaction as crafted by the initiator. Once this method returns, our
// signature for the initiator's version of the commitment transaction is
// available via the .OurSignatures() method. As this method should only be
// called as a response to a single funder channel, only a commitment signature
// will be populated.
func (r *ChannelReservation) CompleteReservationSingle(
fundingPoint *wire.OutPoint, commitSig input.Signature,
auxFundingDesc fn.Option[AuxFundingDesc]) (*channeldb.OpenChannel,
error) {
errChan := make(chan error, 1)
completeChan := make(chan *channeldb.OpenChannel, 1)
r.wallet.msgChan <- &addSingleFunderSigsMsg{
pendingFundingID: r.reservationID,
fundingOutpoint: fundingPoint,
theirCommitmentSig: commitSig,
completeChan: completeChan,
auxFundingDesc: auxFundingDesc,
err: errChan,
}
return <-completeChan, <-errChan
}
// TheirSignatures returns the counterparty's signatures to all inputs to the
// funding transaction belonging to them, as well as their signature for the
// wallet's version of the commitment transaction. This methods is provided for
// additional verification, such as needed by tests.
//
// NOTE: These attributes will be unpopulated before a call to
// .CompleteReservation().
func (r *ChannelReservation) TheirSignatures() ([]*input.Script,
input.Signature) {
r.RLock()
defer r.RUnlock()
return r.theirFundingInputScripts, r.theirCommitmentSig
}
// FinalFundingTx returns the finalized, fully signed funding transaction for
// this reservation.
//
// NOTE: If this reservation was created as the non-initiator to a single
// funding workflow, then the full funding transaction will not be available.
// Instead we will only have the final outpoint of the funding transaction.
func (r *ChannelReservation) FinalFundingTx() *wire.MsgTx {
r.RLock()
defer r.RUnlock()
return r.fundingTx
}
// FundingOutpoint returns the outpoint of the funding transaction.
//
// NOTE: The pointer returned will only be set once the .ProcessContribution()
// method is called in the case of the initiator of a single funder workflow,
// and after the .CompleteReservationSingle() method is called in the case of
// a responder to a single funder workflow.
func (r *ChannelReservation) FundingOutpoint() *wire.OutPoint {
r.RLock()
defer r.RUnlock()
return &r.partialState.FundingOutpoint
}
// SetOurUpfrontShutdown sets the upfront shutdown address on our contribution.
func (r *ChannelReservation) SetOurUpfrontShutdown(shutdown lnwire.DeliveryAddress) {
r.Lock()
defer r.Unlock()
r.ourContribution.UpfrontShutdown = shutdown
}
// Capacity returns the channel capacity for this reservation.
func (r *ChannelReservation) Capacity() btcutil.Amount {
r.RLock()
defer r.RUnlock()
return r.partialState.Capacity
}
// LeaseExpiry returns the absolute expiration height for a leased channel using
// the script enforced commitment type. A zero value is returned when the
// channel is not using a script enforced lease commitment type.
func (r *ChannelReservation) LeaseExpiry() uint32 {
if !r.partialState.ChanType.HasLeaseExpiration() {
return 0
}
return r.partialState.ThawHeight
}
// Cancel abandons this channel reservation. This method should be called in
// the scenario that communications with the counterparty break down. Upon
// cancellation, all resources previously reserved for this pending payment
// channel are returned to the free pool, allowing subsequent reservations to
// utilize the now freed resources.
func (r *ChannelReservation) Cancel() error {
errChan := make(chan error, 1)
r.wallet.msgChan <- &fundingReserveCancelMsg{
pendingFundingID: r.reservationID,
err: errChan,
}
return <-errChan
}
// ChanState the current open channel state.
func (r *ChannelReservation) ChanState() *channeldb.OpenChannel {
r.RLock()
defer r.RUnlock()
return r.partialState
}
// CommitmentKeyRings returns the local+remote key ring used for the very first
// commitment transaction both parties.
//
//nolint:ll
func (r *ChannelReservation) CommitmentKeyRings() lntypes.Dual[CommitmentKeyRing] {
r.RLock()
defer r.RUnlock()
chanType := r.partialState.ChanType
ourChanCfg := r.ourContribution.ChannelConfig
theirChanCfg := r.theirContribution.ChannelConfig
localKeys := DeriveCommitmentKeys(
r.ourContribution.FirstCommitmentPoint, lntypes.Local, chanType,
ourChanCfg, theirChanCfg,
)
remoteKeys := DeriveCommitmentKeys(
r.theirContribution.FirstCommitmentPoint, lntypes.Remote,
chanType, ourChanCfg, theirChanCfg,
)
return lntypes.Dual[CommitmentKeyRing]{
Local: *localKeys,
Remote: *remoteKeys,
}
}
// VerifyConstraints is a helper function that can be used to check the sanity
// of various channel constraints.
func VerifyConstraints(bounds *channeldb.ChannelStateBounds,
commitParams *channeldb.CommitmentParams, maxLocalCSVDelay uint16,
channelCapacity btcutil.Amount) error {
// Fail if the csv delay for our funds exceeds our maximum.
if commitParams.CsvDelay > maxLocalCSVDelay {
return ErrCsvDelayTooLarge(
commitParams.CsvDelay, maxLocalCSVDelay,
)
}
// The channel reserve should always be greater or equal to the dust
// limit. The reservation request should be denied if otherwise.
if commitParams.DustLimit > bounds.ChanReserve {
return ErrChanReserveTooSmall(
bounds.ChanReserve, commitParams.DustLimit,
)
}
// Validate against the maximum-sized witness script dust limit, and
// also ensure that the DustLimit is not too large.
maxWitnessLimit := DustLimitForSize(input.UnknownWitnessSize)
if commitParams.DustLimit < maxWitnessLimit ||
commitParams.DustLimit > 3*maxWitnessLimit {
return ErrInvalidDustLimit(commitParams.DustLimit)
}
// Fail if we consider the channel reserve to be too large. We
// currently fail if it is greater than 20% of the channel capacity.
maxChanReserve := channelCapacity / 5
if bounds.ChanReserve > maxChanReserve {
return ErrChanReserveTooLarge(
bounds.ChanReserve, maxChanReserve,
)
}
// Fail if the minimum HTLC value is too large. If this is too large,
// the channel won't be useful for sending small payments. This limit
// is currently set to maxValueInFlight, effectively letting the remote
// setting this as large as it wants.
if bounds.MinHTLC > bounds.MaxPendingAmount {
return ErrMinHtlcTooLarge(
bounds.MinHTLC, bounds.MaxPendingAmount,
)
}
// Fail if maxHtlcs is above the maximum allowed number of 483. This
// number is specified in BOLT-02.
if bounds.MaxAcceptedHtlcs > uint16(input.MaxHTLCNumber/2) {
return ErrMaxHtlcNumTooLarge(
bounds.MaxAcceptedHtlcs, uint16(input.MaxHTLCNumber/2),
)
}
// Fail if we consider maxHtlcs too small. If this is too small we
// cannot offer many HTLCs to the remote.
const minNumHtlc = 5
if bounds.MaxAcceptedHtlcs < minNumHtlc {
return ErrMaxHtlcNumTooSmall(
bounds.MaxAcceptedHtlcs, minNumHtlc,
)
}
// Fail if we consider maxValueInFlight too small. We currently require
// the remote to at least allow minNumHtlc * minHtlc in flight.
if bounds.MaxPendingAmount < minNumHtlc*bounds.MinHTLC {
return ErrMaxValueInFlightTooSmall(
bounds.MaxPendingAmount, minNumHtlc*bounds.MinHTLC,
)
}
return nil
}
// OpenChannelDetails wraps the finalized fully confirmed channel which
// resulted from a ChannelReservation instance with details concerning exactly
// _where_ in the chain the channel was ultimately opened.
type OpenChannelDetails struct {
// Channel is the active channel created by an instance of a
// ChannelReservation and the required funding workflow.
Channel *LightningChannel
// ConfirmationHeight is the block height within the chain that
// included the channel.
ConfirmationHeight uint32
// TransactionIndex is the index within the confirming block that the
// transaction resides.
TransactionIndex uint32
}
//go:build !integration
package lnwallet
import (
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/shachain"
)
// nextRevocationProducer creates a new revocation producer, deriving the
// revocation root by applying ECDH to a new key from our revocation root
// family and the multisig key we use for the channel. For taproot channels a
// related shachain revocation root is also returned.
func (l *LightningWallet) nextRevocationProducer(res *ChannelReservation,
keyRing keychain.KeyRing,
) (shachain.Producer, shachain.Producer, error) {
// Derive the next key in the revocation root family.
nextRevocationKeyDesc, err := keyRing.DeriveNextKey(
keychain.KeyFamilyRevocationRoot,
)
if err != nil {
return nil, nil, err
}
// If the DeriveNextKey call returns the first key with Index 0, we need
// to re-derive the key as the keychain/btcwallet.go DerivePrivKey call
// special-cases Index 0.
if nextRevocationKeyDesc.Index == 0 {
nextRevocationKeyDesc, err = keyRing.DeriveNextKey(
keychain.KeyFamilyRevocationRoot,
)
if err != nil {
return nil, nil, err
}
}
res.nextRevocationKeyLoc = nextRevocationKeyDesc.KeyLocator
// Perform an ECDH operation between the private key described in
// nextRevocationKeyDesc and our public multisig key. The result will be
// used to seed the revocation producer.
revRoot, err := l.ECDH(
nextRevocationKeyDesc, res.ourContribution.MultiSigKey.PubKey,
)
if err != nil {
return nil, nil, err
}
// Once we have the root, we can then generate our shachain producer
// and from that generate the per-commitment point.
shaChainRoot := shachain.NewRevocationProducer(revRoot)
taprootShaChainRoot, err := channeldb.DeriveMusig2Shachain(shaChainRoot)
if err != nil {
return nil, nil, err
}
return shaChainRoot, taprootShaChainRoot, nil
}
package rpcwallet
import (
"fmt"
"time"
"github.com/lightningnetwork/lnd/lncfg"
)
// HealthCheck returns a health check function for the given remote signing
// configuration.
func HealthCheck(cfg *lncfg.RemoteSigner, timeout time.Duration) func() error {
return func() error {
conn, err := connectRPC(
cfg.RPCHost, cfg.TLSCertPath, cfg.MacaroonPath, timeout,
)
if err != nil {
return fmt.Errorf("error connecting to the remote "+
"signing node through RPC: %v", err)
}
defer func() {
err = conn.Close()
if err != nil {
log.Warnf("Failed to close health check "+
"connection to remote signing node: %v",
err)
}
}()
return nil
}
}
package rpcwallet
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// Subsystem defines the logging code for this subsystem.
const Subsystem = "RPWL"
// log is a logger that is initialized with no output filters. This means the
// package will not perform any logging by default until the caller requests
// it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled by
// default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info. This
// should be used in preference to SetLogWriter if the caller is also using
// btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package rpcwallet
import (
"bytes"
"context"
"crypto/sha256"
"crypto/x509"
"errors"
"fmt"
"os"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/waddrmgr"
basewallet "github.com/btcsuite/btcwallet/wallet"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/macaroons"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/status"
"gopkg.in/macaroon.v2"
)
var (
// ErrRemoteSigningPrivateKeyNotAvailable is the error that is returned
// if an operation is requested from the RPC wallet that is not
// supported in remote signing mode.
ErrRemoteSigningPrivateKeyNotAvailable = errors.New("deriving " +
"private key is not supported by RPC based key ring")
)
// RPCKeyRing is an implementation of the SecretKeyRing interface that uses a
// local watch-only wallet for keeping track of addresses and transactions but
// delegates any signing or ECDH operations to a remote node through RPC.
type RPCKeyRing struct {
// WalletController is the embedded wallet controller of the watch-only
// base wallet. We need to overwrite/shadow certain of the implemented
// methods to make sure we can mirror them to the remote wallet.
lnwallet.WalletController
watchOnlyKeyRing keychain.SecretKeyRing
netParams *chaincfg.Params
rpcTimeout time.Duration
signerClient signrpc.SignerClient
walletClient walletrpc.WalletKitClient
}
var _ keychain.SecretKeyRing = (*RPCKeyRing)(nil)
var _ input.Signer = (*RPCKeyRing)(nil)
var _ keychain.MessageSignerRing = (*RPCKeyRing)(nil)
var _ lnwallet.WalletController = (*RPCKeyRing)(nil)
// NewRPCKeyRing creates a new remote signing secret key ring that uses the
// given watch-only base wallet to keep track of addresses and transactions but
// delegates any signing or ECDH operations to the remove signer through RPC.
func NewRPCKeyRing(watchOnlyKeyRing keychain.SecretKeyRing,
watchOnlyWalletController lnwallet.WalletController,
remoteSigner *lncfg.RemoteSigner,
netParams *chaincfg.Params) (*RPCKeyRing, error) {
rpcConn, err := connectRPC(
remoteSigner.RPCHost, remoteSigner.TLSCertPath,
remoteSigner.MacaroonPath, remoteSigner.Timeout,
)
if err != nil {
return nil, fmt.Errorf("error connecting to the remote "+
"signing node through RPC: %v", err)
}
return &RPCKeyRing{
WalletController: watchOnlyWalletController,
watchOnlyKeyRing: watchOnlyKeyRing,
netParams: netParams,
rpcTimeout: remoteSigner.Timeout,
signerClient: signrpc.NewSignerClient(rpcConn),
walletClient: walletrpc.NewWalletKitClient(rpcConn),
}, nil
}
// NewAddress returns the next external or internal address for the
// wallet dictated by the value of the `change` parameter. If change is
// true, then an internal address should be used, otherwise an external
// address should be returned. The type of address returned is dictated
// by the wallet's capabilities, and may be of type: p2sh, p2wkh,
// p2wsh, etc. The account parameter must be non-empty as it determines
// which account the address should be generated from.
func (r *RPCKeyRing) NewAddress(addrType lnwallet.AddressType, change bool,
account string) (btcutil.Address, error) {
return r.WalletController.NewAddress(addrType, change, account)
}
// SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying out to
// the specified outputs. In the case the wallet has insufficient funds, or the
// outputs are non-standard, a non-nil error will be returned.
//
// NOTE: This method requires the global coin selection lock to be held.
//
// NOTE: This is a part of the WalletController interface.
//
// NOTE: This method only signs with BIP49/84 keys.
func (r *RPCKeyRing) SendOutputs(inputs fn.Set[wire.OutPoint],
outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight,
minConfs int32, label string,
strategy basewallet.CoinSelectionStrategy) (*wire.MsgTx, error) {
tx, err := r.WalletController.SendOutputs(
inputs, outputs, feeRate, minConfs, label, strategy,
)
if err != nil && err != basewallet.ErrTxUnsigned {
return nil, err
}
if err == nil {
// This shouldn't happen since our wallet controller is watch-
// only and can't sign the TX.
return tx, nil
}
// We know at this point that we only have inputs from our own wallet.
// So we can just compute the input script using the remote signer.
outputFetcher := lnwallet.NewWalletPrevOutputFetcher(r.WalletController)
for i, txIn := range tx.TxIn {
signDesc := input.SignDescriptor{
HashType: txscript.SigHashAll,
SigHashes: txscript.NewTxSigHashes(
tx, outputFetcher,
),
PrevOutputFetcher: outputFetcher,
}
// We can only sign this input if it's ours, so we'll ask the
// watch-only wallet if it can map this outpoint into a coin we
// own. If not, then we can't continue because our wallet state
// is out of sync.
info, err := r.WalletController.FetchOutpointInfo(
&txIn.PreviousOutPoint,
)
if err != nil {
return nil, fmt.Errorf("error looking up utxo: %w", err)
}
if txscript.IsPayToTaproot(info.PkScript) {
signDesc.HashType = txscript.SigHashDefault
}
// Now that we know the input is ours, we'll populate the
// signDesc with the per input unique information.
signDesc.Output = &wire.TxOut{
Value: int64(info.Value),
PkScript: info.PkScript,
}
signDesc.InputIndex = i
// Finally, we'll sign the input as is, and populate the input
// with the witness and sigScript (if needed).
inputScript, err := r.ComputeInputScript(tx, &signDesc)
if err != nil {
return nil, err
}
txIn.SignatureScript = inputScript.SigScript
txIn.Witness = inputScript.Witness
}
return tx, r.WalletController.PublishTransaction(tx, label)
}
// SignPsbt expects a partial transaction with all inputs and outputs fully
// declared and tries to sign all unsigned inputs that have all required fields
// (UTXO information, BIP32 derivation information, witness or sig scripts) set.
// If no error is returned, the PSBT is ready to be given to the next signer or
// to be finalized if lnd was the last signer.
//
// NOTE: This RPC only signs inputs (and only those it can sign), it does not
// perform any other tasks (such as coin selection, UTXO locking or
// input/output/fee value validation, PSBT finalization). Any input that is
// incomplete will be skipped.
func (r *RPCKeyRing) SignPsbt(packet *psbt.Packet) ([]uint32, error) {
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel()
var buf bytes.Buffer
if err := packet.Serialize(&buf); err != nil {
return nil, fmt.Errorf("error serializing PSBT: %w", err)
}
resp, err := r.walletClient.SignPsbt(ctxt, &walletrpc.SignPsbtRequest{
FundedPsbt: buf.Bytes(),
})
if err != nil {
considerShutdown(err)
return nil, fmt.Errorf("error signing PSBT in remote signer "+
"instance: %v", err)
}
signedPacket, err := psbt.NewFromRawBytes(
bytes.NewReader(resp.SignedPsbt), false,
)
if err != nil {
return nil, fmt.Errorf("error parsing signed PSBT: %w", err)
}
// The caller expects the packet to be modified instead of a new
// instance to be returned. So we just overwrite all fields in the
// original packet.
packet.UnsignedTx = signedPacket.UnsignedTx
packet.Inputs = signedPacket.Inputs
packet.Outputs = signedPacket.Outputs
packet.Unknowns = signedPacket.Unknowns
return resp.SignedInputs, nil
}
// FinalizePsbt expects a partial transaction with all inputs and outputs fully
// declared and tries to sign all inputs that belong to the specified account.
// Lnd must be the last signer of the transaction. That means, if there are any
// unsigned non-witness inputs or inputs without UTXO information attached or
// inputs without witness data that do not belong to lnd's wallet, this method
// will fail. If no error is returned, the PSBT is ready to be extracted and the
// final TX within to be broadcast.
//
// NOTE: This method does NOT publish the transaction after it's been
// finalized successfully.
//
// NOTE: This is a part of the WalletController interface.
//
// NOTE: We need to overwrite this method because we need to redirect the call
// to ComputeInputScript to the RPC key ring's implementation. If we forward
// the call to the default WalletController implementation, we get an error
// since that wallet is watch-only. If we forward the call to the remote signer,
// we get an error because the signer doesn't know the UTXO information required
// in ComputeInputScript.
//
// TODO(guggero): Refactor btcwallet to accept ComputeInputScript as a function
// parameter in FinalizePsbt so we can get rid of this code duplication.
func (r *RPCKeyRing) FinalizePsbt(packet *psbt.Packet, _ string) error {
// Let's check that this is actually something we can and want to sign.
// We need at least one input and one output. In addition each
// input needs nonWitness Utxo or witness Utxo data specified.
err := psbt.InputsReadyToSign(packet)
if err != nil {
return err
}
// Go through each input that doesn't have final witness data attached
// to it already and try to sign it. We do expect that we're the last
// ones to sign. If there is any input without witness data that we
// cannot sign because it's not our UTXO, this will be a hard failure.
tx := packet.UnsignedTx
prevOutFetcher := basewallet.PsbtPrevOutputFetcher(packet)
sigHashes := txscript.NewTxSigHashes(tx, prevOutFetcher)
for idx, txIn := range tx.TxIn {
in := packet.Inputs[idx]
// We can only sign if we have UTXO information available. We
// can just continue here as a later step will fail with a more
// precise error message.
if in.WitnessUtxo == nil && in.NonWitnessUtxo == nil {
continue
}
// Skip this input if it's got final witness data attached.
if len(in.FinalScriptWitness) > 0 {
continue
}
// We can only sign this input if it's ours, so we try to map it
// to a coin we own. If we can't, then we'll continue as it
// isn't our input.
utxo, err := r.FetchOutpointInfo(&txIn.PreviousOutPoint)
if err != nil {
continue
}
fullTx := utxo.PrevTx
signDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{},
Output: &wire.TxOut{
Value: int64(utxo.Value),
PkScript: utxo.PkScript,
},
HashType: in.SighashType,
SigHashes: sigHashes,
InputIndex: idx,
PrevOutputFetcher: prevOutFetcher,
}
// Find out what UTXO we are signing. Wallets _should_ always
// provide the full non-witness UTXO for segwit v0.
var signOutput *wire.TxOut
if in.NonWitnessUtxo != nil {
prevIndex := txIn.PreviousOutPoint.Index
signOutput = in.NonWitnessUtxo.TxOut[prevIndex]
if !psbt.TxOutsEqual(signDesc.Output, signOutput) {
return fmt.Errorf("found UTXO %#v but it "+
"doesn't match PSBT's input %v",
signDesc.Output, signOutput)
}
if fullTx.TxHash() != txIn.PreviousOutPoint.Hash {
return fmt.Errorf("found UTXO tx %v but it "+
"doesn't match PSBT's input %v",
fullTx.TxHash(),
txIn.PreviousOutPoint.Hash)
}
}
// Fall back to witness UTXO only for older wallets.
if in.WitnessUtxo != nil {
signOutput = in.WitnessUtxo
if !psbt.TxOutsEqual(signDesc.Output, signOutput) {
return fmt.Errorf("found UTXO %#v but it "+
"doesn't match PSBT's input %v",
signDesc.Output, signOutput)
}
}
// Do the actual signing in ComputeInputScript which in turn
// will invoke the remote signer.
script, err := r.ComputeInputScript(tx, signDesc)
if err != nil {
return fmt.Errorf("error computing input script for "+
"input %d: %v", idx, err)
}
// Serialize the witness format from the stack representation to
// the wire representation.
var witnessBytes bytes.Buffer
err = psbt.WriteTxWitness(&witnessBytes, script.Witness)
if err != nil {
return fmt.Errorf("error serializing witness: %w", err)
}
packet.Inputs[idx].FinalScriptWitness = witnessBytes.Bytes()
packet.Inputs[idx].FinalScriptSig = script.SigScript
}
// Make sure the PSBT itself thinks it's finalized and ready to be
// broadcast.
err = psbt.MaybeFinalizeAll(packet)
if err != nil {
return fmt.Errorf("error finalizing PSBT: %w", err)
}
return nil
}
// DeriveNextKey attempts to derive the *next* key within the key family
// (account in BIP43) specified. This method should return the next external
// child within this branch.
//
// NOTE: This method is part of the keychain.KeyRing interface.
func (r *RPCKeyRing) DeriveNextKey(
keyFam keychain.KeyFamily) (keychain.KeyDescriptor, error) {
return r.watchOnlyKeyRing.DeriveNextKey(keyFam)
}
// DeriveKey attempts to derive an arbitrary key specified by the passed
// KeyLocator. This may be used in several recovery scenarios, or when manually
// rotating something like our current default node key.
//
// NOTE: This method is part of the keychain.KeyRing interface.
func (r *RPCKeyRing) DeriveKey(
keyLoc keychain.KeyLocator) (keychain.KeyDescriptor, error) {
return r.watchOnlyKeyRing.DeriveKey(keyLoc)
}
// ECDH performs a scalar multiplication (ECDH-like operation) between the
// target key descriptor and remote public key. The output returned will be the
// sha256 of the resulting shared point serialized in compressed format. If k is
// our private key, and P is the public key, we perform the following operation:
//
// sx := k*P
// s := sha256(sx.SerializeCompressed())
//
// NOTE: This method is part of the keychain.ECDHRing interface.
func (r *RPCKeyRing) ECDH(keyDesc keychain.KeyDescriptor,
pubKey *btcec.PublicKey) ([32]byte, error) {
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel()
key := [32]byte{}
req := &signrpc.SharedKeyRequest{
EphemeralPubkey: pubKey.SerializeCompressed(),
KeyDesc: &signrpc.KeyDescriptor{
KeyLoc: &signrpc.KeyLocator{
KeyFamily: int32(keyDesc.Family),
KeyIndex: int32(keyDesc.Index),
},
},
}
if keyDesc.Index == 0 && keyDesc.PubKey != nil {
req.KeyDesc.RawKeyBytes = keyDesc.PubKey.SerializeCompressed()
}
resp, err := r.signerClient.DeriveSharedKey(ctxt, req)
if err != nil {
considerShutdown(err)
return key, fmt.Errorf("error deriving shared key in remote "+
"signer instance: %v", err)
}
copy(key[:], resp.SharedKey)
return key, nil
}
// SignMessage attempts to sign a target message with the private key described
// in the key locator. If the target private key is unable to be found, then an
// error will be returned. The actual digest signed is the single or double
// SHA-256 of the passed message.
//
// NOTE: This method is part of the keychain.MessageSignerRing interface.
func (r *RPCKeyRing) SignMessage(keyLoc keychain.KeyLocator,
msg []byte, doubleHash bool) (*ecdsa.Signature, error) {
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel()
resp, err := r.signerClient.SignMessage(ctxt, &signrpc.SignMessageReq{
Msg: msg,
KeyLoc: &signrpc.KeyLocator{
KeyFamily: int32(keyLoc.Family),
KeyIndex: int32(keyLoc.Index),
},
DoubleHash: doubleHash,
})
if err != nil {
considerShutdown(err)
return nil, fmt.Errorf("error signing message in remote "+
"signer instance: %v", err)
}
wireSig, err := lnwire.NewSigFromECDSARawSignature(resp.Signature)
if err != nil {
return nil, fmt.Errorf("unable to create sig: %w", err)
}
sig, err := wireSig.ToSignature()
if err != nil {
return nil, fmt.Errorf("unable to parse sig: %w", err)
}
ecdsaSig, ok := sig.(*ecdsa.Signature)
if !ok {
return nil, fmt.Errorf("unexpected signature type: %T", sig)
}
return ecdsaSig, nil
}
// SignMessageCompact signs the given message, single or double SHA256 hashing
// it first, with the private key described in the key locator and returns the
// signature in the compact, public key recoverable format.
//
// NOTE: This method is part of the keychain.MessageSignerRing interface.
func (r *RPCKeyRing) SignMessageCompact(keyLoc keychain.KeyLocator,
msg []byte, doubleHash bool) ([]byte, error) {
if keyLoc.Family != keychain.KeyFamilyNodeKey {
return nil, fmt.Errorf("error compact signing with key "+
"locator %v, can only sign with node key", keyLoc)
}
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel()
resp, err := r.signerClient.SignMessage(ctxt, &signrpc.SignMessageReq{
Msg: msg,
KeyLoc: &signrpc.KeyLocator{
KeyFamily: int32(keyLoc.Family),
KeyIndex: int32(keyLoc.Index),
},
DoubleHash: doubleHash,
CompactSig: true,
})
if err != nil {
considerShutdown(err)
return nil, fmt.Errorf("error signing message in remote "+
"signer instance: %v", err)
}
// The signature in the response is zbase32 encoded, so we need to
// decode it before returning.
return resp.Signature, nil
}
// SignMessageSchnorr attempts to sign a target message with the private key
// described in the key locator. If the target private key is unable to be
// found, then an error will be returned. The actual digest signed is the
// single or double SHA-256 of the passed message.
//
// NOTE: This method is part of the keychain.MessageSignerRing interface.
func (r *RPCKeyRing) SignMessageSchnorr(keyLoc keychain.KeyLocator,
msg []byte, doubleHash bool, taprootTweak []byte,
tag []byte) (*schnorr.Signature, error) {
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel()
resp, err := r.signerClient.SignMessage(ctxt, &signrpc.SignMessageReq{
Msg: msg,
KeyLoc: &signrpc.KeyLocator{
KeyFamily: int32(keyLoc.Family),
KeyIndex: int32(keyLoc.Index),
},
DoubleHash: doubleHash,
SchnorrSig: true,
SchnorrSigTapTweak: taprootTweak,
Tag: tag,
})
if err != nil {
considerShutdown(err)
return nil, fmt.Errorf("error signing message in remote "+
"signer instance: %w", err)
}
sigParsed, err := schnorr.ParseSignature(resp.Signature)
if err != nil {
return nil, fmt.Errorf("can't parse schnorr signature: %w",
err)
}
return sigParsed, nil
}
// DerivePrivKey attempts to derive the private key that corresponds to the
// passed key descriptor. If the public key is set, then this method will
// perform an in-order scan over the key set, with a max of MaxKeyRangeScan
// keys. In order for this to work, the caller MUST set the KeyFamily within the
// partially populated KeyLocator.
//
// NOTE: This method is part of the keychain.SecretKeyRing interface.
func (r *RPCKeyRing) DerivePrivKey(_ keychain.KeyDescriptor) (*btcec.PrivateKey,
error) {
// This operation is not supported with remote signing. There should be
// no need for invoking this method unless a channel backup (SCB) file
// for pre-0.13.0 channels are attempted to be restored. In that case
// it is recommended to restore the channels using a node with the full
// seed available.
return nil, ErrRemoteSigningPrivateKeyNotAvailable
}
// SignOutputRaw generates a signature for the passed transaction
// according to the data within the passed SignDescriptor.
//
// NOTE: The resulting signature should be void of a sighash byte.
//
// NOTE: This method is part of the input.Signer interface.
//
// NOTE: This method only signs with BIP1017 (internal) keys!
func (r *RPCKeyRing) SignOutputRaw(tx *wire.MsgTx,
signDesc *input.SignDescriptor) (input.Signature, error) {
// Forward the call to the remote signing instance. This call is only
// ever called for signing witness (p2pkh or p2wsh) inputs and never
// nested witness inputs, so the sigScript is always nil.
return r.remoteSign(tx, signDesc, nil)
}
// ComputeInputScript generates a complete InputIndex for the passed
// transaction with the signature as defined within the passed
// SignDescriptor. This method should be capable of generating the
// proper input script for both regular p2wkh output and p2wkh outputs
// nested within a regular p2sh output.
//
// NOTE: This method will ignore any tweak parameters set within the
// passed SignDescriptor as it assumes a set of typical script
// templates (p2wkh, np2wkh, BIP0086 p2tr, etc).
//
// NOTE: This method is part of the input.Signer interface.
func (r *RPCKeyRing) ComputeInputScript(tx *wire.MsgTx,
signDesc *input.SignDescriptor) (*input.Script, error) {
addr, witnessProgram, sigScript, err := r.WalletController.ScriptForOutput(
signDesc.Output,
)
if err != nil {
return nil, err
}
signDesc.WitnessScript = witnessProgram
// If this is a p2tr address, then it must be a BIP0086 key spend if we
// are coming through this path (instead of SignOutputRaw).
switch addr.AddrType() {
case waddrmgr.TaprootPubKey:
signDesc.SignMethod = input.TaprootKeySpendBIP0086SignMethod
signDesc.WitnessScript = nil
sig, err := r.remoteSign(tx, signDesc, nil)
if err != nil {
return nil, fmt.Errorf("error signing with remote"+
"instance: %v", err)
}
rawSig := sig.Serialize()
if signDesc.HashType != txscript.SigHashDefault {
rawSig = append(rawSig, byte(signDesc.HashType))
}
return &input.Script{
Witness: wire.TxWitness{
rawSig,
},
}, nil
case waddrmgr.TaprootScript:
return nil, fmt.Errorf("computing input script for taproot " +
"script address not supported")
}
// Let's give the TX to the remote instance now, so it can sign the
// input.
sig, err := r.remoteSign(tx, signDesc, witnessProgram)
if err != nil {
return nil, fmt.Errorf("error signing with remote instance: %w",
err)
}
// ComputeInputScript currently is only used for P2WKH and NP2WKH
// addresses. So the last item on the stack is always the compressed
// public key.
return &input.Script{
Witness: wire.TxWitness{
append(sig.Serialize(), byte(signDesc.HashType)),
addr.PubKey().SerializeCompressed(),
},
SigScript: sigScript,
}, nil
}
// MuSig2CreateSession creates a new MuSig2 signing session using the local
// key identified by the key locator. The complete list of all public keys of
// all signing parties must be provided, including the public key of the local
// signing key. If nonces of other parties are already known, they can be
// submitted as well to reduce the number of method calls necessary later on.
func (r *RPCKeyRing) MuSig2CreateSession(bipVersion input.MuSig2Version,
keyLoc keychain.KeyLocator, pubKeys []*btcec.PublicKey,
tweaks *input.MuSig2Tweaks, otherNonces [][musig2.PubNonceSize]byte,
localNonces *musig2.Nonces) (*input.MuSig2SessionInfo, error) {
apiVersion, err := signrpc.MarshalMuSig2Version(bipVersion)
if err != nil {
return nil, err
}
// We need to serialize all data for the RPC call. We can do that by
// putting everything directly into the request struct.
req := &signrpc.MuSig2SessionRequest{
KeyLoc: &signrpc.KeyLocator{
KeyFamily: int32(keyLoc.Family),
KeyIndex: int32(keyLoc.Index),
},
AllSignerPubkeys: make([][]byte, len(pubKeys)),
Tweaks: make(
[]*signrpc.TweakDesc, len(tweaks.GenericTweaks),
),
OtherSignerPublicNonces: make([][]byte, len(otherNonces)),
Version: apiVersion,
}
for idx, pubKey := range pubKeys {
switch bipVersion {
case input.MuSig2Version040:
req.AllSignerPubkeys[idx] = schnorr.SerializePubKey(
pubKey,
)
case input.MuSig2Version100RC2:
req.AllSignerPubkeys[idx] = pubKey.SerializeCompressed()
}
}
for idx, genericTweak := range tweaks.GenericTweaks {
req.Tweaks[idx] = &signrpc.TweakDesc{
Tweak: genericTweak.Tweak[:],
IsXOnly: genericTweak.IsXOnly,
}
}
for idx, nonce := range otherNonces {
req.OtherSignerPublicNonces[idx] = make([]byte, len(nonce))
copy(req.OtherSignerPublicNonces[idx], nonce[:])
}
if tweaks.HasTaprootTweak() {
req.TaprootTweak = &signrpc.TaprootTweakDesc{
KeySpendOnly: tweaks.TaprootBIP0086Tweak,
ScriptRoot: tweaks.TaprootTweak,
}
}
if localNonces != nil {
req.PregeneratedLocalNonce = localNonces.SecNonce[:]
}
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel()
resp, err := r.signerClient.MuSig2CreateSession(ctxt, req)
if err != nil {
considerShutdown(err)
return nil, fmt.Errorf("error creating MuSig2 session in "+
"remote signer instance: %v", err)
}
// De-Serialize all the info back into our native struct.
info := &input.MuSig2SessionInfo{
Version: bipVersion,
TaprootTweak: tweaks.HasTaprootTweak(),
HaveAllNonces: resp.HaveAllNonces,
}
copy(info.SessionID[:], resp.SessionId)
copy(info.PublicNonce[:], resp.LocalPublicNonces)
info.CombinedKey, err = schnorr.ParsePubKey(resp.CombinedKey)
if err != nil {
return nil, fmt.Errorf("error parsing combined key: %w", err)
}
if tweaks.HasTaprootTweak() {
info.TaprootInternalKey, err = schnorr.ParsePubKey(
resp.TaprootInternalKey,
)
if err != nil {
return nil, fmt.Errorf("error parsing internal key: %w",
err)
}
}
return info, nil
}
// MuSig2RegisterNonces registers one or more public nonces of other signing
// participants for a session identified by its ID. This method returns true
// once we have all nonces for all other signing participants.
func (r *RPCKeyRing) MuSig2RegisterNonces(sessionID input.MuSig2SessionID,
pubNonces [][musig2.PubNonceSize]byte) (bool, error) {
// We need to serialize all data for the RPC call. We can do that by
// putting everything directly into the request struct.
req := &signrpc.MuSig2RegisterNoncesRequest{
SessionId: sessionID[:],
OtherSignerPublicNonces: make([][]byte, len(pubNonces)),
}
for idx, nonce := range pubNonces {
req.OtherSignerPublicNonces[idx] = make([]byte, len(nonce))
copy(req.OtherSignerPublicNonces[idx], nonce[:])
}
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel()
resp, err := r.signerClient.MuSig2RegisterNonces(ctxt, req)
if err != nil {
considerShutdown(err)
return false, fmt.Errorf("error registering MuSig2 nonces in "+
"remote signer instance: %v", err)
}
return resp.HaveAllNonces, nil
}
// MuSig2Sign creates a partial signature using the local signing key
// that was specified when the session was created. This can only be
// called when all public nonces of all participants are known and have
// been registered with the session. If this node isn't responsible for
// combining all the partial signatures, then the cleanup parameter
// should be set, indicating that the session can be removed from memory
// once the signature was produced.
func (r *RPCKeyRing) MuSig2Sign(sessionID input.MuSig2SessionID,
msg [sha256.Size]byte, cleanUp bool) (*musig2.PartialSignature, error) {
// We need to serialize all data for the RPC call. We can do that by
// putting everything directly into the request struct.
req := &signrpc.MuSig2SignRequest{
SessionId: sessionID[:],
MessageDigest: msg[:],
Cleanup: cleanUp,
}
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel()
resp, err := r.signerClient.MuSig2Sign(ctxt, req)
if err != nil {
considerShutdown(err)
return nil, fmt.Errorf("error signing MuSig2 session in "+
"remote signer instance: %v", err)
}
partialSig, err := input.DeserializePartialSignature(
resp.LocalPartialSignature,
)
if err != nil {
return nil, fmt.Errorf("error parsing partial signature from "+
"remote signer: %v", err)
}
return partialSig, nil
}
// MuSig2CombineSig combines the given partial signature(s) with the
// local one, if it already exists. Once a partial signature of all
// participants is registered, the final signature will be combined and
// returned.
func (r *RPCKeyRing) MuSig2CombineSig(sessionID input.MuSig2SessionID,
partialSigs []*musig2.PartialSignature) (*schnorr.Signature, bool,
error) {
// We need to serialize all data for the RPC call. We can do that by
// putting everything directly into the request struct.
req := &signrpc.MuSig2CombineSigRequest{
SessionId: sessionID[:],
OtherPartialSignatures: make([][]byte, len(partialSigs)),
}
for idx, partialSig := range partialSigs {
rawSig, err := input.SerializePartialSignature(partialSig)
if err != nil {
return nil, false, fmt.Errorf("error serializing "+
"partial signature: %v", err)
}
req.OtherPartialSignatures[idx] = rawSig[:]
}
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel()
resp, err := r.signerClient.MuSig2CombineSig(ctxt, req)
if err != nil {
considerShutdown(err)
return nil, false, fmt.Errorf("error combining MuSig2 "+
"signatures in remote signer instance: %v", err)
}
// The final signature is only available when we have all the other
// partial signatures from all participants.
if !resp.HaveAllSignatures {
return nil, resp.HaveAllSignatures, nil
}
finalSig, err := schnorr.ParseSignature(resp.FinalSignature)
if err != nil {
return nil, false, fmt.Errorf("error parsing final signature: "+
"%v", err)
}
return finalSig, resp.HaveAllSignatures, nil
}
// MuSig2Cleanup removes a session from memory to free up resources.
func (r *RPCKeyRing) MuSig2Cleanup(sessionID input.MuSig2SessionID) error {
req := &signrpc.MuSig2CleanupRequest{
SessionId: sessionID[:],
}
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel()
_, err := r.signerClient.MuSig2Cleanup(ctxt, req)
if err != nil {
considerShutdown(err)
return fmt.Errorf("error cleaning up MuSig2 session in remote "+
"signer instance: %v", err)
}
return nil
}
// remoteSign signs the input specified in signDesc of the given transaction tx
// using the remote signing instance.
func (r *RPCKeyRing) remoteSign(tx *wire.MsgTx, signDesc *input.SignDescriptor,
sigScript []byte) (input.Signature, error) {
packet, err := packetFromTx(tx)
if err != nil {
return nil, fmt.Errorf("error converting TX into PSBT: %w", err)
}
// We need to add witness information for all inputs! Otherwise, we'll
// have a problem when attempting to sign a taproot input!
for idx := range packet.Inputs {
// Skip the input we're signing for, that will get a special
// treatment later on.
if idx == signDesc.InputIndex {
continue
}
txIn := tx.TxIn[idx]
info, err := r.WalletController.FetchOutpointInfo(
&txIn.PreviousOutPoint,
)
if err != nil {
// Maybe we have an UTXO in the previous output fetcher?
if signDesc.PrevOutputFetcher != nil {
utxo := signDesc.PrevOutputFetcher.FetchPrevOutput(
txIn.PreviousOutPoint,
)
if utxo != nil && utxo.Value != 0 &&
len(utxo.PkScript) > 0 {
packet.Inputs[idx].WitnessUtxo = utxo
continue
}
}
log.Warnf("No UTXO info found for index %d "+
"(prev_outpoint=%v), won't be able to sign "+
"for taproot output!", idx,
txIn.PreviousOutPoint)
continue
}
packet.Inputs[idx].WitnessUtxo = &wire.TxOut{
Value: int64(info.Value),
PkScript: info.PkScript,
}
}
// Catch incorrect signing input index, just in case.
if signDesc.InputIndex < 0 || signDesc.InputIndex >= len(packet.Inputs) {
return nil, fmt.Errorf("invalid input index in sign descriptor")
}
in := &packet.Inputs[signDesc.InputIndex]
txIn := tx.TxIn[signDesc.InputIndex]
// Things are a bit tricky with the sign descriptor. There basically are
// four ways to describe a key:
// 1. By public key only. To match this case both family and index
// must be set to 0.
// 2. By family and index only. To match this case the public key
// must be nil and either the family or index must be non-zero.
// 3. All values are set and locator is non-empty. To match this case
// the public key must be set and either the family or index must
// be non-zero.
// 4. All values are set and locator is empty. This is a special case
// for the very first channel ever created (with the multi-sig key
// family which is 0 and the index which is 0 as well). This looks
// identical to case 1 and will also be handled like that case.
// We only really handle case 1 and 2 here, since 3 is no problem and 4
// is identical to 1.
switch {
// Case 1: Public key only. We need to find out the derivation path for
// this public key by asking the wallet. This is only possible for our
// internal, custom 1017 scope since we know all keys derived there are
// internally stored as p2wkh addresses.
case signDesc.KeyDesc.PubKey != nil && signDesc.KeyDesc.IsEmpty():
pubKeyBytes := signDesc.KeyDesc.PubKey.SerializeCompressed()
addr, err := btcutil.NewAddressWitnessPubKeyHash(
btcutil.Hash160(pubKeyBytes), r.netParams,
)
if err != nil {
return nil, fmt.Errorf("error deriving address from "+
"public key %x: %v", pubKeyBytes, err)
}
managedAddr, err := r.AddressInfo(addr)
if err != nil {
return nil, fmt.Errorf("error fetching address info "+
"for public key %x: %v", pubKeyBytes, err)
}
pubKeyAddr, ok := managedAddr.(waddrmgr.ManagedPubKeyAddress)
if !ok {
return nil, fmt.Errorf("address derived for public "+
"key %x is not a p2wkh address", pubKeyBytes)
}
scope, path, _ := pubKeyAddr.DerivationInfo()
if scope.Purpose != keychain.BIP0043Purpose {
return nil, fmt.Errorf("address derived for public "+
"key %x is not in custom key scope %d'",
pubKeyBytes, keychain.BIP0043Purpose)
}
// We now have all the information we need to complete our key
// locator information.
signDesc.KeyDesc.KeyLocator = keychain.KeyLocator{
Family: keychain.KeyFamily(path.InternalAccount),
Index: path.Index,
}
// Case 2: Family and index only. This case is easy, we can just go
// ahead and derive the public key from the family and index and then
// supply that information in the BIP32 derivation field.
case signDesc.KeyDesc.PubKey == nil && !signDesc.KeyDesc.IsEmpty():
fullDesc, err := r.watchOnlyKeyRing.DeriveKey(
signDesc.KeyDesc.KeyLocator,
)
if err != nil {
return nil, fmt.Errorf("error deriving key with "+
"family %d and index %d from the watch-only "+
"wallet: %v",
signDesc.KeyDesc.KeyLocator.Family,
signDesc.KeyDesc.KeyLocator.Index, err)
}
signDesc.KeyDesc.PubKey = fullDesc.PubKey
}
var derivation *psbt.Bip32Derivation
// Make sure we actually know about the input. We either have been
// watching the UTXO on-chain or we have been given all the required
// info in the sign descriptor.
info, err := r.WalletController.FetchOutpointInfo(
&txIn.PreviousOutPoint,
)
// If the wallet is aware of this outpoint, we go ahead and fetch the
// derivation info.
if err == nil {
derivation, err = r.WalletController.FetchDerivationInfo(
info.PkScript,
)
}
switch {
// No error, we do have the full UTXO info available.
case err == nil:
in.WitnessUtxo = &wire.TxOut{
Value: int64(info.Value),
PkScript: info.PkScript,
}
in.NonWitnessUtxo = info.PrevTx
in.Bip32Derivation = []*psbt.Bip32Derivation{derivation}
// The wallet doesn't know about this UTXO, so it's probably a TX that
// we haven't published yet (e.g. a channel funding TX). So we need to
// assemble everything from the sign descriptor. We won't be able to
// supply a non-witness UTXO (=full TX of the input being spent) in this
// case. That is no problem if the signing instance is another lnd
// instance since we don't require it for pure witness inputs. But a
// hardware wallet might require it for security reasons.
case signDesc.KeyDesc.PubKey != nil && signDesc.Output != nil:
in.WitnessUtxo = signDesc.Output
in.Bip32Derivation = []*psbt.Bip32Derivation{{
Bip32Path: []uint32{
keychain.BIP0043Purpose +
hdkeychain.HardenedKeyStart,
r.netParams.HDCoinType +
hdkeychain.HardenedKeyStart,
uint32(signDesc.KeyDesc.Family) +
hdkeychain.HardenedKeyStart,
0,
signDesc.KeyDesc.Index,
},
PubKey: signDesc.KeyDesc.PubKey.SerializeCompressed(),
}}
// We need to specify a pk script in the witness UTXO, otherwise
// the field becomes invalid when serialized as a PSBT. To avoid
// running into a generic "Invalid PSBT serialization format"
// error later, we return a more descriptive error now.
if len(in.WitnessUtxo.PkScript) == 0 {
return nil, fmt.Errorf("error assembling UTXO " +
"information, output not known to wallet and " +
"no UTXO pk script provided in sign descriptor")
}
default:
return nil, fmt.Errorf("error assembling UTXO information, "+
"wallet returned err='%v' and sign descriptor is "+
"incomplete", err)
}
// Assemble all other information about the input we have.
in.RedeemScript = sigScript
in.SighashType = signDesc.HashType
in.WitnessScript = signDesc.WitnessScript
if len(signDesc.SingleTweak) > 0 {
in.Unknowns = append(in.Unknowns, &psbt.Unknown{
Key: btcwallet.PsbtKeyTypeInputSignatureTweakSingle,
Value: signDesc.SingleTweak,
})
}
if signDesc.DoubleTweak != nil {
in.Unknowns = append(in.Unknowns, &psbt.Unknown{
Key: btcwallet.PsbtKeyTypeInputSignatureTweakDouble,
Value: signDesc.DoubleTweak.Serialize(),
})
}
// Add taproot specific fields.
switch signDesc.SignMethod {
case input.TaprootKeySpendBIP0086SignMethod,
input.TaprootKeySpendSignMethod:
// The key identifying factor for a key spend is that we don't
// provide any leaf hashes to signal we want a signature for the
// key spend path (with the internal key).
d := in.Bip32Derivation[0]
in.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{
// The x-only public key is just our compressed public
// key without the first byte (type/parity).
XOnlyPubKey: d.PubKey[1:],
LeafHashes: nil,
MasterKeyFingerprint: d.MasterKeyFingerprint,
Bip32Path: d.Bip32Path,
}}
// If this is a BIP0086 key spend then the tap tweak is empty,
// otherwise it's set to the Taproot root hash.
in.TaprootMerkleRoot = signDesc.TapTweak
case input.TaprootScriptSpendSignMethod:
// The script spend path is a bit more involved when doing it
// through the PSBT method. We need to specify the leaf hash
// that the signer should sign for.
leaf := txscript.TapLeaf{
LeafVersion: txscript.BaseLeafVersion,
Script: signDesc.WitnessScript,
}
leafHash := leaf.TapHash()
d := in.Bip32Derivation[0]
in.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{
XOnlyPubKey: d.PubKey[1:],
LeafHashes: [][]byte{leafHash[:]},
MasterKeyFingerprint: d.MasterKeyFingerprint,
Bip32Path: d.Bip32Path,
}}
// We also need to supply a control block. But because we don't
// know the internal key nor the merkle proofs (both is not
// supplied through the SignOutputRaw RPC) and is technically
// not really needed by the signer (since we only want a
// signature, the full witness stack is assembled by the caller
// of this RPC), we can get by with faking certain information
// that we don't have.
fakeInternalKey, _ := btcec.ParsePubKey(d.PubKey)
fakeKeyIsOdd := d.PubKey[0] == input.PubKeyFormatCompressedOdd
controlBlock := txscript.ControlBlock{
InternalKey: fakeInternalKey,
OutputKeyYIsOdd: fakeKeyIsOdd,
LeafVersion: leaf.LeafVersion,
}
blockBytes, err := controlBlock.ToBytes()
if err != nil {
return nil, fmt.Errorf("error serializing control "+
"block: %v", err)
}
in.TaprootLeafScript = []*psbt.TaprootTapLeafScript{{
ControlBlock: blockBytes,
Script: leaf.Script,
LeafVersion: leaf.LeafVersion,
}}
}
// Okay, let's sign the input by the remote signer now.
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel()
var buf bytes.Buffer
if err := packet.Serialize(&buf); err != nil {
return nil, fmt.Errorf("error serializing PSBT: %w", err)
}
resp, err := r.walletClient.SignPsbt(
ctxt, &walletrpc.SignPsbtRequest{FundedPsbt: buf.Bytes()},
)
if err != nil {
considerShutdown(err)
return nil, fmt.Errorf("error signing PSBT in remote signer "+
"instance: %v", err)
}
signedPacket, err := psbt.NewFromRawBytes(
bytes.NewReader(resp.SignedPsbt), false,
)
if err != nil {
return nil, fmt.Errorf("error parsing signed PSBT: %w", err)
}
// We expect a signature in the input now.
if signDesc.InputIndex >= len(signedPacket.Inputs) {
return nil, fmt.Errorf("remote signer returned invalid PSBT")
}
in = &signedPacket.Inputs[signDesc.InputIndex]
return extractSignature(in, signDesc.SignMethod)
}
// extractSignature attempts to extract the signature from the PSBT input,
// looking at different fields depending on the signing method that was used.
func extractSignature(in *psbt.PInput,
signMethod input.SignMethod) (input.Signature, error) {
switch signMethod {
case input.WitnessV0SignMethod:
if len(in.PartialSigs) != 1 {
return nil, fmt.Errorf("remote signer returned "+
"invalid partial signature, wanted 1, got %d",
len(in.PartialSigs))
}
sigWithSigHash := in.PartialSigs[0]
if sigWithSigHash == nil {
return nil, fmt.Errorf("remote signer returned nil " +
"signature")
}
// The remote signer always adds the sighash type, so we need to
// account for that.
sigLen := len(sigWithSigHash.Signature)
if sigLen < ecdsa.MinSigLen+1 {
return nil, fmt.Errorf("remote signer returned "+
"invalid partial signature: signature too "+
"short with %d bytes", sigLen)
}
// Parse the signature, but chop off the last byte which is the
// sighash type.
sig := sigWithSigHash.Signature[0 : sigLen-1]
return ecdsa.ParseDERSignature(sig)
// The type of key spend doesn't matter, the signature should be in the
// same field for both of those signing methods.
case input.TaprootKeySpendBIP0086SignMethod,
input.TaprootKeySpendSignMethod:
sigLen := len(in.TaprootKeySpendSig)
if sigLen < schnorr.SignatureSize {
return nil, fmt.Errorf("remote signer returned "+
"invalid key spend signature: signature too "+
"short with %d bytes", sigLen)
}
return schnorr.ParseSignature(
in.TaprootKeySpendSig[:schnorr.SignatureSize],
)
case input.TaprootScriptSpendSignMethod:
if len(in.TaprootScriptSpendSig) != 1 {
return nil, fmt.Errorf("remote signer returned "+
"invalid taproot script spend signature, "+
"wanted 1, got %d",
len(in.TaprootScriptSpendSig))
}
scriptSpendSig := in.TaprootScriptSpendSig[0]
if scriptSpendSig == nil {
return nil, fmt.Errorf("remote signer returned nil " +
"taproot script spend signature")
}
return schnorr.ParseSignature(scriptSpendSig.Signature)
default:
return nil, fmt.Errorf("can't extract signature, unsupported "+
"signing method: %v", signMethod)
}
}
// connectRPC tries to establish an RPC connection to the given host:port with
// the supplied certificate and macaroon.
func connectRPC(hostPort, tlsCertPath, macaroonPath string,
timeout time.Duration) (*grpc.ClientConn, error) {
certBytes, err := os.ReadFile(tlsCertPath)
if err != nil {
return nil, fmt.Errorf("error reading TLS cert file %v: %w",
tlsCertPath, err)
}
cp := x509.NewCertPool()
if !cp.AppendCertsFromPEM(certBytes) {
return nil, fmt.Errorf("credentials: failed to append " +
"certificate")
}
macBytes, err := os.ReadFile(macaroonPath)
if err != nil {
return nil, fmt.Errorf("error reading macaroon file %v: %w",
macaroonPath, err)
}
mac := &macaroon.Macaroon{}
if err := mac.UnmarshalBinary(macBytes); err != nil {
return nil, fmt.Errorf("error decoding macaroon: %w", err)
}
macCred, err := macaroons.NewMacaroonCredential(mac)
if err != nil {
return nil, fmt.Errorf("error creating creds: %w", err)
}
opts := []grpc.DialOption{
grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(
cp, "",
)),
grpc.WithPerRPCCredentials(macCred),
grpc.WithBlock(),
}
ctxt, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
conn, err := grpc.DialContext(ctxt, hostPort, opts...)
if err != nil {
return nil, fmt.Errorf("unable to connect to RPC server: %w",
err)
}
return conn, nil
}
// packetFromTx creates a PSBT from a tx that potentially already contains
// signed inputs.
func packetFromTx(original *wire.MsgTx) (*psbt.Packet, error) {
// The psbt.NewFromUnsignedTx function complains if there are any
// scripts or witness content on a TX. So we create a copy of the TX and
// nil out all the offending data, but also keep a backup around that we
// add to the PSBT afterwards.
noSigs := original.Copy()
for idx := range noSigs.TxIn {
noSigs.TxIn[idx].SignatureScript = nil
noSigs.TxIn[idx].Witness = nil
}
// With all the data that is seen as "signed", we can now create the
// empty packet.
packet, err := psbt.NewFromUnsignedTx(noSigs)
if err != nil {
return nil, err
}
var buf bytes.Buffer
for idx, txIn := range original.TxIn {
if len(txIn.SignatureScript) > 0 {
packet.Inputs[idx].FinalScriptSig = txIn.SignatureScript
}
if len(txIn.Witness) > 0 {
buf.Reset()
err = psbt.WriteTxWitness(&buf, txIn.Witness)
if err != nil {
return nil, err
}
packet.Inputs[idx].FinalScriptWitness = buf.Bytes()
}
}
return packet, nil
}
// considerShutdown inspects the error and issues a shutdown (through logging
// a critical error, which will cause the logger to issue a clean shutdown
// request) if the error looks like a connection or general availability error
// and not some application specific problem.
func considerShutdown(err error) {
statusErr, isStatusErr := status.FromError(err)
switch {
// The context attached to the client request has timed out. This can be
// due to not being able to reach the signing server, or it's taking too
// long to respond. In either case, request a shutdown.
case err == context.DeadlineExceeded:
fallthrough
// The signing server's context timed out before the client's due to
// clock skew, request a shutdown anyway.
case isStatusErr && statusErr.Code() == codes.DeadlineExceeded:
log.Critical("RPC signing timed out: %v", err)
case isStatusErr && statusErr.Code() == codes.Unavailable:
log.Critical("RPC signing server not available: %v", err)
}
}
package lnwallet
import (
"fmt"
"sync"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwire"
)
const (
// jobBuffer is a constant the represents the buffer of jobs in the two
// main queues. This allows clients avoid necessarily blocking when
// submitting jobs into the queue.
jobBuffer = 100
// TODO(roasbeef): job buffer pool?
)
// VerifyJob is a job sent to the sigPool sig pool to verify a signature
// on a transaction. The items contained in the struct are necessary and
// sufficient to verify the full signature. The passed sigHash closure function
// should be set to a function that generates the relevant sighash.
//
// TODO(roasbeef): when we move to ecschnorr, make into batch signature
// verification using bos-coster (or pip?).
type VerifyJob struct {
// PubKey is the public key that was used to generate the purported
// valid signature. Note that with the current channel construction,
// this public key will likely have been tweaked using the current per
// commitment point for a particular commitment transactions.
PubKey *btcec.PublicKey
// Sig is the raw signature generated using the above public key. This
// is the signature to be verified.
Sig input.Signature
// SigHash is a function closure generates the sighashes that the
// passed signature is known to have signed.
SigHash func() ([]byte, error)
// HtlcIndex is the index of the HTLC from the PoV of the remote
// party's update log.
HtlcIndex uint64
// Cancel is a channel that is closed by the caller if they wish to
// cancel all pending verification jobs part of a single batch. This
// channel is closed in the case that a single signature in a batch has
// been returned as invalid, as there is no need to verify the remainder
// of the signatures.
Cancel <-chan struct{}
// ErrResp is the channel that the result of the signature verification
// is to be sent over. In the see that the signature is valid, a nil
// error will be passed. Otherwise, a concrete error detailing the
// issue will be passed. This channel MUST be buffered.
ErrResp chan *HtlcIndexErr
}
// HtlcIndexErr is a special type of error that also includes a pointer to the
// original validation job. This error message allows us to craft more detailed
// errors at upper layers.
type HtlcIndexErr struct {
error
*VerifyJob
}
// SignJob is a job sent to the sigPool sig pool to generate a valid
// signature according to the passed SignDescriptor for the passed transaction.
// Jobs are intended to be sent in batches in order to parallelize the job of
// generating signatures for a new commitment transaction.
type SignJob struct {
// SignDesc is intended to be a full populated SignDescriptor which
// encodes the necessary material (keys, witness script, etc) required
// to generate a valid signature for the specified input.
SignDesc input.SignDescriptor
// Tx is the transaction to be signed. This is required to generate the
// proper sighash for the input to be signed.
Tx *wire.MsgTx
// OutputIndex is the output index of the HTLC on the commitment
// transaction being signed.
OutputIndex int32
// Cancel is a channel that is closed by the caller if they wish to
// abandon all pending sign jobs part of a single batch. This should
// never be closed by the validator.
Cancel <-chan struct{}
// Resp is the channel that the response to this particular SignJob
// will be sent over. This channel MUST be buffered.
//
// TODO(roasbeef): actually need to allow caller to set, need to retain
// order mark commit sig as special
Resp chan SignJobResp
}
// SignJobResp is the response to a sign job. Both channels are to be read in
// order to ensure no unnecessary goroutine blocking occurs. Additionally, both
// channels should be buffered.
type SignJobResp struct {
// Sig is the generated signature for a particular SignJob In the case
// of an error during signature generation, then this value sent will
// be nil.
Sig lnwire.Sig
// Err is the error that occurred when executing the specified
// signature job. In the case that no error occurred, this value will
// be nil.
Err error
}
// SigPool is a struct that is meant to allow the current channel state
// machine to parallelize all signature generation and verification. This
// struct is needed as _each_ HTLC when creating a commitment transaction
// requires a signature, and similarly a receiver of a new commitment must
// verify all the HTLC signatures included within the CommitSig message. A pool
// of workers will be maintained by the sigPool. Batches of jobs (either
// to sign or verify) can be sent to the pool of workers which will
// asynchronously perform the specified job.
type SigPool struct {
started sync.Once
stopped sync.Once
signer input.Signer
verifyJobs chan VerifyJob
signJobs chan SignJob
wg sync.WaitGroup
quit chan struct{}
numWorkers int
}
// NewSigPool creates a new signature pool with the specified number of
// workers. The recommended parameter for the number of works is the number of
// physical CPU cores available on the target machine.
func NewSigPool(numWorkers int, signer input.Signer) *SigPool {
return &SigPool{
signer: signer,
numWorkers: numWorkers,
verifyJobs: make(chan VerifyJob, jobBuffer),
signJobs: make(chan SignJob, jobBuffer),
quit: make(chan struct{}),
}
}
// Start starts of all goroutines that the sigPool sig pool needs to
// carry out its duties.
func (s *SigPool) Start() error {
s.started.Do(func() {
walletLog.Info("SigPool starting")
for i := 0; i < s.numWorkers; i++ {
s.wg.Add(1)
go s.poolWorker()
}
})
return nil
}
// Stop signals any active workers carrying out jobs to exit so the sigPool can
// gracefully shutdown.
func (s *SigPool) Stop() error {
s.stopped.Do(func() {
close(s.quit)
s.wg.Wait()
})
return nil
}
// poolWorker is the main worker goroutine within the sigPool sig pool.
// Individual batches are distributed amongst each of the active workers. The
// workers then execute the task based on the type of job, and return the
// result back to caller.
func (s *SigPool) poolWorker() {
defer s.wg.Done()
for {
select {
// We've just received a new signature job. Given the items
// contained within the message, we'll craft a signature and
// send the result along with a possible error back to the
// caller.
case sigMsg := <-s.signJobs:
rawSig, err := s.signer.SignOutputRaw(
sigMsg.Tx, &sigMsg.SignDesc,
)
if err != nil {
select {
case sigMsg.Resp <- SignJobResp{
Sig: lnwire.Sig{},
Err: err,
}:
continue
case <-sigMsg.Cancel:
continue
case <-s.quit:
return
}
}
// Use the sig mapper to go from the input.Signature
// into the serialized lnwire.Sig that we'll send
// across the wire.
sig, err := lnwire.NewSigFromSignature(rawSig)
select {
case sigMsg.Resp <- SignJobResp{
Sig: sig,
Err: err,
}:
case <-sigMsg.Cancel:
continue
case <-s.quit:
return
}
// We've just received a new verification job from the outside
// world. We'll attempt to construct the sighash, parse the
// signature, and finally verify the signature.
case verifyMsg := <-s.verifyJobs:
sigHash, err := verifyMsg.SigHash()
if err != nil {
select {
case verifyMsg.ErrResp <- &HtlcIndexErr{
error: err,
VerifyJob: &verifyMsg,
}:
continue
case <-verifyMsg.Cancel:
continue
}
}
rawSig := verifyMsg.Sig
if !rawSig.Verify(sigHash, verifyMsg.PubKey) {
err := fmt.Errorf("invalid signature "+
"sighash: %x, sig: %x", sigHash,
rawSig.Serialize())
select {
case verifyMsg.ErrResp <- &HtlcIndexErr{
error: err,
VerifyJob: &verifyMsg,
}:
case <-verifyMsg.Cancel:
case <-s.quit:
return
}
} else {
select {
case verifyMsg.ErrResp <- nil:
case <-verifyMsg.Cancel:
case <-s.quit:
return
}
}
// The sigPool sig pool is exiting, so we will as well.
case <-s.quit:
return
}
}
}
// SubmitSignBatch submits a batch of signature jobs to the sigPool. The
// response and cancel channels for each of the SignJob's are expected to be
// fully populated, as the response for each job will be sent over the
// response channel within the job itself.
func (s *SigPool) SubmitSignBatch(signJobs []SignJob) {
for _, job := range signJobs {
select {
case s.signJobs <- job:
case <-job.Cancel:
// TODO(roasbeef): return error?
case <-s.quit:
return
}
}
}
// SubmitVerifyBatch submits a batch of verification jobs to the sigPool. For
// each job submitted, an error will be passed into the returned channel
// denoting if signature verification was valid or not. The passed cancelChan
// allows the caller to cancel all pending jobs in the case that they wish to
// bail early.
func (s *SigPool) SubmitVerifyBatch(verifyJobs []VerifyJob,
cancelChan chan struct{}) <-chan *HtlcIndexErr {
errChan := make(chan *HtlcIndexErr, len(verifyJobs))
for _, job := range verifyJobs {
job.Cancel = cancelChan
job.ErrResp = errChan
select {
case s.verifyJobs <- job:
case <-job.Cancel:
return errChan
}
}
return errChan
}
package lnwallettest
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"fmt"
"net"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
"time"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/integration/rpctest"
"github.com/btcsuite/btcd/mempool"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/walletdb"
_ "github.com/btcsuite/btcwallet/walletdb/bdb"
"github.com/davecgh/go-spew/spew"
"github.com/lightninglabs/neutrino"
"github.com/lightningnetwork/lnd/blockcache"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lntest/unittest"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
var (
bobsPrivKey = []byte{
0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda,
0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17,
0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d,
0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9,
}
// Use a hard-coded HD seed.
testHdSeed = chainhash.Hash{
0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9,
0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
}
aliceHDSeed = chainhash.Hash{
0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
0x4f, 0x2f, 0x6f, 0x25, 0x18, 0xa3, 0xef, 0xb9,
0x64, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
}
bobHDSeed = chainhash.Hash{
0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
0x4f, 0x2f, 0x6f, 0x25, 0x98, 0xa3, 0xef, 0xb9,
0x69, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
}
netParams = &chaincfg.RegressionNetParams
chainHash = netParams.GenesisHash
_, alicePub = btcec.PrivKeyFromBytes(testHdSeed[:])
_, bobPub = btcec.PrivKeyFromBytes(bobsPrivKey)
// The number of confirmations required to consider any created channel
// open.
numReqConfs uint16 = 1
csvDelay uint16 = 4
bobAddr, _ = net.ResolveTCPAddr("tcp", "10.0.0.2:9000")
aliceAddr, _ = net.ResolveTCPAddr("tcp", "10.0.0.3:9000")
defaultMaxLocalCsvDelay uint16 = 10000
)
// assertProperBalance asserts than the total value of the unspent outputs
// within the wallet are *exactly* amount. If unable to retrieve the current
// balance, or the assertion fails, the test will halt with a fatal error.
func assertProperBalance(t *testing.T, lw *lnwallet.LightningWallet,
numConfirms int32, amount float64) {
balance, err := lw.ConfirmedBalance(numConfirms, lnwallet.DefaultAccountName)
require.NoError(t, err, "unable to query for balance")
if balance.ToBTC() != amount {
t.Fatalf("wallet credits not properly loaded, should have 40BTC, "+
"instead have %v", balance)
}
}
func assertReservationDeleted(res *lnwallet.ChannelReservation, t *testing.T) {
if err := res.Cancel(); err == nil {
t.Fatalf("reservation wasn't deleted from wallet")
}
}
// mineAndAssertTxInBlock asserts that a transaction is included within the next
// block mined.
func mineAndAssertTxInBlock(t *testing.T, miner *rpctest.Harness,
txid chainhash.Hash) {
t.Helper()
// First, we'll wait for the transaction to arrive in the mempool.
if err := waitForMempoolTx(miner, &txid); err != nil {
t.Fatalf("unable to find %v in the mempool: %v", txid, err)
}
// We'll mined a block to confirm it.
blockHashes, err := miner.Client.Generate(1)
require.NoError(t, err, "unable to generate new block")
// Finally, we'll check it was actually mined in this block.
block, err := miner.Client.GetBlock(blockHashes[0])
if err != nil {
t.Fatalf("unable to get block %v: %v", blockHashes[0], err)
}
if len(block.Transactions) != 2 {
t.Fatalf("expected 2 transactions in block, found %d",
len(block.Transactions))
}
txHash := block.Transactions[1].TxHash()
if txHash != txid {
t.Fatalf("expected transaction %v to be mined, found %v", txid,
txHash)
}
}
// newPkScript generates a new public key script of the given address type.
func newPkScript(t *testing.T, w *lnwallet.LightningWallet,
addrType lnwallet.AddressType) []byte {
t.Helper()
addr, err := w.NewAddress(addrType, false, lnwallet.DefaultAccountName)
require.NoError(t, err, "unable to create new address")
pkScript, err := txscript.PayToAddrScript(addr)
require.NoError(t, err, "unable to create output script")
return pkScript
}
// sendCoins is a helper function that encompasses all the things needed for two
// parties to send on-chain funds to each other.
func sendCoins(t *testing.T, miner *rpctest.Harness,
sender, receiver *lnwallet.LightningWallet, output *wire.TxOut,
feeRate chainfee.SatPerKWeight, mineBlock bool, minConf int32) *wire.MsgTx { //nolint:unparam
t.Helper()
tx, err := sender.SendOutputs(
nil, []*wire.TxOut{output}, feeRate, minConf, labels.External,
sender.Cfg.CoinSelectionStrategy,
)
require.NoError(t, err, "unable to send transaction")
if mineBlock {
mineAndAssertTxInBlock(t, miner, tx.TxHash())
}
if err := waitForWalletSync(miner, sender); err != nil {
t.Fatalf("unable to sync alice: %v", err)
}
if err := waitForWalletSync(miner, receiver); err != nil {
t.Fatalf("unable to sync bob: %v", err)
}
return tx
}
// assertTxInWallet asserts that a transaction exists in the wallet with the
// expected confirmation status.
func assertTxInWallet(t *testing.T, w *lnwallet.LightningWallet,
txHash chainhash.Hash, confirmed bool) {
t.Helper()
// We'll fetch all of our transaction and go through each one until
// finding the expected transaction with its expected confirmation
// status.
txs, _, _, err := w.ListTransactionDetails(
0, btcwallet.UnconfirmedHeight, "", 0, 1000,
)
require.NoError(t, err, "unable to retrieve transactions")
for _, tx := range txs {
if tx.Hash != txHash {
continue
}
if tx.NumConfirmations <= 0 && confirmed {
t.Fatalf("expected transaction %v to be confirmed",
txHash)
}
if tx.NumConfirmations > 0 && !confirmed {
t.Fatalf("expected transaction %v to be unconfirmed",
txHash)
}
// We've found the transaction and it matches the desired
// confirmation status, so we can exit.
return
}
t.Fatalf("transaction %v not found", txHash)
}
func loadTestCredits(miner *rpctest.Harness, w *lnwallet.LightningWallet,
numOutputs int, btcPerOutput float64) error {
// For initial neutrino connection, wait a second.
// TODO(aakselrod): Eliminate the need for this.
switch w.BackEnd() {
case "neutrino":
time.Sleep(time.Second)
}
// Using the mining node, spend from a coinbase output numOutputs to
// give us btcPerOutput with each output.
satoshiPerOutput, err := btcutil.NewAmount(btcPerOutput)
if err != nil {
return fmt.Errorf("unable to create amt: %w", err)
}
expectedBalance, err := w.ConfirmedBalance(1, lnwallet.DefaultAccountName)
if err != nil {
return err
}
expectedBalance += btcutil.Amount(int64(satoshiPerOutput) * int64(numOutputs))
addrs := make([]btcutil.Address, 0, numOutputs)
for i := 0; i < numOutputs; i++ {
// Grab a fresh address from the wallet to house this output.
walletAddr, err := w.NewAddress(
lnwallet.WitnessPubKey, false,
lnwallet.DefaultAccountName,
)
if err != nil {
return err
}
script, err := txscript.PayToAddrScript(walletAddr)
if err != nil {
return err
}
addrs = append(addrs, walletAddr)
output := &wire.TxOut{
Value: int64(satoshiPerOutput),
PkScript: script,
}
if _, err := miner.SendOutputs([]*wire.TxOut{output}, 2500); err != nil {
return err
}
}
// TODO(roasbeef): shouldn't hardcode 10, use config param that dictates
// how many confs we wait before opening a channel.
// Generate 10 blocks with the mining node, this should mine all
// numOutputs transactions created above. We generate 10 blocks here
// in order to give all the outputs a "sufficient" number of confirmations.
if _, err := miner.Client.Generate(10); err != nil {
return err
}
// Wait until the wallet has finished syncing up to the main chain.
ticker := time.NewTicker(100 * time.Millisecond)
timeout := time.After(30 * time.Second)
for range ticker.C {
balance, err := w.ConfirmedBalance(1, lnwallet.DefaultAccountName)
if err != nil {
return err
}
if balance == expectedBalance {
break
}
select {
case <-timeout:
synced, _, err := w.IsSynced()
if err != nil {
return err
}
return fmt.Errorf("timed out after 30 seconds "+
"waiting for balance %v, current balance %v, "+
"synced: %t", expectedBalance, balance, synced)
default:
}
}
ticker.Stop()
return nil
}
// createTestWallet creates a test LightningWallet will a total of 20BTC
// available for funding channels.
func createTestWallet(t *testing.T, tempTestDir string,
miningNode *rpctest.Harness, netParams *chaincfg.Params,
notifier chainntnfs.ChainNotifier, wc lnwallet.WalletController,
keyRing keychain.SecretKeyRing, signer input.Signer,
bio lnwallet.BlockChainIO) *lnwallet.LightningWallet {
dbDir := filepath.Join(tempTestDir, "cdb")
fullDB := channeldb.OpenForTesting(t, dbDir)
cfg := lnwallet.Config{
Database: fullDB.ChannelStateDB(),
Notifier: notifier,
SecretKeyRing: keyRing,
WalletController: wc,
Signer: signer,
ChainIO: bio,
FeeEstimator: chainfee.NewStaticEstimator(2500, 0),
NetParams: *netParams,
CoinSelectionStrategy: wallet.CoinSelectionLargest,
}
wallet, err := lnwallet.NewLightningWallet(cfg)
require.NoError(t, err)
require.NoError(t, wallet.Startup())
t.Cleanup(func() {
require.NoError(t, wallet.Shutdown())
})
// Load our test wallet with 20 outputs each holding 4BTC.
require.NoError(t, loadTestCredits(miningNode, wallet, 20, 4))
return wallet
}
func testGetRecoveryInfo(miner *rpctest.Harness,
alice, bob *lnwallet.LightningWallet, t *testing.T) {
// alice's wallet is in recovery mode
expectedRecoveryMode := true
expectedProgress := float64(1)
isRecoveryMode, progress, err := alice.GetRecoveryInfo()
require.NoError(t, err, "unable to get alice's recovery info")
require.Equal(t,
expectedRecoveryMode, isRecoveryMode, "recovery mode incorrect",
)
require.Equal(t, expectedProgress, progress, "progress incorrect")
// Generate 5 blocks and check the recovery process again.
const numBlocksMined = 5
_, err = miner.Client.Generate(numBlocksMined)
require.NoError(t, err, "unable to mine blocks")
// Check the recovery process. Once synced, the progress should be 1.
err = waitForWalletSync(miner, alice)
require.NoError(t, err, "Couldn't sync Alice's wallet")
isRecoveryMode, progress, err = alice.GetRecoveryInfo()
require.NoError(t, err, "unable to get alice's recovery info")
require.Equal(t,
expectedRecoveryMode, isRecoveryMode, "recovery mode incorrect",
)
require.Equal(t, expectedProgress, progress, "progress incorrect")
// bob's wallet is not in recovery mode
expectedRecoveryMode = false
expectedProgress = float64(0)
isRecoveryMode, progress, err = bob.GetRecoveryInfo()
require.NoError(t, err, "unable to get bob's recovery info")
require.Equal(t,
expectedRecoveryMode, isRecoveryMode, "recovery mode incorrect",
)
require.Equal(t, expectedProgress, progress, "progress incorrect")
}
func testDualFundingReservationWorkflow(miner *rpctest.Harness,
alice, bob *lnwallet.LightningWallet, t *testing.T) {
t.Skipf("dual funding isn't exposed on the p2p layer")
fundingAmount, err := btcutil.NewAmount(5)
require.NoError(t, err, "unable to create amt")
// In this scenario, we'll test a dual funder reservation, with each
// side putting in 10 BTC.
// Alice initiates a channel funded with 5 BTC for each side, so 10 BTC
// total. She also generates 2 BTC in change.
feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1)
require.NoError(t, err, "unable to query fee estimator")
aliceReq := &lnwallet.InitFundingReserveMsg{
ChainHash: chainHash,
NodeID: bobPub,
NodeAddr: bobAddr,
LocalFundingAmt: fundingAmount,
RemoteFundingAmt: fundingAmount,
CommitFeePerKw: feePerKw,
FundingFeePerKw: feePerKw,
PushMSat: 0,
Flags: lnwire.FFAnnounceChannel,
}
aliceChanReservation, err := alice.InitChannelReservation(aliceReq)
require.NoError(t, err, "unable to initialize funding reservation")
aliceChanReservation.SetNumConfsRequired(numReqConfs)
bounds := &channeldb.ChannelStateBounds{
ChanReserve: fundingAmount / 100,
MaxPendingAmount: lnwire.NewMSatFromSatoshis(fundingAmount),
MinHTLC: 1,
MaxAcceptedHtlcs: input.MaxHTLCNumber / 2,
}
commitParams := &channeldb.CommitmentParams{
DustLimit: lnwallet.DustLimitUnknownWitness(),
CsvDelay: csvDelay,
}
err = aliceChanReservation.CommitConstraints(
bounds, commitParams, defaultMaxLocalCsvDelay, false,
)
require.NoError(t, err, "unable to verify constraints")
// The channel reservation should now be populated with a multi-sig key
// from our HD chain, a change output with 3 BTC, and 2 outputs
// selected of 4 BTC each. Additionally, the rest of the items needed
// to fulfill a funding contribution should also have been filled in.
aliceContribution := aliceChanReservation.OurContribution()
if len(aliceContribution.Inputs) != 2 {
t.Fatalf("outputs for funding tx not properly selected, have %v "+
"outputs should have 2", len(aliceContribution.Inputs))
}
assertContributionInitPopulated(t, aliceContribution)
// Bob does the same, generating his own contribution. He then also
// receives' Alice's contribution, and consumes that so we can continue
// the funding process.
bobReq := &lnwallet.InitFundingReserveMsg{
ChainHash: chainHash,
NodeID: alicePub,
NodeAddr: aliceAddr,
LocalFundingAmt: fundingAmount,
RemoteFundingAmt: fundingAmount,
CommitFeePerKw: feePerKw,
FundingFeePerKw: feePerKw,
PushMSat: 0,
Flags: lnwire.FFAnnounceChannel,
}
bobChanReservation, err := bob.InitChannelReservation(bobReq)
require.NoError(t, err, "bob unable to init channel reservation")
err = bobChanReservation.CommitConstraints(
bounds, commitParams, defaultMaxLocalCsvDelay, true,
)
require.NoError(t, err, "unable to verify constraints")
bobChanReservation.SetNumConfsRequired(numReqConfs)
assertContributionInitPopulated(t, bobChanReservation.OurContribution())
err = bobChanReservation.ProcessContribution(aliceContribution)
require.NoError(t, err, "bob unable to process alice's contribution")
assertContributionInitPopulated(t, bobChanReservation.TheirContribution())
bobContribution := bobChanReservation.OurContribution()
// Bob then sends over his contribution, which will be consumed by
// Alice. After this phase, Alice should have all the necessary
// material required to craft the funding transaction and commitment
// transactions.
err = aliceChanReservation.ProcessContribution(bobContribution)
require.NoError(t, err, "alice unable to process bob's contribution")
assertContributionInitPopulated(t, aliceChanReservation.TheirContribution())
// At this point, all Alice's signatures should be fully populated.
aliceFundingSigs, aliceCommitSig := aliceChanReservation.OurSignatures()
if aliceFundingSigs == nil {
t.Fatalf("alice's funding signatures not populated")
}
if aliceCommitSig == nil {
t.Fatalf("alice's commit signatures not populated")
}
// Additionally, Bob's signatures should also be fully populated.
bobFundingSigs, bobCommitSig := bobChanReservation.OurSignatures()
if bobFundingSigs == nil {
t.Fatalf("bob's funding signatures not populated")
}
if bobCommitSig == nil {
t.Fatalf("bob's commit signatures not populated")
}
// To conclude, we'll consume first Alice's signatures with Bob, and
// then the other way around.
_, err = aliceChanReservation.CompleteReservation(
bobFundingSigs, bobCommitSig,
)
if err != nil {
for _, in := range aliceChanReservation.FinalFundingTx().TxIn {
fmt.Println(in.PreviousOutPoint.String())
}
t.Fatalf("unable to consume alice's sigs: %v", err)
}
_, err = bobChanReservation.CompleteReservation(
aliceFundingSigs, aliceCommitSig,
)
require.NoError(t, err, "unable to consume bob's sigs")
// At this point, the funding tx should have been populated.
fundingTx := aliceChanReservation.FinalFundingTx()
if fundingTx == nil {
t.Fatalf("funding transaction never created!")
}
// The resulting active channel state should have been persisted to the
// DB.
fundingSha := fundingTx.TxHash()
aliceChannels, err := alice.Cfg.Database.FetchOpenChannels(bobPub)
require.NoError(t, err, "unable to retrieve channel from DB")
if !bytes.Equal(aliceChannels[0].FundingOutpoint.Hash[:], fundingSha[:]) {
t.Fatalf("channel state not properly saved")
}
if !aliceChannels[0].ChanType.IsDualFunder() {
t.Fatalf("channel not detected as dual funder")
}
bobChannels, err := bob.Cfg.Database.FetchOpenChannels(alicePub)
require.NoError(t, err, "unable to retrieve channel from DB")
if !bytes.Equal(bobChannels[0].FundingOutpoint.Hash[:], fundingSha[:]) {
t.Fatalf("channel state not properly saved")
}
if !bobChannels[0].ChanType.IsDualFunder() {
t.Fatalf("channel not detected as dual funder")
}
// Let Alice publish the funding transaction.
err = alice.PublishTransaction(fundingTx, "")
require.NoError(t, err, "unable to publish funding tx")
// Mine a single block, the funding transaction should be included
// within this block.
err = waitForMempoolTx(miner, &fundingSha)
require.NoError(t, err, "tx not relayed to miner")
blockHashes, err := miner.Client.Generate(1)
require.NoError(t, err, "unable to generate block")
block, err := miner.Client.GetBlock(blockHashes[0])
require.NoError(t, err, "unable to find block")
if len(block.Transactions) != 2 {
t.Fatalf("funding transaction wasn't mined: %v", err)
}
blockTx := block.Transactions[1]
if blockTx.TxHash() != fundingSha {
t.Fatalf("incorrect transaction was mined")
}
assertReservationDeleted(aliceChanReservation, t)
assertReservationDeleted(bobChanReservation, t)
// Wait for wallets to catch up to prevent issues in subsequent tests.
err = waitForWalletSync(miner, alice)
require.NoError(t, err, "unable to sync alice")
err = waitForWalletSync(miner, bob)
require.NoError(t, err, "unable to sync bob")
}
func testFundingTransactionLockedOutputs(miner *rpctest.Harness,
alice, _ *lnwallet.LightningWallet, t *testing.T) {
// Create a single channel asking for 16 BTC total.
fundingAmount, err := btcutil.NewAmount(8)
require.NoError(t, err, "unable to create amt")
feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1)
require.NoError(t, err, "unable to query fee estimator")
req := &lnwallet.InitFundingReserveMsg{
ChainHash: chainHash,
NodeID: bobPub,
NodeAddr: bobAddr,
LocalFundingAmt: fundingAmount,
RemoteFundingAmt: 0,
CommitFeePerKw: feePerKw,
FundingFeePerKw: feePerKw,
PushMSat: 0,
Flags: lnwire.FFAnnounceChannel,
PendingChanID: [32]byte{0, 1, 2, 3},
}
if _, err := alice.InitChannelReservation(req); err != nil {
t.Fatalf("unable to initialize funding reservation 1: %v", err)
}
// Now attempt to reserve funds for another channel, this time
// requesting 900 BTC. We only have around 64BTC worth of outpoints
// that aren't locked, so this should fail.
amt, err := btcutil.NewAmount(900)
require.NoError(t, err, "unable to create amt")
failedReq := &lnwallet.InitFundingReserveMsg{
ChainHash: chainHash,
NodeID: bobPub,
NodeAddr: bobAddr,
LocalFundingAmt: amt,
RemoteFundingAmt: 0,
CommitFeePerKw: feePerKw,
FundingFeePerKw: feePerKw,
PushMSat: 0,
Flags: lnwire.FFAnnounceChannel,
PendingChanID: [32]byte{1, 2, 3, 4},
}
failedReservation, err := alice.InitChannelReservation(failedReq)
if err == nil {
t.Fatalf("not error returned, should fail on coin selection")
}
if _, ok := err.(*chanfunding.ErrInsufficientFunds); !ok {
t.Fatalf("error not coinselect error: %v", err)
}
if failedReservation != nil {
t.Fatalf("reservation should be nil")
}
}
func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness,
alice, _ *lnwallet.LightningWallet, t *testing.T) {
feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1)
require.NoError(t, err, "unable to query fee estimator")
// Create a reservation for 44 BTC.
fundingAmount, err := btcutil.NewAmount(44)
require.NoError(t, err, "unable to create amt")
req := &lnwallet.InitFundingReserveMsg{
ChainHash: chainHash,
NodeID: bobPub,
NodeAddr: bobAddr,
LocalFundingAmt: fundingAmount,
RemoteFundingAmt: 0,
CommitFeePerKw: feePerKw,
FundingFeePerKw: feePerKw,
PushMSat: 0,
Flags: lnwire.FFAnnounceChannel,
PendingChanID: [32]byte{2, 3, 4, 5},
}
chanReservation, err := alice.InitChannelReservation(req)
require.NoError(t, err, "unable to initialize funding reservation")
// Attempt to create another channel with 44 BTC, this should fail.
req.PendingChanID = [32]byte{3, 4, 5, 6}
_, err = alice.InitChannelReservation(req)
if _, ok := err.(*chanfunding.ErrInsufficientFunds); !ok {
t.Fatalf("coin selection succeeded should have insufficient funds: %v",
err)
}
// Now cancel that old reservation.
if err := chanReservation.Cancel(); err != nil {
t.Fatalf("unable to cancel reservation: %v", err)
}
// Those outpoints should no longer be locked.
lockedOutPoints := alice.LockedOutpoints()
if len(lockedOutPoints) != 0 {
t.Fatalf("outpoints still locked")
}
// Reservation ID should no longer be tracked.
numReservations := alice.ActiveReservations()
if len(alice.ActiveReservations()) != 0 {
t.Fatalf("should have 0 reservations, instead have %v",
numReservations)
}
// TODO(roasbeef): create method like Balance that ignores locked
// outpoints, will let us fail early/fast instead of querying and
// attempting coin selection.
// Request to fund a new channel should now succeed.
req.PendingChanID = [32]byte{4, 5, 6, 7, 8}
if _, err := alice.InitChannelReservation(req); err != nil {
t.Fatalf("unable to initialize funding reservation: %v", err)
}
}
func testCancelNonExistentReservation(miner *rpctest.Harness,
alice, _ *lnwallet.LightningWallet, t *testing.T) {
feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1)
require.NoError(t, err, "unable to query fee estimator")
req := &lnwallet.InitFundingReserveMsg{
CommitFeePerKw: feePerKw,
PushMSat: 10,
Flags: lnwire.FFAnnounceChannel,
CommitType: lnwallet.CommitmentTypeTweakless,
PendingChanID: [32]byte{},
}
// Create our own reservation, give it some ID.
res, err := lnwallet.NewChannelReservation(
10000, 10000, alice, 22, &testHdSeed, 0, req,
)
require.NoError(t, err, "unable to create res")
// Attempt to cancel this reservation. This should fail, we know
// nothing of it.
if err := res.Cancel(); err == nil {
t.Fatalf("canceled non-existent reservation")
}
}
func testReservationInitiatorBalanceBelowDustCancel(miner *rpctest.Harness,
alice, _ *lnwallet.LightningWallet, t *testing.T) {
// We'll attempt to create a new reservation with an extremely high
// commitment fee rate. This should push our balance into the negative
// and result in a failure to create the reservation.
const numBTC = 4
fundingAmount, err := btcutil.NewAmount(numBTC)
require.NoError(t, err, "unable to create amt")
feePerKw := chainfee.SatPerKWeight(
numBTC * numBTC * btcutil.SatoshiPerBitcoin,
)
req := &lnwallet.InitFundingReserveMsg{
ChainHash: chainHash,
NodeID: bobPub,
NodeAddr: bobAddr,
LocalFundingAmt: fundingAmount,
RemoteFundingAmt: 0,
CommitFeePerKw: feePerKw,
FundingFeePerKw: 1000,
PushMSat: 0,
Flags: lnwire.FFAnnounceChannel,
CommitType: lnwallet.CommitmentTypeTweakless,
PendingChanID: [32]byte{1},
}
_, err = alice.InitChannelReservation(req)
switch {
case err == nil:
t.Fatalf("initialization should have failed due to " +
"insufficient local amount")
case !strings.Contains(err.Error(), "funder balance too small"):
t.Fatalf("incorrect error: %v", err)
}
}
func assertContributionInitPopulated(t *testing.T, c *lnwallet.ChannelContribution) {
_, _, line, _ := runtime.Caller(1)
if c.FirstCommitmentPoint == nil {
t.Fatalf("line #%v: commitment point not fond", line)
}
if c.CsvDelay == 0 {
t.Fatalf("line #%v: csv delay not set", line)
}
if c.MultiSigKey.PubKey == nil {
t.Fatalf("line #%v: multi-sig key not set", line)
}
if c.RevocationBasePoint.PubKey == nil {
t.Fatalf("line #%v: revocation key not set", line)
}
if c.PaymentBasePoint.PubKey == nil {
t.Fatalf("line #%v: payment key not set", line)
}
if c.DelayBasePoint.PubKey == nil {
t.Fatalf("line #%v: delay key not set", line)
}
if c.DustLimit == 0 {
t.Fatalf("line #%v: dust limit not set", line)
}
if c.MaxPendingAmount == 0 {
t.Fatalf("line #%v: max pending amt not set", line)
}
if c.ChanReserve == 0 {
t.Fatalf("line #%v: chan reserve not set", line)
}
if c.MinHTLC == 0 {
t.Fatalf("line #%v: min htlc not set", line)
}
if c.MaxAcceptedHtlcs == 0 {
t.Fatalf("line #%v: max accepted htlc's not set", line)
}
}
func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
alice, bob *lnwallet.LightningWallet, t *testing.T,
commitType lnwallet.CommitmentType,
aliceChanFunder chanfunding.Assembler, fetchFundingTx func() *wire.MsgTx,
pendingChanID [32]byte, thawHeight uint32) {
// For this scenario, Alice will be the channel initiator while bob
// will act as the responder to the workflow.
// First, Alice will Initialize a reservation for a channel with 4 BTC
// funded solely by us. We'll also initially push 1 BTC of the channel
// towards Bob's side.
fundingAmt, err := btcutil.NewAmount(4)
require.NoError(t, err, "unable to create amt")
pushAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1)
require.NoError(t, err, "unable to query fee estimator")
aliceReq := &lnwallet.InitFundingReserveMsg{
ChainHash: chainHash,
PendingChanID: pendingChanID,
NodeID: bobPub,
NodeAddr: bobAddr,
LocalFundingAmt: fundingAmt,
RemoteFundingAmt: 0,
CommitFeePerKw: feePerKw,
FundingFeePerKw: feePerKw,
PushMSat: pushAmt,
Flags: lnwire.FFAnnounceChannel,
CommitType: commitType,
ChanFunder: aliceChanFunder,
}
aliceChanReservation, err := alice.InitChannelReservation(aliceReq)
require.NoError(t, err, "unable to init channel reservation")
aliceChanReservation.SetNumConfsRequired(numReqConfs)
bounds := &channeldb.ChannelStateBounds{
ChanReserve: fundingAmt / 100,
MaxPendingAmount: lnwire.NewMSatFromSatoshis(fundingAmt),
MinHTLC: 1,
MaxAcceptedHtlcs: input.MaxHTLCNumber / 2,
}
commitParams := &channeldb.CommitmentParams{
DustLimit: lnwallet.DustLimitUnknownWitness(),
CsvDelay: csvDelay,
}
err = aliceChanReservation.CommitConstraints(
bounds, commitParams, defaultMaxLocalCsvDelay, false,
)
require.NoError(t, err, "unable to verify constraints")
// Verify all contribution fields have been set properly, but only if
// Alice is the funder herself.
aliceContribution := aliceChanReservation.OurContribution()
if fetchFundingTx == nil {
if len(aliceContribution.Inputs) < 1 {
t.Fatalf("outputs for funding tx not properly "+
"selected, have %v outputs should at least 1",
len(aliceContribution.Inputs))
}
if len(aliceContribution.ChangeOutputs) != 1 {
t.Fatalf("coin selection failed, should have one "+
"change outputs, instead have: %v",
len(aliceContribution.ChangeOutputs))
}
}
assertContributionInitPopulated(t, aliceContribution)
// Next, Bob receives the initial request, generates a corresponding
// reservation initiation, then consume Alice's contribution.
bobReq := &lnwallet.InitFundingReserveMsg{
ChainHash: chainHash,
PendingChanID: pendingChanID,
NodeID: alicePub,
NodeAddr: aliceAddr,
LocalFundingAmt: 0,
RemoteFundingAmt: fundingAmt,
CommitFeePerKw: feePerKw,
FundingFeePerKw: feePerKw,
PushMSat: pushAmt,
Flags: lnwire.FFAnnounceChannel,
CommitType: commitType,
}
bobChanReservation, err := bob.InitChannelReservation(bobReq)
require.NoError(t, err, "unable to create bob reservation")
err = bobChanReservation.CommitConstraints(
bounds, commitParams, defaultMaxLocalCsvDelay, true,
)
require.NoError(t, err, "unable to verify constraints")
bobChanReservation.SetNumConfsRequired(numReqConfs)
// We'll ensure that Bob's contribution also gets generated properly.
bobContribution := bobChanReservation.OurContribution()
assertContributionInitPopulated(t, bobContribution)
// With his contribution generated, he can now process Alice's
// contribution.
err = bobChanReservation.ProcessSingleContribution(aliceContribution)
require.NoError(t, err, "bob unable to process alice's contribution")
assertContributionInitPopulated(t, bobChanReservation.TheirContribution())
// Bob will next send over his contribution to Alice, we simulate this
// by having Alice immediately process his contribution.
err = aliceChanReservation.ProcessContribution(bobContribution)
if err != nil {
t.Fatalf("alice unable to process bob's contribution: %v", err)
}
assertContributionInitPopulated(t, bobChanReservation.TheirContribution())
// At this point, Alice should have generated all the signatures
// required for the funding transaction, as well as Alice's commitment
// signature to bob, but only if the funding transaction was
// constructed internally.
aliceRemoteContribution := aliceChanReservation.TheirContribution()
aliceFundingSigs, aliceCommitSig := aliceChanReservation.OurSignatures()
if fetchFundingTx == nil && aliceFundingSigs == nil {
t.Fatalf("funding sigs not found")
}
if aliceCommitSig == nil {
t.Fatalf("commitment sig not found")
}
// Additionally, the funding tx and the funding outpoint should have
// been populated.
if aliceChanReservation.FinalFundingTx() == nil && fetchFundingTx == nil {
t.Fatalf("funding transaction never created!")
}
if aliceChanReservation.FundingOutpoint() == nil {
t.Fatalf("funding outpoint never created!")
}
// Their funds should also be filled in.
if len(aliceRemoteContribution.Inputs) != 0 {
t.Fatalf("bob shouldn't have any inputs, instead has %v",
len(aliceRemoteContribution.Inputs))
}
if len(aliceRemoteContribution.ChangeOutputs) != 0 {
t.Fatalf("bob shouldn't have any change outputs, instead "+
"has %v",
aliceRemoteContribution.ChangeOutputs[0].Value)
}
// Next, Alice will send over her signature for Bob's commitment
// transaction, as well as the funding outpoint.
fundingPoint := aliceChanReservation.FundingOutpoint()
_, err = bobChanReservation.CompleteReservationSingle(
fundingPoint, aliceCommitSig,
fn.None[lnwallet.AuxFundingDesc](),
)
require.NoError(t, err, "bob unable to consume single reservation")
// Finally, we'll conclude the reservation process by sending over
// Bob's commitment signature, which is the final thing Alice needs to
// be able to safely broadcast the funding transaction.
_, bobCommitSig := bobChanReservation.OurSignatures()
if bobCommitSig == nil {
t.Fatalf("bob failed to generate commitment signature: %v", err)
}
_, err = aliceChanReservation.CompleteReservation(
nil, bobCommitSig,
)
require.NoError(t, err, "alice unable to complete reservation")
// If the caller provided an alternative way to obtain the funding tx,
// then we'll use that. Otherwise, we'll obtain it directly from Alice.
var fundingTx *wire.MsgTx
if fetchFundingTx != nil {
fundingTx = fetchFundingTx()
} else {
fundingTx = aliceChanReservation.FinalFundingTx()
}
// The resulting active channel state should have been persisted to the
// DB for both Alice and Bob.
fundingSha := fundingTx.TxHash()
aliceChannels, err := alice.Cfg.Database.FetchOpenChannels(bobPub)
require.NoError(t, err, "unable to retrieve channel from DB")
if len(aliceChannels) != 1 {
t.Fatalf("alice didn't save channel state: %v", err)
}
if !bytes.Equal(aliceChannels[0].FundingOutpoint.Hash[:], fundingSha[:]) {
t.Fatalf("channel state not properly saved: %v vs %v",
hex.EncodeToString(aliceChannels[0].FundingOutpoint.Hash[:]),
hex.EncodeToString(fundingSha[:]))
}
if !aliceChannels[0].IsInitiator {
t.Fatalf("alice not detected as channel initiator")
}
if !aliceChannels[0].ChanType.IsSingleFunder() {
t.Fatalf("channel type is incorrect, expected %v instead got %v",
channeldb.SingleFunderBit, aliceChannels[0].ChanType)
}
bobChannels, err := bob.Cfg.Database.FetchOpenChannels(alicePub)
require.NoError(t, err, "unable to retrieve channel from DB")
if len(bobChannels) != 1 {
t.Fatalf("bob didn't save channel state: %v", err)
}
if !bytes.Equal(bobChannels[0].FundingOutpoint.Hash[:], fundingSha[:]) {
t.Fatalf("channel state not properly saved: %v vs %v",
hex.EncodeToString(bobChannels[0].FundingOutpoint.Hash[:]),
hex.EncodeToString(fundingSha[:]))
}
if bobChannels[0].IsInitiator {
t.Fatalf("bob not detected as channel responder")
}
if !bobChannels[0].ChanType.IsSingleFunder() {
t.Fatalf("channel type is incorrect, expected %v instead got %v",
channeldb.SingleFunderBit, bobChannels[0].ChanType)
}
// Let Alice publish the funding transaction.
err = alice.PublishTransaction(fundingTx, "")
require.NoError(t, err, "unable to publish funding tx")
// Mine a single block, the funding transaction should be included
// within this block.
err = waitForMempoolTx(miner, &fundingSha)
require.NoError(t, err, "tx not relayed to miner")
blockHashes, err := miner.Client.Generate(1)
require.NoError(t, err, "unable to generate block")
block, err := miner.Client.GetBlock(blockHashes[0])
require.NoError(t, err, "unable to find block")
if len(block.Transactions) != 2 {
t.Fatalf("funding transaction wasn't mined: %d",
len(block.Transactions))
}
blockTx := block.Transactions[1]
if blockTx.TxHash() != fundingSha {
t.Fatalf("incorrect transaction was mined")
}
// If a frozen channel was requested, then we expect that both channel
// types show as being a frozen channel type.
aliceChanFrozen := aliceChannels[0].ChanType.IsFrozen()
bobChanFrozen := bobChannels[0].ChanType.IsFrozen()
if thawHeight != 0 && (!aliceChanFrozen || !bobChanFrozen) {
t.Fatalf("expected both alice and bob to have frozen chans: "+
"alice_frozen=%v, bob_frozen=%v", aliceChanFrozen,
bobChanFrozen)
}
if thawHeight != bobChannels[0].ThawHeight {
t.Fatalf("wrong thaw height: expected %v got %v", thawHeight,
bobChannels[0].ThawHeight)
}
if thawHeight != aliceChannels[0].ThawHeight {
t.Fatalf("wrong thaw height: expected %v got %v", thawHeight,
aliceChannels[0].ThawHeight)
}
assertReservationDeleted(aliceChanReservation, t)
assertReservationDeleted(bobChanReservation, t)
}
func testListTransactionDetails(miner *rpctest.Harness,
alice, _ *lnwallet.LightningWallet, t *testing.T) {
// Create 5 new outputs spendable by the wallet.
const numTxns = 5
const outputAmt = btcutil.SatoshiPerBitcoin
isOurAddress := make(map[string]bool)
txids := make(map[chainhash.Hash]struct{})
for i := 0; i < numTxns; i++ {
addr, err := alice.NewAddress(
lnwallet.WitnessPubKey, false,
lnwallet.DefaultAccountName,
)
if err != nil {
t.Fatalf("unable to create new address: %v", err)
}
isOurAddress[addr.EncodeAddress()] = true
script, err := txscript.PayToAddrScript(addr)
if err != nil {
t.Fatalf("unable to create output script: %v", err)
}
output := &wire.TxOut{
Value: outputAmt,
PkScript: script,
}
txid, err := miner.SendOutputs([]*wire.TxOut{output}, 2500)
if err != nil {
t.Fatalf("unable to send coinbase: %v", err)
}
txids[*txid] = struct{}{}
}
// Get the miner's current best block height before we mine blocks.
_, startHeight, err := miner.Client.GetBestBlock()
require.NoError(t, err, "cannot get best block")
// Generate 10 blocks to mine all the transactions created above.
const numBlocksMined = 10
blocks, err := miner.Client.Generate(numBlocksMined)
require.NoError(t, err, "unable to mine blocks")
// Our new best block height should be our start height + the number of
// blocks we just mined.
chainTip := startHeight + numBlocksMined
// Next, fetch all the current transaction details. We should find all
// of our transactions between our start height before we generated
// blocks, and our end height, which is the chain tip. This query does
// not include unconfirmed transactions, since all of our transactions
// should be confirmed.
err = waitForWalletSync(miner, alice)
require.NoError(t, err, "Couldn't sync Alice's wallet")
txDetails, _, _, err := alice.ListTransactionDetails(
startHeight, chainTip, "", 0, 1000,
)
require.NoError(t, err, "unable to fetch tx details")
// This is a mapping from:
// blockHash -> transactionHash -> transactionOutputs
blockTxOuts := make(map[chainhash.Hash]map[chainhash.Hash][]*wire.TxOut)
// Each of the transactions created above should be found with the
// proper details populated.
for _, txDetail := range txDetails {
if _, ok := txids[txDetail.Hash]; !ok {
continue
}
if txDetail.NumConfirmations != numBlocksMined {
t.Fatalf("num confs incorrect, got %v expected %v",
txDetail.NumConfirmations, numBlocksMined)
}
if txDetail.Value != outputAmt {
t.Fatalf("tx value incorrect, got %v expected %v",
txDetail.Value, outputAmt)
}
if !bytes.Equal(txDetail.BlockHash[:], blocks[0][:]) {
t.Fatalf("block hash mismatch, got %v expected %v",
txDetail.BlockHash, blocks[0])
}
// This fetches the transactions in a block so that we can compare the
// txouts stored in the mined transaction against the ones in the transaction
// details
if _, ok := blockTxOuts[*txDetail.BlockHash]; !ok {
fetchedBlock, err := alice.Cfg.ChainIO.GetBlock(txDetail.BlockHash)
if err != nil {
t.Fatalf("err fetching block: %s", err)
}
transactions := make(
map[chainhash.Hash][]*wire.TxOut,
len(fetchedBlock.Transactions),
)
for _, tx := range fetchedBlock.Transactions {
transactions[tx.TxHash()] = tx.Copy().TxOut
}
blockTxOuts[fetchedBlock.BlockHash()] = transactions
}
if txOuts, ok := blockTxOuts[*txDetail.BlockHash][txDetail.Hash]; !ok {
t.Fatalf("tx (%v) not found in block (%v)",
txDetail.Hash, txDetail.BlockHash)
} else {
var destinationOutputs []lnwallet.OutputDetail
for i, txOut := range txOuts {
sc, addrs, _, err :=
txscript.ExtractPkScriptAddrs(txOut.PkScript, &alice.Cfg.NetParams)
if err != nil {
t.Fatalf("err extract script addresses: %s", err)
}
destinationOutputs = append(destinationOutputs, lnwallet.OutputDetail{
OutputType: sc,
Addresses: addrs,
PkScript: txOut.PkScript,
OutputIndex: i,
Value: btcutil.Amount(txOut.Value),
IsOurAddress: isOurAddress[addrs[0].EncodeAddress()],
})
}
if !reflect.DeepEqual(txDetail.OutputDetails, destinationOutputs) {
t.Fatalf("destination outputs mismatch, got %v expected %v",
txDetail.OutputDetails, destinationOutputs)
}
}
delete(txids, txDetail.Hash)
}
if len(txids) != 0 {
t.Fatalf("all transactions not found in details: left=%v, "+
"returned_set=%v", spew.Sdump(txids),
spew.Sdump(txDetails))
}
// Next create a transaction paying to an output which isn't under the
// wallet's control.
minerAddr, err := miner.NewAddress()
require.NoError(t, err, "unable to generate address")
outputScript, err := txscript.PayToAddrScript(minerAddr)
require.NoError(t, err, "unable to make output script")
burnOutput := wire.NewTxOut(outputAmt, outputScript)
burnTX, err := alice.SendOutputs(
nil, []*wire.TxOut{burnOutput}, 2500, 1, labels.External,
alice.Cfg.CoinSelectionStrategy,
)
require.NoError(t, err, "unable to create burn tx")
burnTXID := burnTX.TxHash()
err = waitForMempoolTx(miner, &burnTXID)
require.NoError(t, err, "tx not relayed to miner")
// Before we mine the next block, we'll ensure that the above
// transaction shows up in the set of unconfirmed transactions returned
// by ListTransactionDetails.
err = waitForWalletSync(miner, alice)
require.NoError(t, err, "Couldn't sync Alice's wallet")
// Query our wallet for transactions from the chain tip, including
// unconfirmed transactions. The transaction above should be included
// with a confirmation height of 0, indicating that it has not been
// mined yet.
txDetails, _, _, err = alice.ListTransactionDetails(
chainTip, btcwallet.UnconfirmedHeight, "", 0, 1000,
)
require.NoError(t, err, "unable to fetch tx details")
var mempoolTxFound bool
for _, txDetail := range txDetails {
if !bytes.Equal(txDetail.Hash[:], burnTXID[:]) {
continue
}
// Now that we've found the transaction, ensure that it has a
// negative number of confirmations to indicate that it's
// unconfirmed.
mempoolTxFound = true
if txDetail.NumConfirmations != 0 {
t.Fatalf("num confs incorrect, got %v expected %v",
txDetail.NumConfirmations, 0)
}
// We test that each txDetail has destination addresses. This ensures
// that even when we have 0 confirmation transactions, the destination
// addresses are returned.
var match bool
for _, o := range txDetail.OutputDetails {
for _, addr := range o.Addresses {
if addr.String() == minerAddr.String() {
match = true
break
}
}
}
if !match {
t.Fatalf("minerAddr: %v should have been a dest addr", minerAddr)
}
}
if !mempoolTxFound {
t.Fatalf("unable to find mempool tx in tx details!")
}
// Generate one block for our transaction to confirm in.
var numBlocks int32 = 1
burnBlock, err := miner.Client.Generate(uint32(numBlocks))
require.NoError(t, err, "unable to mine block")
// Progress our chain tip by the number of blocks we have just mined.
chainTip += numBlocks
// Fetch the transaction details again, the new transaction should be
// shown as debiting from the wallet's balance. Start and end height
// are inclusive, so we use chainTip for both parameters to get only
// transactions from the last block.
err = waitForWalletSync(miner, alice)
require.NoError(t, err, "Couldn't sync Alice's wallet")
txDetails, _, _, err = alice.ListTransactionDetails(
chainTip, chainTip, "", 0, 1000,
)
require.NoError(t, err, "unable to fetch tx details")
var burnTxFound bool
for _, txDetail := range txDetails {
if !bytes.Equal(txDetail.Hash[:], burnTXID[:]) {
continue
}
burnTxFound = true
if txDetail.NumConfirmations != 1 {
t.Fatalf("num confs incorrect, got %v expected %v",
txDetail.NumConfirmations, 1)
}
// We assert that the value is greater than the amount we
// attempted to send, as the wallet should have paid some amount
// of network fees.
if txDetail.Value >= -outputAmt {
fmt.Println(spew.Sdump(txDetail))
t.Fatalf("tx value incorrect, got %v expected %v",
int64(txDetail.Value), -int64(outputAmt))
}
if !bytes.Equal(txDetail.BlockHash[:], burnBlock[0][:]) {
t.Fatalf("block hash mismatch, got %v expected %v",
txDetail.BlockHash, burnBlock[0])
}
}
if !burnTxFound {
t.Fatal("tx burning btc not found")
}
// Generate a block which has no wallet transactions in it.
chainTip += numBlocks
_, err = miner.Client.Generate(uint32(numBlocks))
require.NoError(t, err, "unable to mine block")
err = waitForWalletSync(miner, alice)
require.NoError(t, err, "Couldn't sync Alice's wallet")
// Query for transactions only in the latest block. We do not expect
// any transactions to be returned.
txDetails, _, _, err = alice.ListTransactionDetails(
chainTip, chainTip, "", 0, 1000,
)
require.NoError(t, err, "unexpected error")
if len(txDetails) != 0 {
t.Fatalf("expected 0 transactions, got: %v", len(txDetails))
}
}
func testListTransactionDetailsOffset(miner *rpctest.Harness,
alice, _ *lnwallet.LightningWallet, t *testing.T) {
// Create 5 new outputs spendable by the wallet.
const numTxns = 5
const outputAmt = btcutil.SatoshiPerBitcoin
isOurAddress := make(map[string]bool)
txids := make(map[chainhash.Hash]struct{})
for i := 0; i < numTxns; i++ {
addr, err := alice.NewAddress(
lnwallet.WitnessPubKey, false,
lnwallet.DefaultAccountName,
)
require.NoError(t, err)
isOurAddress[addr.EncodeAddress()] = true
script, err := txscript.PayToAddrScript(addr)
require.NoError(t, err)
output := &wire.TxOut{
Value: outputAmt,
PkScript: script,
}
txid, err := miner.SendOutputs([]*wire.TxOut{output}, 2500)
require.NoError(t, err)
txids[*txid] = struct{}{}
}
// Get the miner's current best block height before we mine blocks.
_, startHeight, err := miner.Client.GetBestBlock()
require.NoError(t, err, "cannot get best block")
// Generate 10 blocks to mine all the transactions created above.
const numBlocksMined = 10
_, err = miner.Client.Generate(numBlocksMined)
require.NoError(t, err, "unable to mine blocks")
// Our new best block height should be our start height + the number of
// blocks we just mined.
chainTip := startHeight + numBlocksMined
err = waitForWalletSync(miner, alice)
require.NoError(t, err, "Couldn't sync Alice's wallet")
// Query for transactions, setting max_transactions to 5. We expect 5
// transactions to be returned.
txDetails, firstIdx, lastIdx, err := alice.ListTransactionDetails(
startHeight, chainTip, "", 0, 5,
)
require.NoError(t, err)
require.Len(t, txDetails, 5)
require.EqualValues(t, 0, firstIdx)
require.EqualValues(t, 4, lastIdx)
// Query for transactions, setting max_transactions to less than the
// number of transactions we have (5).
txDetails, _, _, err = alice.ListTransactionDetails(
startHeight, chainTip, "", 0, 1,
)
require.NoError(t, err)
require.Len(t, txDetails, 1)
// Query for transactions, setting indexOffset to 5 (equal to number
// of transactions we have) and max_transactions to 0.
txDetails, _, _, err = alice.ListTransactionDetails(
startHeight, chainTip, "", 5, 0,
)
require.NoError(t, err)
require.Len(t, txDetails, 0)
// Query for transactions, setting indexOffset to 4 (edge offset) and
// max_transactions to 0.
txDetails, _, _, err = alice.ListTransactionDetails(
startHeight, chainTip, "", 4, 0,
)
require.NoError(t, err)
require.Len(t, txDetails, 1)
// Query for transactions, setting max_transactions to 0.
txDetails, _, _, err = alice.ListTransactionDetails(
startHeight, chainTip, "", 0, 0,
)
require.NoError(t, err)
require.Len(t, txDetails, 5)
// Query for transactions, more than we have in the wallet (5).
txDetails, _, _, err = alice.ListTransactionDetails(
startHeight, chainTip, "", 0, 10,
)
require.NoError(t, err)
require.Len(t, txDetails, 5)
// Query for transactions where the offset is greater than the number
// of transactions available.
txDetails, _, _, err = alice.ListTransactionDetails(
startHeight, chainTip, "", 10, 100,
)
require.NoError(t, err)
require.Len(t, txDetails, 0)
}
func testTransactionSubscriptions(miner *rpctest.Harness,
alice, _ *lnwallet.LightningWallet, t *testing.T) {
// First, check to see if this wallet meets the TransactionNotifier
// interface, if not then we'll skip this test for this particular
// implementation of the WalletController.
txClient, err := alice.SubscribeTransactions()
if err != nil {
t.Skipf("unable to generate tx subscription: %v", err)
}
defer txClient.Cancel()
const (
outputAmt = btcutil.SatoshiPerBitcoin
numTxns = 3
)
errCh1 := make(chan error, 1)
switch alice.BackEnd() {
case "neutrino":
// Neutrino doesn't listen for unconfirmed transactions.
default:
go func() {
for i := 0; i < numTxns; i++ {
txDetail := <-txClient.UnconfirmedTransactions()
if txDetail.NumConfirmations != 0 {
errCh1 <- fmt.Errorf("incorrect number of confs, "+
"expected %v got %v", 0,
txDetail.NumConfirmations)
return
}
if txDetail.Value != outputAmt {
errCh1 <- fmt.Errorf("incorrect output amt, "+
"expected %v got %v", outputAmt,
txDetail.Value)
return
}
if txDetail.BlockHash != nil {
errCh1 <- fmt.Errorf("block hash should be nil, "+
"is instead %v",
txDetail.BlockHash)
return
}
}
errCh1 <- nil
}()
}
// Next, fetch a fresh address from the wallet, create 3 new outputs
// with the pkScript.
for i := 0; i < numTxns; i++ {
addr, err := alice.NewAddress(
lnwallet.WitnessPubKey, false,
lnwallet.DefaultAccountName,
)
if err != nil {
t.Fatalf("unable to create new address: %v", err)
}
script, err := txscript.PayToAddrScript(addr)
if err != nil {
t.Fatalf("unable to create output script: %v", err)
}
output := &wire.TxOut{
Value: outputAmt,
PkScript: script,
}
txid, err := miner.SendOutputs([]*wire.TxOut{output}, 2500)
if err != nil {
t.Fatalf("unable to send coinbase: %v", err)
}
err = waitForMempoolTx(miner, txid)
if err != nil {
t.Fatalf("tx not relayed to miner: %v", err)
}
}
switch alice.BackEnd() {
case "neutrino":
// Neutrino doesn't listen for on unconfirmed transactions.
default:
// We should receive a notification for all three transactions
// generated above.
select {
case <-time.After(time.Second * 10):
t.Fatalf("transactions not received after 10 seconds")
case err := <-errCh1:
if err != nil {
t.Fatal(err)
}
}
}
errCh2 := make(chan error, 1)
go func() {
for i := 0; i < numTxns; i++ {
txDetail := <-txClient.ConfirmedTransactions()
if txDetail.NumConfirmations != 1 {
errCh2 <- fmt.Errorf("incorrect number of confs for %s, expected %v got %v",
txDetail.Hash, 1, txDetail.NumConfirmations)
return
}
if txDetail.Value != outputAmt {
errCh2 <- fmt.Errorf("incorrect output amt, expected %v got %v in txid %s",
outputAmt, txDetail.Value, txDetail.Hash)
return
}
}
errCh2 <- nil
}()
// Next mine a single block, all the transactions generated above
// should be included.
if _, err := miner.Client.Generate(1); err != nil {
t.Fatalf("unable to generate block: %v", err)
}
// We should receive a notification for all three transactions
// since they should be mined in the next block.
select {
case <-time.After(time.Second * 5):
t.Fatalf("transactions not received after 5 seconds")
case err := <-errCh2:
if err != nil {
t.Fatal(err)
}
}
// We'll also ensure that the client is able to send our new
// notifications when we _create_ transactions ourselves that spend our
// own outputs.
addr, err := alice.NewAddress(
lnwallet.WitnessPubKey, false,
lnwallet.DefaultAccountName,
)
require.NoError(t, err)
outputScript, err := txscript.PayToAddrScript(addr)
require.NoError(t, err)
burnOutput := wire.NewTxOut(outputAmt, outputScript)
tx, err := alice.SendOutputs(
nil, []*wire.TxOut{burnOutput}, 2500, 1, labels.External,
alice.Cfg.CoinSelectionStrategy,
)
require.NoError(t, err, "unable to create tx")
txid := tx.TxHash()
err = waitForMempoolTx(miner, &txid)
require.NoError(t, err, "tx not relayed to miner")
// Before we mine the next block, we'll ensure that the above
// transaction shows up in the set of unconfirmed transactions returned
// by ListTransactionDetails.
err = waitForWalletSync(miner, alice)
require.NoError(t, err, "Couldn't sync Alice's wallet")
// As we just sent the transaction and it was landed in the mempool, we
// should get a notification for a new unconfirmed transactions
select {
case <-time.After(time.Second * 10):
t.Fatalf("transactions not received after 10 seconds")
case unConfTx := <-txClient.UnconfirmedTransactions():
if unConfTx.Hash != txid {
t.Fatalf("wrong txn notified: expected %v got %v",
txid, unConfTx.Hash)
}
}
}
// scriptFromKey creates a P2WKH script from the given pubkey.
func scriptFromKey(pubkey *btcec.PublicKey) ([]byte, error) {
pubkeyHash := btcutil.Hash160(pubkey.SerializeCompressed())
keyAddr, err := btcutil.NewAddressWitnessPubKeyHash(
pubkeyHash, &chaincfg.RegressionNetParams,
)
if err != nil {
return nil, fmt.Errorf("unable to create addr: %w", err)
}
keyScript, err := txscript.PayToAddrScript(keyAddr)
if err != nil {
return nil, fmt.Errorf("unable to generate script: %w", err)
}
return keyScript, nil
}
// mineAndAssert mines a block and ensures the passed TX is part of that block.
func mineAndAssert(r *rpctest.Harness, tx *wire.MsgTx) error {
txid := tx.TxHash()
err := waitForMempoolTx(r, &txid)
if err != nil {
return fmt.Errorf("tx not relayed to miner: %w", err)
}
blockHashes, err := r.Client.Generate(1)
if err != nil {
return fmt.Errorf("unable to generate block: %w", err)
}
block, err := r.Client.GetBlock(blockHashes[0])
if err != nil {
return fmt.Errorf("unable to find block: %w", err)
}
if len(block.Transactions) != 2 {
return fmt.Errorf("expected 2 txs in block, got %d",
len(block.Transactions))
}
blockTx := block.Transactions[1]
if blockTx.TxHash() != tx.TxHash() {
return fmt.Errorf("incorrect transaction was mined")
}
// Sleep for a second before returning, to make sure the block has
// propagated.
time.Sleep(1 * time.Second)
return nil
}
// txFromOutput takes a tx paying to fromPubKey, and creates a new tx that
// spends the output from this tx, to an address derived from payToPubKey.
func txFromOutput(tx *wire.MsgTx, signer input.Signer, fromPubKey,
payToPubKey *btcec.PublicKey, txFee btcutil.Amount,
rbf bool) (*wire.MsgTx, error) {
// Generate the script we want to spend from.
keyScript, err := scriptFromKey(fromPubKey)
if err != nil {
return nil, fmt.Errorf("unable to generate script: %w", err)
}
// We assume the output was paid to the keyScript made earlier.
var outputIndex uint32
if len(tx.TxOut) == 1 || bytes.Equal(tx.TxOut[0].PkScript, keyScript) {
outputIndex = 0
} else {
outputIndex = 1
}
outputValue := tx.TxOut[outputIndex].Value
// With the index located, we can create a transaction spending the
// referenced output.
tx1 := wire.NewMsgTx(2)
// If we want to create a tx that signals replacement, set its
// sequence number to the max one that signals replacement.
// Otherwise we just use the standard max sequence.
sequence := wire.MaxTxInSequenceNum
if rbf {
sequence = mempool.MaxRBFSequence
}
tx1.AddTxIn(&wire.TxIn{
PreviousOutPoint: wire.OutPoint{
Hash: tx.TxHash(),
Index: outputIndex,
},
Sequence: sequence,
})
// Create a script to pay to.
payToScript, err := scriptFromKey(payToPubKey)
if err != nil {
return nil, fmt.Errorf("unable to generate script: %w", err)
}
tx1.AddTxOut(&wire.TxOut{
Value: outputValue - int64(txFee),
PkScript: payToScript,
})
// Now we can populate the sign descriptor which we'll use to generate
// the signature.
signDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: fromPubKey,
},
WitnessScript: keyScript,
Output: tx.TxOut[outputIndex],
HashType: txscript.SigHashAll,
SigHashes: input.NewTxSigHashesV0Only(tx1),
InputIndex: 0, // Has only one input.
}
// With the descriptor created, we use it to generate a signature, then
// manually create a valid witness stack we'll use for signing.
spendSig, err := signer.SignOutputRaw(tx1, signDesc)
if err != nil {
return nil, fmt.Errorf("unable to generate signature: %w", err)
}
witness := make([][]byte, 2)
witness[0] = append(spendSig.Serialize(), byte(txscript.SigHashAll))
witness[1] = fromPubKey.SerializeCompressed()
tx1.TxIn[0].Witness = witness
// Finally, attempt to validate the completed transaction. This should
// succeed if the wallet was able to properly generate the proper
// private key.
vm, err := txscript.NewEngine(
keyScript, tx1, 0, txscript.StandardVerifyFlags, nil,
nil, outputValue, txscript.NewCannedPrevOutputFetcher(
keyScript, outputValue,
),
)
if err != nil {
return nil, fmt.Errorf("unable to create engine: %w", err)
}
if err := vm.Execute(); err != nil {
return nil, fmt.Errorf("spend is invalid: %w", err)
}
return tx1, nil
}
// newTx sends coins from Alice's wallet, mines this transaction, and creates a
// new, unconfirmed tx that spends this output to pubKey.
func newTx(t *testing.T, r *rpctest.Harness, pubKey *btcec.PublicKey,
alice *lnwallet.LightningWallet, rbf bool) *wire.MsgTx {
t.Helper()
keyScript, err := scriptFromKey(pubKey)
require.NoError(t, err, "unable to generate script")
// Instruct the wallet to fund the output with a newly created
// transaction.
newOutput := &wire.TxOut{
Value: btcutil.SatoshiPerBitcoin,
PkScript: keyScript,
}
tx, err := alice.SendOutputs(
nil, []*wire.TxOut{newOutput}, 2500, 1, labels.External,
alice.Cfg.CoinSelectionStrategy,
)
require.NoError(t, err, "unable to create output")
// Query for the transaction generated above so we can located the
// index of our output.
if err := mineAndAssert(r, tx); err != nil {
t.Fatalf("unable to mine tx: %v", err)
}
// Create a new unconfirmed tx that spends this output.
txFee := btcutil.Amount(0.001 * btcutil.SatoshiPerBitcoin)
tx1, err := txFromOutput(
tx, alice.Cfg.Signer, pubKey, pubKey, txFee, rbf,
)
if err != nil {
t.Fatal(err)
}
return tx1
}
// testGetTransactionDetails checks that GetTransactionDetails returns the
// correct amount after a transaction has been sent from alice to bob.
func testGetTransactionDetails(r *rpctest.Harness,
alice, bob *lnwallet.LightningWallet, t *testing.T) {
const txFee = int64(14500)
bobPkScript := newPkScript(t, bob, lnwallet.WitnessPubKey)
txFeeRate := chainfee.SatPerKWeight(2500)
amountSats := btcutil.Amount(btcutil.SatoshiPerBitcoin - txFee)
output := &wire.TxOut{
Value: int64(amountSats),
PkScript: bobPkScript,
}
tx := sendCoins(t, r, alice, bob, output, txFeeRate, true, 1)
txHash := tx.TxHash()
assertTxInWallet(t, alice, txHash, true)
assertTxInWallet(t, bob, txHash, true)
txDetails, err := bob.GetTransactionDetails(&txHash)
require.NoError(t, err, "unable to receive transaction details")
require.Equal(t, txDetails.Value, amountSats, "tx value")
}
// testPublishTransaction checks that PublishTransaction returns the expected
// error types in case the transaction being published conflicts with the
// current mempool or chain.
func testPublishTransaction(r *rpctest.Harness,
alice, _ *lnwallet.LightningWallet, t *testing.T) {
// Generate a pubkey, and pay-to-addr script.
keyDesc, err := alice.DeriveNextKey(keychain.KeyFamilyMultiSig)
require.NoError(t, err, "unable to obtain public key")
t.Run("no_error_on_duplicate_tx", func(t *testing.T) {
// We will first check that publishing a transaction already in
// the mempool does NOT return an error. Create the tx.
tx1 := newTx(t, r, keyDesc.PubKey, alice, false)
// Publish the transaction.
err = alice.PublishTransaction(tx1, labels.External)
require.NoError(t, err)
txid1 := tx1.TxHash()
err = waitForMempoolTx(r, &txid1)
require.NoError(t, err, "tx not relayed to miner")
// Publish the exact same transaction again. This should not
// return an error, even though the transaction is already in
// the mempool.
err = alice.PublishTransaction(tx1, labels.External)
require.NoError(t, err)
// Mine the transaction.
_, err := r.Client.Generate(1)
require.NoError(t, err)
})
t.Run("no_error_on_minged_tx", func(t *testing.T) {
// We'll now test that we don't get an error if we try to
// publish a transaction that is already mined.
//
// Create a new transaction. We must do this to properly test
// the reject messages from our peers. They might only send us
// a reject message for a given tx once, so we create a new to
// make sure it is not just immediately rejected.
tx2 := newTx(t, r, keyDesc.PubKey, alice, false)
// Publish this tx.
err = alice.PublishTransaction(tx2, labels.External)
require.NoError(t, err)
// Mine the transaction.
err := mineAndAssert(r, tx2)
require.NoError(t, err)
// Publish the transaction again. It is already mined, and we
// don't expect this to return an error.
err = alice.PublishTransaction(tx2, labels.External)
require.NoError(t, err)
})
// We'll do the next mempool check on both RBF and non-RBF
// enabled transactions.
var (
txFee = btcutil.Amount(
0.005 * btcutil.SatoshiPerBitcoin,
)
tx3, tx3Spend *wire.MsgTx
)
t.Run("rbf_tests", func(t *testing.T) {
// Starting with bitcoind v28.0 and later, mempool full RBF is
// turned on, so there's no way to _not_ signal RBF anymore.
const rbf = true
// Now we'll try to double spend an output with a
// different transaction. Create a new tx and publish
// it. This is the output we'll try to double spend.
tx3 = newTx(t, r, keyDesc.PubKey, alice, false)
err := alice.PublishTransaction(tx3, labels.External)
require.NoError(t, err)
// Mine the transaction.
err = mineAndAssert(r, tx3)
require.NoError(t, err)
// Now we create a transaction that spends the output
// from the tx just mined.
tx4, err := txFromOutput(
tx3, alice.Cfg.Signer, keyDesc.PubKey,
keyDesc.PubKey, txFee, rbf,
)
require.NoError(t, err)
// This should be accepted into the mempool.
err = alice.PublishTransaction(tx4, labels.External)
require.NoError(t, err)
// Keep track of the last successfully published tx to
// spend tx3.
tx3Spend = tx4
txid4 := tx4.TxHash()
err = waitForMempoolTx(r, &txid4)
require.NoError(t, err, "tx not relayed to miner")
// Create a new key we'll pay to, to ensure we create a
// unique transaction.
keyDesc2, err := alice.DeriveNextKey(
keychain.KeyFamilyMultiSig,
)
require.NoError(t, err, "unable to obtain public key")
// Create a new transaction that spends the output from
// tx3, and that pays to a different address.
tx5, err := txFromOutput(
tx3, alice.Cfg.Signer, keyDesc.PubKey,
keyDesc2.PubKey, txFee, rbf,
)
require.NoError(t, err)
err = alice.PublishTransaction(tx5, labels.External)
// We expect it to be rejected/ because it doesn't pay enough
// fees.
expectedErr := chain.ErrInsufficientFee
// Assert the expected error.
require.ErrorIsf(t, err, expectedErr, "has rbf=%v", rbf)
// Create another transaction that spends the same
// output, but has a higher fee. We expect also this tx
// to be rejected for non-RBF enabled transactions,
// while it should succeed otherwise.
pubKey3, err := alice.DeriveNextKey(
keychain.KeyFamilyMultiSig,
)
require.NoError(t, err, "unable to obtain public key")
tx6, err := txFromOutput(
tx3, alice.Cfg.Signer, keyDesc.PubKey,
pubKey3.PubKey, 2*txFee, rbf,
)
require.NoError(t, err)
// Expect rejection in non-RBF case.
tx3Spend = tx6
err = alice.PublishTransaction(tx6, labels.External)
require.NoError(t, err)
// Mine the tx spending tx3.
err = mineAndAssert(r, tx3Spend)
require.NoError(t, err)
})
t.Run("tx_double_spend", func(t *testing.T) {
// At last we try to spend an output already spent by a
// confirmed transaction.
//
// TODO(halseth): we currently skip this test for neutrino, as
// the backing btcd node will consider the tx being an orphan,
// and will accept it. Should look into if this is the behavior
// also for bitcoind, and update test accordingly.
if alice.BackEnd() != "neutrino" {
// Create another tx spending tx3.
pubKey4, err := alice.DeriveNextKey(
keychain.KeyFamilyMultiSig,
)
require.NoError(t, err, "unable to obtain public key")
tx7, err := txFromOutput(
tx3, alice.Cfg.Signer, keyDesc.PubKey,
pubKey4.PubKey, txFee, false,
)
require.NoError(t, err)
// Expect rejection.
err = alice.PublishTransaction(tx7, labels.External)
require.ErrorIs(t, err, lnwallet.ErrDoubleSpend)
}
})
t.Run("test_tx_size_limit", func(t *testing.T) {
// In this test, we'll try to create a massive transaction that
// can't be mined as dictacted by widely deployed transaction
// policy.
//
// To do this, we'll take out of the prior transactions, and
// add a bunch of outputs, putting it over the max weight
// limit.
testTx := tx3.Copy()
for i := 0; i < blockchain.MaxOutputsPerBlock; i++ {
testTx.AddTxOut(&wire.TxOut{
Value: tx3.TxOut[0].Value,
PkScript: tx3.TxOut[0].PkScript,
})
}
// Now broadcast the transaction, we should get an error that
// the weight is too large.
err := alice.PublishTransaction(testTx, labels.External)
require.ErrorIs(t, err, chain.ErrOversizeTx)
})
}
func testSignOutputUsingTweaks(r *rpctest.Harness,
alice, _ *lnwallet.LightningWallet, t *testing.T) {
// We'd like to test the ability of the wallet's Signer implementation
// to be able to sign with a private key derived from tweaking the
// specific public key. This scenario exercises the case when the
// wallet needs to sign for a sweep of a revoked output, or just claim
// any output that pays to a tweaked key.
// First, generate a new public key under the control of the wallet,
// then generate a revocation key using it.
pubKey, err := alice.DeriveNextKey(
keychain.KeyFamilyMultiSig,
)
require.NoError(t, err, "unable to obtain public key")
// As we'd like to test both single tweak, and double tweak spends,
// we'll generate a commitment pre-image, then derive a revocation key
// and single tweak from that.
commitPreimage := bytes.Repeat([]byte{2}, 32)
commitSecret, commitPoint := btcec.PrivKeyFromBytes(commitPreimage)
revocationKey := input.DeriveRevocationPubkey(pubKey.PubKey, commitPoint)
commitTweak := input.SingleTweakBytes(commitPoint, pubKey.PubKey)
tweakedPub := input.TweakPubKey(pubKey.PubKey, commitPoint)
// As we'd like to test both single and double tweaks, we'll repeat
// the same set up twice. The first will use a regular single tweak,
// and the second will use a double tweak.
baseKey := pubKey
for i := 0; i < 2; i++ {
var tweakedKey *btcec.PublicKey
if i == 0 {
tweakedKey = tweakedPub
} else {
tweakedKey = revocationKey
}
// Using the given key for the current iteration, we'll
// generate a regular p2wkh from that.
pubkeyHash := btcutil.Hash160(tweakedKey.SerializeCompressed())
keyAddr, err := btcutil.NewAddressWitnessPubKeyHash(pubkeyHash,
&chaincfg.RegressionNetParams)
if err != nil {
t.Fatalf("unable to create addr: %v", err)
}
keyScript, err := txscript.PayToAddrScript(keyAddr)
if err != nil {
t.Fatalf("unable to generate script: %v", err)
}
// With the script fully assembled, instruct the wallet to fund
// the output with a newly created transaction.
newOutput := &wire.TxOut{
Value: btcutil.SatoshiPerBitcoin,
PkScript: keyScript,
}
tx, err := alice.SendOutputs(
nil, []*wire.TxOut{newOutput}, 2500, 1, labels.External,
alice.Cfg.CoinSelectionStrategy,
)
if err != nil {
t.Fatalf("unable to create output: %v", err)
}
txid := tx.TxHash()
// Query for the transaction generated above so we can located
// the index of our output.
err = waitForMempoolTx(r, &txid)
if err != nil {
t.Fatalf("tx not relayed to miner: %v", err)
}
var outputIndex uint32
if bytes.Equal(tx.TxOut[0].PkScript, keyScript) {
outputIndex = 0
} else {
outputIndex = 1
}
// With the index located, we can create a transaction spending
// the referenced output.
sweepTx := wire.NewMsgTx(2)
sweepTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: wire.OutPoint{
Hash: txid,
Index: outputIndex,
},
})
sweepTx.AddTxOut(&wire.TxOut{
Value: 1000,
PkScript: keyScript,
})
// Now we can populate the sign descriptor which we'll use to
// generate the signature. Within the descriptor we set the
// private tweak value as the key in the script is derived
// based on this tweak value and the key we originally
// generated above.
signDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: baseKey.PubKey,
},
WitnessScript: keyScript,
Output: newOutput,
HashType: txscript.SigHashAll,
SigHashes: input.NewTxSigHashesV0Only(sweepTx),
InputIndex: 0,
}
// If this is the first, loop, we'll use the generated single
// tweak, otherwise, we'll use the double tweak.
if i == 0 {
signDesc.SingleTweak = commitTweak
} else {
signDesc.DoubleTweak = commitSecret
}
// With the descriptor created, we use it to generate a
// signature, then manually create a valid witness stack we'll
// use for signing.
spendSig, err := alice.Cfg.Signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
t.Fatalf("unable to generate signature: %v", err)
}
witness := make([][]byte, 2)
witness[0] = append(spendSig.Serialize(), byte(txscript.SigHashAll))
witness[1] = tweakedKey.SerializeCompressed()
sweepTx.TxIn[0].Witness = witness
// Finally, attempt to validate the completed transaction. This
// should succeed if the wallet was able to properly generate
// the proper private key.
vm, err := txscript.NewEngine(
keyScript, sweepTx, 0, txscript.StandardVerifyFlags,
nil, nil, int64(btcutil.SatoshiPerBitcoin),
txscript.NewCannedPrevOutputFetcher(
keyScript, int64(btcutil.SatoshiPerBitcoin),
),
)
if err != nil {
t.Fatalf("unable to create engine: %v", err)
}
if err := vm.Execute(); err != nil {
t.Fatalf("spend #%v is invalid: %v", i, err)
}
}
}
func testReorgWalletBalance(r *rpctest.Harness, w *lnwallet.LightningWallet,
_ *lnwallet.LightningWallet, t *testing.T) {
// We first mine a few blocks to ensure any transactions still in the
// mempool confirm, and then get the original balance, before a
// reorganization that doesn't invalidate any existing transactions or
// create any new non-coinbase transactions. We'll then check if it's
// the same after the empty reorg.
_, err := r.Client.Generate(5)
require.NoError(t, err, "unable to generate blocks on passed node")
// Give wallet time to catch up.
err = waitForWalletSync(r, w)
require.NoError(t, err, "unable to sync wallet")
// Send some money from the miner to the wallet
err = loadTestCredits(r, w, 20, 4)
require.NoError(t, err, "unable to send money to lnwallet")
// Send some money from the wallet back to the miner.
// Grab a fresh address from the miner to house this output.
minerAddr, err := r.NewAddress()
require.NoError(t, err, "unable to generate address for miner")
script, err := txscript.PayToAddrScript(minerAddr)
require.NoError(t, err, "unable to create pay to addr script")
output := &wire.TxOut{
Value: 1e8,
PkScript: script,
}
tx, err := w.SendOutputs(
nil, []*wire.TxOut{output}, 2500, 1, labels.External,
w.Cfg.CoinSelectionStrategy,
)
require.NoError(t, err, "unable to send outputs")
txid := tx.TxHash()
err = waitForMempoolTx(r, &txid)
require.NoError(t, err, "tx not relayed to miner")
_, err = r.Client.Generate(50)
require.NoError(t, err, "unable to generate blocks on passed node")
// Give wallet time to catch up.
err = waitForWalletSync(r, w)
require.NoError(t, err, "unable to sync wallet")
// Get the original balance.
origBalance, err := w.ConfirmedBalance(1, lnwallet.DefaultAccountName)
require.NoError(t, err, "unable to query for balance")
// Now we cause a reorganization as follows.
// Step 1: create a new miner and start it.
r2 := unittest.NewMiner(
t, r.ActiveNet, []string{"--txindex"}, false, 0,
)
newBalance, err := w.ConfirmedBalance(1, lnwallet.DefaultAccountName)
require.NoError(t, err, "unable to query for balance")
if origBalance != newBalance {
t.Fatalf("wallet balance incorrect, should have %v, "+
"instead have %v", origBalance, newBalance)
}
// Step 2: connect the miner to the passed miner and wait for
// synchronization.
err = r2.Client.AddNode(r.P2PAddress(), rpcclient.ANAdd)
require.NoError(t, err, "unable to connect mining nodes together")
err = rpctest.JoinNodes([]*rpctest.Harness{r2, r}, rpctest.Blocks)
require.NoError(t, err, "unable to synchronize mining nodes")
// Step 3: Do a set of reorgs by disconnecting the two miners, mining
// one block on the passed miner and two on the created miner,
// connecting them, and waiting for them to sync.
for i := 0; i < 5; i++ {
// Wait for disconnection
timeout := time.After(30 * time.Second)
stillConnected := true
var peers []btcjson.GetPeerInfoResult
for stillConnected {
// Allow for timeout
time.Sleep(100 * time.Millisecond)
select {
case <-timeout:
t.Fatalf("timeout waiting for miner disconnect")
default:
}
err = r2.Client.AddNode(r.P2PAddress(), rpcclient.ANRemove)
if err != nil {
t.Fatalf("unable to disconnect mining nodes: %v",
err)
}
peers, err = r2.Client.GetPeerInfo()
if err != nil {
t.Fatalf("unable to get peer info: %v", err)
}
stillConnected = false
for _, peer := range peers {
if peer.Addr == r.P2PAddress() {
stillConnected = true
break
}
}
}
_, err = r.Client.Generate(2)
if err != nil {
t.Fatalf("unable to generate blocks on passed node: %v",
err)
}
_, err = r2.Client.Generate(3)
if err != nil {
t.Fatalf("unable to generate blocks on created node: %v",
err)
}
// Step 5: Reconnect the miners and wait for them to synchronize.
err = r2.Client.AddNode(r.P2PAddress(), rpcclient.ANAdd)
if err != nil {
switch err := err.(type) {
case *btcjson.RPCError:
if err.Code != -8 {
t.Fatalf("unable to connect mining "+
"nodes together: %v", err)
}
default:
t.Fatalf("unable to connect mining nodes "+
"together: %v", err)
}
}
err = rpctest.JoinNodes([]*rpctest.Harness{r2, r},
rpctest.Blocks)
if err != nil {
t.Fatalf("unable to synchronize mining nodes: %v", err)
}
// Give wallet time to catch up.
err = waitForWalletSync(r, w)
if err != nil {
t.Fatalf("unable to sync wallet: %v", err)
}
}
// Now we check that the wallet balance stays the same.
newBalance, err = w.ConfirmedBalance(1, lnwallet.DefaultAccountName)
require.NoError(t, err, "unable to query for balance")
if origBalance != newBalance {
t.Fatalf("wallet balance incorrect, should have %v, "+
"instead have %v", origBalance, newBalance)
}
}
// testChangeOutputSpendConfirmation ensures that when we attempt to spend a
// change output created by the wallet, the wallet receives its confirmation
// once included in the chain.
func testChangeOutputSpendConfirmation(r *rpctest.Harness,
alice, bob *lnwallet.LightningWallet, t *testing.T) {
// In order to test that we see the confirmation of a transaction that
// spends an output created by SendOutputs, we'll start by emptying
// Alice's wallet so that no other UTXOs can be picked. To do so, we'll
// generate an address for Bob, who will receive all the coins.
// Assuming a balance of 80 BTC and a transaction fee of 2500 sat/kw,
// we'll craft the following transaction so that Alice doesn't have any
// UTXOs left.
aliceBalance, err := alice.ConfirmedBalance(0, lnwallet.DefaultAccountName)
require.NoError(t, err, "unable to retrieve alice's balance")
bobPkScript := newPkScript(t, bob, lnwallet.WitnessPubKey)
// We'll use a transaction fee of 14380 satoshis, which will allow us to
// sweep all of Alice's balance in one transaction containing 1 input
// and 1 output.
//
// TODO(wilmer): replace this once SendOutputs easily supports sending
// all funds in one transaction.
txFeeRate := chainfee.SatPerKWeight(2500)
const txFee = int64(14500)
output := &wire.TxOut{
Value: int64(aliceBalance) - txFee,
PkScript: bobPkScript,
}
tx := sendCoins(t, r, alice, bob, output, txFeeRate, true, 1)
txHash := tx.TxHash()
assertTxInWallet(t, alice, txHash, true)
assertTxInWallet(t, bob, txHash, true)
// With the transaction sent and confirmed, Alice's balance should now
// be 0.
aliceBalance, err = alice.ConfirmedBalance(0, lnwallet.DefaultAccountName)
require.NoError(t, err, "unable to retrieve alice's balance")
if aliceBalance != 0 {
t.Fatalf("expected alice's balance to be 0 BTC, found %v",
aliceBalance)
}
// Now, we'll send an output back to Alice from Bob of 1 BTC.
alicePkScript := newPkScript(t, alice, lnwallet.WitnessPubKey)
output = &wire.TxOut{
Value: btcutil.SatoshiPerBitcoin,
PkScript: alicePkScript,
}
tx = sendCoins(t, r, bob, alice, output, txFeeRate, true, 1)
txHash = tx.TxHash()
assertTxInWallet(t, alice, txHash, true)
assertTxInWallet(t, bob, txHash, true)
// Alice now has an available output to spend, but it was not a change
// output, which is what the test expects. Therefore, we'll generate one
// by sending Bob back some coins.
output = &wire.TxOut{
Value: btcutil.SatoshiPerBitcent,
PkScript: bobPkScript,
}
tx = sendCoins(t, r, alice, bob, output, txFeeRate, true, 1)
txHash = tx.TxHash()
assertTxInWallet(t, alice, txHash, true)
assertTxInWallet(t, bob, txHash, true)
// Then, we'll spend the change output and ensure we see its
// confirmation come in.
tx = sendCoins(t, r, alice, bob, output, txFeeRate, true, 1)
txHash = tx.TxHash()
assertTxInWallet(t, alice, txHash, true)
assertTxInWallet(t, bob, txHash, true)
// Finally, we'll replenish Alice's wallet with some more coins to
// ensure she has enough for any following test cases.
if err := loadTestCredits(r, alice, 20, 4); err != nil {
t.Fatalf("unable to replenish alice's wallet: %v", err)
}
}
// testSpendUnconfirmed ensures that when can spend unconfirmed outputs.
func testSpendUnconfirmed(miner *rpctest.Harness,
alice, bob *lnwallet.LightningWallet, t *testing.T) {
bobPkScript := newPkScript(t, bob, lnwallet.WitnessPubKey)
alicePkScript := newPkScript(t, alice, lnwallet.WitnessPubKey)
txFeeRate := chainfee.SatPerKWeight(2500)
// First we will empty out bob's wallet, sending the entire balance
// to alice.
bobBalance, err := bob.ConfirmedBalance(0, lnwallet.DefaultAccountName)
require.NoError(t, err, "unable to retrieve bob's balance")
txFee := btcutil.Amount(28760)
output := &wire.TxOut{
Value: int64(bobBalance - txFee),
PkScript: alicePkScript,
}
tx := sendCoins(t, miner, bob, alice, output, txFeeRate, true, 1)
txHash := tx.TxHash()
assertTxInWallet(t, alice, txHash, true)
assertTxInWallet(t, bob, txHash, true)
// Verify that bob doesn't have enough balance to send coins.
output = &wire.TxOut{
Value: btcutil.SatoshiPerBitcoin * 0.5,
PkScript: alicePkScript,
}
_, err = bob.SendOutputs(
nil, []*wire.TxOut{output}, txFeeRate, 0, labels.External,
bob.Cfg.CoinSelectionStrategy,
)
if err == nil {
t.Fatalf("should have not been able to pay due to insufficient balance: %v", err)
}
// Next we will send a transaction to bob but leave it in an
// unconfirmed state.
output = &wire.TxOut{
Value: btcutil.SatoshiPerBitcoin,
PkScript: bobPkScript,
}
tx = sendCoins(t, miner, alice, bob, output, txFeeRate, false, 1)
txHash = tx.TxHash()
assertTxInWallet(t, alice, txHash, false)
assertTxInWallet(t, bob, txHash, false)
// Now, try to spend some of the unconfirmed funds from bob's wallet.
output = &wire.TxOut{
Value: btcutil.SatoshiPerBitcoin * 0.5,
PkScript: alicePkScript,
}
// First, verify that we don't have enough balance to send the coins
// using confirmed outputs only.
_, err = bob.SendOutputs(
nil, []*wire.TxOut{output}, txFeeRate, 1, labels.External,
bob.Cfg.CoinSelectionStrategy,
)
if err == nil {
t.Fatalf("should have not been able to pay due to insufficient balance: %v", err)
}
// Now try the send again using unconfirmed outputs.
tx = sendCoins(t, miner, bob, alice, output, txFeeRate, false, 0)
txHash = tx.TxHash()
assertTxInWallet(t, alice, txHash, false)
assertTxInWallet(t, bob, txHash, false)
// Mine the unconfirmed transactions.
err = waitForMempoolTx(miner, &txHash)
require.NoError(t, err, "tx not relayed to miner")
if _, err := miner.Client.Generate(1); err != nil {
t.Fatalf("unable to generate block: %v", err)
}
if err := waitForWalletSync(miner, alice); err != nil {
t.Fatalf("unable to sync alice: %v", err)
}
if err := waitForWalletSync(miner, bob); err != nil {
t.Fatalf("unable to sync bob: %v", err)
}
// Finally, send the remainder of bob's wallet balance back to him so
// that these money movements dont mess up later tests.
output = &wire.TxOut{
Value: int64(bobBalance) - (btcutil.SatoshiPerBitcoin * 0.4),
PkScript: bobPkScript,
}
tx = sendCoins(t, miner, alice, bob, output, txFeeRate, true, 1)
txHash = tx.TxHash()
assertTxInWallet(t, alice, txHash, true)
assertTxInWallet(t, bob, txHash, true)
}
// testLastUnusedAddr tests that the LastUnusedAddress returns the address if
// it isn't used, and also that once the address becomes used, then it's
// properly rotated.
func testLastUnusedAddr(miner *rpctest.Harness,
alice, bob *lnwallet.LightningWallet, t *testing.T) {
if _, err := miner.Client.Generate(1); err != nil {
t.Fatalf("unable to generate block: %v", err)
}
// We'll repeat this test for each address type to ensure they're all
// rotated properly.
addrTypes := []lnwallet.AddressType{
lnwallet.WitnessPubKey, lnwallet.NestedWitnessPubKey,
}
for _, addrType := range addrTypes {
addr1, err := alice.LastUnusedAddress(
addrType, lnwallet.DefaultAccountName,
)
if err != nil {
t.Fatalf("unable to get addr: %v", err)
}
addr2, err := alice.LastUnusedAddress(
addrType, lnwallet.DefaultAccountName,
)
if err != nil {
t.Fatalf("unable to get addr: %v", err)
}
// If we generate two addresses back to back, then we should
// get the same addr, as none of them have been used yet.
if addr1.String() != addr2.String() {
t.Fatalf("addresses changed w/o use: %v vs %v", addr1, addr2)
}
// Next, we'll have Bob pay to Alice's new address. This should
// trigger address rotation at the backend wallet.
addrScript, err := txscript.PayToAddrScript(addr1)
if err != nil {
t.Fatalf("unable to convert addr to script: %v", err)
}
feeRate := chainfee.SatPerKWeight(2500)
output := &wire.TxOut{
Value: 1000000,
PkScript: addrScript,
}
sendCoins(t, miner, bob, alice, output, feeRate, true, 1)
// If we make a new address, then it should be brand new, as
// the prior address has been used.
addr3, err := alice.LastUnusedAddress(
addrType, lnwallet.DefaultAccountName,
)
if err != nil {
t.Fatalf("unable to get addr: %v", err)
}
if addr1.String() == addr3.String() {
t.Fatalf("address should have changed but didn't")
}
}
}
// testCreateSimpleTx checks that a call to CreateSimpleTx will return a
// transaction that is equal to the one that is being created by SendOutputs in
// a subsequent call.
// All test cases are doubled-up: one for testing unconfirmed inputs,
// one for testing only confirmed inputs.
func testCreateSimpleTx(r *rpctest.Harness, w *lnwallet.LightningWallet,
_ *lnwallet.LightningWallet, t *testing.T) {
// Send some money from the miner to the wallet
err := loadTestCredits(r, w, 20, 4)
require.NoError(t, err, "unable to send money to lnwallet")
// The test cases we will run through for all backends.
testCases := []struct {
outVals []int64
feeRate chainfee.SatPerKWeight
valid bool
unconfirmed bool
}{
{
outVals: []int64{},
feeRate: 2500,
valid: false, // No outputs.
unconfirmed: false,
},
{
outVals: []int64{},
feeRate: 2500,
valid: false, // No outputs.
unconfirmed: true,
},
{
outVals: []int64{200},
feeRate: 2500,
valid: false, // Dust output.
unconfirmed: false,
},
{
outVals: []int64{200},
feeRate: 2500,
valid: false, // Dust output.
unconfirmed: true,
},
{
outVals: []int64{1e8},
feeRate: 2500,
valid: true,
unconfirmed: false,
},
{
outVals: []int64{1e8},
feeRate: 2500,
valid: true,
unconfirmed: true,
},
{
outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5},
feeRate: 2500,
valid: true,
unconfirmed: false,
},
{
outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5},
feeRate: 2500,
valid: true,
unconfirmed: true,
},
{
outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5},
feeRate: 12500,
valid: true,
unconfirmed: false,
},
{
outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5},
feeRate: 12500,
valid: true,
unconfirmed: true,
},
{
outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5},
feeRate: 50000,
valid: true,
unconfirmed: false,
},
{
outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5},
feeRate: 50000,
valid: true,
unconfirmed: true,
},
{
outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5, 1e8, 2e8,
1e8, 2e7, 3e5},
feeRate: 44250,
valid: true,
unconfirmed: false,
},
{
outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5, 1e8, 2e8,
1e8, 2e7, 3e5},
feeRate: 44250,
valid: true,
unconfirmed: true,
},
}
for i, test := range testCases {
var minConfs int32 = 1
feeRate := test.feeRate
// Grab some fresh addresses from the miner that we will send
// to.
outputs := make([]*wire.TxOut, len(test.outVals))
for i, outVal := range test.outVals {
minerAddr, err := r.NewAddress()
if err != nil {
t.Fatalf("unable to generate address for "+
"miner: %v", err)
}
script, err := txscript.PayToAddrScript(minerAddr)
if err != nil {
t.Fatalf("unable to create pay to addr "+
"script: %v", err)
}
output := &wire.TxOut{
Value: outVal,
PkScript: script,
}
outputs[i] = output
}
// Now try creating a tx spending to these outputs.
createTx, createErr := w.CreateSimpleTx(
nil, outputs, feeRate, minConfs,
w.Cfg.CoinSelectionStrategy, true,
)
switch {
case test.valid && createErr != nil:
fmt.Println(spew.Sdump(createTx.Tx))
t.Fatalf("got unexpected error when creating tx: %v",
createErr)
case !test.valid && createErr == nil:
t.Fatalf("test #%v should have failed on tx "+
"creation", i)
}
// Also send to these outputs. This should result in a tx
// _very_ similar to the one we just created being sent. The
// only difference is that the dry run tx is not signed, and
// that the change output position might be different.
tx, sendErr := w.SendOutputs(
nil, outputs, feeRate, minConfs, labels.External,
w.Cfg.CoinSelectionStrategy,
)
switch {
case test.valid && sendErr != nil:
t.Fatalf("got unexpected error when sending tx: %v",
sendErr)
case !test.valid && sendErr == nil:
t.Fatalf("test #%v should fail for tx sending", i)
}
// We expected either both to not fail, or both to fail with
// the same error.
if createErr != sendErr {
t.Fatalf("error creating tx (%v) different "+
"from error sending outputs (%v)",
createErr, sendErr)
}
// If we expected the creation to fail, then this test is over.
if !test.valid {
continue
}
txid := tx.TxHash()
err = waitForMempoolTx(r, &txid)
if err != nil {
t.Fatalf("tx not relayed to miner: %v", err)
}
// Helper method to check that the two txs are similar.
assertSimilarTx := func(a, b *wire.MsgTx) error {
if a.Version != b.Version {
return fmt.Errorf("different versions: "+
"%v vs %v", a.Version, b.Version)
}
if a.LockTime != b.LockTime {
return fmt.Errorf("different locktimes: "+
"%v vs %v", a.LockTime, b.LockTime)
}
if len(a.TxIn) != len(b.TxIn) {
return fmt.Errorf("different number of "+
"inputs: %v vs %v", len(a.TxIn),
len(b.TxIn))
}
if len(a.TxOut) != len(b.TxOut) {
return fmt.Errorf("different number of "+
"outputs: %v vs %v", len(a.TxOut),
len(b.TxOut))
}
// They should be spending the same inputs.
for i := range a.TxIn {
prevA := a.TxIn[i].PreviousOutPoint
prevB := b.TxIn[i].PreviousOutPoint
if prevA != prevB {
return fmt.Errorf("different inputs: "+
"%v vs %v", spew.Sdump(prevA),
spew.Sdump(prevB))
}
}
// They should have the same outputs. Since the change
// output position gets randomized, they are not
// guaranteed to be in the same order.
for _, outA := range a.TxOut {
found := false
for _, outB := range b.TxOut {
if reflect.DeepEqual(outA, outB) {
found = true
break
}
}
if !found {
return fmt.Errorf("did not find "+
"output %v", spew.Sdump(outA))
}
}
return nil
}
// Assert that our "template tx" was similar to the one that
// ended up being sent.
if err := assertSimilarTx(createTx.Tx, tx); err != nil {
t.Fatalf("transactions not similar: %v", err)
}
// Now that we know both transactions were essentially
// identical, we'll make sure that a P2TR addr was used as the
// change output, which is the current default.
changeTxOut := createTx.Tx.TxOut[createTx.ChangeIndex]
changeScriptType, _, _, err := txscript.ExtractPkScriptAddrs(
changeTxOut.PkScript, &w.Cfg.NetParams,
)
require.NoError(t, err)
require.Equal(t, changeScriptType, txscript.WitnessV1TaprootTy)
}
}
// testSignOutputCreateAccount tests that we're able to properly sign for an
// output if the target account hasn't yet been created on disk. In this case,
// we'll create the account, then sign.
func testSignOutputCreateAccount(r *rpctest.Harness, w *lnwallet.LightningWallet,
_ *lnwallet.LightningWallet, t *testing.T) {
// First, we'll create a sign desc that references a non-default key
// family. Under the hood, key families are actually accounts, so this
// should force create of the account so we can sign with it.
fakeTx := wire.NewMsgTx(2)
fakeTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: wire.OutPoint{
Hash: chainhash.Hash{},
Index: 0,
},
})
signDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: 99,
Index: 1,
},
},
WitnessScript: []byte{},
Output: &wire.TxOut{
Value: 1000,
},
HashType: txscript.SigHashAll,
SigHashes: input.NewTxSigHashesV0Only(fakeTx),
InputIndex: 0,
}
// We'll now sign and expect this to succeed, as even though the
// account doesn't exist atm, it should be created in order to process
// the inbound signing request.
_, err := w.Cfg.Signer.SignOutputRaw(fakeTx, signDesc)
if err != nil {
t.Fatalf("unable to sign for output with non-existent "+
"account: %v", err)
}
}
type walletTestCase struct {
name string
test func(miner *rpctest.Harness, alice, bob *lnwallet.LightningWallet,
test *testing.T)
}
var walletTests = []walletTestCase{
{
// TODO(wilmer): this test should remain first until the wallet
// can properly craft a transaction that spends all of its
// on-chain funds.
name: "change output spend confirmation",
test: testChangeOutputSpendConfirmation,
},
{
// TODO(guggero): this test should remain second until dual
// funding can properly exchange full UTXO information and we
// can use P2TR change outputs as the funding inputs for a dual
// funded channel.
name: "dual funder workflow",
test: testDualFundingReservationWorkflow,
},
{
name: "spend unconfirmed outputs",
test: testSpendUnconfirmed,
},
{
name: "insane fee reject",
test: testReservationInitiatorBalanceBelowDustCancel,
},
{
name: "single funding workflow",
test: func(miner *rpctest.Harness, alice,
bob *lnwallet.LightningWallet, t *testing.T) {
testSingleFunderReservationWorkflow(
miner, alice, bob, t,
lnwallet.CommitmentTypeLegacy, nil,
nil, [32]byte{1}, 0,
)
},
},
{
name: "single funding workflow tweakless",
test: func(miner *rpctest.Harness, alice,
bob *lnwallet.LightningWallet, t *testing.T) {
testSingleFunderReservationWorkflow(
miner, alice, bob, t,
lnwallet.CommitmentTypeTweakless, nil,
nil, [32]byte{1}, 0,
)
},
},
{
name: "single funding workflow musig2",
test: func(miner *rpctest.Harness, alice,
bob *lnwallet.LightningWallet, t *testing.T) {
testSingleFunderReservationWorkflow(
miner, alice, bob, t,
lnwallet.CommitmentTypeSimpleTaproot, nil,
nil, [32]byte{1}, 0,
)
},
},
// TODO(roasbeef): add musig2 external funding
{
name: "single funding workflow external funding tx",
test: testSingleFunderExternalFundingTx,
},
{
name: "output locking",
test: testFundingTransactionLockedOutputs,
},
{
name: "reservation insufficient funds",
test: testFundingCancellationNotEnoughFunds,
},
{
name: "transaction subscriptions",
test: testTransactionSubscriptions,
},
{
name: "transaction details",
test: testListTransactionDetails,
},
{
name: "transaction details offset",
test: testListTransactionDetailsOffset,
},
{
name: "get transaction details",
test: testGetTransactionDetails,
},
{
name: "publish transaction",
test: testPublishTransaction,
},
{
name: "signed with tweaked pubkeys",
test: testSignOutputUsingTweaks,
},
{
name: "test cancel non-existent reservation",
test: testCancelNonExistentReservation,
},
{
name: "last unused addr",
test: testLastUnusedAddr,
},
{
name: "reorg wallet balance",
test: testReorgWalletBalance,
},
{
name: "create simple tx",
test: testCreateSimpleTx,
},
{
name: "test sign create account",
test: testSignOutputCreateAccount,
},
{
name: "test get recovery info",
test: testGetRecoveryInfo,
},
}
func clearWalletStates(a, b *lnwallet.LightningWallet) error {
a.ResetReservations()
b.ResetReservations()
if err := a.Cfg.Database.GetParentDB().Wipe(); err != nil {
return err
}
return b.Cfg.Database.GetParentDB().Wipe()
}
func waitForMempoolTx(r *rpctest.Harness, txid *chainhash.Hash) error {
var found bool
var tx *btcutil.Tx
var err error
timeout := time.After(30 * time.Second)
for !found {
// Do a short wait
select {
case <-timeout:
return fmt.Errorf("timeout after 10s")
default:
}
time.Sleep(100 * time.Millisecond)
// Check for the harness' knowledge of the txid
tx, err = r.Client.GetRawTransaction(txid)
if err != nil {
switch e := err.(type) {
case *btcjson.RPCError:
if e.Code == btcjson.ErrRPCNoTxInfo {
continue
}
default:
}
return err
}
if tx != nil && tx.MsgTx().TxHash() == *txid {
found = true
}
}
return nil
}
func waitForWalletSync(r *rpctest.Harness, w *lnwallet.LightningWallet) error {
var (
synced bool
err error
bestHash, knownHash *chainhash.Hash
bestHeight, knownHeight int32
)
timeout := time.After(10 * time.Second)
for !synced {
// Do a short wait
select {
case <-timeout:
return fmt.Errorf("timeout after 30s")
case <-time.Tick(100 * time.Millisecond):
}
// Check whether the chain source of the wallet is caught up to
// the harness it's supposed to be catching up to.
bestHash, bestHeight, err = r.Client.GetBestBlock()
if err != nil {
return err
}
knownHash, knownHeight, err = w.Cfg.ChainIO.GetBestBlock()
if err != nil {
return err
}
if knownHeight != bestHeight {
continue
}
if *knownHash != *bestHash {
return fmt.Errorf("hash at height %d doesn't match: "+
"expected %s, got %s", bestHeight, bestHash,
knownHash)
}
// Check for synchronization.
synced, _, err = w.IsSynced()
if err != nil {
return err
}
}
return nil
}
// testSingleFunderExternalFundingTx tests that the wallet is able to properly
// carry out a funding flow backed by a channel point that has been crafted
// outside the wallet.
func testSingleFunderExternalFundingTx(miner *rpctest.Harness,
alice, bob *lnwallet.LightningWallet, t *testing.T) {
// Define a filter function without any restrictions.
allowUtxo := func(lnwallet.Utxo) bool {
return true
}
// First, we'll obtain multi-sig keys from both Alice and Bob which
// simulates them exchanging keys on a higher level.
aliceFundingKey, err := alice.DeriveNextKey(keychain.KeyFamilyMultiSig)
require.NoError(t, err, "unable to obtain alice funding key")
bobFundingKey, err := bob.DeriveNextKey(keychain.KeyFamilyMultiSig)
require.NoError(t, err, "unable to obtain bob funding key")
// We'll now set up for them to open a 4 BTC channel, with 1 BTC pushed
// to Bob's side.
chanAmt := 4 * btcutil.SatoshiPerBitcoin
// Simulating external funding negotiation, we'll now create the
// funding transaction for both parties. Utilizing existing tools,
// we'll create a new chanfunding.Assembler hacked by Alice's wallet.
aliceChanFunder := chanfunding.NewWalletAssembler(
chanfunding.WalletConfig{
CoinSource: lnwallet.NewCoinSource(
alice, allowUtxo,
),
CoinSelectLocker: alice,
CoinLeaser: alice,
Signer: alice.Cfg.Signer,
DustLimit: 600,
CoinSelectionStrategy: wallet.CoinSelectionLargest,
},
)
// With the chan funder created, we'll now provision a funding intent,
// bind the keys we obtained above, and finally obtain our funding
// transaction and outpoint.
fundingIntent, err := aliceChanFunder.ProvisionChannel(
&chanfunding.Request{
LocalAmt: btcutil.Amount(chanAmt),
MinConfs: 1,
FeeRate: 253,
ChangeAddr: func() (btcutil.Address, error) {
return alice.NewAddress(
lnwallet.WitnessPubKey, true,
lnwallet.DefaultAccountName,
)
},
},
)
require.NoError(t, err, "unable to perform coin selection")
// With our intent created, we'll instruct it to finalize the funding
// transaction, and also hand us the outpoint so we can simulate
// external crafting of the funding transaction.
var (
fundingTx *wire.MsgTx
chanPoint *wire.OutPoint
)
if fullIntent, ok := fundingIntent.(*chanfunding.FullIntent); ok {
fullIntent.BindKeys(&aliceFundingKey, bobFundingKey.PubKey)
fundingTx, err = fullIntent.CompileFundingTx(nil, nil)
if err != nil {
t.Fatalf("unable to compile funding tx: %v", err)
}
chanPoint, err = fullIntent.ChanPoint()
if err != nil {
t.Fatalf("unable to obtain chan point: %v", err)
}
} else {
t.Fatalf("expected full intent, instead got: %T", fullIntent)
}
// Now that we have the fully constructed funding transaction, we'll
// create a new shim external funder out of it for Alice, and prep a
// shim intent for Bob.
thawHeight := uint32(200)
aliceExternalFunder := chanfunding.NewCannedAssembler(
thawHeight, *chanPoint, btcutil.Amount(chanAmt), &aliceFundingKey,
bobFundingKey.PubKey, true, false,
)
bobShimIntent, err := chanfunding.NewCannedAssembler(
thawHeight, *chanPoint, btcutil.Amount(chanAmt), &bobFundingKey,
aliceFundingKey.PubKey, false, false,
).ProvisionChannel(&chanfunding.Request{
LocalAmt: btcutil.Amount(chanAmt),
MinConfs: 1,
FeeRate: 253,
ChangeAddr: func() (btcutil.Address, error) {
return bob.NewAddress(
lnwallet.WitnessPubKey, true,
lnwallet.DefaultAccountName,
)
},
})
require.NoError(t, err, "unable to create shim intent for bob")
// At this point, we have everything we need to carry out our test, so
// we'll being the funding flow between Alice and Bob.
//
// However, before we do so, we'll register a new shim intent for Bob,
// so he knows what keys to use when he receives the funding request
// from Alice.
pendingChanID := testHdSeed
err = bob.RegisterFundingIntent(pendingChanID, bobShimIntent)
require.NoError(t, err, "unable to register intent")
// Now we can carry out the single funding flow as normal, we'll
// specify our external funder and funding transaction, as well as the
// pending channel ID generated above to allow Alice and Bob to track
// the funding flow externally.
testSingleFunderReservationWorkflow(
miner, alice, bob, t, lnwallet.CommitmentTypeTweakless,
aliceExternalFunder, func() *wire.MsgTx {
return fundingTx
}, pendingChanID, thawHeight,
)
}
// TestLightningWallet tests all registered interfaces with a unified set of
// tests which exercise each of the required methods found within the
// WalletController interface.
//
// NOTE: In the future, when additional implementations of the WalletController
// interface have been implemented, in order to ensure the new concrete
// implementation is automatically tested, two steps must be undertaken. First,
// one needs add a "non-captured" (_) import from the new sub-package. This
// import should trigger an init() method within the package which registers
// the interface. Second, an additional case in the switch within the main loop
// below needs to be added which properly initializes the interface.
//
// TODO(roasbeef): purge bobNode in favor of dual lnwallet's
func TestLightningWallet(t *testing.T, targetBackEnd string) {
t.Parallel()
// Initialize the harness around a btcd node which will serve as our
// dedicated miner to generate blocks, cause re-orgs, etc. We'll set
// up this node with a chain length of 125, so we have plenty of BTC
// to play around with.
miningNode := unittest.NewMiner(
t, netParams, []string{"--txindex"}, true, 25,
)
// Next mine enough blocks in order for segwit and the CSV package
// soft-fork to activate on RegNet.
numBlocks := netParams.MinerConfirmationWindow * 2
if _, err := miningNode.Client.Generate(numBlocks); err != nil {
t.Fatalf("unable to generate blocks: %v", err)
}
rpcConfig := miningNode.RPCConfig()
db := channeldb.OpenForTesting(t, t.TempDir())
testCfg := channeldb.CacheConfig{
QueryDisable: false,
}
hintCache, err := channeldb.NewHeightHintCache(testCfg, db.Backend)
require.NoError(t, err, "unable to create height hint cache")
blockCache := blockcache.NewBlockCache(10000)
chainNotifier, err := btcdnotify.New(
&rpcConfig, netParams, hintCache, hintCache, blockCache,
)
require.NoError(t, err, "unable to create notifier")
if err := chainNotifier.Start(); err != nil {
t.Fatalf("unable to start notifier: %v", err)
}
for _, walletDriver := range lnwallet.RegisteredWallets() {
for _, backEnd := range walletDriver.BackEnds() {
if backEnd != targetBackEnd {
continue
}
if !runTests(t, walletDriver, backEnd, miningNode,
rpcConfig, chainNotifier) {
return
}
}
}
}
// runTests runs all of the tests for a single interface implementation and
// chain back-end combination. This makes it easier to use `defer` as well as
// factoring out the test logic from the loop which cycles through the
// interface implementations.
func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver,
backEnd string, miningNode *rpctest.Harness,
rpcConfig rpcclient.ConnConfig,
chainNotifier chainntnfs.ChainNotifier) bool {
var (
bio lnwallet.BlockChainIO
aliceSigner input.Signer
bobSigner input.Signer
aliceKeyRing keychain.SecretKeyRing
bobKeyRing keychain.SecretKeyRing
aliceWalletController lnwallet.WalletController
bobWalletController lnwallet.WalletController
err error
)
tempTestDirAlice := t.TempDir()
tempTestDirBob := t.TempDir()
blockCache := blockcache.NewBlockCache(10000)
walletType := walletDriver.WalletType
switch walletType {
case "btcwallet":
var aliceClient, bobClient chain.Interface
switch backEnd {
case "btcd":
aliceClient, err = chain.NewRPCClient(
netParams, rpcConfig.Host, rpcConfig.User,
rpcConfig.Pass, rpcConfig.Certificates, false,
20,
)
if err != nil {
t.Fatalf("unable to make chain rpc: %v", err)
}
bobClient, err = chain.NewRPCClient(
netParams, rpcConfig.Host, rpcConfig.User,
rpcConfig.Pass, rpcConfig.Certificates, false,
20,
)
if err != nil {
t.Fatalf("unable to make chain rpc: %v", err)
}
case "neutrino":
// Set some package-level variable to speed up
// operation for tests.
neutrino.BanDuration = time.Millisecond * 100
neutrino.QueryTimeout = time.Millisecond * 500
neutrino.QueryNumRetries = 1
// Start Alice - open a database, start a neutrino
// instance, and initialize a btcwallet driver for it.
aliceDB, err := walletdb.Create(
kvdb.BoltBackendName,
filepath.Join(tempTestDirAlice, "neutrino.db"),
true, kvdb.DefaultDBTimeout, false,
)
if err != nil {
t.Fatalf("unable to create DB: %v", err)
}
defer aliceDB.Close()
aliceChain, err := neutrino.NewChainService(
neutrino.Config{
DataDir: tempTestDirAlice,
Database: aliceDB,
ChainParams: *netParams,
ConnectPeers: []string{
miningNode.P2PAddress(),
},
},
)
if err != nil {
t.Fatalf("unable to make neutrino: %v", err)
}
aliceChain.Start()
defer aliceChain.Stop()
aliceClient = chain.NewNeutrinoClient(
netParams, aliceChain,
)
// Start Bob - open a database, start a neutrino
// instance, and initialize a btcwallet driver for it.
bobDB, err := walletdb.Create(
kvdb.BoltBackendName,
filepath.Join(tempTestDirBob, "neutrino.db"),
true, kvdb.DefaultDBTimeout, false,
)
if err != nil {
t.Fatalf("unable to create DB: %v", err)
}
defer bobDB.Close()
bobChain, err := neutrino.NewChainService(
neutrino.Config{
DataDir: tempTestDirBob,
Database: bobDB,
ChainParams: *netParams,
ConnectPeers: []string{
miningNode.P2PAddress(),
},
},
)
if err != nil {
t.Fatalf("unable to make neutrino: %v", err)
}
bobChain.Start()
defer bobChain.Stop()
bobClient = chain.NewNeutrinoClient(
netParams, bobChain,
)
case "bitcoind":
// Start a bitcoind instance.
chainConn := unittest.NewBitcoindBackend(
t, unittest.NetParams, miningNode, true, false,
)
// Create a btcwallet bitcoind client for both Alice and
// Bob.
aliceClient = chainConn.NewBitcoindClient()
bobClient = chainConn.NewBitcoindClient()
case "bitcoind-rpc-polling":
// Start a bitcoind instance.
chainConn := unittest.NewBitcoindBackend(
t, unittest.NetParams, miningNode, true, true,
)
// Create a btcwallet bitcoind client for both Alice and
// Bob.
aliceClient = chainConn.NewBitcoindClient()
bobClient = chainConn.NewBitcoindClient()
default:
t.Fatalf("unknown chain driver: %v", backEnd)
}
aliceSeed := sha256.New()
aliceSeed.Write([]byte(backEnd))
aliceSeed.Write(aliceHDSeed[:])
aliceSeedBytes := aliceSeed.Sum(nil)
aliceWalletConfig := &btcwallet.Config{
PrivatePass: []byte("alice-pass"),
HdSeed: aliceSeedBytes,
NetParams: netParams,
ChainSource: aliceClient,
CoinType: keychain.CoinTypeTestnet,
// wallet starts in recovery mode
RecoveryWindow: 2,
LoaderOptions: []btcwallet.LoaderOption{
btcwallet.LoaderWithLocalWalletDB(
tempTestDirAlice, false, time.Minute,
),
},
}
aliceWalletController, err = walletDriver.New(
aliceWalletConfig, blockCache,
)
if err != nil {
t.Fatalf("unable to create btcwallet: %v", err)
}
aliceSigner = aliceWalletController.(*btcwallet.BtcWallet)
aliceKeyRing = keychain.NewBtcWalletKeyRing(
aliceWalletController.(*btcwallet.BtcWallet).InternalWallet(),
keychain.CoinTypeTestnet,
)
bobSeed := sha256.New()
bobSeed.Write([]byte(backEnd))
bobSeed.Write(bobHDSeed[:])
bobSeedBytes := bobSeed.Sum(nil)
bobWalletConfig := &btcwallet.Config{
PrivatePass: []byte("bob-pass"),
HdSeed: bobSeedBytes,
NetParams: netParams,
ChainSource: bobClient,
CoinType: keychain.CoinTypeTestnet,
// wallet starts without recovery mode
RecoveryWindow: 0,
LoaderOptions: []btcwallet.LoaderOption{
btcwallet.LoaderWithLocalWalletDB(
tempTestDirBob, false, time.Minute,
),
},
}
bobWalletController, err = walletDriver.New(
bobWalletConfig, blockCache,
)
if err != nil {
t.Fatalf("unable to create btcwallet: %v", err)
}
bobSigner = bobWalletController.(*btcwallet.BtcWallet)
bobKeyRing = keychain.NewBtcWalletKeyRing(
bobWalletController.(*btcwallet.BtcWallet).InternalWallet(),
keychain.CoinTypeTestnet,
)
bio = bobWalletController.(*btcwallet.BtcWallet)
default:
t.Fatalf("unknown wallet driver: %v", walletType)
}
// Funding via 20 outputs with 4BTC each.
alice := createTestWallet(
t, tempTestDirAlice, miningNode, netParams,
chainNotifier, aliceWalletController, aliceKeyRing,
aliceSigner, bio,
)
bob := createTestWallet(
t, tempTestDirBob, miningNode, netParams,
chainNotifier, bobWalletController, bobKeyRing, bobSigner, bio,
)
// Both wallets should now have 80BTC available for
// spending.
assertProperBalance(t, alice, 1, 80)
assertProperBalance(t, bob, 1, 80)
// Execute every test, clearing possibly mutated
// wallet state after each step.
for _, walletTest := range walletTests {
walletTest := walletTest
testName := fmt.Sprintf("%v/%v:%v", walletType, backEnd,
walletTest.name)
success := t.Run(testName, func(t *testing.T) {
if backEnd == "neutrino" &&
strings.Contains(walletTest.name, "dual funder") {
t.Skip("skipping dual funder tests for neutrino")
}
if backEnd == "neutrino" &&
strings.Contains(walletTest.name, "spend unconfirmed") {
t.Skip("skipping spend unconfirmed tests for neutrino")
}
walletTest.test(miningNode, alice, bob, t)
})
if !success {
return false
}
// TODO(roasbeef): possible reset mining
// node's chainstate to initial level, cleanly
// wipe buckets
if err := clearWalletStates(alice, bob); err !=
nil && err != kvdb.ErrBucketNotFound {
t.Fatalf("unable to wipe wallet state: %v", err)
}
}
return true
}
package lnwallet
import (
"bytes"
"context"
"crypto/rand"
"encoding/binary"
"encoding/hex"
"io"
prand "math/rand"
"net"
"testing"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/shachain"
"github.com/lightningnetwork/lnd/tlv"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
var (
// For simplicity a single priv key controls all of our test outputs.
testWalletPrivKey = []byte{
0x2b, 0xd8, 0x06, 0xc9, 0x7f, 0x0e, 0x00, 0xaf,
0x1a, 0x1f, 0xc3, 0x32, 0x8f, 0xa7, 0x63, 0xa9,
0x26, 0x97, 0x23, 0xc8, 0xdb, 0x8f, 0xac, 0x4f,
0x93, 0xaf, 0x71, 0xdb, 0x18, 0x6d, 0x6e, 0x90,
}
// We're alice :)
bobsPrivKey = []byte{
0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda,
0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17,
0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d,
0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9,
}
// Use a hard-coded HD seed.
testHdSeed = chainhash.Hash{
0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9,
0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
}
// A serializable txn for testing funding txn.
testTx = &wire.MsgTx{
Version: 1,
TxIn: []*wire.TxIn{
{
PreviousOutPoint: wire.OutPoint{
Hash: chainhash.Hash{},
Index: 0xffffffff,
},
SignatureScript: []byte{0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62},
Sequence: 0xffffffff,
},
},
TxOut: []*wire.TxOut{
{
Value: 5000000000,
PkScript: []byte{
0x41, // OP_DATA_65
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
0xa6, // 65-byte signature
0xac, // OP_CHECKSIG
},
},
},
LockTime: 5,
}
// A valid, DER-encoded signature (taken from btcec unit tests).
testSigBytes = []byte{
0x30, 0x44, 0x02, 0x20, 0x4e, 0x45, 0xe1, 0x69,
0x32, 0xb8, 0xaf, 0x51, 0x49, 0x61, 0xa1, 0xd3,
0xa1, 0xa2, 0x5f, 0xdf, 0x3f, 0x4f, 0x77, 0x32,
0xe9, 0xd6, 0x24, 0xc6, 0xc6, 0x15, 0x48, 0xab,
0x5f, 0xb8, 0xcd, 0x41, 0x02, 0x20, 0x18, 0x15,
0x22, 0xec, 0x8e, 0xca, 0x07, 0xde, 0x48, 0x60,
0xa4, 0xac, 0xdd, 0x12, 0x90, 0x9d, 0x83, 0x1c,
0xc5, 0x6c, 0xbb, 0xac, 0x46, 0x22, 0x08, 0x22,
0x21, 0xa8, 0x76, 0x8d, 0x1d, 0x09,
}
aliceDustLimit = btcutil.Amount(200)
bobDustLimit = btcutil.Amount(1300)
testChannelCapacity float64 = 10
// ctxb is a context that will never be cancelled, that is used in
// place of a real quit context.
ctxb = context.Background()
)
// CreateTestChannels creates to fully populated channels to be used within
// testing fixtures. The channels will be returned as if the funding process
// has just completed. The channel itself is funded with 10 BTC, with 5 BTC
// allocated to each side. Within the channel, Alice is the initiator. If
// tweaklessCommits is true, then the commits within the channels will use the
// new format, otherwise the legacy format.
func CreateTestChannels(t *testing.T, chanType channeldb.ChannelType,
dbModifiers ...channeldb.OptionModifier) (*LightningChannel,
*LightningChannel, error) {
channelCapacity, err := btcutil.NewAmount(testChannelCapacity)
if err != nil {
return nil, nil, err
}
channelBal := channelCapacity / 2
csvTimeoutAlice := uint32(5)
csvTimeoutBob := uint32(4)
isAliceInitiator := true
prevOut := &wire.OutPoint{
Hash: chainhash.Hash(testHdSeed),
Index: prand.Uint32(),
}
fundingTxIn := wire.NewTxIn(prevOut, nil, nil)
// For each party, we'll create a distinct set of keys in order to
// emulate the typical set up with live channels.
var (
aliceKeys []*btcec.PrivateKey
bobKeys []*btcec.PrivateKey
)
for i := 0; i < 5; i++ {
key := make([]byte, len(testWalletPrivKey))
copy(key[:], testWalletPrivKey[:])
key[0] ^= byte(i + 1)
aliceKey, _ := btcec.PrivKeyFromBytes(key)
aliceKeys = append(aliceKeys, aliceKey)
key = make([]byte, len(bobsPrivKey))
copy(key[:], bobsPrivKey)
key[0] ^= byte(i + 1)
bobKey, _ := btcec.PrivKeyFromBytes(key)
bobKeys = append(bobKeys, bobKey)
}
aliceCfg := channeldb.ChannelConfig{
ChannelStateBounds: channeldb.ChannelStateBounds{
MaxPendingAmount: lnwire.NewMSatFromSatoshis(channelCapacity),
ChanReserve: channelCapacity / 100,
MinHTLC: 0,
MaxAcceptedHtlcs: input.MaxHTLCNumber / 2,
},
CommitmentParams: channeldb.CommitmentParams{
DustLimit: aliceDustLimit,
CsvDelay: uint16(csvTimeoutAlice),
},
MultiSigKey: keychain.KeyDescriptor{
PubKey: aliceKeys[0].PubKey(),
},
RevocationBasePoint: keychain.KeyDescriptor{
PubKey: aliceKeys[1].PubKey(),
},
PaymentBasePoint: keychain.KeyDescriptor{
PubKey: aliceKeys[2].PubKey(),
},
DelayBasePoint: keychain.KeyDescriptor{
PubKey: aliceKeys[3].PubKey(),
},
HtlcBasePoint: keychain.KeyDescriptor{
PubKey: aliceKeys[4].PubKey(),
},
}
bobCfg := channeldb.ChannelConfig{
ChannelStateBounds: channeldb.ChannelStateBounds{
MaxPendingAmount: lnwire.NewMSatFromSatoshis(channelCapacity),
ChanReserve: channelCapacity / 100,
MinHTLC: 0,
MaxAcceptedHtlcs: input.MaxHTLCNumber / 2,
},
CommitmentParams: channeldb.CommitmentParams{
DustLimit: bobDustLimit,
CsvDelay: uint16(csvTimeoutBob),
},
MultiSigKey: keychain.KeyDescriptor{
PubKey: bobKeys[0].PubKey(),
},
RevocationBasePoint: keychain.KeyDescriptor{
PubKey: bobKeys[1].PubKey(),
},
PaymentBasePoint: keychain.KeyDescriptor{
PubKey: bobKeys[2].PubKey(),
},
DelayBasePoint: keychain.KeyDescriptor{
PubKey: bobKeys[3].PubKey(),
},
HtlcBasePoint: keychain.KeyDescriptor{
PubKey: bobKeys[4].PubKey(),
},
}
bobRoot, err := chainhash.NewHash(bobKeys[0].Serialize())
if err != nil {
return nil, nil, err
}
bobPreimageProducer := shachain.NewRevocationProducer(*bobRoot)
bobFirstRevoke, err := bobPreimageProducer.AtIndex(0)
if err != nil {
return nil, nil, err
}
bobCommitPoint := input.ComputeCommitmentPoint(bobFirstRevoke[:])
aliceRoot, err := chainhash.NewHash(aliceKeys[0].Serialize())
if err != nil {
return nil, nil, err
}
alicePreimageProducer := shachain.NewRevocationProducer(*aliceRoot)
aliceFirstRevoke, err := alicePreimageProducer.AtIndex(0)
if err != nil {
return nil, nil, err
}
aliceCommitPoint := input.ComputeCommitmentPoint(aliceFirstRevoke[:])
aliceCommitTx, bobCommitTx, err := CreateCommitmentTxns(
channelBal, channelBal, &aliceCfg, &bobCfg, aliceCommitPoint,
bobCommitPoint, *fundingTxIn, chanType, isAliceInitiator, 0,
)
if err != nil {
return nil, nil, err
}
dbAlice := channeldb.OpenForTesting(t, t.TempDir(), dbModifiers...)
dbBob := channeldb.OpenForTesting(t, t.TempDir(), dbModifiers...)
estimator := chainfee.NewStaticEstimator(6000, 0)
feePerKw, err := estimator.EstimateFeePerKW(1)
if err != nil {
return nil, nil, err
}
commitFee := calcStaticFee(chanType, 0)
var anchorAmt btcutil.Amount
if chanType.HasAnchors() {
anchorAmt += 2 * AnchorSize
}
aliceBalance := lnwire.NewMSatFromSatoshis(
channelBal - commitFee - anchorAmt,
)
bobBalance := lnwire.NewMSatFromSatoshis(channelBal)
aliceLocalCommit := channeldb.ChannelCommitment{
CommitHeight: 0,
LocalBalance: aliceBalance,
RemoteBalance: bobBalance,
CommitFee: commitFee,
FeePerKw: btcutil.Amount(feePerKw),
CommitTx: aliceCommitTx,
CommitSig: testSigBytes,
}
aliceRemoteCommit := channeldb.ChannelCommitment{
CommitHeight: 0,
LocalBalance: aliceBalance,
RemoteBalance: bobBalance,
CommitFee: commitFee,
FeePerKw: btcutil.Amount(feePerKw),
CommitTx: bobCommitTx,
CommitSig: testSigBytes,
}
bobLocalCommit := channeldb.ChannelCommitment{
CommitHeight: 0,
LocalBalance: bobBalance,
RemoteBalance: aliceBalance,
CommitFee: commitFee,
FeePerKw: btcutil.Amount(feePerKw),
CommitTx: bobCommitTx,
CommitSig: testSigBytes,
}
bobRemoteCommit := channeldb.ChannelCommitment{
CommitHeight: 0,
LocalBalance: bobBalance,
RemoteBalance: aliceBalance,
CommitFee: commitFee,
FeePerKw: btcutil.Amount(feePerKw),
CommitTx: aliceCommitTx,
CommitSig: testSigBytes,
}
var chanIDBytes [8]byte
if _, err := io.ReadFull(rand.Reader, chanIDBytes[:]); err != nil {
return nil, nil, err
}
shortChanID := lnwire.NewShortChanIDFromInt(
binary.BigEndian.Uint64(chanIDBytes[:]),
)
aliceChannelState := &channeldb.OpenChannel{
LocalChanCfg: aliceCfg,
RemoteChanCfg: bobCfg,
IdentityPub: aliceKeys[0].PubKey(),
FundingOutpoint: *prevOut,
ShortChannelID: shortChanID,
ChanType: chanType,
IsInitiator: isAliceInitiator,
Capacity: channelCapacity,
RemoteCurrentRevocation: bobCommitPoint,
RevocationProducer: alicePreimageProducer,
RevocationStore: shachain.NewRevocationStore(),
LocalCommitment: aliceLocalCommit,
RemoteCommitment: aliceRemoteCommit,
Db: dbAlice.ChannelStateDB(),
Packager: channeldb.NewChannelPackager(shortChanID),
FundingTxn: testTx,
}
bobChannelState := &channeldb.OpenChannel{
LocalChanCfg: bobCfg,
RemoteChanCfg: aliceCfg,
IdentityPub: bobKeys[0].PubKey(),
FundingOutpoint: *prevOut,
ShortChannelID: shortChanID,
ChanType: chanType,
IsInitiator: !isAliceInitiator,
Capacity: channelCapacity,
RemoteCurrentRevocation: aliceCommitPoint,
RevocationProducer: bobPreimageProducer,
RevocationStore: shachain.NewRevocationStore(),
LocalCommitment: bobLocalCommit,
RemoteCommitment: bobRemoteCommit,
Db: dbBob.ChannelStateDB(),
Packager: channeldb.NewChannelPackager(shortChanID),
}
// If the channel type has a tapscript root, then we'll also specify
// one here to apply to both the channels.
if chanType.HasTapscriptRoot() {
var tapscriptRoot chainhash.Hash
_, err := io.ReadFull(rand.Reader, tapscriptRoot[:])
if err != nil {
return nil, nil, err
}
someRoot := fn.Some(tapscriptRoot)
aliceChannelState.TapscriptRoot = someRoot
bobChannelState.TapscriptRoot = someRoot
}
aliceSigner := input.NewMockSigner(aliceKeys, nil)
bobSigner := input.NewMockSigner(bobKeys, nil)
// TODO(roasbeef): make mock version of pre-image store
auxSigner := NewDefaultAuxSignerMock(t)
alicePool := NewSigPool(1, aliceSigner)
channelAlice, err := NewLightningChannel(
aliceSigner, aliceChannelState, alicePool,
WithLeafStore(&MockAuxLeafStore{}),
WithAuxSigner(auxSigner),
)
if err != nil {
return nil, nil, err
}
alicePool.Start()
t.Cleanup(func() {
require.NoError(t, alicePool.Stop())
})
obfuscator := createStateHintObfuscator(aliceChannelState)
bobPool := NewSigPool(1, bobSigner)
channelBob, err := NewLightningChannel(
bobSigner, bobChannelState, bobPool,
WithLeafStore(&MockAuxLeafStore{}),
WithAuxSigner(auxSigner),
)
if err != nil {
return nil, nil, err
}
bobPool.Start()
t.Cleanup(func() {
require.NoError(t, bobPool.Stop())
})
err = SetStateNumHint(
aliceCommitTx, 0, obfuscator,
)
if err != nil {
return nil, nil, err
}
err = SetStateNumHint(
bobCommitTx, 0, obfuscator,
)
if err != nil {
return nil, nil, err
}
addr := &net.TCPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 18556,
}
if err := channelAlice.channelState.SyncPending(addr, 101); err != nil {
return nil, nil, err
}
addr = &net.TCPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 18555,
}
if err := channelBob.channelState.SyncPending(addr, 101); err != nil {
return nil, nil, err
}
// Now that the channel are open, simulate the start of a session by
// having Alice and Bob extend their revocation windows to each other.
err = initRevocationWindows(channelAlice, channelBob)
if err != nil {
return nil, nil, err
}
return channelAlice, channelBob, nil
}
// initMusigNonce is used to manually setup musig2 nonces for a new channel,
// outside the normal chan-reest flow.
func initMusigNonce(chanA, chanB *LightningChannel) error {
chanANonces, err := chanA.GenMusigNonces()
if err != nil {
return err
}
chanBNonces, err := chanB.GenMusigNonces()
if err != nil {
return err
}
if err := chanA.InitRemoteMusigNonces(chanBNonces); err != nil {
return err
}
if err := chanB.InitRemoteMusigNonces(chanANonces); err != nil {
return err
}
return nil
}
// initRevocationWindows simulates a new channel being opened within the p2p
// network by populating the initial revocation windows of the passed
// commitment state machines.
func initRevocationWindows(chanA, chanB *LightningChannel) error {
// If these are taproot chanenls, then we need to also simulate sending
// either FundingLocked or ChannelReestablish by calling
// InitRemoteMusigNonces for both sides.
if chanA.channelState.ChanType.IsTaproot() {
if err := initMusigNonce(chanA, chanB); err != nil {
return err
}
}
aliceNextRevoke, err := chanA.NextRevocationKey()
if err != nil {
return err
}
if err := chanB.InitNextRevocation(aliceNextRevoke); err != nil {
return err
}
bobNextRevoke, err := chanB.NextRevocationKey()
if err != nil {
return err
}
if err := chanA.InitNextRevocation(bobNextRevoke); err != nil {
return err
}
return nil
}
// pubkeyFromHex parses a Bitcoin public key from a hex encoded string.
func pubkeyFromHex(keyHex string) (*btcec.PublicKey, error) {
bytes, err := hex.DecodeString(keyHex)
if err != nil {
return nil, err
}
return btcec.ParsePubKey(bytes)
}
// privkeyFromHex parses a Bitcoin private key from a hex encoded string.
func privkeyFromHex(keyHex string) (*btcec.PrivateKey, error) {
bytes, err := hex.DecodeString(keyHex)
if err != nil {
return nil, err
}
key, _ := btcec.PrivKeyFromBytes(bytes)
return key, nil
}
// blockFromHex parses a full Bitcoin block from a hex encoded string.
func blockFromHex(blockHex string) (*btcutil.Block, error) {
bytes, err := hex.DecodeString(blockHex)
if err != nil {
return nil, err
}
return btcutil.NewBlockFromBytes(bytes)
}
// txFromHex parses a full Bitcoin transaction from a hex encoded string.
func txFromHex(txHex string) (*btcutil.Tx, error) {
bytes, err := hex.DecodeString(txHex)
if err != nil {
return nil, err
}
return btcutil.NewTxFromBytes(bytes)
}
// calcStaticFee calculates appropriate fees for commitment transactions. This
// function provides a simple way to allow test balance assertions to take fee
// calculations into account.
//
// TODO(bvu): Refactor when dynamic fee estimation is added.
func calcStaticFee(chanType channeldb.ChannelType, numHTLCs int) btcutil.Amount {
const (
htlcWeight = 172
feePerKw = btcutil.Amount(24/4) * 1000
)
htlcsWeight := htlcWeight * int64(numHTLCs)
totalWeight := CommitWeight(chanType) + lntypes.WeightUnit(htlcsWeight)
return feePerKw * (btcutil.Amount(totalWeight)) / 1000
}
// ForceStateTransition executes the necessary interaction between the two
// commitment state machines to transition to a new state locking in any
// pending updates. This method is useful when testing interactions between two
// live state machines.
func ForceStateTransition(chanA, chanB *LightningChannel) error {
aliceNewCommit, err := chanA.SignNextCommitment(ctxb)
if err != nil {
return err
}
err = chanB.ReceiveNewCommitment(aliceNewCommit.CommitSigs)
if err != nil {
return err
}
bobRevocation, _, _, err := chanB.RevokeCurrentCommitment()
if err != nil {
return err
}
bobNewCommit, err := chanB.SignNextCommitment(ctxb)
if err != nil {
return err
}
_, _, err = chanA.ReceiveRevocation(bobRevocation)
if err != nil {
return err
}
err = chanA.ReceiveNewCommitment(bobNewCommit.CommitSigs)
if err != nil {
return err
}
aliceRevocation, _, _, err := chanA.RevokeCurrentCommitment()
if err != nil {
return err
}
_, _, err = chanB.ReceiveRevocation(aliceRevocation)
if err != nil {
return err
}
return nil
}
func NewDefaultAuxSignerMock(t *testing.T) *MockAuxSigner {
auxSigner := NewAuxSignerMock(EmptyMockJobHandler)
type testSigBlob struct {
BlobInt tlv.RecordT[tlv.TlvType65634, uint16]
}
var sigBlobBuf bytes.Buffer
sigBlob := testSigBlob{
BlobInt: tlv.NewPrimitiveRecord[tlv.TlvType65634, uint16](5),
}
tlvStream, err := tlv.NewStream(sigBlob.BlobInt.Record())
require.NoError(t, err, "unable to create tlv stream")
require.NoError(t, tlvStream.Encode(&sigBlobBuf))
auxSigner.On(
"SubmitSecondLevelSigBatch", mock.Anything, mock.Anything,
mock.Anything,
).Return(nil)
auxSigner.On(
"PackSigs", mock.Anything,
).Return(fn.Ok(fn.Some(sigBlobBuf.Bytes())))
auxSigner.On(
"UnpackSigs", mock.Anything,
).Return(fn.Ok([]fn.Option[tlv.Blob]{
fn.Some(sigBlobBuf.Bytes()),
}))
auxSigner.On(
"VerifySecondLevelSigs", mock.Anything, mock.Anything,
mock.Anything,
).Return(nil)
return auxSigner
}
package lnwallet
import (
"encoding/binary"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input"
)
const (
// StateHintSize is the total number of bytes used between the sequence
// number and locktime of the commitment transaction use to encode a hint
// to the state number of a particular commitment transaction.
StateHintSize = 6
// MaxStateHint is the maximum state number we're able to encode using
// StateHintSize bytes amongst the sequence number and locktime fields
// of the commitment transaction.
maxStateHint uint64 = (1 << 48) - 1
)
var (
// TimelockShift is used to make sure the commitment transaction is
// spendable by setting the locktime with it so that it is larger than
// 500,000,000, thus interpreting it as Unix epoch timestamp and not
// a block height. It is also smaller than the current timestamp which
// has bit (1 << 30) set, so there is no risk of having the commitment
// transaction be rejected. This way we can safely use the lower 24 bits
// of the locktime field for part of the obscured commitment transaction
// number.
TimelockShift = uint32(1 << 29)
)
// CreateHtlcSuccessTx creates a transaction that spends the output on the
// commitment transaction of the peer that receives an HTLC. This transaction
// essentially acts as an off-chain covenant as it's only permitted to spend
// the designated HTLC output, and also that spend can _only_ be used as a
// state transition to create another output which actually allows redemption
// or revocation of an HTLC.
//
// In order to spend the segwit v0 HTLC output, the witness for the passed
// transaction should be:
// - <0> <sender sig> <recvr sig> <preimage>
//
// In order to spend the segwit v1 (taproot) HTLC output, the witness for the
// passed transaction should be:
// - <sender sig> <receiver sig> <preimage> <success_script> <control_block>
func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool,
htlcOutput wire.OutPoint, htlcAmt btcutil.Amount, csvDelay,
leaseExpiry uint32, revocationKey, delayKey *btcec.PublicKey,
auxLeaf input.AuxTapLeaf) (*wire.MsgTx, error) {
// Create a version two transaction (as the success version of this
// spends an output with a CSV timeout).
successTx := wire.NewMsgTx(2)
// The input to the transaction is the outpoint that creates the
// original HTLC on the sender's commitment transaction. Set the
// sequence number based on the channel type.
txin := &wire.TxIn{
PreviousOutPoint: htlcOutput,
Sequence: HtlcSecondLevelInputSequence(chanType),
}
successTx.AddTxIn(txin)
// Next, we'll generate the script used as the output for all second
// level HTLC which forces a covenant w.r.t what can be done with all
// HTLC outputs.
scriptInfo, err := SecondLevelHtlcScript(
chanType, initiator, revocationKey, delayKey, csvDelay,
leaseExpiry, auxLeaf,
)
if err != nil {
return nil, err
}
// Finally, the output is simply the amount of the HTLC (minus the
// required fees), paying to the timeout script.
successTx.AddTxOut(&wire.TxOut{
Value: int64(htlcAmt),
PkScript: scriptInfo.PkScript(),
})
return successTx, nil
}
// CreateHtlcTimeoutTx creates a transaction that spends the HTLC output on the
// commitment transaction of the peer that created an HTLC (the sender). This
// transaction essentially acts as an off-chain covenant as it spends a 2-of-2
// multi-sig output. This output requires a signature from both the sender and
// receiver of the HTLC. By using a distinct transaction, we're able to
// uncouple the timeout and delay clauses of the HTLC contract. This
// transaction is locked with an absolute lock-time so the sender can only
// attempt to claim the output using it after the lock time has passed.
//
// In order to spend the HTLC output for segwit v0, the witness for the passed
// transaction should be:
// - <0> <sender sig> <receiver sig> <0>
//
// In order to spend the HTLC output for segwit v1, then witness for the passed
// transaction should be:
// - <sender sig> <receiver sig> <timeout_script> <control_block>
//
// NOTE: The passed amount for the HTLC should take into account the required
// fee rate at the time the HTLC was created. The fee should be able to
// entirely pay for this (tiny: 1-in 1-out) transaction.
func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, initiator bool,
htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
cltvExpiry, csvDelay, leaseExpiry uint32,
revocationKey, delayKey *btcec.PublicKey,
auxLeaf input.AuxTapLeaf) (*wire.MsgTx, error) {
// Create a version two transaction (as the success version of this
// spends an output with a CSV timeout), and set the lock-time to the
// specified absolute lock-time in blocks.
timeoutTx := wire.NewMsgTx(2)
timeoutTx.LockTime = cltvExpiry
// The input to the transaction is the outpoint that creates the
// original HTLC on the sender's commitment transaction. Set the
// sequence number based on the channel type.
txin := &wire.TxIn{
PreviousOutPoint: htlcOutput,
SignatureScript: []byte{},
Witness: [][]byte{},
Sequence: HtlcSecondLevelInputSequence(chanType),
}
timeoutTx.AddTxIn(txin)
// Next, we'll generate the script used as the output for all second
// level HTLC which forces a covenant w.r.t what can be done with all
// HTLC outputs.
scriptInfo, err := SecondLevelHtlcScript(
chanType, initiator, revocationKey, delayKey, csvDelay,
leaseExpiry, auxLeaf,
)
if err != nil {
return nil, err
}
// Finally, the output is simply the amount of the HTLC (minus the
// required fees), paying to the regular second level HTLC script.
timeoutTx.AddTxOut(&wire.TxOut{
Value: int64(htlcAmt),
PkScript: scriptInfo.PkScript(),
})
return timeoutTx, nil
}
// SetStateNumHint encodes the current state number within the passed
// commitment transaction by re-purposing the locktime and sequence fields in
// the commitment transaction to encode the obfuscated state number. The state
// number is encoded using 48 bits. The lower 24 bits of the lock time are the
// lower 24 bits of the obfuscated state number and the lower 24 bits of the
// sequence field are the higher 24 bits. Finally before encoding, the
// obfuscator is XOR'd against the state number in order to hide the exact
// state number from the PoV of outside parties.
func SetStateNumHint(commitTx *wire.MsgTx, stateNum uint64,
obfuscator [StateHintSize]byte) error {
// With the current schema we are only able to encode state num
// hints up to 2^48. Therefore if the passed height is greater than our
// state hint ceiling, then exit early.
if stateNum > maxStateHint {
return fmt.Errorf("unable to encode state, %v is greater "+
"state num that max of %v", stateNum, maxStateHint)
}
if len(commitTx.TxIn) != 1 {
return fmt.Errorf("commitment tx must have exactly 1 input, "+
"instead has %v", len(commitTx.TxIn))
}
// Convert the obfuscator into a uint64, then XOR that against the
// targeted height in order to obfuscate the state number of the
// commitment transaction in the case that either commitment
// transaction is broadcast directly on chain.
var obfs [8]byte
copy(obfs[2:], obfuscator[:])
xorInt := binary.BigEndian.Uint64(obfs[:])
stateNum = stateNum ^ xorInt
// Set the height bit of the sequence number in order to disable any
// sequence locks semantics.
commitTx.TxIn[0].Sequence = uint32(stateNum>>24) | wire.SequenceLockTimeDisabled
commitTx.LockTime = uint32(stateNum&0xFFFFFF) | TimelockShift
return nil
}
// GetStateNumHint recovers the current state number given a commitment
// transaction which has previously had the state number encoded within it via
// setStateNumHint and a shared obfuscator.
//
// See setStateNumHint for further details w.r.t exactly how the state-hints
// are encoded.
func GetStateNumHint(commitTx *wire.MsgTx, obfuscator [StateHintSize]byte) uint64 {
// Convert the obfuscator into a uint64, this will be used to
// de-obfuscate the final recovered state number.
var obfs [8]byte
copy(obfs[2:], obfuscator[:])
xorInt := binary.BigEndian.Uint64(obfs[:])
// Retrieve the state hint from the sequence number and locktime
// of the transaction.
stateNumXor := uint64(commitTx.TxIn[0].Sequence&0xFFFFFF) << 24
stateNumXor |= uint64(commitTx.LockTime & 0xFFFFFF)
// Finally, to obtain the final state number, we XOR by the obfuscator
// value to de-obfuscate the state number.
return stateNumXor ^ xorInt
}
package lnwallet
import (
"github.com/lightningnetwork/lnd/fn/v2"
)
// updateLog is an append-only log that stores updates to a node's commitment
// chain. This structure can be seen as the "mempool" within Lightning where
// changes are stored before they're committed to the chain. Once an entry has
// been committed in both the local and remote commitment chain, then it can be
// removed from this log.
//
// TODO(roasbeef): create lightning package, move commitment and update to
// package?
// - also move state machine, separate from lnwallet package
// - possible embed updateLog within commitmentChain.
type updateLog struct {
// logIndex is a monotonically increasing integer that tracks the total
// number of update entries ever applied to the log. When sending new
// commitment states, we include all updates up to this index.
logIndex uint64
// htlcCounter is a monotonically increasing integer that tracks the
// total number of offered HTLC's by the owner of this update log,
// hence the `Add` update type. We use a distinct index for this
// purpose, as update's that remove entries from the log will be
// indexed using this counter.
htlcCounter uint64
// List is the updatelog itself, we embed this value so updateLog has
// access to all the method of a list.List.
*fn.List[*paymentDescriptor]
// updateIndex maps a `logIndex` to a particular update entry. It
// deals with the four update types:
// `Fail|MalformedFail|Settle|FeeUpdate`
updateIndex map[uint64]*fn.Node[*paymentDescriptor]
// htlcIndex maps a `htlcCounter` to an offered HTLC entry, hence the
// `Add` update.
htlcIndex map[uint64]*fn.Node[*paymentDescriptor]
// modifiedHtlcs is a set that keeps track of all the current modified
// htlcs, hence update types `Fail|MalformedFail|Settle`. A modified
// HTLC is one that's present in the log, and has as a pending fail or
// settle that's attempting to consume it.
modifiedHtlcs fn.Set[uint64]
}
// newUpdateLog creates a new updateLog instance.
func newUpdateLog(logIndex, htlcCounter uint64) *updateLog {
return &updateLog{
List: fn.NewList[*paymentDescriptor](),
updateIndex: make(map[uint64]*fn.Node[*paymentDescriptor]),
htlcIndex: make(map[uint64]*fn.Node[*paymentDescriptor]),
logIndex: logIndex,
htlcCounter: htlcCounter,
modifiedHtlcs: fn.NewSet[uint64](),
}
}
// restoreHtlc will "restore" a prior HTLC to the updateLog. We say restore as
// this method is intended to be used when re-covering a prior commitment
// state. This function differs from appendHtlc in that it won't increment
// either of log's counters. If the HTLC is already present, then it is
// ignored.
func (u *updateLog) restoreHtlc(pd *paymentDescriptor) {
if _, ok := u.htlcIndex[pd.HtlcIndex]; ok {
return
}
u.htlcIndex[pd.HtlcIndex] = u.PushBack(pd)
}
// appendUpdate appends a new update to the tip of the updateLog. The entry is
// also added to index accordingly.
func (u *updateLog) appendUpdate(pd *paymentDescriptor) {
u.updateIndex[u.logIndex] = u.PushBack(pd)
u.logIndex++
}
// restoreUpdate appends a new update to the tip of the updateLog. The entry is
// also added to index accordingly. This function differs from appendUpdate in
// that it won't increment the log index counter.
func (u *updateLog) restoreUpdate(pd *paymentDescriptor) {
u.updateIndex[pd.LogIndex] = u.PushBack(pd)
}
// appendHtlc appends a new HTLC offer to the tip of the update log. The entry
// is also added to the offer index accordingly.
func (u *updateLog) appendHtlc(pd *paymentDescriptor) {
u.htlcIndex[u.htlcCounter] = u.PushBack(pd)
u.htlcCounter++
u.logIndex++
}
// lookupHtlc attempts to look up an offered HTLC according to its offer
// index. If the entry isn't found, then a nil pointer is returned.
func (u *updateLog) lookupHtlc(i uint64) *paymentDescriptor {
htlc, ok := u.htlcIndex[i]
if !ok {
return nil
}
return htlc.Value
}
// remove attempts to remove an entry from the update log. If the entry is
// found, then the entry will be removed from the update log and index.
func (u *updateLog) removeUpdate(i uint64) {
entry := u.updateIndex[i]
u.Remove(entry)
delete(u.updateIndex, i)
}
// removeHtlc attempts to remove an HTLC offer form the update log. If the
// entry is found, then the entry will be removed from both the main log and
// the offer index.
func (u *updateLog) removeHtlc(i uint64) {
entry := u.htlcIndex[i]
u.Remove(entry)
delete(u.htlcIndex, i)
u.modifiedHtlcs.Remove(i)
}
// htlcHasModification returns true if the HTLC identified by the passed index
// has a pending modification within the log.
func (u *updateLog) htlcHasModification(i uint64) bool {
return u.modifiedHtlcs.Contains(i)
}
// markHtlcModified marks an HTLC as modified based on its HTLC index. After a
// call to this method, htlcHasModification will return true until the HTLC is
// removed.
func (u *updateLog) markHtlcModified(i uint64) {
u.modifiedHtlcs.Add(i)
}
// compactLogs performs garbage collection within the log removing HTLCs which
// have been removed from the point-of-view of the tail of both chains. The
// entries which timeout/settle HTLCs are also removed.
func compactLogs(ourLog, theirLog *updateLog,
localChainTail, remoteChainTail uint64) {
compactLog := func(logA, logB *updateLog) {
var nextA *fn.Node[*paymentDescriptor]
for e := logA.Front(); e != nil; e = nextA {
// Assign next iteration element at top of loop because
// we may remove the current element from the list,
// which can change the iterated sequence.
nextA = e.Next()
htlc := e.Value
rmvHeights := htlc.removeCommitHeights
// We skip Adds, as they will be removed along with the
// fail/settles below.
if htlc.EntryType == Add {
continue
}
// If the HTLC hasn't yet been removed from either
// chain, the skip it.
if rmvHeights.Remote == 0 || rmvHeights.Local == 0 {
continue
}
// Otherwise if the height of the tail of both chains
// is at least the height in which the HTLC was
// removed, then evict the settle/timeout entry along
// with the original add entry.
if remoteChainTail >= rmvHeights.Remote &&
localChainTail >= rmvHeights.Local {
// Fee updates have no parent htlcs, so we only
// remove the update itself.
if htlc.EntryType == FeeUpdate {
logA.removeUpdate(htlc.LogIndex)
continue
}
// The other types (fail/settle) do have a
// parent HTLC, so we'll remove that HTLC from
// the other log.
logA.removeUpdate(htlc.LogIndex)
logB.removeHtlc(htlc.ParentIndex)
}
}
}
compactLog(ourLog, theirLog)
compactLog(theirLog, ourLog)
}
package lnwallet
import (
"bytes"
"crypto/sha256"
"errors"
"fmt"
"math"
"net"
"sync"
"sync/atomic"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/btcutil/txsort"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/wallet"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
"github.com/lightningnetwork/lnd/lnwallet/chanvalidate"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/shachain"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// The size of the buffered queue of requests to the wallet from the
// outside word.
msgBufferSize = 100
// AnchorChanReservedValue is the amount we'll keep around in the
// wallet in case we have to fee bump anchor channels on force close.
// TODO(halseth): update constant to target a specific commit size at
// set fee rate.
AnchorChanReservedValue = btcutil.Amount(10_000)
// MaxAnchorChanReservedValue is the maximum value we'll reserve for
// anchor channel fee bumping. We cap it at 10 times the per-channel
// amount such that nodes with a high number of channels don't have to
// keep around a very large amount for the unlikely scenario that they
// all close at the same time.
MaxAnchorChanReservedValue = 10 * AnchorChanReservedValue
)
var (
// ErrPsbtFundingRequired is the error that is returned during the
// contribution handling process if the process should be paused for
// the construction of a PSBT outside of lnd's wallet.
ErrPsbtFundingRequired = errors.New("PSBT funding required")
// ErrReservedValueInvalidated is returned if we try to publish a
// transaction that would take the walletbalance below what we require
// to keep around to fee bump our open anchor channels.
ErrReservedValueInvalidated = errors.New("reserved wallet balance " +
"invalidated: transaction would leave insufficient funds for " +
"fee bumping anchor channel closings (see debug log for details)")
// ErrEmptyPendingChanID is returned when an empty value is used for
// the pending channel ID.
ErrEmptyPendingChanID = errors.New("pending channel ID is empty")
// ErrDuplicatePendingChanID is returned when an existing pending
// channel ID is registered again.
ErrDuplicatePendingChanID = errors.New("duplicate pending channel ID")
)
// PsbtFundingRequired is a type that implements the error interface and
// contains the information needed to construct a PSBT.
type PsbtFundingRequired struct {
// Intent is the pending PSBT funding intent that needs to be funded
// if the wrapping error is returned.
Intent *chanfunding.PsbtIntent
}
// Error returns the underlying error.
//
// NOTE: This method is part of the error interface.
func (p *PsbtFundingRequired) Error() string {
return ErrPsbtFundingRequired.Error()
}
// AuxFundingDesc stores a series of attributes that may be used to modify the
// way the channel funding occurs. This struct contains information that can
// only be derived once both sides have received and sent their contributions
// to the channel (keys, etc.).
type AuxFundingDesc struct {
// CustomFundingBlob is a custom blob that'll be stored in the database
// within the OpenChannel struct. This should represent information
// static to the channel lifetime.
CustomFundingBlob tlv.Blob
// CustomLocalCommitBlob is a custom blob that'll be stored in the
// first commitment entry for the local party.
CustomLocalCommitBlob tlv.Blob
// CustomRemoteCommitBlob is a custom blob that'll be stored in the
// first commitment entry for the remote party.
CustomRemoteCommitBlob tlv.Blob
// LocalInitAuxLeaves is the set of aux leaves that'll be used for our
// very first commitment state.
LocalInitAuxLeaves CommitAuxLeaves
// RemoteInitAuxLeaves is the set of aux leaves that'll be used for the
// very first commitment state for the remote party.
RemoteInitAuxLeaves CommitAuxLeaves
}
// InitFundingReserveMsg is the first message sent to initiate the workflow
// required to open a payment channel with a remote peer. The initial required
// parameters are configurable across channels. These parameters are to be
// chosen depending on the fee climate within the network, and time value of
// funds to be locked up within the channel. Upon success a ChannelReservation
// will be created in order to track the lifetime of this pending channel.
// Outputs selected will be 'locked', making them unavailable, for any other
// pending reservations. Therefore, all channels in reservation limbo will be
// periodically timed out after an idle period in order to avoid "exhaustion"
// attacks.
type InitFundingReserveMsg struct {
// ChainHash denotes that chain to be used to ultimately open the
// target channel.
ChainHash *chainhash.Hash
// PendingChanID is the pending channel ID for this funding flow as
// used in the wire protocol.
PendingChanID [32]byte
// NodeID is the ID of the remote node we would like to open a channel
// with.
NodeID *btcec.PublicKey
// NodeAddr is the address port that we used to either establish or
// accept the connection which led to the negotiation of this funding
// workflow.
NodeAddr net.Addr
// SubtractFees should be set if we intend to spend exactly
// LocalFundingAmt when opening the channel, subtracting the fees from
// the funding output. This can be used for instance to use all our
// remaining funds to open the channel, since it will take fees into
// account.
SubtractFees bool
// LocalFundingAmt is the amount of funds requested from us for this
// channel.
LocalFundingAmt btcutil.Amount
// RemoteFundingAmnt is the amount of funds the remote will contribute
// to this channel.
RemoteFundingAmt btcutil.Amount
// FundUpToMaxAmt defines if channel funding should try to add as many
// funds to the channel opening as possible up to this amount. If used,
// then MinFundAmt is treated as the minimum amount of funds that must
// be available to open the channel. If set to zero it is ignored.
FundUpToMaxAmt btcutil.Amount
// MinFundAmt denotes the minimum channel capacity that has to be
// allocated iff the FundUpToMaxAmt is set.
MinFundAmt btcutil.Amount
// Outpoints is a list of client-selected outpoints that should be used
// for funding a channel. If LocalFundingAmt is specified then this
// amount is allocated from the sum of outpoints towards funding. If the
// FundUpToMaxAmt is specified the entirety of selected funds is
// allocated towards channel funding.
Outpoints []wire.OutPoint
// RemoteChanReserve is the channel reserve we required for the remote
// peer.
RemoteChanReserve btcutil.Amount
// CommitFeePerKw is the starting accepted satoshis/Kw fee for the set
// of initial commitment transactions. In order to ensure timely
// confirmation, it is recommended that this fee should be generous,
// paying some multiple of the accepted base fee rate of the network.
CommitFeePerKw chainfee.SatPerKWeight
// FundingFeePerKw is the fee rate in sat/kw to use for the initial
// funding transaction.
FundingFeePerKw chainfee.SatPerKWeight
// PushMSat is the number of milli-satoshis that should be pushed over
// the responder as part of the initial channel creation.
PushMSat lnwire.MilliSatoshi
// Flags are the channel flags specified by the initiator in the
// open_channel message.
Flags lnwire.FundingFlag
// MinConfs indicates the minimum number of confirmations that each
// output selected to fund the channel should satisfy.
MinConfs int32
// CommitType indicates what type of commitment type the channel should
// be using, like tweakless or anchors.
CommitType CommitmentType
// ChanFunder is an optional channel funder that allows the caller to
// control exactly how the channel funding is carried out. If not
// specified, then the default chanfunding.WalletAssembler will be
// used.
ChanFunder chanfunding.Assembler
// AllowUtxoForFunding enables the channel funding workflow to restrict
// the selection of utxos when selecting the inputs for the channel
// opening. This does ONLY apply for the internal wallet backed channel
// opening case.
//
// NOTE: This is very useful when opening channels with unconfirmed
// inputs to make sure stable non-replaceable inputs are used.
AllowUtxoForFunding func(Utxo) bool
// ZeroConf is a boolean that is true if a zero-conf channel was
// negotiated.
ZeroConf bool
// OptionScidAlias is a boolean that is true if an option-scid-alias
// channel type was explicitly negotiated.
OptionScidAlias bool
// ScidAliasFeature is true if the option-scid-alias feature bit was
// negotiated.
ScidAliasFeature bool
// Memo is any arbitrary information we wish to store locally about the
// channel that will be useful to our future selves.
Memo []byte
// TapscriptRoot is an optional tapscript root that if provided, will
// be used to create the combined key for musig2 based channels.
TapscriptRoot fn.Option[chainhash.Hash]
// err is a channel in which all errors will be sent across. Will be
// nil if this initial set is successful.
//
// NOTE: In order to avoid deadlocks, this channel MUST be buffered.
err chan error
// resp is channel in which a ChannelReservation with our contributions
// filled in will be sent across this channel in the case of a
// successfully reservation initiation. In the case of an error, this
// will read a nil pointer.
//
// NOTE: In order to avoid deadlocks, this channel MUST be buffered.
resp chan *ChannelReservation
}
// fundingReserveCancelMsg is a message reserved for cancelling an existing
// channel reservation identified by its reservation ID. Cancelling a reservation
// frees its locked outputs up, for inclusion within further reservations.
type fundingReserveCancelMsg struct {
pendingFundingID uint64
// NOTE: In order to avoid deadlocks, this channel MUST be buffered.
err chan error // Buffered
}
// addContributionMsg represents a message executing the second phase of the
// channel reservation workflow. This message carries the counterparty's
// "contribution" to the payment channel. In the case that this message is
// processed without generating any errors, then channel reservation will then
// be able to construct the funding tx, both commitment transactions, and
// finally generate signatures for all our inputs to the funding transaction,
// and for the remote node's version of the commitment transaction.
type addContributionMsg struct {
pendingFundingID uint64
contribution *ChannelContribution
// NOTE: In order to avoid deadlocks, this channel MUST be buffered.
err chan error
}
// continueContributionMsg represents a message that signals that the
// interrupted funding process involving a PSBT can now be continued because the
// finalized transaction is now available.
type continueContributionMsg struct {
pendingFundingID uint64
// auxFundingDesc is an optional descriptor that contains information
// about the custom channel funding flow.
auxFundingDesc fn.Option[AuxFundingDesc]
// NOTE: In order to avoid deadlocks, this channel MUST be buffered.
err chan error
}
// addSingleContributionMsg represents a message executing the second phase of
// a single funder channel reservation workflow. This messages carries the
// counterparty's "contribution" to the payment channel. As this message is
// sent when on the responding side to a single funder workflow, no further
// action apart from storing the provided contribution is carried out.
type addSingleContributionMsg struct {
pendingFundingID uint64
contribution *ChannelContribution
// NOTE: In order to avoid deadlocks, this channel MUST be buffered.
err chan error
}
// addCounterPartySigsMsg represents the final message required to complete,
// and 'open' a payment channel. This message carries the counterparty's
// signatures for each of their inputs to the funding transaction, and also a
// signature allowing us to spend our version of the commitment transaction.
// If we're able to verify all the signatures are valid, the funding transaction
// will be broadcast to the network. After the funding transaction gains a
// configurable number of confirmations, the channel is officially considered
// 'open'.
type addCounterPartySigsMsg struct {
pendingFundingID uint64
// Should be order of sorted inputs that are theirs. Sorting is done
// in accordance to BIP-69:
// https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
theirFundingInputScripts []*input.Script
// This should be 1/2 of the signatures needed to successfully spend our
// version of the commitment transaction.
theirCommitmentSig input.Signature
// This channel is used to return the completed channel after the wallet
// has completed all of its stages in the funding process.
completeChan chan *channeldb.OpenChannel
// NOTE: In order to avoid deadlocks, this channel MUST be buffered.
err chan error
}
// addSingleFunderSigsMsg represents the next-to-last message required to
// complete a single-funder channel workflow. Once the initiator is able to
// construct the funding transaction, they send both the outpoint and a
// signature for our version of the commitment transaction. Once this message
// is processed we (the responder) are able to construct both commitment
// transactions, signing the remote party's version.
type addSingleFunderSigsMsg struct {
pendingFundingID uint64
// auxFundingDesc is an optional descriptor that contains information
// about the custom channel funding flow.
auxFundingDesc fn.Option[AuxFundingDesc]
// fundingOutpoint is the outpoint of the completed funding
// transaction as assembled by the workflow initiator.
fundingOutpoint *wire.OutPoint
// theirCommitmentSig are the 1/2 of the signatures needed to
// successfully spend our version of the commitment transaction.
theirCommitmentSig input.Signature
// This channel is used to return the completed channel after the wallet
// has completed all of its stages in the funding process.
completeChan chan *channeldb.OpenChannel
// NOTE: In order to avoid deadlocks, this channel MUST be buffered.
err chan error
}
// CheckReservedValueTxReq is the request struct used to call
// CheckReservedValueTx with. It contains the transaction to check as well as
// an optional explicitly defined index to denote a change output that is not
// watched by the wallet.
type CheckReservedValueTxReq struct {
// Tx is the transaction to check the outputs for.
Tx *wire.MsgTx
// ChangeIndex denotes an optional output index that can be explicitly
// set for a change that is not being watched by the wallet and would
// otherwise not be recognized as a change output.
ChangeIndex *int
}
// LightningWallet is a domain specific, yet general Bitcoin wallet capable of
// executing workflow required to interact with the Lightning Network. It is
// domain specific in the sense that it understands all the fancy scripts used
// within the Lightning Network, channel lifetimes, etc. However, it embeds a
// general purpose Bitcoin wallet within it. Therefore, it is also able to
// serve as a regular Bitcoin wallet which uses HD keys. The wallet is highly
// concurrent internally. All communication, and requests towards the wallet
// are dispatched as messages over channels, ensuring thread safety across all
// operations. Interaction has been designed independent of any peer-to-peer
// communication protocol, allowing the wallet to be self-contained and
// embeddable within future projects interacting with the Lightning Network.
//
// NOTE: At the moment the wallet requires a btcd full node, as it's dependent
// on btcd's websockets notifications as event triggers during the lifetime of a
// channel. However, once the chainntnfs package is complete, the wallet will
// be compatible with multiple RPC/notification services such as Electrum,
// Bitcoin Core + ZeroMQ, etc. Eventually, the wallet won't require a full-node
// at all, as SPV support is integrated into btcwallet.
type LightningWallet struct {
started int32 // To be used atomically.
shutdown int32 // To be used atomically.
nextFundingID uint64 // To be used atomically.
// Cfg is the configuration struct that will be used by the wallet to
// access the necessary interfaces and default it needs to carry on its
// duties.
Cfg Config
// WalletController is the core wallet, all non Lightning Network
// specific interaction is proxied to the internal wallet.
WalletController
// SecretKeyRing is the interface we'll use to derive any keys related
// to our purpose within the network including: multi-sig keys, node
// keys, revocation keys, etc.
keychain.SecretKeyRing
// This mutex MUST be held when performing coin selection in order to
// avoid inadvertently creating multiple funding transaction which
// double spend inputs across each other.
coinSelectMtx sync.RWMutex
// All messages to the wallet are to be sent across this channel.
msgChan chan interface{}
// Incomplete payment channels are stored in the map below. An intent
// to create a payment channel is tracked as a "reservation" within
// limbo. Once the final signatures have been exchanged, a reservation
// is removed from limbo. Each reservation is tracked by a unique
// monotonically integer. All requests concerning the channel MUST
// carry a valid, active funding ID.
fundingLimbo map[uint64]*ChannelReservation
// reservationIDs maps a pending channel ID to the reservation ID used
// as key in the fundingLimbo map. Used to easily look up a channel
// reservation given a pending channel ID.
reservationIDs map[[32]byte]uint64
limboMtx sync.RWMutex
// lockedOutPoints is a set of the currently locked outpoint. This
// information is kept in order to provide an easy way to unlock all
// the currently locked outpoints.
lockedOutPoints map[wire.OutPoint]struct{}
// fundingIntents houses all the "interception" registered by a caller
// using the RegisterFundingIntent method.
intentMtx sync.RWMutex
fundingIntents map[[32]byte]chanfunding.Intent
quit chan struct{}
wg sync.WaitGroup
}
// NewLightningWallet creates/opens and initializes a LightningWallet instance.
// If the wallet has never been created (according to the passed dataDir), first-time
// setup is executed.
func NewLightningWallet(Cfg Config) (*LightningWallet, error) {
return &LightningWallet{
Cfg: Cfg,
SecretKeyRing: Cfg.SecretKeyRing,
WalletController: Cfg.WalletController,
msgChan: make(chan interface{}, msgBufferSize),
nextFundingID: 0,
fundingLimbo: make(map[uint64]*ChannelReservation),
reservationIDs: make(map[[32]byte]uint64),
lockedOutPoints: make(map[wire.OutPoint]struct{}),
fundingIntents: make(map[[32]byte]chanfunding.Intent),
quit: make(chan struct{}),
}, nil
}
// Startup establishes a connection to the RPC source, and spins up all
// goroutines required to handle incoming messages.
func (l *LightningWallet) Startup() error {
// Already started?
if atomic.AddInt32(&l.started, 1) != 1 {
return nil
}
// Start the underlying wallet controller.
if err := l.Start(); err != nil {
return err
}
if l.Cfg.Rebroadcaster != nil {
go func() {
if err := l.Cfg.Rebroadcaster.Start(); err != nil {
walletLog.Errorf("unable to start "+
"rebroadcaster: %v", err)
}
}()
}
l.wg.Add(1)
go l.requestHandler()
return nil
}
// Shutdown gracefully stops the wallet, and all active goroutines.
func (l *LightningWallet) Shutdown() error {
if atomic.AddInt32(&l.shutdown, 1) != 1 {
return nil
}
// Signal the underlying wallet controller to shutdown, waiting until
// all active goroutines have been shutdown.
if err := l.Stop(); err != nil {
return err
}
if l.Cfg.Rebroadcaster != nil && l.Cfg.Rebroadcaster.Started() {
l.Cfg.Rebroadcaster.Stop()
}
close(l.quit)
l.wg.Wait()
return nil
}
// PublishTransaction wraps the wallet controller tx publish method with an
// extra rebroadcaster layer if the sub-system is configured.
func (l *LightningWallet) PublishTransaction(tx *wire.MsgTx,
label string) error {
sendTxToWallet := func() error {
return l.WalletController.PublishTransaction(tx, label)
}
// If we don't have rebroadcaster then we can exit early (and send only
// to the wallet).
if l.Cfg.Rebroadcaster == nil || !l.Cfg.Rebroadcaster.Started() {
return sendTxToWallet()
}
// We pass this into the rebroadcaster first, so the initial attempt
// will succeed if the transaction isn't yet in the mempool. However we
// ignore the error here as this might be resent on start up and the
// transaction already exists.
_ = l.Cfg.Rebroadcaster.Broadcast(tx)
// Then we pass things into the wallet as normal, which'll add the
// transaction label on disk.
if err := sendTxToWallet(); err != nil {
return err
}
// TODO(roasbeef): want diff height actually? no context though
_, bestHeight, err := l.Cfg.ChainIO.GetBestBlock()
if err != nil {
return err
}
txHash := tx.TxHash()
go func() {
const numConfs = 6
txConf, err := l.Cfg.Notifier.RegisterConfirmationsNtfn(
&txHash, tx.TxOut[0].PkScript, numConfs,
uint32(bestHeight),
)
if err != nil {
return
}
select {
case <-txConf.Confirmed:
// TODO(roasbeef): also want to remove from
// rebroadcaster if conflict happens...deeper wallet
// integration?
l.Cfg.Rebroadcaster.MarkAsConfirmed(tx.TxHash())
case <-l.quit:
return
}
}()
return nil
}
// ConfirmedBalance returns the current confirmed balance of a wallet account.
// This methods wraps the internal WalletController method so we're able to
// properly hold the coin select mutex while we compute the balance.
func (l *LightningWallet) ConfirmedBalance(confs int32,
account string) (btcutil.Amount, error) {
l.coinSelectMtx.Lock()
defer l.coinSelectMtx.Unlock()
return l.WalletController.ConfirmedBalance(confs, account)
}
// ListUnspentWitnessFromDefaultAccount returns all unspent outputs from the
// default wallet account which are version 0 witness programs. The 'minConfs'
// and 'maxConfs' parameters indicate the minimum and maximum number of
// confirmations an output needs in order to be returned by this method. Passing
// -1 as 'minConfs' indicates that even unconfirmed outputs should be returned.
// Using MaxInt32 as 'maxConfs' implies returning all outputs with at least
// 'minConfs'.
//
// NOTE: This method requires the global coin selection lock to be held.
func (l *LightningWallet) ListUnspentWitnessFromDefaultAccount(
minConfs, maxConfs int32) ([]*Utxo, error) {
return l.WalletController.ListUnspentWitness(
minConfs, maxConfs, DefaultAccountName,
)
}
// LockedOutpoints returns a list of all currently locked outpoint.
func (l *LightningWallet) LockedOutpoints() []*wire.OutPoint {
outPoints := make([]*wire.OutPoint, 0, len(l.lockedOutPoints))
for outPoint := range l.lockedOutPoints {
outPoint := outPoint
outPoints = append(outPoints, &outPoint)
}
return outPoints
}
// ResetReservations reset the volatile wallet state which tracks all currently
// active reservations.
func (l *LightningWallet) ResetReservations() {
l.nextFundingID = 0
l.fundingLimbo = make(map[uint64]*ChannelReservation)
l.reservationIDs = make(map[[32]byte]uint64)
for outpoint := range l.lockedOutPoints {
_ = l.ReleaseOutput(chanfunding.LndInternalLockID, outpoint)
}
l.lockedOutPoints = make(map[wire.OutPoint]struct{})
}
// ActiveReservations returns a slice of all the currently active
// (non-canceled) reservations.
func (l *LightningWallet) ActiveReservations() []*ChannelReservation {
reservations := make([]*ChannelReservation, 0, len(l.fundingLimbo))
for _, reservation := range l.fundingLimbo {
reservations = append(reservations, reservation)
}
return reservations
}
// requestHandler is the primary goroutine(s) responsible for handling, and
// dispatching replies to all messages.
func (l *LightningWallet) requestHandler() {
defer l.wg.Done()
out:
for {
select {
case m := <-l.msgChan:
switch msg := m.(type) {
case *InitFundingReserveMsg:
l.handleFundingReserveRequest(msg)
case *fundingReserveCancelMsg:
l.handleFundingCancelRequest(msg)
case *addSingleContributionMsg:
l.handleSingleContribution(msg)
case *addContributionMsg:
l.handleContributionMsg(msg)
case *continueContributionMsg:
l.handleChanPointReady(msg)
case *addSingleFunderSigsMsg:
l.handleSingleFunderSigs(msg)
case *addCounterPartySigsMsg:
l.handleFundingCounterPartySigs(msg)
}
case <-l.quit:
// TODO: do some clean up
break out
}
}
}
// InitChannelReservation kicks off the 3-step workflow required to successfully
// open a payment channel with a remote node. As part of the funding
// reservation, the inputs selected for the funding transaction are 'locked'.
// This ensures that multiple channel reservations aren't double spending the
// same inputs in the funding transaction. If reservation initialization is
// successful, a ChannelReservation containing our completed contribution is
// returned. Our contribution contains all the items necessary to allow the
// counterparty to build the funding transaction, and both versions of the
// commitment transaction. Otherwise, an error occurred and a nil pointer along
// with an error are returned.
//
// Once a ChannelReservation has been obtained, two additional steps must be
// processed before a payment channel can be considered 'open'. The second step
// validates, and processes the counterparty's channel contribution. The third,
// and final step verifies all signatures for the inputs of the funding
// transaction, and that the signature we record for our version of the
// commitment transaction is valid.
func (l *LightningWallet) InitChannelReservation(
req *InitFundingReserveMsg) (*ChannelReservation, error) {
req.resp = make(chan *ChannelReservation, 1)
req.err = make(chan error, 1)
select {
case l.msgChan <- req:
case <-l.quit:
return nil, errors.New("wallet shutting down")
}
return <-req.resp, <-req.err
}
// RegisterFundingIntent allows a caller to signal to the wallet that if a
// pending channel ID of expectedID is found, then it can skip constructing a
// new chanfunding.Assembler, and instead use the specified chanfunding.Intent.
// As an example, this lets some of the parameters for funding transaction to
// be negotiated outside the regular funding protocol.
func (l *LightningWallet) RegisterFundingIntent(expectedID [32]byte,
shimIntent chanfunding.Intent) error {
l.intentMtx.Lock()
defer l.intentMtx.Unlock()
// Sanity check the pending channel ID is not empty.
var zeroID [32]byte
if expectedID == zeroID {
return ErrEmptyPendingChanID
}
if _, ok := l.fundingIntents[expectedID]; ok {
return fmt.Errorf("%w: already has intent registered: %x",
ErrDuplicatePendingChanID, expectedID[:])
}
l.fundingIntents[expectedID] = shimIntent
return nil
}
// PsbtFundingVerify looks up a previously registered funding intent by its
// pending channel ID and tries to advance the state machine by verifying the
// passed PSBT.
func (l *LightningWallet) PsbtFundingVerify(pendingChanID [32]byte,
packet *psbt.Packet, skipFinalize bool) error {
l.intentMtx.Lock()
defer l.intentMtx.Unlock()
intent, ok := l.fundingIntents[pendingChanID]
if !ok {
return fmt.Errorf("no funding intent found for "+
"pendingChannelID(%x)", pendingChanID[:])
}
psbtIntent, ok := intent.(*chanfunding.PsbtIntent)
if !ok {
return fmt.Errorf("incompatible funding intent")
}
if skipFinalize && psbtIntent.ShouldPublishFundingTX() {
return fmt.Errorf("cannot set skip_finalize for channel that " +
"did not set no_publish")
}
err := psbtIntent.Verify(packet, skipFinalize)
if err != nil {
return fmt.Errorf("error verifying PSBT: %w", err)
}
// Get the channel reservation for that corresponds to this pending
// channel ID.
l.limboMtx.Lock()
pid, ok := l.reservationIDs[pendingChanID]
if !ok {
l.limboMtx.Unlock()
return fmt.Errorf("no channel reservation found for "+
"pendingChannelID(%x)", pendingChanID[:])
}
pendingReservation, ok := l.fundingLimbo[pid]
l.limboMtx.Unlock()
if !ok {
return fmt.Errorf("no channel reservation found for "+
"reservation ID %v", pid)
}
// Now the PSBT has been populated and verified, we can again check
// whether the value reserved for anchor fee bumping is respected.
isPublic := pendingReservation.partialState.ChannelFlags&lnwire.FFAnnounceChannel != 0
hasAnchors := pendingReservation.partialState.ChanType.HasAnchors()
return l.enforceNewReservedValue(intent, isPublic, hasAnchors)
}
// PsbtFundingFinalize looks up a previously registered funding intent by its
// pending channel ID and tries to advance the state machine by finalizing the
// passed PSBT.
func (l *LightningWallet) PsbtFundingFinalize(pid [32]byte, packet *psbt.Packet,
rawTx *wire.MsgTx) error {
l.intentMtx.Lock()
defer l.intentMtx.Unlock()
intent, ok := l.fundingIntents[pid]
if !ok {
return fmt.Errorf("no funding intent found for "+
"pendingChannelID(%x)", pid[:])
}
psbtIntent, ok := intent.(*chanfunding.PsbtIntent)
if !ok {
return fmt.Errorf("incompatible funding intent")
}
// Either the PSBT or the raw TX must be set.
switch {
case packet != nil && rawTx == nil:
err := psbtIntent.Finalize(packet)
if err != nil {
return fmt.Errorf("error finalizing PSBT: %w", err)
}
case rawTx != nil && packet == nil:
err := psbtIntent.FinalizeRawTX(rawTx)
if err != nil {
return fmt.Errorf("error finalizing raw TX: %w", err)
}
default:
return fmt.Errorf("either a PSBT or raw TX must be specified")
}
return nil
}
// CancelFundingIntent allows a caller to cancel a previously registered
// funding intent. If no intent was found, then an error will be returned.
func (l *LightningWallet) CancelFundingIntent(pid [32]byte) error {
l.intentMtx.Lock()
defer l.intentMtx.Unlock()
intent, ok := l.fundingIntents[pid]
if !ok {
return fmt.Errorf("no funding intent found for "+
"pendingChannelID(%x)", pid[:])
}
// Give the intent a chance to clean up after itself, removing coin
// locks or similar reserved resources.
intent.Cancel()
delete(l.fundingIntents, pid)
return nil
}
// handleFundingReserveRequest processes a message intending to create, and
// validate a funding reservation request.
func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg) {
noFundsCommitted := req.LocalFundingAmt == 0 &&
req.RemoteFundingAmt == 0 && req.FundUpToMaxAmt == 0
// It isn't possible to create a channel with zero funds committed.
if noFundsCommitted {
err := ErrZeroCapacity()
req.err <- err
req.resp <- nil
return
}
// If the funding request is for a different chain than the one the
// wallet is aware of, then we'll reject the request.
if !bytes.Equal(l.Cfg.NetParams.GenesisHash[:], req.ChainHash[:]) {
err := ErrChainMismatch(
l.Cfg.NetParams.GenesisHash, req.ChainHash,
)
req.err <- err
req.resp <- nil
return
}
// We need to avoid enforcing reserved value in the middle of PSBT
// funding because some of the following steps may add UTXOs funding
// the on-chain wallet.
// The enforcement still happens at the last step - in PsbtFundingVerify
enforceNewReservedValue := true
// If no chanFunder was provided, then we'll assume the default
// assembler, which is backed by the wallet's internal coin selection.
if req.ChanFunder == nil {
// We use the P2WSH dust limit since it is larger than the
// P2WPKH dust limit and to avoid threading through two
// different dust limits.
cfg := chanfunding.WalletConfig{
CoinSource: NewCoinSource(
l, req.AllowUtxoForFunding,
),
CoinSelectLocker: l,
CoinLeaser: l,
Signer: l.Cfg.Signer,
DustLimit: DustLimitForSize(
input.P2WSHSize,
),
CoinSelectionStrategy: l.Cfg.CoinSelectionStrategy,
}
req.ChanFunder = chanfunding.NewWalletAssembler(cfg)
} else {
_, isPsbtFunder := req.ChanFunder.(*chanfunding.PsbtAssembler)
enforceNewReservedValue = !isPsbtFunder
}
localFundingAmt := req.LocalFundingAmt
remoteFundingAmt := req.RemoteFundingAmt
hasAnchors := req.CommitType.HasAnchors()
var (
fundingIntent chanfunding.Intent
err error
)
// If we've just received an inbound funding request that we have a
// registered shim intent to, then we'll obtain the backing intent now.
// In this case, we're doing a special funding workflow that allows
// more advanced constructions such as channel factories to be
// instantiated.
l.intentMtx.Lock()
fundingIntent, ok := l.fundingIntents[req.PendingChanID]
l.intentMtx.Unlock()
// Otherwise, this is a normal funding flow, so we'll use the chan
// funder in the attached request to provision the inputs/outputs
// that'll ultimately be used to construct the funding transaction.
if !ok {
var err error
var numAnchorChans int
// Get the number of anchor channels to determine if there is a
// reserved value that must be respected when funding up to the
// maximum amount. Since private channels (most likely) won't be
// used for routing other than the last hop, they bear a smaller
// risk that we must force close them in order to resolve a HTLC
// up/downstream. Hence we exclude them from the count of anchor
// channels in order to attribute the respective anchor amount
// to the channel capacity.
if req.FundUpToMaxAmt > 0 && req.MinFundAmt > 0 {
numAnchorChans, err = l.CurrentNumAnchorChans()
if err != nil {
req.err <- err
req.resp <- nil
return
}
isPublic := req.Flags&lnwire.FFAnnounceChannel != 0
if hasAnchors && isPublic {
numAnchorChans++
}
}
// Coin selection is done on the basis of sat/kw, so we'll use
// the fee rate passed in to perform coin selection.
fundingReq := &chanfunding.Request{
RemoteAmt: req.RemoteFundingAmt,
LocalAmt: req.LocalFundingAmt,
FundUpToMaxAmt: req.FundUpToMaxAmt,
MinFundAmt: req.MinFundAmt,
RemoteChanReserve: req.RemoteChanReserve,
PushAmt: lnwire.MilliSatoshi.ToSatoshis(
req.PushMSat,
),
WalletReserve: l.RequiredReserve(
uint32(numAnchorChans),
),
Outpoints: req.Outpoints,
MinConfs: req.MinConfs,
SubtractFees: req.SubtractFees,
FeeRate: req.FundingFeePerKw,
ChangeAddr: func() (btcutil.Address, error) {
return l.NewAddress(
TaprootPubkey, true, DefaultAccountName,
)
},
Musig2: req.CommitType.IsTaproot(),
}
fundingIntent, err = req.ChanFunder.ProvisionChannel(
fundingReq,
)
if err != nil {
req.err <- err
req.resp <- nil
return
}
// Register the funding intent now in case we need to access it
// again later, as it's the case for the PSBT state machine for
// example.
err = l.RegisterFundingIntent(req.PendingChanID, fundingIntent)
if err != nil {
req.err <- err
req.resp <- nil
return
}
walletLog.Debugf("Registered funding intent for "+
"PendingChanID: %x", req.PendingChanID)
localFundingAmt = fundingIntent.LocalFundingAmt()
remoteFundingAmt = fundingIntent.RemoteFundingAmt()
}
// At this point there _has_ to be a funding intent, otherwise something
// went really wrong.
if fundingIntent == nil {
req.err <- fmt.Errorf("no funding intent present")
req.resp <- nil
return
}
// If this is a shim intent, then it may be attempting to use an
// existing set of keys for the funding workflow. In this case, we'll
// make a simple wrapper keychain.KeyRing that will proxy certain
// derivation calls to future callers.
var (
keyRing keychain.KeyRing = l.SecretKeyRing
thawHeight uint32
)
if shimIntent, ok := fundingIntent.(*chanfunding.ShimIntent); ok {
keyRing = &shimKeyRing{
KeyRing: keyRing,
ShimIntent: shimIntent,
}
// As this was a registered shim intent, we'll obtain the thaw
// height of the intent, if present at all. If this is
// non-zero, then we'll mark this as the proper channel type.
thawHeight = shimIntent.ThawHeight()
}
// Now that we have a funding intent, we'll check whether funding a
// channel using it would violate our reserved value for anchor channel
// fee bumping.
//
// Check the reserved value using the inputs and outputs given by the
// intent. Note that for the PSBT intent type we don't yet have the
// funding tx ready, so this will always pass. We'll do another check
// when the PSBT has been verified.
isPublic := req.Flags&lnwire.FFAnnounceChannel != 0
if enforceNewReservedValue {
err = l.enforceNewReservedValue(fundingIntent, isPublic, hasAnchors)
if err != nil {
fundingIntent.Cancel()
req.err <- err
req.resp <- nil
return
}
}
// The total channel capacity will be the size of the funding output we
// created plus the remote contribution.
capacity := localFundingAmt + remoteFundingAmt
id := atomic.AddUint64(&l.nextFundingID, 1)
reservation, err := NewChannelReservation(
capacity, localFundingAmt, l, id, l.Cfg.NetParams.GenesisHash,
thawHeight, req,
)
if err != nil {
fundingIntent.Cancel()
req.err <- err
req.resp <- nil
return
}
err = l.initOurContribution(
reservation, fundingIntent, req.NodeAddr, req.NodeID, keyRing,
)
if err != nil {
fundingIntent.Cancel()
req.err <- err
req.resp <- nil
return
}
// Create a limbo and record entry for this newly pending funding
// request.
l.limboMtx.Lock()
l.fundingLimbo[id] = reservation
l.reservationIDs[req.PendingChanID] = id
l.limboMtx.Unlock()
// Funding reservation request successfully handled. The funding inputs
// will be marked as unavailable until the reservation is either
// completed, or canceled.
req.resp <- reservation
req.err <- nil
walletLog.Debugf("Successfully handled funding reservation with "+
"pendingChanID: %x, reservationID: %v",
reservation.pendingChanID, reservation.reservationID)
}
// enforceReservedValue enforces that the wallet, upon a new channel being
// opened, meets the minimum amount of funds required for each advertised anchor
// channel.
//
// We only enforce the reserve if we are contributing funds to the channel. This
// is done to still allow incoming channels even though we have no UTXOs
// available, as in bootstrapping phases.
func (l *LightningWallet) enforceNewReservedValue(fundingIntent chanfunding.Intent,
isPublic, hasAnchors bool) error {
// Only enforce the reserve when an advertised channel is being opened
// in which we are contributing funds to. This ensures we never dip
// below the reserve.
if !isPublic || fundingIntent.LocalFundingAmt() == 0 {
return nil
}
numAnchors, err := l.CurrentNumAnchorChans()
if err != nil {
return err
}
// Add the to-be-opened channel.
if hasAnchors {
numAnchors++
}
return l.WithCoinSelectLock(func() error {
_, err := l.CheckReservedValue(
fundingIntent.Inputs(), fundingIntent.Outputs(),
numAnchors,
)
return err
})
}
// CurrentNumAnchorChans returns the current number of non-private anchor
// channels the wallet should be ready to fee bump if needed.
func (l *LightningWallet) CurrentNumAnchorChans() (int, error) {
// Count all anchor channels that are open or pending
// open, or waiting close.
chans, err := l.Cfg.Database.FetchAllChannels()
if err != nil {
return 0, err
}
var numAnchors int
cntChannel := func(c *channeldb.OpenChannel) {
// We skip private channels, as we assume they won't be used
// for routing.
if c.ChannelFlags&lnwire.FFAnnounceChannel == 0 {
return
}
// Count anchor channels.
if c.ChanType.HasAnchors() {
numAnchors++
}
}
for _, c := range chans {
cntChannel(c)
}
// We also count pending close channels.
pendingClosed, err := l.Cfg.Database.FetchClosedChannels(
true,
)
if err != nil {
return 0, err
}
for _, c := range pendingClosed {
c, err := l.Cfg.Database.FetchHistoricalChannel(
&c.ChanPoint,
)
if err != nil {
// We don't have a guarantee that all channels re found
// in the historical channels bucket, so we continue.
walletLog.Warnf("Unable to fetch historical "+
"channel: %v", err)
continue
}
cntChannel(c)
}
return numAnchors, nil
}
// CheckReservedValue checks whether publishing a transaction with the given
// inputs and outputs would violate the value we reserve in the wallet for
// bumping the fee of anchor channels. The numAnchorChans argument should be
// set the number of open anchor channels controlled by the wallet after
// the transaction has been published.
//
// If the reserved value is violated, the returned error will be
// ErrReservedValueInvalidated. The method will also return the current
// reserved value, both in case of success and in case of
// ErrReservedValueInvalidated.
//
// NOTE: This method should only be run with the CoinSelectLock held.
func (l *LightningWallet) CheckReservedValue(in []wire.OutPoint,
out []*wire.TxOut, numAnchorChans int) (btcutil.Amount, error) {
// Get all unspent coins in the wallet. We only care about those part of
// the wallet's default account as we know we can readily sign for those
// at any time.
witnessOutputs, err := l.ListUnspentWitnessFromDefaultAccount(
0, math.MaxInt32,
)
if err != nil {
return 0, err
}
ourInput := make(map[wire.OutPoint]struct{})
for _, op := range in {
ourInput[op] = struct{}{}
}
// When crafting a transaction with inputs from the wallet, these coins
// will usually be locked in the process, and not be returned when
// listing unspents. In this case they have already been deducted from
// the wallet balance. In case they haven't been properly locked, we
// check whether they are still listed among our unspents and deduct
// them.
var walletBalance btcutil.Amount
for _, in := range witnessOutputs {
// Spending an unlocked wallet UTXO, don't add it to the
// balance.
if _, ok := ourInput[in.OutPoint]; ok {
continue
}
walletBalance += in.Value
}
// Now we go through the outputs of the transaction, if any of the
// outputs are paying into the wallet (likely a change output), we add
// it to our final balance.
for _, txOut := range out {
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
txOut.PkScript, &l.Cfg.NetParams,
)
if err != nil {
// Non-standard outputs can safely be skipped because
// they're not supported by the wallet.
continue
}
for _, addr := range addrs {
if !l.IsOurAddress(addr) {
continue
}
walletBalance += btcutil.Amount(txOut.Value)
// We break since we don't want to double count the output.
break
}
}
// We reserve a given amount for each anchor channel.
reserved := l.RequiredReserve(uint32(numAnchorChans))
if walletBalance < reserved {
walletLog.Debugf("Reserved value=%v above final "+
"walletbalance=%v with %d anchor channels open",
reserved, walletBalance, numAnchorChans)
return reserved, ErrReservedValueInvalidated
}
return reserved, nil
}
// CheckReservedValueTx calls CheckReservedValue with the inputs and outputs
// from the given tx, with the number of anchor channels currently open in the
// database.
//
// NOTE: This method should only be run with the CoinSelectLock held.
func (l *LightningWallet) CheckReservedValueTx(req CheckReservedValueTxReq) (
btcutil.Amount, error) {
numAnchors, err := l.CurrentNumAnchorChans()
if err != nil {
return 0, err
}
var inputs []wire.OutPoint
for _, txIn := range req.Tx.TxIn {
inputs = append(inputs, txIn.PreviousOutPoint)
}
reservedVal, err := l.CheckReservedValue(
inputs, req.Tx.TxOut, numAnchors,
)
switch {
// If the error returned from CheckReservedValue is
// ErrReservedValueInvalidated, then it did nonetheless return
// the required reserved value and we check for the optional
// change index.
case errors.Is(err, ErrReservedValueInvalidated):
// Without a change index provided there is nothing more to
// check and the error is returned.
if req.ChangeIndex == nil {
return reservedVal, err
}
// If a change index was provided we make only sure that it
// would leave sufficient funds for the reserved balance value.
//
// Note: This is used if a change output index is explicitly set
// but that may not be watched by the wallet and therefore is
// not picked up by the call to CheckReservedValue above.
chIdx := *req.ChangeIndex
if chIdx < 0 || chIdx >= len(req.Tx.TxOut) ||
req.Tx.TxOut[chIdx].Value < int64(reservedVal) {
return reservedVal, err
}
case err != nil:
return reservedVal, err
}
return reservedVal, nil
}
// initOurContribution initializes the given ChannelReservation with our coins
// and change reserved for the channel, and derives the keys to use for this
// channel.
func (l *LightningWallet) initOurContribution(reservation *ChannelReservation,
fundingIntent chanfunding.Intent, nodeAddr net.Addr,
nodeID *btcec.PublicKey, keyRing keychain.KeyRing) error {
// Grab the mutex on the ChannelReservation to ensure thread-safety
reservation.Lock()
defer reservation.Unlock()
// At this point, if we have a funding intent, we'll use it to populate
// the existing reservation state entries for our coin selection.
if fundingIntent != nil {
if intent, ok := fundingIntent.(*chanfunding.FullIntent); ok {
for _, coin := range intent.InputCoins {
reservation.ourContribution.Inputs = append(
reservation.ourContribution.Inputs,
&wire.TxIn{
PreviousOutPoint: coin.OutPoint,
},
)
}
reservation.ourContribution.ChangeOutputs = intent.ChangeOutputs
}
reservation.fundingIntent = fundingIntent
}
reservation.nodeAddr = nodeAddr
reservation.partialState.IdentityPub = nodeID
var err error
reservation.ourContribution.MultiSigKey, err = keyRing.DeriveNextKey(
keychain.KeyFamilyMultiSig,
)
if err != nil {
return err
}
reservation.ourContribution.RevocationBasePoint, err = keyRing.DeriveNextKey(
keychain.KeyFamilyRevocationBase,
)
if err != nil {
return err
}
reservation.ourContribution.HtlcBasePoint, err = keyRing.DeriveNextKey(
keychain.KeyFamilyHtlcBase,
)
if err != nil {
return err
}
reservation.ourContribution.PaymentBasePoint, err = keyRing.DeriveNextKey(
keychain.KeyFamilyPaymentBase,
)
if err != nil {
return err
}
reservation.ourContribution.DelayBasePoint, err = keyRing.DeriveNextKey(
keychain.KeyFamilyDelayBase,
)
if err != nil {
return err
}
// With the above keys created, we'll also need to initialize our
// revocation tree state, and from that generate the per-commitment
// point.
producer, taprootNonceProducer, err := l.nextRevocationProducer(
reservation, keyRing,
)
if err != nil {
return err
}
firstPreimage, err := producer.AtIndex(0)
if err != nil {
return err
}
reservation.ourContribution.FirstCommitmentPoint = input.ComputeCommitmentPoint(
firstPreimage[:],
)
reservation.partialState.RevocationProducer = producer
reservation.ourContribution.CommitmentParams.DustLimit =
DustLimitUnknownWitness()
// If taproot channels are active, then we'll generate our verification
// nonce here. We'll use this nonce to verify the signature for our
// local commitment transaction. If we need to force close, then this
// is also what'll be used to sign that transaction.
if reservation.partialState.ChanType.IsTaproot() {
firstNoncePreimage, err := taprootNonceProducer.AtIndex(0)
if err != nil {
return err
}
// As we'd like the local nonce we send over to be generated
// deterministically, we'll provide a custom reader that
// actually just uses our sha-chain pre-image as the primary
// randomness source.
shaChainRand := musig2.WithCustomRand(
bytes.NewBuffer(firstNoncePreimage[:]),
)
pubKeyOpt := musig2.WithPublicKey(
reservation.ourContribution.MultiSigKey.PubKey,
)
reservation.ourContribution.LocalNonce, err = musig2.GenNonces(
pubKeyOpt, shaChainRand,
)
if err != nil {
return err
}
}
return nil
}
// handleFundingReserveCancel cancels an existing channel reservation. As part
// of the cancellation, outputs previously selected as inputs for the funding
// transaction via coin selection are freed allowing future reservations to
// include them.
func (l *LightningWallet) handleFundingCancelRequest(req *fundingReserveCancelMsg) {
l.limboMtx.Lock()
defer l.limboMtx.Unlock()
pendingReservation, ok := l.fundingLimbo[req.pendingFundingID]
if !ok {
// TODO(roasbeef): make new error, "unknown funding state" or something
req.err <- fmt.Errorf("attempted to cancel non-existent funding state")
return
}
// Grab the mutex on the ChannelReservation to ensure thread-safety
pendingReservation.Lock()
defer pendingReservation.Unlock()
// Mark all previously locked outpoints as usable for future funding
// requests.
for _, unusedInput := range pendingReservation.ourContribution.Inputs {
delete(l.lockedOutPoints, unusedInput.PreviousOutPoint)
_ = l.ReleaseOutput(
chanfunding.LndInternalLockID,
unusedInput.PreviousOutPoint,
)
}
delete(l.fundingLimbo, req.pendingFundingID)
pid := pendingReservation.pendingChanID
delete(l.reservationIDs, pid)
l.intentMtx.Lock()
if intent, ok := l.fundingIntents[pid]; ok {
intent.Cancel()
delete(l.fundingIntents, pendingReservation.pendingChanID)
}
l.intentMtx.Unlock()
req.err <- nil
}
// createCommitOpts is a struct that holds the options for creating a new
// commitment transaction.
type createCommitOpts struct {
localAuxLeaves fn.Option[CommitAuxLeaves]
remoteAuxLeaves fn.Option[CommitAuxLeaves]
}
// defaultCommitOpts returns a new createCommitOpts with default values.
func defaultCommitOpts() createCommitOpts {
return createCommitOpts{}
}
// WithAuxLeaves is a functional option that can be used to set the aux leaves
// for a new commitment transaction.
func WithAuxLeaves(localLeaves,
remoteLeaves fn.Option[CommitAuxLeaves]) CreateCommitOpt {
return func(o *createCommitOpts) {
o.localAuxLeaves = localLeaves
o.remoteAuxLeaves = remoteLeaves
}
}
// CreateCommitOpt is a functional option that can be used to modify the way a
// new commitment transaction is created.
type CreateCommitOpt func(*createCommitOpts)
// CreateCommitmentTxns is a helper function that creates the initial
// commitment transaction for both parties. This function is used during the
// initial funding workflow as both sides must generate a signature for the
// remote party's commitment transaction, and verify the signature for their
// version of the commitment transaction.
func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount,
ourChanCfg, theirChanCfg *channeldb.ChannelConfig,
localCommitPoint, remoteCommitPoint *btcec.PublicKey,
fundingTxIn wire.TxIn, chanType channeldb.ChannelType, initiator bool,
leaseExpiry uint32, opts ...CreateCommitOpt) (*wire.MsgTx, *wire.MsgTx,
error) {
options := defaultCommitOpts()
for _, optFunc := range opts {
optFunc(&options)
}
localCommitmentKeys := DeriveCommitmentKeys(
localCommitPoint, lntypes.Local, chanType, ourChanCfg,
theirChanCfg,
)
remoteCommitmentKeys := DeriveCommitmentKeys(
remoteCommitPoint, lntypes.Remote, chanType, ourChanCfg,
theirChanCfg,
)
ourCommitTx, err := CreateCommitTx(
chanType, fundingTxIn, localCommitmentKeys, ourChanCfg,
theirChanCfg, localBalance, remoteBalance, 0, initiator,
leaseExpiry, options.localAuxLeaves,
)
if err != nil {
return nil, nil, err
}
otxn := btcutil.NewTx(ourCommitTx)
if err := blockchain.CheckTransactionSanity(otxn); err != nil {
return nil, nil, err
}
theirCommitTx, err := CreateCommitTx(
chanType, fundingTxIn, remoteCommitmentKeys, theirChanCfg,
ourChanCfg, remoteBalance, localBalance, 0, !initiator,
leaseExpiry, options.remoteAuxLeaves,
)
if err != nil {
return nil, nil, err
}
ttxn := btcutil.NewTx(theirCommitTx)
if err := blockchain.CheckTransactionSanity(ttxn); err != nil {
return nil, nil, err
}
return ourCommitTx, theirCommitTx, nil
}
// handleContributionMsg processes the second workflow step for the lifetime of
// a channel reservation. Upon completion, the reservation will carry a
// completed funding transaction (minus the counterparty's input signatures),
// both versions of the commitment transaction, and our signature for their
// version of the commitment transaction.
func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
l.limboMtx.Lock()
pendingReservation, ok := l.fundingLimbo[req.pendingFundingID]
l.limboMtx.Unlock()
if !ok {
req.err <- fmt.Errorf("attempted to update non-existent funding state")
return
}
// Grab the mutex on the ChannelReservation to ensure thread-safety
pendingReservation.Lock()
defer pendingReservation.Unlock()
// If UpfrontShutdownScript is set, validate that it is a valid script.
shutdown := req.contribution.UpfrontShutdown
if len(shutdown) > 0 {
// Validate the shutdown script.
if !ValidateUpfrontShutdown(shutdown, &l.Cfg.NetParams) {
req.err <- fmt.Errorf("invalid shutdown script")
return
}
}
// Some temporary variables to cut down on the resolution verbosity.
pendingReservation.theirContribution = req.contribution
theirContribution := req.contribution
ourContribution := pendingReservation.ourContribution
// Perform bounds-checking on both ChannelReserve and DustLimit
// parameters.
if !pendingReservation.validateReserveBounds() {
req.err <- fmt.Errorf("invalid reserve and dust bounds")
return
}
var (
chanPoint *wire.OutPoint
err error
)
// At this point, we can now construct our channel point. Depending on
// which type of intent we obtained from our chanfunding.Assembler,
// we'll carry out a distinct set of steps.
switch fundingIntent := pendingReservation.fundingIntent.(type) {
// The transaction was created outside of the wallet and might already
// be published. Nothing left to do other than using the correct
// outpoint.
case *chanfunding.ShimIntent:
chanPoint, err = fundingIntent.ChanPoint()
if err != nil {
req.err <- fmt.Errorf("unable to obtain chan point: %w",
err)
return
}
pendingReservation.partialState.FundingOutpoint = *chanPoint
// The user has signaled that they want to use a PSBT to construct the
// funding transaction. Because we now have the multisig keys from both
// parties, we can create the multisig script that needs to be funded
// and then pause the process until the user supplies the PSBT
// containing the eventual funding transaction.
case *chanfunding.PsbtIntent:
if fundingIntent.PendingPsbt != nil {
req.err <- fmt.Errorf("PSBT funding already in" +
"progress")
return
}
// Now that we know our contribution, we can bind both the local
// and remote key which will be needed to calculate the multisig
// funding output in a next step.
pendingChanID := pendingReservation.pendingChanID
walletLog.Debugf("Advancing PSBT funding flow for "+
"pending_id(%x), binding keys local_key=%v, "+
"remote_key=%x", pendingChanID,
&ourContribution.MultiSigKey,
theirContribution.MultiSigKey.PubKey.SerializeCompressed())
fundingIntent.BindKeys(
&ourContribution.MultiSigKey,
theirContribution.MultiSigKey.PubKey,
)
// We might have a tapscript root, so we'll bind that now to
// ensure we make the proper funding output.
fundingIntent.BindTapscriptRoot(
pendingReservation.partialState.TapscriptRoot,
)
// Exit early because we can't continue the funding flow yet.
req.err <- &PsbtFundingRequired{
Intent: fundingIntent,
}
return
case *chanfunding.FullIntent:
// Now that we know their public key, we can bind theirs as
// well as ours to the funding intent.
fundingIntent.BindKeys(
&pendingReservation.ourContribution.MultiSigKey,
theirContribution.MultiSigKey.PubKey,
)
// With our keys bound, we can now construct+sign the final
// funding transaction and also obtain the chanPoint that
// creates the channel.
fundingTx, err := fundingIntent.CompileFundingTx(
theirContribution.Inputs,
theirContribution.ChangeOutputs,
)
if err != nil {
req.err <- fmt.Errorf("unable to construct funding "+
"tx: %v", err)
return
}
chanPoint, err = fundingIntent.ChanPoint()
if err != nil {
req.err <- fmt.Errorf("unable to obtain chan "+
"point: %v", err)
return
}
// Finally, we'll populate the relevant information in our
// pendingReservation so the rest of the funding flow can
// continue as normal.
pendingReservation.fundingTx = fundingTx
pendingReservation.partialState.FundingOutpoint = *chanPoint
pendingReservation.ourFundingInputScripts = make(
[]*input.Script, 0, len(ourContribution.Inputs),
)
for _, txIn := range fundingTx.TxIn {
_, err := l.FetchOutpointInfo(&txIn.PreviousOutPoint)
if err != nil {
continue
}
pendingReservation.ourFundingInputScripts = append(
pendingReservation.ourFundingInputScripts,
&input.Script{
Witness: txIn.Witness,
SigScript: txIn.SignatureScript,
},
)
}
walletLog.Tracef("Funding tx for ChannelPoint(%v) "+
"generated: %v", chanPoint, spew.Sdump(fundingTx))
}
// If we landed here and didn't exit early, it means we already have
// the channel point ready. We can jump directly to the next step.
l.handleChanPointReady(&continueContributionMsg{
pendingFundingID: req.pendingFundingID,
err: req.err,
})
}
// genMusigSession generates a new musig2 pair session that we can use to sign
// the commitment transaction for the remote party, and verify their incoming
// partial signature.
func genMusigSession(ourContribution, theirContribution *ChannelContribution,
signer input.MuSig2Signer, fundingOutput *wire.TxOut,
tapscriptRoot fn.Option[chainhash.Hash]) *MusigPairSession {
return NewMusigPairSession(&MusigSessionCfg{
LocalKey: ourContribution.MultiSigKey,
RemoteKey: theirContribution.MultiSigKey,
LocalNonce: *ourContribution.LocalNonce,
RemoteNonce: *theirContribution.LocalNonce,
Signer: signer,
InputTxOut: fundingOutput,
TapscriptTweak: tapscriptRoot,
})
}
// signCommitTx generates a valid input.Signature to send to the remote party
// for their version of the commitment transaction. For regular channels, this
// will be a normal ECDSA signature. For taproot channels, this will instead be
// a musig2 partial signature that also includes the nonce used to generate it.
func (l *LightningWallet) signCommitTx(pendingReservation *ChannelReservation,
commitTx *wire.MsgTx, fundingOutput *wire.TxOut,
fundingWitnessScript []byte) (input.Signature, error) {
ourContribution := pendingReservation.ourContribution
theirContribution := pendingReservation.theirContribution
var (
sigTheirCommit input.Signature
err error
)
switch {
// For regular channels, we can just send over a normal ECDSA signature
// w/o any extra steps.
case !pendingReservation.partialState.ChanType.IsTaproot():
ourKey := ourContribution.MultiSigKey
signDesc := input.SignDescriptor{
WitnessScript: fundingWitnessScript,
KeyDesc: ourKey,
Output: fundingOutput,
HashType: txscript.SigHashAll,
SigHashes: input.NewTxSigHashesV0Only(
commitTx,
),
InputIndex: 0,
}
sigTheirCommit, err = l.Cfg.Signer.SignOutputRaw(
commitTx, &signDesc,
)
if err != nil {
return nil, err
}
// If this is a taproot channel, then we'll need to create an initial
// musig2 session here as we'll be sending over a _partial_ signature.
default:
// We're now ready to sign the first commitment. However, we'll
// only create the session if that hasn't been done already.
if pendingReservation.musigSessions == nil {
musigSessions := genMusigSession(
ourContribution, theirContribution,
l.Cfg.Signer, fundingOutput,
pendingReservation.partialState.TapscriptRoot,
)
pendingReservation.musigSessions = musigSessions
}
// Now that we have the funding outpoint, we'll generate a
// musig2 signature for their version of the commitment
// transaction. We use the remote session as this is for the
// remote commitment transaction.
musigSessions := pendingReservation.musigSessions
partialSig, err := musigSessions.RemoteSession.SignCommit(
commitTx,
)
if err != nil {
return nil, fmt.Errorf("unable to sign "+
"commitment: %w", err)
}
sigTheirCommit = partialSig
}
return sigTheirCommit, nil
}
// handleChanPointReady continues the funding process once the channel point is
// known and the funding transaction can be completed.
func (l *LightningWallet) handleChanPointReady(req *continueContributionMsg) {
l.limboMtx.Lock()
pendingReservation, ok := l.fundingLimbo[req.pendingFundingID]
l.limboMtx.Unlock()
if !ok {
req.err <- fmt.Errorf("attempted to update non-existent " +
"funding state")
return
}
chanState := pendingReservation.partialState
// If we have an aux funding desc, then we can use it to populate some
// of the optional, but opaque TLV blobs we'll carry for the channel.
chanState.CustomBlob = fn.MapOption(func(desc AuxFundingDesc) tlv.Blob {
return desc.CustomFundingBlob
})(req.auxFundingDesc)
chanState.LocalCommitment.CustomBlob = fn.MapOption(
func(desc AuxFundingDesc) tlv.Blob {
return desc.CustomLocalCommitBlob
},
)(req.auxFundingDesc)
chanState.RemoteCommitment.CustomBlob = fn.MapOption(
func(desc AuxFundingDesc) tlv.Blob {
return desc.CustomRemoteCommitBlob
},
)(req.auxFundingDesc)
ourContribution := pendingReservation.ourContribution
theirContribution := pendingReservation.theirContribution
chanPoint := pendingReservation.partialState.FundingOutpoint
// If we're in the PSBT funding flow, we now should have everything that
// is needed to construct and publish the full funding transaction.
intent := pendingReservation.fundingIntent
if psbtIntent, ok := intent.(*chanfunding.PsbtIntent); ok {
// With our keys bound, we can now construct and possibly sign
// the final funding transaction and also obtain the chanPoint
// that creates the channel. We _have_ to call CompileFundingTx
// even if we don't publish ourselves as that sets the actual
// funding outpoint in stone for this channel.
fundingTx, err := psbtIntent.CompileFundingTx()
if err != nil {
req.err <- fmt.Errorf("unable to construct funding "+
"tx: %v", err)
return
}
chanPointPtr, err := psbtIntent.ChanPoint()
if err != nil {
req.err <- fmt.Errorf("unable to obtain chan "+
"point: %v", err)
return
}
pendingReservation.partialState.FundingOutpoint = *chanPointPtr
chanPoint = *chanPointPtr
// Finally, we'll populate the relevant information in our
// pendingReservation so the rest of the funding flow can
// continue as normal in case we are going to publish ourselves.
if psbtIntent.ShouldPublishFundingTX() {
pendingReservation.fundingTx = fundingTx
pendingReservation.ourFundingInputScripts = make(
[]*input.Script, 0, len(ourContribution.Inputs),
)
for _, txIn := range fundingTx.TxIn {
pendingReservation.ourFundingInputScripts = append(
pendingReservation.ourFundingInputScripts,
&input.Script{
Witness: txIn.Witness,
SigScript: txIn.SignatureScript,
},
)
}
}
}
// Initialize an empty sha-chain for them, tracking the current pending
// revocation hash (we don't yet know the preimage so we can't add it
// to the chain).
s := shachain.NewRevocationStore()
pendingReservation.partialState.RevocationStore = s
// Store their current commitment point. We'll need this after the
// first state transition in order to verify the authenticity of the
// revocation.
chanState.RemoteCurrentRevocation = theirContribution.FirstCommitmentPoint
// Create the txin to our commitment transaction; required to construct
// the commitment transactions.
fundingTxIn := wire.TxIn{
PreviousOutPoint: chanPoint,
}
// With the funding tx complete, create both commitment transactions.
localBalance := pendingReservation.partialState.LocalCommitment.LocalBalance.ToSatoshis()
remoteBalance := pendingReservation.partialState.LocalCommitment.RemoteBalance.ToSatoshis()
var leaseExpiry uint32
if pendingReservation.partialState.ChanType.HasLeaseExpiration() {
leaseExpiry = pendingReservation.partialState.ThawHeight
}
localAuxLeaves := fn.MapOption(
func(desc AuxFundingDesc) CommitAuxLeaves {
return desc.LocalInitAuxLeaves
},
)(req.auxFundingDesc)
remoteAuxLeaves := fn.MapOption(
func(desc AuxFundingDesc) CommitAuxLeaves {
return desc.RemoteInitAuxLeaves
},
)(req.auxFundingDesc)
ourCommitTx, theirCommitTx, err := CreateCommitmentTxns(
localBalance, remoteBalance, ourContribution.ChannelConfig,
theirContribution.ChannelConfig,
ourContribution.FirstCommitmentPoint,
theirContribution.FirstCommitmentPoint, fundingTxIn,
pendingReservation.partialState.ChanType,
pendingReservation.partialState.IsInitiator, leaseExpiry,
WithAuxLeaves(localAuxLeaves, remoteAuxLeaves),
)
if err != nil {
req.err <- err
return
}
// With both commitment transactions constructed, generate the state
// obfuscator then use it to encode the current state number within
// both commitment transactions.
var stateObfuscator [StateHintSize]byte
if chanState.ChanType.IsSingleFunder() {
stateObfuscator = DeriveStateHintObfuscator(
ourContribution.PaymentBasePoint.PubKey,
theirContribution.PaymentBasePoint.PubKey,
)
} else {
ourSer := ourContribution.PaymentBasePoint.PubKey.SerializeCompressed()
theirSer := theirContribution.PaymentBasePoint.PubKey.SerializeCompressed()
switch bytes.Compare(ourSer, theirSer) {
case -1:
stateObfuscator = DeriveStateHintObfuscator(
ourContribution.PaymentBasePoint.PubKey,
theirContribution.PaymentBasePoint.PubKey,
)
default:
stateObfuscator = DeriveStateHintObfuscator(
theirContribution.PaymentBasePoint.PubKey,
ourContribution.PaymentBasePoint.PubKey,
)
}
}
err = initStateHints(ourCommitTx, theirCommitTx, stateObfuscator)
if err != nil {
req.err <- err
return
}
// Sort both transactions according to the agreed upon canonical
// ordering. This lets us skip sending the entire transaction over,
// instead we'll just send signatures.
txsort.InPlaceSort(ourCommitTx)
txsort.InPlaceSort(theirCommitTx)
walletLog.Tracef("Local commit tx for ChannelPoint(%v): %v",
chanPoint, spew.Sdump(ourCommitTx))
walletLog.Tracef("Remote commit tx for ChannelPoint(%v): %v",
chanPoint, spew.Sdump(theirCommitTx))
// Record newly available information within the open channel state.
chanState.FundingOutpoint = chanPoint
chanState.LocalCommitment.CommitTx = ourCommitTx
chanState.RemoteCommitment.CommitTx = theirCommitTx
// Next, we'll obtain the funding witness script, and the funding
// output itself so we can generate a valid signature for the remote
// party.
fundingIntent := pendingReservation.fundingIntent
fundingWitnessScript, fundingOutput, err := fundingIntent.FundingOutput()
if err != nil {
req.err <- fmt.Errorf("unable to obtain funding "+
"output: %w", err)
return
}
// Generate a signature for their version of the initial commitment
// transaction.
sigTheirCommit, err := l.signCommitTx(
pendingReservation, theirCommitTx, fundingOutput,
fundingWitnessScript,
)
if err != nil {
req.err <- err
return
}
pendingReservation.ourCommitmentSig = sigTheirCommit
req.err <- nil
}
// handleSingleContribution is called as the second step to a single funder
// workflow to which we are the responder. It simply saves the remote peer's
// contribution to the channel, as solely the remote peer will contribute any
// funds to the channel.
func (l *LightningWallet) handleSingleContribution(req *addSingleContributionMsg) {
l.limboMtx.Lock()
pendingReservation, ok := l.fundingLimbo[req.pendingFundingID]
l.limboMtx.Unlock()
if !ok {
req.err <- fmt.Errorf("attempted to update non-existent funding state")
return
}
// Grab the mutex on the channelReservation to ensure thread-safety.
pendingReservation.Lock()
defer pendingReservation.Unlock()
// Validate that the remote's UpfrontShutdownScript is a valid script
// if it's set.
shutdown := req.contribution.UpfrontShutdown
if len(shutdown) > 0 {
// Validate the shutdown script.
if !ValidateUpfrontShutdown(shutdown, &l.Cfg.NetParams) {
req.err <- fmt.Errorf("invalid shutdown script")
return
}
}
// Simply record the counterparty's contribution into the pending
// reservation data as they'll be solely funding the channel entirely.
pendingReservation.theirContribution = req.contribution
theirContribution := pendingReservation.theirContribution
chanState := pendingReservation.partialState
// Perform bounds checking on both ChannelReserve and DustLimit
// parameters. The ChannelReserve may have been changed by the
// ChannelAcceptor RPC, so this is necessary.
if !pendingReservation.validateReserveBounds() {
req.err <- fmt.Errorf("invalid reserve and dust bounds")
return
}
// Initialize an empty sha-chain for them, tracking the current pending
// revocation hash (we don't yet know the preimage so we can't add it
// to the chain).
remotePreimageStore := shachain.NewRevocationStore()
chanState.RevocationStore = remotePreimageStore
// Now that we've received their first commitment point, we'll store it
// within the channel state so we can sync it to disk once the funding
// process is complete.
chanState.RemoteCurrentRevocation = theirContribution.FirstCommitmentPoint
req.err <- nil
}
// verifyFundingInputs attempts to verify all remote inputs to the funding
// transaction.
func (l *LightningWallet) verifyFundingInputs(fundingTx *wire.MsgTx,
remoteInputScripts []*input.Script) error {
sigIndex := 0
fundingHashCache := input.NewTxSigHashesV0Only(fundingTx)
inputScripts := remoteInputScripts
for i, txin := range fundingTx.TxIn {
if len(inputScripts) != 0 && len(txin.Witness) == 0 {
// Attach the input scripts so we can verify it below.
txin.Witness = inputScripts[sigIndex].Witness
txin.SignatureScript = inputScripts[sigIndex].SigScript
// Fetch the alleged previous output along with the
// pkscript referenced by this input.
//
// TODO(roasbeef): when dual funder pass actual
// height-hint
//
// TODO(roasbeef): this fails for neutrino always as it
// treats the height hint as an exact birthday of the
// utxo rather than a lower bound
pkScript, err := txscript.ComputePkScript(
txin.SignatureScript, txin.Witness,
)
if err != nil {
return fmt.Errorf("cannot create script: %w",
err)
}
output, err := l.Cfg.ChainIO.GetUtxo(
&txin.PreviousOutPoint,
pkScript.Script(), 0, l.quit,
)
if output == nil {
return fmt.Errorf("input to funding tx does "+
"not exist: %v", err)
}
// Ensure that the witness+sigScript combo is valid.
vm, err := txscript.NewEngine(
output.PkScript, fundingTx, i,
txscript.StandardVerifyFlags, nil,
fundingHashCache, output.Value,
txscript.NewCannedPrevOutputFetcher(
output.PkScript, output.Value,
),
)
if err != nil {
return fmt.Errorf("cannot create script "+
"engine: %s", err)
}
if err = vm.Execute(); err != nil {
return fmt.Errorf("cannot validate "+
"transaction: %s", err)
}
sigIndex++
}
}
return nil
}
// verifyCommitSig verifies an incoming signature for our version of the
// commitment transaction. For normal channels, this will verify that the ECDSA
// signature is valid. For taproot channels, we'll verify that their partial
// signature is valid, so it can properly be combined with our eventual
// signature when we need to broadcast.
func (l *LightningWallet) verifyCommitSig(res *ChannelReservation,
commitSig input.Signature, commitTx *wire.MsgTx) error {
localKey := res.ourContribution.MultiSigKey.PubKey
remoteKey := res.theirContribution.MultiSigKey.PubKey
channelValue := int64(res.partialState.Capacity)
switch {
// If this isn't a taproot channel, then we'll construct a segwit v0
// p2wsh sighash.
case !res.partialState.ChanType.IsTaproot():
hashCache := input.NewTxSigHashesV0Only(commitTx)
witnessScript, _, err := input.GenFundingPkScript(
localKey.SerializeCompressed(),
remoteKey.SerializeCompressed(), channelValue,
)
if err != nil {
return err
}
sigHash, err := txscript.CalcWitnessSigHash(
witnessScript, hashCache, txscript.SigHashAll,
commitTx, 0, channelValue,
)
if err != nil {
return err
}
// Verify that we've received a valid signature from the remote
// party for our version of the commitment transaction.
if !commitSig.Verify(sigHash, remoteKey) {
return fmt.Errorf("counterparty's commitment " +
"signature is invalid")
}
return nil
// Otherwise for taproot channels, we'll compute the segwit v1 sighash,
// which is slightly different.
default:
// First, check to see if we've generated the musig session
// already. If we're the responder in the funding flow, we may
// not have generated it already.
if res.musigSessions == nil {
_, fundingOutput, err := input.GenTaprootFundingScript(
localKey, remoteKey, channelValue,
res.partialState.TapscriptRoot,
)
if err != nil {
return err
}
res.musigSessions = genMusigSession(
res.ourContribution, res.theirContribution,
l.Cfg.Signer, fundingOutput,
res.partialState.TapscriptRoot,
)
}
// For the musig2 based channels, we'll use the generated local
// musig2 session to verify the signature.
localSession := res.musigSessions.LocalSession
// At this point, the commitment signature passed in should
// actually be a wrapped musig2 signature, so we'll do a type
// asset to the get the signature we actually need.
partialSig, ok := commitSig.(*MusigPartialSig)
if !ok {
return fmt.Errorf("expected *musig2.PartialSignature, "+
"got: %T", commitSig)
}
_, err := localSession.VerifyCommitSig(
commitTx, partialSig.ToWireSig(),
)
return err
}
}
// handleFundingCounterPartySigs is the final step in the channel reservation
// workflow. During this step, we validate *all* the received signatures for
// inputs to the funding transaction. If any of these are invalid, we bail,
// and forcibly cancel this funding request. Additionally, we ensure that the
// signature we received from the counterparty for our version of the commitment
// transaction allows us to spend from the funding output with the addition of
// our signature.
func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigsMsg) {
l.limboMtx.RLock()
res, ok := l.fundingLimbo[msg.pendingFundingID]
l.limboMtx.RUnlock()
if !ok {
msg.err <- fmt.Errorf("attempted to update non-existent funding state")
return
}
// Grab the mutex on the ChannelReservation to ensure thread-safety
res.Lock()
defer res.Unlock()
// Now we can complete the funding transaction by adding their
// signatures to their inputs.
res.theirFundingInputScripts = msg.theirFundingInputScripts
inputScripts := msg.theirFundingInputScripts
// Only if we have the final funding transaction do we need to verify
// the final set of inputs. Otherwise, it may be the case that the
// channel was funded via an external wallet.
fundingTx := res.fundingTx
if res.partialState.ChanType.HasFundingTx() {
err := l.verifyFundingInputs(fundingTx, inputScripts)
if err != nil {
msg.err <- err
msg.completeChan <- nil
return
}
}
// At this point, we can also record and verify their signature for our
// commitment transaction.
res.theirCommitmentSig = msg.theirCommitmentSig
commitTx := res.partialState.LocalCommitment.CommitTx
err := l.verifyCommitSig(res, msg.theirCommitmentSig, commitTx)
if err != nil {
msg.err <- fmt.Errorf("counterparty's commitment signature is "+
"invalid: %w", err)
msg.completeChan <- nil
return
}
theirCommitSigBytes := msg.theirCommitmentSig.Serialize()
res.partialState.LocalCommitment.CommitSig = theirCommitSigBytes
// Funding complete, this entry can be removed from limbo.
l.limboMtx.Lock()
delete(l.fundingLimbo, res.reservationID)
delete(l.reservationIDs, res.pendingChanID)
l.limboMtx.Unlock()
l.intentMtx.Lock()
delete(l.fundingIntents, res.pendingChanID)
l.intentMtx.Unlock()
// As we're about to broadcast the funding transaction, we'll take note
// of the current height for record keeping purposes.
_, bestHeight, err := l.Cfg.ChainIO.GetBestBlock()
if err != nil {
msg.err <- err
msg.completeChan <- nil
return
}
// As we've completed the funding process, we'll no convert the
// contribution structs into their underlying channel config objects to
// he stored within the database.
res.partialState.LocalChanCfg = res.ourContribution.toChanConfig()
res.partialState.RemoteChanCfg = res.theirContribution.toChanConfig()
// We'll also record the finalized funding txn, which will allow us to
// rebroadcast on startup in case we fail.
res.partialState.FundingTxn = fundingTx
// Set optional upfront shutdown scripts on the channel state so that they
// are persisted. These values may be nil.
res.partialState.LocalShutdownScript =
res.ourContribution.UpfrontShutdown
res.partialState.RemoteShutdownScript =
res.theirContribution.UpfrontShutdown
res.partialState.RevocationKeyLocator = res.nextRevocationKeyLoc
// Add the complete funding transaction to the DB, in its open bucket
// which will be used for the lifetime of this channel.
nodeAddr := res.nodeAddr
err = res.partialState.SyncPending(nodeAddr, uint32(bestHeight))
if err != nil {
msg.err <- err
msg.completeChan <- nil
return
}
msg.completeChan <- res.partialState
msg.err <- nil
}
// handleSingleFunderSigs is called once the remote peer who initiated the
// single funder workflow has assembled the funding transaction, and generated
// a signature for our version of the commitment transaction. This method
// progresses the workflow by generating a signature for the remote peer's
// version of the commitment transaction.
func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
l.limboMtx.RLock()
pendingReservation, ok := l.fundingLimbo[req.pendingFundingID]
l.limboMtx.RUnlock()
if !ok {
req.err <- fmt.Errorf("attempted to update non-existent funding state")
req.completeChan <- nil
return
}
// Grab the mutex on the ChannelReservation to ensure thread-safety
pendingReservation.Lock()
defer pendingReservation.Unlock()
chanState := pendingReservation.partialState
// If we have an aux funding desc, then we can use it to populate some
// of the optional, but opaque TLV blobs we'll carry for the channel.
chanState.CustomBlob = fn.MapOption(func(desc AuxFundingDesc) tlv.Blob {
return desc.CustomFundingBlob
})(req.auxFundingDesc)
chanState.LocalCommitment.CustomBlob = fn.MapOption(
func(desc AuxFundingDesc) tlv.Blob {
return desc.CustomLocalCommitBlob
},
)(req.auxFundingDesc)
chanState.RemoteCommitment.CustomBlob = fn.MapOption(
func(desc AuxFundingDesc) tlv.Blob {
return desc.CustomRemoteCommitBlob
},
)(req.auxFundingDesc)
chanType := pendingReservation.partialState.ChanType
chanState.FundingOutpoint = *req.fundingOutpoint
fundingTxIn := wire.NewTxIn(req.fundingOutpoint, nil, nil)
// Now that we have the funding outpoint, we can generate both versions
// of the commitment transaction, and generate a signature for the
// remote node's commitment transactions.
localBalance := pendingReservation.partialState.LocalCommitment.LocalBalance.ToSatoshis()
remoteBalance := pendingReservation.partialState.LocalCommitment.RemoteBalance.ToSatoshis()
var leaseExpiry uint32
if pendingReservation.partialState.ChanType.HasLeaseExpiration() {
leaseExpiry = pendingReservation.partialState.ThawHeight
}
localAuxLeaves := fn.MapOption(
func(desc AuxFundingDesc) CommitAuxLeaves {
return desc.LocalInitAuxLeaves
},
)(req.auxFundingDesc)
remoteAuxLeaves := fn.MapOption(
func(desc AuxFundingDesc) CommitAuxLeaves {
return desc.RemoteInitAuxLeaves
},
)(req.auxFundingDesc)
ourCommitTx, theirCommitTx, err := CreateCommitmentTxns(
localBalance, remoteBalance,
pendingReservation.ourContribution.ChannelConfig,
pendingReservation.theirContribution.ChannelConfig,
pendingReservation.ourContribution.FirstCommitmentPoint,
pendingReservation.theirContribution.FirstCommitmentPoint,
*fundingTxIn, chanType,
pendingReservation.partialState.IsInitiator, leaseExpiry,
WithAuxLeaves(localAuxLeaves, remoteAuxLeaves),
)
if err != nil {
req.err <- err
req.completeChan <- nil
return
}
// With both commitment transactions constructed, we can now use the
// generator state obfuscator to encode the current state number within
// both commitment transactions.
stateObfuscator := DeriveStateHintObfuscator(
pendingReservation.theirContribution.PaymentBasePoint.PubKey,
pendingReservation.ourContribution.PaymentBasePoint.PubKey,
)
err = initStateHints(ourCommitTx, theirCommitTx, stateObfuscator)
if err != nil {
req.err <- err
req.completeChan <- nil
return
}
// Sort both transactions according to the agreed upon canonical
// ordering. This ensures that both parties sign the same sighash
// without further synchronization.
txsort.InPlaceSort(ourCommitTx)
txsort.InPlaceSort(theirCommitTx)
chanState.LocalCommitment.CommitTx = ourCommitTx
chanState.RemoteCommitment.CommitTx = theirCommitTx
walletLog.Debugf("Local commit tx for ChannelPoint(%v): %v",
req.fundingOutpoint, spew.Sdump(ourCommitTx))
walletLog.Debugf("Remote commit tx for ChannelPoint(%v): %v",
req.fundingOutpoint, spew.Sdump(theirCommitTx))
// With both commitment transactions created, we'll now verify their
// signature on our commitment.
err = l.verifyCommitSig(
pendingReservation, req.theirCommitmentSig, ourCommitTx,
)
if err != nil {
req.err <- err
req.completeChan <- nil
return
}
theirCommitSigBytes := req.theirCommitmentSig.Serialize()
chanState.LocalCommitment.CommitSig = theirCommitSigBytes
channelValue := int64(pendingReservation.partialState.Capacity)
theirKey := pendingReservation.theirContribution.MultiSigKey
ourKey := pendingReservation.ourContribution.MultiSigKey
var (
fundingWitnessScript []byte
fundingTxOut *wire.TxOut
)
if chanType.IsTaproot() {
//nolint:ll
fundingWitnessScript, fundingTxOut, err = input.GenTaprootFundingScript(
ourKey.PubKey, theirKey.PubKey, channelValue,
pendingReservation.partialState.TapscriptRoot,
)
} else {
//nolint:ll
fundingWitnessScript, fundingTxOut, err = input.GenFundingPkScript(
ourKey.PubKey.SerializeCompressed(),
theirKey.PubKey.SerializeCompressed(), channelValue,
)
}
if err != nil {
req.err <- err
req.completeChan <- nil
return
}
// With their signature for our version of the commitment transactions
// verified, we can now generate a signature for their version,
// allowing the funding transaction to be safely broadcast.
sigTheirCommit, err := l.signCommitTx(
pendingReservation, theirCommitTx, fundingTxOut,
fundingWitnessScript,
)
if err != nil {
req.err <- err
req.completeChan <- nil
return
}
pendingReservation.ourCommitmentSig = sigTheirCommit
_, bestHeight, err := l.Cfg.ChainIO.GetBestBlock()
if err != nil {
req.err <- err
req.completeChan <- nil
return
}
// Set optional upfront shutdown scripts on the channel state so that they
// are persisted. These values may be nil.
chanState.LocalShutdownScript =
pendingReservation.ourContribution.UpfrontShutdown
chanState.RemoteShutdownScript =
pendingReservation.theirContribution.UpfrontShutdown
// Add the complete funding transaction to the DB, in it's open bucket
// which will be used for the lifetime of this channel.
chanState.LocalChanCfg = pendingReservation.ourContribution.toChanConfig()
chanState.RemoteChanCfg = pendingReservation.theirContribution.toChanConfig()
chanState.RevocationKeyLocator = pendingReservation.nextRevocationKeyLoc
err = chanState.SyncPending(pendingReservation.nodeAddr, uint32(bestHeight))
if err != nil {
req.err <- err
req.completeChan <- nil
return
}
req.completeChan <- chanState
req.err <- nil
l.limboMtx.Lock()
delete(l.fundingLimbo, req.pendingFundingID)
delete(l.reservationIDs, pendingReservation.pendingChanID)
l.limboMtx.Unlock()
l.intentMtx.Lock()
delete(l.fundingIntents, pendingReservation.pendingChanID)
l.intentMtx.Unlock()
}
// WithCoinSelectLock will execute the passed function closure in a
// synchronized manner preventing any coin selection operations from proceeding
// while the closure is executing. This can be seen as the ability to execute a
// function closure under an exclusive coin selection lock.
func (l *LightningWallet) WithCoinSelectLock(f func() error) error {
l.coinSelectMtx.Lock()
defer l.coinSelectMtx.Unlock()
return f()
}
// DeriveStateHintObfuscator derives the bytes to be used for obfuscating the
// state hints from the root to be used for a new channel. The obfuscator is
// generated via the following computation:
//
// - sha256(initiatorKey || responderKey)[26:]
// -- where both keys are the multi-sig keys of the respective parties
//
// The first 6 bytes of the resulting hash are used as the state hint.
func DeriveStateHintObfuscator(key1, key2 *btcec.PublicKey) [StateHintSize]byte {
h := sha256.New()
h.Write(key1.SerializeCompressed())
h.Write(key2.SerializeCompressed())
sha := h.Sum(nil)
var obfuscator [StateHintSize]byte
copy(obfuscator[:], sha[26:])
return obfuscator
}
// initStateHints properly sets the obfuscated state hints on both commitment
// transactions using the passed obfuscator.
func initStateHints(commit1, commit2 *wire.MsgTx,
obfuscator [StateHintSize]byte) error {
if err := SetStateNumHint(commit1, 0, obfuscator); err != nil {
return err
}
if err := SetStateNumHint(commit2, 0, obfuscator); err != nil {
return err
}
return nil
}
// ValidateChannel will attempt to fully validate a newly mined channel, given
// its funding transaction and existing channel state. If this method returns
// an error, then the mined channel is invalid, and shouldn't be used.
func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel,
fundingTx *wire.MsgTx) error {
var chanOpts []ChannelOpt
l.Cfg.AuxLeafStore.WhenSome(func(s AuxLeafStore) {
chanOpts = append(chanOpts, WithLeafStore(s))
})
l.Cfg.AuxSigner.WhenSome(func(s AuxSigner) {
chanOpts = append(chanOpts, WithAuxSigner(s))
})
// First, we'll obtain a fully signed commitment transaction so we can
// pass into it on the chanvalidate package for verification.
channel, err := NewLightningChannel(
l.Cfg.Signer, channelState, nil, chanOpts...,
)
if err != nil {
return err
}
localKey := channelState.LocalChanCfg.MultiSigKey.PubKey
remoteKey := channelState.RemoteChanCfg.MultiSigKey.PubKey
// We'll also need the multi-sig witness script itself so the
// chanvalidate package can check it for correctness against the
// funding transaction, and also commitment validity.
var fundingScript []byte
if channelState.ChanType.IsTaproot() {
fundingScript, _, err = input.GenTaprootFundingScript(
localKey, remoteKey, int64(channel.Capacity),
channelState.TapscriptRoot,
)
if err != nil {
return err
}
} else {
witnessScript, err := input.GenMultiSigScript(
localKey.SerializeCompressed(),
remoteKey.SerializeCompressed(),
)
if err != nil {
return err
}
fundingScript, err = input.WitnessScriptHash(witnessScript)
if err != nil {
return err
}
}
signedCommitTx, err := channel.getSignedCommitTx()
if err != nil {
return err
}
commitCtx := &chanvalidate.CommitmentContext{
Value: channel.Capacity,
FullySignedCommitTx: signedCommitTx,
}
// Finally, we'll pass in all the necessary context needed to fully
// validate that this channel is indeed what we expect, and can be
// used.
_, err = chanvalidate.Validate(&chanvalidate.Context{
Locator: &chanvalidate.OutPointChanLocator{
ChanPoint: channelState.FundingOutpoint,
},
MultiSigPkScript: fundingScript,
FundingTx: fundingTx,
CommitCtx: commitCtx,
})
if err != nil {
return err
}
return nil
}
// CancelRebroadcast cancels the rebroadcast of the given transaction.
func (l *LightningWallet) CancelRebroadcast(txid chainhash.Hash) {
// For neutrino, we don't config the rebroadcaster for the wallet as it
// manages the rebroadcasting logic in neutrino itself.
if l.Cfg.Rebroadcaster != nil {
l.Cfg.Rebroadcaster.MarkAsConfirmed(txid)
}
}
// CoinSource is a wrapper around the wallet that implements the
// chanfunding.CoinSource interface.
type CoinSource struct {
wallet *LightningWallet
allowUtxo func(Utxo) bool
}
// NewCoinSource creates a new instance of the CoinSource wrapper struct.
func NewCoinSource(w *LightningWallet, allowUtxo func(Utxo) bool) *CoinSource {
return &CoinSource{
wallet: w,
allowUtxo: allowUtxo,
}
}
// ListCoins returns all UTXOs from the source that have between
// minConfs and maxConfs number of confirmations.
func (c *CoinSource) ListCoins(minConfs int32,
maxConfs int32) ([]wallet.Coin, error) {
utxos, err := c.wallet.ListUnspentWitnessFromDefaultAccount(
minConfs, maxConfs,
)
if err != nil {
return nil, err
}
var coins []wallet.Coin
for _, utxo := range utxos {
// If there is a filter function supplied all utxos not adhering
// to these conditions will be discarded.
if c.allowUtxo != nil && !c.allowUtxo(*utxo) {
walletLog.Infof("Cannot use unconfirmed "+
"utxo=%v because it is unstable and could be "+
"replaced", utxo.OutPoint)
continue
}
coins = append(coins, wallet.Coin{
TxOut: wire.TxOut{
Value: int64(utxo.Value),
PkScript: utxo.PkScript,
},
OutPoint: utxo.OutPoint,
})
}
return coins, nil
}
// CoinFromOutPoint attempts to locate details pertaining to a coin based on
// its outpoint. If the coin isn't under the control of the backing CoinSource,
// then an error should be returned.
func (c *CoinSource) CoinFromOutPoint(op wire.OutPoint) (*wallet.Coin, error) {
inputInfo, err := c.wallet.FetchOutpointInfo(&op)
if err != nil {
return nil, err
}
return &wallet.Coin{
TxOut: wire.TxOut{
Value: int64(inputInfo.Value),
PkScript: inputInfo.PkScript,
},
OutPoint: inputInfo.OutPoint,
}, nil
}
// shimKeyRing is a wrapper struct that's used to provide the proper multi-sig
// key for an initiated external funding flow.
type shimKeyRing struct {
keychain.KeyRing
*chanfunding.ShimIntent
}
// DeriveNextKey intercepts the normal DeriveNextKey call to a keychain.KeyRing
// instance, and supplies the multi-sig key specified by the ShimIntent. This
// allows us to transparently insert new keys into the existing funding flow,
// as these keys may not come from the wallet itself.
func (s *shimKeyRing) DeriveNextKey(keyFam keychain.KeyFamily) (keychain.KeyDescriptor, error) {
if keyFam != keychain.KeyFamilyMultiSig {
return s.KeyRing.DeriveNextKey(keyFam)
}
fundingKeys, err := s.ShimIntent.MultiSigKeys()
if err != nil {
return keychain.KeyDescriptor{}, err
}
return *fundingKeys.LocalKey, nil
}
// ValidateUpfrontShutdown checks whether the provided upfront_shutdown_script
// is of a valid type that we accept.
func ValidateUpfrontShutdown(shutdown lnwire.DeliveryAddress,
params *chaincfg.Params) bool {
// We don't need to worry about a large UpfrontShutdownScript since it
// was already checked in lnwire when decoding from the wire.
scriptClass, _, _, _ := txscript.ExtractPkScriptAddrs(shutdown, params)
switch {
case scriptClass == txscript.WitnessV0PubKeyHashTy,
scriptClass == txscript.WitnessV0ScriptHashTy,
scriptClass == txscript.WitnessV1TaprootTy:
// The above three types are permitted according to BOLT#02 and
// BOLT#05. Everything else is disallowed.
return true
// In this case, we don't know about the actual script template, but it
// might be a witness program with versions 2-16. So we'll check that
// now
case txscript.IsWitnessProgram(shutdown):
version, _, err := txscript.ExtractWitnessProgramInfo(shutdown)
if err != nil {
walletLog.Warnf("unable to extract witness program "+
"version (script=%x): %v", shutdown, err)
return false
}
return version >= 1 && version <= 16
default:
return false
}
}
// WalletPrevOutputFetcher is a txscript.PrevOutputFetcher that can fetch
// outputs from a given wallet controller.
type WalletPrevOutputFetcher struct {
wc WalletController
}
// A compile time assertion that WalletPrevOutputFetcher implements the
// txscript.PrevOutputFetcher interface.
var _ txscript.PrevOutputFetcher = (*WalletPrevOutputFetcher)(nil)
// NewWalletPrevOutputFetcher creates a new WalletPrevOutputFetcher that fetches
// previous outputs from the given wallet controller.
func NewWalletPrevOutputFetcher(wc WalletController) *WalletPrevOutputFetcher {
return &WalletPrevOutputFetcher{
wc: wc,
}
}
// FetchPrevOutput attempts to fetch the previous output referenced by the
// passed outpoint. A nil value will be returned if the passed outpoint doesn't
// exist.
func (w *WalletPrevOutputFetcher) FetchPrevOutput(op wire.OutPoint) *wire.TxOut {
utxo, err := w.wc.FetchOutpointInfo(&op)
if err != nil {
return nil
}
return &wire.TxOut{
Value: int64(utxo.Value),
PkScript: utxo.PkScript,
}
}
package lnwire
import (
"bytes"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/tlv"
)
// AcceptChannel is the message Bob sends to Alice after she initiates the
// single funder channel workflow via an AcceptChannel message. Once Alice
// receives Bob's response, then she has all the items necessary to construct
// the funding transaction, and both commitment transactions.
type AcceptChannel struct {
// PendingChannelID serves to uniquely identify the future channel
// created by the initiated single funder workflow.
PendingChannelID [32]byte
// DustLimit is the specific dust limit the sender of this message
// would like enforced on their version of the commitment transaction.
// Any output below this value will be "trimmed" from the commitment
// transaction, with the amount of the HTLC going to dust.
DustLimit btcutil.Amount
// MaxValueInFlight represents the maximum amount of coins that can be
// pending within the channel at any given time. If the amount of funds
// in limbo exceeds this amount, then the channel will be failed.
MaxValueInFlight MilliSatoshi
// ChannelReserve is the amount of BTC that the receiving party MUST
// maintain a balance above at all times. This is a safety mechanism to
// ensure that both sides always have skin in the game during the
// channel's lifetime.
ChannelReserve btcutil.Amount
// HtlcMinimum is the smallest HTLC that the sender of this message
// will accept.
HtlcMinimum MilliSatoshi
// MinAcceptDepth is the minimum depth that the initiator of the
// channel should wait before considering the channel open.
MinAcceptDepth uint32
// CsvDelay is the number of blocks to use for the relative time lock
// in the pay-to-self output of both commitment transactions.
CsvDelay uint16
// MaxAcceptedHTLCs is the total number of incoming HTLC's that the
// sender of this channel will accept.
//
// TODO(roasbeef): acks the initiator's, same with max in flight?
MaxAcceptedHTLCs uint16
// FundingKey is the key that should be used on behalf of the sender
// within the 2-of-2 multi-sig output that it contained within the
// funding transaction.
FundingKey *btcec.PublicKey
// RevocationPoint is the base revocation point for the sending party.
// Any commitment transaction belonging to the receiver of this message
// should use this key and their per-commitment point to derive the
// revocation key for the commitment transaction.
RevocationPoint *btcec.PublicKey
// PaymentPoint is the base payment point for the sending party. This
// key should be combined with the per commitment point for a
// particular commitment state in order to create the key that should
// be used in any output that pays directly to the sending party, and
// also within the HTLC covenant transactions.
PaymentPoint *btcec.PublicKey
// DelayedPaymentPoint is the delay point for the sending party. This
// key should be combined with the per commitment point to derive the
// keys that are used in outputs of the sender's commitment transaction
// where they claim funds.
DelayedPaymentPoint *btcec.PublicKey
// HtlcPoint is the base point used to derive the set of keys for this
// party that will be used within the HTLC public key scripts. This
// value is combined with the receiver's revocation base point in order
// to derive the keys that are used within HTLC scripts.
HtlcPoint *btcec.PublicKey
// FirstCommitmentPoint is the first commitment point for the sending
// party. This value should be combined with the receiver's revocation
// base point in order to derive the revocation keys that are placed
// within the commitment transaction of the sender.
FirstCommitmentPoint *btcec.PublicKey
// UpfrontShutdownScript is the script to which the channel funds should
// be paid when mutually closing the channel. This field is optional, and
// and has a length prefix, so a zero will be written if it is not set
// and its length followed by the script will be written if it is set.
UpfrontShutdownScript DeliveryAddress
// ChannelType is the explicit channel type the initiator wishes to
// open.
ChannelType *ChannelType
// LeaseExpiry represents the absolute expiration height of a channel
// lease. This is a custom TLV record that will only apply when a leased
// channel is being opened using the script enforced lease commitment
// type.
LeaseExpiry *LeaseExpiry
// LocalNonce is an optional field that transmits the
// local/verification nonce for a party. This nonce will be used to
// verify the very first commitment transaction signature.
// This will only be populated if the simple taproot channels type was
// negotiated.
LocalNonce OptMusig2NonceTLV
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
//
// NOTE: Since the upfront shutdown script MUST be present (though can
// be zero-length) if any TLV data is available, the script will be
// extracted and removed from this blob when decoding. ExtraData will
// contain all TLV records _except_ the DeliveryAddress record in that
// case.
ExtraData ExtraOpaqueData
}
// A compile time check to ensure AcceptChannel implements the lnwire.Message
// interface.
var _ Message = (*AcceptChannel)(nil)
// A compile time check to ensure AcceptChannel implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*AcceptChannel)(nil)
// Encode serializes the target AcceptChannel into the passed io.Writer
// implementation. Serialization will observe the rules defined by the passed
// protocol version.
//
// This is part of the lnwire.Message interface.
func (a *AcceptChannel) Encode(w *bytes.Buffer, pver uint32) error {
recordProducers := []tlv.RecordProducer{&a.UpfrontShutdownScript}
if a.ChannelType != nil {
recordProducers = append(recordProducers, a.ChannelType)
}
if a.LeaseExpiry != nil {
recordProducers = append(recordProducers, a.LeaseExpiry)
}
a.LocalNonce.WhenSome(func(localNonce Musig2NonceTLV) {
recordProducers = append(recordProducers, &localNonce)
})
err := EncodeMessageExtraData(&a.ExtraData, recordProducers...)
if err != nil {
return err
}
if err := WriteBytes(w, a.PendingChannelID[:]); err != nil {
return err
}
if err := WriteSatoshi(w, a.DustLimit); err != nil {
return err
}
if err := WriteMilliSatoshi(w, a.MaxValueInFlight); err != nil {
return err
}
if err := WriteSatoshi(w, a.ChannelReserve); err != nil {
return err
}
if err := WriteMilliSatoshi(w, a.HtlcMinimum); err != nil {
return err
}
if err := WriteUint32(w, a.MinAcceptDepth); err != nil {
return err
}
if err := WriteUint16(w, a.CsvDelay); err != nil {
return err
}
if err := WriteUint16(w, a.MaxAcceptedHTLCs); err != nil {
return err
}
if err := WritePublicKey(w, a.FundingKey); err != nil {
return err
}
if err := WritePublicKey(w, a.RevocationPoint); err != nil {
return err
}
if err := WritePublicKey(w, a.PaymentPoint); err != nil {
return err
}
if err := WritePublicKey(w, a.DelayedPaymentPoint); err != nil {
return err
}
if err := WritePublicKey(w, a.HtlcPoint); err != nil {
return err
}
if err := WritePublicKey(w, a.FirstCommitmentPoint); err != nil {
return err
}
return WriteBytes(w, a.ExtraData)
}
// Decode deserializes the serialized AcceptChannel stored in the passed
// io.Reader into the target AcceptChannel using the deserialization rules
// defined by the passed protocol version.
//
// This is part of the lnwire.Message interface.
func (a *AcceptChannel) Decode(r io.Reader, pver uint32) error {
// Read all the mandatory fields in the accept message.
err := ReadElements(r,
a.PendingChannelID[:],
&a.DustLimit,
&a.MaxValueInFlight,
&a.ChannelReserve,
&a.HtlcMinimum,
&a.MinAcceptDepth,
&a.CsvDelay,
&a.MaxAcceptedHTLCs,
&a.FundingKey,
&a.RevocationPoint,
&a.PaymentPoint,
&a.DelayedPaymentPoint,
&a.HtlcPoint,
&a.FirstCommitmentPoint,
)
if err != nil {
return err
}
// For backwards compatibility, the optional extra data blob for
// AcceptChannel must contain an entry for the upfront shutdown script.
// We'll read it out and attempt to parse it.
var tlvRecords ExtraOpaqueData
if err := ReadElements(r, &tlvRecords); err != nil {
return err
}
// Next we'll parse out the set of known records, keeping the raw tlv
// bytes untouched to ensure we don't drop any bytes erroneously.
var (
chanType ChannelType
leaseExpiry LeaseExpiry
localNonce = a.LocalNonce.Zero()
)
typeMap, err := tlvRecords.ExtractRecords(
&a.UpfrontShutdownScript, &chanType, &leaseExpiry,
&localNonce,
)
if err != nil {
return err
}
// Set the corresponding TLV types if they were included in the stream.
if val, ok := typeMap[ChannelTypeRecordType]; ok && val == nil {
a.ChannelType = &chanType
}
if val, ok := typeMap[LeaseExpiryRecordType]; ok && val == nil {
a.LeaseExpiry = &leaseExpiry
}
if val, ok := typeMap[a.LocalNonce.TlvType()]; ok && val == nil {
a.LocalNonce = tlv.SomeRecordT(localNonce)
}
a.ExtraData = tlvRecords
return nil
}
// MsgType returns the MessageType code which uniquely identifies this message
// as an AcceptChannel on the wire.
//
// This is part of the lnwire.Message interface.
func (a *AcceptChannel) MsgType() MessageType {
return MsgAcceptChannel
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (a *AcceptChannel) SerializedSize() (uint32, error) {
return MessageSerializedSize(a)
}
package lnwire
import (
"bytes"
"io"
)
// AnnounceSignatures1 is a direct message between two endpoints of a
// channel and serves as an opt-in mechanism to allow the announcement of
// the channel to the rest of the network. It contains the necessary
// signatures by the sender to construct the channel announcement message.
type AnnounceSignatures1 struct {
// ChannelID is the unique description of the funding transaction.
// Channel id is better for users and debugging and short channel id is
// used for quick test on existence of the particular utxo inside the
// block chain, because it contains information about block.
ChannelID ChannelID
// ShortChannelID is the unique description of the funding
// transaction. It is constructed with the most significant 3 bytes
// as the block height, the next 3 bytes indicating the transaction
// index within the block, and the least significant two bytes
// indicating the output index which pays to the channel.
ShortChannelID ShortChannelID
// NodeSignature is the signature which contains the signed announce
// channel message, by this signature we proof that we possess of the
// node pub key and creating the reference node_key -> bitcoin_key.
NodeSignature Sig
// BitcoinSignature is the signature which contains the signed node
// public key, by this signature we proof that we possess of the
// bitcoin key and and creating the reverse reference bitcoin_key ->
// node_key.
BitcoinSignature Sig
// ExtraOpaqueData is the set of data that was appended to this
// message, some of which we may not actually know how to iterate or
// parse. By holding onto this data, we ensure that we're able to
// properly validate the set of signatures that cover these new fields,
// and ensure we're able to make upgrades to the network in a forwards
// compatible manner.
ExtraOpaqueData ExtraOpaqueData
}
// A compile time check to ensure AnnounceSignatures1 implements the
// lnwire.Message interface.
var _ Message = (*AnnounceSignatures1)(nil)
// A compile time check to ensure AnnounceSignatures1 implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*AnnounceSignatures1)(nil)
// A compile time check to ensure AnnounceSignatures1 implements the
// lnwire.AnnounceSignatures interface.
var _ AnnounceSignatures = (*AnnounceSignatures1)(nil)
// Decode deserializes a serialized AnnounceSignatures1 stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (a *AnnounceSignatures1) Decode(r io.Reader, _ uint32) error {
return ReadElements(r,
&a.ChannelID,
&a.ShortChannelID,
&a.NodeSignature,
&a.BitcoinSignature,
&a.ExtraOpaqueData,
)
}
// Encode serializes the target AnnounceSignatures1 into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (a *AnnounceSignatures1) Encode(w *bytes.Buffer, _ uint32) error {
if err := WriteChannelID(w, a.ChannelID); err != nil {
return err
}
if err := WriteShortChannelID(w, a.ShortChannelID); err != nil {
return err
}
if err := WriteSig(w, a.NodeSignature); err != nil {
return err
}
if err := WriteSig(w, a.BitcoinSignature); err != nil {
return err
}
return WriteBytes(w, a.ExtraOpaqueData)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (a *AnnounceSignatures1) MsgType() MessageType {
return MsgAnnounceSignatures
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (a *AnnounceSignatures1) SerializedSize() (uint32, error) {
return MessageSerializedSize(a)
}
// SCID returns the ShortChannelID of the channel.
//
// This is part of the lnwire.AnnounceSignatures interface.
func (a *AnnounceSignatures1) SCID() ShortChannelID {
return a.ShortChannelID
}
// ChanID returns the ChannelID identifying the channel.
//
// This is part of the lnwire.AnnounceSignatures interface.
func (a *AnnounceSignatures1) ChanID() ChannelID {
return a.ChannelID
}
package lnwire
import (
"bytes"
"io"
)
// AnnounceSignatures2 is a direct message between two endpoints of a
// channel and serves as an opt-in mechanism to allow the announcement of
// a taproot channel to the rest of the network. It contains the necessary
// signatures by the sender to construct the channel_announcement_2 message.
type AnnounceSignatures2 struct {
// ChannelID is the unique description of the funding transaction.
// Channel id is better for users and debugging and short channel id is
// used for quick test on existence of the particular utxo inside the
// blockchain, because it contains information about block.
ChannelID ChannelID
// ShortChannelID is the unique description of the funding transaction.
// It is constructed with the most significant 3 bytes as the block
// height, the next 3 bytes indicating the transaction index within the
// block, and the least significant two bytes indicating the output
// index which pays to the channel.
ShortChannelID ShortChannelID
// PartialSignature is the combination of the partial Schnorr signature
// created for the node's bitcoin key with the partial signature created
// for the node's node ID key.
PartialSignature PartialSig
// ExtraOpaqueData is the set of data that was appended to this
// message, some of which we may not actually know how to iterate or
// parse. By holding onto this data, we ensure that we're able to
// properly validate the set of signatures that cover these new fields,
// and ensure we're able to make upgrades to the network in a forwards
// compatible manner.
ExtraOpaqueData ExtraOpaqueData
}
// A compile time check to ensure AnnounceSignatures2 implements the
// lnwire.Message interface.
var _ Message = (*AnnounceSignatures2)(nil)
// A compile time check to ensure AnnounceSignatures2 implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*AnnounceSignatures2)(nil)
// Decode deserializes a serialized AnnounceSignatures2 stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (a *AnnounceSignatures2) Decode(r io.Reader, _ uint32) error {
return ReadElements(r,
&a.ChannelID,
&a.ShortChannelID,
&a.PartialSignature,
&a.ExtraOpaqueData,
)
}
// Encode serializes the target AnnounceSignatures2 into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (a *AnnounceSignatures2) Encode(w *bytes.Buffer, _ uint32) error {
if err := WriteChannelID(w, a.ChannelID); err != nil {
return err
}
if err := WriteShortChannelID(w, a.ShortChannelID); err != nil {
return err
}
if err := WriteElement(w, a.PartialSignature); err != nil {
return err
}
return WriteBytes(w, a.ExtraOpaqueData)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (a *AnnounceSignatures2) MsgType() MessageType {
return MsgAnnounceSignatures2
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (a *AnnounceSignatures2) SerializedSize() (uint32, error) {
return MessageSerializedSize(a)
}
// SCID returns the ShortChannelID of the channel.
//
// NOTE: this is part of the AnnounceSignatures interface.
func (a *AnnounceSignatures2) SCID() ShortChannelID {
return a.ShortChannelID
}
// ChanID returns the ChannelID identifying the channel.
//
// NOTE: this is part of the AnnounceSignatures interface.
func (a *AnnounceSignatures2) ChanID() ChannelID {
return a.ChannelID
}
package lnwire
import (
"bytes"
"io"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// ChannelAnnouncement1 message is used to announce the existence of a channel
// between two peers in the overlay, which is propagated by the discovery
// service over broadcast handler.
type ChannelAnnouncement1 struct {
// This signatures are used by nodes in order to create cross
// references between node's channel and node. Requiring both nodes
// to sign indicates they are both willing to route other payments via
// this node.
NodeSig1 Sig
NodeSig2 Sig
// This signatures are used by nodes in order to create cross
// references between node's channel and node. Requiring the bitcoin
// signatures proves they control the channel.
BitcoinSig1 Sig
BitcoinSig2 Sig
// Features is the feature vector that encodes the features supported
// by the target node. This field can be used to signal the type of the
// channel, or modifications to the fields that would normally follow
// this vector.
Features *RawFeatureVector
// ChainHash denotes the target chain that this channel was opened
// within. This value should be the genesis hash of the target chain.
ChainHash chainhash.Hash
// ShortChannelID is the unique description of the funding transaction,
// or where exactly it's located within the target blockchain.
ShortChannelID ShortChannelID
// The public keys of the two nodes who are operating the channel, such
// that is NodeID1 the numerically-lesser than NodeID2 (ascending
// numerical order).
NodeID1 [33]byte
NodeID2 [33]byte
// Public keys which corresponds to the keys which was declared in
// multisig funding transaction output.
BitcoinKey1 [33]byte
BitcoinKey2 [33]byte
// ExtraOpaqueData is the set of data that was appended to this
// message, some of which we may not actually know how to iterate or
// parse. By holding onto this data, we ensure that we're able to
// properly validate the set of signatures that cover these new fields,
// and ensure we're able to make upgrades to the network in a forwards
// compatible manner.
ExtraOpaqueData ExtraOpaqueData
}
// A compile time check to ensure ChannelAnnouncement implements the
// lnwire.Message interface.
var _ Message = (*ChannelAnnouncement1)(nil)
// A compile time check to ensure ChannelAnnouncement1 implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*ChannelAnnouncement1)(nil)
// Decode deserializes a serialized ChannelAnnouncement stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (a *ChannelAnnouncement1) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&a.NodeSig1,
&a.NodeSig2,
&a.BitcoinSig1,
&a.BitcoinSig2,
&a.Features,
a.ChainHash[:],
&a.ShortChannelID,
&a.NodeID1,
&a.NodeID2,
&a.BitcoinKey1,
&a.BitcoinKey2,
&a.ExtraOpaqueData,
)
}
// Encode serializes the target ChannelAnnouncement into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (a *ChannelAnnouncement1) Encode(w *bytes.Buffer, pver uint32) error {
if err := WriteSig(w, a.NodeSig1); err != nil {
return err
}
if err := WriteSig(w, a.NodeSig2); err != nil {
return err
}
if err := WriteSig(w, a.BitcoinSig1); err != nil {
return err
}
if err := WriteSig(w, a.BitcoinSig2); err != nil {
return err
}
if err := WriteRawFeatureVector(w, a.Features); err != nil {
return err
}
if err := WriteBytes(w, a.ChainHash[:]); err != nil {
return err
}
if err := WriteShortChannelID(w, a.ShortChannelID); err != nil {
return err
}
if err := WriteBytes(w, a.NodeID1[:]); err != nil {
return err
}
if err := WriteBytes(w, a.NodeID2[:]); err != nil {
return err
}
if err := WriteBytes(w, a.BitcoinKey1[:]); err != nil {
return err
}
if err := WriteBytes(w, a.BitcoinKey2[:]); err != nil {
return err
}
return WriteBytes(w, a.ExtraOpaqueData)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (a *ChannelAnnouncement1) MsgType() MessageType {
return MsgChannelAnnouncement
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (a *ChannelAnnouncement1) SerializedSize() (uint32, error) {
return MessageSerializedSize(a)
}
// DataToSign is used to retrieve part of the announcement message which should
// be signed.
func (a *ChannelAnnouncement1) DataToSign() ([]byte, error) {
// We should not include the signatures itself.
b := make([]byte, 0, MaxMsgBody)
buf := bytes.NewBuffer(b)
if err := WriteRawFeatureVector(buf, a.Features); err != nil {
return nil, err
}
if err := WriteBytes(buf, a.ChainHash[:]); err != nil {
return nil, err
}
if err := WriteShortChannelID(buf, a.ShortChannelID); err != nil {
return nil, err
}
if err := WriteBytes(buf, a.NodeID1[:]); err != nil {
return nil, err
}
if err := WriteBytes(buf, a.NodeID2[:]); err != nil {
return nil, err
}
if err := WriteBytes(buf, a.BitcoinKey1[:]); err != nil {
return nil, err
}
if err := WriteBytes(buf, a.BitcoinKey2[:]); err != nil {
return nil, err
}
if err := WriteBytes(buf, a.ExtraOpaqueData); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// Node1KeyBytes returns the bytes representing the public key of node 1 in the
// channel.
//
// NOTE: This is part of the ChannelAnnouncement interface.
func (a *ChannelAnnouncement1) Node1KeyBytes() [33]byte {
return a.NodeID1
}
// Node2KeyBytes returns the bytes representing the public key of node 2 in the
// channel.
//
// NOTE: This is part of the ChannelAnnouncement interface.
func (a *ChannelAnnouncement1) Node2KeyBytes() [33]byte {
return a.NodeID2
}
// GetChainHash returns the hash of the chain which this channel's funding
// transaction is confirmed in.
//
// NOTE: This is part of the ChannelAnnouncement interface.
func (a *ChannelAnnouncement1) GetChainHash() chainhash.Hash {
return a.ChainHash
}
// SCID returns the short channel ID of the channel.
//
// NOTE: This is part of the ChannelAnnouncement interface.
func (a *ChannelAnnouncement1) SCID() ShortChannelID {
return a.ShortChannelID
}
// A compile-time check to ensure that ChannelAnnouncement1 implements the
// ChannelAnnouncement interface.
var _ ChannelAnnouncement = (*ChannelAnnouncement1)(nil)
package lnwire
import (
"bytes"
"io"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/tlv"
)
// ChannelAnnouncement2 message is used to announce the existence of a taproot
// channel between two peers in the network.
type ChannelAnnouncement2 struct {
// Signature is a Schnorr signature over the TLV stream of the message.
Signature Sig
// ChainHash denotes the target chain that this channel was opened
// within. This value should be the genesis hash of the target chain.
ChainHash tlv.RecordT[tlv.TlvType0, chainhash.Hash]
// Features is the feature vector that encodes the features supported
// by the target node. This field can be used to signal the type of the
// channel, or modifications to the fields that would normally follow
// this vector.
Features tlv.RecordT[tlv.TlvType2, RawFeatureVector]
// ShortChannelID is the unique description of the funding transaction,
// or where exactly it's located within the target blockchain.
ShortChannelID tlv.RecordT[tlv.TlvType4, ShortChannelID]
// Capacity is the number of satoshis of the capacity of this channel.
// It must be less than or equal to the value of the on-chain funding
// output.
Capacity tlv.RecordT[tlv.TlvType6, uint64]
// NodeID1 is the numerically-lesser public key ID of one of the channel
// operators.
NodeID1 tlv.RecordT[tlv.TlvType8, [33]byte]
// NodeID2 is the numerically-greater public key ID of one of the
// channel operators.
NodeID2 tlv.RecordT[tlv.TlvType10, [33]byte]
// BitcoinKey1 is the public key of the key used by Node1 in the
// construction of the on-chain funding transaction. This is an optional
// field and only needs to be set if the 4-of-4 MuSig construction was
// used in the creation of the message signature.
BitcoinKey1 tlv.OptionalRecordT[tlv.TlvType12, [33]byte]
// BitcoinKey2 is the public key of the key used by Node2 in the
// construction of the on-chain funding transaction. This is an optional
// field and only needs to be set if the 4-of-4 MuSig construction was
// used in the creation of the message signature.
BitcoinKey2 tlv.OptionalRecordT[tlv.TlvType14, [33]byte]
// MerkleRootHash is the hash used to create the optional tweak in the
// funding output. If this is not set but the bitcoin keys are, then
// the funding output is a pure 2-of-2 MuSig aggregate public key.
MerkleRootHash tlv.OptionalRecordT[tlv.TlvType16, [32]byte]
// ExtraOpaqueData is the set of data that was appended to this
// message, some of which we may not actually know how to iterate or
// parse. By holding onto this data, we ensure that we're able to
// properly validate the set of signatures that cover these new fields,
// and ensure we're able to make upgrades to the network in a forwards
// compatible manner.
ExtraOpaqueData ExtraOpaqueData
}
// Decode deserializes a serialized AnnounceSignatures1 stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *ChannelAnnouncement2) Decode(r io.Reader, _ uint32) error {
err := ReadElement(r, &c.Signature)
if err != nil {
return err
}
c.Signature.ForceSchnorr()
return c.DecodeTLVRecords(r)
}
// DecodeTLVRecords decodes only the TLV section of the message.
func (c *ChannelAnnouncement2) DecodeTLVRecords(r io.Reader) error {
// First extract into extra opaque data.
var tlvRecords ExtraOpaqueData
if err := ReadElements(r, &tlvRecords); err != nil {
return err
}
var (
chainHash = tlv.ZeroRecordT[tlv.TlvType0, [32]byte]()
btcKey1 = tlv.ZeroRecordT[tlv.TlvType12, [33]byte]()
btcKey2 = tlv.ZeroRecordT[tlv.TlvType14, [33]byte]()
merkleRootHash = tlv.ZeroRecordT[tlv.TlvType16, [32]byte]()
)
typeMap, err := tlvRecords.ExtractRecords(
&chainHash, &c.Features, &c.ShortChannelID, &c.Capacity,
&c.NodeID1, &c.NodeID2, &btcKey1, &btcKey2, &merkleRootHash,
)
if err != nil {
return err
}
// By default, the chain-hash is the bitcoin mainnet genesis block hash.
c.ChainHash.Val = *chaincfg.MainNetParams.GenesisHash
if _, ok := typeMap[c.ChainHash.TlvType()]; ok {
c.ChainHash.Val = chainHash.Val
}
if _, ok := typeMap[c.BitcoinKey1.TlvType()]; ok {
c.BitcoinKey1 = tlv.SomeRecordT(btcKey1)
}
if _, ok := typeMap[c.BitcoinKey2.TlvType()]; ok {
c.BitcoinKey2 = tlv.SomeRecordT(btcKey2)
}
if _, ok := typeMap[c.MerkleRootHash.TlvType()]; ok {
c.MerkleRootHash = tlv.SomeRecordT(merkleRootHash)
}
if len(tlvRecords) != 0 {
c.ExtraOpaqueData = tlvRecords
}
return nil
}
// Encode serializes the target AnnounceSignatures1 into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *ChannelAnnouncement2) Encode(w *bytes.Buffer, _ uint32) error {
_, err := w.Write(c.Signature.RawBytes())
if err != nil {
return err
}
_, err = c.DataToSign()
if err != nil {
return err
}
return WriteBytes(w, c.ExtraOpaqueData)
}
// DataToSign encodes the data to be signed into the ExtraOpaqueData member and
// returns it.
func (c *ChannelAnnouncement2) DataToSign() ([]byte, error) {
// The chain-hash record is only included if it is _not_ equal to the
// bitcoin mainnet genisis block hash.
var recordProducers []tlv.RecordProducer
if !c.ChainHash.Val.IsEqual(chaincfg.MainNetParams.GenesisHash) {
hash := tlv.ZeroRecordT[tlv.TlvType0, [32]byte]()
hash.Val = c.ChainHash.Val
recordProducers = append(recordProducers, &hash)
}
recordProducers = append(recordProducers,
&c.Features, &c.ShortChannelID, &c.Capacity, &c.NodeID1,
&c.NodeID2,
)
c.BitcoinKey1.WhenSome(func(key tlv.RecordT[tlv.TlvType12, [33]byte]) {
recordProducers = append(recordProducers, &key)
})
c.BitcoinKey2.WhenSome(func(key tlv.RecordT[tlv.TlvType14, [33]byte]) {
recordProducers = append(recordProducers, &key)
})
c.MerkleRootHash.WhenSome(
func(hash tlv.RecordT[tlv.TlvType16, [32]byte]) {
recordProducers = append(recordProducers, &hash)
},
)
err := EncodeMessageExtraData(&c.ExtraOpaqueData, recordProducers...)
if err != nil {
return nil, err
}
return c.ExtraOpaqueData, nil
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *ChannelAnnouncement2) MsgType() MessageType {
return MsgChannelAnnouncement2
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (c *ChannelAnnouncement2) SerializedSize() (uint32, error) {
return MessageSerializedSize(c)
}
// A compile time check to ensure ChannelAnnouncement2 implements the
// lnwire.Message interface.
var _ Message = (*ChannelAnnouncement2)(nil)
// A compile time check to ensure ChannelAnnouncement2 implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*ChannelAnnouncement2)(nil)
// Node1KeyBytes returns the bytes representing the public key of node 1 in the
// channel.
//
// NOTE: This is part of the ChannelAnnouncement interface.
func (c *ChannelAnnouncement2) Node1KeyBytes() [33]byte {
return c.NodeID1.Val
}
// Node2KeyBytes returns the bytes representing the public key of node 2 in the
// channel.
//
// NOTE: This is part of the ChannelAnnouncement interface.
func (c *ChannelAnnouncement2) Node2KeyBytes() [33]byte {
return c.NodeID2.Val
}
// GetChainHash returns the hash of the chain which this channel's funding
// transaction is confirmed in.
//
// NOTE: This is part of the ChannelAnnouncement interface.
func (c *ChannelAnnouncement2) GetChainHash() chainhash.Hash {
return c.ChainHash.Val
}
// SCID returns the short channel ID of the channel.
//
// NOTE: This is part of the ChannelAnnouncement interface.
func (c *ChannelAnnouncement2) SCID() ShortChannelID {
return c.ShortChannelID.Val
}
// A compile-time check to ensure that ChannelAnnouncement2 implements the
// ChannelAnnouncement interface.
var _ ChannelAnnouncement = (*ChannelAnnouncement2)(nil)
package lnwire
import (
"encoding/binary"
"encoding/hex"
"math"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
)
const (
// MaxFundingTxOutputs is the maximum number of allowed outputs on a
// funding transaction within the protocol. This is due to the fact
// that we use 2-bytes to encode the index within the funding output
// during the funding workflow. Funding transaction with more outputs
// than this are considered invalid within the protocol.
MaxFundingTxOutputs = math.MaxUint16
)
// ChannelID is a series of 32-bytes that uniquely identifies all channels
// within the network. The ChannelID is computed using the outpoint of the
// funding transaction (the txid, and output index). Given a funding output the
// ChannelID can be calculated by XOR'ing the big-endian serialization of the
// txid and the big-endian serialization of the output index, truncated to
// 2 bytes.
type ChannelID [32]byte
// ConnectionWideID is an all-zero ChannelID, which is used to represent a
// message intended for all channels to specific peer.
var ConnectionWideID = ChannelID{}
// String returns the string representation of the ChannelID. This is just the
// hex string encoding of the ChannelID itself.
func (c ChannelID) String() string {
return hex.EncodeToString(c[:])
}
// NewChanIDFromOutPoint converts a target OutPoint into a ChannelID that is
// usable within the network. In order to convert the OutPoint into a ChannelID,
// we XOR the lower 2-bytes of the txid within the OutPoint with the big-endian
// serialization of the Index of the OutPoint, truncated to 2-bytes.
func NewChanIDFromOutPoint(op wire.OutPoint) ChannelID {
// First we'll copy the txid of the outpoint into our channel ID slice.
var cid ChannelID
copy(cid[:], op.Hash[:])
// With the txid copied over, we'll now XOR the lower 2-bytes of the
// partial channelID with big-endian serialization of output index.
xorTxid(&cid, uint16(op.Index))
return cid
}
// xorTxid performs the transformation needed to transform an OutPoint into a
// ChannelID. To do this, we expect the cid parameter to contain the txid
// unaltered and the outputIndex to be the output index
func xorTxid(cid *ChannelID, outputIndex uint16) {
var buf [2]byte
binary.BigEndian.PutUint16(buf[:], outputIndex)
cid[30] ^= buf[0]
cid[31] ^= buf[1]
}
// GenPossibleOutPoints generates all the possible outputs given a channel ID.
// In order to generate these possible outpoints, we perform a brute-force
// search through the candidate output index space, performing a reverse
// mapping from channelID back to OutPoint.
func (c *ChannelID) GenPossibleOutPoints() [MaxFundingTxOutputs]wire.OutPoint {
var possiblePoints [MaxFundingTxOutputs]wire.OutPoint
for i := uint16(0); i < MaxFundingTxOutputs; i++ {
cidCopy := *c
xorTxid(&cidCopy, i)
possiblePoints[i] = wire.OutPoint{
Hash: chainhash.Hash(cidCopy),
Index: uint32(i),
}
}
return possiblePoints
}
// IsChanPoint returns true if the OutPoint passed corresponds to the target
// ChannelID.
func (c ChannelID) IsChanPoint(op *wire.OutPoint) bool {
candidateCid := NewChanIDFromOutPoint(*op)
return candidateCid == c
}
package lnwire
import (
"bytes"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/tlv"
)
// ChannelReady is the message that both parties to a new channel creation
// send once they have observed the funding transaction being confirmed on the
// blockchain. ChannelReady contains the signatures necessary for the channel
// participants to advertise the existence of the channel to the rest of the
// network.
type ChannelReady struct {
// ChanID is the outpoint of the channel's funding transaction. This
// can be used to query for the channel in the database.
ChanID ChannelID
// NextPerCommitmentPoint is the secret that can be used to revoke the
// next commitment transaction for the channel.
NextPerCommitmentPoint *btcec.PublicKey
// AliasScid is an alias ShortChannelID used to refer to the underlying
// channel. It can be used instead of the confirmed on-chain
// ShortChannelID for forwarding.
AliasScid *ShortChannelID
// NextLocalNonce is an optional field that stores a local musig2 nonce.
// This will only be populated if the simple taproot channels type was
// negotiated. This is the local nonce that will be used by the sender
// to accept a new commitment state transition.
NextLocalNonce OptMusig2NonceTLV
// AnnouncementNodeNonce is an optional field that stores a public
// nonce that will be used along with the node's ID key during signing
// of the ChannelAnnouncement2 message.
AnnouncementNodeNonce tlv.OptionalRecordT[tlv.TlvType0, Musig2Nonce]
// AnnouncementBitcoinNonce is an optional field that stores a public
// nonce that will be used along with the node's bitcoin key during
// signing of the ChannelAnnouncement2 message.
AnnouncementBitcoinNonce tlv.OptionalRecordT[tlv.TlvType2, Musig2Nonce]
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// NewChannelReady creates a new ChannelReady message, populating it with the
// necessary IDs and revocation secret.
func NewChannelReady(cid ChannelID, npcp *btcec.PublicKey) *ChannelReady {
return &ChannelReady{
ChanID: cid,
NextPerCommitmentPoint: npcp,
ExtraData: nil,
}
}
// A compile time check to ensure ChannelReady implements the lnwire.Message
// interface.
var _ Message = (*ChannelReady)(nil)
// A compile time check to ensure ChannelReady implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*ChannelReady)(nil)
// Decode deserializes the serialized ChannelReady message stored in the
// passed io.Reader into the target ChannelReady using the deserialization
// rules defined by the passed protocol version.
//
// This is part of the lnwire.Message interface.
func (c *ChannelReady) Decode(r io.Reader, _ uint32) error {
// Read all the mandatory fields in the message.
err := ReadElements(r,
&c.ChanID,
&c.NextPerCommitmentPoint,
)
if err != nil {
return err
}
var tlvRecords ExtraOpaqueData
if err := ReadElements(r, &tlvRecords); err != nil {
return err
}
// Next we'll parse out the set of known records. For now, this is just
// the AliasScidRecordType.
var (
aliasScid ShortChannelID
localNonce = c.NextLocalNonce.Zero()
nodeNonce = tlv.ZeroRecordT[tlv.TlvType0, Musig2Nonce]()
btcNonce = tlv.ZeroRecordT[tlv.TlvType2, Musig2Nonce]()
)
typeMap, err := tlvRecords.ExtractRecords(
&btcNonce, &aliasScid, &nodeNonce, &localNonce,
)
if err != nil {
return err
}
// We'll only set AliasScid if the corresponding TLV type was included
// in the stream.
if val, ok := typeMap[AliasScidRecordType]; ok && val == nil {
c.AliasScid = &aliasScid
}
if val, ok := typeMap[c.NextLocalNonce.TlvType()]; ok && val == nil {
c.NextLocalNonce = tlv.SomeRecordT(localNonce)
}
val, ok := typeMap[c.AnnouncementBitcoinNonce.TlvType()]
if ok && val == nil {
c.AnnouncementBitcoinNonce = tlv.SomeRecordT(btcNonce)
}
val, ok = typeMap[c.AnnouncementNodeNonce.TlvType()]
if ok && val == nil {
c.AnnouncementNodeNonce = tlv.SomeRecordT(nodeNonce)
}
if len(tlvRecords) != 0 {
c.ExtraData = tlvRecords
}
return nil
}
// Encode serializes the target ChannelReady message into the passed io.Writer
// implementation. Serialization will observe the rules defined by the passed
// protocol version.
//
// This is part of the lnwire.Message interface.
func (c *ChannelReady) Encode(w *bytes.Buffer, _ uint32) error {
if err := WriteChannelID(w, c.ChanID); err != nil {
return err
}
if err := WritePublicKey(w, c.NextPerCommitmentPoint); err != nil {
return err
}
// We'll only encode the AliasScid in a TLV segment if it exists.
recordProducers := make([]tlv.RecordProducer, 0, 4)
if c.AliasScid != nil {
recordProducers = append(recordProducers, c.AliasScid)
}
c.NextLocalNonce.WhenSome(func(localNonce Musig2NonceTLV) {
recordProducers = append(recordProducers, &localNonce)
})
c.AnnouncementBitcoinNonce.WhenSome(
func(nonce tlv.RecordT[tlv.TlvType2, Musig2Nonce]) {
recordProducers = append(recordProducers, &nonce)
},
)
c.AnnouncementNodeNonce.WhenSome(
func(nonce tlv.RecordT[tlv.TlvType0, Musig2Nonce]) {
recordProducers = append(recordProducers, &nonce)
},
)
err := EncodeMessageExtraData(&c.ExtraData, recordProducers...)
if err != nil {
return err
}
return WriteBytes(w, c.ExtraData)
}
// MsgType returns the uint32 code which uniquely identifies this message as a
// ChannelReady message on the wire.
//
// This is part of the lnwire.Message interface.
func (c *ChannelReady) MsgType() MessageType {
return MsgChannelReady
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (c *ChannelReady) SerializedSize() (uint32, error) {
return MessageSerializedSize(c)
}
package lnwire
import (
"bytes"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/tlv"
)
const (
CRDynHeight tlv.Type = 20
)
// DynHeight is a newtype wrapper to get the proper RecordProducer instance
// to smoothly integrate with the ChannelReestablish Message instance.
type DynHeight uint64
// Record implements the RecordProducer interface, allowing a full tlv.Record
// object to be constructed from a DynHeight.
func (d *DynHeight) Record() tlv.Record {
return tlv.MakePrimitiveRecord(CRDynHeight, (*uint64)(d))
}
// ChannelReestablish is a message sent between peers that have an existing
// open channel upon connection reestablishment. This message allows both sides
// to report their local state, and their current knowledge of the state of the
// remote commitment chain. If a deviation is detected and can be recovered
// from, then the necessary messages will be retransmitted. If the level of
// desynchronization is irreconcilable, then the channel will be force closed.
type ChannelReestablish struct {
// ChanID is the channel ID of the channel state we're attempting to
// synchronize with the remote party.
ChanID ChannelID
// NextLocalCommitHeight is the next local commitment height of the
// sending party. If the height of the sender's commitment chain from
// the receiver's Pov is one less that this number, then the sender
// should re-send the *exact* same proposed commitment.
//
// In other words, the receiver should re-send their last sent
// commitment iff:
//
// * NextLocalCommitHeight == remoteCommitChain.Height
//
// This covers the case of a lost commitment which was sent by the
// sender of this message, but never received by the receiver of this
// message.
NextLocalCommitHeight uint64
// RemoteCommitTailHeight is the height of the receiving party's
// unrevoked commitment from the PoV of the sender of this message. If
// the height of the receiver's commitment is *one more* than this
// value, then their prior RevokeAndAck message should be
// retransmitted.
//
// In other words, the receiver should re-send their last sent
// RevokeAndAck message iff:
//
// * localCommitChain.tail().Height == RemoteCommitTailHeight + 1
//
// This covers the case of a lost revocation, wherein the receiver of
// the message sent a revocation for a prior state, but the sender of
// the message never fully processed it.
RemoteCommitTailHeight uint64
// LastRemoteCommitSecret is the last commitment secret that the
// receiving node has sent to the sending party. This will be the
// secret of the last revoked commitment transaction. Including this
// provides proof that the sending node at least knows of this state,
// as they couldn't have produced it if it wasn't sent, as the value
// can be authenticated by querying the shachain or the receiving
// party.
LastRemoteCommitSecret [32]byte
// LocalUnrevokedCommitPoint is the commitment point used in the
// current un-revoked commitment transaction of the sending party.
LocalUnrevokedCommitPoint *btcec.PublicKey
// LocalNonce is an optional field that stores a local musig2 nonce.
// This will only be populated if the simple taproot channels type was
// negotiated.
//
LocalNonce OptMusig2NonceTLV
// DynHeight is an optional field that stores the dynamic commitment
// negotiation height that is incremented upon successful completion of
// a dynamic commitment negotiation
DynHeight fn.Option[DynHeight]
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// A compile time check to ensure ChannelReestablish implements the
// lnwire.Message interface.
var _ Message = (*ChannelReestablish)(nil)
// A compile time check to ensure ChannelReestablish implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*ChannelReestablish)(nil)
// Encode serializes the target ChannelReestablish into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (a *ChannelReestablish) Encode(w *bytes.Buffer, pver uint32) error {
if err := WriteChannelID(w, a.ChanID); err != nil {
return err
}
if err := WriteUint64(w, a.NextLocalCommitHeight); err != nil {
return err
}
if err := WriteUint64(w, a.RemoteCommitTailHeight); err != nil {
return err
}
// If the commit point wasn't sent, then we won't write out any of the
// remaining fields as they're optional.
if a.LocalUnrevokedCommitPoint == nil {
// However, we'll still write out the extra data if it's
// present.
//
// NOTE: This is here primarily for the quickcheck tests, in
// practice, we'll always populate this field.
return WriteBytes(w, a.ExtraData)
}
// Otherwise, we'll write out the remaining elements.
if err := WriteBytes(w, a.LastRemoteCommitSecret[:]); err != nil {
return err
}
if err := WritePublicKey(w, a.LocalUnrevokedCommitPoint); err != nil {
return err
}
recordProducers := make([]tlv.RecordProducer, 0, 1)
a.LocalNonce.WhenSome(func(localNonce Musig2NonceTLV) {
recordProducers = append(recordProducers, &localNonce)
})
a.DynHeight.WhenSome(func(h DynHeight) {
recordProducers = append(recordProducers, &h)
})
err := EncodeMessageExtraData(&a.ExtraData, recordProducers...)
if err != nil {
return err
}
return WriteBytes(w, a.ExtraData)
}
// Decode deserializes a serialized ChannelReestablish stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (a *ChannelReestablish) Decode(r io.Reader, pver uint32) error {
err := ReadElements(r,
&a.ChanID,
&a.NextLocalCommitHeight,
&a.RemoteCommitTailHeight,
)
if err != nil {
return err
}
// This message has currently defined optional fields. As a result,
// we'll only proceed if there's still bytes remaining within the
// reader.
//
// We'll manually parse out the optional fields in order to be able to
// still utilize the io.Reader interface.
// We'll first attempt to read the optional commit secret, if we're at
// the EOF, then this means the field wasn't included so we can exit
// early.
var buf [32]byte
_, err = io.ReadFull(r, buf[:32])
if err == io.EOF {
// If there aren't any more bytes, then we'll emplace an empty
// extra data to make our quickcheck tests happy.
a.ExtraData = make([]byte, 0)
return nil
} else if err != nil {
return err
}
// If the field is present, then we'll copy it over and proceed.
copy(a.LastRemoteCommitSecret[:], buf[:])
// We'll conclude by parsing out the commitment point. We don't check
// the error in this case, as it has included the commit secret, then
// they MUST also include the commit point.
if err = ReadElement(r, &a.LocalUnrevokedCommitPoint); err != nil {
return err
}
var tlvRecords ExtraOpaqueData
if err := ReadElements(r, &tlvRecords); err != nil {
return err
}
var (
dynHeight DynHeight
localNonce = a.LocalNonce.Zero()
)
typeMap, err := tlvRecords.ExtractRecords(
&localNonce, &dynHeight,
)
if err != nil {
return err
}
if val, ok := typeMap[a.LocalNonce.TlvType()]; ok && val == nil {
a.LocalNonce = tlv.SomeRecordT(localNonce)
}
if val, ok := typeMap[CRDynHeight]; ok && val == nil {
a.DynHeight = fn.Some(dynHeight)
}
if len(tlvRecords) != 0 {
a.ExtraData = tlvRecords
}
return nil
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (a *ChannelReestablish) MsgType() MessageType {
return MsgChannelReestablish
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (a *ChannelReestablish) SerializedSize() (uint32, error) {
return MessageSerializedSize(a)
}
package lnwire
import (
"io"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// ChannelTypeRecordType is the type of the experimental record used
// to denote which channel type is being negotiated.
ChannelTypeRecordType tlv.Type = 1
)
// ChannelType represents a specific channel type as a set of feature bits that
// comprise it.
type ChannelType RawFeatureVector
// featureBitLen returns the length in bytes of the encoded feature bits.
func (c ChannelType) featureBitLen() uint64 {
fv := RawFeatureVector(c)
return fv.sizeFunc()
}
// Record returns a TLV record that can be used to encode/decode the channel
// type from a given TLV stream.
func (c *ChannelType) Record() tlv.Record {
return tlv.MakeDynamicRecord(
ChannelTypeRecordType, c, c.featureBitLen, channelTypeEncoder,
channelTypeDecoder,
)
}
// channelTypeEncoder is a custom TLV encoder for the ChannelType record.
func channelTypeEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
if v, ok := val.(*ChannelType); ok {
fv := RawFeatureVector(*v)
return rawFeatureEncoder(w, &fv, buf)
}
return tlv.NewTypeForEncodingErr(val, "*lnwire.ChannelType")
}
// channelTypeDecoder is a custom TLV decoder for the ChannelType record.
func channelTypeDecoder(r io.Reader, val interface{}, buf *[8]byte,
l uint64) error {
if v, ok := val.(*ChannelType); ok {
fv := NewRawFeatureVector()
if err := rawFeatureDecoder(r, fv, buf, l); err != nil {
return err
}
*v = ChannelType(*fv)
return nil
}
return tlv.NewTypeForEncodingErr(val, "*lnwire.ChannelType")
}
package lnwire
import (
"bytes"
"fmt"
"io"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// ChanUpdateMsgFlags is a bitfield that signals whether optional fields are
// present in the ChannelUpdate.
type ChanUpdateMsgFlags uint8
const (
// ChanUpdateRequiredMaxHtlc is a bit that indicates whether the
// required htlc_maximum_msat field is present in this ChannelUpdate.
ChanUpdateRequiredMaxHtlc ChanUpdateMsgFlags = 1 << iota
)
// String returns the bitfield flags as a string.
func (c ChanUpdateMsgFlags) String() string {
return fmt.Sprintf("%08b", c)
}
// HasMaxHtlc returns true if the htlc_maximum_msat option bit is set in the
// message flags.
func (c ChanUpdateMsgFlags) HasMaxHtlc() bool {
return c&ChanUpdateRequiredMaxHtlc != 0
}
// ChanUpdateChanFlags is a bitfield that signals various options concerning a
// particular channel edge. Each bit is to be examined in order to determine
// how the ChannelUpdate message is to be interpreted.
type ChanUpdateChanFlags uint8
const (
// ChanUpdateDirection indicates the direction of a channel update. If
// this bit is set to 0 if Node1 (the node with the "smaller" Node ID)
// is updating the channel, and to 1 otherwise.
ChanUpdateDirection ChanUpdateChanFlags = 1 << iota
// ChanUpdateDisabled is a bit that indicates if the channel edge
// selected by the ChanUpdateDirection bit is to be treated as being
// disabled.
ChanUpdateDisabled
)
// IsDisabled determines whether the channel flags has the disabled bit set.
func (c ChanUpdateChanFlags) IsDisabled() bool {
return c&ChanUpdateDisabled == ChanUpdateDisabled
}
// String returns the bitfield flags as a string.
func (c ChanUpdateChanFlags) String() string {
return fmt.Sprintf("%08b", c)
}
// ChannelUpdate1 message is used after channel has been initially announced.
// Each side independently announces its fees and minimum expiry for HTLCs and
// other parameters. Also this message is used to redeclare initially set
// channel parameters.
type ChannelUpdate1 struct {
// Signature is used to validate the announced data and prove the
// ownership of node id.
Signature Sig
// ChainHash denotes the target chain that this channel was opened
// within. This value should be the genesis hash of the target chain.
// Along with the short channel ID, this uniquely identifies the
// channel globally in a blockchain.
ChainHash chainhash.Hash
// ShortChannelID is the unique description of the funding transaction.
ShortChannelID ShortChannelID
// Timestamp allows ordering in the case of multiple announcements. We
// should ignore the message if timestamp is not greater than
// the last-received.
Timestamp uint32
// MessageFlags is a bitfield that describes whether optional fields
// are present in this update. Currently, the least-significant bit
// must be set to 1 if the optional field MaxHtlc is present.
MessageFlags ChanUpdateMsgFlags
// ChannelFlags is a bitfield that describes additional meta-data
// concerning how the update is to be interpreted. Currently, the
// least-significant bit must be set to 0 if the creating node
// corresponds to the first node in the previously sent channel
// announcement and 1 otherwise. If the second bit is set, then the
// channel is set to be disabled.
ChannelFlags ChanUpdateChanFlags
// TimeLockDelta is the minimum number of blocks this node requires to
// be added to the expiry of HTLCs. This is a security parameter
// determined by the node operator. This value represents the required
// gap between the time locks of the incoming and outgoing HTLC's set
// to this node.
TimeLockDelta uint16
// HtlcMinimumMsat is the minimum HTLC value which will be accepted.
HtlcMinimumMsat MilliSatoshi
// BaseFee is the base fee that must be used for incoming HTLC's to
// this particular channel. This value will be tacked onto the required
// for a payment independent of the size of the payment.
BaseFee uint32
// FeeRate is the fee rate that will be charged per millionth of a
// satoshi.
FeeRate uint32
// HtlcMaximumMsat is the maximum HTLC value which will be accepted.
HtlcMaximumMsat MilliSatoshi
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraOpaqueData ExtraOpaqueData
}
// A compile time check to ensure ChannelUpdate implements the lnwire.Message
// interface.
var _ Message = (*ChannelUpdate1)(nil)
// A compile time check to ensure ChannelUpdate1 implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*ChannelUpdate1)(nil)
// Decode deserializes a serialized ChannelUpdate stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (a *ChannelUpdate1) Decode(r io.Reader, pver uint32) error {
err := ReadElements(r,
&a.Signature,
a.ChainHash[:],
&a.ShortChannelID,
&a.Timestamp,
&a.MessageFlags,
&a.ChannelFlags,
&a.TimeLockDelta,
&a.HtlcMinimumMsat,
&a.BaseFee,
&a.FeeRate,
)
if err != nil {
return err
}
// Now check whether the max HTLC field is present and read it if so.
if a.MessageFlags.HasMaxHtlc() {
if err := ReadElements(r, &a.HtlcMaximumMsat); err != nil {
return err
}
}
return a.ExtraOpaqueData.Decode(r)
}
// Encode serializes the target ChannelUpdate into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (a *ChannelUpdate1) Encode(w *bytes.Buffer, pver uint32) error {
if err := WriteSig(w, a.Signature); err != nil {
return err
}
if err := WriteBytes(w, a.ChainHash[:]); err != nil {
return err
}
if err := WriteShortChannelID(w, a.ShortChannelID); err != nil {
return err
}
if err := WriteUint32(w, a.Timestamp); err != nil {
return err
}
if err := WriteChanUpdateMsgFlags(w, a.MessageFlags); err != nil {
return err
}
if err := WriteChanUpdateChanFlags(w, a.ChannelFlags); err != nil {
return err
}
if err := WriteUint16(w, a.TimeLockDelta); err != nil {
return err
}
if err := WriteMilliSatoshi(w, a.HtlcMinimumMsat); err != nil {
return err
}
if err := WriteUint32(w, a.BaseFee); err != nil {
return err
}
if err := WriteUint32(w, a.FeeRate); err != nil {
return err
}
// Now append optional fields if they are set. Currently, the only
// optional field is max HTLC.
if a.MessageFlags.HasMaxHtlc() {
err := WriteMilliSatoshi(w, a.HtlcMaximumMsat)
if err != nil {
return err
}
}
// Finally, append any extra opaque data.
return WriteBytes(w, a.ExtraOpaqueData)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (a *ChannelUpdate1) MsgType() MessageType {
return MsgChannelUpdate
}
// DataToSign is used to retrieve part of the announcement message which should
// be signed.
func (a *ChannelUpdate1) DataToSign() ([]byte, error) {
// We should not include the signatures itself.
b := make([]byte, 0, MaxMsgBody)
buf := bytes.NewBuffer(b)
if err := WriteBytes(buf, a.ChainHash[:]); err != nil {
return nil, err
}
if err := WriteShortChannelID(buf, a.ShortChannelID); err != nil {
return nil, err
}
if err := WriteUint32(buf, a.Timestamp); err != nil {
return nil, err
}
if err := WriteChanUpdateMsgFlags(buf, a.MessageFlags); err != nil {
return nil, err
}
if err := WriteChanUpdateChanFlags(buf, a.ChannelFlags); err != nil {
return nil, err
}
if err := WriteUint16(buf, a.TimeLockDelta); err != nil {
return nil, err
}
if err := WriteMilliSatoshi(buf, a.HtlcMinimumMsat); err != nil {
return nil, err
}
if err := WriteUint32(buf, a.BaseFee); err != nil {
return nil, err
}
if err := WriteUint32(buf, a.FeeRate); err != nil {
return nil, err
}
// Now append optional fields if they are set. Currently, the only
// optional field is max HTLC.
if a.MessageFlags.HasMaxHtlc() {
err := WriteMilliSatoshi(buf, a.HtlcMaximumMsat)
if err != nil {
return nil, err
}
}
// Finally, append any extra opaque data.
if err := WriteBytes(buf, a.ExtraOpaqueData); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// SCID returns the ShortChannelID of the channel that the update applies to.
//
// NOTE: this is part of the ChannelUpdate interface.
func (a *ChannelUpdate1) SCID() ShortChannelID {
return a.ShortChannelID
}
// IsNode1 is true if the update was produced by node 1 of the channel peers.
// Node 1 is the node with the lexicographically smaller public key.
//
// NOTE: this is part of the ChannelUpdate interface.
func (a *ChannelUpdate1) IsNode1() bool {
return a.ChannelFlags&ChanUpdateDirection == 0
}
// IsDisabled is true if the update is announcing that the channel should be
// considered disabled.
//
// NOTE: this is part of the ChannelUpdate interface.
func (a *ChannelUpdate1) IsDisabled() bool {
return a.ChannelFlags&ChanUpdateDisabled == ChanUpdateDisabled
}
// GetChainHash returns the hash of the chain that the message is referring to.
//
// NOTE: this is part of the ChannelUpdate interface.
func (a *ChannelUpdate1) GetChainHash() chainhash.Hash {
return a.ChainHash
}
// ForwardingPolicy returns the set of forwarding constraints of the update.
//
// NOTE: this is part of the ChannelUpdate interface.
func (a *ChannelUpdate1) ForwardingPolicy() *ForwardingPolicy {
return &ForwardingPolicy{
TimeLockDelta: a.TimeLockDelta,
BaseFee: MilliSatoshi(a.BaseFee),
FeeRate: MilliSatoshi(a.FeeRate),
MinHTLC: a.HtlcMinimumMsat,
HasMaxHTLC: a.MessageFlags.HasMaxHtlc(),
MaxHTLC: a.HtlcMaximumMsat,
}
}
// CmpAge can be used to determine if the update is older or newer than the
// passed update. It returns 1 if this update is newer, -1 if it is older, and
// 0 if they are the same age.
//
// NOTE: this is part of the ChannelUpdate interface.
func (a *ChannelUpdate1) CmpAge(update ChannelUpdate) (CompareResult, error) {
other, ok := update.(*ChannelUpdate1)
if !ok {
return 0, fmt.Errorf("expected *ChannelUpdate1, got: %T",
update)
}
switch {
case a.Timestamp > other.Timestamp:
return GreaterThan, nil
case a.Timestamp < other.Timestamp:
return LessThan, nil
default:
return EqualTo, nil
}
}
// SetDisabledFlag can be used to adjust the disabled flag of an update.
//
// NOTE: this is part of the ChannelUpdate interface.
func (a *ChannelUpdate1) SetDisabledFlag(disabled bool) {
if disabled {
a.ChannelFlags |= ChanUpdateDisabled
} else {
a.ChannelFlags &= ^ChanUpdateDisabled
}
}
// SetSCID can be used to overwrite the SCID of the update.
//
// NOTE: this is part of the ChannelUpdate interface.
func (a *ChannelUpdate1) SetSCID(scid ShortChannelID) {
a.ShortChannelID = scid
}
// A compile time assertion to ensure ChannelUpdate1 implements the
// ChannelUpdate interface.
var _ ChannelUpdate = (*ChannelUpdate1)(nil)
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (a *ChannelUpdate1) SerializedSize() (uint32, error) {
return MessageSerializedSize(a)
}
package lnwire
import (
"bytes"
"fmt"
"io"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/tlv"
)
const (
defaultCltvExpiryDelta = uint16(80)
defaultHtlcMinMsat = MilliSatoshi(1)
defaultFeeBaseMsat = uint32(1000)
defaultFeeProportionalMillionths = uint32(1)
)
// ChannelUpdate2 message is used after taproot channel has been initially
// announced. Each side independently announces its fees and minimum expiry for
// HTLCs and other parameters. This message is also used to redeclare initially
// set channel parameters.
type ChannelUpdate2 struct {
// Signature is used to validate the announced data and prove the
// ownership of node id.
Signature Sig
// ChainHash denotes the target chain that this channel was opened
// within. This value should be the genesis hash of the target chain.
// Along with the short channel ID, this uniquely identifies the
// channel globally in a blockchain.
ChainHash tlv.RecordT[tlv.TlvType0, chainhash.Hash]
// ShortChannelID is the unique description of the funding transaction.
ShortChannelID tlv.RecordT[tlv.TlvType2, ShortChannelID]
// BlockHeight allows ordering in the case of multiple announcements. We
// should ignore the message if block height is not greater than the
// last-received. The block height must always be greater or equal to
// the block height that the channel funding transaction was confirmed
// in.
BlockHeight tlv.RecordT[tlv.TlvType4, uint32]
// DisabledFlags is an optional bitfield that describes various reasons
// that the node is communicating that the channel should be considered
// disabled.
DisabledFlags tlv.RecordT[tlv.TlvType6, ChanUpdateDisableFlags]
// SecondPeer is used to indicate which node the channel node has
// created and signed this message. If this field is present, it was
// node 2 otherwise it was node 1.
SecondPeer tlv.OptionalRecordT[tlv.TlvType8, TrueBoolean]
// CLTVExpiryDelta is the minimum number of blocks this node requires to
// be added to the expiry of HTLCs. This is a security parameter
// determined by the node operator. This value represents the required
// gap between the time locks of the incoming and outgoing HTLC's set
// to this node.
CLTVExpiryDelta tlv.RecordT[tlv.TlvType10, uint16]
// HTLCMinimumMsat is the minimum HTLC value which will be accepted.
HTLCMinimumMsat tlv.RecordT[tlv.TlvType12, MilliSatoshi]
// HtlcMaximumMsat is the maximum HTLC value which will be accepted.
HTLCMaximumMsat tlv.RecordT[tlv.TlvType14, MilliSatoshi]
// FeeBaseMsat is the base fee that must be used for incoming HTLC's to
// this particular channel. This value will be tacked onto the required
// for a payment independent of the size of the payment.
FeeBaseMsat tlv.RecordT[tlv.TlvType16, uint32]
// FeeProportionalMillionths is the fee rate that will be charged per
// millionth of a satoshi.
FeeProportionalMillionths tlv.RecordT[tlv.TlvType18, uint32]
// ExtraOpaqueData is the set of data that was appended to this message
// to fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraOpaqueData ExtraOpaqueData
}
// Decode deserializes a serialized ChannelUpdate2 stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *ChannelUpdate2) Decode(r io.Reader, _ uint32) error {
err := ReadElement(r, &c.Signature)
if err != nil {
return err
}
c.Signature.ForceSchnorr()
return c.DecodeTLVRecords(r)
}
// DecodeTLVRecords decodes only the TLV section of the message.
func (c *ChannelUpdate2) DecodeTLVRecords(r io.Reader) error {
// First extract into extra opaque data.
var tlvRecords ExtraOpaqueData
if err := ReadElements(r, &tlvRecords); err != nil {
return err
}
var (
chainHash = tlv.ZeroRecordT[tlv.TlvType0, [32]byte]()
secondPeer = tlv.ZeroRecordT[tlv.TlvType8, TrueBoolean]()
)
typeMap, err := tlvRecords.ExtractRecords(
&chainHash, &c.ShortChannelID, &c.BlockHeight, &c.DisabledFlags,
&secondPeer, &c.CLTVExpiryDelta, &c.HTLCMinimumMsat,
&c.HTLCMaximumMsat, &c.FeeBaseMsat,
&c.FeeProportionalMillionths,
)
if err != nil {
return err
}
// By default, the chain-hash is the bitcoin mainnet genesis block hash.
c.ChainHash.Val = *chaincfg.MainNetParams.GenesisHash
if _, ok := typeMap[c.ChainHash.TlvType()]; ok {
c.ChainHash.Val = chainHash.Val
}
// The presence of the second_peer tlv type indicates "true".
if _, ok := typeMap[c.SecondPeer.TlvType()]; ok {
c.SecondPeer = tlv.SomeRecordT(secondPeer)
}
// If the CLTV expiry delta was not encoded, then set it to the default
// value.
if _, ok := typeMap[c.CLTVExpiryDelta.TlvType()]; !ok {
c.CLTVExpiryDelta.Val = defaultCltvExpiryDelta
}
// If the HTLC Minimum msat was not encoded, then set it to the default
// value.
if _, ok := typeMap[c.HTLCMinimumMsat.TlvType()]; !ok {
c.HTLCMinimumMsat.Val = defaultHtlcMinMsat
}
// If the base fee was not encoded, then set it to the default value.
if _, ok := typeMap[c.FeeBaseMsat.TlvType()]; !ok {
c.FeeBaseMsat.Val = defaultFeeBaseMsat
}
// If the proportional fee was not encoded, then set it to the default
// value.
if _, ok := typeMap[c.FeeProportionalMillionths.TlvType()]; !ok {
c.FeeProportionalMillionths.Val = defaultFeeProportionalMillionths //nolint:ll
}
if len(tlvRecords) != 0 {
c.ExtraOpaqueData = tlvRecords
}
return nil
}
// Encode serializes the target ChannelUpdate2 into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *ChannelUpdate2) Encode(w *bytes.Buffer, _ uint32) error {
_, err := w.Write(c.Signature.RawBytes())
if err != nil {
return err
}
_, err = c.DataToSign()
if err != nil {
return err
}
return WriteBytes(w, c.ExtraOpaqueData)
}
// DataToSign is used to retrieve part of the announcement message which should
// be signed. For the ChannelUpdate2 message, this includes the serialised TLV
// records.
func (c *ChannelUpdate2) DataToSign() ([]byte, error) {
// The chain-hash record is only included if it is _not_ equal to the
// bitcoin mainnet genisis block hash.
var recordProducers []tlv.RecordProducer
if !c.ChainHash.Val.IsEqual(chaincfg.MainNetParams.GenesisHash) {
hash := tlv.ZeroRecordT[tlv.TlvType0, [32]byte]()
hash.Val = c.ChainHash.Val
recordProducers = append(recordProducers, &hash)
}
recordProducers = append(recordProducers,
&c.ShortChannelID, &c.BlockHeight,
)
// Only include the disable flags if any bit is set.
if !c.DisabledFlags.Val.IsEnabled() {
recordProducers = append(recordProducers, &c.DisabledFlags)
}
// We only need to encode the second peer boolean if it is true
c.SecondPeer.WhenSome(func(r tlv.RecordT[tlv.TlvType8, TrueBoolean]) {
recordProducers = append(recordProducers, &r)
})
// We only encode the cltv expiry delta if it is not equal to the
// default.
if c.CLTVExpiryDelta.Val != defaultCltvExpiryDelta {
recordProducers = append(recordProducers, &c.CLTVExpiryDelta)
}
if c.HTLCMinimumMsat.Val != defaultHtlcMinMsat {
recordProducers = append(recordProducers, &c.HTLCMinimumMsat)
}
recordProducers = append(recordProducers, &c.HTLCMaximumMsat)
if c.FeeBaseMsat.Val != defaultFeeBaseMsat {
recordProducers = append(recordProducers, &c.FeeBaseMsat)
}
if c.FeeProportionalMillionths.Val != defaultFeeProportionalMillionths {
recordProducers = append(
recordProducers, &c.FeeProportionalMillionths,
)
}
err := EncodeMessageExtraData(&c.ExtraOpaqueData, recordProducers...)
if err != nil {
return nil, err
}
return c.ExtraOpaqueData, nil
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *ChannelUpdate2) MsgType() MessageType {
return MsgChannelUpdate2
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (c *ChannelUpdate2) SerializedSize() (uint32, error) {
return MessageSerializedSize(c)
}
func (c *ChannelUpdate2) ExtraData() ExtraOpaqueData {
return c.ExtraOpaqueData
}
// A compile time check to ensure ChannelUpdate2 implements the
// lnwire.Message interface.
var _ Message = (*ChannelUpdate2)(nil)
// SCID returns the ShortChannelID of the channel that the update applies to.
//
// NOTE: this is part of the ChannelUpdate interface.
func (c *ChannelUpdate2) SCID() ShortChannelID {
return c.ShortChannelID.Val
}
// IsNode1 is true if the update was produced by node 1 of the channel peers.
// Node 1 is the node with the lexicographically smaller public key.
//
// NOTE: this is part of the ChannelUpdate interface.
func (c *ChannelUpdate2) IsNode1() bool {
return c.SecondPeer.IsNone()
}
// IsDisabled is true if the update is announcing that the channel should be
// considered disabled.
//
// NOTE: this is part of the ChannelUpdate interface.
func (c *ChannelUpdate2) IsDisabled() bool {
return !c.DisabledFlags.Val.IsEnabled()
}
// GetChainHash returns the hash of the chain that the message is referring to.
//
// NOTE: this is part of the ChannelUpdate interface.
func (c *ChannelUpdate2) GetChainHash() chainhash.Hash {
return c.ChainHash.Val
}
// ForwardingPolicy returns the set of forwarding constraints of the update.
//
// NOTE: this is part of the ChannelUpdate interface.
func (c *ChannelUpdate2) ForwardingPolicy() *ForwardingPolicy {
return &ForwardingPolicy{
TimeLockDelta: c.CLTVExpiryDelta.Val,
BaseFee: MilliSatoshi(c.FeeBaseMsat.Val),
FeeRate: MilliSatoshi(c.FeeProportionalMillionths.Val),
MinHTLC: c.HTLCMinimumMsat.Val,
HasMaxHTLC: true,
MaxHTLC: c.HTLCMaximumMsat.Val,
}
}
// CmpAge can be used to determine if the update is older or newer than the
// passed update. It returns 1 if this update is newer, -1 if it is older, and
// 0 if they are the same age.
//
// NOTE: this is part of the ChannelUpdate interface.
func (c *ChannelUpdate2) CmpAge(update ChannelUpdate) (CompareResult, error) {
other, ok := update.(*ChannelUpdate2)
if !ok {
return 0, fmt.Errorf("expected *ChannelUpdate2, got: %T",
update)
}
switch {
case c.BlockHeight.Val > other.BlockHeight.Val:
return GreaterThan, nil
case c.BlockHeight.Val < other.BlockHeight.Val:
return LessThan, nil
default:
return EqualTo, nil
}
}
// SetDisabledFlag can be used to adjust the disabled flag of an update.
//
// NOTE: this is part of the ChannelUpdate interface.
func (c *ChannelUpdate2) SetDisabledFlag(disabled bool) {
if disabled {
c.DisabledFlags.Val |= ChanUpdateDisableIncoming
c.DisabledFlags.Val |= ChanUpdateDisableOutgoing
} else {
c.DisabledFlags.Val &^= ChanUpdateDisableIncoming
c.DisabledFlags.Val &^= ChanUpdateDisableOutgoing
}
}
// SetSCID can be used to overwrite the SCID of the update.
//
// NOTE: this is part of the ChannelUpdate interface.
func (c *ChannelUpdate2) SetSCID(scid ShortChannelID) {
c.ShortChannelID.Val = scid
}
// A compile time check to ensure ChannelUpdate2 implements the
// lnwire.ChannelUpdate interface.
var _ ChannelUpdate = (*ChannelUpdate2)(nil)
// ChanUpdateDisableFlags is a bit vector that can be used to indicate various
// reasons for the channel being marked as disabled.
type ChanUpdateDisableFlags uint8
const (
// ChanUpdateDisableIncoming is a bit indicates that a channel is
// disabled in the inbound direction meaning that the node broadcasting
// the update is communicating that they cannot receive funds.
ChanUpdateDisableIncoming ChanUpdateDisableFlags = 1 << iota
// ChanUpdateDisableOutgoing is a bit indicates that a channel is
// disabled in the outbound direction meaning that the node broadcasting
// the update is communicating that they cannot send or route funds.
ChanUpdateDisableOutgoing = 2
)
// IncomingDisabled returns true if the ChanUpdateDisableIncoming bit is set.
func (c ChanUpdateDisableFlags) IncomingDisabled() bool {
return c&ChanUpdateDisableIncoming == ChanUpdateDisableIncoming
}
// OutgoingDisabled returns true if the ChanUpdateDisableOutgoing bit is set.
func (c ChanUpdateDisableFlags) OutgoingDisabled() bool {
return c&ChanUpdateDisableOutgoing == ChanUpdateDisableOutgoing
}
// IsEnabled returns true if none of the disable bits are set.
func (c ChanUpdateDisableFlags) IsEnabled() bool {
return c == 0
}
// String returns the bitfield flags as a string.
func (c ChanUpdateDisableFlags) String() string {
return fmt.Sprintf("%08b", c)
}
// Record returns the tlv record for the disable flags.
func (c *ChanUpdateDisableFlags) Record() tlv.Record {
return tlv.MakeStaticRecord(0, c, 1, encodeDisableFlags,
decodeDisableFlags)
}
func encodeDisableFlags(w io.Writer, val interface{}, buf *[8]byte) error {
if v, ok := val.(*ChanUpdateDisableFlags); ok {
flagsInt := uint8(*v)
return tlv.EUint8(w, &flagsInt, buf)
}
return tlv.NewTypeForEncodingErr(val, "lnwire.ChanUpdateDisableFlags")
}
func decodeDisableFlags(r io.Reader, val interface{}, buf *[8]byte,
l uint64) error {
if v, ok := val.(*ChanUpdateDisableFlags); ok {
var flagsInt uint8
err := tlv.DUint8(r, &flagsInt, buf, l)
if err != nil {
return err
}
*v = ChanUpdateDisableFlags(flagsInt)
return nil
}
return tlv.NewTypeForDecodingErr(val, "lnwire.ChanUpdateDisableFlags",
l, l)
}
// TrueBoolean is a record that indicates true or false using the presence of
// the record. If the record is absent, it indicates false. If it is present,
// it indicates true.
type TrueBoolean struct{}
// Record returns the tlv record for the boolean entry.
func (b *TrueBoolean) Record() tlv.Record {
return tlv.MakeStaticRecord(
0, b, 0, booleanEncoder, booleanDecoder,
)
}
func booleanEncoder(_ io.Writer, val interface{}, _ *[8]byte) error {
if _, ok := val.(*TrueBoolean); ok {
return nil
}
return tlv.NewTypeForEncodingErr(val, "TrueBoolean")
}
func booleanDecoder(_ io.Reader, val interface{}, _ *[8]byte,
l uint64) error {
if _, ok := val.(*TrueBoolean); ok && (l == 0 || l == 1) {
return nil
}
return tlv.NewTypeForEncodingErr(val, "TrueBoolean")
}
package lnwire
import (
"bytes"
"io"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/tlv"
)
// ClosingSigs houses the 3 possible signatures that can be sent when
// attempting to complete a cooperative channel closure. A signature will
// either include both outputs, or only one of the outputs from either side.
type ClosingSigs struct {
// CloserNoClosee is a signature that excludes the output of the
// clsoee.
CloserNoClosee tlv.OptionalRecordT[tlv.TlvType1, Sig]
// NoCloserClosee is a signature that excludes the output of the
// closer.
NoCloserClosee tlv.OptionalRecordT[tlv.TlvType2, Sig]
// CloserAndClosee is a signature that includes both outputs.
CloserAndClosee tlv.OptionalRecordT[tlv.TlvType3, Sig]
}
// ClosingComplete is sent by either side to kick off the process of obtaining
// a valid signature on a c o-operative channel closure of their choice.
type ClosingComplete struct {
// ChannelID serves to identify which channel is to be closed.
ChannelID ChannelID
// CloserScript is the script to which the channel funds will be paid
// for the closer (the person sending the ClosingComplete) message.
CloserScript DeliveryAddress
// CloseeScript is the script to which the channel funds will be paid
// (the person receiving the ClosingComplete message).
CloseeScript DeliveryAddress
// FeeSatoshis is the total fee in satoshis that the party to the
// channel would like to propose for the close transaction.
FeeSatoshis btcutil.Amount
// LockTime is the locktime number to be used in the input spending the
// funding transaction.
LockTime uint32
// ClosingSigs houses the 3 possible signatures that can be sent.
ClosingSigs
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// decodeClosingSigs decodes the closing sig TLV records in the passed
// ExtraOpaqueData.
func decodeClosingSigs(c *ClosingSigs, tlvRecords ExtraOpaqueData) error {
sig1 := c.CloserNoClosee.Zero()
sig2 := c.NoCloserClosee.Zero()
sig3 := c.CloserAndClosee.Zero()
typeMap, err := tlvRecords.ExtractRecords(&sig1, &sig2, &sig3)
if err != nil {
return err
}
// TODO(roasbeef): helper func to made decode of the optional vals
// easier?
if val, ok := typeMap[c.CloserNoClosee.TlvType()]; ok && val == nil {
c.CloserNoClosee = tlv.SomeRecordT(sig1)
}
if val, ok := typeMap[c.NoCloserClosee.TlvType()]; ok && val == nil {
c.NoCloserClosee = tlv.SomeRecordT(sig2)
}
if val, ok := typeMap[c.CloserAndClosee.TlvType()]; ok && val == nil {
c.CloserAndClosee = tlv.SomeRecordT(sig3)
}
return nil
}
// Decode deserializes a serialized ClosingComplete message stored in the
// passed io.Reader.
func (c *ClosingComplete) Decode(r io.Reader, _ uint32) error {
// First, read out all the fields that are hard coded into the message.
err := ReadElements(
r, &c.ChannelID, &c.CloserScript, &c.CloseeScript,
&c.FeeSatoshis, &c.LockTime,
)
if err != nil {
return err
}
// With the hard coded messages read, we'll now read out the TLV fields
// of the message.
var tlvRecords ExtraOpaqueData
if err := ReadElements(r, &tlvRecords); err != nil {
return err
}
if err := decodeClosingSigs(&c.ClosingSigs, tlvRecords); err != nil {
return err
}
if len(tlvRecords) != 0 {
c.ExtraData = tlvRecords
}
return nil
}
// closingSigRecords returns the set of records that encode the closing sigs,
// if present.
func closingSigRecords(c *ClosingSigs) []tlv.RecordProducer {
recordProducers := make([]tlv.RecordProducer, 0, 3)
c.CloserNoClosee.WhenSome(func(sig tlv.RecordT[tlv.TlvType1, Sig]) {
recordProducers = append(recordProducers, &sig)
})
c.NoCloserClosee.WhenSome(func(sig tlv.RecordT[tlv.TlvType2, Sig]) {
recordProducers = append(recordProducers, &sig)
})
c.CloserAndClosee.WhenSome(func(sig tlv.RecordT[tlv.TlvType3, Sig]) {
recordProducers = append(recordProducers, &sig)
})
return recordProducers
}
// Encode serializes the target ClosingComplete into the passed io.Writer.
func (c *ClosingComplete) Encode(w *bytes.Buffer, _ uint32) error {
if err := WriteChannelID(w, c.ChannelID); err != nil {
return err
}
if err := WriteDeliveryAddress(w, c.CloserScript); err != nil {
return err
}
if err := WriteDeliveryAddress(w, c.CloseeScript); err != nil {
return err
}
if err := WriteSatoshi(w, c.FeeSatoshis); err != nil {
return err
}
if err := WriteUint32(w, c.LockTime); err != nil {
return err
}
recordProducers := closingSigRecords(&c.ClosingSigs)
err := EncodeMessageExtraData(&c.ExtraData, recordProducers...)
if err != nil {
return err
}
return WriteBytes(w, c.ExtraData)
}
// MsgType returns the uint32 code which uniquely identifies this message as a
// ClosingComplete message on the wire.
//
// This is part of the lnwire.Message interface.
func (c *ClosingComplete) MsgType() MessageType {
return MsgClosingComplete
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (c *ClosingComplete) SerializedSize() (uint32, error) {
return MessageSerializedSize(c)
}
// A compile time check to ensure ClosingComplete implements the lnwire.Message
// interface.
var _ Message = (*ClosingComplete)(nil)
// A compile time check to ensure ClosingComplete implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*ClosingComplete)(nil)
package lnwire
import (
"bytes"
"io"
"github.com/btcsuite/btcd/btcutil"
)
// ClosingSig is sent in response to a ClosingComplete message. It carries the
// signatures of the closee to the closer.
type ClosingSig struct {
// ChannelID serves to identify which channel is to be closed.
ChannelID ChannelID
// CloserScript is the script to which the channel funds will be paid
// for the closer (the person sending the ClosingComplete) message.
CloserScript DeliveryAddress
// CloseeScript is the script to which the channel funds will be paid
// (the person receiving the ClosingComplete message).
CloseeScript DeliveryAddress
// FeeSatoshis is the total fee in satoshis that the party to the
// channel proposed for the close transaction.
FeeSatoshis btcutil.Amount
// LockTime is the locktime number to be used in the input spending the
// funding transaction.
LockTime uint32
// ClosingSigs houses the 3 possible signatures that can be sent.
ClosingSigs
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// Decode deserializes a serialized ClosingSig message stored in the passed
// io.Reader.
func (c *ClosingSig) Decode(r io.Reader, _ uint32) error {
// First, read out all the fields that are hard coded into the message.
err := ReadElements(
r, &c.ChannelID, &c.CloserScript, &c.CloseeScript,
&c.FeeSatoshis, &c.LockTime,
)
if err != nil {
return err
}
// With the hard coded messages read, we'll now read out the TLV fields
// of the message.
var tlvRecords ExtraOpaqueData
if err := ReadElements(r, &tlvRecords); err != nil {
return err
}
if err := decodeClosingSigs(&c.ClosingSigs, tlvRecords); err != nil {
return err
}
if len(tlvRecords) != 0 {
c.ExtraData = tlvRecords
}
return nil
}
// Encode serializes the target ClosingSig into the passed io.Writer.
func (c *ClosingSig) Encode(w *bytes.Buffer, _ uint32) error {
if err := WriteChannelID(w, c.ChannelID); err != nil {
return err
}
if err := WriteDeliveryAddress(w, c.CloserScript); err != nil {
return err
}
if err := WriteDeliveryAddress(w, c.CloseeScript); err != nil {
return err
}
if err := WriteSatoshi(w, c.FeeSatoshis); err != nil {
return err
}
if err := WriteUint32(w, c.LockTime); err != nil {
return err
}
recordProducers := closingSigRecords(&c.ClosingSigs)
err := EncodeMessageExtraData(&c.ExtraData, recordProducers...)
if err != nil {
return err
}
return WriteBytes(w, c.ExtraData)
}
// MsgType returns the uint32 code which uniquely identifies this message as a
// ClosingSig message on the wire.
//
// This is part of the lnwire.Message interface.
func (c *ClosingSig) MsgType() MessageType {
return MsgClosingSig
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (c *ClosingSig) SerializedSize() (uint32, error) {
return MessageSerializedSize(c)
}
// A compile time check to ensure ClosingSig implements the lnwire.Message
// interface.
var _ Message = (*ClosingSig)(nil)
// A compile time check to ensure ClosingSig implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*ClosingSig)(nil)
package lnwire
import (
"bytes"
"io"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/tlv"
)
// ClosingSigned is sent by both parties to a channel once the channel is clear
// of HTLCs, and is primarily concerned with negotiating fees for the close
// transaction. Each party provides a signature for a transaction with a fee
// that they believe is fair. The process terminates when both sides agree on
// the same fee, or when one side force closes the channel.
//
// NOTE: The responder is able to send a signature without any additional
// messages as all transactions are assembled observing BIP 69 which defines a
// canonical ordering for input/outputs. Therefore, both sides are able to
// arrive at an identical closure transaction as they know the order of the
// inputs/outputs.
type ClosingSigned struct {
// ChannelID serves to identify which channel is to be closed.
ChannelID ChannelID
// FeeSatoshis is the total fee in satoshis that the party to the
// channel would like to propose for the close transaction.
FeeSatoshis btcutil.Amount
// Signature is for the proposed channel close transaction.
Signature Sig
// PartialSig is used to transmit a musig2 extended partial signature
// that signs the latest fee offer. The nonce isn't sent along side, as
// that has already been sent in the initial shutdown message.
//
// NOTE: This field is only populated if a musig2 taproot channel is
// being signed for. In this case, the above Sig type MUST be blank.
PartialSig OptPartialSigTLV
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// NewClosingSigned creates a new empty ClosingSigned message.
func NewClosingSigned(cid ChannelID, fs btcutil.Amount,
sig Sig) *ClosingSigned {
return &ClosingSigned{
ChannelID: cid,
FeeSatoshis: fs,
Signature: sig,
}
}
// A compile time check to ensure ClosingSigned implements the lnwire.Message
// interface.
var _ Message = (*ClosingSigned)(nil)
// A compile time check to ensure ClosingSigned implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*ClosingSigned)(nil)
// Decode deserializes a serialized ClosingSigned message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *ClosingSigned) Decode(r io.Reader, pver uint32) error {
err := ReadElements(
r, &c.ChannelID, &c.FeeSatoshis, &c.Signature,
)
if err != nil {
return err
}
var tlvRecords ExtraOpaqueData
if err := ReadElements(r, &tlvRecords); err != nil {
return err
}
partialSig := c.PartialSig.Zero()
typeMap, err := tlvRecords.ExtractRecords(&partialSig)
if err != nil {
return err
}
// Set the corresponding TLV types if they were included in the stream.
if val, ok := typeMap[c.PartialSig.TlvType()]; ok && val == nil {
c.PartialSig = tlv.SomeRecordT(partialSig)
}
if len(tlvRecords) != 0 {
c.ExtraData = tlvRecords
}
return nil
}
// Encode serializes the target ClosingSigned into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *ClosingSigned) Encode(w *bytes.Buffer, pver uint32) error {
recordProducers := make([]tlv.RecordProducer, 0, 1)
c.PartialSig.WhenSome(func(sig PartialSigTLV) {
recordProducers = append(recordProducers, &sig)
})
err := EncodeMessageExtraData(&c.ExtraData, recordProducers...)
if err != nil {
return err
}
if err := WriteChannelID(w, c.ChannelID); err != nil {
return err
}
if err := WriteSatoshi(w, c.FeeSatoshis); err != nil {
return err
}
if err := WriteSig(w, c.Signature); err != nil {
return err
}
return WriteBytes(w, c.ExtraData)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *ClosingSigned) MsgType() MessageType {
return MsgClosingSigned
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (c *ClosingSigned) SerializedSize() (uint32, error) {
return MessageSerializedSize(c)
}
package lnwire
import (
"bytes"
"io"
"github.com/lightningnetwork/lnd/tlv"
)
// CommitSig is sent by either side to stage any pending HTLC's in the
// receiver's pending set into a new commitment state. Implicitly, the new
// commitment transaction constructed which has been signed by CommitSig
// includes all HTLC's in the remote node's pending set. A CommitSig message
// may be sent after a series of UpdateAddHTLC/UpdateFulfillHTLC messages in
// order to batch add several HTLC's with a single signature covering all
// implicitly accepted HTLC's.
type CommitSig struct {
// ChanID uniquely identifies to which currently active channel this
// CommitSig applies to.
ChanID ChannelID
// CommitSig is Alice's signature for Bob's new commitment transaction.
// Alice is able to send this signature without requesting any
// additional data due to the piggybacking of Bob's next revocation
// hash in his prior RevokeAndAck message, as well as the canonical
// ordering used for all inputs/outputs within commitment transactions.
// If initiating a new commitment state, this signature should ONLY
// cover all of the sending party's pending log updates, and the log
// updates of the remote party that have been ACK'd.
CommitSig Sig
// HtlcSigs is a signature for each relevant HTLC output within the
// created commitment. The order of the signatures is expected to be
// identical to the placement of the HTLC's within the BIP 69 sorted
// commitment transaction. For each outgoing HTLC (from the PoV of the
// sender of this message), a signature for an HTLC timeout transaction
// should be signed, for each incoming HTLC the HTLC timeout
// transaction should be signed.
HtlcSigs []Sig
// PartialSig is used to transmit a musig2 extended partial signature
// that also carries along the public nonce of the signer.
//
// NOTE: This field is only populated if a musig2 taproot channel is
// being signed for. In this case, the above Sig type MUST be blank.
PartialSig OptPartialSigWithNonceTLV
// CustomRecords maps TLV types to byte slices, storing arbitrary data
// intended for inclusion in the ExtraData field.
CustomRecords CustomRecords
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// NewCommitSig creates a new empty CommitSig message.
func NewCommitSig() *CommitSig {
return &CommitSig{}
}
// A compile time check to ensure CommitSig implements the lnwire.Message
// interface.
var _ Message = (*CommitSig)(nil)
// A compile time check to ensure CommitSig implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*CommitSig)(nil)
// Decode deserializes a serialized CommitSig message stored in the
// passed io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *CommitSig) Decode(r io.Reader, pver uint32) error {
// msgExtraData is a temporary variable used to read the message extra
// data field from the reader.
var msgExtraData ExtraOpaqueData
err := ReadElements(r,
&c.ChanID,
&c.CommitSig,
&c.HtlcSigs,
&msgExtraData,
)
if err != nil {
return err
}
// Extract TLV records from the extra data field.
partialSig := c.PartialSig.Zero()
customRecords, parsed, extraData, err := ParseAndExtractCustomRecords(
msgExtraData, &partialSig,
)
if err != nil {
return err
}
// Set the corresponding TLV types if they were included in the stream.
if _, ok := parsed[partialSig.TlvType()]; ok {
c.PartialSig = tlv.SomeRecordT(partialSig)
}
c.CustomRecords = customRecords
c.ExtraData = extraData
return nil
}
// Encode serializes the target CommitSig into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *CommitSig) Encode(w *bytes.Buffer, pver uint32) error {
recordProducers := make([]tlv.RecordProducer, 0, 1)
c.PartialSig.WhenSome(func(sig PartialSigWithNonceTLV) {
recordProducers = append(recordProducers, &sig)
})
extraData, err := MergeAndEncode(
recordProducers, c.ExtraData, c.CustomRecords,
)
if err != nil {
return err
}
if err := WriteChannelID(w, c.ChanID); err != nil {
return err
}
if err := WriteSig(w, c.CommitSig); err != nil {
return err
}
if err := WriteSigs(w, c.HtlcSigs); err != nil {
return err
}
return WriteBytes(w, extraData)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *CommitSig) MsgType() MessageType {
return MsgCommitSig
}
// TargetChanID returns the channel id of the link for which this message is
// intended.
//
// NOTE: Part of peer.LinkUpdater interface.
func (c *CommitSig) TargetChanID() ChannelID {
return c.ChanID
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (c *CommitSig) SerializedSize() (uint32, error) {
return MessageSerializedSize(c)
}
package lnwire
import (
"bytes"
"fmt"
"io"
"sync"
)
// CustomTypeStart is the start of the custom type range for peer messages as
// defined in BOLT 01.
const CustomTypeStart MessageType = 32768
var (
// customTypeOverride contains a set of message types < CustomTypeStart
// that lnd allows to be treated as custom messages. This allows us to
// override messages reserved for the protocol level and treat them as
// custom messages. This set of message types is stored as a global so
// that we do not need to pass around state when accounting for this
// set of messages in message creation.
//
// Note: This global is protected by the customTypeOverride mutex.
customTypeOverride map[MessageType]struct{}
// customTypeOverrideMtx manages concurrent access to
// customTypeOverride.
customTypeOverrideMtx sync.RWMutex
)
// SetCustomOverrides validates that the set of override types are outside of
// the custom message range (there's no reason to override messages that are
// already within the range), and updates the customTypeOverride global to hold
// this set of message types. Note that this function will completely overwrite
// the set of overrides, so should be called with the full set of types.
func SetCustomOverrides(overrideTypes []uint16) error {
customTypeOverrideMtx.Lock()
defer customTypeOverrideMtx.Unlock()
customTypeOverride = make(map[MessageType]struct{}, len(overrideTypes))
for _, t := range overrideTypes {
msgType := MessageType(t)
if msgType >= CustomTypeStart {
return fmt.Errorf("can't override type: %v, already "+
"in custom range", t)
}
customTypeOverride[msgType] = struct{}{}
}
return nil
}
// IsCustomOverride returns a bool indicating whether the message type is one
// of the protocol messages that we override for custom use.
func IsCustomOverride(t MessageType) bool {
customTypeOverrideMtx.RLock()
defer customTypeOverrideMtx.RUnlock()
_, ok := customTypeOverride[t]
return ok
}
// Custom represents an application-defined wire message.
type Custom struct {
Type MessageType
Data []byte
}
// A compile time check to ensure Custom implements the lnwire.Message
// interface.
var _ Message = (*Custom)(nil)
// A compile time check to ensure Custom implements the lnwire.SizeableMessage
// interface.
var _ SizeableMessage = (*Custom)(nil)
// NewCustom instantiates a new custom message.
func NewCustom(msgType MessageType, data []byte) (*Custom, error) {
if msgType < CustomTypeStart && !IsCustomOverride(msgType) {
return nil, fmt.Errorf("msg type: %d not in custom range: %v "+
"and not overridden", msgType, CustomTypeStart)
}
return &Custom{
Type: msgType,
Data: data,
}, nil
}
// Encode serializes the target Custom message into the passed io.Writer
// implementation.
//
// This is part of the lnwire.Message interface.
func (c *Custom) Encode(b *bytes.Buffer, pver uint32) error {
_, err := b.Write(c.Data)
return err
}
// Decode deserializes the serialized Custom message stored in the passed
// io.Reader into the target Custom message.
//
// This is part of the lnwire.Message interface.
func (c *Custom) Decode(r io.Reader, pver uint32) error {
var b bytes.Buffer
if _, err := io.Copy(&b, r); err != nil {
return err
}
c.Data = b.Bytes()
return nil
}
// MsgType returns the uint32 code which uniquely identifies this message as a
// Custom message on the wire.
//
// This is part of the lnwire.Message interface.
func (c *Custom) MsgType() MessageType {
return c.Type
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (c *Custom) SerializedSize() (uint32, error) {
return MessageSerializedSize(c)
}
package lnwire
import (
"bytes"
"fmt"
"io"
"maps"
"sort"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// MinCustomRecordsTlvType is the minimum custom records TLV type as
// defined in BOLT 01.
MinCustomRecordsTlvType = 65536
)
// CustomRecords stores a set of custom key/value pairs. Map keys are TLV types
// which must be greater than or equal to MinCustomRecordsTlvType.
type CustomRecords map[uint64][]byte
// NewCustomRecords creates a new CustomRecords instance from a
// tlv.TypeMap.
func NewCustomRecords(tlvMap tlv.TypeMap) (CustomRecords, error) {
// Make comparisons in unit tests easy by returning nil if the map is
// empty.
if len(tlvMap) == 0 {
return nil, nil
}
customRecords := make(CustomRecords, len(tlvMap))
for k, v := range tlvMap {
customRecords[uint64(k)] = v
}
// Validate the custom records.
err := customRecords.Validate()
if err != nil {
return nil, fmt.Errorf("custom records from tlv map "+
"validation error: %w", err)
}
return customRecords, nil
}
// ParseCustomRecords creates a new CustomRecords instance from a tlv.Blob.
func ParseCustomRecords(b tlv.Blob) (CustomRecords, error) {
return ParseCustomRecordsFrom(bytes.NewReader(b))
}
// ParseCustomRecordsFrom creates a new CustomRecords instance from a reader.
func ParseCustomRecordsFrom(r io.Reader) (CustomRecords, error) {
typeMap, err := DecodeRecords(r)
if err != nil {
return nil, fmt.Errorf("error decoding HTLC record: %w", err)
}
return NewCustomRecords(typeMap)
}
// Validate checks that all custom records are in the custom type range.
func (c CustomRecords) Validate() error {
if c == nil {
return nil
}
for key := range c {
if key < MinCustomRecordsTlvType {
return fmt.Errorf("custom records entry with TLV "+
"type below min: %d", MinCustomRecordsTlvType)
}
}
return nil
}
// Copy returns a copy of the custom records.
func (c CustomRecords) Copy() CustomRecords {
if c == nil {
return nil
}
customRecords := make(CustomRecords, len(c))
for k, v := range c {
customRecords[k] = v
}
return customRecords
}
// MergedCopy creates a copy of the records and merges them with the given
// records. If the same key is present in both sets, the value from the other
// records will be used.
func (c CustomRecords) MergedCopy(other CustomRecords) CustomRecords {
copiedRecords := make(CustomRecords, len(c))
maps.Copy(copiedRecords, c)
maps.Copy(copiedRecords, other)
return copiedRecords
}
// ExtendRecordProducers extends the given records slice with the custom
// records. The resultant records slice will be sorted if the given records
// slice contains TLV types greater than or equal to MinCustomRecordsTlvType.
func (c CustomRecords) ExtendRecordProducers(
producers []tlv.RecordProducer) ([]tlv.RecordProducer, error) {
// If the custom records are nil or empty, there is nothing to do.
if len(c) == 0 {
return producers, nil
}
// Validate the custom records.
err := c.Validate()
if err != nil {
return nil, err
}
// Ensure that the existing records slice TLV types are not also present
// in the custom records. If they are, the resultant extended records
// slice would erroneously contain duplicate TLV types.
for _, rp := range producers {
record := rp.Record()
recordTlvType := uint64(record.Type())
_, foundDuplicateTlvType := c[recordTlvType]
if foundDuplicateTlvType {
return nil, fmt.Errorf("custom records contains a TLV "+
"type that is already present in the "+
"existing records: %d", recordTlvType)
}
}
// Convert the custom records map to a TLV record producer slice and
// append them to the exiting records slice.
customRecordProducers := RecordsAsProducers(tlv.MapToRecords(c))
producers = append(producers, customRecordProducers...)
// If the records slice which was given as an argument included TLV
// values greater than or equal to the minimum custom records TLV type
// we will sort the extended records slice to ensure that it is ordered
// correctly.
SortProducers(producers)
return producers, nil
}
// RecordProducers returns a slice of record producers for the custom records.
func (c CustomRecords) RecordProducers() []tlv.RecordProducer {
// If the custom records are nil or empty, return an empty slice.
if len(c) == 0 {
return nil
}
// Convert the custom records map to a TLV record producer slice.
records := tlv.MapToRecords(c)
return RecordsAsProducers(records)
}
// Serialize serializes the custom records into a byte slice.
func (c CustomRecords) Serialize() ([]byte, error) {
records := tlv.MapToRecords(c)
return EncodeRecords(records)
}
// SerializeTo serializes the custom records into the given writer.
func (c CustomRecords) SerializeTo(w io.Writer) error {
records := tlv.MapToRecords(c)
return EncodeRecordsTo(w, records)
}
// ProduceRecordsSorted converts a slice of record producers into a slice of
// records and then sorts it by type.
func ProduceRecordsSorted(recordProducers ...tlv.RecordProducer) []tlv.Record {
records := fn.Map(
recordProducers,
func(producer tlv.RecordProducer) tlv.Record {
return producer.Record()
},
)
// Ensure that the set of records are sorted before we attempt to
// decode from the stream, to ensure they're canonical.
tlv.SortRecords(records)
return records
}
// SortProducers sorts the given record producers by their type.
func SortProducers(producers []tlv.RecordProducer) {
sort.Slice(producers, func(i, j int) bool {
recordI := producers[i].Record()
recordJ := producers[j].Record()
return recordI.Type() < recordJ.Type()
})
}
// TlvMapToRecords converts a TLV map into a slice of records.
func TlvMapToRecords(tlvMap tlv.TypeMap) []tlv.Record {
tlvMapGeneric := make(map[uint64][]byte)
for k, v := range tlvMap {
tlvMapGeneric[uint64(k)] = v
}
return tlv.MapToRecords(tlvMapGeneric)
}
// RecordsAsProducers converts a slice of records into a slice of record
// producers.
func RecordsAsProducers(records []tlv.Record) []tlv.RecordProducer {
return fn.Map(records, func(record tlv.Record) tlv.RecordProducer {
return &record
})
}
// EncodeRecords encodes the given records into a byte slice.
func EncodeRecords(records []tlv.Record) ([]byte, error) {
var buf bytes.Buffer
if err := EncodeRecordsTo(&buf, records); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// EncodeRecordsTo encodes the given records into the given writer.
func EncodeRecordsTo(w io.Writer, records []tlv.Record) error {
tlvStream, err := tlv.NewStream(records...)
if err != nil {
return err
}
return tlvStream.Encode(w)
}
// DecodeRecords decodes the given byte slice into the given records and returns
// the rest as a TLV type map.
func DecodeRecords(r io.Reader,
records ...tlv.Record) (tlv.TypeMap, error) {
tlvStream, err := tlv.NewStream(records...)
if err != nil {
return nil, err
}
return tlvStream.DecodeWithParsedTypes(r)
}
// DecodeRecordsP2P decodes the given byte slice into the given records and
// returns the rest as a TLV type map. This function is identical to
// DecodeRecords except that the record size is capped at 65535.
func DecodeRecordsP2P(r *bytes.Reader,
records ...tlv.Record) (tlv.TypeMap, error) {
tlvStream, err := tlv.NewStream(records...)
if err != nil {
return nil, err
}
return tlvStream.DecodeWithParsedTypesP2P(r)
}
// AssertUniqueTypes asserts that the given records have unique types.
func AssertUniqueTypes(r []tlv.Record) error {
seen := make(fn.Set[tlv.Type], len(r))
for _, record := range r {
t := record.Type()
if seen.Contains(t) {
return fmt.Errorf("duplicate record type: %d", t)
}
seen.Add(t)
}
return nil
}
package lnwire
import (
"bytes"
"io"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// DALocalMusig2Pubnonce is the TLV type number that identifies the
// musig2 public nonce that we need to verify the commitment transaction
// signature.
DALocalMusig2Pubnonce tlv.Type = 0
)
// DynAck is the message used to accept the parameters of a dynamic commitment
// negotiation. Additional optional parameters will need to be present depending
// on the details of the dynamic commitment upgrade.
type DynAck struct {
// ChanID is the ChannelID of the channel that is currently undergoing
// a dynamic commitment negotiation
ChanID ChannelID
// LocalNonce is an optional field that is transmitted when accepting
// a dynamic commitment upgrade to Taproot Channels. This nonce will be
// used to verify the first commitment transaction signature. This will
// only be populated if the DynPropose we are responding to specifies
// taproot channels in the ChannelType field.
LocalNonce fn.Option[Musig2Nonce]
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// A compile time check to ensure DynAck implements the lnwire.Message
// interface.
var _ Message = (*DynAck)(nil)
// A compile time check to ensure DynAck implements the lnwire.SizeableMessage
// interface.
var _ SizeableMessage = (*DynAck)(nil)
// Encode serializes the target DynAck into the passed io.Writer. Serialization
// will observe the rules defined by the passed protocol version.
//
// This is a part of the lnwire.Message interface.
func (da *DynAck) Encode(w *bytes.Buffer, _ uint32) error {
if err := WriteChannelID(w, da.ChanID); err != nil {
return err
}
var tlvRecords []tlv.Record
da.LocalNonce.WhenSome(func(nonce Musig2Nonce) {
tlvRecords = append(
tlvRecords, tlv.MakeStaticRecord(
DALocalMusig2Pubnonce, &nonce,
musig2.PubNonceSize, nonceTypeEncoder,
nonceTypeDecoder,
),
)
})
tlv.SortRecords(tlvRecords)
tlvStream, err := tlv.NewStream(tlvRecords...)
if err != nil {
return err
}
var extraBytesWriter bytes.Buffer
if err := tlvStream.Encode(&extraBytesWriter); err != nil {
return err
}
da.ExtraData = ExtraOpaqueData(extraBytesWriter.Bytes())
return WriteBytes(w, da.ExtraData)
}
// Decode deserializes the serialized DynAck stored in the passed io.Reader into
// the target DynAck using the deserialization rules defined by the passed
// protocol version.
//
// This is a part of the lnwire.Message interface.
func (da *DynAck) Decode(r io.Reader, _ uint32) error {
// Parse out main message.
if err := ReadElements(r, &da.ChanID); err != nil {
return err
}
// Parse out TLV records.
var tlvRecords ExtraOpaqueData
if err := ReadElement(r, &tlvRecords); err != nil {
return err
}
// Prepare receiving buffers to be filled by TLV extraction.
var localNonceScratch Musig2Nonce
localNonce := tlv.MakeStaticRecord(
DALocalMusig2Pubnonce, &localNonceScratch, musig2.PubNonceSize,
nonceTypeEncoder, nonceTypeDecoder,
)
// Create set of Records to read TLV bytestream into.
records := []tlv.Record{localNonce}
tlv.SortRecords(records)
// Read TLV stream into record set.
extraBytesReader := bytes.NewReader(tlvRecords)
tlvStream, err := tlv.NewStream(records...)
if err != nil {
return err
}
typeMap, err := tlvStream.DecodeWithParsedTypesP2P(extraBytesReader)
if err != nil {
return err
}
// Check the results of the TLV Stream decoding and appropriately set
// message fields.
if val, ok := typeMap[DALocalMusig2Pubnonce]; ok && val == nil {
da.LocalNonce = fn.Some(localNonceScratch)
}
if len(tlvRecords) != 0 {
da.ExtraData = tlvRecords
}
return nil
}
// MsgType returns the MessageType code which uniquely identifies this message
// as a DynAck on the wire.
//
// This is part of the lnwire.Message interface.
func (da *DynAck) MsgType() MessageType {
return MsgDynAck
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (da *DynAck) SerializedSize() (uint32, error) {
return MessageSerializedSize(da)
}
package lnwire
import (
"bytes"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// DPDustLimitSatoshis is the TLV type number that identifies the record
// for DynPropose.DustLimit.
DPDustLimitSatoshis tlv.Type = 0
// DPMaxHtlcValueInFlightMsat is the TLV type number that identifies the
// record for DynPropose.MaxValueInFlight.
DPMaxHtlcValueInFlightMsat tlv.Type = 1
// DPChannelReserveSatoshis is the TLV type number that identifies the
// for DynPropose.ChannelReserve.
DPChannelReserveSatoshis tlv.Type = 2
// DPToSelfDelay is the TLV type number that identifies the record for
// DynPropose.CsvDelay.
DPToSelfDelay tlv.Type = 3
// DPMaxAcceptedHtlcs is the TLV type number that identifies the record
// for DynPropose.MaxAcceptedHTLCs.
DPMaxAcceptedHtlcs tlv.Type = 4
// DPFundingPubkey is the TLV type number that identifies the record for
// DynPropose.FundingKey.
DPFundingPubkey tlv.Type = 5
// DPChannelType is the TLV type number that identifies the record for
// DynPropose.ChannelType.
DPChannelType tlv.Type = 6
// DPKickoffFeerate is the TLV type number that identifies the record
// for DynPropose.KickoffFeerate.
DPKickoffFeerate tlv.Type = 7
)
// DynPropose is a message that is sent during a dynamic commitments negotiation
// process. It is sent by both parties to propose new channel parameters.
type DynPropose struct {
// ChanID identifies the channel whose parameters we are trying to
// re-negotiate.
ChanID ChannelID
// Initiator is a byte that identifies whether this message was sent as
// the initiator of a dynamic commitment negotiation or the responder
// of a dynamic commitment negotiation. bool true indicates it is the
// initiator
Initiator bool
// DustLimit, if not nil, proposes a change to the dust_limit_satoshis
// for the sender's commitment transaction.
DustLimit fn.Option[btcutil.Amount]
// MaxValueInFlight, if not nil, proposes a change to the
// max_htlc_value_in_flight_msat limit of the sender.
MaxValueInFlight fn.Option[MilliSatoshi]
// ChannelReserve, if not nil, proposes a change to the
// channel_reserve_satoshis requirement of the recipient.
ChannelReserve fn.Option[btcutil.Amount]
// CsvDelay, if not nil, proposes a change to the to_self_delay
// requirement of the recipient.
CsvDelay fn.Option[uint16]
// MaxAcceptedHTLCs, if not nil, proposes a change to the
// max_accepted_htlcs limit of the sender.
MaxAcceptedHTLCs fn.Option[uint16]
// FundingKey, if not nil, proposes a change to the funding_pubkey
// parameter of the sender.
FundingKey fn.Option[btcec.PublicKey]
// ChannelType, if not nil, proposes a change to the channel_type
// parameter.
ChannelType fn.Option[ChannelType]
// KickoffFeerate proposes the fee rate in satoshis per kw that it
// is offering for a ChannelType conversion that requires a kickoff
// transaction.
KickoffFeerate fn.Option[chainfee.SatPerKWeight]
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
//
// NOTE: Since the fields in this structure are part of the TLV stream,
// ExtraData will contain all TLV records _except_ the ones that are
// present in earlier parts of this structure.
ExtraData ExtraOpaqueData
}
// A compile time check to ensure DynPropose implements the lnwire.Message
// interface.
var _ Message = (*DynPropose)(nil)
// A compile time check to ensure DynPropose implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*DynPropose)(nil)
// Encode serializes the target DynPropose into the passed io.Writer.
// Serialization will observe the rules defined by the passed protocol version.
//
// This is a part of the lnwire.Message interface.
func (dp *DynPropose) Encode(w *bytes.Buffer, _ uint32) error {
var tlvRecords []tlv.Record
dp.DustLimit.WhenSome(func(dl btcutil.Amount) {
protoSats := uint64(dl)
tlvRecords = append(
tlvRecords, tlv.MakePrimitiveRecord(
DPDustLimitSatoshis, &protoSats,
),
)
})
dp.MaxValueInFlight.WhenSome(func(max MilliSatoshi) {
protoSats := uint64(max)
tlvRecords = append(
tlvRecords, tlv.MakePrimitiveRecord(
DPMaxHtlcValueInFlightMsat, &protoSats,
),
)
})
dp.ChannelReserve.WhenSome(func(min btcutil.Amount) {
channelReserve := uint64(min)
tlvRecords = append(
tlvRecords, tlv.MakePrimitiveRecord(
DPChannelReserveSatoshis, &channelReserve,
),
)
})
dp.CsvDelay.WhenSome(func(wait uint16) {
tlvRecords = append(
tlvRecords, tlv.MakePrimitiveRecord(
DPToSelfDelay, &wait,
),
)
})
dp.MaxAcceptedHTLCs.WhenSome(func(max uint16) {
tlvRecords = append(
tlvRecords, tlv.MakePrimitiveRecord(
DPMaxAcceptedHtlcs, &max,
),
)
})
dp.FundingKey.WhenSome(func(key btcec.PublicKey) {
keyScratch := &key
tlvRecords = append(
tlvRecords, tlv.MakePrimitiveRecord(
DPFundingPubkey, &keyScratch,
),
)
})
dp.ChannelType.WhenSome(func(ty ChannelType) {
tlvRecords = append(
tlvRecords, tlv.MakeDynamicRecord(
DPChannelType, &ty,
ty.featureBitLen,
channelTypeEncoder, channelTypeDecoder,
),
)
})
dp.KickoffFeerate.WhenSome(func(kickoffFeerate chainfee.SatPerKWeight) {
protoSats := uint32(kickoffFeerate)
tlvRecords = append(
tlvRecords, tlv.MakePrimitiveRecord(
DPKickoffFeerate, &protoSats,
),
)
})
tlv.SortRecords(tlvRecords)
tlvStream, err := tlv.NewStream(tlvRecords...)
if err != nil {
return err
}
var extraBytesWriter bytes.Buffer
if err := tlvStream.Encode(&extraBytesWriter); err != nil {
return err
}
dp.ExtraData = ExtraOpaqueData(extraBytesWriter.Bytes())
if err := WriteChannelID(w, dp.ChanID); err != nil {
return err
}
if err := WriteBool(w, dp.Initiator); err != nil {
return err
}
return WriteBytes(w, dp.ExtraData)
}
// Decode deserializes the serialized DynPropose stored in the passed io.Reader
// into the target DynPropose using the deserialization rules defined by the
// passed protocol version.
//
// This is a part of the lnwire.Message interface.
func (dp *DynPropose) Decode(r io.Reader, _ uint32) error {
// Parse out the only required field.
if err := ReadElements(r, &dp.ChanID, &dp.Initiator); err != nil {
return err
}
// Parse out TLV stream.
var tlvRecords ExtraOpaqueData
if err := ReadElements(r, &tlvRecords); err != nil {
return err
}
// Prepare receiving buffers to be filled by TLV extraction.
var dustLimitScratch uint64
dustLimit := tlv.MakePrimitiveRecord(
DPDustLimitSatoshis, &dustLimitScratch,
)
var maxValueScratch uint64
maxValue := tlv.MakePrimitiveRecord(
DPMaxHtlcValueInFlightMsat, &maxValueScratch,
)
var reserveScratch uint64
reserve := tlv.MakePrimitiveRecord(
DPChannelReserveSatoshis, &reserveScratch,
)
var csvDelayScratch uint16
csvDelay := tlv.MakePrimitiveRecord(DPToSelfDelay, &csvDelayScratch)
var maxHtlcsScratch uint16
maxHtlcs := tlv.MakePrimitiveRecord(
DPMaxAcceptedHtlcs, &maxHtlcsScratch,
)
var fundingKeyScratch *btcec.PublicKey
fundingKey := tlv.MakePrimitiveRecord(
DPFundingPubkey, &fundingKeyScratch,
)
var chanTypeScratch ChannelType
chanType := tlv.MakeDynamicRecord(
DPChannelType, &chanTypeScratch, chanTypeScratch.featureBitLen,
channelTypeEncoder, channelTypeDecoder,
)
var kickoffFeerateScratch uint32
kickoffFeerate := tlv.MakePrimitiveRecord(
DPKickoffFeerate, &kickoffFeerateScratch,
)
// Create set of Records to read TLV bytestream into.
records := []tlv.Record{
dustLimit, maxValue, reserve, csvDelay, maxHtlcs, fundingKey,
chanType, kickoffFeerate,
}
tlv.SortRecords(records)
// Read TLV stream into record set.
extraBytesReader := bytes.NewReader(tlvRecords)
tlvStream, err := tlv.NewStream(records...)
if err != nil {
return err
}
typeMap, err := tlvStream.DecodeWithParsedTypesP2P(extraBytesReader)
if err != nil {
return err
}
// Check the results of the TLV Stream decoding and appropriately set
// message fields.
if val, ok := typeMap[DPDustLimitSatoshis]; ok && val == nil {
dp.DustLimit = fn.Some(btcutil.Amount(dustLimitScratch))
}
if val, ok := typeMap[DPMaxHtlcValueInFlightMsat]; ok && val == nil {
dp.MaxValueInFlight = fn.Some(MilliSatoshi(maxValueScratch))
}
if val, ok := typeMap[DPChannelReserveSatoshis]; ok && val == nil {
dp.ChannelReserve = fn.Some(btcutil.Amount(reserveScratch))
}
if val, ok := typeMap[DPToSelfDelay]; ok && val == nil {
dp.CsvDelay = fn.Some(csvDelayScratch)
}
if val, ok := typeMap[DPMaxAcceptedHtlcs]; ok && val == nil {
dp.MaxAcceptedHTLCs = fn.Some(maxHtlcsScratch)
}
if val, ok := typeMap[DPFundingPubkey]; ok && val == nil {
dp.FundingKey = fn.Some(*fundingKeyScratch)
}
if val, ok := typeMap[DPChannelType]; ok && val == nil {
dp.ChannelType = fn.Some(chanTypeScratch)
}
if val, ok := typeMap[DPKickoffFeerate]; ok && val == nil {
dp.KickoffFeerate = fn.Some(
chainfee.SatPerKWeight(kickoffFeerateScratch),
)
}
if len(tlvRecords) != 0 {
dp.ExtraData = tlvRecords
}
return nil
}
// MsgType returns the MessageType code which uniquely identifies this message
// as a DynPropose on the wire.
//
// This is part of the lnwire.Message interface.
func (dp *DynPropose) MsgType() MessageType {
return MsgDynPropose
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (dp *DynPropose) SerializedSize() (uint32, error) {
return MessageSerializedSize(dp)
}
package lnwire
import (
"bytes"
"io"
)
// DynReject is a message that is sent during a dynamic commitments negotiation
// process. It is sent by both parties to propose new channel parameters.
type DynReject struct {
// ChanID identifies the channel whose parameters we are trying to
// re-negotiate.
ChanID ChannelID
// UpdateRejections is a bit vector that specifies which of the
// DynPropose parameters we wish to call out as being unacceptable.
UpdateRejections RawFeatureVector
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
//
// NOTE: Since the fields in this structure are part of the TLV stream,
// ExtraData will contain all TLV records _except_ the ones that are
// present in earlier parts of this structure.
ExtraData ExtraOpaqueData
}
// A compile time check to ensure DynReject implements the lnwire.Message
// interface.
var _ Message = (*DynReject)(nil)
// A compile time check to ensure DynReject implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*DynReject)(nil)
// Encode serializes the target DynReject into the passed io.Writer.
// Serialization will observe the rules defined by the passed protocol version.
//
// This is a part of the lnwire.Message interface.
func (dr *DynReject) Encode(w *bytes.Buffer, _ uint32) error {
if err := WriteChannelID(w, dr.ChanID); err != nil {
return err
}
if err := WriteRawFeatureVector(w, &dr.UpdateRejections); err != nil {
return err
}
return WriteBytes(w, dr.ExtraData)
}
// Decode deserializes the serialized DynReject stored in the passed io.Reader
// into the target DynReject using the deserialization rules defined by the
// passed protocol version.
//
// This is a part of the lnwire.Message interface.
func (dr *DynReject) Decode(r io.Reader, _ uint32) error {
var extra ExtraOpaqueData
if err := ReadElements(
r, &dr.ChanID, &dr.UpdateRejections, &extra,
); err != nil {
return err
}
if len(extra) != 0 {
dr.ExtraData = extra
}
return nil
}
// MsgType returns the MessageType code which uniquely identifies this message
// as a DynReject on the wire.
//
// This is part of the lnwire.Message interface.
func (dr *DynReject) MsgType() MessageType {
return MsgDynReject
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (dr *DynReject) SerializedSize() (uint32, error) {
return MessageSerializedSize(dr)
}
package lnwire
import "github.com/lightningnetwork/lnd/tlv"
// QueryEncoding is an enum-like type that represents exactly how a set data is
// encoded on the wire.
type QueryEncoding uint8
const (
// EncodingSortedPlain signals that the set of data is encoded using the
// regular encoding, in a sorted order.
EncodingSortedPlain QueryEncoding = 0
// EncodingSortedZlib signals that the set of data is encoded by first
// sorting the set of channel ID's, as then compressing them using zlib.
//
// NOTE: this should no longer be used or accepted.
EncodingSortedZlib QueryEncoding = 1
)
// recordProducer is a simple helper struct that implements the
// tlv.RecordProducer interface.
type recordProducer struct {
record tlv.Record
}
// Record returns the underlying record.
func (r *recordProducer) Record() tlv.Record {
return r.record
}
// Ensure that recordProducer implements the tlv.RecordProducer interface.
var _ tlv.RecordProducer = (*recordProducer)(nil)
package lnwire
import (
"bytes"
"fmt"
"io"
)
// FundingError represents a set of errors that can be encountered and sent
// during the funding workflow.
type FundingError uint8
const (
// ErrMaxPendingChannels is returned by remote peer when the number of
// active pending channels exceeds their maximum policy limit.
ErrMaxPendingChannels FundingError = 1
// ErrChanTooLarge is returned by a remote peer that receives a
// FundingOpen request for a channel that is above their current
// soft-limit.
ErrChanTooLarge FundingError = 2
)
// String returns a human readable version of the target FundingError.
func (e FundingError) String() string {
switch e {
case ErrMaxPendingChannels:
return "Number of pending channels exceed maximum"
case ErrChanTooLarge:
return "channel too large"
default:
return "unknown error"
}
}
// Error returns the human readable version of the target FundingError.
//
// NOTE: Satisfies the Error interface.
func (e FundingError) Error() string {
return e.String()
}
// ErrorData is a set of bytes associated with a particular sent error. A
// receiving node SHOULD only print out data verbatim if the string is composed
// solely of printable ASCII characters. For reference, the printable character
// set includes byte values 32 through 127 inclusive.
type ErrorData []byte
// Error represents a generic error bound to an exact channel. The message
// format is purposefully general in order to allow expression of a wide array
// of possible errors. Each Error message is directed at a particular open
// channel referenced by ChannelPoint.
type Error struct {
// ChanID references the active channel in which the error occurred
// within. If the ChanID is all zeros, then this error applies to the
// entire established connection.
ChanID ChannelID
// Data is the attached error data that describes the exact failure
// which caused the error message to be sent.
Data ErrorData
}
// NewError creates a new Error message.
func NewError() *Error {
return &Error{}
}
// A compile time check to ensure Error implements the lnwire.Message
// interface.
var _ Message = (*Error)(nil)
// A compile time check to ensure Error implements the lnwire.SizeableMessage
// interface.
var _ SizeableMessage = (*Error)(nil)
// Error returns the string representation to Error.
//
// NOTE: Satisfies the error interface.
func (c *Error) Error() string {
errMsg := "non-ascii data"
if isASCII(c.Data) {
errMsg = string(c.Data)
}
return fmt.Sprintf("chan_id=%v, err=%v", c.ChanID, errMsg)
}
// Decode deserializes a serialized Error message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *Error) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&c.ChanID,
&c.Data,
)
}
// Encode serializes the target Error into the passed io.Writer observing the
// protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *Error) Encode(w *bytes.Buffer, pver uint32) error {
if err := WriteBytes(w, c.ChanID[:]); err != nil {
return err
}
return WriteErrorData(w, c.Data)
}
// MsgType returns the integer uniquely identifying an Error message on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *Error) MsgType() MessageType {
return MsgError
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (c *Error) SerializedSize() (uint32, error) {
return MessageSerializedSize(c)
}
// isASCII is a helper method that checks whether all bytes in `data` would be
// printable ASCII characters if interpreted as a string.
func isASCII(data []byte) bool {
for _, c := range data {
if c < 32 || c > 126 {
return false
}
}
return true
}
package lnwire
import (
"bytes"
"fmt"
"io"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/tlv"
)
// ExtraOpaqueData is the set of data that was appended to this message, some
// of which we may not actually know how to iterate or parse. By holding onto
// this data, we ensure that we're able to properly validate the set of
// signatures that cover these new fields, and ensure we're able to make
// upgrades to the network in a forwards compatible manner.
type ExtraOpaqueData []byte
// NewExtraOpaqueData creates a new ExtraOpaqueData instance from a tlv.TypeMap.
func NewExtraOpaqueData(tlvMap tlv.TypeMap) (ExtraOpaqueData, error) {
// If the tlv map is empty, we'll want to mirror the behavior of
// decoding an empty extra opaque data field (see Decode method).
if len(tlvMap) == 0 {
return make([]byte, 0), nil
}
// Convert the TLV map into a slice of records.
records := TlvMapToRecords(tlvMap)
// Encode the records into the extra data byte slice.
return EncodeRecords(records)
}
// Encode attempts to encode the raw extra bytes into the passed io.Writer.
func (e *ExtraOpaqueData) Encode(w *bytes.Buffer) error {
eBytes := []byte((*e)[:])
if err := WriteBytes(w, eBytes); err != nil {
return err
}
return nil
}
// Decode attempts to unpack the raw bytes encoded in the passed-in io.Reader as
// a set of extra opaque data.
func (e *ExtraOpaqueData) Decode(r io.Reader) error {
// First, we'll attempt to read a set of bytes contained within the
// passed io.Reader (if any exist).
rawBytes, err := io.ReadAll(r)
if err != nil {
return err
}
// If we _do_ have some bytes, then we'll swap out our backing pointer.
// This ensures that any struct that embeds this type will properly
// store the bytes once this method exits.
if len(rawBytes) > 0 {
*e = rawBytes
} else {
*e = make([]byte, 0)
}
return nil
}
// PackRecords attempts to encode the set of tlv records into the target
// ExtraOpaqueData instance. The records will be encoded as a raw TLV stream
// and stored within the backing slice pointer.
func (e *ExtraOpaqueData) PackRecords(
recordProducers ...tlv.RecordProducer) error {
// Assemble all the records passed in series, then encode them.
records := ProduceRecordsSorted(recordProducers...)
encoded, err := EncodeRecords(records)
if err != nil {
return err
}
*e = encoded
return nil
}
// ExtractRecords attempts to decode any types in the internal raw bytes as if
// it were a tlv stream. The set of raw parsed types is returned, and any
// passed records (if found in the stream) will be parsed into the proper
// tlv.Record.
func (e *ExtraOpaqueData) ExtractRecords(
recordProducers ...tlv.RecordProducer) (tlv.TypeMap, error) {
// First, assemble all the records passed in series.
records := ProduceRecordsSorted(recordProducers...)
extraBytesReader := bytes.NewReader(*e)
// Since ExtraOpaqueData is provided by a potentially malicious peer,
// pass it into the P2P decoding variant.
return DecodeRecordsP2P(extraBytesReader, records...)
}
// RecordProducers parses ExtraOpaqueData into a slice of TLV record producers
// by interpreting it as a TLV map.
func (e *ExtraOpaqueData) RecordProducers() ([]tlv.RecordProducer, error) {
var recordProducers []tlv.RecordProducer
// If the instance is nil or empty, return an empty slice.
if e == nil || len(*e) == 0 {
return recordProducers, nil
}
// Parse the extra opaque data as a TLV map.
tlvMap, err := e.ExtractRecords()
if err != nil {
return nil, err
}
// Convert the TLV map into a slice of record producers.
records := TlvMapToRecords(tlvMap)
return RecordsAsProducers(records), nil
}
// EncodeMessageExtraData encodes the given recordProducers into the given
// extraData.
func EncodeMessageExtraData(extraData *ExtraOpaqueData,
recordProducers ...tlv.RecordProducer) error {
// Treat extraData as a mutable reference.
if extraData == nil {
return fmt.Errorf("extra data cannot be nil")
}
// Pack in the series of TLV records into this message. The order we
// pass them in doesn't matter, as the method will ensure that things
// are all properly sorted.
return extraData.PackRecords(recordProducers...)
}
// ParseAndExtractCustomRecords parses the given extra data into the passed-in
// records, then returns any remaining records split into custom records and
// extra data.
func ParseAndExtractCustomRecords(allExtraData ExtraOpaqueData,
knownRecords ...tlv.RecordProducer) (CustomRecords,
fn.Set[tlv.Type], ExtraOpaqueData, error) {
extraDataTlvMap, err := allExtraData.ExtractRecords(knownRecords...)
if err != nil {
return nil, nil, nil, err
}
// Remove the known and now extracted records from the leftover extra
// data map.
parsedKnownRecords := make(fn.Set[tlv.Type], len(knownRecords))
for _, producer := range knownRecords {
r := producer.Record()
// Only remove the records if it was parsed (remainder is nil).
// We'll just store the type so we can tell the caller which
// records were actually parsed fully.
val, ok := extraDataTlvMap[r.Type()]
if ok && val == nil {
parsedKnownRecords.Add(r.Type())
delete(extraDataTlvMap, r.Type())
}
}
// Any records from the extra data TLV map which are in the custom
// records TLV type range will be included in the custom records field
// and removed from the extra data field.
customRecordsTlvMap := make(tlv.TypeMap, len(extraDataTlvMap))
for k, v := range extraDataTlvMap {
// Skip records that are not in the custom records TLV type
// range.
if k < MinCustomRecordsTlvType {
continue
}
// Include the record in the custom records map.
customRecordsTlvMap[k] = v
// Now that the record is included in the custom records map,
// we can remove it from the extra data TLV map.
delete(extraDataTlvMap, k)
}
// Set the custom records field to the custom records specific TLV
// record map.
customRecords, err := NewCustomRecords(customRecordsTlvMap)
if err != nil {
return nil, nil, nil, err
}
// Encode the remaining records back into the extra data field. These
// records are not in the custom records TLV type range and do not
// have associated fields in the struct that produced the records.
extraData, err := NewExtraOpaqueData(extraDataTlvMap)
if err != nil {
return nil, nil, nil, err
}
// Help with unit testing where we might have the empty value (nil) for
// the extra data instead of the default that's returned by the
// constructor (empty slice).
if len(extraData) == 0 {
extraData = nil
}
return customRecords, parsedKnownRecords, extraData, nil
}
// MergeAndEncode merges the known records with the extra data and custom
// records, then encodes the merged records into raw bytes.
func MergeAndEncode(knownRecords []tlv.RecordProducer,
extraData ExtraOpaqueData, customRecords CustomRecords) ([]byte,
error) {
// Construct a slice of all the records that we should include in the
// message extra data field. We will start by including any records from
// the extra data field.
mergedRecords, err := extraData.RecordProducers()
if err != nil {
return nil, err
}
// Merge the known and extra data records.
mergedRecords = append(mergedRecords, knownRecords...)
// Include custom records in the extra data wire field if they are
// present. Ensure that the custom records are validated before encoding
// them.
if err := customRecords.Validate(); err != nil {
return nil, fmt.Errorf("custom records validation error: %w",
err)
}
// Extend the message extra data records slice with TLV records from the
// custom records field.
mergedRecords = append(
mergedRecords, customRecords.RecordProducers()...,
)
// Now we can sort the records and make sure there are no records with
// the same type that would collide when encoding.
sortedRecords := ProduceRecordsSorted(mergedRecords...)
if err := AssertUniqueTypes(sortedRecords); err != nil {
return nil, err
}
return EncodeRecords(sortedRecords)
}
package lnwire
import (
"encoding/binary"
"errors"
"fmt"
"io"
"github.com/lightningnetwork/lnd/tlv"
)
var (
// ErrFeaturePairExists signals an error in feature vector construction
// where the opposing bit in a feature pair has already been set.
ErrFeaturePairExists = errors.New("feature pair exists")
// ErrFeatureStandard is returned when attempts to modify LND's known
// set of features are made.
ErrFeatureStandard = errors.New("feature is used in standard " +
"protocol set")
// ErrFeatureBitMaximum is returned when a feature bit exceeds the
// maximum allowable value.
ErrFeatureBitMaximum = errors.New("feature bit exceeds allowed maximum")
)
// FeatureBit represents a feature that can be enabled in either a local or
// global feature vector at a specific bit position. Feature bits follow the
// "it's OK to be odd" rule, where features at even bit positions must be known
// to a node receiving them from a peer while odd bits do not. In accordance,
// feature bits are usually assigned in pairs, first being assigned an odd bit
// position which may later be changed to the preceding even position once
// knowledge of the feature becomes required on the network.
type FeatureBit uint16
const (
// DataLossProtectRequired is a feature bit that indicates that a peer
// *requires* the other party know about the data-loss-protect optional
// feature. If the remote peer does not know of such a feature, then
// the sending peer SHOULD disconnect them. The data-loss-protect
// feature allows a peer that's lost partial data to recover their
// settled funds of the latest commitment state.
DataLossProtectRequired FeatureBit = 0
// DataLossProtectOptional is an optional feature bit that indicates
// that the sending peer knows of this new feature and can activate it
// it. The data-loss-protect feature allows a peer that's lost partial
// data to recover their settled funds of the latest commitment state.
DataLossProtectOptional FeatureBit = 1
// InitialRoutingSync is a local feature bit meaning that the receiving
// node should send a complete dump of routing information when a new
// connection is established.
InitialRoutingSync FeatureBit = 3
// UpfrontShutdownScriptRequired is a feature bit which indicates that a
// peer *requires* that the remote peer accept an upfront shutdown script to
// which payout is enforced on cooperative closes.
UpfrontShutdownScriptRequired FeatureBit = 4
// UpfrontShutdownScriptOptional is an optional feature bit which indicates
// that the peer will accept an upfront shutdown script to which payout is
// enforced on cooperative closes.
UpfrontShutdownScriptOptional FeatureBit = 5
// GossipQueriesRequired is a feature bit that indicates that the
// receiving peer MUST know of the set of features that allows nodes to
// more efficiently query the network view of peers on the network for
// reconciliation purposes.
GossipQueriesRequired FeatureBit = 6
// GossipQueriesOptional is an optional feature bit that signals that
// the setting peer knows of the set of features that allows more
// efficient network view reconciliation.
GossipQueriesOptional FeatureBit = 7
// TLVOnionPayloadRequired is a feature bit that indicates a node is
// able to decode the new TLV information included in the onion packet.
TLVOnionPayloadRequired FeatureBit = 8
// TLVOnionPayloadOptional is an optional feature bit that indicates a
// node is able to decode the new TLV information included in the onion
// packet.
TLVOnionPayloadOptional FeatureBit = 9
// StaticRemoteKeyRequired is a required feature bit that signals that
// within one's commitment transaction, the key used for the remote
// party's non-delay output should not be tweaked.
StaticRemoteKeyRequired FeatureBit = 12
// StaticRemoteKeyOptional is an optional feature bit that signals that
// within one's commitment transaction, the key used for the remote
// party's non-delay output should not be tweaked.
StaticRemoteKeyOptional FeatureBit = 13
// PaymentAddrRequired is a required feature bit that signals that a
// node requires payment addresses, which are used to mitigate probing
// attacks on the receiver of a payment.
PaymentAddrRequired FeatureBit = 14
// PaymentAddrOptional is an optional feature bit that signals that a
// node supports payment addresses, which are used to mitigate probing
// attacks on the receiver of a payment.
PaymentAddrOptional FeatureBit = 15
// MPPRequired is a required feature bit that signals that the receiver
// of a payment requires settlement of an invoice with more than one
// HTLC.
MPPRequired FeatureBit = 16
// MPPOptional is an optional feature bit that signals that the receiver
// of a payment supports settlement of an invoice with more than one
// HTLC.
MPPOptional FeatureBit = 17
// WumboChannelsRequired is a required feature bit that signals that a
// node is willing to accept channels larger than 2^24 satoshis.
WumboChannelsRequired FeatureBit = 18
// WumboChannelsOptional is an optional feature bit that signals that a
// node is willing to accept channels larger than 2^24 satoshis.
WumboChannelsOptional FeatureBit = 19
// AnchorsRequired is a required feature bit that signals that the node
// requires channels to be made using commitments having anchor
// outputs.
AnchorsRequired FeatureBit = 20
// AnchorsOptional is an optional feature bit that signals that the
// node supports channels to be made using commitments having anchor
// outputs.
AnchorsOptional FeatureBit = 21
// AnchorsZeroFeeHtlcTxRequired is a required feature bit that signals
// that the node requires channels having zero-fee second-level HTLC
// transactions, which also imply anchor commitments.
AnchorsZeroFeeHtlcTxRequired FeatureBit = 22
// AnchorsZeroFeeHtlcTxOptional is an optional feature bit that signals
// that the node supports channels having zero-fee second-level HTLC
// transactions, which also imply anchor commitments.
AnchorsZeroFeeHtlcTxOptional FeatureBit = 23
// RouteBlindingRequired is a required feature bit that signals that
// the node supports blinded payments.
RouteBlindingRequired FeatureBit = 24
// RouteBlindingOptional is an optional feature bit that signals that
// the node supports blinded payments.
RouteBlindingOptional FeatureBit = 25
// ShutdownAnySegwitRequired is an required feature bit that signals
// that the sender is able to properly handle/parse segwit witness
// programs up to version 16. This enables utilization of Taproot
// addresses for cooperative closure addresses.
ShutdownAnySegwitRequired FeatureBit = 26
// ShutdownAnySegwitOptional is an optional feature bit that signals
// that the sender is able to properly handle/parse segwit witness
// programs up to version 16. This enables utilization of Taproot
// addresses for cooperative closure addresses.
ShutdownAnySegwitOptional FeatureBit = 27
// AMPRequired is a required feature bit that signals that the receiver
// of a payment supports accepts spontaneous payments, i.e.
// sender-generated preimages according to BOLT XX.
AMPRequired FeatureBit = 30
// AMPOptional is an optional feature bit that signals that the receiver
// of a payment supports accepts spontaneous payments, i.e.
// sender-generated preimages according to BOLT XX.
AMPOptional FeatureBit = 31
// QuiescenceRequired is a required feature bit that denotes that a
// connection established with this node must support the quiescence
// protocol if it wants to have a channel relationship.
QuiescenceRequired FeatureBit = 34
// QuiescenceOptional is an optional feature bit that denotes that a
// connection established with this node is permitted to use the
// quiescence protocol.
QuiescenceOptional FeatureBit = 35
// ExplicitChannelTypeRequired is a required bit that denotes that a
// connection established with this node is to use explicit channel
// commitment types for negotiation instead of the existing implicit
// negotiation methods. With this bit, there is no longer a "default"
// implicit channel commitment type, allowing a connection to
// open/maintain types of several channels over its lifetime.
ExplicitChannelTypeRequired = 44
// ExplicitChannelTypeOptional is an optional bit that denotes that a
// connection established with this node is to use explicit channel
// commitment types for negotiation instead of the existing implicit
// negotiation methods. With this bit, there is no longer a "default"
// implicit channel commitment type, allowing a connection to
// TODO: Decide on actual feature bit value.
ExplicitChannelTypeOptional = 45
// ScidAliasRequired is a required feature bit that signals that the
// node requires understanding of ShortChannelID aliases in the TLV
// segment of the channel_ready message.
ScidAliasRequired FeatureBit = 46
// ScidAliasOptional is an optional feature bit that signals that the
// node understands ShortChannelID aliases in the TLV segment of the
// channel_ready message.
ScidAliasOptional FeatureBit = 47
// PaymentMetadataRequired is a required bit that denotes that if an
// invoice contains metadata, it must be passed along with the payment
// htlc(s).
PaymentMetadataRequired = 48
// PaymentMetadataOptional is an optional bit that denotes that if an
// invoice contains metadata, it may be passed along with the payment
// htlc(s).
PaymentMetadataOptional = 49
// ZeroConfRequired is a required feature bit that signals that the
// node requires understanding of the zero-conf channel_type.
ZeroConfRequired FeatureBit = 50
// ZeroConfOptional is an optional feature bit that signals that the
// node understands the zero-conf channel type.
ZeroConfOptional FeatureBit = 51
// KeysendRequired is a required bit that indicates that the node is
// able and willing to accept keysend payments.
KeysendRequired = 54
// KeysendOptional is an optional bit that indicates that the node is
// able and willing to accept keysend payments.
KeysendOptional = 55
// RbfCoopCloseRequired is a required feature bit that signals that
// the new RBF-based co-op close protocol is supported.
RbfCoopCloseRequired = 60
// RbfCoopCloseOptional is an optional feature bit that signals that the
// new RBF-based co-op close protocol is supported.
RbfCoopCloseOptional = 61
// RbfCoopCloseRequiredStaging is a required feature bit that signals
// that the new RBF-based co-op close protocol is supported.
RbfCoopCloseRequiredStaging = 160
// RbfCoopCloseOptionalStaging is an optional feature bit that signals
// that the new RBF-based co-op close protocol is supported.
RbfCoopCloseOptionalStaging = 161
// ScriptEnforcedLeaseRequired is a required feature bit that signals
// that the node requires channels having zero-fee second-level HTLC
// transactions, which also imply anchor commitments, along with an
// additional CLTV constraint of a channel lease's expiration height
// applied to all outputs that pay directly to the channel initiator.
//
// TODO: Decide on actual feature bit value.
ScriptEnforcedLeaseRequired FeatureBit = 2022
// ScriptEnforcedLeaseOptional is an optional feature bit that signals
// that the node requires channels having zero-fee second-level HTLC
// transactions, which also imply anchor commitments, along with an
// additional CLTV constraint of a channel lease's expiration height
// applied to all outputs that pay directly to the channel initiator.
//
// TODO: Decide on actual feature bit value.
ScriptEnforcedLeaseOptional FeatureBit = 2023
// SimpleTaprootChannelsRequiredFinal is a required bit that indicates
// the node is able to create taproot-native channels. This is the
// final feature bit to be used once the channel type is finalized.
SimpleTaprootChannelsRequiredFinal = 80
// SimpleTaprootChannelsOptionalFinal is an optional bit that indicates
// the node is able to create taproot-native channels. This is the
// final feature bit to be used once the channel type is finalized.
SimpleTaprootChannelsOptionalFinal = 81
// SimpleTaprootChannelsRequiredStaging is a required bit that indicates
// the node is able to create taproot-native channels. This is a
// feature bit used in the wild while the channel type is still being
// finalized.
SimpleTaprootChannelsRequiredStaging = 180
// SimpleTaprootChannelsOptionalStaging is an optional bit that
// indicates the node is able to create taproot-native channels. This
// is a feature bit used in the wild while the channel type is still
// being finalized.
SimpleTaprootChannelsOptionalStaging = 181
// ExperimentalEndorsementRequired is a required feature bit that
// indicates that the node will relay experimental endorsement signals.
ExperimentalEndorsementRequired FeatureBit = 260
// ExperimentalEndorsementOptional is an optional feature bit that
// indicates that the node will relay experimental endorsement signals.
ExperimentalEndorsementOptional FeatureBit = 261
// Bolt11BlindedPathsRequired is a required feature bit that indicates
// that the node is able to understand the blinded path tagged field in
// a BOLT 11 invoice.
Bolt11BlindedPathsRequired = 262
// Bolt11BlindedPathsOptional is an optional feature bit that indicates
// that the node is able to understand the blinded path tagged field in
// a BOLT 11 invoice.
Bolt11BlindedPathsOptional = 263
// SimpleTaprootOverlayChansRequired is a required bit that indicates
// support for the special custom taproot overlay channel.
SimpleTaprootOverlayChansOptional = 2025
// SimpleTaprootOverlayChansRequired is a required bit that indicates
// support for the special custom taproot overlay channel.
SimpleTaprootOverlayChansRequired = 2026
// MaxBolt11Feature is the maximum feature bit value allowed in bolt 11
// invoices.
//
// The base 32 encoded tagged fields in invoices are limited to 10 bits
// to express the length of the field's data.
//nolint:ll
// See: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md#tagged-fields
//
// With a maximum length field of 1023 (2^10 -1) and 5 bit encoding,
// the highest feature bit that can be expressed is:
// 1023 * 5 - 1 = 5114.
MaxBolt11Feature = 5114
)
// IsRequired returns true if the feature bit is even, and false otherwise.
func (b FeatureBit) IsRequired() bool {
return b&0x01 == 0x00
}
// Features is a mapping of known feature bits to a descriptive name. All known
// feature bits must be assigned a name in this mapping, and feature bit pairs
// must be assigned together for correct behavior.
var Features = map[FeatureBit]string{
DataLossProtectRequired: "data-loss-protect",
DataLossProtectOptional: "data-loss-protect",
InitialRoutingSync: "initial-routing-sync",
UpfrontShutdownScriptRequired: "upfront-shutdown-script",
UpfrontShutdownScriptOptional: "upfront-shutdown-script",
GossipQueriesRequired: "gossip-queries",
GossipQueriesOptional: "gossip-queries",
TLVOnionPayloadRequired: "tlv-onion",
TLVOnionPayloadOptional: "tlv-onion",
StaticRemoteKeyOptional: "static-remote-key",
StaticRemoteKeyRequired: "static-remote-key",
PaymentAddrOptional: "payment-addr",
PaymentAddrRequired: "payment-addr",
MPPOptional: "multi-path-payments",
MPPRequired: "multi-path-payments",
AnchorsRequired: "anchor-commitments",
AnchorsOptional: "anchor-commitments",
AnchorsZeroFeeHtlcTxRequired: "anchors-zero-fee-htlc-tx",
AnchorsZeroFeeHtlcTxOptional: "anchors-zero-fee-htlc-tx",
WumboChannelsRequired: "wumbo-channels",
WumboChannelsOptional: "wumbo-channels",
AMPRequired: "amp",
AMPOptional: "amp",
QuiescenceRequired: "quiescence",
QuiescenceOptional: "quiescence",
PaymentMetadataOptional: "payment-metadata",
PaymentMetadataRequired: "payment-metadata",
ExplicitChannelTypeOptional: "explicit-commitment-type",
ExplicitChannelTypeRequired: "explicit-commitment-type",
KeysendOptional: "keysend",
KeysendRequired: "keysend",
ScriptEnforcedLeaseRequired: "script-enforced-lease",
ScriptEnforcedLeaseOptional: "script-enforced-lease",
ScidAliasRequired: "scid-alias",
ScidAliasOptional: "scid-alias",
ZeroConfRequired: "zero-conf",
ZeroConfOptional: "zero-conf",
RouteBlindingRequired: "route-blinding",
RouteBlindingOptional: "route-blinding",
ShutdownAnySegwitRequired: "shutdown-any-segwit",
ShutdownAnySegwitOptional: "shutdown-any-segwit",
SimpleTaprootChannelsRequiredFinal: "simple-taproot-chans",
SimpleTaprootChannelsOptionalFinal: "simple-taproot-chans",
SimpleTaprootChannelsRequiredStaging: "simple-taproot-chans-x",
SimpleTaprootChannelsOptionalStaging: "simple-taproot-chans-x",
SimpleTaprootOverlayChansOptional: "taproot-overlay-chans",
SimpleTaprootOverlayChansRequired: "taproot-overlay-chans",
ExperimentalEndorsementRequired: "endorsement-x",
ExperimentalEndorsementOptional: "endorsement-x",
Bolt11BlindedPathsOptional: "bolt-11-blinded-paths",
Bolt11BlindedPathsRequired: "bolt-11-blinded-paths",
RbfCoopCloseOptional: "rbf-coop-close",
RbfCoopCloseRequired: "rbf-coop-close",
RbfCoopCloseOptionalStaging: "rbf-coop-close-x",
RbfCoopCloseRequiredStaging: "rbf-coop-close-x",
}
// RawFeatureVector represents a set of feature bits as defined in BOLT-09. A
// RawFeatureVector itself just stores a set of bit flags but can be used to
// construct a FeatureVector which binds meaning to each bit. Feature vectors
// can be serialized and deserialized to/from a byte representation that is
// transmitted in Lightning network messages.
type RawFeatureVector struct {
features map[FeatureBit]struct{}
}
// NewRawFeatureVector creates a feature vector with all of the feature bits
// given as arguments enabled.
func NewRawFeatureVector(bits ...FeatureBit) *RawFeatureVector {
fv := &RawFeatureVector{features: make(map[FeatureBit]struct{})}
for _, bit := range bits {
fv.Set(bit)
}
return fv
}
// IsEmpty returns whether the feature vector contains any feature bits.
func (fv RawFeatureVector) IsEmpty() bool {
return len(fv.features) == 0
}
// OnlyContains determines whether only the specified feature bits are found.
func (fv RawFeatureVector) OnlyContains(bits ...FeatureBit) bool {
if len(bits) != len(fv.features) {
return false
}
for _, bit := range bits {
if !fv.IsSet(bit) {
return false
}
}
return true
}
// Equals determines whether two features vectors contain exactly the same
// features.
func (fv RawFeatureVector) Equals(other *RawFeatureVector) bool {
if len(fv.features) != len(other.features) {
return false
}
for bit := range fv.features {
if _, ok := other.features[bit]; !ok {
return false
}
}
return true
}
// Merges sets all feature bits in other on the receiver's feature vector.
func (fv *RawFeatureVector) Merge(other *RawFeatureVector) error {
for bit := range other.features {
err := fv.SafeSet(bit)
if err != nil {
return err
}
}
return nil
}
// ValidateUpdate checks whether a feature vector can safely be updated to the
// new feature vector provided, checking that it does not alter any of the
// "standard" features that are defined by LND. The new feature vector should
// be inclusive of all features in the original vector that it still wants to
// advertise, setting and unsetting updates as desired. Features in the vector
// are also checked against a maximum inclusive value, as feature vectors in
// different contexts have different maximum values.
func (fv *RawFeatureVector) ValidateUpdate(other *RawFeatureVector,
maximumValue FeatureBit) error {
// Run through the new set of features and check that we're not adding
// any feature bits that are defined but not set in LND.
for feature := range other.features {
if fv.IsSet(feature) {
continue
}
if feature > maximumValue {
return fmt.Errorf("can't set feature bit %d: %w %v",
feature, ErrFeatureBitMaximum,
maximumValue)
}
if name, known := Features[feature]; known {
return fmt.Errorf("can't set feature "+
"bit %d (%v): %w", feature, name,
ErrFeatureStandard)
}
}
// Check that the new feature vector for this set does not unset any
// features that are standard in LND by comparing the features in our
// current set to the omitted values in the new set.
for feature := range fv.features {
if other.IsSet(feature) {
continue
}
if name, known := Features[feature]; known {
return fmt.Errorf("can't unset feature "+
"bit %d (%v): %w", feature, name,
ErrFeatureStandard)
}
}
return nil
}
// ValidatePairs checks each feature bit in a raw vector to ensure that the
// opposing bit is not set, validating that the vector has either the optional
// or required bit set, not both.
func (fv *RawFeatureVector) ValidatePairs() error {
for feature := range fv.features {
if _, ok := fv.features[feature^1]; ok {
return ErrFeaturePairExists
}
}
return nil
}
// Clone makes a copy of a feature vector.
func (fv *RawFeatureVector) Clone() *RawFeatureVector {
newFeatures := NewRawFeatureVector()
for bit := range fv.features {
newFeatures.Set(bit)
}
return newFeatures
}
// IsSet returns whether a particular feature bit is enabled in the vector.
func (fv *RawFeatureVector) IsSet(feature FeatureBit) bool {
_, ok := fv.features[feature]
return ok
}
// Set marks a feature as enabled in the vector.
func (fv *RawFeatureVector) Set(feature FeatureBit) {
fv.features[feature] = struct{}{}
}
// SafeSet sets the chosen feature bit in the feature vector, but returns an
// error if the opposing feature bit is already set. This ensures both that we
// are creating properly structured feature vectors, and in some cases, that
// peers are sending properly encoded ones, i.e. it can't be both optional and
// required.
func (fv *RawFeatureVector) SafeSet(feature FeatureBit) error {
if _, ok := fv.features[feature^1]; ok {
return ErrFeaturePairExists
}
fv.Set(feature)
return nil
}
// Unset marks a feature as disabled in the vector.
func (fv *RawFeatureVector) Unset(feature FeatureBit) {
delete(fv.features, feature)
}
// SerializeSize returns the number of bytes needed to represent feature vector
// in byte format.
func (fv *RawFeatureVector) SerializeSize() int {
// We calculate byte-length via the largest bit index.
return fv.serializeSize(8)
}
// SerializeSize32 returns the number of bytes needed to represent feature
// vector in base32 format.
func (fv *RawFeatureVector) SerializeSize32() int {
// We calculate base32-length via the largest bit index.
return fv.serializeSize(5)
}
// serializeSize returns the number of bytes required to encode the feature
// vector using at most width bits per encoded byte.
func (fv *RawFeatureVector) serializeSize(width int) int {
// Find the largest feature bit index
max := -1
for feature := range fv.features {
index := int(feature)
if index > max {
max = index
}
}
if max == -1 {
return 0
}
return max/width + 1
}
// Encode writes the feature vector in byte representation. Every feature
// encoded as a bit, and the bit vector is serialized using the least number of
// bytes. Since the bit vector length is variable, the first two bytes of the
// serialization represent the length.
func (fv *RawFeatureVector) Encode(w io.Writer) error {
// Write length of feature vector.
var l [2]byte
length := fv.SerializeSize()
binary.BigEndian.PutUint16(l[:], uint16(length))
if _, err := w.Write(l[:]); err != nil {
return err
}
return fv.encode(w, length, 8)
}
// EncodeBase256 writes the feature vector in base256 representation. Every
// feature is encoded as a bit, and the bit vector is serialized using the least
// number of bytes.
func (fv *RawFeatureVector) EncodeBase256(w io.Writer) error {
length := fv.SerializeSize()
return fv.encode(w, length, 8)
}
// EncodeBase32 writes the feature vector in base32 representation. Every feature
// is encoded as a bit, and the bit vector is serialized using the least number of
// bytes.
func (fv *RawFeatureVector) EncodeBase32(w io.Writer) error {
length := fv.SerializeSize32()
return fv.encode(w, length, 5)
}
// encode writes the feature vector
func (fv *RawFeatureVector) encode(w io.Writer, length, width int) error {
// Generate the data and write it.
data := make([]byte, length)
for feature := range fv.features {
byteIndex := int(feature) / width
bitIndex := int(feature) % width
data[length-byteIndex-1] |= 1 << uint(bitIndex)
}
_, err := w.Write(data)
return err
}
// Decode reads the feature vector from its byte representation. Every feature
// is encoded as a bit, and the bit vector is serialized using the least number
// of bytes. Since the bit vector length is variable, the first two bytes of the
// serialization represent the length.
func (fv *RawFeatureVector) Decode(r io.Reader) error {
// Read the length of the feature vector.
var l [2]byte
if _, err := io.ReadFull(r, l[:]); err != nil {
return err
}
length := binary.BigEndian.Uint16(l[:])
return fv.decode(r, int(length), 8)
}
// DecodeBase256 reads the feature vector from its base256 representation. Every
// feature encoded as a bit, and the bit vector is serialized using the least
// number of bytes.
func (fv *RawFeatureVector) DecodeBase256(r io.Reader, length int) error {
return fv.decode(r, length, 8)
}
// DecodeBase32 reads the feature vector from its base32 representation. Every
// feature encoded as a bit, and the bit vector is serialized using the least
// number of bytes.
func (fv *RawFeatureVector) DecodeBase32(r io.Reader, length int) error {
return fv.decode(r, length, 5)
}
// decode reads a feature vector from the next length bytes of the io.Reader,
// assuming each byte has width feature bits encoded per byte.
func (fv *RawFeatureVector) decode(r io.Reader, length, width int) error {
// Read the feature vector data.
data := make([]byte, length)
if _, err := io.ReadFull(r, data); err != nil {
return err
}
// Set feature bits from parsed data.
bitsNumber := len(data) * width
for i := 0; i < bitsNumber; i++ {
byteIndex := int(i / width)
bitIndex := uint(i % width)
if (data[length-byteIndex-1]>>bitIndex)&1 == 1 {
fv.Set(FeatureBit(i))
}
}
return nil
}
// sizeFunc returns the length required to encode the feature vector.
func (fv *RawFeatureVector) sizeFunc() uint64 {
return uint64(fv.SerializeSize())
}
// Record returns a TLV record that can be used to encode/decode raw feature
// vectors. Note that the length of the feature vector is not included, because
// it is covered by the TLV record's length field.
func (fv *RawFeatureVector) Record() tlv.Record {
return tlv.MakeDynamicRecord(
0, fv, fv.sizeFunc, rawFeatureEncoder, rawFeatureDecoder,
)
}
// rawFeatureEncoder is a custom TLV encoder for raw feature vectors.
func rawFeatureEncoder(w io.Writer, val interface{}, _ *[8]byte) error {
if v, ok := val.(*RawFeatureVector); ok {
// Encode the feature bits as a byte slice without its length
// prepended, as that's already taken care of by the TLV record.
fv := *v
return fv.encode(w, fv.SerializeSize(), 8)
}
return tlv.NewTypeForEncodingErr(val, "lnwire.RawFeatureVector")
}
// rawFeatureDecoder is a custom TLV decoder for raw feature vectors.
func rawFeatureDecoder(r io.Reader, val interface{}, _ *[8]byte,
l uint64) error {
if v, ok := val.(*RawFeatureVector); ok {
fv := NewRawFeatureVector()
if err := fv.decode(r, int(l), 8); err != nil {
return err
}
*v = *fv
return nil
}
return tlv.NewTypeForEncodingErr(val, "lnwire.RawFeatureVector")
}
// FeatureVector represents a set of enabled features. The set stores
// information on enabled flags and metadata about the feature names. A feature
// vector is serializable to a compact byte representation that is included in
// Lightning network messages.
type FeatureVector struct {
*RawFeatureVector
featureNames map[FeatureBit]string
}
// NewFeatureVector constructs a new FeatureVector from a raw feature vector
// and mapping of feature definitions. If the feature vector argument is nil, a
// new one will be constructed with no enabled features.
func NewFeatureVector(featureVector *RawFeatureVector,
featureNames map[FeatureBit]string) *FeatureVector {
if featureVector == nil {
featureVector = NewRawFeatureVector()
}
return &FeatureVector{
RawFeatureVector: featureVector,
featureNames: featureNames,
}
}
// EmptyFeatureVector returns a feature vector with no bits set.
func EmptyFeatureVector() *FeatureVector {
return NewFeatureVector(nil, Features)
}
// Record implements the RecordProducer interface for FeatureVector. Note that
// it uses a zero-value type is used to produce the record, as we expect this
// type value to be overwritten when used in generic TLV record production.
// This allows a single Record function to serve in the many different contexts
// in which feature vectors are encoded. This record wraps the encoding/
// decoding for our raw feature vectors so that we can directly parse fully
// formed feature vector types.
func (fv *FeatureVector) Record() tlv.Record {
return tlv.MakeDynamicRecord(0, fv, fv.sizeFunc,
func(w io.Writer, val interface{}, buf *[8]byte) error {
if f, ok := val.(*FeatureVector); ok {
return rawFeatureEncoder(
w, f.RawFeatureVector, buf,
)
}
return tlv.NewTypeForEncodingErr(
val, "*lnwire.FeatureVector",
)
},
func(r io.Reader, val interface{}, buf *[8]byte,
l uint64) error {
if f, ok := val.(*FeatureVector); ok {
features := NewFeatureVector(nil, Features)
err := rawFeatureDecoder(
r, features.RawFeatureVector, buf, l,
)
if err != nil {
return err
}
*f = *features
return nil
}
return tlv.NewTypeForDecodingErr(
val, "*lnwire.FeatureVector", l, l,
)
},
)
}
// HasFeature returns whether a particular feature is included in the set. The
// feature can be seen as set either if the bit is set directly OR the queried
// bit has the same meaning as its corresponding even/odd bit, which is set
// instead. The second case is because feature bits are generally assigned in
// pairs where both the even and odd position represent the same feature.
func (fv *FeatureVector) HasFeature(feature FeatureBit) bool {
return fv.IsSet(feature) ||
(fv.isFeatureBitPair(feature) && fv.IsSet(feature^1))
}
// RequiresFeature returns true if the referenced feature vector *requires*
// that the given required bit be set. This method can be used with both
// optional and required feature bits as a parameter.
func (fv *FeatureVector) RequiresFeature(feature FeatureBit) bool {
// If we weren't passed a required feature bit, then we'll flip the
// lowest bit to query for the required version of the feature. This
// lets callers pass in both the optional and required bits.
if !feature.IsRequired() {
feature ^= 1
}
return fv.IsSet(feature)
}
// UnknownRequiredFeatures returns a list of feature bits set in the vector
// that are unknown and in an even bit position. Feature bits with an even
// index must be known to a node receiving the feature vector in a message.
func (fv *FeatureVector) UnknownRequiredFeatures() []FeatureBit {
var unknown []FeatureBit
for feature := range fv.features {
if feature%2 == 0 && !fv.IsKnown(feature) {
unknown = append(unknown, feature)
}
}
return unknown
}
// UnknownFeatures returns a boolean if a feature vector contains *any*
// unknown features (even if they are odd).
func (fv *FeatureVector) UnknownFeatures() bool {
for feature := range fv.features {
if !fv.IsKnown(feature) {
return true
}
}
return false
}
// Name returns a string identifier for the feature represented by this bit. If
// the bit does not represent a known feature, this returns a string indicating
// as such.
func (fv *FeatureVector) Name(bit FeatureBit) string {
name, known := fv.featureNames[bit]
if !known {
return "unknown"
}
return name
}
// IsKnown returns whether this feature bit represents a known feature.
func (fv *FeatureVector) IsKnown(bit FeatureBit) bool {
_, known := fv.featureNames[bit]
return known
}
// isFeatureBitPair returns whether this feature bit and its corresponding
// even/odd bit both represent the same feature. This may often be the case as
// bits are generally assigned in pairs, first being assigned an odd bit
// position then being promoted to an even bit position once the network is
// ready.
func (fv *FeatureVector) isFeatureBitPair(bit FeatureBit) bool {
name1, known1 := fv.featureNames[bit]
name2, known2 := fv.featureNames[bit^1]
return known1 && known2 && name1 == name2
}
// Features returns the set of raw features contained in the feature vector.
func (fv *FeatureVector) Features() map[FeatureBit]struct{} {
fs := make(map[FeatureBit]struct{}, len(fv.RawFeatureVector.features))
for b := range fv.RawFeatureVector.features {
fs[b] = struct{}{}
}
return fs
}
// Clone copies a feature vector, carrying over its feature bits. The feature
// names are not copied.
func (fv *FeatureVector) Clone() *FeatureVector {
features := fv.RawFeatureVector.Clone()
return NewFeatureVector(features, fv.featureNames)
}
package lnwire
import (
"bytes"
"io"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/tlv"
)
// FundingCreated is sent from Alice (the initiator) to Bob (the responder),
// once Alice receives Bob's contributions as well as his channel constraints.
// Once bob receives this message, he'll gain access to an immediately
// broadcastable commitment transaction and will reply with a signature for
// Alice's version of the commitment transaction.
type FundingCreated struct {
// PendingChannelID serves to uniquely identify the future channel
// created by the initiated single funder workflow.
PendingChannelID [32]byte
// FundingPoint is the outpoint of the funding transaction created by
// Alice. With this, Bob is able to generate both his version and
// Alice's version of the commitment transaction.
FundingPoint wire.OutPoint
// CommitSig is Alice's signature from Bob's version of the commitment
// transaction.
CommitSig Sig
// PartialSig is used to transmit a musig2 extended partial signature
// that also carries along the public nonce of the signer.
//
// NOTE: This field is only populated if a musig2 taproot channel is
// being signed for. In this case, the above Sig type MUST be blank.
PartialSig OptPartialSigWithNonceTLV
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// A compile time check to ensure FundingCreated implements the lnwire.Message
// interface.
var _ Message = (*FundingCreated)(nil)
// A compile time check to ensure FundingCreated implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*FundingCreated)(nil)
// Encode serializes the target FundingCreated into the passed io.Writer
// implementation. Serialization will observe the rules defined by the passed
// protocol version.
//
// This is part of the lnwire.Message interface.
func (f *FundingCreated) Encode(w *bytes.Buffer, pver uint32) error {
recordProducers := make([]tlv.RecordProducer, 0, 1)
f.PartialSig.WhenSome(func(sig PartialSigWithNonceTLV) {
recordProducers = append(recordProducers, &sig)
})
err := EncodeMessageExtraData(&f.ExtraData, recordProducers...)
if err != nil {
return err
}
if err := WriteBytes(w, f.PendingChannelID[:]); err != nil {
return err
}
if err := WriteOutPoint(w, f.FundingPoint); err != nil {
return err
}
if err := WriteSig(w, f.CommitSig); err != nil {
return err
}
return WriteBytes(w, f.ExtraData)
}
// Decode deserializes the serialized FundingCreated stored in the passed
// io.Reader into the target FundingCreated using the deserialization rules
// defined by the passed protocol version.
//
// This is part of the lnwire.Message interface.
func (f *FundingCreated) Decode(r io.Reader, pver uint32) error {
err := ReadElements(
r, f.PendingChannelID[:], &f.FundingPoint, &f.CommitSig,
)
if err != nil {
return err
}
var tlvRecords ExtraOpaqueData
if err := ReadElements(r, &tlvRecords); err != nil {
return err
}
partialSig := f.PartialSig.Zero()
typeMap, err := tlvRecords.ExtractRecords(&partialSig)
if err != nil {
return err
}
// Set the corresponding TLV types if they were included in the stream.
if val, ok := typeMap[f.PartialSig.TlvType()]; ok && val == nil {
f.PartialSig = tlv.SomeRecordT(partialSig)
}
if len(tlvRecords) != 0 {
f.ExtraData = tlvRecords
}
return nil
}
// MsgType returns the uint32 code which uniquely identifies this message as a
// FundingCreated on the wire.
//
// This is part of the lnwire.Message interface.
func (f *FundingCreated) MsgType() MessageType {
return MsgFundingCreated
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (f *FundingCreated) SerializedSize() (uint32, error) {
return MessageSerializedSize(f)
}
package lnwire
import (
"bytes"
"io"
"github.com/lightningnetwork/lnd/tlv"
)
// FundingSigned is sent from Bob (the responder) to Alice (the initiator)
// after receiving the funding outpoint and her signature for Bob's version of
// the commitment transaction.
type FundingSigned struct {
// ChannelPoint is the particular active channel that this
// FundingSigned is bound to.
ChanID ChannelID
// CommitSig is Bob's signature for Alice's version of the commitment
// transaction.
CommitSig Sig
// PartialSig is used to transmit a musig2 extended partial signature
// that also carries along the public nonce of the signer.
//
// NOTE: This field is only populated if a musig2 taproot channel is
// being signed for. In this case, the above Sig type MUST be blank.
PartialSig OptPartialSigWithNonceTLV
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// A compile time check to ensure FundingSigned implements the lnwire.Message
// interface.
var _ Message = (*FundingSigned)(nil)
// A compile time check to ensure FundingSigned implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*FundingSigned)(nil)
// Encode serializes the target FundingSigned into the passed io.Writer
// implementation. Serialization will observe the rules defined by the passed
// protocol version.
//
// This is part of the lnwire.Message interface.
func (f *FundingSigned) Encode(w *bytes.Buffer, pver uint32) error {
recordProducers := make([]tlv.RecordProducer, 0, 1)
f.PartialSig.WhenSome(func(sig PartialSigWithNonceTLV) {
recordProducers = append(recordProducers, &sig)
})
err := EncodeMessageExtraData(&f.ExtraData, recordProducers...)
if err != nil {
return err
}
if err := WriteChannelID(w, f.ChanID); err != nil {
return err
}
if err := WriteSig(w, f.CommitSig); err != nil {
return err
}
return WriteBytes(w, f.ExtraData)
}
// Decode deserializes the serialized FundingSigned stored in the passed
// io.Reader into the target FundingSigned using the deserialization rules
// defined by the passed protocol version.
//
// This is part of the lnwire.Message interface.
func (f *FundingSigned) Decode(r io.Reader, pver uint32) error {
err := ReadElements(r, &f.ChanID, &f.CommitSig)
if err != nil {
return err
}
var tlvRecords ExtraOpaqueData
if err := ReadElements(r, &tlvRecords); err != nil {
return err
}
partialSig := f.PartialSig.Zero()
typeMap, err := tlvRecords.ExtractRecords(&partialSig)
if err != nil {
return err
}
// Set the corresponding TLV types if they were included in the stream.
if val, ok := typeMap[f.PartialSig.TlvType()]; ok && val == nil {
f.PartialSig = tlv.SomeRecordT(partialSig)
}
if len(tlvRecords) != 0 {
f.ExtraData = tlvRecords
}
return nil
}
// MsgType returns the uint32 code which uniquely identifies this message as a
// FundingSigned on the wire.
//
// This is part of the lnwire.Message interface.
func (f *FundingSigned) MsgType() MessageType {
return MsgFundingSigned
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (f *FundingSigned) SerializedSize() (uint32, error) {
return MessageSerializedSize(f)
}
package lnwire
import (
"bytes"
"io"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/tlv"
)
// GossipTimestampRange is a message that allows the sender to restrict the set
// of future gossip announcements sent by the receiver. Nodes should send this
// if they have the gossip-queries feature bit active. Nodes are able to send
// new GossipTimestampRange messages to replace the prior window.
type GossipTimestampRange struct {
// ChainHash denotes the chain that the sender wishes to restrict the
// set of received announcements of.
ChainHash chainhash.Hash
// FirstTimestamp is the timestamp of the earliest announcement message
// that should be sent by the receiver. This is only to be used for
// querying message of gossip 1.0 which are timestamped using Unix
// timestamps. FirstBlockHeight and BlockRange should be used to
// query for announcement messages timestamped using block heights.
FirstTimestamp uint32
// TimestampRange is the horizon beyond the FirstTimestamp that any
// announcement messages should be sent for. The receiving node MUST
// NOT send any announcements that have a timestamp greater than
// FirstTimestamp + TimestampRange. This is used together with
// FirstTimestamp to query for gossip 1.0 messages timestamped with
// Unix timestamps.
TimestampRange uint32
// FirstBlockHeight is the height of earliest announcement message that
// should be sent by the receiver. This is used only for querying
// announcement messages that use block heights as a timestamp.
FirstBlockHeight tlv.OptionalRecordT[tlv.TlvType2, uint32]
// BlockRange is the horizon beyond FirstBlockHeight that any
// announcement messages should be sent for. The receiving node MUST NOT
// send any announcements that have a timestamp greater than
// FirstBlockHeight + BlockRange.
BlockRange tlv.OptionalRecordT[tlv.TlvType4, uint32]
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// NewGossipTimestampRange creates a new empty GossipTimestampRange message.
func NewGossipTimestampRange() *GossipTimestampRange {
return &GossipTimestampRange{}
}
// A compile time check to ensure GossipTimestampRange implements the
// lnwire.Message interface.
var _ Message = (*GossipTimestampRange)(nil)
// A compile time check to ensure GossipTimestampRange implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*GossipTimestampRange)(nil)
// Decode deserializes a serialized GossipTimestampRange message stored in the
// passed io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (g *GossipTimestampRange) Decode(r io.Reader, _ uint32) error {
err := ReadElements(r,
g.ChainHash[:],
&g.FirstTimestamp,
&g.TimestampRange,
)
if err != nil {
return err
}
var tlvRecords ExtraOpaqueData
if err := ReadElements(r, &tlvRecords); err != nil {
return err
}
var (
firstBlock = tlv.ZeroRecordT[tlv.TlvType2, uint32]()
blockRange = tlv.ZeroRecordT[tlv.TlvType4, uint32]()
)
typeMap, err := tlvRecords.ExtractRecords(&firstBlock, &blockRange)
if err != nil {
return err
}
if val, ok := typeMap[g.FirstBlockHeight.TlvType()]; ok && val == nil {
g.FirstBlockHeight = tlv.SomeRecordT(firstBlock)
}
if val, ok := typeMap[g.BlockRange.TlvType()]; ok && val == nil {
g.BlockRange = tlv.SomeRecordT(blockRange)
}
if len(tlvRecords) != 0 {
g.ExtraData = tlvRecords
}
return nil
}
// Encode serializes the target GossipTimestampRange into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (g *GossipTimestampRange) Encode(w *bytes.Buffer, pver uint32) error {
if err := WriteBytes(w, g.ChainHash[:]); err != nil {
return err
}
if err := WriteUint32(w, g.FirstTimestamp); err != nil {
return err
}
if err := WriteUint32(w, g.TimestampRange); err != nil {
return err
}
recordProducers := make([]tlv.RecordProducer, 0, 2)
g.FirstBlockHeight.WhenSome(
func(height tlv.RecordT[tlv.TlvType2, uint32]) {
recordProducers = append(recordProducers, &height)
},
)
g.BlockRange.WhenSome(
func(blockRange tlv.RecordT[tlv.TlvType4, uint32]) {
recordProducers = append(recordProducers, &blockRange)
},
)
err := EncodeMessageExtraData(&g.ExtraData, recordProducers...)
if err != nil {
return err
}
return WriteBytes(w, g.ExtraData)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (g *GossipTimestampRange) MsgType() MessageType {
return MsgGossipTimestampRange
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (g *GossipTimestampRange) SerializedSize() (uint32, error) {
return MessageSerializedSize(g)
}
package lnwire
import (
"bytes"
"io"
)
// Init is the first message reveals the features supported or required by this
// node. Nodes wait for receipt of the other's features to simplify error
// diagnosis where features are incompatible. Each node MUST wait to receive
// init before sending any other messages.
type Init struct {
// GlobalFeatures is a legacy feature vector used for backwards
// compatibility with older nodes. Any features defined here should be
// merged with those presented in Features.
GlobalFeatures *RawFeatureVector
// Features is a feature vector containing the features supported by
// the remote node.
//
// NOTE: Older nodes may place some features in GlobalFeatures, but all
// new features are to be added in Features. When handling an Init
// message, any GlobalFeatures should be merged into the unified
// Features field.
Features *RawFeatureVector
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// NewInitMessage creates new instance of init message object.
func NewInitMessage(gf *RawFeatureVector, f *RawFeatureVector) *Init {
return &Init{
GlobalFeatures: gf,
Features: f,
ExtraData: make([]byte, 0),
}
}
// A compile time check to ensure Init implements the lnwire.Message
// interface.
var _ Message = (*Init)(nil)
// A compile time check to ensure Init implements the lnwire.SizeableMessage
// interface.
var _ SizeableMessage = (*Init)(nil)
// Decode deserializes a serialized Init message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (msg *Init) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&msg.GlobalFeatures,
&msg.Features,
&msg.ExtraData,
)
}
// Encode serializes the target Init into the passed io.Writer observing
// the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (msg *Init) Encode(w *bytes.Buffer, pver uint32) error {
if err := WriteRawFeatureVector(w, msg.GlobalFeatures); err != nil {
return err
}
if err := WriteRawFeatureVector(w, msg.Features); err != nil {
return err
}
return WriteBytes(w, msg.ExtraData)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (msg *Init) MsgType() MessageType {
return MsgInit
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (msg *Init) SerializedSize() (uint32, error) {
return MessageSerializedSize(msg)
}
package lnwire
import (
"bytes"
"io"
)
// KickoffSig is the message used to transmit the signature for a kickoff
// transaction during the execution phase of a dynamic commitment negotiation
// that requires a reanchoring step.
type KickoffSig struct {
// ChanID identifies the channel id for which this signature is
// intended.
ChanID ChannelID
// Signature contains the ECDSA signature that signs the kickoff
// transaction.
Signature Sig
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// A compile time check to ensure that KickoffSig implements the lnwire.Message
// interface.
var _ Message = (*KickoffSig)(nil)
// A compile time check to ensure KickoffSig implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*KickoffSig)(nil)
// Encode serializes the target KickoffSig into the passed bytes.Buffer
// observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (ks *KickoffSig) Encode(w *bytes.Buffer, _ uint32) error {
if err := WriteChannelID(w, ks.ChanID); err != nil {
return err
}
if err := WriteSig(w, ks.Signature); err != nil {
return err
}
return WriteBytes(w, ks.ExtraData)
}
// Decode deserializes a serialized KickoffSig message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (ks *KickoffSig) Decode(r io.Reader, _ uint32) error {
return ReadElements(r, &ks.ChanID, &ks.Signature, &ks.ExtraData)
}
// MsgType returns the integer uniquely identifying KickoffSig on the wire.
//
// This is part of the lnwire.Message interface.
func (ks *KickoffSig) MsgType() MessageType { return MsgKickoffSig }
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (ks *KickoffSig) SerializedSize() (uint32, error) {
return MessageSerializedSize(ks)
}
package lnwire
import (
"bytes"
"encoding/binary"
"fmt"
"image/color"
"io"
"math"
"net"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/tor"
)
const (
// MaxSliceLength is the maximum allowed length for any opaque byte
// slices in the wire protocol.
MaxSliceLength = 65535
// MaxMsgBody is the largest payload any message is allowed to provide.
// This is two less than the MaxSliceLength as each message has a 2
// byte type that precedes the message body.
MaxMsgBody = 65533
)
// PkScript is simple type definition which represents a raw serialized public
// key script.
type PkScript []byte
// addressType specifies the network protocol and version that should be used
// when connecting to a node at a particular address.
type addressType uint8
const (
// noAddr denotes a blank address. An address of this type indicates
// that a node doesn't have any advertised addresses.
noAddr addressType = 0
// tcp4Addr denotes an IPv4 TCP address.
tcp4Addr addressType = 1
// tcp6Addr denotes an IPv6 TCP address.
tcp6Addr addressType = 2
// v2OnionAddr denotes a version 2 Tor onion service address.
v2OnionAddr addressType = 3
// v3OnionAddr denotes a version 3 Tor (prop224) onion service address.
v3OnionAddr addressType = 4
)
// AddrLen returns the number of bytes that it takes to encode the target
// address.
func (a addressType) AddrLen() uint16 {
switch a {
case noAddr:
return 0
case tcp4Addr:
return 6
case tcp6Addr:
return 18
case v2OnionAddr:
return 12
case v3OnionAddr:
return 37
default:
return 0
}
}
// WriteElement is a one-stop shop to write the big endian representation of
// any element which is to be serialized for the wire protocol.
//
// TODO(yy): rm this method once we finish dereferencing it from other
// packages.
func WriteElement(w *bytes.Buffer, element interface{}) error {
switch e := element.(type) {
case NodeAlias:
if _, err := w.Write(e[:]); err != nil {
return err
}
case QueryEncoding:
var b [1]byte
b[0] = uint8(e)
if _, err := w.Write(b[:]); err != nil {
return err
}
case uint8:
var b [1]byte
b[0] = e
if _, err := w.Write(b[:]); err != nil {
return err
}
case FundingFlag:
var b [1]byte
b[0] = uint8(e)
if _, err := w.Write(b[:]); err != nil {
return err
}
case uint16:
var b [2]byte
binary.BigEndian.PutUint16(b[:], e)
if _, err := w.Write(b[:]); err != nil {
return err
}
case ChanUpdateMsgFlags:
var b [1]byte
b[0] = uint8(e)
if _, err := w.Write(b[:]); err != nil {
return err
}
case ChanUpdateChanFlags:
var b [1]byte
b[0] = uint8(e)
if _, err := w.Write(b[:]); err != nil {
return err
}
case MilliSatoshi:
var b [8]byte
binary.BigEndian.PutUint64(b[:], uint64(e))
if _, err := w.Write(b[:]); err != nil {
return err
}
case btcutil.Amount:
var b [8]byte
binary.BigEndian.PutUint64(b[:], uint64(e))
if _, err := w.Write(b[:]); err != nil {
return err
}
case uint32:
var b [4]byte
binary.BigEndian.PutUint32(b[:], e)
if _, err := w.Write(b[:]); err != nil {
return err
}
case uint64:
var b [8]byte
binary.BigEndian.PutUint64(b[:], e)
if _, err := w.Write(b[:]); err != nil {
return err
}
case *btcec.PublicKey:
if e == nil {
return fmt.Errorf("cannot write nil pubkey")
}
var b [33]byte
serializedPubkey := e.SerializeCompressed()
copy(b[:], serializedPubkey)
if _, err := w.Write(b[:]); err != nil {
return err
}
case []Sig:
var b [2]byte
numSigs := uint16(len(e))
binary.BigEndian.PutUint16(b[:], numSigs)
if _, err := w.Write(b[:]); err != nil {
return err
}
for _, sig := range e {
if err := WriteElement(w, sig); err != nil {
return err
}
}
case Sig:
// Write buffer
if _, err := w.Write(e.bytes[:]); err != nil {
return err
}
case PartialSig:
if err := e.Encode(w); err != nil {
return err
}
case PingPayload:
var l [2]byte
binary.BigEndian.PutUint16(l[:], uint16(len(e)))
if _, err := w.Write(l[:]); err != nil {
return err
}
if _, err := w.Write(e[:]); err != nil {
return err
}
case PongPayload:
var l [2]byte
binary.BigEndian.PutUint16(l[:], uint16(len(e)))
if _, err := w.Write(l[:]); err != nil {
return err
}
if _, err := w.Write(e[:]); err != nil {
return err
}
case WarningData:
var l [2]byte
binary.BigEndian.PutUint16(l[:], uint16(len(e)))
if _, err := w.Write(l[:]); err != nil {
return err
}
if _, err := w.Write(e[:]); err != nil {
return err
}
case ErrorData:
var l [2]byte
binary.BigEndian.PutUint16(l[:], uint16(len(e)))
if _, err := w.Write(l[:]); err != nil {
return err
}
if _, err := w.Write(e[:]); err != nil {
return err
}
case OpaqueReason:
var l [2]byte
binary.BigEndian.PutUint16(l[:], uint16(len(e)))
if _, err := w.Write(l[:]); err != nil {
return err
}
if _, err := w.Write(e[:]); err != nil {
return err
}
case [33]byte:
if _, err := w.Write(e[:]); err != nil {
return err
}
case []byte:
if _, err := w.Write(e[:]); err != nil {
return err
}
case PkScript:
// The largest script we'll accept is a p2wsh which is exactly
// 34 bytes long.
scriptLength := len(e)
if scriptLength > 34 {
return fmt.Errorf("'PkScript' too long")
}
if err := wire.WriteVarBytes(w, 0, e); err != nil {
return err
}
case *RawFeatureVector:
if e == nil {
return fmt.Errorf("cannot write nil feature vector")
}
if err := e.Encode(w); err != nil {
return err
}
case wire.OutPoint:
var h [32]byte
copy(h[:], e.Hash[:])
if _, err := w.Write(h[:]); err != nil {
return err
}
if e.Index > math.MaxUint16 {
return fmt.Errorf("index for outpoint (%v) is "+
"greater than max index of %v", e.Index,
math.MaxUint16)
}
var idx [2]byte
binary.BigEndian.PutUint16(idx[:], uint16(e.Index))
if _, err := w.Write(idx[:]); err != nil {
return err
}
case ChannelID:
if _, err := w.Write(e[:]); err != nil {
return err
}
case FailCode:
if err := WriteElement(w, uint16(e)); err != nil {
return err
}
case ShortChannelID:
// Check that field fit in 3 bytes and write the blockHeight
if e.BlockHeight > ((1 << 24) - 1) {
return errors.New("block height should fit in 3 bytes")
}
var blockHeight [4]byte
binary.BigEndian.PutUint32(blockHeight[:], e.BlockHeight)
if _, err := w.Write(blockHeight[1:]); err != nil {
return err
}
// Check that field fit in 3 bytes and write the txIndex
if e.TxIndex > ((1 << 24) - 1) {
return errors.New("tx index should fit in 3 bytes")
}
var txIndex [4]byte
binary.BigEndian.PutUint32(txIndex[:], e.TxIndex)
if _, err := w.Write(txIndex[1:]); err != nil {
return err
}
// Write the txPosition
var txPosition [2]byte
binary.BigEndian.PutUint16(txPosition[:], e.TxPosition)
if _, err := w.Write(txPosition[:]); err != nil {
return err
}
case *net.TCPAddr:
if e == nil {
return fmt.Errorf("cannot write nil TCPAddr")
}
if e.IP.To4() != nil {
var descriptor [1]byte
descriptor[0] = uint8(tcp4Addr)
if _, err := w.Write(descriptor[:]); err != nil {
return err
}
var ip [4]byte
copy(ip[:], e.IP.To4())
if _, err := w.Write(ip[:]); err != nil {
return err
}
} else {
var descriptor [1]byte
descriptor[0] = uint8(tcp6Addr)
if _, err := w.Write(descriptor[:]); err != nil {
return err
}
var ip [16]byte
copy(ip[:], e.IP.To16())
if _, err := w.Write(ip[:]); err != nil {
return err
}
}
var port [2]byte
binary.BigEndian.PutUint16(port[:], uint16(e.Port))
if _, err := w.Write(port[:]); err != nil {
return err
}
case *tor.OnionAddr:
if e == nil {
return errors.New("cannot write nil onion address")
}
var suffixIndex int
switch len(e.OnionService) {
case tor.V2Len:
descriptor := []byte{byte(v2OnionAddr)}
if _, err := w.Write(descriptor); err != nil {
return err
}
suffixIndex = tor.V2Len - tor.OnionSuffixLen
case tor.V3Len:
descriptor := []byte{byte(v3OnionAddr)}
if _, err := w.Write(descriptor); err != nil {
return err
}
suffixIndex = tor.V3Len - tor.OnionSuffixLen
default:
return errors.New("unknown onion service length")
}
host, err := tor.Base32Encoding.DecodeString(
e.OnionService[:suffixIndex],
)
if err != nil {
return err
}
if _, err := w.Write(host); err != nil {
return err
}
var port [2]byte
binary.BigEndian.PutUint16(port[:], uint16(e.Port))
if _, err := w.Write(port[:]); err != nil {
return err
}
case []net.Addr:
// First, we'll encode all the addresses into an intermediate
// buffer. We need to do this in order to compute the total
// length of the addresses.
var addrBuf bytes.Buffer
for _, address := range e {
if err := WriteElement(&addrBuf, address); err != nil {
return err
}
}
// With the addresses fully encoded, we can now write out the
// number of bytes needed to encode them.
addrLen := addrBuf.Len()
if err := WriteElement(w, uint16(addrLen)); err != nil {
return err
}
// Finally, we'll write out the raw addresses themselves, but
// only if we have any bytes to write.
if addrLen > 0 {
if _, err := w.Write(addrBuf.Bytes()); err != nil {
return err
}
}
case color.RGBA:
if err := WriteElements(w, e.R, e.G, e.B); err != nil {
return err
}
case DeliveryAddress:
var length [2]byte
binary.BigEndian.PutUint16(length[:], uint16(len(e)))
if _, err := w.Write(length[:]); err != nil {
return err
}
if _, err := w.Write(e[:]); err != nil {
return err
}
case bool:
var b [1]byte
if e {
b[0] = 1
}
if _, err := w.Write(b[:]); err != nil {
return err
}
case ExtraOpaqueData:
return e.Encode(w)
default:
return fmt.Errorf("unknown type in WriteElement: %T", e)
}
return nil
}
// WriteElements is writes each element in the elements slice to the passed
// buffer using WriteElement.
//
// TODO(yy): rm this method once we finish dereferencing it from other
// packages.
func WriteElements(buf *bytes.Buffer, elements ...interface{}) error {
for _, element := range elements {
err := WriteElement(buf, element)
if err != nil {
return err
}
}
return nil
}
// ReadElement is a one-stop utility function to deserialize any datastructure
// encoded using the serialization format of lnwire.
func ReadElement(r io.Reader, element interface{}) error {
var err error
switch e := element.(type) {
case *bool:
var b [1]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
if b[0] == 1 {
*e = true
}
case *NodeAlias:
var a [32]byte
if _, err := io.ReadFull(r, a[:]); err != nil {
return err
}
alias, err := NewNodeAlias(string(a[:]))
if err != nil {
return err
}
*e = alias
case *QueryEncoding:
var b [1]uint8
if _, err := r.Read(b[:]); err != nil {
return err
}
*e = QueryEncoding(b[0])
case *uint8:
var b [1]uint8
if _, err := r.Read(b[:]); err != nil {
return err
}
*e = b[0]
case *FundingFlag:
var b [1]uint8
if _, err := r.Read(b[:]); err != nil {
return err
}
*e = FundingFlag(b[0])
case *uint16:
var b [2]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
*e = binary.BigEndian.Uint16(b[:])
case *ChanUpdateMsgFlags:
var b [1]uint8
if _, err := r.Read(b[:]); err != nil {
return err
}
*e = ChanUpdateMsgFlags(b[0])
case *ChanUpdateChanFlags:
var b [1]uint8
if _, err := r.Read(b[:]); err != nil {
return err
}
*e = ChanUpdateChanFlags(b[0])
case *uint32:
var b [4]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
*e = binary.BigEndian.Uint32(b[:])
case *uint64:
var b [8]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
*e = binary.BigEndian.Uint64(b[:])
case *MilliSatoshi:
var b [8]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
*e = MilliSatoshi(int64(binary.BigEndian.Uint64(b[:])))
case *btcutil.Amount:
var b [8]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
*e = btcutil.Amount(int64(binary.BigEndian.Uint64(b[:])))
case **btcec.PublicKey:
var b [btcec.PubKeyBytesLenCompressed]byte
if _, err = io.ReadFull(r, b[:]); err != nil {
return err
}
pubKey, err := btcec.ParsePubKey(b[:])
if err != nil {
return err
}
*e = pubKey
case *RawFeatureVector:
f := NewRawFeatureVector()
err = f.Decode(r)
if err != nil {
return err
}
*e = *f
case **RawFeatureVector:
f := NewRawFeatureVector()
err = f.Decode(r)
if err != nil {
return err
}
*e = f
case *[]Sig:
var l [2]byte
if _, err := io.ReadFull(r, l[:]); err != nil {
return err
}
numSigs := binary.BigEndian.Uint16(l[:])
var sigs []Sig
if numSigs > 0 {
sigs = make([]Sig, numSigs)
for i := 0; i < int(numSigs); i++ {
if err := ReadElement(r, &sigs[i]); err != nil {
return err
}
}
}
*e = sigs
case *Sig:
if _, err := io.ReadFull(r, e.bytes[:]); err != nil {
return err
}
case *OpaqueReason:
var l [2]byte
if _, err := io.ReadFull(r, l[:]); err != nil {
return err
}
reasonLen := binary.BigEndian.Uint16(l[:])
*e = OpaqueReason(make([]byte, reasonLen))
if _, err := io.ReadFull(r, *e); err != nil {
return err
}
case *WarningData:
var l [2]byte
if _, err := io.ReadFull(r, l[:]); err != nil {
return err
}
errorLen := binary.BigEndian.Uint16(l[:])
*e = WarningData(make([]byte, errorLen))
if _, err := io.ReadFull(r, *e); err != nil {
return err
}
case *ErrorData:
var l [2]byte
if _, err := io.ReadFull(r, l[:]); err != nil {
return err
}
errorLen := binary.BigEndian.Uint16(l[:])
*e = ErrorData(make([]byte, errorLen))
if _, err := io.ReadFull(r, *e); err != nil {
return err
}
case *PingPayload:
var l [2]byte
if _, err := io.ReadFull(r, l[:]); err != nil {
return err
}
pingLen := binary.BigEndian.Uint16(l[:])
*e = PingPayload(make([]byte, pingLen))
if _, err := io.ReadFull(r, *e); err != nil {
return err
}
case *PongPayload:
var l [2]byte
if _, err := io.ReadFull(r, l[:]); err != nil {
return err
}
pongLen := binary.BigEndian.Uint16(l[:])
*e = PongPayload(make([]byte, pongLen))
if _, err := io.ReadFull(r, *e); err != nil {
return err
}
case *[33]byte:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case []byte:
if _, err := io.ReadFull(r, e); err != nil {
return err
}
case *PkScript:
pkScript, err := wire.ReadVarBytes(r, 0, 34, "pkscript")
if err != nil {
return err
}
*e = pkScript
case *wire.OutPoint:
var h [32]byte
if _, err = io.ReadFull(r, h[:]); err != nil {
return err
}
hash, err := chainhash.NewHash(h[:])
if err != nil {
return err
}
var idxBytes [2]byte
_, err = io.ReadFull(r, idxBytes[:])
if err != nil {
return err
}
index := binary.BigEndian.Uint16(idxBytes[:])
*e = wire.OutPoint{
Hash: *hash,
Index: uint32(index),
}
case *FailCode:
if err := ReadElement(r, (*uint16)(e)); err != nil {
return err
}
case *ChannelID:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *ShortChannelID:
var blockHeight [4]byte
if _, err = io.ReadFull(r, blockHeight[1:]); err != nil {
return err
}
var txIndex [4]byte
if _, err = io.ReadFull(r, txIndex[1:]); err != nil {
return err
}
var txPosition [2]byte
if _, err = io.ReadFull(r, txPosition[:]); err != nil {
return err
}
*e = ShortChannelID{
BlockHeight: binary.BigEndian.Uint32(blockHeight[:]),
TxIndex: binary.BigEndian.Uint32(txIndex[:]),
TxPosition: binary.BigEndian.Uint16(txPosition[:]),
}
case *[]net.Addr:
// First, we'll read the number of total bytes that have been
// used to encode the set of addresses.
var numAddrsBytes [2]byte
if _, err = io.ReadFull(r, numAddrsBytes[:]); err != nil {
return err
}
addrsLen := binary.BigEndian.Uint16(numAddrsBytes[:])
// With the number of addresses, read, we'll now pull in the
// buffer of the encoded addresses into memory.
addrs := make([]byte, addrsLen)
if _, err := io.ReadFull(r, addrs[:]); err != nil {
return err
}
addrBuf := bytes.NewReader(addrs)
// Finally, we'll parse the remaining address payload in
// series, using the first byte to denote how to decode the
// address itself.
var (
addresses []net.Addr
addrBytesRead uint16
)
for addrBytesRead < addrsLen {
var descriptor [1]byte
if _, err = io.ReadFull(addrBuf, descriptor[:]); err != nil {
return err
}
addrBytesRead++
var address net.Addr
switch aType := addressType(descriptor[0]); aType {
case noAddr:
addrBytesRead += aType.AddrLen()
continue
case tcp4Addr:
var ip [4]byte
if _, err := io.ReadFull(addrBuf, ip[:]); err != nil {
return err
}
var port [2]byte
if _, err := io.ReadFull(addrBuf, port[:]); err != nil {
return err
}
address = &net.TCPAddr{
IP: net.IP(ip[:]),
Port: int(binary.BigEndian.Uint16(port[:])),
}
addrBytesRead += aType.AddrLen()
case tcp6Addr:
var ip [16]byte
if _, err := io.ReadFull(addrBuf, ip[:]); err != nil {
return err
}
var port [2]byte
if _, err := io.ReadFull(addrBuf, port[:]); err != nil {
return err
}
address = &net.TCPAddr{
IP: net.IP(ip[:]),
Port: int(binary.BigEndian.Uint16(port[:])),
}
addrBytesRead += aType.AddrLen()
case v2OnionAddr:
var h [tor.V2DecodedLen]byte
if _, err := io.ReadFull(addrBuf, h[:]); err != nil {
return err
}
var p [2]byte
if _, err := io.ReadFull(addrBuf, p[:]); err != nil {
return err
}
onionService := tor.Base32Encoding.EncodeToString(h[:])
onionService += tor.OnionSuffix
port := int(binary.BigEndian.Uint16(p[:]))
address = &tor.OnionAddr{
OnionService: onionService,
Port: port,
}
addrBytesRead += aType.AddrLen()
case v3OnionAddr:
var h [tor.V3DecodedLen]byte
if _, err := io.ReadFull(addrBuf, h[:]); err != nil {
return err
}
var p [2]byte
if _, err := io.ReadFull(addrBuf, p[:]); err != nil {
return err
}
onionService := tor.Base32Encoding.EncodeToString(h[:])
onionService += tor.OnionSuffix
port := int(binary.BigEndian.Uint16(p[:]))
address = &tor.OnionAddr{
OnionService: onionService,
Port: port,
}
addrBytesRead += aType.AddrLen()
default:
// If we don't understand this address type,
// we just store it along with the remaining
// address bytes as type OpaqueAddrs. We need
// to hold onto the bytes so that we can still
// write them back to the wire when we
// propagate this message.
payloadLen := 1 + addrsLen - addrBytesRead
payload := make([]byte, payloadLen)
// First write a byte for the address type that
// we already read.
payload[0] = byte(aType)
// Now append the rest of the address bytes.
_, err := io.ReadFull(addrBuf, payload[1:])
if err != nil {
return err
}
address = &OpaqueAddrs{
Payload: payload,
}
addrBytesRead = addrsLen
}
addresses = append(addresses, address)
}
*e = addresses
case *color.RGBA:
err := ReadElements(r,
&e.R,
&e.G,
&e.B,
)
if err != nil {
return err
}
case *DeliveryAddress:
var addrLen [2]byte
if _, err = io.ReadFull(r, addrLen[:]); err != nil {
return err
}
length := binary.BigEndian.Uint16(addrLen[:])
var addrBytes [deliveryAddressMaxSize]byte
if length > deliveryAddressMaxSize {
return fmt.Errorf(
"cannot read %d bytes into addrBytes", length,
)
}
if _, err = io.ReadFull(r, addrBytes[:length]); err != nil {
return err
}
*e = addrBytes[:length]
case *PartialSig:
var sig PartialSig
if err = sig.Decode(r); err != nil {
return err
}
*e = sig
case *ExtraOpaqueData:
return e.Decode(r)
default:
return fmt.Errorf("unknown type in ReadElement: %T", e)
}
return nil
}
// ReadElements deserializes a variable number of elements into the passed
// io.Reader, with each element being deserialized according to the ReadElement
// function.
func ReadElements(r io.Reader, elements ...interface{}) error {
for _, element := range elements {
err := ReadElement(r, element)
if err != nil {
return err
}
}
return nil
}
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// code derived from https://github .com/btcsuite/btcd/blob/master/wire/message.go
// Copyright (C) 2015-2022 The Lightning Network Developers
package lnwire
import (
"bytes"
"encoding/binary"
"fmt"
"io"
)
// MessageTypeSize is the size in bytes of the message type field in the header
// of all messages.
const MessageTypeSize = 2
// MessageType is the unique 2 byte big-endian integer that indicates the type
// of message on the wire. All messages have a very simple header which
// consists simply of 2-byte message type. We omit a length field, and checksum
// as the Lightning Protocol is intended to be encapsulated within a
// confidential+authenticated cryptographic messaging protocol.
type MessageType uint16
// The currently defined message types within this current version of the
// Lightning protocol.
const (
MsgWarning MessageType = 1
MsgStfu = 2
MsgInit = 16
MsgError = 17
MsgPing = 18
MsgPong = 19
MsgOpenChannel = 32
MsgAcceptChannel = 33
MsgFundingCreated = 34
MsgFundingSigned = 35
MsgChannelReady = 36
MsgShutdown = 38
MsgClosingSigned = 39
MsgClosingComplete = 40
MsgClosingSig = 41
MsgDynPropose = 111
MsgDynAck = 113
MsgDynReject = 115
MsgUpdateAddHTLC = 128
MsgUpdateFulfillHTLC = 130
MsgUpdateFailHTLC = 131
MsgCommitSig = 132
MsgRevokeAndAck = 133
MsgUpdateFee = 134
MsgUpdateFailMalformedHTLC = 135
MsgChannelReestablish = 136
MsgChannelAnnouncement = 256
MsgNodeAnnouncement = 257
MsgChannelUpdate = 258
MsgAnnounceSignatures = 259
MsgAnnounceSignatures2 = 260
MsgQueryShortChanIDs = 261
MsgReplyShortChanIDsEnd = 262
MsgQueryChannelRange = 263
MsgReplyChannelRange = 264
MsgGossipTimestampRange = 265
MsgChannelAnnouncement2 = 267
MsgChannelUpdate2 = 271
MsgKickoffSig = 777
// MsgEnd defines the end of the official message range of the protocol.
// If a new message is added beyond this message, then this should be
// modified.
MsgEnd = 778
)
// IsChannelUpdate is a filter function that discerns channel update messages
// from the other messages in the Lightning Network Protocol.
func (t MessageType) IsChannelUpdate() bool {
switch t {
case MsgUpdateAddHTLC:
return true
case MsgUpdateFulfillHTLC:
return true
case MsgUpdateFailHTLC:
return true
case MsgUpdateFailMalformedHTLC:
return true
case MsgUpdateFee:
return true
default:
return false
}
}
// ErrorEncodeMessage is used when failed to encode the message payload.
func ErrorEncodeMessage(err error) error {
return fmt.Errorf("failed to encode message to buffer, got %w", err)
}
// ErrorWriteMessageType is used when failed to write the message type.
func ErrorWriteMessageType(err error) error {
return fmt.Errorf("failed to write message type, got %w", err)
}
// ErrorPayloadTooLarge is used when the payload size exceeds the
// MaxMsgBody.
func ErrorPayloadTooLarge(size int) error {
return fmt.Errorf(
"message payload is too large - encoded %d bytes, "+
"but maximum message payload is %d bytes",
size, MaxMsgBody,
)
}
// String return the string representation of message type.
func (t MessageType) String() string {
switch t {
case MsgWarning:
return "Warning"
case MsgStfu:
return "Stfu"
case MsgInit:
return "Init"
case MsgOpenChannel:
return "MsgOpenChannel"
case MsgAcceptChannel:
return "MsgAcceptChannel"
case MsgFundingCreated:
return "MsgFundingCreated"
case MsgFundingSigned:
return "MsgFundingSigned"
case MsgChannelReady:
return "ChannelReady"
case MsgShutdown:
return "Shutdown"
case MsgClosingSigned:
return "ClosingSigned"
case MsgDynPropose:
return "DynPropose"
case MsgDynAck:
return "DynAck"
case MsgDynReject:
return "DynReject"
case MsgKickoffSig:
return "KickoffSig"
case MsgUpdateAddHTLC:
return "UpdateAddHTLC"
case MsgUpdateFailHTLC:
return "UpdateFailHTLC"
case MsgUpdateFulfillHTLC:
return "UpdateFulfillHTLC"
case MsgCommitSig:
return "CommitSig"
case MsgRevokeAndAck:
return "RevokeAndAck"
case MsgUpdateFailMalformedHTLC:
return "UpdateFailMalformedHTLC"
case MsgChannelReestablish:
return "ChannelReestablish"
case MsgError:
return "Error"
case MsgChannelAnnouncement:
return "ChannelAnnouncement"
case MsgChannelUpdate:
return "ChannelUpdate"
case MsgNodeAnnouncement:
return "NodeAnnouncement"
case MsgPing:
return "Ping"
case MsgAnnounceSignatures:
return "AnnounceSignatures"
case MsgPong:
return "Pong"
case MsgUpdateFee:
return "UpdateFee"
case MsgQueryShortChanIDs:
return "QueryShortChanIDs"
case MsgReplyShortChanIDsEnd:
return "ReplyShortChanIDsEnd"
case MsgQueryChannelRange:
return "QueryChannelRange"
case MsgReplyChannelRange:
return "ReplyChannelRange"
case MsgGossipTimestampRange:
return "GossipTimestampRange"
case MsgClosingComplete:
return "ClosingComplete"
case MsgClosingSig:
return "ClosingSig"
case MsgAnnounceSignatures2:
return "MsgAnnounceSignatures2"
case MsgChannelAnnouncement2:
return "ChannelAnnouncement2"
case MsgChannelUpdate2:
return "ChannelUpdate2"
default:
return "<unknown>"
}
}
// UnknownMessage is an implementation of the error interface that allows the
// creation of an error in response to an unknown message.
type UnknownMessage struct {
messageType MessageType
}
// Error returns a human readable string describing the error.
//
// This is part of the error interface.
func (u *UnknownMessage) Error() string {
return fmt.Sprintf("unable to parse message of unknown type: %v",
u.messageType)
}
// Serializable is an interface which defines a lightning wire serializable
// object.
type Serializable interface {
// Decode reads the bytes stream and converts it to the object.
Decode(io.Reader, uint32) error
// Encode converts object to the bytes stream and write it into the
// write buffer.
Encode(*bytes.Buffer, uint32) error
}
// Message is an interface that defines a lightning wire protocol message. The
// interface is general in order to allow implementing types full control over
// the representation of its data.
type Message interface {
Serializable
MsgType() MessageType
}
// LinkUpdater is an interface implemented by most messages in BOLT 2 that are
// allowed to update the channel state.
type LinkUpdater interface {
// All LinkUpdater messages are messages and so we embed the interface
// so that we can treat it as a message if all we know about it is that
// it is a LinkUpdater message.
Message
// TargetChanID returns the channel id of the link for which this
// message is intended.
TargetChanID() ChannelID
}
// SizeableMessage is an interface that extends the base Message interface with
// a method to calculate the serialized size of a message.
type SizeableMessage interface {
Message
// SerializedSize returns the serialized size of the message in bytes.
// The returned size includes the message type header bytes.
SerializedSize() (uint32, error)
}
// MessageSerializedSize calculates the serialized size of a message in bytes.
// This is a helper function that can be used by all message types to implement
// the SerializedSize method.
func MessageSerializedSize(msg Message) (uint32, error) {
var buf bytes.Buffer
// Encode the message to the buffer.
if err := msg.Encode(&buf, 0); err != nil {
return 0, err
}
// Add the size of the message type.
return uint32(buf.Len()) + MessageTypeSize, nil
}
// makeEmptyMessage creates a new empty message of the proper concrete type
// based on the passed message type.
func makeEmptyMessage(msgType MessageType) (Message, error) {
var msg Message
switch msgType {
case MsgWarning:
msg = &Warning{}
case MsgStfu:
msg = &Stfu{}
case MsgInit:
msg = &Init{}
case MsgOpenChannel:
msg = &OpenChannel{}
case MsgAcceptChannel:
msg = &AcceptChannel{}
case MsgFundingCreated:
msg = &FundingCreated{}
case MsgFundingSigned:
msg = &FundingSigned{}
case MsgChannelReady:
msg = &ChannelReady{}
case MsgShutdown:
msg = &Shutdown{}
case MsgClosingSigned:
msg = &ClosingSigned{}
case MsgDynPropose:
msg = &DynPropose{}
case MsgDynAck:
msg = &DynAck{}
case MsgDynReject:
msg = &DynReject{}
case MsgKickoffSig:
msg = &KickoffSig{}
case MsgUpdateAddHTLC:
msg = &UpdateAddHTLC{}
case MsgUpdateFailHTLC:
msg = &UpdateFailHTLC{}
case MsgUpdateFulfillHTLC:
msg = &UpdateFulfillHTLC{}
case MsgCommitSig:
msg = &CommitSig{}
case MsgRevokeAndAck:
msg = &RevokeAndAck{}
case MsgUpdateFee:
msg = &UpdateFee{}
case MsgUpdateFailMalformedHTLC:
msg = &UpdateFailMalformedHTLC{}
case MsgChannelReestablish:
msg = &ChannelReestablish{}
case MsgError:
msg = &Error{}
case MsgChannelAnnouncement:
msg = &ChannelAnnouncement1{}
case MsgChannelUpdate:
msg = &ChannelUpdate1{}
case MsgNodeAnnouncement:
msg = &NodeAnnouncement{}
case MsgPing:
msg = &Ping{}
case MsgAnnounceSignatures:
msg = &AnnounceSignatures1{}
case MsgPong:
msg = &Pong{}
case MsgQueryShortChanIDs:
msg = &QueryShortChanIDs{}
case MsgReplyShortChanIDsEnd:
msg = &ReplyShortChanIDsEnd{}
case MsgQueryChannelRange:
msg = &QueryChannelRange{}
case MsgReplyChannelRange:
msg = &ReplyChannelRange{}
case MsgGossipTimestampRange:
msg = &GossipTimestampRange{}
case MsgClosingComplete:
msg = &ClosingComplete{}
case MsgClosingSig:
msg = &ClosingSig{}
case MsgAnnounceSignatures2:
msg = &AnnounceSignatures2{}
case MsgChannelAnnouncement2:
msg = &ChannelAnnouncement2{}
case MsgChannelUpdate2:
msg = &ChannelUpdate2{}
default:
// If the message is not within our custom range and has not
// specifically been overridden, return an unknown message.
//
// Note that we do not allow custom message overrides to replace
// known message types, only protocol messages that are not yet
// known to lnd.
if msgType < CustomTypeStart && !IsCustomOverride(msgType) {
return nil, &UnknownMessage{msgType}
}
msg = &Custom{
Type: msgType,
}
}
return msg, nil
}
// MakeEmptyMessage creates a new empty message of the proper concrete type
// based on the passed message type. This is exported to be used in tests.
func MakeEmptyMessage(msgType MessageType) (Message, error) {
return makeEmptyMessage(msgType)
}
// WriteMessage writes a lightning Message to a buffer including the necessary
// header information and returns the number of bytes written. If any error is
// encountered, the buffer passed will be reset to its original state since we
// don't want any broken bytes left. In other words, no bytes will be written
// if there's an error. Either all or none of the message bytes will be written
// to the buffer.
//
// NOTE: this method is not concurrent safe.
func WriteMessage(buf *bytes.Buffer, msg Message, pver uint32) (int, error) {
// Record the size of the bytes already written in buffer.
oldByteSize := buf.Len()
// cleanBrokenBytes is a helper closure that helps reset the buffer to
// its original state. It truncates all the bytes written in current
// scope.
var cleanBrokenBytes = func(b *bytes.Buffer) int {
b.Truncate(oldByteSize)
return 0
}
// Write the message type.
var mType [2]byte
binary.BigEndian.PutUint16(mType[:], uint16(msg.MsgType()))
msgTypeBytes, err := buf.Write(mType[:])
if err != nil {
return cleanBrokenBytes(buf), ErrorWriteMessageType(err)
}
// Use the write buffer to encode our message.
if err := msg.Encode(buf, pver); err != nil {
return cleanBrokenBytes(buf), ErrorEncodeMessage(err)
}
// Enforce maximum overall message payload. The write buffer now has
// the size of len(originalBytes) + len(payload) + len(type). We want
// to enforce the payload here, so we subtract it by the length of the
// type and old bytes.
lenp := buf.Len() - oldByteSize - msgTypeBytes
if lenp > MaxMsgBody {
return cleanBrokenBytes(buf), ErrorPayloadTooLarge(lenp)
}
return buf.Len() - oldByteSize, nil
}
// ReadMessage reads, validates, and parses the next Lightning message from r
// for the provided protocol version.
func ReadMessage(r io.Reader, pver uint32) (Message, error) {
// First, we'll read out the first two bytes of the message so we can
// create the proper empty message.
var mType [2]byte
if _, err := io.ReadFull(r, mType[:]); err != nil {
return nil, err
}
msgType := MessageType(binary.BigEndian.Uint16(mType[:]))
// Now that we know the target message type, we can create the proper
// empty message type and decode the message into it.
msg, err := makeEmptyMessage(msgType)
if err != nil {
return nil, err
}
if err := msg.Decode(r, pver); err != nil {
return nil, err
}
return msg, nil
}
package lnwire
import (
"fmt"
"io"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// mSatScale is a value that's used to scale satoshis to milli-satoshis, and
// the other way around.
mSatScale uint64 = 1000
// MaxMilliSatoshi is the maximum number of msats that can be expressed
// in this data type.
MaxMilliSatoshi = ^MilliSatoshi(0)
)
// MilliSatoshi are the native unit of the Lightning Network. A milli-satoshi
// is simply 1/1000th of a satoshi. There are 1000 milli-satoshis in a single
// satoshi. Within the network, all HTLC payments are denominated in
// milli-satoshis. As milli-satoshis aren't deliverable on the native
// blockchain, before settling to broadcasting, the values are rounded down to
// the nearest satoshi.
type MilliSatoshi uint64
// NewMSatFromSatoshis creates a new MilliSatoshi instance from a target amount
// of satoshis.
func NewMSatFromSatoshis(sat btcutil.Amount) MilliSatoshi {
return MilliSatoshi(uint64(sat) * mSatScale)
}
// ToBTC converts the target MilliSatoshi amount to its corresponding value
// when expressed in BTC.
func (m MilliSatoshi) ToBTC() float64 {
sat := m.ToSatoshis()
return sat.ToBTC()
}
// ToSatoshis converts the target MilliSatoshi amount to satoshis. Simply, this
// sheds a factor of 1000 from the mSAT amount in order to convert it to SAT.
func (m MilliSatoshi) ToSatoshis() btcutil.Amount {
return btcutil.Amount(uint64(m) / mSatScale)
}
// String returns the string representation of the mSAT amount.
func (m MilliSatoshi) String() string {
return fmt.Sprintf("%v mSAT", uint64(m))
}
// TODO(roasbeef): extend with arithmetic operations?
// Record returns a TLV record that can be used to encode/decode a MilliSatoshi
// to/from a TLV stream.
func (m *MilliSatoshi) Record() tlv.Record {
return tlv.MakeDynamicRecord(
0, m, tlv.SizeBigSize(m), encodeMilliSatoshis,
decodeMilliSatoshis,
)
}
func encodeMilliSatoshis(w io.Writer, val interface{}, buf *[8]byte) error {
if v, ok := val.(*MilliSatoshi); ok {
bigSize := uint64(*v)
return tlv.EBigSize(w, &bigSize, buf)
}
return tlv.NewTypeForEncodingErr(val, "lnwire.MilliSatoshi")
}
func decodeMilliSatoshis(r io.Reader, val interface{}, buf *[8]byte,
l uint64) error {
if v, ok := val.(*MilliSatoshi); ok {
var bigSize uint64
err := tlv.DBigSize(r, &bigSize, buf, l)
if err != nil {
return err
}
*v = MilliSatoshi(bigSize)
return nil
}
return tlv.NewTypeForDecodingErr(val, "lnwire.MilliSatoshi", l, l)
}
package lnwire
import (
"io"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/lightningnetwork/lnd/tlv"
)
// NonceRecordTypeT is the TLV type used to encode a local musig2 nonce.
type NonceRecordTypeT = tlv.TlvType4
// nonceRecordType is the TLV (integer) type used to encode a local musig2
// nonce.
var nonceRecordType tlv.Type = (NonceRecordTypeT)(nil).TypeVal()
type (
// Musig2Nonce represents a musig2 public nonce, which is the
// concatenation of two EC points serialized in compressed format.
Musig2Nonce [musig2.PubNonceSize]byte
// Musig2NonceTLV is a TLV type that can be used to encode/decode a
// musig2 nonce. This is an optional TLV.
Musig2NonceTLV = tlv.RecordT[NonceRecordTypeT, Musig2Nonce]
// OptMusig2NonceTLV is a TLV type that can be used to encode/decode a
// musig2 nonce.
OptMusig2NonceTLV = tlv.OptionalRecordT[NonceRecordTypeT, Musig2Nonce]
)
// Record returns a TLV record that can be used to encode/decode the musig2
// nonce from a given TLV stream.
func (m *Musig2Nonce) Record() tlv.Record {
return tlv.MakeStaticRecord(
nonceRecordType, m, musig2.PubNonceSize,
nonceTypeEncoder, nonceTypeDecoder,
)
}
// nonceTypeEncoder is a custom TLV encoder for the Musig2Nonce type.
func nonceTypeEncoder(w io.Writer, val interface{}, _ *[8]byte) error {
if v, ok := val.(*Musig2Nonce); ok {
_, err := w.Write(v[:])
return err
}
return tlv.NewTypeForEncodingErr(val, "lnwire.Musig2Nonce")
}
// nonceTypeDecoder is a custom TLV decoder for the Musig2Nonce record.
func nonceTypeDecoder(r io.Reader, val interface{}, _ *[8]byte,
l uint64) error {
if v, ok := val.(*Musig2Nonce); ok {
_, err := io.ReadFull(r, v[:])
return err
}
return tlv.NewTypeForDecodingErr(
val, "lnwire.Musig2Nonce", l, musig2.PubNonceSize,
)
}
// SomeMusig2Nonce is a helper function that creates a musig2 nonce TLV.
func SomeMusig2Nonce(nonce Musig2Nonce) OptMusig2NonceTLV {
return tlv.SomeRecordT(
tlv.NewRecordT[NonceRecordTypeT, Musig2Nonce](nonce),
)
}
package lnwire
import (
"fmt"
"net"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/wire"
)
// NetAddress represents information pertaining to the identity and network
// reachability of a peer. Information stored includes the node's identity
// public key for establishing a confidential+authenticated connection, the
// service bits it supports, and a TCP address the node is reachable at.
//
// TODO(roasbeef): merge with LinkNode in some fashion
type NetAddress struct {
// IdentityKey is the long-term static public key for a node. This node is
// used throughout the network as a node's identity key. It is used to
// authenticate any data sent to the network on behalf of the node, and
// additionally to establish a confidential+authenticated connection with
// the node.
IdentityKey *btcec.PublicKey
// Address is the IP address and port of the node. This is left
// general so that multiple implementations can be used.
Address net.Addr
// ChainNet is the Bitcoin network this node is associated with.
// TODO(roasbeef): make a slice in the future for multi-chain
ChainNet wire.BitcoinNet
}
// A compile time assertion to ensure that NetAddress meets the net.Addr
// interface.
var _ net.Addr = (*NetAddress)(nil)
// String returns a human readable string describing the target NetAddress. The
// current string format is: <pubkey>@host.
//
// This part of the net.Addr interface.
func (n *NetAddress) String() string {
// TODO(roasbeef): use base58?
pubkey := n.IdentityKey.SerializeCompressed()
return fmt.Sprintf("%x@%v", pubkey, n.Address)
}
// Network returns the name of the network this address is bound to.
//
// This part of the net.Addr interface.
func (n *NetAddress) Network() string {
return n.Address.Network()
}
package lnwire
import (
"bytes"
"fmt"
"image/color"
"io"
"net"
"unicode/utf8"
)
// ErrUnknownAddrType is an error returned if we encounter an unknown address type
// when parsing addresses.
type ErrUnknownAddrType struct {
addrType addressType
}
// Error returns a human readable string describing the error.
//
// NOTE: implements the error interface.
func (e ErrUnknownAddrType) Error() string {
return fmt.Sprintf("unknown address type: %v", e.addrType)
}
// ErrInvalidNodeAlias is an error returned if a node alias we parse on the
// wire is invalid, as in it has non UTF-8 characters.
type ErrInvalidNodeAlias struct{}
// Error returns a human readable string describing the error.
//
// NOTE: implements the error interface.
func (e ErrInvalidNodeAlias) Error() string {
return "node alias has non-utf8 characters"
}
// NodeAlias is a hex encoded UTF-8 string that may be displayed as an
// alternative to the node's ID. Notice that aliases are not unique and may be
// freely chosen by the node operators.
type NodeAlias [32]byte
// NewNodeAlias creates a new instance of a NodeAlias. Verification is
// performed on the passed string to ensure it meets the alias requirements.
func NewNodeAlias(s string) (NodeAlias, error) {
var n NodeAlias
if len(s) > 32 {
return n, fmt.Errorf("alias too large: max is %v, got %v", 32,
len(s))
}
if !utf8.ValidString(s) {
return n, &ErrInvalidNodeAlias{}
}
copy(n[:], []byte(s))
return n, nil
}
// String returns a utf8 string representation of the alias bytes.
func (n NodeAlias) String() string {
// Trim trailing zero-bytes for presentation
return string(bytes.Trim(n[:], "\x00"))
}
// NodeAnnouncement message is used to announce the presence of a Lightning
// node and also to signal that the node is accepting incoming connections.
// Each NodeAnnouncement authenticating the advertised information within the
// announcement via a signature using the advertised node pubkey.
type NodeAnnouncement struct {
// Signature is used to prove the ownership of node id.
Signature Sig
// Features is the list of protocol features this node supports.
Features *RawFeatureVector
// Timestamp allows ordering in the case of multiple announcements.
Timestamp uint32
// NodeID is a public key which is used as node identification.
NodeID [33]byte
// RGBColor is used to customize their node's appearance in maps and
// graphs
RGBColor color.RGBA
// Alias is used to customize their node's appearance in maps and
// graphs
Alias NodeAlias
// Address includes two specification fields: 'ipv6' and 'port' on
// which the node is accepting incoming connections.
Addresses []net.Addr
// ExtraOpaqueData is the set of data that was appended to this
// message, some of which we may not actually know how to iterate or
// parse. By holding onto this data, we ensure that we're able to
// properly validate the set of signatures that cover these new fields,
// and ensure we're able to make upgrades to the network in a forwards
// compatible manner.
ExtraOpaqueData ExtraOpaqueData
}
// A compile time check to ensure NodeAnnouncement implements the
// lnwire.Message interface.
var _ Message = (*NodeAnnouncement)(nil)
// A compile time check to ensure NodeAnnouncement implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*NodeAnnouncement)(nil)
// Decode deserializes a serialized NodeAnnouncement stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (a *NodeAnnouncement) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&a.Signature,
&a.Features,
&a.Timestamp,
&a.NodeID,
&a.RGBColor,
&a.Alias,
&a.Addresses,
&a.ExtraOpaqueData,
)
}
// Encode serializes the target NodeAnnouncement into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (a *NodeAnnouncement) Encode(w *bytes.Buffer, pver uint32) error {
if err := WriteSig(w, a.Signature); err != nil {
return err
}
if err := WriteRawFeatureVector(w, a.Features); err != nil {
return err
}
if err := WriteUint32(w, a.Timestamp); err != nil {
return err
}
if err := WriteBytes(w, a.NodeID[:]); err != nil {
return err
}
if err := WriteColorRGBA(w, a.RGBColor); err != nil {
return err
}
if err := WriteNodeAlias(w, a.Alias); err != nil {
return err
}
if err := WriteNetAddrs(w, a.Addresses); err != nil {
return err
}
return WriteBytes(w, a.ExtraOpaqueData)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (a *NodeAnnouncement) MsgType() MessageType {
return MsgNodeAnnouncement
}
// DataToSign returns the part of the message that should be signed.
func (a *NodeAnnouncement) DataToSign() ([]byte, error) {
// We should not include the signatures itself.
buffer := make([]byte, 0, MaxMsgBody)
buf := bytes.NewBuffer(buffer)
if err := WriteRawFeatureVector(buf, a.Features); err != nil {
return nil, err
}
if err := WriteUint32(buf, a.Timestamp); err != nil {
return nil, err
}
if err := WriteBytes(buf, a.NodeID[:]); err != nil {
return nil, err
}
if err := WriteColorRGBA(buf, a.RGBColor); err != nil {
return nil, err
}
if err := WriteNodeAlias(buf, a.Alias); err != nil {
return nil, err
}
if err := WriteNetAddrs(buf, a.Addresses); err != nil {
return nil, err
}
if err := WriteBytes(buf, a.ExtraOpaqueData); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (a *NodeAnnouncement) SerializedSize() (uint32, error) {
return MessageSerializedSize(a)
}
package lnwire
import (
"bufio"
"bytes"
"crypto/sha256"
"encoding/binary"
"fmt"
"io"
"github.com/davecgh/go-spew/spew"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/tlv"
)
// FailureMessage represents the onion failure object identified by its unique
// failure code.
type FailureMessage interface {
// Code returns a failure code describing the exact nature of the
// error.
Code() FailCode
// Error returns a human readable string describing the error. With
// this method, the FailureMessage interface meets the built-in error
// interface.
Error() string
}
// FailureMessageLength is the size of the failure message plus the size of
// padding. The FailureMessage message should always be EXACTLY this size.
const FailureMessageLength = 256
const (
// FlagBadOnion error flag describes an unparsable, encrypted by
// previous node.
FlagBadOnion FailCode = 0x8000
// FlagPerm error flag indicates a permanent failure.
FlagPerm FailCode = 0x4000
// FlagNode error flag indicates a node failure.
FlagNode FailCode = 0x2000
// FlagUpdate error flag indicates a new channel update is enclosed
// within the error.
FlagUpdate FailCode = 0x1000
)
// FailCode specifies the precise reason that an upstream HTLC was canceled.
// Each UpdateFailHTLC message carries a FailCode which is to be passed
// backwards, encrypted at each step back to the source of the HTLC within the
// route.
type FailCode uint16
// The currently defined onion failure types within this current version of the
// Lightning protocol.
const (
CodeNone FailCode = 0
CodeInvalidRealm = FlagBadOnion | 1
CodeTemporaryNodeFailure = FlagNode | 2
CodePermanentNodeFailure = FlagPerm | FlagNode | 2
CodeRequiredNodeFeatureMissing = FlagPerm | FlagNode | 3
CodeInvalidOnionVersion = FlagBadOnion | FlagPerm | 4
CodeInvalidOnionHmac = FlagBadOnion | FlagPerm | 5
CodeInvalidOnionKey = FlagBadOnion | FlagPerm | 6
CodeTemporaryChannelFailure = FlagUpdate | 7
CodePermanentChannelFailure = FlagPerm | 8
CodeRequiredChannelFeatureMissing = FlagPerm | 9
CodeUnknownNextPeer = FlagPerm | 10
CodeAmountBelowMinimum = FlagUpdate | 11
CodeFeeInsufficient = FlagUpdate | 12
CodeIncorrectCltvExpiry = FlagUpdate | 13
CodeExpiryTooSoon = FlagUpdate | 14
CodeChannelDisabled = FlagUpdate | 20
CodeIncorrectOrUnknownPaymentDetails = FlagPerm | 15
CodeIncorrectPaymentAmount = FlagPerm | 16
CodeFinalExpiryTooSoon FailCode = 17
CodeFinalIncorrectCltvExpiry FailCode = 18
CodeFinalIncorrectHtlcAmount FailCode = 19
CodeExpiryTooFar FailCode = 21
CodeInvalidOnionPayload = FlagPerm | 22
CodeMPPTimeout FailCode = 23
CodeInvalidBlinding = FlagBadOnion | FlagPerm | 24 //nolint:ll
)
// String returns the string representation of the failure code.
func (c FailCode) String() string {
switch c {
case CodeInvalidRealm:
return "InvalidRealm"
case CodeTemporaryNodeFailure:
return "TemporaryNodeFailure"
case CodePermanentNodeFailure:
return "PermanentNodeFailure"
case CodeRequiredNodeFeatureMissing:
return "RequiredNodeFeatureMissing"
case CodeInvalidOnionVersion:
return "InvalidOnionVersion"
case CodeInvalidOnionHmac:
return "InvalidOnionHmac"
case CodeInvalidOnionKey:
return "InvalidOnionKey"
case CodeTemporaryChannelFailure:
return "TemporaryChannelFailure"
case CodePermanentChannelFailure:
return "PermanentChannelFailure"
case CodeRequiredChannelFeatureMissing:
return "RequiredChannelFeatureMissing"
case CodeUnknownNextPeer:
return "UnknownNextPeer"
case CodeAmountBelowMinimum:
return "AmountBelowMinimum"
case CodeFeeInsufficient:
return "FeeInsufficient"
case CodeIncorrectCltvExpiry:
return "IncorrectCltvExpiry"
case CodeIncorrectPaymentAmount:
return "IncorrectPaymentAmount"
case CodeExpiryTooSoon:
return "ExpiryTooSoon"
case CodeChannelDisabled:
return "ChannelDisabled"
case CodeIncorrectOrUnknownPaymentDetails:
return "IncorrectOrUnknownPaymentDetails"
case CodeFinalExpiryTooSoon:
return "FinalExpiryTooSoon"
case CodeFinalIncorrectCltvExpiry:
return "FinalIncorrectCltvExpiry"
case CodeFinalIncorrectHtlcAmount:
return "FinalIncorrectHtlcAmount"
case CodeExpiryTooFar:
return "ExpiryTooFar"
case CodeInvalidOnionPayload:
return "InvalidOnionPayload"
case CodeMPPTimeout:
return "MPPTimeout"
case CodeInvalidBlinding:
return "InvalidBlinding"
default:
return "<unknown>"
}
}
// FailInvalidRealm is returned if the realm byte is unknown.
//
// NOTE: May be returned by any node in the payment route.
type FailInvalidRealm struct{}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailInvalidRealm) Error() string {
return f.Code().String()
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailInvalidRealm) Code() FailCode {
return CodeInvalidRealm
}
// FailTemporaryNodeFailure is returned if an otherwise unspecified transient
// error occurs for the entire node.
//
// NOTE: May be returned by any node in the payment route.
type FailTemporaryNodeFailure struct{}
// Code returns the failure unique code.
// NOTE: Part of the FailureMessage interface.
func (f *FailTemporaryNodeFailure) Code() FailCode {
return CodeTemporaryNodeFailure
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailTemporaryNodeFailure) Error() string {
return f.Code().String()
}
// FailPermanentNodeFailure is returned if an otherwise unspecified permanent
// error occurs for the entire node.
//
// NOTE: May be returned by any node in the payment route.
type FailPermanentNodeFailure struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailPermanentNodeFailure) Code() FailCode {
return CodePermanentNodeFailure
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailPermanentNodeFailure) Error() string {
return f.Code().String()
}
// FailRequiredNodeFeatureMissing is returned if a node has requirement
// advertised in its node_announcement features which were not present in the
// onion.
//
// NOTE: May be returned by any node in the payment route.
type FailRequiredNodeFeatureMissing struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailRequiredNodeFeatureMissing) Code() FailCode {
return CodeRequiredNodeFeatureMissing
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailRequiredNodeFeatureMissing) Error() string {
return f.Code().String()
}
// FailPermanentChannelFailure is return if an otherwise unspecified permanent
// error occurs for the outgoing channel (eg. channel (recently).
//
// NOTE: May be returned by any node in the payment route.
type FailPermanentChannelFailure struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailPermanentChannelFailure) Code() FailCode {
return CodePermanentChannelFailure
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailPermanentChannelFailure) Error() string {
return f.Code().String()
}
// FailRequiredChannelFeatureMissing is returned if the outgoing channel has a
// requirement advertised in its channel announcement features which were not
// present in the onion.
//
// NOTE: May only be returned by intermediate nodes.
type FailRequiredChannelFeatureMissing struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailRequiredChannelFeatureMissing) Code() FailCode {
return CodeRequiredChannelFeatureMissing
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailRequiredChannelFeatureMissing) Error() string {
return f.Code().String()
}
// FailUnknownNextPeer is returned if the next peer specified by the onion is
// not known.
//
// NOTE: May only be returned by intermediate nodes.
type FailUnknownNextPeer struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailUnknownNextPeer) Code() FailCode {
return CodeUnknownNextPeer
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailUnknownNextPeer) Error() string {
return f.Code().String()
}
// FailIncorrectPaymentAmount is returned if the amount paid is less than the
// amount expected, the final node MUST fail the HTLC. If the amount paid is
// more than twice the amount expected, the final node SHOULD fail the HTLC.
// This allows the sender to reduce information leakage by altering the amount,
// without allowing accidental gross overpayment.
//
// NOTE: May only be returned by the final node in the path.
type FailIncorrectPaymentAmount struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailIncorrectPaymentAmount) Code() FailCode {
return CodeIncorrectPaymentAmount
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailIncorrectPaymentAmount) Error() string {
return f.Code().String()
}
// FailIncorrectDetails is returned for two reasons:
//
// 1) if the payment hash has already been paid, the final node MAY treat the
// payment hash as unknown, or may succeed in accepting the HTLC. If the
// payment hash is unknown, the final node MUST fail the HTLC.
//
// 2) if the amount paid is less than the amount expected, the final node MUST
// fail the HTLC. If the amount paid is more than twice the amount expected,
// the final node SHOULD fail the HTLC. This allows the sender to reduce
// information leakage by altering the amount, without allowing accidental
// gross overpayment.
//
// NOTE: May only be returned by the final node in the path.
type FailIncorrectDetails struct {
// amount is the value of the extended HTLC.
amount MilliSatoshi
// height is the block height when the htlc was received.
height uint32
// extraOpaqueData contains additional failure message tlv data.
extraOpaqueData ExtraOpaqueData
}
// NewFailIncorrectDetails makes a new instance of the FailIncorrectDetails
// error bound to the specified HTLC amount and acceptance height.
func NewFailIncorrectDetails(amt MilliSatoshi,
height uint32) *FailIncorrectDetails {
return &FailIncorrectDetails{
amount: amt,
height: height,
extraOpaqueData: []byte{},
}
}
// Amount is the value of the extended HTLC.
func (f *FailIncorrectDetails) Amount() MilliSatoshi {
return f.amount
}
// Height is the block height when the htlc was received.
func (f *FailIncorrectDetails) Height() uint32 {
return f.height
}
// ExtraOpaqueData returns additional failure message tlv data.
func (f *FailIncorrectDetails) ExtraOpaqueData() ExtraOpaqueData {
return f.extraOpaqueData
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailIncorrectDetails) Code() FailCode {
return CodeIncorrectOrUnknownPaymentDetails
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailIncorrectDetails) Error() string {
return fmt.Sprintf(
"%v(amt=%v, height=%v)", CodeIncorrectOrUnknownPaymentDetails,
f.amount, f.height,
)
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailIncorrectDetails) Decode(r io.Reader, pver uint32) error {
err := ReadElement(r, &f.amount)
switch {
// This is an optional tack on that was added later in the protocol. As
// a result, older nodes may not include this value. We'll account for
// this by checking for io.EOF here which means that no bytes were read
// at all.
case err == io.EOF:
return nil
case err != nil:
return err
}
// At a later stage, the height field was also tacked on. We need to
// check for io.EOF here as well.
err = ReadElement(r, &f.height)
switch {
case err == io.EOF:
return nil
case err != nil:
return err
}
return f.extraOpaqueData.Decode(r)
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailIncorrectDetails) Encode(w *bytes.Buffer, pver uint32) error {
if err := WriteMilliSatoshi(w, f.amount); err != nil {
return err
}
if err := WriteUint32(w, f.height); err != nil {
return err
}
return f.extraOpaqueData.Encode(w)
}
// FailFinalExpiryTooSoon is returned if the cltv_expiry is too low, the final
// node MUST fail the HTLC.
//
// NOTE: May only be returned by the final node in the path.
type FailFinalExpiryTooSoon struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailFinalExpiryTooSoon) Code() FailCode {
return CodeFinalExpiryTooSoon
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailFinalExpiryTooSoon) Error() string {
return f.Code().String()
}
// NewFinalExpiryTooSoon creates new instance of the FailFinalExpiryTooSoon.
func NewFinalExpiryTooSoon() *FailFinalExpiryTooSoon {
return &FailFinalExpiryTooSoon{}
}
// FailInvalidOnionVersion is returned if the onion version byte is unknown.
//
// NOTE: May be returned only by intermediate nodes.
type FailInvalidOnionVersion struct {
// OnionSHA256 hash of the onion blob which haven't been proceeded.
OnionSHA256 [sha256.Size]byte
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailInvalidOnionVersion) Error() string {
return fmt.Sprintf("InvalidOnionVersion(onion_sha=%x)", f.OnionSHA256[:])
}
// NewInvalidOnionVersion creates new instance of the FailInvalidOnionVersion.
func NewInvalidOnionVersion(onion []byte) *FailInvalidOnionVersion {
return &FailInvalidOnionVersion{OnionSHA256: sha256.Sum256(onion)}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailInvalidOnionVersion) Code() FailCode {
return CodeInvalidOnionVersion
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailInvalidOnionVersion) Decode(r io.Reader, pver uint32) error {
return ReadElement(r, f.OnionSHA256[:])
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailInvalidOnionVersion) Encode(w *bytes.Buffer, pver uint32) error {
return WriteBytes(w, f.OnionSHA256[:])
}
// FailInvalidOnionHmac is return if the onion HMAC is incorrect.
//
// NOTE: May only be returned by intermediate nodes.
type FailInvalidOnionHmac struct {
// OnionSHA256 hash of the onion blob which haven't been proceeded.
OnionSHA256 [sha256.Size]byte
}
// NewInvalidOnionHmac creates new instance of the FailInvalidOnionHmac.
func NewInvalidOnionHmac(onion []byte) *FailInvalidOnionHmac {
return &FailInvalidOnionHmac{OnionSHA256: sha256.Sum256(onion)}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailInvalidOnionHmac) Code() FailCode {
return CodeInvalidOnionHmac
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailInvalidOnionHmac) Decode(r io.Reader, pver uint32) error {
return ReadElement(r, f.OnionSHA256[:])
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailInvalidOnionHmac) Encode(w *bytes.Buffer, pver uint32) error {
return WriteBytes(w, f.OnionSHA256[:])
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailInvalidOnionHmac) Error() string {
return fmt.Sprintf("InvalidOnionHMAC(onion_sha=%x)", f.OnionSHA256[:])
}
// FailInvalidOnionKey is return if the ephemeral key in the onion is
// unparsable.
//
// NOTE: May only be returned by intermediate nodes.
type FailInvalidOnionKey struct {
// OnionSHA256 hash of the onion blob which haven't been proceeded.
OnionSHA256 [sha256.Size]byte
}
// NewInvalidOnionKey creates new instance of the FailInvalidOnionKey.
func NewInvalidOnionKey(onion []byte) *FailInvalidOnionKey {
return &FailInvalidOnionKey{OnionSHA256: sha256.Sum256(onion)}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailInvalidOnionKey) Code() FailCode {
return CodeInvalidOnionKey
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailInvalidOnionKey) Decode(r io.Reader, pver uint32) error {
return ReadElement(r, f.OnionSHA256[:])
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailInvalidOnionKey) Encode(w *bytes.Buffer, pver uint32) error {
return WriteBytes(w, f.OnionSHA256[:])
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailInvalidOnionKey) Error() string {
return fmt.Sprintf("InvalidOnionKey(onion_sha=%x)", f.OnionSHA256[:])
}
// parseChannelUpdateCompatibilityMode will attempt to parse a channel updated
// encoded into an onion error payload in two ways. First, we'll try the
// compatibility oriented version wherein we'll _skip_ the length prefixing on
// the channel update message. Older versions of c-lighting do this so we'll
// attempt to parse these messages in order to retain compatibility. If we're
// unable to pull out a fully valid version, then we'll fall back to the
// regular parsing mechanism which includes the length prefix an NO type byte.
func parseChannelUpdateCompatibilityMode(reader io.Reader, length uint16,
chanUpdate *ChannelUpdate1, pver uint32) error {
// Instantiate a LimitReader because there may be additional data
// present after the channel update. Without limiting the stream, the
// additional data would be interpreted as channel update tlv data.
limitReader := io.LimitReader(reader, int64(length))
r := bufio.NewReader(limitReader)
// We'll peek out two bytes from the buffer without advancing the
// buffer so we can decide how to parse the remainder of it.
maybeTypeBytes, err := r.Peek(2)
if err != nil {
return err
}
// Some nodes well prefix an additional set of bytes in front of their
// channel updates. These bytes will _almost_ always be 258 or the type
// of the ChannelUpdate message.
typeInt := binary.BigEndian.Uint16(maybeTypeBytes)
if typeInt == MsgChannelUpdate {
// At this point it's likely the case that this is a channel
// update message with its type prefixed, so we'll snip off the
// first two bytes and parse it as normal.
var throwAwayTypeBytes [2]byte
_, err := r.Read(throwAwayTypeBytes[:])
if err != nil {
return err
}
}
// At this pint, we've either decided to keep the entire thing, or snip
// off the first two bytes. In either case, we can just read it as
// normal.
return chanUpdate.Decode(r, pver)
}
// FailTemporaryChannelFailure is if an otherwise unspecified transient error
// occurs for the outgoing channel (eg. channel capacity reached, too many
// in-flight htlcs)
//
// NOTE: May only be returned by intermediate nodes.
type FailTemporaryChannelFailure struct {
// Update is used to update information about state of the channel
// which caused the failure.
//
// NOTE: This field is optional.
Update *ChannelUpdate1
}
// NewTemporaryChannelFailure creates new instance of the FailTemporaryChannelFailure.
func NewTemporaryChannelFailure(
update *ChannelUpdate1) *FailTemporaryChannelFailure {
return &FailTemporaryChannelFailure{Update: update}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailTemporaryChannelFailure) Code() FailCode {
return CodeTemporaryChannelFailure
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailTemporaryChannelFailure) Error() string {
if f.Update == nil {
return f.Code().String()
}
return fmt.Sprintf("TemporaryChannelFailure(update=%v)",
spew.Sdump(f.Update))
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailTemporaryChannelFailure) Decode(r io.Reader, pver uint32) error {
var length uint16
err := ReadElement(r, &length)
if err != nil {
return err
}
if length != 0 {
f.Update = &ChannelUpdate1{}
return parseChannelUpdateCompatibilityMode(
r, length, f.Update, pver,
)
}
return nil
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailTemporaryChannelFailure) Encode(w *bytes.Buffer,
pver uint32) error {
if f.Update != nil {
return writeOnionErrorChanUpdate(w, f.Update, pver)
}
// Write zero length to indicate no channel_update is present.
return WriteUint16(w, 0)
}
// FailAmountBelowMinimum is returned if the HTLC does not reach the current
// minimum amount, we tell them the amount of the incoming HTLC and the current
// channel setting for the outgoing channel.
//
// NOTE: May only be returned by the intermediate nodes in the path.
type FailAmountBelowMinimum struct {
// HtlcMsat is the wrong amount of the incoming HTLC.
HtlcMsat MilliSatoshi
// Update is used to update information about state of the channel
// which caused the failure.
Update ChannelUpdate1
}
// NewAmountBelowMinimum creates new instance of the FailAmountBelowMinimum.
func NewAmountBelowMinimum(htlcMsat MilliSatoshi,
update ChannelUpdate1) *FailAmountBelowMinimum {
return &FailAmountBelowMinimum{
HtlcMsat: htlcMsat,
Update: update,
}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailAmountBelowMinimum) Code() FailCode {
return CodeAmountBelowMinimum
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailAmountBelowMinimum) Error() string {
return fmt.Sprintf("AmountBelowMinimum(amt=%v, update=%v", f.HtlcMsat,
spew.Sdump(f.Update))
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailAmountBelowMinimum) Decode(r io.Reader, pver uint32) error {
if err := ReadElement(r, &f.HtlcMsat); err != nil {
return err
}
var length uint16
if err := ReadElement(r, &length); err != nil {
return err
}
f.Update = ChannelUpdate1{}
return parseChannelUpdateCompatibilityMode(
r, length, &f.Update, pver,
)
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailAmountBelowMinimum) Encode(w *bytes.Buffer, pver uint32) error {
if err := WriteMilliSatoshi(w, f.HtlcMsat); err != nil {
return err
}
return writeOnionErrorChanUpdate(w, &f.Update, pver)
}
// FailFeeInsufficient is returned if the HTLC does not pay sufficient fee, we
// tell them the amount of the incoming HTLC and the current channel setting
// for the outgoing channel.
//
// NOTE: May only be returned by intermediate nodes.
type FailFeeInsufficient struct {
// HtlcMsat is the wrong amount of the incoming HTLC.
HtlcMsat MilliSatoshi
// Update is used to update information about state of the channel
// which caused the failure.
Update ChannelUpdate1
}
// NewFeeInsufficient creates new instance of the FailFeeInsufficient.
func NewFeeInsufficient(htlcMsat MilliSatoshi,
update ChannelUpdate1) *FailFeeInsufficient {
return &FailFeeInsufficient{
HtlcMsat: htlcMsat,
Update: update,
}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailFeeInsufficient) Code() FailCode {
return CodeFeeInsufficient
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailFeeInsufficient) Error() string {
return fmt.Sprintf("FeeInsufficient(htlc_amt==%v, update=%v", f.HtlcMsat,
spew.Sdump(f.Update))
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailFeeInsufficient) Decode(r io.Reader, pver uint32) error {
if err := ReadElement(r, &f.HtlcMsat); err != nil {
return err
}
var length uint16
if err := ReadElement(r, &length); err != nil {
return err
}
f.Update = ChannelUpdate1{}
return parseChannelUpdateCompatibilityMode(
r, length, &f.Update, pver,
)
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailFeeInsufficient) Encode(w *bytes.Buffer, pver uint32) error {
if err := WriteMilliSatoshi(w, f.HtlcMsat); err != nil {
return err
}
return writeOnionErrorChanUpdate(w, &f.Update, pver)
}
// FailIncorrectCltvExpiry is returned if outgoing cltv value does not match
// the update add htlc's cltv expiry minus cltv expiry delta for the outgoing
// channel, we tell them the cltv expiry and the current channel setting for
// the outgoing channel.
//
// NOTE: May only be returned by intermediate nodes.
type FailIncorrectCltvExpiry struct {
// CltvExpiry is the wrong absolute timeout in blocks, after which
// outgoing HTLC expires.
CltvExpiry uint32
// Update is used to update information about state of the channel
// which caused the failure.
Update ChannelUpdate1
}
// NewIncorrectCltvExpiry creates new instance of the FailIncorrectCltvExpiry.
func NewIncorrectCltvExpiry(cltvExpiry uint32,
update ChannelUpdate1) *FailIncorrectCltvExpiry {
return &FailIncorrectCltvExpiry{
CltvExpiry: cltvExpiry,
Update: update,
}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailIncorrectCltvExpiry) Code() FailCode {
return CodeIncorrectCltvExpiry
}
func (f *FailIncorrectCltvExpiry) Error() string {
return fmt.Sprintf("IncorrectCltvExpiry(expiry=%v, update=%v",
f.CltvExpiry, spew.Sdump(f.Update))
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailIncorrectCltvExpiry) Decode(r io.Reader, pver uint32) error {
if err := ReadElement(r, &f.CltvExpiry); err != nil {
return err
}
var length uint16
if err := ReadElement(r, &length); err != nil {
return err
}
f.Update = ChannelUpdate1{}
return parseChannelUpdateCompatibilityMode(
r, length, &f.Update, pver,
)
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailIncorrectCltvExpiry) Encode(w *bytes.Buffer, pver uint32) error {
if err := WriteUint32(w, f.CltvExpiry); err != nil {
return err
}
return writeOnionErrorChanUpdate(w, &f.Update, pver)
}
// FailExpiryTooSoon is returned if the ctlv-expiry is too near, we tell them
// the current channel setting for the outgoing channel.
//
// NOTE: May only be returned by intermediate nodes.
type FailExpiryTooSoon struct {
// Update is used to update information about state of the channel
// which caused the failure.
Update ChannelUpdate1
}
// NewExpiryTooSoon creates new instance of the FailExpiryTooSoon.
func NewExpiryTooSoon(update ChannelUpdate1) *FailExpiryTooSoon {
return &FailExpiryTooSoon{
Update: update,
}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailExpiryTooSoon) Code() FailCode {
return CodeExpiryTooSoon
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailExpiryTooSoon) Error() string {
return fmt.Sprintf("ExpiryTooSoon(update=%v", spew.Sdump(f.Update))
}
// Decode decodes the failure from l stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailExpiryTooSoon) Decode(r io.Reader, pver uint32) error {
var length uint16
if err := ReadElement(r, &length); err != nil {
return err
}
f.Update = ChannelUpdate1{}
return parseChannelUpdateCompatibilityMode(
r, length, &f.Update, pver,
)
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailExpiryTooSoon) Encode(w *bytes.Buffer, pver uint32) error {
return writeOnionErrorChanUpdate(w, &f.Update, pver)
}
// FailChannelDisabled is returned if the channel is disabled, we tell them the
// current channel setting for the outgoing channel.
//
// NOTE: May only be returned by intermediate nodes.
type FailChannelDisabled struct {
// Flags least-significant bit must be set to 0 if the creating node
// corresponds to the first node in the previously sent channel
// announcement and 1 otherwise.
Flags uint16
// Update is used to update information about state of the channel
// which caused the failure.
Update ChannelUpdate1
}
// NewChannelDisabled creates new instance of the FailChannelDisabled.
func NewChannelDisabled(flags uint16,
update ChannelUpdate1) *FailChannelDisabled {
return &FailChannelDisabled{
Flags: flags,
Update: update,
}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailChannelDisabled) Code() FailCode {
return CodeChannelDisabled
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailChannelDisabled) Error() string {
return fmt.Sprintf("ChannelDisabled(flags=%v, update=%v", f.Flags,
spew.Sdump(f.Update))
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailChannelDisabled) Decode(r io.Reader, pver uint32) error {
if err := ReadElement(r, &f.Flags); err != nil {
return err
}
var length uint16
if err := ReadElement(r, &length); err != nil {
return err
}
f.Update = ChannelUpdate1{}
return parseChannelUpdateCompatibilityMode(
r, length, &f.Update, pver,
)
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailChannelDisabled) Encode(w *bytes.Buffer, pver uint32) error {
if err := WriteUint16(w, f.Flags); err != nil {
return err
}
return writeOnionErrorChanUpdate(w, &f.Update, pver)
}
// FailFinalIncorrectCltvExpiry is returned if the outgoing_cltv_value does not
// match the ctlv_expiry of the HTLC at the final hop.
//
// NOTE: might be returned by final node only.
type FailFinalIncorrectCltvExpiry struct {
// CltvExpiry is the wrong absolute timeout in blocks, after which
// outgoing HTLC expires.
CltvExpiry uint32
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailFinalIncorrectCltvExpiry) Error() string {
return fmt.Sprintf("FinalIncorrectCltvExpiry(expiry=%v)", f.CltvExpiry)
}
// NewFinalIncorrectCltvExpiry creates new instance of the
// FailFinalIncorrectCltvExpiry.
func NewFinalIncorrectCltvExpiry(cltvExpiry uint32) *FailFinalIncorrectCltvExpiry {
return &FailFinalIncorrectCltvExpiry{
CltvExpiry: cltvExpiry,
}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailFinalIncorrectCltvExpiry) Code() FailCode {
return CodeFinalIncorrectCltvExpiry
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailFinalIncorrectCltvExpiry) Decode(r io.Reader, pver uint32) error {
return ReadElement(r, &f.CltvExpiry)
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailFinalIncorrectCltvExpiry) Encode(w *bytes.Buffer,
pver uint32) error {
return WriteUint32(w, f.CltvExpiry)
}
// FailFinalIncorrectHtlcAmount is returned if the amt_to_forward is higher
// than incoming_htlc_amt of the HTLC at the final hop.
//
// NOTE: May only be returned by the final node.
type FailFinalIncorrectHtlcAmount struct {
// IncomingHTLCAmount is the wrong forwarded htlc amount.
IncomingHTLCAmount MilliSatoshi
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailFinalIncorrectHtlcAmount) Error() string {
return fmt.Sprintf("FinalIncorrectHtlcAmount(amt=%v)",
f.IncomingHTLCAmount)
}
// NewFinalIncorrectHtlcAmount creates new instance of the
// FailFinalIncorrectHtlcAmount.
func NewFinalIncorrectHtlcAmount(amount MilliSatoshi) *FailFinalIncorrectHtlcAmount {
return &FailFinalIncorrectHtlcAmount{
IncomingHTLCAmount: amount,
}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailFinalIncorrectHtlcAmount) Code() FailCode {
return CodeFinalIncorrectHtlcAmount
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailFinalIncorrectHtlcAmount) Decode(r io.Reader, pver uint32) error {
return ReadElement(r, &f.IncomingHTLCAmount)
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailFinalIncorrectHtlcAmount) Encode(w *bytes.Buffer,
pver uint32) error {
return WriteMilliSatoshi(w, f.IncomingHTLCAmount)
}
// FailExpiryTooFar is returned if the CLTV expiry in the HTLC is too far in the
// future.
//
// NOTE: May be returned by any node in the payment route.
type FailExpiryTooFar struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailExpiryTooFar) Code() FailCode {
return CodeExpiryTooFar
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailExpiryTooFar) Error() string {
return f.Code().String()
}
// InvalidOnionPayload is returned if the hop could not process the TLV payload
// enclosed in the onion.
type InvalidOnionPayload struct {
// Type is the TLV type that caused the specific failure.
Type uint64
// Offset is the byte offset within the payload where the failure
// occurred.
Offset uint16
}
// NewInvalidOnionPayload initializes a new InvalidOnionPayload failure.
func NewInvalidOnionPayload(typ uint64, offset uint16) *InvalidOnionPayload {
return &InvalidOnionPayload{
Type: typ,
Offset: offset,
}
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *InvalidOnionPayload) Code() FailCode {
return CodeInvalidOnionPayload
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *InvalidOnionPayload) Error() string {
return fmt.Sprintf("%v(type=%v, offset=%d)",
f.Code(), f.Type, f.Offset)
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *InvalidOnionPayload) Decode(r io.Reader, pver uint32) error {
var buf [8]byte
typ, err := tlv.ReadVarInt(r, &buf)
if err != nil {
return err
}
f.Type = typ
return ReadElements(r, &f.Offset)
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *InvalidOnionPayload) Encode(w *bytes.Buffer, pver uint32) error {
var buf [8]byte
if err := tlv.WriteVarInt(w, f.Type, &buf); err != nil {
return err
}
return WriteUint16(w, f.Offset)
}
// FailMPPTimeout is returned if the complete amount for a multi part payment
// was not received within a reasonable time.
//
// NOTE: May only be returned by the final node in the path.
type FailMPPTimeout struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailMPPTimeout) Code() FailCode {
return CodeMPPTimeout
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailMPPTimeout) Error() string {
return f.Code().String()
}
// FailInvalidBlinding is returned if there has been a route blinding related
// error.
type FailInvalidBlinding struct {
OnionSHA256 [sha256.Size]byte
}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailInvalidBlinding) Code() FailCode {
return CodeInvalidBlinding
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailInvalidBlinding) Error() string {
return f.Code().String()
}
// Decode decodes the failure from bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailInvalidBlinding) Decode(r io.Reader, _ uint32) error {
return ReadElement(r, f.OnionSHA256[:])
}
// Encode writes the failure in bytes stream.
//
// NOTE: Part of the Serializable interface.
func (f *FailInvalidBlinding) Encode(w *bytes.Buffer, _ uint32) error {
return WriteBytes(w, f.OnionSHA256[:])
}
// NewInvalidBlinding creates new instance of FailInvalidBlinding.
func NewInvalidBlinding(
onion fn.Option[[OnionPacketSize]byte]) *FailInvalidBlinding {
// The spec allows empty onion hashes for invalid blinding, so we only
// include our onion hash if it's provided.
if onion.IsNone() {
return &FailInvalidBlinding{}
}
shaSum := fn.MapOptionZ(onion, func(o [OnionPacketSize]byte) [32]byte {
return sha256.Sum256(o[:])
})
return &FailInvalidBlinding{OnionSHA256: shaSum}
}
// DecodeFailure decodes, validates, and parses the lnwire onion failure, for
// the provided protocol version.
func DecodeFailure(r io.Reader, pver uint32) (FailureMessage, error) {
// First, we'll parse out the encapsulated failure message itself. This
// is a 2 byte length followed by the payload itself.
var failureLength uint16
if err := ReadElement(r, &failureLength); err != nil {
return nil, fmt.Errorf("unable to read failure len: %w", err)
}
failureData := make([]byte, failureLength)
if _, err := io.ReadFull(r, failureData); err != nil {
return nil, fmt.Errorf("unable to full read payload of "+
"%v: %w", failureLength, err)
}
// Read the padding.
var padLength uint16
if err := ReadElement(r, &padLength); err != nil {
return nil, fmt.Errorf("unable to read pad len: %w", err)
}
if _, err := io.CopyN(io.Discard, r, int64(padLength)); err != nil {
return nil, fmt.Errorf("unable to read padding %w", err)
}
// Verify that we are at the end of the stream now.
scratch := make([]byte, 1)
_, err := r.Read(scratch)
if err != io.EOF {
return nil, fmt.Errorf("unexpected failure bytes")
}
// Check the total length. Convert to 32 bits to prevent overflow.
totalLength := uint32(padLength) + uint32(failureLength)
if totalLength < FailureMessageLength {
return nil, fmt.Errorf("failure message too short: "+
"msg=%v, pad=%v, total=%v",
failureLength, padLength, totalLength)
}
// Decode the failure message.
dataReader := bytes.NewReader(failureData)
return DecodeFailureMessage(dataReader, pver)
}
// DecodeFailureMessage decodes just the failure message, ignoring any padding
// that may be present at the end.
func DecodeFailureMessage(r io.Reader, pver uint32) (FailureMessage, error) {
// Once we have the failure data, we can obtain the failure code from
// the first two bytes of the buffer.
var codeBytes [2]byte
if _, err := io.ReadFull(r, codeBytes[:]); err != nil {
return nil, fmt.Errorf("unable to read failure code: %w", err)
}
failCode := FailCode(binary.BigEndian.Uint16(codeBytes[:]))
// Create the empty failure by given code and populate the failure with
// additional data if needed.
failure, err := makeEmptyOnionError(failCode)
if err != nil {
return nil, fmt.Errorf("unable to make empty error: %w", err)
}
// Finally, if this failure has a payload, then we'll read that now as
// well.
switch f := failure.(type) {
case Serializable:
if err := f.Decode(r, pver); err != nil {
return nil, fmt.Errorf("unable to decode error "+
"update (type=%T): %v", failure, err)
}
}
return failure, nil
}
// EncodeFailure encodes, including the necessary onion failure header
// information.
func EncodeFailure(w *bytes.Buffer, failure FailureMessage, pver uint32) error {
var failureMessageBuffer bytes.Buffer
err := EncodeFailureMessage(&failureMessageBuffer, failure, pver)
if err != nil {
return err
}
// The combined size of this message must be below the max allowed
// failure message length.
failureMessage := failureMessageBuffer.Bytes()
if len(failureMessage) > FailureMessageLength {
return fmt.Errorf("failure message exceed max "+
"available size: %v", len(failureMessage))
}
// Finally, we'll add some padding in order to ensure that all failure
// messages are fixed size.
pad := make([]byte, FailureMessageLength-len(failureMessage))
if err := WriteUint16(w, uint16(len(failureMessage))); err != nil {
return err
}
if err := WriteBytes(w, failureMessage); err != nil {
return err
}
if err := WriteUint16(w, uint16(len(pad))); err != nil {
return err
}
return WriteBytes(w, pad)
}
// EncodeFailureMessage encodes just the failure message without adding a length
// and padding the message for the onion protocol.
func EncodeFailureMessage(w *bytes.Buffer,
failure FailureMessage, pver uint32) error {
// First, we'll write out the error code itself into the failure
// buffer.
var codeBytes [2]byte
code := uint16(failure.Code())
binary.BigEndian.PutUint16(codeBytes[:], code)
_, err := w.Write(codeBytes[:])
if err != nil {
return err
}
// Next, some message have an additional message payload, if this is
// one of those types, then we'll also encode the error payload as
// well.
switch failure := failure.(type) {
case Serializable:
if err := failure.Encode(w, pver); err != nil {
return err
}
}
return nil
}
// makeEmptyOnionError creates a new empty onion error of the proper concrete
// type based on the passed failure code.
func makeEmptyOnionError(code FailCode) (FailureMessage, error) {
switch code {
case CodeInvalidRealm:
return &FailInvalidRealm{}, nil
case CodeTemporaryNodeFailure:
return &FailTemporaryNodeFailure{}, nil
case CodePermanentNodeFailure:
return &FailPermanentNodeFailure{}, nil
case CodeRequiredNodeFeatureMissing:
return &FailRequiredNodeFeatureMissing{}, nil
case CodePermanentChannelFailure:
return &FailPermanentChannelFailure{}, nil
case CodeRequiredChannelFeatureMissing:
return &FailRequiredChannelFeatureMissing{}, nil
case CodeUnknownNextPeer:
return &FailUnknownNextPeer{}, nil
case CodeIncorrectOrUnknownPaymentDetails:
return &FailIncorrectDetails{}, nil
case CodeIncorrectPaymentAmount:
return &FailIncorrectPaymentAmount{}, nil
case CodeFinalExpiryTooSoon:
return &FailFinalExpiryTooSoon{}, nil
case CodeInvalidOnionVersion:
return &FailInvalidOnionVersion{}, nil
case CodeInvalidOnionHmac:
return &FailInvalidOnionHmac{}, nil
case CodeInvalidOnionKey:
return &FailInvalidOnionKey{}, nil
case CodeTemporaryChannelFailure:
return &FailTemporaryChannelFailure{}, nil
case CodeAmountBelowMinimum:
return &FailAmountBelowMinimum{}, nil
case CodeFeeInsufficient:
return &FailFeeInsufficient{}, nil
case CodeIncorrectCltvExpiry:
return &FailIncorrectCltvExpiry{}, nil
case CodeExpiryTooSoon:
return &FailExpiryTooSoon{}, nil
case CodeChannelDisabled:
return &FailChannelDisabled{}, nil
case CodeFinalIncorrectCltvExpiry:
return &FailFinalIncorrectCltvExpiry{}, nil
case CodeFinalIncorrectHtlcAmount:
return &FailFinalIncorrectHtlcAmount{}, nil
case CodeExpiryTooFar:
return &FailExpiryTooFar{}, nil
case CodeInvalidOnionPayload:
return &InvalidOnionPayload{}, nil
case CodeMPPTimeout:
return &FailMPPTimeout{}, nil
case CodeInvalidBlinding:
return &FailInvalidBlinding{}, nil
default:
return nil, errors.Errorf("unknown error code: %v", code)
}
}
// writeOnionErrorChanUpdate writes out a ChannelUpdate using the onion error
// format. The format is that we first write out the true serialized length of
// the channel update, followed by the serialized channel update itself.
func writeOnionErrorChanUpdate(w *bytes.Buffer, chanUpdate *ChannelUpdate1,
pver uint32) error {
// First, we encode the channel update in a temporary buffer in order
// to get the exact serialized size.
var b bytes.Buffer
updateLen, err := WriteMessage(&b, chanUpdate, pver)
if err != nil {
return err
}
// Now that we know the size, we can write the length out in the main
// writer.
if err := WriteUint16(w, uint16(updateLen)); err != nil {
return err
}
// With the length written, we'll then write out the serialized channel
// update.
if _, err := w.Write(b.Bytes()); err != nil {
return err
}
return nil
}
package lnwire
import (
"encoding/hex"
"net"
)
// OpaqueAddrs is used to store the address bytes for address types that are
// unknown to us.
type OpaqueAddrs struct {
Payload []byte
}
// A compile-time assertion to ensure that OpaqueAddrs meets the net.Addr
// interface.
var _ net.Addr = (*OpaqueAddrs)(nil)
// String returns a human-readable string describing the target OpaqueAddrs.
// Since this is an unknown address (and could even be multiple addresses), we
// just return the hex string of the payload.
//
// This part of the net.Addr interface.
func (o *OpaqueAddrs) String() string {
return hex.EncodeToString(o.Payload)
}
// Network returns the name of the network this address is bound to. Since this
// is an unknown address, we don't know the network and so just return a string
// indicating this.
//
// This part of the net.Addr interface.
func (o *OpaqueAddrs) Network() string {
return "unknown network for unrecognized address type"
}
package lnwire
import (
"bytes"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/tlv"
)
// FundingFlag represents the possible bit mask values for the ChannelFlags
// field within the OpenChannel struct.
type FundingFlag uint8
const (
// FFAnnounceChannel is a FundingFlag that when set, indicates the
// initiator of a funding flow wishes to announce the channel to the
// greater network.
FFAnnounceChannel FundingFlag = 1 << iota
)
// OpenChannel is the message Alice sends to Bob if we should like to create a
// channel with Bob where she's the sole provider of funds to the channel.
// Single funder channels simplify the initial funding workflow, are supported
// by nodes backed by SPV Bitcoin clients, and have a simpler security models
// than dual funded channels.
type OpenChannel struct {
// ChainHash is the target chain that the initiator wishes to open a
// channel within.
ChainHash chainhash.Hash
// PendingChannelID serves to uniquely identify the future channel
// created by the initiated single funder workflow.
PendingChannelID [32]byte
// FundingAmount is the amount of satoshis that the initiator of the
// channel wishes to use as the total capacity of the channel. The
// initial balance of the funding will be this value minus the push
// amount (if set).
FundingAmount btcutil.Amount
// PushAmount is the value that the initiating party wishes to "push"
// to the responding as part of the first commitment state. If the
// responder accepts, then this will be their initial balance.
PushAmount MilliSatoshi
// DustLimit is the specific dust limit the sender of this message
// would like enforced on their version of the commitment transaction.
// Any output below this value will be "trimmed" from the commitment
// transaction, with the amount of the HTLC going to dust.
DustLimit btcutil.Amount
// MaxValueInFlight represents the maximum amount of coins that can be
// pending within the channel at any given time. If the amount of funds
// in limbo exceeds this amount, then the channel will be failed.
MaxValueInFlight MilliSatoshi
// ChannelReserve is the amount of BTC that the receiving party MUST
// maintain a balance above at all times. This is a safety mechanism to
// ensure that both sides always have skin in the game during the
// channel's lifetime.
ChannelReserve btcutil.Amount
// HtlcMinimum is the smallest HTLC that the sender of this message
// will accept.
HtlcMinimum MilliSatoshi
// FeePerKiloWeight is the initial fee rate that the initiator suggests
// for both commitment transaction. This value is expressed in sat per
// kilo-weight.
//
// TODO(halseth): make SatPerKWeight when fee estimation is in own
// package. Currently this will cause an import cycle.
FeePerKiloWeight uint32
// CsvDelay is the number of blocks to use for the relative time lock
// in the pay-to-self output of both commitment transactions.
CsvDelay uint16
// MaxAcceptedHTLCs is the total number of incoming HTLC's that the
// sender of this channel will accept.
MaxAcceptedHTLCs uint16
// FundingKey is the key that should be used on behalf of the sender
// within the 2-of-2 multi-sig output that it contained within the
// funding transaction.
FundingKey *btcec.PublicKey
// RevocationPoint is the base revocation point for the sending party.
// Any commitment transaction belonging to the receiver of this message
// should use this key and their per-commitment point to derive the
// revocation key for the commitment transaction.
RevocationPoint *btcec.PublicKey
// PaymentPoint is the base payment point for the sending party. This
// key should be combined with the per commitment point for a
// particular commitment state in order to create the key that should
// be used in any output that pays directly to the sending party, and
// also within the HTLC covenant transactions.
PaymentPoint *btcec.PublicKey
// DelayedPaymentPoint is the delay point for the sending party. This
// key should be combined with the per commitment point to derive the
// keys that are used in outputs of the sender's commitment transaction
// where they claim funds.
DelayedPaymentPoint *btcec.PublicKey
// HtlcPoint is the base point used to derive the set of keys for this
// party that will be used within the HTLC public key scripts. This
// value is combined with the receiver's revocation base point in order
// to derive the keys that are used within HTLC scripts.
HtlcPoint *btcec.PublicKey
// FirstCommitmentPoint is the first commitment point for the sending
// party. This value should be combined with the receiver's revocation
// base point in order to derive the revocation keys that are placed
// within the commitment transaction of the sender.
FirstCommitmentPoint *btcec.PublicKey
// ChannelFlags is a bit-field which allows the initiator of the
// channel to specify further behavior surrounding the channel.
// Currently, the least significant bit of this bit field indicates the
// initiator of the channel wishes to advertise this channel publicly.
ChannelFlags FundingFlag
// UpfrontShutdownScript is the script to which the channel funds should
// be paid when mutually closing the channel. This field is optional, and
// and has a length prefix, so a zero will be written if it is not set
// and its length followed by the script will be written if it is set.
UpfrontShutdownScript DeliveryAddress
// ChannelType is the explicit channel type the initiator wishes to
// open.
ChannelType *ChannelType
// LeaseExpiry represents the absolute expiration height of a channel
// lease. This is a custom TLV record that will only apply when a leased
// channel is being opened using the script enforced lease commitment
// type.
LeaseExpiry *LeaseExpiry
// LocalNonce is an optional field that transmits the
// local/verification nonce for a party. This nonce will be used to
// verify the very first commitment transaction signature. This will
// only be populated if the simple taproot channels type was
// negotiated.
LocalNonce OptMusig2NonceTLV
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
//
// NOTE: Since the upfront shutdown script MUST be present (though can
// be zero-length) if any TLV data is available, the script will be
// extracted and removed from this blob when decoding. ExtraData will
// contain all TLV records _except_ the DeliveryAddress record in that
// case.
ExtraData ExtraOpaqueData
}
// A compile time check to ensure OpenChannel implements the lnwire.Message
// interface.
var _ Message = (*OpenChannel)(nil)
// A compile time check to ensure OpenChannel implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*OpenChannel)(nil)
// Encode serializes the target OpenChannel into the passed io.Writer
// implementation. Serialization will observe the rules defined by the passed
// protocol version.
func (o *OpenChannel) Encode(w *bytes.Buffer, pver uint32) error {
recordProducers := []tlv.RecordProducer{&o.UpfrontShutdownScript}
if o.ChannelType != nil {
recordProducers = append(recordProducers, o.ChannelType)
}
if o.LeaseExpiry != nil {
recordProducers = append(recordProducers, o.LeaseExpiry)
}
o.LocalNonce.WhenSome(func(localNonce Musig2NonceTLV) {
recordProducers = append(recordProducers, &localNonce)
})
err := EncodeMessageExtraData(&o.ExtraData, recordProducers...)
if err != nil {
return err
}
if err := WriteBytes(w, o.ChainHash[:]); err != nil {
return err
}
if err := WriteBytes(w, o.PendingChannelID[:]); err != nil {
return err
}
if err := WriteSatoshi(w, o.FundingAmount); err != nil {
return err
}
if err := WriteMilliSatoshi(w, o.PushAmount); err != nil {
return err
}
if err := WriteSatoshi(w, o.DustLimit); err != nil {
return err
}
if err := WriteMilliSatoshi(w, o.MaxValueInFlight); err != nil {
return err
}
if err := WriteSatoshi(w, o.ChannelReserve); err != nil {
return err
}
if err := WriteMilliSatoshi(w, o.HtlcMinimum); err != nil {
return err
}
if err := WriteUint32(w, o.FeePerKiloWeight); err != nil {
return err
}
if err := WriteUint16(w, o.CsvDelay); err != nil {
return err
}
if err := WriteUint16(w, o.MaxAcceptedHTLCs); err != nil {
return err
}
if err := WritePublicKey(w, o.FundingKey); err != nil {
return err
}
if err := WritePublicKey(w, o.RevocationPoint); err != nil {
return err
}
if err := WritePublicKey(w, o.PaymentPoint); err != nil {
return err
}
if err := WritePublicKey(w, o.DelayedPaymentPoint); err != nil {
return err
}
if err := WritePublicKey(w, o.HtlcPoint); err != nil {
return err
}
if err := WritePublicKey(w, o.FirstCommitmentPoint); err != nil {
return err
}
if err := WriteFundingFlag(w, o.ChannelFlags); err != nil {
return err
}
return WriteBytes(w, o.ExtraData)
}
// Decode deserializes the serialized OpenChannel stored in the passed
// io.Reader into the target OpenChannel using the deserialization rules
// defined by the passed protocol version.
//
// This is part of the lnwire.Message interface.
func (o *OpenChannel) Decode(r io.Reader, pver uint32) error {
// Read all the mandatory fields in the open message.
err := ReadElements(r,
o.ChainHash[:],
o.PendingChannelID[:],
&o.FundingAmount,
&o.PushAmount,
&o.DustLimit,
&o.MaxValueInFlight,
&o.ChannelReserve,
&o.HtlcMinimum,
&o.FeePerKiloWeight,
&o.CsvDelay,
&o.MaxAcceptedHTLCs,
&o.FundingKey,
&o.RevocationPoint,
&o.PaymentPoint,
&o.DelayedPaymentPoint,
&o.HtlcPoint,
&o.FirstCommitmentPoint,
&o.ChannelFlags,
)
if err != nil {
return err
}
// For backwards compatibility, the optional extra data blob for
// OpenChannel must contain an entry for the upfront shutdown script.
// We'll read it out and attempt to parse it.
var tlvRecords ExtraOpaqueData
if err := ReadElements(r, &tlvRecords); err != nil {
return err
}
// Next we'll parse out the set of known records, keeping the raw tlv
// bytes untouched to ensure we don't drop any bytes erroneously.
var (
chanType ChannelType
leaseExpiry LeaseExpiry
localNonce = o.LocalNonce.Zero()
)
typeMap, err := tlvRecords.ExtractRecords(
&o.UpfrontShutdownScript, &chanType, &leaseExpiry,
&localNonce,
)
if err != nil {
return err
}
// Set the corresponding TLV types if they were included in the stream.
if val, ok := typeMap[ChannelTypeRecordType]; ok && val == nil {
o.ChannelType = &chanType
}
if val, ok := typeMap[LeaseExpiryRecordType]; ok && val == nil {
o.LeaseExpiry = &leaseExpiry
}
if val, ok := typeMap[o.LocalNonce.TlvType()]; ok && val == nil {
o.LocalNonce = tlv.SomeRecordT(localNonce)
}
o.ExtraData = tlvRecords
return nil
}
// MsgType returns the MessageType code which uniquely identifies this message
// as an OpenChannel on the wire.
//
// This is part of the lnwire.Message interface.
func (o *OpenChannel) MsgType() MessageType {
return MsgOpenChannel
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (o *OpenChannel) SerializedSize() (uint32, error) {
return MessageSerializedSize(o)
}
package lnwire
import (
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// PartialSigLen is the length of a musig2 partial signature.
PartialSigLen = 32
)
type (
// PartialSigType is the type of the tlv record for a musig2
// partial signature. This is an _even_ type, which means it's required
// if included.
PartialSigType = tlv.TlvType6
// PartialSigTLV is a tlv record for a musig2 partial signature.
PartialSigTLV = tlv.RecordT[PartialSigType, PartialSig]
// OptPartialSigTLV is a tlv record for a musig2 partial signature.
// This is an optional record type.
OptPartialSigTLV = tlv.OptionalRecordT[PartialSigType, PartialSig]
)
// PartialSig is the base partial sig type. This only encodes the 32-byte
// partial signature. This is used for the co-op close flow, as both sides have
// already exchanged nonces, so they can send just the partial signature.
type PartialSig struct {
// Sig is the 32-byte musig2 partial signature.
Sig btcec.ModNScalar
}
// NewPartialSig creates a new partial sig.
func NewPartialSig(sig btcec.ModNScalar) PartialSig {
return PartialSig{
Sig: sig,
}
}
// Record returns the tlv record for the partial sig.
func (p *PartialSig) Record() tlv.Record {
return tlv.MakeStaticRecord(
(PartialSigType)(nil).TypeVal(), p, PartialSigLen,
partialSigTypeEncoder, partialSigTypeDecoder,
)
}
// partialSigTypeEncoder encodes a 32-byte musig2 partial signature as a TLV
// value.
func partialSigTypeEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
if v, ok := val.(*PartialSig); ok {
sigBytes := v.Sig.Bytes()
return tlv.EBytes32(w, &sigBytes, buf)
}
return tlv.NewTypeForEncodingErr(val, "lnwire.PartialSig")
}
// Encode writes the encoded version of this message to the passed io.Writer.
func (p *PartialSig) Encode(w io.Writer) error {
return partialSigTypeEncoder(w, p, nil)
}
// partialSigTypeDecoder decodes a 32-byte musig2 extended partial signature.
func partialSigTypeDecoder(r io.Reader, val interface{}, buf *[8]byte,
l uint64) error {
if v, ok := val.(*PartialSig); ok && l == PartialSigLen {
var sBytes [32]byte
err := tlv.DBytes32(r, &sBytes, buf, PartialSigLen)
if err != nil {
return err
}
var s btcec.ModNScalar
s.SetBytes(&sBytes)
*v = PartialSig{
Sig: s,
}
return nil
}
return tlv.NewTypeForDecodingErr(val, "lnwire.PartialSig", l,
PartialSigLen)
}
// Decode reads the encoded version of this message from the passed io.Reader.
func (p *PartialSig) Decode(r io.Reader) error {
return partialSigTypeDecoder(r, p, nil, PartialSigLen)
}
// SomePartialSig is a helper function that returns an otional PartialSig.
func SomePartialSig(sig PartialSig) OptPartialSigTLV {
return tlv.SomeRecordT(tlv.NewRecordT[PartialSigType, PartialSig](sig))
}
const (
// PartialSigWithNonceLen is the length of a serialized
// PartialSigWithNonce. The sig is encoded as the 32 byte S value
// followed by the 66 nonce value.
PartialSigWithNonceLen = 98
)
type (
// PartialSigWithNonceType is the type of the tlv record for a musig2
// partial signature with nonce. This is an _even_ type, which means
// it's required if included.
PartialSigWithNonceType = tlv.TlvType2
// PartialSigWithNonceTLV is a tlv record for a musig2 partial
// signature.
PartialSigWithNonceTLV = tlv.RecordT[
PartialSigWithNonceType, PartialSigWithNonce,
]
// OptPartialSigWithNonceTLV is a tlv record for a musig2 partial
// signature. This is an optional record type.
OptPartialSigWithNonceTLV = tlv.OptionalRecordT[
PartialSigWithNonceType, PartialSigWithNonce,
]
)
// PartialSigWithNonce is a partial signature with the nonce that was used to
// generate the signature. This is used for funding as well as the commitment
// transaction update dance. By sending the nonce only with the signature, we
// enable the sender to generate their nonce just before they create their
// signature. Signers can use this trait to mix in additional contextual data
// such as the commitment txn itself into their nonce generation function.
//
// The final signature is 98 bytes: 32 bytes for the S value, and 66 bytes for
// the public nonce (two compressed points).
type PartialSigWithNonce struct {
PartialSig
// Nonce is the 66-byte musig2 nonce.
Nonce Musig2Nonce
}
// NewPartialSigWithNonce creates a new partial sig with nonce.
func NewPartialSigWithNonce(nonce [musig2.PubNonceSize]byte,
sig btcec.ModNScalar) *PartialSigWithNonce {
return &PartialSigWithNonce{
Nonce: nonce,
PartialSig: NewPartialSig(sig),
}
}
// Record returns the tlv record for the partial sig with nonce.
func (p *PartialSigWithNonce) Record() tlv.Record {
return tlv.MakeStaticRecord(
(PartialSigWithNonceType)(nil).TypeVal(), p,
PartialSigWithNonceLen, partialSigWithNonceTypeEncoder,
partialSigWithNonceTypeDecoder,
)
}
// partialSigWithNonceTypeEncoder encodes 98-byte musig2 extended partial
// signature as: s {32} || nonce {66}.
func partialSigWithNonceTypeEncoder(w io.Writer, val interface{},
_ *[8]byte) error {
if v, ok := val.(*PartialSigWithNonce); ok {
sigBytes := v.Sig.Bytes()
if _, err := w.Write(sigBytes[:]); err != nil {
return err
}
if _, err := w.Write(v.Nonce[:]); err != nil {
return err
}
return nil
}
return tlv.NewTypeForEncodingErr(val, "lnwire.PartialSigWithNonce")
}
// Encode writes the encoded version of this message to the passed io.Writer.
func (p *PartialSigWithNonce) Encode(w io.Writer) error {
return partialSigWithNonceTypeEncoder(w, p, nil)
}
// partialSigWithNonceTypeDecoder decodes a 98-byte musig2 extended partial
// signature.
func partialSigWithNonceTypeDecoder(r io.Reader, val interface{}, buf *[8]byte,
l uint64) error {
if v, ok := val.(*PartialSigWithNonce); ok &&
l == PartialSigWithNonceLen {
var sBytes [32]byte
err := tlv.DBytes32(r, &sBytes, buf, PartialSigLen)
if err != nil {
return err
}
var s btcec.ModNScalar
s.SetBytes(&sBytes)
var nonce [66]byte
if _, err := io.ReadFull(r, nonce[:]); err != nil {
return err
}
*v = PartialSigWithNonce{
PartialSig: NewPartialSig(s),
Nonce: nonce,
}
return nil
}
return tlv.NewTypeForDecodingErr(val, "lnwire.PartialSigWithNonce", l,
PartialSigWithNonceLen)
}
// Decode reads the encoded version of this message from the passed io.Reader.
func (p *PartialSigWithNonce) Decode(r io.Reader) error {
return partialSigWithNonceTypeDecoder(
r, p, nil, PartialSigWithNonceLen,
)
}
// MaybePartialSigWithNonce is a helper function that returns an optional
// PartialSigWithNonceTLV.
func MaybePartialSigWithNonce(sig *PartialSigWithNonce,
) OptPartialSigWithNonceTLV {
if sig == nil {
var none OptPartialSigWithNonceTLV
return none
}
return tlv.SomeRecordT(
tlv.NewRecordT[PartialSigWithNonceType, PartialSigWithNonce](
*sig,
),
)
}
package lnwire
import (
"bytes"
"io"
)
// PingPayload is a set of opaque bytes used to pad out a ping message.
type PingPayload []byte
// Ping defines a message which is sent by peers periodically to determine if
// the connection is still valid. Each ping message carries the number of bytes
// to pad the pong response with, and also a number of bytes to be ignored at
// the end of the ping message (which is padding).
type Ping struct {
// NumPongBytes is the number of bytes the pong response to this
// message should carry.
NumPongBytes uint16
// PaddingBytes is a set of opaque bytes used to pad out this ping
// message. Using this field in conjunction to the one above, it's
// possible for node to generate fake cover traffic.
PaddingBytes PingPayload
}
// NewPing returns a new Ping message.
func NewPing(numBytes uint16) *Ping {
return &Ping{
NumPongBytes: numBytes,
}
}
// A compile time check to ensure Ping implements the lnwire.Message interface.
var _ Message = (*Ping)(nil)
// A compile time check to ensure Ping implements the lnwire.SizeableMessage
// interface.
var _ SizeableMessage = (*Ping)(nil)
// Decode deserializes a serialized Ping message stored in the passed io.Reader
// observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (p *Ping) Decode(r io.Reader, pver uint32) error {
err := ReadElements(r, &p.NumPongBytes, &p.PaddingBytes)
if err != nil {
return err
}
if p.NumPongBytes > MaxPongBytes {
return ErrMaxPongBytesExceeded
}
return nil
}
// Encode serializes the target Ping into the passed io.Writer observing the
// protocol version specified.
//
// This is part of the lnwire.Message interface.
func (p *Ping) Encode(w *bytes.Buffer, pver uint32) error {
if err := WriteUint16(w, p.NumPongBytes); err != nil {
return err
}
return WritePingPayload(w, p.PaddingBytes)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (p *Ping) MsgType() MessageType {
return MsgPing
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (p *Ping) SerializedSize() (uint32, error) {
return MessageSerializedSize(p)
}
package lnwire
import (
"bytes"
"fmt"
"io"
)
// MaxPongBytes is the maximum number of extra bytes a pong can be requested to
// send. The type of the message (19) takes 2 bytes, the length field takes up
// 2 bytes, leaving 65531 bytes.
const MaxPongBytes = 65531
// ErrMaxPongBytesExceeded indicates that the NumPongBytes field from the ping
// message has exceeded MaxPongBytes.
var ErrMaxPongBytesExceeded = fmt.Errorf("pong bytes exceeded")
// PongPayload is a set of opaque bytes sent in response to a ping message.
type PongPayload []byte
// Pong defines a message which is the direct response to a received Ping
// message. A Pong reply indicates that a connection is still active. The Pong
// reply to a Ping message should contain the nonce carried in the original
// Pong message.
type Pong struct {
// PongBytes is a set of opaque bytes that corresponds to the
// NumPongBytes defined in the ping message that this pong is
// replying to.
PongBytes PongPayload
}
// NewPong returns a new Pong message.
func NewPong(pongBytes []byte) *Pong {
return &Pong{
PongBytes: pongBytes,
}
}
// A compile time check to ensure Pong implements the lnwire.Message interface.
var _ Message = (*Pong)(nil)
// A compile time check to ensure Pong implements the lnwire.SizeableMessage
// interface.
var _ SizeableMessage = (*Pong)(nil)
// Decode deserializes a serialized Pong message stored in the passed io.Reader
// observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (p *Pong) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&p.PongBytes,
)
}
// Encode serializes the target Pong into the passed io.Writer observing the
// protocol version specified.
//
// This is part of the lnwire.Message interface.
func (p *Pong) Encode(w *bytes.Buffer, pver uint32) error {
return WritePongPayload(w, p.PongBytes)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (p *Pong) MsgType() MessageType {
return MsgPong
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (p *Pong) SerializedSize() (uint32, error) {
return MessageSerializedSize(p)
}
package lnwire
import (
"bytes"
"io"
"math"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/tlv"
)
// QueryChannelRange is a message sent by a node in order to query the
// receiving node of the set of open channel they know of with short channel
// ID's after the specified block height, capped at the number of blocks beyond
// that block height. This will be used by nodes upon initial connect to
// synchronize their views of the network.
type QueryChannelRange struct {
// ChainHash denotes the target chain that we're trying to synchronize
// channel graph state for.
ChainHash chainhash.Hash
// FirstBlockHeight is the first block in the query range. The
// responder should send all new short channel IDs from this block
// until this block plus the specified number of blocks.
FirstBlockHeight uint32
// NumBlocks is the number of blocks beyond the first block that short
// channel ID's should be sent for.
NumBlocks uint32
// QueryOptions is an optional feature bit vector that can be used to
// specify additional query options.
QueryOptions *QueryOptions
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// NewQueryChannelRange creates a new empty QueryChannelRange message.
func NewQueryChannelRange() *QueryChannelRange {
return &QueryChannelRange{
ExtraData: make([]byte, 0),
}
}
// A compile time check to ensure QueryChannelRange implements the
// lnwire.Message interface.
var _ Message = (*QueryChannelRange)(nil)
// A compile time check to ensure QueryChannelRange implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*QueryChannelRange)(nil)
// Decode deserializes a serialized QueryChannelRange message stored in the
// passed io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (q *QueryChannelRange) Decode(r io.Reader, _ uint32) error {
err := ReadElements(
r, q.ChainHash[:], &q.FirstBlockHeight, &q.NumBlocks,
)
if err != nil {
return err
}
var tlvRecords ExtraOpaqueData
if err := ReadElements(r, &tlvRecords); err != nil {
return err
}
var queryOptions QueryOptions
typeMap, err := tlvRecords.ExtractRecords(&queryOptions)
if err != nil {
return err
}
// Set the corresponding TLV types if they were included in the stream.
if val, ok := typeMap[QueryOptionsRecordType]; ok && val == nil {
q.QueryOptions = &queryOptions
}
if len(tlvRecords) != 0 {
q.ExtraData = tlvRecords
}
return nil
}
// Encode serializes the target QueryChannelRange into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (q *QueryChannelRange) Encode(w *bytes.Buffer, _ uint32) error {
if err := WriteBytes(w, q.ChainHash[:]); err != nil {
return err
}
if err := WriteUint32(w, q.FirstBlockHeight); err != nil {
return err
}
if err := WriteUint32(w, q.NumBlocks); err != nil {
return err
}
recordProducers := make([]tlv.RecordProducer, 0, 1)
if q.QueryOptions != nil {
recordProducers = append(recordProducers, q.QueryOptions)
}
err := EncodeMessageExtraData(&q.ExtraData, recordProducers...)
if err != nil {
return err
}
return WriteBytes(w, q.ExtraData)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (q *QueryChannelRange) MsgType() MessageType {
return MsgQueryChannelRange
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (q *QueryChannelRange) SerializedSize() (uint32, error) {
msgCpy := *q
return MessageSerializedSize(&msgCpy)
}
// LastBlockHeight returns the last block height covered by the range of a
// QueryChannelRange message.
func (q *QueryChannelRange) LastBlockHeight() uint32 {
// Handle overflows by casting to uint64.
lastBlockHeight := uint64(q.FirstBlockHeight) + uint64(q.NumBlocks) - 1
if lastBlockHeight > math.MaxUint32 {
return math.MaxUint32
}
return uint32(lastBlockHeight)
}
// WithTimestamps returns true if the query has asked for timestamps too.
func (q *QueryChannelRange) WithTimestamps() bool {
if q.QueryOptions == nil {
return false
}
queryOpts := RawFeatureVector(*q.QueryOptions)
return queryOpts.IsSet(QueryOptionTimestampBit)
}
package lnwire
import (
"io"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// QueryOptionsRecordType is the TLV number of the query_options TLV
// record in the query_channel_range message.
QueryOptionsRecordType tlv.Type = 1
// QueryOptionTimestampBit is the bit position in the query_option
// feature bit vector which is used to indicate that timestamps are
// desired in the reply_channel_range response.
QueryOptionTimestampBit = 0
)
// QueryOptions is the type used to represent the query_options feature bit
// vector in the query_channel_range message.
type QueryOptions RawFeatureVector
// NewTimestampQueryOption is a helper constructor used to construct a
// QueryOption with the timestamp bit set.
func NewTimestampQueryOption() *QueryOptions {
opt := QueryOptions(*NewRawFeatureVector(
QueryOptionTimestampBit,
))
return &opt
}
// featureBitLen calculates and returns the size of the resulting feature bit
// vector.
func (c *QueryOptions) featureBitLen() uint64 {
fv := RawFeatureVector(*c)
return uint64(fv.SerializeSize())
}
// Record constructs a tlv.Record from the QueryOptions to be used in the
// query_channel_range message.
func (c *QueryOptions) Record() tlv.Record {
return tlv.MakeDynamicRecord(
QueryOptionsRecordType, c, c.featureBitLen, queryOptionsEncoder,
queryOptionsDecoder,
)
}
// queryOptionsEncoder encodes the QueryOptions and writes it to the provided
// writer.
func queryOptionsEncoder(w io.Writer, val interface{}, _ *[8]byte) error {
if v, ok := val.(*QueryOptions); ok {
// Encode the feature bits as a byte slice without its length
// prepended, as that's already taken care of by the TLV record.
fv := RawFeatureVector(*v)
return fv.encode(w, fv.SerializeSize(), 8)
}
return tlv.NewTypeForEncodingErr(val, "lnwire.QueryOptions")
}
// queryOptionsDecoder attempts to read a QueryOptions from the given reader.
func queryOptionsDecoder(r io.Reader, val interface{}, _ *[8]byte,
l uint64) error {
if v, ok := val.(*QueryOptions); ok {
fv := NewRawFeatureVector()
if err := fv.decode(r, int(l), 8); err != nil {
return err
}
*v = QueryOptions(*fv)
return nil
}
return tlv.NewTypeForEncodingErr(val, "lnwire.QueryOptions")
}
package lnwire
import (
"bytes"
"compress/zlib"
"fmt"
"io"
"sort"
"sync"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
const (
// maxZlibBufSize is the max number of bytes that we'll accept from a
// zlib decoding instance. We do this in order to limit the total
// amount of memory allocated during a decoding instance.
maxZlibBufSize = 67413630
)
// ErrUnsortedSIDs is returned when decoding a QueryShortChannelID request whose
// items were not sorted.
type ErrUnsortedSIDs struct {
prevSID ShortChannelID
curSID ShortChannelID
}
// Error returns a human-readable description of the error.
func (e ErrUnsortedSIDs) Error() string {
return fmt.Sprintf("current sid: %v isn't greater than last sid: %v",
e.curSID, e.prevSID)
}
// zlibDecodeMtx is a package level mutex that we'll use in order to ensure
// that we'll only attempt a single zlib decoding instance at a time. This
// allows us to also further bound our memory usage.
var zlibDecodeMtx sync.Mutex
// ErrUnknownShortChanIDEncoding is a parametrized error that indicates that we
// came across an unknown short channel ID encoding, and therefore were unable
// to continue parsing.
func ErrUnknownShortChanIDEncoding(encoding QueryEncoding) error {
return fmt.Errorf("unknown short chan id encoding: %v", encoding)
}
// QueryShortChanIDs is a message that allows the sender to query a set of
// channel announcement and channel update messages that correspond to the set
// of encoded short channel ID's. The encoding of the short channel ID's is
// detailed in the query message ensuring that the receiver knows how to
// properly decode each encode short channel ID which may be encoded using a
// compression format. The receiver should respond with a series of channel
// announcement and channel updates, finally sending a ReplyShortChanIDsEnd
// message.
type QueryShortChanIDs struct {
// ChainHash denotes the target chain that we're querying for the
// channel ID's of.
ChainHash chainhash.Hash
// EncodingType is a signal to the receiver of the message that
// indicates exactly how the set of short channel ID's that follow have
// been encoded.
EncodingType QueryEncoding
// ShortChanIDs is a slice of decoded short channel ID's.
ShortChanIDs []ShortChannelID
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
// noSort indicates whether or not to sort the short channel ids before
// writing them out.
//
// NOTE: This should only be used during testing.
noSort bool
}
// NewQueryShortChanIDs creates a new QueryShortChanIDs message.
func NewQueryShortChanIDs(h chainhash.Hash, e QueryEncoding,
s []ShortChannelID) *QueryShortChanIDs {
return &QueryShortChanIDs{
ChainHash: h,
EncodingType: e,
ShortChanIDs: s,
}
}
// A compile time check to ensure QueryShortChanIDs implements the
// lnwire.Message interface.
var _ Message = (*QueryShortChanIDs)(nil)
// A compile time check to ensure QueryShortChanIDs implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*QueryShortChanIDs)(nil)
// Decode deserializes a serialized QueryShortChanIDs message stored in the
// passed io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (q *QueryShortChanIDs) Decode(r io.Reader, pver uint32) error {
err := ReadElements(r, q.ChainHash[:])
if err != nil {
return err
}
q.EncodingType, q.ShortChanIDs, err = decodeShortChanIDs(r)
if err != nil {
return err
}
return q.ExtraData.Decode(r)
}
// decodeShortChanIDs decodes a set of short channel ID's that have been
// encoded. The first byte of the body details how the short chan ID's were
// encoded. We'll use this type to govern exactly how we go about encoding the
// set of short channel ID's.
func decodeShortChanIDs(r io.Reader) (QueryEncoding, []ShortChannelID, error) {
// First, we'll attempt to read the number of bytes in the body of the
// set of encoded short channel ID's.
var numBytesResp uint16
err := ReadElements(r, &numBytesResp)
if err != nil {
return 0, nil, err
}
if numBytesResp == 0 {
return 0, nil, nil
}
queryBody := make([]byte, numBytesResp)
if _, err := io.ReadFull(r, queryBody); err != nil {
return 0, nil, err
}
// The first byte is the encoding type, so we'll extract that so we can
// continue our parsing.
encodingType := QueryEncoding(queryBody[0])
// Before continuing, we'll snip off the first byte of the query body
// as that was just the encoding type.
queryBody = queryBody[1:]
// Otherwise, depending on the encoding type, we'll decode the encode
// short channel ID's in a different manner.
switch encodingType {
// In this encoding, we'll simply read a sort array of encoded short
// channel ID's from the buffer.
case EncodingSortedPlain:
// If after extracting the encoding type, the number of
// remaining bytes is not a whole multiple of the size of an
// encoded short channel ID (8 bytes), then we'll return a
// parsing error.
if len(queryBody)%8 != 0 {
return 0, nil, fmt.Errorf("whole number of short "+
"chan ID's cannot be encoded in len=%v",
len(queryBody))
}
// As each short channel ID is encoded as 8 bytes, we can
// compute the number of bytes encoded based on the size of the
// query body.
numShortChanIDs := len(queryBody) / 8
if numShortChanIDs == 0 {
return encodingType, nil, nil
}
// Finally, we'll read out the exact number of short channel
// ID's to conclude our parsing.
shortChanIDs := make([]ShortChannelID, numShortChanIDs)
bodyReader := bytes.NewReader(queryBody)
var lastChanID ShortChannelID
for i := 0; i < numShortChanIDs; i++ {
if err := ReadElements(bodyReader, &shortChanIDs[i]); err != nil {
return 0, nil, fmt.Errorf("unable to parse "+
"short chan ID: %v", err)
}
// We'll ensure that this short chan ID is greater than
// the last one. This is a requirement within the
// encoding, and if violated can aide us in detecting
// malicious payloads. This can only be true starting
// at the second chanID.
cid := shortChanIDs[i]
if i > 0 && cid.ToUint64() <= lastChanID.ToUint64() {
return 0, nil, ErrUnsortedSIDs{lastChanID, cid}
}
lastChanID = cid
}
return encodingType, shortChanIDs, nil
// In this encoding, we'll use zlib to decode the compressed payload.
// However, we'll pay attention to ensure that we don't open our selves
// up to a memory exhaustion attack.
case EncodingSortedZlib:
// We'll obtain an ultimately release the zlib decode mutex.
// This guards us against allocating too much memory to decode
// each instance from concurrent peers.
zlibDecodeMtx.Lock()
defer zlibDecodeMtx.Unlock()
// At this point, if there's no body remaining, then only the encoding
// type was specified, meaning that there're no further bytes to be
// parsed.
if len(queryBody) == 0 {
return encodingType, nil, nil
}
// Before we start to decode, we'll create a limit reader over
// the current reader. This will ensure that we can control how
// much memory we're allocating during the decoding process.
limitedDecompressor, err := zlib.NewReader(&io.LimitedReader{
R: bytes.NewReader(queryBody),
N: maxZlibBufSize,
})
if err != nil {
return 0, nil, fmt.Errorf("unable to create zlib "+
"reader: %w", err)
}
var (
shortChanIDs []ShortChannelID
lastChanID ShortChannelID
i int
)
for {
// We'll now attempt to read the next short channel ID
// encoded in the payload.
var cid ShortChannelID
err := ReadElements(limitedDecompressor, &cid)
switch {
// If we get an EOF error, then that either means we've
// read all that's contained in the buffer, or have hit
// our limit on the number of bytes we'll read. In
// either case, we'll return what we have so far.
case err == io.ErrUnexpectedEOF || err == io.EOF:
return encodingType, shortChanIDs, nil
// Otherwise, we hit some other sort of error, possibly
// an invalid payload, so we'll exit early with the
// error.
case err != nil:
return 0, nil, fmt.Errorf("unable to "+
"deflate next short chan "+
"ID: %v", err)
}
// We successfully read the next ID, so we'll collect
// that in the set of final ID's to return.
shortChanIDs = append(shortChanIDs, cid)
// Finally, we'll ensure that this short chan ID is
// greater than the last one. This is a requirement
// within the encoding, and if violated can aide us in
// detecting malicious payloads. This can only be true
// starting at the second chanID.
if i > 0 && cid.ToUint64() <= lastChanID.ToUint64() {
return 0, nil, ErrUnsortedSIDs{lastChanID, cid}
}
lastChanID = cid
i++
}
default:
// If we've been sent an encoding type that we don't know of,
// then we'll return a parsing error as we can't continue if
// we're unable to encode them.
return 0, nil, ErrUnknownShortChanIDEncoding(encodingType)
}
}
// Encode serializes the target QueryShortChanIDs into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (q *QueryShortChanIDs) Encode(w *bytes.Buffer, pver uint32) error {
// First, we'll write out the chain hash.
if err := WriteBytes(w, q.ChainHash[:]); err != nil {
return err
}
// For both of the current encoding types, the channel ID's are to be
// sorted in place, so we'll do that now. The sorting is applied unless
// we were specifically requested not to for testing purposes.
if !q.noSort {
sort.Slice(q.ShortChanIDs, func(i, j int) bool {
return q.ShortChanIDs[i].ToUint64() <
q.ShortChanIDs[j].ToUint64()
})
}
// Base on our encoding type, we'll write out the set of short channel
// ID's.
err := encodeShortChanIDs(w, q.EncodingType, q.ShortChanIDs)
if err != nil {
return err
}
return WriteBytes(w, q.ExtraData)
}
// encodeShortChanIDs encodes the passed short channel ID's into the passed
// io.Writer, respecting the specified encoding type.
func encodeShortChanIDs(w *bytes.Buffer, encodingType QueryEncoding,
shortChanIDs []ShortChannelID) error {
switch encodingType {
// In this encoding, we'll simply write a sorted array of encoded short
// channel ID's from the buffer.
case EncodingSortedPlain:
// First, we'll write out the number of bytes of the query
// body. We add 1 as the response will have the encoding type
// prepended to it.
numBytesBody := uint16(len(shortChanIDs)*8) + 1
if err := WriteUint16(w, numBytesBody); err != nil {
return err
}
// We'll then write out the encoding that that follows the
// actual encoded short channel ID's.
err := WriteQueryEncoding(w, encodingType)
if err != nil {
return err
}
// Now that we know they're sorted, we can write out each short
// channel ID to the buffer.
for _, chanID := range shortChanIDs {
if err := WriteShortChannelID(w, chanID); err != nil {
return fmt.Errorf("unable to write short chan "+
"ID: %v", err)
}
}
return nil
// For this encoding we'll first write out a serialized version of all
// the channel ID's into a buffer, then zlib encode that. The final
// payload is what we'll write out to the passed io.Writer.
//
// TODO(roasbeef): assumes the caller knows the proper chunk size to
// pass to avoid bin-packing here
case EncodingSortedZlib:
// If we don't have anything at all to write, then we'll write
// an empty payload so we don't include things like the zlib
// header when the remote party is expecting no actual short
// channel IDs.
var compressedPayload []byte
if len(shortChanIDs) > 0 {
// We'll make a new write buffer to hold the bytes of
// shortChanIDs.
var wb bytes.Buffer
// Next, we'll write out all the channel ID's directly
// into the zlib writer, which will do compressing on
// the fly.
for _, chanID := range shortChanIDs {
err := WriteShortChannelID(&wb, chanID)
if err != nil {
return fmt.Errorf(
"unable to write short chan "+
"ID: %v", err,
)
}
}
// With shortChanIDs written into wb, we'll create a
// zlib writer and write all the compressed bytes.
var zlibBuffer bytes.Buffer
zlibWriter := zlib.NewWriter(&zlibBuffer)
if _, err := zlibWriter.Write(wb.Bytes()); err != nil {
return fmt.Errorf(
"unable to write compressed short chan"+
"ID: %w", err)
}
// Now that we've written all the elements, we'll
// ensure the compressed stream is written to the
// underlying buffer.
if err := zlibWriter.Close(); err != nil {
return fmt.Errorf("unable to finalize "+
"compression: %v", err)
}
compressedPayload = zlibBuffer.Bytes()
}
// Now that we have all the items compressed, we can compute
// what the total payload size will be. We add one to account
// for the byte to encode the type.
//
// If we don't have any actual bytes to write, then we'll end
// up emitting one byte for the length, followed by the
// encoding type, and nothing more. The spec isn't 100% clear
// in this area, but we do this as this is what most of the
// other implementations do.
numBytesBody := len(compressedPayload) + 1
// Finally, we can write out the number of bytes, the
// compression type, and finally the buffer itself.
if err := WriteUint16(w, uint16(numBytesBody)); err != nil {
return err
}
err := WriteQueryEncoding(w, encodingType)
if err != nil {
return err
}
return WriteBytes(w, compressedPayload)
default:
// If we're trying to encode with an encoding type that we
// don't know of, then we'll return a parsing error as we can't
// continue if we're unable to encode them.
return ErrUnknownShortChanIDEncoding(encodingType)
}
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (q *QueryShortChanIDs) MsgType() MessageType {
return MsgQueryShortChanIDs
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (q *QueryShortChanIDs) SerializedSize() (uint32, error) {
return MessageSerializedSize(q)
}
package lnwire
import (
"bytes"
"fmt"
"io"
"math"
"sort"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/tlv"
)
// ReplyChannelRange is the response to the QueryChannelRange message. It
// includes the original query, and the next streaming chunk of encoded short
// channel ID's as the response. We'll also include a byte that indicates if
// this is the last query in the message.
type ReplyChannelRange struct {
// ChainHash denotes the target chain that we're trying to synchronize
// channel graph state for.
ChainHash chainhash.Hash
// FirstBlockHeight is the first block in the query range. The
// responder should send all new short channel IDs from this block
// until this block plus the specified number of blocks.
FirstBlockHeight uint32
// NumBlocks is the number of blocks beyond the first block that short
// channel ID's should be sent for.
NumBlocks uint32
// Complete denotes if this is the conclusion of the set of streaming
// responses to the original query.
Complete uint8
// EncodingType is a signal to the receiver of the message that
// indicates exactly how the set of short channel ID's that follow have
// been encoded.
EncodingType QueryEncoding
// ShortChanIDs is a slice of decoded short channel ID's.
ShortChanIDs []ShortChannelID
// Timestamps is an optional set of timestamps corresponding to the
// latest timestamps for the channel update messages corresponding to
// those referenced in the ShortChanIDs list. If this field is used,
// then the length must match the length of ShortChanIDs.
Timestamps Timestamps
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
// noSort indicates whether or not to sort the short channel ids before
// writing them out.
//
// NOTE: This should only be used for testing.
noSort bool
}
// NewReplyChannelRange creates a new empty ReplyChannelRange message.
func NewReplyChannelRange() *ReplyChannelRange {
return &ReplyChannelRange{
ExtraData: make([]byte, 0),
}
}
// A compile time check to ensure ReplyChannelRange implements the
// lnwire.Message interface.
var _ Message = (*ReplyChannelRange)(nil)
// A compile time check to ensure ReplyChannelRange implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*ReplyChannelRange)(nil)
// Decode deserializes a serialized ReplyChannelRange message stored in the
// passed io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *ReplyChannelRange) Decode(r io.Reader, pver uint32) error {
err := ReadElements(r,
c.ChainHash[:],
&c.FirstBlockHeight,
&c.NumBlocks,
&c.Complete,
)
if err != nil {
return err
}
c.EncodingType, c.ShortChanIDs, err = decodeShortChanIDs(r)
if err != nil {
return err
}
var tlvRecords ExtraOpaqueData
if err := ReadElements(r, &tlvRecords); err != nil {
return err
}
var timeStamps Timestamps
typeMap, err := tlvRecords.ExtractRecords(&timeStamps)
if err != nil {
return err
}
// Set the corresponding TLV types if they were included in the stream.
if val, ok := typeMap[TimestampsRecordType]; ok && val == nil {
c.Timestamps = timeStamps
// Check that a timestamp was provided for each SCID.
if len(c.Timestamps) != len(c.ShortChanIDs) {
return fmt.Errorf("number of timestamps does not " +
"match number of SCIDs")
}
}
if len(tlvRecords) != 0 {
c.ExtraData = tlvRecords
}
return nil
}
// Encode serializes the target ReplyChannelRange into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *ReplyChannelRange) Encode(w *bytes.Buffer, pver uint32) error {
if err := WriteBytes(w, c.ChainHash[:]); err != nil {
return err
}
if err := WriteUint32(w, c.FirstBlockHeight); err != nil {
return err
}
if err := WriteUint32(w, c.NumBlocks); err != nil {
return err
}
if err := WriteUint8(w, c.Complete); err != nil {
return err
}
// For both of the current encoding types, the channel ID's are to be
// sorted in place, so we'll do that now. The sorting is applied unless
// we were specifically requested not to for testing purposes.
if !c.noSort {
var scidPreSortIndex map[uint64]int
if len(c.Timestamps) != 0 {
// Sanity check that a timestamp was provided for each
// SCID.
if len(c.Timestamps) != len(c.ShortChanIDs) {
return fmt.Errorf("must provide a timestamp " +
"pair for each of the given SCIDs")
}
// Create a map from SCID value to the original index of
// the SCID in the unsorted list.
scidPreSortIndex = make(
map[uint64]int, len(c.ShortChanIDs),
)
for i, scid := range c.ShortChanIDs {
scidPreSortIndex[scid.ToUint64()] = i
}
// Sanity check that there were no duplicates in the
// SCID list.
if len(scidPreSortIndex) != len(c.ShortChanIDs) {
return fmt.Errorf("scid list should not " +
"contain duplicates")
}
}
// Now sort the SCIDs.
sort.Slice(c.ShortChanIDs, func(i, j int) bool {
return c.ShortChanIDs[i].ToUint64() <
c.ShortChanIDs[j].ToUint64()
})
if len(c.Timestamps) != 0 {
timestamps := make(Timestamps, len(c.Timestamps))
for i, scid := range c.ShortChanIDs {
timestamps[i] = []ChanUpdateTimestamps(
c.Timestamps,
)[scidPreSortIndex[scid.ToUint64()]]
}
c.Timestamps = timestamps
}
}
err := encodeShortChanIDs(w, c.EncodingType, c.ShortChanIDs)
if err != nil {
return err
}
recordProducers := make([]tlv.RecordProducer, 0, 1)
if len(c.Timestamps) != 0 {
recordProducers = append(recordProducers, &c.Timestamps)
}
err = EncodeMessageExtraData(&c.ExtraData, recordProducers...)
if err != nil {
return err
}
return WriteBytes(w, c.ExtraData)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *ReplyChannelRange) MsgType() MessageType {
return MsgReplyChannelRange
}
// LastBlockHeight returns the last block height covered by the range of a
// QueryChannelRange message.
func (c *ReplyChannelRange) LastBlockHeight() uint32 {
// Handle overflows by casting to uint64.
lastBlockHeight := uint64(c.FirstBlockHeight) + uint64(c.NumBlocks) - 1
if lastBlockHeight > math.MaxUint32 {
return math.MaxUint32
}
return uint32(lastBlockHeight)
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (c *ReplyChannelRange) SerializedSize() (uint32, error) {
return MessageSerializedSize(c)
}
package lnwire
import (
"bytes"
"io"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// ReplyShortChanIDsEnd is a message that marks the end of a streaming message
// response to an initial QueryShortChanIDs message. This marks that the
// receiver of the original QueryShortChanIDs for the target chain has either
// sent all adequate responses it knows of, or doesn't know of any short chan
// ID's for the target chain.
type ReplyShortChanIDsEnd struct {
// ChainHash denotes the target chain that we're respond to a short
// chan ID query for.
ChainHash chainhash.Hash
// Complete will be set to 0 if we don't know of the chain that the
// remote peer sent their query for. Otherwise, we'll set this to 1 in
// order to indicate that we've sent all known responses for the prior
// set of short chan ID's in the corresponding QueryShortChanIDs
// message.
Complete uint8
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// NewReplyShortChanIDsEnd creates a new empty ReplyShortChanIDsEnd message.
func NewReplyShortChanIDsEnd() *ReplyShortChanIDsEnd {
return &ReplyShortChanIDsEnd{}
}
// A compile time check to ensure ReplyShortChanIDsEnd implements the
// lnwire.Message interface.
var _ Message = (*ReplyShortChanIDsEnd)(nil)
// A compile time check to ensure ReplyShortChanIDsEnd implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*ReplyShortChanIDsEnd)(nil)
// Decode deserializes a serialized ReplyShortChanIDsEnd message stored in the
// passed io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *ReplyShortChanIDsEnd) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
c.ChainHash[:],
&c.Complete,
&c.ExtraData,
)
}
// Encode serializes the target ReplyShortChanIDsEnd into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *ReplyShortChanIDsEnd) Encode(w *bytes.Buffer, pver uint32) error {
if err := WriteBytes(w, c.ChainHash[:]); err != nil {
return err
}
if err := WriteUint8(w, c.Complete); err != nil {
return err
}
return WriteBytes(w, c.ExtraData)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *ReplyShortChanIDsEnd) MsgType() MessageType {
return MsgReplyShortChanIDsEnd
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (c *ReplyShortChanIDsEnd) SerializedSize() (uint32, error) {
return MessageSerializedSize(c)
}
package lnwire
import (
"bytes"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/tlv"
)
// RevokeAndAck is sent by either side once a CommitSig message has been
// received, and validated. This message serves to revoke the prior commitment
// transaction, which was the most up to date version until a CommitSig message
// referencing the specified ChannelPoint was received. Additionally, this
// message also piggyback's the next revocation hash that Alice should use when
// constructing the Bob's version of the next commitment transaction (which
// would be done before sending a CommitSig message). This piggybacking allows
// Alice to send the next CommitSig message modifying Bob's commitment
// transaction without first asking for a revocation hash initially.
type RevokeAndAck struct {
// ChanID uniquely identifies to which currently active channel this
// RevokeAndAck applies to.
ChanID ChannelID
// Revocation is the preimage to the revocation hash of the now prior
// commitment transaction.
Revocation [32]byte
// NextRevocationKey is the next commitment point which should be used
// for the next commitment transaction the remote peer creates for us.
// This, in conjunction with revocation base point will be used to
// create the proper revocation key used within the commitment
// transaction.
NextRevocationKey *btcec.PublicKey
// LocalNonce is the next _local_ nonce for the sending party. This
// allows the receiving party to propose a new commitment using their
// remote nonce and the sender's local nonce.
LocalNonce OptMusig2NonceTLV
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// NewRevokeAndAck creates a new RevokeAndAck message.
func NewRevokeAndAck() *RevokeAndAck {
return &RevokeAndAck{
ExtraData: make([]byte, 0),
}
}
// A compile time check to ensure RevokeAndAck implements the lnwire.Message
// interface.
var _ Message = (*RevokeAndAck)(nil)
// A compile time check to ensure RevokeAndAck implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*RevokeAndAck)(nil)
// Decode deserializes a serialized RevokeAndAck message stored in the
// passed io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *RevokeAndAck) Decode(r io.Reader, pver uint32) error {
err := ReadElements(r,
&c.ChanID,
c.Revocation[:],
&c.NextRevocationKey,
)
if err != nil {
return err
}
var tlvRecords ExtraOpaqueData
if err := ReadElements(r, &tlvRecords); err != nil {
return err
}
localNonce := c.LocalNonce.Zero()
typeMap, err := tlvRecords.ExtractRecords(&localNonce)
if err != nil {
return err
}
// Set the corresponding TLV types if they were included in the stream.
if val, ok := typeMap[c.LocalNonce.TlvType()]; ok && val == nil {
c.LocalNonce = tlv.SomeRecordT(localNonce)
}
if len(tlvRecords) != 0 {
c.ExtraData = tlvRecords
}
return nil
}
// Encode serializes the target RevokeAndAck into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *RevokeAndAck) Encode(w *bytes.Buffer, pver uint32) error {
recordProducers := make([]tlv.RecordProducer, 0, 1)
c.LocalNonce.WhenSome(func(localNonce Musig2NonceTLV) {
recordProducers = append(recordProducers, &localNonce)
})
err := EncodeMessageExtraData(&c.ExtraData, recordProducers...)
if err != nil {
return err
}
if err := WriteChannelID(w, c.ChanID); err != nil {
return err
}
if err := WriteBytes(w, c.Revocation[:]); err != nil {
return err
}
if err := WritePublicKey(w, c.NextRevocationKey); err != nil {
return err
}
return WriteBytes(w, c.ExtraData)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *RevokeAndAck) MsgType() MessageType {
return MsgRevokeAndAck
}
// TargetChanID returns the channel id of the link for which this message is
// intended.
//
// NOTE: Part of peer.LinkUpdater interface.
func (c *RevokeAndAck) TargetChanID() ChannelID {
return c.ChanID
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (c *RevokeAndAck) SerializedSize() (uint32, error) {
return MessageSerializedSize(c)
}
package lnwire
import (
"fmt"
"io"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// AliasScidRecordType is the type of the experimental record to denote
// the alias being used in an option_scid_alias channel.
AliasScidRecordType tlv.Type = 1
)
// ShortChannelID represents the set of data which is needed to retrieve all
// necessary data to validate the channel existence.
type ShortChannelID struct {
// BlockHeight is the height of the block where funding transaction
// located.
//
// NOTE: This field is limited to 3 bytes.
BlockHeight uint32
// TxIndex is a position of funding transaction within a block.
//
// NOTE: This field is limited to 3 bytes.
TxIndex uint32
// TxPosition indicating transaction output which pays to the channel.
TxPosition uint16
}
// NewShortChanIDFromInt returns a new ShortChannelID which is the decoded
// version of the compact channel ID encoded within the uint64. The format of
// the compact channel ID is as follows: 3 bytes for the block height, 3 bytes
// for the transaction index, and 2 bytes for the output index.
func NewShortChanIDFromInt(chanID uint64) ShortChannelID {
return ShortChannelID{
BlockHeight: uint32(chanID >> 40),
TxIndex: uint32(chanID>>16) & 0xFFFFFF,
TxPosition: uint16(chanID),
}
}
// ToUint64 converts the ShortChannelID into a compact format encoded within a
// uint64 (8 bytes).
func (c ShortChannelID) ToUint64() uint64 {
// TODO(roasbeef): explicit error on overflow?
return ((uint64(c.BlockHeight) << 40) | (uint64(c.TxIndex) << 16) |
(uint64(c.TxPosition)))
}
// String generates a human-readable representation of the channel ID.
func (c ShortChannelID) String() string {
return fmt.Sprintf("%d:%d:%d", c.BlockHeight, c.TxIndex, c.TxPosition)
}
// AltString generates a human-readable representation of the channel ID
// with 'x' as a separator.
func (c ShortChannelID) AltString() string {
return fmt.Sprintf("%dx%dx%d", c.BlockHeight, c.TxIndex, c.TxPosition)
}
// Record returns a TLV record that can be used to encode/decode a
// ShortChannelID to/from a TLV stream.
func (c *ShortChannelID) Record() tlv.Record {
return tlv.MakeStaticRecord(
AliasScidRecordType, c, 8, EShortChannelID, DShortChannelID,
)
}
// IsDefault returns true if the ShortChannelID represents the zero value for
// its type.
func (c ShortChannelID) IsDefault() bool {
return c == ShortChannelID{}
}
// EShortChannelID is an encoder for ShortChannelID. It is exported so other
// packages can use the encoding scheme.
func EShortChannelID(w io.Writer, val interface{}, buf *[8]byte) error {
if v, ok := val.(*ShortChannelID); ok {
return tlv.EUint64T(w, v.ToUint64(), buf)
}
return tlv.NewTypeForEncodingErr(val, "lnwire.ShortChannelID")
}
// DShortChannelID is a decoder for ShortChannelID. It is exported so other
// packages can use the decoding scheme.
func DShortChannelID(r io.Reader, val interface{}, buf *[8]byte,
l uint64) error {
if v, ok := val.(*ShortChannelID); ok {
var scid uint64
err := tlv.DUint64(r, &scid, buf, 8)
if err != nil {
return err
}
*v = NewShortChanIDFromInt(scid)
return nil
}
return tlv.NewTypeForDecodingErr(val, "lnwire.ShortChannelID", l, 8)
}
package lnwire
import (
"bytes"
"io"
"github.com/lightningnetwork/lnd/tlv"
)
type (
// ShutdownNonceType is the type of the shutdown nonce TLV record.
ShutdownNonceType = tlv.TlvType8
// ShutdownNonceTLV is the TLV record that contains the shutdown nonce.
ShutdownNonceTLV = tlv.OptionalRecordT[ShutdownNonceType, Musig2Nonce]
)
// SomeShutdownNonce returns a ShutdownNonceTLV with the given nonce.
func SomeShutdownNonce(nonce Musig2Nonce) ShutdownNonceTLV {
return tlv.SomeRecordT(
tlv.NewRecordT[ShutdownNonceType, Musig2Nonce](nonce),
)
}
// Shutdown is sent by either side in order to initiate the cooperative closure
// of a channel. This message is sparse as both sides implicitly have the
// information necessary to construct a transaction that will send the settled
// funds of both parties to the final delivery addresses negotiated during the
// funding workflow.
type Shutdown struct {
// ChannelID serves to identify which channel is to be closed.
ChannelID ChannelID
// Address is the script to which the channel funds will be paid.
Address DeliveryAddress
// ShutdownNonce is the nonce the sender will use to sign the first
// co-op sign offer.
ShutdownNonce ShutdownNonceTLV
// CustomRecords maps TLV types to byte slices, storing arbitrary data
// intended for inclusion in the ExtraData field of the Shutdown
// message.
CustomRecords CustomRecords
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// NewShutdown creates a new Shutdown message.
func NewShutdown(cid ChannelID, addr DeliveryAddress) *Shutdown {
return &Shutdown{
ChannelID: cid,
Address: addr,
}
}
// A compile-time check to ensure Shutdown implements the lnwire.Message
// interface.
var _ Message = (*Shutdown)(nil)
// A compile-time check to ensure Shutdown implements the lnwire.SizeableMessage
// interface.
var _ SizeableMessage = (*Shutdown)(nil)
// Decode deserializes a serialized Shutdown from the passed io.Reader,
// observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (s *Shutdown) Decode(r io.Reader, pver uint32) error {
err := ReadElements(r, &s.ChannelID, &s.Address)
if err != nil {
return err
}
var tlvRecords ExtraOpaqueData
if err := ReadElements(r, &tlvRecords); err != nil {
return err
}
// Extract TLV records from the extra data field.
musigNonce := s.ShutdownNonce.Zero()
customRecords, parsed, extraData, err := ParseAndExtractCustomRecords(
tlvRecords, &musigNonce,
)
if err != nil {
return err
}
// Assign the parsed records back to the message.
if _, ok := parsed[musigNonce.TlvType()]; ok {
s.ShutdownNonce = tlv.SomeRecordT(musigNonce)
}
s.CustomRecords = customRecords
s.ExtraData = extraData
return nil
}
// Encode serializes the target Shutdown into the passed io.Writer observing
// the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (s *Shutdown) Encode(w *bytes.Buffer, pver uint32) error {
if err := WriteChannelID(w, s.ChannelID); err != nil {
return err
}
if err := WriteDeliveryAddress(w, s.Address); err != nil {
return err
}
// Only include nonce in extra data if present.
var records []tlv.RecordProducer
s.ShutdownNonce.WhenSome(
func(nonce tlv.RecordT[ShutdownNonceType, Musig2Nonce]) {
records = append(records, &nonce)
},
)
extraData, err := MergeAndEncode(records, s.ExtraData, s.CustomRecords)
if err != nil {
return err
}
return WriteBytes(w, extraData)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (s *Shutdown) MsgType() MessageType {
return MsgShutdown
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (s *Shutdown) SerializedSize() (uint32, error) {
return MessageSerializedSize(s)
}
package lnwire
import (
"errors"
"fmt"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/tlv"
)
var (
errSigTooShort = errors.New("malformed signature: too short")
errBadLength = errors.New("malformed signature: bad length")
errBadRLength = errors.New("malformed signature: bogus R length")
errBadSLength = errors.New("malformed signature: bogus S length")
errRTooLong = errors.New("R is over 32 bytes long without padding")
errSTooLong = errors.New("S is over 32 bytes long without padding")
)
// sigType represents the type of signature that is carried within the Sig.
// Today this can either be an ECDSA sig or a schnorr sig. Both of these can
// fit cleanly into 64 bytes.
type sigType uint
const (
// sigTypeECDSA represents an ECDSA signature.
sigTypeECDSA sigType = iota
// sigTypeSchnorr represents a schnorr signature.
sigTypeSchnorr
)
// Sig is a fixed-sized ECDSA signature or 64-byte schnorr signature. For the
// ECDSA sig, unlike Bitcoin, we use fixed sized signatures on the wire,
// instead of DER encoded signatures. This type provides several methods to
// convert to/from a regular Bitcoin DER encoded signature (raw bytes and
// *ecdsa.Signature).
type Sig struct {
bytes [64]byte
sigType sigType
}
// ForceSchnorr forces the signature to be interpreted as a schnorr signature.
// This is useful when reading an HTLC sig off the wire for a taproot channel.
// In this case, in order to obtain an input.Signature, we need to know that
// the sig is a schnorr sig.
func (s *Sig) ForceSchnorr() {
s.sigType = sigTypeSchnorr
}
// RawBytes returns the raw bytes of signature.
func (s *Sig) RawBytes() []byte {
return s.bytes[:]
}
// Copy copies the signature into a new Sig instance.
func (s *Sig) Copy() Sig {
var sCopy Sig
copy(sCopy.bytes[:], s.bytes[:])
sCopy.sigType = s.sigType
return sCopy
}
// Record returns a Record that can be used to encode or decode the backing
// object.
//
// This returns a record that serializes the sig as a 64-byte fixed size
// signature.
func (s *Sig) Record() tlv.Record {
// We set a type here as zero as it isn't needed when used as a
// RecordT.
return tlv.MakePrimitiveRecord(0, &s.bytes)
}
// NewSigFromWireECDSA returns a Sig instance based on an ECDSA signature
// that's already in the 64-byte format we expect.
func NewSigFromWireECDSA(sig []byte) (Sig, error) {
if len(sig) != 64 {
return Sig{}, fmt.Errorf("%w: %v bytes", errSigTooShort,
len(sig))
}
var s Sig
copy(s.bytes[:], sig)
return s, nil
}
// NewSigFromECDSARawSignature returns a Sig from a Bitcoin raw signature
// encoded in the canonical DER encoding.
func NewSigFromECDSARawSignature(sig []byte) (Sig, error) {
var b [64]byte
// Check the total length is above the minimal.
if len(sig) < ecdsa.MinSigLen {
return Sig{}, errSigTooShort
}
// The DER representation is laid out as:
// 0x30 <length> 0x02 <length r> r 0x02 <length s> s
// which means the length of R is the 4th byte and the length of S is
// the second byte after R ends. 0x02 signifies a length-prefixed,
// zero-padded, big-endian bigint. 0x30 signifies a DER signature.
// See the Serialize() method for ecdsa.Signature for details.
// Reading <length>, remaining: [0x02 <length r> r 0x02 <length s> s]
sigLen := int(sig[1])
// siglen should be less than the entire message and greater than
// the minimal message size.
if sigLen+2 > len(sig) || sigLen+2 < ecdsa.MinSigLen {
return Sig{}, errBadLength
}
// Reading <length r>, remaining: [r 0x02 <length s> s]
rLen := int(sig[3])
// rLen must be positive and must be able to fit in other elements.
// Assuming s is one byte, then we have 0x30, <length>, 0x20,
// <length r>, 0x20, <length s>, s, a total of 7 bytes.
if rLen <= 0 || rLen+7 > len(sig) {
return Sig{}, errBadRLength
}
// Reading <length s>, remaining: [s]
sLen := int(sig[5+rLen])
// S should be the rest of the string.
// sLen must be positive and must be able to fit in other elements.
// We know r is rLen bytes, and we have 0x30, <length>, 0x20,
// <length r>, 0x20, <length s>, a total of rLen+6 bytes.
if sLen <= 0 || sLen+rLen+6 > len(sig) {
return Sig{}, errBadSLength
}
// Check to make sure R and S can both fit into their intended buffers.
// We check S first because these code blocks decrement sLen and rLen
// in the case of a 33-byte 0-padded integer returned from Serialize()
// and rLen is used in calculating array indices for S. We can track
// this with additional variables, but it's more efficient to just
// check S first.
if sLen > 32 {
if (sLen > 33) || (sig[6+rLen] != 0x00) {
return Sig{}, errSTooLong
}
sLen--
copy(b[64-sLen:], sig[7+rLen:])
} else {
copy(b[64-sLen:], sig[6+rLen:])
}
// Do the same for R as we did for S
if rLen > 32 {
if (rLen > 33) || (sig[4] != 0x00) {
return Sig{}, errRTooLong
}
rLen--
copy(b[32-rLen:], sig[5:5+rLen])
} else {
copy(b[32-rLen:], sig[4:4+rLen])
}
return Sig{
bytes: b,
sigType: sigTypeECDSA,
}, nil
}
// NewSigFromSchnorrRawSignature converts a raw schnorr signature into an
// lnwire.Sig.
func NewSigFromSchnorrRawSignature(sig []byte) (Sig, error) {
var s Sig
copy(s.bytes[:], sig)
s.sigType = sigTypeSchnorr
return s, nil
}
// NewSigFromSignature creates a new signature as used on the wire, from an
// existing ecdsa.Signature or schnorr.Signature.
func NewSigFromSignature(e input.Signature) (Sig, error) {
if e == nil {
return Sig{}, fmt.Errorf("cannot decode empty signature")
}
// Nil is still a valid interface, apparently. So we need a more
// explicit check here.
if ecsig, ok := e.(*ecdsa.Signature); ok && ecsig == nil {
return Sig{}, fmt.Errorf("cannot decode empty signature")
}
switch ecSig := e.(type) {
// If this is a schnorr signature, then we can just pack it as normal,
// since the default encoding is already 64 bytes.
case *schnorr.Signature:
return NewSigFromSchnorrRawSignature(e.Serialize())
// For ECDSA signatures, we'll need to do a bit more work to map the
// signature into a compact 64 byte form.
case *ecdsa.Signature:
// Serialize the signature with all the checks that entails.
return NewSigFromECDSARawSignature(e.Serialize())
default:
return Sig{}, fmt.Errorf("unknown wire sig type: %T", ecSig)
}
}
// ToSignature converts the fixed-sized signature to a input.Signature which
// can be used for signature validation checks.
func (s *Sig) ToSignature() (input.Signature, error) {
switch s.sigType {
case sigTypeSchnorr:
return schnorr.ParseSignature(s.bytes[:])
case sigTypeECDSA:
// Parse the signature with strict checks.
sigBytes := s.ToSignatureBytes()
sig, err := ecdsa.ParseDERSignature(sigBytes)
if err != nil {
return nil, err
}
return sig, nil
default:
return nil, fmt.Errorf("unknown sig type: %v", s.sigType)
}
}
// ToSignatureBytes serializes the target fixed-sized signature into the
// encoding of the primary domain for the signature. For ECDSA signatures, this
// is the raw bytes of a DER encoding.
func (s *Sig) ToSignatureBytes() []byte {
switch s.sigType {
// For ECDSA signatures, we'll convert to DER encoding.
case sigTypeECDSA:
// Extract canonically-padded bigint representations from buffer
r := extractCanonicalPadding(s.bytes[0:32])
s := extractCanonicalPadding(s.bytes[32:64])
rLen := uint8(len(r))
sLen := uint8(len(s))
// Create a canonical serialized signature. DER format is:
// 0x30 <length> 0x02 <length r> r 0x02 <length s> s
sigBytes := make([]byte, 6+rLen+sLen)
sigBytes[0] = 0x30 // DER signature magic value
sigBytes[1] = 4 + rLen + sLen // Length of rest of signature
sigBytes[2] = 0x02 // Big integer magic value
sigBytes[3] = rLen // Length of R
sigBytes[rLen+4] = 0x02 // Big integer magic value
sigBytes[rLen+5] = sLen // Length of S
copy(sigBytes[4:], r) // Copy R
copy(sigBytes[rLen+6:], s) // Copy S
return sigBytes
// For schnorr signatures, we can use the same internal 64 bytes.
case sigTypeSchnorr:
// We'll make a copy of the signature so we don't return a
// reference into the raw slice.
var sig [64]byte
copy(sig[:], s.bytes[:])
return sig[:]
default:
// TODO(roasbeef): can only be called via public methods so
// never reachable?
panic("sig type not set")
}
}
// extractCanonicalPadding is a utility function to extract the canonical
// padding of a big-endian integer from the wire encoding (a 0-padded
// big-endian integer) such that it passes btcec.canonicalPadding test.
func extractCanonicalPadding(b []byte) []byte {
for i := 0; i < len(b); i++ {
// Found first non-zero byte.
if b[i] > 0 {
// If the MSB is set, we need zero padding.
if b[i]&0x80 == 0x80 {
return append([]byte{0x00}, b[i:]...)
}
return b[i:]
}
}
return []byte{0x00}
}
package lnwire
import (
"bytes"
"io"
)
// Stfu is a message that is sent to lock the channel state prior to some other
// interactive protocol where channel updates need to be paused.
type Stfu struct {
// ChanID identifies which channel needs to be frozen.
ChanID ChannelID
// Initiator is a byte that identifies whether we are the initiator of
// this process.
Initiator bool
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// A compile time check to ensure Stfu implements the lnwire.Message interface.
var _ Message = (*Stfu)(nil)
// A compile time check to ensure Stfu implements the lnwire.SizeableMessage
// interface.
var _ SizeableMessage = (*Stfu)(nil)
// Encode serializes the target Stfu into the passed io.Writer.
// Serialization will observe the rules defined by the passed protocol version.
//
// This is a part of the lnwire.Message interface.
func (s *Stfu) Encode(w *bytes.Buffer, _ uint32) error {
if err := WriteChannelID(w, s.ChanID); err != nil {
return err
}
if err := WriteBool(w, s.Initiator); err != nil {
return err
}
return WriteBytes(w, s.ExtraData)
}
// Decode deserializes the serialized Stfu stored in the passed io.Reader
// into the target Stfu using the deserialization rules defined by the
// passed protocol version.
//
// This is a part of the lnwire.Message interface.
func (s *Stfu) Decode(r io.Reader, _ uint32) error {
if err := ReadElements(
r, &s.ChanID, &s.Initiator, &s.ExtraData,
); err != nil {
return err
}
// This is required to pass the fuzz test round trip equality check.
if len(s.ExtraData) == 0 {
s.ExtraData = nil
}
return nil
}
// MsgType returns the MessageType code which uniquely identifies this message
// as a Stfu on the wire.
//
// This is part of the lnwire.Message interface.
func (s *Stfu) MsgType() MessageType {
return MsgStfu
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (s *Stfu) SerializedSize() (uint32, error) {
return MessageSerializedSize(s)
}
// A compile time check to ensure Stfu implements the
// lnwire.LinkUpdater interface.
var _ LinkUpdater = (*Stfu)(nil)
// TargetChanID returns the channel id of the link for which this message is
// intended.
//
// NOTE: Part of peer.LinkUpdater interface.
func (s *Stfu) TargetChanID() ChannelID {
return s.ChanID
}
package lnwire
import (
"bytes"
"fmt"
"image/color"
"math"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/tlv"
"pgregory.net/rapid"
)
// TestMessage is an interface that extends the base Message interface with a
// method to populate the message with random testing data.
type TestMessage interface {
Message
// RandTestMessage populates the message with random data suitable for
// testing. It uses the rapid testing framework to generate random
// values.
RandTestMessage(t *rapid.T) Message
}
// A compile time check to ensure AcceptChannel implements the TestMessage
// interface.
var _ TestMessage = (*AcceptChannel)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (a *AcceptChannel) RandTestMessage(t *rapid.T) Message {
var pendingChanID [32]byte
pendingChanIDBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(
t, "pendingChanID",
)
copy(pendingChanID[:], pendingChanIDBytes)
var channelType *ChannelType
includeChannelType := rapid.Bool().Draw(t, "includeChannelType")
includeLeaseExpiry := rapid.Bool().Draw(t, "includeLeaseExpiry")
includeLocalNonce := rapid.Bool().Draw(t, "includeLocalNonce")
if includeChannelType {
channelType = RandChannelType(t)
}
var leaseExpiry *LeaseExpiry
if includeLeaseExpiry {
leaseExpiry = RandLeaseExpiry(t)
}
var localNonce OptMusig2NonceTLV
if includeLocalNonce {
nonce := RandMusig2Nonce(t)
localNonce = tlv.SomeRecordT(
tlv.NewRecordT[NonceRecordTypeT, Musig2Nonce](nonce),
)
}
return &AcceptChannel{
PendingChannelID: pendingChanID,
DustLimit: btcutil.Amount(
rapid.IntRange(100, 1000).Draw(t, "dustLimit"),
),
MaxValueInFlight: MilliSatoshi(
rapid.IntRange(10000, 1000000).Draw(
t, "maxValueInFlight",
),
),
ChannelReserve: btcutil.Amount(
rapid.IntRange(1000, 10000).Draw(t, "channelReserve"),
),
HtlcMinimum: MilliSatoshi(
rapid.IntRange(1, 1000).Draw(t, "htlcMinimum"),
),
MinAcceptDepth: uint32(
rapid.IntRange(1, 10).Draw(t, "minAcceptDepth"),
),
CsvDelay: uint16(
rapid.IntRange(144, 1000).Draw(t, "csvDelay"),
),
MaxAcceptedHTLCs: uint16(
rapid.IntRange(10, 500).Draw(t, "maxAcceptedHTLCs"),
),
FundingKey: RandPubKey(t),
RevocationPoint: RandPubKey(t),
PaymentPoint: RandPubKey(t),
DelayedPaymentPoint: RandPubKey(t),
HtlcPoint: RandPubKey(t),
FirstCommitmentPoint: RandPubKey(t),
UpfrontShutdownScript: RandDeliveryAddress(t),
ChannelType: channelType,
LeaseExpiry: leaseExpiry,
LocalNonce: localNonce,
ExtraData: RandExtraOpaqueData(t, nil),
}
}
// A compile time check to ensure AnnounceSignatures1 implements the
// lnwire.TestMessage interface.
var _ TestMessage = (*AnnounceSignatures1)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (a *AnnounceSignatures1) RandTestMessage(t *rapid.T) Message {
return &AnnounceSignatures1{
ChannelID: RandChannelID(t),
ShortChannelID: RandShortChannelID(t),
NodeSignature: RandSignature(t),
BitcoinSignature: RandSignature(t),
ExtraOpaqueData: RandExtraOpaqueData(t, nil),
}
}
// A compile time check to ensure AnnounceSignatures2 implements the
// lnwire.TestMessage interface.
var _ TestMessage = (*AnnounceSignatures2)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (a *AnnounceSignatures2) RandTestMessage(t *rapid.T) Message {
return &AnnounceSignatures2{
ChannelID: RandChannelID(t),
ShortChannelID: RandShortChannelID(t),
PartialSignature: *RandPartialSig(t),
ExtraOpaqueData: RandExtraOpaqueData(t, nil),
}
}
// A compile time check to ensure ChannelAnnouncement1 implements the
// TestMessage interface.
var _ TestMessage = (*ChannelAnnouncement1)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (a *ChannelAnnouncement1) RandTestMessage(t *rapid.T) Message {
// Generate Node IDs and Bitcoin keys (compressed public keys)
node1PubKey := RandPubKey(t)
node2PubKey := RandPubKey(t)
bitcoin1PubKey := RandPubKey(t)
bitcoin2PubKey := RandPubKey(t)
// Convert to byte arrays
var nodeID1, nodeID2, bitcoinKey1, bitcoinKey2 [33]byte
copy(nodeID1[:], node1PubKey.SerializeCompressed())
copy(nodeID2[:], node2PubKey.SerializeCompressed())
copy(bitcoinKey1[:], bitcoin1PubKey.SerializeCompressed())
copy(bitcoinKey2[:], bitcoin2PubKey.SerializeCompressed())
// Ensure nodeID1 is numerically less than nodeID2
// This is a requirement stated in the field description
if bytes.Compare(nodeID1[:], nodeID2[:]) > 0 {
nodeID1, nodeID2 = nodeID2, nodeID1
}
// Generate chain hash
chainHash := RandChainHash(t)
var hash chainhash.Hash
copy(hash[:], chainHash[:])
return &ChannelAnnouncement1{
NodeSig1: RandSignature(t),
NodeSig2: RandSignature(t),
BitcoinSig1: RandSignature(t),
BitcoinSig2: RandSignature(t),
Features: RandFeatureVector(t),
ChainHash: hash,
ShortChannelID: RandShortChannelID(t),
NodeID1: nodeID1,
NodeID2: nodeID2,
BitcoinKey1: bitcoinKey1,
BitcoinKey2: bitcoinKey2,
ExtraOpaqueData: RandExtraOpaqueData(t, nil),
}
}
// A compile time check to ensure ChannelAnnouncement2 implements the
// lnwire.TestMessage interface.
var _ TestMessage = (*ChannelAnnouncement2)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (c *ChannelAnnouncement2) RandTestMessage(t *rapid.T) Message {
features := RandFeatureVector(t)
shortChanID := RandShortChannelID(t)
capacity := uint64(rapid.IntRange(1, 16777215).Draw(t, "capacity"))
var nodeID1, nodeID2 [33]byte
copy(nodeID1[:], RandPubKey(t).SerializeCompressed())
copy(nodeID2[:], RandPubKey(t).SerializeCompressed())
// Make sure nodeID1 is numerically less than nodeID2 (as per spec).
if bytes.Compare(nodeID1[:], nodeID2[:]) > 0 {
nodeID1, nodeID2 = nodeID2, nodeID1
}
chainHash := RandChainHash(t)
var chainHashObj chainhash.Hash
copy(chainHashObj[:], chainHash[:])
msg := &ChannelAnnouncement2{
Signature: RandSignature(t),
ChainHash: tlv.NewPrimitiveRecord[tlv.TlvType0, chainhash.Hash](
chainHashObj,
),
Features: tlv.NewRecordT[tlv.TlvType2, RawFeatureVector](
*features,
),
ShortChannelID: tlv.NewRecordT[tlv.TlvType4, ShortChannelID](
shortChanID,
),
Capacity: tlv.NewPrimitiveRecord[tlv.TlvType6, uint64](
capacity,
),
NodeID1: tlv.NewPrimitiveRecord[tlv.TlvType8, [33]byte](
nodeID1,
),
NodeID2: tlv.NewPrimitiveRecord[tlv.TlvType10, [33]byte](
nodeID2,
),
ExtraOpaqueData: RandExtraOpaqueData(t, nil),
}
msg.Signature.ForceSchnorr()
// Randomly include optional fields
if rapid.Bool().Draw(t, "includeBitcoinKey1") {
var bitcoinKey1 [33]byte
copy(bitcoinKey1[:], RandPubKey(t).SerializeCompressed())
msg.BitcoinKey1 = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType12, [33]byte](
bitcoinKey1,
),
)
}
if rapid.Bool().Draw(t, "includeBitcoinKey2") {
var bitcoinKey2 [33]byte
copy(bitcoinKey2[:], RandPubKey(t).SerializeCompressed())
msg.BitcoinKey2 = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType14, [33]byte](
bitcoinKey2,
),
)
}
if rapid.Bool().Draw(t, "includeMerkleRootHash") {
hash := RandSHA256Hash(t)
var merkleRootHash [32]byte
copy(merkleRootHash[:], hash[:])
msg.MerkleRootHash = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType16, [32]byte](
merkleRootHash,
),
)
}
return msg
}
// A compile time check to ensure ChannelReady implements the lnwire.TestMessage
// interface.
var _ TestMessage = (*ChannelReady)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (c *ChannelReady) RandTestMessage(t *rapid.T) Message {
msg := &ChannelReady{
ChanID: RandChannelID(t),
NextPerCommitmentPoint: RandPubKey(t),
ExtraData: RandExtraOpaqueData(t, nil),
}
includeAliasScid := rapid.Bool().Draw(t, "includeAliasScid")
includeNextLocalNonce := rapid.Bool().Draw(t, "includeNextLocalNonce")
includeAnnouncementNodeNonce := rapid.Bool().Draw(
t, "includeAnnouncementNodeNonce",
)
includeAnnouncementBitcoinNonce := rapid.Bool().Draw(
t, "includeAnnouncementBitcoinNonce",
)
if includeAliasScid {
scid := RandShortChannelID(t)
msg.AliasScid = &scid
}
if includeNextLocalNonce {
nonce := RandMusig2Nonce(t)
msg.NextLocalNonce = SomeMusig2Nonce(nonce)
}
if includeAnnouncementNodeNonce {
nonce := RandMusig2Nonce(t)
msg.AnnouncementNodeNonce = tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType0, Musig2Nonce](nonce),
)
}
if includeAnnouncementBitcoinNonce {
nonce := RandMusig2Nonce(t)
msg.AnnouncementBitcoinNonce = tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType2, Musig2Nonce](nonce),
)
}
return msg
}
// A compile time check to ensure ChannelReestablish implements the
// lnwire.TestMessage interface.
var _ TestMessage = (*ChannelReestablish)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (a *ChannelReestablish) RandTestMessage(t *rapid.T) Message {
msg := &ChannelReestablish{
ChanID: RandChannelID(t),
NextLocalCommitHeight: rapid.Uint64().Draw(
t, "nextLocalCommitHeight",
),
RemoteCommitTailHeight: rapid.Uint64().Draw(
t, "remoteCommitTailHeight",
),
LastRemoteCommitSecret: RandPaymentPreimage(t),
LocalUnrevokedCommitPoint: RandPubKey(t),
ExtraData: RandExtraOpaqueData(t, nil),
}
// Randomly decide whether to include optional fields
includeLocalNonce := rapid.Bool().Draw(t, "includeLocalNonce")
includeDynHeight := rapid.Bool().Draw(t, "includeDynHeight")
if includeLocalNonce {
nonce := RandMusig2Nonce(t)
msg.LocalNonce = SomeMusig2Nonce(nonce)
}
if includeDynHeight {
height := DynHeight(rapid.Uint64().Draw(t, "dynHeight"))
msg.DynHeight = fn.Some(height)
}
return msg
}
// A compile time check to ensure ChannelUpdate1 implements the TestMessage
// interface.
var _ TestMessage = (*ChannelUpdate1)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (a *ChannelUpdate1) RandTestMessage(t *rapid.T) Message {
// Generate random message flags
// Randomly decide whether to include max HTLC field
includeMaxHtlc := rapid.Bool().Draw(t, "includeMaxHtlc")
var msgFlags ChanUpdateMsgFlags
if includeMaxHtlc {
msgFlags |= ChanUpdateRequiredMaxHtlc
}
// Generate random channel flags
// Randomly decide direction (node1 or node2)
isNode2 := rapid.Bool().Draw(t, "isNode2")
var chanFlags ChanUpdateChanFlags
if isNode2 {
chanFlags |= ChanUpdateDirection
}
// Randomly decide if channel is disabled
isDisabled := rapid.Bool().Draw(t, "isDisabled")
if isDisabled {
chanFlags |= ChanUpdateDisabled
}
// Generate chain hash
chainHash := RandChainHash(t)
var hash chainhash.Hash
copy(hash[:], chainHash[:])
// Generate other random fields
maxHtlc := MilliSatoshi(rapid.Uint64().Draw(t, "maxHtlc"))
// If max HTLC flag is not set, we need to zero the value
if !includeMaxHtlc {
maxHtlc = 0
}
return &ChannelUpdate1{
Signature: RandSignature(t),
ChainHash: hash,
ShortChannelID: RandShortChannelID(t),
Timestamp: uint32(rapid.IntRange(0, 0x7FFFFFFF).Draw(
t, "timestamp"),
),
MessageFlags: msgFlags,
ChannelFlags: chanFlags,
TimeLockDelta: uint16(rapid.IntRange(0, 65535).Draw(
t, "timelockDelta"),
),
HtlcMinimumMsat: MilliSatoshi(rapid.Uint64().Draw(
t, "htlcMinimum"),
),
BaseFee: uint32(rapid.IntRange(0, 0x7FFFFFFF).Draw(
t, "baseFee"),
),
FeeRate: uint32(rapid.IntRange(0, 0x7FFFFFFF).Draw(
t, "feeRate"),
),
HtlcMaximumMsat: maxHtlc,
ExtraOpaqueData: RandExtraOpaqueData(t, nil),
}
}
// A compile time check to ensure ChannelUpdate2 implements the
// lnwire.TestMessage interface.
var _ TestMessage = (*ChannelUpdate2)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (c *ChannelUpdate2) RandTestMessage(t *rapid.T) Message {
shortChanID := RandShortChannelID(t)
blockHeight := uint32(rapid.IntRange(0, 1000000).Draw(t, "blockHeight"))
var disabledFlags ChanUpdateDisableFlags
if rapid.Bool().Draw(t, "disableIncoming") {
disabledFlags |= ChanUpdateDisableIncoming
}
if rapid.Bool().Draw(t, "disableOutgoing") {
disabledFlags |= ChanUpdateDisableOutgoing
}
cltvExpiryDelta := uint16(rapid.IntRange(10, 200).Draw(
t, "cltvExpiryDelta"),
)
htlcMinMsat := MilliSatoshi(rapid.IntRange(1, 10000).Draw(
t, "htlcMinMsat"),
)
htlcMaxMsat := MilliSatoshi(rapid.IntRange(10000, 100000000).Draw(
t, "htlcMaxMsat"),
)
feeBaseMsat := uint32(rapid.IntRange(0, 10000).Draw(t, "feeBaseMsat"))
feeProportionalMillionths := uint32(rapid.IntRange(0, 10000).Draw(
t, "feeProportionalMillionths"),
)
chainHash := RandChainHash(t)
var chainHashObj chainhash.Hash
copy(chainHashObj[:], chainHash[:])
//nolint:ll
msg := &ChannelUpdate2{
Signature: RandSignature(t),
ChainHash: tlv.NewPrimitiveRecord[tlv.TlvType0, chainhash.Hash](
chainHashObj,
),
ShortChannelID: tlv.NewRecordT[tlv.TlvType2, ShortChannelID](
shortChanID,
),
BlockHeight: tlv.NewPrimitiveRecord[tlv.TlvType4, uint32](
blockHeight,
),
DisabledFlags: tlv.NewPrimitiveRecord[tlv.TlvType6, ChanUpdateDisableFlags]( //nolint:ll
disabledFlags,
),
CLTVExpiryDelta: tlv.NewPrimitiveRecord[tlv.TlvType10, uint16](
cltvExpiryDelta,
),
HTLCMinimumMsat: tlv.NewPrimitiveRecord[tlv.TlvType12, MilliSatoshi](
htlcMinMsat,
),
HTLCMaximumMsat: tlv.NewPrimitiveRecord[tlv.TlvType14, MilliSatoshi](
htlcMaxMsat,
),
FeeBaseMsat: tlv.NewPrimitiveRecord[tlv.TlvType16, uint32](
feeBaseMsat,
),
FeeProportionalMillionths: tlv.NewPrimitiveRecord[tlv.TlvType18, uint32](
feeProportionalMillionths,
),
ExtraOpaqueData: RandExtraOpaqueData(t, nil),
}
msg.Signature.ForceSchnorr()
if rapid.Bool().Draw(t, "isSecondPeer") {
msg.SecondPeer = tlv.SomeRecordT(
tlv.RecordT[tlv.TlvType8, TrueBoolean]{},
)
}
return msg
}
// A compile time check to ensure ClosingComplete implements the
// lnwire.TestMessage interface.
var _ TestMessage = (*ClosingComplete)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (c *ClosingComplete) RandTestMessage(t *rapid.T) Message {
msg := &ClosingComplete{
ChannelID: RandChannelID(t),
FeeSatoshis: btcutil.Amount(rapid.Int64Range(0, 1000000).Draw(
t, "feeSatoshis"),
),
LockTime: rapid.Uint32Range(0, 0xffffffff).Draw(
t, "lockTime",
),
CloseeScript: RandDeliveryAddress(t),
CloserScript: RandDeliveryAddress(t),
ExtraData: RandExtraOpaqueData(t, nil),
}
includeCloserNoClosee := rapid.Bool().Draw(t, "includeCloserNoClosee")
includeNoCloserClosee := rapid.Bool().Draw(t, "includeNoCloserClosee")
includeCloserAndClosee := rapid.Bool().Draw(t, "includeCloserAndClosee")
// Ensure at least one signature is present.
if !includeCloserNoClosee && !includeNoCloserClosee &&
!includeCloserAndClosee {
// If all are false, enable at least one randomly.
choice := rapid.IntRange(0, 2).Draw(t, "sigChoice")
switch choice {
case 0:
includeCloserNoClosee = true
case 1:
includeNoCloserClosee = true
case 2:
includeCloserAndClosee = true
}
}
if includeCloserNoClosee {
sig := RandSignature(t)
msg.CloserNoClosee = tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType1, Sig](sig),
)
}
if includeNoCloserClosee {
sig := RandSignature(t)
msg.NoCloserClosee = tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType2, Sig](sig),
)
}
if includeCloserAndClosee {
sig := RandSignature(t)
msg.CloserAndClosee = tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType3, Sig](sig),
)
}
return msg
}
// A compile time check to ensure ClosingSig implements the lnwire.TestMessage
// interface.
var _ TestMessage = (*ClosingSig)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (c *ClosingSig) RandTestMessage(t *rapid.T) Message {
msg := &ClosingSig{
ChannelID: RandChannelID(t),
CloseeScript: RandDeliveryAddress(t),
CloserScript: RandDeliveryAddress(t),
ExtraData: RandExtraOpaqueData(t, nil),
}
includeCloserNoClosee := rapid.Bool().Draw(t, "includeCloserNoClosee")
includeNoCloserClosee := rapid.Bool().Draw(t, "includeNoCloserClosee")
includeCloserAndClosee := rapid.Bool().Draw(t, "includeCloserAndClosee")
// Ensure at least one signature is present.
if !includeCloserNoClosee && !includeNoCloserClosee &&
!includeCloserAndClosee {
// If all are false, enable at least one randomly.
choice := rapid.IntRange(0, 2).Draw(t, "sigChoice")
switch choice {
case 0:
includeCloserNoClosee = true
case 1:
includeNoCloserClosee = true
case 2:
includeCloserAndClosee = true
}
}
if includeCloserNoClosee {
sig := RandSignature(t)
msg.CloserNoClosee = tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType1, Sig](sig),
)
}
if includeNoCloserClosee {
sig := RandSignature(t)
msg.NoCloserClosee = tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType2, Sig](sig),
)
}
if includeCloserAndClosee {
sig := RandSignature(t)
msg.CloserAndClosee = tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType3, Sig](sig),
)
}
return msg
}
// A compile time check to ensure ClosingSigned implements the
// lnwire.TestMessage interface.
var _ TestMessage = (*ClosingSigned)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (c *ClosingSigned) RandTestMessage(t *rapid.T) Message {
// Generate a random boolean to decide whether to include CommitSig or
// PartialSig Since they're mutually exclusive, when one is populated,
// the other must be blank.
usePartialSig := rapid.Bool().Draw(t, "usePartialSig")
msg := &ClosingSigned{
ChannelID: RandChannelID(t),
FeeSatoshis: btcutil.Amount(
rapid.Int64Range(0, 1000000).Draw(t, "feeSatoshis"),
),
ExtraData: RandExtraOpaqueData(t, nil),
}
if usePartialSig {
sigBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(
t, "sigScalar",
)
var s btcec.ModNScalar
_ = s.SetByteSlice(sigBytes)
msg.PartialSig = SomePartialSig(NewPartialSig(s))
msg.Signature = Sig{}
} else {
msg.Signature = RandSignature(t)
}
return msg
}
// A compile time check to ensure CommitSig implements the lnwire.TestMessage
// interface.
var _ TestMessage = (*CommitSig)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (c *CommitSig) RandTestMessage(t *rapid.T) Message {
cr, _ := RandCustomRecords(t, nil, true)
sig := &CommitSig{
ChanID: RandChannelID(t),
CommitSig: RandSignature(t),
CustomRecords: cr,
}
numHtlcSigs := rapid.IntRange(0, 20).Draw(t, "numHtlcSigs")
htlcSigs := make([]Sig, numHtlcSigs)
for i := 0; i < numHtlcSigs; i++ {
htlcSigs[i] = RandSignature(t)
}
if len(htlcSigs) > 0 {
sig.HtlcSigs = htlcSigs
}
includePartialSig := rapid.Bool().Draw(t, "includePartialSig")
if includePartialSig {
sigWithNonce := RandPartialSigWithNonce(t)
sig.PartialSig = MaybePartialSigWithNonce(sigWithNonce)
}
return sig
}
// A compile time check to ensure Custom implements the lnwire.TestMessage
// interface.
var _ TestMessage = (*Custom)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (c *Custom) RandTestMessage(t *rapid.T) Message {
msgType := MessageType(
rapid.IntRange(int(CustomTypeStart), 65535).Draw(
t, "customMsgType",
),
)
dataLen := rapid.IntRange(0, 1000).Draw(t, "customDataLength")
data := rapid.SliceOfN(rapid.Byte(), dataLen, dataLen).Draw(
t, "customData",
)
msg, err := NewCustom(msgType, data)
if err != nil {
panic(fmt.Sprintf("Error creating custom message: %v", err))
}
return msg
}
// A compile time check to ensure DynAck implements the lnwire.TestMessage
// interface.
var _ TestMessage = (*DynAck)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (da *DynAck) RandTestMessage(t *rapid.T) Message {
msg := &DynAck{
ChanID: RandChannelID(t),
ExtraData: RandExtraOpaqueData(t, nil),
}
includeLocalNonce := rapid.Bool().Draw(t, "includeLocalNonce")
if includeLocalNonce {
msg.LocalNonce = fn.Some(RandMusig2Nonce(t))
}
return msg
}
// A compile time check to ensure DynPropose implements the lnwire.TestMessage
// interface.
var _ TestMessage = (*DynPropose)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (dp *DynPropose) RandTestMessage(t *rapid.T) Message {
msg := &DynPropose{
ChanID: RandChannelID(t),
Initiator: rapid.Bool().Draw(t, "initiator"),
ExtraData: RandExtraOpaqueData(t, nil),
}
// Randomly decide which optional fields to include
includeDustLimit := rapid.Bool().Draw(t, "includeDustLimit")
includeMaxValueInFlight := rapid.Bool().Draw(
t, "includeMaxValueInFlight",
)
includeChannelReserve := rapid.Bool().Draw(t, "includeChannelReserve")
includeCsvDelay := rapid.Bool().Draw(t, "includeCsvDelay")
includeMaxAcceptedHTLCs := rapid.Bool().Draw(
t, "includeMaxAcceptedHTLCs",
)
includeFundingKey := rapid.Bool().Draw(t, "includeFundingKey")
includeChannelType := rapid.Bool().Draw(t, "includeChannelType")
includeKickoffFeerate := rapid.Bool().Draw(t, "includeKickoffFeerate")
// Generate random values for each included field
if includeDustLimit {
dl := btcutil.Amount(rapid.Uint32().Draw(t, "dustLimit"))
msg.DustLimit = fn.Some(dl)
}
if includeMaxValueInFlight {
mvif := MilliSatoshi(rapid.Uint64().Draw(t, "maxValueInFlight"))
msg.MaxValueInFlight = fn.Some(mvif)
}
if includeChannelReserve {
cr := btcutil.Amount(rapid.Uint32().Draw(t, "channelReserve"))
msg.ChannelReserve = fn.Some(cr)
}
if includeCsvDelay {
cd := rapid.Uint16().Draw(t, "csvDelay")
msg.CsvDelay = fn.Some(cd)
}
if includeMaxAcceptedHTLCs {
mah := rapid.Uint16().Draw(t, "maxAcceptedHTLCs")
msg.MaxAcceptedHTLCs = fn.Some(mah)
}
if includeFundingKey {
msg.FundingKey = fn.Some(*RandPubKey(t))
}
if includeChannelType {
msg.ChannelType = fn.Some(*RandChannelType(t))
}
if includeKickoffFeerate {
kf := chainfee.SatPerKWeight(rapid.Uint32().Draw(
t, "kickoffFeerate"),
)
msg.KickoffFeerate = fn.Some(kf)
}
return msg
}
// A compile time check to ensure DynReject implements the lnwire.TestMessage
// interface.
var _ TestMessage = (*DynReject)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (dr *DynReject) RandTestMessage(t *rapid.T) Message {
featureVec := NewRawFeatureVector()
numFeatures := rapid.IntRange(0, 8).Draw(t, "numRejections")
for i := 0; i < numFeatures; i++ {
bit := FeatureBit(
rapid.IntRange(0, 31).Draw(
t, fmt.Sprintf("rejectionBit-%d", i),
),
)
featureVec.Set(bit)
}
var extraData ExtraOpaqueData
randData := RandExtraOpaqueData(t, nil)
if len(randData) > 0 {
extraData = randData
}
return &DynReject{
ChanID: RandChannelID(t),
UpdateRejections: *featureVec,
ExtraData: extraData,
}
}
// A compile time check to ensure FundingCreated implements the TestMessage
// interface.
var _ TestMessage = (*FundingCreated)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (f *FundingCreated) RandTestMessage(t *rapid.T) Message {
var pendingChanID [32]byte
pendingChanIDBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(
t, "pendingChanID",
)
copy(pendingChanID[:], pendingChanIDBytes)
includePartialSig := rapid.Bool().Draw(t, "includePartialSig")
var partialSig OptPartialSigWithNonceTLV
var commitSig Sig
if includePartialSig {
sigWithNonce := RandPartialSigWithNonce(t)
partialSig = MaybePartialSigWithNonce(sigWithNonce)
// When using partial sig, CommitSig should be empty/blank.
commitSig = Sig{}
} else {
commitSig = RandSignature(t)
}
return &FundingCreated{
PendingChannelID: pendingChanID,
FundingPoint: RandOutPoint(t),
CommitSig: commitSig,
PartialSig: partialSig,
ExtraData: RandExtraOpaqueData(t, nil),
}
}
// A compile time check to ensure FundingSigned implements the
// lnwire.TestMessage interface.
var _ TestMessage = (*FundingSigned)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (f *FundingSigned) RandTestMessage(t *rapid.T) Message {
usePartialSig := rapid.Bool().Draw(t, "usePartialSig")
msg := &FundingSigned{
ChanID: RandChannelID(t),
ExtraData: RandExtraOpaqueData(t, nil),
}
if usePartialSig {
sigWithNonce := RandPartialSigWithNonce(t)
msg.PartialSig = MaybePartialSigWithNonce(sigWithNonce)
msg.CommitSig = Sig{}
} else {
msg.CommitSig = RandSignature(t)
}
return msg
}
// A compile time check to ensure GossipTimestampRange implements the
// lnwire.TestMessage interface.
var _ TestMessage = (*GossipTimestampRange)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (g *GossipTimestampRange) RandTestMessage(t *rapid.T) Message {
var chainHash chainhash.Hash
hashBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "chainHash")
copy(chainHash[:], hashBytes)
msg := &GossipTimestampRange{
ChainHash: chainHash,
FirstTimestamp: rapid.Uint32().Draw(t, "firstTimestamp"),
TimestampRange: rapid.Uint32().Draw(t, "timestampRange"),
ExtraData: RandExtraOpaqueData(t, nil),
}
includeFirstBlockHeight := rapid.Bool().Draw(
t, "includeFirstBlockHeight",
)
includeBlockRange := rapid.Bool().Draw(t, "includeBlockRange")
if includeFirstBlockHeight {
height := rapid.Uint32().Draw(t, "firstBlockHeight")
msg.FirstBlockHeight = tlv.SomeRecordT(
tlv.RecordT[tlv.TlvType2, uint32]{Val: height},
)
}
if includeBlockRange {
blockRange := rapid.Uint32().Draw(t, "blockRange")
msg.BlockRange = tlv.SomeRecordT(
tlv.RecordT[tlv.TlvType4, uint32]{Val: blockRange},
)
}
return msg
}
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (msg *Init) RandTestMessage(t *rapid.T) Message {
global := NewRawFeatureVector()
local := NewRawFeatureVector()
numGlobalFeatures := rapid.IntRange(0, 20).Draw(t, "numGlobalFeatures")
for i := 0; i < numGlobalFeatures; i++ {
bit := FeatureBit(
rapid.IntRange(0, 100).Draw(
t, fmt.Sprintf("globalFeatureBit%d", i),
),
)
global.Set(bit)
}
numLocalFeatures := rapid.IntRange(0, 20).Draw(t, "numLocalFeatures")
for i := 0; i < numLocalFeatures; i++ {
bit := FeatureBit(
rapid.IntRange(0, 100).Draw(
t, fmt.Sprintf("localFeatureBit%d", i),
),
)
local.Set(bit)
}
return NewInitMessage(global, local)
}
// A compile time check to ensure KickoffSig implements the lnwire.TestMessage
// interface.
var _ TestMessage = (*KickoffSig)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (ks *KickoffSig) RandTestMessage(t *rapid.T) Message {
return &KickoffSig{
ChanID: RandChannelID(t),
Signature: RandSignature(t),
ExtraData: RandExtraOpaqueData(t, nil),
}
}
// A compile time check to ensure NodeAnnouncement implements the
// lnwire.TestMessage interface.
var _ TestMessage = (*NodeAnnouncement)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (a *NodeAnnouncement) RandTestMessage(t *rapid.T) Message {
// Generate random compressed public key for node ID
pubKey := RandPubKey(t)
var nodeID [33]byte
copy(nodeID[:], pubKey.SerializeCompressed())
// Generate random RGB color
rgbColor := color.RGBA{
R: uint8(rapid.IntRange(0, 255).Draw(t, "rgbR")),
G: uint8(rapid.IntRange(0, 255).Draw(t, "rgbG")),
B: uint8(rapid.IntRange(0, 255).Draw(t, "rgbB")),
}
return &NodeAnnouncement{
Signature: RandSignature(t),
Features: RandFeatureVector(t),
Timestamp: uint32(rapid.IntRange(0, 0x7FFFFFFF).Draw(
t, "timestamp"),
),
NodeID: nodeID,
RGBColor: rgbColor,
Alias: RandNodeAlias(t),
Addresses: RandNetAddrs(t),
ExtraOpaqueData: RandExtraOpaqueData(t, nil),
}
}
// A compile time check to ensure OpenChannel implements the TestMessage
// interface.
var _ TestMessage = (*OpenChannel)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (o *OpenChannel) RandTestMessage(t *rapid.T) Message {
chainHash := RandChainHash(t)
var hash chainhash.Hash
copy(hash[:], chainHash[:])
var pendingChanID [32]byte
pendingChanIDBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(
t, "pendingChanID",
)
copy(pendingChanID[:], pendingChanIDBytes)
includeChannelType := rapid.Bool().Draw(t, "includeChannelType")
includeLeaseExpiry := rapid.Bool().Draw(t, "includeLeaseExpiry")
includeLocalNonce := rapid.Bool().Draw(t, "includeLocalNonce")
var channelFlags FundingFlag
if rapid.Bool().Draw(t, "announceChannel") {
channelFlags |= FFAnnounceChannel
}
var localNonce OptMusig2NonceTLV
if includeLocalNonce {
nonce := RandMusig2Nonce(t)
localNonce = tlv.SomeRecordT(
tlv.NewRecordT[NonceRecordTypeT, Musig2Nonce](nonce),
)
}
var channelType *ChannelType
if includeChannelType {
channelType = RandChannelType(t)
}
var leaseExpiry *LeaseExpiry
if includeLeaseExpiry {
leaseExpiry = RandLeaseExpiry(t)
}
return &OpenChannel{
ChainHash: hash,
PendingChannelID: pendingChanID,
FundingAmount: btcutil.Amount(
rapid.IntRange(5000, 10000000).Draw(t, "fundingAmount"),
),
PushAmount: MilliSatoshi(
rapid.IntRange(0, 1000000).Draw(t, "pushAmount"),
),
DustLimit: btcutil.Amount(
rapid.IntRange(100, 1000).Draw(t, "dustLimit"),
),
MaxValueInFlight: MilliSatoshi(
rapid.IntRange(10000, 1000000).Draw(
t, "maxValueInFlight",
),
),
ChannelReserve: btcutil.Amount(
rapid.IntRange(1000, 10000).Draw(t, "channelReserve"),
),
HtlcMinimum: MilliSatoshi(
rapid.IntRange(1, 1000).Draw(t, "htlcMinimum"),
),
FeePerKiloWeight: uint32(
rapid.IntRange(250, 10000).Draw(t, "feePerKw"),
),
CsvDelay: uint16(
rapid.IntRange(144, 1000).Draw(t, "csvDelay"),
),
MaxAcceptedHTLCs: uint16(
rapid.IntRange(10, 500).Draw(t, "maxAcceptedHTLCs"),
),
FundingKey: RandPubKey(t),
RevocationPoint: RandPubKey(t),
PaymentPoint: RandPubKey(t),
DelayedPaymentPoint: RandPubKey(t),
HtlcPoint: RandPubKey(t),
FirstCommitmentPoint: RandPubKey(t),
ChannelFlags: channelFlags,
UpfrontShutdownScript: RandDeliveryAddress(t),
ChannelType: channelType,
LeaseExpiry: leaseExpiry,
LocalNonce: localNonce,
ExtraData: RandExtraOpaqueData(t, nil),
}
}
// A compile time check to ensure Ping implements the lnwire.TestMessage
// interface.
var _ TestMessage = (*Ping)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (p *Ping) RandTestMessage(t *rapid.T) Message {
numPongBytes := uint16(rapid.IntRange(0, int(MaxPongBytes)).Draw(
t, "numPongBytes"),
)
// Generate padding bytes (but keeping within allowed message size)
// MaxMsgBody - 2 (for NumPongBytes) - 2 (for padding length)
maxPaddingLen := MaxMsgBody - 4
paddingLen := rapid.IntRange(0, maxPaddingLen).Draw(
t, "paddingLen",
)
padding := make(PingPayload, paddingLen)
// Fill padding with random bytes
for i := 0; i < paddingLen; i++ {
padding[i] = byte(rapid.IntRange(0, 255).Draw(
t, fmt.Sprintf("paddingByte%d", i)),
)
}
return &Ping{
NumPongBytes: numPongBytes,
PaddingBytes: padding,
}
}
// A compile time check to ensure Pong implements the lnwire.TestMessage
// interface.
var _ TestMessage = (*Pong)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (p *Pong) RandTestMessage(t *rapid.T) Message {
payloadLen := rapid.IntRange(0, 1000).Draw(t, "pongPayloadLength")
payload := rapid.SliceOfN(rapid.Byte(), payloadLen, payloadLen).Draw(
t, "pongPayload",
)
return &Pong{
PongBytes: payload,
}
}
// A compile time check to ensure QueryChannelRange implements the
// lnwire.TestMessage interface.
var _ TestMessage = (*QueryChannelRange)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (q *QueryChannelRange) RandTestMessage(t *rapid.T) Message {
msg := &QueryChannelRange{
FirstBlockHeight: uint32(rapid.IntRange(0, 1000000).Draw(
t, "firstBlockHeight"),
),
NumBlocks: uint32(rapid.IntRange(1, 10000).Draw(
t, "numBlocks"),
),
ExtraData: RandExtraOpaqueData(t, nil),
}
// Generate chain hash
chainHash := RandChainHash(t)
var chainHashObj chainhash.Hash
copy(chainHashObj[:], chainHash[:])
msg.ChainHash = chainHashObj
// Randomly include QueryOptions
if rapid.Bool().Draw(t, "includeQueryOptions") {
queryOptions := &QueryOptions{}
*queryOptions = QueryOptions(*RandFeatureVector(t))
msg.QueryOptions = queryOptions
}
return msg
}
// A compile time check to ensure QueryShortChanIDs implements the
// lnwire.TestMessage interface.
var _ TestMessage = (*QueryShortChanIDs)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (q *QueryShortChanIDs) RandTestMessage(t *rapid.T) Message {
var chainHash chainhash.Hash
hashBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "chainHash")
copy(chainHash[:], hashBytes)
encodingType := EncodingSortedPlain
if rapid.Bool().Draw(t, "useZlibEncoding") {
encodingType = EncodingSortedZlib
}
msg := &QueryShortChanIDs{
ChainHash: chainHash,
EncodingType: encodingType,
ExtraData: RandExtraOpaqueData(t, nil),
noSort: false,
}
numIDs := rapid.IntRange(2, 20).Draw(t, "numShortChanIDs")
// Generate sorted short channel IDs.
shortChanIDs := make([]ShortChannelID, numIDs)
for i := 0; i < numIDs; i++ {
shortChanIDs[i] = RandShortChannelID(t)
// Ensure they're properly sorted.
if i > 0 && shortChanIDs[i].ToUint64() <=
shortChanIDs[i-1].ToUint64() {
// Ensure this ID is larger than the previous one.
shortChanIDs[i] = NewShortChanIDFromInt(
shortChanIDs[i-1].ToUint64() + 1,
)
}
}
msg.ShortChanIDs = shortChanIDs
return msg
}
// A compile time check to ensure ReplyChannelRange implements the
// lnwire.TestMessage interface.
var _ TestMessage = (*ReplyChannelRange)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (c *ReplyChannelRange) RandTestMessage(t *rapid.T) Message {
msg := &ReplyChannelRange{
FirstBlockHeight: uint32(rapid.IntRange(0, 1000000).Draw(
t, "firstBlockHeight"),
),
NumBlocks: uint32(rapid.IntRange(1, 10000).Draw(
t, "numBlocks"),
),
Complete: uint8(rapid.IntRange(0, 1).Draw(t, "complete")),
EncodingType: QueryEncoding(
rapid.IntRange(0, 1).Draw(t, "encodingType"),
),
ExtraData: RandExtraOpaqueData(t, nil),
}
msg.ChainHash = RandChainHash(t)
numShortChanIDs := rapid.IntRange(0, 20).Draw(t, "numShortChanIDs")
if numShortChanIDs == 0 {
return msg
}
scidSet := fn.NewSet[ShortChannelID]()
scids := make([]ShortChannelID, numShortChanIDs)
for i := 0; i < numShortChanIDs; i++ {
scid := RandShortChannelID(t)
for scidSet.Contains(scid) {
scid = RandShortChannelID(t)
}
scids[i] = scid
scidSet.Add(scid)
}
// Make sure there're no duplicates.
msg.ShortChanIDs = scids
if rapid.Bool().Draw(t, "includeTimestamps") && numShortChanIDs > 0 {
msg.Timestamps = make(Timestamps, numShortChanIDs)
for i := 0; i < numShortChanIDs; i++ {
msg.Timestamps[i] = ChanUpdateTimestamps{
Timestamp1: uint32(rapid.IntRange(0, math.MaxInt32).Draw(t, fmt.Sprintf("timestamp-1-%d", i))), //nolint:ll
Timestamp2: uint32(rapid.IntRange(0, math.MaxInt32).Draw(t, fmt.Sprintf("timestamp-2-%d", i))), //nolint:ll
}
}
}
return msg
}
// A compile time check to ensure ReplyShortChanIDsEnd implements the
// lnwire.TestMessage interface.
var _ TestMessage = (*ReplyShortChanIDsEnd)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (c *ReplyShortChanIDsEnd) RandTestMessage(t *rapid.T) Message {
var chainHash chainhash.Hash
hashBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "chainHash")
copy(chainHash[:], hashBytes)
complete := uint8(rapid.IntRange(0, 1).Draw(t, "complete"))
return &ReplyShortChanIDsEnd{
ChainHash: chainHash,
Complete: complete,
ExtraData: RandExtraOpaqueData(t, nil),
}
}
// RandTestMessage returns a RevokeAndAck message populated with random data.
//
// This is part of the TestMessage interface.
func (c *RevokeAndAck) RandTestMessage(t *rapid.T) Message {
msg := NewRevokeAndAck()
var chanID ChannelID
bytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "channelID")
copy(chanID[:], bytes)
msg.ChanID = chanID
revBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "revocation")
copy(msg.Revocation[:], revBytes)
msg.NextRevocationKey = RandPubKey(t)
if rapid.Bool().Draw(t, "includeLocalNonce") {
var nonce Musig2Nonce
nonceBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(
t, "nonce",
)
copy(nonce[:], nonceBytes)
msg.LocalNonce = tlv.SomeRecordT(
tlv.NewRecordT[NonceRecordTypeT, Musig2Nonce](nonce),
)
}
return msg
}
// A compile-time check to ensure Shutdown implements the lnwire.TestMessage
// interface.
var _ TestMessage = (*Shutdown)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (s *Shutdown) RandTestMessage(t *rapid.T) Message {
// Generate random delivery address
// First decide the address type (P2PKH, P2SH, P2WPKH, P2WSH, P2TR)
addrType := rapid.IntRange(0, 4).Draw(t, "addrType")
// Generate random address length based on type
var addrLen int
switch addrType {
// P2PKH
case 0:
addrLen = 25
// P2SH
case 1:
addrLen = 23
// P2WPKH
case 2:
addrLen = 22
// P2WSH
case 3:
addrLen = 34
// P2TR
case 4:
addrLen = 34
}
addr := rapid.SliceOfN(rapid.Byte(), addrLen, addrLen).Draw(
t, "address",
)
// Randomly decide whether to include a shutdown nonce
includeNonce := rapid.Bool().Draw(t, "includeNonce")
var shutdownNonce ShutdownNonceTLV
if includeNonce {
shutdownNonce = SomeShutdownNonce(RandMusig2Nonce(t))
}
cr, _ := RandCustomRecords(t, nil, true)
return &Shutdown{
ChannelID: RandChannelID(t),
Address: addr,
ShutdownNonce: shutdownNonce,
CustomRecords: cr,
}
}
// A compile time check to ensure Stfu implements the lnwire.TestMessage
// interface.
var _ TestMessage = (*Stfu)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (s *Stfu) RandTestMessage(t *rapid.T) Message {
m := &Stfu{
ChanID: RandChannelID(t),
Initiator: rapid.Bool().Draw(t, "initiator"),
}
extraData := RandExtraOpaqueData(t, nil)
if len(extraData) > 0 {
m.ExtraData = extraData
}
return m
}
// A compile time check to ensure UpdateAddHTLC implements the
// lnwire.TestMessage interface.
var _ TestMessage = (*UpdateAddHTLC)(nil)
// RandTestMessage returns an UpdateAddHTLC message populated with random data.
//
// This is part of the TestMessage interface.
func (c *UpdateAddHTLC) RandTestMessage(t *rapid.T) Message {
msg := &UpdateAddHTLC{
ChanID: RandChannelID(t),
ID: rapid.Uint64().Draw(t, "id"),
Amount: MilliSatoshi(rapid.Uint64().Draw(t, "amount")),
Expiry: rapid.Uint32().Draw(t, "expiry"),
}
hashBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "paymentHash")
copy(msg.PaymentHash[:], hashBytes)
onionBytes := rapid.SliceOfN(
rapid.Byte(), OnionPacketSize, OnionPacketSize,
).Draw(t, "onionBlob")
copy(msg.OnionBlob[:], onionBytes)
numRecords := rapid.IntRange(0, 5).Draw(t, "numRecords")
if numRecords > 0 {
msg.CustomRecords, _ = RandCustomRecords(t, nil, true)
}
// 50/50 chance to add a blinding point
if rapid.Bool().Draw(t, "includeBlindingPoint") {
pubKey := RandPubKey(t)
msg.BlindingPoint = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[BlindingPointTlvType](pubKey),
)
}
return msg
}
// A compile time check to ensure UpdateFailHTLC implements the TestMessage
// interface.
var _ TestMessage = (*UpdateFailHTLC)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (c *UpdateFailHTLC) RandTestMessage(t *rapid.T) Message {
return &UpdateFailHTLC{
ChanID: RandChannelID(t),
ID: rapid.Uint64().Draw(t, "id"),
Reason: RandOpaqueReason(t),
ExtraData: RandExtraOpaqueData(t, nil),
}
}
// A compile time check to ensure UpdateFailMalformedHTLC implements the
// TestMessage interface.
var _ TestMessage = (*UpdateFailMalformedHTLC)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (c *UpdateFailMalformedHTLC) RandTestMessage(t *rapid.T) Message {
return &UpdateFailMalformedHTLC{
ChanID: RandChannelID(t),
ID: rapid.Uint64().Draw(t, "id"),
ShaOnionBlob: RandSHA256Hash(t),
FailureCode: RandFailCode(t),
ExtraData: RandExtraOpaqueData(t, nil),
}
}
// A compile time check to ensure UpdateFee implements the TestMessage
// interface.
var _ TestMessage = (*UpdateFee)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (c *UpdateFee) RandTestMessage(t *rapid.T) Message {
return &UpdateFee{
ChanID: RandChannelID(t),
FeePerKw: uint32(rapid.IntRange(1, 10000).Draw(t, "feePerKw")),
ExtraData: RandExtraOpaqueData(t, nil),
}
}
// A compile time check to ensure UpdateFulfillHTLC implements the TestMessage
// interface.
var _ TestMessage = (*UpdateFulfillHTLC)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (c *UpdateFulfillHTLC) RandTestMessage(t *rapid.T) Message {
msg := &UpdateFulfillHTLC{
ChanID: RandChannelID(t),
ID: rapid.Uint64().Draw(t, "id"),
PaymentPreimage: RandPaymentPreimage(t),
}
cr, ignoreRecords := RandCustomRecords(t, nil, true)
msg.CustomRecords = cr
randData := RandExtraOpaqueData(t, ignoreRecords)
if len(randData) > 0 {
msg.ExtraData = randData
}
return msg
}
// A compile time check to ensure Warning implements the lnwire.TestMessage
// interface.
var _ TestMessage = (*Warning)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (c *Warning) RandTestMessage(t *rapid.T) Message {
msg := &Warning{
ChanID: RandChannelID(t),
}
useASCII := rapid.Bool().Draw(t, "useASCII")
if useASCII {
length := rapid.IntRange(1, 100).Draw(t, "warningDataLength")
data := make([]byte, length)
for i := 0; i < length; i++ {
data[i] = byte(
rapid.IntRange(32, 126).Draw(
t, fmt.Sprintf("warningDataByte-%d", i),
),
)
}
msg.Data = data
} else {
length := rapid.IntRange(1, 100).Draw(t, "warningDataLength")
msg.Data = rapid.SliceOfN(rapid.Byte(), length, length).Draw(
t, "warningData",
)
}
return msg
}
// A compile time check to ensure Error implements the lnwire.TestMessage
// interface.
var _ TestMessage = (*Error)(nil)
// RandTestMessage populates the message with random data suitable for testing.
// It uses the rapid testing framework to generate random values.
//
// This is part of the TestMessage interface.
func (c *Error) RandTestMessage(t *rapid.T) Message {
msg := &Error{
ChanID: RandChannelID(t),
}
useASCII := rapid.Bool().Draw(t, "useASCII")
if useASCII {
length := rapid.IntRange(1, 100).Draw(t, "errorDataLength")
data := make([]byte, length)
for i := 0; i < length; i++ {
data[i] = byte(
rapid.IntRange(32, 126).Draw(
t, fmt.Sprintf("errorDataByte-%d", i),
),
)
}
msg.Data = data
} else {
// Generate random binary data
length := rapid.IntRange(1, 100).Draw(t, "errorDataLength")
msg.Data = rapid.SliceOfN(
rapid.Byte(), length, length,
).Draw(t, "errorData")
}
return msg
}
package lnwire
import (
"crypto/sha256"
"fmt"
"net"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/stretchr/testify/require"
"pgregory.net/rapid"
)
// RandChannelUpdate generates a random ChannelUpdate message using rapid's
// generators.
func RandPartialSig(t *rapid.T) *PartialSig {
// Generate random private key bytes
sigBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "privKeyBytes")
var s btcec.ModNScalar
s.SetByteSlice(sigBytes)
return &PartialSig{
Sig: s,
}
}
// RandPartialSigWithNonce generates a random PartialSigWithNonce using rapid
// generators.
func RandPartialSigWithNonce(t *rapid.T) *PartialSigWithNonce {
sigLen := rapid.IntRange(1, 65).Draw(t, "partialSigLen")
sigBytes := rapid.SliceOfN(
rapid.Byte(), sigLen, sigLen,
).Draw(t, "partialSig")
sigScalar := new(btcec.ModNScalar)
sigScalar.SetByteSlice(sigBytes)
return NewPartialSigWithNonce(
RandMusig2Nonce(t), *sigScalar,
)
}
// RandPubKey generates a random public key using rapid's generators.
func RandPubKey(t *rapid.T) *btcec.PublicKey {
privKeyBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(
t, "privKeyBytes",
)
_, pub := btcec.PrivKeyFromBytes(privKeyBytes)
return pub
}
// RandChannelID generates a random channel ID.
func RandChannelID(t *rapid.T) ChannelID {
var c ChannelID
bytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "channelID")
copy(c[:], bytes)
return c
}
// RandShortChannelID generates a random short channel ID.
func RandShortChannelID(t *rapid.T) ShortChannelID {
return NewShortChanIDFromInt(
uint64(rapid.IntRange(1, 100000).Draw(t, "shortChanID")),
)
}
// RandFeatureVector generates a random feature vector.
func RandFeatureVector(t *rapid.T) *RawFeatureVector {
featureVec := NewRawFeatureVector()
// Add a random number of random feature bits
numFeatures := rapid.IntRange(0, 20).Draw(t, "numFeatures")
for i := 0; i < numFeatures; i++ {
bit := FeatureBit(rapid.IntRange(0, 100).Draw(
t, fmt.Sprintf("featureBit-%d", i)),
)
featureVec.Set(bit)
}
return featureVec
}
// RandSignature generates a signature for testing.
func RandSignature(t *rapid.T) Sig {
testRScalar := new(btcec.ModNScalar)
testSScalar := new(btcec.ModNScalar)
// Generate random bytes for R and S
rBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "rBytes")
sBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "sBytes")
_ = testRScalar.SetByteSlice(rBytes)
_ = testSScalar.SetByteSlice(sBytes)
testSig := ecdsa.NewSignature(testRScalar, testSScalar)
sig, err := NewSigFromSignature(testSig)
if err != nil {
panic(fmt.Sprintf("unable to create signature: %v", err))
}
return sig
}
// RandPaymentHash generates a random payment hash.
func RandPaymentHash(t *rapid.T) [32]byte {
var hash [32]byte
bytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "paymentHash")
copy(hash[:], bytes)
return hash
}
// RandPaymentPreimage generates a random payment preimage.
func RandPaymentPreimage(t *rapid.T) [32]byte {
var preimage [32]byte
bytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "preimage")
copy(preimage[:], bytes)
return preimage
}
// RandChainHash generates a random chain hash.
func RandChainHash(t *rapid.T) chainhash.Hash {
var hash [32]byte
bytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "chainHash")
copy(hash[:], bytes)
return hash
}
// RandNodeAlias generates a random node alias.
func RandNodeAlias(t *rapid.T) NodeAlias {
var alias NodeAlias
aliasLength := rapid.IntRange(0, 32).Draw(t, "aliasLength")
aliasBytes := rapid.StringN(
0, aliasLength, aliasLength,
).Draw(t, "alias")
copy(alias[:], aliasBytes)
return alias
}
// RandNetAddrs generates random network addresses.
func RandNetAddrs(t *rapid.T) []net.Addr {
numAddresses := rapid.IntRange(0, 5).Draw(t, "numAddresses")
if numAddresses == 0 {
return nil
}
addresses := make([]net.Addr, numAddresses)
for i := 0; i < numAddresses; i++ {
addressType := rapid.IntRange(0, 1).Draw(
t, fmt.Sprintf("addressType-%d", i),
)
switch addressType {
// IPv4.
case 0:
ipBytes := rapid.SliceOfN(rapid.Byte(), 4, 4).Draw(
t, fmt.Sprintf("ipv4-%d", i),
)
port := rapid.IntRange(1, 65535).Draw(
t, fmt.Sprintf("port-%d", i),
)
addresses[i] = &net.TCPAddr{
IP: ipBytes,
Port: port,
}
// IPv6.
case 1:
ipBytes := rapid.SliceOfN(rapid.Byte(), 16, 16).Draw(
t, fmt.Sprintf("ipv6-%d", i),
)
port := rapid.IntRange(1, 65535).Draw(
t, fmt.Sprintf("port-%d", i),
)
addresses[i] = &net.TCPAddr{
IP: ipBytes,
Port: port,
}
}
}
return addresses
}
// RandCustomRecords generates random custom TLV records.
func RandCustomRecords(t *rapid.T,
ignoreRecords fn.Set[uint64],
custom bool) (CustomRecords, fn.Set[uint64]) {
numRecords := rapid.IntRange(0, 5).Draw(t, "numCustomRecords")
customRecords := make(CustomRecords)
if numRecords == 0 {
return nil, nil
}
rangeStart := 0
rangeStop := int(CustomTypeStart)
if custom {
rangeStart = 70_000
rangeStop = 100_000
}
ignoreSet := fn.NewSet[uint64]()
for i := 0; i < numRecords; i++ {
recordType := uint64(
rapid.IntRange(rangeStart, rangeStop).
Filter(func(i int) bool {
return !ignoreRecords.Contains(
uint64(i),
)
}).
Draw(
t, fmt.Sprintf("recordType-%d", i),
),
)
recordLen := rapid.IntRange(4, 64).Draw(
t, fmt.Sprintf("recordLen-%d", i),
)
record := rapid.SliceOfN(
rapid.Byte(), recordLen, recordLen,
).Draw(t, fmt.Sprintf("record-%d", i))
customRecords[recordType] = record
ignoreSet.Add(recordType)
}
return customRecords, ignoreSet
}
// RandMusig2Nonce generates a random musig2 nonce.
func RandMusig2Nonce(t *rapid.T) Musig2Nonce {
var nonce Musig2Nonce
bytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "nonce")
copy(nonce[:], bytes)
return nonce
}
// RandExtraOpaqueData generates random extra opaque data.
func RandExtraOpaqueData(t *rapid.T,
ignoreRecords fn.Set[uint64]) ExtraOpaqueData {
// Make some random records.
cRecords, _ := RandCustomRecords(t, ignoreRecords, false)
if cRecords == nil {
return ExtraOpaqueData{}
}
// Encode those records as opaque data.
recordBytes, err := cRecords.Serialize()
require.NoError(t, err)
return ExtraOpaqueData(recordBytes)
}
// RandOpaqueReason generates a random opaque reason for HTLC failures.
func RandOpaqueReason(t *rapid.T) OpaqueReason {
reasonLen := rapid.IntRange(32, 300).Draw(t, "reasonLen")
return rapid.SliceOfN(rapid.Byte(), reasonLen, reasonLen).Draw(
t, "opaqueReason",
)
}
// RandFailCode generates a random HTLC failure code.
func RandFailCode(t *rapid.T) FailCode {
// List of known failure codes to choose from Using only the documented
// codes.
validCodes := []FailCode{
CodeInvalidRealm,
CodeTemporaryNodeFailure,
CodePermanentNodeFailure,
CodeRequiredNodeFeatureMissing,
CodePermanentChannelFailure,
CodeRequiredChannelFeatureMissing,
CodeUnknownNextPeer,
CodeIncorrectOrUnknownPaymentDetails,
CodeIncorrectPaymentAmount,
CodeFinalExpiryTooSoon,
CodeInvalidOnionVersion,
CodeInvalidOnionHmac,
CodeInvalidOnionKey,
CodeTemporaryChannelFailure,
CodeChannelDisabled,
CodeExpiryTooSoon,
CodeMPPTimeout,
CodeInvalidOnionPayload,
CodeFeeInsufficient,
}
// Choose a random code from the list.
idx := rapid.IntRange(0, len(validCodes)-1).Draw(t, "failCodeIndex")
return validCodes[idx]
}
// RandSHA256Hash generates a random SHA256 hash.
func RandSHA256Hash(t *rapid.T) [sha256.Size]byte {
var hash [sha256.Size]byte
bytes := rapid.SliceOfN(rapid.Byte(), sha256.Size, sha256.Size).Draw(
t, "sha256Hash",
)
copy(hash[:], bytes)
return hash
}
// RandDeliveryAddress generates a random delivery address (script).
func RandDeliveryAddress(t *rapid.T) DeliveryAddress {
addrLen := rapid.IntRange(1, 34).Draw(t, "addrLen")
return rapid.SliceOfN(rapid.Byte(), addrLen, addrLen).Draw(
t, "deliveryAddress",
)
}
// RandChannelType generates a random channel type.
func RandChannelType(t *rapid.T) *ChannelType {
vec := RandFeatureVector(t)
chanType := ChannelType(*vec)
return &chanType
}
// RandLeaseExpiry generates a random lease expiry.
func RandLeaseExpiry(t *rapid.T) *LeaseExpiry {
exp := LeaseExpiry(
uint32(rapid.IntRange(1000, 1000000).Draw(t, "leaseExpiry")),
)
return &exp
}
// RandOutPoint generates a random transaction outpoint.
func RandOutPoint(t *rapid.T) wire.OutPoint {
// Generate a random transaction ID
var txid chainhash.Hash
txidBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "txid")
copy(txid[:], txidBytes)
// Generate a random output index
vout := uint32(rapid.IntRange(0, 10).Draw(t, "vout"))
return wire.OutPoint{
Hash: txid,
Index: vout,
}
}
package lnwire
import (
"bytes"
"fmt"
"io"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// TimestampsRecordType is the TLV number of the timestamps TLV record
// in the reply_channel_range message.
TimestampsRecordType tlv.Type = 1
// timestampPairSize is the number of bytes required to encode two
// timestamps. Each timestamp is four bytes.
timestampPairSize = 8
)
// Timestamps is a type representing the timestamps TLV field used in the
// reply_channel_range message to communicate the timestamps info of the updates
// of the SCID list being communicated.
type Timestamps []ChanUpdateTimestamps
// ChanUpdateTimestamps holds the timestamp info of the latest known channel
// updates corresponding to the two sides of a channel.
type ChanUpdateTimestamps struct {
Timestamp1 uint32
Timestamp2 uint32
}
// Record constructs the tlv.Record from the Timestamps.
func (t *Timestamps) Record() tlv.Record {
return tlv.MakeDynamicRecord(
TimestampsRecordType, t, t.encodedLen, timeStampsEncoder,
timeStampsDecoder,
)
}
// encodedLen calculates the length of the encoded Timestamps.
func (t *Timestamps) encodedLen() uint64 {
return uint64(1 + timestampPairSize*(len(*t)))
}
// timeStampsEncoder encodes the Timestamps and writes the encoded bytes to the
// given writer.
func timeStampsEncoder(w io.Writer, val interface{}, _ *[8]byte) error {
if v, ok := val.(*Timestamps); ok {
var buf bytes.Buffer
// Add the encoding byte.
err := WriteQueryEncoding(&buf, EncodingSortedPlain)
if err != nil {
return err
}
// For each timestamp, write 4 byte timestamp of node 1 and the
// 4 byte timestamp of node 2.
for _, timestamps := range *v {
err = WriteUint32(&buf, timestamps.Timestamp1)
if err != nil {
return err
}
err = WriteUint32(&buf, timestamps.Timestamp2)
if err != nil {
return err
}
}
_, err = w.Write(buf.Bytes())
return err
}
return tlv.NewTypeForEncodingErr(val, "lnwire.Timestamps")
}
// timeStampsDecoder attempts to read and reconstruct a Timestamps object from
// the given reader.
func timeStampsDecoder(r io.Reader, val interface{}, _ *[8]byte,
l uint64) error {
if v, ok := val.(*Timestamps); ok {
var encodingByte [1]byte
if _, err := r.Read(encodingByte[:]); err != nil {
return err
}
encoding := QueryEncoding(encodingByte[0])
if encoding != EncodingSortedPlain {
return fmt.Errorf("unsupported encoding: %x", encoding)
}
// The number of timestamps bytes is equal to the passed length
// minus one since the first byte is used for the encoding type.
numTimestampBytes := l - 1
if numTimestampBytes%timestampPairSize != 0 {
return fmt.Errorf("whole number of timestamps not " +
"encoded")
}
numTimestamps := int(numTimestampBytes) / timestampPairSize
timestamps := make(Timestamps, numTimestamps)
for i := 0; i < numTimestamps; i++ {
err := ReadElements(
r, ×tamps[i].Timestamp1,
×tamps[i].Timestamp2,
)
if err != nil {
return err
}
}
*v = timestamps
return nil
}
return tlv.NewTypeForEncodingErr(val, "lnwire.Timestamps")
}
package lnwire
import (
"github.com/lightningnetwork/lnd/tlv"
)
const (
// DeliveryAddrType is the TLV record type for delivery addreses within
// the name space of the OpenChannel and AcceptChannel messages.
DeliveryAddrType = 0
// deliveryAddressMaxSize is the maximum expected size in bytes of a
// DeliveryAddress based on the types of scripts we know.
// Following are the known scripts and their sizes in bytes.
// - pay to witness script hash: 34
// - pay to pubkey hash: 25
// - pay to script hash: 22
// - pay to witness pubkey hash: 22.
deliveryAddressMaxSize = 34
)
// DeliveryAddress is used to communicate the address to which funds from a
// closed channel should be sent. The address can be a p2wsh, p2pkh, p2sh or
// p2wpkh.
type DeliveryAddress []byte
// Record returns a TLV record that can be used to encode the delivery
// address within the ExtraData TLV stream. This was introduced in order to
// allow the OpenChannel/AcceptChannel messages to properly be extended with
// TLV types.
func (d *DeliveryAddress) Record() tlv.Record {
addrBytes := (*[]byte)(d)
return tlv.MakeDynamicRecord(
DeliveryAddrType, addrBytes,
func() uint64 {
return uint64(len(*addrBytes))
},
tlv.EVarBytes, tlv.DVarBytes,
)
}
package lnwire
import (
"io"
"github.com/lightningnetwork/lnd/tlv"
)
const (
FeeRecordType tlv.Type = 55555
)
// Fee represents a fee schedule.
type Fee struct {
BaseFee int32
FeeRate int32
}
// Record returns a TLV record that can be used to encode/decode the fee
// type from a given TLV stream.
func (l *Fee) Record() tlv.Record {
return tlv.MakeStaticRecord(
FeeRecordType, l, 8, feeEncoder, feeDecoder, //nolint:gomnd
)
}
// feeEncoder is a custom TLV encoder for the fee record.
func feeEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
v, ok := val.(*Fee)
if !ok {
return tlv.NewTypeForEncodingErr(val, "lnwire.Fee")
}
if err := tlv.EUint32T(w, uint32(v.BaseFee), buf); err != nil {
return err
}
return tlv.EUint32T(w, uint32(v.FeeRate), buf)
}
// feeDecoder is a custom TLV decoder for the fee record.
func feeDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
v, ok := val.(*Fee)
if !ok {
return tlv.NewTypeForDecodingErr(val, "lnwire.Fee", l, 8)
}
var baseFee, feeRate uint32
if err := tlv.DUint32(r, &baseFee, buf, 4); err != nil {
return err
}
if err := tlv.DUint32(r, &feeRate, buf, 4); err != nil {
return err
}
v.FeeRate = int32(feeRate)
v.BaseFee = int32(baseFee)
return nil
}
package lnwire
import (
"io"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// LeaseExpiryType is the type of the experimental record used to
// communicate the expiration of a channel lease throughout the channel
// funding process.
//
// TODO: Decide on actual TLV type. Custom records start at 2^16.
LeaseExpiryRecordType tlv.Type = 1 << 16
)
// LeaseExpiry represents the absolute expiration height of a channel lease. All
// outputs that pay directly to the channel initiator are locked until this
// height is reached.
type LeaseExpiry uint32
// Record returns a TLV record that can be used to encode/decode the LeaseExpiry
// type from a given TLV stream.
func (l *LeaseExpiry) Record() tlv.Record {
return tlv.MakeStaticRecord(
LeaseExpiryRecordType, l, 4, leaseExpiryEncoder, leaseExpiryDecoder,
)
}
// leaseExpiryEncoder is a custom TLV encoder for the LeaseExpiry record.
func leaseExpiryEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
if v, ok := val.(*LeaseExpiry); ok {
return tlv.EUint32T(w, uint32(*v), buf)
}
return tlv.NewTypeForEncodingErr(val, "lnwire.LeaseExpiry")
}
// leaseExpiryDecoder is a custom TLV decoder for the LeaseExpiry record.
func leaseExpiryDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
if v, ok := val.(*LeaseExpiry); ok {
var leaseExpiry uint32
if err := tlv.DUint32(r, &leaseExpiry, buf, l); err != nil {
return err
}
*v = LeaseExpiry(leaseExpiry)
return nil
}
return tlv.NewTypeForEncodingErr(val, "lnwire.LeaseExpiry")
}
package lnwire
import (
"bytes"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// OnionPacketSize is the size of the serialized Sphinx onion packet
// included in each UpdateAddHTLC message. The breakdown of the onion
// packet is as follows: 1-byte version, 33-byte ephemeral public key
// (for ECDH), 1300-bytes of per-hop data, and a 32-byte HMAC over the
// entire packet.
OnionPacketSize = 1366
// ExperimentalEndorsementType is the TLV type used for a custom
// record that sets an experimental endorsement value.
ExperimentalEndorsementType tlv.Type = 106823
// ExperimentalUnendorsed is the value that the experimental endorsement
// field contains when a htlc is not endorsed.
ExperimentalUnendorsed = 0
// ExperimentalEndorsed is the value that the experimental endorsement
// field contains when a htlc is endorsed. We're using a single byte
// to represent our endorsement value, but limit the value to using
// the first three bits (max value = 00000111). Interpreted as a uint8
// (an alias for byte in go), we can just define this constant as 7.
ExperimentalEndorsed = 7
)
type (
// BlindingPointTlvType is the type for ephemeral pubkeys used in
// route blinding.
BlindingPointTlvType = tlv.TlvType0
// BlindingPointRecord holds an optional blinding point on update add
// htlc.
//nolint:ll
BlindingPointRecord = tlv.OptionalRecordT[BlindingPointTlvType, *btcec.PublicKey]
)
// UpdateAddHTLC is the message sent by Alice to Bob when she wishes to add an
// HTLC to his remote commitment transaction. In addition to information
// detailing the value, the ID, expiry, and the onion blob is also included
// which allows Bob to derive the next hop in the route. The HTLC added by this
// message is to be added to the remote node's "pending" HTLCs. A subsequent
// CommitSig message will move the pending HTLC to the newly created commitment
// transaction, marking them as "staged".
type UpdateAddHTLC struct {
// ChanID is the particular active channel that this UpdateAddHTLC is
// bound to.
ChanID ChannelID
// ID is the identification server for this HTLC. This value is
// explicitly included as it allows nodes to survive single-sided
// restarts. The ID value for this sides starts at zero, and increases
// with each offered HTLC.
ID uint64
// Amount is the amount of millisatoshis this HTLC is worth.
Amount MilliSatoshi
// PaymentHash is the payment hash to be included in the HTLC this
// request creates. The pre-image to this HTLC must be revealed by the
// upstream peer in order to fully settle the HTLC.
PaymentHash [32]byte
// Expiry is the number of blocks after which this HTLC should expire.
// It is the receiver's duty to ensure that the outgoing HTLC has a
// sufficient expiry value to allow her to redeem the incoming HTLC.
Expiry uint32
// OnionBlob is the raw serialized mix header used to route an HTLC in
// a privacy-preserving manner. The mix header is defined currently to
// be parsed as a 4-tuple: (groupElement, routingInfo, headerMAC,
// body). First the receiving node should use the groupElement, and
// its current onion key to derive a shared secret with the source.
// Once the shared secret has been derived, the headerMAC should be
// checked FIRST. Note that the MAC only covers the routingInfo field.
// If the MAC matches, and the shared secret is fresh, then the node
// should strip off a layer of encryption, exposing the next hop to be
// used in the subsequent UpdateAddHTLC message.
OnionBlob [OnionPacketSize]byte
// BlindingPoint is the ephemeral pubkey used to optionally blind the
// next hop for this htlc.
BlindingPoint BlindingPointRecord
// CustomRecords maps TLV types to byte slices, storing arbitrary data
// intended for inclusion in the ExtraData field of the UpdateAddHTLC
// message.
CustomRecords CustomRecords
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// NewUpdateAddHTLC returns a new empty UpdateAddHTLC message.
func NewUpdateAddHTLC() *UpdateAddHTLC {
return &UpdateAddHTLC{}
}
// A compile time check to ensure UpdateAddHTLC implements the lnwire.Message
// interface.
var _ Message = (*UpdateAddHTLC)(nil)
// Decode deserializes a serialized UpdateAddHTLC message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *UpdateAddHTLC) Decode(r io.Reader, pver uint32) error {
// msgExtraData is a temporary variable used to read the message extra
// data field from the reader.
var msgExtraData ExtraOpaqueData
if err := ReadElements(r,
&c.ChanID,
&c.ID,
&c.Amount,
c.PaymentHash[:],
&c.Expiry,
c.OnionBlob[:],
&msgExtraData,
); err != nil {
return err
}
// Extract TLV records from the extra data field.
blindingRecord := c.BlindingPoint.Zero()
customRecords, parsed, extraData, err := ParseAndExtractCustomRecords(
msgExtraData, &blindingRecord,
)
if err != nil {
return err
}
// Assign the parsed records back to the message.
if parsed.Contains(blindingRecord.TlvType()) {
c.BlindingPoint = tlv.SomeRecordT(blindingRecord)
}
c.CustomRecords = customRecords
c.ExtraData = extraData
return nil
}
// Encode serializes the target UpdateAddHTLC into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *UpdateAddHTLC) Encode(w *bytes.Buffer, pver uint32) error {
if err := WriteChannelID(w, c.ChanID); err != nil {
return err
}
if err := WriteUint64(w, c.ID); err != nil {
return err
}
if err := WriteMilliSatoshi(w, c.Amount); err != nil {
return err
}
if err := WriteBytes(w, c.PaymentHash[:]); err != nil {
return err
}
if err := WriteUint32(w, c.Expiry); err != nil {
return err
}
if err := WriteBytes(w, c.OnionBlob[:]); err != nil {
return err
}
// Only include blinding point in extra data if present.
var records []tlv.RecordProducer
c.BlindingPoint.WhenSome(
func(b tlv.RecordT[BlindingPointTlvType, *btcec.PublicKey]) {
records = append(records, &b)
},
)
extraData, err := MergeAndEncode(records, c.ExtraData, c.CustomRecords)
if err != nil {
return err
}
return WriteBytes(w, extraData)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *UpdateAddHTLC) MsgType() MessageType {
return MsgUpdateAddHTLC
}
// TargetChanID returns the channel id of the link for which this message is
// intended.
//
// NOTE: Part of peer.LinkUpdater interface.
func (c *UpdateAddHTLC) TargetChanID() ChannelID {
return c.ChanID
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (c *UpdateAddHTLC) SerializedSize() (uint32, error) {
return MessageSerializedSize(c)
}
// A compile time check to ensure UpdateAddHTLC implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*UpdateAddHTLC)(nil)
package lnwire
import (
"bytes"
"io"
)
// OpaqueReason is an opaque encrypted byte slice that encodes the exact
// failure reason and additional some supplemental data. The contents of this
// slice can only be decrypted by the sender of the original HTLC.
type OpaqueReason []byte
// UpdateFailHTLC is sent by Alice to Bob in order to remove a previously added
// HTLC. Upon receipt of an UpdateFailHTLC the HTLC should be removed from the
// next commitment transaction, with the UpdateFailHTLC propagated backwards in
// the route to fully undo the HTLC.
type UpdateFailHTLC struct {
// ChanID is the particular active channel that this
// UpdateFailHTLC is bound to.
ChanID ChannelID
// ID references which HTLC on the remote node's commitment transaction
// has timed out.
ID uint64
// Reason is an onion-encrypted blob that details why the HTLC was
// failed. This blob is only fully decryptable by the initiator of the
// HTLC message.
Reason OpaqueReason
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// A compile time check to ensure UpdateFailHTLC implements the lnwire.Message
// interface.
var _ Message = (*UpdateFailHTLC)(nil)
// A compile time check to ensure UpdateFailHTLC implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*UpdateFailHTLC)(nil)
// Decode deserializes a serialized UpdateFailHTLC message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFailHTLC) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&c.ChanID,
&c.ID,
&c.Reason,
&c.ExtraData,
)
}
// Encode serializes the target UpdateFailHTLC into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFailHTLC) Encode(w *bytes.Buffer, pver uint32) error {
if err := WriteChannelID(w, c.ChanID); err != nil {
return err
}
if err := WriteUint64(w, c.ID); err != nil {
return err
}
if err := WriteOpaqueReason(w, c.Reason); err != nil {
return err
}
return WriteBytes(w, c.ExtraData)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFailHTLC) MsgType() MessageType {
return MsgUpdateFailHTLC
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (c *UpdateFailHTLC) SerializedSize() (uint32, error) {
return MessageSerializedSize(c)
}
// TargetChanID returns the channel id of the link for which this message is
// intended.
//
// NOTE: Part of peer.LinkUpdater interface.
func (c *UpdateFailHTLC) TargetChanID() ChannelID {
return c.ChanID
}
package lnwire
import (
"bytes"
"crypto/sha256"
"io"
)
// UpdateFailMalformedHTLC is sent by either the payment forwarder or by
// payment receiver to the payment sender in order to notify it that the onion
// blob can't be parsed. For that reason we send this message instead of
// obfuscate the onion failure.
type UpdateFailMalformedHTLC struct {
// ChanID is the particular active channel that this
// UpdateFailMalformedHTLC is bound to.
ChanID ChannelID
// ID references which HTLC on the remote node's commitment transaction
// has timed out.
ID uint64
// ShaOnionBlob hash of the onion blob on which can't be parsed by the
// node in the payment path.
ShaOnionBlob [sha256.Size]byte
// FailureCode the exact reason why onion blob haven't been parsed.
FailureCode FailCode
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// A compile time check to ensure UpdateFailMalformedHTLC implements the
// lnwire.Message interface.
var _ Message = (*UpdateFailMalformedHTLC)(nil)
// A compile time check to ensure UpdateFailMalformedHTLC implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*UpdateFailMalformedHTLC)(nil)
// Decode deserializes a serialized UpdateFailMalformedHTLC message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFailMalformedHTLC) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&c.ChanID,
&c.ID,
c.ShaOnionBlob[:],
&c.FailureCode,
&c.ExtraData,
)
}
// Encode serializes the target UpdateFailMalformedHTLC into the passed
// io.Writer observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFailMalformedHTLC) Encode(w *bytes.Buffer,
pver uint32) error {
if err := WriteChannelID(w, c.ChanID); err != nil {
return err
}
if err := WriteUint64(w, c.ID); err != nil {
return err
}
if err := WriteBytes(w, c.ShaOnionBlob[:]); err != nil {
return err
}
if err := WriteFailCode(w, c.FailureCode); err != nil {
return err
}
return WriteBytes(w, c.ExtraData)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFailMalformedHTLC) MsgType() MessageType {
return MsgUpdateFailMalformedHTLC
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (c *UpdateFailMalformedHTLC) SerializedSize() (uint32, error) {
return MessageSerializedSize(c)
}
// TargetChanID returns the channel id of the link for which this message is
// intended.
//
// NOTE: Part of peer.LinkUpdater interface.
func (c *UpdateFailMalformedHTLC) TargetChanID() ChannelID {
return c.ChanID
}
package lnwire
import (
"bytes"
"io"
)
// UpdateFee is the message the channel initiator sends to the other peer if
// the channel commitment fee needs to be updated.
type UpdateFee struct {
// ChanID is the channel that this UpdateFee is meant for.
ChanID ChannelID
// FeePerKw is the fee-per-kw on commit transactions that the sender of
// this message wants to use for this channel.
//
// TODO(halseth): make SatPerKWeight when fee estimation is moved to
// own package. Currently this will cause an import cycle.
FeePerKw uint32
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// NewUpdateFee creates a new UpdateFee message.
func NewUpdateFee(chanID ChannelID, feePerKw uint32) *UpdateFee {
return &UpdateFee{
ChanID: chanID,
FeePerKw: feePerKw,
}
}
// A compile time check to ensure UpdateFee implements the lnwire.Message
// interface.
var _ Message = (*UpdateFee)(nil)
// A compile time check to ensure UpdateFee implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*UpdateFee)(nil)
// Decode deserializes a serialized UpdateFee message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFee) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&c.ChanID,
&c.FeePerKw,
&c.ExtraData,
)
}
// Encode serializes the target UpdateFee into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFee) Encode(w *bytes.Buffer, pver uint32) error {
if err := WriteChannelID(w, c.ChanID); err != nil {
return err
}
if err := WriteUint32(w, c.FeePerKw); err != nil {
return err
}
return WriteBytes(w, c.ExtraData)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFee) MsgType() MessageType {
return MsgUpdateFee
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (c *UpdateFee) SerializedSize() (uint32, error) {
return MessageSerializedSize(c)
}
// TargetChanID returns the channel id of the link for which this message is
// intended.
//
// NOTE: Part of peer.LinkUpdater interface.
func (c *UpdateFee) TargetChanID() ChannelID {
return c.ChanID
}
package lnwire
import (
"bytes"
"io"
)
// UpdateFulfillHTLC is sent by Alice to Bob when she wishes to settle a
// particular HTLC referenced by its HTLCKey within a specific active channel
// referenced by ChannelPoint. A subsequent CommitSig message will be sent by
// Alice to "lock-in" the removal of the specified HTLC, possible containing a
// batch signature covering several settled HTLC's.
type UpdateFulfillHTLC struct {
// ChanID references an active channel which holds the HTLC to be
// settled.
ChanID ChannelID
// ID denotes the exact HTLC stage within the receiving node's
// commitment transaction to be removed.
ID uint64
// PaymentPreimage is the R-value preimage required to fully settle an
// HTLC.
PaymentPreimage [32]byte
// CustomRecords maps TLV types to byte slices, storing arbitrary data
// intended for inclusion in the ExtraData field.
CustomRecords CustomRecords
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// NewUpdateFulfillHTLC returns a new empty UpdateFulfillHTLC.
func NewUpdateFulfillHTLC(chanID ChannelID, id uint64,
preimage [32]byte) *UpdateFulfillHTLC {
return &UpdateFulfillHTLC{
ChanID: chanID,
ID: id,
PaymentPreimage: preimage,
}
}
// A compile time check to ensure UpdateFulfillHTLC implements the
// lnwire.Message interface.
var _ Message = (*UpdateFulfillHTLC)(nil)
// A compile time check to ensure UpdateFulfillHTLC implements the
// lnwire.SizeableMessage interface.
var _ SizeableMessage = (*UpdateFulfillHTLC)(nil)
// Decode deserializes a serialized UpdateFulfillHTLC message stored in the
// passed io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFulfillHTLC) Decode(r io.Reader, pver uint32) error {
// msgExtraData is a temporary variable used to read the message extra
// data field from the reader.
var msgExtraData ExtraOpaqueData
if err := ReadElements(r,
&c.ChanID,
&c.ID,
c.PaymentPreimage[:],
&msgExtraData,
); err != nil {
return err
}
// Extract custom records from the extra data field.
customRecords, _, extraData, err := ParseAndExtractCustomRecords(
msgExtraData,
)
if err != nil {
return err
}
c.CustomRecords = customRecords
c.ExtraData = extraData
return nil
}
// Encode serializes the target UpdateFulfillHTLC into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFulfillHTLC) Encode(w *bytes.Buffer, pver uint32) error {
if err := WriteChannelID(w, c.ChanID); err != nil {
return err
}
if err := WriteUint64(w, c.ID); err != nil {
return err
}
if err := WriteBytes(w, c.PaymentPreimage[:]); err != nil {
return err
}
// Combine the custom records and the extra data, then encode the
// result as a byte slice.
extraData, err := MergeAndEncode(nil, c.ExtraData, c.CustomRecords)
if err != nil {
return err
}
return WriteBytes(w, extraData)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *UpdateFulfillHTLC) MsgType() MessageType {
return MsgUpdateFulfillHTLC
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (c *UpdateFulfillHTLC) SerializedSize() (uint32, error) {
return MessageSerializedSize(c)
}
// TargetChanID returns the channel id of the link for which this message is
// intended.
//
// NOTE: Part of peer.LinkUpdater interface.
func (c *UpdateFulfillHTLC) TargetChanID() ChannelID {
return c.ChanID
}
package lnwire
import (
"bytes"
"fmt"
"io"
)
// WarningData is a set of bytes associated with a particular sent warning. A
// receiving node SHOULD only print out data verbatim if the string is composed
// solely of printable ASCII characters. For reference, the printable character
// set includes byte values 32 through 127 inclusive.
type WarningData []byte
// Warning is used to express non-critical errors in the protocol, providing
// a "soft" way for nodes to communicate failures.
type Warning struct {
// ChanID references the active channel in which the warning occurred
// within. If the ChanID is all zeros, then this warning applies to the
// entire established connection.
ChanID ChannelID
// Data is the attached warning data that describes the exact failure
// which caused the warning message to be sent.
Data WarningData
}
// A compile time check to ensure Warning implements the lnwire.Message
// interface.
var _ Message = (*Warning)(nil)
// A compile time check to ensure Warning implements the lnwire.SizeableMessage
// interface.
var _ SizeableMessage = (*Warning)(nil)
// NewWarning creates a new Warning message.
func NewWarning() *Warning {
return &Warning{}
}
// Warning returns the string representation to Warning.
func (c *Warning) Warning() string {
errMsg := "non-ascii data"
if isASCII(c.Data) {
errMsg = string(c.Data)
}
return fmt.Sprintf("chan_id=%v, err=%v", c.ChanID, errMsg)
}
// Decode deserializes a serialized Warning message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *Warning) Decode(r io.Reader, _ uint32) error {
return ReadElements(r,
&c.ChanID,
&c.Data,
)
}
// Encode serializes the target Warning into the passed io.Writer observing the
// protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *Warning) Encode(w *bytes.Buffer, _ uint32) error {
if err := WriteBytes(w, c.ChanID[:]); err != nil {
return err
}
return WriteWarningData(w, c.Data)
}
// MsgType returns the integer uniquely identifying an Warning message on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *Warning) MsgType() MessageType {
return MsgWarning
}
// SerializedSize returns the serialized size of the message in bytes.
//
// This is part of the lnwire.SizeableMessage interface.
func (c *Warning) SerializedSize() (uint32, error) {
return MessageSerializedSize(c)
}
package lnwire
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"image/color"
"math"
"net"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/tor"
)
var (
// ErrNilFeatureVector is returned when the supplied feature is nil.
ErrNilFeatureVector = errors.New("cannot write nil feature vector")
// ErrPkScriptTooLong is returned when the length of the provided
// script exceeds 34.
ErrPkScriptTooLong = errors.New("'PkScript' too long")
// ErrNilTCPAddress is returned when the supplied address is nil.
ErrNilTCPAddress = errors.New("cannot write nil TCPAddr")
// ErrNilOnionAddress is returned when the supplied address is nil.
ErrNilOnionAddress = errors.New("cannot write nil onion address")
// ErrNilNetAddress is returned when a nil value is used in []net.Addr.
ErrNilNetAddress = errors.New("cannot write nil address")
// ErrNilOpaqueAddrs is returned when the supplied address is nil.
ErrNilOpaqueAddrs = errors.New("cannot write nil OpaqueAddrs")
// ErrNilPublicKey is returned when a nil pubkey is used.
ErrNilPublicKey = errors.New("cannot write nil pubkey")
// ErrUnknownServiceLength is returned when the onion service length is
// unknown.
ErrUnknownServiceLength = errors.New("unknown onion service length")
)
// ErrOutpointIndexTooBig is used when the outpoint index exceeds the max value
// of uint16.
func ErrOutpointIndexTooBig(index uint32) error {
return fmt.Errorf(
"index for outpoint (%v) is greater than "+
"max index of %v", index, math.MaxUint16,
)
}
// WriteBytes appends the given bytes to the provided buffer.
func WriteBytes(buf *bytes.Buffer, b []byte) error {
_, err := buf.Write(b)
return err
}
// WriteUint8 appends the uint8 to the provided buffer.
func WriteUint8(buf *bytes.Buffer, n uint8) error {
_, err := buf.Write([]byte{n})
return err
}
// WriteUint16 appends the uint16 to the provided buffer. It encodes the
// integer using big endian byte order.
func WriteUint16(buf *bytes.Buffer, n uint16) error {
var b [2]byte
binary.BigEndian.PutUint16(b[:], n)
_, err := buf.Write(b[:])
return err
}
// WriteUint32 appends the uint32 to the provided buffer. It encodes the
// integer using big endian byte order.
func WriteUint32(buf *bytes.Buffer, n uint32) error {
var b [4]byte
binary.BigEndian.PutUint32(b[:], n)
_, err := buf.Write(b[:])
return err
}
// WriteUint64 appends the uint64 to the provided buffer. It encodes the
// integer using big endian byte order.
func WriteUint64(buf *bytes.Buffer, n uint64) error {
var b [8]byte
binary.BigEndian.PutUint64(b[:], n)
_, err := buf.Write(b[:])
return err
}
// WriteSatoshi appends the Satoshi value to the provided buffer.
func WriteSatoshi(buf *bytes.Buffer, amount btcutil.Amount) error {
return WriteUint64(buf, uint64(amount))
}
// WriteMilliSatoshi appends the MilliSatoshi value to the provided buffer.
func WriteMilliSatoshi(buf *bytes.Buffer, amount MilliSatoshi) error {
return WriteUint64(buf, uint64(amount))
}
// WritePublicKey appends the compressed public key to the provided buffer.
func WritePublicKey(buf *bytes.Buffer, pub *btcec.PublicKey) error {
if pub == nil {
return ErrNilPublicKey
}
serializedPubkey := pub.SerializeCompressed()
return WriteBytes(buf, serializedPubkey)
}
// WriteChannelID appends the ChannelID to the provided buffer.
func WriteChannelID(buf *bytes.Buffer, channelID ChannelID) error {
return WriteBytes(buf, channelID[:])
}
// WriteNodeAlias appends the alias to the provided buffer.
func WriteNodeAlias(buf *bytes.Buffer, alias NodeAlias) error {
return WriteBytes(buf, alias[:])
}
// WriteShortChannelID appends the ShortChannelID to the provided buffer. It
// encodes the BlockHeight and TxIndex each using 3 bytes with big endian byte
// order, and encodes txPosition using 2 bytes with big endian byte order.
func WriteShortChannelID(buf *bytes.Buffer, shortChanID ShortChannelID) error {
// Check that field fit in 3 bytes and write the blockHeight
if shortChanID.BlockHeight > ((1 << 24) - 1) {
return errors.New("block height should fit in 3 bytes")
}
var blockHeight [4]byte
binary.BigEndian.PutUint32(blockHeight[:], shortChanID.BlockHeight)
if _, err := buf.Write(blockHeight[1:]); err != nil {
return err
}
// Check that field fit in 3 bytes and write the txIndex
if shortChanID.TxIndex > ((1 << 24) - 1) {
return errors.New("tx index should fit in 3 bytes")
}
var txIndex [4]byte
binary.BigEndian.PutUint32(txIndex[:], shortChanID.TxIndex)
if _, err := buf.Write(txIndex[1:]); err != nil {
return err
}
// Write the TxPosition
return WriteUint16(buf, shortChanID.TxPosition)
}
// WriteSig appends the signature to the provided buffer.
func WriteSig(buf *bytes.Buffer, sig Sig) error {
return WriteBytes(buf, sig.bytes[:])
}
// WriteSigs appends the slice of signatures to the provided buffer with its
// length.
func WriteSigs(buf *bytes.Buffer, sigs []Sig) error {
// Write the length of the sigs.
if err := WriteUint16(buf, uint16(len(sigs))); err != nil {
return err
}
for _, sig := range sigs {
if err := WriteSig(buf, sig); err != nil {
return err
}
}
return nil
}
// WriteFailCode appends the FailCode to the provided buffer.
func WriteFailCode(buf *bytes.Buffer, e FailCode) error {
return WriteUint16(buf, uint16(e))
}
// WriteRawFeatureVector encodes the feature using the feature's Encode method
// and appends the data to the provided buffer. An error will return if the
// passed feature is nil.
func WriteRawFeatureVector(buf *bytes.Buffer, feature *RawFeatureVector) error {
if feature == nil {
return ErrNilFeatureVector
}
return feature.Encode(buf)
}
// WriteColorRGBA appends the RGBA color using three bytes.
func WriteColorRGBA(buf *bytes.Buffer, e color.RGBA) error {
// Write R
if err := WriteUint8(buf, e.R); err != nil {
return err
}
// Write G
if err := WriteUint8(buf, e.G); err != nil {
return err
}
// Write B
return WriteUint8(buf, e.B)
}
// WriteQueryEncoding appends the QueryEncoding to the provided buffer.
func WriteQueryEncoding(buf *bytes.Buffer, e QueryEncoding) error {
return WriteUint8(buf, uint8(e))
}
// WriteFundingFlag appends the FundingFlag to the provided buffer.
func WriteFundingFlag(buf *bytes.Buffer, flag FundingFlag) error {
return WriteUint8(buf, uint8(flag))
}
// WriteChanUpdateMsgFlags appends the update flag to the provided buffer.
func WriteChanUpdateMsgFlags(buf *bytes.Buffer, f ChanUpdateMsgFlags) error {
return WriteUint8(buf, uint8(f))
}
// WriteChanUpdateChanFlags appends the update flag to the provided buffer.
func WriteChanUpdateChanFlags(buf *bytes.Buffer, f ChanUpdateChanFlags) error {
return WriteUint8(buf, uint8(f))
}
// WriteDeliveryAddress appends the address to the provided buffer.
func WriteDeliveryAddress(buf *bytes.Buffer, addr DeliveryAddress) error {
return writeDataWithLength(buf, addr)
}
// WritePingPayload appends the payload to the provided buffer.
func WritePingPayload(buf *bytes.Buffer, payload PingPayload) error {
return writeDataWithLength(buf, payload)
}
// WritePongPayload appends the payload to the provided buffer.
func WritePongPayload(buf *bytes.Buffer, payload PongPayload) error {
return writeDataWithLength(buf, payload)
}
// WriteWarningData appends the data to the provided buffer.
func WriteWarningData(buf *bytes.Buffer, data WarningData) error {
return writeDataWithLength(buf, data)
}
// WriteErrorData appends the data to the provided buffer.
func WriteErrorData(buf *bytes.Buffer, data ErrorData) error {
return writeDataWithLength(buf, data)
}
// WriteOpaqueReason appends the reason to the provided buffer.
func WriteOpaqueReason(buf *bytes.Buffer, reason OpaqueReason) error {
return writeDataWithLength(buf, reason)
}
// WriteBool appends the boolean to the provided buffer.
func WriteBool(buf *bytes.Buffer, b bool) error {
if b {
return WriteBytes(buf, []byte{1})
}
return WriteBytes(buf, []byte{0})
}
// WritePkScript appends the script to the provided buffer. Returns an error if
// the provided script exceeds 34 bytes.
func WritePkScript(buf *bytes.Buffer, s PkScript) error {
// The largest script we'll accept is a p2wsh which is exactly
// 34 bytes long.
scriptLength := len(s)
if scriptLength > 34 {
return ErrPkScriptTooLong
}
return wire.WriteVarBytes(buf, 0, s)
}
// WriteOutPoint appends the outpoint to the provided buffer.
func WriteOutPoint(buf *bytes.Buffer, p wire.OutPoint) error {
// Before we write anything to the buffer, check the Index is sane.
if p.Index > math.MaxUint16 {
return ErrOutpointIndexTooBig(p.Index)
}
var h [32]byte
copy(h[:], p.Hash[:])
if _, err := buf.Write(h[:]); err != nil {
return err
}
// Write the index using two bytes.
return WriteUint16(buf, uint16(p.Index))
}
// WriteTCPAddr appends the TCP address to the provided buffer, either a IPv4
// or a IPv6.
func WriteTCPAddr(buf *bytes.Buffer, addr *net.TCPAddr) error {
if addr == nil {
return ErrNilTCPAddress
}
// Make a slice of bytes to hold the data of descriptor and ip. At
// most, we need 17 bytes - 1 byte for the descriptor, 16 bytes for
// IPv6.
data := make([]byte, 0, 17)
if addr.IP.To4() != nil {
data = append(data, uint8(tcp4Addr))
data = append(data, addr.IP.To4()...)
} else {
data = append(data, uint8(tcp6Addr))
data = append(data, addr.IP.To16()...)
}
if _, err := buf.Write(data); err != nil {
return err
}
return WriteUint16(buf, uint16(addr.Port))
}
// WriteOnionAddr appends the onion address to the provided buffer.
func WriteOnionAddr(buf *bytes.Buffer, addr *tor.OnionAddr) error {
if addr == nil {
return ErrNilOnionAddress
}
var (
suffixIndex int
descriptor []byte
)
// Decide the suffixIndex and descriptor.
switch len(addr.OnionService) {
case tor.V2Len:
descriptor = []byte{byte(v2OnionAddr)}
suffixIndex = tor.V2Len - tor.OnionSuffixLen
case tor.V3Len:
descriptor = []byte{byte(v3OnionAddr)}
suffixIndex = tor.V3Len - tor.OnionSuffixLen
default:
return ErrUnknownServiceLength
}
// Decode the address.
host, err := tor.Base32Encoding.DecodeString(
addr.OnionService[:suffixIndex],
)
if err != nil {
return err
}
// Perform the actual write when the above checks passed.
if _, err := buf.Write(descriptor); err != nil {
return err
}
if _, err := buf.Write(host); err != nil {
return err
}
return WriteUint16(buf, uint16(addr.Port))
}
// WriteOpaqueAddrs appends the payload of the given OpaqueAddrs to buffer.
func WriteOpaqueAddrs(buf *bytes.Buffer, addr *OpaqueAddrs) error {
if addr == nil {
return ErrNilOpaqueAddrs
}
_, err := buf.Write(addr.Payload)
return err
}
// WriteNetAddrs appends a slice of addresses to the provided buffer with the
// length info.
func WriteNetAddrs(buf *bytes.Buffer, addresses []net.Addr) error {
// First, we'll encode all the addresses into an intermediate
// buffer. We need to do this in order to compute the total
// length of the addresses.
buffer := make([]byte, 0, MaxMsgBody)
addrBuf := bytes.NewBuffer(buffer)
for _, address := range addresses {
switch a := address.(type) {
case *net.TCPAddr:
if err := WriteTCPAddr(addrBuf, a); err != nil {
return err
}
case *tor.OnionAddr:
if err := WriteOnionAddr(addrBuf, a); err != nil {
return err
}
case *OpaqueAddrs:
if err := WriteOpaqueAddrs(addrBuf, a); err != nil {
return err
}
default:
return ErrNilNetAddress
}
}
// With the addresses fully encoded, we can now write out data.
return writeDataWithLength(buf, addrBuf.Bytes())
}
// writeDataWithLength writes the data and its length to the buffer.
func writeDataWithLength(buf *bytes.Buffer, data []byte) error {
var l [2]byte
binary.BigEndian.PutUint16(l[:], uint16(len(data)))
if _, err := buf.Write(l[:]); err != nil {
return err
}
_, err := buf.Write(data)
return err
}
package lnd
import (
"github.com/btcsuite/btcd/connmgr"
"github.com/btcsuite/btcd/rpcclient"
btclogv1 "github.com/btcsuite/btclog"
"github.com/btcsuite/btclog/v2"
"github.com/lightninglabs/neutrino"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/autopilot"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/chainio"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/chanacceptor"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/chanfitness"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channelnotifier"
"github.com/lightningnetwork/lnd/cluster"
"github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/discovery"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/graph"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/healthcheck"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/kvdb/sqlbase"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc/autopilotrpc"
"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
"github.com/lightningnetwork/lnd/lnrpc/devrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lnrpc/neutrinorpc"
"github.com/lightningnetwork/lnd/lnrpc/peersrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnrpc/verrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
"github.com/lightningnetwork/lnd/lnwallet/rpcwallet"
"github.com/lightningnetwork/lnd/monitoring"
"github.com/lightningnetwork/lnd/msgmux"
"github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/peer"
"github.com/lightningnetwork/lnd/peernotifier"
"github.com/lightningnetwork/lnd/protofsm"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/routing/blindedpath"
"github.com/lightningnetwork/lnd/routing/localchans"
"github.com/lightningnetwork/lnd/rpcperms"
"github.com/lightningnetwork/lnd/signal"
"github.com/lightningnetwork/lnd/sqldb"
"github.com/lightningnetwork/lnd/sweep"
"github.com/lightningnetwork/lnd/tor"
"github.com/lightningnetwork/lnd/watchtower"
"github.com/lightningnetwork/lnd/watchtower/wtclient"
)
// replaceableLogger is a thin wrapper around a logger that is used so the
// logger can be replaced easily without some black pointer magic.
type replaceableLogger struct {
btclog.Logger
subsystem string
}
// Loggers can not be used before the log rotator has been initialized with a
// log file. This must be performed early during application startup by
// calling InitLogRotator() on the main log writer instance in the config.
var (
// lndPkgLoggers is a list of all lnd package level loggers that are
// registered. They are tracked here so they can be replaced once the
// SetupLoggers function is called with the final root logger.
lndPkgLoggers []*replaceableLogger
// addLndPkgLogger is a helper function that creates a new replaceable
// main lnd package level logger and adds it to the list of loggers that
// are replaced again later, once the final root logger is ready.
addLndPkgLogger = func(subsystem string) *replaceableLogger {
l := &replaceableLogger{
Logger: build.NewSubLogger(subsystem, nil),
subsystem: subsystem,
}
lndPkgLoggers = append(lndPkgLoggers, l)
return l
}
// Loggers that need to be accessible from the lnd package can be placed
// here. Loggers that are only used in sub modules can be added directly
// by using the addSubLogger method. We declare all loggers so we never
// run into a nil reference if they are used early. But the SetupLoggers
// function should always be called as soon as possible to finish
// setting them up properly with a root logger.
ltndLog = addLndPkgLogger("LTND")
rpcsLog = addLndPkgLogger("RPCS")
srvrLog = addLndPkgLogger("SRVR")
atplLog = addLndPkgLogger("ATPL")
)
// genSubLogger creates a logger for a subsystem. We provide an instance of
// a signal.Interceptor to be able to shutdown in the case of a critical error.
func genSubLogger(root *build.SubLoggerManager,
interceptor signal.Interceptor) func(string) btclog.Logger {
// Create a shutdown function which will request shutdown from our
// interceptor if it is listening.
shutdown := func() {
if !interceptor.Listening() {
return
}
interceptor.RequestShutdown()
}
// Return a function which will create a sublogger from our root
// logger without shutdown fn.
return func(tag string) btclog.Logger {
return root.GenSubLogger(tag, shutdown)
}
}
// SetupLoggers initializes all package-global logger variables.
//
//nolint:ll
func SetupLoggers(root *build.SubLoggerManager, interceptor signal.Interceptor) {
genLogger := genSubLogger(root, interceptor)
// Now that we have the proper root logger, we can replace the
// placeholder lnd package loggers.
for _, l := range lndPkgLoggers {
l.Logger = build.NewSubLogger(l.subsystem, genLogger)
SetSubLogger(root, l.subsystem, l.Logger)
}
// Initialize loggers from packages outside of `lnd` first. The
// packages below will overwrite the names of the loggers they import.
// For instance, the logger in `neutrino.query` is overwritten by
// `btcwallet.chain`, which is overwritten by `lnwallet`. To ensure the
// overwriting works, we need to initialize the loggers here so they
// can be overwritten later.
AddV1SubLogger(root, "BTCN", interceptor, neutrino.UseLogger)
AddV1SubLogger(root, "CMGR", interceptor, connmgr.UseLogger)
AddV1SubLogger(root, "RPCC", interceptor, rpcclient.UseLogger)
// Some of the loggers declared in the main lnd package are also used
// in sub packages.
signal.UseLogger(ltndLog)
autopilot.UseLogger(atplLog)
AddSubLogger(root, "LNWL", interceptor, lnwallet.UseLogger)
AddSubLogger(root, "DISC", interceptor, discovery.UseLogger)
AddSubLogger(root, "NTFN", interceptor, chainntnfs.UseLogger)
AddSubLogger(root, "CHDB", interceptor, channeldb.UseLogger)
AddSubLogger(root, "SQLB", interceptor, sqlbase.UseLogger)
AddSubLogger(root, "HSWC", interceptor, htlcswitch.UseLogger)
AddSubLogger(root, "CNCT", interceptor, contractcourt.UseLogger)
AddSubLogger(root, "UTXN", interceptor, contractcourt.UseNurseryLogger)
AddSubLogger(root, "BRAR", interceptor, contractcourt.UseBreachLogger)
AddV1SubLogger(root, "SPHX", interceptor, sphinx.UseLogger)
AddSubLogger(root, "SWPR", interceptor, sweep.UseLogger)
AddSubLogger(root, "SGNR", interceptor, signrpc.UseLogger)
AddSubLogger(root, "WLKT", interceptor, walletrpc.UseLogger)
AddSubLogger(root, "ARPC", interceptor, autopilotrpc.UseLogger)
AddSubLogger(root, "NRPC", interceptor, neutrinorpc.UseLogger)
AddSubLogger(root, "DRPC", interceptor, devrpc.UseLogger)
AddSubLogger(root, "INVC", interceptor, invoices.UseLogger)
AddSubLogger(root, "NANN", interceptor, netann.UseLogger)
AddSubLogger(root, "WTWR", interceptor, watchtower.UseLogger)
AddSubLogger(root, "NTFR", interceptor, chainrpc.UseLogger)
AddSubLogger(root, "IRPC", interceptor, invoicesrpc.UseLogger)
AddSubLogger(root, "CHNF", interceptor, channelnotifier.UseLogger)
AddSubLogger(root, "CHBU", interceptor, chanbackup.UseLogger)
AddSubLogger(root, "PROM", interceptor, monitoring.UseLogger)
AddSubLogger(root, "WTCL", interceptor, wtclient.UseLogger)
AddSubLogger(root, "PRNF", interceptor, peernotifier.UseLogger)
AddSubLogger(root, "CHFD", interceptor, chanfunding.UseLogger)
AddSubLogger(root, "PEER", interceptor, peer.UseLogger)
AddSubLogger(root, "CHCL", interceptor, chancloser.UseLogger)
AddSubLogger(root, "LCHN", interceptor, localchans.UseLogger)
AddSubLogger(root, "PFSM", interceptor, protofsm.UseLogger)
AddSubLogger(root, routing.Subsystem, interceptor, routing.UseLogger)
AddSubLogger(root, routerrpc.Subsystem, interceptor, routerrpc.UseLogger)
AddSubLogger(root, chanfitness.Subsystem, interceptor, chanfitness.UseLogger)
AddSubLogger(root, verrpc.Subsystem, interceptor, verrpc.UseLogger)
AddSubLogger(root, healthcheck.Subsystem, interceptor, healthcheck.UseLogger)
AddSubLogger(root, chainreg.Subsystem, interceptor, chainreg.UseLogger)
AddSubLogger(root, chanacceptor.Subsystem, interceptor, chanacceptor.UseLogger)
AddSubLogger(root, funding.Subsystem, interceptor, funding.UseLogger)
AddSubLogger(root, cluster.Subsystem, interceptor, cluster.UseLogger)
AddSubLogger(root, rpcperms.Subsystem, interceptor, rpcperms.UseLogger)
AddSubLogger(root, tor.Subsystem, interceptor, tor.UseLogger)
AddSubLogger(root, btcwallet.Subsystem, interceptor, btcwallet.UseLogger)
AddSubLogger(root, rpcwallet.Subsystem, interceptor, rpcwallet.UseLogger)
AddSubLogger(root, peersrpc.Subsystem, interceptor, peersrpc.UseLogger)
AddSubLogger(root, graph.Subsystem, interceptor, graph.UseLogger)
AddSubLogger(root, lncfg.Subsystem, interceptor, lncfg.UseLogger)
AddSubLogger(
root, blindedpath.Subsystem, interceptor, blindedpath.UseLogger,
)
AddV1SubLogger(root, graphdb.Subsystem, interceptor, graphdb.UseLogger)
AddSubLogger(root, chainio.Subsystem, interceptor, chainio.UseLogger)
AddSubLogger(root, msgmux.Subsystem, interceptor, msgmux.UseLogger)
AddSubLogger(root, sqldb.Subsystem, interceptor, sqldb.UseLogger)
}
// AddSubLogger is a helper method to conveniently create and register the
// logger of one or more sub systems.
func AddSubLogger(root *build.SubLoggerManager, subsystem string,
interceptor signal.Interceptor, useLoggers ...func(btclog.Logger)) {
// genSubLogger will return a callback for creating a logger instance,
// which we will give to the root logger.
genLogger := genSubLogger(root, interceptor)
// Create and register just a single logger to prevent them from
// overwriting each other internally.
logger := build.NewSubLogger(subsystem, genLogger)
SetSubLogger(root, subsystem, logger, useLoggers...)
}
// SetSubLogger is a helper method to conveniently register the logger of a
// sub system.
func SetSubLogger(root *build.SubLoggerManager, subsystem string,
logger btclog.Logger, useLoggers ...func(btclog.Logger)) {
root.RegisterSubLogger(subsystem, logger)
for _, useLogger := range useLoggers {
useLogger(logger)
}
}
// AddV1SubLogger is a helper method to conveniently create and register the
// logger of one or more sub systems.
func AddV1SubLogger(root *build.SubLoggerManager, subsystem string,
interceptor signal.Interceptor, useLoggers ...func(btclogv1.Logger)) {
// genSubLogger will return a callback for creating a logger instance,
// which we will give to the root logger.
genLogger := genSubLogger(root, interceptor)
// Create and register just a single logger to prevent them from
// overwriting each other internally.
logger := build.NewSubLogger(subsystem, genLogger)
SetV1SubLogger(root, subsystem, logger, useLoggers...)
}
// SetV1SubLogger is a helper method to conveniently register the logger of a
// sub system. Note that the btclog v2 logger implements the btclog v1 logger
// which is why we can pass the v2 logger to the UseLogger call-backs that
// expect the v1 logger.
func SetV1SubLogger(root *build.SubLoggerManager, subsystem string,
logger btclog.Logger, useLoggers ...func(btclogv1.Logger)) {
root.RegisterSubLogger(subsystem, logger)
for _, useLogger := range useLoggers {
useLogger(logger)
}
}
package macaroons
import (
"context"
"encoding/hex"
macaroon "gopkg.in/macaroon.v2"
)
// MacaroonCredential wraps a macaroon to implement the
// credentials.PerRPCCredentials interface.
type MacaroonCredential struct {
*macaroon.Macaroon
}
// RequireTransportSecurity implements the PerRPCCredentials interface.
func (m MacaroonCredential) RequireTransportSecurity() bool {
return true
}
// GetRequestMetadata implements the PerRPCCredentials interface. This method
// is required in order to pass the wrapped macaroon into the gRPC context.
// With this, the macaroon will be available within the request handling scope
// of the ultimate gRPC server implementation.
func (m MacaroonCredential) GetRequestMetadata(ctx context.Context,
uri ...string) (map[string]string, error) {
macBytes, err := m.MarshalBinary()
if err != nil {
return nil, err
}
md := make(map[string]string)
md["macaroon"] = hex.EncodeToString(macBytes)
return md, nil
}
// NewMacaroonCredential returns a copy of the passed macaroon wrapped in a
// MacaroonCredential struct which implements PerRPCCredentials.
func NewMacaroonCredential(m *macaroon.Macaroon) (MacaroonCredential, error) {
ms := MacaroonCredential{}
// The macaroon library's Clone() method has a subtle bug that doesn't
// correctly clone all caveats. We need to use our own, safe clone
// function instead.
var err error
ms.Macaroon, err = SafeCopyMacaroon(m)
if err != nil {
return ms, err
}
return ms, nil
}
package macaroons
import (
"bytes"
"fmt"
"golang.org/x/net/context"
"gopkg.in/macaroon-bakery.v2/bakery"
"gopkg.in/macaroon.v2"
)
// inMemoryRootKeyStore is a simple implementation of bakery.RootKeyStore that
// stores a single root key in memory.
type inMemoryRootKeyStore struct {
rootKey []byte
}
// A compile-time check to ensure that inMemoryRootKeyStore implements
// bakery.RootKeyStore.
var _ bakery.RootKeyStore = (*inMemoryRootKeyStore)(nil)
// Get returns the root key for the given id. If the item is not there, it
// returns ErrNotFound.
func (s *inMemoryRootKeyStore) Get(_ context.Context, id []byte) ([]byte,
error) {
if !bytes.Equal(id, DefaultRootKeyID) {
return nil, bakery.ErrNotFound
}
return s.rootKey, nil
}
// RootKey returns the root key to be used for making a new macaroon, and an id
// that can be used to look it up later with the Get method.
func (s *inMemoryRootKeyStore) RootKey(context.Context) ([]byte, []byte,
error) {
return s.rootKey, DefaultRootKeyID, nil
}
// BakeFromRootKey creates a new macaroon that is derived from the given root
// key and permissions.
func BakeFromRootKey(rootKey []byte,
permissions []bakery.Op) (*macaroon.Macaroon, error) {
if len(rootKey) != RootKeyLen {
return nil, fmt.Errorf("root key must be %d bytes, is %d",
RootKeyLen, len(rootKey))
}
rootKeyStore := &inMemoryRootKeyStore{
rootKey: rootKey,
}
service, err := NewService(rootKeyStore, "lnd", false)
if err != nil {
return nil, fmt.Errorf("unable to create service: %w", err)
}
ctx := context.Background()
mac, err := service.NewMacaroon(ctx, DefaultRootKeyID, permissions...)
if err != nil {
return nil, fmt.Errorf("unable to create macaroon: %w", err)
}
return mac.M(), nil
}
package macaroons
import (
"bytes"
"context"
"errors"
"fmt"
"net"
"strings"
"time"
"google.golang.org/grpc/peer"
"gopkg.in/macaroon-bakery.v2/bakery/checkers"
macaroon "gopkg.in/macaroon.v2"
)
const (
// CondLndCustom is the first party caveat condition name that is used
// for all custom caveats in lnd. Every custom caveat entry will be
// encoded as the string
// "lnd-custom <custom-caveat-name> <custom-caveat-condition>"
// in the serialized macaroon. We choose a single space as the delimiter
// between the because that is also used by the macaroon bakery library.
CondLndCustom = "lnd-custom"
// CondIPRange is the caveat condition name that is used for tying an IP
// range to a macaroon.
CondIPRange = "iprange"
)
// CustomCaveatAcceptor is an interface that contains a single method for
// checking whether a macaroon with the given custom caveat name should be
// accepted or not.
type CustomCaveatAcceptor interface {
// CustomCaveatSupported returns nil if a macaroon with the given custom
// caveat name can be validated by any component in lnd (for example an
// RPC middleware). If no component is registered to handle the given
// custom caveat then an error must be returned. This method only checks
// the availability of a validating component, not the validity of the
// macaroon itself.
CustomCaveatSupported(customCaveatName string) error
}
// Constraint type adds a layer of indirection over macaroon caveats.
type Constraint func(*macaroon.Macaroon) error
// Checker type adds a layer of indirection over macaroon checkers. A Checker
// returns the name of the checker and the checker function; these are used to
// register the function with the bakery service's compound checker.
type Checker func() (string, checkers.Func)
// AddConstraints returns new derived macaroon by applying every passed
// constraint and tightening its restrictions.
func AddConstraints(mac *macaroon.Macaroon,
cs ...Constraint) (*macaroon.Macaroon, error) {
// The macaroon library's Clone() method has a subtle bug that doesn't
// correctly clone all caveats. We need to use our own, safe clone
// function instead.
newMac, err := SafeCopyMacaroon(mac)
if err != nil {
return nil, err
}
for _, constraint := range cs {
if err := constraint(newMac); err != nil {
return nil, err
}
}
return newMac, nil
}
// Each *Constraint function is a functional option, which takes a pointer
// to the macaroon and adds another restriction to it. For each *Constraint,
// the corresponding *Checker is provided if not provided by default.
// TimeoutConstraint restricts the lifetime of the macaroon
// to the amount of seconds given.
func TimeoutConstraint(seconds int64) func(*macaroon.Macaroon) error {
return func(mac *macaroon.Macaroon) error {
macaroonTimeout := time.Duration(seconds)
requestTimeout := time.Now().Add(time.Second * macaroonTimeout)
caveat := checkers.TimeBeforeCaveat(requestTimeout)
return mac.AddFirstPartyCaveat([]byte(caveat.Condition))
}
}
// IPLockConstraint locks a macaroon to a specific IP address. If ipAddr is an
// empty string, this constraint does nothing to accommodate default value's
// desired behavior.
func IPLockConstraint(ipAddr string) func(*macaroon.Macaroon) error {
return func(mac *macaroon.Macaroon) error {
if ipAddr != "" {
macaroonIPAddr := net.ParseIP(ipAddr)
if macaroonIPAddr == nil {
return fmt.Errorf("incorrect macaroon IP-" +
"lock address")
}
caveat := checkers.Condition("ipaddr",
macaroonIPAddr.String())
return mac.AddFirstPartyCaveat([]byte(caveat))
}
return nil
}
}
// IPRangeLockConstraint locks a macaroon to a specific IP address range. If
// ipRange is an empty string, this constraint does nothing to accommodate
// default value's desired behavior.
func IPRangeLockConstraint(ipRange string) func(*macaroon.Macaroon) error {
return func(mac *macaroon.Macaroon) error {
if ipRange != "" {
_, parsedNet, err := net.ParseCIDR(ipRange)
if err != nil {
return fmt.Errorf("incorrect macaroon IP "+
"range: %w", err)
}
caveat := checkers.Condition(
CondIPRange, parsedNet.String(),
)
return mac.AddFirstPartyCaveat([]byte(caveat))
}
return nil
}
}
// IPLockChecker accepts client IP from the validation context and compares it
// with IP locked in the macaroon. It is of the `Checker` type.
func IPLockChecker() (string, checkers.Func) {
return "ipaddr", func(ctx context.Context, cond, arg string) error {
// Get peer info and extract IP address from it for macaroon
// check.
pr, ok := peer.FromContext(ctx)
if !ok {
return fmt.Errorf("unable to get peer info from context")
}
peerAddr, _, err := net.SplitHostPort(pr.Addr.String())
if err != nil {
return fmt.Errorf("unable to parse peer address")
}
if !net.ParseIP(arg).Equal(net.ParseIP(peerAddr)) {
msg := "macaroon locked to different IP address"
return fmt.Errorf(msg)
}
return nil
}
}
// IPRangeLockChecker accepts client IP range from the validation context and
// compares it with the IP range locked in the macaroon. It is of the `Checker`
// type.
func IPRangeLockChecker() (string, checkers.Func) {
return CondIPRange, func(ctx context.Context, cond, arg string) error {
// Get peer info and extract IP range from it for macaroon
// check.
pr, ok := peer.FromContext(ctx)
if !ok {
return errors.New("unable to get peer info from " +
"context")
}
peerAddr, _, err := net.SplitHostPort(pr.Addr.String())
if err != nil {
return fmt.Errorf("unable to parse peer address: %w",
err)
}
_, ipNet, err := net.ParseCIDR(arg)
if err != nil {
return fmt.Errorf("unable to parse macaroon IP "+
"range: %w", err)
}
if !ipNet.Contains(net.ParseIP(peerAddr)) {
return errors.New("macaroon locked to different " +
"IP range")
}
return nil
}
}
// CustomConstraint returns a function that adds a custom caveat condition to
// a macaroon.
func CustomConstraint(name, condition string) func(*macaroon.Macaroon) error {
return func(mac *macaroon.Macaroon) error {
// We rely on a name being set for the interception, so don't
// allow creating a caveat without a name in the first place.
if name == "" {
return fmt.Errorf("name cannot be empty")
}
// The inner (custom) condition is optional.
outerCondition := fmt.Sprintf("%s %s", name, condition)
if condition == "" {
outerCondition = name
}
caveat := checkers.Condition(CondLndCustom, outerCondition)
return mac.AddFirstPartyCaveat([]byte(caveat))
}
}
// CustomChecker returns a Checker function that is used by the macaroon bakery
// library to check whether a custom caveat is supported by lnd in general or
// not. Support in this context means: An additional gRPC interceptor was set up
// that validates the content (=condition) of the custom caveat. If such an
// interceptor is in place then the acceptor should return a nil error. If no
// interceptor exists for the custom caveat in the macaroon of a request context
// then a non-nil error should be returned and the macaroon is rejected as a
// whole.
func CustomChecker(acceptor CustomCaveatAcceptor) Checker {
// We return the general name of all lnd custom macaroons and a function
// that splits the outer condition to extract the name of the custom
// condition and the condition itself. In the bakery library that's used
// here, a caveat always has the following form:
//
// <condition-name> <condition-value>
//
// Because a checker function needs to be bound to the condition name we
// have to choose a static name for the first part ("lnd-custom", see
// CondLndCustom. Otherwise we'd need to register a new Checker function
// for each custom caveat that's registered. To allow for a generic
// custom caveat handling, we just add another layer and expand the
// initial <condition-value> into
//
// "<custom-condition-name> <custom-condition-value>"
//
// The full caveat string entry of a macaroon that uses this generic
// mechanism would therefore look like this:
//
// "lnd-custom <custom-condition-name> <custom-condition-value>"
checker := func(_ context.Context, _, outerCondition string) error {
if outerCondition != strings.TrimSpace(outerCondition) {
return fmt.Errorf("unexpected white space found in " +
"caveat condition")
}
if outerCondition == "" {
return fmt.Errorf("expected custom caveat, got empty " +
"string")
}
// The condition part of the original caveat is now name and
// condition of the custom caveat (we add a layer of conditions
// to allow one custom checker to work for all custom lnd
// conditions that implement arbitrary business logic).
parts := strings.Split(outerCondition, " ")
customCaveatName := parts[0]
return acceptor.CustomCaveatSupported(customCaveatName)
}
return func() (string, checkers.Func) {
return CondLndCustom, checker
}
}
// HasCustomCaveat tests if the given macaroon has a custom caveat with the
// given custom caveat name.
func HasCustomCaveat(mac *macaroon.Macaroon, customCaveatName string) bool {
if mac == nil {
return false
}
caveatPrefix := []byte(fmt.Sprintf(
"%s %s", CondLndCustom, customCaveatName,
))
for _, caveat := range mac.Caveats() {
if bytes.HasPrefix(caveat.Id, caveatPrefix) {
return true
}
}
return false
}
// GetCustomCaveatCondition returns the custom caveat condition for the given
// custom caveat name from the given macaroon.
func GetCustomCaveatCondition(mac *macaroon.Macaroon,
customCaveatName string) string {
if mac == nil {
return ""
}
caveatPrefix := []byte(fmt.Sprintf(
"%s %s ", CondLndCustom, customCaveatName,
))
for _, caveat := range mac.Caveats() {
// The caveat id has a format of
// "lnd-custom [custom-caveat-name] [custom-caveat-condition]"
// and we only want the condition part. If we match the prefix
// part we return the condition that comes after the prefix.
if bytes.HasPrefix(caveat.Id, caveatPrefix) {
caveatSplit := strings.SplitN(
string(caveat.Id),
string(caveatPrefix),
2,
)
if len(caveatSplit) == 2 {
return caveatSplit[1]
}
}
}
// We didn't find a condition for the given custom caveat name.
return ""
}
package macaroons
import (
"context"
"fmt"
)
var (
// RootKeyIDContextKey is the key to get rootKeyID from context.
RootKeyIDContextKey = contextKey{"rootkeyid"}
// ErrContextRootKeyID is used when the supplied context doesn't have
// a root key ID.
ErrContextRootKeyID = fmt.Errorf("failed to read root key ID " +
"from context")
)
// contextKey is the type we use to identify values in the context.
type contextKey struct {
Name string
}
// ContextWithRootKeyID passes the root key ID value to context.
func ContextWithRootKeyID(ctx context.Context,
value interface{}) context.Context {
return context.WithValue(ctx, RootKeyIDContextKey, value)
}
// RootKeyIDFromContext retrieves the root key ID from context using the key
// RootKeyIDContextKey.
func RootKeyIDFromContext(ctx context.Context) ([]byte, error) {
id, ok := ctx.Value(RootKeyIDContextKey).([]byte)
if !ok {
return nil, ErrContextRootKeyID
}
// Check that the id is not empty.
if len(id) == 0 {
return nil, ErrMissingRootKeyID
}
return id, nil
}
package macaroons
import (
"context"
"encoding/hex"
"fmt"
"google.golang.org/grpc/metadata"
"gopkg.in/macaroon-bakery.v2/bakery"
"gopkg.in/macaroon-bakery.v2/bakery/checkers"
macaroon "gopkg.in/macaroon.v2"
)
var (
// ErrMissingRootKeyID specifies the root key ID is missing.
ErrMissingRootKeyID = fmt.Errorf("missing root key ID")
// ErrDeletionForbidden is used when attempting to delete the
// DefaultRootKeyID or the encryptedKeyID.
ErrDeletionForbidden = fmt.Errorf("the specified ID cannot be deleted")
// PermissionEntityCustomURI is a special entity name for a permission
// that does not describe an entity:action pair but instead specifies a
// specific URI that needs to be granted access to. This can be used for
// more fine-grained permissions where a macaroon only grants access to
// certain methods instead of a whole list of methods that define the
// same entity:action pairs. For example: uri:/lnrpc.Lightning/GetInfo
// only gives access to the GetInfo call.
PermissionEntityCustomURI = "uri"
// ErrUnknownVersion is returned when a macaroon is of an unknown
// is presented.
ErrUnknownVersion = fmt.Errorf("unknown macaroon version")
// ErrInvalidID is returned when a macaroon ID is invalid.
ErrInvalidID = fmt.Errorf("invalid ID")
)
// MacaroonValidator is an interface type that can check if macaroons are valid.
type MacaroonValidator interface {
// ValidateMacaroon extracts the macaroon from the context's gRPC
// metadata, checks its signature, makes sure all specified permissions
// for the called method are contained within and finally ensures all
// caveat conditions are met. A non-nil error is returned if any of the
// checks fail.
ValidateMacaroon(ctx context.Context,
requiredPermissions []bakery.Op, fullMethod string) error
}
// ExtendedRootKeyStore is an interface augments the existing
// macaroons.RootKeyStorage interface by adding a number of additional utility
// methods such as encrypting and decrypting the root key given a password.
type ExtendedRootKeyStore interface {
bakery.RootKeyStore
// Close closes the RKS and zeros out any in-memory encryption keys.
Close() error
// CreateUnlock calls the underlying root key store's CreateUnlock and
// returns the result.
CreateUnlock(password *[]byte) error
// ListMacaroonIDs returns all the root key ID values except the value
// of encryptedKeyID.
ListMacaroonIDs(ctxt context.Context) ([][]byte, error)
// DeleteMacaroonID removes one specific root key ID. If the root key
// ID is found and deleted, it will be returned.
DeleteMacaroonID(ctxt context.Context, rootKeyID []byte) ([]byte, error)
// ChangePassword calls the underlying root key store's ChangePassword
// and returns the result.
ChangePassword(oldPw, newPw []byte) error
// GenerateNewRootKey calls the underlying root key store's
// GenerateNewRootKey and returns the result.
GenerateNewRootKey() error
// SetRootKey calls the underlying root key store's SetRootKey and
// returns the result.
SetRootKey(rootKey []byte) error
}
// Service encapsulates bakery.Bakery and adds a Close() method that zeroes the
// root key service encryption keys, as well as utility methods to validate a
// macaroon against the bakery and gRPC middleware for macaroon-based auth.
type Service struct {
bakery.Bakery
rks bakery.RootKeyStore
// ExternalValidators is a map between an absolute gRPC URIs and the
// corresponding external macaroon validator to be used for that URI.
// If no external validator for an URI is specified, the service will
// use the internal validator.
ExternalValidators map[string]MacaroonValidator
// StatelessInit denotes if the service was initialized in the stateless
// mode where no macaroon files should be created on disk.
StatelessInit bool
}
// NewService returns a service backed by the macaroon DB backend. The `checks`
// argument can be any of the `Checker` type functions defined in this package,
// or a custom checker if desired. This constructor prevents double-registration
// of checkers to prevent panics, so listing the same checker more than once is
// not harmful. Default checkers, such as those for `allow`, `time-before`,
// `declared`, and `error` caveats are registered automatically and don't need
// to be added.
func NewService(keyStore bakery.RootKeyStore, location string,
statelessInit bool, checks ...Checker) (*Service, error) {
macaroonParams := bakery.BakeryParams{
Location: location,
RootKeyStore: keyStore,
// No third-party caveat support for now.
// TODO(aakselrod): Add third-party caveat support.
Locator: nil,
Key: nil,
}
svc := bakery.New(macaroonParams)
// Register all custom caveat checkers with the bakery's checker.
// TODO(aakselrod): Add more checks as required.
checker := svc.Checker.FirstPartyCaveatChecker.(*checkers.Checker)
for _, check := range checks {
cond, fun := check()
if !isRegistered(checker, cond) {
checker.Register(cond, "std", fun)
}
}
return &Service{
Bakery: *svc,
rks: keyStore,
ExternalValidators: make(map[string]MacaroonValidator),
StatelessInit: statelessInit,
}, nil
}
// isRegistered checks to see if the required checker has already been
// registered in order to avoid a panic caused by double registration.
func isRegistered(c *checkers.Checker, name string) bool {
if c == nil {
return false
}
for _, info := range c.Info() {
if info.Name == name &&
info.Prefix == "" &&
info.Namespace == "std" {
return true
}
}
return false
}
// RegisterExternalValidator registers a custom, external macaroon validator for
// the specified absolute gRPC URI. That validator is then fully responsible to
// make sure any macaroon passed for a request to that URI is valid and
// satisfies all conditions.
func (svc *Service) RegisterExternalValidator(fullMethod string,
validator MacaroonValidator) error {
if validator == nil {
return fmt.Errorf("validator cannot be nil")
}
_, ok := svc.ExternalValidators[fullMethod]
if ok {
return fmt.Errorf("external validator for method %s already "+
"registered", fullMethod)
}
svc.ExternalValidators[fullMethod] = validator
return nil
}
// ValidateMacaroon validates the capabilities of a given request given a
// bakery service, context, and uri. Within the passed context.Context, we
// expect a macaroon to be encoded as request metadata using the key
// "macaroon".
func (svc *Service) ValidateMacaroon(ctx context.Context,
requiredPermissions []bakery.Op, fullMethod string) error {
// Get macaroon bytes from context and unmarshal into macaroon.
macHex, err := RawMacaroonFromContext(ctx)
if err != nil {
return err
}
// With the macaroon obtained, we'll now decode the hex-string encoding.
macBytes, err := hex.DecodeString(macHex)
if err != nil {
return err
}
return svc.CheckMacAuth(
ctx, macBytes, requiredPermissions, fullMethod,
)
}
// CheckMacAuth checks that the macaroon is not disobeying any caveats and is
// authorized to perform the operation the user wants to perform.
func (svc *Service) CheckMacAuth(ctx context.Context, macBytes []byte,
requiredPermissions []bakery.Op, fullMethod string) error {
// With the macaroon obtained, we'll now unmarshal it from binary into
// its concrete struct representation.
mac := &macaroon.Macaroon{}
err := mac.UnmarshalBinary(macBytes)
if err != nil {
return err
}
// Ensure that the macaroon is using the exact same version as we
// expect. In the future, we can relax this check to phase in new
// versions.
if mac.Version() != macaroon.V2 {
return fmt.Errorf("%w: %v", ErrUnknownVersion,
mac.Version())
}
// Run a similar version check on the ID used for the macaroon as well.
const minIDLength = 1
if len(mac.Id()) < minIDLength {
return ErrInvalidID
}
if mac.Id()[0] != byte(bakery.Version3) {
return ErrInvalidID
}
// Check the method being called against the permitted operation, the
// expiration time and IP address and return the result.
authChecker := svc.Checker.Auth(macaroon.Slice{mac})
_, err = authChecker.Allow(ctx, requiredPermissions...)
// If the macaroon contains broad permissions and checks out, we're
// done.
if err == nil {
return nil
}
// To also allow the special permission of "uri:<FullMethod>" to be a
// valid permission, we need to check it manually in case there is no
// broader scope permission defined.
_, err = authChecker.Allow(ctx, bakery.Op{
Entity: PermissionEntityCustomURI,
Action: fullMethod,
})
return err
}
// Close closes the database that underlies the RootKeyStore and zeroes the
// encryption keys.
func (svc *Service) Close() error {
if boltRKS, ok := svc.rks.(ExtendedRootKeyStore); ok {
return boltRKS.Close()
}
return nil
}
// CreateUnlock calls the underlying root key store's CreateUnlock and returns
// the result.
func (svc *Service) CreateUnlock(password *[]byte) error {
if boltRKS, ok := svc.rks.(ExtendedRootKeyStore); ok {
return boltRKS.CreateUnlock(password)
}
return nil
}
// NewMacaroon wraps around the function Oven.NewMacaroon with the defaults,
// - version is always bakery.LatestVersion;
// - caveats is always nil.
//
// In addition, it takes a rootKeyID parameter, and puts it into the context.
// The context is passed through Oven.NewMacaroon(), in which calls the function
// RootKey(), that reads the context for rootKeyID.
func (svc *Service) NewMacaroon(
ctx context.Context, rootKeyID []byte,
ops ...bakery.Op) (*bakery.Macaroon, error) {
// Check rootKeyID is not called with nil or empty bytes. We want the
// caller to be aware the value of root key ID used, so we won't replace
// it with the DefaultRootKeyID if not specified.
if len(rootKeyID) == 0 {
return nil, ErrMissingRootKeyID
}
// Pass the root key ID to context.
ctx = ContextWithRootKeyID(ctx, rootKeyID)
return svc.Oven.NewMacaroon(ctx, bakery.LatestVersion, nil, ops...)
}
// ListMacaroonIDs returns all the root key ID values except the value of
// encryptedKeyID.
func (svc *Service) ListMacaroonIDs(ctxt context.Context) ([][]byte, error) {
if boltRKS, ok := svc.rks.(ExtendedRootKeyStore); ok {
return boltRKS.ListMacaroonIDs(ctxt)
}
return nil, nil
}
// DeleteMacaroonID removes one specific root key ID. If the root key ID is
// found and deleted, it will be returned.
func (svc *Service) DeleteMacaroonID(ctxt context.Context,
rootKeyID []byte) ([]byte, error) {
if boltRKS, ok := svc.rks.(ExtendedRootKeyStore); ok {
return boltRKS.DeleteMacaroonID(ctxt, rootKeyID)
}
return nil, nil
}
// GenerateNewRootKey calls the underlying root key store's GenerateNewRootKey
// and returns the result.
func (svc *Service) GenerateNewRootKey() error {
if boltRKS, ok := svc.rks.(ExtendedRootKeyStore); ok {
return boltRKS.GenerateNewRootKey()
}
return nil
}
// SetRootKey calls the underlying root key store's SetRootKey and returns the
// result.
func (svc *Service) SetRootKey(rootKey []byte) error {
if boltRKS, ok := svc.rks.(ExtendedRootKeyStore); ok {
return boltRKS.SetRootKey(rootKey)
}
return nil
}
// ChangePassword calls the underlying root key store's ChangePassword and
// returns the result.
func (svc *Service) ChangePassword(oldPw, newPw []byte) error {
if boltRKS, ok := svc.rks.(ExtendedRootKeyStore); ok {
return boltRKS.ChangePassword(oldPw, newPw)
}
return nil
}
// RawMacaroonFromContext is a helper function that extracts a raw macaroon
// from the given incoming gRPC request context.
func RawMacaroonFromContext(ctx context.Context) (string, error) {
// Get macaroon bytes from context and unmarshal into macaroon.
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return "", fmt.Errorf("unable to get metadata from context")
}
if len(md["macaroon"]) != 1 {
return "", fmt.Errorf("expected 1 macaroon, got %d",
len(md["macaroon"]))
}
return md["macaroon"][0], nil
}
// SafeCopyMacaroon creates a copy of a macaroon that is safe to be used and
// modified. This is necessary because the macaroon library's own Clone() method
// is unsafe for certain edge cases, resulting in both the cloned and the
// original macaroons to be modified.
func SafeCopyMacaroon(mac *macaroon.Macaroon) (*macaroon.Macaroon, error) {
macBytes, err := mac.MarshalBinary()
if err != nil {
return nil, err
}
newMac := &macaroon.Macaroon{}
if err := newMac.UnmarshalBinary(macBytes); err != nil {
return nil, err
}
return newMac, nil
}
package macaroons
import (
"bytes"
"context"
"crypto/rand"
"fmt"
"io"
"sync"
"github.com/btcsuite/btcwallet/snacl"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/lightningnetwork/lnd/kvdb"
)
const (
// RootKeyLen is the length of a root key.
RootKeyLen = 32
)
var (
// rootKeyBucketName is the name of the root key store bucket.
rootKeyBucketName = []byte("macrootkeys")
// DefaultRootKeyID is the ID of the default root key. The first is
// just 0, to emulate the memory storage that comes with bakery.
DefaultRootKeyID = []byte("0")
// encryptionKeyID is the name of the database key that stores the
// encryption key, encrypted with a salted + hashed password. The
// format is 32 bytes of salt, and the rest is encrypted key.
encryptionKeyID = []byte("enckey")
// ErrAlreadyUnlocked specifies that the store has already been
// unlocked.
ErrAlreadyUnlocked = fmt.Errorf("macaroon store already unlocked")
// ErrStoreLocked specifies that the store needs to be unlocked with
// a password.
ErrStoreLocked = fmt.Errorf("macaroon store is locked")
// ErrPasswordRequired specifies that a nil password has been passed.
ErrPasswordRequired = fmt.Errorf("a non-nil password is required")
// ErrKeyValueForbidden is used when the root key ID uses encryptedKeyID as
// its value.
ErrKeyValueForbidden = fmt.Errorf("root key ID value is not allowed")
// ErrRootKeyBucketNotFound specifies that there is no macaroon root key
// bucket yet which can/should only happen if the store has been
// corrupted or was initialized incorrectly.
ErrRootKeyBucketNotFound = fmt.Errorf("root key bucket not found")
// ErrEncKeyNotFound specifies that there was no encryption key found
// even if one was expected to be generated.
ErrEncKeyNotFound = fmt.Errorf("macaroon encryption key not found")
// ErrDefaultRootKeyNotFound is returned when the default root key is
// not found in the DB when it is expected to be.
ErrDefaultRootKeyNotFound = fmt.Errorf("default root key not found")
)
// RootKeyStorage implements the bakery.RootKeyStorage interface.
type RootKeyStorage struct {
kvdb.Backend
encKeyMtx sync.RWMutex
encKey *snacl.SecretKey
}
// NewRootKeyStorage creates a RootKeyStorage instance.
func NewRootKeyStorage(db kvdb.Backend) (*RootKeyStorage, error) {
// If the store's bucket doesn't exist, create it.
err := kvdb.Update(db, func(tx kvdb.RwTx) error {
_, err := tx.CreateTopLevelBucket(rootKeyBucketName)
return err
}, func() {})
if err != nil {
return nil, err
}
// Return the DB wrapped in a RootKeyStorage object.
return &RootKeyStorage{
Backend: db,
encKey: nil,
}, nil
}
// CreateUnlock sets an encryption key if one is not already set, otherwise it
// checks if the password is correct for the stored encryption key.
func (r *RootKeyStorage) CreateUnlock(password *[]byte) error {
r.encKeyMtx.Lock()
defer r.encKeyMtx.Unlock()
// Check if we've already unlocked the store; return an error if so.
if r.encKey != nil {
return ErrAlreadyUnlocked
}
// Check if a nil password has been passed; return an error if so.
if password == nil {
return ErrPasswordRequired
}
return kvdb.Update(r.Backend, func(tx kvdb.RwTx) error {
bucket := tx.ReadWriteBucket(rootKeyBucketName)
if bucket == nil {
return ErrRootKeyBucketNotFound
}
dbKey := bucket.Get(encryptionKeyID)
if len(dbKey) > 0 {
// We've already stored a key, so try to unlock with
// the password.
encKey := &snacl.SecretKey{}
err := encKey.Unmarshal(dbKey)
if err != nil {
return err
}
err = encKey.DeriveKey(password)
if err != nil {
return err
}
r.encKey = encKey
return nil
}
// We haven't yet stored a key, so create a new one.
encKey, err := snacl.NewSecretKey(
password, scryptN, scryptR, scryptP,
)
if err != nil {
return err
}
err = bucket.Put(encryptionKeyID, encKey.Marshal())
if err != nil {
return err
}
r.encKey = encKey
return nil
}, func() {})
}
// ChangePassword decrypts all the macaroon root keys with the old password and
// then encrypts them again with the new password.
func (r *RootKeyStorage) ChangePassword(oldPw, newPw []byte) error {
// We need the store to already be unlocked. With this we can make sure
// that there already is a key in the DB.
if r.encKey == nil {
return ErrStoreLocked
}
// Check if a nil password has been passed; return an error if so.
if oldPw == nil || newPw == nil {
return ErrPasswordRequired
}
return kvdb.Update(r.Backend, func(tx kvdb.RwTx) error {
bucket := tx.ReadWriteBucket(rootKeyBucketName)
if bucket == nil {
return ErrRootKeyBucketNotFound
}
// The encryption key must be present, otherwise we are in the
// wrong state to change the password.
encKeyDB := bucket.Get(encryptionKeyID)
if len(encKeyDB) == 0 {
return ErrEncKeyNotFound
}
// Unmarshal parameters for old encryption key and derive the
// old key with them.
encKeyOld := &snacl.SecretKey{}
err := encKeyOld.Unmarshal(encKeyDB)
if err != nil {
return err
}
err = encKeyOld.DeriveKey(&oldPw)
if err != nil {
return err
}
// Create a new encryption key from the new password.
encKeyNew, err := snacl.NewSecretKey(
&newPw, scryptN, scryptR, scryptP,
)
if err != nil {
return err
}
// foundDefaultRootKey is used to keep track of if we have
// found and re-encrypted the default root key so that we can
// return an error if it is not found.
var foundDefaultRootKey bool
err = bucket.ForEach(func(k, v []byte) error {
// Skip the key if it is the encryption key ID since
// we do not want to re-encrypt this.
if bytes.Equal(k, encryptionKeyID) {
return nil
}
if bytes.Equal(k, DefaultRootKeyID) {
foundDefaultRootKey = true
}
// Now try to decrypt the root key with the old
// encryption key, encrypt it with the new one and then
// store it in the DB.
decryptedKey, err := encKeyOld.Decrypt(v)
if err != nil {
return err
}
encryptedKey, err := encKeyNew.Encrypt(decryptedKey)
if err != nil {
return err
}
return bucket.Put(k, encryptedKey)
})
if err != nil {
return err
}
if !foundDefaultRootKey {
return ErrDefaultRootKeyNotFound
}
// Finally, store the new encryption key parameters in the DB
// as well.
err = bucket.Put(encryptionKeyID, encKeyNew.Marshal())
if err != nil {
return err
}
r.encKey = encKeyNew
return nil
}, func() {})
}
// Get implements the Get method for the bakery.RootKeyStorage interface.
func (r *RootKeyStorage) Get(_ context.Context, id []byte) ([]byte, error) {
r.encKeyMtx.RLock()
defer r.encKeyMtx.RUnlock()
if r.encKey == nil {
return nil, ErrStoreLocked
}
var rootKey []byte
err := kvdb.View(r.Backend, func(tx kvdb.RTx) error {
bucket := tx.ReadBucket(rootKeyBucketName)
if bucket == nil {
return ErrRootKeyBucketNotFound
}
dbKey := bucket.Get(id)
if len(dbKey) == 0 {
return fmt.Errorf("root key with id %s doesn't exist",
string(id))
}
decKey, err := r.encKey.Decrypt(dbKey)
if err != nil {
return err
}
rootKey = make([]byte, len(decKey))
copy(rootKey[:], decKey)
return nil
}, func() {
rootKey = nil
})
if err != nil {
return nil, err
}
return rootKey, nil
}
// RootKey implements the RootKey method for the bakery.RootKeyStorage
// interface.
func (r *RootKeyStorage) RootKey(ctx context.Context) ([]byte, []byte, error) {
r.encKeyMtx.RLock()
defer r.encKeyMtx.RUnlock()
if r.encKey == nil {
return nil, nil, ErrStoreLocked
}
var rootKey []byte
// Read the root key ID from the context. If no key is specified in the
// context, an error will be returned.
id, err := RootKeyIDFromContext(ctx)
if err != nil {
return nil, nil, err
}
if bytes.Equal(id, encryptionKeyID) {
return nil, nil, ErrKeyValueForbidden
}
err = kvdb.Update(r.Backend, func(tx kvdb.RwTx) error {
bucket := tx.ReadWriteBucket(rootKeyBucketName)
if bucket == nil {
return ErrRootKeyBucketNotFound
}
dbKey := bucket.Get(id)
// If there's a root key stored in the bucket, decrypt it and
// return it.
if len(dbKey) != 0 {
decKey, err := r.encKey.Decrypt(dbKey)
if err != nil {
return err
}
rootKey = make([]byte, len(decKey))
copy(rootKey[:], decKey[:])
return nil
}
// Otherwise, create a new root key, encrypt it,
// and store it in the bucket.
newKey, err := generateAndStoreNewRootKey(bucket, id, r.encKey)
rootKey = newKey
return err
}, func() {
rootKey = nil
})
if err != nil {
return nil, nil, err
}
return rootKey, id, nil
}
// GenerateNewRootKey generates a new macaroon root key, replacing the previous
// root key if it existed.
func (r *RootKeyStorage) GenerateNewRootKey() error {
// We need the store to already be unlocked. With this we can make sure
// that there already is a key in the DB that can be replaced.
if r.encKey == nil {
return ErrStoreLocked
}
return kvdb.Update(r.Backend, func(tx kvdb.RwTx) error {
bucket := tx.ReadWriteBucket(rootKeyBucketName)
if bucket == nil {
return ErrRootKeyBucketNotFound
}
// The default root key should be created even if it does not
// yet exist, so we do this separately from the rest of the
// root keys.
_, err := generateAndStoreNewRootKey(
bucket, DefaultRootKeyID, r.encKey,
)
if err != nil {
return err
}
// Now iterate over all the other root keys that may exist
// and re-generate each of them.
return bucket.ForEach(func(k, v []byte) error {
if bytes.Equal(k, encryptionKeyID) {
return nil
}
if bytes.Equal(k, DefaultRootKeyID) {
return nil
}
_, err := generateAndStoreNewRootKey(
bucket, k, r.encKey,
)
return err
})
}, func() {})
}
// SetRootKey sets the default macaroon root key, replacing the previous root
// key if it existed.
func (r *RootKeyStorage) SetRootKey(rootKey []byte) error {
if r.encKey == nil {
return ErrStoreLocked
}
if len(rootKey) != RootKeyLen {
return fmt.Errorf("root key must be %v bytes",
RootKeyLen)
}
encryptedKey, err := r.encKey.Encrypt(rootKey)
if err != nil {
return err
}
return kvdb.Update(r.Backend, func(tx kvdb.RwTx) error {
bucket := tx.ReadWriteBucket(rootKeyBucketName)
if bucket == nil {
return ErrRootKeyBucketNotFound
}
return bucket.Put(DefaultRootKeyID, encryptedKey)
}, func() {})
}
// Close closes the underlying database and zeroes the encryption key stored
// in memory.
func (r *RootKeyStorage) Close() error {
r.encKeyMtx.Lock()
defer r.encKeyMtx.Unlock()
if r.encKey != nil {
r.encKey.Zero()
r.encKey = nil
}
// Since we're not responsible for _creating_ the connection to our DB
// backend, we also shouldn't close it. This should be handled
// externally as to not interfere with remote DB connections in case we
// need to open/close the store twice as happens in the password change
// case.
return nil
}
// generateAndStoreNewRootKey creates a new random RootKeyLen-byte root key,
// encrypts it with the given encryption key and stores it in the bucket.
// Any previously set key will be overwritten.
func generateAndStoreNewRootKey(bucket walletdb.ReadWriteBucket, id []byte,
key *snacl.SecretKey) ([]byte, error) {
rootKey := make([]byte, RootKeyLen)
if _, err := io.ReadFull(rand.Reader, rootKey); err != nil {
return nil, err
}
encryptedKey, err := key.Encrypt(rootKey)
if err != nil {
return nil, err
}
return rootKey, bucket.Put(id, encryptedKey)
}
// ListMacaroonIDs returns all the root key ID values except the value of
// encryptedKeyID.
func (r *RootKeyStorage) ListMacaroonIDs(_ context.Context) ([][]byte, error) {
r.encKeyMtx.RLock()
defer r.encKeyMtx.RUnlock()
// Check it's unlocked.
if r.encKey == nil {
return nil, ErrStoreLocked
}
var rootKeySlice [][]byte
// Read all the items in the bucket and append the keys, which are the
// root key IDs we want.
err := kvdb.View(r.Backend, func(tx kvdb.RTx) error {
// appendRootKey is a function closure that appends root key ID
// to rootKeySlice.
appendRootKey := func(k, _ []byte) error {
// Only append when the key value is not encryptedKeyID.
if !bytes.Equal(k, encryptionKeyID) {
rootKeySlice = append(rootKeySlice, k)
}
return nil
}
return tx.ReadBucket(rootKeyBucketName).ForEach(appendRootKey)
}, func() {
rootKeySlice = nil
})
if err != nil {
return nil, err
}
return rootKeySlice, nil
}
// DeleteMacaroonID removes one specific root key ID. If the root key ID is
// found and deleted, it will be returned.
func (r *RootKeyStorage) DeleteMacaroonID(
_ context.Context, rootKeyID []byte) ([]byte, error) {
r.encKeyMtx.RLock()
defer r.encKeyMtx.RUnlock()
// Check it's unlocked.
if r.encKey == nil {
return nil, ErrStoreLocked
}
// Check the rootKeyID is not empty.
if len(rootKeyID) == 0 {
return nil, ErrMissingRootKeyID
}
// Deleting encryptedKeyID or DefaultRootKeyID is not allowed.
if bytes.Equal(rootKeyID, encryptionKeyID) ||
bytes.Equal(rootKeyID, DefaultRootKeyID) {
return nil, ErrDeletionForbidden
}
var rootKeyIDDeleted []byte
err := kvdb.Update(r.Backend, func(tx kvdb.RwTx) error {
bucket := tx.ReadWriteBucket(rootKeyBucketName)
// Check the key can be found. If not, return nil.
if bucket.Get(rootKeyID) == nil {
return nil
}
// Once the key is found, we do the deletion.
if err := bucket.Delete(rootKeyID); err != nil {
return err
}
rootKeyIDDeleted = rootKeyID
return nil
}, func() {
rootKeyIDDeleted = nil
})
if err != nil {
return nil, err
}
return rootKeyIDDeleted, nil
}
package monitoring
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This means the
// package will not perform any logging by default until the caller requests
// it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("PROM", nil))
}
// DisableLog disables all library log output. Logging output is disabled by
// default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info. This
// should be used in preference to SetLogWriter if the caller is also using
// btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
//go:build !monitoring
// +build !monitoring
package monitoring
import (
"fmt"
"github.com/lightningnetwork/lnd/lncfg"
"google.golang.org/grpc"
)
// GetPromInterceptors returns the set of interceptors for Prometheus
// monitoring if monitoring is enabled, else empty slices. Monitoring is
// currently disabled.
func GetPromInterceptors() ([]grpc.UnaryServerInterceptor, []grpc.StreamServerInterceptor) {
return []grpc.UnaryServerInterceptor{}, []grpc.StreamServerInterceptor{}
}
// ExportPrometheusMetrics is required for lnd to compile so that Prometheus
// metric exporting can be hidden behind a build tag.
func ExportPrometheusMetrics(_ *grpc.Server, _ lncfg.Prometheus) error {
return fmt.Errorf("lnd must be built with the monitoring tag to " +
"enable exporting Prometheus metrics")
}
package msgmux
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// Subsystem defines the logging code for this subsystem.
const Subsystem = "MSGX"
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package msgmux
import (
"context"
"fmt"
"maps"
"sync"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// ErrDuplicateEndpoint is returned when an endpoint is registered with
// a name that already exists.
ErrDuplicateEndpoint = fmt.Errorf("endpoint already registered")
// ErrUnableToRouteMsg is returned when a message is unable to be
// routed to any endpoints.
ErrUnableToRouteMsg = fmt.Errorf("unable to route message")
)
// EndpointName is the name of a given endpoint. This MUST be unique across all
// registered endpoints.
type EndpointName = string
// PeerMsg is a wire message that includes the public key of the peer that sent
// it.
type PeerMsg struct {
lnwire.Message
// PeerPub is the public key of the peer that sent this message.
PeerPub btcec.PublicKey
}
// Endpoint is an interface that represents a message endpoint, or the
// sub-system that will handle processing an incoming wire message.
type Endpoint interface {
// Name returns the name of this endpoint. This MUST be unique across
// all registered endpoints.
Name() EndpointName
// CanHandle returns true if the target message can be routed to this
// endpoint.
CanHandle(msg PeerMsg) bool
// SendMessage handles the target message, and returns true if the
// message was able being processed.
SendMessage(ctx context.Context, msg PeerMsg) bool
}
// MsgRouter is an interface that represents a message router, which is generic
// sub-system capable of routing any incoming wire message to a set of
// registered endpoints.
type Router interface {
// RegisterEndpoint registers a new endpoint with the router. If a
// duplicate endpoint exists, an error is returned.
RegisterEndpoint(Endpoint) error
// UnregisterEndpoint unregisters the target endpoint from the router.
UnregisterEndpoint(EndpointName) error
// RouteMsg attempts to route the target message to a registered
// endpoint. If ANY endpoint could handle the message, then nil is
// returned. Otherwise, ErrUnableToRouteMsg is returned.
RouteMsg(PeerMsg) error
// Start starts the peer message router.
Start(ctx context.Context)
// Stop stops the peer message router.
Stop()
}
// sendQuery sends a query to the main event loop, and returns the response.
func sendQuery[Q any, R any](sendChan chan fn.Req[Q, R], queryArg Q,
quit chan struct{}) fn.Result[R] {
query, respChan := fn.NewReq[Q, R](queryArg)
if !fn.SendOrQuit(sendChan, query, quit) {
return fn.Errf[R]("router shutting down")
}
return fn.NewResult(fn.RecvResp(respChan, nil, quit))
}
// sendQueryErr is a helper function based on sendQuery that can be used when
// the query only needs an error response.
func sendQueryErr[Q any](sendChan chan fn.Req[Q, error], queryArg Q,
quitChan chan struct{}) error {
return fn.ElimEither(
sendQuery(sendChan, queryArg, quitChan).Either,
fn.Iden, fn.Iden,
)
}
// EndpointsMap is a map of all registered endpoints.
type EndpointsMap map[EndpointName]Endpoint
// MultiMsgRouter is a type of message router that is capable of routing new
// incoming messages, permitting a message to be routed to multiple registered
// endpoints.
type MultiMsgRouter struct {
startOnce sync.Once
stopOnce sync.Once
// registerChan is the channel that all new endpoints will be sent to.
registerChan chan fn.Req[Endpoint, error]
// unregisterChan is the channel that all endpoints that are to be
// removed are sent to.
unregisterChan chan fn.Req[EndpointName, error]
// msgChan is the channel that all messages will be sent to for
// processing.
msgChan chan fn.Req[PeerMsg, error]
// endpointsQueries is a channel that all queries to the endpoints map
// will be sent to.
endpointQueries chan fn.Req[Endpoint, EndpointsMap]
wg sync.WaitGroup
quit chan struct{}
}
// NewMultiMsgRouter creates a new instance of a peer message router.
func NewMultiMsgRouter() *MultiMsgRouter {
return &MultiMsgRouter{
registerChan: make(chan fn.Req[Endpoint, error]),
unregisterChan: make(chan fn.Req[EndpointName, error]),
msgChan: make(chan fn.Req[PeerMsg, error]),
endpointQueries: make(chan fn.Req[Endpoint, EndpointsMap]),
quit: make(chan struct{}),
}
}
// Start starts the peer message router.
func (p *MultiMsgRouter) Start(ctx context.Context) {
log.Infof("Starting Router")
p.startOnce.Do(func() {
p.wg.Add(1)
go p.msgRouter(ctx)
})
}
// Stop stops the peer message router.
func (p *MultiMsgRouter) Stop() {
log.Infof("Stopping Router")
p.stopOnce.Do(func() {
close(p.quit)
p.wg.Wait()
})
}
// RegisterEndpoint registers a new endpoint with the router. If a duplicate
// endpoint exists, an error is returned.
func (p *MultiMsgRouter) RegisterEndpoint(endpoint Endpoint) error {
return sendQueryErr(p.registerChan, endpoint, p.quit)
}
// UnregisterEndpoint unregisters the target endpoint from the router.
func (p *MultiMsgRouter) UnregisterEndpoint(name EndpointName) error {
return sendQueryErr(p.unregisterChan, name, p.quit)
}
// RouteMsg attempts to route the target message to a registered endpoint. If
// ANY endpoint could handle the message, then nil is returned.
func (p *MultiMsgRouter) RouteMsg(msg PeerMsg) error {
return sendQueryErr(p.msgChan, msg, p.quit)
}
// Endpoints returns a list of all registered endpoints.
func (p *MultiMsgRouter) endpoints() fn.Result[EndpointsMap] {
return sendQuery(p.endpointQueries, nil, p.quit)
}
// msgRouter is the main goroutine that handles all incoming messages.
func (p *MultiMsgRouter) msgRouter(ctx context.Context) {
defer p.wg.Done()
// endpoints is a map of all registered endpoints.
endpoints := make(map[EndpointName]Endpoint)
for {
select {
// A new endpoint was just sent in, so we'll add it to our set
// of registered endpoints.
case newEndpointMsg := <-p.registerChan:
endpoint := newEndpointMsg.Request
log.Infof("MsgRouter: registering new "+
"Endpoint(%s)", endpoint.Name())
// If this endpoint already exists, then we'll return
// an error as we require unique names.
if _, ok := endpoints[endpoint.Name()]; ok {
log.Errorf("MsgRouter: rejecting "+
"duplicate endpoint: %v",
endpoint.Name())
newEndpointMsg.Resolve(ErrDuplicateEndpoint)
continue
}
endpoints[endpoint.Name()] = endpoint
newEndpointMsg.Resolve(nil)
// A request to unregister an endpoint was just sent in, so
// we'll attempt to remove it.
case endpointName := <-p.unregisterChan:
delete(endpoints, endpointName.Request)
log.Infof("MsgRouter: unregistering "+
"Endpoint(%s)", endpointName.Request)
endpointName.Resolve(nil)
// A new message was just sent in. We'll attempt to route it to
// all the endpoints that can handle it.
case msgQuery := <-p.msgChan:
msg := msgQuery.Request
// Loop through all the endpoints and send the message
// to those that can handle it the message.
var couldSend bool
for _, endpoint := range endpoints {
if endpoint.CanHandle(msg) {
log.Tracef("MsgRouter: sending "+
"msg %T to endpoint %s", msg,
endpoint.Name())
sent := endpoint.SendMessage(ctx, msg)
couldSend = couldSend || sent
}
}
var err error
if !couldSend {
log.Tracef("MsgRouter: unable to route "+
"msg %T", msg.Message)
err = ErrUnableToRouteMsg
}
msgQuery.Resolve(err)
// A query for the endpoint state just came in, we'll send back
// a copy of our current state.
case endpointQuery := <-p.endpointQueries:
endpointsCopy := make(EndpointsMap, len(endpoints))
maps.Copy(endpointsCopy, endpoints)
endpointQuery.Resolve(endpointsCopy)
case <-p.quit:
return
}
}
}
// A compile time check to ensure MultiMsgRouter implements the MsgRouter
// interface.
var _ Router = (*MultiMsgRouter)(nil)
package multimutex
import (
"fmt"
"sync"
)
// cntMutex is a struct that wraps a counter and a mutex, and is used to keep
// track of the number of goroutines waiting for access to the
// mutex, such that we can forget about it when the counter is zero.
type cntMutex struct {
cnt int
sync.Mutex
}
// Mutex is a struct that keeps track of a set of mutexes with a given ID. It
// can be used for making sure only one goroutine gets given the mutex per ID.
type Mutex[T comparable] struct {
// mutexes is a map of IDs to a cntMutex. The cntMutex for a given ID
// will hold the mutex to be used by all callers requesting access for
// the ID, in addition to the count of callers.
mutexes map[T]*cntMutex
// mapMtx is used to give synchronize concurrent access to the mutexes
// map.
mapMtx sync.Mutex
}
// NewMutex creates a new Mutex.
func NewMutex[T comparable]() *Mutex[T] {
return &Mutex[T]{
mutexes: make(map[T]*cntMutex),
}
}
// Lock locks the mutex by the given ID. If the mutex is already locked by this
// ID, Lock blocks until the mutex is available.
func (c *Mutex[T]) Lock(id T) {
c.mapMtx.Lock()
mtx, ok := c.mutexes[id]
if ok {
// If the mutex already existed in the map, we increment its
// counter, to indicate that there now is one more goroutine
// waiting for it.
mtx.cnt++
} else {
// If it was not in the map, it means no other goroutine has
// locked the mutex for this ID, and we can create a new mutex
// with count 1 and add it to the map.
mtx = &cntMutex{
cnt: 1,
}
c.mutexes[id] = mtx
}
c.mapMtx.Unlock()
// Acquire the mutex for this ID.
mtx.Lock()
}
// Unlock unlocks the mutex by the given ID. It is a run-time error if the
// mutex is not locked by the ID on entry to Unlock.
func (c *Mutex[T]) Unlock(id T) {
// Since we are done with all the work for this update, we update the
// map to reflect that.
c.mapMtx.Lock()
mtx, ok := c.mutexes[id]
if !ok {
// The mutex not existing in the map means an unlock for an ID
// not currently locked was attempted.
panic(fmt.Sprintf("double unlock for id %v",
id))
}
// Decrement the counter. If the count goes to zero, it means this
// caller was the last one to wait for the mutex, and we can delete it
// from the map. We can do this safely since we are under the mapMtx,
// meaning that all other goroutines waiting for the mutex already have
// incremented it, or will create a new mutex when they get the mapMtx.
mtx.cnt--
if mtx.cnt == 0 {
delete(c.mutexes, id)
}
c.mapMtx.Unlock()
// Unlock the mutex for this ID.
mtx.Unlock()
}
package nat
import (
"fmt"
"net"
"sync"
"time"
"github.com/jackpal/gateway"
natpmp "github.com/jackpal/go-nat-pmp"
)
// Compile-time check to ensure PMP implements the Traversal interface.
var _ Traversal = (*PMP)(nil)
// PMP is a concrete implementation of the Traversal interface that uses the
// NAT-PMP technique.
type PMP struct {
client *natpmp.Client
forwardedPortsMtx sync.Mutex
forwardedPorts map[uint16]struct{}
}
// DiscoverPMP attempts to scan the local network for a NAT-PMP enabled device
// within the given timeout.
func DiscoverPMP(timeout time.Duration) (*PMP, error) {
// Retrieve the gateway IP address of the local network.
gatewayIP, err := gateway.DiscoverGateway()
if err != nil {
return nil, err
}
pmp := &PMP{
client: natpmp.NewClientWithTimeout(gatewayIP, timeout),
forwardedPorts: make(map[uint16]struct{}),
}
// We'll then attempt to retrieve the external IP address of this
// device to ensure it is not behind multiple NATs.
if _, err := pmp.ExternalIP(); err != nil {
return nil, err
}
return pmp, nil
}
// ExternalIP returns the external IP address of the NAT-PMP enabled device.
func (p *PMP) ExternalIP() (net.IP, error) {
res, err := p.client.GetExternalAddress()
if err != nil {
return nil, err
}
ip := net.IP(res.ExternalIPAddress[:])
if isPrivateIP(ip) {
return nil, ErrMultipleNAT
}
return ip, nil
}
// AddPortMapping enables port forwarding for the given port.
func (p *PMP) AddPortMapping(port uint16) error {
p.forwardedPortsMtx.Lock()
defer p.forwardedPortsMtx.Unlock()
_, err := p.client.AddPortMapping("tcp", int(port), int(port), 0)
if err != nil {
return err
}
p.forwardedPorts[port] = struct{}{}
return nil
}
// DeletePortMapping disables port forwarding for the given port.
func (p *PMP) DeletePortMapping(port uint16) error {
p.forwardedPortsMtx.Lock()
defer p.forwardedPortsMtx.Unlock()
if _, exists := p.forwardedPorts[port]; !exists {
return fmt.Errorf("port %d is not being forwarded", port)
}
_, err := p.client.AddPortMapping("tcp", int(port), 0, 0)
if err != nil {
return err
}
delete(p.forwardedPorts, port)
return nil
}
// ForwardedPorts returns a list of ports currently being forwarded.
func (p *PMP) ForwardedPorts() []uint16 {
p.forwardedPortsMtx.Lock()
defer p.forwardedPortsMtx.Unlock()
ports := make([]uint16, 0, len(p.forwardedPorts))
for port := range p.forwardedPorts {
ports = append(ports, port)
}
return ports
}
// Name returns the name of the specific NAT traversal technique used.
func (p *PMP) Name() string {
return "NAT-PMP"
}
package nat
import (
"errors"
"net"
)
var (
// private24BitBlock contains the set of private IPv4 addresses within
// the 10.0.0.0/8 address space.
private24BitBlock *net.IPNet
// private20BitBlock contains the set of private IPv4 addresses within
// the 172.16.0.0/12 address space.
private20BitBlock *net.IPNet
// private16BitBlock contains the set of private IPv4 addresses within
// the 192.168.0.0/16 address space.
private16BitBlock *net.IPNet
// ErrMultipleNAT is an error returned when multiple NATs have been
// detected.
ErrMultipleNAT = errors.New("multiple NATs detected")
)
func init() {
_, private24BitBlock, _ = net.ParseCIDR("10.0.0.0/8")
_, private20BitBlock, _ = net.ParseCIDR("172.16.0.0/12")
_, private16BitBlock, _ = net.ParseCIDR("192.168.0.0/16")
}
// Traversal is an interface that brings together the different NAT traversal
// techniques.
type Traversal interface {
// ExternalIP returns the external IP address.
ExternalIP() (net.IP, error)
// AddPortMapping adds a port mapping for the given port between the
// private and public addresses.
AddPortMapping(port uint16) error
// DeletePortMapping deletes a port mapping for the given port between
// the private and public addresses.
DeletePortMapping(port uint16) error
// ForwardedPorts returns the ports currently being forwarded using NAT
// traversal.
ForwardedPorts() []uint16
// Name returns the name of the specific NAT traversal technique used.
Name() string
}
// isPrivateIP determines if the IP is private.
func isPrivateIP(ip net.IP) bool {
return private24BitBlock.Contains(ip) ||
private20BitBlock.Contains(ip) || private16BitBlock.Contains(ip)
}
package nat
import (
"context"
"fmt"
"net"
"sync"
upnp "github.com/NebulousLabs/go-upnp"
)
// Compile-time check to ensure UPnP implements the Traversal interface.
var _ Traversal = (*UPnP)(nil)
// UPnP is a concrete implementation of the Traversal interface that uses the
// UPnP technique.
type UPnP struct {
device *upnp.IGD
forwardedPortsMtx sync.Mutex
forwardedPorts map[uint16]struct{}
}
// DiscoverUPnP scans the local network for a UPnP enabled device.
func DiscoverUPnP(ctx context.Context) (*UPnP, error) {
// Scan the local network for a UPnP-enabled device.
device, err := upnp.DiscoverCtx(ctx)
if err != nil {
return nil, err
}
u := &UPnP{
device: device,
forwardedPorts: make(map[uint16]struct{}),
}
// We'll then attempt to retrieve the external IP address of this
// device to ensure it is not behind multiple NATs.
if _, err := u.ExternalIP(); err != nil {
return nil, err
}
return u, nil
}
// ExternalIP returns the external IP address of the UPnP enabled device.
func (u *UPnP) ExternalIP() (net.IP, error) {
ip, err := u.device.ExternalIP()
if err != nil {
return nil, err
}
if isPrivateIP(net.ParseIP(ip)) {
return nil, ErrMultipleNAT
}
return net.ParseIP(ip), nil
}
// AddPortMapping enables port forwarding for the given port.
func (u *UPnP) AddPortMapping(port uint16) error {
u.forwardedPortsMtx.Lock()
defer u.forwardedPortsMtx.Unlock()
if err := u.device.Forward(port, ""); err != nil {
return err
}
u.forwardedPorts[port] = struct{}{}
return nil
}
// DeletePortMapping disables port forwarding for the given port.
func (u *UPnP) DeletePortMapping(port uint16) error {
u.forwardedPortsMtx.Lock()
defer u.forwardedPortsMtx.Unlock()
if _, exists := u.forwardedPorts[port]; !exists {
return fmt.Errorf("port %d is not being forwarded", port)
}
if err := u.device.Clear(port); err != nil {
return err
}
delete(u.forwardedPorts, port)
return nil
}
// ForwardedPorts returns a list of ports currently being forwarded.
func (u *UPnP) ForwardedPorts() []uint16 {
u.forwardedPortsMtx.Lock()
defer u.forwardedPortsMtx.Unlock()
ports := make([]uint16, 0, len(u.forwardedPorts))
for port := range u.forwardedPorts {
ports = append(ports, port)
}
return ports
}
// Name returns the name of the specific NAT traversal technique used.
func (u *UPnP) Name() string {
return "UPnP"
}
package netann
import (
"errors"
"sync"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// ErrChanStatusManagerExiting signals that a shutdown of the
// ChanStatusManager has already been requested.
ErrChanStatusManagerExiting = errors.New("chan status manager exiting")
// ErrInvalidTimeoutConstraints signals that the ChanStatusManager could
// not be initialized because the timeouts and sample intervals were
// malformed.
ErrInvalidTimeoutConstraints = errors.New("chan-enable-timeout + " +
"chan-status-sample-interval must <= chan-disable-timeout " +
"and all three chan configs must be positive integers")
// ErrEnableInactiveChan signals that a request to enable a channel
// could not be completed because the channel isn't actually active at
// the time of the request.
ErrEnableInactiveChan = errors.New("unable to enable channel which " +
"is not currently active")
// ErrEnableManuallyDisabledChan signals that an automatic / background
// request to enable a channel could not be completed because the channel
// was manually disabled.
ErrEnableManuallyDisabledChan = errors.New("unable to enable channel " +
"which was manually disabled")
)
// ChanStatusConfig holds parameters and resources required by the
// ChanStatusManager to perform its duty.
type ChanStatusConfig struct {
// OurPubKey is the public key identifying this node on the network.
OurPubKey *btcec.PublicKey
// OurKeyLoc is the locator for the public key identifying this node on
// the network.
OurKeyLoc keychain.KeyLocator
// MessageSigner signs messages that validate under OurPubKey.
MessageSigner lnwallet.MessageSigner
// IsChannelActive checks whether the channel identified by the provided
// ChannelID is considered active. This should only return true if the
// channel has been sufficiently confirmed, the channel has received
// ChannelReady, and the remote peer is online.
IsChannelActive func(lnwire.ChannelID) bool
// ApplyChannelUpdate processes new ChannelUpdates signed by our node by
// updating our local routing table and broadcasting the update to our
// peers.
ApplyChannelUpdate func(*lnwire.ChannelUpdate1, *wire.OutPoint,
bool) error
// DB stores the set of channels that are to be monitored.
DB DB
// Graph stores the channel info and policies for channels in DB.
Graph ChannelGraph
// ChanEnableTimeout is the duration a peer's connect must remain stable
// before attempting to re-enable the channel.
//
// NOTE: This value is only used to verify that the relation between
// itself, ChanDisableTimeout, and ChanStatusSampleInterval is correct.
// The user is still responsible for ensuring that the same duration
// elapses before attempting to re-enable a channel.
ChanEnableTimeout time.Duration
// ChanDisableTimeout is the duration the manager will wait after
// detecting that a channel has become inactive before broadcasting an
// update to disable the channel.
ChanDisableTimeout time.Duration
// ChanStatusSampleInterval is the long-polling interval used by the
// manager to check if the channels being monitored have become
// inactive.
ChanStatusSampleInterval time.Duration
}
// ChanStatusManager facilitates requests to enable or disable a channel via a
// network announcement that sets the disable bit on the ChannelUpdate
// accordingly. The manager will periodically sample to detect cases where a
// link has become inactive, and facilitate the process of disabling the channel
// passively. The ChanStatusManager state machine is designed to reduce the
// likelihood of spamming the network with updates for flapping peers.
type ChanStatusManager struct {
started sync.Once
stopped sync.Once
cfg *ChanStatusConfig
// ourPubKeyBytes is the serialized compressed pubkey of our node.
ourPubKeyBytes []byte
// chanStates contains the set of channels being monitored for status
// updates. Access to the map is serialized by the statusManager's event
// loop.
chanStates channelStates
// enableRequests pipes external requests to enable a channel into the
// primary event loop.
enableRequests chan statusRequest
// disableRequests pipes external requests to disable a channel into the
// primary event loop.
disableRequests chan statusRequest
// autoRequests pipes external requests to restore automatic channel
// state management into the primary event loop.
autoRequests chan statusRequest
// statusSampleTicker fires at the interval prescribed by
// ChanStatusSampleInterval to check if channels in chanStates have
// become inactive.
statusSampleTicker *time.Ticker
wg sync.WaitGroup
quit chan struct{}
}
// NewChanStatusManager initializes a new ChanStatusManager using the given
// configuration. An error is returned if the timeouts and sample interval fail
// to meet do not satisfy the equation:
//
// ChanEnableTimeout + ChanStatusSampleInterval > ChanDisableTimeout.
func NewChanStatusManager(cfg *ChanStatusConfig) (*ChanStatusManager, error) {
// Assert that the config timeouts are properly formed. We require the
// enable_timeout + sample_interval to be less than or equal to the
// disable_timeout and that all are positive values. A peer that
// disconnects and reconnects quickly may cause a disable update to be
// sent, shortly followed by a re-enable. Ensuring a healthy separation
// helps dampen the possibility of spamming updates that toggle the
// disable bit for such events.
if cfg.ChanStatusSampleInterval <= 0 {
return nil, ErrInvalidTimeoutConstraints
}
if cfg.ChanEnableTimeout <= 0 {
return nil, ErrInvalidTimeoutConstraints
}
if cfg.ChanDisableTimeout <= 0 {
return nil, ErrInvalidTimeoutConstraints
}
if cfg.ChanEnableTimeout+cfg.ChanStatusSampleInterval >
cfg.ChanDisableTimeout {
return nil, ErrInvalidTimeoutConstraints
}
return &ChanStatusManager{
cfg: cfg,
ourPubKeyBytes: cfg.OurPubKey.SerializeCompressed(),
chanStates: make(channelStates),
statusSampleTicker: time.NewTicker(cfg.ChanStatusSampleInterval),
enableRequests: make(chan statusRequest),
disableRequests: make(chan statusRequest),
autoRequests: make(chan statusRequest),
quit: make(chan struct{}),
}, nil
}
// Start safely starts the ChanStatusManager.
func (m *ChanStatusManager) Start() error {
var err error
m.started.Do(func() {
log.Info("Channel Status Manager starting")
err = m.start()
})
return err
}
func (m *ChanStatusManager) start() error {
channels, err := m.fetchChannels()
if err != nil {
return err
}
// Populate the initial states of all confirmed, public channels.
for _, c := range channels {
_, err := m.getOrInitChanStatus(c.FundingOutpoint)
switch {
// If we can't retrieve the edge info for this channel, it may
// have been pruned from the channel graph but not yet from our
// set of channels. We'll skip it as we can't determine its
// initial state.
case errors.Is(err, graphdb.ErrEdgeNotFound):
log.Warnf("Unable to find channel policies for %v, "+
"skipping. This is typical if the channel is "+
"in the process of closing.", c.FundingOutpoint)
continue
// If we are in the process of opening a channel, the funding
// manager might not have added the ChannelUpdate to the graph
// yet. We'll ignore the channel for now.
case err == ErrUnableToExtractChanUpdate:
log.Warnf("Unable to find channel policies for %v, "+
"skipping. This is typical if the channel is "+
"in the process of being opened.",
c.FundingOutpoint)
continue
case err != nil:
return err
}
}
m.wg.Add(1)
go m.statusManager()
return nil
}
// Stop safely shuts down the ChanStatusManager.
func (m *ChanStatusManager) Stop() error {
m.stopped.Do(func() {
log.Info("Channel Status Manager shutting down...")
defer log.Debug("Channel Status Manager shutdown complete")
close(m.quit)
m.wg.Wait()
})
return nil
}
// RequestEnable submits a request to immediately enable a channel identified by
// the provided outpoint. If the channel is already enabled, no action will be
// taken. If the channel is marked pending-disable the channel will be returned
// to an active status as the scheduled disable was never sent. Otherwise if the
// channel is found to be disabled, a new announcement will be signed with the
// disabled bit cleared and broadcast to the network.
//
// If the channel was manually disabled and RequestEnable is called with
// manual = false, then the request will be ignored.
//
// NOTE: RequestEnable should only be called after a stable connection with the
// channel's peer has lasted at least the ChanEnableTimeout. Failure to do so
// may result in behavior that deviates from the expected behavior of the state
// machine.
func (m *ChanStatusManager) RequestEnable(outpoint wire.OutPoint,
manual bool) error {
return m.submitRequest(m.enableRequests, outpoint, manual)
}
// RequestDisable submits a request to immediately disable a channel identified
// by the provided outpoint. If the channel is already disabled, no action will
// be taken. Otherwise, a new announcement will be signed with the disabled bit
// set and broadcast to the network.
//
// The channel state will be changed to either ChanStatusDisabled or
// ChanStatusManuallyDisabled, depending on the passed-in value of manual. In
// particular, note the following state transitions:
//
// current state | manual | new state
// ---------------------------------------------------
// Disabled | false | Disabled
// ManuallyDisabled | false | ManuallyDisabled (*)
// Disabled | true | ManuallyDisabled
// ManuallyDisabled | true | ManuallyDisabled
//
// (*) If a channel was manually disabled, subsequent automatic / background
//
// requests to disable the channel do not change the fact that the channel
// was manually disabled.
func (m *ChanStatusManager) RequestDisable(outpoint wire.OutPoint,
manual bool) error {
return m.submitRequest(m.disableRequests, outpoint, manual)
}
// RequestAuto submits a request to restore automatic channel state management.
// If the channel is in the state ChanStatusManuallyDisabled, it will be moved
// back to the state ChanStatusDisabled. Otherwise, no action will be taken.
func (m *ChanStatusManager) RequestAuto(outpoint wire.OutPoint) error {
return m.submitRequest(m.autoRequests, outpoint, true)
}
// statusRequest is passed to the statusManager to request a change in status
// for a particular channel point. The exact action is governed by passing the
// request through one of the enableRequests or disableRequests channels.
type statusRequest struct {
outpoint wire.OutPoint
manual bool
errChan chan error
}
// submitRequest sends a request for either enabling or disabling a particular
// outpoint and awaits an error response. The request type is dictated by the
// reqChan passed in, which can be either of the enableRequests or
// disableRequests channels.
func (m *ChanStatusManager) submitRequest(reqChan chan statusRequest,
outpoint wire.OutPoint, manual bool) error {
req := statusRequest{
outpoint: outpoint,
manual: manual,
errChan: make(chan error, 1),
}
select {
case reqChan <- req:
case <-m.quit:
return ErrChanStatusManagerExiting
}
select {
case err := <-req.errChan:
return err
case <-m.quit:
return ErrChanStatusManagerExiting
}
}
// statusManager is the primary event loop for the ChanStatusManager, providing
// the necessary synchronization primitive to protect access to the chanStates
// map. All requests to explicitly enable or disable a channel are processed
// within this method. The statusManager will also periodically poll the active
// status of channels within the htlcswitch to see if a disable announcement
// should be scheduled or broadcast.
//
// NOTE: This method MUST be run as a goroutine.
func (m *ChanStatusManager) statusManager() {
defer m.wg.Done()
for {
select {
// Process any requests to mark channel as enabled.
case req := <-m.enableRequests:
req.errChan <- m.processEnableRequest(req.outpoint, req.manual)
// Process any requests to mark channel as disabled.
case req := <-m.disableRequests:
req.errChan <- m.processDisableRequest(req.outpoint, req.manual)
// Process any requests to restore automatic channel state management.
case req := <-m.autoRequests:
req.errChan <- m.processAutoRequest(req.outpoint)
// Use long-polling to detect when channels become inactive.
case <-m.statusSampleTicker.C:
// First, do a sweep and mark any ChanStatusEnabled
// channels that are not active within the htlcswitch as
// ChanStatusPendingDisabled. The channel will then be
// disabled if no request to enable is received before
// the ChanDisableTimeout expires.
m.markPendingInactiveChannels()
// Now, do another sweep to disable any channels that
// were marked in a prior iteration as pending inactive
// if the inactive chan timeout has elapsed.
m.disableInactiveChannels()
case <-m.quit:
return
}
}
}
// processEnableRequest attempts to enable the given outpoint.
//
// - If the channel is not active at the time of the request,
// ErrEnableInactiveChan will be returned.
// - If the channel was in the ManuallyDisabled state and manual = false,
// the request will be ignored and ErrEnableManuallyDisabledChan will be
// returned.
// - Otherwise, the status of the channel in chanStates will be
// ChanStatusEnabled and the method will return nil.
//
// An update will be broadcast only if the channel is currently disabled,
// otherwise no update will be sent on the network.
func (m *ChanStatusManager) processEnableRequest(outpoint wire.OutPoint,
manual bool) error {
curState, err := m.getOrInitChanStatus(outpoint)
if err != nil {
return err
}
// Quickly check to see if the requested channel is active within the
// htlcswitch and return an error if it isn't.
chanID := lnwire.NewChanIDFromOutPoint(outpoint)
if !m.cfg.IsChannelActive(chanID) {
return ErrEnableInactiveChan
}
switch curState.Status {
// Channel is already enabled, nothing to do.
case ChanStatusEnabled:
log.Debugf("Channel(%v) already enabled, skipped "+
"announcement", outpoint)
return nil
// The channel is enabled, though we are now canceling the scheduled
// disable.
case ChanStatusPendingDisabled:
log.Debugf("Channel(%v) already enabled, canceling scheduled "+
"disable", outpoint)
// We'll sign a new update if the channel is still disabled.
case ChanStatusManuallyDisabled:
if !manual {
return ErrEnableManuallyDisabledChan
}
fallthrough
case ChanStatusDisabled:
log.Infof("Announcing channel(%v) enabled", outpoint)
err := m.signAndSendNextUpdate(outpoint, false)
if err != nil {
return err
}
}
m.chanStates.markEnabled(outpoint)
return nil
}
// processDisableRequest attempts to disable the given outpoint. If the method
// returns nil, the status of the channel in chanStates will be either
// ChanStatusDisabled or ChanStatusManuallyDisabled, depending on the
// passed-in value of manual.
//
// An update will only be sent if the channel has a status other than
// ChanStatusEnabled, otherwise no update will be sent on the network.
func (m *ChanStatusManager) processDisableRequest(outpoint wire.OutPoint,
manual bool) error {
curState, err := m.getOrInitChanStatus(outpoint)
if err != nil {
return err
}
status := curState.Status
if status == ChanStatusEnabled || status == ChanStatusPendingDisabled {
log.Infof("Announcing channel(%v) disabled [requested]",
outpoint)
err := m.signAndSendNextUpdate(outpoint, true)
if err != nil {
return err
}
}
// Typically, a request to disable a channel via the manager's public
// interface signals that the channel is being closed.
//
// If we don't need to keep track of a manual request to disable the
// channel, then we can remove the outpoint to free up space in the map
// of channel states. If for some reason the channel isn't closed, the
// state will be repopulated on subsequent calls to the manager's public
// interface via a db lookup, or on startup.
if manual {
m.chanStates.markManuallyDisabled(outpoint)
} else if status != ChanStatusManuallyDisabled {
delete(m.chanStates, outpoint)
}
return nil
}
// processAutoRequest attempts to restore automatic channel state management
// for the given outpoint. If the method returns nil, the state of the channel
// will no longer be ChanStatusManuallyDisabled (currently the only state in
// which automatic / background requests are ignored).
//
// No update will be sent on the network.
func (m *ChanStatusManager) processAutoRequest(outpoint wire.OutPoint) error {
curState, err := m.getOrInitChanStatus(outpoint)
if err != nil {
return err
}
if curState.Status == ChanStatusManuallyDisabled {
log.Debugf("Restoring automatic control for manually disabled "+
"channel(%v)", outpoint)
m.chanStates.markDisabled(outpoint)
}
return nil
}
// markPendingInactiveChannels performs a sweep of the database's active
// channels and determines which, if any, should have a disable announcement
// scheduled. Once an active channel is determined to be pending-inactive, one
// of two transitions can follow. Either the channel is disabled because no
// request to enable is received before the scheduled disable is broadcast, or
// the channel is successfully re-enabled and channel is returned to an active
// state from the POV of the ChanStatusManager.
func (m *ChanStatusManager) markPendingInactiveChannels() {
channels, err := m.fetchChannels()
if err != nil {
log.Errorf("Unable to load active channels: %v", err)
return
}
for _, c := range channels {
// Determine the initial status of the active channel, and
// populate the entry in the chanStates map.
curState, err := m.getOrInitChanStatus(c.FundingOutpoint)
if err != nil {
log.Errorf("Unable to retrieve chan status for "+
"Channel(%v): %v", c.FundingOutpoint, err)
continue
}
// If the channel's status is not ChanStatusEnabled, we are
// done. Either it is already disabled, or it has been marked
// ChanStatusPendingDisable meaning that we have already
// scheduled the time at which it will be disabled.
if curState.Status != ChanStatusEnabled {
continue
}
// If our bookkeeping shows the channel as active, sample the
// htlcswitch to see if it believes the link is also active. If
// so, we will skip marking it as ChanStatusPendingDisabled.
chanID := lnwire.NewChanIDFromOutPoint(c.FundingOutpoint)
if m.cfg.IsChannelActive(chanID) {
continue
}
// Otherwise, we discovered that this link was inactive within
// the switch. Compute the time at which we will send out a
// disable if the peer is unable to reestablish a stable
// connection.
disableTime := time.Now().Add(m.cfg.ChanDisableTimeout)
log.Debugf("Marking channel(%v) pending-inactive",
c.FundingOutpoint)
m.chanStates.markPendingDisabled(c.FundingOutpoint, disableTime)
}
}
// disableInactiveChannels scans through the set of monitored channels, and
// broadcast a disable update for any pending inactive channels whose
// SendDisableTime has been superseded by the current time.
func (m *ChanStatusManager) disableInactiveChannels() {
// Now, disable any channels whose inactive chan timeout has elapsed.
now := time.Now()
for outpoint, state := range m.chanStates {
// Ignore statuses that are not in the pending-inactive state.
if state.Status != ChanStatusPendingDisabled {
continue
}
// Ignore statuses for which the disable timeout has not
// expired.
if state.SendDisableTime.After(now) {
continue
}
log.Infof("Announcing channel(%v) disabled "+
"[detected]", outpoint)
// Sign an update disabling the channel.
err := m.signAndSendNextUpdate(outpoint, true)
if err != nil {
log.Errorf("Unable to sign update disabling "+
"channel(%v): %v", outpoint, err)
// If the edge was not found, this is a likely indicator
// that the channel has been closed. Thus we remove the
// outpoint from the set of tracked outpoints to prevent
// further attempts.
if errors.Is(err, graphdb.ErrEdgeNotFound) {
log.Debugf("Removing channel(%v) from "+
"consideration for passive disabling",
outpoint)
delete(m.chanStates, outpoint)
}
continue
}
// Record that the channel has now been disabled.
m.chanStates.markDisabled(outpoint)
}
}
// fetchChannels returns the working set of channels managed by the
// ChanStatusManager. The returned channels are filtered to only contain public
// channels.
func (m *ChanStatusManager) fetchChannels() ([]*channeldb.OpenChannel, error) {
allChannels, err := m.cfg.DB.FetchAllOpenChannels()
if err != nil {
return nil, err
}
// Filter out private channels.
var channels []*channeldb.OpenChannel
for _, c := range allChannels {
// We'll skip any private channels, as they aren't used for
// routing within the network by other nodes.
if c.ChannelFlags&lnwire.FFAnnounceChannel == 0 {
continue
}
channels = append(channels, c)
}
return channels, nil
}
// signAndSendNextUpdate computes and signs a valid update for the passed
// outpoint, with the ability to toggle the disabled bit. The new update will
// use the current time as the update's timestamp, or increment the old
// timestamp by 1 to ensure the update can propagate. If signing is successful,
// the new update will be sent out on the network.
func (m *ChanStatusManager) signAndSendNextUpdate(outpoint wire.OutPoint,
disabled bool) error {
// Retrieve the latest update for this channel. We'll use this
// as our starting point to send the new update.
chanUpdate, private, err := m.fetchLastChanUpdateByOutPoint(outpoint)
if err != nil {
return err
}
err = SignChannelUpdate(
m.cfg.MessageSigner, m.cfg.OurKeyLoc, chanUpdate,
ChanUpdSetDisable(disabled), ChanUpdSetTimestamp,
)
if err != nil {
return err
}
return m.cfg.ApplyChannelUpdate(chanUpdate, &outpoint, private)
}
// fetchLastChanUpdateByOutPoint fetches the latest policy for our direction of
// a channel, and crafts a new ChannelUpdate with this policy. Returns an error
// in case our ChannelEdgePolicy is not found in the database. Also returns if
// the channel is private by checking AuthProof for nil.
func (m *ChanStatusManager) fetchLastChanUpdateByOutPoint(op wire.OutPoint) (
*lnwire.ChannelUpdate1, bool, error) {
// Get the edge info and policies for this channel from the graph.
info, edge1, edge2, err := m.cfg.Graph.FetchChannelEdgesByOutpoint(&op)
if err != nil {
return nil, false, err
}
update, err := ExtractChannelUpdate(
m.ourPubKeyBytes, info, edge1, edge2,
)
return update, info.AuthProof == nil, err
}
// loadInitialChanState determines the initial ChannelState for a particular
// outpoint. The initial ChanStatus for a given outpoint will either be
// ChanStatusEnabled or ChanStatusDisabled, determined by inspecting the bits on
// the most recent announcement. An error is returned if the latest update could
// not be retrieved.
func (m *ChanStatusManager) loadInitialChanState(
outpoint *wire.OutPoint) (ChannelState, error) {
lastUpdate, _, err := m.fetchLastChanUpdateByOutPoint(*outpoint)
if err != nil {
return ChannelState{}, err
}
// Determine the channel's starting status by inspecting the disable bit
// on last announcement we sent out.
var initialStatus ChanStatus
if lastUpdate.ChannelFlags&lnwire.ChanUpdateDisabled == 0 {
initialStatus = ChanStatusEnabled
} else {
initialStatus = ChanStatusDisabled
}
return ChannelState{
Status: initialStatus,
}, nil
}
// getOrInitChanStatus retrieves the current ChannelState for a particular
// outpoint. If the chanStates map already contains an entry for the outpoint,
// the value in the map is returned. Otherwise, the outpoint's initial status is
// computed and updated in the chanStates map before being returned.
func (m *ChanStatusManager) getOrInitChanStatus(
outpoint wire.OutPoint) (ChannelState, error) {
// Return the current ChannelState from the chanStates map if it is
// already known to the ChanStatusManager.
if curState, ok := m.chanStates[outpoint]; ok {
return curState, nil
}
// Otherwise, determine the initial state based on the last update we
// sent for the outpoint.
initialState, err := m.loadInitialChanState(&outpoint)
if err != nil {
return ChannelState{}, err
}
// Finally, store the initial state in the chanStates map. This will
// serve as are up-to-date view of the outpoint's current status, in
// addition to making the channel eligible for detecting inactivity.
m.chanStates[outpoint] = initialState
return initialState, nil
}
package netann
import (
"bytes"
"errors"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// chanAnn2MsgName is a string representing the name of the
// ChannelAnnouncement2 message. This string will be used during the
// construction of the tagged hash message to be signed when producing
// the signature for the ChannelAnnouncement2 message.
chanAnn2MsgName = "channel_announcement_2"
// chanAnn2SigFieldName is the name of the signature field of the
// ChannelAnnouncement2 message. This string will be used during the
// construction of the tagged hash message to be signed when producing
// the signature for the ChannelAnnouncement2 message.
chanAnn2SigFieldName = "signature"
)
// CreateChanAnnouncement is a helper function which creates all channel
// announcements given the necessary channel related database items. This
// function is used to transform out database structs into the corresponding wire
// structs for announcing new channels to other peers, or simply syncing up a
// peer's initial routing table upon connect.
func CreateChanAnnouncement(chanProof *models.ChannelAuthProof,
chanInfo *models.ChannelEdgeInfo,
e1, e2 *models.ChannelEdgePolicy) (*lnwire.ChannelAnnouncement1,
*lnwire.ChannelUpdate1, *lnwire.ChannelUpdate1, error) {
// First, using the parameters of the channel, along with the channel
// authentication chanProof, we'll create re-create the original
// authenticated channel announcement.
chanID := lnwire.NewShortChanIDFromInt(chanInfo.ChannelID)
chanAnn := &lnwire.ChannelAnnouncement1{
ShortChannelID: chanID,
NodeID1: chanInfo.NodeKey1Bytes,
NodeID2: chanInfo.NodeKey2Bytes,
ChainHash: chanInfo.ChainHash,
BitcoinKey1: chanInfo.BitcoinKey1Bytes,
BitcoinKey2: chanInfo.BitcoinKey2Bytes,
Features: lnwire.NewRawFeatureVector(),
ExtraOpaqueData: chanInfo.ExtraOpaqueData,
}
err := chanAnn.Features.Decode(bytes.NewReader(chanInfo.Features))
if err != nil {
return nil, nil, nil, err
}
chanAnn.BitcoinSig1, err = lnwire.NewSigFromECDSARawSignature(
chanProof.BitcoinSig1Bytes,
)
if err != nil {
return nil, nil, nil, err
}
chanAnn.BitcoinSig2, err = lnwire.NewSigFromECDSARawSignature(
chanProof.BitcoinSig2Bytes,
)
if err != nil {
return nil, nil, nil, err
}
chanAnn.NodeSig1, err = lnwire.NewSigFromECDSARawSignature(
chanProof.NodeSig1Bytes,
)
if err != nil {
return nil, nil, nil, err
}
chanAnn.NodeSig2, err = lnwire.NewSigFromECDSARawSignature(
chanProof.NodeSig2Bytes,
)
if err != nil {
return nil, nil, nil, err
}
// We'll unconditionally queue the channel's existence chanProof as it
// will need to be processed before either of the channel update
// networkMsgs.
// Since it's up to a node's policy as to whether they advertise the
// edge in a direction, we don't create an advertisement if the edge is
// nil.
var edge1Ann, edge2Ann *lnwire.ChannelUpdate1
if e1 != nil {
edge1Ann, err = ChannelUpdateFromEdge(chanInfo, e1)
if err != nil {
return nil, nil, nil, err
}
}
if e2 != nil {
edge2Ann, err = ChannelUpdateFromEdge(chanInfo, e2)
if err != nil {
return nil, nil, nil, err
}
}
return chanAnn, edge1Ann, edge2Ann, nil
}
// FetchPkScript defines a function that can be used to fetch the output script
// for the transaction with the given SCID.
type FetchPkScript func(*lnwire.ShortChannelID) ([]byte, error)
// ValidateChannelAnn validates the channel announcement.
func ValidateChannelAnn(a lnwire.ChannelAnnouncement,
fetchPkScript FetchPkScript) error {
switch ann := a.(type) {
case *lnwire.ChannelAnnouncement1:
return validateChannelAnn1(ann)
case *lnwire.ChannelAnnouncement2:
return validateChannelAnn2(ann, fetchPkScript)
default:
return fmt.Errorf("unhandled implementation of "+
"lnwire.ChannelAnnouncement: %T", a)
}
}
// validateChannelAnn1 validates the channel announcement message and checks
// that node signatures covers the announcement message, and that the bitcoin
// signatures covers the node keys.
func validateChannelAnn1(a *lnwire.ChannelAnnouncement1) error {
// First, we'll compute the digest (h) which is to be signed by each of
// the keys included within the node announcement message. This hash
// digest includes all the keys, so the (up to 4 signatures) will
// attest to the validity of each of the keys.
data, err := a.DataToSign()
if err != nil {
return err
}
dataHash := chainhash.DoubleHashB(data)
// First we'll verify that the passed bitcoin key signature is indeed a
// signature over the computed hash digest.
bitcoinSig1, err := a.BitcoinSig1.ToSignature()
if err != nil {
return err
}
bitcoinKey1, err := btcec.ParsePubKey(a.BitcoinKey1[:])
if err != nil {
return err
}
if !bitcoinSig1.Verify(dataHash, bitcoinKey1) {
return errors.New("can't verify first bitcoin signature")
}
// If that checks out, then we'll verify that the second bitcoin
// signature is a valid signature of the bitcoin public key over hash
// digest as well.
bitcoinSig2, err := a.BitcoinSig2.ToSignature()
if err != nil {
return err
}
bitcoinKey2, err := btcec.ParsePubKey(a.BitcoinKey2[:])
if err != nil {
return err
}
if !bitcoinSig2.Verify(dataHash, bitcoinKey2) {
return errors.New("can't verify second bitcoin signature")
}
// Both node signatures attached should indeed be a valid signature
// over the selected digest of the channel announcement signature.
nodeSig1, err := a.NodeSig1.ToSignature()
if err != nil {
return err
}
nodeKey1, err := btcec.ParsePubKey(a.NodeID1[:])
if err != nil {
return err
}
if !nodeSig1.Verify(dataHash, nodeKey1) {
return errors.New("can't verify data in first node signature")
}
nodeSig2, err := a.NodeSig2.ToSignature()
if err != nil {
return err
}
nodeKey2, err := btcec.ParsePubKey(a.NodeID2[:])
if err != nil {
return err
}
if !nodeSig2.Verify(dataHash, nodeKey2) {
return errors.New("can't verify data in second node signature")
}
return nil
}
// validateChannelAnn2 validates the channel announcement message and checks
// that message signature covers the announcement message.
func validateChannelAnn2(a *lnwire.ChannelAnnouncement2,
fetchPkScript FetchPkScript) error {
dataHash, err := ChanAnn2DigestToSign(a)
if err != nil {
return err
}
sig, err := a.Signature.ToSignature()
if err != nil {
return err
}
nodeKey1, err := btcec.ParsePubKey(a.NodeID1.Val[:])
if err != nil {
return err
}
nodeKey2, err := btcec.ParsePubKey(a.NodeID2.Val[:])
if err != nil {
return err
}
keys := []*btcec.PublicKey{
nodeKey1, nodeKey2,
}
// If the bitcoin keys are provided in the announcement, then it is
// assumed that the signature of the announcement is a 4-of-4 MuSig2
// over the bitcoin keys and node ID keys.
if a.BitcoinKey1.IsSome() && a.BitcoinKey2.IsSome() {
var (
btcKey1 tlv.RecordT[tlv.TlvType12, [33]byte]
btcKey2 tlv.RecordT[tlv.TlvType14, [33]byte]
)
btcKey1 = a.BitcoinKey1.UnwrapOr(btcKey1)
btcKey2 = a.BitcoinKey2.UnwrapOr(btcKey2)
bitcoinKey1, err := btcec.ParsePubKey(btcKey1.Val[:])
if err != nil {
return err
}
bitcoinKey2, err := btcec.ParsePubKey(btcKey2.Val[:])
if err != nil {
return err
}
keys = append(keys, bitcoinKey1, bitcoinKey2)
} else {
// If bitcoin keys are not provided, then we need to get the
// on-chain output key since this will be the 3rd key in the
// 3-of-3 MuSig2 signature.
pkScript, err := fetchPkScript(&a.ShortChannelID.Val)
if err != nil {
return err
}
outputKey, err := schnorr.ParsePubKey(pkScript[2:])
if err != nil {
return err
}
keys = append(keys, outputKey)
}
aggKey, _, _, err := musig2.AggregateKeys(keys, true)
if err != nil {
return err
}
if !sig.Verify(dataHash.CloneBytes(), aggKey.FinalKey) {
return fmt.Errorf("invalid sig")
}
return nil
}
// ChanAnn2DigestToSign computes the digest of the message to be signed.
func ChanAnn2DigestToSign(a *lnwire.ChannelAnnouncement2) (*chainhash.Hash,
error) {
data, err := a.DataToSign()
if err != nil {
return nil, err
}
return MsgHash(chanAnn2MsgName, chanAnn2SigFieldName, data), nil
}
package netann
import (
"time"
"github.com/btcsuite/btcd/wire"
)
// ChanStatus is a type that enumerates the possible states a ChanStatusManager
// tracks for its known channels.
type ChanStatus uint8
const (
// ChanStatusEnabled indicates that the channel's last announcement has
// the disabled bit cleared.
ChanStatusEnabled ChanStatus = iota
// ChanStatusPendingDisabled indicates that the channel's last
// announcement has the disabled bit cleared, but that the channel was
// detected in an inactive state. Channels in this state will have a
// disabling announcement sent after the ChanInactiveTimeout expires
// from the time of the first detection--unless the channel is
// explicitly re-enabled before the disabling occurs.
ChanStatusPendingDisabled
// ChanStatusDisabled indicates that the channel's last announcement has
// the disabled bit set.
ChanStatusDisabled
// ChanStatusManuallyDisabled indicates that the channel's last
// announcement had the disabled bit set, and that a user manually
// requested disabling the channel. Channels in this state will ignore
// automatic / background attempts to re-enable the channel.
//
// Note that there's no corresponding ChanStatusManuallyEnabled state
// because even if a user manually requests enabling a channel, we still
// DO want to allow automatic / background processes to disable it.
// Otherwise, the network might be cluttered with channels that are
// advertised as enabled, but don't actually work or even exist.
ChanStatusManuallyDisabled
)
// ChannelState describes the ChanStatusManager's view of a channel, and
// describes the current state the channel's disabled status on the network.
type ChannelState struct {
// Status is the channel's current ChanStatus from the POV of the
// ChanStatusManager.
Status ChanStatus
// SendDisableTime is the earliest time at which the ChanStatusManager
// will passively send a new disable announcement on behalf of this
// channel.
//
// NOTE: This field is only non-zero if status is
// ChanStatusPendingDisabled.
SendDisableTime time.Time
}
// channelStates is a map of channel outpoints to their channelState. All
// changes made after setting an entry initially should be made using receiver
// methods below.
type channelStates map[wire.OutPoint]ChannelState
// markEnabled creates a channelState using ChanStatusEnabled.
func (s *channelStates) markEnabled(outpoint wire.OutPoint) {
(*s)[outpoint] = ChannelState{
Status: ChanStatusEnabled,
}
}
// markDisabled creates a channelState using ChanStatusDisabled.
func (s *channelStates) markDisabled(outpoint wire.OutPoint) {
(*s)[outpoint] = ChannelState{
Status: ChanStatusDisabled,
}
}
// markManuallyDisabled creates a channelState using
// ChanStatusManuallyDisabled.
func (s *channelStates) markManuallyDisabled(outpoint wire.OutPoint) {
(*s)[outpoint] = ChannelState{
Status: ChanStatusManuallyDisabled,
}
}
// markPendingDisabled creates a channelState using ChanStatusPendingDisabled
// and sets the ChannelState's SendDisableTime to sendDisableTime.
func (s *channelStates) markPendingDisabled(outpoint wire.OutPoint,
sendDisableTime time.Time) {
(*s)[outpoint] = ChannelState{
Status: ChanStatusPendingDisabled,
SendDisableTime: sendDisableTime,
}
}
package netann
import (
"bytes"
"fmt"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/pkg/errors"
)
const (
// chanUpdate2MsgName is a string representing the name of the
// ChannelUpdate2 message. This string will be used during the
// construction of the tagged hash message to be signed when producing
// the signature for the ChannelUpdate2 message.
chanUpdate2MsgName = "channel_update_2"
// chanUpdate2SigField is the name of the signature field of the
// ChannelUpdate2 message. This string will be used during the
// construction of the tagged hash message to be signed when producing
// the signature for the ChannelUpdate2 message.
chanUpdate2SigField = "signature"
)
// ErrUnableToExtractChanUpdate is returned when a channel update cannot be
// found for one of our active channels.
var ErrUnableToExtractChanUpdate = fmt.Errorf("unable to extract ChannelUpdate")
// ChannelUpdateModifier is a closure that makes in-place modifications to an
// lnwire.ChannelUpdate.
type ChannelUpdateModifier func(*lnwire.ChannelUpdate1)
// ChanUpdSetDisable is a functional option that sets the disabled channel flag
// if disabled is true, and clears the bit otherwise.
func ChanUpdSetDisable(disabled bool) ChannelUpdateModifier {
return func(update *lnwire.ChannelUpdate1) {
if disabled {
// Set the bit responsible for marking a channel as
// disabled.
update.ChannelFlags |= lnwire.ChanUpdateDisabled
} else {
// Clear the bit responsible for marking a channel as
// disabled.
update.ChannelFlags &= ^lnwire.ChanUpdateDisabled
}
}
}
// ChanUpdSetTimestamp is a functional option that sets the timestamp of the
// update to the current time, or increments it if the timestamp is already in
// the future.
func ChanUpdSetTimestamp(update *lnwire.ChannelUpdate1) {
newTimestamp := uint32(time.Now().Unix())
if newTimestamp <= update.Timestamp {
// Increment the prior value to ensure the timestamp
// monotonically increases, otherwise the update won't
// propagate.
newTimestamp = update.Timestamp + 1
}
update.Timestamp = newTimestamp
}
// SignChannelUpdate applies the given modifiers to the passed
// lnwire.ChannelUpdate, then signs the resulting update. The provided update
// should be the most recent, valid update, otherwise the timestamp may not
// monotonically increase from the prior.
//
// NOTE: This method modifies the given update.
func SignChannelUpdate(signer lnwallet.MessageSigner, keyLoc keychain.KeyLocator,
update *lnwire.ChannelUpdate1, mods ...ChannelUpdateModifier) error {
// Apply the requested changes to the channel update.
for _, modifier := range mods {
modifier(update)
}
// Create the DER-encoded ECDSA signature over the message digest.
sig, err := SignAnnouncement(signer, keyLoc, update)
if err != nil {
return err
}
// Parse the DER-encoded signature into a fixed-size 64-byte array.
update.Signature, err = lnwire.NewSigFromSignature(sig)
if err != nil {
return err
}
return nil
}
// ExtractChannelUpdate attempts to retrieve a lnwire.ChannelUpdate message from
// an edge's info and a set of routing policies.
//
// NOTE: The passed policies can be nil.
func ExtractChannelUpdate(ownerPubKey []byte,
info *models.ChannelEdgeInfo,
policies ...*models.ChannelEdgePolicy) (
*lnwire.ChannelUpdate1, error) {
// Helper function to extract the owner of the given policy.
owner := func(edge *models.ChannelEdgePolicy) []byte {
var pubKey *btcec.PublicKey
if edge.ChannelFlags&lnwire.ChanUpdateDirection == 0 {
pubKey, _ = info.NodeKey1()
} else {
pubKey, _ = info.NodeKey2()
}
// If pubKey was not found, just return nil.
if pubKey == nil {
return nil
}
return pubKey.SerializeCompressed()
}
// Extract the channel update from the policy we own, if any.
for _, edge := range policies {
if edge != nil && bytes.Equal(ownerPubKey, owner(edge)) {
return ChannelUpdateFromEdge(info, edge)
}
}
return nil, ErrUnableToExtractChanUpdate
}
// UnsignedChannelUpdateFromEdge reconstructs an unsigned ChannelUpdate from the
// given edge info and policy.
func UnsignedChannelUpdateFromEdge(info *models.ChannelEdgeInfo,
policy *models.ChannelEdgePolicy) *lnwire.ChannelUpdate1 {
return &lnwire.ChannelUpdate1{
ChainHash: info.ChainHash,
ShortChannelID: lnwire.NewShortChanIDFromInt(policy.ChannelID),
Timestamp: uint32(policy.LastUpdate.Unix()),
ChannelFlags: policy.ChannelFlags,
MessageFlags: policy.MessageFlags,
TimeLockDelta: policy.TimeLockDelta,
HtlcMinimumMsat: policy.MinHTLC,
HtlcMaximumMsat: policy.MaxHTLC,
BaseFee: uint32(policy.FeeBaseMSat),
FeeRate: uint32(policy.FeeProportionalMillionths),
ExtraOpaqueData: policy.ExtraOpaqueData,
}
}
// ChannelUpdateFromEdge reconstructs a signed ChannelUpdate from the given edge
// info and policy.
func ChannelUpdateFromEdge(info *models.ChannelEdgeInfo,
policy *models.ChannelEdgePolicy) (*lnwire.ChannelUpdate1, error) {
update := UnsignedChannelUpdateFromEdge(info, policy)
var err error
update.Signature, err = lnwire.NewSigFromECDSARawSignature(
policy.SigBytes,
)
if err != nil {
return nil, err
}
return update, nil
}
// ValidateChannelUpdateAnn validates the channel update announcement by
// checking (1) that the included signature covers the announcement and has been
// signed by the node's private key, and (2) that the announcement's message
// flags and optional fields are sane.
func ValidateChannelUpdateAnn(pubKey *btcec.PublicKey, capacity btcutil.Amount,
a lnwire.ChannelUpdate) error {
if err := ValidateChannelUpdateFields(capacity, a); err != nil {
return err
}
return VerifyChannelUpdateSignature(a, pubKey)
}
// VerifyChannelUpdateSignature verifies that the channel update message was
// signed by the party with the given node public key.
func VerifyChannelUpdateSignature(msg lnwire.ChannelUpdate,
pubKey *btcec.PublicKey) error {
switch u := msg.(type) {
case *lnwire.ChannelUpdate1:
return verifyChannelUpdate1Signature(u, pubKey)
case *lnwire.ChannelUpdate2:
return verifyChannelUpdate2Signature(u, pubKey)
default:
return fmt.Errorf("unhandled implementation of "+
"lnwire.ChannelUpdate: %T", msg)
}
}
// verifyChannelUpdateSignature1 verifies that the channel update message was
// signed by the party with the given node public key.
func verifyChannelUpdate1Signature(msg *lnwire.ChannelUpdate1,
pubKey *btcec.PublicKey) error {
data, err := msg.DataToSign()
if err != nil {
return fmt.Errorf("unable to reconstruct message data: %w", err)
}
dataHash := chainhash.DoubleHashB(data)
nodeSig, err := msg.Signature.ToSignature()
if err != nil {
return err
}
if !nodeSig.Verify(dataHash, pubKey) {
return fmt.Errorf("invalid signature for channel update %v",
spew.Sdump(msg))
}
return nil
}
// verifyChannelUpdateSignature2 verifies that the channel update message was
// signed by the party with the given node public key.
func verifyChannelUpdate2Signature(c *lnwire.ChannelUpdate2,
pubKey *btcec.PublicKey) error {
digest, err := chanUpdate2DigestToSign(c)
if err != nil {
return fmt.Errorf("unable to reconstruct message data: %w", err)
}
nodeSig, err := c.Signature.ToSignature()
if err != nil {
return err
}
if !nodeSig.Verify(digest, pubKey) {
return fmt.Errorf("invalid signature for channel update %v",
spew.Sdump(c))
}
return nil
}
// ValidateChannelUpdateFields validates a channel update's message flags and
// corresponding update fields.
func ValidateChannelUpdateFields(capacity btcutil.Amount,
msg lnwire.ChannelUpdate) error {
switch u := msg.(type) {
case *lnwire.ChannelUpdate1:
return validateChannelUpdate1Fields(capacity, u)
case *lnwire.ChannelUpdate2:
return validateChannelUpdate2Fields(capacity, u)
default:
return fmt.Errorf("unhandled implementation of "+
"lnwire.ChannelUpdate: %T", msg)
}
}
// validateChannelUpdate1Fields validates a channel update's message flags and
// corresponding update fields.
func validateChannelUpdate1Fields(capacity btcutil.Amount,
msg *lnwire.ChannelUpdate1) error {
// The maxHTLC flag is mandatory.
if !msg.MessageFlags.HasMaxHtlc() {
return errors.Errorf("max htlc flag not set for channel "+
"update %v", spew.Sdump(msg))
}
maxHtlc := msg.HtlcMaximumMsat
if maxHtlc == 0 || maxHtlc < msg.HtlcMinimumMsat {
return errors.Errorf("invalid max htlc for channel "+
"update %v", spew.Sdump(msg))
}
// For light clients, the capacity will not be set so we'll skip
// checking whether the MaxHTLC value respects the channel's
// capacity.
capacityMsat := lnwire.NewMSatFromSatoshis(capacity)
if capacityMsat != 0 && maxHtlc > capacityMsat {
return errors.Errorf("max_htlc (%v) for channel update "+
"greater than capacity (%v)", maxHtlc, capacityMsat)
}
return nil
}
// validateChannelUpdate2Fields validates a channel update's message flags and
// corresponding update fields.
func validateChannelUpdate2Fields(capacity btcutil.Amount,
c *lnwire.ChannelUpdate2) error {
maxHtlc := c.HTLCMaximumMsat.Val
if maxHtlc == 0 || maxHtlc < c.HTLCMinimumMsat.Val {
return fmt.Errorf("invalid max htlc for channel update %v",
spew.Sdump(c))
}
// Checking whether the MaxHTLC value respects the channel's capacity.
capacityMsat := lnwire.NewMSatFromSatoshis(capacity)
if maxHtlc > capacityMsat {
return fmt.Errorf("max_htlc (%v) for channel update greater "+
"than capacity (%v)", maxHtlc, capacityMsat)
}
return nil
}
// ChanUpdate2DigestTag returns the tag to be used when signing the digest of
// a channel_update_2 message.
func ChanUpdate2DigestTag() []byte {
return MsgTag(chanUpdate2MsgName, chanUpdate2SigField)
}
// chanUpdate2DigestToSign computes the digest of the ChannelUpdate2 message to
// be signed.
func chanUpdate2DigestToSign(c *lnwire.ChannelUpdate2) ([]byte, error) {
data, err := c.DataToSign()
if err != nil {
return nil, err
}
hash := MsgHash(chanUpdate2MsgName, chanUpdate2SigField, data)
return hash[:], nil
}
package netann
import (
"net"
"sync"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/ticker"
)
// HostAnnouncerConfig is the main config for the HostAnnouncer.
type HostAnnouncerConfig struct {
// Hosts is the set of hosts we should watch for IP changes.
Hosts []string
// RefreshTicker ticks each time we should check for any address
// changes.
RefreshTicker ticker.Ticker
// LookupHost performs DNS resolution on a given host and returns its
// addresses.
LookupHost func(string) (net.Addr, error)
// AdvertisedIPs is the set of IPs that we've already announced with
// our current NodeAnnouncement. This set will be constructed to avoid
// unnecessary node NodeAnnouncement updates.
AdvertisedIPs map[string]struct{}
// AnnounceNewIPs announces a new set of IP addresses for the backing
// Lightning node. The first set of addresses is the new set of
// addresses that we should advertise, while the other set are the
// stale addresses that we should no longer advertise.
AnnounceNewIPs func([]net.Addr, map[string]struct{}) error
}
// HostAnnouncer is a sub-system that allows a user to specify a set of hosts
// for lnd that will be continually resolved to notice any IP address changes.
// If the target IP address for a host changes, then we'll generate a new
// NodeAnnouncement that includes these new IPs.
type HostAnnouncer struct {
cfg HostAnnouncerConfig
quit chan struct{}
wg sync.WaitGroup
startOnce sync.Once
stopOnce sync.Once
}
// NewHostAnnouncer returns a new instance of the HostAnnouncer.
func NewHostAnnouncer(cfg HostAnnouncerConfig) *HostAnnouncer {
return &HostAnnouncer{
cfg: cfg,
quit: make(chan struct{}),
}
}
// Start starts the HostAnnouncer.
func (h *HostAnnouncer) Start() error {
h.startOnce.Do(func() {
log.Info("HostAnnouncer starting")
h.wg.Add(1)
go h.hostWatcher()
})
return nil
}
// Stop signals the HostAnnouncer for a graceful stop.
func (h *HostAnnouncer) Stop() error {
h.stopOnce.Do(func() {
log.Info("HostAnnouncer shutting down...")
defer log.Debug("HostAnnouncer shutdown complete")
close(h.quit)
h.wg.Wait()
})
return nil
}
// hostWatcher periodically attempts to resolve the IP for each host, updating
// them if they change within the interval.
func (h *HostAnnouncer) hostWatcher() {
defer h.wg.Done()
ipMapping := make(map[string]net.Addr)
refreshHosts := func() {
// We'll now run through each of our hosts to check if they had
// their backing IPs changed. If so, we'll want to re-announce
// them.
var addrsToUpdate []net.Addr
addrsToRemove := make(map[string]struct{})
for _, host := range h.cfg.Hosts {
newAddr, err := h.cfg.LookupHost(host)
if err != nil {
log.Warnf("unable to resolve IP for "+
"host %v: %v", host, err)
continue
}
// If nothing has changed since the last time we
// checked, then we don't need to do any updates.
oldAddr, oldAddrFound := ipMapping[host]
if oldAddrFound && oldAddr.String() == newAddr.String() {
continue
}
// Update the IP mapping now, as if this is the first
// time then we don't need to send a new announcement.
ipMapping[host] = newAddr
// If this IP has already been announced, then we'll
// skip it to avoid triggering an unnecessary node
// announcement update.
_, ipAnnounced := h.cfg.AdvertisedIPs[newAddr.String()]
if ipAnnounced {
continue
}
// If we've reached this point, then the old address
// was found, and the new address we just looked up
// differs from the old one.
log.Debugf("IP change detected! %v: %v -> %v", host,
oldAddr, newAddr)
// If we had already advertised an addr for this host,
// then we'll need to remove that old stale address.
if oldAddr != nil {
addrsToRemove[oldAddr.String()] = struct{}{}
}
addrsToUpdate = append(addrsToUpdate, newAddr)
}
// If we don't have any addresses to update, then we can skip
// things around until the next round.
if len(addrsToUpdate) == 0 {
log.Debugf("No IP changes detected for hosts: %v",
h.cfg.Hosts)
return
}
// Now that we know the set of IPs we need to update, we'll do
// them all in a single batch.
err := h.cfg.AnnounceNewIPs(addrsToUpdate, addrsToRemove)
if err != nil {
log.Warnf("unable to announce new IPs: %v", err)
}
}
refreshHosts()
h.cfg.RefreshTicker.Resume()
for {
select {
case <-h.cfg.RefreshTicker.Ticks():
log.Debugf("HostAnnouncer checking for any IP " +
"changes...")
refreshHosts()
case <-h.quit:
return
}
}
}
// NodeAnnUpdater describes a function that's able to update our current node
// announcement on disk. It returns the updated node announcement given a set
// of updates to be applied to the current node announcement.
type NodeAnnUpdater func(modifier ...NodeAnnModifier,
) (lnwire.NodeAnnouncement, error)
// IPAnnouncer is a factory function that generates a new function that uses
// the passed annUpdater function to to announce new IP changes for a given
// host.
func IPAnnouncer(annUpdater NodeAnnUpdater) func([]net.Addr,
map[string]struct{}) error {
return func(newAddrs []net.Addr, oldAddrs map[string]struct{}) error {
_, err := annUpdater(func(
currentNodeAnn *lnwire.NodeAnnouncement) {
// To ensure we don't duplicate any addresses, we'll
// filter out the same of addresses we should no longer
// advertise.
filteredAddrs := make(
[]net.Addr, 0, len(currentNodeAnn.Addresses),
)
for _, addr := range currentNodeAnn.Addresses {
if _, ok := oldAddrs[addr.String()]; ok {
continue
}
filteredAddrs = append(filteredAddrs, addr)
}
filteredAddrs = append(filteredAddrs, newAddrs...)
currentNodeAnn.Addresses = filteredAddrs
})
return err
}
}
package netann
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This means the
// package will not perform any logging by default until the caller requests
// it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("NANN", nil))
}
// DisableLog disables all library log output. Logging output is disabled by
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info. This
// should be used in preference to SetLogWriter if the caller is also using
// btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package netann
import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// MsgHashTag will prefix the message name and the field name in order to
// construct the message tag.
const MsgHashTag = "lightning"
// MsgTag computes the full tag that will be used to prefix a message before
// calculating the tagged hash. The tag is constructed as follows:
//
// tag = "lightning"||"msg_name"||"field_name"
func MsgTag(msgName, fieldName string) []byte {
tag := []byte(MsgHashTag)
tag = append(tag, []byte(msgName)...)
return append(tag, []byte(fieldName)...)
}
// MsgHash computes the tagged hash of the given message as follows:
//
// tag = "lightning"||"msg_name"||"field_name"
// hash = sha256(sha246(tag) || sha256(tag) || msg)
func MsgHash(msgName, fieldName string, msg []byte) *chainhash.Hash {
tag := MsgTag(msgName, fieldName)
return chainhash.TaggedHash(tag, msg)
}
package netann
import (
"bytes"
"image/color"
"net"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
)
// NodeAnnModifier is a closure that makes in-place modifications to an
// lnwire.NodeAnnouncement.
type NodeAnnModifier func(*lnwire.NodeAnnouncement)
// NodeAnnSetAlias is a functional option that sets the alias of the
// given node announcement.
func NodeAnnSetAlias(alias lnwire.NodeAlias) func(*lnwire.NodeAnnouncement) {
return func(nodeAnn *lnwire.NodeAnnouncement) {
nodeAnn.Alias = alias
}
}
// NodeAnnSetAddrs is a functional option that allows updating the addresses of
// the given node announcement.
func NodeAnnSetAddrs(addrs []net.Addr) func(*lnwire.NodeAnnouncement) {
return func(nodeAnn *lnwire.NodeAnnouncement) {
nodeAnn.Addresses = addrs
}
}
// NodeAnnSetColor is a functional option that sets the color of the
// given node announcement.
func NodeAnnSetColor(newColor color.RGBA) func(*lnwire.NodeAnnouncement) {
return func(nodeAnn *lnwire.NodeAnnouncement) {
nodeAnn.RGBColor = newColor
}
}
// NodeAnnSetFeatures is a functional option that allows updating the features of
// the given node announcement.
func NodeAnnSetFeatures(features *lnwire.RawFeatureVector) func(*lnwire.NodeAnnouncement) {
return func(nodeAnn *lnwire.NodeAnnouncement) {
nodeAnn.Features = features
}
}
// NodeAnnSetTimestamp is a functional option that sets the timestamp of the
// announcement to the current time, or increments it if the timestamp is
// already in the future.
func NodeAnnSetTimestamp(nodeAnn *lnwire.NodeAnnouncement) {
newTimestamp := uint32(time.Now().Unix())
if newTimestamp <= nodeAnn.Timestamp {
// Increment the prior value to ensure the timestamp
// monotonically increases, otherwise the announcement won't
// propagate.
newTimestamp = nodeAnn.Timestamp + 1
}
nodeAnn.Timestamp = newTimestamp
}
// SignNodeAnnouncement signs the lnwire.NodeAnnouncement provided, which
// should be the most recent, valid update, otherwise the timestamp may not
// monotonically increase from the prior.
func SignNodeAnnouncement(signer lnwallet.MessageSigner,
keyLoc keychain.KeyLocator, nodeAnn *lnwire.NodeAnnouncement) error {
// Create the DER-encoded ECDSA signature over the message digest.
sig, err := SignAnnouncement(signer, keyLoc, nodeAnn)
if err != nil {
return err
}
// Parse the DER-encoded signature into a fixed-size 64-byte array.
nodeAnn.Signature, err = lnwire.NewSigFromSignature(sig)
return err
}
// ValidateNodeAnn validates the node announcement by ensuring that the
// attached signature is needed a signature of the node announcement under the
// specified node public key.
func ValidateNodeAnn(a *lnwire.NodeAnnouncement) error {
// Reconstruct the data of announcement which should be covered by the
// signature so we can verify the signature shortly below
data, err := a.DataToSign()
if err != nil {
return err
}
nodeSig, err := a.Signature.ToSignature()
if err != nil {
return err
}
nodeKey, err := btcec.ParsePubKey(a.NodeID[:])
if err != nil {
return err
}
// Finally ensure that the passed signature is valid, if not we'll
// return an error so this node announcement can be rejected.
dataHash := chainhash.DoubleHashB(data)
if !nodeSig.Verify(dataHash, nodeKey) {
var msgBuf bytes.Buffer
if _, err := lnwire.WriteMessage(&msgBuf, a, 0); err != nil {
return err
}
return errors.Errorf("signature on NodeAnnouncement(%x) is "+
"invalid: %x", nodeKey.SerializeCompressed(),
msgBuf.Bytes())
}
return nil
}
package netann
import (
"fmt"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet"
)
// NodeSigner is an implementation of the MessageSigner interface backed by the
// identity private key of running lnd node.
type NodeSigner struct {
keySigner keychain.SingleKeyMessageSigner
}
// NewNodeSigner creates a new instance of the NodeSigner backed by the target
// private key.
func NewNodeSigner(keySigner keychain.SingleKeyMessageSigner) *NodeSigner {
return &NodeSigner{
keySigner: keySigner,
}
}
// SignMessage signs a double-sha256 digest of the passed msg under the
// resident node's private key described in the key locator. If the target key
// locator is _not_ the node's private key, then an error will be returned.
func (n *NodeSigner) SignMessage(keyLoc keychain.KeyLocator,
msg []byte, doubleHash bool) (*ecdsa.Signature, error) {
// If this isn't our identity public key, then we'll exit early with an
// error as we can't sign with this key.
if keyLoc != n.keySigner.KeyLocator() {
return nil, fmt.Errorf("unknown public key locator")
}
// Otherwise, we'll sign the double-sha256 of the target message.
sig, err := n.keySigner.SignMessage(msg, doubleHash)
if err != nil {
return nil, fmt.Errorf("can't sign the message: %w", err)
}
return sig, nil
}
// SignMessageCompact signs a single or double sha256 digest of the msg
// parameter under the resident node's private key. The returned signature is a
// pubkey-recoverable signature.
func (n *NodeSigner) SignMessageCompact(msg []byte, doubleHash bool) ([]byte,
error) {
return n.keySigner.SignMessageCompact(msg, doubleHash)
}
// A compile time check to ensure that NodeSigner implements the MessageSigner
// interface.
var _ lnwallet.MessageSigner = (*NodeSigner)(nil)
package netann
import (
"fmt"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
)
// SignAnnouncement signs any type of gossip message that is announced on the
// network.
func SignAnnouncement(signer lnwallet.MessageSigner, keyLoc keychain.KeyLocator,
msg lnwire.Message) (input.Signature, error) {
var (
data []byte
err error
)
switch m := msg.(type) {
case *lnwire.ChannelAnnouncement1:
data, err = m.DataToSign()
case *lnwire.ChannelUpdate1:
data, err = m.DataToSign()
case *lnwire.NodeAnnouncement:
data, err = m.DataToSign()
default:
return nil, fmt.Errorf("can't sign %T message", m)
}
if err != nil {
return nil, fmt.Errorf("unable to get data to sign: %w", err)
}
return signer.SignMessage(keyLoc, data, true)
}
package peer
import (
"bytes"
"container/list"
"context"
"errors"
"fmt"
"math/rand"
"net"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/connmgr"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btclog/v2"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/buffer"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channelnotifier"
"github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/discovery"
"github.com/lightningnetwork/lnd/feature"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/funding"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/htlcswitch/hodl"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnpeer"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/msgmux"
"github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/pool"
"github.com/lightningnetwork/lnd/protofsm"
"github.com/lightningnetwork/lnd/queue"
"github.com/lightningnetwork/lnd/subscribe"
"github.com/lightningnetwork/lnd/ticker"
"github.com/lightningnetwork/lnd/tlv"
"github.com/lightningnetwork/lnd/watchtower/wtclient"
)
const (
// pingInterval is the interval at which ping messages are sent.
pingInterval = 1 * time.Minute
// pingTimeout is the amount of time we will wait for a pong response
// before considering the peer to be unresponsive.
//
// This MUST be a smaller value than the pingInterval.
pingTimeout = 30 * time.Second
// idleTimeout is the duration of inactivity before we time out a peer.
idleTimeout = 5 * time.Minute
// writeMessageTimeout is the timeout used when writing a message to the
// peer.
writeMessageTimeout = 5 * time.Second
// readMessageTimeout is the timeout used when reading a message from a
// peer.
readMessageTimeout = 5 * time.Second
// handshakeTimeout is the timeout used when waiting for the peer's init
// message.
handshakeTimeout = 15 * time.Second
// ErrorBufferSize is the number of historic peer errors that we store.
ErrorBufferSize = 10
// pongSizeCeiling is the upper bound on a uniformly distributed random
// variable that we use for requesting pong responses. We don't use the
// MaxPongBytes (upper bound accepted by the protocol) because it is
// needlessly wasteful of precious Tor bandwidth for little to no gain.
pongSizeCeiling = 4096
// torTimeoutMultiplier is the scaling factor we use on network timeouts
// for Tor peers.
torTimeoutMultiplier = 3
// msgStreamSize is the size of the message streams.
msgStreamSize = 5
)
var (
// ErrChannelNotFound is an error returned when a channel is queried and
// either the Brontide doesn't know of it, or the channel in question
// is pending.
ErrChannelNotFound = fmt.Errorf("channel not found")
)
// outgoingMsg packages an lnwire.Message to be sent out on the wire, along with
// a buffered channel which will be sent upon once the write is complete. This
// buffered channel acts as a semaphore to be used for synchronization purposes.
type outgoingMsg struct {
priority bool
msg lnwire.Message
errChan chan error // MUST be buffered.
}
// newChannelMsg packages a channeldb.OpenChannel with a channel that allows
// the receiver of the request to report when the channel creation process has
// completed.
type newChannelMsg struct {
// channel is used when the pending channel becomes active.
channel *lnpeer.NewChannel
// channelID is used when there's a new pending channel.
channelID lnwire.ChannelID
err chan error
}
type customMsg struct {
peer [33]byte
msg lnwire.Custom
}
// closeMsg is a wrapper struct around any wire messages that deal with the
// cooperative channel closure negotiation process. This struct includes the
// raw channel ID targeted along with the original message.
type closeMsg struct {
cid lnwire.ChannelID
msg lnwire.Message
}
// PendingUpdate describes the pending state of a closing channel.
type PendingUpdate struct {
// Txid is the txid of the closing transaction.
Txid []byte
// OutputIndex is the output index of our output in the closing
// transaction.
OutputIndex uint32
// FeePerVByte is an optional field, that is set only when the new RBF
// coop close flow is used. This indicates the new closing fee rate on
// the closing transaction.
FeePerVbyte fn.Option[chainfee.SatPerVByte]
// IsLocalCloseTx is an optional field that indicates if this update is
// sent for our local close txn, or the close txn of the remote party.
// This is only set if the new RBF coop close flow is used.
IsLocalCloseTx fn.Option[bool]
}
// ChannelCloseUpdate contains the outcome of the close channel operation.
type ChannelCloseUpdate struct {
ClosingTxid []byte
Success bool
// LocalCloseOutput is an optional, additional output on the closing
// transaction that the local party should be paid to. This will only be
// populated if the local balance isn't dust.
LocalCloseOutput fn.Option[chancloser.CloseOutput]
// RemoteCloseOutput is an optional, additional output on the closing
// transaction that the remote party should be paid to. This will only
// be populated if the remote balance isn't dust.
RemoteCloseOutput fn.Option[chancloser.CloseOutput]
// AuxOutputs is an optional set of additional outputs that might be
// included in the closing transaction. These are used for custom
// channel types.
AuxOutputs fn.Option[chancloser.AuxCloseOutputs]
}
// TimestampedError is a timestamped error that is used to store the most recent
// errors we have experienced with our peers.
type TimestampedError struct {
Error error
Timestamp time.Time
}
// Config defines configuration fields that are necessary for a peer object
// to function.
type Config struct {
// Conn is the underlying network connection for this peer.
Conn MessageConn
// ConnReq stores information related to the persistent connection request
// for this peer.
ConnReq *connmgr.ConnReq
// PubKeyBytes is the serialized, compressed public key of this peer.
PubKeyBytes [33]byte
// Addr is the network address of the peer.
Addr *lnwire.NetAddress
// Inbound indicates whether or not the peer is an inbound peer.
Inbound bool
// Features is the set of features that we advertise to the remote party.
Features *lnwire.FeatureVector
// LegacyFeatures is the set of features that we advertise to the remote
// peer for backwards compatibility. Nodes that have not implemented
// flat features will still be able to read our feature bits from the
// legacy global field, but we will also advertise everything in the
// default features field.
LegacyFeatures *lnwire.FeatureVector
// OutgoingCltvRejectDelta defines the number of blocks before expiry of
// an htlc where we don't offer it anymore.
OutgoingCltvRejectDelta uint32
// ChanActiveTimeout specifies the duration the peer will wait to request
// a channel reenable, beginning from the time the peer was started.
ChanActiveTimeout time.Duration
// ErrorBuffer stores a set of errors related to a peer. It contains error
// messages that our peer has recently sent us over the wire and records of
// unknown messages that were sent to us so that we can have a full track
// record of the communication errors we have had with our peer. If we
// choose to disconnect from a peer, it also stores the reason we had for
// disconnecting.
ErrorBuffer *queue.CircularBuffer
// WritePool is the task pool that manages reuse of write buffers. Write
// tasks are submitted to the pool in order to conserve the total number of
// write buffers allocated at any one time, and decouple write buffer
// allocation from the peer life cycle.
WritePool *pool.Write
// ReadPool is the task pool that manages reuse of read buffers.
ReadPool *pool.Read
// Switch is a pointer to the htlcswitch. It is used to setup, get, and
// tear-down ChannelLinks.
Switch messageSwitch
// InterceptSwitch is a pointer to the InterceptableSwitch, a wrapper around
// the regular Switch. We only export it here to pass ForwardPackets to the
// ChannelLinkConfig.
InterceptSwitch *htlcswitch.InterceptableSwitch
// ChannelDB is used to fetch opened channels, and closed channels.
ChannelDB *channeldb.ChannelStateDB
// ChannelGraph is a pointer to the channel graph which is used to
// query information about the set of known active channels.
ChannelGraph *graphdb.ChannelGraph
// ChainArb is used to subscribe to channel events, update contract signals,
// and force close channels.
ChainArb *contractcourt.ChainArbitrator
// AuthGossiper is needed so that the Brontide impl can register with the
// gossiper and process remote channel announcements.
AuthGossiper *discovery.AuthenticatedGossiper
// ChanStatusMgr is used to set or un-set the disabled bit in channel
// updates.
ChanStatusMgr *netann.ChanStatusManager
// ChainIO is used to retrieve the best block.
ChainIO lnwallet.BlockChainIO
// FeeEstimator is used to compute our target ideal fee-per-kw when
// initializing the coop close process.
FeeEstimator chainfee.Estimator
// Signer is used when creating *lnwallet.LightningChannel instances.
Signer input.Signer
// SigPool is used when creating *lnwallet.LightningChannel instances.
SigPool *lnwallet.SigPool
// Wallet is used to publish transactions and generates delivery
// scripts during the coop close process.
Wallet *lnwallet.LightningWallet
// ChainNotifier is used to receive confirmations of a coop close
// transaction.
ChainNotifier chainntnfs.ChainNotifier
// BestBlockView is used to efficiently query for up-to-date
// blockchain state information
BestBlockView chainntnfs.BestBlockView
// RoutingPolicy is used to set the forwarding policy for links created by
// the Brontide.
RoutingPolicy models.ForwardingPolicy
// Sphinx is used when setting up ChannelLinks so they can decode sphinx
// onion blobs.
Sphinx *hop.OnionProcessor
// WitnessBeacon is used when setting up ChannelLinks so they can add any
// preimages that they learn.
WitnessBeacon contractcourt.WitnessBeacon
// Invoices is passed to the ChannelLink on creation and handles all
// invoice-related logic.
Invoices *invoices.InvoiceRegistry
// ChannelNotifier is used by the link to notify other sub-systems about
// channel-related events and by the Brontide to subscribe to
// ActiveLinkEvents.
ChannelNotifier *channelnotifier.ChannelNotifier
// HtlcNotifier is used when creating a ChannelLink.
HtlcNotifier *htlcswitch.HtlcNotifier
// TowerClient is used to backup revoked states.
TowerClient wtclient.ClientManager
// DisconnectPeer is used to disconnect this peer if the cooperative close
// process fails.
DisconnectPeer func(*btcec.PublicKey) error
// GenNodeAnnouncement is used to send our node announcement to the remote
// on startup.
GenNodeAnnouncement func(...netann.NodeAnnModifier) (
lnwire.NodeAnnouncement, error)
// PrunePersistentPeerConnection is used to remove all internal state
// related to this peer in the server.
PrunePersistentPeerConnection func([33]byte)
// FetchLastChanUpdate fetches our latest channel update for a target
// channel.
FetchLastChanUpdate func(lnwire.ShortChannelID) (*lnwire.ChannelUpdate1,
error)
// FundingManager is an implementation of the funding.Controller interface.
FundingManager funding.Controller
// Hodl is used when creating ChannelLinks to specify HodlFlags as
// breakpoints in dev builds.
Hodl *hodl.Config
// UnsafeReplay is used when creating ChannelLinks to specify whether or
// not to replay adds on its commitment tx.
UnsafeReplay bool
// MaxOutgoingCltvExpiry is used when creating ChannelLinks and is the max
// number of blocks that funds could be locked up for when forwarding
// payments.
MaxOutgoingCltvExpiry uint32
// MaxChannelFeeAllocation is used when creating ChannelLinks and is the
// maximum percentage of total funds that can be allocated to a channel's
// commitment fee. This only applies for the initiator of the channel.
MaxChannelFeeAllocation float64
// MaxAnchorsCommitFeeRate is the maximum fee rate we'll use as an
// initiator for anchor channel commitments.
MaxAnchorsCommitFeeRate chainfee.SatPerKWeight
// CoopCloseTargetConfs is the confirmation target that will be used
// to estimate the fee rate to use during a cooperative channel
// closure initiated by the remote peer.
CoopCloseTargetConfs uint32
// ServerPubKey is the serialized, compressed public key of our lnd node.
// It is used to determine which policy (channel edge) to pass to the
// ChannelLink.
ServerPubKey [33]byte
// ChannelCommitInterval is the maximum time that is allowed to pass between
// receiving a channel state update and signing the next commitment.
// Setting this to a longer duration allows for more efficient channel
// operations at the cost of latency.
ChannelCommitInterval time.Duration
// PendingCommitInterval is the maximum time that is allowed to pass
// while waiting for the remote party to revoke a locally initiated
// commitment state. Setting this to a longer duration if a slow
// response is expected from the remote party or large number of
// payments are attempted at the same time.
PendingCommitInterval time.Duration
// ChannelCommitBatchSize is the maximum number of channel state updates
// that is accumulated before signing a new commitment.
ChannelCommitBatchSize uint32
// HandleCustomMessage is called whenever a custom message is received
// from the peer.
HandleCustomMessage func(peer [33]byte, msg *lnwire.Custom) error
// GetAliases is passed to created links so the Switch and link can be
// aware of the channel's aliases.
GetAliases func(base lnwire.ShortChannelID) []lnwire.ShortChannelID
// RequestAlias allows the Brontide struct to request an alias to send
// to the peer.
RequestAlias func() (lnwire.ShortChannelID, error)
// AddLocalAlias persists an alias to an underlying alias store.
AddLocalAlias func(alias, base lnwire.ShortChannelID,
gossip, liveUpdate bool) error
// AuxLeafStore is an optional store that can be used to store auxiliary
// leaves for certain custom channel types.
AuxLeafStore fn.Option[lnwallet.AuxLeafStore]
// AuxSigner is an optional signer that can be used to sign auxiliary
// leaves for certain custom channel types.
AuxSigner fn.Option[lnwallet.AuxSigner]
// AuxResolver is an optional interface that can be used to modify the
// way contracts are resolved.
AuxResolver fn.Option[lnwallet.AuxContractResolver]
// AuxTrafficShaper is an optional auxiliary traffic shaper that can be
// used to manage the bandwidth of peer links.
AuxTrafficShaper fn.Option[htlcswitch.AuxTrafficShaper]
// PongBuf is a slice we'll reuse instead of allocating memory on the
// heap. Since only reads will occur and no writes, there is no need
// for any synchronization primitives. As a result, it's safe to share
// this across multiple Peer struct instances.
PongBuf []byte
// Adds the option to disable forwarding payments in blinded routes
// by failing back any blinding-related payloads as if they were
// invalid.
DisallowRouteBlinding bool
// DisallowQuiescence is a flag that indicates whether the Brontide
// should have the quiescence feature disabled.
DisallowQuiescence bool
// MaxFeeExposure limits the number of outstanding fees in a channel.
// This value will be passed to created links.
MaxFeeExposure lnwire.MilliSatoshi
// MsgRouter is an optional instance of the main message router that
// the peer will use. If None, then a new default version will be used
// in place.
MsgRouter fn.Option[msgmux.Router]
// AuxChanCloser is an optional instance of an abstraction that can be
// used to modify the way the co-op close transaction is constructed.
AuxChanCloser fn.Option[chancloser.AuxChanCloser]
// ShouldFwdExpEndorsement is a closure that indicates whether
// experimental endorsement signals should be set.
ShouldFwdExpEndorsement func() bool
// Quit is the server's quit channel. If this is closed, we halt operation.
Quit chan struct{}
}
// chanCloserFsm is a union-like type that can hold the two versions of co-op
// close we support: negotiation, and RBF based.
//
// TODO(roasbeef): rename to chancloser.Negotiator and chancloser.RBF?
type chanCloserFsm = fn.Either[*chancloser.ChanCloser, *chancloser.RbfChanCloser] //nolint:ll
// makeNegotiateCloser creates a new negotiate closer from a
// chancloser.ChanCloser.
func makeNegotiateCloser(chanCloser *chancloser.ChanCloser) chanCloserFsm {
return fn.NewLeft[*chancloser.ChanCloser, *chancloser.RbfChanCloser](
chanCloser,
)
}
// makeRbfCloser creates a new RBF closer from a chancloser.RbfChanCloser.
func makeRbfCloser(rbfCloser *chancloser.RbfChanCloser) chanCloserFsm {
return fn.NewRight[*chancloser.ChanCloser](
rbfCloser,
)
}
// Brontide is an active peer on the Lightning Network. This struct is responsible
// for managing any channel state related to this peer. To do so, it has
// several helper goroutines to handle events such as HTLC timeouts, new
// funding workflow, and detecting an uncooperative closure of any active
// channels.
type Brontide struct {
// MUST be used atomically.
started int32
disconnect int32
// MUST be used atomically.
bytesReceived uint64
bytesSent uint64
// isTorConnection is a flag that indicates whether or not we believe
// the remote peer is a tor connection. It is not always possible to
// know this with certainty but we have heuristics we use that should
// catch most cases.
//
// NOTE: We judge the tor-ness of a connection by if the remote peer has
// ".onion" in the address OR if it's connected over localhost.
// This will miss cases where our peer is connected to our clearnet
// address over the tor network (via exit nodes). It will also misjudge
// actual localhost connections as tor. We need to include this because
// inbound connections to our tor address will appear to come from the
// local socks5 proxy. This heuristic is only used to expand the timeout
// window for peers so it is OK to misjudge this. If you use this field
// for any other purpose you should seriously consider whether or not
// this heuristic is good enough for your use case.
isTorConnection bool
pingManager *PingManager
// lastPingPayload stores an unsafe pointer wrapped as an atomic
// variable which points to the last payload the remote party sent us
// as their ping.
//
// MUST be used atomically.
lastPingPayload atomic.Value
cfg Config
// activeSignal when closed signals that the peer is now active and
// ready to process messages.
activeSignal chan struct{}
// startTime is the time this peer connection was successfully established.
// It will be zero for peers that did not successfully call Start().
startTime time.Time
// sendQueue is the channel which is used to queue outgoing messages to be
// written onto the wire. Note that this channel is unbuffered.
sendQueue chan outgoingMsg
// outgoingQueue is a buffered channel which allows second/third party
// objects to queue messages to be sent out on the wire.
outgoingQueue chan outgoingMsg
// activeChannels is a map which stores the state machines of all
// active channels. Channels are indexed into the map by the txid of
// the funding transaction which opened the channel.
//
// NOTE: On startup, pending channels are stored as nil in this map.
// Confirmed channels have channel data populated in the map. This means
// that accesses to this map should nil-check the LightningChannel to
// see if this is a pending channel or not. The tradeoff here is either
// having two maps everywhere (one for pending, one for confirmed chans)
// or having an extra nil-check per access.
activeChannels *lnutils.SyncMap[
lnwire.ChannelID, *lnwallet.LightningChannel]
// addedChannels tracks any new channels opened during this peer's
// lifecycle. We use this to filter out these new channels when the time
// comes to request a reenable for active channels, since they will have
// waited a shorter duration.
addedChannels *lnutils.SyncMap[lnwire.ChannelID, struct{}]
// newActiveChannel is used by the fundingManager to send fully opened
// channels to the source peer which handled the funding workflow.
newActiveChannel chan *newChannelMsg
// newPendingChannel is used by the fundingManager to send pending open
// channels to the source peer which handled the funding workflow.
newPendingChannel chan *newChannelMsg
// removePendingChannel is used by the fundingManager to cancel pending
// open channels to the source peer when the funding flow is failed.
removePendingChannel chan *newChannelMsg
// activeMsgStreams is a map from channel id to the channel streams that
// proxy messages to individual, active links.
activeMsgStreams map[lnwire.ChannelID]*msgStream
// activeChanCloses is a map that keeps track of all the active
// cooperative channel closures. Any channel closing messages are directed
// to one of these active state machines. Once the channel has been closed,
// the state machine will be deleted from the map.
activeChanCloses *lnutils.SyncMap[lnwire.ChannelID, chanCloserFsm]
// localCloseChanReqs is a channel in which any local requests to close
// a particular channel are sent over.
localCloseChanReqs chan *htlcswitch.ChanClose
// linkFailures receives all reported channel failures from the switch,
// and instructs the channelManager to clean remaining channel state.
linkFailures chan linkFailureReport
// chanCloseMsgs is a channel that any message related to channel
// closures are sent over. This includes lnwire.Shutdown message as
// well as lnwire.ClosingSigned messages.
chanCloseMsgs chan *closeMsg
// remoteFeatures is the feature vector received from the peer during
// the connection handshake.
remoteFeatures *lnwire.FeatureVector
// resentChanSyncMsg is a set that keeps track of which channels we
// have re-sent channel reestablishment messages for. This is done to
// avoid getting into loop where both peers will respond to the other
// peer's chansync message with its own over and over again.
resentChanSyncMsg map[lnwire.ChannelID]struct{}
// channelEventClient is the channel event subscription client that's
// used to assist retry enabling the channels. This client is only
// created when the reenableTimeout is no greater than 1 minute. Once
// created, it is canceled once the reenabling has been finished.
//
// NOTE: we choose to create the client conditionally to avoid
// potentially holding lots of un-consumed events.
channelEventClient *subscribe.Client
// msgRouter is an instance of the msgmux.Router which is used to send
// off new wire messages for handing.
msgRouter fn.Option[msgmux.Router]
// globalMsgRouter is a flag that indicates whether we have a global
// msg router. If so, then we don't worry about stopping the msg router
// when a peer disconnects.
globalMsgRouter bool
startReady chan struct{}
// cg is a helper that encapsulates a wait group and quit channel and
// allows contexts that either block or cancel on those depending on
// the use case.
cg *fn.ContextGuard
// log is a peer-specific logging instance.
log btclog.Logger
}
// A compile-time check to ensure that Brontide satisfies the lnpeer.Peer
// interface.
var _ lnpeer.Peer = (*Brontide)(nil)
// NewBrontide creates a new Brontide from a peer.Config struct.
func NewBrontide(cfg Config) *Brontide {
logPrefix := fmt.Sprintf("Peer(%x):", cfg.PubKeyBytes)
// We have a global message router if one was passed in via the config.
// In this case, we don't need to attempt to tear it down when the peer
// is stopped.
globalMsgRouter := cfg.MsgRouter.IsSome()
// We'll either use the msg router instance passed in, or create a new
// blank instance.
msgRouter := cfg.MsgRouter.Alt(fn.Some[msgmux.Router](
msgmux.NewMultiMsgRouter(),
))
p := &Brontide{
cfg: cfg,
activeSignal: make(chan struct{}),
sendQueue: make(chan outgoingMsg),
outgoingQueue: make(chan outgoingMsg),
addedChannels: &lnutils.SyncMap[lnwire.ChannelID, struct{}]{},
activeChannels: &lnutils.SyncMap[
lnwire.ChannelID, *lnwallet.LightningChannel,
]{},
newActiveChannel: make(chan *newChannelMsg, 1),
newPendingChannel: make(chan *newChannelMsg, 1),
removePendingChannel: make(chan *newChannelMsg),
activeMsgStreams: make(map[lnwire.ChannelID]*msgStream),
activeChanCloses: &lnutils.SyncMap[
lnwire.ChannelID, chanCloserFsm,
]{},
localCloseChanReqs: make(chan *htlcswitch.ChanClose),
linkFailures: make(chan linkFailureReport),
chanCloseMsgs: make(chan *closeMsg),
resentChanSyncMsg: make(map[lnwire.ChannelID]struct{}),
startReady: make(chan struct{}),
log: peerLog.WithPrefix(logPrefix),
msgRouter: msgRouter,
globalMsgRouter: globalMsgRouter,
cg: fn.NewContextGuard(),
}
if cfg.Conn != nil && cfg.Conn.RemoteAddr() != nil {
remoteAddr := cfg.Conn.RemoteAddr().String()
p.isTorConnection = strings.Contains(remoteAddr, ".onion") ||
strings.Contains(remoteAddr, "127.0.0.1")
}
var (
lastBlockHeader *wire.BlockHeader
lastSerializedBlockHeader [wire.MaxBlockHeaderPayload]byte
)
newPingPayload := func() []byte {
// We query the BestBlockHeader from our BestBlockView each time
// this is called, and update our serialized block header if
// they differ. Over time, we'll use this to disseminate the
// latest block header between all our peers, which can later be
// used to cross-check our own view of the network to mitigate
// various types of eclipse attacks.
header, err := p.cfg.BestBlockView.BestBlockHeader()
if err != nil && header == lastBlockHeader {
return lastSerializedBlockHeader[:]
}
buf := bytes.NewBuffer(lastSerializedBlockHeader[0:0])
err = header.Serialize(buf)
if err == nil {
lastBlockHeader = header
} else {
p.log.Warn("unable to serialize current block" +
"header for ping payload generation." +
"This should be impossible and means" +
"there is an implementation bug.")
}
return lastSerializedBlockHeader[:]
}
// TODO(roasbeef): make dynamic in order to create fake cover traffic.
//
// NOTE(proofofkeags): this was changed to be dynamic to allow better
// pong identification, however, more thought is needed to make this
// actually usable as a traffic decoy.
randPongSize := func() uint16 {
return uint16(
// We don't need cryptographic randomness here.
/* #nosec */
rand.Intn(pongSizeCeiling) + 1,
)
}
p.pingManager = NewPingManager(&PingManagerConfig{
NewPingPayload: newPingPayload,
NewPongSize: randPongSize,
IntervalDuration: p.scaleTimeout(pingInterval),
TimeoutDuration: p.scaleTimeout(pingTimeout),
SendPing: func(ping *lnwire.Ping) {
p.queueMsg(ping, nil)
},
OnPongFailure: func(err error) {
eStr := "pong response failure for %s: %v " +
"-- disconnecting"
p.log.Warnf(eStr, p, err)
go p.Disconnect(fmt.Errorf(eStr, p, err))
},
})
return p
}
// Start starts all helper goroutines the peer needs for normal operations. In
// the case this peer has already been started, then this function is a noop.
func (p *Brontide) Start() error {
if atomic.AddInt32(&p.started, 1) != 1 {
return nil
}
// Once we've finished starting up the peer, we'll signal to other
// goroutines that the they can move forward to tear down the peer, or
// carry out other relevant changes.
defer close(p.startReady)
p.log.Tracef("starting with conn[%v->%v]",
p.cfg.Conn.LocalAddr(), p.cfg.Conn.RemoteAddr())
// Fetch and then load all the active channels we have with this remote
// peer from the database.
activeChans, err := p.cfg.ChannelDB.FetchOpenChannels(
p.cfg.Addr.IdentityKey,
)
if err != nil {
p.log.Errorf("Unable to fetch active chans "+
"for peer: %v", err)
return err
}
if len(activeChans) == 0 {
go p.cfg.PrunePersistentPeerConnection(p.cfg.PubKeyBytes)
}
// Quickly check if we have any existing legacy channels with this
// peer.
haveLegacyChan := false
for _, c := range activeChans {
if c.ChanType.IsTweakless() {
continue
}
haveLegacyChan = true
break
}
// Exchange local and global features, the init message should be very
// first between two nodes.
if err := p.sendInitMsg(haveLegacyChan); err != nil {
return fmt.Errorf("unable to send init msg: %w", err)
}
// Before we launch any of the helper goroutines off the peer struct,
// we'll first ensure proper adherence to the p2p protocol. The init
// message MUST be sent before any other message.
readErr := make(chan error, 1)
msgChan := make(chan lnwire.Message, 1)
p.cg.WgAdd(1)
go func() {
defer p.cg.WgDone()
msg, err := p.readNextMessage()
if err != nil {
readErr <- err
msgChan <- nil
return
}
readErr <- nil
msgChan <- msg
}()
select {
// In order to avoid blocking indefinitely, we'll give the other peer
// an upper timeout to respond before we bail out early.
case <-time.After(handshakeTimeout):
return fmt.Errorf("peer did not complete handshake within %v",
handshakeTimeout)
case err := <-readErr:
if err != nil {
return fmt.Errorf("unable to read init msg: %w", err)
}
}
// Once the init message arrives, we can parse it so we can figure out
// the negotiation of features for this session.
msg := <-msgChan
if msg, ok := msg.(*lnwire.Init); ok {
if err := p.handleInitMsg(msg); err != nil {
p.storeError(err)
return err
}
} else {
return errors.New("very first message between nodes " +
"must be init message")
}
// Next, load all the active channels we have with this peer,
// registering them with the switch and launching the necessary
// goroutines required to operate them.
p.log.Debugf("Loaded %v active channels from database",
len(activeChans))
// Conditionally subscribe to channel events before loading channels so
// we won't miss events. This subscription is used to listen to active
// channel event when reenabling channels. Once the reenabling process
// is finished, this subscription will be canceled.
//
// NOTE: ChannelNotifier must be started before subscribing events
// otherwise we'd panic here.
if err := p.attachChannelEventSubscription(); err != nil {
return err
}
// Register the message router now as we may need to register some
// endpoints while loading the channels below.
p.msgRouter.WhenSome(func(router msgmux.Router) {
router.Start(context.Background())
})
msgs, err := p.loadActiveChannels(activeChans)
if err != nil {
return fmt.Errorf("unable to load channels: %w", err)
}
p.startTime = time.Now()
// Before launching the writeHandler goroutine, we send any channel
// sync messages that must be resent for borked channels. We do this to
// avoid data races with WriteMessage & Flush calls.
if len(msgs) > 0 {
p.log.Infof("Sending %d channel sync messages to peer after "+
"loading active channels", len(msgs))
// Send the messages directly via writeMessage and bypass the
// writeHandler goroutine.
for _, msg := range msgs {
if err := p.writeMessage(msg); err != nil {
return fmt.Errorf("unable to send "+
"reestablish msg: %v", err)
}
}
}
err = p.pingManager.Start()
if err != nil {
return fmt.Errorf("could not start ping manager %w", err)
}
p.cg.WgAdd(4)
go p.queueHandler()
go p.writeHandler()
go p.channelManager()
go p.readHandler()
// Signal to any external processes that the peer is now active.
close(p.activeSignal)
// Node announcements don't propagate very well throughout the network
// as there isn't a way to efficiently query for them through their
// timestamp, mostly affecting nodes that were offline during the time
// of broadcast. We'll resend our node announcement to the remote peer
// as a best-effort delivery such that it can also propagate to their
// peers. To ensure they can successfully process it in most cases,
// we'll only resend it as long as we have at least one confirmed
// advertised channel with the remote peer.
//
// TODO(wilmer): Remove this once we're able to query for node
// announcements through their timestamps.
p.cg.WgAdd(2)
go p.maybeSendNodeAnn(activeChans)
go p.maybeSendChannelUpdates()
return nil
}
// initGossipSync initializes either a gossip syncer or an initial routing
// dump, depending on the negotiated synchronization method.
func (p *Brontide) initGossipSync() {
// If the remote peer knows of the new gossip queries feature, then
// we'll create a new gossipSyncer in the AuthenticatedGossiper for it.
if p.remoteFeatures.HasFeature(lnwire.GossipQueriesOptional) {
p.log.Info("Negotiated chan series queries")
if p.cfg.AuthGossiper == nil {
// This should only ever be hit in the unit tests.
p.log.Warn("No AuthGossiper configured. Abandoning " +
"gossip sync.")
return
}
// Register the peer's gossip syncer with the gossiper.
// This blocks synchronously to ensure the gossip syncer is
// registered with the gossiper before attempting to read
// messages from the remote peer.
//
// TODO(wilmer): Only sync updates from non-channel peers. This
// requires an improved version of the current network
// bootstrapper to ensure we can find and connect to non-channel
// peers.
p.cfg.AuthGossiper.InitSyncState(p)
}
}
// taprootShutdownAllowed returns true if both parties have negotiated the
// shutdown-any-segwit feature.
func (p *Brontide) taprootShutdownAllowed() bool {
return p.RemoteFeatures().HasFeature(lnwire.ShutdownAnySegwitOptional) &&
p.LocalFeatures().HasFeature(lnwire.ShutdownAnySegwitOptional)
}
// rbfCoopCloseAllowed returns true if both parties have negotiated the new RBF
// coop close feature.
func (p *Brontide) rbfCoopCloseAllowed() bool {
return p.RemoteFeatures().HasFeature(
lnwire.RbfCoopCloseOptionalStaging,
) && p.LocalFeatures().HasFeature(
lnwire.RbfCoopCloseOptionalStaging,
)
}
// QuitSignal is a method that should return a channel which will be sent upon
// or closed once the backing peer exits. This allows callers using the
// interface to cancel any processing in the event the backing implementation
// exits.
//
// NOTE: Part of the lnpeer.Peer interface.
func (p *Brontide) QuitSignal() <-chan struct{} {
return p.cg.Done()
}
// addrWithInternalKey takes a delivery script, then attempts to supplement it
// with information related to the internal key for the addr, but only if it's
// a taproot addr.
func (p *Brontide) addrWithInternalKey(
deliveryScript []byte) (*chancloser.DeliveryAddrWithKey, error) {
// Currently, custom channels cannot be created with external upfront
// shutdown addresses, so this shouldn't be an issue. We only require
// the internal key for taproot addresses to be able to provide a non
// inclusion proof of any scripts.
internalKeyDesc, err := lnwallet.InternalKeyForAddr(
p.cfg.Wallet, &p.cfg.Wallet.Cfg.NetParams, deliveryScript,
)
if err != nil {
return nil, fmt.Errorf("unable to fetch internal key: %w", err)
}
return &chancloser.DeliveryAddrWithKey{
DeliveryAddress: deliveryScript,
InternalKey: fn.MapOption(
func(desc keychain.KeyDescriptor) btcec.PublicKey {
return *desc.PubKey
},
)(internalKeyDesc),
}, nil
}
// loadActiveChannels creates indexes within the peer for tracking all active
// channels returned by the database. It returns a slice of channel reestablish
// messages that should be sent to the peer immediately, in case we have borked
// channels that haven't been closed yet.
func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) (
[]lnwire.Message, error) {
// Return a slice of messages to send to the peers in case the channel
// cannot be loaded normally.
var msgs []lnwire.Message
scidAliasNegotiated := p.hasNegotiatedScidAlias()
for _, dbChan := range chans {
hasScidFeature := dbChan.ChanType.HasScidAliasFeature()
if scidAliasNegotiated && !hasScidFeature {
// We'll request and store an alias, making sure that a
// gossiper mapping is not created for the alias to the
// real SCID. This is done because the peer and funding
// manager are not aware of each other's states and if
// we did not do this, we would accept alias channel
// updates after 6 confirmations, which would be buggy.
// We'll queue a channel_ready message with the new
// alias. This should technically be done *after* the
// reestablish, but this behavior is pre-existing since
// the funding manager may already queue a
// channel_ready before the channel_reestablish.
if !dbChan.IsPending {
aliasScid, err := p.cfg.RequestAlias()
if err != nil {
return nil, err
}
err = p.cfg.AddLocalAlias(
aliasScid, dbChan.ShortChanID(), false,
false,
)
if err != nil {
return nil, err
}
chanID := lnwire.NewChanIDFromOutPoint(
dbChan.FundingOutpoint,
)
// Fetch the second commitment point to send in
// the channel_ready message.
second, err := dbChan.SecondCommitmentPoint()
if err != nil {
return nil, err
}
channelReadyMsg := lnwire.NewChannelReady(
chanID, second,
)
channelReadyMsg.AliasScid = &aliasScid
msgs = append(msgs, channelReadyMsg)
}
// If we've negotiated the option-scid-alias feature
// and this channel does not have ScidAliasFeature set
// to true due to an upgrade where the feature bit was
// turned on, we'll update the channel's database
// state.
err := dbChan.MarkScidAliasNegotiated()
if err != nil {
return nil, err
}
}
var chanOpts []lnwallet.ChannelOpt
p.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) {
chanOpts = append(chanOpts, lnwallet.WithLeafStore(s))
})
p.cfg.AuxSigner.WhenSome(func(s lnwallet.AuxSigner) {
chanOpts = append(chanOpts, lnwallet.WithAuxSigner(s))
})
p.cfg.AuxResolver.WhenSome(
func(s lnwallet.AuxContractResolver) {
chanOpts = append(
chanOpts, lnwallet.WithAuxResolver(s),
)
},
)
lnChan, err := lnwallet.NewLightningChannel(
p.cfg.Signer, dbChan, p.cfg.SigPool, chanOpts...,
)
if err != nil {
return nil, fmt.Errorf("unable to create channel "+
"state machine: %w", err)
}
chanPoint := dbChan.FundingOutpoint
chanID := lnwire.NewChanIDFromOutPoint(chanPoint)
p.log.Infof("Loading ChannelPoint(%v), isPending=%v",
chanPoint, lnChan.IsPending())
// Skip adding any permanently irreconcilable channels to the
// htlcswitch.
if !dbChan.HasChanStatus(channeldb.ChanStatusDefault) &&
!dbChan.HasChanStatus(channeldb.ChanStatusRestored) {
p.log.Warnf("ChannelPoint(%v) has status %v, won't "+
"start.", chanPoint, dbChan.ChanStatus())
// To help our peer recover from a potential data loss,
// we resend our channel reestablish message if the
// channel is in a borked state. We won't process any
// channel reestablish message sent from the peer, but
// that's okay since the assumption is that we did when
// marking the channel borked.
chanSync, err := dbChan.ChanSyncMsg()
if err != nil {
p.log.Errorf("Unable to create channel "+
"reestablish message for channel %v: "+
"%v", chanPoint, err)
continue
}
msgs = append(msgs, chanSync)
// Check if this channel needs to have the cooperative
// close process restarted. If so, we'll need to send
// the Shutdown message that is returned.
if dbChan.HasChanStatus(
channeldb.ChanStatusCoopBroadcasted,
) {
shutdownMsg, err := p.restartCoopClose(lnChan)
if err != nil {
p.log.Errorf("Unable to restart "+
"coop close for channel: %v",
err)
continue
}
if shutdownMsg == nil {
continue
}
// Append the message to the set of messages to
// send.
msgs = append(msgs, shutdownMsg)
}
continue
}
// Before we register this new link with the HTLC Switch, we'll
// need to fetch its current link-layer forwarding policy from
// the database.
graph := p.cfg.ChannelGraph
info, p1, p2, err := graph.FetchChannelEdgesByOutpoint(
&chanPoint,
)
if err != nil && !errors.Is(err, graphdb.ErrEdgeNotFound) {
return nil, err
}
// We'll filter out our policy from the directional channel
// edges based whom the edge connects to. If it doesn't connect
// to us, then we know that we were the one that advertised the
// policy.
//
// TODO(roasbeef): can add helper method to get policy for
// particular channel.
var selfPolicy *models.ChannelEdgePolicy
if info != nil && bytes.Equal(info.NodeKey1Bytes[:],
p.cfg.ServerPubKey[:]) {
selfPolicy = p1
} else {
selfPolicy = p2
}
// If we don't yet have an advertised routing policy, then
// we'll use the current default, otherwise we'll translate the
// routing policy into a forwarding policy.
var forwardingPolicy *models.ForwardingPolicy
if selfPolicy != nil {
var inboundWireFee lnwire.Fee
_, err := selfPolicy.ExtraOpaqueData.ExtractRecords(
&inboundWireFee,
)
if err != nil {
return nil, err
}
inboundFee := models.NewInboundFeeFromWire(
inboundWireFee,
)
forwardingPolicy = &models.ForwardingPolicy{
MinHTLCOut: selfPolicy.MinHTLC,
MaxHTLC: selfPolicy.MaxHTLC,
BaseFee: selfPolicy.FeeBaseMSat,
FeeRate: selfPolicy.FeeProportionalMillionths,
TimeLockDelta: uint32(selfPolicy.TimeLockDelta),
InboundFee: inboundFee,
}
} else {
p.log.Warnf("Unable to find our forwarding policy "+
"for channel %v, using default values",
chanPoint)
forwardingPolicy = &p.cfg.RoutingPolicy
}
p.log.Tracef("Using link policy of: %v",
spew.Sdump(forwardingPolicy))
// If the channel is pending, set the value to nil in the
// activeChannels map. This is done to signify that the channel
// is pending. We don't add the link to the switch here - it's
// the funding manager's responsibility to spin up pending
// channels. Adding them here would just be extra work as we'll
// tear them down when creating + adding the final link.
if lnChan.IsPending() {
p.activeChannels.Store(chanID, nil)
continue
}
shutdownInfo, err := lnChan.State().ShutdownInfo()
if err != nil && !errors.Is(err, channeldb.ErrNoShutdownInfo) {
return nil, err
}
isTaprootChan := lnChan.ChanType().IsTaproot()
var (
shutdownMsg fn.Option[lnwire.Shutdown]
shutdownInfoErr error
)
shutdownInfo.WhenSome(func(info channeldb.ShutdownInfo) {
// If we can use the new RBF close feature, we don't
// need to create the legacy closer. However for taproot
// channels, we'll continue to use the legacy closer.
if p.rbfCoopCloseAllowed() && !isTaprootChan {
return
}
// Compute an ideal fee.
feePerKw, err := p.cfg.FeeEstimator.EstimateFeePerKW(
p.cfg.CoopCloseTargetConfs,
)
if err != nil {
shutdownInfoErr = fmt.Errorf("unable to "+
"estimate fee: %w", err)
return
}
addr, err := p.addrWithInternalKey(
info.DeliveryScript.Val,
)
if err != nil {
shutdownInfoErr = fmt.Errorf("unable to make "+
"delivery addr: %w", err)
return
}
negotiateChanCloser, err := p.createChanCloser(
lnChan, addr, feePerKw, nil,
info.Closer(),
)
if err != nil {
shutdownInfoErr = fmt.Errorf("unable to "+
"create chan closer: %w", err)
return
}
chanID := lnwire.NewChanIDFromOutPoint(
lnChan.State().FundingOutpoint,
)
p.activeChanCloses.Store(chanID, makeNegotiateCloser(
negotiateChanCloser,
))
// Create the Shutdown message.
shutdown, err := negotiateChanCloser.ShutdownChan()
if err != nil {
p.activeChanCloses.Delete(chanID)
shutdownInfoErr = err
return
}
shutdownMsg = fn.Some(*shutdown)
})
if shutdownInfoErr != nil {
return nil, shutdownInfoErr
}
// Subscribe to the set of on-chain events for this channel.
chainEvents, err := p.cfg.ChainArb.SubscribeChannelEvents(
chanPoint,
)
if err != nil {
return nil, err
}
err = p.addLink(
&chanPoint, lnChan, forwardingPolicy, chainEvents,
true, shutdownMsg,
)
if err != nil {
return nil, fmt.Errorf("unable to add link %v to "+
"switch: %v", chanPoint, err)
}
p.activeChannels.Store(chanID, lnChan)
// We're using the old co-op close, so we don't need to init
// the new RBF chan closer. If we have a taproot chan, then
// we'll also use the legacy type, so we don't need to make the
// new closer.
if !p.rbfCoopCloseAllowed() || isTaprootChan {
continue
}
// Now that the link has been added above, we'll also init an
// RBF chan closer for this channel, but only if the new close
// feature is negotiated.
//
// Creating this here ensures that any shutdown messages sent
// will be automatically routed by the msg router.
if _, err := p.initRbfChanCloser(lnChan); err != nil {
p.activeChanCloses.Delete(chanID)
return nil, fmt.Errorf("unable to init RBF chan "+
"closer during peer connect: %w", err)
}
// If the shutdown info isn't blank, then we should kick things
// off by sending a shutdown message to the remote party to
// continue the old shutdown flow.
restartShutdown := func(s channeldb.ShutdownInfo) error {
return p.startRbfChanCloser(
newRestartShutdownInit(s),
lnChan.ChannelPoint(),
)
}
err = fn.MapOptionZ(shutdownInfo, restartShutdown)
if err != nil {
return nil, fmt.Errorf("unable to start RBF "+
"chan closer: %w", err)
}
}
return msgs, nil
}
// addLink creates and adds a new ChannelLink from the specified channel.
func (p *Brontide) addLink(chanPoint *wire.OutPoint,
lnChan *lnwallet.LightningChannel,
forwardingPolicy *models.ForwardingPolicy,
chainEvents *contractcourt.ChainEventSubscription,
syncStates bool, shutdownMsg fn.Option[lnwire.Shutdown]) error {
// onChannelFailure will be called by the link in case the channel
// fails for some reason.
onChannelFailure := func(chanID lnwire.ChannelID,
shortChanID lnwire.ShortChannelID,
linkErr htlcswitch.LinkFailureError) {
failure := linkFailureReport{
chanPoint: *chanPoint,
chanID: chanID,
shortChanID: shortChanID,
linkErr: linkErr,
}
select {
case p.linkFailures <- failure:
case <-p.cg.Done():
case <-p.cfg.Quit:
}
}
updateContractSignals := func(signals *contractcourt.ContractSignals) error {
return p.cfg.ChainArb.UpdateContractSignals(*chanPoint, signals)
}
notifyContractUpdate := func(update *contractcourt.ContractUpdate) error {
return p.cfg.ChainArb.NotifyContractUpdate(*chanPoint, update)
}
//nolint:ll
linkCfg := htlcswitch.ChannelLinkConfig{
Peer: p,
DecodeHopIterators: p.cfg.Sphinx.DecodeHopIterators,
ExtractErrorEncrypter: p.cfg.Sphinx.ExtractErrorEncrypter,
FetchLastChannelUpdate: p.cfg.FetchLastChanUpdate,
HodlMask: p.cfg.Hodl.Mask(),
Registry: p.cfg.Invoices,
BestHeight: p.cfg.Switch.BestHeight,
Circuits: p.cfg.Switch.CircuitModifier(),
ForwardPackets: p.cfg.InterceptSwitch.ForwardPackets,
FwrdingPolicy: *forwardingPolicy,
FeeEstimator: p.cfg.FeeEstimator,
PreimageCache: p.cfg.WitnessBeacon,
ChainEvents: chainEvents,
UpdateContractSignals: updateContractSignals,
NotifyContractUpdate: notifyContractUpdate,
OnChannelFailure: onChannelFailure,
SyncStates: syncStates,
BatchTicker: ticker.New(p.cfg.ChannelCommitInterval),
FwdPkgGCTicker: ticker.New(time.Hour),
PendingCommitTicker: ticker.New(
p.cfg.PendingCommitInterval,
),
BatchSize: p.cfg.ChannelCommitBatchSize,
UnsafeReplay: p.cfg.UnsafeReplay,
MinUpdateTimeout: htlcswitch.DefaultMinLinkFeeUpdateTimeout,
MaxUpdateTimeout: htlcswitch.DefaultMaxLinkFeeUpdateTimeout,
OutgoingCltvRejectDelta: p.cfg.OutgoingCltvRejectDelta,
TowerClient: p.cfg.TowerClient,
MaxOutgoingCltvExpiry: p.cfg.MaxOutgoingCltvExpiry,
MaxFeeAllocation: p.cfg.MaxChannelFeeAllocation,
MaxAnchorsCommitFeeRate: p.cfg.MaxAnchorsCommitFeeRate,
NotifyActiveLink: p.cfg.ChannelNotifier.NotifyActiveLinkEvent,
NotifyActiveChannel: p.cfg.ChannelNotifier.NotifyActiveChannelEvent,
NotifyInactiveChannel: p.cfg.ChannelNotifier.NotifyInactiveChannelEvent,
NotifyInactiveLinkEvent: p.cfg.ChannelNotifier.NotifyInactiveLinkEvent,
HtlcNotifier: p.cfg.HtlcNotifier,
GetAliases: p.cfg.GetAliases,
PreviouslySentShutdown: shutdownMsg,
DisallowRouteBlinding: p.cfg.DisallowRouteBlinding,
MaxFeeExposure: p.cfg.MaxFeeExposure,
ShouldFwdExpEndorsement: p.cfg.ShouldFwdExpEndorsement,
DisallowQuiescence: p.cfg.DisallowQuiescence ||
!p.remoteFeatures.HasFeature(lnwire.QuiescenceOptional),
AuxTrafficShaper: p.cfg.AuxTrafficShaper,
}
// Before adding our new link, purge the switch of any pending or live
// links going by the same channel id. If one is found, we'll shut it
// down to ensure that the mailboxes are only ever under the control of
// one link.
chanID := lnwire.NewChanIDFromOutPoint(*chanPoint)
p.cfg.Switch.RemoveLink(chanID)
// With the channel link created, we'll now notify the htlc switch so
// this channel can be used to dispatch local payments and also
// passively forward payments.
return p.cfg.Switch.CreateAndAddLink(linkCfg, lnChan)
}
// maybeSendNodeAnn sends our node announcement to the remote peer if at least
// one confirmed public channel exists with them.
func (p *Brontide) maybeSendNodeAnn(channels []*channeldb.OpenChannel) {
defer p.cg.WgDone()
hasConfirmedPublicChan := false
for _, channel := range channels {
if channel.IsPending {
continue
}
if channel.ChannelFlags&lnwire.FFAnnounceChannel == 0 {
continue
}
hasConfirmedPublicChan = true
break
}
if !hasConfirmedPublicChan {
return
}
ourNodeAnn, err := p.cfg.GenNodeAnnouncement()
if err != nil {
p.log.Debugf("Unable to retrieve node announcement: %v", err)
return
}
if err := p.SendMessageLazy(false, &ourNodeAnn); err != nil {
p.log.Debugf("Unable to resend node announcement: %v", err)
}
}
// maybeSendChannelUpdates sends our channel updates to the remote peer if we
// have any active channels with them.
func (p *Brontide) maybeSendChannelUpdates() {
defer p.cg.WgDone()
// If we don't have any active channels, then we can exit early.
if p.activeChannels.Len() == 0 {
return
}
maybeSendUpd := func(cid lnwire.ChannelID,
lnChan *lnwallet.LightningChannel) error {
// Nil channels are pending, so we'll skip them.
if lnChan == nil {
return nil
}
dbChan := lnChan.State()
scid := func() lnwire.ShortChannelID {
switch {
// Otherwise if it's a zero conf channel and confirmed,
// then we need to use the "real" scid.
case dbChan.IsZeroConf() && dbChan.ZeroConfConfirmed():
return dbChan.ZeroConfRealScid()
// Otherwise, we can use the normal scid.
default:
return dbChan.ShortChanID()
}
}()
// Now that we know the channel is in a good state, we'll try
// to fetch the update to send to the remote peer. If the
// channel is pending, and not a zero conf channel, we'll get
// an error here which we'll ignore.
chanUpd, err := p.cfg.FetchLastChanUpdate(scid)
if err != nil {
p.log.Debugf("Unable to fetch channel update for "+
"ChannelPoint(%v), scid=%v: %v",
dbChan.FundingOutpoint, dbChan.ShortChanID, err)
return nil
}
p.log.Debugf("Sending channel update for ChannelPoint(%v), "+
"scid=%v", dbChan.FundingOutpoint, dbChan.ShortChanID)
// We'll send it as a normal message instead of using the lazy
// queue to prioritize transmission of the fresh update.
if err := p.SendMessage(false, chanUpd); err != nil {
err := fmt.Errorf("unable to send channel update for "+
"ChannelPoint(%v), scid=%v: %w",
dbChan.FundingOutpoint, dbChan.ShortChanID(),
err)
p.log.Errorf(err.Error())
return err
}
return nil
}
p.activeChannels.ForEach(maybeSendUpd)
}
// WaitForDisconnect waits until the peer has disconnected. A peer may be
// disconnected if the local or remote side terminates the connection, or an
// irrecoverable protocol error has been encountered. This method will only
// begin watching the peer's waitgroup after the ready channel or the peer's
// quit channel are signaled. The ready channel should only be signaled if a
// call to Start returns no error. Otherwise, if the peer fails to start,
// calling Disconnect will signal the quit channel and the method will not
// block, since no goroutines were spawned.
func (p *Brontide) WaitForDisconnect(ready chan struct{}) {
// Before we try to call the `Wait` goroutine, we'll make sure the main
// set of goroutines are already active.
select {
case <-p.startReady:
case <-p.cg.Done():
return
}
select {
case <-ready:
case <-p.cg.Done():
}
p.cg.WgWait()
}
// Disconnect terminates the connection with the remote peer. Additionally, a
// signal is sent to the server and htlcSwitch indicating the resources
// allocated to the peer can now be cleaned up.
func (p *Brontide) Disconnect(reason error) {
if !atomic.CompareAndSwapInt32(&p.disconnect, 0, 1) {
return
}
// Make sure initialization has completed before we try to tear things
// down.
//
// NOTE: We only read the `startReady` chan if the peer has been
// started, otherwise we will skip reading it as this chan won't be
// closed, hence blocks forever.
if atomic.LoadInt32(&p.started) == 1 {
p.log.Debugf("Started, waiting on startReady signal")
select {
case <-p.startReady:
case <-p.cg.Done():
return
}
}
err := fmt.Errorf("disconnecting %s, reason: %v", p, reason)
p.storeError(err)
p.log.Infof(err.Error())
// Stop PingManager before closing TCP connection.
p.pingManager.Stop()
// Ensure that the TCP connection is properly closed before continuing.
p.cfg.Conn.Close()
p.cg.Quit()
// If our msg router isn't global (local to this instance), then we'll
// stop it. Otherwise, we'll leave it running.
if !p.globalMsgRouter {
p.msgRouter.WhenSome(func(router msgmux.Router) {
router.Stop()
})
}
}
// String returns the string representation of this peer.
func (p *Brontide) String() string {
return fmt.Sprintf("%x@%s", p.cfg.PubKeyBytes, p.cfg.Conn.RemoteAddr())
}
// readNextMessage reads, and returns the next message on the wire along with
// any additional raw payload.
func (p *Brontide) readNextMessage() (lnwire.Message, error) {
noiseConn := p.cfg.Conn
err := noiseConn.SetReadDeadline(time.Time{})
if err != nil {
return nil, err
}
pktLen, err := noiseConn.ReadNextHeader()
if err != nil {
return nil, fmt.Errorf("read next header: %w", err)
}
// First we'll read the next _full_ message. We do this rather than
// reading incrementally from the stream as the Lightning wire protocol
// is message oriented and allows nodes to pad on additional data to
// the message stream.
var (
nextMsg lnwire.Message
msgLen uint64
)
err = p.cfg.ReadPool.Submit(func(buf *buffer.Read) error {
// Before reading the body of the message, set the read timeout
// accordingly to ensure we don't block other readers using the
// pool. We do so only after the task has been scheduled to
// ensure the deadline doesn't expire while the message is in
// the process of being scheduled.
readDeadline := time.Now().Add(
p.scaleTimeout(readMessageTimeout),
)
readErr := noiseConn.SetReadDeadline(readDeadline)
if readErr != nil {
return readErr
}
// The ReadNextBody method will actually end up re-using the
// buffer, so within this closure, we can continue to use
// rawMsg as it's just a slice into the buf from the buffer
// pool.
rawMsg, readErr := noiseConn.ReadNextBody(buf[:pktLen])
if readErr != nil {
return fmt.Errorf("read next body: %w", readErr)
}
msgLen = uint64(len(rawMsg))
// Next, create a new io.Reader implementation from the raw
// message, and use this to decode the message directly from.
msgReader := bytes.NewReader(rawMsg)
nextMsg, err = lnwire.ReadMessage(msgReader, 0)
if err != nil {
return err
}
// At this point, rawMsg and buf will be returned back to the
// buffer pool for re-use.
return nil
})
atomic.AddUint64(&p.bytesReceived, msgLen)
if err != nil {
return nil, err
}
p.logWireMessage(nextMsg, true)
return nextMsg, nil
}
// msgStream implements a goroutine-safe, in-order stream of messages to be
// delivered via closure to a receiver. These messages MUST be in order due to
// the nature of the lightning channel commitment and gossiper state machines.
// TODO(conner): use stream handler interface to abstract out stream
// state/logging.
type msgStream struct {
streamShutdown int32 // To be used atomically.
peer *Brontide
apply func(lnwire.Message)
startMsg string
stopMsg string
msgCond *sync.Cond
msgs []lnwire.Message
mtx sync.Mutex
producerSema chan struct{}
wg sync.WaitGroup
quit chan struct{}
}
// newMsgStream creates a new instance of a chanMsgStream for a particular
// channel identified by its channel ID. bufSize is the max number of messages
// that should be buffered in the internal queue. Callers should set this to a
// sane value that avoids blocking unnecessarily, but doesn't allow an
// unbounded amount of memory to be allocated to buffer incoming messages.
func newMsgStream(p *Brontide, startMsg, stopMsg string, bufSize uint32,
apply func(lnwire.Message)) *msgStream {
stream := &msgStream{
peer: p,
apply: apply,
startMsg: startMsg,
stopMsg: stopMsg,
producerSema: make(chan struct{}, bufSize),
quit: make(chan struct{}),
}
stream.msgCond = sync.NewCond(&stream.mtx)
// Before we return the active stream, we'll populate the producer's
// semaphore channel. We'll use this to ensure that the producer won't
// attempt to allocate memory in the queue for an item until it has
// sufficient extra space.
for i := uint32(0); i < bufSize; i++ {
stream.producerSema <- struct{}{}
}
return stream
}
// Start starts the chanMsgStream.
func (ms *msgStream) Start() {
ms.wg.Add(1)
go ms.msgConsumer()
}
// Stop stops the chanMsgStream.
func (ms *msgStream) Stop() {
// TODO(roasbeef): signal too?
close(ms.quit)
// Now that we've closed the channel, we'll repeatedly signal the msg
// consumer until we've detected that it has exited.
for atomic.LoadInt32(&ms.streamShutdown) == 0 {
ms.msgCond.Signal()
time.Sleep(time.Millisecond * 100)
}
ms.wg.Wait()
}
// msgConsumer is the main goroutine that streams messages from the peer's
// readHandler directly to the target channel.
func (ms *msgStream) msgConsumer() {
defer ms.wg.Done()
defer peerLog.Tracef(ms.stopMsg)
defer atomic.StoreInt32(&ms.streamShutdown, 1)
peerLog.Tracef(ms.startMsg)
for {
// First, we'll check our condition. If the queue of messages
// is empty, then we'll wait until a new item is added.
ms.msgCond.L.Lock()
for len(ms.msgs) == 0 {
ms.msgCond.Wait()
// If we woke up in order to exit, then we'll do so.
// Otherwise, we'll check the message queue for any new
// items.
select {
case <-ms.peer.cg.Done():
ms.msgCond.L.Unlock()
return
case <-ms.quit:
ms.msgCond.L.Unlock()
return
default:
}
}
// Grab the message off the front of the queue, shifting the
// slice's reference down one in order to remove the message
// from the queue.
msg := ms.msgs[0]
ms.msgs[0] = nil // Set to nil to prevent GC leak.
ms.msgs = ms.msgs[1:]
ms.msgCond.L.Unlock()
ms.apply(msg)
// We've just successfully processed an item, so we'll signal
// to the producer that a new slot in the buffer. We'll use
// this to bound the size of the buffer to avoid allowing it to
// grow indefinitely.
select {
case ms.producerSema <- struct{}{}:
case <-ms.peer.cg.Done():
return
case <-ms.quit:
return
}
}
}
// AddMsg adds a new message to the msgStream. This function is safe for
// concurrent access.
func (ms *msgStream) AddMsg(msg lnwire.Message) {
// First, we'll attempt to receive from the producerSema struct. This
// acts as a semaphore to prevent us from indefinitely buffering
// incoming items from the wire. Either the msg queue isn't full, and
// we'll not block, or the queue is full, and we'll block until either
// we're signalled to quit, or a slot is freed up.
select {
case <-ms.producerSema:
case <-ms.peer.cg.Done():
return
case <-ms.quit:
return
}
// Next, we'll lock the condition, and add the message to the end of
// the message queue.
ms.msgCond.L.Lock()
ms.msgs = append(ms.msgs, msg)
ms.msgCond.L.Unlock()
// With the message added, we signal to the msgConsumer that there are
// additional messages to consume.
ms.msgCond.Signal()
}
// waitUntilLinkActive waits until the target link is active and returns a
// ChannelLink to pass messages to. It accomplishes this by subscribing to
// an ActiveLinkEvent which is emitted by the link when it first starts up.
func waitUntilLinkActive(p *Brontide,
cid lnwire.ChannelID) htlcswitch.ChannelUpdateHandler {
p.log.Tracef("Waiting for link=%v to be active", cid)
// Subscribe to receive channel events.
//
// NOTE: If the link is already active by SubscribeChannelEvents, then
// GetLink will retrieve the link and we can send messages. If the link
// becomes active between SubscribeChannelEvents and GetLink, then GetLink
// will retrieve the link. If the link becomes active after GetLink, then
// we will get an ActiveLinkEvent notification and retrieve the link. If
// the call to GetLink is before SubscribeChannelEvents, however, there
// will be a race condition.
sub, err := p.cfg.ChannelNotifier.SubscribeChannelEvents()
if err != nil {
// If we have a non-nil error, then the server is shutting down and we
// can exit here and return nil. This means no message will be delivered
// to the link.
return nil
}
defer sub.Cancel()
// The link may already be active by this point, and we may have missed the
// ActiveLinkEvent. Check if the link exists.
link := p.fetchLinkFromKeyAndCid(cid)
if link != nil {
return link
}
// If the link is nil, we must wait for it to be active.
for {
select {
// A new event has been sent by the ChannelNotifier. We first check
// whether the event is an ActiveLinkEvent. If it is, we'll check
// that the event is for this channel. Otherwise, we discard the
// message.
case e := <-sub.Updates():
event, ok := e.(channelnotifier.ActiveLinkEvent)
if !ok {
// Ignore this notification.
continue
}
chanPoint := event.ChannelPoint
// Check whether the retrieved chanPoint matches the target
// channel id.
if !cid.IsChanPoint(chanPoint) {
continue
}
// The link shouldn't be nil as we received an
// ActiveLinkEvent. If it is nil, we return nil and the
// calling function should catch it.
return p.fetchLinkFromKeyAndCid(cid)
case <-p.cg.Done():
return nil
}
}
}
// newChanMsgStream is used to create a msgStream between the peer and
// particular channel link in the htlcswitch. We utilize additional
// synchronization with the fundingManager to ensure we don't attempt to
// dispatch a message to a channel before it is fully active. A reference to the
// channel this stream forwards to is held in scope to prevent unnecessary
// lookups.
func newChanMsgStream(p *Brontide, cid lnwire.ChannelID) *msgStream {
var chanLink htlcswitch.ChannelUpdateHandler
apply := func(msg lnwire.Message) {
// This check is fine because if the link no longer exists, it will
// be removed from the activeChannels map and subsequent messages
// shouldn't reach the chan msg stream.
if chanLink == nil {
chanLink = waitUntilLinkActive(p, cid)
// If the link is still not active and the calling function
// errored out, just return.
if chanLink == nil {
p.log.Warnf("Link=%v is not active", cid)
return
}
}
// In order to avoid unnecessarily delivering message
// as the peer is exiting, we'll check quickly to see
// if we need to exit.
select {
case <-p.cg.Done():
return
default:
}
chanLink.HandleChannelUpdate(msg)
}
return newMsgStream(p,
fmt.Sprintf("Update stream for ChannelID(%x) created", cid[:]),
fmt.Sprintf("Update stream for ChannelID(%x) exiting", cid[:]),
msgStreamSize,
apply,
)
}
// newDiscMsgStream is used to setup a msgStream between the peer and the
// authenticated gossiper. This stream should be used to forward all remote
// channel announcements.
func newDiscMsgStream(p *Brontide) *msgStream {
apply := func(msg lnwire.Message) {
// TODO(yy): `ProcessRemoteAnnouncement` returns an error chan
// and we need to process it.
p.cfg.AuthGossiper.ProcessRemoteAnnouncement(msg, p)
}
return newMsgStream(
p,
"Update stream for gossiper created",
"Update stream for gossiper exited",
msgStreamSize,
apply,
)
}
// readHandler is responsible for reading messages off the wire in series, then
// properly dispatching the handling of the message to the proper subsystem.
//
// NOTE: This method MUST be run as a goroutine.
func (p *Brontide) readHandler() {
defer p.cg.WgDone()
// We'll stop the timer after a new messages is received, and also
// reset it after we process the next message.
idleTimer := time.AfterFunc(idleTimeout, func() {
err := fmt.Errorf("peer %s no answer for %s -- disconnecting",
p, idleTimeout)
p.Disconnect(err)
})
// Initialize our negotiated gossip sync method before reading messages
// off the wire. When using gossip queries, this ensures a gossip
// syncer is active by the time query messages arrive.
//
// TODO(conner): have peer store gossip syncer directly and bypass
// gossiper?
p.initGossipSync()
discStream := newDiscMsgStream(p)
discStream.Start()
defer discStream.Stop()
out:
for atomic.LoadInt32(&p.disconnect) == 0 {
nextMsg, err := p.readNextMessage()
if !idleTimer.Stop() {
select {
case <-idleTimer.C:
default:
}
}
if err != nil {
p.log.Infof("unable to read message from peer: %v", err)
// If we could not read our peer's message due to an
// unknown type or invalid alias, we continue processing
// as normal. We store unknown message and address
// types, as they may provide debugging insight.
switch e := err.(type) {
// If this is just a message we don't yet recognize,
// we'll continue processing as normal as this allows
// us to introduce new messages in a forwards
// compatible manner.
case *lnwire.UnknownMessage:
p.storeError(e)
idleTimer.Reset(idleTimeout)
continue
// If they sent us an address type that we don't yet
// know of, then this isn't a wire error, so we'll
// simply continue parsing the remainder of their
// messages.
case *lnwire.ErrUnknownAddrType:
p.storeError(e)
idleTimer.Reset(idleTimeout)
continue
// If the NodeAnnouncement has an invalid alias, then
// we'll log that error above and continue so we can
// continue to read messages from the peer. We do not
// store this error because it is of little debugging
// value.
case *lnwire.ErrInvalidNodeAlias:
idleTimer.Reset(idleTimeout)
continue
// If the error we encountered wasn't just a message we
// didn't recognize, then we'll stop all processing as
// this is a fatal error.
default:
break out
}
}
// If a message router is active, then we'll try to have it
// handle this message. If it can, then we're able to skip the
// rest of the message handling logic.
err = fn.MapOptionZ(p.msgRouter, func(r msgmux.Router) error {
return r.RouteMsg(msgmux.PeerMsg{
PeerPub: *p.IdentityKey(),
Message: nextMsg,
})
})
// No error occurred, and the message was handled by the
// router.
if err == nil {
continue
}
var (
targetChan lnwire.ChannelID
isLinkUpdate bool
)
switch msg := nextMsg.(type) {
case *lnwire.Pong:
// When we receive a Pong message in response to our
// last ping message, we send it to the pingManager
p.pingManager.ReceivedPong(msg)
case *lnwire.Ping:
// First, we'll store their latest ping payload within
// the relevant atomic variable.
p.lastPingPayload.Store(msg.PaddingBytes[:])
// Next, we'll send over the amount of specified pong
// bytes.
pong := lnwire.NewPong(p.cfg.PongBuf[0:msg.NumPongBytes])
p.queueMsg(pong, nil)
case *lnwire.OpenChannel,
*lnwire.AcceptChannel,
*lnwire.FundingCreated,
*lnwire.FundingSigned,
*lnwire.ChannelReady:
p.cfg.FundingManager.ProcessFundingMsg(msg, p)
case *lnwire.Shutdown:
select {
case p.chanCloseMsgs <- &closeMsg{msg.ChannelID, msg}:
case <-p.cg.Done():
break out
}
case *lnwire.ClosingSigned:
select {
case p.chanCloseMsgs <- &closeMsg{msg.ChannelID, msg}:
case <-p.cg.Done():
break out
}
case *lnwire.Warning:
targetChan = msg.ChanID
isLinkUpdate = p.handleWarningOrError(targetChan, msg)
case *lnwire.Error:
targetChan = msg.ChanID
isLinkUpdate = p.handleWarningOrError(targetChan, msg)
case *lnwire.ChannelReestablish:
targetChan = msg.ChanID
isLinkUpdate = p.hasChannel(targetChan)
// If we failed to find the link in question, and the
// message received was a channel sync message, then
// this might be a peer trying to resync closed channel.
// In this case we'll try to resend our last channel
// sync message, such that the peer can recover funds
// from the closed channel.
if !isLinkUpdate {
err := p.resendChanSyncMsg(targetChan)
if err != nil {
// TODO(halseth): send error to peer?
p.log.Errorf("resend failed: %v",
err)
}
}
// For messages that implement the LinkUpdater interface, we
// will consider them as link updates and send them to
// chanStream. These messages will be queued inside chanStream
// if the channel is not active yet.
case lnwire.LinkUpdater:
targetChan = msg.TargetChanID()
isLinkUpdate = p.hasChannel(targetChan)
// Log an error if we don't have this channel. This
// means the peer has sent us a message with unknown
// channel ID.
if !isLinkUpdate {
p.log.Errorf("Unknown channel ID: %v found "+
"in received msg=%s", targetChan,
nextMsg.MsgType())
}
case *lnwire.ChannelUpdate1,
*lnwire.ChannelAnnouncement1,
*lnwire.NodeAnnouncement,
*lnwire.AnnounceSignatures1,
*lnwire.GossipTimestampRange,
*lnwire.QueryShortChanIDs,
*lnwire.QueryChannelRange,
*lnwire.ReplyChannelRange,
*lnwire.ReplyShortChanIDsEnd:
discStream.AddMsg(msg)
case *lnwire.Custom:
err := p.handleCustomMessage(msg)
if err != nil {
p.storeError(err)
p.log.Errorf("%v", err)
}
default:
// If the message we received is unknown to us, store
// the type to track the failure.
err := fmt.Errorf("unknown message type %v received",
uint16(msg.MsgType()))
p.storeError(err)
p.log.Errorf("%v", err)
}
if isLinkUpdate {
// If this is a channel update, then we need to feed it
// into the channel's in-order message stream.
p.sendLinkUpdateMsg(targetChan, nextMsg)
}
idleTimer.Reset(idleTimeout)
}
p.Disconnect(errors.New("read handler closed"))
p.log.Trace("readHandler for peer done")
}
// handleCustomMessage handles the given custom message if a handler is
// registered.
func (p *Brontide) handleCustomMessage(msg *lnwire.Custom) error {
if p.cfg.HandleCustomMessage == nil {
return fmt.Errorf("no custom message handler for "+
"message type %v", uint16(msg.MsgType()))
}
return p.cfg.HandleCustomMessage(p.PubKey(), msg)
}
// isLoadedFromDisk returns true if the provided channel ID is loaded from
// disk.
//
// NOTE: only returns true for pending channels.
func (p *Brontide) isLoadedFromDisk(chanID lnwire.ChannelID) bool {
// If this is a newly added channel, no need to reestablish.
_, added := p.addedChannels.Load(chanID)
if added {
return false
}
// Return false if the channel is unknown.
channel, ok := p.activeChannels.Load(chanID)
if !ok {
return false
}
// During startup, we will use a nil value to mark a pending channel
// that's loaded from disk.
return channel == nil
}
// isActiveChannel returns true if the provided channel id is active, otherwise
// returns false.
func (p *Brontide) isActiveChannel(chanID lnwire.ChannelID) bool {
// The channel would be nil if,
// - the channel doesn't exist, or,
// - the channel exists, but is pending. In this case, we don't
// consider this channel active.
channel, _ := p.activeChannels.Load(chanID)
return channel != nil
}
// isPendingChannel returns true if the provided channel ID is pending, and
// returns false if the channel is active or unknown.
func (p *Brontide) isPendingChannel(chanID lnwire.ChannelID) bool {
// Return false if the channel is unknown.
channel, ok := p.activeChannels.Load(chanID)
if !ok {
return false
}
return channel == nil
}
// hasChannel returns true if the peer has a pending/active channel specified
// by the channel ID.
func (p *Brontide) hasChannel(chanID lnwire.ChannelID) bool {
_, ok := p.activeChannels.Load(chanID)
return ok
}
// storeError stores an error in our peer's buffer of recent errors with the
// current timestamp. Errors are only stored if we have at least one active
// channel with the peer to mitigate a dos vector where a peer costlessly
// connects to us and spams us with errors.
func (p *Brontide) storeError(err error) {
var haveChannels bool
p.activeChannels.Range(func(_ lnwire.ChannelID,
channel *lnwallet.LightningChannel) bool {
// Pending channels will be nil in the activeChannels map.
if channel == nil {
// Return true to continue the iteration.
return true
}
haveChannels = true
// Return false to break the iteration.
return false
})
// If we do not have any active channels with the peer, we do not store
// errors as a dos mitigation.
if !haveChannels {
p.log.Trace("no channels with peer, not storing err")
return
}
p.cfg.ErrorBuffer.Add(
&TimestampedError{Timestamp: time.Now(), Error: err},
)
}
// handleWarningOrError processes a warning or error msg and returns true if
// msg should be forwarded to the associated channel link. False is returned if
// any necessary forwarding of msg was already handled by this method. If msg is
// an error from a peer with an active channel, we'll store it in memory.
//
// NOTE: This method should only be called from within the readHandler.
func (p *Brontide) handleWarningOrError(chanID lnwire.ChannelID,
msg lnwire.Message) bool {
if errMsg, ok := msg.(*lnwire.Error); ok {
p.storeError(errMsg)
}
switch {
// Connection wide messages should be forwarded to all channel links
// with this peer.
case chanID == lnwire.ConnectionWideID:
for _, chanStream := range p.activeMsgStreams {
chanStream.AddMsg(msg)
}
return false
// If the channel ID for the message corresponds to a pending channel,
// then the funding manager will handle it.
case p.cfg.FundingManager.IsPendingChannel(chanID, p):
p.cfg.FundingManager.ProcessFundingMsg(msg, p)
return false
// If not we hand the message to the channel link for this channel.
case p.isActiveChannel(chanID):
return true
default:
return false
}
}
// messageSummary returns a human-readable string that summarizes a
// incoming/outgoing message. Not all messages will have a summary, only those
// which have additional data that can be informative at a glance.
func messageSummary(msg lnwire.Message) string {
switch msg := msg.(type) {
case *lnwire.Init:
// No summary.
return ""
case *lnwire.OpenChannel:
return fmt.Sprintf("temp_chan_id=%x, chain=%v, csv=%v, amt=%v, "+
"push_amt=%v, reserve=%v, flags=%v",
msg.PendingChannelID[:], msg.ChainHash,
msg.CsvDelay, msg.FundingAmount, msg.PushAmount,
msg.ChannelReserve, msg.ChannelFlags)
case *lnwire.AcceptChannel:
return fmt.Sprintf("temp_chan_id=%x, reserve=%v, csv=%v, num_confs=%v",
msg.PendingChannelID[:], msg.ChannelReserve, msg.CsvDelay,
msg.MinAcceptDepth)
case *lnwire.FundingCreated:
return fmt.Sprintf("temp_chan_id=%x, chan_point=%v",
msg.PendingChannelID[:], msg.FundingPoint)
case *lnwire.FundingSigned:
return fmt.Sprintf("chan_id=%v", msg.ChanID)
case *lnwire.ChannelReady:
return fmt.Sprintf("chan_id=%v, next_point=%x",
msg.ChanID, msg.NextPerCommitmentPoint.SerializeCompressed())
case *lnwire.Shutdown:
return fmt.Sprintf("chan_id=%v, script=%x", msg.ChannelID,
msg.Address[:])
case *lnwire.ClosingComplete:
return fmt.Sprintf("chan_id=%v, fee_sat=%v, locktime=%v",
msg.ChannelID, msg.FeeSatoshis, msg.LockTime)
case *lnwire.ClosingSig:
return fmt.Sprintf("chan_id=%v", msg.ChannelID)
case *lnwire.ClosingSigned:
return fmt.Sprintf("chan_id=%v, fee_sat=%v", msg.ChannelID,
msg.FeeSatoshis)
case *lnwire.UpdateAddHTLC:
var blindingPoint []byte
msg.BlindingPoint.WhenSome(
func(b tlv.RecordT[lnwire.BlindingPointTlvType,
*btcec.PublicKey]) {
blindingPoint = b.Val.SerializeCompressed()
},
)
return fmt.Sprintf("chan_id=%v, id=%v, amt=%v, expiry=%v, "+
"hash=%x, blinding_point=%x, custom_records=%v",
msg.ChanID, msg.ID, msg.Amount, msg.Expiry,
msg.PaymentHash[:], blindingPoint, msg.CustomRecords)
case *lnwire.UpdateFailHTLC:
return fmt.Sprintf("chan_id=%v, id=%v, reason=%x", msg.ChanID,
msg.ID, msg.Reason)
case *lnwire.UpdateFulfillHTLC:
return fmt.Sprintf("chan_id=%v, id=%v, preimage=%x, "+
"custom_records=%v", msg.ChanID, msg.ID,
msg.PaymentPreimage[:], msg.CustomRecords)
case *lnwire.CommitSig:
return fmt.Sprintf("chan_id=%v, num_htlcs=%v", msg.ChanID,
len(msg.HtlcSigs))
case *lnwire.RevokeAndAck:
return fmt.Sprintf("chan_id=%v, rev=%x, next_point=%x",
msg.ChanID, msg.Revocation[:],
msg.NextRevocationKey.SerializeCompressed())
case *lnwire.UpdateFailMalformedHTLC:
return fmt.Sprintf("chan_id=%v, id=%v, fail_code=%v",
msg.ChanID, msg.ID, msg.FailureCode)
case *lnwire.Warning:
return fmt.Sprintf("%v", msg.Warning())
case *lnwire.Error:
return fmt.Sprintf("%v", msg.Error())
case *lnwire.AnnounceSignatures1:
return fmt.Sprintf("chan_id=%v, short_chan_id=%v", msg.ChannelID,
msg.ShortChannelID.ToUint64())
case *lnwire.ChannelAnnouncement1:
return fmt.Sprintf("chain_hash=%v, short_chan_id=%v",
msg.ChainHash, msg.ShortChannelID.ToUint64())
case *lnwire.ChannelUpdate1:
return fmt.Sprintf("chain_hash=%v, short_chan_id=%v, "+
"mflags=%v, cflags=%v, update_time=%v", msg.ChainHash,
msg.ShortChannelID.ToUint64(), msg.MessageFlags,
msg.ChannelFlags, time.Unix(int64(msg.Timestamp), 0))
case *lnwire.NodeAnnouncement:
return fmt.Sprintf("node=%x, update_time=%v",
msg.NodeID, time.Unix(int64(msg.Timestamp), 0))
case *lnwire.Ping:
return fmt.Sprintf("ping_bytes=%x", msg.PaddingBytes[:])
case *lnwire.Pong:
return fmt.Sprintf("len(pong_bytes)=%d", len(msg.PongBytes[:]))
case *lnwire.UpdateFee:
return fmt.Sprintf("chan_id=%v, fee_update_sat=%v",
msg.ChanID, int64(msg.FeePerKw))
case *lnwire.ChannelReestablish:
return fmt.Sprintf("chan_id=%v, next_local_height=%v, "+
"remote_tail_height=%v", msg.ChanID,
msg.NextLocalCommitHeight, msg.RemoteCommitTailHeight)
case *lnwire.ReplyShortChanIDsEnd:
return fmt.Sprintf("chain_hash=%v, complete=%v", msg.ChainHash,
msg.Complete)
case *lnwire.ReplyChannelRange:
return fmt.Sprintf("start_height=%v, end_height=%v, "+
"num_chans=%v, encoding=%v", msg.FirstBlockHeight,
msg.LastBlockHeight(), len(msg.ShortChanIDs),
msg.EncodingType)
case *lnwire.QueryShortChanIDs:
return fmt.Sprintf("chain_hash=%v, encoding=%v, num_chans=%v",
msg.ChainHash, msg.EncodingType, len(msg.ShortChanIDs))
case *lnwire.QueryChannelRange:
return fmt.Sprintf("chain_hash=%v, start_height=%v, "+
"end_height=%v", msg.ChainHash, msg.FirstBlockHeight,
msg.LastBlockHeight())
case *lnwire.GossipTimestampRange:
return fmt.Sprintf("chain_hash=%v, first_stamp=%v, "+
"stamp_range=%v", msg.ChainHash,
time.Unix(int64(msg.FirstTimestamp), 0),
msg.TimestampRange)
case *lnwire.Stfu:
return fmt.Sprintf("chan_id=%v, initiator=%v", msg.ChanID,
msg.Initiator)
case *lnwire.Custom:
return fmt.Sprintf("type=%d", msg.Type)
}
return fmt.Sprintf("unknown msg type=%T", msg)
}
// logWireMessage logs the receipt or sending of particular wire message. This
// function is used rather than just logging the message in order to produce
// less spammy log messages in trace mode by setting the 'Curve" parameter to
// nil. Doing this avoids printing out each of the field elements in the curve
// parameters for secp256k1.
func (p *Brontide) logWireMessage(msg lnwire.Message, read bool) {
summaryPrefix := "Received"
if !read {
summaryPrefix = "Sending"
}
p.log.Debugf("%v", lnutils.NewLogClosure(func() string {
// Debug summary of message.
summary := messageSummary(msg)
if len(summary) > 0 {
summary = "(" + summary + ")"
}
preposition := "to"
if read {
preposition = "from"
}
var msgType string
if msg.MsgType() < lnwire.CustomTypeStart {
msgType = msg.MsgType().String()
} else {
msgType = "custom"
}
return fmt.Sprintf("%v %v%s %v %s", summaryPrefix,
msgType, summary, preposition, p)
}))
prefix := "readMessage from peer"
if !read {
prefix = "writeMessage to peer"
}
p.log.Tracef(prefix+": %v", lnutils.SpewLogClosure(msg))
}
// writeMessage writes and flushes the target lnwire.Message to the remote peer.
// If the passed message is nil, this method will only try to flush an existing
// message buffered on the connection. It is safe to call this method again
// with a nil message iff a timeout error is returned. This will continue to
// flush the pending message to the wire.
//
// NOTE:
// Besides its usage in Start, this function should not be used elsewhere
// except in writeHandler. If multiple goroutines call writeMessage at the same
// time, panics can occur because WriteMessage and Flush don't use any locking
// internally.
func (p *Brontide) writeMessage(msg lnwire.Message) error {
// Only log the message on the first attempt.
if msg != nil {
p.logWireMessage(msg, false)
}
noiseConn := p.cfg.Conn
flushMsg := func() error {
// Ensure the write deadline is set before we attempt to send
// the message.
writeDeadline := time.Now().Add(
p.scaleTimeout(writeMessageTimeout),
)
err := noiseConn.SetWriteDeadline(writeDeadline)
if err != nil {
return err
}
// Flush the pending message to the wire. If an error is
// encountered, e.g. write timeout, the number of bytes written
// so far will be returned.
n, err := noiseConn.Flush()
// Record the number of bytes written on the wire, if any.
if n > 0 {
atomic.AddUint64(&p.bytesSent, uint64(n))
}
return err
}
// If the current message has already been serialized, encrypted, and
// buffered on the underlying connection we will skip straight to
// flushing it to the wire.
if msg == nil {
return flushMsg()
}
// Otherwise, this is a new message. We'll acquire a write buffer to
// serialize the message and buffer the ciphertext on the connection.
err := p.cfg.WritePool.Submit(func(buf *bytes.Buffer) error {
// Using a buffer allocated by the write pool, encode the
// message directly into the buffer.
_, writeErr := lnwire.WriteMessage(buf, msg, 0)
if writeErr != nil {
return writeErr
}
// Finally, write the message itself in a single swoop. This
// will buffer the ciphertext on the underlying connection. We
// will defer flushing the message until the write pool has been
// released.
return noiseConn.WriteMessage(buf.Bytes())
})
if err != nil {
return err
}
return flushMsg()
}
// writeHandler is a goroutine dedicated to reading messages off of an incoming
// queue, and writing them out to the wire. This goroutine coordinates with the
// queueHandler in order to ensure the incoming message queue is quickly
// drained.
//
// NOTE: This method MUST be run as a goroutine.
func (p *Brontide) writeHandler() {
// We'll stop the timer after a new messages is sent, and also reset it
// after we process the next message.
idleTimer := time.AfterFunc(idleTimeout, func() {
err := fmt.Errorf("peer %s no write for %s -- disconnecting",
p, idleTimeout)
p.Disconnect(err)
})
var exitErr error
out:
for {
select {
case outMsg := <-p.sendQueue:
// Record the time at which we first attempt to send the
// message.
startTime := time.Now()
retry:
// Write out the message to the socket. If a timeout
// error is encountered, we will catch this and retry
// after backing off in case the remote peer is just
// slow to process messages from the wire.
err := p.writeMessage(outMsg.msg)
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
p.log.Debugf("Write timeout detected for "+
"peer, first write for message "+
"attempted %v ago",
time.Since(startTime))
// If we received a timeout error, this implies
// that the message was buffered on the
// connection successfully and that a flush was
// attempted. We'll set the message to nil so
// that on a subsequent pass we only try to
// flush the buffered message, and forgo
// reserializing or reencrypting it.
outMsg.msg = nil
goto retry
}
// The write succeeded, reset the idle timer to prevent
// us from disconnecting the peer.
if !idleTimer.Stop() {
select {
case <-idleTimer.C:
default:
}
}
idleTimer.Reset(idleTimeout)
// If the peer requested a synchronous write, respond
// with the error.
if outMsg.errChan != nil {
outMsg.errChan <- err
}
if err != nil {
exitErr = fmt.Errorf("unable to write "+
"message: %v", err)
break out
}
case <-p.cg.Done():
exitErr = lnpeer.ErrPeerExiting
break out
}
}
// Avoid an exit deadlock by ensuring WaitGroups are decremented before
// disconnect.
p.cg.WgDone()
p.Disconnect(exitErr)
p.log.Trace("writeHandler for peer done")
}
// queueHandler is responsible for accepting messages from outside subsystems
// to be eventually sent out on the wire by the writeHandler.
//
// NOTE: This method MUST be run as a goroutine.
func (p *Brontide) queueHandler() {
defer p.cg.WgDone()
// priorityMsgs holds an in order list of messages deemed high-priority
// to be added to the sendQueue. This predominately includes messages
// from the funding manager and htlcswitch.
priorityMsgs := list.New()
// lazyMsgs holds an in order list of messages deemed low-priority to be
// added to the sendQueue only after all high-priority messages have
// been queued. This predominately includes messages from the gossiper.
lazyMsgs := list.New()
for {
// Examine the front of the priority queue, if it is empty check
// the low priority queue.
elem := priorityMsgs.Front()
if elem == nil {
elem = lazyMsgs.Front()
}
if elem != nil {
front := elem.Value.(outgoingMsg)
// There's an element on the queue, try adding
// it to the sendQueue. We also watch for
// messages on the outgoingQueue, in case the
// writeHandler cannot accept messages on the
// sendQueue.
select {
case p.sendQueue <- front:
if front.priority {
priorityMsgs.Remove(elem)
} else {
lazyMsgs.Remove(elem)
}
case msg := <-p.outgoingQueue:
if msg.priority {
priorityMsgs.PushBack(msg)
} else {
lazyMsgs.PushBack(msg)
}
case <-p.cg.Done():
return
}
} else {
// If there weren't any messages to send to the
// writeHandler, then we'll accept a new message
// into the queue from outside sub-systems.
select {
case msg := <-p.outgoingQueue:
if msg.priority {
priorityMsgs.PushBack(msg)
} else {
lazyMsgs.PushBack(msg)
}
case <-p.cg.Done():
return
}
}
}
}
// PingTime returns the estimated ping time to the peer in microseconds.
func (p *Brontide) PingTime() int64 {
return p.pingManager.GetPingTimeMicroSeconds()
}
// queueMsg adds the lnwire.Message to the back of the high priority send queue.
// If the errChan is non-nil, an error is sent back if the msg failed to queue
// or failed to write, and nil otherwise.
func (p *Brontide) queueMsg(msg lnwire.Message, errChan chan error) {
p.queue(true, msg, errChan)
}
// queueMsgLazy adds the lnwire.Message to the back of the low priority send
// queue. If the errChan is non-nil, an error is sent back if the msg failed to
// queue or failed to write, and nil otherwise.
func (p *Brontide) queueMsgLazy(msg lnwire.Message, errChan chan error) {
p.queue(false, msg, errChan)
}
// queue sends a given message to the queueHandler using the passed priority. If
// the errChan is non-nil, an error is sent back if the msg failed to queue or
// failed to write, and nil otherwise.
func (p *Brontide) queue(priority bool, msg lnwire.Message,
errChan chan error) {
select {
case p.outgoingQueue <- outgoingMsg{priority, msg, errChan}:
case <-p.cg.Done():
p.log.Tracef("Peer shutting down, could not enqueue msg: %v.",
spew.Sdump(msg))
if errChan != nil {
errChan <- lnpeer.ErrPeerExiting
}
}
}
// ChannelSnapshots returns a slice of channel snapshots detailing all
// currently active channels maintained with the remote peer.
func (p *Brontide) ChannelSnapshots() []*channeldb.ChannelSnapshot {
snapshots := make(
[]*channeldb.ChannelSnapshot, 0, p.activeChannels.Len(),
)
p.activeChannels.ForEach(func(_ lnwire.ChannelID,
activeChan *lnwallet.LightningChannel) error {
// If the activeChan is nil, then we skip it as the channel is
// pending.
if activeChan == nil {
return nil
}
// We'll only return a snapshot for channels that are
// *immediately* available for routing payments over.
if activeChan.RemoteNextRevocation() == nil {
return nil
}
snapshot := activeChan.StateSnapshot()
snapshots = append(snapshots, snapshot)
return nil
})
return snapshots
}
// genDeliveryScript returns a new script to be used to send our funds to in
// the case of a cooperative channel close negotiation.
func (p *Brontide) genDeliveryScript() ([]byte, error) {
// We'll send a normal p2wkh address unless we've negotiated the
// shutdown-any-segwit feature.
addrType := lnwallet.WitnessPubKey
if p.taprootShutdownAllowed() {
addrType = lnwallet.TaprootPubkey
}
deliveryAddr, err := p.cfg.Wallet.NewAddress(
addrType, false, lnwallet.DefaultAccountName,
)
if err != nil {
return nil, err
}
p.log.Infof("Delivery addr for channel close: %v",
deliveryAddr)
return txscript.PayToAddrScript(deliveryAddr)
}
// channelManager is goroutine dedicated to handling all requests/signals
// pertaining to the opening, cooperative closing, and force closing of all
// channels maintained with the remote peer.
//
// NOTE: This method MUST be run as a goroutine.
func (p *Brontide) channelManager() {
defer p.cg.WgDone()
// reenableTimeout will fire once after the configured channel status
// interval has elapsed. This will trigger us to sign new channel
// updates and broadcast them with the "disabled" flag unset.
reenableTimeout := time.After(p.cfg.ChanActiveTimeout)
out:
for {
select {
// A new pending channel has arrived which means we are about
// to complete a funding workflow and is waiting for the final
// `ChannelReady` messages to be exchanged. We will add this
// channel to the `activeChannels` with a nil value to indicate
// this is a pending channel.
case req := <-p.newPendingChannel:
p.handleNewPendingChannel(req)
// A new channel has arrived which means we've just completed a
// funding workflow. We'll initialize the necessary local
// state, and notify the htlc switch of a new link.
case req := <-p.newActiveChannel:
p.handleNewActiveChannel(req)
// The funding flow for a pending channel is failed, we will
// remove it from Brontide.
case req := <-p.removePendingChannel:
p.handleRemovePendingChannel(req)
// We've just received a local request to close an active
// channel. It will either kick of a cooperative channel
// closure negotiation, or be a notification of a breached
// contract that should be abandoned.
case req := <-p.localCloseChanReqs:
p.handleLocalCloseReq(req)
// We've received a link failure from a link that was added to
// the switch. This will initiate the teardown of the link, and
// initiate any on-chain closures if necessary.
case failure := <-p.linkFailures:
p.handleLinkFailure(failure)
// We've received a new cooperative channel closure related
// message from the remote peer, we'll use this message to
// advance the chan closer state machine.
case closeMsg := <-p.chanCloseMsgs:
p.handleCloseMsg(closeMsg)
// The channel reannounce delay has elapsed, broadcast the
// reenabled channel updates to the network. This should only
// fire once, so we set the reenableTimeout channel to nil to
// mark it for garbage collection. If the peer is torn down
// before firing, reenabling will not be attempted.
// TODO(conner): consolidate reenables timers inside chan status
// manager
case <-reenableTimeout:
p.reenableActiveChannels()
// Since this channel will never fire again during the
// lifecycle of the peer, we nil the channel to mark it
// eligible for garbage collection, and make this
// explicitly ineligible to receive in future calls to
// select. This also shaves a few CPU cycles since the
// select will ignore this case entirely.
reenableTimeout = nil
// Once the reenabling is attempted, we also cancel the
// channel event subscription to free up the overflow
// queue used in channel notifier.
//
// NOTE: channelEventClient will be nil if the
// reenableTimeout is greater than 1 minute.
if p.channelEventClient != nil {
p.channelEventClient.Cancel()
}
case <-p.cg.Done():
// As, we've been signalled to exit, we'll reset all
// our active channel back to their default state.
p.activeChannels.ForEach(func(_ lnwire.ChannelID,
lc *lnwallet.LightningChannel) error {
// Exit if the channel is nil as it's a pending
// channel.
if lc == nil {
return nil
}
lc.ResetState()
return nil
})
break out
}
}
}
// reenableActiveChannels searches the index of channels maintained with this
// peer, and reenables each public, non-pending channel. This is done at the
// gossip level by broadcasting a new ChannelUpdate with the disabled bit unset.
// No message will be sent if the channel is already enabled.
func (p *Brontide) reenableActiveChannels() {
// First, filter all known channels with this peer for ones that are
// both public and not pending.
activePublicChans := p.filterChannelsToEnable()
// Create a map to hold channels that needs to be retried.
retryChans := make(map[wire.OutPoint]struct{}, len(activePublicChans))
// For each of the public, non-pending channels, set the channel
// disabled bit to false and send out a new ChannelUpdate. If this
// channel is already active, the update won't be sent.
for _, chanPoint := range activePublicChans {
err := p.cfg.ChanStatusMgr.RequestEnable(chanPoint, false)
switch {
// No error occurred, continue to request the next channel.
case err == nil:
continue
// Cannot auto enable a manually disabled channel so we do
// nothing but proceed to the next channel.
case errors.Is(err, netann.ErrEnableManuallyDisabledChan):
p.log.Debugf("Channel(%v) was manually disabled, "+
"ignoring automatic enable request", chanPoint)
continue
// If the channel is reported as inactive, we will give it
// another chance. When handling the request, ChanStatusManager
// will check whether the link is active or not. One of the
// conditions is whether the link has been marked as
// reestablished, which happens inside a goroutine(htlcManager)
// after the link is started. And we may get a false negative
// saying the link is not active because that goroutine hasn't
// reached the line to mark the reestablishment. Thus we give
// it a second chance to send the request.
case errors.Is(err, netann.ErrEnableInactiveChan):
// If we don't have a client created, it means we
// shouldn't retry enabling the channel.
if p.channelEventClient == nil {
p.log.Errorf("Channel(%v) request enabling "+
"failed due to inactive link",
chanPoint)
continue
}
p.log.Warnf("Channel(%v) cannot be enabled as " +
"ChanStatusManager reported inactive, retrying")
// Add the channel to the retry map.
retryChans[chanPoint] = struct{}{}
}
}
// Retry the channels if we have any.
if len(retryChans) != 0 {
p.retryRequestEnable(retryChans)
}
}
// fetchActiveChanCloser attempts to fetch the active chan closer state machine
// for the target channel ID. If the channel isn't active an error is returned.
// Otherwise, either an existing state machine will be returned, or a new one
// will be created.
func (p *Brontide) fetchActiveChanCloser(chanID lnwire.ChannelID) (
*chanCloserFsm, error) {
chanCloser, found := p.activeChanCloses.Load(chanID)
if found {
// An entry will only be found if the closer has already been
// created for a non-pending channel or for a channel that had
// previously started the shutdown process but the connection
// was restarted.
return &chanCloser, nil
}
// First, we'll ensure that we actually know of the target channel. If
// not, we'll ignore this message.
channel, ok := p.activeChannels.Load(chanID)
// If the channel isn't in the map or the channel is nil, return
// ErrChannelNotFound as the channel is pending.
if !ok || channel == nil {
return nil, ErrChannelNotFound
}
// We'll create a valid closing state machine in order to respond to
// the initiated cooperative channel closure. First, we set the
// delivery script that our funds will be paid out to. If an upfront
// shutdown script was set, we will use it. Otherwise, we get a fresh
// delivery script.
//
// TODO: Expose option to allow upfront shutdown script from watch-only
// accounts.
deliveryScript := channel.LocalUpfrontShutdownScript()
if len(deliveryScript) == 0 {
var err error
deliveryScript, err = p.genDeliveryScript()
if err != nil {
p.log.Errorf("unable to gen delivery script: %v",
err)
return nil, fmt.Errorf("close addr unavailable")
}
}
// In order to begin fee negotiations, we'll first compute our target
// ideal fee-per-kw.
feePerKw, err := p.cfg.FeeEstimator.EstimateFeePerKW(
p.cfg.CoopCloseTargetConfs,
)
if err != nil {
p.log.Errorf("unable to query fee estimator: %v", err)
return nil, fmt.Errorf("unable to estimate fee")
}
addr, err := p.addrWithInternalKey(deliveryScript)
if err != nil {
return nil, fmt.Errorf("unable to parse addr: %w", err)
}
negotiateChanCloser, err := p.createChanCloser(
channel, addr, feePerKw, nil, lntypes.Remote,
)
if err != nil {
p.log.Errorf("unable to create chan closer: %v", err)
return nil, fmt.Errorf("unable to create chan closer")
}
chanCloser = makeNegotiateCloser(negotiateChanCloser)
p.activeChanCloses.Store(chanID, chanCloser)
return &chanCloser, nil
}
// filterChannelsToEnable filters a list of channels to be enabled upon start.
// The filtered channels are active channels that's neither private nor
// pending.
func (p *Brontide) filterChannelsToEnable() []wire.OutPoint {
var activePublicChans []wire.OutPoint
p.activeChannels.Range(func(chanID lnwire.ChannelID,
lnChan *lnwallet.LightningChannel) bool {
// If the lnChan is nil, continue as this is a pending channel.
if lnChan == nil {
return true
}
dbChan := lnChan.State()
isPublic := dbChan.ChannelFlags&lnwire.FFAnnounceChannel != 0
if !isPublic || dbChan.IsPending {
return true
}
// We'll also skip any channels added during this peer's
// lifecycle since they haven't waited out the timeout. Their
// first announcement will be enabled, and the chan status
// manager will begin monitoring them passively since they exist
// in the database.
if _, ok := p.addedChannels.Load(chanID); ok {
return true
}
activePublicChans = append(
activePublicChans, dbChan.FundingOutpoint,
)
return true
})
return activePublicChans
}
// retryRequestEnable takes a map of channel outpoints and a channel event
// client. It listens to the channel events and removes a channel from the map
// if it's matched to the event. Upon receiving an active channel event, it
// will send the enabling request again.
func (p *Brontide) retryRequestEnable(activeChans map[wire.OutPoint]struct{}) {
p.log.Debugf("Retry enabling %v channels", len(activeChans))
// retryEnable is a helper closure that sends an enable request and
// removes the channel from the map if it's matched.
retryEnable := func(chanPoint wire.OutPoint) error {
// If this is an active channel event, check whether it's in
// our targeted channels map.
_, found := activeChans[chanPoint]
// If this channel is irrelevant, return nil so the loop can
// jump to next iteration.
if !found {
return nil
}
// Otherwise we've just received an active signal for a channel
// that's previously failed to be enabled, we send the request
// again.
//
// We only give the channel one more shot, so we delete it from
// our map first to keep it from being attempted again.
delete(activeChans, chanPoint)
// Send the request.
err := p.cfg.ChanStatusMgr.RequestEnable(chanPoint, false)
if err != nil {
return fmt.Errorf("request enabling channel %v "+
"failed: %w", chanPoint, err)
}
return nil
}
for {
// If activeChans is empty, we've done processing all the
// channels.
if len(activeChans) == 0 {
p.log.Debug("Finished retry enabling channels")
return
}
select {
// A new event has been sent by the ChannelNotifier. We now
// check whether it's an active or inactive channel event.
case e := <-p.channelEventClient.Updates():
// If this is an active channel event, try enable the
// channel then jump to the next iteration.
active, ok := e.(channelnotifier.ActiveChannelEvent)
if ok {
chanPoint := *active.ChannelPoint
// If we received an error for this particular
// channel, we log an error and won't quit as
// we still want to retry other channels.
if err := retryEnable(chanPoint); err != nil {
p.log.Errorf("Retry failed: %v", err)
}
continue
}
// Otherwise check for inactive link event, and jump to
// next iteration if it's not.
inactive, ok := e.(channelnotifier.InactiveLinkEvent)
if !ok {
continue
}
// Found an inactive link event, if this is our
// targeted channel, remove it from our map.
chanPoint := *inactive.ChannelPoint
_, found := activeChans[chanPoint]
if !found {
continue
}
delete(activeChans, chanPoint)
p.log.Warnf("Re-enable channel %v failed, received "+
"inactive link event", chanPoint)
case <-p.cg.Done():
p.log.Debugf("Peer shutdown during retry enabling")
return
}
}
}
// chooseDeliveryScript takes two optionally set shutdown scripts and returns
// a suitable script to close out to. This may be nil if neither script is
// set. If both scripts are set, this function will error if they do not match.
func chooseDeliveryScript(upfront, requested lnwire.DeliveryAddress,
genDeliveryScript func() ([]byte, error),
) (lnwire.DeliveryAddress, error) {
switch {
// If no script was provided, then we'll generate a new delivery script.
case len(upfront) == 0 && len(requested) == 0:
return genDeliveryScript()
// If no upfront shutdown script was provided, return the user
// requested address (which may be nil).
case len(upfront) == 0:
return requested, nil
// If an upfront shutdown script was provided, and the user did not
// request a custom shutdown script, return the upfront address.
case len(requested) == 0:
return upfront, nil
// If both an upfront shutdown script and a custom close script were
// provided, error if the user provided shutdown script does not match
// the upfront shutdown script (because closing out to a different
// script would violate upfront shutdown).
case !bytes.Equal(upfront, requested):
return nil, chancloser.ErrUpfrontShutdownScriptMismatch
// The user requested script matches the upfront shutdown script, so we
// can return it without error.
default:
return upfront, nil
}
}
// restartCoopClose checks whether we need to restart the cooperative close
// process for a given channel.
func (p *Brontide) restartCoopClose(lnChan *lnwallet.LightningChannel) (
*lnwire.Shutdown, error) {
isTaprootChan := lnChan.ChanType().IsTaproot()
// If this channel has status ChanStatusCoopBroadcasted and does not
// have a closing transaction, then the cooperative close process was
// started but never finished. We'll re-create the chanCloser state
// machine and resend Shutdown. BOLT#2 requires that we retransmit
// Shutdown exactly, but doing so would mean persisting the RPC
// provided close script. Instead use the LocalUpfrontShutdownScript
// or generate a script.
c := lnChan.State()
_, err := c.BroadcastedCooperative()
if err != nil && err != channeldb.ErrNoCloseTx {
// An error other than ErrNoCloseTx was encountered.
return nil, err
} else if err == nil && !p.rbfCoopCloseAllowed() {
// This is a channel that doesn't support RBF coop close, and it
// already had a coop close txn broadcast. As a result, we can
// just exit here as all we can do is wait for it to confirm.
return nil, nil
}
chanID := lnwire.NewChanIDFromOutPoint(c.FundingOutpoint)
var deliveryScript []byte
shutdownInfo, err := c.ShutdownInfo()
switch {
// We have previously stored the delivery script that we need to use
// in the shutdown message. Re-use this script.
case err == nil:
shutdownInfo.WhenSome(func(info channeldb.ShutdownInfo) {
deliveryScript = info.DeliveryScript.Val
})
// An error other than ErrNoShutdownInfo was returned
case !errors.Is(err, channeldb.ErrNoShutdownInfo):
return nil, err
case errors.Is(err, channeldb.ErrNoShutdownInfo):
deliveryScript = c.LocalShutdownScript
if len(deliveryScript) == 0 {
var err error
deliveryScript, err = p.genDeliveryScript()
if err != nil {
p.log.Errorf("unable to gen delivery script: "+
"%v", err)
return nil, fmt.Errorf("close addr unavailable")
}
}
}
// If the new RBF co-op close is negotiated, then we'll init and start
// that state machine, skipping the steps for the negotiate machine
// below. We don't support this close type for taproot channels though.
if p.rbfCoopCloseAllowed() && !isTaprootChan {
_, err := p.initRbfChanCloser(lnChan)
if err != nil {
return nil, fmt.Errorf("unable to init rbf chan "+
"closer during restart: %w", err)
}
shutdownDesc := fn.MapOption(
newRestartShutdownInit,
)(shutdownInfo)
err = p.startRbfChanCloser(
fn.FlattenOption(shutdownDesc), lnChan.ChannelPoint(),
)
return nil, err
}
// Compute an ideal fee.
feePerKw, err := p.cfg.FeeEstimator.EstimateFeePerKW(
p.cfg.CoopCloseTargetConfs,
)
if err != nil {
p.log.Errorf("unable to query fee estimator: %v", err)
return nil, fmt.Errorf("unable to estimate fee")
}
// Determine whether we or the peer are the initiator of the coop
// close attempt by looking at the channel's status.
closingParty := lntypes.Remote
if c.HasChanStatus(channeldb.ChanStatusLocalCloseInitiator) {
closingParty = lntypes.Local
}
addr, err := p.addrWithInternalKey(deliveryScript)
if err != nil {
return nil, fmt.Errorf("unable to parse addr: %w", err)
}
chanCloser, err := p.createChanCloser(
lnChan, addr, feePerKw, nil, closingParty,
)
if err != nil {
p.log.Errorf("unable to create chan closer: %v", err)
return nil, fmt.Errorf("unable to create chan closer")
}
p.activeChanCloses.Store(chanID, makeNegotiateCloser(chanCloser))
// Create the Shutdown message.
shutdownMsg, err := chanCloser.ShutdownChan()
if err != nil {
p.log.Errorf("unable to create shutdown message: %v", err)
p.activeChanCloses.Delete(chanID)
return nil, err
}
return shutdownMsg, nil
}
// createChanCloser constructs a ChanCloser from the passed parameters and is
// used to de-duplicate code.
func (p *Brontide) createChanCloser(channel *lnwallet.LightningChannel,
deliveryScript *chancloser.DeliveryAddrWithKey,
fee chainfee.SatPerKWeight, req *htlcswitch.ChanClose,
closer lntypes.ChannelParty) (*chancloser.ChanCloser, error) {
_, startingHeight, err := p.cfg.ChainIO.GetBestBlock()
if err != nil {
p.log.Errorf("unable to obtain best block: %v", err)
return nil, fmt.Errorf("cannot obtain best block")
}
// The req will only be set if we initiated the co-op closing flow.
var maxFee chainfee.SatPerKWeight
if req != nil {
maxFee = req.MaxFee
}
chanCloser := chancloser.NewChanCloser(
chancloser.ChanCloseCfg{
Channel: channel,
MusigSession: NewMusigChanCloser(channel),
FeeEstimator: &chancloser.SimpleCoopFeeEstimator{},
BroadcastTx: p.cfg.Wallet.PublishTransaction,
AuxCloser: p.cfg.AuxChanCloser,
DisableChannel: func(op wire.OutPoint) error {
return p.cfg.ChanStatusMgr.RequestDisable(
op, false,
)
},
MaxFee: maxFee,
Disconnect: func() error {
return p.cfg.DisconnectPeer(p.IdentityKey())
},
ChainParams: &p.cfg.Wallet.Cfg.NetParams,
},
*deliveryScript,
fee,
uint32(startingHeight),
req,
closer,
)
return chanCloser, nil
}
// initNegotiateChanCloser initializes the channel closer for a channel that is
// using the original "negotiation" based protocol. This path is used when
// we're the one initiating the channel close.
//
// TODO(roasbeef): can make a MsgEndpoint for existing handling logic to
// further abstract.
func (p *Brontide) initNegotiateChanCloser(req *htlcswitch.ChanClose,
channel *lnwallet.LightningChannel) error {
// First, we'll choose a delivery address that we'll use to send the
// funds to in the case of a successful negotiation.
// An upfront shutdown and user provided script are both optional, but
// must be equal if both set (because we cannot serve a request to
// close out to a script which violates upfront shutdown). Get the
// appropriate address to close out to (which may be nil if neither are
// set) and error if they are both set and do not match.
deliveryScript, err := chooseDeliveryScript(
channel.LocalUpfrontShutdownScript(), req.DeliveryScript,
p.genDeliveryScript,
)
if err != nil {
return fmt.Errorf("cannot close channel %v: %w",
req.ChanPoint, err)
}
addr, err := p.addrWithInternalKey(deliveryScript)
if err != nil {
return fmt.Errorf("unable to parse addr for channel "+
"%v: %w", req.ChanPoint, err)
}
chanCloser, err := p.createChanCloser(
channel, addr, req.TargetFeePerKw, req, lntypes.Local,
)
if err != nil {
return fmt.Errorf("unable to make chan closer: %w", err)
}
chanID := lnwire.NewChanIDFromOutPoint(channel.ChannelPoint())
p.activeChanCloses.Store(chanID, makeNegotiateCloser(chanCloser))
// Finally, we'll initiate the channel shutdown within the
// chanCloser, and send the shutdown message to the remote
// party to kick things off.
shutdownMsg, err := chanCloser.ShutdownChan()
if err != nil {
// As we were unable to shutdown the channel, we'll return it
// back to its normal state.
defer channel.ResetState()
p.activeChanCloses.Delete(chanID)
return fmt.Errorf("unable to shutdown channel: %w", err)
}
link := p.fetchLinkFromKeyAndCid(chanID)
if link == nil {
// If the link is nil then it means it was already removed from
// the switch or it never existed in the first place. The
// latter case is handled at the beginning of this function, so
// in the case where it has already been removed, we can skip
// adding the commit hook to queue a Shutdown message.
p.log.Warnf("link not found during attempted closure: "+
"%v", chanID)
return nil
}
if !link.DisableAdds(htlcswitch.Outgoing) {
p.log.Warnf("Outgoing link adds already "+
"disabled: %v", link.ChanID())
}
link.OnCommitOnce(htlcswitch.Outgoing, func() {
p.queueMsg(shutdownMsg, nil)
})
return nil
}
// chooseAddr returns the provided address if it is non-zero length, otherwise
// None.
func chooseAddr(addr lnwire.DeliveryAddress) fn.Option[lnwire.DeliveryAddress] {
if len(addr) == 0 {
return fn.None[lnwire.DeliveryAddress]()
}
return fn.Some(addr)
}
// observeRbfCloseUpdates observes the channel for any updates that may
// indicate that a new txid has been broadcasted, or the channel fully closed
// on chain.
func (p *Brontide) observeRbfCloseUpdates(chanCloser *chancloser.RbfChanCloser,
closeReq *htlcswitch.ChanClose,
coopCloseStates chancloser.RbfStateSub) {
newStateChan := coopCloseStates.NewItemCreated.ChanOut()
defer chanCloser.RemoveStateSub(coopCloseStates)
var (
lastTxids lntypes.Dual[chainhash.Hash]
lastFeeRates lntypes.Dual[chainfee.SatPerVByte]
)
maybeNotifyTxBroadcast := func(state chancloser.AsymmetricPeerState,
party lntypes.ChannelParty) {
// First, check to see if we have an error to report to the
// caller. If so, then we''ll return that error and exit, as the
// stream will exit as well.
if closeErr, ok := state.(*chancloser.CloseErr); ok {
// We hit an error during the last state transition, so
// we'll extract the error then send it to the
// user.
err := closeErr.Err()
peerLog.Warnf("ChannelPoint(%v): encountered close "+
"err: %v", closeReq.ChanPoint, err)
select {
case closeReq.Err <- err:
case <-closeReq.Ctx.Done():
case <-p.cg.Done():
}
return
}
closePending, ok := state.(*chancloser.ClosePending)
// If this isn't the close pending state, we aren't at the
// terminal state yet.
if !ok {
return
}
// Only notify if the fee rate is greater.
newFeeRate := closePending.FeeRate
lastFeeRate := lastFeeRates.GetForParty(party)
if newFeeRate <= lastFeeRate {
peerLog.Debugf("ChannelPoint(%v): remote party made "+
"update for fee rate %v, but we already have "+
"a higher fee rate of %v", closeReq.ChanPoint,
newFeeRate, lastFeeRate)
return
}
feeRate := closePending.FeeRate
lastFeeRates.SetForParty(party, feeRate)
// At this point, we'll have a txid that we can use to notify
// the client, but only if it's different from the last one we
// sent. If the user attempted to bump, but was rejected due to
// RBF, then we'll send a redundant update.
closingTxid := closePending.CloseTx.TxHash()
lastTxid := lastTxids.GetForParty(party)
if closeReq != nil && closingTxid != lastTxid {
select {
case closeReq.Updates <- &PendingUpdate{
Txid: closingTxid[:],
FeePerVbyte: fn.Some(closePending.FeeRate),
IsLocalCloseTx: fn.Some(
party == lntypes.Local,
),
}:
case <-closeReq.Ctx.Done():
return
case <-p.cg.Done():
return
}
}
lastTxids.SetForParty(party, closingTxid)
}
peerLog.Infof("Observing RBF close updates for channel %v",
closeReq.ChanPoint)
// We'll consume each new incoming state to send out the appropriate
// RPC update.
for {
select {
case newState := <-newStateChan:
switch closeState := newState.(type) {
// Once we've reached the state of pending close, we
// have a txid that we broadcasted.
case *chancloser.ClosingNegotiation:
peerState := closeState.PeerState
// Each side may have gained a new co-op close
// tx, so we'll examine both to see if they've
// changed.
maybeNotifyTxBroadcast(
peerState.GetForParty(lntypes.Local),
lntypes.Local,
)
maybeNotifyTxBroadcast(
peerState.GetForParty(lntypes.Remote),
lntypes.Remote,
)
// Otherwise, if we're transition to CloseFin, then we
// know that we're done.
case *chancloser.CloseFin:
// To clean up, we'll remove the chan closer
// from the active map, and send the final
// update to the client.
closingTxid := closeState.ConfirmedTx.TxHash()
if closeReq != nil {
closeReq.Updates <- &ChannelCloseUpdate{
ClosingTxid: closingTxid[:],
Success: true,
}
}
chanID := lnwire.NewChanIDFromOutPoint(
*closeReq.ChanPoint,
)
p.activeChanCloses.Delete(chanID)
return
}
case <-closeReq.Ctx.Done():
return
case <-p.cg.Done():
return
}
}
}
// chanErrorReporter is a simple implementation of the
// chancloser.ErrorReporter. This is bound to a single channel by the channel
// ID.
type chanErrorReporter struct {
chanID lnwire.ChannelID
peer *Brontide
}
// newChanErrorReporter creates a new instance of the chanErrorReporter.
func newChanErrorReporter(chanID lnwire.ChannelID,
peer *Brontide) *chanErrorReporter {
return &chanErrorReporter{
chanID: chanID,
peer: peer,
}
}
// ReportError is a method that's used to report an error that occurred during
// state machine execution. This is used by the RBF close state machine to
// terminate the state machine and send an error to the remote peer.
//
// This is a part of the chancloser.ErrorReporter interface.
func (c *chanErrorReporter) ReportError(chanErr error) {
c.peer.log.Errorf("coop close error for channel %v: %v",
c.chanID, chanErr)
var errMsg []byte
if errors.Is(chanErr, chancloser.ErrInvalidStateTransition) {
errMsg = []byte("unexpected protocol message")
} else {
errMsg = []byte(chanErr.Error())
}
err := c.peer.SendMessageLazy(false, &lnwire.Error{
ChanID: c.chanID,
Data: errMsg,
})
if err != nil {
c.peer.log.Warnf("unable to send error message to peer: %v",
err)
}
// After we send the error message to the peer, we'll re-initialize the
// coop close state machine as they may send a shutdown message to
// retry the coop close.
lnChan, ok := c.peer.activeChannels.Load(c.chanID)
if !ok {
return
}
if lnChan == nil {
c.peer.log.Debugf("channel %v is pending, not "+
"re-initializing coop close state machine",
c.chanID)
return
}
if _, err := c.peer.initRbfChanCloser(lnChan); err != nil {
c.peer.activeChanCloses.Delete(c.chanID)
c.peer.log.Errorf("unable to init RBF chan closer after "+
"error case: %v", err)
}
}
// chanFlushEventSentinel is used to send the RBF coop close state machine the
// channel flushed event. We'll wait until the state machine enters the
// ChannelFlushing state, then request the link to send the event once flushed.
//
// NOTE: This MUST be run as a goroutine.
func (p *Brontide) chanFlushEventSentinel(chanCloser *chancloser.RbfChanCloser,
link htlcswitch.ChannelUpdateHandler,
channel *lnwallet.LightningChannel) {
defer p.cg.WgDone()
// If there's no link, then the channel has already been flushed, so we
// don't need to continue.
if link == nil {
return
}
coopCloseStates := chanCloser.RegisterStateEvents()
defer chanCloser.RemoveStateSub(coopCloseStates)
newStateChan := coopCloseStates.NewItemCreated.ChanOut()
sendChanFlushed := func() {
chanState := channel.StateSnapshot()
peerLog.Infof("ChannelPoint(%v) has been flushed for co-op "+
"close, sending event to chan closer",
channel.ChannelPoint())
chanBalances := chancloser.ShutdownBalances{
LocalBalance: chanState.LocalBalance,
RemoteBalance: chanState.RemoteBalance,
}
ctx := context.Background()
chanCloser.SendEvent(ctx, &chancloser.ChannelFlushed{
ShutdownBalances: chanBalances,
FreshFlush: true,
})
}
// We'll wait until the channel enters the ChannelFlushing state. We
// exit after a success loop. As after the first RBF iteration, the
// channel will always be flushed.
for newState := range newStateChan {
if _, ok := newState.(*chancloser.ChannelFlushing); ok {
peerLog.Infof("ChannelPoint(%v): rbf coop "+
"close is awaiting a flushed state, "+
"registering with link..., ",
channel.ChannelPoint())
// Request the link to send the event once the channel
// is flushed. We only need this event sent once, so we
// can exit now.
link.OnFlushedOnce(sendChanFlushed)
return
}
}
}
// initRbfChanCloser initializes the channel closer for a channel that
// is using the new RBF based co-op close protocol. This only creates the chan
// closer, but doesn't attempt to trigger any manual state transitions.
func (p *Brontide) initRbfChanCloser(
channel *lnwallet.LightningChannel) (*chancloser.RbfChanCloser, error) {
chanID := lnwire.NewChanIDFromOutPoint(channel.ChannelPoint())
link := p.fetchLinkFromKeyAndCid(chanID)
_, startingHeight, err := p.cfg.ChainIO.GetBestBlock()
if err != nil {
return nil, fmt.Errorf("cannot obtain best block: %w", err)
}
defaultFeePerKw, err := p.cfg.FeeEstimator.EstimateFeePerKW(
p.cfg.CoopCloseTargetConfs,
)
if err != nil {
return nil, fmt.Errorf("unable to estimate fee: %w", err)
}
thawHeight, err := channel.AbsoluteThawHeight()
if err != nil {
return nil, fmt.Errorf("unable to get thaw height: %w", err)
}
peerPub := *p.IdentityKey()
msgMapper := chancloser.NewRbfMsgMapper(
uint32(startingHeight), chanID, peerPub,
)
initialState := chancloser.ChannelActive{}
scid := channel.ZeroConfRealScid().UnwrapOr(
channel.ShortChanID(),
)
env := chancloser.Environment{
ChainParams: p.cfg.Wallet.Cfg.NetParams,
ChanPeer: peerPub,
ChanPoint: channel.ChannelPoint(),
ChanID: chanID,
Scid: scid,
ChanType: channel.ChanType(),
DefaultFeeRate: defaultFeePerKw.FeePerVByte(),
ThawHeight: fn.Some(thawHeight),
RemoteUpfrontShutdown: chooseAddr(
channel.RemoteUpfrontShutdownScript(),
),
LocalUpfrontShutdown: chooseAddr(
channel.LocalUpfrontShutdownScript(),
),
NewDeliveryScript: func() (lnwire.DeliveryAddress, error) {
return p.genDeliveryScript()
},
FeeEstimator: &chancloser.SimpleCoopFeeEstimator{},
CloseSigner: channel,
ChanObserver: newChanObserver(
channel, link, p.cfg.ChanStatusMgr,
),
}
spendEvent := protofsm.RegisterSpend[chancloser.ProtocolEvent]{
OutPoint: channel.ChannelPoint(),
PkScript: channel.FundingTxOut().PkScript,
HeightHint: channel.DeriveHeightHint(),
PostSpendEvent: fn.Some[chancloser.RbfSpendMapper](
chancloser.SpendMapper,
),
}
daemonAdapters := NewLndDaemonAdapters(LndAdapterCfg{
MsgSender: newPeerMsgSender(peerPub, p),
TxBroadcaster: p.cfg.Wallet,
ChainNotifier: p.cfg.ChainNotifier,
})
protoCfg := chancloser.RbfChanCloserCfg{
Daemon: daemonAdapters,
InitialState: &initialState,
Env: &env,
InitEvent: fn.Some[protofsm.DaemonEvent](&spendEvent),
ErrorReporter: newChanErrorReporter(chanID, p),
MsgMapper: fn.Some[protofsm.MsgMapper[chancloser.ProtocolEvent]]( //nolint:ll
msgMapper,
),
}
ctx := context.Background()
chanCloser := protofsm.NewStateMachine(protoCfg)
chanCloser.Start(ctx)
// Finally, we'll register this new endpoint with the message router so
// future co-op close messages are handled by this state machine.
err = fn.MapOptionZ(p.msgRouter, func(r msgmux.Router) error {
_ = r.UnregisterEndpoint(chanCloser.Name())
return r.RegisterEndpoint(&chanCloser)
})
if err != nil {
chanCloser.Stop()
return nil, fmt.Errorf("unable to register endpoint for co-op "+
"close: %w", err)
}
p.activeChanCloses.Store(chanID, makeRbfCloser(&chanCloser))
// Now that we've created the rbf closer state machine, we'll launch a
// new goroutine to eventually send in the ChannelFlushed event once
// needed.
p.cg.WgAdd(1)
go p.chanFlushEventSentinel(&chanCloser, link, channel)
return &chanCloser, nil
}
// shutdownInit describes the two ways we can initiate a new shutdown. Either we
// got an RPC request to do so (left), or we sent a shutdown message to the
// party (for w/e reason), but crashed before the close was complete.
//
//nolint:ll
type shutdownInit = fn.Option[fn.Either[*htlcswitch.ChanClose, channeldb.ShutdownInfo]]
// shutdownStartFeeRate returns the fee rate that should be used for the
// shutdown. This returns a doubly wrapped option as the shutdown info might
// be none, and the fee rate is only defined for the user initiated shutdown.
func shutdownStartFeeRate(s shutdownInit) fn.Option[chainfee.SatPerKWeight] {
feeRateOpt := fn.MapOption(func(init fn.Either[*htlcswitch.ChanClose,
channeldb.ShutdownInfo]) fn.Option[chainfee.SatPerKWeight] {
var feeRate fn.Option[chainfee.SatPerKWeight]
init.WhenLeft(func(req *htlcswitch.ChanClose) {
feeRate = fn.Some(req.TargetFeePerKw)
})
return feeRate
})(s)
return fn.FlattenOption(feeRateOpt)
}
// shutdownStartAddr returns the delivery address that should be used when
// restarting the shutdown process. If we didn't send a shutdown before we
// restarted, and the user didn't initiate one either, then None is returned.
func shutdownStartAddr(s shutdownInit) fn.Option[lnwire.DeliveryAddress] {
addrOpt := fn.MapOption(func(init fn.Either[*htlcswitch.ChanClose,
channeldb.ShutdownInfo]) fn.Option[lnwire.DeliveryAddress] {
var addr fn.Option[lnwire.DeliveryAddress]
init.WhenLeft(func(req *htlcswitch.ChanClose) {
if len(req.DeliveryScript) != 0 {
addr = fn.Some(req.DeliveryScript)
}
})
init.WhenRight(func(info channeldb.ShutdownInfo) {
addr = fn.Some(info.DeliveryScript.Val)
})
return addr
})(s)
return fn.FlattenOption(addrOpt)
}
// whenRPCShutdown registers a callback to be executed when the shutdown init
// type is and RPC request.
func whenRPCShutdown(s shutdownInit, f func(r *htlcswitch.ChanClose)) {
s.WhenSome(func(init fn.Either[*htlcswitch.ChanClose,
channeldb.ShutdownInfo]) {
init.WhenLeft(f)
})
}
// newRestartShutdownInit creates a new shutdownInit for the case where we need
// to restart the shutdown flow after a restart.
func newRestartShutdownInit(info channeldb.ShutdownInfo) shutdownInit {
return fn.Some(fn.NewRight[*htlcswitch.ChanClose](info))
}
// newRPCShutdownInit creates a new shutdownInit for the case where we
// initiated the shutdown via an RPC client.
func newRPCShutdownInit(req *htlcswitch.ChanClose) shutdownInit {
return fn.Some(
fn.NewLeft[*htlcswitch.ChanClose, channeldb.ShutdownInfo](req),
)
}
// waitUntilRbfCoastClear waits until the RBF co-op close state machine has
// advanced to a terminal state before attempting another fee bump.
func waitUntilRbfCoastClear(ctx context.Context,
rbfCloser *chancloser.RbfChanCloser) error {
coopCloseStates := rbfCloser.RegisterStateEvents()
newStateChan := coopCloseStates.NewItemCreated.ChanOut()
defer rbfCloser.RemoveStateSub(coopCloseStates)
isTerminalState := func(newState chancloser.RbfState) bool {
// If we're not in the negotiation sub-state, then we aren't at
// the terminal state yet.
state, ok := newState.(*chancloser.ClosingNegotiation)
if !ok {
return false
}
localState := state.PeerState.GetForParty(lntypes.Local)
// If this isn't the close pending state, we aren't at the
// terminal state yet.
_, ok = localState.(*chancloser.ClosePending)
return ok
}
// Before we enter the subscription loop below, check to see if we're
// already in the terminal state.
rbfState, err := rbfCloser.CurrentState()
if err != nil {
return err
}
if isTerminalState(rbfState) {
return nil
}
peerLog.Debugf("Waiting for RBF iteration to complete...")
for {
select {
case newState := <-newStateChan:
if isTerminalState(newState) {
return nil
}
case <-ctx.Done():
return fmt.Errorf("context canceled")
}
}
}
// startRbfChanCloser kicks off the co-op close process using the new RBF based
// co-op close protocol. This is called when we're the one that's initiating
// the cooperative channel close.
//
// TODO(roasbeef): just accept the two shutdown pointer params instead??
func (p *Brontide) startRbfChanCloser(shutdown shutdownInit,
chanPoint wire.OutPoint) error {
// Unlike the old negotiate chan closer, we'll always create the RBF
// chan closer on startup, so we can skip init here.
chanID := lnwire.NewChanIDFromOutPoint(chanPoint)
chanCloser, found := p.activeChanCloses.Load(chanID)
if !found {
return fmt.Errorf("rbf chan closer not found for channel %v",
chanPoint)
}
defaultFeePerKw, err := shutdownStartFeeRate(
shutdown,
).UnwrapOrFuncErr(func() (chainfee.SatPerKWeight, error) {
return p.cfg.FeeEstimator.EstimateFeePerKW(
p.cfg.CoopCloseTargetConfs,
)
})
if err != nil {
return fmt.Errorf("unable to estimate fee: %w", err)
}
chanCloser.WhenRight(func(rbfCloser *chancloser.RbfChanCloser) {
peerLog.Infof("ChannelPoint(%v): rbf-coop close requested, "+
"sending shutdown", chanPoint)
rbfState, err := rbfCloser.CurrentState()
if err != nil {
peerLog.Warnf("ChannelPoint(%v): unable to get "+
"current state for rbf-coop close: %v",
chanPoint, err)
return
}
coopCloseStates := rbfCloser.RegisterStateEvents()
// Before we send our event below, we'll launch a goroutine to
// watch for the final terminal state to send updates to the RPC
// client. We only need to do this if there's an RPC caller.
var rpcShutdown bool
whenRPCShutdown(shutdown, func(req *htlcswitch.ChanClose) {
rpcShutdown = true
p.cg.WgAdd(1)
go func() {
defer p.cg.WgDone()
p.observeRbfCloseUpdates(
rbfCloser, req, coopCloseStates,
)
}()
})
if !rpcShutdown {
defer rbfCloser.RemoveStateSub(coopCloseStates)
}
ctx, _ := p.cg.Create(context.Background())
feeRate := defaultFeePerKw.FeePerVByte()
// Depending on the state of the state machine, we'll either
// kick things off by sending shutdown, or attempt to send a new
// offer to the remote party.
switch rbfState.(type) {
// The channel is still active, so we'll now kick off the co-op
// close process by instructing it to send a shutdown message to
// the remote party.
case *chancloser.ChannelActive:
rbfCloser.SendEvent(
context.Background(),
&chancloser.SendShutdown{
IdealFeeRate: feeRate,
DeliveryAddr: shutdownStartAddr(
shutdown,
),
},
)
// If we haven't yet sent an offer (didn't have enough funds at
// the prior fee rate), or we've sent an offer, then we'll
// trigger a new offer event.
case *chancloser.ClosingNegotiation:
// Before we send the event below, we'll wait until
// we're in a semi-terminal state.
err := waitUntilRbfCoastClear(ctx, rbfCloser)
if err != nil {
peerLog.Warnf("ChannelPoint(%v): unable to "+
"wait for coast to clear: %v",
chanPoint, err)
return
}
event := chancloser.ProtocolEvent(
&chancloser.SendOfferEvent{
TargetFeeRate: feeRate,
},
)
rbfCloser.SendEvent(ctx, event)
default:
peerLog.Warnf("ChannelPoint(%v): unexpected state "+
"for rbf-coop close: %T", chanPoint, rbfState)
}
})
return nil
}
// handleLocalCloseReq kicks-off the workflow to execute a cooperative or
// forced unilateral closure of the channel initiated by a local subsystem.
func (p *Brontide) handleLocalCloseReq(req *htlcswitch.ChanClose) {
chanID := lnwire.NewChanIDFromOutPoint(*req.ChanPoint)
channel, ok := p.activeChannels.Load(chanID)
// Though this function can't be called for pending channels, we still
// check whether channel is nil for safety.
if !ok || channel == nil {
err := fmt.Errorf("unable to close channel, ChannelID(%v) is "+
"unknown", chanID)
p.log.Errorf(err.Error())
req.Err <- err
return
}
isTaprootChan := channel.ChanType().IsTaproot()
switch req.CloseType {
// A type of CloseRegular indicates that the user has opted to close
// out this channel on-chain, so we execute the cooperative channel
// closure workflow.
case contractcourt.CloseRegular:
var err error
switch {
// If this is the RBF coop state machine, then we'll instruct
// it to send the shutdown message. This also might be an RBF
// iteration, in which case we'll be obtaining a new
// transaction w/ a higher fee rate.
//
// We don't support this close type for taproot channels yet
// however.
case !isTaprootChan && p.rbfCoopCloseAllowed():
err = p.startRbfChanCloser(
newRPCShutdownInit(req), channel.ChannelPoint(),
)
default:
err = p.initNegotiateChanCloser(req, channel)
}
if err != nil {
p.log.Errorf(err.Error())
req.Err <- err
}
// A type of CloseBreach indicates that the counterparty has breached
// the channel therefore we need to clean up our local state.
case contractcourt.CloseBreach:
// TODO(roasbeef): no longer need with newer beach logic?
p.log.Infof("ChannelPoint(%v) has been breached, wiping "+
"channel", req.ChanPoint)
p.WipeChannel(req.ChanPoint)
}
}
// linkFailureReport is sent to the channelManager whenever a link reports a
// link failure, and is forced to exit. The report houses the necessary
// information to clean up the channel state, send back the error message, and
// force close if necessary.
type linkFailureReport struct {
chanPoint wire.OutPoint
chanID lnwire.ChannelID
shortChanID lnwire.ShortChannelID
linkErr htlcswitch.LinkFailureError
}
// handleLinkFailure processes a link failure report when a link in the switch
// fails. It facilitates the removal of all channel state within the peer,
// force closing the channel depending on severity, and sending the error
// message back to the remote party.
func (p *Brontide) handleLinkFailure(failure linkFailureReport) {
// Retrieve the channel from the map of active channels. We do this to
// have access to it even after WipeChannel remove it from the map.
chanID := lnwire.NewChanIDFromOutPoint(failure.chanPoint)
lnChan, _ := p.activeChannels.Load(chanID)
// We begin by wiping the link, which will remove it from the switch,
// such that it won't be attempted used for any more updates.
//
// TODO(halseth): should introduce a way to atomically stop/pause the
// link and cancel back any adds in its mailboxes such that we can
// safely force close without the link being added again and updates
// being applied.
p.WipeChannel(&failure.chanPoint)
// If the error encountered was severe enough, we'll now force close
// the channel to prevent reading it to the switch in the future.
if failure.linkErr.FailureAction == htlcswitch.LinkFailureForceClose {
p.log.Warnf("Force closing link(%v)", failure.shortChanID)
closeTx, err := p.cfg.ChainArb.ForceCloseContract(
failure.chanPoint,
)
if err != nil {
p.log.Errorf("unable to force close "+
"link(%v): %v", failure.shortChanID, err)
} else {
p.log.Infof("channel(%v) force "+
"closed with txid %v",
failure.shortChanID, closeTx.TxHash())
}
}
// If this is a permanent failure, we will mark the channel borked.
if failure.linkErr.PermanentFailure && lnChan != nil {
p.log.Warnf("Marking link(%v) borked due to permanent "+
"failure", failure.shortChanID)
if err := lnChan.State().MarkBorked(); err != nil {
p.log.Errorf("Unable to mark channel %v borked: %v",
failure.shortChanID, err)
}
}
// Send an error to the peer, why we failed the channel.
if failure.linkErr.ShouldSendToPeer() {
// If SendData is set, send it to the peer. If not, we'll use
// the standard error messages in the payload. We only include
// sendData in the cases where the error data does not contain
// sensitive information.
data := []byte(failure.linkErr.Error())
if failure.linkErr.SendData != nil {
data = failure.linkErr.SendData
}
var networkMsg lnwire.Message
if failure.linkErr.Warning {
networkMsg = &lnwire.Warning{
ChanID: failure.chanID,
Data: data,
}
} else {
networkMsg = &lnwire.Error{
ChanID: failure.chanID,
Data: data,
}
}
err := p.SendMessage(true, networkMsg)
if err != nil {
p.log.Errorf("unable to send msg to "+
"remote peer: %v", err)
}
}
// If the failure action is disconnect, then we'll execute that now. If
// we had to send an error above, it was a sync call, so we expect the
// message to be flushed on the wire by now.
if failure.linkErr.FailureAction == htlcswitch.LinkFailureDisconnect {
p.Disconnect(fmt.Errorf("link requested disconnect"))
}
}
// fetchLinkFromKeyAndCid fetches a link from the switch via the remote's
// public key and the channel id.
func (p *Brontide) fetchLinkFromKeyAndCid(
cid lnwire.ChannelID) htlcswitch.ChannelUpdateHandler {
var chanLink htlcswitch.ChannelUpdateHandler
// We don't need to check the error here, and can instead just loop
// over the slice and return nil.
links, _ := p.cfg.Switch.GetLinksByInterface(p.cfg.PubKeyBytes)
for _, link := range links {
if link.ChanID() == cid {
chanLink = link
break
}
}
return chanLink
}
// finalizeChanClosure performs the final clean up steps once the cooperative
// closure transaction has been fully broadcast. The finalized closing state
// machine should be passed in. Once the transaction has been sufficiently
// confirmed, the channel will be marked as fully closed within the database,
// and any clients will be notified of updates to the closing state.
func (p *Brontide) finalizeChanClosure(chanCloser *chancloser.ChanCloser) {
closeReq := chanCloser.CloseRequest()
// First, we'll clear all indexes related to the channel in question.
chanPoint := chanCloser.Channel().ChannelPoint()
p.WipeChannel(&chanPoint)
// Also clear the activeChanCloses map of this channel.
cid := lnwire.NewChanIDFromOutPoint(chanPoint)
p.activeChanCloses.Delete(cid) // TODO(roasbeef): existing race
// Next, we'll launch a goroutine which will request to be notified by
// the ChainNotifier once the closure transaction obtains a single
// confirmation.
notifier := p.cfg.ChainNotifier
// If any error happens during waitForChanToClose, forward it to
// closeReq. If this channel closure is not locally initiated, closeReq
// will be nil, so just ignore the error.
errChan := make(chan error, 1)
if closeReq != nil {
errChan = closeReq.Err
}
closingTx, err := chanCloser.ClosingTx()
if err != nil {
if closeReq != nil {
p.log.Error(err)
closeReq.Err <- err
}
}
closingTxid := closingTx.TxHash()
// If this is a locally requested shutdown, update the caller with a
// new event detailing the current pending state of this request.
if closeReq != nil {
closeReq.Updates <- &PendingUpdate{
Txid: closingTxid[:],
}
}
localOut := chanCloser.LocalCloseOutput()
remoteOut := chanCloser.RemoteCloseOutput()
auxOut := chanCloser.AuxOutputs()
go WaitForChanToClose(
chanCloser.NegotiationHeight(), notifier, errChan,
&chanPoint, &closingTxid, closingTx.TxOut[0].PkScript, func() {
// Respond to the local subsystem which requested the
// channel closure.
if closeReq != nil {
closeReq.Updates <- &ChannelCloseUpdate{
ClosingTxid: closingTxid[:],
Success: true,
LocalCloseOutput: localOut,
RemoteCloseOutput: remoteOut,
AuxOutputs: auxOut,
}
}
},
)
}
// WaitForChanToClose uses the passed notifier to wait until the channel has
// been detected as closed on chain and then concludes by executing the
// following actions: the channel point will be sent over the settleChan, and
// finally the callback will be executed. If any error is encountered within
// the function, then it will be sent over the errChan.
func WaitForChanToClose(bestHeight uint32, notifier chainntnfs.ChainNotifier,
errChan chan error, chanPoint *wire.OutPoint,
closingTxID *chainhash.Hash, closeScript []byte, cb func()) {
peerLog.Infof("Waiting for confirmation of close of ChannelPoint(%v) "+
"with txid: %v", chanPoint, closingTxID)
// TODO(roasbeef): add param for num needed confs
confNtfn, err := notifier.RegisterConfirmationsNtfn(
closingTxID, closeScript, 1, bestHeight,
)
if err != nil {
if errChan != nil {
errChan <- err
}
return
}
// In the case that the ChainNotifier is shutting down, all subscriber
// notification channels will be closed, generating a nil receive.
height, ok := <-confNtfn.Confirmed
if !ok {
return
}
// The channel has been closed, remove it from any active indexes, and
// the database state.
peerLog.Infof("ChannelPoint(%v) is now closed at "+
"height %v", chanPoint, height.BlockHeight)
// Finally, execute the closure call back to mark the confirmation of
// the transaction closing the contract.
cb()
}
// WipeChannel removes the passed channel point from all indexes associated with
// the peer and the switch.
func (p *Brontide) WipeChannel(chanPoint *wire.OutPoint) {
chanID := lnwire.NewChanIDFromOutPoint(*chanPoint)
p.activeChannels.Delete(chanID)
// Instruct the HtlcSwitch to close this link as the channel is no
// longer active.
p.cfg.Switch.RemoveLink(chanID)
}
// handleInitMsg handles the incoming init message which contains global and
// local feature vectors. If feature vectors are incompatible then disconnect.
func (p *Brontide) handleInitMsg(msg *lnwire.Init) error {
// First, merge any features from the legacy global features field into
// those presented in the local features fields.
err := msg.Features.Merge(msg.GlobalFeatures)
if err != nil {
return fmt.Errorf("unable to merge legacy global features: %w",
err)
}
// Then, finalize the remote feature vector providing the flattened
// feature bit namespace.
p.remoteFeatures = lnwire.NewFeatureVector(
msg.Features, lnwire.Features,
)
// Now that we have their features loaded, we'll ensure that they
// didn't set any required bits that we don't know of.
err = feature.ValidateRequired(p.remoteFeatures)
if err != nil {
return fmt.Errorf("invalid remote features: %w", err)
}
// Ensure the remote party's feature vector contains all transitive
// dependencies. We know ours are correct since they are validated
// during the feature manager's instantiation.
err = feature.ValidateDeps(p.remoteFeatures)
if err != nil {
return fmt.Errorf("invalid remote features: %w", err)
}
// Now that we know we understand their requirements, we'll check to
// see if they don't support anything that we deem to be mandatory.
if !p.remoteFeatures.HasFeature(lnwire.DataLossProtectRequired) {
return fmt.Errorf("data loss protection required")
}
return nil
}
// LocalFeatures returns the set of global features that has been advertised by
// the local node. This allows sub-systems that use this interface to gate their
// behavior off the set of negotiated feature bits.
//
// NOTE: Part of the lnpeer.Peer interface.
func (p *Brontide) LocalFeatures() *lnwire.FeatureVector {
return p.cfg.Features
}
// RemoteFeatures returns the set of global features that has been advertised by
// the remote node. This allows sub-systems that use this interface to gate
// their behavior off the set of negotiated feature bits.
//
// NOTE: Part of the lnpeer.Peer interface.
func (p *Brontide) RemoteFeatures() *lnwire.FeatureVector {
return p.remoteFeatures
}
// hasNegotiatedScidAlias returns true if we've negotiated the
// option-scid-alias feature bit with the peer.
func (p *Brontide) hasNegotiatedScidAlias() bool {
peerHas := p.remoteFeatures.HasFeature(lnwire.ScidAliasOptional)
localHas := p.cfg.Features.HasFeature(lnwire.ScidAliasOptional)
return peerHas && localHas
}
// sendInitMsg sends the Init message to the remote peer. This message contains
// our currently supported local and global features.
func (p *Brontide) sendInitMsg(legacyChan bool) error {
features := p.cfg.Features.Clone()
legacyFeatures := p.cfg.LegacyFeatures.Clone()
// If we have a legacy channel open with a peer, we downgrade static
// remote required to optional in case the peer does not understand the
// required feature bit. If we do not do this, the peer will reject our
// connection because it does not understand a required feature bit, and
// our channel will be unusable.
if legacyChan && features.RequiresFeature(lnwire.StaticRemoteKeyRequired) {
p.log.Infof("Legacy channel open with peer, " +
"downgrading static remote required feature bit to " +
"optional")
// Unset and set in both the local and global features to
// ensure both sets are consistent and merge able by old and
// new nodes.
features.Unset(lnwire.StaticRemoteKeyRequired)
legacyFeatures.Unset(lnwire.StaticRemoteKeyRequired)
features.Set(lnwire.StaticRemoteKeyOptional)
legacyFeatures.Set(lnwire.StaticRemoteKeyOptional)
}
msg := lnwire.NewInitMessage(
legacyFeatures.RawFeatureVector,
features.RawFeatureVector,
)
return p.writeMessage(msg)
}
// resendChanSyncMsg will attempt to find a channel sync message for the closed
// channel and resend it to our peer.
func (p *Brontide) resendChanSyncMsg(cid lnwire.ChannelID) error {
// If we already re-sent the mssage for this channel, we won't do it
// again.
if _, ok := p.resentChanSyncMsg[cid]; ok {
return nil
}
// Check if we have any channel sync messages stored for this channel.
c, err := p.cfg.ChannelDB.FetchClosedChannelForID(cid)
if err != nil {
return fmt.Errorf("unable to fetch channel sync messages for "+
"peer %v: %v", p, err)
}
if c.LastChanSyncMsg == nil {
return fmt.Errorf("no chan sync message stored for channel %v",
cid)
}
if !c.RemotePub.IsEqual(p.IdentityKey()) {
return fmt.Errorf("ignoring channel reestablish from "+
"peer=%x", p.IdentityKey().SerializeCompressed())
}
p.log.Debugf("Re-sending channel sync message for channel %v to "+
"peer", cid)
if err := p.SendMessage(true, c.LastChanSyncMsg); err != nil {
return fmt.Errorf("failed resending channel sync "+
"message to peer %v: %v", p, err)
}
p.log.Debugf("Re-sent channel sync message for channel %v to peer ",
cid)
// Note down that we sent the message, so we won't resend it again for
// this connection.
p.resentChanSyncMsg[cid] = struct{}{}
return nil
}
// SendMessage sends a variadic number of high-priority messages to the remote
// peer. The first argument denotes if the method should block until the
// messages have been sent to the remote peer or an error is returned,
// otherwise it returns immediately after queuing.
//
// NOTE: Part of the lnpeer.Peer interface.
func (p *Brontide) SendMessage(sync bool, msgs ...lnwire.Message) error {
return p.sendMessage(sync, true, msgs...)
}
// SendMessageLazy sends a variadic number of low-priority messages to the
// remote peer. The first argument denotes if the method should block until
// the messages have been sent to the remote peer or an error is returned,
// otherwise it returns immediately after queueing.
//
// NOTE: Part of the lnpeer.Peer interface.
func (p *Brontide) SendMessageLazy(sync bool, msgs ...lnwire.Message) error {
return p.sendMessage(sync, false, msgs...)
}
// sendMessage queues a variadic number of messages using the passed priority
// to the remote peer. If sync is true, this method will block until the
// messages have been sent to the remote peer or an error is returned, otherwise
// it returns immediately after queueing.
func (p *Brontide) sendMessage(sync, priority bool, msgs ...lnwire.Message) error {
// Add all incoming messages to the outgoing queue. A list of error
// chans is populated for each message if the caller requested a sync
// send.
var errChans []chan error
if sync {
errChans = make([]chan error, 0, len(msgs))
}
for _, msg := range msgs {
// If a sync send was requested, create an error chan to listen
// for an ack from the writeHandler.
var errChan chan error
if sync {
errChan = make(chan error, 1)
errChans = append(errChans, errChan)
}
if priority {
p.queueMsg(msg, errChan)
} else {
p.queueMsgLazy(msg, errChan)
}
}
// Wait for all replies from the writeHandler. For async sends, this
// will be a NOP as the list of error chans is nil.
for _, errChan := range errChans {
select {
case err := <-errChan:
return err
case <-p.cg.Done():
return lnpeer.ErrPeerExiting
case <-p.cfg.Quit:
return lnpeer.ErrPeerExiting
}
}
return nil
}
// PubKey returns the pubkey of the peer in compressed serialized format.
//
// NOTE: Part of the lnpeer.Peer interface.
func (p *Brontide) PubKey() [33]byte {
return p.cfg.PubKeyBytes
}
// IdentityKey returns the public key of the remote peer.
//
// NOTE: Part of the lnpeer.Peer interface.
func (p *Brontide) IdentityKey() *btcec.PublicKey {
return p.cfg.Addr.IdentityKey
}
// Address returns the network address of the remote peer.
//
// NOTE: Part of the lnpeer.Peer interface.
func (p *Brontide) Address() net.Addr {
return p.cfg.Addr.Address
}
// AddNewChannel adds a new channel to the peer. The channel should fail to be
// added if the cancel channel is closed.
//
// NOTE: Part of the lnpeer.Peer interface.
func (p *Brontide) AddNewChannel(newChan *lnpeer.NewChannel,
cancel <-chan struct{}) error {
errChan := make(chan error, 1)
newChanMsg := &newChannelMsg{
channel: newChan,
err: errChan,
}
select {
case p.newActiveChannel <- newChanMsg:
case <-cancel:
return errors.New("canceled adding new channel")
case <-p.cg.Done():
return lnpeer.ErrPeerExiting
}
// We pause here to wait for the peer to recognize the new channel
// before we close the channel barrier corresponding to the channel.
select {
case err := <-errChan:
return err
case <-p.cg.Done():
return lnpeer.ErrPeerExiting
}
}
// AddPendingChannel adds a pending open channel to the peer. The channel
// should fail to be added if the cancel channel is closed.
//
// NOTE: Part of the lnpeer.Peer interface.
func (p *Brontide) AddPendingChannel(cid lnwire.ChannelID,
cancel <-chan struct{}) error {
errChan := make(chan error, 1)
newChanMsg := &newChannelMsg{
channelID: cid,
err: errChan,
}
select {
case p.newPendingChannel <- newChanMsg:
case <-cancel:
return errors.New("canceled adding pending channel")
case <-p.cg.Done():
return lnpeer.ErrPeerExiting
}
// We pause here to wait for the peer to recognize the new pending
// channel before we close the channel barrier corresponding to the
// channel.
select {
case err := <-errChan:
return err
case <-cancel:
return errors.New("canceled adding pending channel")
case <-p.cg.Done():
return lnpeer.ErrPeerExiting
}
}
// RemovePendingChannel removes a pending open channel from the peer.
//
// NOTE: Part of the lnpeer.Peer interface.
func (p *Brontide) RemovePendingChannel(cid lnwire.ChannelID) error {
errChan := make(chan error, 1)
newChanMsg := &newChannelMsg{
channelID: cid,
err: errChan,
}
select {
case p.removePendingChannel <- newChanMsg:
case <-p.cg.Done():
return lnpeer.ErrPeerExiting
}
// We pause here to wait for the peer to respond to the cancellation of
// the pending channel before we close the channel barrier
// corresponding to the channel.
select {
case err := <-errChan:
return err
case <-p.cg.Done():
return lnpeer.ErrPeerExiting
}
}
// StartTime returns the time at which the connection was established if the
// peer started successfully, and zero otherwise.
func (p *Brontide) StartTime() time.Time {
return p.startTime
}
// handleCloseMsg is called when a new cooperative channel closure related
// message is received from the remote peer. We'll use this message to advance
// the chan closer state machine.
func (p *Brontide) handleCloseMsg(msg *closeMsg) {
link := p.fetchLinkFromKeyAndCid(msg.cid)
// We'll now fetch the matching closing state machine in order to
// continue, or finalize the channel closure process.
chanCloserE, err := p.fetchActiveChanCloser(msg.cid)
if err != nil {
// If the channel is not known to us, we'll simply ignore this
// message.
if err == ErrChannelNotFound {
return
}
p.log.Errorf("Unable to respond to remote close msg: %v", err)
errMsg := &lnwire.Error{
ChanID: msg.cid,
Data: lnwire.ErrorData(err.Error()),
}
p.queueMsg(errMsg, nil)
return
}
if chanCloserE.IsRight() {
// TODO(roasbeef): assert?
return
}
// At this point, we'll only enter this call path if a negotiate chan
// closer was used. So we'll extract that from the either now.
//
// TODO(roabeef): need extra helper func for either to make cleaner
var chanCloser *chancloser.ChanCloser
chanCloserE.WhenLeft(func(c *chancloser.ChanCloser) {
chanCloser = c
})
handleErr := func(err error) {
err = fmt.Errorf("unable to process close msg: %w", err)
p.log.Error(err)
// As the negotiations failed, we'll reset the channel state
// machine to ensure we act to on-chain events as normal.
chanCloser.Channel().ResetState()
if chanCloser.CloseRequest() != nil {
chanCloser.CloseRequest().Err <- err
}
p.activeChanCloses.Delete(msg.cid)
p.Disconnect(err)
}
// Next, we'll process the next message using the target state machine.
// We'll either continue negotiation, or halt.
switch typed := msg.msg.(type) {
case *lnwire.Shutdown:
// Disable incoming adds immediately.
if link != nil && !link.DisableAdds(htlcswitch.Incoming) {
p.log.Warnf("Incoming link adds already disabled: %v",
link.ChanID())
}
oShutdown, err := chanCloser.ReceiveShutdown(*typed)
if err != nil {
handleErr(err)
return
}
oShutdown.WhenSome(func(msg lnwire.Shutdown) {
// If the link is nil it means we can immediately queue
// the Shutdown message since we don't have to wait for
// commitment transaction synchronization.
if link == nil {
p.queueMsg(&msg, nil)
return
}
// Immediately disallow any new HTLC's from being added
// in the outgoing direction.
if !link.DisableAdds(htlcswitch.Outgoing) {
p.log.Warnf("Outgoing link adds already "+
"disabled: %v", link.ChanID())
}
// When we have a Shutdown to send, we defer it till the
// next time we send a CommitSig to remain spec
// compliant.
link.OnCommitOnce(htlcswitch.Outgoing, func() {
p.queueMsg(&msg, nil)
})
})
beginNegotiation := func() {
oClosingSigned, err := chanCloser.BeginNegotiation()
if err != nil {
handleErr(err)
return
}
oClosingSigned.WhenSome(func(msg lnwire.ClosingSigned) {
p.queueMsg(&msg, nil)
})
}
if link == nil {
beginNegotiation()
} else {
// Now we register a flush hook to advance the
// ChanCloser and possibly send out a ClosingSigned
// when the link finishes draining.
link.OnFlushedOnce(func() {
// Remove link in goroutine to prevent deadlock.
go p.cfg.Switch.RemoveLink(msg.cid)
beginNegotiation()
})
}
case *lnwire.ClosingSigned:
oClosingSigned, err := chanCloser.ReceiveClosingSigned(*typed)
if err != nil {
handleErr(err)
return
}
oClosingSigned.WhenSome(func(msg lnwire.ClosingSigned) {
p.queueMsg(&msg, nil)
})
default:
panic("impossible closeMsg type")
}
// If we haven't finished close negotiations, then we'll continue as we
// can't yet finalize the closure.
if _, err := chanCloser.ClosingTx(); err != nil {
return
}
// Otherwise, we've agreed on a closing fee! In this case, we'll wrap up
// the channel closure by notifying relevant sub-systems and launching a
// goroutine to wait for close tx conf.
p.finalizeChanClosure(chanCloser)
}
// HandleLocalCloseChanReqs accepts a *htlcswitch.ChanClose and passes it onto
// the channelManager goroutine, which will shut down the link and possibly
// close the channel.
func (p *Brontide) HandleLocalCloseChanReqs(req *htlcswitch.ChanClose) {
select {
case p.localCloseChanReqs <- req:
p.log.Info("Local close channel request is going to be " +
"delivered to the peer")
case <-p.cg.Done():
p.log.Info("Unable to deliver local close channel request " +
"to peer")
}
}
// NetAddress returns the network of the remote peer as an lnwire.NetAddress.
func (p *Brontide) NetAddress() *lnwire.NetAddress {
return p.cfg.Addr
}
// Inbound is a getter for the Brontide's Inbound boolean in cfg.
func (p *Brontide) Inbound() bool {
return p.cfg.Inbound
}
// ConnReq is a getter for the Brontide's connReq in cfg.
func (p *Brontide) ConnReq() *connmgr.ConnReq {
return p.cfg.ConnReq
}
// ErrorBuffer is a getter for the Brontide's errorBuffer in cfg.
func (p *Brontide) ErrorBuffer() *queue.CircularBuffer {
return p.cfg.ErrorBuffer
}
// SetAddress sets the remote peer's address given an address.
func (p *Brontide) SetAddress(address net.Addr) {
p.cfg.Addr.Address = address
}
// ActiveSignal returns the peer's active signal.
func (p *Brontide) ActiveSignal() chan struct{} {
return p.activeSignal
}
// Conn returns a pointer to the peer's connection struct.
func (p *Brontide) Conn() net.Conn {
return p.cfg.Conn
}
// BytesReceived returns the number of bytes received from the peer.
func (p *Brontide) BytesReceived() uint64 {
return atomic.LoadUint64(&p.bytesReceived)
}
// BytesSent returns the number of bytes sent to the peer.
func (p *Brontide) BytesSent() uint64 {
return atomic.LoadUint64(&p.bytesSent)
}
// LastRemotePingPayload returns the last payload the remote party sent as part
// of their ping.
func (p *Brontide) LastRemotePingPayload() []byte {
pingPayload := p.lastPingPayload.Load()
if pingPayload == nil {
return []byte{}
}
pingBytes, ok := pingPayload.(lnwire.PingPayload)
if !ok {
return nil
}
return pingBytes
}
// attachChannelEventSubscription creates a channel event subscription and
// attaches to client to Brontide if the reenableTimeout is no greater than 1
// minute.
func (p *Brontide) attachChannelEventSubscription() error {
// If the timeout is greater than 1 minute, it's unlikely that the link
// hasn't yet finished its reestablishment. Return a nil without
// creating the client to specify that we don't want to retry.
if p.cfg.ChanActiveTimeout > 1*time.Minute {
return nil
}
// When the reenable timeout is less than 1 minute, it's likely the
// channel link hasn't finished its reestablishment yet. In that case,
// we'll give it a second chance by subscribing to the channel update
// events. Upon receiving the `ActiveLinkEvent`, we'll then request
// enabling the channel again.
sub, err := p.cfg.ChannelNotifier.SubscribeChannelEvents()
if err != nil {
return fmt.Errorf("SubscribeChannelEvents failed: %w", err)
}
p.channelEventClient = sub
return nil
}
// updateNextRevocation updates the existing channel's next revocation if it's
// nil.
func (p *Brontide) updateNextRevocation(c *channeldb.OpenChannel) error {
chanPoint := c.FundingOutpoint
chanID := lnwire.NewChanIDFromOutPoint(chanPoint)
// Read the current channel.
currentChan, loaded := p.activeChannels.Load(chanID)
// currentChan should exist, but we perform a check anyway to avoid nil
// pointer dereference.
if !loaded {
return fmt.Errorf("missing active channel with chanID=%v",
chanID)
}
// currentChan should not be nil, but we perform a check anyway to
// avoid nil pointer dereference.
if currentChan == nil {
return fmt.Errorf("found nil active channel with chanID=%v",
chanID)
}
// If we're being sent a new channel, and our existing channel doesn't
// have the next revocation, then we need to update the current
// existing channel.
if currentChan.RemoteNextRevocation() != nil {
return nil
}
p.log.Infof("Processing retransmitted ChannelReady for "+
"ChannelPoint(%v)", chanPoint)
nextRevoke := c.RemoteNextRevocation
err := currentChan.InitNextRevocation(nextRevoke)
if err != nil {
return fmt.Errorf("unable to init next revocation: %w", err)
}
return nil
}
// addActiveChannel adds a new active channel to the `activeChannels` map. It
// takes a `channeldb.OpenChannel`, creates a `lnwallet.LightningChannel` from
// it and assembles it with a channel link.
func (p *Brontide) addActiveChannel(c *lnpeer.NewChannel) error {
chanPoint := c.FundingOutpoint
chanID := lnwire.NewChanIDFromOutPoint(chanPoint)
// If we've reached this point, there are two possible scenarios. If
// the channel was in the active channels map as nil, then it was
// loaded from disk and we need to send reestablish. Else, it was not
// loaded from disk and we don't need to send reestablish as this is a
// fresh channel.
shouldReestablish := p.isLoadedFromDisk(chanID)
chanOpts := c.ChanOpts
if shouldReestablish {
// If we have to do the reestablish dance for this channel,
// ensure that we don't try to call InitRemoteMusigNonces twice
// by calling SkipNonceInit.
chanOpts = append(chanOpts, lnwallet.WithSkipNonceInit())
}
p.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) {
chanOpts = append(chanOpts, lnwallet.WithLeafStore(s))
})
p.cfg.AuxSigner.WhenSome(func(s lnwallet.AuxSigner) {
chanOpts = append(chanOpts, lnwallet.WithAuxSigner(s))
})
p.cfg.AuxResolver.WhenSome(func(s lnwallet.AuxContractResolver) {
chanOpts = append(chanOpts, lnwallet.WithAuxResolver(s))
})
// If not already active, we'll add this channel to the set of active
// channels, so we can look it up later easily according to its channel
// ID.
lnChan, err := lnwallet.NewLightningChannel(
p.cfg.Signer, c.OpenChannel, p.cfg.SigPool, chanOpts...,
)
if err != nil {
return fmt.Errorf("unable to create LightningChannel: %w", err)
}
// Store the channel in the activeChannels map.
p.activeChannels.Store(chanID, lnChan)
p.log.Infof("New channel active ChannelPoint(%v) with peer", chanPoint)
// Next, we'll assemble a ChannelLink along with the necessary items it
// needs to function.
chainEvents, err := p.cfg.ChainArb.SubscribeChannelEvents(chanPoint)
if err != nil {
return fmt.Errorf("unable to subscribe to chain events: %w",
err)
}
// We'll query the channel DB for the new channel's initial forwarding
// policies to determine the policy we start out with.
initialPolicy, err := p.cfg.ChannelDB.GetInitialForwardingPolicy(chanID)
if err != nil {
return fmt.Errorf("unable to query for initial forwarding "+
"policy: %v", err)
}
// Create the link and add it to the switch.
err = p.addLink(
&chanPoint, lnChan, initialPolicy, chainEvents,
shouldReestablish, fn.None[lnwire.Shutdown](),
)
if err != nil {
return fmt.Errorf("can't register new channel link(%v) with "+
"peer", chanPoint)
}
isTaprootChan := c.ChanType.IsTaproot()
// We're using the old co-op close, so we don't need to init the new RBF
// chan closer. If this is a taproot channel, then we'll also fall
// through, as we don't support this type yet w/ rbf close.
if !p.rbfCoopCloseAllowed() || isTaprootChan {
return nil
}
// Now that the link has been added above, we'll also init an RBF chan
// closer for this channel, but only if the new close feature is
// negotiated.
//
// Creating this here ensures that any shutdown messages sent will be
// automatically routed by the msg router.
if _, err := p.initRbfChanCloser(lnChan); err != nil {
p.activeChanCloses.Delete(chanID)
return fmt.Errorf("unable to init RBF chan closer for new "+
"chan: %w", err)
}
return nil
}
// handleNewActiveChannel handles a `newChannelMsg` request. Depending on we
// know this channel ID or not, we'll either add it to the `activeChannels` map
// or init the next revocation for it.
func (p *Brontide) handleNewActiveChannel(req *newChannelMsg) {
newChan := req.channel
chanPoint := newChan.FundingOutpoint
chanID := lnwire.NewChanIDFromOutPoint(chanPoint)
// Only update RemoteNextRevocation if the channel is in the
// activeChannels map and if we added the link to the switch. Only
// active channels will be added to the switch.
if p.isActiveChannel(chanID) {
p.log.Infof("Already have ChannelPoint(%v), ignoring",
chanPoint)
// Handle it and close the err chan on the request.
close(req.err)
// Update the next revocation point.
err := p.updateNextRevocation(newChan.OpenChannel)
if err != nil {
p.log.Errorf(err.Error())
}
return
}
// This is a new channel, we now add it to the map.
if err := p.addActiveChannel(req.channel); err != nil {
// Log and send back the error to the request.
p.log.Errorf(err.Error())
req.err <- err
return
}
// Close the err chan if everything went fine.
close(req.err)
}
// handleNewPendingChannel takes a `newChannelMsg` request and add it to
// `activeChannels` map with nil value. This pending channel will be saved as
// it may become active in the future. Once active, the funding manager will
// send it again via `AddNewChannel`, and we'd handle the link creation there.
func (p *Brontide) handleNewPendingChannel(req *newChannelMsg) {
defer close(req.err)
chanID := req.channelID
// If we already have this channel, something is wrong with the funding
// flow as it will only be marked as active after `ChannelReady` is
// handled. In this case, we will do nothing but log an error, just in
// case this is a legit channel.
if p.isActiveChannel(chanID) {
p.log.Errorf("Channel(%v) is already active, ignoring "+
"pending channel request", chanID)
return
}
// The channel has already been added, we will do nothing and return.
if p.isPendingChannel(chanID) {
p.log.Infof("Channel(%v) is already added, ignoring "+
"pending channel request", chanID)
return
}
// This is a new channel, we now add it to the map `activeChannels`
// with nil value and mark it as a newly added channel in
// `addedChannels`.
p.activeChannels.Store(chanID, nil)
p.addedChannels.Store(chanID, struct{}{})
}
// handleRemovePendingChannel takes a `newChannelMsg` request and removes it
// from `activeChannels` map. The request will be ignored if the channel is
// considered active by Brontide. Noop if the channel ID cannot be found.
func (p *Brontide) handleRemovePendingChannel(req *newChannelMsg) {
defer close(req.err)
chanID := req.channelID
// If we already have this channel, something is wrong with the funding
// flow as it will only be marked as active after `ChannelReady` is
// handled. In this case, we will log an error and exit.
if p.isActiveChannel(chanID) {
p.log.Errorf("Channel(%v) is active, ignoring remove request",
chanID)
return
}
// The channel has not been added yet, we will log a warning as there
// is an unexpected call from funding manager.
if !p.isPendingChannel(chanID) {
p.log.Warnf("Channel(%v) not found, removing it anyway", chanID)
}
// Remove the record of this pending channel.
p.activeChannels.Delete(chanID)
p.addedChannels.Delete(chanID)
}
// sendLinkUpdateMsg sends a message that updates the channel to the
// channel's message stream.
func (p *Brontide) sendLinkUpdateMsg(cid lnwire.ChannelID, msg lnwire.Message) {
p.log.Tracef("Sending link update msg=%v", msg.MsgType())
chanStream, ok := p.activeMsgStreams[cid]
if !ok {
// If a stream hasn't yet been created, then we'll do so, add
// it to the map, and finally start it.
chanStream = newChanMsgStream(p, cid)
p.activeMsgStreams[cid] = chanStream
chanStream.Start()
// Stop the stream when quit.
go func() {
<-p.cg.Done()
chanStream.Stop()
}()
}
// With the stream obtained, add the message to the stream so we can
// continue processing message.
chanStream.AddMsg(msg)
}
// scaleTimeout multiplies the argument duration by a constant factor depending
// on variious heuristics. Currently this is only used to check whether our peer
// appears to be connected over Tor and relaxes the timout deadline. However,
// this is subject to change and should be treated as opaque.
func (p *Brontide) scaleTimeout(timeout time.Duration) time.Duration {
if p.isTorConnection {
return timeout * time.Duration(torTimeoutMultiplier)
}
return timeout
}
// CoopCloseUpdates is a struct used to communicate updates for an active close
// to the caller.
type CoopCloseUpdates struct {
UpdateChan chan interface{}
ErrChan chan error
}
// ChanHasRbfCoopCloser returns true if the channel as identifier by the channel
// point has an active RBF chan closer.
func (p *Brontide) ChanHasRbfCoopCloser(chanPoint wire.OutPoint) bool {
chanID := lnwire.NewChanIDFromOutPoint(chanPoint)
chanCloser, found := p.activeChanCloses.Load(chanID)
if !found {
return false
}
return chanCloser.IsRight()
}
// TriggerCoopCloseRbfBump given a chan ID, and the params needed to trigger a
// new RBF co-op close update, a bump is attempted. A channel used for updates,
// along with one used to o=communicate any errors is returned. If no chan
// closer is found, then false is returned for the second argument.
func (p *Brontide) TriggerCoopCloseRbfBump(ctx context.Context,
chanPoint wire.OutPoint, feeRate chainfee.SatPerKWeight,
deliveryScript lnwire.DeliveryAddress) (*CoopCloseUpdates, error) {
// If RBF coop close isn't permitted, then we'll an error.
if !p.rbfCoopCloseAllowed() {
return nil, fmt.Errorf("rbf coop close not enabled for " +
"channel")
}
closeUpdates := &CoopCloseUpdates{
UpdateChan: make(chan interface{}, 1),
ErrChan: make(chan error, 1),
}
// We'll re-use the existing switch struct here, even though we're
// bypassing the switch entirely.
closeReq := htlcswitch.ChanClose{
CloseType: contractcourt.CloseRegular,
ChanPoint: &chanPoint,
TargetFeePerKw: feeRate,
DeliveryScript: deliveryScript,
Updates: closeUpdates.UpdateChan,
Err: closeUpdates.ErrChan,
Ctx: ctx,
}
err := p.startRbfChanCloser(newRPCShutdownInit(&closeReq), chanPoint)
if err != nil {
return nil, err
}
return closeUpdates, nil
}
package peer
import (
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
)
// channelView is a view into the current active/global channel state machine
// for a given link.
type channelView interface {
// OweCommitment returns a boolean value reflecting whether we need to
// send out a commitment signature because there are outstanding local
// updates and/or updates in the local commit tx that aren't reflected
// in the remote commit tx yet.
OweCommitment() bool
// IsChannelClean returns true if neither side has pending commitments,
// neither side has HTLC's, and all updates are locked in irrevocably.
IsChannelClean() bool
// MarkCoopBroadcasted persistently marks that the channel close
// transaction has been broadcast.
MarkCoopBroadcasted(*wire.MsgTx, lntypes.ChannelParty) error
// StateSnapshot returns a snapshot of the current fully committed
// state within the channel.
StateSnapshot() *channeldb.ChannelSnapshot
// MarkShutdownSent persists the given ShutdownInfo. The existence of
// the ShutdownInfo represents the fact that the Shutdown message has
// been sent by us and so should be re-sent on re-establish.
MarkShutdownSent(info *channeldb.ShutdownInfo) error
}
// linkController is capable of controlling the flow out incoming/outgoing
// HTLCs to/from the link.
type linkController interface {
// DisableAdds sets the ChannelUpdateHandler state to allow/reject
// UpdateAddHtlc's in the specified direction. It returns true if the
// state was changed and false if the desired state was already set
// before the method was called.
DisableAdds(outgoing bool) bool
// IsFlushing returns true when UpdateAddHtlc's are disabled in the
// direction of the argument.
IsFlushing(direction bool) bool
}
// linkNetworkController is an interface that represents an object capable of
// managing interactions with the active channel links from the PoV of the
// gossip network.
type linkNetworkController interface {
// RequestDisable disables a channel by its channel point.
RequestDisable(wire.OutPoint, bool) error
}
// chanObserver implements the chancloser.ChanObserver interface for the
// existing LightningChannel struct/instance.
type chanObserver struct {
chanView channelView
link linkController
linkNetwork linkNetworkController
}
// newChanObserver creates a new instance of a chanObserver from an active
// channelView.
func newChanObserver(chanView channelView,
link linkController, linkNetwork linkNetworkController) *chanObserver {
return &chanObserver{
chanView: chanView,
link: link,
linkNetwork: linkNetwork,
}
}
// NoDanglingUpdates returns true if there are no dangling updates in the
// channel. In other words, there are no active update messages that haven't
// already been covered by a commit sig.
func (l *chanObserver) NoDanglingUpdates() bool {
return !l.chanView.OweCommitment()
}
// DisableIncomingAdds instructs the channel link to disable process new
// incoming add messages.
func (l *chanObserver) DisableIncomingAdds() error {
// If there's no link, then we don't need to disable any adds.
if l.link == nil {
return nil
}
disabled := l.link.DisableAdds(htlcswitch.Incoming)
if disabled {
chanPoint := l.chanView.StateSnapshot().ChannelPoint
peerLog.Debugf("ChannelPoint(%v): link already disabled",
chanPoint)
}
return nil
}
// DisableOutgoingAdds instructs the channel link to disable process new
// outgoing add messages.
func (l *chanObserver) DisableOutgoingAdds() error {
// If there's no link, then we don't need to disable any adds.
if l.link == nil {
return nil
}
_ = l.link.DisableAdds(htlcswitch.Outgoing)
return nil
}
// MarkCoopBroadcasted persistently marks that the channel close transaction
// has been broadcast.
func (l *chanObserver) MarkCoopBroadcasted(tx *wire.MsgTx, local bool) error {
return l.chanView.MarkCoopBroadcasted(tx, lntypes.Local)
}
// MarkShutdownSent persists the given ShutdownInfo. The existence of the
// ShutdownInfo represents the fact that the Shutdown message has been sent by
// us and so should be re-sent on re-establish.
func (l *chanObserver) MarkShutdownSent(deliveryAddr []byte,
isInitiator bool) error {
shutdownInfo := channeldb.NewShutdownInfo(deliveryAddr, isInitiator)
return l.chanView.MarkShutdownSent(shutdownInfo)
}
// FinalBalances is the balances of the channel once it has been flushed. If
// Some, then this indicates that the channel is now in a state where it's
// always flushed, so we can accelerate the state transitions.
func (l *chanObserver) FinalBalances() fn.Option[chancloser.ShutdownBalances] {
chanClean := l.chanView.IsChannelClean()
switch {
// If we have a link, then the balances are final if both the incoming
// and outgoing adds are disabled _and_ the channel is clean.
case l.link != nil && l.link.IsFlushing(htlcswitch.Incoming) &&
l.link.IsFlushing(htlcswitch.Outgoing) && chanClean:
fallthrough
// If we don't have a link, then this is a restart case, so the
// balances are final.
case l.link == nil:
snapshot := l.chanView.StateSnapshot()
return fn.Some(chancloser.ShutdownBalances{
LocalBalance: snapshot.LocalBalance,
RemoteBalance: snapshot.RemoteBalance,
})
// Otherwise, the link is still active and not flushed, so the balances
// aren't yet final.
default:
return fn.None[chancloser.ShutdownBalances]()
}
}
// DisableChannel disables the target channel.
func (l *chanObserver) DisableChannel() error {
op := l.chanView.StateSnapshot().ChannelPoint
return l.linkNetwork.RequestDisable(op, false)
}
// A compile-time assertion to ensure that chanObserver meets the
// chancloser.ChanStateObserver interface.
var _ chancloser.ChanStateObserver = (*chanObserver)(nil)
package peer
import (
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/protofsm"
)
// MessageSender is an interface that represents an object capable of sending
// p2p messages to a destination.
type MessageSender interface {
// SendMessages sends the target set of messages to the target peer.
//
// TODO(roasbeef): current impl bound to single peer, need server
// pointer otherwise
SendMessages(btcec.PublicKey, []lnwire.Message) error
}
// flexMessageSender is a message sender-like interface that is aware of
// sync/async semantics, and is bound to a single peer.
type flexMessageSender interface {
// SendMessage sends a variadic number of high-priority messages to the
// remote peer. The first argument denotes if the method should block
// until the messages have been sent to the remote peer or an error is
// returned, otherwise it returns immediately after queuing.
SendMessage(sync bool, msgs ...lnwire.Message) error
}
// peerMsgSender implements the MessageSender interface for a single peer.
// It'll return an error if the target public isn't equal to public key of the
// backing peer.
type peerMsgSender struct {
sender flexMessageSender
peerPub btcec.PublicKey
}
// newPeerMsgSender creates a new instance of a peerMsgSender.
func newPeerMsgSender(peerPub btcec.PublicKey,
msgSender flexMessageSender) *peerMsgSender {
return &peerMsgSender{
sender: msgSender,
peerPub: peerPub,
}
}
// SendMessages sends the target set of messages to the target peer.
//
// TODO(roasbeef): current impl bound to single peer, need server pointer
// otherwise?
func (p *peerMsgSender) SendMessages(pub btcec.PublicKey,
msgs []lnwire.Message) error {
if !p.peerPub.IsEqual(&pub) {
return fmt.Errorf("wrong peer pubkey: got %x, can only send "+
"to %x", pub.SerializeCompressed(),
p.peerPub.SerializeCompressed())
}
return p.sender.SendMessage(true, msgs...)
}
// TxBroadcaster is an interface that represents an object capable of
// broadcasting transactions to the network.
type TxBroadcaster interface {
// PublishTransaction broadcasts a transaction to the network.
PublishTransaction(*wire.MsgTx, string) error
}
// LndAdapterCfg is a struct that holds the configuration for the
// LndDaemonAdapters instance.
type LndAdapterCfg struct {
// MsgSender is capable of sending messages to an arbitrary peer.
MsgSender MessageSender
// TxBroadcaster is capable of broadcasting a transaction to the
// network.
TxBroadcaster TxBroadcaster
// ChainNotifier is capable of receiving notifications for on-chain
// events.
ChainNotifier chainntnfs.ChainNotifier
}
// LndDaemonAdapters is a struct that implements the protofsm.DaemonAdapters
// interface using common lnd abstractions.
type LndDaemonAdapters struct {
cfg LndAdapterCfg
}
// NewLndDaemonAdapters creates a new instance of the lndDaemonAdapters struct.
func NewLndDaemonAdapters(cfg LndAdapterCfg) *LndDaemonAdapters {
return &LndDaemonAdapters{
cfg: cfg,
}
}
// SendMessages sends the target set of messages to the target peer.
func (l *LndDaemonAdapters) SendMessages(pub btcec.PublicKey,
msgs []lnwire.Message) error {
return l.cfg.MsgSender.SendMessages(pub, msgs)
}
// BroadcastTransaction broadcasts a transaction with the target label.
func (l *LndDaemonAdapters) BroadcastTransaction(tx *wire.MsgTx,
label string) error {
return l.cfg.TxBroadcaster.PublishTransaction(tx, label)
}
// RegisterConfirmationsNtfn registers an intent to be notified once txid
// reaches numConfs confirmations.
func (l *LndDaemonAdapters) RegisterConfirmationsNtfn(txid *chainhash.Hash,
pkScript []byte, numConfs, heightHint uint32,
opts ...chainntnfs.NotifierOption,
) (*chainntnfs.ConfirmationEvent, error) {
return l.cfg.ChainNotifier.RegisterConfirmationsNtfn(
txid, pkScript, numConfs, heightHint, opts...,
)
}
// RegisterSpendNtfn registers an intent to be notified once the target
// outpoint is successfully spent within a transaction.
func (l *LndDaemonAdapters) RegisterSpendNtfn(outpoint *wire.OutPoint,
pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) {
return l.cfg.ChainNotifier.RegisterSpendNtfn(
outpoint, pkScript, heightHint,
)
}
// A compile time check to ensure that lndDaemonAdapters fully implements the
// DaemonAdapters interface.
var _ protofsm.DaemonAdapters = (*LndDaemonAdapters)(nil)
package peer
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// peerLog is a logger that is initialized with the btclog.Disabled logger.
var peerLog btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("PEER", nil))
}
// DisableLog disables all logging output.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
peerLog = logger
}
package peer
import (
"fmt"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
"github.com/lightningnetwork/lnd/lnwire"
)
// MusigChanCloser is an adapter over the normal channel state machine that
// allows the chan closer to handle the musig2 details of closing taproot
// channels.
type MusigChanCloser struct {
channel *lnwallet.LightningChannel
musigSession *lnwallet.MusigSession
localNonce *musig2.Nonces
remoteNonce *musig2.Nonces
}
// NewMusigChanCloser creates a new musig chan closer from a normal channel.
func NewMusigChanCloser(channel *lnwallet.LightningChannel) *MusigChanCloser {
return &MusigChanCloser{
channel: channel,
}
}
// ProposalClosingOpts returns the options that should be used when
// generating a new co-op close signature.
func (m *MusigChanCloser) ProposalClosingOpts() (
[]lnwallet.ChanCloseOpt, error) {
switch {
case m.localNonce == nil:
return nil, fmt.Errorf("local nonce not generated")
case m.remoteNonce == nil:
return nil, fmt.Errorf("remote nonce not generated")
}
localKey, remoteKey := m.channel.MultiSigKeys()
tapscriptTweak := fn.MapOption(lnwallet.TapscriptRootToTweak)(
m.channel.State().TapscriptRoot,
)
m.musigSession = lnwallet.NewPartialMusigSession(
*m.remoteNonce, localKey, remoteKey,
m.channel.Signer, m.channel.FundingTxOut(),
lnwallet.RemoteMusigCommit, tapscriptTweak,
)
err := m.musigSession.FinalizeSession(*m.localNonce)
if err != nil {
return nil, err
}
return []lnwallet.ChanCloseOpt{
lnwallet.WithCoopCloseMusigSession(m.musigSession),
}, nil
}
// CombineClosingOpts returns the options that should be used when combining
// the final musig partial signature. The method also maps the lnwire partial
// signatures into an input.Signature that can be used more generally.
func (m *MusigChanCloser) CombineClosingOpts(localSig,
remoteSig lnwire.PartialSig) (input.Signature, input.Signature,
[]lnwallet.ChanCloseOpt, error) {
if m.musigSession == nil {
return nil, nil, nil, fmt.Errorf("musig session not created")
}
// We'll convert the wire partial signatures into an input.Signature
// compliant struct so we can pass it into the final combination
// function.
localPartialSig := &lnwire.PartialSigWithNonce{
PartialSig: localSig,
Nonce: m.localNonce.PubNonce,
}
remotePartialSig := &lnwire.PartialSigWithNonce{
PartialSig: remoteSig,
Nonce: m.remoteNonce.PubNonce,
}
localMuSig := new(lnwallet.MusigPartialSig).FromWireSig(
localPartialSig,
)
remoteMuSig := new(lnwallet.MusigPartialSig).FromWireSig(
remotePartialSig,
)
opts := []lnwallet.ChanCloseOpt{
lnwallet.WithCoopCloseMusigSession(m.musigSession),
}
// For taproot channels, we'll need to pass along the session so the
// final combined signature can be created.
return localMuSig, remoteMuSig, opts, nil
}
// ClosingNonce returns the nonce that should be used when generating the our
// partial signature for the remote party.
func (m *MusigChanCloser) ClosingNonce() (*musig2.Nonces, error) {
if m.localNonce != nil {
return m.localNonce, nil
}
localKey, _ := m.channel.MultiSigKeys()
nonce, err := musig2.GenNonces(
musig2.WithPublicKey(localKey.PubKey),
)
if err != nil {
return nil, err
}
m.localNonce = nonce
return nonce, nil
}
// InitRemoteNonce saves the remote nonce the party sent during their shutdown
// message so it can be used later to generate and verify signatures.
func (m *MusigChanCloser) InitRemoteNonce(nonce *musig2.Nonces) {
m.remoteNonce = nonce
}
// A compile-time assertion to ensure MusigChanCloser implements the
// chancloser.MusigSession interface.
var _ chancloser.MusigSession = (*MusigChanCloser)(nil)
package peer
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"time"
"github.com/lightningnetwork/lnd/lnwire"
)
// PingManagerConfig is a structure containing various parameters that govern
// how the PingManager behaves.
type PingManagerConfig struct {
// NewPingPayload is a closure that returns the payload to be packaged
// in the Ping message.
NewPingPayload func() []byte
// NewPongSize is a closure that returns a random value between
// [0, lnwire.MaxPongBytes]. This random value helps to more effectively
// pair Pong messages with Ping.
NewPongSize func() uint16
// IntervalDuration is the Duration between attempted pings.
IntervalDuration time.Duration
// TimeoutDuration is the Duration we wait before declaring a ping
// attempt failed.
TimeoutDuration time.Duration
// SendPing is a closure that is responsible for sending the Ping
// message out to our peer
SendPing func(ping *lnwire.Ping)
// OnPongFailure is a closure that is responsible for executing the
// logic when a Pong message is either late or does not match our
// expectations for that Pong
OnPongFailure func(error)
}
// PingManager is a structure that is designed to manage the internal state
// of the ping pong lifecycle with the remote peer. We assume there is only one
// ping outstanding at once.
//
// NOTE: This structure MUST be initialized with NewPingManager.
type PingManager struct {
cfg *PingManagerConfig
// pingTime is a rough estimate of the RTT (round-trip-time) between us
// and the connected peer.
// To be used atomically.
// TODO(roasbeef): also use a WMA or EMA?
pingTime atomic.Pointer[time.Duration]
// pingLastSend is the time when we sent our last ping message.
// To be used atomically.
pingLastSend *time.Time
// outstandingPongSize is the current size of the requested pong
// payload. This value can only validly range from [0,65531]. Any
// value < 0 is interpreted as if there is no outstanding ping message.
outstandingPongSize int32
// pingTicker is a pointer to a Ticker that fires on every ping
// interval.
pingTicker *time.Ticker
// pingTimeout is a Timer that will fire when we want to time out a
// ping
pingTimeout *time.Timer
// pongChan is the channel on which the pingManager will write Pong
// messages it is evaluating
pongChan chan *lnwire.Pong
started sync.Once
stopped sync.Once
quit chan struct{}
wg sync.WaitGroup
}
// NewPingManager constructs a pingManager in a valid state. It must be started
// before it does anything useful, though.
func NewPingManager(cfg *PingManagerConfig) *PingManager {
m := PingManager{
cfg: cfg,
outstandingPongSize: -1,
pongChan: make(chan *lnwire.Pong, 1),
quit: make(chan struct{}),
}
return &m
}
// Start launches the primary goroutine that is owned by the pingManager.
func (m *PingManager) Start() error {
var err error
m.started.Do(func() {
m.pingTicker = time.NewTicker(m.cfg.IntervalDuration)
m.pingTimeout = time.NewTimer(0)
m.wg.Add(1)
go m.pingHandler()
})
return err
}
// pingHandler is the main goroutine responsible for enforcing the ping/pong
// protocol.
func (m *PingManager) pingHandler() {
defer m.wg.Done()
defer m.pingTimeout.Stop()
// Ensure that the pingTimeout channel is empty.
if !m.pingTimeout.Stop() {
<-m.pingTimeout.C
}
for {
select {
case <-m.pingTicker.C:
// If this occurs it means that the new ping cycle has
// begun while there is still an outstanding ping
// awaiting a pong response. This should never occur,
// but if it does, it implies a timeout.
if m.outstandingPongSize >= 0 {
e := errors.New("impossible: new ping" +
"in unclean state",
)
m.cfg.OnPongFailure(e)
return
}
pongSize := m.cfg.NewPongSize()
ping := &lnwire.Ping{
NumPongBytes: pongSize,
PaddingBytes: m.cfg.NewPingPayload(),
}
// Set up our bookkeeping for the new Ping.
if err := m.setPingState(pongSize); err != nil {
m.cfg.OnPongFailure(err)
return
}
m.cfg.SendPing(ping)
case <-m.pingTimeout.C:
m.resetPingState()
e := errors.New("timeout while waiting for " +
"pong response",
)
m.cfg.OnPongFailure(e)
return
case pong := <-m.pongChan:
pongSize := int32(len(pong.PongBytes))
// Save off values we are about to override when we
// call resetPingState.
expected := m.outstandingPongSize
lastPing := m.pingLastSend
m.resetPingState()
// If the pong we receive doesn't match the ping we
// sent out, then we fail out.
if pongSize != expected {
e := errors.New("pong response does " +
"not match expected size",
)
m.cfg.OnPongFailure(e)
return
}
// Compute RTT of ping and save that for future
// querying.
if lastPing != nil {
rtt := time.Since(*lastPing)
m.pingTime.Store(&rtt)
}
case <-m.quit:
return
}
}
}
// Stop interrupts the goroutines that the PingManager owns.
func (m *PingManager) Stop() {
if m.pingTicker == nil {
return
}
m.stopped.Do(func() {
close(m.quit)
m.wg.Wait()
m.pingTicker.Stop()
m.pingTimeout.Stop()
})
}
// setPingState is a private method to keep track of all of the fields we need
// to set when we send out a Ping.
func (m *PingManager) setPingState(pongSize uint16) error {
t := time.Now()
m.pingLastSend = &t
m.outstandingPongSize = int32(pongSize)
if m.pingTimeout.Reset(m.cfg.TimeoutDuration) {
return fmt.Errorf(
"impossible: ping timeout reset when already active",
)
}
return nil
}
// resetPingState is a private method that resets all of the bookkeeping that
// is tracking a currently outstanding Ping.
func (m *PingManager) resetPingState() {
m.pingLastSend = nil
m.outstandingPongSize = -1
if !m.pingTimeout.Stop() {
select {
case <-m.pingTimeout.C:
default:
}
}
}
// GetPingTimeMicroSeconds reports back the RTT calculated by the pingManager.
func (m *PingManager) GetPingTimeMicroSeconds() int64 {
rtt := m.pingTime.Load()
if rtt == nil {
return -1
}
return rtt.Microseconds()
}
// ReceivedPong is called to evaluate a Pong message against the expectations
// we have for it. It will cause the PingManager to invoke the supplied
// OnPongFailure function if the Pong argument supplied violates expectations.
func (m *PingManager) ReceivedPong(msg *lnwire.Pong) {
select {
case m.pongChan <- msg:
case <-m.quit:
}
}
package peer
import (
"bytes"
crand "crypto/rand"
"encoding/binary"
"io"
"math/rand"
"net"
"sync/atomic"
"testing"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channelnotifier"
"github.com/lightningnetwork/lnd/fn/v2"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntest/channels"
"github.com/lightningnetwork/lnd/lntest/mock"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/pool"
"github.com/lightningnetwork/lnd/queue"
"github.com/lightningnetwork/lnd/shachain"
"github.com/stretchr/testify/require"
)
const (
broadcastHeight = 100
// timeout is a timeout value to use for tests which need to wait for
// a return value on a channel.
timeout = time.Second * 5
// testCltvRejectDelta is the minimum delta between expiry and current
// height below which htlcs are rejected.
testCltvRejectDelta = 13
)
var (
testKeyLoc = keychain.KeyLocator{Family: keychain.KeyFamilyNodeKey}
)
// noUpdate is a function which can be used as a parameter in
// createTestPeerWithChannel to call the setup code with no custom values on
// the channels set up.
var noUpdate = func(a, b *channeldb.OpenChannel) {}
type peerTestCtx struct {
peer *Brontide
channel *lnwallet.LightningChannel
notifier *mock.ChainNotifier
publishTx <-chan *wire.MsgTx
mockSwitch *mockMessageSwitch
db *channeldb.DB
privKey *btcec.PrivateKey
mockConn *mockMessageConn
customChan chan *customMsg
chanStatusMgr *netann.ChanStatusManager
}
// createTestPeerWithChannel creates a channel between two nodes, and returns a
// peer for one of the nodes, together with the channel seen from both nodes.
// It takes an updateChan function which can be used to modify the default
// values on the channel states for each peer.
func createTestPeerWithChannel(t *testing.T, updateChan func(a,
b *channeldb.OpenChannel)) (*peerTestCtx, error) {
params := createTestPeer(t)
var (
publishTx = params.publishTx
mockSwitch = params.mockSwitch
alicePeer = params.peer
notifier = params.notifier
aliceKeyPriv = params.privKey
dbAlice = params.db
chanStatusMgr = params.chanStatusMgr
)
err := chanStatusMgr.Start()
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, chanStatusMgr.Stop())
})
aliceKeyPub := alicePeer.IdentityKey()
estimator := alicePeer.cfg.FeeEstimator
channelCapacity := btcutil.Amount(10 * 1e8)
channelBal := channelCapacity / 2
aliceDustLimit := btcutil.Amount(200)
bobDustLimit := btcutil.Amount(1300)
csvTimeoutAlice := uint32(5)
csvTimeoutBob := uint32(4)
isAliceInitiator := true
prevOut := &wire.OutPoint{
Hash: channels.TestHdSeed,
Index: 0,
}
fundingTxIn := wire.NewTxIn(prevOut, nil, nil)
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(
channels.BobsPrivKey,
)
aliceCfg := channeldb.ChannelConfig{
ChannelStateBounds: channeldb.ChannelStateBounds{
MaxPendingAmount: lnwire.MilliSatoshi(rand.Int63()),
ChanReserve: btcutil.Amount(rand.Int63()),
MinHTLC: lnwire.MilliSatoshi(rand.Int63()),
MaxAcceptedHtlcs: uint16(rand.Int31()),
},
CommitmentParams: channeldb.CommitmentParams{
DustLimit: aliceDustLimit,
CsvDelay: uint16(csvTimeoutAlice),
},
MultiSigKey: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
},
RevocationBasePoint: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
},
PaymentBasePoint: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
},
DelayBasePoint: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
},
HtlcBasePoint: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
},
}
bobCfg := channeldb.ChannelConfig{
ChannelStateBounds: channeldb.ChannelStateBounds{
MaxPendingAmount: lnwire.MilliSatoshi(rand.Int63()),
ChanReserve: btcutil.Amount(rand.Int63()),
MinHTLC: lnwire.MilliSatoshi(rand.Int63()),
MaxAcceptedHtlcs: uint16(rand.Int31()),
},
CommitmentParams: channeldb.CommitmentParams{
DustLimit: bobDustLimit,
CsvDelay: uint16(csvTimeoutBob),
},
MultiSigKey: keychain.KeyDescriptor{
PubKey: bobKeyPub,
},
RevocationBasePoint: keychain.KeyDescriptor{
PubKey: bobKeyPub,
},
PaymentBasePoint: keychain.KeyDescriptor{
PubKey: bobKeyPub,
},
DelayBasePoint: keychain.KeyDescriptor{
PubKey: bobKeyPub,
},
HtlcBasePoint: keychain.KeyDescriptor{
PubKey: bobKeyPub,
},
}
bobRoot, err := chainhash.NewHash(bobKeyPriv.Serialize())
if err != nil {
return nil, err
}
bobPreimageProducer := shachain.NewRevocationProducer(*bobRoot)
bobFirstRevoke, err := bobPreimageProducer.AtIndex(0)
if err != nil {
return nil, err
}
bobCommitPoint := input.ComputeCommitmentPoint(bobFirstRevoke[:])
aliceRoot, err := chainhash.NewHash(aliceKeyPriv.Serialize())
if err != nil {
return nil, err
}
alicePreimageProducer := shachain.NewRevocationProducer(*aliceRoot)
aliceFirstRevoke, err := alicePreimageProducer.AtIndex(0)
if err != nil {
return nil, err
}
aliceCommitPoint := input.ComputeCommitmentPoint(aliceFirstRevoke[:])
aliceCommitTx, bobCommitTx, err := lnwallet.CreateCommitmentTxns(
channelBal, channelBal, &aliceCfg, &bobCfg, aliceCommitPoint,
bobCommitPoint, *fundingTxIn, channeldb.SingleFunderTweaklessBit,
isAliceInitiator, 0,
)
if err != nil {
return nil, err
}
dbBob := channeldb.OpenForTesting(t, t.TempDir())
feePerKw, err := estimator.EstimateFeePerKW(1)
if err != nil {
return nil, err
}
// TODO(roasbeef): need to factor in commit fee?
aliceCommit := channeldb.ChannelCommitment{
CommitHeight: 0,
LocalBalance: lnwire.NewMSatFromSatoshis(channelBal),
RemoteBalance: lnwire.NewMSatFromSatoshis(channelBal),
FeePerKw: btcutil.Amount(feePerKw),
CommitFee: feePerKw.FeeForWeight(input.CommitWeight),
CommitTx: aliceCommitTx,
CommitSig: bytes.Repeat([]byte{1}, 71),
}
bobCommit := channeldb.ChannelCommitment{
CommitHeight: 0,
LocalBalance: lnwire.NewMSatFromSatoshis(channelBal),
RemoteBalance: lnwire.NewMSatFromSatoshis(channelBal),
FeePerKw: btcutil.Amount(feePerKw),
CommitFee: feePerKw.FeeForWeight(input.CommitWeight),
CommitTx: bobCommitTx,
CommitSig: bytes.Repeat([]byte{1}, 71),
}
var chanIDBytes [8]byte
if _, err := io.ReadFull(crand.Reader, chanIDBytes[:]); err != nil {
return nil, err
}
shortChanID := lnwire.NewShortChanIDFromInt(
binary.BigEndian.Uint64(chanIDBytes[:]),
)
aliceChannelState := &channeldb.OpenChannel{
LocalChanCfg: aliceCfg,
RemoteChanCfg: bobCfg,
IdentityPub: aliceKeyPub,
FundingOutpoint: *prevOut,
ShortChannelID: shortChanID,
ChanType: channeldb.SingleFunderTweaklessBit,
IsInitiator: isAliceInitiator,
Capacity: channelCapacity,
RemoteCurrentRevocation: bobCommitPoint,
RevocationProducer: alicePreimageProducer,
RevocationStore: shachain.NewRevocationStore(),
LocalCommitment: aliceCommit,
RemoteCommitment: aliceCommit,
Db: dbAlice.ChannelStateDB(),
Packager: channeldb.NewChannelPackager(shortChanID),
FundingTxn: channels.TestFundingTx,
}
bobChannelState := &channeldb.OpenChannel{
LocalChanCfg: bobCfg,
RemoteChanCfg: aliceCfg,
IdentityPub: bobKeyPub,
FundingOutpoint: *prevOut,
ChanType: channeldb.SingleFunderTweaklessBit,
IsInitiator: !isAliceInitiator,
Capacity: channelCapacity,
RemoteCurrentRevocation: aliceCommitPoint,
RevocationProducer: bobPreimageProducer,
RevocationStore: shachain.NewRevocationStore(),
LocalCommitment: bobCommit,
RemoteCommitment: bobCommit,
Db: dbBob.ChannelStateDB(),
Packager: channeldb.NewChannelPackager(shortChanID),
}
// Set custom values on the channel states.
updateChan(aliceChannelState, bobChannelState)
aliceAddr := alicePeer.cfg.Addr.Address
if err := aliceChannelState.SyncPending(aliceAddr, 0); err != nil {
return nil, err
}
bobAddr := &net.TCPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 18556,
}
if err := bobChannelState.SyncPending(bobAddr, 0); err != nil {
return nil, err
}
aliceSigner := input.NewMockSigner(
[]*btcec.PrivateKey{aliceKeyPriv}, nil,
)
bobSigner := input.NewMockSigner(
[]*btcec.PrivateKey{bobKeyPriv}, nil,
)
alicePool := lnwallet.NewSigPool(1, aliceSigner)
channelAlice, err := lnwallet.NewLightningChannel(
aliceSigner, aliceChannelState, alicePool,
lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}),
lnwallet.WithAuxSigner(lnwallet.NewAuxSignerMock(
lnwallet.EmptyMockJobHandler,
)),
)
if err != nil {
return nil, err
}
_ = alicePool.Start()
t.Cleanup(func() {
require.NoError(t, alicePool.Stop())
})
bobPool := lnwallet.NewSigPool(1, bobSigner)
channelBob, err := lnwallet.NewLightningChannel(
bobSigner, bobChannelState, bobPool,
lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}),
lnwallet.WithAuxSigner(lnwallet.NewAuxSignerMock(
lnwallet.EmptyMockJobHandler,
)),
)
if err != nil {
return nil, err
}
_ = bobPool.Start()
t.Cleanup(func() {
require.NoError(t, bobPool.Stop())
})
alicePeer.remoteFeatures = lnwire.NewFeatureVector(
nil, lnwire.Features,
)
chanID := lnwire.NewChanIDFromOutPoint(channelAlice.ChannelPoint())
alicePeer.activeChannels.Store(chanID, channelAlice)
alicePeer.cg.WgAdd(1)
go alicePeer.channelManager()
return &peerTestCtx{
peer: alicePeer,
channel: channelBob,
notifier: notifier,
publishTx: publishTx,
mockSwitch: mockSwitch,
mockConn: params.mockConn,
}, nil
}
// mockMessageSwitch is a mock implementation of the messageSwitch interface
// used for testing without relying on a *htlcswitch.Switch in unit tests.
type mockMessageSwitch struct {
links []htlcswitch.ChannelUpdateHandler
}
// BestHeight currently returns a dummy value.
func (m *mockMessageSwitch) BestHeight() uint32 {
return 0
}
// CircuitModifier currently returns a dummy value.
func (m *mockMessageSwitch) CircuitModifier() htlcswitch.CircuitModifier {
return nil
}
// RemoveLink currently does nothing.
func (m *mockMessageSwitch) RemoveLink(cid lnwire.ChannelID) {}
// CreateAndAddLink currently returns a dummy value.
func (m *mockMessageSwitch) CreateAndAddLink(cfg htlcswitch.ChannelLinkConfig,
lnChan *lnwallet.LightningChannel) error {
return nil
}
// GetLinksByInterface returns the active links.
func (m *mockMessageSwitch) GetLinksByInterface(pub [33]byte) (
[]htlcswitch.ChannelUpdateHandler, error) {
return m.links, nil
}
// mockUpdateHandler is a mock implementation of the ChannelUpdateHandler
// interface. It is used in mockMessageSwitch's GetLinksByInterface method.
type mockUpdateHandler struct {
cid lnwire.ChannelID
isOutgoingAddBlocked atomic.Bool
isIncomingAddBlocked atomic.Bool
}
// newMockUpdateHandler creates a new mockUpdateHandler.
func newMockUpdateHandler(cid lnwire.ChannelID) *mockUpdateHandler {
return &mockUpdateHandler{
cid: cid,
}
}
// HandleChannelUpdate currently does nothing.
func (m *mockUpdateHandler) HandleChannelUpdate(msg lnwire.Message) {}
// ChanID returns the mockUpdateHandler's cid.
func (m *mockUpdateHandler) ChanID() lnwire.ChannelID { return m.cid }
// Bandwidth currently returns a dummy value.
func (m *mockUpdateHandler) Bandwidth() lnwire.MilliSatoshi { return 0 }
// EligibleToForward currently returns a dummy value.
func (m *mockUpdateHandler) EligibleToForward() bool { return false }
// MayAddOutgoingHtlc currently returns nil.
func (m *mockUpdateHandler) MayAddOutgoingHtlc(lnwire.MilliSatoshi) error { return nil }
type mockMessageConn struct {
t *testing.T
// MessageConn embeds our interface so that the mock does not need to
// implement every function. The mock will panic if an unspecified function
// is called.
MessageConn
// writtenMessages is a channel that our mock pushes written messages into.
writtenMessages chan []byte
readMessages chan []byte
curReadMessage []byte
// writeRaceDetectingCounter is incremented on any function call
// associated with writing to the connection. The race detector will
// trigger on this counter if a data race exists.
writeRaceDetectingCounter int
// readRaceDetectingCounter is incremented on any function call
// associated with reading from the connection. The race detector will
// trigger on this counter if a data race exists.
readRaceDetectingCounter int
}
func (m *mockUpdateHandler) EnableAdds(dir htlcswitch.LinkDirection) bool {
if dir == htlcswitch.Outgoing {
return m.isOutgoingAddBlocked.Swap(false)
}
return m.isIncomingAddBlocked.Swap(false)
}
func (m *mockUpdateHandler) DisableAdds(dir htlcswitch.LinkDirection) bool {
if dir == htlcswitch.Outgoing {
return !m.isOutgoingAddBlocked.Swap(true)
}
return !m.isIncomingAddBlocked.Swap(true)
}
func (m *mockUpdateHandler) IsFlushing(dir htlcswitch.LinkDirection) bool {
switch dir {
case htlcswitch.Outgoing:
return m.isOutgoingAddBlocked.Load()
case htlcswitch.Incoming:
return m.isIncomingAddBlocked.Load()
}
return false
}
func (m *mockUpdateHandler) OnFlushedOnce(hook func()) {
hook()
}
func (m *mockUpdateHandler) OnCommitOnce(
_ htlcswitch.LinkDirection, hook func(),
) {
hook()
}
func (m *mockUpdateHandler) InitStfu() <-chan fn.Result[lntypes.ChannelParty] {
// TODO(proofofkeags): Implement
c := make(chan fn.Result[lntypes.ChannelParty], 1)
c <- fn.Errf[lntypes.ChannelParty]("InitStfu not yet implemented")
return c
}
func newMockConn(t *testing.T, expectedMessages int) *mockMessageConn {
return &mockMessageConn{
t: t,
writtenMessages: make(chan []byte, expectedMessages),
readMessages: make(chan []byte, 1),
}
}
// SetWriteDeadline mocks setting write deadline for our conn.
func (m *mockMessageConn) SetWriteDeadline(time.Time) error {
m.writeRaceDetectingCounter++
return nil
}
// Flush mocks a message conn flush.
func (m *mockMessageConn) Flush() (int, error) {
m.writeRaceDetectingCounter++
return 0, nil
}
// WriteMessage mocks sending of a message on our connection. It will push
// the bytes sent into the mock's writtenMessages channel.
func (m *mockMessageConn) WriteMessage(msg []byte) error {
m.writeRaceDetectingCounter++
msgCopy := make([]byte, len(msg))
copy(msgCopy, msg)
select {
case m.writtenMessages <- msgCopy:
case <-time.After(timeout):
m.t.Fatalf("timeout sending message: %v", msgCopy)
}
return nil
}
// assertWrite asserts that our mock as had WriteMessage called with the byte
// slice we expect.
func (m *mockMessageConn) assertWrite(expected []byte) {
select {
case actual := <-m.writtenMessages:
require.Equal(m.t, expected, actual)
case <-time.After(timeout):
m.t.Fatalf("timeout waiting for write: %v", expected)
}
}
func (m *mockMessageConn) SetReadDeadline(t time.Time) error {
m.readRaceDetectingCounter++
return nil
}
func (m *mockMessageConn) ReadNextHeader() (uint32, error) {
m.readRaceDetectingCounter++
m.curReadMessage = <-m.readMessages
return uint32(len(m.curReadMessage)), nil
}
func (m *mockMessageConn) ReadNextBody(buf []byte) ([]byte, error) {
m.readRaceDetectingCounter++
return m.curReadMessage, nil
}
func (m *mockMessageConn) RemoteAddr() net.Addr {
return nil
}
func (m *mockMessageConn) LocalAddr() net.Addr {
return nil
}
func (m *mockMessageConn) Close() error {
return nil
}
// createTestPeer creates a new peer for testing and returns a context struct
// containing necessary handles and mock objects for conducting tests on peer
// functionalities.
func createTestPeer(t *testing.T) *peerTestCtx {
nodeKeyLocator := keychain.KeyLocator{
Family: keychain.KeyFamilyNodeKey,
}
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(
channels.AlicesPrivKey,
)
aliceKeySigner := keychain.NewPrivKeyMessageSigner(
aliceKeyPriv, nodeKeyLocator,
)
aliceAddr := &net.TCPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 18555,
}
cfgAddr := &lnwire.NetAddress{
IdentityKey: aliceKeyPub,
Address: aliceAddr,
ChainNet: wire.SimNet,
}
errBuffer, err := queue.NewCircularBuffer(ErrorBufferSize)
require.NoError(t, err)
chainIO := &mock.ChainIO{
BestHeight: broadcastHeight,
}
publishTx := make(chan *wire.MsgTx)
wallet := &lnwallet.LightningWallet{
WalletController: &mock.WalletController{
RootKey: aliceKeyPriv,
PublishedTransactions: publishTx,
},
}
const chanActiveTimeout = time.Minute
dbPath := t.TempDir()
graphBackend, err := kvdb.GetBoltBackend(&kvdb.BoltBackendConfig{
DBPath: dbPath,
DBFileName: "graph.db",
NoFreelistSync: true,
AutoCompact: false,
AutoCompactMinAge: kvdb.DefaultBoltAutoCompactMinAge,
DBTimeout: kvdb.DefaultDBTimeout,
})
require.NoError(t, err)
dbAliceGraph, err := graphdb.NewChannelGraph(&graphdb.Config{
KVDB: graphBackend,
})
require.NoError(t, err)
require.NoError(t, dbAliceGraph.Start())
t.Cleanup(func() {
require.NoError(t, dbAliceGraph.Stop())
})
dbAliceChannel := channeldb.OpenForTesting(t, dbPath)
nodeSignerAlice := netann.NewNodeSigner(aliceKeySigner)
chanStatusMgr, err := netann.NewChanStatusManager(&netann.
ChanStatusConfig{
ChanStatusSampleInterval: 30 * time.Second,
ChanEnableTimeout: chanActiveTimeout,
ChanDisableTimeout: 2 * time.Minute,
DB: dbAliceChannel.ChannelStateDB(),
Graph: dbAliceGraph,
MessageSigner: nodeSignerAlice,
OurPubKey: aliceKeyPub,
OurKeyLoc: testKeyLoc,
IsChannelActive: func(lnwire.ChannelID) bool {
return true
},
ApplyChannelUpdate: func(*lnwire.ChannelUpdate1,
*wire.OutPoint, bool) error {
return nil
},
})
require.NoError(t, err)
interceptableSwitchNotifier := &mock.ChainNotifier{
EpochChan: make(chan *chainntnfs.BlockEpoch, 1),
}
interceptableSwitchNotifier.EpochChan <- &chainntnfs.BlockEpoch{
Height: 1,
}
interceptableSwitch, err := htlcswitch.NewInterceptableSwitch(
&htlcswitch.InterceptableSwitchConfig{
CltvRejectDelta: testCltvRejectDelta,
CltvInterceptDelta: testCltvRejectDelta + 3,
Notifier: interceptableSwitchNotifier,
},
)
require.NoError(t, err)
// TODO(yy): create interface for lnwallet.LightningChannel so we can
// easily mock it without the following setups.
notifier := &mock.ChainNotifier{
SpendChan: make(chan *chainntnfs.SpendDetail),
EpochChan: make(chan *chainntnfs.BlockEpoch),
ConfChan: make(chan *chainntnfs.TxConfirmation),
}
mockSwitch := &mockMessageSwitch{}
// TODO(yy): change ChannelNotifier to be an interface.
channelNotifier := channelnotifier.New(dbAliceChannel.ChannelStateDB())
require.NoError(t, channelNotifier.Start())
t.Cleanup(func() {
require.NoError(t, channelNotifier.Stop(),
"stop channel notifier failed")
})
writeBufferPool := pool.NewWriteBuffer(
pool.DefaultWriteBufferGCInterval,
pool.DefaultWriteBufferExpiryInterval,
)
writePool := pool.NewWrite(
writeBufferPool, 1, timeout,
)
require.NoError(t, writePool.Start())
readBufferPool := pool.NewReadBuffer(
pool.DefaultReadBufferGCInterval,
pool.DefaultReadBufferExpiryInterval,
)
readPool := pool.NewRead(
readBufferPool, 1, timeout,
)
require.NoError(t, readPool.Start())
mockConn := newMockConn(t, 1)
receivedCustomChan := make(chan *customMsg)
var pubKey [33]byte
copy(pubKey[:], aliceKeyPub.SerializeCompressed())
estimator := chainfee.NewStaticEstimator(12500, 0)
cfg := &Config{
Addr: cfgAddr,
PubKeyBytes: pubKey,
ErrorBuffer: errBuffer,
ChainIO: chainIO,
Switch: mockSwitch,
ChanActiveTimeout: chanActiveTimeout,
InterceptSwitch: interceptableSwitch,
ChannelDB: dbAliceChannel.ChannelStateDB(),
FeeEstimator: estimator,
Wallet: wallet,
ChainNotifier: notifier,
ChanStatusMgr: chanStatusMgr,
Features: lnwire.NewFeatureVector(
nil, lnwire.Features,
),
DisconnectPeer: func(b *btcec.PublicKey) error {
return nil
},
ChannelNotifier: channelNotifier,
PrunePersistentPeerConnection: func([33]byte) {},
LegacyFeatures: lnwire.EmptyFeatureVector(),
WritePool: writePool,
ReadPool: readPool,
Conn: mockConn,
HandleCustomMessage: func(
peer [33]byte, msg *lnwire.Custom) error {
receivedCustomChan <- &customMsg{
peer: peer,
msg: *msg,
}
return nil
},
PongBuf: make([]byte, lnwire.MaxPongBytes),
FetchLastChanUpdate: func(chanID lnwire.ShortChannelID,
) (*lnwire.ChannelUpdate1, error) {
return &lnwire.ChannelUpdate1{}, nil
},
}
alicePeer := NewBrontide(*cfg)
return &peerTestCtx{
publishTx: publishTx,
mockSwitch: mockSwitch,
peer: alicePeer,
notifier: notifier,
db: dbAliceChannel,
privKey: aliceKeyPriv,
mockConn: mockConn,
customChan: receivedCustomChan,
chanStatusMgr: chanStatusMgr,
}
}
// startPeer invokes the `Start` method on the specified peer and handles any
// initial startup messages for testing.
func startPeer(t *testing.T, mockConn *mockMessageConn,
peer *Brontide) <-chan struct{} {
// Start the peer in a goroutine so that we can handle and test for
// startup messages. Successfully sending and receiving init message,
// indicates a successful startup.
done := make(chan struct{})
go func() {
require.NoError(t, peer.Start())
close(done)
}()
// Receive the init message that should be the first message received on
// startup.
rawMsg, err := fn.RecvOrTimeout[[]byte](
mockConn.writtenMessages, timeout,
)
require.NoError(t, err)
msgReader := bytes.NewReader(rawMsg)
nextMsg, err := lnwire.ReadMessage(msgReader, 0)
require.NoError(t, err)
_, ok := nextMsg.(*lnwire.Init)
require.True(t, ok)
// Write the reply for the init message to complete the startup.
initReplyMsg := lnwire.NewInitMessage(
lnwire.NewRawFeatureVector(
lnwire.DataLossProtectRequired,
lnwire.GossipQueriesOptional,
),
lnwire.NewRawFeatureVector(),
)
var b bytes.Buffer
_, err = lnwire.WriteMessage(&b, initReplyMsg, 0)
require.NoError(t, err)
ok = fn.SendOrQuit[[]byte, struct{}](
mockConn.readMessages, b.Bytes(), make(chan struct{}),
)
require.True(t, ok)
return done
}
package peernotifier
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("PRNF", nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package peernotifier
import (
"sync"
"github.com/lightningnetwork/lnd/subscribe"
)
// PeerNotifier is a subsystem which observes peer offline and online events.
// It takes subscriptions for its events, and whenever it observes a new event
// it notifies its subscribers over the proper channel.
type PeerNotifier struct {
started sync.Once
stopped sync.Once
ntfnServer *subscribe.Server
}
// PeerOnlineEvent represents a new event where a peer comes online.
type PeerOnlineEvent struct {
// PubKey is the peer's compressed public key.
PubKey [33]byte
}
// PeerOfflineEvent represents a new event where a peer goes offline.
type PeerOfflineEvent struct {
// PubKey is the peer's compressed public key.
PubKey [33]byte
}
// New creates a new peer notifier which notifies clients of peer online
// and offline events.
func New() *PeerNotifier {
return &PeerNotifier{
ntfnServer: subscribe.NewServer(),
}
}
// Start starts the PeerNotifier's subscription server.
func (p *PeerNotifier) Start() error {
var err error
p.started.Do(func() {
log.Info("PeerNotifier starting")
err = p.ntfnServer.Start()
})
return err
}
// Stop signals the notifier for a graceful shutdown.
func (p *PeerNotifier) Stop() error {
var err error
p.stopped.Do(func() {
log.Info("PeerNotifier shutting down...")
defer log.Debug("PeerNotifier shutdown complete")
err = p.ntfnServer.Stop()
})
return err
}
// SubscribePeerEvents returns a subscribe.Client that will receive updates
// any time the Server is informed of a peer event.
func (p *PeerNotifier) SubscribePeerEvents() (*subscribe.Client, error) {
return p.ntfnServer.Subscribe()
}
// NotifyPeerOnline sends a peer online event to all clients subscribed to the
// peer notifier.
func (p *PeerNotifier) NotifyPeerOnline(pubKey [33]byte) {
event := PeerOnlineEvent{PubKey: pubKey}
log.Debugf("PeerNotifier notifying peer: %x online", pubKey)
if err := p.ntfnServer.SendUpdate(event); err != nil {
log.Warnf("Unable to send peer online update: %v", err)
}
}
// NotifyPeerOffline sends a peer offline event to all the clients subscribed
// to the peer notifier.
func (p *PeerNotifier) NotifyPeerOffline(pubKey [33]byte) {
event := PeerOfflineEvent{PubKey: pubKey}
log.Debugf("PeerNotifier notifying peer: %x offline", pubKey)
if err := p.ntfnServer.SendUpdate(event); err != nil {
log.Warnf("Unable to send peer offline update: %v", err)
}
}
package lnd
import (
"errors"
"fmt"
"net"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/autopilot"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tor"
)
// validateAtplCfg is a helper method that makes sure the passed
// configuration is sane. Currently it checks that the heuristic configuration
// makes sense. In case the config is valid, it will return a list of
// WeightedHeuristics that can be combined for use with the autopilot agent.
func validateAtplCfg(cfg *lncfg.AutoPilot) ([]*autopilot.WeightedHeuristic,
error) {
var (
heuristicsStr string
sum float64
heuristics []*autopilot.WeightedHeuristic
)
// Create a help text that we can return in case the config is not
// correct.
for _, a := range autopilot.AvailableHeuristics {
heuristicsStr += fmt.Sprintf(" '%v' ", a.Name())
}
availStr := fmt.Sprintf("Available heuristics are: [%v]", heuristicsStr)
// We'll go through the config and make sure all the heuristics exists,
// and that the sum of their weights is 1.0.
for name, weight := range cfg.Heuristic {
a, ok := autopilot.AvailableHeuristics[name]
if !ok {
// No heuristic matching this config option was found.
return nil, fmt.Errorf("heuristic %v not available. %v",
name, availStr)
}
// If this heuristic was among the registered ones, we add it
// to the list we'll give to the agent, and keep track of the
// sum of weights.
heuristics = append(
heuristics,
&autopilot.WeightedHeuristic{
Weight: weight,
AttachmentHeuristic: a,
},
)
sum += weight
}
// Check found heuristics. We must have at least one to operate.
if len(heuristics) == 0 {
return nil, fmt.Errorf("no active heuristics: %v", availStr)
}
if sum != 1.0 {
return nil, fmt.Errorf("heuristic weights must sum to 1.0")
}
return heuristics, nil
}
// chanController is an implementation of the autopilot.ChannelController
// interface that's backed by a running lnd instance.
type chanController struct {
server *server
private bool
minConfs int32
confTarget uint32
chanMinHtlcIn lnwire.MilliSatoshi
netParams chainreg.BitcoinNetParams
}
// OpenChannel opens a channel to a target peer, with a capacity of the
// specified amount. This function should un-block immediately after the
// funding transaction that marks the channel open has been broadcast.
func (c *chanController) OpenChannel(target *btcec.PublicKey,
amt btcutil.Amount) error {
// With the connection established, we'll now establish our connection
// to the target peer, waiting for the first update before we exit.
feePerKw, err := c.server.cc.FeeEstimator.EstimateFeePerKW(
c.confTarget,
)
if err != nil {
return err
}
// Construct the open channel request and send it to the server to begin
// the funding workflow.
req := &funding.InitFundingMsg{
TargetPubkey: target,
ChainHash: *c.netParams.GenesisHash,
SubtractFees: true,
LocalFundingAmt: amt,
PushAmt: 0,
MinHtlcIn: c.chanMinHtlcIn,
FundingFeePerKw: feePerKw,
Private: c.private,
RemoteCsvDelay: 0,
MinConfs: c.minConfs,
MaxValueInFlight: 0,
}
updateStream, errChan := c.server.OpenChannel(req)
select {
case err := <-errChan:
return err
case <-updateStream:
return nil
case <-c.server.quit:
return nil
}
}
func (c *chanController) CloseChannel(chanPoint *wire.OutPoint) error {
return nil
}
// A compile time assertion to ensure chanController meets the
// autopilot.ChannelController interface.
var _ autopilot.ChannelController = (*chanController)(nil)
// initAutoPilot initializes a new autopilot.ManagerCfg to manage an autopilot.
// Agent instance based on the passed configuration structs. The agent and all
// interfaces needed to drive it won't be launched before the Manager's
// StartAgent method is called.
func initAutoPilot(svr *server, cfg *lncfg.AutoPilot,
minHTLCIn lnwire.MilliSatoshi, netParams chainreg.BitcoinNetParams) (
*autopilot.ManagerCfg, error) {
atplLog.Infof("Instantiating autopilot with active=%v, "+
"max_channels=%d, allocation=%f, min_chan_size=%d, "+
"max_chan_size=%d, private=%t, min_confs=%d, conf_target=%d",
cfg.Active, cfg.MaxChannels, cfg.Allocation, cfg.MinChannelSize,
cfg.MaxChannelSize, cfg.Private, cfg.MinConfs, cfg.ConfTarget)
// Set up the constraints the autopilot heuristics must adhere to.
atplConstraints := autopilot.NewConstraints(
btcutil.Amount(cfg.MinChannelSize),
btcutil.Amount(cfg.MaxChannelSize),
uint16(cfg.MaxChannels),
10,
cfg.Allocation,
)
heuristics, err := validateAtplCfg(cfg)
if err != nil {
return nil, err
}
weightedAttachment, err := autopilot.NewWeightedCombAttachment(
heuristics...,
)
if err != nil {
return nil, err
}
// With the heuristic itself created, we can now populate the remainder
// of the items that the autopilot agent needs to perform its duties.
self := svr.identityECDH.PubKey()
pilotCfg := autopilot.Config{
Self: self,
Heuristic: weightedAttachment,
ChanController: &chanController{
server: svr,
private: cfg.Private,
minConfs: cfg.MinConfs,
confTarget: cfg.ConfTarget,
chanMinHtlcIn: minHTLCIn,
netParams: netParams,
},
WalletBalance: func() (btcutil.Amount, error) {
return svr.cc.Wallet.ConfirmedBalance(
cfg.MinConfs, lnwallet.DefaultAccountName,
)
},
Graph: autopilot.ChannelGraphFromDatabase(svr.graphDB),
Constraints: atplConstraints,
ConnectToPeer: func(target *btcec.PublicKey, addrs []net.Addr) (bool, error) {
// First, we'll check if we're already connected to the
// target peer. If we are, we can exit early. Otherwise,
// we'll need to establish a connection.
if _, err := svr.FindPeer(target); err == nil {
return true, nil
}
// We can't establish a channel if no addresses were
// provided for the peer.
if len(addrs) == 0 {
return false, errors.New("no addresses specified")
}
atplLog.Tracef("Attempting to connect to %x",
target.SerializeCompressed())
lnAddr := &lnwire.NetAddress{
IdentityKey: target,
ChainNet: netParams.Net,
}
// We'll attempt to successively connect to each of the
// advertised IP addresses until we've either exhausted
// the advertised IP addresses, or have made a
// connection.
var connected bool
for _, addr := range addrs {
switch addr.(type) {
case *net.TCPAddr, *tor.OnionAddr:
lnAddr.Address = addr
default:
return false, fmt.Errorf("unknown "+
"address type %T", addr)
}
err := svr.ConnectToPeer(
lnAddr, false, svr.cfg.ConnectionTimeout,
)
if err != nil {
// If we weren't able to connect to the
// peer at this address, then we'll move
// onto the next.
continue
}
connected = true
break
}
// If we weren't able to establish a connection at all,
// then we'll error out.
if !connected {
return false, errors.New("exhausted all " +
"advertised addresses")
}
return false, nil
},
DisconnectPeer: svr.DisconnectPeer,
}
// Create and return the autopilot.ManagerCfg that administrates this
// agent-pilot instance.
return &autopilot.ManagerCfg{
Self: self,
PilotCfg: &pilotCfg,
ChannelState: func() ([]autopilot.LocalChannel, error) {
// We'll fetch the current state of open
// channels from the database to use as initial
// state for the auto-pilot agent.
activeChannels, err := svr.chanStateDB.FetchAllChannels()
if err != nil {
return nil, err
}
chanState := make([]autopilot.LocalChannel,
len(activeChannels))
for i, channel := range activeChannels {
localCommit := channel.LocalCommitment
balance := localCommit.LocalBalance.ToSatoshis()
chanState[i] = autopilot.LocalChannel{
ChanID: channel.ShortChanID(),
Balance: balance,
Node: autopilot.NewNodeID(
channel.IdentityPub,
),
}
}
return chanState, nil
},
ChannelInfo: func(chanPoint wire.OutPoint) (
*autopilot.LocalChannel, error) {
channel, err := svr.chanStateDB.FetchChannel(chanPoint)
if err != nil {
return nil, err
}
localCommit := channel.LocalCommitment
return &autopilot.LocalChannel{
ChanID: channel.ShortChanID(),
Balance: localCommit.LocalBalance.ToSatoshis(),
Node: autopilot.NewNodeID(channel.IdentityPub),
}, nil
},
SubscribeTransactions: svr.cc.Wallet.SubscribeTransactions,
SubscribeTopology: svr.graphDB.SubscribeTopology,
}, nil
}
package pool
import (
"time"
"github.com/lightningnetwork/lnd/buffer"
)
// Read is a worker pool specifically designed for sharing access to buffer.Read
// objects amongst a set of worker goroutines. This enables an application to
// limit the total number of buffer.Read objects allocated at any given time.
type Read struct {
workerPool *Worker
bufferPool *ReadBuffer
}
// NewRead creates a new Read pool, using an underlying ReadBuffer pool to
// recycle buffer.Read objects across the lifetime of the Read pool's workers.
func NewRead(readBufferPool *ReadBuffer, numWorkers int,
workerTimeout time.Duration) *Read {
r := &Read{
bufferPool: readBufferPool,
}
r.workerPool = NewWorker(&WorkerConfig{
NewWorkerState: r.newWorkerState,
NumWorkers: numWorkers,
WorkerTimeout: workerTimeout,
})
return r
}
// Start safely spins up the Read pool.
func (r *Read) Start() error {
return r.workerPool.Start()
}
// Stop safely shuts down the Read pool.
func (r *Read) Stop() error {
return r.workerPool.Stop()
}
// Submit accepts a function closure that provides access to the fresh
// buffer.Read object. The function's execution will be allocated to one of the
// underlying Worker pool's goroutines.
func (r *Read) Submit(inner func(*buffer.Read) error) error {
return r.workerPool.Submit(func(s WorkerState) error {
state := s.(*readWorkerState)
return inner(state.readBuf)
})
}
// readWorkerState is the per-goroutine state maintained by a Read pool's
// goroutines.
type readWorkerState struct {
// bufferPool is the pool to which the readBuf will be returned when the
// goroutine exits.
bufferPool *ReadBuffer
// readBuf is a buffer taken from the bufferPool on initialization,
// which will be cleaned and provided to any tasks that the goroutine
// processes before exiting.
readBuf *buffer.Read
}
// newWorkerState initializes a new readWorkerState, which will be called
// whenever a new goroutine is allocated to begin processing read tasks.
func (r *Read) newWorkerState() WorkerState {
return &readWorkerState{
bufferPool: r.bufferPool,
readBuf: r.bufferPool.Take(),
}
}
// Cleanup returns the readBuf to the underlying buffer pool, and removes the
// goroutine's reference to the readBuf.
func (r *readWorkerState) Cleanup() {
r.bufferPool.Return(r.readBuf)
r.readBuf = nil
}
// Reset recycles the readBuf to make it ready for any subsequent tasks the
// goroutine may process.
func (r *readWorkerState) Reset() {
r.readBuf.Recycle()
}
package pool
import (
"time"
"github.com/lightningnetwork/lnd/buffer"
)
const (
// DefaultReadBufferGCInterval is the default interval that a Read will
// perform a sweep to see which expired buffer.Reads can be released to
// the runtime.
DefaultReadBufferGCInterval = 15 * time.Second
// DefaultReadBufferExpiryInterval is the default, minimum interval that
// must elapse before a Read will release a buffer.Read. The maximum
// time before the buffer can be released is equal to the expiry
// interval plus the gc interval.
DefaultReadBufferExpiryInterval = 30 * time.Second
)
// ReadBuffer is a pool of buffer.Read items, that dynamically allocates and
// reclaims buffers in response to load.
type ReadBuffer struct {
pool *Recycle
}
// NewReadBuffer returns a freshly instantiated ReadBuffer, using the given
// gcInterval and expiryInterval.
func NewReadBuffer(gcInterval, expiryInterval time.Duration) *ReadBuffer {
return &ReadBuffer{
pool: NewRecycle(
func() interface{} { return new(buffer.Read) },
100, gcInterval, expiryInterval,
),
}
}
// Take returns a fresh buffer.Read to the caller.
func (p *ReadBuffer) Take() *buffer.Read {
return p.pool.Take().(*buffer.Read)
}
// Return returns the buffer.Read to the pool, so that it can be cycled or
// released.
func (p *ReadBuffer) Return(buf *buffer.Read) {
p.pool.Return(buf)
}
package pool
import (
"time"
"github.com/lightningnetwork/lnd/queue"
)
// Recycler is an interface that allows an object to be reclaimed without
// needing to be returned to the runtime.
type Recycler interface {
// Recycle resets the object to its default state.
Recycle()
}
// Recycle is a generic queue for recycling objects implementing the Recycler
// interface. It is backed by an underlying queue.GCQueue, and invokes the
// Recycle method on returned objects before returning them to the queue.
type Recycle struct {
queue *queue.GCQueue
}
// NewRecycle initializes a fresh Recycle instance.
func NewRecycle(newItem func() interface{}, returnQueueSize int,
gcInterval, expiryInterval time.Duration) *Recycle {
return &Recycle{
queue: queue.NewGCQueue(
newItem, returnQueueSize,
gcInterval, expiryInterval,
),
}
}
// Take returns an element from the pool.
func (r *Recycle) Take() interface{} {
return r.queue.Take()
}
// Return returns an item implementing the Recycler interface to the pool. The
// Recycle method is invoked before returning the item to improve performance
// and utilization under load.
func (r *Recycle) Return(item Recycler) {
// Recycle the item to ensure that a dirty instance is never offered
// from Take. The call is done here so that the CPU cycles spent
// clearing the buffer are owned by the caller, and not by the queue
// itself. This makes the queue more likely to be available to deliver
// items in the free list.
item.Recycle()
r.queue.Return(item)
}
package pool
import (
"errors"
"sync"
"time"
)
// ErrWorkerPoolExiting signals that a shutdown of the Worker has been
// requested.
var ErrWorkerPoolExiting = errors.New("worker pool exiting")
// DefaultWorkerTimeout is the default duration after which a worker goroutine
// will exit to free up resources after having received no newly submitted
// tasks.
const DefaultWorkerTimeout = 90 * time.Second
type (
// WorkerState is an interface used by the Worker to abstract the
// lifecycle of internal state used by a worker goroutine.
WorkerState interface {
// Reset clears any internal state that may have been dirtied in
// processing a prior task.
Reset()
// Cleanup releases any shared state before a worker goroutine
// exits.
Cleanup()
}
// WorkerConfig parametrizes the behavior of a Worker pool.
WorkerConfig struct {
// NewWorkerState allocates a new state for a worker goroutine.
// This method is called each time a new worker goroutine is
// spawned by the pool.
NewWorkerState func() WorkerState
// NumWorkers is the maximum number of workers the Worker pool
// will permit to be allocated. Once the maximum number is
// reached, any newly submitted tasks are forced to be processed
// by existing worker goroutines.
NumWorkers int
// WorkerTimeout is the duration after which a worker goroutine
// will exit after having received no newly submitted tasks.
WorkerTimeout time.Duration
}
// Worker maintains a pool of goroutines that process submitted function
// closures, and enable more efficient reuse of expensive state.
Worker struct {
started sync.Once
stopped sync.Once
cfg *WorkerConfig
// requests is a channel where new tasks are submitted. Tasks
// submitted through this channel may cause a new worker
// goroutine to be allocated.
requests chan *request
// work is a channel where new tasks are submitted, but is only
// read by active worker goroutines.
work chan *request
// workerSem is a channel-based semaphore that is used to limit
// the total number of worker goroutines to the number
// prescribed by the WorkerConfig.
workerSem chan struct{}
wg sync.WaitGroup
quit chan struct{}
}
// request is a tuple of task closure and error channel that is used to
// both submit a task to the pool and respond with any errors
// encountered during the task's execution.
request struct {
fn func(WorkerState) error
errChan chan error
}
)
// NewWorker initializes a new Worker pool using the provided WorkerConfig.
func NewWorker(cfg *WorkerConfig) *Worker {
return &Worker{
cfg: cfg,
requests: make(chan *request),
workerSem: make(chan struct{}, cfg.NumWorkers),
work: make(chan *request),
quit: make(chan struct{}),
}
}
// Start safely spins up the Worker pool.
func (w *Worker) Start() error {
w.started.Do(func() {
w.wg.Add(1)
go w.requestHandler()
})
return nil
}
// Stop safely shuts down the Worker pool.
func (w *Worker) Stop() error {
w.stopped.Do(func() {
close(w.quit)
w.wg.Wait()
})
return nil
}
// Submit accepts a function closure to the worker pool. The returned error will
// be either the result of the closure's execution or an ErrWorkerPoolExiting if
// a shutdown is requested.
func (w *Worker) Submit(fn func(WorkerState) error) error {
req := &request{
fn: fn,
errChan: make(chan error, 1),
}
select {
// Send request to requestHandler, where either a new worker is spawned
// or the task will be handed to an existing worker.
case w.requests <- req:
// Fast path directly to existing worker.
case w.work <- req:
case <-w.quit:
return ErrWorkerPoolExiting
}
select {
// Wait for task to be processed.
case err := <-req.errChan:
return err
case <-w.quit:
return ErrWorkerPoolExiting
}
}
// requestHandler processes incoming tasks by either allocating new worker
// goroutines to process the incoming tasks, or by feeding a submitted task to
// an already running worker goroutine.
func (w *Worker) requestHandler() {
defer w.wg.Done()
for {
select {
case req := <-w.requests:
select {
// If we have not reached our maximum number of workers,
// spawn one to process the submitted request.
case w.workerSem <- struct{}{}:
w.wg.Add(1)
go w.spawnWorker(req)
// Otherwise, submit the task to any of the active
// workers.
case w.work <- req:
case <-w.quit:
return
}
case <-w.quit:
return
}
}
}
// spawnWorker is used when the Worker pool wishes to create a new worker
// goroutine. The worker's state is initialized by calling the config's
// NewWorkerState method, and will continue to process incoming tasks until the
// pool is shut down or no new tasks are received before the worker's timeout
// elapses.
//
// NOTE: This method MUST be run as a goroutine.
func (w *Worker) spawnWorker(req *request) {
defer w.wg.Done()
defer func() { <-w.workerSem }()
state := w.cfg.NewWorkerState()
defer state.Cleanup()
req.errChan <- req.fn(state)
// We'll use a timer to implement the worker timeouts, as this reduces
// the number of total allocations that would otherwise be necessary
// with time.After.
var t *time.Timer
for {
// Before processing another request, we'll reset the worker
// state to that each request is processed against a clean
// state.
state.Reset()
select {
// Process any new requests that get submitted. We use a
// non-blocking case first so that under high load we can spare
// allocating a timeout.
case req := <-w.work:
req.errChan <- req.fn(state)
continue
case <-w.quit:
return
default:
}
// There were no new requests that could be taken immediately
// from the work channel. Initialize or reset the timeout, which
// will fire if the worker doesn't receive a new task before
// needing to exit.
if t != nil {
t.Reset(w.cfg.WorkerTimeout)
} else {
t = time.NewTimer(w.cfg.WorkerTimeout)
}
select {
// Process any new requests that get submitted.
case req := <-w.work:
req.errChan <- req.fn(state)
// Stop the timer, draining the timer's channel if a
// notification was already delivered.
if !t.Stop() {
<-t.C
}
// The timeout has elapsed, meaning the worker did not receive
// any new tasks. Exit to allow the worker to return and free
// its resources.
case <-t.C:
return
case <-w.quit:
return
}
}
}
package pool
import (
"bytes"
"time"
"github.com/lightningnetwork/lnd/buffer"
)
// Write is a worker pool specifically designed for sharing access to
// buffer.Write objects amongst a set of worker goroutines. This enables an
// application to limit the total number of buffer.Write objects allocated at
// any given time.
type Write struct {
workerPool *Worker
bufferPool *WriteBuffer
}
// NewWrite creates a Write pool, using an underlying Writebuffer pool to
// recycle buffer.Write objects across the lifetime of the Write pool's
// workers.
func NewWrite(writeBufferPool *WriteBuffer, numWorkers int,
workerTimeout time.Duration) *Write {
w := &Write{
bufferPool: writeBufferPool,
}
w.workerPool = NewWorker(&WorkerConfig{
NewWorkerState: w.newWorkerState,
NumWorkers: numWorkers,
WorkerTimeout: workerTimeout,
})
return w
}
// Start safely spins up the Write pool.
func (w *Write) Start() error {
return w.workerPool.Start()
}
// Stop safely shuts down the Write pool.
func (w *Write) Stop() error {
return w.workerPool.Stop()
}
// Submit accepts a function closure that provides access to a fresh
// bytes.Buffer backed by a buffer.Write object. The function's execution will
// be allocated to one of the underlying Worker pool's goroutines.
func (w *Write) Submit(inner func(*bytes.Buffer) error) error {
return w.workerPool.Submit(func(s WorkerState) error {
state := s.(*writeWorkerState)
return inner(state.buf)
})
}
// writeWorkerState is the per-goroutine state maintained by a Write pool's
// goroutines.
type writeWorkerState struct {
// bufferPool is the pool to which the writeBuf will be returned when
// the goroutine exits.
bufferPool *WriteBuffer
// writeBuf is the buffer taken from the bufferPool on initialization,
// which will be used to back the buf object provided to any tasks that
// the goroutine processes before exiting.
writeBuf *buffer.Write
// buf is a buffer backed by writeBuf, that can be written to by tasks
// submitted to the Write pool. The buf will be reset between each task
// processed by a goroutine before exiting, and allows the task
// submitters to interact with the writeBuf as if it were an io.Writer.
buf *bytes.Buffer
}
// newWorkerState initializes a new writeWorkerState, which will be called
// whenever a new goroutine is allocated to begin processing write tasks.
func (w *Write) newWorkerState() WorkerState {
writeBuf := w.bufferPool.Take()
return &writeWorkerState{
bufferPool: w.bufferPool,
writeBuf: writeBuf,
buf: bytes.NewBuffer(writeBuf[0:0:len(writeBuf)]),
}
}
// Cleanup returns the writeBuf to the underlying buffer pool, and removes the
// goroutine's reference to the writeBuf and encapsulating buf.
func (w *writeWorkerState) Cleanup() {
w.bufferPool.Return(w.writeBuf)
w.writeBuf = nil
w.buf = nil
}
// Reset resets the bytes.Buffer so that it is zero-length and has the capacity
// of the underlying buffer.Write.k
func (w *writeWorkerState) Reset() {
w.buf.Reset()
}
package pool
import (
"time"
"github.com/lightningnetwork/lnd/buffer"
)
const (
// DefaultWriteBufferGCInterval is the default interval that a Write
// will perform a sweep to see which expired buffer.Writes can be
// released to the runtime.
DefaultWriteBufferGCInterval = 15 * time.Second
// DefaultWriteBufferExpiryInterval is the default, minimum interval
// that must elapse before a Write will release a buffer.Write. The
// maximum time before the buffer can be released is equal to the expiry
// interval plus the gc interval.
DefaultWriteBufferExpiryInterval = 30 * time.Second
)
// WriteBuffer is a pool of recycled buffer.Write items, that dynamically
// allocates and reclaims buffers in response to load.
type WriteBuffer struct {
pool *Recycle
}
// NewWriteBuffer returns a freshly instantiated WriteBuffer, using the given
// gcInterval and expiryIntervals.
func NewWriteBuffer(gcInterval, expiryInterval time.Duration) *WriteBuffer {
return &WriteBuffer{
pool: NewRecycle(
func() interface{} { return new(buffer.Write) },
100, gcInterval, expiryInterval,
),
}
}
// Take returns a fresh buffer.Write to the caller.
func (p *WriteBuffer) Take() *buffer.Write {
return p.pool.Take().(*buffer.Write)
}
// Return returns the buffer.Write to the pool, so that it can be recycled or
// released.
func (p *WriteBuffer) Return(buf *buffer.Write) {
p.pool.Return(buf)
}
package protofsm
import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnwire"
)
// DaemonEvent is a special event that can be emitted by a state transition
// function. A state machine can use this to perform side effects, such as
// sending a message to a peer, or broadcasting a transaction.
type DaemonEvent interface {
daemonSealed()
}
// DaemonEventSet is a set of daemon events that can be emitted by a state
// transition.
type DaemonEventSet []DaemonEvent
// DaemonEvents is a special type constraint that enumerates all the possible
// types of daemon events.
type DaemonEvents interface {
SendMsgEvent[any] | BroadcastTxn | RegisterSpend[any] |
RegisterConf[any]
}
// SendPredicate is a function that returns true if the target message should
// sent.
type SendPredicate = func() bool
// SendMsgEvent is a special event that can be emitted by a state transition
// that instructs the daemon to send the contained message to the target peer.
type SendMsgEvent[Event any] struct {
// TargetPeer is the peer to send the message to.
TargetPeer btcec.PublicKey
// Msgs is the set of messages to send to the target peer.
Msgs []lnwire.Message
// SendWhen implements a system for a conditional send once a special
// send predicate has been met.
//
// TODO(roasbeef): contrast with usage of OnCommitFlush, etc
SendWhen fn.Option[SendPredicate]
// PostSendEvent is an optional event that is to be emitted after the
// message has been sent. If a SendWhen is specified, then this will
// only be executed after that returns true to unblock the send.
PostSendEvent fn.Option[Event]
}
// daemonSealed indicates that this struct is a DaemonEvent instance.
func (s *SendMsgEvent[E]) daemonSealed() {}
// BroadcastTxn indicates the target transaction should be broadcast to the
// network.
type BroadcastTxn struct {
// Tx is the transaction to broadcast.
Tx *wire.MsgTx
// Label is an optional label to attach to the transaction.
Label string
}
// daemonSealed indicates that this struct is a DaemonEvent instance.
func (b *BroadcastTxn) daemonSealed() {}
// SpendMapper is a function that's used to map a spend notification to a
// custom state machine event.
type SpendMapper[Event any] func(*chainntnfs.SpendDetail) Event
// RegisterSpend is used to request that a certain event is sent into the state
// machine once the specified outpoint has been spent.
type RegisterSpend[Event any] struct {
// OutPoint is the outpoint on chain to watch.
OutPoint wire.OutPoint
// PkScript is the script that we expect to be spent along with the
// outpoint.
PkScript []byte
// HeightHint is a value used to give the chain scanner a hint on how
// far back it needs to start its search.
HeightHint uint32
// PostSpendEvent is a special spend mapper, that if present, will be
// used to map the protofsm spend event to a custom event.
PostSpendEvent fn.Option[SpendMapper[Event]]
}
// daemonSealed indicates that this struct is a DaemonEvent instance.
func (r *RegisterSpend[E]) daemonSealed() {}
// RegisterConf is used to request that a certain event is sent into the state
// machien once the specified outpoint has been spent.
type RegisterConf[Event any] struct {
// Txid is the txid of the txn we want to watch the chain for.
Txid chainhash.Hash
// PkScript is the script that we expect to be created along with the
// outpoint.
PkScript []byte
// HeightHint is a value used to give the chain scanner a hint on how
// far back it needs to start its search.
HeightHint uint32
// NumConfs is the number of confirmations that the spending
// transaction needs to dispatch an event.
NumConfs fn.Option[uint32]
// PostConfEvent is an event that's sent back to the requester once the
// transaction specified above has confirmed in the chain with
// sufficient depth.
PostConfEvent fn.Option[Event]
}
// daemonSealed indicates that this struct is a DaemonEvent instance.
func (r *RegisterConf[E]) daemonSealed() {}
package protofsm
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("PFSM", nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package protofsm
import (
"context"
"fmt"
"sync"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/msgmux"
)
const (
// pollInterval is the interval at which we'll poll the SendWhen
// predicate if specified.
pollInterval = time.Millisecond * 100
)
var (
// ErrStateMachineShutdown occurs when trying to feed an event to a
// StateMachine that has been asked to Stop.
ErrStateMachineShutdown = fmt.Errorf("StateMachine is shutting down")
)
// EmittedEvent is a special type that can be emitted by a state transition.
// This can container internal events which are to be routed back to the state,
// or external events which are to be sent to the daemon.
type EmittedEvent[Event any] struct {
// InternalEvent is an optional internal event that is to be routed
// back to the target state. This enables state to trigger one or many
// state transitions without a new external event.
InternalEvent []Event
// ExternalEvent is an optional external event that is to be sent to
// the daemon for dispatch. Usually, this is some form of I/O.
ExternalEvents DaemonEventSet
}
// StateTransition is a state transition type. It denotes the next state to go
// to, and also the set of events to emit.
type StateTransition[Event any, Env Environment] struct {
// NextState is the next state to transition to.
NextState State[Event, Env]
// NewEvents is the set of events to emit.
NewEvents fn.Option[EmittedEvent[Event]]
}
// Environment is an abstract interface that represents the environment that
// the state machine will execute using. From the PoV of the main state machine
// executor, we just care about being able to clean up any resources that were
// allocated by the environment.
type Environment interface {
// Name returns the name of the environment. This is used to uniquely
// identify the environment of related state machines.
Name() string
}
// State defines an abstract state along, namely its state transition function
// that takes as input an event and an environment, and returns a state
// transition (next state, and set of events to emit). As state can also either
// be terminal, or not, a terminal event causes state execution to halt.
type State[Event any, Env Environment] interface {
// ProcessEvent takes an event and an environment, and returns a new
// state transition. This will be iteratively called until either a
// terminal state is reached, or no further internal events are
// emitted.
ProcessEvent(event Event, env Env) (*StateTransition[Event, Env], error)
// IsTerminal returns true if this state is terminal, and false
// otherwise.
IsTerminal() bool
// String returns a human readable string that represents the state.
String() string
}
// DaemonAdapters is a set of methods that server as adapters to bridge the
// pure world of the FSM to the real world of the daemon. These will be used to
// do things like broadcast transactions, or send messages to peers.
type DaemonAdapters interface {
// SendMessages sends the target set of messages to the target peer.
SendMessages(btcec.PublicKey, []lnwire.Message) error
// BroadcastTransaction broadcasts a transaction with the target label.
BroadcastTransaction(*wire.MsgTx, string) error
// RegisterConfirmationsNtfn registers an intent to be notified once
// txid reaches numConfs confirmations. We also pass in the pkScript as
// the default light client instead needs to match on scripts created
// in the block. If a nil txid is passed in, then not only should we
// match on the script, but we should also dispatch once the
// transaction containing the script reaches numConfs confirmations.
// This can be useful in instances where we only know the script in
// advance, but not the transaction containing it.
//
// TODO(roasbeef): could abstract further?
RegisterConfirmationsNtfn(txid *chainhash.Hash, pkScript []byte,
numConfs, heightHint uint32,
opts ...chainntnfs.NotifierOption,
) (*chainntnfs.ConfirmationEvent, error)
// RegisterSpendNtfn registers an intent to be notified once the target
// outpoint is successfully spent within a transaction. The script that
// the outpoint creates must also be specified. This allows this
// interface to be implemented by BIP 158-like filtering.
RegisterSpendNtfn(outpoint *wire.OutPoint, pkScript []byte,
heightHint uint32) (*chainntnfs.SpendEvent, error)
}
// stateQuery is used by outside callers to query the internal state of the
// state machine.
type stateQuery[Event any, Env Environment] struct {
// CurrentState is a channel that will be sent the current state of the
// state machine.
CurrentState chan State[Event, Env]
}
// StateMachine represents an abstract FSM that is able to process new incoming
// events and drive a state machine to termination. This implementation uses
// type params to abstract over the types of events and environment. Events
// trigger new state transitions, that use the environment to perform some
// action.
//
// TODO(roasbeef): terminal check, daemon event execution, init?
type StateMachine[Event any, Env Environment] struct {
cfg StateMachineCfg[Event, Env]
log btclog.Logger
// events is the channel that will be used to send new events to the
// FSM.
events chan Event
// newStateEvents is an EventDistributor that will be used to notify
// any relevant callers of new state transitions that occur.
newStateEvents *fn.EventDistributor[State[Event, Env]]
// stateQuery is a channel that will be used by outside callers to
// query the internal state machine state.
stateQuery chan stateQuery[Event, Env]
gm fn.GoroutineManager
quit chan struct{}
startOnce sync.Once
stopOnce sync.Once
}
// ErrorReporter is an interface that's used to report errors that occur during
// state machine execution.
type ErrorReporter interface {
// ReportError is a method that's used to report an error that occurred
// during state machine execution.
ReportError(err error)
}
// StateMachineCfg is a configuration struct that's used to create a new state
// machine.
type StateMachineCfg[Event any, Env Environment] struct {
// ErrorReporter is used to report errors that occur during state
// transitions.
ErrorReporter ErrorReporter
// Daemon is a set of adapters that will be used to bridge the FSM to
// the daemon.
Daemon DaemonAdapters
// InitialState is the initial state of the state machine.
InitialState State[Event, Env]
// Env is the environment that the state machine will use to execute.
Env Env
// InitEvent is an optional event that will be sent to the state
// machine as if it was emitted at the onset of the state machine. This
// can be used to set up tracking state such as a txid confirmation
// event.
InitEvent fn.Option[DaemonEvent]
// MsgMapper is an optional message mapper that can be used to map
// normal wire messages into FSM events.
MsgMapper fn.Option[MsgMapper[Event]]
// CustomPollInterval is an optional custom poll interval that can be
// used to set a quicker interval for tests.
CustomPollInterval fn.Option[time.Duration]
}
// NewStateMachine creates a new state machine given a set of daemon adapters,
// an initial state, an environment, and an event to process as if emitted at
// the onset of the state machine. Such an event can be used to set up tracking
// state such as a txid confirmation event.
func NewStateMachine[Event any, Env Environment](
cfg StateMachineCfg[Event, Env]) StateMachine[Event, Env] {
return StateMachine[Event, Env]{
cfg: cfg,
log: log.WithPrefix(
fmt.Sprintf("FSM(%v):", cfg.Env.Name()),
),
events: make(chan Event, 1),
stateQuery: make(chan stateQuery[Event, Env]),
gm: *fn.NewGoroutineManager(),
newStateEvents: fn.NewEventDistributor[State[Event, Env]](),
quit: make(chan struct{}),
}
}
// Start starts the state machine. This will spawn a goroutine that will drive
// the state machine to completion.
func (s *StateMachine[Event, Env]) Start(ctx context.Context) {
s.startOnce.Do(func() {
_ = s.gm.Go(ctx, func(ctx context.Context) {
s.driveMachine(ctx)
})
})
}
// Stop stops the state machine. This will block until the state machine has
// reached a stopping point.
func (s *StateMachine[Event, Env]) Stop() {
s.stopOnce.Do(func() {
close(s.quit)
s.gm.Stop()
})
}
// SendEvent sends a new event to the state machine.
//
// TODO(roasbeef): bool if processed?
func (s *StateMachine[Event, Env]) SendEvent(ctx context.Context, event Event) {
s.log.Debugf("Sending event %T", event)
select {
case s.events <- event:
case <-ctx.Done():
return
case <-s.quit:
return
}
}
// CanHandle returns true if the target message can be routed to the state
// machine.
func (s *StateMachine[Event, Env]) CanHandle(msg msgmux.PeerMsg) bool {
cfgMapper := s.cfg.MsgMapper
return fn.MapOptionZ(cfgMapper, func(mapper MsgMapper[Event]) bool {
return mapper.MapMsg(msg).IsSome()
})
}
// Name returns the name of the state machine's environment.
func (s *StateMachine[Event, Env]) Name() string {
return s.cfg.Env.Name()
}
// SendMessage attempts to send a wire message to the state machine. If the
// message can be mapped using the default message mapper, then true is
// returned indicating that the message was processed. Otherwise, false is
// returned.
func (s *StateMachine[Event, Env]) SendMessage(ctx context.Context,
msg msgmux.PeerMsg) bool {
// If we have no message mapper, then return false as we can't process
// this message.
if !s.cfg.MsgMapper.IsSome() {
return false
}
s.log.DebugS(ctx, "Sending msg", "msg", lnutils.SpewLogClosure(msg))
// Otherwise, try to map the message using the default message mapper.
// If we can't extract an event, then we'll return false to indicate
// that the message wasn't processed.
var processed bool
s.cfg.MsgMapper.WhenSome(func(mapper MsgMapper[Event]) {
event := mapper.MapMsg(msg)
event.WhenSome(func(event Event) {
s.SendEvent(ctx, event)
processed = true
})
})
return processed
}
// CurrentState returns the current state of the state machine.
func (s *StateMachine[Event, Env]) CurrentState() (State[Event, Env], error) {
query := stateQuery[Event, Env]{
CurrentState: make(chan State[Event, Env], 1),
}
if !fn.SendOrQuit(s.stateQuery, query, s.quit) {
return nil, ErrStateMachineShutdown
}
return fn.RecvOrTimeout(query.CurrentState, time.Second)
}
// StateSubscriber represents an active subscription to be notified of new
// state transitions.
type StateSubscriber[E any, F Environment] *fn.EventReceiver[State[E, F]]
// RegisterStateEvents registers a new event listener that will be notified of
// new state transitions.
func (s *StateMachine[Event, Env]) RegisterStateEvents() StateSubscriber[
Event, Env] {
subscriber := fn.NewEventReceiver[State[Event, Env]](10)
// TODO(roasbeef): instead give the state and the input event?
s.newStateEvents.RegisterSubscriber(subscriber)
return subscriber
}
// RemoveStateSub removes the target state subscriber from the set of active
// subscribers.
func (s *StateMachine[Event, Env]) RemoveStateSub(sub StateSubscriber[
Event, Env]) {
_ = s.newStateEvents.RemoveSubscriber(sub)
}
// executeDaemonEvent executes a daemon event, which is a special type of event
// that can be emitted as part of the state transition function of the state
// machine. An error is returned if the type of event is unknown.
func (s *StateMachine[Event, Env]) executeDaemonEvent(ctx context.Context,
event DaemonEvent) error {
switch daemonEvent := event.(type) {
// This is a send message event, so we'll send the event, and also mind
// any preconditions as well as post-send events.
case *SendMsgEvent[Event]:
sendAndCleanUp := func() error {
s.log.DebugS(ctx, "Sending message:",
btclog.Hex6("target", daemonEvent.TargetPeer.SerializeCompressed()),
"messages", lnutils.SpewLogClosure(daemonEvent.Msgs))
err := s.cfg.Daemon.SendMessages(
daemonEvent.TargetPeer, daemonEvent.Msgs,
)
if err != nil {
return fmt.Errorf("unable to send msgs: %w",
err)
}
// If a post-send event was specified, then we'll funnel
// that back into the main state machine now as well.
return fn.MapOptionZ(daemonEvent.PostSendEvent, func(event Event) error { //nolint:ll
launched := s.gm.Go(
ctx, func(ctx context.Context) {
s.log.DebugS(ctx, "Sending post-send event",
"event", lnutils.SpewLogClosure(event))
s.SendEvent(ctx, event)
},
)
if !launched {
return ErrStateMachineShutdown
}
return nil
})
}
canSend := func() bool {
return fn.MapOptionZ(
daemonEvent.SendWhen,
func(pred SendPredicate) bool {
return pred()
},
)
}
// If this doesn't have a SendWhen predicate, or if it's already
// true, then we can just send it off right away.
if !daemonEvent.SendWhen.IsSome() || canSend() {
return sendAndCleanUp()
}
// Otherwise, this has a SendWhen predicate, so we'll need
// launch a goroutine to poll the SendWhen, then send only once
// the predicate is true.
launched := s.gm.Go(ctx, func(ctx context.Context) {
predicateTicker := time.NewTicker(
s.cfg.CustomPollInterval.UnwrapOr(pollInterval),
)
defer predicateTicker.Stop()
s.log.InfoS(ctx, "Waiting for send predicate to be true")
for {
select {
case <-predicateTicker.C:
if canSend() {
s.log.InfoS(ctx, "Send active predicate")
err := sendAndCleanUp()
if err != nil {
s.log.ErrorS(ctx, "Unable to send message", err)
}
return
}
case <-ctx.Done():
return
}
}
})
if !launched {
return ErrStateMachineShutdown
}
return nil
// If this is a broadcast transaction event, then we'll broadcast with
// the label attached.
case *BroadcastTxn:
s.log.DebugS(ctx, "Broadcasting txn",
"txid", daemonEvent.Tx.TxHash())
err := s.cfg.Daemon.BroadcastTransaction(
daemonEvent.Tx, daemonEvent.Label,
)
if err != nil {
log.Errorf("unable to broadcast txn: %v", err)
}
return nil
// The state machine has requested a new event to be sent once a
// transaction spending a specified outpoint has confirmed.
case *RegisterSpend[Event]:
s.log.DebugS(ctx, "Registering spend",
"outpoint", daemonEvent.OutPoint)
spendEvent, err := s.cfg.Daemon.RegisterSpendNtfn(
&daemonEvent.OutPoint, daemonEvent.PkScript,
daemonEvent.HeightHint,
)
if err != nil {
return fmt.Errorf("unable to register spend: %w", err)
}
launched := s.gm.Go(ctx, func(ctx context.Context) {
for {
select {
case spend, ok := <-spendEvent.Spend:
if !ok {
return
}
// If there's a post-send event, then
// we'll send that into the current
// state now.
postSpend := daemonEvent.PostSpendEvent
postSpend.WhenSome(func(f SpendMapper[Event]) { //nolint:ll
customEvent := f(spend)
s.SendEvent(ctx, customEvent)
})
return
case <-ctx.Done():
return
}
}
})
if !launched {
return ErrStateMachineShutdown
}
return nil
// The state machine has requested a new event to be sent once a
// specified txid+pkScript pair has confirmed.
case *RegisterConf[Event]:
s.log.DebugS(ctx, "Registering conf",
"txid", daemonEvent.Txid)
numConfs := daemonEvent.NumConfs.UnwrapOr(1)
confEvent, err := s.cfg.Daemon.RegisterConfirmationsNtfn(
&daemonEvent.Txid, daemonEvent.PkScript,
numConfs, daemonEvent.HeightHint,
)
if err != nil {
return fmt.Errorf("unable to register conf: %w", err)
}
launched := s.gm.Go(ctx, func(ctx context.Context) {
for {
select {
case <-confEvent.Confirmed:
// If there's a post-conf event, then
// we'll send that into the current
// state now.
//
// TODO(roasbeef): refactor to
// dispatchAfterRecv w/ above
postConf := daemonEvent.PostConfEvent
postConf.WhenSome(func(e Event) {
s.SendEvent(ctx, e)
})
return
case <-ctx.Done():
return
}
}
})
if !launched {
return ErrStateMachineShutdown
}
return nil
}
return fmt.Errorf("unknown daemon event: %T", event)
}
// applyEvents applies a new event to the state machine. This will continue
// until no further events are emitted by the state machine. Along the way,
// we'll also ensure to execute any daemon events that are emitted.
func (s *StateMachine[Event, Env]) applyEvents(ctx context.Context,
currentState State[Event, Env], newEvent Event) (State[Event, Env],
error) {
eventQueue := fn.NewQueue(newEvent)
// Given the next event to handle, we'll process the event, then add
// any new emitted internal events to our event queue. This continues
// until we reach a terminal state, or we run out of internal events to
// process.
//
//nolint:ll
for nextEvent := eventQueue.Dequeue(); nextEvent.IsSome(); nextEvent = eventQueue.Dequeue() {
err := fn.MapOptionZ(nextEvent, func(event Event) error {
s.log.DebugS(ctx, "Processing event",
"event", lnutils.SpewLogClosure(event))
// Apply the state transition function of the current
// state given this new event and our existing env.
transition, err := currentState.ProcessEvent(
event, s.cfg.Env,
)
if err != nil {
return err
}
newEvents := transition.NewEvents
err = fn.MapOptionZ(newEvents, func(events EmittedEvent[Event]) error { //nolint:ll
// With the event processed, we'll process any
// new daemon events that were emitted as part
// of this new state transition.
for _, dEvent := range events.ExternalEvents {
err := s.executeDaemonEvent(
ctx, dEvent,
)
if err != nil {
return err
}
}
// Next, we'll add any new emitted events to our
// event queue.
//
//nolint:ll
for _, inEvent := range events.InternalEvent {
s.log.DebugS(ctx, "Adding new internal event to queue",
"event", lnutils.SpewLogClosure(inEvent))
eventQueue.Enqueue(inEvent)
}
return nil
})
if err != nil {
return err
}
s.log.InfoS(ctx, "State transition",
btclog.Fmt("from_state", "%v", currentState),
btclog.Fmt("to_state", "%v", transition.NextState))
// With our events processed, we'll now update our
// internal state.
currentState = transition.NextState
// Notify our subscribers of the new state transition.
//
// TODO(roasbeef): will only give us the outer state?
// * let FSMs choose which state to emit?
s.newStateEvents.NotifySubscribers(currentState)
return nil
})
if err != nil {
return currentState, err
}
}
return currentState, nil
}
// driveMachine is the main event loop of the state machine. It accepts any new
// incoming events, and then drives the state machine forward until it reaches
// a terminal state.
func (s *StateMachine[Event, Env]) driveMachine(ctx context.Context) {
s.log.DebugS(ctx, "Starting state machine")
currentState := s.cfg.InitialState
// Before we start, if we have an init daemon event specified, then
// we'll handle that now.
err := fn.MapOptionZ(s.cfg.InitEvent, func(event DaemonEvent) error {
return s.executeDaemonEvent(ctx, event)
})
if err != nil {
s.log.ErrorS(ctx, "Unable to execute init event", err)
return
}
// We just started driving the state machine, so we'll notify our
// subscribers of this starting state.
s.newStateEvents.NotifySubscribers(currentState)
for {
select {
// We have a new external event, so we'll drive the state
// machine forward until we either run out of internal events,
// or we reach a terminal state.
case newEvent := <-s.events:
newState, err := s.applyEvents(
ctx, currentState, newEvent,
)
if err != nil {
s.cfg.ErrorReporter.ReportError(err)
s.log.ErrorS(ctx, "Unable to apply event", err)
// An error occurred, so we'll tear down the
// entire state machine as we can't proceed.
go s.Stop()
return
}
currentState = newState
// An outside caller is querying our state, so we'll return the
// latest state.
case stateQuery := <-s.stateQuery:
if !fn.SendOrQuit(stateQuery.CurrentState, currentState, s.quit) { //nolint:ll
return
}
case <-s.gm.Done():
return
}
}
}
package record
import (
"fmt"
"io"
"github.com/lightningnetwork/lnd/tlv"
)
// AMPOnionType is the type used in the onion to reference the AMP fields:
// root_share, set_id, and child_index.
const AMPOnionType tlv.Type = 14
// AMP is a record that encodes the fields necessary for atomic multi-path
// payments.
type AMP struct {
rootShare [32]byte
setID [32]byte
childIndex uint32
}
// MaxAmpPayLoadSize is an AMP Record which when serialized to a tlv record uses
// the maximum payload size. The `childIndex` is created randomly and is a
// 4 byte `varint` type so we make sure we use an index which will be encoded in
// 4 bytes.
var MaxAmpPayLoadSize = AMP{
rootShare: [32]byte{},
setID: [32]byte{},
childIndex: 0x80000000,
}
// NewAMP generate a new AMP record with the given root_share, set_id, and
// child_index.
func NewAMP(rootShare, setID [32]byte, childIndex uint32) *AMP {
return &{
rootShare: rootShare,
setID: setID,
childIndex: childIndex,
}
}
// RootShare returns the root share contained in the AMP record.
func (a *AMP) RootShare() [32]byte {
return a.rootShare
}
// SetID returns the set id contained in the AMP record.
func (a *AMP) SetID() [32]byte {
return a.setID
}
// ChildIndex returns the child index contained in the AMP record.
func (a *AMP) ChildIndex() uint32 {
return a.childIndex
}
// AMPEncoder writes the AMP record to the provided io.Writer.
func AMPEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
if v, ok := val.(*AMP); ok {
if err := tlv.EBytes32(w, &v.rootShare, buf); err != nil {
return err
}
if err := tlv.EBytes32(w, &v.setID, buf); err != nil {
return err
}
return tlv.ETUint32T(w, v.childIndex, buf)
}
return tlv.NewTypeForEncodingErr(val, "AMP")
}
const (
// minAMPLength is the minimum length of a serialized AMP TLV record,
// which occurs when the truncated encoding of child_index takes 0
// bytes, leaving only the root_share and set_id.
minAMPLength = 64
// maxAMPLength is the maximum length of a serialized AMP TLV record,
// which occurs when the truncated encoding of a child_index takes 2
// bytes.
maxAMPLength = 68
)
// AMPDecoder reads the AMP record from the provided io.Reader.
func AMPDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
if v, ok := val.(*AMP); ok && minAMPLength <= l && l <= maxAMPLength {
if err := tlv.DBytes32(r, &v.rootShare, buf, 32); err != nil {
return err
}
if err := tlv.DBytes32(r, &v.setID, buf, 32); err != nil {
return err
}
return tlv.DTUint32(r, &v.childIndex, buf, l-minAMPLength)
}
return tlv.NewTypeForDecodingErr(val, "AMP", l, maxAMPLength)
}
// Record returns a tlv.Record that can be used to encode or decode this record.
func (a *AMP) Record() tlv.Record {
return tlv.MakeDynamicRecord(
AMPOnionType, a, a.PayloadSize, AMPEncoder, AMPDecoder,
)
}
// PayloadSize returns the size this record takes up in encoded form.
func (a *AMP) PayloadSize() uint64 {
return 32 + 32 + tlv.SizeTUint32(a.childIndex)
}
// String returns a human-readable description of the amp payload fields.
func (a *AMP) String() string {
if a == nil {
return "<nil>"
}
return fmt.Sprintf("root_share=%x set_id=%x child_index=%d",
a.rootShare, a.setID, a.childIndex)
}
package record
import (
"bytes"
"encoding/binary"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tlv"
)
// AverageDummyHopPayloadSize is the size of a standard blinded path dummy hop
// payload. In most cases, this is larger than the other payload types and so
// to make sure that a sender cannot use this fact to know if a dummy hop is
// present or not, we'll make sure to always pad all payloads to at least this
// size.
const AverageDummyHopPayloadSize = 51
// BlindedRouteData contains the information that is included in a blinded
// route encrypted data blob that is created by the recipient to provide
// forwarding information.
type BlindedRouteData struct {
// Padding is an optional set of bytes that a recipient can use to pad
// the data so that the encrypted recipient data blobs are all the same
// length.
Padding tlv.OptionalRecordT[tlv.TlvType1, []byte]
// ShortChannelID is the channel ID of the next hop.
ShortChannelID tlv.OptionalRecordT[tlv.TlvType2, lnwire.ShortChannelID]
// NextNodeID is the node ID of the next node on the path. In the
// context of blinded path payments, this is used to indicate the
// presence of dummy hops that need to be peeled from the onion.
NextNodeID tlv.OptionalRecordT[tlv.TlvType4, *btcec.PublicKey]
// PathID is a secret set of bytes that the blinded path creator will
// set so that they can check the value on decryption to ensure that the
// path they created was used for the intended purpose.
PathID tlv.OptionalRecordT[tlv.TlvType6, []byte]
// NextBlindingOverride is a blinding point that should be switched
// in for the next hop. This is used to combine two blinded paths into
// one (which primarily is used in onion messaging, but in theory
// could be used for payments as well).
NextBlindingOverride tlv.OptionalRecordT[tlv.TlvType8, *btcec.PublicKey]
// RelayInfo provides the relay parameters for the hop.
RelayInfo tlv.OptionalRecordT[tlv.TlvType10, PaymentRelayInfo]
// Constraints provides the payment relay constraints for the hop.
Constraints tlv.OptionalRecordT[tlv.TlvType12, PaymentConstraints]
// Features is the set of features the payment requires.
Features tlv.OptionalRecordT[tlv.TlvType14, lnwire.FeatureVector]
}
// NewNonFinalBlindedRouteData creates the data that's provided for hops within
// a blinded route.
func NewNonFinalBlindedRouteData(chanID lnwire.ShortChannelID,
blindingOverride *btcec.PublicKey, relayInfo PaymentRelayInfo,
constraints *PaymentConstraints,
features *lnwire.FeatureVector) *BlindedRouteData {
info := &BlindedRouteData{
ShortChannelID: tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType2](chanID),
),
RelayInfo: tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType10](relayInfo),
),
}
if blindingOverride != nil {
info.NextBlindingOverride = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType8](blindingOverride))
}
if constraints != nil {
info.Constraints = tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType12](*constraints))
}
if features != nil {
info.Features = tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType14](*features),
)
}
return info
}
// NewFinalHopBlindedRouteData creates the data that's provided for the final
// hop in a blinded route.
func NewFinalHopBlindedRouteData(constraints *PaymentConstraints,
pathID []byte) *BlindedRouteData {
var data BlindedRouteData
if pathID != nil {
data.PathID = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType6](pathID),
)
}
if constraints != nil {
data.Constraints = tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType12](*constraints))
}
return &data
}
// NewDummyHopRouteData creates the data that's provided for any hop preceding
// a dummy hop. The presence of such a payload indicates to the reader that
// they are the intended recipient and should peel the remainder of the onion.
func NewDummyHopRouteData(ourPubKey *btcec.PublicKey,
relayInfo PaymentRelayInfo,
constraints PaymentConstraints) *BlindedRouteData {
return &BlindedRouteData{
NextNodeID: tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType4](ourPubKey),
),
RelayInfo: tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType10](relayInfo),
),
Constraints: tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType12](constraints),
),
}
}
// DecodeBlindedRouteData decodes the data provided within a blinded route.
func DecodeBlindedRouteData(r io.Reader) (*BlindedRouteData, error) {
var (
d BlindedRouteData
padding = d.Padding.Zero()
scid = d.ShortChannelID.Zero()
nextNodeID = d.NextNodeID.Zero()
pathID = d.PathID.Zero()
blindingOverride = d.NextBlindingOverride.Zero()
relayInfo = d.RelayInfo.Zero()
constraints = d.Constraints.Zero()
features = d.Features.Zero()
)
var tlvRecords lnwire.ExtraOpaqueData
if err := lnwire.ReadElements(r, &tlvRecords); err != nil {
return nil, err
}
typeMap, err := tlvRecords.ExtractRecords(
&padding, &scid, &nextNodeID, &pathID, &blindingOverride,
&relayInfo, &constraints, &features,
)
if err != nil {
return nil, err
}
val, ok := typeMap[d.Padding.TlvType()]
if ok && val == nil {
d.Padding = tlv.SomeRecordT(padding)
}
if val, ok := typeMap[d.ShortChannelID.TlvType()]; ok && val == nil {
d.ShortChannelID = tlv.SomeRecordT(scid)
}
if val, ok := typeMap[d.NextNodeID.TlvType()]; ok && val == nil {
d.NextNodeID = tlv.SomeRecordT(nextNodeID)
}
if val, ok := typeMap[d.PathID.TlvType()]; ok && val == nil {
d.PathID = tlv.SomeRecordT(pathID)
}
val, ok = typeMap[d.NextBlindingOverride.TlvType()]
if ok && val == nil {
d.NextBlindingOverride = tlv.SomeRecordT(blindingOverride)
}
if val, ok := typeMap[d.RelayInfo.TlvType()]; ok && val == nil {
d.RelayInfo = tlv.SomeRecordT(relayInfo)
}
if val, ok := typeMap[d.Constraints.TlvType()]; ok && val == nil {
d.Constraints = tlv.SomeRecordT(constraints)
}
if val, ok := typeMap[d.Features.TlvType()]; ok && val == nil {
d.Features = tlv.SomeRecordT(features)
}
return &d, nil
}
// EncodeBlindedRouteData encodes the blinded route data provided.
func EncodeBlindedRouteData(data *BlindedRouteData) ([]byte, error) {
var (
e lnwire.ExtraOpaqueData
recordProducers = make([]tlv.RecordProducer, 0, 5)
)
data.Padding.WhenSome(func(p tlv.RecordT[tlv.TlvType1, []byte]) {
recordProducers = append(recordProducers, &p)
})
data.ShortChannelID.WhenSome(func(scid tlv.RecordT[tlv.TlvType2,
lnwire.ShortChannelID]) {
recordProducers = append(recordProducers, &scid)
})
data.NextNodeID.WhenSome(func(f tlv.RecordT[tlv.TlvType4,
*btcec.PublicKey]) {
recordProducers = append(recordProducers, &f)
})
data.PathID.WhenSome(func(pathID tlv.RecordT[tlv.TlvType6, []byte]) {
recordProducers = append(recordProducers, &pathID)
})
data.NextBlindingOverride.WhenSome(func(pk tlv.RecordT[tlv.TlvType8,
*btcec.PublicKey]) {
recordProducers = append(recordProducers, &pk)
})
data.RelayInfo.WhenSome(func(r tlv.RecordT[tlv.TlvType10,
PaymentRelayInfo]) {
recordProducers = append(recordProducers, &r)
})
data.Constraints.WhenSome(func(cs tlv.RecordT[tlv.TlvType12,
PaymentConstraints]) {
recordProducers = append(recordProducers, &cs)
})
data.Features.WhenSome(func(f tlv.RecordT[tlv.TlvType14,
lnwire.FeatureVector]) {
recordProducers = append(recordProducers, &f)
})
if err := e.PackRecords(recordProducers...); err != nil {
return nil, err
}
return e[:], nil
}
// PadBy adds "n" padding bytes to the BlindedRouteData using the Padding field.
// Callers should be aware that the total payload size will change by more than
// "n" since the "n" bytes will be prefixed by BigSize type and length fields.
// Callers may need to call PadBy iteratively until each encrypted data packet
// is the same size and so each call will overwrite the Padding record.
// Note that calling PadBy with an n value of 0 will still result in a zero
// length TLV entry being added.
func (b *BlindedRouteData) PadBy(n int) {
b.Padding = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType1](make([]byte, n)),
)
}
// PaymentRelayInfo describes the relay policy for a blinded path.
type PaymentRelayInfo struct {
// CltvExpiryDelta is the expiry delta for the payment.
CltvExpiryDelta uint16
// FeeRate is the fee rate that will be charged per millionth of a
// satoshi.
FeeRate uint32
// BaseFee is the per-htlc fee charged in milli-satoshis.
BaseFee lnwire.MilliSatoshi
}
// Record creates a tlv.Record that encodes the payment relay (type 10) type for
// an encrypted blob payload.
func (i *PaymentRelayInfo) Record() tlv.Record {
return tlv.MakeDynamicRecord(
10, &i, func() uint64 {
// uint16 + uint32 + tuint32
return 2 + 4 + tlv.SizeTUint32(uint32(i.BaseFee))
}, encodePaymentRelay, decodePaymentRelay,
)
}
func encodePaymentRelay(w io.Writer, val interface{}, buf *[8]byte) error {
if t, ok := val.(**PaymentRelayInfo); ok {
relayInfo := *t
// Just write our first 6 bytes directly.
binary.BigEndian.PutUint16(buf[:2], relayInfo.CltvExpiryDelta)
binary.BigEndian.PutUint32(buf[2:6], relayInfo.FeeRate)
if _, err := w.Write(buf[0:6]); err != nil {
return err
}
baseFee := uint32(relayInfo.BaseFee)
// We can safely reuse buf here because we overwrite its
// contents.
return tlv.ETUint32(w, &baseFee, buf)
}
return tlv.NewTypeForEncodingErr(val, "**hop.PaymentRelayInfo")
}
func decodePaymentRelay(r io.Reader, val interface{}, buf *[8]byte,
l uint64) error {
if t, ok := val.(**PaymentRelayInfo); ok && l <= 10 {
scratch := make([]byte, l)
n, err := io.ReadFull(r, scratch)
if err != nil {
return err
}
// We expect at least 6 bytes, because we have 2 bytes for
// cltv delta and 4 bytes for fee rate.
if n < 6 {
return tlv.NewTypeForDecodingErr(val,
"*hop.paymentRelayInfo", uint64(n), 6)
}
relayInfo := *t
relayInfo.CltvExpiryDelta = binary.BigEndian.Uint16(
scratch[0:2],
)
relayInfo.FeeRate = binary.BigEndian.Uint32(scratch[2:6])
// To be able to re-use the DTUint32 function we create a
// buffer with just the bytes holding the variable length u32.
// If the base fee is zero, this will be an empty buffer, which
// is okay.
b := bytes.NewBuffer(scratch[6:])
var baseFee uint32
err = tlv.DTUint32(b, &baseFee, buf, l-6)
if err != nil {
return err
}
relayInfo.BaseFee = lnwire.MilliSatoshi(baseFee)
return nil
}
return tlv.NewTypeForDecodingErr(val, "*hop.paymentRelayInfo", l, 10)
}
// PaymentConstraints is a set of restrictions on a payment.
type PaymentConstraints struct {
// MaxCltvExpiry is the maximum expiry height for the payment.
MaxCltvExpiry uint32
// HtlcMinimumMsat is the minimum htlc size for the payment.
HtlcMinimumMsat lnwire.MilliSatoshi
}
func (p *PaymentConstraints) Record() tlv.Record {
return tlv.MakeDynamicRecord(
12, &p, func() uint64 {
// uint32 + tuint64.
return 4 + tlv.SizeTUint64(uint64(
p.HtlcMinimumMsat,
))
},
encodePaymentConstraints, decodePaymentConstraints,
)
}
func encodePaymentConstraints(w io.Writer, val interface{},
buf *[8]byte) error {
if c, ok := val.(**PaymentConstraints); ok {
constraints := *c
binary.BigEndian.PutUint32(buf[:4], constraints.MaxCltvExpiry)
if _, err := w.Write(buf[:4]); err != nil {
return err
}
// We can safely re-use buf here because we overwrite its
// contents.
htlcMsat := uint64(constraints.HtlcMinimumMsat)
return tlv.ETUint64(w, &htlcMsat, buf)
}
return tlv.NewTypeForEncodingErr(val, "**PaymentConstraints")
}
func decodePaymentConstraints(r io.Reader, val interface{}, buf *[8]byte,
l uint64) error {
if c, ok := val.(**PaymentConstraints); ok && l <= 12 {
scratch := make([]byte, l)
n, err := io.ReadFull(r, scratch)
if err != nil {
return err
}
// We expect at least 4 bytes for our uint32.
if n < 4 {
return tlv.NewTypeForDecodingErr(val,
"*paymentConstraints", uint64(n), 4)
}
payConstraints := *c
payConstraints.MaxCltvExpiry = binary.BigEndian.Uint32(
scratch[:4],
)
// This could be empty if our minimum is zero, that's okay.
var (
b = bytes.NewBuffer(scratch[4:])
minHtlc uint64
)
err = tlv.DTUint64(b, &minHtlc, buf, l-4)
if err != nil {
return err
}
payConstraints.HtlcMinimumMsat = lnwire.MilliSatoshi(minHtlc)
return nil
}
return tlv.NewTypeForDecodingErr(val, "**PaymentConstraints", l, l)
}
package record
import (
"fmt"
)
const (
// CustomTypeStart is the start of the custom tlv type range as defined
// in BOLT 01.
CustomTypeStart = 65536
)
// CustomSet stores a set of custom key/value pairs.
type CustomSet map[uint64][]byte
// Validate checks that all custom records are in the custom type range.
func (c CustomSet) Validate() error {
for key := range c {
if key < CustomTypeStart {
return fmt.Errorf("no custom records with types "+
"below %v allowed", CustomTypeStart)
}
}
return nil
}
// IsKeysend checks if the custom records contain the key send type.
func (c CustomSet) IsKeysend() bool {
return c[KeySendType] != nil
}
package record
import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// AmtOnionType is the type used in the onion to reference the amount to
// send to the next hop.
AmtOnionType tlv.Type = 2
// LockTimeTLV is the type used in the onion to reference the CLTV
// value that should be used for the next hop's HTLC.
LockTimeOnionType tlv.Type = 4
// NextHopOnionType is the type used in the onion to reference the ID
// of the next hop.
NextHopOnionType tlv.Type = 6
// EncryptedDataOnionType is the type used to include encrypted data
// provided by the receiver in the onion for use in blinded paths.
EncryptedDataOnionType tlv.Type = 10
// BlindingPointOnionType is the type used to include receiver provided
// ephemeral keys in the onion that are used in blinded paths.
BlindingPointOnionType tlv.Type = 12
// MetadataOnionType is the type used in the onion for the payment
// metadata.
MetadataOnionType tlv.Type = 16
// TotalAmtMsatBlindedType is the type used in the onion for the total
// amount field that is included in the final hop for blinded payments.
TotalAmtMsatBlindedType tlv.Type = 18
)
// NewAmtToFwdRecord creates a tlv.Record that encodes the amount_to_forward
// (type 2) for an onion payload.
func NewAmtToFwdRecord(amt *uint64) tlv.Record {
return tlv.MakeDynamicRecord(
AmtOnionType, amt, func() uint64 {
return tlv.SizeTUint64(*amt)
},
tlv.ETUint64, tlv.DTUint64,
)
}
// NewLockTimeRecord creates a tlv.Record that encodes the outgoing_cltv_value
// (type 4) for an onion payload.
func NewLockTimeRecord(lockTime *uint32) tlv.Record {
return tlv.MakeDynamicRecord(
LockTimeOnionType, lockTime, func() uint64 {
return tlv.SizeTUint32(*lockTime)
},
tlv.ETUint32, tlv.DTUint32,
)
}
// NewNextHopIDRecord creates a tlv.Record that encodes the short_channel_id
// (type 6) for an onion payload.
func NewNextHopIDRecord(cid *uint64) tlv.Record {
return tlv.MakePrimitiveRecord(NextHopOnionType, cid)
}
// NewEncryptedDataRecord creates a tlv.Record that encodes the encrypted_data
// (type 10) record for an onion payload.
func NewEncryptedDataRecord(data *[]byte) tlv.Record {
return tlv.MakePrimitiveRecord(EncryptedDataOnionType, data)
}
// NewBlindingPointRecord creates a tlv.Record that encodes the blinding_point
// (type 12) record for an onion payload.
func NewBlindingPointRecord(point **btcec.PublicKey) tlv.Record {
return tlv.MakePrimitiveRecord(BlindingPointOnionType, point)
}
// NewMetadataRecord creates a tlv.Record that encodes the metadata (type 10)
// for an onion payload.
func NewMetadataRecord(metadata *[]byte) tlv.Record {
return tlv.MakeDynamicRecord(
MetadataOnionType, metadata,
func() uint64 {
return uint64(len(*metadata))
},
tlv.EVarBytes, tlv.DVarBytes,
)
}
// NewTotalAmtMsatBlinded creates a tlv.Record that encodes the
// total_amount_msat for the final an onion payload within a blinded route.
func NewTotalAmtMsatBlinded(amt *uint64) tlv.Record {
return tlv.MakeDynamicRecord(
TotalAmtMsatBlindedType, amt, func() uint64 {
return tlv.SizeTUint64(*amt)
},
tlv.ETUint64, tlv.DTUint64,
)
}
package record
import (
"fmt"
"io"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tlv"
)
// MPPOnionType is the type used in the onion to reference the MPP fields:
// total_amt and payment_addr.
const MPPOnionType tlv.Type = 8
// MPP is a record that encodes the fields necessary for multi-path payments.
type MPP struct {
// paymentAddr is a random, receiver-generated value used to avoid
// collisions with concurrent payers.
paymentAddr [32]byte
// totalMsat is the total value of the payment, potentially spread
// across more than one HTLC.
totalMsat lnwire.MilliSatoshi
}
// NewMPP generates a new MPP record with the given total and payment address.
func NewMPP(total lnwire.MilliSatoshi, addr [32]byte) *MPP {
return &MPP{
paymentAddr: addr,
totalMsat: total,
}
}
// PaymentAddr returns the payment address contained in the MPP record.
func (r *MPP) PaymentAddr() [32]byte {
return r.paymentAddr
}
// TotalMsat returns the total value of an MPP payment in msats.
func (r *MPP) TotalMsat() lnwire.MilliSatoshi {
return r.totalMsat
}
// MPPEncoder writes the MPP record to the provided io.Writer.
func MPPEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
if v, ok := val.(*MPP); ok {
err := tlv.EBytes32(w, &v.paymentAddr, buf)
if err != nil {
return err
}
return tlv.ETUint64T(w, uint64(v.totalMsat), buf)
}
return tlv.NewTypeForEncodingErr(val, "MPP")
}
const (
// minMPPLength is the minimum length of a serialized MPP TLV record,
// which occurs when the truncated encoding of total_amt_msat takes 0
// bytes, leaving only the payment_addr.
minMPPLength = 32
// maxMPPLength is the maximum length of a serialized MPP TLV record,
// which occurs when the truncated encoding of total_amt_msat takes 8
// bytes.
maxMPPLength = 40
)
// MPPDecoder reads the MPP record to the provided io.Reader.
func MPPDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
if v, ok := val.(*MPP); ok && minMPPLength <= l && l <= maxMPPLength {
if err := tlv.DBytes32(r, &v.paymentAddr, buf, 32); err != nil {
return err
}
var total uint64
if err := tlv.DTUint64(r, &total, buf, l-32); err != nil {
return err
}
v.totalMsat = lnwire.MilliSatoshi(total)
return nil
}
return tlv.NewTypeForDecodingErr(val, "MPP", l, maxMPPLength)
}
// Record returns a tlv.Record that can be used to encode or decode this record.
func (r *MPP) Record() tlv.Record {
// Fixed-size, 32 byte payment address followed by truncated 64-bit
// total msat.
size := func() uint64 {
return 32 + tlv.SizeTUint64(uint64(r.totalMsat))
}
return tlv.MakeDynamicRecord(
MPPOnionType, r, size, MPPEncoder, MPPDecoder,
)
}
// PayloadSize returns the size this record takes up in encoded form.
func (r *MPP) PayloadSize() uint64 {
return 32 + tlv.SizeTUint64(uint64(r.totalMsat))
}
// String returns a human-readable representation of the mpp payload field.
func (r *MPP) String() string {
if r == nil {
return "<nil>"
}
return fmt.Sprintf("total=%v, addr=%x", r.totalMsat, r.paymentAddr)
}
package routing
import (
"errors"
"fmt"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
var (
// ErrNoPayLoadSizeFunc is returned when no payload size function is
// definied.
ErrNoPayLoadSizeFunc = errors.New("no payloadSizeFunc defined for " +
"additional edge")
)
// AdditionalEdge is an interface which specifies additional edges which can
// be appended to an existing route. Compared to normal edges of a route they
// provide an explicit payload size function and are introduced because blinded
// paths differ in their payload structure.
type AdditionalEdge interface {
// IntermediatePayloadSize returns the size of the payload for the
// additional edge when being an intermediate hop in a route NOT the
// final hop.
IntermediatePayloadSize(amount lnwire.MilliSatoshi, expiry uint32,
channelID uint64) uint64
// EdgePolicy returns the policy of the additional edge.
EdgePolicy() *models.CachedEdgePolicy
// BlindedPayment returns the BlindedPayment that this additional edge
// info was derived from. It will return nil if this edge was not
// derived from a blinded route.
BlindedPayment() *BlindedPayment
}
// PayloadSizeFunc defines the interface for the payload size function.
type PayloadSizeFunc func(amount lnwire.MilliSatoshi, expiry uint32,
channelID uint64) uint64
// PrivateEdge implements the AdditionalEdge interface. As the name implies it
// is used for private route hints that the receiver adds for example to an
// invoice.
type PrivateEdge struct {
policy *models.CachedEdgePolicy
}
// EdgePolicy return the policy of the PrivateEdge.
func (p *PrivateEdge) EdgePolicy() *models.CachedEdgePolicy {
return p.policy
}
// IntermediatePayloadSize returns the sphinx payload size defined in BOLT04 if
// this edge were to be included in a route.
func (p *PrivateEdge) IntermediatePayloadSize(amount lnwire.MilliSatoshi,
expiry uint32, channelID uint64) uint64 {
hop := route.Hop{
AmtToForward: amount,
OutgoingTimeLock: expiry,
}
return hop.PayloadSize(channelID)
}
// BlindedPayment is a no-op for a PrivateEdge since it is not associated with
// a blinded payment. This will thus return nil.
func (p *PrivateEdge) BlindedPayment() *BlindedPayment {
return nil
}
// BlindedEdge implements the AdditionalEdge interface. Blinded hops are viewed
// as additional edges because they are appended at the end of a normal route.
type BlindedEdge struct {
policy *models.CachedEdgePolicy
// blindedPayment is the BlindedPayment that this blinded edge was
// derived from.
blindedPayment *BlindedPayment
// hopIndex is the index of the hop in the blinded payment path that
// this edge is associated with.
hopIndex int
}
// NewBlindedEdge constructs a new BlindedEdge which packages the policy info
// for a specific hop within the given blinded payment path. The hop index
// should correspond to the hop within the blinded payment that this edge is
// associated with.
func NewBlindedEdge(policy *models.CachedEdgePolicy, payment *BlindedPayment,
hopIndex int) (*BlindedEdge, error) {
if payment == nil {
return nil, fmt.Errorf("blinded payment cannot be nil for " +
"blinded edge")
}
if hopIndex < 0 || hopIndex >= len(payment.BlindedPath.BlindedHops) {
return nil, fmt.Errorf("the hop index %d is outside the "+
"valid range between 0 and %d", hopIndex,
len(payment.BlindedPath.BlindedHops)-1)
}
return &BlindedEdge{
policy: policy,
hopIndex: hopIndex,
blindedPayment: payment,
}, nil
}
// EdgePolicy return the policy of the BlindedEdge.
func (b *BlindedEdge) EdgePolicy() *models.CachedEdgePolicy {
return b.policy
}
// IntermediatePayloadSize returns the sphinx payload size defined in BOLT04 if
// this edge were to be included in a route.
func (b *BlindedEdge) IntermediatePayloadSize(_ lnwire.MilliSatoshi, _ uint32,
_ uint64) uint64 {
blindedPath := b.blindedPayment.BlindedPath
hop := route.Hop{
BlindingPoint: blindedPath.BlindingPoint,
EncryptedData: blindedPath.BlindedHops[b.hopIndex].CipherText,
}
// For blinded paths the next chanID is in the encrypted data tlv.
return hop.PayloadSize(0)
}
// BlindedPayment returns the blinded payment that this edge is associated
// with.
func (b *BlindedEdge) BlindedPayment() *BlindedPayment {
return b.blindedPayment
}
// Compile-time constraints to ensure the PrivateEdge and the BlindedEdge
// implement the AdditionalEdge interface.
var _ AdditionalEdge = (*PrivateEdge)(nil)
var _ AdditionalEdge = (*BlindedEdge)(nil)
// defaultHopPayloadSize is the default payload size of a normal (not-blinded)
// hop in the route.
func defaultHopPayloadSize(amount lnwire.MilliSatoshi, expiry uint32,
channelID uint64) uint64 {
// The payload size of a cleartext intermediate hop is equal to the
// payload size of a private edge therefore we reuse its size function.
edge := PrivateEdge{}
return edge.IntermediatePayloadSize(amount, expiry, channelID)
}
package routing
import (
"fmt"
"github.com/lightningnetwork/lnd/fn/v2"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/tlv"
)
// bandwidthHints provides hints about the currently available balance in our
// channels.
type bandwidthHints interface {
// availableChanBandwidth returns the total available bandwidth for a
// channel and a bool indicating whether the channel hint was found.
// The amount parameter is used to validate the outgoing htlc amount
// that we wish to add to the channel against its flow restrictions. If
// a zero amount is provided, the minimum htlc value for the channel
// will be used. If the channel is unavailable, a zero amount is
// returned.
availableChanBandwidth(channelID uint64,
amount lnwire.MilliSatoshi) (lnwire.MilliSatoshi, bool)
// firstHopCustomBlob returns the custom blob for the first hop of the
// payment, if available.
firstHopCustomBlob() fn.Option[tlv.Blob]
}
// getLinkQuery is the function signature used to lookup a link.
type getLinkQuery func(lnwire.ShortChannelID) (
htlcswitch.ChannelLink, error)
// bandwidthManager is an implementation of the bandwidthHints interface which
// uses the link lookup provided to query the link for our latest local channel
// balances.
type bandwidthManager struct {
getLink getLinkQuery
localChans map[lnwire.ShortChannelID]struct{}
firstHopBlob fn.Option[tlv.Blob]
trafficShaper fn.Option[htlcswitch.AuxTrafficShaper]
}
// newBandwidthManager creates a bandwidth manager for the source node provided
// which is used to obtain hints from the lower layer w.r.t the available
// bandwidth of edges on the network. Currently, we'll only obtain bandwidth
// hints for the edges we directly have open ourselves. Obtaining these hints
// allows us to reduce the number of extraneous attempts as we can skip channels
// that are inactive, or just don't have enough bandwidth to carry the payment.
func newBandwidthManager(graph Graph, sourceNode route.Vertex,
linkQuery getLinkQuery, firstHopBlob fn.Option[tlv.Blob],
ts fn.Option[htlcswitch.AuxTrafficShaper]) (*bandwidthManager,
error) {
manager := &bandwidthManager{
getLink: linkQuery,
localChans: make(map[lnwire.ShortChannelID]struct{}),
firstHopBlob: firstHopBlob,
trafficShaper: ts,
}
// First, we'll collect the set of outbound edges from the target
// source node and add them to our bandwidth manager's map of channels.
err := graph.ForEachNodeDirectedChannel(sourceNode,
func(channel *graphdb.DirectedChannel) error {
shortID := lnwire.NewShortChanIDFromInt(
channel.ChannelID,
)
manager.localChans[shortID] = struct{}{}
return nil
})
if err != nil {
return nil, err
}
return manager, nil
}
// getBandwidth queries the current state of a link and gets its currently
// available bandwidth. Note that this function assumes that the channel being
// queried is one of our local channels, so any failure to retrieve the link
// is interpreted as the link being offline.
func (b *bandwidthManager) getBandwidth(cid lnwire.ShortChannelID,
amount lnwire.MilliSatoshi) lnwire.MilliSatoshi {
link, err := b.getLink(cid)
if err != nil {
// If the link isn't online, then we'll report that it has
// zero bandwidth.
log.Warnf("ShortChannelID=%v: link not found: %v", cid, err)
return 0
}
// If the link is found within the switch, but it isn't yet eligible
// to forward any HTLCs, then we'll treat it as if it isn't online in
// the first place.
if !link.EligibleToForward() {
log.Warnf("ShortChannelID=%v: not eligible to forward", cid)
return 0
}
// bandwidthResult is an inline type that we'll use to pass the
// bandwidth result from the external traffic shaper to the main logic
// below.
type bandwidthResult struct {
// bandwidth is the available bandwidth for the channel as
// reported by the external traffic shaper. If the external
// traffic shaper is not handling the channel, this value will
// be fn.None
bandwidth fn.Option[lnwire.MilliSatoshi]
// htlcAmount is the amount we're going to use to check if we
// can add another HTLC to the channel. If the external traffic
// shaper is handling the channel, we'll use 0 to just sanity
// check the number of HTLCs on the channel, since we don't know
// the actual HTLC amount that will be sent.
htlcAmount fn.Option[lnwire.MilliSatoshi]
}
var (
// We will pass the link bandwidth to the external traffic
// shaper. This is the current best estimate for the available
// bandwidth for the link.
linkBandwidth = link.Bandwidth()
bandwidthErr = func(err error) fn.Result[bandwidthResult] {
return fn.Err[bandwidthResult](err)
}
)
result, err := fn.MapOptionZ(
b.trafficShaper,
func(s htlcswitch.AuxTrafficShaper) fn.Result[bandwidthResult] {
auxBandwidth, err := link.AuxBandwidth(
amount, cid, b.firstHopBlob, s,
).Unpack()
if err != nil {
return bandwidthErr(fmt.Errorf("failed to get "+
"auxiliary bandwidth: %w", err))
}
// If the external traffic shaper is not handling the
// channel, we'll just return the original bandwidth and
// no custom amount.
if !auxBandwidth.IsHandled {
return fn.Ok(bandwidthResult{})
}
// We don't know the actual HTLC amount that will be
// sent using the custom channel. But we'll still want
// to make sure we can add another HTLC, using the
// MayAddOutgoingHtlc method below. Passing 0 into that
// method will use the minimum HTLC value for the
// channel, which is okay to just check we don't exceed
// the max number of HTLCs on the channel. A proper
// balance check is done elsewhere.
return fn.Ok(bandwidthResult{
bandwidth: auxBandwidth.Bandwidth,
htlcAmount: fn.Some[lnwire.MilliSatoshi](0),
})
},
).Unpack()
if err != nil {
log.Errorf("ShortChannelID=%v: failed to get bandwidth from "+
"external traffic shaper: %v", cid, err)
return 0
}
htlcAmount := result.htlcAmount.UnwrapOr(amount)
// If our link isn't currently in a state where it can add another
// outgoing htlc, treat the link as unusable.
if err := link.MayAddOutgoingHtlc(htlcAmount); err != nil {
log.Warnf("ShortChannelID=%v: cannot add outgoing "+
"htlc with amount %v: %v", cid, htlcAmount, err)
return 0
}
// If the external traffic shaper determined the bandwidth, we'll return
// that value, even if it is zero (which would mean no bandwidth is
// available on that channel).
reportedBandwidth := result.bandwidth.UnwrapOr(linkBandwidth)
return reportedBandwidth
}
// availableChanBandwidth returns the total available bandwidth for a channel
// and a bool indicating whether the channel hint was found. If the channel is
// unavailable, a zero amount is returned.
func (b *bandwidthManager) availableChanBandwidth(channelID uint64,
amount lnwire.MilliSatoshi) (lnwire.MilliSatoshi, bool) {
shortID := lnwire.NewShortChanIDFromInt(channelID)
_, ok := b.localChans[shortID]
if !ok {
return 0, false
}
return b.getBandwidth(shortID, amount), true
}
// firstHopCustomBlob returns the custom blob for the first hop of the payment,
// if available.
func (b *bandwidthManager) firstHopCustomBlob() fn.Option[tlv.Blob] {
return b.firstHopBlob
}
package blindedpath
import (
"bytes"
"errors"
"fmt"
"math"
"sort"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/tlv"
"github.com/lightningnetwork/lnd/zpay32"
)
const (
// oneMillion is a constant used frequently in fee rate calculations.
oneMillion = uint32(1_000_000)
)
// errInvalidBlindedPath indicates that the chosen real path is not usable as
// a blinded path.
var errInvalidBlindedPath = errors.New("the chosen path results in an " +
"unusable blinded path")
// BuildBlindedPathCfg defines the various resources and configuration values
// required to build a blinded payment path to this node.
type BuildBlindedPathCfg struct {
// FindRoutes returns a set of routes to us that can be used for the
// construction of blinded paths. These routes will consist of real
// nodes advertising the route blinding feature bit. They may be of
// various lengths and may even contain only a single hop. Any route
// shorter than MinNumHops will be padded with dummy hops during route
// construction.
FindRoutes func(value lnwire.MilliSatoshi) ([]*route.Route, error)
// FetchChannelEdgesByID attempts to look up the two directed edges for
// the channel identified by the channel ID.
FetchChannelEdgesByID func(chanID uint64) (*models.ChannelEdgeInfo,
*models.ChannelEdgePolicy, *models.ChannelEdgePolicy, error)
// FetchOurOpenChannels fetches this node's set of open channels.
FetchOurOpenChannels func() ([]*channeldb.OpenChannel, error)
// BestHeight can be used to fetch the best block height that this node
// is aware of.
BestHeight func() (uint32, error)
// AddPolicyBuffer is a function that can be used to alter the policy
// values of the given channel edge. The main reason for doing this is
// to add a safety buffer so that if the node makes small policy changes
// during the lifetime of the blinded path, then the path remains valid
// and so probing is more difficult. Note that this will only be called
// for the policies of real nodes and won't be applied to
// DefaultDummyHopPolicy.
AddPolicyBuffer func(policy *BlindedHopPolicy) (*BlindedHopPolicy,
error)
// PathID is the secret data to embed in the blinded path data that we
// will receive back as the recipient. This is the equivalent of the
// payment address used in normal payments. It lets the recipient check
// that the path is being used in the correct context.
PathID []byte
// ValueMsat is the payment amount in milli-satoshis that must be
// routed. This will be used for selecting appropriate routes to use for
// the blinded path.
ValueMsat lnwire.MilliSatoshi
// MinFinalCLTVExpiryDelta is the minimum CLTV delta that the recipient
// requires for the final hop of the payment.
//
// NOTE that the caller is responsible for adding additional block
// padding to this value to account for blocks being mined while the
// payment is in-flight.
MinFinalCLTVExpiryDelta uint32
// BlocksUntilExpiry is the number of blocks that this blinded path
// should remain valid for. This is a relative number of blocks. This
// number in addition with a potential minimum cltv delta for the last
// hop and some block padding will be the payment constraint which is
// part of the blinded hop info. Every htlc using the provided blinded
// hops cannot have a higher cltv delta otherwise it will get rejected
// by the forwarding nodes or the final node.
//
// This number should at least be greater than the invoice expiry time
// so that the blinded route is always valid as long as the invoice is
// valid.
BlocksUntilExpiry uint32
// MinNumHops is the minimum number of hops that each blinded path
// should be. If the number of hops in a path returned by FindRoutes is
// less than this number, then dummy hops will be post-fixed to the
// route.
MinNumHops uint8
// DefaultDummyHopPolicy holds the policy values that should be used for
// dummy hops in the cases where it cannot be derived via other means
// such as averaging the policy values of other hops on the path. This
// would happen in the case where the introduction node is also the
// introduction node. If these default policy values are used, then
// the MaxHTLCMsat value must be carefully chosen.
DefaultDummyHopPolicy *BlindedHopPolicy
}
// BuildBlindedPaymentPaths uses the passed config to construct a set of blinded
// payment paths that can be added to the invoice.
func BuildBlindedPaymentPaths(cfg *BuildBlindedPathCfg) (
[]*zpay32.BlindedPaymentPath, error) {
// Find some appropriate routes for the value to be routed. This will
// return a set of routes made up of real nodes.
routes, err := cfg.FindRoutes(cfg.ValueMsat)
if err != nil {
return nil, err
}
if len(routes) == 0 {
return nil, fmt.Errorf("could not find any routes to self to " +
"use for blinded route construction")
}
// Not every route returned will necessarily result in a usable blinded
// path and so the number of paths returned might be less than the
// number of real routes returned by FindRoutes above.
paths := make([]*zpay32.BlindedPaymentPath, 0, len(routes))
// For each route returned, we will construct the associated blinded
// payment path.
for _, route := range routes {
// Extract the information we need from the route.
candidatePath := extractCandidatePath(route)
// Pad the given route with dummy hops until the minimum number
// of hops is met.
candidatePath.padWithDummyHops(cfg.MinNumHops)
path, err := buildBlindedPaymentPath(cfg, candidatePath)
if errors.Is(err, errInvalidBlindedPath) {
log.Debugf("Not using route (%s) as a blinded path "+
"since it resulted in an invalid blinded path",
route)
continue
} else if err != nil {
log.Errorf("Not using route (%s) as a blinded path: %v",
route, err)
continue
}
log.Debugf("Route selected for blinded path: %s", candidatePath)
paths = append(paths, path)
}
if len(paths) == 0 {
return nil, fmt.Errorf("could not build any blinded paths")
}
return paths, nil
}
// buildBlindedPaymentPath takes a route from an introduction node to this node
// and uses the given config to convert it into a blinded payment path.
func buildBlindedPaymentPath(cfg *BuildBlindedPathCfg, path *candidatePath) (
*zpay32.BlindedPaymentPath, error) {
hops, minHTLC, maxHTLC, err := collectRelayInfo(cfg, path)
if err != nil {
return nil, fmt.Errorf("could not collect blinded path relay "+
"info: %w", err)
}
relayInfo := make([]*record.PaymentRelayInfo, len(hops))
for i, hop := range hops {
relayInfo[i] = hop.relayInfo
}
// Using the collected relay info, we can calculate the aggregated
// policy values for the route.
baseFee, feeRate, cltvDelta := calcBlindedPathPolicies(
relayInfo, uint16(cfg.MinFinalCLTVExpiryDelta),
)
currentHeight, err := cfg.BestHeight()
if err != nil {
return nil, err
}
// The next step is to calculate the payment constraints to communicate
// to each hop and to package up the hop info for each hop. We will
// handle the final hop first since its payload looks a bit different,
// and then we will iterate backwards through the remaining hops.
//
// Note that the +1 here is required because the route won't have the
// introduction node included in the "Hops". But since we want to create
// payloads for all the hops as well as the introduction node, we add 1
// here to get the full hop length along with the introduction node.
hopDataSet := make([]*hopData, 0, len(path.hops)+1)
// Determine the maximum CLTV expiry for the destination node.
cltvExpiry := currentHeight + cfg.BlocksUntilExpiry +
cfg.MinFinalCLTVExpiryDelta
constraints := &record.PaymentConstraints{
MaxCltvExpiry: cltvExpiry,
HtlcMinimumMsat: minHTLC,
}
// If the blinded route has only a source node (introduction node) and
// no hops, then the destination node is also the source node.
finalHopPubKey := path.introNode
if len(path.hops) > 0 {
finalHopPubKey = path.hops[len(path.hops)-1].pubKey
}
// For the final hop, we only send it the path ID and payment
// constraints.
info, err := buildFinalHopRouteData(
finalHopPubKey, cfg.PathID, constraints,
)
if err != nil {
return nil, err
}
hopDataSet = append(hopDataSet, info)
// Iterate through the remaining (non-final) hops, back to front.
for i := len(hops) - 1; i >= 0; i-- {
hop := hops[i]
cltvExpiry += uint32(hop.relayInfo.CltvExpiryDelta)
constraints = &record.PaymentConstraints{
MaxCltvExpiry: cltvExpiry,
HtlcMinimumMsat: minHTLC,
}
var info *hopData
if hop.nextHopIsDummy {
info, err = buildDummyRouteData(
hop.hopPubKey, hop.relayInfo, constraints,
)
} else {
info, err = buildHopRouteData(
hop.hopPubKey, hop.nextSCID, hop.relayInfo,
constraints,
)
}
if err != nil {
return nil, err
}
hopDataSet = append(hopDataSet, info)
}
// Sort the hop info list in reverse order so that the data for the
// introduction node is first.
sort.Slice(hopDataSet, func(i, j int) bool {
return j < i
})
// Add padding to each route data instance until the encrypted data
// blobs are all the same size.
paymentPath, _, err := padHopInfo(
hopDataSet, true, record.AverageDummyHopPayloadSize,
)
if err != nil {
return nil, err
}
// Derive an ephemeral session key.
sessionKey, err := btcec.NewPrivateKey()
if err != nil {
return nil, err
}
// Encrypt the hop info.
blindedPath, err := sphinx.BuildBlindedPath(sessionKey, paymentPath)
if err != nil {
return nil, err
}
if len(blindedPath.BlindedHops) < 1 {
return nil, fmt.Errorf("blinded path must have at least one " +
"hop")
}
// Overwrite the introduction point's blinded pub key with the real
// pub key since then we can use this more compact format in the
// invoice without needing to encode the un-used blinded node pub key of
// the intro node.
blindedPath.BlindedHops[0].BlindedNodePub =
blindedPath.IntroductionPoint
// Now construct a z32 blinded path.
return &zpay32.BlindedPaymentPath{
FeeBaseMsat: uint32(baseFee),
FeeRate: feeRate,
CltvExpiryDelta: cltvDelta,
HTLCMinMsat: uint64(minHTLC),
HTLCMaxMsat: uint64(maxHTLC),
Features: lnwire.EmptyFeatureVector(),
FirstEphemeralBlindingPoint: blindedPath.BlindingPoint,
Hops: blindedPath.BlindedHops,
}, nil
}
// hopRelayInfo packages together the relay info to send to hop on a blinded
// path along with the pub key of that hop and the SCID that the hop should
// forward the payment on to.
type hopRelayInfo struct {
hopPubKey route.Vertex
nextSCID lnwire.ShortChannelID
relayInfo *record.PaymentRelayInfo
nextHopIsDummy bool
}
// collectRelayInfo collects the relay policy rules for each relay hop on the
// route and applies any policy buffers.
//
// For the blinded route:
//
// C --chan(CB)--> B --chan(BA)--> A
//
// where C is the introduction node, the route.Route struct we are given will
// have SourcePubKey set to C's pub key, and then it will have the following
// route.Hops:
//
// - PubKeyBytes: B, ChannelID: chan(CB)
// - PubKeyBytes: A, ChannelID: chan(BA)
//
// We, however, want to collect the channel policies for the following PubKey
// and ChannelID pairs:
//
// - PubKey: C, ChannelID: chan(CB)
// - PubKey: B, ChannelID: chan(BA)
//
// Therefore, when we go through the route and its hops to collect policies, our
// index for collecting public keys will be trailing that of the channel IDs by
// 1.
//
// For any dummy hops on the route, this function also decides what to use as
// policy values for the dummy hops. If there are other real hops, then the
// dummy hop policy values are derived by taking the average of the real
// policy values. If there are no real hops (in other words we are the
// introduction node), then we use some default routing values and we use the
// average of our channel capacities for the MaxHTLC value.
func collectRelayInfo(cfg *BuildBlindedPathCfg, path *candidatePath) (
[]*hopRelayInfo, lnwire.MilliSatoshi, lnwire.MilliSatoshi, error) {
var (
// The first pub key is that of the introduction node.
hopSource = path.introNode
// A collection of the policy values of real hops on the path.
policies = make(map[uint64]*BlindedHopPolicy)
hasDummyHops bool
)
// On this first iteration, we just collect policy values of the real
// hops on the path.
for _, hop := range path.hops {
// Once we have hit a dummy hop, all hops after will be dummy
// hops too.
if hop.isDummy {
hasDummyHops = true
break
}
// For real hops, retrieve the channel policy for this hop's
// channel ID in the direction pointing away from the hopSource
// node.
policy, err := getNodeChannelPolicy(
cfg, hop.channelID, hopSource,
)
if err != nil {
return nil, 0, 0, err
}
policies[hop.channelID] = policy
// This hop's pub key will be the policy creator for the next
// hop.
hopSource = hop.pubKey
}
var (
dummyHopPolicy *BlindedHopPolicy
err error
)
// If the path does have dummy hops, we need to decide which policy
// values to use for these hops.
if hasDummyHops {
dummyHopPolicy, err = computeDummyHopPolicy(
cfg.DefaultDummyHopPolicy, cfg.FetchOurOpenChannels,
policies,
)
if err != nil {
return nil, 0, 0, err
}
}
// We iterate through the hops one more time. This time it is to
// buffer the policy values, collect the payment relay info to send to
// each hop, and to compute the min and max HTLC values for the path.
var (
hops = make([]*hopRelayInfo, 0, len(path.hops))
minHTLC lnwire.MilliSatoshi
maxHTLC lnwire.MilliSatoshi
)
// The first pub key is that of the introduction node.
hopSource = path.introNode
for _, hop := range path.hops {
var (
policy = dummyHopPolicy
ok bool
err error
)
if !hop.isDummy {
policy, ok = policies[hop.channelID]
if !ok {
return nil, 0, 0, fmt.Errorf("no cached "+
"policy found for channel ID: %d",
hop.channelID)
}
}
if policy.MinHTLCMsat > cfg.ValueMsat {
return nil, 0, 0, fmt.Errorf("%w: minHTLC of hop "+
"policy larger than payment amt: sentAmt(%v), "+
"minHTLC(%v)", errInvalidBlindedPath,
cfg.ValueMsat, policy.MinHTLCMsat)
}
bufferPolicy, err := cfg.AddPolicyBuffer(policy)
if err != nil {
return nil, 0, 0, err
}
// We only use the new buffered policy if the new minHTLC value
// does not violate the sender amount.
//
// NOTE: We don't check this for maxHTLC, because the payment
// amount can always be splitted using MPP.
if bufferPolicy.MinHTLCMsat <= cfg.ValueMsat {
policy = bufferPolicy
}
// If this is the first policy we are collecting, then use this
// policy to set the base values for min/max htlc.
if len(hops) == 0 {
minHTLC = policy.MinHTLCMsat
maxHTLC = policy.MaxHTLCMsat
} else {
if policy.MinHTLCMsat > minHTLC {
minHTLC = policy.MinHTLCMsat
}
if policy.MaxHTLCMsat < maxHTLC {
maxHTLC = policy.MaxHTLCMsat
}
}
// From the policy values for this hop, we can collect the
// payment relay info that we will send to this hop.
hops = append(hops, &hopRelayInfo{
hopPubKey: hopSource,
nextSCID: lnwire.NewShortChanIDFromInt(hop.channelID),
relayInfo: &record.PaymentRelayInfo{
FeeRate: policy.FeeRate,
BaseFee: policy.BaseFee,
CltvExpiryDelta: policy.CLTVExpiryDelta,
},
nextHopIsDummy: hop.isDummy,
})
// This hop's pub key will be the policy creator for the next
// hop.
hopSource = hop.pubKey
}
// It can happen that there is no HTLC-range overlap between the various
// hops along the path. We return errInvalidBlindedPath to indicate that
// this route was not usable
if minHTLC > maxHTLC {
return nil, 0, 0, fmt.Errorf("%w: resulting blinded path min "+
"HTLC value is larger than the resulting max HTLC "+
"value", errInvalidBlindedPath)
}
return hops, minHTLC, maxHTLC, nil
}
// buildDummyRouteData constructs the record.BlindedRouteData struct for the
// given a hop in a blinded route where the following hop is a dummy hop.
func buildDummyRouteData(node route.Vertex, relayInfo *record.PaymentRelayInfo,
constraints *record.PaymentConstraints) (*hopData, error) {
nodeID, err := btcec.ParsePubKey(node[:])
if err != nil {
return nil, err
}
return &hopData{
data: record.NewDummyHopRouteData(
nodeID, *relayInfo, *constraints,
),
nodeID: nodeID,
}, nil
}
// computeDummyHopPolicy determines policy values to use for a dummy hop on a
// blinded path. If other real policy values exist, then we use the average of
// those values for the dummy hop policy values. Otherwise, in the case were
// there are no real policy values due to this node being the introduction node,
// we use the provided default policy values, and we get the average capacity of
// this node's channels to compute a MaxHTLC value.
func computeDummyHopPolicy(defaultPolicy *BlindedHopPolicy,
fetchOurChannels func() ([]*channeldb.OpenChannel, error),
policies map[uint64]*BlindedHopPolicy) (*BlindedHopPolicy, error) {
numPolicies := len(policies)
// If there are no real policies to calculate an average policy from,
// then we use the default. The only thing we need to calculate here
// though is the MaxHTLC value.
if numPolicies == 0 {
chans, err := fetchOurChannels()
if err != nil {
return nil, err
}
if len(chans) == 0 {
return nil, fmt.Errorf("node has no channels to " +
"receive on")
}
// Calculate the average channel capacity and use this as the
// MaxHTLC value.
var maxHTLC btcutil.Amount
for _, c := range chans {
maxHTLC += c.Capacity
}
maxHTLC = btcutil.Amount(float64(maxHTLC) / float64(len(chans)))
return &BlindedHopPolicy{
CLTVExpiryDelta: defaultPolicy.CLTVExpiryDelta,
FeeRate: defaultPolicy.FeeRate,
BaseFee: defaultPolicy.BaseFee,
MinHTLCMsat: defaultPolicy.MinHTLCMsat,
MaxHTLCMsat: lnwire.NewMSatFromSatoshis(maxHTLC),
}, nil
}
var avgPolicy BlindedHopPolicy
for _, policy := range policies {
avgPolicy.MinHTLCMsat += policy.MinHTLCMsat
avgPolicy.MaxHTLCMsat += policy.MaxHTLCMsat
avgPolicy.BaseFee += policy.BaseFee
avgPolicy.FeeRate += policy.FeeRate
avgPolicy.CLTVExpiryDelta += policy.CLTVExpiryDelta
}
avgPolicy.MinHTLCMsat = lnwire.MilliSatoshi(
float64(avgPolicy.MinHTLCMsat) / float64(numPolicies),
)
avgPolicy.MaxHTLCMsat = lnwire.MilliSatoshi(
float64(avgPolicy.MaxHTLCMsat) / float64(numPolicies),
)
avgPolicy.BaseFee = lnwire.MilliSatoshi(
float64(avgPolicy.BaseFee) / float64(numPolicies),
)
avgPolicy.FeeRate = uint32(
float64(avgPolicy.FeeRate) / float64(numPolicies),
)
avgPolicy.CLTVExpiryDelta = uint16(
float64(avgPolicy.CLTVExpiryDelta) / float64(numPolicies),
)
return &avgPolicy, nil
}
// buildHopRouteData constructs the record.BlindedRouteData struct for the given
// non-final hop on a blinded path and packages it with the node's ID.
func buildHopRouteData(node route.Vertex, scid lnwire.ShortChannelID,
relayInfo *record.PaymentRelayInfo,
constraints *record.PaymentConstraints) (*hopData, error) {
// Wrap up the data we want to send to this hop.
blindedRouteHopData := record.NewNonFinalBlindedRouteData(
scid, nil, *relayInfo, constraints, nil,
)
nodeID, err := btcec.ParsePubKey(node[:])
if err != nil {
return nil, err
}
return &hopData{
data: blindedRouteHopData,
nodeID: nodeID,
}, nil
}
// buildFinalHopRouteData constructs the record.BlindedRouteData struct for the
// final hop and packages it with the real node ID of the node it is intended
// for.
func buildFinalHopRouteData(node route.Vertex, pathID []byte,
constraints *record.PaymentConstraints) (*hopData, error) {
blindedRouteHopData := record.NewFinalHopBlindedRouteData(
constraints, pathID,
)
nodeID, err := btcec.ParsePubKey(node[:])
if err != nil {
return nil, err
}
return &hopData{
data: blindedRouteHopData,
nodeID: nodeID,
}, nil
}
// getNodeChanPolicy fetches the routing policy info for the given channel and
// node pair.
func getNodeChannelPolicy(cfg *BuildBlindedPathCfg, chanID uint64,
nodeID route.Vertex) (*BlindedHopPolicy, error) {
// Attempt to fetch channel updates for the given channel. We will have
// at most two updates for a given channel.
_, update1, update2, err := cfg.FetchChannelEdgesByID(chanID)
if err != nil {
return nil, err
}
// Now we need to determine which of the updates was created by the
// node in question. We know the update is the correct one if the
// "ToNode" for the fetched policy is _not_ equal to the node ID in
// question.
var policy *models.ChannelEdgePolicy
switch {
case update1 != nil && !bytes.Equal(update1.ToNode[:], nodeID[:]):
policy = update1
case update2 != nil && !bytes.Equal(update2.ToNode[:], nodeID[:]):
policy = update2
default:
return nil, fmt.Errorf("no channel updates found from node "+
"%s for channel %d", nodeID, chanID)
}
return &BlindedHopPolicy{
CLTVExpiryDelta: policy.TimeLockDelta,
FeeRate: uint32(policy.FeeProportionalMillionths),
BaseFee: policy.FeeBaseMSat,
MinHTLCMsat: policy.MinHTLC,
MaxHTLCMsat: policy.MaxHTLC,
}, nil
}
// candidatePath holds all the information about a route to this node that we
// need in order to build a blinded route.
type candidatePath struct {
introNode route.Vertex
finalNodeID route.Vertex
hops []*blindedPathHop
}
// String returns a string representation of the candidatePath which can be
// useful for logging and debugging.
func (c *candidatePath) String() string {
str := fmt.Sprintf("[%s (intro node)]", c.introNode)
for _, hop := range c.hops {
if hop.isDummy {
str += "--->[dummy hop]"
continue
}
str += fmt.Sprintf("--<%d>-->[%s]", hop.channelID, hop.pubKey)
}
return str
}
// padWithDummyHops will append n dummy hops to the candidatePath hop set. The
// pub key for the dummy hop will be the same as the pub key for the final hop
// of the path. That way, the final hop will be able to decrypt the data
// encrypted for each dummy hop.
func (c *candidatePath) padWithDummyHops(n uint8) {
for len(c.hops) < int(n) {
c.hops = append(c.hops, &blindedPathHop{
pubKey: c.finalNodeID,
isDummy: true,
})
}
}
// blindedPathHop holds the information we need to know about a hop in a route
// in order to use it in the construction of a blinded path.
type blindedPathHop struct {
// pubKey is the real pub key of a node on a blinded path.
pubKey route.Vertex
// channelID is the channel along which the previous hop should forward
// their HTLC in order to reach this hop.
channelID uint64
// isDummy is true if this hop is an appended dummy hop.
isDummy bool
}
// extractCandidatePath extracts the data it needs from the given route.Route in
// order to construct a candidatePath.
func extractCandidatePath(path *route.Route) *candidatePath {
var (
hops = make([]*blindedPathHop, len(path.Hops))
finalNode = path.SourcePubKey
)
for i, hop := range path.Hops {
hops[i] = &blindedPathHop{
pubKey: hop.PubKeyBytes,
channelID: hop.ChannelID,
}
if i == len(path.Hops)-1 {
finalNode = hop.PubKeyBytes
}
}
return &candidatePath{
introNode: path.SourcePubKey,
finalNodeID: finalNode,
hops: hops,
}
}
// BlindedHopPolicy holds the set of relay policy values to use for a channel
// in a blinded path.
type BlindedHopPolicy struct {
CLTVExpiryDelta uint16
FeeRate uint32
BaseFee lnwire.MilliSatoshi
MinHTLCMsat lnwire.MilliSatoshi
MaxHTLCMsat lnwire.MilliSatoshi
}
// AddPolicyBuffer constructs the bufferedChanPolicies for a path hop by taking
// its actual policy values and multiplying them by the given multipliers.
// The base fee, fee rate and minimum HTLC msat values are adjusted via the
// incMultiplier while the maximum HTLC msat value is adjusted via the
// decMultiplier. If adjustments of the HTLC values no longer make sense
// then the original HTLC value is used.
func AddPolicyBuffer(policy *BlindedHopPolicy, incMultiplier,
decMultiplier float64) (*BlindedHopPolicy, error) {
if incMultiplier < 1 {
return nil, fmt.Errorf("blinded path policy increase " +
"multiplier must be greater than or equal to 1")
}
if decMultiplier < 0 || decMultiplier > 1 {
return nil, fmt.Errorf("blinded path policy decrease " +
"multiplier must be in the range [0;1]")
}
var (
minHTLCMsat = lnwire.MilliSatoshi(
float64(policy.MinHTLCMsat) * incMultiplier,
)
maxHTLCMsat = lnwire.MilliSatoshi(
float64(policy.MaxHTLCMsat) * decMultiplier,
)
)
// Make sure the new minimum is not more than the original maximum.
// If it is, then just stick to the original minimum.
if minHTLCMsat > policy.MaxHTLCMsat {
minHTLCMsat = policy.MinHTLCMsat
}
// Make sure the new maximum is not less than the original minimum.
// If it is, then just stick to the original maximum.
if maxHTLCMsat < policy.MinHTLCMsat {
maxHTLCMsat = policy.MaxHTLCMsat
}
// Also ensure that the new htlc bounds make sense. If the new minimum
// is greater than the new maximum, then just let both to their original
// values.
if minHTLCMsat > maxHTLCMsat {
minHTLCMsat = policy.MinHTLCMsat
maxHTLCMsat = policy.MaxHTLCMsat
}
return &BlindedHopPolicy{
CLTVExpiryDelta: uint16(
float64(policy.CLTVExpiryDelta) * incMultiplier,
),
FeeRate: uint32(
float64(policy.FeeRate) * incMultiplier,
),
BaseFee: lnwire.MilliSatoshi(
float64(policy.BaseFee) * incMultiplier,
),
MinHTLCMsat: minHTLCMsat,
MaxHTLCMsat: maxHTLCMsat,
}, nil
}
// calcBlindedPathPolicies computes the accumulated policy values for the path.
// These values include the total base fee, the total proportional fee and the
// total CLTV delta. This function assumes that all the passed relay infos have
// already been adjusted with a buffer to account for easy probing attacks.
func calcBlindedPathPolicies(relayInfo []*record.PaymentRelayInfo,
ourMinFinalCLTVDelta uint16) (lnwire.MilliSatoshi, uint32, uint16) {
var (
totalFeeBase lnwire.MilliSatoshi
totalFeeProp uint32
totalCLTV = ourMinFinalCLTVDelta
)
// Use the algorithms defined in BOLT 4 to calculate the accumulated
// relay fees for the route:
//nolint:ll
// https://github.com/lightning/bolts/blob/db278ab9b2baa0b30cfe79fb3de39280595938d3/04-onion-routing.md?plain=1#L255
for i := len(relayInfo) - 1; i >= 0; i-- {
info := relayInfo[i]
totalFeeBase = calcNextTotalBaseFee(
totalFeeBase, info.BaseFee, info.FeeRate,
)
totalFeeProp = calcNextTotalFeeRate(totalFeeProp, info.FeeRate)
totalCLTV += info.CltvExpiryDelta
}
return totalFeeBase, totalFeeProp, totalCLTV
}
// calcNextTotalBaseFee takes the current total accumulated base fee of a
// blinded path at hop `n` along with the fee rate and base fee of the hop at
// `n+1` and uses these to calculate the accumulated base fee at hop `n+1`.
func calcNextTotalBaseFee(currentTotal, hopBaseFee lnwire.MilliSatoshi,
hopFeeRate uint32) lnwire.MilliSatoshi {
numerator := (uint32(hopBaseFee) * oneMillion) +
(uint32(currentTotal) * (oneMillion + hopFeeRate)) +
oneMillion - 1
return lnwire.MilliSatoshi(numerator / oneMillion)
}
// calculateNextTotalFeeRate takes the current total accumulated fee rate of a
// blinded path at hop `n` along with the fee rate of the hop at `n+1` and uses
// these to calculate the accumulated fee rate at hop `n+1`.
func calcNextTotalFeeRate(currentTotal, hopFeeRate uint32) uint32 {
numerator := (currentTotal+hopFeeRate)*oneMillion +
currentTotal*hopFeeRate + oneMillion - 1
return numerator / oneMillion
}
// hopData packages the record.BlindedRouteData for a hop on a blinded path with
// the real node ID of that hop.
type hopData struct {
data *record.BlindedRouteData
nodeID *btcec.PublicKey
}
// padStats can be used to keep track of various pieces of data that we collect
// during a call to padHopInfo. This is useful for logging and for test
// assertions.
type padStats struct {
minPayloadSize int
maxPayloadSize int
finalPaddedSize int
numIterations int
}
// padHopInfo iterates over a set of record.BlindedRouteData and adds padding
// where needed until the resulting encrypted data blobs are all the same size.
// This may take a few iterations due to the fact that a TLV field is used to
// add this padding. For example, if we want to add a 1 byte padding to a
// record.BlindedRouteData when it does not yet have any padding, then adding
// a 1 byte padding will actually add 3 bytes due to the bytes required when
// adding the initial type and length bytes. However, on the next iteration if
// we again add just 1 byte, then only a single byte will be added. The same
// iteration is required for padding values on the BigSize encoding bucket
// edges. The number of iterations that this function takes is also returned for
// testing purposes. If prePad is true, then zero byte padding is added to each
// payload that does not yet have padding. This will save some iterations for
// the majority of cases. minSize can be used to specify a minimum size that all
// payloads should be.
func padHopInfo(hopInfo []*hopData, prePad bool, minSize int) (
[]*sphinx.HopInfo, *padStats, error) {
var (
paymentPath = make([]*sphinx.HopInfo, len(hopInfo))
stats = padStats{finalPaddedSize: minSize}
)
// Pre-pad each payload with zero byte padding (if it does not yet have
// padding) to save a couple of iterations in the majority of cases.
if prePad {
for _, info := range hopInfo {
if info.data.Padding.IsSome() {
continue
}
info.data.PadBy(0)
}
}
for {
stats.numIterations++
// On each iteration of the loop, we first determine the
// current largest encoded data blob size. This will be the
// size we aim to get the others to match.
var (
maxLen = minSize
minLen = math.MaxInt8
)
for i, hop := range hopInfo {
plainText, err := record.EncodeBlindedRouteData(
hop.data,
)
if err != nil {
return nil, nil, err
}
if len(plainText) > maxLen {
maxLen = len(plainText)
// Update the stats to take note of this new
// max since this may be the final max that all
// payloads will be padded to.
stats.finalPaddedSize = maxLen
}
if len(plainText) < minLen {
minLen = len(plainText)
}
paymentPath[i] = &sphinx.HopInfo{
NodePub: hop.nodeID,
PlainText: plainText,
}
}
// If this is our first iteration, then we take note of the min
// and max lengths of the payloads pre-padding for logging
// later.
if stats.numIterations == 1 {
stats.minPayloadSize = minLen
stats.maxPayloadSize = maxLen
}
// Now we iterate over them again and determine which ones we
// need to add padding to.
var numEqual int
for i, hop := range hopInfo {
plainText := paymentPath[i].PlainText
// If the plaintext length is equal to the desired
// length, then we can continue. We use numEqual to
// keep track of how many have the same length.
if len(plainText) == maxLen {
numEqual++
continue
}
// If we previously added padding to this hop, we keep
// the length of that initial padding too.
var existingPadding int
hop.data.Padding.WhenSome(
func(p tlv.RecordT[tlv.TlvType1, []byte]) {
existingPadding = len(p.Val)
},
)
// Add some padding bytes to the hop.
hop.data.PadBy(
existingPadding + maxLen - len(plainText),
)
}
// If all the payloads have the same length, we can exit the
// loop.
if numEqual == len(hopInfo) {
break
}
}
log.Debugf("Finished padding %d blinded path payloads to %d bytes "+
"each where the pre-padded min and max sizes were %d and %d "+
"bytes respectively", len(hopInfo), stats.finalPaddedSize,
stats.minPayloadSize, stats.maxPayloadSize)
return paymentPath, &stats, nil
}
package blindedpath
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This means the
// package will not perform any logging by default until the caller requests
// it.
var log btclog.Logger
const Subsystem = "BLPT"
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled by
// default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info. This
// should be used in preference to SetLogWriter if the caller is also using
// btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package routing
import (
"bytes"
"errors"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
// BlindedPathNUMSHex is the hex encoded version of the blinded path target
// NUMs key (in compressed format) which has no known private key.
// This was generated using the following script:
// https://github.com/lightninglabs/lightning-node-connect/tree/master/
// mailbox/numsgen, with the seed phrase "Lightning Blinded Path".
const BlindedPathNUMSHex = "02667a98ef82ecb522f803b17a74f14508a48b25258f9831" +
"dd6e95f5e299dfd54e"
var (
// ErrNoBlindedPath is returned when the blinded path in a blinded
// payment is missing.
ErrNoBlindedPath = errors.New("blinded path required")
// ErrInsufficientBlindedHops is returned when a blinded path does
// not have enough blinded hops.
ErrInsufficientBlindedHops = errors.New("blinded path requires " +
"at least one hop")
// ErrHTLCRestrictions is returned when a blinded path has invalid
// HTLC maximum and minimum values.
ErrHTLCRestrictions = errors.New("invalid htlc minimum and maximum")
// BlindedPathNUMSKey is a NUMS key (nothing up my sleeves number) that
// has no known private key.
BlindedPathNUMSKey = input.MustParsePubKey(BlindedPathNUMSHex)
// CompressedBlindedPathNUMSKey is the compressed version of the
// BlindedPathNUMSKey.
CompressedBlindedPathNUMSKey = BlindedPathNUMSKey.SerializeCompressed()
)
// BlindedPaymentPathSet groups the data we need to handle sending to a set of
// blinded paths provided by the recipient of a payment.
//
// NOTE: for now this only holds a single BlindedPayment. By the end of the PR
// series, it will handle multiple paths.
type BlindedPaymentPathSet struct {
// paths is the set of blinded payment paths for a single payment.
// NOTE: For now this will always only have a single entry. By the end
// of this PR, it can hold multiple.
paths []*BlindedPayment
// targetPubKey is the ephemeral node pub key that we will inject into
// each path as the last hop. This is only for the sake of path finding.
// Once the path has been found, the original destination pub key is
// used again. In the edge case where there is only a single hop in the
// path (the introduction node is the destination node), then this will
// just be the introduction node's real public key.
targetPubKey *btcec.PublicKey
// features is the set of relay features available for the payment.
// This is extracted from the set of blinded payment paths. At the
// moment we require that all paths for the same payment have the
// same feature set.
features *lnwire.FeatureVector
// finalCLTV is the final hop's expiry delta of _any_ path in the set.
// For any multi-hop path, the final CLTV delta should be seen as zero
// since the final hop's final CLTV delta is accounted for in the
// accumulated path policy values. The only edge case is for when the
// final hop in the path is also the introduction node in which case
// that path's FinalCLTV must be the non-zero min CLTV of the final hop
// so that it is accounted for in path finding. For this reason, if
// we have any single path in the set with only one hop, then we throw
// away all the other paths. This should be fine to do since if there is
// a path where the intro node is also the destination node, then there
// isn't any need to try any other longer blinded path. In other words,
// if this value is non-zero, then there is only one path in this
// blinded path set and that path only has a single hop: the
// introduction node.
finalCLTV uint16
}
// NewBlindedPaymentPathSet constructs a new BlindedPaymentPathSet from a set of
// BlindedPayments. For blinded paths which have more than one single hop a
// dummy hop via a NUMS key is appeneded to allow for MPP path finding via
// multiple blinded paths.
func NewBlindedPaymentPathSet(paths []*BlindedPayment) (*BlindedPaymentPathSet,
error) {
if len(paths) == 0 {
return nil, ErrNoBlindedPath
}
// For now, we assert that all the paths have the same set of features.
features := paths[0].Features
noFeatures := features == nil || features.IsEmpty()
for i := 1; i < len(paths); i++ {
noFeats := paths[i].Features == nil ||
paths[i].Features.IsEmpty()
if noFeatures && !noFeats {
return nil, fmt.Errorf("all blinded paths must have " +
"the same set of features")
}
if noFeatures {
continue
}
if !features.RawFeatureVector.Equals(
paths[i].Features.RawFeatureVector,
) {
return nil, fmt.Errorf("all blinded paths must have " +
"the same set of features")
}
}
// Deep copy the paths to avoid mutating the original paths.
pathSet := make([]*BlindedPayment, len(paths))
for i, path := range paths {
pathSet[i] = path.deepCopy()
}
// For blinded paths we use the NUMS key as a target if the blinded
// path has more hops than just the introduction node.
targetPub := &BlindedPathNUMSKey
var finalCLTVDelta uint16
// In case the paths do NOT include a single hop route we append a
// dummy hop via a NUMS key to allow for MPP path finding via multiple
// blinded paths. A unified target is needed to use all blinded paths
// during the payment lifecycle. A dummy hop is solely added for the
// path finding process and is removed after the path is found. This
// ensures that we still populate the mission control with the correct
// data and also respect these mc entries when looking for a path.
for _, path := range pathSet {
pathLength := len(path.BlindedPath.BlindedHops)
// If any provided blinded path only has a single hop (ie, the
// destination node is also the introduction node), then we
// discard all other paths since we know the real pub key of the
// destination node. We also then set the final CLTV delta to
// the path's delta since there are no other edge hints that
// will account for it.
if pathLength == 1 {
pathSet = []*BlindedPayment{path}
finalCLTVDelta = path.CltvExpiryDelta
targetPub = path.BlindedPath.IntroductionPoint
break
}
lastHop := path.BlindedPath.BlindedHops[pathLength-1]
path.BlindedPath.BlindedHops = append(
path.BlindedPath.BlindedHops,
&sphinx.BlindedHopInfo{
BlindedNodePub: &BlindedPathNUMSKey,
// We add the last hop's cipher text so that
// the payload size of the final hop is equal
// to the real last hop.
CipherText: lastHop.CipherText,
},
)
}
return &BlindedPaymentPathSet{
paths: pathSet,
targetPubKey: targetPub,
features: features,
finalCLTV: finalCLTVDelta,
}, nil
}
// TargetPubKey returns the public key to be used as the destination node's
// public key during pathfinding.
func (s *BlindedPaymentPathSet) TargetPubKey() *btcec.PublicKey {
return s.targetPubKey
}
// Features returns the set of relay features available for the payment.
func (s *BlindedPaymentPathSet) Features() *lnwire.FeatureVector {
return s.features
}
// IntroNodeOnlyPath can be called if it is expected that the path set only
// contains a single payment path which itself only has one hop. It errors if
// this is not the case.
func (s *BlindedPaymentPathSet) IntroNodeOnlyPath() (*BlindedPayment, error) {
if len(s.paths) != 1 {
return nil, fmt.Errorf("expected only a single path in the "+
"blinded payment set, got %d", len(s.paths))
}
if len(s.paths[0].BlindedPath.BlindedHops) > 1 {
return nil, fmt.Errorf("an intro node only path cannot have " +
"more than one hop")
}
return s.paths[0], nil
}
// IsIntroNode returns true if the given vertex is an introduction node for one
// of the paths in the blinded payment path set.
func (s *BlindedPaymentPathSet) IsIntroNode(source route.Vertex) bool {
for _, path := range s.paths {
introVertex := route.NewVertex(
path.BlindedPath.IntroductionPoint,
)
if source == introVertex {
return true
}
}
return false
}
// FinalCLTVDelta is the minimum CLTV delta to use for the final hop on the
// route. In most cases this will return zero since the value is accounted for
// in the path's accumulated CLTVExpiryDelta. Only in the edge case of the path
// set only including a single path which only includes an introduction node
// will this return a non-zero value.
func (s *BlindedPaymentPathSet) FinalCLTVDelta() uint16 {
return s.finalCLTV
}
// LargestLastHopPayloadPath returns the BlindedPayment in the set that has the
// largest last-hop payload. This is to be used for onion size estimation in
// path finding.
func (s *BlindedPaymentPathSet) LargestLastHopPayloadPath() (*BlindedPayment,
error) {
var (
largestPath *BlindedPayment
currentMax int
)
if len(s.paths) == 0 {
return nil, fmt.Errorf("no blinded paths in the set")
}
// We set the largest path to make sure we always return a path even
// if the cipher text is empty.
largestPath = s.paths[0]
for _, path := range s.paths {
numHops := len(path.BlindedPath.BlindedHops)
lastHop := path.BlindedPath.BlindedHops[numHops-1]
if len(lastHop.CipherText) > currentMax {
largestPath = path
currentMax = len(lastHop.CipherText)
}
}
return largestPath, nil
}
// ToRouteHints converts the blinded path payment set into a RouteHints map so
// that the blinded payment paths can be treated like route hints throughout the
// code base.
func (s *BlindedPaymentPathSet) ToRouteHints() (RouteHints, error) {
hints := make(RouteHints)
for _, path := range s.paths {
pathHints, err := path.toRouteHints()
if err != nil {
return nil, err
}
for from, edges := range pathHints {
hints[from] = append(hints[from], edges...)
}
}
if len(hints) == 0 {
return nil, nil
}
return hints, nil
}
// IsBlindedRouteNUMSTargetKey returns true if the given public key is the
// NUMS key used as a target for blinded path final hops.
func IsBlindedRouteNUMSTargetKey(pk []byte) bool {
return bytes.Equal(pk, CompressedBlindedPathNUMSKey)
}
// BlindedPayment provides the path and payment parameters required to send a
// payment along a blinded path.
type BlindedPayment struct {
// BlindedPath contains the unblinded introduction point and blinded
// hops for the blinded section of the payment.
BlindedPath *sphinx.BlindedPath
// BaseFee is the total base fee to be paid for payments made over the
// blinded path.
BaseFee uint32
// ProportionalFeeRate is the aggregated proportional fee rate for
// payments made over the blinded path.
ProportionalFeeRate uint32
// CltvExpiryDelta is the total expiry delta for the blinded path. This
// field includes the CLTV for the blinded hops *and* the final cltv
// delta for the receiver.
CltvExpiryDelta uint16
// HtlcMinimum is the highest HLTC minimum supported along the blinded
// path (while some hops may have lower values, we're effectively
// bounded by the highest minimum).
HtlcMinimum uint64
// HtlcMaximum is the lowest HTLC maximum supported along the blinded
// path (while some hops may have higher values, we're effectively
// bounded by the lowest maximum).
HtlcMaximum uint64
// Features is the set of relay features available for the payment.
Features *lnwire.FeatureVector
}
// Validate performs validation on a blinded payment.
func (b *BlindedPayment) Validate() error {
if b.BlindedPath == nil {
return ErrNoBlindedPath
}
// The sphinx library inserts the introduction node as the first hop,
// so we expect at least one hop.
if len(b.BlindedPath.BlindedHops) < 1 {
return fmt.Errorf("%w got: %v", ErrInsufficientBlindedHops,
len(b.BlindedPath.BlindedHops))
}
if b.HtlcMaximum < b.HtlcMinimum {
return fmt.Errorf("%w: %v < %v", ErrHTLCRestrictions,
b.HtlcMaximum, b.HtlcMinimum)
}
for _, hop := range b.BlindedPath.BlindedHops {
// The first hop of the blinded path does not necessarily have
// blinded node pub key because it is the introduction point.
if hop.BlindedNodePub == nil {
continue
}
if IsBlindedRouteNUMSTargetKey(
hop.BlindedNodePub.SerializeCompressed(),
) {
return fmt.Errorf("blinded path cannot include NUMS "+
"key: %s", BlindedPathNUMSHex)
}
}
return nil
}
// toRouteHints produces a set of chained route hints that represent a blinded
// path. In the case of a single hop blinded route (which is paying directly
// to the introduction point), no hints will be returned. In this case callers
// *must* account for the blinded route's CLTV delta elsewhere (as this is
// effectively the final_cltv_delta for the receiving introduction node). In
// the case of multiple blinded hops, CLTV delta is fully accounted for in the
// hints (both for intermediate hops and the final_cltv_delta for the receiving
// node).
func (b *BlindedPayment) toRouteHints() (RouteHints, error) {
// If we just have a single hop in our blinded route, it just contains
// an introduction node (this is a valid path according to the spec).
// Since we have the un-blinded node ID for the introduction node, we
// don't need to add any route hints.
if len(b.BlindedPath.BlindedHops) == 1 {
return nil, nil
}
hintCount := len(b.BlindedPath.BlindedHops) - 1
hints := make(
RouteHints, hintCount,
)
// Start at the unblinded introduction node, because our pathfinding
// will be able to locate this point in the graph.
fromNode := route.NewVertex(b.BlindedPath.IntroductionPoint)
features := lnwire.EmptyFeatureVector()
if b.Features != nil {
features = b.Features.Clone()
}
// Use the total aggregate relay parameters for the entire blinded
// route as the policy for the hint from our introduction node. This
// will ensure that pathfinding provides sufficient fees/delay for the
// blinded portion to the introduction node.
firstBlindedHop := b.BlindedPath.BlindedHops[1].BlindedNodePub
edgePolicy := &models.CachedEdgePolicy{
TimeLockDelta: b.CltvExpiryDelta,
MinHTLC: lnwire.MilliSatoshi(b.HtlcMinimum),
MaxHTLC: lnwire.MilliSatoshi(b.HtlcMaximum),
FeeBaseMSat: lnwire.MilliSatoshi(b.BaseFee),
FeeProportionalMillionths: lnwire.MilliSatoshi(
b.ProportionalFeeRate,
),
ToNodePubKey: func() route.Vertex {
return route.NewVertex(
// The first node in this slice is
// the introduction node, so we start
// at index 1 to get the first blinded
// relaying node.
firstBlindedHop,
)
},
ToNodeFeatures: features,
}
lastEdge, err := NewBlindedEdge(edgePolicy, b, 0)
if err != nil {
return nil, err
}
hints[fromNode] = []AdditionalEdge{lastEdge}
// Start at an offset of 1 because the first node in our blinded hops
// is the introduction node and terminate at the second-last node
// because we're dealing with hops as pairs.
for i := 1; i < hintCount; i++ {
// Set our origin node to the current
fromNode = route.NewVertex(
b.BlindedPath.BlindedHops[i].BlindedNodePub,
)
// Create a hint which has no fee or cltv delta. We
// specifically want zero values here because our relay
// parameters are expressed in encrypted blobs rather than the
// route itself for blinded routes.
nextHopIdx := i + 1
nextNode := route.NewVertex(
b.BlindedPath.BlindedHops[nextHopIdx].BlindedNodePub,
)
edgePolicy := &models.CachedEdgePolicy{
ToNodePubKey: func() route.Vertex {
return nextNode
},
ToNodeFeatures: features,
}
lastEdge, err = NewBlindedEdge(edgePolicy, b, i)
if err != nil {
return nil, err
}
hints[fromNode] = []AdditionalEdge{lastEdge}
}
return hints, nil
}
// deepCopy returns a deep copy of the BlindedPayment.
func (b *BlindedPayment) deepCopy() *BlindedPayment {
if b == nil {
return nil
}
cpyPayment := &BlindedPayment{
BaseFee: b.BaseFee,
ProportionalFeeRate: b.ProportionalFeeRate,
CltvExpiryDelta: b.CltvExpiryDelta,
HtlcMinimum: b.HtlcMinimum,
HtlcMaximum: b.HtlcMaximum,
}
// Deep copy the BlindedPath if it exists
if b.BlindedPath != nil {
cpyPayment.BlindedPath = &sphinx.BlindedPath{
BlindedHops: make([]*sphinx.BlindedHopInfo,
len(b.BlindedPath.BlindedHops)),
}
if b.BlindedPath.IntroductionPoint != nil {
cpyPayment.BlindedPath.IntroductionPoint =
copyPublicKey(b.BlindedPath.IntroductionPoint)
}
if b.BlindedPath.BlindingPoint != nil {
cpyPayment.BlindedPath.BlindingPoint =
copyPublicKey(b.BlindedPath.BlindingPoint)
}
// Copy each blinded hop info.
for i, hop := range b.BlindedPath.BlindedHops {
if hop == nil {
continue
}
cpyHop := &sphinx.BlindedHopInfo{
CipherText: hop.CipherText,
}
if hop.BlindedNodePub != nil {
cpyHop.BlindedNodePub =
copyPublicKey(hop.BlindedNodePub)
}
cpyHop.CipherText = make([]byte, len(hop.CipherText))
copy(cpyHop.CipherText, hop.CipherText)
cpyPayment.BlindedPath.BlindedHops[i] = cpyHop
}
}
// Deep copy the Features if they exist
if b.Features != nil {
cpyPayment.Features = b.Features.Clone()
}
return cpyPayment
}
// copyPublicKey makes a deep copy of a public key.
//
// TODO(ziggie): Remove this function if this is available in the btcec library.
func copyPublicKey(pk *btcec.PublicKey) *btcec.PublicKey {
var result secp256k1.JacobianPoint
pk.AsJacobian(&result)
result.ToAffine()
return btcec.NewPublicKey(&result.X, &result.Y)
}
package chainview
import (
"bytes"
"encoding/hex"
"fmt"
"sync"
"sync/atomic"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/lightningnetwork/lnd/blockcache"
graphdb "github.com/lightningnetwork/lnd/graph/db"
)
// BitcoindFilteredChainView is an implementation of the FilteredChainView
// interface which is backed by bitcoind.
type BitcoindFilteredChainView struct {
started int32 // To be used atomically.
stopped int32 // To be used atomically.
// bestHeight is the height of the latest block added to the
// blockQueue from the onFilteredConnectedMethod. It is used to
// determine up to what height we would need to rescan in case
// of a filter update.
bestHeightMtx sync.Mutex
bestHeight uint32
// TODO: Factor out common logic between bitcoind and btcd into a
// NodeFilteredView interface.
chainClient *chain.BitcoindClient
// blockEventQueue is the ordered queue used to keep the order
// of connected and disconnected blocks sent to the reader of the
// chainView.
blockQueue *blockEventQueue
// blockCache is an LRU block cache.
blockCache *blockcache.BlockCache
// filterUpdates is a channel in which updates to the utxo filter
// attached to this instance are sent over.
filterUpdates chan filterUpdate
// chainFilter is the set of utox's that we're currently watching
// spends for within the chain.
filterMtx sync.RWMutex
chainFilter map[wire.OutPoint]struct{}
// filterBlockReqs is a channel in which requests to filter select
// blocks will be sent over.
filterBlockReqs chan *filterBlockReq
quit chan struct{}
wg sync.WaitGroup
}
// A compile time check to ensure BitcoindFilteredChainView implements the
// chainview.FilteredChainView.
var _ FilteredChainView = (*BitcoindFilteredChainView)(nil)
// NewBitcoindFilteredChainView creates a new instance of a FilteredChainView
// from RPC credentials and a ZMQ socket address for a bitcoind instance.
func NewBitcoindFilteredChainView(
chainConn *chain.BitcoindConn,
blockCache *blockcache.BlockCache) *BitcoindFilteredChainView {
chainView := &BitcoindFilteredChainView{
chainFilter: make(map[wire.OutPoint]struct{}),
filterUpdates: make(chan filterUpdate),
filterBlockReqs: make(chan *filterBlockReq),
blockCache: blockCache,
quit: make(chan struct{}),
}
chainView.chainClient = chainConn.NewBitcoindClient()
chainView.blockQueue = newBlockEventQueue()
return chainView
}
// Start starts all goroutines necessary for normal operation.
//
// NOTE: This is part of the FilteredChainView interface.
func (b *BitcoindFilteredChainView) Start() error {
// Already started?
if atomic.AddInt32(&b.started, 1) != 1 {
return nil
}
log.Infof("FilteredChainView starting")
err := b.chainClient.Start()
if err != nil {
return err
}
err = b.chainClient.NotifyBlocks()
if err != nil {
return err
}
_, bestHeight, err := b.chainClient.GetBestBlock()
if err != nil {
return err
}
b.bestHeightMtx.Lock()
b.bestHeight = uint32(bestHeight)
b.bestHeightMtx.Unlock()
b.blockQueue.Start()
b.wg.Add(1)
go b.chainFilterer()
return nil
}
// Stop stops all goroutines which we launched by the prior call to the Start
// method.
//
// NOTE: This is part of the FilteredChainView interface.
func (b *BitcoindFilteredChainView) Stop() error {
log.Debug("BitcoindFilteredChainView stopping")
defer log.Debug("BitcoindFilteredChainView stopped")
// Already shutting down?
if atomic.AddInt32(&b.stopped, 1) != 1 {
return nil
}
// Shutdown the rpc client, this gracefully disconnects from bitcoind's
// zmq socket, and cleans up all related resources.
b.chainClient.Stop()
b.chainClient.WaitForShutdown()
b.blockQueue.Stop()
close(b.quit)
b.wg.Wait()
return nil
}
// onFilteredBlockConnected is called for each block that's connected to the
// end of the main chain. Based on our current chain filter, the block may or
// may not include any relevant transactions.
func (b *BitcoindFilteredChainView) onFilteredBlockConnected(height int32,
hash chainhash.Hash, txns []*wtxmgr.TxRecord) {
mtxs := make([]*wire.MsgTx, len(txns))
b.filterMtx.Lock()
for i, tx := range txns {
mtxs[i] = &tx.MsgTx
for _, txIn := range mtxs[i].TxIn {
// We can delete this outpoint from the chainFilter, as
// we just received a block where it was spent. In case
// of a reorg, this outpoint might get "un-spent", but
// that's okay since it would never be wise to consider
// the channel open again (since a spending transaction
// exists on the network).
delete(b.chainFilter, txIn.PreviousOutPoint)
}
}
b.filterMtx.Unlock()
// We record the height of the last connected block added to the
// blockQueue such that we can scan up to this height in case of
// a rescan. It must be protected by a mutex since a filter update
// might be trying to read it concurrently.
b.bestHeightMtx.Lock()
b.bestHeight = uint32(height)
b.bestHeightMtx.Unlock()
block := &FilteredBlock{
Hash: hash,
Height: uint32(height),
Transactions: mtxs,
}
b.blockQueue.Add(&blockEvent{
eventType: connected,
block: block,
})
}
// onFilteredBlockDisconnected is a callback which is executed once a block is
// disconnected from the end of the main chain.
func (b *BitcoindFilteredChainView) onFilteredBlockDisconnected(height int32,
hash chainhash.Hash) {
log.Debugf("got disconnected block at height %d: %v", height,
hash)
filteredBlock := &FilteredBlock{
Hash: hash,
Height: uint32(height),
}
b.blockQueue.Add(&blockEvent{
eventType: disconnected,
block: filteredBlock,
})
}
// FilterBlock takes a block hash, and returns a FilteredBlocks which is the
// result of applying the current registered UTXO sub-set on the block
// corresponding to that block hash. If any watched UTOX's are spent by the
// selected lock, then the internal chainFilter will also be updated.
//
// NOTE: This is part of the FilteredChainView interface.
func (b *BitcoindFilteredChainView) FilterBlock(blockHash *chainhash.Hash) (*FilteredBlock, error) {
req := &filterBlockReq{
blockHash: blockHash,
resp: make(chan *FilteredBlock, 1),
err: make(chan error, 1),
}
select {
case b.filterBlockReqs <- req:
case <-b.quit:
return nil, fmt.Errorf("FilteredChainView shutting down")
}
return <-req.resp, <-req.err
}
// chainFilterer is the primary goroutine which: listens for new blocks coming
// and dispatches the relevant FilteredBlock notifications, updates the filter
// due to requests by callers, and finally is able to perform targeted block
// filtration.
//
// TODO(roasbeef): change to use loadfilter RPC's
func (b *BitcoindFilteredChainView) chainFilterer() {
defer b.wg.Done()
// filterBlock is a helper function that scans the given block, and
// notes which transactions spend outputs which are currently being
// watched. Additionally, the chain filter will also be updated by
// removing any spent outputs.
filterBlock := func(blk *wire.MsgBlock) []*wire.MsgTx {
b.filterMtx.Lock()
defer b.filterMtx.Unlock()
var filteredTxns []*wire.MsgTx
for _, tx := range blk.Transactions {
var txAlreadyFiltered bool
for _, txIn := range tx.TxIn {
prevOp := txIn.PreviousOutPoint
if _, ok := b.chainFilter[prevOp]; !ok {
continue
}
delete(b.chainFilter, prevOp)
// Only add this txn to our list of filtered
// txns if it is the first previous outpoint to
// cause a match.
if txAlreadyFiltered {
continue
}
filteredTxns = append(filteredTxns, tx.Copy())
txAlreadyFiltered = true
}
}
return filteredTxns
}
decodeJSONBlock := func(block *btcjson.RescannedBlock,
height uint32) (*FilteredBlock, error) {
hash, err := chainhash.NewHashFromStr(block.Hash)
if err != nil {
return nil, err
}
txs := make([]*wire.MsgTx, 0, len(block.Transactions))
for _, str := range block.Transactions {
b, err := hex.DecodeString(str)
if err != nil {
return nil, err
}
tx := &wire.MsgTx{}
err = tx.Deserialize(bytes.NewReader(b))
if err != nil {
return nil, err
}
txs = append(txs, tx)
}
return &FilteredBlock{
Hash: *hash,
Height: height,
Transactions: txs,
}, nil
}
for {
select {
// The caller has just sent an update to the current chain
// filter, so we'll apply the update, possibly rewinding our
// state partially.
case update := <-b.filterUpdates:
// First, we'll add all the new UTXO's to the set of
// watched UTXO's, eliminating any duplicates in the
// process.
log.Tracef("Updating chain filter with new UTXO's: %v",
update.newUtxos)
b.filterMtx.Lock()
for _, newOp := range update.newUtxos {
b.chainFilter[newOp] = struct{}{}
}
b.filterMtx.Unlock()
// Apply the new TX filter to the chain client, which
// will cause all following notifications from and
// calls to it return blocks filtered with the new
// filter.
err := b.chainClient.LoadTxFilter(false, update.newUtxos)
if err != nil {
log.Errorf("Unable to update filter: %v", err)
continue
}
// All blocks gotten after we loaded the filter will
// have the filter applied, but we will need to rescan
// the blocks up to the height of the block we last
// added to the blockQueue.
b.bestHeightMtx.Lock()
bestHeight := b.bestHeight
b.bestHeightMtx.Unlock()
// If the update height matches our best known height,
// then we don't need to do any rewinding.
if update.updateHeight == bestHeight {
continue
}
// Otherwise, we'll rewind the state to ensure the
// caller doesn't miss any relevant notifications.
// Starting from the height _after_ the update height,
// we'll walk forwards, rescanning one block at a time
// with the chain client applying the newly loaded
// filter to each block.
for i := update.updateHeight + 1; i < bestHeight+1; i++ {
blockHash, err := b.chainClient.GetBlockHash(int64(i))
if err != nil {
log.Warnf("Unable to get block hash "+
"for block at height %d: %v",
i, err)
continue
}
// To avoid dealing with the case where a reorg
// is happening while we rescan, we scan one
// block at a time, skipping blocks that might
// have gone missing.
rescanned, err := b.chainClient.RescanBlocks(
[]chainhash.Hash{*blockHash},
)
if err != nil {
log.Warnf("Unable to rescan block "+
"with hash %v at height %d: %v",
blockHash, i, err)
continue
}
// If no block was returned from the rescan, it
// means no matching transactions were found.
if len(rescanned) != 1 {
log.Tracef("rescan of block %v at "+
"height=%d yielded no "+
"transactions", blockHash, i)
continue
}
decoded, err := decodeJSONBlock(
&rescanned[0], i,
)
if err != nil {
log.Errorf("Unable to decode block: %v",
err)
continue
}
b.blockQueue.Add(&blockEvent{
eventType: connected,
block: decoded,
})
}
// We've received a new request to manually filter a block.
case req := <-b.filterBlockReqs:
// First we'll fetch the block itself as well as some
// additional information including its height.
block, err := b.GetBlock(req.blockHash)
if err != nil {
req.err <- err
req.resp <- nil
continue
}
header, err := b.chainClient.GetBlockHeaderVerbose(
req.blockHash)
if err != nil {
req.err <- err
req.resp <- nil
continue
}
// Once we have this info, we can directly filter the
// block and dispatch the proper notification.
req.resp <- &FilteredBlock{
Hash: *req.blockHash,
Height: uint32(header.Height),
Transactions: filterBlock(block),
}
req.err <- err
// We've received a new event from the chain client.
case event := <-b.chainClient.Notifications():
switch e := event.(type) {
case chain.FilteredBlockConnected:
b.onFilteredBlockConnected(
e.Block.Height, e.Block.Hash, e.RelevantTxs,
)
case chain.BlockDisconnected:
b.onFilteredBlockDisconnected(e.Height, e.Hash)
}
case <-b.quit:
return
}
}
}
// UpdateFilter updates the UTXO filter which is to be consulted when creating
// FilteredBlocks to be sent to subscribed clients. This method is cumulative
// meaning repeated calls to this method should _expand_ the size of the UTXO
// sub-set currently being watched. If the set updateHeight is _lower_ than
// the best known height of the implementation, then the state should be
// rewound to ensure all relevant notifications are dispatched.
//
// NOTE: This is part of the FilteredChainView interface.
func (b *BitcoindFilteredChainView) UpdateFilter(ops []graphdb.EdgePoint,
updateHeight uint32) error {
newUtxos := make([]wire.OutPoint, len(ops))
for i, op := range ops {
newUtxos[i] = op.OutPoint
}
select {
case b.filterUpdates <- filterUpdate{
newUtxos: newUtxos,
updateHeight: updateHeight,
}:
return nil
case <-b.quit:
return fmt.Errorf("chain filter shutting down")
}
}
// FilteredBlocks returns the channel that filtered blocks are to be sent over.
// Each time a block is connected to the end of a main chain, and appropriate
// FilteredBlock which contains the transactions which mutate our watched UTXO
// set is to be returned.
//
// NOTE: This is part of the FilteredChainView interface.
func (b *BitcoindFilteredChainView) FilteredBlocks() <-chan *FilteredBlock {
return b.blockQueue.newBlocks
}
// DisconnectedBlocks returns a receive only channel which will be sent upon
// with the empty filtered blocks of blocks which are disconnected from the
// main chain in the case of a re-org.
//
// NOTE: This is part of the FilteredChainView interface.
func (b *BitcoindFilteredChainView) DisconnectedBlocks() <-chan *FilteredBlock {
return b.blockQueue.staleBlocks
}
// GetBlock is used to retrieve the block with the given hash. This function
// wraps the blockCache's GetBlock function.
func (b *BitcoindFilteredChainView) GetBlock(hash *chainhash.Hash) (
*wire.MsgBlock, error) {
return b.blockCache.GetBlock(hash, b.chainClient.GetBlock)
}
package chainview
import (
"bytes"
"encoding/hex"
"fmt"
"sync"
"sync/atomic"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/blockcache"
graphdb "github.com/lightningnetwork/lnd/graph/db"
)
// BtcdFilteredChainView is an implementation of the FilteredChainView
// interface which is backed by an active websockets connection to btcd.
type BtcdFilteredChainView struct {
started int32 // To be used atomically.
stopped int32 // To be used atomically.
// bestHeight is the height of the latest block added to the
// blockQueue from the onFilteredConnectedMethod. It is used to
// determine up to what height we would need to rescan in case
// of a filter update.
bestHeightMtx sync.Mutex
bestHeight uint32
btcdConn *rpcclient.Client
// blockEventQueue is the ordered queue used to keep the order
// of connected and disconnected blocks sent to the reader of the
// chainView.
blockQueue *blockEventQueue
// blockCache is an LRU block cache.
blockCache *blockcache.BlockCache
// filterUpdates is a channel in which updates to the utxo filter
// attached to this instance are sent over.
filterUpdates chan filterUpdate
// chainFilter is the set of utox's that we're currently watching
// spends for within the chain.
filterMtx sync.RWMutex
chainFilter map[wire.OutPoint]struct{}
// filterBlockReqs is a channel in which requests to filter select
// blocks will be sent over.
filterBlockReqs chan *filterBlockReq
quit chan struct{}
wg sync.WaitGroup
}
// A compile time check to ensure BtcdFilteredChainView implements the
// chainview.FilteredChainView.
var _ FilteredChainView = (*BtcdFilteredChainView)(nil)
// NewBtcdFilteredChainView creates a new instance of a FilteredChainView from
// RPC credentials for an active btcd instance.
func NewBtcdFilteredChainView(config rpcclient.ConnConfig,
blockCache *blockcache.BlockCache) (*BtcdFilteredChainView, error) {
chainView := &BtcdFilteredChainView{
chainFilter: make(map[wire.OutPoint]struct{}),
filterUpdates: make(chan filterUpdate),
filterBlockReqs: make(chan *filterBlockReq),
blockCache: blockCache,
quit: make(chan struct{}),
}
ntfnCallbacks := &rpcclient.NotificationHandlers{
OnFilteredBlockConnected: chainView.onFilteredBlockConnected,
OnFilteredBlockDisconnected: chainView.onFilteredBlockDisconnected,
}
// Disable connecting to btcd within the rpcclient.New method. We
// defer establishing the connection to our .Start() method.
config.DisableConnectOnNew = true
config.DisableAutoReconnect = false
chainConn, err := rpcclient.New(&config, ntfnCallbacks)
if err != nil {
return nil, err
}
chainView.btcdConn = chainConn
chainView.blockQueue = newBlockEventQueue()
return chainView, nil
}
// Start starts all goroutines necessary for normal operation.
//
// NOTE: This is part of the FilteredChainView interface.
func (b *BtcdFilteredChainView) Start() error {
// Already started?
if atomic.AddInt32(&b.started, 1) != 1 {
return nil
}
log.Infof("FilteredChainView starting")
// Connect to btcd, and register for notifications on connected, and
// disconnected blocks.
if err := b.btcdConn.Connect(20); err != nil {
return err
}
if err := b.btcdConn.NotifyBlocks(); err != nil {
return err
}
_, bestHeight, err := b.btcdConn.GetBestBlock()
if err != nil {
return err
}
b.bestHeightMtx.Lock()
b.bestHeight = uint32(bestHeight)
b.bestHeightMtx.Unlock()
b.blockQueue.Start()
b.wg.Add(1)
go b.chainFilterer()
return nil
}
// Stop stops all goroutines which we launched by the prior call to the Start
// method.
//
// NOTE: This is part of the FilteredChainView interface.
func (b *BtcdFilteredChainView) Stop() error {
log.Debug("BtcdFilteredChainView stopping")
defer log.Debug("BtcdFilteredChainView stopped")
// Already shutting down?
if atomic.AddInt32(&b.stopped, 1) != 1 {
return nil
}
// Shutdown the rpc client, this gracefully disconnects from btcd, and
// cleans up all related resources.
b.btcdConn.Shutdown()
b.btcdConn.WaitForShutdown()
b.blockQueue.Stop()
close(b.quit)
b.wg.Wait()
return nil
}
// onFilteredBlockConnected is called for each block that's connected to the
// end of the main chain. Based on our current chain filter, the block may or
// may not include any relevant transactions.
func (b *BtcdFilteredChainView) onFilteredBlockConnected(height int32,
header *wire.BlockHeader, txns []*btcutil.Tx) {
mtxs := make([]*wire.MsgTx, len(txns))
b.filterMtx.Lock()
for i, tx := range txns {
mtx := tx.MsgTx()
mtxs[i] = mtx
for _, txIn := range mtx.TxIn {
// We can delete this outpoint from the chainFilter, as
// we just received a block where it was spent. In case
// of a reorg, this outpoint might get "un-spent", but
// that's okay since it would never be wise to consider
// the channel open again (since a spending transaction
// exists on the network).
delete(b.chainFilter, txIn.PreviousOutPoint)
}
}
b.filterMtx.Unlock()
// We record the height of the last connected block added to the
// blockQueue such that we can scan up to this height in case of
// a rescan. It must be protected by a mutex since a filter update
// might be trying to read it concurrently.
b.bestHeightMtx.Lock()
b.bestHeight = uint32(height)
b.bestHeightMtx.Unlock()
block := &FilteredBlock{
Hash: header.BlockHash(),
Height: uint32(height),
Transactions: mtxs,
}
b.blockQueue.Add(&blockEvent{
eventType: connected,
block: block,
})
}
// onFilteredBlockDisconnected is a callback which is executed once a block is
// disconnected from the end of the main chain.
func (b *BtcdFilteredChainView) onFilteredBlockDisconnected(height int32,
header *wire.BlockHeader) {
log.Debugf("got disconnected block at height %d: %v", height,
header.BlockHash())
filteredBlock := &FilteredBlock{
Hash: header.BlockHash(),
Height: uint32(height),
}
b.blockQueue.Add(&blockEvent{
eventType: disconnected,
block: filteredBlock,
})
}
// filterBlockReq houses a request to manually filter a block specified by
// block hash.
type filterBlockReq struct {
blockHash *chainhash.Hash
resp chan *FilteredBlock
err chan error
}
// FilterBlock takes a block hash, and returns a FilteredBlocks which is the
// result of applying the current registered UTXO sub-set on the block
// corresponding to that block hash. If any watched UTOX's are spent by the
// selected lock, then the internal chainFilter will also be updated.
//
// NOTE: This is part of the FilteredChainView interface.
func (b *BtcdFilteredChainView) FilterBlock(blockHash *chainhash.Hash) (*FilteredBlock, error) {
req := &filterBlockReq{
blockHash: blockHash,
resp: make(chan *FilteredBlock, 1),
err: make(chan error, 1),
}
select {
case b.filterBlockReqs <- req:
case <-b.quit:
return nil, fmt.Errorf("FilteredChainView shutting down")
}
return <-req.resp, <-req.err
}
// chainFilterer is the primary goroutine which: listens for new blocks coming
// and dispatches the relevant FilteredBlock notifications, updates the filter
// due to requests by callers, and finally is able to preform targeted block
// filtration.
//
// TODO(roasbeef): change to use loadfilter RPC's
func (b *BtcdFilteredChainView) chainFilterer() {
defer b.wg.Done()
// filterBlock is a helper function that scans the given block, and
// notes which transactions spend outputs which are currently being
// watched. Additionally, the chain filter will also be updated by
// removing any spent outputs.
filterBlock := func(blk *wire.MsgBlock) []*wire.MsgTx {
b.filterMtx.Lock()
defer b.filterMtx.Unlock()
var filteredTxns []*wire.MsgTx
for _, tx := range blk.Transactions {
var txAlreadyFiltered bool
for _, txIn := range tx.TxIn {
prevOp := txIn.PreviousOutPoint
if _, ok := b.chainFilter[prevOp]; !ok {
continue
}
delete(b.chainFilter, prevOp)
// Only add this txn to our list of filtered
// txns if it is the first previous outpoint to
// cause a match.
if txAlreadyFiltered {
continue
}
filteredTxns = append(filteredTxns, tx.Copy())
txAlreadyFiltered = true
}
}
return filteredTxns
}
decodeJSONBlock := func(block *btcjson.RescannedBlock,
height uint32) (*FilteredBlock, error) {
hash, err := chainhash.NewHashFromStr(block.Hash)
if err != nil {
return nil, err
}
txs := make([]*wire.MsgTx, 0, len(block.Transactions))
for _, str := range block.Transactions {
b, err := hex.DecodeString(str)
if err != nil {
return nil, err
}
tx := &wire.MsgTx{}
err = tx.Deserialize(bytes.NewReader(b))
if err != nil {
return nil, err
}
txs = append(txs, tx)
}
return &FilteredBlock{
Hash: *hash,
Height: height,
Transactions: txs,
}, nil
}
for {
select {
// The caller has just sent an update to the current chain
// filter, so we'll apply the update, possibly rewinding our
// state partially.
case update := <-b.filterUpdates:
// First, we'll add all the new UTXO's to the set of
// watched UTXO's, eliminating any duplicates in the
// process.
log.Tracef("Updating chain filter with new UTXO's: %v",
update.newUtxos)
b.filterMtx.Lock()
for _, newOp := range update.newUtxos {
b.chainFilter[newOp] = struct{}{}
}
b.filterMtx.Unlock()
// Apply the new TX filter to btcd, which will cause
// all following notifications from and calls to it
// return blocks filtered with the new filter.
b.btcdConn.LoadTxFilter(false, []btcutil.Address{},
update.newUtxos)
// All blocks gotten after we loaded the filter will
// have the filter applied, but we will need to rescan
// the blocks up to the height of the block we last
// added to the blockQueue.
b.bestHeightMtx.Lock()
bestHeight := b.bestHeight
b.bestHeightMtx.Unlock()
// If the update height matches our best known height,
// then we don't need to do any rewinding.
if update.updateHeight == bestHeight {
continue
}
// Otherwise, we'll rewind the state to ensure the
// caller doesn't miss any relevant notifications.
// Starting from the height _after_ the update height,
// we'll walk forwards, rescanning one block at a time
// with btcd applying the newly loaded filter to each
// block.
for i := update.updateHeight + 1; i < bestHeight+1; i++ {
blockHash, err := b.btcdConn.GetBlockHash(int64(i))
if err != nil {
log.Warnf("Unable to get block hash "+
"for block at height %d: %v",
i, err)
continue
}
// To avoid dealing with the case where a reorg
// is happening while we rescan, we scan one
// block at a time, skipping blocks that might
// have gone missing.
rescanned, err := b.btcdConn.RescanBlocks(
[]chainhash.Hash{*blockHash})
if err != nil {
log.Warnf("Unable to rescan block "+
"with hash %v at height %d: %v",
blockHash, i, err)
continue
}
// If no block was returned from the rescan, it
// means no matching transactions were found.
if len(rescanned) != 1 {
log.Tracef("rescan of block %v at "+
"height=%d yielded no "+
"transactions", blockHash, i)
continue
}
decoded, err := decodeJSONBlock(
&rescanned[0], uint32(i))
if err != nil {
log.Errorf("Unable to decode block: %v",
err)
continue
}
b.blockQueue.Add(&blockEvent{
eventType: connected,
block: decoded,
})
}
// We've received a new request to manually filter a block.
case req := <-b.filterBlockReqs:
// First we'll fetch the block itself as well as some
// additional information including its height.
block, err := b.GetBlock(req.blockHash)
if err != nil {
req.err <- err
req.resp <- nil
continue
}
header, err := b.btcdConn.GetBlockHeaderVerbose(req.blockHash)
if err != nil {
req.err <- err
req.resp <- nil
continue
}
// Once we have this info, we can directly filter the
// block and dispatch the proper notification.
req.resp <- &FilteredBlock{
Hash: *req.blockHash,
Height: uint32(header.Height),
Transactions: filterBlock(block),
}
req.err <- err
case <-b.quit:
return
}
}
}
// filterUpdate is a message sent to the chainFilterer to update the current
// chainFilter state.
type filterUpdate struct {
newUtxos []wire.OutPoint
updateHeight uint32
}
// UpdateFilter updates the UTXO filter which is to be consulted when creating
// FilteredBlocks to be sent to subscribed clients. This method is cumulative
// meaning repeated calls to this method should _expand_ the size of the UTXO
// sub-set currently being watched. If the set updateHeight is _lower_ than
// the best known height of the implementation, then the state should be
// rewound to ensure all relevant notifications are dispatched.
//
// NOTE: This is part of the FilteredChainView interface.
func (b *BtcdFilteredChainView) UpdateFilter(ops []graphdb.EdgePoint,
updateHeight uint32) error {
newUtxos := make([]wire.OutPoint, len(ops))
for i, op := range ops {
newUtxos[i] = op.OutPoint
}
select {
case b.filterUpdates <- filterUpdate{
newUtxos: newUtxos,
updateHeight: updateHeight,
}:
return nil
case <-b.quit:
return fmt.Errorf("chain filter shutting down")
}
}
// FilteredBlocks returns the channel that filtered blocks are to be sent over.
// Each time a block is connected to the end of a main chain, and appropriate
// FilteredBlock which contains the transactions which mutate our watched UTXO
// set is to be returned.
//
// NOTE: This is part of the FilteredChainView interface.
func (b *BtcdFilteredChainView) FilteredBlocks() <-chan *FilteredBlock {
return b.blockQueue.newBlocks
}
// DisconnectedBlocks returns a receive only channel which will be sent upon
// with the empty filtered blocks of blocks which are disconnected from the
// main chain in the case of a re-org.
//
// NOTE: This is part of the FilteredChainView interface.
func (b *BtcdFilteredChainView) DisconnectedBlocks() <-chan *FilteredBlock {
return b.blockQueue.staleBlocks
}
// GetBlock is used to retrieve the block with the given hash. This function
// wraps the blockCache's GetBlock function.
func (b *BtcdFilteredChainView) GetBlock(hash *chainhash.Hash) (
*wire.MsgBlock, error) {
return b.blockCache.GetBlock(hash, b.btcdConn.GetBlock)
}
package chainview
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("CRTR", nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until either UseLogger or SetLogWriter are called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package chainview
import (
"fmt"
"sync"
"sync/atomic"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/gcs/builder"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/neutrino"
"github.com/lightningnetwork/lnd/blockcache"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/lntypes"
)
// CfFilteredChainView is an implementation of the FilteredChainView interface
// which is supported by an underlying Bitcoin light client which supports
// client side filtering of Golomb Coded Sets. Rather than fetching all the
// blocks, the light client is able to query filters locally, to test if an
// item in a block modifies any of our watched set of UTXOs.
type CfFilteredChainView struct {
started int32 // To be used atomically.
stopped int32 // To be used atomically.
// p2pNode is a pointer to the running GCS-filter supported Bitcoin
// light clientl
p2pNode *neutrino.ChainService
// chainView is the active rescan which only watches our specified
// sub-set of the UTXO set.
chainView *neutrino.Rescan
// rescanErrChan is the channel that any errors encountered during the
// rescan will be sent over.
rescanErrChan <-chan error
// blockEventQueue is the ordered queue used to keep the order
// of connected and disconnected blocks sent to the reader of the
// chainView.
blockQueue *blockEventQueue
// blockCache is an LRU block cache.
blockCache *blockcache.BlockCache
// chainFilter is the
filterMtx sync.RWMutex
chainFilter map[wire.OutPoint][]byte
quit chan struct{}
wg sync.WaitGroup
}
// A compile time check to ensure CfFilteredChainView implements the
// chainview.FilteredChainView.
var _ FilteredChainView = (*CfFilteredChainView)(nil)
// NewCfFilteredChainView creates a new instance of the CfFilteredChainView
// which is connected to an active neutrino node.
//
// NOTE: The node should already be running and syncing before being passed into
// this function.
func NewCfFilteredChainView(node *neutrino.ChainService,
blockCache *blockcache.BlockCache) (*CfFilteredChainView, error) {
return &CfFilteredChainView{
blockQueue: newBlockEventQueue(),
quit: make(chan struct{}),
rescanErrChan: make(chan error),
chainFilter: make(map[wire.OutPoint][]byte),
p2pNode: node,
blockCache: blockCache,
}, nil
}
// Start kicks off the FilteredChainView implementation. This function must be
// called before any calls to UpdateFilter can be processed.
//
// NOTE: This is part of the FilteredChainView interface.
func (c *CfFilteredChainView) Start() error {
// Already started?
if atomic.AddInt32(&c.started, 1) != 1 {
return nil
}
log.Infof("FilteredChainView starting")
// First, we'll obtain the latest block height of the p2p node. We'll
// start the auto-rescan from this point. Once a caller actually wishes
// to register a chain view, the rescan state will be rewound
// accordingly.
startingPoint, err := c.p2pNode.BestBlock()
if err != nil {
return err
}
// Next, we'll create our set of rescan options. Currently it's
// required that an user MUST set a addr/outpoint/txid when creating a
// rescan. To get around this, we'll add a "zero" outpoint, that won't
// actually be matched.
var zeroPoint neutrino.InputWithScript
rescanOptions := []neutrino.RescanOption{
neutrino.StartBlock(startingPoint),
neutrino.QuitChan(c.quit),
neutrino.NotificationHandlers(
rpcclient.NotificationHandlers{
OnFilteredBlockConnected: c.onFilteredBlockConnected,
OnFilteredBlockDisconnected: c.onFilteredBlockDisconnected,
},
),
neutrino.WatchInputs(zeroPoint),
}
// Finally, we'll create our rescan struct, start it, and launch all
// the goroutines we need to operate this FilteredChainView instance.
c.chainView = neutrino.NewRescan(
&neutrino.RescanChainSource{
ChainService: c.p2pNode,
},
rescanOptions...,
)
c.rescanErrChan = c.chainView.Start()
c.blockQueue.Start()
c.wg.Add(1)
go c.chainFilterer()
return nil
}
// Stop signals all active goroutines for a graceful shutdown.
//
// NOTE: This is part of the FilteredChainView interface.
func (c *CfFilteredChainView) Stop() error {
log.Debug("CfFilteredChainView stopping")
defer log.Debug("CfFilteredChainView stopped")
// Already shutting down?
if atomic.AddInt32(&c.stopped, 1) != 1 {
return nil
}
close(c.quit)
c.blockQueue.Stop()
c.wg.Wait()
return nil
}
// onFilteredBlockConnected is called for each block that's connected to the
// end of the main chain. Based on our current chain filter, the block may or
// may not include any relevant transactions.
func (c *CfFilteredChainView) onFilteredBlockConnected(height int32,
header *wire.BlockHeader, txns []*btcutil.Tx) {
mtxs := make([]*wire.MsgTx, len(txns))
for i, tx := range txns {
mtx := tx.MsgTx()
mtxs[i] = mtx
for _, txIn := range mtx.TxIn {
c.filterMtx.Lock()
delete(c.chainFilter, txIn.PreviousOutPoint)
c.filterMtx.Unlock()
}
}
block := &FilteredBlock{
Hash: header.BlockHash(),
Height: uint32(height),
Transactions: mtxs,
}
c.blockQueue.Add(&blockEvent{
eventType: connected,
block: block,
})
}
// onFilteredBlockDisconnected is a callback which is executed once a block is
// disconnected from the end of the main chain.
func (c *CfFilteredChainView) onFilteredBlockDisconnected(height int32,
header *wire.BlockHeader) {
log.Debugf("got disconnected block at height %d: %v", height,
header.BlockHash())
filteredBlock := &FilteredBlock{
Hash: header.BlockHash(),
Height: uint32(height),
}
c.blockQueue.Add(&blockEvent{
eventType: disconnected,
block: filteredBlock,
})
}
// chainFilterer is the primary coordination goroutine within the
// CfFilteredChainView. This goroutine handles errors from the running rescan.
func (c *CfFilteredChainView) chainFilterer() {
defer c.wg.Done()
for {
select {
case err := <-c.rescanErrChan:
log.Errorf("Error encountered during rescan: %v", err)
case <-c.quit:
return
}
}
}
// FilterBlock takes a block hash, and returns a FilteredBlocks which is the
// result of applying the current registered UTXO sub-set on the block
// corresponding to that block hash. If any watched UTXO's are spent by the
// selected lock, then the internal chainFilter will also be updated.
//
// NOTE: This is part of the FilteredChainView interface.
func (c *CfFilteredChainView) FilterBlock(blockHash *chainhash.Hash) (*FilteredBlock, error) {
// First, we'll fetch the block header itself so we can obtain the
// height which is part of our return value.
blockHeight, err := c.p2pNode.GetBlockHeight(blockHash)
if err != nil {
return nil, err
}
filteredBlock := &FilteredBlock{
Hash: *blockHash,
Height: uint32(blockHeight),
}
// If we don't have any items within our current chain filter, then we
// can exit early as we don't need to fetch the filter.
c.filterMtx.RLock()
if len(c.chainFilter) == 0 {
c.filterMtx.RUnlock()
return filteredBlock, nil
}
c.filterMtx.RUnlock()
// Next, using the block, hash, we'll fetch the compact filter for this
// block. We only require the regular filter as we're just looking for
// outpoint that have been spent.
filter, err := c.p2pNode.GetCFilter(*blockHash, wire.GCSFilterRegular)
if err != nil {
return nil, fmt.Errorf("unable to fetch filter: %w", err)
}
// Before we can match the filter, we'll need to map each item in our
// chain filter to the representation that included in the compact
// filters.
c.filterMtx.RLock()
relevantPoints := make([][]byte, 0, len(c.chainFilter))
for _, filterEntry := range c.chainFilter {
relevantPoints = append(relevantPoints, filterEntry)
}
c.filterMtx.RUnlock()
// With our relevant points constructed, we can finally match against
// the retrieved filter.
matched, err := filter.MatchAny(builder.DeriveKey(blockHash),
relevantPoints)
if err != nil {
return nil, err
}
// If there wasn't a match, then we'll return the filtered block as is
// (void of any transactions).
if !matched {
return filteredBlock, nil
}
// If we reach this point, then there was a match, so we'll need to
// fetch the block itself so we can scan it for any actual matches (as
// there's a fp rate).
block, err := c.GetBlock(*blockHash)
if err != nil {
return nil, err
}
// Finally, we'll step through the block, input by input, to see if any
// transactions spend any outputs from our watched sub-set of the UTXO
// set.
for _, tx := range block.Transactions() {
for _, txIn := range tx.MsgTx().TxIn {
prevOp := txIn.PreviousOutPoint
c.filterMtx.RLock()
_, ok := c.chainFilter[prevOp]
c.filterMtx.RUnlock()
if ok {
filteredBlock.Transactions = append(
filteredBlock.Transactions,
tx.MsgTx().Copy(),
)
c.filterMtx.Lock()
delete(c.chainFilter, prevOp)
c.filterMtx.Unlock()
break
}
}
}
return filteredBlock, nil
}
// UpdateFilter updates the UTXO filter which is to be consulted when creating
// FilteredBlocks to be sent to subscribed clients. This method is cumulative
// meaning repeated calls to this method should _expand_ the size of the UTXO
// sub-set currently being watched. If the set updateHeight is _lower_ than
// the best known height of the implementation, then the state should be
// rewound to ensure all relevant notifications are dispatched.
//
// NOTE: This is part of the FilteredChainView interface.
func (c *CfFilteredChainView) UpdateFilter(ops []graphdb.EdgePoint,
updateHeight uint32) error {
log.Tracef("Updating chain filter with new UTXO's: %v", ops)
// First, we'll update the current chain view, by adding any new
// UTXO's, ignoring duplicates in the process.
c.filterMtx.Lock()
for _, op := range ops {
c.chainFilter[op.OutPoint] = op.FundingPkScript
}
c.filterMtx.Unlock()
inputs := make([]neutrino.InputWithScript, len(ops))
for i, op := range ops {
inputs[i] = neutrino.InputWithScript{
PkScript: op.FundingPkScript,
OutPoint: op.OutPoint,
}
}
// With our internal chain view update, we'll craft a new update to the
// chainView which includes our new UTXO's, and current update height.
rescanUpdate := []neutrino.UpdateOption{
neutrino.AddInputs(inputs...),
neutrino.Rewind(updateHeight),
neutrino.DisableDisconnectedNtfns(true),
}
err := c.chainView.Update(rescanUpdate...)
if err != nil {
return fmt.Errorf("unable to update rescan: %w", err)
}
return nil
}
// FilteredBlocks returns the channel that filtered blocks are to be sent over.
// Each time a block is connected to the end of a main chain, and appropriate
// FilteredBlock which contains the transactions which mutate our watched UTXO
// set is to be returned.
//
// NOTE: This is part of the FilteredChainView interface.
func (c *CfFilteredChainView) FilteredBlocks() <-chan *FilteredBlock {
return c.blockQueue.newBlocks
}
// DisconnectedBlocks returns a receive only channel which will be sent upon
// with the empty filtered blocks of blocks which are disconnected from the
// main chain in the case of a re-org.
//
// NOTE: This is part of the FilteredChainView interface.
func (c *CfFilteredChainView) DisconnectedBlocks() <-chan *FilteredBlock {
return c.blockQueue.staleBlocks
}
// GetBlock is used to retrieve the block with the given hash. Since the block
// cache used by neutrino will be the same as that used by LND (since it is
// passed to neutrino on initialisation), the neutrino GetBlock method can be
// called directly since it already uses the block cache. However, neutrino
// does not lock the block cache mutex for the given block hash and so that is
// done here.
func (c *CfFilteredChainView) GetBlock(hash chainhash.Hash) (
*btcutil.Block, error) {
c.blockCache.HashMutex.Lock(lntypes.Hash(hash))
defer c.blockCache.HashMutex.Unlock(lntypes.Hash(hash))
return c.p2pNode.GetBlock(hash)
}
package chainview
import "sync"
// blockEventType is the possible types of a blockEvent.
type blockEventType uint8
const (
// connected is the type of a blockEvent representing a block
// that was connected to our current chain.
connected blockEventType = iota
// disconnected is the type of a blockEvent representing a
// block that is stale/disconnected from our current chain.
disconnected
)
// blockEvent represent a block that was either connected
// or disconnected from the current chain.
type blockEvent struct {
eventType blockEventType
block *FilteredBlock
}
// blockEventQueue is an ordered queue for block events sent from a
// FilteredChainView. The two types of possible block events are
// connected/new blocks, and disconnected/stale blocks. The
// blockEventQueue keeps the order of these events intact, while
// still being non-blocking. This is important in order for the
// chainView's call to onBlockConnected/onBlockDisconnected to not
// get blocked, and for the consumer of the block events to always
// get the events in the correct order.
type blockEventQueue struct {
queueCond *sync.Cond
queueMtx sync.Mutex
queue []*blockEvent
// newBlocks is the channel where the consumer of the queue
// will receive connected/new blocks from the FilteredChainView.
newBlocks chan *FilteredBlock
// staleBlocks is the channel where the consumer of the queue will
// receive disconnected/stale blocks from the FilteredChainView.
staleBlocks chan *FilteredBlock
wg sync.WaitGroup
quit chan struct{}
}
// newBlockEventQueue creates a new blockEventQueue.
func newBlockEventQueue() *blockEventQueue {
b := &blockEventQueue{
newBlocks: make(chan *FilteredBlock),
staleBlocks: make(chan *FilteredBlock),
quit: make(chan struct{}),
}
b.queueCond = sync.NewCond(&b.queueMtx)
return b
}
// Start starts the blockEventQueue coordinator such that it can start handling
// events.
func (b *blockEventQueue) Start() {
b.wg.Add(1)
go b.queueCoordinator()
}
// Stop signals the queue coordinator to stop, such that the queue can be
// shut down.
func (b *blockEventQueue) Stop() {
close(b.quit)
b.queueCond.Signal()
}
// queueCoordinator is the queue's main loop, handling incoming block events
// and handing them off to the correct output channel.
//
// NB: MUST be run as a goroutine from the Start() method.
func (b *blockEventQueue) queueCoordinator() {
defer b.wg.Done()
for {
// First, we'll check our condition. If the queue of events is
// empty, then we'll wait until a new item is added.
b.queueCond.L.Lock()
for len(b.queue) == 0 {
b.queueCond.Wait()
// If we were woke up in order to exit, then we'll do
// so. Otherwise, we'll check the queue for any new
// items.
select {
case <-b.quit:
b.queueCond.L.Unlock()
return
default:
}
}
// Grab the first element in the queue, and nil the index to
// avoid gc leak.
event := b.queue[0]
b.queue[0] = nil
b.queue = b.queue[1:]
b.queueCond.L.Unlock()
// In the case this is a connected block, we'll send it on the
// newBlocks channel. In case it is a disconnected block, we'll
// send it on the staleBlocks channel. This send will block
// until it is received by the consumer on the other end, making
// sure we won't try to send any other block event before the
// consumer is aware of this one.
switch event.eventType {
case connected:
select {
case b.newBlocks <- event.block:
case <-b.quit:
return
}
case disconnected:
select {
case b.staleBlocks <- event.block:
case <-b.quit:
return
}
}
}
}
// Add puts the provided blockEvent at the end of the event queue, making sure
// it will first be received after all previous events. This method is
// non-blocking, in the sense that it will never wait for the consumer of the
// queue to read form the other end, making it safe to call from the
// FilteredChainView's onBlockConnected/onBlockDisconnected.
func (b *blockEventQueue) Add(event *blockEvent) {
// Lock the condition, and add the event to the end of queue.
b.queueCond.L.Lock()
b.queue = append(b.queue, event)
b.queueCond.L.Unlock()
// With the event added, we signal to the queueCoordinator that
// there are new events to handle.
b.queueCond.Signal()
}
package routing
import (
"sync"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/multimutex"
"github.com/lightningnetwork/lnd/queue"
)
// DBMPPayment is an interface derived from channeldb.MPPayment that is used by
// the payment lifecycle.
type DBMPPayment interface {
// GetState returns the current state of the payment.
GetState() *channeldb.MPPaymentState
// Terminated returns true if the payment is in a final state.
Terminated() bool
// GetStatus returns the current status of the payment.
GetStatus() channeldb.PaymentStatus
// NeedWaitAttempts specifies whether the payment needs to wait for the
// outcome of an attempt.
NeedWaitAttempts() (bool, error)
// GetHTLCs returns all HTLCs of this payment.
GetHTLCs() []channeldb.HTLCAttempt
// InFlightHTLCs returns all HTLCs that are in flight.
InFlightHTLCs() []channeldb.HTLCAttempt
// AllowMoreAttempts is used to decide whether we can safely attempt
// more HTLCs for a given payment state. Return an error if the payment
// is in an unexpected state.
AllowMoreAttempts() (bool, error)
// TerminalInfo returns the settled HTLC attempt or the payment's
// failure reason.
TerminalInfo() (*channeldb.HTLCAttempt, *channeldb.FailureReason)
}
// ControlTower tracks all outgoing payments made, whose primary purpose is to
// prevent duplicate payments to the same payment hash. In production, a
// persistent implementation is preferred so that tracking can survive across
// restarts. Payments are transitioned through various payment states, and the
// ControlTower interface provides access to driving the state transitions.
type ControlTower interface {
// This method checks that no succeeded payment exist for this payment
// hash.
InitPayment(lntypes.Hash, *channeldb.PaymentCreationInfo) error
// DeleteFailedAttempts removes all failed HTLCs from the db. It should
// be called for a given payment whenever all inflight htlcs are
// completed, and the payment has reached a final settled state.
DeleteFailedAttempts(lntypes.Hash) error
// RegisterAttempt atomically records the provided HTLCAttemptInfo.
RegisterAttempt(lntypes.Hash, *channeldb.HTLCAttemptInfo) error
// SettleAttempt marks the given attempt settled with the preimage. If
// this is a multi shard payment, this might implicitly mean the the
// full payment succeeded.
//
// After invoking this method, InitPayment should always return an
// error to prevent us from making duplicate payments to the same
// payment hash. The provided preimage is atomically saved to the DB
// for record keeping.
SettleAttempt(lntypes.Hash, uint64, *channeldb.HTLCSettleInfo) (
*channeldb.HTLCAttempt, error)
// FailAttempt marks the given payment attempt failed.
FailAttempt(lntypes.Hash, uint64, *channeldb.HTLCFailInfo) (
*channeldb.HTLCAttempt, error)
// FetchPayment fetches the payment corresponding to the given payment
// hash.
FetchPayment(paymentHash lntypes.Hash) (DBMPPayment, error)
// FailPayment transitions a payment into the Failed state, and records
// the ultimate reason the payment failed. Note that this should only
// be called when all active attempts are already failed. After
// invoking this method, InitPayment should return nil on its next call
// for this payment hash, allowing the user to make a subsequent
// payment.
FailPayment(lntypes.Hash, channeldb.FailureReason) error
// FetchInFlightPayments returns all payments with status InFlight.
FetchInFlightPayments() ([]*channeldb.MPPayment, error)
// SubscribePayment subscribes to updates for the payment with the given
// hash. A first update with the current state of the payment is always
// sent out immediately.
SubscribePayment(paymentHash lntypes.Hash) (ControlTowerSubscriber,
error)
// SubscribeAllPayments subscribes to updates for all payments. A first
// update with the current state of every inflight payment is always
// sent out immediately.
SubscribeAllPayments() (ControlTowerSubscriber, error)
}
// ControlTowerSubscriber contains the state for a payment update subscriber.
type ControlTowerSubscriber interface {
// Updates is the channel over which *channeldb.MPPayment updates can be
// received.
Updates() <-chan interface{}
// Close signals that the subscriber is no longer interested in updates.
Close()
}
// ControlTowerSubscriberImpl contains the state for a payment update
// subscriber.
type controlTowerSubscriberImpl struct {
updates <-chan interface{}
queue *queue.ConcurrentQueue
quit chan struct{}
}
// newControlTowerSubscriber instantiates a new subscriber state object.
func newControlTowerSubscriber() *controlTowerSubscriberImpl {
// Create a queue for payment updates.
queue := queue.NewConcurrentQueue(20)
queue.Start()
return &controlTowerSubscriberImpl{
updates: queue.ChanOut(),
queue: queue,
quit: make(chan struct{}),
}
}
// Close signals that the subscriber is no longer interested in updates.
func (s *controlTowerSubscriberImpl) Close() {
// Close quit channel so that any pending writes to the queue are
// cancelled.
close(s.quit)
// Stop the queue goroutine so that it won't leak.
s.queue.Stop()
}
// Updates is the channel over which *channeldb.MPPayment updates can be
// received.
func (s *controlTowerSubscriberImpl) Updates() <-chan interface{} {
return s.updates
}
// controlTower is persistent implementation of ControlTower to restrict
// double payment sending.
type controlTower struct {
db *channeldb.PaymentControl
// subscriberIndex is used to provide a unique id for each subscriber
// to all payments. This is used to easily remove the subscriber when
// necessary.
subscriberIndex uint64
subscribersAllPayments map[uint64]*controlTowerSubscriberImpl
subscribers map[lntypes.Hash][]*controlTowerSubscriberImpl
subscribersMtx sync.Mutex
// paymentsMtx provides synchronization on the payment level to ensure
// that no race conditions occur in between updating the database and
// sending a notification.
paymentsMtx *multimutex.Mutex[lntypes.Hash]
}
// NewControlTower creates a new instance of the controlTower.
func NewControlTower(db *channeldb.PaymentControl) ControlTower {
return &controlTower{
db: db,
subscribersAllPayments: make(
map[uint64]*controlTowerSubscriberImpl,
),
subscribers: make(map[lntypes.Hash][]*controlTowerSubscriberImpl),
paymentsMtx: multimutex.NewMutex[lntypes.Hash](),
}
}
// InitPayment checks or records the given PaymentCreationInfo with the DB,
// making sure it does not already exist as an in-flight payment. Then this
// method returns successfully, the payment is guaranteed to be in the
// Initiated state.
func (p *controlTower) InitPayment(paymentHash lntypes.Hash,
info *channeldb.PaymentCreationInfo) error {
err := p.db.InitPayment(paymentHash, info)
if err != nil {
return err
}
// Take lock before querying the db to prevent missing or duplicating
// an update.
p.paymentsMtx.Lock(paymentHash)
defer p.paymentsMtx.Unlock(paymentHash)
payment, err := p.db.FetchPayment(paymentHash)
if err != nil {
return err
}
p.notifySubscribers(paymentHash, payment)
return nil
}
// DeleteFailedAttempts deletes all failed htlcs if the payment was
// successfully settled.
func (p *controlTower) DeleteFailedAttempts(paymentHash lntypes.Hash) error {
return p.db.DeleteFailedAttempts(paymentHash)
}
// RegisterAttempt atomically records the provided HTLCAttemptInfo to the
// DB.
func (p *controlTower) RegisterAttempt(paymentHash lntypes.Hash,
attempt *channeldb.HTLCAttemptInfo) error {
p.paymentsMtx.Lock(paymentHash)
defer p.paymentsMtx.Unlock(paymentHash)
payment, err := p.db.RegisterAttempt(paymentHash, attempt)
if err != nil {
return err
}
// Notify subscribers of the attempt registration.
p.notifySubscribers(paymentHash, payment)
return nil
}
// SettleAttempt marks the given attempt settled with the preimage. If
// this is a multi shard payment, this might implicitly mean the the
// full payment succeeded.
func (p *controlTower) SettleAttempt(paymentHash lntypes.Hash,
attemptID uint64, settleInfo *channeldb.HTLCSettleInfo) (
*channeldb.HTLCAttempt, error) {
p.paymentsMtx.Lock(paymentHash)
defer p.paymentsMtx.Unlock(paymentHash)
payment, err := p.db.SettleAttempt(paymentHash, attemptID, settleInfo)
if err != nil {
return nil, err
}
// Notify subscribers of success event.
p.notifySubscribers(paymentHash, payment)
return payment.GetAttempt(attemptID)
}
// FailAttempt marks the given payment attempt failed.
func (p *controlTower) FailAttempt(paymentHash lntypes.Hash,
attemptID uint64, failInfo *channeldb.HTLCFailInfo) (
*channeldb.HTLCAttempt, error) {
p.paymentsMtx.Lock(paymentHash)
defer p.paymentsMtx.Unlock(paymentHash)
payment, err := p.db.FailAttempt(paymentHash, attemptID, failInfo)
if err != nil {
return nil, err
}
// Notify subscribers of failed attempt.
p.notifySubscribers(paymentHash, payment)
return payment.GetAttempt(attemptID)
}
// FetchPayment fetches the payment corresponding to the given payment hash.
func (p *controlTower) FetchPayment(paymentHash lntypes.Hash) (
DBMPPayment, error) {
return p.db.FetchPayment(paymentHash)
}
// FailPayment transitions a payment into the Failed state, and records the
// reason the payment failed. After invoking this method, InitPayment should
// return nil on its next call for this payment hash, allowing the switch to
// make a subsequent payment.
//
// NOTE: This method will overwrite the failure reason if the payment is already
// failed.
func (p *controlTower) FailPayment(paymentHash lntypes.Hash,
reason channeldb.FailureReason) error {
p.paymentsMtx.Lock(paymentHash)
defer p.paymentsMtx.Unlock(paymentHash)
payment, err := p.db.Fail(paymentHash, reason)
if err != nil {
return err
}
// Notify subscribers of fail event.
p.notifySubscribers(paymentHash, payment)
return nil
}
// FetchInFlightPayments returns all payments with status InFlight.
func (p *controlTower) FetchInFlightPayments() ([]*channeldb.MPPayment, error) {
return p.db.FetchInFlightPayments()
}
// SubscribePayment subscribes to updates for the payment with the given hash. A
// first update with the current state of the payment is always sent out
// immediately.
func (p *controlTower) SubscribePayment(paymentHash lntypes.Hash) (
ControlTowerSubscriber, error) {
// Take lock before querying the db to prevent missing or duplicating an
// update.
p.paymentsMtx.Lock(paymentHash)
defer p.paymentsMtx.Unlock(paymentHash)
payment, err := p.db.FetchPayment(paymentHash)
if err != nil {
return nil, err
}
subscriber := newControlTowerSubscriber()
// Always write current payment state to the channel.
subscriber.queue.ChanIn() <- payment
// Payment is currently in flight. Register this subscriber for further
// updates. Otherwise this update is the final update and the incoming
// channel can be closed. This will close the queue's outgoing channel
// when all updates have been written.
if !payment.Terminated() {
p.subscribersMtx.Lock()
p.subscribers[paymentHash] = append(
p.subscribers[paymentHash], subscriber,
)
p.subscribersMtx.Unlock()
} else {
close(subscriber.queue.ChanIn())
}
return subscriber, nil
}
// SubscribeAllPayments subscribes to updates for all inflight payments. A first
// update with the current state of every inflight payment is always sent out
// immediately.
// Note: If payments are in-flight while starting a new subscription, the start
// of the payment stream could produce out-of-order and/or duplicate events. In
// order to get updates for every in-flight payment attempt make sure to
// subscribe to this method before initiating any payments.
func (p *controlTower) SubscribeAllPayments() (ControlTowerSubscriber, error) {
subscriber := newControlTowerSubscriber()
// Add the subscriber to the list before fetching in-flight payments, so
// no events are missed. If a payment attempt update occurs after
// appending and before fetching in-flight payments, an out-of-order
// duplicate may be produced, because it is then fetched in below call
// and notified through the subscription.
p.subscribersMtx.Lock()
p.subscribersAllPayments[p.subscriberIndex] = subscriber
p.subscriberIndex++
p.subscribersMtx.Unlock()
inflightPayments, err := p.db.FetchInFlightPayments()
if err != nil {
return nil, err
}
for index := range inflightPayments {
// Always write current payment state to the channel.
subscriber.queue.ChanIn() <- inflightPayments[index]
}
return subscriber, nil
}
// notifySubscribers sends a final payment event to all subscribers of this
// payment. The channel will be closed after this. Note that this function must
// be executed atomically (by means of a lock) with the database update to
// guarantee consistency of the notifications.
func (p *controlTower) notifySubscribers(paymentHash lntypes.Hash,
event *channeldb.MPPayment) {
// Get all subscribers for this payment.
p.subscribersMtx.Lock()
subscribersPaymentHash, ok := p.subscribers[paymentHash]
if !ok && len(p.subscribersAllPayments) == 0 {
p.subscribersMtx.Unlock()
return
}
// If the payment reached a terminal state, the subscriber list can be
// cleared. There won't be any more updates.
terminal := event.Terminated()
if terminal {
delete(p.subscribers, paymentHash)
}
// Copy subscribers to all payments locally while holding the lock in
// order to avoid concurrency issues while reading/writing the map.
subscribersAllPayments := make(map[uint64]*controlTowerSubscriberImpl)
for k, v := range p.subscribersAllPayments {
subscribersAllPayments[k] = v
}
p.subscribersMtx.Unlock()
// Notify all subscribers that subscribed to the current payment hash.
for _, subscriber := range subscribersPaymentHash {
select {
case subscriber.queue.ChanIn() <- event:
// If this event is the last, close the incoming channel
// of the queue. This will signal the subscriber that
// there won't be any more updates.
if terminal {
close(subscriber.queue.ChanIn())
}
// If subscriber disappeared, skip notification. For further
// notifications, we'll keep skipping over this subscriber.
case <-subscriber.quit:
}
}
// Notify all subscribers that subscribed to all payments.
for key, subscriber := range subscribersAllPayments {
select {
case subscriber.queue.ChanIn() <- event:
// If subscriber disappeared, remove it from the subscribers
// list.
case <-subscriber.quit:
p.subscribersMtx.Lock()
delete(p.subscribersAllPayments, key)
p.subscribersMtx.Unlock()
}
}
}
package routing
import (
"fmt"
"github.com/btcsuite/btcd/btcutil"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
// Graph is an abstract interface that provides information about nodes and
// edges to pathfinding.
type Graph interface {
// ForEachNodeDirectedChannel calls the callback for every channel of
// the given node.
ForEachNodeDirectedChannel(nodePub route.Vertex,
cb func(channel *graphdb.DirectedChannel) error) error
// FetchNodeFeatures returns the features of the given node.
FetchNodeFeatures(nodePub route.Vertex) (*lnwire.FeatureVector, error)
}
// GraphSessionFactory can be used to gain access to a graphdb.NodeTraverser
// instance which can then be used for a path-finding session. Depending on the
// implementation, the session will represent a DB connection where a read-lock
// is being held across calls to the backing graph.
type GraphSessionFactory interface {
// GraphSession will provide the call-back with access to a
// graphdb.NodeTraverser instance which can be used to perform queries
// against the channel graph.
GraphSession(cb func(graph graphdb.NodeTraverser) error) error
}
// FetchAmountPairCapacity determines the maximal public capacity between two
// nodes depending on the amount we try to send.
func FetchAmountPairCapacity(graph Graph, source, nodeFrom, nodeTo route.Vertex,
amount lnwire.MilliSatoshi) (btcutil.Amount, error) {
// Create unified edges for all incoming connections.
//
// Note: Inbound fees are not used here because this method is only used
// by a deprecated router rpc.
u := newNodeEdgeUnifier(source, nodeTo, false, nil)
err := u.addGraphPolicies(graph)
if err != nil {
return 0, err
}
edgeUnifier, ok := u.edgeUnifiers[nodeFrom]
if !ok {
return 0, fmt.Errorf("no edge info for node pair %v -> %v",
nodeFrom, nodeTo)
}
edge := edgeUnifier.getEdgeNetwork(amount, 0)
if edge == nil {
return 0, fmt.Errorf("no edge for node pair %v -> %v "+
"(amount %v)", nodeFrom, nodeTo, amount)
}
return edge.capacity, nil
}
package routing
import (
"container/heap"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
// nodeWithDist is a helper struct that couples the distance from the current
// source to a node with a pointer to the node itself.
type nodeWithDist struct {
// dist is the distance to this node from the source node in our
// current context.
dist int64
// node is the vertex itself. This can be used to explore all the
// outgoing edges (channels) emanating from a node.
node route.Vertex
// netAmountReceived is the amount that should be received by this node.
// Either as final payment to the final node or as an intermediate
// amount that includes also the fees for subsequent hops. This node's
// inbound fee is already subtracted from the htlc amount - if
// applicable.
netAmountReceived lnwire.MilliSatoshi
// outboundFee is the fee that this node charges on the outgoing
// channel.
outboundFee lnwire.MilliSatoshi
// incomingCltv is the expected absolute expiry height for the incoming
// htlc of this node. This value should already include the final cltv
// delta.
incomingCltv int32
// probability is the probability that from this node onward the route
// is successful.
probability float64
// weight is the cost of the route from this node to the destination.
// Includes the routing fees and a virtual cost factor to account for
// time locks.
weight int64
// nextHop is the edge this route comes from.
nextHop *unifiedEdge
// routingInfoSize is the total size requirement for the payloads field
// in the onion packet from this hop towards the final destination.
routingInfoSize uint64
}
// distanceHeap is a min-distance heap that's used within our path finding
// algorithm to keep track of the "closest" node to our source node.
type distanceHeap struct {
nodes []*nodeWithDist
// pubkeyIndices maps public keys of nodes to their respective index in
// the heap. This is used as a way to avoid db lookups by using heap.Fix
// instead of having duplicate entries on the heap.
pubkeyIndices map[route.Vertex]int
}
// newDistanceHeap initializes a new distance heap. This is required because
// we must initialize the pubkeyIndices map for path-finding optimizations.
func newDistanceHeap(numNodes int) distanceHeap {
distHeap := distanceHeap{
pubkeyIndices: make(map[route.Vertex]int, numNodes),
nodes: make([]*nodeWithDist, 0, numNodes),
}
return distHeap
}
// Len returns the number of nodes in the priority queue.
//
// NOTE: This is part of the heap.Interface implementation.
func (d *distanceHeap) Len() int { return len(d.nodes) }
// Less returns whether the item in the priority queue with index i should sort
// before the item with index j.
//
// NOTE: This is part of the heap.Interface implementation.
func (d *distanceHeap) Less(i, j int) bool {
// If distances are equal, tie break on probability.
if d.nodes[i].dist == d.nodes[j].dist {
return d.nodes[i].probability > d.nodes[j].probability
}
return d.nodes[i].dist < d.nodes[j].dist
}
// Swap swaps the nodes at the passed indices in the priority queue.
//
// NOTE: This is part of the heap.Interface implementation.
func (d *distanceHeap) Swap(i, j int) {
d.nodes[i], d.nodes[j] = d.nodes[j], d.nodes[i]
d.pubkeyIndices[d.nodes[i].node] = i
d.pubkeyIndices[d.nodes[j].node] = j
}
// Push pushes the passed item onto the priority queue.
//
// NOTE: This is part of the heap.Interface implementation.
func (d *distanceHeap) Push(x interface{}) {
n := x.(*nodeWithDist)
d.nodes = append(d.nodes, n)
d.pubkeyIndices[n.node] = len(d.nodes) - 1
}
// Pop removes the highest priority item (according to Less) from the priority
// queue and returns it.
//
// NOTE: This is part of the heap.Interface implementation.
func (d *distanceHeap) Pop() interface{} {
n := len(d.nodes)
x := d.nodes[n-1]
d.nodes[n-1] = nil
d.nodes = d.nodes[0 : n-1]
delete(d.pubkeyIndices, x.node)
return x
}
// PushOrFix attempts to adjust the position of a given node in the heap.
// If the vertex already exists in the heap, then we must call heap.Fix to
// modify its position and reorder the heap. If the vertex does not already
// exist in the heap, then it is pushed onto the heap. Otherwise, we will end
// up performing more db lookups on the same node in the pathfinding algorithm.
func (d *distanceHeap) PushOrFix(dist *nodeWithDist) {
index, ok := d.pubkeyIndices[dist.node]
if !ok {
heap.Push(d, dist)
return
}
// Change the value at the specified index.
d.nodes[index] = dist
// Call heap.Fix to reorder the heap.
heap.Fix(d, index)
}
package localchans
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This means the
// package will not perform any logging by default until the caller requests
// it.
var log btclog.Logger
const Subsystem = "LCHN"
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled by
// default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info. This
// should be used in preference to SetLogWriter if the caller is also using
// btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package localchans
import (
"bytes"
"errors"
"fmt"
"sync"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/discovery"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing"
)
// Manager manages the node's local channels. The only operation that is
// currently implemented is updating forwarding policies.
type Manager struct {
// SelfPub contains the public key of the local node.
SelfPub *btcec.PublicKey
// DefaultRoutingPolicy is the default routing policy.
DefaultRoutingPolicy models.ForwardingPolicy
// UpdateForwardingPolicies is used by the manager to update active
// links with a new policy.
UpdateForwardingPolicies func(
chanPolicies map[wire.OutPoint]models.ForwardingPolicy)
// PropagateChanPolicyUpdate is called to persist a new policy to disk
// and broadcast it to the network.
PropagateChanPolicyUpdate func(
edgesToUpdate []discovery.EdgeWithInfo) error
// ForAllOutgoingChannels is required to iterate over all our local
// channels. The ChannelEdgePolicy parameter may be nil.
ForAllOutgoingChannels func(cb func(*models.ChannelEdgeInfo,
*models.ChannelEdgePolicy) error) error
// FetchChannel is used to query local channel parameters. Optionally an
// existing db tx can be supplied.
FetchChannel func(chanPoint wire.OutPoint) (*channeldb.OpenChannel,
error)
// AddEdge is used to add edge/channel to the topology of the router.
AddEdge func(edge *models.ChannelEdgeInfo) error
// policyUpdateLock ensures that the database and the link do not fall
// out of sync if there are concurrent fee update calls. Without it,
// there is a chance that policy A updates the database, then policy B
// updates the database, then policy B updates the link, then policy A
// updates the link.
policyUpdateLock sync.Mutex
}
// UpdatePolicy updates the policy for the specified channels on disk and in
// the active links.
func (r *Manager) UpdatePolicy(newSchema routing.ChannelPolicy,
createMissingEdge bool, chanPoints ...wire.OutPoint) (
[]*lnrpc.FailedUpdate, error) {
r.policyUpdateLock.Lock()
defer r.policyUpdateLock.Unlock()
// First, we'll construct a set of all the channels that we are
// trying to update.
unprocessedChans := make(map[wire.OutPoint]struct{})
for _, chanPoint := range chanPoints {
unprocessedChans[chanPoint] = struct{}{}
}
haveChanFilter := len(unprocessedChans) != 0
var failedUpdates []*lnrpc.FailedUpdate
var edgesToUpdate []discovery.EdgeWithInfo
policiesToUpdate := make(map[wire.OutPoint]models.ForwardingPolicy)
// NOTE: edge may be nil when this function is called.
processChan := func(info *models.ChannelEdgeInfo,
edge *models.ChannelEdgePolicy) error {
// If we have a channel filter, and this channel isn't a part
// of it, then we'll skip it.
_, ok := unprocessedChans[info.ChannelPoint]
if !ok && haveChanFilter {
return nil
}
// Mark this channel as found by removing it. unprocessedChans
// will be used to report invalid channels later on.
delete(unprocessedChans, info.ChannelPoint)
if edge == nil {
log.Errorf("Got nil channel edge policy when updating "+
"a channel. Channel point: %v",
info.ChannelPoint.String())
failedUpdates = append(failedUpdates, makeFailureItem(
info.ChannelPoint,
lnrpc.UpdateFailure_UPDATE_FAILURE_NOT_FOUND,
"edge policy not found",
))
return nil
}
// Apply the new policy to the edge.
err := r.updateEdge(info.ChannelPoint, edge, newSchema)
if err != nil {
failedUpdates = append(failedUpdates,
makeFailureItem(info.ChannelPoint,
lnrpc.UpdateFailure_UPDATE_FAILURE_INVALID_PARAMETER,
err.Error(),
))
return nil
}
// Add updated edge to list of edges to send to gossiper.
edgesToUpdate = append(edgesToUpdate, discovery.EdgeWithInfo{
Info: info,
Edge: edge,
})
// Extract inbound fees from the ExtraOpaqueData.
var inboundWireFee lnwire.Fee
_, err = edge.ExtraOpaqueData.ExtractRecords(&inboundWireFee)
if err != nil {
return err
}
inboundFee := models.NewInboundFeeFromWire(inboundWireFee)
// Add updated policy to list of policies to send to switch.
policiesToUpdate[info.ChannelPoint] = models.ForwardingPolicy{
BaseFee: edge.FeeBaseMSat,
FeeRate: edge.FeeProportionalMillionths,
TimeLockDelta: uint32(edge.TimeLockDelta),
MinHTLCOut: edge.MinHTLC,
MaxHTLC: edge.MaxHTLC,
InboundFee: inboundFee,
}
return nil
}
// Next, we'll loop over all the outgoing channels the router knows of.
// If we have a filter then we'll only collect those channels, otherwise
// we'll collect them all.
err := r.ForAllOutgoingChannels(processChan)
if err != nil {
return nil, err
}
// Construct a list of failed policy updates.
for chanPoint := range unprocessedChans {
channel, err := r.FetchChannel(chanPoint)
switch {
case errors.Is(err, channeldb.ErrChannelNotFound):
failedUpdates = append(failedUpdates,
makeFailureItem(chanPoint,
lnrpc.UpdateFailure_UPDATE_FAILURE_NOT_FOUND,
"not found",
))
case err != nil:
failedUpdates = append(failedUpdates,
makeFailureItem(chanPoint,
lnrpc.UpdateFailure_UPDATE_FAILURE_INTERNAL_ERR,
err.Error(),
))
case channel.IsPending:
failedUpdates = append(failedUpdates,
makeFailureItem(chanPoint,
lnrpc.UpdateFailure_UPDATE_FAILURE_PENDING,
"not yet confirmed",
))
// If the edge was not found, but the channel is found, that
// means the edge is missing in the graph database and should be
// recreated. The edge and policy are created in-memory. The
// edge is inserted in createEdge below and the policy will be
// added to the graph in the PropagateChanPolicyUpdate call
// below.
case createMissingEdge:
log.Warnf("Missing edge for active channel (%s) "+
"during policy update. Recreating edge with "+
"default policy.",
channel.FundingOutpoint.String())
info, edge, failedUpdate := r.createMissingEdge(
channel, newSchema,
)
if failedUpdate == nil {
err = processChan(info, edge)
if err != nil {
return nil, err
}
} else {
failedUpdates = append(
failedUpdates, failedUpdate,
)
}
default:
log.Warnf("Missing edge for active channel (%s) "+
"during policy update. Could not update "+
"policy.", channel.FundingOutpoint.String())
failedUpdates = append(failedUpdates,
makeFailureItem(chanPoint,
lnrpc.UpdateFailure_UPDATE_FAILURE_UNKNOWN,
"could not update policies",
))
}
}
// Commit the policy updates to disk and broadcast to the network. We
// validated the new policy above, so we expect no validation errors. If
// this would happen because of a bug, the link policy will be
// desynchronized. It is currently not possible to atomically commit
// multiple edge updates.
err = r.PropagateChanPolicyUpdate(edgesToUpdate)
if err != nil {
return nil, err
}
// Update active links.
r.UpdateForwardingPolicies(policiesToUpdate)
return failedUpdates, nil
}
func (r *Manager) createMissingEdge(channel *channeldb.OpenChannel,
newSchema routing.ChannelPolicy) (*models.ChannelEdgeInfo,
*models.ChannelEdgePolicy, *lnrpc.FailedUpdate) {
info, edge, err := r.createEdge(channel, time.Now())
if err != nil {
log.Errorf("Failed to recreate missing edge "+
"for channel (%s): %v",
channel.FundingOutpoint.String(), err)
return nil, nil, makeFailureItem(
channel.FundingOutpoint,
lnrpc.UpdateFailure_UPDATE_FAILURE_UNKNOWN,
"could not update policies",
)
}
// Validate the newly created edge policy with the user defined new
// schema before adding the edge to the database.
err = r.updateEdge(channel.FundingOutpoint, edge, newSchema)
if err != nil {
return nil, nil, makeFailureItem(
info.ChannelPoint,
lnrpc.UpdateFailure_UPDATE_FAILURE_INVALID_PARAMETER,
err.Error(),
)
}
// Insert the edge into the database to avoid `edge not
// found` errors during policy update propagation.
err = r.AddEdge(info)
if err != nil {
log.Errorf("Attempt to add missing edge for "+
"channel (%s) errored with: %v",
channel.FundingOutpoint.String(), err)
return nil, nil, makeFailureItem(
channel.FundingOutpoint,
lnrpc.UpdateFailure_UPDATE_FAILURE_UNKNOWN,
"could not add edge",
)
}
return info, edge, nil
}
// createEdge recreates an edge and policy from an open channel in-memory.
func (r *Manager) createEdge(channel *channeldb.OpenChannel,
timestamp time.Time) (*models.ChannelEdgeInfo,
*models.ChannelEdgePolicy, error) {
nodeKey1Bytes := r.SelfPub.SerializeCompressed()
nodeKey2Bytes := channel.IdentityPub.SerializeCompressed()
bitcoinKey1Bytes := channel.LocalChanCfg.MultiSigKey.PubKey.
SerializeCompressed()
bitcoinKey2Bytes := channel.RemoteChanCfg.MultiSigKey.PubKey.
SerializeCompressed()
channelFlags := lnwire.ChanUpdateChanFlags(0)
// Make it such that node_id_1 is the lexicographically-lesser of the
// two compressed keys sorted in ascending lexicographic order.
if bytes.Compare(nodeKey2Bytes, nodeKey1Bytes) < 0 {
nodeKey1Bytes, nodeKey2Bytes = nodeKey2Bytes, nodeKey1Bytes
bitcoinKey1Bytes, bitcoinKey2Bytes = bitcoinKey2Bytes,
bitcoinKey1Bytes
channelFlags = 1
}
var featureBuf bytes.Buffer
err := lnwire.NewRawFeatureVector().Encode(&featureBuf)
if err != nil {
return nil, nil, fmt.Errorf("unable to encode features: %w",
err)
}
// We need to make sure we use the real scid for public confirmed
// zero-conf channels.
shortChanID := channel.ShortChanID()
isPublic := channel.ChannelFlags&lnwire.FFAnnounceChannel != 0
if isPublic && channel.IsZeroConf() && channel.ZeroConfConfirmed() {
shortChanID = channel.ZeroConfRealScid()
}
info := &models.ChannelEdgeInfo{
ChannelID: shortChanID.ToUint64(),
ChainHash: channel.ChainHash,
Features: featureBuf.Bytes(),
Capacity: channel.Capacity,
ChannelPoint: channel.FundingOutpoint,
}
copy(info.NodeKey1Bytes[:], nodeKey1Bytes)
copy(info.NodeKey2Bytes[:], nodeKey2Bytes)
copy(info.BitcoinKey1Bytes[:], bitcoinKey1Bytes)
copy(info.BitcoinKey2Bytes[:], bitcoinKey2Bytes)
// Construct a dummy channel edge policy with default values that will
// be updated with the new values in the call to processChan below.
timeLockDelta := uint16(r.DefaultRoutingPolicy.TimeLockDelta)
edge := &models.ChannelEdgePolicy{
ChannelID: shortChanID.ToUint64(),
LastUpdate: timestamp,
TimeLockDelta: timeLockDelta,
ChannelFlags: channelFlags,
MessageFlags: lnwire.ChanUpdateRequiredMaxHtlc,
FeeBaseMSat: r.DefaultRoutingPolicy.BaseFee,
FeeProportionalMillionths: r.DefaultRoutingPolicy.FeeRate,
MinHTLC: r.DefaultRoutingPolicy.MinHTLCOut,
MaxHTLC: r.DefaultRoutingPolicy.MaxHTLC,
}
copy(edge.ToNode[:], channel.IdentityPub.SerializeCompressed())
return info, edge, nil
}
// updateEdge updates the given edge with the new schema.
func (r *Manager) updateEdge(chanPoint wire.OutPoint,
edge *models.ChannelEdgePolicy,
newSchema routing.ChannelPolicy) error {
channel, err := r.FetchChannel(chanPoint)
if err != nil {
return err
}
// Update forwarding fee scheme and required time lock delta.
edge.FeeBaseMSat = newSchema.BaseFee
edge.FeeProportionalMillionths = lnwire.MilliSatoshi(
newSchema.FeeRate,
)
// If inbound fees are set, we update the edge with them.
err = fn.MapOptionZ(newSchema.InboundFee,
func(f models.InboundFee) error {
inboundWireFee := f.ToWire()
return edge.ExtraOpaqueData.PackRecords(
&inboundWireFee,
)
})
if err != nil {
return err
}
edge.TimeLockDelta = uint16(newSchema.TimeLockDelta)
// Retrieve negotiated channel htlc amt limits.
amtMin, amtMax, err := r.getHtlcAmtLimits(channel)
if err != nil {
return err
}
// We now update the edge max htlc value.
switch {
// If a non-zero max htlc was specified, use it to update the edge.
// Otherwise keep the value unchanged.
case newSchema.MaxHTLC != 0:
edge.MaxHTLC = newSchema.MaxHTLC
// If this edge still doesn't have a max htlc set, set it to the max.
// This is an on-the-fly migration.
case !edge.MessageFlags.HasMaxHtlc():
edge.MaxHTLC = amtMax
// If this edge has a max htlc that exceeds what the channel can
// actually carry, correct it now. This can happen, because we
// previously set the max htlc to the channel capacity.
case edge.MaxHTLC > amtMax:
edge.MaxHTLC = amtMax
}
// If a new min htlc is specified, update the edge.
if newSchema.MinHTLC != nil {
edge.MinHTLC = *newSchema.MinHTLC
}
// If the MaxHtlc flag wasn't already set, we can set it now.
edge.MessageFlags |= lnwire.ChanUpdateRequiredMaxHtlc
// Validate htlc amount constraints.
switch {
case edge.MinHTLC < amtMin:
return fmt.Errorf(
"min htlc amount of %v is below min htlc parameter of %v",
edge.MinHTLC, amtMin,
)
case edge.MaxHTLC > amtMax:
return fmt.Errorf(
"max htlc size of %v is above max pending amount of %v",
edge.MaxHTLC, amtMax,
)
case edge.MinHTLC > edge.MaxHTLC:
return fmt.Errorf(
"min_htlc %v greater than max_htlc %v",
edge.MinHTLC, edge.MaxHTLC,
)
}
// Clear signature to help prevent usage of the previous signature.
edge.SetSigBytes(nil)
return nil
}
// getHtlcAmtLimits retrieves the negotiated channel min and max htlc amount
// constraints.
func (r *Manager) getHtlcAmtLimits(ch *channeldb.OpenChannel) (
lnwire.MilliSatoshi, lnwire.MilliSatoshi, error) {
// The max htlc policy field must be less than or equal to the channel
// capacity AND less than or equal to the max in-flight HTLC value.
// Since the latter is always less than or equal to the former, just
// return the max in-flight value.
maxAmt := ch.LocalChanCfg.ChannelStateBounds.MaxPendingAmount
return ch.LocalChanCfg.MinHTLC, maxAmt, nil
}
// makeFailureItem creates a lnrpc.FailedUpdate object.
func makeFailureItem(outPoint wire.OutPoint, updateFailure lnrpc.UpdateFailure,
errStr string) *lnrpc.FailedUpdate {
outpoint := lnrpc.MarshalOutPoint(&outPoint)
return &lnrpc.FailedUpdate{
Outpoint: outpoint,
Reason: updateFailure,
UpdateError: errStr,
}
}
package routing
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/routing/chainview"
)
// log is a logger that is initialized with no output filters. This means the
// package will not perform any logging by default until the caller requests
// it.
var log btclog.Logger
const Subsystem = "CRTR"
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled by
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info. This
// should be used in preference to SetLogWriter if the caller is also using
// btclog.
func UseLogger(logger btclog.Logger) {
log = logger
chainview.UseLogger(logger)
}
package routing
import (
"bytes"
"errors"
"fmt"
"io"
"sync"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btclog/v2"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// DefaultPenaltyHalfLife is the default half-life duration. The
// half-life duration defines after how much time a penalized node or
// channel is back at 50% probability.
DefaultPenaltyHalfLife = time.Hour
// minSecondChanceInterval is the minimum time required between
// second-chance failures.
//
// If nodes return a channel policy related failure, they may get a
// second chance to forward the payment. It could be that the channel
// policy that we are aware of is not up to date. This is especially
// important in case of mobile apps that are mostly offline.
//
// However, we don't want to give nodes the option to endlessly return
// new channel updates so that we are kept busy trying to route through
// that node until the payment loop times out.
//
// Therefore we only grant a second chance to a node if the previous
// second chance is sufficiently long ago. This is what
// minSecondChanceInterval defines. If a second policy failure comes in
// within that interval, we will apply a penalty.
//
// Second chances granted are tracked on the level of node pairs. This
// means that if a node has multiple channels to the same peer, they
// will only get a single second chance to route to that peer again.
// Nodes forward non-strict, so it isn't necessary to apply a less
// restrictive channel level tracking scheme here.
minSecondChanceInterval = time.Minute
// DefaultMaxMcHistory is the default maximum history size.
DefaultMaxMcHistory = 1000
// DefaultMcFlushInterval is the default interval we use to flush MC state
// to the database.
DefaultMcFlushInterval = time.Second
// prevSuccessProbability is the assumed probability for node pairs that
// successfully relayed the previous attempt.
prevSuccessProbability = 0.95
// DefaultAprioriWeight is the default a priori weight. See
// MissionControlConfig for further explanation.
DefaultAprioriWeight = 0.5
// DefaultMinFailureRelaxInterval is the default minimum time that must
// have passed since the previously recorded failure before the failure
// amount may be raised.
DefaultMinFailureRelaxInterval = time.Minute
// DefaultFeeEstimationTimeout is the default value for
// FeeEstimationTimeout. It defines the maximum duration that the
// probing fee estimation is allowed to take.
DefaultFeeEstimationTimeout = time.Minute
// DefaultMissionControlNamespace is the name of the default mission
// control name space. This is used as the sub-bucket key within the
// top level DB bucket to store mission control results.
DefaultMissionControlNamespace = "default"
)
var (
// ErrInvalidMcHistory is returned if we get a negative mission control
// history count.
ErrInvalidMcHistory = errors.New("mission control history must be " +
">= 0")
// ErrInvalidFailureInterval is returned if we get an invalid failure
// interval.
ErrInvalidFailureInterval = errors.New("failure interval must be >= 0")
)
// NodeResults contains previous results from a node to its peers.
type NodeResults map[route.Vertex]TimedPairResult
// mcConfig holds various config members that will be required by all
// MissionControl instances and will be the same regardless of namespace.
type mcConfig struct {
// clock is a time source used by mission control.
clock clock.Clock
// selfNode is our pubkey.
selfNode route.Vertex
}
// MissionControl contains state which summarizes the past attempts of HTLC
// routing by external callers when sending payments throughout the network. It
// acts as a shared memory during routing attempts with the goal to optimize the
// payment attempt success rate.
//
// Failed payment attempts are reported to mission control. These reports are
// used to track the time of the last node or channel level failure. The time
// since the last failure is used to estimate a success probability that is fed
// into the path finding process for subsequent payment attempts.
type MissionControl struct {
cfg *mcConfig
// state is the internal mission control state that is input for
// probability estimation.
state *missionControlState
store *missionControlStore
// estimator is the probability estimator that is used with the payment
// results that mission control collects.
estimator Estimator
// onConfigUpdate is a function that is called whenever the
// mission control state is updated.
onConfigUpdate fn.Option[func(cfg *MissionControlConfig)]
log btclog.Logger
mu sync.Mutex
}
// MissionController manages MissionControl instances in various namespaces.
type MissionController struct {
db kvdb.Backend
cfg *mcConfig
defaultMCCfg *MissionControlConfig
mc map[string]*MissionControl
mu sync.Mutex
// TODO(roasbeef): further counters, if vertex continually unavailable,
// add to another generation
// TODO(roasbeef): also add favorable metrics for nodes
}
// GetNamespacedStore returns the MissionControl in the given namespace. If one
// does not yet exist, then it is initialised.
func (m *MissionController) GetNamespacedStore(ns string) (*MissionControl,
error) {
m.mu.Lock()
defer m.mu.Unlock()
if mc, ok := m.mc[ns]; ok {
return mc, nil
}
return m.initMissionControl(ns)
}
// ListNamespaces returns a list of the namespaces that the MissionController
// is aware of.
func (m *MissionController) ListNamespaces() []string {
m.mu.Lock()
defer m.mu.Unlock()
namespaces := make([]string, 0, len(m.mc))
for ns := range m.mc {
namespaces = append(namespaces, ns)
}
return namespaces
}
// MissionControlConfig defines parameters that control mission control
// behaviour.
type MissionControlConfig struct {
// Estimator gives probability estimates for node pairs.
Estimator Estimator
// OnConfigUpdate is function that is called whenever the
// mission control state is updated.
OnConfigUpdate fn.Option[func(cfg *MissionControlConfig)]
// MaxMcHistory defines the maximum number of payment results that are
// held on disk.
MaxMcHistory int
// McFlushInterval defines the ticker interval when we flush the
// accumulated state to the DB.
McFlushInterval time.Duration
// MinFailureRelaxInterval is the minimum time that must have passed
// since the previously recorded failure before the failure amount may
// be raised.
MinFailureRelaxInterval time.Duration
}
func (c *MissionControlConfig) validate() error {
if c.MaxMcHistory < 0 {
return ErrInvalidMcHistory
}
if c.MinFailureRelaxInterval < 0 {
return ErrInvalidFailureInterval
}
return nil
}
// String returns a string representation of a mission control config.
func (c *MissionControlConfig) String() string {
return fmt.Sprintf("maximum history: %v, minimum failure relax "+
"interval: %v", c.MaxMcHistory, c.MinFailureRelaxInterval)
}
// TimedPairResult describes a timestamped pair result.
type TimedPairResult struct {
// FailTime is the time of the last failure.
FailTime time.Time
// FailAmt is the amount of the last failure. This amount may be pushed
// up if a later success is higher than the last failed amount.
FailAmt lnwire.MilliSatoshi
// SuccessTime is the time of the last success.
SuccessTime time.Time
// SuccessAmt is the highest amount that successfully forwarded. This
// isn't necessarily the last success amount. The value of this field
// may also be pushed down if a later failure is lower than the highest
// success amount. Because of this, SuccessAmt may not match
// SuccessTime.
SuccessAmt lnwire.MilliSatoshi
}
// MissionControlSnapshot contains a snapshot of the current state of mission
// control.
type MissionControlSnapshot struct {
// Pairs is a list of channels for which specific information is
// logged.
Pairs []MissionControlPairSnapshot
}
// MissionControlPairSnapshot contains a snapshot of the current node pair
// state in mission control.
type MissionControlPairSnapshot struct {
// Pair is the node pair of which the state is described.
Pair DirectedNodePair
// TimedPairResult contains the data for this pair.
TimedPairResult
}
// paymentResult is the information that becomes available when a payment
// attempt completes.
type paymentResult struct {
id uint64
timeFwd tlv.RecordT[tlv.TlvType0, uint64]
timeReply tlv.RecordT[tlv.TlvType1, uint64]
route tlv.RecordT[tlv.TlvType2, mcRoute]
// failure holds information related to the failure of a payment. The
// presence of this record indicates a payment failure. The absence of
// this record indicates a successful payment.
failure tlv.OptionalRecordT[tlv.TlvType3, paymentFailure]
}
// newPaymentResult constructs a new paymentResult.
func newPaymentResult(id uint64, rt *mcRoute, timeFwd, timeReply time.Time,
failure *paymentFailure) *paymentResult {
result := &paymentResult{
id: id,
timeFwd: tlv.NewPrimitiveRecord[tlv.TlvType0](
uint64(timeFwd.UnixNano()),
),
timeReply: tlv.NewPrimitiveRecord[tlv.TlvType1](
uint64(timeReply.UnixNano()),
),
route: tlv.NewRecordT[tlv.TlvType2](*rt),
}
if failure != nil {
result.failure = tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType3](*failure),
)
}
return result
}
// NewMissionController returns a new instance of MissionController.
func NewMissionController(db kvdb.Backend, self route.Vertex,
cfg *MissionControlConfig) (*MissionController, error) {
log.Debugf("Instantiating mission control with config: %v, %v", cfg,
cfg.Estimator)
if err := cfg.validate(); err != nil {
return nil, err
}
mcCfg := &mcConfig{
clock: clock.NewDefaultClock(),
selfNode: self,
}
mgr := &MissionController{
db: db,
defaultMCCfg: cfg,
cfg: mcCfg,
mc: make(map[string]*MissionControl),
}
if err := mgr.loadMissionControls(); err != nil {
return nil, err
}
for _, mc := range mgr.mc {
if err := mc.init(); err != nil {
return nil, err
}
}
return mgr, nil
}
// loadMissionControls initialises a MissionControl in the default namespace if
// one does not yet exist. It then initialises a MissionControl for all other
// namespaces found in the DB.
//
// NOTE: this should only be called once during MissionController construction.
func (m *MissionController) loadMissionControls() error {
m.mu.Lock()
defer m.mu.Unlock()
// Always initialise the default namespace.
_, err := m.initMissionControl(DefaultMissionControlNamespace)
if err != nil {
return err
}
namespaces := make(map[string]struct{})
err = m.db.View(func(tx walletdb.ReadTx) error {
mcStoreBkt := tx.ReadBucket(resultsKey)
if mcStoreBkt == nil {
return fmt.Errorf("top level mission control bucket " +
"not found")
}
// Iterate through all the keys in the bucket and collect the
// namespaces.
return mcStoreBkt.ForEach(func(k, _ []byte) error {
// We've already initialised the default namespace so
// we can skip it.
if string(k) == DefaultMissionControlNamespace {
return nil
}
namespaces[string(k)] = struct{}{}
return nil
})
}, func() {})
if err != nil {
return err
}
// Now, iterate through all the namespaces and initialise them.
for ns := range namespaces {
_, err = m.initMissionControl(ns)
if err != nil {
return err
}
}
return nil
}
// initMissionControl creates a new MissionControl instance with the given
// namespace if one does not yet exist.
//
// NOTE: the MissionController's mutex must be held before calling this method.
func (m *MissionController) initMissionControl(namespace string) (
*MissionControl, error) {
// If a mission control with this namespace has already been initialised
// then there is nothing left to do.
if mc, ok := m.mc[namespace]; ok {
return mc, nil
}
cfg := m.defaultMCCfg
store, err := newMissionControlStore(
newNamespacedDB(m.db, namespace), cfg.MaxMcHistory,
cfg.McFlushInterval,
)
if err != nil {
return nil, err
}
mc := &MissionControl{
cfg: m.cfg,
state: newMissionControlState(
cfg.MinFailureRelaxInterval,
),
store: store,
estimator: cfg.Estimator,
log: log.WithPrefix(fmt.Sprintf("[%s]:", namespace)),
onConfigUpdate: cfg.OnConfigUpdate,
}
m.mc[namespace] = mc
return mc, nil
}
// RunStoreTickers runs the mission controller store's tickers.
func (m *MissionController) RunStoreTickers() {
m.mu.Lock()
defer m.mu.Unlock()
for _, mc := range m.mc {
mc.store.run()
}
}
// StopStoreTickers stops the mission control store's tickers.
func (m *MissionController) StopStoreTickers() {
log.Debug("Stopping mission control store ticker")
defer log.Debug("Mission control store ticker stopped")
m.mu.Lock()
defer m.mu.Unlock()
for _, mc := range m.mc {
mc.store.stop()
}
}
// init initializes mission control with historical data.
func (m *MissionControl) init() error {
m.log.Debugf("Mission control state reconstruction started")
m.mu.Lock()
defer m.mu.Unlock()
start := time.Now()
results, err := m.store.fetchAll()
if err != nil {
return err
}
for _, result := range results {
_ = m.applyPaymentResult(result)
}
m.log.Debugf("Mission control state reconstruction finished: "+
"n=%v, time=%v", len(results), time.Since(start))
return nil
}
// GetConfig returns the config that mission control is currently configured
// with. All fields are copied by value, so we do not need to worry about
// mutation.
func (m *MissionControl) GetConfig() *MissionControlConfig {
m.mu.Lock()
defer m.mu.Unlock()
return &MissionControlConfig{
Estimator: m.estimator,
MaxMcHistory: m.store.maxRecords,
McFlushInterval: m.store.flushInterval,
MinFailureRelaxInterval: m.state.minFailureRelaxInterval,
}
}
// SetConfig validates the config provided and updates mission control's config
// if it is valid.
func (m *MissionControl) SetConfig(cfg *MissionControlConfig) error {
if cfg == nil {
return errors.New("nil mission control config")
}
if err := cfg.validate(); err != nil {
return err
}
m.mu.Lock()
defer m.mu.Unlock()
m.log.Infof("Active mission control cfg: %v, estimator: %v", cfg,
cfg.Estimator)
m.store.maxRecords = cfg.MaxMcHistory
m.state.minFailureRelaxInterval = cfg.MinFailureRelaxInterval
m.estimator = cfg.Estimator
// Execute the callback function if it is set.
m.onConfigUpdate.WhenSome(func(f func(cfg *MissionControlConfig)) {
f(cfg)
})
return nil
}
// ResetHistory resets the history of MissionControl returning it to a state as
// if no payment attempts have been made.
func (m *MissionControl) ResetHistory() error {
m.mu.Lock()
defer m.mu.Unlock()
if err := m.store.clear(); err != nil {
return err
}
m.state.resetHistory()
m.log.Debugf("Mission control history cleared")
return nil
}
// GetProbability is expected to return the success probability of a payment
// from fromNode along edge.
func (m *MissionControl) GetProbability(fromNode, toNode route.Vertex,
amt lnwire.MilliSatoshi, capacity btcutil.Amount) float64 {
m.mu.Lock()
defer m.mu.Unlock()
now := m.cfg.clock.Now()
results, _ := m.state.getLastPairResult(fromNode)
// Use a distinct probability estimation function for local channels.
if fromNode == m.cfg.selfNode {
return m.estimator.LocalPairProbability(now, results, toNode)
}
return m.estimator.PairProbability(
now, results, toNode, amt, capacity,
)
}
// GetHistorySnapshot takes a snapshot from the current mission control state
// and actual probability estimates.
func (m *MissionControl) GetHistorySnapshot() *MissionControlSnapshot {
m.mu.Lock()
defer m.mu.Unlock()
m.log.Debugf("Requesting history snapshot from mission control")
return m.state.getSnapshot()
}
// ImportHistory imports the set of mission control results provided to our
// in-memory state. These results are not persisted, so will not survive
// restarts.
func (m *MissionControl) ImportHistory(history *MissionControlSnapshot,
force bool) error {
if history == nil {
return errors.New("cannot import nil history")
}
m.mu.Lock()
defer m.mu.Unlock()
m.log.Infof("Importing history snapshot with %v pairs to mission "+
"control", len(history.Pairs))
imported := m.state.importSnapshot(history, force)
m.log.Infof("Imported %v results to mission control", imported)
return nil
}
// GetPairHistorySnapshot returns the stored history for a given node pair.
func (m *MissionControl) GetPairHistorySnapshot(
fromNode, toNode route.Vertex) TimedPairResult {
m.mu.Lock()
defer m.mu.Unlock()
results, ok := m.state.getLastPairResult(fromNode)
if !ok {
return TimedPairResult{}
}
result, ok := results[toNode]
if !ok {
return TimedPairResult{}
}
return result
}
// ReportPaymentFail reports a failed payment to mission control as input for
// future probability estimates. The failureSourceIdx argument indicates the
// failure source. If it is nil, the failure source is unknown. This function
// returns a reason if this failure is a final failure. In that case no further
// payment attempts need to be made.
func (m *MissionControl) ReportPaymentFail(paymentID uint64, rt *route.Route,
failureSourceIdx *int, failure lnwire.FailureMessage) (
*channeldb.FailureReason, error) {
timestamp := m.cfg.clock.Now()
result := newPaymentResult(
paymentID, extractMCRoute(rt), timestamp, timestamp,
newPaymentFailure(failureSourceIdx, failure),
)
return m.processPaymentResult(result)
}
// ReportPaymentSuccess reports a successful payment to mission control as input
// for future probability estimates.
func (m *MissionControl) ReportPaymentSuccess(paymentID uint64,
rt *route.Route) error {
timestamp := m.cfg.clock.Now()
result := newPaymentResult(
paymentID, extractMCRoute(rt), timestamp, timestamp, nil,
)
_, err := m.processPaymentResult(result)
return err
}
// processPaymentResult stores a payment result in the mission control store and
// updates mission control's in-memory state.
func (m *MissionControl) processPaymentResult(result *paymentResult) (
*channeldb.FailureReason, error) {
// Store complete result in database.
m.store.AddResult(result)
m.mu.Lock()
defer m.mu.Unlock()
// Apply result to update mission control state.
reason := m.applyPaymentResult(result)
return reason, nil
}
// applyPaymentResult applies a payment result as input for future probability
// estimates. It returns a bool indicating whether this error is a final error
// and no further payment attempts need to be made.
func (m *MissionControl) applyPaymentResult(
result *paymentResult) *channeldb.FailureReason {
// Interpret result.
i := interpretResult(&result.route.Val, result.failure.ValOpt())
if i.policyFailure != nil {
if m.state.requestSecondChance(
time.Unix(0, int64(result.timeReply.Val)),
i.policyFailure.From, i.policyFailure.To,
) {
return nil
}
}
// If there is a node-level failure, record a failure for every tried
// connection of that node. A node-level failure can be considered as a
// failure that would have occurred with any of the node's channels.
//
// Ideally we'd also record the failure for the untried connections of
// the node. Unfortunately this would require access to the graph and
// adding this dependency and db calls does not outweigh the benefits.
//
// Untried connections will fall back to the node probability. After the
// call to setAllPairResult below, the node probability will be equal to
// the probability of the tried channels except that the a priori
// probability is mixed in too. This effect is controlled by the
// aprioriWeight parameter. If that parameter isn't set to an extreme
// and there are a few known connections, there shouldn't be much of a
// difference. The largest difference occurs when aprioriWeight is 1. In
// that case, a node-level failure would not be applied to untried
// channels.
if i.nodeFailure != nil {
m.log.Debugf("Reporting node failure to Mission Control: "+
"node=%v", *i.nodeFailure)
m.state.setAllFail(
*i.nodeFailure,
time.Unix(0, int64(result.timeReply.Val)),
)
}
for pair, pairResult := range i.pairResults {
pairResult := pairResult
if pairResult.success {
m.log.Debugf("Reporting pair success to Mission "+
"Control: pair=%v, amt=%v",
pair, pairResult.amt)
} else {
m.log.Debugf("Reporting pair failure to Mission "+
"Control: pair=%v, amt=%v",
pair, pairResult.amt)
}
m.state.setLastPairResult(
pair.From, pair.To,
time.Unix(0, int64(result.timeReply.Val)), &pairResult,
false,
)
}
return i.finalFailureReason
}
// namespacedDB is an implementation of the missionControlDB that gives a user
// of the interface access to a namespaced bucket within the top level mission
// control bucket.
type namespacedDB struct {
topLevelBucketKey []byte
namespace []byte
db kvdb.Backend
}
// A compile-time check to ensure that namespacedDB implements missionControlDB.
var _ missionControlDB = (*namespacedDB)(nil)
// newDefaultNamespacedStore creates an instance of namespaceDB that uses the
// default namespace.
func newDefaultNamespacedStore(db kvdb.Backend) missionControlDB {
return newNamespacedDB(db, DefaultMissionControlNamespace)
}
// newNamespacedDB creates a new instance of missionControlDB where the DB will
// have access to a namespaced bucket within the top level mission control
// bucket.
func newNamespacedDB(db kvdb.Backend, namespace string) missionControlDB {
return &namespacedDB{
db: db,
namespace: []byte(namespace),
topLevelBucketKey: resultsKey,
}
}
// update can be used to perform reads and writes on the given bucket.
//
// NOTE: this is part of the missionControlDB interface.
func (n *namespacedDB) update(f func(bkt walletdb.ReadWriteBucket) error,
reset func()) error {
return n.db.Update(func(tx kvdb.RwTx) error {
mcStoreBkt, err := tx.CreateTopLevelBucket(n.topLevelBucketKey)
if err != nil {
return fmt.Errorf("cannot create top level mission "+
"control bucket: %w", err)
}
namespacedBkt, err := mcStoreBkt.CreateBucketIfNotExists(
n.namespace,
)
if err != nil {
return fmt.Errorf("cannot create namespaced bucket "+
"(%s) in mission control store: %w",
n.namespace, err)
}
return f(namespacedBkt)
}, reset)
}
// view can be used to perform reads on the given bucket.
//
// NOTE: this is part of the missionControlDB interface.
func (n *namespacedDB) view(f func(bkt walletdb.ReadBucket) error,
reset func()) error {
return n.db.View(func(tx kvdb.RTx) error {
mcStoreBkt := tx.ReadBucket(n.topLevelBucketKey)
if mcStoreBkt == nil {
return fmt.Errorf("top level mission control bucket " +
"not found")
}
namespacedBkt := mcStoreBkt.NestedReadBucket(n.namespace)
if namespacedBkt == nil {
return fmt.Errorf("namespaced bucket (%s) not found "+
"in mission control store", n.namespace)
}
return f(namespacedBkt)
}, reset)
}
// purge will delete all the contents in the namespace.
//
// NOTE: this is part of the missionControlDB interface.
func (n *namespacedDB) purge() error {
return n.db.Update(func(tx kvdb.RwTx) error {
mcStoreBkt := tx.ReadWriteBucket(n.topLevelBucketKey)
if mcStoreBkt == nil {
return nil
}
err := mcStoreBkt.DeleteNestedBucket(n.namespace)
if err != nil {
return err
}
_, err = mcStoreBkt.CreateBucket(n.namespace)
return err
}, func() {})
}
// paymentFailure represents the presence of a payment failure. It may or may
// not include additional information about said failure.
type paymentFailure struct {
info tlv.OptionalRecordT[tlv.TlvType0, paymentFailureInfo]
}
// newPaymentFailure constructs a new paymentFailure struct. If the source
// index is nil, then an empty paymentFailure is returned. This represents a
// failure with unknown details. Otherwise, the index and failure message are
// used to populate the info field of the paymentFailure.
func newPaymentFailure(sourceIdx *int,
failureMsg lnwire.FailureMessage) *paymentFailure {
if sourceIdx == nil {
return &paymentFailure{}
}
info := paymentFailureInfo{
sourceIdx: tlv.NewPrimitiveRecord[tlv.TlvType0](
uint8(*sourceIdx),
),
msg: tlv.NewRecordT[tlv.TlvType1](failureMessage{failureMsg}),
}
return &paymentFailure{
info: tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType0](info)),
}
}
// Record returns a TLV record that can be used to encode/decode a
// paymentFailure to/from a TLV stream.
func (r *paymentFailure) Record() tlv.Record {
recordSize := func() uint64 {
var (
b bytes.Buffer
buf [8]byte
)
if err := encodePaymentFailure(&b, r, &buf); err != nil {
panic(err)
}
return uint64(len(b.Bytes()))
}
return tlv.MakeDynamicRecord(
0, r, recordSize, encodePaymentFailure, decodePaymentFailure,
)
}
func encodePaymentFailure(w io.Writer, val interface{}, _ *[8]byte) error {
if v, ok := val.(*paymentFailure); ok {
var recordProducers []tlv.RecordProducer
v.info.WhenSome(
func(r tlv.RecordT[tlv.TlvType0, paymentFailureInfo]) {
recordProducers = append(recordProducers, &r)
},
)
return lnwire.EncodeRecordsTo(
w, lnwire.ProduceRecordsSorted(recordProducers...),
)
}
return tlv.NewTypeForEncodingErr(val, "routing.paymentFailure")
}
func decodePaymentFailure(r io.Reader, val interface{}, _ *[8]byte,
l uint64) error {
if v, ok := val.(*paymentFailure); ok {
var h paymentFailure
info := tlv.ZeroRecordT[tlv.TlvType0, paymentFailureInfo]()
typeMap, err := lnwire.DecodeRecords(
r, lnwire.ProduceRecordsSorted(&info)...,
)
if err != nil {
return err
}
if _, ok := typeMap[h.info.TlvType()]; ok {
h.info = tlv.SomeRecordT(info)
}
*v = h
return nil
}
return tlv.NewTypeForDecodingErr(val, "routing.paymentFailure", l, l)
}
// paymentFailureInfo holds additional information about a payment failure.
type paymentFailureInfo struct {
sourceIdx tlv.RecordT[tlv.TlvType0, uint8]
msg tlv.RecordT[tlv.TlvType1, failureMessage]
}
// Record returns a TLV record that can be used to encode/decode a
// paymentFailureInfo to/from a TLV stream.
func (r *paymentFailureInfo) Record() tlv.Record {
recordSize := func() uint64 {
var (
b bytes.Buffer
buf [8]byte
)
if err := encodePaymentFailureInfo(&b, r, &buf); err != nil {
panic(err)
}
return uint64(len(b.Bytes()))
}
return tlv.MakeDynamicRecord(
0, r, recordSize, encodePaymentFailureInfo,
decodePaymentFailureInfo,
)
}
func encodePaymentFailureInfo(w io.Writer, val interface{}, _ *[8]byte) error {
if v, ok := val.(*paymentFailureInfo); ok {
return lnwire.EncodeRecordsTo(
w, lnwire.ProduceRecordsSorted(
&v.sourceIdx, &v.msg,
),
)
}
return tlv.NewTypeForEncodingErr(val, "routing.paymentFailureInfo")
}
func decodePaymentFailureInfo(r io.Reader, val interface{}, _ *[8]byte,
l uint64) error {
if v, ok := val.(*paymentFailureInfo); ok {
var h paymentFailureInfo
_, err := lnwire.DecodeRecords(
r,
lnwire.ProduceRecordsSorted(&h.sourceIdx, &h.msg)...,
)
if err != nil {
return err
}
*v = h
return nil
}
return tlv.NewTypeForDecodingErr(
val, "routing.paymentFailureInfo", l, l,
)
}
package routing
import (
"time"
"github.com/lightningnetwork/lnd/routing/route"
)
// missionControlState is an object that manages the internal mission control
// state. Note that it isn't thread safe and synchronization needs to be
// enforced externally.
type missionControlState struct {
// lastPairResult tracks the last payment result (on a pair basis) for
// each transited node. This is a multi-layer map that allows us to look
// up the failure history of all connected channels (node pairs) for a
// particular node.
lastPairResult map[route.Vertex]NodeResults
// lastSecondChance tracks the last time a second chance was granted for
// a directed node pair.
lastSecondChance map[DirectedNodePair]time.Time
// minFailureRelaxInterval is the minimum time that must have passed
// since the previously recorded failure before the failure amount may
// be raised.
minFailureRelaxInterval time.Duration
}
// newMissionControlState instantiates a new mission control state object.
func newMissionControlState(
minFailureRelaxInterval time.Duration) *missionControlState {
return &missionControlState{
lastPairResult: make(map[route.Vertex]NodeResults),
lastSecondChance: make(map[DirectedNodePair]time.Time),
minFailureRelaxInterval: minFailureRelaxInterval,
}
}
// getLastPairResult returns the current state for connections to the given
// node.
func (m *missionControlState) getLastPairResult(node route.Vertex) (NodeResults,
bool) {
result, ok := m.lastPairResult[node]
return result, ok
}
// ResetHistory resets the history of missionControlState returning it to a
// state as if no payment attempts have been made.
func (m *missionControlState) resetHistory() {
m.lastPairResult = make(map[route.Vertex]NodeResults)
m.lastSecondChance = make(map[DirectedNodePair]time.Time)
}
// setLastPairResult stores a result for a node pair.
func (m *missionControlState) setLastPairResult(fromNode, toNode route.Vertex,
timestamp time.Time, result *pairResult, force bool) {
nodePairs, ok := m.lastPairResult[fromNode]
if !ok {
nodePairs = make(NodeResults)
m.lastPairResult[fromNode] = nodePairs
}
current := nodePairs[toNode]
// Apply the new result to the existing data for this pair. If there is
// no existing data, apply it to the default values for TimedPairResult.
if result.success {
successAmt := result.amt
current.SuccessTime = timestamp
// Only update the success amount if this amount is higher. This
// prevents the success range from shrinking when there is no
// reason to do so. For example: small amount probes shouldn't
// affect a previous success for a much larger amount.
if force || successAmt > current.SuccessAmt {
current.SuccessAmt = successAmt
}
// If the success amount goes into the failure range, move the
// failure range up. Future attempts up to the success amount
// are likely to succeed. We don't want to clear the failure
// completely, because we haven't learnt much for amounts above
// the current success amount.
if force || (!current.FailTime.IsZero() &&
successAmt >= current.FailAmt) {
current.FailAmt = successAmt + 1
}
} else {
// For failures we always want to update both the amount and the
// time. Those need to relate to the same result, because the
// time is used to gradually diminish the penalty for that
// specific result. Updating the timestamp but not the amount
// could cause a failure for a lower amount (a more severe
// condition) to be revived as if it just happened.
failAmt := result.amt
// Drop result if it would increase the failure amount too soon
// after a previous failure. This can happen if htlc results
// come in out of order. This check makes it easier for payment
// processes to converge to a final state.
failInterval := timestamp.Sub(current.FailTime)
if !force && failAmt > current.FailAmt &&
failInterval < m.minFailureRelaxInterval {
log.Debugf("Ignoring higher amount failure within min "+
"failure relaxation interval: prev_fail_amt=%v, "+
"fail_amt=%v, interval=%v",
current.FailAmt, failAmt, failInterval)
return
}
current.FailTime = timestamp
current.FailAmt = failAmt
switch {
// The failure amount is set to zero when the failure is
// amount-independent, meaning that the attempt would have
// failed regardless of the amount. This should also reset the
// success amount to zero.
case failAmt == 0:
current.SuccessAmt = 0
// If the failure range goes into the success range, move the
// success range down.
case failAmt <= current.SuccessAmt:
current.SuccessAmt = failAmt - 1
}
}
log.Debugf("Setting %v->%v range to [%v-%v]",
fromNode, toNode, current.SuccessAmt, current.FailAmt)
nodePairs[toNode] = current
}
// setAllFail stores a fail result for all known connections to and from the
// given node.
func (m *missionControlState) setAllFail(node route.Vertex,
timestamp time.Time) {
for fromNode, nodePairs := range m.lastPairResult {
for toNode := range nodePairs {
if fromNode == node || toNode == node {
nodePairs[toNode] = TimedPairResult{
FailTime: timestamp,
}
}
}
}
}
// requestSecondChance checks whether the node fromNode can have a second chance
// at providing a channel update for its channel with toNode.
func (m *missionControlState) requestSecondChance(timestamp time.Time,
fromNode, toNode route.Vertex) bool {
// Look up previous second chance time.
pair := DirectedNodePair{
From: fromNode,
To: toNode,
}
lastSecondChance, ok := m.lastSecondChance[pair]
// If the channel hasn't already be given a second chance or its last
// second chance was long ago, we give it another chance.
if !ok || timestamp.Sub(lastSecondChance) > minSecondChanceInterval {
m.lastSecondChance[pair] = timestamp
log.Debugf("Second chance granted for %v->%v", fromNode, toNode)
return true
}
// Otherwise penalize the channel, because we don't allow channel
// updates that are that frequent. This is to prevent nodes from keeping
// us busy by continuously sending new channel updates.
log.Debugf("Second chance denied for %v->%v, remaining interval: %v",
fromNode, toNode, timestamp.Sub(lastSecondChance))
return false
}
// GetHistorySnapshot takes a snapshot from the current mission control state
// and actual probability estimates.
func (m *missionControlState) getSnapshot() *MissionControlSnapshot {
log.Debugf("Requesting history snapshot from mission control: "+
"pair_result_count=%v", len(m.lastPairResult))
pairs := make([]MissionControlPairSnapshot, 0, len(m.lastPairResult))
for fromNode, fromPairs := range m.lastPairResult {
for toNode, result := range fromPairs {
pair := NewDirectedNodePair(fromNode, toNode)
pairSnapshot := MissionControlPairSnapshot{
Pair: pair,
TimedPairResult: result,
}
pairs = append(pairs, pairSnapshot)
}
}
snapshot := MissionControlSnapshot{
Pairs: pairs,
}
return &snapshot
}
// importSnapshot takes an existing snapshot and merges it with our current
// state if the result provided are fresher than our current results. It returns
// the number of pairs that were used.
func (m *missionControlState) importSnapshot(snapshot *MissionControlSnapshot,
force bool) int {
var imported int
for _, pair := range snapshot.Pairs {
fromNode := pair.Pair.From
toNode := pair.Pair.To
results, found := m.getLastPairResult(fromNode)
if !found {
results = make(map[route.Vertex]TimedPairResult)
}
lastResult := results[toNode]
failResult := failPairResult(pair.FailAmt)
imported += m.importResult(
lastResult.FailTime, pair.FailTime, failResult,
fromNode, toNode, force,
)
successResult := successPairResult(pair.SuccessAmt)
imported += m.importResult(
lastResult.SuccessTime, pair.SuccessTime, successResult,
fromNode, toNode, force,
)
}
return imported
}
func (m *missionControlState) importResult(currentTs, importedTs time.Time,
importedResult pairResult, fromNode, toNode route.Vertex,
force bool) int {
if !force && currentTs.After(importedTs) {
log.Debugf("Not setting pair result for %v->%v (%v) "+
"success=%v, timestamp %v older than last result %v",
fromNode, toNode, importedResult.amt,
importedResult.success, importedTs, currentTs)
return 0
}
m.setLastPairResult(
fromNode, toNode, importedTs, &importedResult, force,
)
return 1
}
package routing
import (
"bytes"
"container/list"
"encoding/binary"
"fmt"
"io"
"sync"
"time"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tlv"
)
var (
// resultsKey is the fixed key under which the attempt results are
// stored.
resultsKey = []byte("missioncontrol-results")
// Big endian is the preferred byte order, due to cursor scans over
// integer keys iterating in order.
byteOrder = binary.BigEndian
)
// missionControlDB is an interface that defines the database methods that a
// single missionControlStore has access to. It allows the missionControlStore
// to be unaware of the overall DB structure and restricts its access to the DB
// by only providing it the bucket that it needs to care about.
type missionControlDB interface {
// update can be used to perform reads and writes on the given bucket.
update(f func(bkt kvdb.RwBucket) error, reset func()) error
// view can be used to perform reads on the given bucket.
view(f func(bkt kvdb.RBucket) error, reset func()) error
// purge will delete all the contents in this store.
purge() error
}
// missionControlStore is a bolt db based implementation of a mission control
// store. It stores the raw payment attempt data from which the internal mission
// controls state can be rederived on startup. This allows the mission control
// internal data structure to be changed without requiring a database migration.
// Also changes to mission control parameters can be applied to historical data.
// Finally, it enables importing raw data from an external source.
type missionControlStore struct {
done chan struct{}
wg sync.WaitGroup
db missionControlDB
// TODO(yy): Remove the usage of sync.Cond - we are better off using
// channes than a Cond as suggested in the official godoc.
//
// queueCond is signalled when items are put into the queue.
queueCond *sync.Cond
// queue stores all pending payment results not yet added to the store.
// Access is protected by the queueCond.L mutex.
queue *list.List
// keys holds the stored MC store item keys in the order of storage.
// We use this list when adding/deleting items from the database to
// avoid cursor use which may be slow in the remote DB case.
keys *list.List
// keysMap holds the stored MC store item keys. We use this map to check
// if a new payment result has already been stored.
keysMap map[string]struct{}
// maxRecords is the maximum amount of records we will store in the db.
maxRecords int
// flushInterval is the configured interval we use to store new results
// and delete outdated ones from the db.
flushInterval time.Duration
}
func newMissionControlStore(db missionControlDB, maxRecords int,
flushInterval time.Duration) (*missionControlStore, error) {
var (
keys *list.List
keysMap map[string]struct{}
)
// Create buckets if not yet existing.
err := db.update(func(resultsBucket kvdb.RwBucket) error {
// Collect all keys to be able to quickly calculate the
// difference when updating the DB state.
c := resultsBucket.ReadCursor()
for k, _ := c.First(); k != nil; k, _ = c.Next() {
keys.PushBack(string(k))
keysMap[string(k)] = struct{}{}
}
return nil
}, func() {
keys = list.New()
keysMap = make(map[string]struct{})
})
if err != nil {
return nil, err
}
log.Infof("Loaded %d mission control entries", len(keysMap))
return &missionControlStore{
done: make(chan struct{}),
db: db,
queueCond: sync.NewCond(&sync.Mutex{}),
queue: list.New(),
keys: keys,
keysMap: keysMap,
maxRecords: maxRecords,
flushInterval: flushInterval,
}, nil
}
// clear removes all results from the db.
func (b *missionControlStore) clear() error {
b.queueCond.L.Lock()
defer b.queueCond.L.Unlock()
if err := b.db.purge(); err != nil {
return err
}
b.queue = list.New()
return nil
}
// fetchAll returns all results currently stored in the database.
func (b *missionControlStore) fetchAll() ([]*paymentResult, error) {
var results []*paymentResult
err := b.db.view(func(resultBucket kvdb.RBucket) error {
results = make([]*paymentResult, 0)
return resultBucket.ForEach(func(k, v []byte) error {
result, err := deserializeResult(k, v)
if err != nil {
return err
}
results = append(results, result)
return nil
})
}, func() {
results = nil
})
if err != nil {
return nil, err
}
return results, nil
}
// serializeResult serializes a payment result and returns a key and value byte
// slice to insert into the bucket.
func serializeResult(rp *paymentResult) ([]byte, []byte, error) {
recordProducers := []tlv.RecordProducer{
&rp.timeFwd,
&rp.timeReply,
&rp.route,
}
rp.failure.WhenSome(
func(failure tlv.RecordT[tlv.TlvType3, paymentFailure]) {
recordProducers = append(recordProducers, &failure)
},
)
// Compose key that identifies this result.
key := getResultKey(rp)
var buff bytes.Buffer
err := lnwire.EncodeRecordsTo(
&buff, lnwire.ProduceRecordsSorted(recordProducers...),
)
if err != nil {
return nil, nil, err
}
return key, buff.Bytes(), nil
}
// deserializeResult deserializes a payment result.
func deserializeResult(k, v []byte) (*paymentResult, error) {
// Parse payment id.
result := paymentResult{
id: byteOrder.Uint64(k[8:]),
}
failure := tlv.ZeroRecordT[tlv.TlvType3, paymentFailure]()
recordProducers := []tlv.RecordProducer{
&result.timeFwd,
&result.timeReply,
&result.route,
&failure,
}
r := bytes.NewReader(v)
typeMap, err := lnwire.DecodeRecords(
r, lnwire.ProduceRecordsSorted(recordProducers...)...,
)
if err != nil {
return nil, err
}
if _, ok := typeMap[result.failure.TlvType()]; ok {
result.failure = tlv.SomeRecordT(failure)
}
return &result, nil
}
// serializeRoute serializes a mcRoute and writes the resulting bytes to the
// given io.Writer.
func serializeRoute(w io.Writer, r *mcRoute) error {
records := lnwire.ProduceRecordsSorted(
&r.sourcePubKey,
&r.totalAmount,
&r.hops,
)
return lnwire.EncodeRecordsTo(w, records)
}
// deserializeRoute deserializes the mcRoute from the given io.Reader.
func deserializeRoute(r io.Reader) (*mcRoute, error) {
var rt mcRoute
records := lnwire.ProduceRecordsSorted(
&rt.sourcePubKey,
&rt.totalAmount,
&rt.hops,
)
_, err := lnwire.DecodeRecords(r, records...)
if err != nil {
return nil, err
}
return &rt, nil
}
// deserializeHop deserializes the mcHop from the given io.Reader.
func deserializeHop(r io.Reader) (*mcHop, error) {
var (
h mcHop
blinding = tlv.ZeroRecordT[tlv.TlvType3, lnwire.TrueBoolean]()
custom = tlv.ZeroRecordT[tlv.TlvType4, lnwire.TrueBoolean]()
)
records := lnwire.ProduceRecordsSorted(
&h.channelID,
&h.pubKeyBytes,
&h.amtToFwd,
&blinding,
&custom,
)
typeMap, err := lnwire.DecodeRecords(r, records...)
if err != nil {
return nil, err
}
if _, ok := typeMap[h.hasBlindingPoint.TlvType()]; ok {
h.hasBlindingPoint = tlv.SomeRecordT(blinding)
}
if _, ok := typeMap[h.hasCustomRecords.TlvType()]; ok {
h.hasCustomRecords = tlv.SomeRecordT(custom)
}
return &h, nil
}
// serializeHop serializes a mcHop and writes the resulting bytes to the given
// io.Writer.
func serializeHop(w io.Writer, h *mcHop) error {
recordProducers := []tlv.RecordProducer{
&h.channelID,
&h.pubKeyBytes,
&h.amtToFwd,
}
h.hasBlindingPoint.WhenSome(func(
hasBlinding tlv.RecordT[tlv.TlvType3, lnwire.TrueBoolean]) {
recordProducers = append(recordProducers, &hasBlinding)
})
h.hasCustomRecords.WhenSome(func(
hasCustom tlv.RecordT[tlv.TlvType4, lnwire.TrueBoolean]) {
recordProducers = append(recordProducers, &hasCustom)
})
return lnwire.EncodeRecordsTo(
w, lnwire.ProduceRecordsSorted(recordProducers...),
)
}
// AddResult adds a new result to the db.
func (b *missionControlStore) AddResult(rp *paymentResult) {
b.queueCond.L.Lock()
b.queue.PushBack(rp)
b.queueCond.L.Unlock()
b.queueCond.Signal()
}
// stop stops the store ticker goroutine.
func (b *missionControlStore) stop() {
close(b.done)
b.queueCond.Signal()
b.wg.Wait()
}
// run runs the MC store ticker goroutine.
func (b *missionControlStore) run() {
b.wg.Add(1)
go func() {
defer b.wg.Done()
timer := time.NewTimer(b.flushInterval)
// Immediately stop the timer. It will be started once new
// items are added to the store. As the doc for time.Timer
// states, every call to Stop() done on a timer that is not
// known to have been fired needs to be checked and the timer's
// channel needs to be drained appropriately. This could happen
// if the flushInterval is very small (e.g. 1 nanosecond).
if !timer.Stop() {
select {
case <-timer.C:
case <-b.done:
log.Debugf("Stopping mission control store")
}
}
for {
// Wait for the queue to not be empty.
b.queueCond.L.Lock()
for b.queue.Front() == nil {
// To make sure we can properly stop, we must
// read the `done` channel first before
// attempting to call `Wait()`. This is due to
// the fact when `Signal` is called before the
// `Wait` call, the `Wait` call will block
// indefinitely.
//
// TODO(yy): replace this with channels.
select {
case <-b.done:
b.queueCond.L.Unlock()
return
default:
}
b.queueCond.Wait()
}
b.queueCond.L.Unlock()
// Restart the timer.
timer.Reset(b.flushInterval)
select {
case <-timer.C:
if err := b.storeResults(); err != nil {
log.Errorf("Failed to update mission "+
"control store: %v", err)
}
case <-b.done:
// Release the timer's resources.
if !timer.Stop() {
select {
case <-timer.C:
case <-b.done:
log.Debugf("Mission control " +
"store stopped")
}
}
return
}
}
}()
}
// storeResults stores all accumulated results.
func (b *missionControlStore) storeResults() error {
// We copy a reference to the queue and clear the original queue to be
// able to release the lock.
b.queueCond.L.Lock()
l := b.queue
if l.Len() == 0 {
b.queueCond.L.Unlock()
return nil
}
b.queue = list.New()
b.queueCond.L.Unlock()
var (
newKeys map[string]struct{}
delKeys []string
storeCount int
pruneCount int
)
// Create a deduped list of new entries.
newKeys = make(map[string]struct{}, l.Len())
for e := l.Front(); e != nil; e = e.Next() {
pr, ok := e.Value.(*paymentResult)
if !ok {
return fmt.Errorf("wrong type %T (not *paymentResult)",
e.Value)
}
key := string(getResultKey(pr))
if _, ok := b.keysMap[key]; ok {
l.Remove(e)
continue
}
if _, ok := newKeys[key]; ok {
l.Remove(e)
continue
}
newKeys[key] = struct{}{}
}
// Create a list of entries to delete.
toDelete := b.keys.Len() + len(newKeys) - b.maxRecords
if b.maxRecords > 0 && toDelete > 0 {
delKeys = make([]string, 0, toDelete)
// Delete as many as needed from old keys.
for e := b.keys.Front(); len(delKeys) < toDelete && e != nil; {
key, ok := e.Value.(string)
if !ok {
return fmt.Errorf("wrong type %T (not string)",
e.Value)
}
delKeys = append(delKeys, key)
e = e.Next()
}
// If more deletions are needed, simply do not add from the
// list of new keys.
for e := l.Front(); len(delKeys) < toDelete && e != nil; {
toDelete--
pr, ok := e.Value.(*paymentResult)
if !ok {
return fmt.Errorf("wrong type %T (not "+
"*paymentResult )", e.Value)
}
key := string(getResultKey(pr))
delete(newKeys, key)
l.Remove(e)
e = l.Front()
}
}
err := b.db.update(func(bucket kvdb.RwBucket) error {
for e := l.Front(); e != nil; e = e.Next() {
pr, ok := e.Value.(*paymentResult)
if !ok {
return fmt.Errorf("wrong type %T (not "+
"*paymentResult)", e.Value)
}
// Serialize result into key and value byte slices.
k, v, err := serializeResult(pr)
if err != nil {
return err
}
// Put into results bucket.
if err := bucket.Put(k, v); err != nil {
return err
}
storeCount++
}
// Prune oldest entries.
for _, key := range delKeys {
if err := bucket.Delete([]byte(key)); err != nil {
return err
}
pruneCount++
}
return nil
}, func() {
storeCount, pruneCount = 0, 0
})
if err != nil {
return err
}
log.Debugf("Stored mission control results: %d added, %d deleted",
storeCount, pruneCount)
// DB Update was successful, update the in-memory cache.
for _, key := range delKeys {
delete(b.keysMap, key)
b.keys.Remove(b.keys.Front())
}
for e := l.Front(); e != nil; e = e.Next() {
pr, ok := e.Value.(*paymentResult)
if !ok {
return fmt.Errorf("wrong type %T (not *paymentResult)",
e.Value)
}
key := string(getResultKey(pr))
b.keys.PushBack(key)
}
return nil
}
// getResultKey returns a byte slice representing a unique key for this payment
// result.
func getResultKey(rp *paymentResult) []byte {
var keyBytes [8 + 8 + 33]byte
// Identify records by a combination of time, payment id and sender pub
// key. This allows importing mission control data from an external
// source without key collisions and keeps the records sorted
// chronologically.
byteOrder.PutUint64(keyBytes[:], rp.timeReply.Val)
byteOrder.PutUint64(keyBytes[8:], rp.id)
copy(keyBytes[16:], rp.route.Val.sourcePubKey.Val[:])
return keyBytes[:]
}
// failureMessage wraps the lnwire.FailureMessage interface such that we can
// apply a Record method and use the failureMessage in a TLV encoded type.
type failureMessage struct {
lnwire.FailureMessage
}
// Record returns a TLV record that can be used to encode/decode a list of
// failureMessage to/from a TLV stream.
func (r *failureMessage) Record() tlv.Record {
recordSize := func() uint64 {
var (
b bytes.Buffer
buf [8]byte
)
if err := encodeFailureMessage(&b, r, &buf); err != nil {
panic(err)
}
return uint64(len(b.Bytes()))
}
return tlv.MakeDynamicRecord(
0, r, recordSize, encodeFailureMessage, decodeFailureMessage,
)
}
func encodeFailureMessage(w io.Writer, val interface{}, _ *[8]byte) error {
if v, ok := val.(*failureMessage); ok {
var b bytes.Buffer
err := lnwire.EncodeFailureMessage(&b, v.FailureMessage, 0)
if err != nil {
return err
}
_, err = w.Write(b.Bytes())
return err
}
return tlv.NewTypeForEncodingErr(val, "routing.failureMessage")
}
func decodeFailureMessage(r io.Reader, val interface{}, _ *[8]byte,
l uint64) error {
if v, ok := val.(*failureMessage); ok {
msg, err := lnwire.DecodeFailureMessage(r, 0)
if err != nil {
return err
}
*v = failureMessage{
FailureMessage: msg,
}
return nil
}
return tlv.NewTypeForDecodingErr(val, "routing.failureMessage", l, l)
}
package routing
import (
"fmt"
"github.com/lightningnetwork/lnd/routing/route"
)
// DirectedNodePair stores a directed pair of nodes.
type DirectedNodePair struct {
From, To route.Vertex
}
// NewDirectedNodePair instantiates a new DirectedNodePair struct.
func NewDirectedNodePair(from, to route.Vertex) DirectedNodePair {
return DirectedNodePair{
From: from,
To: to,
}
}
// String converts a node pair to its human readable representation.
func (d DirectedNodePair) String() string {
return fmt.Sprintf("%v -> %v", d.From, d.To)
}
// Reverse returns a reversed copy of the pair.
func (d DirectedNodePair) Reverse() DirectedNodePair {
return DirectedNodePair{From: d.To, To: d.From}
}
package routing
import (
"bytes"
"container/heap"
"errors"
"fmt"
"math"
"sort"
"time"
"github.com/btcsuite/btcd/btcutil"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/feature"
"github.com/lightningnetwork/lnd/fn/v2"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/routing/route"
)
const (
// infinity is used as a starting distance in our shortest path search.
infinity = math.MaxInt64
// RiskFactorBillionths controls the influence of time lock delta
// of a channel on route selection. It is expressed as billionths
// of msat per msat sent through the channel per time lock delta
// block. See edgeWeight function below for more details.
// The chosen value is based on the previous incorrect weight function
// 1 + timelock + fee * fee. In this function, the fee penalty
// diminishes the time lock penalty for all but the smallest amounts.
// To not change the behaviour of path finding too drastically, a
// relatively small value is chosen which is still big enough to give
// some effect with smaller time lock values. The value may need
// tweaking and/or be made configurable in the future.
RiskFactorBillionths = 15
// estimatedNodeCount is used to preallocate the path finding structures
// to avoid resizing and copies. It should be number on the same order as
// the number of active nodes in the network.
estimatedNodeCount = 10000
// fakeHopHintCapacity is the capacity we assume for hop hint channels.
// This is a high number, which expresses that a hop hint channel should
// be able to route payments.
fakeHopHintCapacity = btcutil.Amount(10 * btcutil.SatoshiPerBitcoin)
)
// pathFinder defines the interface of a path finding algorithm.
type pathFinder = func(g *graphParams, r *RestrictParams,
cfg *PathFindingConfig, self, source, target route.Vertex,
amt lnwire.MilliSatoshi, timePref float64, finalHtlcExpiry int32) (
[]*unifiedEdge, float64, error)
var (
// DefaultEstimator is the default estimator used for computing
// probabilities in pathfinding.
DefaultEstimator = AprioriEstimatorName
// DefaultAttemptCost is the default fixed virtual cost in path finding
// of a failed payment attempt. It is used to trade off potentially
// better routes against their probability of succeeding.
DefaultAttemptCost = lnwire.NewMSatFromSatoshis(100)
// DefaultAttemptCostPPM is the default proportional virtual cost in
// path finding weight units of executing a payment attempt that fails.
// It is used to trade off potentially better routes against their
// probability of succeeding. This parameter is expressed in parts per
// million of the payment amount.
//
// It is impossible to pick a perfect default value. The current value
// of 0.1% is based on the idea that a transaction fee of 1% is within
// reasonable territory and that a payment shouldn't need more than 10
// attempts.
DefaultAttemptCostPPM = int64(1000)
// DefaultMinRouteProbability is the default minimum probability for routes
// returned from findPath.
DefaultMinRouteProbability = float64(0.01)
// DefaultAprioriHopProbability is the default a priori probability for
// a hop.
DefaultAprioriHopProbability = float64(0.6)
)
// edgePolicyWithSource is a helper struct to keep track of the source node
// of a channel edge. ChannelEdgePolicy only contains to destination node
// of the edge.
type edgePolicyWithSource struct {
sourceNode route.Vertex
edge AdditionalEdge
}
// finalHopParams encapsulates various parameters for route construction that
// apply to the final hop in a route. These features include basic payment data
// such as amounts and cltvs, as well as more complex features like destination
// custom records and payment address.
type finalHopParams struct {
amt lnwire.MilliSatoshi
totalAmt lnwire.MilliSatoshi
// cltvDelta is the final hop's minimum CLTV expiry delta.
//
// NOTE that in the case of paying to a blinded path, this value will
// be set to a duplicate of the blinded path's accumulated CLTV value.
// We would then only need to use this value in the case where the
// introduction node of the path is also the destination node.
cltvDelta uint16
records record.CustomSet
paymentAddr fn.Option[[32]byte]
// metadata is additional data that is sent along with the payment to
// the payee.
metadata []byte
}
// newRoute constructs a route using the provided path and final hop constraints.
// Any destination specific fields from the final hop params will be attached
// assuming the destination's feature vector signals support, otherwise this
// method will fail. If the route is too long, or the selected path cannot
// support the fully payment including fees, then a non-nil error is returned.
// If the route is to a blinded path, the blindedPath parameter is used to
// back fill additional fields that are required for a blinded payment. This is
// done in a separate pass to keep our route construction simple, as blinded
// paths require zero expiry and amount values for intermediate hops (which
// makes calculating the totals during route construction difficult if we
// include blinded paths on the first pass).
//
// NOTE: The passed slice of unified edges MUST be sorted in forward order: from
// the source to the target node of the path finding attempt. It is assumed that
// any feature vectors on all hops have been validated for transitive
// dependencies.
// NOTE: If a non-nil blinded path is provided it is assumed to have been
// validated by the caller.
func newRoute(sourceVertex route.Vertex,
pathEdges []*unifiedEdge, currentHeight uint32, finalHop finalHopParams,
blindedPathSet *BlindedPaymentPathSet) (*route.Route, error) {
var (
hops []*route.Hop
// totalTimeLock will accumulate the cumulative time lock
// across the entire route. This value represents how long the
// sender will need to wait in the *worst* case.
totalTimeLock = currentHeight
// nextIncomingAmount is the amount that will need to flow into
// the *next* hop. Since we're going to be walking the route
// backwards below, this next hop gets closer and closer to the
// sender of the payment.
nextIncomingAmount lnwire.MilliSatoshi
blindedPayment *BlindedPayment
)
pathLength := len(pathEdges)
// When paying to a blinded route we might have appended a dummy hop at
// the end to make MPP payments possible via all paths of the blinded
// route set. We always append a dummy hop when the internal pathfiner
// looks for a route to a blinded path which is at least one hop long
// (excluding the introduction point). We add this dummy hop so that
// we search for a universal target but also respect potential mc
// entries which might already be present for a particular blinded path.
// However when constructing the Sphinx packet we need to remove this
// dummy hop again which we do here.
//
// NOTE: The path length is always at least 1 because there must be one
// edge from the source to the destination. However we check for > 0
// just for robustness here.
if blindedPathSet != nil && pathLength > 0 {
finalBlindedPubKey := pathEdges[pathLength-1].policy.
ToNodePubKey()
if IsBlindedRouteNUMSTargetKey(finalBlindedPubKey[:]) {
// If the last hop is the NUMS key for blinded paths, we
// remove the dummy hop from the route.
pathEdges = pathEdges[:pathLength-1]
pathLength--
}
}
for i := pathLength - 1; i >= 0; i-- {
// Now we'll start to calculate the items within the per-hop
// payload for the hop this edge is leading to.
edge := pathEdges[i].policy
// If this is an edge from a blinded path and the
// blindedPayment variable has not been set yet, then set it now
// by extracting the corresponding blinded payment from the
// edge.
isBlindedEdge := pathEdges[i].blindedPayment != nil
if isBlindedEdge && blindedPayment == nil {
blindedPayment = pathEdges[i].blindedPayment
}
// We'll calculate the amounts, timelocks, and fees for each hop
// in the route. The base case is the final hop which includes
// their amount and timelocks. These values will accumulate
// contributions from the preceding hops back to the sender as
// we compute the route in reverse.
var (
amtToForward lnwire.MilliSatoshi
fee int64
totalAmtMsatBlinded lnwire.MilliSatoshi
outgoingTimeLock uint32
customRecords record.CustomSet
mpp *record.MPP
metadata []byte
)
// Define a helper function that checks this edge's feature
// vector for support for a given feature. We assume at this
// point that the feature vectors transitive dependencies have
// been validated.
supports := func(feature lnwire.FeatureBit) bool {
// If this edge comes from router hints, the features
// could be nil.
if edge.ToNodeFeatures == nil {
return false
}
return edge.ToNodeFeatures.HasFeature(feature)
}
if i == len(pathEdges)-1 {
// If this is the last hop, then the hop payload will
// contain the exact amount. In BOLT #4: Onion Routing
// Protocol / "Payload for the Last Node", this is
// detailed.
amtToForward = finalHop.amt
// Fee is not part of the hop payload, but only used for
// reporting through RPC. Set to zero for the final hop.
fee = 0
if blindedPathSet == nil {
totalTimeLock += uint32(finalHop.cltvDelta)
} else {
totalTimeLock += uint32(
blindedPathSet.FinalCLTVDelta(),
)
}
outgoingTimeLock = totalTimeLock
// Attach any custom records to the final hop.
customRecords = finalHop.records
// If we're attaching a payment addr but the receiver
// doesn't support both TLV and payment addrs, fail.
payAddr := supports(lnwire.PaymentAddrOptional)
if !payAddr && finalHop.paymentAddr.IsSome() {
return nil, errors.New("cannot attach " +
"payment addr")
}
// Otherwise attach the mpp record if it exists.
// TODO(halseth): move this to payment life cycle,
// where AMP options are set.
finalHop.paymentAddr.WhenSome(func(addr [32]byte) {
mpp = record.NewMPP(finalHop.totalAmt, addr)
})
metadata = finalHop.metadata
if blindedPathSet != nil {
totalAmtMsatBlinded = finalHop.totalAmt
}
} else {
// The amount that the current hop needs to forward is
// equal to the incoming amount of the next hop.
amtToForward = nextIncomingAmount
// The fee that needs to be paid to the current hop is
// based on the amount that this hop needs to forward
// and its policy for the outgoing channel. This policy
// is stored as part of the incoming channel of
// the next hop.
outboundFee := pathEdges[i+1].policy.ComputeFee(
amtToForward,
)
inboundFee := pathEdges[i].inboundFees.CalcFee(
amtToForward + outboundFee,
)
fee = int64(outboundFee) + inboundFee
if fee < 0 {
fee = 0
}
// We'll take the total timelock of the preceding hop as
// the outgoing timelock or this hop. Then we'll
// increment the total timelock incurred by this hop.
outgoingTimeLock = totalTimeLock
totalTimeLock += uint32(
pathEdges[i+1].policy.TimeLockDelta,
)
}
// Since we're traversing the path backwards atm, we prepend
// each new hop such that, the final slice of hops will be in
// the forwards order.
currentHop := &route.Hop{
PubKeyBytes: edge.ToNodePubKey(),
ChannelID: edge.ChannelID,
AmtToForward: amtToForward,
OutgoingTimeLock: outgoingTimeLock,
CustomRecords: customRecords,
MPP: mpp,
Metadata: metadata,
TotalAmtMsat: totalAmtMsatBlinded,
}
hops = append([]*route.Hop{currentHop}, hops...)
// Finally, we update the amount that needs to flow into the
// *next* hop, which is the amount this hop needs to forward,
// accounting for the fee that it takes.
nextIncomingAmount = amtToForward + lnwire.MilliSatoshi(fee)
}
// If we are creating a route to a blinded path, we need to add some
// additional data to the route that is required for blinded forwarding.
// We do another pass on our edges to append this data.
if blindedPathSet != nil {
// If the passed in BlindedPaymentPathSet is non-nil but no
// edge had a BlindedPayment attached, it means that the path
// chosen was an introduction-node-only path. So in this case,
// we can assume the relevant payment is the only one in the
// payment set.
if blindedPayment == nil {
var err error
blindedPayment, err = blindedPathSet.IntroNodeOnlyPath()
if err != nil {
return nil, err
}
}
var (
inBlindedRoute bool
dataIndex = 0
blindedPath = blindedPayment.BlindedPath
introVertex = route.NewVertex(
blindedPath.IntroductionPoint,
)
)
for i, hop := range hops {
// Once we locate our introduction node, we know that
// every hop after this is part of the blinded route.
if bytes.Equal(hop.PubKeyBytes[:], introVertex[:]) {
inBlindedRoute = true
hop.BlindingPoint = blindedPath.BlindingPoint
}
// We don't need to modify edges outside of our blinded
// route.
if !inBlindedRoute {
continue
}
payload := blindedPath.BlindedHops[dataIndex].CipherText
hop.EncryptedData = payload
// All of the hops in a blinded route *except* the
// final hop should have zero amounts / time locks.
if i != len(hops)-1 {
hop.AmtToForward = 0
hop.OutgoingTimeLock = 0
}
dataIndex++
}
}
// With the base routing data expressed as hops, build the full route
newRoute, err := route.NewRouteFromHops(
nextIncomingAmount, totalTimeLock, route.Vertex(sourceVertex),
hops,
)
if err != nil {
return nil, err
}
return newRoute, nil
}
// edgeWeight computes the weight of an edge. This value is used when searching
// for the shortest path within the channel graph between two nodes. Weight is
// is the fee itself plus a time lock penalty added to it. This benefits
// channels with shorter time lock deltas and shorter (hops) routes in general.
// RiskFactor controls the influence of time lock on route selection. This is
// currently a fixed value, but might be configurable in the future.
func edgeWeight(lockedAmt lnwire.MilliSatoshi, fee lnwire.MilliSatoshi,
timeLockDelta uint16) int64 {
// timeLockPenalty is the penalty for the time lock delta of this channel.
// It is controlled by RiskFactorBillionths and scales proportional
// to the amount that will pass through channel. Rationale is that it if
// a twice as large amount gets locked up, it is twice as bad.
timeLockPenalty := int64(lockedAmt) * int64(timeLockDelta) *
RiskFactorBillionths / 1000000000
return int64(fee) + timeLockPenalty
}
// graphParams wraps the set of graph parameters passed to findPath.
type graphParams struct {
// graph is the ChannelGraph to be used during path finding.
graph Graph
// additionalEdges is an optional set of edges that should be
// considered during path finding, that is not already found in the
// channel graph. These can either be private edges for bolt 11 invoices
// or blinded edges when a payment to a blinded path is made.
additionalEdges map[route.Vertex][]AdditionalEdge
// bandwidthHints is an interface that provides bandwidth hints that
// can provide a better estimate of the current channel bandwidth than
// what is found in the graph. It will override the capacities and
// disabled flags found in the graph for local channels when doing
// path finding if it has updated values for that channel. In
// particular, it should be set to the current available sending
// bandwidth for active local channels, and 0 for inactive channels.
bandwidthHints bandwidthHints
}
// RestrictParams wraps the set of restrictions passed to findPath that the
// found path must adhere to.
type RestrictParams struct {
// ProbabilitySource is a callback that is expected to return the
// success probability of traversing the channel from the node.
ProbabilitySource func(route.Vertex, route.Vertex,
lnwire.MilliSatoshi, btcutil.Amount) float64
// FeeLimit is a maximum fee amount allowed to be used on the path from
// the source to the target.
FeeLimit lnwire.MilliSatoshi
// OutgoingChannelIDs is the list of channels that are allowed for the
// first hop. If nil, any channel may be used.
OutgoingChannelIDs []uint64
// LastHop is the pubkey of the last node before the final destination
// is reached. If nil, any node may be used.
LastHop *route.Vertex
// CltvLimit is the maximum time lock of the route excluding the final
// ctlv. After path finding is complete, the caller needs to increase
// all cltv expiry heights with the required final cltv delta.
CltvLimit uint32
// DestCustomRecords contains the custom records to drop off at the
// final hop, if any.
DestCustomRecords record.CustomSet
// DestFeatures is a feature vector describing what the final hop
// supports. If none are provided, pathfinding will try to inspect any
// features on the node announcement instead.
DestFeatures *lnwire.FeatureVector
// PaymentAddr is a random 32-byte value generated by the receiver to
// mitigate probing vectors and payment sniping attacks on overpaid
// invoices.
PaymentAddr fn.Option[[32]byte]
// Amp signals to the pathfinder that this payment is an AMP payment
// and therefore it needs to account for additional AMP data in the
// final hop payload size calculation.
Amp *AMPOptions
// Metadata is additional data that is sent along with the payment to
// the payee.
Metadata []byte
// BlindedPaymentPathSet is necessary to determine the hop size of the
// last/exit hop.
BlindedPaymentPathSet *BlindedPaymentPathSet
// FirstHopCustomRecords includes any records that should be included in
// the update_add_htlc message towards our peer.
FirstHopCustomRecords lnwire.CustomRecords
}
// PathFindingConfig defines global parameters that control the trade-off in
// path finding between fees and probability.
type PathFindingConfig struct {
// AttemptCost is the fixed virtual cost in path finding of a failed
// payment attempt. It is used to trade off potentially better routes
// against their probability of succeeding.
AttemptCost lnwire.MilliSatoshi
// AttemptCostPPM is the proportional virtual cost in path finding of a
// failed payment attempt. It is used to trade off potentially better
// routes against their probability of succeeding. This parameter is
// expressed in parts per million of the total payment amount.
AttemptCostPPM int64
// MinProbability defines the minimum success probability of the
// returned route.
MinProbability float64
}
// getOutgoingBalance returns the maximum available balance in any of the
// channels of the given node. The second return parameters is the total
// available balance.
func getOutgoingBalance(node route.Vertex, outgoingChans map[uint64]struct{},
bandwidthHints bandwidthHints,
g Graph) (lnwire.MilliSatoshi, lnwire.MilliSatoshi, error) {
var max, total lnwire.MilliSatoshi
cb := func(channel *graphdb.DirectedChannel) error {
if !channel.OutPolicySet {
return nil
}
chanID := channel.ChannelID
// Enforce outgoing channel restriction.
if outgoingChans != nil {
if _, ok := outgoingChans[chanID]; !ok {
return nil
}
}
bandwidth, ok := bandwidthHints.availableChanBandwidth(
chanID, 0,
)
// If the bandwidth is not available, use the channel capacity.
// This can happen when a channel is added to the graph after
// we've already queried the bandwidth hints.
if !ok {
bandwidth = lnwire.NewMSatFromSatoshis(channel.Capacity)
}
if bandwidth > max {
max = bandwidth
}
var overflow bool
total, overflow = overflowSafeAdd(total, bandwidth)
if overflow {
// If the current total and the bandwidth would
// overflow the maximum value, we set the total to the
// maximum value. Which is more milli-satoshis than are
// in existence anyway, so the actual value is
// irrelevant.
total = lnwire.MilliSatoshi(math.MaxUint64)
}
return nil
}
// Iterate over all channels of the to node.
err := g.ForEachNodeDirectedChannel(node, cb)
if err != nil {
return 0, 0, err
}
return max, total, err
}
// findPath attempts to find a path from the source node within the ChannelGraph
// to the target node that's capable of supporting a payment of `amt` value. The
// current approach implemented is modified version of Dijkstra's algorithm to
// find a single shortest path between the source node and the destination. The
// distance metric used for edges is related to the time-lock+fee costs along a
// particular edge. If a path is found, this function returns a slice of
// ChannelHop structs which encoded the chosen path from the target to the
// source. The search is performed backwards from destination node back to
// source. This is to properly accumulate fees that need to be paid along the
// path and accurately check the amount to forward at every node against the
// available bandwidth.
func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
self, source, target route.Vertex, amt lnwire.MilliSatoshi,
timePref float64, finalHtlcExpiry int32) ([]*unifiedEdge, float64,
error) {
// Pathfinding can be a significant portion of the total payment
// latency, especially on low-powered devices. Log several metrics to
// aid in the analysis performance problems in this area.
start := time.Now()
nodesVisited := 0
edgesExpanded := 0
defer func() {
timeElapsed := time.Since(start)
log.Debugf("Pathfinding perf metrics: nodes=%v, edges=%v, "+
"time=%v", nodesVisited, edgesExpanded, timeElapsed)
}()
// If no destination features are provided, we will load what features
// we have for the target node from our graph.
features := r.DestFeatures
if features == nil {
var err error
features, err = g.graph.FetchNodeFeatures(target)
if err != nil {
return nil, 0, err
}
}
// Ensure that the destination's features don't include unknown
// required features.
err := feature.ValidateRequired(features)
if err != nil {
log.Warnf("Pathfinding destination node features: %v", err)
return nil, 0, errUnknownRequiredFeature
}
// Ensure that all transitive dependencies are set.
err = feature.ValidateDeps(features)
if err != nil {
log.Warnf("Pathfinding destination node features: %v", err)
return nil, 0, errMissingDependentFeature
}
// Now that we know the feature vector is well-formed, we'll proceed in
// checking that it supports the features we need. If the caller has a
// payment address to attach, check that our destination feature vector
// supports them.
if r.PaymentAddr.IsSome() &&
!features.HasFeature(lnwire.PaymentAddrOptional) {
return nil, 0, errNoPaymentAddr
}
// Set up outgoing channel map for quicker access.
var outgoingChanMap map[uint64]struct{}
if len(r.OutgoingChannelIDs) > 0 {
outgoingChanMap = make(map[uint64]struct{})
for _, outChan := range r.OutgoingChannelIDs {
outgoingChanMap[outChan] = struct{}{}
}
}
// If we are routing from ourselves, check that we have enough local
// balance available.
if source == self {
max, total, err := getOutgoingBalance(
self, outgoingChanMap, g.bandwidthHints, g.graph,
)
if err != nil {
return nil, 0, err
}
// If the total outgoing balance isn't sufficient, it will be
// impossible to complete the payment.
if total < amt {
log.Warnf("Not enough outbound balance to send "+
"htlc of amount: %v, only have local "+
"balance: %v", amt, total)
return nil, 0, errInsufficientBalance
}
// If there is only not enough capacity on a single route, it
// may still be possible to complete the payment by splitting.
if max < amt {
return nil, 0, errNoPathFound
}
}
// First we'll initialize an empty heap which'll help us to quickly
// locate the next edge we should visit next during our graph
// traversal.
nodeHeap := newDistanceHeap(estimatedNodeCount)
// Holds the current best distance for a given node.
distance := make(map[route.Vertex]*nodeWithDist, estimatedNodeCount)
additionalEdgesWithSrc := make(map[route.Vertex][]*edgePolicyWithSource)
for vertex, additionalEdges := range g.additionalEdges {
// Edges connected to self are always included in the graph,
// therefore can be skipped. This prevents us from trying
// routes to malformed hop hints.
if vertex == self {
continue
}
// Build reverse lookup to find incoming edges. Needed because
// search is taken place from target to source.
for _, additionalEdge := range additionalEdges {
outgoingEdgePolicy := additionalEdge.EdgePolicy()
toVertex := outgoingEdgePolicy.ToNodePubKey()
incomingEdgePolicy := &edgePolicyWithSource{
sourceNode: vertex,
edge: additionalEdge,
}
additionalEdgesWithSrc[toVertex] =
append(additionalEdgesWithSrc[toVertex],
incomingEdgePolicy)
}
}
// The payload size of the final hop differ from intermediate hops
// and depends on whether the destination is blinded or not.
lastHopPayloadSize, err := lastHopPayloadSize(r, finalHtlcExpiry, amt)
if err != nil {
return nil, 0, err
}
// We can't always assume that the end destination is publicly
// advertised to the network so we'll manually include the target node.
// The target node charges no fee. Distance is set to 0, because this is
// the starting point of the graph traversal. We are searching backwards
// to get the fees first time right and correctly match channel
// bandwidth.
//
// Don't record the initial partial path in the distance map and reserve
// that key for the source key in the case we route to ourselves.
partialPath := &nodeWithDist{
dist: 0,
weight: 0,
node: target,
netAmountReceived: amt,
incomingCltv: finalHtlcExpiry,
probability: 1,
routingInfoSize: lastHopPayloadSize,
}
// Calculate the absolute cltv limit. Use uint64 to prevent an overflow
// if the cltv limit is MaxUint32.
absoluteCltvLimit := uint64(r.CltvLimit) + uint64(finalHtlcExpiry)
// Calculate the default attempt cost as configured globally.
defaultAttemptCost := float64(
cfg.AttemptCost +
amt*lnwire.MilliSatoshi(cfg.AttemptCostPPM)/1000000,
)
// Validate time preference value.
if math.Abs(timePref) > 1 {
return nil, 0, fmt.Errorf("time preference %v out of range "+
"[-1, 1]", timePref)
}
// Scale to avoid the extremes -1 and 1 which run into infinity issues.
timePref *= 0.9
// Apply time preference. At 0, the default attempt cost will
// be used.
absoluteAttemptCost := defaultAttemptCost * (1/(0.5-timePref/2) - 1)
log.Debugf("Pathfinding absolute attempt cost: %v sats",
absoluteAttemptCost/1000)
// processEdge is a helper closure that will be used to make sure edges
// satisfy our specific requirements.
processEdge := func(fromVertex route.Vertex,
edge *unifiedEdge, toNodeDist *nodeWithDist) {
edgesExpanded++
// Calculate inbound fee charged by "to" node. The exit hop
// doesn't charge inbound fees. If the "to" node is the exit
// hop, its inbound fees have already been set to zero by
// nodeEdgeUnifier.
inboundFee := edge.inboundFees.CalcFee(
toNodeDist.netAmountReceived,
)
// Make sure that the node total fee is never negative.
// Routing nodes treat a total fee that turns out
// negative as a zero fee and pathfinding should do the
// same.
minInboundFee := -int64(toNodeDist.outboundFee)
if inboundFee < minInboundFee {
inboundFee = minInboundFee
}
// Calculate amount that the candidate node would have to send
// out.
amountToSend := toNodeDist.netAmountReceived +
lnwire.MilliSatoshi(inboundFee)
// Check if accumulated fees would exceed fee limit when this
// node would be added to the path.
totalFee := int64(amountToSend) - int64(amt)
log.Trace(lnutils.NewLogClosure(func() string {
return fmt.Sprintf(
"Checking fromVertex (%v) with "+
"minInboundFee=%v, inboundFee=%v, "+
"amountToSend=%v, amt=%v, totalFee=%v",
fromVertex, minInboundFee, inboundFee,
amountToSend, amt, totalFee,
)
}))
if totalFee > 0 && lnwire.MilliSatoshi(totalFee) > r.FeeLimit {
return
}
// Request the success probability for this edge.
edgeProbability := r.ProbabilitySource(
fromVertex, toNodeDist.node, amountToSend,
edge.capacity,
)
log.Trace(lnutils.NewLogClosure(func() string {
return fmt.Sprintf("path finding probability: fromnode=%v,"+
" tonode=%v, amt=%v, cap=%v, probability=%v",
fromVertex, toNodeDist.node, amountToSend,
edge.capacity, edgeProbability)
}))
// If the probability is zero, there is no point in trying.
if edgeProbability == 0 {
return
}
// Compute fee that fromVertex is charging. It is based on the
// amount that needs to be sent to the next node in the route.
//
// Source node has no predecessor to pay a fee. Therefore set
// fee to zero, because it should not be included in the fee
// limit check and edge weight.
//
// Also determine the time lock delta that will be added to the
// route if fromVertex is selected. If fromVertex is the source
// node, no additional timelock is required.
var (
timeLockDelta uint16
outboundFee int64
)
if fromVertex != source {
outboundFee = int64(
edge.policy.ComputeFee(amountToSend),
)
timeLockDelta = edge.policy.TimeLockDelta
}
incomingCltv := toNodeDist.incomingCltv + int32(timeLockDelta)
// Check that we are within our CLTV limit.
if uint64(incomingCltv) > absoluteCltvLimit {
return
}
// netAmountToReceive is the amount that the node that is added
// to the distance map needs to receive from a (to be found)
// previous node in the route. The inbound fee of the receiving
// node is already subtracted from this value. The previous node
// will need to pay the amount that this node forwards plus the
// fee it charges plus this node's inbound fee.
netAmountToReceive := amountToSend +
lnwire.MilliSatoshi(outboundFee)
// Calculate total probability of successfully reaching target
// by multiplying the probabilities. Both this edge and the rest
// of the route must succeed.
probability := toNodeDist.probability * edgeProbability
// If the probability is below the specified lower bound, we can
// abandon this direction. Adding further nodes can only lower
// the probability more.
if probability < cfg.MinProbability {
return
}
// Calculate the combined fee for this edge. Dijkstra does not
// support negative edge weights. Because this fee feeds into
// the edge weight calculation, we don't allow it to be
// negative.
signedFee := inboundFee + outboundFee
fee := lnwire.MilliSatoshi(0)
if signedFee > 0 {
fee = lnwire.MilliSatoshi(signedFee)
}
// By adding fromVertex in the route, there will be an extra
// weight composed of the fee that this node will charge and
// the amount that will be locked for timeLockDelta blocks in
// the HTLC that is handed out to fromVertex.
weight := edgeWeight(amountToSend, fee, timeLockDelta)
// Compute the tentative weight to this new channel/edge
// which is the weight from our toNode to the target node
// plus the weight of this edge.
tempWeight := toNodeDist.weight + weight
// Add an extra factor to the weight to take into account the
// probability. Another reason why we rounded the fee up to zero
// is to prevent a highly negative fee from cancelling out the
// extra factor. We don't want an always-failing node to attract
// traffic using a highly negative fee and escape penalization.
tempDist := getProbabilityBasedDist(
tempWeight, probability,
absoluteAttemptCost,
)
// If there is already a best route stored, compare this
// candidate route with the best route so far.
current, ok := distance[fromVertex]
if ok {
// If this route is worse than what we already found,
// skip this route.
if tempDist > current.dist {
return
}
// If the route is equally good and the probability
// isn't better, skip this route. It is important to
// also return if both cost and probability are equal,
// because otherwise the algorithm could run into an
// endless loop.
probNotBetter := probability <= current.probability
if tempDist == current.dist && probNotBetter {
return
}
}
// Calculate the total routing info size if this hop were to be
// included. If we are coming from the source hop, the payload
// size is zero, because the original htlc isn't in the onion
// blob.
//
// NOTE: For blinded paths with the NUMS key as the last hop,
// the payload size accounts for this dummy hop which is of
// the same size as the real last hop. So we account for a
// bigger size than the route is however we accept this
// little inaccuracy here because we are over estimating by
// 1 hop.
var payloadSize uint64
if fromVertex != source {
// In case the unifiedEdge does not have a payload size
// function supplied we request a graceful shutdown
// because this should never happen.
if edge.hopPayloadSizeFn == nil {
log.Criticalf("No payload size function "+
"available for edge=%v unable to "+
"determine payload size: %v", edge,
ErrNoPayLoadSizeFunc)
return
}
payloadSize = edge.hopPayloadSizeFn(
amountToSend,
uint32(toNodeDist.incomingCltv),
edge.policy.ChannelID,
)
}
routingInfoSize := toNodeDist.routingInfoSize + payloadSize
// Skip paths that would exceed the maximum routing info size.
if routingInfoSize > sphinx.MaxPayloadSize {
return
}
// All conditions are met and this new tentative distance is
// better than the current best known distance to this node.
// The new better distance is recorded, and also our "next hop"
// map is populated with this edge.
withDist := &nodeWithDist{
dist: tempDist,
weight: tempWeight,
node: fromVertex,
netAmountReceived: netAmountToReceive,
outboundFee: lnwire.MilliSatoshi(outboundFee),
incomingCltv: incomingCltv,
probability: probability,
nextHop: edge,
routingInfoSize: routingInfoSize,
}
distance[fromVertex] = withDist
// Either push withDist onto the heap if the node
// represented by fromVertex is not already on the heap OR adjust
// its position within the heap via heap.Fix.
nodeHeap.PushOrFix(withDist)
}
// TODO(roasbeef): also add path caching
// * similar to route caching, but doesn't factor in the amount
// Cache features because we visit nodes multiple times.
featureCache := make(map[route.Vertex]*lnwire.FeatureVector)
// getGraphFeatures returns (cached) node features from the graph.
getGraphFeatures := func(node route.Vertex) (*lnwire.FeatureVector,
error) {
// Check cache for features of the fromNode.
fromFeatures, ok := featureCache[node]
if ok {
return fromFeatures, nil
}
// Fetch node features fresh from the graph.
fromFeatures, err := g.graph.FetchNodeFeatures(node)
if err != nil {
return nil, err
}
// Don't route through nodes that contain unknown required
// features and mark as nil in the cache.
err = feature.ValidateRequired(fromFeatures)
if err != nil {
featureCache[node] = nil
return nil, nil
}
// Don't route through nodes that don't properly set all
// transitive feature dependencies and mark as nil in the cache.
err = feature.ValidateDeps(fromFeatures)
if err != nil {
featureCache[node] = nil
return nil, nil
}
// Update cache.
featureCache[node] = fromFeatures
return fromFeatures, nil
}
routeToSelf := source == target
for {
nodesVisited++
pivot := partialPath.node
isExitHop := partialPath.nextHop == nil
// Create unified policies for all incoming connections. Don't
// use inbound fees for the exit hop.
u := newNodeEdgeUnifier(
self, pivot, !isExitHop, outgoingChanMap,
)
err := u.addGraphPolicies(g.graph)
if err != nil {
return nil, 0, err
}
// We add hop hints that were supplied externally.
for _, reverseEdge := range additionalEdgesWithSrc[pivot] {
// Assume zero inbound fees for route hints. If inbound
// fees would apply, they couldn't be communicated in
// bolt11 invoices currently.
inboundFee := models.InboundFee{}
// Hop hints don't contain a capacity. We set one here,
// since a capacity is needed for probability
// calculations. We set a high capacity to act as if
// there is enough liquidity, otherwise the hint would
// not have been added by a wallet.
// We also pass the payload size function to the
// graph data so that we calculate the exact payload
// size when evaluating this hop for a route.
u.addPolicy(
reverseEdge.sourceNode,
reverseEdge.edge.EdgePolicy(),
inboundFee,
fakeHopHintCapacity,
reverseEdge.edge.IntermediatePayloadSize,
reverseEdge.edge.BlindedPayment(),
)
}
netAmountReceived := partialPath.netAmountReceived
// Expand all connections using the optimal policy for each
// connection.
for fromNode, edgeUnifier := range u.edgeUnifiers {
// The target node is not recorded in the distance map.
// Therefore we need to have this check to prevent
// creating a cycle. Only when we intend to route to
// self, we allow this cycle to form. In that case we'll
// also break out of the search loop below.
if !routeToSelf && fromNode == target {
continue
}
// Apply last hop restriction if set.
if r.LastHop != nil &&
pivot == target && fromNode != *r.LastHop {
continue
}
edge := edgeUnifier.getEdge(
netAmountReceived, g.bandwidthHints,
partialPath.outboundFee,
)
if edge == nil {
continue
}
// Get feature vector for fromNode.
fromFeatures, err := getGraphFeatures(fromNode)
if err != nil {
return nil, 0, err
}
// If there are no valid features, skip this node.
if fromFeatures == nil {
continue
}
// Check if this candidate node is better than what we
// already have.
processEdge(fromNode, edge, partialPath)
}
if nodeHeap.Len() == 0 {
break
}
// Fetch the node within the smallest distance from our source
// from the heap.
partialPath = heap.Pop(&nodeHeap).(*nodeWithDist)
// If we've reached our source (or we don't have any incoming
// edges), then we're done here and can exit the graph
// traversal early.
if partialPath.node == source {
break
}
}
// Use the distance map to unravel the forward path from source to
// target.
var pathEdges []*unifiedEdge
currentNode := source
for {
// Determine the next hop forward using the next map.
currentNodeWithDist, ok := distance[currentNode]
if !ok {
// If the node doesn't have a next hop it means we
// didn't find a path.
return nil, 0, errNoPathFound
}
// Add the next hop to the list of path edges.
pathEdges = append(pathEdges, currentNodeWithDist.nextHop)
// Advance current node.
currentNode = currentNodeWithDist.nextHop.policy.ToNodePubKey()
// Check stop condition at the end of this loop. This prevents
// breaking out too soon for self-payments that have target set
// to source.
if currentNode == target {
break
}
}
// For the final hop, we'll set the node features to those determined
// above. These are either taken from the destination features, e.g.
// virtual or invoice features, or loaded as a fallback from the graph.
// The transitive dependencies were already validated above, so no need
// to do so now.
//
// NOTE: This may overwrite features loaded from the graph if
// destination features were provided. This is fine though, since our
// route construction does not care where the features are actually
// taken from. In the future we may wish to do route construction within
// findPath, and avoid using ChannelEdgePolicy altogether.
pathEdges[len(pathEdges)-1].policy.ToNodeFeatures = features
log.Debugf("Found route: probability=%v, hops=%v, fee=%v",
distance[source].probability, len(pathEdges),
distance[source].netAmountReceived-amt)
return pathEdges, distance[source].probability, nil
}
// blindedPathRestrictions are a set of constraints to adhere to when
// choosing a set of blinded paths to this node.
type blindedPathRestrictions struct {
// minNumHops is the minimum number of hops to include in a blinded
// path. This doesn't include our node, so if the minimum is 1, then
// the path will contain at minimum our node along with an introduction
// node hop. A minimum of 0 will include paths where this node is the
// introduction node and so should be used with caution.
minNumHops uint8
// maxNumHops is the maximum number of hops to include in a blinded
// path. This doesn't include our node, so if the maximum is 1, then
// the path will contain our node along with an introduction node hop.
maxNumHops uint8
// nodeOmissionSet holds a set of node IDs of nodes that we should
// ignore during blinded path selection.
nodeOmissionSet fn.Set[route.Vertex]
}
// blindedHop holds the information about a hop we have selected for a blinded
// path.
type blindedHop struct {
vertex route.Vertex
channelID uint64
edgeCapacity btcutil.Amount
}
// findBlindedPaths does a depth first search from the target node to find a set
// of blinded paths to the target node given the set of restrictions. This
// function will select and return any candidate path. A candidate path is a
// path to the target node with a size determined by the given hop number
// constraints where all the nodes on the path signal the route blinding feature
// _and_ the introduction node for the path has more than one public channel.
// Any filtering of paths based on payment value or success probabilities is
// left to the caller.
func findBlindedPaths(g Graph, target route.Vertex,
restrictions *blindedPathRestrictions) ([][]blindedHop, error) {
// Sanity check the restrictions.
if restrictions.minNumHops > restrictions.maxNumHops {
return nil, fmt.Errorf("maximum number of blinded path hops "+
"(%d) must be greater than or equal to the minimum "+
"number of hops (%d)", restrictions.maxNumHops,
restrictions.minNumHops)
}
// If the node is not the destination node, then it is required that the
// node advertise the route blinding feature-bit in order for it to be
// chosen as a node on the blinded path.
supportsRouteBlinding := func(node route.Vertex) (bool, error) {
if node == target {
return true, nil
}
features, err := g.FetchNodeFeatures(node)
if err != nil {
return false, err
}
return features.HasFeature(lnwire.RouteBlindingOptional), nil
}
// This function will have some recursion. We will spin out from the
// target node & append edges to the paths until we reach various exit
// conditions such as: The maxHops number being reached or reaching
// a node that doesn't have any other edges - in that final case, the
// whole path should be ignored.
paths, _, err := processNodeForBlindedPath(
g, target, supportsRouteBlinding, nil, restrictions,
)
if err != nil {
return nil, err
}
// Reverse each path so that the order is correct (from introduction
// node to last hop node) and then append this node on as the
// destination of each path.
orderedPaths := make([][]blindedHop, len(paths))
for i, path := range paths {
sort.Slice(path, func(i, j int) bool {
return j < i
})
orderedPaths[i] = append(path, blindedHop{vertex: target})
}
// Handle the special case that allows a blinded path with the
// introduction node as the destination node.
if restrictions.minNumHops == 0 {
singleHopPath := [][]blindedHop{{{vertex: target}}}
//nolint:makezero
orderedPaths = append(
orderedPaths, singleHopPath...,
)
}
return orderedPaths, err
}
// processNodeForBlindedPath is a recursive function that traverses the graph
// in a depth first manner searching for a set of blinded paths to the given
// node.
func processNodeForBlindedPath(g Graph, node route.Vertex,
supportsRouteBlinding func(vertex route.Vertex) (bool, error),
alreadyVisited map[route.Vertex]bool,
restrictions *blindedPathRestrictions) ([][]blindedHop, bool, error) {
// If we have already visited the maximum number of hops, then this path
// is complete and we can exit now.
if len(alreadyVisited) > int(restrictions.maxNumHops) {
return nil, false, nil
}
// If we have already visited this peer on this path, then we skip
// processing it again.
if alreadyVisited[node] {
return nil, false, nil
}
// If we have explicitly been told to ignore this node for blinded paths
// then we skip it too.
if restrictions.nodeOmissionSet.Contains(node) {
return nil, false, nil
}
supports, err := supportsRouteBlinding(node)
if err != nil {
return nil, false, err
}
if !supports {
return nil, false, nil
}
// At this point, copy the alreadyVisited map.
visited := make(map[route.Vertex]bool, len(alreadyVisited))
for r := range alreadyVisited {
visited[r] = true
}
// Add this node the visited set.
visited[node] = true
var (
hopSets [][]blindedHop
chanCount int
)
// Now, iterate over the node's channels in search for paths to this
// node that can be used for blinded paths
err = g.ForEachNodeDirectedChannel(node,
func(channel *graphdb.DirectedChannel) error {
// Keep track of how many incoming channels this node
// has. We only use a node as an introduction node if it
// has channels other than the one that lead us to it.
chanCount++
// Process each channel peer to gather any paths that
// lead to the peer.
nextPaths, hasMoreChans, err := processNodeForBlindedPath( //nolint:ll
g, channel.OtherNode, supportsRouteBlinding,
visited, restrictions,
)
if err != nil {
return err
}
hop := blindedHop{
vertex: channel.OtherNode,
channelID: channel.ChannelID,
edgeCapacity: channel.Capacity,
}
// For each of the paths returned, unwrap them and
// append this hop to them.
for _, path := range nextPaths {
hopSets = append(
hopSets,
append([]blindedHop{hop}, path...),
)
}
// If this node does have channels other than the one
// that lead to it, and if the hop count up to this node
// meets the minHop requirement, then we also add a
// path that starts at this node.
if hasMoreChans &&
len(visited) >= int(restrictions.minNumHops) {
hopSets = append(hopSets, []blindedHop{hop})
}
return nil
},
)
if err != nil {
return nil, false, err
}
return hopSets, chanCount > 1, nil
}
// getProbabilityBasedDist converts a weight into a distance that takes into
// account the success probability and the (virtual) cost of a failed payment
// attempt.
//
// Derivation:
//
// Suppose there are two routes A and B with fees Fa and Fb and success
// probabilities Pa and Pb.
//
// Is the expected cost of trying route A first and then B lower than trying the
// other way around?
//
// The expected cost of A-then-B is: Pa*Fa + (1-Pa)*Pb*(c+Fb)
//
// The expected cost of B-then-A is: Pb*Fb + (1-Pb)*Pa*(c+Fa)
//
// In these equations, the term representing the case where both A and B fail is
// left out because its value would be the same in both cases.
//
// Pa*Fa + (1-Pa)*Pb*(c+Fb) < Pb*Fb + (1-Pb)*Pa*(c+Fa)
//
// Pa*Fa + Pb*c + Pb*Fb - Pa*Pb*c - Pa*Pb*Fb < Pb*Fb + Pa*c + Pa*Fa - Pa*Pb*c - Pa*Pb*Fa
//
// Removing terms that cancel out:
// Pb*c - Pa*Pb*Fb < Pa*c - Pa*Pb*Fa
//
// Divide by Pa*Pb:
// c/Pa - Fb < c/Pb - Fa
//
// Move terms around:
// Fa + c/Pa < Fb + c/Pb
//
// So the value of F + c/P can be used to compare routes.
func getProbabilityBasedDist(weight int64, probability float64,
penalty float64) int64 {
// Prevent divide by zero by returning early.
if probability == 0 {
return infinity
}
// Calculate distance.
dist := float64(weight) + penalty/probability
// Avoid cast if an overflow would occur. The maxFloat constant is
// chosen to stay well below the maximum float64 value that is still
// convertible to int64.
const maxFloat = 9000000000000000000
if dist > maxFloat {
return infinity
}
return int64(dist)
}
// lastHopPayloadSize calculates the payload size of the final hop in a route.
// It depends on the tlv types which are present and also whether the hop is
// part of a blinded route or not.
func lastHopPayloadSize(r *RestrictParams, finalHtlcExpiry int32,
amount lnwire.MilliSatoshi) (uint64, error) {
if r.BlindedPaymentPathSet != nil {
paymentPath, err := r.BlindedPaymentPathSet.
LargestLastHopPayloadPath()
if err != nil {
return 0, err
}
blindedPath := paymentPath.BlindedPath.BlindedHops
blindedPoint := paymentPath.BlindedPath.BlindingPoint
encryptedData := blindedPath[len(blindedPath)-1].CipherText
finalHop := route.Hop{
AmtToForward: amount,
OutgoingTimeLock: uint32(finalHtlcExpiry),
EncryptedData: encryptedData,
}
if len(blindedPath) == 1 {
finalHop.BlindingPoint = blindedPoint
}
// The final hop does not have a short chanID set.
return finalHop.PayloadSize(0), nil
}
var mpp *record.MPP
r.PaymentAddr.WhenSome(func(addr [32]byte) {
mpp = record.NewMPP(amount, addr)
})
var amp *record.AMP
if r.Amp != nil {
// The AMP payload is not easy accessible at this point but we
// are only interested in the size of the payload so we just use
// the AMP record dummy.
amp = &record.MaxAmpPayLoadSize
}
finalHop := route.Hop{
AmtToForward: amount,
OutgoingTimeLock: uint32(finalHtlcExpiry),
CustomRecords: r.DestCustomRecords,
MPP: mpp,
AMP: amp,
Metadata: r.Metadata,
}
// The final hop does not have a short chanID set.
return finalHop.PayloadSize(0), nil
}
// overflowSafeAdd adds two MilliSatoshi values and returns the result. If an
// overflow could occur, zero is returned instead and the boolean is set to
// true.
func overflowSafeAdd(x, y lnwire.MilliSatoshi) (lnwire.MilliSatoshi, bool) {
if y > math.MaxUint64-x {
// Overflow would occur, return 0 and set overflow flag.
return 0, true
}
return x + y, false
}
package routing
import (
"context"
"errors"
"fmt"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/davecgh/go-spew/spew"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/routing/shards"
"github.com/lightningnetwork/lnd/tlv"
)
// ErrPaymentLifecycleExiting is used when waiting for htlc attempt result, but
// the payment lifecycle is exiting .
var ErrPaymentLifecycleExiting = errors.New("payment lifecycle exiting")
// switchResult is the result sent back from the switch after processing the
// HTLC.
type switchResult struct {
// attempt is the HTLC sent to the switch.
attempt *channeldb.HTLCAttempt
// result is sent from the switch which contains either a preimage if
// ths HTLC is settled or an error if it's failed.
result *htlcswitch.PaymentResult
}
// paymentLifecycle holds all information about the current state of a payment
// needed to resume if from any point.
type paymentLifecycle struct {
router *ChannelRouter
feeLimit lnwire.MilliSatoshi
identifier lntypes.Hash
paySession PaymentSession
shardTracker shards.ShardTracker
currentHeight int32
firstHopCustomRecords lnwire.CustomRecords
// quit is closed to signal the sub goroutines of the payment lifecycle
// to stop.
quit chan struct{}
// resultCollected is used to send the result returned from the switch
// for a given HTLC attempt.
resultCollected chan *switchResult
// resultCollector is a function that is used to collect the result of
// an HTLC attempt, which is always mounted to `p.collectResultAsync`
// except in unit test, where we use a much simpler resultCollector to
// decouple the test flow for the payment lifecycle.
resultCollector func(attempt *channeldb.HTLCAttempt)
}
// newPaymentLifecycle initiates a new payment lifecycle and returns it.
func newPaymentLifecycle(r *ChannelRouter, feeLimit lnwire.MilliSatoshi,
identifier lntypes.Hash, paySession PaymentSession,
shardTracker shards.ShardTracker, currentHeight int32,
firstHopCustomRecords lnwire.CustomRecords) *paymentLifecycle {
p := &paymentLifecycle{
router: r,
feeLimit: feeLimit,
identifier: identifier,
paySession: paySession,
shardTracker: shardTracker,
currentHeight: currentHeight,
quit: make(chan struct{}),
resultCollected: make(chan *switchResult, 1),
firstHopCustomRecords: firstHopCustomRecords,
}
// Mount the result collector.
p.resultCollector = p.collectResultAsync
return p
}
// calcFeeBudget returns the available fee to be used for sending HTLC
// attempts.
func (p *paymentLifecycle) calcFeeBudget(
feesPaid lnwire.MilliSatoshi) lnwire.MilliSatoshi {
budget := p.feeLimit
// We'll subtract the used fee from our fee budget. In case of
// overflow, we need to check whether feesPaid exceeds our budget
// already.
if feesPaid <= budget {
budget -= feesPaid
} else {
budget = 0
}
return budget
}
// stateStep defines an action to be taken in our payment lifecycle. We either
// quit, continue, or exit the lifecycle, see details below.
type stateStep uint8
const (
// stepSkip is used when we need to skip the current lifecycle and jump
// to the next one.
stepSkip stateStep = iota
// stepProceed is used when we can proceed the current lifecycle.
stepProceed
// stepExit is used when we need to quit the current lifecycle.
stepExit
)
// decideNextStep is used to determine the next step in the payment lifecycle.
// It first checks whether the current state of the payment allows more HTLC
// attempts to be made. If allowed, it will return so the lifecycle can continue
// making new attempts. Otherwise, it checks whether we need to wait for the
// results of already sent attempts. If needed, it will block until one of the
// results is sent back. then process its result here. When there's no need to
// wait for results, the method will exit with `stepExit` such that the payment
// lifecycle loop will terminate.
func (p *paymentLifecycle) decideNextStep(
payment DBMPPayment) (stateStep, error) {
// Check whether we could make new HTLC attempts.
allow, err := payment.AllowMoreAttempts()
if err != nil {
return stepExit, err
}
// Exit early we need to make more attempts.
if allow {
return stepProceed, nil
}
// We cannot make more attempts, we now check whether we need to wait
// for results.
wait, err := payment.NeedWaitAttempts()
if err != nil {
return stepExit, err
}
// If we are not allowed to make new HTLC attempts and there's no need
// to wait, the lifecycle is done and we can exit.
if !wait {
return stepExit, nil
}
log.Tracef("Waiting for attempt results for payment %v", p.identifier)
// Otherwise we wait for the result for one HTLC attempt then continue
// the lifecycle.
select {
case r := <-p.resultCollected:
log.Tracef("Received attempt result for payment %v",
p.identifier)
// Handle the result here. If there's no error, we will return
// stepSkip and move to the next lifecycle iteration, which will
// refresh the payment and wait for the next attempt result, if
// any.
_, err := p.handleAttemptResult(r.attempt, r.result)
// We would only get a DB-related error here, which will cause
// us to abort the payment flow.
if err != nil {
return stepExit, err
}
case <-p.quit:
return stepExit, ErrPaymentLifecycleExiting
case <-p.router.quit:
return stepExit, ErrRouterShuttingDown
}
return stepSkip, nil
}
// resumePayment resumes the paymentLifecycle from the current state.
func (p *paymentLifecycle) resumePayment(ctx context.Context) ([32]byte,
*route.Route, error) {
// When the payment lifecycle loop exits, we make sure to signal any
// sub goroutine of the HTLC attempt to exit, then wait for them to
// return.
defer p.stop()
// If we had any existing attempts outstanding, we'll start by spinning
// up goroutines that'll collect their results and deliver them to the
// lifecycle loop below.
payment, err := p.reloadInflightAttempts()
if err != nil {
return [32]byte{}, nil, err
}
// Get the payment status.
status := payment.GetStatus()
// exitWithErr is a helper closure that logs and returns an error.
exitWithErr := func(err error) ([32]byte, *route.Route, error) {
// Log an error with the latest payment status.
//
// NOTE: this `status` variable is reassigned in the loop
// below. We could also call `payment.GetStatus` here, but in a
// rare case when the critical log is triggered when using
// postgres as db backend, the `payment` could be nil, causing
// the payment fetching to return an error.
log.Errorf("Payment %v with status=%v failed: %v", p.identifier,
status, err)
return [32]byte{}, nil, err
}
// We'll continue until either our payment succeeds, or we encounter a
// critical error during path finding.
lifecycle:
for {
// We update the payment state on every iteration.
currentPayment, ps, err := p.reloadPayment()
if err != nil {
return exitWithErr(err)
}
// Reassign status so it can be read in `exitWithErr`.
status = currentPayment.GetStatus()
// Reassign payment such that when the lifecycle exits, the
// latest payment can be read when we access its terminal info.
payment = currentPayment
// We now proceed our lifecycle with the following tasks in
// order,
// 1. check context.
// 2. request route.
// 3. create HTLC attempt.
// 4. send HTLC attempt.
// 5. collect HTLC attempt result.
//
// Before we attempt any new shard, we'll check to see if we've
// gone past the payment attempt timeout, or if the context was
// cancelled, or the router is exiting. In any of these cases,
// we'll stop this payment attempt short.
if err := p.checkContext(ctx); err != nil {
return exitWithErr(err)
}
// Now decide the next step of the current lifecycle.
step, err := p.decideNextStep(payment)
if err != nil {
return exitWithErr(err)
}
switch step {
// Exit the for loop and return below.
case stepExit:
break lifecycle
// Continue the for loop and skip the rest.
case stepSkip:
continue lifecycle
// Continue the for loop and proceed the rest.
case stepProceed:
// Unknown step received, exit with an error.
default:
err = fmt.Errorf("unknown step: %v", step)
return exitWithErr(err)
}
// Now request a route to be used to create our HTLC attempt.
rt, err := p.requestRoute(ps)
if err != nil {
return exitWithErr(err)
}
// We may not be able to find a route for current attempt. In
// that case, we continue the loop and move straight to the
// next iteration in case there are results for inflight HTLCs
// that still need to be collected.
if rt == nil {
log.Errorf("No route found for payment %v",
p.identifier)
continue lifecycle
}
log.Tracef("Found route: %s", spew.Sdump(rt.Hops))
// We found a route to try, create a new HTLC attempt to try.
attempt, err := p.registerAttempt(rt, ps.RemainingAmt)
if err != nil {
return exitWithErr(err)
}
// Once the attempt is created, send it to the htlcswitch.
result, err := p.sendAttempt(attempt)
if err != nil {
return exitWithErr(err)
}
// Now that the shard was successfully sent, launch a go
// routine that will handle its result when its back.
if result.err == nil {
p.resultCollector(attempt)
}
}
// Once we are out the lifecycle loop, it means we've reached a
// terminal condition. We either return the settled preimage or the
// payment's failure reason.
//
// Optionally delete the failed attempts from the database.
err = p.router.cfg.Control.DeleteFailedAttempts(p.identifier)
if err != nil {
log.Errorf("Error deleting failed htlc attempts for payment "+
"%v: %v", p.identifier, err)
}
htlc, failure := payment.TerminalInfo()
if htlc != nil {
return htlc.Settle.Preimage, &htlc.Route, nil
}
// Otherwise return the payment failure reason.
return [32]byte{}, nil, *failure
}
// checkContext checks whether the payment context has been canceled.
// Cancellation occurs manually or if the context times out.
func (p *paymentLifecycle) checkContext(ctx context.Context) error {
select {
case <-ctx.Done():
// If the context was canceled, we'll mark the payment as
// failed. There are two cases to distinguish here: Either a
// user-provided timeout was reached, or the context was
// canceled, either to a manual cancellation or due to an
// unknown error.
var reason channeldb.FailureReason
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
reason = channeldb.FailureReasonTimeout
log.Warnf("Payment attempt not completed before "+
"context timeout, id=%s", p.identifier.String())
} else {
reason = channeldb.FailureReasonCanceled
log.Warnf("Payment attempt context canceled, id=%s",
p.identifier.String())
}
// By marking the payment failed, depending on whether it has
// inflight HTLCs or not, its status will now either be
// `StatusInflight` or `StatusFailed`. In either case, no more
// HTLCs will be attempted.
err := p.router.cfg.Control.FailPayment(p.identifier, reason)
if err != nil {
return fmt.Errorf("FailPayment got %w", err)
}
case <-p.router.quit:
return fmt.Errorf("check payment timeout got: %w",
ErrRouterShuttingDown)
// Fall through if we haven't hit our time limit.
default:
}
return nil
}
// requestRoute is responsible for finding a route to be used to create an HTLC
// attempt.
func (p *paymentLifecycle) requestRoute(
ps *channeldb.MPPaymentState) (*route.Route, error) {
remainingFees := p.calcFeeBudget(ps.FeesPaid)
// Query our payment session to construct a route.
rt, err := p.paySession.RequestRoute(
ps.RemainingAmt, remainingFees,
uint32(ps.NumAttemptsInFlight), uint32(p.currentHeight),
p.firstHopCustomRecords,
)
// Exit early if there's no error.
if err == nil {
// Allow the traffic shaper to add custom records to the
// outgoing HTLC and also adjust the amount if needed.
err = p.amendFirstHopData(rt)
if err != nil {
return nil, err
}
return rt, nil
}
// Otherwise we need to handle the error.
log.Warnf("Failed to find route for payment %v: %v", p.identifier, err)
// If the error belongs to `noRouteError` set, it means a non-critical
// error has happened during path finding, and we will mark the payment
// failed with this reason. Otherwise, we'll return the critical error
// found to abort the lifecycle.
var routeErr noRouteError
if !errors.As(err, &routeErr) {
return nil, fmt.Errorf("requestRoute got: %w", err)
}
// It's the `paymentSession`'s responsibility to find a route for us
// with the best effort. When it cannot find a path, we need to treat it
// as a terminal condition and fail the payment no matter it has
// inflight HTLCs or not.
failureCode := routeErr.FailureReason()
log.Warnf("Marking payment %v permanently failed with no route: %v",
p.identifier, failureCode)
err = p.router.cfg.Control.FailPayment(p.identifier, failureCode)
if err != nil {
return nil, fmt.Errorf("FailPayment got: %w", err)
}
// NOTE: we decide to not return the non-critical noRouteError here to
// avoid terminating the payment lifecycle as there might be other
// inflight HTLCs which we must wait for their results.
return nil, nil
}
// stop signals any active shard goroutine to exit.
func (p *paymentLifecycle) stop() {
close(p.quit)
}
// attemptResult holds the HTLC attempt and a possible error returned from
// sending it.
type attemptResult struct {
// err is non-nil if a non-critical error was encountered when trying
// to send the attempt, and we successfully updated the control tower
// to reflect this error. This can be errors like not enough local
// balance for the given route etc.
err error
// attempt is the attempt structure as recorded in the database.
attempt *channeldb.HTLCAttempt
}
// collectResultAsync launches a goroutine that will wait for the result of the
// given HTLC attempt to be available then save its result in a map. Once
// received, it will send the result returned from the switch to channel
// `resultCollected`.
func (p *paymentLifecycle) collectResultAsync(attempt *channeldb.HTLCAttempt) {
log.Debugf("Collecting result for attempt %v in payment %v",
attempt.AttemptID, p.identifier)
go func() {
result, err := p.collectResult(attempt)
if err != nil {
log.Errorf("Error collecting result for attempt %v in "+
"payment %v: %v", attempt.AttemptID,
p.identifier, err)
return
}
log.Debugf("Result collected for attempt %v in payment %v",
attempt.AttemptID, p.identifier)
// Create a switch result and send it to the resultCollected
// chan, which gets processed when the lifecycle is waiting for
// a result to be received in decideNextStep.
r := &switchResult{
attempt: attempt,
result: result,
}
// Signal that a result has been collected.
select {
// Send the result so decideNextStep can proceed.
case p.resultCollected <- r:
case <-p.quit:
log.Debugf("Lifecycle exiting while collecting "+
"result for payment %v", p.identifier)
case <-p.router.quit:
}
}()
}
// collectResult waits for the result of the given HTLC attempt to be sent by
// the switch and returns it.
func (p *paymentLifecycle) collectResult(
attempt *channeldb.HTLCAttempt) (*htlcswitch.PaymentResult, error) {
log.Tracef("Collecting result for attempt %v",
lnutils.SpewLogClosure(attempt))
result := &htlcswitch.PaymentResult{}
// Regenerate the circuit for this attempt.
circuit, err := attempt.Circuit()
// TODO(yy): We generate this circuit to create the error decryptor,
// which is then used in htlcswitch as the deobfuscator to decode the
// error from `UpdateFailHTLC`. However, suppose it's an
// `UpdateFulfillHTLC` message yet for some reason the sphinx packet is
// failed to be generated, we'd miss settling it. This means we should
// give it a second chance to try the settlement path in case
// `GetAttemptResult` gives us back the preimage. And move the circuit
// creation into htlcswitch so it's only constructed when there's a
// failure message we need to decode.
if err != nil {
log.Debugf("Unable to generate circuit for attempt %v: %v",
attempt.AttemptID, err)
return nil, err
}
// Using the created circuit, initialize the error decrypter, so we can
// parse+decode any failures incurred by this payment within the
// switch.
errorDecryptor := &htlcswitch.SphinxErrorDecrypter{
OnionErrorDecrypter: sphinx.NewOnionErrorDecrypter(circuit),
}
// Now ask the switch to return the result of the payment when
// available.
//
// TODO(yy): consider using htlcswitch to create the `errorDecryptor`
// since the htlc is already in db. This will also make the interface
// `PaymentAttemptDispatcher` deeper and easier to use. Moreover, we'd
// only create the decryptor when received a failure, further saving us
// a few CPU cycles.
resultChan, err := p.router.cfg.Payer.GetAttemptResult(
attempt.AttemptID, p.identifier, errorDecryptor,
)
// Handle the switch error.
if err != nil {
log.Errorf("Failed getting result for attemptID %d "+
"from switch: %v", attempt.AttemptID, err)
result.Error = err
return result, nil
}
// The switch knows about this payment, we'll wait for a result to be
// available.
select {
case r, ok := <-resultChan:
if !ok {
return nil, htlcswitch.ErrSwitchExiting
}
result = r
case <-p.quit:
return nil, ErrPaymentLifecycleExiting
case <-p.router.quit:
return nil, ErrRouterShuttingDown
}
return result, nil
}
// registerAttempt is responsible for creating and saving an HTLC attempt in db
// by using the route info provided. The `remainingAmt` is used to decide
// whether this is the last attempt.
func (p *paymentLifecycle) registerAttempt(rt *route.Route,
remainingAmt lnwire.MilliSatoshi) (*channeldb.HTLCAttempt, error) {
// If this route will consume the last remaining amount to send
// to the receiver, this will be our last shard (for now).
isLastAttempt := rt.ReceiverAmt() == remainingAmt
// Using the route received from the payment session, create a new
// shard to send.
attempt, err := p.createNewPaymentAttempt(rt, isLastAttempt)
if err != nil {
return nil, err
}
// Before sending this HTLC to the switch, we checkpoint the fresh
// paymentID and route to the DB. This lets us know on startup the ID
// of the payment that we attempted to send, such that we can query the
// Switch for its whereabouts. The route is needed to handle the result
// when it eventually comes back.
err = p.router.cfg.Control.RegisterAttempt(
p.identifier, &attempt.HTLCAttemptInfo,
)
return attempt, err
}
// createNewPaymentAttempt creates a new payment attempt from the given route.
func (p *paymentLifecycle) createNewPaymentAttempt(rt *route.Route,
lastShard bool) (*channeldb.HTLCAttempt, error) {
// Generate a new key to be used for this attempt.
sessionKey, err := generateNewSessionKey()
if err != nil {
return nil, err
}
// We generate a new, unique payment ID that we will use for
// this HTLC.
attemptID, err := p.router.cfg.NextPaymentID()
if err != nil {
return nil, err
}
// Request a new shard from the ShardTracker. If this is an AMP
// payment, and this is the last shard, the outstanding shards together
// with this one will be enough for the receiver to derive all HTLC
// preimages. If this a non-AMP payment, the ShardTracker will return a
// simple shard with the payment's static payment hash.
shard, err := p.shardTracker.NewShard(attemptID, lastShard)
if err != nil {
return nil, err
}
// If this shard carries MPP or AMP options, add them to the last hop
// on the route.
hop := rt.Hops[len(rt.Hops)-1]
if shard.MPP() != nil {
hop.MPP = shard.MPP()
}
if shard.AMP() != nil {
hop.AMP = shard.AMP()
}
hash := shard.Hash()
// We now have all the information needed to populate the current
// attempt information.
return channeldb.NewHtlcAttempt(
attemptID, sessionKey, *rt, p.router.cfg.Clock.Now(), &hash,
)
}
// sendAttempt attempts to send the current attempt to the switch to complete
// the payment. If this attempt fails, then we'll continue on to the next
// available route.
func (p *paymentLifecycle) sendAttempt(
attempt *channeldb.HTLCAttempt) (*attemptResult, error) {
log.Debugf("Sending HTLC attempt(id=%v, total_amt=%v, first_hop_amt=%d"+
") for payment %v", attempt.AttemptID,
attempt.Route.TotalAmount, attempt.Route.FirstHopAmount.Val,
p.identifier)
rt := attempt.Route
// Construct the first hop.
firstHop := lnwire.NewShortChanIDFromInt(rt.Hops[0].ChannelID)
// Craft an HTLC packet to send to the htlcswitch. The metadata within
// this packet will be used to route the payment through the network,
// starting with the first-hop.
htlcAdd := &lnwire.UpdateAddHTLC{
Amount: rt.FirstHopAmount.Val.Int(),
Expiry: rt.TotalTimeLock,
PaymentHash: *attempt.Hash,
CustomRecords: rt.FirstHopWireCustomRecords,
}
// Generate the raw encoded sphinx packet to be included along
// with the htlcAdd message that we send directly to the
// switch.
onionBlob, err := attempt.OnionBlob()
if err != nil {
log.Errorf("Failed to create onion blob: attempt=%d in "+
"payment=%v, err:%v", attempt.AttemptID,
p.identifier, err)
return p.failAttempt(attempt.AttemptID, err)
}
htlcAdd.OnionBlob = onionBlob
// Send it to the Switch. When this method returns we assume
// the Switch successfully has persisted the payment attempt,
// such that we can resume waiting for the result after a
// restart.
err = p.router.cfg.Payer.SendHTLC(firstHop, attempt.AttemptID, htlcAdd)
if err != nil {
log.Errorf("Failed sending attempt %d for payment %v to "+
"switch: %v", attempt.AttemptID, p.identifier, err)
return p.handleSwitchErr(attempt, err)
}
log.Debugf("Attempt %v for payment %v successfully sent to switch, "+
"route: %v", attempt.AttemptID, p.identifier, &attempt.Route)
return &attemptResult{
attempt: attempt,
}, nil
}
// amendFirstHopData is a function that calls the traffic shaper to allow it to
// add custom records to the outgoing HTLC and also adjust the amount if
// needed.
func (p *paymentLifecycle) amendFirstHopData(rt *route.Route) error {
// The first hop amount on the route is the full route amount if not
// overwritten by the traffic shaper. So we set the initial value now
// and potentially overwrite it later.
rt.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0](
tlv.NewBigSizeT(rt.TotalAmount),
)
// By default, we set the first hop custom records to the initial
// value requested by the RPC. The traffic shaper may overwrite this
// value.
rt.FirstHopWireCustomRecords = p.firstHopCustomRecords
// extraDataRequest is a helper struct to pass the custom records and
// amount back from the traffic shaper.
type extraDataRequest struct {
customRecords fn.Option[lnwire.CustomRecords]
amount fn.Option[lnwire.MilliSatoshi]
}
// If a hook exists that may affect our outgoing message, we call it now
// and apply its side effects to the UpdateAddHTLC message.
result, err := fn.MapOptionZ(
p.router.cfg.TrafficShaper,
//nolint:ll
func(ts htlcswitch.AuxTrafficShaper) fn.Result[extraDataRequest] {
newAmt, newRecords, err := ts.ProduceHtlcExtraData(
rt.TotalAmount, p.firstHopCustomRecords,
)
if err != nil {
return fn.Err[extraDataRequest](err)
}
// Make sure we only received valid records.
if err := newRecords.Validate(); err != nil {
return fn.Err[extraDataRequest](err)
}
log.Debugf("Aux traffic shaper returned custom "+
"records %v and amount %d msat for HTLC",
spew.Sdump(newRecords), newAmt)
return fn.Ok(extraDataRequest{
customRecords: fn.Some(newRecords),
amount: fn.Some(newAmt),
})
},
).Unpack()
if err != nil {
return fmt.Errorf("traffic shaper failed to produce extra "+
"data: %w", err)
}
// Apply the side effects to the UpdateAddHTLC message.
result.customRecords.WhenSome(func(records lnwire.CustomRecords) {
rt.FirstHopWireCustomRecords = records
})
result.amount.WhenSome(func(amount lnwire.MilliSatoshi) {
rt.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0](
tlv.NewBigSizeT(amount),
)
})
return nil
}
// failAttemptAndPayment fails both the payment and its attempt via the
// router's control tower, which marks the payment as failed in db.
func (p *paymentLifecycle) failPaymentAndAttempt(
attemptID uint64, reason *channeldb.FailureReason,
sendErr error) (*attemptResult, error) {
log.Errorf("Payment %v failed: final_outcome=%v, raw_err=%v",
p.identifier, *reason, sendErr)
// Fail the payment via control tower.
//
// NOTE: we must fail the payment first before failing the attempt.
// Otherwise, once the attempt is marked as failed, another goroutine
// might make another attempt while we are failing the payment.
err := p.router.cfg.Control.FailPayment(p.identifier, *reason)
if err != nil {
log.Errorf("Unable to fail payment: %v", err)
return nil, err
}
// Fail the attempt.
return p.failAttempt(attemptID, sendErr)
}
// handleSwitchErr inspects the given error from the Switch and determines
// whether we should make another payment attempt, or if it should be
// considered a terminal error. Terminal errors will be recorded with the
// control tower. It analyzes the sendErr for the payment attempt received from
// the switch and updates mission control and/or channel policies. Depending on
// the error type, the error is either the final outcome of the payment or we
// need to continue with an alternative route. A final outcome is indicated by
// a non-nil reason value.
func (p *paymentLifecycle) handleSwitchErr(attempt *channeldb.HTLCAttempt,
sendErr error) (*attemptResult, error) {
internalErrorReason := channeldb.FailureReasonError
attemptID := attempt.AttemptID
// reportAndFail is a helper closure that reports the failure to the
// mission control, which helps us to decide whether we want to retry
// the payment or not. If a non nil reason is returned from mission
// control, it will further fail the payment via control tower.
reportAndFail := func(srcIdx *int,
msg lnwire.FailureMessage) (*attemptResult, error) {
// Report outcome to mission control.
reason, err := p.router.cfg.MissionControl.ReportPaymentFail(
attemptID, &attempt.Route, srcIdx, msg,
)
if err != nil {
log.Errorf("Error reporting payment result to mc: %v",
err)
reason = &internalErrorReason
}
// Fail the attempt only if there's no reason.
if reason == nil {
// Fail the attempt.
return p.failAttempt(attemptID, sendErr)
}
// Otherwise fail both the payment and the attempt.
return p.failPaymentAndAttempt(attemptID, reason, sendErr)
}
// If this attempt ID is unknown to the Switch, it means it was never
// checkpointed and forwarded by the switch before a restart. In this
// case we can safely send a new payment attempt, and wait for its
// result to be available.
if errors.Is(sendErr, htlcswitch.ErrPaymentIDNotFound) {
log.Warnf("Failing attempt=%v for payment=%v as it's not "+
"found in the Switch", attempt.AttemptID, p.identifier)
return p.failAttempt(attemptID, sendErr)
}
if errors.Is(sendErr, htlcswitch.ErrUnreadableFailureMessage) {
log.Warn("Unreadable failure when sending htlc: id=%v, hash=%v",
attempt.AttemptID, attempt.Hash)
// Since this error message cannot be decrypted, we will send a
// nil error message to our mission controller and fail the
// payment.
return reportAndFail(nil, nil)
}
// If the error is a ClearTextError, we have received a valid wire
// failure message, either from our own outgoing link or from a node
// down the route. If the error is not related to the propagation of
// our payment, we can stop trying because an internal error has
// occurred.
var rtErr htlcswitch.ClearTextError
ok := errors.As(sendErr, &rtErr)
if !ok {
return p.failPaymentAndAttempt(
attemptID, &internalErrorReason, sendErr,
)
}
// failureSourceIdx is the index of the node that the failure occurred
// at. If the ClearTextError received is not a ForwardingError the
// payment error occurred at our node, so we leave this value as 0
// to indicate that the failure occurred locally. If the error is a
// ForwardingError, it did not originate at our node, so we set
// failureSourceIdx to the index of the node where the failure occurred.
failureSourceIdx := 0
var source *htlcswitch.ForwardingError
ok = errors.As(rtErr, &source)
if ok {
failureSourceIdx = source.FailureSourceIdx
}
// Extract the wire failure and apply channel update if it contains one.
// If we received an unknown failure message from a node along the
// route, the failure message will be nil.
failureMessage := rtErr.WireMessage()
err := p.handleFailureMessage(
&attempt.Route, failureSourceIdx, failureMessage,
)
if err != nil {
return p.failPaymentAndAttempt(
attemptID, &internalErrorReason, sendErr,
)
}
log.Tracef("Node=%v reported failure when sending htlc",
failureSourceIdx)
return reportAndFail(&failureSourceIdx, failureMessage)
}
// handleFailureMessage tries to apply a channel update present in the failure
// message if any.
func (p *paymentLifecycle) handleFailureMessage(rt *route.Route,
errorSourceIdx int, failure lnwire.FailureMessage) error {
if failure == nil {
return nil
}
// It makes no sense to apply our own channel updates.
if errorSourceIdx == 0 {
log.Errorf("Channel update of ourselves received")
return nil
}
// Extract channel update if the error contains one.
update := p.router.extractChannelUpdate(failure)
if update == nil {
return nil
}
// Parse pubkey to allow validation of the channel update. This should
// always succeed, otherwise there is something wrong in our
// implementation. Therefore, return an error.
errVertex := rt.Hops[errorSourceIdx-1].PubKeyBytes
errSource, err := btcec.ParsePubKey(errVertex[:])
if err != nil {
log.Errorf("Cannot parse pubkey: idx=%v, pubkey=%v",
errorSourceIdx, errVertex)
return err
}
var (
isAdditionalEdge bool
policy *models.CachedEdgePolicy
)
// Before we apply the channel update, we need to decide whether the
// update is for additional (ephemeral) edge or normal edge stored in
// db.
//
// Note: the p.paySession might be nil here if it's called inside
// SendToRoute where there's no payment lifecycle.
if p.paySession != nil {
policy = p.paySession.GetAdditionalEdgePolicy(
errSource, update.ShortChannelID.ToUint64(),
)
if policy != nil {
isAdditionalEdge = true
}
}
// Apply channel update to additional edge policy.
if isAdditionalEdge {
if !p.paySession.UpdateAdditionalEdge(
update, errSource, policy) {
log.Debugf("Invalid channel update received: node=%v",
errVertex)
}
return nil
}
// Apply channel update to the channel edge policy in our db.
if !p.router.cfg.ApplyChannelUpdate(update) {
log.Debugf("Invalid channel update received: node=%v",
errVertex)
}
return nil
}
// failAttempt calls control tower to fail the current payment attempt.
func (p *paymentLifecycle) failAttempt(attemptID uint64,
sendError error) (*attemptResult, error) {
log.Warnf("Attempt %v for payment %v failed: %v", attemptID,
p.identifier, sendError)
failInfo := marshallError(
sendError,
p.router.cfg.Clock.Now(),
)
// Now that we are failing this payment attempt, cancel the shard with
// the ShardTracker such that it can derive the correct hash for the
// next attempt.
if err := p.shardTracker.CancelShard(attemptID); err != nil {
return nil, err
}
attempt, err := p.router.cfg.Control.FailAttempt(
p.identifier, attemptID, failInfo,
)
if err != nil {
return nil, err
}
return &attemptResult{
attempt: attempt,
err: sendError,
}, nil
}
// marshallError marshall an error as received from the switch to a structure
// that is suitable for database storage.
func marshallError(sendError error, time time.Time) *channeldb.HTLCFailInfo {
response := &channeldb.HTLCFailInfo{
FailTime: time,
}
switch {
case errors.Is(sendError, htlcswitch.ErrPaymentIDNotFound):
response.Reason = channeldb.HTLCFailInternal
return response
case errors.Is(sendError, htlcswitch.ErrUnreadableFailureMessage):
response.Reason = channeldb.HTLCFailUnreadable
return response
}
var rtErr htlcswitch.ClearTextError
ok := errors.As(sendError, &rtErr)
if !ok {
response.Reason = channeldb.HTLCFailInternal
return response
}
message := rtErr.WireMessage()
if message != nil {
response.Reason = channeldb.HTLCFailMessage
response.Message = message
} else {
response.Reason = channeldb.HTLCFailUnknown
}
// If the ClearTextError received is a ForwardingError, the error
// originated from a node along the route, not locally on our outgoing
// link. We set failureSourceIdx to the index of the node where the
// failure occurred. If the error is not a ForwardingError, the failure
// occurred at our node, so we leave the index as 0 to indicate that
// we failed locally.
var fErr *htlcswitch.ForwardingError
ok = errors.As(rtErr, &fErr)
if ok {
response.FailureSourceIndex = uint32(fErr.FailureSourceIdx)
}
return response
}
// patchLegacyPaymentHash will make a copy of the passed attempt and sets its
// Hash field to be the payment hash if it's nil.
//
// NOTE: For legacy payments, which were created before the AMP feature was
// enabled, the `Hash` field in their HTLC attempts is nil. In that case, we use
// the payment hash as the `attempt.Hash` as they are identical.
func (p *paymentLifecycle) patchLegacyPaymentHash(
a channeldb.HTLCAttempt) channeldb.HTLCAttempt {
// Exit early if this is not a legacy attempt.
if a.Hash != nil {
return a
}
// Log a warning if the user is still using legacy payments, which has
// weaker support.
log.Warnf("Found legacy htlc attempt %v in payment %v", a.AttemptID,
p.identifier)
// Set the attempt's hash to be the payment hash, which is the payment's
// `PaymentHash`` in the `PaymentCreationInfo`. For legacy payments
// before AMP feature, the `Hash` field was not set so we use the
// payment hash instead.
//
// NOTE: During the router's startup, we have a similar logic in
// `resumePayments`, in which we will use the payment hash instead if
// the attempt's hash is nil.
a.Hash = &p.identifier
return a
}
// reloadInflightAttempts is called when the payment lifecycle is resumed after
// a restart. It reloads all inflight attempts from the control tower and
// collects the results of the attempts that have been sent before.
func (p *paymentLifecycle) reloadInflightAttempts() (DBMPPayment, error) {
payment, err := p.router.cfg.Control.FetchPayment(p.identifier)
if err != nil {
return nil, err
}
for _, a := range payment.InFlightHTLCs() {
a := a
log.Infof("Resuming HTLC attempt %v for payment %v",
a.AttemptID, p.identifier)
// Potentially attach the payment hash to the `Hash` field if
// it's a legacy payment.
a = p.patchLegacyPaymentHash(a)
p.resultCollector(&a)
}
return payment, nil
}
// reloadPayment returns the latest payment found in the db (control tower).
func (p *paymentLifecycle) reloadPayment() (DBMPPayment,
*channeldb.MPPaymentState, error) {
// Read the db to get the latest state of the payment.
payment, err := p.router.cfg.Control.FetchPayment(p.identifier)
if err != nil {
return nil, nil, err
}
ps := payment.GetState()
remainingFees := p.calcFeeBudget(ps.FeesPaid)
log.Debugf("Payment %v: status=%v, active_shards=%v, rem_value=%v, "+
"fee_limit=%v", p.identifier, payment.GetStatus(),
ps.NumAttemptsInFlight, ps.RemainingAmt, remainingFees)
return payment, ps, nil
}
// handleAttemptResult processes the result of an HTLC attempt returned from
// the htlcswitch.
func (p *paymentLifecycle) handleAttemptResult(attempt *channeldb.HTLCAttempt,
result *htlcswitch.PaymentResult) (*attemptResult, error) {
// If the result has an error, we need to further process it by failing
// the attempt and maybe fail the payment.
if result.Error != nil {
return p.handleSwitchErr(attempt, result.Error)
}
// We got an attempt settled result back from the switch.
log.Debugf("Payment(%v): attempt(%v) succeeded", p.identifier,
attempt.AttemptID)
// Report success to mission control.
err := p.router.cfg.MissionControl.ReportPaymentSuccess(
attempt.AttemptID, &attempt.Route,
)
if err != nil {
log.Errorf("Error reporting payment success to mc: %v", err)
}
// In case of success we atomically store settle result to the DB and
// move the shard to the settled state.
htlcAttempt, err := p.router.cfg.Control.SettleAttempt(
p.identifier, attempt.AttemptID,
&channeldb.HTLCSettleInfo{
Preimage: result.Preimage,
SettleTime: p.router.cfg.Clock.Now(),
},
)
if err != nil {
log.Errorf("Error settling attempt %v for payment %v with "+
"preimage %v: %v", attempt.AttemptID, p.identifier,
result.Preimage, err)
// We won't mark the attempt as failed since we already have
// the preimage.
return nil, err
}
return &attemptResult{
attempt: htlcAttempt,
}, nil
}
// collectAndHandleResult waits for the result for the given attempt to be
// available from the Switch, then records the attempt outcome with the control
// tower. An attemptResult is returned, indicating the final outcome of this
// HTLC attempt.
func (p *paymentLifecycle) collectAndHandleResult(
attempt *channeldb.HTLCAttempt) (*attemptResult, error) {
result, err := p.collectResult(attempt)
if err != nil {
return nil, err
}
return p.handleAttemptResult(attempt, result)
}
package routing
import (
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/channeldb"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/routing/route"
)
// BlockPadding is used to increment the finalCltvDelta value for the last hop
// to prevent an HTLC being failed if some blocks are mined while it's in-flight.
const BlockPadding uint16 = 3
// ValidateCLTVLimit is a helper function that validates that the cltv limit is
// greater than the final cltv delta parameter, optionally including the
// BlockPadding in this calculation.
func ValidateCLTVLimit(limit uint32, delta uint16, includePad bool) error {
if includePad {
delta += BlockPadding
}
if limit <= uint32(delta) {
return fmt.Errorf("cltv limit %v should be greater than %v",
limit, delta)
}
return nil
}
// noRouteError encodes a non-critical error encountered during path finding.
type noRouteError uint8
const (
// errNoTlvPayload is returned when the destination hop does not support
// a tlv payload.
errNoTlvPayload noRouteError = iota
// errNoPaymentAddr is returned when the destination hop does not
// support payment addresses.
errNoPaymentAddr
// errNoPathFound is returned when a path to the target destination does
// not exist in the graph.
errNoPathFound
// errInsufficientLocalBalance is returned when none of the local
// channels have enough balance for the payment.
errInsufficientBalance
// errEmptyPaySession is returned when the empty payment session is
// queried for a route.
errEmptyPaySession
// errUnknownRequiredFeature is returned when the destination node
// requires an unknown feature.
errUnknownRequiredFeature
// errMissingDependentFeature is returned when the destination node
// misses a feature that a feature that we require depends on.
errMissingDependentFeature
)
var (
// DefaultShardMinAmt is the default amount beyond which we won't try to
// further split the payment if no route is found. It is the minimum
// amount that we use as the shard size when splitting.
DefaultShardMinAmt = lnwire.NewMSatFromSatoshis(10000)
)
// Error returns the string representation of the noRouteError.
func (e noRouteError) Error() string {
switch e {
case errNoTlvPayload:
return "destination hop doesn't understand new TLV payloads"
case errNoPaymentAddr:
return "destination hop doesn't understand payment addresses"
case errNoPathFound:
return "unable to find a path to destination"
case errEmptyPaySession:
return "empty payment session"
case errInsufficientBalance:
return "insufficient local balance"
case errUnknownRequiredFeature:
return "unknown required feature"
case errMissingDependentFeature:
return "missing dependent feature"
default:
return "unknown no-route error"
}
}
// FailureReason converts a path finding error into a payment-level failure.
func (e noRouteError) FailureReason() channeldb.FailureReason {
switch e {
case
errNoTlvPayload,
errNoPaymentAddr,
errNoPathFound,
errEmptyPaySession,
errUnknownRequiredFeature,
errMissingDependentFeature:
return channeldb.FailureReasonNoRoute
case errInsufficientBalance:
return channeldb.FailureReasonInsufficientBalance
default:
return channeldb.FailureReasonError
}
}
// PaymentSession is used during SendPayment attempts to provide routes to
// attempt. It also defines methods to give the PaymentSession additional
// information learned during the previous attempts.
type PaymentSession interface {
// RequestRoute returns the next route to attempt for routing the
// specified HTLC payment to the target node. The returned route should
// carry at most maxAmt to the target node, and pay at most feeLimit in
// fees. It can carry less if the payment is MPP. The activeShards
// argument should be set to instruct the payment session about the
// number of in flight HTLCS for the payment, such that it can choose
// splitting strategy accordingly.
//
// A noRouteError is returned if a non-critical error is encountered
// during path finding.
RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
activeShards, height uint32,
firstHopCustomRecords lnwire.CustomRecords) (*route.Route,
error)
// UpdateAdditionalEdge takes an additional channel edge policy
// (private channels) and applies the update from the message. Returns
// a boolean to indicate whether the update has been applied without
// error.
UpdateAdditionalEdge(msg *lnwire.ChannelUpdate1,
pubKey *btcec.PublicKey, policy *models.CachedEdgePolicy) bool
// GetAdditionalEdgePolicy uses the public key and channel ID to query
// the ephemeral channel edge policy for additional edges. Returns a nil
// if nothing found.
GetAdditionalEdgePolicy(pubKey *btcec.PublicKey,
channelID uint64) *models.CachedEdgePolicy
}
// paymentSession is used during an HTLC routings session to prune the local
// chain view in response to failures, and also report those failures back to
// MissionController. The snapshot copied for this session will only ever grow,
// and will now be pruned after a decay like the main view within mission
// control. We do this as we want to avoid the case where we continually try a
// bad edge or route multiple times in a session. This can lead to an infinite
// loop if payment attempts take long enough. An additional set of edges can
// also be provided to assist in reaching the payment's destination.
type paymentSession struct {
selfNode route.Vertex
additionalEdges map[route.Vertex][]AdditionalEdge
getBandwidthHints func(Graph) (bandwidthHints, error)
payment *LightningPayment
empty bool
pathFinder pathFinder
graphSessFactory GraphSessionFactory
// pathFindingConfig defines global parameters that control the
// trade-off in path finding between fees and probability.
pathFindingConfig PathFindingConfig
missionControl MissionControlQuerier
// minShardAmt is the amount beyond which we won't try to further split
// the payment if no route is found. If the maximum number of htlcs
// specified in the payment is one, under no circumstances splitting
// will happen and this value remains unused.
minShardAmt lnwire.MilliSatoshi
// log is a payment session-specific logger.
log btclog.Logger
}
// newPaymentSession instantiates a new payment session.
func newPaymentSession(p *LightningPayment, selfNode route.Vertex,
getBandwidthHints func(Graph) (bandwidthHints, error),
graphSessFactory GraphSessionFactory,
missionControl MissionControlQuerier,
pathFindingConfig PathFindingConfig) (*paymentSession, error) {
edges, err := RouteHintsToEdges(p.RouteHints, p.Target)
if err != nil {
return nil, err
}
if p.BlindedPathSet != nil {
if len(edges) != 0 {
return nil, fmt.Errorf("cannot have both route hints " +
"and blinded path")
}
edges, err = p.BlindedPathSet.ToRouteHints()
if err != nil {
return nil, err
}
}
logPrefix := fmt.Sprintf("PaymentSession(%x):", p.Identifier())
return &paymentSession{
selfNode: selfNode,
additionalEdges: edges,
getBandwidthHints: getBandwidthHints,
payment: p,
pathFinder: findPath,
graphSessFactory: graphSessFactory,
pathFindingConfig: pathFindingConfig,
missionControl: missionControl,
minShardAmt: DefaultShardMinAmt,
log: log.WithPrefix(logPrefix),
}, nil
}
// pathFindingError is a wrapper error type that is used to distinguish path
// finding errors from other errors in path finding loop.
type pathFindingError struct {
error
}
// Unwrap returns the underlying error.
func (e *pathFindingError) Unwrap() error {
return e.error
}
// RequestRoute returns a route which is likely to be capable for successfully
// routing the specified HTLC payment to the target node. Initially the first
// set of paths returned from this method may encounter routing failure along
// the way, however as more payments are sent, mission control will start to
// build an up to date view of the network itself. With each payment a new area
// will be explored, which feeds into the recommendations made for routing.
//
// NOTE: This function is safe for concurrent access.
// NOTE: Part of the PaymentSession interface.
func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
activeShards, height uint32,
firstHopCustomRecords lnwire.CustomRecords) (*route.Route, error) {
if p.empty {
return nil, errEmptyPaySession
}
// Add BlockPadding to the finalCltvDelta so that the receiving node
// does not reject the HTLC if some blocks are mined while it's in-flight.
finalCltvDelta := p.payment.FinalCLTVDelta
finalCltvDelta += BlockPadding
// We need to subtract the final delta before passing it into path
// finding. The optimal path is independent of the final cltv delta and
// the path finding algorithm is unaware of this value.
cltvLimit := p.payment.CltvLimit - uint32(finalCltvDelta)
// TODO(roasbeef): sync logic amongst dist sys
// Taking into account this prune view, we'll attempt to locate a path
// to our destination, respecting the recommendations from
// MissionController.
restrictions := &RestrictParams{
ProbabilitySource: p.missionControl.GetProbability,
FeeLimit: feeLimit,
OutgoingChannelIDs: p.payment.OutgoingChannelIDs,
LastHop: p.payment.LastHop,
CltvLimit: cltvLimit,
DestCustomRecords: p.payment.DestCustomRecords,
DestFeatures: p.payment.DestFeatures,
PaymentAddr: p.payment.PaymentAddr,
Amp: p.payment.amp,
Metadata: p.payment.Metadata,
FirstHopCustomRecords: firstHopCustomRecords,
}
finalHtlcExpiry := int32(height) + int32(finalCltvDelta)
// Before we enter the loop below, we'll make sure to respect the max
// payment shard size (if it's set), which is effectively our
// client-side MTU that we'll attempt to respect at all times.
maxShardActive := p.payment.MaxShardAmt != nil
if maxShardActive && maxAmt > *p.payment.MaxShardAmt {
p.log.Debugf("Clamping payment attempt from %v to %v due to "+
"max shard size of %v", maxAmt, *p.payment.MaxShardAmt,
maxAmt)
maxAmt = *p.payment.MaxShardAmt
}
var path []*unifiedEdge
findPath := func(graph graphdb.NodeTraverser) error {
// We'll also obtain a set of bandwidthHints from the lower
// layer for each of our outbound channels. This will allow the
// path finding to skip any links that aren't active or just
// don't have enough bandwidth to carry the payment. New
// bandwidth hints are queried for every new path finding
// attempt, because concurrent payments may change balances.
bandwidthHints, err := p.getBandwidthHints(graph)
if err != nil {
return err
}
p.log.Debugf("pathfinding for amt=%v", maxAmt)
// Find a route for the current amount.
path, _, err = p.pathFinder(
&graphParams{
additionalEdges: p.additionalEdges,
bandwidthHints: bandwidthHints,
graph: graph,
},
restrictions, &p.pathFindingConfig,
p.selfNode, p.selfNode, p.payment.Target,
maxAmt, p.payment.TimePref, finalHtlcExpiry,
)
if err != nil {
// Wrap the error to distinguish path finding errors
// from other errors in this closure.
return &pathFindingError{err}
}
return nil
}
for {
err := p.graphSessFactory.GraphSession(findPath)
// If there is an error, and it is not a path finding error, we
// return it immediately.
if err != nil && !lnutils.ErrorAs[*pathFindingError](err) {
return nil, err
} else if err != nil {
// If the error is a path finding error, we'll unwrap it
// to check the underlying error.
//
//nolint:errorlint
pErr, _ := err.(*pathFindingError)
err = pErr.Unwrap()
}
// Otherwise, we'll switch on the path finding error.
switch {
case err == errNoPathFound:
// Don't split if this is a legacy payment without mpp
// record. If it has a blinded path though, then we
// can split. Split payments to blinded paths won't have
// MPP records.
if p.payment.PaymentAddr.IsNone() &&
p.payment.BlindedPathSet == nil {
p.log.Debugf("not splitting because payment " +
"address is unspecified")
return nil, errNoPathFound
}
if p.payment.DestFeatures == nil {
p.log.Debug("Not splitting because " +
"destination DestFeatures is nil")
return nil, errNoPathFound
}
destFeatures := p.payment.DestFeatures
if !destFeatures.HasFeature(lnwire.MPPOptional) &&
!destFeatures.HasFeature(lnwire.AMPOptional) {
p.log.Debug("not splitting because " +
"destination doesn't declare MPP or " +
"AMP")
return nil, errNoPathFound
}
// No splitting if this is the last shard.
isLastShard := activeShards+1 >= p.payment.MaxParts
if isLastShard {
p.log.Debugf("not splitting because shard "+
"limit %v has been reached",
p.payment.MaxParts)
return nil, errNoPathFound
}
// This is where the magic happens. If we can't find a
// route, try it for half the amount.
maxAmt /= 2
// Put a lower bound on the minimum shard size.
if maxAmt < p.minShardAmt {
p.log.Debugf("not splitting because minimum "+
"shard amount %v has been reached",
p.minShardAmt)
return nil, errNoPathFound
}
// Go pathfinding.
continue
// If there isn't enough local bandwidth, there is no point in
// splitting. It won't be possible to create a complete set in
// any case, but the sent out partial payments would be held by
// the receiver until the mpp timeout.
case err == errInsufficientBalance:
p.log.Debug("not splitting because local balance " +
"is insufficient")
return nil, err
case err != nil:
return nil, err
}
// With the next candidate path found, we'll attempt to turn
// this into a route by applying the time-lock and fee
// requirements.
route, err := newRoute(
p.selfNode, path, height,
finalHopParams{
amt: maxAmt,
totalAmt: p.payment.Amount,
cltvDelta: finalCltvDelta,
records: p.payment.DestCustomRecords,
paymentAddr: p.payment.PaymentAddr,
metadata: p.payment.Metadata,
}, p.payment.BlindedPathSet,
)
if err != nil {
return nil, err
}
return route, err
}
}
// UpdateAdditionalEdge updates the channel edge policy for a private edge. It
// validates the message signature and checks it's up to date, then applies the
// updates to the supplied policy. It returns a boolean to indicate whether
// there's an error when applying the updates.
func (p *paymentSession) UpdateAdditionalEdge(msg *lnwire.ChannelUpdate1,
pubKey *btcec.PublicKey, policy *models.CachedEdgePolicy) bool {
// Validate the message signature.
if err := netann.VerifyChannelUpdateSignature(msg, pubKey); err != nil {
log.Errorf(
"Unable to validate channel update signature: %v", err,
)
return false
}
// Update channel policy for the additional edge.
policy.TimeLockDelta = msg.TimeLockDelta
policy.FeeBaseMSat = lnwire.MilliSatoshi(msg.BaseFee)
policy.FeeProportionalMillionths = lnwire.MilliSatoshi(msg.FeeRate)
log.Debugf("New private channel update applied: %v",
lnutils.SpewLogClosure(msg))
return true
}
// GetAdditionalEdgePolicy uses the public key and channel ID to query the
// ephemeral channel edge policy for additional edges. Returns a nil if nothing
// found.
func (p *paymentSession) GetAdditionalEdgePolicy(pubKey *btcec.PublicKey,
channelID uint64) *models.CachedEdgePolicy {
target := route.NewVertex(pubKey)
edges, ok := p.additionalEdges[target]
if !ok {
return nil
}
for _, edge := range edges {
policy := edge.EdgePolicy()
if policy.ChannelID != channelID {
continue
}
return policy
}
return nil
}
package routing
import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/tlv"
"github.com/lightningnetwork/lnd/zpay32"
)
// A compile time assertion to ensure SessionSource meets the
// PaymentSessionSource interface.
var _ PaymentSessionSource = (*SessionSource)(nil)
// SessionSource defines a source for the router to retrieve new payment
// sessions.
type SessionSource struct {
// GraphSessionFactory can be used to gain access to a Graph session.
// If the backing DB allows it, this will mean that a read transaction
// is being held during the use of the session.
GraphSessionFactory GraphSessionFactory
// SourceNode is the graph's source node.
SourceNode *models.LightningNode
// GetLink is a method that allows querying the lower link layer
// to determine the up to date available bandwidth at a prospective link
// to be traversed. If the link isn't available, then a value of zero
// should be returned. Otherwise, the current up to date knowledge of
// the available bandwidth of the link should be returned.
GetLink getLinkQuery
// MissionControl is a shared memory of sorts that executions of payment
// path finding use in order to remember which vertexes/edges were
// pruned from prior attempts. During payment execution, errors sent by
// nodes are mapped into a vertex or edge to be pruned. Each run will
// then take into account this set of pruned vertexes/edges to reduce
// route failure and pass on graph information gained to the next
// execution.
MissionControl MissionControlQuerier
// PathFindingConfig defines global parameters that control the
// trade-off in path finding between fees and probability.
PathFindingConfig PathFindingConfig
}
// NewPaymentSession creates a new payment session backed by the latest prune
// view from Mission Control. An optional set of routing hints can be provided
// in order to populate additional edges to explore when finding a path to the
// payment's destination.
func (m *SessionSource) NewPaymentSession(p *LightningPayment,
firstHopBlob fn.Option[tlv.Blob],
trafficShaper fn.Option[htlcswitch.AuxTrafficShaper]) (PaymentSession,
error) {
getBandwidthHints := func(graph Graph) (bandwidthHints, error) {
return newBandwidthManager(
graph, m.SourceNode.PubKeyBytes, m.GetLink,
firstHopBlob, trafficShaper,
)
}
session, err := newPaymentSession(
p, m.SourceNode.PubKeyBytes, getBandwidthHints,
m.GraphSessionFactory, m.MissionControl, m.PathFindingConfig,
)
if err != nil {
return nil, err
}
return session, nil
}
// NewPaymentSessionEmpty creates a new paymentSession instance that is empty,
// and will be exhausted immediately. Used for failure reporting to
// missioncontrol for resumed payment we don't want to make more attempts for.
func (m *SessionSource) NewPaymentSessionEmpty() PaymentSession {
return &paymentSession{
empty: true,
}
}
// RouteHintsToEdges converts a list of invoice route hints to an edge map that
// can be passed into pathfinding.
func RouteHintsToEdges(routeHints [][]zpay32.HopHint, target route.Vertex) (
map[route.Vertex][]AdditionalEdge, error) {
edges := make(map[route.Vertex][]AdditionalEdge)
// Traverse through all of the available hop hints and include them in
// our edges map, indexed by the public key of the channel's starting
// node.
for _, routeHint := range routeHints {
// If multiple hop hints are provided within a single route
// hint, we'll assume they must be chained together and sorted
// in forward order in order to reach the target successfully.
for i, hopHint := range routeHint {
// In order to determine the end node of this hint,
// we'll need to look at the next hint's start node. If
// we've reached the end of the hints list, we can
// assume we've reached the destination.
endNode := &models.LightningNode{}
if i != len(routeHint)-1 {
endNode.AddPubKey(routeHint[i+1].NodeID)
} else {
targetPubKey, err := btcec.ParsePubKey(
target[:],
)
if err != nil {
return nil, err
}
endNode.AddPubKey(targetPubKey)
}
// Finally, create the channel edge from the hop hint
// and add it to list of edges corresponding to the node
// at the start of the channel.
edgePolicy := &models.CachedEdgePolicy{
ToNodePubKey: func() route.Vertex {
return endNode.PubKeyBytes
},
ToNodeFeatures: lnwire.EmptyFeatureVector(),
ChannelID: hopHint.ChannelID,
FeeBaseMSat: lnwire.MilliSatoshi(
hopHint.FeeBaseMSat,
),
FeeProportionalMillionths: lnwire.MilliSatoshi(
hopHint.FeeProportionalMillionths,
),
TimeLockDelta: hopHint.CLTVExpiryDelta,
}
edge := &PrivateEdge{
policy: edgePolicy,
}
v := route.NewVertex(hopHint.NodeID)
edges[v] = append(edges[v], edge)
}
}
return edges, nil
}
package routing
import (
"errors"
"fmt"
"math"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
const (
// CapacityFraction and capacitySmearingFraction define how
// capacity-related probability reweighting works. CapacityFraction
// defines the fraction of the channel capacity at which the effect
// roughly sets in and capacitySmearingFraction defines over which range
// the factor changes from 1 to minCapacityFactor.
// DefaultCapacityFraction is the default value for CapacityFraction.
// It is chosen such that the capacity factor is active but with a small
// effect. This value together with capacitySmearingFraction leads to a
// noticeable reduction in probability if the amount starts to come
// close to 90% of a channel's capacity.
DefaultCapacityFraction = 0.9999
// capacitySmearingFraction defines how quickly the capacity factor
// drops from 1 to minCapacityFactor. This value results in about a
// variation over 20% of the capacity.
capacitySmearingFraction = 0.025
// minCapacityFactor is the minimal value the capacityFactor can take.
// Having a too low value can lead to discarding of paths due to the
// enforced minimal probability or to too high pathfinding weights.
minCapacityFactor = 0.5
// minCapacityFraction is the minimum allowed value for
// CapacityFraction. The success probability in the random balance model
// (which may not be an accurate description of the liquidity
// distribution in the network) can be approximated with P(a) = 1 - a/c,
// for amount a and capacity c. If we require a probability P(a) = 0.25,
// this translates into a value of 0.75 for a/c. We limit this value in
// order to not discard too many channels.
minCapacityFraction = 0.75
// AprioriEstimatorName is used to identify the apriori probability
// estimator.
AprioriEstimatorName = "apriori"
)
var (
// ErrInvalidHalflife is returned when we get an invalid half life.
ErrInvalidHalflife = errors.New("penalty half life must be >= 0")
// ErrInvalidHopProbability is returned when we get an invalid hop
// probability.
ErrInvalidHopProbability = errors.New("hop probability must be in " +
"[0, 1]")
// ErrInvalidAprioriWeight is returned when we get an apriori weight
// that is out of range.
ErrInvalidAprioriWeight = errors.New("apriori weight must be in [0, 1]")
// ErrInvalidCapacityFraction is returned when we get a capacity
// fraction that is out of range.
ErrInvalidCapacityFraction = fmt.Errorf("capacity fraction must be in "+
"[%v, 1]", minCapacityFraction)
)
// AprioriConfig contains configuration for our probability estimator.
type AprioriConfig struct {
// PenaltyHalfLife defines after how much time a penalized node or
// channel is back at 50% probability.
PenaltyHalfLife time.Duration
// AprioriHopProbability is the assumed success probability of a hop in
// a route when no other information is available.
AprioriHopProbability float64
// AprioriWeight is a value in the range [0, 1] that defines to what
// extent historical results should be extrapolated to untried
// connections. Setting it to one will completely ignore historical
// results and always assume the configured a priori probability for
// untried connections. A value of zero will ignore the a priori
// probability completely and only base the probability on historical
// results, unless there are none available.
AprioriWeight float64
// CapacityFraction is the fraction of a channel's capacity that we
// consider to have liquidity. For amounts that come close to or exceed
// the fraction, an additional penalty is applied. A value of 1.0
// disables the capacityFactor.
CapacityFraction float64
}
// validate checks the configuration of the estimator for allowed values.
func (p AprioriConfig) validate() error {
if p.PenaltyHalfLife < 0 {
return ErrInvalidHalflife
}
if p.AprioriHopProbability < 0 || p.AprioriHopProbability > 1 {
return ErrInvalidHopProbability
}
if p.AprioriWeight < 0 || p.AprioriWeight > 1 {
return ErrInvalidAprioriWeight
}
if p.CapacityFraction < minCapacityFraction || p.CapacityFraction > 1 {
return ErrInvalidCapacityFraction
}
return nil
}
// DefaultAprioriConfig returns the default configuration for the estimator.
func DefaultAprioriConfig() AprioriConfig {
return AprioriConfig{
PenaltyHalfLife: DefaultPenaltyHalfLife,
AprioriHopProbability: DefaultAprioriHopProbability,
AprioriWeight: DefaultAprioriWeight,
CapacityFraction: DefaultCapacityFraction,
}
}
// AprioriEstimator returns node and pair probabilities based on historical
// payment results. It uses a preconfigured success probability value for
// untried hops (AprioriHopProbability) and returns a high success probability
// for hops that could previously conduct a payment (prevSuccessProbability).
// Successful edges are retried until proven otherwise. Recently failed hops are
// penalized by an exponential time decay (PenaltyHalfLife), after which they
// are reconsidered for routing. If information was learned about a forwarding
// node, the information is taken into account to estimate a per node
// probability that mixes with the a priori probability (AprioriWeight).
type AprioriEstimator struct {
// AprioriConfig contains configuration options for our estimator.
AprioriConfig
// prevSuccessProbability is the assumed probability for node pairs that
// successfully relayed the previous attempt.
prevSuccessProbability float64
}
// NewAprioriEstimator creates a new AprioriEstimator.
func NewAprioriEstimator(cfg AprioriConfig) (*AprioriEstimator, error) {
if err := cfg.validate(); err != nil {
return nil, err
}
return &AprioriEstimator{
AprioriConfig: cfg,
prevSuccessProbability: prevSuccessProbability,
}, nil
}
// Compile-time checks that interfaces are implemented.
var _ Estimator = (*AprioriEstimator)(nil)
var _ estimatorConfig = (*AprioriConfig)(nil)
// Config returns the estimator's configuration.
func (p *AprioriEstimator) Config() estimatorConfig {
return p.AprioriConfig
}
// String returns the estimator's configuration as a string representation.
func (p *AprioriEstimator) String() string {
return fmt.Sprintf("estimator type: %v, penalty halflife time: %v, "+
"apriori hop probability: %v, apriori weight: %v, previous "+
"success probability: %v, capacity fraction: %v",
AprioriEstimatorName, p.PenaltyHalfLife,
p.AprioriHopProbability, p.AprioriWeight,
p.prevSuccessProbability, p.CapacityFraction)
}
// getNodeProbability calculates the probability for connections from a node
// that have not been tried before. The results parameter is a list of last
// payment results for that node.
func (p *AprioriEstimator) getNodeProbability(now time.Time,
results NodeResults, amt lnwire.MilliSatoshi,
capacity btcutil.Amount) float64 {
// We reduce the apriori hop probability if the amount comes close to
// the capacity.
apriori := p.AprioriHopProbability * capacityFactor(
amt, capacity, p.CapacityFraction,
)
// If the channel history is not to be taken into account, we can return
// early here with the configured a priori probability.
if p.AprioriWeight == 1 {
return apriori
}
// If there is no channel history, our best estimate is still the a
// priori probability.
if len(results) == 0 {
return apriori
}
// The value of the apriori weight is in the range [0, 1]. Convert it to
// a factor that properly expresses the intention of the weight in the
// following weight average calculation. When the apriori weight is 0,
// the apriori factor is also 0. This means it won't have any effect on
// the weighted average calculation below. When the apriori weight
// approaches 1, the apriori factor goes to infinity. It will heavily
// outweigh any observations that have been collected.
aprioriFactor := 1/(1-p.AprioriWeight) - 1
// Calculate a weighted average consisting of the apriori probability
// and historical observations. This is the part that incentivizes nodes
// to make sure that all (not just some) of their channels are in good
// shape. Senders will steer around nodes that have shown a few
// failures, even though there may be many channels still untried.
//
// If there is just a single observation and the apriori weight is 0,
// this single observation will totally determine the node probability.
// The node probability is returned for all other channels of the node.
// This means that one failure will lead to the success probability
// estimates for all other channels being 0 too. The probability for the
// channel that was tried will not even recover, because it is
// recovering to the node probability (which is zero). So one failure
// effectively prunes all channels of the node forever. This is the most
// aggressive way in which we can penalize nodes and unlikely to yield
// good results in a real network.
probabilitiesTotal := apriori * aprioriFactor
totalWeight := aprioriFactor
for _, result := range results {
switch {
// Weigh success with a constant high weight of 1. There is no
// decay. Amt is never zero, so this clause is never executed
// when result.SuccessAmt is zero.
case amt <= result.SuccessAmt:
totalWeight++
probabilitiesTotal += p.prevSuccessProbability
// Weigh failures in accordance with their age. The base
// probability of a failure is considered zero, so nothing needs
// to be added to probabilitiesTotal.
case !result.FailTime.IsZero() && amt >= result.FailAmt:
age := now.Sub(result.FailTime)
totalWeight += p.getWeight(age)
}
}
return probabilitiesTotal / totalWeight
}
// getWeight calculates a weight in the range [0, 1] that should be assigned to
// a payment result. Weight follows an exponential curve that starts at 1 when
// the result is fresh and asymptotically approaches zero over time. The rate at
// which this happens is controlled by the penaltyHalfLife parameter.
func (p *AprioriEstimator) getWeight(age time.Duration) float64 {
exp := -age.Hours() / p.PenaltyHalfLife.Hours()
return math.Pow(2, exp)
}
// capacityFactor is a multiplier that can be used to reduce the probability
// depending on how much of the capacity is sent. In other words, the factor
// sorts out channels that don't provide enough liquidity. Effectively, this
// leads to usage of larger channels in total to increase success probability,
// but it may also increase fees. The limits are 1 for amt == 0 and
// minCapacityFactor for amt >> capacityCutoffFraction. The function drops
// significantly when amt reaches cutoffMsat. smearingMsat determines over which
// scale the reduction takes place.
func capacityFactor(amt lnwire.MilliSatoshi, capacity btcutil.Amount,
capacityCutoffFraction float64) float64 {
// The special value of 1.0 for capacityFactor disables any effect from
// this factor.
if capacityCutoffFraction == 1 {
return 1.0
}
// If we don't have information about the capacity, which can be the
// case for hop hints or local channels, we return unity to not alter
// anything.
if capacity == 0 {
return 1.0
}
capMsat := float64(lnwire.NewMSatFromSatoshis(capacity))
amtMsat := float64(amt)
if amtMsat > capMsat {
return 0
}
cutoffMsat := capacityCutoffFraction * capMsat
smearingMsat := capacitySmearingFraction * capMsat
// We compute a logistic function mirrored around the y axis, centered
// at cutoffMsat, decaying over the smearingMsat scale.
denominator := 1 + math.Exp(-(amtMsat-cutoffMsat)/smearingMsat)
// The numerator decides what the minimal value of this function will
// be. The minimal value is set by minCapacityFactor.
numerator := 1 - minCapacityFactor
return 1 - numerator/denominator
}
// PairProbability estimates the probability of successfully traversing to
// toNode based on historical payment outcomes for the from node. Those outcomes
// are passed in via the results parameter.
func (p *AprioriEstimator) PairProbability(now time.Time,
results NodeResults, toNode route.Vertex, amt lnwire.MilliSatoshi,
capacity btcutil.Amount) float64 {
nodeProbability := p.getNodeProbability(now, results, amt, capacity)
return p.calculateProbability(
now, results, nodeProbability, toNode, amt,
)
}
// LocalPairProbability estimates the probability of successfully traversing
// our own local channels to toNode.
func (p *AprioriEstimator) LocalPairProbability(
now time.Time, results NodeResults, toNode route.Vertex) float64 {
// For local channels that have never been tried before, we assume them
// to be successful. We have accurate balance and online status
// information on our own channels, so when we select them in a route it
// is close to certain that those channels will work.
nodeProbability := p.prevSuccessProbability
return p.calculateProbability(
now, results, nodeProbability, toNode, lnwire.MaxMilliSatoshi,
)
}
// calculateProbability estimates the probability of successfully traversing to
// toNode based on historical payment outcomes and a fall-back node probability.
func (p *AprioriEstimator) calculateProbability(
now time.Time, results NodeResults,
nodeProbability float64, toNode route.Vertex,
amt lnwire.MilliSatoshi) float64 {
// Retrieve the last pair outcome.
lastPairResult, ok := results[toNode]
// If there is no history for this pair, return the node probability
// that is a probability estimate for untried channel.
if !ok {
return nodeProbability
}
// For successes, we have a fixed (high) probability. Those pairs will
// be assumed good until proven otherwise. Amt is never zero, so this
// clause is never executed when lastPairResult.SuccessAmt is zero.
if amt <= lastPairResult.SuccessAmt {
return p.prevSuccessProbability
}
// Take into account a minimum penalize amount. For balance errors, a
// failure may be reported with such a minimum to prevent too aggressive
// penalization. If the current amount is smaller than the amount that
// previously triggered a failure, we act as if this is an untried
// channel.
if lastPairResult.FailTime.IsZero() || amt < lastPairResult.FailAmt {
return nodeProbability
}
timeSinceLastFailure := now.Sub(lastPairResult.FailTime)
// Calculate success probability based on the weight of the last
// failure. When the failure is fresh, its weight is 1 and we'll return
// probability 0. Over time the probability recovers to the node
// probability. It would be as if this channel was never tried before.
weight := p.getWeight(timeSinceLastFailure)
probability := nodeProbability * (1 - weight)
return probability
}
package routing
import (
"fmt"
"math"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
const (
// DefaultBimodalScaleMsat is the default value for BimodalScaleMsat in
// BimodalConfig. It describes the distribution of funds in the LN based
// on empirical findings. We assume an unbalanced network by default.
DefaultBimodalScaleMsat = lnwire.MilliSatoshi(300_000_000)
// DefaultBimodalNodeWeight is the default value for the
// BimodalNodeWeight in BimodalConfig. It is chosen such that past
// forwardings on other channels of a router are only slightly taken
// into account.
DefaultBimodalNodeWeight = 0.2
// DefaultBimodalDecayTime is the default value for BimodalDecayTime.
// We will forget about previous learnings about channel liquidity on
// the timescale of about a week.
DefaultBimodalDecayTime = 7 * 24 * time.Hour
// BimodalScaleMsatMax is the maximum value for BimodalScaleMsat. We
// limit it here to the fakeHopHintCapacity to avoid issues with hop
// hint probability calculations.
BimodalScaleMsatMax = lnwire.MilliSatoshi(
1000 * fakeHopHintCapacity / 4,
)
// BimodalEstimatorName is used to identify the bimodal estimator.
BimodalEstimatorName = "bimodal"
)
var (
// ErrInvalidScale is returned when we get a scale below or equal zero.
ErrInvalidScale = errors.New("scale must be >= 0 and sane")
// ErrInvalidNodeWeight is returned when we get a node weight that is
// out of range.
ErrInvalidNodeWeight = errors.New("node weight must be in [0, 1]")
// ErrInvalidDecayTime is returned when we get a decay time below zero.
ErrInvalidDecayTime = errors.New("decay time must be larger than zero")
// ErrZeroCapacity is returned when we encounter a channel with zero
// capacity in probability estimation.
ErrZeroCapacity = errors.New("capacity must be larger than zero")
)
// BimodalConfig contains configuration for our probability estimator.
type BimodalConfig struct {
// BimodalNodeWeight defines how strongly other previous forwardings on
// channels of a router should be taken into account when computing a
// channel's probability to route. The allowed values are in the range
// [0, 1], where a value of 0 means that only direct information about a
// channel is taken into account.
BimodalNodeWeight float64
// BimodalScaleMsat describes the scale over which channels
// statistically have some liquidity left. The value determines how
// quickly the bimodal distribution drops off from the edges of a
// channel. A larger value (compared to typical channel capacities)
// means that the drop off is slow and that channel balances are
// distributed more uniformly. A small value leads to the assumption of
// very unbalanced channels.
BimodalScaleMsat lnwire.MilliSatoshi
// BimodalDecayTime is the scale for the exponential information decay
// over time for previous successes or failures.
BimodalDecayTime time.Duration
}
// validate checks the configuration of the estimator for allowed values.
func (p BimodalConfig) validate() error {
if p.BimodalDecayTime <= 0 {
return fmt.Errorf("%v: %w", BimodalEstimatorName,
ErrInvalidDecayTime)
}
if p.BimodalNodeWeight < 0 || p.BimodalNodeWeight > 1 {
return fmt.Errorf("%v: %w", BimodalEstimatorName,
ErrInvalidNodeWeight)
}
if p.BimodalScaleMsat == 0 || p.BimodalScaleMsat > BimodalScaleMsatMax {
return fmt.Errorf("%v: %w", BimodalEstimatorName,
ErrInvalidScale)
}
return nil
}
// DefaultBimodalConfig returns the default configuration for the estimator.
func DefaultBimodalConfig() BimodalConfig {
return BimodalConfig{
BimodalNodeWeight: DefaultBimodalNodeWeight,
BimodalScaleMsat: DefaultBimodalScaleMsat,
BimodalDecayTime: DefaultBimodalDecayTime,
}
}
// BimodalEstimator returns node and pair probabilities based on historical
// payment results based on a liquidity distribution model of the LN. The main
// function is to estimate the direct channel probability based on a depleted
// liquidity distribution model, with additional information decay over time. A
// per-node probability can be mixed with the direct probability, taking into
// account successes/failures on other channels of the forwarder.
type BimodalEstimator struct {
// BimodalConfig contains configuration options for our estimator.
BimodalConfig
}
// NewBimodalEstimator creates a new BimodalEstimator.
func NewBimodalEstimator(cfg BimodalConfig) (*BimodalEstimator, error) {
if err := cfg.validate(); err != nil {
return nil, err
}
return &BimodalEstimator{
BimodalConfig: cfg,
}, nil
}
// Compile-time checks that interfaces are implemented.
var _ Estimator = (*BimodalEstimator)(nil)
var _ estimatorConfig = (*BimodalConfig)(nil)
// config returns the current configuration of the estimator.
func (p *BimodalEstimator) Config() estimatorConfig {
return p.BimodalConfig
}
// String returns the estimator's configuration as a string representation.
func (p *BimodalEstimator) String() string {
return fmt.Sprintf("estimator type: %v, decay time: %v, liquidity "+
"scale: %v, node weight: %v", BimodalEstimatorName,
p.BimodalDecayTime, p.BimodalScaleMsat, p.BimodalNodeWeight)
}
// PairProbability estimates the probability of successfully traversing to
// toNode based on historical payment outcomes for the from node. Those outcomes
// are passed in via the results parameter.
func (p *BimodalEstimator) PairProbability(now time.Time,
results NodeResults, toNode route.Vertex, amt lnwire.MilliSatoshi,
capacity btcutil.Amount) float64 {
// We first compute the probability for the desired hop taking into
// account previous knowledge.
directProbability := p.directProbability(
now, results, toNode, amt, lnwire.NewMSatFromSatoshis(capacity),
)
// The final probability is computed by taking into account other
// channels of the from node.
return p.calculateProbability(directProbability, now, results, toNode)
}
// LocalPairProbability computes the probability to reach toNode given a set of
// previous learnings.
func (p *BimodalEstimator) LocalPairProbability(now time.Time,
results NodeResults, toNode route.Vertex) float64 {
// For direct local probabilities we assume to know exactly how much we
// can send over a channel, which assumes that channels are active and
// have enough liquidity.
directProbability := 1.0
// If we had an unexpected failure for this node, we reduce the
// probability for some time to avoid infinite retries.
result, ok := results[toNode]
if ok && !result.FailTime.IsZero() {
timeAgo := now.Sub(result.FailTime)
// We only expect results in the past to get a probability
// between 0 and 1.
if timeAgo < 0 {
timeAgo = 0
}
exponent := -float64(timeAgo) / float64(p.BimodalDecayTime)
directProbability -= math.Exp(exponent)
}
return directProbability
}
// directProbability computes the probability to reach a node based on the
// liquidity distribution in the LN.
func (p *BimodalEstimator) directProbability(now time.Time,
results NodeResults, toNode route.Vertex, amt lnwire.MilliSatoshi,
capacity lnwire.MilliSatoshi) float64 {
// We first determine the time-adjusted success and failure amounts to
// then compute a probability. We know that we can send a zero amount.
successAmount := lnwire.MilliSatoshi(0)
// We know that we cannot send the full capacity.
failAmount := capacity
// If we have information about past successes or failures, we modify
// them with a time decay.
result, ok := results[toNode]
if ok {
// Apply a time decay for the amount we cannot send.
if !result.FailTime.IsZero() {
failAmount = cannotSend(
result.FailAmt, capacity, now, result.FailTime,
p.BimodalDecayTime,
)
}
// Apply a time decay for the amount we can send.
if !result.SuccessTime.IsZero() {
successAmount = canSend(
result.SuccessAmt, now, result.SuccessTime,
p.BimodalDecayTime,
)
}
}
// Compute the direct channel probability.
probability, err := p.probabilityFormula(
capacity, successAmount, failAmount, amt,
)
if err != nil {
log.Errorf("error computing probability to node: %v "+
"(node: %v, results: %v, amt: %v, capacity: %v)",
err, toNode, results, amt, capacity)
return 0.0
}
return probability
}
// calculateProbability computes the total hop probability combining the channel
// probability and historic forwarding data of other channels of the node we try
// to send from.
//
// Goals:
// * We want to incentivize good routing nodes: the more routable channels a
// node has, the more we want to incentivize (vice versa for failures).
// -> We reduce/increase the direct probability depending on past
// failures/successes for other channels of the node.
//
// * We want to be forgiving/give other nodes a chance as well: we want to
// forget about (non-)routable channels over time.
// -> We weight the successes/failures with a time decay such that they will not
// influence the total probability if a long time went by.
//
// * If we don't have other info, we want to solely rely on the direct
// probability.
//
// * We want to be able to specify how important the other channels are compared
// to the direct channel.
// -> Introduce a node weight factor that weights the direct probability against
// the node-wide average. The larger the node weight, the more important other
// channels of the node are.
//
// How do failures on low fee nodes redirect routing to higher fee nodes?
// Assumptions:
// * attemptCostPPM of 1000 PPM
// * constant direct channel probability of P0 (usually 0.5 for large amounts)
// * node weight w of 0.2
//
// The question we want to answer is:
// How often would a zero-fee node be tried (even if there were failures for its
// other channels) over trying a high-fee node with 2000 PPM and no direct
// knowledge about the channel to send over?
//
// The probability of a route of length l is P(l) = l * P0.
//
// The total probability after n failures (with the implemented method here) is:
// P(l, n) = P(l-1) * P(n)
// = P(l-1) * (P0 + n*0) / (1 + n*w)
// = P(l) / (1 + n*w)
//
// Condition for a high-fee channel to overcome a low fee channel in the
// Dijkstra weight function (only looking at fee and probability PPM terms):
// highFeePPM + attemptCostPPM * 1/P(l) = 0PPM + attemptCostPPM * 1/P(l, n)
// highFeePPM/attemptCostPPM = 1/P(l, n) - 1/P(l) =
// = (1 + n*w)/P(l) - 1/P(l) =
// = n*w/P(l)
//
// Therefore:
// n = (highFeePPM/attemptCostPPM) * (P(l)/w) =
// = (2000/1000) * 0.5 * l / w = l/w
//
// For a one-hop route we get:
// n = 1/0.2 = 5 tolerated failures
//
// For a three-hop route we get:
// n = 3/0.2 = 15 tolerated failures
//
// For more details on the behavior see tests.
func (p *BimodalEstimator) calculateProbability(directProbability float64,
now time.Time, results NodeResults, toNode route.Vertex) float64 {
// If we don't take other channels into account, we can return early.
if p.BimodalNodeWeight == 0.0 {
return directProbability
}
// If we have up-to-date information about the channel we want to use,
// i.e. the info stems from results not longer ago than the decay time,
// we will only use the direct probability. This is needed in order to
// avoid that other previous results (on all other channels of the same
// routing node) will distort and pin the calculated probability even if
// we have accurate direct information. This helps to dip the
// probability below the min probability in case of failures, to start
// the splitting process.
directResult, ok := results[toNode]
if ok {
latest := directResult.SuccessTime
if directResult.FailTime.After(latest) {
latest = directResult.FailTime
}
// We use BimonodalDecayTime to judge the currentness of the
// data. It is the time scale on which we assume to have lost
// information.
if now.Sub(latest) < p.BimodalDecayTime {
log.Tracef("Using direct probability for node %v: %v",
toNode, directResult)
return directProbability
}
}
// w is a parameter which determines how strongly the other channels of
// a node should be incorporated, the higher the stronger.
w := p.BimodalNodeWeight
// dt determines the timeliness of the previous successes/failures
// to be taken into account.
dt := float64(p.BimodalDecayTime)
// The direct channel probability is weighted fully, all other results
// are weighted according to how recent the information is.
totalProbabilities := directProbability
totalWeights := 1.0
for peer, result := range results {
// We don't include the direct hop probability here because it
// is already included in totalProbabilities.
if peer == toNode {
continue
}
// We add probabilities weighted by how recent the info is.
var weight float64
if result.SuccessAmt > 0 {
exponent := -float64(now.Sub(result.SuccessTime)) / dt
weight = math.Exp(exponent)
totalProbabilities += w * weight
totalWeights += w * weight
}
if result.FailAmt > 0 {
exponent := -float64(now.Sub(result.FailTime)) / dt
weight = math.Exp(exponent)
// Failures don't add to total success probability.
totalWeights += w * weight
}
}
return totalProbabilities / totalWeights
}
// canSend returns the sendable amount over the channel, respecting time decay.
// canSend approaches zero, if we wait for a much longer time than the decay
// time.
func canSend(successAmount lnwire.MilliSatoshi, now, successTime time.Time,
decayConstant time.Duration) lnwire.MilliSatoshi {
// The factor approaches 0 for successTime a long time in the past,
// is 1 when the successTime is now.
factor := math.Exp(
-float64(now.Sub(successTime)) / float64(decayConstant),
)
canSend := factor * float64(successAmount)
return lnwire.MilliSatoshi(canSend)
}
// cannotSend returns the not sendable amount over the channel, respecting time
// decay. cannotSend approaches the capacity, if we wait for a much longer time
// than the decay time.
func cannotSend(failAmount, capacity lnwire.MilliSatoshi, now,
failTime time.Time, decayConstant time.Duration) lnwire.MilliSatoshi {
if failAmount > capacity {
failAmount = capacity
}
// The factor approaches 0 for failTime a long time in the past and it
// is 1 when the failTime is now.
factor := math.Exp(
-float64(now.Sub(failTime)) / float64(decayConstant),
)
cannotSend := capacity - lnwire.MilliSatoshi(
factor*float64(capacity-failAmount),
)
return cannotSend
}
// primitive computes the indefinite integral of our assumed (normalized)
// liquidity probability distribution. The distribution of liquidity x here is
// the function P(x) ~ exp(-x/s) + exp((x-c)/s), i.e., two exponentials residing
// at the ends of channels. This means that we expect liquidity to be at either
// side of the channel with capacity c. The s parameter (scale) defines how far
// the liquidity leaks into the channel. A very low scale assumes completely
// unbalanced channels, a very high scale assumes a random distribution. More
// details can be found in
// https://github.com/lightningnetwork/lnd/issues/5988#issuecomment-1131234858.
func (p *BimodalEstimator) primitive(c, x float64) float64 {
s := float64(p.BimodalScaleMsat)
// The indefinite integral of P(x) is given by
// Int P(x) dx = H(x) = s * (-e(-x/s) + e((x-c)/s)),
// and its norm from 0 to c can be computed from it,
// norm = [H(x)]_0^c = s * (-e(-c/s) + 1 -(1 + e(-c/s))).
ecs := math.Exp(-c / s)
exs := math.Exp(-x / s)
// It would be possible to split the next term and reuse the factors
// from before, but this can lead to numerical issues with large
// numbers.
excs := math.Exp((x - c) / s)
// norm can only become zero, if c is zero, which we sorted out before
// calling this method.
norm := -2*ecs + 2
// We end up with the primitive function of the normalized P(x).
return (-exs + excs) / norm
}
// integral computes the integral of our liquidity distribution from the lower
// to the upper value.
func (p *BimodalEstimator) integral(capacity, lower, upper float64) float64 {
if lower < 0 || lower > upper {
log.Errorf("probability integral limits nonsensical: capacity:"+
"%v lower: %v upper: %v", capacity, lower, upper)
return 0.0
}
return p.primitive(capacity, upper) - p.primitive(capacity, lower)
}
// probabilityFormula computes the expected probability for a payment of
// amountMsat given prior learnings for a channel of certain capacity.
// successAmountMsat and failAmountMsat stand for the unsettled success and
// failure amounts, respectively. The formula is derived using the formalism
// presented in Pickhardt et al., https://arxiv.org/abs/2103.08576.
func (p *BimodalEstimator) probabilityFormula(capacityMsat, successAmountMsat,
failAmountMsat, amountMsat lnwire.MilliSatoshi) (float64, error) {
// Convert to positive-valued floats.
capacity := float64(capacityMsat)
successAmount := float64(successAmountMsat)
failAmount := float64(failAmountMsat)
amount := float64(amountMsat)
// In order for this formula to give reasonable results, we need to have
// an estimate of the capacity of a channel (or edge between nodes).
if capacity == 0.0 {
return 0, ErrZeroCapacity
}
// We cannot send more than the capacity.
if amount > capacity {
return 0.0, nil
}
// Mission control may have some outdated values, we correct them here.
// TODO(bitromortac): there may be better decisions to make in these
// cases, e.g., resetting failAmount=cap and successAmount=0.
// failAmount should be capacity at max.
if failAmount > capacity {
log.Debugf("Correcting failAmount %v to capacity %v",
failAmount, capacity)
failAmount = capacity
}
// successAmount should be capacity at max.
if successAmount > capacity {
log.Debugf("Correcting successAmount %v to capacity %v",
successAmount, capacity)
successAmount = capacity
}
// The next statement is a safety check against an illogical condition,
// otherwise the renormalization integral would become zero. This may
// happen if a large channel gets closed and smaller ones remain, but
// it should recover with the time decay.
if failAmount <= successAmount {
log.Tracef("fail amount (%v) is smaller than or equal the "+
"success amount (%v) for capacity (%v)",
failAmountMsat, successAmountMsat, capacityMsat)
return 0.0, nil
}
// We cannot send more than the fail amount.
if amount >= failAmount {
return 0.0, nil
}
// The success probability for payment amount a is the integral over the
// prior distribution P(x), the probability to find liquidity between
// the amount a and channel capacity c (or failAmount a_f):
// P(X >= a | X < a_f) = Integral_{a}^{a_f} P(x) dx
prob := p.integral(capacity, amount, failAmount)
if math.IsNaN(prob) {
return 0.0, fmt.Errorf("non-normalized probability is NaN, "+
"capacity: %v, amount: %v, fail amount: %v",
capacity, amount, failAmount)
}
// If we have payment information, we need to adjust the prior
// distribution P(x) and get the posterior distribution by renormalizing
// the prior distribution in such a way that the probability mass lies
// between a_s and a_f.
reNorm := p.integral(capacity, successAmount, failAmount)
if math.IsNaN(reNorm) {
return 0.0, fmt.Errorf("normalization factor is NaN, "+
"capacity: %v, success amount: %v, fail amount: %v",
capacity, successAmount, failAmount)
}
// The normalization factor can only be zero if the success amount is
// equal or larger than the fail amount. This should not happen as we
// have checked this scenario above.
if reNorm == 0.0 {
return 0.0, fmt.Errorf("normalization factor is zero, "+
"capacity: %v, success amount: %v, fail amount: %v",
capacity, successAmount, failAmount)
}
prob /= reNorm
// Note that for payment amounts smaller than successAmount, we can get
// a value larger than unity, which we cap here to get a proper
// probability.
if prob > 1.0 {
if amount > successAmount {
return 0.0, fmt.Errorf("unexpected large probability "+
"(%v) capacity: %v, amount: %v, success "+
"amount: %v, fail amount: %v", prob, capacity,
amount, successAmount, failAmount)
}
return 1.0, nil
} else if prob < 0.0 {
return 0.0, fmt.Errorf("negative probability "+
"(%v) capacity: %v, amount: %v, success "+
"amount: %v, fail amount: %v", prob, capacity,
amount, successAmount, failAmount)
}
return prob, nil
}
package routing
import (
"bytes"
"fmt"
"io"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/tlv"
)
// Instantiate variables to allow taking a reference from the failure reason.
var (
reasonError = channeldb.FailureReasonError
reasonIncorrectDetails = channeldb.FailureReasonPaymentDetails
)
// pairResult contains the result of the interpretation of a payment attempt for
// a specific node pair.
type pairResult struct {
// amt is the amount that was forwarded for this pair. Can be set to
// zero for failures that are amount independent.
amt lnwire.MilliSatoshi
// success indicates whether the payment attempt was successful through
// this pair.
success bool
}
// failPairResult creates a new result struct for a failure.
func failPairResult(minPenalizeAmt lnwire.MilliSatoshi) pairResult {
return pairResult{
amt: minPenalizeAmt,
}
}
// newSuccessPairResult creates a new result struct for a success.
func successPairResult(successAmt lnwire.MilliSatoshi) pairResult {
return pairResult{
success: true,
amt: successAmt,
}
}
// String returns the human-readable representation of a pair result.
func (p pairResult) String() string {
var resultType string
if p.success {
resultType = "success"
} else {
resultType = "failed"
}
return fmt.Sprintf("%v (amt=%v)", resultType, p.amt)
}
// interpretedResult contains the result of the interpretation of a payment
// attempt.
type interpretedResult struct {
// nodeFailure points to a node pubkey if all channels of that node are
// responsible for the result.
nodeFailure *route.Vertex
// pairResults contains a map of node pairs for which we have a result.
pairResults map[DirectedNodePair]pairResult
// finalFailureReason is set to a non-nil value if it makes no more
// sense to start another payment attempt. It will contain the reason
// why.
finalFailureReason *channeldb.FailureReason
// policyFailure is set to a node pair if there is a policy failure on
// that connection. This is used to control the second chance logic for
// policy failures.
policyFailure *DirectedNodePair
}
// interpretResult interprets a payment outcome and returns an object that
// contains information required to update mission control.
func interpretResult(rt *mcRoute,
failure fn.Option[paymentFailure]) *interpretedResult {
i := &interpretedResult{
pairResults: make(map[DirectedNodePair]pairResult),
}
return fn.ElimOption(failure, func() *interpretedResult {
i.processSuccess(rt)
return i
}, func(info paymentFailure) *interpretedResult {
i.processFail(rt, info)
return i
})
}
// processSuccess processes a successful payment attempt.
func (i *interpretedResult) processSuccess(route *mcRoute) {
// For successes, all nodes must have acted in the right way.
// Therefore we mark all of them with a success result. However we need
// to handle the blinded route part separately because for intermediate
// blinded nodes the amount field is set to zero so we use the receiver
// amount.
introIdx, isBlinded := introductionPointIndex(route)
if isBlinded {
// Report success for all the pairs until the introduction
// point.
i.successPairRange(route, 0, introIdx-1)
// Handle the blinded route part.
//
// NOTE: The introIdx index here does describe the node after
// the introduction point.
i.markBlindedRouteSuccess(route, introIdx)
return
}
// Mark nodes as successful in the non-blinded case of the payment.
i.successPairRange(route, 0, len(route.hops.Val)-1)
}
// processFail processes a failed payment attempt.
func (i *interpretedResult) processFail(rt *mcRoute, failure paymentFailure) {
if failure.info.IsNone() {
i.processPaymentOutcomeUnknown(rt)
return
}
var (
idx int
failMsg lnwire.FailureMessage
)
failure.info.WhenSome(
func(r tlv.RecordT[tlv.TlvType0, paymentFailureInfo]) {
idx = int(r.Val.sourceIdx.Val)
failMsg = r.Val.msg.Val.FailureMessage
},
)
// If the payment was to a blinded route and we received an error from
// after the introduction point, handle this error separately - there
// has been a protocol violation from the introduction node. This
// penalty applies regardless of the error code that is returned.
introIdx, isBlinded := introductionPointIndex(rt)
if isBlinded && introIdx < idx {
i.processPaymentOutcomeBadIntro(rt, introIdx, idx)
return
}
switch idx {
// We are the source of the failure.
case 0:
i.processPaymentOutcomeSelf(rt, failMsg)
// A failure from the final hop was received.
case len(rt.hops.Val):
i.processPaymentOutcomeFinal(rt, failMsg)
// An intermediate hop failed. Interpret the outcome, update reputation
// and try again.
default:
i.processPaymentOutcomeIntermediate(rt, idx, failMsg)
}
}
// processPaymentOutcomeBadIntro handles the case where we have made payment
// to a blinded route, but received an error from a node after the introduction
// node. This indicates that the introduction node is not obeying the route
// blinding specification, as we expect all errors from the introduction node
// to be source from it.
func (i *interpretedResult) processPaymentOutcomeBadIntro(route *mcRoute,
introIdx, errSourceIdx int) {
// We fail the introduction node for not obeying the specification.
i.failNode(route, introIdx)
// Other preceding channels in the route forwarded correctly. Note
// that we do not assign success to the incoming link to the
// introduction node because it has not handled the error correctly.
if introIdx > 1 {
i.successPairRange(route, 0, introIdx-2)
}
// If the source of the failure was from the final node, we also set
// a final failure reason because the recipient can't process the
// payment (independent of the introduction failing to convert the
// error, we can't complete the payment if the last hop fails).
if errSourceIdx == len(route.hops.Val) {
i.finalFailureReason = &reasonError
}
}
// processPaymentOutcomeSelf handles failures sent by ourselves.
func (i *interpretedResult) processPaymentOutcomeSelf(rt *mcRoute,
failure lnwire.FailureMessage) {
switch failure.(type) {
// We receive a malformed htlc failure from our peer. We trust ourselves
// to send the correct htlc, so our peer must be at fault.
case *lnwire.FailInvalidOnionVersion,
*lnwire.FailInvalidOnionHmac,
*lnwire.FailInvalidOnionKey:
i.failNode(rt, 1)
// If this was a payment to a direct peer, we can stop trying.
if len(rt.hops.Val) == 1 {
i.finalFailureReason = &reasonError
}
// Any other failure originating from ourselves should be temporary and
// caused by changing conditions between path finding and execution of
// the payment. We just retry and trust that the information locally
// available in the link has been updated.
default:
log.Warnf("Routing failure for local channel %v occurred",
rt.hops.Val[0].channelID)
}
}
// processPaymentOutcomeFinal handles failures sent by the final hop.
func (i *interpretedResult) processPaymentOutcomeFinal(route *mcRoute,
failure lnwire.FailureMessage) {
n := len(route.hops.Val)
failNode := func() {
i.failNode(route, n)
// Other channels in the route forwarded correctly.
if n > 1 {
i.successPairRange(route, 0, n-2)
}
i.finalFailureReason = &reasonError
}
// If a failure from the final node is received, we will fail the
// payment in almost all cases. Only when the penultimate node sends an
// incorrect htlc, we want to retry via another route. Invalid onion
// failures are not expected, because the final node wouldn't be able to
// encrypt that failure.
switch failure.(type) {
// Expiry or amount of the HTLC doesn't match the onion, try another
// route.
case *lnwire.FailFinalIncorrectCltvExpiry,
*lnwire.FailFinalIncorrectHtlcAmount:
// We trust ourselves. If this is a direct payment, we penalize
// the final node and fail the payment.
if n == 1 {
i.failNode(route, n)
i.finalFailureReason = &reasonError
return
}
// Otherwise penalize the last pair of the route and retry.
// Either the final node is at fault, or it gets sent a bad htlc
// from its predecessor.
i.failPair(route, n-1)
// The other hops relayed correctly, so assign those pairs a
// success result. At this point, n >= 2.
i.successPairRange(route, 0, n-2)
// We are using wrong payment hash or amount, fail the payment.
case *lnwire.FailIncorrectPaymentAmount,
*lnwire.FailIncorrectDetails:
// Assign all pairs a success result, as the payment reached the
// destination correctly.
i.successPairRange(route, 0, n-1)
i.finalFailureReason = &reasonIncorrectDetails
// The HTLC that was extended to the final hop expires too soon. Fail
// the payment, because we may be using the wrong final cltv delta.
case *lnwire.FailFinalExpiryTooSoon:
// TODO(roasbeef): can happen to to race condition, try again
// with recent block height
// TODO(joostjager): can also happen because a node delayed
// deliberately. What to penalize?
i.finalFailureReason = &reasonIncorrectDetails
case *lnwire.FailMPPTimeout:
// Assign all pairs a success result, as the payment reached the
// destination correctly. Continue the payment process.
i.successPairRange(route, 0, n-1)
// We do not expect to receive an invalid blinding error from the final
// node in the route. This could erroneously happen in the following
// cases:
// 1. Unblinded node: misuses the error code.
// 2. A receiving introduction node: erroneously sends the error code,
// as the spec indicates that receiving introduction nodes should
// use regular errors.
//
// Note that we expect the case where this error is sent from a node
// after the introduction node to be handled elsewhere as this is part
// of a more general class of errors where the introduction node has
// failed to convert errors for the blinded route.
case *lnwire.FailInvalidBlinding:
failNode()
// All other errors are considered terminal if coming from the
// final hop. They indicate that something is wrong at the
// recipient, so we do apply a penalty.
default:
failNode()
}
}
// processPaymentOutcomeIntermediate handles failures sent by an intermediate
// hop.
//
//nolint:funlen
func (i *interpretedResult) processPaymentOutcomeIntermediate(route *mcRoute,
errorSourceIdx int, failure lnwire.FailureMessage) {
reportOutgoing := func() {
i.failPair(
route, errorSourceIdx,
)
}
reportOutgoingBalance := func() {
i.failPairBalance(
route, errorSourceIdx,
)
// All nodes up to the failing pair must have forwarded
// successfully.
i.successPairRange(route, 0, errorSourceIdx-1)
}
reportIncoming := func() {
// We trust ourselves. If the error comes from the first hop, we
// can penalize the whole node. In that case there is no
// uncertainty as to which node to blame.
if errorSourceIdx == 1 {
i.failNode(route, errorSourceIdx)
return
}
// Otherwise report the incoming pair.
i.failPair(
route, errorSourceIdx-1,
)
// All nodes up to the failing pair must have forwarded
// successfully.
if errorSourceIdx > 1 {
i.successPairRange(route, 0, errorSourceIdx-2)
}
}
reportNode := func() {
// Fail only the node that reported the failure.
i.failNode(route, errorSourceIdx)
// Other preceding channels in the route forwarded correctly.
if errorSourceIdx > 1 {
i.successPairRange(route, 0, errorSourceIdx-2)
}
}
reportAll := func() {
// We trust ourselves. If the error comes from the first hop, we
// can penalize the whole node. In that case there is no
// uncertainty as to which node to blame.
if errorSourceIdx == 1 {
i.failNode(route, errorSourceIdx)
return
}
// Otherwise penalize all pairs up to the error source. This
// includes our own outgoing connection.
i.failPairRange(
route, 0, errorSourceIdx-1,
)
}
switch failure.(type) {
// If a node reports onion payload corruption or an invalid version,
// that node may be responsible, but it could also be that it is just
// relaying a malformed htlc failure from it successor. By reporting the
// outgoing channel set, we will surely hit the responsible node. At
// this point, it is not possible that the node's predecessor corrupted
// the onion blob. If the predecessor would have corrupted the payload,
// the error source wouldn't have been able to encrypt this failure
// message for us.
case *lnwire.FailInvalidOnionVersion,
*lnwire.FailInvalidOnionHmac,
*lnwire.FailInvalidOnionKey:
reportOutgoing()
// If InvalidOnionPayload is received, we penalize only the reporting
// node. We know the preceding hop didn't corrupt the onion, since the
// reporting node is able to send the failure. We assume that we
// constructed a valid onion payload and that the failure is most likely
// an unknown required type or a bug in their implementation.
case *lnwire.InvalidOnionPayload:
reportNode()
// If the next hop in the route wasn't known or offline, we'll only
// penalize the channel set which we attempted to route over. This is
// conservative, and it can handle faulty channels between nodes
// properly. Additionally, this guards against routing nodes returning
// errors in order to attempt to black list another node.
case *lnwire.FailUnknownNextPeer:
reportOutgoing()
// Some implementations use this error when the next hop is offline, so we
// do the same as FailUnknownNextPeer and also process the channel update.
case *lnwire.FailChannelDisabled:
// Set the node pair for which a channel update may be out of
// date. The second chance logic uses the policyFailure field.
i.policyFailure = &DirectedNodePair{
From: route.hops.Val[errorSourceIdx-1].pubKeyBytes.Val,
To: route.hops.Val[errorSourceIdx].pubKeyBytes.Val,
}
reportOutgoing()
// All nodes up to the failing pair must have forwarded
// successfully.
i.successPairRange(route, 0, errorSourceIdx-1)
// If we get a permanent channel, we'll prune the channel set in both
// directions and continue with the rest of the routes.
case *lnwire.FailPermanentChannelFailure:
reportOutgoing()
// When an HTLC parameter is incorrect, the node sending the error may
// be doing something wrong. But it could also be that its predecessor
// is intentionally modifying the htlc parameters that we instructed it
// via the hop payload. Therefore we penalize the incoming node pair. A
// third cause of this error may be that we have an out of date channel
// update. This is handled by the second chance logic up in mission
// control.
case *lnwire.FailAmountBelowMinimum,
*lnwire.FailFeeInsufficient,
*lnwire.FailIncorrectCltvExpiry:
// Set the node pair for which a channel update may be out of
// date. The second chance logic uses the policyFailure field.
i.policyFailure = &DirectedNodePair{
From: route.hops.Val[errorSourceIdx-1].pubKeyBytes.Val,
To: route.hops.Val[errorSourceIdx].pubKeyBytes.Val,
}
// We report incoming channel. If a second pair is granted in
// mission control, this report is ignored.
reportIncoming()
// If the outgoing channel doesn't have enough capacity, we penalize.
// But we penalize only in a single direction and only for amounts
// greater than the attempted amount.
case *lnwire.FailTemporaryChannelFailure:
reportOutgoingBalance()
// If FailExpiryTooSoon is received, there must have been some delay
// along the path. We can't know which node is causing the delay, so we
// penalize all of them up to the error source.
//
// Alternatively it could also be that we ourselves have fallen behind
// somehow. We ignore that case for now.
case *lnwire.FailExpiryTooSoon:
reportAll()
// We only expect to get FailInvalidBlinding from an introduction node
// in a blinded route. The introduction node in a blinded route is
// always responsible for reporting errors for the blinded portion of
// the route (to protect the privacy of the members of the route), so
// we need to be careful not to unfairly "shoot the messenger".
//
// The introduction node has no incentive to falsely report errors to
// sabotage the blinded route because:
// 1. Its ability to route this payment is strictly tied to the
// blinded route.
// 2. The pubkeys in the blinded route are ephemeral, so doing so
// will have no impact on the nodes beyond the individual payment.
//
// Here we handle a few cases where we could unexpectedly receive this
// error:
// 1. Outside of a blinded route: erring node is not spec compliant.
// 2. Before the introduction point: erring node is not spec compliant.
//
// Note that we expect the case where this error is sent from a node
// after the introduction node to be handled elsewhere as this is part
// of a more general class of errors where the introduction node has
// failed to convert errors for the blinded route.
case *lnwire.FailInvalidBlinding:
introIdx, isBlinded := introductionPointIndex(route)
// Deal with cases where a node has incorrectly returned a
// blinding error:
// 1. A node before the introduction point returned it.
// 2. A node in a non-blinded route returned it.
if errorSourceIdx < introIdx || !isBlinded {
reportNode()
return
}
// Otherwise, the error was at the introduction node. All
// nodes up until the introduction node forwarded correctly,
// so we award them as successful.
if introIdx >= 1 {
i.successPairRange(route, 0, introIdx-1)
}
// If the hop after the introduction node that sent us an
// error is the final recipient, then we finally fail the
// payment because the receiver has generated a blinded route
// that they're unable to use. We have this special case so
// that we don't penalize the introduction node, and there is
// no point in retrying the payment while LND only supports
// one blinded route per payment.
//
// Note that if LND is extended to support multiple blinded
// routes, this will terminate the payment without re-trying
// the other routes.
if introIdx == len(route.hops.Val)-1 {
i.finalFailureReason = &reasonError
} else {
// We penalize the final hop of the blinded route which
// is sufficient to not reuse this route again and is
// also more memory efficient because the other hops
// of the blinded path are ephemeral and will only be
// used in conjunction with the final hop. Moreover we
// don't want to punish the introduction node because
// the blinded failure does not necessarily mean that
// the introduction node was at fault.
//
// TODO(ziggie): Make sure we only keep mc data for
// blinded paths, in both the success and failure case,
// in memory during the time of the payment and remove
// it afterwards. Blinded paths and their blinded hop
// keys are always changing per blinded route so there
// is no point in persisting this data.
i.failBlindedRoute(route)
}
// In all other cases, we penalize the reporting node. These are all
// failures that should not happen.
default:
i.failNode(route, errorSourceIdx)
}
}
// introductionPointIndex returns the index of an introduction point in a
// route, using the same indexing in the route that we use for errorSourceIdx
// (i.e., that we consider our own node to be at index zero). A boolean is
// returned to indicate whether the route contains a blinded portion at all.
func introductionPointIndex(route *mcRoute) (int, bool) {
for i, hop := range route.hops.Val {
if hop.hasBlindingPoint.IsSome() {
return i + 1, true
}
}
return 0, false
}
// processPaymentOutcomeUnknown processes a payment outcome for which no failure
// message or source is available.
func (i *interpretedResult) processPaymentOutcomeUnknown(route *mcRoute) {
n := len(route.hops.Val)
// If this is a direct payment, the destination must be at fault.
if n == 1 {
i.failNode(route, n)
i.finalFailureReason = &reasonError
return
}
// Otherwise penalize all channels in the route to make sure the
// responsible node is at least hit too. We even penalize the connection
// to our own peer, because that peer could also be responsible.
i.failPairRange(route, 0, n-1)
}
// extractMCRoute extracts the fields required by MC from the Route struct to
// create the more minimal mcRoute struct.
func extractMCRoute(r *route.Route) *mcRoute {
return &mcRoute{
sourcePubKey: tlv.NewRecordT[tlv.TlvType0](r.SourcePubKey),
totalAmount: tlv.NewRecordT[tlv.TlvType1](r.TotalAmount),
hops: tlv.NewRecordT[tlv.TlvType2](
extractMCHops(r.Hops),
),
}
}
// extractMCHops extracts the Hop fields that MC actually uses from a slice of
// Hops.
func extractMCHops(hops []*route.Hop) mcHops {
return fn.Map(hops, extractMCHop)
}
// extractMCHop extracts the Hop fields that MC actually uses from a Hop.
func extractMCHop(hop *route.Hop) *mcHop {
h := mcHop{
channelID: tlv.NewPrimitiveRecord[tlv.TlvType0](
hop.ChannelID,
),
pubKeyBytes: tlv.NewRecordT[tlv.TlvType1](hop.PubKeyBytes),
amtToFwd: tlv.NewRecordT[tlv.TlvType2](hop.AmtToForward),
}
if hop.BlindingPoint != nil {
h.hasBlindingPoint = tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType3](lnwire.TrueBoolean{}),
)
}
if hop.CustomRecords != nil {
h.hasCustomRecords = tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType4](lnwire.TrueBoolean{}),
)
}
return &h
}
// mcRoute holds the bare minimum info about a payment attempt route that MC
// requires.
type mcRoute struct {
sourcePubKey tlv.RecordT[tlv.TlvType0, route.Vertex]
totalAmount tlv.RecordT[tlv.TlvType1, lnwire.MilliSatoshi]
hops tlv.RecordT[tlv.TlvType2, mcHops]
}
// Record returns a TLV record that can be used to encode/decode an mcRoute
// to/from a TLV stream.
func (r *mcRoute) Record() tlv.Record {
recordSize := func() uint64 {
var (
b bytes.Buffer
buf [8]byte
)
if err := encodeMCRoute(&b, r, &buf); err != nil {
panic(err)
}
return uint64(len(b.Bytes()))
}
return tlv.MakeDynamicRecord(
0, r, recordSize, encodeMCRoute, decodeMCRoute,
)
}
func encodeMCRoute(w io.Writer, val interface{}, _ *[8]byte) error {
if v, ok := val.(*mcRoute); ok {
return serializeRoute(w, v)
}
return tlv.NewTypeForEncodingErr(val, "routing.mcRoute")
}
func decodeMCRoute(r io.Reader, val interface{}, _ *[8]byte, l uint64) error {
if v, ok := val.(*mcRoute); ok {
route, err := deserializeRoute(io.LimitReader(r, int64(l)))
if err != nil {
return err
}
*v = *route
return nil
}
return tlv.NewTypeForDecodingErr(val, "routing.mcRoute", l, l)
}
// mcHops is a list of mcHop records.
type mcHops []*mcHop
// Record returns a TLV record that can be used to encode/decode a list of
// mcHop to/from a TLV stream.
func (h *mcHops) Record() tlv.Record {
recordSize := func() uint64 {
var (
b bytes.Buffer
buf [8]byte
)
if err := encodeMCHops(&b, h, &buf); err != nil {
panic(err)
}
return uint64(len(b.Bytes()))
}
return tlv.MakeDynamicRecord(
0, h, recordSize, encodeMCHops, decodeMCHops,
)
}
func encodeMCHops(w io.Writer, val interface{}, buf *[8]byte) error {
if v, ok := val.(*mcHops); ok {
// Encode the number of hops as a var int.
if err := tlv.WriteVarInt(w, uint64(len(*v)), buf); err != nil {
return err
}
// With that written out, we'll now encode the entries
// themselves as a sub-TLV record, which includes its _own_
// inner length prefix.
for _, hop := range *v {
var hopBytes bytes.Buffer
if err := serializeHop(&hopBytes, hop); err != nil {
return err
}
// We encode the record with a varint length followed by
// the _raw_ TLV bytes.
tlvLen := uint64(len(hopBytes.Bytes()))
if err := tlv.WriteVarInt(w, tlvLen, buf); err != nil {
return err
}
if _, err := w.Write(hopBytes.Bytes()); err != nil {
return err
}
}
return nil
}
return tlv.NewTypeForEncodingErr(val, "routing.mcHops")
}
func decodeMCHops(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
if v, ok := val.(*mcHops); ok {
// First, we'll decode the varint that encodes how many hops
// are encoded in the stream.
numHops, err := tlv.ReadVarInt(r, buf)
if err != nil {
return err
}
// Now that we know how many records we'll need to read, we can
// iterate and read them all out in series.
for i := uint64(0); i < numHops; i++ {
// Read out the varint that encodes the size of this
// inner TLV record.
hopSize, err := tlv.ReadVarInt(r, buf)
if err != nil {
return err
}
// Using this information, we'll create a new limited
// reader that'll return an EOF once the end has been
// reached so the stream stops consuming bytes.
innerTlvReader := &io.LimitedReader{
R: r,
N: int64(hopSize),
}
hop, err := deserializeHop(innerTlvReader)
if err != nil {
return err
}
*v = append(*v, hop)
}
return nil
}
return tlv.NewTypeForDecodingErr(val, "routing.mcHops", l, l)
}
// mcHop holds the bare minimum info about a payment attempt route hop that MC
// requires.
type mcHop struct {
channelID tlv.RecordT[tlv.TlvType0, uint64]
pubKeyBytes tlv.RecordT[tlv.TlvType1, route.Vertex]
amtToFwd tlv.RecordT[tlv.TlvType2, lnwire.MilliSatoshi]
hasBlindingPoint tlv.OptionalRecordT[tlv.TlvType3, lnwire.TrueBoolean]
hasCustomRecords tlv.OptionalRecordT[tlv.TlvType4, lnwire.TrueBoolean]
}
// failNode marks the node indicated by idx in the route as failed. It also
// marks the incoming and outgoing channels of the node as failed. This function
// intentionally panics when the self node is failed.
func (i *interpretedResult) failNode(rt *mcRoute, idx int) {
// Mark the node as failing.
i.nodeFailure = &rt.hops.Val[idx-1].pubKeyBytes.Val
// Mark the incoming connection as failed for the node. We intent to
// penalize as much as we can for a node level failure, including future
// outgoing traffic for this connection. The pair as it is returned by
// getPair is penalized in the original and the reversed direction. Note
// that this will also affect the score of the failing node's peers.
// This is necessary to prevent future routes from keep going into the
// same node again.
incomingChannelIdx := idx - 1
inPair, _ := getPair(rt, incomingChannelIdx)
i.pairResults[inPair] = failPairResult(0)
i.pairResults[inPair.Reverse()] = failPairResult(0)
// If not the ultimate node, mark the outgoing connection as failed for
// the node.
if idx < len(rt.hops.Val) {
outgoingChannelIdx := idx
outPair, _ := getPair(rt, outgoingChannelIdx)
i.pairResults[outPair] = failPairResult(0)
i.pairResults[outPair.Reverse()] = failPairResult(0)
}
}
// failPairRange marks the node pairs from node fromIdx to node toIdx as failed
// in both direction.
func (i *interpretedResult) failPairRange(rt *mcRoute, fromIdx, toIdx int) {
for idx := fromIdx; idx <= toIdx; idx++ {
i.failPair(rt, idx)
}
}
// failPair marks a pair as failed in both directions.
func (i *interpretedResult) failPair(rt *mcRoute, idx int) {
pair, _ := getPair(rt, idx)
// Report pair in both directions without a minimum penalization amount.
i.pairResults[pair] = failPairResult(0)
i.pairResults[pair.Reverse()] = failPairResult(0)
}
// failPairBalance marks a pair as failed with a minimum penalization amount.
func (i *interpretedResult) failPairBalance(rt *mcRoute, channelIdx int) {
pair, amt := getPair(rt, channelIdx)
i.pairResults[pair] = failPairResult(amt)
}
// successPairRange marks the node pairs from node fromIdx to node toIdx as
// succeeded.
func (i *interpretedResult) successPairRange(rt *mcRoute, fromIdx, toIdx int) {
for idx := fromIdx; idx <= toIdx; idx++ {
pair, amt := getPair(rt, idx)
i.pairResults[pair] = successPairResult(amt)
}
}
// failBlindedRoute marks a blinded route as failed for the specific amount to
// send by only punishing the last pair.
func (i *interpretedResult) failBlindedRoute(rt *mcRoute) {
// We fail the last pair of the route, in order to fail the complete
// blinded route. This is because the combination of ephemeral pubkeys
// is unique to the route. We fail the last pair in order to not punish
// the introduction node, since we don't want to disincentivize them
// from providing that service.
pair, _ := getPair(rt, len(rt.hops.Val)-1)
// Since all the hops along a blinded path don't have any amount set, we
// extract the minimal amount to punish from the value that is tried to
// be sent to the receiver.
amt := rt.hops.Val[len(rt.hops.Val)-1].amtToFwd.Val
i.pairResults[pair] = failPairResult(amt)
}
// markBlindedRouteSuccess marks the hops of the blinded route AFTER the
// introduction node as successful.
//
// NOTE: The introIdx must be the index of the first hop of the blinded route
// AFTER the introduction node.
func (i *interpretedResult) markBlindedRouteSuccess(rt *mcRoute, introIdx int) {
// For blinded hops we do not have the forwarding amount so we take the
// minimal amount which went through the route by looking at the last
// hop.
successAmt := rt.hops.Val[len(rt.hops.Val)-1].amtToFwd.Val
for idx := introIdx; idx < len(rt.hops.Val); idx++ {
pair, _ := getPair(rt, idx)
i.pairResults[pair] = successPairResult(successAmt)
}
}
// getPair returns a node pair from the route and the amount passed between that
// pair.
func getPair(rt *mcRoute, channelIdx int) (DirectedNodePair,
lnwire.MilliSatoshi) {
nodeTo := rt.hops.Val[channelIdx].pubKeyBytes.Val
var (
nodeFrom route.Vertex
amt lnwire.MilliSatoshi
)
if channelIdx == 0 {
nodeFrom = rt.sourcePubKey.Val
amt = rt.totalAmount.Val
} else {
nodeFrom = rt.hops.Val[channelIdx-1].pubKeyBytes.Val
amt = rt.hops.Val[channelIdx-1].amtToFwd.Val
}
pair := NewDirectedNodePair(nodeFrom, nodeTo)
return pair, amt
}
package route
import (
"bytes"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"io"
"strconv"
"strings"
"github.com/btcsuite/btcd/btcec/v2"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/tlv"
)
// VertexSize is the size of the array to store a vertex.
const VertexSize = 33
var (
// ErrNoRouteHopsProvided is returned when a caller attempts to
// construct a new sphinx packet, but provides an empty set of hops for
// each route.
ErrNoRouteHopsProvided = fmt.Errorf("empty route hops provided")
// ErrMaxRouteHopsExceeded is returned when a caller attempts to
// construct a new sphinx packet, but provides too many hops.
ErrMaxRouteHopsExceeded = fmt.Errorf("route has too many hops")
// ErrIntermediateMPPHop is returned when a hop tries to deliver an MPP
// record to an intermediate hop, only final hops can receive MPP
// records.
ErrIntermediateMPPHop = errors.New("cannot send MPP to intermediate")
// ErrAMPMissingMPP is returned when the caller tries to attach an AMP
// record but no MPP record is presented for the final hop.
ErrAMPMissingMPP = errors.New("cannot send AMP without MPP record")
// ErrMissingField is returned if a required TLV is missing.
ErrMissingField = errors.New("required tlv missing")
// ErrUnexpectedField is returned if a tlv field is included when it
// should not be.
ErrUnexpectedField = errors.New("unexpected tlv included")
)
// Vertex is a simple alias for the serialization of a compressed Bitcoin
// public key.
type Vertex [VertexSize]byte
// NewVertex returns a new Vertex given a public key.
func NewVertex(pub *btcec.PublicKey) Vertex {
var v Vertex
copy(v[:], pub.SerializeCompressed())
return v
}
// NewVertexFromBytes returns a new Vertex based on a serialized pubkey in a
// byte slice.
func NewVertexFromBytes(b []byte) (Vertex, error) {
vertexLen := len(b)
if vertexLen != VertexSize {
return Vertex{}, fmt.Errorf("invalid vertex length of %v, "+
"want %v", vertexLen, VertexSize)
}
var v Vertex
copy(v[:], b)
return v, nil
}
// NewVertexFromStr returns a new Vertex given its hex-encoded string format.
func NewVertexFromStr(v string) (Vertex, error) {
// Return error if hex string is of incorrect length.
if len(v) != VertexSize*2 {
return Vertex{}, fmt.Errorf("invalid vertex string length of "+
"%v, want %v", len(v), VertexSize*2)
}
vertex, err := hex.DecodeString(v)
if err != nil {
return Vertex{}, err
}
return NewVertexFromBytes(vertex)
}
// String returns a human readable version of the Vertex which is the
// hex-encoding of the serialized compressed public key.
func (v Vertex) String() string {
return fmt.Sprintf("%x", v[:])
}
// Record returns a TLV record that can be used to encode/decode a Vertex
// to/from a TLV stream.
func (v *Vertex) Record() tlv.Record {
return tlv.MakeStaticRecord(
0, v, VertexSize, encodeVertex, decodeVertex,
)
}
func encodeVertex(w io.Writer, val interface{}, _ *[8]byte) error {
if b, ok := val.(*Vertex); ok {
_, err := w.Write(b[:])
return err
}
return tlv.NewTypeForEncodingErr(val, "Vertex")
}
func decodeVertex(r io.Reader, val interface{}, _ *[8]byte, l uint64) error {
if b, ok := val.(*Vertex); ok {
_, err := io.ReadFull(r, b[:])
return err
}
return tlv.NewTypeForDecodingErr(val, "Vertex", l, VertexSize)
}
// Hop represents an intermediate or final node of the route. This naming
// is in line with the definition given in BOLT #4: Onion Routing Protocol.
// The struct houses the channel along which this hop can be reached and
// the values necessary to create the HTLC that needs to be sent to the
// next hop. It is also used to encode the per-hop payload included within
// the Sphinx packet.
type Hop struct {
// PubKeyBytes is the raw bytes of the public key of the target node.
PubKeyBytes Vertex
// ChannelID is the unique channel ID for the channel. The first 3
// bytes are the block height, the next 3 the index within the block,
// and the last 2 bytes are the output index for the channel.
ChannelID uint64
// OutgoingTimeLock is the timelock value that should be used when
// crafting the _outgoing_ HTLC from this hop.
OutgoingTimeLock uint32
// AmtToForward is the amount that this hop will forward to the next
// hop. This value is less than the value that the incoming HTLC
// carries as a fee will be subtracted by the hop.
AmtToForward lnwire.MilliSatoshi
// MPP encapsulates the data required for option_mpp. This field should
// only be set for the final hop.
MPP *record.MPP
// AMP encapsulates the data required for option_amp. This field should
// only be set for the final hop.
AMP *record.AMP
// CustomRecords if non-nil are a set of additional TLV records that
// should be included in the forwarding instructions for this node.
CustomRecords record.CustomSet
// LegacyPayload if true, then this signals that this node doesn't
// understand the new TLV payload, so we must instead use the legacy
// payload.
//
// NOTE: we should no longer ever create a Hop with Legacy set to true.
// The only reason we are keeping this member is that it could be the
// case that we have serialised hops persisted to disk where
// LegacyPayload is true.
LegacyPayload bool
// Metadata is additional data that is sent along with the payment to
// the payee.
Metadata []byte
// EncryptedData is an encrypted data blob includes for hops that are
// part of a blinded route.
EncryptedData []byte
// BlindingPoint is an ephemeral public key used by introduction nodes
// in blinded routes to unblind their portion of the route and pass on
// the next ephemeral key to the next blinded node to do the same.
BlindingPoint *btcec.PublicKey
// TotalAmtMsat is the total amount for a blinded payment, potentially
// spread over more than one HTLC. This field should only be set for
// the final hop in a blinded path.
TotalAmtMsat lnwire.MilliSatoshi
}
// Copy returns a deep copy of the Hop.
func (h *Hop) Copy() *Hop {
c := *h
if h.MPP != nil {
m := *h.MPP
c.MPP = &m
}
if h.AMP != nil {
a := *h.AMP
c.AMP = &a
}
if h.BlindingPoint != nil {
b := *h.BlindingPoint
c.BlindingPoint = &b
}
return &c
}
// PackHopPayload writes to the passed io.Writer, the series of byes that can
// be placed directly into the per-hop payload (EOB) for this hop. This will
// include the required routing fields, as well as serializing any of the
// passed optional TLVRecords. nextChanID is the unique channel ID that
// references the _outgoing_ channel ID that follows this hop. The lastHop bool
// is used to signal whether this hop is the final hop in a route. Previously,
// a zero nextChanID would be used for this purpose, but with the addition of
// blinded routes which allow zero nextChanID values for intermediate hops we
// add an explicit signal.
func (h *Hop) PackHopPayload(w io.Writer, nextChanID uint64,
finalHop bool) error {
// If this is a legacy payload, then we'll exit here as this method
// shouldn't be called.
if h.LegacyPayload {
return fmt.Errorf("cannot pack hop payloads for legacy " +
"payloads")
}
// Otherwise, we'll need to make a new stream that includes our
// required routing fields, as well as these optional values.
var records []tlv.Record
// Hops that are not part of a blinded path will have an amount and
// a CLTV expiry field. In a blinded route (where encrypted data is
// non-nil), these values may be omitted for intermediate nodes.
// Validate these fields against the structure of the payload so that
// we know they're included (or excluded) correctly.
isBlinded := h.EncryptedData != nil
if err := optionalBlindedField(
h.AmtToForward == 0, isBlinded, finalHop,
); err != nil {
return fmt.Errorf("%w: amount to forward: %v", err,
h.AmtToForward)
}
if err := optionalBlindedField(
h.OutgoingTimeLock == 0, isBlinded, finalHop,
); err != nil {
return fmt.Errorf("%w: outgoing timelock: %v", err,
h.OutgoingTimeLock)
}
// Once we've validated that these TLVs are set as we expect, we can
// go ahead and include them if non-zero.
amt := uint64(h.AmtToForward)
if amt != 0 {
records = append(
records, record.NewAmtToFwdRecord(&amt),
)
}
if h.OutgoingTimeLock != 0 {
records = append(
records, record.NewLockTimeRecord(&h.OutgoingTimeLock),
)
}
// Validate channel TLV is present as expected based on location in
// route and whether this hop is blinded.
err := validateNextChanID(nextChanID != 0, isBlinded, finalHop)
if err != nil {
return fmt.Errorf("%w: channel id: %v", err, nextChanID)
}
if nextChanID != 0 {
records = append(records,
record.NewNextHopIDRecord(&nextChanID),
)
}
// If an MPP record is destined for this hop, ensure that we only ever
// attach it to the final hop. Otherwise the route was constructed
// incorrectly.
if h.MPP != nil {
if finalHop {
records = append(records, h.MPP.Record())
} else {
return ErrIntermediateMPPHop
}
}
// Add encrypted data and blinding point if present.
if h.EncryptedData != nil {
records = append(records, record.NewEncryptedDataRecord(
&h.EncryptedData,
))
}
if h.BlindingPoint != nil {
records = append(records, record.NewBlindingPointRecord(
&h.BlindingPoint,
))
}
// If an AMP record is destined for this hop, ensure that we only ever
// attach it if we also have an MPP record. We can infer that this is
// already a final hop if MPP is non-nil otherwise we would have exited
// above.
if h.AMP != nil {
if h.MPP != nil {
records = append(records, h.AMP.Record())
} else {
return ErrAMPMissingMPP
}
}
// If metadata is specified, generate a tlv record for it.
if h.Metadata != nil {
records = append(records,
record.NewMetadataRecord(&h.Metadata),
)
}
if h.TotalAmtMsat != 0 {
totalAmtInt := uint64(h.TotalAmtMsat)
records = append(records,
record.NewTotalAmtMsatBlinded(&totalAmtInt),
)
}
// Append any custom types destined for this hop.
tlvRecords := tlv.MapToRecords(h.CustomRecords)
records = append(records, tlvRecords...)
// To ensure we produce a canonical stream, we'll sort the records
// before encoding them as a stream in the hop payload.
tlv.SortRecords(records)
tlvStream, err := tlv.NewStream(records...)
if err != nil {
return err
}
return tlvStream.Encode(w)
}
// optionalBlindedField validates fields that we expect to be non-zero for all
// hops in a regular route, but may be zero for intermediate nodes in a blinded
// route. It will validate the following cases:
// - Not blinded: require non-zero values.
// - Intermediate blinded node: require zero values.
// - Final blinded node: require non-zero values.
func optionalBlindedField(isZero, blindedHop, finalHop bool) error {
switch {
// We are not in a blinded route and the TLV is not set when it should
// be.
case !blindedHop && isZero:
return ErrMissingField
// We are not in a blinded route and the TLV is set as expected.
case !blindedHop:
return nil
// In a blinded route the final hop is expected to have TLV values set.
case finalHop && isZero:
return ErrMissingField
// In an intermediate hop in a blinded route and the field is not zero.
case !finalHop && !isZero:
return ErrUnexpectedField
}
return nil
}
// validateNextChanID validates the presence of the nextChanID TLV field in
// a payload. For regular payments, it is expected to be present for all hops
// except the final hop. For blinded paths, it is not expected to be included
// at all (as this value is provided in encrypted data).
func validateNextChanID(nextChanIDIsSet, isBlinded, finalHop bool) error {
switch {
// Hops in a blinded route should not have a next channel ID set.
case isBlinded && nextChanIDIsSet:
return ErrUnexpectedField
// Otherwise, blinded hops are allowed to have a zero value.
case isBlinded:
return nil
// The final hop in a regular route is expected to have a zero value.
case finalHop && nextChanIDIsSet:
return ErrUnexpectedField
// Intermediate hops in regular routes require non-zero value.
case !finalHop && !nextChanIDIsSet:
return ErrMissingField
default:
return nil
}
}
// PayloadSize returns the total size this hop's payload would take up in the
// onion packet.
func (h *Hop) PayloadSize(nextChanID uint64) uint64 {
if h.LegacyPayload {
return sphinx.LegacyHopDataSize
}
var payloadSize uint64
addRecord := func(tlvType tlv.Type, length uint64) {
payloadSize += tlv.VarIntSize(uint64(tlvType)) +
tlv.VarIntSize(length) + length
}
// Add amount size.
if h.AmtToForward != 0 {
addRecord(record.AmtOnionType, tlv.SizeTUint64(
uint64(h.AmtToForward),
))
}
// Add lock time size.
if h.OutgoingTimeLock != 0 {
addRecord(
record.LockTimeOnionType,
tlv.SizeTUint64(uint64(h.OutgoingTimeLock)),
)
}
// Add next hop if present.
if nextChanID != 0 {
addRecord(record.NextHopOnionType, 8)
}
// Add mpp if present.
if h.MPP != nil {
addRecord(record.MPPOnionType, h.MPP.PayloadSize())
}
// Add amp if present.
if h.AMP != nil {
addRecord(record.AMPOnionType, h.AMP.PayloadSize())
}
// Add encrypted data and blinding point if present.
if h.EncryptedData != nil {
addRecord(
record.EncryptedDataOnionType,
uint64(len(h.EncryptedData)),
)
}
if h.BlindingPoint != nil {
addRecord(
record.BlindingPointOnionType,
btcec.PubKeyBytesLenCompressed,
)
}
// Add metadata if present.
if h.Metadata != nil {
addRecord(record.MetadataOnionType, uint64(len(h.Metadata)))
}
if h.TotalAmtMsat != 0 {
addRecord(
record.TotalAmtMsatBlindedType,
tlv.SizeTUint64(uint64(h.AmtToForward)),
)
}
// Add custom records.
for k, v := range h.CustomRecords {
addRecord(tlv.Type(k), uint64(len(v)))
}
// Add the size required to encode the payload length.
payloadSize += tlv.VarIntSize(payloadSize)
// Add HMAC.
payloadSize += sphinx.HMACSize
return payloadSize
}
// Route represents a path through the channel graph which runs over one or
// more channels in succession. This struct carries all the information
// required to craft the Sphinx onion packet, and send the payment along the
// first hop in the path. A route is only selected as valid if all the channels
// have sufficient capacity to carry the initial payment amount after fees are
// accounted for.
type Route struct {
// TotalTimeLock is the cumulative (final) time lock across the entire
// route. This is the CLTV value that should be extended to the first
// hop in the route. All other hops will decrement the time-lock as
// advertised, leaving enough time for all hops to wait for or present
// the payment preimage to complete the payment.
TotalTimeLock uint32
// TotalAmount is the total amount of funds required to complete a
// payment over this route. This value includes the cumulative fees at
// each hop. As a result, the HTLC extended to the first-hop in the
// route will need to have at least this many satoshis, otherwise the
// route will fail at an intermediate node due to an insufficient
// amount of fees.
TotalAmount lnwire.MilliSatoshi
// SourcePubKey is the pubkey of the node where this route originates
// from.
SourcePubKey Vertex
// Hops contains details concerning the specific forwarding details at
// each hop.
Hops []*Hop
// FirstHopAmount is the amount that should actually be sent to the
// first hop in the route. This is only different from TotalAmount above
// for custom channels where the on-chain amount doesn't necessarily
// reflect all the value of an outgoing payment.
FirstHopAmount tlv.RecordT[
tlv.TlvType0, tlv.BigSizeT[lnwire.MilliSatoshi],
]
// FirstHopWireCustomRecords is a set of custom records that should be
// included in the wire message sent to the first hop. This is only set
// on custom channels and is used to include additional information
// about the actual value of the payment.
//
// NOTE: Since these records already represent TLV records, and we
// enforce them to be in the custom range (e.g. >= 65536), we don't use
// another parent record type here. Instead, when serializing the Route
// we merge the TLV records together with the custom records and encode
// everything as a single TLV stream.
FirstHopWireCustomRecords lnwire.CustomRecords
}
// Copy returns a deep copy of the Route.
func (r *Route) Copy() *Route {
c := *r
c.Hops = make([]*Hop, len(r.Hops))
for i := range r.Hops {
c.Hops[i] = r.Hops[i].Copy()
}
return &c
}
// HopFee returns the fee charged by the route hop indicated by hopIndex.
//
// This calculation takes into account the possibility that the route contains
// some blinded hops, that will not have the amount to forward set. We take
// note of various points in the blinded route.
//
// Given the following route where Carol is the introduction node and B2 is
// the recipient, Carol and B1's hops will not have an amount to forward set:
// Alice --- Bob ---- Carol (introduction) ----- B1 ----- B2
//
// We locate ourselves in the route as follows:
// * Regular Hop (eg Alice - Bob):
//
// incomingAmt !=0
// outgoingAmt !=0
// -> Fee = incomingAmt - outgoingAmt
//
// * Introduction Hop (eg Bob - Carol):
//
// incomingAmt !=0
// outgoingAmt = 0
// -> Fee = incomingAmt - receiverAmt
//
// This has the impact of attributing the full fees for the blinded route to
// the introduction node.
//
// * Blinded Intermediate Hop (eg Carol - B1):
//
// incomingAmt = 0
// outgoingAmt = 0
// -> Fee = 0
//
// * Final Blinded Hop (B1 - B2):
//
// incomingAmt = 0
// outgoingAmt !=0
// -> Fee = 0
func (r *Route) HopFee(hopIndex int) lnwire.MilliSatoshi {
var incomingAmt lnwire.MilliSatoshi
if hopIndex == 0 {
incomingAmt = r.TotalAmount
} else {
incomingAmt = r.Hops[hopIndex-1].AmtToForward
}
outgoingAmt := r.Hops[hopIndex].AmtToForward
switch {
// If both incoming and outgoing amounts are set, we're in a normal
// hop
case incomingAmt != 0 && outgoingAmt != 0:
return incomingAmt - outgoingAmt
// If the incoming amount is zero, we're at an intermediate hop in
// a blinded route, so the fee is zero.
case incomingAmt == 0:
return 0
// If we have a non-zero incoming amount and a zero outgoing amount,
// we're at the introduction hop so we express the fees for the full
// blinded route at this hop.
default:
return incomingAmt - r.ReceiverAmt()
}
}
// TotalFees is the sum of the fees paid at each hop within the final route. In
// the case of a one-hop payment, this value will be zero as we don't need to
// pay a fee to ourself.
func (r *Route) TotalFees() lnwire.MilliSatoshi {
if len(r.Hops) == 0 {
return 0
}
return r.TotalAmount - r.ReceiverAmt()
}
// ReceiverAmt is the amount received by the final hop of this route.
func (r *Route) ReceiverAmt() lnwire.MilliSatoshi {
if len(r.Hops) == 0 {
return 0
}
return r.Hops[len(r.Hops)-1].AmtToForward
}
// FinalHop returns the last hop of the route, or nil if the route is empty.
func (r *Route) FinalHop() *Hop {
if len(r.Hops) == 0 {
return nil
}
return r.Hops[len(r.Hops)-1]
}
// NewRouteFromHops creates a new Route structure from the minimally required
// information to perform the payment. It infers fee amounts and populates the
// node, chan and prev/next hop maps.
func NewRouteFromHops(amtToSend lnwire.MilliSatoshi, timeLock uint32,
sourceVertex Vertex, hops []*Hop) (*Route, error) {
if len(hops) == 0 {
return nil, ErrNoRouteHopsProvided
}
// First, we'll create a route struct and populate it with the fields
// for which the values are provided as arguments of this function.
// TotalFees is determined based on the difference between the amount
// that is send from the source and the final amount that is received
// by the destination.
route := &Route{
SourcePubKey: sourceVertex,
Hops: hops,
TotalTimeLock: timeLock,
TotalAmount: amtToSend,
}
return route, nil
}
// ToSphinxPath converts a complete route into a sphinx PaymentPath that
// contains the per-hop payloads used to encoding the HTLC routing data for each
// hop in the route. This method also accepts an optional EOB payload for the
// final hop.
func (r *Route) ToSphinxPath() (*sphinx.PaymentPath, error) {
var path sphinx.PaymentPath
// We can only construct a route if there are hops provided.
if len(r.Hops) == 0 {
return nil, ErrNoRouteHopsProvided
}
// Check maximum route length.
if len(r.Hops) > sphinx.NumMaxHops {
return nil, ErrMaxRouteHopsExceeded
}
// For each hop encoded within the route, we'll convert the hop struct
// to an OnionHop with matching per-hop payload within the path as used
// by the sphinx package.
for i, hop := range r.Hops {
pub, err := btcec.ParsePubKey(hop.PubKeyBytes[:])
if err != nil {
return nil, err
}
// As a base case, the next hop is set to all zeroes in order
// to indicate that the "last hop" as no further hops after it.
nextHop := uint64(0)
// If we aren't on the last hop, then we set the "next address"
// field to be the channel that directly follows it.
finalHop := i == len(r.Hops)-1
if !finalHop {
nextHop = r.Hops[i+1].ChannelID
}
var payload sphinx.HopPayload
// If this is the legacy payload, then we can just include the
// hop data as normal.
if hop.LegacyPayload {
// Before we encode this value, we'll pack the next hop
// into the NextAddress field of the hop info to ensure
// we point to the right now.
hopData := sphinx.HopData{
ForwardAmount: uint64(hop.AmtToForward),
OutgoingCltv: hop.OutgoingTimeLock,
}
binary.BigEndian.PutUint64(
hopData.NextAddress[:], nextHop,
)
payload, err = sphinx.NewLegacyHopPayload(&hopData)
if err != nil {
return nil, err
}
} else {
// For non-legacy payloads, we'll need to pack the
// routing information, along with any extra TLV
// information into the new per-hop payload format.
// We'll also pass in the chan ID of the hop this
// channel should be forwarded to so we can construct a
// valid payload.
var b bytes.Buffer
err := hop.PackHopPayload(&b, nextHop, finalHop)
if err != nil {
return nil, err
}
payload, err = sphinx.NewTLVHopPayload(b.Bytes())
if err != nil {
return nil, err
}
}
path[i] = sphinx.OnionHop{
NodePub: *pub,
HopPayload: payload,
}
}
return &path, nil
}
// String returns a human readable representation of the route.
func (r *Route) String() string {
var b strings.Builder
amt := r.TotalAmount
for i, hop := range r.Hops {
if i > 0 {
b.WriteString(" -> ")
}
b.WriteString(fmt.Sprintf("%v (%v)",
strconv.FormatUint(hop.ChannelID, 10),
amt,
))
amt = hop.AmtToForward
}
return fmt.Sprintf("%v, cltv %v",
b.String(), r.TotalTimeLock,
)
}
package routing
import (
"context"
"fmt"
"math"
"math/big"
"sort"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/davecgh/go-spew/spew"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/amp"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/routing/shards"
"github.com/lightningnetwork/lnd/tlv"
"github.com/lightningnetwork/lnd/zpay32"
)
const (
// DefaultPayAttemptTimeout is the default payment attempt timeout. The
// payment attempt timeout defines the duration after which we stop
// trying more routes for a payment.
DefaultPayAttemptTimeout = time.Second * 60
// MinCLTVDelta is the minimum CLTV value accepted by LND for all
// timelock deltas. This includes both forwarding CLTV deltas set on
// channel updates, as well as final CLTV deltas used to create BOLT 11
// payment requests.
//
// NOTE: For payment requests, BOLT 11 stipulates that a final CLTV
// delta of 9 should be used when no value is decoded. This however
// leads to inflexibility in upgrading this default parameter, since it
// can create inconsistencies around the assumed value between sender
// and receiver. Specifically, if the receiver assumes a higher value
// than the sender, the receiver will always see the received HTLCs as
// invalid due to their timelock not meeting the required delta.
//
// We skirt this by always setting an explicit CLTV delta when creating
// invoices. This allows LND nodes to freely update the minimum without
// creating incompatibilities during the upgrade process. For some time
// LND has used an explicit default final CLTV delta of 40 blocks for
// bitcoin, though we now clamp the lower end of this
// range for user-chosen deltas to 18 blocks to be conservative.
MinCLTVDelta = 18
// MaxCLTVDelta is the maximum CLTV value accepted by LND for all
// timelock deltas.
MaxCLTVDelta = math.MaxUint16
)
var (
// ErrRouterShuttingDown is returned if the router is in the process of
// shutting down.
ErrRouterShuttingDown = fmt.Errorf("router shutting down")
// ErrSelfIntro is a failure returned when the source node of a
// route request is also the introduction node. This is not yet
// supported because LND does not support blinded forwardingg.
ErrSelfIntro = errors.New("introduction point as own node not " +
"supported")
// ErrHintsAndBlinded is returned if a route request has both
// bolt 11 route hints and a blinded path set.
ErrHintsAndBlinded = errors.New("bolt 11 route hints and blinded " +
"paths are mutually exclusive")
// ErrExpiryAndBlinded is returned if a final cltv and a blinded path
// are provided, as the cltv should be provided within the blinded
// path.
ErrExpiryAndBlinded = errors.New("final cltv delta and blinded " +
"paths are mutually exclusive")
// ErrTargetAndBlinded is returned is a target destination and a
// blinded path are both set (as the target is inferred from the
// blinded path).
ErrTargetAndBlinded = errors.New("target node and blinded paths " +
"are mutually exclusive")
// ErrNoTarget is returned when the target node for a route is not
// provided by either a blinded route or a cleartext pubkey.
ErrNoTarget = errors.New("destination not set in target or blinded " +
"path")
// ErrSkipTempErr is returned when a non-MPP is made yet the
// skipTempErr flag is set.
ErrSkipTempErr = errors.New("cannot skip temp error for non-MPP")
)
// PaymentAttemptDispatcher is used by the router to send payment attempts onto
// the network, and receive their results.
type PaymentAttemptDispatcher interface {
// SendHTLC is a function that directs a link-layer switch to
// forward a fully encoded payment to the first hop in the route
// denoted by its public key. A non-nil error is to be returned if the
// payment was unsuccessful.
SendHTLC(firstHop lnwire.ShortChannelID,
attemptID uint64,
htlcAdd *lnwire.UpdateAddHTLC) error
// GetAttemptResult returns the result of the payment attempt with
// the given attemptID. The paymentHash should be set to the payment's
// overall hash, or in case of AMP payments the payment's unique
// identifier.
//
// The method returns a channel where the payment result will be sent
// when available, or an error is encountered during forwarding. When a
// result is received on the channel, the HTLC is guaranteed to no
// longer be in flight. The switch shutting down is signaled by
// closing the channel. If the attemptID is unknown,
// ErrPaymentIDNotFound will be returned.
GetAttemptResult(attemptID uint64, paymentHash lntypes.Hash,
deobfuscator htlcswitch.ErrorDecrypter) (
<-chan *htlcswitch.PaymentResult, error)
// CleanStore calls the underlying result store, telling it is safe to
// delete all entries except the ones in the keepPids map. This should
// be called periodically to let the switch clean up payment results
// that we have handled.
// NOTE: New payment attempts MUST NOT be made after the keepPids map
// has been created and this method has returned.
CleanStore(keepPids map[uint64]struct{}) error
// HasAttemptResult reads the network result store to fetch the
// specified attempt. Returns true if the attempt result exists.
//
// NOTE: This method is used and should only be used by the router to
// resume payments during startup. It can be viewed as a subset of
// `GetAttemptResult` in terms of its functionality, and can be removed
// once we move the construction of `UpdateAddHTLC` and
// `ErrorDecrypter` into `htlcswitch`.
HasAttemptResult(attemptID uint64) (bool, error)
}
// PaymentSessionSource is an interface that defines a source for the router to
// retrieve new payment sessions.
type PaymentSessionSource interface {
// NewPaymentSession creates a new payment session that will produce
// routes to the given target. An optional set of routing hints can be
// provided in order to populate additional edges to explore when
// finding a path to the payment's destination.
NewPaymentSession(p *LightningPayment,
firstHopBlob fn.Option[tlv.Blob],
ts fn.Option[htlcswitch.AuxTrafficShaper]) (PaymentSession,
error)
// NewPaymentSessionEmpty creates a new paymentSession instance that is
// empty, and will be exhausted immediately. Used for failure reporting
// to missioncontrol for resumed payment we don't want to make more
// attempts for.
NewPaymentSessionEmpty() PaymentSession
}
// MissionControlQuerier is an interface that exposes failure reporting and
// probability estimation.
type MissionControlQuerier interface {
// ReportPaymentFail reports a failed payment to mission control as
// input for future probability estimates. It returns a bool indicating
// whether this error is a final error and no further payment attempts
// need to be made.
ReportPaymentFail(attemptID uint64, rt *route.Route,
failureSourceIdx *int, failure lnwire.FailureMessage) (
*channeldb.FailureReason, error)
// ReportPaymentSuccess reports a successful payment to mission control
// as input for future probability estimates.
ReportPaymentSuccess(attemptID uint64, rt *route.Route) error
// GetProbability is expected to return the success probability of a
// payment from fromNode along edge.
GetProbability(fromNode, toNode route.Vertex,
amt lnwire.MilliSatoshi, capacity btcutil.Amount) float64
}
// FeeSchema is the set fee configuration for a Lightning Node on the network.
// Using the coefficients described within the schema, the required fee to
// forward outgoing payments can be derived.
type FeeSchema struct {
// BaseFee is the base amount of milli-satoshis that will be chained
// for ANY payment forwarded.
BaseFee lnwire.MilliSatoshi
// FeeRate is the rate that will be charged for forwarding payments.
// This value should be interpreted as the numerator for a fraction
// (fixed point arithmetic) whose denominator is 1 million. As a result
// the effective fee rate charged per mSAT will be: (amount *
// FeeRate/1,000,000).
FeeRate uint32
// InboundFee is the inbound fee schedule that applies to forwards
// coming in through a channel to which this FeeSchema pertains.
InboundFee fn.Option[models.InboundFee]
}
// ChannelPolicy holds the parameters that determine the policy we enforce
// when forwarding payments on a channel. These parameters are communicated
// to the rest of the network in ChannelUpdate messages.
type ChannelPolicy struct {
// FeeSchema holds the fee configuration for a channel.
FeeSchema
// TimeLockDelta is the required HTLC timelock delta to be used
// when forwarding payments.
TimeLockDelta uint32
// MaxHTLC is the maximum HTLC size including fees we are allowed to
// forward over this channel.
MaxHTLC lnwire.MilliSatoshi
// MinHTLC is the minimum HTLC size including fees we are allowed to
// forward over this channel.
MinHTLC *lnwire.MilliSatoshi
}
// Config defines the configuration for the ChannelRouter. ALL elements within
// the configuration MUST be non-nil for the ChannelRouter to carry out its
// duties.
type Config struct {
// SelfNode is the public key of the node that this channel router
// belongs to.
SelfNode route.Vertex
// RoutingGraph is a graph source that will be used for pathfinding.
RoutingGraph Graph
// Chain is the router's source to the most up-to-date blockchain data.
// All incoming advertised channels will be checked against the chain
// to ensure that the channels advertised are still open.
Chain lnwallet.BlockChainIO
// Payer is an instance of a PaymentAttemptDispatcher and is used by
// the router to send payment attempts onto the network, and receive
// their results.
Payer PaymentAttemptDispatcher
// Control keeps track of the status of ongoing payments, ensuring we
// can properly resume them across restarts.
Control ControlTower
// MissionControl is a shared memory of sorts that executions of
// payment path finding use in order to remember which vertexes/edges
// were pruned from prior attempts. During SendPayment execution,
// errors sent by nodes are mapped into a vertex or edge to be pruned.
// Each run will then take into account this set of pruned
// vertexes/edges to reduce route failure and pass on graph information
// gained to the next execution.
MissionControl MissionControlQuerier
// SessionSource defines a source for the router to retrieve new payment
// sessions.
SessionSource PaymentSessionSource
// GetLink is a method that allows the router to query the lower link
// layer to determine the up-to-date available bandwidth at a
// prospective link to be traversed. If the link isn't available, then
// a value of zero should be returned. Otherwise, the current up-to-
// date knowledge of the available bandwidth of the link should be
// returned.
GetLink getLinkQuery
// NextPaymentID is a method that guarantees to return a new, unique ID
// each time it is called. This is used by the router to generate a
// unique payment ID for each payment it attempts to send, such that
// the switch can properly handle the HTLC.
NextPaymentID func() (uint64, error)
// PathFindingConfig defines global path finding parameters.
PathFindingConfig PathFindingConfig
// Clock is mockable time provider.
Clock clock.Clock
// ApplyChannelUpdate can be called to apply a new channel update to the
// graph that we received from a payment failure.
ApplyChannelUpdate func(msg *lnwire.ChannelUpdate1) bool
// ClosedSCIDs is used by the router to fetch closed channels.
//
// TODO(yy): remove it once the root cause of stuck payments is found.
ClosedSCIDs map[lnwire.ShortChannelID]struct{}
// TrafficShaper is an optional traffic shaper that can be used to
// control the outgoing channel of a payment.
TrafficShaper fn.Option[htlcswitch.AuxTrafficShaper]
}
// EdgeLocator is a struct used to identify a specific edge.
type EdgeLocator struct {
// ChannelID is the channel of this edge.
ChannelID uint64
// Direction takes the value of 0 or 1 and is identical in definition to
// the channel direction flag. A value of 0 means the direction from the
// lower node pubkey to the higher.
Direction uint8
}
// String returns a human-readable version of the edgeLocator values.
func (e *EdgeLocator) String() string {
return fmt.Sprintf("%v:%v", e.ChannelID, e.Direction)
}
// ChannelRouter is the layer 3 router within the Lightning stack. Below the
// ChannelRouter is the HtlcSwitch, and below that is the Bitcoin blockchain
// itself. The primary role of the ChannelRouter is to respond to queries for
// potential routes that can support a payment amount, and also general graph
// reachability questions. The router will prune the channel graph
// automatically as new blocks are discovered which spend certain known funding
// outpoints, thereby closing their respective channels.
type ChannelRouter struct {
started uint32 // To be used atomically.
stopped uint32 // To be used atomically.
// cfg is a copy of the configuration struct that the ChannelRouter was
// initialized with.
cfg *Config
quit chan struct{}
wg sync.WaitGroup
}
// New creates a new instance of the ChannelRouter with the specified
// configuration parameters. As part of initialization, if the router detects
// that the channel graph isn't fully in sync with the latest UTXO (since the
// channel graph is a subset of the UTXO set) set, then the router will proceed
// to fully sync to the latest state of the UTXO set.
func New(cfg Config) (*ChannelRouter, error) {
return &ChannelRouter{
cfg: &cfg,
quit: make(chan struct{}),
}, nil
}
// Start launches all the goroutines the ChannelRouter requires to carry out
// its duties. If the router has already been started, then this method is a
// noop.
func (r *ChannelRouter) Start() error {
if !atomic.CompareAndSwapUint32(&r.started, 0, 1) {
return nil
}
log.Info("Channel Router starting")
// If any payments are still in flight, we resume, to make sure their
// results are properly handled.
if err := r.resumePayments(); err != nil {
log.Error("Failed to resume payments during startup")
}
return nil
}
// Stop signals the ChannelRouter to gracefully halt all routines. This method
// will *block* until all goroutines have excited. If the channel router has
// already stopped then this method will return immediately.
func (r *ChannelRouter) Stop() error {
if !atomic.CompareAndSwapUint32(&r.stopped, 0, 1) {
return nil
}
log.Info("Channel Router shutting down...")
defer log.Debug("Channel Router shutdown complete")
close(r.quit)
r.wg.Wait()
return nil
}
// RouteRequest contains the parameters for a pathfinding request. It may
// describe a request to make a regular payment or one to a blinded path
// (incdicated by a non-nil BlindedPayment field).
type RouteRequest struct {
// Source is the node that the path originates from.
Source route.Vertex
// Target is the node that the path terminates at. If the route
// includes a blinded path, target will be the blinded node id of the
// final hop in the blinded route.
Target route.Vertex
// Amount is the Amount in millisatoshis to be delivered to the target
// node.
Amount lnwire.MilliSatoshi
// TimePreference expresses the caller's time preference for
// pathfinding.
TimePreference float64
// Restrictions provides a set of additional Restrictions that the
// route must adhere to.
Restrictions *RestrictParams
// CustomRecords is a set of custom tlv records to include for the
// final hop.
CustomRecords record.CustomSet
// RouteHints contains an additional set of edges to include in our
// view of the graph. This may either be a set of hints for private
// channels or a "virtual" hop hint that represents a blinded route.
RouteHints RouteHints
// FinalExpiry is the cltv delta for the final hop. If paying to a
// blinded path, this value is a duplicate of the delta provided
// in blinded payment.
FinalExpiry uint16
// BlindedPathSet contains a set of optional blinded paths and
// parameters used to reach a target node blinded paths. This field is
// mutually exclusive with the Target field.
BlindedPathSet *BlindedPaymentPathSet
}
// RouteHints is an alias type for a set of route hints, with the source node
// as the map's key and the details of the hint(s) in the edge policy.
type RouteHints map[route.Vertex][]AdditionalEdge
// NewRouteRequest produces a new route request for a regular payment or one
// to a blinded route, validating that the target, routeHints and finalExpiry
// parameters are mutually exclusive with the blindedPayment parameter (which
// contains these values for blinded payments).
func NewRouteRequest(source route.Vertex, target *route.Vertex,
amount lnwire.MilliSatoshi, timePref float64,
restrictions *RestrictParams, customRecords record.CustomSet,
routeHints RouteHints, blindedPathSet *BlindedPaymentPathSet,
finalExpiry uint16) (*RouteRequest, error) {
var (
// Assume that we're starting off with a regular payment.
requestHints = routeHints
requestExpiry = finalExpiry
err error
)
if blindedPathSet != nil {
if blindedPathSet.IsIntroNode(source) {
return nil, ErrSelfIntro
}
// Check that the values for a clear path have not been set,
// as this is an ambiguous signal from the caller.
if routeHints != nil {
return nil, ErrHintsAndBlinded
}
if finalExpiry != 0 {
return nil, ErrExpiryAndBlinded
}
requestExpiry = blindedPathSet.FinalCLTVDelta()
requestHints, err = blindedPathSet.ToRouteHints()
if err != nil {
return nil, err
}
}
requestTarget, err := getTargetNode(target, blindedPathSet)
if err != nil {
return nil, err
}
return &RouteRequest{
Source: source,
Target: requestTarget,
Amount: amount,
TimePreference: timePref,
Restrictions: restrictions,
CustomRecords: customRecords,
RouteHints: requestHints,
FinalExpiry: requestExpiry,
BlindedPathSet: blindedPathSet,
}, nil
}
func getTargetNode(target *route.Vertex,
blindedPathSet *BlindedPaymentPathSet) (route.Vertex, error) {
var (
blinded = blindedPathSet != nil
targetSet = target != nil
)
switch {
case blinded && targetSet:
return route.Vertex{}, ErrTargetAndBlinded
case blinded:
return route.NewVertex(blindedPathSet.TargetPubKey()), nil
case targetSet:
return *target, nil
default:
return route.Vertex{}, ErrNoTarget
}
}
// FindRoute attempts to query the ChannelRouter for the optimum path to a
// particular target destination to which it is able to send `amt` after
// factoring in channel capacities and cumulative fees along the route.
func (r *ChannelRouter) FindRoute(req *RouteRequest) (*route.Route, float64,
error) {
log.Debugf("Searching for path to %v, sending %v", req.Target,
req.Amount)
// We'll attempt to obtain a set of bandwidth hints that can help us
// eliminate certain routes early on in the path finding process.
bandwidthHints, err := newBandwidthManager(
r.cfg.RoutingGraph, r.cfg.SelfNode, r.cfg.GetLink,
fn.None[tlv.Blob](), r.cfg.TrafficShaper,
)
if err != nil {
return nil, 0, err
}
// We'll fetch the current block height, so we can properly calculate
// the required HTLC time locks within the route.
_, currentHeight, err := r.cfg.Chain.GetBestBlock()
if err != nil {
return nil, 0, err
}
// Now that we know the destination is reachable within the graph, we'll
// execute our path finding algorithm.
finalHtlcExpiry := currentHeight + int32(req.FinalExpiry)
// Validate time preference.
timePref := req.TimePreference
if timePref < -1 || timePref > 1 {
return nil, 0, errors.New("time preference out of range")
}
path, probability, err := findPath(
&graphParams{
additionalEdges: req.RouteHints,
bandwidthHints: bandwidthHints,
graph: r.cfg.RoutingGraph,
},
req.Restrictions, &r.cfg.PathFindingConfig,
r.cfg.SelfNode, req.Source, req.Target, req.Amount,
req.TimePreference, finalHtlcExpiry,
)
if err != nil {
return nil, 0, err
}
// Create the route with absolute time lock values.
route, err := newRoute(
req.Source, path, uint32(currentHeight),
finalHopParams{
amt: req.Amount,
totalAmt: req.Amount,
cltvDelta: req.FinalExpiry,
records: req.CustomRecords,
}, req.BlindedPathSet,
)
if err != nil {
return nil, 0, err
}
go log.Tracef("Obtained path to send %v to %x: %v",
req.Amount, req.Target, lnutils.SpewLogClosure(route))
return route, probability, nil
}
// probabilitySource defines the signature of a function that can be used to
// query the success probability of sending a given amount between the two
// given vertices.
type probabilitySource func(route.Vertex, route.Vertex, lnwire.MilliSatoshi,
btcutil.Amount) float64
// BlindedPathRestrictions are a set of constraints to adhere to when
// choosing a set of blinded paths to this node.
type BlindedPathRestrictions struct {
// MinDistanceFromIntroNode is the minimum number of _real_ (non-dummy)
// hops to include in a blinded path. Since we post-fix dummy hops, this
// is the minimum distance between our node and the introduction node
// of the path. This doesn't include our node, so if the minimum is 1,
// then the path will contain at minimum our node along with an
// introduction node hop.
MinDistanceFromIntroNode uint8
// NumHops is the number of hops that each blinded path should consist
// of. If paths are found with a number of hops less that NumHops, then
// dummy hops will be padded on to the route. This value doesn't
// include our node, so if the maximum is 1, then the path will contain
// our node along with an introduction node hop.
NumHops uint8
// MaxNumPaths is the maximum number of blinded paths to select.
MaxNumPaths uint8
// NodeOmissionSet is a set of nodes that should not be used within any
// of the blinded paths that we generate.
NodeOmissionSet fn.Set[route.Vertex]
}
// FindBlindedPaths finds a selection of paths to the destination node that can
// be used in blinded payment paths.
func (r *ChannelRouter) FindBlindedPaths(destination route.Vertex,
amt lnwire.MilliSatoshi, probabilitySrc probabilitySource,
restrictions *BlindedPathRestrictions) ([]*route.Route, error) {
// First, find a set of candidate paths given the destination node and
// path length restrictions.
paths, err := findBlindedPaths(
r.cfg.RoutingGraph, destination, &blindedPathRestrictions{
minNumHops: restrictions.MinDistanceFromIntroNode,
maxNumHops: restrictions.NumHops,
nodeOmissionSet: restrictions.NodeOmissionSet,
},
)
if err != nil {
return nil, err
}
// routeWithProbability groups a route with the probability of a
// payment of the given amount succeeding on that path.
type routeWithProbability struct {
route *route.Route
probability float64
}
// Iterate over all the candidate paths and determine the success
// probability of each path given the data we have about forwards
// between any two nodes on a path.
routes := make([]*routeWithProbability, 0, len(paths))
for _, path := range paths {
if len(path) < 1 {
return nil, fmt.Errorf("a blinded path must have at " +
"least one hop")
}
var (
introNode = path[0].vertex
prevNode = introNode
hops = make(
[]*route.Hop, 0, len(path)-1,
)
totalRouteProbability = float64(1)
)
// For each set of hops on the path, get the success probability
// of a forward between those two vertices and use that to
// update the overall route probability.
for j := 1; j < len(path); j++ {
probability := probabilitySrc(
prevNode, path[j].vertex, amt,
path[j-1].edgeCapacity,
)
totalRouteProbability *= probability
hops = append(hops, &route.Hop{
PubKeyBytes: path[j].vertex,
ChannelID: path[j-1].channelID,
})
prevNode = path[j].vertex
}
// Don't bother adding a route if its success probability less
// minimum that can be assigned to any single pair.
if totalRouteProbability <= DefaultMinRouteProbability {
continue
}
routes = append(routes, &routeWithProbability{
route: &route.Route{
SourcePubKey: introNode,
Hops: hops,
},
probability: totalRouteProbability,
})
}
// Sort the routes based on probability.
sort.Slice(routes, func(i, j int) bool {
return routes[i].probability > routes[j].probability
})
// Now just choose the best paths up until the maximum number of allowed
// paths.
bestRoutes := make([]*route.Route, 0, restrictions.MaxNumPaths)
for _, route := range routes {
if len(bestRoutes) >= int(restrictions.MaxNumPaths) {
break
}
bestRoutes = append(bestRoutes, route.route)
}
return bestRoutes, nil
}
// generateNewSessionKey generates a new ephemeral private key to be used for a
// payment attempt.
func generateNewSessionKey() (*btcec.PrivateKey, error) {
// Generate a new random session key to ensure that we don't trigger
// any replay.
//
// TODO(roasbeef): add more sources of randomness?
return btcec.NewPrivateKey()
}
// LightningPayment describes a payment to be sent through the network to the
// final destination.
type LightningPayment struct {
// Target is the node in which the payment should be routed towards.
Target route.Vertex
// Amount is the value of the payment to send through the network in
// milli-satoshis.
Amount lnwire.MilliSatoshi
// FeeLimit is the maximum fee in millisatoshis that the payment should
// accept when sending it through the network. The payment will fail
// if there isn't a route with lower fees than this limit.
FeeLimit lnwire.MilliSatoshi
// CltvLimit is the maximum time lock that is allowed for attempts to
// complete this payment.
CltvLimit uint32
// paymentHash is the r-hash value to use within the HTLC extended to
// the first hop. This won't be set for AMP payments.
paymentHash *lntypes.Hash
// amp is an optional field that is set if and only if this is am AMP
// payment.
amp *AMPOptions
// FinalCLTVDelta is the CTLV expiry delta to use for the _final_ hop
// in the route. This means that the final hop will have a CLTV delta
// of at least: currentHeight + FinalCLTVDelta.
FinalCLTVDelta uint16
// PayAttemptTimeout is a timeout value that we'll use to determine
// when we should should abandon the payment attempt after consecutive
// payment failure. This prevents us from attempting to send a payment
// indefinitely. A zero value means the payment will never time out.
//
// TODO(halseth): make wallclock time to allow resume after startup.
PayAttemptTimeout time.Duration
// RouteHints represents the different routing hints that can be used to
// assist a payment in reaching its destination successfully. These
// hints will act as intermediate hops along the route.
//
// NOTE: This is optional unless required by the payment. When providing
// multiple routes, ensure the hop hints within each route are chained
// together and sorted in forward order in order to reach the
// destination successfully. This is mutually exclusive to the
// BlindedPayment field.
RouteHints [][]zpay32.HopHint
// BlindedPathSet holds the information about a set of blinded paths to
// the payment recipient. This is mutually exclusive to the RouteHints
// field.
BlindedPathSet *BlindedPaymentPathSet
// OutgoingChannelIDs is the list of channels that are allowed for the
// first hop. If nil, any channel may be used.
OutgoingChannelIDs []uint64
// LastHop is the pubkey of the last node before the final destination
// is reached. If nil, any node may be used.
LastHop *route.Vertex
// DestFeatures specifies the set of features we assume the final node
// has for pathfinding. Typically, these will be taken directly from an
// invoice, but they can also be manually supplied or assumed by the
// sender. If a nil feature vector is provided, the router will try to
// fall back to the graph in order to load a feature vector for a node
// in the public graph.
DestFeatures *lnwire.FeatureVector
// PaymentAddr is the payment address specified by the receiver. This
// field should be a random 32-byte nonce presented in the receiver's
// invoice to prevent probing of the destination.
PaymentAddr fn.Option[[32]byte]
// PaymentRequest is an optional payment request that this payment is
// attempting to complete.
PaymentRequest []byte
// DestCustomRecords are TLV records that are to be sent to the final
// hop in the new onion payload format. If the destination does not
// understand this new onion payload format, then the payment will
// fail.
DestCustomRecords record.CustomSet
// FirstHopCustomRecords are the TLV records that are to be sent to the
// first hop of this payment. These records will be transmitted via the
// wire message and therefore do not affect the onion payload size.
FirstHopCustomRecords lnwire.CustomRecords
// MaxParts is the maximum number of partial payments that may be used
// to complete the full amount.
MaxParts uint32
// MaxShardAmt is the largest shard that we'll attempt to split using.
// If this field is set, and we need to split, rather than attempting
// half of the original payment amount, we'll use this value if half
// the payment amount is greater than it.
//
// NOTE: This field is _optional_.
MaxShardAmt *lnwire.MilliSatoshi
// TimePref is the time preference for this payment. Set to -1 to
// optimize for fees only, to 1 to optimize for reliability only or a
// value in between for a mix.
TimePref float64
// Metadata is additional data that is sent along with the payment to
// the payee.
Metadata []byte
}
// AMPOptions houses information that must be known in order to send an AMP
// payment.
type AMPOptions struct {
SetID [32]byte
RootShare [32]byte
}
// SetPaymentHash sets the given hash as the payment's overall hash. This
// should only be used for non-AMP payments.
func (l *LightningPayment) SetPaymentHash(hash lntypes.Hash) error {
if l.amp != nil {
return fmt.Errorf("cannot set payment hash for AMP payment")
}
l.paymentHash = &hash
return nil
}
// SetAMP sets the given AMP options for the payment.
func (l *LightningPayment) SetAMP(amp *AMPOptions) error {
if l.paymentHash != nil {
return fmt.Errorf("cannot set amp options for payment " +
"with payment hash")
}
l.amp = amp
return nil
}
// Identifier returns a 32-byte slice that uniquely identifies this single
// payment. For non-AMP payments this will be the payment hash, for AMP
// payments this will be the used SetID.
func (l *LightningPayment) Identifier() [32]byte {
if l.amp != nil {
return l.amp.SetID
}
return *l.paymentHash
}
// SendPayment attempts to send a payment as described within the passed
// LightningPayment. This function is blocking and will return either: when the
// payment is successful, or all candidates routes have been attempted and
// resulted in a failed payment. If the payment succeeds, then a non-nil Route
// will be returned which describes the path the successful payment traversed
// within the network to reach the destination. Additionally, the payment
// preimage will also be returned.
func (r *ChannelRouter) SendPayment(payment *LightningPayment) ([32]byte,
*route.Route, error) {
paySession, shardTracker, err := r.PreparePayment(payment)
if err != nil {
return [32]byte{}, nil, err
}
log.Tracef("Dispatching SendPayment for lightning payment: %v",
spewPayment(payment))
return r.sendPayment(
context.Background(), payment.FeeLimit, payment.Identifier(),
payment.PayAttemptTimeout, paySession, shardTracker,
payment.FirstHopCustomRecords,
)
}
// SendPaymentAsync is the non-blocking version of SendPayment. The payment
// result needs to be retrieved via the control tower.
func (r *ChannelRouter) SendPaymentAsync(ctx context.Context,
payment *LightningPayment, ps PaymentSession, st shards.ShardTracker) {
// Since this is the first time this payment is being made, we pass nil
// for the existing attempt.
r.wg.Add(1)
go func() {
defer r.wg.Done()
log.Tracef("Dispatching SendPayment for lightning payment: %v",
spewPayment(payment))
_, _, err := r.sendPayment(
ctx, payment.FeeLimit, payment.Identifier(),
payment.PayAttemptTimeout, ps, st,
payment.FirstHopCustomRecords,
)
if err != nil {
log.Errorf("Payment %x failed: %v",
payment.Identifier(), err)
}
}()
}
// spewPayment returns a log closures that provides a spewed string
// representation of the passed payment.
func spewPayment(payment *LightningPayment) lnutils.LogClosure {
return lnutils.NewLogClosure(func() string {
// Make a copy of the payment with a nilled Curve
// before spewing.
var routeHints [][]zpay32.HopHint
for _, routeHint := range payment.RouteHints {
var hopHints []zpay32.HopHint
for _, hopHint := range routeHint {
h := hopHint.Copy()
hopHints = append(hopHints, h)
}
routeHints = append(routeHints, hopHints)
}
p := *payment
p.RouteHints = routeHints
return spew.Sdump(p)
})
}
// PreparePayment creates the payment session and registers the payment with the
// control tower.
func (r *ChannelRouter) PreparePayment(payment *LightningPayment) (
PaymentSession, shards.ShardTracker, error) {
// Assemble any custom data we want to send to the first hop only.
var firstHopData fn.Option[tlv.Blob]
if len(payment.FirstHopCustomRecords) > 0 {
if err := payment.FirstHopCustomRecords.Validate(); err != nil {
return nil, nil, fmt.Errorf("invalid first hop custom "+
"records: %w", err)
}
firstHopBlob, err := payment.FirstHopCustomRecords.Serialize()
if err != nil {
return nil, nil, fmt.Errorf("unable to serialize "+
"first hop custom records: %w", err)
}
firstHopData = fn.Some(firstHopBlob)
}
// Before starting the HTLC routing attempt, we'll create a fresh
// payment session which will report our errors back to mission
// control.
paySession, err := r.cfg.SessionSource.NewPaymentSession(
payment, firstHopData, r.cfg.TrafficShaper,
)
if err != nil {
return nil, nil, err
}
// Record this payment hash with the ControlTower, ensuring it is not
// already in-flight.
//
// TODO(roasbeef): store records as part of creation info?
info := &channeldb.PaymentCreationInfo{
PaymentIdentifier: payment.Identifier(),
Value: payment.Amount,
CreationTime: r.cfg.Clock.Now(),
PaymentRequest: payment.PaymentRequest,
FirstHopCustomRecords: payment.FirstHopCustomRecords,
}
// Create a new ShardTracker that we'll use during the life cycle of
// this payment.
var shardTracker shards.ShardTracker
switch {
// If this is an AMP payment, we'll use the AMP shard tracker.
case payment.amp != nil:
addr := payment.PaymentAddr.UnwrapOr([32]byte{})
shardTracker = amp.NewShardTracker(
payment.amp.RootShare, payment.amp.SetID, addr,
payment.Amount,
)
// Otherwise we'll use the simple tracker that will map each attempt to
// the same payment hash.
default:
shardTracker = shards.NewSimpleShardTracker(
payment.Identifier(), nil,
)
}
err = r.cfg.Control.InitPayment(payment.Identifier(), info)
if err != nil {
return nil, nil, err
}
return paySession, shardTracker, nil
}
// SendToRoute sends a payment using the provided route and fails the payment
// when an error is returned from the attempt.
func (r *ChannelRouter) SendToRoute(htlcHash lntypes.Hash, rt *route.Route,
firstHopCustomRecords lnwire.CustomRecords) (*channeldb.HTLCAttempt,
error) {
return r.sendToRoute(htlcHash, rt, false, firstHopCustomRecords)
}
// SendToRouteSkipTempErr sends a payment using the provided route and fails
// the payment ONLY when a terminal error is returned from the attempt.
func (r *ChannelRouter) SendToRouteSkipTempErr(htlcHash lntypes.Hash,
rt *route.Route,
firstHopCustomRecords lnwire.CustomRecords) (*channeldb.HTLCAttempt,
error) {
return r.sendToRoute(htlcHash, rt, true, firstHopCustomRecords)
}
// sendToRoute attempts to send a payment with the given hash through the
// provided route. This function is blocking and will return the attempt
// information as it is stored in the database. For a successful htlc, this
// information will contain the preimage. If an error occurs after the attempt
// was initiated, both return values will be non-nil. If skipTempErr is true,
// the payment won't be failed unless a terminal error has occurred.
func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route,
skipTempErr bool,
firstHopCustomRecords lnwire.CustomRecords) (*channeldb.HTLCAttempt,
error) {
// Helper function to fail a payment. It makes sure the payment is only
// failed once so that the failure reason is not overwritten.
failPayment := func(paymentIdentifier lntypes.Hash,
reason channeldb.FailureReason) error {
payment, fetchErr := r.cfg.Control.FetchPayment(
paymentIdentifier,
)
if fetchErr != nil {
return fetchErr
}
// NOTE: We cannot rely on the payment status to be failed here
// because it can still be in-flight although the payment is
// already failed.
_, failedReason := payment.TerminalInfo()
if failedReason != nil {
return nil
}
return r.cfg.Control.FailPayment(paymentIdentifier, reason)
}
log.Debugf("SendToRoute for payment %v with skipTempErr=%v",
htlcHash, skipTempErr)
// Calculate amount paid to receiver.
amt := rt.ReceiverAmt()
// If this is meant as an MP payment shard, we set the amount for the
// creating info to the total amount of the payment.
finalHop := rt.Hops[len(rt.Hops)-1]
mpp := finalHop.MPP
if mpp != nil {
amt = mpp.TotalMsat()
}
// For non-MPP, there's no such thing as temp error as there's only one
// HTLC attempt being made. When this HTLC is failed, the payment is
// failed hence cannot be retried.
if skipTempErr && mpp == nil {
return nil, ErrSkipTempErr
}
// For non-AMP payments the overall payment identifier will be the same
// hash as used for this HTLC.
paymentIdentifier := htlcHash
// For AMP-payments, we'll use the setID as the unique ID for the
// overall payment.
amp := finalHop.AMP
if amp != nil {
paymentIdentifier = amp.SetID()
}
// Record this payment hash with the ControlTower, ensuring it is not
// already in-flight.
info := &channeldb.PaymentCreationInfo{
PaymentIdentifier: paymentIdentifier,
Value: amt,
CreationTime: r.cfg.Clock.Now(),
PaymentRequest: nil,
FirstHopCustomRecords: firstHopCustomRecords,
}
err := r.cfg.Control.InitPayment(paymentIdentifier, info)
switch {
// If this is an MPP attempt and the hash is already registered with
// the database, we can go on to launch the shard.
case mpp != nil && errors.Is(err, channeldb.ErrPaymentInFlight):
case mpp != nil && errors.Is(err, channeldb.ErrPaymentExists):
// Any other error is not tolerated.
case err != nil:
return nil, err
}
log.Tracef("Dispatching SendToRoute for HTLC hash %v: %v", htlcHash,
lnutils.SpewLogClosure(rt))
// Since the HTLC hashes and preimages are specified manually over the
// RPC for SendToRoute requests, we don't have to worry about creating
// a ShardTracker that can generate hashes for AMP payments. Instead, we
// create a simple tracker that can just return the hash for the single
// shard we'll now launch.
shardTracker := shards.NewSimpleShardTracker(htlcHash, nil)
// Create a payment lifecycle using the given route with,
// - zero fee limit as we are not requesting routes.
// - nil payment session (since we already have a route).
// - no payment timeout.
// - no current block height.
p := newPaymentLifecycle(
r, 0, paymentIdentifier, nil, shardTracker, 0,
firstHopCustomRecords,
)
// Allow the traffic shaper to add custom records to the outgoing HTLC
// and also adjust the amount if needed.
err = p.amendFirstHopData(rt)
if err != nil {
return nil, err
}
// We found a route to try, create a new HTLC attempt to try.
//
// NOTE: we use zero `remainingAmt` here to simulate the same effect of
// setting the lastShard to be false, which is used by previous
// implementation.
attempt, err := p.registerAttempt(rt, 0)
if err != nil {
return nil, err
}
// Once the attempt is created, send it to the htlcswitch. Notice that
// the `err` returned here has already been processed by
// `handleSwitchErr`, which means if there's a terminal failure, the
// payment has been failed.
result, err := p.sendAttempt(attempt)
if err != nil {
return nil, err
}
// Since for SendToRoute we won't retry in case the shard fails, we'll
// mark the payment failed with the control tower immediately if the
// skipTempErr is false.
reason := channeldb.FailureReasonError
// If we failed to send the HTLC, we need to further decide if we want
// to fail the payment.
if result.err != nil {
// If skipTempErr, we'll return the attempt and the temp error.
if skipTempErr {
return result.attempt, result.err
}
err := failPayment(paymentIdentifier, reason)
if err != nil {
return nil, err
}
return result.attempt, result.err
}
// The attempt was successfully sent, wait for the result to be
// available.
result, err = p.collectAndHandleResult(attempt)
if err != nil {
return nil, err
}
// We got a successful result.
if result.err == nil {
return result.attempt, nil
}
// An error returned from collecting the result, we'll mark the payment
// as failed if we don't skip temp error.
if !skipTempErr {
err := failPayment(paymentIdentifier, reason)
if err != nil {
return nil, err
}
}
return result.attempt, result.err
}
// sendPayment attempts to send a payment to the passed payment hash. This
// function is blocking and will return either: when the payment is successful,
// or all candidates routes have been attempted and resulted in a failed
// payment. If the payment succeeds, then a non-nil Route will be returned
// which describes the path the successful payment traversed within the network
// to reach the destination. Additionally, the payment preimage will also be
// returned.
//
// This method relies on the ControlTower's internal payment state machine to
// carry out its execution. After restarts, it is safe, and assumed, that the
// router will call this method for every payment still in-flight according to
// the ControlTower.
func (r *ChannelRouter) sendPayment(ctx context.Context,
feeLimit lnwire.MilliSatoshi, identifier lntypes.Hash,
paymentAttemptTimeout time.Duration, paySession PaymentSession,
shardTracker shards.ShardTracker,
firstHopCustomRecords lnwire.CustomRecords) ([32]byte, *route.Route,
error) {
// If the user provides a timeout, we will additionally wrap the context
// in a deadline.
cancel := func() {}
if paymentAttemptTimeout > 0 {
ctx, cancel = context.WithTimeout(ctx, paymentAttemptTimeout)
}
// Since resumePayment is a blocking call, we'll cancel this
// context if the payment completes before the optional
// deadline.
defer cancel()
// We'll also fetch the current block height, so we can properly
// calculate the required HTLC time locks within the route.
_, currentHeight, err := r.cfg.Chain.GetBestBlock()
if err != nil {
return [32]byte{}, nil, err
}
// Validate the custom records before we attempt to send the payment.
// TODO(ziggie): Move this check before registering the payment in the
// db (InitPayment).
if err := firstHopCustomRecords.Validate(); err != nil {
return [32]byte{}, nil, err
}
// Now set up a paymentLifecycle struct with these params, such that we
// can resume the payment from the current state.
p := newPaymentLifecycle(
r, feeLimit, identifier, paySession, shardTracker,
currentHeight, firstHopCustomRecords,
)
return p.resumePayment(ctx)
}
// extractChannelUpdate examines the error and extracts the channel update.
func (r *ChannelRouter) extractChannelUpdate(
failure lnwire.FailureMessage) *lnwire.ChannelUpdate1 {
var update *lnwire.ChannelUpdate1
switch onionErr := failure.(type) {
case *lnwire.FailExpiryTooSoon:
update = &onionErr.Update
case *lnwire.FailAmountBelowMinimum:
update = &onionErr.Update
case *lnwire.FailFeeInsufficient:
update = &onionErr.Update
case *lnwire.FailIncorrectCltvExpiry:
update = &onionErr.Update
case *lnwire.FailChannelDisabled:
update = &onionErr.Update
case *lnwire.FailTemporaryChannelFailure:
update = onionErr.Update
}
return update
}
// ErrNoChannel is returned when a route cannot be built because there are no
// channels that satisfy all requirements.
type ErrNoChannel struct {
position int
}
// Error returns a human-readable string describing the error.
func (e ErrNoChannel) Error() string {
return fmt.Sprintf("no matching outgoing channel available for "+
"node index %v", e.position)
}
// BuildRoute returns a fully specified route based on a list of pubkeys. If
// amount is nil, the minimum routable amount is used. To force a specific
// outgoing channel, use the outgoingChan parameter.
func (r *ChannelRouter) BuildRoute(amt fn.Option[lnwire.MilliSatoshi],
hops []route.Vertex, outgoingChan *uint64, finalCltvDelta int32,
payAddr fn.Option[[32]byte], firstHopBlob fn.Option[[]byte]) (
*route.Route, error) {
log.Tracef("BuildRoute called: hopsCount=%v, amt=%v", len(hops), amt)
var outgoingChans map[uint64]struct{}
if outgoingChan != nil {
outgoingChans = map[uint64]struct{}{
*outgoingChan: {},
}
}
// We'll attempt to obtain a set of bandwidth hints that helps us select
// the best outgoing channel to use in case no outgoing channel is set.
bandwidthHints, err := newBandwidthManager(
r.cfg.RoutingGraph, r.cfg.SelfNode, r.cfg.GetLink, firstHopBlob,
r.cfg.TrafficShaper,
)
if err != nil {
return nil, err
}
sourceNode := r.cfg.SelfNode
// We check that each node in the route has a connection to others that
// can forward in principle.
unifiers, err := getEdgeUnifiers(
r.cfg.SelfNode, hops, outgoingChans, r.cfg.RoutingGraph,
)
if err != nil {
return nil, err
}
var (
receiverAmt lnwire.MilliSatoshi
senderAmt lnwire.MilliSatoshi
pathEdges []*unifiedEdge
)
// We determine the edges compatible with the requested amount, as well
// as the amount to send, which can be used to determine the final
// receiver amount, if a minimal amount was requested.
pathEdges, senderAmt, err = senderAmtBackwardPass(
unifiers, amt, bandwidthHints,
)
if err != nil {
return nil, err
}
// For the minimal amount search, we need to do a forward pass to find a
// larger receiver amount due to possible min HTLC bumps, otherwise we
// just use the requested amount.
receiverAmt, err = fn.ElimOption(
amt,
func() fn.Result[lnwire.MilliSatoshi] {
return fn.NewResult(
receiverAmtForwardPass(senderAmt, pathEdges),
)
},
fn.Ok[lnwire.MilliSatoshi],
).Unpack()
if err != nil {
return nil, err
}
// Fetch the current block height outside the routing transaction, to
// prevent the rpc call blocking the database.
_, height, err := r.cfg.Chain.GetBestBlock()
if err != nil {
return nil, err
}
// Build and return the final route.
return newRoute(
sourceNode, pathEdges, uint32(height),
finalHopParams{
amt: receiverAmt,
totalAmt: receiverAmt,
cltvDelta: uint16(finalCltvDelta),
records: nil,
paymentAddr: payAddr,
}, nil,
)
}
// resumePayments fetches inflight payments and resumes their payment
// lifecycles.
func (r *ChannelRouter) resumePayments() error {
// Get all payments that are inflight.
payments, err := r.cfg.Control.FetchInFlightPayments()
if err != nil {
return err
}
// Before we restart existing payments and start accepting more
// payments to be made, we clean the network result store of the
// Switch. We do this here at startup to ensure no more payments can be
// made concurrently, so we know the toKeep map will be up-to-date
// until the cleaning has finished.
toKeep := make(map[uint64]struct{})
for _, p := range payments {
for _, a := range p.HTLCs {
toKeep[a.AttemptID] = struct{}{}
// Try to fail the attempt if the route contains a dead
// channel.
r.failStaleAttempt(a, p.Info.PaymentIdentifier)
}
}
log.Debugf("Cleaning network result store.")
if err := r.cfg.Payer.CleanStore(toKeep); err != nil {
return err
}
// launchPayment is a helper closure that handles resuming the payment.
launchPayment := func(payment *channeldb.MPPayment) {
defer r.wg.Done()
// Get the hashes used for the outstanding HTLCs.
htlcs := make(map[uint64]lntypes.Hash)
for _, a := range payment.HTLCs {
a := a
// We check whether the individual attempts have their
// HTLC hash set, if not we'll fall back to the overall
// payment hash.
hash := payment.Info.PaymentIdentifier
if a.Hash != nil {
hash = *a.Hash
}
htlcs[a.AttemptID] = hash
}
payHash := payment.Info.PaymentIdentifier
// Since we are not supporting creating more shards after a
// restart (only receiving the result of the shards already
// outstanding), we create a simple shard tracker that will map
// the attempt IDs to hashes used for the HTLCs. This will be
// enough also for AMP payments, since we only need the hashes
// for the individual HTLCs to regenerate the circuits, and we
// don't currently persist the root share necessary to
// re-derive them.
shardTracker := shards.NewSimpleShardTracker(payHash, htlcs)
// We create a dummy, empty payment session such that we won't
// make another payment attempt when the result for the
// in-flight attempt is received.
paySession := r.cfg.SessionSource.NewPaymentSessionEmpty()
// We pass in a non-timeout context, to indicate we don't need
// it to timeout. It will stop immediately after the existing
// attempt has finished anyway. We also set a zero fee limit,
// as no more routes should be tried.
noTimeout := time.Duration(0)
_, _, err := r.sendPayment(
context.Background(), 0, payHash, noTimeout, paySession,
shardTracker, payment.Info.FirstHopCustomRecords,
)
if err != nil {
log.Errorf("Resuming payment %v failed: %v", payHash,
err)
return
}
log.Infof("Resumed payment %v completed", payHash)
}
for _, payment := range payments {
log.Infof("Resuming payment %v", payment.Info.PaymentIdentifier)
r.wg.Add(1)
go launchPayment(payment)
}
return nil
}
// failStaleAttempt will fail an HTLC attempt if it's using an unknown channel
// in its route. It first consults the switch to see if there's already a
// network result stored for this attempt. If not, it will check whether the
// first hop of this attempt is using an active channel known to us. If
// inactive, this attempt will be failed.
//
// NOTE: there's an unknown bug that caused the network result for a particular
// attempt to NOT be saved, resulting a payment being stuck forever. More info:
// - https://github.com/lightningnetwork/lnd/issues/8146
// - https://github.com/lightningnetwork/lnd/pull/8174
func (r *ChannelRouter) failStaleAttempt(a channeldb.HTLCAttempt,
payHash lntypes.Hash) {
// We can only fail inflight HTLCs so we skip the settled/failed ones.
if a.Failure != nil || a.Settle != nil {
return
}
// First, check if we've already had a network result for this attempt.
// If no result is found, we'll check whether the reference link is
// still known to us.
ok, err := r.cfg.Payer.HasAttemptResult(a.AttemptID)
if err != nil {
log.Errorf("Failed to fetch network result for attempt=%v",
a.AttemptID)
return
}
// There's already a network result, no need to fail it here as the
// payment lifecycle will take care of it, so we can exit early.
if ok {
log.Debugf("Already have network result for attempt=%v",
a.AttemptID)
return
}
// We now need to decide whether this attempt should be failed here.
// For very old payments, it's possible that the network results were
// never saved, causing the payments to be stuck inflight. We now check
// whether the first hop is referencing an active channel ID and, if
// not, we will fail the attempt as it has no way to be retried again.
var shouldFail bool
// Validate that the attempt has hop info. If this attempt has no hop
// info it indicates an error in our db.
if len(a.Route.Hops) == 0 {
log.Errorf("Found empty hop for attempt=%v", a.AttemptID)
shouldFail = true
} else {
// Get the short channel ID.
chanID := a.Route.Hops[0].ChannelID
scid := lnwire.NewShortChanIDFromInt(chanID)
// Check whether this link is active. If so, we won't fail the
// attempt but keep waiting for its result.
_, err := r.cfg.GetLink(scid)
if err == nil {
return
}
// We should get the link not found err. If not, we will log an
// error and skip failing this attempt since an unknown error
// occurred.
if !errors.Is(err, htlcswitch.ErrChannelLinkNotFound) {
log.Errorf("Failed to get link for attempt=%v for "+
"payment=%v: %v", a.AttemptID, payHash, err)
return
}
// The channel link is not active, we now check whether this
// channel is already closed. If so, we fail the HTLC attempt
// as there's no need to wait for its network result because
// there's no link. If the channel is still pending, we'll keep
// waiting for the result as we may get a contract resolution
// for this HTLC.
if _, ok := r.cfg.ClosedSCIDs[scid]; ok {
shouldFail = true
}
}
// Exit if there's no need to fail.
if !shouldFail {
return
}
log.Errorf("Failing stale attempt=%v for payment=%v", a.AttemptID,
payHash)
// Fail the attempt in db. If there's an error, there's nothing we can
// do here but logging it.
failInfo := &channeldb.HTLCFailInfo{
Reason: channeldb.HTLCFailUnknown,
FailTime: r.cfg.Clock.Now(),
}
_, err = r.cfg.Control.FailAttempt(payHash, a.AttemptID, failInfo)
if err != nil {
log.Errorf("Fail attempt=%v got error: %v", a.AttemptID, err)
}
}
// getEdgeUnifiers returns a list of edge unifiers for the given route.
func getEdgeUnifiers(source route.Vertex, hops []route.Vertex,
outgoingChans map[uint64]struct{},
graph Graph) ([]*edgeUnifier, error) {
// Allocate a list that will contain the edge unifiers for this route.
unifiers := make([]*edgeUnifier, len(hops))
// Traverse hops backwards to accumulate fees in the running amounts.
for i := len(hops) - 1; i >= 0; i-- {
toNode := hops[i]
var fromNode route.Vertex
if i == 0 {
fromNode = source
} else {
fromNode = hops[i-1]
}
// Build unified policies for this hop based on the channels
// known in the graph. Inbound fees are only active if the edge
// is not the last hop.
isExitHop := i == len(hops)-1
u := newNodeEdgeUnifier(
source, toNode, !isExitHop, outgoingChans,
)
err := u.addGraphPolicies(graph)
if err != nil {
return nil, err
}
// Exit if there are no channels.
edgeUnifier, ok := u.edgeUnifiers[fromNode]
if !ok {
log.Errorf("Cannot find policy for node %v", fromNode)
return nil, ErrNoChannel{position: i}
}
unifiers[i] = edgeUnifier
}
return unifiers, nil
}
// senderAmtBackwardPass returns a list of unified edges for the given route and
// determines the amount that should be sent to fulfill min HTLC requirements.
// The minimal sender amount can be searched for by using amt=None.
func senderAmtBackwardPass(unifiers []*edgeUnifier,
amt fn.Option[lnwire.MilliSatoshi],
bandwidthHints bandwidthHints) ([]*unifiedEdge, lnwire.MilliSatoshi,
error) {
if len(unifiers) == 0 {
return nil, 0, fmt.Errorf("no unifiers provided")
}
var unifiedEdges = make([]*unifiedEdge, len(unifiers))
// We traverse the route backwards and handle the last hop separately.
edgeUnifier := unifiers[len(unifiers)-1]
// incomingAmt tracks the amount that is forwarded on the edges of a
// route. The last hop only forwards the amount that the receiver should
// receive, as there are no fees paid to the last node.
// For minimum amount routes, aim to deliver at least 1 msat to
// the destination. There are nodes in the wild that have a
// min_htlc channel policy of zero, which could lead to a zero
// amount payment being made.
incomingAmt := amt.UnwrapOr(1)
// If using min amt, increase the amount if needed to fulfill min HTLC
// requirements.
if amt.IsNone() {
min := edgeUnifier.minAmt()
if min > incomingAmt {
incomingAmt = min
}
}
// Get an edge for the specific amount that we want to forward.
edge := edgeUnifier.getEdge(incomingAmt, bandwidthHints, 0)
if edge == nil {
log.Errorf("Cannot find policy with amt=%v "+
"for hop %v", incomingAmt, len(unifiers)-1)
return nil, 0, ErrNoChannel{position: len(unifiers) - 1}
}
unifiedEdges[len(unifiers)-1] = edge
// Handle the rest of the route except the last hop.
for i := len(unifiers) - 2; i >= 0; i-- {
edgeUnifier = unifiers[i]
// If using min amt, increase the amount if needed to fulfill
// min HTLC requirements.
if amt.IsNone() {
min := edgeUnifier.minAmt()
if min > incomingAmt {
incomingAmt = min
}
}
// A --current hop-- B --next hop: incomingAmt-- C
// The outbound fee paid to the current end node B is based on
// the amount that the next hop forwards and B's policy for that
// hop.
outboundFee := unifiedEdges[i+1].policy.ComputeFee(
incomingAmt,
)
netAmount := incomingAmt + outboundFee
// We need to select an edge that can forward the requested
// amount.
edge = edgeUnifier.getEdge(
netAmount, bandwidthHints, outboundFee,
)
if edge == nil {
return nil, 0, ErrNoChannel{position: i}
}
// The fee paid to B depends on the current hop's inbound fee
// policy and on the outbound fee for the next hop as any
// inbound fee discount is capped by the outbound fee such that
// the total fee for B can't become negative.
inboundFee := calcCappedInboundFee(edge, netAmount, outboundFee)
fee := lnwire.MilliSatoshi(int64(outboundFee) + inboundFee)
log.Tracef("Select channel %v at position %v",
edge.policy.ChannelID, i)
// Finally, we update the amount that needs to flow into node B
// from A, which is the next hop's forwarding amount plus the
// fee for B: A --current hop: incomingAmt-- B --next hop-- C
incomingAmt += fee
unifiedEdges[i] = edge
}
return unifiedEdges, incomingAmt, nil
}
// receiverAmtForwardPass returns the amount that a receiver will receive after
// deducting all fees from the sender amount.
func receiverAmtForwardPass(runningAmt lnwire.MilliSatoshi,
unifiedEdges []*unifiedEdge) (lnwire.MilliSatoshi, error) {
if len(unifiedEdges) == 0 {
return 0, fmt.Errorf("no edges to forward through")
}
inEdge := unifiedEdges[0]
if !inEdge.amtInRange(runningAmt) {
log.Errorf("Amount %v not in range for hop index %v",
runningAmt, 0)
return 0, ErrNoChannel{position: 0}
}
// Now that we arrived at the start of the route and found out the route
// total amount, we make a forward pass. Because the amount may have
// been increased in the backward pass, fees need to be recalculated and
// amount ranges re-checked.
for i := 1; i < len(unifiedEdges); i++ {
inEdge := unifiedEdges[i-1]
outEdge := unifiedEdges[i]
// Decrease the amount to send while going forward.
runningAmt = outgoingFromIncoming(runningAmt, inEdge, outEdge)
if !outEdge.amtInRange(runningAmt) {
log.Errorf("Amount %v not in range for hop index %v",
runningAmt, i)
return 0, ErrNoChannel{position: i}
}
}
return runningAmt, nil
}
// incomingFromOutgoing computes the incoming amount based on the outgoing
// amount by adding fees to the outgoing amount, replicating the path finding
// and routing process, see also CheckHtlcForward.
func incomingFromOutgoing(outgoingAmt lnwire.MilliSatoshi,
incoming, outgoing *unifiedEdge) lnwire.MilliSatoshi {
outgoingFee := outgoing.policy.ComputeFee(outgoingAmt)
// Net amount is the amount the inbound fees are calculated with.
netAmount := outgoingAmt + outgoingFee
inboundFee := incoming.inboundFees.CalcFee(netAmount)
// The inbound fee is not allowed to reduce the incoming amount below
// the outgoing amount.
if int64(outgoingFee)+inboundFee < 0 {
return outgoingAmt
}
return netAmount + lnwire.MilliSatoshi(inboundFee)
}
// outgoingFromIncoming computes the outgoing amount based on the incoming
// amount by subtracting fees from the incoming amount. Note that this is not
// exactly the inverse of incomingFromOutgoing, because of some rounding.
func outgoingFromIncoming(incomingAmt lnwire.MilliSatoshi,
incoming, outgoing *unifiedEdge) lnwire.MilliSatoshi {
// Convert all quantities to big.Int to be able to hande negative
// values. The formulas to compute the outgoing amount involve terms
// with PPM*PPM*A, which can easily overflow an int64.
A := big.NewInt(int64(incomingAmt))
Ro := big.NewInt(int64(outgoing.policy.FeeProportionalMillionths))
Bo := big.NewInt(int64(outgoing.policy.FeeBaseMSat))
Ri := big.NewInt(int64(incoming.inboundFees.Rate))
Bi := big.NewInt(int64(incoming.inboundFees.Base))
PPM := big.NewInt(1_000_000)
// The following discussion was contributed by user feelancer21, see
//nolint:ll
// https://github.com/feelancer21/lnd/commit/f6f05fa930985aac0d27c3f6681aada1b599162a.
// The incoming amount Ai based on the outgoing amount Ao is computed by
// Ai = max(Ai(Ao), Ao), which caps the incoming amount such that the
// total node fee (Ai - Ao) is non-negative. This is commonly enforced
// by routing nodes.
// The function Ai(Ao) is given by:
// Ai(Ao) = (Ao + Bo + Ro/PPM) + (Bi + (Ao + Ro/PPM + Bo)*Ri/PPM), where
// the first term is the net amount (the outgoing amount plus the
// outbound fee), and the second is the inbound fee computed based on
// the net amount.
// Ai(Ao) can potentially become more negative in absolute value than
// Ao, which is why the above mentioned capping is needed. We can
// abbreviate Ai(Ao) with Ai(Ao) = m*Ao + n, where m and n are:
// m := (1 + Ro/PPM) * (1 + Ri/PPM)
// n := Bi + Bo*(1 + Ri/PPM)
// If we know that m > 0, this is equivalent of Ri/PPM > -1, because Ri
// is the only factor that can become negative. A value or Ri/PPM = -1,
// means that the routing node is willing to give up on 100% of the
// net amount (based on the fee rate), which is likely to not happen in
// practice. This condition will be important for a later trick.
// If we want to compute the incoming amount based on the outgoing
// amount, which is the reverse problem, we need to solve Ai =
// max(Ai(Ao), Ao) for Ao(Ai). Given an incoming amount A,
// we look for an Ao such that A = max(Ai(Ao), Ao).
// The max function separates this into two cases. The case to take is
// not clear yet, because we don't know Ao, but later we see a trick
// how to determine which case is the one to take.
// first case: Ai(Ao) <= Ao:
// Therefore, A = max(Ai(Ao), Ao) = Ao, we find Ao = A.
// This also leads to Ai(A) <= A by substitution into the condition.
// second case: Ai(Ao) > Ao:
// Therefore, A = max(Ai(Ao), Ao) = Ai(Ao) = m*Ao + n. Solving for Ao
// gives Ao = (A - n)/m.
//
// We know
// Ai(Ao) > Ao <=> A = Ai(Ao) > Ao = (A - n)/m,
// so A > (A - n)/m.
//
// **Assuming m > 0**, by multiplying with m, we can transform this to
// A * m + n > A.
//
// We know Ai(A) = A*m + n, therefore Ai(A) > A.
//
// This means that if we apply the incoming amount calculation to the
// **incoming** amount, and this condition holds, then we know that we
// deal with the second case, being able to compute the outgoing amount
// based off the formula Ao = (A - n)/m, otherwise we will just return
// the incoming amount.
// In case the inbound fee rate is less than -1 (-100%), we fail to
// compute the outbound amount and return the incoming amount. This also
// protects against zero division later.
// We compute m in terms of big.Int to be safe from overflows and to be
// consistent with later calculations.
// m := (PPM*PPM + Ri*PPM + Ro*PPM + Ro*Ri)/(PPM*PPM)
// Compute terms in (PPM*PPM + Ri*PPM + Ro*PPM + Ro*Ri).
m1 := new(big.Int).Mul(PPM, PPM)
m2 := new(big.Int).Mul(Ri, PPM)
m3 := new(big.Int).Mul(Ro, PPM)
m4 := new(big.Int).Mul(Ro, Ri)
// Add up terms m1..m4.
m := big.NewInt(0)
m.Add(m, m1)
m.Add(m, m2)
m.Add(m, m3)
m.Add(m, m4)
// Since we compare to 0, we can multiply by PPM*PPM to avoid the
// division.
if m.Int64() <= 0 {
return incomingAmt
}
// In order to decide if the total fee is negative, we apply the fee
// to the *incoming* amount as mentioned before.
// We compute the test amount in terms of big.Int to be safe from
// overflows and to be consistent later calculations.
// testAmtF := A*m + n =
// = A + Bo + Bi + (PPM*(A*Ri + A*Ro + Ro*Ri) + A*Ri*Ro)/(PPM*PPM)
// Compute terms in (A*Ri + A*Ro + Ro*Ri).
t1 := new(big.Int).Mul(A, Ri)
t2 := new(big.Int).Mul(A, Ro)
t3 := new(big.Int).Mul(Ro, Ri)
// Sum up terms t1-t3.
t4 := big.NewInt(0)
t4.Add(t4, t1)
t4.Add(t4, t2)
t4.Add(t4, t3)
// Compute PPM*(A*Ri + A*Ro + Ro*Ri).
t6 := new(big.Int).Mul(PPM, t4)
// Compute A*Ri*Ro.
t7 := new(big.Int).Mul(A, Ri)
t7.Mul(t7, Ro)
// Compute (PPM*(A*Ri + A*Ro + Ro*Ri) + A*Ri*Ro)/(PPM*PPM).
num := new(big.Int).Add(t6, t7)
denom := new(big.Int).Mul(PPM, PPM)
fraction := new(big.Int).Div(num, denom)
// Sum up all terms.
testAmt := big.NewInt(0)
testAmt.Add(testAmt, A)
testAmt.Add(testAmt, Bo)
testAmt.Add(testAmt, Bi)
testAmt.Add(testAmt, fraction)
// Protect against negative values for the integer cast to Msat.
if testAmt.Int64() < 0 {
return incomingAmt
}
// If the second case holds, we have to compute the outgoing amount.
if lnwire.MilliSatoshi(testAmt.Int64()) > incomingAmt {
// Compute the outgoing amount by integer ceiling division. This
// precision is needed because PPM*PPM*A and other terms can
// easily overflow with int64, which happens with about
// A = 10_000 sat.
// out := (A - n) / m = numerator / denominator
// numerator := PPM*(PPM*(A - Bo - Bi) - Bo*Ri)
// denominator := PPM*(PPM + Ri + Ro) + Ri*Ro
var numerator big.Int
// Compute (A - Bo - Bi).
temp1 := new(big.Int).Sub(A, Bo)
temp2 := new(big.Int).Sub(temp1, Bi)
// Compute terms in (PPM*(A - Bo - Bi) - Bo*Ri).
temp3 := new(big.Int).Mul(PPM, temp2)
temp4 := new(big.Int).Mul(Bo, Ri)
// Compute PPM*(PPM*(A - Bo - Bi) - Bo*Ri)
temp5 := new(big.Int).Sub(temp3, temp4)
numerator.Mul(PPM, temp5)
var denominator big.Int
// Compute (PPM + Ri + Ro).
temp1 = new(big.Int).Add(PPM, Ri)
temp2 = new(big.Int).Add(temp1, Ro)
// Compute PPM*(PPM + Ri + Ro) + Ri*Ro.
temp3 = new(big.Int).Mul(PPM, temp2)
temp4 = new(big.Int).Mul(Ri, Ro)
denominator.Add(temp3, temp4)
// We overestimate the outgoing amount by taking the ceiling of
// the division. This means that we may round slightly up by a
// MilliSatoshi, but this helps to ensure that we don't hit min
// HTLC constrains in the context of finding the minimum amount
// of a route.
// ceil = floor((numerator + denominator - 1) / denominator)
ceil := new(big.Int).Add(&numerator, &denominator)
ceil.Sub(ceil, big.NewInt(1))
ceil.Div(ceil, &denominator)
return lnwire.MilliSatoshi(ceil.Int64())
}
// Otherwise the inbound fee made up for the outbound fee, which is why
// we just return the incoming amount.
return incomingAmt
}
package shards
import (
"fmt"
"sync"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/record"
)
// PaymentShard is an interface representing a shard tracked by the
// ShardTracker. It contains options that are specific to the given shard that
// might differ from the overall payment.
type PaymentShard interface {
// Hash returns the hash used for the HTLC representing this shard.
Hash() lntypes.Hash
// MPP returns any extra MPP records that should be set for the final
// hop on the route used by this shard.
MPP() *record.MPP
// AMP returns any extra AMP records that should be set for the final
// hop on the route used by this shard.
AMP() *record.AMP
}
// ShardTracker is an interface representing a tracker that keeps track of the
// inflight shards of a payment, and is able to assign new shards the correct
// options such as hash and extra records.
type ShardTracker interface {
// NewShard registers a new attempt with the ShardTracker and returns a
// new shard representing this attempt. This attempt's shard should be
// canceled if it ends up not being used by the overall payment, i.e.
// if the attempt fails.
NewShard(uint64, bool) (PaymentShard, error)
// CancelShard cancel's the shard corresponding to the given attempt
// ID. This lets the ShardTracker free up any slots used by this shard,
// and in case of AMP payments return the share used by this shard to
// the root share.
CancelShard(uint64) error
// GetHash retrieves the hash used by the shard of the given attempt
// ID. This will return an error if the attempt ID is unknown.
GetHash(uint64) (lntypes.Hash, error)
}
// Shard is a struct used for simple shards where we only need to keep map it
// to a single hash.
type Shard struct {
hash lntypes.Hash
}
// Hash returns the hash used for the HTLC representing this shard.
func (s *Shard) Hash() lntypes.Hash {
return s.hash
}
// MPP returns any extra MPP records that should be set for the final hop on
// the route used by this shard.
func (s *Shard) MPP() *record.MPP {
return nil
}
// AMP returns any extra AMP records that should be set for the final hop on
// the route used by this shard.
func (s *Shard) AMP() *record.AMP {
return nil
}
// SimpleShardTracker is an implementation of the ShardTracker interface that
// simply maps attempt IDs to hashes. New shards will be given a static payment
// hash. This should be used for regular and MPP payments, in addition to
// resumed payments where all the attempt's hashes have already been created.
type SimpleShardTracker struct {
hash lntypes.Hash
shards map[uint64]lntypes.Hash
sync.Mutex
}
// A compile time check to ensure SimpleShardTracker implements the
// ShardTracker interface.
var _ ShardTracker = (*SimpleShardTracker)(nil)
// NewSimpleShardTracker creates a new instance of the SimpleShardTracker with
// the given payment hash and existing attempts.
func NewSimpleShardTracker(paymentHash lntypes.Hash,
shards map[uint64]lntypes.Hash) ShardTracker {
if shards == nil {
shards = make(map[uint64]lntypes.Hash)
}
return &SimpleShardTracker{
hash: paymentHash,
shards: shards,
}
}
// NewShard registers a new attempt with the ShardTracker and returns a
// new shard representing this attempt. This attempt's shard should be canceled
// if it ends up not being used by the overall payment, i.e. if the attempt
// fails.
func (m *SimpleShardTracker) NewShard(id uint64, _ bool) (PaymentShard, error) {
m.Lock()
m.shards[id] = m.hash
m.Unlock()
return &Shard{
hash: m.hash,
}, nil
}
// CancelShard cancel's the shard corresponding to the given attempt ID.
func (m *SimpleShardTracker) CancelShard(id uint64) error {
m.Lock()
delete(m.shards, id)
m.Unlock()
return nil
}
// GetHash retrieves the hash used by the shard of the given attempt ID. This
// will return an error if the attempt ID is unknown.
func (m *SimpleShardTracker) GetHash(id uint64) (lntypes.Hash, error) {
m.Lock()
hash, ok := m.shards[id]
m.Unlock()
if !ok {
return lntypes.Hash{}, fmt.Errorf("hash for attempt id %v "+
"not found", id)
}
return hash, nil
}
package routing
import (
"math"
"github.com/btcsuite/btcd/btcutil"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
// nodeEdgeUnifier holds all edge unifiers for connections towards a node.
type nodeEdgeUnifier struct {
// edgeUnifiers contains an edge unifier for every from node.
edgeUnifiers map[route.Vertex]*edgeUnifier
// sourceNode is the sender of a payment. The rules to pick the final
// policy are different for local channels.
sourceNode route.Vertex
// toNode is the node for which the edge unifiers are instantiated.
toNode route.Vertex
// useInboundFees indicates whether to take inbound fees into account.
useInboundFees bool
// outChanRestr is an optional outgoing channel restriction for the
// local channel to use.
outChanRestr map[uint64]struct{}
}
// newNodeEdgeUnifier instantiates a new nodeEdgeUnifier object. Channel
// policies can be added to this object.
func newNodeEdgeUnifier(sourceNode, toNode route.Vertex, useInboundFees bool,
outChanRestr map[uint64]struct{}) *nodeEdgeUnifier {
return &nodeEdgeUnifier{
edgeUnifiers: make(map[route.Vertex]*edgeUnifier),
toNode: toNode,
useInboundFees: useInboundFees,
sourceNode: sourceNode,
outChanRestr: outChanRestr,
}
}
// addPolicy adds a single channel policy. Capacity may be zero if unknown
// (light clients). We expect a non-nil payload size function and will request a
// graceful shutdown if it is not provided as this indicates that edges are
// incorrectly specified.
func (u *nodeEdgeUnifier) addPolicy(fromNode route.Vertex,
edge *models.CachedEdgePolicy, inboundFee models.InboundFee,
capacity btcutil.Amount, hopPayloadSizeFn PayloadSizeFunc,
blindedPayment *BlindedPayment) {
localChan := fromNode == u.sourceNode
// Skip channels if there is an outgoing channel restriction.
if localChan && u.outChanRestr != nil {
if _, ok := u.outChanRestr[edge.ChannelID]; !ok {
log.Debugf("Skipped adding policy for restricted edge "+
"%v", edge.ChannelID)
return
}
}
// Update the edgeUnifiers map.
unifier, ok := u.edgeUnifiers[fromNode]
if !ok {
unifier = &edgeUnifier{
localChan: localChan,
}
u.edgeUnifiers[fromNode] = unifier
}
// In case no payload size function was provided a graceful shutdown
// is requested, because this function is not used as intended.
if hopPayloadSizeFn == nil {
log.Criticalf("No payloadsize function was provided for the "+
"edge (chanid=%v) when adding it to the edge unifier "+
"of node: %v", edge.ChannelID, fromNode)
return
}
// Zero inbound fee for exit hops.
if !u.useInboundFees {
inboundFee = models.InboundFee{}
}
unifier.edges = append(unifier.edges, newUnifiedEdge(
edge, capacity, inboundFee, hopPayloadSizeFn, blindedPayment,
))
}
// addGraphPolicies adds all policies that are known for the toNode in the
// graph.
func (u *nodeEdgeUnifier) addGraphPolicies(g Graph) error {
cb := func(channel *graphdb.DirectedChannel) error {
// If there is no edge policy for this candidate node, skip.
// Note that we are searching backwards so this node would have
// come prior to the pivot node in the route.
if channel.InPolicy == nil {
log.Debugf("Skipped adding edge %v due to nil policy",
channel.ChannelID)
return nil
}
// Add this policy to the corresponding edgeUnifier. We default
// to the clear hop payload size function because
// `addGraphPolicies` is only used for cleartext intermediate
// hops in a route.
inboundFee := models.NewInboundFeeFromWire(
channel.InboundFee,
)
u.addPolicy(
channel.OtherNode, channel.InPolicy, inboundFee,
channel.Capacity, defaultHopPayloadSize, nil,
)
return nil
}
// Iterate over all channels of the to node.
return g.ForEachNodeDirectedChannel(u.toNode, cb)
}
// unifiedEdge is the individual channel data that is kept inside an edgeUnifier
// object.
type unifiedEdge struct {
policy *models.CachedEdgePolicy
capacity btcutil.Amount
inboundFees models.InboundFee
// hopPayloadSize supplies an edge with the ability to calculate the
// exact payload size if this edge would be included in a route. This
// is needed because hops of a blinded path differ in their payload
// structure compared to cleartext hops.
hopPayloadSizeFn PayloadSizeFunc
// blindedPayment if set, is the BlindedPayment that this edge was
// derived from originally.
blindedPayment *BlindedPayment
}
// newUnifiedEdge constructs a new unifiedEdge.
func newUnifiedEdge(policy *models.CachedEdgePolicy, capacity btcutil.Amount,
inboundFees models.InboundFee, hopPayloadSizeFn PayloadSizeFunc,
blindedPayment *BlindedPayment) *unifiedEdge {
return &unifiedEdge{
policy: policy,
capacity: capacity,
inboundFees: inboundFees,
hopPayloadSizeFn: hopPayloadSizeFn,
blindedPayment: blindedPayment,
}
}
// amtInRange checks whether an amount falls within the valid range for a
// channel.
func (u *unifiedEdge) amtInRange(amt lnwire.MilliSatoshi) bool {
// If the capacity is available (non-light clients), skip channels that
// are too small.
if u.capacity > 0 &&
amt > lnwire.NewMSatFromSatoshis(u.capacity) {
log.Tracef("Not enough capacity: amt=%v, capacity=%v",
amt, u.capacity)
return false
}
// Skip channels for which this htlc is too large.
if u.policy.MessageFlags.HasMaxHtlc() &&
amt > u.policy.MaxHTLC {
log.Tracef("Exceeds policy's MaxHTLC: amt=%v, MaxHTLC=%v",
amt, u.policy.MaxHTLC)
return false
}
// Skip channels for which this htlc is too small.
if amt < u.policy.MinHTLC {
log.Tracef("below policy's MinHTLC: amt=%v, MinHTLC=%v",
amt, u.policy.MinHTLC)
return false
}
return true
}
// edgeUnifier is an object that covers all channels between a pair of nodes.
type edgeUnifier struct {
edges []*unifiedEdge
localChan bool
}
// getEdge returns the optimal unified edge to use for this connection given a
// specific amount to send. It differentiates between local and network
// channels.
func (u *edgeUnifier) getEdge(netAmtReceived lnwire.MilliSatoshi,
bandwidthHints bandwidthHints,
nextOutFee lnwire.MilliSatoshi) *unifiedEdge {
if u.localChan {
return u.getEdgeLocal(
netAmtReceived, bandwidthHints, nextOutFee,
)
}
return u.getEdgeNetwork(netAmtReceived, nextOutFee)
}
// calcCappedInboundFee calculates the inbound fee for a channel, taking into
// account the total node fee for the "to" node.
func calcCappedInboundFee(edge *unifiedEdge, amt lnwire.MilliSatoshi,
nextOutFee lnwire.MilliSatoshi) int64 {
// Calculate the inbound fee charged for the amount that passes over the
// channel.
inboundFee := edge.inboundFees.CalcFee(amt)
// Take into account that the total node fee cannot be negative.
if inboundFee < -int64(nextOutFee) {
inboundFee = -int64(nextOutFee)
}
return inboundFee
}
// getEdgeLocal returns the optimal unified edge to use for this local
// connection given a specific amount to send.
func (u *edgeUnifier) getEdgeLocal(netAmtReceived lnwire.MilliSatoshi,
bandwidthHints bandwidthHints,
nextOutFee lnwire.MilliSatoshi) *unifiedEdge {
var (
bestEdge *unifiedEdge
maxBandwidth lnwire.MilliSatoshi
)
for _, edge := range u.edges {
// Calculate the inbound fee charged at the receiving node.
inboundFee := calcCappedInboundFee(
edge, netAmtReceived, nextOutFee,
)
// Add inbound fee to get to the amount that is sent over the
// local channel.
amt := netAmtReceived + lnwire.MilliSatoshi(inboundFee)
// Check valid amount range for the channel. We skip this test
// for payments with custom HTLC data, as the amount sent on
// the BTC layer may differ from the amount that is actually
// forwarded in custom channels.
if bandwidthHints.firstHopCustomBlob().IsNone() &&
!edge.amtInRange(amt) {
log.Debugf("Amount %v not in range for edge %v",
netAmtReceived, edge.policy.ChannelID)
continue
}
// For local channels, there is no fee to pay or an extra time
// lock. We only consider the currently available bandwidth for
// channel selection. The disabled flag is ignored for local
// channels.
// Retrieve bandwidth for this local channel. If not
// available, assume this channel has enough bandwidth.
//
// TODO(joostjager): Possibly change to skipping this
// channel. The bandwidth hint is expected to be
// available.
bandwidth, ok := bandwidthHints.availableChanBandwidth(
edge.policy.ChannelID, amt,
)
if !ok {
log.Debugf("Cannot get bandwidth for edge %v, use max "+
"instead", edge.policy.ChannelID)
bandwidth = lnwire.MaxMilliSatoshi
}
// TODO(yy): if the above `!ok` is chosen, we'd have
// `bandwidth` to be the max value, which will end up having
// the `maxBandwidth` to be have the largest value and this
// edge will be the chosen one. This is wrong in two ways,
// 1. we need to understand why `availableChanBandwidth` cannot
// find bandwidth for this edge as something is wrong with this
// channel, and,
// 2. this edge is likely NOT the local channel with the
// highest available bandwidth.
//
// Skip channels that can't carry the payment.
if amt > bandwidth {
log.Debugf("Skipped edge %v: not enough bandwidth, "+
"bandwidth=%v, amt=%v", edge.policy.ChannelID,
bandwidth, amt)
continue
}
// We pick the local channel with the highest available
// bandwidth, to maximize the success probability. It can be
// that the channel state changes between querying the bandwidth
// hints and sending out the htlc.
if bandwidth < maxBandwidth {
log.Debugf("Skipped edge %v: not max bandwidth, "+
"bandwidth=%v, maxBandwidth=%v",
edge.policy.ChannelID, bandwidth, maxBandwidth)
continue
}
maxBandwidth = bandwidth
// Update best edge.
bestEdge = newUnifiedEdge(
edge.policy, edge.capacity, edge.inboundFees,
edge.hopPayloadSizeFn, edge.blindedPayment,
)
}
return bestEdge
}
// getEdgeNetwork returns the optimal unified edge to use for this connection
// given a specific amount to send. The goal is to return a unified edge with a
// policy that maximizes the probability of a successful forward in a non-strict
// forwarding context.
func (u *edgeUnifier) getEdgeNetwork(netAmtReceived lnwire.MilliSatoshi,
nextOutFee lnwire.MilliSatoshi) *unifiedEdge {
var (
bestPolicy *unifiedEdge
maxFee int64 = math.MinInt64
maxTimelock uint16
maxCapMsat lnwire.MilliSatoshi
hopPayloadSizeFn PayloadSizeFunc
)
for _, edge := range u.edges {
// Calculate the inbound fee charged at the receiving node.
inboundFee := calcCappedInboundFee(
edge, netAmtReceived, nextOutFee,
)
// Add inbound fee to get to the amount that is sent over the
// channel.
amt := netAmtReceived + lnwire.MilliSatoshi(inboundFee)
// Check valid amount range for the channel.
if !edge.amtInRange(amt) {
log.Debugf("Amount %v not in range for edge %v",
amt, edge.policy.ChannelID)
continue
}
// For network channels, skip the disabled ones.
edgeFlags := edge.policy.ChannelFlags
isDisabled := edgeFlags&lnwire.ChanUpdateDisabled != 0
if isDisabled {
log.Debugf("Skipped edge %v due to it being disabled",
edge.policy.ChannelID)
continue
}
// Track the maximal capacity for usable channels. If we don't
// know the capacity, we fall back to MaxHTLC.
capMsat := lnwire.NewMSatFromSatoshis(edge.capacity)
if capMsat == 0 && edge.policy.MessageFlags.HasMaxHtlc() {
log.Tracef("No capacity available for channel %v, "+
"using MaxHtlcMsat (%v) as a fallback.",
edge.policy.ChannelID, edge.policy.MaxHTLC)
capMsat = edge.policy.MaxHTLC
}
maxCapMsat = max(capMsat, maxCapMsat)
// Track the maximum time lock of all channels that are
// candidate for non-strict forwarding at the routing node.
maxTimelock = max(maxTimelock, edge.policy.TimeLockDelta)
outboundFee := int64(edge.policy.ComputeFee(amt))
fee := outboundFee + inboundFee
// Use the policy that results in the highest fee for this
// specific amount.
if fee < maxFee {
log.Debugf("Skipped edge %v due to it produces less "+
"fee: fee=%v, maxFee=%v",
edge.policy.ChannelID, fee, maxFee)
continue
}
maxFee = fee
bestPolicy = newUnifiedEdge(
edge.policy, 0, edge.inboundFees, nil,
edge.blindedPayment,
)
// The payload size function for edges to a connected peer is
// always the same hence there is not need to find the maximum.
// This also counts for blinded edges where we only have one
// edge to a blinded peer.
hopPayloadSizeFn = edge.hopPayloadSizeFn
}
// Return early if no channel matches.
if bestPolicy == nil {
return nil
}
// We have already picked the highest fee that could be required for
// non-strict forwarding. To also cover the case where a lower fee
// channel requires a longer time lock, we modify the policy by setting
// the maximum encountered time lock. Note that this results in a
// synthetic policy that is not actually present on the routing node.
//
// The reason we do this, is that we try to maximize the chance that we
// get forwarded. Because we penalize pair-wise, there won't be a second
// chance for this node pair. But this is all only needed for nodes that
// have distinct policies for channels to the same peer.
policyCopy := *bestPolicy.policy
policyCopy.TimeLockDelta = maxTimelock
modifiedEdge := newUnifiedEdge(
&policyCopy, maxCapMsat.ToSatoshis(), bestPolicy.inboundFees,
hopPayloadSizeFn, bestPolicy.blindedPayment,
)
return modifiedEdge
}
// minAmt returns the minimum amount that can be forwarded on this connection.
func (u *edgeUnifier) minAmt() lnwire.MilliSatoshi {
minAmount := lnwire.MaxMilliSatoshi
for _, edge := range u.edges {
minAmount = min(minAmount, edge.policy.MinHTLC)
}
return minAmount
}
package rpcperms
import (
"context"
"errors"
"fmt"
"sync"
"sync/atomic"
"github.com/btcsuite/btclog/v2"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/lightningnetwork/lnd/monitoring"
"github.com/lightningnetwork/lnd/subscribe"
"google.golang.org/grpc"
"gopkg.in/macaroon-bakery.v2/bakery"
)
// rpcState is an enum that we use to keep track of the current RPC service
// state. This will transition as we go from startup to unlocking the wallet,
// and finally fully active.
type rpcState uint8
const (
// waitingToStart indicates that we're at the beginning of the startup
// process. In a cluster environment this may mean that we're waiting to
// become the leader in which case RPC calls will be disabled until
// this instance has been elected as leader.
waitingToStart rpcState = iota
// walletNotCreated is the starting state if the RPC server is active,
// but the wallet is not yet created. In this state we'll only allow
// calls to the WalletUnlockerService.
walletNotCreated
// walletLocked indicates the RPC server is active, but the wallet is
// locked. In this state we'll only allow calls to the
// WalletUnlockerService.
walletLocked
// walletUnlocked means that the wallet has been unlocked, but the full
// RPC server is not yet ready.
walletUnlocked
// rpcActive means that the RPC server is ready to accept calls.
rpcActive
// serverActive means that the lnd server is ready to accept calls.
serverActive
)
var (
// ErrWaitingToStart is returned if LND is still waiting to start,
// possibly blocked until elected as the leader.
ErrWaitingToStart = fmt.Errorf("waiting to start, RPC services not " +
"available")
// ErrNoWallet is returned if the wallet does not exist.
ErrNoWallet = fmt.Errorf("wallet not created, create one to enable " +
"full RPC access")
// ErrWalletLocked is returned if the wallet is locked and any service
// other than the WalletUnlocker is called.
ErrWalletLocked = fmt.Errorf("wallet locked, unlock it to enable " +
"full RPC access")
// ErrWalletUnlocked is returned if the WalletUnlocker service is
// called when the wallet already has been unlocked.
ErrWalletUnlocked = fmt.Errorf("wallet already unlocked, " +
"WalletUnlocker service is no longer available")
// ErrRPCStarting is returned if the wallet has been unlocked but the
// RPC server is not yet ready to accept calls.
ErrRPCStarting = fmt.Errorf("the RPC server is in the process of " +
"starting up, but not yet ready to accept calls")
// macaroonWhitelist defines methods that we don't require macaroons to
// access. We also allow these methods to be called even if not all
// mandatory middlewares are registered yet. If the wallet is locked
// then a middleware cannot register itself, creating an impossible
// situation. Also, a middleware might want to check the state of lnd
// by calling the State service before it registers itself. So we also
// need to exclude those calls from the mandatory middleware check.
macaroonWhitelist = map[string]struct{}{
// We allow all calls to the WalletUnlocker without macaroons.
"/lnrpc.WalletUnlocker/GenSeed": {},
"/lnrpc.WalletUnlocker/InitWallet": {},
"/lnrpc.WalletUnlocker/UnlockWallet": {},
"/lnrpc.WalletUnlocker/ChangePassword": {},
// The State service must be available at all times, even
// before we can check macaroons, so we whitelist it.
"/lnrpc.State/SubscribeState": {},
"/lnrpc.State/GetState": {},
}
)
// InterceptorChain is a struct that can be added to the running GRPC server,
// intercepting API calls. This is useful for logging, enforcing permissions,
// supporting middleware etc. The following diagram shows the order of each
// interceptor in the chain and when exactly requests/responses are intercepted
// and forwarded to external middleware for approval/modification. Middleware in
// general can only intercept gRPC requests/responses that are sent by the
// client with a macaroon that contains a custom caveat that is supported by one
// of the registered middlewares.
//
// |
// | gRPC request from client
// |
// +---v--------------------------------+
// | InterceptorChain |
// +-+----------------------------------+
// | Log Interceptor |
// +----------------------------------+
// | RPC State Interceptor |
// +----------------------------------+
// | Macaroon Interceptor |
// +----------------------------------+--------> +---------------------+
// | RPC Macaroon Middleware Handler |<-------- | External Middleware |
// +----------------------------------+ | - modify request |
// | Prometheus Interceptor | +---------------------+
// +-+--------------------------------+
// | validated gRPC request from client
// +---v--------------------------------+
// | main gRPC server |
// +---+--------------------------------+
// |
// | original gRPC request to client
// |
// +---v--------------------------------+--------> +---------------------+
// | RPC Macaroon Middleware Handler |<-------- | External Middleware |
// +---+--------------------------------+ | - modify response |
// | +---------------------+
// | edited gRPC request to client
// v
type InterceptorChain struct {
// lastRequestID is the ID of the last gRPC request or stream that was
// intercepted by the middleware interceptor.
//
// NOTE: Must be used atomically!
lastRequestID uint64
// Required by the grpc-gateway/v2 library for forward compatibility.
lnrpc.UnimplementedStateServer
started sync.Once
stopped sync.Once
// state is the current RPC state of our RPC server.
state rpcState
// ntfnServer is a subscription server we use to notify clients of the
// State service when the state changes.
ntfnServer *subscribe.Server
// noMacaroons should be set true if we don't want to check macaroons.
noMacaroons bool
// svc is the macaroon service used to enforce permissions in case
// macaroons are used.
svc *macaroons.Service
// permissionMap is the permissions to enforce if macaroons are used.
permissionMap map[string][]bakery.Op
// rpcsLog is the logger used to log calls to the RPCs intercepted.
rpcsLog btclog.Logger
// registeredMiddleware is a slice of all macaroon permission based RPC
// middleware clients that are currently registered. The
// registeredMiddlewareNames can be used to find the index of a specific
// interceptor within the registeredMiddleware slide using the name of
// the interceptor as the key. The reason for using these two separate
// structures is so that the order in which interceptors are run is
// the same as the order in which they were registered.
registeredMiddleware []*MiddlewareHandler
// registeredMiddlewareNames is a map of registered middleware names
// to the index at which they are stored in the registeredMiddleware
// map.
registeredMiddlewareNames map[string]int
// mandatoryMiddleware is a list of all middleware that is considered to
// be mandatory. If any of them is not registered then all RPC requests
// (except for the macaroon white listed methods and the middleware
// registration itself) are blocked. This is a security feature to make
// sure that requests can't just go through unobserved/unaudited if a
// middleware crashes.
mandatoryMiddleware []string
quit chan struct{}
sync.RWMutex
}
// A compile time check to ensure that InterceptorChain fully implements the
// StateServer gRPC service.
var _ lnrpc.StateServer = (*InterceptorChain)(nil)
// NewInterceptorChain creates a new InterceptorChain.
func NewInterceptorChain(log btclog.Logger, noMacaroons bool,
mandatoryMiddleware []string) *InterceptorChain {
return &InterceptorChain{
state: waitingToStart,
ntfnServer: subscribe.NewServer(),
noMacaroons: noMacaroons,
permissionMap: make(map[string][]bakery.Op),
rpcsLog: log,
registeredMiddlewareNames: make(map[string]int),
mandatoryMiddleware: mandatoryMiddleware,
quit: make(chan struct{}),
}
}
// Start starts the InterceptorChain, which is needed to start the state
// subscription server it powers.
func (r *InterceptorChain) Start() error {
var err error
r.started.Do(func() {
err = r.ntfnServer.Start()
})
return err
}
// Stop stops the InterceptorChain and its internal state subscription server.
func (r *InterceptorChain) Stop() error {
var err error
r.stopped.Do(func() {
close(r.quit)
err = r.ntfnServer.Stop()
})
return err
}
// SetWalletNotCreated moves the RPC state from either waitingToStart to
// walletNotCreated.
func (r *InterceptorChain) SetWalletNotCreated() {
r.Lock()
defer r.Unlock()
r.state = walletNotCreated
_ = r.ntfnServer.SendUpdate(r.state)
}
// SetWalletLocked moves the RPC state from either walletNotCreated to
// walletLocked.
func (r *InterceptorChain) SetWalletLocked() {
r.Lock()
defer r.Unlock()
r.state = walletLocked
_ = r.ntfnServer.SendUpdate(r.state)
}
// SetWalletUnlocked moves the RPC state from either walletNotCreated or
// walletLocked to walletUnlocked.
func (r *InterceptorChain) SetWalletUnlocked() {
r.Lock()
defer r.Unlock()
r.state = walletUnlocked
_ = r.ntfnServer.SendUpdate(r.state)
}
// SetRPCActive moves the RPC state from walletUnlocked to rpcActive.
func (r *InterceptorChain) SetRPCActive() {
r.Lock()
defer r.Unlock()
r.state = rpcActive
_ = r.ntfnServer.SendUpdate(r.state)
}
// SetServerActive moves the RPC state from walletUnlocked to rpcActive.
func (r *InterceptorChain) SetServerActive() {
r.Lock()
defer r.Unlock()
r.state = serverActive
_ = r.ntfnServer.SendUpdate(r.state)
}
// rpcStateToWalletState converts rpcState to lnrpc.WalletState. Returns
// WAITING_TO_START and an error on conversion error.
func rpcStateToWalletState(state rpcState) (lnrpc.WalletState, error) {
const defaultState = lnrpc.WalletState_WAITING_TO_START
var walletState lnrpc.WalletState
switch state {
case waitingToStart:
walletState = lnrpc.WalletState_WAITING_TO_START
case walletNotCreated:
walletState = lnrpc.WalletState_NON_EXISTING
case walletLocked:
walletState = lnrpc.WalletState_LOCKED
case walletUnlocked:
walletState = lnrpc.WalletState_UNLOCKED
case rpcActive:
walletState = lnrpc.WalletState_RPC_ACTIVE
case serverActive:
walletState = lnrpc.WalletState_SERVER_ACTIVE
default:
return defaultState, fmt.Errorf("unknown wallet state %v", state)
}
return walletState, nil
}
// SubscribeState subscribes to the state of the wallet. The current wallet
// state will always be delivered immediately.
//
// NOTE: Part of the StateService interface.
func (r *InterceptorChain) SubscribeState(_ *lnrpc.SubscribeStateRequest,
stream lnrpc.State_SubscribeStateServer) error {
sendStateUpdate := func(state rpcState) error {
walletState, err := rpcStateToWalletState(state)
if err != nil {
return err
}
return stream.Send(&lnrpc.SubscribeStateResponse{
State: walletState,
})
}
// Subscribe to state updates.
client, err := r.ntfnServer.Subscribe()
if err != nil {
return err
}
defer client.Cancel()
// Always start by sending the current state.
r.RLock()
state := r.state
r.RUnlock()
if err := sendStateUpdate(state); err != nil {
return err
}
for {
select {
case e := <-client.Updates():
newState := e.(rpcState)
// Ignore already sent state.
if newState == state {
continue
}
state = newState
err := sendStateUpdate(state)
if err != nil {
return err
}
// The response stream's context for whatever reason has been
// closed. If context is closed by an exceeded deadline we will
// return an error.
case <-stream.Context().Done():
if errors.Is(stream.Context().Err(), context.Canceled) {
return nil
}
return stream.Context().Err()
case <-r.quit:
return fmt.Errorf("server exiting")
}
}
}
// GetState returns the current wallet state.
func (r *InterceptorChain) GetState(_ context.Context,
_ *lnrpc.GetStateRequest) (*lnrpc.GetStateResponse, error) {
r.RLock()
state := r.state
r.RUnlock()
walletState, err := rpcStateToWalletState(state)
if err != nil {
return nil, err
}
return &lnrpc.GetStateResponse{
State: walletState,
}, nil
}
// AddMacaroonService adds a macaroon service to the interceptor. After this is
// done every RPC call made will have to pass a valid macaroon to be accepted.
func (r *InterceptorChain) AddMacaroonService(svc *macaroons.Service) {
r.Lock()
defer r.Unlock()
r.svc = svc
}
// MacaroonService returns the currently registered macaroon service. This might
// be nil if none was registered (yet).
func (r *InterceptorChain) MacaroonService() *macaroons.Service {
r.RLock()
defer r.RUnlock()
return r.svc
}
// AddPermission adds a new macaroon rule for the given method.
func (r *InterceptorChain) AddPermission(method string, ops []bakery.Op) error {
r.Lock()
defer r.Unlock()
if _, ok := r.permissionMap[method]; ok {
return fmt.Errorf("detected duplicate macaroon constraints "+
"for path: %v", method)
}
r.permissionMap[method] = ops
return nil
}
// Permissions returns the current set of macaroon permissions.
func (r *InterceptorChain) Permissions() map[string][]bakery.Op {
r.RLock()
defer r.RUnlock()
// Make a copy under the read lock to avoid races.
c := make(map[string][]bakery.Op)
for k, v := range r.permissionMap {
s := make([]bakery.Op, len(v))
copy(s, v)
c[k] = s
}
return c
}
// RegisterMiddleware registers a new middleware that will handle request/
// response interception for all RPC messages that are initiated with a custom
// macaroon caveat. The name of the custom caveat a middleware is handling is
// also its unique identifier. Only one middleware can be registered for each
// custom caveat.
func (r *InterceptorChain) RegisterMiddleware(mw *MiddlewareHandler) error {
r.Lock()
defer r.Unlock()
// The name of the middleware is the unique identifier.
_, ok := r.registeredMiddlewareNames[mw.middlewareName]
if ok {
return fmt.Errorf("a middleware with the name '%s' is already "+
"registered", mw.middlewareName)
}
// For now, we only want one middleware per custom caveat name. If we
// allowed multiple middlewares handling the same caveat there would be
// a need for extra call chaining logic, and they could overwrite each
// other's responses.
for _, middleware := range r.registeredMiddleware {
if middleware.customCaveatName == mw.customCaveatName {
return fmt.Errorf("a middleware is already registered "+
"for the custom caveat name '%s': %v",
mw.customCaveatName, middleware.middlewareName)
}
}
r.registeredMiddleware = append(r.registeredMiddleware, mw)
index := len(r.registeredMiddleware) - 1
r.registeredMiddlewareNames[mw.middlewareName] = index
return nil
}
// RemoveMiddleware removes the middleware that handles the given custom caveat
// name.
func (r *InterceptorChain) RemoveMiddleware(middlewareName string) {
r.Lock()
defer r.Unlock()
log.Debugf("Removing middleware %s", middlewareName)
index, ok := r.registeredMiddlewareNames[middlewareName]
if !ok {
return
}
delete(r.registeredMiddlewareNames, middlewareName)
r.registeredMiddleware = append(
r.registeredMiddleware[:index],
r.registeredMiddleware[index+1:]...,
)
// Re-initialise the middleware look-up map with the updated indexes.
r.registeredMiddlewareNames = make(map[string]int)
for i, mw := range r.registeredMiddleware {
r.registeredMiddlewareNames[mw.middlewareName] = i
}
}
// CustomCaveatSupported makes sure a middleware that handles the given custom
// caveat name is registered. If none is, an error is returned, signalling to
// the macaroon bakery and its validator to reject macaroons that have a custom
// caveat with that name.
//
// NOTE: This method is part of the macaroons.CustomCaveatAcceptor interface.
func (r *InterceptorChain) CustomCaveatSupported(customCaveatName string) error {
r.RLock()
defer r.RUnlock()
// We only accept requests with a custom caveat if we also have a
// middleware registered that handles that custom caveat. That is
// crucial for security! Otherwise a request with an encumbered (=has
// restricted permissions based upon the custom caveat condition)
// macaroon would not be validated against the limitations that the
// custom caveat implicate. Since the map is keyed by the _name_ of the
// middleware, we need to loop through all of them to see if one has
// the given custom macaroon caveat name.
for _, middleware := range r.registeredMiddleware {
if middleware.customCaveatName == customCaveatName {
return nil
}
}
return fmt.Errorf("cannot accept macaroon with custom caveat '%s', "+
"no middleware registered to handle it", customCaveatName)
}
// CreateServerOpts creates the GRPC server options that can be added to a GRPC
// server in order to add this InterceptorChain.
func (r *InterceptorChain) CreateServerOpts() []grpc.ServerOption {
var unaryInterceptors []grpc.UnaryServerInterceptor
var strmInterceptors []grpc.StreamServerInterceptor
// The first interceptors we'll add to the chain is our logging
// interceptors, so we can automatically log all errors that happen
// during RPC calls.
unaryInterceptors = append(
unaryInterceptors, errorLogUnaryServerInterceptor(r.rpcsLog),
)
strmInterceptors = append(
strmInterceptors, errorLogStreamServerInterceptor(r.rpcsLog),
)
// Next we'll add our RPC state check interceptors, that will check
// whether the attempted call is allowed in the current state.
unaryInterceptors = append(
unaryInterceptors, r.rpcStateUnaryServerInterceptor(),
)
strmInterceptors = append(
strmInterceptors, r.rpcStateStreamServerInterceptor(),
)
// We'll add the macaroon interceptors. If macaroons aren't disabled,
// then these interceptors will enforce macaroon authentication.
unaryInterceptors = append(
unaryInterceptors, r.MacaroonUnaryServerInterceptor(),
)
strmInterceptors = append(
strmInterceptors, r.MacaroonStreamServerInterceptor(),
)
// Next, we'll add the interceptors for our custom macaroon caveat based
// middleware.
unaryInterceptors = append(
unaryInterceptors, r.middlewareUnaryServerInterceptor(),
)
strmInterceptors = append(
strmInterceptors, r.middlewareStreamServerInterceptor(),
)
// Get interceptors for Prometheus to gather gRPC performance metrics.
// If monitoring is disabled, GetPromInterceptors() will return empty
// slices.
promUnaryInterceptors, promStrmInterceptors :=
monitoring.GetPromInterceptors()
// Concatenate the slices of unary and stream interceptors respectively.
unaryInterceptors = append(unaryInterceptors, promUnaryInterceptors...)
strmInterceptors = append(strmInterceptors, promStrmInterceptors...)
// Create server options from the interceptors we just set up.
chainedUnary := grpc_middleware.WithUnaryServerChain(
unaryInterceptors...,
)
chainedStream := grpc_middleware.WithStreamServerChain(
strmInterceptors...,
)
serverOpts := []grpc.ServerOption{chainedUnary, chainedStream}
return serverOpts
}
// errorLogUnaryServerInterceptor is a simple UnaryServerInterceptor that will
// automatically log any errors that occur when serving a client's unary
// request.
func errorLogUnaryServerInterceptor(logger btclog.Logger) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (interface{}, error) {
resp, err := handler(ctx, req)
if err != nil {
// TODO(roasbeef): also log request details?
logger.Errorf("[%v]: %v", info.FullMethod, err)
}
return resp, err
}
}
// errorLogStreamServerInterceptor is a simple StreamServerInterceptor that
// will log any errors that occur while processing a client or server streaming
// RPC.
func errorLogStreamServerInterceptor(logger btclog.Logger) grpc.StreamServerInterceptor {
return func(srv interface{}, ss grpc.ServerStream,
info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
err := handler(srv, ss)
if err != nil {
logger.Errorf("[%v]: %v", info.FullMethod, err)
}
return err
}
}
// checkMacaroon validates that the context contains the macaroon needed to
// invoke the given RPC method.
func (r *InterceptorChain) checkMacaroon(ctx context.Context,
fullMethod string) error {
// If noMacaroons is set, we'll always allow the call.
if r.noMacaroons {
return nil
}
// Check whether the method is whitelisted, if so we'll allow it
// regardless of macaroons.
_, ok := macaroonWhitelist[fullMethod]
if ok {
return nil
}
r.RLock()
svc := r.svc
r.RUnlock()
// If the macaroon service is not yet active, we cannot allow
// the call.
if svc == nil {
return fmt.Errorf("unable to determine macaroon permissions")
}
r.RLock()
uriPermissions, ok := r.permissionMap[fullMethod]
r.RUnlock()
if !ok {
return fmt.Errorf("%s: unknown permissions required for method",
fullMethod)
}
// Find out if there is an external validator registered for
// this method. Fall back to the internal one if there isn't.
validator, ok := svc.ExternalValidators[fullMethod]
if !ok {
validator = svc
}
// Now that we know what validator to use, let it do its work.
return validator.ValidateMacaroon(ctx, uriPermissions, fullMethod)
}
// MacaroonUnaryServerInterceptor is a GRPC interceptor that checks whether the
// request is authorized by the included macaroons.
func (r *InterceptorChain) MacaroonUnaryServerInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (interface{}, error) {
// Check macaroons.
if err := r.checkMacaroon(ctx, info.FullMethod); err != nil {
return nil, err
}
return handler(ctx, req)
}
}
// MacaroonStreamServerInterceptor is a GRPC interceptor that checks whether
// the request is authorized by the included macaroons.
func (r *InterceptorChain) MacaroonStreamServerInterceptor() grpc.StreamServerInterceptor {
return func(srv interface{}, ss grpc.ServerStream,
info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
// Check macaroons.
err := r.checkMacaroon(ss.Context(), info.FullMethod)
if err != nil {
return err
}
return handler(srv, ss)
}
}
// checkRPCState checks whether a call to the given server is allowed in the
// current RPC state.
func (r *InterceptorChain) checkRPCState(srv interface{}) error {
// The StateService is being accessed, we allow the call regardless of
// the current state.
_, ok := srv.(lnrpc.StateServer)
if ok {
return nil
}
r.RLock()
state := r.state
r.RUnlock()
switch state {
// Do not accept any RPC calls (unless to the state service) until LND
// has not started.
case waitingToStart:
return ErrWaitingToStart
// If the wallet does not exists, only calls to the WalletUnlocker are
// accepted.
case walletNotCreated:
_, ok := srv.(lnrpc.WalletUnlockerServer)
if !ok {
return ErrNoWallet
}
// If the wallet is locked, only calls to the WalletUnlocker are
// accepted.
case walletLocked:
_, ok := srv.(lnrpc.WalletUnlockerServer)
if !ok {
return ErrWalletLocked
}
// If the wallet is unlocked, but the RPC not yet active, we reject.
case walletUnlocked:
_, ok := srv.(lnrpc.WalletUnlockerServer)
if ok {
return ErrWalletUnlocked
}
return ErrRPCStarting
// If the RPC server or lnd server is active, we allow calls to any
// service except the WalletUnlocker.
case rpcActive, serverActive:
_, ok := srv.(lnrpc.WalletUnlockerServer)
if ok {
return ErrWalletUnlocked
}
default:
return fmt.Errorf("unknown RPC state: %v", state)
}
return nil
}
// rpcStateUnaryServerInterceptor is a GRPC interceptor that checks whether
// calls to the given gGRPC server is allowed in the current rpc state.
func (r *InterceptorChain) rpcStateUnaryServerInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (interface{}, error) {
r.rpcsLog.Debugf("[%v] requested", info.FullMethod)
if err := r.checkRPCState(info.Server); err != nil {
return nil, err
}
return handler(ctx, req)
}
}
// rpcStateStreamServerInterceptor is a GRPC interceptor that checks whether
// calls to the given gGRPC server is allowed in the current rpc state.
func (r *InterceptorChain) rpcStateStreamServerInterceptor() grpc.StreamServerInterceptor {
return func(srv interface{}, ss grpc.ServerStream,
info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
r.rpcsLog.Debugf("[%v] requested", info.FullMethod)
if err := r.checkRPCState(srv); err != nil {
return err
}
return handler(srv, ss)
}
}
// middlewareUnaryServerInterceptor is a unary gRPC interceptor that intercepts
// all requests and responses that are sent with a macaroon containing a custom
// caveat condition that is handled by registered middleware.
func (r *InterceptorChain) middlewareUnaryServerInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context,
req interface{}, info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (interface{}, error) {
// Make sure we don't allow any requests through if one of the
// mandatory middlewares is missing.
fullMethod := info.FullMethod
if err := r.checkMandatoryMiddleware(fullMethod); err != nil {
return nil, err
}
// If there is no middleware registered, we don't need to
// intercept anything.
if !r.middlewareRegistered() {
return handler(ctx, req)
}
requestID := atomic.AddUint64(&r.lastRequestID, 1)
req, err := r.interceptMessage(
ctx, TypeRequest, requestID, false, info.FullMethod,
req,
)
if err != nil {
return nil, err
}
// Call the handler, which executes the request against lnd.
lndResp, lndErr := handler(ctx, req)
if lndErr != nil {
// The call to lnd ended in an error and not a normal
// proto message response. Send the error to the
// interceptor as well to inform about the abnormal
// termination of the stream and to give the option to
// replace the error message with a custom one.
replacedErr, err := r.interceptMessage(
ctx, TypeResponse, requestID, false,
info.FullMethod, lndErr,
)
if err != nil {
return nil, err
}
return lndResp, replacedErr.(error)
}
return r.interceptMessage(
ctx, TypeResponse, requestID, false, info.FullMethod,
lndResp,
)
}
}
// middlewareStreamServerInterceptor is a streaming gRPC interceptor that
// intercepts all requests and responses that are sent with a macaroon
// containing a custom caveat condition that is handled by registered
// middleware.
func (r *InterceptorChain) middlewareStreamServerInterceptor() grpc.StreamServerInterceptor {
return func(srv interface{},
ss grpc.ServerStream, info *grpc.StreamServerInfo,
handler grpc.StreamHandler) error {
// Don't intercept the interceptor itself which is a streaming
// RPC too!
fullMethod := info.FullMethod
if fullMethod == lnrpc.RegisterRPCMiddlewareURI {
return handler(srv, ss)
}
// Make sure we don't allow any requests through if one of the
// mandatory middlewares is missing. We add this check here to
// make sure the middleware registration itself can still be
// called.
if err := r.checkMandatoryMiddleware(fullMethod); err != nil {
return err
}
// If there is no middleware registered, we don't need to
// intercept anything.
if !r.middlewareRegistered() {
return handler(srv, ss)
}
// To give the middleware a chance to accept or reject the
// establishment of the stream itself (and not only when the
// first message is sent on the stream), we send an intercept
// request for the stream auth now:
msg, err := NewStreamAuthInterceptionRequest(
ss.Context(), info.FullMethod,
)
if err != nil {
return err
}
requestID := atomic.AddUint64(&r.lastRequestID, 1)
err = r.acceptStream(requestID, msg)
if err != nil {
return err
}
wrappedSS := &serverStreamWrapper{
ServerStream: ss,
requestID: requestID,
fullMethod: info.FullMethod,
interceptor: r,
}
// Call the stream handler, which will block as long as the
// stream is alive.
lndErr := handler(srv, wrappedSS)
if lndErr != nil {
// This is an error being returned from lnd. Send it to
// the interceptor as well to inform about the abnormal
// termination of the stream and to give the option to
// replace the error message with a custom one.
replacedErr, err := r.interceptMessage(
ss.Context(), TypeResponse, requestID,
true, info.FullMethod, lndErr,
)
if err != nil {
return err
}
return replacedErr.(error)
}
// Normal/successful termination of the stream.
return nil
}
}
// checkMandatoryMiddleware makes sure that each of the middlewares declared as
// mandatory is currently registered.
func (r *InterceptorChain) checkMandatoryMiddleware(fullMethod string) error {
r.RLock()
defer r.RUnlock()
// Allow calls that are whitelisted for macaroons as well, otherwise we
// get into impossible situations where the wallet is locked but the
// unlock call is denied because the middleware isn't registered. But
// the middleware cannot register itself because the wallet is locked.
if _, ok := macaroonWhitelist[fullMethod]; ok {
return nil
}
// Not a white listed call so make sure every mandatory middleware is
// currently connected to lnd.
for _, name := range r.mandatoryMiddleware {
if _, ok := r.registeredMiddlewareNames[name]; !ok {
return fmt.Errorf("mandatory middleware '%s' is "+
"currently not registered, not allowing any "+
"RPC calls", name)
}
}
return nil
}
// middlewareRegistered returns true if there is at least one middleware
// currently registered.
func (r *InterceptorChain) middlewareRegistered() bool {
r.RLock()
defer r.RUnlock()
return len(r.registeredMiddleware) > 0
}
// acceptStream sends an intercept request to all middlewares that have
// registered for it. This means either a middleware has requested read-only
// access or the request actually has a macaroon with a caveat the middleware
// registered for.
func (r *InterceptorChain) acceptStream(requestID uint64,
msg *InterceptionRequest) error {
r.RLock()
defer r.RUnlock()
for _, middleware := range r.registeredMiddleware {
// If there is a custom caveat in the macaroon, make sure the
// middleware registered for it. Or if a middleware registered
// for read-only mode, it also gets the request.
hasCustomCaveat := macaroons.HasCustomCaveat(
msg.Macaroon, middleware.customCaveatName,
)
if !hasCustomCaveat && !middleware.readOnly {
continue
}
msg.CustomCaveatCondition = macaroons.GetCustomCaveatCondition(
msg.Macaroon, middleware.customCaveatName,
)
resp, err := middleware.intercept(requestID, msg)
// Error during interception itself.
if err != nil {
return err
}
// Error returned from middleware client.
if resp.err != nil {
return resp.err
}
}
return nil
}
// interceptMessage sends out an intercept request for an RPC response. Since
// middleware that hasn't registered for the read-only mode has the option to
// overwrite/replace the message, this needs to be handled differently than the
// auth path above.
func (r *InterceptorChain) interceptMessage(ctx context.Context,
interceptType InterceptType, requestID uint64, isStream bool,
fullMethod string, m interface{}) (interface{}, error) {
r.RLock()
defer r.RUnlock()
currentMessage := m
for _, middleware := range r.registeredMiddleware {
msg, err := NewMessageInterceptionRequest(
ctx, interceptType, isStream, fullMethod,
currentMessage,
)
if err != nil {
return nil, err
}
// If there is a custom caveat in the macaroon, make sure the
// middleware registered for it. Or if a middleware registered
// for read-only mode, it also gets the request.
hasCustomCaveat := macaroons.HasCustomCaveat(
msg.Macaroon, middleware.customCaveatName,
)
if !hasCustomCaveat && !middleware.readOnly {
continue
}
msg.CustomCaveatCondition = macaroons.GetCustomCaveatCondition(
msg.Macaroon, middleware.customCaveatName,
)
resp, err := middleware.intercept(requestID, msg)
// Error during interception itself.
if err != nil {
return nil, err
}
// Error returned from middleware client.
if resp.err != nil {
return nil, resp.err
}
// The message was replaced, make sure the next middleware in
// line receives the updated message.
if !middleware.readOnly && resp.replace {
currentMessage = resp.replacement
}
}
return currentMessage, nil
}
// serverStreamWrapper is a struct that wraps a server stream in a way that all
// requests and responses can be intercepted individually.
type serverStreamWrapper struct {
// ServerStream is the stream that's being wrapped.
grpc.ServerStream
requestID uint64
fullMethod string
interceptor *InterceptorChain
}
// SendMsg is called when lnd sends a message to the client. This is wrapped to
// intercept streaming RPC responses.
func (w *serverStreamWrapper) SendMsg(m interface{}) error {
newMsg, err := w.interceptor.interceptMessage(
w.ServerStream.Context(), TypeResponse, w.requestID, true,
w.fullMethod, m,
)
if err != nil {
return err
}
return w.ServerStream.SendMsg(newMsg)
}
// RecvMsg is called when lnd wants to receive a message from the client. This
// is wrapped to intercept streaming RPC requests.
func (w *serverStreamWrapper) RecvMsg(m interface{}) error {
err := w.ServerStream.RecvMsg(m)
if err != nil {
return err
}
req, err := w.interceptor.interceptMessage(
w.ServerStream.Context(), TypeRequest, w.requestID, true,
w.fullMethod, m,
)
if err != nil {
return err
}
return replaceProtoMsg(m, req)
}
package rpcperms
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// Subsystem defines the logging code for this subsystem.
const Subsystem = "RPCP"
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package rpcperms
import (
"context"
"encoding/hex"
"errors"
"fmt"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/macaroons"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"gopkg.in/macaroon.v2"
)
var (
// ErrShuttingDown is the error that's returned when the server is
// shutting down and a request cannot be served anymore.
ErrShuttingDown = errors.New("server shutting down")
// ErrTimeoutReached is the error that's returned if any of the
// middleware's tasks is not completed in the given time.
ErrTimeoutReached = errors.New("intercept timeout reached")
// errClientQuit is the error that's returned if the client closes the
// middleware communication stream before a request was fully handled.
errClientQuit = errors.New("interceptor RPC client quit")
)
// MiddlewareHandler is a type that communicates with a middleware over the
// established bi-directional RPC stream. It sends messages to the middleware
// whenever the custom business logic implemented there should give feedback to
// a request or response that's happening on the main gRPC server.
type MiddlewareHandler struct {
// lastMsgID is the ID of the last intercept message that was forwarded
// to the middleware.
//
// NOTE: Must be used atomically!
lastMsgID uint64
middlewareName string
readOnly bool
customCaveatName string
receive func() (*lnrpc.RPCMiddlewareResponse, error)
send func(request *lnrpc.RPCMiddlewareRequest) error
interceptRequests chan *interceptRequest
timeout time.Duration
// params are our current chain params.
params *chaincfg.Params
// done is closed when the rpc client terminates.
done chan struct{}
// quit is closed when lnd is shutting down.
quit chan struct{}
wg sync.WaitGroup
}
// NewMiddlewareHandler creates a new handler for the middleware with the given
// name and custom caveat name.
func NewMiddlewareHandler(name, customCaveatName string, readOnly bool,
receive func() (*lnrpc.RPCMiddlewareResponse, error),
send func(request *lnrpc.RPCMiddlewareRequest) error,
timeout time.Duration, params *chaincfg.Params,
quit chan struct{}) *MiddlewareHandler {
// We explicitly want to log this as a warning since intercepting any
// gRPC messages can also be used for malicious purposes and the user
// should be made aware of the risks.
log.Warnf("A new gRPC middleware with the name '%s' was registered "+
" with custom_macaroon_caveat='%s', read_only=%v. Make sure "+
"you trust the middleware author since that code will be able "+
"to intercept and possibly modify any gRPC messages sent/"+
"received to/from a client that has a macaroon with that "+
"custom caveat.", name, customCaveatName, readOnly)
return &MiddlewareHandler{
middlewareName: name,
customCaveatName: customCaveatName,
readOnly: readOnly,
receive: receive,
send: send,
interceptRequests: make(chan *interceptRequest),
timeout: timeout,
params: params,
done: make(chan struct{}),
quit: quit,
}
}
// intercept handles the full interception lifecycle of a single middleware
// event (stream authentication, request interception or response interception).
// The lifecycle consists of sending a message to the middleware, receiving a
// feedback on it and sending the feedback to the appropriate channel. All steps
// are guarded by the configured timeout to make sure a middleware cannot slow
// down requests too much.
func (h *MiddlewareHandler) intercept(requestID uint64,
req *InterceptionRequest) (*interceptResponse, error) {
respChan := make(chan *interceptResponse, 1)
newRequest := &interceptRequest{
requestID: requestID,
request: req,
response: respChan,
}
// timeout is the time after which intercept requests expire.
timeout := time.After(h.timeout)
// Send the request to the interceptRequests channel for the main
// goroutine to be picked up.
select {
case h.interceptRequests <- newRequest:
case <-timeout:
log.Errorf("MiddlewareHandler returned error - reached "+
"timeout of %v for request interception", h.timeout)
return nil, ErrTimeoutReached
case <-h.done:
return nil, errClientQuit
case <-h.quit:
return nil, ErrShuttingDown
}
// Receive the response and return it. If no response has been received
// in AcceptorTimeout, then return false.
select {
case resp := <-respChan:
return resp, nil
case <-timeout:
log.Errorf("MiddlewareHandler returned error - reached "+
"timeout of %v for response interception", h.timeout)
return nil, ErrTimeoutReached
case <-h.done:
return nil, errClientQuit
case <-h.quit:
return nil, ErrShuttingDown
}
}
// Run is the main loop for the middleware handler. This function will block
// until it receives the signal that lnd is shutting down, or the rpc stream is
// cancelled by the client.
func (h *MiddlewareHandler) Run() error {
// Wait for our goroutines to exit before we return.
defer h.wg.Wait()
defer log.Debugf("Exiting middleware run loop for %s", h.middlewareName)
// Create a channel that responses from middlewares are sent into.
responses := make(chan *lnrpc.RPCMiddlewareResponse)
// errChan is used by the receive loop to signal any errors that occur
// during reading from the stream. This is primarily used to shutdown
// the send loop in the case of an RPC client disconnecting.
errChan := make(chan error, 1)
// Start a goroutine to receive responses from the interceptor. We
// expect the receive function to block, so it must be run in a
// goroutine (otherwise we could not send more than one intercept
// request to the client).
h.wg.Add(1)
go func() {
defer h.wg.Done()
h.receiveResponses(errChan, responses)
}()
return h.sendInterceptRequests(errChan, responses)
}
// receiveResponses receives responses for our intercept requests and dispatches
// them into the responses channel provided, sending any errors that occur into
// the error channel provided.
func (h *MiddlewareHandler) receiveResponses(errChan chan error,
responses chan *lnrpc.RPCMiddlewareResponse) {
for {
resp, err := h.receive()
if err != nil {
errChan <- err
return
}
select {
case responses <- resp:
case <-h.done:
return
case <-h.quit:
return
}
}
}
// sendInterceptRequests handles intercept requests sent to us by our Accept()
// function, dispatching them to our acceptor stream and coordinating return of
// responses to their callers.
func (h *MiddlewareHandler) sendInterceptRequests(errChan chan error,
responses chan *lnrpc.RPCMiddlewareResponse) error {
// Close the done channel to indicate that the interceptor is no longer
// listening and any in-progress requests should be terminated.
defer close(h.done)
interceptRequests := make(map[uint64]*interceptRequest)
for {
select {
// Consume requests passed to us from our Accept() function and
// send them into our stream.
case newRequest := <-h.interceptRequests:
msgID := atomic.AddUint64(&h.lastMsgID, 1)
req := newRequest.request
interceptRequests[msgID] = newRequest
interceptReq, err := req.ToRPC(
newRequest.requestID, msgID,
)
if err != nil {
return err
}
if err := h.send(interceptReq); err != nil {
return err
}
// Process newly received responses from our interceptor,
// looking the original request up in our map of requests and
// dispatching the response.
case resp := <-responses:
requestInfo, ok := interceptRequests[resp.RefMsgId]
if !ok {
continue
}
response := &interceptResponse{}
switch msg := resp.GetMiddlewareMessage().(type) {
case *lnrpc.RPCMiddlewareResponse_Feedback:
t := msg.Feedback
if t.Error != "" {
response.err = fmt.Errorf("%s", t.Error)
break
}
// If there's nothing to replace, we're done,
// this request was just accepted.
if !t.ReplaceResponse {
break
}
// We are replacing the response, the question
// now just is: was it an error or a proper
// proto message?
response.replace = true
if requestInfo.request.IsError {
response.replacement = errors.New(
string(t.ReplacementSerialized),
)
break
}
// Not an error but a proper proto message that
// needs to be replaced. For that we need to
// parse it from the raw bytes into the full RPC
// message.
protoMsg, err := parseProto(
requestInfo.request.ProtoTypeName,
t.ReplacementSerialized,
)
if err != nil {
response.err = err
break
}
response.replacement = protoMsg
default:
return fmt.Errorf("unknown middleware "+
"message: %v", msg)
}
select {
case requestInfo.response <- response:
case <-h.quit:
}
delete(interceptRequests, resp.RefMsgId)
// If we failed to receive from our middleware, we exit.
case err := <-errChan:
log.Errorf("Received an error: %v, shutting down", err)
return err
// Exit if we are shutting down.
case <-h.quit:
return ErrShuttingDown
}
}
}
// InterceptType defines the different types of intercept messages a middleware
// can receive.
type InterceptType uint8
const (
// TypeStreamAuth is the type of intercept message that is sent when a
// client or streaming RPC is initialized. A message with this type will
// be sent out during stream initialization so a middleware can
// accept/deny the whole stream instead of only single messages on the
// stream.
TypeStreamAuth InterceptType = 1
// TypeRequest is the type of intercept message that is sent when an RPC
// request message is sent to lnd. For client-streaming RPCs a new
// message of this type is sent for each individual RPC request sent to
// the stream. Middleware has the option to modify a request message
// before it is delivered to lnd.
TypeRequest InterceptType = 2
// TypeResponse is the type of intercept message that is sent when an
// RPC response message is sent from lnd to a client. For
// server-streaming RPCs a new message of this type is sent for each
// individual RPC response sent to the stream. Middleware has the option
// to modify a response message before it is sent out to the client.
TypeResponse InterceptType = 3
)
// InterceptionRequest is a struct holding all information that is sent to a
// middleware whenever there is something to intercept (auth, request,
// response).
type InterceptionRequest struct {
// Type is the type of the interception message.
Type InterceptType
// StreamRPC is set to true if the invoked RPC method is client or
// server streaming.
StreamRPC bool
// Macaroon holds the macaroon that the client sent to lnd.
Macaroon *macaroon.Macaroon
// RawMacaroon holds the raw binary serialized macaroon that the client
// sent to lnd.
RawMacaroon []byte
// CustomCaveatName is the name of the custom caveat that the middleware
// was intercepting for.
CustomCaveatName string
// CustomCaveatCondition is the condition of the custom caveat that the
// middleware was intercepting for. This can be empty for custom caveats
// that only have a name (marker caveats).
CustomCaveatCondition string
// FullURI is the full RPC method URI that was invoked.
FullURI string
// ProtoSerialized is the full request or response object in the
// protobuf binary serialization format.
ProtoSerialized []byte
// ProtoTypeName is the fully qualified name of the protobuf type of the
// request or response message that is serialized in the field above.
ProtoTypeName string
// IsError indicates that the message contained within this request is
// an error. Will only ever be true for response messages.
IsError bool
}
// NewMessageInterceptionRequest creates a new interception request for either
// a request or response message.
func NewMessageInterceptionRequest(ctx context.Context,
authType InterceptType, isStream bool, fullMethod string,
m interface{}) (*InterceptionRequest, error) {
mac, rawMacaroon, err := macaroonFromContext(ctx)
if err != nil {
return nil, err
}
req := &InterceptionRequest{
Type: authType,
StreamRPC: isStream,
Macaroon: mac,
RawMacaroon: rawMacaroon,
FullURI: fullMethod,
}
// The message is either a proto message or an error, we don't support
// any other types being intercepted.
switch t := m.(type) {
case proto.Message:
req.ProtoSerialized, err = proto.Marshal(t)
if err != nil {
return nil, fmt.Errorf("cannot marshal proto msg: %w",
err)
}
req.ProtoTypeName = string(proto.MessageName(t))
case error:
req.ProtoSerialized = []byte(t.Error())
req.ProtoTypeName = "error"
req.IsError = true
default:
return nil, fmt.Errorf("unsupported type for interception "+
"request: %v", m)
}
return req, nil
}
// NewStreamAuthInterceptionRequest creates a new interception request for a
// stream authentication message.
func NewStreamAuthInterceptionRequest(ctx context.Context,
fullMethod string) (*InterceptionRequest, error) {
mac, rawMacaroon, err := macaroonFromContext(ctx)
if err != nil {
return nil, err
}
return &InterceptionRequest{
Type: TypeStreamAuth,
StreamRPC: true,
Macaroon: mac,
RawMacaroon: rawMacaroon,
FullURI: fullMethod,
}, nil
}
// macaroonFromContext tries to extract the macaroon from the incoming context.
// If there is no macaroon, a nil error is returned since some RPCs might not
// require a macaroon. But in case there is something in the macaroon header
// field that cannot be parsed, a non-nil error is returned.
func macaroonFromContext(ctx context.Context) (*macaroon.Macaroon, []byte,
error) {
macHex, err := macaroons.RawMacaroonFromContext(ctx)
if err != nil {
// If there is no macaroon, we continue anyway as it might be an
// RPC that doesn't require a macaroon.
return nil, nil, nil
}
macBytes, err := hex.DecodeString(macHex)
if err != nil {
return nil, nil, err
}
mac := &macaroon.Macaroon{}
if err := mac.UnmarshalBinary(macBytes); err != nil {
return nil, nil, err
}
return mac, macBytes, nil
}
// ToRPC converts the interception request to its RPC counterpart.
func (r *InterceptionRequest) ToRPC(requestID,
msgID uint64) (*lnrpc.RPCMiddlewareRequest, error) {
rpcRequest := &lnrpc.RPCMiddlewareRequest{
RequestId: requestID,
MsgId: msgID,
RawMacaroon: r.RawMacaroon,
CustomCaveatCondition: r.CustomCaveatCondition,
}
switch r.Type {
case TypeStreamAuth:
rpcRequest.InterceptType = &lnrpc.RPCMiddlewareRequest_StreamAuth{
StreamAuth: &lnrpc.StreamAuth{
MethodFullUri: r.FullURI,
},
}
case TypeRequest:
rpcRequest.InterceptType = &lnrpc.RPCMiddlewareRequest_Request{
Request: &lnrpc.RPCMessage{
MethodFullUri: r.FullURI,
StreamRpc: r.StreamRPC,
TypeName: r.ProtoTypeName,
Serialized: r.ProtoSerialized,
},
}
case TypeResponse:
rpcRequest.InterceptType = &lnrpc.RPCMiddlewareRequest_Response{
Response: &lnrpc.RPCMessage{
MethodFullUri: r.FullURI,
StreamRpc: r.StreamRPC,
TypeName: r.ProtoTypeName,
Serialized: r.ProtoSerialized,
IsError: r.IsError,
},
}
default:
return nil, fmt.Errorf("unknown intercept type %v", r.Type)
}
return rpcRequest, nil
}
// interceptRequest is a struct that keeps track of an interception request sent
// out to a middleware and the response that is eventually sent back by the
// middleware.
type interceptRequest struct {
requestID uint64
request *InterceptionRequest
response chan *interceptResponse
}
// interceptResponse is the response a middleware sends back for each
// intercepted message.
type interceptResponse struct {
err error
replace bool
replacement interface{}
}
// parseProto parses a proto serialized message of the given type into its
// native version.
func parseProto(typeName string, serialized []byte) (proto.Message, error) {
messageType, err := protoregistry.GlobalTypes.FindMessageByName(
protoreflect.FullName(typeName),
)
if err != nil {
return nil, err
}
msg := messageType.New()
err = proto.Unmarshal(serialized, msg.Interface())
if err != nil {
return nil, err
}
return msg.Interface(), nil
}
// replaceProtoMsg replaces the given target message with the content of the
// replacement message.
func replaceProtoMsg(target interface{}, replacement interface{}) error {
targetMsg, ok := target.(proto.Message)
if !ok {
return fmt.Errorf("target is not a proto message: %v", target)
}
replacementMsg, ok := replacement.(proto.Message)
if !ok {
return fmt.Errorf("replacement is not a proto message: %v",
replacement)
}
if targetMsg.ProtoReflect().Type() !=
replacementMsg.ProtoReflect().Type() {
return fmt.Errorf("replacement message is of wrong type")
}
replacementBytes, err := proto.Marshal(replacementMsg)
if err != nil {
return fmt.Errorf("error marshaling replacement: %w", err)
}
err = proto.Unmarshal(replacementBytes, targetMsg)
if err != nil {
return fmt.Errorf("error unmarshaling replacement: %w", err)
}
return nil
}
package lnd
import (
"bytes"
"context"
"encoding/hex"
"errors"
"fmt"
"io"
"maps"
"math"
"net"
"net/http"
"os"
"path/filepath"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/wallet/txauthor"
"github.com/davecgh/go-spew/spew"
proxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/lightningnetwork/lnd/autopilot"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/chanacceptor"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/chanfitness"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channelnotifier"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/discovery"
"github.com/lightningnetwork/lnd/feature"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/funding"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/lightningnetwork/lnd/peer"
"github.com/lightningnetwork/lnd/peernotifier"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/routing/blindedpath"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/rpcperms"
"github.com/lightningnetwork/lnd/signal"
"github.com/lightningnetwork/lnd/sweep"
"github.com/lightningnetwork/lnd/tlv"
"github.com/lightningnetwork/lnd/watchtower"
"github.com/lightningnetwork/lnd/zpay32"
"github.com/tv42/zbase32"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
"gopkg.in/macaroon-bakery.v2/bakery"
)
const (
// defaultNumBlocksEstimate is the number of blocks that we fall back
// to issuing an estimate for if a fee pre fence doesn't specify an
// explicit conf target or fee rate.
defaultNumBlocksEstimate = 6
)
var (
// readPermissions is a slice of all entities that allow read
// permissions for authorization purposes, all lowercase.
readPermissions = []bakery.Op{
{
Entity: "onchain",
Action: "read",
},
{
Entity: "offchain",
Action: "read",
},
{
Entity: "address",
Action: "read",
},
{
Entity: "message",
Action: "read",
},
{
Entity: "peers",
Action: "read",
},
{
Entity: "info",
Action: "read",
},
{
Entity: "invoices",
Action: "read",
},
{
Entity: "signer",
Action: "read",
},
{
Entity: "macaroon",
Action: "read",
},
}
// writePermissions is a slice of all entities that allow write
// permissions for authorization purposes, all lowercase.
writePermissions = []bakery.Op{
{
Entity: "onchain",
Action: "write",
},
{
Entity: "offchain",
Action: "write",
},
{
Entity: "address",
Action: "write",
},
{
Entity: "message",
Action: "write",
},
{
Entity: "peers",
Action: "write",
},
{
Entity: "info",
Action: "write",
},
{
Entity: "invoices",
Action: "write",
},
{
Entity: "signer",
Action: "generate",
},
{
Entity: "macaroon",
Action: "generate",
},
{
Entity: "macaroon",
Action: "write",
},
}
// invoicePermissions is a slice of all the entities that allows a user
// to only access calls that are related to invoices, so: streaming
// RPCs, generating, and listening invoices.
invoicePermissions = []bakery.Op{
{
Entity: "invoices",
Action: "read",
},
{
Entity: "invoices",
Action: "write",
},
{
Entity: "address",
Action: "read",
},
{
Entity: "address",
Action: "write",
},
{
Entity: "onchain",
Action: "read",
},
}
// TODO(guggero): Refactor into constants that are used for all
// permissions in this file. Also expose the list of possible
// permissions in an RPC when per RPC permissions are
// implemented.
validActions = []string{"read", "write", "generate"}
validEntities = []string{
"onchain", "offchain", "address", "message",
"peers", "info", "invoices", "signer", "macaroon",
macaroons.PermissionEntityCustomURI,
}
// If the --no-macaroons flag is used to start lnd, the macaroon service
// is not initialized. errMacaroonDisabled is then returned when
// macaroon related services are used.
errMacaroonDisabled = fmt.Errorf("macaroon authentication disabled, " +
"remove --no-macaroons flag to enable")
)
// stringInSlice returns true if a string is contained in the given slice.
func stringInSlice(a string, slice []string) bool {
for _, b := range slice {
if b == a {
return true
}
}
return false
}
// GetAllPermissions returns all the permissions required to interact with lnd.
func GetAllPermissions() []bakery.Op {
allPerms := make([]bakery.Op, 0)
// The map will help keep track of which specific permission pairs have
// already been added to the slice.
allPermsMap := make(map[string]map[string]struct{})
for _, perms := range MainRPCServerPermissions() {
for _, perm := range perms {
entity := perm.Entity
action := perm.Action
// If this specific entity-action permission pair isn't
// in the map yet. Add it to map, and the permission
// slice.
if acts, ok := allPermsMap[entity]; ok {
if _, ok := acts[action]; !ok {
allPermsMap[entity][action] = struct{}{}
allPerms = append(
allPerms, perm,
)
}
} else {
allPermsMap[entity] = make(map[string]struct{})
allPermsMap[entity][action] = struct{}{}
allPerms = append(allPerms, perm)
}
}
}
return allPerms
}
// MainRPCServerPermissions returns a mapping of the main RPC server calls to
// the permissions they require.
func MainRPCServerPermissions() map[string][]bakery.Op {
return map[string][]bakery.Op{
"/lnrpc.Lightning/SendCoins": {{
Entity: "onchain",
Action: "write",
}},
"/lnrpc.Lightning/ListUnspent": {{
Entity: "onchain",
Action: "read",
}},
"/lnrpc.Lightning/SendMany": {{
Entity: "onchain",
Action: "write",
}},
"/lnrpc.Lightning/NewAddress": {{
Entity: "address",
Action: "write",
}},
"/lnrpc.Lightning/SignMessage": {{
Entity: "message",
Action: "write",
}},
"/lnrpc.Lightning/VerifyMessage": {{
Entity: "message",
Action: "read",
}},
"/lnrpc.Lightning/ConnectPeer": {{
Entity: "peers",
Action: "write",
}},
"/lnrpc.Lightning/DisconnectPeer": {{
Entity: "peers",
Action: "write",
}},
"/lnrpc.Lightning/OpenChannel": {{
Entity: "onchain",
Action: "write",
}, {
Entity: "offchain",
Action: "write",
}},
"/lnrpc.Lightning/BatchOpenChannel": {{
Entity: "onchain",
Action: "write",
}, {
Entity: "offchain",
Action: "write",
}},
"/lnrpc.Lightning/OpenChannelSync": {{
Entity: "onchain",
Action: "write",
}, {
Entity: "offchain",
Action: "write",
}},
"/lnrpc.Lightning/CloseChannel": {{
Entity: "onchain",
Action: "write",
}, {
Entity: "offchain",
Action: "write",
}},
"/lnrpc.Lightning/AbandonChannel": {{
Entity: "offchain",
Action: "write",
}},
"/lnrpc.Lightning/GetInfo": {{
Entity: "info",
Action: "read",
}},
"/lnrpc.Lightning/GetDebugInfo": {{
Entity: "info",
Action: "read",
}, {
Entity: "offchain",
Action: "read",
}, {
Entity: "onchain",
Action: "read",
}, {
Entity: "peers",
Action: "read",
}},
"/lnrpc.Lightning/GetRecoveryInfo": {{
Entity: "info",
Action: "read",
}},
"/lnrpc.Lightning/ListPeers": {{
Entity: "peers",
Action: "read",
}},
"/lnrpc.Lightning/WalletBalance": {{
Entity: "onchain",
Action: "read",
}},
"/lnrpc.Lightning/EstimateFee": {{
Entity: "onchain",
Action: "read",
}},
"/lnrpc.Lightning/ChannelBalance": {{
Entity: "offchain",
Action: "read",
}},
"/lnrpc.Lightning/PendingChannels": {{
Entity: "offchain",
Action: "read",
}},
"/lnrpc.Lightning/ListChannels": {{
Entity: "offchain",
Action: "read",
}},
"/lnrpc.Lightning/SubscribeChannelEvents": {{
Entity: "offchain",
Action: "read",
}},
"/lnrpc.Lightning/ClosedChannels": {{
Entity: "offchain",
Action: "read",
}},
"/lnrpc.Lightning/SendPayment": {{
Entity: "offchain",
Action: "write",
}},
"/lnrpc.Lightning/SendPaymentSync": {{
Entity: "offchain",
Action: "write",
}},
"/lnrpc.Lightning/SendToRoute": {{
Entity: "offchain",
Action: "write",
}},
"/lnrpc.Lightning/SendToRouteSync": {{
Entity: "offchain",
Action: "write",
}},
"/lnrpc.Lightning/AddInvoice": {{
Entity: "invoices",
Action: "write",
}},
"/lnrpc.Lightning/LookupInvoice": {{
Entity: "invoices",
Action: "read",
}},
"/lnrpc.Lightning/ListInvoices": {{
Entity: "invoices",
Action: "read",
}},
"/lnrpc.Lightning/SubscribeInvoices": {{
Entity: "invoices",
Action: "read",
}},
"/lnrpc.Lightning/SubscribeTransactions": {{
Entity: "onchain",
Action: "read",
}},
"/lnrpc.Lightning/GetTransactions": {{
Entity: "onchain",
Action: "read",
}},
"/lnrpc.Lightning/DescribeGraph": {{
Entity: "info",
Action: "read",
}},
"/lnrpc.Lightning/GetNodeMetrics": {{
Entity: "info",
Action: "read",
}},
"/lnrpc.Lightning/GetChanInfo": {{
Entity: "info",
Action: "read",
}},
"/lnrpc.Lightning/GetNodeInfo": {{
Entity: "info",
Action: "read",
}},
"/lnrpc.Lightning/QueryRoutes": {{
Entity: "info",
Action: "read",
}},
"/lnrpc.Lightning/GetNetworkInfo": {{
Entity: "info",
Action: "read",
}},
"/lnrpc.Lightning/StopDaemon": {{
Entity: "info",
Action: "write",
}},
"/lnrpc.Lightning/SubscribeChannelGraph": {{
Entity: "info",
Action: "read",
}},
"/lnrpc.Lightning/ListPayments": {{
Entity: "offchain",
Action: "read",
}},
"/lnrpc.Lightning/DeletePayment": {{
Entity: "offchain",
Action: "write",
}},
"/lnrpc.Lightning/DeleteAllPayments": {{
Entity: "offchain",
Action: "write",
}},
"/lnrpc.Lightning/DebugLevel": {{
Entity: "info",
Action: "write",
}},
"/lnrpc.Lightning/DecodePayReq": {{
Entity: "offchain",
Action: "read",
}},
"/lnrpc.Lightning/FeeReport": {{
Entity: "offchain",
Action: "read",
}},
"/lnrpc.Lightning/UpdateChannelPolicy": {{
Entity: "offchain",
Action: "write",
}},
"/lnrpc.Lightning/ForwardingHistory": {{
Entity: "offchain",
Action: "read",
}},
"/lnrpc.Lightning/RestoreChannelBackups": {{
Entity: "offchain",
Action: "write",
}},
"/lnrpc.Lightning/ExportChannelBackup": {{
Entity: "offchain",
Action: "read",
}},
"/lnrpc.Lightning/VerifyChanBackup": {{
Entity: "offchain",
Action: "read",
}},
"/lnrpc.Lightning/ExportAllChannelBackups": {{
Entity: "offchain",
Action: "read",
}},
"/lnrpc.Lightning/SubscribeChannelBackups": {{
Entity: "offchain",
Action: "read",
}},
"/lnrpc.Lightning/ChannelAcceptor": {{
Entity: "onchain",
Action: "write",
}, {
Entity: "offchain",
Action: "write",
}},
"/lnrpc.Lightning/BakeMacaroon": {{
Entity: "macaroon",
Action: "generate",
}},
"/lnrpc.Lightning/ListMacaroonIDs": {{
Entity: "macaroon",
Action: "read",
}},
"/lnrpc.Lightning/DeleteMacaroonID": {{
Entity: "macaroon",
Action: "write",
}},
"/lnrpc.Lightning/ListPermissions": {{
Entity: "info",
Action: "read",
}},
"/lnrpc.Lightning/CheckMacaroonPermissions": {{
Entity: "macaroon",
Action: "read",
}},
"/lnrpc.Lightning/SubscribePeerEvents": {{
Entity: "peers",
Action: "read",
}},
"/lnrpc.Lightning/FundingStateStep": {{
Entity: "onchain",
Action: "write",
}, {
Entity: "offchain",
Action: "write",
}},
lnrpc.RegisterRPCMiddlewareURI: {{
Entity: "macaroon",
Action: "write",
}},
"/lnrpc.Lightning/SendCustomMessage": {{
Entity: "offchain",
Action: "write",
}},
"/lnrpc.Lightning/SubscribeCustomMessages": {{
Entity: "offchain",
Action: "read",
}},
"/lnrpc.Lightning/LookupHtlcResolution": {{
Entity: "offchain",
Action: "read",
}},
"/lnrpc.Lightning/ListAliases": {{
Entity: "offchain",
Action: "read",
}},
}
}
// AuxDataParser is an interface that is used to parse auxiliary custom data
// within RPC messages. This is used to transform binary blobs to human-readable
// JSON representations.
type AuxDataParser interface {
// InlineParseCustomData replaces any custom data binary blob in the
// given RPC message with its corresponding JSON formatted data. This
// transforms the binary (likely TLV encoded) data to a human-readable
// JSON representation (still as byte slice).
InlineParseCustomData(msg proto.Message) error
}
// rpcServer is a gRPC, RPC front end to the lnd daemon.
// TODO(roasbeef): pagination support for the list-style calls
type rpcServer struct {
started int32 // To be used atomically.
shutdown int32 // To be used atomically.
// Required by the grpc-gateway/v2 library for forward compatibility.
// Must be after the atomically used variables to not break struct
// alignment.
lnrpc.UnimplementedLightningServer
server *server
cfg *Config
// subServers are a set of sub-RPC servers that use the same gRPC and
// listening sockets as the main RPC server, but which maintain their
// own independent service. This allows us to expose a set of
// micro-service like abstractions to the outside world for users to
// consume.
subServers []lnrpc.SubServer
subGrpcHandlers []lnrpc.GrpcHandler
// routerBackend contains the backend implementation of the router
// rpc sub server.
routerBackend *routerrpc.RouterBackend
// chanPredicate is used in the bidirectional ChannelAcceptor streaming
// method.
chanPredicate chanacceptor.MultiplexAcceptor
quit chan struct{}
// macService is the macaroon service that we need to mint new
// macaroons.
macService *macaroons.Service
// selfNode is our own pubkey.
selfNode route.Vertex
// interceptorChain is the interceptor added to our gRPC server.
interceptorChain *rpcperms.InterceptorChain
// implCfg is the configuration for some of the interfaces that can be
// provided externally.
implCfg *ImplementationCfg
// interceptor is used to be able to request a shutdown
interceptor signal.Interceptor
graphCache sync.RWMutex
describeGraphResp *lnrpc.ChannelGraph
graphCacheEvictor *time.Timer
}
// A compile time check to ensure that rpcServer fully implements the
// LightningServer gRPC service.
var _ lnrpc.LightningServer = (*rpcServer)(nil)
// newRPCServer creates and returns a new instance of the rpcServer. Before
// dependencies are added, this will be an non-functioning RPC server only to
// be used to register the LightningService with the gRPC server.
func newRPCServer(cfg *Config, interceptorChain *rpcperms.InterceptorChain,
implCfg *ImplementationCfg, interceptor signal.Interceptor) *rpcServer {
// We go trhough the list of registered sub-servers, and create a gRPC
// handler for each. These are used to register with the gRPC server
// before all dependencies are available.
registeredSubServers := lnrpc.RegisteredSubServers()
var subServerHandlers []lnrpc.GrpcHandler
for _, subServer := range registeredSubServers {
subServerHandlers = append(
subServerHandlers, subServer.NewGrpcHandler(),
)
}
return &rpcServer{
cfg: cfg,
subGrpcHandlers: subServerHandlers,
interceptorChain: interceptorChain,
implCfg: implCfg,
quit: make(chan struct{}, 1),
interceptor: interceptor,
}
}
// addDeps populates all dependencies needed by the RPC server, and any
// of the sub-servers that it maintains. When this is done, the RPC server can
// be started, and start accepting RPC calls.
func (r *rpcServer) addDeps(s *server, macService *macaroons.Service,
subServerCgs *subRPCServerConfigs, atpl *autopilot.Manager,
invoiceRegistry *invoices.InvoiceRegistry, tower *watchtower.Standalone,
chanPredicate chanacceptor.MultiplexAcceptor,
invoiceHtlcModifier *invoices.HtlcModificationInterceptor) error {
// Set up router rpc backend.
selfNode, err := s.graphDB.SourceNode()
if err != nil {
return err
}
graph := s.graphDB
routerBackend := &routerrpc.RouterBackend{
SelfNode: selfNode.PubKeyBytes,
FetchChannelCapacity: func(chanID uint64) (btcutil.Amount,
error) {
info, _, _, err := graph.FetchChannelEdgesByID(chanID)
if err != nil {
return 0, err
}
return info.Capacity, nil
},
FetchAmountPairCapacity: func(nodeFrom, nodeTo route.Vertex,
amount lnwire.MilliSatoshi) (btcutil.Amount, error) {
return routing.FetchAmountPairCapacity(
graph, selfNode.PubKeyBytes, nodeFrom, nodeTo,
amount,
)
},
FetchChannelEndpoints: func(chanID uint64) (route.Vertex,
route.Vertex, error) {
info, _, _, err := graph.FetchChannelEdgesByID(
chanID,
)
if err != nil {
return route.Vertex{}, route.Vertex{},
fmt.Errorf("unable to fetch channel "+
"edges by channel ID %d: %v",
chanID, err)
}
return info.NodeKey1Bytes, info.NodeKey2Bytes, nil
},
FindRoute: s.chanRouter.FindRoute,
MissionControl: s.defaultMC,
ActiveNetParams: r.cfg.ActiveNetParams.Params,
Tower: s.controlTower,
MaxTotalTimelock: r.cfg.MaxOutgoingCltvExpiry,
DefaultFinalCltvDelta: uint16(r.cfg.Bitcoin.TimeLockDelta),
SubscribeHtlcEvents: s.htlcNotifier.SubscribeHtlcEvents,
InterceptableForwarder: s.interceptableSwitch,
SetChannelEnabled: func(outpoint wire.OutPoint) error {
return s.chanStatusMgr.RequestEnable(outpoint, true)
},
SetChannelDisabled: func(outpoint wire.OutPoint) error {
return s.chanStatusMgr.RequestDisable(outpoint, true)
},
SetChannelAuto: s.chanStatusMgr.RequestAuto,
UseStatusInitiated: subServerCgs.RouterRPC.UseStatusInitiated,
ParseCustomChannelData: func(msg proto.Message) error {
err = fn.MapOptionZ(
r.server.implCfg.AuxDataParser,
func(parser AuxDataParser) error {
return parser.InlineParseCustomData(msg)
},
)
if err != nil {
return fmt.Errorf("error parsing custom data: "+
"%w", err)
}
return nil
},
ShouldSetExpEndorsement: func() bool {
if s.cfg.ProtocolOptions.NoExperimentalEndorsement() {
return false
}
return clock.NewDefaultClock().Now().Before(
EndorsementExperimentEnd,
)
},
}
genInvoiceFeatures := func() *lnwire.FeatureVector {
return s.featureMgr.Get(feature.SetInvoice)
}
genAmpInvoiceFeatures := func() *lnwire.FeatureVector {
return s.featureMgr.Get(feature.SetInvoiceAmp)
}
parseAddr := func(addr string) (net.Addr, error) {
return parseAddr(addr, r.cfg.net)
}
var (
subServers []lnrpc.SubServer
subServerPerms []lnrpc.MacaroonPerms
)
// Before we create any of the sub-servers, we need to ensure that all
// the dependencies they need are properly populated within each sub
// server configuration struct.
//
// TODO(roasbeef): extend sub-sever config to have both (local vs remote) DB
err = subServerCgs.PopulateDependencies(
r.cfg, s.cc, r.cfg.networkDir, macService, atpl, invoiceRegistry,
s.htlcSwitch, r.cfg.ActiveNetParams.Params, s.chanRouter,
routerBackend, s.nodeSigner, s.graphDB, s.chanStateDB,
s.sweeper, tower, s.towerClientMgr, r.cfg.net.ResolveTCPAddr,
genInvoiceFeatures, genAmpInvoiceFeatures,
s.getNodeAnnouncement, s.updateAndBroadcastSelfNode, parseAddr,
rpcsLog, s.aliasMgr, r.implCfg.AuxDataParser,
invoiceHtlcModifier,
)
if err != nil {
return err
}
// Now that the sub-servers have all their dependencies in place, we
// can create each sub-server!
for _, subServerInstance := range r.subGrpcHandlers {
subServer, macPerms, err := subServerInstance.CreateSubServer(
subServerCgs,
)
if err != nil {
return err
}
// We'll collect the sub-server, and also the set of
// permissions it needs for macaroons so we can apply the
// interceptors below.
subServers = append(subServers, subServer)
subServerPerms = append(subServerPerms, macPerms)
}
// Next, we need to merge the set of sub server macaroon permissions
// with the main RPC server permissions so we can unite them under a
// single set of interceptors.
for m, ops := range MainRPCServerPermissions() {
err := r.interceptorChain.AddPermission(m, ops)
if err != nil {
return err
}
}
for _, subServerPerm := range subServerPerms {
for method, ops := range subServerPerm {
err := r.interceptorChain.AddPermission(method, ops)
if err != nil {
return err
}
}
}
// External subserver possibly need to register their own permissions
// and macaroon validator.
for method, ops := range r.implCfg.ExternalValidator.Permissions() {
err := r.interceptorChain.AddPermission(method, ops)
if err != nil {
return err
}
// Give the external subservers the possibility to also use
// their own validator to check any macaroons attached to calls
// to this method. This allows them to have their own root key
// ID database and permission entities.
err = macService.RegisterExternalValidator(
method, r.implCfg.ExternalValidator,
)
if err != nil {
return fmt.Errorf("could not register external "+
"macaroon validator: %v", err)
}
}
// Finally, with all the set up complete, add the last dependencies to
// the rpc server.
r.server = s
r.subServers = subServers
r.routerBackend = routerBackend
r.chanPredicate = chanPredicate
r.macService = macService
r.selfNode = selfNode.PubKeyBytes
graphCacheDuration := r.cfg.Caches.RPCGraphCacheDuration
if graphCacheDuration != 0 {
r.graphCacheEvictor = time.AfterFunc(graphCacheDuration, func() {
// Grab the mutex and purge the current populated
// describe graph response.
r.graphCache.Lock()
defer r.graphCache.Unlock()
r.describeGraphResp = nil
// Reset ourselves as well at the end so we run again
// after the duration.
r.graphCacheEvictor.Reset(graphCacheDuration)
})
}
return nil
}
// RegisterWithGrpcServer registers the rpcServer and any subservers with the
// root gRPC server.
func (r *rpcServer) RegisterWithGrpcServer(grpcServer *grpc.Server) error {
// Register the main RPC server.
lnrpc.RegisterLightningServer(grpcServer, r)
// Now the main RPC server has been registered, we'll iterate through
// all the sub-RPC servers and register them to ensure that requests
// are properly routed towards them.
for _, subServer := range r.subGrpcHandlers {
err := subServer.RegisterWithRootServer(grpcServer)
if err != nil {
return fmt.Errorf("unable to register "+
"sub-server with root: %v", err)
}
}
// Before actually listening on the gRPC listener, give external
// subservers the chance to register to our gRPC server. Those external
// subservers (think GrUB) are responsible for starting/stopping on
// their own, we just let them register their services to the same
// server instance so all of them can be exposed on the same
// port/listener.
err := r.implCfg.RegisterGrpcSubserver(grpcServer)
if err != nil {
rpcsLog.Errorf("error registering external gRPC "+
"subserver: %v", err)
}
return nil
}
// Start launches any helper goroutines required for the rpcServer to function.
func (r *rpcServer) Start() error {
if atomic.AddInt32(&r.started, 1) != 1 {
return nil
}
// First, we'll start all the sub-servers to ensure that they're ready
// to take new requests in.
//
// TODO(roasbeef): some may require that the entire daemon be started
// at that point
for _, subServer := range r.subServers {
rpcsLog.Debugf("Starting sub RPC server: %v", subServer.Name())
if err := subServer.Start(); err != nil {
return err
}
}
return nil
}
// RegisterWithRestProxy registers the RPC server and any subservers with the
// given REST proxy.
func (r *rpcServer) RegisterWithRestProxy(restCtx context.Context,
restMux *proxy.ServeMux, restDialOpts []grpc.DialOption,
restProxyDest string) error {
// With our custom REST proxy mux created, register our main RPC and
// give all subservers a chance to register as well.
err := lnrpc.RegisterLightningHandlerFromEndpoint(
restCtx, restMux, restProxyDest, restDialOpts,
)
if err != nil {
return err
}
// Register our State service with the REST proxy.
err = lnrpc.RegisterStateHandlerFromEndpoint(
restCtx, restMux, restProxyDest, restDialOpts,
)
if err != nil {
return err
}
// Register all the subservers with the REST proxy.
for _, subServer := range r.subGrpcHandlers {
err := subServer.RegisterWithRestServer(
restCtx, restMux, restProxyDest, restDialOpts,
)
if err != nil {
return fmt.Errorf("unable to register REST sub-server "+
"with root: %v", err)
}
}
// Before listening on any of the interfaces, we also want to give the
// external subservers a chance to register their own REST proxy stub
// with our mux instance.
err = r.implCfg.RegisterRestSubserver(
restCtx, restMux, restProxyDest, restDialOpts,
)
if err != nil {
rpcsLog.Errorf("error registering external REST subserver: %v",
err)
}
return nil
}
// Stop signals any active goroutines for a graceful closure.
func (r *rpcServer) Stop() error {
if atomic.AddInt32(&r.shutdown, 1) != 1 {
return nil
}
rpcsLog.Infof("Stopping RPC Server")
close(r.quit)
// After we've signalled all of our active goroutines to exit, we'll
// then do the same to signal a graceful shutdown of all the sub
// servers.
for _, subServer := range r.subServers {
rpcsLog.Infof("Stopping %v Sub-RPC Server",
subServer.Name())
if err := subServer.Stop(); err != nil {
rpcsLog.Errorf("unable to stop sub-server %v: %v",
subServer.Name(), err)
continue
}
}
return nil
}
// addrPairsToOutputs converts a map describing a set of outputs to be created,
// the outputs themselves. The passed map pairs up an address, to a desired
// output value amount. Each address is converted to its corresponding pkScript
// to be used within the constructed output(s).
func addrPairsToOutputs(addrPairs map[string]int64,
params *chaincfg.Params) ([]*wire.TxOut, error) {
outputs := make([]*wire.TxOut, 0, len(addrPairs))
for addr, amt := range addrPairs {
addr, err := btcutil.DecodeAddress(addr, params)
if err != nil {
return nil, err
}
if !addr.IsForNet(params) {
return nil, fmt.Errorf("address is not for %s",
params.Name)
}
pkscript, err := txscript.PayToAddrScript(addr)
if err != nil {
return nil, err
}
outputs = append(outputs, wire.NewTxOut(amt, pkscript))
}
return outputs, nil
}
// allowCORS wraps the given http.Handler with a function that adds the
// Access-Control-Allow-Origin header to the response.
func allowCORS(handler http.Handler, origins []string) http.Handler {
allowHeaders := "Access-Control-Allow-Headers"
allowMethods := "Access-Control-Allow-Methods"
allowOrigin := "Access-Control-Allow-Origin"
// If the user didn't supply any origins that means CORS is disabled
// and we should return the original handler.
if len(origins) == 0 {
return handler
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
// Skip everything if the browser doesn't send the Origin field.
if origin == "" {
handler.ServeHTTP(w, r)
return
}
// Set the static header fields first.
w.Header().Set(
allowHeaders,
"Content-Type, Accept, Grpc-Metadata-Macaroon",
)
w.Header().Set(allowMethods, "GET, POST, DELETE")
// Either we allow all origins or the incoming request matches
// a specific origin in our list of allowed origins.
for _, allowedOrigin := range origins {
if allowedOrigin == "*" || origin == allowedOrigin {
// Only set allowed origin to requested origin.
w.Header().Set(allowOrigin, origin)
break
}
}
// For a pre-flight request we only need to send the headers
// back. No need to call the rest of the chain.
if r.Method == "OPTIONS" {
return
}
// Everything's prepared now, we can pass the request along the
// chain of handlers.
handler.ServeHTTP(w, r)
})
}
// sendCoinsOnChain makes an on-chain transaction in or to send coins to one or
// more addresses specified in the passed payment map. The payment map maps an
// address to a specified output value to be sent to that address.
func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64,
feeRate chainfee.SatPerKWeight, minConfs int32, label string,
strategy wallet.CoinSelectionStrategy,
selectedUtxos fn.Set[wire.OutPoint]) (*chainhash.Hash, error) {
outputs, err := addrPairsToOutputs(paymentMap, r.cfg.ActiveNetParams.Params)
if err != nil {
return nil, err
}
// We first do a dry run, to sanity check we won't spend our wallet
// balance below the reserved amount.
authoredTx, err := r.server.cc.Wallet.CreateSimpleTx(
selectedUtxos, outputs, feeRate, minConfs, strategy, true,
)
if err != nil {
return nil, err
}
// Check the authored transaction and use the explicitly set change index
// to make sure that the wallet reserved balance is not invalidated.
_, err = r.server.cc.Wallet.CheckReservedValueTx(
lnwallet.CheckReservedValueTxReq{
Tx: authoredTx.Tx,
ChangeIndex: &authoredTx.ChangeIndex,
},
)
if err != nil {
return nil, err
}
// If that checks out, we're fairly confident that creating sending to
// these outputs will keep the wallet balance above the reserve.
tx, err := r.server.cc.Wallet.SendOutputs(
selectedUtxos, outputs, feeRate, minConfs, label, strategy,
)
if err != nil {
return nil, err
}
txHash := tx.TxHash()
return &txHash, nil
}
// ListUnspent returns useful information about each unspent output owned by
// the wallet, as reported by the underlying `ListUnspentWitness`; the
// information returned is: outpoint, amount in satoshis, address, address
// type, scriptPubKey in hex and number of confirmations. The result is
// filtered to contain outputs whose number of confirmations is between a
// minimum and maximum number of confirmations specified by the user, with
// 0 meaning unconfirmed.
func (r *rpcServer) ListUnspent(ctx context.Context,
in *lnrpc.ListUnspentRequest) (*lnrpc.ListUnspentResponse, error) {
// Validate the confirmation arguments.
minConfs, maxConfs, err := lnrpc.ParseConfs(in.MinConfs, in.MaxConfs)
if err != nil {
return nil, err
}
// With our arguments validated, we'll query the internal wallet for
// the set of UTXOs that match our query.
//
// We'll acquire the global coin selection lock to ensure there aren't
// any other concurrent processes attempting to lock any UTXOs which may
// be shown available to us.
var utxos []*lnwallet.Utxo
err = r.server.cc.Wallet.WithCoinSelectLock(func() error {
utxos, err = r.server.cc.Wallet.ListUnspentWitness(
minConfs, maxConfs, in.Account,
)
return err
})
if err != nil {
return nil, err
}
rpcUtxos, err := lnrpc.MarshalUtxos(utxos, r.cfg.ActiveNetParams.Params)
if err != nil {
return nil, err
}
maxStr := ""
if maxConfs != math.MaxInt32 {
maxStr = " max=" + fmt.Sprintf("%d", maxConfs)
}
rpcsLog.Debugf("[listunspent] min=%v%v, generated utxos: %v", minConfs,
maxStr, utxos)
return &lnrpc.ListUnspentResponse{
Utxos: rpcUtxos,
}, nil
}
// EstimateFee handles a request for estimating the fee for sending a
// transaction spending to multiple specified outputs in parallel.
func (r *rpcServer) EstimateFee(ctx context.Context,
in *lnrpc.EstimateFeeRequest) (*lnrpc.EstimateFeeResponse, error) {
// Create the list of outputs we are spending to.
outputs, err := addrPairsToOutputs(in.AddrToAmount, r.cfg.ActiveNetParams.Params)
if err != nil {
return nil, err
}
// Query the fee estimator for the fee rate for the given confirmation
// target.
target := in.TargetConf
feePref := sweep.FeeEstimateInfo{
ConfTarget: uint32(target),
}
// Since we are providing a fee estimation as an RPC response, there's
// no need to set a max feerate here, so we use 0.
feePerKw, err := feePref.Estimate(r.server.cc.FeeEstimator, 0)
if err != nil {
return nil, err
}
// Then, we'll extract the minimum number of confirmations that each
// output we use to fund the transaction should satisfy.
minConfs, err := lnrpc.ExtractMinConfs(
in.GetMinConfs(), in.GetSpendUnconfirmed(),
)
if err != nil {
return nil, err
}
coinSelectionStrategy, err := lnrpc.UnmarshallCoinSelectionStrategy(
in.CoinSelectionStrategy,
r.server.cc.Wallet.Cfg.CoinSelectionStrategy,
)
if err != nil {
return nil, err
}
// We will ask the wallet to create a tx using this fee rate. We set
// dryRun=true to avoid inflating the change addresses in the db.
var tx *txauthor.AuthoredTx
wallet := r.server.cc.Wallet
err = wallet.WithCoinSelectLock(func() error {
tx, err = wallet.CreateSimpleTx(
nil, outputs, feePerKw, minConfs, coinSelectionStrategy,
true,
)
return err
})
if err != nil {
return nil, err
}
// Use the created tx to calculate the total fee.
totalOutput := int64(0)
for _, out := range tx.Tx.TxOut {
totalOutput += out.Value
}
totalFee := int64(tx.TotalInput) - totalOutput
resp := &lnrpc.EstimateFeeResponse{
FeeSat: totalFee,
SatPerVbyte: uint64(feePerKw.FeePerVByte()),
// Deprecated field.
FeerateSatPerByte: int64(feePerKw.FeePerVByte()),
}
rpcsLog.Debugf("[estimatefee] fee estimate for conf target %d: %v",
target, resp)
return resp, nil
}
// maybeUseDefaultConf makes sure that when the user doesn't set either the fee
// rate or conf target, the default conf target is used.
func maybeUseDefaultConf(satPerByte int64, satPerVByte uint64,
targetConf uint32) uint32 {
// If the fee rate is set, there's no need to use the default conf
// target. In this case, we just return the targetConf from the
// request.
if satPerByte != 0 || satPerVByte != 0 {
return targetConf
}
// Return the user specified conf target if set.
if targetConf != 0 {
return targetConf
}
// If the fee rate is not set, yet the conf target is zero, the default
// 6 will be returned.
rpcsLog.Warnf("Expected either 'sat_per_vbyte' or 'conf_target' to " +
"be set, using default conf of 6 instead")
return defaultNumBlocksEstimate
}
// SendCoins executes a request to send coins to a particular address. Unlike
// SendMany, this RPC call only allows creating a single output at a time.
func (r *rpcServer) SendCoins(ctx context.Context,
in *lnrpc.SendCoinsRequest) (*lnrpc.SendCoinsResponse, error) {
// Keep the old behavior prior to 0.18.0 - when the user doesn't set
// fee rate or conf target, the default conf target of 6 is used.
targetConf := maybeUseDefaultConf(
in.SatPerByte, in.SatPerVbyte, uint32(in.TargetConf),
)
// Calculate an appropriate fee rate for this transaction.
feePerKw, err := lnrpc.CalculateFeeRate(
uint64(in.SatPerByte), in.SatPerVbyte, // nolint:staticcheck
targetConf, r.server.cc.FeeEstimator,
)
if err != nil {
return nil, err
}
// Then, we'll extract the minimum number of confirmations that each
// output we use to fund the transaction should satisfy.
minConfs, err := lnrpc.ExtractMinConfs(in.MinConfs, in.SpendUnconfirmed)
if err != nil {
return nil, err
}
rpcsLog.Infof("[sendcoins] addr=%v, amt=%v, sat/kw=%v, min_confs=%v, "+
"send_all=%v, select_outpoints=%v",
in.Addr, btcutil.Amount(in.Amount), int64(feePerKw), minConfs,
in.SendAll, len(in.Outpoints))
// Decode the address receiving the coins, we need to check whether the
// address is valid for this network.
targetAddr, err := btcutil.DecodeAddress(
in.Addr, r.cfg.ActiveNetParams.Params,
)
if err != nil {
return nil, err
}
// Make the check on the decoded address according to the active network.
if !targetAddr.IsForNet(r.cfg.ActiveNetParams.Params) {
return nil, fmt.Errorf("address: %v is not valid for this "+
"network: %v", targetAddr.String(),
r.cfg.ActiveNetParams.Params.Name)
}
// If the destination address parses to a valid pubkey, we assume the user
// accidentally tried to send funds to a bare pubkey address. This check is
// here to prevent unintended transfers.
decodedAddr, _ := hex.DecodeString(in.Addr)
_, err = btcec.ParsePubKey(decodedAddr)
if err == nil {
return nil, fmt.Errorf("cannot send coins to pubkeys")
}
label, err := labels.ValidateAPI(in.Label)
if err != nil {
return nil, err
}
coinSelectionStrategy, err := lnrpc.UnmarshallCoinSelectionStrategy(
in.CoinSelectionStrategy,
r.server.cc.Wallet.Cfg.CoinSelectionStrategy,
)
if err != nil {
return nil, err
}
var txid *chainhash.Hash
wallet := r.server.cc.Wallet
maxFeeRate := r.cfg.Sweeper.MaxFeeRate.FeePerKWeight()
var selectOutpoints fn.Set[wire.OutPoint]
if len(in.Outpoints) != 0 {
wireOutpoints, err := toWireOutpoints(in.Outpoints)
if err != nil {
return nil, fmt.Errorf("can't create outpoints "+
"%w", err)
}
if fn.HasDuplicates(wireOutpoints) {
return nil, fmt.Errorf("selected outpoints contain " +
"duplicate values")
}
selectOutpoints = fn.NewSet(wireOutpoints...)
}
// If the send all flag is active, then we'll attempt to sweep all the
// coins in the wallet in a single transaction (if possible),
// otherwise, we'll respect the amount, and attempt a regular 2-output
// send.
if in.SendAll {
// At this point, the amount shouldn't be set since we've been
// instructed to sweep all the coins from the wallet.
if in.Amount != 0 {
return nil, fmt.Errorf("amount set while SendAll is " +
"active")
}
_, bestHeight, err := r.server.cc.ChainIO.GetBestBlock()
if err != nil {
return nil, err
}
// With the sweeper instance created, we can now generate a
// transaction that will sweep ALL outputs from the wallet in a
// single transaction. This will be generated in a concurrent
// safe manner, so no need to worry about locking. The tx will
// pay to the change address created above if we needed to
// reserve any value, the rest will go to targetAddr.
sweepTxPkg, err := sweep.CraftSweepAllTx(
feePerKw, maxFeeRate, uint32(bestHeight), nil,
targetAddr, wallet, wallet, wallet.WalletController,
r.server.cc.Signer, minConfs, selectOutpoints,
)
if err != nil {
return nil, err
}
// Before we publish the transaction we make sure it won't
// violate our reserved wallet value.
var reservedVal btcutil.Amount
err = wallet.WithCoinSelectLock(func() error {
var err error
reservedVal, err = wallet.CheckReservedValueTx(
lnwallet.CheckReservedValueTxReq{
Tx: sweepTxPkg.SweepTx,
},
)
return err
})
// If sending everything to this address would invalidate our
// reserved wallet balance, we create a new sweep tx, where
// we'll send the reserved value back to our wallet.
if err == lnwallet.ErrReservedValueInvalidated {
sweepTxPkg.CancelSweepAttempt()
rpcsLog.Debugf("Reserved value %v not satisfied after "+
"send_all, trying with change output",
reservedVal)
// We'll request a change address from the wallet,
// where we'll send this reserved value back to. This
// ensures this is an address the wallet knows about,
// allowing us to pass the reserved value check.
changeAddr, err := r.server.cc.Wallet.NewAddress(
lnwallet.TaprootPubkey, true,
lnwallet.DefaultAccountName,
)
if err != nil {
return nil, err
}
// Send the reserved value to this change address, the
// remaining funds will go to the targetAddr.
outputs := []sweep.DeliveryAddr{
{
Addr: changeAddr,
Amt: reservedVal,
},
}
sweepTxPkg, err = sweep.CraftSweepAllTx(
feePerKw, maxFeeRate, uint32(bestHeight),
outputs, targetAddr, wallet, wallet,
wallet.WalletController,
r.server.cc.Signer, minConfs, selectOutpoints,
)
if err != nil {
return nil, err
}
// Sanity check the new tx by re-doing the check.
err = wallet.WithCoinSelectLock(func() error {
_, err := wallet.CheckReservedValueTx(
lnwallet.CheckReservedValueTxReq{
Tx: sweepTxPkg.SweepTx,
},
)
return err
})
if err != nil {
sweepTxPkg.CancelSweepAttempt()
return nil, err
}
} else if err != nil {
sweepTxPkg.CancelSweepAttempt()
return nil, err
}
rpcsLog.Debugf("Sweeping coins from wallet to addr=%v, "+
"with tx=%v", in.Addr, spew.Sdump(sweepTxPkg.SweepTx))
// As our sweep transaction was created, successfully, we'll
// now attempt to publish it, cancelling the sweep pkg to
// return all outputs if it fails.
err = wallet.PublishTransaction(sweepTxPkg.SweepTx, label)
if err != nil {
sweepTxPkg.CancelSweepAttempt()
return nil, fmt.Errorf("unable to broadcast sweep "+
"transaction: %v", err)
}
sweepTXID := sweepTxPkg.SweepTx.TxHash()
txid = &sweepTXID
} else {
// We'll now construct out payment map, and use the wallet's
// coin selection synchronization method to ensure that no coin
// selection (funding, sweep alls, other sends) can proceed
// while we instruct the wallet to send this transaction.
paymentMap := map[string]int64{targetAddr.String(): in.Amount}
err := wallet.WithCoinSelectLock(func() error {
newTXID, err := r.sendCoinsOnChain(
paymentMap, feePerKw, minConfs, label,
coinSelectionStrategy, selectOutpoints,
)
if err != nil {
return err
}
txid = newTXID
return nil
})
if err != nil {
return nil, err
}
}
rpcsLog.Infof("[sendcoins] spend generated txid: %v", txid.String())
return &lnrpc.SendCoinsResponse{Txid: txid.String()}, nil
}
// SendMany handles a request for a transaction create multiple specified
// outputs in parallel.
func (r *rpcServer) SendMany(ctx context.Context,
in *lnrpc.SendManyRequest) (*lnrpc.SendManyResponse, error) {
// Keep the old behavior prior to 0.18.0 - when the user doesn't set
// fee rate or conf target, the default conf target of 6 is used.
targetConf := maybeUseDefaultConf(
in.SatPerByte, in.SatPerVbyte, uint32(in.TargetConf),
)
// Calculate an appropriate fee rate for this transaction.
feePerKw, err := lnrpc.CalculateFeeRate(
uint64(in.SatPerByte), in.SatPerVbyte, // nolint:staticcheck
targetConf, r.server.cc.FeeEstimator,
)
if err != nil {
return nil, err
}
// Then, we'll extract the minimum number of confirmations that each
// output we use to fund the transaction should satisfy.
minConfs, err := lnrpc.ExtractMinConfs(in.MinConfs, in.SpendUnconfirmed)
if err != nil {
return nil, err
}
label, err := labels.ValidateAPI(in.Label)
if err != nil {
return nil, err
}
coinSelectionStrategy, err := lnrpc.UnmarshallCoinSelectionStrategy(
in.CoinSelectionStrategy,
r.server.cc.Wallet.Cfg.CoinSelectionStrategy,
)
if err != nil {
return nil, err
}
rpcsLog.Infof("[sendmany] outputs=%v, sat/kw=%v",
spew.Sdump(in.AddrToAmount), int64(feePerKw))
var txid *chainhash.Hash
// We'll attempt to send to the target set of outputs, ensuring that we
// synchronize with any other ongoing coin selection attempts which
// happen to also be concurrently executing.
wallet := r.server.cc.Wallet
err = wallet.WithCoinSelectLock(func() error {
sendManyTXID, err := r.sendCoinsOnChain(
in.AddrToAmount, feePerKw, minConfs, label,
coinSelectionStrategy, nil,
)
if err != nil {
return err
}
txid = sendManyTXID
return nil
})
if err != nil {
return nil, err
}
rpcsLog.Infof("[sendmany] spend generated txid: %v", txid.String())
return &lnrpc.SendManyResponse{Txid: txid.String()}, nil
}
// NewAddress creates a new address under control of the local wallet.
func (r *rpcServer) NewAddress(ctx context.Context,
in *lnrpc.NewAddressRequest) (*lnrpc.NewAddressResponse, error) {
// Always use the default wallet account unless one was specified.
account := lnwallet.DefaultAccountName
if in.Account != "" {
account = in.Account
}
// Translate the gRPC proto address type to the wallet controller's
// available address types.
var (
addr btcutil.Address
err error
)
switch in.Type {
case lnrpc.AddressType_WITNESS_PUBKEY_HASH:
addr, err = r.server.cc.Wallet.NewAddress(
lnwallet.WitnessPubKey, false, account,
)
if err != nil {
return nil, err
}
case lnrpc.AddressType_NESTED_PUBKEY_HASH:
addr, err = r.server.cc.Wallet.NewAddress(
lnwallet.NestedWitnessPubKey, false, account,
)
if err != nil {
return nil, err
}
case lnrpc.AddressType_TAPROOT_PUBKEY:
addr, err = r.server.cc.Wallet.NewAddress(
lnwallet.TaprootPubkey, false, account,
)
if err != nil {
return nil, err
}
case lnrpc.AddressType_UNUSED_WITNESS_PUBKEY_HASH:
addr, err = r.server.cc.Wallet.LastUnusedAddress(
lnwallet.WitnessPubKey, account,
)
if err != nil {
return nil, err
}
case lnrpc.AddressType_UNUSED_NESTED_PUBKEY_HASH:
addr, err = r.server.cc.Wallet.LastUnusedAddress(
lnwallet.NestedWitnessPubKey, account,
)
if err != nil {
return nil, err
}
case lnrpc.AddressType_UNUSED_TAPROOT_PUBKEY:
addr, err = r.server.cc.Wallet.LastUnusedAddress(
lnwallet.TaprootPubkey, account,
)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unknown address type: %v", in.Type)
}
rpcsLog.Debugf("[newaddress] account=%v type=%v addr=%v", account,
in.Type, addr.String())
return &lnrpc.NewAddressResponse{Address: addr.String()}, nil
}
var (
// signedMsgPrefix is a special prefix that we'll prepend to any
// messages we sign/verify. We do this to ensure that we don't
// accidentally sign a sighash, or other sensitive material. By
// prepending this fragment, we mind message signing to our particular
// context.
signedMsgPrefix = []byte("Lightning Signed Message:")
)
// SignMessage signs a message with the resident node's private key. The
// returned signature string is zbase32 encoded and pubkey recoverable, meaning
// that only the message digest and signature are needed for verification.
func (r *rpcServer) SignMessage(_ context.Context,
in *lnrpc.SignMessageRequest) (*lnrpc.SignMessageResponse, error) {
if in.Msg == nil {
return nil, fmt.Errorf("need a message to sign")
}
in.Msg = append(signedMsgPrefix, in.Msg...)
sigBytes, err := r.server.nodeSigner.SignMessageCompact(
in.Msg, !in.SingleHash,
)
if err != nil {
return nil, err
}
sig := zbase32.EncodeToString(sigBytes)
return &lnrpc.SignMessageResponse{Signature: sig}, nil
}
// VerifyMessage verifies a signature over a msg. The signature must be zbase32
// encoded and signed by an active node in the resident node's channel
// database. In addition to returning the validity of the signature,
// VerifyMessage also returns the recovered pubkey from the signature.
func (r *rpcServer) VerifyMessage(ctx context.Context,
in *lnrpc.VerifyMessageRequest) (*lnrpc.VerifyMessageResponse, error) {
if in.Msg == nil {
return nil, fmt.Errorf("need a message to verify")
}
// The signature should be zbase32 encoded
sig, err := zbase32.DecodeString(in.Signature)
if err != nil {
return nil, fmt.Errorf("failed to decode signature: %w", err)
}
// The signature is over the double-sha256 hash of the message.
in.Msg = append(signedMsgPrefix, in.Msg...)
digest := chainhash.DoubleHashB(in.Msg)
// RecoverCompact both recovers the pubkey and validates the signature.
pubKey, _, err := ecdsa.RecoverCompact(sig, digest)
if err != nil {
return &lnrpc.VerifyMessageResponse{Valid: false}, nil
}
pubKeyHex := hex.EncodeToString(pubKey.SerializeCompressed())
var pub [33]byte
copy(pub[:], pubKey.SerializeCompressed())
// Query the channel graph to ensure a node in the network with active
// channels signed the message.
//
// TODO(phlip9): Require valid nodes to have capital in active channels.
graph := r.server.graphDB
_, active, err := graph.HasLightningNode(pub)
if err != nil {
return nil, fmt.Errorf("failed to query graph: %w", err)
}
return &lnrpc.VerifyMessageResponse{
Valid: active,
Pubkey: pubKeyHex,
}, nil
}
// ConnectPeer attempts to establish a connection to a remote peer.
func (r *rpcServer) ConnectPeer(ctx context.Context,
in *lnrpc.ConnectPeerRequest) (*lnrpc.ConnectPeerResponse, error) {
// The server hasn't yet started, so it won't be able to service any of
// our requests, so we'll bail early here.
if !r.server.Started() {
return nil, ErrServerNotActive
}
if in.Addr == nil {
return nil, fmt.Errorf("need: lnc pubkeyhash@hostname")
}
pubkeyHex, err := hex.DecodeString(in.Addr.Pubkey)
if err != nil {
return nil, err
}
pubKey, err := btcec.ParsePubKey(pubkeyHex)
if err != nil {
return nil, err
}
// Connections to ourselves are disallowed for obvious reasons.
if pubKey.IsEqual(r.server.identityECDH.PubKey()) {
return nil, fmt.Errorf("cannot make connection to self")
}
addr, err := parseAddr(in.Addr.Host, r.cfg.net)
if err != nil {
return nil, err
}
peerAddr := &lnwire.NetAddress{
IdentityKey: pubKey,
Address: addr,
ChainNet: r.cfg.ActiveNetParams.Net,
}
rpcsLog.Debugf("[connectpeer] requested connection to %x@%s",
peerAddr.IdentityKey.SerializeCompressed(), peerAddr.Address)
// By default, we will use the global connection timeout value.
timeout := r.cfg.ConnectionTimeout
// Check if the connection timeout is set. If set, we will use it in our
// request.
if in.Timeout != 0 {
timeout = time.Duration(in.Timeout) * time.Second
rpcsLog.Debugf("[connectpeer] connection timeout is set to %v",
timeout)
}
if err := r.server.ConnectToPeer(
peerAddr, in.Perm, timeout,
); err != nil {
rpcsLog.Errorf("[connectpeer]: error connecting to peer: %v",
err)
return nil, err
}
rpcsLog.Debugf("Connected to peer: %v", peerAddr.String())
return &lnrpc.ConnectPeerResponse{
Status: fmt.Sprintf("connection to %v initiated",
peerAddr.String()),
}, nil
}
// DisconnectPeer attempts to disconnect one peer from another identified by a
// given pubKey. In the case that we currently have a pending or active channel
// with the target peer, this action will be disallowed.
func (r *rpcServer) DisconnectPeer(ctx context.Context,
in *lnrpc.DisconnectPeerRequest) (*lnrpc.DisconnectPeerResponse, error) {
rpcsLog.Debugf("[disconnectpeer] from peer(%s)", in.PubKey)
if !r.server.Started() {
return nil, ErrServerNotActive
}
// First we'll validate the string passed in within the request to
// ensure that it's a valid hex-string, and also a valid compressed
// public key.
pubKeyBytes, err := hex.DecodeString(in.PubKey)
if err != nil {
return nil, fmt.Errorf("unable to decode pubkey bytes: %w", err)
}
peerPubKey, err := btcec.ParsePubKey(pubKeyBytes)
if err != nil {
return nil, fmt.Errorf("unable to parse pubkey: %w", err)
}
// Next, we'll fetch the pending/active channels we have with a
// particular peer.
nodeChannels, err := r.server.chanStateDB.FetchOpenChannels(peerPubKey)
if err != nil {
return nil, fmt.Errorf("unable to fetch channels for peer: %w",
err)
}
// In order to avoid erroneously disconnecting from a peer that we have
// an active channel with, if we have any channels active with this
// peer, then we'll disallow disconnecting from them.
if len(nodeChannels) != 0 {
// If we are not in a dev environment or the configed dev value
// `unsafedisconnect` is false, we return an error since there
// are active channels.
if !r.cfg.Dev.GetUnsafeDisconnect() {
return nil, fmt.Errorf("cannot disconnect from "+
"peer(%x), still has %d active channels",
pubKeyBytes, len(nodeChannels))
}
// We are in a dev environment, print a warning log and
// disconnect.
rpcsLog.Warnf("UnsafeDisconnect mode, disconnecting from "+
"peer(%x) while there are %d active channels",
pubKeyBytes, len(nodeChannels))
}
// With all initial validation complete, we'll now request that the
// server disconnects from the peer.
err = r.server.DisconnectPeer(peerPubKey)
if err != nil {
return nil, fmt.Errorf("unable to disconnect peer: %w", err)
}
return &lnrpc.DisconnectPeerResponse{
Status: "disconnect initiated",
}, nil
}
// newFundingShimAssembler returns a new fully populated
// chanfunding.CannedAssembler using a FundingShim obtained from an RPC caller.
func newFundingShimAssembler(chanPointShim *lnrpc.ChanPointShim, initiator bool,
keyRing keychain.KeyRing) (chanfunding.Assembler, error) {
// Perform some basic sanity checks to ensure that all the expected
// fields are populated.
switch {
case chanPointShim.RemoteKey == nil:
return nil, fmt.Errorf("remote key not set")
case chanPointShim.LocalKey == nil:
return nil, fmt.Errorf("local key desc not set")
case chanPointShim.LocalKey.RawKeyBytes == nil:
return nil, fmt.Errorf("local raw key bytes not set")
case chanPointShim.LocalKey.KeyLoc == nil:
return nil, fmt.Errorf("local key loc not set")
case chanPointShim.ChanPoint == nil:
return nil, fmt.Errorf("chan point not set")
case len(chanPointShim.PendingChanId) != 32:
return nil, fmt.Errorf("pending chan ID not set")
}
// First, we'll map the RPC's channel point to one we can actually use.
index := chanPointShim.ChanPoint.OutputIndex
txid, err := lnrpc.GetChanPointFundingTxid(chanPointShim.ChanPoint)
if err != nil {
return nil, err
}
chanPoint := wire.NewOutPoint(txid, index)
// Next we'll parse out the remote party's funding key, as well as our
// full key descriptor.
remoteKey, err := btcec.ParsePubKey(chanPointShim.RemoteKey)
if err != nil {
return nil, err
}
shimKeyDesc := chanPointShim.LocalKey
localKey, err := btcec.ParsePubKey(shimKeyDesc.RawKeyBytes)
if err != nil {
return nil, err
}
localKeyDesc := keychain.KeyDescriptor{
PubKey: localKey,
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamily(
shimKeyDesc.KeyLoc.KeyFamily,
),
Index: uint32(shimKeyDesc.KeyLoc.KeyIndex),
},
}
// Verify that if we re-derive this key according to the passed
// KeyLocator, that we get the exact same key back. Otherwise, we may
// end up in a situation where we aren't able to actually sign for this
// newly created channel.
derivedKey, err := keyRing.DeriveKey(localKeyDesc.KeyLocator)
if err != nil {
return nil, err
}
if !derivedKey.PubKey.IsEqual(localKey) {
return nil, fmt.Errorf("KeyLocator does not match attached " +
"raw pubkey")
}
// With all the parts assembled, we can now make the canned assembler
// to pass into the wallet.
//
// TODO(roasbeef): update to support musig2
return chanfunding.NewCannedAssembler(
chanPointShim.ThawHeight, *chanPoint,
btcutil.Amount(chanPointShim.Amt), &localKeyDesc,
remoteKey, initiator, chanPointShim.Musig2,
), nil
}
// newPsbtAssembler returns a new fully populated
// chanfunding.PsbtAssembler using a FundingShim obtained from an RPC caller.
func newPsbtAssembler(req *lnrpc.OpenChannelRequest,
psbtShim *lnrpc.PsbtShim, netParams *chaincfg.Params) (
chanfunding.Assembler, error) {
var (
packet *psbt.Packet
err error
)
// Perform some basic sanity checks to ensure that all the expected
// fields are populated and none of the incompatible fields are.
if len(psbtShim.PendingChanId) != 32 {
return nil, fmt.Errorf("pending chan ID not set")
}
if req.SatPerByte != 0 || req.SatPerVbyte != 0 || req.TargetConf != 0 { // nolint:staticcheck
return nil, fmt.Errorf("specifying fee estimation parameters " +
"is not supported for PSBT funding")
}
// The base PSBT is optional. But if it's set, it has to be a valid,
// binary serialized PSBT.
if len(psbtShim.BasePsbt) > 0 {
packet, err = psbt.NewFromRawBytes(
bytes.NewReader(psbtShim.BasePsbt), false,
)
if err != nil {
return nil, fmt.Errorf("error parsing base PSBT: %w",
err)
}
}
// With all the parts assembled, we can now make the canned assembler
// to pass into the wallet.
return chanfunding.NewPsbtAssembler(
btcutil.Amount(req.LocalFundingAmount), packet, netParams,
!psbtShim.NoPublish,
), nil
}
// canOpenChannel returns an error if the necessary subsystems for channel
// funding are not ready.
func (r *rpcServer) canOpenChannel() error {
// We can't open a channel until the main server has started.
if !r.server.Started() {
return ErrServerNotActive
}
// Creation of channels before the wallet syncs up is currently
// disallowed.
isSynced, _, err := r.server.cc.Wallet.IsSynced()
if err != nil {
return err
}
if !isSynced {
return errors.New("channels cannot be created before the " +
"wallet is fully synced")
}
return nil
}
// parseOpenChannelReq parses an OpenChannelRequest message into an InitFundingMsg
// struct. The logic is abstracted so that it can be shared between OpenChannel
// and OpenChannelSync.
func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest,
isSync bool) (*funding.InitFundingMsg, error) {
rpcsLog.Debugf("[openchannel] request to NodeKey(%x) "+
"allocation(us=%v, them=%v)", in.NodePubkey,
in.LocalFundingAmount, in.PushSat)
localFundingAmt := btcutil.Amount(in.LocalFundingAmount)
remoteInitialBalance := btcutil.Amount(in.PushSat)
// If we are not committing the maximum viable balance towards a channel
// then the local funding amount must be specified. In case FundMax is
// set the funding amount is specified as the interval between minimum
// funding amount and by the configured maximum channel size.
if !in.FundMax && localFundingAmt == 0 {
return nil, fmt.Errorf("local funding amount must be non-zero")
}
// Ensure that the initial balance of the remote party (if pushing
// satoshis) does not exceed the amount the local party has requested
// for funding. This is only checked if we are not committing the
// maximum viable amount towards the channel balance. If we do commit
// the maximum then the remote balance is checked in a dedicated FundMax
// check.
if !in.FundMax && remoteInitialBalance >= localFundingAmt {
return nil, fmt.Errorf("amount pushed to remote peer for " +
"initial state must be below the local funding amount")
}
// We either allow the fundmax or the psbt flow hence we return an error
// if both are set.
if in.FundingShim != nil && in.FundMax {
return nil, fmt.Errorf("cannot provide a psbt funding shim " +
"while committing the maximum wallet balance towards " +
"the channel opening")
}
// If the FundMax flag is set, ensure that the acceptable minimum local
// amount adheres to the amount to be pushed to the remote, and to
// current rules, while also respecting the settings for the maximum
// channel size.
var minFundAmt, fundUpToMaxAmt btcutil.Amount
if in.FundMax {
// We assume the configured maximum channel size to be the upper
// bound of our "maxed" out funding attempt.
fundUpToMaxAmt = btcutil.Amount(r.cfg.MaxChanSize)
// Since the standard non-fundmax flow requires the minimum
// funding amount to be at least in the amount of the initial
// remote balance(push amount) we need to adjust the minimum
// funding amount accordingly. We initially assume the minimum
// allowed channel size as minimum funding amount.
minFundAmt = funding.MinChanFundingSize
// If minFundAmt is less than the initial remote balance we
// simply assign the initial remote balance to minFundAmt in
// order to fullfil the criterion. Whether or not this so
// determined minimum amount is actually available is
// ascertained downstream in the lnwallet's reservation
// workflow.
if remoteInitialBalance >= minFundAmt {
minFundAmt = remoteInitialBalance
}
}
minHtlcIn := lnwire.MilliSatoshi(in.MinHtlcMsat)
remoteCsvDelay := uint16(in.RemoteCsvDelay)
maxValue := lnwire.MilliSatoshi(in.RemoteMaxValueInFlightMsat)
maxHtlcs := uint16(in.RemoteMaxHtlcs)
remoteChanReserve := btcutil.Amount(in.RemoteChanReserveSat)
globalFeatureSet := r.server.featureMgr.Get(feature.SetNodeAnn)
// Determine if the user provided channel fees
// and if so pass them on to the funding workflow.
var channelBaseFee, channelFeeRate *uint64
if in.UseBaseFee {
channelBaseFee = &in.BaseFee
}
if in.UseFeeRate {
channelFeeRate = &in.FeeRate
}
// Ensure that the remote channel reserve does not exceed 20% of the
// channel capacity.
if !in.FundMax && remoteChanReserve >= localFundingAmt/5 {
return nil, fmt.Errorf("remote channel reserve must be less " +
"than the %%20 of the channel capacity")
}
// Ensure that the user doesn't exceed the current soft-limit for
// channel size. If the funding amount is above the soft-limit, then
// we'll reject the request.
// If the FundMax flag is set the local amount is determined downstream
// in the wallet hence we do not check it here against the maximum
// funding amount. Only if the localFundingAmt is specified we can check
// if it exceeds the maximum funding amount.
wumboEnabled := globalFeatureSet.HasFeature(
lnwire.WumboChannelsOptional,
)
if !in.FundMax && !wumboEnabled && localFundingAmt > MaxFundingAmount {
return nil, fmt.Errorf("funding amount is too large, the max "+
"channel size is: %v", MaxFundingAmount)
}
// Restrict the size of the channel we'll actually open. At a later
// level, we'll ensure that the output we create, after accounting for
// fees, does not leave a dust output. In case of the FundMax flow
// dedicated checks ensure that the lower boundary of the channel size
// is at least in the amount of MinChanFundingSize or potentially higher
// if a remote balance is specified.
if !in.FundMax && localFundingAmt < funding.MinChanFundingSize {
return nil, fmt.Errorf("channel is too small, the minimum "+
"channel size is: %v SAT", int64(funding.MinChanFundingSize))
}
// Prevent users from submitting a max-htlc value that would exceed the
// protocol maximum.
if maxHtlcs > input.MaxHTLCNumber/2 {
return nil, fmt.Errorf("remote-max-htlcs (%v) cannot be "+
"greater than %v", maxHtlcs, input.MaxHTLCNumber/2)
}
// Then, we'll extract the minimum number of confirmations that each
// output we use to fund the channel's funding transaction should
// satisfy.
minConfs, err := lnrpc.ExtractMinConfs(in.MinConfs, in.SpendUnconfirmed)
if err != nil {
return nil, err
}
// TODO(roasbeef): also return channel ID?
var nodePubKey *btcec.PublicKey
// Parse the remote pubkey the NodePubkey field of the request. If it's
// not present, we'll fallback to the deprecated version that parses the
// key from a hex string if this is for REST for backwards compatibility.
switch {
// Parse the raw bytes of the node key into a pubkey object so we can
// easily manipulate it.
case len(in.NodePubkey) > 0:
nodePubKey, err = btcec.ParsePubKey(in.NodePubkey)
if err != nil {
return nil, err
}
// Decode the provided target node's public key, parsing it into a pub
// key object. For all sync call, byte slices are expected to be encoded
// as hex strings.
case isSync:
keyBytes, err := hex.DecodeString(in.NodePubkeyString) // nolint:staticcheck
if err != nil {
return nil, err
}
nodePubKey, err = btcec.ParsePubKey(keyBytes)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("NodePubkey is not set")
}
// Making a channel to ourselves wouldn't be of any use, so we
// explicitly disallow them.
if nodePubKey.IsEqual(r.server.identityECDH.PubKey()) {
return nil, fmt.Errorf("cannot open channel to self")
}
// NOTE: We also need to do the fee rate calculation for the psbt
// funding flow because the `batchfund` depends on it.
targetConf := maybeUseDefaultConf(
in.SatPerByte, in.SatPerVbyte, uint32(in.TargetConf),
)
// Calculate an appropriate fee rate for this transaction.
feeRate, err := lnrpc.CalculateFeeRate(
uint64(in.SatPerByte), in.SatPerVbyte,
targetConf, r.server.cc.FeeEstimator,
)
if err != nil {
return nil, err
}
rpcsLog.Debugf("[openchannel]: using fee of %v sat/kw for "+
"funding tx", int64(feeRate))
script, err := chancloser.ParseUpfrontShutdownAddress(
in.CloseAddress, r.cfg.ActiveNetParams.Params,
)
if err != nil {
return nil, fmt.Errorf("error parsing upfront shutdown: %w",
err)
}
var channelType *lnwire.ChannelType
switch in.CommitmentType {
case lnrpc.CommitmentType_UNKNOWN_COMMITMENT_TYPE:
if in.ZeroConf {
return nil, fmt.Errorf("use anchors for zero-conf")
}
case lnrpc.CommitmentType_LEGACY:
channelType = new(lnwire.ChannelType)
*channelType = lnwire.ChannelType(*lnwire.NewRawFeatureVector())
case lnrpc.CommitmentType_STATIC_REMOTE_KEY:
channelType = new(lnwire.ChannelType)
*channelType = lnwire.ChannelType(*lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyRequired,
))
case lnrpc.CommitmentType_ANCHORS:
channelType = new(lnwire.ChannelType)
fv := lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
)
if in.ZeroConf {
fv.Set(lnwire.ZeroConfRequired)
}
if in.ScidAlias {
fv.Set(lnwire.ScidAliasRequired)
}
*channelType = lnwire.ChannelType(*fv)
case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
channelType = new(lnwire.ChannelType)
fv := lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.ScriptEnforcedLeaseRequired,
)
if in.ZeroConf {
fv.Set(lnwire.ZeroConfRequired)
}
if in.ScidAlias {
fv.Set(lnwire.ScidAliasRequired)
}
*channelType = lnwire.ChannelType(*fv)
case lnrpc.CommitmentType_SIMPLE_TAPROOT:
// If the taproot channel type is being set, then the channel
// MUST be private (unadvertised) for now.
if !in.Private {
return nil, fmt.Errorf("taproot channels must be " +
"private")
}
channelType = new(lnwire.ChannelType)
fv := lnwire.NewRawFeatureVector(
lnwire.SimpleTaprootChannelsRequiredStaging,
)
// TODO(roasbeef): no need for the rest as they're now
// implicit?
if in.ZeroConf {
fv.Set(lnwire.ZeroConfRequired)
}
if in.ScidAlias {
fv.Set(lnwire.ScidAliasRequired)
}
*channelType = lnwire.ChannelType(*fv)
case lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY:
// If the taproot overlay channel type is being set, then the
// channel MUST be private.
if !in.Private {
return nil, fmt.Errorf("taproot overlay channels " +
"must be private")
}
channelType = new(lnwire.ChannelType)
fv := lnwire.NewRawFeatureVector(
lnwire.SimpleTaprootOverlayChansRequired,
)
if in.ZeroConf {
fv.Set(lnwire.ZeroConfRequired)
}
if in.ScidAlias {
fv.Set(lnwire.ScidAliasRequired)
}
*channelType = lnwire.ChannelType(*fv)
default:
return nil, fmt.Errorf("unhandled request channel type %v",
in.CommitmentType)
}
// We limit the channel memo to be 500 characters long. This enforces
// a reasonable upper bound on storage consumption. This also mimics
// the length limit for the label of a TX.
const maxMemoLength = 500
if len(in.Memo) > maxMemoLength {
return nil, fmt.Errorf("provided memo (%s) is of length %d, "+
"exceeds %d", in.Memo, len(in.Memo), maxMemoLength)
}
// Check, if manually selected outpoints are present to fund a channel.
var outpoints []wire.OutPoint
if len(in.Outpoints) > 0 {
outpoints, err = toWireOutpoints(in.Outpoints)
if err != nil {
return nil, fmt.Errorf("can't create outpoints %w", err)
}
}
// Instruct the server to trigger the necessary events to attempt to
// open a new channel. A stream is returned in place, this stream will
// be used to consume updates of the state of the pending channel.
return &funding.InitFundingMsg{
TargetPubkey: nodePubKey,
ChainHash: *r.cfg.ActiveNetParams.GenesisHash,
LocalFundingAmt: localFundingAmt,
BaseFee: channelBaseFee,
FeeRate: channelFeeRate,
PushAmt: lnwire.NewMSatFromSatoshis(
remoteInitialBalance,
),
MinHtlcIn: minHtlcIn,
FundingFeePerKw: feeRate,
Private: in.Private,
RemoteCsvDelay: remoteCsvDelay,
RemoteChanReserve: remoteChanReserve,
MinConfs: minConfs,
ShutdownScript: script,
MaxValueInFlight: maxValue,
MaxHtlcs: maxHtlcs,
MaxLocalCsv: uint16(in.MaxLocalCsv),
ChannelType: channelType,
FundUpToMaxAmt: fundUpToMaxAmt,
MinFundAmt: minFundAmt,
Memo: []byte(in.Memo),
Outpoints: outpoints,
}, nil
}
// toWireOutpoints converts a list of outpoints from the rpc format to the wire
// format.
func toWireOutpoints(outpoints []*lnrpc.OutPoint) ([]wire.OutPoint, error) {
var wireOutpoints []wire.OutPoint
for _, outpoint := range outpoints {
hash, err := chainhash.NewHashFromStr(outpoint.TxidStr)
if err != nil {
return nil, fmt.Errorf("cannot create chainhash")
}
wireOutpoint := wire.NewOutPoint(
hash, outpoint.OutputIndex,
)
wireOutpoints = append(wireOutpoints, *wireOutpoint)
}
return wireOutpoints, nil
}
// OpenChannel attempts to open a singly funded channel specified in the
// request to a remote peer.
func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest,
updateStream lnrpc.Lightning_OpenChannelServer) error {
if err := r.canOpenChannel(); err != nil {
return err
}
req, err := r.parseOpenChannelReq(in, false)
if err != nil {
return err
}
// If the user has provided a shim, then we'll now augment the based
// open channel request with this additional logic.
if in.FundingShim != nil {
switch {
// If we have a chan point shim, then this means the funding
// transaction was crafted externally. In this case we only
// need to hand a channel point down into the wallet.
case in.FundingShim.GetChanPointShim() != nil:
chanPointShim := in.FundingShim.GetChanPointShim()
// Map the channel point shim into a new
// chanfunding.CannedAssembler that the wallet will use
// to obtain the channel point details.
copy(req.PendingChanID[:], chanPointShim.PendingChanId)
req.ChanFunder, err = newFundingShimAssembler(
chanPointShim, true, r.server.cc.KeyRing,
)
if err != nil {
return err
}
// If we have a PSBT shim, then this means the funding
// transaction will be crafted outside of the wallet, once the
// funding multisig output script is known. We'll create an
// intent that will supervise the multi-step process.
case in.FundingShim.GetPsbtShim() != nil:
psbtShim := in.FundingShim.GetPsbtShim()
// Instruct the wallet to use the new
// chanfunding.PsbtAssembler to construct the funding
// transaction.
copy(req.PendingChanID[:], psbtShim.PendingChanId)
// NOTE: For the PSBT case we do also allow unconfirmed
// utxos to fund the psbt transaction because we make
// sure we only use stable utxos.
req.ChanFunder, err = newPsbtAssembler(
in, psbtShim,
&r.server.cc.Wallet.Cfg.NetParams,
)
if err != nil {
return err
}
}
}
updateChan, errChan := r.server.OpenChannel(req)
var outpoint wire.OutPoint
out:
for {
select {
case err := <-errChan:
rpcsLog.Errorf("unable to open channel to NodeKey(%x): %v",
req.TargetPubkey.SerializeCompressed(), err)
return err
case fundingUpdate := <-updateChan:
rpcsLog.Tracef("[openchannel] sending update: %v",
fundingUpdate)
if err := updateStream.Send(fundingUpdate); err != nil {
return err
}
// If a final channel open update is being sent, then
// we can break out of our recv loop as we no longer
// need to process any further updates.
update, ok := fundingUpdate.Update.(*lnrpc.OpenStatusUpdate_ChanOpen)
if ok {
chanPoint := update.ChanOpen.ChannelPoint
txid, err := lnrpc.GetChanPointFundingTxid(chanPoint)
if err != nil {
return err
}
outpoint = wire.OutPoint{
Hash: *txid,
Index: chanPoint.OutputIndex,
}
break out
}
case <-r.quit:
return nil
}
}
rpcsLog.Tracef("[openchannel] success NodeKey(%x), ChannelPoint(%v)",
req.TargetPubkey.SerializeCompressed(), outpoint)
return nil
}
// OpenChannelSync is a synchronous version of the OpenChannel RPC call. This
// call is meant to be consumed by clients to the REST proxy. As with all other
// sync calls, all byte slices are instead to be populated as hex encoded
// strings.
func (r *rpcServer) OpenChannelSync(ctx context.Context,
in *lnrpc.OpenChannelRequest) (*lnrpc.ChannelPoint, error) {
if err := r.canOpenChannel(); err != nil {
return nil, err
}
req, err := r.parseOpenChannelReq(in, true)
if err != nil {
return nil, err
}
updateChan, errChan := r.server.OpenChannel(req)
select {
// If an error occurs them immediately return the error to the client.
case err := <-errChan:
rpcsLog.Errorf("unable to open channel to NodeKey(%x): %v",
req.TargetPubkey.SerializeCompressed(), err)
return nil, err
// Otherwise, wait for the first channel update. The first update sent
// is when the funding transaction is broadcast to the network.
case fundingUpdate := <-updateChan:
rpcsLog.Tracef("[openchannel] sending update: %v",
fundingUpdate)
// Parse out the txid of the pending funding transaction. The
// sync client can use this to poll against the list of
// PendingChannels.
openUpdate := fundingUpdate.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
chanUpdate := openUpdate.ChanPending
return &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: chanUpdate.Txid,
},
OutputIndex: chanUpdate.OutputIndex,
}, nil
case <-r.quit:
return nil, nil
}
}
// BatchOpenChannel attempts to open multiple single-funded channels in a
// single transaction in an atomic way. This means either all channel open
// requests succeed at once or all attempts are aborted if any of them fail.
// This is the safer variant of using PSBTs to manually fund a batch of
// channels through the OpenChannel RPC.
func (r *rpcServer) BatchOpenChannel(ctx context.Context,
in *lnrpc.BatchOpenChannelRequest) (*lnrpc.BatchOpenChannelResponse,
error) {
if err := r.canOpenChannel(); err != nil {
return nil, err
}
// We need the wallet kit server to do the heavy lifting on the PSBT
// part. If we didn't rely on re-using the wallet kit server's logic we
// would need to re-implement everything here. Since we deliver lnd with
// the wallet kit server enabled by default we can assume it's okay to
// make this functionality dependent on that server being active.
var walletKitServer walletrpc.WalletKitServer
for _, subServer := range r.subServers {
if subServer.Name() == walletrpc.SubServerName {
walletKitServer = subServer.(walletrpc.WalletKitServer)
}
}
if walletKitServer == nil {
return nil, fmt.Errorf("batch channel open is only possible " +
"if walletrpc subserver is active")
}
rpcsLog.Debugf("[batchopenchannel] request to open batch of %d "+
"channels", len(in.Channels))
// Make sure there is at least one channel to open. We could say we want
// at least two channels for a batch. But maybe it's nice if developers
// can use the same API for a single channel as well as a batch of
// channels.
if len(in.Channels) == 0 {
return nil, fmt.Errorf("specify at least one channel")
}
// In case we remove a pending channel from the database, we need to set
// a close height, so we'll just use the current best known height.
_, bestHeight, err := r.server.cc.ChainIO.GetBestBlock()
if err != nil {
return nil, fmt.Errorf("error fetching best block: %w", err)
}
// So far everything looks good and we can now start the heavy lifting
// that's done in the funding package.
requestParser := func(req *lnrpc.OpenChannelRequest) (
*funding.InitFundingMsg, error) {
return r.parseOpenChannelReq(req, false)
}
channelAbandoner := func(point *wire.OutPoint) error {
return r.abandonChan(point, uint32(bestHeight))
}
batcher := funding.NewBatcher(&funding.BatchConfig{
RequestParser: requestParser,
ChannelAbandoner: channelAbandoner,
ChannelOpener: r.server.OpenChannel,
WalletKitServer: walletKitServer,
Wallet: r.server.cc.Wallet,
NetParams: &r.server.cc.Wallet.Cfg.NetParams,
Quit: r.quit,
})
rpcPoints, err := batcher.BatchFund(ctx, in)
if err != nil {
return nil, fmt.Errorf("batch funding failed: %w", err)
}
// Now all that's left to do is send back the response with the channel
// points we created.
return &lnrpc.BatchOpenChannelResponse{
PendingChannels: rpcPoints,
}, nil
}
// CloseChannel attempts to close an active channel identified by its channel
// point. The actions of this method can additionally be augmented to attempt
// a force close after a timeout period in the case of an inactive peer.
func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest,
updateStream lnrpc.Lightning_CloseChannelServer) error {
if !r.server.Started() {
return ErrServerNotActive
}
// If the user didn't specify a channel point, then we'll reject this
// request all together.
if in.GetChannelPoint() == nil {
return fmt.Errorf("must specify channel point in close channel")
}
// If force closing a channel, the fee set in the commitment transaction
// is used.
if in.Force && (in.SatPerByte != 0 || in.SatPerVbyte != 0 || // nolint:staticcheck
in.TargetConf != 0) {
return fmt.Errorf("force closing a channel uses a pre-defined fee")
}
force := in.Force
index := in.ChannelPoint.OutputIndex
txid, err := lnrpc.GetChanPointFundingTxid(in.GetChannelPoint())
if err != nil {
rpcsLog.Errorf("[closechannel] unable to get funding txid: %v", err)
return err
}
chanPoint := wire.NewOutPoint(txid, index)
rpcsLog.Tracef("[closechannel] request for ChannelPoint(%v), force=%v",
chanPoint, force)
var (
updateChan chan interface{}
errChan chan error
)
// TODO(roasbeef): if force and peer online then don't force?
// First, we'll fetch the channel as is, as we'll need to examine it
// regardless of if this is a force close or not.
channel, err := r.server.chanStateDB.FetchChannel(*chanPoint)
if err != nil {
return err
}
// We can't coop or force close restored channels or channels that have
// experienced local data loss. Normally we would detect this in the
// channel arbitrator if the channel has the status
// ChanStatusLocalDataLoss after connecting to its peer. But if no
// connection can be established, the channel arbitrator doesn't know it
// can't be force closed yet.
if channel.HasChanStatus(channeldb.ChanStatusRestored) ||
channel.HasChanStatus(channeldb.ChanStatusLocalDataLoss) {
return fmt.Errorf("cannot close channel with state: %v",
channel.ChanStatus())
}
// Retrieve the best height of the chain, which we'll use to complete
// either closing flow.
_, bestHeight, err := r.server.cc.ChainIO.GetBestBlock()
if err != nil {
return err
}
// Retrieve the number of active HTLCs on the channel.
activeHtlcs := channel.ActiveHtlcs()
// If a force closure was requested, then we'll handle all the details
// around the creation and broadcast of the unilateral closure
// transaction here rather than going to the switch as we don't require
// interaction from the peer.
if force {
// As we're force closing this channel, as a precaution, we'll
// ensure that the switch doesn't continue to see this channel
// as eligible for forwarding HTLC's. If the peer is online,
// then we'll also purge all of its indexes.
remotePub := channel.IdentityPub
if peer, err := r.server.FindPeer(remotePub); err == nil {
// TODO(roasbeef): actually get the active channel
// instead too?
// * so only need to grab from database
peer.WipeChannel(&channel.FundingOutpoint)
} else {
chanID := lnwire.NewChanIDFromOutPoint(
channel.FundingOutpoint,
)
r.server.htlcSwitch.RemoveLink(chanID)
}
// With the necessary indexes cleaned up, we'll now force close
// the channel.
chainArbitrator := r.server.chainArb
closingTx, err := chainArbitrator.ForceCloseContract(
*chanPoint,
)
if err != nil {
rpcsLog.Errorf("unable to force close transaction: %v", err)
return err
}
// Safety check which should never happen.
//
// TODO(ziggie): remove pointer as return value from
// ForceCloseContract.
if closingTx == nil {
return fmt.Errorf("force close transaction is nil")
}
closingTxid := closingTx.TxHash()
// With the transaction broadcast, we send our first update to
// the client.
updateChan = make(chan interface{}, 2)
updateChan <- &peer.PendingUpdate{
Txid: closingTxid[:],
}
errChan = make(chan error, 1)
notifier := r.server.cc.ChainNotifier
go peer.WaitForChanToClose(
uint32(bestHeight), notifier, errChan, chanPoint,
&closingTxid, closingTx.TxOut[0].PkScript, func() {
// Respond to the local subsystem which
// requested the channel closure.
updateChan <- &peer.ChannelCloseUpdate{
ClosingTxid: closingTxid[:],
Success: true,
// Force closure transactions don't have
// additional local/remote outputs.
}
},
)
} else {
// If this is a frozen channel, then we only allow the co-op
// close to proceed if we were the responder to this channel if
// the absolute thaw height has not been met.
if channel.IsInitiator {
absoluteThawHeight, err := channel.AbsoluteThawHeight()
if err != nil {
return err
}
if uint32(bestHeight) < absoluteThawHeight {
return fmt.Errorf("cannot co-op close frozen "+
"channel as initiator until height=%v, "+
"(current_height=%v)",
absoluteThawHeight, bestHeight)
}
}
var (
chanInSwitch = true
chanHasRbfCloser = r.server.ChanHasRbfCoopCloser(
channel.IdentityPub, *chanPoint,
)
)
// If the link is not known by the switch, we cannot gracefully close
// the channel.
channelID := lnwire.NewChanIDFromOutPoint(*chanPoint)
if _, err := r.server.htlcSwitch.GetLink(channelID); err != nil {
chanInSwitch = false
// The channel isn't in the switch, but if there's an
// active chan closer for the channel, and it's of the
// RBF variant, then we can actually bypass the switch.
// Otherwise, we'll return an error.
if !chanHasRbfCloser {
rpcsLog.Debugf("Trying to non-force close "+
"offline channel with chan_point=%v",
chanPoint)
return fmt.Errorf("unable to gracefully close "+
"channel while peer is offline (try "+
"force closing it instead): %v", err)
}
}
// Keep the old behavior prior to 0.18.0 - when the user
// doesn't set fee rate or conf target, the default conf target
// of 6 is used.
targetConf := maybeUseDefaultConf(
in.SatPerByte, in.SatPerVbyte, uint32(in.TargetConf),
)
// Based on the passed fee related parameters, we'll determine
// an appropriate fee rate for the cooperative closure
// transaction.
feeRate, err := lnrpc.CalculateFeeRate(
uint64(in.SatPerByte), in.SatPerVbyte, // nolint:staticcheck
targetConf, r.server.cc.FeeEstimator,
)
if err != nil {
return err
}
rpcsLog.Debugf("Target sat/kw for closing transaction: %v",
int64(feeRate))
// If the user hasn't specified NoWait, then before we attempt
// to close the channel we ensure there are no active HTLCs on
// the link.
if !in.NoWait && len(activeHtlcs) != 0 {
return fmt.Errorf("cannot coop close channel with "+
"active htlcs (number of active htlcs: %d), "+
"bypass this check and initiate the coop "+
"close by setting no_wait=true",
len(activeHtlcs))
}
// Otherwise, the caller has requested a regular interactive
// cooperative channel closure. So we'll forward the request to
// the htlc switch which will handle the negotiation and
// broadcast details.
var deliveryScript lnwire.DeliveryAddress
// If a delivery address to close out to was specified, decode it.
if len(in.DeliveryAddress) > 0 {
// Decode the address provided.
addr, err := btcutil.DecodeAddress(
in.DeliveryAddress, r.cfg.ActiveNetParams.Params,
)
if err != nil {
return fmt.Errorf("invalid delivery address: "+
"%v", err)
}
if !addr.IsForNet(r.cfg.ActiveNetParams.Params) {
return fmt.Errorf("delivery address is not "+
"for %s",
r.cfg.ActiveNetParams.Params.Name)
}
// Create a script to pay out to the address provided.
deliveryScript, err = txscript.PayToAddrScript(addr)
if err != nil {
return err
}
}
maxFee := chainfee.SatPerKVByte(
in.MaxFeePerVbyte * 1000,
).FeePerKWeight()
// In case the max fee was specified, we check if it's less than
// the initial fee rate and abort if it is.
if maxFee != 0 && maxFee < feeRate {
return fmt.Errorf("max_fee_per_vbyte (%v) is less "+
"than the required fee rate (%v)", maxFee,
feeRate)
}
if chanHasRbfCloser && !chanInSwitch {
rpcsLog.Infof("Bypassing Switch to do fee bump "+
"for ChannelPoint(%v)", chanPoint)
closeUpdates, err := r.server.AttemptRBFCloseUpdate(
updateStream.Context(), *chanPoint, feeRate,
deliveryScript,
)
if err != nil {
return fmt.Errorf("unable to do RBF close "+
"update: %w", err)
}
updateChan = closeUpdates.UpdateChan
errChan = closeUpdates.ErrChan
} else {
maxFee := chainfee.SatPerKVByte(
in.MaxFeePerVbyte * 1000,
).FeePerKWeight()
updateChan, errChan = r.server.htlcSwitch.CloseLink(
updateStream.Context(), chanPoint,
contractcourt.CloseRegular, feeRate, maxFee,
deliveryScript,
)
}
}
// If the user doesn't want to wait for the txid to come back then we
// will send an empty update to kick off the stream. This is also used
// when active htlcs are still on the channel to give the client
// immediate feedback.
if in.NoWait {
rpcsLog.Trace("[closechannel] sending instant update")
if err := updateStream.Send(
//nolint:ll
&lnrpc.CloseStatusUpdate{
Update: &lnrpc.CloseStatusUpdate_CloseInstant{
CloseInstant: &lnrpc.InstantUpdate{
NumPendingHtlcs: int32(len(activeHtlcs)),
},
},
},
); err != nil {
return err
}
}
out:
for {
select {
case err := <-errChan:
rpcsLog.Errorf("[closechannel] unable to close "+
"ChannelPoint(%v): %v", chanPoint, err)
return err
case closingUpdate := <-updateChan:
rpcClosingUpdate, err := createRPCCloseUpdate(
closingUpdate,
)
if err != nil {
return err
}
err = fn.MapOptionZ(
r.server.implCfg.AuxDataParser,
func(parser AuxDataParser) error {
return parser.InlineParseCustomData(
rpcClosingUpdate,
)
},
)
if err != nil {
return fmt.Errorf("error parsing custom data: "+
"%w", err)
}
rpcsLog.Tracef("[closechannel] sending update: %v",
rpcClosingUpdate)
if err := updateStream.Send(rpcClosingUpdate); err != nil {
return err
}
// If a final channel closing updates is being sent,
// then we can break out of our dispatch loop as we no
// longer need to process any further updates.
switch closeUpdate := closingUpdate.(type) {
case *peer.ChannelCloseUpdate:
h, _ := chainhash.NewHash(closeUpdate.ClosingTxid)
rpcsLog.Infof("[closechannel] close completed: "+
"txid(%v)", h)
break out
}
case <-r.quit:
return nil
}
}
return nil
}
func createRPCCloseUpdate(
update interface{}) (*lnrpc.CloseStatusUpdate, error) {
switch u := update.(type) {
case *peer.ChannelCloseUpdate:
ccu := &lnrpc.ChannelCloseUpdate{
ClosingTxid: u.ClosingTxid,
Success: u.Success,
}
err := fn.MapOptionZ(
u.LocalCloseOutput,
func(closeOut chancloser.CloseOutput) error {
cr, err := closeOut.ShutdownRecords.Serialize()
if err != nil {
return fmt.Errorf("error serializing "+
"local close out custom "+
"records: %w", err)
}
rpcCloseOut := &lnrpc.CloseOutput{
AmountSat: int64(closeOut.Amt),
PkScript: closeOut.PkScript,
IsLocal: true,
CustomChannelData: cr,
}
ccu.LocalCloseOutput = rpcCloseOut
return nil
},
)
if err != nil {
return nil, err
}
err = fn.MapOptionZ(
u.RemoteCloseOutput,
func(closeOut chancloser.CloseOutput) error {
cr, err := closeOut.ShutdownRecords.Serialize()
if err != nil {
return fmt.Errorf("error serializing "+
"remote close out custom "+
"records: %w", err)
}
rpcCloseOut := &lnrpc.CloseOutput{
AmountSat: int64(closeOut.Amt),
PkScript: closeOut.PkScript,
CustomChannelData: cr,
}
ccu.RemoteCloseOutput = rpcCloseOut
return nil
},
)
if err != nil {
return nil, err
}
u.AuxOutputs.WhenSome(func(outs chancloser.AuxCloseOutputs) {
for _, out := range outs.ExtraCloseOutputs {
ccu.AdditionalOutputs = append(
ccu.AdditionalOutputs,
&lnrpc.CloseOutput{
AmountSat: out.Value,
PkScript: out.PkScript,
IsLocal: out.IsLocal,
},
)
}
})
return &lnrpc.CloseStatusUpdate{
Update: &lnrpc.CloseStatusUpdate_ChanClose{
ChanClose: ccu,
},
}, nil
case *peer.PendingUpdate:
upd := &lnrpc.PendingUpdate{
Txid: u.Txid,
OutputIndex: u.OutputIndex,
}
// Potentially set the optional fields that are only set for
// the new RBF close flow.
u.IsLocalCloseTx.WhenSome(func(isLocal bool) {
upd.LocalCloseTx = isLocal
})
u.FeePerVbyte.WhenSome(func(feeRate chainfee.SatPerVByte) {
upd.FeePerVbyte = int64(feeRate)
})
return &lnrpc.CloseStatusUpdate{
Update: &lnrpc.CloseStatusUpdate_ClosePending{
ClosePending: upd,
},
}, nil
}
return nil, errors.New("unknown close status update")
}
// abandonChanFromGraph attempts to remove a channel from the channel graph. If
// we can't find the chanID in the graph, then we assume it has already been
// removed, and will return a nop.
func abandonChanFromGraph(chanGraph *graphdb.ChannelGraph,
chanPoint *wire.OutPoint) error {
// First, we'll obtain the channel ID. If we can't locate this, then
// it's the case that the channel may have already been removed from
// the graph, so we'll return a nil error.
chanID, err := chanGraph.ChannelID(chanPoint)
switch {
case errors.Is(err, graphdb.ErrEdgeNotFound):
return nil
case err != nil:
return err
}
// If the channel ID is still in the graph, then that means the channel
// is still open, so we'll now move to purge it from the graph.
return chanGraph.DeleteChannelEdges(false, true, chanID)
}
// abandonChan removes a channel from the database, graph and contract court.
func (r *rpcServer) abandonChan(chanPoint *wire.OutPoint,
bestHeight uint32) error {
// Before we remove the channel we cancel the rebroadcasting of the
// transaction. If this transaction does not exist in the rebroadcast
// queue anymore it is a noop.
txid, err := chainhash.NewHash(chanPoint.Hash[:])
if err != nil {
return err
}
r.server.cc.Wallet.CancelRebroadcast(*txid)
// Abandoning a channel is a three-step process: remove from the open
// channel state, remove from the graph, remove from the contract
// court. Between any step it's possible that the users restarts the
// process all over again. As a result, each of the steps below are
// intended to be idempotent.
err = r.server.chanStateDB.AbandonChannel(chanPoint, bestHeight)
if err != nil {
return err
}
err = abandonChanFromGraph(r.server.graphDB, chanPoint)
if err != nil {
return err
}
err = r.server.chainArb.ResolveContract(*chanPoint)
if err != nil {
return err
}
// If this channel was in the process of being closed, but didn't fully
// close, then it's possible that the nursery is hanging on to some
// state. To err on the side of caution, we'll now attempt to wipe any
// state for this channel from the nursery.
err = r.server.utxoNursery.RemoveChannel(chanPoint)
if err != nil && err != contractcourt.ErrContractNotFound {
return err
}
// Finally, notify the backup listeners that the channel can be removed
// from any channel backups.
r.server.channelNotifier.NotifyClosedChannelEvent(*chanPoint)
return nil
}
// AbandonChannel removes all channel state from the database except for a
// close summary. This method can be used to get rid of permanently unusable
// channels due to bugs fixed in newer versions of lnd.
func (r *rpcServer) AbandonChannel(_ context.Context,
in *lnrpc.AbandonChannelRequest) (*lnrpc.AbandonChannelResponse, error) {
// If this isn't the dev build, then we won't allow the RPC to be
// executed, as it's an advanced feature and won't be activated in
// regular production/release builds except for the explicit case of
// externally funded channels that are still pending. Due to repeated
// requests, we also allow this requirement to be overwritten by a new
// flag that attests to the user knowing what they're doing and the risk
// associated with the command/RPC.
if !in.IKnowWhatIAmDoing && !in.PendingFundingShimOnly &&
!build.IsDevBuild() {
return nil, fmt.Errorf("AbandonChannel RPC call only " +
"available in dev builds")
}
// We'll parse out the arguments to we can obtain the chanPoint of the
// target channel.
txid, err := lnrpc.GetChanPointFundingTxid(in.GetChannelPoint())
if err != nil {
return nil, err
}
index := in.ChannelPoint.OutputIndex
chanPoint := wire.NewOutPoint(txid, index)
// When we remove the channel from the database, we need to set a close
// height, so we'll just use the current best known height.
_, bestHeight, err := r.server.cc.ChainIO.GetBestBlock()
if err != nil {
return nil, err
}
dbChan, err := r.server.chanStateDB.FetchChannel(*chanPoint)
switch {
// If the channel isn't found in the set of open channels, then we can
// continue on as it can't be loaded into the link/peer.
case err == channeldb.ErrChannelNotFound:
break
// If the channel is still known to be open, then before we modify any
// on-disk state, we'll remove the channel from the switch and peer
// state if it's been loaded in.
case err == nil:
// If the user requested the more safe version that only allows
// the removal of externally (shim) funded channels that are
// still pending, we enforce this option now that we know the
// state of the channel.
//
// TODO(guggero): Properly store the funding type (wallet, shim,
// PSBT) on the channel so we don't need to use the thaw height.
isShimFunded := dbChan.ThawHeight > 0
isPendingShimFunded := isShimFunded && dbChan.IsPending
if !in.IKnowWhatIAmDoing && in.PendingFundingShimOnly &&
!isPendingShimFunded {
return nil, fmt.Errorf("channel %v is not externally "+
"funded or not pending", chanPoint)
}
// We'll mark the channel as borked before we remove the state
// from the switch/peer so it won't be loaded back in if the
// peer reconnects.
if err := dbChan.MarkBorked(); err != nil {
return nil, err
}
remotePub := dbChan.IdentityPub
if peer, err := r.server.FindPeer(remotePub); err == nil {
peer.WipeChannel(chanPoint)
}
default:
return nil, err
}
// Remove the channel from the graph, database and contract court.
if err := r.abandonChan(chanPoint, uint32(bestHeight)); err != nil {
return nil, err
}
return &lnrpc.AbandonChannelResponse{
Status: fmt.Sprintf("channel %v abandoned", chanPoint.String()),
}, nil
}
// GetInfo returns general information concerning the lightning node including
// its identity pubkey, alias, the chains it is connected to, and information
// concerning the number of open+pending channels.
func (r *rpcServer) GetInfo(_ context.Context,
_ *lnrpc.GetInfoRequest) (*lnrpc.GetInfoResponse, error) {
serverPeers := r.server.Peers()
openChannels, err := r.server.chanStateDB.FetchAllOpenChannels()
if err != nil {
return nil, err
}
var activeChannels uint32
for _, channel := range openChannels {
chanID := lnwire.NewChanIDFromOutPoint(channel.FundingOutpoint)
if r.server.htlcSwitch.HasActiveLink(chanID) {
activeChannels++
}
}
inactiveChannels := uint32(len(openChannels)) - activeChannels
pendingChannels, err := r.server.chanStateDB.FetchPendingChannels()
if err != nil {
return nil, fmt.Errorf("unable to get retrieve pending "+
"channels: %v", err)
}
nPendingChannels := uint32(len(pendingChannels))
idPub := r.server.identityECDH.PubKey().SerializeCompressed()
encodedIDPub := hex.EncodeToString(idPub)
// Get the system's chain sync info.
syncInfo, err := r.getChainSyncInfo()
if err != nil {
return nil, err
}
network := lncfg.NormalizeNetwork(r.cfg.ActiveNetParams.Name)
activeChains := []*lnrpc.Chain{
{
Chain: BitcoinChainName,
Network: network,
},
}
// Check if external IP addresses were provided to lnd and use them
// to set the URIs.
nodeAnn := r.server.getNodeAnnouncement()
addrs := nodeAnn.Addresses
uris := make([]string, len(addrs))
for i, addr := range addrs {
uris[i] = fmt.Sprintf("%s@%s", encodedIDPub, addr.String())
}
isGraphSynced := r.server.authGossiper.SyncManager().IsGraphSynced()
features := make(map[uint32]*lnrpc.Feature)
sets := r.server.featureMgr.ListSets()
for _, set := range sets {
// Get the a list of lnrpc features for each set we support.
featureVector := r.server.featureMgr.Get(set)
rpcFeatures := invoicesrpc.CreateRPCFeatures(featureVector)
// Add the features to our map of features, allowing over writing of
// existing values because features in different sets with the same bit
// are duplicated across sets.
maps.Copy(features, rpcFeatures)
}
// TODO(roasbeef): add synced height n stuff
isTestNet := chainreg.IsTestnet(&r.cfg.ActiveNetParams)
nodeColor := graphdb.EncodeHexColor(nodeAnn.RGBColor)
version := build.Version() + " commit=" + build.Commit
return &lnrpc.GetInfoResponse{
IdentityPubkey: encodedIDPub,
NumPendingChannels: nPendingChannels,
NumActiveChannels: activeChannels,
NumInactiveChannels: inactiveChannels,
NumPeers: uint32(len(serverPeers)),
BlockHeight: uint32(syncInfo.bestHeight),
BlockHash: syncInfo.blockHash.String(),
SyncedToChain: syncInfo.isSynced,
Testnet: isTestNet,
Chains: activeChains,
Uris: uris,
Alias: nodeAnn.Alias.String(),
Color: nodeColor,
BestHeaderTimestamp: syncInfo.timestamp,
Version: version,
CommitHash: build.CommitHash,
SyncedToGraph: isGraphSynced,
Features: features,
RequireHtlcInterceptor: r.cfg.RequireInterceptor,
StoreFinalHtlcResolutions: r.cfg.StoreFinalHtlcResolutions,
}, nil
}
// GetDebugInfo returns debug information concerning the state of the daemon
// and its subsystems. This includes the full configuration and the latest log
// entries from the log file.
func (r *rpcServer) GetDebugInfo(_ context.Context,
_ *lnrpc.GetDebugInfoRequest) (*lnrpc.GetDebugInfoResponse, error) {
flatConfig, _, err := configToFlatMap(*r.cfg)
if err != nil {
return nil, fmt.Errorf("error converting config to flat map: "+
"%w", err)
}
logFileName := filepath.Join(r.cfg.LogDir, defaultLogFilename)
logContent, err := os.ReadFile(logFileName)
if err != nil {
return nil, fmt.Errorf("error reading log file '%s': %w",
logFileName, err)
}
return &lnrpc.GetDebugInfoResponse{
Config: flatConfig,
Log: strings.Split(string(logContent), "\n"),
}, nil
}
// GetRecoveryInfo returns a boolean indicating whether the wallet is started
// in recovery mode, whether the recovery is finished, and the progress made
// so far.
func (r *rpcServer) GetRecoveryInfo(ctx context.Context,
in *lnrpc.GetRecoveryInfoRequest) (*lnrpc.GetRecoveryInfoResponse, error) {
isRecoveryMode, progress, err := r.server.cc.Wallet.GetRecoveryInfo()
if err != nil {
return nil, fmt.Errorf("unable to get wallet recovery info: %w",
err)
}
rpcsLog.Debugf("[getrecoveryinfo] is recovery mode=%v, progress=%v",
isRecoveryMode, progress)
return &lnrpc.GetRecoveryInfoResponse{
RecoveryMode: isRecoveryMode,
RecoveryFinished: progress == 1,
Progress: progress,
}, nil
}
// ListPeers returns a verbose listing of all currently active peers.
func (r *rpcServer) ListPeers(ctx context.Context,
in *lnrpc.ListPeersRequest) (*lnrpc.ListPeersResponse, error) {
serverPeers := r.server.Peers()
resp := &lnrpc.ListPeersResponse{
Peers: make([]*lnrpc.Peer, 0, len(serverPeers)),
}
for _, serverPeer := range serverPeers {
var (
satSent int64
satRecv int64
)
// In order to display the total number of satoshis of outbound
// (sent) and inbound (recv'd) satoshis that have been
// transported through this peer, we'll sum up the sent/recv'd
// values for each of the active channels we have with the
// peer.
chans := serverPeer.ChannelSnapshots()
for _, c := range chans {
satSent += int64(c.TotalMSatSent.ToSatoshis())
satRecv += int64(c.TotalMSatReceived.ToSatoshis())
}
nodePub := serverPeer.PubKey()
// Retrieve the peer's sync type. If we don't currently have a
// syncer for the peer, then we'll default to a passive sync.
// This can happen if the RPC is called while a peer is
// initializing.
syncer, ok := r.server.authGossiper.SyncManager().GossipSyncer(
nodePub,
)
var lnrpcSyncType lnrpc.Peer_SyncType
if !ok {
rpcsLog.Warnf("Gossip syncer for peer=%x not found",
nodePub)
lnrpcSyncType = lnrpc.Peer_UNKNOWN_SYNC
} else {
syncType := syncer.SyncType()
switch syncType {
case discovery.ActiveSync:
lnrpcSyncType = lnrpc.Peer_ACTIVE_SYNC
case discovery.PassiveSync:
lnrpcSyncType = lnrpc.Peer_PASSIVE_SYNC
case discovery.PinnedSync:
lnrpcSyncType = lnrpc.Peer_PINNED_SYNC
default:
return nil, fmt.Errorf("unhandled sync type %v",
syncType)
}
}
features := invoicesrpc.CreateRPCFeatures(
serverPeer.RemoteFeatures(),
)
rpcPeer := &lnrpc.Peer{
PubKey: hex.EncodeToString(nodePub[:]),
Address: serverPeer.Conn().RemoteAddr().String(),
Inbound: serverPeer.Inbound(),
BytesRecv: serverPeer.BytesReceived(),
BytesSent: serverPeer.BytesSent(),
SatSent: satSent,
SatRecv: satRecv,
PingTime: serverPeer.PingTime(),
SyncType: lnrpcSyncType,
Features: features,
LastPingPayload: serverPeer.LastRemotePingPayload(),
}
var peerErrors []interface{}
// If we only want the most recent error, get the most recent
// error from the buffer and add it to our list of errors if
// it is non-nil. If we want all the stored errors, simply
// add the full list to our set of errors.
if in.LatestError {
latestErr := serverPeer.ErrorBuffer().Latest()
if latestErr != nil {
peerErrors = []interface{}{latestErr}
}
} else {
peerErrors = serverPeer.ErrorBuffer().List()
}
// Add the relevant peer errors to our response.
for _, error := range peerErrors {
tsError := error.(*peer.TimestampedError)
rpcErr := &lnrpc.TimestampedError{
Timestamp: uint64(tsError.Timestamp.Unix()),
Error: tsError.Error.Error(),
}
rpcPeer.Errors = append(rpcPeer.Errors, rpcErr)
}
// If the server has started, we can query the event store
// for our peer's flap count. If we do so when the server has
// not started, the request will block.
if r.server.Started() {
vertex, err := route.NewVertexFromBytes(nodePub[:])
if err != nil {
return nil, err
}
flap, ts, err := r.server.chanEventStore.FlapCount(
vertex,
)
if err != nil {
return nil, err
}
// If our timestamp is non-nil, we have values for our
// peer's flap count, so we set them.
if ts != nil {
rpcPeer.FlapCount = int32(flap)
rpcPeer.LastFlapNs = ts.UnixNano()
}
}
resp.Peers = append(resp.Peers, rpcPeer)
}
rpcsLog.Debugf("[listpeers] yielded %v peers", serverPeers)
return resp, nil
}
// SubscribePeerEvents returns a uni-directional stream (server -> client)
// for notifying the client of peer online and offline events.
func (r *rpcServer) SubscribePeerEvents(req *lnrpc.PeerEventSubscription,
eventStream lnrpc.Lightning_SubscribePeerEventsServer) error {
peerEventSub, err := r.server.peerNotifier.SubscribePeerEvents()
if err != nil {
return err
}
defer peerEventSub.Cancel()
for {
select {
// A new update has been sent by the peer notifier, we'll
// marshal it into the form expected by the gRPC client, then
// send it off to the client.
case e := <-peerEventSub.Updates():
var event *lnrpc.PeerEvent
switch peerEvent := e.(type) {
case peernotifier.PeerOfflineEvent:
event = &lnrpc.PeerEvent{
PubKey: hex.EncodeToString(peerEvent.PubKey[:]),
Type: lnrpc.PeerEvent_PEER_OFFLINE,
}
case peernotifier.PeerOnlineEvent:
event = &lnrpc.PeerEvent{
PubKey: hex.EncodeToString(peerEvent.PubKey[:]),
Type: lnrpc.PeerEvent_PEER_ONLINE,
}
default:
return fmt.Errorf("unexpected peer event: %v", event)
}
if err := eventStream.Send(event); err != nil {
return err
}
// The response stream's context for whatever reason has been
// closed. If context is closed by an exceeded deadline we will
// return an error.
case <-eventStream.Context().Done():
if errors.Is(eventStream.Context().Err(), context.Canceled) {
return nil
}
return eventStream.Context().Err()
case <-r.quit:
return nil
}
}
}
// WalletBalance returns total unspent outputs(confirmed and unconfirmed), all
// confirmed unspent outputs and all unconfirmed unspent outputs under control
// by the wallet. This method can be modified by having the request specify
// only witness outputs should be factored into the final output sum.
// TODO(roasbeef): add async hooks into wallet balance changes.
func (r *rpcServer) WalletBalance(ctx context.Context,
in *lnrpc.WalletBalanceRequest) (*lnrpc.WalletBalanceResponse, error) {
// Retrieve all existing wallet accounts. We'll compute the confirmed
// and unconfirmed balance for each and tally them up.
accounts, err := r.server.cc.Wallet.ListAccounts(in.Account, nil)
if err != nil {
return nil, err
}
var totalBalance, confirmedBalance, unconfirmedBalance btcutil.Amount
rpcAccountBalances := make(
map[string]*lnrpc.WalletAccountBalance, len(accounts),
)
for _, account := range accounts {
// There are two default accounts, one for NP2WKH outputs and
// another for P2WKH outputs. The balance will be computed for
// both given one call to ConfirmedBalance with the default
// wallet and imported account, so we'll skip the second
// instance to avoid inflating the balance.
switch account.AccountName {
case waddrmgr.ImportedAddrAccountName:
// Omit the imported account from the response unless we
// actually have any keys imported.
if account.ImportedKeyCount == 0 {
continue
}
fallthrough
case lnwallet.DefaultAccountName:
if _, ok := rpcAccountBalances[account.AccountName]; ok {
continue
}
default:
}
// There now also are the accounts for the internal channel
// related keys. We skip those as they'll never have any direct
// balance.
if account.KeyScope.Purpose == keychain.BIP0043Purpose {
continue
}
// Get total balance, from txs that have >= 0 confirmations.
totalBal, err := r.server.cc.Wallet.ConfirmedBalance(
0, account.AccountName,
)
if err != nil {
return nil, err
}
totalBalance += totalBal
// Get confirmed balance, from txs that have >= 1 confirmations.
// TODO(halseth): get both unconfirmed and confirmed balance in
// one call, as this is racy.
if in.MinConfs <= 0 {
in.MinConfs = 1
}
confirmedBal, err := r.server.cc.Wallet.ConfirmedBalance(
in.MinConfs, account.AccountName,
)
if err != nil {
return nil, err
}
confirmedBalance += confirmedBal
// Get unconfirmed balance, from txs with 0 confirmations.
unconfirmedBal := totalBal - confirmedBal
unconfirmedBalance += unconfirmedBal
rpcAccountBalances[account.AccountName] = &lnrpc.WalletAccountBalance{
ConfirmedBalance: int64(confirmedBal),
UnconfirmedBalance: int64(unconfirmedBal),
}
}
// Now that we have the base balance accounted for with each account,
// we'll look at the set of locked UTXOs to tally that as well. If we
// don't display this, then anytime we attempt a funding reservation,
// the outputs will chose as being "gone" until they're confirmed on
// chain.
var lockedBalance btcutil.Amount
leases, err := r.server.cc.Wallet.ListLeasedOutputs()
if err != nil {
return nil, err
}
for _, leasedOutput := range leases {
lockedBalance += btcutil.Amount(leasedOutput.Value)
}
// Get the current number of non-private anchor channels.
currentNumAnchorChans, err := r.server.cc.Wallet.CurrentNumAnchorChans()
if err != nil {
return nil, err
}
// Get the required reserve for the wallet.
requiredReserve := r.server.cc.Wallet.RequiredReserve(
uint32(currentNumAnchorChans),
)
rpcsLog.Debugf("[walletbalance] Total balance=%v (confirmed=%v, "+
"unconfirmed=%v, locked=%v)", totalBalance, confirmedBalance,
unconfirmedBalance, lockedBalance)
return &lnrpc.WalletBalanceResponse{
TotalBalance: int64(totalBalance),
ConfirmedBalance: int64(confirmedBalance),
UnconfirmedBalance: int64(unconfirmedBalance),
LockedBalance: int64(lockedBalance),
ReservedBalanceAnchorChan: int64(requiredReserve),
AccountBalance: rpcAccountBalances,
}, nil
}
// ChannelBalance returns the total available channel flow across all open
// channels in satoshis.
func (r *rpcServer) ChannelBalance(ctx context.Context,
in *lnrpc.ChannelBalanceRequest) (
*lnrpc.ChannelBalanceResponse, error) {
var (
localBalance lnwire.MilliSatoshi
remoteBalance lnwire.MilliSatoshi
unsettledLocalBalance lnwire.MilliSatoshi
unsettledRemoteBalance lnwire.MilliSatoshi
pendingOpenLocalBalance lnwire.MilliSatoshi
pendingOpenRemoteBalance lnwire.MilliSatoshi
customDataBuf bytes.Buffer
)
openChannels, err := r.server.chanStateDB.FetchAllOpenChannels()
if err != nil {
return nil, err
}
// Encode the number of open channels to the custom data buffer.
err = wire.WriteVarInt(&customDataBuf, 0, uint64(len(openChannels)))
if err != nil {
return nil, err
}
for _, channel := range openChannels {
c := channel.LocalCommitment
localBalance += c.LocalBalance
remoteBalance += c.RemoteBalance
// Add pending htlc amount.
for _, htlc := range c.Htlcs {
if htlc.Incoming {
unsettledLocalBalance += htlc.Amt
} else {
unsettledRemoteBalance += htlc.Amt
}
}
// Encode the custom data for this open channel.
openChanData := channel.LocalCommitment.CustomBlob.UnwrapOr(nil)
err = wire.WriteVarBytes(&customDataBuf, 0, openChanData)
if err != nil {
return nil, err
}
}
pendingChannels, err := r.server.chanStateDB.FetchPendingChannels()
if err != nil {
return nil, err
}
// Encode the number of pending channels to the custom data buffer.
err = wire.WriteVarInt(&customDataBuf, 0, uint64(len(pendingChannels)))
if err != nil {
return nil, err
}
for _, channel := range pendingChannels {
c := channel.LocalCommitment
pendingOpenLocalBalance += c.LocalBalance
pendingOpenRemoteBalance += c.RemoteBalance
// Encode the custom data for this pending channel.
openChanData := channel.LocalCommitment.CustomBlob.UnwrapOr(nil)
err = wire.WriteVarBytes(&customDataBuf, 0, openChanData)
if err != nil {
return nil, err
}
}
rpcsLog.Debugf("[channelbalance] local_balance=%v remote_balance=%v "+
"unsettled_local_balance=%v unsettled_remote_balance=%v "+
"pending_open_local_balance=%v pending_open_remote_balance=%v",
localBalance, remoteBalance, unsettledLocalBalance,
unsettledRemoteBalance, pendingOpenLocalBalance,
pendingOpenRemoteBalance)
resp := &lnrpc.ChannelBalanceResponse{
LocalBalance: &lnrpc.Amount{
Sat: uint64(localBalance.ToSatoshis()),
Msat: uint64(localBalance),
},
RemoteBalance: &lnrpc.Amount{
Sat: uint64(remoteBalance.ToSatoshis()),
Msat: uint64(remoteBalance),
},
UnsettledLocalBalance: &lnrpc.Amount{
Sat: uint64(unsettledLocalBalance.ToSatoshis()),
Msat: uint64(unsettledLocalBalance),
},
UnsettledRemoteBalance: &lnrpc.Amount{
Sat: uint64(unsettledRemoteBalance.ToSatoshis()),
Msat: uint64(unsettledRemoteBalance),
},
PendingOpenLocalBalance: &lnrpc.Amount{
Sat: uint64(pendingOpenLocalBalance.ToSatoshis()),
Msat: uint64(pendingOpenLocalBalance),
},
PendingOpenRemoteBalance: &lnrpc.Amount{
Sat: uint64(pendingOpenRemoteBalance.ToSatoshis()),
Msat: uint64(pendingOpenRemoteBalance),
},
CustomChannelData: customDataBuf.Bytes(),
// Deprecated fields.
Balance: int64(localBalance.ToSatoshis()),
PendingOpenBalance: int64(pendingOpenLocalBalance.ToSatoshis()),
}
err = fn.MapOptionZ(
r.server.implCfg.AuxDataParser,
func(parser AuxDataParser) error {
return parser.InlineParseCustomData(resp)
},
)
if err != nil {
return nil, fmt.Errorf("error parsing custom data: %w", err)
}
return resp, nil
}
type (
pendingOpenChannels []*lnrpc.PendingChannelsResponse_PendingOpenChannel
pendingForceClose []*lnrpc.PendingChannelsResponse_ForceClosedChannel
waitingCloseChannels []*lnrpc.PendingChannelsResponse_WaitingCloseChannel
)
// fetchPendingOpenChannels queries the database for a list of channels that
// have pending open state. The returned result is used in the response of the
// PendingChannels RPC.
func (r *rpcServer) fetchPendingOpenChannels() (pendingOpenChannels, error) {
// First, we'll populate the response with all the channels that are
// soon to be opened. We can easily fetch this data from the database
// and map the db struct to the proto response.
channels, err := r.server.chanStateDB.FetchPendingChannels()
if err != nil {
rpcsLog.Errorf("unable to fetch pending channels: %v", err)
return nil, err
}
_, currentHeight, err := r.server.cc.ChainIO.GetBestBlock()
if err != nil {
return nil, err
}
result := make(pendingOpenChannels, len(channels))
for i, pendingChan := range channels {
pub := pendingChan.IdentityPub.SerializeCompressed()
// As this is required for display purposes, we'll calculate
// the weight of the commitment transaction. We also add on the
// estimated weight of the witness to calculate the weight of
// the transaction if it were to be immediately unilaterally
// broadcast.
// TODO(roasbeef): query for funding tx from wallet, display
// that also?
var witnessWeight int64
if pendingChan.ChanType.IsTaproot() {
witnessWeight = input.TaprootKeyPathWitnessSize
} else {
witnessWeight = input.WitnessCommitmentTxWeight
}
localCommitment := pendingChan.LocalCommitment
utx := btcutil.NewTx(localCommitment.CommitTx)
commitBaseWeight := blockchain.GetTransactionWeight(utx)
commitWeight := commitBaseWeight + witnessWeight
// The value of waitBlocksForFundingConf is adjusted in a
// development environment to enhance test capabilities.
// Otherwise, it is set to DefaultMaxWaitNumBlocksFundingConf.
waitBlocksForFundingConf := uint32(
lncfg.DefaultMaxWaitNumBlocksFundingConf,
)
if lncfg.IsDevBuild() {
waitBlocksForFundingConf =
r.cfg.Dev.GetMaxWaitNumBlocksFundingConf()
}
// FundingExpiryBlocks is the distance from the current block
// height to the broadcast height + waitBlocksForFundingConf.
maxFundingHeight := waitBlocksForFundingConf +
pendingChan.BroadcastHeight()
fundingExpiryBlocks := int32(maxFundingHeight) - currentHeight
customChanBytes, err := encodeCustomChanData(pendingChan)
if err != nil {
return nil, fmt.Errorf("unable to encode open chan "+
"data: %w", err)
}
result[i] = &lnrpc.PendingChannelsResponse_PendingOpenChannel{
Channel: &lnrpc.PendingChannelsResponse_PendingChannel{
RemoteNodePub: hex.EncodeToString(pub),
ChannelPoint: pendingChan.FundingOutpoint.String(),
Capacity: int64(pendingChan.Capacity),
LocalBalance: int64(localCommitment.LocalBalance.ToSatoshis()),
RemoteBalance: int64(localCommitment.RemoteBalance.ToSatoshis()),
LocalChanReserveSat: int64(pendingChan.LocalChanCfg.ChanReserve),
RemoteChanReserveSat: int64(pendingChan.RemoteChanCfg.ChanReserve),
Initiator: rpcInitiator(pendingChan.IsInitiator),
CommitmentType: rpcCommitmentType(pendingChan.ChanType),
Private: isPrivate(pendingChan),
Memo: string(pendingChan.Memo),
CustomChannelData: customChanBytes,
},
CommitWeight: commitWeight,
CommitFee: int64(localCommitment.CommitFee),
FeePerKw: int64(localCommitment.FeePerKw),
FundingExpiryBlocks: fundingExpiryBlocks,
// TODO(roasbeef): need to track confirmation height
}
}
return result, nil
}
// fetchPendingForceCloseChannels queries the database for a list of channels
// that have their closing transactions confirmed but not fully resolved yet.
// The returned result is used in the response of the PendingChannels RPC.
func (r *rpcServer) fetchPendingForceCloseChannels() (pendingForceClose,
int64, error) {
_, currentHeight, err := r.server.cc.ChainIO.GetBestBlock()
if err != nil {
return nil, 0, err
}
// Next, we'll examine the channels that are soon to be closed so we
// can populate these fields within the response.
channels, err := r.server.chanStateDB.FetchClosedChannels(true)
if err != nil {
rpcsLog.Errorf("unable to fetch closed channels: %v", err)
return nil, 0, err
}
result := make(pendingForceClose, 0)
limboBalance := int64(0)
for _, pendingClose := range channels {
// First construct the channel struct itself, this will be
// needed regardless of how this channel was closed.
pub := pendingClose.RemotePub.SerializeCompressed()
chanPoint := pendingClose.ChanPoint
// Create the pending channel. If this channel was closed before
// we started storing historical channel data, we will not know
// who initiated the channel, so we set the initiator field to
// unknown.
channel := &lnrpc.PendingChannelsResponse_PendingChannel{
RemoteNodePub: hex.EncodeToString(pub),
ChannelPoint: chanPoint.String(),
Capacity: int64(pendingClose.Capacity),
LocalBalance: int64(pendingClose.SettledBalance),
CommitmentType: lnrpc.CommitmentType_UNKNOWN_COMMITMENT_TYPE,
Initiator: lnrpc.Initiator_INITIATOR_UNKNOWN,
}
// Lookup the channel in the historical channel bucket to obtain
// initiator information. If the historical channel bucket was
// not found, or the channel itself, this channel was closed
// in a version before we started persisting historical
// channels, so we silence the error.
historical, err := r.server.chanStateDB.FetchHistoricalChannel(
&pendingClose.ChanPoint,
)
switch err {
// If the channel was closed in a version that did not record
// historical channels, ignore the error.
case channeldb.ErrNoHistoricalBucket:
case channeldb.ErrChannelNotFound:
case nil:
channel.Initiator = rpcInitiator(historical.IsInitiator)
channel.CommitmentType = rpcCommitmentType(
historical.ChanType,
)
// Get the number of forwarding packages from the
// historical channel.
fwdPkgs, err := historical.LoadFwdPkgs()
if err != nil {
rpcsLog.Errorf("unable to load forwarding "+
"packages for channel:%s, %v",
historical.ShortChannelID, err)
return nil, 0, err
}
channel.NumForwardingPackages = int64(len(fwdPkgs))
channel.RemoteBalance = int64(
historical.LocalCommitment.RemoteBalance.ToSatoshis(),
)
customChanBytes, err := encodeCustomChanData(historical)
if err != nil {
return nil, 0, fmt.Errorf("unable to encode "+
"open chan data: %w", err)
}
channel.CustomChannelData = customChanBytes
channel.Private = isPrivate(historical)
channel.Memo = string(historical.Memo)
// If the error is non-nil, and not due to older versions of lnd
// not persisting historical channels, return it.
default:
return nil, 0, err
}
closeTXID := pendingClose.ClosingTXID.String()
switch pendingClose.CloseType {
// A coop closed channel should never be in the "pending close"
// state. If a node upgraded from an older lnd version in the
// middle of a their channel confirming, it will be in this
// state. We log a warning that the channel will not be included
// in the now deprecated pending close channels field.
case channeldb.CooperativeClose:
rpcsLog.Warnf("channel %v cooperatively closed and "+
"in pending close state",
pendingClose.ChanPoint)
// If the channel was force closed, then we'll need to query
// the utxoNursery for additional information.
// TODO(halseth): distinguish remote and local case?
case channeldb.LocalForceClose, channeldb.RemoteForceClose:
forceClose := &lnrpc.PendingChannelsResponse_ForceClosedChannel{
Channel: channel,
ClosingTxid: closeTXID,
}
// Fetch reports from both nursery and resolvers. At the
// moment this is not an atomic snapshot. This is
// planned to be resolved when the nursery is removed
// and channel arbitrator will be the single source for
// these kind of reports.
err := r.nurseryPopulateForceCloseResp(
&chanPoint, currentHeight, forceClose,
)
if err != nil {
rpcsLog.Errorf("unable to populate nursery "+
"force close resp:%s, %v",
chanPoint, err)
return nil, 0, err
}
err = r.arbitratorPopulateForceCloseResp(
&chanPoint, currentHeight, forceClose,
)
if err != nil {
rpcsLog.Errorf("unable to populate arbitrator "+
"force close resp:%s, %v",
chanPoint, err)
return nil, 0, err
}
limboBalance += forceClose.LimboBalance
result = append(result, forceClose)
}
}
return result, limboBalance, nil
}
// fetchWaitingCloseChannels queries the database for a list of channels
// that have their closing transactions broadcast but not confirmed yet.
// The returned result is used in the response of the PendingChannels RPC.
func (r *rpcServer) fetchWaitingCloseChannels(
includeRawTx bool) (waitingCloseChannels, int64, error) {
// We'll also fetch all channels that are open, but have had their
// commitment broadcasted, meaning they are waiting for the closing
// transaction to confirm.
channels, err := r.server.chanStateDB.FetchWaitingCloseChannels()
if err != nil {
rpcsLog.Errorf("unable to fetch channels waiting close: %v",
err)
return nil, 0, err
}
result := make(waitingCloseChannels, 0)
limboBalance := int64(0)
// getClosingTx is a helper closure that tries to find the closing tx of
// a given waiting close channel. Notice that if the remote closes the
// channel, we may not have the closing tx.
getClosingTx := func(c *channeldb.OpenChannel) (*wire.MsgTx, error) {
var (
tx *wire.MsgTx
err error
)
// First, we try to locate the force closing tx. If not found,
// we will then try to find its coop closing tx.
tx, err = c.BroadcastedCommitment()
if err == nil {
return tx, nil
}
// If the error returned is not ErrNoCloseTx, something
// unexpected happened and we will return the error.
if err != channeldb.ErrNoCloseTx {
return nil, err
}
// Otherwise, we continue to locate its coop closing tx.
tx, err = c.BroadcastedCooperative()
if err == nil {
return tx, nil
}
// Return the error if it's not ErrNoCloseTx.
if err != channeldb.ErrNoCloseTx {
return nil, err
}
// Otherwise return an empty tx. This can happen if the remote
// broadcast the closing tx and we haven't recorded it yet.
return nil, nil
}
for _, waitingClose := range channels {
pub := waitingClose.IdentityPub.SerializeCompressed()
chanPoint := waitingClose.FundingOutpoint
var commitments lnrpc.PendingChannelsResponse_Commitments
// Report local commit. May not be present when DLP is active.
if waitingClose.LocalCommitment.CommitTx != nil {
commitments.LocalTxid =
waitingClose.LocalCommitment.CommitTx.TxHash().
String()
commitments.LocalCommitFeeSat = uint64(
waitingClose.LocalCommitment.CommitFee,
)
}
// Report remote commit. May not be present when DLP is active.
if waitingClose.RemoteCommitment.CommitTx != nil {
commitments.RemoteTxid =
waitingClose.RemoteCommitment.CommitTx.TxHash().
String()
commitments.RemoteCommitFeeSat = uint64(
waitingClose.RemoteCommitment.CommitFee,
)
}
// Report the remote pending commit if any.
remoteCommitDiff, err := waitingClose.RemoteCommitChainTip()
switch {
// Don't set hash if there is no pending remote commit.
case err == channeldb.ErrNoPendingCommit:
// An unexpected error occurred.
case err != nil:
return nil, 0, err
// There is a pending remote commit. Set its hash in the
// response.
default:
hash := remoteCommitDiff.Commitment.CommitTx.TxHash()
commitments.RemotePendingTxid = hash.String()
commitments.RemoteCommitFeeSat = uint64(
remoteCommitDiff.Commitment.CommitFee,
)
}
fwdPkgs, err := waitingClose.LoadFwdPkgs()
if err != nil {
rpcsLog.Errorf("unable to load forwarding packages "+
"for channel:%s, %v",
waitingClose.ShortChannelID, err)
return nil, 0, err
}
// Get the closing tx.
// NOTE: the closing tx could be nil here if it's the remote
// that broadcasted the closing tx.
closingTx, err := getClosingTx(waitingClose)
if err != nil {
rpcsLog.Errorf("unable to find closing tx for "+
"channel:%s, %v",
waitingClose.ShortChannelID, err)
return nil, 0, err
}
customChanBytes, err := encodeCustomChanData(waitingClose)
if err != nil {
return nil, 0, fmt.Errorf("unable to encode "+
"open chan data: %w", err)
}
localCommit := waitingClose.LocalCommitment
chanStatus := waitingClose.ChanStatus()
channel := &lnrpc.PendingChannelsResponse_PendingChannel{
RemoteNodePub: hex.EncodeToString(pub),
ChannelPoint: chanPoint.String(),
Capacity: int64(waitingClose.Capacity),
LocalBalance: int64(
localCommit.LocalBalance.ToSatoshis(),
),
RemoteBalance: int64(
localCommit.RemoteBalance.ToSatoshis(),
),
LocalChanReserveSat: int64(
waitingClose.LocalChanCfg.ChanReserve,
),
RemoteChanReserveSat: int64(
waitingClose.RemoteChanCfg.ChanReserve,
),
Initiator: rpcInitiator(
waitingClose.IsInitiator,
),
CommitmentType: rpcCommitmentType(
waitingClose.ChanType,
),
NumForwardingPackages: int64(len(fwdPkgs)),
ChanStatusFlags: chanStatus.String(),
Private: isPrivate(waitingClose),
Memo: string(waitingClose.Memo),
CustomChannelData: customChanBytes,
}
var closingTxid, closingTxHex string
if closingTx != nil {
closingTxid = closingTx.TxHash().String()
if includeRawTx {
var txBuf bytes.Buffer
err = closingTx.Serialize(&txBuf)
if err != nil {
return nil, 0, fmt.Errorf("failed to "+
"serialize closing transaction"+
": %w", err)
}
closingTxHex = hex.EncodeToString(txBuf.Bytes())
}
}
waitingCloseResp := &lnrpc.PendingChannelsResponse_WaitingCloseChannel{
Channel: channel,
LimboBalance: channel.LocalBalance,
Commitments: &commitments,
ClosingTxid: closingTxid,
ClosingTxHex: closingTxHex,
}
// A close tx has been broadcasted, all our balance will be in
// limbo until it confirms.
result = append(result, waitingCloseResp)
limboBalance += channel.LocalBalance
}
return result, limboBalance, nil
}
// PendingChannels returns a list of all the channels that are currently
// considered "pending". A channel is pending if it has finished the funding
// workflow and is waiting for confirmations for the funding txn, or is in the
// process of closure, either initiated cooperatively or non-cooperatively.
func (r *rpcServer) PendingChannels(ctx context.Context,
in *lnrpc.PendingChannelsRequest) (
*lnrpc.PendingChannelsResponse, error) {
resp := &lnrpc.PendingChannelsResponse{}
// First, we find all the channels that will soon be opened.
pendingOpenChannels, err := r.fetchPendingOpenChannels()
if err != nil {
return nil, err
}
resp.PendingOpenChannels = pendingOpenChannels
// Second, we fetch all channels that considered pending force closing.
// This means the channels here have their closing transactions
// confirmed but not considered fully resolved yet. For instance, they
// may have a second level HTLCs to be resolved onchain.
pendingCloseChannels, limbo, err := r.fetchPendingForceCloseChannels()
if err != nil {
return nil, err
}
resp.PendingForceClosingChannels = pendingCloseChannels
resp.TotalLimboBalance = limbo
// Third, we fetch all channels that are open, but have had their
// commitment broadcasted, meaning they are waiting for the closing
// transaction to confirm.
waitingCloseChannels, limbo, err := r.fetchWaitingCloseChannels(
in.IncludeRawTx,
)
if err != nil {
return nil, err
}
resp.WaitingCloseChannels = waitingCloseChannels
resp.TotalLimboBalance += limbo
err = fn.MapOptionZ(
r.server.implCfg.AuxDataParser,
func(parser AuxDataParser) error {
return parser.InlineParseCustomData(resp)
},
)
if err != nil {
return nil, fmt.Errorf("error parsing custom data: %w", err)
}
return resp, nil
}
// arbitratorPopulateForceCloseResp populates the pending channels response
// message with channel resolution information from the contract resolvers.
func (r *rpcServer) arbitratorPopulateForceCloseResp(chanPoint *wire.OutPoint,
currentHeight int32,
forceClose *lnrpc.PendingChannelsResponse_ForceClosedChannel) error {
// Query for contract resolvers state.
arbitrator, err := r.server.chainArb.GetChannelArbitrator(*chanPoint)
if err != nil {
return err
}
reports := arbitrator.Report()
for _, report := range reports {
switch report.Type {
// For a direct output, populate/update the top level
// response properties.
case contractcourt.ReportOutputUnencumbered:
// Populate the maturity height fields for the direct
// commitment output to us.
forceClose.MaturityHeight = report.MaturityHeight
// If the transaction has been confirmed, then we can
// compute how many blocks it has left.
if forceClose.MaturityHeight != 0 {
forceClose.BlocksTilMaturity =
int32(forceClose.MaturityHeight) -
currentHeight
}
// Add htlcs to the PendingHtlcs response property.
case contractcourt.ReportOutputIncomingHtlc,
contractcourt.ReportOutputOutgoingHtlc:
// Don't report details on htlcs that are no longer in
// limbo.
if report.LimboBalance == 0 {
break
}
incoming := report.Type == contractcourt.ReportOutputIncomingHtlc
htlc := &lnrpc.PendingHTLC{
Incoming: incoming,
Amount: int64(report.Amount),
Outpoint: report.Outpoint.String(),
MaturityHeight: report.MaturityHeight,
Stage: report.Stage,
}
if htlc.MaturityHeight != 0 {
htlc.BlocksTilMaturity =
int32(htlc.MaturityHeight) - currentHeight
}
forceClose.PendingHtlcs = append(forceClose.PendingHtlcs, htlc)
case contractcourt.ReportOutputAnchor:
// There are three resolution states for the anchor:
// limbo, lost and recovered. Derive the current state
// from the limbo and recovered balances.
switch {
case report.RecoveredBalance != 0:
forceClose.Anchor = lnrpc.PendingChannelsResponse_ForceClosedChannel_RECOVERED
case report.LimboBalance != 0:
forceClose.Anchor = lnrpc.PendingChannelsResponse_ForceClosedChannel_LIMBO
default:
forceClose.Anchor = lnrpc.PendingChannelsResponse_ForceClosedChannel_LOST
}
default:
return fmt.Errorf("unknown report output type: %v",
report.Type)
}
forceClose.LimboBalance += int64(report.LimboBalance)
forceClose.RecoveredBalance += int64(report.RecoveredBalance)
}
return nil
}
// nurseryPopulateForceCloseResp populates the pending channels response
// message with contract resolution information from utxonursery.
func (r *rpcServer) nurseryPopulateForceCloseResp(chanPoint *wire.OutPoint,
currentHeight int32,
forceClose *lnrpc.PendingChannelsResponse_ForceClosedChannel) error {
// Query for the maturity state for this force closed channel. If we
// didn't have any time-locked outputs, then the nursery may not know of
// the contract.
nurseryInfo, err := r.server.utxoNursery.NurseryReport(chanPoint)
if err == contractcourt.ErrContractNotFound {
return nil
}
if err != nil {
return fmt.Errorf("unable to obtain "+
"nursery report for ChannelPoint(%v): %v",
chanPoint, err)
}
// If the nursery knows of this channel, then we can populate
// information detailing exactly how much funds are time locked and also
// the height in which we can ultimately sweep the funds into the
// wallet.
forceClose.LimboBalance = int64(nurseryInfo.LimboBalance)
forceClose.RecoveredBalance = int64(nurseryInfo.RecoveredBalance)
for _, htlcReport := range nurseryInfo.Htlcs {
// TODO(conner) set incoming flag appropriately after handling
// incoming incubation
htlc := &lnrpc.PendingHTLC{
Incoming: false,
Amount: int64(htlcReport.Amount),
Outpoint: htlcReport.Outpoint.String(),
MaturityHeight: htlcReport.MaturityHeight,
Stage: htlcReport.Stage,
}
if htlc.MaturityHeight != 0 {
htlc.BlocksTilMaturity =
int32(htlc.MaturityHeight) -
currentHeight
}
forceClose.PendingHtlcs = append(forceClose.PendingHtlcs,
htlc)
}
return nil
}
// ClosedChannels returns a list of all the channels have been closed.
// This does not include channels that are still in the process of closing.
func (r *rpcServer) ClosedChannels(ctx context.Context,
in *lnrpc.ClosedChannelsRequest) (*lnrpc.ClosedChannelsResponse,
error) {
// Show all channels when no filter flags are set.
filterResults := in.Cooperative || in.LocalForce ||
in.RemoteForce || in.Breach || in.FundingCanceled ||
in.Abandoned
resp := &lnrpc.ClosedChannelsResponse{}
dbChannels, err := r.server.chanStateDB.FetchClosedChannels(false)
if err != nil {
return nil, err
}
// In order to make the response easier to parse for clients, we'll
// sort the set of closed channels by their closing height before
// serializing the proto response.
sort.Slice(dbChannels, func(i, j int) bool {
return dbChannels[i].CloseHeight < dbChannels[j].CloseHeight
})
for _, dbChannel := range dbChannels {
if dbChannel.IsPending {
continue
}
switch dbChannel.CloseType {
case channeldb.CooperativeClose:
if filterResults && !in.Cooperative {
continue
}
case channeldb.LocalForceClose:
if filterResults && !in.LocalForce {
continue
}
case channeldb.RemoteForceClose:
if filterResults && !in.RemoteForce {
continue
}
case channeldb.BreachClose:
if filterResults && !in.Breach {
continue
}
case channeldb.FundingCanceled:
if filterResults && !in.FundingCanceled {
continue
}
case channeldb.Abandoned:
if filterResults && !in.Abandoned {
continue
}
}
channel, err := r.createRPCClosedChannel(dbChannel)
if err != nil {
return nil, err
}
resp.Channels = append(resp.Channels, channel)
}
err = fn.MapOptionZ(
r.server.implCfg.AuxDataParser,
func(parser AuxDataParser) error {
return parser.InlineParseCustomData(resp)
},
)
if err != nil {
return nil, fmt.Errorf("error parsing custom data: %w", err)
}
return resp, nil
}
// LookupHtlcResolution retrieves a final htlc resolution from the database. If
// the htlc has no final resolution yet, a NotFound grpc status code is
// returned.
func (r *rpcServer) LookupHtlcResolution(
_ context.Context, in *lnrpc.LookupHtlcResolutionRequest) (
*lnrpc.LookupHtlcResolutionResponse, error) {
if !r.cfg.StoreFinalHtlcResolutions {
return nil, status.Error(codes.Unavailable, "cannot lookup "+
"with flag --store-final-htlc-resolutions=false")
}
chanID := lnwire.NewShortChanIDFromInt(in.ChanId)
info, err := r.server.chanStateDB.LookupFinalHtlc(chanID, in.HtlcIndex)
switch {
case errors.Is(err, channeldb.ErrHtlcUnknown):
return nil, status.Error(codes.NotFound, err.Error())
case err != nil:
return nil, err
}
return &lnrpc.LookupHtlcResolutionResponse{
Settled: info.Settled,
Offchain: info.Offchain,
}, nil
}
// ListChannels returns a description of all the open channels that this node
// is a participant in.
func (r *rpcServer) ListChannels(ctx context.Context,
in *lnrpc.ListChannelsRequest) (*lnrpc.ListChannelsResponse, error) {
if in.ActiveOnly && in.InactiveOnly {
return nil, fmt.Errorf("either `active_only` or " +
"`inactive_only` can be set, but not both")
}
if in.PublicOnly && in.PrivateOnly {
return nil, fmt.Errorf("either `public_only` or " +
"`private_only` can be set, but not both")
}
if len(in.Peer) > 0 && len(in.Peer) != 33 {
_, err := route.NewVertexFromBytes(in.Peer)
return nil, fmt.Errorf("invalid `peer` key: %w", err)
}
resp := &lnrpc.ListChannelsResponse{}
dbChannels, err := r.server.chanStateDB.FetchAllOpenChannels()
if err != nil {
return nil, err
}
rpcsLog.Debugf("[listchannels] fetched %v channels from DB",
len(dbChannels))
for _, dbChannel := range dbChannels {
nodePub := dbChannel.IdentityPub
nodePubBytes := nodePub.SerializeCompressed()
chanPoint := dbChannel.FundingOutpoint
// If the caller requested channels for a target node, skip any
// that don't match the provided pubkey.
if len(in.Peer) > 0 && !bytes.Equal(nodePubBytes, in.Peer) {
continue
}
var peerOnline bool
if _, err := r.server.FindPeer(nodePub); err == nil {
peerOnline = true
}
channelID := lnwire.NewChanIDFromOutPoint(chanPoint)
var linkActive bool
if link, err := r.server.htlcSwitch.GetLink(channelID); err == nil {
// A channel is only considered active if it is known
// by the switch *and* able to forward
// incoming/outgoing payments.
linkActive = link.EligibleToForward()
}
// Next, we'll determine whether we should add this channel to
// our list depending on the type of channels requested to us.
isActive := peerOnline && linkActive
channel, err := createRPCOpenChannel(
r, dbChannel, isActive, in.PeerAliasLookup,
)
if err != nil {
return nil, err
}
// We'll only skip returning this channel if we were requested
// for a specific kind and this channel doesn't satisfy it.
switch {
case in.ActiveOnly && !isActive:
continue
case in.InactiveOnly && isActive:
continue
case in.PublicOnly && channel.Private:
continue
case in.PrivateOnly && !channel.Private:
continue
}
resp.Channels = append(resp.Channels, channel)
}
err = fn.MapOptionZ(
r.server.implCfg.AuxDataParser,
func(parser AuxDataParser) error {
return parser.InlineParseCustomData(resp)
},
)
if err != nil {
return nil, fmt.Errorf("error parsing custom data: %w", err)
}
return resp, nil
}
// rpcCommitmentType takes the channel type and converts it to an rpc commitment
// type value.
func rpcCommitmentType(chanType channeldb.ChannelType) lnrpc.CommitmentType {
// Extract the commitment type from the channel type flags. We must
// first check whether it has anchors, since in that case it would also
// be tweakless.
switch {
case chanType.HasTapscriptRoot():
return lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY
case chanType.IsTaproot():
return lnrpc.CommitmentType_SIMPLE_TAPROOT
case chanType.HasLeaseExpiration():
return lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE
case chanType.HasAnchors():
return lnrpc.CommitmentType_ANCHORS
case chanType.IsTweakless():
return lnrpc.CommitmentType_STATIC_REMOTE_KEY
default:
return lnrpc.CommitmentType_LEGACY
}
}
// createChannelConstraint creates a *lnrpc.ChannelConstraints using the
// *Channeldb.ChannelConfig.
func createChannelConstraint(
chanCfg *channeldb.ChannelConfig) *lnrpc.ChannelConstraints {
return &lnrpc.ChannelConstraints{
CsvDelay: uint32(chanCfg.CsvDelay),
ChanReserveSat: uint64(chanCfg.ChanReserve),
DustLimitSat: uint64(chanCfg.DustLimit),
MaxPendingAmtMsat: uint64(chanCfg.MaxPendingAmount),
MinHtlcMsat: uint64(chanCfg.MinHTLC),
MaxAcceptedHtlcs: uint32(chanCfg.MaxAcceptedHtlcs),
}
}
// isPrivate evaluates the ChannelFlags of the db channel to determine if the
// channel is private or not.
func isPrivate(dbChannel *channeldb.OpenChannel) bool {
if dbChannel == nil {
return false
}
return dbChannel.ChannelFlags&lnwire.FFAnnounceChannel != 1
}
// encodeCustomChanData encodes the custom channel data for the open channel.
// It encodes that data as a pair of var bytes blobs.
func encodeCustomChanData(lnChan *channeldb.OpenChannel) ([]byte, error) {
customOpenChanData := lnChan.CustomBlob.UnwrapOr(nil)
customLocalCommitData := lnChan.LocalCommitment.CustomBlob.UnwrapOr(nil)
// Don't write any custom data if both blobs are empty.
if len(customOpenChanData) == 0 && len(customLocalCommitData) == 0 {
return nil, nil
}
// We'll encode our custom channel data as two blobs. The first is a
// set of var bytes encoding of the open chan data, the second is an
// encoding of the local commitment data.
var customChanDataBuf bytes.Buffer
err := wire.WriteVarBytes(&customChanDataBuf, 0, customOpenChanData)
if err != nil {
return nil, fmt.Errorf("unable to encode open chan "+
"data: %w", err)
}
err = wire.WriteVarBytes(&customChanDataBuf, 0, customLocalCommitData)
if err != nil {
return nil, fmt.Errorf("unable to encode local commit "+
"data: %w", err)
}
return customChanDataBuf.Bytes(), nil
}
// createRPCOpenChannel creates an *lnrpc.Channel from the *channeldb.Channel.
func createRPCOpenChannel(r *rpcServer, dbChannel *channeldb.OpenChannel,
isActive, peerAliasLookup bool) (*lnrpc.Channel, error) {
nodePub := dbChannel.IdentityPub
nodeID := hex.EncodeToString(nodePub.SerializeCompressed())
chanPoint := dbChannel.FundingOutpoint
chanID := lnwire.NewChanIDFromOutPoint(chanPoint)
// As this is required for display purposes, we'll calculate
// the weight of the commitment transaction. We also add on the
// estimated weight of the witness to calculate the weight of
// the transaction if it were to be immediately unilaterally
// broadcast.
var witnessWeight int64
if dbChannel.ChanType.IsTaproot() {
witnessWeight = input.TaprootKeyPathWitnessSize
} else {
witnessWeight = input.WitnessCommitmentTxWeight
}
localCommit := dbChannel.LocalCommitment
utx := btcutil.NewTx(localCommit.CommitTx)
commitBaseWeight := blockchain.GetTransactionWeight(utx)
commitWeight := commitBaseWeight + witnessWeight
localBalance := localCommit.LocalBalance
remoteBalance := localCommit.RemoteBalance
// As an artifact of our usage of mSAT internally, either party
// may end up in a state where they're holding a fractional
// amount of satoshis which can't be expressed within the
// actual commitment output. Since we round down when going
// from mSAT -> SAT, we may at any point be adding an
// additional SAT to miners fees. As a result, we display a
// commitment fee that accounts for this externally.
var sumOutputs btcutil.Amount
for _, txOut := range localCommit.CommitTx.TxOut {
sumOutputs += btcutil.Amount(txOut.Value)
}
externalCommitFee := dbChannel.Capacity - sumOutputs
// Extract the commitment type from the channel type flags.
commitmentType := rpcCommitmentType(dbChannel.ChanType)
dbScid := dbChannel.ShortChannelID
// Fetch the set of aliases for the channel.
channelAliases := r.server.aliasMgr.GetAliases(dbScid)
// Fetch the peer alias. If one does not exist, errNoPeerAlias
// is returned and peerScidAlias will be an empty ShortChannelID.
peerScidAlias, _ := r.server.aliasMgr.GetPeerAlias(chanID)
// Finally we'll attempt to encode the custom channel data if any
// exists.
customChanBytes, err := encodeCustomChanData(dbChannel)
if err != nil {
return nil, fmt.Errorf("unable to encode open chan data: %w",
err)
}
channel := &lnrpc.Channel{
Active: isActive,
Private: isPrivate(dbChannel),
RemotePubkey: nodeID,
ChannelPoint: chanPoint.String(),
ChanId: dbScid.ToUint64(),
Capacity: int64(dbChannel.Capacity),
LocalBalance: int64(localBalance.ToSatoshis()),
RemoteBalance: int64(remoteBalance.ToSatoshis()),
CommitFee: int64(externalCommitFee),
CommitWeight: commitWeight,
FeePerKw: int64(localCommit.FeePerKw),
TotalSatoshisSent: int64(dbChannel.TotalMSatSent.ToSatoshis()),
TotalSatoshisReceived: int64(dbChannel.TotalMSatReceived.ToSatoshis()),
NumUpdates: localCommit.CommitHeight,
PendingHtlcs: make([]*lnrpc.HTLC, len(localCommit.Htlcs)),
Initiator: dbChannel.IsInitiator,
ChanStatusFlags: dbChannel.ChanStatus().String(),
StaticRemoteKey: commitmentType == lnrpc.CommitmentType_STATIC_REMOTE_KEY,
CommitmentType: commitmentType,
ThawHeight: dbChannel.ThawHeight,
LocalConstraints: createChannelConstraint(
&dbChannel.LocalChanCfg,
),
RemoteConstraints: createChannelConstraint(
&dbChannel.RemoteChanCfg,
),
AliasScids: make([]uint64, 0, len(channelAliases)),
PeerScidAlias: peerScidAlias.ToUint64(),
ZeroConf: dbChannel.IsZeroConf(),
ZeroConfConfirmedScid: dbChannel.ZeroConfRealScid().ToUint64(),
Memo: string(dbChannel.Memo),
CustomChannelData: customChanBytes,
// TODO: remove the following deprecated fields
CsvDelay: uint32(dbChannel.LocalChanCfg.CsvDelay),
LocalChanReserveSat: int64(dbChannel.LocalChanCfg.ChanReserve),
RemoteChanReserveSat: int64(dbChannel.RemoteChanCfg.ChanReserve),
}
// Look up our channel peer's node alias if the caller requests it.
if peerAliasLookup {
peerAlias, err := r.server.graphDB.LookupAlias(nodePub)
if err != nil {
peerAlias = fmt.Sprintf("unable to lookup "+
"peer alias: %v", err)
}
channel.PeerAlias = peerAlias
}
// Populate the set of aliases.
for _, chanAlias := range channelAliases {
channel.AliasScids = append(
channel.AliasScids, chanAlias.ToUint64(),
)
}
// Create two sets of the HTLCs found in the remote commitment, which is
// used to decide whether the HTLCs from the local commitment has been
// locked in or not.
remoteIncomingHTLCs := fn.NewSet[uint64]()
remoteOutgoingHTLCs := fn.NewSet[uint64]()
for _, htlc := range dbChannel.RemoteCommitment.Htlcs {
if htlc.Incoming {
remoteIncomingHTLCs.Add(htlc.HtlcIndex)
} else {
remoteOutgoingHTLCs.Add(htlc.HtlcIndex)
}
}
for i, htlc := range localCommit.Htlcs {
var rHash [32]byte
copy(rHash[:], htlc.RHash[:])
circuitMap := r.server.htlcSwitch.CircuitLookup()
var (
forwardingChannel, forwardingHtlcIndex uint64
lockedIn bool
)
switch {
case htlc.Incoming:
circuit := circuitMap.LookupCircuit(
htlcswitch.CircuitKey{
ChanID: dbChannel.ShortChannelID,
HtlcID: htlc.HtlcIndex,
},
)
if circuit != nil && circuit.Outgoing != nil {
forwardingChannel = circuit.Outgoing.ChanID.
ToUint64()
forwardingHtlcIndex = circuit.Outgoing.HtlcID
}
lockedIn = remoteIncomingHTLCs.Contains(htlc.HtlcIndex)
case !htlc.Incoming:
circuit := circuitMap.LookupOpenCircuit(
htlcswitch.CircuitKey{
ChanID: dbChannel.ShortChannelID,
HtlcID: htlc.HtlcIndex,
},
)
// If the incoming channel id is the special hop.Source
// value, the htlc index is a local payment identifier.
// In this case, report nothing.
if circuit != nil &&
circuit.Incoming.ChanID != hop.Source {
forwardingChannel = circuit.Incoming.ChanID.
ToUint64()
forwardingHtlcIndex = circuit.Incoming.HtlcID
}
lockedIn = remoteOutgoingHTLCs.Contains(htlc.HtlcIndex)
}
channel.PendingHtlcs[i] = &lnrpc.HTLC{
Incoming: htlc.Incoming,
Amount: int64(htlc.Amt.ToSatoshis()),
HashLock: rHash[:],
ExpirationHeight: htlc.RefundTimeout,
HtlcIndex: htlc.HtlcIndex,
ForwardingChannel: forwardingChannel,
ForwardingHtlcIndex: forwardingHtlcIndex,
LockedIn: lockedIn,
}
// Add the Pending Htlc Amount to UnsettledBalance field.
channel.UnsettledBalance += channel.PendingHtlcs[i].Amount
}
// If we initiated opening the channel, the zero height remote balance
// is the push amount. Otherwise, our starting balance is the push
// amount. If there is no push amount, these values will simply be zero.
if dbChannel.IsInitiator {
amt := dbChannel.InitialRemoteBalance.ToSatoshis()
channel.PushAmountSat = uint64(amt)
} else {
amt := dbChannel.InitialLocalBalance.ToSatoshis()
channel.PushAmountSat = uint64(amt)
}
if len(dbChannel.LocalShutdownScript) > 0 {
_, addresses, _, err := txscript.ExtractPkScriptAddrs(
dbChannel.LocalShutdownScript, r.cfg.ActiveNetParams.Params,
)
if err != nil {
return nil, err
}
// We only expect one upfront shutdown address for a channel. If
// LocalShutdownScript is non-zero, there should be one payout
// address set.
if len(addresses) != 1 {
return nil, fmt.Errorf("expected one upfront shutdown "+
"address, got: %v", len(addresses))
}
channel.CloseAddress = addresses[0].String()
}
// If the server hasn't fully started yet, it's possible that the
// channel event store hasn't either, so it won't be able to consume any
// requests until then. To prevent blocking, we'll just omit the uptime
// related fields for now.
if !r.server.Started() {
return channel, nil
}
peer, err := route.NewVertexFromBytes(nodePub.SerializeCompressed())
if err != nil {
return nil, err
}
// Query the event store for additional information about the channel.
// Do not fail if it is not available, because there is a potential
// race between a channel being added to our node and the event store
// being notified of it.
outpoint := dbChannel.FundingOutpoint
info, err := r.server.chanEventStore.GetChanInfo(outpoint, peer)
switch err {
// If the store does not know about the channel, we just log it.
case chanfitness.ErrChannelNotFound:
rpcsLog.Infof("channel: %v not found by channel event store",
outpoint)
// If we got our channel info, we further populate the channel.
case nil:
channel.Uptime = int64(info.Uptime.Seconds())
channel.Lifetime = int64(info.Lifetime.Seconds())
// If we get an unexpected error, we return it.
default:
return nil, err
}
return channel, nil
}
// createRPCClosedChannel creates an *lnrpc.ClosedChannelSummary from a
// *channeldb.ChannelCloseSummary.
func (r *rpcServer) createRPCClosedChannel(
dbChannel *channeldb.ChannelCloseSummary) (*lnrpc.ChannelCloseSummary,
error) {
nodePub := dbChannel.RemotePub
nodeID := hex.EncodeToString(nodePub.SerializeCompressed())
var (
closeType lnrpc.ChannelCloseSummary_ClosureType
openInit lnrpc.Initiator
closeInitiator lnrpc.Initiator
err error
)
// Lookup local and remote cooperative initiators. If these values
// are not known they will just return unknown.
openInit, closeInitiator, err = r.getInitiators(&dbChannel.ChanPoint)
if err != nil {
return nil, err
}
// Convert the close type to rpc type.
switch dbChannel.CloseType {
case channeldb.CooperativeClose:
closeType = lnrpc.ChannelCloseSummary_COOPERATIVE_CLOSE
case channeldb.LocalForceClose:
closeType = lnrpc.ChannelCloseSummary_LOCAL_FORCE_CLOSE
case channeldb.RemoteForceClose:
closeType = lnrpc.ChannelCloseSummary_REMOTE_FORCE_CLOSE
case channeldb.BreachClose:
closeType = lnrpc.ChannelCloseSummary_BREACH_CLOSE
case channeldb.FundingCanceled:
closeType = lnrpc.ChannelCloseSummary_FUNDING_CANCELED
case channeldb.Abandoned:
closeType = lnrpc.ChannelCloseSummary_ABANDONED
}
dbScid := dbChannel.ShortChanID
// Fetch the set of aliases for this channel.
channelAliases := r.server.aliasMgr.GetAliases(dbScid)
channel := &lnrpc.ChannelCloseSummary{
Capacity: int64(dbChannel.Capacity),
RemotePubkey: nodeID,
CloseHeight: dbChannel.CloseHeight,
CloseType: closeType,
ChannelPoint: dbChannel.ChanPoint.String(),
ChanId: dbChannel.ShortChanID.ToUint64(),
SettledBalance: int64(dbChannel.SettledBalance),
TimeLockedBalance: int64(dbChannel.TimeLockedBalance),
ChainHash: dbChannel.ChainHash.String(),
ClosingTxHash: dbChannel.ClosingTXID.String(),
OpenInitiator: openInit,
CloseInitiator: closeInitiator,
AliasScids: make([]uint64, 0, len(channelAliases)),
}
// Populate the set of aliases.
for _, chanAlias := range channelAliases {
channel.AliasScids = append(
channel.AliasScids, chanAlias.ToUint64(),
)
}
// Populate any historical data that the summary needs.
histChan, err := r.server.chanStateDB.FetchHistoricalChannel(
&dbChannel.ChanPoint,
)
switch err {
// The channel was closed in a pre-historic version of lnd. Ignore the
// error.
case channeldb.ErrNoHistoricalBucket:
case channeldb.ErrChannelNotFound:
case nil:
if histChan.IsZeroConf() && histChan.ZeroConfConfirmed() {
// If the channel was zero-conf, it may have confirmed.
// Populate the confirmed SCID if so.
confirmedScid := histChan.ZeroConfRealScid().ToUint64()
channel.ZeroConfConfirmedScid = confirmedScid
}
// Finally we'll attempt to encode the custom channel data if
// any exists.
channel.CustomChannelData, err = encodeCustomChanData(histChan)
if err != nil {
return nil, fmt.Errorf("unable to encode open chan "+
"data: %w", err)
}
// Non-nil error not due to older versions of lnd.
default:
return nil, err
}
reports, err := r.server.miscDB.FetchChannelReports(
*r.cfg.ActiveNetParams.GenesisHash, &dbChannel.ChanPoint,
)
switch err {
// If the channel does not have its resolver outcomes stored,
// ignore it.
case channeldb.ErrNoChainHashBucket:
fallthrough
case channeldb.ErrNoChannelSummaries:
return channel, nil
// If there is no error, fallthrough the switch to process reports.
case nil:
// If another error occurred, return it.
default:
return nil, err
}
for _, report := range reports {
rpcResolution, err := rpcChannelResolution(report)
if err != nil {
return nil, err
}
channel.Resolutions = append(channel.Resolutions, rpcResolution)
}
return channel, nil
}
func rpcChannelResolution(report *channeldb.ResolverReport) (*lnrpc.Resolution,
error) {
res := &lnrpc.Resolution{
AmountSat: uint64(report.Amount),
Outpoint: lnrpc.MarshalOutPoint(&report.OutPoint),
}
if report.SpendTxID != nil {
res.SweepTxid = report.SpendTxID.String()
}
switch report.ResolverType {
case channeldb.ResolverTypeAnchor:
res.ResolutionType = lnrpc.ResolutionType_ANCHOR
case channeldb.ResolverTypeIncomingHtlc:
res.ResolutionType = lnrpc.ResolutionType_INCOMING_HTLC
case channeldb.ResolverTypeOutgoingHtlc:
res.ResolutionType = lnrpc.ResolutionType_OUTGOING_HTLC
case channeldb.ResolverTypeCommit:
res.ResolutionType = lnrpc.ResolutionType_COMMIT
default:
return nil, fmt.Errorf("unknown resolver type: %v",
report.ResolverType)
}
switch report.ResolverOutcome {
case channeldb.ResolverOutcomeClaimed:
res.Outcome = lnrpc.ResolutionOutcome_CLAIMED
case channeldb.ResolverOutcomeUnclaimed:
res.Outcome = lnrpc.ResolutionOutcome_UNCLAIMED
case channeldb.ResolverOutcomeAbandoned:
res.Outcome = lnrpc.ResolutionOutcome_ABANDONED
case channeldb.ResolverOutcomeFirstStage:
res.Outcome = lnrpc.ResolutionOutcome_FIRST_STAGE
case channeldb.ResolverOutcomeTimeout:
res.Outcome = lnrpc.ResolutionOutcome_TIMEOUT
default:
return nil, fmt.Errorf("unknown outcome: %v",
report.ResolverOutcome)
}
return res, nil
}
// getInitiators returns an initiator enum that provides information about the
// party that initiated channel's open and close. This information is obtained
// from the historical channel bucket, so unknown values are returned when the
// channel is not present (which indicates that it was closed before we started
// writing channels to the historical close bucket).
func (r *rpcServer) getInitiators(chanPoint *wire.OutPoint) (
lnrpc.Initiator,
lnrpc.Initiator, error) {
var (
openInitiator = lnrpc.Initiator_INITIATOR_UNKNOWN
closeInitiator = lnrpc.Initiator_INITIATOR_UNKNOWN
)
// To get the close initiator for cooperative closes, we need
// to get the channel status from the historical channel bucket.
histChan, err := r.server.chanStateDB.FetchHistoricalChannel(chanPoint)
switch {
// The node has upgraded from a version where we did not store
// historical channels, and has not closed a channel since. Do
// not return an error, initiator values are unknown.
case err == channeldb.ErrNoHistoricalBucket:
return openInitiator, closeInitiator, nil
// The channel was closed before we started storing historical
// channels. Do not return an error, initiator values are unknown.
case err == channeldb.ErrChannelNotFound:
return openInitiator, closeInitiator, nil
case err != nil:
return 0, 0, err
}
// If we successfully looked up the channel, determine initiator based
// on channels status.
if histChan.IsInitiator {
openInitiator = lnrpc.Initiator_INITIATOR_LOCAL
} else {
openInitiator = lnrpc.Initiator_INITIATOR_REMOTE
}
localInit := histChan.HasChanStatus(
channeldb.ChanStatusLocalCloseInitiator,
)
remoteInit := histChan.HasChanStatus(
channeldb.ChanStatusRemoteCloseInitiator,
)
switch {
// There is a possible case where closes were attempted by both parties.
// We return the initiator as both in this case to provide full
// information about the close.
case localInit && remoteInit:
closeInitiator = lnrpc.Initiator_INITIATOR_BOTH
case localInit:
closeInitiator = lnrpc.Initiator_INITIATOR_LOCAL
case remoteInit:
closeInitiator = lnrpc.Initiator_INITIATOR_REMOTE
}
return openInitiator, closeInitiator, nil
}
// SubscribeChannelEvents returns a uni-directional stream (server -> client)
// for notifying the client of newly active, inactive or closed channels.
func (r *rpcServer) SubscribeChannelEvents(req *lnrpc.ChannelEventSubscription,
updateStream lnrpc.Lightning_SubscribeChannelEventsServer) error {
channelEventSub, err := r.server.channelNotifier.SubscribeChannelEvents()
if err != nil {
return err
}
// Ensure that the resources for the client is cleaned up once either
// the server, or client exits.
defer channelEventSub.Cancel()
for {
select {
// A new update has been sent by the channel router, we'll
// marshal it into the form expected by the gRPC client, then
// send it off to the client(s).
case e := <-channelEventSub.Updates():
var update *lnrpc.ChannelEventUpdate
switch event := e.(type) {
case channelnotifier.PendingOpenChannelEvent:
update = &lnrpc.ChannelEventUpdate{
Type: lnrpc.ChannelEventUpdate_PENDING_OPEN_CHANNEL,
Channel: &lnrpc.ChannelEventUpdate_PendingOpenChannel{
PendingOpenChannel: &lnrpc.PendingUpdate{
Txid: event.ChannelPoint.Hash[:],
OutputIndex: event.ChannelPoint.Index,
},
},
}
case channelnotifier.OpenChannelEvent:
channel, err := createRPCOpenChannel(
r, event.Channel, true, false,
)
if err != nil {
return err
}
update = &lnrpc.ChannelEventUpdate{
Type: lnrpc.ChannelEventUpdate_OPEN_CHANNEL,
Channel: &lnrpc.ChannelEventUpdate_OpenChannel{
OpenChannel: channel,
},
}
case channelnotifier.ClosedChannelEvent:
closedChannel, err := r.createRPCClosedChannel(
event.CloseSummary,
)
if err != nil {
return err
}
update = &lnrpc.ChannelEventUpdate{
Type: lnrpc.ChannelEventUpdate_CLOSED_CHANNEL,
Channel: &lnrpc.ChannelEventUpdate_ClosedChannel{
ClosedChannel: closedChannel,
},
}
case channelnotifier.ActiveChannelEvent:
update = &lnrpc.ChannelEventUpdate{
Type: lnrpc.ChannelEventUpdate_ACTIVE_CHANNEL,
Channel: &lnrpc.ChannelEventUpdate_ActiveChannel{
ActiveChannel: &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: event.ChannelPoint.Hash[:],
},
OutputIndex: event.ChannelPoint.Index,
},
},
}
case channelnotifier.InactiveChannelEvent:
update = &lnrpc.ChannelEventUpdate{
Type: lnrpc.ChannelEventUpdate_INACTIVE_CHANNEL,
Channel: &lnrpc.ChannelEventUpdate_InactiveChannel{
InactiveChannel: &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: event.ChannelPoint.Hash[:],
},
OutputIndex: event.ChannelPoint.Index,
},
},
}
// Completely ignore ActiveLinkEvent and
// InactiveLinkEvent as this is explicitly not exposed
// to the RPC.
case channelnotifier.ActiveLinkEvent,
channelnotifier.InactiveLinkEvent:
continue
case channelnotifier.FullyResolvedChannelEvent:
update = &lnrpc.ChannelEventUpdate{
Type: lnrpc.ChannelEventUpdate_FULLY_RESOLVED_CHANNEL,
Channel: &lnrpc.ChannelEventUpdate_FullyResolvedChannel{
FullyResolvedChannel: &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: event.ChannelPoint.Hash[:],
},
OutputIndex: event.ChannelPoint.Index,
},
},
}
default:
return fmt.Errorf("unexpected channel event update: %v", event)
}
if err := updateStream.Send(update); err != nil {
return err
}
// The response stream's context for whatever reason has been
// closed. If context is closed by an exceeded deadline we will
// return an error.
case <-updateStream.Context().Done():
if errors.Is(updateStream.Context().Err(), context.Canceled) {
return nil
}
return updateStream.Context().Err()
case <-r.quit:
return nil
}
}
}
// paymentStream enables different types of payment streams, such as:
// lnrpc.Lightning_SendPaymentServer and lnrpc.Lightning_SendToRouteServer to
// execute sendPayment. We use this struct as a sort of bridge to enable code
// re-use between SendPayment and SendToRoute.
type paymentStream struct {
recv func() (*rpcPaymentRequest, error)
send func(*lnrpc.SendResponse) error
}
// rpcPaymentRequest wraps lnrpc.SendRequest so that routes from
// lnrpc.SendToRouteRequest can be passed to sendPayment.
type rpcPaymentRequest struct {
*lnrpc.SendRequest
route *route.Route
}
// SendPayment dispatches a bi-directional streaming RPC for sending payments
// through the Lightning Network. A single RPC invocation creates a persistent
// bi-directional stream allowing clients to rapidly send payments through the
// Lightning Network with a single persistent connection.
func (r *rpcServer) SendPayment(stream lnrpc.Lightning_SendPaymentServer) error {
var lock sync.Mutex
return r.sendPayment(&paymentStream{
recv: func() (*rpcPaymentRequest, error) {
req, err := stream.Recv()
if err != nil {
return nil, err
}
return &rpcPaymentRequest{
SendRequest: req,
}, nil
},
send: func(r *lnrpc.SendResponse) error {
// Calling stream.Send concurrently is not safe.
lock.Lock()
defer lock.Unlock()
return stream.Send(r)
},
})
}
// SendToRoute dispatches a bi-directional streaming RPC for sending payments
// through the Lightning Network via predefined routes passed in. A single RPC
// invocation creates a persistent bi-directional stream allowing clients to
// rapidly send payments through the Lightning Network with a single persistent
// connection.
func (r *rpcServer) SendToRoute(stream lnrpc.Lightning_SendToRouteServer) error {
var lock sync.Mutex
return r.sendPayment(&paymentStream{
recv: func() (*rpcPaymentRequest, error) {
req, err := stream.Recv()
if err != nil {
return nil, err
}
return r.unmarshallSendToRouteRequest(req)
},
send: func(r *lnrpc.SendResponse) error {
// Calling stream.Send concurrently is not safe.
lock.Lock()
defer lock.Unlock()
return stream.Send(r)
},
})
}
// unmarshallSendToRouteRequest unmarshalls an rpc sendtoroute request
func (r *rpcServer) unmarshallSendToRouteRequest(
req *lnrpc.SendToRouteRequest) (*rpcPaymentRequest, error) {
if req.Route == nil {
return nil, fmt.Errorf("unable to send, no route provided")
}
route, err := r.routerBackend.UnmarshallRoute(req.Route)
if err != nil {
return nil, err
}
return &rpcPaymentRequest{
SendRequest: &lnrpc.SendRequest{
PaymentHash: req.PaymentHash,
PaymentHashString: req.PaymentHashString,
},
route: route,
}, nil
}
// rpcPaymentIntent is a small wrapper struct around the of values we can
// receive from a client over RPC if they wish to send a payment. We'll either
// extract these fields from a payment request (which may include routing
// hints), or we'll get a fully populated route from the user that we'll pass
// directly to the channel router for dispatching.
type rpcPaymentIntent struct {
msat lnwire.MilliSatoshi
feeLimit lnwire.MilliSatoshi
cltvLimit uint32
dest route.Vertex
rHash [32]byte
cltvDelta uint16
routeHints [][]zpay32.HopHint
outgoingChannelIDs []uint64
lastHop *route.Vertex
destFeatures *lnwire.FeatureVector
paymentAddr fn.Option[[32]byte]
payReq []byte
metadata []byte
blindedPathSet *routing.BlindedPaymentPathSet
destCustomRecords record.CustomSet
route *route.Route
}
// extractPaymentIntent attempts to parse the complete details required to
// dispatch a client from the information presented by an RPC client. There are
// three ways a client can specify their payment details: a payment request,
// via manual details, or via a complete route.
func (r *rpcServer) extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPaymentIntent, error) {
payIntent := rpcPaymentIntent{}
// If a route was specified, then we can use that directly.
if rpcPayReq.route != nil {
// If the user is using the REST interface, then they'll be
// passing the payment hash as a hex encoded string.
if rpcPayReq.PaymentHashString != "" {
paymentHash, err := hex.DecodeString(
rpcPayReq.PaymentHashString,
)
if err != nil {
return payIntent, err
}
copy(payIntent.rHash[:], paymentHash)
} else {
copy(payIntent.rHash[:], rpcPayReq.PaymentHash)
}
payIntent.route = rpcPayReq.route
return payIntent, nil
}
// If there are no routes specified, pass along a outgoing channel
// restriction if specified. The main server rpc does not support
// multiple channel restrictions.
if rpcPayReq.OutgoingChanId != 0 {
payIntent.outgoingChannelIDs = []uint64{
rpcPayReq.OutgoingChanId,
}
}
// Pass along a last hop restriction if specified.
if len(rpcPayReq.LastHopPubkey) > 0 {
lastHop, err := route.NewVertexFromBytes(
rpcPayReq.LastHopPubkey,
)
if err != nil {
return payIntent, err
}
payIntent.lastHop = &lastHop
}
// Take the CLTV limit from the request if set, otherwise use the max.
cltvLimit, err := routerrpc.ValidateCLTVLimit(
rpcPayReq.CltvLimit, r.cfg.MaxOutgoingCltvExpiry,
)
if err != nil {
return payIntent, err
}
payIntent.cltvLimit = cltvLimit
customRecords := record.CustomSet(rpcPayReq.DestCustomRecords)
if err := customRecords.Validate(); err != nil {
return payIntent, err
}
payIntent.destCustomRecords = customRecords
validateDest := func(dest route.Vertex) error {
if rpcPayReq.AllowSelfPayment {
return nil
}
if dest == r.selfNode {
return errors.New("self-payments not allowed")
}
return nil
}
// If the payment request field isn't blank, then the details of the
// invoice are encoded entirely within the encoded payReq. So we'll
// attempt to decode it, populating the payment accordingly.
if rpcPayReq.PaymentRequest != "" {
payReq, err := zpay32.Decode(
rpcPayReq.PaymentRequest, r.cfg.ActiveNetParams.Params,
zpay32.WithErrorOnUnknownFeatureBit(),
)
if err != nil {
return payIntent, err
}
// Next, we'll ensure that this payreq hasn't already expired.
err = routerrpc.ValidatePayReqExpiry(payReq)
if err != nil {
return payIntent, err
}
// If the amount was not included in the invoice, then we let
// the payer specify the amount of satoshis they wish to send.
// We override the amount to pay with the amount provided from
// the payment request.
if payReq.MilliSat == nil {
amt, err := lnrpc.UnmarshallAmt(
rpcPayReq.Amt, rpcPayReq.AmtMsat,
)
if err != nil {
return payIntent, err
}
if amt == 0 {
return payIntent, errors.New("amount must be " +
"specified when paying a zero amount " +
"invoice")
}
payIntent.msat = amt
} else {
payIntent.msat = *payReq.MilliSat
}
// Calculate the fee limit that should be used for this payment.
payIntent.feeLimit = lnrpc.CalculateFeeLimit(
rpcPayReq.FeeLimit, payIntent.msat,
)
copy(payIntent.rHash[:], payReq.PaymentHash[:])
destKey := payReq.Destination.SerializeCompressed()
copy(payIntent.dest[:], destKey)
payIntent.cltvDelta = uint16(payReq.MinFinalCLTVExpiry())
payIntent.routeHints = payReq.RouteHints
payIntent.payReq = []byte(rpcPayReq.PaymentRequest)
payIntent.destFeatures = payReq.Features
payIntent.paymentAddr = payReq.PaymentAddr
payIntent.metadata = payReq.Metadata
if len(payReq.BlindedPaymentPaths) > 0 {
pathSet, err := routerrpc.BuildBlindedPathSet(
payReq.BlindedPaymentPaths,
)
if err != nil {
return payIntent, err
}
payIntent.blindedPathSet = pathSet
// Replace the destination node with the target public
// key of the blinded path set.
copy(
payIntent.dest[:],
pathSet.TargetPubKey().SerializeCompressed(),
)
pathFeatures := pathSet.Features()
if !pathFeatures.IsEmpty() {
payIntent.destFeatures = pathFeatures.Clone()
}
}
if err := validateDest(payIntent.dest); err != nil {
return payIntent, err
}
// Do bounds checking with the block padding.
err = routing.ValidateCLTVLimit(
payIntent.cltvLimit, payIntent.cltvDelta, true,
)
if err != nil {
return payIntent, err
}
return payIntent, nil
}
// At this point, a destination MUST be specified, so we'll convert it
// into the proper representation now. The destination will either be
// encoded as raw bytes, or via a hex string.
var pubBytes []byte
if len(rpcPayReq.Dest) != 0 {
pubBytes = rpcPayReq.Dest
} else {
var err error
pubBytes, err = hex.DecodeString(rpcPayReq.DestString)
if err != nil {
return payIntent, err
}
}
if len(pubBytes) != 33 {
return payIntent, errors.New("invalid key length")
}
copy(payIntent.dest[:], pubBytes)
if err := validateDest(payIntent.dest); err != nil {
return payIntent, err
}
// Payment address may not be needed by legacy invoices.
if len(rpcPayReq.PaymentAddr) != 0 && len(rpcPayReq.PaymentAddr) != 32 {
return payIntent, errors.New("invalid payment address length")
}
// Set the payment address if it was explicitly defined with the
// rpcPaymentRequest.
// Note that the payment address for the payIntent should be nil if none
// was provided with the rpcPaymentRequest.
if len(rpcPayReq.PaymentAddr) != 0 {
var addr [32]byte
copy(addr[:], rpcPayReq.PaymentAddr)
payIntent.paymentAddr = fn.Some(addr)
}
// Otherwise, If the payment request field was not specified
// (and a custom route wasn't specified), construct the payment
// from the other fields.
payIntent.msat, err = lnrpc.UnmarshallAmt(
rpcPayReq.Amt, rpcPayReq.AmtMsat,
)
if err != nil {
return payIntent, err
}
// Calculate the fee limit that should be used for this payment.
payIntent.feeLimit = lnrpc.CalculateFeeLimit(
rpcPayReq.FeeLimit, payIntent.msat,
)
if rpcPayReq.FinalCltvDelta != 0 {
payIntent.cltvDelta = uint16(rpcPayReq.FinalCltvDelta)
} else {
// If no final cltv delta is given, assume the default that we
// use when creating an invoice. We do not assume the default of
// 9 blocks that is defined in BOLT-11, because this is never
// enough for other lnd nodes.
payIntent.cltvDelta = uint16(r.cfg.Bitcoin.TimeLockDelta)
}
// Do bounds checking with the block padding so the router isn't left
// with a zombie payment in case the user messes up.
err = routing.ValidateCLTVLimit(
payIntent.cltvLimit, payIntent.cltvDelta, true,
)
if err != nil {
return payIntent, err
}
// If the user is manually specifying payment details, then the payment
// hash may be encoded as a string.
switch {
case rpcPayReq.PaymentHashString != "":
paymentHash, err := hex.DecodeString(
rpcPayReq.PaymentHashString,
)
if err != nil {
return payIntent, err
}
copy(payIntent.rHash[:], paymentHash)
default:
copy(payIntent.rHash[:], rpcPayReq.PaymentHash)
}
// Unmarshal any custom destination features.
payIntent.destFeatures, err = routerrpc.UnmarshalFeatures(
rpcPayReq.DestFeatures,
)
if err != nil {
return payIntent, err
}
return payIntent, nil
}
type paymentIntentResponse struct {
Route *route.Route
Preimage [32]byte
Err error
}
// dispatchPaymentIntent attempts to fully dispatch an RPC payment intent.
// We'll either pass the payment as a whole to the channel router, or give it a
// pre-built route. The first error this method returns denotes if we were
// unable to save the payment. The second error returned denotes if the payment
// didn't succeed.
func (r *rpcServer) dispatchPaymentIntent(
payIntent *rpcPaymentIntent) (*paymentIntentResponse, error) {
// Construct a payment request to send to the channel router. If the
// payment is successful, the route chosen will be returned. Otherwise,
// we'll get a non-nil error.
var (
preImage [32]byte
route *route.Route
routerErr error
)
// If a route was specified, then we'll pass the route directly to the
// router, otherwise we'll create a payment session to execute it.
if payIntent.route == nil {
payment := &routing.LightningPayment{
Target: payIntent.dest,
Amount: payIntent.msat,
FinalCLTVDelta: payIntent.cltvDelta,
FeeLimit: payIntent.feeLimit,
CltvLimit: payIntent.cltvLimit,
RouteHints: payIntent.routeHints,
OutgoingChannelIDs: payIntent.outgoingChannelIDs,
LastHop: payIntent.lastHop,
PaymentRequest: payIntent.payReq,
PayAttemptTimeout: routing.DefaultPayAttemptTimeout,
DestCustomRecords: payIntent.destCustomRecords,
DestFeatures: payIntent.destFeatures,
PaymentAddr: payIntent.paymentAddr,
Metadata: payIntent.metadata,
BlindedPathSet: payIntent.blindedPathSet,
// Don't enable multi-part payments on the main rpc.
// Users need to use routerrpc for that.
MaxParts: 1,
}
err := payment.SetPaymentHash(payIntent.rHash)
if err != nil {
return nil, err
}
preImage, route, routerErr = r.server.chanRouter.SendPayment(
payment,
)
} else {
var attempt *channeldb.HTLCAttempt
attempt, routerErr = r.server.chanRouter.SendToRoute(
payIntent.rHash, payIntent.route, nil,
)
if routerErr == nil {
preImage = attempt.Settle.Preimage
}
route = payIntent.route
}
// If the route failed, then we'll return a nil save err, but a non-nil
// routing err.
if routerErr != nil {
rpcsLog.Warnf("Unable to send payment: %v", routerErr)
return &paymentIntentResponse{
Err: routerErr,
}, nil
}
return &paymentIntentResponse{
Route: route,
Preimage: preImage,
}, nil
}
// sendPayment takes a paymentStream (a source of pre-built routes or payment
// requests) and continually attempt to dispatch payment requests written to
// the write end of the stream. Responses will also be streamed back to the
// client via the write end of the stream. This method is by both SendToRoute
// and SendPayment as the logic is virtually identical.
func (r *rpcServer) sendPayment(stream *paymentStream) error {
payChan := make(chan *rpcPaymentIntent)
errChan := make(chan error, 1)
// We don't allow payments to be sent while the daemon itself is still
// syncing as we may be trying to sent a payment over a "stale"
// channel.
if !r.server.Started() {
return ErrServerNotActive
}
// TODO(roasbeef): check payment filter to see if already used?
// In order to limit the level of concurrency and prevent a client from
// attempting to OOM the server, we'll set up a semaphore to create an
// upper ceiling on the number of outstanding payments.
const numOutstandingPayments = 2000
htlcSema := make(chan struct{}, numOutstandingPayments)
for i := 0; i < numOutstandingPayments; i++ {
htlcSema <- struct{}{}
}
// We keep track of the running goroutines and set up a quit signal we
// can use to request them to exit if the method returns because of an
// encountered error.
var wg sync.WaitGroup
reqQuit := make(chan struct{})
defer close(reqQuit)
// Launch a new goroutine to handle reading new payment requests from
// the client. This way we can handle errors independently of blocking
// and waiting for the next payment request to come through.
// TODO(joostjager): Callers expect result to come in in the same order
// as the request were sent, but this is far from guarantueed in the
// code below.
wg.Add(1)
go func() {
defer wg.Done()
for {
select {
case <-reqQuit:
return
default:
// Receive the next pending payment within the
// stream sent by the client. If we read the
// EOF sentinel, then the client has closed the
// stream, and we can exit normally.
nextPayment, err := stream.recv()
if err == io.EOF {
close(payChan)
return
} else if err != nil {
rpcsLog.Errorf("Failed receiving from "+
"stream: %v", err)
select {
case errChan <- err:
default:
}
return
}
// Populate the next payment, either from the
// payment request, or from the explicitly set
// fields. If the payment proto wasn't well
// formed, then we'll send an error reply and
// wait for the next payment.
payIntent, err := r.extractPaymentIntent(
nextPayment,
)
if err != nil {
if err := stream.send(&lnrpc.SendResponse{
PaymentError: err.Error(),
PaymentHash: payIntent.rHash[:],
}); err != nil {
rpcsLog.Errorf("Failed "+
"sending on "+
"stream: %v", err)
select {
case errChan <- err:
default:
}
return
}
continue
}
// If the payment was well formed, then we'll
// send to the dispatch goroutine, or exit,
// which ever comes first.
select {
case payChan <- &payIntent:
case <-reqQuit:
return
}
}
}
}()
sendLoop:
for {
select {
// If we encounter and error either during sending or
// receiving, we return directly, closing the stream.
case err := <-errChan:
return err
case <-r.quit:
return errors.New("rpc server shutting down")
case payIntent, ok := <-payChan:
// If the receive loop is done, we break the send loop
// and wait for the ongoing payments to finish before
// exiting.
if !ok {
break sendLoop
}
// We launch a new goroutine to execute the current
// payment so we can continue to serve requests while
// this payment is being dispatched.
wg.Add(1)
go func(payIntent *rpcPaymentIntent) {
defer wg.Done()
// Attempt to grab a free semaphore slot, using
// a defer to eventually release the slot
// regardless of payment success.
select {
case <-htlcSema:
case <-reqQuit:
return
}
defer func() {
htlcSema <- struct{}{}
}()
resp, saveErr := r.dispatchPaymentIntent(
payIntent,
)
switch {
// If we were unable to save the state of the
// payment, then we'll return the error to the
// user, and terminate.
case saveErr != nil:
rpcsLog.Errorf("Failed dispatching "+
"payment intent: %v", saveErr)
select {
case errChan <- saveErr:
default:
}
return
// If we receive payment error than, instead of
// terminating the stream, send error response
// to the user.
case resp.Err != nil:
err := stream.send(&lnrpc.SendResponse{
PaymentError: resp.Err.Error(),
PaymentHash: payIntent.rHash[:],
})
if err != nil {
rpcsLog.Errorf("Failed "+
"sending error "+
"response: %v", err)
select {
case errChan <- err:
default:
}
}
return
}
backend := r.routerBackend
marshalledRouted, err := backend.MarshallRoute(
resp.Route,
)
if err != nil {
errChan <- err
return
}
err = stream.send(&lnrpc.SendResponse{
PaymentHash: payIntent.rHash[:],
PaymentPreimage: resp.Preimage[:],
PaymentRoute: marshalledRouted,
})
if err != nil {
rpcsLog.Errorf("Failed sending "+
"response: %v", err)
select {
case errChan <- err:
default:
}
return
}
}(payIntent)
}
}
// Wait for all goroutines to finish before closing the stream.
wg.Wait()
return nil
}
// SendPaymentSync is the synchronous non-streaming version of SendPayment.
// This RPC is intended to be consumed by clients of the REST proxy.
// Additionally, this RPC expects the destination's public key and the payment
// hash (if any) to be encoded as hex strings.
func (r *rpcServer) SendPaymentSync(ctx context.Context,
nextPayment *lnrpc.SendRequest) (*lnrpc.SendResponse, error) {
return r.sendPaymentSync(&rpcPaymentRequest{
SendRequest: nextPayment,
})
}
// SendToRouteSync is the synchronous non-streaming version of SendToRoute.
// This RPC is intended to be consumed by clients of the REST proxy.
// Additionally, this RPC expects the payment hash (if any) to be encoded as
// hex strings.
func (r *rpcServer) SendToRouteSync(ctx context.Context,
req *lnrpc.SendToRouteRequest) (*lnrpc.SendResponse, error) {
if req.Route == nil {
return nil, fmt.Errorf("unable to send, no routes provided")
}
paymentRequest, err := r.unmarshallSendToRouteRequest(req)
if err != nil {
return nil, err
}
return r.sendPaymentSync(paymentRequest)
}
// sendPaymentSync is the synchronous variant of sendPayment. It will block and
// wait until the payment has been fully completed.
func (r *rpcServer) sendPaymentSync(
nextPayment *rpcPaymentRequest) (*lnrpc.SendResponse, error) {
// We don't allow payments to be sent while the daemon itself is still
// syncing as we may be trying to sent a payment over a "stale"
// channel.
if !r.server.Started() {
return nil, ErrServerNotActive
}
// First we'll attempt to map the proto describing the next payment to
// an intent that we can pass to local sub-systems.
payIntent, err := r.extractPaymentIntent(nextPayment)
if err != nil {
return nil, err
}
// With the payment validated, we'll now attempt to dispatch the
// payment.
resp, saveErr := r.dispatchPaymentIntent(&payIntent)
switch {
case saveErr != nil:
return nil, saveErr
case resp.Err != nil:
return &lnrpc.SendResponse{
PaymentError: resp.Err.Error(),
PaymentHash: payIntent.rHash[:],
}, nil
}
rpcRoute, err := r.routerBackend.MarshallRoute(resp.Route)
if err != nil {
return nil, err
}
return &lnrpc.SendResponse{
PaymentHash: payIntent.rHash[:],
PaymentPreimage: resp.Preimage[:],
PaymentRoute: rpcRoute,
}, nil
}
// AddInvoice attempts to add a new invoice to the invoice database. Any
// duplicated invoices are rejected, therefore all invoices *must* have a
// unique payment preimage.
func (r *rpcServer) AddInvoice(ctx context.Context,
invoice *lnrpc.Invoice) (*lnrpc.AddInvoiceResponse, error) {
var (
defaultDelta = r.cfg.Bitcoin.TimeLockDelta
blindCfg = invoice.BlindedPathConfig
blind = invoice.IsBlinded
)
globalBlindCfg := r.server.cfg.Routing.BlindedPaths
blindingRestrictions := &routing.BlindedPathRestrictions{
MinDistanceFromIntroNode: globalBlindCfg.MinNumRealHops,
NumHops: globalBlindCfg.NumHops,
MaxNumPaths: globalBlindCfg.MaxNumPaths,
NodeOmissionSet: fn.NewSet[route.Vertex](),
}
if blindCfg != nil && !blind {
return nil, fmt.Errorf("blinded path config provided but " +
"IsBlinded not set")
}
if blind && blindCfg != nil {
if blindCfg.MinNumRealHops != nil {
blindingRestrictions.MinDistanceFromIntroNode =
uint8(*blindCfg.MinNumRealHops)
}
if blindCfg.NumHops != nil {
blindingRestrictions.NumHops = uint8(*blindCfg.NumHops)
}
if blindCfg.MaxNumPaths != nil {
blindingRestrictions.MaxNumPaths =
uint8(*blindCfg.MaxNumPaths)
}
for _, nodeIDBytes := range blindCfg.NodeOmissionList {
vertex, err := route.NewVertexFromBytes(nodeIDBytes)
if err != nil {
return nil, err
}
blindingRestrictions.NodeOmissionSet.Add(vertex)
}
}
if blindingRestrictions.MinDistanceFromIntroNode >
blindingRestrictions.NumHops {
return nil, fmt.Errorf("the minimum number of real " +
"hops in a blinded path must be smaller than " +
"or equal to the number of hops expected to " +
"be included in each path")
}
addInvoiceCfg := &invoicesrpc.AddInvoiceConfig{
AddInvoice: r.server.invoices.AddInvoice,
IsChannelActive: r.server.htlcSwitch.HasActiveLink,
ChainParams: r.cfg.ActiveNetParams.Params,
NodeSigner: r.server.nodeSigner,
DefaultCLTVExpiry: defaultDelta,
ChanDB: r.server.chanStateDB,
Graph: r.server.graphDB,
GenInvoiceFeatures: func() *lnwire.FeatureVector {
v := r.server.featureMgr.Get(feature.SetInvoice)
if blind {
// If an invoice includes blinded paths, then a
// payment address is not required since we use
// the PathID in the final hop's encrypted data
// as equivalent to the payment address
v.Unset(lnwire.PaymentAddrRequired)
v.Set(lnwire.PaymentAddrOptional)
// The invoice payer will also need to
// understand the new BOLT 11 tagged field
// containing the blinded path, so we switch
// the bit to required.
v = feature.SetBit(
v, lnwire.Bolt11BlindedPathsRequired,
)
}
return v
},
GenAmpInvoiceFeatures: func() *lnwire.FeatureVector {
return r.server.featureMgr.Get(feature.SetInvoiceAmp)
},
GetAlias: r.server.aliasMgr.GetPeerAlias,
BestHeight: r.server.cc.BestBlockTracker.BestHeight,
QueryBlindedRoutes: func(amt lnwire.MilliSatoshi) (
[]*route.Route, error) {
return r.server.chanRouter.FindBlindedPaths(
r.selfNode, amt,
r.server.defaultMC.GetProbability,
blindingRestrictions,
)
},
}
value, err := lnrpc.UnmarshallAmt(invoice.Value, invoice.ValueMsat)
if err != nil {
return nil, err
}
// Convert the passed routing hints to the required format.
routeHints, err := invoicesrpc.CreateZpay32HopHints(invoice.RouteHints)
if err != nil {
return nil, err
}
var blindedPathCfg *invoicesrpc.BlindedPathConfig
if blind {
bpConfig := r.server.cfg.Routing.BlindedPaths
blindedPathCfg = &invoicesrpc.BlindedPathConfig{
RoutePolicyIncrMultiplier: bpConfig.
PolicyIncreaseMultiplier,
RoutePolicyDecrMultiplier: bpConfig.
PolicyDecreaseMultiplier,
DefaultDummyHopPolicy: &blindedpath.BlindedHopPolicy{
CLTVExpiryDelta: uint16(defaultDelta),
FeeRate: uint32(
r.server.cfg.Bitcoin.FeeRate,
),
BaseFee: r.server.cfg.Bitcoin.BaseFee,
MinHTLCMsat: r.server.cfg.Bitcoin.MinHTLCIn,
// MaxHTLCMsat will be calculated on the fly by
// using the introduction node's channel's
// capacities.
MaxHTLCMsat: 0,
},
MinNumPathHops: blindingRestrictions.NumHops,
}
}
addInvoiceData := &invoicesrpc.AddInvoiceData{
Memo: invoice.Memo,
Value: value,
DescriptionHash: invoice.DescriptionHash,
Expiry: invoice.Expiry,
FallbackAddr: invoice.FallbackAddr,
CltvExpiry: invoice.CltvExpiry,
Private: invoice.Private,
RouteHints: routeHints,
Amp: invoice.IsAmp,
BlindedPathCfg: blindedPathCfg,
}
if invoice.RPreimage != nil {
preimage, err := lntypes.MakePreimage(invoice.RPreimage)
if err != nil {
return nil, err
}
addInvoiceData.Preimage = &preimage
}
hash, dbInvoice, err := invoicesrpc.AddInvoice(
ctx, addInvoiceCfg, addInvoiceData,
)
if err != nil {
return nil, err
}
return &lnrpc.AddInvoiceResponse{
AddIndex: dbInvoice.AddIndex,
PaymentRequest: string(dbInvoice.PaymentRequest),
RHash: hash[:],
PaymentAddr: dbInvoice.Terms.PaymentAddr[:],
}, nil
}
// LookupInvoice attempts to look up an invoice according to its payment hash.
// The passed payment hash *must* be exactly 32 bytes, if not an error is
// returned.
func (r *rpcServer) LookupInvoice(ctx context.Context,
req *lnrpc.PaymentHash) (*lnrpc.Invoice, error) {
var (
payHash [32]byte
rHash []byte
err error
)
// If the RHash as a raw string was provided, then decode that and use
// that directly. Otherwise, we use the raw bytes provided.
if req.RHashStr != "" {
rHash, err = hex.DecodeString(req.RHashStr)
if err != nil {
return nil, err
}
} else {
rHash = req.RHash
}
// Ensure that the payment hash is *exactly* 32-bytes.
if len(rHash) != 0 && len(rHash) != 32 {
return nil, fmt.Errorf("payment hash must be exactly "+
"32 bytes, is instead %v", len(rHash))
}
copy(payHash[:], rHash)
rpcsLog.Tracef("[lookupinvoice] searching for invoice %x", payHash[:])
invoice, err := r.server.invoices.LookupInvoice(ctx, payHash)
switch {
case errors.Is(err, invoices.ErrInvoiceNotFound):
return nil, status.Error(codes.NotFound, err.Error())
case err != nil:
return nil, err
}
rpcsLog.Tracef("[lookupinvoice] located invoice %v",
lnutils.SpewLogClosure(invoice))
rpcInvoice, err := invoicesrpc.CreateRPCInvoice(
&invoice, r.cfg.ActiveNetParams.Params,
)
if err != nil {
return nil, err
}
// Give the aux data parser a chance to format the custom data in the
// invoice HTLCs.
err = fn.MapOptionZ(
r.server.implCfg.AuxDataParser,
func(parser AuxDataParser) error {
return parser.InlineParseCustomData(rpcInvoice)
},
)
if err != nil {
return nil, fmt.Errorf("error parsing custom data: %w",
err)
}
return rpcInvoice, nil
}
// ListInvoices returns a list of all the invoices currently stored within the
// database. Any active debug invoices are ignored.
func (r *rpcServer) ListInvoices(ctx context.Context,
req *lnrpc.ListInvoiceRequest) (*lnrpc.ListInvoiceResponse, error) {
// If the number of invoices was not specified, then we'll default to
// returning the latest 100 invoices.
if req.NumMaxInvoices == 0 {
req.NumMaxInvoices = 100
}
// If both dates are set, we check that the start date is less than the
// end date, otherwise we'll get an empty result.
if req.CreationDateStart != 0 && req.CreationDateEnd != 0 {
if req.CreationDateStart >= req.CreationDateEnd {
return nil, fmt.Errorf("start date(%v) must be before "+
"end date(%v)", req.CreationDateStart,
req.CreationDateEnd)
}
}
// Next, we'll map the proto request into a format that is understood by
// the database.
q := invoices.InvoiceQuery{
IndexOffset: req.IndexOffset,
NumMaxInvoices: req.NumMaxInvoices,
PendingOnly: req.PendingOnly,
Reversed: req.Reversed,
CreationDateStart: int64(req.CreationDateStart),
CreationDateEnd: int64(req.CreationDateEnd),
}
invoiceSlice, err := r.server.invoicesDB.QueryInvoices(ctx, q)
if err != nil {
return nil, fmt.Errorf("unable to query invoices: %w", err)
}
// Before returning the response, we'll need to convert each invoice
// into it's proto representation.
resp := &lnrpc.ListInvoiceResponse{
Invoices: make([]*lnrpc.Invoice, len(invoiceSlice.Invoices)),
FirstIndexOffset: invoiceSlice.FirstIndexOffset,
LastIndexOffset: invoiceSlice.LastIndexOffset,
}
for i, invoice := range invoiceSlice.Invoices {
invoice := invoice
resp.Invoices[i], err = invoicesrpc.CreateRPCInvoice(
&invoice, r.cfg.ActiveNetParams.Params,
)
if err != nil {
return nil, err
}
// Give the aux data parser a chance to format the custom data
// in the invoice HTLCs.
err = fn.MapOptionZ(
r.server.implCfg.AuxDataParser,
func(parser AuxDataParser) error {
return parser.InlineParseCustomData(
resp.Invoices[i],
)
},
)
if err != nil {
return nil, fmt.Errorf("error parsing custom data: %w",
err)
}
}
return resp, nil
}
// SubscribeInvoices returns a uni-directional stream (server -> client) for
// notifying the client of newly added/settled invoices.
func (r *rpcServer) SubscribeInvoices(req *lnrpc.InvoiceSubscription,
updateStream lnrpc.Lightning_SubscribeInvoicesServer) error {
invoiceClient, err := r.server.invoices.SubscribeNotifications(
updateStream.Context(), req.AddIndex, req.SettleIndex,
)
if err != nil {
return err
}
defer invoiceClient.Cancel()
for {
select {
case newInvoice := <-invoiceClient.NewInvoices:
rpcInvoice, err := invoicesrpc.CreateRPCInvoice(
newInvoice, r.cfg.ActiveNetParams.Params,
)
if err != nil {
return err
}
// Give the aux data parser a chance to format the
// custom data in the invoice HTLCs.
err = fn.MapOptionZ(
r.server.implCfg.AuxDataParser,
func(parser AuxDataParser) error {
return parser.InlineParseCustomData(
rpcInvoice,
)
},
)
if err != nil {
return fmt.Errorf("error parsing custom data: "+
"%w", err)
}
if err := updateStream.Send(rpcInvoice); err != nil {
return err
}
case settledInvoice := <-invoiceClient.SettledInvoices:
rpcInvoice, err := invoicesrpc.CreateRPCInvoice(
settledInvoice, r.cfg.ActiveNetParams.Params,
)
if err != nil {
return err
}
// Give the aux data parser a chance to format the
// custom data in the invoice HTLCs.
err = fn.MapOptionZ(
r.server.implCfg.AuxDataParser,
func(parser AuxDataParser) error {
return parser.InlineParseCustomData(
rpcInvoice,
)
},
)
if err != nil {
return fmt.Errorf("error parsing custom data: "+
"%w", err)
}
if err := updateStream.Send(rpcInvoice); err != nil {
return err
}
// The response stream's context for whatever reason has been
// closed. If context is closed by an exceeded deadline we will
// return an error.
case <-updateStream.Context().Done():
if errors.Is(updateStream.Context().Err(), context.Canceled) {
return nil
}
return updateStream.Context().Err()
case <-r.quit:
return nil
}
}
}
// SubscribeTransactions creates a uni-directional stream (server -> client) in
// which any newly discovered transactions relevant to the wallet are sent
// over.
func (r *rpcServer) SubscribeTransactions(req *lnrpc.GetTransactionsRequest,
updateStream lnrpc.Lightning_SubscribeTransactionsServer) error {
txClient, err := r.server.cc.Wallet.SubscribeTransactions()
if err != nil {
return err
}
defer txClient.Cancel()
rpcsLog.Infof("New transaction subscription")
for {
select {
case tx := <-txClient.ConfirmedTransactions():
detail := lnrpc.RPCTransaction(tx)
if err := updateStream.Send(detail); err != nil {
return err
}
case tx := <-txClient.UnconfirmedTransactions():
detail := lnrpc.RPCTransaction(tx)
if err := updateStream.Send(detail); err != nil {
return err
}
// The response stream's context for whatever reason has been
// closed. If context is closed by an exceeded deadline we will
// return an error.
case <-updateStream.Context().Done():
rpcsLog.Infof("Canceling transaction subscription")
if errors.Is(updateStream.Context().Err(), context.Canceled) {
return nil
}
return updateStream.Context().Err()
case <-r.quit:
return nil
}
}
}
// GetTransactions returns a list of describing all the known transactions
// relevant to the wallet.
func (r *rpcServer) GetTransactions(ctx context.Context,
req *lnrpc.GetTransactionsRequest) (*lnrpc.TransactionDetails, error) {
// To remain backwards compatible with the old api, default to the
// special case end height which will return transactions from the start
// height until the chain tip, including unconfirmed transactions.
var endHeight = btcwallet.UnconfirmedHeight
// If the user has provided an end height, we overwrite our default.
if req.EndHeight != 0 {
endHeight = req.EndHeight
}
txns, firstIdx, lastIdx, err :=
r.server.cc.Wallet.ListTransactionDetails(
req.StartHeight, endHeight, req.Account,
req.IndexOffset, req.MaxTransactions,
)
if err != nil {
return nil, err
}
return lnrpc.RPCTransactionDetails(txns, firstIdx, lastIdx), nil
}
// DescribeGraph returns a description of the latest graph state from the PoV
// of the node. The graph information is partitioned into two components: all
// the nodes/vertexes, and all the edges that connect the vertexes themselves.
// As this is a directed graph, the edges also contain the node directional
// specific routing policy which includes: the time lock delta, fee
// information, etc.
func (r *rpcServer) DescribeGraph(ctx context.Context,
req *lnrpc.ChannelGraphRequest) (*lnrpc.ChannelGraph, error) {
resp := &lnrpc.ChannelGraph{}
includeUnannounced := req.IncludeUnannounced
// Check to see if the cache is already populated, if so then we can
// just return it directly.
//
// TODO(roasbeef): move this to an interceptor level feature?
graphCacheActive := r.cfg.Caches.RPCGraphCacheDuration != 0
if graphCacheActive {
r.graphCache.Lock()
defer r.graphCache.Unlock()
if r.describeGraphResp != nil {
return r.describeGraphResp, nil
}
}
// Obtain the pointer to the global singleton channel graph, this will
// provide a consistent view of the graph due to bolt db's
// transactional model.
graph := r.server.graphDB
// First iterate through all the known nodes (connected or unconnected
// within the graph), collating their current state into the RPC
// response.
err := graph.ForEachNode(func(nodeTx graphdb.NodeRTx) error {
lnNode := marshalNode(nodeTx.Node())
resp.Nodes = append(resp.Nodes, lnNode)
return nil
})
if err != nil {
return nil, err
}
// Next, for each active channel we know of within the graph, create a
// similar response which details both the edge information as well as
// the routing policies of th nodes connecting the two edges.
err = graph.ForEachChannel(func(edgeInfo *models.ChannelEdgeInfo,
c1, c2 *models.ChannelEdgePolicy) error {
// Do not include unannounced channels unless specifically
// requested. Unannounced channels include both private channels as
// well as public channels whose authentication proof were not
// confirmed yet, hence were not announced.
if !includeUnannounced && edgeInfo.AuthProof == nil {
return nil
}
edge := marshalDBEdge(edgeInfo, c1, c2)
resp.Edges = append(resp.Edges, edge)
return nil
})
if err != nil && !errors.Is(err, graphdb.ErrGraphNoEdgesFound) {
return nil, err
}
// We still have the mutex held, so we can safely populate the cache
// now to save on GC churn for this query, but only if the cache isn't
// disabled.
if graphCacheActive {
r.describeGraphResp = resp
}
return resp, nil
}
// marshalExtraOpaqueData marshals the given tlv data. If the tlv stream is
// malformed or empty, an empty map is returned. This makes the method safe to
// use on unvalidated data.
func marshalExtraOpaqueData(data []byte) map[uint64][]byte {
r := bytes.NewReader(data)
tlvStream, err := tlv.NewStream()
if err != nil {
return nil
}
// Since ExtraOpaqueData is provided by a potentially malicious peer,
// pass it into the P2P decoding variant.
parsedTypes, err := tlvStream.DecodeWithParsedTypesP2P(r)
if err != nil || len(parsedTypes) == 0 {
return nil
}
records := make(map[uint64][]byte)
for k, v := range parsedTypes {
records[uint64(k)] = v
}
return records
}
// extractInboundFeeSafe tries to extract the inbound fee from the given extra
// opaque data tlv block. If parsing fails, a zero inbound fee is returned. This
// function is typically used on unvalidated data coming stored in the database.
// There is not much we can do other than ignoring errors here.
func extractInboundFeeSafe(data lnwire.ExtraOpaqueData) lnwire.Fee {
var inboundFee lnwire.Fee
_, err := data.ExtractRecords(&inboundFee)
if err != nil {
// Return zero fee. Do not return the inboundFee variable
// because it may be undefined.
return lnwire.Fee{}
}
return inboundFee
}
func marshalDBEdge(edgeInfo *models.ChannelEdgeInfo,
c1, c2 *models.ChannelEdgePolicy) *lnrpc.ChannelEdge {
// Make sure the policies match the node they belong to. c1 should point
// to the policy for NodeKey1, and c2 for NodeKey2.
if c1 != nil && c1.ChannelFlags&lnwire.ChanUpdateDirection == 1 ||
c2 != nil && c2.ChannelFlags&lnwire.ChanUpdateDirection == 0 {
c2, c1 = c1, c2
}
var lastUpdate int64
if c1 != nil {
lastUpdate = c1.LastUpdate.Unix()
}
if c2 != nil && c2.LastUpdate.Unix() > lastUpdate {
lastUpdate = c2.LastUpdate.Unix()
}
customRecords := marshalExtraOpaqueData(edgeInfo.ExtraOpaqueData)
edge := &lnrpc.ChannelEdge{
ChannelId: edgeInfo.ChannelID,
ChanPoint: edgeInfo.ChannelPoint.String(),
// TODO(roasbeef): update should be on edge info itself
LastUpdate: uint32(lastUpdate),
Node1Pub: hex.EncodeToString(edgeInfo.NodeKey1Bytes[:]),
Node2Pub: hex.EncodeToString(edgeInfo.NodeKey2Bytes[:]),
Capacity: int64(edgeInfo.Capacity),
CustomRecords: customRecords,
}
if c1 != nil {
edge.Node1Policy = marshalDBRoutingPolicy(c1)
}
if c2 != nil {
edge.Node2Policy = marshalDBRoutingPolicy(c2)
}
return edge
}
func marshalDBRoutingPolicy(
policy *models.ChannelEdgePolicy) *lnrpc.RoutingPolicy {
disabled := policy.ChannelFlags&lnwire.ChanUpdateDisabled != 0
customRecords := marshalExtraOpaqueData(policy.ExtraOpaqueData)
inboundFee := extractInboundFeeSafe(policy.ExtraOpaqueData)
return &lnrpc.RoutingPolicy{
TimeLockDelta: uint32(policy.TimeLockDelta),
MinHtlc: int64(policy.MinHTLC),
MaxHtlcMsat: uint64(policy.MaxHTLC),
FeeBaseMsat: int64(policy.FeeBaseMSat),
FeeRateMilliMsat: int64(policy.FeeProportionalMillionths),
Disabled: disabled,
LastUpdate: uint32(policy.LastUpdate.Unix()),
CustomRecords: customRecords,
InboundFeeBaseMsat: inboundFee.BaseFee,
InboundFeeRateMilliMsat: inboundFee.FeeRate,
}
}
// GetNodeMetrics returns all available node metrics calculated from the
// current channel graph.
func (r *rpcServer) GetNodeMetrics(ctx context.Context,
req *lnrpc.NodeMetricsRequest) (*lnrpc.NodeMetricsResponse, error) {
// Get requested metric types.
getCentrality := false
for _, t := range req.Types {
if t == lnrpc.NodeMetricType_BETWEENNESS_CENTRALITY {
getCentrality = true
}
}
// Only centrality can be requested for now.
if !getCentrality {
return nil, nil
}
resp := &lnrpc.NodeMetricsResponse{
BetweennessCentrality: make(map[string]*lnrpc.FloatMetric),
}
// Obtain the pointer to the global singleton channel graph, this will
// provide a consistent view of the graph due to bolt db's
// transactional model.
graph := r.server.graphDB
// Calculate betweenness centrality if requested. Note that depending on the
// graph size, this may take up to a few minutes.
channelGraph := autopilot.ChannelGraphFromDatabase(graph)
centralityMetric, err := autopilot.NewBetweennessCentralityMetric(
runtime.NumCPU(),
)
if err != nil {
return nil, err
}
if err := centralityMetric.Refresh(channelGraph); err != nil {
return nil, err
}
// Fill normalized and non normalized centrality.
centrality := centralityMetric.GetMetric(true)
for nodeID, val := range centrality {
resp.BetweennessCentrality[hex.EncodeToString(nodeID[:])] =
&lnrpc.FloatMetric{
NormalizedValue: val,
}
}
centrality = centralityMetric.GetMetric(false)
for nodeID, val := range centrality {
resp.BetweennessCentrality[hex.EncodeToString(nodeID[:])].Value = val
}
return resp, nil
}
// GetChanInfo returns the latest authenticated network announcement for the
// given channel identified by either its channel ID or a channel outpoint. Both
// uniquely identify the location of transaction's funding output within the
// blockchain. The former is an 8-byte integer, while the latter is a string
// formatted as funding_txid:output_index.
func (r *rpcServer) GetChanInfo(_ context.Context,
in *lnrpc.ChanInfoRequest) (*lnrpc.ChannelEdge, error) {
graph := r.server.graphDB
var (
edgeInfo *models.ChannelEdgeInfo
edge1, edge2 *models.ChannelEdgePolicy
err error
)
switch {
case in.ChanId != 0:
edgeInfo, edge1, edge2, err = graph.FetchChannelEdgesByID(
in.ChanId,
)
case in.ChanPoint != "":
var chanPoint *wire.OutPoint
chanPoint, err = wire.NewOutPointFromString(in.ChanPoint)
if err != nil {
return nil, err
}
edgeInfo, edge1, edge2, err = graph.FetchChannelEdgesByOutpoint(
chanPoint,
)
default:
return nil, fmt.Errorf("specify either chan_id or chan_point")
}
if err != nil {
return nil, err
}
// Convert the database's edge format into the network/RPC edge format
// which couples the edge itself along with the directional node
// routing policies of each node involved within the channel.
channelEdge := marshalDBEdge(edgeInfo, edge1, edge2)
return channelEdge, nil
}
// GetNodeInfo returns the latest advertised and aggregate authenticated
// channel information for the specified node identified by its public key.
func (r *rpcServer) GetNodeInfo(ctx context.Context,
in *lnrpc.NodeInfoRequest) (*lnrpc.NodeInfo, error) {
graph := r.server.graphDB
// First, parse the hex-encoded public key into a full in-memory public
// key object we can work with for querying.
pubKey, err := route.NewVertexFromStr(in.PubKey)
if err != nil {
return nil, err
}
// With the public key decoded, attempt to fetch the node corresponding
// to this public key. If the node cannot be found, then an error will
// be returned.
node, err := graph.FetchLightningNode(pubKey)
switch {
case errors.Is(err, graphdb.ErrGraphNodeNotFound):
return nil, status.Error(codes.NotFound, err.Error())
case err != nil:
return nil, err
}
// With the node obtained, we'll now iterate through all its out going
// edges to gather some basic statistics about its out going channels.
var (
numChannels uint32
totalCapacity btcutil.Amount
channels []*lnrpc.ChannelEdge
)
err = graph.ForEachNodeChannel(node.PubKeyBytes,
func(_ kvdb.RTx, edge *models.ChannelEdgeInfo,
c1, c2 *models.ChannelEdgePolicy) error {
numChannels++
totalCapacity += edge.Capacity
// Only populate the node's channels if the user
// requested them.
if in.IncludeChannels {
// Do not include unannounced channels - private
// channels or public channels whose
// authentication proof were not confirmed yet.
if edge.AuthProof == nil {
return nil
}
// Convert the database's edge format into the
// network/RPC edge format.
channelEdge := marshalDBEdge(edge, c1, c2)
channels = append(channels, channelEdge)
}
return nil
},
)
if err != nil {
return nil, err
}
return &lnrpc.NodeInfo{
Node: marshalNode(node),
NumChannels: numChannels,
TotalCapacity: int64(totalCapacity),
Channels: channels,
}, nil
}
func marshalNode(node *models.LightningNode) *lnrpc.LightningNode {
nodeAddrs := make([]*lnrpc.NodeAddress, len(node.Addresses))
for i, addr := range node.Addresses {
nodeAddr := &lnrpc.NodeAddress{
Network: addr.Network(),
Addr: addr.String(),
}
nodeAddrs[i] = nodeAddr
}
features := invoicesrpc.CreateRPCFeatures(node.Features)
customRecords := marshalExtraOpaqueData(node.ExtraOpaqueData)
return &lnrpc.LightningNode{
LastUpdate: uint32(node.LastUpdate.Unix()),
PubKey: hex.EncodeToString(node.PubKeyBytes[:]),
Addresses: nodeAddrs,
Alias: node.Alias,
Color: graphdb.EncodeHexColor(node.Color),
Features: features,
CustomRecords: customRecords,
}
}
// QueryRoutes attempts to query the daemons' Channel Router for a possible
// route to a target destination capable of carrying a specific amount of
// satoshis within the route's flow. The returned route contains the full
// details required to craft and send an HTLC, also including the necessary
// information that should be present within the Sphinx packet encapsulated
// within the HTLC.
//
// TODO(roasbeef): should return a slice of routes in reality
// - create separate PR to send based on well formatted route
func (r *rpcServer) QueryRoutes(ctx context.Context,
in *lnrpc.QueryRoutesRequest) (*lnrpc.QueryRoutesResponse, error) {
return r.routerBackend.QueryRoutes(ctx, in)
}
// GetNetworkInfo returns some basic stats about the known channel graph from
// the PoV of the node.
func (r *rpcServer) GetNetworkInfo(ctx context.Context,
_ *lnrpc.NetworkInfoRequest) (*lnrpc.NetworkInfo, error) {
graph := r.server.graphDB
var (
numNodes uint32
numChannels uint32
maxChanOut uint32
totalNetworkCapacity btcutil.Amount
minChannelSize btcutil.Amount = math.MaxInt64
maxChannelSize btcutil.Amount
medianChanSize btcutil.Amount
)
// We'll use this map to de-duplicate channels during our traversal.
// This is needed since channels are directional, so there will be two
// edges for each channel within the graph.
seenChans := make(map[uint64]struct{})
// We also keep a list of all encountered capacities, in order to
// calculate the median channel size.
var allChans []btcutil.Amount
// We'll run through all the known nodes in the within our view of the
// network, tallying up the total number of nodes, and also gathering
// each node so we can measure the graph diameter and degree stats
// below.
err := graph.ForEachNodeCached(func(node route.Vertex,
edges map[uint64]*graphdb.DirectedChannel) error {
// Increment the total number of nodes with each iteration.
numNodes++
// For each channel we'll compute the out degree of each node,
// and also update our running tallies of the min/max channel
// capacity, as well as the total channel capacity. We pass
// through the db transaction from the outer view so we can
// re-use it within this inner view.
var outDegree uint32
for _, edge := range edges {
// Bump up the out degree for this node for each
// channel encountered.
outDegree++
// If we've already seen this channel, then we'll
// return early to ensure that we don't double-count
// stats.
if _, ok := seenChans[edge.ChannelID]; ok {
return nil
}
// Compare the capacity of this channel against the
// running min/max to see if we should update the
// extrema.
chanCapacity := edge.Capacity
if chanCapacity < minChannelSize {
minChannelSize = chanCapacity
}
if chanCapacity > maxChannelSize {
maxChannelSize = chanCapacity
}
// Accumulate the total capacity of this channel to the
// network wide-capacity.
totalNetworkCapacity += chanCapacity
numChannels++
seenChans[edge.ChannelID] = struct{}{}
allChans = append(allChans, edge.Capacity)
}
// Finally, if the out degree of this node is greater than what
// we've seen so far, update the maxChanOut variable.
if outDegree > maxChanOut {
maxChanOut = outDegree
}
return nil
})
if err != nil {
return nil, err
}
// Query the graph for the current number of zombie channels.
numZombies, err := graph.NumZombies()
if err != nil {
return nil, err
}
// Find the median.
medianChanSize = autopilot.Median(allChans)
// If we don't have any channels, then reset the minChannelSize to zero
// to avoid outputting NaN in encoded JSON.
if numChannels == 0 {
minChannelSize = 0
}
// Graph diameter.
channelGraph := autopilot.ChannelGraphFromCachedDatabase(graph)
simpleGraph, err := autopilot.NewSimpleGraph(channelGraph)
if err != nil {
return nil, err
}
start := time.Now()
diameter := simpleGraph.DiameterRadialCutoff()
rpcsLog.Infof("elapsed time for diameter (%d) calculation: %v", diameter,
time.Since(start))
// TODO(roasbeef): also add oldest channel?
netInfo := &lnrpc.NetworkInfo{
GraphDiameter: diameter,
MaxOutDegree: maxChanOut,
AvgOutDegree: float64(2*numChannels) / float64(numNodes),
NumNodes: numNodes,
NumChannels: numChannels,
TotalNetworkCapacity: int64(totalNetworkCapacity),
AvgChannelSize: float64(totalNetworkCapacity) / float64(numChannels),
MinChannelSize: int64(minChannelSize),
MaxChannelSize: int64(maxChannelSize),
MedianChannelSizeSat: int64(medianChanSize),
NumZombieChans: numZombies,
}
// Similarly, if we don't have any channels, then we'll also set the
// average channel size to zero in order to avoid weird JSON encoding
// outputs.
if numChannels == 0 {
netInfo.AvgChannelSize = 0
}
return netInfo, nil
}
// StopDaemon will send a shutdown request to the interrupt handler, triggering
// a graceful shutdown of the daemon.
func (r *rpcServer) StopDaemon(_ context.Context,
_ *lnrpc.StopRequest) (*lnrpc.StopResponse, error) {
// Before we even consider a shutdown, are we currently in recovery
// mode? We don't want to allow shutting down during recovery because
// that would mean the user would have to manually continue the rescan
// process next time by using `lncli unlock --recovery_window X`
// otherwise some funds wouldn't be picked up.
isRecoveryMode, progress, err := r.server.cc.Wallet.GetRecoveryInfo()
if err != nil {
return nil, fmt.Errorf("unable to get wallet recovery info: %w",
err)
}
if isRecoveryMode && progress < 1 {
return nil, fmt.Errorf("wallet recovery in progress, cannot " +
"shut down, please wait until rescan finishes")
}
r.interceptor.RequestShutdown()
return &lnrpc.StopResponse{
Status: "shutdown initiated, check logs for progress",
}, nil
}
// SubscribeChannelGraph launches a streaming RPC that allows the caller to
// receive notifications upon any changes the channel graph topology from the
// review of the responding node. Events notified include: new nodes coming
// online, nodes updating their authenticated attributes, new channels being
// advertised, updates in the routing policy for a directional channel edge,
// and finally when prior channels are closed on-chain.
func (r *rpcServer) SubscribeChannelGraph(req *lnrpc.GraphTopologySubscription,
updateStream lnrpc.Lightning_SubscribeChannelGraphServer) error {
// First, we start by subscribing to a new intent to receive
// notifications from the channel router.
client, err := r.server.graphDB.SubscribeTopology()
if err != nil {
return err
}
// Ensure that the resources for the topology update client is cleaned
// up once either the server, or client exists.
defer client.Cancel()
for {
select {
// A new update has been sent by the channel router, we'll
// marshal it into the form expected by the gRPC client, then
// send it off.
case topChange, ok := <-client.TopologyChanges:
// If the second value from the channel read is nil,
// then this means that the channel router is exiting
// or the notification client was canceled. So we'll
// exit early.
if !ok {
return errors.New("server shutting down")
}
// Convert the struct from the channel router into the
// form expected by the gRPC service then send it off
// to the client.
graphUpdate := marshallTopologyChange(topChange)
if err := updateStream.Send(graphUpdate); err != nil {
return err
}
// The response stream's context for whatever reason has been
// closed. If context is closed by an exceeded deadline
// we will return an error.
case <-updateStream.Context().Done():
if errors.Is(updateStream.Context().Err(), context.Canceled) {
return nil
}
return updateStream.Context().Err()
// The server is quitting, so we'll exit immediately. Returning
// nil will close the clients read end of the stream.
case <-r.quit:
return nil
}
}
}
// marshallTopologyChange performs a mapping from the topology change struct
// returned by the router to the form of notifications expected by the current
// gRPC service.
func marshallTopologyChange(
topChange *graphdb.TopologyChange) *lnrpc.GraphTopologyUpdate {
// encodeKey is a simple helper function that converts a live public
// key into a hex-encoded version of the compressed serialization for
// the public key.
encodeKey := func(k *btcec.PublicKey) string {
return hex.EncodeToString(k.SerializeCompressed())
}
nodeUpdates := make([]*lnrpc.NodeUpdate, len(topChange.NodeUpdates))
for i, nodeUpdate := range topChange.NodeUpdates {
nodeAddrs := make(
[]*lnrpc.NodeAddress, 0, len(nodeUpdate.Addresses),
)
for _, addr := range nodeUpdate.Addresses {
nodeAddr := &lnrpc.NodeAddress{
Network: addr.Network(),
Addr: addr.String(),
}
nodeAddrs = append(nodeAddrs, nodeAddr)
}
addrs := make([]string, len(nodeUpdate.Addresses))
for i, addr := range nodeUpdate.Addresses {
addrs[i] = addr.String()
}
nodeUpdates[i] = &lnrpc.NodeUpdate{
Addresses: addrs,
NodeAddresses: nodeAddrs,
IdentityKey: encodeKey(nodeUpdate.IdentityKey),
Alias: nodeUpdate.Alias,
Color: nodeUpdate.Color,
Features: invoicesrpc.CreateRPCFeatures(
nodeUpdate.Features,
),
}
}
channelUpdates := make([]*lnrpc.ChannelEdgeUpdate, len(topChange.ChannelEdgeUpdates))
for i, channelUpdate := range topChange.ChannelEdgeUpdates {
customRecords := marshalExtraOpaqueData(
channelUpdate.ExtraOpaqueData,
)
inboundFee := extractInboundFeeSafe(
channelUpdate.ExtraOpaqueData,
)
channelUpdates[i] = &lnrpc.ChannelEdgeUpdate{
ChanId: channelUpdate.ChanID,
ChanPoint: &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: channelUpdate.ChanPoint.Hash[:],
},
OutputIndex: channelUpdate.ChanPoint.Index,
},
Capacity: int64(channelUpdate.Capacity),
RoutingPolicy: &lnrpc.RoutingPolicy{
TimeLockDelta: uint32(
channelUpdate.TimeLockDelta,
),
MinHtlc: int64(
channelUpdate.MinHTLC,
),
MaxHtlcMsat: uint64(
channelUpdate.MaxHTLC,
),
FeeBaseMsat: int64(
channelUpdate.BaseFee,
),
FeeRateMilliMsat: int64(
channelUpdate.FeeRate,
),
Disabled: channelUpdate.Disabled,
InboundFeeBaseMsat: inboundFee.BaseFee,
InboundFeeRateMilliMsat: inboundFee.FeeRate,
CustomRecords: customRecords,
},
AdvertisingNode: encodeKey(channelUpdate.AdvertisingNode),
ConnectingNode: encodeKey(channelUpdate.ConnectingNode),
}
}
closedChans := make([]*lnrpc.ClosedChannelUpdate, len(topChange.ClosedChannels))
for i, closedChan := range topChange.ClosedChannels {
closedChans[i] = &lnrpc.ClosedChannelUpdate{
ChanId: closedChan.ChanID,
Capacity: int64(closedChan.Capacity),
ClosedHeight: closedChan.ClosedHeight,
ChanPoint: &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: closedChan.ChanPoint.Hash[:],
},
OutputIndex: closedChan.ChanPoint.Index,
},
}
}
return &lnrpc.GraphTopologyUpdate{
NodeUpdates: nodeUpdates,
ChannelUpdates: channelUpdates,
ClosedChans: closedChans,
}
}
// ListPayments returns a list of outgoing payments determined by a paginated
// database query.
func (r *rpcServer) ListPayments(ctx context.Context,
req *lnrpc.ListPaymentsRequest) (*lnrpc.ListPaymentsResponse, error) {
// If both dates are set, we check that the start date is less than the
// end date, otherwise we'll get an empty result.
if req.CreationDateStart != 0 && req.CreationDateEnd != 0 {
if req.CreationDateStart >= req.CreationDateEnd {
return nil, fmt.Errorf("start date(%v) must be before "+
"end date(%v)", req.CreationDateStart,
req.CreationDateEnd)
}
}
query := channeldb.PaymentsQuery{
IndexOffset: req.IndexOffset,
MaxPayments: req.MaxPayments,
Reversed: req.Reversed,
IncludeIncomplete: req.IncludeIncomplete,
CountTotal: req.CountTotalPayments,
CreationDateStart: int64(req.CreationDateStart),
CreationDateEnd: int64(req.CreationDateEnd),
}
// If the maximum number of payments wasn't specified, then we'll
// default to return the maximal number of payments representable.
if req.MaxPayments == 0 {
query.MaxPayments = math.MaxUint64
}
paymentsQuerySlice, err := r.server.miscDB.QueryPayments(query)
if err != nil {
return nil, err
}
paymentsResp := &lnrpc.ListPaymentsResponse{
LastIndexOffset: paymentsQuerySlice.LastIndexOffset,
FirstIndexOffset: paymentsQuerySlice.FirstIndexOffset,
TotalNumPayments: paymentsQuerySlice.TotalCount,
}
for _, payment := range paymentsQuerySlice.Payments {
payment := payment
rpcPayment, err := r.routerBackend.MarshallPayment(payment)
if err != nil {
return nil, err
}
paymentsResp.Payments = append(
paymentsResp.Payments, rpcPayment,
)
}
return paymentsResp, nil
}
// DeletePayment deletes a payment from the DB given its payment hash. If
// failedHtlcsOnly is set, only failed HTLC attempts of the payment will be
// deleted.
func (r *rpcServer) DeletePayment(ctx context.Context,
req *lnrpc.DeletePaymentRequest) (
*lnrpc.DeletePaymentResponse, error) {
hash, err := lntypes.MakeHash(req.PaymentHash)
if err != nil {
return nil, err
}
rpcsLog.Infof("[DeletePayment] payment_identifier=%v, "+
"failed_htlcs_only=%v", hash, req.FailedHtlcsOnly)
err = r.server.miscDB.DeletePayment(hash, req.FailedHtlcsOnly)
if err != nil {
return nil, err
}
return &lnrpc.DeletePaymentResponse{
Status: "payment deleted",
}, nil
}
// DeleteAllPayments deletes all outgoing payments from DB.
func (r *rpcServer) DeleteAllPayments(ctx context.Context,
req *lnrpc.DeleteAllPaymentsRequest) (
*lnrpc.DeleteAllPaymentsResponse, error) {
switch {
// Since this is a destructive operation, at least one of the options
// must be set to true.
case !req.AllPayments && !req.FailedPaymentsOnly &&
!req.FailedHtlcsOnly:
return nil, fmt.Errorf("at least one of the options " +
"`all_payments`, `failed_payments_only`, or " +
"`failed_htlcs_only` must be set to true")
// `all_payments` cannot be true with `failed_payments_only` or
// `failed_htlcs_only`. `all_payments` includes all records, making
// these options contradictory.
case req.AllPayments &&
(req.FailedPaymentsOnly || req.FailedHtlcsOnly):
return nil, fmt.Errorf("`all_payments` cannot be set to true " +
"while either `failed_payments_only` or " +
"`failed_htlcs_only` is also set to true")
}
rpcsLog.Infof("[DeleteAllPayments] failed_payments_only=%v, "+
"failed_htlcs_only=%v", req.FailedPaymentsOnly,
req.FailedHtlcsOnly)
numDeletedPayments, err := r.server.miscDB.DeletePayments(
req.FailedPaymentsOnly, req.FailedHtlcsOnly,
)
if err != nil {
return nil, err
}
return &lnrpc.DeleteAllPaymentsResponse{
Status: fmt.Sprintf("%v payments deleted, failed_htlcs_only=%v",
numDeletedPayments, req.FailedHtlcsOnly),
}, nil
}
// DebugLevel allows a caller to programmatically set the logging verbosity of
// lnd. The logging can be targeted according to a coarse daemon-wide logging
// level, or in a granular fashion to specify the logging for a target
// sub-system.
func (r *rpcServer) DebugLevel(ctx context.Context,
req *lnrpc.DebugLevelRequest) (*lnrpc.DebugLevelResponse, error) {
// If show is set, then we simply print out the list of available
// sub-systems.
if req.Show {
return &lnrpc.DebugLevelResponse{
SubSystems: strings.Join(
r.cfg.SubLogMgr.SupportedSubsystems(), " ",
),
}, nil
}
rpcsLog.Infof("[debuglevel] changing debug level to: %v", req.LevelSpec)
// Otherwise, we'll attempt to set the logging level using the
// specified level spec.
err := build.ParseAndSetDebugLevels(req.LevelSpec, r.cfg.SubLogMgr)
if err != nil {
return nil, err
}
subLoggers := r.cfg.SubLogMgr.SubLoggers()
// Sort alphabetically by subsystem name.
var tags []string
for t := range subLoggers {
tags = append(tags, t)
}
sort.Strings(tags)
// Create the log levels string.
var logLevels []string
for _, t := range tags {
logLevels = append(logLevels, fmt.Sprintf("%s=%s", t,
subLoggers[t].Level().String()))
}
logLevelsString := strings.Join(logLevels, ", ")
// Propagate the new config level to the main config struct.
r.cfg.DebugLevel = logLevelsString
return &lnrpc.DebugLevelResponse{
SubSystems: logLevelsString,
}, nil
}
// DecodePayReq takes an encoded payment request string and attempts to decode
// it, returning a full description of the conditions encoded within the
// payment request.
func (r *rpcServer) DecodePayReq(ctx context.Context,
req *lnrpc.PayReqString) (*lnrpc.PayReq, error) {
rpcsLog.Tracef("[decodepayreq] decoding: %v", req.PayReq)
// Fist we'll attempt to decode the payment request string, if the
// request is invalid or the checksum doesn't match, then we'll exit
// here with an error.
payReq, err := zpay32.Decode(req.PayReq, r.cfg.ActiveNetParams.Params)
if err != nil {
return nil, err
}
// Let the fields default to empty strings.
desc := ""
if payReq.Description != nil {
desc = *payReq.Description
}
descHash := []byte("")
if payReq.DescriptionHash != nil {
descHash = payReq.DescriptionHash[:]
}
fallbackAddr := ""
if payReq.FallbackAddr != nil {
fallbackAddr = payReq.FallbackAddr.String()
}
// Expiry time will default to 3600 seconds if not specified
// explicitly.
expiry := int64(payReq.Expiry().Seconds())
// Convert between the `lnrpc` and `routing` types.
routeHints := invoicesrpc.CreateRPCRouteHints(payReq.RouteHints)
blindedPaymentPaths, err := invoicesrpc.CreateRPCBlindedPayments(
payReq.BlindedPaymentPaths,
)
if err != nil {
return nil, err
}
var amtSat, amtMsat int64
if payReq.MilliSat != nil {
amtSat = int64(payReq.MilliSat.ToSatoshis())
amtMsat = int64(*payReq.MilliSat)
}
// Extract the payment address from the payment request, if present.
paymentAddr := payReq.PaymentAddr.UnwrapOr([32]byte{})
dest := payReq.Destination.SerializeCompressed()
return &lnrpc.PayReq{
Destination: hex.EncodeToString(dest),
PaymentHash: hex.EncodeToString(payReq.PaymentHash[:]),
NumSatoshis: amtSat,
NumMsat: amtMsat,
Timestamp: payReq.Timestamp.Unix(),
Description: desc,
DescriptionHash: hex.EncodeToString(descHash[:]),
FallbackAddr: fallbackAddr,
Expiry: expiry,
CltvExpiry: int64(payReq.MinFinalCLTVExpiry()),
RouteHints: routeHints,
BlindedPaths: blindedPaymentPaths,
PaymentAddr: paymentAddr[:],
Features: invoicesrpc.CreateRPCFeatures(payReq.Features),
}, nil
}
// feeBase is the fixed point that fee rate computation are performed over.
// Nodes on the network advertise their fee rate using this point as a base.
// This means that the minimal possible fee rate if 1e-6, or 0.000001, or
// 0.0001%.
const feeBase float64 = 1000000
// FeeReport allows the caller to obtain a report detailing the current fee
// schedule enforced by the node globally for each channel.
func (r *rpcServer) FeeReport(ctx context.Context,
_ *lnrpc.FeeReportRequest) (*lnrpc.FeeReportResponse, error) {
channelGraph := r.server.graphDB
selfNode, err := channelGraph.SourceNode()
if err != nil {
return nil, err
}
var feeReports []*lnrpc.ChannelFeeReport
err = channelGraph.ForEachNodeChannel(selfNode.PubKeyBytes,
func(_ kvdb.RTx, chanInfo *models.ChannelEdgeInfo,
edgePolicy, _ *models.ChannelEdgePolicy) error {
// Self node should always have policies for its
// channels.
if edgePolicy == nil {
return fmt.Errorf("no policy for outgoing "+
"channel %v ", chanInfo.ChannelID)
}
// We'll compute the effective fee rate by converting
// from a fixed point fee rate to a floating point fee
// rate. The fee rate field in the database the amount
// of mSAT charged per 1mil mSAT sent, so will divide by
// this to get the proper fee rate.
feeRateFixedPoint :=
edgePolicy.FeeProportionalMillionths
feeRate := float64(feeRateFixedPoint) / feeBase
// Decode inbound fee from extra data.
var inboundFee lnwire.Fee
_, err := edgePolicy.ExtraOpaqueData.ExtractRecords(
&inboundFee,
)
if err != nil {
return err
}
// TODO(roasbeef): also add stats for revenue for each
// channel
feeReports = append(feeReports, &lnrpc.ChannelFeeReport{
ChanId: chanInfo.ChannelID,
ChannelPoint: chanInfo.ChannelPoint.String(),
BaseFeeMsat: int64(edgePolicy.FeeBaseMSat),
FeePerMil: int64(feeRateFixedPoint),
FeeRate: feeRate,
InboundBaseFeeMsat: inboundFee.BaseFee,
InboundFeePerMil: inboundFee.FeeRate,
})
return nil
},
)
if err != nil {
return nil, err
}
fwdEventLog := r.server.miscDB.ForwardingLog()
// computeFeeSum is a helper function that computes the total fees for
// a particular time slice described by a forwarding event query.
computeFeeSum := func(query channeldb.ForwardingEventQuery) (lnwire.MilliSatoshi, error) {
var totalFees lnwire.MilliSatoshi
// We'll continue to fetch the next query and accumulate the
// fees until the next query returns no events.
for {
timeSlice, err := fwdEventLog.Query(query)
if err != nil {
return 0, err
}
// If the timeslice is empty, then we'll return as
// we've retrieved all the entries in this range.
if len(timeSlice.ForwardingEvents) == 0 {
break
}
// Otherwise, we'll tally up an accumulate the total
// fees for this time slice.
for _, event := range timeSlice.ForwardingEvents {
fee := event.AmtIn - event.AmtOut
totalFees += fee
}
// We'll now take the last offset index returned as
// part of this response, and modify our query to start
// at this index. This has a pagination effect in the
// case that our query bounds has more than 100k
// entries.
query.IndexOffset = timeSlice.LastIndexOffset
}
return totalFees, nil
}
now := time.Now()
// Before we perform the queries below, we'll instruct the switch to
// flush any pending events to disk. This ensure we get a complete
// snapshot at this particular time.
if err := r.server.htlcSwitch.FlushForwardingEvents(); err != nil {
return nil, fmt.Errorf("unable to flush forwarding "+
"events: %v", err)
}
// In addition to returning the current fee schedule for each channel.
// We'll also perform a series of queries to obtain the total fees
// earned over the past day, week, and month.
dayQuery := channeldb.ForwardingEventQuery{
StartTime: now.Add(-time.Hour * 24),
EndTime: now,
NumMaxEvents: 1000,
}
dayFees, err := computeFeeSum(dayQuery)
if err != nil {
return nil, fmt.Errorf("unable to retrieve day fees: %w", err)
}
weekQuery := channeldb.ForwardingEventQuery{
StartTime: now.Add(-time.Hour * 24 * 7),
EndTime: now,
NumMaxEvents: 1000,
}
weekFees, err := computeFeeSum(weekQuery)
if err != nil {
return nil, fmt.Errorf("unable to retrieve day fees: %w", err)
}
monthQuery := channeldb.ForwardingEventQuery{
StartTime: now.Add(-time.Hour * 24 * 30),
EndTime: now,
NumMaxEvents: 1000,
}
monthFees, err := computeFeeSum(monthQuery)
if err != nil {
return nil, fmt.Errorf("unable to retrieve day fees: %w", err)
}
return &lnrpc.FeeReportResponse{
ChannelFees: feeReports,
DayFeeSum: uint64(dayFees.ToSatoshis()),
WeekFeeSum: uint64(weekFees.ToSatoshis()),
MonthFeeSum: uint64(monthFees.ToSatoshis()),
}, nil
}
// minFeeRate is the smallest permitted fee rate within the network. This is
// derived by the fact that fee rates are computed using a fixed point of
// 1,000,000. As a result, the smallest representable fee rate is 1e-6, or
// 0.000001, or 0.0001%.
const minFeeRate = 1e-6
// UpdateChannelPolicy allows the caller to update the channel forwarding policy
// for all channels globally, or a particular channel.
func (r *rpcServer) UpdateChannelPolicy(ctx context.Context,
req *lnrpc.PolicyUpdateRequest) (*lnrpc.PolicyUpdateResponse, error) {
var targetChans []wire.OutPoint
switch scope := req.Scope.(type) {
// If the request is targeting all active channels, then we don't need
// target any channels by their channel point.
case *lnrpc.PolicyUpdateRequest_Global:
// Otherwise, we're targeting an individual channel by its channel
// point.
case *lnrpc.PolicyUpdateRequest_ChanPoint:
txid, err := lnrpc.GetChanPointFundingTxid(scope.ChanPoint)
if err != nil {
return nil, err
}
targetChans = append(targetChans, wire.OutPoint{
Hash: *txid,
Index: scope.ChanPoint.OutputIndex,
})
default:
return nil, fmt.Errorf("unknown scope: %v", scope)
}
var feeRateFixed uint32
switch {
// The request should use either the fee rate in percent, or the new
// ppm rate, but not both.
case req.FeeRate != 0 && req.FeeRatePpm != 0:
errMsg := "cannot set both FeeRate and FeeRatePpm at the " +
"same time"
return nil, status.Errorf(codes.InvalidArgument, errMsg)
// If the request is using fee_rate.
case req.FeeRate != 0:
// As a sanity check, if the fee isn't zero, we'll ensure that
// the passed fee rate is below 1e-6, or the lowest allowed
// non-zero fee rate expressible within the protocol.
if req.FeeRate != 0 && req.FeeRate < minFeeRate {
return nil, fmt.Errorf("fee rate of %v is too "+
"small, min fee rate is %v", req.FeeRate,
minFeeRate)
}
// We'll also need to convert the floating point fee rate we
// accept over RPC to the fixed point rate that we use within
// the protocol. We do this by multiplying the passed fee rate
// by the fee base. This gives us the fixed point, scaled by 1
// million that's used within the protocol.
//
// Because of the inaccurate precision of the IEEE 754
// standard, we need to round the product of feerate and
// feebase.
feeRateFixed = uint32(math.Round(req.FeeRate * feeBase))
// Otherwise, we use the fee_rate_ppm parameter.
case req.FeeRatePpm != 0:
feeRateFixed = req.FeeRatePpm
}
// We'll also ensure that the user isn't setting a CLTV delta that
// won't give outgoing HTLCs enough time to fully resolve if needed.
if req.TimeLockDelta < minTimeLockDelta {
return nil, fmt.Errorf("time lock delta of %v is too small, "+
"minimum supported is %v", req.TimeLockDelta,
minTimeLockDelta)
} else if req.TimeLockDelta > uint32(MaxTimeLockDelta) {
return nil, fmt.Errorf("time lock delta of %v is too big, "+
"maximum supported is %v", req.TimeLockDelta,
MaxTimeLockDelta)
}
// By default, positive inbound fees are rejected.
if !r.cfg.AcceptPositiveInboundFees && req.InboundFee != nil {
if req.InboundFee.BaseFeeMsat > 0 {
return nil, fmt.Errorf("positive values for inbound "+
"base fee msat are not supported: %v",
req.InboundFee.BaseFeeMsat)
}
if req.InboundFee.FeeRatePpm > 0 {
return nil, fmt.Errorf("positive values for inbound "+
"fee rate ppm are not supported: %v",
req.InboundFee.FeeRatePpm)
}
}
// If no inbound fees have been specified, we indicate with an empty
// option that the previous inbound fee should be retained during the
// edge update.
inboundFee := fn.None[models.InboundFee]()
if req.InboundFee != nil {
inboundFee = fn.Some(models.InboundFee{
Base: req.InboundFee.BaseFeeMsat,
Rate: req.InboundFee.FeeRatePpm,
})
}
baseFeeMsat := lnwire.MilliSatoshi(req.BaseFeeMsat)
feeSchema := routing.FeeSchema{
BaseFee: baseFeeMsat,
FeeRate: feeRateFixed,
InboundFee: inboundFee,
}
maxHtlc := lnwire.MilliSatoshi(req.MaxHtlcMsat)
var minHtlc *lnwire.MilliSatoshi
if req.MinHtlcMsatSpecified {
min := lnwire.MilliSatoshi(req.MinHtlcMsat)
minHtlc = &min
}
chanPolicy := routing.ChannelPolicy{
FeeSchema: feeSchema,
TimeLockDelta: req.TimeLockDelta,
MaxHTLC: maxHtlc,
MinHTLC: minHtlc,
}
rpcsLog.Debugf("[updatechanpolicy] updating channel policy "+
"base_fee=%v, rate_fixed=%v, time_lock_delta: %v, "+
"min_htlc=%v, max_htlc=%v, targets=%v",
req.BaseFeeMsat, feeRateFixed, req.TimeLockDelta,
minHtlc, maxHtlc,
spew.Sdump(targetChans))
// With the scope resolved, we'll now send this to the local channel
// manager so it can propagate the new policy for our target channel(s).
failedUpdates, err := r.server.localChanMgr.UpdatePolicy(chanPolicy,
req.CreateMissingEdge, targetChans...)
if err != nil {
return nil, err
}
return &lnrpc.PolicyUpdateResponse{
FailedUpdates: failedUpdates,
}, nil
}
// ForwardingHistory allows the caller to query the htlcswitch for a record of
// all HTLC's forwarded within the target time range, and integer offset within
// that time range. If no time-range is specified, then the first chunk of the
// past 24 hrs of forwarding history are returned.
// A list of forwarding events are returned. The size of each forwarding event
// is 40 bytes, and the max message size able to be returned in gRPC is 4 MiB.
// In order to safely stay under this max limit, we'll return 50k events per
// response. Each response has the index offset of the last entry. The index
// offset can be provided to the request to allow the caller to skip a series
// of records.
func (r *rpcServer) ForwardingHistory(ctx context.Context,
req *lnrpc.ForwardingHistoryRequest) (*lnrpc.ForwardingHistoryResponse,
error) {
// Before we perform the queries below, we'll instruct the switch to
// flush any pending events to disk. This ensure we get a complete
// snapshot at this particular time.
if err := r.server.htlcSwitch.FlushForwardingEvents(); err != nil {
return nil, fmt.Errorf("unable to flush forwarding "+
"events: %v", err)
}
var (
startTime, endTime time.Time
numEvents uint32
)
// startTime defaults to the Unix epoch (0 unixtime, or
// midnight 01-01-1970).
startTime = time.Unix(int64(req.StartTime), 0)
// If the end time wasn't specified, assume a default end time of now.
if req.EndTime == 0 {
now := time.Now()
endTime = now
} else {
endTime = time.Unix(int64(req.EndTime), 0)
}
// If the number of events wasn't specified, then we'll default to
// returning the last 100 events.
numEvents = req.NumMaxEvents
if numEvents == 0 {
numEvents = 100
}
// Next, we'll map the proto request into a format that is understood by
// the forwarding log.
eventQuery := channeldb.ForwardingEventQuery{
StartTime: startTime,
EndTime: endTime,
IndexOffset: req.IndexOffset,
NumMaxEvents: numEvents,
}
timeSlice, err := r.server.miscDB.ForwardingLog().Query(eventQuery)
if err != nil {
return nil, fmt.Errorf("unable to query forwarding log: %w",
err)
}
// chanToPeerAlias caches previously looked up channel information.
chanToPeerAlias := make(map[lnwire.ShortChannelID]string)
// Helper function to extract a peer's node alias given its SCID.
getRemoteAlias := func(chanID lnwire.ShortChannelID) (string, error) {
// If we'd previously seen this chanID then return the cached
// peer alias.
if peerAlias, ok := chanToPeerAlias[chanID]; ok {
return peerAlias, nil
}
// Else call the server to look up the peer alias.
edge, err := r.GetChanInfo(ctx, &lnrpc.ChanInfoRequest{
ChanId: chanID.ToUint64(),
})
if err != nil {
return "", err
}
remotePub := edge.Node1Pub
if r.selfNode.String() == edge.Node1Pub {
remotePub = edge.Node2Pub
}
vertex, err := route.NewVertexFromStr(remotePub)
if err != nil {
return "", err
}
peer, err := r.server.graphDB.FetchLightningNode(vertex)
if err != nil {
return "", err
}
// Cache the peer alias.
chanToPeerAlias[chanID] = peer.Alias
return peer.Alias, nil
}
// TODO(roasbeef): add settlement latency?
// * use FPE on all records?
// With the events retrieved, we'll now map them into the proper proto
// response.
//
// TODO(roasbeef): show in ns for the outside?
fwdingEvents := make(
[]*lnrpc.ForwardingEvent, len(timeSlice.ForwardingEvents),
)
resp := &lnrpc.ForwardingHistoryResponse{
ForwardingEvents: fwdingEvents,
LastOffsetIndex: timeSlice.LastIndexOffset,
}
for i, event := range timeSlice.ForwardingEvents {
amtInMsat := event.AmtIn
amtOutMsat := event.AmtOut
feeMsat := event.AmtIn - event.AmtOut
resp.ForwardingEvents[i] = &lnrpc.ForwardingEvent{
Timestamp: uint64(event.Timestamp.Unix()),
TimestampNs: uint64(event.Timestamp.UnixNano()),
ChanIdIn: event.IncomingChanID.ToUint64(),
ChanIdOut: event.OutgoingChanID.ToUint64(),
AmtIn: uint64(amtInMsat.ToSatoshis()),
AmtOut: uint64(amtOutMsat.ToSatoshis()),
Fee: uint64(feeMsat.ToSatoshis()),
FeeMsat: uint64(feeMsat),
AmtInMsat: uint64(amtInMsat),
AmtOutMsat: uint64(amtOutMsat),
}
if req.PeerAliasLookup {
aliasIn, err := getRemoteAlias(event.IncomingChanID)
if err != nil {
aliasIn = fmt.Sprintf("unable to lookup peer "+
"alias: %v", err)
}
aliasOut, err := getRemoteAlias(event.OutgoingChanID)
if err != nil {
aliasOut = fmt.Sprintf("unable to lookup peer"+
"alias: %v", err)
}
resp.ForwardingEvents[i].PeerAliasIn = aliasIn
resp.ForwardingEvents[i].PeerAliasOut = aliasOut
}
}
return resp, nil
}
// ExportChannelBackup attempts to return an encrypted static channel backup
// for the target channel identified by it channel point. The backup is
// encrypted with a key generated from the aezeed seed of the user. The
// returned backup can either be restored using the RestoreChannelBackup method
// once lnd is running, or via the InitWallet and UnlockWallet methods from the
// WalletUnlocker service.
func (r *rpcServer) ExportChannelBackup(ctx context.Context,
in *lnrpc.ExportChannelBackupRequest) (*lnrpc.ChannelBackup, error) {
// First, we'll convert the lnrpc channel point into a wire.OutPoint
// that we can manipulate.
txid, err := lnrpc.GetChanPointFundingTxid(in.ChanPoint)
if err != nil {
return nil, err
}
chanPoint := wire.OutPoint{
Hash: *txid,
Index: in.ChanPoint.OutputIndex,
}
// Next, we'll attempt to fetch a channel backup for this channel from
// the database. If this channel has been closed, or the outpoint is
// unknown, then we'll return an error
unpackedBackup, err := chanbackup.FetchBackupForChan(
chanPoint, r.server.chanStateDB, r.server.addrSource,
)
if err != nil {
return nil, err
}
// At this point, we have an unpacked backup (plaintext) so we'll now
// attempt to serialize and encrypt it in order to create a packed
// backup.
packedBackups, err := chanbackup.PackStaticChanBackups(
[]chanbackup.Single{*unpackedBackup},
r.server.cc.KeyRing,
)
if err != nil {
return nil, fmt.Errorf("packing of back ups failed: %w", err)
}
// Before we proceed, we'll ensure that we received a backup for this
// channel, otherwise, we'll bail out.
packedBackup, ok := packedBackups[chanPoint]
if !ok {
return nil, fmt.Errorf("expected single backup for "+
"ChannelPoint(%v), got %v", chanPoint,
len(packedBackup))
}
return &lnrpc.ChannelBackup{
ChanPoint: in.ChanPoint,
ChanBackup: packedBackup,
}, nil
}
// VerifyChanBackup allows a caller to verify the integrity of a channel backup
// snapshot. This method will accept both either a packed Single or a packed
// Multi. Specifying both will result in an error.
func (r *rpcServer) VerifyChanBackup(ctx context.Context,
in *lnrpc.ChanBackupSnapshot) (*lnrpc.VerifyChanBackupResponse, error) {
var (
channels []chanbackup.Single
err error
)
switch {
// If neither a Single or Multi has been specified, then we have nothing
// to verify.
case in.GetSingleChanBackups() == nil && in.GetMultiChanBackup() == nil:
return nil, errors.New("either a Single or Multi channel " +
"backup must be specified")
// Either a Single or a Multi must be specified, but not both.
case in.GetSingleChanBackups() != nil && in.GetMultiChanBackup() != nil:
return nil, errors.New("either a Single or Multi channel " +
"backup must be specified, but not both")
// If a Single is specified then we'll only accept one of them to allow
// the caller to map the valid/invalid state for each individual Single.
case in.GetSingleChanBackups() != nil:
chanBackupsProtos := in.GetSingleChanBackups().ChanBackups
if len(chanBackupsProtos) != 1 {
return nil, errors.New("only one Single is accepted " +
"at a time")
}
// First, we'll convert the raw byte slice into a type we can
// work with a bit better.
chanBackup := chanbackup.PackedSingles(
[][]byte{chanBackupsProtos[0].ChanBackup},
)
// With our PackedSingles created, we'll attempt to unpack the
// backup. If this fails, then we know the backup is invalid for
// some reason.
channels, err = chanBackup.Unpack(r.server.cc.KeyRing)
if err != nil {
return nil, fmt.Errorf("invalid single channel "+
"backup: %v", err)
}
case in.GetMultiChanBackup() != nil:
// We'll convert the raw byte slice into a PackedMulti that we
// can easily work with.
packedMultiBackup := in.GetMultiChanBackup().MultiChanBackup
packedMulti := chanbackup.PackedMulti(packedMultiBackup)
// We'll now attempt to unpack the Multi. If this fails, then we
// know it's invalid.
multi, err := packedMulti.Unpack(r.server.cc.KeyRing)
if err != nil {
return nil, fmt.Errorf("invalid multi channel backup: "+
"%v", err)
}
channels = multi.StaticBackups
}
return &lnrpc.VerifyChanBackupResponse{
ChanPoints: fn.Map(channels, func(c chanbackup.Single) string {
return c.FundingOutpoint.String()
}),
}, nil
}
// createBackupSnapshot converts the passed Single backup into a snapshot which
// contains individual packed single backups, as well as a single packed multi
// backup.
func (r *rpcServer) createBackupSnapshot(backups []chanbackup.Single) (
*lnrpc.ChanBackupSnapshot, error) {
// Once we have the set of back ups, we'll attempt to pack them all
// into a series of single channel backups.
singleChanPackedBackups, err := chanbackup.PackStaticChanBackups(
backups, r.server.cc.KeyRing,
)
if err != nil {
return nil, fmt.Errorf("unable to pack set of chan "+
"backups: %v", err)
}
// Now that we have our set of single packed backups, we'll morph that
// into a form that the proto response requires.
numBackups := len(singleChanPackedBackups)
singleBackupResp := &lnrpc.ChannelBackups{
ChanBackups: make([]*lnrpc.ChannelBackup, 0, numBackups),
}
for chanPoint, singlePackedBackup := range singleChanPackedBackups {
txid := chanPoint.Hash
rpcChanPoint := &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: txid[:],
},
OutputIndex: chanPoint.Index,
}
singleBackupResp.ChanBackups = append(
singleBackupResp.ChanBackups,
&lnrpc.ChannelBackup{
ChanPoint: rpcChanPoint,
ChanBackup: singlePackedBackup,
},
)
}
// In addition, to the set of single chan backups, we'll also create a
// single multi-channel backup which can be serialized into a single
// file for safe storage.
var b bytes.Buffer
unpackedMultiBackup := chanbackup.Multi{
StaticBackups: backups,
}
err = unpackedMultiBackup.PackToWriter(&b, r.server.cc.KeyRing)
if err != nil {
return nil, fmt.Errorf("unable to multi-pack backups: %w", err)
}
multiBackupResp := &lnrpc.MultiChanBackup{
MultiChanBackup: b.Bytes(),
}
for _, singleBackup := range singleBackupResp.ChanBackups {
multiBackupResp.ChanPoints = append(
multiBackupResp.ChanPoints, singleBackup.ChanPoint,
)
}
return &lnrpc.ChanBackupSnapshot{
SingleChanBackups: singleBackupResp,
MultiChanBackup: multiBackupResp,
}, nil
}
// ExportAllChannelBackups returns static channel backups for all existing
// channels known to lnd. A set of regular singular static channel backups for
// each channel are returned. Additionally, a multi-channel backup is returned
// as well, which contains a single encrypted blob containing the backups of
// each channel.
func (r *rpcServer) ExportAllChannelBackups(ctx context.Context,
in *lnrpc.ChanBackupExportRequest) (*lnrpc.ChanBackupSnapshot, error) {
// First, we'll attempt to read back ups for ALL currently opened
// channels from disk.
allUnpackedBackups, err := chanbackup.FetchStaticChanBackups(
r.server.chanStateDB, r.server.addrSource,
)
if err != nil {
return nil, fmt.Errorf("unable to fetch all static chan "+
"backups: %v", err)
}
// With the backups assembled, we'll create a full snapshot.
return r.createBackupSnapshot(allUnpackedBackups)
}
// RestoreChannelBackups accepts a set of singular channel backups, or a single
// encrypted multi-chan backup and attempts to recover any funds remaining
// within the channel. If we're able to unpack the backup, then the new channel
// will be shown under listchannels, as well as pending channels.
func (r *rpcServer) RestoreChannelBackups(ctx context.Context,
in *lnrpc.RestoreChanBackupRequest) (*lnrpc.RestoreBackupResponse, error) {
// The server hasn't yet started, so it won't be able to service any of
// our requests, so we'll bail early here.
if !r.server.Started() {
return nil, ErrServerNotActive
}
// First, we'll make our implementation of the
// chanbackup.ChannelRestorer interface which we'll use to properly
// restore either a set of chanbackup.Single or chanbackup.Multi
// backups.
chanRestorer := &chanDBRestorer{
db: r.server.chanStateDB,
secretKeys: r.server.cc.KeyRing,
chainArb: r.server.chainArb,
}
// We'll accept either a list of Single backups, or a single Multi
// backup which contains several single backups.
var (
numRestored int
err error
)
switch {
case in.GetChanBackups() != nil:
chanBackupsProtos := in.GetChanBackups()
// Now that we know what type of backup we're working with,
// we'll parse them all out into a more suitable format.
packedBackups := make([][]byte, 0, len(chanBackupsProtos.ChanBackups))
for _, chanBackup := range chanBackupsProtos.ChanBackups {
packedBackups = append(
packedBackups, chanBackup.ChanBackup,
)
}
// With our backups obtained, we'll now restore them which will
// write the new backups to disk, and then attempt to connect
// out to any peers that we know of which were our prior
// channel peers.
numRestored, err = chanbackup.UnpackAndRecoverSingles(
chanbackup.PackedSingles(packedBackups),
r.server.cc.KeyRing, chanRestorer, r.server,
)
if err != nil {
return nil, fmt.Errorf("unable to unpack single "+
"backups: %v", err)
}
case in.GetMultiChanBackup() != nil:
packedMultiBackup := in.GetMultiChanBackup()
// With our backups obtained, we'll now restore them which will
// write the new backups to disk, and then attempt to connect
// out to any peers that we know of which were our prior
// channel peers.
packedMulti := chanbackup.PackedMulti(packedMultiBackup)
numRestored, err = chanbackup.UnpackAndRecoverMulti(
packedMulti, r.server.cc.KeyRing, chanRestorer,
r.server,
)
if err != nil {
return nil, fmt.Errorf("unable to unpack chan "+
"backup: %v", err)
}
}
return &lnrpc.RestoreBackupResponse{
NumRestored: uint32(numRestored),
}, nil
}
// SubscribeChannelBackups allows a client to sub-subscribe to the most up to
// date information concerning the state of all channel back ups. Each time a
// new channel is added, we return the new set of channels, along with a
// multi-chan backup containing the backup info for all channels. Each time a
// channel is closed, we send a new update, which contains new new chan back
// ups, but the updated set of encrypted multi-chan backups with the closed
// channel(s) removed.
func (r *rpcServer) SubscribeChannelBackups(req *lnrpc.ChannelBackupSubscription,
updateStream lnrpc.Lightning_SubscribeChannelBackupsServer) error {
// First, we'll subscribe to the primary channel notifier so we can
// obtain events for new pending/opened/closed channels.
chanSubscription, err := r.server.channelNotifier.SubscribeChannelEvents()
if err != nil {
return err
}
defer chanSubscription.Cancel()
for {
select {
// A new event has been sent by the channel notifier, we'll
// assemble, then sling out a new event to the client.
case e := <-chanSubscription.Updates():
// TODO(roasbeef): batch dispatch ntnfs
switch e.(type) {
// We only care about new/closed channels, so we'll
// skip any events for active/inactive channels.
// To make the subscription behave the same way as the
// synchronous call and the file based backup, we also
// include pending channels in the update.
case channelnotifier.ActiveChannelEvent:
continue
case channelnotifier.InactiveChannelEvent:
continue
case channelnotifier.ActiveLinkEvent:
continue
case channelnotifier.InactiveLinkEvent:
continue
}
// Now that we know the channel state has changed,
// we'll obtains the current set of single channel
// backups from disk.
chanBackups, err := chanbackup.FetchStaticChanBackups(
r.server.chanStateDB, r.server.addrSource,
)
if err != nil {
return fmt.Errorf("unable to fetch all "+
"static chan backups: %v", err)
}
// With our backups obtained, we'll pack them into a
// snapshot and send them back to the client.
backupSnapshot, err := r.createBackupSnapshot(
chanBackups,
)
if err != nil {
return err
}
err = updateStream.Send(backupSnapshot)
if err != nil {
return err
}
// The response stream's context for whatever reason has been
// closed. If context is closed by an exceeded deadline we will
// return an error.
case <-updateStream.Context().Done():
if errors.Is(updateStream.Context().Err(), context.Canceled) {
return nil
}
return updateStream.Context().Err()
case <-r.quit:
return nil
}
}
}
// ChannelAcceptor dispatches a bi-directional streaming RPC in which
// OpenChannel requests are sent to the client and the client responds with
// a boolean that tells LND whether or not to accept the channel. This allows
// node operators to specify their own criteria for accepting inbound channels
// through a single persistent connection.
func (r *rpcServer) ChannelAcceptor(stream lnrpc.Lightning_ChannelAcceptorServer) error {
chainedAcceptor := r.chanPredicate
// Create a new RPCAcceptor which will send requests into the
// newRequests channel when it receives them.
rpcAcceptor := chanacceptor.NewRPCAcceptor(
stream.Recv, stream.Send, r.cfg.AcceptorTimeout,
r.cfg.ActiveNetParams.Params, r.quit,
)
// Add the RPCAcceptor to the ChainedAcceptor and defer its removal.
id := chainedAcceptor.AddAcceptor(rpcAcceptor)
defer chainedAcceptor.RemoveAcceptor(id)
// Run the rpc acceptor, which will accept requests for channel
// acceptance decisions from our chained acceptor, send them to the
// channel acceptor and listen for and report responses. This function
// blocks, and will exit if the rpcserver receives the instruction to
// shutdown, or the client cancels.
return rpcAcceptor.Run()
}
// BakeMacaroon allows the creation of a new macaroon with custom read and write
// permissions. No first-party caveats are added since this can be done offline.
// If the --allow-external-permissions flag is set, the RPC will allow
// external permissions that LND is not aware of.
func (r *rpcServer) BakeMacaroon(ctx context.Context,
req *lnrpc.BakeMacaroonRequest) (*lnrpc.BakeMacaroonResponse, error) {
// If the --no-macaroons flag is used to start lnd, the macaroon service
// is not initialized. Therefore we can't bake new macaroons.
if r.macService == nil {
return nil, errMacaroonDisabled
}
helpMsg := fmt.Sprintf("supported actions are %v, supported entities "+
"are %v", validActions, validEntities)
// Don't allow empty permission list as it doesn't make sense to have
// a macaroon that is not allowed to access any RPC.
if len(req.Permissions) == 0 {
return nil, fmt.Errorf("permission list cannot be empty. "+
"specify at least one action/entity pair. %s", helpMsg)
}
// Validate and map permission struct used by gRPC to the one used by
// the bakery. If the --allow-external-permissions flag is set, we
// will not validate, but map.
requestedPermissions := make([]bakery.Op, len(req.Permissions))
for idx, op := range req.Permissions {
if req.AllowExternalPermissions {
requestedPermissions[idx] = bakery.Op{
Entity: op.Entity,
Action: op.Action,
}
continue
}
if !stringInSlice(op.Entity, validEntities) {
return nil, fmt.Errorf("invalid permission entity. %s",
helpMsg)
}
// Either we have the special entity "uri" which specifies a
// full gRPC URI or we have one of the pre-defined actions.
if op.Entity == macaroons.PermissionEntityCustomURI {
allPermissions := r.interceptorChain.Permissions()
_, ok := allPermissions[op.Action]
if !ok {
return nil, fmt.Errorf("invalid permission " +
"action, must be an existing URI in " +
"the format /package.Service/" +
"MethodName")
}
} else if !stringInSlice(op.Action, validActions) {
return nil, fmt.Errorf("invalid permission action. %s",
helpMsg)
}
requestedPermissions[idx] = bakery.Op{
Entity: op.Entity,
Action: op.Action,
}
}
// Convert root key id from uint64 to bytes. Because the
// DefaultRootKeyID is a digit 0 expressed in a byte slice of a string
// "0", we will keep the IDs in the same format - all must be numeric,
// and must be a byte slice of string value of the digit, e.g.,
// uint64(123) to string(123).
rootKeyID := []byte(strconv.FormatUint(req.RootKeyId, 10))
// Bake new macaroon with the given permissions and send it binary
// serialized and hex encoded to the client.
newMac, err := r.macService.NewMacaroon(
ctx, rootKeyID, requestedPermissions...,
)
if err != nil {
return nil, err
}
newMacBytes, err := newMac.M().MarshalBinary()
if err != nil {
return nil, err
}
resp := &lnrpc.BakeMacaroonResponse{}
resp.Macaroon = hex.EncodeToString(newMacBytes)
return resp, nil
}
// ListMacaroonIDs returns a list of macaroon root key IDs in use.
func (r *rpcServer) ListMacaroonIDs(ctx context.Context,
req *lnrpc.ListMacaroonIDsRequest) (
*lnrpc.ListMacaroonIDsResponse, error) {
// If the --no-macaroons flag is used to start lnd, the macaroon service
// is not initialized. Therefore we can't show any IDs.
if r.macService == nil {
return nil, errMacaroonDisabled
}
rootKeyIDByteSlice, err := r.macService.ListMacaroonIDs(ctx)
if err != nil {
return nil, err
}
var rootKeyIDs []uint64
for _, value := range rootKeyIDByteSlice {
// Convert bytes into uint64.
id, err := strconv.ParseUint(string(value), 10, 64)
if err != nil {
return nil, err
}
rootKeyIDs = append(rootKeyIDs, id)
}
return &lnrpc.ListMacaroonIDsResponse{RootKeyIds: rootKeyIDs}, nil
}
// DeleteMacaroonID removes a specific macaroon ID.
func (r *rpcServer) DeleteMacaroonID(ctx context.Context,
req *lnrpc.DeleteMacaroonIDRequest) (
*lnrpc.DeleteMacaroonIDResponse, error) {
// If the --no-macaroons flag is used to start lnd, the macaroon service
// is not initialized. Therefore we can't delete any IDs.
if r.macService == nil {
return nil, errMacaroonDisabled
}
// Convert root key id from uint64 to bytes. Because the
// DefaultRootKeyID is a digit 0 expressed in a byte slice of a string
// "0", we will keep the IDs in the same format - all must be digit, and
// must be a byte slice of string value of the digit.
rootKeyID := []byte(strconv.FormatUint(req.RootKeyId, 10))
deletedIDBytes, err := r.macService.DeleteMacaroonID(ctx, rootKeyID)
if err != nil {
return nil, err
}
return &lnrpc.DeleteMacaroonIDResponse{
// If the root key ID doesn't exist, it won't be deleted. We
// will return a response with deleted = false, otherwise true.
Deleted: deletedIDBytes != nil,
}, nil
}
// ListPermissions lists all RPC method URIs and their required macaroon
// permissions to access them.
func (r *rpcServer) ListPermissions(_ context.Context,
_ *lnrpc.ListPermissionsRequest) (*lnrpc.ListPermissionsResponse,
error) {
permissionMap := make(map[string]*lnrpc.MacaroonPermissionList)
for uri, perms := range r.interceptorChain.Permissions() {
rpcPerms := make([]*lnrpc.MacaroonPermission, len(perms))
for idx, perm := range perms {
rpcPerms[idx] = &lnrpc.MacaroonPermission{
Entity: perm.Entity,
Action: perm.Action,
}
}
permissionMap[uri] = &lnrpc.MacaroonPermissionList{
Permissions: rpcPerms,
}
}
return &lnrpc.ListPermissionsResponse{
MethodPermissions: permissionMap,
}, nil
}
// CheckMacaroonPermissions checks the caveats and permissions of a macaroon.
func (r *rpcServer) CheckMacaroonPermissions(ctx context.Context,
req *lnrpc.CheckMacPermRequest) (*lnrpc.CheckMacPermResponse, error) {
// Turn grpc macaroon permission into bakery.Op for the server to
// process.
permissions := make([]bakery.Op, len(req.Permissions))
for idx, perm := range req.Permissions {
permissions[idx] = bakery.Op{
Entity: perm.Entity,
Action: perm.Action,
}
}
err := r.macService.CheckMacAuth(
ctx, req.Macaroon, permissions, req.FullMethod,
)
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
return &lnrpc.CheckMacPermResponse{
Valid: true,
}, nil
}
// FundingStateStep is an advanced funding related call that allows the caller
// to either execute some preparatory steps for a funding workflow, or manually
// progress a funding workflow. The primary way a funding flow is identified is
// via its pending channel ID. As an example, this method can be used to
// specify that we're expecting a funding flow for a particular pending channel
// ID, for which we need to use specific parameters. Alternatively, this can
// be used to interactively drive PSBT signing for funding for partially
// complete funding transactions.
func (r *rpcServer) FundingStateStep(ctx context.Context,
in *lnrpc.FundingTransitionMsg) (*lnrpc.FundingStateStepResp, error) {
var pendingChanID [32]byte
switch {
// If this is a message to register a new shim that is an external
// channel point, then we'll contact the wallet to register this new
// shim. A user will use this method to register a new channel funding
// workflow which has already been partially negotiated outside of the
// core protocol.
case in.GetShimRegister() != nil &&
in.GetShimRegister().GetChanPointShim() != nil:
rpcShimIntent := in.GetShimRegister().GetChanPointShim()
// Using the rpc shim as a template, we'll construct a new
// chanfunding.Assembler that is able to express proper
// formulation of this expected channel.
shimAssembler, err := newFundingShimAssembler(
rpcShimIntent, false, r.server.cc.KeyRing,
)
if err != nil {
return nil, err
}
req := &chanfunding.Request{
RemoteAmt: btcutil.Amount(rpcShimIntent.Amt),
}
shimIntent, err := shimAssembler.ProvisionChannel(req)
if err != nil {
return nil, err
}
// Once we have the intent, we'll register it with the wallet.
// Once we receive an incoming funding request that uses this
// pending channel ID, then this shim will be dispatched in
// place of our regular funding workflow.
copy(pendingChanID[:], rpcShimIntent.PendingChanId)
err = r.server.cc.Wallet.RegisterFundingIntent(
pendingChanID, shimIntent,
)
if err != nil {
return nil, err
}
// There is no need to register a PSBT shim before opening the channel,
// even though our RPC message structure allows for it. Inform the user
// by returning a proper error instead of just doing nothing.
case in.GetShimRegister() != nil &&
in.GetShimRegister().GetPsbtShim() != nil:
return nil, fmt.Errorf("PSBT shim must only be sent when " +
"opening a channel")
// If this is a transition to cancel an existing shim, then we'll pass
// this message along to the wallet, informing it that the intent no
// longer needs to be considered and should be cleaned up.
case in.GetShimCancel() != nil:
rpcsLog.Debugf("Canceling funding shim for pending_id=%x",
in.GetShimCancel().PendingChanId)
copy(pendingChanID[:], in.GetShimCancel().PendingChanId)
err := r.server.cc.Wallet.CancelFundingIntent(pendingChanID)
if err != nil {
return nil, err
}
// If this is a transition to verify the PSBT for an existing shim,
// we'll do so and then store the verified PSBT for later so we can
// compare it to the final, signed one.
case in.GetPsbtVerify() != nil:
rpcsLog.Debugf("Verifying PSBT for pending_id=%x",
in.GetPsbtVerify().PendingChanId)
copy(pendingChanID[:], in.GetPsbtVerify().PendingChanId)
packet, err := psbt.NewFromRawBytes(
bytes.NewReader(in.GetPsbtVerify().FundedPsbt), false,
)
if err != nil {
return nil, fmt.Errorf("error parsing psbt: %w", err)
}
err = r.server.cc.Wallet.PsbtFundingVerify(
pendingChanID, packet, in.GetPsbtVerify().SkipFinalize,
)
if err != nil {
return nil, err
}
// If this is a transition to finalize the PSBT funding flow, we compare
// the final PSBT to the previously verified one and if nothing
// unexpected was changed, continue the channel opening process.
case in.GetPsbtFinalize() != nil:
msg := in.GetPsbtFinalize()
rpcsLog.Debugf("Finalizing PSBT for pending_id=%x",
msg.PendingChanId)
copy(pendingChanID[:], in.GetPsbtFinalize().PendingChanId)
var (
packet *psbt.Packet
rawTx *wire.MsgTx
err error
)
// Either the signed PSBT or the raw transaction need to be set
// but not both at the same time.
switch {
case len(msg.SignedPsbt) > 0 && len(msg.FinalRawTx) > 0:
return nil, fmt.Errorf("cannot set both signed PSBT " +
"and final raw TX at the same time")
case len(msg.SignedPsbt) > 0:
packet, err = psbt.NewFromRawBytes(
bytes.NewReader(in.GetPsbtFinalize().SignedPsbt),
false,
)
if err != nil {
return nil, fmt.Errorf("error parsing psbt: %w",
err)
}
case len(msg.FinalRawTx) > 0:
rawTx = &wire.MsgTx{}
err = rawTx.Deserialize(bytes.NewReader(msg.FinalRawTx))
if err != nil {
return nil, fmt.Errorf("error parsing final "+
"raw TX: %v", err)
}
default:
return nil, fmt.Errorf("PSBT or raw transaction to " +
"finalize missing")
}
err = r.server.cc.Wallet.PsbtFundingFinalize(
pendingChanID, packet, rawTx,
)
if err != nil {
return nil, err
}
}
// TODO(roasbeef): extend PendingChannels to also show shims
// TODO(roasbeef): return resulting state? also add a method to query
// current state?
return &lnrpc.FundingStateStepResp{}, nil
}
// RegisterRPCMiddleware adds a new gRPC middleware to the interceptor chain. A
// gRPC middleware is software component external to lnd that aims to add
// additional business logic to lnd by observing/intercepting/validating
// incoming gRPC client requests and (if needed) replacing/overwriting outgoing
// messages before they're sent to the client. When registering the middleware
// must identify itself and indicate what custom macaroon caveats it wants to
// be responsible for. Only requests that contain a macaroon with that specific
// custom caveat are then sent to the middleware for inspection. As a security
// measure, _no_ middleware can intercept requests made with _unencumbered_
// macaroons!
func (r *rpcServer) RegisterRPCMiddleware(
stream lnrpc.Lightning_RegisterRPCMiddlewareServer) error {
// This is a security critical functionality and needs to be enabled
// specifically by the user.
if !r.cfg.RPCMiddleware.Enable {
return fmt.Errorf("RPC middleware not enabled in config")
}
// When registering a middleware the first message being sent from the
// middleware must be a registration message containing its name and the
// custom caveat it wants to register for.
var (
registerChan = make(chan *lnrpc.MiddlewareRegistration, 1)
registerDoneChan = make(chan struct{})
errChan = make(chan error, 1)
)
ctxc, cancel := context.WithTimeout(
stream.Context(), r.cfg.RPCMiddleware.InterceptTimeout,
)
defer cancel()
// Read the first message in a goroutine because the Recv method blocks
// until the message arrives.
go func() {
msg, err := stream.Recv()
if err != nil {
errChan <- err
return
}
registerChan <- msg.GetRegister()
}()
// Wait for the initial message to arrive or time out if it takes too
// long.
var registerMsg *lnrpc.MiddlewareRegistration
select {
case registerMsg = <-registerChan:
if registerMsg == nil {
return fmt.Errorf("invalid initial middleware " +
"registration message")
}
case err := <-errChan:
return fmt.Errorf("error receiving initial middleware "+
"registration message: %v", err)
case <-ctxc.Done():
return ctxc.Err()
case <-r.quit:
return ErrServerShuttingDown
}
// Make sure the registration is valid.
const nameMinLength = 5
if len(registerMsg.MiddlewareName) < nameMinLength {
return fmt.Errorf("invalid middleware name, use descriptive "+
"name of at least %d characters", nameMinLength)
}
readOnly := registerMsg.ReadOnlyMode
caveatName := registerMsg.CustomMacaroonCaveatName
switch {
case readOnly && len(caveatName) > 0:
return fmt.Errorf("cannot set read-only and custom caveat " +
"name at the same time")
case !readOnly && len(caveatName) < nameMinLength:
return fmt.Errorf("need to set either custom caveat name "+
"of at least %d characters or read-only mode",
nameMinLength)
}
middleware := rpcperms.NewMiddlewareHandler(
registerMsg.MiddlewareName,
caveatName, readOnly, stream.Recv, stream.Send,
r.cfg.RPCMiddleware.InterceptTimeout,
r.cfg.ActiveNetParams.Params, r.quit,
)
// Add the RPC middleware to the interceptor chain and defer its
// removal.
if err := r.interceptorChain.RegisterMiddleware(middleware); err != nil {
return fmt.Errorf("error registering middleware: %w", err)
}
defer r.interceptorChain.RemoveMiddleware(registerMsg.MiddlewareName)
// Send a message to the client to indicate that the registration has
// successfully completed.
regCompleteMsg := &lnrpc.RPCMiddlewareRequest{
InterceptType: &lnrpc.RPCMiddlewareRequest_RegComplete{
RegComplete: true,
},
}
// Send the message in a goroutine because the Send method blocks until
// the message is read by the client.
go func() {
err := stream.Send(regCompleteMsg)
if err != nil {
errChan <- err
return
}
close(registerDoneChan)
}()
select {
case err := <-errChan:
return fmt.Errorf("error sending middleware registration "+
"complete message: %v", err)
case <-ctxc.Done():
return ctxc.Err()
case <-r.quit:
return ErrServerShuttingDown
case <-registerDoneChan:
}
return middleware.Run()
}
// SendCustomMessage sends a custom peer message.
func (r *rpcServer) SendCustomMessage(_ context.Context,
req *lnrpc.SendCustomMessageRequest) (*lnrpc.SendCustomMessageResponse,
error) {
peer, err := route.NewVertexFromBytes(req.Peer)
if err != nil {
return nil, err
}
err = r.server.SendCustomMessage(
peer, lnwire.MessageType(req.Type), req.Data,
)
switch {
case errors.Is(err, ErrPeerNotConnected):
return nil, status.Error(codes.NotFound, err.Error())
case err != nil:
return nil, err
}
return &lnrpc.SendCustomMessageResponse{
Status: "message sent successfully",
}, nil
}
// SubscribeCustomMessages subscribes to a stream of incoming custom peer
// messages.
func (r *rpcServer) SubscribeCustomMessages(
_ *lnrpc.SubscribeCustomMessagesRequest,
server lnrpc.Lightning_SubscribeCustomMessagesServer) error {
client, err := r.server.SubscribeCustomMessages()
if err != nil {
return err
}
defer client.Cancel()
for {
select {
case <-client.Quit():
return errors.New("shutdown")
case <-server.Context().Done():
return server.Context().Err()
case update := <-client.Updates():
customMsg := update.(*CustomMessage)
err := server.Send(&lnrpc.CustomMessage{
Peer: customMsg.Peer[:],
Data: customMsg.Msg.Data,
Type: uint32(customMsg.Msg.Type),
})
if err != nil {
return err
}
}
}
}
// ListAliases returns the set of all aliases we have ever allocated along with
// their base SCIDs and possibly a separate confirmed SCID in the case of
// zero-conf.
func (r *rpcServer) ListAliases(_ context.Context,
_ *lnrpc.ListAliasesRequest) (*lnrpc.ListAliasesResponse, error) {
// Fetch the map of all aliases.
mapAliases := r.server.aliasMgr.ListAliases()
// Fill out the response. This does not include the zero-conf confirmed
// SCID. Doing so would require more database lookups, and it can be
// cross-referenced with the output of ListChannels/ClosedChannels.
resp := &lnrpc.ListAliasesResponse{
AliasMaps: make([]*lnrpc.AliasMap, 0),
}
// Now we need to parse the created mappings into an rpc response.
resp.AliasMaps = lnrpc.MarshalAliasMap(mapAliases)
return resp, nil
}
// rpcInitiator returns the correct lnrpc initiator for channels where we have
// a record of the opening channel.
func rpcInitiator(isInitiator bool) lnrpc.Initiator {
if isInitiator {
return lnrpc.Initiator_INITIATOR_LOCAL
}
return lnrpc.Initiator_INITIATOR_REMOTE
}
// chainSyncInfo wraps info about the best block and whether the system is
// synced to that block.
type chainSyncInfo struct {
// isSynced specifies whether the whole system is considered synced.
// When true, it means the following subsystems are at the best height
// reported by the chain backend,
// - wallet.
// - channel graph.
// - blockbeat dispatcher.
isSynced bool
// bestHeight is the current height known to the chain backend.
bestHeight int32
// blockHash is the hash of the current block known to the chain
// backend.
blockHash chainhash.Hash
// timestamp is the block's timestamp the wallet has synced to.
timestamp int64
}
// getChainSyncInfo queries the chain backend, the wallet, the channel router
// and the blockbeat dispatcher to determine the best block and whether the
// system is considered synced.
func (r *rpcServer) getChainSyncInfo() (*chainSyncInfo, error) {
bestHash, bestHeight, err := r.server.cc.ChainIO.GetBestBlock()
if err != nil {
return nil, fmt.Errorf("unable to get best block info: %w", err)
}
isSynced, bestHeaderTimestamp, err := r.server.cc.Wallet.IsSynced()
if err != nil {
return nil, fmt.Errorf("unable to sync PoV of the wallet "+
"with current best block in the main chain: %v", err)
}
// Create an info to be returned.
info := &chainSyncInfo{
isSynced: isSynced,
bestHeight: bestHeight,
blockHash: *bestHash,
timestamp: bestHeaderTimestamp,
}
// Exit early if the wallet is not synced.
if !isSynced {
rpcsLog.Debugf("Wallet is not synced to height %v yet",
bestHeight)
return info, nil
}
// If the router does full channel validation, it has a lot of work to
// do for each block. So it might be possible that it isn't yet up to
// date with the most recent block, even if the wallet is. This can
// happen in environments with high CPU load (such as parallel itests).
// Since the `synced_to_chain` flag in the response of this call is used
// by many wallets (and also our itests) to make sure everything's up to
// date, we add the router's state to it. So the flag will only toggle
// to true once the router was also able to catch up.
if !r.cfg.Routing.AssumeChannelValid {
routerHeight := r.server.graphBuilder.SyncedHeight()
isSynced = uint32(bestHeight) == routerHeight
}
// Exit early if the channel graph is not synced.
if !isSynced {
rpcsLog.Debugf("Graph is not synced to height %v yet",
bestHeight)
return info, nil
}
// Given the wallet and the channel router are synced, we now check
// whether the blockbeat dispatcher is synced.
height := r.server.blockbeatDispatcher.CurrentHeight()
// Overwrite isSynced and return.
info.isSynced = height == bestHeight
if !info.isSynced {
rpcsLog.Debugf("Blockbeat is not synced to height %v yet",
bestHeight)
}
return info, nil
}
package lnd
import (
"bytes"
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"math/big"
prand "math/rand"
"net"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/connmgr"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/go-errors/errors"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/aliasmgr"
"github.com/lightningnetwork/lnd/autopilot"
"github.com/lightningnetwork/lnd/brontide"
"github.com/lightningnetwork/lnd/chainio"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/chanacceptor"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/chanfitness"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channelnotifier"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/cluster"
"github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/discovery"
"github.com/lightningnetwork/lnd/feature"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/graph"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/healthcheck"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnencrypt"
"github.com/lightningnetwork/lnd/lnpeer"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
"github.com/lightningnetwork/lnd/lnwallet/rpcwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/nat"
"github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/peer"
"github.com/lightningnetwork/lnd/peernotifier"
"github.com/lightningnetwork/lnd/pool"
"github.com/lightningnetwork/lnd/queue"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/routing/localchans"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/subscribe"
"github.com/lightningnetwork/lnd/sweep"
"github.com/lightningnetwork/lnd/ticker"
"github.com/lightningnetwork/lnd/tor"
"github.com/lightningnetwork/lnd/walletunlocker"
"github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/wtclient"
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
"github.com/lightningnetwork/lnd/watchtower/wtserver"
)
const (
// defaultMinPeers is the minimum number of peers nodes should always be
// connected to.
defaultMinPeers = 3
// defaultStableConnDuration is a floor under which all reconnection
// attempts will apply exponential randomized backoff. Connections
// durations exceeding this value will be eligible to have their
// backoffs reduced.
defaultStableConnDuration = 10 * time.Minute
// numInstantInitReconnect specifies how many persistent peers we should
// always attempt outbound connections to immediately. After this value
// is surpassed, the remaining peers will be randomly delayed using
// maxInitReconnectDelay.
numInstantInitReconnect = 10
// maxInitReconnectDelay specifies the maximum delay in seconds we will
// apply in attempting to reconnect to persistent peers on startup. The
// value used or a particular peer will be chosen between 0s and this
// value.
maxInitReconnectDelay = 30
// multiAddrConnectionStagger is the number of seconds to wait between
// attempting to a peer with each of its advertised addresses.
multiAddrConnectionStagger = 10 * time.Second
)
var (
// ErrPeerNotConnected signals that the server has no connection to the
// given peer.
ErrPeerNotConnected = errors.New("peer is not connected")
// ErrServerNotActive indicates that the server has started but hasn't
// fully finished the startup process.
ErrServerNotActive = errors.New("server is still in the process of " +
"starting")
// ErrServerShuttingDown indicates that the server is in the process of
// gracefully exiting.
ErrServerShuttingDown = errors.New("server is shutting down")
// MaxFundingAmount is a soft-limit of the maximum channel size
// currently accepted within the Lightning Protocol. This is
// defined in BOLT-0002, and serves as an initial precautionary limit
// while implementations are battle tested in the real world.
//
// At the moment, this value depends on which chain is active. It is set
// to the value under the Bitcoin chain as default.
//
// TODO(roasbeef): add command line param to modify.
MaxFundingAmount = funding.MaxBtcFundingAmount
// EndorsementExperimentEnd is the time after which nodes should stop
// propagating experimental endorsement signals.
//
// Per blip04: January 1, 2026 12:00:00 AM UTC in unix seconds.
EndorsementExperimentEnd = time.Unix(1767225600, 0)
// ErrGossiperBan is one of the errors that can be returned when we
// attempt to finalize a connection to a remote peer.
ErrGossiperBan = errors.New("gossiper has banned remote's key")
// ErrNoMoreRestrictedAccessSlots is one of the errors that can be
// returned when we attempt to finalize a connection. It means that
// this peer has no pending-open, open, or closed channels with us and
// are already at our connection ceiling for a peer with this access
// status.
ErrNoMoreRestrictedAccessSlots = errors.New("no more restricted slots")
// ErrNoPeerScore is returned when we expect to find a score in
// peerScores, but one does not exist.
ErrNoPeerScore = errors.New("peer score not found")
// ErrNoPendingPeerInfo is returned when we couldn't find any pending
// peer info.
ErrNoPendingPeerInfo = errors.New("no pending peer info")
)
// errPeerAlreadyConnected is an error returned by the server when we're
// commanded to connect to a peer, but they're already connected.
type errPeerAlreadyConnected struct {
peer *peer.Brontide
}
// Error returns the human readable version of this error type.
//
// NOTE: Part of the error interface.
func (e *errPeerAlreadyConnected) Error() string {
return fmt.Sprintf("already connected to peer: %v", e.peer)
}
// peerAccessStatus denotes the p2p access status of a given peer. This will be
// used to assign peer ban scores that determine an action the server will
// take.
type peerAccessStatus int
const (
// peerStatusRestricted indicates that the peer only has access to the
// limited number of "free" reserved slots.
peerStatusRestricted peerAccessStatus = iota
// peerStatusTemporary indicates that the peer only has temporary p2p
// access to the server.
peerStatusTemporary
// peerStatusProtected indicates that the peer has been granted
// permanent p2p access to the server. The peer can still have its
// access revoked.
peerStatusProtected
)
// peerSlotStatus determines whether a peer gets access to one of our free
// slots or gets to bypass this safety mechanism.
type peerSlotStatus struct {
// state determines which privileges the peer has with our server.
state peerAccessStatus
}
// server is the main server of the Lightning Network Daemon. The server houses
// global state pertaining to the wallet, database, and the rpcserver.
// Additionally, the server is also used as a central messaging bus to interact
// with any of its companion objects.
type server struct {
active int32 // atomic
stopping int32 // atomic
start sync.Once
stop sync.Once
cfg *Config
implCfg *ImplementationCfg
// identityECDH is an ECDH capable wrapper for the private key used
// to authenticate any incoming connections.
identityECDH keychain.SingleKeyECDH
// identityKeyLoc is the key locator for the above wrapped identity key.
identityKeyLoc keychain.KeyLocator
// nodeSigner is an implementation of the MessageSigner implementation
// that's backed by the identity private key of the running lnd node.
nodeSigner *netann.NodeSigner
chanStatusMgr *netann.ChanStatusManager
// listenAddrs is the list of addresses the server is currently
// listening on.
listenAddrs []net.Addr
// torController is a client that will communicate with a locally
// running Tor server. This client will handle initiating and
// authenticating the connection to the Tor server, automatically
// creating and setting up onion services, etc.
torController *tor.Controller
// natTraversal is the specific NAT traversal technique used to
// automatically set up port forwarding rules in order to advertise to
// the network that the node is accepting inbound connections.
natTraversal nat.Traversal
// lastDetectedIP is the last IP detected by the NAT traversal technique
// above. This IP will be watched periodically in a goroutine in order
// to handle dynamic IP changes.
lastDetectedIP net.IP
mu sync.RWMutex
// peersByPub is a map of the active peers.
//
// NOTE: The key used here is the raw bytes of the peer's public key to
// string conversion, which means it cannot be printed using `%s` as it
// will just print the binary.
//
// TODO(yy): Use the hex string instead.
peersByPub map[string]*peer.Brontide
inboundPeers map[string]*peer.Brontide
outboundPeers map[string]*peer.Brontide
peerConnectedListeners map[string][]chan<- lnpeer.Peer
peerDisconnectedListeners map[string][]chan<- struct{}
// TODO(yy): the Brontide.Start doesn't know this value, which means it
// will continue to send messages even if there are no active channels
// and the value below is false. Once it's pruned, all its connections
// will be closed, thus the Brontide.Start will return an error.
persistentPeers map[string]bool
persistentPeersBackoff map[string]time.Duration
persistentPeerAddrs map[string][]*lnwire.NetAddress
persistentConnReqs map[string][]*connmgr.ConnReq
persistentRetryCancels map[string]chan struct{}
// peerErrors keeps a set of peer error buffers for peers that have
// disconnected from us. This allows us to track historic peer errors
// over connections. The string of the peer's compressed pubkey is used
// as a key for this map.
peerErrors map[string]*queue.CircularBuffer
// ignorePeerTermination tracks peers for which the server has initiated
// a disconnect. Adding a peer to this map causes the peer termination
// watcher to short circuit in the event that peers are purposefully
// disconnected.
ignorePeerTermination map[*peer.Brontide]struct{}
// scheduledPeerConnection maps a pubkey string to a callback that
// should be executed in the peerTerminationWatcher the prior peer with
// the same pubkey exits. This allows the server to wait until the
// prior peer has cleaned up successfully, before adding the new peer
// intended to replace it.
scheduledPeerConnection map[string]func()
// pongBuf is a shared pong reply buffer we'll use across all active
// peer goroutines. We know the max size of a pong message
// (lnwire.MaxPongBytes), so we can allocate this ahead of time, and
// avoid allocations each time we need to send a pong message.
pongBuf []byte
cc *chainreg.ChainControl
fundingMgr *funding.Manager
graphDB *graphdb.ChannelGraph
chanStateDB *channeldb.ChannelStateDB
addrSource channeldb.AddrSource
// miscDB is the DB that contains all "other" databases within the main
// channel DB that haven't been separated out yet.
miscDB *channeldb.DB
invoicesDB invoices.InvoiceDB
aliasMgr *aliasmgr.Manager
htlcSwitch *htlcswitch.Switch
interceptableSwitch *htlcswitch.InterceptableSwitch
invoices *invoices.InvoiceRegistry
invoiceHtlcModifier *invoices.HtlcModificationInterceptor
channelNotifier *channelnotifier.ChannelNotifier
peerNotifier *peernotifier.PeerNotifier
htlcNotifier *htlcswitch.HtlcNotifier
witnessBeacon contractcourt.WitnessBeacon
breachArbitrator *contractcourt.BreachArbitrator
missionController *routing.MissionController
defaultMC *routing.MissionControl
graphBuilder *graph.Builder
chanRouter *routing.ChannelRouter
controlTower routing.ControlTower
authGossiper *discovery.AuthenticatedGossiper
localChanMgr *localchans.Manager
utxoNursery *contractcourt.UtxoNursery
sweeper *sweep.UtxoSweeper
chainArb *contractcourt.ChainArbitrator
sphinx *hop.OnionProcessor
towerClientMgr *wtclient.Manager
connMgr *connmgr.ConnManager
sigPool *lnwallet.SigPool
writePool *pool.Write
readPool *pool.Read
tlsManager *TLSManager
// featureMgr dispatches feature vectors for various contexts within the
// daemon.
featureMgr *feature.Manager
// currentNodeAnn is the node announcement that has been broadcast to
// the network upon startup, if the attributes of the node (us) has
// changed since last start.
currentNodeAnn *lnwire.NodeAnnouncement
// chansToRestore is the set of channels that upon starting, the server
// should attempt to restore/recover.
chansToRestore walletunlocker.ChannelsToRecover
// chanSubSwapper is a sub-system that will ensure our on-disk channel
// backups are consistent at all times. It interacts with the
// channelNotifier to be notified of newly opened and closed channels.
chanSubSwapper *chanbackup.SubSwapper
// chanEventStore tracks the behaviour of channels and their remote peers to
// provide insights into their health and performance.
chanEventStore *chanfitness.ChannelEventStore
hostAnn *netann.HostAnnouncer
// livenessMonitor monitors that lnd has access to critical resources.
livenessMonitor *healthcheck.Monitor
customMessageServer *subscribe.Server
// txPublisher is a publisher with fee-bumping capability.
txPublisher *sweep.TxPublisher
// blockbeatDispatcher is a block dispatcher that notifies subscribers
// of new blocks.
blockbeatDispatcher *chainio.BlockbeatDispatcher
// peerAccessMan implements peer access controls.
peerAccessMan *accessMan
quit chan struct{}
wg sync.WaitGroup
}
// updatePersistentPeerAddrs subscribes to topology changes and stores
// advertised addresses for any NodeAnnouncements from our persisted peers.
func (s *server) updatePersistentPeerAddrs() error {
graphSub, err := s.graphDB.SubscribeTopology()
if err != nil {
return err
}
s.wg.Add(1)
go func() {
defer func() {
graphSub.Cancel()
s.wg.Done()
}()
for {
select {
case <-s.quit:
return
case topChange, ok := <-graphSub.TopologyChanges:
// If the router is shutting down, then we will
// as well.
if !ok {
return
}
for _, update := range topChange.NodeUpdates {
pubKeyStr := string(
update.IdentityKey.
SerializeCompressed(),
)
// We only care about updates from
// our persistentPeers.
s.mu.RLock()
_, ok := s.persistentPeers[pubKeyStr]
s.mu.RUnlock()
if !ok {
continue
}
addrs := make([]*lnwire.NetAddress, 0,
len(update.Addresses))
for _, addr := range update.Addresses {
addrs = append(addrs,
&lnwire.NetAddress{
IdentityKey: update.IdentityKey,
Address: addr,
ChainNet: s.cfg.ActiveNetParams.Net,
},
)
}
s.mu.Lock()
// Update the stored addresses for this
// to peer to reflect the new set.
s.persistentPeerAddrs[pubKeyStr] = addrs
// If there are no outstanding
// connection requests for this peer
// then our work is done since we are
// not currently trying to connect to
// them.
if len(s.persistentConnReqs[pubKeyStr]) == 0 {
s.mu.Unlock()
continue
}
s.mu.Unlock()
s.connectToPersistentPeer(pubKeyStr)
}
}
}
}()
return nil
}
// CustomMessage is a custom message that is received from a peer.
type CustomMessage struct {
// Peer is the peer pubkey
Peer [33]byte
// Msg is the custom wire message.
Msg *lnwire.Custom
}
// parseAddr parses an address from its string format to a net.Addr.
func parseAddr(address string, netCfg tor.Net) (net.Addr, error) {
var (
host string
port int
)
// Split the address into its host and port components.
h, p, err := net.SplitHostPort(address)
if err != nil {
// If a port wasn't specified, we'll assume the address only
// contains the host so we'll use the default port.
host = address
port = defaultPeerPort
} else {
// Otherwise, we'll note both the host and ports.
host = h
portNum, err := strconv.Atoi(p)
if err != nil {
return nil, err
}
port = portNum
}
if tor.IsOnionHost(host) {
return &tor.OnionAddr{OnionService: host, Port: port}, nil
}
// If the host is part of a TCP address, we'll use the network
// specific ResolveTCPAddr function in order to resolve these
// addresses over Tor in order to prevent leaking your real IP
// address.
hostPort := net.JoinHostPort(host, strconv.Itoa(port))
return netCfg.ResolveTCPAddr("tcp", hostPort)
}
// noiseDial is a factory function which creates a connmgr compliant dialing
// function by returning a closure which includes the server's identity key.
func noiseDial(idKey keychain.SingleKeyECDH,
netCfg tor.Net, timeout time.Duration) func(net.Addr) (net.Conn, error) {
return func(a net.Addr) (net.Conn, error) {
lnAddr := a.(*lnwire.NetAddress)
return brontide.Dial(idKey, lnAddr, timeout, netCfg.Dial)
}
}
// newServer creates a new instance of the server which is to listen using the
// passed listener address.
func newServer(cfg *Config, listenAddrs []net.Addr,
dbs *DatabaseInstances, cc *chainreg.ChainControl,
nodeKeyDesc *keychain.KeyDescriptor,
chansToRestore walletunlocker.ChannelsToRecover,
chanPredicate chanacceptor.ChannelAcceptor,
torController *tor.Controller, tlsManager *TLSManager,
leaderElector cluster.LeaderElector,
implCfg *ImplementationCfg) (*server, error) {
var (
err error
nodeKeyECDH = keychain.NewPubKeyECDH(*nodeKeyDesc, cc.KeyRing)
// We just derived the full descriptor, so we know the public
// key is set on it.
nodeKeySigner = keychain.NewPubKeyMessageSigner(
nodeKeyDesc.PubKey, nodeKeyDesc.KeyLocator, cc.KeyRing,
)
)
var serializedPubKey [33]byte
copy(serializedPubKey[:], nodeKeyDesc.PubKey.SerializeCompressed())
netParams := cfg.ActiveNetParams.Params
// Initialize the sphinx router.
replayLog := htlcswitch.NewDecayedLog(
dbs.DecayedLogDB, cc.ChainNotifier,
)
sphinxRouter := sphinx.NewRouter(nodeKeyECDH, replayLog)
writeBufferPool := pool.NewWriteBuffer(
pool.DefaultWriteBufferGCInterval,
pool.DefaultWriteBufferExpiryInterval,
)
writePool := pool.NewWrite(
writeBufferPool, cfg.Workers.Write, pool.DefaultWorkerTimeout,
)
readBufferPool := pool.NewReadBuffer(
pool.DefaultReadBufferGCInterval,
pool.DefaultReadBufferExpiryInterval,
)
readPool := pool.NewRead(
readBufferPool, cfg.Workers.Read, pool.DefaultWorkerTimeout,
)
// If the taproot overlay flag is set, but we don't have an aux funding
// controller, then we'll exit as this is incompatible.
if cfg.ProtocolOptions.TaprootOverlayChans &&
implCfg.AuxFundingController.IsNone() {
return nil, fmt.Errorf("taproot overlay flag set, but not " +
"aux controllers")
}
//nolint:ll
featureMgr, err := feature.NewManager(feature.Config{
NoTLVOnion: cfg.ProtocolOptions.LegacyOnion(),
NoStaticRemoteKey: cfg.ProtocolOptions.NoStaticRemoteKey(),
NoAnchors: cfg.ProtocolOptions.NoAnchorCommitments(),
NoWumbo: !cfg.ProtocolOptions.Wumbo(),
NoScriptEnforcementLease: cfg.ProtocolOptions.NoScriptEnforcementLease(),
NoKeysend: !cfg.AcceptKeySend,
NoOptionScidAlias: !cfg.ProtocolOptions.ScidAlias(),
NoZeroConf: !cfg.ProtocolOptions.ZeroConf(),
NoAnySegwit: cfg.ProtocolOptions.NoAnySegwit(),
CustomFeatures: cfg.ProtocolOptions.CustomFeatures(),
NoTaprootChans: !cfg.ProtocolOptions.TaprootChans,
NoTaprootOverlay: !cfg.ProtocolOptions.TaprootOverlayChans,
NoRouteBlinding: cfg.ProtocolOptions.NoRouteBlinding(),
NoExperimentalEndorsement: cfg.ProtocolOptions.NoExperimentalEndorsement(),
NoQuiescence: cfg.ProtocolOptions.NoQuiescence(),
NoRbfCoopClose: !cfg.ProtocolOptions.RbfCoopClose,
})
if err != nil {
return nil, err
}
invoiceHtlcModifier := invoices.NewHtlcModificationInterceptor()
registryConfig := invoices.RegistryConfig{
FinalCltvRejectDelta: lncfg.DefaultFinalCltvRejectDelta,
HtlcHoldDuration: invoices.DefaultHtlcHoldDuration,
Clock: clock.NewDefaultClock(),
AcceptKeySend: cfg.AcceptKeySend,
AcceptAMP: cfg.AcceptAMP,
GcCanceledInvoicesOnStartup: cfg.GcCanceledInvoicesOnStartup,
GcCanceledInvoicesOnTheFly: cfg.GcCanceledInvoicesOnTheFly,
KeysendHoldTime: cfg.KeysendHoldTime,
HtlcInterceptor: invoiceHtlcModifier,
}
addrSource := channeldb.NewMultiAddrSource(dbs.ChanStateDB, dbs.GraphDB)
s := &server{
cfg: cfg,
implCfg: implCfg,
graphDB: dbs.GraphDB,
chanStateDB: dbs.ChanStateDB.ChannelStateDB(),
addrSource: addrSource,
miscDB: dbs.ChanStateDB,
invoicesDB: dbs.InvoiceDB,
cc: cc,
sigPool: lnwallet.NewSigPool(cfg.Workers.Sig, cc.Signer),
writePool: writePool,
readPool: readPool,
chansToRestore: chansToRestore,
blockbeatDispatcher: chainio.NewBlockbeatDispatcher(
cc.ChainNotifier,
),
channelNotifier: channelnotifier.New(
dbs.ChanStateDB.ChannelStateDB(),
),
identityECDH: nodeKeyECDH,
identityKeyLoc: nodeKeyDesc.KeyLocator,
nodeSigner: netann.NewNodeSigner(nodeKeySigner),
listenAddrs: listenAddrs,
// TODO(roasbeef): derive proper onion key based on rotation
// schedule
sphinx: hop.NewOnionProcessor(sphinxRouter),
torController: torController,
persistentPeers: make(map[string]bool),
persistentPeersBackoff: make(map[string]time.Duration),
persistentConnReqs: make(map[string][]*connmgr.ConnReq),
persistentPeerAddrs: make(map[string][]*lnwire.NetAddress),
persistentRetryCancels: make(map[string]chan struct{}),
peerErrors: make(map[string]*queue.CircularBuffer),
ignorePeerTermination: make(map[*peer.Brontide]struct{}),
scheduledPeerConnection: make(map[string]func()),
pongBuf: make([]byte, lnwire.MaxPongBytes),
peersByPub: make(map[string]*peer.Brontide),
inboundPeers: make(map[string]*peer.Brontide),
outboundPeers: make(map[string]*peer.Brontide),
peerConnectedListeners: make(map[string][]chan<- lnpeer.Peer),
peerDisconnectedListeners: make(map[string][]chan<- struct{}),
invoiceHtlcModifier: invoiceHtlcModifier,
customMessageServer: subscribe.NewServer(),
tlsManager: tlsManager,
featureMgr: featureMgr,
quit: make(chan struct{}),
}
// Start the low-level services once they are initialized.
//
// TODO(yy): break the server startup into four steps,
// 1. init the low-level services.
// 2. start the low-level services.
// 3. init the high-level services.
// 4. start the high-level services.
if err := s.startLowLevelServices(); err != nil {
return nil, err
}
currentHash, currentHeight, err := s.cc.ChainIO.GetBestBlock()
if err != nil {
return nil, err
}
expiryWatcher := invoices.NewInvoiceExpiryWatcher(
clock.NewDefaultClock(), cfg.Invoices.HoldExpiryDelta,
uint32(currentHeight), currentHash, cc.ChainNotifier,
)
s.invoices = invoices.NewRegistry(
dbs.InvoiceDB, expiryWatcher, ®istryConfig,
)
s.htlcNotifier = htlcswitch.NewHtlcNotifier(time.Now)
thresholdSats := btcutil.Amount(cfg.MaxFeeExposure)
thresholdMSats := lnwire.NewMSatFromSatoshis(thresholdSats)
linkUpdater := func(shortID lnwire.ShortChannelID) error {
link, err := s.htlcSwitch.GetLinkByShortID(shortID)
if err != nil {
return err
}
s.htlcSwitch.UpdateLinkAliases(link)
return nil
}
s.aliasMgr, err = aliasmgr.NewManager(dbs.ChanStateDB, linkUpdater)
if err != nil {
return nil, err
}
s.htlcSwitch, err = htlcswitch.New(htlcswitch.Config{
DB: dbs.ChanStateDB,
FetchAllOpenChannels: s.chanStateDB.FetchAllOpenChannels,
FetchAllChannels: s.chanStateDB.FetchAllChannels,
FetchClosedChannels: s.chanStateDB.FetchClosedChannels,
LocalChannelClose: func(pubKey []byte,
request *htlcswitch.ChanClose) {
peer, err := s.FindPeerByPubStr(string(pubKey))
if err != nil {
srvrLog.Errorf("unable to close channel, peer"+
" with %v id can't be found: %v",
pubKey, err,
)
return
}
peer.HandleLocalCloseChanReqs(request)
},
FwdingLog: dbs.ChanStateDB.ForwardingLog(),
SwitchPackager: channeldb.NewSwitchPackager(),
ExtractErrorEncrypter: s.sphinx.ExtractErrorEncrypter,
FetchLastChannelUpdate: s.fetchLastChanUpdate(),
Notifier: s.cc.ChainNotifier,
HtlcNotifier: s.htlcNotifier,
FwdEventTicker: ticker.New(htlcswitch.DefaultFwdEventInterval),
LogEventTicker: ticker.New(htlcswitch.DefaultLogInterval),
AckEventTicker: ticker.New(htlcswitch.DefaultAckInterval),
AllowCircularRoute: cfg.AllowCircularRoute,
RejectHTLC: cfg.RejectHTLC,
Clock: clock.NewDefaultClock(),
MailboxDeliveryTimeout: cfg.Htlcswitch.MailboxDeliveryTimeout,
MaxFeeExposure: thresholdMSats,
SignAliasUpdate: s.signAliasUpdate,
IsAlias: aliasmgr.IsAlias,
}, uint32(currentHeight))
if err != nil {
return nil, err
}
s.interceptableSwitch, err = htlcswitch.NewInterceptableSwitch(
&htlcswitch.InterceptableSwitchConfig{
Switch: s.htlcSwitch,
CltvRejectDelta: lncfg.DefaultFinalCltvRejectDelta,
CltvInterceptDelta: lncfg.DefaultCltvInterceptDelta,
RequireInterceptor: s.cfg.RequireInterceptor,
Notifier: s.cc.ChainNotifier,
},
)
if err != nil {
return nil, err
}
s.witnessBeacon = newPreimageBeacon(
dbs.ChanStateDB.NewWitnessCache(),
s.interceptableSwitch.ForwardPacket,
)
chanStatusMgrCfg := &netann.ChanStatusConfig{
ChanStatusSampleInterval: cfg.ChanStatusSampleInterval,
ChanEnableTimeout: cfg.ChanEnableTimeout,
ChanDisableTimeout: cfg.ChanDisableTimeout,
OurPubKey: nodeKeyDesc.PubKey,
OurKeyLoc: nodeKeyDesc.KeyLocator,
MessageSigner: s.nodeSigner,
IsChannelActive: s.htlcSwitch.HasActiveLink,
ApplyChannelUpdate: s.applyChannelUpdate,
DB: s.chanStateDB,
Graph: dbs.GraphDB,
}
chanStatusMgr, err := netann.NewChanStatusManager(chanStatusMgrCfg)
if err != nil {
return nil, err
}
s.chanStatusMgr = chanStatusMgr
// If enabled, use either UPnP or NAT-PMP to automatically configure
// port forwarding for users behind a NAT.
if cfg.NAT {
srvrLog.Info("Scanning local network for a UPnP enabled device")
discoveryTimeout := time.Duration(10 * time.Second)
ctx, cancel := context.WithTimeout(
context.Background(), discoveryTimeout,
)
defer cancel()
upnp, err := nat.DiscoverUPnP(ctx)
if err == nil {
s.natTraversal = upnp
} else {
// If we were not able to discover a UPnP enabled device
// on the local network, we'll fall back to attempting
// to discover a NAT-PMP enabled device.
srvrLog.Errorf("Unable to discover a UPnP enabled "+
"device on the local network: %v", err)
srvrLog.Info("Scanning local network for a NAT-PMP " +
"enabled device")
pmp, err := nat.DiscoverPMP(discoveryTimeout)
if err != nil {
err := fmt.Errorf("unable to discover a "+
"NAT-PMP enabled device on the local "+
"network: %v", err)
srvrLog.Error(err)
return nil, err
}
s.natTraversal = pmp
}
}
// If we were requested to automatically configure port forwarding,
// we'll use the ports that the server will be listening on.
externalIPStrings := make([]string, len(cfg.ExternalIPs))
for idx, ip := range cfg.ExternalIPs {
externalIPStrings[idx] = ip.String()
}
if s.natTraversal != nil {
listenPorts := make([]uint16, 0, len(listenAddrs))
for _, listenAddr := range listenAddrs {
// At this point, the listen addresses should have
// already been normalized, so it's safe to ignore the
// errors.
_, portStr, _ := net.SplitHostPort(listenAddr.String())
port, _ := strconv.Atoi(portStr)
listenPorts = append(listenPorts, uint16(port))
}
ips, err := s.configurePortForwarding(listenPorts...)
if err != nil {
srvrLog.Errorf("Unable to automatically set up port "+
"forwarding using %s: %v",
s.natTraversal.Name(), err)
} else {
srvrLog.Infof("Automatically set up port forwarding "+
"using %s to advertise external IP",
s.natTraversal.Name())
externalIPStrings = append(externalIPStrings, ips...)
}
}
// If external IP addresses have been specified, add those to the list
// of this server's addresses.
externalIPs, err := lncfg.NormalizeAddresses(
externalIPStrings, strconv.Itoa(defaultPeerPort),
cfg.net.ResolveTCPAddr,
)
if err != nil {
return nil, err
}
selfAddrs := make([]net.Addr, 0, len(externalIPs))
selfAddrs = append(selfAddrs, externalIPs...)
// We'll now reconstruct a node announcement based on our current
// configuration so we can send it out as a sort of heart beat within
// the network.
//
// We'll start by parsing the node color from configuration.
color, err := lncfg.ParseHexColor(cfg.Color)
if err != nil {
srvrLog.Errorf("unable to parse color: %v\n", err)
return nil, err
}
// If no alias is provided, default to first 10 characters of public
// key.
alias := cfg.Alias
if alias == "" {
alias = hex.EncodeToString(serializedPubKey[:10])
}
nodeAlias, err := lnwire.NewNodeAlias(alias)
if err != nil {
return nil, err
}
selfNode := &models.LightningNode{
HaveNodeAnnouncement: true,
LastUpdate: time.Now(),
Addresses: selfAddrs,
Alias: nodeAlias.String(),
Features: s.featureMgr.Get(feature.SetNodeAnn),
Color: color,
}
copy(selfNode.PubKeyBytes[:], nodeKeyDesc.PubKey.SerializeCompressed())
// Based on the disk representation of the node announcement generated
// above, we'll generate a node announcement that can go out on the
// network so we can properly sign it.
nodeAnn, err := selfNode.NodeAnnouncement(false)
if err != nil {
return nil, fmt.Errorf("unable to gen self node ann: %w", err)
}
// With the announcement generated, we'll sign it to properly
// authenticate the message on the network.
authSig, err := netann.SignAnnouncement(
s.nodeSigner, nodeKeyDesc.KeyLocator, nodeAnn,
)
if err != nil {
return nil, fmt.Errorf("unable to generate signature for "+
"self node announcement: %v", err)
}
selfNode.AuthSigBytes = authSig.Serialize()
nodeAnn.Signature, err = lnwire.NewSigFromECDSARawSignature(
selfNode.AuthSigBytes,
)
if err != nil {
return nil, err
}
// Finally, we'll update the representation on disk, and update our
// cached in-memory version as well.
if err := dbs.GraphDB.SetSourceNode(selfNode); err != nil {
return nil, fmt.Errorf("can't set self node: %w", err)
}
s.currentNodeAnn = nodeAnn
// The router will get access to the payment ID sequencer, such that it
// can generate unique payment IDs.
sequencer, err := htlcswitch.NewPersistentSequencer(dbs.ChanStateDB)
if err != nil {
return nil, err
}
// Instantiate mission control with config from the sub server.
//
// TODO(joostjager): When we are further in the process of moving to sub
// servers, the mission control instance itself can be moved there too.
routingConfig := routerrpc.GetRoutingConfig(cfg.SubRPCServers.RouterRPC)
// We only initialize a probability estimator if there's no custom one.
var estimator routing.Estimator
if cfg.Estimator != nil {
estimator = cfg.Estimator
} else {
switch routingConfig.ProbabilityEstimatorType {
case routing.AprioriEstimatorName:
aCfg := routingConfig.AprioriConfig
aprioriConfig := routing.AprioriConfig{
AprioriHopProbability: aCfg.HopProbability,
PenaltyHalfLife: aCfg.PenaltyHalfLife,
AprioriWeight: aCfg.Weight,
CapacityFraction: aCfg.CapacityFraction,
}
estimator, err = routing.NewAprioriEstimator(
aprioriConfig,
)
if err != nil {
return nil, err
}
case routing.BimodalEstimatorName:
bCfg := routingConfig.BimodalConfig
bimodalConfig := routing.BimodalConfig{
BimodalNodeWeight: bCfg.NodeWeight,
BimodalScaleMsat: lnwire.MilliSatoshi(
bCfg.Scale,
),
BimodalDecayTime: bCfg.DecayTime,
}
estimator, err = routing.NewBimodalEstimator(
bimodalConfig,
)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unknown estimator type %v",
routingConfig.ProbabilityEstimatorType)
}
}
mcCfg := &routing.MissionControlConfig{
OnConfigUpdate: fn.Some(s.UpdateRoutingConfig),
Estimator: estimator,
MaxMcHistory: routingConfig.MaxMcHistory,
McFlushInterval: routingConfig.McFlushInterval,
MinFailureRelaxInterval: routing.DefaultMinFailureRelaxInterval,
}
s.missionController, err = routing.NewMissionController(
dbs.ChanStateDB, selfNode.PubKeyBytes, mcCfg,
)
if err != nil {
return nil, fmt.Errorf("can't create mission control "+
"manager: %w", err)
}
s.defaultMC, err = s.missionController.GetNamespacedStore(
routing.DefaultMissionControlNamespace,
)
if err != nil {
return nil, fmt.Errorf("can't create mission control in the "+
"default namespace: %w", err)
}
srvrLog.Debugf("Instantiating payment session source with config: "+
"AttemptCost=%v + %v%%, MinRouteProbability=%v",
int64(routingConfig.AttemptCost),
float64(routingConfig.AttemptCostPPM)/10000,
routingConfig.MinRouteProbability)
pathFindingConfig := routing.PathFindingConfig{
AttemptCost: lnwire.NewMSatFromSatoshis(
routingConfig.AttemptCost,
),
AttemptCostPPM: routingConfig.AttemptCostPPM,
MinProbability: routingConfig.MinRouteProbability,
}
sourceNode, err := dbs.GraphDB.SourceNode()
if err != nil {
return nil, fmt.Errorf("error getting source node: %w", err)
}
paymentSessionSource := &routing.SessionSource{
GraphSessionFactory: dbs.GraphDB,
SourceNode: sourceNode,
MissionControl: s.defaultMC,
GetLink: s.htlcSwitch.GetLinkByShortID,
PathFindingConfig: pathFindingConfig,
}
paymentControl := channeldb.NewPaymentControl(dbs.ChanStateDB)
s.controlTower = routing.NewControlTower(paymentControl)
strictPruning := cfg.Bitcoin.Node == "neutrino" ||
cfg.Routing.StrictZombiePruning
s.graphBuilder, err = graph.NewBuilder(&graph.Config{
SelfNode: selfNode.PubKeyBytes,
Graph: dbs.GraphDB,
Chain: cc.ChainIO,
ChainView: cc.ChainView,
Notifier: cc.ChainNotifier,
ChannelPruneExpiry: graph.DefaultChannelPruneExpiry,
GraphPruneInterval: time.Hour,
FirstTimePruneDelay: graph.DefaultFirstTimePruneDelay,
AssumeChannelValid: cfg.Routing.AssumeChannelValid,
StrictZombiePruning: strictPruning,
IsAlias: aliasmgr.IsAlias,
})
if err != nil {
return nil, fmt.Errorf("can't create graph builder: %w", err)
}
s.chanRouter, err = routing.New(routing.Config{
SelfNode: selfNode.PubKeyBytes,
RoutingGraph: dbs.GraphDB,
Chain: cc.ChainIO,
Payer: s.htlcSwitch,
Control: s.controlTower,
MissionControl: s.defaultMC,
SessionSource: paymentSessionSource,
GetLink: s.htlcSwitch.GetLinkByShortID,
NextPaymentID: sequencer.NextID,
PathFindingConfig: pathFindingConfig,
Clock: clock.NewDefaultClock(),
ApplyChannelUpdate: s.graphBuilder.ApplyChannelUpdate,
ClosedSCIDs: s.fetchClosedChannelSCIDs(),
TrafficShaper: implCfg.TrafficShaper,
})
if err != nil {
return nil, fmt.Errorf("can't create router: %w", err)
}
chanSeries := discovery.NewChanSeries(s.graphDB)
gossipMessageStore, err := discovery.NewMessageStore(dbs.ChanStateDB)
if err != nil {
return nil, err
}
waitingProofStore, err := channeldb.NewWaitingProofStore(dbs.ChanStateDB)
if err != nil {
return nil, err
}
scidCloserMan := discovery.NewScidCloserMan(s.graphDB, s.chanStateDB)
s.authGossiper = discovery.New(discovery.Config{
Graph: s.graphBuilder,
ChainIO: s.cc.ChainIO,
Notifier: s.cc.ChainNotifier,
ChainHash: *s.cfg.ActiveNetParams.GenesisHash,
Broadcast: s.BroadcastMessage,
ChanSeries: chanSeries,
NotifyWhenOnline: s.NotifyWhenOnline,
NotifyWhenOffline: s.NotifyWhenOffline,
FetchSelfAnnouncement: s.getNodeAnnouncement,
UpdateSelfAnnouncement: func() (lnwire.NodeAnnouncement,
error) {
return s.genNodeAnnouncement(nil)
},
ProofMatureDelta: cfg.Gossip.AnnouncementConf,
TrickleDelay: time.Millisecond * time.Duration(cfg.TrickleDelay),
RetransmitTicker: ticker.New(time.Minute * 30),
RebroadcastInterval: time.Hour * 24,
WaitingProofStore: waitingProofStore,
MessageStore: gossipMessageStore,
AnnSigner: s.nodeSigner,
RotateTicker: ticker.New(discovery.DefaultSyncerRotationInterval),
HistoricalSyncTicker: ticker.New(cfg.HistoricalSyncInterval),
NumActiveSyncers: cfg.NumGraphSyncPeers,
NoTimestampQueries: cfg.ProtocolOptions.NoTimestampQueryOption, //nolint:ll
MinimumBatchSize: 10,
SubBatchDelay: cfg.Gossip.SubBatchDelay,
IgnoreHistoricalFilters: cfg.IgnoreHistoricalGossipFilters,
PinnedSyncers: cfg.Gossip.PinnedSyncers,
MaxChannelUpdateBurst: cfg.Gossip.MaxChannelUpdateBurst,
ChannelUpdateInterval: cfg.Gossip.ChannelUpdateInterval,
IsAlias: aliasmgr.IsAlias,
SignAliasUpdate: s.signAliasUpdate,
FindBaseByAlias: s.aliasMgr.FindBaseSCID,
GetAlias: s.aliasMgr.GetPeerAlias,
FindChannel: s.findChannel,
IsStillZombieChannel: s.graphBuilder.IsZombieChannel,
ScidCloser: scidCloserMan,
AssumeChannelValid: cfg.Routing.AssumeChannelValid,
MsgRateBytes: cfg.Gossip.MsgRateBytes,
MsgBurstBytes: cfg.Gossip.MsgBurstBytes,
}, nodeKeyDesc)
accessCfg := &accessManConfig{
initAccessPerms: func() (map[string]channeldb.ChanCount,
error) {
genesisHash := *s.cfg.ActiveNetParams.GenesisHash
return s.chanStateDB.FetchPermAndTempPeers(
genesisHash[:],
)
},
shouldDisconnect: s.authGossiper.ShouldDisconnect,
maxRestrictedSlots: int64(s.cfg.NumRestrictedSlots),
}
peerAccessMan, err := newAccessMan(accessCfg)
if err != nil {
return nil, err
}
s.peerAccessMan = peerAccessMan
selfVertex := route.Vertex(nodeKeyDesc.PubKey.SerializeCompressed())
//nolint:ll
s.localChanMgr = &localchans.Manager{
SelfPub: nodeKeyDesc.PubKey,
DefaultRoutingPolicy: cc.RoutingPolicy,
ForAllOutgoingChannels: func(cb func(*models.ChannelEdgeInfo,
*models.ChannelEdgePolicy) error) error {
return s.graphDB.ForEachNodeChannel(selfVertex,
func(_ kvdb.RTx, c *models.ChannelEdgeInfo,
e *models.ChannelEdgePolicy,
_ *models.ChannelEdgePolicy) error {
// NOTE: The invoked callback here may
// receive a nil channel policy.
return cb(c, e)
},
)
},
PropagateChanPolicyUpdate: s.authGossiper.PropagateChanPolicyUpdate,
UpdateForwardingPolicies: s.htlcSwitch.UpdateForwardingPolicies,
FetchChannel: s.chanStateDB.FetchChannel,
AddEdge: func(edge *models.ChannelEdgeInfo) error {
return s.graphBuilder.AddEdge(edge)
},
}
utxnStore, err := contractcourt.NewNurseryStore(
s.cfg.ActiveNetParams.GenesisHash, dbs.ChanStateDB,
)
if err != nil {
srvrLog.Errorf("unable to create nursery store: %v", err)
return nil, err
}
sweeperStore, err := sweep.NewSweeperStore(
dbs.ChanStateDB, s.cfg.ActiveNetParams.GenesisHash,
)
if err != nil {
srvrLog.Errorf("unable to create sweeper store: %v", err)
return nil, err
}
aggregator := sweep.NewBudgetAggregator(
cc.FeeEstimator, sweep.DefaultMaxInputsPerTx,
s.implCfg.AuxSweeper,
)
s.txPublisher = sweep.NewTxPublisher(sweep.TxPublisherConfig{
Signer: cc.Wallet.Cfg.Signer,
Wallet: cc.Wallet,
Estimator: cc.FeeEstimator,
Notifier: cc.ChainNotifier,
AuxSweeper: s.implCfg.AuxSweeper,
})
s.sweeper = sweep.New(&sweep.UtxoSweeperConfig{
FeeEstimator: cc.FeeEstimator,
GenSweepScript: newSweepPkScriptGen(
cc.Wallet, s.cfg.ActiveNetParams.Params,
),
Signer: cc.Wallet.Cfg.Signer,
Wallet: newSweeperWallet(cc.Wallet),
Mempool: cc.MempoolNotifier,
Notifier: cc.ChainNotifier,
Store: sweeperStore,
MaxInputsPerTx: sweep.DefaultMaxInputsPerTx,
MaxFeeRate: cfg.Sweeper.MaxFeeRate,
Aggregator: aggregator,
Publisher: s.txPublisher,
NoDeadlineConfTarget: cfg.Sweeper.NoDeadlineConfTarget,
})
s.utxoNursery = contractcourt.NewUtxoNursery(&contractcourt.NurseryConfig{
ChainIO: cc.ChainIO,
ConfDepth: 1,
FetchClosedChannels: s.chanStateDB.FetchClosedChannels,
FetchClosedChannel: s.chanStateDB.FetchClosedChannel,
Notifier: cc.ChainNotifier,
PublishTransaction: cc.Wallet.PublishTransaction,
Store: utxnStore,
SweepInput: s.sweeper.SweepInput,
Budget: s.cfg.Sweeper.Budget,
})
// Construct a closure that wraps the htlcswitch's CloseLink method.
closeLink := func(chanPoint *wire.OutPoint,
closureType contractcourt.ChannelCloseType) {
// TODO(conner): Properly respect the update and error channels
// returned by CloseLink.
// Instruct the switch to close the channel. Provide no close out
// delivery script or target fee per kw because user input is not
// available when the remote peer closes the channel.
s.htlcSwitch.CloseLink(
context.Background(), chanPoint, closureType, 0, 0, nil,
)
}
// We will use the following channel to reliably hand off contract
// breach events from the ChannelArbitrator to the BreachArbitrator,
contractBreaches := make(chan *contractcourt.ContractBreachEvent, 1)
s.breachArbitrator = contractcourt.NewBreachArbitrator(
&contractcourt.BreachConfig{
CloseLink: closeLink,
DB: s.chanStateDB,
Estimator: s.cc.FeeEstimator,
GenSweepScript: newSweepPkScriptGen(
cc.Wallet, s.cfg.ActiveNetParams.Params,
),
Notifier: cc.ChainNotifier,
PublishTransaction: cc.Wallet.PublishTransaction,
ContractBreaches: contractBreaches,
Signer: cc.Wallet.Cfg.Signer,
Store: contractcourt.NewRetributionStore(
dbs.ChanStateDB,
),
AuxSweeper: s.implCfg.AuxSweeper,
},
)
//nolint:ll
s.chainArb = contractcourt.NewChainArbitrator(contractcourt.ChainArbitratorConfig{
ChainHash: *s.cfg.ActiveNetParams.GenesisHash,
IncomingBroadcastDelta: lncfg.DefaultIncomingBroadcastDelta,
OutgoingBroadcastDelta: lncfg.DefaultOutgoingBroadcastDelta,
NewSweepAddr: func() ([]byte, error) {
addr, err := newSweepPkScriptGen(
cc.Wallet, netParams,
)().Unpack()
if err != nil {
return nil, err
}
return addr.DeliveryAddress, nil
},
PublishTx: cc.Wallet.PublishTransaction,
DeliverResolutionMsg: func(msgs ...contractcourt.ResolutionMsg) error {
for _, msg := range msgs {
err := s.htlcSwitch.ProcessContractResolution(msg)
if err != nil {
return err
}
}
return nil
},
IncubateOutputs: func(chanPoint wire.OutPoint,
outHtlcRes fn.Option[lnwallet.OutgoingHtlcResolution],
inHtlcRes fn.Option[lnwallet.IncomingHtlcResolution],
broadcastHeight uint32,
deadlineHeight fn.Option[int32]) error {
return s.utxoNursery.IncubateOutputs(
chanPoint, outHtlcRes, inHtlcRes,
broadcastHeight, deadlineHeight,
)
},
PreimageDB: s.witnessBeacon,
Notifier: cc.ChainNotifier,
Mempool: cc.MempoolNotifier,
Signer: cc.Wallet.Cfg.Signer,
FeeEstimator: cc.FeeEstimator,
ChainIO: cc.ChainIO,
MarkLinkInactive: func(chanPoint wire.OutPoint) error {
chanID := lnwire.NewChanIDFromOutPoint(chanPoint)
s.htlcSwitch.RemoveLink(chanID)
return nil
},
IsOurAddress: cc.Wallet.IsOurAddress,
ContractBreach: func(chanPoint wire.OutPoint,
breachRet *lnwallet.BreachRetribution) error {
// processACK will handle the BreachArbitrator ACKing
// the event.
finalErr := make(chan error, 1)
processACK := func(brarErr error) {
if brarErr != nil {
finalErr <- brarErr
return
}
// If the BreachArbitrator successfully handled
// the event, we can signal that the handoff
// was successful.
finalErr <- nil
}
event := &contractcourt.ContractBreachEvent{
ChanPoint: chanPoint,
ProcessACK: processACK,
BreachRetribution: breachRet,
}
// Send the contract breach event to the
// BreachArbitrator.
select {
case contractBreaches <- event:
case <-s.quit:
return ErrServerShuttingDown
}
// We'll wait for a final error to be available from
// the BreachArbitrator.
select {
case err := <-finalErr:
return err
case <-s.quit:
return ErrServerShuttingDown
}
},
DisableChannel: func(chanPoint wire.OutPoint) error {
return s.chanStatusMgr.RequestDisable(chanPoint, false)
},
Sweeper: s.sweeper,
Registry: s.invoices,
NotifyClosedChannel: s.channelNotifier.NotifyClosedChannelEvent,
NotifyFullyResolvedChannel: s.channelNotifier.NotifyFullyResolvedChannelEvent,
OnionProcessor: s.sphinx,
PaymentsExpirationGracePeriod: cfg.PaymentsExpirationGracePeriod,
IsForwardedHTLC: s.htlcSwitch.IsForwardedHTLC,
Clock: clock.NewDefaultClock(),
SubscribeBreachComplete: s.breachArbitrator.SubscribeBreachComplete,
PutFinalHtlcOutcome: s.chanStateDB.PutOnchainFinalHtlcOutcome,
HtlcNotifier: s.htlcNotifier,
Budget: *s.cfg.Sweeper.Budget,
// TODO(yy): remove this hack once PaymentCircuit is interfaced.
QueryIncomingCircuit: func(
circuit models.CircuitKey) *models.CircuitKey {
// Get the circuit map.
circuits := s.htlcSwitch.CircuitLookup()
// Lookup the outgoing circuit.
pc := circuits.LookupOpenCircuit(circuit)
if pc == nil {
return nil
}
return &pc.Incoming
},
AuxLeafStore: implCfg.AuxLeafStore,
AuxSigner: implCfg.AuxSigner,
AuxResolver: implCfg.AuxContractResolver,
}, dbs.ChanStateDB)
// Select the configuration and funding parameters for Bitcoin.
chainCfg := cfg.Bitcoin
minRemoteDelay := funding.MinBtcRemoteDelay
maxRemoteDelay := funding.MaxBtcRemoteDelay
var chanIDSeed [32]byte
if _, err := rand.Read(chanIDSeed[:]); err != nil {
return nil, err
}
// Wrap the DeleteChannelEdges method so that the funding manager can
// use it without depending on several layers of indirection.
deleteAliasEdge := func(scid lnwire.ShortChannelID) (
*models.ChannelEdgePolicy, error) {
info, e1, e2, err := s.graphDB.FetchChannelEdgesByID(
scid.ToUint64(),
)
if errors.Is(err, graphdb.ErrEdgeNotFound) {
// This is unlikely but there is a slim chance of this
// being hit if lnd was killed via SIGKILL and the
// funding manager was stepping through the delete
// alias edge logic.
return nil, nil
} else if err != nil {
return nil, err
}
// Grab our key to find our policy.
var ourKey [33]byte
copy(ourKey[:], nodeKeyDesc.PubKey.SerializeCompressed())
var ourPolicy *models.ChannelEdgePolicy
if info != nil && info.NodeKey1Bytes == ourKey {
ourPolicy = e1
} else {
ourPolicy = e2
}
if ourPolicy == nil {
// Something is wrong, so return an error.
return nil, fmt.Errorf("we don't have an edge")
}
err = s.graphDB.DeleteChannelEdges(
false, false, scid.ToUint64(),
)
return ourPolicy, err
}
// For the reservationTimeout and the zombieSweeperInterval different
// values are set in case we are in a dev environment so enhance test
// capacilities.
reservationTimeout := chanfunding.DefaultReservationTimeout
zombieSweeperInterval := lncfg.DefaultZombieSweeperInterval
// Get the development config for funding manager. If we are not in
// development mode, this would be nil.
var devCfg *funding.DevConfig
if lncfg.IsDevBuild() {
devCfg = &funding.DevConfig{
ProcessChannelReadyWait: cfg.Dev.ChannelReadyWait(),
MaxWaitNumBlocksFundingConf: cfg.Dev.
GetMaxWaitNumBlocksFundingConf(),
}
reservationTimeout = cfg.Dev.GetReservationTimeout()
zombieSweeperInterval = cfg.Dev.GetZombieSweeperInterval()
srvrLog.Debugf("Using the dev config for the fundingMgr: %v, "+
"reservationTimeout=%v, zombieSweeperInterval=%v",
devCfg, reservationTimeout, zombieSweeperInterval)
}
//nolint:ll
s.fundingMgr, err = funding.NewFundingManager(funding.Config{
Dev: devCfg,
NoWumboChans: !cfg.ProtocolOptions.Wumbo(),
IDKey: nodeKeyDesc.PubKey,
IDKeyLoc: nodeKeyDesc.KeyLocator,
Wallet: cc.Wallet,
PublishTransaction: cc.Wallet.PublishTransaction,
UpdateLabel: func(hash chainhash.Hash, label string) error {
return cc.Wallet.LabelTransaction(hash, label, true)
},
Notifier: cc.ChainNotifier,
ChannelDB: s.chanStateDB,
FeeEstimator: cc.FeeEstimator,
SignMessage: cc.MsgSigner.SignMessage,
CurrentNodeAnnouncement: func() (lnwire.NodeAnnouncement,
error) {
return s.genNodeAnnouncement(nil)
},
SendAnnouncement: s.authGossiper.ProcessLocalAnnouncement,
NotifyWhenOnline: s.NotifyWhenOnline,
TempChanIDSeed: chanIDSeed,
FindChannel: s.findChannel,
DefaultRoutingPolicy: cc.RoutingPolicy,
DefaultMinHtlcIn: cc.MinHtlcIn,
NumRequiredConfs: func(chanAmt btcutil.Amount,
pushAmt lnwire.MilliSatoshi) uint16 {
// For large channels we increase the number
// of confirmations we require for the
// channel to be considered open. As it is
// always the responder that gets to choose
// value, the pushAmt is value being pushed
// to us. This means we have more to lose
// in the case this gets re-orged out, and
// we will require more confirmations before
// we consider it open.
// In case the user has explicitly specified
// a default value for the number of
// confirmations, we use it.
defaultConf := uint16(chainCfg.DefaultNumChanConfs)
if defaultConf != 0 {
return defaultConf
}
minConf := uint64(3)
maxConf := uint64(6)
// If this is a wumbo channel, then we'll require the
// max amount of confirmations.
if chanAmt > MaxFundingAmount {
return uint16(maxConf)
}
// If not we return a value scaled linearly
// between 3 and 6, depending on channel size.
// TODO(halseth): Use 1 as minimum?
maxChannelSize := uint64(
lnwire.NewMSatFromSatoshis(MaxFundingAmount))
stake := lnwire.NewMSatFromSatoshis(chanAmt) + pushAmt
conf := maxConf * uint64(stake) / maxChannelSize
if conf < minConf {
conf = minConf
}
if conf > maxConf {
conf = maxConf
}
return uint16(conf)
},
RequiredRemoteDelay: func(chanAmt btcutil.Amount) uint16 {
// We scale the remote CSV delay (the time the
// remote have to claim funds in case of a unilateral
// close) linearly from minRemoteDelay blocks
// for small channels, to maxRemoteDelay blocks
// for channels of size MaxFundingAmount.
// In case the user has explicitly specified
// a default value for the remote delay, we
// use it.
defaultDelay := uint16(chainCfg.DefaultRemoteDelay)
if defaultDelay > 0 {
return defaultDelay
}
// If this is a wumbo channel, then we'll require the
// max value.
if chanAmt > MaxFundingAmount {
return maxRemoteDelay
}
// If not we scale according to channel size.
delay := uint16(btcutil.Amount(maxRemoteDelay) *
chanAmt / MaxFundingAmount)
if delay < minRemoteDelay {
delay = minRemoteDelay
}
if delay > maxRemoteDelay {
delay = maxRemoteDelay
}
return delay
},
WatchNewChannel: func(channel *channeldb.OpenChannel,
peerKey *btcec.PublicKey) error {
// First, we'll mark this new peer as a persistent peer
// for re-connection purposes. If the peer is not yet
// tracked or the user hasn't requested it to be perm,
// we'll set false to prevent the server from continuing
// to connect to this peer even if the number of
// channels with this peer is zero.
s.mu.Lock()
pubStr := string(peerKey.SerializeCompressed())
if _, ok := s.persistentPeers[pubStr]; !ok {
s.persistentPeers[pubStr] = false
}
s.mu.Unlock()
// With that taken care of, we'll send this channel to
// the chain arb so it can react to on-chain events.
return s.chainArb.WatchNewChannel(channel)
},
ReportShortChanID: func(chanPoint wire.OutPoint) error {
cid := lnwire.NewChanIDFromOutPoint(chanPoint)
return s.htlcSwitch.UpdateShortChanID(cid)
},
RequiredRemoteChanReserve: func(chanAmt,
dustLimit btcutil.Amount) btcutil.Amount {
// By default, we'll require the remote peer to maintain
// at least 1% of the total channel capacity at all
// times. If this value ends up dipping below the dust
// limit, then we'll use the dust limit itself as the
// reserve as required by BOLT #2.
reserve := chanAmt / 100
if reserve < dustLimit {
reserve = dustLimit
}
return reserve
},
RequiredRemoteMaxValue: func(chanAmt btcutil.Amount) lnwire.MilliSatoshi {
// By default, we'll allow the remote peer to fully
// utilize the full bandwidth of the channel, minus our
// required reserve.
reserve := lnwire.NewMSatFromSatoshis(chanAmt / 100)
return lnwire.NewMSatFromSatoshis(chanAmt) - reserve
},
RequiredRemoteMaxHTLCs: func(chanAmt btcutil.Amount) uint16 {
if cfg.DefaultRemoteMaxHtlcs > 0 {
return cfg.DefaultRemoteMaxHtlcs
}
// By default, we'll permit them to utilize the full
// channel bandwidth.
return uint16(input.MaxHTLCNumber / 2)
},
ZombieSweeperInterval: zombieSweeperInterval,
ReservationTimeout: reservationTimeout,
MinChanSize: btcutil.Amount(cfg.MinChanSize),
MaxChanSize: btcutil.Amount(cfg.MaxChanSize),
MaxPendingChannels: cfg.MaxPendingChannels,
RejectPush: cfg.RejectPush,
MaxLocalCSVDelay: chainCfg.MaxLocalDelay,
NotifyOpenChannelEvent: s.notifyOpenChannelPeerEvent,
OpenChannelPredicate: chanPredicate,
NotifyPendingOpenChannelEvent: s.notifyPendingOpenChannelPeerEvent,
NotifyFundingTimeout: s.notifyFundingTimeoutPeerEvent,
EnableUpfrontShutdown: cfg.EnableUpfrontShutdown,
MaxAnchorsCommitFeeRate: chainfee.SatPerKVByte(
s.cfg.MaxCommitFeeRateAnchors * 1000).FeePerKWeight(),
DeleteAliasEdge: deleteAliasEdge,
AliasManager: s.aliasMgr,
IsSweeperOutpoint: s.sweeper.IsSweeperOutpoint,
AuxFundingController: implCfg.AuxFundingController,
AuxSigner: implCfg.AuxSigner,
AuxResolver: implCfg.AuxContractResolver,
})
if err != nil {
return nil, err
}
// Next, we'll assemble the sub-system that will maintain an on-disk
// static backup of the latest channel state.
chanNotifier := &channelNotifier{
chanNotifier: s.channelNotifier,
addrs: s.addrSource,
}
backupFile := chanbackup.NewMultiFile(
cfg.BackupFilePath, cfg.NoBackupArchive,
)
startingChans, err := chanbackup.FetchStaticChanBackups(
s.chanStateDB, s.addrSource,
)
if err != nil {
return nil, err
}
s.chanSubSwapper, err = chanbackup.NewSubSwapper(
startingChans, chanNotifier, s.cc.KeyRing, backupFile,
)
if err != nil {
return nil, err
}
// Assemble a peer notifier which will provide clients with subscriptions
// to peer online and offline events.
s.peerNotifier = peernotifier.New()
// Create a channel event store which monitors all open channels.
s.chanEventStore = chanfitness.NewChannelEventStore(&chanfitness.Config{
SubscribeChannelEvents: func() (subscribe.Subscription, error) {
return s.channelNotifier.SubscribeChannelEvents()
},
SubscribePeerEvents: func() (subscribe.Subscription, error) {
return s.peerNotifier.SubscribePeerEvents()
},
GetOpenChannels: s.chanStateDB.FetchAllOpenChannels,
Clock: clock.NewDefaultClock(),
ReadFlapCount: s.miscDB.ReadFlapCount,
WriteFlapCount: s.miscDB.WriteFlapCounts,
FlapCountTicker: ticker.New(chanfitness.FlapCountFlushRate),
})
if cfg.WtClient.Active {
policy := wtpolicy.DefaultPolicy()
policy.MaxUpdates = cfg.WtClient.MaxUpdates
// We expose the sweep fee rate in sat/vbyte, but the tower
// protocol operations on sat/kw.
sweepRateSatPerVByte := chainfee.SatPerKVByte(
1000 * cfg.WtClient.SweepFeeRate,
)
policy.SweepFeeRate = sweepRateSatPerVByte.FeePerKWeight()
if err := policy.Validate(); err != nil {
return nil, err
}
// authDial is the wrapper around the btrontide.Dial for the
// watchtower.
authDial := func(localKey keychain.SingleKeyECDH,
netAddr *lnwire.NetAddress,
dialer tor.DialFunc) (wtserver.Peer, error) {
return brontide.Dial(
localKey, netAddr, cfg.ConnectionTimeout, dialer,
)
}
// buildBreachRetribution is a call-back that can be used to
// query the BreachRetribution info and channel type given a
// channel ID and commitment height.
buildBreachRetribution := func(chanID lnwire.ChannelID,
commitHeight uint64) (*lnwallet.BreachRetribution,
channeldb.ChannelType, error) {
channel, err := s.chanStateDB.FetchChannelByID(
nil, chanID,
)
if err != nil {
return nil, 0, err
}
br, err := lnwallet.NewBreachRetribution(
channel, commitHeight, 0, nil,
implCfg.AuxLeafStore,
implCfg.AuxContractResolver,
)
if err != nil {
return nil, 0, err
}
return br, channel.ChanType, nil
}
fetchClosedChannel := s.chanStateDB.FetchClosedChannelForID
// Copy the policy for legacy channels and set the blob flag
// signalling support for anchor channels.
anchorPolicy := policy
anchorPolicy.BlobType |= blob.Type(blob.FlagAnchorChannel)
// Copy the policy for legacy channels and set the blob flag
// signalling support for taproot channels.
taprootPolicy := policy
taprootPolicy.TxPolicy.BlobType |= blob.Type(
blob.FlagTaprootChannel,
)
s.towerClientMgr, err = wtclient.NewManager(&wtclient.Config{
FetchClosedChannel: fetchClosedChannel,
BuildBreachRetribution: buildBreachRetribution,
SessionCloseRange: cfg.WtClient.SessionCloseRange,
ChainNotifier: s.cc.ChainNotifier,
SubscribeChannelEvents: func() (subscribe.Subscription,
error) {
return s.channelNotifier.
SubscribeChannelEvents()
},
Signer: cc.Wallet.Cfg.Signer,
NewAddress: func() ([]byte, error) {
addr, err := newSweepPkScriptGen(
cc.Wallet, netParams,
)().Unpack()
if err != nil {
return nil, err
}
return addr.DeliveryAddress, nil
},
SecretKeyRing: s.cc.KeyRing,
Dial: cfg.net.Dial,
AuthDial: authDial,
DB: dbs.TowerClientDB,
ChainHash: *s.cfg.ActiveNetParams.GenesisHash,
MinBackoff: 10 * time.Second,
MaxBackoff: 5 * time.Minute,
MaxTasksInMemQueue: cfg.WtClient.MaxTasksInMemQueue,
}, policy, anchorPolicy, taprootPolicy)
if err != nil {
return nil, err
}
}
if len(cfg.ExternalHosts) != 0 {
advertisedIPs := make(map[string]struct{})
for _, addr := range s.currentNodeAnn.Addresses {
advertisedIPs[addr.String()] = struct{}{}
}
s.hostAnn = netann.NewHostAnnouncer(netann.HostAnnouncerConfig{
Hosts: cfg.ExternalHosts,
RefreshTicker: ticker.New(defaultHostSampleInterval),
LookupHost: func(host string) (net.Addr, error) {
return lncfg.ParseAddressString(
host, strconv.Itoa(defaultPeerPort),
cfg.net.ResolveTCPAddr,
)
},
AdvertisedIPs: advertisedIPs,
AnnounceNewIPs: netann.IPAnnouncer(
func(modifier ...netann.NodeAnnModifier) (
lnwire.NodeAnnouncement, error) {
return s.genNodeAnnouncement(
nil, modifier...,
)
}),
})
}
// Create liveness monitor.
s.createLivenessMonitor(cfg, cc, leaderElector)
listeners := make([]net.Listener, len(listenAddrs))
for i, listenAddr := range listenAddrs {
// Note: though brontide.NewListener uses ResolveTCPAddr, it
// doesn't need to call the general lndResolveTCP function
// since we are resolving a local address.
// RESOLVE: We are actually partially accepting inbound
// connection requests when we call NewListener.
listeners[i], err = brontide.NewListener(
nodeKeyECDH, listenAddr.String(),
s.peerAccessMan.checkIncomingConnBanScore,
)
if err != nil {
return nil, err
}
}
// Create the connection manager which will be responsible for
// maintaining persistent outbound connections and also accepting new
// incoming connections
cmgr, err := connmgr.New(&connmgr.Config{
Listeners: listeners,
OnAccept: s.InboundPeerConnected,
RetryDuration: time.Second * 5,
TargetOutbound: 100,
Dial: noiseDial(
nodeKeyECDH, s.cfg.net, s.cfg.ConnectionTimeout,
),
OnConnection: s.OutboundPeerConnected,
})
if err != nil {
return nil, err
}
s.connMgr = cmgr
// Finally, register the subsystems in blockbeat.
s.registerBlockConsumers()
return s, nil
}
// UpdateRoutingConfig is a callback function to update the routing config
// values in the main cfg.
func (s *server) UpdateRoutingConfig(cfg *routing.MissionControlConfig) {
routerCfg := s.cfg.SubRPCServers.RouterRPC
switch c := cfg.Estimator.Config().(type) {
case routing.AprioriConfig:
routerCfg.ProbabilityEstimatorType =
routing.AprioriEstimatorName
targetCfg := routerCfg.AprioriConfig
targetCfg.PenaltyHalfLife = c.PenaltyHalfLife
targetCfg.Weight = c.AprioriWeight
targetCfg.CapacityFraction = c.CapacityFraction
targetCfg.HopProbability = c.AprioriHopProbability
case routing.BimodalConfig:
routerCfg.ProbabilityEstimatorType =
routing.BimodalEstimatorName
targetCfg := routerCfg.BimodalConfig
targetCfg.Scale = int64(c.BimodalScaleMsat)
targetCfg.NodeWeight = c.BimodalNodeWeight
targetCfg.DecayTime = c.BimodalDecayTime
}
routerCfg.MaxMcHistory = cfg.MaxMcHistory
}
// registerBlockConsumers registers the subsystems that consume block events.
// By calling `RegisterQueue`, a list of subsystems are registered in the
// blockbeat for block notifications. When a new block arrives, the subsystems
// in the same queue are notified sequentially, and different queues are
// notified concurrently.
//
// NOTE: To put a subsystem in a different queue, create a slice and pass it to
// a new `RegisterQueue` call.
func (s *server) registerBlockConsumers() {
// In this queue, when a new block arrives, it will be received and
// processed in this order: chainArb -> sweeper -> txPublisher.
consumers := []chainio.Consumer{
s.chainArb,
s.sweeper,
s.txPublisher,
}
s.blockbeatDispatcher.RegisterQueue(consumers)
}
// signAliasUpdate takes a ChannelUpdate and returns the signature. This is
// used for option_scid_alias channels where the ChannelUpdate to be sent back
// may differ from what is on disk.
func (s *server) signAliasUpdate(u *lnwire.ChannelUpdate1) (*ecdsa.Signature,
error) {
data, err := u.DataToSign()
if err != nil {
return nil, err
}
return s.cc.MsgSigner.SignMessage(s.identityKeyLoc, data, true)
}
// createLivenessMonitor creates a set of health checks using our configured
// values and uses these checks to create a liveness monitor. Available
// health checks,
// - chainHealthCheck (will be disabled for --nochainbackend mode)
// - diskCheck
// - tlsHealthCheck
// - torController, only created when tor is enabled.
//
// If a health check has been disabled by setting attempts to 0, our monitor
// will not run it.
func (s *server) createLivenessMonitor(cfg *Config, cc *chainreg.ChainControl,
leaderElector cluster.LeaderElector) {
chainBackendAttempts := cfg.HealthChecks.ChainCheck.Attempts
if cfg.Bitcoin.Node == "nochainbackend" {
srvrLog.Info("Disabling chain backend checks for " +
"nochainbackend mode")
chainBackendAttempts = 0
}
chainHealthCheck := healthcheck.NewObservation(
"chain backend",
cc.HealthCheck,
cfg.HealthChecks.ChainCheck.Interval,
cfg.HealthChecks.ChainCheck.Timeout,
cfg.HealthChecks.ChainCheck.Backoff,
chainBackendAttempts,
)
diskCheck := healthcheck.NewObservation(
"disk space",
func() error {
free, err := healthcheck.AvailableDiskSpaceRatio(
cfg.LndDir,
)
if err != nil {
return err
}
// If we have more free space than we require,
// we return a nil error.
if free > cfg.HealthChecks.DiskCheck.RequiredRemaining {
return nil
}
return fmt.Errorf("require: %v free space, got: %v",
cfg.HealthChecks.DiskCheck.RequiredRemaining,
free)
},
cfg.HealthChecks.DiskCheck.Interval,
cfg.HealthChecks.DiskCheck.Timeout,
cfg.HealthChecks.DiskCheck.Backoff,
cfg.HealthChecks.DiskCheck.Attempts,
)
tlsHealthCheck := healthcheck.NewObservation(
"tls",
func() error {
expired, expTime, err := s.tlsManager.IsCertExpired(
s.cc.KeyRing,
)
if err != nil {
return err
}
if expired {
return fmt.Errorf("TLS certificate is "+
"expired as of %v", expTime)
}
// If the certificate is not outdated, no error needs
// to be returned
return nil
},
cfg.HealthChecks.TLSCheck.Interval,
cfg.HealthChecks.TLSCheck.Timeout,
cfg.HealthChecks.TLSCheck.Backoff,
cfg.HealthChecks.TLSCheck.Attempts,
)
checks := []*healthcheck.Observation{
chainHealthCheck, diskCheck, tlsHealthCheck,
}
// If Tor is enabled, add the healthcheck for tor connection.
if s.torController != nil {
torConnectionCheck := healthcheck.NewObservation(
"tor connection",
func() error {
return healthcheck.CheckTorServiceStatus(
s.torController,
s.createNewHiddenService,
)
},
cfg.HealthChecks.TorConnection.Interval,
cfg.HealthChecks.TorConnection.Timeout,
cfg.HealthChecks.TorConnection.Backoff,
cfg.HealthChecks.TorConnection.Attempts,
)
checks = append(checks, torConnectionCheck)
}
// If remote signing is enabled, add the healthcheck for the remote
// signing RPC interface.
if s.cfg.RemoteSigner != nil && s.cfg.RemoteSigner.Enable {
// Because we have two cascading timeouts here, we need to add
// some slack to the "outer" one of them in case the "inner"
// returns exactly on time.
overhead := time.Millisecond * 10
remoteSignerConnectionCheck := healthcheck.NewObservation(
"remote signer connection",
rpcwallet.HealthCheck(
s.cfg.RemoteSigner,
// For the health check we might to be even
// stricter than the initial/normal connect, so
// we use the health check timeout here.
cfg.HealthChecks.RemoteSigner.Timeout,
),
cfg.HealthChecks.RemoteSigner.Interval,
cfg.HealthChecks.RemoteSigner.Timeout+overhead,
cfg.HealthChecks.RemoteSigner.Backoff,
cfg.HealthChecks.RemoteSigner.Attempts,
)
checks = append(checks, remoteSignerConnectionCheck)
}
// If we have a leader elector, we add a health check to ensure we are
// still the leader. During normal operation, we should always be the
// leader, but there are circumstances where this may change, such as
// when we lose network connectivity for long enough expiring out lease.
if leaderElector != nil {
leaderCheck := healthcheck.NewObservation(
"leader status",
func() error {
// Check if we are still the leader. Note that
// we don't need to use a timeout context here
// as the healthcheck observer will handle the
// timeout case for us.
timeoutCtx, cancel := context.WithTimeout(
context.Background(),
cfg.HealthChecks.LeaderCheck.Timeout,
)
defer cancel()
leader, err := leaderElector.IsLeader(
timeoutCtx,
)
if err != nil {
return fmt.Errorf("unable to check if "+
"still leader: %v", err)
}
if !leader {
srvrLog.Debug("Not the current leader")
return fmt.Errorf("not the current " +
"leader")
}
return nil
},
cfg.HealthChecks.LeaderCheck.Interval,
cfg.HealthChecks.LeaderCheck.Timeout,
cfg.HealthChecks.LeaderCheck.Backoff,
cfg.HealthChecks.LeaderCheck.Attempts,
)
checks = append(checks, leaderCheck)
}
// If we have not disabled all of our health checks, we create a
// liveness monitor with our configured checks.
s.livenessMonitor = healthcheck.NewMonitor(
&healthcheck.Config{
Checks: checks,
Shutdown: srvrLog.Criticalf,
},
)
}
// Started returns true if the server has been started, and false otherwise.
// NOTE: This function is safe for concurrent access.
func (s *server) Started() bool {
return atomic.LoadInt32(&s.active) != 0
}
// cleaner is used to aggregate "cleanup" functions during an operation that
// starts several subsystems. In case one of the subsystem fails to start
// and a proper resource cleanup is required, the "run" method achieves this
// by running all these added "cleanup" functions.
type cleaner []func() error
// add is used to add a cleanup function to be called when
// the run function is executed.
func (c cleaner) add(cleanup func() error) cleaner {
return append(c, cleanup)
}
// run is used to run all the previousely added cleanup functions.
func (c cleaner) run() {
for i := len(c) - 1; i >= 0; i-- {
if err := c[i](); err != nil {
srvrLog.Errorf("Cleanup failed: %v", err)
}
}
}
// startLowLevelServices starts the low-level services of the server. These
// services must be started successfully before running the main server. The
// services are,
// 1. the chain notifier.
//
// TODO(yy): identify and add more low-level services here.
func (s *server) startLowLevelServices() error {
var startErr error
cleanup := cleaner{}
cleanup = cleanup.add(s.cc.ChainNotifier.Stop)
if err := s.cc.ChainNotifier.Start(); err != nil {
startErr = err
}
if startErr != nil {
cleanup.run()
}
return startErr
}
// Start starts the main daemon server, all requested listeners, and any helper
// goroutines.
// NOTE: This function is safe for concurrent access.
//
//nolint:funlen
func (s *server) Start() error {
// Get the current blockbeat.
beat, err := s.getStartingBeat()
if err != nil {
return err
}
var startErr error
// If one sub system fails to start, the following code ensures that the
// previous started ones are stopped. It also ensures a proper wallet
// shutdown which is important for releasing its resources (boltdb, etc...)
cleanup := cleaner{}
s.start.Do(func() {
cleanup = cleanup.add(s.customMessageServer.Stop)
if err := s.customMessageServer.Start(); err != nil {
startErr = err
return
}
if s.hostAnn != nil {
cleanup = cleanup.add(s.hostAnn.Stop)
if err := s.hostAnn.Start(); err != nil {
startErr = err
return
}
}
if s.livenessMonitor != nil {
cleanup = cleanup.add(s.livenessMonitor.Stop)
if err := s.livenessMonitor.Start(); err != nil {
startErr = err
return
}
}
// Start the notification server. This is used so channel
// management goroutines can be notified when a funding
// transaction reaches a sufficient number of confirmations, or
// when the input for the funding transaction is spent in an
// attempt at an uncooperative close by the counterparty.
cleanup = cleanup.add(s.sigPool.Stop)
if err := s.sigPool.Start(); err != nil {
startErr = err
return
}
cleanup = cleanup.add(s.writePool.Stop)
if err := s.writePool.Start(); err != nil {
startErr = err
return
}
cleanup = cleanup.add(s.readPool.Stop)
if err := s.readPool.Start(); err != nil {
startErr = err
return
}
cleanup = cleanup.add(s.cc.BestBlockTracker.Stop)
if err := s.cc.BestBlockTracker.Start(); err != nil {
startErr = err
return
}
cleanup = cleanup.add(s.channelNotifier.Stop)
if err := s.channelNotifier.Start(); err != nil {
startErr = err
return
}
cleanup = cleanup.add(func() error {
return s.peerNotifier.Stop()
})
if err := s.peerNotifier.Start(); err != nil {
startErr = err
return
}
cleanup = cleanup.add(s.htlcNotifier.Stop)
if err := s.htlcNotifier.Start(); err != nil {
startErr = err
return
}
if s.towerClientMgr != nil {
cleanup = cleanup.add(s.towerClientMgr.Stop)
if err := s.towerClientMgr.Start(); err != nil {
startErr = err
return
}
}
cleanup = cleanup.add(s.txPublisher.Stop)
if err := s.txPublisher.Start(beat); err != nil {
startErr = err
return
}
cleanup = cleanup.add(s.sweeper.Stop)
if err := s.sweeper.Start(beat); err != nil {
startErr = err
return
}
cleanup = cleanup.add(s.utxoNursery.Stop)
if err := s.utxoNursery.Start(); err != nil {
startErr = err
return
}
cleanup = cleanup.add(s.breachArbitrator.Stop)
if err := s.breachArbitrator.Start(); err != nil {
startErr = err
return
}
cleanup = cleanup.add(s.fundingMgr.Stop)
if err := s.fundingMgr.Start(); err != nil {
startErr = err
return
}
// htlcSwitch must be started before chainArb since the latter
// relies on htlcSwitch to deliver resolution message upon
// start.
cleanup = cleanup.add(s.htlcSwitch.Stop)
if err := s.htlcSwitch.Start(); err != nil {
startErr = err
return
}
cleanup = cleanup.add(s.interceptableSwitch.Stop)
if err := s.interceptableSwitch.Start(); err != nil {
startErr = err
return
}
cleanup = cleanup.add(s.invoiceHtlcModifier.Stop)
if err := s.invoiceHtlcModifier.Start(); err != nil {
startErr = err
return
}
cleanup = cleanup.add(s.chainArb.Stop)
if err := s.chainArb.Start(beat); err != nil {
startErr = err
return
}
cleanup = cleanup.add(s.graphDB.Stop)
if err := s.graphDB.Start(); err != nil {
startErr = err
return
}
cleanup = cleanup.add(s.graphBuilder.Stop)
if err := s.graphBuilder.Start(); err != nil {
startErr = err
return
}
cleanup = cleanup.add(s.chanRouter.Stop)
if err := s.chanRouter.Start(); err != nil {
startErr = err
return
}
// The authGossiper depends on the chanRouter and therefore
// should be started after it.
cleanup = cleanup.add(s.authGossiper.Stop)
if err := s.authGossiper.Start(); err != nil {
startErr = err
return
}
cleanup = cleanup.add(s.invoices.Stop)
if err := s.invoices.Start(); err != nil {
startErr = err
return
}
cleanup = cleanup.add(s.sphinx.Stop)
if err := s.sphinx.Start(); err != nil {
startErr = err
return
}
cleanup = cleanup.add(s.chanStatusMgr.Stop)
if err := s.chanStatusMgr.Start(); err != nil {
startErr = err
return
}
cleanup = cleanup.add(s.chanEventStore.Stop)
if err := s.chanEventStore.Start(); err != nil {
startErr = err
return
}
cleanup.add(func() error {
s.missionController.StopStoreTickers()
return nil
})
s.missionController.RunStoreTickers()
// Before we start the connMgr, we'll check to see if we have
// any backups to recover. We do this now as we want to ensure
// that have all the information we need to handle channel
// recovery _before_ we even accept connections from any peers.
chanRestorer := &chanDBRestorer{
db: s.chanStateDB,
secretKeys: s.cc.KeyRing,
chainArb: s.chainArb,
}
if len(s.chansToRestore.PackedSingleChanBackups) != 0 {
_, err := chanbackup.UnpackAndRecoverSingles(
s.chansToRestore.PackedSingleChanBackups,
s.cc.KeyRing, chanRestorer, s,
)
if err != nil {
startErr = fmt.Errorf("unable to unpack single "+
"backups: %v", err)
return
}
}
if len(s.chansToRestore.PackedMultiChanBackup) != 0 {
_, err := chanbackup.UnpackAndRecoverMulti(
s.chansToRestore.PackedMultiChanBackup,
s.cc.KeyRing, chanRestorer, s,
)
if err != nil {
startErr = fmt.Errorf("unable to unpack chan "+
"backup: %v", err)
return
}
}
// chanSubSwapper must be started after the `channelNotifier`
// because it depends on channel events as a synchronization
// point.
cleanup = cleanup.add(s.chanSubSwapper.Stop)
if err := s.chanSubSwapper.Start(); err != nil {
startErr = err
return
}
if s.torController != nil {
cleanup = cleanup.add(s.torController.Stop)
if err := s.createNewHiddenService(); err != nil {
startErr = err
return
}
}
if s.natTraversal != nil {
s.wg.Add(1)
go s.watchExternalIP()
}
// Start connmgr last to prevent connections before init.
cleanup = cleanup.add(func() error {
s.connMgr.Stop()
return nil
})
// RESOLVE: s.connMgr.Start() is called here, but
// brontide.NewListener() is called in newServer. This means
// that we are actually listening and partially accepting
// inbound connections even before the connMgr starts.
//
// TODO(yy): move the log into the connMgr's `Start` method.
srvrLog.Info("connMgr starting...")
s.connMgr.Start()
srvrLog.Debug("connMgr started")
// If peers are specified as a config option, we'll add those
// peers first.
for _, peerAddrCfg := range s.cfg.AddPeers {
parsedPubkey, parsedHost, err := lncfg.ParseLNAddressPubkey(
peerAddrCfg,
)
if err != nil {
startErr = fmt.Errorf("unable to parse peer "+
"pubkey from config: %v", err)
return
}
addr, err := parseAddr(parsedHost, s.cfg.net)
if err != nil {
startErr = fmt.Errorf("unable to parse peer "+
"address provided as a config option: "+
"%v", err)
return
}
peerAddr := &lnwire.NetAddress{
IdentityKey: parsedPubkey,
Address: addr,
ChainNet: s.cfg.ActiveNetParams.Net,
}
err = s.ConnectToPeer(
peerAddr, true,
s.cfg.ConnectionTimeout,
)
if err != nil {
startErr = fmt.Errorf("unable to connect to "+
"peer address provided as a config "+
"option: %v", err)
return
}
}
// Subscribe to NodeAnnouncements that advertise new addresses
// our persistent peers.
if err := s.updatePersistentPeerAddrs(); err != nil {
srvrLog.Errorf("Failed to update persistent peer "+
"addr: %v", err)
startErr = err
return
}
// With all the relevant sub-systems started, we'll now attempt
// to establish persistent connections to our direct channel
// collaborators within the network. Before doing so however,
// we'll prune our set of link nodes found within the database
// to ensure we don't reconnect to any nodes we no longer have
// open channels with.
if err := s.chanStateDB.PruneLinkNodes(); err != nil {
srvrLog.Errorf("Failed to prune link nodes: %v", err)
startErr = err
return
}
if err := s.establishPersistentConnections(); err != nil {
srvrLog.Errorf("Failed to establish persistent "+
"connections: %v", err)
}
// setSeedList is a helper function that turns multiple DNS seed
// server tuples from the command line or config file into the
// data structure we need and does a basic formal sanity check
// in the process.
setSeedList := func(tuples []string, genesisHash chainhash.Hash) {
if len(tuples) == 0 {
return
}
result := make([][2]string, len(tuples))
for idx, tuple := range tuples {
tuple = strings.TrimSpace(tuple)
if len(tuple) == 0 {
return
}
servers := strings.Split(tuple, ",")
if len(servers) > 2 || len(servers) == 0 {
srvrLog.Warnf("Ignoring invalid DNS "+
"seed tuple: %v", servers)
return
}
copy(result[idx][:], servers)
}
chainreg.ChainDNSSeeds[genesisHash] = result
}
// Let users overwrite the DNS seed nodes. We only allow them
// for bitcoin mainnet/testnet/signet.
if s.cfg.Bitcoin.MainNet {
setSeedList(
s.cfg.Bitcoin.DNSSeeds,
chainreg.BitcoinMainnetGenesis,
)
}
if s.cfg.Bitcoin.TestNet3 {
setSeedList(
s.cfg.Bitcoin.DNSSeeds,
chainreg.BitcoinTestnetGenesis,
)
}
if s.cfg.Bitcoin.TestNet4 {
setSeedList(
s.cfg.Bitcoin.DNSSeeds,
chainreg.BitcoinTestnet4Genesis,
)
}
if s.cfg.Bitcoin.SigNet {
setSeedList(
s.cfg.Bitcoin.DNSSeeds,
chainreg.BitcoinSignetGenesis,
)
}
// If network bootstrapping hasn't been disabled, then we'll
// configure the set of active bootstrappers, and launch a
// dedicated goroutine to maintain a set of persistent
// connections.
if shouldPeerBootstrap(s.cfg) {
bootstrappers, err := initNetworkBootstrappers(s)
if err != nil {
startErr = err
return
}
s.wg.Add(1)
go s.peerBootstrapper(defaultMinPeers, bootstrappers)
} else {
srvrLog.Infof("Auto peer bootstrapping is disabled")
}
// Start the blockbeat after all other subsystems have been
// started so they are ready to receive new blocks.
cleanup = cleanup.add(func() error {
s.blockbeatDispatcher.Stop()
return nil
})
if err := s.blockbeatDispatcher.Start(); err != nil {
startErr = err
return
}
// Set the active flag now that we've completed the full
// startup.
atomic.StoreInt32(&s.active, 1)
})
if startErr != nil {
cleanup.run()
}
return startErr
}
// Stop gracefully shutsdown the main daemon server. This function will signal
// any active goroutines, or helper objects to exit, then blocks until they've
// all successfully exited. Additionally, any/all listeners are closed.
// NOTE: This function is safe for concurrent access.
func (s *server) Stop() error {
s.stop.Do(func() {
atomic.StoreInt32(&s.stopping, 1)
close(s.quit)
// Shutdown connMgr first to prevent conns during shutdown.
s.connMgr.Stop()
// Stop dispatching blocks to other systems immediately.
s.blockbeatDispatcher.Stop()
// Shutdown the wallet, funding manager, and the rpc server.
if err := s.chanStatusMgr.Stop(); err != nil {
srvrLog.Warnf("failed to stop chanStatusMgr: %v", err)
}
if err := s.htlcSwitch.Stop(); err != nil {
srvrLog.Warnf("failed to stop htlcSwitch: %v", err)
}
if err := s.sphinx.Stop(); err != nil {
srvrLog.Warnf("failed to stop sphinx: %v", err)
}
if err := s.invoices.Stop(); err != nil {
srvrLog.Warnf("failed to stop invoices: %v", err)
}
if err := s.interceptableSwitch.Stop(); err != nil {
srvrLog.Warnf("failed to stop interceptable "+
"switch: %v", err)
}
if err := s.invoiceHtlcModifier.Stop(); err != nil {
srvrLog.Warnf("failed to stop htlc invoices "+
"modifier: %v", err)
}
if err := s.chanRouter.Stop(); err != nil {
srvrLog.Warnf("failed to stop chanRouter: %v", err)
}
if err := s.graphBuilder.Stop(); err != nil {
srvrLog.Warnf("failed to stop graphBuilder %v", err)
}
if err := s.graphDB.Stop(); err != nil {
srvrLog.Warnf("failed to stop graphDB %v", err)
}
if err := s.chainArb.Stop(); err != nil {
srvrLog.Warnf("failed to stop chainArb: %v", err)
}
if err := s.fundingMgr.Stop(); err != nil {
srvrLog.Warnf("failed to stop fundingMgr: %v", err)
}
if err := s.breachArbitrator.Stop(); err != nil {
srvrLog.Warnf("failed to stop breachArbitrator: %v",
err)
}
if err := s.utxoNursery.Stop(); err != nil {
srvrLog.Warnf("failed to stop utxoNursery: %v", err)
}
if err := s.authGossiper.Stop(); err != nil {
srvrLog.Warnf("failed to stop authGossiper: %v", err)
}
if err := s.sweeper.Stop(); err != nil {
srvrLog.Warnf("failed to stop sweeper: %v", err)
}
if err := s.txPublisher.Stop(); err != nil {
srvrLog.Warnf("failed to stop txPublisher: %v", err)
}
if err := s.channelNotifier.Stop(); err != nil {
srvrLog.Warnf("failed to stop channelNotifier: %v", err)
}
if err := s.peerNotifier.Stop(); err != nil {
srvrLog.Warnf("failed to stop peerNotifier: %v", err)
}
if err := s.htlcNotifier.Stop(); err != nil {
srvrLog.Warnf("failed to stop htlcNotifier: %v", err)
}
// Update channel.backup file. Make sure to do it before
// stopping chanSubSwapper.
singles, err := chanbackup.FetchStaticChanBackups(
s.chanStateDB, s.addrSource,
)
if err != nil {
srvrLog.Warnf("failed to fetch channel states: %v",
err)
} else {
err := s.chanSubSwapper.ManualUpdate(singles)
if err != nil {
srvrLog.Warnf("Manual update of channel "+
"backup failed: %v", err)
}
}
if err := s.chanSubSwapper.Stop(); err != nil {
srvrLog.Warnf("failed to stop chanSubSwapper: %v", err)
}
if err := s.cc.ChainNotifier.Stop(); err != nil {
srvrLog.Warnf("Unable to stop ChainNotifier: %v", err)
}
if err := s.cc.BestBlockTracker.Stop(); err != nil {
srvrLog.Warnf("Unable to stop BestBlockTracker: %v",
err)
}
if err := s.chanEventStore.Stop(); err != nil {
srvrLog.Warnf("Unable to stop ChannelEventStore: %v",
err)
}
s.missionController.StopStoreTickers()
// Disconnect from each active peers to ensure that
// peerTerminationWatchers signal completion to each peer.
for _, peer := range s.Peers() {
err := s.DisconnectPeer(peer.IdentityKey())
if err != nil {
srvrLog.Warnf("could not disconnect peer: %v"+
"received error: %v", peer.IdentityKey(),
err,
)
}
}
// Now that all connections have been torn down, stop the tower
// client which will reliably flush all queued states to the
// tower. If this is halted for any reason, the force quit timer
// will kick in and abort to allow this method to return.
if s.towerClientMgr != nil {
if err := s.towerClientMgr.Stop(); err != nil {
srvrLog.Warnf("Unable to shut down tower "+
"client manager: %v", err)
}
}
if s.hostAnn != nil {
if err := s.hostAnn.Stop(); err != nil {
srvrLog.Warnf("unable to shut down host "+
"annoucner: %v", err)
}
}
if s.livenessMonitor != nil {
if err := s.livenessMonitor.Stop(); err != nil {
srvrLog.Warnf("unable to shutdown liveness "+
"monitor: %v", err)
}
}
// Wait for all lingering goroutines to quit.
srvrLog.Debug("Waiting for server to shutdown...")
s.wg.Wait()
srvrLog.Debug("Stopping buffer pools...")
s.sigPool.Stop()
s.writePool.Stop()
s.readPool.Stop()
})
return nil
}
// Stopped returns true if the server has been instructed to shutdown.
// NOTE: This function is safe for concurrent access.
func (s *server) Stopped() bool {
return atomic.LoadInt32(&s.stopping) != 0
}
// configurePortForwarding attempts to set up port forwarding for the different
// ports that the server will be listening on.
//
// NOTE: This should only be used when using some kind of NAT traversal to
// automatically set up forwarding rules.
func (s *server) configurePortForwarding(ports ...uint16) ([]string, error) {
ip, err := s.natTraversal.ExternalIP()
if err != nil {
return nil, err
}
s.lastDetectedIP = ip
externalIPs := make([]string, 0, len(ports))
for _, port := range ports {
if err := s.natTraversal.AddPortMapping(port); err != nil {
srvrLog.Debugf("Unable to forward port %d: %v", port, err)
continue
}
hostIP := fmt.Sprintf("%v:%d", ip, port)
externalIPs = append(externalIPs, hostIP)
}
return externalIPs, nil
}
// removePortForwarding attempts to clear the forwarding rules for the different
// ports the server is currently listening on.
//
// NOTE: This should only be used when using some kind of NAT traversal to
// automatically set up forwarding rules.
func (s *server) removePortForwarding() {
forwardedPorts := s.natTraversal.ForwardedPorts()
for _, port := range forwardedPorts {
if err := s.natTraversal.DeletePortMapping(port); err != nil {
srvrLog.Errorf("Unable to remove forwarding rules for "+
"port %d: %v", port, err)
}
}
}
// watchExternalIP continuously checks for an updated external IP address every
// 15 minutes. Once a new IP address has been detected, it will automatically
// handle port forwarding rules and send updated node announcements to the
// currently connected peers.
//
// NOTE: This MUST be run as a goroutine.
func (s *server) watchExternalIP() {
defer s.wg.Done()
// Before exiting, we'll make sure to remove the forwarding rules set
// up by the server.
defer s.removePortForwarding()
// Keep track of the external IPs set by the user to avoid replacing
// them when detecting a new IP.
ipsSetByUser := make(map[string]struct{})
for _, ip := range s.cfg.ExternalIPs {
ipsSetByUser[ip.String()] = struct{}{}
}
forwardedPorts := s.natTraversal.ForwardedPorts()
ticker := time.NewTicker(15 * time.Minute)
defer ticker.Stop()
out:
for {
select {
case <-ticker.C:
// We'll start off by making sure a new IP address has
// been detected.
ip, err := s.natTraversal.ExternalIP()
if err != nil {
srvrLog.Debugf("Unable to retrieve the "+
"external IP address: %v", err)
continue
}
// Periodically renew the NAT port forwarding.
for _, port := range forwardedPorts {
err := s.natTraversal.AddPortMapping(port)
if err != nil {
srvrLog.Warnf("Unable to automatically "+
"re-create port forwarding using %s: %v",
s.natTraversal.Name(), err)
} else {
srvrLog.Debugf("Automatically re-created "+
"forwarding for port %d using %s to "+
"advertise external IP",
port, s.natTraversal.Name())
}
}
if ip.Equal(s.lastDetectedIP) {
continue
}
srvrLog.Infof("Detected new external IP address %s", ip)
// Next, we'll craft the new addresses that will be
// included in the new node announcement and advertised
// to the network. Each address will consist of the new
// IP detected and one of the currently advertised
// ports.
var newAddrs []net.Addr
for _, port := range forwardedPorts {
hostIP := fmt.Sprintf("%v:%d", ip, port)
addr, err := net.ResolveTCPAddr("tcp", hostIP)
if err != nil {
srvrLog.Debugf("Unable to resolve "+
"host %v: %v", addr, err)
continue
}
newAddrs = append(newAddrs, addr)
}
// Skip the update if we weren't able to resolve any of
// the new addresses.
if len(newAddrs) == 0 {
srvrLog.Debug("Skipping node announcement " +
"update due to not being able to " +
"resolve any new addresses")
continue
}
// Now, we'll need to update the addresses in our node's
// announcement in order to propagate the update
// throughout the network. We'll only include addresses
// that have a different IP from the previous one, as
// the previous IP is no longer valid.
currentNodeAnn := s.getNodeAnnouncement()
for _, addr := range currentNodeAnn.Addresses {
host, _, err := net.SplitHostPort(addr.String())
if err != nil {
srvrLog.Debugf("Unable to determine "+
"host from address %v: %v",
addr, err)
continue
}
// We'll also make sure to include external IPs
// set manually by the user.
_, setByUser := ipsSetByUser[addr.String()]
if setByUser || host != s.lastDetectedIP.String() {
newAddrs = append(newAddrs, addr)
}
}
// Then, we'll generate a new timestamped node
// announcement with the updated addresses and broadcast
// it to our peers.
newNodeAnn, err := s.genNodeAnnouncement(
nil, netann.NodeAnnSetAddrs(newAddrs),
)
if err != nil {
srvrLog.Debugf("Unable to generate new node "+
"announcement: %v", err)
continue
}
err = s.BroadcastMessage(nil, &newNodeAnn)
if err != nil {
srvrLog.Debugf("Unable to broadcast new node "+
"announcement to peers: %v", err)
continue
}
// Finally, update the last IP seen to the current one.
s.lastDetectedIP = ip
case <-s.quit:
break out
}
}
}
// initNetworkBootstrappers initializes a set of network peer bootstrappers
// based on the server, and currently active bootstrap mechanisms as defined
// within the current configuration.
func initNetworkBootstrappers(s *server) ([]discovery.NetworkPeerBootstrapper, error) {
srvrLog.Infof("Initializing peer network bootstrappers!")
var bootStrappers []discovery.NetworkPeerBootstrapper
// First, we'll create an instance of the ChannelGraphBootstrapper as
// this can be used by default if we've already partially seeded the
// network.
chanGraph := autopilot.ChannelGraphFromDatabase(s.graphDB)
graphBootstrapper, err := discovery.NewGraphBootstrapper(chanGraph)
if err != nil {
return nil, err
}
bootStrappers = append(bootStrappers, graphBootstrapper)
// If this isn't simnet mode, then one of our additional bootstrapping
// sources will be the set of running DNS seeds.
if !s.cfg.Bitcoin.SimNet {
dnsSeeds, ok := chainreg.ChainDNSSeeds[*s.cfg.ActiveNetParams.GenesisHash]
// If we have a set of DNS seeds for this chain, then we'll add
// it as an additional bootstrapping source.
if ok {
srvrLog.Infof("Creating DNS peer bootstrapper with "+
"seeds: %v", dnsSeeds)
dnsBootStrapper := discovery.NewDNSSeedBootstrapper(
dnsSeeds, s.cfg.net, s.cfg.ConnectionTimeout,
)
bootStrappers = append(bootStrappers, dnsBootStrapper)
}
}
return bootStrappers, nil
}
// createBootstrapIgnorePeers creates a map of peers that the bootstrap process
// needs to ignore, which is made of three parts,
// - the node itself needs to be skipped as it doesn't make sense to connect
// to itself.
// - the peers that already have connections with, as in s.peersByPub.
// - the peers that we are attempting to connect, as in s.persistentPeers.
func (s *server) createBootstrapIgnorePeers() map[autopilot.NodeID]struct{} {
s.mu.RLock()
defer s.mu.RUnlock()
ignore := make(map[autopilot.NodeID]struct{})
// We should ignore ourselves from bootstrapping.
selfKey := autopilot.NewNodeID(s.identityECDH.PubKey())
ignore[selfKey] = struct{}{}
// Ignore all connected peers.
for _, peer := range s.peersByPub {
nID := autopilot.NewNodeID(peer.IdentityKey())
ignore[nID] = struct{}{}
}
// Ignore all persistent peers as they have a dedicated reconnecting
// process.
for pubKeyStr := range s.persistentPeers {
var nID autopilot.NodeID
copy(nID[:], []byte(pubKeyStr))
ignore[nID] = struct{}{}
}
return ignore
}
// peerBootstrapper is a goroutine which is tasked with attempting to establish
// and maintain a target minimum number of outbound connections. With this
// invariant, we ensure that our node is connected to a diverse set of peers
// and that nodes newly joining the network receive an up to date network view
// as soon as possible.
func (s *server) peerBootstrapper(numTargetPeers uint32,
bootstrappers []discovery.NetworkPeerBootstrapper) {
defer s.wg.Done()
// Before we continue, init the ignore peers map.
ignoreList := s.createBootstrapIgnorePeers()
// We'll start off by aggressively attempting connections to peers in
// order to be a part of the network as soon as possible.
s.initialPeerBootstrap(ignoreList, numTargetPeers, bootstrappers)
// Once done, we'll attempt to maintain our target minimum number of
// peers.
//
// We'll use a 15 second backoff, and double the time every time an
// epoch fails up to a ceiling.
backOff := time.Second * 15
// We'll create a new ticker to wake us up every 15 seconds so we can
// see if we've reached our minimum number of peers.
sampleTicker := time.NewTicker(backOff)
defer sampleTicker.Stop()
// We'll use the number of attempts and errors to determine if we need
// to increase the time between discovery epochs.
var epochErrors uint32 // To be used atomically.
var epochAttempts uint32
for {
select {
// The ticker has just woken us up, so we'll need to check if
// we need to attempt to connect our to any more peers.
case <-sampleTicker.C:
// Obtain the current number of peers, so we can gauge
// if we need to sample more peers or not.
s.mu.RLock()
numActivePeers := uint32(len(s.peersByPub))
s.mu.RUnlock()
// If we have enough peers, then we can loop back
// around to the next round as we're done here.
if numActivePeers >= numTargetPeers {
continue
}
// If all of our attempts failed during this last back
// off period, then will increase our backoff to 5
// minute ceiling to avoid an excessive number of
// queries
//
// TODO(roasbeef): add reverse policy too?
if epochAttempts > 0 &&
atomic.LoadUint32(&epochErrors) >= epochAttempts {
sampleTicker.Stop()
backOff *= 2
if backOff > bootstrapBackOffCeiling {
backOff = bootstrapBackOffCeiling
}
srvrLog.Debugf("Backing off peer bootstrapper to "+
"%v", backOff)
sampleTicker = time.NewTicker(backOff)
continue
}
atomic.StoreUint32(&epochErrors, 0)
epochAttempts = 0
// Since we know need more peers, we'll compute the
// exact number we need to reach our threshold.
numNeeded := numTargetPeers - numActivePeers
srvrLog.Debugf("Attempting to obtain %v more network "+
"peers", numNeeded)
// With the number of peers we need calculated, we'll
// query the network bootstrappers to sample a set of
// random addrs for us.
//
// Before we continue, get a copy of the ignore peers
// map.
ignoreList = s.createBootstrapIgnorePeers()
peerAddrs, err := discovery.MultiSourceBootstrap(
ignoreList, numNeeded*2, bootstrappers...,
)
if err != nil {
srvrLog.Errorf("Unable to retrieve bootstrap "+
"peers: %v", err)
continue
}
// Finally, we'll launch a new goroutine for each
// prospective peer candidates.
for _, addr := range peerAddrs {
epochAttempts++
go func(a *lnwire.NetAddress) {
// TODO(roasbeef): can do AS, subnet,
// country diversity, etc
errChan := make(chan error, 1)
s.connectToPeer(
a, errChan,
s.cfg.ConnectionTimeout,
)
select {
case err := <-errChan:
if err == nil {
return
}
srvrLog.Errorf("Unable to "+
"connect to %v: %v",
a, err)
atomic.AddUint32(&epochErrors, 1)
case <-s.quit:
}
}(addr)
}
case <-s.quit:
return
}
}
}
// bootstrapBackOffCeiling is the maximum amount of time we'll wait between
// failed attempts to locate a set of bootstrap peers. We'll slowly double our
// query back off each time we encounter a failure.
const bootstrapBackOffCeiling = time.Minute * 5
// initialPeerBootstrap attempts to continuously connect to peers on startup
// until the target number of peers has been reached. This ensures that nodes
// receive an up to date network view as soon as possible.
func (s *server) initialPeerBootstrap(ignore map[autopilot.NodeID]struct{},
numTargetPeers uint32,
bootstrappers []discovery.NetworkPeerBootstrapper) {
srvrLog.Debugf("Init bootstrap with targetPeers=%v, bootstrappers=%v, "+
"ignore=%v", numTargetPeers, len(bootstrappers), len(ignore))
// We'll start off by waiting 2 seconds between failed attempts, then
// double each time we fail until we hit the bootstrapBackOffCeiling.
var delaySignal <-chan time.Time
delayTime := time.Second * 2
// As want to be more aggressive, we'll use a lower back off celling
// then the main peer bootstrap logic.
backOffCeiling := bootstrapBackOffCeiling / 5
for attempts := 0; ; attempts++ {
// Check if the server has been requested to shut down in order
// to prevent blocking.
if s.Stopped() {
return
}
// We can exit our aggressive initial peer bootstrapping stage
// if we've reached out target number of peers.
s.mu.RLock()
numActivePeers := uint32(len(s.peersByPub))
s.mu.RUnlock()
if numActivePeers >= numTargetPeers {
return
}
if attempts > 0 {
srvrLog.Debugf("Waiting %v before trying to locate "+
"bootstrap peers (attempt #%v)", delayTime,
attempts)
// We've completed at least one iterating and haven't
// finished, so we'll start to insert a delay period
// between each attempt.
delaySignal = time.After(delayTime)
select {
case <-delaySignal:
case <-s.quit:
return
}
// After our delay, we'll double the time we wait up to
// the max back off period.
delayTime *= 2
if delayTime > backOffCeiling {
delayTime = backOffCeiling
}
}
// Otherwise, we'll request for the remaining number of peers
// in order to reach our target.
peersNeeded := numTargetPeers - numActivePeers
bootstrapAddrs, err := discovery.MultiSourceBootstrap(
ignore, peersNeeded, bootstrappers...,
)
if err != nil {
srvrLog.Errorf("Unable to retrieve initial bootstrap "+
"peers: %v", err)
continue
}
// Then, we'll attempt to establish a connection to the
// different peer addresses retrieved by our bootstrappers.
var wg sync.WaitGroup
for _, bootstrapAddr := range bootstrapAddrs {
wg.Add(1)
go func(addr *lnwire.NetAddress) {
defer wg.Done()
errChan := make(chan error, 1)
go s.connectToPeer(
addr, errChan, s.cfg.ConnectionTimeout,
)
// We'll only allow this connection attempt to
// take up to 3 seconds. This allows us to move
// quickly by discarding peers that are slowing
// us down.
select {
case err := <-errChan:
if err == nil {
return
}
srvrLog.Errorf("Unable to connect to "+
"%v: %v", addr, err)
// TODO: tune timeout? 3 seconds might be *too*
// aggressive but works well.
case <-time.After(3 * time.Second):
srvrLog.Tracef("Skipping peer %v due "+
"to not establishing a "+
"connection within 3 seconds",
addr)
case <-s.quit:
}
}(bootstrapAddr)
}
wg.Wait()
}
}
// createNewHiddenService automatically sets up a v2 or v3 onion service in
// order to listen for inbound connections over Tor.
func (s *server) createNewHiddenService() error {
// Determine the different ports the server is listening on. The onion
// service's virtual port will map to these ports and one will be picked
// at random when the onion service is being accessed.
listenPorts := make([]int, 0, len(s.listenAddrs))
for _, listenAddr := range s.listenAddrs {
port := listenAddr.(*net.TCPAddr).Port
listenPorts = append(listenPorts, port)
}
encrypter, err := lnencrypt.KeyRingEncrypter(s.cc.KeyRing)
if err != nil {
return err
}
// Once the port mapping has been set, we can go ahead and automatically
// create our onion service. The service's private key will be saved to
// disk in order to regain access to this service when restarting `lnd`.
onionCfg := tor.AddOnionConfig{
VirtualPort: defaultPeerPort,
TargetPorts: listenPorts,
Store: tor.NewOnionFile(
s.cfg.Tor.PrivateKeyPath, 0600, s.cfg.Tor.EncryptKey,
encrypter,
),
}
switch {
case s.cfg.Tor.V2:
onionCfg.Type = tor.V2
case s.cfg.Tor.V3:
onionCfg.Type = tor.V3
}
addr, err := s.torController.AddOnion(onionCfg)
if err != nil {
return err
}
// Now that the onion service has been created, we'll add the onion
// address it can be reached at to our list of advertised addresses.
newNodeAnn, err := s.genNodeAnnouncement(
nil, func(currentAnn *lnwire.NodeAnnouncement) {
currentAnn.Addresses = append(currentAnn.Addresses, addr)
},
)
if err != nil {
return fmt.Errorf("unable to generate new node "+
"announcement: %v", err)
}
// Finally, we'll update the on-disk version of our announcement so it
// will eventually propagate to nodes in the network.
selfNode := &models.LightningNode{
HaveNodeAnnouncement: true,
LastUpdate: time.Unix(int64(newNodeAnn.Timestamp), 0),
Addresses: newNodeAnn.Addresses,
Alias: newNodeAnn.Alias.String(),
Features: lnwire.NewFeatureVector(
newNodeAnn.Features, lnwire.Features,
),
Color: newNodeAnn.RGBColor,
AuthSigBytes: newNodeAnn.Signature.ToSignatureBytes(),
}
copy(selfNode.PubKeyBytes[:], s.identityECDH.PubKey().SerializeCompressed())
if err := s.graphDB.SetSourceNode(selfNode); err != nil {
return fmt.Errorf("can't set self node: %w", err)
}
return nil
}
// findChannel finds a channel given a public key and ChannelID. It is an
// optimization that is quicker than seeking for a channel given only the
// ChannelID.
func (s *server) findChannel(node *btcec.PublicKey, chanID lnwire.ChannelID) (
*channeldb.OpenChannel, error) {
nodeChans, err := s.chanStateDB.FetchOpenChannels(node)
if err != nil {
return nil, err
}
for _, channel := range nodeChans {
if chanID.IsChanPoint(&channel.FundingOutpoint) {
return channel, nil
}
}
return nil, fmt.Errorf("unable to find channel")
}
// getNodeAnnouncement fetches the current, fully signed node announcement.
func (s *server) getNodeAnnouncement() lnwire.NodeAnnouncement {
s.mu.Lock()
defer s.mu.Unlock()
return *s.currentNodeAnn
}
// genNodeAnnouncement generates and returns the current fully signed node
// announcement. The time stamp of the announcement will be updated in order
// to ensure it propagates through the network.
func (s *server) genNodeAnnouncement(features *lnwire.RawFeatureVector,
modifiers ...netann.NodeAnnModifier) (lnwire.NodeAnnouncement, error) {
s.mu.Lock()
defer s.mu.Unlock()
// First, try to update our feature manager with the updated set of
// features.
if features != nil {
proposedFeatures := map[feature.Set]*lnwire.RawFeatureVector{
feature.SetNodeAnn: features,
}
err := s.featureMgr.UpdateFeatureSets(proposedFeatures)
if err != nil {
return lnwire.NodeAnnouncement{}, err
}
// If we could successfully update our feature manager, add
// an update modifier to include these new features to our
// set.
modifiers = append(
modifiers, netann.NodeAnnSetFeatures(features),
)
}
// Always update the timestamp when refreshing to ensure the update
// propagates.
modifiers = append(modifiers, netann.NodeAnnSetTimestamp)
// Apply the requested changes to the node announcement.
for _, modifier := range modifiers {
modifier(s.currentNodeAnn)
}
// Sign a new update after applying all of the passed modifiers.
err := netann.SignNodeAnnouncement(
s.nodeSigner, s.identityKeyLoc, s.currentNodeAnn,
)
if err != nil {
return lnwire.NodeAnnouncement{}, err
}
return *s.currentNodeAnn, nil
}
// updateAndBroadcastSelfNode generates a new node announcement
// applying the giving modifiers and updating the time stamp
// to ensure it propagates through the network. Then it broadcasts
// it to the network.
func (s *server) updateAndBroadcastSelfNode(features *lnwire.RawFeatureVector,
modifiers ...netann.NodeAnnModifier) error {
newNodeAnn, err := s.genNodeAnnouncement(features, modifiers...)
if err != nil {
return fmt.Errorf("unable to generate new node "+
"announcement: %v", err)
}
// Update the on-disk version of our announcement.
// Load and modify self node istead of creating anew instance so we
// don't risk overwriting any existing values.
selfNode, err := s.graphDB.SourceNode()
if err != nil {
return fmt.Errorf("unable to get current source node: %w", err)
}
selfNode.HaveNodeAnnouncement = true
selfNode.LastUpdate = time.Unix(int64(newNodeAnn.Timestamp), 0)
selfNode.Addresses = newNodeAnn.Addresses
selfNode.Alias = newNodeAnn.Alias.String()
selfNode.Features = s.featureMgr.Get(feature.SetNodeAnn)
selfNode.Color = newNodeAnn.RGBColor
selfNode.AuthSigBytes = newNodeAnn.Signature.ToSignatureBytes()
copy(selfNode.PubKeyBytes[:], s.identityECDH.PubKey().SerializeCompressed())
if err := s.graphDB.SetSourceNode(selfNode); err != nil {
return fmt.Errorf("can't set self node: %w", err)
}
// Finally, propagate it to the nodes in the network.
err = s.BroadcastMessage(nil, &newNodeAnn)
if err != nil {
rpcsLog.Debugf("Unable to broadcast new node "+
"announcement to peers: %v", err)
return err
}
return nil
}
type nodeAddresses struct {
pubKey *btcec.PublicKey
addresses []net.Addr
}
// establishPersistentConnections attempts to establish persistent connections
// to all our direct channel collaborators. In order to promote liveness of our
// active channels, we instruct the connection manager to attempt to establish
// and maintain persistent connections to all our direct channel counterparties.
func (s *server) establishPersistentConnections() error {
// nodeAddrsMap stores the combination of node public keys and addresses
// that we'll attempt to reconnect to. PubKey strings are used as keys
// since other PubKey forms can't be compared.
nodeAddrsMap := map[string]*nodeAddresses{}
// Iterate through the list of LinkNodes to find addresses we should
// attempt to connect to based on our set of previous connections. Set
// the reconnection port to the default peer port.
linkNodes, err := s.chanStateDB.LinkNodeDB().FetchAllLinkNodes()
if err != nil && err != channeldb.ErrLinkNodesNotFound {
return fmt.Errorf("failed to fetch all link nodes: %w", err)
}
for _, node := range linkNodes {
pubStr := string(node.IdentityPub.SerializeCompressed())
nodeAddrs := &nodeAddresses{
pubKey: node.IdentityPub,
addresses: node.Addresses,
}
nodeAddrsMap[pubStr] = nodeAddrs
}
// After checking our previous connections for addresses to connect to,
// iterate through the nodes in our channel graph to find addresses
// that have been added via NodeAnnouncement messages.
sourceNode, err := s.graphDB.SourceNode()
if err != nil {
return fmt.Errorf("failed to fetch source node: %w", err)
}
// TODO(roasbeef): instead iterate over link nodes and query graph for
// each of the nodes.
selfPub := s.identityECDH.PubKey().SerializeCompressed()
err = s.graphDB.ForEachNodeChannel(sourceNode.PubKeyBytes, func(
tx kvdb.RTx,
chanInfo *models.ChannelEdgeInfo,
policy, _ *models.ChannelEdgePolicy) error {
// If the remote party has announced the channel to us, but we
// haven't yet, then we won't have a policy. However, we don't
// need this to connect to the peer, so we'll log it and move on.
if policy == nil {
srvrLog.Warnf("No channel policy found for "+
"ChannelPoint(%v): ", chanInfo.ChannelPoint)
}
// We'll now fetch the peer opposite from us within this
// channel so we can queue up a direct connection to them.
channelPeer, err := s.graphDB.FetchOtherNode(
tx, chanInfo, selfPub,
)
if err != nil {
return fmt.Errorf("unable to fetch channel peer for "+
"ChannelPoint(%v): %v", chanInfo.ChannelPoint,
err)
}
pubStr := string(channelPeer.PubKeyBytes[:])
// Add all unique addresses from channel
// graph/NodeAnnouncements to the list of addresses we'll
// connect to for this peer.
addrSet := make(map[string]net.Addr)
for _, addr := range channelPeer.Addresses {
switch addr.(type) {
case *net.TCPAddr:
addrSet[addr.String()] = addr
// We'll only attempt to connect to Tor addresses if Tor
// outbound support is enabled.
case *tor.OnionAddr:
if s.cfg.Tor.Active {
addrSet[addr.String()] = addr
}
}
}
// If this peer is also recorded as a link node, we'll add any
// additional addresses that have not already been selected.
linkNodeAddrs, ok := nodeAddrsMap[pubStr]
if ok {
for _, lnAddress := range linkNodeAddrs.addresses {
switch lnAddress.(type) {
case *net.TCPAddr:
addrSet[lnAddress.String()] = lnAddress
// We'll only attempt to connect to Tor
// addresses if Tor outbound support is enabled.
case *tor.OnionAddr:
if s.cfg.Tor.Active {
addrSet[lnAddress.String()] = lnAddress
}
}
}
}
// Construct a slice of the deduped addresses.
var addrs []net.Addr
for _, addr := range addrSet {
addrs = append(addrs, addr)
}
n := &nodeAddresses{
addresses: addrs,
}
n.pubKey, err = channelPeer.PubKey()
if err != nil {
return err
}
nodeAddrsMap[pubStr] = n
return nil
})
if err != nil {
srvrLog.Errorf("Failed to iterate channels for node %x",
sourceNode.PubKeyBytes)
if !errors.Is(err, graphdb.ErrGraphNoEdgesFound) &&
!errors.Is(err, graphdb.ErrEdgeNotFound) {
return err
}
}
srvrLog.Debugf("Establishing %v persistent connections on start",
len(nodeAddrsMap))
// Acquire and hold server lock until all persistent connection requests
// have been recorded and sent to the connection manager.
s.mu.Lock()
defer s.mu.Unlock()
// Iterate through the combined list of addresses from prior links and
// node announcements and attempt to reconnect to each node.
var numOutboundConns int
for pubStr, nodeAddr := range nodeAddrsMap {
// Add this peer to the set of peers we should maintain a
// persistent connection with. We set the value to false to
// indicate that we should not continue to reconnect if the
// number of channels returns to zero, since this peer has not
// been requested as perm by the user.
s.persistentPeers[pubStr] = false
if _, ok := s.persistentPeersBackoff[pubStr]; !ok {
s.persistentPeersBackoff[pubStr] = s.cfg.MinBackoff
}
for _, address := range nodeAddr.addresses {
// Create a wrapper address which couples the IP and
// the pubkey so the brontide authenticated connection
// can be established.
lnAddr := &lnwire.NetAddress{
IdentityKey: nodeAddr.pubKey,
Address: address,
}
s.persistentPeerAddrs[pubStr] = append(
s.persistentPeerAddrs[pubStr], lnAddr)
}
// We'll connect to the first 10 peers immediately, then
// randomly stagger any remaining connections if the
// stagger initial reconnect flag is set. This ensures
// that mobile nodes or nodes with a small number of
// channels obtain connectivity quickly, but larger
// nodes are able to disperse the costs of connecting to
// all peers at once.
if numOutboundConns < numInstantInitReconnect ||
!s.cfg.StaggerInitialReconnect {
go s.connectToPersistentPeer(pubStr)
} else {
go s.delayInitialReconnect(pubStr)
}
numOutboundConns++
}
return nil
}
// delayInitialReconnect will attempt a reconnection to the given peer after
// sampling a value for the delay between 0s and the maxInitReconnectDelay.
//
// NOTE: This method MUST be run as a goroutine.
func (s *server) delayInitialReconnect(pubStr string) {
delay := time.Duration(prand.Intn(maxInitReconnectDelay)) * time.Second
select {
case <-time.After(delay):
s.connectToPersistentPeer(pubStr)
case <-s.quit:
}
}
// prunePersistentPeerConnection removes all internal state related to
// persistent connections to a peer within the server. This is used to avoid
// persistent connection retries to peers we do not have any open channels with.
func (s *server) prunePersistentPeerConnection(compressedPubKey [33]byte) {
pubKeyStr := string(compressedPubKey[:])
s.mu.Lock()
if perm, ok := s.persistentPeers[pubKeyStr]; ok && !perm {
delete(s.persistentPeers, pubKeyStr)
delete(s.persistentPeersBackoff, pubKeyStr)
delete(s.persistentPeerAddrs, pubKeyStr)
s.cancelConnReqs(pubKeyStr, nil)
s.mu.Unlock()
srvrLog.Infof("Pruned peer %x from persistent connections, "+
"peer has no open channels", compressedPubKey)
return
}
s.mu.Unlock()
}
// bannedPersistentPeerConnection does not actually "ban" a persistent peer. It
// is instead used to remove persistent peer state for a peer that has been
// disconnected for good cause by the server. Currently, a gossip ban from
// sending garbage and the server running out of restricted-access
// (i.e. "free") connection slots are the only way this logic gets hit. In the
// future, this function may expand when more ban criteria is added.
//
// NOTE: The server's write lock MUST be held when this is called.
func (s *server) bannedPersistentPeerConnection(remotePub string) {
if perm, ok := s.persistentPeers[remotePub]; ok && !perm {
delete(s.persistentPeers, remotePub)
delete(s.persistentPeersBackoff, remotePub)
delete(s.persistentPeerAddrs, remotePub)
s.cancelConnReqs(remotePub, nil)
}
}
// BroadcastMessage sends a request to the server to broadcast a set of
// messages to all peers other than the one specified by the `skips` parameter.
// All messages sent via BroadcastMessage will be queued for lazy delivery to
// the target peers.
//
// NOTE: This function is safe for concurrent access.
func (s *server) BroadcastMessage(skips map[route.Vertex]struct{},
msgs ...lnwire.Message) error {
// Filter out peers found in the skips map. We synchronize access to
// peersByPub throughout this process to ensure we deliver messages to
// exact set of peers present at the time of invocation.
s.mu.RLock()
peers := make([]*peer.Brontide, 0, len(s.peersByPub))
for pubStr, sPeer := range s.peersByPub {
if skips != nil {
if _, ok := skips[sPeer.PubKey()]; ok {
srvrLog.Tracef("Skipping %x in broadcast with "+
"pubStr=%x", sPeer.PubKey(), pubStr)
continue
}
}
peers = append(peers, sPeer)
}
s.mu.RUnlock()
// Iterate over all known peers, dispatching a go routine to enqueue
// all messages to each of peers.
var wg sync.WaitGroup
for _, sPeer := range peers {
srvrLog.Debugf("Sending %v messages to peer %x", len(msgs),
sPeer.PubKey())
// Dispatch a go routine to enqueue all messages to this peer.
wg.Add(1)
s.wg.Add(1)
go func(p lnpeer.Peer) {
defer s.wg.Done()
defer wg.Done()
p.SendMessageLazy(false, msgs...)
}(sPeer)
}
// Wait for all messages to have been dispatched before returning to
// caller.
wg.Wait()
return nil
}
// NotifyWhenOnline can be called by other subsystems to get notified when a
// particular peer comes online. The peer itself is sent across the peerChan.
//
// NOTE: This function is safe for concurrent access.
func (s *server) NotifyWhenOnline(peerKey [33]byte,
peerChan chan<- lnpeer.Peer) {
s.mu.Lock()
// Compute the target peer's identifier.
pubStr := string(peerKey[:])
// Check if peer is connected.
peer, ok := s.peersByPub[pubStr]
if ok {
// Unlock here so that the mutex isn't held while we are
// waiting for the peer to become active.
s.mu.Unlock()
// Wait until the peer signals that it is actually active
// rather than only in the server's maps.
select {
case <-peer.ActiveSignal():
case <-peer.QuitSignal():
// The peer quit, so we'll add the channel to the slice
// and return.
s.mu.Lock()
s.peerConnectedListeners[pubStr] = append(
s.peerConnectedListeners[pubStr], peerChan,
)
s.mu.Unlock()
return
}
// Connected, can return early.
srvrLog.Debugf("Notifying that peer %x is online", peerKey)
select {
case peerChan <- peer:
case <-s.quit:
}
return
}
// Not connected, store this listener such that it can be notified when
// the peer comes online.
s.peerConnectedListeners[pubStr] = append(
s.peerConnectedListeners[pubStr], peerChan,
)
s.mu.Unlock()
}
// NotifyWhenOffline delivers a notification to the caller of when the peer with
// the given public key has been disconnected. The notification is signaled by
// closing the channel returned.
func (s *server) NotifyWhenOffline(peerPubKey [33]byte) <-chan struct{} {
s.mu.Lock()
defer s.mu.Unlock()
c := make(chan struct{})
// If the peer is already offline, we can immediately trigger the
// notification.
peerPubKeyStr := string(peerPubKey[:])
if _, ok := s.peersByPub[peerPubKeyStr]; !ok {
srvrLog.Debugf("Notifying that peer %x is offline", peerPubKey)
close(c)
return c
}
// Otherwise, the peer is online, so we'll keep track of the channel to
// trigger the notification once the server detects the peer
// disconnects.
s.peerDisconnectedListeners[peerPubKeyStr] = append(
s.peerDisconnectedListeners[peerPubKeyStr], c,
)
return c
}
// FindPeer will return the peer that corresponds to the passed in public key.
// This function is used by the funding manager, allowing it to update the
// daemon's local representation of the remote peer.
//
// NOTE: This function is safe for concurrent access.
func (s *server) FindPeer(peerKey *btcec.PublicKey) (*peer.Brontide, error) {
s.mu.RLock()
defer s.mu.RUnlock()
pubStr := string(peerKey.SerializeCompressed())
return s.findPeerByPubStr(pubStr)
}
// FindPeerByPubStr will return the peer that corresponds to the passed peerID,
// which should be a string representation of the peer's serialized, compressed
// public key.
//
// NOTE: This function is safe for concurrent access.
func (s *server) FindPeerByPubStr(pubStr string) (*peer.Brontide, error) {
s.mu.RLock()
defer s.mu.RUnlock()
return s.findPeerByPubStr(pubStr)
}
// findPeerByPubStr is an internal method that retrieves the specified peer from
// the server's internal state using.
func (s *server) findPeerByPubStr(pubStr string) (*peer.Brontide, error) {
peer, ok := s.peersByPub[pubStr]
if !ok {
return nil, ErrPeerNotConnected
}
return peer, nil
}
// nextPeerBackoff computes the next backoff duration for a peer's pubkey using
// exponential backoff. If no previous backoff was known, the default is
// returned.
func (s *server) nextPeerBackoff(pubStr string,
startTime time.Time) time.Duration {
// Now, determine the appropriate backoff to use for the retry.
backoff, ok := s.persistentPeersBackoff[pubStr]
if !ok {
// If an existing backoff was unknown, use the default.
return s.cfg.MinBackoff
}
// If the peer failed to start properly, we'll just use the previous
// backoff to compute the subsequent randomized exponential backoff
// duration. This will roughly double on average.
if startTime.IsZero() {
return computeNextBackoff(backoff, s.cfg.MaxBackoff)
}
// The peer succeeded in starting. If the connection didn't last long
// enough to be considered stable, we'll continue to back off retries
// with this peer.
connDuration := time.Since(startTime)
if connDuration < defaultStableConnDuration {
return computeNextBackoff(backoff, s.cfg.MaxBackoff)
}
// The peer succeed in starting and this was stable peer, so we'll
// reduce the timeout duration by the length of the connection after
// applying randomized exponential backoff. We'll only apply this in the
// case that:
// reb(curBackoff) - connDuration > cfg.MinBackoff
relaxedBackoff := computeNextBackoff(backoff, s.cfg.MaxBackoff) - connDuration
if relaxedBackoff > s.cfg.MinBackoff {
return relaxedBackoff
}
// Lastly, if reb(currBackoff) - connDuration <= cfg.MinBackoff, meaning
// the stable connection lasted much longer than our previous backoff.
// To reward such good behavior, we'll reconnect after the default
// timeout.
return s.cfg.MinBackoff
}
// shouldDropLocalConnection determines if our local connection to a remote peer
// should be dropped in the case of concurrent connection establishment. In
// order to deterministically decide which connection should be dropped, we'll
// utilize the ordering of the local and remote public key. If we didn't use
// such a tie breaker, then we risk _both_ connections erroneously being
// dropped.
func shouldDropLocalConnection(local, remote *btcec.PublicKey) bool {
localPubBytes := local.SerializeCompressed()
remotePubPbytes := remote.SerializeCompressed()
// The connection that comes from the node with a "smaller" pubkey
// should be kept. Therefore, if our pubkey is "greater" than theirs, we
// should drop our established connection.
return bytes.Compare(localPubBytes, remotePubPbytes) > 0
}
// InboundPeerConnected initializes a new peer in response to a new inbound
// connection.
//
// NOTE: This function is safe for concurrent access.
func (s *server) InboundPeerConnected(conn net.Conn) {
// Exit early if we have already been instructed to shutdown, this
// prevents any delayed callbacks from accidentally registering peers.
if s.Stopped() {
return
}
nodePub := conn.(*brontide.Conn).RemotePub()
pubSer := nodePub.SerializeCompressed()
pubStr := string(pubSer)
var pubBytes [33]byte
copy(pubBytes[:], pubSer)
s.mu.Lock()
defer s.mu.Unlock()
// If the remote node's public key is banned, drop the connection.
access, err := s.peerAccessMan.assignPeerPerms(nodePub)
if err != nil {
// Clean up the persistent peer maps if we're dropping this
// connection.
s.bannedPersistentPeerConnection(pubStr)
srvrLog.Debugf("Dropping connection for %x since we are out "+
"of restricted-access connection slots: %v.", pubSer,
err)
conn.Close()
return
}
// If we already have an outbound connection to this peer, then ignore
// this new connection.
if p, ok := s.outboundPeers[pubStr]; ok {
srvrLog.Debugf("Already have outbound connection for %v, "+
"ignoring inbound connection from local=%v, remote=%v",
p, conn.LocalAddr(), conn.RemoteAddr())
conn.Close()
return
}
// If we already have a valid connection that is scheduled to take
// precedence once the prior peer has finished disconnecting, we'll
// ignore this connection.
if p, ok := s.scheduledPeerConnection[pubStr]; ok {
srvrLog.Debugf("Ignoring connection from %v, peer %v already "+
"scheduled", conn.RemoteAddr(), p)
conn.Close()
return
}
srvrLog.Infof("New inbound connection from %v", conn.RemoteAddr())
// Check to see if we already have a connection with this peer. If so,
// we may need to drop our existing connection. This prevents us from
// having duplicate connections to the same peer. We forgo adding a
// default case as we expect these to be the only error values returned
// from findPeerByPubStr.
connectedPeer, err := s.findPeerByPubStr(pubStr)
switch err {
case ErrPeerNotConnected:
// We were unable to locate an existing connection with the
// target peer, proceed to connect.
s.cancelConnReqs(pubStr, nil)
s.peerConnected(conn, nil, true, access)
case nil:
// We already have a connection with the incoming peer. If the
// connection we've already established should be kept and is
// not of the same type of the new connection (inbound), then
// we'll close out the new connection s.t there's only a single
// connection between us.
localPub := s.identityECDH.PubKey()
if !connectedPeer.Inbound() &&
!shouldDropLocalConnection(localPub, nodePub) {
srvrLog.Warnf("Received inbound connection from "+
"peer %v, but already have outbound "+
"connection, dropping conn", connectedPeer)
conn.Close()
return
}
// Otherwise, if we should drop the connection, then we'll
// disconnect our already connected peer.
srvrLog.Debugf("Disconnecting stale connection to %v",
connectedPeer)
s.cancelConnReqs(pubStr, nil)
// Remove the current peer from the server's internal state and
// signal that the peer termination watcher does not need to
// execute for this peer.
s.removePeer(connectedPeer)
s.ignorePeerTermination[connectedPeer] = struct{}{}
s.scheduledPeerConnection[pubStr] = func() {
s.peerConnected(conn, nil, true, access)
}
}
}
// OutboundPeerConnected initializes a new peer in response to a new outbound
// connection.
// NOTE: This function is safe for concurrent access.
func (s *server) OutboundPeerConnected(connReq *connmgr.ConnReq, conn net.Conn) {
// Exit early if we have already been instructed to shutdown, this
// prevents any delayed callbacks from accidentally registering peers.
if s.Stopped() {
return
}
nodePub := conn.(*brontide.Conn).RemotePub()
pubSer := nodePub.SerializeCompressed()
pubStr := string(pubSer)
var pubBytes [33]byte
copy(pubBytes[:], pubSer)
s.mu.Lock()
defer s.mu.Unlock()
access, err := s.peerAccessMan.assignPeerPerms(nodePub)
if err != nil {
// Clean up the persistent peer maps if we're dropping this
// connection.
s.bannedPersistentPeerConnection(pubStr)
srvrLog.Debugf("Dropping connection for %x since we are out "+
"of restricted-access connection slots: %v.", pubSer,
err)
if connReq != nil {
s.connMgr.Remove(connReq.ID())
}
conn.Close()
return
}
// If we already have an inbound connection to this peer, then ignore
// this new connection.
if p, ok := s.inboundPeers[pubStr]; ok {
srvrLog.Debugf("Already have inbound connection for %v, "+
"ignoring outbound connection from local=%v, remote=%v",
p, conn.LocalAddr(), conn.RemoteAddr())
if connReq != nil {
s.connMgr.Remove(connReq.ID())
}
conn.Close()
return
}
if _, ok := s.persistentConnReqs[pubStr]; !ok && connReq != nil {
srvrLog.Debugf("Ignoring canceled outbound connection")
s.connMgr.Remove(connReq.ID())
conn.Close()
return
}
// If we already have a valid connection that is scheduled to take
// precedence once the prior peer has finished disconnecting, we'll
// ignore this connection.
if _, ok := s.scheduledPeerConnection[pubStr]; ok {
srvrLog.Debugf("Ignoring connection, peer already scheduled")
if connReq != nil {
s.connMgr.Remove(connReq.ID())
}
conn.Close()
return
}
srvrLog.Infof("Established connection to: %x@%v", pubStr,
conn.RemoteAddr())
if connReq != nil {
// A successful connection was returned by the connmgr.
// Immediately cancel all pending requests, excluding the
// outbound connection we just established.
ignore := connReq.ID()
s.cancelConnReqs(pubStr, &ignore)
} else {
// This was a successful connection made by some other
// subsystem. Remove all requests being managed by the connmgr.
s.cancelConnReqs(pubStr, nil)
}
// If we already have a connection with this peer, decide whether or not
// we need to drop the stale connection. We forgo adding a default case
// as we expect these to be the only error values returned from
// findPeerByPubStr.
connectedPeer, err := s.findPeerByPubStr(pubStr)
switch err {
case ErrPeerNotConnected:
// We were unable to locate an existing connection with the
// target peer, proceed to connect.
s.peerConnected(conn, connReq, false, access)
case nil:
// We already have a connection with the incoming peer. If the
// connection we've already established should be kept and is
// not of the same type of the new connection (outbound), then
// we'll close out the new connection s.t there's only a single
// connection between us.
localPub := s.identityECDH.PubKey()
if connectedPeer.Inbound() &&
shouldDropLocalConnection(localPub, nodePub) {
srvrLog.Warnf("Established outbound connection to "+
"peer %v, but already have inbound "+
"connection, dropping conn", connectedPeer)
if connReq != nil {
s.connMgr.Remove(connReq.ID())
}
conn.Close()
return
}
// Otherwise, _their_ connection should be dropped. So we'll
// disconnect the peer and send the now obsolete peer to the
// server for garbage collection.
srvrLog.Debugf("Disconnecting stale connection to %v",
connectedPeer)
// Remove the current peer from the server's internal state and
// signal that the peer termination watcher does not need to
// execute for this peer.
s.removePeer(connectedPeer)
s.ignorePeerTermination[connectedPeer] = struct{}{}
s.scheduledPeerConnection[pubStr] = func() {
s.peerConnected(conn, connReq, false, access)
}
}
}
// UnassignedConnID is the default connection ID that a request can have before
// it actually is submitted to the connmgr.
// TODO(conner): move into connmgr package, or better, add connmgr method for
// generating atomic IDs
const UnassignedConnID uint64 = 0
// cancelConnReqs stops all persistent connection requests for a given pubkey.
// Any attempts initiated by the peerTerminationWatcher are canceled first.
// Afterwards, each connection request removed from the connmgr. The caller can
// optionally specify a connection ID to ignore, which prevents us from
// canceling a successful request. All persistent connreqs for the provided
// pubkey are discarded after the operationjw.
func (s *server) cancelConnReqs(pubStr string, skip *uint64) {
// First, cancel any lingering persistent retry attempts, which will
// prevent retries for any with backoffs that are still maturing.
if cancelChan, ok := s.persistentRetryCancels[pubStr]; ok {
close(cancelChan)
delete(s.persistentRetryCancels, pubStr)
}
// Next, check to see if we have any outstanding persistent connection
// requests to this peer. If so, then we'll remove all of these
// connection requests, and also delete the entry from the map.
connReqs, ok := s.persistentConnReqs[pubStr]
if !ok {
return
}
for _, connReq := range connReqs {
srvrLog.Tracef("Canceling %s:", connReqs)
// Atomically capture the current request identifier.
connID := connReq.ID()
// Skip any zero IDs, this indicates the request has not
// yet been schedule.
if connID == UnassignedConnID {
continue
}
// Skip a particular connection ID if instructed.
if skip != nil && connID == *skip {
continue
}
s.connMgr.Remove(connID)
}
delete(s.persistentConnReqs, pubStr)
}
// handleCustomMessage dispatches an incoming custom peers message to
// subscribers.
func (s *server) handleCustomMessage(peer [33]byte, msg *lnwire.Custom) error {
srvrLog.Debugf("Custom message received: peer=%x, type=%d",
peer, msg.Type)
return s.customMessageServer.SendUpdate(&CustomMessage{
Peer: peer,
Msg: msg,
})
}
// SubscribeCustomMessages subscribes to a stream of incoming custom peer
// messages.
func (s *server) SubscribeCustomMessages() (*subscribe.Client, error) {
return s.customMessageServer.Subscribe()
}
// notifyOpenChannelPeerEvent updates the access manager's maps and then calls
// the channelNotifier's NotifyOpenChannelEvent.
func (s *server) notifyOpenChannelPeerEvent(op wire.OutPoint,
remotePub *btcec.PublicKey) error {
// Call newOpenChan to update the access manager's maps for this peer.
if err := s.peerAccessMan.newOpenChan(remotePub); err != nil {
return err
}
// Notify subscribers about this open channel event.
s.channelNotifier.NotifyOpenChannelEvent(op)
return nil
}
// notifyPendingOpenChannelPeerEvent updates the access manager's maps and then
// calls the channelNotifier's NotifyPendingOpenChannelEvent.
func (s *server) notifyPendingOpenChannelPeerEvent(op wire.OutPoint,
pendingChan *channeldb.OpenChannel, remotePub *btcec.PublicKey) error {
// Call newPendingOpenChan to update the access manager's maps for this
// peer.
if err := s.peerAccessMan.newPendingOpenChan(remotePub); err != nil {
return err
}
// Notify subscribers about this event.
s.channelNotifier.NotifyPendingOpenChannelEvent(op, pendingChan)
return nil
}
// notifyFundingTimeoutPeerEvent updates the access manager's maps and then
// calls the channelNotifier's NotifyFundingTimeout.
func (s *server) notifyFundingTimeoutPeerEvent(op wire.OutPoint,
remotePub *btcec.PublicKey) error {
// Call newPendingCloseChan to potentially demote the peer.
err := s.peerAccessMan.newPendingCloseChan(remotePub)
if errors.Is(err, ErrNoMoreRestrictedAccessSlots) {
// If we encounter an error while attempting to disconnect the
// peer, log the error.
if dcErr := s.DisconnectPeer(remotePub); dcErr != nil {
srvrLog.Errorf("Unable to disconnect peer: %v\n", err)
}
}
// Notify subscribers about this event.
s.channelNotifier.NotifyFundingTimeout(op)
return nil
}
// peerConnected is a function that handles initialization a newly connected
// peer by adding it to the server's global list of all active peers, and
// starting all the goroutines the peer needs to function properly. The inbound
// boolean should be true if the peer initiated the connection to us.
func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq,
inbound bool, access peerAccessStatus) {
brontideConn := conn.(*brontide.Conn)
addr := conn.RemoteAddr()
pubKey := brontideConn.RemotePub()
srvrLog.Infof("Finalizing connection to %x@%s, inbound=%v",
pubKey.SerializeCompressed(), addr, inbound)
peerAddr := &lnwire.NetAddress{
IdentityKey: pubKey,
Address: addr,
ChainNet: s.cfg.ActiveNetParams.Net,
}
// With the brontide connection established, we'll now craft the feature
// vectors to advertise to the remote node.
initFeatures := s.featureMgr.Get(feature.SetInit)
legacyFeatures := s.featureMgr.Get(feature.SetLegacyGlobal)
// Lookup past error caches for the peer in the server. If no buffer is
// found, create a fresh buffer.
pkStr := string(peerAddr.IdentityKey.SerializeCompressed())
errBuffer, ok := s.peerErrors[pkStr]
if !ok {
var err error
errBuffer, err = queue.NewCircularBuffer(peer.ErrorBufferSize)
if err != nil {
srvrLog.Errorf("unable to create peer %v", err)
return
}
}
// If we directly set the peer.Config TowerClient member to the
// s.towerClientMgr then in the case that the s.towerClientMgr is nil,
// the peer.Config's TowerClient member will not evaluate to nil even
// though the underlying value is nil. To avoid this gotcha which can
// cause a panic, we need to explicitly pass nil to the peer.Config's
// TowerClient if needed.
var towerClient wtclient.ClientManager
if s.towerClientMgr != nil {
towerClient = s.towerClientMgr
}
thresholdSats := btcutil.Amount(s.cfg.MaxFeeExposure)
thresholdMSats := lnwire.NewMSatFromSatoshis(thresholdSats)
// Now that we've established a connection, create a peer, and it to the
// set of currently active peers. Configure the peer with the incoming
// and outgoing broadcast deltas to prevent htlcs from being accepted or
// offered that would trigger channel closure. In case of outgoing
// htlcs, an extra block is added to prevent the channel from being
// closed when the htlc is outstanding and a new block comes in.
pCfg := peer.Config{
Conn: brontideConn,
ConnReq: connReq,
Addr: peerAddr,
Inbound: inbound,
Features: initFeatures,
LegacyFeatures: legacyFeatures,
OutgoingCltvRejectDelta: lncfg.DefaultOutgoingCltvRejectDelta,
ChanActiveTimeout: s.cfg.ChanEnableTimeout,
ErrorBuffer: errBuffer,
WritePool: s.writePool,
ReadPool: s.readPool,
Switch: s.htlcSwitch,
InterceptSwitch: s.interceptableSwitch,
ChannelDB: s.chanStateDB,
ChannelGraph: s.graphDB,
ChainArb: s.chainArb,
AuthGossiper: s.authGossiper,
ChanStatusMgr: s.chanStatusMgr,
ChainIO: s.cc.ChainIO,
FeeEstimator: s.cc.FeeEstimator,
Signer: s.cc.Wallet.Cfg.Signer,
SigPool: s.sigPool,
Wallet: s.cc.Wallet,
ChainNotifier: s.cc.ChainNotifier,
BestBlockView: s.cc.BestBlockTracker,
RoutingPolicy: s.cc.RoutingPolicy,
Sphinx: s.sphinx,
WitnessBeacon: s.witnessBeacon,
Invoices: s.invoices,
ChannelNotifier: s.channelNotifier,
HtlcNotifier: s.htlcNotifier,
TowerClient: towerClient,
DisconnectPeer: s.DisconnectPeer,
GenNodeAnnouncement: func(...netann.NodeAnnModifier) (
lnwire.NodeAnnouncement, error) {
return s.genNodeAnnouncement(nil)
},
PongBuf: s.pongBuf,
PrunePersistentPeerConnection: s.prunePersistentPeerConnection,
FetchLastChanUpdate: s.fetchLastChanUpdate(),
FundingManager: s.fundingMgr,
Hodl: s.cfg.Hodl,
UnsafeReplay: s.cfg.UnsafeReplay,
MaxOutgoingCltvExpiry: s.cfg.MaxOutgoingCltvExpiry,
MaxChannelFeeAllocation: s.cfg.MaxChannelFeeAllocation,
CoopCloseTargetConfs: s.cfg.CoopCloseTargetConfs,
MaxAnchorsCommitFeeRate: chainfee.SatPerKVByte(
s.cfg.MaxCommitFeeRateAnchors * 1000).FeePerKWeight(),
ChannelCommitInterval: s.cfg.ChannelCommitInterval,
PendingCommitInterval: s.cfg.PendingCommitInterval,
ChannelCommitBatchSize: s.cfg.ChannelCommitBatchSize,
HandleCustomMessage: s.handleCustomMessage,
GetAliases: s.aliasMgr.GetAliases,
RequestAlias: s.aliasMgr.RequestAlias,
AddLocalAlias: s.aliasMgr.AddLocalAlias,
DisallowRouteBlinding: s.cfg.ProtocolOptions.NoRouteBlinding(),
DisallowQuiescence: s.cfg.ProtocolOptions.NoQuiescence(),
MaxFeeExposure: thresholdMSats,
Quit: s.quit,
AuxLeafStore: s.implCfg.AuxLeafStore,
AuxSigner: s.implCfg.AuxSigner,
MsgRouter: s.implCfg.MsgRouter,
AuxChanCloser: s.implCfg.AuxChanCloser,
AuxResolver: s.implCfg.AuxContractResolver,
AuxTrafficShaper: s.implCfg.TrafficShaper,
ShouldFwdExpEndorsement: func() bool {
if s.cfg.ProtocolOptions.NoExperimentalEndorsement() {
return false
}
return clock.NewDefaultClock().Now().Before(
EndorsementExperimentEnd,
)
},
}
copy(pCfg.PubKeyBytes[:], peerAddr.IdentityKey.SerializeCompressed())
copy(pCfg.ServerPubKey[:], s.identityECDH.PubKey().SerializeCompressed())
p := peer.NewBrontide(pCfg)
// Update the access manager with the access permission for this peer.
s.peerAccessMan.addPeerAccess(pubKey, access)
// TODO(roasbeef): update IP address for link-node
// * also mark last-seen, do it one single transaction?
s.addPeer(p)
// Once we have successfully added the peer to the server, we can
// delete the previous error buffer from the server's map of error
// buffers.
delete(s.peerErrors, pkStr)
// Dispatch a goroutine to asynchronously start the peer. This process
// includes sending and receiving Init messages, which would be a DOS
// vector if we held the server's mutex throughout the procedure.
s.wg.Add(1)
go s.peerInitializer(p)
}
// addPeer adds the passed peer to the server's global state of all active
// peers.
func (s *server) addPeer(p *peer.Brontide) {
if p == nil {
return
}
pubBytes := p.IdentityKey().SerializeCompressed()
// Ignore new peers if we're shutting down.
if s.Stopped() {
srvrLog.Infof("Server stopped, skipped adding peer=%x",
pubBytes)
p.Disconnect(ErrServerShuttingDown)
return
}
// Track the new peer in our indexes so we can quickly look it up either
// according to its public key, or its peer ID.
// TODO(roasbeef): pipe all requests through to the
// queryHandler/peerManager
// NOTE: This pubStr is a raw bytes to string conversion and will NOT
// be human-readable.
pubStr := string(pubBytes)
s.peersByPub[pubStr] = p
if p.Inbound() {
s.inboundPeers[pubStr] = p
} else {
s.outboundPeers[pubStr] = p
}
// Inform the peer notifier of a peer online event so that it can be reported
// to clients listening for peer events.
var pubKey [33]byte
copy(pubKey[:], pubBytes)
s.peerNotifier.NotifyPeerOnline(pubKey)
}
// peerInitializer asynchronously starts a newly connected peer after it has
// been added to the server's peer map. This method sets up a
// peerTerminationWatcher for the given peer, and ensures that it executes even
// if the peer failed to start. In the event of a successful connection, this
// method reads the negotiated, local feature-bits and spawns the appropriate
// graph synchronization method. Any registered clients of NotifyWhenOnline will
// be signaled of the new peer once the method returns.
//
// NOTE: This MUST be launched as a goroutine.
func (s *server) peerInitializer(p *peer.Brontide) {
defer s.wg.Done()
pubBytes := p.IdentityKey().SerializeCompressed()
// Avoid initializing peers while the server is exiting.
if s.Stopped() {
srvrLog.Infof("Server stopped, skipped initializing peer=%x",
pubBytes)
return
}
// Create a channel that will be used to signal a successful start of
// the link. This prevents the peer termination watcher from beginning
// its duty too early.
ready := make(chan struct{})
// Before starting the peer, launch a goroutine to watch for the
// unexpected termination of this peer, which will ensure all resources
// are properly cleaned up, and re-establish persistent connections when
// necessary. The peer termination watcher will be short circuited if
// the peer is ever added to the ignorePeerTermination map, indicating
// that the server has already handled the removal of this peer.
s.wg.Add(1)
go s.peerTerminationWatcher(p, ready)
// Start the peer! If an error occurs, we Disconnect the peer, which
// will unblock the peerTerminationWatcher.
if err := p.Start(); err != nil {
srvrLog.Warnf("Starting peer=%x got error: %v", pubBytes, err)
p.Disconnect(fmt.Errorf("unable to start peer: %w", err))
return
}
// Otherwise, signal to the peerTerminationWatcher that the peer startup
// was successful, and to begin watching the peer's wait group.
close(ready)
s.mu.Lock()
defer s.mu.Unlock()
// Check if there are listeners waiting for this peer to come online.
srvrLog.Debugf("Notifying that peer %v is online", p)
// TODO(guggero): Do a proper conversion to a string everywhere, or use
// route.Vertex as the key type of peerConnectedListeners.
pubStr := string(pubBytes)
for _, peerChan := range s.peerConnectedListeners[pubStr] {
select {
case peerChan <- p:
case <-s.quit:
return
}
}
delete(s.peerConnectedListeners, pubStr)
}
// peerTerminationWatcher waits until a peer has been disconnected unexpectedly,
// and then cleans up all resources allocated to the peer, notifies relevant
// sub-systems of its demise, and finally handles re-connecting to the peer if
// it's persistent. If the server intentionally disconnects a peer, it should
// have a corresponding entry in the ignorePeerTermination map which will cause
// the cleanup routine to exit early. The passed `ready` chan is used to
// synchronize when WaitForDisconnect should begin watching on the peer's
// waitgroup. The ready chan should only be signaled if the peer starts
// successfully, otherwise the peer should be disconnected instead.
//
// NOTE: This MUST be launched as a goroutine.
func (s *server) peerTerminationWatcher(p *peer.Brontide, ready chan struct{}) {
defer s.wg.Done()
p.WaitForDisconnect(ready)
srvrLog.Debugf("Peer %v has been disconnected", p)
// If the server is exiting then we can bail out early ourselves as all
// the other sub-systems will already be shutting down.
if s.Stopped() {
srvrLog.Debugf("Server quitting, exit early for peer %v", p)
return
}
// Next, we'll cancel all pending funding reservations with this node.
// If we tried to initiate any funding flows that haven't yet finished,
// then we need to unlock those committed outputs so they're still
// available for use.
s.fundingMgr.CancelPeerReservations(p.PubKey())
pubKey := p.IdentityKey()
// We'll also inform the gossiper that this peer is no longer active,
// so we don't need to maintain sync state for it any longer.
s.authGossiper.PruneSyncState(p.PubKey())
// Tell the switch to remove all links associated with this peer.
// Passing nil as the target link indicates that all links associated
// with this interface should be closed.
//
// TODO(roasbeef): instead add a PurgeInterfaceLinks function?
links, err := s.htlcSwitch.GetLinksByInterface(p.PubKey())
if err != nil && err != htlcswitch.ErrNoLinksFound {
srvrLog.Errorf("Unable to get channel links for %v: %v", p, err)
}
for _, link := range links {
s.htlcSwitch.RemoveLink(link.ChanID())
}
s.mu.Lock()
defer s.mu.Unlock()
// If there were any notification requests for when this peer
// disconnected, we can trigger them now.
srvrLog.Debugf("Notifying that peer %v is offline", p)
pubStr := string(pubKey.SerializeCompressed())
for _, offlineChan := range s.peerDisconnectedListeners[pubStr] {
close(offlineChan)
}
delete(s.peerDisconnectedListeners, pubStr)
// If the server has already removed this peer, we can short circuit the
// peer termination watcher and skip cleanup.
if _, ok := s.ignorePeerTermination[p]; ok {
delete(s.ignorePeerTermination, p)
pubKey := p.PubKey()
pubStr := string(pubKey[:])
// If a connection callback is present, we'll go ahead and
// execute it now that previous peer has fully disconnected. If
// the callback is not present, this likely implies the peer was
// purposefully disconnected via RPC, and that no reconnect
// should be attempted.
connCallback, ok := s.scheduledPeerConnection[pubStr]
if ok {
delete(s.scheduledPeerConnection, pubStr)
connCallback()
}
return
}
// First, cleanup any remaining state the server has regarding the peer
// in question.
s.removePeer(p)
// Next, check to see if this is a persistent peer or not.
if _, ok := s.persistentPeers[pubStr]; !ok {
return
}
// Get the last address that we used to connect to the peer.
addrs := []net.Addr{
p.NetAddress().Address,
}
// We'll ensure that we locate all the peers advertised addresses for
// reconnection purposes.
advertisedAddrs, err := s.fetchNodeAdvertisedAddrs(pubKey)
switch {
// We found advertised addresses, so use them.
case err == nil:
addrs = advertisedAddrs
// The peer doesn't have an advertised address.
case err == errNoAdvertisedAddr:
// If it is an outbound peer then we fall back to the existing
// peer address.
if !p.Inbound() {
break
}
// Fall back to the existing peer address if
// we're not accepting connections over Tor.
if s.torController == nil {
break
}
// If we are, the peer's address won't be known
// to us (we'll see a private address, which is
// the address used by our onion service to dial
// to lnd), so we don't have enough information
// to attempt a reconnect.
srvrLog.Debugf("Ignoring reconnection attempt "+
"to inbound peer %v without "+
"advertised address", p)
return
// We came across an error retrieving an advertised
// address, log it, and fall back to the existing peer
// address.
default:
srvrLog.Errorf("Unable to retrieve advertised "+
"address for node %x: %v", p.PubKey(),
err)
}
// Make an easy lookup map so that we can check if an address
// is already in the address list that we have stored for this peer.
existingAddrs := make(map[string]bool)
for _, addr := range s.persistentPeerAddrs[pubStr] {
existingAddrs[addr.String()] = true
}
// Add any missing addresses for this peer to persistentPeerAddr.
for _, addr := range addrs {
if existingAddrs[addr.String()] {
continue
}
s.persistentPeerAddrs[pubStr] = append(
s.persistentPeerAddrs[pubStr],
&lnwire.NetAddress{
IdentityKey: p.IdentityKey(),
Address: addr,
ChainNet: p.NetAddress().ChainNet,
},
)
}
// Record the computed backoff in the backoff map.
backoff := s.nextPeerBackoff(pubStr, p.StartTime())
s.persistentPeersBackoff[pubStr] = backoff
// Initialize a retry canceller for this peer if one does not
// exist.
cancelChan, ok := s.persistentRetryCancels[pubStr]
if !ok {
cancelChan = make(chan struct{})
s.persistentRetryCancels[pubStr] = cancelChan
}
// We choose not to wait group this go routine since the Connect
// call can stall for arbitrarily long if we shutdown while an
// outbound connection attempt is being made.
go func() {
srvrLog.Debugf("Scheduling connection re-establishment to "+
"persistent peer %x in %s",
p.IdentityKey().SerializeCompressed(), backoff)
select {
case <-time.After(backoff):
case <-cancelChan:
return
case <-s.quit:
return
}
srvrLog.Debugf("Attempting to re-establish persistent "+
"connection to peer %x",
p.IdentityKey().SerializeCompressed())
s.connectToPersistentPeer(pubStr)
}()
}
// connectToPersistentPeer uses all the stored addresses for a peer to attempt
// to connect to the peer. It creates connection requests if there are
// currently none for a given address and it removes old connection requests
// if the associated address is no longer in the latest address list for the
// peer.
func (s *server) connectToPersistentPeer(pubKeyStr string) {
s.mu.Lock()
defer s.mu.Unlock()
// Create an easy lookup map of the addresses we have stored for the
// peer. We will remove entries from this map if we have existing
// connection requests for the associated address and then any leftover
// entries will indicate which addresses we should create new
// connection requests for.
addrMap := make(map[string]*lnwire.NetAddress)
for _, addr := range s.persistentPeerAddrs[pubKeyStr] {
addrMap[addr.String()] = addr
}
// Go through each of the existing connection requests and
// check if they correspond to the latest set of addresses. If
// there is a connection requests that does not use one of the latest
// advertised addresses then remove that connection request.
var updatedConnReqs []*connmgr.ConnReq
for _, connReq := range s.persistentConnReqs[pubKeyStr] {
lnAddr := connReq.Addr.(*lnwire.NetAddress).Address.String()
switch _, ok := addrMap[lnAddr]; ok {
// If the existing connection request is using one of the
// latest advertised addresses for the peer then we add it to
// updatedConnReqs and remove the associated address from
// addrMap so that we don't recreate this connReq later on.
case true:
updatedConnReqs = append(
updatedConnReqs, connReq,
)
delete(addrMap, lnAddr)
// If the existing connection request is using an address that
// is not one of the latest advertised addresses for the peer
// then we remove the connecting request from the connection
// manager.
case false:
srvrLog.Info(
"Removing conn req:", connReq.Addr.String(),
)
s.connMgr.Remove(connReq.ID())
}
}
s.persistentConnReqs[pubKeyStr] = updatedConnReqs
cancelChan, ok := s.persistentRetryCancels[pubKeyStr]
if !ok {
cancelChan = make(chan struct{})
s.persistentRetryCancels[pubKeyStr] = cancelChan
}
// Any addresses left in addrMap are new ones that we have not made
// connection requests for. So create new connection requests for those.
// If there is more than one address in the address map, stagger the
// creation of the connection requests for those.
go func() {
ticker := time.NewTicker(multiAddrConnectionStagger)
defer ticker.Stop()
for _, addr := range addrMap {
// Send the persistent connection request to the
// connection manager, saving the request itself so we
// can cancel/restart the process as needed.
connReq := &connmgr.ConnReq{
Addr: addr,
Permanent: true,
}
s.mu.Lock()
s.persistentConnReqs[pubKeyStr] = append(
s.persistentConnReqs[pubKeyStr], connReq,
)
s.mu.Unlock()
srvrLog.Debugf("Attempting persistent connection to "+
"channel peer %v", addr)
go s.connMgr.Connect(connReq)
select {
case <-s.quit:
return
case <-cancelChan:
return
case <-ticker.C:
}
}
}()
}
// removePeer removes the passed peer from the server's state of all active
// peers.
func (s *server) removePeer(p *peer.Brontide) {
if p == nil {
return
}
srvrLog.Debugf("removing peer %v", p)
// As the peer is now finished, ensure that the TCP connection is
// closed and all of its related goroutines have exited.
p.Disconnect(fmt.Errorf("server: disconnecting peer %v", p))
// If this peer had an active persistent connection request, remove it.
if p.ConnReq() != nil {
s.connMgr.Remove(p.ConnReq().ID())
}
// Ignore deleting peers if we're shutting down.
if s.Stopped() {
return
}
pKey := p.PubKey()
pubSer := pKey[:]
pubStr := string(pubSer)
delete(s.peersByPub, pubStr)
if p.Inbound() {
delete(s.inboundPeers, pubStr)
} else {
delete(s.outboundPeers, pubStr)
}
// Remove the peer's access permission from the access manager.
s.peerAccessMan.removePeerAccess(p.IdentityKey())
// Copy the peer's error buffer across to the server if it has any items
// in it so that we can restore peer errors across connections.
if p.ErrorBuffer().Total() > 0 {
s.peerErrors[pubStr] = p.ErrorBuffer()
}
// Inform the peer notifier of a peer offline event so that it can be
// reported to clients listening for peer events.
var pubKey [33]byte
copy(pubKey[:], pubSer)
s.peerNotifier.NotifyPeerOffline(pubKey)
}
// ConnectToPeer requests that the server connect to a Lightning Network peer
// at the specified address. This function will *block* until either a
// connection is established, or the initial handshake process fails.
//
// NOTE: This function is safe for concurrent access.
func (s *server) ConnectToPeer(addr *lnwire.NetAddress,
perm bool, timeout time.Duration) error {
targetPub := string(addr.IdentityKey.SerializeCompressed())
// Acquire mutex, but use explicit unlocking instead of defer for
// better granularity. In certain conditions, this method requires
// making an outbound connection to a remote peer, which requires the
// lock to be released, and subsequently reacquired.
s.mu.Lock()
// Ensure we're not already connected to this peer.
peer, err := s.findPeerByPubStr(targetPub)
if err == nil {
s.mu.Unlock()
return &errPeerAlreadyConnected{peer: peer}
}
// Peer was not found, continue to pursue connection with peer.
// If there's already a pending connection request for this pubkey,
// then we ignore this request to ensure we don't create a redundant
// connection.
if reqs, ok := s.persistentConnReqs[targetPub]; ok {
srvrLog.Warnf("Already have %d persistent connection "+
"requests for %v, connecting anyway.", len(reqs), addr)
}
// If there's not already a pending or active connection to this node,
// then instruct the connection manager to attempt to establish a
// persistent connection to the peer.
srvrLog.Debugf("Connecting to %v", addr)
if perm {
connReq := &connmgr.ConnReq{
Addr: addr,
Permanent: true,
}
// Since the user requested a permanent connection, we'll set
// the entry to true which will tell the server to continue
// reconnecting even if the number of channels with this peer is
// zero.
s.persistentPeers[targetPub] = true
if _, ok := s.persistentPeersBackoff[targetPub]; !ok {
s.persistentPeersBackoff[targetPub] = s.cfg.MinBackoff
}
s.persistentConnReqs[targetPub] = append(
s.persistentConnReqs[targetPub], connReq,
)
s.mu.Unlock()
go s.connMgr.Connect(connReq)
return nil
}
s.mu.Unlock()
// If we're not making a persistent connection, then we'll attempt to
// connect to the target peer. If the we can't make the connection, or
// the crypto negotiation breaks down, then return an error to the
// caller.
errChan := make(chan error, 1)
s.connectToPeer(addr, errChan, timeout)
select {
case err := <-errChan:
return err
case <-s.quit:
return ErrServerShuttingDown
}
}
// connectToPeer establishes a connection to a remote peer. errChan is used to
// notify the caller if the connection attempt has failed. Otherwise, it will be
// closed.
func (s *server) connectToPeer(addr *lnwire.NetAddress,
errChan chan<- error, timeout time.Duration) {
conn, err := brontide.Dial(
s.identityECDH, addr, timeout, s.cfg.net.Dial,
)
if err != nil {
srvrLog.Errorf("Unable to connect to %v: %v", addr, err)
select {
case errChan <- err:
case <-s.quit:
}
return
}
close(errChan)
srvrLog.Tracef("Brontide dialer made local=%v, remote=%v",
conn.LocalAddr(), conn.RemoteAddr())
s.OutboundPeerConnected(nil, conn)
}
// DisconnectPeer sends the request to server to close the connection with peer
// identified by public key.
//
// NOTE: This function is safe for concurrent access.
func (s *server) DisconnectPeer(pubKey *btcec.PublicKey) error {
pubBytes := pubKey.SerializeCompressed()
pubStr := string(pubBytes)
s.mu.Lock()
defer s.mu.Unlock()
// Check that were actually connected to this peer. If not, then we'll
// exit in an error as we can't disconnect from a peer that we're not
// currently connected to.
peer, err := s.findPeerByPubStr(pubStr)
if err == ErrPeerNotConnected {
return fmt.Errorf("peer %x is not connected", pubBytes)
}
srvrLog.Infof("Disconnecting from %v", peer)
s.cancelConnReqs(pubStr, nil)
// If this peer was formerly a persistent connection, then we'll remove
// them from this map so we don't attempt to re-connect after we
// disconnect.
delete(s.persistentPeers, pubStr)
delete(s.persistentPeersBackoff, pubStr)
// Remove the peer by calling Disconnect. Previously this was done with
// removePeer, which bypassed the peerTerminationWatcher.
peer.Disconnect(fmt.Errorf("server: DisconnectPeer called"))
return nil
}
// OpenChannel sends a request to the server to open a channel to the specified
// peer identified by nodeKey with the passed channel funding parameters.
//
// NOTE: This function is safe for concurrent access.
func (s *server) OpenChannel(
req *funding.InitFundingMsg) (chan *lnrpc.OpenStatusUpdate, chan error) {
// The updateChan will have a buffer of 2, since we expect a ChanPending
// + a ChanOpen update, and we want to make sure the funding process is
// not blocked if the caller is not reading the updates.
req.Updates = make(chan *lnrpc.OpenStatusUpdate, 2)
req.Err = make(chan error, 1)
// First attempt to locate the target peer to open a channel with, if
// we're unable to locate the peer then this request will fail.
pubKeyBytes := req.TargetPubkey.SerializeCompressed()
s.mu.RLock()
peer, ok := s.peersByPub[string(pubKeyBytes)]
if !ok {
s.mu.RUnlock()
req.Err <- fmt.Errorf("peer %x is not online", pubKeyBytes)
return req.Updates, req.Err
}
req.Peer = peer
s.mu.RUnlock()
// We'll wait until the peer is active before beginning the channel
// opening process.
select {
case <-peer.ActiveSignal():
case <-peer.QuitSignal():
req.Err <- fmt.Errorf("peer %x disconnected", pubKeyBytes)
return req.Updates, req.Err
case <-s.quit:
req.Err <- ErrServerShuttingDown
return req.Updates, req.Err
}
// If the fee rate wasn't specified at this point we fail the funding
// because of the missing fee rate information. The caller of the
// `OpenChannel` method needs to make sure that default values for the
// fee rate are set beforehand.
if req.FundingFeePerKw == 0 {
req.Err <- fmt.Errorf("no FundingFeePerKw specified for " +
"the channel opening transaction")
return req.Updates, req.Err
}
// Spawn a goroutine to send the funding workflow request to the funding
// manager. This allows the server to continue handling queries instead
// of blocking on this request which is exported as a synchronous
// request to the outside world.
go s.fundingMgr.InitFundingWorkflow(req)
return req.Updates, req.Err
}
// Peers returns a slice of all active peers.
//
// NOTE: This function is safe for concurrent access.
func (s *server) Peers() []*peer.Brontide {
s.mu.RLock()
defer s.mu.RUnlock()
peers := make([]*peer.Brontide, 0, len(s.peersByPub))
for _, peer := range s.peersByPub {
peers = append(peers, peer)
}
return peers
}
// computeNextBackoff uses a truncated exponential backoff to compute the next
// backoff using the value of the exiting backoff. The returned duration is
// randomized in either direction by 1/20 to prevent tight loops from
// stabilizing.
func computeNextBackoff(currBackoff, maxBackoff time.Duration) time.Duration {
// Double the current backoff, truncating if it exceeds our maximum.
nextBackoff := 2 * currBackoff
if nextBackoff > maxBackoff {
nextBackoff = maxBackoff
}
// Using 1/10 of our duration as a margin, compute a random offset to
// avoid the nodes entering connection cycles.
margin := nextBackoff / 10
var wiggle big.Int
wiggle.SetUint64(uint64(margin))
if _, err := rand.Int(rand.Reader, &wiggle); err != nil {
// Randomizing is not mission critical, so we'll just return the
// current backoff.
return nextBackoff
}
// Otherwise add in our wiggle, but subtract out half of the margin so
// that the backoff can tweaked by 1/20 in either direction.
return nextBackoff + (time.Duration(wiggle.Uint64()) - margin/2)
}
// errNoAdvertisedAddr is an error returned when we attempt to retrieve the
// advertised address of a node, but they don't have one.
var errNoAdvertisedAddr = errors.New("no advertised address found")
// fetchNodeAdvertisedAddrs attempts to fetch the advertised addresses of a node.
func (s *server) fetchNodeAdvertisedAddrs(pub *btcec.PublicKey) ([]net.Addr, error) {
vertex, err := route.NewVertexFromBytes(pub.SerializeCompressed())
if err != nil {
return nil, err
}
node, err := s.graphDB.FetchLightningNode(vertex)
if err != nil {
return nil, err
}
if len(node.Addresses) == 0 {
return nil, errNoAdvertisedAddr
}
return node.Addresses, nil
}
// fetchLastChanUpdate returns a function which is able to retrieve our latest
// channel update for a target channel.
func (s *server) fetchLastChanUpdate() func(lnwire.ShortChannelID) (
*lnwire.ChannelUpdate1, error) {
ourPubKey := s.identityECDH.PubKey().SerializeCompressed()
return func(cid lnwire.ShortChannelID) (*lnwire.ChannelUpdate1, error) {
info, edge1, edge2, err := s.graphBuilder.GetChannelByID(cid)
if err != nil {
return nil, err
}
return netann.ExtractChannelUpdate(
ourPubKey[:], info, edge1, edge2,
)
}
}
// applyChannelUpdate applies the channel update to the different sub-systems of
// the server. The useAlias boolean denotes whether or not to send an alias in
// place of the real SCID.
func (s *server) applyChannelUpdate(update *lnwire.ChannelUpdate1,
op *wire.OutPoint, useAlias bool) error {
var (
peerAlias *lnwire.ShortChannelID
defaultAlias lnwire.ShortChannelID
)
chanID := lnwire.NewChanIDFromOutPoint(*op)
// Fetch the peer's alias from the lnwire.ChannelID so it can be used
// in the ChannelUpdate if it hasn't been announced yet.
if useAlias {
foundAlias, _ := s.aliasMgr.GetPeerAlias(chanID)
if foundAlias != defaultAlias {
peerAlias = &foundAlias
}
}
errChan := s.authGossiper.ProcessLocalAnnouncement(
update, discovery.RemoteAlias(peerAlias),
)
select {
case err := <-errChan:
return err
case <-s.quit:
return ErrServerShuttingDown
}
}
// SendCustomMessage sends a custom message to the peer with the specified
// pubkey.
func (s *server) SendCustomMessage(peerPub [33]byte, msgType lnwire.MessageType,
data []byte) error {
peer, err := s.FindPeerByPubStr(string(peerPub[:]))
if err != nil {
return err
}
// We'll wait until the peer is active.
select {
case <-peer.ActiveSignal():
case <-peer.QuitSignal():
return fmt.Errorf("peer %x disconnected", peerPub)
case <-s.quit:
return ErrServerShuttingDown
}
msg, err := lnwire.NewCustom(msgType, data)
if err != nil {
return err
}
// Send the message as low-priority. For now we assume that all
// application-defined message are low priority.
return peer.SendMessageLazy(true, msg)
}
// newSweepPkScriptGen creates closure that generates a new public key script
// which should be used to sweep any funds into the on-chain wallet.
// Specifically, the script generated is a version 0, pay-to-witness-pubkey-hash
// (p2wkh) output.
func newSweepPkScriptGen(
wallet lnwallet.WalletController,
netParams *chaincfg.Params) func() fn.Result[lnwallet.AddrWithKey] {
return func() fn.Result[lnwallet.AddrWithKey] {
sweepAddr, err := wallet.NewAddress(
lnwallet.TaprootPubkey, false,
lnwallet.DefaultAccountName,
)
if err != nil {
return fn.Err[lnwallet.AddrWithKey](err)
}
addr, err := txscript.PayToAddrScript(sweepAddr)
if err != nil {
return fn.Err[lnwallet.AddrWithKey](err)
}
internalKeyDesc, err := lnwallet.InternalKeyForAddr(
wallet, netParams, addr,
)
if err != nil {
return fn.Err[lnwallet.AddrWithKey](err)
}
return fn.Ok(lnwallet.AddrWithKey{
DeliveryAddress: addr,
InternalKey: internalKeyDesc,
})
}
}
// shouldPeerBootstrap returns true if we should attempt to perform peer
// bootstrapping to actively seek our peers using the set of active network
// bootstrappers.
func shouldPeerBootstrap(cfg *Config) bool {
isSimnet := cfg.Bitcoin.SimNet
isSignet := cfg.Bitcoin.SigNet
isRegtest := cfg.Bitcoin.RegTest
isDevNetwork := isSimnet || isSignet || isRegtest
// TODO(yy): remove the check on simnet/regtest such that the itest is
// covering the bootstrapping process.
return !cfg.NoNetBootstrap && !isDevNetwork
}
// fetchClosedChannelSCIDs returns a set of SCIDs that have their force closing
// finished.
func (s *server) fetchClosedChannelSCIDs() map[lnwire.ShortChannelID]struct{} {
// Get a list of closed channels.
channels, err := s.chanStateDB.FetchClosedChannels(false)
if err != nil {
srvrLog.Errorf("Failed to fetch closed channels: %v", err)
return nil
}
// Save the SCIDs in a map.
closedSCIDs := make(map[lnwire.ShortChannelID]struct{}, len(channels))
for _, c := range channels {
// If the channel is not pending, its FC has been finalized.
if !c.IsPending {
closedSCIDs[c.ShortChanID] = struct{}{}
}
}
// Double check whether the reported closed channel has indeed finished
// closing.
//
// NOTE: There are misalignments regarding when a channel's FC is
// marked as finalized. We double check the pending channels to make
// sure the returned SCIDs are indeed terminated.
//
// TODO(yy): fix the misalignments in `FetchClosedChannels`.
pendings, err := s.chanStateDB.FetchPendingChannels()
if err != nil {
srvrLog.Errorf("Failed to fetch pending channels: %v", err)
return nil
}
for _, c := range pendings {
if _, ok := closedSCIDs[c.ShortChannelID]; !ok {
continue
}
// If the channel is still reported as pending, remove it from
// the map.
delete(closedSCIDs, c.ShortChannelID)
srvrLog.Warnf("Channel=%v is prematurely marked as finalized",
c.ShortChannelID)
}
return closedSCIDs
}
// getStartingBeat returns the current beat. This is used during the startup to
// initialize blockbeat consumers.
func (s *server) getStartingBeat() (*chainio.Beat, error) {
// beat is the current blockbeat.
var beat *chainio.Beat
// If the node is configured with nochainbackend mode (remote signer),
// we will skip fetching the best block.
if s.cfg.Bitcoin.Node == "nochainbackend" {
srvrLog.Info("Skipping block notification for nochainbackend " +
"mode")
return &chainio.Beat{}, nil
}
// We should get a notification with the current best block immediately
// by passing a nil block.
blockEpochs, err := s.cc.ChainNotifier.RegisterBlockEpochNtfn(nil)
if err != nil {
return beat, fmt.Errorf("register block epoch ntfn: %w", err)
}
defer blockEpochs.Cancel()
// We registered for the block epochs with a nil request. The notifier
// should send us the current best block immediately. So we need to
// wait for it here because we need to know the current best height.
select {
case bestBlock := <-blockEpochs.Epochs:
srvrLog.Infof("Received initial block %v at height %d",
bestBlock.Hash, bestBlock.Height)
// Update the current blockbeat.
beat = chainio.NewBeat(*bestBlock)
case <-s.quit:
srvrLog.Debug("LND shutting down")
}
return beat, nil
}
// ChanHasRbfCoopCloser returns true if the channel as identifier by the channel
// point has an active RBF chan closer.
func (s *server) ChanHasRbfCoopCloser(peerPub *btcec.PublicKey,
chanPoint wire.OutPoint) bool {
pubBytes := peerPub.SerializeCompressed()
s.mu.RLock()
targetPeer, ok := s.peersByPub[string(pubBytes)]
s.mu.RUnlock()
if !ok {
return false
}
return targetPeer.ChanHasRbfCoopCloser(chanPoint)
}
// attemptCoopRbfFeeBump attempts to look up the active chan closer for a
// channel given the outpoint. If found, we'll attempt to do a fee bump,
// returning channels used for updates. If the channel isn't currently active
// (p2p connection established), then his function will return an error.
func (s *server) attemptCoopRbfFeeBump(ctx context.Context,
chanPoint wire.OutPoint, feeRate chainfee.SatPerKWeight,
deliveryScript lnwire.DeliveryAddress) (*peer.CoopCloseUpdates, error) {
// First, we'll attempt to look up the channel based on it's
// ChannelPoint.
channel, err := s.chanStateDB.FetchChannel(chanPoint)
if err != nil {
return nil, fmt.Errorf("unable to fetch channel: %w", err)
}
// From the channel, we can now get the pubkey of the peer, then use
// that to eventually get the chan closer.
peerPub := channel.IdentityPub.SerializeCompressed()
// Now that we have the peer pub, we can look up the peer itself.
s.mu.RLock()
targetPeer, ok := s.peersByPub[string(peerPub)]
s.mu.RUnlock()
if !ok {
return nil, fmt.Errorf("peer for ChannelPoint(%v) is "+
"not online", chanPoint)
}
closeUpdates, err := targetPeer.TriggerCoopCloseRbfBump(
ctx, chanPoint, feeRate, deliveryScript,
)
if err != nil {
return nil, fmt.Errorf("unable to trigger coop rbf fee bump: "+
"%w", err)
}
return closeUpdates, nil
}
// AttemptRBFCloseUpdate attempts to trigger a new RBF iteration for a co-op
// close update. This route it to be used only if the target channel in question
// is no longer active in the link. This can happen when we restart while we
// already have done a single RBF co-op close iteration.
func (s *server) AttemptRBFCloseUpdate(ctx context.Context,
chanPoint wire.OutPoint, feeRate chainfee.SatPerKWeight,
deliveryScript lnwire.DeliveryAddress) (*peer.CoopCloseUpdates, error) {
// If the channel is present in the switch, then the request should flow
// through the switch instead.
chanID := lnwire.NewChanIDFromOutPoint(chanPoint)
if _, err := s.htlcSwitch.GetLink(chanID); err == nil {
return nil, fmt.Errorf("ChannelPoint(%v) is active in link, "+
"invalid request", chanPoint)
}
// At this point, we know that the channel isn't present in the link, so
// we'll check to see if we have an entry in the active chan closer map.
updates, err := s.attemptCoopRbfFeeBump(
ctx, chanPoint, feeRate, deliveryScript,
)
if err != nil {
return nil, fmt.Errorf("unable to attempt coop rbf fee bump "+
"ChannelPoint(%v)", chanPoint)
}
return updates, nil
}
package shachain
import (
"crypto/sha256"
"errors"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// element represents the entity which contains the hash and index
// corresponding to it. An element is the output of the shachain PRF. By
// comparing two indexes we're able to mutate the hash in such way to derive
// another element.
type element struct {
index index
hash chainhash.Hash
}
// newElementFromStr creates new element from the given hash string.
func newElementFromStr(s string, index index) (*element, error) {
hash, err := hashFromString(s)
if err != nil {
return nil, err
}
return &element{
index: index,
hash: *hash,
}, nil
}
// derive computes one shachain element from another by applying a series of
// bit flips and hashing operations based on the starting and ending index.
func (e *element) derive(toIndex index) (*element, error) {
fromIndex := e.index
positions, err := fromIndex.deriveBitTransformations(toIndex)
if err != nil {
return nil, err
}
buf := e.hash.CloneBytes()
for _, position := range positions {
// Flip the bit and then hash the current state.
byteNumber := position / 8
bitNumber := position % 8
buf[byteNumber] ^= (1 << bitNumber)
h := sha256.Sum256(buf)
buf = h[:]
}
hash, err := chainhash.NewHash(buf)
if err != nil {
return nil, err
}
return &element{
index: toIndex,
hash: *hash,
}, nil
}
// isEqual returns true if two elements are identical and false otherwise.
func (e *element) isEqual(e2 *element) bool {
return (e.index == e2.index) &&
(&e.hash).IsEqual(&e2.hash)
}
const (
// maxHeight is used to determine the maximum allowable index and the
// length of the array required to order to derive all previous hashes
// by index. The entries of this array as also known as buckets.
maxHeight uint8 = 48
// rootIndex is an index which corresponds to the root hash.
rootIndex index = 0
)
// startIndex is the index of first element in the shachain PRF.
var startIndex index = (1 << maxHeight) - 1
// index is a number which identifies the hash number and serves as a way to
// determine the hashing operation required to derive one hash from another.
// index is initialized with the startIndex and decreases down to zero with
// successive derivations.
type index uint64
// newIndex is used to create index instance. The inner operations with index
// implies that index decreasing from some max number to zero, but for
// simplicity and backward compatibility with previous logic it was transformed
// to work in opposite way.
func newIndex(v uint64) index {
return startIndex - index(v)
}
// deriveBitTransformations function checks that the 'to' index is derivable
// from the 'from' index by checking the indexes are prefixes of another. The
// bit positions where the zeroes should be changed to ones in order for the
// indexes to become the same are returned. This set of bits is needed in order
// to derive one hash from another.
//
// NOTE: The index 'to' is derivable from index 'from' iff index 'from' lies
// left and above index 'to' on graph below, for example:
// 1. 7(0b111) -> 7
// 2. 6(0b110) -> 6,7
// 3. 5(0b101) -> 5
// 4. 4(0b100) -> 4,5,6,7
// 5. 3(0b011) -> 3
// 6. 2(0b010) -> 2, 3
// 7. 1(0b001) -> 1
//
// ^ bucket number
// |
// 3 | x
// | |
// 2 | | x
// | | |
// 1 | | x | x
// | | | | |
// 0 | | x | x | x | x
// | | | | | | | | |
// +---|---|---|---|---|---|---|---|---> index
// 0 1 2 3 4 5 6 7
func (from index) deriveBitTransformations(to index) ([]uint8, error) {
var positions []uint8
if from == to {
return positions, nil
}
// + --------------- +
// | № | from | to |
// + -- + ---- + --- +
// | 48 | 1 | 1 |
// | 47 | 0 | 0 | [48-5] - same part of 'from' and 'to'
// | 46 | 0 | 0 | indexes which also is called prefix.
// ....
// | 5 | 1 | 1 |
// | 4 | 0 | 1 | <--- position after which indexes becomes
// | 3 | 0 | 0 | different, after this position
// | 2 | 0 | 1 | bits in 'from' index all should be
// | 1 | 0 | 0 | zeros or such indexes considered to be
// | 0 | 0 | 1 | not derivable.
// + -- + ---- + --- +
zeros := countTrailingZeros(from)
if uint64(from) != getPrefix(to, zeros) {
return nil, errors.New("prefixes are different - indexes " +
"aren't derivable")
}
// The remaining part of 'to' index represents the positions which we
// will use then in order to derive one element from another.
for position := zeros - 1; ; position-- {
if getBit(to, position) == 1 {
positions = append(positions, position)
}
if position == 0 {
break
}
}
return positions, nil
}
package shachain
import (
"io"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// Producer is an interface which serves as an abstraction over the data
// structure responsible for efficiently generating the secrets for a
// particular index based on a root seed. The generation of secrets should be
// made in such way that secret store might efficiently store and retrieve the
// secrets. This is typically implemented as a tree-based PRF.
type Producer interface {
// AtIndex produces a secret by evaluating using the initial seed and a
// particular index.
AtIndex(uint64) (*chainhash.Hash, error)
// Encode writes a binary serialization of the Producer implementation
// to the passed io.Writer.
Encode(io.Writer) error
}
// RevocationProducer is an implementation of Producer interface using the
// shachain PRF construct. Starting with a single 32-byte element generated
// from a CSPRNG, shachain is able to efficiently generate a nearly unbounded
// number of secrets while maintaining a constant amount of storage. The
// original description of shachain can be found here:
// https://github.com/rustyrussell/ccan/blob/master/ccan/crypto/shachain/design.txt
// with supplementary material here:
// https://github.com/lightningnetwork/lightning-rfc/blob/master/03-transactions.md#per-commitment-secret-requirements
type RevocationProducer struct {
// root is the element from which we may generate all hashes which
// corresponds to the index domain [281474976710655,0].
root *element
}
// A compile time check to ensure RevocationProducer implements the Producer
// interface.
var _ Producer = (*RevocationProducer)(nil)
// NewRevocationProducer creates new instance of shachain producer.
func NewRevocationProducer(root chainhash.Hash) *RevocationProducer {
return &RevocationProducer{
root: &element{
index: rootIndex,
hash: root,
}}
}
// NewRevocationProducerFromBytes deserializes an instance of a
// RevocationProducer encoded in the passed byte slice, returning a fully
// initialized instance of a RevocationProducer.
func NewRevocationProducerFromBytes(data []byte) (*RevocationProducer, error) {
root, err := chainhash.NewHash(data)
if err != nil {
return nil, err
}
return &RevocationProducer{
root: &element{
index: rootIndex,
hash: *root,
},
}, nil
}
// AtIndex produces a secret by evaluating using the initial seed and a
// particular index.
//
// NOTE: Part of the Producer interface.
func (p *RevocationProducer) AtIndex(v uint64) (*chainhash.Hash, error) {
ind := newIndex(v)
element, err := p.root.derive(ind)
if err != nil {
return nil, err
}
return &element.hash, nil
}
// Encode writes a binary serialization of the Producer implementation to the
// passed io.Writer.
//
// NOTE: Part of the Producer interface.
func (p *RevocationProducer) Encode(w io.Writer) error {
_, err := w.Write(p.root.hash[:])
return err
}
package shachain
import (
"encoding/binary"
"io"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/go-errors/errors"
)
// Store is an interface which serves as an abstraction over data structure
// responsible for efficiently storing and restoring of hash secrets by given
// indexes.
//
// Description: The Lightning Network wants a chain of (say 1 million)
// unguessable 256 bit values; we generate them and send them one at a time to
// a remote node. We don't want the remote node to have to store all the
// values, so it's better if they can derive them once they see them.
type Store interface {
// LookUp function is used to restore/lookup/fetch the previous secret
// by its index.
LookUp(uint64) (*chainhash.Hash, error)
// AddNextEntry attempts to store the given hash within its internal
// storage in an efficient manner.
//
// NOTE: The hashes derived from the shachain MUST be inserted in the
// order they're produced by a shachain.Producer.
AddNextEntry(*chainhash.Hash) error
// Encode writes a binary serialization of the shachain elements
// currently saved by implementation of shachain.Store to the passed
// io.Writer.
Encode(io.Writer) error
}
// RevocationStore is a concrete implementation of the Store interface. The
// revocation store is able to efficiently store N derived shachain elements in
// a space efficient manner with a space complexity of O(log N). The original
// description of the storage methodology can be found here:
// https://github.com/lightningnetwork/lightning-rfc/blob/master/03-transactions.md#efficient-per-commitment-secret-storage
type RevocationStore struct {
// lenBuckets stores the number of currently active buckets.
lenBuckets uint8
// buckets is an array of elements from which we may derive all
// previous elements, each bucket corresponds to the element with the
// particular number of trailing zeros.
buckets [maxHeight]element
// index is an available index which will be assigned to the new
// element.
index index
}
// A compile time check to ensure RevocationStore implements the Store
// interface.
var _ Store = (*RevocationStore)(nil)
// NewRevocationStore creates the new shachain store.
func NewRevocationStore() *RevocationStore {
return &RevocationStore{
lenBuckets: 0,
index: startIndex,
}
}
// NewRevocationStoreFromBytes recreates the initial store state from the given
// binary shachain store representation.
func NewRevocationStoreFromBytes(r io.Reader) (*RevocationStore, error) {
store := &RevocationStore{}
if err := binary.Read(r, binary.BigEndian, &store.lenBuckets); err != nil {
return nil, err
}
for i := uint8(0); i < store.lenBuckets; i++ {
var hashIndex index
err := binary.Read(r, binary.BigEndian, &hashIndex)
if err != nil {
return nil, err
}
var nextHash chainhash.Hash
if _, err := io.ReadFull(r, nextHash[:]); err != nil {
return nil, err
}
store.buckets[i] = element{
index: hashIndex,
hash: nextHash,
}
}
if err := binary.Read(r, binary.BigEndian, &store.index); err != nil {
return nil, err
}
return store, nil
}
// LookUp function is used to restore/lookup/fetch the previous secret by its
// index. If secret which corresponds to given index was not previously placed
// in store we will not able to derive it and function will fail.
//
// NOTE: This function is part of the Store interface.
func (store *RevocationStore) LookUp(v uint64) (*chainhash.Hash, error) {
ind := newIndex(v)
// Trying to derive the index from one of the existing buckets elements.
for i := uint8(0); i < store.lenBuckets; i++ {
element, err := store.buckets[i].derive(ind)
if err != nil {
continue
}
return &element.hash, nil
}
return nil, errors.Errorf("unable to derive hash #%v", ind)
}
// AddNextEntry attempts to store the given hash within its internal storage in
// an efficient manner.
//
// NOTE: The hashes derived from the shachain MUST be inserted in the order
// they're produced by a shachain.Producer.
//
// NOTE: This function is part of the Store interface.
func (store *RevocationStore) AddNextEntry(hash *chainhash.Hash) error {
newElement := &element{
index: store.index,
hash: *hash,
}
bucket := countTrailingZeros(newElement.index)
for i := uint8(0); i < bucket; i++ {
e, err := newElement.derive(store.buckets[i].index)
if err != nil {
return err
}
if !e.isEqual(&store.buckets[i]) {
return errors.New("hash isn't derivable from " +
"previous ones")
}
}
store.buckets[bucket] = *newElement
if bucket+1 > store.lenBuckets {
store.lenBuckets = bucket + 1
}
store.index--
return nil
}
// Encode writes a binary serialization of the shachain elements currently
// saved by implementation of shachain.Store to the passed io.Writer.
//
// NOTE: This function is part of the Store interface.
func (store *RevocationStore) Encode(w io.Writer) error {
err := binary.Write(w, binary.BigEndian, store.lenBuckets)
if err != nil {
return err
}
for i := uint8(0); i < store.lenBuckets; i++ {
element := store.buckets[i]
err := binary.Write(w, binary.BigEndian, element.index)
if err != nil {
return err
}
if _, err = w.Write(element.hash[:]); err != nil {
return err
}
}
return binary.Write(w, binary.BigEndian, store.index)
}
package shachain
import (
"encoding/hex"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// getBit return bit on index at position.
func getBit(index index, position uint8) uint8 {
return uint8((uint64(index) >> position) & 1)
}
func getPrefix(index index, position uint8) uint64 {
// + -------------------------- +
// | № | value | mask | return |
// + -- + ----- + ---- + ------ +
// | 63 | 1 | 0 | 0 |
// | 62 | 0 | 0 | 0 |
// | 61 | 1 | 0 | 0 |
// ....
// | 4 | 1 | 0 | 0 |
// | 3 | 1 | 0 | 0 |
// | 2 | 1 | 1 | 1 | <--- position
// | 1 | 0 | 1 | 0 |
// | 0 | 1 | 1 | 1 |
// + -- + ----- + ---- + ------ +
var zero uint64
mask := (zero - 1) - uint64((1<<position)-1)
return (uint64(index) & mask)
}
// countTrailingZeros counts number of trailing zero bits, this function is
// used to determine the number of element bucket.
func countTrailingZeros(index index) uint8 {
var zeros uint8
for ; zeros < maxHeight; zeros++ {
if getBit(index, zeros) != 0 {
break
}
}
return zeros
}
// hashFromString takes a hex-encoded string as input and creates an instance of
// chainhash.Hash. The chainhash.NewHashFromStr function not suitable because
// it reverse the given hash.
func hashFromString(s string) (*chainhash.Hash, error) {
// Return an error if hash string is too long.
if len(s) > chainhash.MaxHashStringSize {
return nil, chainhash.ErrHashStrSize
}
// Hex decoder expects the hash to be a multiple of two.
if len(s)%2 != 0 {
s = "0" + s
}
// Convert string hash to bytes.
buf, err := hex.DecodeString(s)
if err != nil {
return nil, err
}
hash, err := chainhash.NewHash(buf)
if err != nil {
return nil, err
}
return hash, nil
}
package signal
import "github.com/btcsuite/btclog/v2"
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
DisableLog()
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Heavily inspired by https://github.com/btcsuite/btcd/blob/master/signal.go
// Copyright (C) 2015-2022 The Lightning Network Developers
package signal
import (
"errors"
"fmt"
"os"
"os/signal"
"sync/atomic"
"syscall"
"github.com/coreos/go-systemd/daemon"
)
var (
// started indicates whether we have started our main interrupt handler.
// This field should be used atomically.
started int32
)
// systemdNotifyReady notifies systemd about LND being ready, logs the result of
// the operation or possible error. Besides logging, systemd being unavailable
// is ignored.
func systemdNotifyReady() error {
notified, err := daemon.SdNotify(false, daemon.SdNotifyReady)
if err != nil {
err := fmt.Errorf("failed to notify systemd %v (if you aren't "+
"running systemd clear the environment variable "+
"NOTIFY_SOCKET)", err)
log.Error(err)
// The SdNotify doc says it's common to ignore the
// error. We don't want to ignore it because if someone
// set up systemd to wait for initialization other
// processes would get stuck.
return err
}
if notified {
log.Info("Systemd was notified about our readiness")
} else {
log.Info("We're not running within systemd or the service " +
"type is not 'notify'")
}
return nil
}
// systemdNotifyStop notifies systemd that LND is stopping and logs error if
// the notification failed. It also logs if the notification was actually sent.
// Systemd being unavailable is intentionally ignored.
func systemdNotifyStop() {
notified, err := daemon.SdNotify(false, daemon.SdNotifyStopping)
// Just log - we're stopping anyway.
if err != nil {
log.Errorf("Failed to notify systemd: %v", err)
}
if notified {
log.Infof("Systemd was notified about stopping")
}
}
// Notifier handles notifications about status of LND.
type Notifier struct {
// notifiedReady remembers whether Ready was sent to avoid sending it
// multiple times.
notifiedReady bool
}
// NotifyReady notifies other applications that RPC is ready.
func (notifier *Notifier) NotifyReady(walletUnlocked bool) error {
if !notifier.notifiedReady {
err := systemdNotifyReady()
if err != nil {
return err
}
notifier.notifiedReady = true
}
if walletUnlocked {
_, _ = daemon.SdNotify(false, "STATUS=Wallet unlocked")
} else {
_, _ = daemon.SdNotify(false, "STATUS=Wallet locked")
}
return nil
}
// notifyStop notifies other applications that LND is stopping.
func (notifier *Notifier) notifyStop() {
systemdNotifyStop()
}
// Interceptor contains channels and methods regarding application shutdown
// and interrupt signals.
type Interceptor struct {
// interruptChannel is used to receive SIGINT (Ctrl+C) signals.
interruptChannel chan os.Signal
// shutdownChannel is closed once the main interrupt handler exits.
shutdownChannel chan struct{}
// shutdownRequestChannel is used to request the daemon to shutdown
// gracefully, similar to when receiving SIGINT.
shutdownRequestChannel chan struct{}
// quit is closed when instructing the main interrupt handler to exit.
// Note that to avoid losing notifications, only shutdown func may
// close this channel.
quit chan struct{}
// Notifier handles sending shutdown notifications.
Notifier Notifier
}
// Intercept starts the interception of interrupt signals and returns an `Interceptor` instance.
// Note that any previous active interceptor must be stopped before a new one can be created.
func Intercept() (Interceptor, error) {
if !atomic.CompareAndSwapInt32(&started, 0, 1) {
return Interceptor{}, errors.New("intercept already started")
}
channels := Interceptor{
interruptChannel: make(chan os.Signal, 1),
shutdownChannel: make(chan struct{}),
shutdownRequestChannel: make(chan struct{}),
quit: make(chan struct{}),
}
signalsToCatch := []os.Signal{
os.Interrupt,
os.Kill,
syscall.SIGTERM,
syscall.SIGQUIT,
}
signal.Notify(channels.interruptChannel, signalsToCatch...)
go channels.mainInterruptHandler()
return channels, nil
}
// mainInterruptHandler listens for SIGINT (Ctrl+C) signals on the
// interruptChannel and shutdown requests on the shutdownRequestChannel, and
// invokes the registered interruptCallbacks accordingly. It also listens for
// callback registration.
// It must be run as a goroutine.
func (c *Interceptor) mainInterruptHandler() {
defer atomic.StoreInt32(&started, 0)
// isShutdown is a flag which is used to indicate whether or not
// the shutdown signal has already been received and hence any future
// attempts to add a new interrupt handler should invoke them
// immediately.
var isShutdown bool
// shutdown invokes the registered interrupt handlers, then signals the
// shutdownChannel.
shutdown := func() {
// Ignore more than one shutdown signal.
if isShutdown {
log.Infof("Already shutting down...")
return
}
isShutdown = true
log.Infof("Shutting down...")
c.Notifier.notifyStop()
// Signal the main interrupt handler to exit, and stop accept
// post-facto requests.
close(c.quit)
}
for {
select {
case signal := <-c.interruptChannel:
log.Infof("Received %v", signal)
shutdown()
case <-c.shutdownRequestChannel:
log.Infof("Received shutdown request.")
shutdown()
case <-c.quit:
log.Infof("Gracefully shutting down.")
close(c.shutdownChannel)
signal.Stop(c.interruptChannel)
return
}
}
}
// Listening returns true if the main interrupt handler has been started, and
// has not been killed.
func (c *Interceptor) Listening() bool {
// If our started field is not set, we are not yet listening for
// interrupts.
if atomic.LoadInt32(&started) != 1 {
return false
}
// If we have started our main goroutine, we check whether we have
// stopped it yet.
return c.Alive()
}
// Alive returns true if the main interrupt handler has not been killed.
func (c *Interceptor) Alive() bool {
select {
case <-c.quit:
return false
default:
return true
}
}
// RequestShutdown initiates a graceful shutdown from the application.
func (c *Interceptor) RequestShutdown() {
select {
case c.shutdownRequestChannel <- struct{}{}:
case <-c.quit:
}
}
// ShutdownChannel returns the channel that will be closed once the main
// interrupt handler has exited.
func (c *Interceptor) ShutdownChannel() <-chan struct{} {
return c.shutdownChannel
}
package lnd
import (
"fmt"
"net"
"reflect"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/aliasmgr"
"github.com/lightningnetwork/lnd/autopilot"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc/autopilotrpc"
"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
"github.com/lightningnetwork/lnd/lnrpc/devrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lnrpc/neutrinorpc"
"github.com/lightningnetwork/lnd/lnrpc/peersrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc"
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/sweep"
"github.com/lightningnetwork/lnd/watchtower"
"github.com/lightningnetwork/lnd/watchtower/wtclient"
"google.golang.org/protobuf/proto"
)
// subRPCServerConfigs is special sub-config in the main configuration that
// houses the configuration for the optional sub-servers. These sub-RPC servers
// are meant to house experimental new features that may eventually make it
// into the main RPC server that lnd exposes. Special methods are present on
// this struct to allow the main RPC server to create and manipulate the
// sub-RPC servers in a generalized manner.
type subRPCServerConfigs struct {
// SignRPC is a sub-RPC server that exposes signing of arbitrary inputs
// as a gRPC service.
SignRPC *signrpc.Config `group:"signrpc" namespace:"signrpc"`
// WalletKitRPC is a sub-RPC server that exposes functionality allowing
// a client to send transactions through a wallet, publish them, and
// also requests keys and addresses under control of the backing
// wallet.
WalletKitRPC *walletrpc.Config `group:"walletrpc" namespace:"walletrpc"`
// AutopilotRPC is a sub-RPC server that exposes methods on the running
// autopilot as a gRPC service.
AutopilotRPC *autopilotrpc.Config `group:"autopilotrpc" namespace:"autopilotrpc"`
// ChainRPC is a sub-RPC server that exposes functionality allowing a
// client to be notified of certain on-chain events (new blocks,
// confirmations, spends).
ChainRPC *chainrpc.Config `group:"chainrpc" namespace:"chainrpc"`
// InvoicesRPC is a sub-RPC server that exposes invoice related methods
// as a gRPC service.
InvoicesRPC *invoicesrpc.Config `group:"invoicesrpc" namespace:"invoicesrpc"`
// PeersRPC is a sub-RPC server that exposes peer related methods
// as a gRPC service.
PeersRPC *peersrpc.Config `group:"peersrpc" namespace:"peersrpc"`
// NeutrinoKitRPC is a sub-RPC server that exposes functionality allowing
// a client to interact with a running neutrino node.
NeutrinoKitRPC *neutrinorpc.Config `group:"neutrinorpc" namespace:"neutrinorpc"`
// RouterRPC is a sub-RPC server the exposes functionality that allows
// clients to send payments on the network, and perform Lightning
// payment related queries such as requests for estimates of off-chain
// fees.
RouterRPC *routerrpc.Config `group:"routerrpc" namespace:"routerrpc"`
// WatchtowerRPC is a sub-RPC server that exposes functionality allowing
// clients to monitor and control their embedded watchtower.
WatchtowerRPC *watchtowerrpc.Config `group:"watchtowerrpc" namespace:"watchtowerrpc"`
// WatchtowerClientRPC is a sub-RPC server that exposes functionality
// that allows clients to interact with the active watchtower client
// instance within lnd in order to add, remove, list registered client
// towers, etc.
WatchtowerClientRPC *wtclientrpc.Config `group:"wtclientrpc" namespace:"wtclientrpc"`
// DevRPC is a sub-RPC server that exposes functionality that allows
// developers manipulate LND state that is normally not possible.
// Should only be used for development purposes.
DevRPC *devrpc.Config `group:"devrpc" namespace:"devrpc"`
}
// PopulateDependencies attempts to iterate through all the sub-server configs
// within this struct, and populate the items it requires based on the main
// configuration file, and the chain control.
//
// NOTE: This MUST be called before any callers are permitted to execute the
// FetchConfig method.
func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config,
cc *chainreg.ChainControl,
networkDir string, macService *macaroons.Service,
atpl *autopilot.Manager,
invoiceRegistry *invoices.InvoiceRegistry,
htlcSwitch *htlcswitch.Switch,
activeNetParams *chaincfg.Params,
chanRouter *routing.ChannelRouter,
routerBackend *routerrpc.RouterBackend,
nodeSigner *netann.NodeSigner,
graphDB *graphdb.ChannelGraph,
chanStateDB *channeldb.ChannelStateDB,
sweeper *sweep.UtxoSweeper,
tower *watchtower.Standalone,
towerClientMgr *wtclient.Manager,
tcpResolver lncfg.TCPResolver,
genInvoiceFeatures func() *lnwire.FeatureVector,
genAmpInvoiceFeatures func() *lnwire.FeatureVector,
getNodeAnnouncement func() lnwire.NodeAnnouncement,
updateNodeAnnouncement func(features *lnwire.RawFeatureVector,
modifiers ...netann.NodeAnnModifier) error,
parseAddr func(addr string) (net.Addr, error),
rpcLogger btclog.Logger, aliasMgr *aliasmgr.Manager,
auxDataParser fn.Option[AuxDataParser],
invoiceHtlcModifier *invoices.HtlcModificationInterceptor) error {
// First, we'll use reflect to obtain a version of the config struct
// that allows us to programmatically inspect its fields.
selfVal := extractReflectValue(s)
selfType := selfVal.Type()
numFields := selfVal.NumField()
for i := 0; i < numFields; i++ {
field := selfVal.Field(i)
fieldElem := field.Elem()
fieldName := selfType.Field(i).Name
ltndLog.Debugf("Populating dependencies for sub RPC "+
"server: %v", fieldName)
// If this sub-config doesn't actually have any fields, then we
// can skip it, as the build tag for it is likely off.
if fieldElem.NumField() == 0 {
continue
}
if !fieldElem.CanSet() {
continue
}
switch subCfg := field.Interface().(type) {
case *signrpc.Config:
subCfgValue := extractReflectValue(subCfg)
subCfgValue.FieldByName("MacService").Set(
reflect.ValueOf(macService),
)
subCfgValue.FieldByName("NetworkDir").Set(
reflect.ValueOf(networkDir),
)
subCfgValue.FieldByName("Signer").Set(
reflect.ValueOf(cc.Signer),
)
subCfgValue.FieldByName("KeyRing").Set(
reflect.ValueOf(cc.KeyRing),
)
case *walletrpc.Config:
subCfgValue := extractReflectValue(subCfg)
subCfgValue.FieldByName("NetworkDir").Set(
reflect.ValueOf(networkDir),
)
subCfgValue.FieldByName("MacService").Set(
reflect.ValueOf(macService),
)
subCfgValue.FieldByName("FeeEstimator").Set(
reflect.ValueOf(cc.FeeEstimator),
)
subCfgValue.FieldByName("Wallet").Set(
reflect.ValueOf(cc.Wallet),
)
subCfgValue.FieldByName("CoinSelectionLocker").Set(
reflect.ValueOf(cc.Wallet),
)
subCfgValue.FieldByName("KeyRing").Set(
reflect.ValueOf(cc.KeyRing),
)
subCfgValue.FieldByName("Sweeper").Set(
reflect.ValueOf(sweeper),
)
subCfgValue.FieldByName("Chain").Set(
reflect.ValueOf(cc.ChainIO),
)
subCfgValue.FieldByName("ChainParams").Set(
reflect.ValueOf(activeNetParams),
)
subCfgValue.FieldByName("CurrentNumAnchorChans").Set(
reflect.ValueOf(cc.Wallet.CurrentNumAnchorChans),
)
subCfgValue.FieldByName("CoinSelectionStrategy").Set(
reflect.ValueOf(
cc.Wallet.Cfg.CoinSelectionStrategy,
),
)
subCfgValue.FieldByName("ChanStateDB").Set(
reflect.ValueOf(chanStateDB),
)
case *autopilotrpc.Config:
subCfgValue := extractReflectValue(subCfg)
subCfgValue.FieldByName("Manager").Set(
reflect.ValueOf(atpl),
)
case *chainrpc.Config:
subCfgValue := extractReflectValue(subCfg)
subCfgValue.FieldByName("NetworkDir").Set(
reflect.ValueOf(networkDir),
)
subCfgValue.FieldByName("MacService").Set(
reflect.ValueOf(macService),
)
subCfgValue.FieldByName("ChainNotifier").Set(
reflect.ValueOf(cc.ChainNotifier),
)
subCfgValue.FieldByName("Chain").Set(
reflect.ValueOf(cc.ChainIO),
)
case *invoicesrpc.Config:
subCfgValue := extractReflectValue(subCfg)
subCfgValue.FieldByName("NetworkDir").Set(
reflect.ValueOf(networkDir),
)
subCfgValue.FieldByName("MacService").Set(
reflect.ValueOf(macService),
)
subCfgValue.FieldByName("InvoiceRegistry").Set(
reflect.ValueOf(invoiceRegistry),
)
subCfgValue.FieldByName("HtlcModifier").Set(
reflect.ValueOf(invoiceHtlcModifier),
)
subCfgValue.FieldByName("IsChannelActive").Set(
reflect.ValueOf(htlcSwitch.HasActiveLink),
)
subCfgValue.FieldByName("ChainParams").Set(
reflect.ValueOf(activeNetParams),
)
subCfgValue.FieldByName("NodeSigner").Set(
reflect.ValueOf(nodeSigner),
)
defaultDelta := cfg.Bitcoin.TimeLockDelta
subCfgValue.FieldByName("DefaultCLTVExpiry").Set(
reflect.ValueOf(defaultDelta),
)
subCfgValue.FieldByName("Graph").Set(
reflect.ValueOf(graphDB),
)
subCfgValue.FieldByName("ChanStateDB").Set(
reflect.ValueOf(chanStateDB),
)
subCfgValue.FieldByName("GenInvoiceFeatures").Set(
reflect.ValueOf(genInvoiceFeatures),
)
subCfgValue.FieldByName("GenAmpInvoiceFeatures").Set(
reflect.ValueOf(genAmpInvoiceFeatures),
)
subCfgValue.FieldByName("GetAlias").Set(
reflect.ValueOf(aliasMgr.GetPeerAlias),
)
parseAuxData := func(m proto.Message) error {
return fn.MapOptionZ(
auxDataParser,
func(p AuxDataParser) error {
return p.InlineParseCustomData(
m,
)
},
)
}
subCfgValue.FieldByName("ParseAuxData").Set(
reflect.ValueOf(parseAuxData),
)
case *neutrinorpc.Config:
subCfgValue := extractReflectValue(subCfg)
subCfgValue.FieldByName("NeutrinoCS").Set(
reflect.ValueOf(cc.Cfg.NeutrinoCS),
)
// RouterRPC isn't conditionally compiled and doesn't need to be
// populated using reflection.
case *routerrpc.Config:
subCfgValue := extractReflectValue(subCfg)
subCfgValue.FieldByName("AliasMgr").Set(
reflect.ValueOf(aliasMgr),
)
case *watchtowerrpc.Config:
subCfgValue := extractReflectValue(subCfg)
subCfgValue.FieldByName("Active").Set(
reflect.ValueOf(tower != nil),
)
subCfgValue.FieldByName("Tower").Set(
reflect.ValueOf(tower),
)
case *wtclientrpc.Config:
subCfgValue := extractReflectValue(subCfg)
if towerClientMgr != nil {
subCfgValue.FieldByName("Active").Set(
reflect.ValueOf(towerClientMgr != nil),
)
subCfgValue.FieldByName("ClientMgr").Set(
reflect.ValueOf(towerClientMgr),
)
}
subCfgValue.FieldByName("Resolver").Set(
reflect.ValueOf(tcpResolver),
)
subCfgValue.FieldByName("Log").Set(
reflect.ValueOf(rpcLogger),
)
case *devrpc.Config:
subCfgValue := extractReflectValue(subCfg)
subCfgValue.FieldByName("ActiveNetParams").Set(
reflect.ValueOf(activeNetParams),
)
subCfgValue.FieldByName("GraphDB").Set(
reflect.ValueOf(graphDB),
)
subCfgValue.FieldByName("Switch").Set(
reflect.ValueOf(htlcSwitch),
)
case *peersrpc.Config:
subCfgValue := extractReflectValue(subCfg)
subCfgValue.FieldByName("GetNodeAnnouncement").Set(
reflect.ValueOf(getNodeAnnouncement),
)
subCfgValue.FieldByName("ParseAddr").Set(
reflect.ValueOf(parseAddr),
)
subCfgValue.FieldByName("UpdateNodeAnnouncement").Set(
reflect.ValueOf(updateNodeAnnouncement),
)
default:
return fmt.Errorf("unknown field: %v, %T", fieldName,
cfg)
}
}
// Populate routerrpc dependencies.
s.RouterRPC.NetworkDir = networkDir
s.RouterRPC.MacService = macService
s.RouterRPC.Router = chanRouter
s.RouterRPC.RouterBackend = routerBackend
return nil
}
// FetchConfig attempts to locate an existing configuration file mapped to the
// target sub-server. If we're unable to find a config file matching the
// subServerName name, then false will be returned for the second parameter.
//
// NOTE: Part of the lnrpc.SubServerConfigDispatcher interface.
func (s *subRPCServerConfigs) FetchConfig(subServerName string) (interface{}, bool) {
// First, we'll use reflect to obtain a version of the config struct
// that allows us to programmatically inspect its fields.
selfVal := extractReflectValue(s)
// Now that we have the value of the struct, we can check to see if it
// has an attribute with the same name as the subServerName.
configVal := selfVal.FieldByName(subServerName)
// We'll now ensure that this field actually exists in this value. If
// not, then we'll return false for the ok value to indicate to the
// caller that this field doesn't actually exist.
if !configVal.IsValid() {
return nil, false
}
configValElem := configVal.Elem()
// If a config of this type is found, it doesn't have any fields, then
// it's the same as if it wasn't present. This can happen if the build
// tag for the sub-server is inactive.
if configValElem.NumField() == 0 {
return nil, false
}
// At this pint, we know that the field is actually present in the
// config struct, so we can return it directly.
return configVal.Interface(), true
}
// extractReflectValue attempts to extract the value from an interface using
// the reflect package. The resulting reflect.Value allows the caller to
// programmatically examine and manipulate the underlying value.
func extractReflectValue(instance interface{}) reflect.Value {
var val reflect.Value
// If the type of the instance is a pointer, then we need to deference
// the pointer one level to get its value. Otherwise, we can access the
// value directly.
if reflect.TypeOf(instance).Kind() == reflect.Ptr {
val = reflect.ValueOf(instance).Elem()
} else {
val = reflect.ValueOf(instance)
}
return val
}
package subscribe
import (
"errors"
"sync"
"sync/atomic"
"github.com/lightningnetwork/lnd/queue"
)
// ErrServerShuttingDown is an error returned in case the server is in the
// process of shutting down.
var ErrServerShuttingDown = errors.New("subscription server shutting down")
// Client is used to get notified about updates the caller has subscribed to,
type Client struct {
// cancel should be called in case the client no longer wants to
// subscribe for updates from the server.
cancel func()
updates *queue.ConcurrentQueue
quit chan struct{}
}
// Updates returns a read-only channel where the updates the client has
// subscribed to will be delivered.
func (c *Client) Updates() <-chan interface{} {
return c.updates.ChanOut()
}
// Quit is a channel that will be closed in case the server decides to no
// longer deliver updates to this client.
func (c *Client) Quit() <-chan struct{} {
return c.quit
}
// Cancel should be called in case the client no longer wants to
// subscribe for updates from the server.
func (c *Client) Cancel() {
c.cancel()
}
// Server is a struct that manages a set of subscriptions and their
// corresponding clients. Any update will be delivered to all active clients.
type Server struct {
clientCounter uint64 // To be used atomically.
started uint32 // To be used atomically.
stopped uint32 // To be used atomically.
clients map[uint64]*Client
clientUpdates chan *clientUpdate
updates chan interface{}
quit chan struct{}
wg sync.WaitGroup
}
// clientUpdate is an internal message sent to the subscriptionHandler to
// either register a new client for subscription or cancel an existing
// subscription.
type clientUpdate struct {
// cancel indicates if the update to the client is cancelling an
// existing client's subscription. If not then this update will be to
// subscribe a new client.
cancel bool
// clientID is the unique identifier for this client. Any further
// updates (deleting or adding) to this notification client will be
// dispatched according to the target clientID.
clientID uint64
// client is the new client that will receive updates. Will be nil in
// case this is a cancellation update.
client *Client
}
// NewServer returns a new Server.
func NewServer() *Server {
return &Server{
clients: make(map[uint64]*Client),
clientUpdates: make(chan *clientUpdate),
updates: make(chan interface{}),
quit: make(chan struct{}),
}
}
// Start starts the Server, making it ready to accept subscriptions and
// updates.
func (s *Server) Start() error {
if !atomic.CompareAndSwapUint32(&s.started, 0, 1) {
return nil
}
s.wg.Add(1)
go s.subscriptionHandler()
return nil
}
// Stop stops the server.
func (s *Server) Stop() error {
if !atomic.CompareAndSwapUint32(&s.stopped, 0, 1) {
return nil
}
close(s.quit)
s.wg.Wait()
return nil
}
// Subscribe returns a Client that will receive updates any time the Server is
// made aware of a new event.
func (s *Server) Subscribe() (*Client, error) {
// We'll first atomically obtain the next ID for this client from the
// incrementing client ID counter.
clientID := atomic.AddUint64(&s.clientCounter, 1)
// Create the client that will be returned. The Cancel method is
// populated to send the cancellation intent to the
// subscriptionHandler.
client := &Client{
updates: queue.NewConcurrentQueue(20),
quit: make(chan struct{}),
cancel: func() {
select {
case s.clientUpdates <- &clientUpdate{
cancel: true,
clientID: clientID,
}:
case <-s.quit:
return
}
},
}
select {
case s.clientUpdates <- &clientUpdate{
cancel: false,
clientID: clientID,
client: client,
}:
case <-s.quit:
return nil, ErrServerShuttingDown
}
return client, nil
}
// SendUpdate is called to send the passed update to all currently active
// subscription clients.
func (s *Server) SendUpdate(update interface{}) error {
select {
case s.updates <- update:
return nil
case <-s.quit:
return ErrServerShuttingDown
}
}
// subscriptionHandler is the main handler for the Server. It will handle
// incoming updates and subscriptions, and forward the incoming updates to the
// registered clients.
//
// NOTE: MUST be run as a goroutine.
func (s *Server) subscriptionHandler() {
defer s.wg.Done()
for {
select {
// If a client update is received, the either a new
// subscription becomes active, or we cancel and existing one.
case update := <-s.clientUpdates:
clientID := update.clientID
// In case this is a cancellation, stop the client's
// underlying queue, and remove the client from the set
// of active subscription clients.
if update.cancel {
client, ok := s.clients[update.clientID]
if ok {
client.updates.Stop()
close(client.quit)
delete(s.clients, clientID)
}
continue
}
// If this was not a cancellation, start the underlying
// queue and add the client to our set of subscription
// clients. It will be notified about any new updates
// the server receives.
update.client.updates.Start()
s.clients[update.clientID] = update.client
// A new update was received, forward it to all active clients.
case upd := <-s.updates:
for _, client := range s.clients {
select {
case client.updates.ChanIn() <- upd:
case <-client.quit:
case <-s.quit:
return
}
}
// In case the server is shutting down, stop the clients and
// close the quit channels to notify them.
case <-s.quit:
for _, client := range s.clients {
client.updates.Stop()
close(client.quit)
}
return
}
}
}
package sweep
import (
"sort"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)
// UtxoAggregator defines an interface that takes a list of inputs and
// aggregate them into groups. Each group is used as the inputs to create a
// sweeping transaction.
type UtxoAggregator interface {
// ClusterInputs takes a list of inputs and groups them into input
// sets. Each input set will be used to create a sweeping transaction.
ClusterInputs(inputs InputsMap) []InputSet
}
// BudgetAggregator is a budget-based aggregator that creates clusters based on
// deadlines and budgets of inputs.
type BudgetAggregator struct {
// estimator is used when crafting sweep transactions to estimate the
// necessary fee relative to the expected size of the sweep
// transaction.
estimator chainfee.Estimator
// maxInputs specifies the maximum number of inputs allowed in a single
// sweep tx.
maxInputs uint32
// auxSweeper is an optional interface that can be used to modify the
// way sweep transaction are generated.
auxSweeper fn.Option[AuxSweeper]
}
// Compile-time constraint to ensure BudgetAggregator implements UtxoAggregator.
var _ UtxoAggregator = (*BudgetAggregator)(nil)
// NewBudgetAggregator creates a new instance of a BudgetAggregator.
func NewBudgetAggregator(estimator chainfee.Estimator,
maxInputs uint32, auxSweeper fn.Option[AuxSweeper]) *BudgetAggregator {
return &BudgetAggregator{
estimator: estimator,
maxInputs: maxInputs,
auxSweeper: auxSweeper,
}
}
// clusterGroup defines an alias for a set of inputs that are to be grouped.
type clusterGroup map[int32][]SweeperInput
// ClusterInputs creates a list of input sets from pending inputs.
// 1. filter out inputs whose budget cannot cover min relay fee.
// 2. filter a list of exclusive inputs.
// 3. group the inputs into clusters based on their deadline height.
// 4. sort the inputs in each cluster by their budget.
// 5. optionally split a cluster if it exceeds the max input limit.
// 6. create input sets from each of the clusters.
// 7. create input sets for each of the exclusive inputs.
func (b *BudgetAggregator) ClusterInputs(inputs InputsMap) []InputSet {
// Filter out inputs that have a budget below min relay fee.
filteredInputs := b.filterInputs(inputs)
// Create clusters to group inputs based on their deadline height.
clusters := make(clusterGroup, len(filteredInputs))
// exclusiveInputs is a set of inputs that are not to be included in
// any cluster. These inputs can only be swept independently as there's
// no guarantee which input will be confirmed first, which means
// grouping exclusive inputs may jeopardize non-exclusive inputs.
exclusiveInputs := make(map[wire.OutPoint]clusterGroup)
// Iterate all the inputs and group them based on their specified
// deadline heights.
for _, input := range filteredInputs {
// Get deadline height, and use the specified default deadline
// height if it's not set.
height := input.DeadlineHeight
// Put exclusive inputs in their own set.
if input.params.ExclusiveGroup != nil {
log.Tracef("Input %v is exclusive", input.OutPoint())
exclusiveInputs[input.OutPoint()] = clusterGroup{
height: []SweeperInput{*input},
}
continue
}
cluster, ok := clusters[height]
if !ok {
cluster = make([]SweeperInput, 0)
}
cluster = append(cluster, *input)
clusters[height] = cluster
}
// Now that we have the clusters, we can create the input sets.
//
// NOTE: cannot pre-allocate the slice since we don't know the number
// of input sets in advance.
inputSets := make([]InputSet, 0)
for height, cluster := range clusters {
// Sort the inputs by their economical value.
sortedInputs := b.sortInputs(cluster)
// Split on locktimes if they are different.
splitClusters := splitOnLocktime(sortedInputs)
// Create input sets from the cluster.
for _, cluster := range splitClusters {
sets := b.createInputSets(cluster, height)
inputSets = append(inputSets, sets...)
}
}
// Create input sets from the exclusive inputs.
for _, cluster := range exclusiveInputs {
for height, input := range cluster {
sets := b.createInputSets(input, height)
inputSets = append(inputSets, sets...)
}
}
return inputSets
}
// createInputSet takes a set of inputs which share the same deadline height
// and turns them into a list of `InputSet`, each set is then used to create a
// sweep transaction.
//
// TODO(yy): by the time we call this method, all the invalid/uneconomical
// inputs have been filtered out, all the inputs have been sorted based on
// their budgets, and we are about to create input sets. The only thing missing
// here is, we need to group the inputs here even further based on whether
// their budgets can cover the starting fee rate used for this input set.
func (b *BudgetAggregator) createInputSets(inputs []SweeperInput,
deadlineHeight int32) []InputSet {
// sets holds the InputSets that we will return.
sets := make([]InputSet, 0)
// Copy the inputs to a new slice so we can modify it.
remainingInputs := make([]SweeperInput, len(inputs))
copy(remainingInputs, inputs)
// If the number of inputs is greater than the max inputs allowed, we
// will split them into smaller clusters.
for uint32(len(remainingInputs)) > b.maxInputs {
log.Tracef("Cluster has %v inputs, max is %v, dividing...",
len(inputs), b.maxInputs)
// Copy the inputs to be put into the new set, and update the
// remaining inputs by removing currentInputs.
currentInputs := make([]SweeperInput, b.maxInputs)
copy(currentInputs, remainingInputs[:b.maxInputs])
remainingInputs = remainingInputs[b.maxInputs:]
// Create an InputSet using the max allowed number of inputs.
set, err := NewBudgetInputSet(
currentInputs, deadlineHeight, b.auxSweeper,
)
if err != nil {
log.Errorf("unable to create input set: %v", err)
continue
}
sets = append(sets, set)
}
// Create an InputSet from the remaining inputs.
if len(remainingInputs) > 0 {
set, err := NewBudgetInputSet(
remainingInputs, deadlineHeight, b.auxSweeper,
)
if err != nil {
log.Errorf("unable to create input set: %v", err)
return nil
}
sets = append(sets, set)
}
return sets
}
// filterInputs filters out inputs that have,
// - a budget below the min relay fee.
// - a budget below its requested starting fee.
// - a required output that's below the dust.
func (b *BudgetAggregator) filterInputs(inputs InputsMap) InputsMap {
// Get the current min relay fee for this round.
minFeeRate := b.estimator.RelayFeePerKW()
// filterInputs stores a map of inputs that has a budget that at least
// can pay the minimal fee.
filteredInputs := make(InputsMap, len(inputs))
// Iterate all the inputs and filter out the ones whose budget cannot
// cover the min fee.
for _, pi := range inputs {
op := pi.OutPoint()
// Get the size of the witness and skip if there's an error.
witnessSize, _, err := pi.WitnessType().SizeUpperBound()
if err != nil {
log.Warnf("Skipped input=%v: cannot get its size: %v",
op, err)
continue
}
//nolint:ll
// Calculate the size if the input is included in the tx.
//
// NOTE: When including this input, we need to account the
// non-witness data which is expressed in vb.
//
// TODO(yy): This is not accurate for tapscript input. We need
// to unify calculations used in the `TxWeightEstimator` inside
// `input/size.go` and `weightEstimator` in
// `weight_estimator.go`. And calculate the expected weights
// similar to BOLT-3:
// https://github.com/lightning/bolts/blob/master/03-transactions.md#appendix-a-expected-weights
wu := lntypes.VByte(input.InputSize).ToWU() + witnessSize
// Skip inputs that has too little budget.
minFee := minFeeRate.FeeForWeight(wu)
if pi.params.Budget < minFee {
log.Warnf("Skipped input=%v: has budget=%v, but the "+
"min fee requires %v (feerate=%v), size=%v", op,
pi.params.Budget, minFee,
minFeeRate.FeePerVByte(), wu.ToVB())
continue
}
// Skip inputs that has cannot cover its starting fees.
startingFeeRate := pi.params.StartingFeeRate.UnwrapOr(
chainfee.SatPerKWeight(0),
)
startingFee := startingFeeRate.FeeForWeight(wu)
if pi.params.Budget < startingFee {
log.Errorf("Skipped input=%v: has budget=%v, but the "+
"starting fee requires %v (feerate=%v), "+
"size=%v", op, pi.params.Budget, startingFee,
startingFeeRate.FeePerVByte(), wu.ToVB())
continue
}
// If the input comes with a required tx out that is below
// dust, we won't add it.
//
// NOTE: only HtlcSecondLevelAnchorInput returns non-nil
// RequiredTxOut.
reqOut := pi.RequiredTxOut()
if reqOut != nil {
if isDustOutput(reqOut) {
log.Errorf("Rejected input=%v due to dust "+
"required output=%v", op, reqOut.Value)
continue
}
}
filteredInputs[op] = pi
}
return filteredInputs
}
// sortInputs sorts the inputs based on their economical value.
//
// NOTE: besides the forced inputs, the sorting won't make any difference
// because all the inputs are added to the same set. The exception is when the
// number of inputs exceeds the maxInputs limit, it requires us to split them
// into smaller clusters. In that case, the sorting will make a difference as
// the budgets of the clusters will be different.
func (b *BudgetAggregator) sortInputs(inputs []SweeperInput) []SweeperInput {
// sortedInputs is the final list of inputs sorted by their economical
// value.
sortedInputs := make([]SweeperInput, 0, len(inputs))
// Copy the inputs.
sortedInputs = append(sortedInputs, inputs...)
// Sort the inputs based on their budgets.
//
// NOTE: We can implement more sophisticated algorithm as the budget
// left is a function f(minFeeRate, size) = b1 - s1 * r > b2 - s2 * r,
// where b1 and b2 are budgets, s1 and s2 are sizes of the inputs.
sort.Slice(sortedInputs, func(i, j int) bool {
left := sortedInputs[i].params.Budget
right := sortedInputs[j].params.Budget
// Make sure forced inputs are always put in the front.
leftForce := sortedInputs[i].params.Immediate
rightForce := sortedInputs[j].params.Immediate
// If both are forced inputs, we return the one with the higher
// budget. If neither are forced inputs, we also return the one
// with the higher budget.
if leftForce == rightForce {
return left > right
}
// Otherwise, it's either the left or the right is forced. We
// can simply return `leftForce` here as, if it's true, the
// left is forced and should be put in the front. Otherwise,
// the right is forced and should be put in the front.
return leftForce
})
return sortedInputs
}
// splitOnLocktime splits the list of inputs based on their locktime.
//
// TODO(yy): this is a temporary hack as the blocks are not synced among the
// contractcourt and the sweeper.
func splitOnLocktime(inputs []SweeperInput) map[uint32][]SweeperInput {
result := make(map[uint32][]SweeperInput)
noLocktimeInputs := make([]SweeperInput, 0, len(inputs))
// mergeLocktime is the locktime that we use to merge all the
// nolocktime inputs into.
var mergeLocktime uint32
// Iterate all inputs and split them based on their locktimes.
for _, inp := range inputs {
locktime, required := inp.RequiredLockTime()
if !required {
log.Tracef("No locktime required for input=%v",
inp.OutPoint())
noLocktimeInputs = append(noLocktimeInputs, inp)
continue
}
log.Tracef("Split input=%v on locktime=%v", inp.OutPoint(),
locktime)
// Get the slice - the slice will be initialized if not found.
inputList := result[locktime]
// Add the input to the list.
inputList = append(inputList, inp)
// Update the map.
result[locktime] = inputList
// Update the merge locktime.
mergeLocktime = locktime
}
// If there are locktime inputs, we will merge the no locktime inputs
// to the last locktime group found.
if len(result) > 0 {
log.Tracef("No locktime inputs has been merged to locktime=%v",
mergeLocktime)
result[mergeLocktime] = append(
result[mergeLocktime], noLocktimeInputs...,
)
} else {
// Otherwise just return the no locktime inputs.
result[mergeLocktime] = noLocktimeInputs
}
return result
}
// isDustOutput checks if the given output is considered as dust.
func isDustOutput(output *wire.TxOut) bool {
// Fetch the dust limit for this output.
dustLimit := lnwallet.DustLimitForSize(len(output.PkScript))
// If the output is below the dust limit, we consider it dust.
return btcutil.Amount(output.Value) < dustLimit
}
package sweep
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/chain"
"github.com/lightningnetwork/lnd/chainio"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/tlv"
)
var (
// ErrInvalidBumpResult is returned when the bump result is invalid.
ErrInvalidBumpResult = errors.New("invalid bump result")
// ErrNotEnoughBudget is returned when the fee bumper decides the
// current budget cannot cover the fee.
ErrNotEnoughBudget = errors.New("not enough budget")
// ErrLocktimeImmature is returned when sweeping an input whose
// locktime is not reached.
ErrLocktimeImmature = errors.New("immature input")
// ErrTxNoOutput is returned when an output cannot be created during tx
// preparation, usually due to the output being dust.
ErrTxNoOutput = errors.New("tx has no output")
// ErrUnknownSpent is returned when an unknown tx has spent an input in
// the sweeping tx.
ErrUnknownSpent = errors.New("unknown spend of input")
// ErrInputMissing is returned when a given input no longer exists,
// e.g., spending from an orphan tx.
ErrInputMissing = errors.New("input no longer exists")
)
var (
// dummyChangePkScript is a dummy tapscript change script that's used
// when we don't need a real address, just something that can be used
// for fee estimation.
dummyChangePkScript = []byte{
0x51, 0x20,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}
)
// Bumper defines an interface that can be used by other subsystems for fee
// bumping.
type Bumper interface {
// Broadcast is used to publish the tx created from the given inputs
// specified in the request. It handles the tx creation, broadcasts it,
// and monitors its confirmation status for potential fee bumping. It
// returns a chan that the caller can use to receive updates about the
// broadcast result and potential RBF attempts.
Broadcast(req *BumpRequest) <-chan *BumpResult
}
// BumpEvent represents the event of a fee bumping attempt.
type BumpEvent uint8
const (
// TxPublished is sent when the broadcast attempt is finished.
TxPublished BumpEvent = iota
// TxFailed is sent when the tx has encountered a fee-related error
// during its creation or broadcast, or an internal error from the fee
// bumper. In either case the inputs in this tx should be retried with
// either a different grouping strategy or an increased budget.
//
// TODO(yy): Remove the above usage once we remove sweeping non-CPFP
// anchors.
TxFailed
// TxReplaced is sent when the original tx is replaced by a new one.
TxReplaced
// TxConfirmed is sent when the tx is confirmed.
TxConfirmed
// TxUnknownSpend is sent when at least one of the inputs is spent but
// not by the current sweeping tx, this can happen when,
// - a remote party has replaced our sweeping tx by spending the
// input(s), e.g., via the direct preimage spend on our outgoing HTLC.
// - a third party has replaced our sweeping tx, e.g., the anchor output
// after 16 blocks.
// - A previous sweeping tx has confirmed but the fee bumper is not
// aware of it, e.g., a restart happens right after the sweeping tx is
// broadcast and confirmed.
TxUnknownSpend
// TxFatal is sent when the inputs in this tx cannot be retried. Txns
// will end up in this state if they have encountered a non-fee related
// error, which means they cannot be retried with increased budget.
TxFatal
// sentinalEvent is used to check if an event is unknown.
sentinalEvent
)
// String returns a human-readable string for the event.
func (e BumpEvent) String() string {
switch e {
case TxPublished:
return "Published"
case TxFailed:
return "Failed"
case TxReplaced:
return "Replaced"
case TxConfirmed:
return "Confirmed"
case TxUnknownSpend:
return "UnknownSpend"
case TxFatal:
return "Fatal"
default:
return "Unknown"
}
}
// Unknown returns true if the event is unknown.
func (e BumpEvent) Unknown() bool {
return e >= sentinalEvent
}
// BumpRequest is used by the caller to give the Bumper the necessary info to
// create and manage potential fee bumps for a set of inputs.
type BumpRequest struct {
// Budget givens the total amount that can be used as fees by these
// inputs.
Budget btcutil.Amount
// Inputs is the set of inputs to sweep.
Inputs []input.Input
// DeadlineHeight is the block height at which the tx should be
// confirmed.
DeadlineHeight int32
// DeliveryAddress is the script to send the change output to.
DeliveryAddress lnwallet.AddrWithKey
// MaxFeeRate is the maximum fee rate that can be used for fee bumping.
MaxFeeRate chainfee.SatPerKWeight
// StartingFeeRate is an optional parameter that can be used to specify
// the initial fee rate to use for the fee function.
StartingFeeRate fn.Option[chainfee.SatPerKWeight]
// ExtraTxOut tracks if this bump request has an optional set of extra
// outputs to add to the transaction.
ExtraTxOut fn.Option[SweepOutput]
// Immediate is used to specify that the tx should be broadcast
// immediately.
Immediate bool
}
// MaxFeeRateAllowed returns the maximum fee rate allowed for the given
// request. It calculates the feerate using the supplied budget and the weight,
// compares it with the specified MaxFeeRate, and returns the smaller of the
// two.
func (r *BumpRequest) MaxFeeRateAllowed() (chainfee.SatPerKWeight, error) {
// We'll want to know if we have any blobs, as we need to factor this
// into the max fee rate for this bump request.
hasBlobs := fn.Any(r.Inputs, func(i input.Input) bool {
return fn.MapOptionZ(
i.ResolutionBlob(), func(b tlv.Blob) bool {
return len(b) > 0
},
)
})
sweepAddrs := [][]byte{
r.DeliveryAddress.DeliveryAddress,
}
// If we have blobs, then we'll add an extra sweep addr for the size
// estimate below. We know that these blobs will also always be based on
// p2tr addrs.
if hasBlobs {
// We need to pass in a real address, so we'll use a dummy
// tapscript change script that's used elsewhere for tests.
sweepAddrs = append(sweepAddrs, dummyChangePkScript)
}
// Get the size of the sweep tx, which will be used to calculate the
// budget fee rate.
size, err := calcSweepTxWeight(
r.Inputs, sweepAddrs,
)
if err != nil {
return 0, err
}
// Use the budget and MaxFeeRate to decide the max allowed fee rate.
// This is needed as, when the input has a large value and the user
// sets the budget to be proportional to the input value, the fee rate
// can be very high and we need to make sure it doesn't exceed the max
// fee rate.
maxFeeRateAllowed := chainfee.NewSatPerKWeight(r.Budget, size)
if maxFeeRateAllowed > r.MaxFeeRate {
log.Debugf("Budget feerate %v exceeds MaxFeeRate %v, use "+
"MaxFeeRate instead, txWeight=%v", maxFeeRateAllowed,
r.MaxFeeRate, size)
return r.MaxFeeRate, nil
}
log.Debugf("Budget feerate %v below MaxFeeRate %v, use budget feerate "+
"instead, txWeight=%v", maxFeeRateAllowed, r.MaxFeeRate, size)
return maxFeeRateAllowed, nil
}
// calcSweepTxWeight calculates the weight of the sweep tx. It assumes a
// sweeping tx always has a single output(change).
func calcSweepTxWeight(inputs []input.Input,
outputPkScript [][]byte) (lntypes.WeightUnit, error) {
// Use a const fee rate as we only use the weight estimator to
// calculate the size.
const feeRate = 1
// Initialize the tx weight estimator with,
// - nil outputs as we only have one single change output.
// - const fee rate as we don't care about the fees here.
// - 0 maxfeerate as we don't care about fees here.
//
// TODO(yy): we should refactor the weight estimator to not require a
// fee rate and max fee rate and make it a pure tx weight calculator.
_, estimator, err := getWeightEstimate(
inputs, nil, feeRate, 0, outputPkScript,
)
if err != nil {
return 0, err
}
return estimator.weight(), nil
}
// BumpResult is used by the Bumper to send updates about the tx being
// broadcast.
type BumpResult struct {
// Event is the type of event that the result is for.
Event BumpEvent
// Tx is the tx being broadcast.
Tx *wire.MsgTx
// ReplacedTx is the old, replaced tx if a fee bump is attempted.
ReplacedTx *wire.MsgTx
// FeeRate is the fee rate used for the new tx.
FeeRate chainfee.SatPerKWeight
// Fee is the fee paid by the new tx.
Fee btcutil.Amount
// Err is the error that occurred during the broadcast.
Err error
// SpentInputs are the inputs spent by another tx which caused the
// current tx to be failed.
SpentInputs map[wire.OutPoint]*wire.MsgTx
// requestID is the ID of the request that created this record.
requestID uint64
}
// String returns a human-readable string for the result.
func (b *BumpResult) String() string {
desc := fmt.Sprintf("Event=%v", b.Event)
if b.Tx != nil {
desc += fmt.Sprintf(", Tx=%v", b.Tx.TxHash())
}
return fmt.Sprintf("[%s]", desc)
}
// Validate validates the BumpResult so it's safe to use.
func (b *BumpResult) Validate() error {
isFailureEvent := b.Event == TxFailed || b.Event == TxFatal ||
b.Event == TxUnknownSpend
// Every result must have a tx except the fatal or failed case.
if b.Tx == nil && !isFailureEvent {
return fmt.Errorf("%w: nil tx", ErrInvalidBumpResult)
}
// Every result must have a known event.
if b.Event.Unknown() {
return fmt.Errorf("%w: unknown event", ErrInvalidBumpResult)
}
// If it's a replacing event, it must have a replaced tx.
if b.Event == TxReplaced && b.ReplacedTx == nil {
return fmt.Errorf("%w: nil replacing tx", ErrInvalidBumpResult)
}
// If it's a failed or fatal event, it must have an error.
if isFailureEvent && b.Err == nil {
return fmt.Errorf("%w: nil error", ErrInvalidBumpResult)
}
// If it's a confirmed event, it must have a fee rate and fee.
if b.Event == TxConfirmed && (b.FeeRate == 0 || b.Fee == 0) {
return fmt.Errorf("%w: missing fee rate or fee",
ErrInvalidBumpResult)
}
return nil
}
// TxPublisherConfig is the config used to create a new TxPublisher.
type TxPublisherConfig struct {
// Signer is used to create the tx signature.
Signer input.Signer
// Wallet is used primarily to publish the tx.
Wallet Wallet
// Estimator is used to estimate the fee rate for the new tx based on
// its deadline conf target.
Estimator chainfee.Estimator
// Notifier is used to monitor the confirmation status of the tx.
Notifier chainntnfs.ChainNotifier
// AuxSweeper is an optional interface that can be used to modify the
// way sweep transaction are generated.
AuxSweeper fn.Option[AuxSweeper]
}
// TxPublisher is an implementation of the Bumper interface. It utilizes the
// `testmempoolaccept` RPC to bump the fee of txns it created based on
// different fee function selected or configed by the caller. Its purpose is to
// take a list of inputs specified, and create a tx that spends them to a
// specified output. It will then monitor the confirmation status of the tx,
// and if it's not confirmed within a certain time frame, it will attempt to
// bump the fee of the tx by creating a new tx that spends the same inputs to
// the same output, but with a higher fee rate. It will continue to do this
// until the tx is confirmed or the fee rate reaches the maximum fee rate
// specified by the caller.
type TxPublisher struct {
started atomic.Bool
stopped atomic.Bool
// Embed the blockbeat consumer struct to get access to the method
// `NotifyBlockProcessed` and the `BlockbeatChan`.
chainio.BeatConsumer
wg sync.WaitGroup
// cfg specifies the configuration of the TxPublisher.
cfg *TxPublisherConfig
// currentHeight is the current block height.
currentHeight atomic.Int32
// records is a map keyed by the requestCounter and the value is the tx
// being monitored.
records lnutils.SyncMap[uint64, *monitorRecord]
// requestCounter is a monotonically increasing counter used to keep
// track of how many requests have been made.
requestCounter atomic.Uint64
// subscriberChans is a map keyed by the requestCounter, each item is
// the chan that the publisher sends the fee bump result to.
subscriberChans lnutils.SyncMap[uint64, chan *BumpResult]
// quit is used to signal the publisher to stop.
quit chan struct{}
}
// Compile-time constraint to ensure TxPublisher implements Bumper.
var _ Bumper = (*TxPublisher)(nil)
// Compile-time check for the chainio.Consumer interface.
var _ chainio.Consumer = (*TxPublisher)(nil)
// NewTxPublisher creates a new TxPublisher.
func NewTxPublisher(cfg TxPublisherConfig) *TxPublisher {
tp := &TxPublisher{
cfg: &cfg,
records: lnutils.SyncMap[uint64, *monitorRecord]{},
subscriberChans: lnutils.SyncMap[uint64, chan *BumpResult]{},
quit: make(chan struct{}),
}
// Mount the block consumer.
tp.BeatConsumer = chainio.NewBeatConsumer(tp.quit, tp.Name())
return tp
}
// isNeutrinoBackend checks if the wallet backend is neutrino.
func (t *TxPublisher) isNeutrinoBackend() bool {
return t.cfg.Wallet.BackEnd() == "neutrino"
}
// Broadcast is used to publish the tx created from the given inputs. It will
// register the broadcast request and return a chan to the caller to subscribe
// the broadcast result. The initial broadcast is guaranteed to be
// RBF-compliant unless the budget specified cannot cover the fee.
//
// NOTE: part of the Bumper interface.
func (t *TxPublisher) Broadcast(req *BumpRequest) <-chan *BumpResult {
log.Tracef("Received broadcast request: %s",
lnutils.SpewLogClosure(req))
// Store the request.
record := t.storeInitialRecord(req)
// Create a chan to send the result to the caller.
subscriber := make(chan *BumpResult, 1)
t.subscriberChans.Store(record.requestID, subscriber)
// Publish the tx immediately if specified.
if req.Immediate {
t.handleInitialBroadcast(record)
}
return subscriber
}
// storeInitialRecord initializes a monitor record and saves it in the map.
func (t *TxPublisher) storeInitialRecord(req *BumpRequest) *monitorRecord {
// Increase the request counter.
//
// NOTE: this is the only place where we increase the counter.
requestID := t.requestCounter.Add(1)
// Register the record.
record := &monitorRecord{
requestID: requestID,
req: req,
}
t.records.Store(requestID, record)
return record
}
// updateRecord updates the given record's tx and fee, and saves it in the
// records map.
func (t *TxPublisher) updateRecord(r *monitorRecord,
sweepCtx *sweepTxCtx) *monitorRecord {
r.tx = sweepCtx.tx
r.fee = sweepCtx.fee
r.outpointToTxIndex = sweepCtx.outpointToTxIndex
// Register the record.
t.records.Store(r.requestID, r)
return r
}
// NOTE: part of the `chainio.Consumer` interface.
func (t *TxPublisher) Name() string {
return "TxPublisher"
}
// initializeTx initializes a fee function and creates an RBF-compliant tx. If
// succeeded, the initial tx is stored in the records map.
func (t *TxPublisher) initializeTx(r *monitorRecord) (*monitorRecord, error) {
// Create a fee bumping algorithm to be used for future RBF.
feeAlgo, err := t.initializeFeeFunction(r.req)
if err != nil {
return nil, fmt.Errorf("init fee function: %w", err)
}
// Attach the newly created fee function.
//
// TODO(yy): current we'd initialize a monitorRecord before creating the
// fee function, while we could instead create the fee function first
// then save it to the record. To make this happen we need to change the
// conf target calculation below since we would be initializing the fee
// function one block before.
r.feeFunction = feeAlgo
// Create the initial tx to be broadcasted. This tx is guaranteed to
// comply with the RBF restrictions.
record, err := t.createRBFCompliantTx(r)
if err != nil {
return nil, fmt.Errorf("create RBF-compliant tx: %w", err)
}
return record, nil
}
// initializeFeeFunction initializes a fee function to be used for this request
// for future fee bumping.
func (t *TxPublisher) initializeFeeFunction(
req *BumpRequest) (FeeFunction, error) {
// Get the max allowed feerate.
maxFeeRateAllowed, err := req.MaxFeeRateAllowed()
if err != nil {
return nil, err
}
// Get the initial conf target.
confTarget := calcCurrentConfTarget(
t.currentHeight.Load(), req.DeadlineHeight,
)
log.Debugf("Initializing fee function with conf target=%v, budget=%v, "+
"maxFeeRateAllowed=%v", confTarget, req.Budget,
maxFeeRateAllowed)
// Initialize the fee function and return it.
//
// TODO(yy): return based on differet req.Strategy?
return NewLinearFeeFunction(
maxFeeRateAllowed, confTarget, t.cfg.Estimator,
req.StartingFeeRate,
)
}
// createRBFCompliantTx creates a tx that is compliant with RBF rules. It does
// so by creating a tx, validate it using `TestMempoolAccept`, and bump its fee
// and redo the process until the tx is valid, or return an error when non-RBF
// related errors occur or the budget has been used up.
func (t *TxPublisher) createRBFCompliantTx(
r *monitorRecord) (*monitorRecord, error) {
f := r.feeFunction
for {
// Create a new tx with the given fee rate and check its
// mempool acceptance.
sweepCtx, err := t.createAndCheckTx(r)
switch {
case err == nil:
// The tx is valid, store it.
record := t.updateRecord(r, sweepCtx)
log.Infof("Created initial sweep tx=%v for %v inputs: "+
"feerate=%v, fee=%v, inputs:\n%v",
sweepCtx.tx.TxHash(), len(r.req.Inputs),
f.FeeRate(), sweepCtx.fee,
inputTypeSummary(r.req.Inputs))
return record, nil
// If the error indicates the fees paid is not enough, we will
// ask the fee function to increase the fee rate and retry.
case errors.Is(err, lnwallet.ErrMempoolFee):
// We should at least start with a feerate above the
// mempool min feerate, so if we get this error, it
// means something is wrong earlier in the pipeline.
log.Errorf("Current fee=%v, feerate=%v, %v",
sweepCtx.fee, f.FeeRate(), err)
fallthrough
// We are not paying enough fees so we increase it.
case errors.Is(err, chain.ErrInsufficientFee):
increased := false
// Keep calling the fee function until the fee rate is
// increased or maxed out.
for !increased {
log.Debugf("Increasing fee for next round, "+
"current fee=%v, feerate=%v",
sweepCtx.fee, f.FeeRate())
// If the fee function tells us that we have
// used up the budget, we will return an error
// indicating this tx cannot be made. The
// sweeper should handle this error and try to
// cluster these inputs differetly.
increased, err = f.Increment()
if err != nil {
return nil, err
}
}
// TODO(yy): suppose there's only one bad input, we can do a
// binary search to find out which input is causing this error
// by recreating a tx using half of the inputs and check its
// mempool acceptance.
default:
log.Debugf("Failed to create RBF-compliant tx: %v", err)
return nil, err
}
}
}
// createAndCheckTx creates a tx based on the given inputs, change output
// script, and the fee rate. In addition, it validates the tx's mempool
// acceptance before returning a tx that can be published directly, along with
// its fee.
func (t *TxPublisher) createAndCheckTx(r *monitorRecord) (*sweepTxCtx, error) {
req := r.req
f := r.feeFunction
// Create the sweep tx with max fee rate of 0 as the fee function
// guarantees the fee rate used here won't exceed the max fee rate.
sweepCtx, err := t.createSweepTx(
req.Inputs, req.DeliveryAddress, f.FeeRate(),
)
if err != nil {
return sweepCtx, fmt.Errorf("create sweep tx: %w", err)
}
// Sanity check the budget still covers the fee.
if sweepCtx.fee > req.Budget {
return sweepCtx, fmt.Errorf("%w: budget=%v, fee=%v",
ErrNotEnoughBudget, req.Budget, sweepCtx.fee)
}
// If we had an extra txOut, then we'll update the result to include
// it.
req.ExtraTxOut = sweepCtx.extraTxOut
// Validate the tx's mempool acceptance.
err = t.cfg.Wallet.CheckMempoolAcceptance(sweepCtx.tx)
// Exit early if the tx is valid.
if err == nil {
return sweepCtx, nil
}
// Print an error log if the chain backend doesn't support the mempool
// acceptance test RPC.
if errors.Is(err, rpcclient.ErrBackendVersion) {
log.Errorf("TestMempoolAccept not supported by backend, " +
"consider upgrading it to a newer version")
return sweepCtx, nil
}
// We are running on a backend that doesn't implement the RPC
// testmempoolaccept, eg, neutrino, so we'll skip the check.
if errors.Is(err, chain.ErrUnimplemented) {
log.Debug("Skipped testmempoolaccept due to not implemented")
return sweepCtx, nil
}
// If the inputs are spent by another tx, we will exit with the latest
// sweepCtx and an error.
if errors.Is(err, chain.ErrMissingInputs) {
log.Debugf("Tx %v missing inputs, it's likely the input has "+
"been spent by others", sweepCtx.tx.TxHash())
// Make sure to update the record with the latest attempt.
t.updateRecord(r, sweepCtx)
return sweepCtx, ErrInputMissing
}
return sweepCtx, fmt.Errorf("tx=%v failed mempool check: %w",
sweepCtx.tx.TxHash(), err)
}
// handleMissingInputs handles the case when the chain backend reports back a
// missing inputs error, which could happen when one of the input has been spent
// in another tx, or the input is referencing an orphan. When the input is
// spent, it will be handled via the TxUnknownSpend flow by creating a
// TxUnknownSpend bump result, otherwise, a TxFatal bump result is returned.
func (t *TxPublisher) handleMissingInputs(r *monitorRecord) *BumpResult {
// Get the spending txns.
spends := t.getSpentInputs(r)
// Attach the spending txns.
r.spentInputs = spends
// If there are no spending txns found and the input is missing, the
// input is referencing an orphan tx that's no longer valid, e.g., the
// spending the anchor output from the remote commitment after the local
// commitment has confirmed. In this case we will mark it as fatal and
// exit.
if len(spends) == 0 {
log.Warnf("Failing record=%v: found orphan inputs: %v\n",
r.requestID, inputTypeSummary(r.req.Inputs))
// Create a result that will be sent to the resultChan which is
// listened by the caller.
result := &BumpResult{
Event: TxFatal,
Tx: r.tx,
requestID: r.requestID,
Err: ErrInputMissing,
}
return result
}
// Check that the spending tx matches the sweeping tx - given that the
// current sweeping tx has been failed due to missing inputs, the
// spending tx must be a different tx, thus it should NOT be matched. We
// perform a sanity check here to catch the unexpected state.
if !t.isUnknownSpent(r, spends) {
log.Errorf("Sweeping tx %v has missing inputs, yet the "+
"spending tx is the sweeping tx itself: %v",
r.tx.TxHash(), r.spentInputs)
}
return t.createUnknownSpentBumpResult(r)
}
// broadcast takes a monitored tx and publishes it to the network. Prior to the
// broadcast, it will subscribe the tx's confirmation notification and attach
// the event channel to the record. Any broadcast-related errors will not be
// returned here, instead, they will be put inside the `BumpResult` and
// returned to the caller.
func (t *TxPublisher) broadcast(record *monitorRecord) (*BumpResult, error) {
txid := record.tx.TxHash()
tx := record.tx
log.Debugf("Publishing sweep tx %v, num_inputs=%v, height=%v",
txid, len(tx.TxIn), t.currentHeight.Load())
// Before we go to broadcast, we'll notify the aux sweeper, if it's
// present of this new broadcast attempt.
err := fn.MapOptionZ(t.cfg.AuxSweeper, func(aux AuxSweeper) error {
return aux.NotifyBroadcast(
record.req, tx, record.fee, record.outpointToTxIndex,
)
})
if err != nil {
return nil, fmt.Errorf("unable to notify aux sweeper: %w", err)
}
// Set the event, and change it to TxFailed if the wallet fails to
// publish it.
event := TxPublished
// Publish the sweeping tx with customized label. If the publish fails,
// this error will be saved in the `BumpResult` and it will be removed
// from being monitored.
err = t.cfg.Wallet.PublishTransaction(
tx, labels.MakeLabel(labels.LabelTypeSweepTransaction, nil),
)
if err != nil {
// NOTE: we decide to attach this error to the result instead
// of returning it here because by the time the tx reaches
// here, it should have passed the mempool acceptance check. If
// it still fails to be broadcast, it's likely a non-RBF
// related error happened. So we send this error back to the
// caller so that it can handle it properly.
//
// TODO(yy): find out which input is causing the failure.
log.Errorf("Failed to publish tx %v: %v", txid, err)
event = TxFailed
}
result := &BumpResult{
Event: event,
Tx: record.tx,
Fee: record.fee,
FeeRate: record.feeFunction.FeeRate(),
Err: err,
requestID: record.requestID,
}
return result, nil
}
// notifyResult sends the result to the resultChan specified by the requestID.
// This channel is expected to be read by the caller.
func (t *TxPublisher) notifyResult(result *BumpResult) {
id := result.requestID
subscriber, ok := t.subscriberChans.Load(id)
if !ok {
log.Errorf("Result chan for id=%v not found", id)
return
}
log.Debugf("Sending result %v for requestID=%v", result, id)
select {
// Send the result to the subscriber.
//
// TODO(yy): Add timeout in case it's blocking?
case subscriber <- result:
case <-t.quit:
log.Debug("Fee bumper stopped")
}
}
// removeResult removes the tracking of the result if the result contains a
// non-nil error, or the tx is confirmed, the record will be removed from the
// maps.
func (t *TxPublisher) removeResult(result *BumpResult) {
id := result.requestID
var txid chainhash.Hash
if result.Tx != nil {
txid = result.Tx.TxHash()
}
// Remove the record from the maps if there's an error or the tx is
// confirmed. When there's an error, it means this tx has failed its
// broadcast and cannot be retried. There are two cases it may fail,
// - when the budget cannot cover the increased fee calculated by the
// fee function, hence the budget is used up.
// - when a non-fee related error returned from PublishTransaction.
switch result.Event {
case TxFailed:
log.Errorf("Removing monitor record=%v, tx=%v, due to err: %v",
id, txid, result.Err)
case TxConfirmed:
// Remove the record if the tx is confirmed.
log.Debugf("Removing confirmed monitor record=%v, tx=%v", id,
txid)
case TxFatal:
// Remove the record if there's an error.
log.Debugf("Removing monitor record=%v due to fatal err: %v",
id, result.Err)
case TxUnknownSpend:
// Remove the record if there's an unknown spend.
log.Debugf("Removing monitor record=%v due unknown spent: "+
"%v", id, result.Err)
// Do nothing if it's neither failed or confirmed.
default:
log.Tracef("Skipping record removal for id=%v, event=%v", id,
result.Event)
return
}
t.records.Delete(id)
t.subscriberChans.Delete(id)
}
// handleResult handles the result of a tx broadcast. It will notify the
// subscriber and remove the record if the tx is confirmed or failed to be
// broadcast.
func (t *TxPublisher) handleResult(result *BumpResult) {
// Notify the subscriber.
t.notifyResult(result)
// Remove the record if it's failed or confirmed.
t.removeResult(result)
}
// monitorRecord is used to keep track of the tx being monitored by the
// publisher internally.
type monitorRecord struct {
// requestID is the ID of the request that created this record.
requestID uint64
// tx is the tx being monitored.
tx *wire.MsgTx
// req is the original request.
req *BumpRequest
// feeFunction is the fee bumping algorithm used by the publisher.
feeFunction FeeFunction
// fee is the fee paid by the tx.
fee btcutil.Amount
// outpointToTxIndex is a map of outpoint to tx index.
outpointToTxIndex map[wire.OutPoint]int
// spentInputs are the inputs spent by another tx which caused the
// current tx failed.
spentInputs map[wire.OutPoint]*wire.MsgTx
}
// Start starts the publisher by subscribing to block epoch updates and kicking
// off the monitor loop.
func (t *TxPublisher) Start(beat chainio.Blockbeat) error {
log.Info("TxPublisher starting...")
if t.started.Swap(true) {
return fmt.Errorf("TxPublisher started more than once")
}
// Set the current height.
t.currentHeight.Store(beat.Height())
t.wg.Add(1)
go t.monitor()
log.Debugf("TxPublisher started")
return nil
}
// Stop stops the publisher and waits for the monitor loop to exit.
func (t *TxPublisher) Stop() error {
log.Info("TxPublisher stopping...")
if t.stopped.Swap(true) {
return fmt.Errorf("TxPublisher stopped more than once")
}
close(t.quit)
t.wg.Wait()
log.Debug("TxPublisher stopped")
return nil
}
// monitor is the main loop driven by new blocks. Whevenr a new block arrives,
// it will examine all the txns being monitored, and check if any of them needs
// to be bumped. If so, it will attempt to bump the fee of the tx.
//
// NOTE: Must be run as a goroutine.
func (t *TxPublisher) monitor() {
defer t.wg.Done()
for {
select {
case beat := <-t.BlockbeatChan:
height := beat.Height()
log.Debugf("TxPublisher received new block: %v", height)
// Update the best known height for the publisher.
t.currentHeight.Store(height)
// Check all monitored txns to see if any of them needs
// to be bumped.
t.processRecords()
// Notify we've processed the block.
t.NotifyBlockProcessed(beat, nil)
case <-t.quit:
log.Debug("Fee bumper stopped, exit monitor")
return
}
}
}
// processRecords checks all the txns being monitored, and checks if any of
// them needs to be bumped. If so, it will attempt to bump the fee of the tx.
func (t *TxPublisher) processRecords() {
// confirmedRecords stores a map of the records which have been
// confirmed.
confirmedRecords := make(map[uint64]*monitorRecord)
// feeBumpRecords stores a map of records which need to be bumped.
feeBumpRecords := make(map[uint64]*monitorRecord)
// failedRecords stores a map of records which has inputs being spent
// by a third party.
failedRecords := make(map[uint64]*monitorRecord)
// initialRecords stores a map of records which are being created and
// published for the first time.
initialRecords := make(map[uint64]*monitorRecord)
// visitor is a helper closure that visits each record and divides them
// into two groups.
visitor := func(requestID uint64, r *monitorRecord) error {
log.Tracef("Checking monitor recordID=%v", requestID)
// Check whether the inputs have already been spent.
spends := t.getSpentInputs(r)
// If the any of the inputs has been spent, the record will be
// marked as failed or confirmed.
if len(spends) != 0 {
// Attach the spending txns.
r.spentInputs = spends
// When tx is nil, it means we haven't tried the initial
// broadcast yet the input is already spent. This could
// happen when the node shuts down, a previous sweeping
// tx confirmed, then the node comes back online and
// reoffers the inputs. Another case is the remote node
// spends the input quickly before we even attempt the
// sweep. In either case we will fail the record and let
// the sweeper handles it.
if r.tx == nil {
failedRecords[requestID] = r
return nil
}
// Check whether the inputs has been spent by a unknown
// tx.
if t.isUnknownSpent(r, spends) {
failedRecords[requestID] = r
// Move to the next record.
return nil
}
// The tx is ours, we can move it to the confirmed queue
// and stop monitoring it.
confirmedRecords[requestID] = r
// Move to the next record.
return nil
}
// This is the first time we see this record, so we put it in
// the initial queue.
if r.tx == nil {
initialRecords[requestID] = r
return nil
}
// We can only get here when the inputs are not spent and a
// previous sweeping tx has been attempted. In this case we will
// perform an RBF on it in the current block.
feeBumpRecords[requestID] = r
// Return nil to move to the next record.
return nil
}
// Iterate through all the records and divide them into four groups.
t.records.ForEach(visitor)
// Handle the initial broadcast.
for _, r := range initialRecords {
t.handleInitialBroadcast(r)
}
// For records that are confirmed, we'll notify the caller about this
// result.
for _, r := range confirmedRecords {
t.wg.Add(1)
go t.handleTxConfirmed(r)
}
// Get the current height to be used in the following goroutines.
currentHeight := t.currentHeight.Load()
// For records that are not confirmed, we perform a fee bump if needed.
for _, r := range feeBumpRecords {
t.wg.Add(1)
go t.handleFeeBumpTx(r, currentHeight)
}
// For records that are failed, we'll notify the caller about this
// result.
for _, r := range failedRecords {
t.wg.Add(1)
go t.handleUnknownSpent(r)
}
}
// handleTxConfirmed is called when a monitored tx is confirmed. It will
// notify the subscriber then remove the record from the maps .
//
// NOTE: Must be run as a goroutine to avoid blocking on sending the result.
func (t *TxPublisher) handleTxConfirmed(r *monitorRecord) {
defer t.wg.Done()
log.Debugf("Record %v is spent in tx=%v", r.requestID, r.tx.TxHash())
// Create a result that will be sent to the resultChan which is
// listened by the caller.
result := &BumpResult{
Event: TxConfirmed,
Tx: r.tx,
requestID: r.requestID,
Fee: r.fee,
FeeRate: r.feeFunction.FeeRate(),
}
// Notify that this tx is confirmed and remove the record from the map.
t.handleResult(result)
}
// handleInitialTxError takes the error from `initializeTx` and decides the
// bump event. It will construct a BumpResult and handles it.
func (t *TxPublisher) handleInitialTxError(r *monitorRecord, err error) {
// Create a bump result to be sent to the sweeper.
result := &BumpResult{
Err: err,
requestID: r.requestID,
}
// We now decide what type of event to send.
switch {
// When the error is due to a dust output, we'll send a TxFailed so
// these inputs can be retried with a different group in the next
// block.
case errors.Is(err, ErrTxNoOutput):
result.Event = TxFailed
// When the error is due to zero fee rate delta, we'll send a TxFailed
// so these inputs can be retried in the next block.
case errors.Is(err, ErrZeroFeeRateDelta):
result.Event = TxFailed
// When the error is due to budget being used up, we'll send a TxFailed
// so these inputs can be retried with a different group in the next
// block.
case errors.Is(err, ErrMaxPosition):
fallthrough
// If the tx doesn't not have enough budget, or if the inputs amounts
// are not sufficient to cover the budget, we will return a TxFailed
// event so the sweeper can handle it by re-clustering the utxos.
case errors.Is(err, ErrNotEnoughInputs),
errors.Is(err, ErrNotEnoughBudget):
result.Event = TxFailed
// Calculate the starting fee rate to be used when retry
// sweeping these inputs.
feeRate, err := t.calculateRetryFeeRate(r)
if err != nil {
result.Event = TxFatal
result.Err = err
}
// Attach the new fee rate.
result.FeeRate = feeRate
// When there are missing inputs, we'll create a TxUnknownSpend bump
// result here so the rest of the inputs can be retried.
case errors.Is(err, ErrInputMissing):
result = t.handleMissingInputs(r)
// Otherwise this is not a fee-related error and the tx cannot be
// retried. In that case we will fail ALL the inputs in this tx, which
// means they will be removed from the sweeper and never be tried
// again.
//
// TODO(yy): Find out which input is causing the failure and fail that
// one only.
default:
result.Event = TxFatal
}
t.handleResult(result)
}
// handleInitialBroadcast is called when a new request is received. It will
// handle the initial tx creation and broadcast. In details,
// 1. init a fee function based on the given strategy.
// 2. create an RBF-compliant tx and monitor it for confirmation.
// 3. notify the initial broadcast result back to the caller.
func (t *TxPublisher) handleInitialBroadcast(r *monitorRecord) {
log.Debugf("Initial broadcast for requestID=%v", r.requestID)
var (
result *BumpResult
err error
)
// Attempt an initial broadcast which is guaranteed to comply with the
// RBF rules.
//
// Create the initial tx to be broadcasted.
record, err := t.initializeTx(r)
if err != nil {
log.Errorf("Initial broadcast failed: %v", err)
// We now handle the initialization error and exit.
t.handleInitialTxError(r, err)
return
}
// Successfully created the first tx, now broadcast it.
result, err = t.broadcast(record)
if err != nil {
// The broadcast failed, which can only happen if the tx record
// cannot be found or the aux sweeper returns an error. In
// either case, we will send back a TxFail event so these
// inputs can be retried.
result = &BumpResult{
Event: TxFailed,
Err: err,
requestID: r.requestID,
}
}
t.handleResult(result)
}
// handleFeeBumpTx checks if the tx needs to be bumped, and if so, it will
// attempt to bump the fee of the tx.
//
// NOTE: Must be run as a goroutine to avoid blocking on sending the result.
func (t *TxPublisher) handleFeeBumpTx(r *monitorRecord, currentHeight int32) {
defer t.wg.Done()
log.Debugf("Attempting to fee bump tx=%v in record %v", r.tx.TxHash(),
r.requestID)
oldTxid := r.tx.TxHash()
// Get the current conf target for this record.
confTarget := calcCurrentConfTarget(currentHeight, r.req.DeadlineHeight)
// Ask the fee function whether a bump is needed. We expect the fee
// function to increase its returned fee rate after calling this
// method.
increased, err := r.feeFunction.IncreaseFeeRate(confTarget)
if err != nil {
// TODO(yy): send this error back to the sweeper so it can
// re-group the inputs?
log.Errorf("Failed to increase fee rate for tx %v at "+
"height=%v: %v", oldTxid, t.currentHeight.Load(), err)
return
}
// If the fee rate was not increased, there's no need to bump the fee.
if !increased {
log.Tracef("Skip bumping tx %v at height=%v", oldTxid,
t.currentHeight.Load())
return
}
// The fee function now has a new fee rate, we will use it to bump the
// fee of the tx.
resultOpt := t.createAndPublishTx(r)
// If there's a result, we will notify the caller about the result.
resultOpt.WhenSome(func(result BumpResult) {
// Notify the new result.
t.handleResult(&result)
})
}
// handleUnknownSpent is called when the inputs are spent by a unknown tx. It
// will notify the subscriber then remove the record from the maps and send a
// TxUnknownSpend event to the subscriber.
//
// NOTE: Must be run as a goroutine to avoid blocking on sending the result.
func (t *TxPublisher) handleUnknownSpent(r *monitorRecord) {
defer t.wg.Done()
log.Debugf("Record %v has inputs spent by a tx unknown to the fee "+
"bumper, failing it now:\n%v", r.requestID,
inputTypeSummary(r.req.Inputs))
// Create a result that will be sent to the resultChan which is listened
// by the caller.
result := t.createUnknownSpentBumpResult(r)
// Notify the sweeper about this result in the end.
t.handleResult(result)
}
// createUnknownSpentBumpResult creates and returns a BumpResult given the
// monitored record has unknown spends.
func (t *TxPublisher) createUnknownSpentBumpResult(
r *monitorRecord) *BumpResult {
// Create a result that will be sent to the resultChan which is listened
// by the caller.
result := &BumpResult{
Event: TxUnknownSpend,
Tx: r.tx,
requestID: r.requestID,
Err: ErrUnknownSpent,
SpentInputs: r.spentInputs,
}
// Calculate the next fee rate for the retry.
feeRate, err := t.calculateRetryFeeRate(r)
if err != nil {
// Overwrite the event and error so the sweeper will
// remove this input.
result.Event = TxFatal
result.Err = err
}
// Attach the new fee rate to be used for the next sweeping attempt.
result.FeeRate = feeRate
return result
}
// createAndPublishTx creates a new tx with a higher fee rate and publishes it
// to the network. It will update the record with the new tx and fee rate if
// successfully created, and return the result when published successfully.
func (t *TxPublisher) createAndPublishTx(
r *monitorRecord) fn.Option[BumpResult] {
// Fetch the old tx.
oldTx := r.tx
// Create a new tx with the new fee rate.
//
// NOTE: The fee function is expected to have increased its returned
// fee rate after calling the SkipFeeBump method. So we can use it
// directly here.
sweepCtx, err := t.createAndCheckTx(r)
// If there's an error creating the replacement tx, we need to abort the
// flow and handle it.
if err != nil {
return t.handleReplacementTxError(r, oldTx, err)
}
// The tx has been created without any errors, we now register a new
// record by overwriting the same requestID.
record := t.updateRecord(r, sweepCtx)
// Attempt to broadcast this new tx.
result, err := t.broadcast(record)
if err != nil {
log.Infof("Failed to broadcast replacement tx %v: %v",
sweepCtx.tx.TxHash(), err)
return fn.None[BumpResult]()
}
// If the result error is fee related, we will return no error and let
// the fee bumper retry it at next block.
//
// NOTE: we may get this error if we've bypassed the mempool check,
// which means we are suing neutrino backend.
if errors.Is(result.Err, chain.ErrInsufficientFee) ||
errors.Is(result.Err, lnwallet.ErrMempoolFee) {
log.Debugf("Failed to bump tx %v: %v", oldTx.TxHash(),
result.Err)
return fn.None[BumpResult]()
}
// A successful replacement tx is created, attach the old tx.
result.ReplacedTx = oldTx
// If the new tx failed to be published, we will return the result so
// the caller can handle it.
if result.Event == TxFailed {
return fn.Some(*result)
}
log.Debugf("Replaced tx=%v with new tx=%v", oldTx.TxHash(),
sweepCtx.tx.TxHash())
// Otherwise, it's a successful RBF, set the event and return.
result.Event = TxReplaced
return fn.Some(*result)
}
// isUnknownSpent checks whether the inputs of the tx has already been spent by
// a tx not known to us. When a tx is not confirmed, yet its inputs has been
// spent, then it must be spent by a different tx other than the sweeping tx
// here.
func (t *TxPublisher) isUnknownSpent(r *monitorRecord,
spends map[wire.OutPoint]*wire.MsgTx) bool {
txid := r.tx.TxHash()
// Iterate all the spending txns and check if they match the sweeping
// tx.
for op, spendingTx := range spends {
spendingTxID := spendingTx.TxHash()
// If the spending tx is the same as the sweeping tx then we are
// good.
if spendingTxID == txid {
continue
}
log.Warnf("Detected unknown spend of input=%v in tx=%v", op,
spendingTx.TxHash())
return true
}
return false
}
// getSpentInputs performs a non-blocking read on the spending subscriptions to
// see whether any of the monitored inputs has been spent. A map of inputs with
// their spending txns are returned if found.
func (t *TxPublisher) getSpentInputs(
r *monitorRecord) map[wire.OutPoint]*wire.MsgTx {
// Create a slice to record the inputs spent.
spentInputs := make(map[wire.OutPoint]*wire.MsgTx, len(r.req.Inputs))
// Iterate all the inputs and check if they have been spent already.
for _, inp := range r.req.Inputs {
op := inp.OutPoint()
// For wallet utxos, the height hint is not set - we don't need
// to monitor them for third party spend.
//
// TODO(yy): We need to properly lock wallet utxos before
// skipping this check as the same wallet utxo can be used by
// different sweeping txns.
heightHint := inp.HeightHint()
if heightHint == 0 {
heightHint = uint32(t.currentHeight.Load())
log.Debugf("Checking wallet input %v using heightHint "+
"%v", op, heightHint)
}
// If the input has already been spent after the height hint, a
// spend event is sent back immediately.
spendEvent, err := t.cfg.Notifier.RegisterSpendNtfn(
&op, inp.SignDesc().Output.PkScript, heightHint,
)
if err != nil {
log.Criticalf("Failed to register spend ntfn for "+
"input=%v: %v", op, err)
return nil
}
// Remove the subscription when exit.
defer spendEvent.Cancel()
// Do a non-blocking read to see if the output has been spent.
select {
case spend, ok := <-spendEvent.Spend:
if !ok {
log.Debugf("Spend ntfn for %v canceled", op)
continue
}
spendingTx := spend.SpendingTx
log.Debugf("Detected spent of input=%v in tx=%v", op,
spendingTx.TxHash())
spentInputs[op] = spendingTx
// Move to the next input.
default:
log.Tracef("Input %v not spent yet", op)
}
}
return spentInputs
}
// calcCurrentConfTarget calculates the current confirmation target based on
// the deadline height. The conf target is capped at 0 if the deadline has
// already been past.
func calcCurrentConfTarget(currentHeight, deadline int32) uint32 {
var confTarget uint32
// Calculate how many blocks left until the deadline.
deadlineDelta := deadline - currentHeight
// If we are already past the deadline, we will set the conf target to
// be 1.
if deadlineDelta < 0 {
log.Warnf("Deadline is %d blocks behind current height %v",
-deadlineDelta, currentHeight)
confTarget = 0
} else {
confTarget = uint32(deadlineDelta)
}
return confTarget
}
// sweepTxCtx houses a sweep transaction with additional context.
type sweepTxCtx struct {
tx *wire.MsgTx
fee btcutil.Amount
extraTxOut fn.Option[SweepOutput]
// outpointToTxIndex maps the outpoint of the inputs to their index in
// the sweep transaction.
outpointToTxIndex map[wire.OutPoint]int
}
// createSweepTx creates a sweeping tx based on the given inputs, change
// address and fee rate.
func (t *TxPublisher) createSweepTx(inputs []input.Input,
changePkScript lnwallet.AddrWithKey,
feeRate chainfee.SatPerKWeight) (*sweepTxCtx, error) {
// Validate and calculate the fee and change amount.
txFee, changeOutputsOpt, locktimeOpt, err := prepareSweepTx(
inputs, changePkScript, feeRate, t.currentHeight.Load(),
t.cfg.AuxSweeper,
)
if err != nil {
return nil, err
}
var (
// Create the sweep transaction that we will be building. We
// use version 2 as it is required for CSV.
sweepTx = wire.NewMsgTx(2)
// We'll add the inputs as we go so we know the final ordering
// of inputs to sign.
idxs []input.Input
)
// We start by adding all inputs that commit to an output. We do this
// since the input and output index must stay the same for the
// signatures to be valid.
outpointToTxIndex := make(map[wire.OutPoint]int)
for _, o := range inputs {
if o.RequiredTxOut() == nil {
continue
}
idxs = append(idxs, o)
sweepTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: o.OutPoint(),
Sequence: o.BlocksToMaturity(),
})
sweepTx.AddTxOut(o.RequiredTxOut())
outpointToTxIndex[o.OutPoint()] = len(sweepTx.TxOut) - 1
}
// Sum up the value contained in the remaining inputs, and add them to
// the sweep transaction.
for _, o := range inputs {
if o.RequiredTxOut() != nil {
continue
}
idxs = append(idxs, o)
sweepTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: o.OutPoint(),
Sequence: o.BlocksToMaturity(),
})
}
// If we have change outputs to add, then add it the sweep transaction
// here.
changeOutputsOpt.WhenSome(func(changeOuts []SweepOutput) {
for i := range changeOuts {
sweepTx.AddTxOut(&changeOuts[i].TxOut)
}
})
// We'll default to using the current block height as locktime, if none
// of the inputs commits to a different locktime.
sweepTx.LockTime = uint32(locktimeOpt.UnwrapOr(t.currentHeight.Load()))
prevInputFetcher, err := input.MultiPrevOutFetcher(inputs)
if err != nil {
return nil, fmt.Errorf("error creating prev input fetcher "+
"for hash cache: %v", err)
}
hashCache := txscript.NewTxSigHashes(sweepTx, prevInputFetcher)
// With all the inputs in place, use each output's unique input script
// function to generate the final witness required for spending.
addInputScript := func(idx int, tso input.Input) error {
inputScript, err := tso.CraftInputScript(
t.cfg.Signer, sweepTx, hashCache, prevInputFetcher, idx,
)
if err != nil {
return err
}
sweepTx.TxIn[idx].Witness = inputScript.Witness
if len(inputScript.SigScript) == 0 {
return nil
}
sweepTx.TxIn[idx].SignatureScript = inputScript.SigScript
return nil
}
for idx, inp := range idxs {
if err := addInputScript(idx, inp); err != nil {
return nil, err
}
}
log.Debugf("Created sweep tx %v for inputs:\n%v", sweepTx.TxHash(),
inputTypeSummary(inputs))
// Try to locate the extra change output, though there might be None.
extraTxOut := fn.MapOption(
func(sweepOuts []SweepOutput) fn.Option[SweepOutput] {
for _, sweepOut := range sweepOuts {
if !sweepOut.IsExtra {
continue
}
// If we sweep outputs of a custom channel, the
// custom leaves in those outputs will be merged
// into a single output, even if we sweep
// multiple outputs (e.g. to_remote and breached
// to_local of a breached channel) at the same
// time. So there will only ever be one extra
// output.
log.Debugf("Sweep produced extra_sweep_out=%v",
lnutils.SpewLogClosure(sweepOut))
return fn.Some(sweepOut)
}
return fn.None[SweepOutput]()
},
)(changeOutputsOpt)
return &sweepTxCtx{
tx: sweepTx,
fee: txFee,
extraTxOut: fn.FlattenOption(extraTxOut),
outpointToTxIndex: outpointToTxIndex,
}, nil
}
// prepareSweepTx returns the tx fee, a set of optional change outputs and an
// optional locktime after a series of validations:
// 1. check the locktime has been reached.
// 2. check the locktimes are the same.
// 3. check the inputs cover the outputs.
//
// NOTE: if the change amount is below dust, it will be added to the tx fee.
func prepareSweepTx(inputs []input.Input, changePkScript lnwallet.AddrWithKey,
feeRate chainfee.SatPerKWeight, currentHeight int32,
auxSweeper fn.Option[AuxSweeper]) (
btcutil.Amount, fn.Option[[]SweepOutput], fn.Option[int32], error) {
noChange := fn.None[[]SweepOutput]()
noLocktime := fn.None[int32]()
// Given the set of inputs we have, if we have an aux sweeper, then
// we'll attempt to see if we have any other change outputs we'll need
// to add to the sweep transaction.
changePkScripts := [][]byte{changePkScript.DeliveryAddress}
var extraChangeOut fn.Option[SweepOutput]
err := fn.MapOptionZ(
auxSweeper, func(aux AuxSweeper) error {
extraOut := aux.DeriveSweepAddr(inputs, changePkScript)
if err := extraOut.Err(); err != nil {
return err
}
extraChangeOut = extraOut.LeftToSome()
return nil
},
)
if err != nil {
return 0, noChange, noLocktime, err
}
// Creating a weight estimator with nil outputs and zero max fee rate.
// We don't allow adding customized outputs in the sweeping tx, and the
// fee rate is already being managed before we get here.
inputs, estimator, err := getWeightEstimate(
inputs, nil, feeRate, 0, changePkScripts,
)
if err != nil {
return 0, noChange, noLocktime, err
}
txFee := estimator.fee()
var (
// Track whether any of the inputs require a certain locktime.
locktime = int32(-1)
// We keep track of total input amount, and required output
// amount to use for calculating the change amount below.
totalInput btcutil.Amount
requiredOutput btcutil.Amount
)
// If we have an extra change output, then we'll add it as a required
// output amt.
extraChangeOut.WhenSome(func(o SweepOutput) {
requiredOutput += btcutil.Amount(o.Value)
})
// Go through each input and check if the required lock times have
// reached and are the same.
for _, o := range inputs {
// If the input has a required output, we'll add it to the
// required output amount.
if o.RequiredTxOut() != nil {
requiredOutput += btcutil.Amount(
o.RequiredTxOut().Value,
)
}
// Update the total input amount.
totalInput += btcutil.Amount(o.SignDesc().Output.Value)
lt, ok := o.RequiredLockTime()
// Skip if the input doesn't require a lock time.
if !ok {
continue
}
// Check if the lock time has reached
if lt > uint32(currentHeight) {
return 0, noChange, noLocktime,
fmt.Errorf("%w: current height is %v, "+
"locktime is %v", ErrLocktimeImmature,
currentHeight, lt)
}
// If another input commits to a different locktime, they
// cannot be combined in the same transaction.
if locktime != -1 && locktime != int32(lt) {
return 0, noChange, noLocktime, ErrLocktimeConflict
}
// Update the locktime for next iteration.
locktime = int32(lt)
}
// Make sure total output amount is less than total input amount.
if requiredOutput+txFee > totalInput {
log.Errorf("Insufficient input to create sweep tx: "+
"input_sum=%v, output_sum=%v", totalInput,
requiredOutput+txFee)
return 0, noChange, noLocktime, ErrNotEnoughInputs
}
// The value remaining after the required output and fees is the
// change output.
changeAmt := totalInput - requiredOutput - txFee
changeOuts := make([]SweepOutput, 0, 2)
extraChangeOut.WhenSome(func(o SweepOutput) {
changeOuts = append(changeOuts, o)
})
// We'll calculate the dust limit for the given changePkScript since it
// is variable.
changeFloor := lnwallet.DustLimitForSize(
len(changePkScript.DeliveryAddress),
)
switch {
// If the change amount is dust, we'll move it into the fees, and
// ignore it.
case changeAmt < changeFloor:
log.Infof("Change amt %v below dustlimit %v, not adding "+
"change output", changeAmt, changeFloor)
// If there's no required output, and the change output is a
// dust, it means we are creating a tx without any outputs. In
// this case we'll return an error. This could happen when
// creating a tx that has an anchor as the only input.
if requiredOutput == 0 {
return 0, noChange, noLocktime, ErrTxNoOutput
}
// The dust amount is added to the fee.
txFee += changeAmt
// Otherwise, we'll actually recognize it as a change output.
default:
changeOuts = append(changeOuts, SweepOutput{
TxOut: wire.TxOut{
Value: int64(changeAmt),
PkScript: changePkScript.DeliveryAddress,
},
IsExtra: false,
InternalKey: changePkScript.InternalKey,
})
}
// Optionally set the locktime.
locktimeOpt := fn.Some(locktime)
if locktime == -1 {
locktimeOpt = noLocktime
}
var changeOutsOpt fn.Option[[]SweepOutput]
if len(changeOuts) > 0 {
changeOutsOpt = fn.Some(changeOuts)
}
log.Debugf("Creating sweep tx for %v inputs (%s) using %v, "+
"tx_weight=%v, tx_fee=%v, locktime=%v, parents_count=%v, "+
"parents_fee=%v, parents_weight=%v, current_height=%v",
len(inputs), inputTypeSummary(inputs), feeRate,
estimator.weight(), txFee, locktimeOpt, len(estimator.parents),
estimator.parentsFee, estimator.parentsWeight, currentHeight)
return txFee, changeOutsOpt, locktimeOpt, nil
}
// handleReplacementTxError handles the error returned from creating the
// replacement tx. It returns a BumpResult that should be notified to the
// sweeper.
func (t *TxPublisher) handleReplacementTxError(r *monitorRecord,
oldTx *wire.MsgTx, err error) fn.Option[BumpResult] {
// If the error is fee related, we will return no error and let the fee
// bumper retry it at next block.
//
// NOTE: we can check the RBF error here and ask the fee function to
// recalculate the fee rate. However, this would defeat the purpose of
// using a deadline based fee function:
// - if the deadline is far away, there's no rush to RBF the tx.
// - if the deadline is close, we expect the fee function to give us a
// higher fee rate. If the fee rate cannot satisfy the RBF rules, it
// means the budget is not enough.
if errors.Is(err, chain.ErrInsufficientFee) ||
errors.Is(err, lnwallet.ErrMempoolFee) {
log.Debugf("Failed to bump tx %v: %v", oldTx.TxHash(), err)
return fn.None[BumpResult]()
}
// At least one of the inputs is missing, which means it has already
// been spent by another tx and confirmed. In this case we will handle
// it by returning a TxUnknownSpend bump result.
if errors.Is(err, ErrInputMissing) {
log.Warnf("Fail to fee bump tx %v: %v", oldTx.TxHash(), err)
bumpResult := t.handleMissingInputs(r)
return fn.Some(*bumpResult)
}
// Return a failed event to retry the sweep.
event := TxFailed
// Calculate the next fee rate for the retry.
feeRate, ferr := t.calculateRetryFeeRate(r)
if ferr != nil {
// If there's an error with the fee calculation, we need to
// abort the sweep.
event = TxFatal
}
// If the error is not fee related, we will return a `TxFailed` event so
// this input can be retried.
result := fn.Some(BumpResult{
Event: event,
Tx: oldTx,
Err: err,
requestID: r.requestID,
FeeRate: feeRate,
})
// If the tx doesn't not have enough budget, or if the inputs amounts
// are not sufficient to cover the budget, we will return a result so
// the sweeper can handle it by re-clustering the utxos.
if errors.Is(err, ErrNotEnoughBudget) ||
errors.Is(err, ErrNotEnoughInputs) {
log.Warnf("Fail to fee bump tx %v: %v", oldTx.TxHash(), err)
return result
}
// Otherwise, an unexpected error occurred, we will log an error and let
// the sweeper retry the whole process.
log.Errorf("Failed to bump tx %v: %v", oldTx.TxHash(), err)
return result
}
// calculateRetryFeeRate calculates a new fee rate to be used as the starting
// fee rate for the next sweep attempt if the inputs are to be retried. When the
// fee function is nil it will be created here, and an error is returned if the
// fee func cannot be initialized.
func (t *TxPublisher) calculateRetryFeeRate(
r *monitorRecord) (chainfee.SatPerKWeight, error) {
// Get the fee function, which will be used to decided the next fee rate
// to use if the sweeper decides to retry sweeping this input.
feeFunc := r.feeFunction
// When the record is failed before the initial broadcast is attempted,
// it will have a nil fee func. In this case, we'll create the fee func
// here.
//
// NOTE: Since the current record is failed and will be deleted, we
// don't need to update the record on this fee function. We only need
// the fee rate data so the sweeper can pick up where we left off.
if feeFunc == nil {
f, err := t.initializeFeeFunction(r.req)
// TODO(yy): The only error we would receive here is when the
// pkScript is not recognized by the weightEstimator. What we
// should do instead is to check the pkScript immediately after
// receiving a sweep request so we don't need to check it again,
// which will also save us from error checking from several
// callsites.
if err != nil {
log.Errorf("Failed to create fee func for record %v: "+
"%v", r.requestID, err)
return 0, err
}
feeFunc = f
}
// Since we failed to sweep the inputs, either the sweeping tx has been
// replaced by another party's tx, or the current output values cannot
// cover the budget, we missed this block window to increase its fee
// rate. To make sure the fee rate stays in the initial line, we now ask
// the fee function to give us the next fee rate as if the sweeping tx
// were RBFed. This new fee rate will be used as the starting fee rate
// if the upper system decides to continue sweeping the rest of the
// inputs.
_, err := feeFunc.Increment()
if err != nil {
// The fee function has reached its max position - nothing we
// can do here other than letting the user increase the budget.
log.Errorf("Failed to calculate the next fee rate for "+
"Record(%v): %v", r.requestID, err)
}
return feeFunc.FeeRate(), nil
}
package sweep
import (
"errors"
"fmt"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// ErrMaxPosition is returned when trying to increase the position of
// the fee function while it's already at its max.
ErrMaxPosition = errors.New("position already at max")
// ErrZeroFeeRateDelta is returned when the fee rate delta is zero.
ErrZeroFeeRateDelta = errors.New("fee rate delta is zero")
)
// mSatPerKWeight represents a fee rate in msat/kw.
//
// TODO(yy): unify all the units to be virtual bytes.
type mSatPerKWeight lnwire.MilliSatoshi
// String returns a human-readable string of the fee rate.
func (m mSatPerKWeight) String() string {
s := lnwire.MilliSatoshi(m)
return fmt.Sprintf("%v/kw", s)
}
// FeeFunction defines an interface that is used to calculate fee rates for
// transactions. It's expected the implementations use three params, the
// starting fee rate, the ending fee rate, and number of blocks till deadline
// block height, to build an algorithm to calculate the fee rate based on the
// current block height.
type FeeFunction interface {
// FeeRate returns the current fee rate calculated by the fee function.
FeeRate() chainfee.SatPerKWeight
// Increment increases the fee rate by one step. The definition of one
// step is up to the implementation. After calling this method, it's
// expected to change the state of the fee function such that calling
// `FeeRate` again will return the increased value.
//
// It returns a boolean to indicate whether the fee rate is increased,
// as fee bump should not be attempted if the increased fee rate is not
// greater than the current fee rate, which may happen if the algorithm
// gives the same fee rates at two positions.
//
// An error is returned when the max fee rate is reached.
//
// NOTE: we intentionally don't return the new fee rate here, so both
// the implementation and the caller are aware of the state change.
Increment() (bool, error)
// IncreaseFeeRate increases the fee rate to the new position
// calculated using (width - confTarget). It returns a boolean to
// indicate whether the fee rate is increased, and an error if the
// position is greater than the width.
//
// NOTE: this method is provided to allow the caller to increase the
// fee rate based on a conf target without taking care of the fee
// function's current state (position).
IncreaseFeeRate(confTarget uint32) (bool, error)
}
// LinearFeeFunction implements the FeeFunction interface with a linear
// function:
//
// feeRate = startingFeeRate + position * delta.
// - width: deadlineBlockHeight - startingBlockHeight
// - delta: (endingFeeRate - startingFeeRate) / width
// - position: currentBlockHeight - startingBlockHeight
//
// The fee rate will be capped at endingFeeRate.
//
// TODO(yy): implement more functions specified here:
// - https://github.com/lightningnetwork/lnd/issues/4215
type LinearFeeFunction struct {
// startingFeeRate specifies the initial fee rate to begin with.
startingFeeRate chainfee.SatPerKWeight
// endingFeeRate specifies the max allowed fee rate.
endingFeeRate chainfee.SatPerKWeight
// currentFeeRate specifies the current calculated fee rate.
currentFeeRate chainfee.SatPerKWeight
// width is the number of blocks between the starting block height
// and the deadline block height minus one.
//
// NOTE: We do minus one from the conf target here because we want to
// max out the budget before the deadline height is reached.
width uint32
// position is the fee function's current position, given a width of w,
// a valid position should lie in range [0, w].
position uint32
// deltaFeeRate is the fee rate (msat/kw) increase per block.
//
// NOTE: this is used to increase precision.
deltaFeeRate mSatPerKWeight
// estimator is the fee estimator used to estimate the fee rate. We use
// it to get the initial fee rate and, use it as a benchmark to decide
// whether we want to used the estimated fee rate or the calculated fee
// rate based on different strategies.
estimator chainfee.Estimator
}
// Compile-time check to ensure LinearFeeFunction satisfies the FeeFunction.
var _ FeeFunction = (*LinearFeeFunction)(nil)
// NewLinearFeeFunction creates a new linear fee function and initializes it
// with a starting fee rate which is an estimated value returned from the fee
// estimator using the initial conf target.
func NewLinearFeeFunction(maxFeeRate chainfee.SatPerKWeight,
confTarget uint32, estimator chainfee.Estimator,
startingFeeRate fn.Option[chainfee.SatPerKWeight]) (
*LinearFeeFunction, error) {
// If the deadline is one block away or has already been reached,
// there's nothing the fee function can do. In this case, we'll use the
// max fee rate immediately.
if confTarget <= 1 {
return &LinearFeeFunction{
startingFeeRate: maxFeeRate,
endingFeeRate: maxFeeRate,
currentFeeRate: maxFeeRate,
}, nil
}
l := &LinearFeeFunction{
endingFeeRate: maxFeeRate,
width: confTarget - 1,
estimator: estimator,
}
// If the caller specifies the starting fee rate, we'll use it instead
// of estimating it based on the deadline.
start, err := startingFeeRate.UnwrapOrFuncErr(
func() (chainfee.SatPerKWeight, error) {
// Estimate the initial fee rate.
//
// NOTE: estimateFeeRate guarantees the returned fee
// rate is capped by the ending fee rate, so we don't
// need to worry about overpay.
return l.estimateFeeRate(confTarget)
})
if err != nil {
return nil, fmt.Errorf("estimate initial fee rate: %w", err)
}
// Calculate how much fee rate should be increased per block.
end := l.endingFeeRate
// The starting and ending fee rates are in sat/kw, so we need to
// convert them to msat/kw by multiplying by 1000.
delta := btcutil.Amount(end - start).MulF64(1000 / float64(l.width))
l.deltaFeeRate = mSatPerKWeight(delta)
// We only allow the delta to be zero if the width is one - when the
// delta is zero, it means the starting and ending fee rates are the
// same, which means there's nothing to increase, so any width greater
// than 1 doesn't provide any utility. This could happen when the
// budget is too small.
if l.deltaFeeRate == 0 && l.width != 1 {
log.Errorf("Failed to init fee function: startingFeeRate=%v, "+
"endingFeeRate=%v, width=%v, delta=%v", start, end,
l.width, l.deltaFeeRate)
return nil, ErrZeroFeeRateDelta
}
// Attach the calculated values to the fee function.
l.startingFeeRate = start
l.currentFeeRate = start
log.Debugf("Linear fee function initialized with startingFeeRate=%v, "+
"endingFeeRate=%v, width=%v, delta=%v", start, end,
l.width, l.deltaFeeRate)
return l, nil
}
// FeeRate returns the current fee rate.
//
// NOTE: part of the FeeFunction interface.
func (l *LinearFeeFunction) FeeRate() chainfee.SatPerKWeight {
return l.currentFeeRate
}
// Increment increases the fee rate by one position, returns a boolean to
// indicate whether the fee rate was increased, and an error if the position is
// greater than the width. The increased fee rate will be set as the current
// fee rate, and the internal position will be incremented.
//
// NOTE: this method will change the state of the fee function as it increases
// its current fee rate.
//
// NOTE: part of the FeeFunction interface.
func (l *LinearFeeFunction) Increment() (bool, error) {
return l.increaseFeeRate(l.position + 1)
}
// IncreaseFeeRate calculate a new position using the given conf target, and
// increases the fee rate to the new position by calling the Increment method.
//
// NOTE: this method will change the state of the fee function as it increases
// its current fee rate.
//
// NOTE: part of the FeeFunction interface.
func (l *LinearFeeFunction) IncreaseFeeRate(confTarget uint32) (bool, error) {
newPosition := uint32(0)
// Only calculate the new position when the conf target is less than
// the function's width - the width is the initial conf target-1, and
// we expect the current conf target to decrease over time. However, we
// still allow the supplied conf target to be greater than the width,
// and we won't increase the fee rate in that case.
if confTarget < l.width+1 {
newPosition = l.width + 1 - confTarget
log.Tracef("Increasing position from %v to %v", l.position,
newPosition)
}
if newPosition <= l.position {
log.Tracef("Skipped increase feerate: position=%v, "+
"newPosition=%v ", l.position, newPosition)
return false, nil
}
return l.increaseFeeRate(newPosition)
}
// increaseFeeRate increases the fee rate by the specified position, returns a
// boolean to indicate whether the fee rate was increased, and an error if the
// position is greater than the width. The increased fee rate will be set as
// the current fee rate, and the internal position will be set to the specified
// position.
//
// NOTE: this method will change the state of the fee function as it increases
// its current fee rate.
func (l *LinearFeeFunction) increaseFeeRate(position uint32) (bool, error) {
// If the new position is already at the end, we return an error.
if l.position >= l.width {
return false, ErrMaxPosition
}
// Get the old fee rate.
oldFeeRate := l.currentFeeRate
// Update its internal state.
l.position = position
l.currentFeeRate = l.feeRateAtPosition(position)
log.Tracef("Fee rate increased from %v to %v at position %v",
oldFeeRate, l.currentFeeRate, l.position)
return l.currentFeeRate > oldFeeRate, nil
}
// feeRateAtPosition calculates the fee rate at a given position and caps it at
// the ending fee rate.
func (l *LinearFeeFunction) feeRateAtPosition(p uint32) chainfee.SatPerKWeight {
if p >= l.width {
return l.endingFeeRate
}
// deltaFeeRate is in msat/kw, so we need to divide by 1000 to get the
// fee rate in sat/kw.
feeRateDelta := btcutil.Amount(l.deltaFeeRate).MulF64(float64(p) / 1000)
feeRate := l.startingFeeRate + chainfee.SatPerKWeight(feeRateDelta)
if feeRate > l.endingFeeRate {
return l.endingFeeRate
}
return feeRate
}
// estimateFeeRate asks the fee estimator to estimate the fee rate based on its
// conf target.
func (l *LinearFeeFunction) estimateFeeRate(
confTarget uint32) (chainfee.SatPerKWeight, error) {
fee := FeeEstimateInfo{
ConfTarget: confTarget,
}
// If the conf target is greater or equal to the max allowed value
// (1008), we will use the min relay fee instead.
if confTarget >= chainfee.MaxBlockTarget {
minFeeRate := l.estimator.RelayFeePerKW()
log.Infof("Conf target %v is greater than max block target, "+
"using min relay fee rate %v", confTarget, minFeeRate)
return minFeeRate, nil
}
// endingFeeRate comes from budget/txWeight, which means the returned
// fee rate will always be capped by this value, hence we don't need to
// worry about overpay.
estimatedFeeRate, err := fee.Estimate(l.estimator, l.endingFeeRate)
if err != nil {
return 0, err
}
return estimatedFeeRate, nil
}
package sweep
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This means the
// package will not perform any logging by default until the caller requests
// it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("SWPR", nil))
}
// DisableLog disables all library log output. Logging output is disabled by
// default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info. This
// should be used in preference to SetLogWriter if the caller is also using
// btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package sweep
import (
"bytes"
"encoding/binary"
"errors"
"io"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/tlv"
)
var (
// txHashesBucketKey is the key that points to a bucket containing the
// hashes of all sweep txes that were published successfully.
//
// maps: txHash -> TxRecord
txHashesBucketKey = []byte("sweeper-tx-hashes")
// utxnChainPrefix is the bucket prefix for nursery buckets.
utxnChainPrefix = []byte("utxn")
// utxnHeightIndexKey is the sub bucket where the nursery stores the
// height index.
utxnHeightIndexKey = []byte("height-index")
// utxnFinalizedKndrTxnKey is a static key that can be used to locate
// the nursery finalized kindergarten sweep txn.
utxnFinalizedKndrTxnKey = []byte("finalized-kndr-txn")
byteOrder = binary.BigEndian
errNoTxHashesBucket = errors.New("tx hashes bucket does not exist")
// ErrTxNotFound is returned when querying using a txid that's not
// found in our db.
ErrTxNotFound = errors.New("tx not found")
)
// TxRecord specifies a record of a tx that's stored in the database.
type TxRecord struct {
// Txid is the sweeping tx's txid, which is used as the key to store
// the following values.
Txid chainhash.Hash
// FeeRate is the fee rate of the sweeping tx, unit is sats/kw.
FeeRate uint64
// Fee is the fee of the sweeping tx, unit is sat.
Fee uint64
// Published indicates whether the tx has been published.
Published bool
}
// toTlvStream converts TxRecord into a tlv representation.
func (t *TxRecord) toTlvStream() (*tlv.Stream, error) {
const (
// A set of tlv type definitions used to serialize TxRecord.
// We define it here instead of the head of the file to avoid
// naming conflicts.
//
// NOTE: A migration should be added whenever the existing type
// changes.
//
// NOTE: Txid is stored as the key, so it's not included here.
feeRateType tlv.Type = 0
feeType tlv.Type = 1
boolType tlv.Type = 2
)
return tlv.NewStream(
tlv.MakeBigSizeRecord(feeRateType, &t.FeeRate),
tlv.MakeBigSizeRecord(feeType, &t.Fee),
tlv.MakePrimitiveRecord(boolType, &t.Published),
)
}
// serializeTxRecord serializes a TxRecord based on tlv format.
func serializeTxRecord(w io.Writer, tx *TxRecord) error {
// Create the tlv stream.
tlvStream, err := tx.toTlvStream()
if err != nil {
return err
}
// Encode the tlv stream.
var buf bytes.Buffer
if err := tlvStream.Encode(&buf); err != nil {
return err
}
// Write the tlv stream.
if _, err = w.Write(buf.Bytes()); err != nil {
return err
}
return nil
}
// deserializeTxRecord deserializes a TxRecord based on tlv format.
func deserializeTxRecord(r io.Reader) (*TxRecord, error) {
var tx TxRecord
// Create the tlv stream.
tlvStream, err := tx.toTlvStream()
if err != nil {
return nil, err
}
if err := tlvStream.Decode(r); err != nil {
return nil, err
}
return &tx, nil
}
// SweeperStore stores published txes.
type SweeperStore interface {
// IsOurTx determines whether a tx is published by us, based on its
// hash.
IsOurTx(hash chainhash.Hash) bool
// StoreTx stores a tx hash we are about to publish.
StoreTx(*TxRecord) error
// ListSweeps lists all the sweeps we have successfully published.
ListSweeps() ([]chainhash.Hash, error)
// GetTx queries the database to find the tx that matches the given
// txid. Returns ErrTxNotFound if it cannot be found.
GetTx(hash chainhash.Hash) (*TxRecord, error)
// DeleteTx removes a tx specified by the hash from the store.
DeleteTx(hash chainhash.Hash) error
}
type sweeperStore struct {
db kvdb.Backend
}
// NewSweeperStore returns a new store instance.
func NewSweeperStore(db kvdb.Backend, chainHash *chainhash.Hash) (
SweeperStore, error) {
err := kvdb.Update(db, func(tx kvdb.RwTx) error {
if tx.ReadWriteBucket(txHashesBucketKey) != nil {
return nil
}
txHashesBucket, err := tx.CreateTopLevelBucket(
txHashesBucketKey,
)
if err != nil {
return err
}
// Use non-existence of tx hashes bucket as a signal to migrate
// nursery finalized txes.
err = migrateTxHashes(tx, txHashesBucket, chainHash)
return err
}, func() {})
if err != nil {
return nil, err
}
return &sweeperStore{
db: db,
}, nil
}
// migrateTxHashes migrates nursery finalized txes to the tx hashes bucket. This
// is not implemented as a database migration, to keep the downgrade path open.
//
// TODO(yy): delete this function once nursery is removed.
func migrateTxHashes(tx kvdb.RwTx, txHashesBucket kvdb.RwBucket,
chainHash *chainhash.Hash) error {
log.Infof("Migrating UTXO nursery finalized TXIDs")
// Compose chain bucket key.
var b bytes.Buffer
if _, err := b.Write(utxnChainPrefix); err != nil {
return err
}
if _, err := b.Write(chainHash[:]); err != nil {
return err
}
// Get chain bucket if exists.
chainBucket := tx.ReadWriteBucket(b.Bytes())
if chainBucket == nil {
return nil
}
// Retrieve the existing height index.
hghtIndex := chainBucket.NestedReadWriteBucket(utxnHeightIndexKey)
if hghtIndex == nil {
return nil
}
// Retrieve all heights.
err := hghtIndex.ForEach(func(k, v []byte) error {
heightBucket := hghtIndex.NestedReadWriteBucket(k)
if heightBucket == nil {
return nil
}
// Get finalized tx for height.
txBytes := heightBucket.Get(utxnFinalizedKndrTxnKey)
if txBytes == nil {
return nil
}
// Deserialize and skip tx if it cannot be deserialized.
tx := &wire.MsgTx{}
err := tx.Deserialize(bytes.NewReader(txBytes))
if err != nil {
log.Warnf("Cannot deserialize utxn tx")
return nil
}
// Calculate hash.
hash := tx.TxHash()
// Insert utxn tx hash in hashes bucket.
log.Debugf("Inserting nursery tx %v in hash list "+
"(height=%v)", hash, byteOrder.Uint32(k))
// Create the transaction record. Since this is an old record,
// we can assume it's already been published. Although it's
// possible to calculate the fees and fee rate used here, we
// skip it as it's unlikely we'd perform RBF on these old
// sweeping transactions.
tr := &TxRecord{
Txid: hash,
Published: true,
}
// Serialize tx record.
var b bytes.Buffer
err = serializeTxRecord(&b, tr)
if err != nil {
return err
}
return txHashesBucket.Put(tr.Txid[:], b.Bytes())
})
if err != nil {
return err
}
return nil
}
// StoreTx stores that we are about to publish a tx.
func (s *sweeperStore) StoreTx(tr *TxRecord) error {
return kvdb.Update(s.db, func(tx kvdb.RwTx) error {
txHashesBucket := tx.ReadWriteBucket(txHashesBucketKey)
if txHashesBucket == nil {
return errNoTxHashesBucket
}
// Serialize tx record.
var b bytes.Buffer
err := serializeTxRecord(&b, tr)
if err != nil {
return err
}
return txHashesBucket.Put(tr.Txid[:], b.Bytes())
}, func() {})
}
// IsOurTx determines whether a tx is published by us, based on its hash.
func (s *sweeperStore) IsOurTx(hash chainhash.Hash) bool {
var ours bool
err := kvdb.View(s.db, func(tx kvdb.RTx) error {
txHashesBucket := tx.ReadBucket(txHashesBucketKey)
// If the root bucket cannot be found, we consider the tx to be
// not found in our db.
if txHashesBucket == nil {
log.Error("Tx hashes bucket not found in sweeper store")
return nil
}
ours = txHashesBucket.Get(hash[:]) != nil
return nil
}, func() {
ours = false
})
if err != nil {
return false
}
return ours
}
// ListSweeps lists all the sweep transactions we have in the sweeper store.
func (s *sweeperStore) ListSweeps() ([]chainhash.Hash, error) {
var sweepTxns []chainhash.Hash
if err := kvdb.View(s.db, func(tx kvdb.RTx) error {
txHashesBucket := tx.ReadBucket(txHashesBucketKey)
if txHashesBucket == nil {
return errNoTxHashesBucket
}
return txHashesBucket.ForEach(func(resKey, _ []byte) error {
txid, err := chainhash.NewHash(resKey)
if err != nil {
return err
}
sweepTxns = append(sweepTxns, *txid)
return nil
})
}, func() {
sweepTxns = nil
}); err != nil {
return nil, err
}
return sweepTxns, nil
}
// GetTx queries the database to find the tx that matches the given txid.
// Returns ErrTxNotFound if it cannot be found.
func (s *sweeperStore) GetTx(txid chainhash.Hash) (*TxRecord, error) {
// Create a record.
tr := &TxRecord{}
var err error
err = kvdb.View(s.db, func(tx kvdb.RTx) error {
txHashesBucket := tx.ReadBucket(txHashesBucketKey)
if txHashesBucket == nil {
return errNoTxHashesBucket
}
txBytes := txHashesBucket.Get(txid[:])
if txBytes == nil {
return ErrTxNotFound
}
// For old records, we'd get an empty byte slice here. We can
// assume it's already been published. Although it's possible
// to calculate the fees and fee rate used here, we skip it as
// it's unlikely we'd perform RBF on these old sweeping
// transactions.
//
// TODO(yy): remove this check once migration is added.
if len(txBytes) == 0 {
tr.Published = true
return nil
}
tr, err = deserializeTxRecord(bytes.NewReader(txBytes))
if err != nil {
return err
}
return nil
}, func() {
tr = &TxRecord{}
})
if err != nil {
return nil, err
}
// Attach the txid to the record.
tr.Txid = txid
return tr, nil
}
// DeleteTx removes the given tx from db.
func (s *sweeperStore) DeleteTx(txid chainhash.Hash) error {
return kvdb.Update(s.db, func(tx kvdb.RwTx) error {
txHashesBucket := tx.ReadWriteBucket(txHashesBucketKey)
if txHashesBucket == nil {
return errNoTxHashesBucket
}
return txHashesBucket.Delete(txid[:])
}, func() {})
}
// Compile-time constraint to ensure sweeperStore implements SweeperStore.
var _ SweeperStore = (*sweeperStore)(nil)
package sweep
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainio"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)
var (
// ErrRemoteSpend is returned in case an output that we try to sweep is
// confirmed in a tx of the remote party.
ErrRemoteSpend = errors.New("remote party swept utxo")
// ErrFeePreferenceTooLow is returned when the fee preference gives a
// fee rate that's below the relay fee rate.
ErrFeePreferenceTooLow = errors.New("fee preference too low")
// ErrExclusiveGroupSpend is returned in case a different input of the
// same exclusive group was spent.
ErrExclusiveGroupSpend = errors.New("other member of exclusive group " +
"was spent")
// ErrSweeperShuttingDown is an error returned when a client attempts to
// make a request to the UtxoSweeper, but it is unable to handle it as
// it is/has already been stopped.
ErrSweeperShuttingDown = errors.New("utxo sweeper shutting down")
// DefaultDeadlineDelta defines a default deadline delta (1 week) to be
// used when sweeping inputs with no deadline pressure.
DefaultDeadlineDelta = int32(1008)
)
// Params contains the parameters that control the sweeping process.
type Params struct {
// ExclusiveGroup is an identifier that, if set, prevents other inputs
// with the same identifier from being batched together.
ExclusiveGroup *uint64
// DeadlineHeight specifies an absolute block height that this input
// should be confirmed by. This value is used by the fee bumper to
// decide its urgency and adjust its feerate used.
DeadlineHeight fn.Option[int32]
// Budget specifies the maximum amount of satoshis that can be spent on
// fees for this sweep.
Budget btcutil.Amount
// Immediate indicates that the input should be swept immediately
// without waiting for blocks to come to trigger the sweeping of
// inputs.
Immediate bool
// StartingFeeRate is an optional parameter that can be used to specify
// the initial fee rate to use for the fee function.
StartingFeeRate fn.Option[chainfee.SatPerKWeight]
}
// String returns a human readable interpretation of the sweep parameters.
func (p Params) String() string {
deadline := "none"
p.DeadlineHeight.WhenSome(func(d int32) {
deadline = fmt.Sprintf("%d", d)
})
exclusiveGroup := "none"
if p.ExclusiveGroup != nil {
exclusiveGroup = fmt.Sprintf("%d", *p.ExclusiveGroup)
}
return fmt.Sprintf("startingFeeRate=%v, immediate=%v, "+
"exclusive_group=%v, budget=%v, deadline=%v", p.StartingFeeRate,
p.Immediate, exclusiveGroup, p.Budget, deadline)
}
// SweepState represents the current state of a pending input.
//
//nolint:revive
type SweepState uint8
const (
// Init is the initial state of a pending input. This is set when a new
// sweeping request for a given input is made.
Init SweepState = iota
// PendingPublish specifies an input's state where it's already been
// included in a sweeping tx but the tx is not published yet. Inputs
// in this state should not be used for grouping again.
PendingPublish
// Published is the state where the input's sweeping tx has
// successfully been published. Inputs in this state can only be
// updated via RBF.
Published
// PublishFailed is the state when an error is returned from publishing
// the sweeping tx. Inputs in this state can be re-grouped in to a new
// sweeping tx.
PublishFailed
// Swept is the final state of a pending input. This is set when the
// input has been successfully swept.
Swept
// Excluded is the state of a pending input that has been excluded and
// can no longer be swept. For instance, when one of the three anchor
// sweeping transactions confirmed, the remaining two will be excluded.
Excluded
// Fatal is the final state of a pending input. Inputs ending in this
// state won't be retried. This could happen,
// - when a pending input has too many failed publish attempts;
// - the input has been spent by another party;
// - unknown broadcast error is returned.
Fatal
)
// String gives a human readable text for the sweep states.
func (s SweepState) String() string {
switch s {
case Init:
return "Init"
case PendingPublish:
return "PendingPublish"
case Published:
return "Published"
case PublishFailed:
return "PublishFailed"
case Swept:
return "Swept"
case Excluded:
return "Excluded"
case Fatal:
return "Fatal"
default:
return "Unknown"
}
}
// RBFInfo stores the information required to perform a RBF bump on a pending
// sweeping tx.
type RBFInfo struct {
// Txid is the txid of the sweeping tx.
Txid chainhash.Hash
// FeeRate is the fee rate of the sweeping tx.
FeeRate chainfee.SatPerKWeight
// Fee is the total fee of the sweeping tx.
Fee btcutil.Amount
}
// SweeperInput is created when an input reaches the main loop for the first
// time. It wraps the input and tracks all relevant state that is needed for
// sweeping.
type SweeperInput struct {
input.Input
// state tracks the current state of the input.
state SweepState
// listeners is a list of channels over which the final outcome of the
// sweep needs to be broadcasted.
listeners []chan Result
// ntfnRegCancel is populated with a function that cancels the chain
// notifier spend registration.
ntfnRegCancel func()
// publishAttempts records the number of attempts that have already been
// made to sweep this tx.
publishAttempts int
// params contains the parameters that control the sweeping process.
params Params
// lastFeeRate is the most recent fee rate used for this input within a
// transaction broadcast to the network.
lastFeeRate chainfee.SatPerKWeight
// rbf records the RBF constraints.
rbf fn.Option[RBFInfo]
// DeadlineHeight is the deadline height for this input. This is
// different from the DeadlineHeight in its params as it's an actual
// value than an option.
DeadlineHeight int32
}
// String returns a human readable interpretation of the pending input.
func (p *SweeperInput) String() string {
return fmt.Sprintf("%v (%v)", p.Input.OutPoint(), p.Input.WitnessType())
}
// terminated returns a boolean indicating whether the input has reached a
// final state.
func (p *SweeperInput) terminated() bool {
switch p.state {
// If the input has reached a final state, that it's either
// been swept, or failed, or excluded, we will remove it from
// our sweeper.
case Fatal, Swept, Excluded:
return true
default:
return false
}
}
// isMature returns a boolean indicating whether the input has a timelock that
// has been reached or not. The locktime found is also returned.
func (p *SweeperInput) isMature(currentHeight uint32) (bool, uint32) {
locktime, _ := p.RequiredLockTime()
if currentHeight < locktime {
log.Debugf("Input %v has locktime=%v, current height is %v",
p, locktime, currentHeight)
return false, locktime
}
// If the input has a CSV that's not yet reached, we will skip
// this input and wait for the expiry.
//
// NOTE: We need to consider whether this input can be included in the
// next block or not, which means the CSV will be checked against the
// currentHeight plus one.
locktime = p.BlocksToMaturity() + p.HeightHint()
if currentHeight+1 < locktime {
log.Debugf("Input %v has CSV expiry=%v, current height is %v, "+
"skipped sweeping", p, locktime, currentHeight)
return false, locktime
}
return true, locktime
}
// InputsMap is a type alias for a set of pending inputs.
type InputsMap = map[wire.OutPoint]*SweeperInput
// inputsMapToString returns a human readable interpretation of the pending
// inputs.
func inputsMapToString(inputs InputsMap) string {
if len(inputs) == 0 {
return ""
}
inps := make([]input.Input, 0, len(inputs))
for _, in := range inputs {
inps = append(inps, in)
}
return "\n" + inputTypeSummary(inps)
}
// pendingSweepsReq is an internal message we'll use to represent an external
// caller's intent to retrieve all of the pending inputs the UtxoSweeper is
// attempting to sweep.
type pendingSweepsReq struct {
respChan chan map[wire.OutPoint]*PendingInputResponse
errChan chan error
}
// PendingInputResponse contains information about an input that is currently
// being swept by the UtxoSweeper.
type PendingInputResponse struct {
// OutPoint is the identify outpoint of the input being swept.
OutPoint wire.OutPoint
// WitnessType is the witness type of the input being swept.
WitnessType input.WitnessType
// Amount is the amount of the input being swept.
Amount btcutil.Amount
// LastFeeRate is the most recent fee rate used for the input being
// swept within a transaction broadcast to the network.
LastFeeRate chainfee.SatPerKWeight
// BroadcastAttempts is the number of attempts we've made to sweept the
// input.
BroadcastAttempts int
// Params contains the sweep parameters for this pending request.
Params Params
// DeadlineHeight records the deadline height of this input.
DeadlineHeight uint32
}
// updateReq is an internal message we'll use to represent an external caller's
// intent to update the sweep parameters of a given input.
type updateReq struct {
input wire.OutPoint
params Params
responseChan chan *updateResp
}
// updateResp is an internal message we'll use to hand off the response of a
// updateReq from the UtxoSweeper's main event loop back to the caller.
type updateResp struct {
resultChan chan Result
err error
}
// UtxoSweeper is responsible for sweeping outputs back into the wallet
type UtxoSweeper struct {
started uint32 // To be used atomically.
stopped uint32 // To be used atomically.
// Embed the blockbeat consumer struct to get access to the method
// `NotifyBlockProcessed` and the `BlockbeatChan`.
chainio.BeatConsumer
cfg *UtxoSweeperConfig
newInputs chan *sweepInputMessage
spendChan chan *chainntnfs.SpendDetail
// pendingSweepsReq is a channel that will be sent requests by external
// callers in order to retrieve the set of pending inputs the
// UtxoSweeper is attempting to sweep.
pendingSweepsReqs chan *pendingSweepsReq
// updateReqs is a channel that will be sent requests by external
// callers who wish to bump the fee rate of a given input.
updateReqs chan *updateReq
// inputs is the total set of inputs the UtxoSweeper has been requested
// to sweep.
inputs InputsMap
currentOutputScript fn.Option[lnwallet.AddrWithKey]
relayFeeRate chainfee.SatPerKWeight
quit chan struct{}
wg sync.WaitGroup
// currentHeight is the best known height of the main chain. This is
// updated whenever a new block epoch is received.
currentHeight int32
// bumpRespChan is a channel that receives broadcast results from the
// TxPublisher.
bumpRespChan chan *bumpResp
}
// Compile-time check for the chainio.Consumer interface.
var _ chainio.Consumer = (*UtxoSweeper)(nil)
// UtxoSweeperConfig contains dependencies of UtxoSweeper.
type UtxoSweeperConfig struct {
// GenSweepScript generates a P2WKH script belonging to the wallet where
// funds can be swept.
GenSweepScript func() fn.Result[lnwallet.AddrWithKey]
// FeeEstimator is used when crafting sweep transactions to estimate
// the necessary fee relative to the expected size of the sweep
// transaction.
FeeEstimator chainfee.Estimator
// Wallet contains the wallet functions that sweeper requires.
Wallet Wallet
// Notifier is an instance of a chain notifier we'll use to watch for
// certain on-chain events.
Notifier chainntnfs.ChainNotifier
// Mempool is the mempool watcher that will be used to query whether a
// given input is already being spent by a transaction in the mempool.
Mempool chainntnfs.MempoolWatcher
// Store stores the published sweeper txes.
Store SweeperStore
// Signer is used by the sweeper to generate valid witnesses at the
// time the incubated outputs need to be spent.
Signer input.Signer
// MaxInputsPerTx specifies the default maximum number of inputs allowed
// in a single sweep tx. If more need to be swept, multiple txes are
// created and published.
MaxInputsPerTx uint32
// MaxFeeRate is the maximum fee rate allowed within the UtxoSweeper.
MaxFeeRate chainfee.SatPerVByte
// Aggregator is used to group inputs into clusters based on its
// implemention-specific strategy.
Aggregator UtxoAggregator
// Publisher is used to publish the sweep tx crafted here and monitors
// it for potential fee bumps.
Publisher Bumper
// NoDeadlineConfTarget is the conf target to use when sweeping
// non-time-sensitive outputs.
NoDeadlineConfTarget uint32
}
// Result is the struct that is pushed through the result channel. Callers can
// use this to be informed of the final sweep result. In case of a remote
// spend, Err will be ErrRemoteSpend.
type Result struct {
// Err is the final result of the sweep. It is nil when the input is
// swept successfully by us. ErrRemoteSpend is returned when another
// party took the input.
Err error
// Tx is the transaction that spent the input.
Tx *wire.MsgTx
}
// sweepInputMessage structs are used in the internal channel between the
// SweepInput call and the sweeper main loop.
type sweepInputMessage struct {
input input.Input
params Params
resultChan chan Result
}
// New returns a new Sweeper instance.
func New(cfg *UtxoSweeperConfig) *UtxoSweeper {
s := &UtxoSweeper{
cfg: cfg,
newInputs: make(chan *sweepInputMessage),
spendChan: make(chan *chainntnfs.SpendDetail),
updateReqs: make(chan *updateReq),
pendingSweepsReqs: make(chan *pendingSweepsReq),
quit: make(chan struct{}),
inputs: make(InputsMap),
bumpRespChan: make(chan *bumpResp, 100),
}
// Mount the block consumer.
s.BeatConsumer = chainio.NewBeatConsumer(s.quit, s.Name())
return s
}
// Start starts the process of constructing and publish sweep txes.
func (s *UtxoSweeper) Start(beat chainio.Blockbeat) error {
if !atomic.CompareAndSwapUint32(&s.started, 0, 1) {
return nil
}
log.Info("Sweeper starting")
// Retrieve relay fee for dust limit calculation. Assume that this will
// not change from here on.
s.relayFeeRate = s.cfg.FeeEstimator.RelayFeePerKW()
// Set the current height.
s.currentHeight = beat.Height()
// Start sweeper main loop.
s.wg.Add(1)
go s.collector()
return nil
}
// RelayFeePerKW returns the minimum fee rate required for transactions to be
// relayed.
func (s *UtxoSweeper) RelayFeePerKW() chainfee.SatPerKWeight {
return s.relayFeeRate
}
// Stop stops sweeper from listening to block epochs and constructing sweep
// txes.
func (s *UtxoSweeper) Stop() error {
if !atomic.CompareAndSwapUint32(&s.stopped, 0, 1) {
return nil
}
log.Info("Sweeper shutting down...")
defer log.Debug("Sweeper shutdown complete")
close(s.quit)
s.wg.Wait()
return nil
}
// NOTE: part of the `chainio.Consumer` interface.
func (s *UtxoSweeper) Name() string {
return "UtxoSweeper"
}
// SweepInput sweeps inputs back into the wallet. The inputs will be batched and
// swept after the batch time window ends. A custom fee preference can be
// provided to determine what fee rate should be used for the input. Note that
// the input may not always be swept with this exact value, as its possible for
// it to be batched under the same transaction with other similar fee rate
// inputs.
//
// NOTE: Extreme care needs to be taken that input isn't changed externally.
// Because it is an interface and we don't know what is exactly behind it, we
// cannot make a local copy in sweeper.
//
// TODO(yy): make sure the caller is using the Result chan.
func (s *UtxoSweeper) SweepInput(inp input.Input,
params Params) (chan Result, error) {
if inp == nil || inp.OutPoint() == input.EmptyOutPoint ||
inp.SignDesc() == nil {
return nil, errors.New("nil input received")
}
absoluteTimeLock, _ := inp.RequiredLockTime()
log.Debugf("Sweep request received: out_point=%v, witness_type=%v, "+
"relative_time_lock=%v, absolute_time_lock=%v, amount=%v, "+
"parent=(%v), params=(%v)", inp.OutPoint(), inp.WitnessType(),
inp.BlocksToMaturity(), absoluteTimeLock,
btcutil.Amount(inp.SignDesc().Output.Value),
inp.UnconfParent(), params)
sweeperInput := &sweepInputMessage{
input: inp,
params: params,
resultChan: make(chan Result, 1),
}
// Deliver input to the main event loop.
select {
case s.newInputs <- sweeperInput:
case <-s.quit:
return nil, ErrSweeperShuttingDown
}
return sweeperInput.resultChan, nil
}
// removeConflictSweepDescendants removes any transactions from the wallet that
// spend outputs included in the passed outpoint set. This needs to be done in
// cases where we're not the only ones that can sweep an output, but there may
// exist unconfirmed spends that spend outputs created by a sweep transaction.
// The most common case for this is when someone sweeps our anchor outputs
// after 16 blocks. Moreover this is also needed for wallets which use neutrino
// as a backend when a channel is force closed and anchor cpfp txns are
// created to bump the initial commitment transaction. In this case an anchor
// cpfp is broadcasted for up to 3 commitment transactions (local,
// remote-dangling, remote). Using neutrino all of those transactions will be
// accepted (the commitment tx will be different in all of those cases) and have
// to be removed as soon as one of them confirmes (they do have the same
// ExclusiveGroup). For neutrino backends the corresponding BIP 157 serving full
// nodes do not signal invalid transactions anymore.
func (s *UtxoSweeper) removeConflictSweepDescendants(
outpoints map[wire.OutPoint]struct{}) error {
// Obtain all the past sweeps that we've done so far. We'll need these
// to ensure that if the spendingTx spends any of the same inputs, then
// we remove any transaction that may be spending those inputs from the
// wallet.
//
// TODO(roasbeef): can be last sweep here if we remove anything confirmed
// from the store?
pastSweepHashes, err := s.cfg.Store.ListSweeps()
if err != nil {
return err
}
// We'll now go through each past transaction we published during this
// epoch and cross reference the spent inputs. If there're any inputs
// in common with the inputs the spendingTx spent, then we'll remove
// those.
//
// TODO(roasbeef): need to start to remove all transaction hashes after
// every N blocks (assumed point of no return)
for _, sweepHash := range pastSweepHashes {
sweepTx, err := s.cfg.Wallet.FetchTx(sweepHash)
if err != nil {
return err
}
// Transaction wasn't found in the wallet, may have already
// been replaced/removed.
if sweepTx == nil {
// If it was removed, then we'll play it safe and mark
// it as no longer need to be rebroadcasted.
s.cfg.Wallet.CancelRebroadcast(sweepHash)
continue
}
// Check to see if this past sweep transaction spent any of the
// same inputs as spendingTx.
var isConflicting bool
for _, txIn := range sweepTx.TxIn {
if _, ok := outpoints[txIn.PreviousOutPoint]; ok {
isConflicting = true
break
}
}
if !isConflicting {
continue
}
// If it is conflicting, then we'll signal the wallet to remove
// all the transactions that are descendants of outputs created
// by the sweepTx and the sweepTx itself.
log.Debugf("Removing sweep txid=%v from wallet: %v",
sweepTx.TxHash(), spew.Sdump(sweepTx))
err = s.cfg.Wallet.RemoveDescendants(sweepTx)
if err != nil {
log.Warnf("Unable to remove descendants: %v", err)
}
// If this transaction was conflicting, then we'll stop
// rebroadcasting it in the background.
s.cfg.Wallet.CancelRebroadcast(sweepHash)
}
return nil
}
// collector is the sweeper main loop. It processes new inputs, spend
// notifications and counts down to publication of the sweep tx.
func (s *UtxoSweeper) collector() {
defer s.wg.Done()
for {
// Clean inputs, which will remove inputs that are swept,
// failed, or excluded from the sweeper and return inputs that
// are either new or has been published but failed back, which
// will be retried again here.
s.updateSweeperInputs()
select {
// A new inputs is offered to the sweeper. We check to see if
// we are already trying to sweep this input and if not, set up
// a listener to spend and schedule a sweep.
case input := <-s.newInputs:
err := s.handleNewInput(input)
if err != nil {
log.Criticalf("Unable to handle new input: %v",
err)
return
}
// If this input is forced, we perform an sweep
// immediately.
//
// TODO(ziggie): Make sure when `immediate` is selected
// as a parameter that we only trigger the sweeping of
// this specific input rather than triggering the sweeps
// of all current pending inputs registered with the
// sweeper.
if input.params.Immediate {
inputs := s.updateSweeperInputs()
s.sweepPendingInputs(inputs)
}
// A spend of one of our inputs is detected. Signal sweep
// results to the caller(s).
case spend := <-s.spendChan:
s.handleInputSpent(spend)
// A new external request has been received to retrieve all of
// the inputs we're currently attempting to sweep.
case req := <-s.pendingSweepsReqs:
s.handlePendingSweepsReq(req)
// A new external request has been received to bump the fee rate
// of a given input.
case req := <-s.updateReqs:
resultChan, err := s.handleUpdateReq(req)
req.responseChan <- &updateResp{
resultChan: resultChan,
err: err,
}
// Perform an sweep immediately if asked.
if req.params.Immediate {
inputs := s.updateSweeperInputs()
s.sweepPendingInputs(inputs)
}
case resp := <-s.bumpRespChan:
// Handle the bump event.
err := s.handleBumpEvent(resp)
if err != nil {
log.Errorf("Failed to handle bump event: %v",
err)
}
// A new block comes in, update the bestHeight, perform a check
// over all pending inputs and publish sweeping txns if needed.
case beat := <-s.BlockbeatChan:
// Update the sweeper to the best height.
s.currentHeight = beat.Height()
// Update the inputs with the latest height.
inputs := s.updateSweeperInputs()
log.Debugf("Received new block: height=%v, attempt "+
"sweeping %d inputs:%s", s.currentHeight,
len(inputs),
lnutils.NewLogClosure(func() string {
return inputsMapToString(inputs)
}))
// Attempt to sweep any pending inputs.
s.sweepPendingInputs(inputs)
// Notify we've processed the block.
s.NotifyBlockProcessed(beat, nil)
case <-s.quit:
return
}
}
}
// removeExclusiveGroup removes all inputs in the given exclusive group. This
// function is called when one of the exclusive group inputs has been spent. The
// other inputs won't ever be spendable and can be removed. This also prevents
// them from being part of future sweep transactions that would fail. In
// addition sweep transactions of those inputs will be removed from the wallet.
func (s *UtxoSweeper) removeExclusiveGroup(group uint64) {
for outpoint, input := range s.inputs {
outpoint := outpoint
// Skip inputs that aren't exclusive.
if input.params.ExclusiveGroup == nil {
continue
}
// Skip inputs from other exclusive groups.
if *input.params.ExclusiveGroup != group {
continue
}
// Skip inputs that are already terminated.
if input.terminated() {
log.Tracef("Skipped sending error result for "+
"input %v, state=%v", outpoint, input.state)
continue
}
// Signal result channels.
s.signalResult(input, Result{
Err: ErrExclusiveGroupSpend,
})
// Update the input's state as it can no longer be swept.
input.state = Excluded
// Remove all unconfirmed transactions from the wallet which
// spend the passed outpoint of the same exclusive group.
outpoints := map[wire.OutPoint]struct{}{
outpoint: {},
}
err := s.removeConflictSweepDescendants(outpoints)
if err != nil {
log.Warnf("Unable to remove conflicting sweep tx from "+
"wallet for outpoint %v : %v", outpoint, err)
}
}
}
// signalResult notifies the listeners of the final result of the input sweep.
// It also cancels any pending spend notification.
func (s *UtxoSweeper) signalResult(pi *SweeperInput, result Result) {
op := pi.OutPoint()
listeners := pi.listeners
if result.Err == nil {
log.Tracef("Dispatching sweep success for %v to %v listeners",
op, len(listeners),
)
} else {
log.Tracef("Dispatching sweep error for %v to %v listeners: %v",
op, len(listeners), result.Err,
)
}
// Signal all listeners. Channel is buffered. Because we only send once
// on every channel, it should never block.
for _, resultChan := range listeners {
resultChan <- result
}
// Cancel spend notification with chain notifier. This is not necessary
// in case of a success, except for that a reorg could still happen.
if pi.ntfnRegCancel != nil {
log.Debugf("Canceling spend ntfn for %v", op)
pi.ntfnRegCancel()
}
}
// sweep takes a set of preselected inputs, creates a sweep tx and publishes
// the tx. The output address is only marked as used if the publish succeeds.
func (s *UtxoSweeper) sweep(set InputSet) error {
// Generate an output script if there isn't an unused script available.
if s.currentOutputScript.IsNone() {
addr, err := s.cfg.GenSweepScript().Unpack()
if err != nil {
return fmt.Errorf("gen sweep script: %w", err)
}
s.currentOutputScript = fn.Some(addr)
log.Debugf("Created sweep DeliveryAddress %x",
addr.DeliveryAddress)
}
sweepAddr, err := s.currentOutputScript.UnwrapOrErr(
fmt.Errorf("none sweep script"),
)
if err != nil {
return err
}
// Create a fee bump request and ask the publisher to broadcast it. The
// publisher will then take over and start monitoring the tx for
// potential fee bump.
req := &BumpRequest{
Inputs: set.Inputs(),
Budget: set.Budget(),
DeadlineHeight: set.DeadlineHeight(),
DeliveryAddress: sweepAddr,
MaxFeeRate: s.cfg.MaxFeeRate.FeePerKWeight(),
StartingFeeRate: set.StartingFeeRate(),
Immediate: set.Immediate(),
// TODO(yy): pass the strategy here.
}
// Reschedule the inputs that we just tried to sweep. This is done in
// case the following publish fails, we'd like to update the inputs'
// publish attempts and rescue them in the next sweep.
s.markInputsPendingPublish(set)
// Broadcast will return a read-only chan that we will listen to for
// this publish result and future RBF attempt.
resp := s.cfg.Publisher.Broadcast(req)
// Successfully sent the broadcast attempt, we now handle the result by
// subscribing to the result chan and listen for future updates about
// this tx.
s.wg.Add(1)
go s.monitorFeeBumpResult(set, resp)
return nil
}
// markInputsPendingPublish updates the pending inputs with the given tx
// inputs. It also increments the `publishAttempts`.
func (s *UtxoSweeper) markInputsPendingPublish(set InputSet) {
// Reschedule sweep.
for _, input := range set.Inputs() {
op := input.OutPoint()
pi, ok := s.inputs[op]
if !ok {
// It could be that this input is an additional wallet
// input that was attached. In that case there also
// isn't a pending input to update.
log.Tracef("Skipped marking input as pending "+
"published: %v not found in pending inputs", op)
continue
}
// If this input has already terminated, there's clearly
// something wrong as it would have been removed. In this case
// we log an error and skip marking this input as pending
// publish.
if pi.terminated() {
log.Errorf("Expect input %v to not have terminated "+
"state, instead it has %v", op, pi.state)
continue
}
// Update the input's state.
pi.state = PendingPublish
// Record another publish attempt.
pi.publishAttempts++
}
}
// markInputsPublished updates the sweeping tx in db and marks the list of
// inputs as published.
func (s *UtxoSweeper) markInputsPublished(tr *TxRecord, set InputSet) error {
// Mark this tx in db once successfully published.
//
// NOTE: this will behave as an overwrite, which is fine as the record
// is small.
tr.Published = true
err := s.cfg.Store.StoreTx(tr)
if err != nil {
return fmt.Errorf("store tx: %w", err)
}
// Reschedule sweep.
for _, input := range set.Inputs() {
op := input.OutPoint()
pi, ok := s.inputs[op]
if !ok {
// It could be that this input is an additional wallet
// input that was attached. In that case there also
// isn't a pending input to update.
log.Tracef("Skipped marking input as published: %v "+
"not found in pending inputs", op)
continue
}
// Valdiate that the input is in an expected state.
if pi.state != PendingPublish {
// We may get a Published if this is a replacement tx.
log.Debugf("Expect input %v to have %v, instead it "+
"has %v", op, PendingPublish, pi.state)
continue
}
// Update the input's state.
pi.state = Published
// Update the input's latest fee rate.
pi.lastFeeRate = chainfee.SatPerKWeight(tr.FeeRate)
}
return nil
}
// markInputsPublishFailed marks the list of inputs as failed to be published.
func (s *UtxoSweeper) markInputsPublishFailed(set InputSet,
feeRate chainfee.SatPerKWeight) {
// Reschedule sweep.
for _, inp := range set.Inputs() {
op := inp.OutPoint()
pi, ok := s.inputs[op]
if !ok {
// It could be that this input is an additional wallet
// input that was attached. In that case there also
// isn't a pending input to update.
log.Tracef("Skipped marking input as publish failed: "+
"%v not found in pending inputs", op)
continue
}
// Valdiate that the input is in an expected state.
if pi.state != PendingPublish && pi.state != Published {
log.Debugf("Expect input %v to have %v, instead it "+
"has %v", op, PendingPublish, pi.state)
continue
}
log.Warnf("Failed to publish input %v", op)
// Update the input's state.
pi.state = PublishFailed
log.Debugf("Input(%v): updating params: starting fee rate "+
"[%v -> %v]", op, pi.params.StartingFeeRate,
feeRate)
// Update the input using the fee rate specified from the
// BumpResult, which should be the starting fee rate to use for
// the next sweeping attempt.
pi.params.StartingFeeRate = fn.Some(feeRate)
}
}
// monitorSpend registers a spend notification with the chain notifier. It
// returns a cancel function that can be used to cancel the registration.
func (s *UtxoSweeper) monitorSpend(outpoint wire.OutPoint,
script []byte, heightHint uint32) (func(), error) {
log.Tracef("Wait for spend of %v at heightHint=%v",
outpoint, heightHint)
spendEvent, err := s.cfg.Notifier.RegisterSpendNtfn(
&outpoint, script, heightHint,
)
if err != nil {
return nil, fmt.Errorf("register spend ntfn: %w", err)
}
s.wg.Add(1)
go func() {
defer s.wg.Done()
select {
case spend, ok := <-spendEvent.Spend:
if !ok {
log.Debugf("Spend ntfn for %v canceled",
outpoint)
return
}
log.Debugf("Delivering spend ntfn for %v", outpoint)
select {
case s.spendChan <- spend:
log.Debugf("Delivered spend ntfn for %v",
outpoint)
case <-s.quit:
}
case <-s.quit:
}
}()
return spendEvent.Cancel, nil
}
// PendingInputs returns the set of inputs that the UtxoSweeper is currently
// attempting to sweep.
func (s *UtxoSweeper) PendingInputs() (
map[wire.OutPoint]*PendingInputResponse, error) {
respChan := make(chan map[wire.OutPoint]*PendingInputResponse, 1)
errChan := make(chan error, 1)
select {
case s.pendingSweepsReqs <- &pendingSweepsReq{
respChan: respChan,
errChan: errChan,
}:
case <-s.quit:
return nil, ErrSweeperShuttingDown
}
select {
case pendingSweeps := <-respChan:
return pendingSweeps, nil
case err := <-errChan:
return nil, err
case <-s.quit:
return nil, ErrSweeperShuttingDown
}
}
// handlePendingSweepsReq handles a request to retrieve all pending inputs the
// UtxoSweeper is attempting to sweep.
func (s *UtxoSweeper) handlePendingSweepsReq(
req *pendingSweepsReq) map[wire.OutPoint]*PendingInputResponse {
resps := make(map[wire.OutPoint]*PendingInputResponse, len(s.inputs))
for _, inp := range s.inputs {
// Skip immature inputs for compatibility.
mature, _ := inp.isMature(uint32(s.currentHeight))
if !mature {
continue
}
// Only the exported fields are set, as we expect the response
// to only be consumed externally.
op := inp.OutPoint()
resps[op] = &PendingInputResponse{
OutPoint: op,
WitnessType: inp.WitnessType(),
Amount: btcutil.Amount(
inp.SignDesc().Output.Value,
),
LastFeeRate: inp.lastFeeRate,
BroadcastAttempts: inp.publishAttempts,
Params: inp.params,
DeadlineHeight: uint32(inp.DeadlineHeight),
}
}
select {
case req.respChan <- resps:
case <-s.quit:
log.Debug("Skipped sending pending sweep response due to " +
"UtxoSweeper shutting down")
}
return resps
}
// UpdateParams allows updating the sweep parameters of a pending input in the
// UtxoSweeper. This function can be used to provide an updated fee preference
// and force flag that will be used for a new sweep transaction of the input
// that will act as a replacement transaction (RBF) of the original sweeping
// transaction, if any. The exclusive group is left unchanged.
//
// NOTE: This currently doesn't do any fee rate validation to ensure that a bump
// is actually successful. The responsibility of doing so should be handled by
// the caller.
func (s *UtxoSweeper) UpdateParams(input wire.OutPoint,
params Params) (chan Result, error) {
responseChan := make(chan *updateResp, 1)
select {
case s.updateReqs <- &updateReq{
input: input,
params: params,
responseChan: responseChan,
}:
case <-s.quit:
return nil, ErrSweeperShuttingDown
}
select {
case response := <-responseChan:
return response.resultChan, response.err
case <-s.quit:
return nil, ErrSweeperShuttingDown
}
}
// handleUpdateReq handles an update request by simply updating the sweep
// parameters of the pending input. Currently, no validation is done on the new
// fee preference to ensure it will properly create a replacement transaction.
//
// TODO(wilmer):
// - Validate fee preference to ensure we'll create a valid replacement
// transaction to allow the new fee rate to propagate throughout the
// network.
// - Ensure we don't combine this input with any other unconfirmed inputs that
// did not exist in the original sweep transaction, resulting in an invalid
// replacement transaction.
func (s *UtxoSweeper) handleUpdateReq(req *updateReq) (
chan Result, error) {
// If the UtxoSweeper is already trying to sweep this input, then we can
// simply just increase its fee rate. This will allow the input to be
// batched with others which also have a similar fee rate, creating a
// higher fee rate transaction that replaces the original input's
// sweeping transaction.
sweeperInput, ok := s.inputs[req.input]
if !ok {
return nil, lnwallet.ErrNotMine
}
// Create the updated parameters struct. Leave the exclusive group
// unchanged.
newParams := Params{
StartingFeeRate: req.params.StartingFeeRate,
Immediate: req.params.Immediate,
Budget: req.params.Budget,
DeadlineHeight: req.params.DeadlineHeight,
ExclusiveGroup: sweeperInput.params.ExclusiveGroup,
}
log.Debugf("Updating parameters for %v(state=%v) from (%v) to (%v)",
req.input, sweeperInput.state, sweeperInput.params, newParams)
sweeperInput.params = newParams
// We need to reset the state so this input will be attempted again by
// our sweeper.
//
// TODO(yy): a dedicated state?
sweeperInput.state = Init
// If the new input specifies a deadline, update the deadline height.
sweeperInput.DeadlineHeight = req.params.DeadlineHeight.UnwrapOr(
sweeperInput.DeadlineHeight,
)
resultChan := make(chan Result, 1)
sweeperInput.listeners = append(sweeperInput.listeners, resultChan)
return resultChan, nil
}
// ListSweeps returns a list of the sweeps recorded by the sweep store.
func (s *UtxoSweeper) ListSweeps() ([]chainhash.Hash, error) {
return s.cfg.Store.ListSweeps()
}
// mempoolLookup takes an input's outpoint and queries the mempool to see
// whether it's already been spent in a transaction found in the mempool.
// Returns the transaction if found.
func (s *UtxoSweeper) mempoolLookup(op wire.OutPoint) fn.Option[wire.MsgTx] {
// For neutrino backend, there's no mempool available, so we exit
// early.
if s.cfg.Mempool == nil {
log.Debugf("Skipping mempool lookup for %v, no mempool ", op)
return fn.None[wire.MsgTx]()
}
// Query this input in the mempool. If this outpoint is already spent
// in mempool, we should get a spending event back immediately.
return s.cfg.Mempool.LookupInputMempoolSpend(op)
}
// calculateDefaultDeadline calculates the default deadline height for a sweep
// request that has no deadline height specified.
func (s *UtxoSweeper) calculateDefaultDeadline(pi *SweeperInput) int32 {
// Create a default deadline height, which will be used when there's no
// DeadlineHeight specified for a given input.
defaultDeadline := s.currentHeight + int32(s.cfg.NoDeadlineConfTarget)
// If the input is immature and has a locktime, we'll use the locktime
// height as the starting height.
matured, locktime := pi.isMature(uint32(s.currentHeight))
if !matured {
defaultDeadline = int32(locktime + s.cfg.NoDeadlineConfTarget)
log.Debugf("Input %v is immature, using locktime=%v instead "+
"of current height=%d as starting height",
pi.OutPoint(), locktime, s.currentHeight)
}
return defaultDeadline
}
// handleNewInput processes a new input by registering spend notification and
// scheduling sweeping for it.
func (s *UtxoSweeper) handleNewInput(input *sweepInputMessage) error {
outpoint := input.input.OutPoint()
pi, pending := s.inputs[outpoint]
if pending {
log.Infof("Already has pending input %v received, old params: "+
"%v, new params %v", outpoint, pi.params, input.params)
s.handleExistingInput(input, pi)
return nil
}
// This is a new input, and we want to query the mempool to see if this
// input has already been spent. If so, we'll start the input with the
// RBFInfo.
rbfInfo := s.decideRBFInfo(input.input.OutPoint())
// Create a new pendingInput and initialize the listeners slice with
// the passed in result channel. If this input is offered for sweep
// again, the result channel will be appended to this slice.
pi = &SweeperInput{
state: Init,
listeners: []chan Result{input.resultChan},
Input: input.input,
params: input.params,
rbf: rbfInfo,
}
// Set the starting fee rate if a previous sweeping tx is found.
rbfInfo.WhenSome(func(info RBFInfo) {
pi.params.StartingFeeRate = fn.Some(info.FeeRate)
})
// Set the acutal deadline height.
pi.DeadlineHeight = input.params.DeadlineHeight.UnwrapOr(
s.calculateDefaultDeadline(pi),
)
s.inputs[outpoint] = pi
log.Tracef("input %v, state=%v, added to inputs", outpoint, pi.state)
log.Infof("Registered sweep request at block %d: out_point=%v, "+
"witness_type=%v, amount=%v, deadline=%d, state=%v, "+
"params=(%v)", s.currentHeight, pi.OutPoint(), pi.WitnessType(),
btcutil.Amount(pi.SignDesc().Output.Value), pi.DeadlineHeight,
pi.state, pi.params)
// Start watching for spend of this input, either by us or the remote
// party.
cancel, err := s.monitorSpend(
outpoint, input.input.SignDesc().Output.PkScript,
input.input.HeightHint(),
)
if err != nil {
err := fmt.Errorf("wait for spend: %w", err)
s.markInputFatal(pi, nil, err)
return err
}
pi.ntfnRegCancel = cancel
return nil
}
// decideRBFInfo queries the mempool to see whether the given input has already
// been spent. When spent, it will query the sweeper store to fetch the fee info
// of the spending transction, and construct an RBFInfo based on it. Suppose an
// error occurs, fn.None is returned.
func (s *UtxoSweeper) decideRBFInfo(
op wire.OutPoint) fn.Option[RBFInfo] {
// Check if we can find the spending tx of this input in mempool.
txOption := s.mempoolLookup(op)
// Extract the spending tx from the option.
var tx *wire.MsgTx
txOption.WhenSome(func(t wire.MsgTx) {
tx = &t
})
// Exit early if it's not found.
//
// NOTE: this is not accurate for backends that don't support mempool
// lookup:
// - for neutrino we don't have a mempool.
// - for btcd below v0.24.1 we don't have `gettxspendingprevout`.
if tx == nil {
return fn.None[RBFInfo]()
}
// Otherwise the input is already spent in the mempool, so eventually
// we will return Published.
//
// We also need to update the RBF info for this input. If the sweeping
// transaction is broadcast by us, we can find the fee info in the
// sweeper store.
txid := tx.TxHash()
tr, err := s.cfg.Store.GetTx(txid)
log.Debugf("Found spending tx %v in mempool for input %v", tx.TxHash(),
op)
// If the tx is not found in the store, it means it's not broadcast by
// us, hence we can't find the fee info. This is fine as, later on when
// this tx is confirmed, we will remove the input from our inputs.
if errors.Is(err, ErrTxNotFound) {
log.Warnf("Spending tx %v not found in sweeper store", txid)
return fn.None[RBFInfo]()
}
// Exit if we get an db error.
if err != nil {
log.Errorf("Unable to get tx %v from sweeper store: %v",
txid, err)
return fn.None[RBFInfo]()
}
// Prepare the fee info and return it.
rbf := fn.Some(RBFInfo{
Txid: txid,
Fee: btcutil.Amount(tr.Fee),
FeeRate: chainfee.SatPerKWeight(tr.FeeRate),
})
return rbf
}
// handleExistingInput processes an input that is already known to the sweeper.
// It will overwrite the params of the old input with the new ones.
func (s *UtxoSweeper) handleExistingInput(input *sweepInputMessage,
oldInput *SweeperInput) {
// Before updating the input details, check if an exclusive group was
// set. In case the same input is registered again without an exclusive
// group set, the previous input and its sweep parameters are outdated
// hence need to be replaced. This scenario currently only happens for
// anchor outputs. When a channel is force closed, in the worst case 3
// different sweeps with the same exclusive group are registered with
// the sweeper to bump the closing transaction (cpfp) when its time
// critical. Receiving an input which was already registered with the
// sweeper but now without an exclusive group means non of the previous
// inputs were used as CPFP, so we need to make sure we update the
// sweep parameters but also remove all inputs with the same exclusive
// group because the are outdated too.
var prevExclGroup *uint64
if oldInput.params.ExclusiveGroup != nil &&
input.params.ExclusiveGroup == nil {
prevExclGroup = new(uint64)
*prevExclGroup = *oldInput.params.ExclusiveGroup
}
// Update input details and sweep parameters. The re-offered input
// details may contain a change to the unconfirmed parent tx info.
oldInput.params = input.params
oldInput.Input = input.input
// If the new input specifies a deadline, update the deadline height.
oldInput.DeadlineHeight = input.params.DeadlineHeight.UnwrapOr(
oldInput.DeadlineHeight,
)
// Add additional result channel to signal spend of this input.
oldInput.listeners = append(oldInput.listeners, input.resultChan)
if prevExclGroup != nil {
s.removeExclusiveGroup(*prevExclGroup)
}
}
// handleInputSpent takes a spend event of our input and updates the sweeper's
// internal state to remove the input.
func (s *UtxoSweeper) handleInputSpent(spend *chainntnfs.SpendDetail) {
// Query store to find out if we ever published this tx.
spendHash := *spend.SpenderTxHash
isOurTx := s.cfg.Store.IsOurTx(spendHash)
// If this isn't our transaction, it means someone else swept outputs
// that we were attempting to sweep. This can happen for anchor outputs
// as well as justice transactions. In this case, we'll notify the
// wallet to remove any spends that descent from this output.
if !isOurTx {
// Construct a map of the inputs this transaction spends.
spendingTx := spend.SpendingTx
inputsSpent := make(
map[wire.OutPoint]struct{}, len(spendingTx.TxIn),
)
for _, txIn := range spendingTx.TxIn {
inputsSpent[txIn.PreviousOutPoint] = struct{}{}
}
log.Debugf("Attempting to remove descendant txns invalidated "+
"by (txid=%v): %v", spendingTx.TxHash(),
spew.Sdump(spendingTx))
err := s.removeConflictSweepDescendants(inputsSpent)
if err != nil {
log.Warnf("unable to remove descendant transactions "+
"due to tx %v: ", spendHash)
}
log.Debugf("Detected third party spend related to in flight "+
"inputs (is_ours=%v): %v", isOurTx,
lnutils.SpewLogClosure(spend.SpendingTx))
}
// We now use the spending tx to update the state of the inputs.
s.markInputsSwept(spend.SpendingTx, isOurTx)
}
// markInputsSwept marks all inputs swept by the spending transaction as swept.
// It will also notify all the subscribers of this input.
func (s *UtxoSweeper) markInputsSwept(tx *wire.MsgTx, isOurTx bool) {
for _, txIn := range tx.TxIn {
outpoint := txIn.PreviousOutPoint
// Check if this input is known to us. It could probably be
// unknown if we canceled the registration, deleted from inputs
// map but the ntfn was in-flight already. Or this could be not
// one of our inputs.
input, ok := s.inputs[outpoint]
if !ok {
// It's very likely that a spending tx contains inputs
// that we don't know.
log.Tracef("Skipped marking input as swept: %v not "+
"found in pending inputs", outpoint)
continue
}
// This input may already been marked as swept by a previous
// spend notification, which is likely to happen as one sweep
// transaction usually sweeps multiple inputs.
if input.terminated() {
log.Debugf("Skipped marking input as swept: %v "+
"state=%v", outpoint, input.state)
continue
}
input.state = Swept
// Return either a nil or a remote spend result.
var err error
if !isOurTx {
log.Warnf("Input=%v was spent by remote or third "+
"party in tx=%v", outpoint, tx.TxHash())
err = ErrRemoteSpend
}
// Signal result channels.
s.signalResult(input, Result{
Tx: tx,
Err: err,
})
// Remove all other inputs in this exclusive group.
if input.params.ExclusiveGroup != nil {
s.removeExclusiveGroup(*input.params.ExclusiveGroup)
}
}
}
// markInputFatal marks the given input as fatal and won't be retried. It
// will also notify all the subscribers of this input.
func (s *UtxoSweeper) markInputFatal(pi *SweeperInput, tx *wire.MsgTx,
err error) {
log.Errorf("Failed to sweep input: %v, error: %v", pi, err)
pi.state = Fatal
s.signalResult(pi, Result{
Tx: tx,
Err: err,
})
}
// updateSweeperInputs updates the sweeper's internal state and returns a map
// of inputs to be swept. It will remove the inputs that are in final states,
// and returns a map of inputs that have either state Init or PublishFailed.
func (s *UtxoSweeper) updateSweeperInputs() InputsMap {
// Create a map of inputs to be swept.
inputs := make(InputsMap)
// Iterate the pending inputs and update the sweeper's state.
//
// TODO(yy): sweeper is made to communicate via go channels, so no
// locks are needed to access the map. However, it'd be safer if we
// turn this inputs map into a SyncMap in case we wanna add concurrent
// access to the map in the future.
for op, input := range s.inputs {
log.Tracef("Checking input: %s, state=%v", input, input.state)
// If the input has reached a final state, that it's either
// been swept, or failed, or excluded, we will remove it from
// our sweeper.
if input.terminated() {
log.Debugf("Removing input(State=%v) %v from sweeper",
input.state, op)
delete(s.inputs, op)
continue
}
// If this input has been included in a sweep tx that's not
// published yet, we'd skip this input and wait for the sweep
// tx to be published.
if input.state == PendingPublish {
continue
}
// If this input has already been published, we will need to
// check the RBF condition before attempting another sweeping.
if input.state == Published {
continue
}
// If the input has a locktime that's not yet reached, we will
// skip this input and wait for the locktime to be reached.
mature, _ := input.isMature(uint32(s.currentHeight))
if !mature {
continue
}
// If this input is new or has been failed to be published,
// we'd retry it. The assumption here is that when an error is
// returned from `PublishTransaction`, it means the tx has
// failed to meet the policy, hence it's not in the mempool.
inputs[op] = input
}
return inputs
}
// sweepPendingInputs is called when the ticker fires. It will create clusters
// and attempt to create and publish the sweeping transactions.
func (s *UtxoSweeper) sweepPendingInputs(inputs InputsMap) {
log.Debugf("Sweeping %v inputs", len(inputs))
// Cluster all of our inputs based on the specific Aggregator.
sets := s.cfg.Aggregator.ClusterInputs(inputs)
// sweepWithLock is a helper closure that executes the sweep within a
// coin select lock to prevent the coins being selected for other
// transactions like funding of a channel.
sweepWithLock := func(set InputSet) error {
return s.cfg.Wallet.WithCoinSelectLock(func() error {
// Try to add inputs from our wallet.
err := set.AddWalletInputs(s.cfg.Wallet)
if err != nil {
return err
}
// Create sweeping transaction for each set.
err = s.sweep(set)
if err != nil {
return err
}
return nil
})
}
for _, set := range sets {
var err error
if set.NeedWalletInput() {
// Sweep the set of inputs that need the wallet inputs.
err = sweepWithLock(set)
} else {
// Sweep the set of inputs that don't need the wallet
// inputs.
err = s.sweep(set)
}
if err != nil {
log.Errorf("Failed to sweep %v: %v", set, err)
}
}
}
// bumpResp wraps the result of a bump attempt returned from the fee bumper and
// the inputs being used.
type bumpResp struct {
// result is the result of the bump attempt returned from the fee
// bumper.
result *BumpResult
// set is the input set that was used in the bump attempt.
set InputSet
}
// monitorFeeBumpResult subscribes to the passed result chan to listen for
// future updates about the sweeping tx.
//
// NOTE: must run as a goroutine.
func (s *UtxoSweeper) monitorFeeBumpResult(set InputSet,
resultChan <-chan *BumpResult) {
defer s.wg.Done()
for {
select {
case r := <-resultChan:
// Validate the result is valid.
if err := r.Validate(); err != nil {
log.Errorf("Received invalid result: %v", err)
continue
}
resp := &bumpResp{
result: r,
set: set,
}
// Send the result back to the main event loop.
select {
case s.bumpRespChan <- resp:
case <-s.quit:
log.Debug("Sweeper shutting down, skip " +
"sending bump result")
return
}
// The sweeping tx has been confirmed, we can exit the
// monitor now.
//
// TODO(yy): can instead remove the spend subscription
// in sweeper and rely solely on this event to mark
// inputs as Swept?
if r.Event == TxConfirmed || r.Event == TxFailed {
// Exit if the tx is failed to be created.
if r.Tx == nil {
log.Debugf("Received %v for nil tx, "+
"exit monitor", r.Event)
return
}
log.Debugf("Received %v for sweep tx %v, exit "+
"fee bump monitor", r.Event,
r.Tx.TxHash())
// Cancel the rebroadcasting of the failed tx.
s.cfg.Wallet.CancelRebroadcast(r.Tx.TxHash())
return
}
case <-s.quit:
log.Debugf("Sweeper shutting down, exit fee " +
"bump handler")
return
}
}
}
// handleBumpEventTxFailed handles the case where the tx has been failed to
// publish.
func (s *UtxoSweeper) handleBumpEventTxFailed(resp *bumpResp) {
r := resp.result
tx, err := r.Tx, r.Err
if tx != nil {
log.Warnf("Fee bump attempt failed for tx=%v: %v", tx.TxHash(),
err)
}
// NOTE: When marking the inputs as failed, we are using the input set
// instead of the inputs found in the tx. This is fine for current
// version of the sweeper because we always create a tx using ALL of
// the inputs specified by the set.
//
// TODO(yy): should we also remove the failed tx from db?
s.markInputsPublishFailed(resp.set, resp.result.FeeRate)
}
// handleBumpEventTxReplaced handles the case where the sweeping tx has been
// replaced by a new one.
func (s *UtxoSweeper) handleBumpEventTxReplaced(resp *bumpResp) error {
r := resp.result
oldTx := r.ReplacedTx
newTx := r.Tx
// Prepare a new record to replace the old one.
tr := &TxRecord{
Txid: newTx.TxHash(),
FeeRate: uint64(r.FeeRate),
Fee: uint64(r.Fee),
}
// Get the old record for logging purpose.
oldTxid := oldTx.TxHash()
record, err := s.cfg.Store.GetTx(oldTxid)
if err != nil {
log.Errorf("Fetch tx record for %v: %v", oldTxid, err)
return err
}
// Cancel the rebroadcasting of the replaced tx.
s.cfg.Wallet.CancelRebroadcast(oldTxid)
log.Infof("RBFed tx=%v(fee=%v sats, feerate=%v sats/kw) with new "+
"tx=%v(fee=%v sats, feerate=%v sats/kw)", record.Txid,
record.Fee, record.FeeRate, tr.Txid, tr.Fee, tr.FeeRate)
// The old sweeping tx has been replaced by a new one, we will update
// the tx record in the sweeper db.
//
// TODO(yy): we may also need to update the inputs in this tx to a new
// state. Suppose a replacing tx only spends a subset of the inputs
// here, we'd end up with the rest being marked as `Published` and
// won't be aggregated in the next sweep. Atm it's fine as we always
// RBF the same input set.
if err := s.cfg.Store.DeleteTx(oldTxid); err != nil {
log.Errorf("Delete tx record for %v: %v", oldTxid, err)
return err
}
// Mark the inputs as published using the replacing tx.
return s.markInputsPublished(tr, resp.set)
}
// handleBumpEventTxPublished handles the case where the sweeping tx has been
// successfully published.
func (s *UtxoSweeper) handleBumpEventTxPublished(resp *bumpResp) error {
r := resp.result
tx := r.Tx
tr := &TxRecord{
Txid: tx.TxHash(),
FeeRate: uint64(r.FeeRate),
Fee: uint64(r.Fee),
}
// Inputs have been successfully published so we update their
// states.
err := s.markInputsPublished(tr, resp.set)
if err != nil {
return err
}
log.Debugf("Published sweep tx %v, num_inputs=%v, height=%v",
tx.TxHash(), len(tx.TxIn), s.currentHeight)
// If there's no error, remove the output script. Otherwise keep it so
// that it can be reused for the next transaction and causes no address
// inflation.
s.currentOutputScript = fn.None[lnwallet.AddrWithKey]()
return nil
}
// handleBumpEventTxFatal handles the case where there's an unexpected error
// when creating or publishing the sweeping tx. In this case, the tx will be
// removed from the sweeper store and the inputs will be marked as `Failed`,
// which means they will not be retried.
func (s *UtxoSweeper) handleBumpEventTxFatal(resp *bumpResp) error {
r := resp.result
// Remove the tx from the sweeper store if there is one. Since this is
// a broadcast error, it's likely there isn't a tx here.
if r.Tx != nil {
txid := r.Tx.TxHash()
log.Infof("Tx=%v failed with unexpected error: %v", txid, r.Err)
// Remove the tx from the sweeper db if it exists.
if err := s.cfg.Store.DeleteTx(txid); err != nil {
return fmt.Errorf("delete tx record for %v: %w", txid,
err)
}
}
// Mark the inputs as fatal.
s.markInputsFatal(resp.set, r.Err)
return nil
}
// markInputsFatal marks all inputs in the input set as failed. It will also
// notify all the subscribers of these inputs.
func (s *UtxoSweeper) markInputsFatal(set InputSet, err error) {
for _, inp := range set.Inputs() {
outpoint := inp.OutPoint()
input, ok := s.inputs[outpoint]
if !ok {
// It's very likely that a spending tx contains inputs
// that we don't know.
log.Tracef("Skipped marking input as failed: %v not "+
"found in pending inputs", outpoint)
continue
}
// If the input is already in a terminal state, we don't want
// to rewrite it, which also indicates an error as we only get
// an error event during the initial broadcast.
if input.terminated() {
log.Errorf("Skipped marking input=%v as failed due to "+
"unexpected state=%v", outpoint, input.state)
continue
}
s.markInputFatal(input, nil, err)
}
}
// handleBumpEvent handles the result sent from the bumper based on its event
// type.
//
// NOTE: TxConfirmed event is not handled, since we already subscribe to the
// input's spending event, we don't need to do anything here.
func (s *UtxoSweeper) handleBumpEvent(r *bumpResp) error {
log.Debugf("Received bump result %v", r.result)
switch r.result.Event {
// The tx has been published, we update the inputs' state and create a
// record to be stored in the sweeper db.
case TxPublished:
return s.handleBumpEventTxPublished(r)
// The tx has failed, we update the inputs' state.
case TxFailed:
s.handleBumpEventTxFailed(r)
return nil
// The tx has been replaced, we will remove the old tx and replace it
// with the new one.
case TxReplaced:
return s.handleBumpEventTxReplaced(r)
// There are inputs being spent in a tx which the fee bumper doesn't
// understand. We will remove the tx from the sweeper db and mark the
// inputs as swept.
case TxUnknownSpend:
s.handleBumpEventTxUnknownSpend(r)
// There's a fatal error in creating the tx, we will remove the tx from
// the sweeper db and mark the inputs as failed.
case TxFatal:
return s.handleBumpEventTxFatal(r)
}
return nil
}
// IsSweeperOutpoint determines whether the outpoint was created by the sweeper.
//
// NOTE: It is enough to check the txid because the sweeper will create
// outpoints which solely belong to the internal LND wallet.
func (s *UtxoSweeper) IsSweeperOutpoint(op wire.OutPoint) bool {
return s.cfg.Store.IsOurTx(op.Hash)
}
// markInputSwept marks the given input as swept by the tx. It will also notify
// all the subscribers of this input.
func (s *UtxoSweeper) markInputSwept(inp *SweeperInput, tx *wire.MsgTx) {
log.Debugf("Marking input as swept: %v from state=%v", inp.OutPoint(),
inp.state)
inp.state = Swept
// Signal result channels.
s.signalResult(inp, Result{
Tx: tx,
})
// Remove all other inputs in this exclusive group.
if inp.params.ExclusiveGroup != nil {
s.removeExclusiveGroup(*inp.params.ExclusiveGroup)
}
}
// handleUnknownSpendTx takes an input and its spending tx. If the spending tx
// cannot be found in the sweeper store, the input will be marked as fatal,
// otherwise it will be marked as swept.
func (s *UtxoSweeper) handleUnknownSpendTx(inp *SweeperInput, tx *wire.MsgTx) {
op := inp.OutPoint()
txid := tx.TxHash()
isOurTx := s.cfg.Store.IsOurTx(txid)
// If this is our tx, it means it's a previous sweeping tx that got
// confirmed, which could happen when a restart happens during the
// sweeping process.
if isOurTx {
log.Debugf("Found our sweeping tx %v, marking input %v as "+
"swept", txid, op)
// We now use the spending tx to update the state of the inputs.
s.markInputSwept(inp, tx)
return
}
// Since the input is spent by others, we now mark it as fatal and won't
// be retried.
s.markInputFatal(inp, tx, ErrRemoteSpend)
log.Debugf("Removing descendant txns invalidated by (txid=%v): %v",
txid, lnutils.SpewLogClosure(tx))
// Construct a map of the inputs this transaction spends.
spentInputs := make(map[wire.OutPoint]struct{}, len(tx.TxIn))
for _, txIn := range tx.TxIn {
spentInputs[txIn.PreviousOutPoint] = struct{}{}
}
err := s.removeConflictSweepDescendants(spentInputs)
if err != nil {
log.Warnf("unable to remove descendant transactions "+
"due to tx %v: ", txid)
}
}
// handleBumpEventTxUnknownSpend handles the case where the confirmed tx is
// unknown to the fee bumper. In the case when the sweeping tx has been replaced
// by another party with their tx being confirmed. It will retry sweeping the
// "good" inputs once the "bad" ones are kicked out.
func (s *UtxoSweeper) handleBumpEventTxUnknownSpend(r *bumpResp) {
// Mark the inputs as publish failed, which means they will be retried
// later.
s.markInputsPublishFailed(r.set, r.result.FeeRate)
// Get all the inputs that are not spent in the current sweeping tx.
spentInputs := r.result.SpentInputs
// Create a slice to track inputs to be retried.
inputsToRetry := make([]input.Input, 0, len(r.set.Inputs()))
// Iterate all the inputs found in this bump and mark the ones spent by
// the third party as failed. The rest of inputs will then be updated
// with a new fee rate and be retried immediately.
for _, inp := range r.set.Inputs() {
op := inp.OutPoint()
input, ok := s.inputs[op]
// Wallet inputs are not tracked so we will not find them from
// the inputs map.
if !ok {
log.Debugf("Skipped marking input: %v not found in "+
"pending inputs", op)
continue
}
// Check whether this input has been spent, if so we mark it as
// fatal or swept based on whether this is one of our previous
// sweeping txns, then move to the next.
tx, spent := spentInputs[op]
if spent {
s.handleUnknownSpendTx(input, tx)
continue
}
log.Debugf("Input(%v): updating params: immediate [%v -> true]",
op, r.result.FeeRate, input.params.Immediate)
input.params.Immediate = true
inputsToRetry = append(inputsToRetry, input)
}
// Exit early if there are no inputs to be retried.
if len(inputsToRetry) == 0 {
return
}
log.Debugf("Retry sweeping inputs with updated params: %v",
inputTypeSummary(inputsToRetry))
// Get the latest inputs, which should put the PublishFailed inputs back
// to the sweeping queue.
inputs := s.updateSweeperInputs()
// Immediately sweep the remaining inputs - the previous inputs should
// now be swept with the updated StartingFeeRate immediately. We may
// also include more inputs in the new sweeping tx if new ones with the
// same deadline are offered.
s.sweepPendingInputs(inputs)
}
package sweep
import (
"fmt"
"sync"
"testing"
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs"
)
var (
defaultTestTimeout = 5 * time.Second
processingDelay = 1 * time.Second
mockChainHash, _ = chainhash.NewHashFromStr("00aabbccddeeff")
mockChainHeight = int32(100)
)
// MockNotifier simulates the chain notifier for test purposes. This type is
// exported because it is used in nursery tests.
type MockNotifier struct {
confChannel map[chainhash.Hash]chan *chainntnfs.TxConfirmation
epochChan map[chan *chainntnfs.BlockEpoch]int32
spendChan map[wire.OutPoint][]chan *chainntnfs.SpendDetail
spends map[wire.OutPoint]*wire.MsgTx
mutex sync.RWMutex
t *testing.T
}
// NewMockNotifier instantiates a new mock notifier.
func NewMockNotifier(t *testing.T) *MockNotifier {
return &MockNotifier{
confChannel: make(map[chainhash.Hash]chan *chainntnfs.TxConfirmation),
epochChan: make(map[chan *chainntnfs.BlockEpoch]int32),
spendChan: make(map[wire.OutPoint][]chan *chainntnfs.SpendDetail),
spends: make(map[wire.OutPoint]*wire.MsgTx),
t: t,
}
}
// NotifyEpochNonBlocking simulates a new epoch arriving without blocking when
// the epochChan is not read.
func (m *MockNotifier) NotifyEpochNonBlocking(height int32) {
m.t.Helper()
for epochChan, chanHeight := range m.epochChan {
// Only send notifications if the height is greater than the
// height the caller passed into the register call.
if chanHeight >= height {
continue
}
log.Debugf("Notifying height %v to listener", height)
select {
case epochChan <- &chainntnfs.BlockEpoch{Height: height}:
default:
}
}
}
// NotifyEpoch simulates a new epoch arriving.
func (m *MockNotifier) NotifyEpoch(height int32) {
m.t.Helper()
for epochChan, chanHeight := range m.epochChan {
// Only send notifications if the height is greater than the
// height the caller passed into the register call.
if chanHeight >= height {
continue
}
log.Debugf("Notifying height %v to listener", height)
select {
case epochChan <- &chainntnfs.BlockEpoch{
Height: height,
}:
case <-time.After(defaultTestTimeout):
m.t.Fatal("epoch event not consumed")
}
}
}
// ConfirmTx simulates a tx confirming.
func (m *MockNotifier) ConfirmTx(txid *chainhash.Hash, height uint32) error {
confirm := &chainntnfs.TxConfirmation{
BlockHeight: height,
}
select {
case m.getConfChannel(txid) <- confirm:
case <-time.After(defaultTestTimeout):
return fmt.Errorf("confirmation not consumed")
}
return nil
}
// SpendOutpoint simulates a utxo being spent.
func (m *MockNotifier) SpendOutpoint(outpoint wire.OutPoint,
spendingTx wire.MsgTx) {
log.Debugf("Spending outpoint %v", outpoint)
m.mutex.Lock()
defer m.mutex.Unlock()
channels, ok := m.spendChan[outpoint]
if ok {
for _, channel := range channels {
m.sendSpend(channel, &outpoint, &spendingTx)
}
}
m.spends[outpoint] = &spendingTx
}
func (m *MockNotifier) sendSpend(channel chan *chainntnfs.SpendDetail,
outpoint *wire.OutPoint,
spendingTx *wire.MsgTx) {
log.Debugf("Notifying spend of outpoint %v", outpoint)
spenderTxHash := spendingTx.TxHash()
channel <- &chainntnfs.SpendDetail{
SpenderTxHash: &spenderTxHash,
SpendingTx: spendingTx,
SpentOutPoint: outpoint,
}
}
// RegisterConfirmationsNtfn registers for tx confirm notifications.
func (m *MockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
_ []byte, numConfs, heightHint uint32,
opt ...chainntnfs.NotifierOption) (*chainntnfs.ConfirmationEvent, error) {
return &chainntnfs.ConfirmationEvent{
Confirmed: m.getConfChannel(txid),
}, nil
}
func (m *MockNotifier) getConfChannel(
txid *chainhash.Hash) chan *chainntnfs.TxConfirmation {
m.mutex.Lock()
defer m.mutex.Unlock()
channel, ok := m.confChannel[*txid]
if ok {
return channel
}
channel = make(chan *chainntnfs.TxConfirmation)
m.confChannel[*txid] = channel
return channel
}
// RegisterBlockEpochNtfn registers a block notification.
func (m *MockNotifier) RegisterBlockEpochNtfn(
bestBlock *chainntnfs.BlockEpoch) (*chainntnfs.BlockEpochEvent, error) {
log.Tracef("Mock block ntfn registered")
m.mutex.Lock()
epochChan := make(chan *chainntnfs.BlockEpoch, 1)
// The real notifier returns a notification with the current block hash
// and height immediately if no best block hash or height is specified
// in the request. We want to emulate this behaviour as well for the
// mock.
switch {
case bestBlock == nil:
epochChan <- &chainntnfs.BlockEpoch{
Hash: mockChainHash,
Height: mockChainHeight,
}
m.epochChan[epochChan] = mockChainHeight
default:
m.epochChan[epochChan] = bestBlock.Height
}
m.mutex.Unlock()
return &chainntnfs.BlockEpochEvent{
Epochs: epochChan,
Cancel: func() {
log.Tracef("Mock block ntfn canceled")
m.mutex.Lock()
delete(m.epochChan, epochChan)
m.mutex.Unlock()
},
}, nil
}
// Start the notifier.
func (m *MockNotifier) Start() error {
return nil
}
// Started checks if started.
func (m *MockNotifier) Started() bool {
return true
}
// Stop the notifier.
func (m *MockNotifier) Stop() error {
return nil
}
// RegisterSpendNtfn registers for spend notifications.
func (m *MockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
_ []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) {
log.Debugf("RegisterSpendNtfn for outpoint %v", outpoint)
// Add channel to global spend ntfn map.
m.mutex.Lock()
channels, ok := m.spendChan[*outpoint]
if !ok {
channels = make([]chan *chainntnfs.SpendDetail, 0)
}
channel := make(chan *chainntnfs.SpendDetail, 1)
channels = append(channels, channel)
m.spendChan[*outpoint] = channels
// Check if this output has already been spent.
spendingTx, spent := m.spends[*outpoint]
m.mutex.Unlock()
// If output has been spent already, signal now. Do this outside the
// lock to prevent a deadlock.
if spent {
m.sendSpend(channel, outpoint, spendingTx)
}
return &chainntnfs.SpendEvent{
Spend: channel,
Cancel: func() {
log.Infof("Cancelling RegisterSpendNtfn for %v",
outpoint)
m.mutex.Lock()
defer m.mutex.Unlock()
channels := m.spendChan[*outpoint]
for i, c := range channels {
if c == channel {
channels[i] = channels[len(channels)-1]
m.spendChan[*outpoint] =
channels[:len(channels)-1]
}
}
close(channel)
log.Infof("Spend ntfn channel closed for %v",
outpoint)
},
}, nil
}
package sweep
import (
"fmt"
"math"
"sort"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)
var (
// ErrNotEnoughInputs is returned when there are not enough wallet
// inputs to construct a non-dust change output for an input set.
ErrNotEnoughInputs = fmt.Errorf("not enough inputs")
// ErrDeadlinesMismatch is returned when the deadlines of the input
// sets do not match.
ErrDeadlinesMismatch = fmt.Errorf("deadlines mismatch")
// ErrDustOutput is returned when the output value is below the dust
// limit.
ErrDustOutput = fmt.Errorf("dust output")
)
// InputSet defines an interface that's responsible for filtering a set of
// inputs that can be swept economically.
type InputSet interface {
// Inputs returns the set of inputs that should be used to create a tx.
Inputs() []input.Input
// AddWalletInputs adds wallet inputs to the set until a non-dust
// change output can be made. Return an error if there are not enough
// wallet inputs.
AddWalletInputs(wallet Wallet) error
// NeedWalletInput returns true if the input set needs more wallet
// inputs.
NeedWalletInput() bool
// DeadlineHeight returns an absolute block height to express the
// time-sensitivity of the input set. The outputs from a force close tx
// have different time preferences:
// - to_local: no time pressure as it can only be swept by us.
// - first level outgoing HTLC: must be swept before its corresponding
// incoming HTLC's CLTV is reached.
// - first level incoming HTLC: must be swept before its CLTV is
// reached.
// - second level HTLCs: no time pressure.
// - anchor: for CPFP-purpose anchor, it must be swept before any of
// the above CLTVs is reached. For non-CPFP purpose anchor, there's
// no time pressure.
DeadlineHeight() int32
// Budget givens the total amount that can be used as fees by this
// input set.
Budget() btcutil.Amount
// StartingFeeRate returns the max starting fee rate found in the
// inputs.
StartingFeeRate() fn.Option[chainfee.SatPerKWeight]
// Immediate returns a boolean to indicate whether the tx made from
// this input set should be published immediately.
//
// TODO(yy): create a new method `Params` to combine the informational
// methods DeadlineHeight, Budget, StartingFeeRate and Immediate.
Immediate() bool
}
// createWalletTxInput converts a wallet utxo into an object that can be added
// to the other inputs to sweep.
func createWalletTxInput(utxo *lnwallet.Utxo) (input.Input, error) {
signDesc := &input.SignDescriptor{
Output: &wire.TxOut{
PkScript: utxo.PkScript,
Value: int64(utxo.Value),
},
HashType: txscript.SigHashAll,
}
var witnessType input.WitnessType
switch utxo.AddressType {
case lnwallet.WitnessPubKey:
witnessType = input.WitnessKeyHash
case lnwallet.NestedWitnessPubKey:
witnessType = input.NestedWitnessKeyHash
case lnwallet.TaprootPubkey:
witnessType = input.TaprootPubKeySpend
signDesc.HashType = txscript.SigHashDefault
default:
return nil, fmt.Errorf("unknown address type %v",
utxo.AddressType)
}
// A height hint doesn't need to be set, because we don't monitor these
// inputs for spend.
heightHint := uint32(0)
return input.NewBaseInput(
&utxo.OutPoint, witnessType, signDesc, heightHint,
), nil
}
// BudgetInputSet implements the interface `InputSet`. It takes a list of
// pending inputs which share the same deadline height and groups them into a
// set conditionally based on their economical values.
type BudgetInputSet struct {
// inputs is the set of inputs that have been added to the set after
// considering their economical contribution.
inputs []*SweeperInput
// deadlineHeight is the height which the inputs in this set must be
// confirmed by.
deadlineHeight int32
// extraBudget is a value that should be allocated to sweep the given
// set of inputs. This can be used to add extra funds to the sweep
// transaction, for example to cover fees for additional outputs of
// custom channels.
extraBudget btcutil.Amount
}
// Compile-time constraint to ensure budgetInputSet implements InputSet.
var _ InputSet = (*BudgetInputSet)(nil)
// errEmptyInputs is returned when the input slice is empty.
var errEmptyInputs = fmt.Errorf("inputs slice is empty")
// validateInputs is used when creating new BudgetInputSet to ensure there are
// no duplicate inputs and they all share the same deadline heights, if set.
func validateInputs(inputs []SweeperInput, deadlineHeight int32) error {
// Sanity check the input slice to ensure it's non-empty.
if len(inputs) == 0 {
return errEmptyInputs
}
// inputDeadline tracks the input's deadline height. It will be updated
// if the input has a different deadline than the specified
// deadlineHeight.
inputDeadline := deadlineHeight
// dedupInputs is a set used to track unique outpoints of the inputs.
dedupInputs := fn.NewSet(
// Iterate all the inputs and map the function.
fn.Map(inputs, func(inp SweeperInput) wire.OutPoint {
// If the input has a deadline height, we'll check if
// it's the same as the specified.
inp.params.DeadlineHeight.WhenSome(func(h int32) {
// Exit early if the deadlines matched.
if h == deadlineHeight {
return
}
// Update the deadline height if it's
// different.
inputDeadline = h
})
return inp.OutPoint()
})...,
)
// Make sure the inputs share the same deadline height when there is
// one.
if inputDeadline != deadlineHeight {
return fmt.Errorf("input deadline height not matched: want "+
"%d, got %d", deadlineHeight, inputDeadline)
}
// Provide a defensive check to ensure that we don't have any duplicate
// inputs within the set.
if len(dedupInputs) != len(inputs) {
return fmt.Errorf("duplicate inputs")
}
return nil
}
// NewBudgetInputSet creates a new BudgetInputSet.
func NewBudgetInputSet(inputs []SweeperInput, deadlineHeight int32,
auxSweeper fn.Option[AuxSweeper]) (*BudgetInputSet, error) {
// Validate the supplied inputs.
if err := validateInputs(inputs, deadlineHeight); err != nil {
return nil, err
}
bi := &BudgetInputSet{
deadlineHeight: deadlineHeight,
inputs: make([]*SweeperInput, 0, len(inputs)),
}
for _, input := range inputs {
bi.addInput(input)
}
log.Tracef("Created %v", bi.String())
// Attach an optional budget. This will be a no-op if the auxSweeper
// is not set.
if err := bi.attachExtraBudget(auxSweeper); err != nil {
return nil, err
}
return bi, nil
}
// attachExtraBudget attaches an extra budget to the input set, if the passed
// aux sweeper is set.
func (b *BudgetInputSet) attachExtraBudget(s fn.Option[AuxSweeper]) error {
extraBudget, err := fn.MapOptionZ(
s, func(aux AuxSweeper) fn.Result[btcutil.Amount] {
return aux.ExtraBudgetForInputs(b.Inputs())
},
).Unpack()
if err != nil {
return err
}
b.extraBudget = extraBudget
return nil
}
// String returns a human-readable description of the input set.
func (b *BudgetInputSet) String() string {
inputsDesc := ""
for _, input := range b.inputs {
inputsDesc += fmt.Sprintf("\n%v", input)
}
return fmt.Sprintf("BudgetInputSet(budget=%v, deadline=%v, "+
"inputs=[%v])", b.Budget(), b.DeadlineHeight(), inputsDesc)
}
// addInput adds an input to the input set.
func (b *BudgetInputSet) addInput(input SweeperInput) {
b.inputs = append(b.inputs, &input)
}
// addWalletInput takes a wallet UTXO and adds it as an input to be used as
// budget for the input set.
func (b *BudgetInputSet) addWalletInput(utxo *lnwallet.Utxo) error {
input, err := createWalletTxInput(utxo)
if err != nil {
return err
}
pi := SweeperInput{
Input: input,
params: Params{
DeadlineHeight: fn.Some(b.deadlineHeight),
},
}
b.addInput(pi)
log.Debugf("Added wallet input to input set: op=%v, amt=%v",
pi.OutPoint(), utxo.Value)
return nil
}
// NeedWalletInput returns true if the input set needs more wallet inputs.
//
// A set may need wallet inputs when it has a required output or its total
// value cannot cover its total budget.
func (b *BudgetInputSet) NeedWalletInput() bool {
var (
// budgetNeeded is the amount that needs to be covered from
// other inputs. We start at the value of the extra budget,
// which might be needed for custom channels that add extra
// outputs.
budgetNeeded = b.extraBudget
// budgetBorrowable is the amount that can be borrowed from
// other inputs.
budgetBorrowable btcutil.Amount
)
for _, inp := range b.inputs {
// If this input has a required output, we can assume it's a
// second-level htlc txns input. Although this input must have
// a value that can cover its budget, it cannot be used to pay
// fees. Instead, we need to borrow budget from other inputs to
// make the sweep happen. Once swept, the input value will be
// credited to the wallet.
if inp.RequiredTxOut() != nil {
budgetNeeded += inp.params.Budget
continue
}
// Get the amount left after covering the input's own budget.
// This amount can then be lent to the above input. For a wallet
// input, its `Budget` is set to zero, which means the whole
// input can be borrowed to cover the budget.
budget := inp.params.Budget
output := btcutil.Amount(inp.SignDesc().Output.Value)
budgetBorrowable += output - budget
// If the input's budget is not even covered by itself, we need
// to borrow outputs from other inputs.
if budgetBorrowable < 0 {
log.Tracef("Input %v specified a budget that exceeds "+
"its output value: %v > %v", inp, budget,
output)
}
}
log.Debugf("NeedWalletInput: budgetNeeded=%v, budgetBorrowable=%v",
budgetNeeded, budgetBorrowable)
// If we don't have enough extra budget to borrow, we need wallet
// inputs.
return budgetBorrowable < budgetNeeded
}
// hasNormalInput return a bool to indicate whether there exists an input that
// doesn't require a TxOut. When an input has no required outputs, it's either a
// wallet input, or an input we want to sweep.
func (b *BudgetInputSet) hasNormalInput() bool {
for _, inp := range b.inputs {
if inp.RequiredTxOut() != nil {
continue
}
return true
}
return false
}
// AddWalletInputs adds wallet inputs to the set until the specified budget is
// met. When sweeping inputs with required outputs, although there's budget
// specified, it cannot be directly spent from these required outputs. Instead,
// we need to borrow budget from other inputs to make the sweep happen.
// There are two sources to borrow from: 1) other inputs, 2) wallet utxos. If
// we are calling this method, it means other inputs cannot cover the specified
// budget, so we need to borrow from wallet utxos.
//
// Return an error if there are not enough wallet inputs, and the budget set is
// set to its initial state by removing any wallet inputs added.
//
// NOTE: must be called with the wallet lock held via `WithCoinSelectLock`.
func (b *BudgetInputSet) AddWalletInputs(wallet Wallet) error {
// Retrieve wallet utxos. Only consider confirmed utxos to prevent
// problems around RBF rules for unconfirmed inputs. This currently
// ignores the configured coin selection strategy.
utxos, err := wallet.ListUnspentWitnessFromDefaultAccount(
1, math.MaxInt32,
)
if err != nil {
return fmt.Errorf("list unspent witness: %w", err)
}
// Sort the UTXOs by putting smaller values at the start of the slice
// to avoid locking large UTXO for sweeping.
//
// TODO(yy): add more choices to CoinSelectionStrategy and use the
// configured value here.
sort.Slice(utxos, func(i, j int) bool {
return utxos[i].Value < utxos[j].Value
})
// Add wallet inputs to the set until the specified budget is covered.
for _, utxo := range utxos {
err := b.addWalletInput(utxo)
if err != nil {
return err
}
// Return if we've reached the minimum output amount.
if !b.NeedWalletInput() {
return nil
}
}
// Exit if there are no inputs can contribute to the fees.
if !b.hasNormalInput() {
return ErrNotEnoughInputs
}
// If there's at least one input that can contribute to fees, we allow
// the sweep to continue, even though the full budget can't be met.
// Maybe later more wallet inputs will become available and we can add
// them if needed.
budget := b.Budget()
total, spendable := b.inputAmts()
log.Warnf("Not enough wallet UTXOs: need budget=%v, has spendable=%v, "+
"total=%v, missing at least %v, sweeping anyway...", budget,
spendable, total, budget-spendable)
return nil
}
// Budget returns the total budget of the set.
//
// NOTE: part of the InputSet interface.
func (b *BudgetInputSet) Budget() btcutil.Amount {
budget := btcutil.Amount(0)
for _, input := range b.inputs {
budget += input.params.Budget
}
// We'll also tack on the extra budget which will eventually be
// accounted for by the wallet txns when we're broadcasting.
return budget + b.extraBudget
}
// DeadlineHeight returns the deadline height of the set.
//
// NOTE: part of the InputSet interface.
func (b *BudgetInputSet) DeadlineHeight() int32 {
return b.deadlineHeight
}
// Inputs returns the inputs that should be used to create a tx.
//
// NOTE: part of the InputSet interface.
func (b *BudgetInputSet) Inputs() []input.Input {
inputs := make([]input.Input, 0, len(b.inputs))
for _, inp := range b.inputs {
inputs = append(inputs, inp.Input)
}
return inputs
}
// inputAmts returns two values for the set - the total input amount, and the
// spendable amount. Only the spendable amount can be used to pay the fees.
func (b *BudgetInputSet) inputAmts() (btcutil.Amount, btcutil.Amount) {
var (
totalAmt btcutil.Amount
spendableAmt btcutil.Amount
)
for _, inp := range b.inputs {
output := btcutil.Amount(inp.SignDesc().Output.Value)
totalAmt += output
if inp.RequiredTxOut() != nil {
continue
}
spendableAmt += output
}
return totalAmt, spendableAmt
}
// StartingFeeRate returns the max starting fee rate found in the inputs.
//
// NOTE: part of the InputSet interface.
func (b *BudgetInputSet) StartingFeeRate() fn.Option[chainfee.SatPerKWeight] {
maxFeeRate := chainfee.SatPerKWeight(0)
startingFeeRate := fn.None[chainfee.SatPerKWeight]()
for _, inp := range b.inputs {
feerate := inp.params.StartingFeeRate.UnwrapOr(0)
if feerate > maxFeeRate {
maxFeeRate = feerate
startingFeeRate = fn.Some(maxFeeRate)
}
}
return startingFeeRate
}
// Immediate returns whether the inputs should be swept immediately.
//
// NOTE: part of the InputSet interface.
func (b *BudgetInputSet) Immediate() bool {
for _, inp := range b.inputs {
// As long as one of the inputs is immediate, the whole set is
// immediate.
if inp.params.Immediate {
return true
}
}
return false
}
package sweep
import (
"errors"
"fmt"
"sort"
"strings"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)
var (
// DefaultMaxInputsPerTx specifies the default maximum number of inputs
// allowed in a single sweep tx. If more need to be swept, multiple txes
// are created and published.
DefaultMaxInputsPerTx = uint32(100)
// ErrLocktimeConflict is returned when inputs with different
// transaction nLockTime values are included in the same transaction.
//
// NOTE: due the SINGLE|ANYONECANPAY sighash flag, which is used in the
// second level success/timeout txns, only the txns sharing the same
// nLockTime can exist in the same tx.
ErrLocktimeConflict = errors.New("incompatible locktime")
)
// createSweepTx builds a signed tx spending the inputs to the given outputs,
// sending any leftover change to the change script.
func createSweepTx(inputs []input.Input, outputs []*wire.TxOut,
changePkScript []byte, currentBlockHeight uint32,
feeRate, maxFeeRate chainfee.SatPerKWeight,
signer input.Signer) (*wire.MsgTx, btcutil.Amount, error) {
inputs, estimator, err := getWeightEstimate(
inputs, outputs, feeRate, maxFeeRate, [][]byte{changePkScript},
)
if err != nil {
return nil, 0, err
}
txFee := estimator.feeWithParent()
var (
// Create the sweep transaction that we will be building. We
// use version 2 as it is required for CSV.
sweepTx = wire.NewMsgTx(2)
// Track whether any of the inputs require a certain locktime.
locktime = int32(-1)
// We keep track of total input amount, and required output
// amount to use for calculating the change amount below.
totalInput btcutil.Amount
requiredOutput btcutil.Amount
// We'll add the inputs as we go so we know the final ordering
// of inputs to sign.
idxs []input.Input
)
// We start by adding all inputs that commit to an output. We do this
// since the input and output index must stay the same for the
// signatures to be valid.
for _, o := range inputs {
if o.RequiredTxOut() == nil {
continue
}
idxs = append(idxs, o)
sweepTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: o.OutPoint(),
Sequence: o.BlocksToMaturity(),
})
sweepTx.AddTxOut(o.RequiredTxOut())
if lt, ok := o.RequiredLockTime(); ok {
// If another input commits to a different locktime,
// they cannot be combined in the same transaction.
if locktime != -1 && locktime != int32(lt) {
return nil, 0, ErrLocktimeConflict
}
locktime = int32(lt)
}
totalInput += btcutil.Amount(o.SignDesc().Output.Value)
requiredOutput += btcutil.Amount(o.RequiredTxOut().Value)
}
// Sum up the value contained in the remaining inputs, and add them to
// the sweep transaction.
for _, o := range inputs {
if o.RequiredTxOut() != nil {
continue
}
idxs = append(idxs, o)
sweepTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: o.OutPoint(),
Sequence: o.BlocksToMaturity(),
})
if lt, ok := o.RequiredLockTime(); ok {
if locktime != -1 && locktime != int32(lt) {
return nil, 0, ErrLocktimeConflict
}
locktime = int32(lt)
}
totalInput += btcutil.Amount(o.SignDesc().Output.Value)
}
// Add the outputs given, if any.
for _, o := range outputs {
sweepTx.AddTxOut(o)
requiredOutput += btcutil.Amount(o.Value)
}
if requiredOutput+txFee > totalInput {
return nil, 0, fmt.Errorf("insufficient input to create sweep "+
"tx: input_sum=%v, output_sum=%v", totalInput,
requiredOutput+txFee)
}
// The value remaining after the required output and fees, go to
// change. Not that this fee is what we would have to pay in case the
// sweep tx has a change output.
changeAmt := totalInput - requiredOutput - txFee
// We'll calculate the dust limit for the given changePkScript since it
// is variable.
changeLimit := lnwallet.DustLimitForSize(len(changePkScript))
// The txn will sweep the amount after fees to the pkscript generated
// above.
if changeAmt >= changeLimit {
sweepTx.AddTxOut(&wire.TxOut{
PkScript: changePkScript,
Value: int64(changeAmt),
})
} else {
log.Infof("Change amt %v below dustlimit %v, not adding "+
"change output", changeAmt, changeLimit)
// The dust amount is added to the fee as the miner will
// collect it.
txFee += changeAmt
}
// We'll default to using the current block height as locktime, if none
// of the inputs commits to a different locktime.
sweepTx.LockTime = currentBlockHeight
if locktime != -1 {
sweepTx.LockTime = uint32(locktime)
}
// Before signing the transaction, check to ensure that it meets some
// basic validity requirements.
//
// TODO(conner): add more control to sanity checks, allowing us to
// delay spending "problem" outputs, e.g. possibly batching with other
// classes if fees are too low.
btx := btcutil.NewTx(sweepTx)
if err := blockchain.CheckTransactionSanity(btx); err != nil {
return nil, 0, err
}
prevInputFetcher, err := input.MultiPrevOutFetcher(inputs)
if err != nil {
return nil, 0, fmt.Errorf("error creating prev input fetcher "+
"for hash cache: %v", err)
}
hashCache := txscript.NewTxSigHashes(sweepTx, prevInputFetcher)
// With all the inputs in place, use each output's unique input script
// function to generate the final witness required for spending.
addInputScript := func(idx int, tso input.Input) error {
inputScript, err := tso.CraftInputScript(
signer, sweepTx, hashCache, prevInputFetcher, idx,
)
if err != nil {
return err
}
sweepTx.TxIn[idx].Witness = inputScript.Witness
if len(inputScript.SigScript) != 0 {
sweepTx.TxIn[idx].SignatureScript =
inputScript.SigScript
}
return nil
}
for idx, inp := range idxs {
if err := addInputScript(idx, inp); err != nil {
return nil, 0, err
}
}
log.Debugf("Creating sweep transaction %v for %v inputs (%s) "+
"using %v, tx_weight=%v, tx_fee=%v, parents_count=%v, "+
"parents_fee=%v, parents_weight=%v, current_height=%v",
sweepTx.TxHash(), len(inputs),
inputTypeSummary(inputs), feeRate,
estimator.weight(), txFee,
len(estimator.parents), estimator.parentsFee,
estimator.parentsWeight, currentBlockHeight)
return sweepTx, txFee, nil
}
// getWeightEstimate returns a weight estimate for the given inputs.
// Additionally, it returns counts for the number of csv and cltv inputs.
func getWeightEstimate(inputs []input.Input, outputs []*wire.TxOut,
feeRate, maxFeeRate chainfee.SatPerKWeight,
outputPkScripts [][]byte) ([]input.Input, *weightEstimator, error) {
// We initialize a weight estimator so we can accurately asses the
// amount of fees we need to pay for this sweep transaction.
//
// TODO(roasbeef): can be more intelligent about buffering outputs to
// be more efficient on-chain.
weightEstimate := newWeightEstimator(feeRate, maxFeeRate)
// Our sweep transaction will always pay to the given set of outputs.
for _, o := range outputs {
weightEstimate.addOutput(o)
}
// If there is any leftover change after paying to the given outputs
// and required outputs, it will go to a single segwit p2wkh or p2tr
// address. This will be our change address, so ensure it contributes
// to our weight estimate. Note that if we have other outputs, we might
// end up creating a sweep tx without a change output. It is okay to
// add the change output to the weight estimate regardless, since the
// estimated fee will just be subtracted from this already dust output,
// and trimmed.
for _, outputPkScript := range outputPkScripts {
switch {
case txscript.IsPayToTaproot(outputPkScript):
weightEstimate.addP2TROutput()
case txscript.IsPayToWitnessScriptHash(outputPkScript):
weightEstimate.addP2WSHOutput()
case txscript.IsPayToWitnessPubKeyHash(outputPkScript):
weightEstimate.addP2WKHOutput()
case txscript.IsPayToPubKeyHash(outputPkScript):
weightEstimate.estimator.AddP2PKHOutput()
case txscript.IsPayToScriptHash(outputPkScript):
weightEstimate.estimator.AddP2SHOutput()
default:
// Unknown script type.
return nil, nil, fmt.Errorf("unknown script "+
"type: %x", outputPkScript)
}
}
// For each output, use its witness type to determine the estimate
// weight of its witness, and add it to the proper set of spendable
// outputs.
var sweepInputs []input.Input
for i := range inputs {
inp := inputs[i]
err := weightEstimate.add(inp)
if err != nil {
// TODO(yy): check if this is even possible? If so, we
// should return the error here instead of filtering!
log.Errorf("Failed to get weight estimate for "+
"input=%v, witnessType=%v: %v ", inp.OutPoint(),
inp.WitnessType(), err)
// Skip inputs for which no weight estimate can be
// given.
continue
}
// If this input comes with a committed output, add that as
// well.
if inp.RequiredTxOut() != nil {
weightEstimate.addOutput(inp.RequiredTxOut())
}
sweepInputs = append(sweepInputs, inp)
}
return sweepInputs, weightEstimate, nil
}
// inputSummary returns a string containing a human readable summary about the
// witness types of a list of inputs.
func inputTypeSummary(inputs []input.Input) string {
// Sort inputs by witness type.
sortedInputs := make([]input.Input, len(inputs))
copy(sortedInputs, inputs)
sort.Slice(sortedInputs, func(i, j int) bool {
return sortedInputs[i].WitnessType().String() <
sortedInputs[j].WitnessType().String()
})
var parts []string
for _, i := range sortedInputs {
part := fmt.Sprintf("%v (%v)", i.OutPoint(), i.WitnessType())
parts = append(parts, part)
}
return strings.Join(parts, "\n")
}
package sweep
import (
"errors"
"fmt"
"maps"
"math"
"slices"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
)
var (
// ErrNoFeePreference is returned when we attempt to satisfy a sweep
// request from a client whom did not specify a fee preference.
ErrNoFeePreference = errors.New("no fee preference specified")
// ErrFeePreferenceConflict is returned when both a fee rate and a conf
// target is set for a fee preference.
ErrFeePreferenceConflict = errors.New("fee preference conflict")
// ErrUnknownUTXO is returned when creating a sweeping tx using an UTXO
// that's unknown to the wallet.
ErrUnknownUTXO = errors.New("unknown utxo")
)
// FeePreference defines an interface that allows the caller to specify how the
// fee rate should be handled. Depending on the implementation, the fee rate
// can either be specified directly, or via a conf target which relies on the
// chain backend(`bitcoind`) to give a fee estimation, or a customized fee
// function which handles fee calculation based on the specified
// urgency(deadline).
type FeePreference interface {
// String returns a human-readable string of the fee preference.
String() string
// Estimate takes a fee estimator and a max allowed fee rate and
// returns a fee rate for the given fee preference. It ensures that the
// fee rate respects the bounds of the relay fee and the specified max
// fee rates.
Estimate(chainfee.Estimator,
chainfee.SatPerKWeight) (chainfee.SatPerKWeight, error)
}
// FeeEstimateInfo allows callers to express their time value for inclusion of
// a transaction into a block via either a confirmation target, or a fee rate.
type FeeEstimateInfo struct {
// ConfTarget if non-zero, signals a fee preference expressed in the
// number of desired blocks between first broadcast, and confirmation.
ConfTarget uint32
// FeeRate if non-zero, signals a fee pre fence expressed in the fee
// rate expressed in sat/kw for a particular transaction.
FeeRate chainfee.SatPerKWeight
}
// Compile-time constraint to ensure FeeEstimateInfo implements FeePreference.
var _ FeePreference = (*FeeEstimateInfo)(nil)
// String returns a human-readable string of the fee preference.
func (f FeeEstimateInfo) String() string {
if f.ConfTarget != 0 {
return fmt.Sprintf("%v blocks", f.ConfTarget)
}
return f.FeeRate.String()
}
// Estimate returns a fee rate for the given fee preference. It ensures that
// the fee rate respects the bounds of the relay fee and the max fee rates, if
// specified.
func (f FeeEstimateInfo) Estimate(estimator chainfee.Estimator,
maxFeeRate chainfee.SatPerKWeight) (chainfee.SatPerKWeight, error) {
var (
feeRate chainfee.SatPerKWeight
err error
)
switch {
// Ensure a type of fee preference is specified to prevent using a
// default below.
case f.FeeRate == 0 && f.ConfTarget == 0:
return 0, ErrNoFeePreference
// If both values are set, then we'll return an error as we require a
// strict directive.
case f.FeeRate != 0 && f.ConfTarget != 0:
return 0, ErrFeePreferenceConflict
// If the target number of confirmations is set, then we'll use that to
// consult our fee estimator for an adequate fee.
case f.ConfTarget != 0:
feeRate, err = estimator.EstimateFeePerKW((f.ConfTarget))
if err != nil {
return 0, fmt.Errorf("unable to query fee "+
"estimator: %w", err)
}
// If a manual sat/kw fee rate is set, then we'll use that directly.
// We'll need to convert it to sat/kw as this is what we use
// internally.
case f.FeeRate != 0:
feeRate = f.FeeRate
// Because the user can specify 1 sat/vByte on the RPC
// interface, which corresponds to 250 sat/kw, we need to bump
// that to the minimum "safe" fee rate which is 253 sat/kw.
if feeRate == chainfee.AbsoluteFeePerKwFloor {
log.Infof("Manual fee rate input of %d sat/kw is "+
"too low, using %d sat/kw instead", feeRate,
chainfee.FeePerKwFloor)
feeRate = chainfee.FeePerKwFloor
}
}
// Get the relay fee as the min fee rate.
minFeeRate := estimator.RelayFeePerKW()
// If that bumped fee rate of at least 253 sat/kw is still lower than
// the relay fee rate, we return an error to let the user know. Note
// that "Relay fee rate" may mean slightly different things depending
// on the backend. For bitcoind, it is effectively max(relay fee, min
// mempool fee).
if feeRate < minFeeRate {
return 0, fmt.Errorf("%w: got %v, minimum is %v",
ErrFeePreferenceTooLow, feeRate, minFeeRate)
}
// If a maxFeeRate is specified and the estimated fee rate is above the
// maximum allowed fee rate, default to the max fee rate.
if maxFeeRate != 0 && feeRate > maxFeeRate {
log.Warnf("Estimated fee rate %v exceeds max allowed fee "+
"rate %v, using max fee rate instead", feeRate,
maxFeeRate)
return maxFeeRate, nil
}
return feeRate, nil
}
// UtxoSource is an interface that allows a caller to access a source of UTXOs
// to use when crafting sweep transactions.
type UtxoSource interface {
// ListUnspentWitnessFromDefaultAccount returns all UTXOs from the
// default wallet account that have between minConfs and maxConfs
// number of confirmations.
ListUnspentWitnessFromDefaultAccount(minConfs, maxConfs int32) (
[]*lnwallet.Utxo, error)
}
// CoinSelectionLocker is an interface that allows the caller to perform an
// operation, which is synchronized with all coin selection attempts. This can
// be used when an operation requires that all coin selection operations cease
// forward progress. Think of this as an exclusive lock on coin selection
// operations.
type CoinSelectionLocker interface {
// WithCoinSelectLock will execute the passed function closure in a
// synchronized manner preventing any coin selection operations from
// proceeding while the closure is executing. This can be seen as the
// ability to execute a function closure under an exclusive coin
// selection lock.
WithCoinSelectLock(func() error) error
}
// OutputLeaser allows a caller to lease/release an output. When leased, the
// outputs shouldn't be used for any sort of channel funding or coin selection.
// Leased outputs are expected to be persisted between restarts.
type OutputLeaser interface {
// LeaseOutput leases a target output, rendering it unusable for coin
// selection.
LeaseOutput(i wtxmgr.LockID, o wire.OutPoint, d time.Duration) (
time.Time, error)
// ReleaseOutput releases a target output, allowing it to be used for
// coin selection once again.
ReleaseOutput(i wtxmgr.LockID, o wire.OutPoint) error
}
// WalletSweepPackage is a package that gives the caller the ability to sweep
// relevant funds from a wallet in a single transaction. We also package a
// function closure that allows one to abort the operation.
type WalletSweepPackage struct {
// SweepTx is a fully signed, and valid transaction that is broadcast,
// will sweep ALL relevant confirmed coins in the wallet with a single
// transaction.
SweepTx *wire.MsgTx
// CancelSweepAttempt allows the caller to cancel the sweep attempt.
//
// NOTE: If the sweeping transaction isn't or cannot be broadcast, then
// this closure MUST be called, otherwise all selected utxos will be
// unable to be used.
CancelSweepAttempt func()
}
// DeliveryAddr is a pair of (address, amount) used to craft a transaction
// paying to more than one specified address.
type DeliveryAddr struct {
// Addr is the address to pay to.
Addr btcutil.Address
// Amt is the amount to pay to the given address.
Amt btcutil.Amount
}
// CraftSweepAllTx attempts to craft a WalletSweepPackage which will allow the
// caller to sweep ALL funds in ALL or SELECT outputs within the wallet to a
// list of outputs. Any leftover amount after these outputs and transaction fee,
// is sent to a single output, as specified by the change address. The sweep
// transaction will be crafted with the target fee rate, and will use the
// utxoSource and outputLeaser as sources for wallet funds.
func CraftSweepAllTx(feeRate, maxFeeRate chainfee.SatPerKWeight,
blockHeight uint32, deliveryAddrs []DeliveryAddr,
changeAddr btcutil.Address, coinSelectLocker CoinSelectionLocker,
utxoSource UtxoSource, outputLeaser OutputLeaser,
signer input.Signer, minConfs int32,
selectUtxos fn.Set[wire.OutPoint]) (*WalletSweepPackage, error) {
// TODO(roasbeef): turn off ATPL as well when available?
var outputsForSweep []*lnwallet.Utxo
// We'll make a function closure up front that allows us to unlock all
// selected outputs to ensure that they become available again in the
// case of an error after the outputs have been locked, but before we
// can actually craft a sweeping transaction.
unlockOutputs := func() {
for _, utxo := range outputsForSweep {
// Log the error but continue since we're already
// handling an error.
err := outputLeaser.ReleaseOutput(
chanfunding.LndInternalLockID, utxo.OutPoint,
)
if err != nil {
log.Warnf("Failed to release UTXO %s (%v))",
utxo.OutPoint, err)
}
}
}
// Next, we'll use the coinSelectLocker to ensure that no coin
// selection takes place while we fetch and lock outputs in the
// wallet. Otherwise, it may be possible for a new funding flow to lock
// an output while we fetch the set of unspent witnesses.
err := coinSelectLocker.WithCoinSelectLock(func() error {
log.Trace("[WithCoinSelectLock] entered the lock")
// Now that we can be sure that no other coin selection
// operations are going on, we can grab a clean snapshot of the
// current UTXO state of the wallet.
utxos, err := utxoSource.ListUnspentWitnessFromDefaultAccount(
minConfs, math.MaxInt32,
)
if err != nil {
return err
}
log.Trace("[WithCoinSelectLock] finished fetching UTXOs")
// Use select utxos, if provided.
if len(selectUtxos) > 0 {
utxos, err = fetchUtxosFromOutpoints(
utxos, selectUtxos.ToSlice(),
)
if err != nil {
return err
}
}
// We'll now lock each UTXO to ensure that other callers don't
// attempt to use these UTXOs in transactions while we're
// crafting out sweep all transaction.
for _, utxo := range utxos {
log.Tracef("[WithCoinSelectLock] leasing utxo: %v",
utxo.OutPoint)
_, err = outputLeaser.LeaseOutput(
chanfunding.LndInternalLockID, utxo.OutPoint,
chanfunding.DefaultLockDuration,
)
if err != nil {
return err
}
}
log.Trace("[WithCoinSelectLock] exited the lock")
outputsForSweep = append(outputsForSweep, utxos...)
return nil
})
if err != nil {
// If we failed at all, we'll unlock any outputs selected just
// in case we had any lingering outputs.
unlockOutputs()
return nil, fmt.Errorf("unable to fetch+lock wallet utxos: %w",
err)
}
// Now that we've locked all the potential outputs to sweep, we'll
// assemble an input for each of them, so we can hand it off to the
// sweeper to generate and sign a transaction for us.
var inputsToSweep []input.Input
for _, output := range outputsForSweep {
// As we'll be signing for outputs under control of the wallet,
// we only need to populate the output value and output script.
// The rest of the items will be populated internally within
// the sweeper via the witness generation function.
signDesc := &input.SignDescriptor{
Output: &wire.TxOut{
PkScript: output.PkScript,
Value: int64(output.Value),
},
HashType: txscript.SigHashAll,
}
pkScript := output.PkScript
// Based on the output type, we'll map it to the proper witness
// type so we can generate the set of input scripts needed to
// sweep the output.
var witnessType input.WitnessType
switch output.AddressType {
// If this is a p2wkh output, then we'll assume it's a witness
// key hash witness type.
case lnwallet.WitnessPubKey:
witnessType = input.WitnessKeyHash
// If this is a p2sh output, then as since it's under control
// of the wallet, we'll assume it's a nested p2sh output.
case lnwallet.NestedWitnessPubKey:
witnessType = input.NestedWitnessKeyHash
case lnwallet.TaprootPubkey:
witnessType = input.TaprootPubKeySpend
signDesc.HashType = txscript.SigHashDefault
// All other output types we count as unknown and will fail to
// sweep.
default:
unlockOutputs()
return nil, fmt.Errorf("unable to sweep coins, "+
"unknown script: %x", pkScript[:])
}
// Now that we've constructed the items required, we'll make an
// input which can be passed to the sweeper for ultimate
// sweeping.
input := input.MakeBaseInput(
&output.OutPoint, witnessType, signDesc, 0, nil,
)
inputsToSweep = append(inputsToSweep, &input)
}
// Create a list of TxOuts from the given delivery addresses.
var txOuts []*wire.TxOut
for _, d := range deliveryAddrs {
pkScript, err := txscript.PayToAddrScript(d.Addr)
if err != nil {
unlockOutputs()
return nil, err
}
txOuts = append(txOuts, &wire.TxOut{
PkScript: pkScript,
Value: int64(d.Amt),
})
}
// Next, we'll convert the change addr to a pkScript that we can use
// to create the sweep transaction.
changePkScript, err := txscript.PayToAddrScript(changeAddr)
if err != nil {
unlockOutputs()
return nil, err
}
// Finally, we'll ask the sweeper to craft a sweep transaction which
// respects our fee preference and targets all the UTXOs of the wallet.
sweepTx, _, err := createSweepTx(
inputsToSweep, txOuts, changePkScript, blockHeight,
feeRate, maxFeeRate, signer,
)
if err != nil {
unlockOutputs()
return nil, err
}
return &WalletSweepPackage{
SweepTx: sweepTx,
CancelSweepAttempt: unlockOutputs,
}, nil
}
// fetchUtxosFromOutpoints returns UTXOs for given outpoints. Errors if any
// outpoint is not in the passed slice of utxos.
func fetchUtxosFromOutpoints(utxos []*lnwallet.Utxo,
outpoints []wire.OutPoint) ([]*lnwallet.Utxo, error) {
lookup := fn.SliceToMap(utxos, func(utxo *lnwallet.Utxo) wire.OutPoint {
return utxo.OutPoint
}, func(utxo *lnwallet.Utxo) *lnwallet.Utxo {
return utxo
})
subMap, err := fn.NewSubMap(lookup, outpoints)
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrUnknownUTXO, err.Error())
}
fetchedUtxos := slices.Collect(maps.Values(subMap))
return fetchedUtxos, nil
}
package sweep
import (
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)
// weightEstimator wraps a standard weight estimator instance and adds to that
// support for child-pays-for-parent.
type weightEstimator struct {
estimator input.TxWeightEstimator
feeRate chainfee.SatPerKWeight
parents map[chainhash.Hash]struct{}
parentsFee btcutil.Amount
parentsWeight lntypes.WeightUnit
// maxFeeRate is the max allowed fee rate configured by the user.
maxFeeRate chainfee.SatPerKWeight
}
// newWeightEstimator instantiates a new sweeper weight estimator.
func newWeightEstimator(
feeRate, maxFeeRate chainfee.SatPerKWeight) *weightEstimator {
return &weightEstimator{
feeRate: feeRate,
maxFeeRate: maxFeeRate,
parents: make(map[chainhash.Hash]struct{}),
}
}
// add adds the weight of the given input to the weight estimate.
func (w *weightEstimator) add(inp input.Input) error {
// If there is a parent tx, add the parent's fee and weight.
w.tryAddParent(inp)
wt := inp.WitnessType()
return wt.AddWeightEstimation(&w.estimator)
}
// tryAddParent examines the input and updates parent tx totals if required for
// cpfp.
func (w *weightEstimator) tryAddParent(inp input.Input) {
// Get unconfirmed parent info from the input.
unconfParent := inp.UnconfParent()
// If there is no parent, there is nothing to add.
if unconfParent == nil {
return
}
// If we've already accounted for the parent tx, don't do it
// again. This can happens when two outputs of the parent tx are
// included in the same sweep tx.
parentHash := inp.OutPoint().Hash
if _, ok := w.parents[parentHash]; ok {
return
}
// Calculate parent fee rate.
parentFeeRate := chainfee.SatPerKWeight(unconfParent.Fee) * 1000 /
chainfee.SatPerKWeight(unconfParent.Weight)
// Ignore parents that pay at least the fee rate of this transaction.
// Parent pays for child is not happening.
if parentFeeRate >= w.feeRate {
return
}
// Include parent.
w.parents[parentHash] = struct{}{}
w.parentsFee += unconfParent.Fee
w.parentsWeight += unconfParent.Weight
}
// addP2WKHOutput updates the weight estimate to account for an additional
// native P2WKH output.
func (w *weightEstimator) addP2WKHOutput() {
w.estimator.AddP2WKHOutput()
}
// addP2TROutput updates the weight estimate to account for an additional native
// SegWit v1 P2TR output.
func (w *weightEstimator) addP2TROutput() {
w.estimator.AddP2TROutput()
}
// addP2WSHOutput updates the weight estimate to account for an additional
// segwit v0 P2WSH output.
func (w *weightEstimator) addP2WSHOutput() {
w.estimator.AddP2WSHOutput()
}
// addOutput updates the weight estimate to account for the known
// output given.
func (w *weightEstimator) addOutput(txOut *wire.TxOut) {
w.estimator.AddTxOutput(txOut)
}
// weight gets the estimated weight of the transaction.
func (w *weightEstimator) weight() lntypes.WeightUnit {
return w.estimator.Weight()
}
// fee returns the tx fee to use for the aggregated inputs and outputs, which
// is different from feeWithParent as it doesn't take into account unconfirmed
// parent transactions.
func (w *weightEstimator) fee() btcutil.Amount {
// Calculate the weight of the transaction.
weight := w.estimator.Weight()
// Calculate the fee.
fee := w.feeRate.FeeForWeight(weight)
return fee
}
// feeWithParent returns the tx fee to use for the aggregated inputs and
// outputs, taking into account unconfirmed parent transactions (cpfp).
func (w *weightEstimator) feeWithParent() btcutil.Amount {
// Calculate fee and weight for just this tx.
childWeight := w.estimator.Weight()
// Add combined weight of unconfirmed parent txes.
totalWeight := childWeight + w.parentsWeight
// Subtract fee already paid by parents.
fee := w.feeRate.FeeForWeight(totalWeight) - w.parentsFee
// Clamp the fee to what would be required if no parent txes were paid
// for. This is to make sure no rounding errors can get us into trouble.
childFee := w.feeRate.FeeForWeight(childWeight)
if childFee > fee {
fee = childFee
}
// Exit early if maxFeeRate is not set.
if w.maxFeeRate == 0 {
return fee
}
// Clamp the fee to the max fee rate.
maxFee := w.maxFeeRate.FeeForWeight(childWeight)
if fee > maxFee {
// Calculate the effective fee rate for logging.
childFeeRate := chainfee.SatPerKWeight(
fee * 1000 / btcutil.Amount(childWeight),
)
log.Warnf("Child fee rate %v exceeds max allowed fee rate %v, "+
"returning fee %v instead of %v", childFeeRate,
w.maxFeeRate, maxFee, fee)
fee = maxFee
}
return fee
}
package lnd
import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/lnwallet"
)
// sweeperWallet is a wrapper around the LightningWallet that implements the
// sweeper's Wallet interface.
type sweeperWallet struct {
*lnwallet.LightningWallet
}
// newSweeperWallet creates a new sweeper wallet from the given
// LightningWallet.
func newSweeperWallet(w *lnwallet.LightningWallet) *sweeperWallet {
return &sweeperWallet{
LightningWallet: w,
}
}
// CancelRebroadcast cancels the rebroadcast of the given transaction.
func (s *sweeperWallet) CancelRebroadcast(txid chainhash.Hash) {
// For neutrino, we don't config the rebroadcaster for the wallet as it
// manages the rebroadcasting logic in neutrino itself.
if s.Cfg.Rebroadcaster != nil {
s.Cfg.Rebroadcaster.MarkAsConfirmed(txid)
}
}
package lnd
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net"
"net/http"
"os"
"time"
"github.com/lightningnetwork/lnd/cert"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnencrypt"
"github.com/lightningnetwork/lnd/lnrpc"
"golang.org/x/crypto/acme/autocert"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
const (
// modifyFilePermissons is the file permission used for writing
// encrypted tls files.
modifyFilePermissions = 0600
// validityHours is the number of hours the ephemeral tls certificate
// will be valid, if encrypting tls certificates is turned on.
validityHours = 24
)
var (
// privateKeyPrefix is the prefix to a plaintext TLS key.
// It should match these two key formats:
// - `-----BEGIN PRIVATE KEY-----` (PKCS8).
// - `-----BEGIN EC PRIVATE KEY-----` (SEC1/rfc5915, the legacy format).
privateKeyPrefix = []byte("-----BEGIN ")
)
// TLSManagerCfg houses a set of values and methods that is passed to the
// TLSManager for it to properly manage LND's TLS options.
type TLSManagerCfg struct {
TLSCertPath string
TLSKeyPath string
TLSEncryptKey bool
TLSExtraIPs []string
TLSExtraDomains []string
TLSAutoRefresh bool
TLSDisableAutofill bool
TLSCertDuration time.Duration
LetsEncryptDir string
LetsEncryptDomain string
LetsEncryptListen string
DisableRestTLS bool
HTTPHeaderTimeout time.Duration
}
// TLSManager generates/renews a TLS cert/key pair when needed. When required,
// it encrypts the TLS key. It also returns the certificate configuration
// options needed for gRPC and REST.
type TLSManager struct {
cfg *TLSManagerCfg
// tlsReloader is able to reload the certificate with the
// GetCertificate function. In getConfig, tlsCfg.GetCertificate is
// pointed towards t.tlsReloader.GetCertificateFunc(). When
// TLSReloader's AttemptReload is called, the cert that tlsReloader
// holds is changed, in turn changing the cert data
// tlsCfg.GetCertificate will return.
tlsReloader *cert.TLSReloader
// These options are only used if we're currently using an ephemeral
// TLS certificate, used when we're encrypting the TLS key.
ephemeralKey []byte
ephemeralCert []byte
ephemeralCertPath string
}
// NewTLSManager returns a reference to a new TLSManager.
func NewTLSManager(cfg *TLSManagerCfg) *TLSManager {
return &TLSManager{
cfg: cfg,
}
}
// getConfig returns a TLS configuration for the gRPC server and credentials
// and a proxy destination for the REST reverse proxy.
func (t *TLSManager) getConfig() ([]grpc.ServerOption, []grpc.DialOption,
func(net.Addr) (net.Listener, error), func(), error) {
var (
keyBytes, certBytes []byte
err error
)
if t.ephemeralKey != nil {
keyBytes = t.ephemeralKey
certBytes = t.ephemeralCert
} else {
certBytes, keyBytes, err = cert.GetCertBytesFromPath(
t.cfg.TLSCertPath, t.cfg.TLSKeyPath,
)
if err != nil {
return nil, nil, nil, nil, err
}
}
certData, _, err := cert.LoadCertFromBytes(certBytes, keyBytes)
if err != nil {
return nil, nil, nil, nil, err
}
if t.tlsReloader == nil {
tlsr, err := cert.NewTLSReloader(certBytes, keyBytes)
if err != nil {
return nil, nil, nil, nil, err
}
t.tlsReloader = tlsr
}
tlsCfg := cert.TLSConfFromCert(certData)
tlsCfg.GetCertificate = t.tlsReloader.GetCertificateFunc()
// If Let's Encrypt is enabled, we need to set up the autocert manager
// and override the TLS config's GetCertificate function.
cleanUp := t.setUpLetsEncrypt(&certData, tlsCfg)
// Now that we know that we have a certificate, let's generate the
// required config options.
serverCreds := credentials.NewTLS(tlsCfg)
serverOpts := []grpc.ServerOption{grpc.Creds(serverCreds)}
// For our REST dial options, we skip TLS verification, and we also
// increase the max message size that we'll decode to allow clients to
// hit endpoints which return more data such as the DescribeGraph call.
// We set this to 200MiB atm. Should be the same value as maxMsgRecvSize
// in cmd/lncli/main.go.
restDialOpts := []grpc.DialOption{
// We are forwarding the requests directly to the address of our
// own local listener. To not need to mess with the TLS
// certificate (which might be tricky if we're using Let's
// Encrypt or if the ephemeral tls cert is being used), we just
// skip the certificate verification. Injecting a malicious
// hostname into the listener address will result in an error
// on startup so this should be quite safe.
grpc.WithTransportCredentials(credentials.NewTLS(
&tls.Config{InsecureSkipVerify: true},
)),
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(lnrpc.MaxGrpcMsgSize),
),
}
// Return a function closure that can be used to listen on a given
// address with the current TLS config.
restListen := func(addr net.Addr) (net.Listener, error) {
// For restListen we will call ListenOnAddress if TLS is
// disabled.
if t.cfg.DisableRestTLS {
return lncfg.ListenOnAddress(addr)
}
return lncfg.TLSListenOnAddress(addr, tlsCfg)
}
return serverOpts, restDialOpts, restListen, cleanUp, nil
}
// generateOrRenewCert generates a new TLS certificate if we're not using one
// yet or renews it if it's outdated.
func (t *TLSManager) generateOrRenewCert() (*tls.Config, error) {
// Generete a TLS pair if we don't have one yet.
var emptyKeyRing keychain.SecretKeyRing
err := t.generateCertPair(emptyKeyRing)
if err != nil {
return nil, err
}
certData, parsedCert, err := cert.LoadCert(
t.cfg.TLSCertPath, t.cfg.TLSKeyPath,
)
if err != nil {
return nil, err
}
// Check to see if the certificate needs to be renewed. If it does, we
// return the newly generated certificate data instead.
reloadedCertData, err := t.maintainCert(parsedCert)
if err != nil {
return nil, err
}
if reloadedCertData != nil {
certData = *reloadedCertData
}
tlsCfg := cert.TLSConfFromCert(certData)
return tlsCfg, nil
}
// generateCertPair creates and writes a TLS pair to disk if the pair
// doesn't exist yet. If the TLSEncryptKey setting is on, and a plaintext key
// is already written to disk, this function overwrites the plaintext key with
// the encrypted form.
func (t *TLSManager) generateCertPair(keyRing keychain.SecretKeyRing) error {
// Ensure we create TLS key and certificate if they don't exist.
if lnrpc.FileExists(t.cfg.TLSCertPath) ||
lnrpc.FileExists(t.cfg.TLSKeyPath) {
// Handle discrepencies related to the TLSEncryptKey setting.
return t.ensureEncryption(keyRing)
}
rpcsLog.Infof("Generating TLS certificates...")
certBytes, keyBytes, err := cert.GenCertPair(
"lnd autogenerated cert", t.cfg.TLSExtraIPs,
t.cfg.TLSExtraDomains, t.cfg.TLSDisableAutofill,
t.cfg.TLSCertDuration,
)
if err != nil {
return err
}
if t.cfg.TLSEncryptKey {
var b bytes.Buffer
e, err := lnencrypt.KeyRingEncrypter(keyRing)
if err != nil {
return fmt.Errorf("unable to create "+
"encrypt key %v", err)
}
err = e.EncryptPayloadToWriter(
keyBytes, &b,
)
if err != nil {
return err
}
keyBytes = b.Bytes()
}
err = cert.WriteCertPair(
t.cfg.TLSCertPath, t.cfg.TLSKeyPath, certBytes, keyBytes,
)
rpcsLog.Infof("Done generating TLS certificates")
return err
}
// ensureEncryption takes a look at a couple of things:
// 1) If the TLS key is in plaintext, but TLSEncryptKey is set, we need to
// encrypt the file and rewrite it to disk.
// 2) On the flip side, if TLSEncryptKey is not set, but the key on disk
// is encrypted, we need to error out and warn the user.
func (t *TLSManager) ensureEncryption(keyRing keychain.SecretKeyRing) error {
_, keyBytes, err := cert.GetCertBytesFromPath(
t.cfg.TLSCertPath, t.cfg.TLSKeyPath,
)
if err != nil {
return err
}
if t.cfg.TLSEncryptKey && bytes.HasPrefix(keyBytes, privateKeyPrefix) {
var b bytes.Buffer
e, err := lnencrypt.KeyRingEncrypter(keyRing)
if err != nil {
return fmt.Errorf("unable to generate encrypt key %w",
err)
}
err = e.EncryptPayloadToWriter(keyBytes, &b)
if err != nil {
return err
}
err = os.WriteFile(
t.cfg.TLSKeyPath, b.Bytes(), modifyFilePermissions,
)
if err != nil {
return err
}
}
// If the private key is encrypted but the user didn't pass
// --tlsencryptkey we error out. This is because the wallet is not
// unlocked yet and we don't have access to the keys yet for decryption.
if !t.cfg.TLSEncryptKey && !bytes.HasPrefix(keyBytes,
privateKeyPrefix) {
ltndLog.Errorf("The TLS private key is encrypted on disk.")
return errors.New("the TLS key is encrypted but the " +
"--tlsencryptkey flag is not passed. Please either " +
"restart lnd with the --tlsencryptkey flag or delete " +
"the TLS files for regeneration")
}
return nil
}
// decryptTLSKeyBytes decrypts the TLS key.
func decryptTLSKeyBytes(keyRing keychain.SecretKeyRing,
encryptedData []byte) ([]byte, error) {
reader := bytes.NewReader(encryptedData)
encrypter, err := lnencrypt.KeyRingEncrypter(keyRing)
if err != nil {
return nil, err
}
plaintext, err := encrypter.DecryptPayloadFromReader(
reader,
)
if err != nil {
return nil, err
}
return plaintext, nil
}
// maintainCert checks if the certificate IP and domains matches the config,
// and renews the certificate if either this data is outdated or the
// certificate is expired.
func (t *TLSManager) maintainCert(
parsedCert *x509.Certificate) (*tls.Certificate, error) {
// We check whether the certificate we have on disk match the IPs and
// domains specified by the config. If the extra IPs or domains have
// changed from when the certificate was created, we will refresh the
// certificate if auto refresh is active.
refresh := false
var err error
if t.cfg.TLSAutoRefresh {
refresh, err = cert.IsOutdated(
parsedCert, t.cfg.TLSExtraIPs,
t.cfg.TLSExtraDomains, t.cfg.TLSDisableAutofill,
)
if err != nil {
return nil, err
}
}
// If the certificate expired or it was outdated, delete it and the TLS
// key and generate a new pair.
if !time.Now().After(parsedCert.NotAfter) && !refresh {
return nil, nil
}
ltndLog.Info("TLS certificate is expired or outdated, " +
"generating a new one")
err = os.Remove(t.cfg.TLSCertPath)
if err != nil {
return nil, err
}
err = os.Remove(t.cfg.TLSKeyPath)
if err != nil {
return nil, err
}
rpcsLog.Infof("Renewing TLS certificates...")
certBytes, keyBytes, err := cert.GenCertPair(
"lnd autogenerated cert", t.cfg.TLSExtraIPs,
t.cfg.TLSExtraDomains, t.cfg.TLSDisableAutofill,
t.cfg.TLSCertDuration,
)
if err != nil {
return nil, err
}
err = cert.WriteCertPair(
t.cfg.TLSCertPath, t.cfg.TLSKeyPath, certBytes, keyBytes,
)
if err != nil {
return nil, err
}
rpcsLog.Infof("Done renewing TLS certificates")
// Reload the certificate data.
reloadedCertData, _, err := cert.LoadCert(
t.cfg.TLSCertPath, t.cfg.TLSKeyPath,
)
return &reloadedCertData, err
}
// setUpLetsEncrypt automatically generates a Let's Encrypt certificate if the
// option is set.
func (t *TLSManager) setUpLetsEncrypt(certData *tls.Certificate,
tlsCfg *tls.Config) func() {
// If Let's Encrypt is enabled, instantiate autocert to request/renew
// the certificates.
cleanUp := func() {}
if t.cfg.LetsEncryptDomain == "" {
return cleanUp
}
ltndLog.Infof("Using Let's Encrypt certificate for domain %v",
t.cfg.LetsEncryptDomain)
manager := autocert.Manager{
Cache: autocert.DirCache(t.cfg.LetsEncryptDir),
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(
t.cfg.LetsEncryptDomain,
),
}
srv := &http.Server{
Addr: t.cfg.LetsEncryptListen,
Handler: manager.HTTPHandler(nil),
ReadHeaderTimeout: t.cfg.HTTPHeaderTimeout,
}
shutdownCompleted := make(chan struct{})
cleanUp = func() {
err := srv.Shutdown(context.Background())
if err != nil {
ltndLog.Errorf("Autocert listener shutdown "+
" error: %v", err)
return
}
<-shutdownCompleted
ltndLog.Infof("Autocert challenge listener stopped")
}
go func() {
ltndLog.Infof("Autocert challenge listener started "+
"at %v", t.cfg.LetsEncryptListen)
err := srv.ListenAndServe()
if err != http.ErrServerClosed {
ltndLog.Errorf("autocert http: %v", err)
}
close(shutdownCompleted)
}()
getCertificate := func(h *tls.ClientHelloInfo) (
*tls.Certificate, error) {
lecert, err := manager.GetCertificate(h)
if err != nil {
ltndLog.Errorf("GetCertificate: %v", err)
return certData, nil
}
return lecert, err
}
// The self-signed tls.cert remains available as fallback.
tlsCfg.GetCertificate = getCertificate
return cleanUp
}
// SetCertificateBeforeUnlock takes care of loading the certificate before
// the wallet is unlocked. If the TLSEncryptKey setting is on, we need to
// generate an ephemeral certificate we're able to use until the wallet is
// unlocked and a new TLS pair can be encrypted to disk. Otherwise we can
// process the certificate normally.
func (t *TLSManager) SetCertificateBeforeUnlock() ([]grpc.ServerOption,
[]grpc.DialOption, func(net.Addr) (net.Listener, error), func(),
error) {
if t.cfg.TLSEncryptKey {
_, err := t.loadEphemeralCertificate()
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("unable to load "+
"ephemeral certificate: %v", err)
}
} else {
_, err := t.generateOrRenewCert()
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("unable to "+
"generate or renew TLS certificate: %v", err)
}
}
serverOpts, restDialOpts, restListen, cleanUp, err := t.getConfig()
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("unable to load TLS "+
"credentials: %v", err)
}
return serverOpts, restDialOpts, restListen, cleanUp, nil
}
// loadEphemeralCertificate creates and loads the ephemeral certificate which
// is used temporarily for secure communications before the wallet is unlocked.
func (t *TLSManager) loadEphemeralCertificate() ([]byte, error) {
rpcsLog.Infof("Generating ephemeral TLS certificates...")
tmpValidity := validityHours * time.Hour
// Append .tmp to the end of the cert for differentiation.
tmpCertPath := t.cfg.TLSCertPath + ".tmp"
// Pass in a blank string for the key path so the
// function doesn't write them to disk.
certBytes, keyBytes, err := cert.GenCertPair(
"lnd ephemeral autogenerated cert", t.cfg.TLSExtraIPs,
t.cfg.TLSExtraDomains, t.cfg.TLSDisableAutofill, tmpValidity,
)
if err != nil {
return nil, err
}
t.setEphemeralSettings(keyBytes, certBytes)
err = cert.WriteCertPair(tmpCertPath, "", certBytes, keyBytes)
if err != nil {
return nil, err
}
rpcsLog.Infof("Done generating ephemeral TLS certificates")
return keyBytes, nil
}
// LoadPermanentCertificate deletes the ephemeral certificate file and
// generates a new one with the real keyring.
func (t *TLSManager) LoadPermanentCertificate(
keyRing keychain.SecretKeyRing) error {
if !t.cfg.TLSEncryptKey {
return nil
}
tmpCertPath := t.cfg.TLSCertPath + ".tmp"
err := os.Remove(tmpCertPath)
if err != nil {
ltndLog.Warn("Unable to delete temp cert at %v",
tmpCertPath)
}
err = t.generateCertPair(keyRing)
if err != nil {
return err
}
certBytes, encryptedKeyBytes, err := cert.GetCertBytesFromPath(
t.cfg.TLSCertPath, t.cfg.TLSKeyPath,
)
if err != nil {
return err
}
reader := bytes.NewReader(encryptedKeyBytes)
e, err := lnencrypt.KeyRingEncrypter(keyRing)
if err != nil {
return fmt.Errorf("unable to generate encrypt key %w",
err)
}
keyBytes, err := e.DecryptPayloadFromReader(reader)
if err != nil {
return err
}
// Switch the server's TLS certificate to the persistent one. By
// changing the cert data the TLSReloader points to,
err = t.tlsReloader.AttemptReload(certBytes, keyBytes)
if err != nil {
return err
}
t.deleteEphemeralSettings()
return nil
}
// setEphemeralSettings sets the TLSManager settings needed when an ephemeral
// certificate is created.
func (t *TLSManager) setEphemeralSettings(keyBytes, certBytes []byte) {
t.ephemeralKey = keyBytes
t.ephemeralCert = certBytes
t.ephemeralCertPath = t.cfg.TLSCertPath + ".tmp"
}
// deleteEphemeralSettings deletes the TLSManager ephemeral settings that are
// no longer needed when the ephemeral certificate is deleted so the Manager
// knows we're no longer using it.
func (t *TLSManager) deleteEphemeralSettings() {
t.ephemeralKey = nil
t.ephemeralCert = nil
t.ephemeralCertPath = ""
}
// IsCertExpired checks if the current TLS certificate is expired.
func (t *TLSManager) IsCertExpired(keyRing keychain.SecretKeyRing) (bool,
time.Time, error) {
certBytes, keyBytes, err := cert.GetCertBytesFromPath(
t.cfg.TLSCertPath, t.cfg.TLSKeyPath,
)
if err != nil {
return false, time.Time{}, err
}
// If TLSEncryptKey is set, there are two states the
// certificate can be in: ephemeral or permanent.
// Retrieve the key depending on which state it is in.
if t.ephemeralKey != nil {
keyBytes = t.ephemeralKey
} else if t.cfg.TLSEncryptKey {
keyBytes, err = decryptTLSKeyBytes(keyRing, keyBytes)
if err != nil {
return false, time.Time{}, err
}
}
_, parsedCert, err := cert.LoadCertFromBytes(
certBytes, keyBytes,
)
if err != nil {
return false, time.Time{}, err
}
// If the current time is passed the certificate's
// expiry time, then it is considered expired
if time.Now().After(parsedCert.NotAfter) {
return true, parsedCert.NotAfter, nil
}
return false, parsedCert.NotAfter, nil
}
package walletunlocker
import (
"context"
"crypto/rand"
"errors"
"fmt"
"os"
"time"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet"
"github.com/lightningnetwork/lnd/aezeed"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"github.com/lightningnetwork/lnd/macaroons"
)
var (
// ErrUnlockTimeout signals that we did not get the expected unlock
// message before the timeout occurred.
ErrUnlockTimeout = errors.New("got no unlock message before timeout")
)
// WalletUnlockParams holds the variables used to parameterize the unlocking of
// lnd's wallet after it has already been created.
type WalletUnlockParams struct {
// Password is the public and private wallet passphrase.
Password []byte
// Birthday specifies the approximate time that this wallet was created.
// This is used to bound any rescans on startup.
Birthday time.Time
// RecoveryWindow specifies the address lookahead when entering recovery
// mode. A recovery will be attempted if this value is non-zero.
RecoveryWindow uint32
// Wallet is the loaded and unlocked Wallet. This is returned
// from the unlocker service to avoid it being unlocked twice (once in
// the unlocker service to check if the password is correct and again
// later when lnd actually uses it). Because unlocking involves scrypt
// which is resource intensive, we want to avoid doing it twice.
Wallet *wallet.Wallet
// ChansToRestore a set of static channel backups that should be
// restored before the main server instance starts up.
ChansToRestore ChannelsToRecover
// UnloadWallet is a function for unloading the wallet, which should
// be called on shutdown.
UnloadWallet func() error
// StatelessInit signals that the user requested the daemon to be
// initialized stateless, which means no unencrypted macaroons should be
// written to disk.
StatelessInit bool
// MacResponseChan is the channel for sending back the admin macaroon to
// the WalletUnlocker service.
MacResponseChan chan []byte
// MacRootKey is the 32 byte macaroon root key specified by the user
// during wallet initialization.
MacRootKey []byte
}
// ChannelsToRecover wraps any set of packed (serialized+encrypted) channel
// back ups together. These can be passed in when unlocking the wallet, or
// creating a new wallet for the first time with an existing seed.
type ChannelsToRecover struct {
// PackedMultiChanBackup is an encrypted and serialized multi-channel
// backup.
PackedMultiChanBackup chanbackup.PackedMulti
// PackedSingleChanBackups is a series of encrypted and serialized
// single-channel backup for one or more channels.
PackedSingleChanBackups chanbackup.PackedSingles
}
// WalletInitMsg is a message sent by the UnlockerService when a user wishes to
// set up the internal wallet for the first time. The user MUST provide a
// passphrase, but is also able to provide their own source of entropy. If
// provided, then this source of entropy will be used to generate the wallet's
// HD seed. Otherwise, the wallet will generate one itself.
type WalletInitMsg struct {
// Passphrase is the passphrase that will be used to encrypt the wallet
// itself. This MUST be at least 8 characters.
Passphrase []byte
// WalletSeed is the deciphered cipher seed that the wallet should use
// to initialize itself. The seed might be nil if the wallet should be
// created from an extended master root key instead.
WalletSeed *aezeed.CipherSeed
// WalletExtendedKey is the wallet's extended master root key that
// should be used instead of the seed, if non-nil. The extended key is
// mutually exclusive to the wallet seed, but one of both is always set.
WalletExtendedKey *hdkeychain.ExtendedKey
// ExtendedKeyBirthday is the birthday of a wallet that's being restored
// through an extended key instead of an aezeed.
ExtendedKeyBirthday time.Time
// WatchOnlyAccounts is a map of scoped account extended public keys
// that should be imported to create a watch-only wallet.
WatchOnlyAccounts map[waddrmgr.ScopedIndex]*hdkeychain.ExtendedKey
// WatchOnlyBirthday is the birthday of the master root key the above
// watch-only account xpubs were derived from.
WatchOnlyBirthday time.Time
// WatchOnlyMasterFingerprint is the fingerprint of the master root key
// the above watch-only account xpubs were derived from.
WatchOnlyMasterFingerprint uint32
// RecoveryWindow is the address look-ahead used when restoring a seed
// with existing funds. A recovery window zero indicates that no
// recovery should be attempted, such as after the wallet's initial
// creation.
RecoveryWindow uint32
// ChanBackups a set of static channel backups that should be received
// after the wallet has been initialized.
ChanBackups ChannelsToRecover
// StatelessInit signals that the user requested the daemon to be
// initialized stateless, which means no unencrypted macaroons should be
// written to disk.
StatelessInit bool
// MacRootKey is the 32 byte macaroon root key specified by the user
// during wallet initialization.
MacRootKey []byte
}
// WalletUnlockMsg is a message sent by the UnlockerService when a user wishes
// to unlock the internal wallet after initial setup. The user can optionally
// specify a recovery window, which will resume an interrupted rescan for used
// addresses.
type WalletUnlockMsg struct {
// Passphrase is the passphrase that will be used to encrypt the wallet
// itself. This MUST be at least 8 characters.
Passphrase []byte
// RecoveryWindow is the address look-ahead used when restoring a seed
// with existing funds. A recovery window zero indicates that no
// recovery should be attempted, such as after the wallet's initial
// creation, but before any addresses have been created.
RecoveryWindow uint32
// Wallet is the loaded and unlocked Wallet. This is returned through
// the channel to avoid it being unlocked twice (once to check if the
// password is correct, here in the WalletUnlocker and again later when
// lnd actually uses it). Because unlocking involves scrypt which is
// resource intensive, we want to avoid doing it twice.
Wallet *wallet.Wallet
// ChanBackups a set of static channel backups that should be received
// after the wallet has been unlocked.
ChanBackups ChannelsToRecover
// UnloadWallet is a function for unloading the wallet, which should
// be called on shutdown.
UnloadWallet func() error
// StatelessInit signals that the user requested the daemon to be
// initialized stateless, which means no unencrypted macaroons should be
// written to disk.
StatelessInit bool
}
// UnlockerService implements the WalletUnlocker service used to provide lnd
// with a password for wallet encryption at startup. Additionally, during
// initial setup, users can provide their own source of entropy which will be
// used to generate the seed that's ultimately used within the wallet.
type UnlockerService struct {
// Required by the grpc-gateway/v2 library for forward compatibility.
lnrpc.UnimplementedWalletUnlockerServer
// InitMsgs is a channel that carries all wallet init messages.
InitMsgs chan *WalletInitMsg
// UnlockMsgs is a channel where unlock parameters provided by the rpc
// client to be used to unlock and decrypt an existing wallet will be
// sent.
UnlockMsgs chan *WalletUnlockMsg
// MacResponseChan is the channel for sending back the admin macaroon to
// the WalletUnlocker service.
MacResponseChan chan []byte
netParams *chaincfg.Params
// macaroonFiles is the path to the three generated macaroons with
// different access permissions. These might not exist in a stateless
// initialization of lnd.
macaroonFiles []string
// resetWalletTransactions indicates that the wallet state should be
// reset on unlock to force a full chain rescan.
resetWalletTransactions bool
// LoaderOpts holds the functional options for the wallet loader.
loaderOpts []btcwallet.LoaderOption
// macaroonDB is an instance of a database backend that stores all
// macaroon root keys. This will be nil on initialization and must be
// set using the SetMacaroonDB method as soon as it's available.
macaroonDB kvdb.Backend
}
// New creates and returns a new UnlockerService.
func New(params *chaincfg.Params, macaroonFiles []string,
resetWalletTransactions bool,
loaderOpts []btcwallet.LoaderOption) *UnlockerService {
return &UnlockerService{
InitMsgs: make(chan *WalletInitMsg, 1),
UnlockMsgs: make(chan *WalletUnlockMsg, 1),
// Make sure we buffer the channel is buffered so the main lnd
// goroutine isn't blocking on writing to it.
MacResponseChan: make(chan []byte, 1),
netParams: params,
macaroonFiles: macaroonFiles,
resetWalletTransactions: resetWalletTransactions,
loaderOpts: loaderOpts,
}
}
// SetLoaderOpts can be used to inject wallet loader options after the unlocker
// service has been hooked to the main RPC server.
func (u *UnlockerService) SetLoaderOpts(loaderOpts []btcwallet.LoaderOption) {
u.loaderOpts = loaderOpts
}
// SetMacaroonDB can be used to inject the macaroon database after the unlocker
// service has been hooked to the main RPC server.
func (u *UnlockerService) SetMacaroonDB(macaroonDB kvdb.Backend) {
u.macaroonDB = macaroonDB
}
func (u *UnlockerService) newLoader(recoveryWindow uint32) (*wallet.Loader,
error) {
return btcwallet.NewWalletLoader(
u.netParams, recoveryWindow, u.loaderOpts...,
)
}
// WalletExists returns whether a wallet exists on the file path the
// UnlockerService is using.
func (u *UnlockerService) WalletExists() (bool, error) {
loader, err := u.newLoader(0)
if err != nil {
return false, err
}
return loader.WalletExists()
}
// GenSeed is the first method that should be used to instantiate a new lnd
// instance. This method allows a caller to generate a new aezeed cipher seed
// given an optional passphrase. If provided, the passphrase will be necessary
// to decrypt the cipherseed to expose the internal wallet seed.
//
// Once the cipherseed is obtained and verified by the user, the InitWallet
// method should be used to commit the newly generated seed, and create the
// wallet.
func (u *UnlockerService) GenSeed(_ context.Context,
in *lnrpc.GenSeedRequest) (*lnrpc.GenSeedResponse, error) {
// Before we start, we'll ensure that the wallet hasn't already created
// so we don't show a *new* seed to the user if one already exists.
loader, err := u.newLoader(0)
if err != nil {
return nil, err
}
walletExists, err := loader.WalletExists()
if err != nil {
return nil, err
}
if walletExists {
return nil, fmt.Errorf("wallet already exists")
}
var entropy [aezeed.EntropySize]byte
switch {
// If the user provided any entropy, then we'll make sure it's sized
// properly.
case len(in.SeedEntropy) != 0 && len(in.SeedEntropy) != aezeed.EntropySize:
return nil, fmt.Errorf("incorrect entropy length: expected "+
"16 bytes, instead got %v bytes", len(in.SeedEntropy))
// If the user provided the correct number of bytes, then we'll copy it
// over into our buffer for usage.
case len(in.SeedEntropy) == aezeed.EntropySize:
copy(entropy[:], in.SeedEntropy[:])
// Otherwise, we'll generate a fresh new set of bytes to use as entropy
// to generate the seed.
default:
if _, err := rand.Read(entropy[:]); err != nil {
return nil, err
}
}
// Now that we have our set of entropy, we'll create a new cipher seed
// instance.
//
cipherSeed, err := aezeed.New(
keychain.CurrentKeyDerivationVersion, &entropy, time.Now(),
)
if err != nil {
return nil, err
}
// With our raw cipher seed obtained, we'll convert it into an encoded
// mnemonic using the user specified pass phrase.
mnemonic, err := cipherSeed.ToMnemonic(in.AezeedPassphrase)
if err != nil {
return nil, err
}
// Additionally, we'll also obtain the raw enciphered cipher seed as
// well to return to the user.
encipheredSeed, err := cipherSeed.Encipher(in.AezeedPassphrase)
if err != nil {
return nil, err
}
return &lnrpc.GenSeedResponse{
CipherSeedMnemonic: mnemonic[:],
EncipheredSeed: encipheredSeed[:],
}, nil
}
// extractChanBackups is a helper function that extracts the set of channel
// backups from the proto into a format that we'll pass to higher level
// sub-systems.
func extractChanBackups(chanBackups *lnrpc.ChanBackupSnapshot) *ChannelsToRecover {
// If there aren't any populated channel backups, then we can exit
// early as there's nothing to extract.
if chanBackups == nil || (chanBackups.SingleChanBackups == nil &&
chanBackups.MultiChanBackup == nil) {
return nil
}
// Now that we know there's at least a single back up populated, we'll
// extract the multi-chan backup (if it's there).
var backups ChannelsToRecover
if chanBackups.MultiChanBackup != nil {
multiBackup := chanBackups.MultiChanBackup
backups.PackedMultiChanBackup = multiBackup.MultiChanBackup
}
if chanBackups.SingleChanBackups == nil {
return &backups
}
// Finally, we can extract all the single chan backups as well.
for _, backup := range chanBackups.SingleChanBackups.ChanBackups {
singleChanBackup := backup.ChanBackup
backups.PackedSingleChanBackups = append(
backups.PackedSingleChanBackups, singleChanBackup,
)
}
return &backups
}
// InitWallet is used when lnd is starting up for the first time to fully
// initialize the daemon and its internal wallet. At the very least a wallet
// password must be provided. This will be used to encrypt sensitive material
// on disk.
//
// In the case of a recovery scenario, the user can also specify their aezeed
// mnemonic and passphrase. If set, then the daemon will use this prior state
// to initialize its internal wallet.
//
// Alternatively, this can be used along with the GenSeed RPC to obtain a
// seed, then present it to the user. Once it has been verified by the user,
// the seed can be fed into this RPC in order to commit the new wallet.
func (u *UnlockerService) InitWallet(ctx context.Context,
in *lnrpc.InitWalletRequest) (*lnrpc.InitWalletResponse, error) {
// Make sure the password meets our constraints.
password := in.WalletPassword
if err := ValidatePassword(password); err != nil {
return nil, err
}
// Require that the recovery window be non-negative.
recoveryWindow := in.RecoveryWindow
if recoveryWindow < 0 {
return nil, fmt.Errorf("recovery window %d must be "+
"non-negative", recoveryWindow)
}
// Ensure that the macaroon root key is *exactly* 32-bytes.
macaroonRootKey := in.MacaroonRootKey
if len(macaroonRootKey) > 0 &&
len(macaroonRootKey) != macaroons.RootKeyLen {
return nil, fmt.Errorf("macaroon root key must be exactly "+
"%v bytes, is instead %v",
macaroons.RootKeyLen, len(macaroonRootKey),
)
}
// We'll then open up the directory that will be used to store the
// wallet's files so we can check if the wallet already exists.
loader, err := u.newLoader(uint32(recoveryWindow))
if err != nil {
return nil, err
}
walletExists, err := loader.WalletExists()
if err != nil {
return nil, err
}
// If the wallet already exists, then we'll exit early as we can't
// create the wallet if it already exists!
if walletExists {
return nil, fmt.Errorf("wallet already exists")
}
// At this point, we know the wallet doesn't already exist so we can
// prepare the message that we'll send over the channel later.
initMsg := &WalletInitMsg{
Passphrase: password,
RecoveryWindow: uint32(recoveryWindow),
StatelessInit: in.StatelessInit,
MacRootKey: macaroonRootKey,
}
// There are two supported ways to initialize the wallet. Either from
// the aezeed or the final extended master key directly.
switch {
// Don't allow the user to specify both as that would be ambiguous.
case len(in.CipherSeedMnemonic) > 0 && len(in.ExtendedMasterKey) > 0:
return nil, fmt.Errorf("cannot specify both the cipher " +
"seed mnemonic and the extended master key")
// The aezeed is the preferred and default way of initializing a wallet.
case len(in.CipherSeedMnemonic) > 0:
// We'll map the user provided aezeed and passphrase into a
// decoded cipher seed instance.
var mnemonic aezeed.Mnemonic
copy(mnemonic[:], in.CipherSeedMnemonic)
// If we're unable to map it back into the ciphertext, then
// either the mnemonic is wrong, or the passphrase is wrong.
cipherSeed, err := mnemonic.ToCipherSeed(in.AezeedPassphrase)
if err != nil {
return nil, err
}
initMsg.WalletSeed = cipherSeed
// To support restoring a wallet where the seed isn't known or a wallet
// created externally to lnd, we also allow the extended master key
// (xprv) to be imported directly. This is what'll be stored in the
// btcwallet database anyway.
case len(in.ExtendedMasterKey) > 0:
extendedKey, err := hdkeychain.NewKeyFromString(
in.ExtendedMasterKey,
)
if err != nil {
return nil, err
}
// The on-chain wallet of lnd is going to derive keys based on
// the BIP49/84 key derivation paths from this root key. To make
// sure we use default derivation paths, we want to avoid
// deriving keys from something other than the master key (at
// depth 0, denoted with "m/" in BIP32 notation).
if extendedKey.Depth() != 0 {
return nil, fmt.Errorf("extended master key must " +
"be at depth 0 not a child key")
}
// Because we need the master key (at depth 0), it must be an
// extended private key as the first levels of BIP49/84
// derivation paths are hardened, which isn't possible with
// extended public keys.
if !extendedKey.IsPrivate() {
return nil, fmt.Errorf("extended master key must " +
"contain private keys")
}
// To avoid using the wrong master key, we check that it was
// issued for the correct network. This will cause problems if
// someone tries to import a "new" BIP84 zprv key because with
// this we only support the "legacy" zprv prefix. But it is
// trivial to convert between those formats, as long as the user
// knows what they're doing.
if !extendedKey.IsForNet(u.netParams) {
return nil, fmt.Errorf("extended master key must be "+
"for network %s", u.netParams.Name)
}
// When importing a wallet from its extended private key we
// don't know the birthday as that information is not encoded in
// that format. We therefore must set an arbitrary date to start
// rescanning at if the user doesn't provide an explicit value
// for it. Since lnd only uses SegWit addresses, we pick the
// date of the first block that contained SegWit transactions
// (481824).
initMsg.ExtendedKeyBirthday = time.Date(
2017, time.August, 24, 1, 57, 37, 0, time.UTC,
)
if in.ExtendedMasterKeyBirthdayTimestamp != 0 {
initMsg.ExtendedKeyBirthday = time.Unix(
int64(in.ExtendedMasterKeyBirthdayTimestamp), 0,
)
}
initMsg.WalletExtendedKey = extendedKey
// The third option for creating a wallet is the watch-only mode:
// Instead of providing the master root key directly, each individual
// account is passed as an extended public key only. Because of the
// hardened derivation path up to the account (depth 3), it is not
// possible to create a master root extended _public_ key. Therefore, an
// xpub must be derived and passed into the unlocker for _every_ account
// lnd expects.
case in.WatchOnly != nil && len(in.WatchOnly.Accounts) > 0:
initMsg.WatchOnlyAccounts = make(
map[waddrmgr.ScopedIndex]*hdkeychain.ExtendedKey,
len(in.WatchOnly.Accounts),
)
for _, acct := range in.WatchOnly.Accounts {
scopedIndex := waddrmgr.ScopedIndex{
Scope: waddrmgr.KeyScope{
Purpose: acct.Purpose,
Coin: acct.CoinType,
},
Index: acct.Account,
}
acctKey, err := hdkeychain.NewKeyFromString(acct.Xpub)
if err != nil {
return nil, fmt.Errorf("error parsing xpub "+
"%v: %v", acct.Xpub, err)
}
// Just to make sure the user is doing the right thing,
// we expect the public key to be at derivation depth
// three (which is the account level) and the key not to
// contain any private key material.
if acctKey.Depth() != 3 {
return nil, fmt.Errorf("xpub must be at " +
"depth 3")
}
if acctKey.IsPrivate() {
return nil, fmt.Errorf("xpub is not really " +
"an xpub, contains private key")
}
initMsg.WatchOnlyAccounts[scopedIndex] = acctKey
}
// When importing a wallet from its extended public keys we
// don't know the birthday as that information is not encoded in
// that format. We therefore must set an arbitrary date to start
// rescanning at if the user doesn't provide an explicit value
// for it. Since lnd only uses SegWit addresses, we pick the
// date of the first block that contained SegWit transactions
// (481824).
initMsg.WatchOnlyBirthday = time.Date(
2017, time.August, 24, 1, 57, 37, 0, time.UTC,
)
if in.WatchOnly.MasterKeyBirthdayTimestamp != 0 {
initMsg.WatchOnlyBirthday = time.Unix(
int64(in.WatchOnly.MasterKeyBirthdayTimestamp),
0,
)
}
// No key material was set, no wallet can be created.
default:
return nil, fmt.Errorf("must either specify cipher seed " +
"mnemonic or the extended master key")
}
// Before we return the unlock payload, we'll check if we can extract
// any channel backups to pass up to the higher level sub-system.
chansToRestore := extractChanBackups(in.ChannelBackups)
if chansToRestore != nil {
initMsg.ChanBackups = *chansToRestore
}
// Deliver the initialization message back to the main daemon.
select {
case u.InitMsgs <- initMsg:
// We need to read from the channel to let the daemon continue
// its work and to get the admin macaroon. Once the response
// arrives, we directly forward it to the client.
select {
case adminMac := <-u.MacResponseChan:
return &lnrpc.InitWalletResponse{
AdminMacaroon: adminMac,
}, nil
case <-ctx.Done():
return nil, ErrUnlockTimeout
}
case <-ctx.Done():
return nil, ErrUnlockTimeout
}
}
// LoadAndUnlock creates a loader for the wallet and tries to unlock the wallet
// with the given password and recovery window. If the drop wallet transactions
// flag is set, the history state drop is performed before unlocking the wallet
// yet again.
func (u *UnlockerService) LoadAndUnlock(password []byte,
recoveryWindow uint32) (*wallet.Wallet, func() error, error) {
loader, err := u.newLoader(recoveryWindow)
if err != nil {
return nil, nil, err
}
// Check if wallet already exists.
walletExists, err := loader.WalletExists()
if err != nil {
return nil, nil, err
}
if !walletExists {
// Cannot unlock a wallet that does not exist!
return nil, nil, fmt.Errorf("wallet not found")
}
// Try opening the existing wallet with the provided password.
unlockedWallet, err := loader.OpenExistingWallet(password, false)
if err != nil {
// Could not open wallet, most likely this means that provided
// password was incorrect.
return nil, nil, err
}
// The user requested to drop their whole wallet transaction state to
// force a full chain rescan for wallet addresses. Dropping the state
// only properly takes effect after opening the wallet. That's why we
// start, drop, stop and start again.
if u.resetWalletTransactions {
dropErr := wallet.DropTransactionHistory(
unlockedWallet.Database(), true,
)
// Even if dropping the history fails, we'll want to unload the
// wallet. If unloading fails, that error is probably more
// important to be returned to the user anyway.
if err := loader.UnloadWallet(); err != nil {
return nil, nil, fmt.Errorf("could not unload "+
"wallet (tx history drop err: %v): %v", dropErr,
err)
}
// If dropping failed but unloading didn't, we'll still abort
// and inform the user.
if dropErr != nil {
return nil, nil, dropErr
}
// All looks good, let's now open the wallet again. The loader
// was unloaded and might have removed its remote DB connection,
// so let's re-create it as well.
loader, err = u.newLoader(recoveryWindow)
if err != nil {
return nil, nil, err
}
unlockedWallet, err = loader.OpenExistingWallet(password, false)
if err != nil {
return nil, nil, err
}
}
return unlockedWallet, loader.UnloadWallet, nil
}
// UnlockWallet sends the password provided by the incoming UnlockWalletRequest
// over the UnlockMsgs channel in case it successfully decrypts an existing
// wallet found in the chain's wallet database directory.
func (u *UnlockerService) UnlockWallet(ctx context.Context,
in *lnrpc.UnlockWalletRequest) (*lnrpc.UnlockWalletResponse, error) {
password := in.WalletPassword
recoveryWindow := uint32(in.RecoveryWindow)
unlockedWallet, unloadFn, err := u.LoadAndUnlock(
password, recoveryWindow,
)
if err != nil {
return nil, err
}
// We successfully opened the wallet and pass the instance back to
// avoid it needing to be unlocked again.
walletUnlockMsg := &WalletUnlockMsg{
Passphrase: password,
RecoveryWindow: recoveryWindow,
Wallet: unlockedWallet,
UnloadWallet: unloadFn,
StatelessInit: in.StatelessInit,
}
// Before we return the unlock payload, we'll check if we can extract
// any channel backups to pass up to the higher level sub-system.
chansToRestore := extractChanBackups(in.ChannelBackups)
if chansToRestore != nil {
walletUnlockMsg.ChanBackups = *chansToRestore
}
// At this point we were able to open the existing wallet with the
// provided password. We send the password over the UnlockMsgs
// channel, such that it can be used by lnd to open the wallet.
select {
case u.UnlockMsgs <- walletUnlockMsg:
// We need to read from the channel to let the daemon continue
// its work. But we don't need the returned macaroon for this
// operation, so we read it but then discard it.
select {
case <-u.MacResponseChan:
return &lnrpc.UnlockWalletResponse{}, nil
case <-ctx.Done():
return nil, ErrUnlockTimeout
}
case <-ctx.Done():
return nil, ErrUnlockTimeout
}
}
// ChangePassword changes the password of the wallet and sends the new password
// across the UnlockPasswords channel to automatically unlock the wallet if
// successful.
func (u *UnlockerService) ChangePassword(ctx context.Context,
in *lnrpc.ChangePasswordRequest) (*lnrpc.ChangePasswordResponse, error) {
loader, err := u.newLoader(0)
if err != nil {
return nil, err
}
// First, we'll make sure the wallet exists for the specific chain and
// network.
walletExists, err := loader.WalletExists()
if err != nil {
return nil, err
}
if !walletExists {
return nil, errors.New("wallet not found")
}
publicPw := in.CurrentPassword
privatePw := in.CurrentPassword
// If the current password is blank, we'll assume the user is coming
// from a --noseedbackup state, so we'll use the default passwords.
if len(in.CurrentPassword) == 0 {
publicPw = lnwallet.DefaultPublicPassphrase
privatePw = lnwallet.DefaultPrivatePassphrase
}
// Make sure the new password meets our constraints.
if err := ValidatePassword(in.NewPassword); err != nil {
return nil, err
}
// Load the existing wallet in order to proceed with the password change.
w, err := loader.OpenExistingWallet(publicPw, false)
if err != nil {
return nil, err
}
// Now that we've opened the wallet, we need to close it in case of an
// error. But not if we succeed, then the caller must close it.
orderlyReturn := false
defer func() {
if !orderlyReturn {
_ = loader.UnloadWallet()
}
}()
// Before we actually change the password, we need to check if all flags
// were set correctly. The content of the previously generated macaroon
// files will become invalid after we generate a new root key. So we try
// to delete them here and they will be recreated during normal startup
// later. If they are missing, this is only an error if the
// stateless_init flag was not set.
if in.NewMacaroonRootKey || in.StatelessInit {
for _, file := range u.macaroonFiles {
err := os.Remove(file)
if err != nil && !in.StatelessInit {
return nil, fmt.Errorf("could not remove "+
"macaroon file: %v. if the wallet "+
"was initialized stateless please "+
"add the --stateless_init "+
"flag", err)
}
}
}
// Attempt to change both the public and private passphrases for the
// wallet. This will be done atomically in order to prevent one
// passphrase change from being successful and not the other.
err = w.ChangePassphrases(
publicPw, in.NewPassword, privatePw, in.NewPassword,
)
if err != nil {
return nil, fmt.Errorf("unable to change wallet passphrase: "+
"%v", err)
}
// The next step is to load the macaroon database, change the password
// then close it again.
// Attempt to open the macaroon DB, unlock it and then change
// the passphrase.
rootKeyStore, err := macaroons.NewRootKeyStorage(u.macaroonDB)
if err != nil {
return nil, err
}
macaroonService, err := macaroons.NewService(
rootKeyStore, "lnd", in.StatelessInit,
)
if err != nil {
return nil, err
}
err = macaroonService.CreateUnlock(&privatePw)
if err != nil {
closeErr := macaroonService.Close()
if closeErr != nil {
return nil, fmt.Errorf("could not create unlock: %v "+
"--> follow-up error when closing: %v", err,
closeErr)
}
return nil, err
}
err = macaroonService.ChangePassword(privatePw, in.NewPassword)
if err != nil {
closeErr := macaroonService.Close()
if closeErr != nil {
return nil, fmt.Errorf("could not change password: %v "+
"--> follow-up error when closing: %v", err,
closeErr)
}
return nil, err
}
// If requested by the user, attempt to replace the existing
// macaroon root key with a new one.
if in.NewMacaroonRootKey {
err = macaroonService.GenerateNewRootKey()
if err != nil {
closeErr := macaroonService.Close()
if closeErr != nil {
return nil, fmt.Errorf("could not generate "+
"new root key: %v --> follow-up error "+
"when closing: %v", err, closeErr)
}
return nil, err
}
}
err = macaroonService.Close()
if err != nil {
return nil, fmt.Errorf("could not close macaroon service: %w",
err)
}
// Finally, send the new password across the UnlockPasswords channel to
// automatically unlock the wallet.
walletUnlockMsg := &WalletUnlockMsg{
Passphrase: in.NewPassword,
Wallet: w,
StatelessInit: in.StatelessInit,
UnloadWallet: loader.UnloadWallet,
}
select {
case u.UnlockMsgs <- walletUnlockMsg:
// We need to read from the channel to let the daemon continue
// its work and to get the admin macaroon. Once the response
// arrives, we directly forward it to the client.
orderlyReturn = true
select {
case adminMac := <-u.MacResponseChan:
return &lnrpc.ChangePasswordResponse{
AdminMacaroon: adminMac,
}, nil
case <-ctx.Done():
return nil, ErrUnlockTimeout
}
case <-ctx.Done():
return nil, ErrUnlockTimeout
}
}
// ValidatePassword assures the password meets all of our constraints.
func ValidatePassword(password []byte) error {
// Passwords should have a length of at least 8 characters.
if len(password) < 8 {
return errors.New("password must have at least 8 characters")
}
return nil
}
package blob
import (
"fmt"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
)
// CommitmentType characterises the various properties of the breach commitment
// transaction.
type CommitmentType uint8
const (
// LegacyCommitment represents a legacy commitment transaction where
// anchor outputs are not yet used and so the to_remote output is just
// a regular but tweaked P2WKH.
LegacyCommitment CommitmentType = iota
// LegacyTweaklessCommitment is similar to the LegacyCommitment with the
// added detail of the to_remote output not being tweaked.
LegacyTweaklessCommitment
// AnchorCommitment represents the commitment transaction of an
// anchor channel. The key differences are that the to_remote is
// encumbered by a 1 block CSV and so is thus a P2WSH output.
AnchorCommitment
// TaprootCommitment represents the commitment transaction of a simple
// taproot channel.
TaprootCommitment
)
// ToLocalInput constructs the input that will be used to spend the to_local
// output.
func (c CommitmentType) ToLocalInput(info *lnwallet.BreachRetribution) (
input.Input, error) {
witnessType, err := c.ToLocalWitnessType()
if err != nil {
return nil, err
}
return input.NewBaseInput(
&info.RemoteOutpoint, witnessType, info.RemoteOutputSignDesc, 0,
), nil
}
// ToRemoteInput constructs the input that will be used to spend the to_remote
// output.
func (c CommitmentType) ToRemoteInput(info *lnwallet.BreachRetribution) (
input.Input, error) {
witnessType, err := c.ToRemoteWitnessType()
if err != nil {
return nil, err
}
switch c {
case LegacyCommitment, LegacyTweaklessCommitment:
return input.NewBaseInput(
&info.LocalOutpoint, witnessType,
info.LocalOutputSignDesc, 0,
), nil
case AnchorCommitment, TaprootCommitment:
// Anchor and Taproot channels have a CSV-encumbered to-remote
// output. We'll construct a CSV input and assign the proper CSV
// delay of 1.
return input.NewCsvInput(
&info.LocalOutpoint, witnessType,
info.LocalOutputSignDesc, 0, 1,
), nil
default:
return nil, fmt.Errorf("unknown commitment type: %v", c)
}
}
// ToLocalWitnessType is the input type of the to_local output.
func (c CommitmentType) ToLocalWitnessType() (input.WitnessType, error) {
switch c {
case LegacyTweaklessCommitment, LegacyCommitment, AnchorCommitment:
return input.CommitmentRevoke, nil
case TaprootCommitment:
return input.TaprootCommitmentRevoke, nil
default:
return nil, fmt.Errorf("unknown commitment type: %v", c)
}
}
// ToRemoteWitnessType is the input type of the to_remote output.
func (c CommitmentType) ToRemoteWitnessType() (input.WitnessType, error) {
switch c {
case LegacyTweaklessCommitment:
return input.CommitSpendNoDelayTweakless, nil
case LegacyCommitment:
return input.CommitmentNoDelay, nil
case AnchorCommitment:
return input.CommitmentToRemoteConfirmed, nil
case TaprootCommitment:
return input.TaprootRemoteCommitSpend, nil
default:
return nil, fmt.Errorf("unknown commitment type: %v", c)
}
}
// ToRemoteWitnessSize is the size of the witness that will be required to spend
// the to_remote output.
func (c CommitmentType) ToRemoteWitnessSize() (lntypes.WeightUnit, error) {
switch c {
// Legacy channels (both tweaked and non-tweaked) spend from P2WKH
// output.
case LegacyTweaklessCommitment, LegacyCommitment:
return input.P2WKHWitnessSize, nil
// Anchor channels spend a to-remote confirmed P2WSH output.
case AnchorCommitment:
return input.ToRemoteConfirmedWitnessSize, nil
// Taproot channels spend a confirmed P2SH output.
case TaprootCommitment:
return input.TaprootToRemoteWitnessSize, nil
default:
return 0, fmt.Errorf("unknown commitment type: %v", c)
}
}
// ToLocalWitnessSize is the size of the witness that will be required to spend
// the to_local output.
func (c CommitmentType) ToLocalWitnessSize() (lntypes.WeightUnit, error) {
switch c {
// An older ToLocalPenaltyWitnessSize constant used to underestimate the
// size by one byte. The difference in weight can cause different output
// values on the sweep transaction, so we mimic the original bug and
// create signatures using the original weight estimate.
case LegacyTweaklessCommitment, LegacyCommitment:
return input.ToLocalPenaltyWitnessSize - 1, nil
case AnchorCommitment:
return input.ToLocalPenaltyWitnessSize, nil
case TaprootCommitment:
return input.TaprootToLocalRevokeWitnessSize, nil
default:
return 0, fmt.Errorf("unknown commitment type: %v", c)
}
}
// ParseRawSig parses a wire.TxWitness and creates an lnwire.Sig.
func (c CommitmentType) ParseRawSig(witness wire.TxWitness) (lnwire.Sig,
error) {
// Check that the witness has at least one item since this is required
// for all commitment types to follow.
if len(witness) < 1 {
return lnwire.Sig{}, fmt.Errorf("the witness should have at " +
"least one element")
}
// Check that the first witness element is non-nil. This is to ensure
// that the witness length checks below do not panic.
if witness[0] == nil {
return lnwire.Sig{}, fmt.Errorf("the first witness element " +
"should not be nil")
}
switch c {
case LegacyCommitment, LegacyTweaklessCommitment, AnchorCommitment:
// Parse the DER-encoded signature from the first position of
// the resulting witness. We trim an extra byte to remove the
// sighash flag.
rawSignature := witness[0][:len(witness[0])-1]
// Re-encode the DER signature into a fixed-size 64 byte
// signature.
return lnwire.NewSigFromECDSARawSignature(rawSignature)
case TaprootCommitment:
rawSignature := witness[0]
if len(rawSignature) > 64 {
rawSignature = witness[0][:len(witness[0])-1]
}
// Re-encode the schnorr signature into a fixed-size 64 byte
// signature.
return lnwire.NewSigFromSchnorrRawSignature(rawSignature)
default:
return lnwire.Sig{}, fmt.Errorf("unknown commitment type: %v",
c)
}
}
// NewJusticeKit can be used to construct a new JusticeKit depending on the
// CommitmentType.
func (c CommitmentType) NewJusticeKit(sweepScript []byte,
breachInfo *lnwallet.BreachRetribution, withToRemote bool) (JusticeKit,
error) {
switch c {
case LegacyCommitment, LegacyTweaklessCommitment:
return newLegacyJusticeKit(
sweepScript, breachInfo, withToRemote,
), nil
case AnchorCommitment:
return newAnchorJusticeKit(
sweepScript, breachInfo, withToRemote,
), nil
case TaprootCommitment:
return newTaprootJusticeKit(
sweepScript, breachInfo, withToRemote,
)
default:
return nil, fmt.Errorf("unknown commitment type: %v", c)
}
}
// EmptyJusticeKit returns the appropriate empty justice kit for the given
// CommitmentType.
func (c CommitmentType) EmptyJusticeKit() (JusticeKit, error) {
switch c {
case LegacyTweaklessCommitment, LegacyCommitment:
return &legacyJusticeKit{}, nil
case AnchorCommitment:
return &anchorJusticeKit{
legacyJusticeKit: legacyJusticeKit{},
}, nil
case TaprootCommitment:
return &taprootJusticeKit{}, nil
default:
return nil, fmt.Errorf("unknown commitment type: %v", c)
}
}
package blob
import (
"crypto/sha256"
"encoding/hex"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// BreachHintSize is the length of the identifier used to detect remote
// commitment broadcasts.
const BreachHintSize = 16
// BreachHint is the first 16-bytes of SHA256(txid), which is used to identify
// the breach transaction.
type BreachHint [BreachHintSize]byte
// NewBreachHintFromHash creates a breach hint from a transaction ID.
func NewBreachHintFromHash(hash *chainhash.Hash) BreachHint {
h := sha256.New()
h.Write(hash[:])
var hint BreachHint
copy(hint[:], h.Sum(nil))
return hint
}
// String returns a hex encoding of the breach hint.
func (h BreachHint) String() string {
return hex.EncodeToString(h[:])
}
// BreachKey is computed as SHA256(txid || txid), which produces the key for
// decrypting a client's encrypted blobs.
type BreachKey [KeySize]byte
// NewBreachKeyFromHash creates a breach key from a transaction ID.
func NewBreachKeyFromHash(hash *chainhash.Hash) BreachKey {
h := sha256.New()
h.Write(hash[:])
h.Write(hash[:])
var key BreachKey
copy(key[:], h.Sum(nil))
return key
}
// String returns a hex encoding of the breach key.
func (k BreachKey) String() string {
return hex.EncodeToString(k[:])
}
// NewBreachHintAndKeyFromHash derives a BreachHint and BreachKey from a given
// txid in a single pass. The hint and key are computed as:
//
// hint = SHA256(txid)
// key = SHA256(txid || txid)
func NewBreachHintAndKeyFromHash(hash *chainhash.Hash) (BreachHint, BreachKey) {
var (
hint BreachHint
key BreachKey
)
h := sha256.New()
h.Write(hash[:])
copy(hint[:], h.Sum(nil))
h.Write(hash[:])
copy(key[:], h.Sum(nil))
return hint, key
}
package blob
import (
"bytes"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
)
// JusticeKit is an interface that describes lé Blob of Justice. An
// implementation of the JusticeKit contains information required to construct
// a justice transaction, that sweeps a remote party's revoked commitment
// transaction. It supports encryption and decryption using chacha20poly1305,
// allowing the client to encrypt the contents of the blob, and for a
// watchtower to later decrypt if action must be taken.
type JusticeKit interface {
// ToLocalOutputSpendInfo returns the info required to send the to-local
// output. It returns the output pub key script and the witness required
// to spend the output.
ToLocalOutputSpendInfo() (*txscript.PkScript, wire.TxWitness, error)
// ToRemoteOutputSpendInfo returns the info required to send the
// to-remote output. It returns the output pub key script, the witness
// required to spend the output and the sequence to apply.
ToRemoteOutputSpendInfo() (*txscript.PkScript, wire.TxWitness, uint32,
error)
// HasCommitToRemoteOutput returns true if the kit does include the
// information required to sweep the to-remote output.
HasCommitToRemoteOutput() bool
// AddToLocalSig adds the to-local signature to the kit.
AddToLocalSig(sig lnwire.Sig)
// AddToRemoteSig adds the to-remote signature to the kit.
AddToRemoteSig(sig lnwire.Sig)
// SweepAddress returns the sweep address to be used on the justice tx
// output.
SweepAddress() []byte
// PlainTextSize is the size of the encoded-but-unencrypted blob in
// bytes.
PlainTextSize() int
encode(w io.Writer) error
decode(r io.Reader) error
}
// legacyJusticeKit is an implementation of the JusticeKit interface which can
// be used for backing up commitments of legacy (pre-anchor) channels.
type legacyJusticeKit struct {
justiceKitPacketV0
}
// A compile-time check to ensure that legacyJusticeKit implements the
// JusticeKit interface.
var _ JusticeKit = (*legacyJusticeKit)(nil)
// newLegacyJusticeKit constructs a new legacyJusticeKit.
func newLegacyJusticeKit(sweepScript []byte,
breachInfo *lnwallet.BreachRetribution,
withToRemote bool) *legacyJusticeKit {
keyRing := breachInfo.KeyRing
packet := justiceKitPacketV0{
sweepAddress: sweepScript,
revocationPubKey: toBlobPubKey(keyRing.RevocationKey),
localDelayPubKey: toBlobPubKey(keyRing.ToLocalKey),
csvDelay: breachInfo.RemoteDelay,
}
if withToRemote {
packet.commitToRemotePubKey = toBlobPubKey(
keyRing.ToRemoteKey,
)
}
return &legacyJusticeKit{packet}
}
// ToLocalOutputSpendInfo returns the info required to send the to-local output.
// It returns the output pub key script and the witness required to spend the
// output.
//
// NOTE: This is part of the JusticeKit interface.
func (l *legacyJusticeKit) ToLocalOutputSpendInfo() (*txscript.PkScript,
wire.TxWitness, error) {
revocationPubKey, err := btcec.ParsePubKey(l.revocationPubKey[:])
if err != nil {
return nil, nil, err
}
localDelayedPubKey, err := btcec.ParsePubKey(l.localDelayPubKey[:])
if err != nil {
return nil, nil, err
}
script, err := input.CommitScriptToSelf(
l.csvDelay, localDelayedPubKey, revocationPubKey,
)
if err != nil {
return nil, nil, err
}
scriptPubKey, err := input.WitnessScriptHash(script)
if err != nil {
return nil, nil, err
}
toLocalSig, err := l.commitToLocalSig.ToSignature()
if err != nil {
return nil, nil, err
}
witness := make(wire.TxWitness, 3)
witness[0] = append(toLocalSig.Serialize(), byte(txscript.SigHashAll))
witness[1] = []byte{1}
witness[2] = script
pkScript, err := txscript.ParsePkScript(scriptPubKey)
if err != nil {
return nil, nil, err
}
return &pkScript, witness, nil
}
// ToRemoteOutputSpendInfo returns the info required to spend the to-remote
// output. It returns the output pub key script, the witness required to spend
// the output and the sequence to apply.
//
// NOTE: This is part of the JusticeKit interface.
func (l *legacyJusticeKit) ToRemoteOutputSpendInfo() (*txscript.PkScript,
wire.TxWitness, uint32, error) {
if !btcec.IsCompressedPubKey(l.commitToRemotePubKey[:]) {
return nil, nil, 0, ErrNoCommitToRemoteOutput
}
toRemoteScript := l.commitToRemotePubKey[:]
// Since the to-remote witness script should just be a regular p2wkh
// output, we'll parse it to retrieve the public key.
toRemotePubKey, err := btcec.ParsePubKey(toRemoteScript)
if err != nil {
return nil, nil, 0, err
}
// Compute the witness script hash from the to-remote pubkey, which will
// be used to locate the output on the breach commitment transaction.
toRemoteScriptHash, err := input.CommitScriptUnencumbered(
toRemotePubKey,
)
if err != nil {
return nil, nil, 0, err
}
toRemoteSig, err := l.commitToRemoteSig.ToSignature()
if err != nil {
return nil, nil, 0, err
}
witness := make(wire.TxWitness, 2)
witness[0] = append(toRemoteSig.Serialize(), byte(txscript.SigHashAll))
witness[1] = toRemoteScript
pkScript, err := txscript.ParsePkScript(toRemoteScriptHash)
if err != nil {
return nil, nil, 0, err
}
return &pkScript, witness, 0, nil
}
// HasCommitToRemoteOutput returns true if the blob contains a to-remote p2wkh
// pubkey.
//
// NOTE: This is part of the JusticeKit interface.
func (l *legacyJusticeKit) HasCommitToRemoteOutput() bool {
return btcec.IsCompressedPubKey(l.commitToRemotePubKey[:])
}
// SweepAddress returns the sweep address to be used on the justice tx
// output.
//
// NOTE: This is part of the JusticeKit interface.
func (l *legacyJusticeKit) SweepAddress() []byte {
return l.sweepAddress
}
// AddToLocalSig adds the to-local signature to the kit.
//
// NOTE: This is part of the JusticeKit interface.
func (l *legacyJusticeKit) AddToLocalSig(sig lnwire.Sig) {
l.commitToLocalSig = sig
}
// AddToRemoteSig adds the to-remote signature to the kit.
//
// NOTE: This is part of the JusticeKit interface.
func (l *legacyJusticeKit) AddToRemoteSig(sig lnwire.Sig) {
l.commitToRemoteSig = sig
}
// PlainTextSize is the size of the encoded-but-unencrypted blob in
// bytes.
//
// NOTE: This is part of the JusticeKit interface.
func (l *legacyJusticeKit) PlainTextSize() int {
return V0PlaintextSize
}
// anchorJusticeKit is an implementation of the JusticeKit interface which can
// be used for backing up commitments of anchor channels. It inherits most of
// the methods from the legacyJusticeKit and overrides the
// ToRemoteOutputSpendInfo method since the to-remote output of an anchor
// output is a P2WSH instead of the P2WPKH used by the legacy channels.
type anchorJusticeKit struct {
legacyJusticeKit
}
// A compile-time check to ensure that legacyJusticeKit implements the
// JusticeKit interface.
var _ JusticeKit = (*anchorJusticeKit)(nil)
// newAnchorJusticeKit constructs a new anchorJusticeKit.
func newAnchorJusticeKit(sweepScript []byte,
breachInfo *lnwallet.BreachRetribution,
withToRemote bool) *anchorJusticeKit {
legacyKit := newLegacyJusticeKit(sweepScript, breachInfo, withToRemote)
return &anchorJusticeKit{
legacyJusticeKit: *legacyKit,
}
}
// ToRemoteOutputSpendInfo returns the info required to send the to-remote
// output. It returns the output pub key script, the witness required to spend
// the output and the sequence to apply.
//
// NOTE: This is part of the JusticeKit interface.
func (a *anchorJusticeKit) ToRemoteOutputSpendInfo() (*txscript.PkScript,
wire.TxWitness, uint32, error) {
if !btcec.IsCompressedPubKey(a.commitToRemotePubKey[:]) {
return nil, nil, 0, ErrNoCommitToRemoteOutput
}
pk, err := btcec.ParsePubKey(a.commitToRemotePubKey[:])
if err != nil {
return nil, nil, 0, err
}
toRemoteScript, err := input.CommitScriptToRemoteConfirmed(pk)
if err != nil {
return nil, nil, 0, err
}
toRemoteScriptHash, err := input.WitnessScriptHash(toRemoteScript)
if err != nil {
return nil, nil, 0, err
}
toRemoteSig, err := a.commitToRemoteSig.ToSignature()
if err != nil {
return nil, nil, 0, err
}
witness := make([][]byte, 2)
witness[0] = append(toRemoteSig.Serialize(), byte(txscript.SigHashAll))
witness[1] = toRemoteScript
pkScript, err := txscript.ParsePkScript(toRemoteScriptHash)
if err != nil {
return nil, nil, 0, err
}
return &pkScript, witness, 1, nil
}
// taprootJusticeKit is an implementation of the JusticeKit interface which can
// be used for backing up commitments of taproot channels.
type taprootJusticeKit struct {
justiceKitPacketV1
}
// A compile-time check to ensure that taprootJusticeKit implements the
// JusticeKit interface.
var _ JusticeKit = (*taprootJusticeKit)(nil)
// newTaprootJusticeKit constructs a new taprootJusticeKit.
func newTaprootJusticeKit(sweepScript []byte,
breachInfo *lnwallet.BreachRetribution,
withToRemote bool) (*taprootJusticeKit, error) {
keyRing := breachInfo.KeyRing
// TODO(roasbeef): aux leaf tower updates needed
tree, err := input.NewLocalCommitScriptTree(
breachInfo.RemoteDelay, keyRing.ToLocalKey,
keyRing.RevocationKey, fn.None[txscript.TapLeaf](),
)
if err != nil {
return nil, err
}
packet := justiceKitPacketV1{
sweepAddress: sweepScript,
revocationPubKey: toBlobSchnorrPubKey(
keyRing.RevocationKey,
),
localDelayPubKey: toBlobSchnorrPubKey(keyRing.ToLocalKey),
delayScriptHash: tree.SettleLeaf.TapHash(),
}
if withToRemote {
packet.commitToRemotePubKey = toBlobPubKey(keyRing.ToRemoteKey)
}
return &taprootJusticeKit{packet}, nil
}
// ToLocalOutputSpendInfo returns the info required to send the to-local
// output. It returns the output pubkey script and the witness required
// to spend the output.
//
// NOTE: This is part of the JusticeKit interface.
func (t *taprootJusticeKit) ToLocalOutputSpendInfo() (*txscript.PkScript,
wire.TxWitness, error) {
revocationPubKey, err := schnorr.ParsePubKey(t.revocationPubKey[:])
if err != nil {
return nil, nil, err
}
localDelayedPubKey, err := schnorr.ParsePubKey(t.localDelayPubKey[:])
if err != nil {
return nil, nil, err
}
revokeScript, err := input.TaprootLocalCommitRevokeScript(
localDelayedPubKey, revocationPubKey,
)
if err != nil {
return nil, nil, err
}
revokeLeaf := txscript.NewBaseTapLeaf(revokeScript)
revokeLeafHash := revokeLeaf.TapHash()
rootHash := tapBranchHash(revokeLeafHash[:], t.delayScriptHash[:])
outputKey := txscript.ComputeTaprootOutputKey(
&input.TaprootNUMSKey, rootHash[:],
)
scriptPk, err := input.PayToTaprootScript(outputKey)
if err != nil {
return nil, nil, err
}
ctrlBlock := txscript.ControlBlock{
InternalKey: &input.TaprootNUMSKey,
OutputKeyYIsOdd: isOddPub(outputKey),
LeafVersion: revokeLeaf.LeafVersion,
InclusionProof: t.delayScriptHash[:],
}
ctrlBytes, err := ctrlBlock.ToBytes()
if err != nil {
return nil, nil, err
}
toLocalSig, err := t.commitToLocalSig.ToSignature()
if err != nil {
return nil, nil, err
}
witness := make([][]byte, 3)
witness[0] = toLocalSig.Serialize()
witness[1] = revokeScript
witness[2] = ctrlBytes
pkScript, err := txscript.ParsePkScript(scriptPk)
if err != nil {
return nil, nil, err
}
return &pkScript, witness, nil
}
// ToRemoteOutputSpendInfo returns the info required to send the to-remote
// output. It returns the output pubkey script, the witness required to spend
// the output and the sequence to apply.
//
// NOTE: This is part of the JusticeKit interface.
func (t *taprootJusticeKit) ToRemoteOutputSpendInfo() (*txscript.PkScript,
wire.TxWitness, uint32, error) {
if len(t.commitToRemotePubKey[:]) == 0 {
return nil, nil, 0, ErrNoCommitToRemoteOutput
}
toRemotePk, err := btcec.ParsePubKey(t.commitToRemotePubKey[:])
if err != nil {
return nil, nil, 0, err
}
scriptTree, err := input.NewRemoteCommitScriptTree(
toRemotePk, fn.None[txscript.TapLeaf](),
)
if err != nil {
return nil, nil, 0, err
}
script, err := input.PayToTaprootScript(scriptTree.TaprootKey)
if err != nil {
return nil, nil, 0, err
}
settleControlBlock := input.MakeTaprootCtrlBlock(
scriptTree.SettleLeaf.Script, &input.TaprootNUMSKey,
scriptTree.TapscriptTree,
)
ctrl, err := settleControlBlock.ToBytes()
if err != nil {
return nil, nil, 0, err
}
toRemoteSig, err := t.commitToRemoteSig.ToSignature()
if err != nil {
return nil, nil, 0, err
}
witness := make([][]byte, 3)
witness[0] = toRemoteSig.Serialize()
witness[1] = scriptTree.SettleLeaf.Script
witness[2] = ctrl
pkScript, err := txscript.ParsePkScript(script)
if err != nil {
return nil, nil, 0, err
}
return &pkScript, witness, 1, nil
}
// HasCommitToRemoteOutput returns true if the blob contains a to-remote pubkey.
//
// NOTE: This is part of the JusticeKit interface.
func (t *taprootJusticeKit) HasCommitToRemoteOutput() bool {
return btcec.IsCompressedPubKey(t.commitToRemotePubKey[:])
}
// AddToLocalSig adds the to-local signature to the kit.
//
// NOTE: This is part of the JusticeKit interface.
func (t *taprootJusticeKit) AddToLocalSig(sig lnwire.Sig) {
t.commitToLocalSig = sig
}
// AddToRemoteSig adds the to-remote signature to the kit.
//
// NOTE: This is part of the JusticeKit interface.
func (t *taprootJusticeKit) AddToRemoteSig(sig lnwire.Sig) {
t.commitToRemoteSig = sig
}
// SweepAddress returns the sweep address to be used on the justice tx output.
//
// NOTE: This is part of the JusticeKit interface.
func (t *taprootJusticeKit) SweepAddress() []byte {
return t.sweepAddress
}
// PlainTextSize is the size of the encoded-but-unencrypted blob in bytes.
//
// NOTE: This is part of the JusticeKit interface.
func (t *taprootJusticeKit) PlainTextSize() int {
return V1PlaintextSize
}
func tapBranchHash(l, r []byte) chainhash.Hash {
if bytes.Compare(l, r) > 0 {
l, r = r, l
}
return *chainhash.TaggedHash(chainhash.TagTapBranch, l, r)
}
func isOddPub(key *btcec.PublicKey) bool {
return key.SerializeCompressed()[0] == secp.PubKeyFormatCompressedOdd
}
package blob
import (
"bytes"
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/lnwire"
"golang.org/x/crypto/chacha20poly1305"
)
const (
// NonceSize is the length of a chacha20poly1305 nonce, 24 bytes.
NonceSize = chacha20poly1305.NonceSizeX
// KeySize is the length of a chacha20poly1305 key, 32 bytes.
KeySize = chacha20poly1305.KeySize
// CiphertextExpansion is the number of bytes padded to a plaintext
// encrypted with chacha20poly1305, which comes from a 16-byte MAC.
CiphertextExpansion = 16
// V0PlaintextSize is the plaintext size of a version 0 encoded blob.
// sweep address length: 1 byte
// padded sweep address: 42 bytes
// revocation pubkey: 33 bytes
// local delay pubkey: 33 bytes
// csv delay: 4 bytes
// commit to-local revocation sig: 64 bytes
// commit to-remote pubkey: 33 bytes, maybe blank
// commit to-remote sig: 64 bytes, maybe blank
V0PlaintextSize = 274
// V1PlaintextSize is the plaintext size of a version 1 encoded blob.
// sweep address length: 1 byte
// padded sweep address: 42 bytes
// revocation pubkey: 32 bytes
// local delay pubkey: 32 bytes
// commit to-local revocation sig: 64 bytes
// hash of to-local delay script: 32 bytes
// commit to-remote pubkey: 33 bytes, maybe blank
// commit to-remote sig: 64 bytes, maybe blank
V1PlaintextSize = 300
// MaxSweepAddrSize defines the maximum sweep address size that can be
// encoded in a blob.
MaxSweepAddrSize = 42
)
var (
// byteOrder specifies a big-endian encoding of all integer values.
byteOrder = binary.BigEndian
// ErrUnknownBlobType signals that we don't understand the requested
// blob encoding scheme.
ErrUnknownBlobType = errors.New("unknown blob type")
// ErrCiphertextTooSmall is a decryption error signaling that the
// ciphertext is smaller than the ciphertext expansion factor.
ErrCiphertextTooSmall = errors.New(
"ciphertext is too small for chacha20poly1305",
)
// ErrNoCommitToRemoteOutput is returned when trying to retrieve the
// commit to-remote output from the blob, though none exists.
ErrNoCommitToRemoteOutput = errors.New(
"cannot obtain commit to-remote p2wkh output script from blob",
)
// ErrSweepAddressToLong is returned when trying to encode or decode a
// sweep address with length greater than the maximum length of 42
// bytes, which supports p2wkh and p2sh addresses.
ErrSweepAddressToLong = fmt.Errorf(
"sweep address must be less than or equal to %d bytes long",
MaxSweepAddrSize,
)
)
// Size returns the size of the encoded-and-encrypted blob in bytes.
//
// nonce: 24 bytes
// enciphered plaintext: n bytes
// MAC: 16 bytes
func Size(kit JusticeKit) int {
return NonceSize + kit.PlainTextSize() + CiphertextExpansion
}
// schnorrPubKey is a 32-byte serialized x-only public key.
type schnorrPubKey [32]byte
// toBlobSchnorrPubKey serializes the given public key into a schnorrPubKey that
// can be set as a field on a JusticeKit.
func toBlobSchnorrPubKey(pubKey *btcec.PublicKey) schnorrPubKey {
var blobPubKey schnorrPubKey
copy(blobPubKey[:], schnorr.SerializePubKey(pubKey))
return blobPubKey
}
// pubKey is a 33-byte, serialized compressed public key.
type pubKey [33]byte
// toBlobPubKey serializes the given public key into a pubKey that can be set
// as a field on a JusticeKit.
func toBlobPubKey(pk *btcec.PublicKey) pubKey {
var blobPubKey pubKey
copy(blobPubKey[:], pk.SerializeCompressed())
return blobPubKey
}
// justiceKitPacketV0 is lé Blob of Justice. The JusticeKit contains information
// required to construct a justice transaction, that sweeps a remote party's
// revoked commitment transaction. It supports encryption and decryption using
// chacha20poly1305, allowing the client to encrypt the contents of the blob,
// and for a watchtower to later decrypt if action must be taken.
type justiceKitPacketV0 struct {
// sweepAddress is the witness program of the output where the client's
// fund will be deposited. This value is included in the blobs, as
// opposed to the session info, such that the sweep addresses can't be
// correlated across sessions and/or towers.
//
// NOTE: This is chosen to be the length of a maximally sized witness
// program.
sweepAddress []byte
// revocationPubKey is the compressed pubkey that guards the revocation
// clause of the remote party's to-local output.
revocationPubKey pubKey
// localDelayPubKey is the compressed pubkey in the to-local script of
// the remote party, which guards the path where the remote party
// claims their commitment output.
localDelayPubKey pubKey
// csvDelay is the relative timelock in the remote party's to-local
// output, which the remote party must wait out before sweeping their
// commitment output.
csvDelay uint32
// commitToLocalSig is a signature under RevocationPubKey using
// SIGHASH_ALL.
commitToLocalSig lnwire.Sig
// commitToRemotePubKey is the public key in the to-remote output of the
// revoked commitment transaction.
//
// NOTE: This value is only used if it contains a valid compressed
// public key.
commitToRemotePubKey pubKey
// commitToRemoteSig is a signature under CommitToRemotePubKey using
// SIGHASH_ALL.
//
// NOTE: This value is only used if CommitToRemotePubKey contains a
// valid compressed public key.
commitToRemoteSig lnwire.Sig
}
// Encrypt encodes the blob of justice using encoding version, and then
// creates a ciphertext using chacha20poly1305 under the chosen (nonce, key)
// pair.
//
// NOTE: It is the caller's responsibility to ensure that this method is only
// called once for a given (nonce, key) pair.
func Encrypt(kit JusticeKit, key BreachKey) ([]byte, error) {
// Encode the plaintext using the provided version, to obtain the
// plaintext bytes.
var ptxtBuf bytes.Buffer
err := kit.encode(&ptxtBuf)
if err != nil {
return nil, err
}
// Create a new chacha20poly1305 cipher, using a 32-byte key.
cipher, err := chacha20poly1305.NewX(key[:])
if err != nil {
return nil, err
}
// Allocate the ciphertext, which will contain the nonce, encrypted
// plaintext and MAC.
plaintext := ptxtBuf.Bytes()
ciphertext := make([]byte, Size(kit))
// Generate a random 24-byte nonce in the ciphertext's prefix.
nonce := ciphertext[:NonceSize]
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
// Finally, encrypt the plaintext using the given nonce, storing the
// result in the ciphertext buffer.
cipher.Seal(ciphertext[NonceSize:NonceSize], nonce, plaintext, nil)
return ciphertext, nil
}
// Decrypt unenciphers a blob of justice by decrypting the ciphertext using
// chacha20poly1305 with the chosen (nonce, key) pair. The internal plaintext is
// then deserialized using the given encoding version.
func Decrypt(key BreachKey, ciphertext []byte,
blobType Type) (JusticeKit, error) {
// Fail if the blob's overall length is less than required for the nonce
// and expansion factor.
if len(ciphertext) < NonceSize+CiphertextExpansion {
return nil, ErrCiphertextTooSmall
}
// Create a new chacha20poly1305 cipher, using a 32-byte key.
cipher, err := chacha20poly1305.NewX(key[:])
if err != nil {
return nil, err
}
// Allocate the final buffer that will contain the blob's plaintext
// bytes, which is computed by subtracting the ciphertext expansion
// factor from the blob's length.
plaintext := make([]byte, len(ciphertext)-CiphertextExpansion)
// Decrypt the ciphertext, placing the resulting plaintext in our
// plaintext buffer.
nonce := ciphertext[:NonceSize]
_, err = cipher.Open(plaintext[:0], nonce, ciphertext[NonceSize:], nil)
if err != nil {
return nil, err
}
commitment, err := blobType.CommitmentType(nil)
if err != nil {
return nil, err
}
kit, err := commitment.EmptyJusticeKit()
if err != nil {
return nil, err
}
// If decryption succeeded, we will then decode the plaintext bytes
// using the specified blob version.
err = kit.decode(bytes.NewReader(plaintext))
if err != nil {
return nil, err
}
return kit, nil
}
// encode encodes the JusticeKit using the version 0 encoding scheme to the
// provided io.Writer. The encoding supports sweeping of the commit to-local
// output, and optionally the commit to-remote output. The encoding produces a
// constant-size plaintext size of 274 bytes.
//
// blob version 0 plaintext encoding:
//
// sweep address length: 1 byte
// padded sweep address: 42 bytes
// revocation pubkey: 33 bytes
// local delay pubkey: 33 bytes
// csv delay: 4 bytes
// commit to-local revocation sig: 64 bytes
// commit to-remote pubkey: 33 bytes, maybe blank
// commit to-remote sig: 64 bytes, maybe blank
func (b *justiceKitPacketV0) encode(w io.Writer) error {
// Assert the sweep address length is sane.
if len(b.sweepAddress) > MaxSweepAddrSize {
return ErrSweepAddressToLong
}
// Write the actual length of the sweep address as a single byte.
err := binary.Write(w, byteOrder, uint8(len(b.sweepAddress)))
if err != nil {
return err
}
// Pad the sweep address to our maximum length of 42 bytes.
var sweepAddressBuf [MaxSweepAddrSize]byte
copy(sweepAddressBuf[:], b.sweepAddress)
// Write padded 42-byte sweep address.
_, err = w.Write(sweepAddressBuf[:])
if err != nil {
return err
}
// Write 33-byte revocation public key.
_, err = w.Write(b.revocationPubKey[:])
if err != nil {
return err
}
// Write 33-byte local delay public key.
_, err = w.Write(b.localDelayPubKey[:])
if err != nil {
return err
}
// Write 4-byte CSV delay.
err = binary.Write(w, byteOrder, b.csvDelay)
if err != nil {
return err
}
// Write 64-byte revocation signature for commit to-local output.
_, err = w.Write(b.commitToLocalSig.RawBytes())
if err != nil {
return err
}
// Write 33-byte commit to-remote public key, which may be blank.
_, err = w.Write(b.commitToRemotePubKey[:])
if err != nil {
return err
}
// Write 64-byte commit to-remote signature, which may be blank.
_, err = w.Write(b.commitToRemoteSig.RawBytes())
return err
}
// decode reconstructs a JusticeKit from the io.Reader, using version 0
// encoding scheme. This will parse a constant size input stream of 274 bytes to
// recover information for the commit to-local output, and possibly the commit
// to-remote output.
//
// blob version 0 plaintext encoding:
//
// sweep address length: 1 byte
// padded sweep address: 42 bytes
// revocation pubkey: 33 bytes
// local delay pubkey: 33 bytes
// csv delay: 4 bytes
// commit to-local revocation sig: 64 bytes
// commit to-remote pubkey: 33 bytes, maybe blank
// commit to-remote sig: 64 bytes, maybe blank
func (b *justiceKitPacketV0) decode(r io.Reader) error {
// Read the sweep address length as a single byte.
var sweepAddrLen uint8
err := binary.Read(r, byteOrder, &sweepAddrLen)
if err != nil {
return err
}
// Assert the sweep address length is sane.
if sweepAddrLen > MaxSweepAddrSize {
return ErrSweepAddressToLong
}
// Read padded 42-byte sweep address.
var sweepAddressBuf [MaxSweepAddrSize]byte
_, err = io.ReadFull(r, sweepAddressBuf[:])
if err != nil {
return err
}
// Parse sweep address from padded buffer.
b.sweepAddress = make([]byte, sweepAddrLen)
copy(b.sweepAddress, sweepAddressBuf[:])
// Read 33-byte revocation public key.
_, err = io.ReadFull(r, b.revocationPubKey[:])
if err != nil {
return err
}
// Read 33-byte local delay public key.
_, err = io.ReadFull(r, b.localDelayPubKey[:])
if err != nil {
return err
}
// Read 4-byte CSV delay.
err = binary.Read(r, byteOrder, &b.csvDelay)
if err != nil {
return err
}
// Read 64-byte revocation signature for commit to-local output.
var localSig [64]byte
_, err = io.ReadFull(r, localSig[:])
if err != nil {
return err
}
b.commitToLocalSig, err = lnwire.NewSigFromWireECDSA(localSig[:])
if err != nil {
return err
}
var (
commitToRemotePubkey pubKey
commitToRemoteSig [64]byte
)
// Read 33-byte commit to-remote public key, which may be discarded.
_, err = io.ReadFull(r, commitToRemotePubkey[:])
if err != nil {
return err
}
// Read 64-byte commit to-remote signature, which may be discarded.
_, err = io.ReadFull(r, commitToRemoteSig[:])
if err != nil {
return err
}
// Only populate the commit to-remote fields in the decoded blob if a
// valid compressed public key was read from the reader.
if btcec.IsCompressedPubKey(commitToRemotePubkey[:]) {
b.commitToRemotePubKey = commitToRemotePubkey
b.commitToRemoteSig, err = lnwire.NewSigFromWireECDSA(
commitToRemoteSig[:],
)
if err != nil {
return err
}
}
return nil
}
// justiceKitPacketV1 is the Blob of Justice for taproot channels.
type justiceKitPacketV1 struct {
// sweepAddress is the witness program of the output where the client's
// fund will be deposited. This value is included in the blobs, as
// opposed to the session info, such that the sweep addresses can't be
// correlated across sessions and/or towers.
//
// NOTE: This is chosen to be the length of a maximally sized witness
// program.
sweepAddress []byte
// revocationPubKey is the x-only pubkey that guards the revocation
// clause of the remote party's to-local output.
revocationPubKey schnorrPubKey
// localDelayPubKey is the x-only pubkey in the to-local script of
// the remote party, which guards the path where the remote party
// claims their commitment output.
localDelayPubKey schnorrPubKey
// delayScriptHash is the hash of the to_local delay script that is used
// in the TapTree.
delayScriptHash [chainhash.HashSize]byte
// commitToLocalSig is a signature under revocationPubKey using
// SIGHASH_DEFAULT.
commitToLocalSig lnwire.Sig
// commitToRemotePubKey is the public key in the to-remote output of the
// revoked commitment transaction. This uses a 33-byte compressed pubkey
// encoding unlike the other public keys because it will not always be
// present and so this gives us an easy way to check if it is present or
// not.
//
// NOTE: This value is only used if it contains a valid compressed
// public key.
commitToRemotePubKey pubKey
// commitToRemoteSig is a signature under commitToRemotePubKey using
// SIGHASH_DEFAULT.
//
// NOTE: This value is only used if commitToRemotePubKey contains a
// valid compressed public key.
commitToRemoteSig lnwire.Sig
}
// encode encodes the justiceKitPacketV1 to the provided io.Writer. The encoding
// supports sweeping of the commit to-local output, and optionally the commit
// to-remote output. The encoding produces a constant-size plaintext size of
// 300 bytes.
//
// blob version 1 plaintext encoding:
//
// sweep address length: 1 byte
// padded sweep address: 42 bytes
// revocation pubkey: 32 bytes
// local delay pubkey: 32 bytes
// commit to-local revocation sig: 64 bytes
// hash of to-local delay script: 32 bytes
// commit to-remote pubkey: 33 bytes, maybe blank
// commit to-remote sig: 64 bytes, maybe blank
func (t *justiceKitPacketV1) encode(w io.Writer) error {
// Assert the sweep address length is sane.
if len(t.sweepAddress) > MaxSweepAddrSize {
return ErrSweepAddressToLong
}
// Write the actual length of the sweep address as a single byte.
err := binary.Write(w, byteOrder, uint8(len(t.sweepAddress)))
if err != nil {
return err
}
// Pad the sweep address to our maximum length of 42 bytes.
var sweepAddressBuf [MaxSweepAddrSize]byte
copy(sweepAddressBuf[:], t.sweepAddress)
// Write padded 42-byte sweep address.
_, err = w.Write(sweepAddressBuf[:])
if err != nil {
return err
}
// Write 32-byte revocation public key.
_, err = w.Write(t.revocationPubKey[:])
if err != nil {
return err
}
// Write 32-byte local delay public key.
_, err = w.Write(t.localDelayPubKey[:])
if err != nil {
return err
}
// Write 64-byte revocation signature for commit to-local output.
_, err = w.Write(t.commitToLocalSig.RawBytes())
if err != nil {
return err
}
// Write 32-byte hash of the to-local delay script.
_, err = w.Write(t.delayScriptHash[:])
if err != nil {
return err
}
// Write 33-byte commit to-remote public key, which may be blank.
_, err = w.Write(t.commitToRemotePubKey[:])
if err != nil {
return err
}
// Write 64-byte commit to-remote signature, which may be blank.
_, err = w.Write(t.commitToRemoteSig.RawBytes())
return err
}
// decode reconstructs a justiceKitPacketV1 from the io.Reader, using version 1
// encoding scheme. This will parse a constant size input stream of 300 bytes to
// recover information for the commit to-local output, and possibly the commit
// to-remote output.
//
// blob version 1 plaintext encoding:
//
// sweep address length: 1 byte
// padded sweep address: 42 bytes
// revocation pubkey: 32 bytes
// local delay pubkey: 32 bytes
// commit to-local revocation sig: 64 bytes
// hash of to-local delay script: 32 bytes
// commit to-remote pubkey: 33 bytes, maybe blank
// commit to-remote sig: 64 bytes, maybe blank
func (t *justiceKitPacketV1) decode(r io.Reader) error {
// Read the sweep address length as a single byte.
var sweepAddrLen uint8
err := binary.Read(r, byteOrder, &sweepAddrLen)
if err != nil {
return err
}
// Assert the sweep address length is sane.
if sweepAddrLen > MaxSweepAddrSize {
return ErrSweepAddressToLong
}
// Read padded 42-byte sweep address.
var sweepAddressBuf [MaxSweepAddrSize]byte
_, err = io.ReadFull(r, sweepAddressBuf[:])
if err != nil {
return err
}
// Parse sweep address from padded buffer.
t.sweepAddress = make([]byte, sweepAddrLen)
copy(t.sweepAddress, sweepAddressBuf[:])
// Read 32-byte revocation public key.
_, err = io.ReadFull(r, t.revocationPubKey[:])
if err != nil {
return err
}
// Read 32-byte local delay public key.
_, err = io.ReadFull(r, t.localDelayPubKey[:])
if err != nil {
return err
}
// Read 64-byte revocation signature for commit to-local output.
var localSig [64]byte
_, err = io.ReadFull(r, localSig[:])
if err != nil {
return err
}
// Read 32-byte to-local delay script hash.
_, err = io.ReadFull(r, t.delayScriptHash[:])
if err != nil {
return err
}
t.commitToLocalSig, err = lnwire.NewSigFromSchnorrRawSignature(
localSig[:],
)
if err != nil {
return err
}
var (
commitToRemotePubkey pubKey
commitToRemoteSig [64]byte
)
// Read 33-byte commit to-remote public key, which may be discarded.
_, err = io.ReadFull(r, commitToRemotePubkey[:])
if err != nil {
return err
}
// Read 64-byte commit to-remote signature, which may be discarded.
_, err = io.ReadFull(r, commitToRemoteSig[:])
if err != nil {
return err
}
// Only populate the commit to-remote fields in the decoded blob if a
// valid compressed public key was read from the reader.
if !btcec.IsCompressedPubKey(commitToRemotePubkey[:]) {
return nil
}
t.commitToRemotePubKey = commitToRemotePubkey
t.commitToRemoteSig, err = lnwire.NewSigFromSchnorrRawSignature(
commitToRemoteSig[:],
)
if err != nil {
return err
}
return nil
}
package blob
import (
"fmt"
"strings"
"github.com/lightningnetwork/lnd/channeldb"
)
// Flag represents a specify option that can be present in a Type.
type Flag uint16
const (
// FlagReward signals that the justice transaction should contain an
// additional output for itself. Signatures sent by the client should
// include the reward script negotiated during session creation. Without
// the flag, there is only one output sweeping clients funds back to
// them solely.
FlagReward Flag = 1
// FlagCommitOutputs signals that the blob contains the information
// required to sweep commitment outputs.
FlagCommitOutputs Flag = 1 << 1
// FlagAnchorChannel signals that this blob is meant to spend an anchor
// channel, and therefore must expect a P2WSH-style to-remote output if
// one exists.
FlagAnchorChannel Flag = 1 << 2
// FlagTaprootChannel signals that this blob is meant to spend a
// taproot channel and therefore must expect P2TR outputs.
FlagTaprootChannel Flag = 1 << 3
)
// Type returns a Type consisting solely of this flag enabled.
func (f Flag) Type() Type {
return Type(f)
}
// String returns the name of the flag.
func (f Flag) String() string {
switch f {
case FlagReward:
return "FlagReward"
case FlagCommitOutputs:
return "FlagCommitOutputs"
case FlagAnchorChannel:
return "FlagAnchorChannel"
case FlagTaprootChannel:
return "FlagTaprootChannel"
default:
return "FlagUnknown"
}
}
// Type is a bit vector composed of Flags that govern various aspects of
// reconstructing the justice transaction from an encrypted blob. The flags can
// be used to signal behaviors such as which inputs are being swept, which
// outputs should be added to the justice transaction, or modify serialization
// of the blob itself.
type Type uint16
const (
// TypeAltruistCommit sweeps only commitment outputs to a sweep address
// controlled by the user, and does not give the tower a reward.
TypeAltruistCommit = Type(FlagCommitOutputs)
// TypeAltruistAnchorCommit sweeps only commitment outputs from an
// anchor commitment to a sweep address controlled by the user, and does
// not give the tower a reward.
TypeAltruistAnchorCommit = Type(FlagCommitOutputs | FlagAnchorChannel)
// TypeRewardCommit sweeps only commitment outputs to a sweep address
// controlled by the user, and pays a negotiated reward to the tower.
TypeRewardCommit = Type(FlagCommitOutputs | FlagReward)
// TypeAltruistTaprootCommit sweeps only the commitment outputs from a
// taproot channel commitment to a sweep address controlled by the user,
// and does not give the tower a reward.
TypeAltruistTaprootCommit = Type(FlagCommitOutputs | FlagTaprootChannel)
)
// TypeFromChannel returns the appropriate blob Type for the given channel
// type.
func TypeFromChannel(chanType channeldb.ChannelType) Type {
switch {
case chanType.IsTaproot():
return TypeAltruistTaprootCommit
case chanType.HasAnchors():
return TypeAltruistAnchorCommit
default:
return TypeAltruistCommit
}
}
// Identifier returns a unique, stable string identifier for the blob Type.
func (t Type) Identifier() (string, error) {
switch t {
case TypeAltruistCommit:
return "legacy", nil
case TypeAltruistAnchorCommit:
return "anchor", nil
case TypeRewardCommit:
return "reward", nil
case TypeAltruistTaprootCommit:
return "taproot", nil
default:
return "", fmt.Errorf("unknown blob type: %v", t)
}
}
// CommitmentType returns the appropriate CommitmentType for the given blob Type
// and channel type.
func (t Type) CommitmentType(chanType *channeldb.ChannelType) (CommitmentType,
error) {
switch {
case t.Has(FlagTaprootChannel):
return TaprootCommitment, nil
case t.Has(FlagAnchorChannel):
return AnchorCommitment, nil
case t.Has(FlagCommitOutputs):
if chanType != nil && chanType.IsTweakless() {
return LegacyTweaklessCommitment, nil
}
return LegacyCommitment, nil
default:
return 0, ErrUnknownBlobType
}
}
// Has returns true if the Type has the passed flag enabled.
func (t Type) Has(flag Flag) bool {
return Flag(t)&flag == flag
}
// TypeFromFlags creates a single Type from an arbitrary list of flags.
func TypeFromFlags(flags ...Flag) Type {
var typ Type
for _, flag := range flags {
typ |= Type(flag)
}
return typ
}
// IsAnchorChannel returns true if the blob type is for an anchor channel.
func (t Type) IsAnchorChannel() bool {
return t.Has(FlagAnchorChannel)
}
// IsTaprootChannel returns true if the blob type is for a taproot channel.
func (t Type) IsTaprootChannel() bool {
return t.Has(FlagTaprootChannel)
}
// knownFlags maps the supported flags to their name.
var knownFlags = map[Flag]struct{}{
FlagReward: {},
FlagCommitOutputs: {},
FlagAnchorChannel: {},
FlagTaprootChannel: {},
}
// String returns a human-readable description of a Type.
func (t Type) String() string {
var (
hrPieces []string
hasUnknownFlags bool
)
// Iterate through the possible flags from highest to lowest. This will
// ensure that the human readable names will be in the same order as the
// bits (left to right) if the type were to be printed in big-endian
// byte order.
for f := Flag(1 << 15); f != 0; f >>= 1 {
// If this flag is known, we'll add a human-readable name or its
// inverse depending on whether the type has this flag set.
if _, ok := knownFlags[f]; ok {
if t.Has(f) {
hrPieces = append(hrPieces, f.String())
} else {
hrPieces = append(hrPieces, "No-"+f.String())
}
} else {
// Make note of any unknown flags that this type has
// set. If any are present, we'll prepend the bit-wise
// representation of the type in the final string.
if t.Has(f) {
hasUnknownFlags = true
}
}
}
// If there were no unknown flags, we'll simply return the list of human
// readable pieces.
if !hasUnknownFlags {
return fmt.Sprintf("[%s]", strings.Join(hrPieces, "|"))
}
// Otherwise, we'll prepend the bit-wise representation to the human
// readable names.
return fmt.Sprintf("%016b[%s]", t, strings.Join(hrPieces, "|"))
}
// supportedTypes is the set of all configurations known to be supported by the
// package.
var supportedTypes = map[Type]struct{}{
TypeAltruistCommit: {},
TypeRewardCommit: {},
TypeAltruistAnchorCommit: {},
TypeAltruistTaprootCommit: {},
}
// IsSupportedType returns true if the given type is supported by the package.
func IsSupportedType(blobType Type) bool {
_, ok := supportedTypes[blobType]
return ok
}
// SupportedTypes returns a list of all supported blob types.
func SupportedTypes() []Type {
supported := make([]Type, 0, len(supportedTypes))
for t := range supportedTypes {
supported = append(supported, t)
}
return supported
}
package watchtower
import (
"strconv"
"time"
)
// Conf specifies the watchtower options that can be configured from the command
// line or configuration file.
type Conf struct {
// RawListeners configures the watchtower's listening ports/interfaces.
RawListeners []string `long:"listen" description:"Add interfaces/ports to listen for peer connections"`
// RawExternalIPs configures the watchtower's external ports/interfaces.
RawExternalIPs []string `long:"externalip" description:"Add interfaces/ports where the watchtower can accept peer connections"`
// ReadTimeout specifies the duration the tower will wait when trying to
// read a message from a client before hanging up.
ReadTimeout time.Duration `long:"readtimeout" description:"Duration the watchtower server will wait for messages to be received before hanging up on clients"`
// WriteTimeout specifies the duration the tower will wait when trying
// to write a message from a client before hanging up.
WriteTimeout time.Duration `long:"writetimeout" description:"Duration the watchtower server will wait for messages to be written before hanging up on client connections"`
}
// DefaultConf returns a Conf with some default values filled in.
func DefaultConf() *Conf {
return &Conf{
ReadTimeout: DefaultReadTimeout,
WriteTimeout: DefaultWriteTimeout,
}
}
// Apply completes the passed Config struct by applying any parsed Conf options.
// If the corresponding values parsed by Conf are already set in the Config,
// those fields will be not be modified.
func (c *Conf) Apply(cfg *Config,
normalizer AddressNormalizer) (*Config, error) {
// Set the Config's listening addresses if they are empty.
if cfg.ListenAddrs == nil {
// Without a network, we will be unable to resolve the listening
// addresses.
if cfg.Net == nil {
return nil, ErrNoNetwork
}
// If no addresses are specified by the Config, we will resort
// to the default peer port.
if len(c.RawListeners) == 0 {
addr := DefaultListenAddr
c.RawListeners = append(c.RawListeners, addr)
}
// Normalize the raw listening addresses so that they can be
// used by the brontide listener.
var err error
cfg.ListenAddrs, err = normalizer(
c.RawListeners, strconv.Itoa(DefaultPeerPort),
cfg.Net.ResolveTCPAddr,
)
if err != nil {
return nil, err
}
}
// Set the Config's external ips if they are empty.
if cfg.ExternalIPs == nil {
// Without a network, we will be unable to resolve the external
// IP addresses.
if cfg.Net == nil {
return nil, ErrNoNetwork
}
var err error
cfg.ExternalIPs, err = normalizer(
c.RawExternalIPs, strconv.Itoa(DefaultPeerPort),
cfg.Net.ResolveTCPAddr,
)
if err != nil {
return nil, err
}
}
// If the Config has no read timeout, we will use the parsed Conf
// value.
if cfg.ReadTimeout == 0 && c.ReadTimeout != 0 {
cfg.ReadTimeout = c.ReadTimeout
}
// If the Config has no write timeout, we will use the parsed Conf
// value.
if cfg.WriteTimeout == 0 && c.WriteTimeout != 0 {
cfg.WriteTimeout = c.WriteTimeout
}
return cfg, nil
}
package watchtower
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/watchtower/lookout"
"github.com/lightningnetwork/lnd/watchtower/wtclient"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
"github.com/lightningnetwork/lnd/watchtower/wtserver"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("WTWR", nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
lookout.UseLogger(logger)
wtserver.UseLogger(logger)
wtclient.UseLogger(logger)
wtdb.UseLogger(logger)
}
package lookout
import (
"errors"
"fmt"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/txsort"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
)
var (
// ErrOutputNotFound signals that the breached output could not be found
// on the commitment transaction.
ErrOutputNotFound = errors.New("unable to find output on commit tx")
// ErrUnknownSweepAddrType signals that client provided an output that
// was not p2wkh or p2wsh.
ErrUnknownSweepAddrType = errors.New("sweep addr is not p2wkh or p2wsh")
)
// JusticeDescriptor contains the information required to sweep a breached
// channel on behalf of a victim. It supports the ability to create the justice
// transaction that sweeps the commitments and recover a cut of the channel for
// the watcher's eternal vigilance.
type JusticeDescriptor struct {
// BreachedCommitTx is the commitment transaction that caused the breach
// to be detected.
BreachedCommitTx *wire.MsgTx
// SessionInfo contains the contract with the watchtower client and
// the prenegotiated terms they agreed to.
SessionInfo *wtdb.SessionInfo
// JusticeKit contains the decrypted blob and information required to
// construct the transaction scripts and witnesses.
JusticeKit blob.JusticeKit
}
// breachedInput contains the required information to construct and spend
// breached outputs on a commitment transaction.
type breachedInput struct {
txOut *wire.TxOut
outPoint wire.OutPoint
witness [][]byte
sequence uint32
}
// commitToLocalInput extracts the information required to spend the commit
// to-local output.
func (p *JusticeDescriptor) commitToLocalInput() (*breachedInput, error) {
kit := p.JusticeKit
// Retrieve the to-local output script and witness from the justice kit.
toLocalPkScript, witness, err := kit.ToLocalOutputSpendInfo()
if err != nil {
return nil, err
}
// Locate the to-local output on the breaching commitment transaction.
toLocalIndex, toLocalTxOut, err := findTxOutByPkScript(
p.BreachedCommitTx, toLocalPkScript,
)
if err != nil {
return nil, err
}
// Construct the to-local outpoint that will be spent in the justice
// transaction.
toLocalOutPoint := wire.OutPoint{
Hash: p.BreachedCommitTx.TxHash(),
Index: toLocalIndex,
}
return &breachedInput{
txOut: toLocalTxOut,
outPoint: toLocalOutPoint,
witness: witness,
}, nil
}
// commitToRemoteInput extracts the information required to spend the commit
// to-remote output.
func (p *JusticeDescriptor) commitToRemoteInput() (*breachedInput, error) {
kit := p.JusticeKit
// Retrieve the to-remote output script, witness script and sequence
// from the justice kit.
toRemotePkScript, witness, seq, err := kit.ToRemoteOutputSpendInfo()
if err != nil {
return nil, err
}
// Locate the to-remote output on the breaching commitment transaction.
toRemoteIndex, toRemoteTxOut, err := findTxOutByPkScript(
p.BreachedCommitTx, toRemotePkScript,
)
if err != nil {
return nil, err
}
// Construct the to-remote outpoint which will be spent in the justice
// transaction.
toRemoteOutPoint := wire.OutPoint{
Hash: p.BreachedCommitTx.TxHash(),
Index: toRemoteIndex,
}
return &breachedInput{
txOut: toRemoteTxOut,
outPoint: toRemoteOutPoint,
witness: witness,
sequence: seq,
}, nil
}
// assembleJusticeTxn accepts the breached inputs recovered from state update
// and attempts to construct the justice transaction that sweeps the victims
// funds to their wallet and claims the watchtower's reward.
func (p *JusticeDescriptor) assembleJusticeTxn(txWeight lntypes.WeightUnit,
inputs ...*breachedInput) (*wire.MsgTx, error) {
justiceTxn := wire.NewMsgTx(2)
// First, construct add the breached inputs to our justice transaction
// and compute the total amount that will be swept.
var totalAmt btcutil.Amount
for _, inp := range inputs {
totalAmt += btcutil.Amount(inp.txOut.Value)
justiceTxn.AddTxIn(&wire.TxIn{
PreviousOutPoint: inp.outPoint,
Sequence: inp.sequence,
})
}
// Using the session's policy, compute the outputs that should be added
// to the justice transaction. In the case of an altruist sweep, there
// will be a single output paying back to the victim. Otherwise for a
// reward sweep, there will be two outputs, one of which pays back to
// the victim while the other gives a cut to the tower.
outputs, err := p.SessionInfo.Policy.ComputeJusticeTxOuts(
totalAmt, txWeight, p.JusticeKit.SweepAddress(),
p.SessionInfo.RewardAddress,
)
if err != nil {
return nil, err
}
// Attach the computed txouts to the justice transaction.
justiceTxn.TxOut = outputs
// Apply a BIP69 sort to the resulting transaction.
txsort.InPlaceSort(justiceTxn)
btx := btcutil.NewTx(justiceTxn)
if err := blockchain.CheckTransactionSanity(btx); err != nil {
return nil, err
}
// Since the transaction inputs could have been reordered as a result of the
// BIP69 sort, create an index mapping each prevout to it's new index.
inputIndex := make(map[wire.OutPoint]int)
for i, txIn := range justiceTxn.TxIn {
inputIndex[txIn.PreviousOutPoint] = i
}
// Attach each of the provided witnesses to the transaction.
prevOutFetcher, err := prevOutFetcher(inputs)
if err != nil {
return nil, fmt.Errorf("error creating previous output "+
"fetcher: %v", err)
}
hashes := txscript.NewTxSigHashes(justiceTxn, prevOutFetcher)
for _, inp := range inputs {
// Lookup the input's new post-sort position.
i := inputIndex[inp.outPoint]
justiceTxn.TxIn[i].Witness = inp.witness
// Validate the reconstructed witnesses to ensure they are
// valid for the breached inputs.
vm, err := txscript.NewEngine(
inp.txOut.PkScript, justiceTxn, i,
txscript.StandardVerifyFlags,
nil, hashes, inp.txOut.Value, prevOutFetcher,
)
if err != nil {
return nil, err
}
if err := vm.Execute(); err != nil {
log.Debugf("Failed to validate justice transaction: %s",
spew.Sdump(justiceTxn))
return nil, fmt.Errorf("error validating TX: %w", err)
}
}
return justiceTxn, nil
}
// CreateJusticeTxn computes the justice transaction that sweeps a breaching
// commitment transaction. The justice transaction is constructed by assembling
// the witnesses using data provided by the client in a prior state update.
//
// NOTE: An older version of ToLocalPenaltyWitnessSize underestimated the size
// of the witness by one byte, which could cause the signature(s) to break if
// the tower is reconstructing with the newer constant because the output values
// might differ. This method retains that original behavior to not invalidate
// historical signatures.
func (p *JusticeDescriptor) CreateJusticeTxn() (*wire.MsgTx, error) {
var (
sweepInputs = make([]*breachedInput, 0, 2)
weightEstimate input.TxWeightEstimator
)
commitmentType, err := p.SessionInfo.Policy.BlobType.CommitmentType(nil)
if err != nil {
return nil, err
}
// Add the sweep address's contribution, depending on whether it is a
// p2wkh or p2wsh output.
switch len(p.JusticeKit.SweepAddress()) {
case input.P2WPKHSize:
weightEstimate.AddP2WKHOutput()
case input.P2WSHSize:
weightEstimate.AddP2WSHOutput()
default:
return nil, ErrUnknownSweepAddrType
}
// Add our reward address to the weight estimate if the policy's blob
// type specifies a reward output.
if p.SessionInfo.Policy.BlobType.Has(blob.FlagReward) {
weightEstimate.AddP2WKHOutput()
}
// Assemble the breached to-local output from the justice descriptor and
// add it to our weight estimate.
toLocalInput, err := p.commitToLocalInput()
if err != nil {
return nil, err
}
// Get the weight for the to-local witness and add that to the
// estimator.
toLocalWitnessSize, err := commitmentType.ToLocalWitnessSize()
if err != nil {
return nil, err
}
weightEstimate.AddWitnessInput(toLocalWitnessSize)
sweepInputs = append(sweepInputs, toLocalInput)
log.Debugf("Found to local witness output=%#v, stack=%v",
toLocalInput.txOut, toLocalInput.witness)
// If the justice kit specifies that we have to sweep the to-remote
// output, we'll also try to assemble the output and add it to weight
// estimate if successful.
if p.JusticeKit.HasCommitToRemoteOutput() {
toRemoteInput, err := p.commitToRemoteInput()
if err != nil {
return nil, err
}
sweepInputs = append(sweepInputs, toRemoteInput)
log.Debugf("Found to remote witness output=%#v, stack=%v",
toRemoteInput.txOut, toRemoteInput.witness)
// Get the weight for the to-remote witness and add that to the
// estimator.
toRemoteWitnessSize, err := commitmentType.ToRemoteWitnessSize()
if err != nil {
return nil, err
}
weightEstimate.AddWitnessInput(toRemoteWitnessSize)
}
// TODO(conner): sweep htlc outputs
txWeight := weightEstimate.Weight()
return p.assembleJusticeTxn(txWeight, sweepInputs...)
}
// findTxOutByPkScript searches the given transaction for an output whose
// pkscript matches the query. If one is found, the TxOut is returned along with
// the index.
//
// NOTE: The search stops after the first match is found.
func findTxOutByPkScript(txn *wire.MsgTx,
pkScript *txscript.PkScript) (uint32, *wire.TxOut, error) {
found, index := input.FindScriptOutputIndex(txn, pkScript.Script())
if !found {
return 0, nil, ErrOutputNotFound
}
return index, txn.TxOut[index], nil
}
// prevOutFetcher returns a txscript.MultiPrevOutFetcher for the given set
// of inputs.
func prevOutFetcher(inputs []*breachedInput) (*txscript.MultiPrevOutFetcher,
error) {
fetcher := txscript.NewMultiPrevOutFetcher(nil)
for _, inp := range inputs {
if inp.txOut == nil {
return nil, fmt.Errorf("missing input utxo information")
}
fetcher.AddPrevOut(inp.outPoint, inp.txOut)
}
return fetcher, nil
}
package lookout
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("WTWR", nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package lookout
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/watchtower/blob"
)
// ErrLookoutExiting is an error that is returned when the lookout server is
// in the process of shutting down.
var ErrLookoutExiting = errors.New("lookout server is shutting down")
// Config houses the Lookout's required resources to properly fulfill it's duty,
// including block fetching, querying accepted state updates, and construction
// and publication of justice transactions.
type Config struct {
// DB provides persistent access to the watchtower's accepted state
// updates such that they can be queried as new blocks arrive from the
// network.
DB DB
// EpochRegistrar supports the ability to register for events corresponding to
// newly created blocks.
EpochRegistrar EpochRegistrar
// BlockFetcher supports the ability to fetch blocks from the backend or
// network.
BlockFetcher BlockFetcher
// Punisher handles the responsibility of crafting and broadcasting
// justice transaction for any breached transactions.
Punisher Punisher
// MinBackoff is the minimum amount of time to back-off before
// re-attempting to fetch a block.
MinBackoff time.Duration
// MaxBackoff is the maximum amount of time to back-off before
// re-attempting to fetch a block.
MaxBackoff time.Duration
// MaxNumRetries is the maximum number of times that we should
// re-attempt fetching a block before moving on.
MaxNumRetries int
}
// Lookout will check any incoming blocks against the transactions found in the
// database, and in case of matches send the information needed to create a
// penalty transaction to the punisher.
type Lookout struct {
started int32 // atomic
shutdown int32 // atomic
cfg *Config
wg sync.WaitGroup
quit chan struct{}
}
// New constructs a new Lookout from the given LookoutConfig.
func New(cfg *Config) *Lookout {
return &Lookout{
cfg: cfg,
quit: make(chan struct{}),
}
}
// Start safely spins up the Lookout and begins monitoring for breaches.
func (l *Lookout) Start() error {
if !atomic.CompareAndSwapInt32(&l.started, 0, 1) {
return nil
}
log.Infof("Starting lookout")
startEpoch, err := l.cfg.DB.GetLookoutTip()
if err != nil {
return err
}
if startEpoch == nil {
log.Infof("Starting lookout from chain tip")
} else {
log.Infof("Starting lookout from epoch(height=%d hash=%v)",
startEpoch.Height, startEpoch.Hash)
}
events, err := l.cfg.EpochRegistrar.RegisterBlockEpochNtfn(startEpoch)
if err != nil {
log.Errorf("Unable to register for block epochs: %v", err)
return err
}
l.wg.Add(1)
go l.watchBlocks(events)
log.Infof("Lookout started successfully")
return nil
}
// Stop safely shuts down the Lookout.
func (l *Lookout) Stop() error {
if !atomic.CompareAndSwapInt32(&l.shutdown, 0, 1) {
return nil
}
log.Infof("Stopping lookout")
close(l.quit)
l.wg.Wait()
log.Infof("Lookout stopped successfully")
return nil
}
// fetchBlockWithRetries attempts to fetch a block from the blockchain using
// its hash. If it fails to fetch the block, it will back-off and retry up to
// MaxNumRetries times.
func (l *Lookout) fetchBlockWithRetries(hash *chainhash.Hash) (*wire.MsgBlock,
error) {
backoff := l.cfg.MinBackoff
updateBackoff := func() {
backoff *= 2
if backoff > l.cfg.MaxBackoff {
backoff = l.cfg.MaxBackoff
}
}
var attempt int
for {
attempt++
block, err := l.cfg.BlockFetcher.GetBlock(hash)
if err == nil {
return block, nil
}
if attempt > l.cfg.MaxNumRetries {
return nil, fmt.Errorf("failed to fetch block %s "+
"after %d attempts: %v", hash, attempt, err)
}
log.Errorf("Failed to fetch block %s (attempt %d): %v. "+
"Retrying in %v seconds", hash, attempt, err,
backoff.Seconds())
select {
case <-time.After(backoff):
case <-l.quit:
return nil, ErrLookoutExiting
}
updateBackoff()
}
}
// watchBlocks serially pulls incoming epochs from the epoch source and searches
// our accepted state updates for any breached transactions. If any are found,
// we will attempt to decrypt the state updates' encrypted blobs and exact
// justice for the victim.
//
// This method MUST be run as a goroutine.
func (l *Lookout) watchBlocks(epochs *chainntnfs.BlockEpochEvent) {
defer l.wg.Done()
defer epochs.Cancel()
for {
select {
case epoch := <-epochs.Epochs:
log.Debugf("Fetching block for (height=%d, hash=%s)",
epoch.Height, epoch.Hash)
// Fetch the full block corresponding to the newly
// arriving epoch from the backend.
block, err := l.fetchBlockWithRetries(epoch.Hash)
if err != nil {
log.Errorf("Unable to fetch block for "+
"(height=%x, hash=%s): %v",
epoch.Height, epoch.Hash, err)
continue
}
// Process the block to see if it contains any breaches
// that we are monitoring on behalf of our clients.
err = l.processEpoch(epoch, block)
if err != nil {
log.Errorf("Unable to process %v: %v",
epoch, err)
}
case <-l.quit:
return
}
}
}
// processEpoch accepts an Epoch and queries the database for any matching state
// updates for the confirmed transactions. If any are found, the lookout
// responds by attempting to decrypt the encrypted blob and publishing the
// justice transaction.
func (l *Lookout) processEpoch(epoch *chainntnfs.BlockEpoch,
block *wire.MsgBlock) error {
numTxnsInBlock := len(block.Transactions)
log.Debugf("Scanning %d transaction in block (height=%d, hash=%s) "+
"for breaches", numTxnsInBlock, epoch.Height, epoch.Hash)
// Iterate over the transactions contained in the block, deriving a
// breach hint for each transaction and constructing an index mapping
// the hint back to it's original transaction.
hintToTx := make(map[blob.BreachHint]*wire.MsgTx, numTxnsInBlock)
txHints := make([]blob.BreachHint, 0, numTxnsInBlock)
for _, tx := range block.Transactions {
hash := tx.TxHash()
hint := blob.NewBreachHintFromHash(&hash)
txHints = append(txHints, hint)
hintToTx[hint] = tx.Copy()
}
// Query the database to see if any of the breach hints cause a match
// with any of our accepted state updates.
matches, err := l.cfg.DB.QueryMatches(txHints)
if err != nil {
return err
}
// No matches were found, we are done.
if len(matches) == 0 {
log.Debugf("No breaches found in (height=%d, hash=%s)",
epoch.Height, epoch.Hash)
return nil
}
breachCountStr := "breach"
if len(matches) > 1 {
breachCountStr = "breaches"
}
log.Infof("Found %d %s in (height=%d, hash=%s)",
len(matches), breachCountStr, epoch.Height, epoch.Hash)
// For each match, use our index to retrieve the original transaction,
// which corresponds to the breaching commitment transaction. If the
// decryption succeeds, we will accumulate the assembled justice
// descriptors in a single slice
var successes []*JusticeDescriptor
for _, match := range matches {
commitTx := hintToTx[match.Hint]
log.Infof("Dispatching punisher for client %s, breach-txid=%s",
match.ID, commitTx.TxHash())
// The decryption key for the state update should be the full
// txid of the breaching commitment transaction.
// The decryption key for the state update should be computed as
// key = SHA256(txid || txid).
breachTxID := commitTx.TxHash()
breachKey := blob.NewBreachKeyFromHash(&breachTxID)
// Now, decrypt the blob of justice that we received in the
// state update. This will contain all information required to
// sweep the breached commitment outputs.
justiceKit, err := blob.Decrypt(
breachKey, match.EncryptedBlob,
match.SessionInfo.Policy.BlobType,
)
if err != nil {
// If the decryption fails, this implies either that the
// client sent an invalid blob, or that the breach hint
// caused a match on the txid, but this isn't actually
// the right transaction.
log.Debugf("Unable to decrypt blob for client %s, "+
"breach-txid %s: %v", match.ID,
commitTx.TxHash(), err)
continue
}
justiceDesc := &JusticeDescriptor{
BreachedCommitTx: commitTx,
SessionInfo: match.SessionInfo,
JusticeKit: justiceKit,
}
successes = append(successes, justiceDesc)
}
// TODO(conner): mark successfully decrypted blob so that we can
// reliably rebroadcast on startup
// Now, we'll dispatch a punishment for each successful match in
// parallel. This will assemble the justice transaction for each and
// watch for their confirmation on chain.
for _, justiceDesc := range successes {
l.wg.Add(1)
go l.dispatchPunisher(justiceDesc)
}
return l.cfg.DB.SetLookoutTip(epoch)
}
// dispatchPunisher accepts a justice descriptor corresponding to a successfully
// decrypted blob. The punisher will then construct the witness scripts and
// witness stacks for the breached outputs. If construction of the justice
// transaction is successful, it will be published to the network to retrieve
// the funds and claim the watchtower's reward.
//
// This method MUST be run as a goroutine.
func (l *Lookout) dispatchPunisher(desc *JusticeDescriptor) {
defer l.wg.Done()
// Give the justice descriptor to the punisher to construct and publish
// the justice transaction. The lookout's quit channel is provided so
// that long-running tasks that watch for on-chain events can be
// canceled during shutdown since this method is waitgrouped.
err := l.cfg.Punisher.Punish(desc, l.quit)
if err != nil {
log.Errorf("Unable to punish breach-txid %s for %s: %v",
desc.BreachedCommitTx.TxHash(), desc.SessionInfo.ID,
err)
return
}
log.Infof("Punishment for client %s with breach-txid=%s dispatched",
desc.SessionInfo.ID, desc.BreachedCommitTx.TxHash())
}
package lookout
import (
"fmt"
"sync"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs"
)
type MockBackend struct {
mu sync.RWMutex
blocks chan *chainntnfs.BlockEpoch
epochs map[chainhash.Hash]*wire.MsgBlock
quit chan struct{}
}
func NewMockBackend() *MockBackend {
return &MockBackend{
blocks: make(chan *chainntnfs.BlockEpoch),
epochs: make(map[chainhash.Hash]*wire.MsgBlock),
quit: make(chan struct{}),
}
}
func (m *MockBackend) RegisterBlockEpochNtfn(*chainntnfs.BlockEpoch) (
*chainntnfs.BlockEpochEvent, error) {
return &chainntnfs.BlockEpochEvent{
Epochs: m.blocks,
}, nil
}
func (m *MockBackend) GetBlock(hash *chainhash.Hash) (*wire.MsgBlock, error) {
m.mu.RLock()
defer m.mu.RUnlock()
block, ok := m.epochs[*hash]
if !ok {
return nil, fmt.Errorf("unknown block for hash %x", hash)
}
return block, nil
}
func (m *MockBackend) ConnectEpoch(epoch *chainntnfs.BlockEpoch,
block *wire.MsgBlock) {
m.mu.Lock()
m.epochs[*epoch.Hash] = block
m.mu.Unlock()
select {
case m.blocks <- epoch:
case <-m.quit:
}
}
package lookout
import (
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/labels"
)
// PunisherConfig houses the resources required by the Punisher.
type PunisherConfig struct {
// PublishTx provides the ability to send a signed transaction to the
// network.
PublishTx func(*wire.MsgTx, string) error
// TODO(conner) add DB tracking and spend ntfn registration to see if
// ours confirmed or not
}
// BreachPunisher handles the responsibility of constructing and broadcasting
// justice transactions. Justice transactions are constructed from previously
// accepted state updates uploaded by the watchtower's clients.
type BreachPunisher struct {
cfg *PunisherConfig
}
// NewBreachPunisher constructs a new BreachPunisher given a PunisherConfig.
func NewBreachPunisher(cfg *PunisherConfig) *BreachPunisher {
return &BreachPunisher{
cfg: cfg,
}
}
// Punish constructs a justice transaction given a JusticeDescriptor and
// publishes is it to the network.
func (p *BreachPunisher) Punish(desc *JusticeDescriptor, quit <-chan struct{}) error {
justiceTxn, err := desc.CreateJusticeTxn()
if err != nil {
log.Errorf("Unable to create justice txn for "+
"client=%s with breach-txid=%s: %v",
desc.SessionInfo.ID, desc.BreachedCommitTx.TxHash(), err)
return err
}
log.Infof("Publishing justice transaction for client=%s with txid=%s",
desc.SessionInfo.ID, justiceTxn.TxHash())
label := labels.MakeLabel(labels.LabelTypeJusticeTransaction, nil)
err = p.cfg.PublishTx(justiceTxn, label)
if err != nil {
log.Errorf("Unable to publish justice txn for client=%s"+
"with breach-txid=%s: %v",
desc.SessionInfo.ID, desc.BreachedCommitTx.TxHash(), err)
return err
}
// TODO(conner): register for spend and remove from db after
// confirmation
return nil
}
package watchtower
import (
"net"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/brontide"
"github.com/lightningnetwork/lnd/lnencrypt"
"github.com/lightningnetwork/lnd/tor"
"github.com/lightningnetwork/lnd/watchtower/lookout"
"github.com/lightningnetwork/lnd/watchtower/wtserver"
)
// Standalone encapsulates the server-side functionality required by watchtower
// clients. A Standalone couples the two primary subsystems such that, as a
// unit, this instance can negotiate sessions with clients, accept state updates
// for active sessions, monitor the chain for breaches matching known breach
// hints, publish reconstructed justice transactions on behalf of tower clients.
type Standalone struct {
started uint32 // to be used atomically
stopped uint32 // to be used atomically
cfg *Config
// listeners is a reference to the wtserver's listeners.
listeners []net.Listener
// server is the client endpoint, used for negotiating sessions and
// uploading state updates.
server wtserver.Interface
// lookout is a service that monitors the chain and inspects the
// transactions found in new blocks against the state updates received
// by the server.
lookout lookout.Service
}
// New validates the passed Config and returns a fresh Standalone instance if
// the tower's subsystems could be properly initialized.
func New(cfg *Config) (*Standalone, error) {
// The tower must have listening address in order to accept new updates
// from clients.
if len(cfg.ListenAddrs) == 0 {
return nil, ErrNoListeners
}
// Assign the default read timeout if none is provided.
if cfg.ReadTimeout == 0 {
cfg.ReadTimeout = DefaultReadTimeout
}
// Assign the default write timeout if none is provided.
if cfg.WriteTimeout == 0 {
cfg.WriteTimeout = DefaultWriteTimeout
}
punisher := lookout.NewBreachPunisher(&lookout.PunisherConfig{
PublishTx: cfg.PublishTx,
})
// Initialize the lookout service with its required resources.
lookout := lookout.New(&lookout.Config{
BlockFetcher: cfg.BlockFetcher,
DB: cfg.DB,
EpochRegistrar: cfg.EpochRegistrar,
Punisher: punisher,
MinBackoff: time.Second,
MaxBackoff: time.Minute,
MaxNumRetries: 5,
})
// Create a brontide listener on each of the provided listening
// addresses. Client should be able to connect to any of open ports to
// communicate with this Standalone instance.
listeners := make([]net.Listener, 0, len(cfg.ListenAddrs))
for _, listenAddr := range cfg.ListenAddrs {
listener, err := brontide.NewListener(
cfg.NodeKeyECDH, listenAddr.String(),
brontide.DisabledBanClosure,
)
if err != nil {
return nil, err
}
listeners = append(listeners, listener)
}
// Initialize the server with its required resources.
server, err := wtserver.New(&wtserver.Config{
ChainHash: cfg.ChainHash,
DB: cfg.DB,
NodeKeyECDH: cfg.NodeKeyECDH,
Listeners: listeners,
ReadTimeout: cfg.ReadTimeout,
WriteTimeout: cfg.WriteTimeout,
NewAddress: cfg.NewAddress,
DisableReward: true,
})
if err != nil {
return nil, err
}
return &Standalone{
cfg: cfg,
listeners: listeners,
server: server,
lookout: lookout,
}, nil
}
// Start idempotently starts the Standalone, an error is returned if the
// subsystems could not be initialized.
func (w *Standalone) Start() error {
if !atomic.CompareAndSwapUint32(&w.started, 0, 1) {
return nil
}
log.Infof("Starting watchtower")
// If a tor controller exists in the config, then automatically create a
// hidden service for the watchtower to accept inbound connections from.
if w.cfg.TorController != nil {
log.Infof("Creating watchtower hidden service")
if err := w.createNewHiddenService(); err != nil {
return err
}
}
if err := w.lookout.Start(); err != nil {
return err
}
if err := w.server.Start(); err != nil {
w.lookout.Stop()
return err
}
log.Infof("Watchtower started successfully")
return nil
}
// Stop idempotently stops the Standalone and blocks until the subsystems have
// completed their shutdown.
func (w *Standalone) Stop() error {
if !atomic.CompareAndSwapUint32(&w.stopped, 0, 1) {
return nil
}
log.Infof("Stopping watchtower")
w.server.Stop()
w.lookout.Stop()
log.Infof("Watchtower stopped successfully")
return nil
}
// createNewHiddenService automatically sets up a v2 or v3 onion service in
// order to listen for inbound connections over Tor.
func (w *Standalone) createNewHiddenService() error {
// Get all the ports the watchtower is listening on. These will be used to
// map the hidden service's virtual port.
listenPorts := make([]int, 0, len(w.listeners))
for _, listener := range w.listeners {
port := listener.Addr().(*net.TCPAddr).Port
listenPorts = append(listenPorts, port)
}
encrypter, err := lnencrypt.KeyRingEncrypter(w.cfg.KeyRing)
if err != nil {
return err
}
// Once we've created the port mapping, we can automatically create the
// hidden service. The service's private key will be saved on disk in order
// to persistently have access to this hidden service across restarts.
onionCfg := tor.AddOnionConfig{
VirtualPort: DefaultPeerPort,
TargetPorts: listenPorts,
Store: tor.NewOnionFile(
w.cfg.WatchtowerKeyPath, 0600, w.cfg.EncryptKey,
encrypter,
),
Type: w.cfg.Type,
}
addr, err := w.cfg.TorController.AddOnion(onionCfg)
if err != nil {
return err
}
// Append this address to ExternalIPs so that it will be exposed in
// tower info calls.
w.cfg.ExternalIPs = append(w.cfg.ExternalIPs, addr)
return nil
}
// PubKey returns the public key for the watchtower used to authentication and
// encrypt traffic with clients.
//
// NOTE: Part of the watchtowerrpc.WatchtowerBackend interface.
func (w *Standalone) PubKey() *btcec.PublicKey {
return w.cfg.NodeKeyECDH.PubKey()
}
// ListeningAddrs returns the listening addresses where the watchtower server
// can accept client connections.
//
// NOTE: Part of the watchtowerrpc.WatchtowerBackend interface.
func (w *Standalone) ListeningAddrs() []net.Addr {
addrs := make([]net.Addr, 0, len(w.listeners))
for _, listener := range w.listeners {
addrs = append(addrs, listener.Addr())
}
return addrs
}
// ExternalIPs returns the addresses where the watchtower can be reached by
// clients externally.
//
// NOTE: Part of the watchtowerrpc.WatchtowerBackend interface.
func (w *Standalone) ExternalIPs() []net.Addr {
addrs := make([]net.Addr, 0, len(w.cfg.ExternalIPs))
addrs = append(addrs, w.cfg.ExternalIPs...)
return addrs
}
package wtclient
import (
"container/list"
"errors"
"fmt"
"net"
"sync"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
)
var (
// ErrAddressesExhausted signals that a addressIterator has cycled
// through all available addresses.
ErrAddressesExhausted = errors.New("exhausted all addresses")
// ErrAddrInUse indicates that an address is locked and cannot be
// removed from the addressIterator.
ErrAddrInUse = errors.New("address in use")
)
// AddressIterator handles iteration over a list of addresses. It strictly
// disallows the list of addresses it holds to be empty. It also allows callers
// to place locks on certain addresses in order to prevent other callers from
// removing the addresses in question from the iterator.
type AddressIterator interface {
// Next returns the next candidate address. This iterator will always
// return candidates in the order given when the iterator was
// instantiated. If no more candidates are available,
// ErrAddressesExhausted is returned.
Next() (net.Addr, error)
// NextAndLock does the same as described for Next, and it also places a
// lock on the returned address so that the address can not be removed
// until the lock on it has been released via ReleaseLock.
NextAndLock() (net.Addr, error)
// Peek returns the currently selected address in the iterator. If the
// end of the iterator has been reached then it is reset and the first
// item in the iterator is returned. Since the AddressIterator will
// never have an empty address list, this function will never return a
// nil value.
Peek() net.Addr
// PeekAndLock does the same as described for Peek, and it also places
// a lock on the returned address so that the address can not be removed
// until the lock on it has been released via ReleaseLock.
PeekAndLock() net.Addr
// ReleaseLock releases the lock held on the given address.
ReleaseLock(addr net.Addr)
// Add adds a new address to the iterator.
Add(addr net.Addr)
// Remove removes an existing address from the iterator. It disallows
// the address from being removed if it is the last address in the
// iterator or if there is currently a lock on the address.
Remove(addr net.Addr) error
// HasLocked returns true if the addressIterator has any locked
// addresses.
HasLocked() bool
// GetAll returns a copy of all the addresses in the iterator.
GetAll() []net.Addr
// Reset clears the iterators state, and makes the address at the front
// of the list the next item to be returned.
Reset()
// Copy constructs a new AddressIterator that has the same addresses
// as this iterator.
//
// NOTE that the address locks are not expected to be copied.
Copy() AddressIterator
}
// A compile-time check to ensure that addressIterator implements the
// AddressIterator interface.
var _ AddressIterator = (*addressIterator)(nil)
// addressIterator is a linked-list implementation of an AddressIterator.
type addressIterator struct {
mu sync.Mutex
addrList *list.List
currentTopAddr *list.Element
candidates map[string]*candidateAddr
totalLockCount int
}
type candidateAddr struct {
addr net.Addr
numLocks int
}
// newAddressIterator constructs a new addressIterator.
func newAddressIterator(addrs ...net.Addr) (*addressIterator, error) {
if len(addrs) == 0 {
return nil, fmt.Errorf("must have at least one address")
}
iter := &addressIterator{
addrList: list.New(),
candidates: make(map[string]*candidateAddr),
}
for _, addr := range addrs {
addrID := addr.String()
iter.addrList.PushBack(addrID)
iter.candidates[addrID] = &candidateAddr{addr: addr}
}
iter.Reset()
return iter, nil
}
// Reset clears the iterators state, and makes the address at the front of the
// list the next item to be returned.
//
// NOTE: This is part of the AddressIterator interface.
func (a *addressIterator) Reset() {
a.mu.Lock()
defer a.mu.Unlock()
a.unsafeReset()
}
// unsafeReset clears the iterator state and makes the address at the front of
// the list the next item to be returned.
//
// NOTE: this method is not thread safe and so should only be called if the
// appropriate mutex is being held.
func (a *addressIterator) unsafeReset() {
// Reset the next candidate to the front of the linked-list.
a.currentTopAddr = a.addrList.Front()
}
// Next returns the next candidate address. This iterator will always return
// candidates in the order given when the iterator was instantiated. If no more
// candidates are available, ErrAddressesExhausted is returned.
//
// NOTE: This is part of the AddressIterator interface.
func (a *addressIterator) Next() (net.Addr, error) {
return a.next(false)
}
// NextAndLock does the same as described for Next, and it also places a lock on
// the returned address so that the address can not be removed until the lock on
// it has been released via ReleaseLock.
//
// NOTE: This is part of the AddressIterator interface.
func (a *addressIterator) NextAndLock() (net.Addr, error) {
return a.next(true)
}
// next returns the next candidate address. This iterator will always return
// candidates in the order given when the iterator was instantiated. If no more
// candidates are available, ErrAddressesExhausted is returned.
func (a *addressIterator) next(lock bool) (net.Addr, error) {
a.mu.Lock()
defer a.mu.Unlock()
// In-case currentTopAddr is nil (meaning that Reset has not yet been
// called), return an error indicating this.
if a.currentTopAddr == nil {
return nil, ErrAddressesExhausted
}
// Set the next candidate to the subsequent element. If we are at the
// end of the address list, this could mean setting currentTopAddr to
// nil.
a.currentTopAddr = a.currentTopAddr.Next()
for a.currentTopAddr != nil {
// Propose the address at the front of the list.
addrID := a.currentTopAddr.Value.(string)
// Check whether this address is still considered a candidate.
// If it's not, we'll proceed to the next.
candidate, ok := a.candidates[addrID]
// If the address cannot be found in the candidate set, then
// this must mean that the Remove method was called for the
// address. The Remove method would have checked that the
// address is not the last one in the iterator and that it has
// no locks on it. It is therefor safe to remove.
if !ok {
// Grab the next address candidate. This might be nil
// if the iterator is on the last item in the list.
nextCandidate := a.currentTopAddr.Next()
// Remove the address from the list that is no longer
// in the candidate set.
a.addrList.Remove(a.currentTopAddr)
// Set the current top to the next candidate. This might
// mean setting it to nil if the iterator is on its last
// item in which case the loop will be exited and an
// ErrAddressesExhausted exhausted error will be
// returned.
a.currentTopAddr = nextCandidate
continue
}
if lock {
candidate.numLocks++
a.totalLockCount++
}
return candidate.addr, nil
}
return nil, ErrAddressesExhausted
}
// Peek returns the currently selected address in the iterator. If the end of
// the list has been reached then the iterator is reset and the first item in
// the list is returned. Since the addressIterator will never have an empty
// address list, this function will never return a nil value.
//
// NOTE: This is part of the AddressIterator interface.
func (a *addressIterator) Peek() net.Addr {
return a.peek(false)
}
// PeekAndLock does the same as described for Peek, and it also places a lock on
// the returned address so that the address can not be removed until the lock
// on it has been released via ReleaseLock.
//
// NOTE: This is part of the AddressIterator interface.
func (a *addressIterator) PeekAndLock() net.Addr {
return a.peek(true)
}
// peek returns the currently selected address in the iterator. If the end of
// the list has been reached then the iterator is reset and the first item in
// the list is returned. Since the addressIterator will never have an empty
// address list, this function will never return a nil value. If lock is set to
// true, the address will be locked for removal until ReleaseLock has been
// called for the address.
func (a *addressIterator) peek(lock bool) net.Addr {
a.mu.Lock()
defer a.mu.Unlock()
for {
// If currentTopAddr is nil, it means we have reached the end of
// the list, so we reset it here. The iterator always has at
// least one address, so we can be sure that currentTopAddr will
// be non-nil after calling reset here.
if a.currentTopAddr == nil {
a.unsafeReset()
}
addrID := a.currentTopAddr.Value.(string)
candidate, ok := a.candidates[addrID]
// If the address cannot be found in the candidate set, then
// this must mean that the Remove method was called for the
// address. The Remove method would have checked that the
// address is not the last one in the iterator and that it has
// no locks on it. It is therefor safe to remove.
if !ok {
// Grab the next address candidate. This might be nil
// if the iterator is on the last item in the list.
nextCandidate := a.currentTopAddr.Next()
// Remove the address from the list that is no longer
// in the candidate set.
a.addrList.Remove(a.currentTopAddr)
// Set the current top to the next candidate. This might
// mean setting it to nil if the iterator is on its last
// item but this will be reset at the top of the for
// loop.
a.currentTopAddr = nextCandidate
continue
}
if lock {
candidate.numLocks++
a.totalLockCount++
}
return candidate.addr
}
}
// ReleaseLock releases the lock held on the given address.
//
// NOTE: This is part of the AddressIterator interface.
func (a *addressIterator) ReleaseLock(addr net.Addr) {
a.mu.Lock()
defer a.mu.Unlock()
candidateAddr, ok := a.candidates[addr.String()]
if !ok {
return
}
if candidateAddr.numLocks == 0 {
return
}
candidateAddr.numLocks--
a.totalLockCount--
}
// Add adds a new address to the iterator.
//
// NOTE: This is part of the AddressIterator interface.
func (a *addressIterator) Add(addr net.Addr) {
a.mu.Lock()
defer a.mu.Unlock()
if _, ok := a.candidates[addr.String()]; ok {
return
}
a.addrList.PushBack(addr.String())
a.candidates[addr.String()] = &candidateAddr{addr: addr}
// If we've reached the end of our queue, then this candidate
// will become the next.
if a.currentTopAddr == nil {
a.currentTopAddr = a.addrList.Back()
}
}
// Remove removes an existing address from the iterator. It disallows the
// address from being removed if it is the last address in the iterator or if
// there is currently a lock on the address.
//
// NOTE: This is part of the AddressIterator interface.
func (a *addressIterator) Remove(addr net.Addr) error {
a.mu.Lock()
defer a.mu.Unlock()
candidate, ok := a.candidates[addr.String()]
if !ok {
return nil
}
if len(a.candidates) == 1 {
return wtdb.ErrLastTowerAddr
}
if candidate.numLocks > 0 {
return ErrAddrInUse
}
delete(a.candidates, addr.String())
return nil
}
// HasLocked returns true if the addressIterator has any locked addresses.
//
// NOTE: This is part of the AddressIterator interface.
func (a *addressIterator) HasLocked() bool {
a.mu.Lock()
defer a.mu.Unlock()
return a.totalLockCount > 0
}
// GetAll returns a copy of all the addresses in the iterator.
//
// NOTE: This is part of the AddressIterator interface.
func (a *addressIterator) GetAll() []net.Addr {
a.mu.Lock()
defer a.mu.Unlock()
return a.getAllUnsafe()
}
// Copy constructs a new AddressIterator that has the same addresses
// as this iterator.
//
// NOTE that the address locks will not be copied.
func (a *addressIterator) Copy() AddressIterator {
a.mu.Lock()
defer a.mu.Unlock()
addrs := a.getAllUnsafe()
// Since newAddressIterator will only ever return an error if it is
// initialised with zero addresses, we can ignore the error here since
// we are initialising it with the set of addresses of this
// addressIterator which is by definition a non-empty list.
iter, _ := newAddressIterator(addrs...)
return iter
}
// getAllUnsafe returns a copy of all the addresses in the iterator.
//
// NOTE: this method is not thread safe and so must only be called once the
// addressIterator mutex is already being held.
func (a *addressIterator) getAllUnsafe() []net.Addr {
var addrs []net.Addr
cursor := a.addrList.Front()
for cursor != nil {
addrID := cursor.Value.(string)
addr, ok := a.candidates[addrID]
if !ok {
cursor = cursor.Next()
continue
}
addrs = append(addrs, addr.addr)
cursor = cursor.Next()
}
return addrs
}
package wtclient
import (
"fmt"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/txsort"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
)
// backupTask is an internal struct for computing the justice transaction for a
// particular revoked state. A backupTask functions as a scratch pad for storing
// computing values of the transaction itself, such as the final split in
// balance if the justice transaction will give a reward to the tower. The
// backup task has three primary phases:
// 1. Init: Determines which inputs from the breach transaction will be spent,
// and the total amount contained in the inputs.
// 2. Bind: Asserts that the revoked state is eligible under a given session's
// parameters. Certain states may be ineligible due to fee rates, too little
// input amount, etc. Backup of these states can be deferred to a later time
// or session with more favorable parameters. If the session is bound
// successfully, the final session-dependent values to the justice
// transaction are solidified.
// 3. Send: Once the task is bound, it will be queued to send to a specific
// tower corresponding to the session in which it was bound. The justice
// transaction will be assembled by examining the parameters left as a
// result of the binding. After the justice transaction is signed, the
// necessary components are stripped out and encrypted before being sent to
// the tower in a StateUpdate.
type backupTask struct {
id wtdb.BackupID
breachInfo *lnwallet.BreachRetribution
commitmentType blob.CommitmentType
// state-dependent variables
toLocalInput input.Input
toRemoteInput input.Input
totalAmt btcutil.Amount
sweepPkScript []byte
// session-dependent variables
blobType blob.Type
outputs []*wire.TxOut
}
// newBackupTask initializes a new backupTask.
func newBackupTask(id wtdb.BackupID, sweepPkScript []byte) *backupTask {
return &backupTask{
id: id,
sweepPkScript: sweepPkScript,
}
}
// inputs returns all non-dust inputs that we will attempt to spend from.
//
// NOTE: Ordering of the inputs is not critical as we sort the transaction with
// BIP69 in a later stage.
func (t *backupTask) inputs() map[wire.OutPoint]input.Input {
inputs := make(map[wire.OutPoint]input.Input)
if t.toLocalInput != nil {
inputs[t.toLocalInput.OutPoint()] = t.toLocalInput
}
if t.toRemoteInput != nil {
inputs[t.toRemoteInput.OutPoint()] = t.toRemoteInput
}
return inputs
}
// addrType returns the type of an address after parsing it and matching it to
// the set of known script templates.
func addrType(pkScript []byte) txscript.ScriptClass {
// We pass in a set of dummy chain params here as they're only needed
// to make the address struct, which we're ignoring anyway (scripts are
// always the same, it's addresses that change across chains).
scriptClass, _, _, _ := txscript.ExtractPkScriptAddrs(
pkScript, &chaincfg.MainNetParams,
)
return scriptClass
}
// addScriptWeight parses the passed pkScript and adds the computed weight cost
// were the script to be added to the justice transaction.
func addScriptWeight(weightEstimate *input.TxWeightEstimator,
pkScript []byte) error {
switch addrType(pkScript) {
case txscript.WitnessV0PubKeyHashTy:
weightEstimate.AddP2WKHOutput()
case txscript.WitnessV0ScriptHashTy:
weightEstimate.AddP2WSHOutput()
case txscript.WitnessV1TaprootTy:
weightEstimate.AddP2TROutput()
default:
return fmt.Errorf("invalid addr type: %v", addrType(pkScript))
}
return nil
}
// bindSession first populates all state-dependent variables of the task. Then
// it determines if the backupTask is compatible with the passed SessionInfo's
// policy. If no error is returned, the task has been bound to the session and
// can be queued to upload to the tower. Otherwise, the bind failed and should
// be rescheduled with a different session.
func (t *backupTask) bindSession(session *wtdb.ClientSessionBody,
newBreachRetribution BreachRetributionBuilder) error {
breachInfo, chanType, err := newBreachRetribution(
t.id.ChanID, t.id.CommitHeight,
)
if err != nil {
return err
}
commitType, err := session.Policy.BlobType.CommitmentType(&chanType)
if err != nil {
return err
}
// Parse the non-dust outputs from the breach transaction,
// simultaneously computing the total amount contained in the inputs
// present. We can't compute the exact output values at this time
// since the task has not been assigned to a session, at which point
// parameters such as fee rate, number of outputs, and reward rate will
// be finalized.
var (
totalAmt int64
toLocalInput input.Input
toRemoteInput input.Input
)
// Add the sign descriptors and outputs corresponding to the to-local
// and to-remote outputs, respectively, if either input amount is
// non-dust. Note that the naming here seems reversed, but both are
// correct. For example, the to-remote output on the remote party's
// commitment is an output that pays to us. Hence the retribution refers
// to that output as local, though relative to their commitment, it is
// paying to-the-remote party (which is us).
if breachInfo.RemoteOutputSignDesc != nil {
toLocalInput, err = commitType.ToLocalInput(breachInfo)
if err != nil {
return err
}
totalAmt += breachInfo.RemoteOutputSignDesc.Output.Value
}
if breachInfo.LocalOutputSignDesc != nil {
toRemoteInput, err = commitType.ToRemoteInput(breachInfo)
if err != nil {
return err
}
totalAmt += breachInfo.LocalOutputSignDesc.Output.Value
}
t.commitmentType = commitType
t.breachInfo = breachInfo
t.toLocalInput = toLocalInput
t.toRemoteInput = toRemoteInput
t.totalAmt = btcutil.Amount(totalAmt)
// First we'll begin by deriving a weight estimate for the justice
// transaction. The final weight can be different depending on whether
// the watchtower is taking a reward.
var weightEstimate input.TxWeightEstimator
// Next, add the contribution from the inputs that are present on this
// breach transaction.
if t.toLocalInput != nil {
toLocalWitnessSize, err := commitType.ToLocalWitnessSize()
if err != nil {
return err
}
weightEstimate.AddWitnessInput(toLocalWitnessSize)
}
if t.toRemoteInput != nil {
toRemoteWitnessSize, err := commitType.ToRemoteWitnessSize()
if err != nil {
return err
}
weightEstimate.AddWitnessInput(toRemoteWitnessSize)
}
// All justice transactions will either use segwit v0 (p2wkh + p2wsh)
// or segwit v1 (p2tr).
err = addScriptWeight(&weightEstimate, t.sweepPkScript)
if err != nil {
return err
}
// If the justice transaction has a reward output, add the output's
// contribution to the weight estimate.
if session.Policy.BlobType.Has(blob.FlagReward) {
err := addScriptWeight(&weightEstimate, session.RewardPkScript)
if err != nil {
return err
}
}
// Now, compute the output values depending on whether FlagReward is set
// in the current session's policy.
outputs, err := session.Policy.ComputeJusticeTxOuts(
t.totalAmt, weightEstimate.Weight(),
t.sweepPkScript, session.RewardPkScript,
)
if err != nil {
return err
}
t.blobType = session.Policy.BlobType
t.outputs = outputs
return nil
}
// craftSessionPayload is the final stage for a backupTask, and generates the
// encrypted payload and breach hint that should be sent to the tower. This
// method computes the final justice transaction using the bound
// session-dependent variables, and signs the resulting transaction. The
// required pieces from signatures, witness scripts, etc are then packaged into
// a JusticeKit and encrypted using the breach transaction's key.
func (t *backupTask) craftSessionPayload(
signer input.Signer) (blob.BreachHint, []byte, error) {
var hint blob.BreachHint
justiceKit, err := t.commitmentType.NewJusticeKit(
t.sweepPkScript, t.breachInfo, t.toRemoteInput != nil,
)
if err != nil {
return hint, nil, err
}
// Now, begin construction of the justice transaction. We'll start with
// a version 2 transaction.
justiceTxn := wire.NewMsgTx(2)
// Next, add the non-dust inputs that were derived from the breach
// information. This will either be contain both the to-local and
// to-remote outputs, or only be the to-local output.
inputs := t.inputs()
prevOutputFetcher := txscript.NewMultiPrevOutFetcher(nil)
for prevOutPoint, inp := range inputs {
prevOutputFetcher.AddPrevOut(
prevOutPoint, inp.SignDesc().Output,
)
justiceTxn.AddTxIn(&wire.TxIn{
PreviousOutPoint: prevOutPoint,
Sequence: inp.BlocksToMaturity(),
})
}
// Add the sweep output paying directly to the user and possibly a
// reward output, using the outputs computed when the task was bound.
justiceTxn.TxOut = t.outputs
// Sort the justice transaction according to BIP69.
txsort.InPlaceSort(justiceTxn)
// Check that the justice transaction meets basic validity requirements
// before attempting to attach the witnesses.
btx := btcutil.NewTx(justiceTxn)
if err := blockchain.CheckTransactionSanity(btx); err != nil {
return hint, nil, err
}
// Construct a sighash cache to improve signing performance.
hashCache := txscript.NewTxSigHashes(justiceTxn, prevOutputFetcher)
// Since the transaction inputs could have been reordered as a result of
// the BIP69 sort, create an index mapping each prevout to it's new
// index.
inputIndex := make(map[wire.OutPoint]int)
for i, txIn := range justiceTxn.TxIn {
inputIndex[txIn.PreviousOutPoint] = i
}
// Now, iterate through the list of inputs that were initially added to
// the transaction and store the computed witness within the justice
// kit.
commitType := t.commitmentType
for _, inp := range inputs {
// Lookup the input's new post-sort position.
i := inputIndex[inp.OutPoint()]
// Construct the full witness required to spend this input.
inputScript, err := inp.CraftInputScript(
signer, justiceTxn, hashCache, prevOutputFetcher, i,
)
if err != nil {
return hint, nil, err
}
signature, err := commitType.ParseRawSig(inputScript.Witness)
if err != nil {
return hint, nil, err
}
toLocalWitnessType, err := commitType.ToLocalWitnessType()
if err != nil {
return hint, nil, err
}
toRemoteWitnessType, err := commitType.ToRemoteWitnessType()
if err != nil {
return hint, nil, err
}
// Finally, copy the serialized signature into the justice kit,
// using the input's witness type to select the appropriate
// field
switch inp.WitnessType() {
case toLocalWitnessType:
justiceKit.AddToLocalSig(signature)
case toRemoteWitnessType:
justiceKit.AddToRemoteSig(signature)
default:
return hint, nil, fmt.Errorf("invalid witness type: %v",
inp.WitnessType())
}
}
breachTxID := t.breachInfo.BreachTxHash
// Compute the breach hint as SHA256(txid)[:16] and breach key as
// SHA256(txid || txid).
hint, key := blob.NewBreachHintAndKeyFromHash(&breachTxID)
// Then, we'll encrypt the computed justice kit using the full breach
// transaction id, which will allow the tower to recover the contents
// after the transaction is seen in the chain or mempool.
encBlob, err := blob.Encrypt(justiceKit, key)
if err != nil {
return hint, nil, err
}
return hint, encBlob, nil
}
package wtclient
import (
"container/list"
"net"
"sync"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
)
// TowerCandidateIterator provides an abstraction for iterating through possible
// watchtower addresses when attempting to create a new session.
type TowerCandidateIterator interface {
// AddCandidate adds a new candidate tower to the iterator. If the
// candidate already exists, then any new addresses are added to it.
AddCandidate(*Tower)
// RemoveCandidate removes an existing candidate tower from the
// iterator. An optional address can be provided to indicate a stale
// tower address to remove it. If it isn't provided, then the tower is
// completely removed from the iterator.
RemoveCandidate(wtdb.TowerID, net.Addr) error
// IsActive determines whether a given tower is exists within the
// iterator.
IsActive(wtdb.TowerID) bool
// Reset clears any internal iterator state, making previously taken
// candidates available as long as they remain in the set.
Reset() error
// GetTower gets the tower with the given ID from the iterator. If no
// such tower is found then ErrTowerNotInIterator is returned.
GetTower(id wtdb.TowerID) (*Tower, error)
// Next returns the next candidate tower. The iterator is not required
// to return results in any particular order. If no more candidates are
// available, ErrTowerCandidatesExhausted is returned.
Next() (*Tower, error)
}
// towerListIterator is a linked-list backed TowerCandidateIterator.
type towerListIterator struct {
mu sync.Mutex
queue *list.List
nextCandidate *list.Element
candidates map[wtdb.TowerID]*Tower
}
// Compile-time constraint to ensure *towerListIterator implements the
// TowerCandidateIterator interface.
var _ TowerCandidateIterator = (*towerListIterator)(nil)
// newTowerListIterator initializes a new towerListIterator from a variadic list
// of lnwire.NetAddresses.
func newTowerListIterator(candidates ...*Tower) *towerListIterator {
iter := &towerListIterator{
queue: list.New(),
candidates: make(map[wtdb.TowerID]*Tower),
}
for _, candidate := range candidates {
iter.queue.PushBack(candidate.ID)
iter.candidates[candidate.ID] = candidate
}
iter.Reset()
return iter
}
// Reset clears the iterators state, and makes the address at the front of the
// list the next item to be returned..
func (t *towerListIterator) Reset() error {
t.mu.Lock()
defer t.mu.Unlock()
// Reset the next candidate to the front of the linked-list.
t.nextCandidate = t.queue.Front()
return nil
}
// GetTower gets the tower with the given ID from the iterator. If no such tower
// is found then ErrTowerNotInIterator is returned.
func (t *towerListIterator) GetTower(id wtdb.TowerID) (*Tower, error) {
t.mu.Lock()
defer t.mu.Unlock()
tower, ok := t.candidates[id]
if !ok {
return nil, ErrTowerNotInIterator
}
return tower, nil
}
// Next returns the next candidate tower. This iterator will always return
// candidates in the order given when the iterator was instantiated. If no more
// candidates are available, ErrTowerCandidatesExhausted is returned.
func (t *towerListIterator) Next() (*Tower, error) {
t.mu.Lock()
defer t.mu.Unlock()
for t.nextCandidate != nil {
// Propose the tower at the front of the list.
towerID := t.nextCandidate.Value.(wtdb.TowerID)
// Check whether this tower is still considered a candidate. If
// it's not, we'll proceed to the next.
tower, ok := t.candidates[towerID]
if !ok {
nextCandidate := t.nextCandidate.Next()
t.queue.Remove(t.nextCandidate)
t.nextCandidate = nextCandidate
continue
}
// Set the next candidate to the subsequent element.
t.nextCandidate = t.nextCandidate.Next()
return tower, nil
}
return nil, ErrTowerCandidatesExhausted
}
// AddCandidate adds a new candidate tower to the iterator. If the candidate
// already exists, then any new addresses are added to it.
func (t *towerListIterator) AddCandidate(candidate *Tower) {
t.mu.Lock()
defer t.mu.Unlock()
if tower, ok := t.candidates[candidate.ID]; !ok {
t.queue.PushBack(candidate.ID)
t.candidates[candidate.ID] = candidate
// If we've reached the end of our queue, then this candidate
// will become the next.
if t.nextCandidate == nil {
t.nextCandidate = t.queue.Back()
}
} else {
candidate.Addresses.Reset()
firstAddr := candidate.Addresses.Peek()
tower.Addresses.Add(firstAddr)
for {
next, err := candidate.Addresses.Next()
if err != nil {
candidate.Addresses.Reset()
break
}
tower.Addresses.Add(next)
}
}
}
// RemoveCandidate removes an existing candidate tower from the iterator. An
// optional address can be provided to indicate a stale tower address to remove
// it. If it isn't provided, then the tower is completely removed from the
// iterator.
func (t *towerListIterator) RemoveCandidate(candidate wtdb.TowerID,
addr net.Addr) error {
t.mu.Lock()
defer t.mu.Unlock()
tower, ok := t.candidates[candidate]
if !ok {
return nil
}
if addr != nil {
err := tower.Addresses.Remove(addr)
if err != nil {
return err
}
} else {
if tower.Addresses.HasLocked() {
return ErrAddrInUse
}
delete(t.candidates, candidate)
}
return nil
}
// IsActive determines whether a given tower is exists within the iterator.
func (t *towerListIterator) IsActive(tower wtdb.TowerID) bool {
t.mu.Lock()
defer t.mu.Unlock()
_, ok := t.candidates[tower]
return ok
}
// TODO(conner): implement graph-backed candidate iterator for public towers.
package wtclient
import (
"bytes"
"crypto/rand"
"errors"
"fmt"
"maps"
"math/big"
"net"
"sync"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
"github.com/lightningnetwork/lnd/watchtower/wtserver"
"github.com/lightningnetwork/lnd/watchtower/wtwire"
)
const (
// DefaultReadTimeout specifies the default duration we will wait during
// a read before breaking out of a blocking read.
DefaultReadTimeout = 15 * time.Second
// DefaultWriteTimeout specifies the default duration we will wait
// during a write before breaking out of a blocking write.
DefaultWriteTimeout = 15 * time.Second
// DefaultStatInterval specifies the default interval between logging
// metrics about the client's operation.
DefaultStatInterval = time.Minute
// DefaultSessionCloseRange is the range over which we will generate a
// random number of blocks to delay closing a session after its last
// channel has been closed.
DefaultSessionCloseRange = 288
// DefaultMaxTasksInMemQueue is the maximum number of items to be held
// in the in-memory queue.
DefaultMaxTasksInMemQueue = 2000
)
// genSessionFilter constructs a filter that can be used to select sessions only
// if they match the policy of the client (namely anchor vs legacy). If
// activeOnly is set, then only active sessions will be returned.
func (c *client) genSessionFilter(
activeOnly bool) wtdb.ClientSessionFilterFn {
return func(session *wtdb.ClientSession) bool {
if c.cfg.Policy.TxPolicy != session.Policy.TxPolicy {
return false
}
if !activeOnly {
return true
}
return session.Status == wtdb.CSessionActive
}
}
// ExhaustedSessionFilter constructs a wtdb.ClientSessionFilterFn filter
// function that will filter out any sessions that have been exhausted. A
// session is considered exhausted only if it has no un-acked updates and the
// sequence number of the session is equal to the max updates of the session
// policy.
func ExhaustedSessionFilter() wtdb.ClientSessWithNumCommittedUpdatesFilterFn {
return func(session *wtdb.ClientSession, numUnAcked uint16) bool {
return session.SeqNum < session.Policy.MaxUpdates ||
numUnAcked > 0
}
}
// RegisteredTower encompasses information about a registered watchtower with
// the client.
type RegisteredTower struct {
*wtdb.Tower
// Sessions is the set of sessions corresponding to the watchtower.
Sessions map[wtdb.SessionID]*wtdb.ClientSession
// ActiveSessionCandidate determines whether the watchtower is currently
// being considered for new sessions.
ActiveSessionCandidate bool
}
// BreachRetributionBuilder is a function that can be used to construct a
// BreachRetribution from a channel ID and a commitment height.
type BreachRetributionBuilder func(id lnwire.ChannelID,
commitHeight uint64) (*lnwallet.BreachRetribution,
channeldb.ChannelType, error)
// newTowerMsg is an internal message we'll use within the client to signal
// that a new tower can be considered.
type newTowerMsg struct {
// tower holds the info about the new Tower or new tower address
// required to connect to it.
tower *Tower
// errChan is the channel through which we'll send a response back to
// the caller when handling their request.
//
// NOTE: This channel must be buffered.
errChan chan error
}
// staleTowerMsg is an internal message we'll use within the client to
// signal that a tower should no longer be considered.
type staleTowerMsg struct {
// id is the unique database identifier for the tower.
id wtdb.TowerID
// pubKey is the identifying public key of the watchtower.
pubKey *btcec.PublicKey
// addr is an optional field that when set signals that the address
// should be removed from the watchtower's set of addresses, indicating
// that it is stale. If it's not set, then the watchtower should be
// no longer be considered for new sessions.
addr net.Addr
// errChan is the channel through which we'll send a response back to
// the caller when handling their request.
//
// NOTE: This channel must be buffered.
errChan chan error
}
// deactivateTowerMsg is an internal message we'll use within the TowerClient
// to signal that a tower should be marked as inactive.
type deactivateTowerMsg struct {
// id is the unique database identifier for the tower.
id wtdb.TowerID
// pubKey is the identifying public key of the watchtower.
pubKey *btcec.PublicKey
// errChan is the channel through which we'll send a response back to
// the caller when handling their request.
//
// NOTE: This channel must be buffered.
errChan chan error
}
// terminateSessMsg is an internal message we'll use within the TowerClient to
// signal that a session should be terminated.
type terminateSessMsg struct {
// id is the session identifier.
id wtdb.SessionID
// errChan is the channel through which we'll send a response back to
// the caller when handling their request.
//
// NOTE: This channel must be buffered.
errChan chan error
}
// clientCfg holds the configuration values required by a client.
type clientCfg struct {
*Config
// Policy is the session policy the client will propose when creating
// new sessions with the tower. If the policy differs from any active
// sessions recorded in the database, those sessions will be ignored and
// new sessions will be requested immediately.
Policy wtpolicy.Policy
getSweepScript func(lnwire.ChannelID) ([]byte, bool)
}
// client manages backing up revoked states for all states that fall under a
// specific policy type.
type client struct {
cfg *clientCfg
log btclog.Logger
pipeline *DiskOverflowQueue[*wtdb.BackupID]
negotiator SessionNegotiator
candidateTowers TowerCandidateIterator
candidateSessions map[wtdb.SessionID]*ClientSession
activeSessions *sessionQueueSet
sessionQueue *sessionQueue
prevTask *wtdb.BackupID
statTicker *time.Ticker
stats *clientStats
newTowers chan *newTowerMsg
staleTowers chan *staleTowerMsg
deactivateTowers chan *deactivateTowerMsg
terminateSessions chan *terminateSessMsg
wg sync.WaitGroup
quit chan struct{}
}
// newClient initializes a new client from the provided clientCfg. An error is
// returned if the client could not be initialized.
func newClient(cfg *clientCfg) (*client, error) {
identifier, err := cfg.Policy.BlobType.Identifier()
if err != nil {
return nil, err
}
plog := log.WithPrefix(fmt.Sprintf("(%s)", identifier))
queueDB := cfg.DB.GetDBQueue([]byte(identifier))
queue, err := NewDiskOverflowQueue[*wtdb.BackupID](
queueDB, cfg.MaxTasksInMemQueue, plog,
)
if err != nil {
return nil, err
}
c := &client{
cfg: cfg,
log: plog,
pipeline: queue,
activeSessions: newSessionQueueSet(),
statTicker: time.NewTicker(DefaultStatInterval),
stats: new(clientStats),
newTowers: make(chan *newTowerMsg),
staleTowers: make(chan *staleTowerMsg),
deactivateTowers: make(chan *deactivateTowerMsg),
terminateSessions: make(chan *terminateSessMsg),
quit: make(chan struct{}),
}
candidateTowers := newTowerListIterator()
perActiveTower := func(tower *Tower) {
// If the tower has already been marked as active, then there is
// no need to add it to the iterator again.
if candidateTowers.IsActive(tower.ID) {
return
}
c.log.Infof("Using private watchtower %x, offering policy %s",
tower.IdentityKey.SerializeCompressed(), cfg.Policy)
// Add the tower to the set of candidate towers.
candidateTowers.AddCandidate(tower)
}
// Load all candidate sessions and towers from the database into the
// client. We will use any of these sessions if their policies match the
// current policy of the client, otherwise they will be ignored and new
// sessions will be requested.
candidateSessions, err := getTowerAndSessionCandidates(
cfg.DB, cfg.SecretKeyRing, perActiveTower,
wtdb.WithPreEvalFilterFn(c.genSessionFilter(true)),
wtdb.WithPostEvalFilterFn(ExhaustedSessionFilter()),
)
if err != nil {
return nil, err
}
c.candidateTowers = candidateTowers
c.candidateSessions = candidateSessions
c.negotiator = newSessionNegotiator(&NegotiatorConfig{
DB: cfg.DB,
SecretKeyRing: cfg.SecretKeyRing,
Policy: cfg.Policy,
ChainHash: cfg.ChainHash,
SendMessage: c.sendMessage,
ReadMessage: c.readMessage,
Dial: c.dial,
Candidates: c.candidateTowers,
MinBackoff: cfg.MinBackoff,
MaxBackoff: cfg.MaxBackoff,
Log: plog,
})
return c, nil
}
// getTowerAndSessionCandidates loads all the towers from the DB and then
// fetches the sessions for each of tower. Sessions are only collected if they
// pass the sessionFilter check. If a tower has a session that does pass the
// sessionFilter check then the perActiveTower call-back will be called on that
// tower.
func getTowerAndSessionCandidates(db DB, keyRing ECDHKeyRing,
perActiveTower func(tower *Tower),
opts ...wtdb.ClientSessionListOption) (
map[wtdb.SessionID]*ClientSession, error) {
// Fetch all active towers from the DB.
towers, err := db.ListTowers(func(tower *wtdb.Tower) bool {
return tower.Status == wtdb.TowerStatusActive
})
if err != nil {
return nil, err
}
candidateSessions := make(map[wtdb.SessionID]*ClientSession)
for _, dbTower := range towers {
tower, err := NewTowerFromDBTower(dbTower)
if err != nil {
return nil, err
}
sessions, err := db.ListClientSessions(&tower.ID, opts...)
if err != nil {
return nil, err
}
for _, s := range sessions {
cs, err := NewClientSessionFromDBSession(
s, tower, keyRing,
)
if err != nil {
return nil, err
}
// Add the session to the set of candidate sessions.
candidateSessions[s.ID] = cs
}
perActiveTower(tower)
}
return candidateSessions, nil
}
// getClientSessions retrieves the client sessions for a particular tower if
// specified, otherwise all client sessions for all towers are retrieved. An
// optional filter can be provided to filter out any undesired client sessions.
//
// NOTE: This method should only be used when deserialization of a
// ClientSession's SessionPrivKey field is desired, otherwise, the existing
// ListClientSessions method should be used.
func getClientSessions(db DB, keyRing ECDHKeyRing, forTower *wtdb.TowerID,
opts ...wtdb.ClientSessionListOption) (
map[wtdb.SessionID]*ClientSession, error) {
dbSessions, err := db.ListClientSessions(forTower, opts...)
if err != nil {
return nil, err
}
// Reload the tower from disk using the tower ID contained in each
// candidate session. We will also rederive any session keys needed to
// be able to communicate with the towers and authenticate session
// requests. This prevents us from having to store the private keys on
// disk.
sessions := make(map[wtdb.SessionID]*ClientSession)
for _, s := range dbSessions {
dbTower, err := db.LoadTowerByID(s.TowerID)
if err != nil {
return nil, err
}
towerKeyDesc, err := keyRing.DeriveKey(keychain.KeyLocator{
Family: keychain.KeyFamilyTowerSession,
Index: s.KeyIndex,
})
if err != nil {
return nil, err
}
sessionKeyECDH := keychain.NewPubKeyECDH(towerKeyDesc, keyRing)
tower, err := NewTowerFromDBTower(dbTower)
if err != nil {
return nil, err
}
sessions[s.ID] = &ClientSession{
ID: s.ID,
ClientSessionBody: s.ClientSessionBody,
Tower: tower,
SessionKeyECDH: sessionKeyECDH,
}
}
return sessions, nil
}
// start initializes the watchtower client by loading or negotiating an active
// session and then begins processing backup tasks from the request pipeline.
func (c *client) start() error {
c.log.Infof("Watchtower client starting")
// First, restart a session queue for any sessions that have
// committed but unacked state updates. This ensures that these
// sessions will be able to flush the committed updates after a
// restart.
fetchCommittedUpdates := c.cfg.DB.FetchSessionCommittedUpdates
for _, session := range c.candidateSessions {
committedUpdates, err := fetchCommittedUpdates(
&session.ID,
)
if err != nil {
return err
}
if len(committedUpdates) > 0 {
c.log.Infof("Starting session=%s to process "+
"%d committed backups", session.ID,
len(committedUpdates))
c.initActiveQueue(session, committedUpdates)
}
}
// Now start the session negotiator, which will allow us to request new
// session as soon as the backupDispatcher starts up.
err := c.negotiator.Start()
if err != nil {
return err
}
// Start the task pipeline to which new backup tasks will be
// submitted from active links.
err = c.pipeline.Start()
if err != nil {
return err
}
c.wg.Add(1)
go c.backupDispatcher()
c.log.Infof("Watchtower client started successfully")
return nil
}
// stop idempotently initiates a graceful shutdown of the watchtower client.
func (c *client) stop() error {
var returnErr error
c.log.Debugf("Stopping watchtower client")
// 1. Stop the session negotiator.
err := c.negotiator.Stop()
if err != nil {
returnErr = err
}
// 2. Stop the backup dispatcher and any other goroutines.
close(c.quit)
c.wg.Wait()
// 3. If there was a left over 'prevTask' from the backup
// dispatcher, replay that onto the pipeline.
if c.prevTask != nil {
err = c.pipeline.QueueBackupID(c.prevTask)
if err != nil {
returnErr = err
}
}
// 4. Shutdown all active session queues in parallel. These will
// exit once all unhandled updates have been replayed to the
// task pipeline.
c.activeSessions.ApplyAndWait(func(s *sessionQueue) func() {
return func() {
err := s.Stop(false)
if err != nil {
c.log.Errorf("could not stop session "+
"queue: %s: %v", s.ID(), err)
returnErr = err
}
}
})
// 5. Shutdown the backup queue, which will prevent any further
// updates from being accepted.
if err = c.pipeline.Stop(); err != nil {
returnErr = err
}
c.log.Debugf("Client successfully stopped, stats: %s", c.stats)
return returnErr
}
// backupState initiates a request to back up a particular revoked state. If the
// method returns nil, the backup is guaranteed to be successful unless the:
// - justice transaction would create dust outputs when trying to abide by the
// negotiated policy, or
// - breached outputs contain too little value to sweep at the target sweep
// fee rate.
func (c *client) backupState(chanID *lnwire.ChannelID,
stateNum uint64) error {
id := &wtdb.BackupID{
ChanID: *chanID,
CommitHeight: stateNum,
}
return c.pipeline.QueueBackupID(id)
}
// nextSessionQueue attempts to fetch an active session from our set of
// candidate sessions. Candidate sessions with a differing policy from the
// active client's advertised policy will be ignored, but may be resumed if the
// client is restarted with a matching policy. If no candidates were found, nil
// is returned to signal that we need to request a new policy.
func (c *client) nextSessionQueue() (*sessionQueue, error) {
// Select any candidate session at random, and remove it from the set of
// candidate sessions.
var candidateSession *ClientSession
for id, sessionInfo := range c.candidateSessions {
delete(c.candidateSessions, id)
// Skip any sessions with policies that don't match the current
// TxPolicy, as they would result in different justice
// transactions from what is requested. These can be used again
// if the client changes their configuration and restarting.
if sessionInfo.Policy.TxPolicy != c.cfg.Policy.TxPolicy {
continue
}
candidateSession = sessionInfo
break
}
// If none of the sessions could be used or none were found, we'll
// return nil to signal that we need another session to be negotiated.
if candidateSession == nil {
return nil, nil
}
updates, err := c.cfg.DB.FetchSessionCommittedUpdates(
&candidateSession.ID,
)
if err != nil {
return nil, err
}
// Initialize the session queue and spin it up, so it can begin handling
// updates. If the queue was already made active on startup, this will
// simply return the existing session queue from the set.
return c.getOrInitActiveQueue(candidateSession, updates), nil
}
// stopAndRemoveSession stops the session with the given ID and removes it from
// the in-memory active sessions set.
func (c *client) stopAndRemoveSession(id wtdb.SessionID, final bool) error {
return c.activeSessions.StopAndRemove(id, final)
}
// deleteSessionFromTower dials the tower that we created the session with and
// attempts to send the tower the DeleteSession message.
func (c *client) deleteSessionFromTower(sess *wtdb.ClientSession) error {
// First, we check if we have already loaded this tower in our
// candidate towers iterator.
tower, err := c.candidateTowers.GetTower(sess.TowerID)
if errors.Is(err, ErrTowerNotInIterator) {
// If not, then we attempt to load it from the DB.
dbTower, err := c.cfg.DB.LoadTowerByID(sess.TowerID)
if err != nil {
return err
}
tower, err = NewTowerFromDBTower(dbTower)
if err != nil {
return err
}
} else if err != nil {
return err
}
session, err := NewClientSessionFromDBSession(
sess, tower, c.cfg.SecretKeyRing,
)
if err != nil {
return err
}
localInit := wtwire.NewInitMessage(
lnwire.NewRawFeatureVector(wtwire.AltruistSessionsRequired),
c.cfg.ChainHash,
)
var (
conn wtserver.Peer
// addrIterator is a copy of the tower's address iterator.
// We use this copy so that iterating through the addresses does
// not affect any other threads using this iterator.
addrIterator = tower.Addresses.Copy()
towerAddr = addrIterator.Peek()
)
// Attempt to dial the tower with its available addresses.
for {
conn, err = c.dial(
session.SessionKeyECDH, &lnwire.NetAddress{
IdentityKey: tower.IdentityKey,
Address: towerAddr,
},
)
if err != nil {
// If there are more addrs available, immediately try
// those.
nextAddr, iteratorErr := addrIterator.Next()
if iteratorErr == nil {
towerAddr = nextAddr
continue
}
// Otherwise, if we have exhausted the address list,
// exit.
addrIterator.Reset()
return fmt.Errorf("failed to dial tower(%x) at any "+
"available addresses",
tower.IdentityKey.SerializeCompressed())
}
break
}
defer conn.Close()
// Send Init to tower.
err = c.sendMessage(conn, localInit)
if err != nil {
return err
}
// Receive Init from tower.
remoteMsg, err := c.readMessage(conn)
if err != nil {
return err
}
remoteInit, ok := remoteMsg.(*wtwire.Init)
if !ok {
return fmt.Errorf("watchtower %s responded with %T to Init",
towerAddr, remoteMsg)
}
// Validate Init.
err = localInit.CheckRemoteInit(remoteInit, wtwire.FeatureNames)
if err != nil {
return err
}
// Send DeleteSession to tower.
err = c.sendMessage(conn, &wtwire.DeleteSession{})
if err != nil {
return err
}
// Receive DeleteSessionReply from tower.
remoteMsg, err = c.readMessage(conn)
if err != nil {
return err
}
deleteSessionReply, ok := remoteMsg.(*wtwire.DeleteSessionReply)
if !ok {
return fmt.Errorf("watchtower %s responded with %T to "+
"DeleteSession", towerAddr, remoteMsg)
}
switch deleteSessionReply.Code {
case wtwire.CodeOK, wtwire.DeleteSessionCodeNotFound:
return nil
default:
return fmt.Errorf("received error code %v in "+
"DeleteSessionReply when attempting to delete "+
"session from tower", deleteSessionReply.Code)
}
}
// backupDispatcher processes events coming from the taskPipeline and is
// responsible for detecting when the client needs to renegotiate a session to
// fulfill continuing demand. The event loop exits if the client is quit.
//
// NOTE: This method MUST be run as a goroutine.
func (c *client) backupDispatcher() {
defer c.wg.Done()
c.log.Tracef("Starting backup dispatcher")
defer c.log.Tracef("Stopping backup dispatcher")
for {
switch {
// No active session queue and no additional sessions.
case c.sessionQueue == nil && len(c.candidateSessions) == 0:
c.log.Infof("Requesting new session.")
// Immediately request a new session.
c.negotiator.RequestSession()
// Wait until we receive the newly negotiated session.
// All backups sent in the meantime are queued in the
// revoke queue, as we cannot process them.
awaitSession:
select {
case session := <-c.negotiator.NewSessions():
c.log.Infof("Acquired new session with id=%s",
session.ID)
c.candidateSessions[session.ID] = session
c.stats.sessionAcquired()
// We'll continue to choose the newly negotiated
// session as our active session queue.
continue
case <-c.statTicker.C:
c.log.Infof("Client stats: %s", c.stats)
// A new tower has been requested to be added. We'll
// update our persisted and in-memory state and consider
// its corresponding sessions, if any, as new
// candidates.
case msg := <-c.newTowers:
msg.errChan <- c.handleNewTower(msg.tower)
// A tower has been requested to be removed. We'll
// only allow removal of it if the address in question
// is not currently being used for session negotiation.
case msg := <-c.staleTowers:
msg.errChan <- c.handleStaleTower(msg)
// A tower has been requested to be de-activated. We'll
// only allow this if the tower is not currently being
// used for session negotiation.
case msg := <-c.deactivateTowers:
msg.errChan <- c.handleDeactivateTower(msg)
// A request has come through to terminate a session.
case msg := <-c.terminateSessions:
msg.errChan <- c.handleTerminateSession(msg)
case <-c.quit:
return
}
// Instead of looping, we'll jump back into the select
// case and await the delivery of the session to prevent
// us from re-requesting additional sessions.
goto awaitSession
// No active session queue but have additional sessions.
case c.sessionQueue == nil && len(c.candidateSessions) > 0:
// We've exhausted the prior session, we'll pop another
// from the remaining sessions and continue processing
// backup tasks.
var err error
c.sessionQueue, err = c.nextSessionQueue()
if err != nil {
c.log.Errorf("error fetching next session "+
"queue: %v", err)
}
if c.sessionQueue != nil {
c.log.Debugf("Loaded next candidate session "+
"queue id=%s", c.sessionQueue.ID())
}
// Have active session queue, process backups.
case c.sessionQueue != nil:
if c.prevTask != nil {
c.processTask(c.prevTask)
// Continue to ensure the sessionQueue is
// properly initialized before attempting to
// process more tasks from the pipeline.
continue
}
// Normal operation where new tasks are read from the
// pipeline.
select {
// If any sessions are negotiated while we have an
// active session queue, queue them for future use.
// This shouldn't happen with the current design, so
// it doesn't hurt to select here just in case. In the
// future, we will likely allow more asynchrony so that
// we can request new sessions before the session is
// fully empty, which this case would handle.
case session := <-c.negotiator.NewSessions():
c.log.Warnf("Acquired new session with id=%s "+
"while processing tasks", session.ID)
c.candidateSessions[session.ID] = session
c.stats.sessionAcquired()
case <-c.statTicker.C:
c.log.Infof("Client stats: %s", c.stats)
// Process each backup task serially from the queue of
// revoked states.
case task, ok := <-c.pipeline.NextBackupID():
// All backups in the pipeline have been
// processed, it is now safe to exit.
if !ok {
return
}
c.log.Debugf("Processing %v", task)
c.stats.taskReceived()
c.processTask(task)
// A new tower has been requested to be added. We'll
// update our persisted and in-memory state and consider
// its corresponding sessions, if any, as new
// candidates.
case msg := <-c.newTowers:
msg.errChan <- c.handleNewTower(msg.tower)
// A tower has been removed, so we'll remove certain
// information that's persisted and also in our
// in-memory state depending on the request, and set any
// of its corresponding candidate sessions as inactive.
case msg := <-c.staleTowers:
msg.errChan <- c.handleStaleTower(msg)
// A tower has been requested to be de-activated.
case msg := <-c.deactivateTowers:
msg.errChan <- c.handleDeactivateTower(msg)
// A request has come through to terminate a session.
case msg := <-c.terminateSessions:
msg.errChan <- c.handleTerminateSession(msg)
case <-c.quit:
return
}
}
}
}
// processTask attempts to schedule the given backupTask on the active
// sessionQueue. The task will either be accepted or rejected, after which the
// appropriate modifications to the client's state machine will be made. After
// every invocation of processTask, the caller should ensure that the
// sessionQueue hasn't been exhausted before proceeding to the next task. Tasks
// that are rejected because the active sessionQueue is full will be cached as
// the prevTask, and should be reprocessed after obtaining a new sessionQueue.
func (c *client) processTask(task *wtdb.BackupID) {
script, ok := c.cfg.getSweepScript(task.ChanID)
if !ok {
log.Infof("not processing task for unregistered channel: %s",
task.ChanID)
return
}
backupTask := newBackupTask(*task, script)
status, accepted := c.sessionQueue.AcceptTask(backupTask)
if accepted {
c.taskAccepted(task, status)
} else {
c.taskRejected(task, status)
}
}
// taskAccepted processes the acceptance of a task by a sessionQueue depending
// on the state the sessionQueue is in *after* the task is added. The client's
// prevTask is always removed as a result of this call. The client's
// sessionQueue will be removed if accepting the task left the sessionQueue in
// an exhausted state.
func (c *client) taskAccepted(task *wtdb.BackupID,
newStatus sessionQueueStatus) {
c.log.Infof("Queued %v successfully for session %v", task,
c.sessionQueue.ID())
c.stats.taskAccepted()
// If this task was accepted, we discard anything held in the prevTask.
// Either it was nil before, or is the task which was just accepted.
c.prevTask = nil
switch newStatus {
// The sessionQueue still has capacity after accepting this task.
case sessionQueueAvailable:
// The sessionQueue is full after accepting this task, so we will need
// to request a new one before proceeding.
case sessionQueueExhausted:
c.stats.sessionExhausted()
c.log.Debugf("Session %s exhausted", c.sessionQueue.ID())
// This task left the session exhausted, set it to nil and
// proceed to the next loop, so we can consume another
// pre-negotiated session or request another.
c.sessionQueue = nil
}
}
// taskRejected process the rejection of a task by a sessionQueue depending on
// the state the was in *before* the task was rejected. The client's prevTask
// will cache the task if the sessionQueue was exhausted beforehand, and nil
// the sessionQueue to find a new session. If the sessionQueue was not
// exhausted and not shutting down, the client marks the task as ineligible, as
// this implies we couldn't construct a valid justice transaction given the
// session's policy.
func (c *client) taskRejected(task *wtdb.BackupID,
curStatus sessionQueueStatus) {
switch curStatus {
// The sessionQueue has available capacity but the task was rejected,
// this indicates that the task was ineligible for backup.
case sessionQueueAvailable:
c.stats.taskIneligible()
c.log.Infof("Ignoring ineligible %v", task)
err := c.cfg.DB.MarkBackupIneligible(
task.ChanID, task.CommitHeight,
)
if err != nil {
c.log.Errorf("Unable to mark %v ineligible: %v",
task, err)
// It is safe to not handle this error, even if we could
// not persist the result. At worst, this task may be
// reprocessed on a subsequent start up, and will either
// succeed do a change in session parameters or fail in
// the same manner.
}
// If this task was rejected *and* the session had available
// capacity, we discard anything held in the prevTask. Either it
// was nil before, or is the task which was just rejected.
c.prevTask = nil
// The sessionQueue rejected the task because it is full, we will stash
// this task and try to add it to the next available sessionQueue.
case sessionQueueExhausted:
c.stats.sessionExhausted()
c.log.Debugf("Session %v exhausted, %v queued for next session",
c.sessionQueue.ID(), task)
// Cache the task that we pulled off, so that we can process it
// once a new session queue is available.
c.sessionQueue = nil
c.prevTask = task
// The sessionQueue rejected the task because it is shutting down. We
// will stash this task and try to add it to the next available
// sessionQueue.
case sessionQueueShuttingDown:
c.log.Debugf("Session %v is shutting down, %v queued for "+
"next session", c.sessionQueue.ID(), task)
// Cache the task that we pulled off, so that we can process it
// once a new session queue is available.
c.sessionQueue = nil
c.prevTask = task
}
}
// dial connects the peer at addr using privKey as our secret key for the
// connection. The connection will use the configured Net's resolver to resolve
// the address for either Tor or clear net connections.
func (c *client) dial(localKey keychain.SingleKeyECDH,
addr *lnwire.NetAddress) (wtserver.Peer, error) {
return c.cfg.AuthDial(localKey, addr, c.cfg.Dial)
}
// readMessage receives and parses the next message from the given Peer. An
// error is returned if a message is not received before the server's read
// timeout, the read off the wire failed, or the message could not be
// deserialized.
func (c *client) readMessage(peer wtserver.Peer) (wtwire.Message, error) {
// Set a read timeout to ensure we drop the connection if nothing is
// received in a timely manner.
err := peer.SetReadDeadline(time.Now().Add(c.cfg.ReadTimeout))
if err != nil {
err = fmt.Errorf("unable to set read deadline: %w", err)
c.log.Errorf("Unable to read msg: %v", err)
return nil, err
}
// Pull the next message off the wire,
rawMsg, err := peer.ReadNextMessage()
if err != nil {
err = fmt.Errorf("unable to read message: %w", err)
c.log.Errorf("Unable to read msg: %v", err)
return nil, err
}
// Parse the received message according to the watchtower wire
// specification.
msgReader := bytes.NewReader(rawMsg)
msg, err := wtwire.ReadMessage(msgReader, 0)
if err != nil {
err = fmt.Errorf("unable to parse message: %w", err)
c.log.Errorf("Unable to read msg: %v", err)
return nil, err
}
c.logMessage(peer, msg, true)
return msg, nil
}
// sendMessage sends a watchtower wire message to the target peer.
func (c *client) sendMessage(peer wtserver.Peer,
msg wtwire.Message) error {
// Encode the next wire message into the buffer.
// TODO(conner): use buffer pool
var b bytes.Buffer
_, err := wtwire.WriteMessage(&b, msg, 0)
if err != nil {
err = fmt.Errorf("unable to encode msg: %w", err)
c.log.Errorf("Unable to send msg: %v", err)
return err
}
// Set the write deadline for the connection, ensuring we drop the
// connection if nothing is sent in a timely manner.
err = peer.SetWriteDeadline(time.Now().Add(c.cfg.WriteTimeout))
if err != nil {
err = fmt.Errorf("unable to set write deadline: %w", err)
c.log.Errorf("Unable to send msg: %v", err)
return err
}
c.logMessage(peer, msg, false)
// Write out the full message to the remote peer.
_, err = peer.Write(b.Bytes())
if err != nil {
c.log.Errorf("Unable to send msg: %v", err)
}
return err
}
// newSessionQueue creates a sessionQueue from a ClientSession loaded from the
// database and supplying it with the resources needed by the client.
func (c *client) newSessionQueue(s *ClientSession,
updates []wtdb.CommittedUpdate) *sessionQueue {
return newSessionQueue(&sessionQueueConfig{
ClientSession: s,
ChainHash: c.cfg.ChainHash,
Dial: c.dial,
ReadMessage: c.readMessage,
SendMessage: c.sendMessage,
Signer: c.cfg.Signer,
DB: c.cfg.DB,
MinBackoff: c.cfg.MinBackoff,
MaxBackoff: c.cfg.MaxBackoff,
Log: c.log,
BuildBreachRetribution: c.cfg.BuildBreachRetribution,
TaskPipeline: c.pipeline,
}, updates)
}
// getOrInitActiveQueue checks the activeSessions set for a sessionQueue for the
// passed ClientSession. If it exists, the active sessionQueue is returned.
// Otherwise, a new sessionQueue is initialized and added to the set.
func (c *client) getOrInitActiveQueue(s *ClientSession,
updates []wtdb.CommittedUpdate) *sessionQueue {
if sq, ok := c.activeSessions.Get(s.ID); ok {
return sq
}
return c.initActiveQueue(s, updates)
}
// initActiveQueue creates a new sessionQueue from the passed ClientSession,
// adds the sessionQueue to the activeSessions set, and starts the sessionQueue
// so that it can deliver any committed updates or begin accepting newly
// assigned tasks.
func (c *client) initActiveQueue(s *ClientSession,
updates []wtdb.CommittedUpdate) *sessionQueue {
// Initialize the session queue, providing it with all the resources it
// requires from the client instance.
sq := c.newSessionQueue(s, updates)
// Add the session queue as an active session so that we remember to
// stop it on shutdown. This method will also start the queue so that it
// can be active in processing newly assigned tasks or to upload
// previously committed updates.
c.activeSessions.AddAndStart(sq)
return sq
}
// terminateSession sets the given session's status to CSessionTerminal meaning
// that it will not be used again.
func (c *client) terminateSession(id wtdb.SessionID) error {
errChan := make(chan error, 1)
select {
case c.terminateSessions <- &terminateSessMsg{
id: id,
errChan: errChan,
}:
case <-c.pipeline.quit:
return ErrClientExiting
}
select {
case err := <-errChan:
return err
case <-c.pipeline.quit:
return ErrClientExiting
}
}
// handleTerminateSession handles a request to terminate a session. It will
// first shut down the session if it is part of the active session set, then
// it will ensure that the active session queue is set reset if it is using the
// session in question. Finally, the session's status in the DB will be updated.
func (c *client) handleTerminateSession(msg *terminateSessMsg) error {
id := msg.id
delete(c.candidateSessions, id)
err := c.activeSessions.StopAndRemove(id, true)
if err != nil {
return fmt.Errorf("could not stop session %s: %w", id, err)
}
// If our active session queue corresponds to the session being
// terminated, then we'll proceed to negotiate a new one.
if c.sessionQueue != nil {
if bytes.Equal(c.sessionQueue.ID()[:], id[:]) {
c.sessionQueue = nil
}
}
return nil
}
// deactivateTower sends a tower deactivation request to the backupDispatcher
// where it will be handled synchronously. The request should result in all the
// sessions that we have with the given tower being shutdown and removed from
// our in-memory set of active sessions.
func (c *client) deactivateTower(id wtdb.TowerID,
pubKey *btcec.PublicKey) error {
errChan := make(chan error, 1)
select {
case c.deactivateTowers <- &deactivateTowerMsg{
id: id,
pubKey: pubKey,
errChan: errChan,
}:
case <-c.pipeline.quit:
return ErrClientExiting
}
select {
case err := <-errChan:
return err
case <-c.pipeline.quit:
return ErrClientExiting
}
}
// handleDeactivateTower handles a request to deactivate a tower. We will remove
// it from the in-memory candidate set, and we will also stop any active
// sessions we have with this tower.
func (c *client) handleDeactivateTower(msg *deactivateTowerMsg) error {
// Remove the tower from our in-memory candidate set so that it is not
// used for any new session negotiations.
err := c.candidateTowers.RemoveCandidate(msg.id, nil)
if err != nil {
return err
}
pubKey := msg.pubKey.SerializeCompressed()
sessions, err := c.cfg.DB.ListClientSessions(&msg.id)
if err != nil {
return fmt.Errorf("unable to retrieve sessions for tower %x: "+
"%v", pubKey, err)
}
// Iterate over all the sessions we have for this tower and remove them
// from our candidate set and also from our set of started, active
// sessions.
for sessionID := range sessions {
delete(c.candidateSessions, sessionID)
err = c.activeSessions.StopAndRemove(sessionID, false)
if err != nil {
return fmt.Errorf("could not stop session %s: %w",
sessionID, err)
}
}
// If our active session queue corresponds to the stale tower, we'll
// proceed to negotiate a new one.
if c.sessionQueue != nil {
towerKey := c.sessionQueue.tower.IdentityKey
if bytes.Equal(pubKey, towerKey.SerializeCompressed()) {
c.sessionQueue = nil
}
}
return nil
}
// addTower adds a new watchtower reachable at the given address and considers
// it for new sessions. If the watchtower already exists, then any new addresses
// included will be considered when dialing it for session negotiations and
// backups.
func (c *client) addTower(tower *Tower) error {
errChan := make(chan error, 1)
select {
case c.newTowers <- &newTowerMsg{
tower: tower,
errChan: errChan,
}:
case <-c.pipeline.quit:
return ErrClientExiting
}
select {
case err := <-errChan:
return err
case <-c.pipeline.quit:
return ErrClientExiting
}
}
// handleNewTower handles a request for a new tower to be added. If the tower
// already exists, then its corresponding sessions, if any, will be set
// considered as candidates.
func (c *client) handleNewTower(tower *Tower) error {
c.candidateTowers.AddCandidate(tower)
// Include all of its corresponding sessions to our set of candidates.
sessions, err := getClientSessions(
c.cfg.DB, c.cfg.SecretKeyRing, &tower.ID,
wtdb.WithPreEvalFilterFn(c.genSessionFilter(true)),
wtdb.WithPostEvalFilterFn(ExhaustedSessionFilter()),
)
if err != nil {
return fmt.Errorf("unable to determine sessions for tower %x: "+
"%v", tower.IdentityKey.SerializeCompressed(), err)
}
maps.Copy(c.candidateSessions, sessions)
return nil
}
// removeTower removes a watchtower from being considered for future session
// negotiations and from being used for any subsequent backups until it's added
// again. If an address is provided, then this call only serves as a way of
// removing the address from the watchtower instead.
func (c *client) removeTower(id wtdb.TowerID, pubKey *btcec.PublicKey,
addr net.Addr) error {
errChan := make(chan error, 1)
select {
case c.staleTowers <- &staleTowerMsg{
id: id,
pubKey: pubKey,
addr: addr,
errChan: errChan,
}:
case <-c.pipeline.quit:
return ErrClientExiting
}
select {
case err := <-errChan:
return err
case <-c.pipeline.quit:
return ErrClientExiting
}
}
// handleStaleTower handles a request for an existing tower to be removed. If
// none of the tower's sessions have pending updates, then they will become
// inactive and removed as candidates. If the active session queue corresponds
// to any of these sessions, a new one will be negotiated.
func (c *client) handleStaleTower(msg *staleTowerMsg) error {
// We'll first update our in-memory state.
err := c.candidateTowers.RemoveCandidate(msg.id, msg.addr)
if err != nil {
return err
}
// If an address was provided, then we're only meant to remove the
// address from the tower.
if msg.addr != nil {
return nil
}
// Otherwise, the tower should no longer be used for future session
// negotiations and backups.
pubKey := msg.pubKey.SerializeCompressed()
sessions, err := c.cfg.DB.ListClientSessions(&msg.id)
if err != nil {
return fmt.Errorf("unable to retrieve sessions for tower %x: "+
"%v", pubKey, err)
}
for sessionID := range sessions {
delete(c.candidateSessions, sessionID)
// Shutdown the session so that any pending updates are
// replayed back onto the main task pipeline.
err = c.activeSessions.StopAndRemove(sessionID, true)
if err != nil {
c.log.Errorf("could not stop session %s: %w", sessionID,
err)
}
}
// If our active session queue corresponds to the stale tower, we'll
// proceed to negotiate a new one.
if c.sessionQueue != nil {
towerKey := c.sessionQueue.tower.IdentityKey
if bytes.Equal(pubKey, towerKey.SerializeCompressed()) {
c.sessionQueue = nil
}
}
return nil
}
// registeredTowers retrieves the list of watchtowers registered with the
// client.
func (c *client) registeredTowers(towers []*wtdb.Tower,
opts ...wtdb.ClientSessionListOption) ([]*RegisteredTower, error) {
// Generate a filter that will fetch all the client's sessions
// regardless of if they are active or not.
opts = append(opts, wtdb.WithPreEvalFilterFn(c.genSessionFilter(false)))
clientSessions, err := c.cfg.DB.ListClientSessions(nil, opts...)
if err != nil {
return nil, err
}
// Construct a lookup map that coalesces all the sessions for a specific
// watchtower.
towerSessions := make(
map[wtdb.TowerID]map[wtdb.SessionID]*wtdb.ClientSession,
)
for id, s := range clientSessions {
sessions, ok := towerSessions[s.TowerID]
if !ok {
sessions = make(map[wtdb.SessionID]*wtdb.ClientSession)
towerSessions[s.TowerID] = sessions
}
sessions[id] = s
}
registeredTowers := make([]*RegisteredTower, 0, len(towerSessions))
for _, tower := range towers {
isActive := c.candidateTowers.IsActive(tower.ID)
registeredTowers = append(registeredTowers, &RegisteredTower{
Tower: tower,
Sessions: towerSessions[tower.ID],
ActiveSessionCandidate: isActive,
})
}
return registeredTowers, nil
}
// lookupTower retrieves the info of sessions held with the given tower handled
// by this client.
func (c *client) lookupTower(tower *wtdb.Tower,
opts ...wtdb.ClientSessionListOption) (*RegisteredTower, error) {
opts = append(opts, wtdb.WithPreEvalFilterFn(c.genSessionFilter(false)))
towerSessions, err := c.cfg.DB.ListClientSessions(&tower.ID, opts...)
if err != nil {
return nil, err
}
return &RegisteredTower{
Tower: tower,
Sessions: towerSessions,
ActiveSessionCandidate: c.candidateTowers.IsActive(tower.ID),
}, nil
}
// getStats returns the in-memory statistics of the client since startup.
func (c *client) getStats() ClientStats {
return c.stats.getStatsCopy()
}
// policy returns the active client policy configuration.
func (c *client) policy() wtpolicy.Policy {
return c.cfg.Policy
}
// logMessage writes information about a message received from a remote peer,
// using directional prepositions to signal whether the message was sent or
// received.
func (c *client) logMessage(
peer wtserver.Peer, msg wtwire.Message, read bool) {
var action = "Received"
var preposition = "from"
if !read {
action = "Sending"
preposition = "to"
}
summary := wtwire.MessageSummary(msg)
if len(summary) > 0 {
summary = "(" + summary + ")"
}
c.log.Debugf("%s %s%v %s %x@%s", action, msg.MsgType(), summary,
preposition, peer.RemotePub().SerializeCompressed(),
peer.RemoteAddr())
}
func newRandomDelay(max uint32) (uint32, error) {
var maxDelay big.Int
maxDelay.SetUint64(uint64(max))
randDelay, err := rand.Int(rand.Reader, &maxDelay)
if err != nil {
return 0, err
}
return uint32(randDelay.Uint64()), nil
}
package wtclient
import (
"net"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tor"
"github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
"github.com/lightningnetwork/lnd/watchtower/wtserver"
)
// DB abstracts the required database operations required by the watchtower
// client.
type DB interface {
// CreateTower initialize an address record used to communicate with a
// watchtower. Each Tower is assigned a unique ID, that is used to
// amortize storage costs of the public key when used by multiple
// sessions. If the tower already exists, the address is appended to the
// list of all addresses used to that tower previously and its
// corresponding sessions are marked as active.
CreateTower(*lnwire.NetAddress) (*wtdb.Tower, error)
// RemoveTower modifies a tower's record within the database. If an
// address is provided, then _only_ the address record should be removed
// from the tower's persisted state. Otherwise, we'll attempt to mark
// the tower as inactive. If any of its sessions have unacked updates,
// then ErrTowerUnackedUpdates is returned. If the tower doesn't have
// any sessions at all, it'll be completely removed from the database.
//
// NOTE: An error is not returned if the tower doesn't exist.
RemoveTower(*btcec.PublicKey, net.Addr) error
// TerminateSession sets the given session's status to CSessionTerminal
// meaning that it will not be usable again.
TerminateSession(id wtdb.SessionID) error
// LoadTower retrieves a tower by its public key.
LoadTower(*btcec.PublicKey) (*wtdb.Tower, error)
// LoadTowerByID retrieves a tower by its tower ID.
LoadTowerByID(wtdb.TowerID) (*wtdb.Tower, error)
// ListTowers retrieves the list of towers available within the
// database. The filter function may be set in order to filter out the
// towers to be returned.
ListTowers(filter wtdb.TowerFilterFn) ([]*wtdb.Tower, error)
// NextSessionKeyIndex reserves a new session key derivation index for a
// particular tower id and blob type. The index is reserved for that
// (tower, blob type) pair until CreateClientSession is invoked for that
// tower and index, at which point a new index for that tower can be
// reserved. Multiple calls to this method before CreateClientSession is
// invoked should return the same index unless forceNext is true.
NextSessionKeyIndex(wtdb.TowerID, blob.Type, bool) (uint32, error)
// CreateClientSession saves a newly negotiated client session to the
// client's database. This enables the session to be used across
// restarts.
CreateClientSession(*wtdb.ClientSession) error
// ListClientSessions returns the set of all client sessions known to
// the db. An optional tower ID can be used to filter out any client
// sessions in the response that do not correspond to this tower.
ListClientSessions(*wtdb.TowerID, ...wtdb.ClientSessionListOption) (
map[wtdb.SessionID]*wtdb.ClientSession, error)
// GetClientSession loads the ClientSession with the given ID from the
// DB.
GetClientSession(wtdb.SessionID,
...wtdb.ClientSessionListOption) (*wtdb.ClientSession, error)
// FetchSessionCommittedUpdates retrieves the current set of un-acked
// updates of the given session.
FetchSessionCommittedUpdates(id *wtdb.SessionID) (
[]wtdb.CommittedUpdate, error)
// IsAcked returns true if the given backup has been backed up using
// the given session.
IsAcked(id *wtdb.SessionID, backupID *wtdb.BackupID) (bool, error)
// NumAckedUpdates returns the number of backups that have been
// successfully backed up using the given session.
NumAckedUpdates(id *wtdb.SessionID) (uint64, error)
// FetchChanInfos loads a mapping from all registered channels to
// their wtdb.ChannelInfo. Only the channels that have not yet been
// marked as closed will be loaded.
FetchChanInfos() (wtdb.ChannelInfos, error)
// MarkChannelClosed will mark a registered channel as closed by setting
// its closed-height as the given block height. It returns a list of
// session IDs for sessions that are now considered closable due to the
// close of this channel. The details for this channel will be deleted
// from the DB if there are no more sessions in the DB that contain
// updates for this channel.
MarkChannelClosed(chanID lnwire.ChannelID, blockHeight uint32) (
[]wtdb.SessionID, error)
// ListClosableSessions fetches and returns the IDs for all sessions
// marked as closable.
ListClosableSessions() (map[wtdb.SessionID]uint32, error)
// DeleteSession can be called when a session should be deleted from the
// DB. All references to the session will also be deleted from the DB.
// A session will only be deleted if it was previously marked as
// closable.
DeleteSession(id wtdb.SessionID) error
// RegisterChannel registers a channel for use within the client
// database. For now, all that is stored in the channel summary is the
// sweep pkscript that we'd like any tower sweeps to pay into. In the
// future, this will be extended to contain more info to allow the
// client efficiently request historical states to be backed up under
// the client's active policy.
RegisterChannel(lnwire.ChannelID, []byte) error
// MarkBackupIneligible records that the state identified by the
// (channel id, commit height) tuple was ineligible for being backed up
// under the current policy. This state can be retried later under a
// different policy.
MarkBackupIneligible(chanID lnwire.ChannelID, commitHeight uint64) error
// CommitUpdate writes the next state update for a particular
// session, so that we can be sure to resend it after a restart if it
// hasn't been ACK'd by the tower. The sequence number of the update
// should be exactly one greater than the existing entry, and less that
// or equal to the session's MaxUpdates.
CommitUpdate(id *wtdb.SessionID,
update *wtdb.CommittedUpdate) (uint16, error)
// AckUpdate records an acknowledgment from the watchtower that the
// update identified by seqNum was received and saved. The returned
// lastApplied will be recorded.
AckUpdate(id *wtdb.SessionID, seqNum, lastApplied uint16) error
// GetDBQueue returns a BackupID Queue instance under the given name
// space.
GetDBQueue(namespace []byte) wtdb.Queue[*wtdb.BackupID]
// DeleteCommittedUpdates deletes all the committed updates belonging to
// the given session from the db.
DeleteCommittedUpdates(id *wtdb.SessionID) error
// DeactivateTower sets the given tower's status to inactive. This means
// that this tower's sessions won't be loaded and used for backups.
// CreateTower can be used to reactivate the tower again.
DeactivateTower(pubKey *btcec.PublicKey) error
}
// AuthDialer connects to a remote node using an authenticated transport, such
// as brontide. The dialer argument is used to specify a resolver, which allows
// this method to be used over Tor or clear net connections.
type AuthDialer func(localKey keychain.SingleKeyECDH,
netAddr *lnwire.NetAddress,
dialer tor.DialFunc) (wtserver.Peer, error)
// ECDHKeyRing abstracts the ability to derive shared ECDH keys given a
// description of the derivation path of a private key.
type ECDHKeyRing interface {
keychain.ECDHRing
// DeriveKey attempts to derive an arbitrary key specified by the
// passed KeyLocator. This may be used in several recovery scenarios,
// or when manually rotating something like our current default node
// key.
DeriveKey(keyLoc keychain.KeyLocator) (keychain.KeyDescriptor, error)
}
// Tower represents the info about a watchtower server that a watchtower client
// needs in order to connect to it.
type Tower struct {
// ID is the unique, db-assigned, identifier for this tower.
ID wtdb.TowerID
// IdentityKey is the public key of the remote node, used to
// authenticate the brontide transport.
IdentityKey *btcec.PublicKey
// Addresses is an AddressIterator that can be used to manage the
// addresses for this tower.
Addresses AddressIterator
}
// NewTowerFromDBTower converts a wtdb.Tower, which uses a static address list,
// into a Tower which uses an address iterator.
func NewTowerFromDBTower(t *wtdb.Tower) (*Tower, error) {
addrs, err := newAddressIterator(t.Addresses...)
if err != nil {
return nil, err
}
return &Tower{
ID: t.ID,
IdentityKey: t.IdentityKey,
Addresses: addrs,
}, nil
}
// ClientSession represents the session that a tower client has with a server.
type ClientSession struct {
// ID is the client's public key used when authenticating with the
// tower.
ID wtdb.SessionID
wtdb.ClientSessionBody
// Tower represents the tower that the client session has been made
// with.
Tower *Tower
// SessionKeyECDH is the ECDH capable wrapper of the ephemeral secret
// key used to connect to the watchtower.
SessionKeyECDH keychain.SingleKeyECDH
}
// NewClientSessionFromDBSession converts a wtdb.ClientSession to a
// ClientSession.
func NewClientSessionFromDBSession(s *wtdb.ClientSession, tower *Tower,
keyRing ECDHKeyRing) (*ClientSession, error) {
towerKeyDesc, err := keyRing.DeriveKey(
keychain.KeyLocator{
Family: keychain.KeyFamilyTowerSession,
Index: s.KeyIndex,
},
)
if err != nil {
return nil, err
}
sessionKeyECDH := keychain.NewPubKeyECDH(
towerKeyDesc, keyRing,
)
return &ClientSession{
ID: s.ID,
ClientSessionBody: s.ClientSessionBody,
Tower: tower,
SessionKeyECDH: sessionKeyECDH,
}, nil
}
package wtclient
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("WTCL", nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package wtclient
import (
"errors"
"fmt"
"net"
"sync"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channelnotifier"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/subscribe"
"github.com/lightningnetwork/lnd/tor"
"github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
)
// ClientManager is the primary interface used by the daemon to control a
// client's lifecycle and backup revoked states.
type ClientManager interface {
// AddTower adds a new watchtower reachable at the given address and
// considers it for new sessions. If the watchtower already exists, then
// any new addresses included will be considered when dialing it for
// session negotiations and backups.
AddTower(*lnwire.NetAddress) error
// RemoveTower removes a watchtower from being considered for future
// session negotiations and from being used for any subsequent backups
// until it's added again. If an address is provided, then this call
// only serves as a way of removing the address from the watchtower
// instead.
RemoveTower(*btcec.PublicKey, net.Addr) error
// DeactivateTower sets the given tower's status to inactive so that it
// is not considered for session negotiation. Its sessions will also not
// be used while the tower is inactive.
DeactivateTower(pubKey *btcec.PublicKey) error
// TerminateSession sets the given session's status to CSessionTerminal
// meaning that it will not be used again.
TerminateSession(id wtdb.SessionID) error
// Stats returns the in-memory statistics of the client since startup.
Stats() ClientStats
// Policy returns the active client policy configuration.
Policy(blob.Type) (wtpolicy.Policy, error)
// RegisteredTowers retrieves the list of watchtowers registered with
// the client. It returns a set of registered towers per client policy
// type.
RegisteredTowers(opts ...wtdb.ClientSessionListOption) (
map[blob.Type][]*RegisteredTower, error)
// LookupTower retrieves a registered watchtower through its public key.
LookupTower(*btcec.PublicKey, ...wtdb.ClientSessionListOption) (
map[blob.Type]*RegisteredTower, error)
// RegisterChannel persistently initializes any channel-dependent
// parameters within the client. This should be called during link
// startup to ensure that the client is able to support the link during
// operation.
RegisterChannel(lnwire.ChannelID, channeldb.ChannelType) error
// BackupState initiates a request to back up a particular revoked
// state. If the method returns nil, the backup is guaranteed to be
// successful unless the justice transaction would create dust outputs
// when trying to abide by the negotiated policy.
BackupState(chanID *lnwire.ChannelID, stateNum uint64) error
}
// Config provides the client with access to the resources it requires to
// perform its duty. All nillable fields must be non-nil for the tower to be
// initialized properly.
type Config struct {
// Signer provides access to the wallet so that the client can sign
// justice transactions that spend from a remote party's commitment
// transaction.
Signer input.Signer
// SubscribeChannelEvents can be used to subscribe to channel event
// notifications.
SubscribeChannelEvents func() (subscribe.Subscription, error)
// FetchClosedChannel can be used to fetch the info about a closed
// channel. If the channel is not found or not yet closed then
// channeldb.ErrClosedChannelNotFound will be returned.
FetchClosedChannel func(cid lnwire.ChannelID) (
*channeldb.ChannelCloseSummary, error)
// ChainNotifier can be used to subscribe to block notifications.
ChainNotifier chainntnfs.ChainNotifier
// BuildBreachRetribution is a function closure that allows the client
// fetch the breach retribution info for a certain channel at a certain
// revoked commitment height.
BuildBreachRetribution BreachRetributionBuilder
// NewAddress generates a new on-chain sweep pkscript.
NewAddress func() ([]byte, error)
// SecretKeyRing is used to derive the session keys used to communicate
// with the tower. The client only stores the KeyLocators internally so
// that we never store private keys on disk.
SecretKeyRing ECDHKeyRing
// Dial connects to an addr using the specified net and returns the
// connection object.
Dial tor.DialFunc
// AuthDialer establishes a brontide connection over an onion or clear
// network.
AuthDial AuthDialer
// DB provides access to the client's stable storage medium.
DB DB
// ChainHash identifies the chain that the client is on and for which
// the tower must be watching to monitor for breaches.
ChainHash chainhash.Hash
// ReadTimeout is the duration we will wait during a read before
// breaking out of a blocking read. If the value is less than or equal
// to zero, the default will be used instead.
ReadTimeout time.Duration
// WriteTimeout is the duration we will wait during a write before
// breaking out of a blocking write. If the value is less than or equal
// to zero, the default will be used instead.
WriteTimeout time.Duration
// MinBackoff defines the initial backoff applied to connections with
// watchtowers. Subsequent backoff durations will grow exponentially up
// until MaxBackoff.
MinBackoff time.Duration
// MaxBackoff defines the maximum backoff applied to connections with
// watchtowers. If the exponential backoff produces a timeout greater
// than this value, the backoff will be clamped to MaxBackoff.
MaxBackoff time.Duration
// SessionCloseRange is the range over which we will generate a random
// number of blocks to delay closing a session after its last channel
// has been closed.
SessionCloseRange uint32
// MaxTasksInMemQueue is the maximum number of backup tasks that should
// be kept in-memory. Any more tasks will overflow to disk.
MaxTasksInMemQueue uint64
}
// Manager manages the various tower clients that are active. A client is
// required for each different commitment transaction type. The Manager acts as
// a tower client multiplexer.
type Manager struct {
started sync.Once
stopped sync.Once
cfg *Config
clients map[blob.Type]*client
clientsMu sync.Mutex
backupMu sync.Mutex
chanInfos wtdb.ChannelInfos
chanBlobType map[lnwire.ChannelID]blob.Type
closableSessionQueue *sessionCloseMinHeap
wg sync.WaitGroup
quit chan struct{}
}
var _ ClientManager = (*Manager)(nil)
// NewManager constructs a new Manager.
func NewManager(config *Config, policies ...wtpolicy.Policy) (*Manager, error) {
// Copy the config to prevent side effects from modifying both the
// internal and external version of the Config.
cfg := *config
// Set the read timeout to the default if none was provided.
if cfg.ReadTimeout <= 0 {
cfg.ReadTimeout = DefaultReadTimeout
}
// Set the write timeout to the default if none was provided.
if cfg.WriteTimeout <= 0 {
cfg.WriteTimeout = DefaultWriteTimeout
}
chanInfos, err := cfg.DB.FetchChanInfos()
if err != nil {
return nil, err
}
m := &Manager{
cfg: &cfg,
clients: make(map[blob.Type]*client),
chanBlobType: make(map[lnwire.ChannelID]blob.Type),
chanInfos: chanInfos,
closableSessionQueue: newSessionCloseMinHeap(),
quit: make(chan struct{}),
}
for _, policy := range policies {
if err = policy.Validate(); err != nil {
return nil, err
}
if err = m.newClient(policy); err != nil {
return nil, err
}
}
return m, nil
}
// newClient constructs a new client and adds it to the set of clients that
// the Manager is keeping track of.
func (m *Manager) newClient(policy wtpolicy.Policy) error {
m.clientsMu.Lock()
defer m.clientsMu.Unlock()
_, ok := m.clients[policy.BlobType]
if ok {
return fmt.Errorf("a client with blob type %s has "+
"already been registered", policy.BlobType)
}
cfg := &clientCfg{
Config: m.cfg,
Policy: policy,
getSweepScript: m.getSweepScript,
}
client, err := newClient(cfg)
if err != nil {
return err
}
m.clients[policy.BlobType] = client
return nil
}
// Start starts all the clients that have been registered with the Manager.
func (m *Manager) Start() error {
var returnErr error
m.started.Do(func() {
chanSub, err := m.cfg.SubscribeChannelEvents()
if err != nil {
returnErr = err
return
}
// Iterate over the list of registered channels and check if any
// of them can be marked as closed.
for id := range m.chanInfos {
isClosed, closedHeight, err := m.isChannelClosed(id)
if err != nil {
returnErr = err
return
}
if !isClosed {
continue
}
_, err = m.cfg.DB.MarkChannelClosed(id, closedHeight)
if err != nil {
log.Errorf("could not mark channel(%s) as "+
"closed: %v", id, err)
continue
}
// Since the channel has been marked as closed, we can
// also remove it from the channel summaries map.
delete(m.chanInfos, id)
}
// Load all closable sessions.
closableSessions, err := m.cfg.DB.ListClosableSessions()
if err != nil {
returnErr = err
return
}
err = m.trackClosableSessions(closableSessions)
if err != nil {
returnErr = err
return
}
m.wg.Add(1)
go m.handleChannelCloses(chanSub)
// Subscribe to new block events.
blockEvents, err := m.cfg.ChainNotifier.RegisterBlockEpochNtfn(
nil,
)
if err != nil {
returnErr = err
return
}
m.wg.Add(1)
go m.handleClosableSessions(blockEvents)
m.clientsMu.Lock()
defer m.clientsMu.Unlock()
for _, client := range m.clients {
if err := client.start(); err != nil {
returnErr = err
return
}
}
})
return returnErr
}
// Stop stops all the clients that the Manger is managing.
func (m *Manager) Stop() error {
var returnErr error
m.stopped.Do(func() {
m.clientsMu.Lock()
defer m.clientsMu.Unlock()
close(m.quit)
m.wg.Wait()
for _, client := range m.clients {
if err := client.stop(); err != nil {
returnErr = err
}
}
})
return returnErr
}
// AddTower adds a new watchtower reachable at the given address and considers
// it for new sessions. If the watchtower already exists, then any new addresses
// included will be considered when dialing it for session negotiations and
// backups.
func (m *Manager) AddTower(address *lnwire.NetAddress) error {
// We'll start by updating our persisted state, followed by the
// in-memory state of each client, with the new tower. This might not
// actually be a new tower, but it might include a new address at which
// it can be reached.
dbTower, err := m.cfg.DB.CreateTower(address)
if err != nil {
return err
}
tower, err := NewTowerFromDBTower(dbTower)
if err != nil {
return err
}
m.clientsMu.Lock()
defer m.clientsMu.Unlock()
for blobType, client := range m.clients {
clientType, err := blobType.Identifier()
if err != nil {
return err
}
if err := client.addTower(tower); err != nil {
return fmt.Errorf("could not add tower(%x) to the %s "+
"tower client: %w",
tower.IdentityKey.SerializeCompressed(),
clientType, err)
}
}
return nil
}
// RemoveTower removes a watchtower from being considered for future session
// negotiations and from being used for any subsequent backups until it's added
// again. If an address is provided, then this call only serves as a way of
// removing the address from the watchtower instead.
func (m *Manager) RemoveTower(key *btcec.PublicKey, addr net.Addr) error {
// We'll load the tower before potentially removing it in order to
// retrieve its ID within the database.
dbTower, err := m.cfg.DB.LoadTower(key)
if err != nil {
return err
}
m.clientsMu.Lock()
defer m.clientsMu.Unlock()
for _, client := range m.clients {
err := client.removeTower(dbTower.ID, key, addr)
if err != nil {
return err
}
}
if err := m.cfg.DB.RemoveTower(key, addr); err != nil {
// If the persisted state update fails, re-add the address to
// our client's in-memory state.
tower, newTowerErr := NewTowerFromDBTower(dbTower)
if newTowerErr != nil {
log.Errorf("could not create new in-memory tower: %v",
newTowerErr)
return err
}
for _, client := range m.clients {
addTowerErr := client.addTower(tower)
if addTowerErr != nil {
log.Errorf("could not re-add tower: %v",
addTowerErr)
}
}
return err
}
return nil
}
// TerminateSession sets the given session's status to CSessionTerminal meaning
// that it will not be used again.
func (m *Manager) TerminateSession(id wtdb.SessionID) error {
m.clientsMu.Lock()
defer m.clientsMu.Unlock()
for _, client := range m.clients {
err := client.terminateSession(id)
if err != nil {
return err
}
}
// Finally, mark the session as terminated in the DB.
return m.cfg.DB.TerminateSession(id)
}
// DeactivateTower sets the given tower's status to inactive so that it is not
// considered for session negotiation. Its sessions will also not be used while
// the tower is inactive.
func (m *Manager) DeactivateTower(key *btcec.PublicKey) error {
// We'll load the tower in order to retrieve its ID within the database.
tower, err := m.cfg.DB.LoadTower(key)
if err != nil {
return err
}
m.clientsMu.Lock()
defer m.clientsMu.Unlock()
for _, client := range m.clients {
err := client.deactivateTower(tower.ID, tower.IdentityKey)
if err != nil {
return err
}
}
// Finally, mark the tower as inactive in the DB.
err = m.cfg.DB.DeactivateTower(key)
if err != nil {
log.Errorf("Could not deactivate the tower. Re-activating. %v",
err)
// If the persisted state update fails, re-add the address to
// our client's in-memory state.
tower, newTowerErr := NewTowerFromDBTower(tower)
if newTowerErr != nil {
log.Errorf("Could not create new in-memory tower: %v",
newTowerErr)
return err
}
for _, client := range m.clients {
addTowerErr := client.addTower(tower)
if addTowerErr != nil {
log.Errorf("Could not re-add tower: %v",
addTowerErr)
}
}
return err
}
return nil
}
// Stats returns the in-memory statistics of the clients managed by the Manager
// since startup.
func (m *Manager) Stats() ClientStats {
m.clientsMu.Lock()
defer m.clientsMu.Unlock()
var resp ClientStats
for _, client := range m.clients {
stats := client.getStats()
resp.NumTasksAccepted += stats.NumTasksAccepted
resp.NumTasksIneligible += stats.NumTasksIneligible
resp.NumTasksPending += stats.NumTasksPending
resp.NumSessionsAcquired += stats.NumSessionsAcquired
resp.NumSessionsExhausted += stats.NumSessionsExhausted
}
return resp
}
// RegisteredTowers retrieves the list of watchtowers being used by the various
// clients.
func (m *Manager) RegisteredTowers(opts ...wtdb.ClientSessionListOption) (
map[blob.Type][]*RegisteredTower, error) {
towers, err := m.cfg.DB.ListTowers(nil)
if err != nil {
return nil, err
}
m.clientsMu.Lock()
defer m.clientsMu.Unlock()
resp := make(map[blob.Type][]*RegisteredTower)
for _, client := range m.clients {
towers, err := client.registeredTowers(towers, opts...)
if err != nil {
return nil, err
}
resp[client.policy().BlobType] = towers
}
return resp, nil
}
// LookupTower retrieves a registered watchtower through its public key.
func (m *Manager) LookupTower(key *btcec.PublicKey,
opts ...wtdb.ClientSessionListOption) (map[blob.Type]*RegisteredTower,
error) {
tower, err := m.cfg.DB.LoadTower(key)
if err != nil {
return nil, err
}
m.clientsMu.Lock()
defer m.clientsMu.Unlock()
resp := make(map[blob.Type]*RegisteredTower)
for _, client := range m.clients {
tower, err := client.lookupTower(tower, opts...)
if err != nil {
return nil, err
}
resp[client.policy().BlobType] = tower
}
return resp, nil
}
// Policy returns the active client policy configuration for the client using
// the given blob type.
func (m *Manager) Policy(blobType blob.Type) (wtpolicy.Policy, error) {
m.clientsMu.Lock()
defer m.clientsMu.Unlock()
var policy wtpolicy.Policy
client, ok := m.clients[blobType]
if !ok {
return policy, fmt.Errorf("no client for the given blob type")
}
return client.policy(), nil
}
// RegisterChannel persistently initializes any channel-dependent parameters
// within the client. This should be called during link startup to ensure that
// the client is able to support the link during operation.
func (m *Manager) RegisterChannel(id lnwire.ChannelID,
chanType channeldb.ChannelType) error {
blobType := blob.TypeFromChannel(chanType)
m.clientsMu.Lock()
if _, ok := m.clients[blobType]; !ok {
m.clientsMu.Unlock()
return fmt.Errorf("no client registered for blob type %s",
blobType)
}
m.clientsMu.Unlock()
m.backupMu.Lock()
defer m.backupMu.Unlock()
// If a pkscript for this channel already exists, the channel has been
// previously registered.
if _, ok := m.chanInfos[id]; ok {
// Keep track of which blob type this channel will use for
// updates.
m.chanBlobType[id] = blobType
return nil
}
// Otherwise, generate a new sweep pkscript used to sweep funds for this
// channel.
pkScript, err := m.cfg.NewAddress()
if err != nil {
return err
}
// Persist the sweep pkscript so that restarts will not introduce
// address inflation when the channel is reregistered after a restart.
err = m.cfg.DB.RegisterChannel(id, pkScript)
if err != nil {
return err
}
// Finally, cache the pkscript in our in-memory cache to avoid db
// lookups for the remainder of the daemon's execution.
m.chanInfos[id] = &wtdb.ChannelInfo{
ClientChanSummary: wtdb.ClientChanSummary{
SweepPkScript: pkScript,
},
}
// Keep track of which blob type this channel will use for updates.
m.chanBlobType[id] = blobType
return nil
}
// BackupState initiates a request to back up a particular revoked state. If the
// method returns nil, the backup is guaranteed to be successful unless the
// justice transaction would create dust outputs when trying to abide by the
// negotiated policy.
func (m *Manager) BackupState(chanID *lnwire.ChannelID, stateNum uint64) error {
select {
case <-m.quit:
return ErrClientExiting
default:
}
// Make sure that this channel is registered with the tower client.
m.backupMu.Lock()
info, ok := m.chanInfos[*chanID]
if !ok {
m.backupMu.Unlock()
return ErrUnregisteredChannel
}
// Ignore backups that have already been presented to the client.
var duplicate bool
info.MaxHeight.WhenSome(func(maxHeight uint64) {
if stateNum <= maxHeight {
duplicate = true
}
})
if duplicate {
m.backupMu.Unlock()
log.Debugf("Ignoring duplicate backup for chanid=%v at "+
"height=%d", chanID, stateNum)
return nil
}
// This backup has a higher commit height than any known backup for this
// channel. We'll update our tip so that we won't accept it again if the
// link flaps.
m.chanInfos[*chanID].MaxHeight = fn.Some(stateNum)
blobType, ok := m.chanBlobType[*chanID]
if !ok {
m.backupMu.Unlock()
return ErrUnregisteredChannel
}
m.backupMu.Unlock()
m.clientsMu.Lock()
client, ok := m.clients[blobType]
if !ok {
m.clientsMu.Unlock()
return fmt.Errorf("no client registered for blob type %s",
blobType)
}
m.clientsMu.Unlock()
return client.backupState(chanID, stateNum)
}
// isChanClosed can be used to check if the channel with the given ID has been
// closed. If it has been, the block height in which its closing transaction was
// mined will also be returned.
func (m *Manager) isChannelClosed(id lnwire.ChannelID) (bool, uint32,
error) {
chanSum, err := m.cfg.FetchClosedChannel(id)
if errors.Is(err, channeldb.ErrClosedChannelNotFound) {
return false, 0, nil
} else if err != nil {
return false, 0, err
}
return true, chanSum.CloseHeight, nil
}
// trackClosableSessions takes in a map of session IDs to the earliest block
// height at which the session should be deleted. For each of the sessions,
// a random delay is added to the block height and the session is added to the
// closableSessionQueue.
func (m *Manager) trackClosableSessions(
sessions map[wtdb.SessionID]uint32) error {
// For each closable session, add a random delay to its close
// height and add it to the closableSessionQueue.
for sID, blockHeight := range sessions {
delay, err := newRandomDelay(m.cfg.SessionCloseRange)
if err != nil {
return err
}
deleteHeight := blockHeight + delay
m.closableSessionQueue.Push(&sessionCloseItem{
sessionID: sID,
deleteHeight: deleteHeight,
})
}
return nil
}
// handleChannelCloses listens for channel close events and marks channels as
// closed in the DB.
//
// NOTE: This method MUST be run as a goroutine.
func (m *Manager) handleChannelCloses(chanSub subscribe.Subscription) {
defer m.wg.Done()
log.Debugf("Starting channel close handler")
defer log.Debugf("Stopping channel close handler")
for {
select {
case update, ok := <-chanSub.Updates():
if !ok {
log.Debugf("Channel notifier has exited")
return
}
// We only care about channel-close events.
event, ok := update.(channelnotifier.ClosedChannelEvent)
if !ok {
continue
}
chanID := lnwire.NewChanIDFromOutPoint(
event.CloseSummary.ChanPoint,
)
log.Debugf("Received ClosedChannelEvent for "+
"channel: %s", chanID)
err := m.handleClosedChannel(
chanID, event.CloseSummary.CloseHeight,
)
if err != nil {
log.Errorf("Could not handle channel close "+
"event for channel(%s): %v", chanID,
err)
}
case <-m.quit:
return
}
}
}
// handleClosedChannel handles the closure of a single channel. It will mark the
// channel as closed in the DB, then it will handle all the sessions that are
// now closable due to the channel closure.
func (m *Manager) handleClosedChannel(chanID lnwire.ChannelID,
closeHeight uint32) error {
m.backupMu.Lock()
defer m.backupMu.Unlock()
// We only care about channels registered with the tower client.
if _, ok := m.chanInfos[chanID]; !ok {
return nil
}
log.Debugf("Marking channel(%s) as closed", chanID)
sessions, err := m.cfg.DB.MarkChannelClosed(chanID, closeHeight)
if err != nil {
return fmt.Errorf("could not mark channel(%s) as closed: %w",
chanID, err)
}
closableSessions := make(map[wtdb.SessionID]uint32, len(sessions))
for _, sess := range sessions {
closableSessions[sess] = closeHeight
}
log.Debugf("Tracking %d new closable sessions as a result of "+
"closing channel %s", len(closableSessions), chanID)
err = m.trackClosableSessions(closableSessions)
if err != nil {
return fmt.Errorf("could not track closable sessions: %w", err)
}
delete(m.chanInfos, chanID)
return nil
}
// handleClosableSessions listens for new block notifications. For each block,
// it checks the closableSessionQueue to see if there is a closable session with
// a delete-height smaller than or equal to the new block, if there is then the
// tower is informed that it can delete the session, and then we also delete it
// from our DB.
func (m *Manager) handleClosableSessions(
blocksChan *chainntnfs.BlockEpochEvent) {
defer m.wg.Done()
log.Debug("Starting closable sessions handler")
defer log.Debug("Stopping closable sessions handler")
for {
select {
case newBlock := <-blocksChan.Epochs:
if newBlock == nil {
return
}
height := uint32(newBlock.Height)
for {
select {
case <-m.quit:
return
default:
}
// If there are no closable sessions that we
// need to handle, then we are done and can
// reevaluate when the next block comes.
item := m.closableSessionQueue.Top()
if item == nil {
break
}
// If there is closable session but the delete
// height we have set for it is after the
// current block height, then our work is done.
if item.deleteHeight > height {
break
}
// Otherwise, we pop this item from the heap
// and handle it.
m.closableSessionQueue.Pop()
// Fetch the session from the DB so that we can
// extract the Tower info.
sess, err := m.cfg.DB.GetClientSession(
item.sessionID,
)
if err != nil {
log.Errorf("error calling "+
"GetClientSession for "+
"session %s: %v",
item.sessionID, err)
continue
}
// get appropriate client.
m.clientsMu.Lock()
client, ok := m.clients[sess.Policy.BlobType]
if !ok {
m.clientsMu.Unlock()
log.Errorf("no client currently " +
"active for the session type")
continue
}
m.clientsMu.Unlock()
clientName, err := client.policy().BlobType.
Identifier()
if err != nil {
log.Errorf("could not get client "+
"identifier: %v", err)
continue
}
// Stop the session and remove it from the
// in-memory set.
err = client.stopAndRemoveSession(
item.sessionID, true,
)
if err != nil {
log.Errorf("could not remove "+
"session(%s) from in-memory "+
"set of the %s client: %v",
item.sessionID, clientName, err)
continue
}
err = client.deleteSessionFromTower(sess)
if err != nil {
log.Errorf("error deleting "+
"session %s from tower: %v",
sess.ID, err)
continue
}
err = m.cfg.DB.DeleteSession(item.sessionID)
if err != nil {
log.Errorf("could not delete "+
"session(%s) from DB: %w",
sess.ID, err)
continue
}
}
case <-m.quit:
return
}
}
}
func (m *Manager) getSweepScript(id lnwire.ChannelID) ([]byte, bool) {
m.backupMu.Lock()
defer m.backupMu.Unlock()
summary, ok := m.chanInfos[id]
if !ok {
return nil, false
}
return summary.SweepPkScript, true
}
package wtclient
import (
"container/list"
"errors"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
)
const (
// dbErrorBackoff is the length of time we will back off before retrying
// any DB action that failed.
dbErrorBackoff = time.Second * 5
)
// internalTask wraps a BackupID task with a success channel.
type internalTask[T any] struct {
task T
success chan bool
}
// newInternalTask creates a new internalTask with the given task.
func newInternalTask[T any](task T) *internalTask[T] {
return &internalTask[T]{
task: task,
success: make(chan bool),
}
}
// DiskOverflowQueue is a queue that must be initialised with a certain maximum
// buffer size which represents the maximum number of elements that the queue
// should hold in memory. If the queue is full, then any new elements added to
// the queue will be persisted to disk instead. Once a consumer starts reading
// from the front of the queue again then items on disk will be moved into the
// queue again. The queue is also re-start safe. When it is stopped, any items
// in the memory queue, will be persisted to disk. On start up, the queue will
// be re-initialised with the items on disk.
type DiskOverflowQueue[T any] struct {
startOnce sync.Once
stopOnce sync.Once
log btclog.Logger
// db is the database that will be used to persist queue items to disk.
db wtdb.Queue[T]
// toDisk represents the current mode of operation of the queue.
toDisk atomic.Bool
// We used an unbound list for the input of the queue so that producers
// putting items into the queue are never blocked.
inputListMu sync.Mutex
inputListCond *sync.Cond
inputList *list.List
// inputChan is an unbuffered channel used to pass items from
// drainInputList to feedMemQueue.
inputChan chan *internalTask[T]
// memQueue is a buffered channel used to pass items from
// feedMemQueue to feedOutputChan.
memQueue chan T
// outputChan is an unbuffered channel from which items at the head of
// the queue can be read.
outputChan chan T
// newDiskItemSignal is used to signal that there is a new item in the
// main disk queue. There should only be one reader and one writer for
// this channel.
newDiskItemSignal chan struct{}
// leftOverItem1 will be a non-nil task on shutdown if the
// feedOutputChan method was holding an unhandled tasks at shutdown
// time. Since feedOutputChan handles the very head of the queue, this
// item should be the first to be reloaded on restart.
leftOverItem1 *T
// leftOverItems2 will be non-empty on shutdown if the feedMemQueue
// method was holding any unhandled tasks at shutdown time. Since
// feedMemQueue manages the input to the queue, the tasks should be
// pushed to the head of the disk queue.
leftOverItems2 []T
// leftOverItem3 will be non-nil on shutdown if drainInputList was
// holding an unhandled task at shutdown time. This task should be put
// at the tail of the disk queue but should come before any input list
// task.
leftOverItem3 *T
quit chan struct{}
wg sync.WaitGroup
}
// NewDiskOverflowQueue constructs a new DiskOverflowQueue.
func NewDiskOverflowQueue[T any](db wtdb.Queue[T], maxQueueSize uint64,
logger btclog.Logger) (*DiskOverflowQueue[T], error) {
if maxQueueSize < 2 {
return nil, errors.New("the in-memory queue buffer size " +
"must be larger than 2")
}
q := &DiskOverflowQueue[T]{
log: logger,
db: db,
inputList: list.New(),
newDiskItemSignal: make(chan struct{}, 1),
inputChan: make(chan *internalTask[T]),
memQueue: make(chan T, maxQueueSize-2),
outputChan: make(chan T),
quit: make(chan struct{}),
}
q.inputListCond = sync.NewCond(&q.inputListMu)
return q, nil
}
// Start kicks off all the goroutines that are required to manage the queue.
func (q *DiskOverflowQueue[T]) Start() error {
var err error
q.startOnce.Do(func() {
err = q.start()
})
return err
}
// start kicks off all the goroutines that are required to manage the queue.
func (q *DiskOverflowQueue[T]) start() error {
numDisk, err := q.db.Len()
if err != nil {
return err
}
if numDisk != 0 {
q.toDisk.Store(true)
}
// Kick off the three goroutines which will handle the input list, the
// in-memory queue and the output channel.
// The three goroutines are moving items according to the following
// diagram:
//
// ┌─────────┐ drainInputList ┌──────────┐
// │inputList├─────┬──────────►│disk/db │
// └─────────┘ │ └──────────┘
// │ (depending on mode)
// │ ┌──────────┐
// └──────────►│inputChan │
// └──────────┘
//
// ┌─────────┐ feedMemQueue ┌──────────┐
// │disk/db ├───────┬────────►│memQueue │
// └─────────┘ │ └──────────┘
// │ (depending on mode)
// ┌─────────┐ │
// │inputChan├───────┘
// └─────────┘
//
// ┌─────────┐ feedOutputChan ┌──────────┐
// │memQueue ├────────────────►│outputChan│
// └─────────┘ └──────────┘
//
q.wg.Add(3)
go q.drainInputList()
go q.feedMemQueue()
go q.feedOutputChan()
return nil
}
// Stop stops the queue and persists any items in the memory queue to disk.
func (q *DiskOverflowQueue[T]) Stop() error {
var err error
q.stopOnce.Do(func() {
err = q.stop()
})
return err
}
// stop the queue and persists any items in the memory queue to disk.
func (q *DiskOverflowQueue[T]) stop() error {
close(q.quit)
// Signal on the inputListCond until all the goroutines have returned.
shutdown := make(chan struct{})
go func() {
for {
select {
case <-time.After(time.Millisecond):
q.inputListCond.Signal()
case <-shutdown:
return
}
}
}()
q.wg.Wait()
close(shutdown)
// queueHead will be the items that we will be pushed to the head of
// the queue.
var queueHead []T
// First, we append leftOverItem1 since this task is the current head
// of the queue.
if q.leftOverItem1 != nil {
queueHead = append(queueHead, *q.leftOverItem1)
}
// Next, drain the buffered queue.
for {
task, ok := <-q.memQueue
if !ok {
break
}
queueHead = append(queueHead, task)
}
// Then, any items held in leftOverItems2 would have been next to join
// the memQueue. So those gets added next.
if len(q.leftOverItems2) != 0 {
queueHead = append(queueHead, q.leftOverItems2...)
}
// Now, push these items to the head of the queue.
err := q.db.PushHead(queueHead...)
if err != nil {
q.log.Errorf("Could not add tasks to queue head: %v", err)
}
// Next we handle any items that need to be added to the main disk
// queue.
var diskQueue []T
// Any item in leftOverItem3 is the first item that should join the
// disk queue.
if q.leftOverItem3 != nil {
diskQueue = append(diskQueue, *q.leftOverItem3)
}
// Lastly, drain any items in the unbuffered input list.
q.inputListCond.L.Lock()
for q.inputList.Front() != nil {
e := q.inputList.Front()
//nolint:forcetypeassert
task := q.inputList.Remove(e).(T)
diskQueue = append(diskQueue, task)
}
q.inputListCond.L.Unlock()
// Now persist these items to the main disk queue.
err = q.db.Push(diskQueue...)
if err != nil {
q.log.Errorf("Could not add tasks to queue tail: %v", err)
}
return nil
}
// QueueBackupID adds a wtdb.BackupID to the queue. It will only return an error
// if the queue has been stopped. It is non-blocking.
func (q *DiskOverflowQueue[T]) QueueBackupID(item *wtdb.BackupID) error {
// Return an error if the queue has been stopped
select {
case <-q.quit:
return ErrClientExiting
default:
}
// Add the new item to the unbound input list.
q.inputListCond.L.Lock()
q.inputList.PushBack(item)
q.inputListCond.L.Unlock()
// Signal that there is a new item in the input list.
q.inputListCond.Signal()
return nil
}
// NextBackupID can be used to read from the head of the DiskOverflowQueue.
func (q *DiskOverflowQueue[T]) NextBackupID() <-chan T {
return q.outputChan
}
// drainInputList handles the input to the DiskOverflowQueue. It takes from the
// un-bounded input list and then, depending on what mode the queue is in,
// either puts the new item straight onto the persisted disk queue or attempts
// to feed it into the memQueue. On exit, any unhandled task will be assigned to
// leftOverItem3.
func (q *DiskOverflowQueue[T]) drainInputList() {
defer q.wg.Done()
for {
// Wait for the input list to not be empty.
q.inputListCond.L.Lock()
for q.inputList.Front() == nil {
q.inputListCond.Wait()
select {
case <-q.quit:
q.inputListCond.L.Unlock()
return
default:
}
}
// Pop the first element from the queue.
e := q.inputList.Front()
//nolint:forcetypeassert
task := q.inputList.Remove(e).(T)
q.inputListCond.L.Unlock()
// What we do with this new item depends on what the mode of the
// queue currently is.
for q.pushToActiveQueue(task) {
// We retry until the task is handled or the quit
// channel is closed.
}
// If the above returned false because the quit channel was
// closed, then we exit.
select {
case <-q.quit:
return
default:
}
}
}
// pushToActiveQueue handles the input of a new task to the queue. It returns
// true if the task should be retried and false if the task was handled or the
// quit channel fired.
func (q *DiskOverflowQueue[T]) pushToActiveQueue(task T) bool {
// If the queue is in disk mode then any new items should be put
// straight into the disk queue.
if q.toDisk.Load() {
err := q.db.Push(task)
if err != nil {
// Log and back off for a few seconds and then
// try again with the same task.
q.log.Errorf("could not persist %s to disk. "+
"Retrying after backoff", task)
select {
// Backoff for a bit and then re-check the mode
// and try again to handle the task.
case <-time.After(dbErrorBackoff):
return true
// If the queue is quit at this moment, then the
// unhandled task is assigned to leftOverItem3
// so that it can be handled by the stop method.
case <-q.quit:
q.leftOverItem3 = &task
return false
}
}
// Send a signal that there is a new item in the main
// disk queue.
select {
case q.newDiskItemSignal <- struct{}{}:
case <-q.quit:
// Because there might already be a signal in the
// newDiskItemSignal channel, we can skip sending another
// signal. The channel only has a buffer of one, so we would
// block here if we didn't have a default case.
default:
}
// If we got here, we were able to store the task in the disk
// queue, so we can return false as no retry is necessary.
return false
}
// If the mode is memory mode, then try feed it to the feedMemQueue
// handler via the un-buffered inputChan channel. We wrap it in an
// internal task so that we can find out if feedMemQueue successfully
// handled the item. If it did, we continue in memory mode and if not,
// then we switch to disk mode so that we can persist the item to the
// disk queue instead.
it := newInternalTask(task)
select {
// Try feed the task to the feedMemQueue handler. The handler, if it
// does take the task, is guaranteed to respond via the success channel
// of the task to indicate if the task was successfully added to the
// in-mem queue. This is guaranteed even if the queue is being stopped.
case q.inputChan <- it:
// If the queue is quit at this moment, then the unhandled task is
// assigned to leftOverItem3 so that it can be handled by the stop
// method.
case <-q.quit:
q.leftOverItem3 = &task
return false
default:
// The task was not accepted. So maybe the mode changed.
return true
}
// If we get here, it means that the feedMemQueue handler took the task.
// It is guaranteed to respond via the success channel, so we wait for
// that response here.
s := <-it.success
if s {
return false
}
// If the task was not successfully handled by feedMemQueue, then we
// switch to disk mode so that the task can be persisted in the disk
// queue instead.
q.toDisk.Store(true)
return true
}
// feedMemQueue manages which items should be fed onto the buffered
// memQueue. If the queue is then in disk mode, then the handler will read new
// tasks from the disk queue until it is empty. After that, it will switch
// between reading from the input channel or the disk queue depending on the
// queue mode.
func (q *DiskOverflowQueue[T]) feedMemQueue() {
defer func() {
close(q.memQueue)
q.wg.Done()
}()
feedFromDisk := func() {
select {
case <-q.quit:
return
default:
}
for {
// Ideally, we want to do batch reads from the DB. So
// we check how much capacity there is in the memQueue
// and fetch enough tasks to fill that capacity. If
// there is no capacity, however, then we at least want
// to fetch one task.
numToPop := cap(q.memQueue) - len(q.memQueue)
if numToPop == 0 {
numToPop = 1
}
tasks, err := q.db.PopUpTo(numToPop)
if errors.Is(err, wtdb.ErrEmptyQueue) {
q.toDisk.Store(false)
return
} else if err != nil {
q.log.Errorf("Could not load next task from " +
"disk. Retrying.")
select {
case <-time.After(dbErrorBackoff):
continue
case <-q.quit:
return
}
}
// If we did manage to fetch a task from disk, we make
// sure to set the toDisk mode to true since we may
// block indefinitely while trying to push the tasks to
// the memQueue in which case we want the drainInputList
// goroutine to write any new tasks to disk.
q.toDisk.Store(true)
for i, task := range tasks {
select {
case q.memQueue <- task:
// If the queue is quit at this moment, then the
// unhandled tasks are assigned to
// leftOverItems2 so that they can be handled
// by the stop method.
case <-q.quit:
q.leftOverItems2 = tasks[i:]
return
}
}
}
}
// If the queue is in disk mode, then the memQueue is fed with tasks
// from the disk queue until it is empty.
if q.toDisk.Load() {
feedFromDisk()
}
// Now the queue enters its normal operation.
for {
select {
case <-q.quit:
return
// If there is a signal that a new item has been added to disk
// then we use the disk queue as the source of the next task
// to feed into memQueue.
case <-q.newDiskItemSignal:
feedFromDisk()
// If any items come through on the inputChan, then we try feed
// these directly into the memQueue. If there is space in the
// memeQueue then we respond with success to the producer,
// otherwise we respond with failure so that the producer can
// instead persist the task to disk. After the producer,
// drainInputList, has pushed an item to inputChan, it is
// guaranteed to await a response on the task's success channel
// before quitting. Therefore, it is not required to listen on
// the quit channel here.
case task := <-q.inputChan:
select {
case q.memQueue <- task.task:
task.success <- true
continue
default:
task.success <- false
}
}
}
}
// feedOutputChan will pop an item from the buffered memQueue and block until
// the item is taken from the un-buffered outputChan. This is done repeatedly
// for the lifetime of the DiskOverflowQueue. On shutdown of the queue, any
// item not consumed by the outputChan but held by this method is assigned to
// the leftOverItem1 member so that the Stop method can persist the item to
// disk so that it is reloaded on restart.
//
// NOTE: This must be run as a goroutine.
func (q *DiskOverflowQueue[T]) feedOutputChan() {
defer func() {
close(q.outputChan)
q.wg.Done()
}()
for {
select {
case nextTask, ok := <-q.memQueue:
// If the memQueue is closed, then the queue is
// stopping.
if !ok {
return
}
select {
case q.outputChan <- nextTask:
case <-q.quit:
q.leftOverItem1 = &nextTask
return
}
case <-q.quit:
return
}
}
}
package wtclient
import (
"sync"
"github.com/lightningnetwork/lnd/queue"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
)
// sessionCloseMinHeap is a thread-safe min-heap implementation that stores
// sessionCloseItem items and prioritises the item with the lowest block height.
type sessionCloseMinHeap struct {
queue queue.PriorityQueue
mu sync.Mutex
}
// newSessionCloseMinHeap constructs a new sessionCloseMineHeap.
func newSessionCloseMinHeap() *sessionCloseMinHeap {
return &sessionCloseMinHeap{}
}
// Len returns the length of the queue.
func (h *sessionCloseMinHeap) Len() int {
h.mu.Lock()
defer h.mu.Unlock()
return h.queue.Len()
}
// Empty returns true if the queue is empty.
func (h *sessionCloseMinHeap) Empty() bool {
h.mu.Lock()
defer h.mu.Unlock()
return h.queue.Empty()
}
// Push adds an item to the priority queue.
func (h *sessionCloseMinHeap) Push(item *sessionCloseItem) {
h.mu.Lock()
defer h.mu.Unlock()
h.queue.Push(item)
}
// Pop removes the top most item from the queue.
func (h *sessionCloseMinHeap) Pop() *sessionCloseItem {
h.mu.Lock()
defer h.mu.Unlock()
if h.queue.Empty() {
return nil
}
item := h.queue.Pop()
return item.(*sessionCloseItem) //nolint:forcetypeassert
}
// Top returns the top most item from the queue without removing it.
func (h *sessionCloseMinHeap) Top() *sessionCloseItem {
h.mu.Lock()
defer h.mu.Unlock()
if h.queue.Empty() {
return nil
}
item := h.queue.Top()
return item.(*sessionCloseItem) //nolint:forcetypeassert
}
// sessionCloseItem represents a session that is ready to be deleted.
type sessionCloseItem struct {
// sessionID is the ID of the session in question.
sessionID wtdb.SessionID
// deleteHeight is the block height after which we can delete the
// session.
deleteHeight uint32
}
// Less returns true if the current item's delete height is less than the
// other sessionCloseItem's delete height. This results in lower block heights
// being popped first from the heap.
//
// NOTE: this is part of the queue.PriorityQueueItem interface.
func (s *sessionCloseItem) Less(other queue.PriorityQueueItem) bool {
o := other.(*sessionCloseItem).deleteHeight //nolint:forcetypeassert
return s.deleteHeight < o
}
var _ queue.PriorityQueueItem = (*sessionCloseItem)(nil)
package wtclient
import (
"errors"
"fmt"
"sync"
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
"github.com/lightningnetwork/lnd/watchtower/wtserver"
"github.com/lightningnetwork/lnd/watchtower/wtwire"
)
// SessionNegotiator is an interface for asynchronously requesting new sessions.
type SessionNegotiator interface {
// RequestSession signals to the session negotiator that the client
// needs another session. Once the session is negotiated, it should be
// returned via NewSessions.
RequestSession()
// NewSessions is a read-only channel where newly negotiated sessions
// will be delivered.
NewSessions() <-chan *ClientSession
// Start safely initializes the session negotiator.
Start() error
// Stop safely shuts down the session negotiator.
Stop() error
}
// NegotiatorConfig provides access to the resources required by a
// SessionNegotiator to faithfully carry out its duties. All nil-able field must
// be initialized.
type NegotiatorConfig struct {
// DB provides access to a persistent storage medium used by the tower
// to properly allocate session ephemeral keys and record successfully
// negotiated sessions.
DB DB
// SecretKeyRing allows the client to derive new session private keys
// when attempting to negotiate session with a tower.
SecretKeyRing ECDHKeyRing
// Candidates is an abstract set of tower candidates that the negotiator
// will traverse serially when attempting to negotiate a new session.
Candidates TowerCandidateIterator
// Policy defines the session policy that will be proposed to towers
// when attempting to negotiate a new session. This policy will be used
// across all negotiation proposals for the lifetime of the negotiator.
Policy wtpolicy.Policy
// Dial initiates an outbound brontide connection to the given address
// using a specified private key. The peer is returned in the event of a
// successful connection.
Dial func(keychain.SingleKeyECDH, *lnwire.NetAddress) (wtserver.Peer,
error)
// SendMessage writes a wtwire message to remote peer.
SendMessage func(wtserver.Peer, wtwire.Message) error
// ReadMessage reads a message from a remote peer and returns the
// decoded wtwire message.
ReadMessage func(wtserver.Peer) (wtwire.Message, error)
// ChainHash the genesis hash identifying the chain for any negotiated
// sessions. Any state updates sent to that session should also
// originate from this chain.
ChainHash chainhash.Hash
// MinBackoff defines the initial backoff applied by the session
// negotiator after all tower candidates have been exhausted and
// reattempting negotiation with the same set of candidates. Subsequent
// backoff durations will grow exponentially.
MinBackoff time.Duration
// MaxBackoff defines the maximum backoff applied by the session
// negotiator after all tower candidates have been exhausted and
// reattempting negotiation with the same set of candidates. If the
// exponential backoff produces a timeout greater than this value, the
// backoff duration will be clamped to MaxBackoff.
MaxBackoff time.Duration
// Log specifies the desired log output, which should be prefixed by the
// client type, e.g. anchor or legacy.
Log btclog.Logger
}
// sessionNegotiator is concrete SessionNegotiator that is able to request new
// sessions from a set of candidate towers asynchronously and return successful
// sessions to the primary client.
type sessionNegotiator struct {
started sync.Once
stopped sync.Once
localInit *wtwire.Init
cfg *NegotiatorConfig
log btclog.Logger
dispatcher chan struct{}
newSessions chan *ClientSession
successfulNegotiations chan *ClientSession
wg sync.WaitGroup
quit chan struct{}
}
// Compile-time constraint to ensure a *sessionNegotiator implements the
// SessionNegotiator interface.
var _ SessionNegotiator = (*sessionNegotiator)(nil)
// newSessionNegotiator initializes a fresh sessionNegotiator instance.
func newSessionNegotiator(cfg *NegotiatorConfig) *sessionNegotiator {
// Generate the set of features the negotiator will present to the tower
// upon connection.
features := cfg.Policy.FeatureBits()
localInit := wtwire.NewInitMessage(
lnwire.NewRawFeatureVector(features...),
cfg.ChainHash,
)
return &sessionNegotiator{
cfg: cfg,
log: cfg.Log,
localInit: localInit,
dispatcher: make(chan struct{}, 1),
newSessions: make(chan *ClientSession),
successfulNegotiations: make(chan *ClientSession),
quit: make(chan struct{}),
}
}
// Start safely starts up the sessionNegotiator.
func (n *sessionNegotiator) Start() error {
n.started.Do(func() {
n.log.Debugf("Starting session negotiator")
n.wg.Add(1)
go n.negotiationDispatcher()
})
return nil
}
// Stop safely shuts down the sessionNegotiator.
func (n *sessionNegotiator) Stop() error {
n.stopped.Do(func() {
n.log.Debugf("Stopping session negotiator")
close(n.quit)
n.wg.Wait()
})
return nil
}
// NewSessions returns a receive-only channel from which newly negotiated
// sessions will be returned.
func (n *sessionNegotiator) NewSessions() <-chan *ClientSession {
return n.newSessions
}
// RequestSession sends a request to the sessionNegotiator to begin requesting a
// new session. If one is already in the process of being negotiated, the
// request will be ignored.
func (n *sessionNegotiator) RequestSession() {
select {
case n.dispatcher <- struct{}{}:
default:
}
}
// negotiationDispatcher acts as the primary event loop for the
// sessionNegotiator, coordinating requests for more sessions and dispatching
// attempts to negotiate them from a list of candidates.
func (n *sessionNegotiator) negotiationDispatcher() {
defer n.wg.Done()
var pendingNegotiations int
for {
select {
case <-n.dispatcher:
pendingNegotiations++
if pendingNegotiations > 1 {
n.log.Debugf("Already negotiating session, " +
"waiting for existing negotiation to " +
"complete")
continue
}
// TODO(conner): consider reusing good towers
n.log.Debugf("Dispatching session negotiation")
n.wg.Add(1)
go n.negotiate()
case session := <-n.successfulNegotiations:
select {
case n.newSessions <- session:
pendingNegotiations--
case <-n.quit:
return
}
if pendingNegotiations > 0 {
n.log.Debugf("Dispatching pending session " +
"negotiation")
n.wg.Add(1)
go n.negotiate()
}
case <-n.quit:
return
}
}
}
// negotiate handles the process of iterating through potential tower candidates
// and attempting to negotiate a new session until a successful negotiation
// occurs. If the candidate iterator becomes exhausted because none were
// successful, this method will back off exponentially up to the configured max
// backoff. This method will continue trying until a negotiation is successful
// before returning the negotiated session to the dispatcher via the succeed
// channel.
//
// NOTE: This method MUST be run as a goroutine.
func (n *sessionNegotiator) negotiate() {
defer n.wg.Done()
// On the first pass, initialize the backoff to our configured min
// backoff.
var backoff time.Duration
// Create a closure to update the backoff upon failure such that it
// stays within our min and max backoff parameters.
updateBackoff := func() {
if backoff == 0 {
backoff = n.cfg.MinBackoff
} else {
backoff *= 2
if backoff > n.cfg.MaxBackoff {
backoff = n.cfg.MaxBackoff
}
}
}
retryWithBackoff:
// If we are retrying, wait out the delay before continuing.
if backoff > 0 {
select {
case <-time.After(backoff):
case <-n.quit:
return
}
}
tryNextCandidate:
for {
select {
case <-n.quit:
return
default:
}
// Pull the next candidate from our list of addresses.
tower, err := n.cfg.Candidates.Next()
if err != nil {
// We've run out of addresses, update our backoff.
updateBackoff()
n.log.Debugf("Unable to get new tower candidate, "+
"retrying after %v -- reason: %v", backoff, err)
// Only reset the iterator once we've exhausted all
// candidates. Doing so allows us to load balance
// sessions better amongst all of the tower candidates.
if err == ErrTowerCandidatesExhausted {
n.cfg.Candidates.Reset()
}
goto retryWithBackoff
}
towerPub := tower.IdentityKey.SerializeCompressed()
n.log.Debugf("Attempting session negotiation with tower=%x",
towerPub)
var forceNextKey bool
for {
// Before proceeding, we will reserve a session key
// index to use with this specific tower. If one is
// already reserved, the existing index will be
// returned.
keyIndex, err := n.cfg.DB.NextSessionKeyIndex(
tower.ID, n.cfg.Policy.BlobType, forceNextKey,
)
if err != nil {
n.log.Debugf("Unable to reserve session key "+
"index for tower=%x: %v", towerPub, err)
goto tryNextCandidate
}
// We'll now attempt the CreateSession dance with the
// tower to get a new session, trying all addresses if
// necessary.
err = n.createSession(tower, keyIndex)
if err == nil {
return
} else if errors.Is(err, ErrSessionKeyAlreadyUsed) {
forceNextKey = true
continue
}
// An unexpected error occurred, update our backoff.
updateBackoff()
n.log.Debugf("Session negotiation with tower=%x "+
"failed, trying again -- reason: %v", towerPub,
err)
goto retryWithBackoff
}
}
}
// createSession takes a tower and attempts to negotiate a session using any of
// its stored addresses. This method returns after the first successful
// negotiation, or after all addresses have failed with ErrFailedNegotiation.
func (n *sessionNegotiator) createSession(tower *Tower, keyIndex uint32) error {
sessionKeyDesc, err := n.cfg.SecretKeyRing.DeriveKey(
keychain.KeyLocator{
Family: keychain.KeyFamilyTowerSession,
Index: keyIndex,
},
)
if err != nil {
return err
}
sessionKey := keychain.NewPubKeyECDH(
sessionKeyDesc, n.cfg.SecretKeyRing,
)
addr := tower.Addresses.PeekAndLock()
for {
lnAddr := &lnwire.NetAddress{
IdentityKey: tower.IdentityKey,
Address: addr,
}
err = n.tryAddress(sessionKey, keyIndex, tower, lnAddr)
tower.Addresses.ReleaseLock(addr)
switch {
case errors.Is(err, ErrSessionKeyAlreadyUsed):
return err
case errors.Is(err, ErrPermanentTowerFailure):
// TODO(conner): report to iterator? can then be reset
// with restart
fallthrough
case err != nil:
n.log.Debugf("Request for session negotiation with "+
"tower=%s failed, trying again -- reason: "+
"%v", lnAddr, err)
// Get the next tower address if there is one.
addr, err = tower.Addresses.NextAndLock()
if err == ErrAddressesExhausted {
tower.Addresses.Reset()
return ErrFailedNegotiation
}
continue
default:
return nil
}
}
}
// tryAddress executes a single create session dance using the given address.
// The address should belong to the tower's set of addresses. This method only
// returns true if all steps succeed and the new session has been persisted, and
// fails otherwise.
func (n *sessionNegotiator) tryAddress(sessionKey keychain.SingleKeyECDH,
keyIndex uint32, tower *Tower, lnAddr *lnwire.NetAddress) error {
// Connect to the tower address using our generated session key.
conn, err := n.cfg.Dial(sessionKey, lnAddr)
if err != nil {
return err
}
// Send local Init message.
err = n.cfg.SendMessage(conn, n.localInit)
if err != nil {
return fmt.Errorf("unable to send Init: %w", err)
}
// Receive remote Init message.
remoteMsg, err := n.cfg.ReadMessage(conn)
if err != nil {
return fmt.Errorf("unable to read Init: %w", err)
}
// Check that returned message is wtwire.Init.
remoteInit, ok := remoteMsg.(*wtwire.Init)
if !ok {
return fmt.Errorf("expected Init, got %T in reply", remoteMsg)
}
// Verify the watchtower's remote Init message against our own.
err = n.localInit.CheckRemoteInit(remoteInit, wtwire.FeatureNames)
if err != nil {
return err
}
policy := n.cfg.Policy
createSession := &wtwire.CreateSession{
BlobType: policy.BlobType,
MaxUpdates: policy.MaxUpdates,
RewardBase: policy.RewardBase,
RewardRate: policy.RewardRate,
SweepFeeRate: policy.SweepFeeRate,
}
// Send CreateSession message.
err = n.cfg.SendMessage(conn, createSession)
if err != nil {
return fmt.Errorf("unable to send CreateSession: %w", err)
}
// Receive CreateSessionReply message.
remoteMsg, err = n.cfg.ReadMessage(conn)
if err != nil {
return fmt.Errorf("unable to read CreateSessionReply: %w", err)
}
// Check that returned message is wtwire.CreateSessionReply.
createSessionReply, ok := remoteMsg.(*wtwire.CreateSessionReply)
if !ok {
return fmt.Errorf("expected CreateSessionReply, got %T in "+
"reply", remoteMsg)
}
switch createSessionReply.Code {
case wtwire.CodeOK:
// TODO(conner): validate reward address
rewardPkScript := createSessionReply.Data
sessionID := wtdb.NewSessionIDFromPubKey(sessionKey.PubKey())
dbClientSession := &wtdb.ClientSession{
ClientSessionBody: wtdb.ClientSessionBody{
TowerID: tower.ID,
KeyIndex: keyIndex,
Policy: n.cfg.Policy,
RewardPkScript: rewardPkScript,
},
ID: sessionID,
}
err = n.cfg.DB.CreateClientSession(dbClientSession)
if err != nil {
return fmt.Errorf("unable to persist ClientSession: %w",
err)
}
n.log.Debugf("New session negotiated with %s, policy: %s",
lnAddr, dbClientSession.Policy)
clientSession := &ClientSession{
ID: sessionID,
ClientSessionBody: dbClientSession.ClientSessionBody,
Tower: tower,
SessionKeyECDH: sessionKey,
}
// We have a newly negotiated session, return it to the
// dispatcher so that it can update how many outstanding
// negotiation requests we have.
select {
case n.successfulNegotiations <- clientSession:
return nil
case <-n.quit:
return ErrNegotiatorExiting
}
case wtwire.CreateSessionCodeAlreadyExists:
// TODO(conner): use the last-applied in the create session
// reply to handle case where we lose state, session already
// exists, and we want to possibly resume using the session.
// NOTE that this should not be done until the server code
// has been adapted to first check that the CreateSession
// request is for the same blob-type as the initial session.
return ErrSessionKeyAlreadyUsed
// TODO(conner): handle error codes properly
case wtwire.CreateSessionCodeRejectBlobType:
return fmt.Errorf("tower rejected blob type: %v",
policy.BlobType)
case wtwire.CreateSessionCodeRejectMaxUpdates:
return fmt.Errorf("tower rejected max updates: %v",
policy.MaxUpdates)
case wtwire.CreateSessionCodeRejectRewardRate:
// The tower rejected the session because of the reward rate. If
// we didn't request a reward session, we'll treat this as a
// permanent tower failure.
if !policy.BlobType.Has(blob.FlagReward) {
return ErrPermanentTowerFailure
}
return fmt.Errorf("tower rejected reward rate: %v",
policy.RewardRate)
case wtwire.CreateSessionCodeRejectSweepFeeRate:
return fmt.Errorf("tower rejected sweep fee rate: %v",
policy.SweepFeeRate)
default:
return fmt.Errorf("received unhandled error code: %v",
createSessionReply.Code)
}
}
package wtclient
import (
"container/list"
"fmt"
"sync"
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
"github.com/lightningnetwork/lnd/watchtower/wtserver"
"github.com/lightningnetwork/lnd/watchtower/wtwire"
)
// sessionQueueStatus is an enum that signals how full a particular session is.
type sessionQueueStatus uint8
const (
// sessionQueueAvailable indicates that the session has space for at
// least one more backup.
sessionQueueAvailable sessionQueueStatus = iota
// sessionQueueExhausted indicates that all slots in the session have
// been allocated.
sessionQueueExhausted
// sessionQueueShuttingDown indicates that the session queue is
// shutting down and so is no longer accepting any more backups.
sessionQueueShuttingDown
)
// sessionQueueConfig bundles the resources required by the sessionQueue to
// perform its duties. All entries MUST be non-nil.
type sessionQueueConfig struct {
// ClientSession provides access to the negotiated session parameters
// and updating its persistent storage.
ClientSession *ClientSession
// ChainHash identifies the chain for which the session's justice
// transactions are targeted.
ChainHash chainhash.Hash
// Dial allows the client to dial the tower using it's public key and
// net address.
Dial func(keychain.SingleKeyECDH, *lnwire.NetAddress) (wtserver.Peer,
error)
// SendMessage encodes, encrypts, and writes a message to the given
// peer.
SendMessage func(wtserver.Peer, wtwire.Message) error
// ReadMessage receives, decypts, and decodes a message from the given
// peer.
ReadMessage func(wtserver.Peer) (wtwire.Message, error)
// Signer facilitates signing of inputs, used to construct the witnesses
// for justice transaction inputs.
Signer input.Signer
// BuildBreachRetribution is a function closure that allows the client
// to fetch the breach retribution info for a certain channel at a
// certain revoked commitment height.
BuildBreachRetribution BreachRetributionBuilder
// TaskPipeline is a pipeline which the sessionQueue should use to send
// any unhandled tasks on shutdown of the queue.
TaskPipeline *DiskOverflowQueue[*wtdb.BackupID]
// DB provides access to the client's stable storage.
DB DB
// MinBackoff defines the initial backoff applied by the session
// queue before reconnecting to the tower after a failed or partially
// successful batch is sent. Subsequent backoff durations will grow
// exponentially up until MaxBackoff.
MinBackoff time.Duration
// MaxBackoff defines the maximum backoff applied by the session
// queue before reconnecting to the tower after a failed or partially
// successful batch is sent. If the exponential backoff produces a
// timeout greater than this value, the backoff duration will be clamped
// to MaxBackoff.
MaxBackoff time.Duration
// Log specifies the desired log output, which should be prefixed by the
// client type, e.g. anchor or legacy.
Log btclog.Logger
}
// sessionQueue implements a reliable queue that will encrypt and send accepted
// backups to the watchtower specified in the config's ClientSession. Calling
// Stop will attempt to perform a clean shutdown replaying any un-committed
// pending updates to the client's main task pipeline.
type sessionQueue struct {
started sync.Once
stopped sync.Once
forced sync.Once
cfg *sessionQueueConfig
log btclog.Logger
commitQueue *list.List
pendingQueue *list.List
queueMtx sync.Mutex
queueCond *sync.Cond
localInit *wtwire.Init
tower *Tower
seqNum uint16
retryBackoff time.Duration
quit chan struct{}
wg sync.WaitGroup
}
// newSessionQueue initializes a fresh sessionQueue.
func newSessionQueue(cfg *sessionQueueConfig,
updates []wtdb.CommittedUpdate) *sessionQueue {
localInit := wtwire.NewInitMessage(
lnwire.NewRawFeatureVector(wtwire.AltruistSessionsRequired),
cfg.ChainHash,
)
sq := &sessionQueue{
cfg: cfg,
log: cfg.Log,
commitQueue: list.New(),
pendingQueue: list.New(),
localInit: localInit,
tower: cfg.ClientSession.Tower,
seqNum: cfg.ClientSession.SeqNum,
retryBackoff: cfg.MinBackoff,
quit: make(chan struct{}),
}
sq.queueCond = sync.NewCond(&sq.queueMtx)
// The database should return them in sorted order, and session queue's
// sequence number will be equal to that of the last committed update.
for _, update := range updates {
sq.commitQueue.PushBack(update)
}
return sq
}
// Start idempotently starts the sessionQueue so that it can begin accepting
// backups.
func (q *sessionQueue) Start() {
q.started.Do(func() {
q.wg.Add(1)
go q.sessionManager()
})
}
// Stop idempotently stops the sessionQueue by initiating a clean shutdown that
// will clear all pending tasks in the queue before returning to the caller.
// The final param should only be set to true if this is the last time that
// this session will be used. Otherwise, during normal shutdown, the final param
// should be false.
func (q *sessionQueue) Stop(final bool) error {
var returnErr error
q.stopped.Do(func() {
q.log.Debugf("SessionQueue(%s) stopping ...", q.ID())
close(q.quit)
shutdown := make(chan struct{})
go func() {
for {
select {
case <-time.After(time.Millisecond):
q.queueCond.Signal()
case <-shutdown:
return
}
}
}()
q.wg.Wait()
close(shutdown)
// Now, for any task in the pending queue that we have not yet
// created a CommittedUpdate for, re-add the task to the main
// task pipeline.
updates, err := q.cfg.DB.FetchSessionCommittedUpdates(q.ID())
if err != nil {
returnErr = err
return
}
unAckedUpdates := make(map[wtdb.BackupID]bool)
for _, update := range updates {
unAckedUpdates[update.BackupID] = true
if !final {
continue
}
err := q.cfg.TaskPipeline.QueueBackupID(
&update.BackupID,
)
if err != nil {
log.Errorf("could not re-queue %s: %v",
update.BackupID, err)
continue
}
}
if final {
err = q.cfg.DB.DeleteCommittedUpdates(q.ID())
if err != nil {
log.Errorf("could not delete committed "+
"updates for session %s", q.ID())
}
}
// Push any task that was on the pending queue that there is
// not yet a committed update for back to the main task
// pipeline.
q.queueCond.L.Lock()
for q.pendingQueue.Len() > 0 {
next := q.pendingQueue.Front()
q.pendingQueue.Remove(next)
//nolint:forcetypeassert
task := next.Value.(*backupTask)
if unAckedUpdates[task.id] {
continue
}
err := q.cfg.TaskPipeline.QueueBackupID(&task.id)
if err != nil {
log.Errorf("could not re-queue backup task: "+
"%v", err)
continue
}
}
q.queueCond.L.Unlock()
q.log.Debugf("SessionQueue(%s) stopped", q.ID())
})
return returnErr
}
// ID returns the wtdb.SessionID for the queue, which can be used to uniquely
// identify this a particular queue.
func (q *sessionQueue) ID() *wtdb.SessionID {
return &q.cfg.ClientSession.ID
}
// AcceptTask attempts to queue a backupTask for delivery to the sessionQueue's
// tower. The session will only be accepted if the queue is not already
// exhausted or shutting down and the task is successfully bound to the
// ClientSession.
func (q *sessionQueue) AcceptTask(task *backupTask) (sessionQueueStatus, bool) {
// Exit early if the queue has started shutting down.
select {
case <-q.quit:
return sessionQueueShuttingDown, false
default:
}
q.queueCond.L.Lock()
// There is a chance that sessionQueue started shutting down between
// the last quit channel check and waiting for the lock. So check one
// more time here.
select {
case <-q.quit:
q.queueCond.L.Unlock()
return sessionQueueShuttingDown, false
default:
}
numPending := uint32(q.pendingQueue.Len())
maxUpdates := q.cfg.ClientSession.Policy.MaxUpdates
q.log.Debugf("SessionQueue(%s) deciding to accept %v seqnum=%d "+
"pending=%d max-updates=%d",
q.ID(), task.id, q.seqNum, numPending, maxUpdates)
// Examine the current reserve status of the session queue.
curStatus := q.status()
switch curStatus {
// The session queue is exhausted, and cannot accept the task because it
// is full. Reject the task such that it can be tried against a
// different session.
case sessionQueueExhausted:
q.queueCond.L.Unlock()
return curStatus, false
// The session queue is not exhausted. Compute the sweep and reward
// outputs as a function of the session parameters. If the outputs are
// dusty or uneconomical to backup, the task is rejected and will not be
// tried again.
//
// TODO(conner): queue backups and retry with different session params.
case sessionQueueAvailable:
err := task.bindSession(
&q.cfg.ClientSession.ClientSessionBody,
q.cfg.BuildBreachRetribution,
)
if err != nil {
q.queueCond.L.Unlock()
q.log.Debugf("SessionQueue(%s) rejected %v: %v ",
q.ID(), task.id, err)
return curStatus, false
}
}
// The sweep and reward outputs satisfy the session's policy, queue the
// task for final signing and delivery.
q.pendingQueue.PushBack(task)
// Finally, compute the session's *new* reserve status. This will be
// used by the client to determine if it can continue using this session
// queue, or if it should negotiate a new one.
newStatus := q.status()
q.queueCond.L.Unlock()
q.queueCond.Signal()
return newStatus, true
}
// sessionManager is the primary event loop for the sessionQueue, and is
// responsible for encrypting and sending accepted tasks to the tower.
func (q *sessionQueue) sessionManager() {
defer q.wg.Done()
for {
q.queueCond.L.Lock()
for q.commitQueue.Len() == 0 &&
q.pendingQueue.Len() == 0 {
q.queueCond.Wait()
select {
case <-q.quit:
q.queueCond.L.Unlock()
return
default:
}
}
q.queueCond.L.Unlock()
// Exit immediately if the sessionQueue has been stopped.
select {
case <-q.quit:
return
default:
}
// Initiate a new connection to the watchtower and attempt to
// drain all pending tasks.
q.drainBackups()
}
}
// drainBackups attempts to send all pending updates in the queue to the tower.
func (q *sessionQueue) drainBackups() {
var (
conn wtserver.Peer
err error
towerAddr = q.tower.Addresses.Peek()
)
for {
q.log.Infof("SessionQueue(%s) attempting to dial tower at %v",
q.ID(), towerAddr)
// First, check that we are able to dial this session's tower.
conn, err = q.cfg.Dial(
q.cfg.ClientSession.SessionKeyECDH, &lnwire.NetAddress{
IdentityKey: q.tower.IdentityKey,
Address: towerAddr,
},
)
if err != nil {
// If there are more addrs available, immediately try
// those.
nextAddr, iteratorErr := q.tower.Addresses.Next()
if iteratorErr == nil {
towerAddr = nextAddr
continue
}
// Otherwise, if we have exhausted the address list,
// back off and try again later.
q.tower.Addresses.Reset()
q.log.Errorf("SessionQueue(%s) unable to dial tower "+
"at any available Addresses: %v", q.ID(), err)
q.increaseBackoff()
select {
case <-time.After(q.retryBackoff):
case <-q.quit:
}
return
}
break
}
defer conn.Close()
// Begin draining the queue of pending state updates. Before the first
// update is sent, we will precede it with an Init message. If the first
// is successful, subsequent updates can be streamed without sending an
// Init.
for sendInit := true; ; sendInit = false {
// Generate the next state update to upload to the tower. This
// method will first proceed in dequeuing committed updates
// before attempting to dequeue any pending updates.
stateUpdate, isPending, backupID, err := q.nextStateUpdate()
if err != nil {
q.log.Errorf("SessionQueue(%v) unable to get next "+
"state update: %v", q.ID(), err)
return
}
// Now, send the state update to the tower and wait for a reply.
err = q.sendStateUpdate(conn, stateUpdate, sendInit, isPending)
if err != nil {
q.log.Errorf("SessionQueue(%s) unable to send state "+
"update: %v", q.ID(), err)
q.increaseBackoff()
select {
case <-time.After(q.retryBackoff):
case <-q.quit:
}
return
}
q.log.Infof("SessionQueue(%s) uploaded %v seqnum=%d",
q.ID(), backupID, stateUpdate.SeqNum)
// If the last task was backed up successfully, we'll exit and
// continue once more tasks are added to the queue. We'll also
// clear any accumulated backoff as this batch was able to be
// sent reliably.
if stateUpdate.IsComplete == 1 {
q.resetBackoff()
return
}
// Always apply a small delay between sends, which makes the
// unit tests more reliable. If we were requested to back off,
// when we will do so.
select {
case <-time.After(time.Millisecond):
case <-q.quit:
return
}
}
}
// nextStateUpdate returns the next wtwire.StateUpdate to upload to the tower.
// If any committed updates are present, this method will reconstruct the state
// update from the committed update using the current last applied value found
// in the database. Otherwise, it will select the next pending update, craft the
// payload, and commit an update before returning the state update to send. The
// boolean value in the response is true if the state update is taken from the
// pending queue, allowing the caller to remove the update from either the
// commit or pending queue if the update is successfully acked.
func (q *sessionQueue) nextStateUpdate() (*wtwire.StateUpdate, bool,
wtdb.BackupID, error) {
var (
seqNum uint16
update wtdb.CommittedUpdate
isLast bool
isPending bool
)
q.queueCond.L.Lock()
switch {
// If the commit queue is non-empty, parse the next committed update.
case q.commitQueue.Len() > 0:
next := q.commitQueue.Front()
update = next.Value.(wtdb.CommittedUpdate)
seqNum = update.SeqNum
// If this is the last item in the commit queue and no items
// exist in the pending queue, we will use the IsComplete flag
// in the StateUpdate to signal that the tower can release the
// connection after replying to free up resources.
isLast = q.commitQueue.Len() == 1 && q.pendingQueue.Len() == 0
q.queueCond.L.Unlock()
q.log.Debugf("SessionQueue(%s) reprocessing committed state "+
"update for %v seqnum=%d",
q.ID(), update.BackupID, seqNum)
// Otherwise, craft and commit the next update from the pending queue.
default:
isPending = true
// Determine the current sequence number to apply for this
// pending update.
seqNum = q.seqNum + 1
// Obtain the next task from the queue.
next := q.pendingQueue.Front()
task := next.Value.(*backupTask)
// If this is the last item in the pending queue, we will use
// the IsComplete flag in the StateUpdate to signal that the
// tower can release the connection after replying to free up
// resources.
isLast = q.pendingQueue.Len() == 1
q.queueCond.L.Unlock()
hint, encBlob, err := task.craftSessionPayload(q.cfg.Signer)
if err != nil {
// TODO(conner): mark will not send
err := fmt.Errorf("unable to craft session payload: %w",
err)
return nil, false, wtdb.BackupID{}, err
}
// TODO(conner): special case other obscure errors
update = wtdb.CommittedUpdate{
SeqNum: seqNum,
CommittedUpdateBody: wtdb.CommittedUpdateBody{
BackupID: task.id,
Hint: hint,
EncryptedBlob: encBlob,
},
}
q.log.Debugf("SessionQueue(%s) committing state update "+
"%v seqnum=%d", q.ID(), update.BackupID, seqNum)
}
// Before sending the task to the tower, commit the state update
// to disk using the assigned sequence number. If this task has already
// been committed, the call will succeed and only be used for the
// purpose of obtaining the last applied value to send to the tower.
//
// This step ensures that if we crash before receiving an ack that we
// will retransmit the same update. If the tower successfully received
// the update from before, it will reply with an ACK regardless of what
// we send the next time. This step ensures that if we reliably send the
// same update for a given sequence number, to prevent us from thinking
// we backed up a state when we instead backed up another.
lastApplied, err := q.cfg.DB.CommitUpdate(q.ID(), &update)
if err != nil {
// TODO(conner): mark failed/reschedule
err := fmt.Errorf("unable to commit state update for "+
"%v seqnum=%d: %v", update.BackupID, seqNum, err)
return nil, false, wtdb.BackupID{}, err
}
stateUpdate := &wtwire.StateUpdate{
SeqNum: update.SeqNum,
LastApplied: lastApplied,
Hint: update.Hint,
EncryptedBlob: update.EncryptedBlob,
}
// Set the IsComplete flag if this is the last queued item.
if isLast {
stateUpdate.IsComplete = 1
}
return stateUpdate, isPending, update.BackupID, nil
}
// sendStateUpdate sends a wtwire.StateUpdate to the watchtower and processes
// the ACK before returning. If sendInit is true, this method will first send
// the localInit message and verify that the tower supports our required feature
// bits. And error is returned if any part of the send fails. The boolean return
// variable indicates whether we should back off before attempting to send the
// next state update.
func (q *sessionQueue) sendStateUpdate(conn wtserver.Peer,
stateUpdate *wtwire.StateUpdate, sendInit, isPending bool) error {
towerAddr := &lnwire.NetAddress{
IdentityKey: conn.RemotePub(),
Address: conn.RemoteAddr(),
}
// If this is the first message being sent to the tower, we must send an
// Init message to establish that server supports the features we
// require.
if sendInit {
// Send Init to tower.
err := q.cfg.SendMessage(conn, q.localInit)
if err != nil {
return err
}
// Receive Init from tower.
remoteMsg, err := q.cfg.ReadMessage(conn)
if err != nil {
return err
}
remoteInit, ok := remoteMsg.(*wtwire.Init)
if !ok {
return fmt.Errorf("watchtower %s responded with %T "+
"to Init", towerAddr, remoteMsg)
}
// Validate Init.
err = q.localInit.CheckRemoteInit(
remoteInit, wtwire.FeatureNames,
)
if err != nil {
return err
}
}
// Send StateUpdate to tower.
err := q.cfg.SendMessage(conn, stateUpdate)
if err != nil {
return err
}
// Receive StateUpdate from tower.
remoteMsg, err := q.cfg.ReadMessage(conn)
if err != nil {
return err
}
stateUpdateReply, ok := remoteMsg.(*wtwire.StateUpdateReply)
if !ok {
return fmt.Errorf("watchtower %s responded with %T to "+
"StateUpdate", towerAddr, remoteMsg)
}
// Process the reply from the tower.
switch stateUpdateReply.Code {
// The tower reported a successful update, validate the response and
// record the last applied returned.
case wtwire.CodeOK:
// TODO(conner): handle other error cases properly, ban towers, etc.
default:
err := fmt.Errorf("received error code %v in "+
"StateUpdateReply for seqnum=%d",
stateUpdateReply.Code, stateUpdate.SeqNum)
q.log.Warnf("SessionQueue(%s) unable to upload state update "+
"to tower=%s: %v", q.ID(), towerAddr, err)
return err
}
lastApplied := stateUpdateReply.LastApplied
err = q.cfg.DB.AckUpdate(q.ID(), stateUpdate.SeqNum, lastApplied)
switch {
case err == wtdb.ErrUnallocatedLastApplied:
// TODO(conner): borked watchtower
err = fmt.Errorf("unable to ack seqnum=%d: %w",
stateUpdate.SeqNum, err)
q.log.Errorf("SessionQueue(%v) failed to ack update: %v",
q.ID(), err)
return err
case err == wtdb.ErrLastAppliedReversion:
// TODO(conner): borked watchtower
err = fmt.Errorf("unable to ack seqnum=%d: %w",
stateUpdate.SeqNum, err)
q.log.Errorf("SessionQueue(%s) failed to ack update: %v",
q.ID(), err)
return err
case err != nil:
err = fmt.Errorf("unable to ack seqnum=%d: %w",
stateUpdate.SeqNum, err)
q.log.Errorf("SessionQueue(%s) failed to ack update: %v",
q.ID(), err)
return err
}
q.queueCond.L.Lock()
if isPending {
// If a pending update was successfully sent, increment the
// sequence number and remove the item from the queue. This
// ensures the total number of backups in the session remains
// unchanged, which maintains the external view of the session's
// reserve status.
q.seqNum++
q.pendingQueue.Remove(q.pendingQueue.Front())
} else {
// Otherwise, simply remove the update from the committed queue.
// This has no effect on the queues reserve status since the
// update had already been committed.
q.commitQueue.Remove(q.commitQueue.Front())
}
q.queueCond.L.Unlock()
return nil
}
// status returns a sessionQueueStatus indicating whether the sessionQueue can
// accept another task. sessionQueueAvailable is returned when a task can be
// accepted, and sessionQueueExhausted is returned if the all slots in the
// session have been allocated.
//
// NOTE: This method MUST be called with queueCond's exclusive lock held.
func (q *sessionQueue) status() sessionQueueStatus {
numPending := uint32(q.pendingQueue.Len())
maxUpdates := uint32(q.cfg.ClientSession.Policy.MaxUpdates)
if uint32(q.seqNum)+numPending < maxUpdates {
return sessionQueueAvailable
}
return sessionQueueExhausted
}
// resetBackoff returns the connection backoff the minimum configured backoff.
func (q *sessionQueue) resetBackoff() {
q.retryBackoff = q.cfg.MinBackoff
}
// increaseBackoff doubles the current connection backoff, clamping to the
// configured maximum backoff if it would exceed the limit.
func (q *sessionQueue) increaseBackoff() {
q.retryBackoff *= 2
if q.retryBackoff > q.cfg.MaxBackoff {
q.retryBackoff = q.cfg.MaxBackoff
}
}
// sessionQueueSet maintains a mapping of SessionIDs to their corresponding
// sessionQueue.
type sessionQueueSet struct {
queues map[wtdb.SessionID]*sessionQueue
mu sync.Mutex
}
// newSessionQueueSet constructs a new sessionQueueSet.
func newSessionQueueSet() *sessionQueueSet {
return &sessionQueueSet{
queues: make(map[wtdb.SessionID]*sessionQueue),
}
}
// AddAndStart inserts a sessionQueue into the sessionQueueSet and starts it.
func (s *sessionQueueSet) AddAndStart(sessionQueue *sessionQueue) {
s.mu.Lock()
defer s.mu.Unlock()
s.queues[*sessionQueue.ID()] = sessionQueue
sessionQueue.Start()
}
// StopAndRemove stops the given session queue and removes it from the
// sessionQueueSet.
func (s *sessionQueueSet) StopAndRemove(id wtdb.SessionID, final bool) error {
s.mu.Lock()
defer s.mu.Unlock()
queue, ok := s.queues[id]
if !ok {
return nil
}
delete(s.queues, id)
return queue.Stop(final)
}
// Get fetches and returns the sessionQueue with the given ID.
func (s *sessionQueueSet) Get(id wtdb.SessionID) (*sessionQueue, bool) {
s.mu.Lock()
defer s.mu.Unlock()
q, ok := s.queues[id]
return q, ok
}
// ApplyAndWait executes the nil-adic function returned from getApply for each
// sessionQueue in the set in parallel, then waits for all of them to finish
// before returning to the caller.
func (s *sessionQueueSet) ApplyAndWait(getApply func(*sessionQueue) func()) {
s.mu.Lock()
defer s.mu.Unlock()
var wg sync.WaitGroup
for _, sessionq := range s.queues {
wg.Add(1)
go func(sq *sessionQueue) {
defer wg.Done()
getApply(sq)()
}(sessionq)
}
wg.Wait()
}
package wtclient
import (
"fmt"
"sync"
)
// ClientStats is a collection of in-memory statistics of the actions the client
// has performed since its creation.
type ClientStats struct {
// NumTasksPending is the total number of backups that are pending to
// be acknowledged by all active and exhausted watchtower sessions.
NumTasksPending int
// NumTasksAccepted is the total number of backups made to all active
// and exhausted watchtower sessions.
NumTasksAccepted int
// NumTasksIneligible is the total number of backups that all active and
// exhausted watchtower sessions have failed to acknowledge.
NumTasksIneligible int
// NumSessionsAcquired is the total number of new sessions made to
// watchtowers.
NumSessionsAcquired int
// NumSessionsExhausted is the total number of watchtower sessions that
// have been exhausted.
NumSessionsExhausted int
}
// clientStats wraps ClientStats with a mutex so that it's members can be
// accessed in a thread safe manner.
type clientStats struct {
mu sync.Mutex
ClientStats
}
// taskReceived increments the number of backup requests the client has received
// from active channels.
func (s *clientStats) taskReceived() {
s.mu.Lock()
defer s.mu.Unlock()
s.NumTasksPending++
}
// taskAccepted increments the number of tasks that have been assigned to active
// session queues, and are awaiting upload to a tower.
func (s *clientStats) taskAccepted() {
s.mu.Lock()
defer s.mu.Unlock()
s.NumTasksAccepted++
s.NumTasksPending--
}
// getStatsCopy returns a copy of the ClientStats.
func (s *clientStats) getStatsCopy() ClientStats {
s.mu.Lock()
defer s.mu.Unlock()
return s.ClientStats
}
// taskIneligible increments the number of tasks that were unable to satisfy the
// active session queue's policy. These can potentially be retried later, but
// typically this means that the balance created dust outputs, so it may not be
// worth backing up at all.
func (s *clientStats) taskIneligible() {
s.mu.Lock()
defer s.mu.Unlock()
s.NumTasksIneligible++
}
// sessionAcquired increments the number of sessions that have been successfully
// negotiated by the client during this execution.
func (s *clientStats) sessionAcquired() {
s.mu.Lock()
defer s.mu.Unlock()
s.NumSessionsAcquired++
}
// sessionExhausted increments the number of session that have become full as a
// result of accepting backup tasks.
func (s *clientStats) sessionExhausted() {
s.mu.Lock()
defer s.mu.Unlock()
s.NumSessionsExhausted++
}
// String returns a human-readable summary of the client's metrics.
func (s *clientStats) String() string {
s.mu.Lock()
defer s.mu.Unlock()
return fmt.Sprintf("tasks(received=%d accepted=%d ineligible=%d) "+
"sessions(acquired=%d exhausted=%d)", s.NumTasksPending,
s.NumTasksAccepted, s.NumTasksIneligible, s.NumSessionsAcquired,
s.NumSessionsExhausted)
}
package wtdb
import (
"io"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnwire"
)
// ChannelInfos is a map for a given channel id to it's ChannelInfo.
type ChannelInfos map[lnwire.ChannelID]*ChannelInfo
// ChannelInfo contains various useful things about a registered channel.
//
// NOTE: the reason for adding this struct which wraps ClientChanSummary
// instead of extending ClientChanSummary is for faster look-up of added fields.
// If we were to extend ClientChanSummary instead then we would need to decode
// the entire struct each time we want to read the new fields and then re-encode
// the struct each time we want to write to a new field.
type ChannelInfo struct {
ClientChanSummary
// MaxHeight is the highest commitment height that the tower has been
// handed for this channel. An Option type is used to store this since
// a commitment height of zero is valid, and we need a way of knowing if
// we have seen a new height yet or not.
MaxHeight fn.Option[uint64]
}
// ClientChanSummary tracks channel-specific information. A new
// ClientChanSummary is inserted in the database the first time the client
// encounters a particular channel.
type ClientChanSummary struct {
// SweepPkScript is the pkscript to which all justice transactions will
// deposit recovered funds for this particular channel.
SweepPkScript []byte
// TODO(conner): later extend with info about initial commit height,
// ineligible states, etc.
}
// Encode writes the ClientChanSummary to the passed io.Writer.
func (s *ClientChanSummary) Encode(w io.Writer) error {
return WriteElement(w, s.SweepPkScript)
}
// Decode reads a ClientChanSummary form the passed io.Reader.
func (s *ClientChanSummary) Decode(r io.Reader) error {
return ReadElement(r, &s.SweepPkScript)
}
package wtdb
import (
"bytes"
"errors"
"fmt"
"math"
"net"
"sync"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tlv"
"github.com/lightningnetwork/lnd/watchtower/blob"
)
var (
// cSessionKeyIndexBkt is a top-level bucket storing:
// tower-id -> reserved-session-key-index (uint32).
cSessionKeyIndexBkt = []byte("client-session-key-index-bucket")
// cChanDetailsBkt is a top-level bucket storing:
// channel-id => cChannelSummary -> encoded ClientChanSummary.
// => cChanDBID -> db-assigned-id
// => cChanSessions => db-session-id -> 1
// => cChanClosedHeight -> block-height
// => cChanMaxCommitmentHeight -> commitment-height
cChanDetailsBkt = []byte("client-channel-detail-bucket")
// cChanSessions is a sub-bucket of cChanDetailsBkt which stores:
// db-session-id -> 1
cChanSessions = []byte("client-channel-sessions")
// cChanDBID is a key used in the cChanDetailsBkt to store the
// db-assigned-id of a channel.
cChanDBID = []byte("client-channel-db-id")
// cChanClosedHeight is a key used in the cChanDetailsBkt to store the
// block height at which the channel's closing transaction was mined in.
// If this there is no associated value for this key, then the channel
// has not yet been marked as closed.
cChanClosedHeight = []byte("client-channel-closed-height")
// cChannelSummary is a key used in cChanDetailsBkt to store the encoded
// body of ClientChanSummary.
cChannelSummary = []byte("client-channel-summary")
// cChanMaxCommitmentHeight is a key used in the cChanDetailsBkt used
// to store the highest commitment height for this channel that the
// tower has been handed.
cChanMaxCommitmentHeight = []byte(
"client-channel-max-commitment-height",
)
// cSessionBkt is a top-level bucket storing:
// session-id => cSessionBody -> encoded ClientSessionBody
// => cSessionDBID -> db-assigned-id
// => cSessionCommits => seqnum -> encoded CommittedUpdate
// => cSessionAckRangeIndex => db-chan-id => start -> end
// => cSessionRogueUpdateCount -> count
cSessionBkt = []byte("client-session-bucket")
// cSessionDBID is a key used in the cSessionBkt to store the
// db-assigned-id of a session.
cSessionDBID = []byte("client-session-db-id")
// cSessionBody is a sub-bucket of cSessionBkt storing only the body of
// the ClientSession.
cSessionBody = []byte("client-session-body")
// cSessionBody is a sub-bucket of cSessionBkt storing:
// seqnum -> encoded CommittedUpdate.
cSessionCommits = []byte("client-session-commits")
// cSessionAckRangeIndex is a sub-bucket of cSessionBkt storing
// chan-id => start -> end
cSessionAckRangeIndex = []byte("client-session-ack-range-index")
// cSessionRogueUpdateCount is a key in the cSessionBkt bucket storing
// the number of rogue updates that were backed up using the session.
// Rogue updates are updates for channels that have been closed already
// at the time of the back-up.
cSessionRogueUpdateCount = []byte("client-session-rogue-update-count")
// cChanIDIndexBkt is a top-level bucket storing:
// db-assigned-id -> channel-ID
cChanIDIndexBkt = []byte("client-channel-id-index")
// cSessionIDIndexBkt is a top-level bucket storing:
// db-assigned-id -> session-id
cSessionIDIndexBkt = []byte("client-session-id-index")
// cTowerBkt is a top-level bucket storing:
// tower-id -> encoded Tower.
cTowerBkt = []byte("client-tower-bucket")
// cTowerIndexBkt is a top-level bucket storing:
// tower-pubkey -> tower-id.
cTowerIndexBkt = []byte("client-tower-index-bucket")
// cTowerToSessionIndexBkt is a top-level bucket storing:
// tower-id -> session-id -> 1
cTowerToSessionIndexBkt = []byte(
"client-tower-to-session-index-bucket",
)
// cClosableSessionsBkt is a top-level bucket storing:
// db-session-id -> last-channel-close-height
cClosableSessionsBkt = []byte("client-closable-sessions-bucket")
// cTaskQueue is a top-level bucket where the disk queue may store its
// content.
cTaskQueue = []byte("client-task-queue")
// ErrTowerNotFound signals that the target tower was not found in the
// database.
ErrTowerNotFound = errors.New("tower not found")
// ErrTowerUnackedUpdates is an error returned when we attempt to mark a
// tower's sessions as inactive, but one of its sessions has unacked
// updates.
ErrTowerUnackedUpdates = errors.New("tower has unacked updates")
// ErrCorruptClientSession signals that the client session's on-disk
// structure deviates from what is expected.
ErrCorruptClientSession = errors.New("client session corrupted")
// ErrCorruptChanDetails signals that the clients channel detail's
// on-disk structure deviates from what is expected.
ErrCorruptChanDetails = errors.New("channel details corrupted")
// ErrClientSessionAlreadyExists signals an attempt to reinsert a client
// session that has already been created.
ErrClientSessionAlreadyExists = errors.New(
"client session already exists",
)
// ErrChannelAlreadyRegistered signals a duplicate attempt to register a
// channel with the client database.
ErrChannelAlreadyRegistered = errors.New("channel already registered")
// ErrChannelNotRegistered signals a channel has not yet been registered
// in the client database.
ErrChannelNotRegistered = errors.New("channel not registered")
// ErrClientSessionNotFound signals that the requested client session
// was not found in the database.
ErrClientSessionNotFound = errors.New("client session not found")
// ErrUpdateAlreadyCommitted signals that the chosen sequence number has
// already been committed to an update with a different breach hint.
ErrUpdateAlreadyCommitted = errors.New("update already committed")
// ErrCommitUnorderedUpdate signals the client tried to commit a
// sequence number other than the next unallocated sequence number.
ErrCommitUnorderedUpdate = errors.New("update seqnum not monotonic")
// ErrCommittedUpdateNotFound signals that the tower tried to ACK a
// sequence number that has not yet been allocated by the client.
ErrCommittedUpdateNotFound = errors.New("committed update not found")
// ErrUnallocatedLastApplied signals that the tower tried to provide a
// LastApplied value greater than any allocated sequence number.
ErrUnallocatedLastApplied = errors.New("tower echoed last appiled " +
"greater than allocated seqnum")
// ErrNoReservedKeyIndex signals that a client session could not be
// created because no session key index was reserved.
ErrNoReservedKeyIndex = errors.New("key index not reserved")
// ErrIncorrectKeyIndex signals that the client session could not be
// created because session key index differs from the reserved key
// index.
ErrIncorrectKeyIndex = errors.New("incorrect key index")
// ErrLastTowerAddr is an error returned when the last address of a
// watchtower is attempted to be removed.
ErrLastTowerAddr = errors.New("cannot remove last tower address")
// ErrNoRangeIndexFound is returned when there is no persisted
// range-index found for the given session ID to channel ID pair.
ErrNoRangeIndexFound = errors.New("no range index found for the " +
"given session-channel pair")
// ErrSessionFailedFilterFn indicates that a particular session did
// not pass the filter func provided by the caller.
ErrSessionFailedFilterFn = errors.New("session failed filter func")
// ErrSessionNotClosable is returned when a session is not found in the
// closable list.
ErrSessionNotClosable = errors.New("session is not closable")
// errSessionHasOpenChannels is an error used to indicate that a
// session has updates for channels that are still open.
errSessionHasOpenChannels = errors.New("session has open channels")
// ErrSessionHasUnackedUpdates is an error used to indicate that a
// session has un-acked updates.
ErrSessionHasUnackedUpdates = errors.New("session has un-acked updates")
// errChannelHasMoreSessions is an error used to indicate that a channel
// has updates in other non-closed sessions.
errChannelHasMoreSessions = errors.New("channel has updates in " +
"other sessions")
)
// NewBoltBackendCreator returns a function that creates a new bbolt backend for
// the watchtower database.
func NewBoltBackendCreator(active bool, dbPath,
dbFileName string) func(boltCfg *kvdb.BoltConfig) (kvdb.Backend,
error) {
// If the watchtower client isn't active, we return a function that
// always returns a nil DB to make sure we don't create empty database
// files.
if !active {
return func(_ *kvdb.BoltConfig) (kvdb.Backend, error) {
return nil, nil
}
}
return func(boltCfg *kvdb.BoltConfig) (kvdb.Backend, error) {
cfg := &kvdb.BoltBackendConfig{
DBPath: dbPath,
DBFileName: dbFileName,
NoFreelistSync: boltCfg.NoFreelistSync,
AutoCompact: boltCfg.AutoCompact,
AutoCompactMinAge: boltCfg.AutoCompactMinAge,
DBTimeout: boltCfg.DBTimeout,
}
db, err := kvdb.GetBoltBackend(cfg)
if err != nil {
return nil, fmt.Errorf("could not open boltdb: %w", err)
}
return db, nil
}
}
// ClientDB is single database providing a persistent storage engine for the
// wtclient.
type ClientDB struct {
db kvdb.Backend
// ackedRangeIndex is a map from session ID to channel ID to a
// RangeIndex which represents the backups that have been acked for that
// channel using that session.
ackedRangeIndex map[SessionID]map[lnwire.ChannelID]*RangeIndex
ackedRangeIndexMu sync.Mutex
}
// OpenClientDB opens the client database given the path to the database's
// directory. If no such database exists, this method will initialize a fresh
// one using the latest version number and bucket structure. If a database
// exists but has a lower version number than the current version, any necessary
// migrations will be applied before returning. Any attempt to open a database
// with a version number higher that the latest version will fail to prevent
// accidental reversion.
func OpenClientDB(db kvdb.Backend) (*ClientDB, error) {
firstInit, err := isFirstInit(db)
if err != nil {
return nil, err
}
clientDB := &ClientDB{
db: db,
ackedRangeIndex: make(
map[SessionID]map[lnwire.ChannelID]*RangeIndex,
),
}
err = initOrSyncVersions(clientDB, firstInit, clientDBVersions)
if err != nil {
db.Close()
return nil, err
}
// Now that the database version fully consistent with our latest known
// version, ensure that all top-level buckets known to this version are
// initialized. This allows us to assume their presence throughout all
// operations. If an known top-level bucket is expected to exist but is
// missing, this will trigger a ErrUninitializedDB error.
err = kvdb.Update(clientDB.db, initClientDBBuckets, func() {})
if err != nil {
db.Close()
return nil, err
}
return clientDB, nil
}
// initClientDBBuckets creates all top-level buckets required to handle database
// operations required by the latest version.
func initClientDBBuckets(tx kvdb.RwTx) error {
buckets := [][]byte{
cSessionKeyIndexBkt,
cChanDetailsBkt,
cSessionBkt,
cTowerBkt,
cTowerIndexBkt,
cTowerToSessionIndexBkt,
cChanIDIndexBkt,
cSessionIDIndexBkt,
cClosableSessionsBkt,
}
for _, bucket := range buckets {
_, err := tx.CreateTopLevelBucket(bucket)
if err != nil {
return err
}
}
return nil
}
// bdb returns the backing bbolt.DB instance.
//
// NOTE: Part of the versionedDB interface.
func (c *ClientDB) bdb() kvdb.Backend {
return c.db
}
// Version returns the database's current version number.
//
// NOTE: Part of the versionedDB interface.
func (c *ClientDB) Version() (uint32, error) {
var version uint32
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
var err error
version, err = getDBVersion(tx)
return err
}, func() {
version = 0
})
if err != nil {
return 0, err
}
return version, nil
}
// Close closes the underlying database.
func (c *ClientDB) Close() error {
return c.db.Close()
}
// CreateTower initialize an address record used to communicate with a
// watchtower. Each Tower is assigned a unique ID, that is used to amortize
// storage costs of the public key when used by multiple sessions. If the tower
// already exists, the address is appended to the list of all addresses used to
// that tower previously and its corresponding sessions are marked as active.
func (c *ClientDB) CreateTower(lnAddr *lnwire.NetAddress) (*Tower, error) {
var towerPubKey [33]byte
copy(towerPubKey[:], lnAddr.IdentityKey.SerializeCompressed())
var tower *Tower
err := kvdb.Update(c.db, func(tx kvdb.RwTx) error {
towerIndex := tx.ReadWriteBucket(cTowerIndexBkt)
if towerIndex == nil {
return ErrUninitializedDB
}
towers := tx.ReadWriteBucket(cTowerBkt)
if towers == nil {
return ErrUninitializedDB
}
towerToSessionIndex := tx.ReadWriteBucket(
cTowerToSessionIndexBkt,
)
if towerToSessionIndex == nil {
return ErrUninitializedDB
}
// Check if the tower index already knows of this pubkey.
towerIDBytes := towerIndex.Get(towerPubKey[:])
if len(towerIDBytes) == 8 {
// The tower already exists, deserialize the existing
// record.
var err error
tower, err = getTower(towers, towerIDBytes)
if err != nil {
return err
}
// Set its status to active.
tower.Status = TowerStatusActive
// Add the new address to the existing tower. If the
// address is a duplicate, this will result in no
// change.
tower.AddAddress(lnAddr.Address)
} else {
// No such tower exists, create a new tower id for our
// new tower. The error is unhandled since NextSequence
// never fails in an Update.
towerID, _ := towerIndex.NextSequence()
tower = &Tower{
ID: TowerID(towerID),
IdentityKey: lnAddr.IdentityKey,
Addresses: []net.Addr{lnAddr.Address},
Status: TowerStatusActive,
}
towerIDBytes = tower.ID.Bytes()
// Since this tower is new, record the mapping from
// tower pubkey to tower id in the tower index.
err := towerIndex.Put(towerPubKey[:], towerIDBytes)
if err != nil {
return err
}
// Create a new bucket for this tower in the
// tower-to-sessions index.
_, err = towerToSessionIndex.CreateBucket(towerIDBytes)
if err != nil {
return err
}
}
// Store the new or updated tower under its tower id.
return putTower(towers, tower)
}, func() {
tower = nil
})
if err != nil {
return nil, err
}
return tower, nil
}
// RemoveTower modifies a tower's record within the database. If an address is
// provided, then _only_ the address record should be removed from the tower's
// persisted state. Otherwise, we'll attempt to mark the tower as inactive. If
// any of its sessions has unacked updates, then ErrTowerUnackedUpdates is
// returned. If the tower doesn't have any sessions at all, it'll be completely
// removed from the database.
//
// NOTE: An error is not returned if the tower doesn't exist.
func (c *ClientDB) RemoveTower(pubKey *btcec.PublicKey, addr net.Addr) error {
return kvdb.Update(c.db, func(tx kvdb.RwTx) error {
towers := tx.ReadWriteBucket(cTowerBkt)
if towers == nil {
return ErrUninitializedDB
}
towerIndex := tx.ReadWriteBucket(cTowerIndexBkt)
if towerIndex == nil {
return ErrUninitializedDB
}
towersToSessionsIndex := tx.ReadWriteBucket(
cTowerToSessionIndexBkt,
)
if towersToSessionsIndex == nil {
return ErrUninitializedDB
}
chanIDIndexBkt := tx.ReadBucket(cChanIDIndexBkt)
if chanIDIndexBkt == nil {
return ErrUninitializedDB
}
// Don't return an error if the watchtower doesn't exist to act
// as a NOP.
pubKeyBytes := pubKey.SerializeCompressed()
towerIDBytes := towerIndex.Get(pubKeyBytes)
if towerIDBytes == nil {
return nil
}
tower, err := getTower(towers, towerIDBytes)
if err != nil {
return err
}
// If an address is provided, then we should _only_ remove the
// address record from the database.
if addr != nil {
// Towers should always have at least one address saved.
tower.RemoveAddress(addr)
if len(tower.Addresses) == 0 {
return ErrLastTowerAddr
}
return putTower(towers, tower)
}
// Otherwise, we should attempt to mark the tower's sessions as
// inactive.
sessions := tx.ReadWriteBucket(cSessionBkt)
if sessions == nil {
return ErrUninitializedDB
}
towerID := TowerIDFromBytes(towerIDBytes)
committedUpdateCount := make(map[SessionID]uint16)
perCommittedUpdate := func(s *ClientSession,
_ *CommittedUpdate) {
committedUpdateCount[s.ID]++
}
towerSessions, err := c.listTowerSessions(
towerID, sessions, chanIDIndexBkt,
towersToSessionsIndex,
WithPerCommittedUpdate(perCommittedUpdate),
)
if err != nil {
return err
}
// If it doesn't have any, we can completely remove it from the
// database.
if len(towerSessions) == 0 {
if err := towerIndex.Delete(pubKeyBytes); err != nil {
return err
}
if err := towers.Delete(towerIDBytes); err != nil {
return err
}
return towersToSessionsIndex.DeleteNestedBucket(
towerIDBytes,
)
}
// Otherwise, we mark the tower as inactive.
tower.Status = TowerStatusInactive
err = putTower(towers, tower)
if err != nil {
return err
}
// We'll do a check to ensure that the tower's sessions don't
// have any pending back-ups.
for _, session := range towerSessions {
if committedUpdateCount[session.ID] > 0 {
return ErrTowerUnackedUpdates
}
}
return nil
}, func() {})
}
// DeactivateTower sets the given tower's status to inactive. This means that
// this tower's sessions won't be loaded and used for backups. CreateTower can
// be used to reactivate the tower again.
func (c *ClientDB) DeactivateTower(pubKey *btcec.PublicKey) error {
return kvdb.Update(c.db, func(tx kvdb.RwTx) error {
towers := tx.ReadWriteBucket(cTowerBkt)
if towers == nil {
return ErrUninitializedDB
}
towerIndex := tx.ReadWriteBucket(cTowerIndexBkt)
if towerIndex == nil {
return ErrUninitializedDB
}
towersToSessionsIndex := tx.ReadWriteBucket(
cTowerToSessionIndexBkt,
)
if towersToSessionsIndex == nil {
return ErrUninitializedDB
}
chanIDIndexBkt := tx.ReadBucket(cChanIDIndexBkt)
if chanIDIndexBkt == nil {
return ErrUninitializedDB
}
pubKeyBytes := pubKey.SerializeCompressed()
towerIDBytes := towerIndex.Get(pubKeyBytes)
if towerIDBytes == nil {
return ErrTowerNotFound
}
tower, err := getTower(towers, towerIDBytes)
if err != nil {
return err
}
// If the tower already has the desired status, then we can exit
// here.
if tower.Status == TowerStatusInactive {
return nil
}
// Otherwise, we update the status and re-store the tower.
tower.Status = TowerStatusInactive
return putTower(towers, tower)
}, func() {})
}
// LoadTowerByID retrieves a tower by its tower ID.
func (c *ClientDB) LoadTowerByID(towerID TowerID) (*Tower, error) {
var tower *Tower
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
towers := tx.ReadBucket(cTowerBkt)
if towers == nil {
return ErrUninitializedDB
}
var err error
tower, err = getTower(towers, towerID.Bytes())
return err
}, func() {
tower = nil
})
if err != nil {
return nil, err
}
return tower, nil
}
// LoadTower retrieves a tower by its public key.
func (c *ClientDB) LoadTower(pubKey *btcec.PublicKey) (*Tower, error) {
var tower *Tower
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
towers := tx.ReadBucket(cTowerBkt)
if towers == nil {
return ErrUninitializedDB
}
towerIndex := tx.ReadBucket(cTowerIndexBkt)
if towerIndex == nil {
return ErrUninitializedDB
}
towerIDBytes := towerIndex.Get(pubKey.SerializeCompressed())
if towerIDBytes == nil {
return ErrTowerNotFound
}
var err error
tower, err = getTower(towers, towerIDBytes)
return err
}, func() {
tower = nil
})
if err != nil {
return nil, err
}
return tower, nil
}
// TowerFilterFn is the signature of a call-back function that can be used to
// skip certain towers in the ListTowers method.
type TowerFilterFn func(*Tower) bool
// ListTowers retrieves the list of towers available within the database that
// have a status matching the given status. The filter function may be set in
// order to filter out the towers to be returned.
func (c *ClientDB) ListTowers(filter TowerFilterFn) ([]*Tower, error) {
var towers []*Tower
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
towerBucket := tx.ReadBucket(cTowerBkt)
if towerBucket == nil {
return ErrUninitializedDB
}
return towerBucket.ForEach(func(towerIDBytes, _ []byte) error {
tower, err := getTower(towerBucket, towerIDBytes)
if err != nil {
return err
}
if filter != nil && !filter(tower) {
return nil
}
towers = append(towers, tower)
return nil
})
}, func() {
towers = nil
})
if err != nil {
return nil, err
}
return towers, nil
}
// NextSessionKeyIndex reserves a new session key derivation index for a
// particular tower id. The index is reserved for that tower until
// CreateClientSession is invoked for that tower and index, at which point a new
// index for that tower can be reserved. Multiple calls to this method before
// CreateClientSession is invoked should return the same index unless forceNext
// is true.
func (c *ClientDB) NextSessionKeyIndex(towerID TowerID,
blobType blob.Type, forceNext bool) (uint32, error) {
var index uint32
err := kvdb.Update(c.db, func(tx kvdb.RwTx) error {
keyIndex := tx.ReadWriteBucket(cSessionKeyIndexBkt)
if keyIndex == nil {
return ErrUninitializedDB
}
var err error
if !forceNext {
// Check the session key index to see if a key has
// already been reserved for this tower. If so, we'll
// deserialize and return the index directly.
index, err = getSessionKeyIndex(
keyIndex, towerID, blobType,
)
if err == nil {
return nil
}
}
// By default, we use the next available bucket sequence as the
// key index. But if forceNext is true, then it is assumed that
// some data loss occurred and so the sequence is incremented a
// by a jump of 1000 so that we can arrive at a brand new key
// index quicker.
currentSequence := keyIndex.Sequence()
nextIndex := currentSequence + 1
if forceNext {
nextIndex = currentSequence + 1000
}
if err = keyIndex.SetSequence(nextIndex); err != nil {
return fmt.Errorf("could not set next bucket "+
"sequence: %w", err)
}
// As a sanity check, assert that the index is still in the
// valid range of unhardened pubkeys. In the future, we should
// move to only using hardened keys, and this will prevent any
// overlap from occurring until then. This also prevents us from
// overflowing uint32s.
if nextIndex > math.MaxInt32 {
return fmt.Errorf("exhausted session key indexes")
}
// Create the key that will used to be store the reserved index.
keyBytes := createSessionKeyIndexKey(towerID, blobType)
index = uint32(nextIndex)
var indexBuf [4]byte
byteOrder.PutUint32(indexBuf[:], index)
// Record the reserved session key index under this tower's id.
return keyIndex.Put(keyBytes, indexBuf[:])
}, func() {
index = 0
})
if err != nil {
return 0, err
}
return index, nil
}
// CreateClientSession records a newly negotiated client session in the set of
// active sessions. The session can be identified by its SessionID.
func (c *ClientDB) CreateClientSession(session *ClientSession) error {
return kvdb.Update(c.db, func(tx kvdb.RwTx) error {
keyIndexes := tx.ReadWriteBucket(cSessionKeyIndexBkt)
if keyIndexes == nil {
return ErrUninitializedDB
}
sessions := tx.ReadWriteBucket(cSessionBkt)
if sessions == nil {
return ErrUninitializedDB
}
towers := tx.ReadBucket(cTowerBkt)
if towers == nil {
return ErrUninitializedDB
}
towerToSessionIndex := tx.ReadWriteBucket(
cTowerToSessionIndexBkt,
)
if towerToSessionIndex == nil {
return ErrUninitializedDB
}
// Check that client session with this session id doesn't
// already exist.
existingSessionBytes := sessions.NestedReadWriteBucket(
session.ID[:],
)
if existingSessionBytes != nil {
return ErrClientSessionAlreadyExists
}
// Ensure that a tower with the given ID actually exists in the
// DB.
towerID := session.TowerID
if _, err := getTower(towers, towerID.Bytes()); err != nil {
return err
}
blobType := session.Policy.BlobType
// Check that this tower has a reserved key index.
index, err := getSessionKeyIndex(keyIndexes, towerID, blobType)
if err != nil {
return err
}
// Assert that the key index of the inserted session matches the
// reserved session key index.
if index != session.KeyIndex {
return ErrIncorrectKeyIndex
}
// Remove the key index reservation. For altruist commit
// sessions, we'll also purge under the old legacy key format.
key := createSessionKeyIndexKey(towerID, blobType)
err = keyIndexes.Delete(key)
if err != nil {
return err
}
if blobType == blob.TypeAltruistCommit {
err = keyIndexes.Delete(towerID.Bytes())
if err != nil {
return err
}
}
// Get the session-ID index bucket.
dbIDIndex := tx.ReadWriteBucket(cSessionIDIndexBkt)
if dbIDIndex == nil {
return ErrUninitializedDB
}
// Get a new, unique, ID for this session from the session-ID
// index bucket.
nextSeq, err := dbIDIndex.NextSequence()
if err != nil {
return err
}
// Add the new entry to the dbID-to-SessionID index.
newIndex, err := writeBigSize(nextSeq)
if err != nil {
return err
}
err = dbIDIndex.Put(newIndex, session.ID[:])
if err != nil {
return err
}
// Also add the db-assigned-id to the session bucket under the
// cSessionDBID key.
sessionBkt, err := sessions.CreateBucket(session.ID[:])
if err != nil {
return err
}
err = sessionBkt.Put(cSessionDBID, newIndex)
if err != nil {
return err
}
// TODO(elle): migrate the towerID-to-SessionID to use the
// new db-assigned sessionID's rather.
// Add the new entry to the towerID-to-SessionID index.
towerSessions := towerToSessionIndex.NestedReadWriteBucket(
towerID.Bytes(),
)
if towerSessions == nil {
return ErrTowerNotFound
}
err = towerSessions.Put(session.ID[:], []byte{1})
if err != nil {
return err
}
// Finally, write the client session's body in the sessions
// bucket.
return putClientSessionBody(sessionBkt, session)
}, func() {})
}
// readRangeIndex reads a persisted RangeIndex from the passed bucket and into
// a new in-memory RangeIndex.
func readRangeIndex(rangesBkt kvdb.RBucket) (*RangeIndex, error) {
ranges := make(map[uint64]uint64)
err := rangesBkt.ForEach(func(k, v []byte) error {
start, err := readBigSize(k)
if err != nil {
return err
}
end, err := readBigSize(v)
if err != nil {
return err
}
ranges[start] = end
return nil
})
if err != nil {
return nil, err
}
return NewRangeIndex(ranges, WithSerializeUint64Fn(writeBigSize))
}
// getRangeIndex checks the ClientDB's in-memory range index map to see if it
// has an entry for the given session and channel ID. If it does, this is
// returned, otherwise the range index is loaded from the DB. An optional db
// transaction parameter may be provided. If one is provided then it will be
// used to query the DB for the range index, otherwise, a new transaction will
// be created and used.
func (c *ClientDB) getRangeIndex(tx kvdb.RTx, sID SessionID,
chanID lnwire.ChannelID) (*RangeIndex, error) {
c.ackedRangeIndexMu.Lock()
defer c.ackedRangeIndexMu.Unlock()
if _, ok := c.ackedRangeIndex[sID]; !ok {
c.ackedRangeIndex[sID] = make(map[lnwire.ChannelID]*RangeIndex)
}
// If the in-memory range-index map already includes an entry for this
// session ID and channel ID pair, then return it.
if index, ok := c.ackedRangeIndex[sID][chanID]; ok {
return index, nil
}
// readRangeIndexFromBkt is a helper that is used to read in a
// RangeIndex structure from the passed in bucket and store it in the
// ackedRangeIndex map.
readRangeIndexFromBkt := func(rangesBkt kvdb.RBucket) (*RangeIndex,
error) {
// Create a new in-memory RangeIndex by reading in ranges from
// the DB.
rangeIndex, err := readRangeIndex(rangesBkt)
if err != nil {
return nil, err
}
c.ackedRangeIndex[sID][chanID] = rangeIndex
return rangeIndex, nil
}
// If a DB transaction is provided then use it to fetch the ranges
// bucket from the DB.
if tx != nil {
rangesBkt, err := getRangesReadBucket(tx, sID, chanID)
if err != nil {
return nil, err
}
return readRangeIndexFromBkt(rangesBkt)
}
// No DB transaction was provided. So create and use a new one.
var index *RangeIndex
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
rangesBkt, err := getRangesReadBucket(tx, sID, chanID)
if err != nil {
return err
}
index, err = readRangeIndexFromBkt(rangesBkt)
return err
}, func() {})
if err != nil {
return nil, err
}
return index, nil
}
// getRangesReadBucket gets the range index bucket where the range index for the
// given session-channel pair is stored. If any sub-buckets along the way do not
// exist, then an error is returned. If the sub-buckets should be created
// instead, then use getRangesWriteBucket.
func getRangesReadBucket(tx kvdb.RTx, sID SessionID, chanID lnwire.ChannelID) (
kvdb.RBucket, error) {
sessions := tx.ReadBucket(cSessionBkt)
if sessions == nil {
return nil, ErrUninitializedDB
}
chanDetailsBkt := tx.ReadBucket(cChanDetailsBkt)
if chanDetailsBkt == nil {
return nil, ErrUninitializedDB
}
sessionBkt := sessions.NestedReadBucket(sID[:])
if sessionsBkt == nil {
return nil, ErrNoRangeIndexFound
}
// Get the DB representation of the channel-ID.
_, dbChanIDBytes, err := getDBChanID(chanDetailsBkt, chanID)
if err != nil {
return nil, err
}
sessionAckRanges := sessionBkt.NestedReadBucket(cSessionAckRangeIndex)
if sessionAckRanges == nil {
return nil, ErrNoRangeIndexFound
}
return sessionAckRanges.NestedReadBucket(dbChanIDBytes), nil
}
// getRangesWriteBucket gets the range index bucket where the range index for
// the given session-channel pair is stored. If any sub-buckets along the way do
// not exist, then they are created.
func getRangesWriteBucket(sessionBkt kvdb.RwBucket, dbChanIDBytes []byte) (
kvdb.RwBucket, error) {
sessionAckRanges, err := sessionBkt.CreateBucketIfNotExists(
cSessionAckRangeIndex,
)
if err != nil {
return nil, err
}
return sessionAckRanges.CreateBucketIfNotExists(dbChanIDBytes)
}
// createSessionKeyIndexKey returns the identifier used in the
// session-key-index index, created as tower-id||blob-type.
//
// NOTE: The original serialization only used tower-id, which prevents
// concurrent client types from reserving sessions with the same tower.
func createSessionKeyIndexKey(towerID TowerID, blobType blob.Type) []byte {
towerIDBytes := towerID.Bytes()
// Session key indexes are stored under as tower-id||blob-type.
var keyBytes [6]byte
copy(keyBytes[:4], towerIDBytes)
byteOrder.PutUint16(keyBytes[4:], uint16(blobType))
return keyBytes[:]
}
// getSessionKeyIndex is a helper method.
func getSessionKeyIndex(keyIndexes kvdb.RwBucket, towerID TowerID,
blobType blob.Type) (uint32, error) {
// Session key indexes are store under as tower-id||blob-type. The
// original serialization only used tower-id, which prevents concurrent
// client types from reserving sessions with the same tower.
keyBytes := createSessionKeyIndexKey(towerID, blobType)
// Retrieve the index using the key bytes. If the key wasn't found, we
// will fall back to the legacy format that only uses the tower id, but
// _only_ if the blob type is for altruist commit sessions since that
// was the only operational session type prior to changing the key
// format.
keyIndexBytes := keyIndexes.Get(keyBytes)
if keyIndexBytes == nil && blobType == blob.TypeAltruistCommit {
keyIndexBytes = keyIndexes.Get(towerID.Bytes())
}
// All session key indexes should be serialized uint32's. If no key
// index was found, the length of keyIndexBytes will be 0.
if len(keyIndexBytes) != 4 {
return 0, ErrNoReservedKeyIndex
}
return byteOrder.Uint32(keyIndexBytes), nil
}
// GetClientSession loads the ClientSession with the given ID from the DB.
func (c *ClientDB) GetClientSession(id SessionID,
opts ...ClientSessionListOption) (*ClientSession, error) {
var sess *ClientSession
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
sessionsBkt := tx.ReadBucket(cSessionBkt)
if sessionsBkt == nil {
return ErrUninitializedDB
}
chanIDIndexBkt := tx.ReadBucket(cChanIDIndexBkt)
if chanIDIndexBkt == nil {
return ErrUninitializedDB
}
session, err := c.getClientSession(
sessionsBkt, chanIDIndexBkt, id[:], opts...,
)
if err != nil {
return err
}
sess = session
return nil
}, func() {})
return sess, err
}
// ListClientSessions returns the set of all client sessions known to the db. An
// optional tower ID can be used to filter out any client sessions in the
// response that do not correspond to this tower.
func (c *ClientDB) ListClientSessions(id *TowerID,
opts ...ClientSessionListOption) (map[SessionID]*ClientSession, error) {
var clientSessions map[SessionID]*ClientSession
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
sessions := tx.ReadBucket(cSessionBkt)
if sessions == nil {
return ErrUninitializedDB
}
chanIDIndexBkt := tx.ReadBucket(cChanIDIndexBkt)
if chanIDIndexBkt == nil {
return ErrUninitializedDB
}
// If no tower ID is specified, then fetch all the sessions
// known to the db.
var err error
if id == nil {
clientSessions, err = c.listClientAllSessions(
sessions, chanIDIndexBkt, opts...,
)
return err
}
// Otherwise, fetch the sessions for the given tower.
towerToSessionIndex := tx.ReadBucket(cTowerToSessionIndexBkt)
if towerToSessionIndex == nil {
return ErrUninitializedDB
}
clientSessions, err = c.listTowerSessions(
*id, sessions, chanIDIndexBkt, towerToSessionIndex,
opts...,
)
return err
}, func() {
clientSessions = nil
})
if err != nil {
return nil, err
}
return clientSessions, nil
}
// listClientAllSessions returns the set of all client sessions known to the db.
func (c *ClientDB) listClientAllSessions(sessions, chanIDIndexBkt kvdb.RBucket,
opts ...ClientSessionListOption) (map[SessionID]*ClientSession, error) {
clientSessions := make(map[SessionID]*ClientSession)
err := sessions.ForEach(func(k, _ []byte) error {
// We'll load the full client session since the client will need
// the CommittedUpdates and AckedUpdates on startup to resume
// committed updates and compute the highest known commit height
// for each channel.
session, err := c.getClientSession(
sessions, chanIDIndexBkt, k, opts...,
)
if errors.Is(err, ErrSessionFailedFilterFn) {
return nil
} else if err != nil {
return err
}
clientSessions[session.ID] = session
return nil
})
if err != nil {
return nil, err
}
return clientSessions, nil
}
// listTowerSessions returns the set of all client sessions known to the db
// that are associated with the given tower id.
func (c *ClientDB) listTowerSessions(id TowerID, sessionsBkt, chanIDIndexBkt,
towerToSessionIndex kvdb.RBucket, opts ...ClientSessionListOption) (
map[SessionID]*ClientSession, error) {
towerIndexBkt := towerToSessionIndex.NestedReadBucket(id.Bytes())
if towerIndexBkt == nil {
return nil, ErrTowerNotFound
}
clientSessions := make(map[SessionID]*ClientSession)
err := towerIndexBkt.ForEach(func(k, _ []byte) error {
// We'll load the full client session since the client will need
// the CommittedUpdates and AckedUpdates on startup to resume
// committed updates and compute the highest known commit height
// for each channel.
session, err := c.getClientSession(
sessionsBkt, chanIDIndexBkt, k, opts...,
)
if errors.Is(err, ErrSessionFailedFilterFn) {
return nil
} else if err != nil {
return err
}
clientSessions[session.ID] = session
return nil
})
if err != nil {
return nil, err
}
return clientSessions, nil
}
// FetchSessionCommittedUpdates retrieves the current set of un-acked updates
// of the given session.
func (c *ClientDB) FetchSessionCommittedUpdates(id *SessionID) (
[]CommittedUpdate, error) {
var committedUpdates []CommittedUpdate
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
sessions := tx.ReadBucket(cSessionBkt)
if sessions == nil {
return ErrUninitializedDB
}
sessionBkt := sessions.NestedReadBucket(id[:])
if sessionBkt == nil {
return ErrClientSessionNotFound
}
var err error
committedUpdates, err = getClientSessionCommits(
sessionBkt, nil, nil,
)
return err
}, func() {})
if err != nil {
return nil, err
}
return committedUpdates, nil
}
// IsAcked returns true if the given backup has been backed up using the given
// session.
func (c *ClientDB) IsAcked(id *SessionID, backupID *BackupID) (bool, error) {
index, err := c.getRangeIndex(nil, *id, backupID.ChanID)
if errors.Is(err, ErrNoRangeIndexFound) {
return false, nil
} else if err != nil {
return false, err
}
return index.IsInIndex(backupID.CommitHeight), nil
}
// NumAckedUpdates returns the number of backups that have been successfully
// backed up using the given session.
func (c *ClientDB) NumAckedUpdates(id *SessionID) (uint64, error) {
var numAcked uint64
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
sessions := tx.ReadBucket(cSessionBkt)
if sessions == nil {
return ErrUninitializedDB
}
chanIDIndexBkt := tx.ReadBucket(cChanIDIndexBkt)
if chanIDIndexBkt == nil {
return ErrUninitializedDB
}
sessionBkt := sessions.NestedReadBucket(id[:])
if sessionBkt == nil {
return nil
}
// First, account for any rogue updates.
rogueCountBytes := sessionBkt.Get(cSessionRogueUpdateCount)
if len(rogueCountBytes) != 0 {
rogueCount, err := readBigSize(rogueCountBytes)
if err != nil {
return err
}
numAcked += rogueCount
}
// Then, check if the session-ack-ranges contains any entries
// to account for.
sessionAckRanges := sessionBkt.NestedReadBucket(
cSessionAckRangeIndex,
)
if sessionAckRanges == nil {
return nil
}
// Iterate over the channel ID's in the sessionAckRanges
// bucket.
return sessionAckRanges.ForEach(func(dbChanID, _ []byte) error {
// Get the range index for the session-channel pair.
chanIDBytes := chanIDIndexBkt.Get(dbChanID)
var chanID lnwire.ChannelID
copy(chanID[:], chanIDBytes)
index, err := c.getRangeIndex(tx, *id, chanID)
if err != nil {
return err
}
numAcked += index.NumInSet()
return nil
})
}, func() {
numAcked = 0
})
if err != nil {
return 0, err
}
return numAcked, nil
}
// FetchChanInfos loads a mapping from all registered channels to their
// ChannelInfo. Only the channels that have not yet been marked as closed will
// be loaded.
func (c *ClientDB) FetchChanInfos() (ChannelInfos, error) {
var infos ChannelInfos
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
chanDetailsBkt := tx.ReadBucket(cChanDetailsBkt)
if chanDetailsBkt == nil {
return ErrUninitializedDB
}
return chanDetailsBkt.ForEach(func(k, _ []byte) error {
chanDetails := chanDetailsBkt.NestedReadBucket(k)
if chanDetails == nil {
return ErrCorruptChanDetails
}
// If this channel has already been marked as closed,
// then its summary does not need to be loaded.
closedHeight := chanDetails.Get(cChanClosedHeight)
if len(closedHeight) > 0 {
return nil
}
var chanID lnwire.ChannelID
copy(chanID[:], k)
summary, err := getChanSummary(chanDetails)
if err != nil {
return err
}
info := &ChannelInfo{
ClientChanSummary: *summary,
}
maxHeightBytes := chanDetails.Get(
cChanMaxCommitmentHeight,
)
if len(maxHeightBytes) != 0 {
height, err := readBigSize(maxHeightBytes)
if err != nil {
return err
}
info.MaxHeight = fn.Some(height)
}
infos[chanID] = info
return nil
})
}, func() {
infos = make(ChannelInfos)
})
if err != nil {
return nil, err
}
return infos, nil
}
// RegisterChannel registers a channel for use within the client database. For
// now, all that is stored in the channel summary is the sweep pkscript that
// we'd like any tower sweeps to pay into. In the future, this will be extended
// to contain more info to allow the client efficiently request historical
// states to be backed up under the client's active policy.
func (c *ClientDB) RegisterChannel(chanID lnwire.ChannelID,
sweepPkScript []byte) error {
return kvdb.Update(c.db, func(tx kvdb.RwTx) error {
chanDetailsBkt := tx.ReadWriteBucket(cChanDetailsBkt)
if chanDetailsBkt == nil {
return ErrUninitializedDB
}
chanDetails := chanDetailsBkt.NestedReadWriteBucket(chanID[:])
if chanDetails != nil {
// Channel is already registered.
return ErrChannelAlreadyRegistered
}
chanDetails, err := chanDetailsBkt.CreateBucket(chanID[:])
if err != nil {
return err
}
// Get the channel-id-index bucket.
indexBkt := tx.ReadWriteBucket(cChanIDIndexBkt)
if indexBkt == nil {
return ErrUninitializedDB
}
// Request the next unique id from the bucket.
nextSeq, err := indexBkt.NextSequence()
if err != nil {
return err
}
// Use BigSize encoding to encode the db-assigned index.
newIndex, err := writeBigSize(nextSeq)
if err != nil {
return err
}
// Add the new db-assigned ID to channel-ID pair.
err = indexBkt.Put(newIndex, chanID[:])
if err != nil {
return err
}
// Add the db-assigned ID to the channel's channel details
// bucket under the cChanDBID key.
err = chanDetails.Put(cChanDBID, newIndex)
if err != nil {
return err
}
summary := ClientChanSummary{
SweepPkScript: sweepPkScript,
}
return putChanSummary(chanDetails, &summary)
}, func() {})
}
// MarkBackupIneligible records that the state identified by the (channel id,
// commit height) tuple was ineligible for being backed up under the current
// policy. This state can be retried later under a different policy.
func (c *ClientDB) MarkBackupIneligible(chanID lnwire.ChannelID,
commitHeight uint64) error {
return nil
}
// ListClosableSessions fetches and returns the IDs for all sessions marked as
// closable.
func (c *ClientDB) ListClosableSessions() (map[SessionID]uint32, error) {
sessions := make(map[SessionID]uint32)
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
csBkt := tx.ReadBucket(cClosableSessionsBkt)
if csBkt == nil {
return ErrUninitializedDB
}
sessIDIndexBkt := tx.ReadBucket(cSessionIDIndexBkt)
if sessIDIndexBkt == nil {
return ErrUninitializedDB
}
return csBkt.ForEach(func(dbIDBytes, heightBytes []byte) error {
dbID, err := readBigSize(dbIDBytes)
if err != nil {
return err
}
sessID, err := getRealSessionID(sessIDIndexBkt, dbID)
if err != nil {
return err
}
sessions[*sessID] = byteOrder.Uint32(heightBytes)
return nil
})
}, func() {
sessions = make(map[SessionID]uint32)
})
if err != nil {
return nil, err
}
return sessions, nil
}
// DeleteSession can be called when a session should be deleted from the DB.
// All references to the session will also be deleted from the DB. Note that a
// session will only be deleted if was previously marked as closable.
func (c *ClientDB) DeleteSession(id SessionID) error {
return kvdb.Update(c.db, func(tx kvdb.RwTx) error {
sessionsBkt := tx.ReadWriteBucket(cSessionBkt)
if sessionsBkt == nil {
return ErrUninitializedDB
}
closableBkt := tx.ReadWriteBucket(cClosableSessionsBkt)
if closableBkt == nil {
return ErrUninitializedDB
}
chanDetailsBkt := tx.ReadWriteBucket(cChanDetailsBkt)
if chanDetailsBkt == nil {
return ErrUninitializedDB
}
sessIDIndexBkt := tx.ReadWriteBucket(cSessionIDIndexBkt)
if sessIDIndexBkt == nil {
return ErrUninitializedDB
}
chanIDIndexBkt := tx.ReadWriteBucket(cChanIDIndexBkt)
if chanIDIndexBkt == nil {
return ErrUninitializedDB
}
towerToSessBkt := tx.ReadWriteBucket(cTowerToSessionIndexBkt)
if towerToSessBkt == nil {
return ErrUninitializedDB
}
// Get the sub-bucket for this session ID. If it does not exist
// then the session has already been deleted and so our work is
// done.
sessionBkt := sessionsBkt.NestedReadBucket(id[:])
if sessionBkt == nil {
return nil
}
_, dbIDBytes, err := getDBSessionID(sessionsBkt, id)
if err != nil {
return err
}
// First we check if the session has actually been marked as
// closable.
if closableBkt.Get(dbIDBytes) == nil {
return ErrSessionNotClosable
}
sess, err := getClientSessionBody(sessionsBkt, id[:])
if err != nil {
return err
}
// Delete from the tower-to-sessionID index.
towerIndexBkt := towerToSessBkt.NestedReadWriteBucket(
sess.TowerID.Bytes(),
)
if towerIndexBkt == nil {
return fmt.Errorf("no entry in the tower-to-session "+
"index found for tower ID %v", sess.TowerID)
}
err = towerIndexBkt.Delete(id[:])
if err != nil {
return err
}
// Delete entry from session ID index.
err = sessIDIndexBkt.Delete(dbIDBytes)
if err != nil {
return err
}
// Delete the entry from the closable sessions index.
err = closableBkt.Delete(dbIDBytes)
if err != nil {
return err
}
ackRanges := sessionBkt.NestedReadBucket(cSessionAckRangeIndex)
// There is a small chance that the session only contains rogue
// updates. In that case, there will be no ack-ranges index but
// the rogue update count will be equal the MaxUpdates.
rogueCountBytes := sessionBkt.Get(cSessionRogueUpdateCount)
if len(rogueCountBytes) != 0 {
rogueCount, err := readBigSize(rogueCountBytes)
if err != nil {
return err
}
maxUpdates := sess.ClientSessionBody.Policy.MaxUpdates
if rogueCount == uint64(maxUpdates) {
// Do a sanity check to ensure that the acked
// ranges bucket does not exist in this case.
if ackRanges != nil {
return fmt.Errorf("acked updates "+
"exist for session with a "+
"max-updates(%d) rogue count",
rogueCount)
}
return sessionsBkt.DeleteNestedBucket(id[:])
}
}
// A session would only be considered closable if it was
// exhausted. Meaning that it should not be the case that it has
// no acked-updates.
if ackRanges == nil {
return fmt.Errorf("cannot delete session %s since it "+
"is not yet exhausted", id)
}
// For each of the channels, delete the session ID entry.
err = ackRanges.ForEach(func(chanDBID, _ []byte) error {
chanDBIDInt, err := readBigSize(chanDBID)
if err != nil {
return err
}
chanID, err := getRealChannelID(
chanIDIndexBkt, chanDBIDInt,
)
if err != nil {
return err
}
chanDetails := chanDetailsBkt.NestedReadWriteBucket(
chanID[:],
)
if chanDetails == nil {
return ErrChannelNotRegistered
}
chanSessions := chanDetails.NestedReadWriteBucket(
cChanSessions,
)
if chanSessions == nil {
return fmt.Errorf("no session list found for "+
"channel %s", chanID)
}
// Check that this session was actually listed in the
// session list for this channel.
if len(chanSessions.Get(dbIDBytes)) == 0 {
return fmt.Errorf("session %s not found in "+
"the session list for channel %s", id,
chanID)
}
// If it was, then delete it.
err = chanSessions.Delete(dbIDBytes)
if err != nil {
return err
}
// If this was the last session for this channel, we can
// now delete the channel details for this channel
// completely.
err = chanSessions.ForEach(func(_, _ []byte) error {
return errChannelHasMoreSessions
})
if errors.Is(err, errChannelHasMoreSessions) {
return nil
} else if err != nil {
return err
}
// Delete the channel's entry from the channel-id-index.
dbID := chanDetails.Get(cChanDBID)
err = chanIDIndexBkt.Delete(dbID)
if err != nil {
return err
}
// Delete the channel details.
return chanDetailsBkt.DeleteNestedBucket(chanID[:])
})
if err != nil {
return err
}
// Delete the actual session.
return sessionsBkt.DeleteNestedBucket(id[:])
}, func() {})
}
// MarkChannelClosed will mark a registered channel as closed by setting its
// closed-height as the given block height. It returns a list of session IDs for
// sessions that are now considered closable due to the close of this channel.
// The details for this channel will be deleted from the DB if there are no more
// sessions in the DB that contain updates for this channel.
func (c *ClientDB) MarkChannelClosed(chanID lnwire.ChannelID,
blockHeight uint32) ([]SessionID, error) {
var closableSessions []SessionID
err := kvdb.Update(c.db, func(tx kvdb.RwTx) error {
sessionsBkt := tx.ReadBucket(cSessionBkt)
if sessionsBkt == nil {
return ErrUninitializedDB
}
chanDetailsBkt := tx.ReadWriteBucket(cChanDetailsBkt)
if chanDetailsBkt == nil {
return ErrUninitializedDB
}
closableSessBkt := tx.ReadWriteBucket(cClosableSessionsBkt)
if closableSessBkt == nil {
return ErrUninitializedDB
}
chanIDIndexBkt := tx.ReadBucket(cChanIDIndexBkt)
if chanIDIndexBkt == nil {
return ErrUninitializedDB
}
sessIDIndexBkt := tx.ReadBucket(cSessionIDIndexBkt)
if sessIDIndexBkt == nil {
return ErrUninitializedDB
}
chanDetails := chanDetailsBkt.NestedReadWriteBucket(chanID[:])
if chanDetails == nil {
return ErrChannelNotRegistered
}
// If there are no sessions for this channel, the channel
// details can be deleted.
chanSessIDsBkt := chanDetails.NestedReadBucket(cChanSessions)
if chanSessIDsBkt == nil {
return chanDetailsBkt.DeleteNestedBucket(chanID[:])
}
// Otherwise, mark the channel as closed.
var height [4]byte
byteOrder.PutUint32(height[:], blockHeight)
err := chanDetails.Put(cChanClosedHeight, height[:])
if err != nil {
return err
}
// Now iterate through all the sessions of the channel to check
// if any of them are closeable.
return chanSessIDsBkt.ForEach(func(sessDBID, _ []byte) error {
sessDBIDInt, err := readBigSize(sessDBID)
if err != nil {
return err
}
// Use the session-ID index to get the real session ID.
sID, err := getRealSessionID(
sessIDIndexBkt, sessDBIDInt,
)
if err != nil {
return err
}
isClosable, err := isSessionClosable(
sessionsBkt, chanDetailsBkt, chanIDIndexBkt,
sID,
)
if err != nil {
return err
}
if !isClosable {
return nil
}
// Add session to "closableSessions" list and add the
// block height that this last channel was closed in.
// This will be used in future to determine when we
// should delete the session.
var height [4]byte
byteOrder.PutUint32(height[:], blockHeight)
err = closableSessBkt.Put(sessDBID, height[:])
if err != nil {
return err
}
closableSessions = append(closableSessions, *sID)
return nil
})
}, func() {
closableSessions = nil
})
if err != nil {
return nil, err
}
return closableSessions, nil
}
// isSessionClosable returns true if a session is considered closable. A session
// is considered closable only if all the following points are true:
// 1. It has no un-acked updates.
// 2. It is exhausted (ie it can't accept any more updates) OR it has been
// marked as terminal.
// 3. All the channels that it has acked updates for are closed.
func isSessionClosable(sessionsBkt, chanDetailsBkt, chanIDIndexBkt kvdb.RBucket,
id *SessionID) (bool, error) {
sessBkt := sessionsBkt.NestedReadBucket(id[:])
if sessBkt == nil {
return false, ErrSessionNotFound
}
// Since the DeleteCommittedUpdates method deletes the cSessionCommits
// bucket in one go, it is possible for the session to be closable even
// if this bucket no longer exists.
commitsBkt := sessBkt.NestedReadBucket(cSessionCommits)
if commitsBkt != nil {
// If the session has any un-acked updates, then it is not yet
// closable.
err := commitsBkt.ForEach(func(_, _ []byte) error {
return ErrSessionHasUnackedUpdates
})
if errors.Is(err, ErrSessionHasUnackedUpdates) {
return false, nil
} else if err != nil {
return false, err
}
}
session, err := getClientSessionBody(sessionsBkt, id[:])
if err != nil {
return false, err
}
isTerminal := session.Status == CSessionTerminal
// We have already checked that the session has no more committed
// updates. So now we can check if the session is exhausted or has a
// terminal state.
if !isTerminal && session.SeqNum < session.Policy.MaxUpdates {
// If the session is not yet exhausted, and it is not yet in a
// terminal state then it is not yet closable.
return false, nil
}
// Either the acked-update bucket should exist _or_ the rogue update
// count must be equal to the session's MaxUpdates value, otherwise
// something is wrong because the above check ensures that the session
// has been exhausted.
rogueCountBytes := sessBkt.Get(cSessionRogueUpdateCount)
if len(rogueCountBytes) != 0 {
rogueCount, err := readBigSize(rogueCountBytes)
if err != nil {
return false, err
}
if rogueCount == uint64(session.Policy.MaxUpdates) {
return true, nil
}
}
ackedRangeBkt := sessBkt.NestedReadBucket(cSessionAckRangeIndex)
if ackedRangeBkt == nil {
if isTerminal {
return true, nil
}
// If the session has no acked-updates, and it is not in a
// terminal state then something is wrong since the above check
// ensures that this session has been exhausted meaning that it
// should have MaxUpdates acked updates.
return false, fmt.Errorf("no acked-updates found for "+
"exhausted session %s", id)
}
// Iterate over each of the channels that the session has acked-updates
// for. If any of those channels are not closed, then the session is
// not yet closable.
err = ackedRangeBkt.ForEach(func(dbChanID, _ []byte) error {
dbChanIDInt, err := readBigSize(dbChanID)
if err != nil {
return err
}
chanID, err := getRealChannelID(chanIDIndexBkt, dbChanIDInt)
if err != nil {
return err
}
// Get the channel details bucket for the channel.
chanDetails := chanDetailsBkt.NestedReadBucket(chanID[:])
if chanDetails == nil {
return fmt.Errorf("no channel details found for "+
"channel %s referenced by session %s", chanID,
id)
}
// If a closed height has been set, then the channel is closed.
closedHeight := chanDetails.Get(cChanClosedHeight)
if len(closedHeight) > 0 {
return nil
}
// Otherwise, the channel is not yet closed meaning that the
// session is not yet closable. We break the ForEach by
// returning an error to indicate this.
return errSessionHasOpenChannels
})
if errors.Is(err, errSessionHasOpenChannels) {
return false, nil
} else if err != nil {
return false, err
}
return true, nil
}
// CommitUpdate persists the CommittedUpdate provided in the slot for (session,
// seqNum). This allows the client to retransmit this update on startup.
func (c *ClientDB) CommitUpdate(id *SessionID,
update *CommittedUpdate) (uint16, error) {
var lastApplied uint16
err := kvdb.Update(c.db, func(tx kvdb.RwTx) error {
sessions := tx.ReadWriteBucket(cSessionBkt)
if sessions == nil {
return ErrUninitializedDB
}
// We'll only load the ClientSession body for performance, since
// we primarily need to inspect its SeqNum and TowerLastApplied
// fields. The CommittedUpdates will be modified on disk
// directly.
session, err := getClientSessionBody(sessions, id[:])
if err != nil {
return err
}
// Can't fail if the above didn't fail.
sessionBkt := sessions.NestedReadWriteBucket(id[:])
// Ensure the session commits sub-bucket is initialized.
sessionCommits, err := sessionBkt.CreateBucketIfNotExists(
cSessionCommits,
)
if err != nil {
return err
}
var seqNumBuf [2]byte
byteOrder.PutUint16(seqNumBuf[:], update.SeqNum)
// Check to see if a committed update already exists for this
// sequence number.
committedUpdateBytes := sessionCommits.Get(seqNumBuf[:])
if committedUpdateBytes != nil {
var dbUpdate CommittedUpdate
err := dbUpdate.Decode(
bytes.NewReader(committedUpdateBytes),
)
if err != nil {
return err
}
// If an existing committed update has a different hint,
// we'll reject this newer update.
if dbUpdate.Hint != update.Hint {
return ErrUpdateAlreadyCommitted
}
// Otherwise, capture the last applied value and
// succeed.
lastApplied = session.TowerLastApplied
return nil
}
// There's no committed update for this sequence number, ensure
// that we are committing the next unallocated one.
if update.SeqNum != session.SeqNum+1 {
return ErrCommitUnorderedUpdate
}
// Increment the session's sequence number and store the updated
// client session.
//
// TODO(conner): split out seqnum and last applied own bucket to
// eliminate serialization of full struct during CommitUpdate?
// Can also read/write directly to byes [:2] without migration.
session.SeqNum++
err = putClientSessionBody(sessionBkt, session)
if err != nil {
return err
}
// Encode and store the committed update in the sessionCommits
// sub-bucket under the requested sequence number.
var b bytes.Buffer
err = update.Encode(&b)
if err != nil {
return err
}
err = sessionCommits.Put(seqNumBuf[:], b.Bytes())
if err != nil {
return err
}
// Update the channel's max commitment height if needed.
err = maybeUpdateMaxCommitHeight(tx, update.BackupID)
if err != nil {
return err
}
// Finally, capture the session's last applied value so it can
// be sent in the next state update to the tower.
lastApplied = session.TowerLastApplied
return nil
}, func() {
lastApplied = 0
})
if err != nil {
return 0, err
}
return lastApplied, nil
}
// AckUpdate persists an acknowledgment for a given (session, seqnum) pair. This
// removes the update from the set of committed updates, and validates the
// lastApplied value returned from the tower.
func (c *ClientDB) AckUpdate(id *SessionID, seqNum uint16,
lastApplied uint16) error {
return kvdb.Update(c.db, func(tx kvdb.RwTx) error {
sessions := tx.ReadWriteBucket(cSessionBkt)
if sessions == nil {
return ErrUninitializedDB
}
chanDetailsBkt := tx.ReadWriteBucket(cChanDetailsBkt)
if chanDetailsBkt == nil {
return ErrUninitializedDB
}
// We'll only load the ClientSession body for performance, since
// we primarily need to inspect its SeqNum and TowerLastApplied
// fields. The CommittedUpdates and AckedUpdates will be
// modified on disk directly.
session, err := getClientSessionBody(sessions, id[:])
if err != nil {
return err
}
// If the tower has acked a sequence number beyond our highest
// sequence number, fail.
if lastApplied > session.SeqNum {
return ErrUnallocatedLastApplied
}
// If the tower acked with a lower sequence number than it gave
// us prior, fail.
if lastApplied < session.TowerLastApplied {
return ErrLastAppliedReversion
}
// TODO(conner): split out seqnum and last applied own bucket to
// eliminate serialization of full struct during AckUpdate? Can
// also read/write directly to byes [2:4] without migration.
session.TowerLastApplied = lastApplied
// Can't fail because getClientSession succeeded.
sessionBkt := sessions.NestedReadWriteBucket(id[:])
// Write the client session with the updated last applied value.
err = putClientSessionBody(sessionBkt, session)
if err != nil {
return err
}
// If the commits sub-bucket doesn't exist, there can't possibly
// be a corresponding committed update to remove.
sessionCommits := sessionBkt.NestedReadWriteBucket(
cSessionCommits,
)
if sessionCommits == nil {
return ErrCommittedUpdateNotFound
}
var seqNumBuf [2]byte
byteOrder.PutUint16(seqNumBuf[:], seqNum)
// Assert that a committed update exists for this sequence
// number.
committedUpdateBytes := sessionCommits.Get(seqNumBuf[:])
if committedUpdateBytes == nil {
return ErrCommittedUpdateNotFound
}
var committedUpdate CommittedUpdate
err = committedUpdate.Decode(
bytes.NewReader(committedUpdateBytes),
)
if err != nil {
return err
}
// Remove the corresponding committed update.
err = sessionCommits.Delete(seqNumBuf[:])
if err != nil {
return err
}
dbSessionID, dbSessIDBytes, err := getDBSessionID(sessions, *id)
if err != nil {
return err
}
chanID := committedUpdate.BackupID.ChanID
height := committedUpdate.BackupID.CommitHeight
// Get the DB representation of the channel-ID. There is a
// chance that the channel corresponding to this update has been
// closed and that the details for this channel no longer exist
// in the tower client DB. In that case, we consider this a
// rogue update and all we do is make sure to keep track of the
// number of rogue updates for this session.
_, dbChanIDBytes, err := getDBChanID(chanDetailsBkt, chanID)
if errors.Is(err, ErrChannelNotRegistered) {
var (
count uint64
err error
)
rogueCountBytes := sessionBkt.Get(
cSessionRogueUpdateCount,
)
if len(rogueCountBytes) != 0 {
count, err = readBigSize(rogueCountBytes)
if err != nil {
return err
}
}
rogueCount := count + 1
countBytes, err := writeBigSize(rogueCount)
if err != nil {
return err
}
err = sessionBkt.Put(
cSessionRogueUpdateCount, countBytes,
)
if err != nil {
return err
}
// In the rare chance that this session only has rogue
// updates, we check here if the count is equal to the
// MaxUpdate of the session. If it is, then we mark the
// session as closable.
if rogueCount != uint64(session.Policy.MaxUpdates) {
return nil
}
// Before we mark the session as closable, we do a
// sanity check to ensure that this session has no
// acked-update index.
sessionAckRanges := sessionBkt.NestedReadBucket(
cSessionAckRangeIndex,
)
if sessionAckRanges != nil {
return fmt.Errorf("session(%s) has an "+
"acked ranges index but has a rogue "+
"count indicating saturation",
session.ID)
}
closableSessBkt := tx.ReadWriteBucket(
cClosableSessionsBkt,
)
if closableSessBkt == nil {
return ErrUninitializedDB
}
var height [4]byte
byteOrder.PutUint32(height[:], 0)
return closableSessBkt.Put(dbSessIDBytes, height[:])
} else if err != nil {
return err
}
// Get the ranges write bucket before getting the range index to
// ensure that the session acks sub-bucket is initialized, so
// that we can insert an entry.
rangesBkt, err := getRangesWriteBucket(
sessionBkt, dbChanIDBytes,
)
if err != nil {
return err
}
chanDetails := chanDetailsBkt.NestedReadWriteBucket(
committedUpdate.BackupID.ChanID[:],
)
if chanDetails == nil {
return ErrChannelNotRegistered
}
err = putChannelToSessionMapping(chanDetails, dbSessionID)
if err != nil {
return err
}
// Get the range index for the given session-channel pair.
index, err := c.getRangeIndex(tx, *id, chanID)
if err != nil {
return err
}
return index.Add(height, rangesBkt)
}, func() {})
}
// GetDBQueue returns a BackupID Queue instance under the given namespace.
func (c *ClientDB) GetDBQueue(namespace []byte) Queue[*BackupID] {
return NewQueueDB(
c.db, namespace, func() *BackupID {
return &BackupID{}
}, func(tx kvdb.RwTx, item *BackupID) error {
return maybeUpdateMaxCommitHeight(tx, *item)
},
)
}
// TerminateSession sets the given session's status to CSessionTerminal meaning
// that it will not be usable again. An error will be returned if the given
// session still has un-acked updates that should be attended to.
func (c *ClientDB) TerminateSession(id SessionID) error {
return kvdb.Update(c.db, func(tx kvdb.RwTx) error {
sessions := tx.ReadWriteBucket(cSessionBkt)
if sessions == nil {
return ErrUninitializedDB
}
sessionsBkt := tx.ReadBucket(cSessionBkt)
if sessionsBkt == nil {
return ErrUninitializedDB
}
chanIDIndexBkt := tx.ReadBucket(cChanIDIndexBkt)
if chanIDIndexBkt == nil {
return ErrUninitializedDB
}
// Collect any un-acked updates for this session.
committedUpdateCount := make(map[SessionID]uint16)
perCommittedUpdate := func(s *ClientSession,
_ *CommittedUpdate) {
committedUpdateCount[s.ID]++
}
session, err := c.getClientSession(
sessionsBkt, chanIDIndexBkt, id[:],
WithPerCommittedUpdate(perCommittedUpdate),
)
if err != nil {
return err
}
// If there are any un-acked updates for this session then
// we don't allow the change of status as these updates must
// first be dealt with somehow.
if committedUpdateCount[id] > 0 {
return ErrSessionHasUnackedUpdates
}
return markSessionStatus(sessions, session, CSessionTerminal)
}, func() {})
}
// DeleteCommittedUpdates deletes all the committed updates for the given
// session.
func (c *ClientDB) DeleteCommittedUpdates(id *SessionID) error {
return kvdb.Update(c.db, func(tx kvdb.RwTx) error {
sessions := tx.ReadWriteBucket(cSessionBkt)
if sessions == nil {
return ErrUninitializedDB
}
sessionBkt := sessions.NestedReadWriteBucket(id[:])
if sessionBkt == nil {
return fmt.Errorf("session bucket %s not found",
id.String())
}
// If the commits sub-bucket doesn't exist, there can't possibly
// be corresponding updates to remove.
sessionCommits := sessionBkt.NestedReadWriteBucket(
cSessionCommits,
)
if sessionCommits == nil {
return nil
}
// errFoundUpdates is an error we will use to exit early from
// the ForEach loop. The return of this error means that at
// least one committed update exists.
var errFoundUpdates = fmt.Errorf("found committed updates")
err := sessionCommits.ForEach(func(k, v []byte) error {
return errFoundUpdates
})
switch {
// If the errFoundUpdates signal error was returned then there
// are some updates that need to be deleted.
case errors.Is(err, errFoundUpdates):
// If no error is returned then the ForEach call back was never
// entered meaning that there are no un-acked committed updates.
// So we can exit now as there is nothing left to do.
case err == nil:
return nil
// If an expected error is returned, return that error.
default:
return err
}
session, err := getClientSessionBody(sessions, id[:])
if err != nil {
return err
}
// Once we delete a committed update from the session, the
// SeqNum of the session will be incorrect and so the session
// should be marked as terminal.
session.Status = CSessionTerminal
err = putClientSessionBody(sessionBkt, session)
if err != nil {
return err
}
// Delete all the committed updates in one go by deleting the
// session commits bucket.
return sessionBkt.DeleteNestedBucket(cSessionCommits)
}, func() {})
}
// putChannelToSessionMapping adds the given session ID to a channel's
// cChanSessions bucket.
func putChannelToSessionMapping(chanDetails kvdb.RwBucket,
dbSessID uint64) error {
chanSessIDsBkt, err := chanDetails.CreateBucketIfNotExists(
cChanSessions,
)
if err != nil {
return err
}
b, err := writeBigSize(dbSessID)
if err != nil {
return err
}
return chanSessIDsBkt.Put(b, []byte{1})
}
// getClientSessionBody loads the body of a ClientSession from the sessions
// bucket corresponding to the serialized session id. This does not deserialize
// the CommittedUpdates, AckUpdates or the Tower associated with the session.
// If the caller requires this info, use getClientSession.
func getClientSessionBody(sessions kvdb.RBucket,
idBytes []byte) (*ClientSession, error) {
sessionBkt := sessions.NestedReadBucket(idBytes)
if sessionBkt == nil {
return nil, ErrClientSessionNotFound
}
// Should never have a sessionBkt without also having its body.
sessionBody := sessionBkt.Get(cSessionBody)
if sessionBody == nil {
return nil, ErrCorruptClientSession
}
var session ClientSession
copy(session.ID[:], idBytes)
err := session.Decode(bytes.NewReader(sessionBody))
if err != nil {
return nil, err
}
return &session, nil
}
// ClientSessionFilterFn describes the signature of a callback function that can
// be used to filter the sessions that are returned in any of the DB methods
// that read sessions from the DB.
type ClientSessionFilterFn func(*ClientSession) bool
// ClientSessWithNumCommittedUpdatesFilterFn describes the signature of a
// callback function that can be used to filter out a session based on the
// contents of ClientSession along with the number of un-acked committed updates
// that the session has.
type ClientSessWithNumCommittedUpdatesFilterFn func(*ClientSession, uint16) bool
// PerMaxHeightCB describes the signature of a callback function that can be
// called for each channel that a session has updates for to communicate the
// maximum commitment height that the session has backed up for the channel.
type PerMaxHeightCB func(*ClientSession, lnwire.ChannelID, uint64)
// PerNumAckedUpdatesCB describes the signature of a callback function that can
// be called for each channel that a session has updates for to communicate the
// number of updates that the session has for the channel.
type PerNumAckedUpdatesCB func(*ClientSession, lnwire.ChannelID, uint16)
// PerRogueUpdateCountCB describes the signature of a callback function that can
// be called for each session with the number of rogue updates that the session
// has.
type PerRogueUpdateCountCB func(*ClientSession, uint16)
// PerAckedUpdateCB describes the signature of a callback function that can be
// called for each of a session's acked updates.
type PerAckedUpdateCB func(*ClientSession, uint16, BackupID)
// PerCommittedUpdateCB describes the signature of a callback function that can
// be called for each of a session's committed updates (updates that the client
// has not yet received an ACK for).
type PerCommittedUpdateCB func(*ClientSession, *CommittedUpdate)
// ClientSessionListOption describes the signature of a functional option that
// can be used when listing client sessions in order to provide any extra
// instruction to the query.
type ClientSessionListOption func(cfg *ClientSessionListCfg)
// ClientSessionListCfg defines various query parameters that will be used when
// querying the DB for client sessions.
type ClientSessionListCfg struct {
// PerNumAckedUpdates will, if set, be called for each of the session's
// channels to communicate the number of updates stored for that
// channel.
PerNumAckedUpdates PerNumAckedUpdatesCB
// PerRogueUpdateCount will, if set, be called with the number of rogue
// updates that the session has backed up.
PerRogueUpdateCount PerRogueUpdateCountCB
// PerMaxHeight will, if set, be called for each of the session's
// channels to communicate the highest commit height of updates stored
// for that channel.
PerMaxHeight PerMaxHeightCB
// PerCommittedUpdate will, if set, be called for each of the session's
// committed (un-acked) updates.
PerCommittedUpdate PerCommittedUpdateCB
// PreEvaluateFilterFn will be run after loading a session from the DB
// and _before_ any of the other call-back functions in
// ClientSessionListCfg. Therefore, if a session fails this filter
// function, then it will not be passed to any of the other call backs
// and won't be included in the return list.
PreEvaluateFilterFn ClientSessionFilterFn
// PostEvaluateFilterFn will be run _after_ all the other call-back
// functions in ClientSessionListCfg. If a session fails this filter
// function then all it means is that it won't be included in the list
// of sessions to return.
PostEvaluateFilterFn ClientSessWithNumCommittedUpdatesFilterFn
}
// NewClientSessionCfg constructs a new ClientSessionListCfg.
func NewClientSessionCfg() *ClientSessionListCfg {
return &ClientSessionListCfg{}
}
// WithPerMaxHeight constructs a functional option that will set a call-back
// function to be called for each of a session's channels to communicate the
// maximum commitment height that the session has stored for the channel.
func WithPerMaxHeight(cb PerMaxHeightCB) ClientSessionListOption {
return func(cfg *ClientSessionListCfg) {
cfg.PerMaxHeight = cb
}
}
// WithPerNumAckedUpdates constructs a functional option that will set a
// call-back function to be called for each of a session's channels to
// communicate the number of updates that the session has stored for the
// channel.
func WithPerNumAckedUpdates(cb PerNumAckedUpdatesCB) ClientSessionListOption {
return func(cfg *ClientSessionListCfg) {
cfg.PerNumAckedUpdates = cb
}
}
// WithPerRogueUpdateCount constructs a functional option that will set a
// call-back function to be called with the number of rogue updates that the
// session has backed up.
func WithPerRogueUpdateCount(cb PerRogueUpdateCountCB) ClientSessionListOption {
return func(cfg *ClientSessionListCfg) {
cfg.PerRogueUpdateCount = cb
}
}
// WithPerCommittedUpdate constructs a functional option that will set a
// call-back function to be called for each of a client's un-acked updates.
func WithPerCommittedUpdate(cb PerCommittedUpdateCB) ClientSessionListOption {
return func(cfg *ClientSessionListCfg) {
cfg.PerCommittedUpdate = cb
}
}
// WithPreEvalFilterFn constructs a functional option that will set a call-back
// function that will be called immediately after loading a session. If the
// session fails this filter function, then it will not be passed to any of the
// other evaluation call-back functions.
func WithPreEvalFilterFn(fn ClientSessionFilterFn) ClientSessionListOption {
return func(cfg *ClientSessionListCfg) {
cfg.PreEvaluateFilterFn = fn
}
}
// WithPostEvalFilterFn constructs a functional option that will set a call-back
// function that will be used to determine if a session should be included in
// the returned list. This differs from WithPreEvalFilterFn since that call-back
// is used to determine if the session should be evaluated at all (and thus
// run against the other ClientSessionListCfg call-backs) whereas the session
// will only reach the PostEvalFilterFn call-back once it has already been
// evaluated by all the other call-backs.
func WithPostEvalFilterFn(
fn ClientSessWithNumCommittedUpdatesFilterFn) ClientSessionListOption {
return func(cfg *ClientSessionListCfg) {
cfg.PostEvaluateFilterFn = fn
}
}
// getClientSession loads the full ClientSession associated with the serialized
// session id. This method populates the CommittedUpdates, AckUpdates and Tower
// in addition to the ClientSession's body.
func (c *ClientDB) getClientSession(sessionsBkt, chanIDIndexBkt kvdb.RBucket,
idBytes []byte, opts ...ClientSessionListOption) (*ClientSession,
error) {
cfg := NewClientSessionCfg()
for _, o := range opts {
o(cfg)
}
session, err := getClientSessionBody(sessionsBkt, idBytes)
if err != nil {
return nil, err
}
if cfg.PreEvaluateFilterFn != nil && !cfg.PreEvaluateFilterFn(session) {
return nil, ErrSessionFailedFilterFn
}
// Can't fail because client session body has already been read.
sessionBkt := sessionsBkt.NestedReadBucket(idBytes)
// Pass the session's committed (un-acked) updates through the call-back
// if one is provided.
numCommittedUpdates, err := filterClientSessionCommits(
sessionBkt, session, cfg.PerCommittedUpdate,
)
if err != nil {
return nil, err
}
// Pass the session's acked updates through the call-back if one is
// provided.
err = c.filterClientSessionAcks(
sessionBkt, chanIDIndexBkt, session, cfg.PerMaxHeight,
cfg.PerNumAckedUpdates, cfg.PerRogueUpdateCount,
)
if err != nil {
return nil, err
}
if cfg.PostEvaluateFilterFn != nil &&
!cfg.PostEvaluateFilterFn(session, numCommittedUpdates) {
return nil, ErrSessionFailedFilterFn
}
return session, nil
}
// getClientSessionCommits retrieves all committed updates for the session
// identified by the serialized session id. If a PerCommittedUpdateCB is
// provided, then it will be called for each of the session's committed updates.
func getClientSessionCommits(sessionBkt kvdb.RBucket, s *ClientSession,
cb PerCommittedUpdateCB) ([]CommittedUpdate, error) {
// Initialize committedUpdates so that we can return an initialized map
// if no committed updates exist.
committedUpdates := make([]CommittedUpdate, 0)
sessionCommits := sessionBkt.NestedReadBucket(cSessionCommits)
if sessionCommits == nil {
return committedUpdates, nil
}
err := sessionCommits.ForEach(func(k, v []byte) error {
var committedUpdate CommittedUpdate
err := committedUpdate.Decode(bytes.NewReader(v))
if err != nil {
return err
}
committedUpdate.SeqNum = byteOrder.Uint16(k)
committedUpdates = append(committedUpdates, committedUpdate)
if cb != nil {
cb(s, &committedUpdate)
}
return nil
})
if err != nil {
return nil, err
}
return committedUpdates, nil
}
// filterClientSessionAcks retrieves all acked updates for the session
// identified by the serialized session id and passes them to the provided
// call back if one is provided.
func (c *ClientDB) filterClientSessionAcks(sessionBkt,
chanIDIndexBkt kvdb.RBucket, s *ClientSession, perMaxCb PerMaxHeightCB,
perNumAckedUpdates PerNumAckedUpdatesCB,
perRogueUpdateCount PerRogueUpdateCountCB) error {
if perRogueUpdateCount != nil {
var (
count uint64
err error
)
rogueCountBytes := sessionBkt.Get(cSessionRogueUpdateCount)
if len(rogueCountBytes) != 0 {
count, err = readBigSize(rogueCountBytes)
if err != nil {
return err
}
}
perRogueUpdateCount(s, uint16(count))
}
if perMaxCb == nil && perNumAckedUpdates == nil {
return nil
}
sessionAcksRanges := sessionBkt.NestedReadBucket(cSessionAckRangeIndex)
if sessionAcksRanges == nil {
return nil
}
return sessionAcksRanges.ForEach(func(dbChanID, _ []byte) error {
rangeBkt := sessionAcksRanges.NestedReadBucket(dbChanID)
if rangeBkt == nil {
return nil
}
index, err := readRangeIndex(rangeBkt)
if err != nil {
return err
}
chanIDBytes := chanIDIndexBkt.Get(dbChanID)
var chanID lnwire.ChannelID
copy(chanID[:], chanIDBytes)
if perMaxCb != nil {
perMaxCb(s, chanID, index.MaxHeight())
}
if perNumAckedUpdates != nil {
perNumAckedUpdates(s, chanID, uint16(index.NumInSet()))
}
return nil
})
}
// filterClientSessionCommits retrieves all committed updates for the session
// identified by the serialized session id and passes them to the given
// PerCommittedUpdateCB callback.
func filterClientSessionCommits(sessionBkt kvdb.RBucket, s *ClientSession,
cb PerCommittedUpdateCB) (uint16, error) {
sessionCommits := sessionBkt.NestedReadBucket(cSessionCommits)
if sessionCommits == nil {
return 0, nil
}
var numUpdates uint16
err := sessionCommits.ForEach(func(k, v []byte) error {
numUpdates++
if cb == nil {
return nil
}
var committedUpdate CommittedUpdate
err := committedUpdate.Decode(bytes.NewReader(v))
if err != nil {
return err
}
committedUpdate.SeqNum = byteOrder.Uint16(k)
cb(s, &committedUpdate)
return nil
})
if err != nil {
return 0, err
}
return numUpdates, nil
}
// putClientSessionBody stores the body of the ClientSession (everything but the
// CommittedUpdates and AckedUpdates).
func putClientSessionBody(sessionBkt kvdb.RwBucket,
session *ClientSession) error {
var b bytes.Buffer
err := session.Encode(&b)
if err != nil {
return err
}
return sessionBkt.Put(cSessionBody, b.Bytes())
}
// markSessionStatus updates the persisted state of the session to the new
// status.
func markSessionStatus(sessions kvdb.RwBucket, session *ClientSession,
status CSessionStatus) error {
sessionBkt, err := sessions.CreateBucketIfNotExists(session.ID[:])
if err != nil {
return err
}
session.Status = status
return putClientSessionBody(sessionBkt, session)
}
// getChanSummary loads a ClientChanSummary for the passed chanID.
func getChanSummary(chanDetails kvdb.RBucket) (*ClientChanSummary, error) {
chanSummaryBytes := chanDetails.Get(cChannelSummary)
if chanSummaryBytes == nil {
return nil, ErrChannelNotRegistered
}
var summary ClientChanSummary
err := summary.Decode(bytes.NewReader(chanSummaryBytes))
if err != nil {
return nil, err
}
return &summary, nil
}
// putChanSummary stores a ClientChanSummary for the passed chanID.
func putChanSummary(chanDetails kvdb.RwBucket,
summary *ClientChanSummary) error {
var b bytes.Buffer
err := summary.Encode(&b)
if err != nil {
return err
}
return chanDetails.Put(cChannelSummary, b.Bytes())
}
// getTower loads a Tower identified by its serialized tower id.
func getTower(towers kvdb.RBucket, id []byte) (*Tower, error) {
towerBytes := towers.Get(id)
if towerBytes == nil {
return nil, ErrTowerNotFound
}
var tower Tower
err := tower.Decode(bytes.NewReader(towerBytes))
if err != nil {
return nil, err
}
tower.ID = TowerIDFromBytes(id)
return &tower, nil
}
// putTower stores a Tower identified by its serialized tower id.
func putTower(towers kvdb.RwBucket, tower *Tower) error {
var b bytes.Buffer
err := tower.Encode(&b)
if err != nil {
return err
}
return towers.Put(tower.ID.Bytes(), b.Bytes())
}
// getDBChanID returns the db-assigned channel ID for the given real channel ID.
// It returns both the uint64 and byte representation.
func getDBChanID(chanDetailsBkt kvdb.RBucket, chanID lnwire.ChannelID) (uint64,
[]byte, error) {
chanDetails := chanDetailsBkt.NestedReadBucket(chanID[:])
if chanDetails == nil {
return 0, nil, ErrChannelNotRegistered
}
idBytes := chanDetails.Get(cChanDBID)
if len(idBytes) == 0 {
return 0, nil, fmt.Errorf("no db-assigned ID found for "+
"channel ID %s", chanID)
}
id, err := readBigSize(idBytes)
if err != nil {
return 0, nil, err
}
return id, idBytes, nil
}
// getDBSessionID returns the db-assigned session ID for the given real session
// ID. It returns both the uint64 and byte representation.
func getDBSessionID(sessionsBkt kvdb.RBucket, sessionID SessionID) (uint64,
[]byte, error) {
sessionBkt := sessionsBkt.NestedReadBucket(sessionID[:])
if sessionBkt == nil {
return 0, nil, ErrClientSessionNotFound
}
idBytes := sessionBkt.Get(cSessionDBID)
if len(idBytes) == 0 {
return 0, nil, fmt.Errorf("no db-assigned ID found for "+
"session ID %s", sessionID)
}
id, err := readBigSize(idBytes)
if err != nil {
return 0, nil, err
}
return id, idBytes, nil
}
// maybeUpdateMaxCommitHeight updates the given channel details bucket with the
// given height if it is larger than the current max height stored for the
// channel.
func maybeUpdateMaxCommitHeight(tx kvdb.RwTx, backupID BackupID) error {
chanDetailsBkt := tx.ReadWriteBucket(cChanDetailsBkt)
if chanDetailsBkt == nil {
return ErrUninitializedDB
}
// If an entry for this channel does not exist in the channel details
// bucket then we exit here as this means that the channel has been
// closed.
chanDetails := chanDetailsBkt.NestedReadWriteBucket(backupID.ChanID[:])
if chanDetails == nil {
return nil
}
putHeight := func() error {
b, err := writeBigSize(backupID.CommitHeight)
if err != nil {
return err
}
return chanDetails.Put(
cChanMaxCommitmentHeight, b,
)
}
// Get current height.
heightBytes := chanDetails.Get(cChanMaxCommitmentHeight)
// The height might have not been set yet, in which case
// we can just write the new height.
if len(heightBytes) == 0 {
return putHeight()
}
// Otherwise, read in the current max commitment height for the channel.
currentHeight, err := readBigSize(heightBytes)
if err != nil {
return err
}
// If the new height is not larger than the current persisted height,
// then there is nothing left for us to do.
if backupID.CommitHeight <= currentHeight {
return nil
}
return putHeight()
}
func getRealSessionID(sessIDIndexBkt kvdb.RBucket, dbID uint64) (*SessionID,
error) {
dbIDBytes, err := writeBigSize(dbID)
if err != nil {
return nil, err
}
sessIDBytes := sessIDIndexBkt.Get(dbIDBytes)
if len(sessIDBytes) != SessionIDSize {
return nil, fmt.Errorf("session ID not found")
}
var sessID SessionID
copy(sessID[:], sessIDBytes)
return &sessID, nil
}
func getRealChannelID(chanIDIndexBkt kvdb.RBucket,
dbID uint64) (*lnwire.ChannelID, error) {
dbIDBytes, err := writeBigSize(dbID)
if err != nil {
return nil, err
}
chanIDBytes := chanIDIndexBkt.Get(dbIDBytes)
if len(chanIDBytes) != 32 { //nolint:gomnd
return nil, fmt.Errorf("channel ID not found")
}
var chanIDS lnwire.ChannelID
copy(chanIDS[:], chanIDBytes)
return &chanIDS, nil
}
// writeBigSize will encode the given uint64 as a BigSize byte slice.
func writeBigSize(i uint64) ([]byte, error) {
var b bytes.Buffer
err := tlv.WriteVarInt(&b, i, &[8]byte{})
if err != nil {
return nil, err
}
return b.Bytes(), nil
}
// readBigSize converts the given byte slice into a uint64 and assumes that the
// bytes slice is using BigSize encoding.
func readBigSize(b []byte) (uint64, error) {
r := bytes.NewReader(b)
i, err := tlv.ReadVarInt(r, &[8]byte{})
if err != nil {
return 0, err
}
return i, nil
}
package wtdb
import (
"fmt"
"io"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
)
// CSessionStatus is a bit-field representing the possible statuses of
// ClientSessions.
type CSessionStatus uint8
const (
// CSessionActive indicates that the ClientSession is active and can be
// used for backups.
CSessionActive CSessionStatus = 0
// CSessionTerminal indicates that the ClientSession is in a terminal
// state and cannot be used for backups.
CSessionTerminal CSessionStatus = 1
)
// ClientSession encapsulates a SessionInfo returned from a successful
// session negotiation, and also records the tower and ephemeral secret used for
// communicating with the tower.
type ClientSession struct {
// ID is the client's public key used when authenticating with the
// tower.
//
// NOTE: This value is not serialized with the body of the struct, it
// should be set and recovered as the ClientSession's key.
ID SessionID
ClientSessionBody
}
// ClientSessionBody represents the primary components of a ClientSession that
// are serialized together within the database. The CommittedUpdates and
// AckedUpdates are serialized in buckets separate from the body.
type ClientSessionBody struct {
// SeqNum is the next unallocated sequence number that can be sent to
// the tower.
SeqNum uint16
// TowerLastApplied the last last-applied the tower has echoed back.
TowerLastApplied uint16
// TowerID is the unique, db-assigned identifier that references the
// Tower with which the session is negotiated.
TowerID TowerID
// KeyIndex is the index of key locator used to derive the client's
// session key so that it can authenticate with the tower to update its
// session. In order to rederive the private key, the key locator should
// use the keychain.KeyFamilyTowerSession key family.
KeyIndex uint32
// Policy holds the negotiated session parameters.
Policy wtpolicy.Policy
// Status indicates the current state of the ClientSession.
Status CSessionStatus
// RewardPkScript is the pkscript that the tower's reward will be
// deposited to if a sweep transaction confirms and the sessions
// specifies a reward output.
RewardPkScript []byte
}
// Encode writes a ClientSessionBody to the passed io.Writer.
func (s *ClientSessionBody) Encode(w io.Writer) error {
return WriteElements(w,
s.SeqNum,
s.TowerLastApplied,
uint64(s.TowerID),
s.KeyIndex,
uint8(s.Status),
s.Policy,
s.RewardPkScript,
)
}
// Decode reads a ClientSessionBody from the passed io.Reader.
func (s *ClientSessionBody) Decode(r io.Reader) error {
var (
towerID uint64
status uint8
)
err := ReadElements(r,
&s.SeqNum,
&s.TowerLastApplied,
&towerID,
&s.KeyIndex,
&status,
&s.Policy,
&s.RewardPkScript,
)
if err != nil {
return err
}
s.TowerID = TowerID(towerID)
s.Status = CSessionStatus(status)
return nil
}
// BackupID identifies a particular revoked, remote commitment by channel id and
// commitment height.
type BackupID struct {
// ChanID is the channel id of the revoked commitment.
ChanID lnwire.ChannelID
// CommitHeight is the commitment height of the revoked commitment.
CommitHeight uint64
}
// Encode writes the BackupID from the passed io.Writer.
func (b *BackupID) Encode(w io.Writer) error {
return WriteElements(w,
b.ChanID,
b.CommitHeight,
)
}
// Decode reads a BackupID from the passed io.Reader.
func (b *BackupID) Decode(r io.Reader) error {
return ReadElements(r,
&b.ChanID,
&b.CommitHeight,
)
}
// String returns a human-readable encoding of a BackupID.
func (b BackupID) String() string {
return fmt.Sprintf("backup(%v, %d)", b.ChanID, b.CommitHeight)
}
// CommittedUpdate holds a state update sent by a client along with its
// allocated sequence number and the exact remote commitment the encrypted
// justice transaction can rectify.
type CommittedUpdate struct {
// SeqNum is the unique sequence number allocated by the session to this
// update.
SeqNum uint16
CommittedUpdateBody
}
// CommittedUpdateBody represents the primary components of a CommittedUpdate.
// On disk, this is stored under the sequence number, which acts as its key.
type CommittedUpdateBody struct {
// BackupID identifies the breached commitment that the encrypted blob
// can spend from.
BackupID BackupID
// Hint is the 16-byte prefix of the revoked commitment transaction ID.
Hint blob.BreachHint
// EncryptedBlob is a ciphertext containing the sweep information for
// exacting justice if the commitment transaction matching the breach
// hint is broadcast.
EncryptedBlob []byte
}
// Encode writes the CommittedUpdateBody to the passed io.Writer.
func (u *CommittedUpdateBody) Encode(w io.Writer) error {
err := u.BackupID.Encode(w)
if err != nil {
return err
}
return WriteElements(w,
u.Hint,
u.EncryptedBlob,
)
}
// Decode reads a CommittedUpdateBody from the passed io.Reader.
func (u *CommittedUpdateBody) Decode(r io.Reader) error {
err := u.BackupID.Decode(r)
if err != nil {
return err
}
return ReadElements(r,
&u.Hint,
&u.EncryptedBlob,
)
}
package wtdb
import (
"io"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
)
// UnknownElementType is an alias for channeldb.UnknownElementType.
type UnknownElementType = channeldb.UnknownElementType
// ReadElement deserializes a single element from the provided io.Reader.
func ReadElement(r io.Reader, element interface{}) error {
err := channeldb.ReadElement(r, element)
switch {
// Known to channeldb codec.
case err == nil:
return nil
// Fail if error is not UnknownElementType.
default:
if _, ok := err.(UnknownElementType); !ok {
return err
}
}
// Process any wtdb-specific extensions to the codec.
switch e := element.(type) {
case *SessionID:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *blob.BreachHint:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *wtpolicy.Policy:
var (
blobType uint16
sweepFeeRate uint64
)
err := channeldb.ReadElements(r,
&blobType,
&e.MaxUpdates,
&e.RewardBase,
&e.RewardRate,
&sweepFeeRate,
)
if err != nil {
return err
}
e.BlobType = blob.Type(blobType)
e.SweepFeeRate = chainfee.SatPerKWeight(sweepFeeRate)
// Type is still unknown to wtdb extensions, fail.
default:
return channeldb.NewUnknownElementType(
"ReadElement", element,
)
}
return nil
}
// WriteElement serializes a single element into the provided io.Writer.
func WriteElement(w io.Writer, element interface{}) error {
err := channeldb.WriteElement(w, element)
switch {
// Known to channeldb codec.
case err == nil:
return nil
// Fail if error is not UnknownElementType.
default:
if _, ok := err.(UnknownElementType); !ok {
return err
}
}
// Process any wtdb-specific extensions to the codec.
switch e := element.(type) {
case SessionID:
if _, err := w.Write(e[:]); err != nil {
return err
}
case blob.BreachHint:
if _, err := w.Write(e[:]); err != nil {
return err
}
case wtpolicy.Policy:
return channeldb.WriteElements(w,
uint16(e.BlobType),
e.MaxUpdates,
e.RewardBase,
e.RewardRate,
uint64(e.SweepFeeRate),
)
// Type is still unknown to wtdb extensions, fail.
default:
return channeldb.NewUnknownElementType(
"WriteElement", element,
)
}
return nil
}
// WriteElements serializes a variadic list of elements into the given
// io.Writer.
func WriteElements(w io.Writer, elements ...interface{}) error {
for _, element := range elements {
if err := WriteElement(w, element); err != nil {
return err
}
}
return nil
}
// ReadElements deserializes the provided io.Reader into a variadic list of
// target elements.
func ReadElements(r io.Reader, elements ...interface{}) error {
for _, element := range elements {
if err := ReadElement(r, element); err != nil {
return err
}
}
return nil
}
package wtdb
import (
"encoding/binary"
"errors"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
// metadataBkt stores all the meta information concerning the state of
// the database.
metadataBkt = []byte("metadata-bucket")
// dbVersionKey is a static key used to retrieve the database version
// number from the metadataBkt.
dbVersionKey = []byte("version")
// ErrUninitializedDB signals that top-level buckets for the database
// have not been initialized.
ErrUninitializedDB = errors.New("db not initialized")
// ErrNoDBVersion signals that the database contains no version info.
ErrNoDBVersion = errors.New("db has no version")
// byteOrder is the default endianness used when serializing integers.
byteOrder = binary.BigEndian
)
// isFirstInit returns true if the given database has not yet been initialized,
// e.g. no metadata bucket is present yet.
func isFirstInit(db kvdb.Backend) (bool, error) {
var metadataExists bool
err := kvdb.View(db, func(tx kvdb.RTx) error {
metadataExists = tx.ReadBucket(metadataBkt) != nil
return nil
}, func() {
metadataExists = false
})
if err != nil {
return false, err
}
return !metadataExists, nil
}
package wtdb
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration1"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration2"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration3"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration4"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration5"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration6"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration7"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration8"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("WTDB", nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
migration1.UseLogger(logger)
migration2.UseLogger(logger)
migration3.UseLogger(logger)
migration4.UseLogger(logger)
migration5.UseLogger(logger)
migration6.UseLogger(logger)
migration7.UseLogger(logger)
migration8.UseLogger(logger)
}
package migration1
import (
"bytes"
"errors"
"fmt"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
// cSessionBkt is a top-level bucket storing:
// session-id => cSessionBody -> encoded ClientSessionBody
// => cSessionCommits => seqnum -> encoded CommittedUpdate
// => cSessionAcks => seqnum -> encoded BackupID
cSessionBkt = []byte("client-session-bucket")
// cSessionBody is a sub-bucket of cSessionBkt storing only the body of
// the ClientSession.
cSessionBody = []byte("client-session-body")
// cTowerIDToSessionIDIndexBkt is a top-level bucket storing:
// tower-id -> session-id -> 1
cTowerIDToSessionIDIndexBkt = []byte(
"client-tower-to-session-index-bucket",
)
// ErrUninitializedDB signals that top-level buckets for the database
// have not been initialized.
ErrUninitializedDB = errors.New("db not initialized")
// ErrClientSessionNotFound signals that the requested client session
// was not found in the database.
ErrClientSessionNotFound = errors.New("client session not found")
// ErrCorruptClientSession signals that the client session's on-disk
// structure deviates from what is expected.
ErrCorruptClientSession = errors.New("client session corrupted")
)
// MigrateTowerToSessionIndex constructs a new towerID-to-sessionID for the
// watchtower client DB.
func MigrateTowerToSessionIndex(tx kvdb.RwTx) error {
log.Infof("Migrating the tower client db to add a " +
"towerID-to-sessionID index")
// First, we collect all the entries we want to add to the index.
entries, err := getIndexEntries(tx)
if err != nil {
return err
}
// Then we create a new top-level bucket for the index.
indexBkt, err := tx.CreateTopLevelBucket(cTowerIDToSessionIDIndexBkt)
if err != nil {
return err
}
// Finally, we add all the collected entries to the index.
for towerID, sessions := range entries {
// Create a sub-bucket using the tower ID.
towerBkt, err := indexBkt.CreateBucketIfNotExists(
towerID.Bytes(),
)
if err != nil {
return err
}
for sessionID := range sessions {
err := addIndex(towerBkt, sessionID)
if err != nil {
return err
}
}
}
return nil
}
// addIndex adds a new towerID-sessionID pair to the given bucket. The
// session ID is used as a key within the bucket and a value of []byte{1} is
// used for each session ID key.
func addIndex(towerBkt kvdb.RwBucket, sessionID SessionID) error {
session := towerBkt.Get(sessionID[:])
if session != nil {
return fmt.Errorf("session %x duplicated", sessionID)
}
return towerBkt.Put(sessionID[:], []byte{1})
}
// getIndexEntries collects all the towerID-sessionID entries that need to be
// added to the new index.
func getIndexEntries(tx kvdb.RwTx) (map[TowerID]map[SessionID]bool, error) {
sessions := tx.ReadBucket(cSessionBkt)
if sessions == nil {
return nil, ErrUninitializedDB
}
index := make(map[TowerID]map[SessionID]bool)
err := sessions.ForEach(func(k, _ []byte) error {
session, err := getClientSession(sessions, k)
if err != nil {
return err
}
if index[session.TowerID] == nil {
index[session.TowerID] = make(map[SessionID]bool)
}
index[session.TowerID][session.ID] = true
return nil
})
if err != nil {
return nil, err
}
return index, nil
}
// getClientSession fetches the session with the given ID from the db.
func getClientSession(sessions kvdb.RBucket, idBytes []byte) (*ClientSession,
error) {
sessionBkt := sessions.NestedReadBucket(idBytes)
if sessionBkt == nil {
return nil, ErrClientSessionNotFound
}
// Should never have a sessionBkt without also having its body.
sessionBody := sessionBkt.Get(cSessionBody)
if sessionBody == nil {
return nil, ErrCorruptClientSession
}
var session ClientSession
copy(session.ID[:], idBytes)
err := session.Decode(bytes.NewReader(sessionBody))
if err != nil {
return nil, err
}
return &session, nil
}
package migration1
import (
"encoding/binary"
"io"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
)
// SessionIDSize is 33-bytes; it is a serialized, compressed public key.
const SessionIDSize = 33
// UnknownElementType is an alias for channeldb.UnknownElementType.
type UnknownElementType = channeldb.UnknownElementType
// SessionID is created from the remote public key of a client, and serves as a
// unique identifier and authentication for sending state updates.
type SessionID [SessionIDSize]byte
// TowerID is a unique 64-bit identifier allocated to each unique watchtower.
// This allows the client to conserve on-disk space by not needing to always
// reference towers by their pubkey.
type TowerID uint64
// Bytes encodes a TowerID into an 8-byte slice in big-endian byte order.
func (id TowerID) Bytes() []byte {
var buf [8]byte
binary.BigEndian.PutUint64(buf[:], uint64(id))
return buf[:]
}
// ClientSession encapsulates a SessionInfo returned from a successful
// session negotiation, and also records the tower and ephemeral secret used for
// communicating with the tower.
type ClientSession struct {
// ID is the client's public key used when authenticating with the
// tower.
ID SessionID
ClientSessionBody
}
// CSessionStatus is a bit-field representing the possible statuses of
// ClientSessions.
type CSessionStatus uint8
type ClientSessionBody struct {
// SeqNum is the next unallocated sequence number that can be sent to
// the tower.
SeqNum uint16
// TowerLastApplied the last last-applied the tower has echoed back.
TowerLastApplied uint16
// TowerID is the unique, db-assigned identifier that references the
// Tower with which the session is negotiated.
TowerID TowerID
// KeyIndex is the index of key locator used to derive the client's
// session key so that it can authenticate with the tower to update its
// session. In order to rederive the private key, the key locator should
// use the keychain.KeyFamilyTowerSession key family.
KeyIndex uint32
// Policy holds the negotiated session parameters.
Policy wtpolicy.Policy
// Status indicates the current state of the ClientSession.
Status CSessionStatus
// RewardPkScript is the pkscript that the tower's reward will be
// deposited to if a sweep transaction confirms and the sessions
// specifies a reward output.
RewardPkScript []byte
}
// Encode writes a ClientSessionBody to the passed io.Writer.
func (s *ClientSessionBody) Encode(w io.Writer) error {
return WriteElements(w,
s.SeqNum,
s.TowerLastApplied,
uint64(s.TowerID),
s.KeyIndex,
uint8(s.Status),
s.Policy,
s.RewardPkScript,
)
}
// Decode reads a ClientSessionBody from the passed io.Reader.
func (s *ClientSessionBody) Decode(r io.Reader) error {
var (
towerID uint64
status uint8
)
err := ReadElements(r,
&s.SeqNum,
&s.TowerLastApplied,
&towerID,
&s.KeyIndex,
&status,
&s.Policy,
&s.RewardPkScript,
)
if err != nil {
return err
}
s.TowerID = TowerID(towerID)
s.Status = CSessionStatus(status)
return nil
}
// WriteElements serializes a variadic list of elements into the given
// io.Writer.
func WriteElements(w io.Writer, elements ...interface{}) error {
for _, element := range elements {
if err := WriteElement(w, element); err != nil {
return err
}
}
return nil
}
// WriteElement serializes a single element into the provided io.Writer.
func WriteElement(w io.Writer, element interface{}) error {
err := channeldb.WriteElement(w, element)
switch {
// Known to channeldb codec.
case err == nil:
return nil
// Fail if error is not UnknownElementType.
default:
if _, ok := err.(UnknownElementType); !ok {
return err
}
}
// Process any wtdb-specific extensions to the codec.
switch e := element.(type) {
case SessionID:
if _, err := w.Write(e[:]); err != nil {
return err
}
case blob.BreachHint:
if _, err := w.Write(e[:]); err != nil {
return err
}
case wtpolicy.Policy:
return channeldb.WriteElements(w,
uint16(e.BlobType),
e.MaxUpdates,
e.RewardBase,
e.RewardRate,
uint64(e.SweepFeeRate),
)
// Type is still unknown to wtdb extensions, fail.
default:
return channeldb.NewUnknownElementType(
"WriteElement", element,
)
}
return nil
}
// ReadElements deserializes the provided io.Reader into a variadic list of
// target elements.
func ReadElements(r io.Reader, elements ...interface{}) error {
for _, element := range elements {
if err := ReadElement(r, element); err != nil {
return err
}
}
return nil
}
// ReadElement deserializes a single element from the provided io.Reader.
func ReadElement(r io.Reader, element interface{}) error {
err := channeldb.ReadElement(r, element)
switch {
// Known to channeldb codec.
case err == nil:
return nil
// Fail if error is not UnknownElementType.
default:
if _, ok := err.(UnknownElementType); !ok {
return err
}
}
// Process any wtdb-specific extensions to the codec.
switch e := element.(type) {
case *SessionID:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *blob.BreachHint:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *wtpolicy.Policy:
var (
blobType uint16
sweepFeeRate uint64
)
err := channeldb.ReadElements(r,
&blobType,
&e.MaxUpdates,
&e.RewardBase,
&e.RewardRate,
&sweepFeeRate,
)
if err != nil {
return err
}
e.BlobType = blob.Type(blobType)
e.SweepFeeRate = chainfee.SatPerKWeight(sweepFeeRate)
// Type is still unknown to wtdb extensions, fail.
default:
return channeldb.NewUnknownElementType(
"ReadElement", element,
)
}
return nil
}
package migration1
import (
"github.com/btcsuite/btclog/v2"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration2
import (
"errors"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
// cChanSummaryBkt is a top-level bucket storing:
// channel-id -> encoded ClientChanSummary.
cChanSummaryBkt = []byte("client-channel-summary-bucket")
// cChanDetailsBkt is a top-level bucket storing:
// channel-id => cChannelSummary -> encoded ClientChanSummary.
cChanDetailsBkt = []byte("client-channel-detail-bucket")
// cChannelSummary is a key used in cChanDetailsBkt to store the encoded
// body of ClientChanSummary.
cChannelSummary = []byte("client-channel-summary")
// ErrUninitializedDB signals that top-level buckets for the database
// have not been initialized.
ErrUninitializedDB = errors.New("db not initialized")
// ErrCorruptChanSummary signals that the clients channel summary's
// on-disk structure deviates from what is expected.
ErrCorruptChanSummary = errors.New("channel summary corrupted")
)
// MigrateClientChannelDetails creates a new channel-details bucket that uses
// channel IDs as sub-buckets where the channel summaries are moved to from the
// channel summary bucket. If the migration is successful then the channel
// summary bucket is deleted.
func MigrateClientChannelDetails(tx kvdb.RwTx) error {
log.Infof("Migrating the tower client db to move the channel " +
"summaries to the new channel-details bucket")
// Create the new top level cChanDetailsBkt.
chanDetailsBkt, err := tx.CreateTopLevelBucket(cChanDetailsBkt)
if err != nil {
return err
}
// Get the top-level channel summaries bucket.
chanSummaryBkt := tx.ReadWriteBucket(cChanSummaryBkt)
if chanSummaryBkt == nil {
return ErrUninitializedDB
}
// Iterate over the cChanSummaryBkt's keys. Each key is a channel-id.
// For each of these, create a new sub-bucket with this key in
// cChanDetailsBkt. In this sub-bucket, add the cChannelSummary key with
// the encoded ClientChanSummary as the value.
err = chanSummaryBkt.ForEach(func(chanID, summary []byte) error {
// Force the migration to fail if the summary is empty. This
// should never be the case, but it is added so that we can
// force the migration to fail in a test so that we can test
// that the db remains unaffected if a migration failure takes
// place.
if len(summary) == 0 {
return ErrCorruptChanSummary
}
// Create a new sub-bucket in the channel details bucket using
// this channel ID.
channelBkt, err := chanDetailsBkt.CreateBucket(chanID)
if err != nil {
return err
}
// Add the encoded channel summary in the new bucket under the
// channel-summary key.
return channelBkt.Put(cChannelSummary, summary)
})
if err != nil {
return err
}
// Now delete the cChanSummaryBkt from the DB.
return tx.DeleteTopLevelBucket(cChanSummaryBkt)
}
package migration2
import "encoding/hex"
// ChannelID is a series of 32-bytes that uniquely identifies all channels
// within the network. The ChannelID is computed using the outpoint of the
// funding transaction (the txid, and output index). Given a funding output the
// ChannelID can be calculated by XOR'ing the big-endian serialization of the
// txid and the big-endian serialization of the output index, truncated to
// 2 bytes.
type ChannelID [32]byte
// String returns the string representation of the ChannelID. This is just the
// hex string encoding of the ChannelID itself.
func (c ChannelID) String() string {
return hex.EncodeToString(c[:])
}
package migration2
import (
"github.com/btcsuite/btclog/v2"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration3
import (
"bytes"
"encoding/binary"
"errors"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/tlv"
)
var (
// cChanDetailsBkt is a top-level bucket storing:
// channel-id => cChannelSummary -> encoded ClientChanSummary.
// => cChanDBID -> db-assigned-id
cChanDetailsBkt = []byte("client-channel-detail-bucket")
// cChanDBID is a key used in the cChanDetailsBkt to store the
// db-assigned-id of a channel.
cChanDBID = []byte("client-channel-db-id")
// cChanIDIndexBkt is a top-level bucket storing:
// db-assigned-id -> channel-ID
cChanIDIndexBkt = []byte("client-channel-id-index")
// cChannelSummary is a key used in cChanDetailsBkt to store the encoded
// body of ClientChanSummary.
cChannelSummary = []byte("client-channel-summary")
// ErrUninitializedDB signals that top-level buckets for the database
// have not been initialized.
ErrUninitializedDB = errors.New("db not initialized")
// ErrCorruptChanDetails signals that the clients channel detail's
// on-disk structure deviates from what is expected.
ErrCorruptChanDetails = errors.New("channel details corrupted")
byteOrder = binary.BigEndian
)
// MigrateChannelIDIndex adds a new channel ID index to the tower client db.
// This index is a mapping from db-assigned ID (a uint64 encoded using BigSize
// encoding) to real channel ID (32 bytes). This mapping will allow us to
// persist channel pointers with fewer bytes in the future.
func MigrateChannelIDIndex(tx kvdb.RwTx) error {
log.Infof("Migrating the tower client db to add a new channel ID " +
"index which stores a mapping from db-assigned ID to real " +
"channel ID")
// Create a new top-level bucket for the new index.
indexBkt, err := tx.CreateTopLevelBucket(cChanIDIndexBkt)
if err != nil {
return err
}
// Get the top-level channel-details bucket. The keys of this bucket
// are the real channel IDs.
chanDetailsBkt := tx.ReadWriteBucket(cChanDetailsBkt)
if chanDetailsBkt == nil {
return ErrUninitializedDB
}
// Iterate over the keys of the channel-details bucket.
return chanDetailsBkt.ForEach(func(chanID, _ []byte) error {
// Ask the db for a new, unique, ID for the index bucket.
nextSeq, err := indexBkt.NextSequence()
if err != nil {
return err
}
// Encode the sequence number using BigSize encoding.
var newIndex bytes.Buffer
err = tlv.WriteVarInt(&newIndex, nextSeq, &[8]byte{})
if err != nil {
return err
}
// Add the mapping from the db-assigned ID to the channel ID
// to the new index.
newIndexBytes := newIndex.Bytes()
err = indexBkt.Put(newIndexBytes, chanID)
if err != nil {
return err
}
chanDetails := chanDetailsBkt.NestedReadWriteBucket(chanID)
if chanDetails == nil {
return ErrCorruptChanDetails
}
// Here we ensure that the channel-details bucket includes a
// channel summary. The only reason we do this is so that we can
// simulate a migration fail in a test to ensure that a
// migration fail results in an untouched db.
chanSummaryBytes := chanDetails.Get(cChannelSummary)
if chanSummaryBytes == nil {
return ErrCorruptChanDetails
}
// In the channel-details sub-bucket for this channel, add the
// new DB-assigned ID for this channel under the cChanDBID key.
return chanDetails.Put(cChanDBID, newIndexBytes)
})
}
package migration3
import (
"encoding/hex"
)
// ChannelID is a series of 32-bytes that uniquely identifies all channels
// within the network. The ChannelID is computed using the outpoint of the
// funding transaction (the txid, and output index). Given a funding output the
// ChannelID can be calculated by XOR'ing the big-endian serialization of the
// txid and the big-endian serialization of the output index, truncated to
// 2 bytes.
type ChannelID [32]byte
// String returns the string representation of the ChannelID. This is just the
// hex string encoding of the ChannelID itself.
func (c ChannelID) String() string {
return hex.EncodeToString(c[:])
}
package migration3
import (
"github.com/btcsuite/btclog/v2"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration4
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/tlv"
)
var (
// cChanDetailsBkt is a top-level bucket storing:
// channel-id => cChannelSummary -> encoded ClientChanSummary.
// => cChanDBID -> db-assigned-id
cChanDetailsBkt = []byte("client-channel-detail-bucket")
// cChanDBID is a key used in the cChanDetailsBkt to store the
// db-assigned-id of a channel.
cChanDBID = []byte("client-channel-db-id")
// cSessionBkt is a top-level bucket storing:
// session-id => cSessionBody -> encoded ClientSessionBody
// => cSessionCommits => seqnum -> encoded CommittedUpdate
// => cSessionAcks => seqnum -> encoded BackupID
cSessionBkt = []byte("client-session-bucket")
// cSessionAcks is a sub-bucket of cSessionBkt storing:
// seqnum -> encoded BackupID.
cSessionAcks = []byte("client-session-acks")
// cSessionAckRangeIndex is a sub-bucket of cSessionBkt storing:
// chan-id => start -> end
cSessionAckRangeIndex = []byte("client-session-ack-range-index")
// ErrUninitializedDB signals that top-level buckets for the database
// have not been initialized.
ErrUninitializedDB = errors.New("db not initialized")
// ErrClientSessionNotFound signals that the requested client session
// was not found in the database.
ErrClientSessionNotFound = errors.New("client session not found")
// ErrCorruptChanDetails signals that the clients channel detail's
// on-disk structure deviates from what is expected.
ErrCorruptChanDetails = errors.New("channel details corrupted")
// ErrChannelNotRegistered signals a channel has not yet been registered
// in the client database.
ErrChannelNotRegistered = errors.New("channel not registered")
// byteOrder is the default endianness used when serializing integers.
byteOrder = binary.BigEndian
// errExit is an error used to signal that the sessionIterator should
// exit.
errExit = errors.New("the exit condition has been met")
)
// DefaultSessionsPerTx is the default number of sessions that should be
// migrated per db transaction.
const DefaultSessionsPerTx = 5000
// MigrateAckedUpdates migrates the tower client DB. It takes the individual
// Acked Updates that are stored for each session and re-stores them using the
// RangeIndex representation.
func MigrateAckedUpdates(sessionsPerTx int) func(kvdb.Backend) error {
return func(db kvdb.Backend) error {
log.Infof("Migrating the tower client db to move all Acked " +
"Updates to the new Range Index representation.")
// Migrate the old acked-updates.
err := migrateAckedUpdates(db, sessionsPerTx)
if err != nil {
return fmt.Errorf("migration failed: %w", err)
}
log.Infof("Migrating old session acked updates finished, now " +
"checking the migration results...")
// Before we can safety delete the old buckets, we perform a
// check to make sure the sessions have been migrated as
// expected.
err = kvdb.View(db, validateMigration, func() {})
if err != nil {
return fmt.Errorf("validate migration failed: %w", err)
}
// Delete old acked updates.
err = kvdb.Update(db, deleteOldAckedUpdates, func() {})
if err != nil {
return fmt.Errorf("failed to delete old acked "+
"updates: %w", err)
}
return nil
}
}
// migrateAckedUpdates migrates the acked updates of each session in the
// wtclient db into the new RangeIndex form. This is done over multiple db
// transactions in order to prevent the migration from taking up too much RAM.
// The sessionsPerTx parameter can be used to set the maximum number of sessions
// that should be migrated per transaction.
func migrateAckedUpdates(db kvdb.Backend, sessionsPerTx int) error {
// Get migration progress stats.
total, migrated, err := logMigrationStats(db)
if err != nil {
return err
}
log.Infof("Total sessions=%d, migrated=%d", total, migrated)
// Exit early if the old session acked updates have already been
// migrated and deleted.
if total == 0 {
log.Info("Migration already finished!")
return nil
}
var (
finished bool
startKey []byte
)
for {
// Process the migration.
err = kvdb.Update(db, func(tx kvdb.RwTx) error {
startKey, finished, err = processMigration(
tx, startKey, sessionsPerTx,
)
return err
}, func() {})
if err != nil {
return err
}
if finished {
break
}
// Each time we finished the above process, we'd read the stats
// again to understand the current progress.
total, migrated, err = logMigrationStats(db)
if err != nil {
return err
}
// Calculate and log the progress if the progress is less than
// one hundred percent.
progress := float64(migrated) / float64(total) * 100
if progress >= 100 {
break
}
log.Infof("Migration progress: %.3f%%, still have: %d",
progress, total-migrated)
}
return nil
}
func validateMigration(tx kvdb.RTx) error {
mainSessionsBkt := tx.ReadBucket(cSessionBkt)
if mainSessionsBkt == nil {
return ErrUninitializedDB
}
chanDetailsBkt := tx.ReadBucket(cChanDetailsBkt)
if chanDetailsBkt == nil {
return ErrUninitializedDB
}
return mainSessionsBkt.ForEach(func(sessID, _ []byte) error {
// Get the bucket for this particular session.
sessionBkt := mainSessionsBkt.NestedReadBucket(sessID)
if sessionBkt == nil {
return ErrClientSessionNotFound
}
// Get the bucket where any old acked updates would be stored.
oldAcksBucket := sessionBkt.NestedReadBucket(cSessionAcks)
// Get the bucket where any new acked updates would be stored.
newAcksBucket := sessionBkt.NestedReadBucket(
cSessionAckRangeIndex,
)
switch {
// If both the old and new acked updates buckets are nil, then
// we can safely skip this session.
case oldAcksBucket == nil && newAcksBucket == nil:
return nil
case oldAcksBucket == nil:
return fmt.Errorf("no old acks but do have new acks")
case newAcksBucket == nil:
return fmt.Errorf("no new acks but have old acks")
default:
}
// Collect acked ranges for this session.
ackedRanges := make(map[uint64]*RangeIndex)
err := newAcksBucket.ForEach(func(dbChanID, _ []byte) error {
rangeIndexBkt := newAcksBucket.NestedReadBucket(
dbChanID,
)
if rangeIndexBkt == nil {
return fmt.Errorf("no acked updates bucket "+
"found for channel %x", dbChanID)
}
// Read acked ranges from new bucket.
ri, err := readRangeIndex(rangeIndexBkt)
if err != nil {
return err
}
dbChanIDNum, err := readBigSize(dbChanID)
if err != nil {
return err
}
ackedRanges[dbChanIDNum] = ri
return nil
})
if err != nil {
return err
}
// Now we will iterate through each of the old acked updates and
// make sure that the update appears in the new bucket.
return oldAcksBucket.ForEach(func(_, v []byte) error {
var backupID BackupID
err := backupID.Decode(bytes.NewReader(v))
if err != nil {
return err
}
dbChanID, _, err := getDBChanID(
chanDetailsBkt, backupID.ChanID,
)
if err != nil {
return err
}
index, ok := ackedRanges[dbChanID]
if !ok {
return fmt.Errorf("no index found for this " +
"channel")
}
if !index.IsInIndex(backupID.CommitHeight) {
return fmt.Errorf("commit height not found " +
"in index")
}
return nil
})
})
}
func readRangeIndex(rangesBkt kvdb.RBucket) (*RangeIndex, error) {
ranges := make(map[uint64]uint64)
err := rangesBkt.ForEach(func(k, v []byte) error {
start, err := readBigSize(k)
if err != nil {
return err
}
end, err := readBigSize(v)
if err != nil {
return err
}
ranges[start] = end
return nil
})
if err != nil {
return nil, err
}
return NewRangeIndex(ranges, WithSerializeUint64Fn(writeBigSize))
}
func deleteOldAckedUpdates(tx kvdb.RwTx) error {
mainSessionsBkt := tx.ReadWriteBucket(cSessionBkt)
if mainSessionsBkt == nil {
return ErrUninitializedDB
}
return mainSessionsBkt.ForEach(func(sessID, _ []byte) error {
// Get the bucket for this particular session.
sessionBkt := mainSessionsBkt.NestedReadWriteBucket(
sessID,
)
if sessionBkt == nil {
return ErrClientSessionNotFound
}
// Get the bucket where any old acked updates would be stored.
oldAcksBucket := sessionBkt.NestedReadBucket(cSessionAcks)
if oldAcksBucket == nil {
return nil
}
// Now that we have read everything that we need to from
// the cSessionAcks sub-bucket, we can delete it.
return sessionBkt.DeleteNestedBucket(cSessionAcks)
})
}
// processMigration uses the given transaction to perform a maximum of
// sessionsPerTx session migrations. If startKey is non-nil, it is used to
// determine the first session to start the migration at. The first return
// item is the key of the last session that was migrated successfully and the
// boolean is true if there are no more sessions left to migrate.
func processMigration(tx kvdb.RwTx, startKey []byte, sessionsPerTx int) ([]byte,
bool, error) {
chanDetailsBkt := tx.ReadWriteBucket(cChanDetailsBkt)
if chanDetailsBkt == nil {
return nil, false, ErrUninitializedDB
}
// sessionCount keeps track of the number of sessions that have been
// migrated under the current db transaction.
var sessionCount int
// migrateSessionCB is a callback function that calls migrateSession
// in order to migrate a single session. Upon success, the sessionCount
// is incremented and is then compared against sessionsPerTx to
// determine if we should continue migrating more sessions in this db
// transaction.
migrateSessionCB := func(sessionBkt kvdb.RwBucket) error {
err := migrateSession(chanDetailsBkt, sessionBkt)
if err != nil {
return err
}
sessionCount++
// If we have migrated sessionsPerTx sessions in this tx, then
// we return errExit in order to signal that this tx should be
// committed and the migration should be continued in a new
// transaction.
if sessionCount >= sessionsPerTx {
return errExit
}
return nil
}
// Starting at startKey, iterate over the sessions in the db and migrate
// them until either all are migrated or until the errExit signal is
// received.
lastKey, err := sessionIterator(tx, startKey, migrateSessionCB)
if err != nil && errors.Is(err, errExit) {
return lastKey, false, nil
} else if err != nil {
return nil, false, err
}
// The migration is complete.
return nil, true, nil
}
// migrateSession migrates a single session's acked-updates to the new
// RangeIndex form.
func migrateSession(chanDetailsBkt kvdb.RBucket,
sessionBkt kvdb.RwBucket) error {
// Get the existing cSessionAcks bucket. If there is no such bucket,
// then there are no acked-updates to migrate for this session.
sessionAcks := sessionBkt.NestedReadBucket(cSessionAcks)
if sessionAcks == nil {
return nil
}
// If there is already a new cSessionAckedRangeIndex bucket, then this
// session has already been migrated.
sessionAckRangesBkt := sessionBkt.NestedReadBucket(
cSessionAckRangeIndex,
)
if sessionAckRangesBkt != nil {
return nil
}
// Otherwise, we will iterate over each of the acked-updates, and we
// will construct a new RangeIndex for each channel.
m := make(map[ChannelID]*RangeIndex)
if err := sessionAcks.ForEach(func(_, v []byte) error {
var backupID BackupID
err := backupID.Decode(bytes.NewReader(v))
if err != nil {
return err
}
if _, ok := m[backupID.ChanID]; !ok {
index, err := NewRangeIndex(nil)
if err != nil {
return err
}
m[backupID.ChanID] = index
}
return m[backupID.ChanID].Add(backupID.CommitHeight, nil)
}); err != nil {
return err
}
// Create a new sub-bucket that will be used to store the new RangeIndex
// representation of the acked updates.
ackRangeBkt, err := sessionBkt.CreateBucket(cSessionAckRangeIndex)
if err != nil {
return err
}
// Iterate over each of the new range indexes that we will add for this
// session.
for chanID, rangeIndex := range m {
// Get db chanID.
chanDetails := chanDetailsBkt.NestedReadBucket(chanID[:])
if chanDetails == nil {
return ErrCorruptChanDetails
}
// Create a sub-bucket for this channel using the db-assigned ID
// for the channel.
dbChanID := chanDetails.Get(cChanDBID)
chanAcksBkt, err := ackRangeBkt.CreateBucket(dbChanID)
if err != nil {
return err
}
// Iterate over the range pairs that we need to add to the DB.
for k, v := range rangeIndex.GetAllRanges() {
start, err := writeBigSize(k)
if err != nil {
return err
}
end, err := writeBigSize(v)
if err != nil {
return err
}
err = chanAcksBkt.Put(start, end)
if err != nil {
return err
}
}
}
return nil
}
// logMigrationStats reads the buckets to provide stats over current migration
// progress. The returned values are the numbers of total records and already
// migrated records.
func logMigrationStats(db kvdb.Backend) (uint64, uint64, error) {
var (
err error
total uint64
unmigrated uint64
)
err = kvdb.View(db, func(tx kvdb.RTx) error {
total, unmigrated, err = getMigrationStats(tx)
return err
}, func() {})
log.Debugf("Total sessions=%d, unmigrated=%d", total, unmigrated)
return total, total - unmigrated, err
}
// getMigrationStats iterates over all sessions. It counts the total number of
// sessions as well as the total number of unmigrated sessions.
func getMigrationStats(tx kvdb.RTx) (uint64, uint64, error) {
var (
total uint64
unmigrated uint64
)
// Get sessions bucket.
mainSessionsBkt := tx.ReadBucket(cSessionBkt)
if mainSessionsBkt == nil {
return 0, 0, ErrUninitializedDB
}
// Iterate over each session ID in the bucket.
err := mainSessionsBkt.ForEach(func(sessID, _ []byte) error {
// Get the bucket for this particular session.
sessionBkt := mainSessionsBkt.NestedReadBucket(sessID)
if sessionBkt == nil {
return ErrClientSessionNotFound
}
total++
// Get the cSessionAckRangeIndex bucket.
sessionAcksBkt := sessionBkt.NestedReadBucket(cSessionAcks)
// Get the cSessionAckRangeIndex bucket.
sessionAckRangesBkt := sessionBkt.NestedReadBucket(
cSessionAckRangeIndex,
)
// If both buckets do not exist, then this session is empty and
// does not need to be migrated.
if sessionAckRangesBkt == nil && sessionAcksBkt == nil {
return nil
}
// If the sessionAckRangesBkt is not nil, then the session has
// already been migrated.
if sessionAckRangesBkt != nil {
return nil
}
// Else the session has not yet been migrated.
unmigrated++
return nil
})
if err != nil {
return 0, 0, err
}
return total, unmigrated, nil
}
// getDBChanID returns the db-assigned channel ID for the given real channel ID.
// It returns both the uint64 and byte representation.
func getDBChanID(chanDetailsBkt kvdb.RBucket, chanID ChannelID) (uint64,
[]byte, error) {
chanDetails := chanDetailsBkt.NestedReadBucket(chanID[:])
if chanDetails == nil {
return 0, nil, ErrChannelNotRegistered
}
idBytes := chanDetails.Get(cChanDBID)
if len(idBytes) == 0 {
return 0, nil, fmt.Errorf("no db-assigned ID found for "+
"channel ID %s", chanID)
}
id, err := readBigSize(idBytes)
if err != nil {
return 0, nil, err
}
return id, idBytes, nil
}
// callback defines a type that's used by the sessionIterator.
type callback func(bkt kvdb.RwBucket) error
// sessionIterator is a helper function that iterates over the main sessions
// bucket and performs the callback function on each individual session. If a
// seeker is specified, it will move the cursor to the given position otherwise
// it will start from the first item.
func sessionIterator(tx kvdb.RwTx, seeker []byte, cb callback) ([]byte, error) {
// Get sessions bucket.
mainSessionsBkt := tx.ReadWriteBucket(cSessionBkt)
if mainSessionsBkt == nil {
return nil, ErrUninitializedDB
}
c := mainSessionsBkt.ReadCursor()
k, _ := c.First()
// Move the cursor to the specified position if seeker is non-nil.
if seeker != nil {
k, _ = c.Seek(seeker)
}
// Start the iteration and exit on condition.
for k := k; k != nil; k, _ = c.Next() {
// Get the bucket for this particular session.
bkt := mainSessionsBkt.NestedReadWriteBucket(k)
if bkt == nil {
return nil, ErrClientSessionNotFound
}
// Call the callback function with the session's bucket.
if err := cb(bkt); err != nil {
// return k, err
lastIndex := make([]byte, len(k))
copy(lastIndex, k)
return lastIndex, err
}
}
return nil, nil
}
// writeBigSize will encode the given uint64 as a BigSize byte slice.
func writeBigSize(i uint64) ([]byte, error) {
var b bytes.Buffer
err := tlv.WriteVarInt(&b, i, &[8]byte{})
if err != nil {
return nil, err
}
return b.Bytes(), nil
}
// readBigSize converts the given byte slice into a uint64 and assumes that the
// bytes slice is using BigSize encoding.
func readBigSize(b []byte) (uint64, error) {
r := bytes.NewReader(b)
i, err := tlv.ReadVarInt(r, &[8]byte{})
if err != nil {
return 0, err
}
return i, nil
}
package migration4
import (
"encoding/binary"
"encoding/hex"
"fmt"
"io"
)
// SessionIDSize is 33-bytes; it is a serialized, compressed public key.
const SessionIDSize = 33
// SessionID is created from the remote public key of a client, and serves as a
// unique identifier and authentication for sending state updates.
type SessionID [SessionIDSize]byte
// String returns a hex encoding of the session id.
func (s SessionID) String() string {
return hex.EncodeToString(s[:])
}
// ChannelID is a series of 32-bytes that uniquely identifies all channels
// within the network. The ChannelID is computed using the outpoint of the
// funding transaction (the txid, and output index). Given a funding output the
// ChannelID can be calculated by XOR'ing the big-endian serialization of the
// txid and the big-endian serialization of the output index, truncated to
// 2 bytes.
type ChannelID [32]byte
// String returns the string representation of the ChannelID. This is just the
// hex string encoding of the ChannelID itself.
func (c ChannelID) String() string {
return hex.EncodeToString(c[:])
}
// BackupID identifies a particular revoked, remote commitment by channel id and
// commitment height.
type BackupID struct {
// ChanID is the channel id of the revoked commitment.
ChanID ChannelID
// CommitHeight is the commitment height of the revoked commitment.
CommitHeight uint64
}
// Encode writes the BackupID from the passed io.Writer.
func (b *BackupID) Encode(w io.Writer) error {
return WriteElements(w,
b.ChanID,
b.CommitHeight,
)
}
// Decode reads a BackupID from the passed io.Reader.
func (b *BackupID) Decode(r io.Reader) error {
return ReadElements(r,
&b.ChanID,
&b.CommitHeight,
)
}
// String returns a human-readable encoding of a BackupID.
func (b BackupID) String() string {
return fmt.Sprintf("backup(%v, %d)", b.ChanID, b.CommitHeight)
}
// WriteElements serializes a variadic list of elements into the given
// io.Writer.
func WriteElements(w io.Writer, elements ...interface{}) error {
for _, element := range elements {
if err := WriteElement(w, element); err != nil {
return err
}
}
return nil
}
// ReadElements deserializes the provided io.Reader into a variadic list of
// target elements.
func ReadElements(r io.Reader, elements ...interface{}) error {
for _, element := range elements {
if err := ReadElement(r, element); err != nil {
return err
}
}
return nil
}
// WriteElement serializes a single element into the provided io.Writer.
func WriteElement(w io.Writer, element interface{}) error {
switch e := element.(type) {
case ChannelID:
if _, err := w.Write(e[:]); err != nil {
return err
}
case uint64:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
default:
return fmt.Errorf("unexpected type")
}
return nil
}
// ReadElement deserializes a single element from the provided io.Reader.
func ReadElement(r io.Reader, element interface{}) error {
switch e := element.(type) {
case *ChannelID:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *uint64:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
default:
return fmt.Errorf("unexpected type")
}
return nil
}
package migration4
import (
"github.com/btcsuite/btclog/v2"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration4
import (
"fmt"
"sync"
)
// rangeItem represents the start and end values of a range.
type rangeItem struct {
start uint64
end uint64
}
// RangeIndexOption describes the signature of a functional option that can be
// used to modify the behaviour of a RangeIndex.
type RangeIndexOption func(*RangeIndex)
// WithSerializeUint64Fn is a functional option that can be used to set the
// function to be used to do the serialization of a uint64 into a byte slice.
func WithSerializeUint64Fn(fn func(uint64) ([]byte, error)) RangeIndexOption {
return func(index *RangeIndex) {
index.serializeUint64 = fn
}
}
// RangeIndex can be used to keep track of which numbers have been added to a
// set. It does so by keeping track of a sorted list of rangeItems. Each
// rangeItem has a start and end value of a range where all values in-between
// have been added to the set. It works well in situations where it is expected
// numbers in the set are not sparse.
type RangeIndex struct {
// set is a sorted list of rangeItem.
set []rangeItem
// mu is used to ensure safe access to set.
mu sync.Mutex
// serializeUint64 is the function that can be used to convert a uint64
// to a byte slice.
serializeUint64 func(uint64) ([]byte, error)
}
// NewRangeIndex constructs a new RangeIndex. An initial set of ranges may be
// passed to the function in the form of a map.
func NewRangeIndex(ranges map[uint64]uint64,
opts ...RangeIndexOption) (*RangeIndex, error) {
index := &RangeIndex{
serializeUint64: defaultSerializeUint64,
set: make([]rangeItem, 0),
}
// Apply any functional options.
for _, o := range opts {
o(index)
}
for s, e := range ranges {
if err := index.addRange(s, e); err != nil {
return nil, err
}
}
return index, nil
}
// addRange can be used to add an entire new range to the set. This method
// should only ever be called by NewRangeIndex to initialise the in-memory
// structure and so the RangeIndex mutex is not held during this method.
func (a *RangeIndex) addRange(start, end uint64) error {
// Check that the given range is valid.
if start > end {
return fmt.Errorf("invalid range. Start height %d is larger "+
"than end height %d", start, end)
}
// Collect the ranges that fall before and after the new range along
// with the start and end values of the new range.
var before, after []rangeItem
for _, x := range a.set {
// If the new start value can't extend the current ranges end
// value, then the two cannot be merged. The range is added to
// the group of ranges that fall before the new range.
if x.end+1 < start {
before = append(before, x)
continue
}
// If the current ranges start value does not follow on directly
// from the new end value, then the two cannot be merged. The
// range is added to the group of ranges that fall after the new
// range.
if end+1 < x.start {
after = append(after, x)
continue
}
// Otherwise, there is an overlap and so the two can be merged.
start = min(start, x.start)
end = max(end, x.end)
}
// Re-construct the range index set.
a.set = append(append(before, rangeItem{
start: start,
end: end,
}), after...)
return nil
}
// IsInIndex returns true if the given number is in the range set.
func (a *RangeIndex) IsInIndex(n uint64) bool {
a.mu.Lock()
defer a.mu.Unlock()
_, isCovered := a.lowerBoundIndex(n)
return isCovered
}
// NumInSet returns the number of items covered by the range set.
func (a *RangeIndex) NumInSet() uint64 {
a.mu.Lock()
defer a.mu.Unlock()
var numItems uint64
for _, r := range a.set {
numItems += r.end - r.start + 1
}
return numItems
}
// MaxHeight returns the highest number covered in the range.
func (a *RangeIndex) MaxHeight() uint64 {
a.mu.Lock()
defer a.mu.Unlock()
if len(a.set) == 0 {
return 0
}
return a.set[len(a.set)-1].end
}
// GetAllRanges returns a copy of the range set in the form of a map.
func (a *RangeIndex) GetAllRanges() map[uint64]uint64 {
a.mu.Lock()
defer a.mu.Unlock()
cp := make(map[uint64]uint64, len(a.set))
for _, item := range a.set {
cp[item.start] = item.end
}
return cp
}
// lowerBoundIndex returns the index of the RangeIndex that is most appropriate
// for the new value, n. In other words, it returns the index of the rangeItem
// set of the range where the start value is the highest start value in the set
// that is still lower than or equal to the given number, n. The returned
// boolean is true if the given number is already covered in the RangeIndex.
// A returned index of -1 indicates that no lower bound range exists in the set.
// Since the most likely case is that the new number will just extend the
// highest range, a check is first done to see if this is the case which will
// make the methods' computational complexity O(1). Otherwise, a binary search
// is done which brings the computational complexity to O(log N).
func (a *RangeIndex) lowerBoundIndex(n uint64) (int, bool) {
// If the set is empty, then there is no such index and the value
// definitely is not in the set.
if len(a.set) == 0 {
return -1, false
}
// In most cases, the last index item will be the one we want. So just
// do a quick check on that index first to avoid doing the binary
// search.
lastIndex := len(a.set) - 1
lastRange := a.set[lastIndex]
if lastRange.start <= n {
return lastIndex, lastRange.end >= n
}
// Otherwise, do a binary search to find the index of interest.
var (
low = 0
high = len(a.set) - 1
rangeIndex = -1
)
for {
mid := (low + high) / 2
currentRange := a.set[mid]
switch {
case currentRange.start > n:
// If the start of the range is greater than n, we can
// completely cut out that entire part of the array.
high = mid
case currentRange.start < n:
// If the range already includes the given height, we
// can stop searching now.
if currentRange.end >= n {
return mid, true
}
// If the start of the range is smaller than n, we can
// store this as the new best index to return.
rangeIndex = mid
// If low and mid are already equal, then increment low
// by 1. Exit if this means that low is now greater than
// high.
if low == mid {
low = mid + 1
if low > high {
return rangeIndex, false
}
} else {
low = mid
}
continue
default:
// If the height is equal to the start value of the
// current range that mid is pointing to, then the
// height is already covered.
return mid, true
}
// Exit if we have checked all the ranges.
if low == high {
break
}
}
return rangeIndex, false
}
// KVStore is an interface representing a key-value store.
type KVStore interface {
// Put saves the specified key/value pair to the store. Keys that do not
// already exist are added and keys that already exist are overwritten.
Put(key, value []byte) error
// Delete removes the specified key from the bucket. Deleting a key that
// does not exist does not return an error.
Delete(key []byte) error
}
// Add adds a single number to the range set. It first attempts to apply the
// necessary changes to the passed KV store and then only if this succeeds, will
// the changes be applied to the in-memory structure.
func (a *RangeIndex) Add(newHeight uint64, kv KVStore) error {
a.mu.Lock()
defer a.mu.Unlock()
// Compute the changes that will need to be applied to both the sorted
// rangeItem array representation and the key-value store representation
// of the range index.
arrayChanges, kvStoreChanges := a.getChanges(newHeight)
// First attempt to apply the KV store changes. Only if this succeeds
// will we apply the changes to our in-memory range index structure.
err := a.applyKVChanges(kv, kvStoreChanges)
if err != nil {
return err
}
// Since the DB changes were successful, we can now commit the
// changes to our in-memory representation of the range set.
a.applyArrayChanges(arrayChanges)
return nil
}
// applyKVChanges applies the given set of kvChanges to a KV store. It is
// assumed that a transaction is being held on the kv store so that if any
// of the actions of the function fails, the changes will be reverted.
func (a *RangeIndex) applyKVChanges(kv KVStore, changes *kvChanges) error {
// Exit early if there are no changes to apply.
if kv == nil || changes == nil {
return nil
}
// Check if any range pair needs to be deleted.
if changes.deleteKVKey != nil {
del, err := a.serializeUint64(*changes.deleteKVKey)
if err != nil {
return err
}
if err := kv.Delete(del); err != nil {
return err
}
}
start, err := a.serializeUint64(changes.key)
if err != nil {
return err
}
end, err := a.serializeUint64(changes.value)
if err != nil {
return err
}
return kv.Put(start, end)
}
// applyArrayChanges applies the given arrayChanges to the in-memory RangeIndex
// itself. This should only be done once the persisted kv store changes have
// already been applied.
func (a *RangeIndex) applyArrayChanges(changes *arrayChanges) {
if changes == nil {
return
}
if changes.indexToDelete != nil {
a.set = append(
a.set[:*changes.indexToDelete],
a.set[*changes.indexToDelete+1:]...,
)
}
if changes.newIndex != nil {
switch {
case *changes.newIndex == 0:
a.set = append([]rangeItem{{
start: changes.start,
end: changes.end,
}}, a.set...)
case *changes.newIndex == len(a.set):
a.set = append(a.set, rangeItem{
start: changes.start,
end: changes.end,
})
default:
a.set = append(
a.set[:*changes.newIndex+1],
a.set[*changes.newIndex:]...,
)
a.set[*changes.newIndex] = rangeItem{
start: changes.start,
end: changes.end,
}
}
return
}
if changes.indexToEdit != nil {
a.set[*changes.indexToEdit] = rangeItem{
start: changes.start,
end: changes.end,
}
}
}
// arrayChanges encompasses the diff to apply to the sorted rangeItem array
// representation of a range index. Such a diff will either include adding a
// new range or editing an existing range. If an existing range is edited, then
// the diff might also include deleting an index (this will be the case if the
// editing of the one range results in the merge of another range).
type arrayChanges struct {
start uint64
end uint64
// newIndex, if set, is the index of the in-memory range array where a
// new range, [start:end], should be added. newIndex should never be
// set at the same time as indexToEdit or indexToDelete.
newIndex *int
// indexToDelete, if set, is the index of the sorted rangeItem array
// that should be deleted. This should be applied before reading the
// index value of indexToEdit. This should not be set at the same time
// as newIndex.
indexToDelete *int
// indexToEdit is the index of the in-memory range array that should be
// edited. The range at this index will be changed to [start:end]. This
// should only be read after indexToDelete index has been deleted.
indexToEdit *int
}
// kvChanges encompasses the diff to apply to a KV-store representation of a
// range index. A kv-store diff for the addition of a single number to the range
// index will include either a brand new key-value pair or the altering of the
// value of an existing key. Optionally, the diff may also include the deletion
// of an existing key. A deletion will be required if the addition of the new
// number results in the merge of two ranges.
type kvChanges struct {
key uint64
value uint64
// deleteKVKey, if set, is the key of the kv store representation that
// should be deleted.
deleteKVKey *uint64
}
// getChanges will calculate and return the changes that need to be applied to
// both the sorted-rangeItem-array representation and the key-value store
// representation of the range index.
func (a *RangeIndex) getChanges(n uint64) (*arrayChanges, *kvChanges) {
// If the set is empty then a new range item is added.
if len(a.set) == 0 {
// For the array representation, a new range [n:n] is added to
// the first index of the array.
firstIndex := 0
ac := &arrayChanges{
newIndex: &firstIndex,
start: n,
end: n,
}
// For the KV representation, a new [n:n] pair is added.
kvc := &kvChanges{
key: n,
value: n,
}
return ac, kvc
}
// Find the index of the lower bound range to the new number.
indexOfRangeBelow, alreadyCovered := a.lowerBoundIndex(n)
switch {
// The new number is already covered by the range index. No changes are
// required.
case alreadyCovered:
return nil, nil
// No lower bound index exists.
case indexOfRangeBelow < 0:
// Check if the very first range can be merged into this new
// one.
if n+1 == a.set[0].start {
// If so, the two ranges can be merged and so the start
// value of the range is n and the end value is the end
// of the existing first range.
start := n
end := a.set[0].end
// For the array representation, we can just edit the
// first entry of the array
editIndex := 0
ac := &arrayChanges{
indexToEdit: &editIndex,
start: start,
end: end,
}
// For the KV store representation, we add a new kv pair
// and delete the range with the key equal to the start
// value of the range we are merging.
kvKeyToDelete := a.set[0].start
kvc := &kvChanges{
key: start,
value: end,
deleteKVKey: &kvKeyToDelete,
}
return ac, kvc
}
// Otherwise, we add a new index.
// For the array representation, a new range [n:n] is added to
// the first index of the array.
newIndex := 0
ac := &arrayChanges{
newIndex: &newIndex,
start: n,
end: n,
}
// For the KV representation, a new [n:n] pair is added.
kvc := &kvChanges{
key: n,
value: n,
}
return ac, kvc
// A lower range does exist, and it can be extended to include this new
// number.
case a.set[indexOfRangeBelow].end+1 == n:
start := a.set[indexOfRangeBelow].start
end := n
indexToChange := indexOfRangeBelow
// If there are no intervals above this one or if there are, but
// they can't be merged into this one then we just need to edit
// this interval.
if indexOfRangeBelow == len(a.set)-1 ||
a.set[indexOfRangeBelow+1].start != n+1 {
// For the array representation, we just edit the index.
ac := &arrayChanges{
indexToEdit: &indexToChange,
start: start,
end: end,
}
// For the key-value representation, we just overwrite
// the end value at the existing start key.
kvc := &kvChanges{
key: start,
value: end,
}
return ac, kvc
}
// There is a range above this one that we need to merge into
// this one.
delIndex := indexOfRangeBelow + 1
end = a.set[delIndex].end
// For the array representation, we delete the range above this
// one and edit this range to include the end value of the range
// above.
ac := &arrayChanges{
indexToDelete: &delIndex,
indexToEdit: &indexToChange,
start: start,
end: end,
}
// For the kv representation, we tweak the end value of an
// existing key and delete the key of the range we are deleting.
deleteKey := a.set[delIndex].start
kvc := &kvChanges{
key: start,
value: end,
deleteKVKey: &deleteKey,
}
return ac, kvc
// A lower range does exist, but it can't be extended to include this
// new number, and so we need to add a new range after the lower bound
// range.
default:
newIndex := indexOfRangeBelow + 1
// If there are no ranges above this new one or if there are,
// but they can't be merged into this new one, then we can just
// add the new one as is.
if newIndex == len(a.set) || a.set[newIndex].start != n+1 {
ac := &arrayChanges{
newIndex: &newIndex,
start: n,
end: n,
}
kvc := &kvChanges{
key: n,
value: n,
}
return ac, kvc
}
// Else, we merge the above index.
start := n
end := a.set[newIndex].end
toEdit := newIndex
// For the array representation, we edit the range above to
// include the new start value.
ac := &arrayChanges{
indexToEdit: &toEdit,
start: start,
end: end,
}
// For the kv representation, we insert the new start-end key
// value pair and delete the key using the old start value.
delKey := a.set[newIndex].start
kvc := &kvChanges{
key: start,
value: end,
deleteKVKey: &delKey,
}
return ac, kvc
}
}
func defaultSerializeUint64(i uint64) ([]byte, error) {
var b [8]byte
byteOrder.PutUint64(b[:], i)
return b[:], nil
}
package migration5
import (
"encoding/binary"
"errors"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
// cTowerBkt is a top-level bucket storing:
// tower-id -> encoded Tower.
cTowerBkt = []byte("client-tower-bucket")
// cTowerIDToSessionIDIndexBkt is a top-level bucket storing:
// tower-id -> session-id -> 1
cTowerIDToSessionIDIndexBkt = []byte(
"client-tower-to-session-index-bucket",
)
// ErrUninitializedDB signals that top-level buckets for the database
// have not been initialized.
ErrUninitializedDB = errors.New("db not initialized")
// byteOrder is the default endianness used when serializing integers.
byteOrder = binary.BigEndian
)
// MigrateCompleteTowerToSessionIndex ensures that the tower-to-session index
// contains entries for all towers in the db. This is necessary because
// migration1 only created entries in the index for towers that the client had
// at least one session with. This migration thus makes sure that there is
// always a tower-to-sessions index entry for a tower even if there are no
// sessions with that tower.
func MigrateCompleteTowerToSessionIndex(tx kvdb.RwTx) error {
log.Infof("Migrating the tower client db to ensure that there is an " +
"entry in the towerID-to-sessionID index for every tower in " +
"the db")
// First, we collect all the towers that we should add an entry for in
// the index.
towerIDs, err := listTowerIDs(tx)
if err != nil {
return err
}
// Create a new top-level bucket for the index if it does not yet exist.
indexBkt, err := tx.CreateTopLevelBucket(cTowerIDToSessionIDIndexBkt)
if err != nil {
return err
}
// Finally, ensure that there is an entry in the tower-to-session index
// for each of our towers.
for _, id := range towerIDs {
// Create a sub-bucket using the tower ID.
_, err := indexBkt.CreateBucketIfNotExists(id.Bytes())
if err != nil {
return err
}
}
return nil
}
// listTowerIDs iterates through the cTowerBkt and collects a list of all the
// TowerIDs.
func listTowerIDs(tx kvdb.RTx) ([]*TowerID, error) {
var ids []*TowerID
towerBucket := tx.ReadBucket(cTowerBkt)
if towerBucket == nil {
return nil, ErrUninitializedDB
}
err := towerBucket.ForEach(func(towerIDBytes, _ []byte) error {
id, err := TowerIDFromBytes(towerIDBytes)
if err != nil {
return err
}
ids = append(ids, &id)
return nil
})
if err != nil {
return nil, err
}
return ids, nil
}
package migration5
import (
"encoding/binary"
"fmt"
)
// TowerID is a unique 64-bit identifier allocated to each unique watchtower.
// This allows the client to conserve on-disk space by not needing to always
// reference towers by their pubkey.
type TowerID uint64
// TowerIDFromBytes constructs a TowerID from the provided byte slice. The
// argument must have at least 8 bytes, and should contain the TowerID in
// big-endian byte order.
func TowerIDFromBytes(towerIDBytes []byte) (TowerID, error) {
if len(towerIDBytes) != 8 {
return 0, fmt.Errorf("not enough bytes in tower ID. "+
"Expected 8, got: %d", len(towerIDBytes))
}
return TowerID(byteOrder.Uint64(towerIDBytes)), nil
}
// Bytes encodes a TowerID into an 8-byte slice in big-endian byte order.
func (id TowerID) Bytes() []byte {
var buf [8]byte
binary.BigEndian.PutUint64(buf[:], uint64(id))
return buf[:]
}
// SessionIDSize is 33-bytes; it is a serialized, compressed public key.
const SessionIDSize = 33
// SessionID is created from the remote public key of a client, and serves as a
// unique identifier and authentication for sending state updates.
type SessionID [SessionIDSize]byte
package migration5
import (
"github.com/btcsuite/btclog/v2"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration6
import (
"bytes"
"encoding/binary"
"errors"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/tlv"
)
var (
// cSessionBkt is a top-level bucket storing:
// session-id => cSessionBody -> encoded ClientSessionBody
// => cSessionDBID -> db-assigned-id
// => cSessionCommits => seqnum -> encoded CommittedUpdate
// => cSessionAcks => seqnum -> encoded BackupID
cSessionBkt = []byte("client-session-bucket")
// cSessionDBID is a key used in the cSessionBkt to store the
// db-assigned-id of a session.
cSessionDBID = []byte("client-session-db-id")
// cSessionIDIndexBkt is a top-level bucket storing:
// db-assigned-id -> session-id
cSessionIDIndexBkt = []byte("client-session-id-index")
// cSessionBody is a sub-bucket of cSessionBkt storing only the body of
// the ClientSession.
cSessionBody = []byte("client-session-body")
// ErrUninitializedDB signals that top-level buckets for the database
// have not been initialized.
ErrUninitializedDB = errors.New("db not initialized")
// ErrCorruptClientSession signals that the client session's on-disk
// structure deviates from what is expected.
ErrCorruptClientSession = errors.New("client session corrupted")
byteOrder = binary.BigEndian
)
// MigrateSessionIDIndex adds a new session ID index to the tower client db.
// This index is a mapping from db-assigned ID (a uint64 encoded using BigSize)
// to real session ID (33 bytes). This mapping will allow us to persist session
// pointers with fewer bytes in the future.
func MigrateSessionIDIndex(tx kvdb.RwTx) error {
log.Infof("Migrating the tower client db to add a new session ID " +
"index which stores a mapping from db-assigned ID to real " +
"session ID")
// Create a new top-level bucket for the index.
indexBkt, err := tx.CreateTopLevelBucket(cSessionIDIndexBkt)
if err != nil {
return err
}
// Get the existing top-level sessions bucket.
sessionsBkt := tx.ReadWriteBucket(cSessionBkt)
if sessionsBkt == nil {
return ErrUninitializedDB
}
// Iterate over the sessions bucket where each key is a session-ID.
return sessionsBkt.ForEach(func(sessionID, _ []byte) error {
// Ask the DB for a new, unique, id for the index bucket.
nextSeq, err := indexBkt.NextSequence()
if err != nil {
return err
}
newIndex, err := writeBigSize(nextSeq)
if err != nil {
return err
}
// Add the new db-assigned-ID to real-session-ID pair to the
// new index bucket.
err = indexBkt.Put(newIndex, sessionID)
if err != nil {
return err
}
// Get the sub-bucket for this specific session ID.
sessionBkt := sessionsBkt.NestedReadWriteBucket(sessionID)
if sessionBkt == nil {
return ErrCorruptClientSession
}
// Here we ensure that the session bucket includes a session
// body. The only reason we do this is so that we can simulate
// a migration fail in a test to ensure that a migration fail
// results in an untouched db.
sessionBodyBytes := sessionBkt.Get(cSessionBody)
if sessionBodyBytes == nil {
return ErrCorruptClientSession
}
// Add the db-assigned ID of the session to the session under
// the cSessionDBID key.
return sessionBkt.Put(cSessionDBID, newIndex)
})
}
// writeBigSize will encode the given uint64 as a BigSize byte slice.
func writeBigSize(i uint64) ([]byte, error) {
var b bytes.Buffer
err := tlv.WriteVarInt(&b, i, &[8]byte{})
if err != nil {
return nil, err
}
return b.Bytes(), nil
}
package migration6
import (
"encoding/hex"
)
// SessionIDSize is 33-bytes; it is a serialized, compressed public key.
const SessionIDSize = 33
// SessionID is created from the remote public key of a client, and serves as a
// unique identifier and authentication for sending state updates.
type SessionID [SessionIDSize]byte
// String returns a hex encoding of the session id.
func (s SessionID) String() string {
return hex.EncodeToString(s[:])
}
package migration6
import (
"github.com/btcsuite/btclog/v2"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration7
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/tlv"
)
var (
// cSessionBkt is a top-level bucket storing:
// session-id => cSessionBody -> encoded ClientSessionBody
// => cSessionDBID -> db-assigned-id
// => cSessionCommits => seqnum -> encoded CommittedUpdate
// => cSessionAckRangeIndex => chan-id => acked-index-range
cSessionBkt = []byte("client-session-bucket")
// cChanDetailsBkt is a top-level bucket storing:
// channel-id => cChannelSummary -> encoded ClientChanSummary.
// => cChanDBID -> db-assigned-id
// => cChanSessions => db-session-id -> 1
cChanDetailsBkt = []byte("client-channel-detail-bucket")
// cChannelSummary is a sub-bucket of cChanDetailsBkt which stores the
// encoded body of ClientChanSummary.
cChannelSummary = []byte("client-channel-summary")
// cChanSessions is a sub-bucket of cChanDetailsBkt which stores:
// session-id -> 1
cChanSessions = []byte("client-channel-sessions")
// cSessionAckRangeIndex is a sub-bucket of cSessionBkt storing:
// chan-id => start -> end
cSessionAckRangeIndex = []byte("client-session-ack-range-index")
// cSessionDBID is a key used in the cSessionBkt to store the
// db-assigned-d of a session.
cSessionDBID = []byte("client-session-db-id")
// cChanIDIndexBkt is a top-level bucket storing:
// db-assigned-id -> channel-ID
cChanIDIndexBkt = []byte("client-channel-id-index")
// ErrUninitializedDB signals that top-level buckets for the database
// have not been initialized.
ErrUninitializedDB = errors.New("db not initialized")
// ErrCorruptClientSession signals that the client session's on-disk
// structure deviates from what is expected.
ErrCorruptClientSession = errors.New("client session corrupted")
// byteOrder is the default endianness used when serializing integers.
byteOrder = binary.BigEndian
)
// MigrateChannelToSessionIndex migrates the tower client DB to add an index
// from channel-to-session. This will make it easier in future to check which
// sessions have updates for which channels.
func MigrateChannelToSessionIndex(tx kvdb.RwTx) error {
log.Infof("Migrating the tower client DB to build a new " +
"channel-to-session index")
sessionsBkt := tx.ReadBucket(cSessionBkt)
if sessionsBkt == nil {
return ErrUninitializedDB
}
chanDetailsBkt := tx.ReadWriteBucket(cChanDetailsBkt)
if chanDetailsBkt == nil {
return ErrUninitializedDB
}
chanIDsBkt := tx.ReadBucket(cChanIDIndexBkt)
if chanIDsBkt == nil {
return ErrUninitializedDB
}
// First gather all the new channel-to-session pairs that we want to
// add.
index, err := collectIndex(sessionsBkt)
if err != nil {
return err
}
// Then persist those pairs to the db.
return persistIndex(chanDetailsBkt, chanIDsBkt, index)
}
// collectIndex iterates through all the sessions and uses the keys in the
// cSessionAckRangeIndex bucket to collect all the channels that the session
// has updates for. The function returns a map from channel ID to session ID
// (using the db-assigned IDs for both).
func collectIndex(sessionsBkt kvdb.RBucket) (map[uint64]map[uint64]bool,
error) {
index := make(map[uint64]map[uint64]bool)
err := sessionsBkt.ForEach(func(sessID, _ []byte) error {
sessionBkt := sessionsBkt.NestedReadBucket(sessID)
if sessionBkt == nil {
return ErrCorruptClientSession
}
ackedRanges := sessionBkt.NestedReadBucket(
cSessionAckRangeIndex,
)
if ackedRanges == nil {
return nil
}
sessDBIDBytes := sessionBkt.Get(cSessionDBID)
if sessDBIDBytes == nil {
return ErrCorruptClientSession
}
sessDBID, err := readUint64(sessDBIDBytes)
if err != nil {
return err
}
return ackedRanges.ForEach(func(dbChanIDBytes, _ []byte) error {
dbChanID, err := readUint64(dbChanIDBytes)
if err != nil {
return err
}
if _, ok := index[dbChanID]; !ok {
index[dbChanID] = make(map[uint64]bool)
}
index[dbChanID][sessDBID] = true
return nil
})
})
if err != nil {
return nil, err
}
return index, nil
}
// persistIndex adds the channel-to-session mapping in each channel's details
// bucket.
func persistIndex(chanDetailsBkt kvdb.RwBucket, chanIDsBkt kvdb.RBucket,
index map[uint64]map[uint64]bool) error {
for dbChanID, sessIDs := range index {
dbChanIDBytes, err := writeUint64(dbChanID)
if err != nil {
return err
}
realChanID := chanIDsBkt.Get(dbChanIDBytes)
chanBkt := chanDetailsBkt.NestedReadWriteBucket(realChanID)
if chanBkt == nil {
return fmt.Errorf("channel not found")
}
sessIDsBkt, err := chanBkt.CreateBucket(cChanSessions)
if err != nil {
return err
}
for id := range sessIDs {
sessID, err := writeUint64(id)
if err != nil {
return err
}
err = sessIDsBkt.Put(sessID, []byte{1})
if err != nil {
return err
}
}
}
return nil
}
func writeUint64(i uint64) ([]byte, error) {
var b bytes.Buffer
err := tlv.WriteVarInt(&b, i, &[8]byte{})
if err != nil {
return nil, err
}
return b.Bytes(), nil
}
func readUint64(b []byte) (uint64, error) {
r := bytes.NewReader(b)
i, err := tlv.ReadVarInt(r, &[8]byte{})
if err != nil {
return 0, err
}
return i, nil
}
package migration7
import "encoding/hex"
// SessionIDSize is 33-bytes; it is a serialized, compressed public key.
const SessionIDSize = 33
// SessionID is created from the remote public key of a client, and serves as a
// unique identifier and authentication for sending state updates.
type SessionID [SessionIDSize]byte
// String returns a hex encoding of the session id.
func (s SessionID) String() string {
return hex.EncodeToString(s[:])
}
// ChannelID is a series of 32-bytes that uniquely identifies all channels
// within the network. The ChannelID is computed using the outpoint of the
// funding transaction (the txid, and output index). Given a funding output the
// ChannelID can be calculated by XOR'ing the big-endian serialization of the
// txid and the big-endian serialization of the output index, truncated to
// 2 bytes.
type ChannelID [32]byte
// String returns the string representation of the ChannelID. This is just the
// hex string encoding of the ChannelID itself.
func (c ChannelID) String() string {
return hex.EncodeToString(c[:])
}
package migration7
import (
"github.com/btcsuite/btclog/v2"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration8
import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"io"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/tlv"
)
// BreachHintSize is the length of the identifier used to detect remote
// commitment broadcasts.
const BreachHintSize = 16
// BreachHint is the first 16-bytes of SHA256(txid), which is used to identify
// the breach transaction.
type BreachHint [BreachHintSize]byte
// ChannelID is a series of 32-bytes that uniquely identifies all channels
// within the network. The ChannelID is computed using the outpoint of the
// funding transaction (the txid, and output index). Given a funding output the
// ChannelID can be calculated by XOR'ing the big-endian serialization of the
// txid and the big-endian serialization of the output index, truncated to
// 2 bytes.
type ChannelID [32]byte
// writeBigSize will encode the given uint64 as a BigSize byte slice.
func writeBigSize(i uint64) ([]byte, error) {
var b bytes.Buffer
err := tlv.WriteVarInt(&b, i, &[8]byte{})
if err != nil {
return nil, err
}
return b.Bytes(), nil
}
// readBigSize converts the given byte slice into a uint64 and assumes that the
// bytes slice is using BigSize encoding.
func readBigSize(b []byte) (uint64, error) {
r := bytes.NewReader(b)
i, err := tlv.ReadVarInt(r, &[8]byte{})
if err != nil {
return 0, err
}
return i, nil
}
// CommittedUpdate holds a state update sent by a client along with its
// allocated sequence number and the exact remote commitment the encrypted
// justice transaction can rectify.
type CommittedUpdate struct {
// SeqNum is the unique sequence number allocated by the session to this
// update.
SeqNum uint16
CommittedUpdateBody
}
// BackupID identifies a particular revoked, remote commitment by channel id and
// commitment height.
type BackupID struct {
// ChanID is the channel id of the revoked commitment.
ChanID ChannelID
// CommitHeight is the commitment height of the revoked commitment.
CommitHeight uint64
}
// Encode writes the BackupID from the passed io.Writer.
func (b *BackupID) Encode(w io.Writer) error {
return WriteElements(w,
b.ChanID,
b.CommitHeight,
)
}
// Decode reads a BackupID from the passed io.Reader.
func (b *BackupID) Decode(r io.Reader) error {
return ReadElements(r,
&b.ChanID,
&b.CommitHeight,
)
}
// String returns a human-readable encoding of a BackupID.
func (b BackupID) String() string {
return fmt.Sprintf("backup(%v, %d)", b.ChanID, b.CommitHeight)
}
// WriteElements serializes a variadic list of elements into the given
// io.Writer.
func WriteElements(w io.Writer, elements ...interface{}) error {
for _, element := range elements {
if err := WriteElement(w, element); err != nil {
return err
}
}
return nil
}
// ReadElements deserializes the provided io.Reader into a variadic list of
// target elements.
func ReadElements(r io.Reader, elements ...interface{}) error {
for _, element := range elements {
if err := ReadElement(r, element); err != nil {
return err
}
}
return nil
}
// WriteElement serializes a single element into the provided io.Writer.
func WriteElement(w io.Writer, element interface{}) error {
switch e := element.(type) {
case ChannelID:
if _, err := w.Write(e[:]); err != nil {
return err
}
case uint64:
if err := binary.Write(w, byteOrder, e); err != nil {
return err
}
case BreachHint:
if _, err := w.Write(e[:]); err != nil {
return err
}
case []byte:
if err := wire.WriteVarBytes(w, 0, e); err != nil {
return err
}
default:
return fmt.Errorf("unexpected type")
}
return nil
}
// ReadElement deserializes a single element from the provided io.Reader.
func ReadElement(r io.Reader, element interface{}) error {
switch e := element.(type) {
case *ChannelID:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *uint64:
if err := binary.Read(r, byteOrder, e); err != nil {
return err
}
case *BreachHint:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *[]byte:
bytes, err := wire.ReadVarBytes(r, 0, 66000, "[]byte")
if err != nil {
return err
}
*e = bytes
default:
return fmt.Errorf("unexpected type")
}
return nil
}
// CommittedUpdateBody represents the primary components of a CommittedUpdate.
// On disk, this is stored under the sequence number, which acts as its key.
type CommittedUpdateBody struct {
// BackupID identifies the breached commitment that the encrypted blob
// can spend from.
BackupID BackupID
// Hint is the 16-byte prefix of the revoked commitment transaction ID.
Hint BreachHint
// EncryptedBlob is a ciphertext containing the sweep information for
// exacting justice if the commitment transaction matching the breach
// hint is broadcast.
EncryptedBlob []byte
}
// Encode writes the CommittedUpdateBody to the passed io.Writer.
func (u *CommittedUpdateBody) Encode(w io.Writer) error {
err := u.BackupID.Encode(w)
if err != nil {
return err
}
return WriteElements(w,
u.Hint,
u.EncryptedBlob,
)
}
// Decode reads a CommittedUpdateBody from the passed io.Reader.
func (u *CommittedUpdateBody) Decode(r io.Reader) error {
err := u.BackupID.Decode(r)
if err != nil {
return err
}
return ReadElements(r,
&u.Hint,
&u.EncryptedBlob,
)
}
// SessionIDSize is 33-bytes; it is a serialized, compressed public key.
const SessionIDSize = 33
// SessionID is created from the remote public key of a client, and serves as a
// unique identifier and authentication for sending state updates.
type SessionID [SessionIDSize]byte
// String returns a hex encoding of the session id.
func (s SessionID) String() string {
return hex.EncodeToString(s[:])
}
package migration8
import (
"github.com/btcsuite/btclog/v2"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
package migration8
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
// cSessionBkt is a top-level bucket storing:
// session-id => cSessionBody -> encoded ClientSessionBody
// => cSessionDBID -> db-assigned-id
// => cSessionCommits => seqnum -> encoded CommittedUpdate
// => cSessionAckRangeIndex => db-chan-id => start -> end
// => cSessionRogueUpdateCount -> count
cSessionBkt = []byte("client-session-bucket")
// cChanIDIndexBkt is a top-level bucket storing:
// db-assigned-id -> channel-ID
cChanIDIndexBkt = []byte("client-channel-id-index")
// cSessionAckRangeIndex is a sub-bucket of cSessionBkt storing
// chan-id => start -> end
cSessionAckRangeIndex = []byte("client-session-ack-range-index")
// cSessionBody is a sub-bucket of cSessionBkt storing:
// seqnum -> encoded CommittedUpdate.
cSessionCommits = []byte("client-session-commits")
// cChanDetailsBkt is a top-level bucket storing:
// channel-id => cChannelSummary -> encoded ClientChanSummary.
// => cChanDBID -> db-assigned-id
// => cChanSessions => db-session-id -> 1
// => cChanClosedHeight -> block-height
// => cChanMaxCommitmentHeight -> commitment-height
cChanDetailsBkt = []byte("client-channel-detail-bucket")
cChanMaxCommitmentHeight = []byte(
"client-channel-max-commitment-height",
)
// ErrUninitializedDB signals that top-level buckets for the database
// have not been initialized.
ErrUninitializedDB = errors.New("db not initialized")
byteOrder = binary.BigEndian
)
// MigrateChannelMaxHeights migrates the tower client db by collecting all the
// max commitment heights that have been backed up for each channel and then
// storing those heights alongside the channel info.
func MigrateChannelMaxHeights(tx kvdb.RwTx) error {
log.Infof("Migrating the tower client DB for quick channel max " +
"commitment height lookup")
heights, err := collectChanMaxHeights(tx)
if err != nil {
return err
}
return writeChanMaxHeights(tx, heights)
}
// writeChanMaxHeights iterates over the given channel ID to height map and
// writes an entry under the cChanMaxCommitmentHeight key for each channel.
func writeChanMaxHeights(tx kvdb.RwTx, heights map[ChannelID]uint64) error {
chanDetailsBkt := tx.ReadWriteBucket(cChanDetailsBkt)
if chanDetailsBkt == nil {
return ErrUninitializedDB
}
for chanID, maxHeight := range heights {
chanDetails := chanDetailsBkt.NestedReadWriteBucket(chanID[:])
// If the details bucket for this channel ID does not exist,
// it is probably a channel that has been closed and deleted
// already. So we can skip this height.
if chanDetails == nil {
continue
}
b, err := writeBigSize(maxHeight)
if err != nil {
return err
}
err = chanDetails.Put(cChanMaxCommitmentHeight, b)
if err != nil {
return err
}
}
return nil
}
// collectChanMaxHeights iterates over all the sessions in the DB. For each
// session, it iterates over all the Acked updates and the committed updates
// to collect the maximum commitment height for each channel.
func collectChanMaxHeights(tx kvdb.RwTx) (map[ChannelID]uint64, error) {
sessionsBkt := tx.ReadBucket(cSessionBkt)
if sessionsBkt == nil {
return nil, ErrUninitializedDB
}
chanIDIndexBkt := tx.ReadBucket(cChanIDIndexBkt)
if chanIDIndexBkt == nil {
return nil, ErrUninitializedDB
}
heights := make(map[ChannelID]uint64)
// For each update we consider, we will only update the heights map if
// the commitment height for the channel is larger than the current
// max height stored for the channel.
cb := func(chanID ChannelID, commitHeight uint64) {
if commitHeight > heights[chanID] {
heights[chanID] = commitHeight
}
}
err := sessionsBkt.ForEach(func(sessIDBytes, _ []byte) error {
sessBkt := sessionsBkt.NestedReadBucket(sessIDBytes)
if sessBkt == nil {
return fmt.Errorf("bucket not found for session %x",
sessIDBytes)
}
err := forEachCommittedUpdate(sessBkt, cb)
if err != nil {
return err
}
return forEachAckedUpdate(sessBkt, chanIDIndexBkt, cb)
})
if err != nil {
return nil, err
}
return heights, nil
}
// forEachCommittedUpdate iterates over all the given session's committed
// updates and calls the call-back for each.
func forEachCommittedUpdate(sessBkt kvdb.RBucket,
cb func(chanID ChannelID, commitHeight uint64)) error {
sessionCommits := sessBkt.NestedReadBucket(cSessionCommits)
if sessionCommits == nil {
return nil
}
return sessionCommits.ForEach(func(k, v []byte) error {
var update CommittedUpdate
err := update.Decode(bytes.NewReader(v))
if err != nil {
return err
}
cb(update.BackupID.ChanID, update.BackupID.CommitHeight)
return nil
})
}
// forEachAckedUpdate iterates over all the given session's acked update range
// indices and calls the call-back for each.
func forEachAckedUpdate(sessBkt, chanIDIndexBkt kvdb.RBucket,
cb func(chanID ChannelID, commitHeight uint64)) error {
sessionAcksRanges := sessBkt.NestedReadBucket(cSessionAckRangeIndex)
if sessionAcksRanges == nil {
return nil
}
return sessionAcksRanges.ForEach(func(dbChanID, _ []byte) error {
rangeBkt := sessionAcksRanges.NestedReadBucket(dbChanID)
if rangeBkt == nil {
return nil
}
index, err := readRangeIndex(rangeBkt)
if err != nil {
return err
}
chanIDBytes := chanIDIndexBkt.Get(dbChanID)
var chanID ChannelID
copy(chanID[:], chanIDBytes)
cb(chanID, index.MaxHeight())
return nil
})
}
// readRangeIndex reads a persisted RangeIndex from the passed bucket and into
// a new in-memory RangeIndex.
func readRangeIndex(rangesBkt kvdb.RBucket) (*RangeIndex, error) {
ranges := make(map[uint64]uint64)
err := rangesBkt.ForEach(func(k, v []byte) error {
start, err := readBigSize(k)
if err != nil {
return err
}
end, err := readBigSize(v)
if err != nil {
return err
}
ranges[start] = end
return nil
})
if err != nil {
return nil, err
}
return NewRangeIndex(ranges, WithSerializeUint64Fn(writeBigSize))
}
package migration8
import (
"fmt"
"sync"
)
// rangeItem represents the start and end values of a range.
type rangeItem struct {
start uint64
end uint64
}
// RangeIndexOption describes the signature of a functional option that can be
// used to modify the behaviour of a RangeIndex.
type RangeIndexOption func(*RangeIndex)
// WithSerializeUint64Fn is a functional option that can be used to set the
// function to be used to do the serialization of a uint64 into a byte slice.
func WithSerializeUint64Fn(fn func(uint64) ([]byte, error)) RangeIndexOption {
return func(index *RangeIndex) {
index.serializeUint64 = fn
}
}
// RangeIndex can be used to keep track of which numbers have been added to a
// set. It does so by keeping track of a sorted list of rangeItems. Each
// rangeItem has a start and end value of a range where all values in-between
// have been added to the set. It works well in situations where it is expected
// numbers in the set are not sparse.
type RangeIndex struct {
// set is a sorted list of rangeItem.
set []rangeItem
// mu is used to ensure safe access to set.
mu sync.Mutex
// serializeUint64 is the function that can be used to convert a uint64
// to a byte slice.
serializeUint64 func(uint64) ([]byte, error)
}
// NewRangeIndex constructs a new RangeIndex. An initial set of ranges may be
// passed to the function in the form of a map.
func NewRangeIndex(ranges map[uint64]uint64,
opts ...RangeIndexOption) (*RangeIndex, error) {
index := &RangeIndex{
serializeUint64: defaultSerializeUint64,
set: make([]rangeItem, 0),
}
// Apply any functional options.
for _, o := range opts {
o(index)
}
for s, e := range ranges {
if err := index.addRange(s, e); err != nil {
return nil, err
}
}
return index, nil
}
// addRange can be used to add an entire new range to the set. This method
// should only ever be called by NewRangeIndex to initialise the in-memory
// structure and so the RangeIndex mutex is not held during this method.
func (a *RangeIndex) addRange(start, end uint64) error {
// Check that the given range is valid.
if start > end {
return fmt.Errorf("invalid range. Start height %d is larger "+
"than end height %d", start, end)
}
// Collect the ranges that fall before and after the new range along
// with the start and end values of the new range.
var before, after []rangeItem
for _, x := range a.set {
// If the new start value can't extend the current ranges end
// value, then the two cannot be merged. The range is added to
// the group of ranges that fall before the new range.
if x.end+1 < start {
before = append(before, x)
continue
}
// If the current ranges start value does not follow on directly
// from the new end value, then the two cannot be merged. The
// range is added to the group of ranges that fall after the new
// range.
if end+1 < x.start {
after = append(after, x)
continue
}
// Otherwise, there is an overlap and so the two can be merged.
start = min(start, x.start)
end = max(end, x.end)
}
// Re-construct the range index set.
a.set = append(append(before, rangeItem{
start: start,
end: end,
}), after...)
return nil
}
// IsInIndex returns true if the given number is in the range set.
func (a *RangeIndex) IsInIndex(n uint64) bool {
a.mu.Lock()
defer a.mu.Unlock()
_, isCovered := a.lowerBoundIndex(n)
return isCovered
}
// NumInSet returns the number of items covered by the range set.
func (a *RangeIndex) NumInSet() uint64 {
a.mu.Lock()
defer a.mu.Unlock()
var numItems uint64
for _, r := range a.set {
numItems += r.end - r.start + 1
}
return numItems
}
// MaxHeight returns the highest number covered in the range.
func (a *RangeIndex) MaxHeight() uint64 {
a.mu.Lock()
defer a.mu.Unlock()
if len(a.set) == 0 {
return 0
}
return a.set[len(a.set)-1].end
}
// GetAllRanges returns a copy of the range set in the form of a map.
func (a *RangeIndex) GetAllRanges() map[uint64]uint64 {
a.mu.Lock()
defer a.mu.Unlock()
cp := make(map[uint64]uint64, len(a.set))
for _, item := range a.set {
cp[item.start] = item.end
}
return cp
}
// lowerBoundIndex returns the index of the RangeIndex that is most appropriate
// for the new value, n. In other words, it returns the index of the rangeItem
// set of the range where the start value is the highest start value in the set
// that is still lower than or equal to the given number, n. The returned
// boolean is true if the given number is already covered in the RangeIndex.
// A returned index of -1 indicates that no lower bound range exists in the set.
// Since the most likely case is that the new number will just extend the
// highest range, a check is first done to see if this is the case which will
// make the methods' computational complexity O(1). Otherwise, a binary search
// is done which brings the computational complexity to O(log N).
func (a *RangeIndex) lowerBoundIndex(n uint64) (int, bool) {
// If the set is empty, then there is no such index and the value
// definitely is not in the set.
if len(a.set) == 0 {
return -1, false
}
// In most cases, the last index item will be the one we want. So just
// do a quick check on that index first to avoid doing the binary
// search.
lastIndex := len(a.set) - 1
lastRange := a.set[lastIndex]
if lastRange.start <= n {
return lastIndex, lastRange.end >= n
}
// Otherwise, do a binary search to find the index of interest.
var (
low = 0
high = len(a.set) - 1
rangeIndex = -1
)
for {
mid := (low + high) / 2
currentRange := a.set[mid]
switch {
case currentRange.start > n:
// If the start of the range is greater than n, we can
// completely cut out that entire part of the array.
high = mid
case currentRange.start < n:
// If the range already includes the given height, we
// can stop searching now.
if currentRange.end >= n {
return mid, true
}
// If the start of the range is smaller than n, we can
// store this as the new best index to return.
rangeIndex = mid
// If low and mid are already equal, then increment low
// by 1. Exit if this means that low is now greater than
// high.
if low == mid {
low = mid + 1
if low > high {
return rangeIndex, false
}
} else {
low = mid
}
continue
default:
// If the height is equal to the start value of the
// current range that mid is pointing to, then the
// height is already covered.
return mid, true
}
// Exit if we have checked all the ranges.
if low == high {
break
}
}
return rangeIndex, false
}
// KVStore is an interface representing a key-value store.
type KVStore interface {
// Put saves the specified key/value pair to the store. Keys that do not
// already exist are added and keys that already exist are overwritten.
Put(key, value []byte) error
// Delete removes the specified key from the bucket. Deleting a key that
// does not exist does not return an error.
Delete(key []byte) error
}
// Add adds a single number to the range set. It first attempts to apply the
// necessary changes to the passed KV store and then only if this succeeds, will
// the changes be applied to the in-memory structure.
func (a *RangeIndex) Add(newHeight uint64, kv KVStore) error {
a.mu.Lock()
defer a.mu.Unlock()
// Compute the changes that will need to be applied to both the sorted
// rangeItem array representation and the key-value store representation
// of the range index.
arrayChanges, kvStoreChanges := a.getChanges(newHeight)
// First attempt to apply the KV store changes. Only if this succeeds
// will we apply the changes to our in-memory range index structure.
err := a.applyKVChanges(kv, kvStoreChanges)
if err != nil {
return err
}
// Since the DB changes were successful, we can now commit the
// changes to our in-memory representation of the range set.
a.applyArrayChanges(arrayChanges)
return nil
}
// applyKVChanges applies the given set of kvChanges to a KV store. It is
// assumed that a transaction is being held on the kv store so that if any
// of the actions of the function fails, the changes will be reverted.
func (a *RangeIndex) applyKVChanges(kv KVStore, changes *kvChanges) error {
// Exit early if there are no changes to apply.
if kv == nil || changes == nil {
return nil
}
// Check if any range pair needs to be deleted.
if changes.deleteKVKey != nil {
del, err := a.serializeUint64(*changes.deleteKVKey)
if err != nil {
return err
}
if err := kv.Delete(del); err != nil {
return err
}
}
start, err := a.serializeUint64(changes.key)
if err != nil {
return err
}
end, err := a.serializeUint64(changes.value)
if err != nil {
return err
}
return kv.Put(start, end)
}
// applyArrayChanges applies the given arrayChanges to the in-memory RangeIndex
// itself. This should only be done once the persisted kv store changes have
// already been applied.
func (a *RangeIndex) applyArrayChanges(changes *arrayChanges) {
if changes == nil {
return
}
if changes.indexToDelete != nil {
a.set = append(
a.set[:*changes.indexToDelete],
a.set[*changes.indexToDelete+1:]...,
)
}
if changes.newIndex != nil {
switch {
case *changes.newIndex == 0:
a.set = append([]rangeItem{{
start: changes.start,
end: changes.end,
}}, a.set...)
case *changes.newIndex == len(a.set):
a.set = append(a.set, rangeItem{
start: changes.start,
end: changes.end,
})
default:
a.set = append(
a.set[:*changes.newIndex+1],
a.set[*changes.newIndex:]...,
)
a.set[*changes.newIndex] = rangeItem{
start: changes.start,
end: changes.end,
}
}
return
}
if changes.indexToEdit != nil {
a.set[*changes.indexToEdit] = rangeItem{
start: changes.start,
end: changes.end,
}
}
}
// arrayChanges encompasses the diff to apply to the sorted rangeItem array
// representation of a range index. Such a diff will either include adding a
// new range or editing an existing range. If an existing range is edited, then
// the diff might also include deleting an index (this will be the case if the
// editing of the one range results in the merge of another range).
type arrayChanges struct {
start uint64
end uint64
// newIndex, if set, is the index of the in-memory range array where a
// new range, [start:end], should be added. newIndex should never be
// set at the same time as indexToEdit or indexToDelete.
newIndex *int
// indexToDelete, if set, is the index of the sorted rangeItem array
// that should be deleted. This should be applied before reading the
// index value of indexToEdit. This should not be set at the same time
// as newIndex.
indexToDelete *int
// indexToEdit is the index of the in-memory range array that should be
// edited. The range at this index will be changed to [start:end]. This
// should only be read after indexToDelete index has been deleted.
indexToEdit *int
}
// kvChanges encompasses the diff to apply to a KV-store representation of a
// range index. A kv-store diff for the addition of a single number to the range
// index will include either a brand new key-value pair or the altering of the
// value of an existing key. Optionally, the diff may also include the deletion
// of an existing key. A deletion will be required if the addition of the new
// number results in the merge of two ranges.
type kvChanges struct {
key uint64
value uint64
// deleteKVKey, if set, is the key of the kv store representation that
// should be deleted.
deleteKVKey *uint64
}
// getChanges will calculate and return the changes that need to be applied to
// both the sorted-rangeItem-array representation and the key-value store
// representation of the range index.
func (a *RangeIndex) getChanges(n uint64) (*arrayChanges, *kvChanges) {
// If the set is empty then a new range item is added.
if len(a.set) == 0 {
// For the array representation, a new range [n:n] is added to
// the first index of the array.
firstIndex := 0
ac := &arrayChanges{
newIndex: &firstIndex,
start: n,
end: n,
}
// For the KV representation, a new [n:n] pair is added.
kvc := &kvChanges{
key: n,
value: n,
}
return ac, kvc
}
// Find the index of the lower bound range to the new number.
indexOfRangeBelow, alreadyCovered := a.lowerBoundIndex(n)
switch {
// The new number is already covered by the range index. No changes are
// required.
case alreadyCovered:
return nil, nil
// No lower bound index exists.
case indexOfRangeBelow < 0:
// Check if the very first range can be merged into this new
// one.
if n+1 == a.set[0].start {
// If so, the two ranges can be merged and so the start
// value of the range is n and the end value is the end
// of the existing first range.
start := n
end := a.set[0].end
// For the array representation, we can just edit the
// first entry of the array
editIndex := 0
ac := &arrayChanges{
indexToEdit: &editIndex,
start: start,
end: end,
}
// For the KV store representation, we add a new kv pair
// and delete the range with the key equal to the start
// value of the range we are merging.
kvKeyToDelete := a.set[0].start
kvc := &kvChanges{
key: start,
value: end,
deleteKVKey: &kvKeyToDelete,
}
return ac, kvc
}
// Otherwise, we add a new index.
// For the array representation, a new range [n:n] is added to
// the first index of the array.
newIndex := 0
ac := &arrayChanges{
newIndex: &newIndex,
start: n,
end: n,
}
// For the KV representation, a new [n:n] pair is added.
kvc := &kvChanges{
key: n,
value: n,
}
return ac, kvc
// A lower range does exist, and it can be extended to include this new
// number.
case a.set[indexOfRangeBelow].end+1 == n:
start := a.set[indexOfRangeBelow].start
end := n
indexToChange := indexOfRangeBelow
// If there are no intervals above this one or if there are, but
// they can't be merged into this one then we just need to edit
// this interval.
if indexOfRangeBelow == len(a.set)-1 ||
a.set[indexOfRangeBelow+1].start != n+1 {
// For the array representation, we just edit the index.
ac := &arrayChanges{
indexToEdit: &indexToChange,
start: start,
end: end,
}
// For the key-value representation, we just overwrite
// the end value at the existing start key.
kvc := &kvChanges{
key: start,
value: end,
}
return ac, kvc
}
// There is a range above this one that we need to merge into
// this one.
delIndex := indexOfRangeBelow + 1
end = a.set[delIndex].end
// For the array representation, we delete the range above this
// one and edit this range to include the end value of the range
// above.
ac := &arrayChanges{
indexToDelete: &delIndex,
indexToEdit: &indexToChange,
start: start,
end: end,
}
// For the kv representation, we tweak the end value of an
// existing key and delete the key of the range we are deleting.
deleteKey := a.set[delIndex].start
kvc := &kvChanges{
key: start,
value: end,
deleteKVKey: &deleteKey,
}
return ac, kvc
// A lower range does exist, but it can't be extended to include this
// new number, and so we need to add a new range after the lower bound
// range.
default:
newIndex := indexOfRangeBelow + 1
// If there are no ranges above this new one or if there are,
// but they can't be merged into this new one, then we can just
// add the new one as is.
if newIndex == len(a.set) || a.set[newIndex].start != n+1 {
ac := &arrayChanges{
newIndex: &newIndex,
start: n,
end: n,
}
kvc := &kvChanges{
key: n,
value: n,
}
return ac, kvc
}
// Else, we merge the above index.
start := n
end := a.set[newIndex].end
toEdit := newIndex
// For the array representation, we edit the range above to
// include the new start value.
ac := &arrayChanges{
indexToEdit: &toEdit,
start: start,
end: end,
}
// For the kv representation, we insert the new start-end key
// value pair and delete the key using the old start value.
delKey := a.set[newIndex].start
kvc := &kvChanges{
key: start,
value: end,
deleteKVKey: &delKey,
}
return ac, kvc
}
}
func defaultSerializeUint64(i uint64) ([]byte, error) {
var b [8]byte
byteOrder.PutUint64(b[:], i)
return b[:], nil
}
package wtdb
import (
"bytes"
"errors"
"fmt"
"io"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
// queueMainBkt will hold the main queue contents. It will have the
// following structure:
// => oldestIndexKey => oldest index
// => nextIndexKey => newest index
// => itemsBkt => <index> -> item
//
// Any items added to the queue via Push, will be added to this queue.
// Items will only be popped from this queue if the head queue is empty.
queueMainBkt = []byte("queue-main")
// queueHeadBkt will hold the items that have been pushed to the head
// of the queue. It will have the following structure:
// => oldestIndexKey => oldest index
// => nextIndexKey => newest index
// => itemsBkt => <index> -> item
//
// If PushHead is called with a new set of items, then first all
// remaining items in the head queue will be popped and added ot the
// given set of items. Then, once the head queue is empty, the set of
// items will be pushed to the queue. If this queue is not empty, then
// Pop will pop items from this queue before popping from the main
// queue.
queueHeadBkt = []byte("queue-head")
// itemsBkt is a sub-bucket of both the main and head queue storing:
// index -> encoded item
itemsBkt = []byte("items")
// oldestIndexKey is a key of both the main and head queue storing the
// index of the item at the head of the queue.
oldestIndexKey = []byte("oldest-index")
// nextIndexKey is a key of both the main and head queue storing the
// index of the item at the tail of the queue.
nextIndexKey = []byte("next-index")
// ErrEmptyQueue is returned from Pop if there are no items left in
// the queue.
ErrEmptyQueue = errors.New("queue is empty")
)
// Queue is an interface describing a FIFO queue for any generic type T.
type Queue[T any] interface {
// Len returns the number of tasks in the queue.
Len() (uint64, error)
// Push pushes new T items to the tail of the queue.
Push(items ...T) error
// PopUpTo attempts to pop up to n items from the head of the queue. If
// no more items are in the queue then ErrEmptyQueue is returned.
PopUpTo(n int) ([]T, error)
// PushHead pushes new T items to the head of the queue.
PushHead(items ...T) error
}
// Serializable is an interface must be satisfied for any type that the
// DiskQueueDB should handle.
type Serializable interface {
Encode(w io.Writer) error
Decode(r io.Reader) error
}
// DiskQueueDB is a generic Bolt DB implementation of the Queue interface.
type DiskQueueDB[T Serializable] struct {
db kvdb.Backend
topLevelBkt []byte
constructor func() T
onItemWrite func(tx kvdb.RwTx, item T) error
}
// A compile-time check to ensure that DiskQueueDB implements the Queue
// interface.
var _ Queue[Serializable] = (*DiskQueueDB[Serializable])(nil)
// NewQueueDB constructs a new DiskQueueDB. A queueBktName must be provided so
// that the DiskQueueDB can create its own namespace in the bolt db.
func NewQueueDB[T Serializable](db kvdb.Backend, queueBktName []byte,
constructor func() T,
onItemWrite func(tx kvdb.RwTx, item T) error) Queue[T] {
return &DiskQueueDB[T]{
db: db,
topLevelBkt: queueBktName,
constructor: constructor,
onItemWrite: onItemWrite,
}
}
// Len returns the number of tasks in the queue.
//
// NOTE: This is part of the Queue interface.
func (d *DiskQueueDB[T]) Len() (uint64, error) {
var res uint64
err := kvdb.View(d.db, func(tx kvdb.RTx) error {
var err error
res, err = d.len(tx)
return err
}, func() {
res = 0
})
if err != nil {
return 0, err
}
return res, nil
}
// Push adds a T to the tail of the queue.
//
// NOTE: This is part of the Queue interface.
func (d *DiskQueueDB[T]) Push(items ...T) error {
return d.db.Update(func(tx walletdb.ReadWriteTx) error {
for _, item := range items {
err := d.addItem(tx, queueMainBkt, item)
if err != nil {
return err
}
}
return nil
}, func() {})
}
// PopUpTo attempts to pop up to n items from the queue. If the queue is empty,
// then ErrEmptyQueue is returned.
//
// NOTE: This is part of the Queue interface.
func (d *DiskQueueDB[T]) PopUpTo(n int) ([]T, error) {
var items []T
err := d.db.Update(func(tx walletdb.ReadWriteTx) error {
// Get the number of items in the queue.
l, err := d.len(tx)
if err != nil {
return err
}
// If there are no items, then we are done.
if l == 0 {
return ErrEmptyQueue
}
// If the number of items in the queue is less than the maximum
// specified by the caller, then set the maximum to the number
// of items that there actually are.
num := n
if l < uint64(n) {
num = int(l)
}
// Pop the specified number of items off of the queue.
items = make([]T, 0, num)
for i := 0; i < num; i++ {
item, err := d.pop(tx)
if err != nil {
return err
}
items = append(items, item)
}
return err
}, func() {
items = nil
})
if err != nil {
return nil, err
}
return items, nil
}
// PushHead pushes new T items to the head of the queue. For this implementation
// of the Queue interface, this will require popping all items currently in the
// head queue and adding them after first adding the given list of items. Care
// should thus be taken to never have an unbounded number of items in the head
// queue.
//
// NOTE: This is part of the Queue interface.
func (d *DiskQueueDB[T]) PushHead(items ...T) error {
return d.db.Update(func(tx walletdb.ReadWriteTx) error {
// Determine how many items are still in the head queue.
numHead, err := d.numItems(tx, queueHeadBkt)
if err != nil {
return err
}
// Create a new in-memory list that will contain all the new
// items along with the items currently in the queue.
itemList := make([]T, 0, int(numHead)+len(items))
// Insert all the given items into the list first since these
// should be at the head of the queue.
itemList = append(itemList, items...)
// Now, read out all the items that are currently in the
// persisted head queue and add them to the back of the list
// of items to be added.
for {
t, err := d.nextItem(tx, queueHeadBkt)
if errors.Is(err, ErrEmptyQueue) {
break
} else if err != nil {
return err
}
itemList = append(itemList, t)
}
// Now the head queue is empty, the items can be pushed to the
// queue.
for _, item := range itemList {
err := d.addItem(tx, queueHeadBkt, item)
if err != nil {
return err
}
}
return nil
}, func() {})
}
// pop gets the next T item from the head of the queue. If no more items are in
// the queue then ErrEmptyQueue is returned.
func (d *DiskQueueDB[T]) pop(tx walletdb.ReadWriteTx) (T, error) {
// First, check if there are items left in the head queue.
item, err := d.nextItem(tx, queueHeadBkt)
// No error means that an item was found in the head queue.
if err == nil {
return item, nil
}
// Else, if error is not ErrEmptyQueue, then return the error.
if !errors.Is(err, ErrEmptyQueue) {
return item, err
}
// Otherwise, the head queue is empty, so we now check if there are
// items in the main queue.
return d.nextItem(tx, queueMainBkt)
}
// addItem adds the given item to the back of the given queue.
func (d *DiskQueueDB[T]) addItem(tx kvdb.RwTx, queueName []byte, item T) error {
var (
namespacedBkt = tx.ReadWriteBucket(d.topLevelBkt)
err error
)
if namespacedBkt == nil {
namespacedBkt, err = tx.CreateTopLevelBucket(d.topLevelBkt)
if err != nil {
return err
}
}
mainTasksBucket, err := namespacedBkt.CreateBucketIfNotExists(
cTaskQueue,
)
if err != nil {
return err
}
bucket, err := mainTasksBucket.CreateBucketIfNotExists(queueName)
if err != nil {
return err
}
if d.onItemWrite != nil {
err = d.onItemWrite(tx, item)
if err != nil {
return err
}
}
// Find the index to use for placing this new item at the back of the
// queue.
var nextIndex uint64
nextIndexB := bucket.Get(nextIndexKey)
if nextIndexB != nil {
nextIndex, err = readBigSize(nextIndexB)
if err != nil {
return err
}
} else {
nextIndexB, err = writeBigSize(0)
if err != nil {
return err
}
}
tasksBucket, err := bucket.CreateBucketIfNotExists(itemsBkt)
if err != nil {
return err
}
var buff bytes.Buffer
err = item.Encode(&buff)
if err != nil {
return err
}
// Put the new task in the assigned index.
err = tasksBucket.Put(nextIndexB, buff.Bytes())
if err != nil {
return err
}
// Increment the next-index counter.
nextIndex++
nextIndexB, err = writeBigSize(nextIndex)
if err != nil {
return err
}
return bucket.Put(nextIndexKey, nextIndexB)
}
// nextItem pops an item of the queue identified by the given namespace. If
// there are no items on the queue then ErrEmptyQueue is returned.
func (d *DiskQueueDB[T]) nextItem(tx kvdb.RwTx, queueName []byte) (T, error) {
task := d.constructor()
namespacedBkt := tx.ReadWriteBucket(d.topLevelBkt)
if namespacedBkt == nil {
return task, ErrEmptyQueue
}
mainTasksBucket := namespacedBkt.NestedReadWriteBucket(cTaskQueue)
if mainTasksBucket == nil {
return task, ErrEmptyQueue
}
bucket, err := mainTasksBucket.CreateBucketIfNotExists(queueName)
if err != nil {
return task, err
}
// Get the index of the tail of the queue.
var nextIndex uint64
nextIndexB := bucket.Get(nextIndexKey)
if nextIndexB != nil {
nextIndex, err = readBigSize(nextIndexB)
if err != nil {
return task, err
}
}
// Get the index of the head of the queue.
var oldestIndex uint64
oldestIndexB := bucket.Get(oldestIndexKey)
if oldestIndexB != nil {
oldestIndex, err = readBigSize(oldestIndexB)
if err != nil {
return task, err
}
} else {
oldestIndexB, err = writeBigSize(0)
if err != nil {
return task, err
}
}
// If the head and tail are equal, then there are no items in the queue.
if oldestIndex == nextIndex {
// Take this opportunity to reset both indexes to zero.
zeroIndexB, err := writeBigSize(0)
if err != nil {
return task, err
}
err = bucket.Put(oldestIndexKey, zeroIndexB)
if err != nil {
return task, err
}
err = bucket.Put(nextIndexKey, zeroIndexB)
if err != nil {
return task, err
}
return task, ErrEmptyQueue
}
// Otherwise, pop the item at the oldest index.
tasksBucket := bucket.NestedReadWriteBucket(itemsBkt)
if tasksBucket == nil {
return task, fmt.Errorf("client-tasks bucket not found")
}
item := tasksBucket.Get(oldestIndexB)
if item == nil {
return task, fmt.Errorf("no task found under index")
}
err = tasksBucket.Delete(oldestIndexB)
if err != nil {
return task, err
}
// Increment the oldestIndex value so that it now points to the new
// oldest item.
oldestIndex++
oldestIndexB, err = writeBigSize(oldestIndex)
if err != nil {
return task, err
}
err = bucket.Put(oldestIndexKey, oldestIndexB)
if err != nil {
return task, err
}
if err = task.Decode(bytes.NewBuffer(item)); err != nil {
return task, err
}
return task, nil
}
// len returns the number of items in the queue. This will be the addition of
// the number of items in the main queue and the number in the head queue.
func (d *DiskQueueDB[T]) len(tx kvdb.RTx) (uint64, error) {
numMain, err := d.numItems(tx, queueMainBkt)
if err != nil {
return 0, err
}
numHead, err := d.numItems(tx, queueHeadBkt)
if err != nil {
return 0, err
}
return numMain + numHead, nil
}
// numItems returns the number of items in the given queue.
func (d *DiskQueueDB[T]) numItems(tx kvdb.RTx, queueName []byte) (uint64,
error) {
// Get the queue bucket at the correct namespace.
namespacedBkt := tx.ReadBucket(d.topLevelBkt)
if namespacedBkt == nil {
return 0, nil
}
mainTasksBucket := namespacedBkt.NestedReadBucket(cTaskQueue)
if mainTasksBucket == nil {
return 0, nil
}
bucket := mainTasksBucket.NestedReadBucket(queueName)
if bucket == nil {
return 0, nil
}
var (
oldestIndex uint64
nextIndex uint64
err error
)
// Get the next index key.
nextIndexB := bucket.Get(nextIndexKey)
if nextIndexB != nil {
nextIndex, err = readBigSize(nextIndexB)
if err != nil {
return 0, err
}
}
// Get the oldest index.
oldestIndexB := bucket.Get(oldestIndexKey)
if oldestIndexB != nil {
oldestIndex, err = readBigSize(oldestIndexB)
if err != nil {
return 0, err
}
}
return nextIndex - oldestIndex, nil
}
package wtdb
import (
"fmt"
"sync"
)
// rangeItem represents the start and end values of a range.
type rangeItem struct {
start uint64
end uint64
}
// RangeIndexOption describes the signature of a functional option that can be
// used to modify the behaviour of a RangeIndex.
type RangeIndexOption func(*RangeIndex)
// WithSerializeUint64Fn is a functional option that can be used to set the
// function to be used to do the serialization of a uint64 into a byte slice.
func WithSerializeUint64Fn(fn func(uint64) ([]byte, error)) RangeIndexOption {
return func(index *RangeIndex) {
index.serializeUint64 = fn
}
}
// RangeIndex can be used to keep track of which numbers have been added to a
// set. It does so by keeping track of a sorted list of rangeItems. Each
// rangeItem has a start and end value of a range where all values in-between
// have been added to the set. It works well in situations where it is expected
// numbers in the set are not sparse.
type RangeIndex struct {
// set is a sorted list of rangeItem.
set []rangeItem
// mu is used to ensure safe access to set.
mu sync.Mutex
// serializeUint64 is the function that can be used to convert a uint64
// to a byte slice.
serializeUint64 func(uint64) ([]byte, error)
}
// NewRangeIndex constructs a new RangeIndex. An initial set of ranges may be
// passed to the function in the form of a map.
func NewRangeIndex(ranges map[uint64]uint64,
opts ...RangeIndexOption) (*RangeIndex, error) {
index := &RangeIndex{
serializeUint64: defaultSerializeUint64,
set: make([]rangeItem, 0),
}
// Apply any functional options.
for _, o := range opts {
o(index)
}
for s, e := range ranges {
if err := index.addRange(s, e); err != nil {
return nil, err
}
}
return index, nil
}
// addRange can be used to add an entire new range to the set. This method
// should only ever be called by NewRangeIndex to initialise the in-memory
// structure and so the RangeIndex mutex is not held during this method.
func (a *RangeIndex) addRange(start, end uint64) error {
// Check that the given range is valid.
if start > end {
return fmt.Errorf("invalid range. Start height %d is larger "+
"than end height %d", start, end)
}
// Collect the ranges that fall before and after the new range along
// with the start and end values of the new range.
var before, after []rangeItem
for _, x := range a.set {
// If the new start value can't extend the current ranges end
// value, then the two cannot be merged. The range is added to
// the group of ranges that fall before the new range.
if x.end+1 < start {
before = append(before, x)
continue
}
// If the current ranges start value does not follow on directly
// from the new end value, then the two cannot be merged. The
// range is added to the group of ranges that fall after the new
// range.
if end+1 < x.start {
after = append(after, x)
continue
}
// Otherwise, there is an overlap and so the two can be merged.
start = min(start, x.start)
end = max(end, x.end)
}
// Re-construct the range index set.
a.set = append(append(before, rangeItem{
start: start,
end: end,
}), after...)
return nil
}
// IsInIndex returns true if the given number is in the range set.
func (a *RangeIndex) IsInIndex(n uint64) bool {
a.mu.Lock()
defer a.mu.Unlock()
_, isCovered := a.lowerBoundIndex(n)
return isCovered
}
// NumInSet returns the number of items covered by the range set.
func (a *RangeIndex) NumInSet() uint64 {
a.mu.Lock()
defer a.mu.Unlock()
var numItems uint64
for _, r := range a.set {
numItems += r.end - r.start + 1
}
return numItems
}
// MaxHeight returns the highest number covered in the range.
func (a *RangeIndex) MaxHeight() uint64 {
a.mu.Lock()
defer a.mu.Unlock()
if len(a.set) == 0 {
return 0
}
return a.set[len(a.set)-1].end
}
// GetAllRanges returns a copy of the range set in the form of a map.
func (a *RangeIndex) GetAllRanges() map[uint64]uint64 {
a.mu.Lock()
defer a.mu.Unlock()
cp := make(map[uint64]uint64, len(a.set))
for _, item := range a.set {
cp[item.start] = item.end
}
return cp
}
// lowerBoundIndex returns the index of the RangeIndex that is most appropriate
// for the new value, n. In other words, it returns the index of the rangeItem
// set of the range where the start value is the highest start value in the set
// that is still lower than or equal to the given number, n. The returned
// boolean is true if the given number is already covered in the RangeIndex.
// A returned index of -1 indicates that no lower bound range exists in the set.
// Since the most likely case is that the new number will just extend the
// highest range, a check is first done to see if this is the case which will
// make the methods' computational complexity O(1). Otherwise, a binary search
// is done which brings the computational complexity to O(log N).
func (a *RangeIndex) lowerBoundIndex(n uint64) (int, bool) {
// If the set is empty, then there is no such index and the value
// definitely is not in the set.
if len(a.set) == 0 {
return -1, false
}
// In most cases, the last index item will be the one we want. So just
// do a quick check on that index first to avoid doing the binary
// search.
lastIndex := len(a.set) - 1
lastRange := a.set[lastIndex]
if lastRange.start <= n {
return lastIndex, lastRange.end >= n
}
// Otherwise, do a binary search to find the index of interest.
var (
low = 0
high = len(a.set) - 1
rangeIndex = -1
)
for {
mid := (low + high) / 2
currentRange := a.set[mid]
switch {
case currentRange.start > n:
// If the start of the range is greater than n, we can
// completely cut out that entire part of the array.
high = mid
case currentRange.start < n:
// If the range already includes the given height, we
// can stop searching now.
if currentRange.end >= n {
return mid, true
}
// If the start of the range is smaller than n, we can
// store this as the new best index to return.
rangeIndex = mid
// If low and mid are already equal, then increment low
// by 1. Exit if this means that low is now greater than
// high.
if low == mid {
low = mid + 1
if low > high {
return rangeIndex, false
}
} else {
low = mid
}
continue
default:
// If the height is equal to the start value of the
// current range that mid is pointing to, then the
// height is already covered.
return mid, true
}
// Exit if we have checked all the ranges.
if low == high {
break
}
}
return rangeIndex, false
}
// KVStore is an interface representing a key-value store.
type KVStore interface {
// Put saves the specified key/value pair to the store. Keys that do not
// already exist are added and keys that already exist are overwritten.
Put(key, value []byte) error
// Delete removes the specified key from the bucket. Deleting a key that
// does not exist does not return an error.
Delete(key []byte) error
}
// Add adds a single number to the range set. It first attempts to apply the
// necessary changes to the passed KV store and then only if this succeeds, will
// the changes be applied to the in-memory structure.
func (a *RangeIndex) Add(newHeight uint64, kv KVStore) error {
a.mu.Lock()
defer a.mu.Unlock()
// Compute the changes that will need to be applied to both the sorted
// rangeItem array representation and the key-value store representation
// of the range index.
arrayChanges, kvStoreChanges := a.getChanges(newHeight)
// First attempt to apply the KV store changes. Only if this succeeds
// will we apply the changes to our in-memory range index structure.
err := a.applyKVChanges(kv, kvStoreChanges)
if err != nil {
return err
}
// Since the DB changes were successful, we can now commit the
// changes to our in-memory representation of the range set.
a.applyArrayChanges(arrayChanges)
return nil
}
// applyKVChanges applies the given set of kvChanges to a KV store. It is
// assumed that a transaction is being held on the kv store so that if any
// of the actions of the function fails, the changes will be reverted.
func (a *RangeIndex) applyKVChanges(kv KVStore, changes *kvChanges) error {
// Exit early if there are no changes to apply.
if kv == nil || changes == nil {
return nil
}
// Check if any range pair needs to be deleted.
if changes.deleteKVKey != nil {
del, err := a.serializeUint64(*changes.deleteKVKey)
if err != nil {
return err
}
if err := kv.Delete(del); err != nil {
return err
}
}
start, err := a.serializeUint64(changes.key)
if err != nil {
return err
}
end, err := a.serializeUint64(changes.value)
if err != nil {
return err
}
return kv.Put(start, end)
}
// applyArrayChanges applies the given arrayChanges to the in-memory RangeIndex
// itself. This should only be done once the persisted kv store changes have
// already been applied.
func (a *RangeIndex) applyArrayChanges(changes *arrayChanges) {
if changes == nil {
return
}
if changes.indexToDelete != nil {
a.set = append(
a.set[:*changes.indexToDelete],
a.set[*changes.indexToDelete+1:]...,
)
}
if changes.newIndex != nil {
switch {
case *changes.newIndex == 0:
a.set = append([]rangeItem{{
start: changes.start,
end: changes.end,
}}, a.set...)
case *changes.newIndex == len(a.set):
a.set = append(a.set, rangeItem{
start: changes.start,
end: changes.end,
})
default:
a.set = append(
a.set[:*changes.newIndex+1],
a.set[*changes.newIndex:]...,
)
a.set[*changes.newIndex] = rangeItem{
start: changes.start,
end: changes.end,
}
}
return
}
if changes.indexToEdit != nil {
a.set[*changes.indexToEdit] = rangeItem{
start: changes.start,
end: changes.end,
}
}
}
// arrayChanges encompasses the diff to apply to the sorted rangeItem array
// representation of a range index. Such a diff will either include adding a
// new range or editing an existing range. If an existing range is edited, then
// the diff might also include deleting an index (this will be the case if the
// editing of the one range results in the merge of another range).
type arrayChanges struct {
start uint64
end uint64
// newIndex, if set, is the index of the in-memory range array where a
// new range, [start:end], should be added. newIndex should never be
// set at the same time as indexToEdit or indexToDelete.
newIndex *int
// indexToDelete, if set, is the index of the sorted rangeItem array
// that should be deleted. This should be applied before reading the
// index value of indexToEdit. This should not be set at the same time
// as newIndex.
indexToDelete *int
// indexToEdit is the index of the in-memory range array that should be
// edited. The range at this index will be changed to [start:end]. This
// should only be read after indexToDelete index has been deleted.
indexToEdit *int
}
// kvChanges encompasses the diff to apply to a KV-store representation of a
// range index. A kv-store diff for the addition of a single number to the range
// index will include either a brand new key-value pair or the altering of the
// value of an existing key. Optionally, the diff may also include the deletion
// of an existing key. A deletion will be required if the addition of the new
// number results in the merge of two ranges.
type kvChanges struct {
key uint64
value uint64
// deleteKVKey, if set, is the key of the kv store representation that
// should be deleted.
deleteKVKey *uint64
}
// getChanges will calculate and return the changes that need to be applied to
// both the sorted-rangeItem-array representation and the key-value store
// representation of the range index.
func (a *RangeIndex) getChanges(n uint64) (*arrayChanges, *kvChanges) {
// If the set is empty then a new range item is added.
if len(a.set) == 0 {
// For the array representation, a new range [n:n] is added to
// the first index of the array.
firstIndex := 0
ac := &arrayChanges{
newIndex: &firstIndex,
start: n,
end: n,
}
// For the KV representation, a new [n:n] pair is added.
kvc := &kvChanges{
key: n,
value: n,
}
return ac, kvc
}
// Find the index of the lower bound range to the new number.
indexOfRangeBelow, alreadyCovered := a.lowerBoundIndex(n)
switch {
// The new number is already covered by the range index. No changes are
// required.
case alreadyCovered:
return nil, nil
// No lower bound index exists.
case indexOfRangeBelow < 0:
// Check if the very first range can be merged into this new
// one.
if n+1 == a.set[0].start {
// If so, the two ranges can be merged and so the start
// value of the range is n and the end value is the end
// of the existing first range.
start := n
end := a.set[0].end
// For the array representation, we can just edit the
// first entry of the array
editIndex := 0
ac := &arrayChanges{
indexToEdit: &editIndex,
start: start,
end: end,
}
// For the KV store representation, we add a new kv pair
// and delete the range with the key equal to the start
// value of the range we are merging.
kvKeyToDelete := a.set[0].start
kvc := &kvChanges{
key: start,
value: end,
deleteKVKey: &kvKeyToDelete,
}
return ac, kvc
}
// Otherwise, we add a new index.
// For the array representation, a new range [n:n] is added to
// the first index of the array.
newIndex := 0
ac := &arrayChanges{
newIndex: &newIndex,
start: n,
end: n,
}
// For the KV representation, a new [n:n] pair is added.
kvc := &kvChanges{
key: n,
value: n,
}
return ac, kvc
// A lower range does exist, and it can be extended to include this new
// number.
case a.set[indexOfRangeBelow].end+1 == n:
start := a.set[indexOfRangeBelow].start
end := n
indexToChange := indexOfRangeBelow
// If there are no intervals above this one or if there are, but
// they can't be merged into this one then we just need to edit
// this interval.
if indexOfRangeBelow == len(a.set)-1 ||
a.set[indexOfRangeBelow+1].start != n+1 {
// For the array representation, we just edit the index.
ac := &arrayChanges{
indexToEdit: &indexToChange,
start: start,
end: end,
}
// For the key-value representation, we just overwrite
// the end value at the existing start key.
kvc := &kvChanges{
key: start,
value: end,
}
return ac, kvc
}
// There is a range above this one that we need to merge into
// this one.
delIndex := indexOfRangeBelow + 1
end = a.set[delIndex].end
// For the array representation, we delete the range above this
// one and edit this range to include the end value of the range
// above.
ac := &arrayChanges{
indexToDelete: &delIndex,
indexToEdit: &indexToChange,
start: start,
end: end,
}
// For the kv representation, we tweak the end value of an
// existing key and delete the key of the range we are deleting.
deleteKey := a.set[delIndex].start
kvc := &kvChanges{
key: start,
value: end,
deleteKVKey: &deleteKey,
}
return ac, kvc
// A lower range does exist, but it can't be extended to include this
// new number, and so we need to add a new range after the lower bound
// range.
default:
newIndex := indexOfRangeBelow + 1
// If there are no ranges above this new one or if there are,
// but they can't be merged into this new one, then we can just
// add the new one as is.
if newIndex == len(a.set) || a.set[newIndex].start != n+1 {
ac := &arrayChanges{
newIndex: &newIndex,
start: n,
end: n,
}
kvc := &kvChanges{
key: n,
value: n,
}
return ac, kvc
}
// Else, we merge the above index.
start := n
end := a.set[newIndex].end
toEdit := newIndex
// For the array representation, we edit the range above to
// include the new start value.
ac := &arrayChanges{
indexToEdit: &toEdit,
start: start,
end: end,
}
// For the kv representation, we insert the new start-end key
// value pair and delete the key using the old start value.
delKey := a.set[newIndex].start
kvc := &kvChanges{
key: start,
value: end,
deleteKVKey: &delKey,
}
return ac, kvc
}
}
func defaultSerializeUint64(i uint64) ([]byte, error) {
var b [8]byte
byteOrder.PutUint64(b[:], i)
return b[:], nil
}
package wtdb
import (
"encoding/hex"
"github.com/btcsuite/btcd/btcec/v2"
)
// SessionIDSize is 33-bytes; it is a serialized, compressed public key.
const SessionIDSize = 33
// SessionID is created from the remote public key of a client, and serves as a
// unique identifier and authentication for sending state updates.
type SessionID [SessionIDSize]byte
// NewSessionIDFromPubKey creates a new SessionID from a public key.
func NewSessionIDFromPubKey(pubKey *btcec.PublicKey) SessionID {
var sid SessionID
copy(sid[:], pubKey.SerializeCompressed())
return sid
}
// String returns a hex encoding of the session id.
func (s SessionID) String() string {
return hex.EncodeToString(s[:])
}
package wtdb
import (
"errors"
"io"
"github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
)
var (
// ErrSessionNotFound is returned when querying by session id for a
// session that does not exist.
ErrSessionNotFound = errors.New("session not found in db")
// ErrSessionAlreadyExists signals that a session creation failed
// because a session with the same session id already exists.
ErrSessionAlreadyExists = errors.New("session already exists")
// ErrUpdateOutOfOrder is returned when the sequence number is not equal
// to the server's LastApplied+1.
ErrUpdateOutOfOrder = errors.New("update sequence number is not " +
"sequential")
// ErrLastAppliedReversion is returned when the client echos a
// last-applied value that is less than it claimed in a prior update.
ErrLastAppliedReversion = errors.New("update last applied must be " +
"non-decreasing")
// ErrSeqNumAlreadyApplied is returned when the client sends a sequence
// number for which they already claim to have an ACK.
ErrSeqNumAlreadyApplied = errors.New("update sequence number has " +
"already been applied")
// ErrSessionConsumed is returned if the client tries to send a sequence
// number larger than the session's max number of updates.
ErrSessionConsumed = errors.New("all session updates have been " +
"consumed")
)
// SessionInfo holds the negotiated session parameters for single session id,
// and handles the acceptance and validation of state updates sent by the
// client.
type SessionInfo struct {
// ID is the remote public key of the watchtower client.
ID SessionID
// Policy holds the negotiated session parameters.
Policy wtpolicy.Policy
// LastApplied the sequence number of the last successful state update.
LastApplied uint16
// ClientLastApplied the last last-applied the client has echoed back.
ClientLastApplied uint16
// RewardAddress the address that the tower's reward will be deposited
// to if a sweep transaction confirms.
RewardAddress []byte
// TODO(conner): store client metrics, DOS score, etc
}
// Encode serializes the session info to the given io.Writer.
func (s *SessionInfo) Encode(w io.Writer) error {
return WriteElements(w,
s.ID,
s.Policy,
s.LastApplied,
s.ClientLastApplied,
s.RewardAddress,
)
}
// Decode deserializes the session info from the given io.Reader.
func (s *SessionInfo) Decode(r io.Reader) error {
return ReadElements(r,
&s.ID,
&s.Policy,
&s.LastApplied,
&s.ClientLastApplied,
&s.RewardAddress,
)
}
// AcceptUpdateSequence validates that a state update's sequence number and last
// applied are valid given our past history with the client. These checks ensure
// that clients are properly in sync and following the update protocol properly.
// If validation is successful, the receiver's LastApplied and ClientLastApplied
// are updated with the latest values presented by the client. Any errors
// returned from this method are converted into an appropriate
// wtwire.StateUpdateCode.
func (s *SessionInfo) AcceptUpdateSequence(seqNum, lastApplied uint16) error {
switch {
// Client already claims to have an ACK for this seqnum.
case seqNum <= lastApplied:
return ErrSeqNumAlreadyApplied
// Client echos a last applied that is lower than previously sent.
case lastApplied < s.ClientLastApplied:
return ErrLastAppliedReversion
// Client update exceeds capacity of session.
case seqNum > s.Policy.MaxUpdates:
return ErrSessionConsumed
// Client update does not match our expected next seqnum.
case seqNum != s.LastApplied && seqNum != s.LastApplied+1:
return ErrUpdateOutOfOrder
}
s.LastApplied = seqNum
s.ClientLastApplied = lastApplied
return nil
}
// Match is returned in response to a database query for a breach hints
// contained in a particular block. The match encapsulates all data required to
// properly decrypt a client's encrypted blob, and pursue action on behalf of
// the victim by reconstructing the justice transaction and broadcasting it to
// the network.
//
// NOTE: It is possible for a match to cause a false positive, since they are
// matched on a prefix of the txid. In such an event, the likely behavior is
// that the payload will fail to decrypt.
type Match struct {
// ID is the session id of the client who uploaded the state update.
ID SessionID
// SeqNum is the session sequence number occupied by the client's state
// update. Together with ID, this allows the tower to derive the
// appropriate nonce for decryption.
SeqNum uint16
// Hint is the breach hint that triggered the match.
Hint blob.BreachHint
// EncryptedBlob is the encrypted payload containing the justice kit
// uploaded by the client.
EncryptedBlob []byte
// SessionInfo is the contract negotiated between tower and client, that
// provides input parameters such as fee rate, reward rate, and reward
// address when attempting to reconstruct the justice transaction.
SessionInfo *SessionInfo
}
package wtdb
import (
"io"
"github.com/lightningnetwork/lnd/watchtower/blob"
)
// SessionStateUpdate holds a state update sent by a client along with its
// SessionID.
type SessionStateUpdate struct {
// ID the session id of the client who sent the state update.
ID SessionID
// SeqNum the sequence number of the update within the session.
SeqNum uint16
// LastApplied the highest index that client has acknowledged is
// committed
LastApplied uint16
// Hint is the 16-byte prefix of the revoked commitment transaction.
Hint blob.BreachHint
// EncryptedBlob is a ciphertext containing the sweep information for
// exacting justice if the commitment transaction matching the breach
// hint is broadcast.
EncryptedBlob []byte
}
// Encode serializes the state update into the provided io.Writer.
func (u *SessionStateUpdate) Encode(w io.Writer) error {
return WriteElements(w,
u.ID,
u.SeqNum,
u.LastApplied,
u.Hint,
u.EncryptedBlob,
)
}
// Decode deserializes the target state update from the provided io.Reader.
func (u *SessionStateUpdate) Decode(r io.Reader) error {
return ReadElements(r,
&u.ID,
&u.SeqNum,
&u.LastApplied,
&u.Hint,
&u.EncryptedBlob,
)
}
package wtdb
import (
"encoding/hex"
"fmt"
"io"
"net"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/tlv"
)
// TowerStatus represents the state of the tower as set by the tower client.
type TowerStatus uint8
const (
// TowerStatusActive is the default state of the tower, and it indicates
// that this tower should be used to attempt session creation.
TowerStatusActive TowerStatus = 0
// TowerStatusInactive indicates that the tower should not be used to
// attempt session creation.
TowerStatusInactive TowerStatus = 1
)
const (
// TowerStatusTLVType is the TLV type number that will be used to store
// the tower's status.
TowerStatusTLVType = tlv.Type(0)
)
// TowerID is a unique 64-bit identifier allocated to each unique watchtower.
// This allows the client to conserve on-disk space by not needing to always
// reference towers by their pubkey.
type TowerID uint64
// TowerIDFromBytes constructs a TowerID from the provided byte slice. The
// argument must have at least 8 bytes, and should contain the TowerID in
// big-endian byte order.
func TowerIDFromBytes(towerIDBytes []byte) TowerID {
return TowerID(byteOrder.Uint64(towerIDBytes))
}
// Bytes encodes a TowerID into an 8-byte slice in big-endian byte order.
func (id TowerID) Bytes() []byte {
var buf [8]byte
byteOrder.PutUint64(buf[:], uint64(id))
return buf[:]
}
// Tower holds the necessary components required to connect to a remote tower.
// Communication is handled by brontide, and requires both a public key and an
// address.
type Tower struct {
// ID is a unique ID for this record assigned by the database.
ID TowerID
// IdentityKey is the public key of the remote node, used to
// authenticate the brontide transport.
IdentityKey *btcec.PublicKey
// Addresses is a list of possible addresses to reach the tower.
Addresses []net.Addr
// Status is the status of this tower as set by the client.
Status TowerStatus
}
// AddAddress adds the given address to the tower's in-memory list of addresses.
// If the address's string is already present, the Tower will be left
// unmodified. Otherwise, the address is prepended to the beginning of the
// Tower's addresses, on the assumption that it is fresher than the others.
//
// NOTE: This method is NOT safe for concurrent use.
func (t *Tower) AddAddress(addr net.Addr) {
// Ensure we don't add a duplicate address.
addrStr := addr.String()
for _, existingAddr := range t.Addresses {
if existingAddr.String() == addrStr {
return
}
}
// Add this address to the front of the list, on the assumption that it
// is a fresher address and will be tried first.
t.Addresses = append([]net.Addr{addr}, t.Addresses...)
}
// RemoveAddress removes the given address from the tower's in-memory list of
// addresses. If the address doesn't exist, then this will act as a NOP.
func (t *Tower) RemoveAddress(addr net.Addr) {
addrStr := addr.String()
for i, address := range t.Addresses {
if address.String() != addrStr {
continue
}
t.Addresses = append(t.Addresses[:i], t.Addresses[i+1:]...)
return
}
}
// String returns a user-friendly identifier of the tower.
func (t *Tower) String() string {
pubKey := hex.EncodeToString(t.IdentityKey.SerializeCompressed())
if len(t.Addresses) == 0 {
return pubKey
}
return fmt.Sprintf("%v@%v", pubKey, t.Addresses[0])
}
// Encode writes the Tower to the passed io.Writer. The TowerID is not
// serialized, since it acts as the key.
func (t *Tower) Encode(w io.Writer) error {
err := WriteElements(w,
t.IdentityKey,
t.Addresses,
)
if err != nil {
return err
}
status := uint8(t.Status)
tlvRecords := []tlv.Record{
tlv.MakePrimitiveRecord(TowerStatusTLVType, &status),
}
tlvStream, err := tlv.NewStream(tlvRecords...)
if err != nil {
return err
}
return tlvStream.Encode(w)
}
// Decode reads a Tower from the passed io.Reader. The TowerID is meant to be
// decoded from the key.
func (t *Tower) Decode(r io.Reader) error {
err := ReadElements(r,
&t.IdentityKey,
&t.Addresses,
)
if err != nil {
return err
}
var status uint8
tlvRecords := []tlv.Record{
tlv.MakePrimitiveRecord(TowerStatusTLVType, &status),
}
tlvStream, err := tlv.NewStream(tlvRecords...)
if err != nil {
return err
}
typeMap, err := tlvStream.DecodeWithParsedTypes(r)
if err != nil {
return err
}
if _, ok := typeMap[TowerStatusTLVType]; ok {
t.Status = TowerStatus(status)
}
return nil
}
package wtdb
import (
"bytes"
"errors"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/watchtower/blob"
)
var (
// sessionsBkt is a bucket containing all negotiated client sessions.
// session id -> session
sessionsBkt = []byte("sessions-bucket")
// updatesBkt is a bucket containing all state updates sent by clients.
// The updates are further bucketed by session id to prevent clients
// from overwrite each other.
// hint => session id -> update
updatesBkt = []byte("updates-bucket")
// updateIndexBkt is a bucket that indexes all state updates by their
// overarching session id. This allows for efficient lookup of updates
// by their session id, which is currently used to aide deletion
// performance.
// session id => hint1 -> []byte{}
// => hint2 -> []byte{}
updateIndexBkt = []byte("update-index-bucket")
// lookoutTipBkt is a bucket containing the last block epoch processed
// by the lookout subsystem. It has one key, lookoutTipKey.
// lookoutTipKey -> block epoch
lookoutTipBkt = []byte("lookout-tip-bucket")
// lookoutTipKey is a static key used to retrieve lookout tip's block
// epoch from the lookoutTipBkt.
lookoutTipKey = []byte("lookout-tip")
// ErrNoSessionHintIndex signals that an active session does not have an
// initialized index for tracking its own state updates.
ErrNoSessionHintIndex = errors.New("session hint index missing")
// ErrInvalidBlobSize indicates that the encrypted blob provided by the
// client is not valid according to the blob type of the session.
ErrInvalidBlobSize = errors.New("invalid blob size")
)
// TowerDB is single database providing a persistent storage engine for the
// wtserver and lookout subsystems.
type TowerDB struct {
db kvdb.Backend
}
// OpenTowerDB opens the tower database given the path to the database's
// directory. If no such database exists, this method will initialize a fresh
// one using the latest version number and bucket structure. If a database
// exists but has a lower version number than the current version, any necessary
// migrations will be applied before returning. Any attempt to open a database
// with a version number higher that the latest version will fail to prevent
// accidental reversion.
func OpenTowerDB(db kvdb.Backend) (*TowerDB, error) {
firstInit, err := isFirstInit(db)
if err != nil {
return nil, err
}
towerDB := &TowerDB{
db: db,
}
err = initOrSyncVersions(towerDB, firstInit, towerDBVersions)
if err != nil {
db.Close()
return nil, err
}
// Now that the database version fully consistent with our latest known
// version, ensure that all top-level buckets known to this version are
// initialized. This allows us to assume their presence throughout all
// operations. If an known top-level bucket is expected to exist but is
// missing, this will trigger a ErrUninitializedDB error.
err = kvdb.Update(towerDB.db, initTowerDBBuckets, func() {})
if err != nil {
db.Close()
return nil, err
}
return towerDB, nil
}
// initTowerDBBuckets creates all top-level buckets required to handle database
// operations required by the latest version.
func initTowerDBBuckets(tx kvdb.RwTx) error {
buckets := [][]byte{
sessionsBkt,
updateIndexBkt,
updatesBkt,
lookoutTipBkt,
}
for _, bucket := range buckets {
_, err := tx.CreateTopLevelBucket(bucket)
if err != nil {
return err
}
}
return nil
}
// bdb returns the backing bbolt.DB instance.
//
// NOTE: Part of the versionedDB interface.
func (t *TowerDB) bdb() kvdb.Backend {
return t.db
}
// Version returns the database's current version number.
//
// NOTE: Part of the versionedDB interface.
func (t *TowerDB) Version() (uint32, error) {
var version uint32
err := kvdb.View(t.db, func(tx kvdb.RTx) error {
var err error
version, err = getDBVersion(tx)
return err
}, func() {
version = 0
})
if err != nil {
return 0, err
}
return version, nil
}
// Close closes the underlying database.
func (t *TowerDB) Close() error {
return t.db.Close()
}
// GetSessionInfo retrieves the session for the passed session id. An error is
// returned if the session could not be found.
func (t *TowerDB) GetSessionInfo(id *SessionID) (*SessionInfo, error) {
var session *SessionInfo
err := kvdb.View(t.db, func(tx kvdb.RTx) error {
sessions := tx.ReadBucket(sessionsBkt)
if sessions == nil {
return ErrUninitializedDB
}
var err error
session, err = getSession(sessions, id[:])
return err
}, func() {
session = nil
})
if err != nil {
return nil, err
}
return session, nil
}
// InsertSessionInfo records a negotiated session in the tower database. An
// error is returned if the session already exists.
func (t *TowerDB) InsertSessionInfo(session *SessionInfo) error {
return kvdb.Update(t.db, func(tx kvdb.RwTx) error {
sessions := tx.ReadWriteBucket(sessionsBkt)
if sessions == nil {
return ErrUninitializedDB
}
updateIndex := tx.ReadWriteBucket(updateIndexBkt)
if updateIndex == nil {
return ErrUninitializedDB
}
dbSession, err := getSession(sessions, session.ID[:])
switch {
case err == ErrSessionNotFound:
// proceed.
case err != nil:
return err
case dbSession.LastApplied > 0:
return ErrSessionAlreadyExists
}
// Perform a quick sanity check on the session policy before
// accepting.
if err := session.Policy.Validate(); err != nil {
return err
}
err = putSession(sessions, session)
if err != nil {
return err
}
// Initialize the session-hint index which will be used to track
// all updates added for this session. Upon deletion, we will
// consult the index to determine exactly which updates should
// be deleted without needing to iterate over the entire
// database.
return touchSessionHintBkt(updateIndex, &session.ID)
}, func() {})
}
// InsertStateUpdate stores an update sent by the client after validating that
// the update is well-formed in the context of other updates sent for the same
// session. This include verifying that the sequence number is incremented
// properly and the last applied values echoed by the client are sane.
func (t *TowerDB) InsertStateUpdate(update *SessionStateUpdate) (uint16, error) {
var lastApplied uint16
err := kvdb.Update(t.db, func(tx kvdb.RwTx) error {
sessions := tx.ReadWriteBucket(sessionsBkt)
if sessions == nil {
return ErrUninitializedDB
}
updates := tx.ReadWriteBucket(updatesBkt)
if updates == nil {
return ErrUninitializedDB
}
updateIndex := tx.ReadWriteBucket(updateIndexBkt)
if updateIndex == nil {
return ErrUninitializedDB
}
// Fetch the session corresponding to the update's session id.
// This will be used to validate that the update's sequence
// number and last applied values are sane.
session, err := getSession(sessions, update.ID[:])
if err != nil {
return err
}
commitType, err := session.Policy.BlobType.CommitmentType(nil)
if err != nil {
return err
}
kit, err := commitType.EmptyJusticeKit()
if err != nil {
return err
}
// Assert that the blob is the correct size for the session's
// blob type.
expBlobSize := blob.Size(kit)
if len(update.EncryptedBlob) != expBlobSize {
return ErrInvalidBlobSize
}
// Validate the update against the current state of the session.
err = session.AcceptUpdateSequence(
update.SeqNum, update.LastApplied,
)
if err != nil {
return err
}
// Validation succeeded, therefore the update is committed and
// the session's last applied value is equal to the update's
// sequence number.
lastApplied = session.LastApplied
// Store the updated session to persist the updated last applied
// values.
err = putSession(sessions, session)
if err != nil {
return err
}
// Create or load the hint bucket for this state update's hint
// and write the given update.
hints, err := updates.CreateBucketIfNotExists(update.Hint[:])
if err != nil {
return err
}
var b bytes.Buffer
err = update.Encode(&b)
if err != nil {
return err
}
err = hints.Put(update.ID[:], b.Bytes())
if err != nil {
return err
}
// Finally, create an entry in the update index to track this
// hint under its session id. This will allow us to delete the
// entries efficiently if the session is ever removed.
return putHintForSession(updateIndex, &update.ID, update.Hint)
}, func() {
lastApplied = 0
})
if err != nil {
return 0, err
}
return lastApplied, nil
}
// DeleteSession removes all data associated with a particular session id from
// the tower's database.
func (t *TowerDB) DeleteSession(target SessionID) error {
return kvdb.Update(t.db, func(tx kvdb.RwTx) error {
sessions := tx.ReadWriteBucket(sessionsBkt)
if sessions == nil {
return ErrUninitializedDB
}
updates := tx.ReadWriteBucket(updatesBkt)
if updates == nil {
return ErrUninitializedDB
}
updateIndex := tx.ReadWriteBucket(updateIndexBkt)
if updateIndex == nil {
return ErrUninitializedDB
}
// Fail if the session doesn't exit.
_, err := getSession(sessions, target[:])
if err != nil {
return err
}
// Remove the target session.
err = sessions.Delete(target[:])
if err != nil {
return err
}
// Next, check the update index for any hints that were added
// under this session.
hints, err := getHintsForSession(updateIndex, &target)
if err != nil {
return err
}
for _, hint := range hints {
// Remove the state updates for any blobs stored under
// the target session identifier.
updatesForHint := updates.NestedReadWriteBucket(hint[:])
if updatesForHint == nil {
continue
}
update := updatesForHint.Get(target[:])
if update == nil {
continue
}
err := updatesForHint.Delete(target[:])
if err != nil {
return err
}
// If this was the last state update, we can also remove
// the hint that would map to an empty set.
err = isBucketEmpty(updatesForHint)
switch {
// Other updates exist for this hint, keep the bucket.
case err == errBucketNotEmpty:
continue
// Unexpected error.
case err != nil:
return err
// No more updates for this hint, prune hint bucket.
default:
err = updates.DeleteNestedBucket(hint[:])
if err != nil {
return err
}
}
}
// Finally, remove this session from the update index, which
// also removes any of the indexed hints beneath it.
return removeSessionHintBkt(updateIndex, &target)
}, func() {})
}
// QueryMatches searches against all known state updates for any that match the
// passed breachHints. More than one Match will be returned for a given hint if
// they exist in the database.
func (t *TowerDB) QueryMatches(breachHints []blob.BreachHint) ([]Match, error) {
var matches []Match
err := kvdb.View(t.db, func(tx kvdb.RTx) error {
sessions := tx.ReadBucket(sessionsBkt)
if sessions == nil {
return ErrUninitializedDB
}
updates := tx.ReadBucket(updatesBkt)
if updates == nil {
return ErrUninitializedDB
}
// Iterate through the target breach hints, appending any
// matching updates to the set of matches.
for _, hint := range breachHints {
// If a bucket does not exist for this hint, no matches
// are known.
updatesForHint := updates.NestedReadBucket(hint[:])
if updatesForHint == nil {
continue
}
// Otherwise, iterate through all (session id, update)
// pairs, creating a Match for each.
err := updatesForHint.ForEach(func(k, v []byte) error {
// Load the session via the session id for this
// update. The session info contains further
// instructions for how to process the state
// update.
session, err := getSession(sessions, k)
switch {
case err == ErrSessionNotFound:
log.Warnf("Missing session=%x for "+
"matched state update hint=%x",
k, hint)
return nil
case err != nil:
return err
}
// Decode the state update containing the
// encrypted blob.
update := &SessionStateUpdate{}
err = update.Decode(bytes.NewReader(v))
if err != nil {
return err
}
var id SessionID
copy(id[:], k)
// Construct the final match using the found
// update and its session info.
match := Match{
ID: id,
SeqNum: update.SeqNum,
Hint: hint,
EncryptedBlob: update.EncryptedBlob,
SessionInfo: session,
}
matches = append(matches, match)
return nil
})
if err != nil {
return err
}
}
return nil
}, func() {
matches = nil
})
if err != nil {
return nil, err
}
return matches, nil
}
// SetLookoutTip stores the provided epoch as the latest lookout tip epoch in
// the tower database.
func (t *TowerDB) SetLookoutTip(epoch *chainntnfs.BlockEpoch) error {
return kvdb.Update(t.db, func(tx kvdb.RwTx) error {
lookoutTip := tx.ReadWriteBucket(lookoutTipBkt)
if lookoutTip == nil {
return ErrUninitializedDB
}
return putLookoutEpoch(lookoutTip, epoch)
}, func() {})
}
// GetLookoutTip retrieves the current lookout tip block epoch from the tower
// database.
func (t *TowerDB) GetLookoutTip() (*chainntnfs.BlockEpoch, error) {
var epoch *chainntnfs.BlockEpoch
err := kvdb.View(t.db, func(tx kvdb.RTx) error {
lookoutTip := tx.ReadBucket(lookoutTipBkt)
if lookoutTip == nil {
return ErrUninitializedDB
}
epoch = getLookoutEpoch(lookoutTip)
return nil
}, func() {
epoch = nil
})
if err != nil {
return nil, err
}
return epoch, nil
}
// getSession retrieves the session info from the sessions bucket identified by
// its session id. An error is returned if the session is not found or a
// deserialization error occurs.
func getSession(sessions kvdb.RBucket, id []byte) (*SessionInfo, error) {
sessionBytes := sessions.Get(id)
if sessionBytes == nil {
return nil, ErrSessionNotFound
}
var session SessionInfo
err := session.Decode(bytes.NewReader(sessionBytes))
if err != nil {
return nil, err
}
return &session, nil
}
// putSession stores the session info in the sessions bucket identified by its
// session id. An error is returned if a serialization error occurs.
func putSession(sessions kvdb.RwBucket, session *SessionInfo) error {
var b bytes.Buffer
err := session.Encode(&b)
if err != nil {
return err
}
return sessions.Put(session.ID[:], b.Bytes())
}
// touchSessionHintBkt initializes the session-hint bucket for a particular
// session id. This ensures that future calls to getHintsForSession or
// putHintForSession can rely on the bucket already being created, and fail if
// index has not been initialized as this points to improper usage.
func touchSessionHintBkt(updateIndex kvdb.RwBucket, id *SessionID) error {
_, err := updateIndex.CreateBucketIfNotExists(id[:])
return err
}
// removeSessionHintBkt prunes the session-hint bucket for the given session id
// and all of the hints contained inside. This should be used to clean up the
// index upon session deletion.
func removeSessionHintBkt(updateIndex kvdb.RwBucket, id *SessionID) error {
return updateIndex.DeleteNestedBucket(id[:])
}
// getHintsForSession returns all known hints belonging to the given session id.
// If the index for the session has not been initialized, this method returns
// ErrNoSessionHintIndex.
func getHintsForSession(updateIndex kvdb.RBucket,
id *SessionID) ([]blob.BreachHint, error) {
sessionHints := updateIndex.NestedReadBucket(id[:])
if sessionHints == nil {
return nil, ErrNoSessionHintIndex
}
var hints []blob.BreachHint
err := sessionHints.ForEach(func(k, _ []byte) error {
if len(k) != blob.BreachHintSize {
return nil
}
var hint blob.BreachHint
copy(hint[:], k)
hints = append(hints, hint)
return nil
})
if err != nil {
return nil, err
}
return hints, nil
}
// putHintForSession inserts a record into the update index for a given
// (session, hint) pair. The hints are coalesced under a bucket for the target
// session id, and used to perform efficient removal of updates. If the index
// for the session has not been initialized, this method returns
// ErrNoSessionHintIndex.
func putHintForSession(updateIndex kvdb.RwBucket, id *SessionID,
hint blob.BreachHint) error {
sessionHints := updateIndex.NestedReadWriteBucket(id[:])
if sessionHints == nil {
return ErrNoSessionHintIndex
}
return sessionHints.Put(hint[:], []byte{})
}
// putLookoutEpoch stores the given lookout tip block epoch in provided bucket.
func putLookoutEpoch(bkt kvdb.RwBucket, epoch *chainntnfs.BlockEpoch) error {
epochBytes := make([]byte, 36)
copy(epochBytes, epoch.Hash[:])
byteOrder.PutUint32(epochBytes[32:], uint32(epoch.Height))
return bkt.Put(lookoutTipKey, epochBytes)
}
// getLookoutEpoch retrieves the lookout tip block epoch from the given bucket.
// A nil epoch is returned if no update exists.
func getLookoutEpoch(bkt kvdb.RBucket) *chainntnfs.BlockEpoch {
epochBytes := bkt.Get(lookoutTipKey)
if len(epochBytes) != 36 {
return nil
}
var hash chainhash.Hash
copy(hash[:], epochBytes[:32])
height := byteOrder.Uint32(epochBytes[32:])
return &chainntnfs.BlockEpoch{
Hash: &hash,
Height: int32(height),
}
}
// errBucketNotEmpty is a helper error returned when testing whether a bucket is
// empty or not.
var errBucketNotEmpty = errors.New("bucket not empty")
// isBucketEmpty returns errBucketNotEmpty if the bucket is not empty.
func isBucketEmpty(bkt kvdb.RBucket) error {
return bkt.ForEach(func(_, _ []byte) error {
return errBucketNotEmpty
})
}
package wtdb
import (
"fmt"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration1"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration2"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration3"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration4"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration5"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration6"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration7"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration8"
)
// txMigration is a function which takes a prior outdated version of the
// database instances and mutates the key/bucket structure to arrive at a more
// up-to-date version of the database. It uses an existing database transaction
// to do so.
type txMigration func(tx kvdb.RwTx) error
// dbMigration is a function which takes a prior outdated version of the
// database instances and mutates the key/bucket structure to arrive at a more
// up-to-date version of the database. If such a migration is defined, the
// migration is responsible for starting any database transactions. This
// migration type is useful in the case where it would be beneficial (in terms
// of RAM usage) to split the migration between multiple db transactions.
type dbMigration func(db kvdb.Backend) error
// version pairs a version number with the migration that would need to be
// applied from the prior version to upgrade.
type version struct {
txMigration txMigration
dbMigration dbMigration
}
// towerDBVersions stores all versions and migrations of the tower database.
// This list will be used when opening the database to determine if any
// migrations must be applied.
var towerDBVersions = []version{}
// clientDBVersions stores all versions and migrations of the client database.
// This list will be used when opening the database to determine if any
// migrations must be applied.
var clientDBVersions = []version{
{
txMigration: migration1.MigrateTowerToSessionIndex,
},
{
txMigration: migration2.MigrateClientChannelDetails,
},
{
txMigration: migration3.MigrateChannelIDIndex,
},
{
dbMigration: migration4.MigrateAckedUpdates(
migration4.DefaultSessionsPerTx,
),
},
{
txMigration: migration5.MigrateCompleteTowerToSessionIndex,
},
{
txMigration: migration6.MigrateSessionIDIndex,
},
{
txMigration: migration7.MigrateChannelToSessionIndex,
},
{
txMigration: migration8.MigrateChannelMaxHeights,
},
}
// getLatestDBVersion returns the last known database version.
func getLatestDBVersion(versions []version) uint32 {
return uint32(len(versions))
}
// LatestDBMigrationVersion returns the number of the latest existing database
// migration version available.
func LatestDBMigrationVersion() uint32 {
return getLatestDBVersion(clientDBVersions)
}
// getMigrations returns a slice of all updates with a greater number that
// curVersion that need to be applied to sync up with the latest version.
func getMigrations(versions []version, curVersion uint32) []version {
var updates []version
for i, v := range versions {
if uint32(i)+1 > curVersion {
updates = append(updates, v)
}
}
return updates
}
// CurrentDatabaseVersion reads the current database version from the database
// and returns it.
func CurrentDatabaseVersion(db kvdb.Backend) (uint32, error) {
var (
version uint32
err error
)
err = kvdb.View(db, func(tx kvdb.RTx) error {
version, err = getDBVersion(tx)
return err
}, func() {
version = 0
})
if err != nil {
return 0, err
}
return version, nil
}
// getDBVersion retrieves the current database version from the metadata bucket
// using the dbVersionKey.
func getDBVersion(tx kvdb.RTx) (uint32, error) {
metadata := tx.ReadBucket(metadataBkt)
if metadata == nil {
return 0, ErrUninitializedDB
}
versionBytes := metadata.Get(dbVersionKey)
if len(versionBytes) != 4 {
return 0, ErrNoDBVersion
}
return byteOrder.Uint32(versionBytes), nil
}
// initDBVersion initializes the top-level metadata bucket and writes the passed
// version number as the current version.
func initDBVersion(tx kvdb.RwTx, version uint32) error {
_, err := tx.CreateTopLevelBucket(metadataBkt)
if err != nil {
return err
}
return putDBVersion(tx, version)
}
// putDBVersion stores the passed database version in the metadata bucket under
// the dbVersionKey.
func putDBVersion(tx kvdb.RwTx, version uint32) error {
metadata := tx.ReadWriteBucket(metadataBkt)
if metadata == nil {
return ErrUninitializedDB
}
versionBytes := make([]byte, 4)
byteOrder.PutUint32(versionBytes, version)
return metadata.Put(dbVersionKey, versionBytes)
}
// versionedDB is a private interface implemented by both the tower and client
// databases, permitting all versioning operations to be performed generically
// on either.
type versionedDB interface {
// bdb returns the underlying bbolt database.
bdb() kvdb.Backend
// Version returns the current version stored in the database.
Version() (uint32, error)
}
// initOrSyncVersions ensures that the database version is properly set before
// opening the database up for regular use. When the database is being
// initialized for the first time, the caller should set init to true, which
// will simply write the latest version to the database. Otherwise, passing init
// as false will cause the database to apply any needed migrations to ensure its
// version matches the latest version in the provided versions list.
func initOrSyncVersions(db versionedDB, init bool, versions []version) error {
// If the database has not yet been created, we'll initialize the
// database version with the latest known version.
if init {
return kvdb.Update(db.bdb(), func(tx kvdb.RwTx) error {
return initDBVersion(tx, getLatestDBVersion(versions))
}, func() {})
}
// Otherwise, ensure that any migrations are applied to ensure the data
// is in the format expected by the latest version.
return syncVersions(db, versions)
}
// syncVersions ensures the database version is consistent with the highest
// known database version, applying any migrations that have not been made. If
// the highest known version number is lower than the database's version, this
// method will fail to prevent accidental reversions.
func syncVersions(db versionedDB, versions []version) error {
curVersion, err := db.Version()
if err != nil {
return err
}
latestVersion := getLatestDBVersion(versions)
switch {
// Current version is higher than any known version, fail to prevent
// reversion.
case curVersion > latestVersion:
return channeldb.ErrDBReversion
// Current version matches highest known version, nothing to do.
case curVersion == latestVersion:
return nil
}
// Otherwise, apply any migrations in order to bring the database
// version up to the highest known version.
updates := getMigrations(versions, curVersion)
for i, update := range updates {
if update.dbMigration != nil && update.txMigration != nil {
return fmt.Errorf("cannot specify both a " +
"tx-migration and a db-migration for a " +
"single version")
}
version := curVersion + uint32(i) + 1
log.Infof("Applying migration #%d", version)
if update.dbMigration != nil {
err = update.dbMigration(db.bdb())
if err != nil {
log.Errorf("Unable to apply migration #%d: %v",
version, err)
return err
}
// Note that unlike a txMigration, here we update the
// db version in a transaction that is separate to the
// transaction in which the db migration took place.
// This means that the db migration function must be
// idempotent.
err = kvdb.Update(db.bdb(), func(tx kvdb.RwTx) error {
return putDBVersion(tx, version)
}, func() {})
if err != nil {
return err
}
continue
}
if update.txMigration == nil {
continue
}
err = kvdb.Update(db.bdb(), func(tx kvdb.RwTx) error {
err := update.txMigration(tx)
if err != nil {
log.Errorf("Unable to apply migration #%d: %v",
version, err)
return err
}
return putDBVersion(tx, version)
}, func() {})
if err != nil {
return err
}
}
return nil
}
package wtmock
import (
"sync"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/keychain"
)
// SecretKeyRing is a mock, in-memory implementation for deriving private keys.
type SecretKeyRing struct {
mu sync.Mutex
keys map[keychain.KeyLocator]*btcec.PrivateKey
}
// NewSecretKeyRing creates a new mock SecretKeyRing.
func NewSecretKeyRing() *SecretKeyRing {
return &SecretKeyRing{
keys: make(map[keychain.KeyLocator]*btcec.PrivateKey),
}
}
// DeriveKey attempts to derive an arbitrary key specified by the
// passed KeyLocator. This may be used in several recovery scenarios,
// or when manually rotating something like our current default node
// key.
//
// NOTE: This is part of the wtclient.ECDHKeyRing interface.
func (m *SecretKeyRing) DeriveKey(
keyLoc keychain.KeyLocator) (keychain.KeyDescriptor, error) {
m.mu.Lock()
defer m.mu.Unlock()
if key, ok := m.keys[keyLoc]; ok {
return keychain.KeyDescriptor{
KeyLocator: keyLoc,
PubKey: key.PubKey(),
}, nil
}
privKey, err := btcec.NewPrivateKey()
if err != nil {
return keychain.KeyDescriptor{}, err
}
m.keys[keyLoc] = privKey
return keychain.KeyDescriptor{
KeyLocator: keyLoc,
PubKey: privKey.PubKey(),
}, nil
}
// ECDH performs a scalar multiplication (ECDH-like operation) between the
// target key descriptor and remote public key. The output returned will be the
// sha256 of the resulting shared point serialized in compressed format. If k is
// our private key, and P is the public key, we perform the following operation:
//
// sx := k*P
// s := sha256(sx.SerializeCompressed())
//
// NOTE: This is part of the wtclient.ECDHKeyRing interface.
func (m *SecretKeyRing) ECDH(keyDesc keychain.KeyDescriptor,
pub *btcec.PublicKey) ([32]byte, error) {
_, err := m.DeriveKey(keyDesc.KeyLocator)
if err != nil {
return [32]byte{}, err
}
privKey := m.keys[keyDesc.KeyLocator]
ecdh := &keychain.PrivKeyECDH{PrivKey: privKey}
return ecdh.ECDH(pub)
}
package wtmock
import (
"fmt"
"net"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/watchtower/wtserver"
)
// MockPeer emulates a single endpoint of brontide transport.
type MockPeer struct {
remotePub *btcec.PublicKey
remoteAddr net.Addr
localPub *btcec.PublicKey
localAddr net.Addr
IncomingMsgs chan []byte
OutgoingMsgs chan []byte
writeDeadline <-chan time.Time
readDeadline <-chan time.Time
RemoteQuit chan struct{}
Quit chan struct{}
}
// NewMockPeer returns a fresh MockPeer.
func NewMockPeer(lpk, rpk *btcec.PublicKey, addr net.Addr,
bufferSize int) *MockPeer {
return &MockPeer{
remotePub: rpk,
remoteAddr: addr,
localAddr: &net.TCPAddr{
IP: net.IP{0x32, 0x31, 0x30, 0x29},
Port: 36723,
},
localPub: lpk,
IncomingMsgs: make(chan []byte, bufferSize),
OutgoingMsgs: make(chan []byte, bufferSize),
Quit: make(chan struct{}),
}
}
// NewMockConn establishes a bidirectional connection between two MockPeers.
func NewMockConn(localPk, remotePk *btcec.PublicKey,
localAddr, remoteAddr net.Addr,
bufferSize int) (*MockPeer, *MockPeer) {
localPeer := &MockPeer{
remotePub: remotePk,
remoteAddr: remoteAddr,
localPub: localPk,
localAddr: localAddr,
IncomingMsgs: make(chan []byte, bufferSize),
OutgoingMsgs: make(chan []byte, bufferSize),
Quit: make(chan struct{}),
}
remotePeer := &MockPeer{
remotePub: localPk,
remoteAddr: localAddr,
localPub: remotePk,
localAddr: remoteAddr,
IncomingMsgs: localPeer.OutgoingMsgs,
OutgoingMsgs: localPeer.IncomingMsgs,
Quit: make(chan struct{}),
}
localPeer.RemoteQuit = remotePeer.Quit
remotePeer.RemoteQuit = localPeer.Quit
return localPeer, remotePeer
}
// Write sends the raw bytes as the next full message read to the remote peer.
// The write will fail if either party closes the connection or the write
// deadline expires. The passed bytes slice is copied before sending, thus the
// bytes may be reused once the method returns.
func (p *MockPeer) Write(b []byte) (n int, err error) {
bb := make([]byte, len(b))
copy(bb, b)
select {
case p.OutgoingMsgs <- bb:
return len(b), nil
case <-p.writeDeadline:
return 0, fmt.Errorf("write timeout expired")
case <-p.RemoteQuit:
return 0, fmt.Errorf("remote closed connected")
case <-p.Quit:
return 0, fmt.Errorf("connection closed")
}
}
// Close tears down the connection, and fails any pending reads or writes.
func (p *MockPeer) Close() error {
select {
case <-p.Quit:
return fmt.Errorf("connection already closed")
default:
close(p.Quit)
return nil
}
}
// ReadNextMessage returns the raw bytes of the next full message read from the
// remote peer. The read will fail if either party closes the connection or the
// read deadline expires.
func (p *MockPeer) ReadNextMessage() ([]byte, error) {
select {
case b := <-p.IncomingMsgs:
return b, nil
case <-p.readDeadline:
return nil, fmt.Errorf("read timeout expired")
case <-p.RemoteQuit:
return nil, fmt.Errorf("remote closed connected")
case <-p.Quit:
return nil, fmt.Errorf("connection closed")
}
}
// SetWriteDeadline initializes a timer that will cause any pending writes to
// fail at time t. If t is zero, the deadline is infinite.
func (p *MockPeer) SetWriteDeadline(t time.Time) error {
if t.IsZero() {
p.writeDeadline = nil
return nil
}
duration := time.Until(t)
p.writeDeadline = time.After(duration)
return nil
}
// SetReadDeadline initializes a timer that will cause any pending reads to fail
// at time t. If t is zero, the deadline is infinite.
func (p *MockPeer) SetReadDeadline(t time.Time) error {
if t.IsZero() {
p.readDeadline = nil
return nil
}
duration := time.Until(t)
p.readDeadline = time.After(duration)
return nil
}
// RemotePub returns the public key of the remote peer.
func (p *MockPeer) RemotePub() *btcec.PublicKey {
return p.remotePub
}
// RemoteAddr returns the net address of the remote peer.
func (p *MockPeer) RemoteAddr() net.Addr {
return p.remoteAddr
}
// LocalAddr returns the local net address of the peer.
func (p *MockPeer) LocalAddr() net.Addr {
return p.localAddr
}
// Read is not implemented.
func (p *MockPeer) Read(dst []byte) (int, error) {
panic("not implemented")
}
// SetDeadline is not implemented.
func (p *MockPeer) SetDeadline(t time.Time) error {
panic("not implemented")
}
// Compile-time constraint ensuring the MockPeer implements the wserver.Peer
// interface.
var _ wtserver.Peer = (*MockPeer)(nil)
// Compile-time constraint ensuring the MockPeer implements the net.Conn
// interface.
var _ net.Conn = (*MockPeer)(nil)
package wtmock
import (
"container/list"
"fmt"
"sync"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
)
// DiskQueueDB is an in-memory implementation of the wtclient.Queue interface.
type DiskQueueDB[T any] struct {
disk *list.List
mu sync.RWMutex
}
// NewQueueDB constructs a new DiskQueueDB.
func NewQueueDB[T any]() wtdb.Queue[T] {
return &DiskQueueDB[T]{
disk: list.New(),
}
}
// Len returns the number of tasks in the queue.
//
// NOTE: This is part of the wtclient.Queue interface.
func (d *DiskQueueDB[T]) Len() (uint64, error) {
d.mu.Lock()
defer d.mu.Unlock()
return uint64(d.disk.Len()), nil
}
// Push adds new T items to the tail of the queue.
//
// NOTE: This is part of the wtclient.Queue interface.
func (d *DiskQueueDB[T]) Push(items ...T) error {
d.mu.Lock()
defer d.mu.Unlock()
for _, item := range items {
d.disk.PushBack(item)
}
return nil
}
// PopUpTo attempts to pop up to n items from the queue. If the queue is empty,
// then ErrEmptyQueue is returned.
//
// NOTE: This is part of the Queue interface.
func (d *DiskQueueDB[T]) PopUpTo(n int) ([]T, error) {
d.mu.Lock()
defer d.mu.Unlock()
if d.disk.Len() == 0 {
return nil, wtdb.ErrEmptyQueue
}
num := n
if d.disk.Len() < n {
num = d.disk.Len()
}
tasks := make([]T, 0, num)
for i := 0; i < num; i++ {
e := d.disk.Front()
task, ok := d.disk.Remove(e).(T)
if !ok {
return nil, fmt.Errorf("queue item not of type %T",
task)
}
tasks = append(tasks, task)
}
return tasks, nil
}
// PushHead pushes new T items to the head of the queue.
//
// NOTE: This is part of the wtclient.Queue interface.
func (d *DiskQueueDB[T]) PushHead(items ...T) error {
d.mu.Lock()
defer d.mu.Unlock()
for i := len(items) - 1; i >= 0; i-- {
d.disk.PushFront(items[i])
}
return nil
}
package wtmock
import (
"crypto/sha256"
"fmt"
"sync"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
)
// MockSigner is an input.Signer that allows one to add arbitrary private keys
// and sign messages by passing the assigned keychain.KeyLocator.
type MockSigner struct {
mu sync.Mutex
index uint32
keys map[keychain.KeyLocator]*btcec.PrivateKey
}
// NewMockSigner returns a fresh MockSigner.
func NewMockSigner() *MockSigner {
return &MockSigner{
keys: make(map[keychain.KeyLocator]*btcec.PrivateKey),
}
}
// SignOutputRaw signs an input on the passed transaction using the input index
// in the sign descriptor. The returned signature is the raw DER-encoded
// signature without the signhash flag.
func (s *MockSigner) SignOutputRaw(tx *wire.MsgTx,
signDesc *input.SignDescriptor) (input.Signature, error) {
s.mu.Lock()
defer s.mu.Unlock()
witnessScript := signDesc.WitnessScript
amt := signDesc.Output.Value
privKey, ok := s.keys[signDesc.KeyDesc.KeyLocator]
if !ok {
panic("cannot sign w/ unknown key")
}
// In case of a taproot output any signature is always a Schnorr
// signature, based on the new tapscript sighash algorithm.
if txscript.IsPayToTaproot(signDesc.Output.PkScript) {
sigHashes := txscript.NewTxSigHashes(
tx, signDesc.PrevOutputFetcher,
)
// Are we spending a script path or the key path? The API is
// slightly different, so we need to account for that to get the
// raw signature.
var (
rawSig []byte
err error
)
switch signDesc.SignMethod {
case input.TaprootKeySpendBIP0086SignMethod,
input.TaprootKeySpendSignMethod:
// This function tweaks the private key using the tap
// root key supplied as the tweak.
rawSig, err = txscript.RawTxInTaprootSignature(
tx, sigHashes, signDesc.InputIndex,
signDesc.Output.Value, signDesc.Output.PkScript,
signDesc.TapTweak, signDesc.HashType,
privKey,
)
if err != nil {
return nil, err
}
case input.TaprootScriptSpendSignMethod:
leaf := txscript.TapLeaf{
LeafVersion: txscript.BaseLeafVersion,
Script: witnessScript,
}
rawSig, err = txscript.RawTxInTapscriptSignature(
tx, sigHashes, signDesc.InputIndex,
signDesc.Output.Value, signDesc.Output.PkScript,
leaf, signDesc.HashType, privKey,
)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unknown sign method: %v",
signDesc.SignMethod)
}
// The signature returned above might have a sighash flag
// attached if a non-default type was used. We'll slice this
// off if it exists to ensure we can properly parse the raw
// signature.
sig, err := schnorr.ParseSignature(
rawSig[:schnorr.SignatureSize],
)
if err != nil {
return nil, err
}
return sig, nil
}
sig, err := txscript.RawTxInWitnessSignature(
tx, signDesc.SigHashes, signDesc.InputIndex, amt,
witnessScript, signDesc.HashType, privKey,
)
if err != nil {
return nil, err
}
return ecdsa.ParseDERSignature(sig[:len(sig)-1])
}
// ComputeInputScript is not implemented.
func (s *MockSigner) ComputeInputScript(tx *wire.MsgTx,
signDesc *input.SignDescriptor) (*input.Script, error) {
panic("not implemented")
}
// MuSig2CreateSession creates a new MuSig2 signing session using the local
// key identified by the key locator. The complete list of all public keys of
// all signing parties must be provided, including the public key of the local
// signing key. If nonces of other parties are already known, they can be
// submitted as well to reduce the number of method calls necessary later on.
func (s *MockSigner) MuSig2CreateSession(input.MuSig2Version,
keychain.KeyLocator, []*btcec.PublicKey, *input.MuSig2Tweaks,
[][musig2.PubNonceSize]byte,
*musig2.Nonces) (*input.MuSig2SessionInfo, error) {
return nil, nil
}
// MuSig2RegisterNonces registers one or more public nonces of other signing
// participants for a session identified by its ID. This method returns true
// once we have all nonces for all other signing participants.
func (s *MockSigner) MuSig2RegisterNonces(input.MuSig2SessionID,
[][musig2.PubNonceSize]byte) (bool, error) {
return false, nil
}
// MuSig2Sign creates a partial signature using the local signing key
// that was specified when the session was created. This can only be
// called when all public nonces of all participants are known and have
// been registered with the session. If this node isn't responsible for
// combining all the partial signatures, then the cleanup parameter
// should be set, indicating that the session can be removed from memory
// once the signature was produced.
func (s *MockSigner) MuSig2Sign(input.MuSig2SessionID,
[sha256.Size]byte, bool) (*musig2.PartialSignature, error) {
return nil, nil
}
// MuSig2CombineSig combines the given partial signature(s) with the
// local one, if it already exists. Once a partial signature of all
// participants is registered, the final signature will be combined and
// returned.
func (s *MockSigner) MuSig2CombineSig(input.MuSig2SessionID,
[]*musig2.PartialSignature) (*schnorr.Signature, bool, error) {
return nil, false, nil
}
// MuSig2Cleanup removes a session from memory to free up resources.
func (s *MockSigner) MuSig2Cleanup(input.MuSig2SessionID) error {
return nil
}
// AddPrivKey records the passed privKey in the MockSigner's registry of keys it
// can sign with in the future. A unique key locator is returned, allowing the
// caller to sign with this key when presented via an input.SignDescriptor.
func (s *MockSigner) AddPrivKey(privKey *btcec.PrivateKey) keychain.KeyLocator {
s.mu.Lock()
defer s.mu.Unlock()
keyLoc := keychain.KeyLocator{
Index: s.index,
}
s.index++
s.keys[keyLoc] = privKey
return keyLoc
}
// Compile-time constraint ensuring the MockSigner implements the input.Signer
// interface.
var _ input.Signer = (*MockSigner)(nil)
package wtmock
import (
"sync"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
)
// TowerDB is a mock, in-memory implementation of a watchtower.DB.
type TowerDB struct {
mu sync.Mutex
lastEpoch *chainntnfs.BlockEpoch
sessions map[wtdb.SessionID]*wtdb.SessionInfo
blobs map[blob.BreachHint]map[wtdb.SessionID]*wtdb.SessionStateUpdate
}
// NewTowerDB initializes a fresh mock TowerDB.
func NewTowerDB() *TowerDB {
return &TowerDB{
sessions: make(map[wtdb.SessionID]*wtdb.SessionInfo),
blobs: make(map[blob.BreachHint]map[wtdb.SessionID]*wtdb.SessionStateUpdate),
}
}
// InsertStateUpdate stores an update sent by the client after validating that
// the update is well-formed in the context of other updates sent for the same
// session. This include verifying that the sequence number is incremented
// properly and the last applied values echoed by the client are sane.
func (db *TowerDB) InsertStateUpdate(update *wtdb.SessionStateUpdate) (uint16, error) {
db.mu.Lock()
defer db.mu.Unlock()
info, ok := db.sessions[update.ID]
if !ok {
return 0, wtdb.ErrSessionNotFound
}
commitType, err := info.Policy.BlobType.CommitmentType(nil)
if err != nil {
return 0, err
}
kit, err := commitType.EmptyJusticeKit()
if err != nil {
return 0, err
}
// Assert that the blob is the correct size for the session's blob type.
if len(update.EncryptedBlob) != blob.Size(kit) {
return 0, wtdb.ErrInvalidBlobSize
}
err = info.AcceptUpdateSequence(update.SeqNum, update.LastApplied)
if err != nil {
return info.LastApplied, err
}
sessionsToUpdates, ok := db.blobs[update.Hint]
if !ok {
sessionsToUpdates = make(map[wtdb.SessionID]*wtdb.SessionStateUpdate)
db.blobs[update.Hint] = sessionsToUpdates
}
sessionsToUpdates[update.ID] = update
return info.LastApplied, nil
}
// GetSessionInfo retrieves the session for the passed session id. An error is
// returned if the session could not be found.
func (db *TowerDB) GetSessionInfo(id *wtdb.SessionID) (*wtdb.SessionInfo, error) {
db.mu.Lock()
defer db.mu.Unlock()
if info, ok := db.sessions[*id]; ok {
return info, nil
}
return nil, wtdb.ErrSessionNotFound
}
// InsertSessionInfo records a negotiated session in the tower database. An
// error is returned if the session already exists.
func (db *TowerDB) InsertSessionInfo(info *wtdb.SessionInfo) error {
db.mu.Lock()
defer db.mu.Unlock()
dbInfo, ok := db.sessions[info.ID]
if ok && dbInfo.LastApplied > 0 {
return wtdb.ErrSessionAlreadyExists
}
// Perform a quick sanity check on the session policy before accepting.
if err := info.Policy.Validate(); err != nil {
return err
}
db.sessions[info.ID] = info
return nil
}
// DeleteSession removes all data associated with a particular session id from
// the tower's database.
func (db *TowerDB) DeleteSession(target wtdb.SessionID) error {
db.mu.Lock()
defer db.mu.Unlock()
// Fail if the session doesn't exit.
if _, ok := db.sessions[target]; !ok {
return wtdb.ErrSessionNotFound
}
// Remove the target session.
delete(db.sessions, target)
// Remove the state updates for any blobs stored under the target
// session identifier.
for hint, sessionUpdates := range db.blobs {
delete(sessionUpdates, target)
// If this was the last state update, we can also remove the
// hint that would map to an empty set.
if len(sessionUpdates) == 0 {
delete(db.blobs, hint)
}
}
return nil
}
// QueryMatches searches against all known state updates for any that match the
// passed breachHints. More than one Match will be returned for a given hint if
// they exist in the database.
func (db *TowerDB) QueryMatches(
breachHints []blob.BreachHint) ([]wtdb.Match, error) {
db.mu.Lock()
defer db.mu.Unlock()
var matches []wtdb.Match
for _, hint := range breachHints {
sessionsToUpdates, ok := db.blobs[hint]
if !ok {
continue
}
for id, update := range sessionsToUpdates {
info, ok := db.sessions[id]
if !ok {
panic("session not found")
}
match := wtdb.Match{
ID: id,
SeqNum: update.SeqNum,
Hint: hint,
EncryptedBlob: update.EncryptedBlob,
SessionInfo: info,
}
matches = append(matches, match)
}
}
return matches, nil
}
// SetLookoutTip stores the provided epoch as the latest lookout tip epoch in
// the tower database.
func (db *TowerDB) SetLookoutTip(epoch *chainntnfs.BlockEpoch) error {
db.lastEpoch = epoch
return nil
}
// GetLookoutTip retrieves the current lookout tip block epoch from the tower
// database.
func (db *TowerDB) GetLookoutTip() (*chainntnfs.BlockEpoch, error) {
db.mu.Lock()
defer db.mu.Unlock()
return db.lastEpoch, nil
}
package wtpolicy
import (
"errors"
"fmt"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/wtwire"
)
const (
// RewardScale is the denominator applied when computing the
// proportional component for a tower's reward output. The current scale
// is in millionths.
RewardScale = 1000000
// DefaultMaxUpdates specifies the number of encrypted blobs a client
// can send to the tower in a single session.
DefaultMaxUpdates = 1024
// DefaultRewardRate specifies the fraction of the channel that the
// tower takes if it successfully sweeps a breach. The value is
// expressed in millionths of the channel capacity.
DefaultRewardRate = 10000
// DefaultSweepFeeRate specifies the fee rate used to construct justice
// transactions. The value is expressed in satoshis per kilo-weight.
DefaultSweepFeeRate = chainfee.SatPerKWeight(2500)
// MinSweepFeeRate is the minimum sweep fee rate a client may use in its
// policy, the current value is 4 sat/vbyte.
MinSweepFeeRate = chainfee.SatPerKWeight(1000)
)
var (
// ErrFeeExceedsInputs signals that the total input value of breaching
// commitment txn is insufficient to cover the fees required to sweep
// it.
ErrFeeExceedsInputs = errors.New("sweep fee exceeds input value")
// ErrRewardExceedsInputs signals that the reward given to the tower (in
// addition to the transaction fees) is more than the input amount.
ErrRewardExceedsInputs = errors.New("reward amount exceeds input value")
// ErrCreatesDust signals that the session's policy would create a dust
// output for the victim.
ErrCreatesDust = errors.New("justice transaction creates dust at fee rate")
// ErrAltruistReward signals that the policy is invalid because it
// contains a non-zero RewardBase or RewardRate on an altruist policy.
ErrAltruistReward = errors.New("altruist policy has reward params")
// ErrNoMaxUpdates signals that the policy specified zero MaxUpdates.
ErrNoMaxUpdates = errors.New("max updates must be positive")
// ErrSweepFeeRateTooLow signals that the policy's fee rate is too low
// to get into the mempool during low congestion.
ErrSweepFeeRateTooLow = errors.New("sweep fee rate too low")
)
// DefaultPolicy returns a Policy containing the default parameters that can be
// used by clients or servers.
func DefaultPolicy() Policy {
return Policy{
TxPolicy: TxPolicy{
BlobType: blob.TypeAltruistCommit,
SweepFeeRate: DefaultSweepFeeRate,
},
MaxUpdates: DefaultMaxUpdates,
}
}
// TxPolicy defines the negotiate parameters that determine the form of the
// justice transaction for a given breached state. Thus, for any given revoked
// state, an identical key will result in an identical justice transaction
// (barring signatures). The parameters specify the format of encrypted blobs
// sent to the tower, the reward schedule for the tower, and the number of
// encrypted blobs a client can send in one session.
type TxPolicy struct {
// BlobType specifies the blob format that must be used by all updates sent
// under the session key used to negotiate this session.
BlobType blob.Type
// RewardBase is the fixed amount allocated to the tower when the
// policy's blob type specifies a reward for the tower. This is taken
// before adding the proportional reward.
RewardBase uint32
// RewardRate is the fraction of the total balance of the revoked
// commitment that the watchtower is entitled to. This value is
// expressed in millionths of the total balance.
RewardRate uint32
// SweepFeeRate expresses the intended fee rate to be used when
// constructing the justice transaction. All sweep transactions created
// for this session must use this value during construction, and the
// signatures must implicitly commit to the resulting output values.
SweepFeeRate chainfee.SatPerKWeight
}
// Policy defines the negotiated parameters for a session between a client and
// server. In addition to the TxPolicy that governs the shape of the justice
// transaction, the Policy also includes features which only affect the
// operation of the session.
type Policy struct {
TxPolicy
// MaxUpdates is the maximum number of updates the watchtower will honor
// for this session.
MaxUpdates uint16
}
// String returns a human-readable description of the current policy.
func (p Policy) String() string {
return fmt.Sprintf("(blob-type=%b max-updates=%d reward-rate=%d "+
"sweep-fee-rate=%d)", p.BlobType, p.MaxUpdates, p.RewardRate,
p.SweepFeeRate)
}
// FeatureBits returns the watchtower feature bits required for the given
// policy.
func (p *Policy) FeatureBits() []lnwire.FeatureBit {
features := []lnwire.FeatureBit{
wtwire.AltruistSessionsRequired,
}
t := p.TxPolicy.BlobType
switch {
case t.IsTaprootChannel():
features = append(features, wtwire.TaprootCommitRequired)
case t.IsAnchorChannel():
features = append(features, wtwire.AnchorCommitRequired)
}
return features
}
// IsAnchorChannel returns true if the session policy requires anchor channels.
func (p *Policy) IsAnchorChannel() bool {
return p.TxPolicy.BlobType.IsAnchorChannel()
}
// IsTaprootChannel returns true if the session policy requires taproot
// channels.
func (p *Policy) IsTaprootChannel() bool {
return p.TxPolicy.BlobType.IsTaprootChannel()
}
// Validate ensures that the policy satisfies some minimal correctness
// constraints.
func (p *Policy) Validate() error {
// RewardBase and RewardRate should not be set if the policy doesn't
// have a reward.
if !p.BlobType.Has(blob.FlagReward) &&
(p.RewardBase != 0 || p.RewardRate != 0) {
return ErrAltruistReward
}
// MaxUpdates must be positive.
if p.MaxUpdates == 0 {
return ErrNoMaxUpdates
}
// SweepFeeRate must be sane enough to get in the mempool during low
// congestion.
if p.SweepFeeRate < MinSweepFeeRate {
return ErrSweepFeeRateTooLow
}
return nil
}
// ComputeAltruistOutput computes the lone output value of a justice transaction
// that pays no reward to the tower. The value is computed using the weight of
// of the justice transaction and subtracting an amount that satisfies the
// policy's fee rate.
func (p *Policy) ComputeAltruistOutput(
totalAmt btcutil.Amount, txWeight lntypes.WeightUnit,
sweepScript []byte) (btcutil.Amount, error) {
txFee := p.SweepFeeRate.FeeForWeight(txWeight)
if txFee > totalAmt {
return 0, ErrFeeExceedsInputs
}
sweepAmt := totalAmt - txFee
// Check that the created outputs won't be dusty. We'll base the dust
// computation on the type of the script itself.
if sweepAmt < lnwallet.DustLimitForSize(len(sweepScript)) {
return 0, ErrCreatesDust
}
return sweepAmt, nil
}
// ComputeRewardOutputs splits the total funds in a breaching commitment
// transaction between the victim and the tower, according to the sweep fee rate
// and reward rate. The reward to he tower is subtracted first, before
// splitting the remaining balance amongst the victim and fees.
func (p *Policy) ComputeRewardOutputs(totalAmt btcutil.Amount,
txWeight lntypes.WeightUnit,
rewardScript []byte) (btcutil.Amount, btcutil.Amount, error) {
txFee := p.SweepFeeRate.FeeForWeight(txWeight)
if txFee > totalAmt {
return 0, 0, ErrFeeExceedsInputs
}
// Apply the reward rate to the remaining total, specified in millionths
// of the available balance.
rewardAmt := ComputeRewardAmount(totalAmt, p.RewardBase, p.RewardRate)
if rewardAmt+txFee > totalAmt {
return 0, 0, ErrRewardExceedsInputs
}
// The sweep amount for the victim constitutes the remainder of the
// input value.
sweepAmt := totalAmt - rewardAmt - txFee
// Check that the created outputs won't be dusty. We'll base the dust
// computation on the type of the script itself.
if sweepAmt < lnwallet.DustLimitForSize(len(rewardScript)) {
return 0, 0, ErrCreatesDust
}
return sweepAmt, rewardAmt, nil
}
// ComputeRewardAmount computes the amount rewarded to the tower using the
// proportional rate expressed in millionths, e.g. one million is equivalent to
// one hundred percent of the total amount. The amount is rounded up to the
// nearest whole satoshi.
func ComputeRewardAmount(total btcutil.Amount, base, rate uint32) btcutil.Amount {
rewardBase := btcutil.Amount(base)
rewardRate := btcutil.Amount(rate)
// If the base reward exceeds the total, there is no more funds left
// from which to derive the proportional fee. We simply return the base,
// the caller should detect that this exceeds the total amount input.
if rewardBase > total {
return rewardBase
}
// Otherwise, subtract the base from the total and compute the
// proportional reward from the remaining total.
afterBase := total - rewardBase
proportional := (afterBase*rewardRate + RewardScale - 1) / RewardScale
return rewardBase + proportional
}
// ComputeJusticeTxOuts constructs the justice transaction outputs for the
// given policy. If the policy specifies a reward for the tower, there will be
// two outputs paying to the victim and the tower. Otherwise there will be a
// single output sweeping funds back to the victim. The totalAmt should be the
// sum of any inputs used in the transaction. The passed txWeight should
// include the weight of the outputs for the justice transaction, which is
// dependent on whether the justice transaction has a reward. The sweepPkScript
// should be the pkScript of the victim to which funds will be recovered. The
// rewardPkScript is the pkScript of the tower where its reward will be
// deposited, and will be
// ignored if the blob type does not specify a reward.
func (p *Policy) ComputeJusticeTxOuts(
totalAmt btcutil.Amount, txWeight lntypes.WeightUnit,
sweepPkScript, rewardPkScript []byte) ([]*wire.TxOut, error) {
var outputs []*wire.TxOut
// If the policy specifies a reward for the tower, compute a split of
// the funds based on the policy's parameters. Otherwise, we will use
// the altruist output computation and sweep as much of the funds
// back to the victim as possible.
if p.BlobType.Has(blob.FlagReward) {
// Using the total input amount and the transaction's weight,
// compute the sweep and reward amounts. This corresponds to
// the amount returned to the victim and the amount paid to the
// tower, respectively. To do so, the required transaction fee
// is subtracted from the total, and the remaining amount is
// divided according to the pre negotiated reward rate from the
// client's session info.
sweepAmt, rewardAmt, err := p.ComputeRewardOutputs(
totalAmt, txWeight, rewardPkScript,
)
if err != nil {
return nil, err
}
// Add the sweep and reward outputs to the list of txouts.
outputs = append(outputs, &wire.TxOut{
PkScript: sweepPkScript,
Value: int64(sweepAmt),
})
outputs = append(outputs, &wire.TxOut{
PkScript: rewardPkScript,
Value: int64(rewardAmt),
})
} else {
// Using the total input amount and the transaction's weight,
// compute the sweep amount, which corresponds to the amount
// returned to the victim. To do so, the required transaction
// fee is subtracted from the total input amount.
sweepAmt, err := p.ComputeAltruistOutput(
totalAmt, txWeight, sweepPkScript,
)
if err != nil {
return nil, err
}
// Add the sweep output to the list of txouts.
outputs = append(outputs, &wire.TxOut{
PkScript: sweepPkScript,
Value: int64(sweepAmt),
})
}
return outputs, nil
}
package wtserver
import (
"github.com/btcsuite/btcd/txscript"
"github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
"github.com/lightningnetwork/lnd/watchtower/wtwire"
)
// handleCreateSession processes a CreateSession message from the peer, and returns
// a CreateSessionReply in response. This method will only succeed if no existing
// session info is known about the session id. If an existing session is found,
// the reward address is returned in case the client lost our reply.
func (s *Server) handleCreateSession(peer Peer, id *wtdb.SessionID,
req *wtwire.CreateSession) error {
// TODO(conner): validate accept against policy
// Query the db for session info belonging to the client's session id.
existingInfo, err := s.cfg.DB.GetSessionInfo(id)
switch {
// We already have a session, though it is currently unused. We'll allow
// the client to recommit the session if it wanted to change the policy.
case err == nil && existingInfo.LastApplied == 0:
// We already have a session corresponding to this session id, return an
// error signaling that it already exists in our database. We return the
// reward address to the client in case they were not able to process
// our reply earlier.
case err == nil && existingInfo.LastApplied > 0:
log.Debugf("Already have session for %s", id)
return s.replyCreateSession(
peer, id, wtwire.CreateSessionCodeAlreadyExists,
existingInfo.LastApplied, existingInfo.RewardAddress,
)
// Some other database error occurred, return a temporary failure.
case err != wtdb.ErrSessionNotFound:
log.Errorf("unable to load session info for %s", id)
return s.replyCreateSession(
peer, id, wtwire.CodeTemporaryFailure, 0, nil,
)
}
// Ensure that the requested blob type is supported by our tower.
if !blob.IsSupportedType(req.BlobType) {
log.Debugf("Rejecting CreateSession from %s, unsupported blob "+
"type %s", id, req.BlobType)
return s.replyCreateSession(
peer, id, wtwire.CreateSessionCodeRejectBlobType, 0,
nil,
)
}
// If the request asks for a reward session and the tower has them
// disabled, we will reject the request.
if s.cfg.DisableReward && req.BlobType.Has(blob.FlagReward) {
log.Debugf("Rejecting CreateSession from %s, reward "+
"sessions disabled", id)
return s.replyCreateSession(
peer, id, wtwire.CreateSessionCodeRejectBlobType, 0,
nil,
)
}
// Now that we've established that this session does not exist in the
// database, retrieve the sweep address that will be given to the
// client. This address is to be included by the client when signing
// sweep transactions destined for this tower, if its negotiated output
// is not dust.
var rewardScript []byte
if req.BlobType.Has(blob.FlagReward) {
rewardAddress, err := s.cfg.NewAddress()
if err != nil {
log.Errorf("Unable to generate reward addr for %s: %v",
id, err)
return s.replyCreateSession(
peer, id, wtwire.CodeTemporaryFailure, 0, nil,
)
}
// Construct the pkscript the client should pay to when signing
// justice transactions for this session.
rewardScript, err = txscript.PayToAddrScript(rewardAddress)
if err != nil {
log.Errorf("Unable to generate reward script for "+
"%s: %v", id, err)
return s.replyCreateSession(
peer, id, wtwire.CodeTemporaryFailure, 0, nil,
)
}
}
// TODO(conner): create invoice for upfront payment
// Assemble the session info using the agreed upon parameters, reward
// address, and session id.
info := wtdb.SessionInfo{
ID: *id,
Policy: wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{
BlobType: req.BlobType,
RewardBase: req.RewardBase,
RewardRate: req.RewardRate,
SweepFeeRate: req.SweepFeeRate,
},
MaxUpdates: req.MaxUpdates,
},
RewardAddress: rewardScript,
}
// Insert the session info into the watchtower's database. If
// successful, the session will now be ready for use.
err = s.cfg.DB.InsertSessionInfo(&info)
if err != nil {
log.Errorf("Unable to create session for %s: %v", id, err)
return s.replyCreateSession(
peer, id, wtwire.CodeTemporaryFailure, 0, nil,
)
}
log.Infof("Accepted session for %s", id)
return s.replyCreateSession(
peer, id, wtwire.CodeOK, 0, rewardScript,
)
}
// replyCreateSession sends a response to a CreateSession from a client. If the
// status code in the reply is OK, the error from the write will be bubbled up.
// Otherwise, this method returns a connection error to ensure we don't continue
// communication with the client.
func (s *Server) replyCreateSession(peer Peer, id *wtdb.SessionID,
code wtwire.ErrorCode, lastApplied uint16, data []byte) error {
if s.cfg.NoAckCreateSession {
return &connFailure{
ID: *id,
Code: code,
}
}
msg := &wtwire.CreateSessionReply{
Code: code,
LastApplied: lastApplied,
Data: data,
}
err := s.sendMessage(peer, msg)
if err != nil {
log.Errorf("unable to send CreateSessionReply to %s", id)
}
// Return the write error if the request succeeded.
if code == wtwire.CodeOK {
return err
}
// Otherwise the request failed, return a connection failure to
// disconnect the client.
return &connFailure{
ID: *id,
Code: code,
}
}
package wtserver
import (
"github.com/lightningnetwork/lnd/watchtower/wtdb"
"github.com/lightningnetwork/lnd/watchtower/wtwire"
)
// handleDeleteSession processes a DeleteSession request for a client with given
// SessionID. The id is assumed to have been previously authenticated by the
// brontide connection.
func (s *Server) handleDeleteSession(peer Peer, id *wtdb.SessionID) error {
var failCode wtwire.DeleteSessionCode
// Delete all session data associated with id.
err := s.cfg.DB.DeleteSession(*id)
switch {
case err == nil:
failCode = wtwire.CodeOK
log.Debugf("Session %s deleted", id)
case err == wtdb.ErrSessionNotFound:
failCode = wtwire.DeleteSessionCodeNotFound
default:
failCode = wtwire.CodeTemporaryFailure
}
return s.replyDeleteSession(peer, id, failCode)
}
// replyDeleteSession sends a DeleteSessionReply back to the peer containing the
// error code resulting from processes a DeleteSession request.
func (s *Server) replyDeleteSession(peer Peer, id *wtdb.SessionID,
code wtwire.DeleteSessionCode) error {
msg := &wtwire.DeleteSessionReply{
Code: code,
}
err := s.sendMessage(peer, msg)
if err != nil {
log.Errorf("Unable to send DeleteSessionReply to %s", id)
}
// Return the write error if the request succeeded.
if code == wtwire.CodeOK {
return err
}
// Otherwise the request failed, return a connection failure to
// disconnect the client.
return &connFailure{
ID: *id,
Code: code,
}
}
package wtserver
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("WTWR", nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
package wtserver
import (
"bytes"
"errors"
"fmt"
"net"
"sync"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/connmgr"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
"github.com/lightningnetwork/lnd/watchtower/wtwire"
)
var (
// ErrPeerAlreadyConnected signals that a peer with the same session id
// is already active within the server.
ErrPeerAlreadyConnected = errors.New("peer already connected")
// ErrServerExiting signals that a request could not be processed
// because the server has been requested to shut down.
ErrServerExiting = errors.New("server shutting down")
)
// Config abstracts the primary components and dependencies of the server.
type Config struct {
// DB provides persistent access to the server's sessions and for
// storing state updates.
DB DB
// NodeKeyECDH is the ECDH capable wrapper of the key to be used in
// accepting new brontide connections.
NodeKeyECDH keychain.SingleKeyECDH
// Listeners specifies which address to which clients may connect.
Listeners []net.Listener
// ReadTimeout specifies how long a client may go without sending a
// message.
ReadTimeout time.Duration
// WriteTimeout specifies how long a client may go without reading a
// message from the other end, if the connection has stopped buffering
// the server's replies.
WriteTimeout time.Duration
// NewAddress is used to generate reward addresses, where a cut of
// successfully sent funds can be received.
NewAddress func() (btcutil.Address, error)
// ChainHash identifies the network that the server is watching.
ChainHash chainhash.Hash
// NoAckCreateSession causes the server to not reply to create session
// requests, this should only be used for testing.
NoAckCreateSession bool
// NoAckUpdates causes the server to not acknowledge state updates, this
// should only be used for testing.
NoAckUpdates bool
// DisableReward causes the server to reject any session creation
// attempts that request rewards.
DisableReward bool
}
// Server houses the state required to handle watchtower peers. It's primary job
// is to accept incoming connections, and dispatch processing of the client
// message streams.
type Server struct {
started sync.Once
stopped sync.Once
cfg *Config
connMgr *connmgr.ConnManager
clientMtx sync.RWMutex
clients map[wtdb.SessionID]Peer
newPeers chan Peer
localInit *wtwire.Init
wg sync.WaitGroup
quit chan struct{}
}
// New creates a new server to handle watchtower clients. The server will accept
// clients connecting to the listener addresses, and allows them to open
// sessions and send state updates.
func New(cfg *Config) (*Server, error) {
localInit := wtwire.NewInitMessage(
lnwire.NewRawFeatureVector(
wtwire.AltruistSessionsOptional,
wtwire.AnchorCommitOptional,
),
cfg.ChainHash,
)
s := &Server{
cfg: cfg,
clients: make(map[wtdb.SessionID]Peer),
newPeers: make(chan Peer),
localInit: localInit,
quit: make(chan struct{}),
}
connMgr, err := connmgr.New(&connmgr.Config{
Listeners: cfg.Listeners,
OnAccept: s.inboundPeerConnected,
Dial: noDial,
})
if err != nil {
return nil, err
}
s.connMgr = connMgr
return s, nil
}
// Start begins listening on the server's listeners.
func (s *Server) Start() error {
s.started.Do(func() {
log.Infof("Starting watchtower server")
s.wg.Add(1)
go s.peerHandler()
s.connMgr.Start()
log.Infof("Watchtower server started successfully")
})
return nil
}
// Stop shutdowns down the server's listeners and any active requests.
func (s *Server) Stop() error {
s.stopped.Do(func() {
log.Infof("Stopping watchtower server")
s.connMgr.Stop()
close(s.quit)
s.wg.Wait()
log.Infof("Watchtower server stopped successfully")
})
return nil
}
// inboundPeerConnected is the callback given to the connection manager, and is
// called each time a new connection is made to the watchtower. This method
// proxies the new peers by filtering out those that do not satisfy the
// server.Peer interface, and closes their connection. Successful connections
// will be passed on to the public InboundPeerConnected method.
func (s *Server) inboundPeerConnected(c net.Conn) {
peer, ok := c.(Peer)
if !ok {
log.Warnf("incoming connection %T does not satisfy "+
"server.Peer interface", c)
c.Close()
return
}
s.InboundPeerConnected(peer)
}
// InboundPeerConnected accepts a server.Peer, and handles the request submitted
// by the client. This method serves also as a public endpoint for locally
// registering new clients with the server.
func (s *Server) InboundPeerConnected(peer Peer) {
select {
case s.newPeers <- peer:
case <-s.quit:
}
}
// peerHandler processes newly accepted peers and spawns a client handler for
// each. The peerHandler is used to ensure that waitgrouped client handlers are
// spawned from a waitgrouped goroutine.
func (s *Server) peerHandler() {
defer s.wg.Done()
defer s.removeAllPeers()
for {
select {
case peer := <-s.newPeers:
s.wg.Add(1)
go s.handleClient(peer)
case <-s.quit:
return
}
}
}
// handleClient processes a series watchtower messages sent by a client. The
// client may either send:
// - a single CreateSession message.
// - a series of StateUpdate messages.
//
// This method uses the server's peer map to ensure at most one peer using the
// same session id can enter the main event loop. The connection will be
// dropped by the watchtower if no messages are sent or received by the
// configured Read/WriteTimeouts.
//
// NOTE: This method MUST be run as a goroutine.
func (s *Server) handleClient(peer Peer) {
defer s.wg.Done()
// Use the connection's remote pubkey as the client's session id.
id := wtdb.NewSessionIDFromPubKey(peer.RemotePub())
// Register this peer in the server's client map, and defer the
// connection's cleanup. If the peer already exists, we will close the
// connection and exit immediately.
err := s.addPeer(&id, peer)
if err != nil {
peer.Close()
return
}
defer s.removePeer(&id, peer.RemoteAddr())
msg, err := s.readMessage(peer)
if err != nil {
log.Errorf("Unable to read message from client %s@%s: %v",
id, peer.RemoteAddr(), err)
return
}
remoteInit, ok := msg.(*wtwire.Init)
if !ok {
log.Errorf("Client %s@%s did not send Init msg as first "+
"message", id, peer.RemoteAddr())
return
}
err = s.sendMessage(peer, s.localInit)
if err != nil {
log.Errorf("Unable to send Init msg to %s: %v", id, err)
return
}
err = s.localInit.CheckRemoteInit(remoteInit, wtwire.FeatureNames)
if err != nil {
log.Errorf("Cannot support client %s: %v", id, err)
return
}
nextMsg, err := s.readMessage(peer)
if err != nil {
log.Errorf("Unable to read watchtower msg from %s: %v",
id, err)
return
}
switch msg := nextMsg.(type) {
case *wtwire.CreateSession:
// Attempt to open a new session for this client.
err = s.handleCreateSession(peer, &id, msg)
if err != nil {
log.Errorf("Unable to handle CreateSession "+
"from %s: %v", id, err)
}
case *wtwire.DeleteSession:
err = s.handleDeleteSession(peer, &id)
if err != nil {
log.Errorf("Unable to handle DeleteSession "+
"from %s: %v", id, err)
}
case *wtwire.StateUpdate:
err = s.handleStateUpdates(peer, &id, msg)
if err != nil {
log.Errorf("Unable to handle StateUpdate "+
"from %s: %v", id, err)
}
default:
log.Errorf("Received unsupported message type: %T "+
"from %s", nextMsg, id)
}
}
// connFailure is a default error used when a request failed with a non-zero
// error code.
type connFailure struct {
ID wtdb.SessionID
Code wtwire.ErrorCode
}
// Error displays the SessionID and Code that caused the connection failure.
func (f *connFailure) Error() string {
return fmt.Sprintf("connection with %s failed with code=%s",
f.ID, f.Code,
)
}
// readMessage receives and parses the next message from the given Peer. An
// error is returned if a message is not received before the server's read
// timeout, the read off the wire failed, or the message could not be
// deserialized.
func (s *Server) readMessage(peer Peer) (wtwire.Message, error) {
// Set a read timeout to ensure we drop the client if not sent in a
// timely manner.
err := peer.SetReadDeadline(time.Now().Add(s.cfg.ReadTimeout))
if err != nil {
err = fmt.Errorf("unable to set read deadline: %w", err)
return nil, err
}
// Pull the next message off the wire, and parse it according to the
// watchtower wire specification.
rawMsg, err := peer.ReadNextMessage()
if err != nil {
err = fmt.Errorf("unable to read message: %w", err)
return nil, err
}
msgReader := bytes.NewReader(rawMsg)
msg, err := wtwire.ReadMessage(msgReader, 0)
if err != nil {
err = fmt.Errorf("unable to parse message: %w", err)
return nil, err
}
logMessage(peer, msg, true)
return msg, nil
}
// sendMessage sends a watchtower wire message to the target peer.
func (s *Server) sendMessage(peer Peer, msg wtwire.Message) error {
// TODO(conner): use buffer pool?
var b bytes.Buffer
_, err := wtwire.WriteMessage(&b, msg, 0)
if err != nil {
err = fmt.Errorf("unable to encode msg: %w", err)
return err
}
err = peer.SetWriteDeadline(time.Now().Add(s.cfg.WriteTimeout))
if err != nil {
err = fmt.Errorf("unable to set write deadline: %w", err)
return err
}
logMessage(peer, msg, false)
_, err = peer.Write(b.Bytes())
return err
}
// addPeer stores a client in the server's client map. An error is returned if a
// client with the same session id already exists.
func (s *Server) addPeer(id *wtdb.SessionID, peer Peer) error {
s.clientMtx.Lock()
defer s.clientMtx.Unlock()
if existingPeer, ok := s.clients[*id]; ok {
log.Infof("Already connected to peer %s@%s, disconnecting %s",
id, existingPeer.RemoteAddr(), peer.RemoteAddr())
return ErrPeerAlreadyConnected
}
s.clients[*id] = peer
log.Infof("Accepted incoming peer %s@%s",
id, peer.RemoteAddr())
return nil
}
// removePeer deletes a client from the server's client map. If a peer is found,
// this method will close the peer's connection.
func (s *Server) removePeer(id *wtdb.SessionID, addr net.Addr) {
log.Infof("Releasing incoming peer %s@%s", id, addr)
s.clientMtx.Lock()
peer, ok := s.clients[*id]
delete(s.clients, *id)
s.clientMtx.Unlock()
if ok {
peer.Close()
}
}
// removeAllPeers iterates through the server's current set of peers and closes
// all open connections.
func (s *Server) removeAllPeers() {
s.clientMtx.Lock()
defer s.clientMtx.Unlock()
for id, peer := range s.clients {
log.Infof("Releasing incoming peer %s@%s", id,
peer.RemoteAddr())
delete(s.clients, id)
peer.Close()
}
}
// logMessage writes information about a message exchanged with a remote peer,
// using directional prepositions to signal whether the message was sent or
// received.
func logMessage(peer Peer, msg wtwire.Message, read bool) {
var action = "Received"
var preposition = "from"
if !read {
action = "Sending"
preposition = "to"
}
summary := wtwire.MessageSummary(msg)
if len(summary) > 0 {
summary = "(" + summary + ")"
}
log.Debugf("%s %s%v %s %x@%s", action, msg.MsgType(), summary,
preposition, peer.RemotePub().SerializeCompressed(),
peer.RemoteAddr())
}
// noDial is a dummy dial method passed to the server's connmgr.
func noDial(_ net.Addr) (net.Conn, error) {
return nil, fmt.Errorf("watchtower cannot make outgoing conns")
}
package wtserver
import (
"fmt"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
"github.com/lightningnetwork/lnd/watchtower/wtwire"
)
// handleStateUpdates processes a stream of StateUpdate requests from the
// client. The provided update should be the first such update read, subsequent
// updates will be consumed if the peer does not signal IsComplete on a
// particular update.
func (s *Server) handleStateUpdates(peer Peer, id *wtdb.SessionID,
update *wtwire.StateUpdate) error {
// Set the current update to the first update read off the wire.
// Additional updates will be read if this value is set to nil after
// processing the first.
var curUpdate = update
for {
// If this is not the first update, read the next state update
// from the peer.
if curUpdate == nil {
nextMsg, err := s.readMessage(peer)
if err != nil {
return err
}
var ok bool
curUpdate, ok = nextMsg.(*wtwire.StateUpdate)
if !ok {
return fmt.Errorf("client sent %T after "+
"StateUpdate", nextMsg)
}
}
// Try to accept the state update from the client.
err := s.handleStateUpdate(peer, id, curUpdate)
if err != nil {
return err
}
// If the client signals that this is last StateUpdate
// message, we can disconnect the client.
if curUpdate.IsComplete == 1 {
return nil
}
// Reset the current update to read subsequent updates in the
// stream.
curUpdate = nil
select {
case <-s.quit:
return ErrServerExiting
default:
}
}
}
// handleStateUpdate processes a StateUpdate message request from a client. An
// attempt will be made to insert the update into the db, where it is validated
// against the client's session. The possible errors are then mapped back to
// StateUpdateCodes specified by the watchtower wire protocol, and sent back
// using a StateUpdateReply message.
func (s *Server) handleStateUpdate(peer Peer, id *wtdb.SessionID,
update *wtwire.StateUpdate) error {
var (
lastApplied uint16
failCode wtwire.ErrorCode
err error
)
sessionUpdate := wtdb.SessionStateUpdate{
ID: *id,
Hint: update.Hint,
SeqNum: update.SeqNum,
LastApplied: update.LastApplied,
EncryptedBlob: update.EncryptedBlob,
}
lastApplied, err = s.cfg.DB.InsertStateUpdate(&sessionUpdate)
switch {
case err == nil:
log.Debugf("State update %d accepted for %s",
update.SeqNum, id)
failCode = wtwire.CodeOK
// Return a permanent failure if a client tries to send an update for
// which we have no session.
case err == wtdb.ErrSessionNotFound:
failCode = wtwire.CodePermanentFailure
case err == wtdb.ErrSeqNumAlreadyApplied:
failCode = wtwire.CodePermanentFailure
// TODO(conner): remove session state for protocol
// violation. Could also double as clean up method for
// session-related state.
case err == wtdb.ErrLastAppliedReversion:
failCode = wtwire.StateUpdateCodeClientBehind
case err == wtdb.ErrSessionConsumed:
failCode = wtwire.StateUpdateCodeMaxUpdatesExceeded
case err == wtdb.ErrUpdateOutOfOrder:
failCode = wtwire.StateUpdateCodeSeqNumOutOfOrder
default:
failCode = wtwire.CodeTemporaryFailure
}
if s.cfg.NoAckUpdates {
return &connFailure{
ID: *id,
Code: failCode,
}
}
return s.replyStateUpdate(
peer, id, failCode, lastApplied,
)
}
// replyStateUpdate sends a response to a StateUpdate from a client. If the
// status code in the reply is OK, the error from the write will be bubbled up.
// Otherwise, this method returns a connection error to ensure we don't continue
// communication with the client.
func (s *Server) replyStateUpdate(peer Peer, id *wtdb.SessionID,
code wtwire.StateUpdateCode, lastApplied uint16) error {
msg := &wtwire.StateUpdateReply{
Code: code,
LastApplied: lastApplied,
}
err := s.sendMessage(peer, msg)
if err != nil {
log.Errorf("unable to send StateUpdateReply to %s", id)
}
// Return the write error if the request succeeded.
if code == wtwire.CodeOK {
return err
}
// Otherwise the request failed, return a connection failure to
// disconnect the client.
return &connFailure{
ID: *id,
Code: code,
}
}
package wtwire
import (
"io"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/watchtower/blob"
)
// CreateSession is sent from a client to tower when to negotiate a session, which
// specifies the total number of updates that can be made, as well as fee rates.
// An update is consumed by uploading an encrypted blob that contains
// information required to sweep a revoked commitment transaction.
type CreateSession struct {
// BlobType specifies the blob format that must be used by all updates sent
// under the session key used to negotiate this session.
BlobType blob.Type
// MaxUpdates is the maximum number of updates the watchtower will honor
// for this session.
MaxUpdates uint16
// RewardBase is the fixed amount allocated to the tower when the
// policy's blob type specifies a reward for the tower. This is taken
// before adding the proportional reward.
RewardBase uint32
// RewardRate is the fraction of the total balance of the revoked
// commitment that the watchtower is entitled to. This value is
// expressed in millionths of the total balance.
RewardRate uint32
// SweepFeeRate expresses the intended fee rate to be used when
// constructing the justice transaction. All sweep transactions created
// for this session must use this value during construction, and the
// signatures must implicitly commit to the resulting output values.
SweepFeeRate chainfee.SatPerKWeight
}
// A compile time check to ensure CreateSession implements the wtwire.Message
// interface.
var _ Message = (*CreateSession)(nil)
// Decode deserializes a serialized CreateSession message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the wtwire.Message interface.
func (m *CreateSession) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&m.BlobType,
&m.MaxUpdates,
&m.RewardBase,
&m.RewardRate,
&m.SweepFeeRate,
)
}
// Encode serializes the target CreateSession into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the wtwire.Message interface.
func (m *CreateSession) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
m.BlobType,
m.MaxUpdates,
m.RewardBase,
m.RewardRate,
m.SweepFeeRate,
)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the wtwire.Message interface.
func (m *CreateSession) MsgType() MessageType {
return MsgCreateSession
}
// MaxPayloadLength returns the maximum allowed payload size for a CreateSession
// complete message observing the specified protocol version.
//
// This is part of the wtwire.Message interface.
func (m *CreateSession) MaxPayloadLength(uint32) uint32 {
return 2 + 2 + 4 + 4 + 8 // 20
}
package wtwire
import "io"
// CreateSessionCode is an error code returned by a watchtower in response to a
// CreateSession message. The code directs the client in interpreting the payload
// in the reply.
type CreateSessionCode = ErrorCode
const (
// CreateSessionCodeAlreadyExists is returned when a session is already
// active for the public key used to connect to the watchtower. The
// response includes the serialized reward address in case the original
// reply was never received and/or processed by the client.
CreateSessionCodeAlreadyExists CreateSessionCode = 60
// CreateSessionCodeRejectMaxUpdates the tower rejected the maximum
// number of state updates proposed by the client.
CreateSessionCodeRejectMaxUpdates CreateSessionCode = 61
// CreateSessionCodeRejectRewardRate the tower rejected the reward rate
// proposed by the client.
CreateSessionCodeRejectRewardRate CreateSessionCode = 62
// CreateSessionCodeRejectSweepFeeRate the tower rejected the sweep fee
// rate proposed by the client.
CreateSessionCodeRejectSweepFeeRate CreateSessionCode = 63
// CreateSessionCodeRejectBlobType is returned when the tower does not
// support the proposed blob type.
CreateSessionCodeRejectBlobType CreateSessionCode = 64
)
// MaxCreateSessionReplyDataLength is the maximum size of the Data payload
// returned in a CreateSessionReply message. This does not include the length of
// the Data field, which is a varint up to 3 bytes in size.
const MaxCreateSessionReplyDataLength = 1024
// CreateSessionReply is a message sent from watchtower to client in response to a
// CreateSession message, and signals either an acceptance or rejection of the
// proposed session parameters.
type CreateSessionReply struct {
// Code will be non-zero if the watchtower rejected the session init.
Code CreateSessionCode
// LastApplied is the tower's last accepted sequence number for the
// session. This is useful when the session already exists but the
// client doesn't realize it's already used the session, such as after a
// restoration.
LastApplied uint16
// Data is a byte slice returned the caller of the message, and is to be
// interpreted according to the error Code. When the response is
// CreateSessionCodeOK, data encodes the reward address to be included in
// any sweep transactions if the reward is not dusty. Otherwise, it may
// encode the watchtowers configured parameters for any policy
// rejections.
Data []byte
}
// A compile time check to ensure CreateSessionReply implements the wtwire.Message
// interface.
var _ Message = (*CreateSessionReply)(nil)
// Decode deserializes a serialized CreateSessionReply message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the wtwire.Message interface.
func (m *CreateSessionReply) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&m.Code,
&m.LastApplied,
&m.Data,
)
}
// Encode serializes the target CreateSessionReply into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the wtwire.Message interface.
func (m *CreateSessionReply) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
m.Code,
m.LastApplied,
m.Data,
)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the wtwire.Message interface.
func (m *CreateSessionReply) MsgType() MessageType {
return MsgCreateSessionReply
}
// MaxPayloadLength returns the maximum allowed payload size for a CreateSessionReply
// complete message observing the specified protocol version.
//
// This is part of the wtwire.Message interface.
func (m *CreateSessionReply) MaxPayloadLength(uint32) uint32 {
return 2 + 3 + MaxCreateSessionReplyDataLength
}
package wtwire
import "io"
// DeleteSession is sent from the client to the tower to signal that the tower
// can delete all session state for the session key used to authenticate the
// brontide connection. This should be done by the client once all channels that
// have state updates in the session have been resolved on-chain.
type DeleteSession struct{}
// Compile-time constraint to ensure DeleteSession implements the wtwire.Message
// interface.
var _ Message = (*DeleteSession)(nil)
// Decode deserializes a serialized DeleteSession message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the wtwire.Message interface.
func (m *DeleteSession) Decode(r io.Reader, pver uint32) error {
return nil
}
// Encode serializes the target DeleteSession message into the passed io.Writer
// observing the specified protocol version.
//
// This is part of the wtwire.Message interface.
func (m *DeleteSession) Encode(w io.Writer, pver uint32) error {
return nil
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the wtwire.Message interface.
func (m *DeleteSession) MsgType() MessageType {
return MsgDeleteSession
}
// MaxPayloadLength returns the maximum allowed payload size for a DeleteSession
// message observing the specified protocol version.
//
// This is part of the wtwire.Message interface.
func (m *DeleteSession) MaxPayloadLength(uint32) uint32 {
return 0
}
package wtwire
import "io"
// DeleteSessionCode is an error code returned by a watchtower in response to a
// DeleteSession message.
type DeleteSessionCode = ErrorCode
const (
// DeleteSessionCodeNotFound is returned when the watchtower does not
// know of the requested session. This may indicate an error on the
// client side, or that the tower had already deleted the session in a
// prior request that the client may not have received.
DeleteSessionCodeNotFound DeleteSessionCode = 80
)
// DeleteSessionReply is a message sent in response to a client's DeleteSession
// request. The message indicates whether the deletion was a success or failure.
type DeleteSessionReply struct {
// Code will be non-zero if the watchtower was not able to delete the
// requested session.
Code DeleteSessionCode
}
// A compile time check to ensure DeleteSessionReply implements the
// wtwire.Message interface.
var _ Message = (*DeleteSessionReply)(nil)
// Decode deserializes a serialized DeleteSessionReply message stored in the
// passed io.Reader observing the specified protocol version.
//
// This is part of the wtwire.Message interface.
func (m *DeleteSessionReply) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&m.Code,
)
}
// Encode serializes the target DeleteSessionReply into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the wtwire.Message interface.
func (m *DeleteSessionReply) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
m.Code,
)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the wtwire.Message interface.
func (m *DeleteSessionReply) MsgType() MessageType {
return MsgDeleteSessionReply
}
// MaxPayloadLength returns the maximum allowed payload size for a
// DeleteSessionReply complete message observing the specified protocol version.
//
// This is part of the wtwire.Message interface.
func (m *DeleteSessionReply) MaxPayloadLength(uint32) uint32 {
return 2
}
package wtwire
import "io"
// Error is a generic error message that can be sent to a client if a request
// fails outside of prescribed protocol errors. Typically this would be followed
// by the server disconnecting the client, and so can be useful to transferring
// the exact reason.
type Error struct {
// Code specifies the error code encountered by the server.
Code ErrorCode
// Data encodes a payload whose contents can be interpreted by the
// client in response to the error code.
Data []byte
}
// NewError returns an freshly-initialized Error message.
func NewError() *Error {
return &Error{}
}
// A compile time check to ensure Error implements the wtwire.Message interface.
var _ Message = (*Error)(nil)
// Decode deserializes a serialized Error message stored in the passed io.Reader
// observing the specified protocol version.
//
// This is part of the wtwire.Message interface.
func (e *Error) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&e.Code,
&e.Data,
)
}
// Encode serializes the target Error into the passed io.Writer observing the
// protocol version specified.
//
// This is part of the wtwire.Message interface.
func (e *Error) Encode(w io.Writer, prver uint32) error {
return WriteElements(w,
e.Code,
e.Data,
)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the wtwire.Message interface.
func (e *Error) MsgType() MessageType {
return MsgError
}
// MaxPayloadLength returns the maximum allowed payload size for a Error
// complete message observing the specified protocol version.
//
// This is part of the wtwire.Message interface.
func (e *Error) MaxPayloadLength(uint32) uint32 {
return MaxMessagePayload
}
package wtwire
import "fmt"
// ErrorCode represents a generic error code used when replying to watchtower
// clients. Specific reply messages may extend the ErrorCode primitive and add
// custom codes, so long as they don't collide with the generic error codes..
type ErrorCode uint16
const (
// CodeOK signals that the request was successfully processed by the
// watchtower.
CodeOK ErrorCode = 0
// CodeTemporaryFailure alerts the client that the watchtower is
// temporarily unavailable, but that it may try again at a later time.
CodeTemporaryFailure ErrorCode = 40
// CodePermanentFailure alerts the client that the watchtower has
// permanently failed, and further communication should be avoided.
CodePermanentFailure ErrorCode = 50
)
// String returns a human-readable description of an ErrorCode.
func (c ErrorCode) String() string {
switch c {
case CodeOK:
return "CodeOK"
case CodeTemporaryFailure:
return "CodeTemporaryFailure"
case CodePermanentFailure:
return "CodePermanentFailure"
case CreateSessionCodeAlreadyExists:
return "CreateSessionCodeAlreadyExists"
case CreateSessionCodeRejectMaxUpdates:
return "CreateSessionCodeRejectMaxUpdates"
case CreateSessionCodeRejectRewardRate:
return "CreateSessionCodeRejectRewardRate"
case CreateSessionCodeRejectSweepFeeRate:
return "CreateSessionCodeRejectSweepFeeRate"
case CreateSessionCodeRejectBlobType:
return "CreateSessionCodeRejectBlobType"
case StateUpdateCodeClientBehind:
return "StateUpdateCodeClientBehind"
case StateUpdateCodeMaxUpdatesExceeded:
return "StateUpdateCodeMaxUpdatesExceeded"
case StateUpdateCodeSeqNumOutOfOrder:
return "StateUpdateCodeSeqNumOutOfOrder"
case DeleteSessionCodeNotFound:
return "DeleteSessionCodeNotFound"
default:
return fmt.Sprintf("UnknownErrorCode: %d", c)
}
}
package wtwire
import (
"fmt"
"io"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/feature"
"github.com/lightningnetwork/lnd/lnwire"
)
// Init is the first message sent over the watchtower wire protocol, and
// specifies connection features bits and level of requiredness maintained by
// the sending node. The Init message also sends the chain hash identifying the
// network that the sender is on.
type Init struct {
// ConnFeatures are the feature bits being advertised for the duration
// of a single connection with a peer.
ConnFeatures *lnwire.RawFeatureVector
// ChainHash is the genesis hash of the chain that the advertiser claims
// to be on.
ChainHash chainhash.Hash
}
// NewInitMessage generates a new Init message from a raw connection feature
// vector and chain hash.
func NewInitMessage(connFeatures *lnwire.RawFeatureVector,
chainHash chainhash.Hash) *Init {
return &Init{
ConnFeatures: connFeatures,
ChainHash: chainHash,
}
}
// Encode serializes the target Init into the passed io.Writer observing the
// protocol version specified.
//
// This is part of the wtwire.Message interface.
func (msg *Init) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
msg.ConnFeatures,
msg.ChainHash,
)
}
// Decode deserializes a serialized Init message stored in the passed io.Reader
// observing the specified protocol version.
//
// This is part of the wtwire.Message interface.
func (msg *Init) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&msg.ConnFeatures,
&msg.ChainHash,
)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the wtwire.Message interface.
func (msg *Init) MsgType() MessageType {
return MsgInit
}
// MaxPayloadLength returns the maximum allowed payload size for an Init
// complete message observing the specified protocol version.
//
// This is part of the wtwire.Message interface.
func (msg *Init) MaxPayloadLength(uint32) uint32 {
return MaxMessagePayload
}
// A compile-time constraint to ensure Init implements the Message interface.
var _ Message = (*Init)(nil)
// CheckRemoteInit performs basic validation of the remote party's Init message.
// This method checks that the remote Init's chain hash matches our advertised
// chain hash and that the remote Init does not contain any required feature
// bits that we don't understand.
func (msg *Init) CheckRemoteInit(remoteInit *Init,
featureNames map[lnwire.FeatureBit]string) error {
// Check that the remote peer is on the same chain.
if msg.ChainHash != remoteInit.ChainHash {
return NewErrUnknownChainHash(remoteInit.ChainHash)
}
remoteConnFeatures := lnwire.NewFeatureVector(
remoteInit.ConnFeatures, featureNames,
)
// Check that the remote peer doesn't have any required connection
// feature bits that we ourselves are unaware of.
return feature.ValidateRequired(remoteConnFeatures)
}
// ErrUnknownChainHash signals that the remote Init has a different chain hash
// from the one we advertised.
type ErrUnknownChainHash struct {
hash chainhash.Hash
}
// NewErrUnknownChainHash creates an ErrUnknownChainHash using the remote Init's
// chain hash.
func NewErrUnknownChainHash(hash chainhash.Hash) *ErrUnknownChainHash {
return &ErrUnknownChainHash{hash}
}
// Error returns a human-readable error displaying the unknown chain hash.
func (e *ErrUnknownChainHash) Error() string {
return fmt.Sprintf("remote init has unknown chain hash: %s", e.hash)
}
package wtwire
import (
"bytes"
"encoding/binary"
"fmt"
"io"
)
// MaxMessagePayload is the maximum bytes a message can be regardless of other
// individual limits imposed by messages themselves.
const MaxMessagePayload = 65535 // 65KB
// MessageType is the unique 2 byte big-endian integer that indicates the type
// of message on the wire. All messages have a very simple header which
// consists simply of 2-byte message type. We omit a length field, and checksum
// as the Watchtower Protocol is intended to be encapsulated within a
// confidential+authenticated cryptographic messaging protocol.
type MessageType uint16
// The currently defined message types within this current version of the
// Watchtower protocol.
const (
// MsgInit identifies an encoded Init message.
MsgInit MessageType = 600
// MsgError identifies an encoded Error message.
MsgError MessageType = 601
// MsgCreateSession identifies an encoded CreateSession message.
MsgCreateSession MessageType = 602
// MsgCreateSessionReply identifies an encoded CreateSessionReply message.
MsgCreateSessionReply MessageType = 603
// MsgStateUpdate identifies an encoded StateUpdate message.
MsgStateUpdate MessageType = 604
// MsgStateUpdateReply identifies an encoded StateUpdateReply message.
MsgStateUpdateReply MessageType = 605
// MsgDeleteSession identifies an encoded DeleteSession message.
MsgDeleteSession MessageType = 606
// MsgDeleteSessionReply identifies an encoded DeleteSessionReply
// message.
MsgDeleteSessionReply MessageType = 607
)
// String returns a human readable description of the message type.
func (m MessageType) String() string {
switch m {
case MsgInit:
return "Init"
case MsgCreateSession:
return "MsgCreateSession"
case MsgCreateSessionReply:
return "MsgCreateSessionReply"
case MsgStateUpdate:
return "MsgStateUpdate"
case MsgStateUpdateReply:
return "MsgStateUpdateReply"
case MsgDeleteSession:
return "MsgDeleteSession"
case MsgDeleteSessionReply:
return "MsgDeleteSessionReply"
case MsgError:
return "Error"
default:
return "<unknown>"
}
}
// Serializable is an interface which defines a lightning wire serializable
// object.
type Serializable interface {
// Decode reads the bytes stream and converts it to the object.
Decode(io.Reader, uint32) error
// Encode converts object to the bytes stream and write it into the
// write buffer.
Encode(io.Writer, uint32) error
}
// Message is an interface that defines a lightning wire protocol message. The
// interface is general in order to allow implementing types full control over
// the representation of its data.
type Message interface {
Serializable
// MsgType returns a MessageType that uniquely identifies the message to
// be encoded.
MsgType() MessageType
// MaxPayloadLength is the maximum serialized length that a particular
// message type can take.
MaxPayloadLength(uint32) uint32
}
// makeEmptyMessage creates a new empty message of the proper concrete type
// based on the passed message type.
func makeEmptyMessage(msgType MessageType) (Message, error) {
var msg Message
switch msgType {
case MsgInit:
msg = &Init{}
case MsgCreateSession:
msg = &CreateSession{}
case MsgCreateSessionReply:
msg = &CreateSessionReply{}
case MsgStateUpdate:
msg = &StateUpdate{}
case MsgStateUpdateReply:
msg = &StateUpdateReply{}
case MsgDeleteSession:
msg = &DeleteSession{}
case MsgDeleteSessionReply:
msg = &DeleteSessionReply{}
case MsgError:
msg = &Error{}
default:
return nil, fmt.Errorf("unknown message type [%d]", msgType)
}
return msg, nil
}
// WriteMessage writes a lightning Message to w including the necessary header
// information and returns the number of bytes written.
func WriteMessage(w io.Writer, msg Message, pver uint32) (int, error) {
totalBytes := 0
// Encode the message payload itself into a temporary buffer.
// TODO(roasbeef): create buffer pool
var bw bytes.Buffer
if err := msg.Encode(&bw, pver); err != nil {
return totalBytes, err
}
payload := bw.Bytes()
lenp := len(payload)
// Enforce maximum overall message payload.
if lenp > MaxMessagePayload {
return totalBytes, fmt.Errorf("message payload is too large - "+
"encoded %d bytes, but maximum message payload is %d bytes",
lenp, MaxMessagePayload)
}
// Enforce maximum message payload on the message type.
mpl := msg.MaxPayloadLength(pver)
if uint32(lenp) > mpl {
return totalBytes, fmt.Errorf("message payload is too large - "+
"encoded %d bytes, but maximum message payload of "+
"type %v is %d bytes", lenp, msg.MsgType(), mpl)
}
// With the initial sanity checks complete, we'll now write out the
// message type itself.
var mType [2]byte
binary.BigEndian.PutUint16(mType[:], uint16(msg.MsgType()))
n, err := w.Write(mType[:])
totalBytes += n
if err != nil {
return totalBytes, err
}
// With the message type written, we'll now write out the raw payload
// itself.
n, err = w.Write(payload)
totalBytes += n
return totalBytes, err
}
// ReadMessage reads, validates, and parses the next Watchtower message from r
// for the provided protocol version.
func ReadMessage(r io.Reader, pver uint32) (Message, error) {
// First, we'll read out the first two bytes of the message so we can
// create the proper empty message.
var mType [2]byte
if _, err := io.ReadFull(r, mType[:]); err != nil {
return nil, err
}
msgType := MessageType(binary.BigEndian.Uint16(mType[:]))
// Now that we know the target message type, we can create the proper
// empty message type and decode the message into it.
msg, err := makeEmptyMessage(msgType)
if err != nil {
return nil, err
}
if err := msg.Decode(r, pver); err != nil {
return nil, err
}
return msg, nil
}
package wtwire
import "io"
// StateUpdate transmits an encrypted state update from the client to the
// watchtower. Each state update is tied to particular session, identified by
// the client's brontide key used to make the request.
type StateUpdate struct {
// SeqNum is a 1-indexed, monotonically incrementing sequence number.
// This number represents to the client's expected sequence number when
// sending updates sent to the watchtower. This value must always be
// less or equal than the negotiated MaxUpdates for the session, and
// greater than the LastApplied sent in the same message.
SeqNum uint16
// LastApplied echos the LastApplied value returned from watchtower,
// allowing the tower to detect faulty clients. This allow provides a
// feedback mechanism for the tower if updates are allowed to stream in
// an async fashion.
LastApplied uint16
// IsComplete is 1 if the watchtower should close the connection after
// responding, and 0 otherwise.
IsComplete uint8
// Hint is the 16-byte prefix of the revoked commitment transaction ID
// for which the encrypted blob can exact justice.
Hint [16]byte
// EncryptedBlob is the serialized ciphertext containing all necessary
// information to sweep the commitment transaction corresponding to the
// Hint. The ciphertext is to be encrypted using the full transaction ID
// of the revoked commitment transaction.
//
// The plaintext MUST be encoded using the negotiated Version for
// this session. In addition, the signatures must be computed over a
// sweep transaction honoring the decided SweepFeeRate, RewardRate, and
// (possibly) reward address returned in the SessionInitReply.
EncryptedBlob []byte
}
// A compile time check to ensure StateUpdate implements the wtwire.Message
// interface.
var _ Message = (*StateUpdate)(nil)
// Decode deserializes a serialized StateUpdate message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the wtwire.Message interface.
func (m *StateUpdate) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&m.SeqNum,
&m.LastApplied,
&m.IsComplete,
&m.Hint,
&m.EncryptedBlob,
)
}
// Encode serializes the target StateUpdate into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the wtwire.Message interface.
func (m *StateUpdate) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
m.SeqNum,
m.LastApplied,
m.IsComplete,
m.Hint,
m.EncryptedBlob,
)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the wtwire.Message interface.
func (m *StateUpdate) MsgType() MessageType {
return MsgStateUpdate
}
// MaxPayloadLength returns the maximum allowed payload size for a StateUpdate
// complete message observing the specified protocol version.
//
// This is part of the wtwire.Message interface.
func (m *StateUpdate) MaxPayloadLength(uint32) uint32 {
return MaxMessagePayload
}
package wtwire
import "io"
// StateUpdateCode is an error code returned by a watchtower in response to a
// StateUpdate message.
type StateUpdateCode = ErrorCode
const (
// StateUpdateCodeClientBehind signals that the client's sequence number
// is behind what the watchtower expects based on its LastApplied. This
// error should cause the client to record the LastApplied field in the
// response, and initiate another attempt with the proper sequence
// number.
//
// NOTE: Repeated occurrences of this could be interpreted as an attempt
// to siphon state updates from the client. If the client believes it
// is not violating the protocol, this could be grounds to blacklist
// this tower from future session negotiation.
StateUpdateCodeClientBehind StateUpdateCode = 70
// StateUpdateCodeMaxUpdatesExceeded signals that the client tried to
// send a sequence number beyond the negotiated MaxUpdates of the
// session.
StateUpdateCodeMaxUpdatesExceeded StateUpdateCode = 71
// StateUpdateCodeSeqNumOutOfOrder signals the client sent an update
// that does not follow the required incremental monotonicity required
// by the tower.
StateUpdateCodeSeqNumOutOfOrder StateUpdateCode = 72
)
// StateUpdateReply is a message sent from watchtower to client in response to a
// StateUpdate message, and signals either an acceptance or rejection of the
// proposed state update.
type StateUpdateReply struct {
// Code will be non-zero if the watchtower rejected the state update.
Code StateUpdateCode
// LastApplied returns the sequence number of the last accepted update
// known to the watchtower. If the update was successful, this value
// should be the sequence number of the last update sent.
LastApplied uint16
}
// A compile time check to ensure StateUpdateReply implements the wtwire.Message
// interface.
var _ Message = (*StateUpdateReply)(nil)
// Decode deserializes a serialized StateUpdateReply message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the wtwire.Message interface.
func (t *StateUpdateReply) Decode(r io.Reader, pver uint32) error {
return ReadElements(r,
&t.Code,
&t.LastApplied,
)
}
// Encode serializes the target StateUpdateReply into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the wtwire.Message interface.
func (t *StateUpdateReply) Encode(w io.Writer, pver uint32) error {
return WriteElements(w,
t.Code,
t.LastApplied,
)
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the wtwire.Message interface.
func (t *StateUpdateReply) MsgType() MessageType {
return MsgStateUpdateReply
}
// MaxPayloadLength returns the maximum allowed payload size for a
// StateUpdateReply complete message observing the specified protocol version.
//
// This is part of the wtwire.Message interface.
func (t *StateUpdateReply) MaxPayloadLength(uint32) uint32 {
return 4
}
package wtwire
import "fmt"
// MessageSummary creates a human-readable description of a given Message. If
// the type is unknown, an empty string is returned.
func MessageSummary(msg Message) string {
switch msg := msg.(type) {
case *Init:
return ""
case *CreateSession:
return fmt.Sprintf("blob_type=%s, max_updates=%d "+
"reward_base=%d reward_rate=%d sweep_fee_rate=%d",
msg.BlobType, msg.MaxUpdates, msg.RewardBase,
msg.RewardRate, msg.SweepFeeRate)
case *CreateSessionReply:
return fmt.Sprintf("code=%d", msg.Code)
case *StateUpdate:
return fmt.Sprintf("seqnum=%d last_applied=%d is_complete=%d "+
"hint=%x", msg.SeqNum, msg.LastApplied, msg.IsComplete,
msg.Hint)
case *StateUpdateReply:
return fmt.Sprintf("code=%d last_applied=%d", msg.Code,
msg.LastApplied)
case *Error:
return fmt.Sprintf("code=%d", msg.Code)
default:
return ""
}
}
package wtwire
import (
"encoding/binary"
"fmt"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/watchtower/blob"
)
// WriteElement is a one-stop shop to write the big endian representation of
// any element which is to be serialized for the wire protocol. The passed
// io.Writer should be backed by an appropriately sized byte slice, or be able
// to dynamically expand to accommodate additional data.
func WriteElement(w io.Writer, element interface{}) error {
switch e := element.(type) {
case uint8:
var b [1]byte
b[0] = e
if _, err := w.Write(b[:]); err != nil {
return err
}
case uint16:
var b [2]byte
binary.BigEndian.PutUint16(b[:], e)
if _, err := w.Write(b[:]); err != nil {
return err
}
case blob.Type:
var b [2]byte
binary.BigEndian.PutUint16(b[:], uint16(e))
if _, err := w.Write(b[:]); err != nil {
return err
}
case uint32:
var b [4]byte
binary.BigEndian.PutUint32(b[:], e)
if _, err := w.Write(b[:]); err != nil {
return err
}
case uint64:
var b [8]byte
binary.BigEndian.PutUint64(b[:], e)
if _, err := w.Write(b[:]); err != nil {
return err
}
case [16]byte:
if _, err := w.Write(e[:]); err != nil {
return err
}
case [32]byte:
if _, err := w.Write(e[:]); err != nil {
return err
}
case [33]byte:
if _, err := w.Write(e[:]); err != nil {
return err
}
case []byte:
if err := wire.WriteVarBytes(w, 0, e); err != nil {
return err
}
case chainfee.SatPerKWeight:
var b [8]byte
binary.BigEndian.PutUint64(b[:], uint64(e))
if _, err := w.Write(b[:]); err != nil {
return err
}
case ErrorCode:
var b [2]byte
binary.BigEndian.PutUint16(b[:], uint16(e))
if _, err := w.Write(b[:]); err != nil {
return err
}
case chainhash.Hash:
if _, err := w.Write(e[:]); err != nil {
return err
}
case *lnwire.RawFeatureVector:
if e == nil {
return fmt.Errorf("cannot write nil feature vector")
}
if err := e.Encode(w); err != nil {
return err
}
case *btcec.PublicKey:
if e == nil {
return fmt.Errorf("cannot write nil pubkey")
}
var b [33]byte
serializedPubkey := e.SerializeCompressed()
copy(b[:], serializedPubkey)
if _, err := w.Write(b[:]); err != nil {
return err
}
default:
return fmt.Errorf("Unknown type in WriteElement: %T", e)
}
return nil
}
// WriteElements is writes each element in the elements slice to the passed
// io.Writer using WriteElement.
func WriteElements(w io.Writer, elements ...interface{}) error {
for _, element := range elements {
err := WriteElement(w, element)
if err != nil {
return err
}
}
return nil
}
// ReadElement is a one-stop utility function to deserialize any datastructure
// encoded using the serialization format of lnwire.
func ReadElement(r io.Reader, element interface{}) error {
switch e := element.(type) {
case *uint8:
var b [1]uint8
if _, err := r.Read(b[:]); err != nil {
return err
}
*e = b[0]
case *uint16:
var b [2]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
*e = binary.BigEndian.Uint16(b[:])
case *blob.Type:
var b [2]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
*e = blob.Type(binary.BigEndian.Uint16(b[:]))
case *uint32:
var b [4]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
*e = binary.BigEndian.Uint32(b[:])
case *uint64:
var b [8]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
*e = binary.BigEndian.Uint64(b[:])
case *[16]byte:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *[32]byte:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *[33]byte:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case *[]byte:
bytes, err := wire.ReadVarBytes(r, 0, 66000, "[]byte")
if err != nil {
return err
}
*e = bytes
case *chainfee.SatPerKWeight:
var b [8]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
*e = chainfee.SatPerKWeight(binary.BigEndian.Uint64(b[:]))
case *ErrorCode:
var b [2]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
*e = ErrorCode(binary.BigEndian.Uint16(b[:]))
case *chainhash.Hash:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}
case **lnwire.RawFeatureVector:
f := lnwire.NewRawFeatureVector()
err := f.Decode(r)
if err != nil {
return err
}
*e = f
case **btcec.PublicKey:
var b [btcec.PubKeyBytesLenCompressed]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return err
}
pubKey, err := btcec.ParsePubKey(b[:])
if err != nil {
return err
}
*e = pubKey
default:
return fmt.Errorf("Unknown type in ReadElement: %T", e)
}
return nil
}
// ReadElements deserializes a variable number of elements into the passed
// io.Reader, with each element being deserialized according to the ReadElement
// function.
func ReadElements(r io.Reader, elements ...interface{}) error {
for _, element := range elements {
err := ReadElement(r, element)
if err != nil {
return err
}
}
return nil
}
package lnd
import (
"errors"
"sync"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
)
// preimageSubscriber reprints an active subscription to be notified once the
// daemon discovers new preimages, either on chain or off-chain.
type preimageSubscriber struct {
updateChan chan lntypes.Preimage
quit chan struct{}
}
type witnessCache interface {
// LookupSha256Witness attempts to lookup the preimage for a sha256
// hash. If the witness isn't found, ErrNoWitnesses will be returned.
LookupSha256Witness(hash lntypes.Hash) (lntypes.Preimage, error)
// AddSha256Witnesses adds a batch of new sha256 preimages into the
// witness cache. This is an alias for AddWitnesses that uses
// Sha256HashWitness as the preimages' witness type.
AddSha256Witnesses(preimages ...lntypes.Preimage) error
}
// preimageBeacon is an implementation of the contractcourt.WitnessBeacon
// interface, and the lnwallet.PreimageCache interface. This implementation is
// concerned with a single witness type: sha256 hahsh preimages.
type preimageBeacon struct {
sync.RWMutex
wCache witnessCache
clientCounter uint64
subscribers map[uint64]*preimageSubscriber
interceptor func(htlcswitch.InterceptedForward) error
}
func newPreimageBeacon(wCache witnessCache,
interceptor func(htlcswitch.InterceptedForward) error) *preimageBeacon {
return &preimageBeacon{
wCache: wCache,
interceptor: interceptor,
subscribers: make(map[uint64]*preimageSubscriber),
}
}
// SubscribeUpdates returns a channel that will be sent upon *each* time a new
// preimage is discovered.
func (p *preimageBeacon) SubscribeUpdates(
chanID lnwire.ShortChannelID, htlc *channeldb.HTLC,
payload *hop.Payload,
nextHopOnionBlob []byte) (*contractcourt.WitnessSubscription, error) {
p.Lock()
defer p.Unlock()
clientID := p.clientCounter
client := &preimageSubscriber{
updateChan: make(chan lntypes.Preimage, 10),
quit: make(chan struct{}),
}
p.subscribers[p.clientCounter] = client
p.clientCounter++
srvrLog.Debugf("Creating new witness beacon subscriber, id=%v",
p.clientCounter)
sub := &contractcourt.WitnessSubscription{
WitnessUpdates: client.updateChan,
CancelSubscription: func() {
p.Lock()
defer p.Unlock()
delete(p.subscribers, clientID)
close(client.quit)
},
}
// Notify the htlc interceptor. There may be a client connected
// and willing to supply a preimage.
packet := &htlcswitch.InterceptedPacket{
Hash: htlc.RHash,
IncomingExpiry: htlc.RefundTimeout,
IncomingAmount: htlc.Amt,
IncomingCircuit: models.CircuitKey{
ChanID: chanID,
HtlcID: htlc.HtlcIndex,
},
OutgoingChanID: payload.FwdInfo.NextHop,
OutgoingExpiry: payload.FwdInfo.OutgoingCTLV,
OutgoingAmount: payload.FwdInfo.AmountToForward,
InOnionCustomRecords: payload.CustomRecords(),
InWireCustomRecords: htlc.CustomRecords,
}
copy(packet.OnionBlob[:], nextHopOnionBlob)
fwd := newInterceptedForward(packet, p)
err := p.interceptor(fwd)
if err != nil {
return nil, err
}
return sub, nil
}
// LookupPreimage attempts to lookup a preimage in the global cache. True is
// returned for the second argument if the preimage is found.
func (p *preimageBeacon) LookupPreimage(
payHash lntypes.Hash) (lntypes.Preimage, bool) {
p.RLock()
defer p.RUnlock()
// Otherwise, we'll perform a final check using the witness cache.
preimage, err := p.wCache.LookupSha256Witness(payHash)
if errors.Is(err, channeldb.ErrNoWitnesses) {
ltndLog.Debugf("No witness for payment %v", payHash)
return lntypes.Preimage{}, false
}
if err != nil {
ltndLog.Errorf("Unable to lookup witness: %v", err)
return lntypes.Preimage{}, false
}
return preimage, true
}
// AddPreimages adds a batch of newly discovered preimages to the global cache,
// and also signals any subscribers of the newly discovered witness.
func (p *preimageBeacon) AddPreimages(preimages ...lntypes.Preimage) error {
// Exit early if no preimages are presented.
if len(preimages) == 0 {
return nil
}
// Copy the preimages to ensure the backing area can't be modified by
// the caller when delivering notifications.
preimageCopies := make([]lntypes.Preimage, 0, len(preimages))
for _, preimage := range preimages {
srvrLog.Infof("Adding preimage=%v to witness cache for %v",
preimage, preimage.Hash())
preimageCopies = append(preimageCopies, preimage)
}
// First, we'll add the witness to the decaying witness cache.
err := p.wCache.AddSha256Witnesses(preimages...)
if err != nil {
return err
}
p.Lock()
defer p.Unlock()
// With the preimage added to our state, we'll now send a new
// notification to all subscribers.
for _, client := range p.subscribers {
go func(c *preimageSubscriber) {
for _, preimage := range preimageCopies {
select {
case c.updateChan <- preimage:
case <-c.quit:
return
}
}
}(client)
}
srvrLog.Debugf("Added %d preimage(s) to witness cache",
len(preimageCopies))
return nil
}
var _ contractcourt.WitnessBeacon = (*preimageBeacon)(nil)
package zpay32
import (
"fmt"
"strconv"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// toMSat is a map from a unit to a function that converts an amount
// of that unit to millisatoshis.
toMSat = map[byte]func(uint64) (lnwire.MilliSatoshi, error){
'm': mBtcToMSat,
'u': uBtcToMSat,
'n': nBtcToMSat,
'p': pBtcToMSat,
}
// fromMSat is a map from a unit to a function that converts an amount
// in millisatoshis to an amount of that unit.
fromMSat = map[byte]func(lnwire.MilliSatoshi) (uint64, error){
'm': mSatToMBtc,
'u': mSatToUBtc,
'n': mSatToNBtc,
'p': mSatToPBtc,
}
)
// mBtcToMSat converts the given amount in milliBTC to millisatoshis.
func mBtcToMSat(m uint64) (lnwire.MilliSatoshi, error) {
return lnwire.MilliSatoshi(m) * 100000000, nil
}
// uBtcToMSat converts the given amount in microBTC to millisatoshis.
func uBtcToMSat(u uint64) (lnwire.MilliSatoshi, error) {
return lnwire.MilliSatoshi(u * 100000), nil
}
// nBtcToMSat converts the given amount in nanoBTC to millisatoshis.
func nBtcToMSat(n uint64) (lnwire.MilliSatoshi, error) {
return lnwire.MilliSatoshi(n * 100), nil
}
// pBtcToMSat converts the given amount in picoBTC to millisatoshis.
func pBtcToMSat(p uint64) (lnwire.MilliSatoshi, error) {
if p < 10 {
return 0, fmt.Errorf("minimum amount is 10p")
}
if p%10 != 0 {
return 0, fmt.Errorf("amount %d pBTC not expressible in msat",
p)
}
return lnwire.MilliSatoshi(p / 10), nil
}
// mSatToMBtc converts the given amount in millisatoshis to milliBTC.
func mSatToMBtc(msat lnwire.MilliSatoshi) (uint64, error) {
if msat%100000000 != 0 {
return 0, fmt.Errorf("%d msat not expressible "+
"in mBTC", msat)
}
return uint64(msat / 100000000), nil
}
// mSatToUBtc converts the given amount in millisatoshis to microBTC.
func mSatToUBtc(msat lnwire.MilliSatoshi) (uint64, error) {
if msat%100000 != 0 {
return 0, fmt.Errorf("%d msat not expressible "+
"in uBTC", msat)
}
return uint64(msat / 100000), nil
}
// mSatToNBtc converts the given amount in millisatoshis to nanoBTC.
func mSatToNBtc(msat lnwire.MilliSatoshi) (uint64, error) {
if msat%100 != 0 {
return 0, fmt.Errorf("%d msat not expressible in nBTC", msat)
}
return uint64(msat / 100), nil
}
// mSatToPBtc converts the given amount in millisatoshis to picoBTC.
func mSatToPBtc(msat lnwire.MilliSatoshi) (uint64, error) {
return uint64(msat * 10), nil
}
// decodeAmount returns the amount encoded by the provided string in
// millisatoshi.
func decodeAmount(amount string) (lnwire.MilliSatoshi, error) {
if len(amount) < 1 {
return 0, fmt.Errorf("amount must be non-empty")
}
// If last character is a digit, then the amount can just be
// interpreted as BTC.
char := amount[len(amount)-1]
digit := char - '0'
if digit >= 0 && digit <= 9 {
btc, err := strconv.ParseUint(amount, 10, 64)
if err != nil {
return 0, err
}
return lnwire.MilliSatoshi(btc) * mSatPerBtc, nil
}
// If not a digit, it must be part of the known units.
conv, ok := toMSat[char]
if !ok {
return 0, fmt.Errorf("unknown multiplier %c", char)
}
// Known unit.
num := amount[:len(amount)-1]
if len(num) < 1 {
return 0, fmt.Errorf("number must be non-empty")
}
am, err := strconv.ParseUint(num, 10, 64)
if err != nil {
return 0, err
}
return conv(am)
}
// encodeAmount encodes the provided millisatoshi amount using as few characters
// as possible.
func encodeAmount(msat lnwire.MilliSatoshi) (string, error) {
// If possible to express in BTC, that will always be the shortest
// representation.
if msat%mSatPerBtc == 0 {
return strconv.FormatInt(int64(msat/mSatPerBtc), 10), nil
}
// Should always be expressible in pico BTC.
pico, err := fromMSat['p'](msat)
if err != nil {
return "", fmt.Errorf("unable to express %d msat as pBTC: %w",
msat, err)
}
shortened := strconv.FormatUint(pico, 10) + "p"
for unit, conv := range fromMSat {
am, err := conv(msat)
if err != nil {
// Not expressible using this unit.
continue
}
// Save the shortest found representation.
str := strconv.FormatUint(am, 10) + string(unit)
if len(str) < len(shortened) {
shortened = str
}
}
return shortened, nil
}
package zpay32
import (
"fmt"
"strings"
)
const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
var gen = []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}
// NOTE: This method it a slight modification of the method bech32.Decode found
// btcutil, allowing strings to be more than 90 characters.
// decodeBech32 decodes a bech32 encoded string, returning the human-readable
// part and the data part excluding the checksum.
// Note: the data will be base32 encoded, that is each element of the returned
// byte array will encode 5 bits of data. Use the ConvertBits method to convert
// this to 8-bit representation.
func decodeBech32(bech string) (string, []byte, error) {
// The maximum allowed length for a bech32 string is 90. It must also
// be at least 8 characters, since it needs a non-empty HRP, a
// separator, and a 6 character checksum.
// NB: The 90 character check specified in BIP173 is skipped here, to
// allow strings longer than 90 characters.
if len(bech) < 8 {
return "", nil, fmt.Errorf("invalid bech32 string length %d",
len(bech))
}
// Only ASCII characters between 33 and 126 are allowed.
for i := 0; i < len(bech); i++ {
if bech[i] < 33 || bech[i] > 126 {
return "", nil, fmt.Errorf("invalid character in "+
"string: '%c'", bech[i])
}
}
// The characters must be either all lowercase or all uppercase.
lower := strings.ToLower(bech)
upper := strings.ToUpper(bech)
if bech != lower && bech != upper {
return "", nil, fmt.Errorf("string not all lowercase or all " +
"uppercase")
}
// We'll work with the lowercase string from now on.
bech = lower
// The string is invalid if the last '1' is non-existent, it is the
// first character of the string (no human-readable part) or one of the
// last 6 characters of the string (since checksum cannot contain '1'),
// or if the string is more than 90 characters in total.
one := strings.LastIndexByte(bech, '1')
if one < 1 || one+7 > len(bech) {
return "", nil, fmt.Errorf("invalid index of 1")
}
// The human-readable part is everything before the last '1'.
hrp := bech[:one]
data := bech[one+1:]
// Each character corresponds to the byte with value of the index in
// 'charset'.
decoded, err := toBytes(data)
if err != nil {
return "", nil, fmt.Errorf("failed converting data to bytes: "+
"%v", err)
}
if !bech32VerifyChecksum(hrp, decoded) {
moreInfo := ""
checksum := bech[len(bech)-6:]
expected, err := toChars(bech32Checksum(hrp,
decoded[:len(decoded)-6]))
if err == nil {
moreInfo = fmt.Sprintf("Expected %v, got %v.",
expected, checksum)
}
return "", nil, fmt.Errorf("checksum failed. " + moreInfo)
}
// We exclude the last 6 bytes, which is the checksum.
return hrp, decoded[:len(decoded)-6], nil
}
// toBytes converts each character in the string 'chars' to the value of the
// index of the corresponding character in 'charset'.
func toBytes(chars string) ([]byte, error) {
decoded := make([]byte, 0, len(chars))
for i := 0; i < len(chars); i++ {
index := strings.IndexByte(charset, chars[i])
if index < 0 {
return nil, fmt.Errorf("invalid character not part of "+
"charset: %v", chars[i])
}
decoded = append(decoded, byte(index))
}
return decoded, nil
}
// toChars converts the byte slice 'data' to a string where each byte in 'data'
// encodes the index of a character in 'charset'.
func toChars(data []byte) (string, error) {
result := make([]byte, 0, len(data))
for _, b := range data {
if int(b) >= len(charset) {
return "", fmt.Errorf("invalid data byte: %v", b)
}
result = append(result, charset[b])
}
return string(result), nil
}
// For more details on the checksum calculation, please refer to BIP 173.
func bech32Checksum(hrp string, data []byte) []byte {
// Convert the bytes to list of integers, as this is needed for the
// checksum calculation.
integers := make([]int, len(data))
for i, b := range data {
integers[i] = int(b)
}
values := append(bech32HrpExpand(hrp), integers...)
values = append(values, []int{0, 0, 0, 0, 0, 0}...)
polymod := bech32Polymod(values) ^ 1
var res []byte
for i := 0; i < 6; i++ {
res = append(res, byte((polymod>>uint(5*(5-i)))&31))
}
return res
}
// For more details on the polymod calculation, please refer to BIP 173.
func bech32Polymod(values []int) int {
chk := 1
for _, v := range values {
b := chk >> 25
chk = (chk&0x1ffffff)<<5 ^ v
for i := 0; i < 5; i++ {
if (b>>uint(i))&1 == 1 {
chk ^= gen[i]
}
}
}
return chk
}
// For more details on HRP expansion, please refer to BIP 173.
func bech32HrpExpand(hrp string) []int {
v := make([]int, 0, len(hrp)*2+1)
for i := 0; i < len(hrp); i++ {
v = append(v, int(hrp[i]>>5))
}
v = append(v, 0)
for i := 0; i < len(hrp); i++ {
v = append(v, int(hrp[i]&31))
}
return v
}
// For more details on the checksum verification, please refer to BIP 173.
func bech32VerifyChecksum(hrp string, data []byte) bool {
integers := make([]int, len(data))
for i, b := range data {
integers[i] = int(b)
}
concat := append(bech32HrpExpand(hrp), integers...)
return bech32Polymod(concat) == 1
}
package zpay32
import (
"encoding/binary"
"fmt"
"io"
"github.com/btcsuite/btcd/btcec/v2"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// maxNumHopsPerPath is the maximum number of blinded path hops that can
// be included in a single encoded blinded path. This is calculated
// based on the `data_length` limit of 638 bytes for any tagged field in
// a BOLT 11 invoice along with the estimated number of bytes required
// for encoding the most minimal blinded path hop. See the [bLIP
// proposal](https://github.com/lightning/blips/pull/39) for a detailed
// calculation.
maxNumHopsPerPath = 7
// maxCipherTextLength defines the largest cipher text size allowed.
// This is derived by using the `data_length` upper bound of 639 bytes
// and then assuming the case of a path with only a single hop (meaning
// the cipher text may be as large as possible).
maxCipherTextLength = 535
)
var (
// byteOrder defines the endian-ness we use for encoding to and from
// buffers.
byteOrder = binary.BigEndian
)
// BlindedPaymentPath holds all the information a payer needs to know about a
// blinded path to a receiver of a payment.
type BlindedPaymentPath struct {
// FeeBaseMsat is the total base fee for the path in milli-satoshis.
FeeBaseMsat uint32
// FeeRate is the total fee rate for the path in parts per million.
FeeRate uint32
// CltvExpiryDelta is the total CLTV delta to apply to the path.
CltvExpiryDelta uint16
// HTLCMinMsat is the minimum number of milli-satoshis that any hop in
// the path will route.
HTLCMinMsat uint64
// HTLCMaxMsat is the maximum number of milli-satoshis that a hop in the
// path will route.
HTLCMaxMsat uint64
// Features is the feature bit vector for the path.
Features *lnwire.FeatureVector
// FirstEphemeralBlindingPoint is the blinding point to send to the
// introduction node. It will be used by the introduction node to derive
// a shared secret with the receiver which can then be used to decode
// the encrypted payload from the receiver.
FirstEphemeralBlindingPoint *btcec.PublicKey
// Hops is the blinded path. The first hop is the introduction node and
// so the BlindedNodeID of this hop will be the real node ID.
Hops []*sphinx.BlindedHopInfo
}
// DecodeBlindedPayment attempts to parse a BlindedPaymentPath from the passed
// reader.
func DecodeBlindedPayment(r io.Reader) (*BlindedPaymentPath, error) {
var payment BlindedPaymentPath
if err := binary.Read(r, byteOrder, &payment.FeeBaseMsat); err != nil {
return nil, err
}
if err := binary.Read(r, byteOrder, &payment.FeeRate); err != nil {
return nil, err
}
err := binary.Read(r, byteOrder, &payment.CltvExpiryDelta)
if err != nil {
return nil, err
}
err = binary.Read(r, byteOrder, &payment.HTLCMinMsat)
if err != nil {
return nil, err
}
err = binary.Read(r, byteOrder, &payment.HTLCMaxMsat)
if err != nil {
return nil, err
}
// Parse the feature bit vector.
f := lnwire.EmptyFeatureVector()
err = f.Decode(r)
if err != nil {
return nil, err
}
payment.Features = f
// Parse the first ephemeral blinding point.
var blindingPointBytes [btcec.PubKeyBytesLenCompressed]byte
_, err = r.Read(blindingPointBytes[:])
if err != nil {
return nil, err
}
blinding, err := btcec.ParsePubKey(blindingPointBytes[:])
if err != nil {
return nil, err
}
payment.FirstEphemeralBlindingPoint = blinding
// Read the one byte hop number.
var numHops [1]byte
_, err = r.Read(numHops[:])
if err != nil {
return nil, err
}
payment.Hops = make([]*sphinx.BlindedHopInfo, int(numHops[0]))
// Parse each hop.
for i := 0; i < len(payment.Hops); i++ {
hop, err := DecodeBlindedHop(r)
if err != nil {
return nil, err
}
payment.Hops[i] = hop
}
return &payment, nil
}
// Encode serialises the BlindedPaymentPath and writes the bytes to the passed
// writer.
// 1) The first 26 bytes contain the relay info:
// - Base Fee in msat: uint32 (4 bytes).
// - Proportional Fee in PPM: uint32 (4 bytes).
// - CLTV expiry delta: uint16 (2 bytes).
// - HTLC min msat: uint64 (8 bytes).
// - HTLC max msat: uint64 (8 bytes).
//
// 2) Feature bit vector length (2 bytes).
// 3) Feature bit vector (can be zero length).
// 4) First blinding point: 33 bytes.
// 5) Number of hops: 1 byte.
// 6) Encoded BlindedHops.
func (p *BlindedPaymentPath) Encode(w io.Writer) error {
if err := binary.Write(w, byteOrder, p.FeeBaseMsat); err != nil {
return err
}
if err := binary.Write(w, byteOrder, p.FeeRate); err != nil {
return err
}
if err := binary.Write(w, byteOrder, p.CltvExpiryDelta); err != nil {
return err
}
if err := binary.Write(w, byteOrder, p.HTLCMinMsat); err != nil {
return err
}
if err := binary.Write(w, byteOrder, p.HTLCMaxMsat); err != nil {
return err
}
if err := p.Features.Encode(w); err != nil {
return err
}
_, err := w.Write(p.FirstEphemeralBlindingPoint.SerializeCompressed())
if err != nil {
return err
}
numHops := len(p.Hops)
if numHops > maxNumHopsPerPath {
return fmt.Errorf("the number of hops, %d, exceeds the "+
"maximum of %d", numHops, maxNumHopsPerPath)
}
if _, err := w.Write([]byte{byte(numHops)}); err != nil {
return err
}
for _, hop := range p.Hops {
if err := EncodeBlindedHop(w, hop); err != nil {
return err
}
}
return nil
}
// DecodeBlindedHop reads a sphinx.BlindedHopInfo from the passed reader.
func DecodeBlindedHop(r io.Reader) (*sphinx.BlindedHopInfo, error) {
var nodeIDBytes [btcec.PubKeyBytesLenCompressed]byte
_, err := r.Read(nodeIDBytes[:])
if err != nil {
return nil, err
}
nodeID, err := btcec.ParsePubKey(nodeIDBytes[:])
if err != nil {
return nil, err
}
dataLen, err := tlv.ReadVarInt(r, &[8]byte{})
if err != nil {
return nil, err
}
if dataLen > maxCipherTextLength {
return nil, fmt.Errorf("a blinded hop cipher text blob may "+
"not exceed the maximum of %d bytes",
maxCipherTextLength)
}
encryptedData := make([]byte, dataLen)
_, err = r.Read(encryptedData)
if err != nil {
return nil, err
}
return &sphinx.BlindedHopInfo{
BlindedNodePub: nodeID,
CipherText: encryptedData,
}, nil
}
// EncodeBlindedHop writes the passed BlindedHopInfo to the given writer.
//
// 1) Blinded node pub key: 33 bytes
// 2) Cipher text length: BigSize
// 3) Cipher text.
func EncodeBlindedHop(w io.Writer, hop *sphinx.BlindedHopInfo) error {
_, err := w.Write(hop.BlindedNodePub.SerializeCompressed())
if err != nil {
return err
}
if len(hop.CipherText) > maxCipherTextLength {
return fmt.Errorf("encrypted recipient data can not exceed a "+
"length of %d bytes", maxCipherTextLength)
}
err = tlv.WriteVarInt(w, uint64(len(hop.CipherText)), &[8]byte{})
if err != nil {
return err
}
_, err = w.Write(hop.CipherText)
return err
}
package zpay32
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"strings"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/bech32"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnwire"
)
// DecodeOption is a type that can be used to supply functional options to the
// Decode function.
type DecodeOption func(*decodeOptions)
// WithKnownFeatureBits is a functional option that overwrites the set of
// known feature bits. If not set, then LND's lnwire.Features variable will be
// used by default.
func WithKnownFeatureBits(features map[lnwire.FeatureBit]string) DecodeOption {
return func(options *decodeOptions) {
options.knownFeatureBits = features
}
}
// WithErrorOnUnknownFeatureBit is a functional option that will cause the
// Decode function to return an error if the decoded invoice contains an unknown
// feature bit.
func WithErrorOnUnknownFeatureBit() DecodeOption {
return func(options *decodeOptions) {
options.errorOnUnknownFeature = true
}
}
// decodeOptions holds the set of Decode options.
type decodeOptions struct {
knownFeatureBits map[lnwire.FeatureBit]string
errorOnUnknownFeature bool
}
// newDecodeOptions constructs the default decodeOptions struct.
func newDecodeOptions() *decodeOptions {
return &decodeOptions{
knownFeatureBits: lnwire.Features,
errorOnUnknownFeature: false,
}
}
// Decode parses the provided encoded invoice and returns a decoded Invoice if
// it is valid by BOLT-0011 and matches the provided active network.
func Decode(invoice string, net *chaincfg.Params, opts ...DecodeOption) (
*Invoice, error) {
options := newDecodeOptions()
for _, o := range opts {
o(options)
}
var decodedInvoice Invoice
// Before bech32 decoding the invoice, make sure that it is not too large.
// This is done as an anti-DoS measure since bech32 decoding is expensive.
if len(invoice) > maxInvoiceLength {
return nil, ErrInvoiceTooLarge
}
// Decode the invoice using the modified bech32 decoder.
hrp, data, err := decodeBech32(invoice)
if err != nil {
return nil, err
}
// We expect the human-readable part to at least have ln + one char
// encoding the network.
if len(hrp) < 3 {
return nil, fmt.Errorf("hrp too short")
}
// First two characters of HRP should be "ln".
if hrp[:2] != "ln" {
return nil, fmt.Errorf("prefix should be \"ln\"")
}
// The next characters should be a valid prefix for a segwit BIP173
// address that match the active network except for signet where we add
// an additional "s" to differentiate it from the older testnet3 (Core
// devs decided to use the same hrp for signet as for testnet3 which is
// not optimal for LN). See
// https://github.com/lightningnetwork/lightning-rfc/pull/844 for more
// information.
expectedPrefix := net.Bech32HRPSegwit
if net.Name == chaincfg.SigNetParams.Name {
expectedPrefix = "tbs"
}
if !strings.HasPrefix(hrp[2:], expectedPrefix) {
return nil, fmt.Errorf(
"invoice not for current active network '%s'", net.Name)
}
decodedInvoice.Net = net
// Optionally, if there's anything left of the HRP after ln + the segwit
// prefix, we try to decode this as the payment amount.
var netPrefixLength = len(expectedPrefix) + 2
if len(hrp) > netPrefixLength {
amount, err := decodeAmount(hrp[netPrefixLength:])
if err != nil {
return nil, err
}
decodedInvoice.MilliSat = &amount
}
// Everything except the last 520 bits of the data encodes the invoice's
// timestamp and tagged fields.
if len(data) < signatureBase32Len {
return nil, errors.New("short invoice")
}
invoiceData := data[:len(data)-signatureBase32Len]
// Parse the timestamp and tagged fields, and fill the Invoice struct.
if err := parseData(&decodedInvoice, invoiceData, net); err != nil {
return nil, err
}
// The last 520 bits (104 groups) make up the signature.
sigBase32 := data[len(data)-signatureBase32Len:]
sigBase256, err := bech32.ConvertBits(sigBase32, 5, 8, true)
if err != nil {
return nil, err
}
sig, err := lnwire.NewSigFromWireECDSA(sigBase256[:64])
if err != nil {
return nil, err
}
recoveryID := sigBase256[64]
// The signature is over the hrp + the data the invoice, encoded in
// base 256.
taggedDataBytes, err := bech32.ConvertBits(invoiceData, 5, 8, true)
if err != nil {
return nil, err
}
toSign := append([]byte(hrp), taggedDataBytes...)
// We expect the signature to be over the single SHA-256 hash of that
// data.
hash := chainhash.HashB(toSign)
// If the destination pubkey was provided as a tagged field, use that
// to verify the signature, if not do public key recovery.
if decodedInvoice.Destination != nil {
signature, err := sig.ToSignature()
if err != nil {
return nil, fmt.Errorf("unable to deserialize "+
"signature: %v", err)
}
if !signature.Verify(hash, decodedInvoice.Destination) {
return nil, fmt.Errorf("invalid invoice signature")
}
} else {
headerByte := recoveryID + 27 + 4
compactSign := append([]byte{headerByte}, sig.RawBytes()...)
pubkey, _, err := ecdsa.RecoverCompact(compactSign, hash)
if err != nil {
return nil, err
}
decodedInvoice.Destination = pubkey
}
// If no feature vector was decoded, populate an empty one.
if decodedInvoice.Features == nil {
decodedInvoice.Features = lnwire.NewFeatureVector(
nil, options.knownFeatureBits,
)
}
// Now that we have created the invoice, make sure it has the required
// fields set.
if err := validateInvoice(&decodedInvoice); err != nil {
return nil, err
}
if options.errorOnUnknownFeature {
// Make sure that we understand all the required feature bits
// in the invoice.
unknownFeatureBits := decodedInvoice.Features.
UnknownRequiredFeatures()
if len(unknownFeatureBits) > 0 {
errStr := fmt.Sprintf("invoice contains " +
"unknown feature bits:")
for _, bit := range unknownFeatureBits {
errStr += fmt.Sprintf(" %d,", bit)
}
return nil, errors.New(strings.TrimRight(errStr, ","))
}
}
return &decodedInvoice, nil
}
// parseData parses the data part of the invoice. It expects base32 data
// returned from the bech32.Decode method, except signature.
func parseData(invoice *Invoice, data []byte, net *chaincfg.Params) error {
// It must contain the timestamp, encoded using 35 bits (7 groups).
if len(data) < timestampBase32Len {
return fmt.Errorf("data too short: %d", len(data))
}
t, err := parseTimestamp(data[:timestampBase32Len])
if err != nil {
return err
}
invoice.Timestamp = time.Unix(int64(t), 0)
// The rest are tagged parts.
tagData := data[7:]
return parseTaggedFields(invoice, tagData, net)
}
// parseTimestamp converts a 35-bit timestamp (encoded in base32) to uint64.
func parseTimestamp(data []byte) (uint64, error) {
if len(data) != timestampBase32Len {
return 0, fmt.Errorf("timestamp must be 35 bits, was %d",
len(data)*5)
}
return base32ToUint64(data)
}
// parseTaggedFields takes the base32 encoded tagged fields of the invoice, and
// fills the Invoice struct accordingly.
func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) error {
index := 0
for len(fields)-index > 0 {
// If there are less than 3 groups to read, there cannot be more
// interesting information, as we need the type (1 group) and
// length (2 groups).
//
// This means the last tagged field is broken.
if len(fields)-index < 3 {
return ErrBrokenTaggedField
}
typ := fields[index]
dataLength, err := parseFieldDataLength(fields[index+1 : index+3])
if err != nil {
return err
}
// If we don't have enough field data left to read this length,
// return error.
if len(fields) < index+3+int(dataLength) {
return ErrInvalidFieldLength
}
base32Data := fields[index+3 : index+3+int(dataLength)]
// Advance the index in preparation for the next iteration.
index += 3 + int(dataLength)
switch typ {
case fieldTypeP:
if invoice.PaymentHash != nil {
// We skip the field if we have already seen a
// supported one.
continue
}
invoice.PaymentHash, err = parse32Bytes(base32Data)
case fieldTypeS:
if invoice.PaymentAddr.IsSome() {
// We skip the field if we have already seen a
// supported one.
continue
}
addr, err := parse32Bytes(base32Data)
if err != nil {
return err
}
if addr != nil {
invoice.PaymentAddr = fn.Some(*addr)
}
case fieldTypeD:
if invoice.Description != nil {
// We skip the field if we have already seen a
// supported one.
continue
}
invoice.Description, err = parseDescription(base32Data)
case fieldTypeM:
if invoice.Metadata != nil {
// We skip the field if we have already seen a
// supported one.
continue
}
invoice.Metadata, err = parseMetadata(base32Data)
case fieldTypeN:
if invoice.Destination != nil {
// We skip the field if we have already seen a
// supported one.
continue
}
invoice.Destination, err = parseDestination(base32Data)
case fieldTypeH:
if invoice.DescriptionHash != nil {
// We skip the field if we have already seen a
// supported one.
continue
}
invoice.DescriptionHash, err = parse32Bytes(base32Data)
case fieldTypeX:
if invoice.expiry != nil {
// We skip the field if we have already seen a
// supported one.
continue
}
invoice.expiry, err = parseExpiry(base32Data)
case fieldTypeC:
if invoice.minFinalCLTVExpiry != nil {
// We skip the field if we have already seen a
// supported one.
continue
}
invoice.minFinalCLTVExpiry, err =
parseMinFinalCLTVExpiry(base32Data)
case fieldTypeF:
if invoice.FallbackAddr != nil {
// We skip the field if we have already seen a
// supported one.
continue
}
invoice.FallbackAddr, err = parseFallbackAddr(
base32Data, net,
)
case fieldTypeR:
// An `r` field can be included in an invoice multiple
// times, so we won't skip it if we have already seen
// one.
routeHint, err := parseRouteHint(base32Data)
if err != nil {
return err
}
invoice.RouteHints = append(
invoice.RouteHints, routeHint,
)
case fieldType9:
if invoice.Features != nil {
// We skip the field if we have already seen a
// supported one.
continue
}
invoice.Features, err = parseFeatures(base32Data)
case fieldTypeB:
blindedPaymentPath, err := parseBlindedPaymentPath(
base32Data,
)
if err != nil {
return err
}
invoice.BlindedPaymentPaths = append(
invoice.BlindedPaymentPaths, blindedPaymentPath,
)
default:
// Ignore unknown type.
}
// Check if there was an error from parsing any of the tagged
// fields and return it.
if err != nil {
return err
}
}
return nil
}
// parseFieldDataLength converts the two byte slice into a uint16.
func parseFieldDataLength(data []byte) (uint16, error) {
if len(data) != 2 {
return 0, fmt.Errorf("data length must be 2 bytes, was %d",
len(data))
}
return uint16(data[0])<<5 | uint16(data[1]), nil
}
// parse32Bytes converts a 256-bit value (encoded in base32) to *[32]byte. This
// can be used for payment hashes, description hashes, payment addresses, etc.
func parse32Bytes(data []byte) (*[32]byte, error) {
var paymentHash [32]byte
// As BOLT-11 states, a reader must skip over the 32-byte fields if
// it does not have a length of 52, so avoid returning an error.
if len(data) != hashBase32Len {
return nil, nil
}
hash, err := bech32.ConvertBits(data, 5, 8, false)
if err != nil {
return nil, err
}
copy(paymentHash[:], hash)
return &paymentHash, nil
}
// parseDescription converts the data (encoded in base32) into a string to use
// as the description.
func parseDescription(data []byte) (*string, error) {
base256Data, err := bech32.ConvertBits(data, 5, 8, false)
if err != nil {
return nil, err
}
description := string(base256Data)
return &description, nil
}
// parseMetadata converts the data (encoded in base32) into a byte slice to use
// as the metadata.
func parseMetadata(data []byte) ([]byte, error) {
return bech32.ConvertBits(data, 5, 8, false)
}
// parseDestination converts the data (encoded in base32) into a 33-byte public
// key of the payee node.
func parseDestination(data []byte) (*btcec.PublicKey, error) {
// As BOLT-11 states, a reader must skip over the destination field
// if it does not have a length of 53, so avoid returning an error.
if len(data) != pubKeyBase32Len {
return nil, nil
}
base256Data, err := bech32.ConvertBits(data, 5, 8, false)
if err != nil {
return nil, err
}
return btcec.ParsePubKey(base256Data)
}
// parseExpiry converts the data (encoded in base32) into the expiry time.
func parseExpiry(data []byte) (*time.Duration, error) {
expiry, err := base32ToUint64(data)
if err != nil {
return nil, err
}
duration := time.Duration(expiry) * time.Second
return &duration, nil
}
// parseMinFinalCLTVExpiry converts the data (encoded in base32) into a uint64
// to use as the minFinalCLTVExpiry.
func parseMinFinalCLTVExpiry(data []byte) (*uint64, error) {
expiry, err := base32ToUint64(data)
if err != nil {
return nil, err
}
return &expiry, nil
}
// parseFallbackAddr converts the data (encoded in base32) into a fallback
// on-chain address.
func parseFallbackAddr(data []byte, net *chaincfg.Params) (btcutil.Address, error) { // nolint:dupl
// Checks if the data is empty or contains a version without an address.
if len(data) < 2 {
return nil, fmt.Errorf("empty fallback address field")
}
var addr btcutil.Address
version := data[0]
switch version {
case 0:
witness, err := bech32.ConvertBits(data[1:], 5, 8, false)
if err != nil {
return nil, err
}
switch len(witness) {
case 20:
addr, err = btcutil.NewAddressWitnessPubKeyHash(witness, net)
case 32:
addr, err = btcutil.NewAddressWitnessScriptHash(witness, net)
default:
return nil, fmt.Errorf("unknown witness program length %d",
len(witness))
}
if err != nil {
return nil, err
}
case 17:
pubKeyHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
if err != nil {
return nil, err
}
addr, err = btcutil.NewAddressPubKeyHash(pubKeyHash, net)
if err != nil {
return nil, err
}
case 18:
scriptHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
if err != nil {
return nil, err
}
addr, err = btcutil.NewAddressScriptHashFromHash(scriptHash, net)
if err != nil {
return nil, err
}
default:
// Ignore unknown version.
}
return addr, nil
}
// parseRouteHint converts the data (encoded in base32) into an array containing
// one or more routing hop hints that represent a single route hint.
func parseRouteHint(data []byte) ([]HopHint, error) {
base256Data, err := bech32.ConvertBits(data, 5, 8, false)
if err != nil {
return nil, err
}
// Check that base256Data is a multiple of hopHintLen.
if len(base256Data)%hopHintLen != 0 {
return nil, fmt.Errorf("expected length multiple of %d bytes, "+
"got %d", hopHintLen, len(base256Data))
}
var routeHint []HopHint
for len(base256Data) > 0 {
hopHint := HopHint{}
hopHint.NodeID, err = btcec.ParsePubKey(base256Data[:33])
if err != nil {
return nil, err
}
hopHint.ChannelID = binary.BigEndian.Uint64(base256Data[33:41])
hopHint.FeeBaseMSat = binary.BigEndian.Uint32(base256Data[41:45])
hopHint.FeeProportionalMillionths = binary.BigEndian.Uint32(base256Data[45:49])
hopHint.CLTVExpiryDelta = binary.BigEndian.Uint16(base256Data[49:51])
routeHint = append(routeHint, hopHint)
base256Data = base256Data[51:]
}
return routeHint, nil
}
// parseBlindedPaymentPath attempts to parse a BlindedPaymentPath from the given
// byte slice.
func parseBlindedPaymentPath(data []byte) (*BlindedPaymentPath, error) {
base256Data, err := bech32.ConvertBits(data, 5, 8, false)
if err != nil {
return nil, err
}
return DecodeBlindedPayment(bytes.NewReader(base256Data))
}
// parseFeatures decodes any feature bits directly from the base32
// representation.
func parseFeatures(data []byte) (*lnwire.FeatureVector, error) {
rawFeatures := lnwire.NewRawFeatureVector()
err := rawFeatures.DecodeBase32(bytes.NewReader(data), len(data))
if err != nil {
return nil, err
}
return lnwire.NewFeatureVector(rawFeatures, lnwire.Features), nil
}
// base32ToUint64 converts a base32 encoded number to uint64.
func base32ToUint64(data []byte) (uint64, error) {
// Maximum that fits in uint64 is ceil(64 / 5) = 12 groups.
if len(data) > 13 {
return 0, fmt.Errorf("cannot parse data of length %d as uint64",
len(data))
}
val := uint64(0)
for i := 0; i < len(data); i++ {
val = val<<5 | uint64(data[i])
}
return val, nil
}
package zpay32
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/bech32"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnwire"
)
// Encode takes the given MessageSigner and returns a string encoding this
// invoice signed by the node key of the signer.
func (invoice *Invoice) Encode(signer MessageSigner) (string, error) {
// First check that this invoice is valid before starting the encoding.
if err := validateInvoice(invoice); err != nil {
return "", err
}
// The buffer will encoded the invoice data using 5-bit groups (base32).
var bufferBase32 bytes.Buffer
// The timestamp will be encoded using 35 bits, in base32.
timestampBase32 := uint64ToBase32(uint64(invoice.Timestamp.Unix()))
// The timestamp must be exactly 35 bits, which means 7 groups. If it
// can fit into fewer groups we add leading zero groups, if it is too
// big we fail early, as there is not possible to encode it.
if len(timestampBase32) > timestampBase32Len {
return "", fmt.Errorf("timestamp too big: %d",
invoice.Timestamp.Unix())
}
// Add zero bytes to the first timestampBase32Len-len(timestampBase32)
// groups, then add the non-zero groups.
zeroes := make([]byte, timestampBase32Len-len(timestampBase32))
_, err := bufferBase32.Write(zeroes)
if err != nil {
return "", fmt.Errorf("unable to write to buffer: %w", err)
}
_, err = bufferBase32.Write(timestampBase32)
if err != nil {
return "", fmt.Errorf("unable to write to buffer: %w", err)
}
// We now write the tagged fields to the buffer, which will fill the
// rest of the data part before the signature.
if err := writeTaggedFields(&bufferBase32, invoice); err != nil {
return "", err
}
// The human-readable part (hrp) is "ln" + net hrp + optional amount,
// except for signet where we add an additional "s" to differentiate it
// from the older testnet3 (Core devs decided to use the same hrp for
// signet as for testnet3 which is not optimal for LN). See
// https://github.com/lightningnetwork/lightning-rfc/pull/844 for more
// information.
hrp := "ln" + invoice.Net.Bech32HRPSegwit
if invoice.Net.Name == chaincfg.SigNetParams.Name {
hrp = "lntbs"
}
if invoice.MilliSat != nil {
// Encode the amount using the fewest possible characters.
am, err := encodeAmount(*invoice.MilliSat)
if err != nil {
return "", err
}
hrp += am
}
// The signature is over the single SHA-256 hash of the hrp + the
// tagged fields encoded in base256.
taggedFieldsBytes, err := bech32.ConvertBits(bufferBase32.Bytes(), 5, 8, true)
if err != nil {
return "", err
}
toSign := append([]byte(hrp), taggedFieldsBytes...)
// We use compact signature format, and also encoded the recovery ID
// such that a reader of the invoice can recover our pubkey from the
// signature.
sign, err := signer.SignCompact(toSign)
if err != nil {
return "", err
}
// From the header byte we can extract the recovery ID, and the last 64
// bytes encode the signature.
recoveryID := sign[0] - 27 - 4
sig, err := lnwire.NewSigFromWireECDSA(sign[1:])
if err != nil {
return "", err
}
// If the pubkey field was explicitly set, it must be set to the pubkey
// used to create the signature.
if invoice.Destination != nil {
signature, err := sig.ToSignature()
if err != nil {
return "", fmt.Errorf("unable to deserialize "+
"signature: %v", err)
}
hash := chainhash.HashB(toSign)
valid := signature.Verify(hash, invoice.Destination)
if !valid {
return "", fmt.Errorf("signature does not match " +
"provided pubkey")
}
}
// Convert the signature to base32 before writing it to the buffer.
signBase32, err := bech32.ConvertBits(
append(sig.RawBytes(), recoveryID),
8, 5, true,
)
if err != nil {
return "", err
}
bufferBase32.Write(signBase32)
// Now we can create the bech32 encoded string from the base32 buffer.
b32, err := bech32.Encode(hrp, bufferBase32.Bytes())
if err != nil {
return "", err
}
// Before returning, check that the bech32 encoded string is not greater
// than our largest supported invoice size.
if len(b32) > maxInvoiceLength {
return "", ErrInvoiceTooLarge
}
return b32, nil
}
// writeTaggedFields writes the non-nil tagged fields of the Invoice to the
// base32 buffer.
func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error {
if invoice.PaymentHash != nil {
err := writeBytes32(bufferBase32, fieldTypeP, *invoice.PaymentHash)
if err != nil {
return err
}
}
if invoice.Description != nil {
base32, err := bech32.ConvertBits([]byte(*invoice.Description),
8, 5, true)
if err != nil {
return err
}
err = writeTaggedField(bufferBase32, fieldTypeD, base32)
if err != nil {
return err
}
}
if invoice.DescriptionHash != nil {
err := writeBytes32(
bufferBase32, fieldTypeH, *invoice.DescriptionHash,
)
if err != nil {
return err
}
}
if invoice.Metadata != nil {
base32, err := bech32.ConvertBits(invoice.Metadata, 8, 5, true)
if err != nil {
return err
}
err = writeTaggedField(bufferBase32, fieldTypeM, base32)
if err != nil {
return err
}
}
if invoice.minFinalCLTVExpiry != nil {
finalDelta := uint64ToBase32(*invoice.minFinalCLTVExpiry)
err := writeTaggedField(bufferBase32, fieldTypeC, finalDelta)
if err != nil {
return err
}
}
if invoice.expiry != nil {
seconds := invoice.expiry.Seconds()
expiry := uint64ToBase32(uint64(seconds))
err := writeTaggedField(bufferBase32, fieldTypeX, expiry)
if err != nil {
return err
}
}
if invoice.FallbackAddr != nil {
var version byte
switch addr := invoice.FallbackAddr.(type) {
case *btcutil.AddressPubKeyHash:
version = 17
case *btcutil.AddressScriptHash:
version = 18
case *btcutil.AddressWitnessPubKeyHash:
version = addr.WitnessVersion()
case *btcutil.AddressWitnessScriptHash:
version = addr.WitnessVersion()
default:
return fmt.Errorf("unknown fallback address type")
}
base32Addr, err := bech32.ConvertBits(
invoice.FallbackAddr.ScriptAddress(), 8, 5, true)
if err != nil {
return err
}
err = writeTaggedField(bufferBase32, fieldTypeF,
append([]byte{version}, base32Addr...))
if err != nil {
return err
}
}
for _, routeHint := range invoice.RouteHints {
// Each hop hint is encoded using 51 bytes, so we'll make to
// sure to allocate enough space for the whole route hint.
routeHintBase256 := make([]byte, 0, hopHintLen*len(routeHint))
for _, hopHint := range routeHint {
hopHintBase256 := make([]byte, hopHintLen)
copy(hopHintBase256[:33], hopHint.NodeID.SerializeCompressed())
binary.BigEndian.PutUint64(
hopHintBase256[33:41], hopHint.ChannelID,
)
binary.BigEndian.PutUint32(
hopHintBase256[41:45], hopHint.FeeBaseMSat,
)
binary.BigEndian.PutUint32(
hopHintBase256[45:49], hopHint.FeeProportionalMillionths,
)
binary.BigEndian.PutUint16(
hopHintBase256[49:51], hopHint.CLTVExpiryDelta,
)
routeHintBase256 = append(routeHintBase256, hopHintBase256...)
}
routeHintBase32, err := bech32.ConvertBits(
routeHintBase256, 8, 5, true,
)
if err != nil {
return err
}
err = writeTaggedField(bufferBase32, fieldTypeR, routeHintBase32)
if err != nil {
return err
}
}
for _, path := range invoice.BlindedPaymentPaths {
var buf bytes.Buffer
err := path.Encode(&buf)
if err != nil {
return err
}
blindedPathBase32, err := bech32.ConvertBits(
buf.Bytes(), 8, 5, true,
)
if err != nil {
return err
}
err = writeTaggedField(
bufferBase32, fieldTypeB, blindedPathBase32,
)
if err != nil {
return err
}
}
if invoice.Destination != nil {
// Convert 33 byte pubkey to 53 5-bit groups.
pubKeyBase32, err := bech32.ConvertBits(
invoice.Destination.SerializeCompressed(), 8, 5, true)
if err != nil {
return err
}
if len(pubKeyBase32) != pubKeyBase32Len {
return fmt.Errorf("invalid pubkey length: %d",
len(invoice.Destination.SerializeCompressed()))
}
err = writeTaggedField(bufferBase32, fieldTypeN, pubKeyBase32)
if err != nil {
return err
}
}
err := fn.MapOptionZ(invoice.PaymentAddr, func(addr [32]byte) error {
return writeBytes32(bufferBase32, fieldTypeS, addr)
})
if err != nil {
return err
}
if invoice.Features.SerializeSize32() > 0 {
var b bytes.Buffer
err := invoice.Features.RawFeatureVector.EncodeBase32(&b)
if err != nil {
return err
}
err = writeTaggedField(bufferBase32, fieldType9, b.Bytes())
if err != nil {
return err
}
}
return nil
}
// writeBytes32 encodes a 32-byte array as base32 and writes it to bufferBase32
// under the passed fieldType.
func writeBytes32(bufferBase32 *bytes.Buffer, fieldType byte, b [32]byte) error {
// Convert 32 byte hash to 52 5-bit groups.
base32, err := bech32.ConvertBits(b[:], 8, 5, true)
if err != nil {
return err
}
return writeTaggedField(bufferBase32, fieldType, base32)
}
// writeTaggedField takes the type of a tagged data field, and the data of
// the tagged field (encoded in base32), and writes the type, length and data
// to the buffer.
func writeTaggedField(bufferBase32 *bytes.Buffer, dataType byte, data []byte) error {
// Length must be exactly 10 bits, so add leading zero groups if
// needed.
lenBase32 := uint64ToBase32(uint64(len(data)))
for len(lenBase32) < 2 {
lenBase32 = append([]byte{0}, lenBase32...)
}
if len(lenBase32) != 2 {
return fmt.Errorf("data length too big to fit within 10 bits: %d",
len(data))
}
err := bufferBase32.WriteByte(dataType)
if err != nil {
return fmt.Errorf("unable to write to buffer: %w", err)
}
_, err = bufferBase32.Write(lenBase32)
if err != nil {
return fmt.Errorf("unable to write to buffer: %w", err)
}
_, err = bufferBase32.Write(data)
if err != nil {
return fmt.Errorf("unable to write to buffer: %w", err)
}
return nil
}
// uint64ToBase32 converts a uint64 to a base32 encoded integer encoded using
// as few 5-bit groups as possible.
func uint64ToBase32(num uint64) []byte {
// Return at least one group.
if num == 0 {
return []byte{0}
}
// To fit an uint64, we need at most is ceil(64 / 5) = 13 groups.
arr := make([]byte, 13)
i := 13
for num > 0 {
i--
arr[i] = byte(num & uint64(31)) // 0b11111 in binary
num >>= 5
}
// We only return non-zero leading groups.
return arr[i:]
}
package zpay32
import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/lnwire"
)
const (
// DefaultAssumedFinalCLTVDelta is the default value to be used as the
// final CLTV delta for a route if one is unspecified in the payment
// request.
// We adhere to the recommendation in BOLT 02 for terminal payments.
// See also:
// https://github.com/lightning/bolts/blob/master/02-peer-protocol.md
DefaultAssumedFinalCLTVDelta = 18
// feeRateParts is the total number of parts used to express fee rates.
feeRateParts = 1e6
)
// HopHint is a routing hint that contains the minimum information of a channel
// required for an intermediate hop in a route to forward the payment to the
// next. This should be ideally used for private channels, since they are not
// publicly advertised to the network for routing.
type HopHint struct {
// NodeID is the public key of the node at the start of the channel.
NodeID *btcec.PublicKey
// ChannelID is the unique identifier of the channel.
ChannelID uint64
// FeeBaseMSat is the base fee of the channel in millisatoshis.
FeeBaseMSat uint32
// FeeProportionalMillionths is the fee rate, in millionths of a
// satoshi, for every satoshi sent through the channel.
FeeProportionalMillionths uint32
// CLTVExpiryDelta is the time-lock delta of the channel.
CLTVExpiryDelta uint16
}
// Copy returns a deep copy of the hop hint.
func (h HopHint) Copy() HopHint {
nodeID := *h.NodeID
return HopHint{
NodeID: &nodeID,
ChannelID: h.ChannelID,
FeeBaseMSat: h.FeeBaseMSat,
FeeProportionalMillionths: h.FeeProportionalMillionths,
CLTVExpiryDelta: h.CLTVExpiryDelta,
}
}
// HopFee calculates the fee for a given amount that is forwarded over a hop.
// The amount has to be denoted in milli satoshi. The returned fee is also
// denoted in milli satoshi.
func (h HopHint) HopFee(amt lnwire.MilliSatoshi) lnwire.MilliSatoshi {
baseFee := lnwire.MilliSatoshi(h.FeeBaseMSat)
feeRate := lnwire.MilliSatoshi(h.FeeProportionalMillionths)
return baseFee + (amt*feeRate)/feeRateParts
}
package zpay32
import (
"errors"
"fmt"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnwire"
)
const (
// mSatPerBtc is the number of millisatoshis in 1 BTC.
mSatPerBtc = 100000000000
// signatureBase32Len is the number of 5-bit groups needed to encode
// the 512 bit signature + 8 bit recovery ID.
signatureBase32Len = 104
// timestampBase32Len is the number of 5-bit groups needed to encode
// the 35-bit timestamp.
timestampBase32Len = 7
// hashBase32Len is the number of 5-bit groups needed to encode a
// 256-bit hash. Note that the last group will be padded with zeroes.
hashBase32Len = 52
// pubKeyBase32Len is the number of 5-bit groups needed to encode a
// 33-byte compressed pubkey. Note that the last group will be padded
// with zeroes.
pubKeyBase32Len = 53
// hopHintLen is the number of bytes needed to encode the hop hint of a
// single private route.
hopHintLen = 51
// The following byte values correspond to the supported field types.
// The field name is the character representing that 5-bit value in the
// bech32 string.
// fieldTypeP is the field containing the payment hash.
fieldTypeP = 1
// fieldTypeD contains a short description of the payment.
fieldTypeD = 13
// fieldTypeM contains the payment metadata.
fieldTypeM = 27
// fieldTypeN contains the pubkey of the target node.
fieldTypeN = 19
// fieldTypeH contains the hash of a description of the payment.
fieldTypeH = 23
// fieldTypeX contains the expiry in seconds of the invoice.
fieldTypeX = 6
// fieldTypeF contains a fallback on-chain address.
fieldTypeF = 9
// fieldTypeR contains extra routing information.
fieldTypeR = 3
// fieldTypeC contains an optional requested final CLTV delta.
fieldTypeC = 24
// fieldType9 contains one or more bytes for signaling features
// supported or required by the receiver.
fieldType9 = 5
// fieldTypeS contains a 32-byte payment address, which is a nonce
// included in the final hop's payload to prevent intermediaries from
// probing the recipient.
fieldTypeS = 16
// fieldTypeB contains blinded payment path information. This field may
// be repeated to include multiple blinded payment paths in the invoice.
fieldTypeB = 20
// maxInvoiceLength is the maximum total length an invoice can have.
// This is chosen to be the maximum number of bytes that can fit into a
// single QR code: https://en.wikipedia.org/wiki/QR_code#Storage
maxInvoiceLength = 7089
// DefaultInvoiceExpiry is the default expiry duration from the creation
// timestamp if expiry is set to zero.
DefaultInvoiceExpiry = time.Hour
)
var (
// ErrInvoiceTooLarge is returned when an invoice exceeds
// maxInvoiceLength.
ErrInvoiceTooLarge = errors.New("invoice is too large")
// ErrInvalidFieldLength is returned when a tagged field was specified
// with a length larger than the left over bytes of the data field.
ErrInvalidFieldLength = errors.New("invalid field length")
// ErrBrokenTaggedField is returned when the last tagged field is
// incorrectly formatted and doesn't have enough bytes to be read.
ErrBrokenTaggedField = errors.New("last tagged field is broken")
)
// MessageSigner is passed to the Encode method to provide a signature
// corresponding to the node's pubkey.
type MessageSigner struct {
// SignCompact signs the hash of the passed msg with the node's privkey.
// The returned signature should be 65 bytes, where the last 64 are the
// compact signature, and the first one is a header byte. This is the
// format returned by ecdsa.SignCompact.
SignCompact func(msg []byte) ([]byte, error)
}
// Invoice represents a decoded invoice, or to-be-encoded invoice. Some of the
// fields are optional, and will only be non-nil if the invoice this was parsed
// from contains that field. When encoding, only the non-nil fields will be
// added to the encoded invoice.
type Invoice struct {
// Net specifies what network this Lightning invoice is meant for.
Net *chaincfg.Params
// MilliSat specifies the amount of this invoice in millisatoshi.
// Optional.
MilliSat *lnwire.MilliSatoshi
// Timestamp specifies the time this invoice was created.
// Mandatory
Timestamp time.Time
// PaymentHash is the payment hash to be used for a payment to this
// invoice.
PaymentHash *[32]byte
// PaymentAddr is the payment address to be used by payments to prevent
// probing of the destination.
PaymentAddr fn.Option[[32]byte]
// Destination is the public key of the target node. This will always
// be set after decoding, and can optionally be set before encoding to
// include the pubkey as an 'n' field. If this is not set before
// encoding then the destination pubkey won't be added as an 'n' field,
// and the pubkey will be extracted from the signature during decoding.
Destination *btcec.PublicKey
// minFinalCLTVExpiry is the value that the creator of the invoice
// expects to be used for the CLTV expiry of the HTLC extended to it in
// the last hop.
//
// NOTE: This value is optional, and should be set to nil if the
// invoice creator doesn't have a strong requirement on the CLTV expiry
// of the final HTLC extended to it.
//
// This field is un-exported and can only be read by the
// MinFinalCLTVExpiry() method. By forcing callers to read via this
// method, we can easily enforce the default if not specified.
//
// NOTE: this field is ignored in the case that the invoice contains
// blinded paths since then the final minimum cltv expiry delta is
// expected to be included in the route's accumulated CLTV delta value.
minFinalCLTVExpiry *uint64
// Description is a short description of the purpose of this invoice.
// Optional. Non-nil iff DescriptionHash is nil.
Description *string
// DescriptionHash is the SHA256 hash of a description of the purpose of
// this invoice.
// Optional. Non-nil iff Description is nil.
DescriptionHash *[32]byte
// expiry specifies the timespan this invoice will be valid.
// Optional. If not set, a default expiry of 60 min will be implied.
//
// This field is unexported and can be read by the Expiry() method. This
// method makes sure the default expiry time is returned in case the
// field is not set.
expiry *time.Duration
// FallbackAddr is an on-chain address that can be used for payment in
// case the Lightning payment fails.
// Optional.
FallbackAddr btcutil.Address
// RouteHints represents one or more different route hints. Each route
// hint can be individually used to reach the destination. These usually
// represent private routes.
//
// NOTE: This is optional and should not be set at the same time as
// BlindedPaymentPaths.
RouteHints [][]HopHint
// BlindedPaymentPaths is a set of blinded payment paths that can be
// used to find the payment receiver.
//
// NOTE: This is optional and should not be set at the same time as
// RouteHints.
BlindedPaymentPaths []*BlindedPaymentPath
// Features represents an optional field used to signal optional or
// required support for features by the receiver.
Features *lnwire.FeatureVector
// Metadata is additional data that is sent along with the payment to
// the payee.
Metadata []byte
}
// Amount is a functional option that allows callers of NewInvoice to set the
// amount in millisatoshis that the Invoice should encode.
func Amount(milliSat lnwire.MilliSatoshi) func(*Invoice) {
return func(i *Invoice) {
i.MilliSat = &milliSat
}
}
// Destination is a functional option that allows callers of NewInvoice to
// explicitly set the pubkey of the Invoice's destination node.
func Destination(destination *btcec.PublicKey) func(*Invoice) {
return func(i *Invoice) {
i.Destination = destination
}
}
// Description is a functional option that allows callers of NewInvoice to set
// the payment description of the created Invoice.
//
// NOTE: Must be used if and only if DescriptionHash is not used.
func Description(description string) func(*Invoice) {
return func(i *Invoice) {
i.Description = &description
}
}
// CLTVExpiry is an optional value which allows the receiver of the payment to
// specify the delta between the current height and the HTLC extended to the
// receiver.
func CLTVExpiry(delta uint64) func(*Invoice) {
return func(i *Invoice) {
i.minFinalCLTVExpiry = &delta
}
}
// DescriptionHash is a functional option that allows callers of NewInvoice to
// set the payment description hash of the created Invoice.
//
// NOTE: Must be used if and only if Description is not used.
func DescriptionHash(descriptionHash [32]byte) func(*Invoice) {
return func(i *Invoice) {
i.DescriptionHash = &descriptionHash
}
}
// Expiry is a functional option that allows callers of NewInvoice to set the
// expiry of the created Invoice. If not set, a default expiry of 60 min will
// be implied.
func Expiry(expiry time.Duration) func(*Invoice) {
return func(i *Invoice) {
i.expiry = &expiry
}
}
// FallbackAddr is a functional option that allows callers of NewInvoice to set
// the Invoice's fallback on-chain address that can be used for payment in case
// the Lightning payment fails
func FallbackAddr(fallbackAddr btcutil.Address) func(*Invoice) {
return func(i *Invoice) {
i.FallbackAddr = fallbackAddr
}
}
// RouteHint is a functional option that allows callers of NewInvoice to add
// one or more hop hints that represent a private route to the destination.
func RouteHint(routeHint []HopHint) func(*Invoice) {
return func(i *Invoice) {
i.RouteHints = append(i.RouteHints, routeHint)
}
}
// WithBlindedPaymentPath is a functional option that allows a caller of
// NewInvoice to attach a blinded payment path to the invoice. The option can
// be used multiple times to attach multiple paths.
func WithBlindedPaymentPath(p *BlindedPaymentPath) func(*Invoice) {
return func(i *Invoice) {
i.BlindedPaymentPaths = append(i.BlindedPaymentPaths, p)
}
}
// Features is a functional option that allows callers of NewInvoice to set the
// desired feature bits that are advertised on the invoice. If this option is
// not used, an empty feature vector will automatically be populated.
func Features(features *lnwire.FeatureVector) func(*Invoice) {
return func(i *Invoice) {
i.Features = features
}
}
// PaymentAddr is a functional option that allows callers of NewInvoice to set
// the desired payment address that is advertised on the invoice.
func PaymentAddr(addr [32]byte) func(*Invoice) {
return func(i *Invoice) {
i.PaymentAddr = fn.Some(addr)
}
}
// Metadata is a functional option that allows callers of NewInvoice to set
// the desired payment Metadata that is advertised on the invoice.
func Metadata(metadata []byte) func(*Invoice) {
return func(i *Invoice) {
i.Metadata = metadata
}
}
// NewInvoice creates a new Invoice object. The last parameter is a set of
// variadic arguments for setting optional fields of the invoice.
//
// NOTE: Either Description or DescriptionHash must be provided for the Invoice
// to be considered valid.
func NewInvoice(net *chaincfg.Params, paymentHash [32]byte,
timestamp time.Time, options ...func(*Invoice)) (*Invoice, error) {
invoice := &Invoice{
Net: net,
PaymentHash: &paymentHash,
Timestamp: timestamp,
}
for _, option := range options {
option(invoice)
}
// If no features were set, we'll populate an empty feature vector.
if invoice.Features == nil {
invoice.Features = lnwire.NewFeatureVector(
nil, lnwire.Features,
)
}
if err := validateInvoice(invoice); err != nil {
return nil, err
}
return invoice, nil
}
// Expiry returns the expiry time for this invoice. If expiry time is not set
// explicitly, the default 3600 second expiry will be returned.
func (invoice *Invoice) Expiry() time.Duration {
if invoice.expiry != nil {
return *invoice.expiry
}
// If no expiry is set for this invoice, default is 3600 seconds.
return DefaultInvoiceExpiry
}
// MinFinalCLTVExpiry returns the minimum final CLTV expiry delta as specified
// by the creator of the invoice. This value specifies the delta between the
// current height and the expiry height of the HTLC extended in the last hop.
func (invoice *Invoice) MinFinalCLTVExpiry() uint64 {
if invoice.minFinalCLTVExpiry != nil {
return *invoice.minFinalCLTVExpiry
}
return DefaultAssumedFinalCLTVDelta
}
// validateInvoice does a sanity check of the provided Invoice, making sure it
// has all the necessary fields set for it to be considered valid by BOLT-0011.
func validateInvoice(invoice *Invoice) error {
// The net must be set.
if invoice.Net == nil {
return fmt.Errorf("net params not set")
}
// The invoice must contain a payment hash.
if invoice.PaymentHash == nil {
return fmt.Errorf("no payment hash found")
}
if len(invoice.RouteHints) != 0 &&
len(invoice.BlindedPaymentPaths) != 0 {
return fmt.Errorf("cannot have both route hints and blinded " +
"payment paths")
}
// Either Description or DescriptionHash must be set, not both.
if invoice.Description != nil && invoice.DescriptionHash != nil {
return fmt.Errorf("both description and description hash set")
}
if invoice.Description == nil && invoice.DescriptionHash == nil {
return fmt.Errorf("neither description nor description hash set")
}
// Check that we support the field lengths.
if len(invoice.PaymentHash) != 32 {
return fmt.Errorf("unsupported payment hash length: %d",
len(invoice.PaymentHash))
}
if invoice.DescriptionHash != nil && len(invoice.DescriptionHash) != 32 {
return fmt.Errorf("unsupported description hash length: %d",
len(invoice.DescriptionHash))
}
if invoice.Destination != nil &&
len(invoice.Destination.SerializeCompressed()) != 33 {
return fmt.Errorf("unsupported pubkey length: %d",
len(invoice.Destination.SerializeCompressed()))
}
// Ensure that all invoices have feature vectors.
if invoice.Features == nil {
return fmt.Errorf("missing feature vector")
}
return nil
}